Improvements/additions to SkImageCache/SkLazyPixelRef.

SkPurgeableImageCache:
New image cache that uses virtual memory to store the pixels. Combines
features of SkAshmemImageCache (which has been removed) with SkPurgeableMemoryBlock, which has android and Mac versions.

SkImageCache:
Modified the API. pinCache now returns a status out parameter which
states whether the pinned memory retained the old data. This allows
allocAndPinCache to only be used for allocations.
Add a new debug only interface to purge unpinned data.
Updates to documentation, clarifying behavior.
Changed CachedStatus to MemoryStatus

SkLruImageCache:
Implement the new function purgeAllUnpinnedCaches and change implementation
of pinCache for the new behavior.

SkLazyPixelRef:
Rewrite onLockPixels to account for the new behavior of pinCache.

BitmapFactoryTest:
Test the new SkPurgeableImageCache.
Write tests which directly test the SkImageCaches.
Create a larger bitmap, since some of the SkImageCaches are designed
to handle large bitmaps.

bench_ and render_pictures:
Consolidate lazy_decode_bitmap into one function.
Allow using a flag to specify using the purgeable image cache.
Clean up some #includes.

Review URL: https://codereview.chromium.org/12433020

git-svn-id: http://skia.googlecode.com/svn/trunk@8207 2bbb7eff-a529-9590-31e7-b0007b416f81
diff --git a/src/lazy/SkBitmapFactory.cpp b/src/lazy/SkBitmapFactory.cpp
index d67e019..60c4993 100644
--- a/src/lazy/SkBitmapFactory.cpp
+++ b/src/lazy/SkBitmapFactory.cpp
@@ -22,17 +22,19 @@
 
 SkBitmapFactory::~SkBitmapFactory() {
     SkSafeUnref(fImageCache);
+    SkSafeUnref(fCacheSelector);
 }
 
 void SkBitmapFactory::setImageCache(SkImageCache *cache) {
     SkRefCnt_SafeAssign(fImageCache, cache);
     if (cache != NULL) {
+        SkSafeUnref(fCacheSelector);
         fCacheSelector = NULL;
     }
 }
 
