Adding API and unit testing for deferred canvas clearing/purging

REVIEW=http://codereview.appspot.com/5646057/
TEST=DeferredCanvas unit test



git-svn-id: http://skia.googlecode.com/svn/trunk@3181 2bbb7eff-a529-9590-31e7-b0007b416f81
diff --git a/include/core/SkCanvas.h b/include/core/SkCanvas.h
index 4e74e0a..058979d 100644
--- a/include/core/SkCanvas.h
+++ b/include/core/SkCanvas.h
@@ -295,7 +295,7 @@
     /** Returns true if drawing is currently going to a layer (from saveLayer)
      *  rather than to the root device.
      */
-    bool isDrawingToLayer() const;
+    virtual bool isDrawingToLayer() const;
 
     /** Preconcat the current matrix with the specified translation
         @param dx   The distance to translate in X
diff --git a/include/utils/SkDeferredCanvas.h b/include/utils/SkDeferredCanvas.h
index 19d7141..e06ea81 100644
--- a/include/utils/SkDeferredCanvas.h
+++ b/include/utils/SkDeferredCanvas.h
@@ -181,8 +181,14 @@
          */
         SkDevice* immediateDevice() const {return fImmediateDevice;}
 
+        /**
+         *  Returns true if an opaque draw operation covering the entire canvas
+         *  was performed since the last call to isFreshFrame().
+         */
+        bool isFreshFrame();
+
         void flushPending();
-        void purgePending();
+        void contentsCleared();
         void flushIfNeeded(const SkBitmap& bitmap);
 
         virtual uint32_t getDeviceCapabilities() SK_OVERRIDE;
@@ -275,7 +281,7 @@
         SkCanvas* fImmediateCanvas;
         SkCanvas* fRecordingCanvas;
         DeviceContext* fDeviceContext;
-        bool fBitmapInitialized;
+        bool fFreshFrame;
     };
 
     DeferredDevice* getDeferredDevice() const;
diff --git a/src/core/SkPictureRecord.cpp b/src/core/SkPictureRecord.cpp
index f31065b..c56c104 100644
--- a/src/core/SkPictureRecord.cpp
+++ b/src/core/SkPictureRecord.cpp
@@ -23,6 +23,7 @@
     fRestoreOffsetStack.push(0);
 
     fPathHeap = NULL;   // lazy allocate
