Add GrContext::releaseAndAbandonContext()

Like abandonContext() this disconnects the GrContext from the underlying 3D API. However, unlike abandonContext it first frees all allocated GPU resources.

BUG=skia:5142
GOLD_TRYBOT_URL= https://gold.skia.org/search2?unt=true&query=source_type%3Dgm&master=false&issue=1852733002

Review URL: https://codereview.chromium.org/1852733002
diff --git a/dm/DMGpuSupport.h b/dm/DMGpuSupport.h
index c713244..490737f 100644
--- a/dm/DMGpuSupport.h
+++ b/dm/DMGpuSupport.h
@@ -84,6 +84,8 @@
     void destroyContexts() {}
 
     void abandonContexts() {}
+
+    void releaseResourcesAndAbandonContexts() {}
 };
 }  // namespace sk_gpu_test
 
diff --git a/dm/DMSrcSink.cpp b/dm/DMSrcSink.cpp
index e704f74..6584789 100644
--- a/dm/DMSrcSink.cpp
+++ b/dm/DMSrcSink.cpp
@@ -1011,6 +1011,8 @@
     canvas->readPixels(dst, 0, 0);
     if (FLAGS_abandonGpuContext) {
         factory.abandonContexts();
+    } else if (FLAGS_releaseAndAbandonGpuContext) {
+        factory.releaseResourcesAndAbandonContexts();
     }
     return "";
 }
diff --git a/include/gpu/GrContext.h b/include/gpu/GrContext.h
index 6d67d3e..d59b2a2 100644
--- a/include/gpu/GrContext.h
+++ b/include/gpu/GrContext.h
@@ -93,21 +93,29 @@
     }
 
     /**
-     * Abandons all GPU resources and assumes the underlying backend 3D API
-     * context is not longer usable. Call this if you have lost the associated
-     * GPU context, and thus internal texture, buffer, etc. references/IDs are
-     * now invalid. Should be called even when GrContext is no longer going to
-     * be used for two reasons:
-     *  1) ~GrContext will not try to free the objects in the 3D API.
-     *  2) Any GrGpuResources created by this GrContext that outlive
-     *     will be marked as invalid (GrGpuResource::wasDestroyed()) and
-     *     when they're destroyed no 3D API calls will be made.
-     * Content drawn since the last GrContext::flush() may be lost. After this
-     * function is called the only valid action on the GrContext or
-     * GrGpuResources it created is to destroy them.
+     * Abandons all GPU resources and assumes the underlying backend 3D API context is not longer
+     * usable. Call this if you have lost the associated GPU context, and thus internal texture,
+     * buffer, etc. references/IDs are now invalid. Calling this ensures that the destructors of the
+     * GrContext and any of its created resource objects will not make backend 3D API calls. Content
+     * rendered but not previously flushed may be lost. After this function is called all subsequent
+     * calls on the GrContext will fail or be no-ops.
+     *
+     * The typical use case for this function is that the underlying 3D context was lost and further
+     * API calls may crash.
      */
     void abandonContext();
 
+    /**
+     * This is similar to abandonContext() however the underlying 3D context is not yet lost and
+     * the GrContext will cleanup all allocated resources before returning. After returning it will
+     * assume that the underlying context may no longer be valid.
+     *
+     * The typical use case for this function is that the client is going to destroy the 3D context
+     * but can't guarantee that GrContext will be destroyed first (perhaps because it may be ref'ed
+     * elsewhere by either the client or Skia objects).
+     */
+    void releaseResourcesAndAbandonContext();
+
     ///////////////////////////////////////////////////////////////////////////
     // Resource Cache
 
diff --git a/src/gpu/GrContext.cpp b/src/gpu/GrContext.cpp
index 1f6934d..1d6bb32 100644
--- a/src/gpu/GrContext.cpp
+++ b/src/gpu/GrContext.cpp
@@ -148,7 +148,26 @@
     // don't try to free the resources in the API.
     fResourceCache->abandonAll();
 
