mirror of
https://github.com/MobileGL-Dev/MobileGL
synced 2026-11-15 05:53:23 +09:00
[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:
@@ -153,8 +153,9 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
|||||||
Bool UniformManager::ResolveWireImageDescriptor(
|
Bool UniformManager::ResolveWireImageDescriptor(
|
||||||
VkCommandBuffer commandBuffer, const MagmaProgramSource& program,
|
VkCommandBuffer commandBuffer, const MagmaProgramSource& program,
|
||||||
const ProgramFactory::VkProgramObject& programObj, Uint32 binding, Uint32 element,
|
const ProgramFactory::VkProgramObject& programObj, Uint32 binding, Uint32 element,
|
||||||
Bool storage, VkDescriptorImageInfo& out) const {
|
Bool storage, VkDescriptorImageInfo& out, VkTextureManager::TextureResource** outResource) const {
|
||||||
out = {};
|
out = {};
|
||||||
|
if (outResource != nullptr) *outResource = nullptr;
|
||||||
auto& state = MG_Pipe::MGPipeApplier();
|
auto& state = MG_Pipe::MGPipeApplier();
|
||||||
const Int baseLocation = programObj.samplerUniformLocationByBinding[binding];
|
const Int baseLocation = programObj.samplerUniformLocationByBinding[binding];
|
||||||
const Int location = baseLocation + static_cast<Int>(element);
|
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);
|
auto* resource = m_textureManager->SyncTextureResourceByHandle(handle, false, storage);
|
||||||
if (!resource) WireDescriptorFatal("image-resource");
|
if (!resource) WireDescriptorFatal("image-resource");
|
||||||
m_textureManager->FlushPendingUploads();
|
m_textureManager->FlushPendingUploads();
|
||||||
|
if (outResource != nullptr) *outResource = resource;
|
||||||
|
|
||||||
const auto target = static_cast<MG_Pipe::MGPipeResourceTarget>(record.Desc.Target);
|
const auto target = static_cast<MG_Pipe::MGPipeResourceTarget>(record.Desc.Target);
|
||||||
const Bool layerable = WireTargetHasLayers(target);
|
const Bool layerable = WireTargetHasLayers(target);
|
||||||
@@ -630,6 +632,7 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
|||||||
for (auto& entry : m_descriptorReuseMemo) {
|
for (auto& entry : m_descriptorReuseMemo) {
|
||||||
entry.valid = false;
|
entry.valid = false;
|
||||||
}
|
}
|
||||||
|
m_wireDescriptorRebind.valid = false;
|
||||||
m_lastBindValid = false;
|
m_lastBindValid = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -716,6 +719,7 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
|||||||
for (auto& entry : m_descriptorReuseMemo) {
|
for (auto& entry : m_descriptorReuseMemo) {
|
||||||
entry.valid = false;
|
entry.valid = false;
|
||||||
}
|
}
|
||||||
|
m_wireDescriptorRebind.valid = false;
|
||||||
MGLOG_D("UniformDescriptorBinder: freed %zu descriptor sets for destroyed layout", purgedSets);
|
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()) {
|
if (frame.activeDescriptorPoolIndex >= frame.descriptorPools.size()) {
|
||||||
frame.activeDescriptorPoolIndex = 0;
|
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
|
// 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
|
// 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
|
break; // ascending, so nothing past the cap can follow
|
||||||
}
|
}
|
||||||
const auto kind = programObj.bindingKinds[binding];
|
const auto kind = programObj.bindingKinds[binding];
|
||||||
|
if (kind != ProgramFactory::DescriptorBindingKind::UniformBufferDynamic &&
|
||||||
|
kind != ProgramFactory::DescriptorBindingKind::StorageBuffer &&
|
||||||
|
kind != ProgramFactory::DescriptorBindingKind::CombinedImageSampler)
|
||||||
|
rebindEligible = false;
|
||||||
|
|
||||||
VkWriteDescriptorSet write{};
|
VkWriteDescriptorSet write{};
|
||||||
write.sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET;
|
write.sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET;
|
||||||
@@ -1729,8 +1742,12 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
|||||||
const SizeT firstImageInfoIndex = imageInfos.size();
|
const SizeT firstImageInfoIndex = imageInfos.size();
|
||||||
for (Uint32 element = 0; element < descriptorCount; ++element) {
|
for (Uint32 element = 0; element < descriptorCount; ++element) {
|
||||||
VkDescriptorImageInfo imageInfo{};
|
VkDescriptorImageInfo imageInfo{};
|
||||||
|
VkTextureManager::TextureResource* sampledResource = nullptr;
|
||||||
const Bool hasImage = ResolveWireImageDescriptor(commandBuffer, program, programObj, binding,
|
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) {
|
if (!hasImage) {
|
||||||
MGLOG_E_ONCE(
|
MGLOG_E_ONCE(
|
||||||
"UniformDescriptorBinder::BindProgramUniformBuffers failed: sampler binding %u element %u "
|
"UniformDescriptorBinder::BindProgramUniformBuffers failed: sampler binding %u element %u "
|
||||||
@@ -1828,6 +1845,101 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
|||||||
|
|
||||||
BindDescriptorSetDeduped(commandBuffer, bindPoint, programObj.pipelineLayout, descriptorSet,
|
BindDescriptorSetDeduped(commandBuffer, bindPoint, programObj.pipelineLayout, descriptorSet,
|
||||||
dynamicOffsets);
|
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;
|
return true;
|
||||||
}
|
}
|
||||||
} // namespace MobileGL::MG_Backend::DirectVulkan
|
} // namespace MobileGL::MG_Backend::DirectVulkan
|
||||||
|
|||||||
@@ -260,7 +260,41 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
|||||||
Bool storage);
|
Bool storage);
|
||||||
Bool ResolveWireImageDescriptor(VkCommandBuffer commandBuffer, const MagmaProgramSource& program,
|
Bool ResolveWireImageDescriptor(VkCommandBuffer commandBuffer, const MagmaProgramSource& program,
|
||||||
const ProgramFactory::VkProgramObject& programObj, Uint32 binding,
|
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,
|
Bool ResolveWireTexelBufferDescriptor(const MagmaProgramSource& program,
|
||||||
const ProgramFactory::VkProgramObject& programObj, Uint32 binding,
|
const ProgramFactory::VkProgramObject& programObj, Uint32 binding,
|
||||||
Uint32 frameIndex, Bool storage, VkBufferView& out);
|
Uint32 frameIndex, Bool storage, VkBufferView& out);
|
||||||
|
|||||||
Reference in New Issue
Block a user