1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
3 /* This Source Code Form is subject to the terms of the Mozilla Public
4  * License, v. 2.0. If a copy of the MPL was not distributed with this
5  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
6 
7 #include "MacIOSurface.h"
8 #include <OpenGL/gl.h>
9 #include <OpenGL/CGLIOSurface.h>
10 #include <QuartzCore/QuartzCore.h>
11 #include "GLConsts.h"
12 #include "GLContextCGL.h"
13 #include "mozilla/Assertions.h"
14 #include "mozilla/RefPtr.h"
15 
16 using namespace mozilla;
17 
MacIOSurface(CFTypeRefPtr<IOSurfaceRef> aIOSurfaceRef,double aContentsScaleFactor,bool aHasAlpha,gfx::YUVColorSpace aColorSpace)18 MacIOSurface::MacIOSurface(CFTypeRefPtr<IOSurfaceRef> aIOSurfaceRef,
19                            double aContentsScaleFactor, bool aHasAlpha,
20                            gfx::YUVColorSpace aColorSpace)
21     : mIOSurfaceRef(std::move(aIOSurfaceRef)),
22       mContentsScaleFactor(aContentsScaleFactor),
23       mHasAlpha(aHasAlpha),
24       mColorSpace(aColorSpace) {
25   IncrementUseCount();
26 }
27 
~MacIOSurface()28 MacIOSurface::~MacIOSurface() {
29   MOZ_RELEASE_ASSERT(!IsLocked(), "Destroying locked surface");
30   DecrementUseCount();
31 }
32 
33 /* static */
CreateIOSurface(int aWidth,int aHeight,double aContentsScaleFactor,bool aHasAlpha)34 already_AddRefed<MacIOSurface> MacIOSurface::CreateIOSurface(
35     int aWidth, int aHeight, double aContentsScaleFactor, bool aHasAlpha) {
36   if (aContentsScaleFactor <= 0) return nullptr;
37 
38   CFMutableDictionaryRef props = ::CFDictionaryCreateMutable(
39       kCFAllocatorDefault, 4, &kCFTypeDictionaryKeyCallBacks,
40       &kCFTypeDictionaryValueCallBacks);
41   if (!props) return nullptr;
42 
43   MOZ_ASSERT((size_t)aWidth <= GetMaxWidth());
44   MOZ_ASSERT((size_t)aHeight <= GetMaxHeight());
45 
46   int32_t bytesPerElem = 4;
47   size_t intScaleFactor = ceil(aContentsScaleFactor);
48   aWidth *= intScaleFactor;
49   aHeight *= intScaleFactor;
50   CFNumberRef cfWidth = ::CFNumberCreate(nullptr, kCFNumberSInt32Type, &aWidth);
51   CFNumberRef cfHeight =
52       ::CFNumberCreate(nullptr, kCFNumberSInt32Type, &aHeight);
53   CFNumberRef cfBytesPerElem =
54       ::CFNumberCreate(nullptr, kCFNumberSInt32Type, &bytesPerElem);
55   ::CFDictionaryAddValue(props, kIOSurfaceWidth, cfWidth);
56   ::CFRelease(cfWidth);
57   ::CFDictionaryAddValue(props, kIOSurfaceHeight, cfHeight);
58   ::CFRelease(cfHeight);
59   ::CFDictionaryAddValue(props, kIOSurfaceBytesPerElement, cfBytesPerElem);
60   ::CFRelease(cfBytesPerElem);
61   ::CFDictionaryAddValue(props, kIOSurfaceIsGlobal, kCFBooleanTrue);
62 
63   CFTypeRefPtr<IOSurfaceRef> surfaceRef =
64       CFTypeRefPtr<IOSurfaceRef>::WrapUnderCreateRule(::IOSurfaceCreate(props));
65   ::CFRelease(props);
66 
67   if (!surfaceRef) {
68     return nullptr;
69   }
70 
71   RefPtr<MacIOSurface> ioSurface =
72       new MacIOSurface(std::move(surfaceRef), aContentsScaleFactor, aHasAlpha);
73 
74   return ioSurface.forget();
75 }
76 
77 /* static */
LookupSurface(IOSurfaceID aIOSurfaceID,double aContentsScaleFactor,bool aHasAlpha,gfx::YUVColorSpace aColorSpace)78 already_AddRefed<MacIOSurface> MacIOSurface::LookupSurface(
79     IOSurfaceID aIOSurfaceID, double aContentsScaleFactor, bool aHasAlpha,
80     gfx::YUVColorSpace aColorSpace) {
81   if (aContentsScaleFactor <= 0) return nullptr;
82 
83   CFTypeRefPtr<IOSurfaceRef> surfaceRef =
84       CFTypeRefPtr<IOSurfaceRef>::WrapUnderCreateRule(
85           ::IOSurfaceLookup(aIOSurfaceID));
86   if (!surfaceRef) return nullptr;
87 
88   RefPtr<MacIOSurface> ioSurface = new MacIOSurface(
89       std::move(surfaceRef), aContentsScaleFactor, aHasAlpha, aColorSpace);
90 
91   return ioSurface.forget();
92 }
93 
GetIOSurfaceID() const94 IOSurfaceID MacIOSurface::GetIOSurfaceID() const {
95   return ::IOSurfaceGetID(mIOSurfaceRef.get());
96 }
97 
GetBaseAddress() const98 void* MacIOSurface::GetBaseAddress() const {
99   return ::IOSurfaceGetBaseAddress(mIOSurfaceRef.get());
100 }
101 
GetBaseAddressOfPlane(size_t aPlaneIndex) const102 void* MacIOSurface::GetBaseAddressOfPlane(size_t aPlaneIndex) const {
103   return ::IOSurfaceGetBaseAddressOfPlane(mIOSurfaceRef.get(), aPlaneIndex);
104 }
105 
GetWidth(size_t plane) const106 size_t MacIOSurface::GetWidth(size_t plane) const {
107   size_t intScaleFactor = ceil(mContentsScaleFactor);
108   return GetDevicePixelWidth(plane) / intScaleFactor;
109 }
110 
GetHeight(size_t plane) const111 size_t MacIOSurface::GetHeight(size_t plane) const {
112   size_t intScaleFactor = ceil(mContentsScaleFactor);
113   return GetDevicePixelHeight(plane) / intScaleFactor;
114 }
115 
GetPlaneCount() const116 size_t MacIOSurface::GetPlaneCount() const {
117   return ::IOSurfaceGetPlaneCount(mIOSurfaceRef.get());
118 }
119 
120 /*static*/
GetMaxWidth()121 size_t MacIOSurface::GetMaxWidth() {
122   return ::IOSurfaceGetPropertyMaximum(kIOSurfaceWidth);
123 }
124 
125 /*static*/
GetMaxHeight()126 size_t MacIOSurface::GetMaxHeight() {
127   return ::IOSurfaceGetPropertyMaximum(kIOSurfaceHeight);
128 }
129 
GetDevicePixelWidth(size_t plane) const130 size_t MacIOSurface::GetDevicePixelWidth(size_t plane) const {
131   return ::IOSurfaceGetWidthOfPlane(mIOSurfaceRef.get(), plane);
132 }
133 
GetDevicePixelHeight(size_t plane) const134 size_t MacIOSurface::GetDevicePixelHeight(size_t plane) const {
135   return ::IOSurfaceGetHeightOfPlane(mIOSurfaceRef.get(), plane);
136 }
137 
GetBytesPerRow(size_t plane) const138 size_t MacIOSurface::GetBytesPerRow(size_t plane) const {
139   return ::IOSurfaceGetBytesPerRowOfPlane(mIOSurfaceRef.get(), plane);
140 }
141 
GetPixelFormat() const142 OSType MacIOSurface::GetPixelFormat() const {
143   return ::IOSurfaceGetPixelFormat(mIOSurfaceRef.get());
144 }
145 
IncrementUseCount()146 void MacIOSurface::IncrementUseCount() {
147   ::IOSurfaceIncrementUseCount(mIOSurfaceRef.get());
148 }
149 
DecrementUseCount()150 void MacIOSurface::DecrementUseCount() {
151   ::IOSurfaceDecrementUseCount(mIOSurfaceRef.get());
152 }
153 
Lock(bool aReadOnly)154 void MacIOSurface::Lock(bool aReadOnly) {
155   MOZ_RELEASE_ASSERT(!mIsLocked, "double MacIOSurface lock");
156   ::IOSurfaceLock(mIOSurfaceRef.get(), aReadOnly ? kIOSurfaceLockReadOnly : 0,
157                   nullptr);
158   mIsLocked = true;
159 }
160 
Unlock(bool aReadOnly)161 void MacIOSurface::Unlock(bool aReadOnly) {
162   MOZ_RELEASE_ASSERT(mIsLocked, "MacIOSurface unlock without being locked");
163   ::IOSurfaceUnlock(mIOSurfaceRef.get(), aReadOnly ? kIOSurfaceLockReadOnly : 0,
164                     nullptr);
165   mIsLocked = false;
166 }
167 
168 using mozilla::gfx::IntSize;
169 using mozilla::gfx::SourceSurface;
170 using mozilla::gfx::SurfaceFormat;
171 
MacIOSurfaceBufferDeallocator(void * aClosure)172 static void MacIOSurfaceBufferDeallocator(void* aClosure) {
173   MOZ_ASSERT(aClosure);
174 
175   delete[] static_cast<unsigned char*>(aClosure);
176 }
177 
GetAsSurface()178 already_AddRefed<SourceSurface> MacIOSurface::GetAsSurface() {
179   Lock();
180   size_t bytesPerRow = GetBytesPerRow();
181   size_t ioWidth = GetDevicePixelWidth();
182   size_t ioHeight = GetDevicePixelHeight();
183 
184   unsigned char* ioData = (unsigned char*)GetBaseAddress();
185   auto* dataCpy =
186       new unsigned char[bytesPerRow * ioHeight / sizeof(unsigned char)];
187   for (size_t i = 0; i < ioHeight; i++) {
188     memcpy(dataCpy + i * bytesPerRow, ioData + i * bytesPerRow, ioWidth * 4);
189   }
190 
191   Unlock();
192 
193   SurfaceFormat format = HasAlpha() ? mozilla::gfx::SurfaceFormat::B8G8R8A8
194                                     : mozilla::gfx::SurfaceFormat::B8G8R8X8;
195 
196   RefPtr<mozilla::gfx::DataSourceSurface> surf =
197       mozilla::gfx::Factory::CreateWrappingDataSourceSurface(
198           dataCpy, bytesPerRow, IntSize(ioWidth, ioHeight), format,
199           &MacIOSurfaceBufferDeallocator, static_cast<void*>(dataCpy));
200 
201   return surf.forget();
202 }
203 
GetAsDrawTargetLocked(mozilla::gfx::BackendType aBackendType)204 already_AddRefed<mozilla::gfx::DrawTarget> MacIOSurface::GetAsDrawTargetLocked(
205     mozilla::gfx::BackendType aBackendType) {
206   MOZ_RELEASE_ASSERT(
207       IsLocked(),
208       "Only call GetAsDrawTargetLocked while the surface is locked.");
209 
210   size_t bytesPerRow = GetBytesPerRow();
211   size_t ioWidth = GetDevicePixelWidth();
212   size_t ioHeight = GetDevicePixelHeight();
213   unsigned char* ioData = (unsigned char*)GetBaseAddress();
214   SurfaceFormat format = GetFormat();
215   return mozilla::gfx::Factory::CreateDrawTargetForData(
216       aBackendType, ioData, IntSize(ioWidth, ioHeight), bytesPerRow, format);
217 }
218 
GetFormat() const219 SurfaceFormat MacIOSurface::GetFormat() const {
220   switch (GetPixelFormat()) {
221     case kCVPixelFormatType_420YpCbCr8BiPlanarVideoRange:
222     case kCVPixelFormatType_420YpCbCr8BiPlanarFullRange:
223       return SurfaceFormat::NV12;
224     case kCVPixelFormatType_422YpCbCr8:
225       return SurfaceFormat::YUV422;
226     case kCVPixelFormatType_32BGRA:
227       return HasAlpha() ? SurfaceFormat::B8G8R8A8 : SurfaceFormat::B8G8R8X8;
228     default:
229       return HasAlpha() ? SurfaceFormat::R8G8B8A8 : SurfaceFormat::R8G8B8X8;
230   }
231 }
232 
GetReadFormat() const233 SurfaceFormat MacIOSurface::GetReadFormat() const {
234   SurfaceFormat format = GetFormat();
235   if (format == SurfaceFormat::YUV422) {
236     return SurfaceFormat::R8G8B8X8;
237   }
238   return format;
239 }
240 
CGLTexImageIOSurface2D(CGLContextObj ctx,GLenum target,GLenum internalFormat,GLsizei width,GLsizei height,GLenum format,GLenum type,GLuint plane) const241 CGLError MacIOSurface::CGLTexImageIOSurface2D(CGLContextObj ctx, GLenum target,
242                                               GLenum internalFormat,
243                                               GLsizei width, GLsizei height,
244                                               GLenum format, GLenum type,
245                                               GLuint plane) const {
246   return ::CGLTexImageIOSurface2D(ctx, target, internalFormat, width, height,
247                                   format, type, mIOSurfaceRef.get(), plane);
248 }
249 
CGLTexImageIOSurface2D(mozilla::gl::GLContext * aGL,CGLContextObj ctx,size_t plane,mozilla::gfx::SurfaceFormat * aOutReadFormat)250 CGLError MacIOSurface::CGLTexImageIOSurface2D(
251     mozilla::gl::GLContext* aGL, CGLContextObj ctx, size_t plane,
252     mozilla::gfx::SurfaceFormat* aOutReadFormat) {
253   MOZ_ASSERT(plane >= 0);
254   bool isCompatibilityProfile = aGL->IsCompatibilityProfile();
255   OSType pixelFormat = GetPixelFormat();
256 
257   GLenum internalFormat;
258   GLenum format;
259   GLenum type;
260   if (pixelFormat == kCVPixelFormatType_420YpCbCr8BiPlanarVideoRange ||
261       pixelFormat == kCVPixelFormatType_420YpCbCr8BiPlanarFullRange) {
262     MOZ_ASSERT(GetPlaneCount() == 2);
263     MOZ_ASSERT(plane < 2);
264 
265     // The LOCAL_GL_LUMINANCE and LOCAL_GL_LUMINANCE_ALPHA are the deprecated
266     // format. So, use LOCAL_GL_RED and LOCAL_GL_RB if we use core profile.
267     // https://www.khronos.org/opengl/wiki/Image_Format#Legacy_Image_Formats
268     if (plane == 0) {
269       internalFormat = format =
270           (isCompatibilityProfile) ? (LOCAL_GL_LUMINANCE) : (LOCAL_GL_RED);
271     } else {
272       internalFormat = format =
273           (isCompatibilityProfile) ? (LOCAL_GL_LUMINANCE_ALPHA) : (LOCAL_GL_RG);
274     }
275     type = LOCAL_GL_UNSIGNED_BYTE;
276     if (aOutReadFormat) {
277       *aOutReadFormat = mozilla::gfx::SurfaceFormat::NV12;
278     }
279   } else if (pixelFormat == kCVPixelFormatType_422YpCbCr8) {
280     MOZ_ASSERT(plane == 0);
281     // The YCBCR_422_APPLE ext is only available in compatibility profile. So,
282     // we should use RGB_422_APPLE for core profile. The difference between
283     // YCBCR_422_APPLE and RGB_422_APPLE is that the YCBCR_422_APPLE converts
284     // the YCbCr value to RGB with REC 601 conversion. But the RGB_422_APPLE
285     // doesn't contain color conversion. You should do the color conversion by
286     // yourself for RGB_422_APPLE.
287     //
288     // https://www.khronos.org/registry/OpenGL/extensions/APPLE/APPLE_ycbcr_422.txt
289     // https://www.khronos.org/registry/OpenGL/extensions/APPLE/APPLE_rgb_422.txt
290     if (isCompatibilityProfile) {
291       format = LOCAL_GL_YCBCR_422_APPLE;
292       if (aOutReadFormat) {
293         *aOutReadFormat = mozilla::gfx::SurfaceFormat::R8G8B8X8;
294       }
295     } else {
296       format = LOCAL_GL_RGB_422_APPLE;
297       if (aOutReadFormat) {
298         *aOutReadFormat = mozilla::gfx::SurfaceFormat::YUV422;
299       }
300     }
301     internalFormat = LOCAL_GL_RGB;
302     type = LOCAL_GL_UNSIGNED_SHORT_8_8_APPLE;
303   } else {
304     MOZ_ASSERT(plane == 0);
305 
306     internalFormat = HasAlpha() ? LOCAL_GL_RGBA : LOCAL_GL_RGB;
307     format = LOCAL_GL_BGRA;
308     type = LOCAL_GL_UNSIGNED_INT_8_8_8_8_REV;
309     if (aOutReadFormat) {
310       *aOutReadFormat = HasAlpha() ? mozilla::gfx::SurfaceFormat::R8G8B8A8
311                                    : mozilla::gfx::SurfaceFormat::R8G8B8X8;
312     }
313   }
314 
315   return CGLTexImageIOSurface2D(ctx, LOCAL_GL_TEXTURE_RECTANGLE_ARB,
316                                 internalFormat, GetDevicePixelWidth(plane),
317                                 GetDevicePixelHeight(plane), format, type,
318                                 plane);
319 }
320