From 6e2aad4e9f6280aa2b710e7324458fdc6d699ec5 Mon Sep 17 00:00:00 2001 From: bsalomon Date: Fri, 1 Apr 2016 11:54:31 -0700 Subject: [PATCH] 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 --- dm/DMGpuSupport.h | 2 ++ dm/DMSrcSink.cpp | 2 ++ include/gpu/GrContext.h | 32 +++++++++++-------- src/gpu/GrContext.cpp | 21 ++++++++++++- src/gpu/GrGpu.cpp | 2 +- src/gpu/GrGpu.h | 16 +++++++--- src/gpu/gl/GrGLGpu.cpp | 52 ++++++++++++++++++++++++++++--- src/gpu/gl/GrGLGpu.h | 2 +- src/gpu/gl/GrGLPathRendering.cpp | 5 ++- src/gpu/gl/GrGLPathRendering.h | 6 ++-- tests/GrContextAbandonTest.cpp | 53 ++++++++++++++++++++++++++++++++ tools/flags/SkCommonFlags.cpp | 8 +++-- tools/flags/SkCommonFlags.h | 1 + tools/gpu/GrContextFactory.cpp | 11 +++++++ tools/gpu/GrContextFactory.h | 1 + 15 files changed, 184 insertions(+), 30 deletions(-) create mode 100644 tests/GrContextAbandonTest.cpp diff --git a/dm/DMGpuSupport.h b/dm/DMGpuSupport.h index c713244e9d..490737f41e 100644 --- a/dm/DMGpuSupport.h +++ b/dm/DMGpuSupport.h @@ -84,6 +84,8 @@ public: void destroyContexts() {} void abandonContexts() {} + + void releaseResourcesAndAbandonContexts() {} }; } // namespace sk_gpu_test diff --git a/dm/DMSrcSink.cpp b/dm/DMSrcSink.cpp index e704f74d03..6584789a0b 100644 --- a/dm/DMSrcSink.cpp +++ b/dm/DMSrcSink.cpp @@ -1011,6 +1011,8 @@ Error GPUSink::draw(const Src& src, SkBitmap* dst, SkWStream*, SkString* log) co 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 6d67d3ebb3..d59b2a28f0 100644 --- a/include/gpu/GrContext.h +++ b/include/gpu/GrContext.h @@ -93,21 +93,29 @@ public: } /** - * 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 1f6934d441..1d6bb32142 100644 --- a/src/gpu/GrContext.cpp +++ b/src/gpu/GrContext.cpp @@ -148,7 +148,26 @@ void GrContext::abandonContext() { // 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 960526635e..bc11b4b9c6 100644 --- a/src/gpu/GrGpu.cpp +++ b/src/gpu/GrGpu.cpp @@ -51,7 +51,7 @@ GrGpu::GrGpu(GrContext* context) GrGpu::~GrGpu() {} -void GrGpu::contextAbandoned() {} +void GrGpu::disconnect(DisconnectType) {} //////////////////////////////////////////////////////////////////////////////// diff --git a/src/gpu/GrGpu.h b/src/gpu/GrGpu.h index 0da1aa6399..b3251dc14f 100644 --- a/src/gpu/GrGpu.h +++ b/src/gpu/GrGpu.h @@ -61,11 +61,17 @@ public: 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 34223f1cc8..c8c12a388d 100644 --- a/src/gpu/gl/GrGLGpu.cpp +++ b/src/gpu/gl/GrGLGpu.cpp @@ -386,9 +386,51 @@ void GrGLGpu::createPLSSetupProgram() { 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 @@ void GrGLGpu::contextAbandoned() { } 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 6d851ee1dd..c77076fc51 100644 --- a/src/gpu/gl/GrGLGpu.h +++ b/src/gpu/gl/GrGLGpu.h @@ -38,7 +38,7 @@ public: 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 47274f9d69..0ecf58a8e1 100644 --- a/src/gpu/gl/GrGLPathRendering.cpp +++ b/src/gpu/gl/GrGLPathRendering.cpp @@ -91,7 +91,10 @@ GrGLPathRendering::~GrGLPathRendering() { } } -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 cd4668fc88..b39c866c6f 100644 --- a/src/gpu/gl/GrGLPathRendering.h +++ b/src/gpu/gl/GrGLPathRendering.h @@ -41,10 +41,10 @@ public: 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 0000000000..c62973f4da --- /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 e6d269b9e2..eb2075c58a 100644 --- a/tools/flags/SkCommonFlags.cpp +++ b/tools/flags/SkCommonFlags.cpp @@ -30,9 +30,13 @@ DEFINE_string2(match, m, nullptr, 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 1f7bbffe6e..c6cbde45ca 100644 --- a/tools/flags/SkCommonFlags.h +++ b/tools/flags/SkCommonFlags.h @@ -21,6 +21,7 @@ DECLARE_bool(quiet); 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 bae73a4e19..c232121332 100755 --- a/tools/gpu/GrContextFactory.cpp +++ b/tools/gpu/GrContextFactory.cpp @@ -63,6 +63,17 @@ void GrContextFactory::abandonContexts() { } } +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 14a2739815..16f6fb23ff 100644 --- a/tools/gpu/GrContextFactory.h +++ b/tools/gpu/GrContextFactory.h @@ -107,6 +107,7 @@ public: void destroyContexts(); void abandonContexts(); + void releaseResourcesAndAbandonContexts(); struct ContextInfo { ContextInfo()