From 4370aedf7f55af74e9ebb4ad1c2e010c08236dfa Mon Sep 17 00:00:00 2001 From: "junov@google.com" Date: Wed, 18 Jan 2012 16:21:08 +0000 Subject: 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 --- src/utils/SkDeferredCanvas.cpp | 625 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 625 insertions(+) create mode 100644 src/utils/SkDeferredCanvas.cpp (limited to 'src/utils/SkDeferredCanvas.cpp') diff --git a/src/utils/SkDeferredCanvas.cpp b/src/utils/SkDeferredCanvas.cpp new file mode 100644 index 0000000000..e92f1f8079 --- /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(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(&bitmap), + x, y, config8888); +} -- cgit v1.2.3