Make SkGPipe use SkBitmapHeap.

In the single process (or hypothetical cross process/shared address
space) mode, SkGPipe now uses SkBitmapHeap instead of SharedHeap.

Still need to use the shared heap for shaders as well as for cross
process.

TEST=PipeTest

Review URL: https://codereview.appspot.com/6461059

git-svn-id: http://skia.googlecode.com/svn/trunk@5008 2bbb7eff-a529-9590-31e7-b0007b416f81
diff --git a/gyp/tests.gyp b/gyp/tests.gyp
index 278dce7..b128a54 100644
--- a/gyp/tests.gyp
+++ b/gyp/tests.gyp
@@ -11,6 +11,7 @@
         '../src/core',
         '../src/effects',
         '../src/pdf',
+        '../src/pipe/utils',
         '../src/utils',
         '../tools/',
       ],
@@ -61,6 +62,7 @@
         '../tests/PathMeasureTest.cpp',
         '../tests/PathTest.cpp',
         '../tests/PDFPrimitivesTest.cpp',
+        '../tests/PipeTest.cpp',
         '../tests/PictureUtilsTest.cpp',
         '../tests/PointTest.cpp',
         '../tests/PremulAlphaRoundTripTest.cpp',
@@ -90,6 +92,9 @@
         '../tests/WritePixelsTest.cpp',
         '../tests/Writer32Test.cpp',
         '../tests/XfermodeTest.cpp',
+
+        # Needed for PipeTest.
+        '../src/pipe/utils/SamplePipeControllers.cpp',
       ],
       'dependencies': [
         'core.gyp:core',
diff --git a/src/core/SkBitmapHeap.cpp b/src/core/SkBitmapHeap.cpp
index 7cff8ea..924264b 100644
--- a/src/core/SkBitmapHeap.cpp
+++ b/src/core/SkBitmapHeap.cpp
@@ -55,9 +55,26 @@
     , fPreferredCount(preferredSize)
     , fOwnerCount(IGNORE_OWNERS)
     , fBytesAllocated(0) {
+    SkSafeRef(storage);
 }
 
 SkBitmapHeap::~SkBitmapHeap() {
+    SkDEBUGCODE(
+    for (int i = 0; i < fStorage.count(); i++) {
+        bool unused = false;
+        for (int j = 0; j < fUnusedSlots.count(); j++) {
+            if (fUnusedSlots[j] == fStorage[i]->fSlot) {
+                unused = true;
+                break;
+            }
+        }
+        if (!unused) {
+            fBytesAllocated -= fStorage[i]->fBytesAllocated;
+        }
+    }
+    fBytesAllocated -= (fStorage.count() * sizeof(SkBitmapHeapEntry));
+    )
+    SkASSERT(0 == fBytesAllocated);
     fStorage.deleteAll();
     SkSafeUnref(fExternalStorage);
 }
@@ -132,15 +149,53 @@
     return NULL;
 }
 
