Adding SkSurface support to SkDeferredCanvas
Review URL: https://codereview.chromium.org/14178002

git-svn-id: http://skia.googlecode.com/svn/trunk@8648 2bbb7eff-a529-9590-31e7-b0007b416f81
diff --git a/include/utils/SkDeferredCanvas.h b/include/utils/SkDeferredCanvas.h
index 53bea82..97b4a66 100644
--- a/include/utils/SkDeferredCanvas.h
+++ b/include/utils/SkDeferredCanvas.h
@@ -12,6 +12,8 @@
 #include "SkPixelRef.h"
 
 class DeferredDevice;
+class SkImage;
+class SkSurface;
 
 /** \class SkDeferredCanvas
     Subclass of SkCanvas that encapsulates an SkPicture or SkGPipe for deferred
@@ -33,6 +35,12 @@
     */
     explicit SkDeferredCanvas(SkDevice* device);
 
+    /** Construct a canvas with the specified surface to draw into.
+        This constructor must be used for newImageSnapshot to work.
+        @param surface Specifies a surface for the canvas to draw into.
+    */
+    explicit SkDeferredCanvas(SkSurface* surface);
+
     virtual ~SkDeferredCanvas();
 
     /**
@@ -93,6 +101,15 @@
     bool hasPendingCommands() const;
 
     /**
+     *  Flushes pending draw commands, if any, and returns an image of the
+     *  current state of the surface pixels up to this point. Subsequent
+     *  changes to the surface (by drawing into its canvas) will not be
+     *  reflected in this image.  Will return NULL if the deferred canvas
+     *  was not constructed from an SkSurface.
+     */
+    SkImage* newImageShapshot();
+
+    /**
      *  Specify the maximum number of bytes to be allocated for the purpose
      *  of recording draw commands to this canvas.  The default limit, is
      *  64MB.
diff --git a/src/utils/SkDeferredCanvas.cpp b/src/utils/SkDeferredCanvas.cpp
index 5119729..21f717c 100644
--- a/src/utils/SkDeferredCanvas.cpp
+++ b/src/utils/SkDeferredCanvas.cpp
@@ -17,6 +17,7 @@
 #include "SkPaintPriv.h"
 #include "SkRRect.h"
 #include "SkShader.h"
+#include "SkSurface.h"
 
 enum {
     // Deferred canvas will auto-flush when recording reaches this limit
@@ -138,14 +139,15 @@
 //-----------------------------------------------------------------------------
 class DeferredDevice : public SkDevice {
 public:
-    DeferredDevice(SkDevice* immediateDevice,
-        SkDeferredCanvas::NotificationClient* notificationClient = NULL);
+    explicit DeferredDevice(SkDevice* immediateDevice);
+    explicit DeferredDevice(SkSurface* surface);
     ~DeferredDevice();
 
     void setNotificationClient(SkDeferredCanvas::NotificationClient* notificationClient);
     SkCanvas* recordingCanvas();
     SkCanvas* immediateCanvas() const {return fImmediateCanvas;}
     SkDevice* immediateDevice() const {return fImmediateDevice;}
+    SkImage* newImageShapshot();
     bool isFreshFrame();
     bool hasPendingCommands();
     size_t storageAllocatedForRecording() const;
@@ -237,12 +239,14 @@
     virtual void flush();
 
     void beginRecording();
+    void init();
 
     DeferredPipeController fPipeController;
     SkGPipeWriter  fPipeWriter;
     SkDevice* fImmediateDevice;
     SkCanvas* fImmediateCanvas;
     SkCanvas* fRecordingCanvas;
+    SkSurface* fSurface;
     SkDeferredCanvas::NotificationClient* fNotificationClient;
     bool fFreshFrame;
     size_t fMaxRecordingStorageBytes;
@@ -250,21 +254,40 @@
     size_t fBitmapSizeThreshold;
 };
 
-DeferredDevice::DeferredDevice(
-    SkDevice* immediateDevice, SkDeferredCanvas::NotificationClient* notificationClient) :
-    SkDevice(SkBitmap::kNo_Config,
-             immediateDevice->width(), immediateDevice->height(),
-             immediateDevice->isOpaque(),
-             immediateDevice->getDeviceProperties())
-    , fRecordingCanvas(NULL)
-    , fFreshFrame(true)
-    , fPreviousStorageAllocated(0)
-    , fBitmapSizeThreshold(kDeferredCanvasBitmapSizeThreshold){
-
-    fMaxRecordingStorageBytes = kDefaultMaxRecordingStorageBytes;
-    fNotificationClient = notificationClient;
-    fImmediateDevice = immediateDevice; // ref counted via fImmediateCanvas
+DeferredDevice::DeferredDevice(SkDevice* immediateDevice)
+    : SkDevice(SkBitmap::kNo_Config,
+               immediateDevice->width(), immediateDevice->height(),
+               immediateDevice->isOpaque(),
+               immediateDevice->getDeviceProperties()) {
+    fSurface = NULL;
+    fImmediateDevice = immediateDevice;  // ref counted via fImmediateCanvas
     fImmediateCanvas = SkNEW_ARGS(SkCanvas, (fImmediateDevice));
+    this->init();
+}
+
+DeferredDevice::DeferredDevice(SkSurface* surface)
+    : SkDevice(SkBitmap::kNo_Config,
+               surface->getCanvas()->getDevice()->width(),
+               surface->getCanvas()->getDevice()->height(),
+               surface->getCanvas()->getDevice()->isOpaque(),
+               surface->getCanvas()->getDevice()->getDeviceProperties()) {
+    fMaxRecordingStorageBytes = kDefaultMaxRecordingStorageBytes;
+    fNotificationClient = NULL;
+    fImmediateCanvas = surface->getCanvas();
+    SkSafeRef(fImmediateCanvas);
+    fSurface = surface;
+    SkSafeRef(fSurface);
+    fImmediateDevice = fImmediateCanvas->getDevice();  // ref counted via fImmediateCanvas
+    this->init();
+}
+
+void DeferredDevice::init() {
+    fRecordingCanvas = NULL;
+    fFreshFrame = true;
+    fPreviousStorageAllocated = 0;
+    fBitmapSizeThreshold = kDeferredCanvasBitmapSizeThreshold;
+    fMaxRecordingStorageBytes = kDefaultMaxRecordingStorageBytes;
+    fNotificationClient = NULL;
     fPipeController.setPlaybackCanvas(fImmediateCanvas);
     this->beginRecording();
 }
@@ -272,6 +295,7 @@
 DeferredDevice::~DeferredDevice() {
     this->flushPendingCommands(kSilent_PlaybackMode);
     SkSafeUnref(fImmediateCanvas);
+    SkSafeUnref(fSurface);
 }
 
 void DeferredDevice::setMaxRecordingStorage(size_t maxStorage) {
@@ -376,6 +400,11 @@
     return fRecordingCanvas;
 }
 
+SkImage* DeferredDevice::newImageShapshot() {
+    this->flush();
+    return fSurface ? fSurface->newImageShapshot() : NULL;
+}
+
 uint32_t DeferredDevice::getDeviceCapabilities() {
     return fImmediateDevice->getDeviceCapabilities();
 }
@@ -437,7 +466,9 @@
     SkAutoTUnref<SkDevice> compatibleDevice
         (fImmediateDevice->createCompatibleDevice(config, width, height,
             isOpaque));
-    return SkNEW_ARGS(DeferredDevice, (compatibleDevice, fNotificationClient));
+    DeferredDevice* device = SkNEW_ARGS(DeferredDevice, (compatibleDevice));
+    device->setNotificationClient(fNotificationClient);
+    return device;
 }
 
 bool DeferredDevice::onReadPixels(
@@ -488,6 +519,11 @@
     this->setDevice(device);
 }
 
+SkDeferredCanvas::SkDeferredCanvas(SkSurface* surface) {
+    this->init();
+    this->INHERITED::setDevice(SkNEW_ARGS(DeferredDevice, (surface)))->unref();
+}
+
 void SkDeferredCanvas::init() {
     fDeferredDrawing = true; // On by default
 }
@@ -584,6 +620,12 @@
     return notificationClient;
 }
 
+SkImage* SkDeferredCanvas::newImageShapshot() {
+    DeferredDevice* deferredDevice = this->getDeferredDevice();
+    SkASSERT(deferredDevice);
+    return deferredDevice ? deferredDevice->newImageShapshot() : NULL;
+}
+
 bool SkDeferredCanvas::isFullFrame(const SkRect* rect,
                                    const SkPaint* paint) const {
     SkCanvas* canvas = this->drawingCanvas();
diff --git a/tests/DeferredCanvasTest.cpp b/tests/DeferredCanvasTest.cpp
index a95e09c..37e6721 100644
--- a/tests/DeferredCanvasTest.cpp
+++ b/tests/DeferredCanvasTest.cpp
@@ -12,6 +12,12 @@
 #include "SkDevice.h"
 #include "SkGradientShader.h"
 #include "SkShader.h"
+#include "SkSurface.h"
+#if SK_SUPPORT_GPU
+#include "GrContextFactory.h"
+#else
+class GrContextFactory;
+#endif
 
 static const int gWidth = 2;
 static const int gHeight = 2;
@@ -465,7 +471,75 @@
     }
 }
 
-static void TestDeferredCanvas(skiatest::Reporter* reporter) {
+
+typedef void* PixelPtr;
+// Returns an opaque pointer which, either points to a GrTexture or RAM pixel
+// buffer. Used to test pointer equality do determine whether a surface points
+// to the same pixel data storage as before.
+PixelPtr getSurfacePixelPtr(SkSurface* surface, bool useGpu) {
+    return useGpu ? surface->getCanvas()->getDevice()->accessBitmap(false).getTexture() :
+        surface->getCanvas()->getDevice()->accessBitmap(false).getPixels();
+}
+
+static void TestDeferredCanvasSurface(skiatest::Reporter* reporter, GrContextFactory* factory) {
+    SkImage::Info imageSpec = {
+        10,  // width
+        10,  // height
+        SkImage::kPMColor_ColorType,
+        SkImage::kPremul_AlphaType
+    };
+    SkSurface* surface;
+    bool useGpu = NULL != factory;
+#if SK_SUPPORT_GPU
+    if (useGpu) {
+        GrContext* context = factory->get(GrContextFactory::kNative_GLContextType);
+        surface = SkSurface::NewRenderTarget(context, imageSpec);
+    } else {
+        surface = SkSurface::NewRaster(imageSpec);
+    }
+#else
+    SkASSERT(!useGpu);
+    surface = SkSurface::NewRaster(imageSpec);
+#endif
+    SkASSERT(NULL != surface);
+    SkAutoTUnref<SkSurface> aur(surface);
+    SkDeferredCanvas canvas(surface);
+
+    SkImage* image1 = canvas.newImageShapshot();
+    SkAutoTUnref<SkImage> aur_i1(image1);
+    PixelPtr pixels1 = getSurfacePixelPtr(surface, useGpu);
+    // The following clear would normally trigger a copy on write, but
+    // it won't because rendering is deferred.
+    canvas.clear(SK_ColorBLACK);
+    // Obtaining a snapshot directly from the surface (as opposed to the
+    // SkDeferredCanvas) will not trigger a flush of deferred draw operations
+    // and will therefore return the same image as the previous snapshot.
+    SkImage* image2 = surface->newImageShapshot();
+    SkAutoTUnref<SkImage> aur_i2(image2);
+    // Images identical because of deferral
+    REPORTER_ASSERT(reporter, image1->uniqueID() == image2->uniqueID());
+    // Now we obtain a snpshot via the deferred canvas, which triggers a flush.
+    // Because there is a pending clear, this will generate a different image.
+    SkImage* image3 = canvas.newImageShapshot();
+    SkAutoTUnref<SkImage> aur_i3(image3);
+    REPORTER_ASSERT(reporter, image1->uniqueID() != image3->uniqueID());
+    // Verify that backing store is now a different buffer because of copy on
+    // write
+    PixelPtr pixels2 = getSurfacePixelPtr(surface, useGpu);
+    REPORTER_ASSERT(reporter, pixels1 != pixels2);
+    canvas.clear(SK_ColorWHITE);
+    canvas.flush();
+    PixelPtr pixels3 = getSurfacePixelPtr(surface, useGpu);
+    // Verify that a direct canvas flush with a pending draw does not trigger
+    // a copy on write when the surface is not sharing its buffer with an
+    // SkImage.
+    canvas.clear(SK_ColorBLACK);
+    canvas.flush();
+    PixelPtr pixels4 = getSurfacePixelPtr(surface, useGpu);
+    REPORTER_ASSERT(reporter, pixels3 == pixels4);
+}
+
+static void TestDeferredCanvas(skiatest::Reporter* reporter, GrContextFactory* factory) {
     TestDeferredCanvasBitmapAccess(reporter);
     TestDeferredCanvasFlush(reporter);
     TestDeferredCanvasFreshFrame(reporter);
@@ -474,7 +548,12 @@
     TestDeferredCanvasSkip(reporter);
     TestDeferredCanvasBitmapShaderNoLeak(reporter);
     TestDeferredCanvasBitmapSizeThreshold(reporter);
+    TestDeferredCanvasSurface(reporter, NULL);
+    if (NULL != factory) {
+        TestDeferredCanvasSurface(reporter, factory);
+    }
 }
 
 #include "TestClassDef.h"
-DEFINE_TESTCLASS("DeferredCanvas", TestDeferredCanvasClass, TestDeferredCanvas)
+DEFINE_GPUTESTCLASS("DeferredCanvas", TestDeferredCanvasClass, TestDeferredCanvas)
+