-    fGpu->contextAbandoned();
+    fGpu->disconnect(GrGpu::DisconnectType::kAbandon);
+
+    fBatchFontCache->freeAll();
+    fLayerCache->freeAll();
+    fTextBlobCache->freeAll();
+}
+
+void GrContext::releaseResourcesAndAbandonContext() {
+    ASSERT_SINGLE_OWNER
+
+    fResourceProvider->abandon();
+
+    // Need to abandon the drawing manager first so all the render targets
+    // will be released/forgotten before they too are abandoned.
+    fDrawingManager->abandon();
+
+    // Release all resources in the backend 3D API.
+    fResourceCache->releaseAll();
+
+    fGpu->disconnect(GrGpu::DisconnectType::kCleanup);
 
     fBatchFontCache->freeAll();
     fLayerCache->freeAll();
diff --git a/src/gpu/GrGpu.cpp b/src/gpu/GrGpu.cpp
index 9605266..bc11b4b 100644
--- a/src/gpu/GrGpu.cpp
+++ b/src/gpu/GrGpu.cpp
@@ -51,7 +51,7 @@
 
 GrGpu::~GrGpu() {}
 
-void GrGpu::contextAbandoned() {}
+void GrGpu::disconnect(DisconnectType) {}
 
 ////////////////////////////////////////////////////////////////////////////////
 
diff --git a/src/gpu/GrGpu.h b/src/gpu/GrGpu.h
index 0da1aa6..b3251dc 100644
--- a/src/gpu/GrGpu.h
+++ b/src/gpu/GrGpu.h
@@ -61,11 +61,17 @@
 
     GrPathRendering* pathRendering() { return fPathRendering.get();  }
 
-    // Called by GrContext when the underlying backend context has been destroyed.
-    // GrGpu should use this to ensure that no backend API calls will be made from
-    // here onward, including in its destructor. Subclasses should call
-    // INHERITED::contextAbandoned() if they override this.
-    virtual void contextAbandoned();
+    enum class DisconnectType {
+        // No cleanup should be attempted, immediately cease making backend API calls
+        kAbandon,
+        // Free allocated resources (not known by GrResourceCache) before returning and
+        // ensure no backend backend 3D API calls will be made after disconnect() returns.
+        kCleanup,
+    };
+
+    // Called by GrContext when the underlying backend context is already or will be destroyed
+    // before GrContext.
+    virtual void disconnect(DisconnectType);
 
     /**
      * The GrGpu object normally assumes that no outsider is setting state
diff --git a/src/gpu/gl/GrGLGpu.cpp b/src/gpu/gl/GrGLGpu.cpp
index 34223f1..c8c12a3 100644
--- a/src/gpu/gl/GrGLGpu.cpp
+++ b/src/gpu/gl/GrGLGpu.cpp
@@ -386,9 +386,51 @@
                              GR_GL_STATIC_DRAW));
 }
 
-void GrGLGpu::contextAbandoned() {
-    INHERITED::contextAbandoned();
-    fProgramCache->abandon();
+void GrGLGpu::disconnect(DisconnectType type) {
+    INHERITED::disconnect(type);
+    if (DisconnectType::kCleanup == type) {
+        if (fHWProgramID) {
+            GL_CALL(UseProgram(0));
+        }
+        if (fTempSrcFBOID) {
+            GL_CALL(DeleteFramebuffers(1, &fTempSrcFBOID));
+        }
+        if (fTempDstFBOID) {
+            GL_CALL(DeleteFramebuffers(1, &fTempDstFBOID));
+        }
+        if (fStencilClearFBOID) {
+            GL_CALL(DeleteFramebuffers(1, &fStencilClearFBOID));
+        }
+        if (fCopyProgramArrayBuffer) {
+            GL_CALL(DeleteBuffers(1, &fCopyProgramArrayBuffer));
+        }
+        for (size_t i = 0; i < SK_ARRAY_COUNT(fCopyPrograms); ++i) {
+            if (fCopyPrograms[i].fProgram) {
+                GL_CALL(DeleteProgram(fCopyPrograms[i].fProgram));
+            }
+        }
+        if (fWireRectProgram.fProgram) {
+            GL_CALL(DeleteProgram(fWireRectProgram.fProgram));
+        }
+        if (fWireRectArrayBuffer) {
+            GL_CALL(DeleteBuffers(1, &fWireRectArrayBuffer));
+        }
+
+        if (fPLSSetupProgram.fProgram) {
+            GL_CALL(DeleteProgram(fPLSSetupProgram.fProgram));
+        }
+        if (fPLSSetupProgram.fArrayBuffer) {
+            GL_CALL(DeleteBuffers(1, &fPLSSetupProgram.fArrayBuffer));
+        }
+    } else {
+        if (fProgramCache) {
+            fProgramCache->abandon();
+        }
+    }
+
+    delete fProgramCache;
+    fProgramCache = nullptr;
+
     fHWProgramID = 0;
     fTempSrcFBOID = 0;
     fTempDstFBOID = 0;
@@ -399,8 +441,10 @@
     }
     fWireRectProgram.fProgram = 0;
     fWireRectArrayBuffer = 0;
+    fPLSSetupProgram.fProgram = 0;
+    fPLSSetupProgram.fArrayBuffer = 0;
     if (this->glCaps().shaderCaps()->pathRenderingSupport()) {
-        this->glPathRendering()->abandonGpuResources();
+        this->glPathRendering()->disconnect(type);
     }
 }
 
diff --git a/src/gpu/gl/GrGLGpu.h b/src/gpu/gl/GrGLGpu.h
index 6d851ee..c77076f 100644
--- a/src/gpu/gl/GrGLGpu.h
+++ b/src/gpu/gl/GrGLGpu.h
@@ -38,7 +38,7 @@
                          GrContext* context);
     ~GrGLGpu() override;
 
-    void contextAbandoned() override;
+    void disconnect(DisconnectType) override;
 
     const GrGLContext& glContext() const { return *fGLContext; }
 
diff --git a/src/gpu/gl/GrGLPathRendering.cpp b/src/gpu/gl/GrGLPathRendering.cpp
index 47274f9..0ecf58a 100644
--- a/src/gpu/gl/GrGLPathRendering.cpp
+++ b/src/gpu/gl/GrGLPathRendering.cpp
@@ -91,7 +91,10 @@
     }
 }
 
-void GrGLPathRendering::abandonGpuResources() {
+void GrGLPathRendering::disconnect(GrGpu::DisconnectType type) {
+    if (GrGpu::DisconnectType::kCleanup == type) {
+        this->deletePaths(fFirstPreallocatedPathID, fPreallocatedPathCount);
+    };
     fPreallocatedPathCount = 0;
 }
 
diff --git a/src/gpu/gl/GrGLPathRendering.h b/src/gpu/gl/GrGLPathRendering.h
index cd4668f..b39c866 100644
--- a/src/gpu/gl/GrGLPathRendering.h
+++ b/src/gpu/gl/GrGLPathRendering.h
@@ -41,10 +41,10 @@
     void resetContext();
 
     /**
-     * Called when the GPU resources have been lost and need to be abandoned
-     * (for example after a context loss).
+     * Called when the context either is about to be lost or is lost. DisconnectType indicates
+     * whether GPU resources should be cleaned up or abandoned when this is called.
      */
-    void abandonGpuResources();
+    void disconnect(GrGpu::DisconnectType);
 
     bool shouldBindFragmentInputs() const {
         return fCaps.bindFragmentInputSupport;
diff --git a/tests/GrContextAbandonTest.cpp b/tests/GrContextAbandonTest.cpp
new file mode 100644
index 0000000..c62973f
--- /dev/null
+++ b/tests/GrContextAbandonTest.cpp
@@ -0,0 +1,53 @@
+/*
+ * Copyright 2016 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#include "SkTypes.h"
+
+#if SK_SUPPORT_GPU
+
+#include "GrContextFactory.h"
+#include "Test.h"
+
+using sk_gpu_test::GrContextFactory;
+
+DEF_GPUTEST(GrContext_abandonContext, reporter, /*factory*/) {
+    for (int testType = 0; testType < 6; ++testType) {
+    for (int i = 0; i < GrContextFactory::kGLContextTypeCnt; ++i) {
+        GrContextFactory testFactory;
+        GrContextFactory::GLContextType ctxType = (GrContextFactory::GLContextType) i;
+        GrContextFactory::ContextInfo info = testFactory.getContextInfo(ctxType);
+            if (GrContext* context = info.fGrContext) {
+                switch (testType) {
+                    case 0:
+                        context->abandonContext();
+                        break;
+                    case 1:
+                        context->releaseResourcesAndAbandonContext();
+                        break;
+                    case 2:
+                        context->abandonContext();
+                        context->abandonContext();
+                        break;
+                    case 3:
+                        context->abandonContext();
+                        context->releaseResourcesAndAbandonContext();
+                        break;
+                    case 4:
+                        context->releaseResourcesAndAbandonContext();
+                        context->abandonContext();
+                        break;
+                    case 5:
+                        context->releaseResourcesAndAbandonContext();
+                        context->releaseResourcesAndAbandonContext();
+                        break;
+                }
+            }
+        }
+    }
+}
+
+#endif
diff --git a/tools/flags/SkCommonFlags.cpp b/tools/flags/SkCommonFlags.cpp
index e6d269b..eb2075c 100644
--- a/tools/flags/SkCommonFlags.cpp
+++ b/tools/flags/SkCommonFlags.cpp
@@ -30,9 +30,13 @@
 
 DEFINE_bool2(quiet, q, false, "if true, don't print status updates.");
 
-DEFINE_bool(preAbandonGpuContext, false, "Abandons the GrContext before running the test.");
+DEFINE_bool(preAbandonGpuContext, false, "Test abandoning the GrContext before running the test.");
 
-DEFINE_bool(abandonGpuContext, false, "Abandon the GrContext after running each test.");
+DEFINE_bool(abandonGpuContext, false, "Test abandoning the GrContext after running each test.");
+
+DEFINE_bool(releaseAndAbandonGpuContext, false,
+            "Test releasing all gpu resources and abandoning the GrContext after running each "
+            "test");
 
 DEFINE_string(skps, "skps", "Directory to read skps from.");
 
diff --git a/tools/flags/SkCommonFlags.h b/tools/flags/SkCommonFlags.h
index 1f7bbff..c6cbde4 100644
--- a/tools/flags/SkCommonFlags.h
+++ b/tools/flags/SkCommonFlags.h
@@ -21,6 +21,7 @@
 DECLARE_bool(resetGpuContext);
 DECLARE_bool(preAbandonGpuContext);
 DECLARE_bool(abandonGpuContext);
+DECLARE_bool(releaseAndAbandonGpuContext);
 DECLARE_string(skps);
 DECLARE_int32(threads);
 DECLARE_string(resourcePath);
diff --git a/tools/gpu/GrContextFactory.cpp b/tools/gpu/GrContextFactory.cpp
index bae73a4..c232121 100755
--- a/tools/gpu/GrContextFactory.cpp
+++ b/tools/gpu/GrContextFactory.cpp
@@ -63,6 +63,17 @@
     }
 }
 
+void GrContextFactory::releaseResourcesAndAbandonContexts() {
+    for (Context& context : fContexts) {
+        if (context.fGLContext) {
+            context.fGLContext->makeCurrent();
+            context.fGrContext->releaseResourcesAndAbandonContext();
+            delete(context.fGLContext);
+            context.fGLContext = nullptr;
+        }
+    }
+}
+
 GrContextFactory::ContextInfo GrContextFactory::getContextInfo(GLContextType type,
                                                                GLContextOptions options) {
     for (int i = 0; i < fContexts.count(); ++i) {
diff --git a/tools/gpu/GrContextFactory.h b/tools/gpu/GrContextFactory.h
index 14a2739..16f6fb2 100644
--- a/tools/gpu/GrContextFactory.h
+++ b/tools/gpu/GrContextFactory.h
@@ -107,6 +107,7 @@
 
     void destroyContexts();
     void abandonContexts();
+    void releaseResourcesAndAbandonContexts();
 
     struct ContextInfo {
         ContextInfo()