Fix RE's VulkanInterface destruction & unnecessary initialization

SurfaceFlinger's initialization of RE now:
- Only attempts to check for VK support if either the GaneshVk or
  GraphiteVk flag is set.
- Caches the VulkanInterface created to check for VK support until a VK
  instance of RE is created.
- Tears down a partially initialized VulkanInterface if some required
  feature is unsupported.

Additionally, SkiaVkRenderEngine's destructor now tears down the static
VulkanInterfaces it uses, which necessitates:
- Caching whether VK is supported.
- Ensuring all Skia resources are destroyed *before* the VK resources
  managed by VulkanInterface that they rely on are torn down. This
  involves ensuring all textures are destroyed, and all Skia
  context-like objects are destroyed.

This latter change means that tests in librenderengine_test that are
parameterized by Skia backend must now recreate RE's VulkanInterfaces
twice for each test case (once for GaneshVk, and again for GraphiteVk),
which results in a minor regression in test duration. However, this is
necessary because holding on to a VulkanInterface while attempting to
set up a test for GaneshGL will cause contention over the real-time
GPU context priority, which can only be held exclusively by either a GL
context OR a VK context on some hardware.

Many thanks to joseph.cheng@imgtec.com for raising the issue of SF's
init of RE not tearing down the VulkanInterface used to check for
support, and for proposing an initial patch (b/333477752) which this
change builds upon. And thanks to scroggo@google.com for proposing the
reordering of SF's flag vs. VK support checks.

Bug: b/293371537
Bug: b/333477752
Test: librenderengine_test && manual boot validation across 3 backends
Flag: com.android.graphics.surfaceflinger.flags.graphite_renderengine-READ-ONLY
Change-Id: I289c3f7699d16707d1462179f4d5e8c54e4bb049
diff --git a/libs/renderengine/include/renderengine/RenderEngine.h b/libs/renderengine/include/renderengine/RenderEngine.h
index 69e7b88..980d913 100644
--- a/libs/renderengine/include/renderengine/RenderEngine.h
+++ b/libs/renderengine/include/renderengine/RenderEngine.h
@@ -120,7 +120,24 @@
 
     static std::unique_ptr<RenderEngine> create(const RenderEngineCreationArgs& args);
 
-    static bool canSupport(GraphicsApi);
+    // Check if the device supports the given GraphicsApi.
+    //
+    // If called for GraphicsApi::VK then underlying (unprotected) VK resources will be preserved
+    // to optimize subsequent VK initialization, but teardown(GraphicsApi::VK) must be invoked if
+    // the caller subsequently decides to NOT use VK.
+    //
+    // The first call may require significant resource initialization, but subsequent checks are
+    // cached internally.
+    static bool canSupport(GraphicsApi graphicsApi);
+
+    // Teardown any GPU API resources that were previously initialized but are no longer needed.
+    //
+    // Must be called with GraphicsApi::VK if canSupport(GraphicsApi::VK) was previously invoked but
+    // the caller subsequently decided to not use VK.
+    //
+    // This is safe to call if there is nothing to teardown, but NOT safe to call if a RenderEngine
+    // instance exists. The RenderEngine destructor will handle its own teardown logic.
+    static void teardown(GraphicsApi graphicsApi);
 
     virtual ~RenderEngine() = 0;
 
