diff --git a/MobileGL/MG_Backend/DirectVulkan/Renderer/VkTextureManager.cpp b/MobileGL/MG_Backend/DirectVulkan/Renderer/VkTextureManager.cpp index 73814b6f..18a967e7 100644 --- a/MobileGL/MG_Backend/DirectVulkan/Renderer/VkTextureManager.cpp +++ b/MobileGL/MG_Backend/DirectVulkan/Renderer/VkTextureManager.cpp @@ -1246,6 +1246,20 @@ namespace MobileGL::MG_Backend::DirectVulkan { !it->second.storageUsageResolved; } + Bool VkTextureManager::NeedsMipChainGrowth(MG_State::GLState::ITextureObject& texture) const { + const TextureIdentity identity = MakeTextureIdentity(&texture); + const auto it = m_textureResources.find(identity); + // No image yet: the first sync sizes the chain from the levels the texture already + // defines, so nothing is recreated and there is nothing to order against. + if (it == m_textureResources.end() || it->second.image == VK_NULL_HANDLE) { + return false; + } + const TextureResource& resource = it->second; + const IntVec3 extent = {static_cast(resource.extent.width), static_cast(resource.extent.height), + static_cast(resource.depth)}; + return resource.mipLevels < ComputeFullMipLevelCount(extent); + } + Bool VkTextureManager::NeedsStorageImagePreparation(MG_State::GLState::ITextureObject& texture) const { const TextureIdentity identity = MakeTextureIdentity(&texture); const auto it = m_textureResources.find(identity); diff --git a/MobileGL/MG_Backend/DirectVulkan/Renderer/VkTextureManager.h b/MobileGL/MG_Backend/DirectVulkan/Renderer/VkTextureManager.h index a3e3e1ed..50ec65fb 100644 --- a/MobileGL/MG_Backend/DirectVulkan/Renderer/VkTextureManager.h +++ b/MobileGL/MG_Backend/DirectVulkan/Renderer/VkTextureManager.h @@ -350,6 +350,10 @@ public: // will recreate it with STORAGE usage and copy the old contents forward. Callers use this to // submit their pending recording first, so that copy cannot read pre-flush content. Bool NeedsStorageUsageUpgrade(MG_State::GLState::ITextureObject& texture) const; + // The same ordering question for the other recreate-and-preserve trigger: true when this + // texture's live image carries a shorter mip chain than a full one, so defining the missing + // levels recreates it and copies the old contents forward. + Bool NeedsMipChainGrowth(MG_State::GLState::ITextureObject& texture) const; // Non-mutating probe for the per-draw storage-image fast path: true when preparing this // texture as a storage image may need work that is illegal inside a render pass (resource // creation, dirty-content upload, or a layout transition to GENERAL). Unknown state reports diff --git a/MobileGL/MG_Backend/DirectVulkan/Renderer/VulkanRenderer.cpp b/MobileGL/MG_Backend/DirectVulkan/Renderer/VulkanRenderer.cpp index f34b90f0..2a3860d7 100644 --- a/MobileGL/MG_Backend/DirectVulkan/Renderer/VulkanRenderer.cpp +++ b/MobileGL/MG_Backend/DirectVulkan/Renderer/VulkanRenderer.cpp @@ -7793,6 +7793,20 @@ void main() { const Uint32 baseMipLevel = std::min(static_cast(texture->GetLevelRange().x()), currentMipLevelCount - 1); + // A texture that has only ever defined level 0 carries a single-level backing, so defining + // the rest of the chain below recreates the image and carries the old contents over with a + // copy that is submitted and waited on out of band. Anything this frame has already + // recorded into the old image is not submitted yet, so that copy would read pre-flush + // content and every generated level would descend from a stale level 0 - the same hazard + // the storage-usage upgrade flushes for before its own preserve-copy. + if (m_textureManager->NeedsMipChainGrowth(*texture) && HasPendingRecordedWork()) { + if (FlushPendingCommands()) { + // Fresh command buffer: the sampled-descriptor-set memo describes bindings that + // only existed in the retired one. + m_lastSampledSetValid = false; + } + } + auto& frame = m_frameContext.GetCurrent(); if (!frame.isCommandRecording) { m_frameContext.BeginCommandRecording();