Bug 720482. Make CG snapshots work more like the other backends. r=joe,mattwoodrow,bas
authorJeff Muizelaar <jmuizelaar@mozilla.com>
Tue, 31 Jan 2012 01:46:54 -0500
changeset 85733 3f26b7bee352
parent 85732 ba2a5d61cab5
child 85739 de1de9a14af6
push id21957
push userjmuizelaar@mozilla.com
push dateTue, 31 Jan 2012 06:48:31 +0000
treeherdermozilla-central@3f26b7bee352 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersjoe, mattwoodrow, bas
bugs720482
milestone12.0a1
first release with
nightly linux32
3f26b7bee352 / 12.0a1 / 20120131031150 / files
nightly linux64
3f26b7bee352 / 12.0a1 / 20120131031150 / files
nightly mac
3f26b7bee352 / 12.0a1 / 20120131031150 / files
nightly win32
3f26b7bee352 / 12.0a1 / 20120131031150 / files
nightly win64
3f26b7bee352 / 12.0a1 / 20120131031150 / files
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
releases
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 720482. Make CG snapshots work more like the other backends. r=joe,mattwoodrow,bas This has big improvement in the performance of GetImageData() because we make the snapshot lazily instead of using the vm_copy() method of CGBitmapContextCreateImage()
gfx/2d/DrawTargetCG.cpp
gfx/2d/DrawTargetCG.h
gfx/2d/SourceSurfaceCG.cpp
gfx/2d/SourceSurfaceCG.h
gfx/2d/Types.h
--- a/gfx/2d/DrawTargetCG.cpp
+++ b/gfx/2d/DrawTargetCG.cpp
@@ -103,35 +103,40 @@ CGBlendMode ToBlendMode(CompositionOp op
     default:
       mode = kCGBlendModeNormal;
   }
   return mode;
 }
 
 
 
-DrawTargetCG::DrawTargetCG()
+DrawTargetCG::DrawTargetCG() : mSnapshot(NULL)
 {
 }
 
 DrawTargetCG::~DrawTargetCG()
 {
+  MarkChanged();
+
   // We need to conditionally release these because Init can fail without initializing these.
   if (mColorSpace)
     CGColorSpaceRelease(mColorSpace);
   if (mCg)
     CGContextRelease(mCg);
   free(mData);
 }
 
 TemporaryRef<SourceSurface>
 DrawTargetCG::Snapshot()
 {
-  RefPtr<SourceSurfaceCG> newSurf = new SourceSurfaceCG(CGBitmapContextCreateImage(mCg));
-  return newSurf;
+  if (!mSnapshot) {
+    mSnapshot = new SourceSurfaceCGBitmapContext(this);
+  }
+
+  return mSnapshot;
 }
 
 TemporaryRef<DrawTarget>
 DrawTargetCG::CreateSimilarDrawTarget(const IntSize &aSize, SurfaceFormat aFormat) const
 {
   // XXX: in thebes we use CGLayers to do this kind of thing. It probably makes sense
   // to add that in somehow, but at a higher level
   RefPtr<DrawTargetCG> newTarget = new DrawTargetCG();
@@ -152,16 +157,28 @@ DrawTargetCG::CreateSourceSurfaceFromDat
 
  if (!newSurf->InitFromData(aData, aSize, aStride, aFormat)) {
     return NULL;
   }
 
   return newSurf;
 }
 