diff --git a/libs/renderengine/skia/SkiaGLRenderEngine.cpp b/libs/renderengine/skia/SkiaGLRenderEngine.cpp
index 9d3d98e..48270e1 100644
--- a/libs/renderengine/skia/SkiaGLRenderEngine.cpp
+++ b/libs/renderengine/skia/SkiaGLRenderEngine.cpp
@@ -279,7 +279,7 @@
         mProtectedPlaceholderSurface(protectedPlaceholder) {}
 
 SkiaGLRenderEngine::~SkiaGLRenderEngine() {
-    finishRenderingAndAbandonContext();
+    finishRenderingAndAbandonContexts();
     if (mPlaceholderSurface != EGL_NO_SURFACE) {
         eglDestroySurface(mEGLDisplay, mPlaceholderSurface);
     }
diff --git a/libs/renderengine/skia/SkiaRenderEngine.cpp b/libs/renderengine/skia/SkiaRenderEngine.cpp
index 4641cb9..45f0168 100644
--- a/libs/renderengine/skia/SkiaRenderEngine.cpp
+++ b/libs/renderengine/skia/SkiaRenderEngine.cpp
@@ -297,21 +297,23 @@
 
 SkiaRenderEngine::~SkiaRenderEngine() { }
 
-// To be called from backend dtors.
-void SkiaRenderEngine::finishRenderingAndAbandonContext() {
+// To be called from backend dtors. Used to clean up Skia objects before GPU API contexts are
+// destroyed by subclasses.
+void SkiaRenderEngine::finishRenderingAndAbandonContexts() {
     std::lock_guard<std::mutex> lock(mRenderingMutex);
 
     if (mBlurFilter) {
         delete mBlurFilter;
     }
 
-    if (mContext) {
-        mContext->finishRenderingAndAbandonContext();
-    }
+    // Leftover textures may hold refs to backend-specific Skia contexts, which must be released
+    // before ~SkiaGpuContext is called.
+    mTextureCleanupMgr.setDeferredStatus(false);
+    mTextureCleanupMgr.cleanup();
 
-    if (mProtectedContext) {
-        mProtectedContext->finishRenderingAndAbandonContext();
-    }
+    // ~SkiaGpuContext must be called before GPU API contexts are torn down.
+    mContext.reset();
+    mProtectedContext.reset();
 }
 
 void SkiaRenderEngine::useProtectedContext(bool useProtectedContext) {
diff --git a/libs/renderengine/skia/SkiaRenderEngine.h b/libs/renderengine/skia/SkiaRenderEngine.h
index ed50029..82fdbb1 100644
--- a/libs/renderengine/skia/SkiaRenderEngine.h
+++ b/libs/renderengine/skia/SkiaRenderEngine.h
@@ -79,9 +79,9 @@
     void ensureContextsCreated();
 
 protected:
-    // This is so backends can stop the generic rendering state first before
-    // cleaning up backend-specific state
-    void finishRenderingAndAbandonContext();
+    // This is so backends can stop the generic rendering state first before cleaning up
+    // backend-specific state. SkiaGpuContexts are invalid after invocation.
+    void finishRenderingAndAbandonContexts();
 
     // Functions that a given backend (GLES, Vulkan) must implement
     using Contexts = std::pair<unique_ptr<SkiaGpuContext>, unique_ptr<SkiaGpuContext>>;
diff --git a/libs/renderengine/skia/SkiaVkRenderEngine.cpp b/libs/renderengine/skia/SkiaVkRenderEngine.cpp
index 406fd81..bd50107 100644
--- a/libs/renderengine/skia/SkiaVkRenderEngine.cpp
+++ b/libs/renderengine/skia/SkiaVkRenderEngine.cpp
@@ -69,12 +69,38 @@
         case GraphicsApi::GL:
             return true;
         case GraphicsApi::VK: {
-            if (!sVulkanInterface.isInitialized()) {
-                sVulkanInterface.init(false /* no protected content */);
-                ALOGD("%s: initialized == %s.", __func__,
-                      sVulkanInterface.isInitialized() ? "true" : "false");
+            // Static local variables are initialized once, on first invocation of the function.
+            static const bool canSupportVulkan = []() {
+                if (!sVulkanInterface.isInitialized()) {
+                    sVulkanInterface.init(false /* no protected content */);
+                    ALOGD("%s: initialized == %s.", __func__,
+                          sVulkanInterface.isInitialized() ? "true" : "false");
+                    if (!sVulkanInterface.isInitialized()) {
+                        sVulkanInterface.teardown();
+                        return false;
+                    }
+                }
+                return true;
+            }();
+            return canSupportVulkan;
+        }
+    }
+}
+
+void RenderEngine::teardown(GraphicsApi graphicsApi) {
+    switch (graphicsApi) {
+        case GraphicsApi::GL:
+            break;
+        case GraphicsApi::VK: {
+            if (sVulkanInterface.isInitialized()) {
+                sVulkanInterface.teardown();
+                ALOGD("Tearing down the unprotected VulkanInterface.");
             }
-            return sVulkanInterface.isInitialized();
+            if (sProtectedContentVulkanInterface.isInitialized()) {
+                sProtectedContentVulkanInterface.teardown();
+                ALOGD("Tearing down the protected VulkanInterface.");
+            }
+            break;
         }
     }
 }
@@ -88,11 +114,27 @@
                          args.blurAlgorithm) {}
 
 SkiaVkRenderEngine::~SkiaVkRenderEngine() {
-    finishRenderingAndAbandonContext();
+    finishRenderingAndAbandonContexts();
+    // Teardown VulkanInterfaces after Skia contexts have been abandoned
+    teardown(GraphicsApi::VK);
 }
 
 SkiaRenderEngine::Contexts SkiaVkRenderEngine::createContexts() {
     sSetupVulkanInterface();
+    // More work would need to be done in order to have multiple RenderEngine instances. In
+    // particular, they would not be able to share the same VulkanInterface(s).
+    LOG_ALWAYS_FATAL_IF(!sVulkanInterface.takeOwnership(),
+                        "SkiaVkRenderEngine couldn't take ownership of existing unprotected "
+                        "VulkanInterface! Only one SkiaVkRenderEngine instance may exist at a "
+                        "time.");
+    if (sProtectedContentVulkanInterface.isInitialized()) {
+        // takeOwnership fails on an uninitialized VulkanInterface, but protected content support is
+        // optional.
+        LOG_ALWAYS_FATAL_IF(!sProtectedContentVulkanInterface.takeOwnership(),
+                            "SkiaVkRenderEngine couldn't take ownership of existing protected "
+                            "VulkanInterface! Only one SkiaVkRenderEngine instance may exist at a "
+                            "time.");
+    }
 
     SkiaRenderEngine::Contexts contexts;
     contexts.first = createContext(sVulkanInterface);
diff --git a/libs/renderengine/skia/VulkanInterface.cpp b/libs/renderengine/skia/VulkanInterface.cpp
index 49b9f1e..bd3cca7 100644
--- a/libs/renderengine/skia/VulkanInterface.cpp
+++ b/libs/renderengine/skia/VulkanInterface.cpp
@@ -557,13 +557,16 @@
     ALOGD("%s: Success init Vulkan interface in %f ms", __func__, initTimeMs);
 }
 
-// TODO: b/293371537 - Iterate on this.
-// Currently unused, but copied over from its original location for potential future use. This
-// should likely be improved to walk the pNext chain of mPhysicalDeviceFeatures2 and free everything
-// like HWUI's VulkanManager. Also, not all fields are being reset.
-void VulkanInterface::teardown() {
-    mInitialized = false;
+bool VulkanInterface::takeOwnership() {
+    if (!isInitialized() || mIsOwned) {
+        return false;
+    }
+    mIsOwned = true;
+    return true;
+}
 
+void VulkanInterface::teardown() {
+    // Core resources that must be destroyed using Vulkan functions.
     if (mDevice != VK_NULL_HANDLE) {
         mFuncs.vkDeviceWaitIdle(mDevice);
         mFuncs.vkDestroyDevice(mDevice, nullptr);
@@ -574,26 +577,42 @@
         mInstance = VK_NULL_HANDLE;
     }
 
+    // Optional features that can be deleted directly.
+    // TODO: b/293371537 - This section should likely be improved to walk the pNext chain of
+    // mPhysicalDeviceFeatures2 and free everything like HWUI's VulkanManager.
     if (mProtectedMemoryFeatures) {
         delete mProtectedMemoryFeatures;
+        mProtectedMemoryFeatures = nullptr;
     }
-
     if (mSamplerYcbcrConversionFeatures) {
         delete mSamplerYcbcrConversionFeatures;
+        mSamplerYcbcrConversionFeatures = nullptr;
     }
-
     if (mPhysicalDeviceFeatures2) {
         delete mPhysicalDeviceFeatures2;
+        mPhysicalDeviceFeatures2 = nullptr;
     }
-
     if (mDeviceFaultFeatures) {
         delete mDeviceFaultFeatures;
+        mDeviceFaultFeatures = nullptr;
     }
 
-    mSamplerYcbcrConversionFeatures = nullptr;
-    mPhysicalDeviceFeatures2 = nullptr;
-    mProtectedMemoryFeatures = nullptr;
-    mDeviceFaultFeatures = nullptr;
+    // Misc. fields that can be trivially reset without special deletion:
+    mInitialized = false;
+    mIsOwned = false;
+    mPhysicalDevice = VK_NULL_HANDLE; // Implicitly destroyed by destroying mInstance.
+    mQueue = VK_NULL_HANDLE;          // Implicitly destroyed by destroying mDevice.
+    mQueueIndex = 0;
+    mApiVersion = 0;
+    mGrExtensions = GrVkExtensions();
+    mGrGetProc = nullptr;
+    mIsProtected = false;
+    mIsRealtimePriority = false;
+
+    mFuncs = VulkanFuncs();
+
+    mInstanceExtensionNames.clear();
+    mDeviceExtensionNames.clear();
 }
 
 } // namespace skia
diff --git a/libs/renderengine/skia/VulkanInterface.h b/libs/renderengine/skia/VulkanInterface.h
index 2824fcb..af0489a 100644
--- a/libs/renderengine/skia/VulkanInterface.h
+++ b/libs/renderengine/skia/VulkanInterface.h
@@ -41,6 +41,10 @@
     VulkanInterface& operator=(VulkanInterface&&) = delete;
 
     void init(bool protectedContent = false);
+    // Returns true and marks this VulkanInterface as "owned" if it is initialized but unused by any
+    // RenderEngine instances. Returns false if already owned, indicating that it must not be used
+    // by a new RE instance.
+    bool takeOwnership();
     void teardown();
 
     GrVkBackendContext getGaneshBackendContext();
@@ -72,7 +76,9 @@
                                 const std::vector<VkDeviceFaultVendorInfoEXT>& vendorInfos,
                                 const std::vector<std::byte>& vendorBinaryData);
 