-void SkBitmapFactory::setCacheSelector(CacheSelector selector) {
-    fCacheSelector = selector;
+void SkBitmapFactory::setCacheSelector(CacheSelector* selector) {
+    SkRefCnt_SafeAssign(fCacheSelector, selector);
     if (selector != NULL) {
         SkSafeUnref(fImageCache);
         fImageCache = NULL;
@@ -63,7 +65,7 @@
     // fImageCache and fCacheSelector are mutually exclusive.
     SkASSERT(NULL == fImageCache || NULL == fCacheSelector);
 
-    SkImageCache* cache = NULL == fCacheSelector ? fImageCache : fCacheSelector(info);
+    SkImageCache* cache = NULL == fCacheSelector ? fImageCache : fCacheSelector->selectCache(info);
 
     if (cache != NULL) {
         // Now set a new LazyPixelRef on dst.
diff --git a/src/lazy/SkLazyPixelRef.cpp b/src/lazy/SkLazyPixelRef.cpp
index 9ae22f2..dc9aef9 100644
--- a/src/lazy/SkLazyPixelRef.cpp
+++ b/src/lazy/SkLazyPixelRef.cpp
@@ -24,7 +24,8 @@
     : INHERITED(NULL)
     , fDecodeProc(proc)
     , fImageCache(cache)
-    , fCacheId(SkImageCache::UNINITIALIZED_ID) {
+    , fCacheId(SkImageCache::UNINITIALIZED_ID)
+    , fRowBytes(0) {
     SkASSERT(fDecodeProc != NULL);
     if (NULL == data) {
         fData = SkData::NewEmpty();
@@ -71,37 +72,54 @@
     if (SkImageCache::UNINITIALIZED_ID == fCacheId) {
         target.fAddr = NULL;
     } else {
-        target.fAddr = fImageCache->pinCache(fCacheId);
-        if (NULL != target.fAddr) {
+        SkImageCache::DataStatus status;
+        target.fAddr = fImageCache->pinCache(fCacheId, &status);
+        if (target.fAddr == NULL) {
+            fCacheId = SkImageCache::UNINITIALIZED_ID;
+        } else {
+            if (SkImageCache::kRetained_DataStatus == status) {
 #if LAZY_CACHE_STATS
-            sk_atomic_inc(&gCacheHits);
+                sk_atomic_inc(&gCacheHits);
 #endif
-            return target.fAddr;
+                return target.fAddr;
+            }
+            SkASSERT(SkImageCache::kUninitialized_DataStatus == status);
         }
+        // Cache miss. Either pinCache returned NULL or it returned a memory address without the old
+        // data
 #if LAZY_CACHE_STATS
         sk_atomic_inc(&gCacheMisses);
 #endif
     }
-    SkASSERT(NULL == target.fAddr);
     SkImage::Info info;
     SkASSERT(fData != NULL && fData->size() > 0);
-    // FIXME: As an optimization, only do this part once.
-    fErrorInDecoding = !fDecodeProc(fData->data(), fData->size(), &info, NULL);
-    if (fErrorInDecoding) {
-        // In case a previous call to allocAndPinCache succeeded.
-        fImageCache->throwAwayCache(fCacheId);
-        fCacheId = SkImageCache::UNINITIALIZED_ID;
-        return NULL;
-    }
-    // Allocate the memory.
-    size_t bytes = ComputeMinRowBytesAndSize(info, &target.fRowBytes);
-
-    target.fAddr = fImageCache->allocAndPinCache(bytes, &fCacheId);
     if (NULL == target.fAddr) {
-        // Space could not be allocated.
-        fCacheId = SkImageCache::UNINITIALIZED_ID;
-        return NULL;
+        // Determine the size of the image in order to determine how much memory to allocate.
+        // FIXME: As an optimization, only do this part once.
+        fErrorInDecoding = !fDecodeProc(fData->data(), fData->size(), &info, NULL);
+        if (fErrorInDecoding) {
+            // We can only reach here if fCacheId was already set to UNINITIALIZED_ID, or if
+            // pinCache returned NULL, in which case it was reset to UNINITIALIZED_ID.
+            SkASSERT(SkImageCache::UNINITIALIZED_ID == fCacheId);
+            return NULL;
+        }
+
+        size_t bytes = ComputeMinRowBytesAndSize(info, &target.fRowBytes);
+        target.fAddr = fImageCache->allocAndPinCache(bytes, &fCacheId);
+        if (NULL == target.fAddr) {
+            // Space could not be allocated.
+            // Just like the last assert, fCacheId must be UNINITIALIZED_ID.
+            SkASSERT(SkImageCache::UNINITIALIZED_ID == fCacheId);
+            return NULL;
+        }
+    } else {
+        // pinCache returned purged memory to which target.fAddr already points. Set
+        // target.fRowBytes properly.
+        target.fRowBytes = fRowBytes;
+        // Assume that the size is correct, since it was determined by this same function
+        // previously.
     }
+    SkASSERT(target.fAddr != NULL);
     SkASSERT(SkImageCache::UNINITIALIZED_ID != fCacheId);
     fErrorInDecoding = !fDecodeProc(fData->data(), fData->size(), &info, &target);
     if (fErrorInDecoding) {
@@ -109,6 +127,8 @@
         fCacheId = SkImageCache::UNINITIALIZED_ID;
         return NULL;
     }
+    // Upon success, store fRowBytes so it can be used in case pinCache later returns purged memory.
+    fRowBytes = target.fRowBytes;
     return target.fAddr;
 }
 
diff --git a/src/lazy/SkLazyPixelRef.h b/src/lazy/SkLazyPixelRef.h
index af85f90..fd41dd4 100644
--- a/src/lazy/SkLazyPixelRef.h
+++ b/src/lazy/SkLazyPixelRef.h
@@ -68,6 +68,7 @@
     SkBitmapFactory::DecodeProc fDecodeProc;
     SkImageCache*               fImageCache;
     intptr_t                    fCacheId;
+    size_t                      fRowBytes;
 
 #if LAZY_CACHE_STATS
     static int32_t              gCacheHits;
diff --git a/src/lazy/SkLruImageCache.cpp b/src/lazy/SkLruImageCache.cpp
index 54f26fb..26f7ef5 100644
--- a/src/lazy/SkLruImageCache.cpp
+++ b/src/lazy/SkLruImageCache.cpp
@@ -74,16 +74,24 @@
 }
 
 #ifdef SK_DEBUG
-SkImageCache::CacheStatus SkLruImageCache::getCacheStatus(intptr_t ID) const {
+SkImageCache::MemoryStatus SkLruImageCache::getMemoryStatus(intptr_t ID) const {
+    if (SkImageCache::UNINITIALIZED_ID == ID) {
+        return SkImageCache::kFreed_MemoryStatus;
+    }
     SkAutoMutexAcquire ac(&fMutex);
     CachedPixels* pixels = this->findByID(ID);
     if (NULL == pixels) {
-        return SkImageCache::kThrownAway_CacheStatus;
+        return SkImageCache::kFreed_MemoryStatus;
     }
     if (pixels->isLocked()) {
-        return SkImageCache::kPinned_CacheStatus;
+        return SkImageCache::kPinned_MemoryStatus;
     }
-    return SkImageCache::kUnpinned_CacheStatus;
+    return SkImageCache::kUnpinned_MemoryStatus;
+}
+
+void SkLruImageCache::purgeAllUnpinnedCaches() {
+    SkAutoMutexAcquire ac(&fMutex);
+    this->purgeTilAtOrBelow(0);
 }
 #endif
 
@@ -108,7 +116,7 @@
     return pixels->getData();
 }
 
-void* SkLruImageCache::pinCache(intptr_t ID) {
+void* SkLruImageCache::pinCache(intptr_t ID, SkImageCache::DataStatus* status) {
     SkASSERT(ID != SkImageCache::UNINITIALIZED_ID);
     SkAutoMutexAcquire ac(&fMutex);
     CachedPixels* pixels = this->findByID(ID);
@@ -119,6 +127,9 @@
         fLRU.remove(pixels);
         fLRU.addToHead(pixels);
     }
+    SkASSERT(status != NULL);
+    // This cache will never return pinned memory whose data has been overwritten.
+    *status = SkImageCache::kRetained_DataStatus;
     pixels->lock();
     return pixels->getData();
 }
@@ -133,6 +144,7 @@
 }
 
 void SkLruImageCache::throwAwayCache(intptr_t ID) {
+    SkASSERT(ID != SkImageCache::UNINITIALIZED_ID);
     SkAutoMutexAcquire ac(&fMutex);
     CachedPixels* pixels = this->findByID(ID);
     if (pixels != NULL) {
@@ -155,9 +167,6 @@
 
 CachedPixels* SkLruImageCache::findByID(intptr_t ID) const {
     // Mutex is already locked.
-    if (SkImageCache::UNINITIALIZED_ID == ID) {
-        return NULL;
-    }
     Iter iter;
     // Start from the head, most recently used.
     CachedPixels* pixels = iter.init(fLRU, Iter::kHead_IterStart);
diff --git a/src/lazy/SkPurgeableImageCache.cpp b/src/lazy/SkPurgeableImageCache.cpp
new file mode 100644
index 0000000..0f2c5e3
--- /dev/null
+++ b/src/lazy/SkPurgeableImageCache.cpp
@@ -0,0 +1,159 @@
+/*
+ * Copyright 2013 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#include "SkThread.h"
+#include "SkPurgeableImageCache.h"
+#include "SkPurgeableMemoryBlock.h"
+
+#ifdef SK_DEBUG
+    #include "SkTSearch.h"
+#endif
+
+SK_DECLARE_STATIC_MUTEX(gPurgeableImageMutex);
+
+SkImageCache* SkPurgeableImageCache::Create() {
+    if (!SkPurgeableMemoryBlock::IsSupported()) {
+        return NULL;
+    }
+    SkAutoMutexAcquire ac(&gPurgeableImageMutex);
+    static SkPurgeableImageCache gCache;
+    gCache.ref();
+    return &gCache;
+}
+
+SkPurgeableImageCache::SkPurgeableImageCache() {}
+
+#ifdef SK_DEBUG
+SkPurgeableImageCache::~SkPurgeableImageCache() {
+    SkASSERT(fRecs.count() == 0);
+}
+#endif
+
+
+void* SkPurgeableImageCache::allocAndPinCache(size_t bytes, intptr_t* ID) {
+    SkAutoMutexAcquire ac(&gPurgeableImageMutex);
+
+    SkPurgeableMemoryBlock* block = SkPurgeableMemoryBlock::Create(bytes);
+    if (NULL == block) {
+        return NULL;
+    }
+
+    SkPurgeableMemoryBlock::PinResult pinResult;
+    void* data = block->pin(&pinResult);
+    if (NULL == data) {
+        SkDELETE(block);
+        return NULL;
+    }
+
+    SkASSERT(ID != NULL);
+    *ID = reinterpret_cast<intptr_t>(block);
+#ifdef SK_DEBUG
+    // Insert into the array of all recs:
+    int index = this->findRec(*ID);
+    SkASSERT(index < 0);
+    fRecs.insert(~index, 1, ID);
+#endif
+    return data;
+}
+
+void* SkPurgeableImageCache::pinCache(intptr_t ID, SkImageCache::DataStatus* status) {
+    SkASSERT(ID != SkImageCache::UNINITIALIZED_ID);
+    SkAutoMutexAcquire ac(&gPurgeableImageMutex);
+
+    SkASSERT(this->findRec(ID) >= 0);
+    SkPurgeableMemoryBlock* block = reinterpret_cast<SkPurgeableMemoryBlock*>(ID);
+    SkPurgeableMemoryBlock::PinResult pinResult;
+    void* data = block->pin(&pinResult);
+    if (NULL == data) {
+        this->removeRec(ID);
+        return NULL;
+    }
+
+    switch (pinResult) {
+        case SkPurgeableMemoryBlock::kRetained_PinResult:
+            *status = SkImageCache::kRetained_DataStatus;
+            break;
+
+        case SkPurgeableMemoryBlock::kUninitialized_PinResult:
+            *status = SkImageCache::kUninitialized_DataStatus;
+            break;
+
+        default:
+            // Invalid value. Treat as a failure to pin.
+            SkASSERT(false);
+            this->removeRec(ID);
+            return NULL;
+    }
+
+    return data;
+}
+
+void SkPurgeableImageCache::releaseCache(intptr_t ID) {
+    SkASSERT(ID != SkImageCache::UNINITIALIZED_ID);
+    SkAutoMutexAcquire ac(&gPurgeableImageMutex);
+
+    SkASSERT(this->findRec(ID) >= 0);
+    SkPurgeableMemoryBlock* block = reinterpret_cast<SkPurgeableMemoryBlock*>(ID);
+    block->unpin();
+}
+
+void SkPurgeableImageCache::throwAwayCache(intptr_t ID) {
+    SkASSERT(ID != SkImageCache::UNINITIALIZED_ID);
+    SkAutoMutexAcquire ac(&gPurgeableImageMutex);
+
+    this->removeRec(ID);
+}
+
+#ifdef SK_DEBUG
+SkImageCache::MemoryStatus SkPurgeableImageCache::getMemoryStatus(intptr_t ID) const {
+    SkAutoMutexAcquire ac(&gPurgeableImageMutex);
+    if (SkImageCache::UNINITIALIZED_ID == ID || this->findRec(ID) < 0) {
+        return SkImageCache::kFreed_MemoryStatus;
+    }
+
+    SkPurgeableMemoryBlock* block = reinterpret_cast<SkPurgeableMemoryBlock*>(ID);
+    if (block->isPinned()) {
+        return SkImageCache::kPinned_MemoryStatus;
+    }
+    return SkImageCache::kUnpinned_MemoryStatus;
+}
+
+void SkPurgeableImageCache::purgeAllUnpinnedCaches() {
+    SkAutoMutexAcquire ac(&gPurgeableImageMutex);
+    if (SkPurgeableMemoryBlock::PlatformSupportsPurgingAllUnpinnedBlocks()) {
+        SkPurgeableMemoryBlock::PurgeAllUnpinnedBlocks();
+    } else {
+        // Go through the blocks, and purge them individually.
+        // Rather than deleting the blocks, which would interfere with further calls, purge them
+        // and keep them around.
+        for (int i = 0; i < fRecs.count(); i++) {
+            SkPurgeableMemoryBlock* block = reinterpret_cast<SkPurgeableMemoryBlock*>(fRecs[i]);
+            if (!block->isPinned()) {
+                if (!block->purge()) {
+                    // FIXME: This should be more meaningful (which one, etc...)
+                    SkDebugf("Failed to purge\n");
+                }
+            }
+        }
+    }
+}
+
+int SkPurgeableImageCache::findRec(intptr_t rec) const {
+    return SkTSearch(fRecs.begin(), fRecs.count(), rec, sizeof(intptr_t));
+}
+#endif
+
+void SkPurgeableImageCache::removeRec(intptr_t ID) {
+#ifdef SK_DEBUG
+    int index = this->findRec(ID);
+    SkASSERT(index >= 0);
+    fRecs.remove(index);
+#endif
+    SkPurgeableMemoryBlock* block = reinterpret_cast<SkPurgeableMemoryBlock*>(ID);
+    SkASSERT(!block->isPinned());
+    SkDELETE(block);
+}
diff --git a/src/ports/SkAshmemImageCache.cpp b/src/ports/SkAshmemImageCache.cpp
deleted file mode 100644
index b7c6c70..0000000
--- a/src/ports/SkAshmemImageCache.cpp
+++ /dev/null
@@ -1,161 +0,0 @@
-/*
- * Copyright 2013 Google Inc.
- *
- * Use of this source code is governed by a BSD-style license that can be
- * found in the LICENSE file.
- */
-
-#include "SkAshmemImageCache.h"
-#include "SkThread.h"
-
-#ifdef SK_DEBUG
-    #include "SkTSearch.h"
-#endif
-
-#include "android/ashmem.h"
-#include <sys/mman.h>
-#include <unistd.h>
-
-
-SkAshmemImageCache::SkAshmemImageCache() {}
-
-SK_DECLARE_STATIC_MUTEX(gAshmemMutex);
-
-SkAshmemImageCache* SkAshmemImageCache::GetAshmemImageCache() {
-    SkAutoMutexAcquire ac(&gAshmemMutex);
-    static SkAshmemImageCache gCache;
-    return &gCache;
-}
-
-#ifdef SK_DEBUG
-SkAshmemImageCache::~SkAshmemImageCache() {
-    SkASSERT(fRecs.count() == 0);
-}
-#endif
-
-// ashmem likes lengths on page boundaries.
-static size_t roundToPageSize(size_t size) {
-    const size_t mask = getpagesize() - 1;
-    size_t newSize = (size + mask) & ~mask;
-    return newSize;
-}
-
-void* SkAshmemImageCache::allocAndPinCache(size_t bytes, intptr_t* ID) {
-    SkASSERT(ID != NULL);
-
-    SkAutoMutexAcquire ac(&gAshmemMutex);
-
-    if (*ID != SkImageCache::UNINITIALIZED_ID) {
-        // This rec was previously allocated, but pinCache subsequently
-        // failed.
-        AshmemRec* pRec = reinterpret_cast<AshmemRec*>(*ID);
-        SkASSERT(roundToPageSize(bytes) == pRec->fSize);
-        SkASSERT(pRec->fFD != -1);
-        (void) ashmem_pin_region(pRec->fFD, 0, 0);
-#ifdef SK_DEBUG
-        pRec->fPinned = true;
-#endif
-        return pRec->fAddr;
-    }
-
-    AshmemRec rec;
-    rec.fSize = roundToPageSize(bytes);
-
-    rec.fFD = ashmem_create_region(NULL, rec.fSize);
-    if (-1 == rec.fFD) {
-        SkDebugf("ashmem_create_region failed\n");
-        return NULL;
-    }
-    int err = ashmem_set_prot_region(rec.fFD, PROT_READ | PROT_WRITE);
-    if (err != 0) {
-        SkDebugf("ashmem_set_prot_region failed\n");
-        close(rec.fFD);
-        return NULL;
-    }
-    rec.fAddr = mmap(NULL, rec.fSize, PROT_READ | PROT_WRITE, MAP_PRIVATE, rec.fFD, 0);
-    if (-1 == (long) rec.fAddr) {
-        SkDebugf("mmap failed\n");
-        close(rec.fFD);
-        return NULL;
-    }
-    (void) ashmem_pin_region(rec.fFD, 0, 0);
-#ifdef SK_DEBUG
-    rec.fPinned = true;
-#endif
-    // In release mode, we do not keep a pointer to this object. It will be destroyed
-    // either when pinCache returns NULL or when throwAwayCache is called.
-    AshmemRec* pRec = SkNEW_ARGS(AshmemRec, (rec));
-    *ID = reinterpret_cast<intptr_t>(pRec);
-#ifdef SK_DEBUG
-    this->appendRec(pRec);
-#endif
-    return rec.fAddr;
-}
-
-void* SkAshmemImageCache::pinCache(intptr_t ID) {
-    SkAutoMutexAcquire ac(&gAshmemMutex);
-    AshmemRec* rec = reinterpret_cast<AshmemRec*>(ID);
-    const int fd = rec->fFD;
-    int pin = ashmem_pin_region(fd, 0, 0);
-    if (ASHMEM_NOT_PURGED == pin) {
-#ifdef SK_DEBUG
-        rec->fPinned = true;
-#endif
-        return rec->fAddr;
-    }
-    ashmem_unpin_region(fd, 0, 0);
-    return NULL;
-}
-
-void SkAshmemImageCache::releaseCache(intptr_t ID) {
-    SkAutoMutexAcquire ac(&gAshmemMutex);
-    AshmemRec* rec = reinterpret_cast<AshmemRec*>(ID);
-    ashmem_unpin_region(rec->fFD, 0, 0);
-#ifdef SK_DEBUG
-    rec->fPinned = false;
-#endif
-}
-
-void SkAshmemImageCache::throwAwayCache(intptr_t ID) {
-    SkAutoMutexAcquire ac(&gAshmemMutex);
-    AshmemRec* rec = reinterpret_cast<AshmemRec*>(ID);
-    munmap(rec->fAddr, rec->fSize);
-    close(rec->fFD);
-#ifdef SK_DEBUG
-    SkASSERT(!rec->fPinned);
-    int index = this->findRec(rec);
-    SkASSERT(index >= 0);
-    fRecs.remove(index);
-#endif
-    SkDELETE(rec);
-}
-
-#ifdef SK_DEBUG
-void SkAshmemImageCache::appendRec(SkAshmemImageCache::AshmemRec* rec) {
-    int index = this->findRec(rec);
-    // Should not already exist.
-    SkASSERT(index < 0);
-    fRecs.insert(~index, 1, &rec);
-}
-
-int SkAshmemImageCache::AshmemRec::Compare(const SkAshmemImageCache::AshmemRec* a,
-                                           const SkAshmemImageCache::AshmemRec* b) {
-    return reinterpret_cast<intptr_t>(a) - reinterpret_cast<intptr_t>(b);
-}
-
-int SkAshmemImageCache::findRec(const SkAshmemImageCache::AshmemRec* rec) const {
-    return SkTSearch<AshmemRec>((const AshmemRec**)fRecs.begin(), fRecs.count(), rec,
-                                sizeof(intptr_t), AshmemRec::Compare);
-}
-
-SkImageCache::CacheStatus SkAshmemImageCache::getCacheStatus(intptr_t ID) const {
-    SkAutoMutexAcquire ac(&gAshmemMutex);
-    AshmemRec* rec = reinterpret_cast<AshmemRec*>(ID);
-    int index = this->findRec(rec);
-    if (index < 0) {
-        return SkImageCache::kThrownAway_CacheStatus;
-    }
-    return rec->fPinned ? SkImageCache::kPinned_CacheStatus
-                        : SkImageCache::kUnpinned_CacheStatus;
-}
-#endif