+static CGImageRef
+GetImageFromSourceSurface(SourceSurface *aSurface)
+{
+  if (aSurface->GetType() == SURFACE_COREGRAPHICS_IMAGE)
+    return static_cast<SourceSurfaceCG*>(aSurface)->GetImage();
+  else if (aSurface->GetType() == SURFACE_COREGRAPHICS_CGCONTEXT)
+    return static_cast<SourceSurfaceCGBitmapContext*>(aSurface)->GetImage();
+  else if (aSurface->GetType() == SURFACE_DATA)
+    return static_cast<DataSourceSurfaceCG*>(aSurface)->GetImage();
+  assert(0);
+}
+
 TemporaryRef<SourceSurface>
 DrawTargetCG::OptimizeSourceSurface(SourceSurface *aSurface) const
 {
   return NULL;
 }
 
 class UnboundnessFixer
 {
@@ -176,16 +193,17 @@ class UnboundnessFixer
       if (!IsOperatorBoundByMask(blend)) {
         mClipBounds = CGContextGetClipBoundingBox(baseCg);
         // TransparencyLayers aren't blended using the blend mode so
         // we are forced to use CGLayers
 
         //XXX: The size here is in default user space units, of the layer relative to the graphics context.
         // is the clip bounds still correct if, for example, we have a scale applied to the context?
         mLayer = CGLayerCreateWithContext(baseCg, mClipBounds.size, NULL);
+        //XXX: if the size is 0x0 we get a NULL CGContext back from GetContext
         mCg = CGLayerGetContext(mLayer);
         // CGContext's default to have the origin at the bottom left
         // so flip it to the top left and adjust for the origin
         // of the layer
         CGContextTranslateCTM(mCg, -mClipBounds.origin.x, mClipBounds.origin.y + mClipBounds.size.height);
         CGContextScaleCTM(mCg, 1, -1);
 
         return mCg;
@@ -208,53 +226,53 @@ class UnboundnessFixer
 
 void
 DrawTargetCG::DrawSurface(SourceSurface *aSurface,
                            const Rect &aDest,
                            const Rect &aSource,
                            const DrawSurfaceOptions &aSurfOptions,
                            const DrawOptions &aDrawOptions)
 {
+  MarkChanged();
+
   CGImageRef image;
   CGImageRef subimage = NULL;
-  if (aSurface->GetType() == SURFACE_COREGRAPHICS_IMAGE) {
-    CGContextSaveGState(mCg);
+  CGContextSaveGState(mCg);
 
-    CGContextSetBlendMode(mCg, ToBlendMode(aDrawOptions.mCompositionOp));
-    UnboundnessFixer fixer;
-    CGContextRef cg = fixer.Check(mCg, aDrawOptions.mCompositionOp);
-    CGContextSetAlpha(cg, aDrawOptions.mAlpha);
+  CGContextSetBlendMode(mCg, ToBlendMode(aDrawOptions.mCompositionOp));
+  UnboundnessFixer fixer;
+  CGContextRef cg = fixer.Check(mCg, aDrawOptions.mCompositionOp);
+  CGContextSetAlpha(cg, aDrawOptions.mAlpha);
 
-    CGContextConcatCTM(cg, GfxMatrixToCGAffineTransform(mTransform));
-    image = static_cast<SourceSurfaceCG*>(aSurface)->GetImage();
-    /* we have two options here:
-     *  - create a subimage -- this is slower
-     *  - fancy things with clip and different dest rects */
-    {
-      subimage = CGImageCreateWithImageInRect(image, RectToCGRect(aSource));
-      image = subimage;
-    }
+  CGContextConcatCTM(cg, GfxMatrixToCGAffineTransform(mTransform));
+  image = GetImageFromSourceSurface(aSurface);
+  /* we have two options here:
+   *  - create a subimage -- this is slower
+   *  - fancy things with clip and different dest rects */
+  {
+    subimage = CGImageCreateWithImageInRect(image, RectToCGRect(aSource));
+    image = subimage;
+  }
 
-    CGContextScaleCTM(cg, 1, -1);
+  CGContextScaleCTM(cg, 1, -1);
 
-    CGRect flippedRect = CGRectMake(aDest.x, -(aDest.y + aDest.height),
-                                    aDest.width, aDest.height);
-
-    //XXX: we should implement this for patterns too
-    if (aSurfOptions.mFilter == FILTER_POINT)
-      CGContextSetInterpolationQuality(cg, kCGInterpolationNone);
+  CGRect flippedRect = CGRectMake(aDest.x, -(aDest.y + aDest.height),
+                                  aDest.width, aDest.height);
 
-    CGContextDrawImage(cg, flippedRect, image);
+  //XXX: we should implement this for patterns too
+  if (aSurfOptions.mFilter == FILTER_POINT)
+    CGContextSetInterpolationQuality(cg, kCGInterpolationNone);
 
-    fixer.Fix(mCg);
+  CGContextDrawImage(cg, flippedRect, image);
 
-    CGContextRestoreGState(mCg);
+  fixer.Fix(mCg);
 
-    CGImageRelease(subimage);
-  }
+  CGContextRestoreGState(mCg);
+
+  CGImageRelease(subimage);
 }
 
 static CGColorRef ColorToCGColor(CGColorSpaceRef aColorSpace, const Color& aColor)
 {
   CGFloat components[4] = {aColor.r, aColor.g, aColor.b, aColor.a};
   return CGColorCreate(aColorSpace, components);
 }
 
@@ -368,17 +386,17 @@ isGradient(const Pattern &aPattern)
 
 /* CoreGraphics patterns ignore the userspace transform so
  * we need to multiply it in */
 static CGPatternRef
 CreateCGPattern(const Pattern &aPattern, CGAffineTransform aUserSpace)
 {
   const SurfacePattern& pat = static_cast<const SurfacePattern&>(aPattern);
   // XXX: is .get correct here?
-  CGImageRef image = static_cast<SourceSurfaceCG*>(pat.mSurface.get())->GetImage();
+  CGImageRef image = GetImageFromSourceSurface(pat.mSurface.get());
   CGFloat xStep, yStep;
   switch (pat.mExtendMode) {
     case EXTEND_CLAMP:
       // The 1 << 22 comes from Webkit see Pattern::createPlatformPattern() in PatternCG.cpp for more info
       xStep = static_cast<CGFloat>(1 << 22);
       yStep = static_cast<CGFloat>(1 << 22);
       break;
     case EXTEND_REFLECT:
@@ -460,16 +478,18 @@ SetStrokeFromPattern(CGContextRef cg, CG
 }
 
 
 void
 DrawTargetCG::FillRect(const Rect &aRect,
                         const Pattern &aPattern,
                         const DrawOptions &aDrawOptions)
 {
+  MarkChanged();
+
   CGContextSaveGState(mCg);
 
   UnboundnessFixer fixer;
   CGContextRef cg = fixer.Check(mCg, aDrawOptions.mCompositionOp);
   CGContextSetAlpha(mCg, aDrawOptions.mAlpha);
   CGContextSetBlendMode(mCg, ToBlendMode(aDrawOptions.mCompositionOp));
 
   CGContextConcatCTM(cg, GfxMatrixToCGAffineTransform(mTransform));
@@ -484,16 +504,18 @@ DrawTargetCG::FillRect(const Rect &aRect
 
   fixer.Fix(mCg);
   CGContextRestoreGState(mCg);
 }
 
 void
 DrawTargetCG::StrokeLine(const Point &p1, const Point &p2, const Pattern &aPattern, const StrokeOptions &aStrokeOptions, const DrawOptions &aDrawOptions)
 {
+  MarkChanged();
+
   CGContextSaveGState(mCg);
 
   UnboundnessFixer fixer;
   CGContextRef cg = fixer.Check(mCg, aDrawOptions.mCompositionOp);
   CGContextSetAlpha(mCg, aDrawOptions.mAlpha);
   CGContextSetBlendMode(mCg, ToBlendMode(aDrawOptions.mCompositionOp));
 
   CGContextConcatCTM(cg, GfxMatrixToCGAffineTransform(mTransform));
@@ -519,16 +541,18 @@ DrawTargetCG::StrokeLine(const Point &p1
 }
 
 void
 DrawTargetCG::StrokeRect(const Rect &aRect,
                          const Pattern &aPattern,
                          const StrokeOptions &aStrokeOptions,
                          const DrawOptions &aDrawOptions)
 {
+  MarkChanged();
+
   CGContextSaveGState(mCg);
 
   UnboundnessFixer fixer;
   CGContextRef cg = fixer.Check(mCg, aDrawOptions.mCompositionOp);
   CGContextSetAlpha(mCg, aDrawOptions.mAlpha);
   CGContextSetBlendMode(mCg, ToBlendMode(aDrawOptions.mCompositionOp));
 
   CGContextConcatCTM(cg, GfxMatrixToCGAffineTransform(mTransform));
@@ -566,27 +590,31 @@ DrawTargetCG::StrokeRect(const Rect &aRe
   fixer.Fix(mCg);
   CGContextRestoreGState(mCg);
 }
 
 
 void
 DrawTargetCG::ClearRect(const Rect &aRect)
 {
+  MarkChanged();
+
   CGContextSaveGState(mCg);
   CGContextConcatCTM(mCg, GfxMatrixToCGAffineTransform(mTransform));
 
   CGContextClearRect(mCg, RectToCGRect(aRect));
 
   CGContextRestoreGState(mCg);
 }
 
 void
 DrawTargetCG::Stroke(const Path *aPath, const Pattern &aPattern, const StrokeOptions &aStrokeOptions, const DrawOptions &aDrawOptions)
 {
+  MarkChanged();
+
   CGContextSaveGState(mCg);
 
   UnboundnessFixer fixer;
   CGContextRef cg = fixer.Check(mCg, aDrawOptions.mCompositionOp);
   CGContextSetAlpha(mCg, aDrawOptions.mAlpha);
   CGContextSetBlendMode(mCg, ToBlendMode(aDrawOptions.mCompositionOp));
 
   CGContextConcatCTM(cg, GfxMatrixToCGAffineTransform(mTransform));
@@ -617,16 +645,18 @@ DrawTargetCG::Stroke(const Path *aPath, 
 
   fixer.Fix(mCg);
   CGContextRestoreGState(mCg);
 }
 
 void
 DrawTargetCG::Fill(const Path *aPath, const Pattern &aPattern, const DrawOptions &aDrawOptions)
 {
+  MarkChanged();
+
   assert(aPath->GetBackendType() == BACKEND_COREGRAPHICS);
 
   CGContextSaveGState(mCg);
 
   CGContextSetBlendMode(mCg, ToBlendMode(aDrawOptions.mCompositionOp));
   UnboundnessFixer fixer;
   CGContextRef cg = fixer.Check(mCg, aDrawOptions.mCompositionOp);
   CGContextSetAlpha(cg, aDrawOptions.mAlpha);
@@ -655,16 +685,18 @@ DrawTargetCG::Fill(const Path *aPath, co
   fixer.Fix(mCg);
   CGContextRestoreGState(mCg);
 }
 
 
 void
 DrawTargetCG::FillGlyphs(ScaledFont *aFont, const GlyphBuffer &aBuffer, const Pattern &aPattern, const DrawOptions &aDrawOptions)
 {
+  MarkChanged();
+
   assert(aBuffer.mNumGlyphs);
   CGContextSaveGState(mCg);
 
   CGContextSetBlendMode(mCg, ToBlendMode(aDrawOptions.mCompositionOp));
   UnboundnessFixer fixer;
   CGContextRef cg = fixer.Check(mCg, aDrawOptions.mCompositionOp);
   CGContextSetAlpha(cg, aDrawOptions.mAlpha);
 
@@ -718,20 +750,22 @@ void
 CGContextResetClip(CGContextRef);
 };
 
 void
 DrawTargetCG::CopySurface(SourceSurface *aSurface,
                           const IntRect& aSourceRect,
                           const IntPoint &aDestination)
 {
+  MarkChanged();
+
   CGImageRef image;
   CGImageRef subimage = NULL;
   if (aSurface->GetType() == SURFACE_COREGRAPHICS_IMAGE) {
-    image = static_cast<SourceSurfaceCG*>(aSurface)->GetImage();
+    image = GetImageFromSourceSurface(aSurface);
     /* we have two options here:
      *  - create a subimage -- this is slower
      *  - fancy things with clip and different dest rects */
     {
       subimage = CGImageCreateWithImageInRect(image, IntRectToCGRect(aSourceRect));
       image = subimage;
     }
     // XXX: it might be more efficient for us to do the copy directly if we have access to the bits
@@ -753,53 +787,54 @@ DrawTargetCG::CopySurface(SourceSurface 
 
     CGImageRelease(subimage);
   }
 }
 
 void
 DrawTargetCG::DrawSurfaceWithShadow(SourceSurface *aSurface, const Point &aDest, const Color &aColor, const Point &aOffset, Float aSigma, CompositionOp aOperator)
 {
+  MarkChanged();
+
   CGImageRef image;
   CGImageRef subimage = NULL;
-  if (aSurface->GetType() == SURFACE_COREGRAPHICS_IMAGE) {
-    image = static_cast<SourceSurfaceCG*>(aSurface)->GetImage();
+  image = GetImageFromSourceSurface(aSurface);
 
-    IntSize size = aSurface->GetSize();
-    CGContextSaveGState(mCg);
-    //XXX do we need to do the fixup here?
-    CGContextSetBlendMode(mCg, ToBlendMode(aOperator));
+  IntSize size = aSurface->GetSize();
+  CGContextSaveGState(mCg);
+  //XXX do we need to do the fixup here?
+  CGContextSetBlendMode(mCg, ToBlendMode(aOperator));
 
-    CGContextScaleCTM(mCg, 1, -1);
+  CGContextScaleCTM(mCg, 1, -1);
 
-    CGRect flippedRect = CGRectMake(aDest.x, -(aDest.y + size.height),
-                                    size.width, size.height);
+  CGRect flippedRect = CGRectMake(aDest.x, -(aDest.y + size.height),
+                                  size.width, size.height);
 
-    CGColorRef color = ColorToCGColor(mColorSpace, aColor);
-    CGSize offset = {aOffset.x, -aOffset.y};
-    // CoreGraphics needs twice sigma as it's amount of blur
-    CGContextSetShadowWithColor(mCg, offset, 2*aSigma, color);
-    CGColorRelease(color);
+  CGColorRef color = ColorToCGColor(mColorSpace, aColor);
+  CGSize offset = {aOffset.x, -aOffset.y};
+  // CoreGraphics needs twice sigma as it's amount of blur
+  CGContextSetShadowWithColor(mCg, offset, 2*aSigma, color);
+  CGColorRelease(color);
 
-    CGContextDrawImage(mCg, flippedRect, image);
+  CGContextDrawImage(mCg, flippedRect, image);
 
-    CGContextRestoreGState(mCg);
+  CGContextRestoreGState(mCg);
 
-    CGImageRelease(subimage);
-  }
+  CGImageRelease(subimage);
 }
 
 bool
 DrawTargetCG::Init(CGContextRef cgContext, const IntSize &aSize)
 {
   // XXX: we should come up with some consistent semantics for dealing
   // with zero area drawtargets
   if (aSize.width == 0 || aSize.height == 0) {
     mColorSpace = NULL;
     mCg = NULL;
+    mData = NULL;
     return false;
   }
 
   //XXX: handle SurfaceFormat
 
   //XXX: we'd be better off reusing the Colorspace across draw targets
   mColorSpace = CGColorSpaceCreateDeviceRGB();
 
@@ -824,16 +859,17 @@ DrawTargetCG::Init(CGContextRef cgContex
 bool
 DrawTargetCG::Init(const IntSize &aSize, SurfaceFormat &)
 {
   // XXX: we should come up with some consistent semantics for dealing
   // with zero area drawtargets
   if (aSize.width == 0 || aSize.height == 0) {
     mColorSpace = NULL;
     mCg = NULL;
+    mData = NULL;
     return false;
   }
 
   //XXX: handle SurfaceFormat
 
   //XXX: we'd be better off reusing the Colorspace across draw targets
   mColorSpace = CGColorSpaceCreateDeviceRGB();
 
@@ -889,33 +925,34 @@ DrawTargetCG::GetNativeSurface(NativeSur
   }
 }
 
 void
 DrawTargetCG::Mask(const Pattern &aSource,
                    const Pattern &aMask,
                    const DrawOptions &aDrawOptions)
 {
+  MarkChanged();
 
   CGContextSaveGState(mCg);
 
   if (isGradient(aMask)) {
     assert(0);
   } else {
     if (aMask.GetType() == PATTERN_COLOR) {
       DrawOptions drawOptions(aDrawOptions);
       const Color& color = static_cast<const ColorPattern&>(aMask).mColor;
       drawOptions.mAlpha *= color.a;
       assert(0);
       // XXX: we need to get a rect that when transformed covers the entire surface
       //Rect
       //FillRect(rect, aSource, drawOptions);
     } else if (aMask.GetType() == PATTERN_SURFACE) {
       const SurfacePattern& pat = static_cast<const SurfacePattern&>(aMask);
-      CGImageRef mask = static_cast<SourceSurfaceCG*>(pat.mSurface.get())->GetImage();
+      CGImageRef mask = GetImageFromSourceSurface(pat.mSurface.get());
       Rect rect(0,0, CGImageGetWidth(mask), CGImageGetHeight(mask));
       // XXX: probably we need to do some flipping of the image or something
       CGContextClipToMask(mCg, RectToCGRect(rect), mask);
       FillRect(rect, aSource, aDrawOptions);
     }
   }
 
   CGContextRestoreGState(mCg);
@@ -969,12 +1006,24 @@ DrawTargetCG::PushClip(const Path *aPath
 }
 
 void
 DrawTargetCG::PopClip()
 {
   CGContextRestoreGState(mCg);
 }
 
+void
+DrawTargetCG::MarkChanged()
+{
+  if (mSnapshot) {
+    if (mSnapshot->refCount() > 1) {
+      // We only need to worry about snapshots that someone else knows about
+      mSnapshot->DrawTargetWillChange();
+    }
+    mSnapshot = NULL;
+  }
+}
+
 
 
 }
 }
--- a/gfx/2d/DrawTargetCG.h
+++ b/gfx/2d/DrawTargetCG.h
@@ -35,16 +35,17 @@
  *
  * ***** END LICENSE BLOCK ***** */
 
 #include <ApplicationServices/ApplicationServices.h>
 
 #include "2D.h"
 #include "Rect.h"
 #include "PathCG.h"
+#include "SourceSurfaceCG.h"
 
 namespace mozilla {
 namespace gfx {
 
 static inline CGAffineTransform
 GfxMatrixToCGAffineTransform(Matrix m)
 {
   CGAffineTransform t;
@@ -169,22 +170,23 @@ public:
                                                             const IntSize &aSize,
                                                             int32_t aStride,
                                                             SurfaceFormat aFormat) const;
   virtual TemporaryRef<SourceSurface> OptimizeSourceSurface(SourceSurface *aSurface) const;
   CGContextRef GetCGContext() {
       return mCg;
   }
 private:
-  bool InitCGRenderTarget();
+  void MarkChanged();
 
   IntSize mSize;
   CGColorSpaceRef mColorSpace;
   CGContextRef mCg;
 
   void *mData;
 
   SurfaceFormat mFormat;
 
+  RefPtr<SourceSurfaceCGBitmapContext> mSnapshot;
 };
 
 }
 }
--- a/gfx/2d/SourceSurfaceCG.cpp
+++ b/gfx/2d/SourceSurfaceCG.cpp
@@ -31,16 +31,17 @@
  * decision by deleting the provisions above and replace them with the notice
  * and other provisions required by the GPL or the LGPL. If you do not delete
  * the provisions above, a recipient may use your version of this file under
  * the terms of any one of the MPL, the GPL or the LGPL.
  *
  * ***** END LICENSE BLOCK ***** */
 
 #include "SourceSurfaceCG.h"
+#include "DrawTargetCG.h"
 
 namespace mozilla {
 namespace gfx {
 
 
 SourceSurfaceCG::~SourceSurfaceCG()
 {
   CGImageRelease(mImage);
@@ -304,16 +305,98 @@ DataSourceSurfaceCG::GetData()
   // See http://developer.apple.com/library/mac/#qa/qa1509/_index.html
   // the following only works on 10.5+, the Q&A above suggests a method
   // that can be used for earlier versions
   //CFDataRef data = CGDataProviderCopyData(CGImageGetDataProvider(cgImage));
   //unsigned char *dataPtr = CFDataGetBytePtr(data);
   //CFDataRelease(data);
   // unfortunately the the method above only works for read-only access and
   // we need read-write for DataSourceSurfaces
-
   return (unsigned char*)mData;
 }
 
+SourceSurfaceCGBitmapContext::SourceSurfaceCGBitmapContext(DrawTargetCG *aDrawTarget)
+{
+  mDrawTarget = aDrawTarget;
+  mCg = (CGContextRef)aDrawTarget->GetNativeSurface(NATIVE_SURFACE_CGCONTEXT);
+  CGContextRetain(mCg);
 
+  mSize.width = CGBitmapContextGetWidth(mCg);
+  mSize.height = CGBitmapContextGetHeight(mCg);
+  mStride = CGBitmapContextGetBytesPerRow(mCg);
+  mData = CGBitmapContextGetData(mCg);
+
+  mImage = NULL;
+}
+
+void SourceSurfaceCGBitmapContext::EnsureImage() const
+{
+  if (!mImage) {
+    if (mCg) {
+      mImage = CGBitmapContextCreateImage(mCg);
+    } else {
+      //XXX: we should avoid creating this colorspace everytime
+      CGColorSpaceRef colorSpace = NULL;
+      CGBitmapInfo bitinfo = 0;
+      CGDataProviderRef dataProvider = NULL;
+      int bitsPerComponent = 8;
+      int bitsPerPixel = 32;
+
+      colorSpace = CGColorSpaceCreateDeviceRGB();
+      bitinfo = kCGImageAlphaPremultipliedFirst | kCGBitmapByteOrder32Host;
+
+      dataProvider = CGDataProviderCreateWithData (mData,
+                                                   mData,
+                                                   mSize.height * mStride,
+                                                   releaseCallback);
+
+      mImage = CGImageCreate (mSize.width, mSize.height,
+                              bitsPerComponent,
+                              bitsPerPixel,
+                              mStride,
+                              colorSpace,
+                              bitinfo,
+                              dataProvider,
+                              NULL,
+                              true,
+                              kCGRenderingIntentDefault);
+
+      CGDataProviderRelease(dataProvider);
+      CGColorSpaceRelease (colorSpace);
+    }
+  }
+}
+
+IntSize
+SourceSurfaceCGBitmapContext::GetSize() const
+{
+  return mSize;
+}
+
+void
+SourceSurfaceCGBitmapContext::DrawTargetWillChange()
+{
+  if (mDrawTarget) {
+    size_t stride = CGBitmapContextGetBytesPerRow(mCg);
+    size_t height = CGBitmapContextGetHeight(mCg);
+    //XXX: infalliable malloc?
+    mData = malloc(stride * height);
+    memcpy(mData, CGBitmapContextGetData(mCg), stride*height);
+    CGContextRelease(mCg);
+    mCg = NULL;
+    mDrawTarget = NULL;
+  }
+}
+
+SourceSurfaceCGBitmapContext::~SourceSurfaceCGBitmapContext()
+{
+  if (!mImage && !mCg) {
+    // neither mImage or mCg owns the data
+    free(mData);
+  }
+  if (mCg)
+    CGContextRelease(mCg);
+  if (mImage)
+    CGImageRelease(mImage);
+}
 
 }
 }
--- a/gfx/2d/SourceSurfaceCG.h
+++ b/gfx/2d/SourceSurfaceCG.h
@@ -39,16 +39,18 @@
 
 #include <ApplicationServices/ApplicationServices.h>
 
 #include "2D.h"
 
 namespace mozilla {
 namespace gfx {
 
+class DrawTargetCG;
+
 class SourceSurfaceCG : public SourceSurface
 {
 public:
   SourceSurfaceCG() {}
   SourceSurfaceCG(CGImageRef aImage) : mImage(aImage) {}
   ~SourceSurfaceCG();
 
   virtual SurfaceType GetType() const { return SURFACE_COREGRAPHICS_IMAGE; }
@@ -100,10 +102,41 @@ private:
   CGImageRef mImage;
   //XXX: we don't need to store mData we can just get it from the CGContext
   void *mData;
   /* It might be better to just use the bitmap info from the CGImageRef to
    * deduce the format to save space in SourceSurfaceCG,
    * for now we just store it in mFormat */
 };
 
+class SourceSurfaceCGBitmapContext : public DataSourceSurface
+{
+public:
+  SourceSurfaceCGBitmapContext(DrawTargetCG *);
+  ~SourceSurfaceCGBitmapContext();
+
+  virtual SurfaceType GetType() const { return SURFACE_COREGRAPHICS_CGCONTEXT; }
+  virtual IntSize GetSize() const;
+  virtual SurfaceFormat GetFormat() const { return FORMAT_B8G8R8A8; }
+
+  CGImageRef GetImage() { EnsureImage(); return mImage; }
+
+  virtual unsigned char *GetData() { return static_cast<unsigned char*>(mData); }
+
+  virtual int32_t Stride() { return mStride; }
+
+private:
+  //XXX: do the other backends friend their DrawTarget?
+  friend class DrawTargetCG;
+  void DrawTargetWillChange();
+  void EnsureImage() const;
+
+  DrawTargetCG *mDrawTarget;
+  CGContextRef mCg;
+  mutable CGImageRef mImage;
+  void *mData;
+  int32_t mStride;
+  IntSize mSize;
+};
+
+
 }
 }
--- a/gfx/2d/Types.h
+++ b/gfx/2d/Types.h
@@ -50,16 +50,17 @@ typedef float Float;
 enum SurfaceType
 {
   SURFACE_DATA, /* Data surface - bitmap in memory */
   SURFACE_D2D1_BITMAP, /* Surface wrapping a ID2D1Bitmap */
   SURFACE_D2D1_DRAWTARGET, /* Surface made from a D2D draw target */
   SURFACE_CAIRO, /* Surface wrapping a cairo surface */
   SURFACE_CAIRO_IMAGE, /* Data surface wrapping a cairo image surface */
   SURFACE_COREGRAPHICS_IMAGE, /* Surface wrapping a CoreGraphics Image */
+  SURFACE_COREGRAPHICS_CGCONTEXT, /* Surface wrapping a CG context */
   SURFACE_SKIA /* Surface wrapping a Skia bitmap */
 };
 
 enum SurfaceFormat
 {
   FORMAT_B8G8R8A8,
   FORMAT_B8G8R8X8,
   FORMAT_R5G6B5,