mirror of
https://github.com/MobileGL-Dev/MobileGL
synced 2026-09-09 12:48:32 +09:00
[Fix] (DirectVulkan): submit pending work before growing a texture's mip chain
Sizing backings by their defined mip level count gave every level-0-only texture a single-level image, and left growing it to the recreate-and-preserve path: the new image is created and the old contents are carried over by a vkCmdCopyImage that PreserveTextureContentsOnRecreate submits on its own command buffer and waits on straight away. Whatever the frame has already recorded into the old image has not been submitted yet at that point, so that copy reads the texture as it stood before this frame's writes. GenerateMipmap then descends the whole chain from a stale level 0, and the composite pass that samples it renders a washed-out frame - minecraft-1.21.4-fabric-iris-iterationt-in-world (Iris's mipmapped colour target, the one texture in the trace that grows 1 -> 10 levels) came back at ssim 0.5699 against a 0.99 threshold. This is the hazard the storage-usage upgrade already flushes for before its own preserve-copy; growing the mip chain is simply the second trigger of that same recreate, and it was added without the same ordering guarantee. Flush there too, gated on a texture whose live image really does carry a short chain, so the submit happens once per texture and only when a recreate is actually coming. Keeps the single-level backing and its memory saving; ssim goes back to 0.9992.
This commit is contained in:
@@ -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<Int>(resource.extent.width), static_cast<Int>(resource.extent.height),
|
||||
static_cast<Int>(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);
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -7793,6 +7793,20 @@ void main() {
|
||||
|
||||
const Uint32 baseMipLevel = std::min(static_cast<Uint32>(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();
|
||||
|
||||
Reference in New Issue
Block a user