Adding API to SkGPipe and SkDeferredCanvas for controlling memory usage externally

BUG=http://code.google.com/p/chromium/issues/detail?id=136828
Review URL: https://codereview.appspot.com/6454102

git-svn-id: http://skia.googlecode.com/svn/trunk@4971 2bbb7eff-a529-9590-31e7-b0007b416f81
diff --git a/src/pipe/SkGPipeWrite.cpp b/src/pipe/SkGPipeWrite.cpp
index 2ea642e..e020bea 100644
--- a/src/pipe/SkGPipeWrite.cpp
+++ b/src/pipe/SkGPipeWrite.cpp
@@ -290,6 +290,38 @@
         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;
@@ -386,6 +418,7 @@
     }
 
     void flushRecording(bool detachCurrentBlock);
+    size_t freeMemoryIfPossible(size_t bytesToFree);
 
     size_t storageAllocatedForRecording() {
         return fSharedHeap.bytesAllocated();
@@ -1156,6 +1189,10 @@
     }
 }
 
+size_t SkGPipeCanvas::freeMemoryIfPossible(size_t bytesToFree) {
+    return fSharedHeap.freeMemoryIfPossible(bytesToFree);
+}
+
 ///////////////////////////////////////////////////////////////////////////////
 
 template <typename T> uint32_t castToU32(T value) {
@@ -1316,11 +1353,20 @@
     }
 }
 
-void SkGPipeWriter::flushRecording(bool detachCurrentBlock){
-    fCanvas->flushRecording(detachCurrentBlock);
+void SkGPipeWriter::flushRecording(bool detachCurrentBlock) {
+    if (fCanvas) {
+        fCanvas->flushRecording(detachCurrentBlock);
+    }
 }
 
-size_t SkGPipeWriter::storageAllocatedForRecording() {
+size_t SkGPipeWriter::freeMemoryIfPossible(size_t bytesToFree) {
+    if (fCanvas) {
+        return fCanvas->freeMemoryIfPossible(bytesToFree);
+    }
+    return 0;
+}
+
+size_t SkGPipeWriter::storageAllocatedForRecording() const {
     return NULL == fCanvas ? 0 : fCanvas->storageAllocatedForRecording();
 }
 
diff --git a/src/utils/SkDeferredCanvas.cpp b/src/utils/SkDeferredCanvas.cpp
index 2bcecf9..acac47c 100644
--- a/src/utils/SkDeferredCanvas.cpp
+++ b/src/utils/SkDeferredCanvas.cpp
@@ -164,6 +164,18 @@
     this->getDeferredDevice()->setMaxRecordingStorage(maxStorage);
 }
 
+size_t SkDeferredCanvas::storageAllocatedForRecording() const {
+    return this->getDeferredDevice()->storageAllocatedForRecording();
+}
+
+size_t SkDeferredCanvas::freeMemoryIfPossible(size_t bytesToFree) {
+#if SK_DEFERRED_CANVAS_USES_GPIPE
+    return this->getDeferredDevice()->freeMemoryIfPossible(bytesToFree);
+#else
+    return 0;
+#endif
+}
+
 void SkDeferredCanvas::validate() const {
     SkASSERT(getDevice());
 }
@@ -690,12 +702,34 @@
     fImmediateCanvas->flush();
 }
 
+#if SK_DEFERRED_CANVAS_USES_GPIPE
+size_t SkDeferredCanvas::DeferredDevice::freeMemoryIfPossible(size_t bytesToFree) {
+    return fPipeWriter.freeMemoryIfPossible(bytesToFree);
+}
+#endif
+
+size_t SkDeferredCanvas::DeferredDevice::storageAllocatedForRecording() const {
+#if SK_DEFERRED_CANVAS_USES_GPIPE
+    return (fPipeController.storageAllocatedForRecording()
+            + fPipeWriter.storageAllocatedForRecording());
+#else
+    return 0;
+#endif
+}
+
 SkCanvas* SkDeferredCanvas::DeferredDevice::recordingCanvas() {
 #if SK_DEFERRED_CANVAS_USES_GPIPE
-    if (fPipeController.storageAllocatedForRecording()
-            + fPipeWriter.storageAllocatedForRecording()
-            > fMaxRecordingStorageBytes) {
-        this->flushPending();
+    size_t storageAllocated = this->storageAllocatedForRecording();
+    if (storageAllocated > fMaxRecordingStorageBytes) {
+        // First, attempt to reduce cache without flushing
+        size_t tryFree = storageAllocated - fMaxRecordingStorageBytes;
+        if (this->freeMemoryIfPossible(tryFree) < tryFree) {
+            // Flush is necessary to free more space.
+            this->flushPending();
+            // Free as much as possible to avoid oscillating around fMaxRecordingStorageBytes
+            // which could cause a high flushing frequency.
+            this->freeMemoryIfPossible(~0);
+        }
     }
 #endif
     return fRecordingCanvas;