diff options
Diffstat (limited to 'gfx/2d/QuartzSupport.mm')
-rw-r--r-- | gfx/2d/QuartzSupport.mm | 582 |
1 files changed, 582 insertions, 0 deletions
diff --git a/gfx/2d/QuartzSupport.mm b/gfx/2d/QuartzSupport.mm new file mode 100644 index 0000000000..91b754e380 --- /dev/null +++ b/gfx/2d/QuartzSupport.mm @@ -0,0 +1,582 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +// vim:set ts=2 sts=2 sw=2 et cin: +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "QuartzSupport.h" +#include "nsDebug.h" +#include "MacIOSurface.h" +#include "mozilla/Sprintf.h" + +#import <QuartzCore/QuartzCore.h> +#import <AppKit/NSOpenGL.h> +#import <OpenGL/CGLIOSurface.h> +#include <dlfcn.h> +#include "GLDefs.h" + +#define IOSURFACE_FRAMEWORK_PATH "/System/Library/Frameworks/IOSurface.framework/IOSurface" +#define OPENGL_FRAMEWORK_PATH "/System/Library/Frameworks/OpenGL.framework/OpenGL" +#define COREGRAPHICS_FRAMEWORK_PATH \ + "/System/Library/Frameworks/ApplicationServices.framework/Frameworks/CoreGraphics.framework/" \ + "CoreGraphics" + +@interface CALayer (ContentsScale) +- (double)contentsScale; +- (void)setContentsScale:(double)scale; +@end + +CGColorSpaceRef CreateSystemColorSpace() { + CGColorSpaceRef cspace = ::CGDisplayCopyColorSpace(::CGMainDisplayID()); + if (!cspace) { + cspace = ::CGColorSpaceCreateDeviceRGB(); + } + return cspace; +} + +nsCARenderer::~nsCARenderer() { Destroy(); } + +static void cgdata_release_callback(void* aCGData, const void* data, size_t size) { + if (aCGData) { + free(aCGData); + } +} + +void nsCARenderer::Destroy() { + if (mCARenderer) { + CARenderer* caRenderer = (CARenderer*)mCARenderer; + // Bug 556453: + // Explicitly remove the layer from the renderer + // otherwise it does not always happen right away. + caRenderer.layer = nullptr; + [caRenderer release]; + } + if (mWrapperCALayer) { + CALayer* wrapperLayer = (CALayer*)mWrapperCALayer; + [wrapperLayer release]; + } + if (mOpenGLContext) { + if (mFBO || mIOTexture || mFBOTexture) { + // Release these resources with the context that allocated them + CGLContextObj oldContext = ::CGLGetCurrentContext(); + ::CGLSetCurrentContext(mOpenGLContext); + + if (mFBOTexture) { + ::glDeleteTextures(1, &mFBOTexture); + } + if (mIOTexture) { + ::glDeleteTextures(1, &mIOTexture); + } + if (mFBO) { + ::glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, 0); + ::glDeleteFramebuffersEXT(1, &mFBO); + } + + if (oldContext) ::CGLSetCurrentContext(oldContext); + } + ::CGLDestroyContext((CGLContextObj)mOpenGLContext); + } + if (mCGImage) { + ::CGImageRelease(mCGImage); + } + // mCGData is deallocated by cgdata_release_callback + + mCARenderer = nil; + mWrapperCALayer = nil; + mFBOTexture = 0; + mOpenGLContext = nullptr; + mCGImage = nullptr; + mIOSurface = nullptr; + mFBO = 0; + mIOTexture = 0; +} + +nsresult nsCARenderer::SetupRenderer(void* aCALayer, int aWidth, int aHeight, + double aContentsScaleFactor, + AllowOfflineRendererEnum aAllowOfflineRenderer) { + mAllowOfflineRenderer = aAllowOfflineRenderer; + + if (aWidth == 0 || aHeight == 0 || aContentsScaleFactor <= 0) return NS_ERROR_FAILURE; + + if (aWidth == mUnsupportedWidth && aHeight == mUnsupportedHeight) { + return NS_ERROR_FAILURE; + } + + CGLPixelFormatAttribute attributes[] = {kCGLPFAAccelerated, kCGLPFADepthSize, + (CGLPixelFormatAttribute)24, kCGLPFAAllowOfflineRenderers, + (CGLPixelFormatAttribute)0}; + + if (mAllowOfflineRenderer == DISALLOW_OFFLINE_RENDERER) { + attributes[3] = (CGLPixelFormatAttribute)0; + } + + GLint screen; + CGLPixelFormatObj format; + if (::CGLChoosePixelFormat(attributes, &format, &screen) != kCGLNoError) { + mUnsupportedWidth = aWidth; + mUnsupportedHeight = aHeight; + Destroy(); + return NS_ERROR_FAILURE; + } + + if (::CGLCreateContext(format, nullptr, &mOpenGLContext) != kCGLNoError) { + mUnsupportedWidth = aWidth; + mUnsupportedHeight = aHeight; + Destroy(); + return NS_ERROR_FAILURE; + } + ::CGLDestroyPixelFormat(format); + + CARenderer* caRenderer = [[CARenderer rendererWithCGLContext:mOpenGLContext options:nil] retain]; + if (caRenderer == nil) { + mUnsupportedWidth = aWidth; + mUnsupportedHeight = aHeight; + Destroy(); + return NS_ERROR_FAILURE; + } + CALayer* wrapperCALayer = [[CALayer layer] retain]; + if (wrapperCALayer == nil) { + [caRenderer release]; + mUnsupportedWidth = aWidth; + mUnsupportedHeight = aHeight; + Destroy(); + return NS_ERROR_FAILURE; + } + + mCARenderer = caRenderer; + mWrapperCALayer = wrapperCALayer; + caRenderer.layer = wrapperCALayer; + [wrapperCALayer addSublayer:(CALayer*)aCALayer]; + mContentsScaleFactor = aContentsScaleFactor; + size_t intScaleFactor = ceil(mContentsScaleFactor); + SetBounds(aWidth, aHeight); + + // We target rendering to a CGImage if no shared IOSurface are given. + if (!mIOSurface) { + mCGData = malloc(aWidth * intScaleFactor * aHeight * 4 * intScaleFactor); + if (!mCGData) { + mUnsupportedWidth = aWidth; + mUnsupportedHeight = aHeight; + Destroy(); + return NS_ERROR_FAILURE; + } + memset(mCGData, 0, aWidth * intScaleFactor * aHeight * 4 * intScaleFactor); + + CGDataProviderRef dataProvider = nullptr; + dataProvider = ::CGDataProviderCreateWithData( + mCGData, mCGData, aHeight * intScaleFactor * aWidth * 4 * intScaleFactor, + cgdata_release_callback); + if (!dataProvider) { + cgdata_release_callback(mCGData, mCGData, + aHeight * intScaleFactor * aWidth * 4 * intScaleFactor); + mUnsupportedWidth = aWidth; + mUnsupportedHeight = aHeight; + Destroy(); + return NS_ERROR_FAILURE; + } + + CGColorSpaceRef colorSpace = CreateSystemColorSpace(); + + mCGImage = ::CGImageCreate(aWidth * intScaleFactor, aHeight * intScaleFactor, 8, 32, + aWidth * intScaleFactor * 4, colorSpace, + kCGImageAlphaPremultipliedFirst | kCGBitmapByteOrder32Host, + dataProvider, nullptr, true, kCGRenderingIntentDefault); + + ::CGDataProviderRelease(dataProvider); + ::CGColorSpaceRelease(colorSpace); + if (!mCGImage) { + mUnsupportedWidth = aWidth; + mUnsupportedHeight = aHeight; + Destroy(); + return NS_ERROR_FAILURE; + } + } + + CGLContextObj oldContext = ::CGLGetCurrentContext(); + ::CGLSetCurrentContext(mOpenGLContext); + + if (mIOSurface) { + // Create the IOSurface mapped texture. + ::glGenTextures(1, &mIOTexture); + ::glBindTexture(GL_TEXTURE_RECTANGLE_ARB, mIOTexture); + ::glTexParameteri(GL_TEXTURE_RECTANGLE_ARB, GL_TEXTURE_MIN_FILTER, GL_NEAREST); + ::glTexParameteri(GL_TEXTURE_RECTANGLE_ARB, GL_TEXTURE_MAG_FILTER, GL_NEAREST); + ::CGLTexImageIOSurface2D(mOpenGLContext, GL_TEXTURE_RECTANGLE_ARB, GL_RGBA, + aWidth * intScaleFactor, aHeight * intScaleFactor, GL_BGRA, + GL_UNSIGNED_INT_8_8_8_8_REV, mIOSurface->GetIOSurfaceRef().get(), 0); + ::glBindTexture(GL_TEXTURE_RECTANGLE_ARB, 0); + } else { + ::glGenTextures(1, &mFBOTexture); + ::glBindTexture(GL_TEXTURE_RECTANGLE_ARB, mFBOTexture); + ::glTexParameteri(GL_TEXTURE_RECTANGLE_ARB, GL_TEXTURE_MIN_FILTER, GL_NEAREST); + ::glTexParameteri(GL_TEXTURE_RECTANGLE_ARB, GL_TEXTURE_MAG_FILTER, GL_NEAREST); + ::glBindTexture(GL_TEXTURE_RECTANGLE_ARB, 0); + } + + // Create the fbo + ::glGenFramebuffersEXT(1, &mFBO); + ::glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, mFBO); + if (mIOSurface) { + ::glFramebufferTexture2DEXT(GL_FRAMEBUFFER_EXT, GL_COLOR_ATTACHMENT0_EXT, + GL_TEXTURE_RECTANGLE_ARB, mIOTexture, 0); + } else { + ::glFramebufferTexture2DEXT(GL_FRAMEBUFFER_EXT, GL_COLOR_ATTACHMENT0_EXT, + GL_TEXTURE_RECTANGLE_ARB, mFBOTexture, 0); + } + + // Make sure that the Framebuffer configuration is supported on the client machine + GLenum fboStatus; + fboStatus = ::glCheckFramebufferStatusEXT(GL_FRAMEBUFFER_EXT); + if (fboStatus != GL_FRAMEBUFFER_COMPLETE_EXT) { + NS_ERROR("FBO not supported"); + if (oldContext) ::CGLSetCurrentContext(oldContext); + mUnsupportedWidth = aWidth; + mUnsupportedHeight = aHeight; + Destroy(); + return NS_ERROR_FAILURE; + } + + SetViewport(aWidth, aHeight); + + GLenum result = ::glGetError(); + if (result != GL_NO_ERROR) { + NS_ERROR("Unexpected OpenGL Error"); + mUnsupportedWidth = aWidth; + mUnsupportedHeight = aHeight; + Destroy(); + if (oldContext) ::CGLSetCurrentContext(oldContext); + return NS_ERROR_FAILURE; + } + + if (oldContext) ::CGLSetCurrentContext(oldContext); + + return NS_OK; +} + +void nsCARenderer::SetBounds(int aWidth, int aHeight) { + CARenderer* caRenderer = (CARenderer*)mCARenderer; + CALayer* wrapperLayer = (CALayer*)mWrapperCALayer; + NSArray* sublayers = [wrapperLayer sublayers]; + CALayer* pluginLayer = (CALayer*)[sublayers objectAtIndex:0]; + + // Create a transaction and disable animations + // to make the position update instant. + [CATransaction begin]; + NSMutableDictionary* newActions = [[NSMutableDictionary alloc] + initWithObjectsAndKeys:[NSNull null], @"onOrderIn", [NSNull null], @"onOrderOut", + [NSNull null], @"sublayers", [NSNull null], @"contents", [NSNull null], + @"position", [NSNull null], @"bounds", nil]; + wrapperLayer.actions = newActions; + [newActions release]; + + // If we're in HiDPI mode, mContentsScaleFactor will (presumably) be 2.0. + // For some reason, to make things work properly in HiDPI mode we need to + // make caRenderer's 'bounds' and 'layer' different sizes -- to set 'bounds' + // to the size of 'layer's backing store. And to avoid this possibly + // confusing the plugin, we need to hide it's effects from the plugin by + // making pluginLayer (usually the CALayer* provided by the plugin) a + // sublayer of our own wrapperLayer (see bug 829284). + size_t intScaleFactor = ceil(mContentsScaleFactor); + [CATransaction setValue:[NSNumber numberWithFloat:0.0f] forKey:kCATransactionAnimationDuration]; + [CATransaction setValue:(id)kCFBooleanTrue forKey:kCATransactionDisableActions]; + [wrapperLayer setBounds:CGRectMake(0, 0, aWidth, aHeight)]; + [wrapperLayer setPosition:CGPointMake(aWidth / 2.0, aHeight / 2.0)]; + [pluginLayer setBounds:CGRectMake(0, 0, aWidth, aHeight)]; + [pluginLayer setFrame:CGRectMake(0, 0, aWidth, aHeight)]; + caRenderer.bounds = CGRectMake(0, 0, aWidth * intScaleFactor, aHeight * intScaleFactor); + if (mContentsScaleFactor != 1.0) { + CGAffineTransform affineTransform = [wrapperLayer affineTransform]; + affineTransform.a = mContentsScaleFactor; + affineTransform.d = mContentsScaleFactor; + affineTransform.tx = ((double)aWidth) / mContentsScaleFactor; + affineTransform.ty = ((double)aHeight) / mContentsScaleFactor; + [wrapperLayer setAffineTransform:affineTransform]; + } else { + // These settings are the default values. But they might have been + // changed as above if we were previously running in a HiDPI mode + // (i.e. if we just switched from that to a non-HiDPI mode). + [wrapperLayer setAffineTransform:CGAffineTransformIdentity]; + } + [CATransaction commit]; +} + +void nsCARenderer::SetViewport(int aWidth, int aHeight) { + size_t intScaleFactor = ceil(mContentsScaleFactor); + aWidth *= intScaleFactor; + aHeight *= intScaleFactor; + + ::glViewport(0.0, 0.0, aWidth, aHeight); + ::glMatrixMode(GL_PROJECTION); + ::glLoadIdentity(); + ::glOrtho(0.0, aWidth, 0.0, aHeight, -1, 1); + + // Render upside down to speed up CGContextDrawImage + ::glTranslatef(0.0f, aHeight, 0.0); + ::glScalef(1.0, -1.0, 1.0); +} + +void nsCARenderer::AttachIOSurface(MacIOSurface* aSurface) { + if (mIOSurface && aSurface->GetIOSurfaceID() == mIOSurface->GetIOSurfaceID()) { + return; + } + + mIOSurface = aSurface; + + // Update the framebuffer and viewport + if (mCARenderer) { + CARenderer* caRenderer = (CARenderer*)mCARenderer; + size_t intScaleFactor = ceil(mContentsScaleFactor); + int width = caRenderer.bounds.size.width / intScaleFactor; + int height = caRenderer.bounds.size.height / intScaleFactor; + + CGLContextObj oldContext = ::CGLGetCurrentContext(); + ::CGLSetCurrentContext(mOpenGLContext); + ::glBindTexture(GL_TEXTURE_RECTANGLE_ARB, mIOTexture); + ::CGLTexImageIOSurface2D(mOpenGLContext, GL_TEXTURE_RECTANGLE_ARB, GL_RGBA, + mIOSurface->GetDevicePixelWidth(), mIOSurface->GetDevicePixelHeight(), + GL_BGRA, GL_UNSIGNED_INT_8_8_8_8_REV, + mIOSurface->GetIOSurfaceRef().get(), 0); + ::glBindTexture(GL_TEXTURE_RECTANGLE_ARB, 0); + + // Rebind the FBO to make it live + ::glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, mFBO); + + if (static_cast<int>(mIOSurface->GetWidth()) != width || + static_cast<int>(mIOSurface->GetHeight()) != height) { + width = mIOSurface->GetWidth(); + height = mIOSurface->GetHeight(); + SetBounds(width, height); + SetViewport(width, height); + } + + if (oldContext) { + ::CGLSetCurrentContext(oldContext); + } + } +} + +IOSurfaceID nsCARenderer::GetIOSurfaceID() { + if (!mIOSurface) { + return 0; + } + + return mIOSurface->GetIOSurfaceID(); +} + +nsresult nsCARenderer::Render(int aWidth, int aHeight, double aContentsScaleFactor, + CGImageRef* aOutCGImage) { + if (!aOutCGImage && !mIOSurface) { + NS_ERROR("No target destination for rendering"); + } else if (aOutCGImage) { + // We are expected to return a CGImageRef, we will set + // it to nullptr in case we fail before the image is ready. + *aOutCGImage = nullptr; + } + + if (aWidth == 0 || aHeight == 0 || aContentsScaleFactor <= 0) return NS_OK; + + if (!mCARenderer || !mWrapperCALayer) { + return NS_ERROR_FAILURE; + } + + CARenderer* caRenderer = (CARenderer*)mCARenderer; + CALayer* wrapperLayer = (CALayer*)mWrapperCALayer; + size_t intScaleFactor = ceil(aContentsScaleFactor); + int renderer_width = caRenderer.bounds.size.width / intScaleFactor; + int renderer_height = caRenderer.bounds.size.height / intScaleFactor; + + if (renderer_width != aWidth || renderer_height != aHeight || + mContentsScaleFactor != aContentsScaleFactor) { + // XXX: This should be optimized to not rescale the buffer + // if we are resizing down. + // caLayer may be the CALayer* provided by the plugin, so we need to + // preserve it across the call to Destroy(). + NSArray* sublayers = [wrapperLayer sublayers]; + CALayer* caLayer = (CALayer*)[sublayers objectAtIndex:0]; + // mIOSurface is set by AttachIOSurface(), not by SetupRenderer(). So + // since it may have been set by a prior call to AttachIOSurface(), we + // need to preserve it across the call to Destroy(). + RefPtr<MacIOSurface> ioSurface = mIOSurface; + Destroy(); + mIOSurface = ioSurface; + if (SetupRenderer(caLayer, aWidth, aHeight, aContentsScaleFactor, mAllowOfflineRenderer) != + NS_OK) { + return NS_ERROR_FAILURE; + } + + caRenderer = (CARenderer*)mCARenderer; + } + + CGLContextObj oldContext = ::CGLGetCurrentContext(); + ::CGLSetCurrentContext(mOpenGLContext); + if (!mIOSurface) { + // If no shared IOSurface is given render to our own + // texture for readback. + ::glGenTextures(1, &mFBOTexture); + } + + GLenum result = ::glGetError(); + if (result != GL_NO_ERROR) { + NS_ERROR("Unexpected OpenGL Error"); + Destroy(); + if (oldContext) ::CGLSetCurrentContext(oldContext); + return NS_ERROR_FAILURE; + } + + ::glClearColor(0.0, 0.0, 0.0, 0.0); + ::glClear(GL_COLOR_BUFFER_BIT); + + [CATransaction commit]; + double caTime = ::CACurrentMediaTime(); + [caRenderer beginFrameAtTime:caTime timeStamp:nullptr]; + [caRenderer addUpdateRect:CGRectMake(0, 0, aWidth * intScaleFactor, aHeight * intScaleFactor)]; + [caRenderer render]; + [caRenderer endFrame]; + + // Read the data back either to the IOSurface or mCGImage. + if (mIOSurface) { + ::glFlush(); + } else { + ::glPixelStorei(GL_PACK_ALIGNMENT, 4); + ::glPixelStorei(GL_PACK_ROW_LENGTH, 0); + ::glPixelStorei(GL_PACK_SKIP_ROWS, 0); + ::glPixelStorei(GL_PACK_SKIP_PIXELS, 0); + + ::glReadPixels(0.0f, 0.0f, aWidth * intScaleFactor, aHeight * intScaleFactor, GL_BGRA, + GL_UNSIGNED_BYTE, mCGData); + + *aOutCGImage = mCGImage; + } + + if (oldContext) { + ::CGLSetCurrentContext(oldContext); + } + + return NS_OK; +} + +nsresult nsCARenderer::DrawSurfaceToCGContext(CGContextRef aContext, MacIOSurface* surf, + CGColorSpaceRef aColorSpace, int aX, int aY, + size_t aWidth, size_t aHeight) { + surf->Lock(); + size_t bytesPerRow = surf->GetBytesPerRow(); + size_t ioWidth = surf->GetWidth(); + size_t ioHeight = surf->GetHeight(); + + // We get rendering glitches if we use a width/height that falls + // outside of the IOSurface. + if (aWidth + aX > ioWidth) aWidth = ioWidth - aX; + if (aHeight + aY > ioHeight) aHeight = ioHeight - aY; + + if (aX < 0 || static_cast<size_t>(aX) >= ioWidth || aY < 0 || + static_cast<size_t>(aY) >= ioHeight) { + surf->Unlock(); + return NS_ERROR_FAILURE; + } + + void* ioData = surf->GetBaseAddress(); + CGDataProviderRef dataProvider = + ::CGDataProviderCreateWithData(ioData, ioData, ioHeight * (bytesPerRow)*4, + nullptr); // No release callback + if (!dataProvider) { + surf->Unlock(); + return NS_ERROR_FAILURE; + } + + CGImageRef cgImage = ::CGImageCreate(ioWidth, ioHeight, 8, 32, bytesPerRow, aColorSpace, + kCGImageAlphaPremultipliedFirst | kCGBitmapByteOrder32Host, + dataProvider, nullptr, true, kCGRenderingIntentDefault); + ::CGDataProviderRelease(dataProvider); + if (!cgImage) { + surf->Unlock(); + return NS_ERROR_FAILURE; + } + CGImageRef subImage = + ::CGImageCreateWithImageInRect(cgImage, ::CGRectMake(aX, aY, aWidth, aHeight)); + if (!subImage) { + ::CGImageRelease(cgImage); + surf->Unlock(); + return NS_ERROR_FAILURE; + } + + ::CGContextScaleCTM(aContext, 1.0f, -1.0f); + ::CGContextDrawImage(aContext, CGRectMake(aX, -(CGFloat)aY - (CGFloat)aHeight, aWidth, aHeight), + subImage); + + ::CGImageRelease(subImage); + ::CGImageRelease(cgImage); + surf->Unlock(); + return NS_OK; +} + +void nsCARenderer::DetachCALayer() { + CALayer* wrapperLayer = (CALayer*)mWrapperCALayer; + NSArray* sublayers = [wrapperLayer sublayers]; + CALayer* oldLayer = (CALayer*)[sublayers objectAtIndex:0]; + [oldLayer removeFromSuperlayer]; +} + +void nsCARenderer::AttachCALayer(void* aCALayer) { + CALayer* wrapperLayer = (CALayer*)mWrapperCALayer; + NSArray* sublayers = [wrapperLayer sublayers]; + CALayer* oldLayer = (CALayer*)[sublayers objectAtIndex:0]; + [oldLayer removeFromSuperlayer]; + [wrapperLayer addSublayer:(CALayer*)aCALayer]; +} + +#ifdef DEBUG + +int sSaveToDiskSequence = 0; +void nsCARenderer::SaveToDisk(MacIOSurface* surf) { + surf->Lock(); + size_t bytesPerRow = surf->GetBytesPerRow(); + size_t ioWidth = surf->GetWidth(); + size_t ioHeight = surf->GetHeight(); + void* ioData = surf->GetBaseAddress(); + CGDataProviderRef dataProvider = + ::CGDataProviderCreateWithData(ioData, ioData, ioHeight * (bytesPerRow)*4, + nullptr); // No release callback + if (!dataProvider) { + surf->Unlock(); + return; + } + + CGColorSpaceRef colorSpace = CreateSystemColorSpace(); + CGImageRef cgImage = ::CGImageCreate(ioWidth, ioHeight, 8, 32, bytesPerRow, colorSpace, + kCGImageAlphaPremultipliedFirst | kCGBitmapByteOrder32Host, + dataProvider, nullptr, true, kCGRenderingIntentDefault); + ::CGDataProviderRelease(dataProvider); + ::CGColorSpaceRelease(colorSpace); + if (!cgImage) { + surf->Unlock(); + return; + } + + char cstr[1000]; + SprintfLiteral(cstr, "file:///Users/benoitgirard/debug/iosurface_%i.png", ++sSaveToDiskSequence); + + CFStringRef cfStr = + ::CFStringCreateWithCString(kCFAllocatorDefault, cstr, kCFStringEncodingMacRoman); + + printf("Exporting: %s\n", cstr); + CFURLRef url = ::CFURLCreateWithString(nullptr, cfStr, nullptr); + ::CFRelease(cfStr); + + CFStringRef type = kUTTypePNG; + size_t count = 1; + CFDictionaryRef options = nullptr; + CGImageDestinationRef dest = ::CGImageDestinationCreateWithURL(url, type, count, options); + ::CFRelease(url); + + ::CGImageDestinationAddImage(dest, cgImage, nullptr); + + ::CGImageDestinationFinalize(dest); + ::CFRelease(dest); + ::CGImageRelease(cgImage); + + surf->Unlock(); + + return; +} + +#endif |