Adding setSurface public API method to SkDeferredCanvas

The purpose of this change is to provide an API that Blink 2D canvas layers can use
to install a new render target when recovering from a lost graphics context.

Review URL: https://codereview.chromium.org/15896005

git-svn-id: http://skia.googlecode.com/svn/trunk@9276 2bbb7eff-a529-9590-31e7-b0007b416f81
diff --git a/include/utils/SkDeferredCanvas.h b/include/utils/SkDeferredCanvas.h
index 60f709c..bef26c0 100644
--- a/include/utils/SkDeferredCanvas.h
+++ b/include/utils/SkDeferredCanvas.h
@@ -54,6 +54,16 @@
     virtual SkDevice* setDevice(SkDevice* device);
 
     /**
+     *  Specify the surface to be used by this canvas. Calling setSurface will
+     *  release the previously set surface or device. Takes a reference on the
+     *  surface.
+     *
+     *  @param surface The surface that the canvas will raw into
+     *  @return The surface argument, for convenience.
+     */
+    SkSurface* setSurface(SkSurface* surface);
+
+    /**
      *  Specify a NotificationClient to be used by this canvas. Calling
      *  setNotificationClient will release the previously set
      *  NotificationClient, if any. SkDeferredCanvas does not take ownership
diff --git a/src/utils/SkDeferredCanvas.cpp b/src/utils/SkDeferredCanvas.cpp
index f2aae06..b396a08 100644
--- a/src/utils/SkDeferredCanvas.cpp
+++ b/src/utils/SkDeferredCanvas.cpp
@@ -148,6 +148,7 @@
     SkCanvas* immediateCanvas() const {return fImmediateCanvas;}
     SkDevice* immediateDevice() const {return fImmediateCanvas->getTopDevice();}
     SkImage* newImageSnapshot();
+    void setSurface(SkSurface* surface);
     bool isFreshFrame();
     bool hasPendingCommands();
     size_t storageAllocatedForRecording() const;
@@ -261,6 +262,7 @@
                immediateDevice->getDeviceProperties()) {
     fSurface = NULL;
     fImmediateCanvas = SkNEW_ARGS(SkCanvas, (immediateDevice));
+    fPipeController.setPlaybackCanvas(fImmediateCanvas);
     this->init();
 }
 
@@ -272,13 +274,18 @@
                surface->getCanvas()->getDevice()->getDeviceProperties()) {
     fMaxRecordingStorageBytes = kDefaultMaxRecordingStorageBytes;
     fNotificationClient = NULL;
-    fImmediateCanvas = surface->getCanvas();
-    SkSafeRef(fImmediateCanvas);
-    fSurface = surface;
-    SkSafeRef(fSurface);
+    fImmediateCanvas = NULL;
+    fSurface = NULL;
+    this->setSurface(surface);
     this->init();
 }
 
+void DeferredDevice::setSurface(SkSurface* surface) {
+    SkRefCnt_SafeAssign(fImmediateCanvas, surface->getCanvas());
+    SkRefCnt_SafeAssign(fSurface, surface);
+    fPipeController.setPlaybackCanvas(fImmediateCanvas);
+}
+
 void DeferredDevice::init() {
     fRecordingCanvas = NULL;
     fFreshFrame = true;
@@ -287,7 +294,6 @@
     fBitmapSizeThreshold = kDeferredCanvasBitmapSizeThreshold;
     fMaxRecordingStorageBytes = kDefaultMaxRecordingStorageBytes;
     fNotificationClient = NULL;
-    fPipeController.setPlaybackCanvas(fImmediateCanvas);
     this->beginRecording();
 }
 
@@ -620,6 +626,19 @@
     return device;
 }
 
+SkSurface* SkDeferredCanvas::setSurface(SkSurface* surface) {
+    DeferredDevice* deferredDevice = this->getDeferredDevice();
+    if (NULL != deferredDevice) {
+        // By swapping the surface into the existing device, we preserve
+        // all pending commands, which can help to seamlessly recover from
+        // a lost accelerated graphics context.
+        deferredDevice->setSurface(surface);
+    } else {
+        this->INHERITED::setDevice(SkNEW_ARGS(DeferredDevice, (surface)))->unref();
+    }
+    return surface;
+}
+
 SkDeferredCanvas::NotificationClient* SkDeferredCanvas::setNotificationClient(
     NotificationClient* notificationClient) {
 
diff --git a/tests/DeferredCanvasTest.cpp b/tests/DeferredCanvasTest.cpp
index 3def190..762c916 100644
--- a/tests/DeferredCanvasTest.cpp
+++ b/tests/DeferredCanvasTest.cpp
@@ -548,6 +548,51 @@
     REPORTER_ASSERT(reporter, pixels4 == pixels5);
 }
 
+static void TestDeferredCanvasSetSurface(skiatest::Reporter* reporter, GrContextFactory* factory) {
+    SkImage::Info imageSpec = {
+        10,  // width
+        10,  // height
+        SkImage::kPMColor_ColorType,
+        SkImage::kPremul_AlphaType
+    };
+    SkSurface* surface;
+    SkSurface* alternateSurface;
+    bool useGpu = NULL != factory;
+#if SK_SUPPORT_GPU
+    if (useGpu) {
+        GrContext* context = factory->get(GrContextFactory::kNative_GLContextType);
+        surface = SkSurface::NewRenderTarget(context, imageSpec);
+        alternateSurface = SkSurface::NewRenderTarget(context, imageSpec);
+    } else {
+        surface = SkSurface::NewRaster(imageSpec);
+        alternateSurface = SkSurface::NewRaster(imageSpec);
+    }
+#else
+    SkASSERT(!useGpu);
+    surface = SkSurface::NewRaster(imageSpec);
+    alternateSurface = SkSurface::NewRaster(imageSpec);
+#endif
+    SkASSERT(NULL != surface);
+    SkASSERT(NULL != alternateSurface);
+    SkAutoTUnref<SkSurface> aur1(surface);
+    SkAutoTUnref<SkSurface> aur2(alternateSurface);
+    PixelPtr pixels1 = getSurfacePixelPtr(surface, useGpu);
+    PixelPtr pixels2 = getSurfacePixelPtr(alternateSurface, useGpu);
+    SkDeferredCanvas canvas(surface);
+    SkAutoTUnref<SkImage> image1(canvas.newImageSnapshot());
+    canvas.setSurface(alternateSurface);
+    SkAutoTUnref<SkImage> image2(canvas.newImageSnapshot());
+    REPORTER_ASSERT(reporter, image1->uniqueID() != image2->uniqueID());
+    // Verify that none of the above operations triggered a surface copy on write.
+    REPORTER_ASSERT(reporter, getSurfacePixelPtr(surface, useGpu) == pixels1);
+    REPORTER_ASSERT(reporter, getSurfacePixelPtr(alternateSurface, useGpu) == pixels2);
+    // Verify that a flushed draw command will trigger a copy on write on alternateSurface.
+    canvas.clear(SK_ColorWHITE);
+    canvas.flush();
+    REPORTER_ASSERT(reporter, getSurfacePixelPtr(surface, useGpu) == pixels1);
+    REPORTER_ASSERT(reporter, getSurfacePixelPtr(alternateSurface, useGpu) != pixels2);
+}
+
 static void TestDeferredCanvasCreateCompatibleDevice(skiatest::Reporter* reporter) {
     SkBitmap store;
     store.setConfig(SkBitmap::kARGB_8888_Config, 100, 100);
@@ -581,8 +626,10 @@
     TestDeferredCanvasBitmapSizeThreshold(reporter);
     TestDeferredCanvasCreateCompatibleDevice(reporter);
     TestDeferredCanvasSurface(reporter, NULL);
+    TestDeferredCanvasSetSurface(reporter, NULL);
     if (NULL != factory) {
         TestDeferredCanvasSurface(reporter, factory);
+        TestDeferredCanvasSetSurface(reporter, factory);
     }
 }