Add GrResourceAllocator::makeBudgetHeadroom

This allows the caller to decide whether a plan is feasible before
committing to it. In this CL the drawing manager doesn't actually
call it, but we test it. We'll call it in a follow-up CL

Bug: skia:10877
Change-Id: Ie3a6c14a0196f595c522a0c961aba7b10c980711
Reviewed-on: https://skia-review.googlesource.com/c/skia/+/394157
Commit-Queue: Adlai Holler <adlai@google.com>
Reviewed-by: Robert Phillips <robertphillips@google.com>
diff --git a/src/gpu/GrDrawingManager.cpp b/src/gpu/GrDrawingManager.cpp
index 9f983f6..e1ce658 100644
--- a/src/gpu/GrDrawingManager.cpp
+++ b/src/gpu/GrDrawingManager.cpp
@@ -211,6 +211,7 @@
             task->gatherProxyIntervals(&alloc);
         }
 
+        // TODO: Call makeBudgetHeadroom before proceeding with reordered DAG.
         flushed = alloc.planAssignment() && alloc.assign() && this->executeRenderTasks(&flushState);
     }
     this->removeRenderTasks();
diff --git a/src/gpu/GrResourceAllocator.cpp b/src/gpu/GrResourceAllocator.cpp
index 9fc6afe..1dff77d 100644
--- a/src/gpu/GrResourceAllocator.cpp
+++ b/src/gpu/GrResourceAllocator.cpp
@@ -362,6 +362,31 @@
     return !fFailedInstantiation;
 }
 
+bool GrResourceAllocator::makeBudgetHeadroom() {
+    SkASSERT(fPlanned);
+    SkASSERT(!fFailedInstantiation);
+    size_t additionalBytesNeeded = 0;
+    for (Interval* cur = fFinishedIntvls.peekHead(); cur; cur = cur->next()) {
+        GrSurfaceProxy* proxy = cur->proxy();
+        if (SkBudgeted::kNo == proxy->isBudgeted() || proxy->isInstantiated()) {
+            continue;
+        }
+
+        // N.B Fully-lazy proxies were already instantiated in planAssignment
+        if (proxy->isLazy()) {
+            additionalBytesNeeded += proxy->gpuMemorySize();
+        } else {
+            Register* r = cur->getRegister();
+            SkASSERT(r);
+            if (!r->accountedForInBudget() && !r->existingSurface()) {
+                additionalBytesNeeded += proxy->gpuMemorySize();
+            }
+            r->setAccountedForInBudget();
+        }
+    }
+    return fDContext->priv().getResourceCache()->purgeToMakeHeadroom(additionalBytesNeeded);
+}
+
 bool GrResourceAllocator::assign() {
     SkASSERT(fPlanned && !fAssigned);
     SkDEBUGCODE(fAssigned = true;)
diff --git a/src/gpu/GrResourceAllocator.h b/src/gpu/GrResourceAllocator.h
index 301a26b..8175dca 100644
--- a/src/gpu/GrResourceAllocator.h
+++ b/src/gpu/GrResourceAllocator.h
@@ -91,11 +91,16 @@
     void addInterval(GrSurfaceProxy*, unsigned int start, unsigned int end, ActualUse actualUse
                      SkDEBUGCODE(, bool isDirectDstRead = false));
 
-    // Generate an internal plan for resource allocation.
+    // Generate an internal plan for resource allocation. After this you can optionally call
+    // `makeBudgetHeadroom` to check whether that plan would go over our memory budget.
     // Fully-lazy proxies are also instantiated at this point so that their size can
     // be known accurately. Returns false if any lazy proxy failed to instantiate, true otherwise.
     bool planAssignment();
 
+    // Figure out how much VRAM headroom this plan requires. If there's enough purgeable resources,
+    // purge them and return true. Otherwise return false.
+    bool makeBudgetHeadroom();
+
     // Instantiate and assign resources to all proxies.
     bool assign();
 
@@ -143,6 +148,9 @@
         const GrScratchKey& scratchKey() const { return fScratchKey; }
         const GrUniqueKey& uniqueKey() const { return fOriginatingProxy->getUniqueKey(); }
 
+        bool accountedForInBudget() const { return fAccountedForInBudget; }
+        void setAccountedForInBudget() { fAccountedForInBudget = true; }
+
         GrSurface* existingSurface() const { return fExistingSurface.get(); }
 
         // Can this register be used by other proxies after this one?
@@ -159,6 +167,7 @@
         GrSurfaceProxy*  fOriginatingProxy;
         GrScratchKey     fScratchKey; // free pool wants a reference to this.
         sk_sp<GrSurface> fExistingSurface; // queried from resource cache. may be null.
+        bool             fAccountedForInBudget = false;
 
 #ifdef SK_DEBUG
         uint32_t         fUniqueID;
diff --git a/src/gpu/GrResourceCache.cpp b/src/gpu/GrResourceCache.cpp
index 94211f2..64d470c 100644
--- a/src/gpu/GrResourceCache.cpp
+++ b/src/gpu/GrResourceCache.cpp
@@ -622,22 +622,27 @@
 
 bool GrResourceCache::purgeToMakeHeadroom(size_t desiredHeadroomBytes) {
     AutoValidate av(this);
+    if (desiredHeadroomBytes > fMaxBytes) {
+        return false;
+    }
     if (this->wouldFit(desiredHeadroomBytes)) {
         return true;
     }
     fPurgeableQueue.sort();
 
-    size_t headroom = this->overBudget() ? 0 : fMaxBytes - fBudgetedBytes;
+    size_t projectedBudget = fBudgetedBytes;
     int purgeCnt = 0;
     for (int i = 0; i < fPurgeableQueue.count(); i++) {
         GrGpuResource* resource = fPurgeableQueue.at(i);
-        headroom += resource->gpuMemorySize();
-        if (headroom >= desiredHeadroomBytes) {
+        if (GrBudgetedType::kBudgeted == resource->resourcePriv().budgetedType()) {
+            projectedBudget -= resource->gpuMemorySize();
+        }
+        if (projectedBudget + desiredHeadroomBytes <= fMaxBytes) {
             purgeCnt = i + 1;
             break;
         }
     }
-    if (headroom < desiredHeadroomBytes) {
+    if (purgeCnt == 0) {
         return false;
     }