/*
- * Copyright 2011 Google Inc.
+ * Copyright 2012 Google Inc.
*
* Use of this source code is governed by a BSD-style license that can be
* found in the LICENSE file.
*/
class SK_API SkDeferredCanvas : public SkCanvas {
public:
- class DeviceContext;
+ class NotificationClient;
SkDeferredCanvas();
/** Construct a canvas with the specified device to draw into, and
* a device context. Equivalent to calling default constructor, then
- * setDevice.
+ * setDevice. The canvas takes reference on the device and notification
+ * client.
* @param device Specifies a device for the canvas to draw into.
- * @param deviceContext interface for the device's the graphics context
+ * @param client Interface for dispatching notifications
*/
- explicit SkDeferredCanvas(SkDevice* device, DeviceContext* deviceContext);
+ explicit SkDeferredCanvas(SkDevice* device, NotificationClient* client);
virtual ~SkDeferredCanvas();
/**
* Specify a device to be used by this canvas. Calling setDevice will
- * release the previously set device, if any.
+ * release the previously set device, if any. Takes a reference on the
+ * device.
*
* @param device The device that the canvas will raw into
* @return The device argument, for convenience.
virtual SkDevice* setDevice(SkDevice* device);
/**
- * Specify a deviceContext to be used by this canvas. Calling
- * setDeviceContext will release the previously set deviceContext, if any.
- * A deviceContext must be specified if the device uses a graphics context
- * that requires some form of state initialization prior to drawing
- * and/or explicit flushing to synchronize the execution of rendering
- * operations.
+ * Specify a NotificationClient to be used by this canvas. Calling
+ * setNotificationClient will release the previously set
+ * NotificationClient, if any. Takes a reference on the notification
+ * client.
* Note: Must be called after the device is set with setDevice.
*
- * @deviceContext interface for the device's the graphics context
- * @return The deviceContext argument, for convenience.
+ * @param notificationClient interface for dispatching notifications
+ * @return The notificationClient argument, for convenience.
*/
- DeviceContext* setDeviceContext(DeviceContext* deviceContext);
+ NotificationClient* setNotificationClient(NotificationClient* notificationClient);
+ // Temporarily bootstrapping the deprecated method name
+ NotificationClient* setDeviceContext(NotificationClient* notificationClient) {
+ return setNotificationClient(notificationClient);
+ }
/**
* Enable or disable deferred drawing. When deferral is disabled,
virtual SkDrawFilter* setDrawFilter(SkDrawFilter* filter) SK_OVERRIDE;
public:
- class DeviceContext : public SkRefCnt {
+ class NotificationClient : public SkRefCnt {
public:
- SK_DECLARE_INST_COUNT(DeviceContext)
+ SK_DECLARE_INST_COUNT(NotificationClient)
+ /**
+ * Called before executing one or several draw commands, which means
+ * once per flush when deferred rendering is enabled.
+ */
virtual void prepareForDraw() {}
+
+ /**
+ * Called after a recording a draw command if additional memory
+ * had to be allocated for recording.
+ * @param newAllocatedStorage same value as would be returned by
+ * storageAllocatedForRecording(), for convenience.
+ */
+ virtual void storageAllocatedForRecordingChanged(
+ size_t newAllocatedStorage) {}
+
+ /**
+ * Called after pending draw commands have been flushed
+ */
+ virtual void flushedDrawCommands() {}
private:
typedef SkRefCnt INHERITED;
};
+ // Temporarily bootstrapping the deprecated name for a smooth chromium DEPS roll
+ typedef NotificationClient DeviceContext;
+
protected:
virtual SkCanvas* canvasForDrawIter();
DeferredDevice* getDeferredDevice() const;
private:
+ void recordedDrawCommand();
SkCanvas* drawingCanvas() const;
SkCanvas* immediateCanvas() const;
bool isFullFrame(const SkRect*, const SkPaint*) const;
#include "SkPaint.h"
#include "SkShader.h"
-SK_DEFINE_INST_COUNT(SkDeferredCanvas::DeviceContext)
+SK_DEFINE_INST_COUNT(SkDeferredCanvas::NotificationClient)
enum {
// Deferred canvas will auto-flush when recording reaches this limit
class DeferredDevice : public SkDevice {
public:
DeferredDevice(SkDevice* immediateDevice,
- SkDeferredCanvas::DeviceContext* deviceContext = NULL);
+ SkDeferredCanvas::NotificationClient* notificationClient = NULL);
~DeferredDevice();
- void setDeviceContext(SkDeferredCanvas::DeviceContext* deviceContext);
+ void setNotificationClient(SkDeferredCanvas::NotificationClient* notificationClient);
SkCanvas* recordingCanvas();
SkCanvas* immediateCanvas() const {return fImmediateCanvas;}
SkDevice* immediateDevice() const {return fImmediateDevice;}
void flushPending();
void contentsCleared();
void setMaxRecordingStorage(size_t);
+ void recordedDrawCommand();
virtual uint32_t getDeviceCapabilities() SK_OVERRIDE;
virtual int width() const SK_OVERRIDE;
SkDevice* fImmediateDevice;
SkCanvas* fImmediateCanvas;
SkCanvas* fRecordingCanvas;
- SkDeferredCanvas::DeviceContext* fDeviceContext;
+ SkDeferredCanvas::NotificationClient* fNotificationClient;
bool fFreshFrame;
size_t fMaxRecordingStorageBytes;
+ size_t fPreviousStorageAllocated;
};
DeferredDevice::DeferredDevice(
- SkDevice* immediateDevice, SkDeferredCanvas::DeviceContext* deviceContext) :
+ SkDevice* immediateDevice, SkDeferredCanvas::NotificationClient* notificationClient) :
SkDevice(SkBitmap::kNo_Config, immediateDevice->width(),
immediateDevice->height(), immediateDevice->isOpaque())
- , fFreshFrame(true) {
+ , fFreshFrame(true)
+ , fPreviousStorageAllocated(0){
fMaxRecordingStorageBytes = kDefaultMaxRecordingStorageBytes;
- fDeviceContext = deviceContext;
- SkSafeRef(fDeviceContext);
+ fNotificationClient = notificationClient;
+ SkSafeRef(fNotificationClient);
fImmediateDevice = immediateDevice; // ref counted via fImmediateCanvas
fImmediateCanvas = SkNEW_ARGS(SkCanvas, (fImmediateDevice));
fPipeController.setPlaybackCanvas(fImmediateCanvas);
DeferredDevice::~DeferredDevice() {
this->flushPending();
SkSafeUnref(fImmediateCanvas);
- SkSafeUnref(fDeviceContext);
+ SkSafeUnref(fNotificationClient);
}
void DeferredDevice::setMaxRecordingStorage(size_t maxStorage) {
fRecordingCanvas = fPipeWriter.startRecording(&fPipeController, 0);
}
-void DeferredDevice::setDeviceContext(
- SkDeferredCanvas::DeviceContext* deviceContext) {
- SkRefCnt_SafeAssign(fDeviceContext, deviceContext);
+void DeferredDevice::setNotificationClient(
+ SkDeferredCanvas::NotificationClient* notificationClient) {
+ SkRefCnt_SafeAssign(fNotificationClient, notificationClient);
}
void DeferredDevice::contentsCleared() {
// old one, hence purging deferred draw ops.
this->endRecording();
this->beginRecording();
+ fPreviousStorageAllocated = storageAllocatedForRecording();
// Restore pre-purge state
if (!clipRegion.isEmpty()) {
if (!fPipeController.hasRecorded()) {
return;
}
- if (fDeviceContext) {
- fDeviceContext->prepareForDraw();
+ if (fNotificationClient) {
+ fNotificationClient->prepareForDraw();
}
-
fPipeWriter.flushRecording(true);
fPipeController.playback();
+ if (fNotificationClient) {
+ fNotificationClient->flushedDrawCommands();
+ }
+ fPreviousStorageAllocated = storageAllocatedForRecording();
}
void DeferredDevice::flush() {
}
size_t DeferredDevice::freeMemoryIfPossible(size_t bytesToFree) {
- return fPipeWriter.freeMemoryIfPossible(bytesToFree);
+ size_t val = fPipeWriter.freeMemoryIfPossible(bytesToFree);
+ fPreviousStorageAllocated = storageAllocatedForRecording();
+ return val;
}
size_t DeferredDevice::storageAllocatedForRecording() const {
+ fPipeWriter.storageAllocatedForRecording());
}
-SkCanvas* DeferredDevice::recordingCanvas() {
+void DeferredDevice::recordedDrawCommand() {
size_t storageAllocated = this->storageAllocatedForRecording();
+
if (storageAllocated > fMaxRecordingStorageBytes) {
// First, attempt to reduce cache without flushing
size_t tryFree = storageAllocated - fMaxRecordingStorageBytes;
// which could cause a high flushing frequency.
this->freeMemoryIfPossible(~0);
}
+ storageAllocated = this->storageAllocatedForRecording();
}
+
+ if (fNotificationClient &&
+ storageAllocated != fPreviousStorageAllocated) {
+ fPreviousStorageAllocated = storageAllocated;
+ fNotificationClient->storageAllocatedForRecordingChanged(storageAllocated);
+ }
+}
+
+SkCanvas* DeferredDevice::recordingCanvas() {
return fRecordingCanvas;
}
fImmediateCanvas->drawSprite(bitmap, x, y, &paint);
} else {
this->recordingCanvas()->drawSprite(bitmap, x, y, &paint);
+ this->recordedDrawCommand();
+
}
}
SkAutoTUnref<SkDevice> compatibleDevice
(fImmediateDevice->createCompatibleDevice(config, width, height,
isOpaque));
- return SkNEW_ARGS(DeferredDevice, (compatibleDevice, fDeviceContext));
+ return SkNEW_ARGS(DeferredDevice, (compatibleDevice, fNotificationClient));
}
bool DeferredDevice::onReadPixels(
}
SkDeferredCanvas::SkDeferredCanvas(SkDevice* device,
- DeviceContext* deviceContext) {
+ NotificationClient* notificationClient) {
this->init();
this->setDevice(device);
- this->setDeviceContext(deviceContext);
+ this->setNotificationClient(notificationClient);
}
void SkDeferredCanvas::init() {
return this->getDeferredDevice()->freeMemoryIfPossible(bytesToFree);
}
+void SkDeferredCanvas::recordedDrawCommand() {
+ if (fDeferredDrawing) {
+ this->getDeferredDevice()->recordedDrawCommand();
+ }
+}
+
void SkDeferredCanvas::validate() const {
SkASSERT(this->getDevice());
}
return device;
}
-SkDeferredCanvas::DeviceContext* SkDeferredCanvas::setDeviceContext(
- DeviceContext* deviceContext) {
+SkDeferredCanvas::NotificationClient* SkDeferredCanvas::setNotificationClient(
+ NotificationClient* notificationClient) {
DeferredDevice* deferredDevice = this->getDeferredDevice();
SkASSERT(deferredDevice);
if (deferredDevice) {
- deferredDevice->setDeviceContext(deviceContext);
+ deferredDevice->setNotificationClient(notificationClient);
}
- return deviceContext;
+ return notificationClient;
}
bool SkDeferredCanvas::isFullFrame(const SkRect* rect,
int SkDeferredCanvas::save(SaveFlags flags) {
this->drawingCanvas()->save(flags);
- return this->INHERITED::save(flags);
+ int val = this->INHERITED::save(flags);
+ this->recordedDrawCommand();
+
+ return val;
}
int SkDeferredCanvas::saveLayer(const SkRect* bounds, const SkPaint* paint,
this->drawingCanvas()->saveLayer(bounds, paint, flags);
int count = this->INHERITED::save(flags);
this->clipRectBounds(bounds, flags, NULL);
+ this->recordedDrawCommand();
+
return count;
}
void SkDeferredCanvas::restore() {
this->drawingCanvas()->restore();
this->INHERITED::restore();
+ this->recordedDrawCommand();
}
bool SkDeferredCanvas::isDrawingToLayer() const {
bool SkDeferredCanvas::translate(SkScalar dx, SkScalar dy) {
this->drawingCanvas()->translate(dx, dy);
- return this->INHERITED::translate(dx, dy);
+ bool val = this->INHERITED::translate(dx, dy);
+ this->recordedDrawCommand();
+ return val;
}
bool SkDeferredCanvas::scale(SkScalar sx, SkScalar sy) {
this->drawingCanvas()->scale(sx, sy);
- return this->INHERITED::scale(sx, sy);
+ bool val = this->INHERITED::scale(sx, sy);
+ this->recordedDrawCommand();
+ return val;
}
bool SkDeferredCanvas::rotate(SkScalar degrees) {
this->drawingCanvas()->rotate(degrees);
- return this->INHERITED::rotate(degrees);
+ bool val = this->INHERITED::rotate(degrees);
+ this->recordedDrawCommand();
+ return val;
}
bool SkDeferredCanvas::skew(SkScalar sx, SkScalar sy) {
this->drawingCanvas()->skew(sx, sy);
- return this->INHERITED::skew(sx, sy);
+ bool val = this->INHERITED::skew(sx, sy);
+ this->recordedDrawCommand();
+ return val;
}
bool SkDeferredCanvas::concat(const SkMatrix& matrix) {
this->drawingCanvas()->concat(matrix);
- return this->INHERITED::concat(matrix);
+ bool val = this->INHERITED::concat(matrix);
+ this->recordedDrawCommand();
+ return val;
}
void SkDeferredCanvas::setMatrix(const SkMatrix& matrix) {
this->drawingCanvas()->setMatrix(matrix);
this->INHERITED::setMatrix(matrix);
+ this->recordedDrawCommand();
}
bool SkDeferredCanvas::clipRect(const SkRect& rect,
SkRegion::Op op,
bool doAntiAlias) {
this->drawingCanvas()->clipRect(rect, op, doAntiAlias);
- return this->INHERITED::clipRect(rect, op, doAntiAlias);
+ bool val = this->INHERITED::clipRect(rect, op, doAntiAlias);
+ this->recordedDrawCommand();
+ return val;
}
bool SkDeferredCanvas::clipPath(const SkPath& path,
SkRegion::Op op,
bool doAntiAlias) {
this->drawingCanvas()->clipPath(path, op, doAntiAlias);
- return this->INHERITED::clipPath(path, op, doAntiAlias);
+ bool val = this->INHERITED::clipPath(path, op, doAntiAlias);
+ this->recordedDrawCommand();
+ return val;
}
bool SkDeferredCanvas::clipRegion(const SkRegion& deviceRgn,
SkRegion::Op op) {
this->drawingCanvas()->clipRegion(deviceRgn, op);
- return this->INHERITED::clipRegion(deviceRgn, op);
+ bool val = this->INHERITED::clipRegion(deviceRgn, op);
+ this->recordedDrawCommand();
+ return val;
}
void SkDeferredCanvas::clear(SkColor color) {
}
this->drawingCanvas()->clear(color);
+ this->recordedDrawCommand();
}
void SkDeferredCanvas::drawPaint(const SkPaint& paint) {
}
AutoImmediateDrawIfNeeded autoDraw(*this, &paint);
this->drawingCanvas()->drawPaint(paint);
+ this->recordedDrawCommand();
}
void SkDeferredCanvas::drawPoints(PointMode mode, size_t count,
const SkPoint pts[], const SkPaint& paint) {
AutoImmediateDrawIfNeeded autoDraw(*this, &paint);
this->drawingCanvas()->drawPoints(mode, count, pts, paint);
+ this->recordedDrawCommand();
}
void SkDeferredCanvas::drawRect(const SkRect& rect, const SkPaint& paint) {
AutoImmediateDrawIfNeeded autoDraw(*this, &paint);
this->drawingCanvas()->drawRect(rect, paint);
+ this->recordedDrawCommand();
}
void SkDeferredCanvas::drawPath(const SkPath& path, const SkPaint& paint) {
AutoImmediateDrawIfNeeded autoDraw(*this, &paint);
this->drawingCanvas()->drawPath(path, paint);
+ this->recordedDrawCommand();
}
void SkDeferredCanvas::drawBitmap(const SkBitmap& bitmap, SkScalar left,
AutoImmediateDrawIfNeeded autoDraw(*this, &bitmap, paint);
this->drawingCanvas()->drawBitmap(bitmap, left, top, paint);
+ this->recordedDrawCommand();
}
void SkDeferredCanvas::drawBitmapRect(const SkBitmap& bitmap,
AutoImmediateDrawIfNeeded autoDraw(*this, &bitmap, paint);
this->drawingCanvas()->drawBitmapRect(bitmap, src, dst, paint);
+ this->recordedDrawCommand();
}
// covers canvas entirely and transformed bitmap covers canvas entirely
AutoImmediateDrawIfNeeded autoDraw(*this, &bitmap, paint);
this->drawingCanvas()->drawBitmapMatrix(bitmap, m, paint);
+ this->recordedDrawCommand();
}
void SkDeferredCanvas::drawBitmapNine(const SkBitmap& bitmap,
// covers canvas entirely and dst covers canvas entirely
AutoImmediateDrawIfNeeded autoDraw(*this, &bitmap, paint);
this->drawingCanvas()->drawBitmapNine(bitmap, center, dst, paint);
+ this->recordedDrawCommand();
}
void SkDeferredCanvas::drawSprite(const SkBitmap& bitmap, int left, int top,
AutoImmediateDrawIfNeeded autoDraw(*this, &bitmap, paint);
this->drawingCanvas()->drawSprite(bitmap, left, top, paint);
+ this->recordedDrawCommand();
}
void SkDeferredCanvas::drawText(const void* text, size_t byteLength,
SkScalar x, SkScalar y, const SkPaint& paint) {
AutoImmediateDrawIfNeeded autoDraw(*this, &paint);
this->drawingCanvas()->drawText(text, byteLength, x, y, paint);
+ this->recordedDrawCommand();
}
void SkDeferredCanvas::drawPosText(const void* text, size_t byteLength,
const SkPoint pos[], const SkPaint& paint) {
AutoImmediateDrawIfNeeded autoDraw(*this, &paint);
this->drawingCanvas()->drawPosText(text, byteLength, pos, paint);
+ this->recordedDrawCommand();
}
void SkDeferredCanvas::drawPosTextH(const void* text, size_t byteLength,
const SkPaint& paint) {
AutoImmediateDrawIfNeeded autoDraw(*this, &paint);
this->drawingCanvas()->drawPosTextH(text, byteLength, xpos, constY, paint);
+ this->recordedDrawCommand();
}
void SkDeferredCanvas::drawTextOnPath(const void* text, size_t byteLength,
const SkPaint& paint) {
AutoImmediateDrawIfNeeded autoDraw(*this, &paint);
this->drawingCanvas()->drawTextOnPath(text, byteLength, path, matrix, paint);
+ this->recordedDrawCommand();
}
void SkDeferredCanvas::drawPicture(SkPicture& picture) {
this->drawingCanvas()->drawPicture(picture);
+ this->recordedDrawCommand();
}
void SkDeferredCanvas::drawVertices(VertexMode vmode, int vertexCount,
AutoImmediateDrawIfNeeded autoDraw(*this, &paint);
this->drawingCanvas()->drawVertices(vmode, vertexCount, vertices, texs, colors, xmode,
indices, indexCount, paint);
+ this->recordedDrawCommand();
}
SkBounder* SkDeferredCanvas::setBounder(SkBounder* bounder) {
this->drawingCanvas()->setBounder(bounder);
- return this->INHERITED::setBounder(bounder);
+ this->INHERITED::setBounder(bounder);
+ this->recordedDrawCommand();
+ return bounder;
}
SkDrawFilter* SkDeferredCanvas::setDrawFilter(SkDrawFilter* filter) {
this->drawingCanvas()->setDrawFilter(filter);
- return this->INHERITED::setDrawFilter(filter);
+ this->INHERITED::setDrawFilter(filter);
+ this->recordedDrawCommand();
+ return filter;
}
SkCanvas* SkDeferredCanvas::canvasForDrawIter() {
REPORTER_ASSERT(reporter, mockDevice.fDrawBitmapCallCount == 4);
}
+class NotificationCounter : public SkDeferredCanvas::NotificationClient {
+public:
+ NotificationCounter() {
+ fPrepareForDrawCount = fStorageAllocatedChangedCount = fFlushedDrawCommandsCount = 0;
+ }
+
+ virtual void prepareForDraw() SK_OVERRIDE {
+ fPrepareForDrawCount++;
+ }
+ virtual void storageAllocatedForRecordingChanged(size_t size) SK_OVERRIDE {
+ fStorageAllocatedChangedCount++;
+ }
+ virtual void flushedDrawCommands() SK_OVERRIDE {
+ fFlushedDrawCommandsCount++;
+ }
+
+ int fPrepareForDrawCount;
+ int fStorageAllocatedChangedCount;
+ int fFlushedDrawCommandsCount;
+};
+
static void TestDeferredCanvasBitmapCaching(skiatest::Reporter* reporter) {
SkBitmap store;
store.setConfig(SkBitmap::kARGB_8888_Config, 100, 100);
store.allocPixels();
SkDevice device(store);
- SkDeferredCanvas canvas(&device);
+ NotificationCounter notificationCounter;
+ SkDeferredCanvas canvas(&device, ¬ificationCounter);
const int imageCount = 2;
SkBitmap sourceImages[imageCount];
size_t bitmapSize = sourceImages[0].getSize();
canvas.drawBitmap(sourceImages[0], 0, 0, NULL);
+ REPORTER_ASSERT(reporter, 1 == notificationCounter.fStorageAllocatedChangedCount);
// stored bitmap + drawBitmap command
REPORTER_ASSERT(reporter, canvas.storageAllocatedForRecording() > bitmapSize);
REPORTER_ASSERT(reporter, 0 == canvas.freeMemoryIfPossible(~0));
// verify that flush leaves image in cache
+ REPORTER_ASSERT(reporter, 0 == notificationCounter.fFlushedDrawCommandsCount);
+ REPORTER_ASSERT(reporter, 0 == notificationCounter.fPrepareForDrawCount);
canvas.flush();
+ REPORTER_ASSERT(reporter, 1 == notificationCounter.fFlushedDrawCommandsCount);
+ REPORTER_ASSERT(reporter, 1 == notificationCounter.fPrepareForDrawCount);
REPORTER_ASSERT(reporter, canvas.storageAllocatedForRecording() >= bitmapSize);
// verify that after a flush, cached image can be freed
// Verify that caching works for avoiding multiple copies of the same bitmap
canvas.drawBitmap(sourceImages[0], 0, 0, NULL);
+ REPORTER_ASSERT(reporter, 2 == notificationCounter.fStorageAllocatedChangedCount);
canvas.drawBitmap(sourceImages[0], 0, 0, NULL);
+ REPORTER_ASSERT(reporter, 2 == notificationCounter.fStorageAllocatedChangedCount);
+ REPORTER_ASSERT(reporter, 1 == notificationCounter.fFlushedDrawCommandsCount);
REPORTER_ASSERT(reporter, canvas.storageAllocatedForRecording() < 2 * bitmapSize);
// Verify partial eviction based on bytesToFree
canvas.drawBitmap(sourceImages[1], 0, 0, NULL);
+ REPORTER_ASSERT(reporter, 1 == notificationCounter.fFlushedDrawCommandsCount);
canvas.flush();
+ REPORTER_ASSERT(reporter, 2 == notificationCounter.fFlushedDrawCommandsCount);
REPORTER_ASSERT(reporter, canvas.storageAllocatedForRecording() > 2 * bitmapSize);
size_t bytesFreed = canvas.freeMemoryIfPossible(1);
+ REPORTER_ASSERT(reporter, 2 == notificationCounter.fFlushedDrawCommandsCount);
REPORTER_ASSERT(reporter, bytesFreed >= bitmapSize);
REPORTER_ASSERT(reporter, bytesFreed < 2*bitmapSize);
// Verifiy that partial purge works, image zero is in cache but not reffed by
// a pending draw, while image 1 is locked-in.
canvas.freeMemoryIfPossible(~0);
+ REPORTER_ASSERT(reporter, 2 == notificationCounter.fFlushedDrawCommandsCount);
canvas.drawBitmap(sourceImages[0], 0, 0, NULL);
canvas.flush();
canvas.drawBitmap(sourceImages[1], 0, 0, NULL);