+    // Note: keep all field defaults in sync with teardown()
     bool mInitialized = false;
+    bool mIsOwned = false;
     VkInstance mInstance = VK_NULL_HANDLE;
     VkPhysicalDevice mPhysicalDevice = VK_NULL_HANDLE;
     VkDevice mDevice = VK_NULL_HANDLE;
diff --git a/libs/renderengine/skia/compat/GaneshGpuContext.cpp b/libs/renderengine/skia/compat/GaneshGpuContext.cpp
index e3fec19..b2eae00 100644
--- a/libs/renderengine/skia/compat/GaneshGpuContext.cpp
+++ b/libs/renderengine/skia/compat/GaneshGpuContext.cpp
@@ -38,7 +38,6 @@
 namespace android::renderengine::skia {
 
 namespace {
-// TODO: b/293371537 - Graphite variant.
 static GrContextOptions ganeshOptions(GrContextOptions::PersistentCache& skSLCacheMonitor) {
     GrContextOptions options;
     options.fDisableDriverCorrectnessWorkarounds = true;
@@ -67,6 +66,11 @@
     LOG_ALWAYS_FATAL_IF(mGrContext.get() == nullptr, "GrDirectContext creation failed");
 }
 
+GaneshGpuContext::~GaneshGpuContext() {
+    mGrContext->flushAndSubmit(GrSyncCpu::kYes);
+    mGrContext->abandonContext();
+};
+
 sk_sp<GrDirectContext> GaneshGpuContext::grDirectContext() {
     return mGrContext;
 }
@@ -101,11 +105,6 @@
     mGrContext->setResourceCacheLimit(maxResourceBytes);
 }
 
-void GaneshGpuContext::finishRenderingAndAbandonContext() {
-    mGrContext->flushAndSubmit(GrSyncCpu::kYes);
-    mGrContext->abandonContext();
-};
-
 void GaneshGpuContext::purgeUnlockedScratchResources() {
     mGrContext->purgeUnlockedResources(GrPurgeResourceOptions::kScratchResourcesOnly);
 }
diff --git a/libs/renderengine/skia/compat/GaneshGpuContext.h b/libs/renderengine/skia/compat/GaneshGpuContext.h
index d815d15..aeb1a82 100644
--- a/libs/renderengine/skia/compat/GaneshGpuContext.h
+++ b/libs/renderengine/skia/compat/GaneshGpuContext.h
@@ -25,7 +25,7 @@
 class GaneshGpuContext : public SkiaGpuContext {
 public:
     GaneshGpuContext(sk_sp<GrDirectContext> grContext);
-    ~GaneshGpuContext() override = default;
+    ~GaneshGpuContext() override;
 
     sk_sp<GrDirectContext> grDirectContext() override;
 
@@ -39,7 +39,6 @@
     bool isAbandonedOrDeviceLost() override;
     void setResourceCacheLimit(size_t maxResourceBytes) override;
 
-    void finishRenderingAndAbandonContext() override;
     void purgeUnlockedScratchResources() override;
     void resetContextIfApplicable() override;
 
diff --git a/libs/renderengine/skia/compat/GraphiteGpuContext.cpp b/libs/renderengine/skia/compat/GraphiteGpuContext.cpp
index e19d66f..69f5832 100644
--- a/libs/renderengine/skia/compat/GraphiteGpuContext.cpp
+++ b/libs/renderengine/skia/compat/GraphiteGpuContext.cpp
@@ -62,6 +62,22 @@
     LOG_ALWAYS_FATAL_IF(mRecorder.get() == nullptr, "graphite::Recorder creation failed");
 }
 
+GraphiteGpuContext::~GraphiteGpuContext() {
+    // The equivalent operation would occur when destroying the graphite::Context, but calling this
+    // explicitly allows any outstanding GraphiteBackendTextures to be released, thus allowing us to
+    // assert that this GraphiteGpuContext holds the last ref to the underlying graphite::Recorder.
+    mContext->submit(skgpu::graphite::SyncToCpu::kYes);
+    // We must call the Context's and Recorder's dtors before exiting this function, so all other
+    // refs must be released by now. Note: these assertions may be unreliable in a hypothetical
+    // future world where we take advantage of Graphite's multi-threading capabilities!
+    LOG_ALWAYS_FATAL_IF(mRecorder.use_count() > 1,
+                        "Something other than GraphiteGpuContext holds a ref to the underlying "
+                        "graphite::Recorder");
+    LOG_ALWAYS_FATAL_IF(mContext.use_count() > 1,
+                        "Something other than GraphiteGpuContext holds a ref to the underlying "
+                        "graphite::Context");
+};
+
 std::shared_ptr<skgpu::graphite::Context> GraphiteGpuContext::graphiteContext() {
     return mContext;
 }
@@ -94,11 +110,6 @@
     return mContext->isDeviceLost();
 }
 
