| |
| /* |
| * 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" |
| |
| SK_DEFINE_INST_COUNT(SkDeferredCanvas::DeviceContext) |
| |
| 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. |
| |
| if (!paint) { |
| return bmpReplacesShader ? bmpReplacesShader->isOpaque() : true; |
| } |
| |
| 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()); |
| } |
| |
| 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 > SkIntToScalar(0) || |
| transformedRect.fTop > SkIntToScalar(0) || |
| transformedRect.fRight < SkIntToScalar(canvasSize.fWidth) || |
| transformedRect.fBottom < SkIntToScalar(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) { |
| drawingCanvas()->save(flags); |
| return this->INHERITED::save(flags); |
| } |
| |
| int SkDeferredCanvas::saveLayer(const SkRect* bounds, const SkPaint* paint, |
| SaveFlags flags) { |
| drawingCanvas()->saveLayer(bounds, paint, flags); |
| int count = this->INHERITED::save(flags); |
| this->clipRectBounds(bounds, flags, NULL); |
| return count; |
| } |
| |
| void SkDeferredCanvas::restore() { |
| drawingCanvas()->restore(); |
| this->INHERITED::restore(); |
| } |
| |
| bool SkDeferredCanvas::isDrawingToLayer() const { |
| return drawingCanvas()->isDrawingToLayer(); |
| } |
| |
| bool SkDeferredCanvas::translate(SkScalar dx, SkScalar dy) { |
| drawingCanvas()->translate(dx, dy); |
| return this->INHERITED::translate(dx, dy); |
| } |
| |
| bool SkDeferredCanvas::scale(SkScalar sx, SkScalar sy) { |
| drawingCanvas()->scale(sx, sy); |
| return this->INHERITED::scale(sx, sy); |
| } |
| |
| bool SkDeferredCanvas::rotate(SkScalar degrees) { |
| drawingCanvas()->rotate(degrees); |
| return this->INHERITED::rotate(degrees); |
| } |
| |
| bool SkDeferredCanvas::skew(SkScalar sx, SkScalar sy) { |
| drawingCanvas()->skew(sx, sy); |
| return this->INHERITED::skew(sx, sy); |
| } |
| |
| bool SkDeferredCanvas::concat(const SkMatrix& matrix) { |
| drawingCanvas()->concat(matrix); |
| return this->INHERITED::concat(matrix); |
| } |
| |
| void SkDeferredCanvas::setMatrix(const SkMatrix& matrix) { |
| drawingCanvas()->setMatrix(matrix); |
| this->INHERITED::setMatrix(matrix); |
| } |
| |
| bool SkDeferredCanvas::clipRect(const SkRect& rect, |
| SkRegion::Op op, |
| bool doAntiAlias) { |
| drawingCanvas()->clipRect(rect, op, doAntiAlias); |
| return this->INHERITED::clipRect(rect, op, doAntiAlias); |
| } |
| |
| bool SkDeferredCanvas::clipPath(const SkPath& path, |
| SkRegion::Op op, |
| bool doAntiAlias) { |
| drawingCanvas()->clipPath(path, op, doAntiAlias); |
| return this->INHERITED::clipPath(path, op, doAntiAlias); |
| } |
| |
| bool SkDeferredCanvas::clipRegion(const SkRegion& deviceRgn, |
| SkRegion::Op op) { |
| drawingCanvas()->clipRegion(deviceRgn, op); |
| return this->INHERITED::clipRegion(deviceRgn, op); |
| } |
| |
| void SkDeferredCanvas::clear(SkColor color) { |
| // purge pending commands |
| if (fDeferredDrawing) { |
| getDeferredDevice()->contentsCleared(); |
| } |
| |
| drawingCanvas()->clear(color); |
| } |
| |
| void SkDeferredCanvas::drawPaint(const SkPaint& paint) { |
| if (fDeferredDrawing && isFullFrame(NULL, &paint) && |
| isPaintOpaque(&paint)) { |
| getDeferredDevice()->contentsCleared(); |
| } |
| |
| 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()->contentsCleared(); |
| } |
| |
| 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, |
| SkIntToScalar(bitmap.width()), SkIntToScalar(bitmap.height())); |
| if (fDeferredDrawing && |
| isFullFrame(&bitmapRect, paint) && |
| isPaintOpaque(paint, &bitmap)) { |
| getDeferredDevice()->contentsCleared(); |
| } |
| |
| 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()->contentsCleared(); |
| } |
| |
| 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( |
| SkIntToScalar(left), |
| SkIntToScalar(top), |
| SkIntToScalar(bitmap.width()), |
| SkIntToScalar(bitmap.height())); |
| if (fDeferredDrawing && |
| isFullFrame(&bitmapRect, paint) && |
| isPaintOpaque(paint, &bitmap)) { |
| getDeferredDevice()->contentsCleared(); |
| } |
| |
| 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) { |
| drawingCanvas()->setBounder(bounder); |
| return INHERITED::setBounder(bounder); |
| } |
| |
| SkDrawFilter* SkDeferredCanvas::setDrawFilter(SkDrawFilter* filter) { |
| drawingCanvas()->setDrawFilter(filter); |
| return INHERITED::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()) |
| , fFreshFrame(true) { |
| |
| fDeviceContext = deviceContext; |
| SkSafeRef(fDeviceContext); |
| fImmediateDevice = immediateDevice; // ref counted via fImmediateCanvas |
| fImmediateCanvas = SkNEW_ARGS(SkCanvas, (fImmediateDevice)); |
| fRecordingCanvas = fPicture.beginRecording(fImmediateDevice->width(), |
| fImmediateDevice->height(), |
| SkPicture::kFlattenMutableNonTexturePixelRefs_RecordingFlag); |
| } |
| |
| SkDeferredCanvas::DeferredDevice::~DeferredDevice() { |
| SkSafeUnref(fImmediateCanvas); |
| SkSafeUnref(fDeviceContext); |
| } |
| |
| void SkDeferredCanvas::DeferredDevice::setDeviceContext( |
| DeviceContext* deviceContext) { |
| SkRefCnt_SafeAssign(fDeviceContext, deviceContext); |
| } |
| |
| void SkDeferredCanvas::DeferredDevice::contentsCleared() { |
| if (!fRecordingCanvas->isDrawingToLayer()) { |
| fFreshFrame = true; |
| |
| // 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) { |
| |
| // 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::kFlattenMutableNonTexturePixelRefs_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(); |
| } |
| } |
| } |
| } |
| |
| bool SkDeferredCanvas::DeferredDevice::isFreshFrame() { |
| bool ret = fFreshFrame; |
| fFreshFrame = false; |
| return ret; |
| } |
| |
| void SkDeferredCanvas::DeferredDevice::flushPending() { |
| if (!fPicture.hasRecorded()) { |
| return; |
| } |
| if (fDeviceContext) { |
| fDeviceContext->prepareForDraw(); |
| } |
| fPicture.draw(fImmediateCanvas); |
| fRecordingCanvas = fPicture.beginRecording(fImmediateDevice->width(), |
| fImmediateDevice->height(), |
| SkPicture::kFlattenMutableNonTexturePixelRefs_RecordingFlag); |
| } |
| |
| void SkDeferredCanvas::DeferredDevice::flush() { |
| flushPending(); |
| fImmediateCanvas->flush(); |
| } |
| |
| void SkDeferredCanvas::DeferredDevice::flushIfNeeded(const SkBitmap& bitmap) { |
| if (bitmap.isImmutable() || fPicture.willFlattenPixelsOnRecord(bitmap)) { |
| return; // safe to defer. |
| } |
| |
| // 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()) { |
| contentsCleared(); |
| } |
| |
| 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); |
| } |
| |
| const SkBitmap& SkDeferredCanvas::DeferredDevice::onAccessBitmap(SkBitmap*) { |
| flushPending(); |
| return fImmediateDevice->accessBitmap(false); |
| } |
| |
| 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. |
| SkAutoTUnref<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); |
| } |