Adding option to serialize mutable bitmaps in SkPicture
BUG=http://code.google.com/p/chromium/issues/detail?id=115654
REVIEW=http://codereview.appspot.com/6221066/
git-svn-id: http://skia.googlecode.com/svn/trunk@4130 2bbb7eff-a529-9590-31e7-b0007b416f81
diff --git a/include/core/SkFlattenable.h b/include/core/SkFlattenable.h
index dc115fc..253f571 100644
--- a/include/core/SkFlattenable.h
+++ b/include/core/SkFlattenable.h
@@ -267,15 +267,20 @@
SkFactorySet* setFactoryRecorder(SkFactorySet*);
enum Flags {
- kCrossProcess_Flag = 0x01,
+ kCrossProcess_Flag = 0x01,
/**
* Instructs the writer to inline Factory names as there are seen the
* first time (after that we store an index). The pipe code uses this.
*/
- kInlineFactoryNames_Flag = 0x02
+ kInlineFactoryNames_Flag = 0x02,
+ /**
+ * Instructs the writer to always serialize bitmap pixel data.
+ */
+ kForceFlattenBitmapPixels_Flag = 0x04
};
- Flags getFlags() const { return (Flags)fFlags; }
- void setFlags(Flags flags) { fFlags = flags; }
+
+ uint32_t getFlags() const { return fFlags; }
+ void setFlags(uint32_t flags) { fFlags = flags; }
bool isCrossProcess() const {
return SkToBool(fFlags & kCrossProcess_Flag);
@@ -285,7 +290,7 @@
}
bool persistBitmapPixels() const {
- return (fFlags & kCrossProcess_Flag) != 0;
+ return (fFlags & (kCrossProcess_Flag | kForceFlattenBitmapPixels_Flag)) != 0;
}
bool persistTypeface() const { return (fFlags & kCrossProcess_Flag) != 0; }
diff --git a/include/core/SkPicture.h b/include/core/SkPicture.h
index 47a2b95..21a4fcc 100644
--- a/include/core/SkPicture.h
+++ b/include/core/SkPicture.h
@@ -12,6 +12,7 @@
#include "SkRefCnt.h"
+class SkBitmap;
class SkCanvas;
class SkPicturePlayback;
class SkPictureRecord;
@@ -51,7 +52,16 @@
clip-query calls will reflect the path's bounds, not the actual
path.
*/
- kUsePathBoundsForClip_RecordingFlag = 0x01
+ kUsePathBoundsForClip_RecordingFlag = 0x01,
+
+ /* When a draw operation is recorded that has a bitmap parameter, it
+ may be unsafe to defer rendering if source bitmap may be written to
+ between the time of recording and the time of executing the draw
+ operation. This flag specifies that SkPicture should serialize a
+ snapshot of any source bitmaps that reside in RAM and are not
+ marked as immutable, making the draw operation safe for deferral.
+ */
+ kFlattenMutableNonTexturePixelRefs_RecordingFlag = 0x02
};
/** Returns the canvas that records the drawing commands.
@@ -74,6 +84,16 @@
is drawn.
*/
void endRecording();
+
+ /** Returns true if any draw commands have been recorded since the last
+ call to beginRecording.
+ */
+ bool hasRecorded() const;
+
+ /** Returns true if a snapshot of the specified bitmap will be flattened
+ whaen a draw operation using the bitmap is recorded.
+ */
+ bool willFlattenPixelsOnRecord(const SkBitmap&) const;
/** Replays the drawing commands on the specified canvas. This internally
calls endRecording() if that has not already been called.
diff --git a/src/core/SkPicture.cpp b/src/core/SkPicture.cpp
index a3b7396..812ea65 100644
--- a/src/core/SkPicture.cpp
+++ b/src/core/SkPicture.cpp
@@ -162,6 +162,14 @@
return fRecord;
}
+bool SkPicture::hasRecorded() const {
+ return NULL != fRecord && fRecord->writeStream().size() > 0;
+}
+
+bool SkPicture::willFlattenPixelsOnRecord(const SkBitmap& bitmap) const {
+ return NULL != fRecord && fRecord->shouldFlattenPixels(bitmap);
+}
+
SkCanvas* SkPicture::getRecordingCanvas() const {
// will be null if we are not recording
return fRecord;
diff --git a/src/core/SkPictureFlat.cpp b/src/core/SkPictureFlat.cpp
index edbf4e3..14d912d 100644
--- a/src/core/SkPictureFlat.cpp
+++ b/src/core/SkPictureFlat.cpp
@@ -61,7 +61,8 @@
SkFlatData* SkFlatData::Create(SkChunkAlloc* heap, const void* obj,
int index, void (*flattenProc)(SkOrderedWriteBuffer&, const void*),
- SkRefCntSet* refCntRecorder, SkRefCntSet* faceRecorder) {
+ SkRefCntSet* refCntRecorder, SkRefCntSet* faceRecorder,
+ uint32_t writeBufferflags) {
// a buffer of 256 bytes should be sufficient for most paints, regions,
// and matrices.
@@ -73,6 +74,7 @@
if (faceRecorder) {
buffer.setTypefaceRecorder(faceRecorder);
}
+ buffer.setFlags(writeBufferflags);
flattenProc(buffer, obj);
uint32_t size = buffer.size();
diff --git a/src/core/SkPictureFlat.h b/src/core/SkPictureFlat.h
index e0a5b4f..832a645 100644
--- a/src/core/SkPictureFlat.h
+++ b/src/core/SkPictureFlat.h
@@ -168,7 +168,8 @@
static SkFlatData* Create(SkChunkAlloc* heap, const void* obj, int index,
void (*flattenProc)(SkOrderedWriteBuffer&, const void*),
SkRefCntSet* refCntRecorder = NULL,
- SkRefCntSet* faceRecorder = NULL);
+ SkRefCntSet* faceRecorder = NULL,
+ uint32_t writeBufferflags = 0);
void unflatten(void* result,
void (*unflattenProc)(SkOrderedReadBuffer&, void*),
SkRefCntPlayback* refCntPlayback = NULL,
@@ -208,11 +209,11 @@
* the element wasn't previously in the dictionary it is automatically added
*/
int find(const T* element, SkRefCntSet* refCntRecorder = NULL,
- SkRefCntSet* faceRecorder = NULL) {
+ SkRefCntSet* faceRecorder = NULL, uint32_t writeBufferflags = 0) {
if (element == NULL)
return 0;
SkFlatData* flat = SkFlatData::Create(fHeap, element, fNextIndex,
- fFlattenProc, refCntRecorder, faceRecorder);
+ fFlattenProc, refCntRecorder, faceRecorder, writeBufferflags);
int index = SkTSearch<SkFlatData>((const SkFlatData**) fData.begin(),
fData.count(), flat, sizeof(flat), &SkFlatData::Compare);
if (index >= 0) {
diff --git a/src/core/SkPictureRecord.cpp b/src/core/SkPictureRecord.cpp
index 422001b..a499ee1 100644
--- a/src/core/SkPictureRecord.cpp
+++ b/src/core/SkPictureRecord.cpp
@@ -7,6 +7,7 @@
*/
#include "SkPictureRecord.h"
#include "SkTSearch.h"
+#include "SkPixelRef.h"
#define MIN_WRITER_SIZE 16384
#define HEAP_BLOCK_SIZE 4096
@@ -495,6 +496,7 @@
fPathHeap = NULL;
fBitmaps.reset();
+ fPixelRefDictionary.reset();
fMatrices.reset();
fPaints.reset();
fPictureRefs.unrefAll();
@@ -510,7 +512,7 @@
}
void SkPictureRecord::addBitmap(const SkBitmap& bitmap) {
- addInt(fBitmaps.find(&bitmap, &fRCSet));
+ addInt(find(bitmap));
}
void SkPictureRecord::addMatrix(const SkMatrix& matrix) {
@@ -611,6 +613,42 @@
///////////////////////////////////////////////////////////////////////////////
+bool SkPictureRecord::shouldFlattenPixels(const SkBitmap& bitmap) const {
+ return (fRecordFlags &
+ SkPicture::kFlattenMutableNonTexturePixelRefs_RecordingFlag)
+ && !bitmap.isImmutable() && bitmap.pixelRef()
+ && NULL == bitmap.getTexture();
+}
+
+int SkPictureRecord::find(const SkBitmap& bitmap) {
+ int dictionaryIndex = 0;
+ PixelRefDictionaryEntry entry;
+ bool flattenPixels = shouldFlattenPixels(bitmap);
+ if (flattenPixels) {
+ // Flattened bitmap may be very large. First attempt a fast lookup
+ // based on generation ID to avoid unnecessary flattening in
+ // fBitmaps.find()
+ entry.fKey = bitmap.pixelRef()->getGenerationID();
+ dictionaryIndex =
+ SkTSearch<const PixelRefDictionaryEntry>(fPixelRefDictionary.begin(),
+ fPixelRefDictionary.count(), entry, sizeof(entry));
+ if (dictionaryIndex >= 0) {
+ return fPixelRefDictionary[dictionaryIndex].fIndex;
+ }
+ }
+
+ uint32_t writeFlags = flattenPixels ?
+ SkFlattenableWriteBuffer::kForceFlattenBitmapPixels_Flag : 0;
+ int index = fBitmaps.find(&bitmap, &fRCSet, NULL, writeFlags);
+
+ if (flattenPixels) {
+ entry.fIndex = index;
+ dictionaryIndex = ~dictionaryIndex;
+ *fPixelRefDictionary.insert(dictionaryIndex) = entry;
+ }
+ return index;
+}
+
#ifdef SK_DEBUG_SIZE
size_t SkPictureRecord::size() const {
size_t result = 0;
diff --git a/src/core/SkPictureRecord.h b/src/core/SkPictureRecord.h
index 96809bf..4d0e962 100644
--- a/src/core/SkPictureRecord.h
+++ b/src/core/SkPictureRecord.h
@@ -86,12 +86,27 @@
}
void reset();
+ void setFlags(uint32_t recordFlags) {
+ fRecordFlags = recordFlags;
+ }
const SkWriter32& writeStream() const {
return fWriter;
}
+ bool shouldFlattenPixels(const SkBitmap&) const;
private:
+ struct PixelRefDictionaryEntry {
+ uint32_t fKey; // SkPixelRef GenerationID.
+ uint32_t fIndex; // Index of corresponding flattened bitmap in fBitmaps.
+ bool operator < (const PixelRefDictionaryEntry& other) const {
+ return this->fKey < other.fKey;
+ }
+ bool operator != (const PixelRefDictionaryEntry& other) const {
+ return this->fKey != other.fKey;
+ }
+ };
+
SkTDArray<uint32_t> fRestoreOffsetStack;
int fFirstSavedLayerIndex;
enum {
@@ -127,6 +142,8 @@
void addRegion(const SkRegion& region);
void addText(const void* text, size_t byteLength);
+ int find(const SkBitmap& bitmap);
+
#ifdef SK_DEBUG_DUMP
public:
void dumpMatrices();
@@ -163,6 +180,8 @@
private:
SkChunkAlloc fHeap;
+
+ SkTDArray<PixelRefDictionaryEntry> fPixelRefDictionary;
SkBitmapDictionary fBitmaps;
SkMatrixDictionary fMatrices;
SkPaintDictionary fPaints;
diff --git a/src/utils/SkDeferredCanvas.cpp b/src/utils/SkDeferredCanvas.cpp
index e965050..ac2b3cc 100644
--- a/src/utils/SkDeferredCanvas.cpp
+++ b/src/utils/SkDeferredCanvas.cpp
@@ -450,7 +450,8 @@
fImmediateDevice = immediateDevice; // ref counted via fImmediateCanvas
fImmediateCanvas = SkNEW_ARGS(SkCanvas, (fImmediateDevice));
fRecordingCanvas = fPicture.beginRecording(fImmediateDevice->width(),
- fImmediateDevice->height(), 0);
+ fImmediateDevice->height(),
+ SkPicture::kFlattenMutableNonTexturePixelRefs_RecordingFlag);
}
SkDeferredCanvas::DeferredDevice::~DeferredDevice() {
@@ -482,7 +483,8 @@
// old one, hence purging deferred draw ops.
fRecordingCanvas = fPicture.beginRecording(
fImmediateDevice->width(),
- fImmediateDevice->height(), 0);
+ fImmediateDevice->height(),
+ SkPicture::kFlattenMutableNonTexturePixelRefs_RecordingFlag);
// Restore pre-purge state
if (!clipRegion.isEmpty()) {
@@ -506,12 +508,16 @@
}
void SkDeferredCanvas::DeferredDevice::flushPending() {
+ if (!fPicture.hasRecorded()) {
+ return;
+ }
if (fDeviceContext) {
fDeviceContext->prepareForDraw();
}
fPicture.draw(fImmediateCanvas);
fRecordingCanvas = fPicture.beginRecording(fImmediateDevice->width(),
- fImmediateDevice->height(), 0);
+ fImmediateDevice->height(),
+ SkPicture::kFlattenMutableNonTexturePixelRefs_RecordingFlag);
}
void SkDeferredCanvas::DeferredDevice::flush() {
@@ -520,8 +526,8 @@
}
void SkDeferredCanvas::DeferredDevice::flushIfNeeded(const SkBitmap& bitmap) {
- if (bitmap.isImmutable()) {
- return; // safe to deffer without registering a dependency
+ if (bitmap.isImmutable() || fPicture.willFlattenPixelsOnRecord(bitmap)) {
+ return; // safe to defer.
}
// For now, drawing a writable bitmap triggers a flush
diff --git a/tests/CanvasTest.cpp b/tests/CanvasTest.cpp
index 814386e..80f44c6 100644
--- a/tests/CanvasTest.cpp
+++ b/tests/CanvasTest.cpp
@@ -562,17 +562,19 @@
public:
static void TestPictureSerializationRoundTrip(skiatest::Reporter* reporter,
- CanvasTestStep* testStep) {
+ CanvasTestStep* testStep,
+ uint32_t recordFlags) {
testStep->setAssertMessageFormat(kPictureDrawAssertMessageFormat);
SkPicture referencePicture;
- testStep->draw(referencePicture.beginRecording(kWidth, kHeight),
- reporter);
+ testStep->draw(referencePicture.beginRecording(kWidth, kHeight,
+ recordFlags), reporter);
SkPicture initialPicture;
- testStep->draw(initialPicture.beginRecording(kWidth, kHeight),
- reporter);
+ testStep->draw(initialPicture.beginRecording(kWidth, kHeight,
+ recordFlags), reporter);
testStep->setAssertMessageFormat(kPictureReDrawAssertMessageFormat);
SkPicture roundTripPicture;
- initialPicture.draw(roundTripPicture.beginRecording(kWidth, kHeight));
+ initialPicture.draw(roundTripPicture.beginRecording(kWidth, kHeight,
+ recordFlags));
SkPictureRecord* referenceRecord = static_cast<SkPictureRecord*>(
referencePicture.getRecordingCanvas());
@@ -618,17 +620,18 @@
}
static void TestPictureFlattenedObjectReuse(skiatest::Reporter* reporter,
- CanvasTestStep* testStep) {
+ CanvasTestStep* testStep,
+ uint32_t recordFlags) {
// Verify that when a test step is executed twice, no extra resources
// are flattened during the second execution
testStep->setAssertMessageFormat(kPictureDrawAssertMessageFormat);
SkPicture referencePicture;
SkCanvas* referenceCanvas = referencePicture.beginRecording(kWidth,
- kHeight);
+ kHeight, recordFlags);
testStep->draw(referenceCanvas, reporter);
SkPicture testPicture;
SkCanvas* testCanvas = testPicture.beginRecording(kWidth,
- kHeight);
+ kHeight, recordFlags);
testStep->draw(testCanvas, reporter);
testStep->setAssertMessageFormat(kPictureSecondDrawAssertMessageFormat);
testStep->draw(testCanvas, reporter);
@@ -645,11 +648,13 @@
static void TestPictureStateConsistency(skiatest::Reporter* reporter,
CanvasTestStep* testStep,
- const SkCanvas& referenceCanvas) {
+ const SkCanvas& referenceCanvas,
+ uint32_t recordFlags) {
// Verify that the recording canvas's state is consistent
// with that of a regular canvas
SkPicture testPicture;
- SkCanvas* pictureCanvas = testPicture.beginRecording(kWidth, kHeight);
+ SkCanvas* pictureCanvas = testPicture.beginRecording(kWidth, kHeight,
+ recordFlags);
testStep->setAssertMessageFormat(kPictureDrawAssertMessageFormat);
testStep->draw(pictureCanvas, reporter);
testStep->setAssertMessageFormat(kPictureRecoringAssertMessageFormat);
@@ -668,7 +673,8 @@
// The following test code is commented out because SkPicture is not
// currently expected to preserve state when restarting recording.
/*
- SkCanvas* pictureCanvas = testPicture.beginRecording(kWidth, kHeight);
+ SkCanvas* pictureCanvas = testPicture.beginRecording(kWidth, kHeight,
+ recordFlags);
testStep->setAssertMessageFormat(kPictureResumeAssertMessageFormat);
AssertCanvasStatesEqual(reporter, pictureCanvas, &referenceCanvas,
testStep);
@@ -775,7 +781,9 @@
testStep->setAssertMessageFormat(kCanvasDrawAssertMessageFormat);
testStep->draw(&referenceCanvas, reporter);
- TestPictureStateConsistency(reporter, testStep, referenceCanvas);
+ TestPictureStateConsistency(reporter, testStep, referenceCanvas, 0);
+ TestPictureStateConsistency(reporter, testStep, referenceCanvas,
+ SkPicture::kFlattenMutableNonTexturePixelRefs_RecordingFlag);
TestDeferredCanvasStateConsistency(reporter, testStep, referenceCanvas);
// The following test code is commented out because SkProxyCanvas is
@@ -800,9 +808,15 @@
for (int testStep = 0; testStep < testStepArray().count(); testStep++) {
TestOverrideStateConsistency(reporter, testStepArray()[testStep]);
SkPictureTester::TestPictureSerializationRoundTrip(reporter,
- testStepArray()[testStep]);
+ testStepArray()[testStep], 0);
+ SkPictureTester::TestPictureSerializationRoundTrip(reporter,
+ testStepArray()[testStep],
+ SkPicture::kFlattenMutableNonTexturePixelRefs_RecordingFlag);
SkPictureTester::TestPictureFlattenedObjectReuse(reporter,
- testStepArray()[testStep]);
+ testStepArray()[testStep], 0);
+ SkPictureTester::TestPictureFlattenedObjectReuse(reporter,
+ testStepArray()[testStep],
+ SkPicture::kFlattenMutableNonTexturePixelRefs_RecordingFlag);
}
}