Allow the resource cache to (indirectly) flush the InOrderDrawBuffer

R=bsalomon@google.com

Author: robertphillips@google.com

Review URL: https://chromiumcodereview.appspot.com/18466005

git-svn-id: http://skia.googlecode.com/svn/trunk@9949 2bbb7eff-a529-9590-31e7-b0007b416f81
diff --git a/include/gpu/GrContext.h b/include/gpu/GrContext.h
index 9f3c9f6..09be365 100644
--- a/include/gpu/GrContext.h
+++ b/include/gpu/GrContext.h
@@ -929,6 +929,12 @@
                                            bool swapRAndB,
                                            const SkMatrix& matrix);
 
+    /**
+     *  This callback allows the resource cache to callback into the GrContext
+     *  when the cache is still overbudget after a purge.
+     */
+    static bool OverbudgetCB(void* data);
+
     typedef GrRefCnt INHERITED;
 };
 
diff --git a/src/gpu/GrContext.cpp b/src/gpu/GrContext.cpp
index 78220c0..39d0db3 100644
--- a/src/gpu/GrContext.cpp
+++ b/src/gpu/GrContext.cpp
@@ -541,6 +541,20 @@
     }
 }
 
+bool GrContext::OverbudgetCB(void* data) {
+    GrAssert(NULL != data);
+
+    GrContext* context = reinterpret_cast<GrContext*>(data);
+
+    // Flush the InOrderDrawBuffer to possibly free up some textures
+    context->flush();
+
+    // TODO: actually track flush's behavior rather than always just
+    // returning true.
+    return true;
+}
+
+
 GrTexture* GrContext::createUncachedTexture(const GrTextureDesc& descIn,
                                             void* srcData,
                                             size_t rowBytes) {
diff --git a/src/gpu/GrResourceCache.cpp b/src/gpu/GrResourceCache.cpp
index fa4edad..baae6bd 100644
--- a/src/gpu/GrResourceCache.cpp
+++ b/src/gpu/GrResourceCache.cpp
@@ -62,7 +62,10 @@
     fClientDetachedCount          = 0;
     fClientDetachedBytes          = 0;
 
-    fPurging = false;
+    fPurging                      = false;
+
+    fOverbudgetCB                 = NULL;
+    fOverbudgetData               = NULL;
 }
 
 GrResourceCache::~GrResourceCache() {
@@ -275,48 +278,66 @@
  * potentially make purgeAsNeeded loop infinitely.
  */
 void GrResourceCache::purgeAsNeeded() {
-    if (!fPurging) {
-        fPurging = true;
-        bool withinBudget = false;
-        bool changed = false;
-
-        // The purging process is repeated several times since one pass
-        // may free up other resources
-        do {
-            EntryList::Iter iter;
-
-            changed = false;
-
-            // Note: the following code relies on the fact that the
-            // doubly linked list doesn't invalidate its data/pointers
-            // outside of the specific area where a deletion occurs (e.g.,
-            // in internalDetach)
-            GrResourceEntry* entry = iter.init(fList, EntryList::Iter::kTail_IterStart);
-
-            while (NULL != entry) {
-                GrAutoResourceCacheValidate atcv(this);
-
-                if (fEntryCount <= fMaxCount && fEntryBytes <= fMaxBytes) {
-                    withinBudget = true;
-                    break;
-                }
-
-                GrResourceEntry* prev = iter.prev();
-                if (1 == entry->fResource->getRefCnt()) {
-                    changed = true;
-
-                    // remove from our cache
-                    fCache.remove(entry->key(), entry);
-
-                    // remove from our llist
-                    this->internalDetach(entry);
-                    delete entry;
-                }
-                entry = prev;
-            }
-        } while (!withinBudget && changed);
-        fPurging = false;
+    if (fPurging) {
+        return;
     }
+
+    fPurging = true;
+
+    this->internalPurge();
+    if ((fEntryCount > fMaxCount || fEntryBytes > fMaxBytes) &&
+        NULL != fOverbudgetCB) {
+        // Despite the purge we're still over budget. See if Ganesh can
+        // release some resources and purge again.
+        if ((*fOverbudgetCB)(fOverbudgetData)) {
+            this->internalPurge();
+        }
+    }
+
+    fPurging = false;
+}
+
+void GrResourceCache::internalPurge() {
+    SkASSERT(fPurging);
+
+    bool withinBudget = false;
+    bool changed = false;
+
+    // The purging process is repeated several times since one pass
+    // may free up other resources
+    do {
+        EntryList::Iter iter;
+
+        changed = false;
+
+        // Note: the following code relies on the fact that the
+        // doubly linked list doesn't invalidate its data/pointers
+        // outside of the specific area where a deletion occurs (e.g.,
+        // in internalDetach)
+        GrResourceEntry* entry = iter.init(fList, EntryList::Iter::kTail_IterStart);
+
+        while (NULL != entry) {
+            GrAutoResourceCacheValidate atcv(this);
+
+            if (fEntryCount <= fMaxCount && fEntryBytes <= fMaxBytes) {
+                withinBudget = true;
+                break;
+            }
+
+            GrResourceEntry* prev = iter.prev();
+            if (1 == entry->fResource->getRefCnt()) {
+                changed = true;
+
+                // remove from our cache
+                fCache.remove(entry->key(), entry);
+
+                // remove from our llist
+                this->internalDetach(entry);
+                delete entry;
+            }
+            entry = prev;
+        }
+    } while (!withinBudget && changed);
 }
 
 void GrResourceCache::purgeAllUnlocked() {
diff --git a/src/gpu/GrResourceCache.h b/src/gpu/GrResourceCache.h
index c4ead34..5ac4bf1 100644
--- a/src/gpu/GrResourceCache.h
+++ b/src/gpu/GrResourceCache.h
@@ -238,6 +238,25 @@
     void setLimits(int maxResource, size_t maxResourceBytes);
 
     /**
+     *  The callback function used by the cache when it is still over budget
+     *  after a purge. The passed in 'data' is the same 'data' handed to 
+     *  setOverbudgetCallback. The callback returns true if some resources
+     *  have been freed.
+     */
+    typedef bool (*PFOverbudgetCB)(void* data);
+
+    /**
+     *  Set the callback the cache should use when it is still over budget
+     *  after a purge. The 'data' provided here will be passed back to the 
+     *  callback. The cache will attempt to purge any resources newly freed
+     *  by the callback.
+     */
+    void setOverbudgetCallback(PFOverbudgetCB overbudgetCB, void* data) {
+        fOverbudgetCB = overbudgetCB;
+        fOverbudgetData = data;
+    }
+
+    /**
      * Returns the number of bytes consumed by cached resources.
      */
     size_t getCachedResourceBytes() const { return fEntryBytes; }
@@ -360,7 +379,12 @@
     size_t fClientDetachedBytes;
 
     // prevents recursive purging
-    bool fPurging;
+    bool           fPurging;
+
+    PFOverbudgetCB fOverbudgetCB;
+    void*          fOverbudgetData;
+
+    void internalPurge();
 
 #if GR_DEBUG
     static size_t countBytes(const SkTInternalLList<GrResourceEntry>& list);