[Perf] (MG_Backend/DirectVulkan): skip per-draw render-pass hash when framebuffer state unchanged; SetupDraw 60%->54%, fps 96->109

This commit is contained in:
2026-07-12 08:18:37 -04:00
parent ad9ee99521
commit 542e50be33
4 changed files with 61 additions and 0 deletions
@@ -210,6 +210,7 @@ namespace MobileGL::MG_Backend::DirectVulkan {
RenderPassEntry::s_textureResourcesScratch.clear();
s_activeRenderPass = {};
s_hasActiveRenderPass = false;
m_rpFastValid = false;
}
void VkRenderPassManager::CollectRenderbufferGarbage() {
@@ -305,6 +306,7 @@ namespace MobileGL::MG_Backend::DirectVulkan {
allocationInfo.requiredFlags = VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT;
VK_VERIFY(vmaCreateImage(m_allocator, &imageInfo, &allocationInfo, &resource.image, &resource.allocation, nullptr),
"vmaCreateImage(renderbuffer)");
++m_renderbufferImageEpoch; // a new attachment image invalidates cached render passes
VkImageViewCreateInfo viewInfo{};
viewInfo.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO;
@@ -585,6 +587,25 @@ namespace MobileGL::MG_Backend::DirectVulkan {
// retrieve from cache first
auto* activeRenderPass = GetActiveRenderPass();
// Dirty-flag state tracking: when the framebuffer state is provably unchanged since the
// render pass was last resolved, the active render pass is still valid -> skip the
// expensive per-draw ComputeHash (XXH64 over every attachment + a SyncTexture per
// attachment). Correctness signals: same FBO object + GetObjectVersion (attachment /
// draw-buffer / read-buffer changes bump it), same swapchain image, no attachment VkImage
// recreated since (texture + renderbuffer image epochs), and no pending clear (which alters
// load ops). Any of these differing forces the full recompute below. Portable to VK 1.1.
if (activeRenderPass != nullptr && m_rpFastValid && m_rpFastFbo == &fbo &&
m_rpFastFboVersion == fbo.GetObjectVersion() && m_rpFastSwapchainIndex == swapchainImageIndex &&
m_rpFastTexEpoch == m_textureManager.GetTextureImageEpoch() &&
m_rpFastRbEpoch == m_renderbufferImageEpoch &&
m_rpFastRenderPassHash == activeRenderPass->hash && !hasPendingClearOnFramebuffer()) {
auto activeIt = m_renderPasses.find(activeRenderPass->hash);
if (activeIt != m_renderPasses.end()) {
return activeIt->second;
}
}
auto compatibilityHash = ComputeHash(fbo, swapchainImageIndex, false);
if (activeRenderPass != nullptr &&
activeRenderPass->CompatibleWith(compatibilityHash) &&
@@ -593,6 +614,15 @@ namespace MobileGL::MG_Backend::DirectVulkan {
MOBILEGL_ASSERT(activeIt != m_renderPasses.end(),
"GetOrCreateRenderPass: active render pass hash=0x%llx is missing from cache",
static_cast<unsigned long long>(activeRenderPass->hash));
// Populate the fast-path memo so subsequent unchanged draws skip ComputeHash. Read the
// epochs AFTER ComputeHash: its attachment SyncTexture can create an image (bump the epoch).
m_rpFastValid = true;
m_rpFastFbo = &fbo;
m_rpFastFboVersion = fbo.GetObjectVersion();
m_rpFastSwapchainIndex = swapchainImageIndex;
m_rpFastTexEpoch = m_textureManager.GetTextureImageEpoch();
m_rpFastRbEpoch = m_renderbufferImageEpoch;
m_rpFastRenderPassHash = activeRenderPass->hash;
return activeIt->second;
}
auto hash = ComputeHash(fbo, swapchainImageIndex, true);
@@ -1069,6 +1099,11 @@ namespace MobileGL::MG_Backend::DirectVulkan {
Bool VkRenderPassManager::EndRenderPass(VkCommandBuffer commandBuffer) {
auto* activeRenderPass = GetActiveRenderPass();
vkCmdEndRenderPass(commandBuffer);
// The fast-path memo reuses the ACTIVE render pass; once the pass ends it must not carry
// over (the next span may be a different FBO resolved before its render pass is begun).
if (s_renderPassManager != nullptr) {
s_renderPassManager->m_rpFastValid = false;
}
if (activeRenderPass != nullptr && !activeRenderPass->trackedAttachmentLayouts.empty()) {
for (const auto& trackedAttachment : activeRenderPass->trackedAttachmentLayouts) {
switch (trackedAttachment.target) {
@@ -179,6 +179,24 @@ namespace MobileGL::MG_Backend::DirectVulkan {
SwapchainObject& m_swapchainObject;
UnorderedMap<Uint64, RenderPassEntry> m_renderPasses;
// Bumped whenever a renderbuffer VkImage is (re)created; together with the texture
// manager's image epoch this invalidates the render-pass fast path on any attachment
// image recreation.
Uint64 m_renderbufferImageEpoch = 1;
// Per-draw fast-path memo for GetOrCreateRenderPass (dirty-flag state tracking): when the
// framebuffer state is provably unchanged since the last resolution, the active render pass
// is reused WITHOUT recomputing the expensive per-draw hash. Invalidated by FBO switch /
// version change, swapchain rotation, any attachment image recreation (the two epochs),
// or a pending clear. Portable to Vulkan 1.1 (no dynamic_rendering / imageless FB needed).
Bool m_rpFastValid = false;
const MG_State::GLState::FramebufferObject* m_rpFastFbo = nullptr;
Uint16 m_rpFastFboVersion = 0;
Uint32 m_rpFastSwapchainIndex = 0;
Uint64 m_rpFastTexEpoch = 0;
Uint64 m_rpFastRbEpoch = 0;
Uint64 m_rpFastRenderPassHash = 0;
struct RenderbufferResource {
WeakPtr<MG_State::GLState::RenderbufferObject> renderbuffer;
VkImage image = VK_NULL_HANDLE;
@@ -1252,6 +1252,7 @@ namespace MobileGL::MG_Backend::DirectVulkan {
allocationInfo.requiredFlags = VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT;
VK_VERIFY(vmaCreateImage(m_allocator, &imageInfo, &allocationInfo, &resource.image, &resource.allocation, nullptr),
"vmaCreateImage(texture)");
++m_textureImageEpoch; // a new attachment image invalidates cached render passes
resource.layout = VK_IMAGE_LAYOUT_UNDEFINED;
resource.extent = {static_cast<Uint32>(texelSize.x()), static_cast<Uint32>(texelSize.y())};
@@ -21,6 +21,11 @@ class ITextureObject;
namespace MobileGL::MG_Backend::DirectVulkan {
class VkTextureManager {
public:
// Monotonic epoch bumped whenever a texture VkImage is (re)created. The render-pass
// manager keys its per-draw fast path on this so an attachment's image recreation
// invalidates the cached render pass (dirty-flag tracking; portable to Vulkan 1.1).
Uint64 GetTextureImageEpoch() const { return m_textureImageEpoch; }
struct TextureIdentity {
MG_State::GLState::ITextureObject* texture = nullptr;
Uint64 lifetimeId = 0;
@@ -225,6 +230,8 @@ public:
};
private:
// Bumped in SyncTextureResource right after vmaCreateImage(texture). See GetTextureImageEpoch().
Uint64 m_textureImageEpoch = 1;
Bool SyncTexture(MG_State::GLState::ITextureObject &texture,
TextureResource &outResource);