From 5788a6023e724f7abd0525eb85528b9c48e78ecd Mon Sep 17 00:00:00 2001 From: Swung0x48 Date: Wed, 7 Oct 2026 12:15:10 -0400 Subject: [PATCH] [Perf] (Magma): a wire draw whose image descriptors cannot have moved rebinds its descriptor set with fresh buffer offsets (rd12 monolith 60.5 -> 63.3, inproc 83.7 -> 89.7 fps). --- .../DirectVulkan/Renderer/UniformManager.cpp | 116 +++++++++++++++++- .../DirectVulkan/Renderer/UniformManager.h | 36 +++++- 2 files changed, 149 insertions(+), 3 deletions(-) diff --git a/MobileGL/MG_Backend/DirectVulkan/Renderer/UniformManager.cpp b/MobileGL/MG_Backend/DirectVulkan/Renderer/UniformManager.cpp index df43ed72..c306ef75 100644 --- a/MobileGL/MG_Backend/DirectVulkan/Renderer/UniformManager.cpp +++ b/MobileGL/MG_Backend/DirectVulkan/Renderer/UniformManager.cpp @@ -153,8 +153,9 @@ namespace MobileGL::MG_Backend::DirectVulkan { Bool UniformManager::ResolveWireImageDescriptor( VkCommandBuffer commandBuffer, const MagmaProgramSource& program, const ProgramFactory::VkProgramObject& programObj, Uint32 binding, Uint32 element, - Bool storage, VkDescriptorImageInfo& out) const { + Bool storage, VkDescriptorImageInfo& out, VkTextureManager::TextureResource** outResource) const { out = {}; + if (outResource != nullptr) *outResource = nullptr; auto& state = MG_Pipe::MGPipeApplier(); const Int baseLocation = programObj.samplerUniformLocationByBinding[binding]; const Int location = baseLocation + static_cast(element); @@ -183,6 +184,7 @@ namespace MobileGL::MG_Backend::DirectVulkan { auto* resource = m_textureManager->SyncTextureResourceByHandle(handle, false, storage); if (!resource) WireDescriptorFatal("image-resource"); m_textureManager->FlushPendingUploads(); + if (outResource != nullptr) *outResource = resource; const auto target = static_cast(record.Desc.Target); const Bool layerable = WireTargetHasLayers(target); @@ -630,6 +632,7 @@ namespace MobileGL::MG_Backend::DirectVulkan { for (auto& entry : m_descriptorReuseMemo) { entry.valid = false; } + m_wireDescriptorRebind.valid = false; m_lastBindValid = false; } @@ -716,6 +719,7 @@ namespace MobileGL::MG_Backend::DirectVulkan { for (auto& entry : m_descriptorReuseMemo) { entry.valid = false; } + m_wireDescriptorRebind.valid = false; MGLOG_D("UniformDescriptorBinder: freed %zu descriptor sets for destroyed layout", purgedSets); } } @@ -1550,6 +1554,11 @@ namespace MobileGL::MG_Backend::DirectVulkan { if (frame.activeDescriptorPoolIndex >= frame.descriptorPools.size()) { frame.activeDescriptorPoolIndex = 0; } + if (TryRebindWireDescriptorSet(commandBuffer, program, programObj, frameIndex, bindPoint)) return true; + // What the full resolve below proves for the next draw's rebind (WireDescriptorRebind). + Bool rebindEligible = true; + auto& rebindResources = m_wireRebindResourcesScratch; + rebindResources.clear(); // The descriptor set is chosen AFTER the writes are built (below), so a draw // whose resolved descriptor content matches the previous draw can reuse that @@ -1609,6 +1618,10 @@ namespace MobileGL::MG_Backend::DirectVulkan { break; // ascending, so nothing past the cap can follow } const auto kind = programObj.bindingKinds[binding]; + if (kind != ProgramFactory::DescriptorBindingKind::UniformBufferDynamic && + kind != ProgramFactory::DescriptorBindingKind::StorageBuffer && + kind != ProgramFactory::DescriptorBindingKind::CombinedImageSampler) + rebindEligible = false; VkWriteDescriptorSet write{}; write.sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET; @@ -1729,8 +1742,12 @@ namespace MobileGL::MG_Backend::DirectVulkan { const SizeT firstImageInfoIndex = imageInfos.size(); for (Uint32 element = 0; element < descriptorCount; ++element) { VkDescriptorImageInfo imageInfo{}; + VkTextureManager::TextureResource* sampledResource = nullptr; const Bool hasImage = ResolveWireImageDescriptor(commandBuffer, program, programObj, binding, - element, false, imageInfo); + element, false, imageInfo, &sampledResource); + // A placeholder (an empty unit) has no resource to re-check: full resolve. + if (sampledResource == nullptr) rebindEligible = false; + else rebindResources.push_back(sampledResource); if (!hasImage) { MGLOG_E_ONCE( "UniformDescriptorBinder::BindProgramUniformBuffers failed: sampler binding %u element %u " @@ -1828,6 +1845,101 @@ namespace MobileGL::MG_Backend::DirectVulkan { BindDescriptorSetDeduped(commandBuffer, bindPoint, programObj.pipelineLayout, descriptorSet, dynamicOffsets); + + // Stamp what this full resolve depended on, for TryRebindWireDescriptorSet. Taken after + // the walk: the walk's own syncs and barriers are part of what it proved. + auto& rebind = m_wireDescriptorRebind; + rebind.valid = rebindEligible && descriptorSet != VK_NULL_HANDLE && + WireImageWriteEpoch() == m_wireImageBarrierEpoch; + if (rebind.valid) { + const auto& st = MG_Pipe::MGPipeApplier(); + rebind.frameIndex = frameIndex; + rebind.bindPoint = bindPoint; + rebind.programHash = programObj.hash; + rebind.layout = programObj.descriptorSetLayout; + rebind.pipelineLayout = programObj.pipelineLayout; + rebind.programHandle = program.Handle(); + rebind.programSerial = program.GetBackendStateVersion(); + rebind.programBindingsSerial = program.GetBlockBindingVersion(); + rebind.contextSerial = st.ContextSerial; + rebind.samplerViewsSerial = st.SamplerViewsSerial; + rebind.samplerStatesSerial = st.SamplerStatesSerial; + rebind.shaderImagesSerial = st.ShaderImagesSerial; + rebind.shaderBuffersSerial = st.ShaderBuffersSerial; + rebind.textureShutterSerial = st.TextureShutterSerial; + rebind.storeDestroyEpoch = m_bufferManager->GetWireStoreDestroyEpoch(); + rebind.textureImageEpoch = m_textureManager->GetTextureImageEpoch(); + rebind.resourceEraseEpoch = m_textureManager->GetResourceEraseEpoch(); + rebind.samplerDestroyEpoch = m_samplerManager->DestroyEpoch(); + rebind.samplerFrameBoundary = m_samplerManager->FrameBoundaryCount(); + rebind.set = descriptorSet; + rebind.sampledResources.assign(rebindResources.begin(), rebindResources.end()); + rebind.bufferInfos.assign(bufferInfos.begin(), bufferInfos.end()); + } + return true; + } + + Bool UniformManager::TryRebindWireDescriptorSet(VkCommandBuffer commandBuffer, const MagmaProgramSource& program, + const ProgramFactory::VkProgramObject& programObj, + Uint32 frameIndex, VkPipelineBindPoint bindPoint) { + const auto& rebind = m_wireDescriptorRebind; + if (!rebind.valid || rebind.frameIndex != frameIndex || rebind.bindPoint != bindPoint || + rebind.programHash != programObj.hash || rebind.layout != programObj.descriptorSetLayout || + rebind.pipelineLayout != programObj.pipelineLayout || !(rebind.programHandle == program.Handle()) || + rebind.programSerial != program.GetBackendStateVersion() || + rebind.programBindingsSerial != program.GetBlockBindingVersion()) + return false; + const auto& st = MG_Pipe::MGPipeApplier(); + if (rebind.contextSerial != st.ContextSerial || rebind.samplerViewsSerial != st.SamplerViewsSerial || + rebind.samplerStatesSerial != st.SamplerStatesSerial || rebind.shaderImagesSerial != st.ShaderImagesSerial || + rebind.shaderBuffersSerial != st.ShaderBuffersSerial || rebind.textureShutterSerial != st.TextureShutterSerial) + return false; + if (rebind.storeDestroyEpoch != m_bufferManager->GetWireStoreDestroyEpoch() || + rebind.textureImageEpoch != m_textureManager->GetTextureImageEpoch() || + rebind.resourceEraseEpoch != m_textureManager->GetResourceEraseEpoch() || + rebind.samplerDestroyEpoch != m_samplerManager->DestroyEpoch() || + rebind.samplerFrameBoundary != m_samplerManager->FrameBoundaryCount() || + WireImageWriteEpoch() != m_wireImageBarrierEpoch) + return false; + // The resource pointers are good while the erase epoch holds (checked above). A sampled + // image that left GENERAL (rendered to, copied into) needs the full resolve's transition. + for (const auto* resource : rebind.sampledResources) + if (resource->layout != VK_IMAGE_LAYOUT_GENERAL) return false; + // The buffer descriptors, in the walk's order, re-resolved: a UBO's dynamic offset is this + // draw's; its buffer and range, and every storage buffer's window, must be the set's. + auto& infos = m_wireRebindBufferScratch; + infos.clear(); + auto& dynamicOffsets = m_dynamicOffsetsScratch; + dynamicOffsets.clear(); + for (const Uint32 binding : programObj.activeBindings) { + if (binding >= m_maxBindings) break; + const auto kind = programObj.bindingKinds[binding]; + const Uint32 count = BindingDescriptorCount(programObj, binding); + if (kind == ProgramFactory::DescriptorBindingKind::UniformBufferDynamic) { + for (Uint32 element = 0; element < count; ++element) { + VkDescriptorBufferInfo info{}; + Uint32 offset = 0; + if (!ResolveDynamicUboDescriptor(program, programObj, binding, element, frameIndex, info.buffer, + info.range, offset)) + return false; + infos.push_back(info); + dynamicOffsets.push_back(offset); + } + } else if (kind == ProgramFactory::DescriptorBindingKind::StorageBuffer) { + for (Uint32 element = 0; element < count; ++element) { + VkDescriptorBufferInfo info{}; + if (!ResolveStorageBufferDescriptor(program, programObj, binding, element, info)) return false; + infos.push_back(info); + } + } + } + if (infos.size() != rebind.bufferInfos.size()) return false; + for (SizeT i = 0; i < infos.size(); ++i) { + const auto& a = infos[i]; + const auto& b = rebind.bufferInfos[i]; + if (a.buffer != b.buffer || a.offset != b.offset || a.range != b.range) return false; + } + BindDescriptorSetDeduped(commandBuffer, bindPoint, programObj.pipelineLayout, rebind.set, dynamicOffsets); return true; } } // namespace MobileGL::MG_Backend::DirectVulkan diff --git a/MobileGL/MG_Backend/DirectVulkan/Renderer/UniformManager.h b/MobileGL/MG_Backend/DirectVulkan/Renderer/UniformManager.h index bae2e5bc..08060b82 100644 --- a/MobileGL/MG_Backend/DirectVulkan/Renderer/UniformManager.h +++ b/MobileGL/MG_Backend/DirectVulkan/Renderer/UniformManager.h @@ -260,7 +260,41 @@ namespace MobileGL::MG_Backend::DirectVulkan { Bool storage); Bool ResolveWireImageDescriptor(VkCommandBuffer commandBuffer, const MagmaProgramSource& program, const ProgramFactory::VkProgramObject& programObj, Uint32 binding, - Uint32 element, Bool storage, VkDescriptorImageInfo& out) const; + Uint32 element, Bool storage, VkDescriptorImageInfo& out, + VkTextureManager::TextureResource** outResource = nullptr) const; + // P14: THE WIRE DESCRIPTOR REBIND. A draw whose image descriptors cannot have changed since + // the last full resolve of the same program in the same frame - same applier context, same + // unit / sampler / image / buffer-binding serials, same texture shutter, no texture image + // re-created or erased, no sampler destroyed, every sampled image still in GENERAL and no + // image write awaiting its barrier - re-resolves only its buffer descriptors (the UBO + // dynamic offsets move every draw) and, when those name the same buffers and ranges, + // rebinds the set it already has. Anything else, or any program with a texel buffer, storage + // image or placeholder descriptor, takes the full resolve. + struct WireDescriptorRebind { + Bool valid = false; + Uint32 frameIndex = 0; + VkPipelineBindPoint bindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS; + Uint64 programHash = 0; + VkDescriptorSetLayout layout = VK_NULL_HANDLE; + VkPipelineLayout pipelineLayout = VK_NULL_HANDLE; + MG_Pipe::MGPipeHandle programHandle = MG_Pipe::kMGPipeNullHandle; + Uint64 programSerial = 0, programBindingsSerial = 0; + Uint64 contextSerial = 0, samplerViewsSerial = 0, samplerStatesSerial = 0, shaderImagesSerial = 0; + Uint64 shaderBuffersSerial = 0, textureShutterSerial = 0; + Uint64 storeDestroyEpoch = 0, textureImageEpoch = 0, resourceEraseEpoch = 0; + Uint64 samplerDestroyEpoch = 0, samplerFrameBoundary = 0; + VkDescriptorSet set = VK_NULL_HANDLE; + Vector sampledResources; + // The buffer descriptors in walk order: dynamic UBOs with offset 0 (their offset is the + // dynamic one), storage buffers whole. + Vector bufferInfos; + }; + WireDescriptorRebind m_wireDescriptorRebind; + Vector m_wireRebindBufferScratch; + Vector m_wireRebindResourcesScratch; + Bool TryRebindWireDescriptorSet(VkCommandBuffer commandBuffer, const MagmaProgramSource& program, + const ProgramFactory::VkProgramObject& programObj, Uint32 frameIndex, + VkPipelineBindPoint bindPoint); Bool ResolveWireTexelBufferDescriptor(const MagmaProgramSource& program, const ProgramFactory::VkProgramObject& programObj, Uint32 binding, Uint32 frameIndex, Bool storage, VkBufferView& out);