Adding optimization to avoid image copy in SkSurface copy on write when content is discardable

This patch also adds code to SkDeferredCanvas to trigger the optimization.

TEST=DeferredSurfaceCopy bench, Surface unit test
R=reed@google.com

Author: junov@chromium.org

Review URL: https://chromiumcodereview.appspot.com/14063015

git-svn-id: http://skia.googlecode.com/svn/trunk@8797 2bbb7eff-a529-9590-31e7-b0007b416f81
diff --git a/src/core/SkCanvas.cpp b/src/core/SkCanvas.cpp
index 2d8212d..1dc4fad 100644
--- a/src/core/SkCanvas.cpp
+++ b/src/core/SkCanvas.cpp
@@ -122,7 +122,7 @@
 
 void SkCanvas::predrawNotify() {
     if (fSurfaceBase) {
-        fSurfaceBase->aboutToDraw();
+        fSurfaceBase->aboutToDraw(SkSurface::kRetain_ContentChangeMode);
     }
 }
 
diff --git a/src/image/SkSurface.cpp b/src/image/SkSurface.cpp
index 72507c8..e8b6d38 100644
--- a/src/image/SkSurface.cpp
+++ b/src/image/SkSurface.cpp
@@ -37,7 +37,7 @@
     }
 }
 
-void SkSurface_Base::aboutToDraw() {
+void SkSurface_Base::aboutToDraw(ContentChangeMode mode) {
     this->dirtyGenerationID();
 
     if (NULL != fCachedCanvas) {
@@ -51,7 +51,7 @@
         // the cached image. Note: we only call if there is an outstanding owner
         // on the image (besides us).
         if (fCachedImage->getRefCnt() > 1) {
-            this->onCopyOnWrite();
+            this->onCopyOnWrite(mode);
         }
 
         // regardless of copy-on-write, we must drop our cached image now, so
@@ -87,8 +87,8 @@
     return fGenerationID;
 }
 
-void SkSurface::notifyContentChanged() {
-    asSB(this)->aboutToDraw();
+void SkSurface::notifyContentWillChange(ContentChangeMode mode) {
+    asSB(this)->aboutToDraw(mode);
 }
 
 SkCanvas* SkSurface::getCanvas() {
diff --git a/src/image/SkSurface_Base.h b/src/image/SkSurface_Base.h
index 32cfc88..6ea8d60 100644
--- a/src/image/SkSurface_Base.h
+++ b/src/image/SkSurface_Base.h
@@ -49,10 +49,8 @@
      *  If the surface is about to change, we call this so that our subclass
      *  can optionally fork their backend (copy-on-write) in case it was
      *  being shared with the cachedImage.
-     *
-     *  The default implementation does nothing.
      */
-    virtual void onCopyOnWrite() = 0;
+    virtual void onCopyOnWrite(ContentChangeMode) = 0;
 
     inline SkCanvas* getCachedCanvas();
     inline SkImage* getCachedImage();
@@ -64,7 +62,7 @@
     SkCanvas*   fCachedCanvas;
     SkImage*    fCachedImage;
 
-    void aboutToDraw();
+    void aboutToDraw(ContentChangeMode mode);
     friend class SkCanvas;
     friend class SkSurface;
 
diff --git a/src/image/SkSurface_Gpu.cpp b/src/image/SkSurface_Gpu.cpp
index 098c92b..687ca6c 100644
--- a/src/image/SkSurface_Gpu.cpp
+++ b/src/image/SkSurface_Gpu.cpp
@@ -23,7 +23,7 @@
     virtual SkImage* onNewImageSnapshot() SK_OVERRIDE;
     virtual void onDraw(SkCanvas*, SkScalar x, SkScalar y,
                         const SkPaint*) SK_OVERRIDE;
-    virtual void onCopyOnWrite() SK_OVERRIDE;
+    virtual void onCopyOnWrite(ContentChangeMode) SK_OVERRIDE;
 
 private:
     SkGpuDevice* fDevice;
@@ -86,9 +86,8 @@
 // Create a new SkGpuDevice and, if necessary, copy the contents of the old
 // device into it. Note that this flushes the SkGpuDevice but
 // doesn't force an OpenGL flush.
-void SkSurface_Gpu::onCopyOnWrite() {
+void SkSurface_Gpu::onCopyOnWrite(ContentChangeMode mode) {
     GrRenderTarget* rt = (GrRenderTarget*) fDevice->accessRenderTarget();
-
     // are we sharing our render target with the image?
     SkASSERT(NULL != this->getCachedImage());
     if (rt->asTexture() == SkTextureImageGetTexture(this->getCachedImage())) {
@@ -96,8 +95,10 @@
             fDevice->createCompatibleDevice(fDevice->config(), fDevice->width(),
             fDevice->height(), fDevice->isOpaque()));
         SkAutoTUnref<SkGpuDevice> aurd(newDevice);
-        fDevice->context()->copyTexture(rt->asTexture(),
-            (GrRenderTarget*)newDevice->accessRenderTarget());
+        if (kRetain_ContentChangeMode == mode) {
+            fDevice->context()->copyTexture(rt->asTexture(),
+                reinterpret_cast<GrRenderTarget*>(newDevice->accessRenderTarget()));
+        }
         SkASSERT(NULL != this->getCachedCanvas());
         SkASSERT(this->getCachedCanvas()->getDevice() == fDevice);
         this->getCachedCanvas()->setDevice(newDevice);
diff --git a/src/image/SkSurface_Picture.cpp b/src/image/SkSurface_Picture.cpp
index affa05c..79812c4 100644
--- a/src/image/SkSurface_Picture.cpp
+++ b/src/image/SkSurface_Picture.cpp
@@ -24,7 +24,7 @@
     virtual SkImage* onNewImageSnapshot() SK_OVERRIDE;
     virtual void onDraw(SkCanvas*, SkScalar x, SkScalar y,
                         const SkPaint*) SK_OVERRIDE;
-    virtual void onCopyOnWrite() SK_OVERRIDE;
+    virtual void onCopyOnWrite(ContentChangeMode) SK_OVERRIDE;
 
 private:
     SkPicture*  fPicture;
@@ -75,7 +75,7 @@
     SkImagePrivDrawPicture(canvas, fPicture, x, y, paint);
 }
 
-void SkSurface_Picture::onCopyOnWrite() {
+void SkSurface_Picture::onCopyOnWrite(ContentChangeMode /*mode*/) {
     // We always spawn a copy of the recording picture when we
     // are asked for a snapshot, so we never need to do anything here.
 }
diff --git a/src/image/SkSurface_Raster.cpp b/src/image/SkSurface_Raster.cpp
index 9a1f312..ccfdd27 100644
--- a/src/image/SkSurface_Raster.cpp
+++ b/src/image/SkSurface_Raster.cpp
@@ -25,7 +25,7 @@
     virtual SkImage* onNewImageSnapshot() SK_OVERRIDE;
     virtual void onDraw(SkCanvas*, SkScalar x, SkScalar y,
                         const SkPaint*) SK_OVERRIDE;
-    virtual void onCopyOnWrite() SK_OVERRIDE;
+    virtual void onCopyOnWrite(ContentChangeMode) SK_OVERRIDE;
 
 private:
     SkBitmap    fBitmap;
@@ -124,13 +124,18 @@
     return SkNewImageFromBitmap(fBitmap, fWeOwnThePixels);
 }
 
-void SkSurface_Raster::onCopyOnWrite() {
+void SkSurface_Raster::onCopyOnWrite(ContentChangeMode mode) {
     // are we sharing pixelrefs with the image?
-    SkASSERT(NULL !=this->getCachedImage());
+    SkASSERT(NULL != this->getCachedImage());
     if (SkBitmapImageGetPixelRef(this->getCachedImage()) == fBitmap.pixelRef()) {
         SkASSERT(fWeOwnThePixels);
-        SkBitmap prev(fBitmap);
-        prev.deepCopyTo(&fBitmap, prev.config());
+        if (kDiscard_ContentChangeMode == mode) {
+            fBitmap.setPixelRef(NULL, 0);
+            fBitmap.allocPixels();
+        } else {
+            SkBitmap prev(fBitmap);
+            prev.deepCopyTo(&fBitmap, prev.config());
+        }
         // Now fBitmap is a deep copy of itself (and therefore different from
         // what is being used by the image. Next we update the canvas to use
         // this as its backend, so we can't modify the image's pixels anymore.
diff --git a/src/utils/SkDeferredCanvas.cpp b/src/utils/SkDeferredCanvas.cpp
index 42e9537..c181818 100644
--- a/src/utils/SkDeferredCanvas.cpp
+++ b/src/utils/SkDeferredCanvas.cpp
@@ -248,6 +248,7 @@
     SkSurface* fSurface;
     SkDeferredCanvas::NotificationClient* fNotificationClient;
     bool fFreshFrame;
+    bool fCanDiscardCanvasContents;
     size_t fMaxRecordingStorageBytes;
     size_t fPreviousStorageAllocated;
     size_t fBitmapSizeThreshold;
@@ -281,6 +282,7 @@
 void DeferredDevice::init() {
     fRecordingCanvas = NULL;
     fFreshFrame = true;
+    fCanDiscardCanvasContents = false;
     fPreviousStorageAllocated = 0;
     fBitmapSizeThreshold = kDeferredCanvasBitmapSizeThreshold;
     fMaxRecordingStorageBytes = kDefaultMaxRecordingStorageBytes;
@@ -312,11 +314,14 @@
 }
 
 void DeferredDevice::skipPendingCommands() {
-    if (!fRecordingCanvas->isDrawingToLayer() && fPipeController.hasPendingCommands()) {
-        fFreshFrame = true;
-        flushPendingCommands(kSilent_PlaybackMode);
-        if (fNotificationClient) {
-            fNotificationClient->skippedPendingDrawCommands();
+    if (!fRecordingCanvas->isDrawingToLayer()) {
+        fCanDiscardCanvasContents = true;
+        if (fPipeController.hasPendingCommands()) {
+            fFreshFrame = true;
+            flushPendingCommands(kSilent_PlaybackMode);
+            if (fNotificationClient) {
+                fNotificationClient->skippedPendingDrawCommands();
+            }
         }
     }
 }
@@ -335,8 +340,18 @@
     if (!fPipeController.hasPendingCommands()) {
         return;
     }
-    if (playbackMode == kNormal_PlaybackMode && fNotificationClient) {
-        fNotificationClient->prepareForDraw();
+    if (playbackMode == kNormal_PlaybackMode) {
+        if (NULL != fNotificationClient) {
+            fNotificationClient->prepareForDraw();
+        }
+        if (fCanDiscardCanvasContents) {
+            if (NULL != fSurface) {
+                // Pre-empt notifyContentChanged(false) calls that will happen
+                // during flush
+                fSurface->notifyContentWillChange(SkSurface::kDiscard_ContentChangeMode);
+            }
+            fCanDiscardCanvasContents = false;
+        }
     }
     fPipeWriter.flushRecording(true);
     fPipeController.playback(kSilent_PlaybackMode == playbackMode);