diff --git a/MobileGL/MG_Backend/DirectVulkan/Renderer/VkTextureManager.cpp b/MobileGL/MG_Backend/DirectVulkan/Renderer/VkTextureManager.cpp index 69a2b049..322805b9 100644 --- a/MobileGL/MG_Backend/DirectVulkan/Renderer/VkTextureManager.cpp +++ b/MobileGL/MG_Backend/DirectVulkan/Renderer/VkTextureManager.cpp @@ -683,10 +683,39 @@ namespace MobileGL::MG_Backend::DirectVulkan { } } + void VkTextureManager::BeginDrawSyncScope() { + m_drawSyncedThisDraw.clear(); + m_drawSyncScopeActive = true; + } + + void VkTextureManager::EndDrawSyncScope() { + m_drawSyncScopeActive = false; + m_drawSyncedThisDraw.clear(); + } + VkTextureManager::TextureResource* VkTextureManager::SyncTextureAndGetDescriptor(MG_State::GLState::ITextureObject& texture) { MOBILEGL_ASSERT(m_device != VK_NULL_HANDLE, "SyncTextureAndGetDescriptor: m_device == VK_NULL_HANDLE"); const TextureIdentity identity = MakeTextureIdentity(&texture); + + // Per-draw memo fast path (see BeginDrawSyncScope): a texture already fully + // synced earlier in this draw cannot have changed since (no GL mutation runs + // mid-SetupDraw), so skip the heavy SyncTexture work and hand back the + // already-synced resource. Layout lives on the resource and is updated by the + // transition path, so the short-circuited resource still reflects the truth. + const Bool memoActive = m_drawSyncScopeActive; + if (memoActive) { + for (const TextureIdentity& synced : m_drawSyncedThisDraw) { + if (synced == identity) { + auto cachedIt = m_textureResources.find(identity); + if (cachedIt != m_textureResources.end() && cachedIt->second.image != VK_NULL_HANDLE) { + return &(cachedIt->second); + } + break; // resource unexpectedly gone -> fall through to a full sync + } + } + } + auto aliveIt = m_aliveObjects.find(identity); if (aliveIt != m_aliveObjects.end() && aliveIt->second.expired()) { EraseTrackedTexture(aliveIt->first); @@ -717,6 +746,19 @@ namespace MobileGL::MG_Backend::DirectVulkan { return nullptr; } + if (memoActive) { + Bool recorded = false; + for (const TextureIdentity& synced : m_drawSyncedThisDraw) { + if (synced == identity) { + recorded = true; + break; + } + } + if (!recorded) { + m_drawSyncedThisDraw.push_back(identity); + } + } + return &(it->second); } diff --git a/MobileGL/MG_Backend/DirectVulkan/Renderer/VkTextureManager.h b/MobileGL/MG_Backend/DirectVulkan/Renderer/VkTextureManager.h index 1727481f..5aca9718 100644 --- a/MobileGL/MG_Backend/DirectVulkan/Renderer/VkTextureManager.h +++ b/MobileGL/MG_Backend/DirectVulkan/Renderer/VkTextureManager.h @@ -200,6 +200,30 @@ public: Uint32 layerCount = 1); SizeT CollectGarbage(); + + // Per-draw sync memo. Within a single SetupDraw the same sampled texture is + // resolved ~3x (SetupDraw's layout-probe loop, its post-transition loop, and + // again inside ResolveSamplerDescriptor). No GL texture mutation can happen + // mid-SetupDraw, and layout is tracked on the TextureResource independently of + // SyncTexture, so after the first successful sync of a texture in a draw the + // heavy SyncTexture work (mip-completeness/resource/view resync + dirty scan) + // is pure redundancy. BeginDrawSyncScope opens a window in which repeat + // SyncTextureAndGetDescriptor calls short-circuit to the already-synced + // resource; EndDrawSyncScope closes it. Use the RAII DrawSyncScope guard. + void BeginDrawSyncScope(); + void EndDrawSyncScope(); + + // RAII guard that opens/closes a per-draw sync memo window (see above). + class DrawSyncScope { + public: + explicit DrawSyncScope(VkTextureManager& manager) : m_manager(manager) { m_manager.BeginDrawSyncScope(); } + ~DrawSyncScope() { m_manager.EndDrawSyncScope(); } + DrawSyncScope(const DrawSyncScope&) = delete; + DrawSyncScope& operator=(const DrawSyncScope&) = delete; + private: + VkTextureManager& m_manager; + }; + private: Bool SyncTexture(MG_State::GLState::ITextureObject &texture, @@ -242,6 +266,10 @@ private: Uint32 m_currentFrameIndex = 0; Uint8 m_gcCounter = 0; + // Active only between BeginDrawSyncScope/EndDrawSyncScope; identities of + // textures already fully synced in the current draw (small N -> flat scan). + Bool m_drawSyncScopeActive = false; + Vector m_drawSyncedThisDraw; std::unordered_map, TextureIdentityHash> m_aliveObjects; std::unordered_map m_textureResources; Vector> m_deferredReleases; diff --git a/MobileGL/MG_Backend/DirectVulkan/Renderer/VulkanRenderer.cpp b/MobileGL/MG_Backend/DirectVulkan/Renderer/VulkanRenderer.cpp index 2639a263..6f8535a0 100644 --- a/MobileGL/MG_Backend/DirectVulkan/Renderer/VulkanRenderer.cpp +++ b/MobileGL/MG_Backend/DirectVulkan/Renderer/VulkanRenderer.cpp @@ -3285,6 +3285,10 @@ void main() { Bool VulkanRenderer::SetupDraw(FrameContext::FrameData& frame, GLenum mode, Flags aspects, const DrawCmdParam& drawParams, const IndexBufferView* pIndexBufferView) { + // Sync each sampled texture at most once across this whole draw: the layout + // probe loop, the post-transition loop, and ResolveSamplerDescriptor would + // otherwise each re-run the full SyncTexture path on the same textures. + VkTextureManager::DrawSyncScope drawSyncScope(*m_textureManager); m_textureManager->CollectGarbage(); const auto& drawFbo = MG_State::pGLContext->GetFramebufferBindingSlot(FramebufferTarget::Draw).GetBoundObject();