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);
     }
 }