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);