aboutsummaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorGravatar junov@chromium.org <junov@chromium.org@2bbb7eff-a529-9590-31e7-b0007b416f81>2012-08-07 14:26:57 +0000
committerGravatar junov@chromium.org <junov@chromium.org@2bbb7eff-a529-9590-31e7-b0007b416f81>2012-08-07 14:26:57 +0000
commit2e14ba8ceb41c68042ff133fecf0561a2c22efca (patch)
tree1736e2e6717cdb59d5aeb352a9f948faff69e202
parente763951a5c0a9130eb6a7a9f05ab848eb9b3acf8 (diff)
Adding API to SkGPipe and SkDeferredCanvas for controlling memory usage externally
-rw-r--r--include/pipe/SkGPipe.h11
-rw-r--r--include/utils/SkDeferredCanvas.h17
-rw-r--r--src/pipe/SkGPipeWrite.cpp52
-rw-r--r--src/utils/SkDeferredCanvas.cpp42
-rw-r--r--tests/DeferredCanvasTest.cpp70
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"