-void GraphiteGpuContext::finishRenderingAndAbandonContext() {
-    // TODO: b/293371537 - Validate that nothing else needs to be explicitly abandoned.
-    mContext->submit(skgpu::graphite::SyncToCpu::kYes);
-};
-
 void GraphiteGpuContext::dumpMemoryStatistics(SkTraceMemoryDump* traceMemoryDump) const {
     mContext->dumpMemoryStatistics(traceMemoryDump);
 }
diff --git a/libs/renderengine/skia/compat/GraphiteGpuContext.h b/libs/renderengine/skia/compat/GraphiteGpuContext.h
index 685f899..413817f 100644
--- a/libs/renderengine/skia/compat/GraphiteGpuContext.h
+++ b/libs/renderengine/skia/compat/GraphiteGpuContext.h
@@ -26,7 +26,7 @@
 class GraphiteGpuContext : public SkiaGpuContext {
 public:
     GraphiteGpuContext(std::unique_ptr<skgpu::graphite::Context> context);
-    ~GraphiteGpuContext() override = default;
+    ~GraphiteGpuContext() override;
 
     std::shared_ptr<skgpu::graphite::Context> graphiteContext() override;
     std::shared_ptr<skgpu::graphite::Recorder> graphiteRecorder() override;
@@ -45,7 +45,6 @@
     // functions yet, as its design may evolve.)
     void setResourceCacheLimit(size_t maxResourceBytes) override{};
 
-    void finishRenderingAndAbandonContext() override;
     // TODO: b/293371537 - Triple-check and validate that no cleanup is necessary when switching
     // contexts.
     // No-op (unnecessary during context switch for Graphite's client-budgeted memory model).
@@ -58,7 +57,7 @@
 private:
     DISALLOW_COPY_AND_ASSIGN(GraphiteGpuContext);
 
-    const std::shared_ptr<skgpu::graphite::Context> mContext;
+    std::shared_ptr<skgpu::graphite::Context> mContext;
     std::shared_ptr<skgpu::graphite::Recorder> mRecorder;
 };
 