-int SkBitmapHeap::findInLookupTable(const SkBitmap& bitmap, SkBitmapHeapEntry** entry) {
-    LookupEntry indexEntry;
-    indexEntry.fGenerationId = bitmap.getGenerationID();
-    indexEntry.fPixelOffset = bitmap.pixelRefOffset();
-    indexEntry.fWidth = bitmap.width();
-    indexEntry.fHeight = bitmap.height();
+size_t SkBitmapHeap::freeMemoryIfPossible(size_t bytesToFree) {
+    if (UNLIMITED_SIZE == fPreferredCount) {
+        return 0;
+    }
+    SkBitmapHeapEntry* iter = fLeastRecentlyUsed;
+    size_t origBytesAllocated = fBytesAllocated;
+    // Purge starting from LRU until a non-evictable bitmap is found or until
+    // everything is evicted.
+    while (iter && 0 == iter->fRefCount) {
+        SkBitmapHeapEntry* next = iter->fMoreRecentlyUsed;
+        this->removeEntryFromLookupTable(*iter);
+        // Free the pixel memory. removeEntryFromLookupTable already reduced
+        // fBytesAllocated properly.
+        iter->fBitmap.reset();
+        // Add to list of unused slots which can be reused in the future.
+        fUnusedSlots.push(iter->fSlot);
+        // Remove its LRU pointers, so that it does not pretend it is already in
+        // the list the next time it is used.
+        iter->fMoreRecentlyUsed = iter->fLessRecentlyUsed = NULL;
+        iter = next;
+        if (origBytesAllocated - fBytesAllocated >= bytesToFree) {
+            break;
+        }
+    }
+
+    if (fLeastRecentlyUsed != iter) {
+        // There was at least one eviction.
+        fLeastRecentlyUsed = iter;
+        if (NULL == fLeastRecentlyUsed) {
+            // Everything was evicted
+            fMostRecentlyUsed = NULL;
+            fBytesAllocated -= (fStorage.count() * sizeof(SkBitmapHeapEntry));
+            fStorage.deleteAll();
+            fUnusedSlots.reset();
+            SkASSERT(0 == fBytesAllocated);
+        } else {
+            fLeastRecentlyUsed->fLessRecentlyUsed = NULL;
+        }
+    }
+    
+    return origBytesAllocated - fBytesAllocated;
+}
+
+int SkBitmapHeap::findInLookupTable(const LookupEntry& indexEntry, SkBitmapHeapEntry** entry) {
     int index = SkTSearch<const LookupEntry>(fLookupTable.begin(),
-                                                  fLookupTable.count(),
-                                                  indexEntry, sizeof(indexEntry));
+                                             fLookupTable.count(),
+                                             indexEntry, sizeof(indexEntry));
 
     if (index < 0) {
         // insert ourselves into the bitmapIndex
@@ -174,9 +229,25 @@
     return true;
 }
 
+int SkBitmapHeap::removeEntryFromLookupTable(const SkBitmapHeapEntry& entry) {
+    // remove the bitmap index for the deleted entry
+    SkDEBUGCODE(int count = fLookupTable.count();)
+    // FIXME: If copying bitmaps retained the generation ID, we could
+    // just grab the generation ID from entry.fBitmap
+    LookupEntry key(entry.fBitmap, entry.fGenerationID);
+    int index = this->findInLookupTable(key, NULL);
+    // Verify that findInLookupTable found an existing entry rather than adding
+    // a new entry to the lookup table.
+    SkASSERT(count == fLookupTable.count());
+    
+    fLookupTable.remove(index);
+    fBytesAllocated -= entry.fBytesAllocated;
+    return index;
+}
+
 int32_t SkBitmapHeap::insert(const SkBitmap& originalBitmap) {
     SkBitmapHeapEntry* entry = NULL;
-    int searchIndex = this->findInLookupTable(originalBitmap, &entry);
+    int searchIndex = this->findInLookupTable(LookupEntry(originalBitmap), &entry);
 
     // check to see if we already had a copy of the bitmap in the heap
     if (entry) {
@@ -195,13 +266,7 @@
         entry = this->findEntryToReplace(originalBitmap);
         // we found an entry to evict
         if (entry) {
-            // remove the bitmap index for the deleted entry
-            SkDEBUGCODE(int count = fLookupTable.count();)
-            int index = findInLookupTable(entry->fBitmap, NULL);
-            SkASSERT(count == fLookupTable.count());
-
-            fLookupTable.remove(index);
-            fBytesAllocated -= entry->fBytesAllocated;
+            int index = this->removeEntryFromLookupTable(*entry);
 
             // update the current search index now that we have removed one
             if (index < searchIndex) {
@@ -212,10 +277,16 @@
 
     // if we didn't have an entry yet we need to create one
     if (!entry) {
-        entry = SkNEW(SkBitmapHeapEntry);
-        fStorage.append(1, &entry);
-        entry->fSlot = fStorage.count() - 1;
-        fBytesAllocated += sizeof(SkBitmapHeapEntry);
+        if (fPreferredCount != UNLIMITED_SIZE && fUnusedSlots.count() > 0) {
+            int slot;
+            fUnusedSlots.pop(&slot);
+            entry = fStorage[slot];
+        } else {
+            entry = SkNEW(SkBitmapHeapEntry);
+            fStorage.append(1, &entry);
+            entry->fSlot = fStorage.count() - 1;
+            fBytesAllocated += sizeof(SkBitmapHeapEntry);
+        }
     }
 
     // create a copy of the bitmap
@@ -230,9 +301,13 @@
     if (!copySucceeded) {
         // delete the index
         fLookupTable.remove(searchIndex);
-        // free the slot
-        fStorage.remove(entry->fSlot);
-        SkDELETE(entry);
+        // If entry is the last slot in storage, it is safe to delete it.
+        if (fStorage.count() - 1 == entry->fSlot) {
+            // free the slot
+            fStorage.remove(entry->fSlot);
+            fBytesAllocated -= sizeof(SkBitmapHeapEntry);
+            SkDELETE(entry);
+        }
         return INVALID_SLOT;
     }
 
@@ -248,6 +323,8 @@
     // add the bytes from this entry to the total count
     fBytesAllocated += entry->fBytesAllocated;
 
+    entry->fGenerationID = originalBitmap.getGenerationID();
+
     if (fOwnerCount != IGNORE_OWNERS) {
         entry->addReferences(fOwnerCount);
     }
diff --git a/src/core/SkBitmapHeap.h b/src/core/SkBitmapHeap.h
index bcfd039..3c00b52 100644
--- a/src/core/SkBitmapHeap.h
+++ b/src/core/SkBitmapHeap.h
@@ -39,6 +39,7 @@
 
     int32_t fSlot;
     int32_t fRefCount;
+    uint32_t fGenerationID;
 
     SkBitmap fBitmap;
     // Keep track of the bytes allocated for this bitmap. When replacing the
@@ -186,8 +187,22 @@
         return fBytesAllocated;
     }
 
+    /**
+     * Attempt to reduce the storage allocated.
+     * @param bytesToFree minimum number of bytes that should be attempted to
+     *   be freed.
+     * @return number of bytes actually freed.
+     */
+    size_t freeMemoryIfPossible(size_t bytesToFree);
+
 private:
     struct LookupEntry {
+        LookupEntry(const SkBitmap& bm, uint32_t genId = 0) {
+            fGenerationId = 0 == genId ? bm.getGenerationID() : genId;
+            fPixelOffset = bm.pixelRefOffset();
+            fWidth = bm.width();
+            fHeight = bm.height();
+        }
         uint32_t fGenerationId; // SkPixelRef GenerationID.
         size_t fPixelOffset;
         uint32_t fWidth;
@@ -215,14 +230,20 @@
     };
 
     /**
+     * Remove the entry from the lookup table.
+     * @return The index in the lookup table of the entry before removal.
+     */
+    int removeEntryFromLookupTable(const SkBitmapHeapEntry&);
+
+    /**
      * Searches for the bitmap in the lookup table and returns the bitmaps index within the table.
      * If the bitmap was not already in the table it is added.
      *
-     * @param bitmap  The bitmap we using as a key to search the lookup table
+     * @param key    The key to search the lookup table, created from a bitmap.
      * @param entry  A pointer to a SkBitmapHeapEntry* that if non-null AND the bitmap is found
      *               in the lookup table is populated with the entry from the heap storage.
      */
-    int findInLookupTable(const SkBitmap& bitmap, SkBitmapHeapEntry** entry);
+    int findInLookupTable(const LookupEntry& key, SkBitmapHeapEntry** entry);
 
     SkBitmapHeapEntry* findEntryToReplace(const SkBitmap& replacement);
     bool copyBitmap(const SkBitmap& originalBitmap, SkBitmap& copiedBitmap);
@@ -233,6 +254,9 @@
 
     // heap storage
     SkTDArray<SkBitmapHeapEntry*> fStorage;
+    // Used to mark slots in fStorage as deleted without actually deleting
+    // the slot so as not to mess up the numbering.
+    SkTDArray<int> fUnusedSlots;
     ExternalStorage* fExternalStorage;
 
     SkBitmapHeapEntry* fMostRecentlyUsed;
diff --git a/src/pipe/SkGPipePriv.h b/src/pipe/SkGPipePriv.h
index 1eb0604..5caecd8 100644
--- a/src/pipe/SkGPipePriv.h
+++ b/src/pipe/SkGPipePriv.h
@@ -74,6 +74,7 @@
 
     // these are signals to playback, not drawing verbs
     kReportFlags_DrawOp,
+    kShareHeap_DrawOp,
     kDone_DrawOp,
 };
 
diff --git a/src/pipe/SkGPipeRead.cpp b/src/pipe/SkGPipeRead.cpp
index 725a57b..e6013db 100644
--- a/src/pipe/SkGPipeRead.cpp
+++ b/src/pipe/SkGPipeRead.cpp
@@ -7,7 +7,7 @@
  */
 
 
-
+#include "SkBitmapHeap.h"
 #include "SkCanvas.h"
 #include "SkPaint.h"
 #include "SkGPipe.h"
@@ -128,12 +128,22 @@
         return fBitmaps[index - 1];
     }
 
+    void setSharedHeap(SkBitmapHeap* heap) {
+        SkASSERT(!shouldFlattenBitmaps(fFlags) || NULL == heap);
+        SkRefCnt_SafeAssign(fSharedHeap, heap);
+    }
+
+    SkBitmapHeap* getSharedHeap() const {
+        return fSharedHeap;
+    }
+
     void addTypeface() {
         size_t size = fReader->read32();
         const void* data = fReader->skip(SkAlign4(size));
         SkMemoryStream stream(data, size, false);
         *fTypefaces.append() = SkTypeface::Deserialize(&stream);
     }    
+
     void setTypeface(SkPaint* paint, unsigned id) {
         paint->setTypeface(id ? fTypefaces[id - 1] : NULL);
     }
@@ -158,6 +168,8 @@
     SkTDArray<SkTypeface*>    fTypefaces;
     SkTDArray<SkFlattenable::Factory> fFactoryArray;
     SkTDArray<SkBitmap*>      fBitmaps;
+    // Only used when sharing bitmaps with the writer.
+    SkBitmapHeap*             fSharedHeap;
     unsigned                  fFlags;
 };
 
@@ -389,26 +401,27 @@
 public:
     BitmapHolder(SkReader32* reader, uint32_t op32, SkGPipeState* state);
     ~BitmapHolder() {
-        if (fInfo != NULL) {
-            fInfo->decDraws();
+        if (fHeapEntry != NULL) {
+            fHeapEntry->releaseRef();
         }
     }
     const SkBitmap* getBitmap() {
         return fBitmap;
     }
 private:
-    BitmapInfo* fInfo;
-    const SkBitmap* fBitmap;
+    SkBitmapHeapEntry* fHeapEntry;
+    const SkBitmap*    fBitmap;
 };
 
 BitmapHolder::BitmapHolder(SkReader32* reader, uint32_t op32,
                            SkGPipeState* state) {
+    unsigned index = DrawOp_unpackData(op32);
     if (shouldFlattenBitmaps(state->getFlags())) {
-        fInfo = NULL;
-        fBitmap = state->getBitmap(DrawOp_unpackData(op32));
+        fHeapEntry = NULL;
+        fBitmap = state->getBitmap(index);
     } else {
-        fInfo = static_cast<BitmapInfo*>(reader->readPtr());
-        fBitmap = fInfo->fBitmap;
+        fHeapEntry = state->getSharedHeap()->getEntry(index);
+        fBitmap = fHeapEntry->getBitmap();
     }
 }
 
@@ -571,12 +584,17 @@
     (void)reader->skip(bytes);
 }
 
