diff options
author | 2012-08-07 14:26:57 +0000 | |
---|---|---|
committer | 2012-08-07 14:26:57 +0000 | |
commit | 2e14ba8ceb41c68042ff133fecf0561a2c22efca (patch) | |
tree | 1736e2e6717cdb59d5aeb352a9f948faff69e202 | |
parent | e763951a5c0a9130eb6a7a9f05ab848eb9b3acf8 (diff) |
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
-rw-r--r-- | include/pipe/SkGPipe.h | 11 | ||||
-rw-r--r-- | include/utils/SkDeferredCanvas.h | 17 | ||||
-rw-r--r-- | src/pipe/SkGPipeWrite.cpp | 52 | ||||
-rw-r--r-- | src/utils/SkDeferredCanvas.cpp | 42 | ||||
-rw-r--r-- | tests/DeferredCanvasTest.cpp | 70 |
5 files changed, 184 insertions, 8 deletions
diff --git a/include/pipe/SkGPipe.h b/include/pipe/SkGPipe.h index 0a908d0480..40ab30af7c 100644 --- a/include/pipe/SkGPipe.h +++ b/include/pipe/SkGPipe.h @@ -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; diff --git a/include/utils/SkDeferredCanvas.h b/include/utils/SkDeferredCanvas.h index dab1eb5643..363de1bcf2 100644 --- a/include/utils/SkDeferredCanvas.h +++ b/include/utils/SkDeferredCanvas.h @@ -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); diff --git a/src/pipe/SkGPipeWrite.cpp b/src/pipe/SkGPipeWrite.cpp index 2ea642e30a..e020beae74 100644 --- a/src/pipe/SkGPipeWrite.cpp +++ b/src/pipe/SkGPipeWrite.cpp @@ -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(); } diff --git a/src/utils/SkDeferredCanvas.cpp b/src/utils/SkDeferredCanvas.cpp index 2bcecf97e9..acac47cf06 100644 --- a/src/utils/SkDeferredCanvas.cpp +++ b/src/utils/SkDeferredCanvas.cpp @@ -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; diff --git a/tests/DeferredCanvasTest.cpp b/tests/DeferredCanvasTest.cpp index 7a8ed9d659..701b5cf392 100644 --- a/tests/DeferredCanvasTest.cpp +++ b/tests/DeferredCanvasTest.cpp @@ -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" |