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"