Adding class SkDeferredCanvas for deferred rendering.

TEST=added a new pass to gm, so all gm tests are run through SkDeferredCanvas
REVIEW=http://codereview.appspot.com/5430058/



git-svn-id: http://skia.googlecode.com/svn/trunk@3059 2bbb7eff-a529-9590-31e7-b0007b416f81
diff --git a/src/utils/SkDeferredCanvas.cpp b/src/utils/SkDeferredCanvas.cpp
new file mode 100644
index 0000000..e92f1f8
--- /dev/null
+++ b/src/utils/SkDeferredCanvas.cpp
@@ -0,0 +1,625 @@
+
+/*
+ * Copyright 2011 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#include "SkDeferredCanvas.h"
+
+#include "SkPaint.h"
+#include "SkShader.h"
+#include "SkColorFilter.h"
+#include "SkDrawFilter.h"
+
+namespace {
+
+bool isPaintOpaque(const SkPaint& paint, const SkBitmap* bmpReplacesShader = NULL) {
+    // TODO: SkXfermode should have a virtual isOpaque method, which would
+    // make it possible to test modes that do not have a Coeff representation.
+    SkXfermode::Coeff srcCoeff, dstCoeff;
+    if (SkXfermode::AsCoeff(paint.getXfermode(), &srcCoeff, &dstCoeff)){
+        switch (dstCoeff) {
+        case SkXfermode::kZero_Coeff:
+            return true;
+        case SkXfermode::kISA_Coeff:
+            if (paint.getAlpha() != 255) {
+                break;
+            }
+            if (bmpReplacesShader) {
+                if (!bmpReplacesShader->isOpaque()) {
+                    break;
+                }
+            } else if (paint.getShader() && !paint.getShader()->isOpaque()) {
+                break;
+            }
+            if (paint.getColorFilter() && ((paint.getColorFilter()->getFlags() &
+                SkColorFilter::kAlphaUnchanged_Flag) == 0)) {
+                break;
+            }
+            return true;
+        case SkXfermode::kSA_Coeff:
+            if (paint.getAlpha() != 0) {
+                break;
+            }
+            if (paint.getColorFilter() && ((paint.getColorFilter()->getFlags() &
+                SkColorFilter::kAlphaUnchanged_Flag) == 0)) {
+                break;
+            }
+            return true;
+        case SkXfermode::kSC_Coeff:
+            if (paint.getColor() != 0) { // all components must be 0
+                break;
+            }
+            if (bmpReplacesShader || paint.getShader()) {
+                break;
+            }
+            if (paint.getColorFilter() && ((paint.getColorFilter()->getFlags() &
+                SkColorFilter::kAlphaUnchanged_Flag) == 0)) {
+                break;
+            }
+            return true;
+        default:
+            break;
+        }
+    }
+    return false;
+}
+
+} // unnamed namespace
+
+SkDeferredCanvas::SkDeferredCanvas()
+{
+    init();
+}
+
+SkDeferredCanvas::SkDeferredCanvas(SkDevice* device)
+{
+    init();
+    setDevice(device);
+}
+
+SkDeferredCanvas::SkDeferredCanvas(SkDevice* device, 
+                                   DeviceContext* deviceContext)
+{
+    init();
+    setDevice(device);
+    setDeviceContext(deviceContext);
+}
+
+void SkDeferredCanvas::init()
+{
+    fDeferredDrawing = true; // On by default
+}
+
+void SkDeferredCanvas::validate() const
+{
+    SkASSERT(getDevice());
+    SkASSERT(INHERITED::getTotalMatrix().isIdentity());
+}
+
+SkCanvas* SkDeferredCanvas::drawingCanvas() const
+{
+    validate();
+    return fDeferredDrawing ? getDeferredDevice()->recordingCanvas() :
+        getDeferredDevice()->immediateCanvas();
+}
+
+void SkDeferredCanvas::flushIfNeeded(const SkBitmap& bitmap)
+{
+    validate();
+    if (fDeferredDrawing) {
+        getDeferredDevice()->flushIfNeeded(bitmap);
+    }
+}
+
+SkDeferredCanvas::DeferredDevice* SkDeferredCanvas::getDeferredDevice() const
+{
+    return static_cast<SkDeferredCanvas::DeferredDevice*>(getDevice());
+}
+
+void SkDeferredCanvas::setDeferredDrawing(bool val)
+{
+    validate(); // Must set device before calling this method
+    SkASSERT(drawingCanvas()->getSaveCount() == 1);
+    if (val != fDeferredDrawing) {
+        if (fDeferredDrawing) {
+            // Going live.
+            getDeferredDevice()->flushPending();
+        }
+        fDeferredDrawing = val;
+    }
+}
+
+SkDeferredCanvas::~SkDeferredCanvas()
+{
+}
+
+SkDevice* SkDeferredCanvas::setDevice(SkDevice* device)
+{
+    INHERITED::setDevice(SkNEW_ARGS(DeferredDevice, (device)))->unref();
+    return device;
+}
+
+SkDeferredCanvas::DeviceContext* SkDeferredCanvas::setDeviceContext(
+    DeviceContext* deviceContext) 
+{
+    DeferredDevice* deferredDevice = getDeferredDevice();
+    SkASSERT(deferredDevice);
+    if (deferredDevice) {
+        deferredDevice->setDeviceContext(deviceContext);
+    }
+    return deviceContext;
+}
+
+bool SkDeferredCanvas::isFullFrame(const SkRect* rect,
+                                   const SkPaint* paint) const
+{
+    SkCanvas* canvas = drawingCanvas();
+    SkISize canvasSize = getDeviceSize();
+    if (rect) {
+        if (!canvas->getTotalMatrix().rectStaysRect()) {
+            return false; // conservative
+        }
+
+        SkRect transformedRect;
+        canvas->getTotalMatrix().mapRect(&transformedRect, *rect);
+
+        if (paint) {
+            SkPaint::Style paintStyle = paint->getStyle();
+            if (!(paintStyle == SkPaint::kFill_Style || 
+                paintStyle == SkPaint::kStrokeAndFill_Style)) {
+                return false;
+            }
+            if (paint->getMaskFilter() || paint->getLooper()
+                || paint->getPathEffect() || paint->getImageFilter()) {
+                return false; // conservative
+            }
+        }
+
+        // The following test holds with AA enabled, and is conservative
+        // by a 0.5 pixel margin with AA disabled
+        if (transformedRect.fLeft > 0 || transformedRect.fTop > 0 || 
+            transformedRect.fRight < canvasSize.fWidth || 
+            transformedRect.fBottom < canvasSize.fHeight) {
+            return false;
+        }
+    }
+
+    switch (canvas->getClipType()) {
+        case SkCanvas::kRect_ClipType :
+            {
+                SkIRect bounds;
+                canvas->getClipDeviceBounds(&bounds);
+                if (bounds.fLeft > 0 || bounds.fTop > 0 || 
+                    bounds.fRight < canvasSize.fWidth || 
+                    bounds.fBottom < canvasSize.fHeight)
+                    return false;
+            }
+            break;
+        case SkCanvas::kComplex_ClipType :
+            return false; // conservative
+        case SkCanvas::kEmpty_ClipType:
+        default:
+            break;
+    };
+
+    return true;
+}
+
+int SkDeferredCanvas::save(SaveFlags flags)
+{
+    return drawingCanvas()->save(flags);
+}
+
+int SkDeferredCanvas::saveLayer(const SkRect* bounds, const SkPaint* paint,
+                          SaveFlags flags)
+{
+    return drawingCanvas()->saveLayer(bounds, paint, flags);
+}
+
+void SkDeferredCanvas::restore()
+{
+    drawingCanvas()->restore();
+}
+
+int SkDeferredCanvas::getSaveCount() const
+{
+    return drawingCanvas()->getSaveCount();
+}
+
+bool SkDeferredCanvas::translate(SkScalar dx, SkScalar dy)
+{
+    return drawingCanvas()->translate(dx, dy);
+}
+
+bool SkDeferredCanvas::scale(SkScalar sx, SkScalar sy)
+{
+    return drawingCanvas()->scale(sx, sy);
+}
+
+bool SkDeferredCanvas::rotate(SkScalar degrees)
+{
+    return drawingCanvas()->rotate(degrees);
+}
+
+bool SkDeferredCanvas::skew(SkScalar sx, SkScalar sy)
+{
+    return drawingCanvas()->skew(sx, sy);
+}
+
+bool SkDeferredCanvas::concat(const SkMatrix& matrix)
+{
+    return drawingCanvas()->concat(matrix);
+}
+
+void SkDeferredCanvas::setMatrix(const SkMatrix& matrix)
+{
+    drawingCanvas()->setMatrix(matrix);
+}
+
+const SkMatrix& SkDeferredCanvas::getTotalMatrix() const 
+{
+    return drawingCanvas()->getTotalMatrix();
+}
+
+bool SkDeferredCanvas::clipRect(const SkRect& rect,
+                                SkRegion::Op op,
+                                bool doAntiAlias)
+{
+    return drawingCanvas()->clipRect(rect, op, doAntiAlias);
+}
+
+bool SkDeferredCanvas::clipPath(const SkPath& path,
+                                SkRegion::Op op,
+                                bool doAntiAlias)
+{
+    return drawingCanvas()->clipPath(path, op, doAntiAlias);
+}
+
+bool SkDeferredCanvas::clipRegion(const SkRegion& deviceRgn,
+                                  SkRegion::Op op)
+{
+    return drawingCanvas()->clipRegion(deviceRgn, op);
+}
+
+void SkDeferredCanvas::clear(SkColor color)
+{
+    // purge pending commands
+    if (fDeferredDrawing) {
+        getDeferredDevice()->purgePending();
+    }
+
+    drawingCanvas()->clear(color);
+}
+
+void SkDeferredCanvas::drawPaint(const SkPaint& paint)
+{
+    if (fDeferredDrawing && isFullFrame(NULL, &paint) && isPaintOpaque(paint)) {
+        getDeferredDevice()->purgePending();
+    }
+
+    drawingCanvas()->drawPaint(paint);
+}
+
+void SkDeferredCanvas::drawPoints(PointMode mode, size_t count,
+                                  const SkPoint pts[], const SkPaint& paint)
+{
+    drawingCanvas()->drawPoints(mode, count, pts, paint);
+}
+
+void SkDeferredCanvas::drawRect(const SkRect& rect, const SkPaint& paint)
+{
+    if (fDeferredDrawing && isFullFrame(&rect, &paint) && isPaintOpaque(paint)) {
+        getDeferredDevice()->purgePending();
+    }
+
+    drawingCanvas()->drawRect(rect, paint);
+}
+
+void SkDeferredCanvas::drawPath(const SkPath& path, const SkPaint& paint)
+{
+    drawingCanvas()->drawPath(path, paint);
+}
+
+void SkDeferredCanvas::drawBitmap(const SkBitmap& bitmap, SkScalar left,
+                                  SkScalar top, const SkPaint* paint)
+{
+    SkRect bitmapRect = SkRect::MakeXYWH(left, top, bitmap.width(),
+        bitmap.height());
+    if (fDeferredDrawing && 
+        isFullFrame(&bitmapRect, paint) &&
+        isPaintOpaque(*paint, &bitmap)) {
+        getDeferredDevice()->purgePending();
+    }
+
+    drawingCanvas()->drawBitmap(bitmap, left, top, paint);
+    flushIfNeeded(bitmap);
+}
+
+void SkDeferredCanvas::drawBitmapRect(const SkBitmap& bitmap, 
+                                      const SkIRect* src,
+                                      const SkRect& dst, const SkPaint* paint)
+{
+    if (fDeferredDrawing && 
+        isFullFrame(&dst, paint) &&
+        isPaintOpaque(*paint, &bitmap)) {
+        getDeferredDevice()->purgePending();
+    }
+
+    drawingCanvas()->drawBitmapRect(bitmap, src,
+                                    dst, paint);
+    flushIfNeeded(bitmap);
+}
+
+
+void SkDeferredCanvas::drawBitmapMatrix(const SkBitmap& bitmap,
+                                        const SkMatrix& m,
+                                        const SkPaint* paint)
+{
+    // TODO: reset recording canvas if paint+bitmap is opaque and clip rect
+    // covers canvas entirely and transformed bitmap covers canvas entirely
+    drawingCanvas()->drawBitmapMatrix(bitmap, m, paint);
+    flushIfNeeded(bitmap);
+}
+
+void SkDeferredCanvas::drawBitmapNine(const SkBitmap& bitmap,
+                                      const SkIRect& center, const SkRect& dst,
+                                      const SkPaint* paint)
+{
+    // TODO: reset recording canvas if paint+bitmap is opaque and clip rect
+    // covers canvas entirely and dst covers canvas entirely
+    drawingCanvas()->drawBitmapNine(bitmap, center,
+                                    dst, paint);
+    flushIfNeeded(bitmap);
+}
+
+void SkDeferredCanvas::drawSprite(const SkBitmap& bitmap, int left, int top,
+                                  const SkPaint* paint)
+{
+    SkRect bitmapRect = SkRect::MakeXYWH(left, top, bitmap.width(),
+        bitmap.height());
+    if (fDeferredDrawing && 
+        isFullFrame(&bitmapRect, paint) &&
+        isPaintOpaque(*paint, &bitmap)) {
+        getDeferredDevice()->purgePending();
+    }
+
+    drawingCanvas()->drawSprite(bitmap, left, top,
+                                paint);
+    flushIfNeeded(bitmap);
+}
+
+void SkDeferredCanvas::drawText(const void* text, size_t byteLength,
+                                SkScalar x, SkScalar y, const SkPaint& paint)
+{
+    drawingCanvas()->drawText(text, byteLength, x,
+                              y, paint);
+}
+
+void SkDeferredCanvas::drawPosText(const void* text, size_t byteLength,
+                                   const SkPoint pos[], const SkPaint& paint)
+{
+    drawingCanvas()->drawPosText(text, byteLength,
+                                 pos, paint);
+}
+
+void SkDeferredCanvas::drawPosTextH(const void* text, size_t byteLength,
+                                    const SkScalar xpos[], SkScalar constY,
+                                    const SkPaint& paint)
+{
+    drawingCanvas()->drawPosTextH(text, byteLength,
+                                  xpos, constY,
+                                  paint);
+}
+
+void SkDeferredCanvas::drawTextOnPath(const void* text, size_t byteLength,
+                                      const SkPath& path,
+                                      const SkMatrix* matrix,
+                                      const SkPaint& paint)
+{
+    drawingCanvas()->drawTextOnPath(text, byteLength,
+                                    path, matrix,
+                                    paint);
+}
+
+void SkDeferredCanvas::drawPicture(SkPicture& picture)
+{
+    drawingCanvas()->drawPicture(picture);
+}
+
+void SkDeferredCanvas::drawVertices(VertexMode vmode, int vertexCount,
+                                    const SkPoint vertices[],
+                                    const SkPoint texs[],
+                                    const SkColor colors[], SkXfermode* xmode,
+                                    const uint16_t indices[], int indexCount,
+                                    const SkPaint& paint)
+{
+    drawingCanvas()->drawVertices(vmode, vertexCount,
+                                  vertices, texs,
+                                  colors, xmode,
+                                  indices, indexCount,
+                                  paint);
+}
+
+SkBounder* SkDeferredCanvas::setBounder(SkBounder* bounder)
+{
+    INHERITED::setBounder(bounder); // So non-virtual getBounder works
+    return drawingCanvas()->setBounder(bounder);
+}
+
+SkDrawFilter* SkDeferredCanvas::setDrawFilter(SkDrawFilter* filter)
+{
+    INHERITED::setDrawFilter(filter); // So non-virtual getDrawFilter works
+    return drawingCanvas()->setDrawFilter(filter); 
+}
+
+SkCanvas* SkDeferredCanvas::canvasForDrawIter() {
+    return drawingCanvas();
+}
+
+// SkDeferredCanvas::DeferredDevice
+//------------------------------------
+
+SkDeferredCanvas::DeferredDevice::DeferredDevice(
+    SkDevice* immediateDevice, DeviceContext* deviceContext) :
+    SkDevice(SkBitmap::kNo_Config, immediateDevice->width(),
+             immediateDevice->height(), immediateDevice->isOpaque())
+{
+    fDeviceContext = deviceContext;
+    SkSafeRef(fDeviceContext);
+    fImmediateDevice = immediateDevice; // ref counted via fImmediateCanvas
+    fImmediateCanvas = SkNEW_ARGS(SkCanvas, (fImmediateDevice));
+    SkSafeRef(fImmediateCanvas); 
+    fRecordingCanvas = fPicture.beginRecording(fImmediateDevice->width(),
+        fImmediateDevice->height(),
+        SkPicture::kUsePathBoundsForClip_RecordingFlag);
+}
+
+SkDeferredCanvas::DeferredDevice::~DeferredDevice()
+{
+    SkSafeUnref(fImmediateCanvas);
+    SkSafeUnref(fDeviceContext);
+}
+    
+void SkDeferredCanvas::DeferredDevice::setDeviceContext(
+    DeviceContext* deviceContext)
+{
+    SkRefCnt_SafeAssign(fDeviceContext, deviceContext);
+}
+
+void SkDeferredCanvas::DeferredDevice::purgePending()
+{
+    // 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;
+
+    // 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();
+
+    // 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();
+    }
+}
+
+void SkDeferredCanvas::DeferredDevice::flushPending()
+{
+    if (fDeviceContext) {
+        fDeviceContext->prepareForDraw();
+    }
+    fPicture.draw(fImmediateCanvas);
+    fRecordingCanvas = fPicture.beginRecording(fImmediateDevice->width(), 
+        fImmediateDevice->height(),
+        SkPicture::kUsePathBoundsForClip_RecordingFlag);
+}
+
+void SkDeferredCanvas::DeferredDevice::flush()
+{
+    flushPending();
+}
+
+void SkDeferredCanvas::DeferredDevice::flushContext()
+{
+    if (fDeviceContext) {
+        fDeviceContext->flush();
+    }
+}
+
+void SkDeferredCanvas::DeferredDevice::flushIfNeeded(const SkBitmap& bitmap)
+{
+    if (bitmap.isImmutable()) {
+        return; // safe to deffer without registering a dependency
+    }
+
+    // For now, drawing a writable bitmap triggers a flush
+    // TODO: implement read-only semantics and auto buffer duplication on write
+    // in SkBitmap/SkPixelRef, which will make deferral possible in this case.
+    flushPending();
+}
+
+uint32_t SkDeferredCanvas::DeferredDevice::getDeviceCapabilities() 
+{ 
+    return fImmediateDevice->getDeviceCapabilities();
+}
+
+int SkDeferredCanvas::DeferredDevice::width() const
+{ 
+    return fImmediateDevice->width();
+}
+
+int SkDeferredCanvas::DeferredDevice::height() const
+{
+    return fImmediateDevice->height(); 
+}
+
+SkGpuRenderTarget* SkDeferredCanvas::DeferredDevice::accessRenderTarget()
+{
+    flushPending();
+    return fImmediateDevice->accessRenderTarget();
+}
+
+void SkDeferredCanvas::DeferredDevice::writePixels(const SkBitmap& bitmap,
+    int x, int y, SkCanvas::Config8888 config8888)
+{
+    if (x <= 0 && y <= 0 && (x + bitmap.width()) >= width() &&
+        (y + bitmap.height()) >= height()) {
+        purgePending();
+    }
+
+    if (SkBitmap::kARGB_8888_Config == bitmap.config() &&
+        SkCanvas::kNative_Premul_Config8888 != config8888 &&
+        kPMColorAlias != config8888) {
+        //Special case config: no deferral
+        flushPending();
+        fImmediateDevice->writePixels(bitmap, x, y, config8888);
+    }
+
+    SkPaint paint;
+    paint.setXfermodeMode(SkXfermode::kSrc_Mode);
+    fRecordingCanvas->drawSprite(bitmap, x, y, &paint);
+    flushIfNeeded(bitmap);
+}
+
+void SkDeferredCanvas::DeferredDevice::onAccessBitmap(SkBitmap* bitmap)
+{
+    SkASSERT(bitmap);
+    flushPending();
+}
+
+SkDevice* SkDeferredCanvas::DeferredDevice::onCreateCompatibleDevice(
+    SkBitmap::Config config, int width, int height, bool isOpaque, Usage usage)
+{
+    // Save layer usage not supported, and not required by SkDeferredCanvas.
+    SkASSERT(usage != kSaveLayer_Usage);
+    // Create a compatible non-deferred device.
+    SkDevice* compatibleDevice = fImmediateDevice->createCompatibleDevice(config, width,
+        height, isOpaque);
+    return SkNEW_ARGS(DeferredDevice, (compatibleDevice, fDeviceContext));
+}
+
+bool SkDeferredCanvas::DeferredDevice::onReadPixels(
+    const SkBitmap& bitmap, int x, int y, SkCanvas::Config8888 config8888)
+{
+    flushPending();
+    return fImmediateCanvas->readPixels(const_cast<SkBitmap*>(&bitmap),
+                                                   x, y, config8888);
+}