diff --git a/libs/renderengine/skia/compat/SkiaGpuContext.h b/libs/renderengine/skia/compat/SkiaGpuContext.h
index a2457e5..282dfe7 100644
--- a/libs/renderengine/skia/compat/SkiaGpuContext.h
+++ b/libs/renderengine/skia/compat/SkiaGpuContext.h
@@ -36,18 +36,32 @@
 
 /**
  * Abstraction over Ganesh and Graphite's underlying context-like objects.
+ *
+ * On destruction, subclasses will submit any pending work before destroying their internal Skia
+ * context(s). Any unused cached SkiaBackendTextures created from a SkiaGpuContext that are awaiting
+ * cleanup must be deleted before destroying that SkiaGpuContext, and any textures that are released
+ * during ~SkiaGpuContext must be configured to be deleted immediately.
  */
 class SkiaGpuContext {
 public:
+    /**
+     * glInterface must remain valid until after SkiaGpuContext is destroyed.
+     */
     static std::unique_ptr<SkiaGpuContext> MakeGL_Ganesh(
             sk_sp<const GrGLInterface> glInterface,
             GrContextOptions::PersistentCache& skSLCacheMonitor);
 
+    /**
+     * grVkBackendContext must remain valid until after SkiaGpuContext is destroyed.
+     */
     static std::unique_ptr<SkiaGpuContext> MakeVulkan_Ganesh(
             const GrVkBackendContext& grVkBackendContext,
             GrContextOptions::PersistentCache& skSLCacheMonitor);
 
     // TODO: b/293371537 - Need shader / pipeline monitoring support in Graphite.
+    /**
+     * vulkanBackendContext must remain valid until after SkiaGpuContext is destroyed.
+     */
     static std::unique_ptr<SkiaGpuContext> MakeVulkan_Graphite(
             const skgpu::VulkanBackendContext& vulkanBackendContext);
 
@@ -91,7 +105,6 @@
     virtual size_t getMaxTextureSize() const = 0;
     virtual void setResourceCacheLimit(size_t maxResourceBytes) = 0;
 
-    virtual void finishRenderingAndAbandonContext() = 0;
     virtual void purgeUnlockedScratchResources() = 0;
     virtual void resetContextIfApplicable() = 0; // No-op outside of GL (&& Ganesh at this point.)
 
diff --git a/services/surfaceflinger/SurfaceFlinger.cpp b/services/surfaceflinger/SurfaceFlinger.cpp
index 1793af6..52cbcdf 100644
--- a/services/surfaceflinger/SurfaceFlinger.cpp
+++ b/services/surfaceflinger/SurfaceFlinger.cpp
@@ -809,11 +809,11 @@
                 .setGraphicsApi(renderengine::RenderEngine::GraphicsApi::VK);
     } else {
         const auto kVulkan = renderengine::RenderEngine::GraphicsApi::VK;
-        const bool canSupportVulkan = renderengine::RenderEngine::canSupport(kVulkan);
-        const bool useGraphite =
-                canSupportVulkan && FlagManager::getInstance().graphite_renderengine();
+        const bool useGraphite = FlagManager::getInstance().graphite_renderengine() &&
+                renderengine::RenderEngine::canSupport(kVulkan);
         const bool useVulkan = useGraphite ||
-                (canSupportVulkan && FlagManager::getInstance().vulkan_renderengine());
+                (FlagManager::getInstance().vulkan_renderengine() &&
+                 renderengine::RenderEngine::canSupport(kVulkan));
 
         builder.setSkiaBackend(useGraphite ? renderengine::RenderEngine::SkiaBackend::GRAPHITE
                                            : renderengine::RenderEngine::SkiaBackend::GANESH);