Adding API to SkGPipe and SkDeferredCanvas for controlling memory usage externally
authorjunov@chromium.org <junov@chromium.org@2bbb7eff-a529-9590-31e7-b0007b416f81>
Tue, 7 Aug 2012 14:26:57 +0000 (14:26 +0000)
committerjunov@chromium.org <junov@chromium.org@2bbb7eff-a529-9590-31e7-b0007b416f81>
Tue, 7 Aug 2012 14:26:57 +0000 (14:26 +0000)
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

include/pipe/SkGPipe.h
include/utils/SkDeferredCanvas.h
src/pipe/SkGPipeWrite.cpp
src/utils/SkDeferredCanvas.cpp
tests/DeferredCanvasTest.cpp

index 0a908d0..40ab30a 100644 (file)
@@ -124,7 +124,16 @@ public:
      * Currently only returns the amount used for SkBitmaps, since they are
      * potentially unbounded (if the client is not calling playback).
      */
-    size_t storageAllocatedForRecording();
+    size_t storageAllocatedForRecording() const;
+
+    /**
+     * Attempt to reduce the storage allocated for recording by evicting
+     * cache resources.
+     * @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:
     SkGPipeCanvas* fCanvas;
index dab1eb5..363de1b 100644 (file)
@@ -98,6 +98,21 @@ public:
      */
     void setMaxRecordingStorage(size_t maxStorage);
 
+    /**
+     *  Returns the number of bytes currently allocated for the purpose of
+     *  recording draw commands.
+     */
+    size_t storageAllocatedForRecording() const;
+
+    /**
+     * Attempt to reduce the storage allocated for recording by evicting
+     * cache resources.
+     * @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);
+
     // Overrides of the SkCanvas interface
     virtual int save(SaveFlags flags) SK_OVERRIDE;
     virtual int saveLayer(const SkRect* bounds, const SkPaint* paint,
@@ -241,6 +256,8 @@ public:
          */
         bool isFreshFrame();
 
+        size_t storageAllocatedForRecording() const;
+        size_t freeMemoryIfPossible(size_t bytesToFree);
         void flushPending();
         void contentsCleared();
         void setMaxRecordingStorage(size_t);
index 2ea642e..e020bea 100644 (file)
@@ -290,6 +290,38 @@ public:
         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 @@ public:
     }
 
     void flushRecording(bool detachCurrentBlock);
+    size_t freeMemoryIfPossible(size_t bytesToFree);
 
     size_t storageAllocatedForRecording() {
         return fSharedHeap.bytesAllocated();
@@ -1156,6 +1189,10 @@ void SkGPipeCanvas::flushRecording(bool detachCurrentBlock) {
     }
 }
 
+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::endRecording() {
     }
 }
 