+    fFirstSavedLayerIndex = kNoSavedLayerIndex;
 }
 
 SkPictureRecord::~SkPictureRecord() {
@@ -50,6 +51,10 @@
 
     fRestoreOffsetStack.push(0);
 
+    if (kNoSavedLayerIndex == fFirstSavedLayerIndex) {
+        fFirstSavedLayerIndex = fRestoreOffsetStack.count();
+    }
+
     validate();
     /*  Don't actually call saveLayer, because that will try to allocate an
         offscreen device (potentially very big) which we don't actually need
@@ -60,6 +65,10 @@
     return this->INHERITED::save(flags);
 }
 
+bool SkPictureRecord::isDrawingToLayer() const {
+    return fFirstSavedLayerIndex != kNoSavedLayerIndex;
+}
+
 void SkPictureRecord::restore() {
     // check for underflow
     if (fRestoreOffsetStack.count() == 0) {
@@ -74,6 +83,11 @@
         offset = *peek;
         *peek = restoreOffset;
     }
+
+    if (fRestoreOffsetStack.count() == fFirstSavedLayerIndex) {
+        fFirstSavedLayerIndex = kNoSavedLayerIndex;
+    }
+
     fRestoreOffsetStack.pop();
 
     addDraw(RESTORE);
diff --git a/src/core/SkPictureRecord.h b/src/core/SkPictureRecord.h
index ff6585d..e8fa370 100644
--- a/src/core/SkPictureRecord.h
+++ b/src/core/SkPictureRecord.h
@@ -65,6 +65,7 @@
                           const uint16_t indices[], int indexCount,
                               const SkPaint&) SK_OVERRIDE;
     virtual void drawData(const void*, size_t) SK_OVERRIDE;
+    virtual bool isDrawingToLayer() const SK_OVERRIDE;
 
     void addFontMetricsTopBottom(const SkPaint& paint, SkScalar minY, SkScalar maxY);
 
@@ -92,6 +93,10 @@
 
 private:
     SkTDArray<uint32_t> fRestoreOffsetStack;
+    int fFirstSavedLayerIndex;
+    enum {
+        kNoSavedLayerIndex = -1
+    };
 
     void addDraw(DrawType drawType) {
 #ifdef SK_DEBUG_TRACE
diff --git a/src/utils/SkDeferredCanvas.cpp b/src/utils/SkDeferredCanvas.cpp
index 6988517..27fc0c5 100644
--- a/src/utils/SkDeferredCanvas.cpp
+++ b/src/utils/SkDeferredCanvas.cpp
@@ -288,7 +288,7 @@
 {
     // purge pending commands
     if (fDeferredDrawing) {
-        getDeferredDevice()->purgePending();
+        getDeferredDevice()->contentsCleared();
     }
 
     drawingCanvas()->clear(color);
@@ -297,7 +297,7 @@
 void SkDeferredCanvas::drawPaint(const SkPaint& paint)
 {
     if (fDeferredDrawing && isFullFrame(NULL, &paint) && isPaintOpaque(paint)) {
-        getDeferredDevice()->purgePending();
+        getDeferredDevice()->contentsCleared();
     }
 
     drawingCanvas()->drawPaint(paint);
@@ -312,7 +312,7 @@
 void SkDeferredCanvas::drawRect(const SkRect& rect, const SkPaint& paint)
 {
     if (fDeferredDrawing && isFullFrame(&rect, &paint) && isPaintOpaque(paint)) {
-        getDeferredDevice()->purgePending();
+        getDeferredDevice()->contentsCleared();
     }
 
     drawingCanvas()->drawRect(rect, paint);
@@ -326,12 +326,12 @@
 void SkDeferredCanvas::drawBitmap(const SkBitmap& bitmap, SkScalar left,
                                   SkScalar top, const SkPaint* paint)
 {
-    SkRect bitmapRect = SkRect::MakeXYWH(left, top, bitmap.width(),
-        bitmap.height());
+    SkRect bitmapRect = SkRect::MakeXYWH(left, top,
+        SkIntToScalar(bitmap.width()), SkIntToScalar(bitmap.height()));
     if (fDeferredDrawing && 
         isFullFrame(&bitmapRect, paint) &&
         isPaintOpaque(*paint, &bitmap)) {
-        getDeferredDevice()->purgePending();
+        getDeferredDevice()->contentsCleared();
     }
 
     drawingCanvas()->drawBitmap(bitmap, left, top, paint);
@@ -345,7 +345,7 @@
     if (fDeferredDrawing && 
         isFullFrame(&dst, paint) &&
         isPaintOpaque(*paint, &bitmap)) {
-        getDeferredDevice()->purgePending();
+        getDeferredDevice()->contentsCleared();
     }
 
     drawingCanvas()->drawBitmapRect(bitmap, src,
@@ -378,12 +378,15 @@
 void SkDeferredCanvas::drawSprite(const SkBitmap& bitmap, int left, int top,
                                   const SkPaint* paint)
 {
-    SkRect bitmapRect = SkRect::MakeXYWH(left, top, bitmap.width(),
-        bitmap.height());
+    SkRect bitmapRect = SkRect::MakeXYWH(
+        SkIntToScalar(left),
+        SkIntToScalar(top), 
+        SkIntToScalar(bitmap.width()),
+        SkIntToScalar(bitmap.height()));
     if (fDeferredDrawing && 
         isFullFrame(&bitmapRect, paint) &&
         isPaintOpaque(*paint, &bitmap)) {
-        getDeferredDevice()->purgePending();
+        getDeferredDevice()->contentsCleared();
     }
 
     drawingCanvas()->drawSprite(bitmap, left, top,
@@ -466,6 +469,7 @@
     SkDevice* immediateDevice, DeviceContext* deviceContext) :
     SkDevice(SkBitmap::kNo_Config, immediateDevice->width(),
              immediateDevice->height(), immediateDevice->isOpaque())
+    , fFreshFrame(true)
 {
     fDeviceContext = deviceContext;
     SkSafeRef(fDeviceContext);
@@ -474,7 +478,6 @@
     fRecordingCanvas = fPicture.beginRecording(fImmediateDevice->width(),
         fImmediateDevice->height(),
         SkPicture::kUsePathBoundsForClip_RecordingFlag);
-    fBitmapInitialized = false;
 }
 
 SkDeferredCanvas::DeferredDevice::~DeferredDevice()
@@ -489,36 +492,47 @@
     SkRefCnt_SafeAssign(fDeviceContext, deviceContext);
 }
 
-void SkDeferredCanvas::DeferredDevice::purgePending()
+void SkDeferredCanvas::DeferredDevice::contentsCleared()
 {
-    // TODO: find a way to transfer the state stack and layers
-    // to the new recording canvas.  For now, purge only works
-    // with an empty stack.
-    if (fRecordingCanvas->getSaveCount() > 1)
-        return;
+    if (!fRecordingCanvas->isDrawingToLayer()) {
+        fFreshFrame = true;
 
-    // Save state that is trashed by the purge
-    SkDrawFilter* drawFilter = fRecordingCanvas->getDrawFilter();
-    SkSafeRef(drawFilter); // So that it survives the purge
-    SkMatrix matrix = fRecordingCanvas->getTotalMatrix();
-    SkRegion clipRegion = fRecordingCanvas->getTotalClip();
+        // TODO: find a way to transfer the state stack and layers
+        // to the new recording canvas.  For now, purging only works
+        // with an empty stack.
+        if (fRecordingCanvas->getSaveCount() == 0) {
 
-    // beginRecording creates a new recording canvas and discards the old one,
-    // hence purging deferred draw ops.
-    fRecordingCanvas = fPicture.beginRecording(fImmediateDevice->width(), 
-        fImmediateDevice->height(),
-        SkPicture::kUsePathBoundsForClip_RecordingFlag);
+            // Save state that is trashed by the purge
+            SkDrawFilter* drawFilter = fRecordingCanvas->getDrawFilter();
+            SkSafeRef(drawFilter); // So that it survives the purge
+            SkMatrix matrix = fRecordingCanvas->getTotalMatrix();
+            SkRegion clipRegion = fRecordingCanvas->getTotalClip();
 
-    // Restore pre-purge state
-    if (!clipRegion.isEmpty()) {
-        fRecordingCanvas->clipRegion(clipRegion, SkRegion::kReplace_Op);
+            // beginRecording creates a new recording canvas and discards the
+            // old one, hence purging deferred draw ops.
+            fRecordingCanvas = fPicture.beginRecording(
+                fImmediateDevice->width(),
+                fImmediateDevice->height(),
+                SkPicture::kUsePathBoundsForClip_RecordingFlag);
+
+            // Restore pre-purge state
+            if (!clipRegion.isEmpty()) {
+                fRecordingCanvas->clipRegion(clipRegion, SkRegion::kReplace_Op);
+            }
+            if (!matrix.isIdentity()) {
+                fRecordingCanvas->setMatrix(matrix);
+            }
+            if (drawFilter) {
+                fRecordingCanvas->setDrawFilter(drawFilter)->unref();
+            }
+        }
     }
-    if (!matrix.isIdentity()) {
-        fRecordingCanvas->setMatrix(matrix);
-    }
-    if (drawFilter) {
-        fRecordingCanvas->setDrawFilter(drawFilter)->unref();
-    }
+}
+
+bool SkDeferredCanvas::DeferredDevice::isFreshFrame() {
+    bool ret = fFreshFrame;
+    fFreshFrame = false;
+    return ret;
 }
 
 void SkDeferredCanvas::DeferredDevice::flushPending()
@@ -576,7 +590,7 @@
 {
     if (x <= 0 && y <= 0 && (x + bitmap.width()) >= width() &&
         (y + bitmap.height()) >= height()) {
-        purgePending();
+        contentsCleared();
     }
 
     if (SkBitmap::kARGB_8888_Config == bitmap.config() &&
diff --git a/tests/DeferredCanvasTest.cpp b/tests/DeferredCanvasTest.cpp
index 14ae1f4..7fbb581 100644
--- a/tests/DeferredCanvasTest.cpp
+++ b/tests/DeferredCanvasTest.cpp
@@ -8,6 +8,7 @@
 #include "Test.h"
 #include "SkBitmap.h"
 #include "SkDeferredCanvas.h"
+#include "SkShader.h"
 
 
 static const int gWidth = 2;
@@ -50,9 +51,135 @@
     REPORTER_ASSERT(reporter, store.getColor(0,0) == 0x00000000); //verify that clear was executed
 }
 
+static void TestDeferredCanvasFreshFrame(skiatest::Reporter* reporter) {
+    SkBitmap store;
+    SkRect fullRect;
+    fullRect.setXYWH(SkIntToScalar(0), SkIntToScalar(0), SkIntToScalar(gWidth),
+        SkIntToScalar(gHeight));
+    SkRect partialRect;
+    partialRect.setXYWH(0, 0, 1, 1);
+    create(&store, SkBitmap::kARGB_8888_Config, 0xFFFFFFFF);
+    SkDevice device(store);
+    SkDeferredCanvas canvas(&device);
+
+    // verify that frame is intially fresh
+    REPORTER_ASSERT(reporter, canvas.getDeferredDevice()->isFreshFrame());
+    // no clearing op since last call to isFreshFrame -> not fresh
+    REPORTER_ASSERT(reporter, !canvas.getDeferredDevice()->isFreshFrame());
+
+    // Verify that clear triggers a fresh frame
+    canvas.clear(0x00000000);
+    REPORTER_ASSERT(reporter, canvas.getDeferredDevice()->isFreshFrame());
+
+    // Verify that clear with saved state triggers a fresh frame
+    canvas.save(SkCanvas::kMatrixClip_SaveFlag);
+    canvas.clear(0x00000000);
+    canvas.restore();
+    REPORTER_ASSERT(reporter, canvas.getDeferredDevice()->isFreshFrame());
+
+    // Verify that clear within a layer does NOT trigger a fresh frame
+    canvas.saveLayer(NULL, NULL, SkCanvas::kARGB_ClipLayer_SaveFlag);
+    canvas.clear(0x00000000);
+    canvas.restore();
+    REPORTER_ASSERT(reporter, !canvas.getDeferredDevice()->isFreshFrame());
+
+    // Verify that a clear with clipping triggers a fresh frame
+    // (clear is not affected by clipping)
+    canvas.save(SkCanvas::kMatrixClip_SaveFlag);
+    canvas.clipRect(partialRect, SkRegion::kIntersect_Op, false);
+    canvas.clear(0x00000000);
+    canvas.restore();
+    REPORTER_ASSERT(reporter, canvas.getDeferredDevice()->isFreshFrame());    
+
+    // Verify that full frame rects with different forms of opaque paint
+    // trigger frames to be marked as fresh
+    {
+        SkPaint paint;
+        paint.setStyle( SkPaint::kFill_Style );
+        paint.setAlpha( 255 );
+        canvas.drawRect(fullRect, paint);
+        REPORTER_ASSERT(reporter, canvas.getDeferredDevice()->isFreshFrame());
+    }
+    {
+        SkPaint paint;
+        paint.setStyle( SkPaint::kFill_Style );
+        SkBitmap bmp;
+        create(&bmp, SkBitmap::kARGB_8888_Config, 0xFFFFFFFF);
+        bmp.setIsOpaque(true);
+        SkShader* shader = SkShader::CreateBitmapShader(bmp, 
+            SkShader::kClamp_TileMode, SkShader::kClamp_TileMode);
+        paint.setShader(shader)->unref();
+        canvas.drawRect(fullRect, paint);
+        REPORTER_ASSERT(reporter, canvas.getDeferredDevice()->isFreshFrame());        
+    }
+
+    // Verify that full frame rects with different forms of non-opaque paint
+    // do not trigger frames to be marked as fresh
+    {
+        SkPaint paint;
+        paint.setStyle( SkPaint::kFill_Style );
+        paint.setAlpha( 254 );
+        canvas.drawRect(fullRect, paint);
+        REPORTER_ASSERT(reporter, !canvas.getDeferredDevice()->isFreshFrame());
+    }
+    {
+        SkPaint paint;
+        paint.setStyle( SkPaint::kFill_Style );
+        SkBitmap bmp;
+        create(&bmp, SkBitmap::kARGB_8888_Config, 0xFFFFFFFF);
+        bmp.setIsOpaque(false);
+        SkShader* shader = SkShader::CreateBitmapShader(bmp, 
+            SkShader::kClamp_TileMode, SkShader::kClamp_TileMode);
+        paint.setShader(shader)->unref();
+        canvas.drawRect(fullRect, paint);
+        REPORTER_ASSERT(reporter, !canvas.getDeferredDevice()->isFreshFrame());        
+    }
+
+    // Verify that incomplete coverage does not trigger a fresh frame
+    {
+        SkPaint paint;
+        paint.setStyle(SkPaint::kFill_Style);
+        paint.setAlpha(255);
+        canvas.drawRect(partialRect, paint);
+        REPORTER_ASSERT(reporter, !canvas.getDeferredDevice()->isFreshFrame());
+    }
+
+    // Verify that incomplete coverage due to clipping does not trigger a fresh
+    // frame
+    {
+        canvas.save(SkCanvas::kMatrixClip_SaveFlag);
+        canvas.clipRect(partialRect, SkRegion::kIntersect_Op, false);
+        SkPaint paint;
+        paint.setStyle(SkPaint::kFill_Style);
+        paint.setAlpha(255);
+        canvas.drawRect(fullRect, paint);
+        REPORTER_ASSERT(reporter, !canvas.getDeferredDevice()->isFreshFrame());
+    }
+
+    // Verify that stroked rect does not trigger a fresh frame
+    {
+        SkPaint paint;
+        paint.setStyle( SkPaint::kStroke_Style );
+        paint.setAlpha( 255 );
+        canvas.drawRect(fullRect, paint);
+        REPORTER_ASSERT(reporter, !canvas.getDeferredDevice()->isFreshFrame());
+    }
+    
+    // Verify kSrcMode triggers a fresh frame even with transparent color
+    {
+        SkPaint paint;
+        paint.setStyle( SkPaint::kFill_Style );
+        paint.setAlpha( 100 );
+        paint.setXfermodeMode(SkXfermode::kSrc_Mode);
+        canvas.drawRect(fullRect, paint);
+        REPORTER_ASSERT(reporter, !canvas.getDeferredDevice()->isFreshFrame());
+    }
+}
+
 static void TestDeferredCanvas(skiatest::Reporter* reporter) {
     TestDeferredCanvasBitmapAccess(reporter);
     TestDeferredCanvasFlush(reporter);
+    TestDeferredCanvasFreshFrame(reporter);
 }
 
 #include "TestClassDef.h"