-static void reportflags_rp(SkCanvas*, SkReader32*, uint32_t op32,
+static void reportFlags_rp(SkCanvas*, SkReader32*, uint32_t op32,
                            SkGPipeState* state) {
     unsigned flags = DrawOp_unpackFlags(op32);
     state->setFlags(flags);
 }
 
+static void shareHeap_rp(SkCanvas*, SkReader32* reader, uint32_t,
+                           SkGPipeState* state) {
+    state->setSharedHeap(static_cast<SkBitmapHeap*>(reader->readPtr()));
+}
+
 static void done_rp(SkCanvas*, SkReader32*, uint32_t, SkGPipeState*) {}
 
 typedef void (*ReadProc)(SkCanvas*, SkReader32*, uint32_t op32, SkGPipeState*);
@@ -620,18 +638,20 @@
     def_Bitmap_rp,
     def_Factory_rp,
 
-    reportflags_rp,
+    reportFlags_rp,
+    shareHeap_rp,
     done_rp
 };
 
 ///////////////////////////////////////////////////////////////////////////////
 
-SkGPipeState::SkGPipeState(): fReader(0), fFlags(0) {}
+SkGPipeState::SkGPipeState(): fReader(0), fFlags(0), fSharedHeap(NULL) {}
 
 SkGPipeState::~SkGPipeState() {
     fTypefaces.safeUnrefAll();
     fFlatArray.safeUnrefAll();
     fBitmaps.deleteAll();
+    SkSafeUnref(fSharedHeap);
 }
 
 ///////////////////////////////////////////////////////////////////////////////
