mirror of
https://github.com/MobileGL-Dev/MobileGL
synced 2026-09-11 21:58:31 +09:00
[Perf] (MG_Backend/DirectVulkan): zero-copy UBO bind - point descriptor at the app's persistent VkBuffer instead of a per-draw transient copy; fps 127->166
This commit is contained in:
@@ -578,9 +578,9 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
|||||||
|
|
||||||
Bool UniformManager::ResolveUniformBufferPayload(const MG_State::GLState::ProgramObject& program,
|
Bool UniformManager::ResolveUniformBufferPayload(const MG_State::GLState::ProgramObject& program,
|
||||||
const ProgramFactory::VkProgramObject& programObj, Uint32 binding,
|
const ProgramFactory::VkProgramObject& programObj, Uint32 binding,
|
||||||
const void*& outData, VkDeviceSize& outSize) const {
|
UboBindResult& out) const {
|
||||||
outData = nullptr;
|
const void* outData = nullptr;
|
||||||
outSize = 0;
|
VkDeviceSize outSize = 0;
|
||||||
|
|
||||||
MOBILEGL_ASSERT(MG_State::pGLContext != nullptr, "ResolveUniformBufferPayload: GL context is null");
|
MOBILEGL_ASSERT(MG_State::pGLContext != nullptr, "ResolveUniformBufferPayload: GL context is null");
|
||||||
MOBILEGL_ASSERT(binding < programObj.bindingKinds.size(),
|
MOBILEGL_ASSERT(binding < programObj.bindingKinds.size(),
|
||||||
@@ -596,6 +596,9 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
|||||||
outData = emptyGlobalUbo.data();
|
outData = emptyGlobalUbo.data();
|
||||||
outSize = static_cast<VkDeviceSize>(emptyGlobalUbo.size());
|
outSize = static_cast<VkDeviceSize>(emptyGlobalUbo.size());
|
||||||
}
|
}
|
||||||
|
// The global UBO is CPU uniform data, not an app buffer -> always UploadTransient.
|
||||||
|
out.payload = outData;
|
||||||
|
out.payloadSize = outSize;
|
||||||
return outData != nullptr && outSize > 0;
|
return outData != nullptr && outSize > 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -659,6 +662,26 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
|||||||
Memcpy(paddedUbo.data(), outData, static_cast<SizeT>(available));
|
Memcpy(paddedUbo.data(), outData, static_cast<SizeT>(available));
|
||||||
outData = paddedUbo.data();
|
outData = paddedUbo.data();
|
||||||
}
|
}
|
||||||
|
out.payload = outData;
|
||||||
|
out.payloadSize = outSize;
|
||||||
|
|
||||||
|
// Zero-copy direct bind: for a persistent-mapped coherent app buffer whose full reflected
|
||||||
|
// block fits within the aligned bound range, point the descriptor straight at the app's
|
||||||
|
// resident VkBuffer (the same buffer the GLES backend binds) with the block range as the
|
||||||
|
// dynamic offset - no per-draw copy into a transient ring. Any gate failing keeps the
|
||||||
|
// UploadTransient payload above. AcquireResidentSlice does the persistent busy-tracking and
|
||||||
|
// (for the persistent case) hits a zero-work fast path returning the whole-buffer slice.
|
||||||
|
if (bufferObject->IsBackendPersistentMapped() && available >= blockSize &&
|
||||||
|
(rangeStart % m_minDynamicOffsetAlignment) == 0) {
|
||||||
|
BufferSlice slice{};
|
||||||
|
if (m_bufferManager->AcquireResidentSlice(BufferKind::Uniform, bufferObject, slice) &&
|
||||||
|
slice.IsValid() && slice.offset == 0 && slice.size >= rangeStart + blockSize) {
|
||||||
|
out.directBindable = true;
|
||||||
|
out.buffer = slice.buffer;
|
||||||
|
out.range = blockSize;
|
||||||
|
out.dynamicOffset = rangeStart;
|
||||||
|
}
|
||||||
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -850,31 +873,40 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
|||||||
write.descriptorCount = 1;
|
write.descriptorCount = 1;
|
||||||
|
|
||||||
if (kind == ProgramFactory::DescriptorBindingKind::UniformBufferDynamic) {
|
if (kind == ProgramFactory::DescriptorBindingKind::UniformBufferDynamic) {
|
||||||
const void* payload = nullptr;
|
UboBindResult ubo{};
|
||||||
VkDeviceSize payloadSize = 0;
|
const Bool hasPayload = ResolveUniformBufferPayload(program, programObj, binding, ubo);
|
||||||
const Bool hasPayload = ResolveUniformBufferPayload(program, programObj, binding, payload, payloadSize);
|
MOBILEGL_ASSERT(hasPayload && ubo.payload != nullptr && ubo.payloadSize > 0,
|
||||||
MOBILEGL_ASSERT(hasPayload && payload != nullptr && payloadSize > 0,
|
|
||||||
"UniformDescriptorBinder::BindProgramUniformBuffers failed: missing UBO payload on binding %u",
|
"UniformDescriptorBinder::BindProgramUniformBuffers failed: missing UBO payload on binding %u",
|
||||||
binding);
|
binding);
|
||||||
|
|
||||||
BufferSlice slice{};
|
|
||||||
if (!m_bufferManager->UploadTransient(BufferKind::Uniform, frameIndex, payload, payloadSize,
|
|
||||||
m_minDynamicOffsetAlignment, slice)) {
|
|
||||||
MOBILEGL_ASSERT(false, "UniformDescriptorBinder::BindProgramUniformBuffers failed: UBO upload failed on binding %u",
|
|
||||||
binding);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
VkDescriptorBufferInfo bufferInfo{};
|
VkDescriptorBufferInfo bufferInfo{};
|
||||||
bufferInfo.buffer = slice.buffer;
|
// Keep offset 0 (sub-range selected via the dynamic offset) so the hashed bufferInfo
|
||||||
|
// is stable across draws and the descriptor-set reuse cache keeps hitting.
|
||||||
bufferInfo.offset = 0;
|
bufferInfo.offset = 0;
|
||||||
bufferInfo.range = payloadSize;
|
Uint32 dynOffset;
|
||||||
|
if (ubo.directBindable) {
|
||||||
|
// Zero-copy: bind the app's resident VkBuffer directly, no per-draw memcpy.
|
||||||
|
bufferInfo.buffer = ubo.buffer;
|
||||||
|
bufferInfo.range = ubo.range;
|
||||||
|
dynOffset = static_cast<Uint32>(ubo.dynamicOffset);
|
||||||
|
} else {
|
||||||
|
BufferSlice slice{};
|
||||||
|
if (!m_bufferManager->UploadTransient(BufferKind::Uniform, frameIndex, ubo.payload,
|
||||||
|
ubo.payloadSize, m_minDynamicOffsetAlignment, slice)) {
|
||||||
|
MOBILEGL_ASSERT(false, "UniformDescriptorBinder::BindProgramUniformBuffers failed: UBO upload failed on binding %u",
|
||||||
|
binding);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
bufferInfo.buffer = slice.buffer;
|
||||||
|
bufferInfo.range = ubo.payloadSize;
|
||||||
|
dynOffset = static_cast<Uint32>(slice.offset);
|
||||||
|
}
|
||||||
bufferInfos.push_back(bufferInfo);
|
bufferInfos.push_back(bufferInfo);
|
||||||
|
|
||||||
write.descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER_DYNAMIC;
|
write.descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER_DYNAMIC;
|
||||||
write.pBufferInfo = &bufferInfos.back();
|
write.pBufferInfo = &bufferInfos.back();
|
||||||
writes.push_back(write);
|
writes.push_back(write);
|
||||||
dynamicOffsets.push_back(static_cast<Uint32>(slice.offset));
|
dynamicOffsets.push_back(dynOffset);
|
||||||
} else if (kind == ProgramFactory::DescriptorBindingKind::UniformTexelBuffer) {
|
} else if (kind == ProgramFactory::DescriptorBindingKind::UniformTexelBuffer) {
|
||||||
VkBufferView bufferView = VK_NULL_HANDLE;
|
VkBufferView bufferView = VK_NULL_HANDLE;
|
||||||
if (!ResolveTexelBufferDescriptor(program, programObj, binding, frameIndex, bufferView) ||
|
if (!ResolveTexelBufferDescriptor(program, programObj, binding, frameIndex, bufferView) ||
|
||||||
|
|||||||
@@ -89,9 +89,19 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
|||||||
const MG_State::GLState::ProgramObject& program,
|
const MG_State::GLState::ProgramObject& program,
|
||||||
const ProgramFactory::VkProgramObject& programObj, Uint32 binding,
|
const ProgramFactory::VkProgramObject& programObj, Uint32 binding,
|
||||||
VkDescriptorImageInfo& outImageInfo) const;
|
VkDescriptorImageInfo& outImageInfo) const;
|
||||||
|
// Result of resolving a UBO binding: either a zero-copy direct bind to the app's resident
|
||||||
|
// VkBuffer (the GLES backend's approach - no per-draw copy) or the CPU payload to upload.
|
||||||
|
struct UboBindResult {
|
||||||
|
Bool directBindable = false;
|
||||||
|
VkBuffer buffer = VK_NULL_HANDLE;
|
||||||
|
VkDeviceSize range = 0; // reflected block size; constant across draws (hashed)
|
||||||
|
VkDeviceSize dynamicOffset = 0; // block range start; moves per draw (NOT hashed)
|
||||||
|
const void* payload = nullptr; // fallback UploadTransient path
|
||||||
|
VkDeviceSize payloadSize = 0;
|
||||||
|
};
|
||||||
Bool ResolveUniformBufferPayload(const MG_State::GLState::ProgramObject& program,
|
Bool ResolveUniformBufferPayload(const MG_State::GLState::ProgramObject& program,
|
||||||
const ProgramFactory::VkProgramObject& programObj, Uint32 binding,
|
const ProgramFactory::VkProgramObject& programObj, Uint32 binding,
|
||||||
const void*& outData, VkDeviceSize& outSize) const;
|
UboBindResult& out) const;
|
||||||
Bool CreateDescriptorPool(Uint32 maxSets, VkDescriptorPool& outPool) const;
|
Bool CreateDescriptorPool(Uint32 maxSets, VkDescriptorPool& outPool) const;
|
||||||
Bool GrowFrameDescriptorPool(FrameResources& frame, Uint32 frameIndex);
|
Bool GrowFrameDescriptorPool(FrameResources& frame, Uint32 frameIndex);
|
||||||
VkResult AllocateDescriptorSetsFromActivePool(
|
VkResult AllocateDescriptorSetsFromActivePool(
|
||||||
|
|||||||
Reference in New Issue
Block a user