-void SkGPipeWriter::flushRecording(bool detachCurrentBlock){
-    fCanvas->flushRecording(detachCurrentBlock);
+void SkGPipeWriter::flushRecording(bool detachCurrentBlock) {
+    if (fCanvas) {
+        fCanvas->flushRecording(detachCurrentBlock);
+    }
+}
+
+size_t SkGPipeWriter::freeMemoryIfPossible(size_t bytesToFree) {
+    if (fCanvas) {
+        return fCanvas->freeMemoryIfPossible(bytesToFree);
+    }
+    return 0;
 }
 
-size_t SkGPipeWriter::storageAllocatedForRecording() {
+size_t SkGPipeWriter::storageAllocatedForRecording() const {
     return NULL == fCanvas ? 0 : fCanvas->storageAllocatedForRecording();
 }
 
index 2bcecf9..acac47c 100644 (file)
@@ -164,6 +164,18 @@ void SkDeferredCanvas::setMaxRecordingStorage(size_t maxStorage) {
     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 @@ void SkDeferredCanvas::DeferredDevice::flush() {
     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;
index 7a8ed9d..701b5cf 100644 (file)
@@ -216,12 +216,82 @@ static void TestDeferredCanvasMemoryLimit(skiatest::Reporter* reporter) {
 #endif
 }
 
+#if SK_DEFERRED_CANVAS_USES_GPIPE
+static void TestDeferredCanvasBitmapCaching(skiatest::Reporter* reporter) {
+    SkBitmap store;
+    store.setConfig(SkBitmap::kARGB_8888_Config, 100, 100);
+    store.allocPixels();
+    SkDevice device(store);
+    SkDeferredCanvas canvas(&device);
+
+    const int imageCount = 2;
+    SkBitmap sourceImages[imageCount];
+    for (int i = 0; i < imageCount; i++)
+    {
+        sourceImages[i].setConfig(SkBitmap::kARGB_8888_Config, 100, 100);
+        sourceImages[i].allocPixels();
+    }
+
+    size_t bitmapSize = sourceImages[0].getSize();
+
+    canvas.drawBitmap(sourceImages[0], 0, 0, NULL);
+    // stored bitmap + drawBitmap command
+    REPORTER_ASSERT(reporter, canvas.storageAllocatedForRecording() > bitmapSize);
+    
+    // verify that nothing can be freed at this point
+    REPORTER_ASSERT(reporter, 0 == canvas.freeMemoryIfPossible(~0));
+
+    // verify that flush leaves image in cache
+    canvas.flush();
+    REPORTER_ASSERT(reporter, canvas.storageAllocatedForRecording() >= bitmapSize);
+
+    // verify that after a flush, cached image can be freed
+    REPORTER_ASSERT(reporter, canvas.freeMemoryIfPossible(~0) >= bitmapSize);
+
+    // Verify that caching works for avoiding multiple copies of the same bitmap
+    canvas.drawBitmap(sourceImages[0], 0, 0, NULL);
+    canvas.drawBitmap(sourceImages[0], 0, 0, NULL);
+    REPORTER_ASSERT(reporter, canvas.storageAllocatedForRecording() < 2 * bitmapSize);
+
+    // Verify partial eviction based on bytesToFree
+    canvas.drawBitmap(sourceImages[1], 0, 0, NULL);
+    canvas.flush();
+    REPORTER_ASSERT(reporter, canvas.storageAllocatedForRecording() > 2 * bitmapSize);
+    size_t bytesFreed = canvas.freeMemoryIfPossible(1);
+    REPORTER_ASSERT(reporter,  bytesFreed >= bitmapSize);
+    REPORTER_ASSERT(reporter,  bytesFreed < 2*bitmapSize);
+
+    // Verifiy that partial purge works, image zero is in cache but not reffed by 
+    // a pending draw, while image 1 is locked-in.
+    canvas.freeMemoryIfPossible(~0);
+    canvas.drawBitmap(sourceImages[0], 0, 0, NULL);
+    canvas.flush();
+    canvas.drawBitmap(sourceImages[1], 0, 0, NULL);
+    bytesFreed = canvas.freeMemoryIfPossible(~0);
+    // only one bitmap should have been freed.
+    REPORTER_ASSERT(reporter,  bytesFreed >= bitmapSize);
+    REPORTER_ASSERT(reporter,  bytesFreed < 2*bitmapSize);
+    // Clear for next test
+    canvas.flush();
+    canvas.freeMemoryIfPossible(~0);
+    REPORTER_ASSERT(reporter, canvas.storageAllocatedForRecording() < bitmapSize);
+
+    // Verify the image cache is sensitive to genID bumps
+    canvas.drawBitmap(sourceImages[1], 0, 0, NULL);
+    sourceImages[1].notifyPixelsChanged();
+    canvas.drawBitmap(sourceImages[1], 0, 0, NULL);
+    REPORTER_ASSERT(reporter, canvas.storageAllocatedForRecording() > 2*bitmapSize);
+}
+#endif
 
 static void TestDeferredCanvas(skiatest::Reporter* reporter) {
     TestDeferredCanvasBitmapAccess(reporter);
     TestDeferredCanvasFlush(reporter);
     TestDeferredCanvasFreshFrame(reporter);
     TestDeferredCanvasMemoryLimit(reporter);
+#if SK_DEFERRED_CANVAS_USES_GPIPE
+    TestDeferredCanvasBitmapCaching(reporter);
+#endif
 }
 
 #include "TestClassDef.h"