diff --git a/src/pipe/SkGPipeWrite.cpp b/src/pipe/SkGPipeWrite.cpp
index e020bea..128a458 100644
--- a/src/pipe/SkGPipeWrite.cpp
+++ b/src/pipe/SkGPipeWrite.cpp
@@ -6,27 +6,26 @@
  * found in the LICENSE file.
  */
 
-
-
+#include "SkBitmapHeap.h"
 #include "SkCanvas.h"
+#include "SkColorFilter.h"
 #include "SkData.h"
+#include "SkDrawLooper.h"
 #include "SkDevice.h"
-#include "SkPaint.h"
-#include "SkPathEffect.h"
 #include "SkGPipe.h"
 #include "SkGPipePriv.h"
 #include "SkImageFilter.h"
+#include "SkMaskFilter.h"
+#include "SkOrderedWriteBuffer.h"
+#include "SkPaint.h"
+#include "SkPathEffect.h"
+#include "SkPictureFlat.h"
+#include "SkRasterizer.h"
+#include "SkShader.h"
 #include "SkStream.h"
 #include "SkTSearch.h"
 #include "SkTypeface.h"
 #include "SkWriter32.h"
-#include "SkColorFilter.h"
-#include "SkDrawLooper.h"
-#include "SkMaskFilter.h"
-#include "SkRasterizer.h"
-#include "SkShader.h"
-#include "SkOrderedWriteBuffer.h"
-#include "SkPictureFlat.h"
 
 static bool isCrossProcess(uint32_t flags) {
     return SkToBool(flags & SkGPipeWriter::kCrossProcess_Flag);
@@ -154,254 +153,6 @@
 
 ///////////////////////////////////////////////////////////////////////////////
 
-/*
- * Shared heap for storing large things that can be shared, for a stream
- * used by multiple readers.
- * TODO: Make the allocations all come from cross process safe address space
- * TODO: Store paths (others?)
- * TODO: Generalize the LRU caching mechanism
- */
-class SharedHeap {
-public:
-    SharedHeap(bool shallow, int numOfReaders)
-        : fBitmapCount(0)
-        , fMostRecentlyUsed(NULL)
-        , fLeastRecentlyUsed(NULL)
-        , fCanDoShallowCopies(shallow)
-        , fNumberOfReaders(numOfReaders)
-        , fBytesAllocated(0) {}
-    ~SharedHeap() {
-        BitmapInfo* iter = fMostRecentlyUsed;
-        while (iter != NULL) {
-            SkDEBUGCODE(fBytesAllocated -= (iter->fBytesAllocated + sizeof(BitmapInfo)));
-            BitmapInfo* next = iter->fLessRecentlyUsed;
-            SkDELETE(iter);
-            fBitmapCount--;
-            iter = next;
-        }
-        SkASSERT(0 == fBitmapCount);
-        SkASSERT(0 == fBytesAllocated);
-    }
-
-    /*
-     * Get the approximate number of bytes allocated.
-     *
-     * Not exact. Some SkBitmaps may share SkPixelRefs, in which case only one
-     * SkBitmap will take the size of the SkPixelRef into account (the first
-     * one). It is possible that the one which accounts for the SkPixelRef has
-     * been removed, in which case we will no longer be counting those bytes.
-     */
-    size_t bytesAllocated() { return fBytesAllocated; }
-
-    /*
-     * Add a copy of a bitmap to the heap.
-     * @param bm The SkBitmap to be copied and placed in the heap.
-     * @return void* Pointer to the BitmapInfo stored in the heap, which
-     *               contains a copy of the SkBitmap. If NULL,
-     *               the bitmap could not be copied.
-     */
-    const void* addBitmap(const SkBitmap& orig) {
-        const uint32_t genID = orig.getGenerationID();
-        SkPixelRef* sharedPixelRef = NULL;
-        // When looking to see if we've previously used this bitmap, start at
-        // the end, assuming that the caller is more likely to reuse a recent
-        // one.
-        BitmapInfo* iter = fMostRecentlyUsed;
-        while (iter != NULL) {
-            if (genID == iter->fGenID) {
-                SkBitmap* storedBitmap = iter->fBitmap;
-                // TODO: Perhaps we can share code with
-                // SkPictureRecord::PixelRefDictionaryEntry/
-                // BitmapIndexCacheEntry so we can do a binary search for a
-                // matching bitmap
-                if (orig.pixelRefOffset() != storedBitmap->pixelRefOffset()
-                    || orig.width() != storedBitmap->width()
-                    || orig.height() != storedBitmap->height()) {
-                    // In this case, the bitmaps share a pixelRef, but have
-                    // different offsets or sizes. Keep track of the other
-                    // bitmap so that instead of making another copy of the
-                    // pixelRef we can use the copy we already made.
-                    sharedPixelRef = storedBitmap->pixelRef();
-                    break;
-                }
-                iter->addDraws(fNumberOfReaders);
-                this->setMostRecentlyUsed(iter);
-                return iter;
-            }
-            iter = iter->fLessRecentlyUsed;
-        }
-        SkAutoRef ar((SkRefCnt*)sharedPixelRef);
-        BitmapInfo* replace = this->bitmapToReplace(orig);
-        SkBitmap* copy;
-        // If the bitmap is mutable, we still need to do a deep copy, since the
-        // caller may modify it afterwards. That said, if the bitmap is mutable,
-        // but has no pixelRef, the copy constructor actually does a deep copy.
-        if (fCanDoShallowCopies && (orig.isImmutable() || !orig.pixelRef())) {
-            if (NULL == replace) {
-                copy = SkNEW_ARGS(SkBitmap, (orig));
-            } else {
-                *replace->fBitmap = orig;
-            }
-        } else {
-            if (sharedPixelRef != NULL) {
-                if (NULL == replace) {
-                    // Do a shallow copy of the bitmap to get the width, height, etc
-                    copy = SkNEW_ARGS(SkBitmap, (orig));
-                    // Replace the pixelRef with the copy that was already made, and
-                    // use the appropriate offset.
-                    copy->setPixelRef(sharedPixelRef, orig.pixelRefOffset());
-                } else {
-                    *replace->fBitmap = orig;
-                    replace->fBitmap->setPixelRef(sharedPixelRef, orig.pixelRefOffset());
-                }
-            } else {
-                if (NULL == replace) {
-                    copy = SkNEW(SkBitmap);
-                    if (!orig.copyTo(copy, orig.getConfig())) {
-                        delete copy;
-                        return NULL;
-                    }
-                } else {
-                    if (!orig.copyTo(replace->fBitmap, orig.getConfig())) {
-                        return NULL;
-                    }
-                }
-            }
-        }
-        BitmapInfo* info;
-        if (NULL == replace) {
-            fBytesAllocated += sizeof(BitmapInfo);
-            info = SkNEW_ARGS(BitmapInfo, (copy, genID, fNumberOfReaders));
-            fBitmapCount++;
-        } else {
-            fBytesAllocated -= replace->fBytesAllocated;
-            replace->fGenID = genID;
-            replace->addDraws(fNumberOfReaders);
-            info = replace;
-        }
-        // Always include the size of the SkBitmap struct.
-        info->fBytesAllocated = sizeof(SkBitmap);
-        // If the SkBitmap does not share an SkPixelRef with an SkBitmap already
-        // in the SharedHeap, also include the size of its pixels.
-        if (NULL == sharedPixelRef) {
-            info->fBytesAllocated += orig.getSize();
-        }
-        fBytesAllocated += info->fBytesAllocated;
-        this->setMostRecentlyUsed(info);
-        return info;
-    }
-
-    size_t freeMemoryIfPossible(size_t bytesToFree) {
-        BitmapInfo* info = fLeastRecentlyUsed;
-        size_t origBytesAllocated = fBytesAllocated;
-        // Purge starting from LRU until a non-evictable bitmap is found
-        // or until everything is evicted.
-        while (info && info->drawCount() == 0) {
-            fBytesAllocated -= (info->fBytesAllocated + sizeof(BitmapInfo));
-            fBitmapCount--;
-            BitmapInfo* nextInfo = info->fMoreRecentlyUsed;
-            SkDELETE(info);
-            info = nextInfo;
-            if ((origBytesAllocated - fBytesAllocated) >= bytesToFree) {
-                break;
-            }
-        }
-
-        if (fLeastRecentlyUsed != info) { // at least one eviction
-            fLeastRecentlyUsed = info;
-            if (NULL != fLeastRecentlyUsed) {
-                fLeastRecentlyUsed->fLessRecentlyUsed = NULL;
-            } else {
-                // everything was evicted
-                fMostRecentlyUsed = NULL;
-                SkASSERT(0 == fBytesAllocated);
-                SkASSERT(0 == fBitmapCount);
-            }
-        }
-
-        return origBytesAllocated - fBytesAllocated;
-    }
-
-private:
-    void setMostRecentlyUsed(BitmapInfo* info);
-    BitmapInfo* bitmapToReplace(const SkBitmap& bm) const;
-
-    int         fBitmapCount;
-    BitmapInfo* fLeastRecentlyUsed;
-    BitmapInfo* fMostRecentlyUsed;
-    const bool  fCanDoShallowCopies;
-    const int   fNumberOfReaders;
-    size_t      fBytesAllocated;
-};
-
-// We just "used" info. Update our LRU accordingly
-void SharedHeap::setMostRecentlyUsed(BitmapInfo* info) {
-    SkASSERT(info != NULL);
-    if (info == fMostRecentlyUsed) {
-        return;
-    }
-    // Remove info from its prior place, and make sure to cover the hole.
-    if (fLeastRecentlyUsed == info) {
-        SkASSERT(info->fMoreRecentlyUsed != NULL);
-        fLeastRecentlyUsed = info->fMoreRecentlyUsed;
-    }
-    if (info->fMoreRecentlyUsed != NULL) {
-        SkASSERT(fMostRecentlyUsed != info);
-        info->fMoreRecentlyUsed->fLessRecentlyUsed = info->fLessRecentlyUsed;
-    }
-    if (info->fLessRecentlyUsed != NULL) {
-        SkASSERT(fLeastRecentlyUsed != info);
-        info->fLessRecentlyUsed->fMoreRecentlyUsed = info->fMoreRecentlyUsed;
-    }
-    info->fMoreRecentlyUsed = NULL;
-    // Set up the head and tail pointers properly.
-    if (fMostRecentlyUsed != NULL) {
-        SkASSERT(NULL == fMostRecentlyUsed->fMoreRecentlyUsed);
-        fMostRecentlyUsed->fMoreRecentlyUsed = info;
-        info->fLessRecentlyUsed = fMostRecentlyUsed;
-    }
-    fMostRecentlyUsed = info;
-    if (NULL == fLeastRecentlyUsed) {
-        fLeastRecentlyUsed = info;
-    }
-}
-
-/**
- * Given a new bitmap to be added to the cache, return an existing one that
- * should be removed to make room, or NULL if there is already room.
- */
-BitmapInfo* SharedHeap::bitmapToReplace(const SkBitmap& bm) const {
-    // Arbitrarily set a limit of 5. We should test to find the best tradeoff
-    // between time and space. A lower limit means that we use less space, but
-    // it also means that we may have to insert the same bitmap into the heap
-    // multiple times (depending on the input), potentially taking more time.
-    // On the other hand, a lower limit also means searching through our stored
-    // bitmaps takes less time.
-    if (fBitmapCount > 5) {
-        BitmapInfo* iter = fLeastRecentlyUsed;
-        while (iter != NULL) {
-            if (iter->drawCount() > 0) {
-                // If the least recently used bitmap has not been drawn by some
-                // reader, then a more recently used one will not have been
-                // drawn yet either.
-                return NULL;
-            }
-            if (bm.pixelRef() != NULL
-                    && bm.pixelRef() == iter->fBitmap->pixelRef()) {
-                // Do not replace a bitmap with a new one using the same
-                // pixel ref. Instead look for a different one that will
-                // potentially free up more space.
-                iter = iter->fMoreRecentlyUsed;
-            } else {
-                return iter;
-            }
-        }
-    }
-    return NULL;
-}
-
-///////////////////////////////////////////////////////////////////////////////
-
 class SkGPipeCanvas : public SkCanvas {
 public:
     SkGPipeCanvas(SkGPipeController*, SkWriter32*, uint32_t flags);
@@ -421,7 +172,12 @@
     size_t freeMemoryIfPossible(size_t bytesToFree);
 
     size_t storageAllocatedForRecording() {
-        return fSharedHeap.bytesAllocated();
+        // FIXME: This can be removed once fSharedHeap is used by cross process
+        // case.
+        if (NULL == fSharedHeap) {
+            return 0;
+        }
+        return fSharedHeap->bytesAllocated();
     }
 
     // overrides from SkCanvas
@@ -481,7 +237,7 @@
     };
     SkNamedFactorySet* fFactorySet;
     int                fFirstSaveLayerStackLevel;
-    SharedHeap         fSharedHeap;
+    SkBitmapHeap*      fSharedHeap;
     SkGPipeController* fController;
     SkWriter32&        fWriter;
     size_t             fBlockSize; // amount allocated for writer
@@ -628,7 +384,6 @@
 SkGPipeCanvas::SkGPipeCanvas(SkGPipeController* controller,
                              SkWriter32* writer, uint32_t flags)
 : fFactorySet(isCrossProcess(flags) ? SkNEW(SkNamedFactorySet) : NULL)
-, fSharedHeap(!isCrossProcess(flags), controller->numberOfReaders())
 , fWriter(*writer)
 , fFlags(flags)
 , fBitmapHeap(BITMAPS_TO_KEEP, fFactorySet)
@@ -649,15 +404,31 @@
     bitmap.setConfig(SkBitmap::kARGB_8888_Config, 32767, 32767);
     SkDevice* device = SkNEW_ARGS(SkDevice, (bitmap));
     this->setDevice(device)->unref();
+
     // Tell the reader the appropriate flags to use.
     if (this->needOpBytes()) {
         this->writeOp(kReportFlags_DrawOp, fFlags, 0);
     }
+    
+    if (shouldFlattenBitmaps(flags)) {
+        // TODO: Use the shared heap for cross process case as well.
+        fSharedHeap = NULL;
+    } else {
+        fSharedHeap = SkNEW_ARGS(SkBitmapHeap, (5, controller->numberOfReaders()));
+        if (this->needOpBytes(sizeof(void*))) {
+            this->writeOp(kShareHeap_DrawOp);
+            fWriter.writePtr(static_cast<void*>(fSharedHeap));
+        }
+    }
+    this->doNotify();
 }
 
 SkGPipeCanvas::~SkGPipeCanvas() {
     this->finish();
     SkSafeUnref(fFactorySet);
+    // FIXME: This can be changed to unref() once fSharedHeap is used by cross
+    // process case.
+    SkSafeUnref(fSharedHeap);
 }
 
 bool SkGPipeCanvas::needOpBytes(size_t needed) {
@@ -931,17 +702,16 @@
                                          unsigned flags,
                                          size_t opBytesNeeded,
                                          const SkPaint* paint) {
-    const void* ptr = fSharedHeap.addBitmap(bm);
-    if (NULL == ptr) {
+    int32_t bitmapIndex = fSharedHeap->insert(bm);
+    if (SkBitmapHeap::INVALID_SLOT == bitmapIndex) {
         return false;
     }
     if (paint != NULL) {
         flags |= kDrawBitmap_HasPaint_DrawOpsFlag;
         this->writePaint(*paint);
     }
-    if (this->needOpBytes(opBytesNeeded + sizeof(void*))) {
-        this->writeOp(op, flags, 0);
-        fWriter.writePtr(const_cast<void*>(ptr));
+    if (this->needOpBytes(opBytesNeeded)) {
+        this->writeOp(op, flags, bitmapIndex);
         return true;
     }
     return false;
@@ -1190,7 +960,12 @@
 }
 
 size_t SkGPipeCanvas::freeMemoryIfPossible(size_t bytesToFree) {
-    return fSharedHeap.freeMemoryIfPossible(bytesToFree);
+    // FIXME: This can be removed once fSharedHeap is used by cross process
+    // case.
+    if (NULL == fSharedHeap) {
+        return 0;
+    }
+    return fSharedHeap->freeMemoryIfPossible(bytesToFree);
 }
 
 ///////////////////////////////////////////////////////////////////////////////
diff --git a/tests/PipeTest.cpp b/tests/PipeTest.cpp
new file mode 100644
index 0000000..3724223
--- /dev/null
+++ b/tests/PipeTest.cpp
@@ -0,0 +1,35 @@
+
+/*
+ * Copyright 2012 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#include "SamplePipeControllers.h"
+#include "SkBitmap.h"
+#include "SkCanvas.h"
+#include "SkGPipe.h"
+#include "Test.h"
+
+// Ensures that the pipe gracefully handles drawing an invalid bitmap.
+static void testDrawingBadBitmap(skiatest::Reporter* reporter, SkCanvas* pipeCanvas) {
+    SkBitmap badBitmap;
+    badBitmap.setConfig(SkBitmap::kNo_Config, 5, 5);
+    pipeCanvas->drawBitmap(badBitmap, 0, 0);
+}
+
+static void test_pipeTests(skiatest::Reporter* reporter) {
+    SkBitmap bitmap;
+    bitmap.setConfig(SkBitmap::kARGB_8888_Config, 64, 64);
+    SkCanvas canvas(bitmap);
+
+    PipeController pipeController(&canvas);
+    SkGPipeWriter writer;
+    SkCanvas* pipeCanvas = writer.startRecording(&pipeController);
+    testDrawingBadBitmap(reporter, pipeCanvas);
+    writer.endRecording();
+}
+
+#include "TestClassDef.h"
+DEFINE_TESTCLASS("PipeTest", PipeTestClass, test_pipeTests)