/* -*- 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"

#import <QuartzCore/QuartzCore.h>
#import <AppKit/NSOpenGL.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

using mozilla::RefPtr;
using mozilla::TemporaryRef;

CGColorSpaceRef CreateSystemColorSpace() {
  CGColorSpaceRef cspace = ::CGDisplayCopyColorSpace(::CGMainDisplayID());
  if (!cspace) {
    cspace = ::CGColorSpaceCreateDeviceRGB();
  }
  return cspace;
}

nsCARenderer::~nsCARenderer() {
  Destroy();
}

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);
    MacIOSurfaceLib::CGLTexImageIOSurface2D(mOpenGLContext, GL_TEXTURE_RECTANGLE_ARB,
                                           GL_RGBA, aWidth * intScaleFactor,
                                           aHeight * intScaleFactor,
                                           GL_BGRA, GL_UNSIGNED_INT_8_8_8_8_REV,
                                           mIOSurface->mIOSurfacePtr, 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(RefPtr<MacIOSurface> aSurface) {
  if (mIOSurface &&
      aSurface->GetIOSurfaceID() == mIOSurface->GetIOSurfaceID()) {
    // This object isn't needed since we already have a
    // handle to the same io surface.
    aSurface = nullptr;
    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);
    MacIOSurfaceLib::CGLTexImageIOSurface2D(mOpenGLContext, GL_TEXTURE_RECTANGLE_ARB,
                                           GL_RGBA, mIOSurface->GetDevicePixelWidth(),
                                           mIOSurface->GetDevicePixelHeight(),
                                           GL_BGRA, GL_UNSIGNED_INT_8_8_8_8_REV,
                                           mIOSurface->mIOSurfacePtr, 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().
    mozilla::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();
  double scaleFactor = surf->GetContentsScaleFactor();
  size_t intScaleFactor = ceil(surf->GetContentsScaleFactor());
  CGDataProviderRef dataProvider = ::CGDataProviderCreateWithData(ioData,
                                      ioData, ioHeight*intScaleFactor*(bytesPerRow)*4, 
                                      nullptr); //No release callback 
  if (!dataProvider) {
    surf->Unlock();
    return NS_ERROR_FAILURE;
  }

  CGImageRef cgImage = ::CGImageCreate(ioWidth * intScaleFactor,
              ioHeight * intScaleFactor, 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 * scaleFactor,
                                                    aY * scaleFactor,
                                                    aWidth * scaleFactor,
                                                    aHeight * scaleFactor));
  if (!subImage) {
    ::CGImageRelease(cgImage);
    surf->Unlock();
    return NS_ERROR_FAILURE;
  }

  ::CGContextScaleCTM(aContext, 1.0f, -1.0f);
  ::CGContextDrawImage(aContext, 
                       CGRectMake(aX * scaleFactor,
                                  (-(CGFloat)aY - (CGFloat)aHeight) * scaleFactor, 
                                  aWidth * scaleFactor,
                                  aHeight * scaleFactor), 
                       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];

  sprintf(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
