[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).

This commit is contained in:
2026-10-07 12:15:10 -04:00
parent afcf8d96f9
commit 5788a6023e
2 changed files with 149 additions and 3 deletions
@@ -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<Int>(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<MG_Pipe::MGPipeResourceTarget>(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
@@ -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<VkTextureManager::TextureResource*> sampledResources;
// The buffer descriptors in walk order: dynamic UBOs with offset 0 (their offset is the
// dynamic one), storage buffers whole.
Vector<VkDescriptorBufferInfo> bufferInfos;
};
WireDescriptorRebind m_wireDescriptorRebind;
Vector<VkDescriptorBufferInfo> m_wireRebindBufferScratch;
Vector<VkTextureManager::TextureResource*> 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);