[Feat] (MG_Backend/DirectVulkan): VkBufferManager transient upload now includes uniform

This commit is contained in:
2026-03-20 13:31:51 +08:00
parent bfa4049ac1
commit 9d1e8bd7cd
5 changed files with 42 additions and 50 deletions
@@ -174,14 +174,14 @@ namespace MobileGL::MG_Backend::DirectVulkan {
}
}
Bool UniformDescriptorBinder::Initialize(VkDevice device, VmaAllocator allocator,
Bool UniformDescriptorBinder::Initialize(VkDevice device, VkBufferManager* bufferManager,
VkDeviceSize minUniformBufferOffsetAlignment, Uint32 frameCount,
Uint32 maxBindings, Uint32 setsPerFrame, VkDeviceSize perFrameUploadBytes,
Uint32 maxBindings, Uint32 setsPerFrame,
VkTextureManager* textureManager, VkSamplerManager* samplerManager) {
Shutdown();
MOBILEGL_ASSERT(device != VK_NULL_HANDLE, "UniformDescriptorBinder::Initialize requires valid VkDevice");
MOBILEGL_ASSERT(allocator != nullptr, "UniformDescriptorBinder::Initialize requires valid VMA allocator");
MOBILEGL_ASSERT(bufferManager != nullptr, "UniformDescriptorBinder::Initialize requires valid buffer manager");
MOBILEGL_ASSERT(frameCount > 0, "UniformDescriptorBinder::Initialize requires frameCount > 0");
MOBILEGL_ASSERT(maxBindings > 0, "UniformDescriptorBinder::Initialize requires maxBindings > 0");
MOBILEGL_ASSERT(setsPerFrame > 0, "UniformDescriptorBinder::Initialize requires setsPerFrame > 0");
@@ -191,9 +191,8 @@ namespace MobileGL::MG_Backend::DirectVulkan {
"UniformDescriptorBinder::Initialize requires valid sampler manager");
m_device = device;
m_allocator = allocator;
m_bufferManager = bufferManager;
m_minDynamicOffsetAlignment = std::max<VkDeviceSize>(1, minUniformBufferOffsetAlignment);
m_perFrameUploadBytes = perFrameUploadBytes;
m_frameCount = frameCount;
m_maxBindings = maxBindings;
m_setsPerFrame = setsPerFrame;
@@ -204,7 +203,6 @@ namespace MobileGL::MG_Backend::DirectVulkan {
m_frames.resize(m_frameCount);
for (Uint32 frameIndex = 0; frameIndex < m_frameCount; ++frameIndex) {
auto& frame = m_frames[frameIndex];
frame.writeCursor = 0;
frame.activeDescriptorPoolIndex = 0;
frame.allocatedSetsThisFrame = 0;
frame.peakAllocatedSetsThisFrame = 0;
@@ -219,15 +217,6 @@ namespace MobileGL::MG_Backend::DirectVulkan {
}
frame.descriptorPools.push_back({initialPool, m_setsPerFrame, 0});
MGLOG_D("UniformDescriptorBinder: frame %u descriptor pool created (maxSets=%u)", frameIndex, m_setsPerFrame);
const Bool created = frame.uploadBuffer.Create(
m_allocator, m_perFrameUploadBytes, VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT, VMA_MEMORY_USAGE_AUTO,
VMA_ALLOCATION_CREATE_HOST_ACCESS_SEQUENTIAL_WRITE_BIT);
if (!created) {
MGLOG_E("UniformDescriptorBinder::Initialize failed: cannot create frame upload buffer %u", frameIndex);
Shutdown();
return false;
}
}
return true;
@@ -235,7 +224,6 @@ namespace MobileGL::MG_Backend::DirectVulkan {
void UniformDescriptorBinder::Shutdown() {
for (auto& frame : m_frames) {
frame.uploadBuffer.Destroy();
if (m_device != VK_NULL_HANDLE) {
for (auto& bucket : frame.descriptorPools) {
if (bucket.handle != VK_NULL_HANDLE) {
@@ -248,15 +236,13 @@ namespace MobileGL::MG_Backend::DirectVulkan {
frame.activeDescriptorPoolIndex = 0;
frame.allocatedSetsThisFrame = 0;
frame.peakAllocatedSetsThisFrame = 0;
frame.writeCursor = 0;
}
m_frames.clear();
DestroyProgramLayouts();
m_allocator = nullptr;
m_bufferManager = nullptr;
m_device = VK_NULL_HANDLE;
m_minDynamicOffsetAlignment = 1;
m_perFrameUploadBytes = 0;
m_frameCount = 0;
m_maxBindings = 0;
m_setsPerFrame = 0;
@@ -274,7 +260,6 @@ namespace MobileGL::MG_Backend::DirectVulkan {
"UniformDescriptorBinder: new descriptor set peak observed=%u (base setsPerFrame=%u, frame=%u, pools=%zu)",
m_peakDescriptorSetsObserved, m_setsPerFrame, frameIndex, frame.descriptorPools.size());
}
frame.writeCursor = 0;
frame.activeDescriptorPoolIndex = 0;
frame.allocatedSetsThisFrame = 0;
frame.peakAllocatedSetsThisFrame = 0;
@@ -678,16 +663,6 @@ namespace MobileGL::MG_Backend::DirectVulkan {
return layout ? layout->pipelineLayout : VK_NULL_HANDLE;
}
Bool UniformDescriptorBinder::AllocateUploadRegion(FrameResources& frame, VkDeviceSize size, VkDeviceSize& outOffset) {
const VkDeviceSize alignedOffset = AlignUp(frame.writeCursor, m_minDynamicOffsetAlignment);
if (alignedOffset + size > m_perFrameUploadBytes) {
return false;
}
outOffset = alignedOffset;
frame.writeCursor = alignedOffset + size;
return true;
}
Bool UniformDescriptorBinder::GatherBindingPayloads(const MG_State::GLState::ProgramObject& program,
Vector<const void*>& outData,
Vector<VkDeviceSize>& outSizes) const {
@@ -870,6 +845,7 @@ namespace MobileGL::MG_Backend::DirectVulkan {
static const Uint8 kFallbackData[16] = {};
MOBILEGL_ASSERT(m_textureManager != nullptr, "BindProgramUniformBuffers: texture manager is null");
MOBILEGL_ASSERT(m_samplerManager != nullptr, "BindProgramUniformBuffers: sampler manager is null");
MOBILEGL_ASSERT(m_bufferManager != nullptr, "BindProgramUniformBuffers: buffer manager is null");
Vector<VkWriteDescriptorSet> writes;
writes.reserve(m_maxBindings);
@@ -911,19 +887,16 @@ namespace MobileGL::MG_Backend::DirectVulkan {
}
}
VkDeviceSize payloadOffset = 0;
if (!AllocateUploadRegion(frame, payloadSize, payloadOffset)) {
MGLOG_E("UniformDescriptorBinder::BindProgramUniformBuffers failed: frame upload buffer exhausted");
return false;
}
if (!frame.uploadBuffer.Upload(payload, payloadSize, payloadOffset)) {
BufferSlice slice{};
if (!m_bufferManager->UploadTransient(TransientBufferKind::Uniform, frameIndex, payload, payloadSize,
m_minDynamicOffsetAlignment, slice)) {
MGLOG_E("UniformDescriptorBinder::BindProgramUniformBuffers failed: UBO upload failed on binding %u",
binding);
return false;
}
VkDescriptorBufferInfo bufferInfo{};
bufferInfo.buffer = frame.uploadBuffer.GetHandle();
bufferInfo.buffer = slice.buffer;
bufferInfo.offset = 0;
bufferInfo.range = payloadSize;
bufferInfos.push_back(bufferInfo);
@@ -931,7 +904,7 @@ namespace MobileGL::MG_Backend::DirectVulkan {
write.descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER_DYNAMIC;
write.pBufferInfo = &bufferInfos.back();
writes.push_back(write);
dynamicOffsets.push_back(static_cast<Uint32>(payloadOffset));
dynamicOffsets.push_back(static_cast<Uint32>(slice.offset));
} else {
VkDescriptorImageInfo imageInfo{};
Bool hasImage = false;
@@ -8,12 +8,11 @@
#pragma once
#include "VkBufferObject.h"
#include "VkBufferManager.h"
#include "VkSamplerManager.h"
#include "VkTextureManager.h"
#include "../VkIncludes.h"
#include <Includes.h>
#include <vk_mem_alloc.h>
namespace MobileGL::MG_State::GLState {
class ITextureObject;
@@ -36,9 +35,9 @@ namespace MobileGL::MG_Backend::DirectVulkan {
const MG_State::GLState::SamplerObject* sampler = nullptr;
};
Bool Initialize(VkDevice device, VmaAllocator allocator, VkDeviceSize minUniformBufferOffsetAlignment,
Uint32 frameCount, Uint32 maxBindings = 16, Uint32 setsPerFrame = 64,
VkDeviceSize perFrameUploadBytes = 4 * 1024 * 1024,
Bool Initialize(VkDevice device, VkBufferManager* bufferManager,
VkDeviceSize minUniformBufferOffsetAlignment, Uint32 frameCount,
Uint32 maxBindings = 16, Uint32 setsPerFrame = 64,
VkTextureManager* textureManager = nullptr, VkSamplerManager* samplerManager = nullptr);
void Shutdown();
@@ -60,12 +59,10 @@ namespace MobileGL::MG_Backend::DirectVulkan {
};
struct FrameResources {
VkBufferObject uploadBuffer;
Vector<DescriptorPoolBucket> descriptorPools;
Uint32 activeDescriptorPoolIndex = 0;
Uint32 allocatedSetsThisFrame = 0;
Uint32 peakAllocatedSetsThisFrame = 0;
VkDeviceSize writeCursor = 0;
};
struct ProgramLayout {
@@ -94,7 +91,6 @@ namespace MobileGL::MG_Backend::DirectVulkan {
VkDescriptorImageInfo& outImageInfo) const;
Bool ReflectBindingKinds(const MG_State::GLState::ProgramObject& program, Vector<BindingKind>& outKinds) const;
ProgramLayout* GetOrCreateProgramLayout(const MG_State::GLState::ProgramObject& program);
Bool AllocateUploadRegion(FrameResources& frame, VkDeviceSize size, VkDeviceSize& outOffset);
Bool GatherBindingPayloads(const MG_State::GLState::ProgramObject& program, Vector<const void*>& outData,
Vector<VkDeviceSize>& outSizes) const;
Bool CreateDescriptorPool(Uint32 maxSets, VkDescriptorPool& outPool) const;
@@ -102,12 +98,11 @@ namespace MobileGL::MG_Backend::DirectVulkan {
void DestroyProgramLayouts();
VkDevice m_device = VK_NULL_HANDLE;
VmaAllocator m_allocator = nullptr;
VkBufferManager* m_bufferManager = nullptr;
Vector<FrameResources> m_frames;
UnorderedMap<Uint64, ProgramLayout> m_programLayouts;
VkDeviceSize m_minDynamicOffsetAlignment = 1;
VkDeviceSize m_perFrameUploadBytes = 0;
Uint32 m_frameCount = 0;
Uint32 m_maxBindings = 0;
Uint32 m_setsPerFrame = 0;
@@ -22,6 +22,7 @@ namespace MobileGL::MG_Backend::DirectVulkan {
void VkBufferManager::Shutdown() {
m_vertexUploadArena.Shutdown();
m_indexUploadArena.Shutdown();
m_uniformUploadArena.Shutdown();
m_initInfo = {};
}
@@ -31,6 +32,7 @@ namespace MobileGL::MG_Backend::DirectVulkan {
m_vertexUploadArena.Shutdown();
m_indexUploadArena.Shutdown();
m_uniformUploadArena.Shutdown();
m_initInfo.frameCount = frameCount;
return InitializeTransientArenas();
}
@@ -38,6 +40,7 @@ namespace MobileGL::MG_Backend::DirectVulkan {
void VkBufferManager::BeginFrame(Uint32 frameIndex) {
m_vertexUploadArena.BeginFrame(frameIndex);
m_indexUploadArena.BeginFrame(frameIndex);
m_uniformUploadArena.BeginFrame(frameIndex);
}
Bool VkBufferManager::UploadTransient(TransientBufferKind kind, Uint32 frameIndex, const void* data,
@@ -47,6 +50,8 @@ namespace MobileGL::MG_Backend::DirectVulkan {
return m_vertexUploadArena.Upload(frameIndex, data, size, alignment, outSlice);
case TransientBufferKind::Index:
return m_indexUploadArena.Upload(frameIndex, data, size, alignment, outSlice);
case TransientBufferKind::Uniform:
return m_uniformUploadArena.Upload(frameIndex, data, size, alignment, outSlice);
default:
return false;
}
@@ -80,6 +85,21 @@ namespace MobileGL::MG_Backend::DirectVulkan {
return false;
}
ok = m_uniformUploadArena.Initialize({
.allocator = m_initInfo.allocator,
.frameCount = m_initInfo.frameCount,
.usage = VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT,
.memoryUsage = m_initInfo.transientMemoryUsage,
.allocationFlags = m_initInfo.transientAllocationFlags,
.minBufferSize = m_initInfo.minUniformUploadBytes,
.persistentlyMapped = m_initInfo.transientPersistentMapping,
});
if (!ok) {
m_vertexUploadArena.Shutdown();
m_indexUploadArena.Shutdown();
return false;
}
return true;
}
} // namespace MobileGL::MG_Backend::DirectVulkan
@@ -17,6 +17,7 @@ namespace MobileGL::MG_Backend::DirectVulkan {
enum class TransientBufferKind : Uint8 {
Vertex,
Index,
Uniform,
};
struct VkBufferManagerInitInfo {
@@ -24,6 +25,7 @@ namespace MobileGL::MG_Backend::DirectVulkan {
Uint32 frameCount = 0;
VkDeviceSize minVertexUploadBytes = 4 * 1024 * 1024;
VkDeviceSize minIndexUploadBytes = 1 * 1024 * 1024;
VkDeviceSize minUniformUploadBytes = 4 * 1024 * 1024;
VmaMemoryUsage transientMemoryUsage = VMA_MEMORY_USAGE_AUTO;
VmaAllocationCreateFlags transientAllocationFlags = VMA_ALLOCATION_CREATE_HOST_ACCESS_SEQUENTIAL_WRITE_BIT;
Bool transientPersistentMapping = false;
@@ -47,5 +49,6 @@ namespace MobileGL::MG_Backend::DirectVulkan {
VkBufferManagerInitInfo m_initInfo{};
BufferArena m_vertexUploadArena;
BufferArena m_indexUploadArena;
BufferArena m_uniformUploadArena;
};
} // namespace MobileGL::MG_Backend::DirectVulkan
@@ -293,6 +293,7 @@ namespace MobileGL::MG_Backend::DirectVulkan {
.frameCount = m_frameContext.GetFrameCount(),
.minVertexUploadBytes = 4 * 1024 * 1024,
.minIndexUploadBytes = 1 * 1024 * 1024,
.minUniformUploadBytes = 4 * 1024 * 1024,
.transientMemoryUsage = VMA_MEMORY_USAGE_AUTO,
.transientAllocationFlags = VMA_ALLOCATION_CREATE_HOST_ACCESS_SEQUENTIAL_WRITE_BIT,
.transientPersistentMapping = false,
@@ -329,9 +330,9 @@ namespace MobileGL::MG_Backend::DirectVulkan {
m_uniformDescriptorBinder = MakeUnique<UniformDescriptorBinder>();
MOBILEGL_ASSERT(m_uniformDescriptorBinder != nullptr, "UniformDescriptorBinder creation failed.");
succeeded = m_uniformDescriptorBinder->Initialize(m_device, m_allocator,
succeeded = m_uniformDescriptorBinder->Initialize(m_device, &m_bufferManager,
m_physicalDevice.properties.limits.minUniformBufferOffsetAlignment,
m_config.MaxFramesInFlight, 16, 64, 4 * 1024 * 1024,
m_config.MaxFramesInFlight, 16, 64,
m_textureManager.get(), m_samplerManager.get());
MOBILEGL_ASSERT(succeeded, "UniformDescriptorBinder initialization failed.");
m_vertexInputStateFactory = MakeUnique<VertexInputStateFactory>(m_config);