From cf229c4a5bb444600d48daa2e2b0433f3c648fa7 Mon Sep 17 00:00:00 2001 From: Swung0x48 Date: Tue, 17 Feb 2026 11:45:17 +0800 Subject: [PATCH] [Feat] (MG_Backend/DirectVulkan): implement implement multiple vbo in vao binding --- .../Renderer/VertexInputStateFactory.cpp | 48 ++--- .../Renderer/VertexInputStateFactory.h | 1 + .../DirectVulkan/Renderer/VulkanRenderer.cpp | 178 ++++++++++-------- .../DirectVulkan/Renderer/VulkanRenderer.h | 9 +- .../MG_Test/Backend/DirectVulkan/TestExec.cpp | 46 +++-- 5 files changed, 160 insertions(+), 122 deletions(-) diff --git a/MobileGL/MG_Backend/DirectVulkan/Renderer/VertexInputStateFactory.cpp b/MobileGL/MG_Backend/DirectVulkan/Renderer/VertexInputStateFactory.cpp index c98d29df..be1f5030 100644 --- a/MobileGL/MG_Backend/DirectVulkan/Renderer/VertexInputStateFactory.cpp +++ b/MobileGL/MG_Backend/DirectVulkan/Renderer/VertexInputStateFactory.cpp @@ -8,6 +8,7 @@ #include "VertexInputStateFactory.h" #include "MG_Util/Converters/MGToStr/DataTypeConverter.h" +#include namespace MobileGL::MG_Backend::DirectVulkan { VertexInputStateFactory::HashType VertexInputStateFactory::ComputeHash( @@ -30,8 +31,8 @@ namespace MobileGL::MG_Backend::DirectVulkan { XXHASH_VERIFY(XXH64_update(m_hashState, &attr.IsInteger, sizeof(attr.IsInteger))); XXHASH_VERIFY(XXH64_update(m_hashState, &attr.Divisor, sizeof(attr.Divisor))); - const Uint bufferIndex = attr.Buffer ? attr.Buffer->GetExternalIndex() : 0; - XXHASH_VERIFY(XXH64_update(m_hashState, &bufferIndex, sizeof(bufferIndex))); + const SizeT bufferKey = reinterpret_cast(attr.Buffer.get()); + XXHASH_VERIFY(XXH64_update(m_hashState, &bufferKey, sizeof(bufferKey))); } return XXH64_digest(m_hashState); @@ -46,10 +47,10 @@ namespace MobileGL::MG_Backend::DirectVulkan { } VertexInputStateBuilder builder; - const void* sourceBuffer = nullptr; - Uint32 sourceStride = 0; - VkVertexInputRate sourceInputRate = VK_VERTEX_INPUT_RATE_VERTEX; - Bool hasBinding = false; + UnorderedMap bindingByBufferKey; + UnorderedMap strideByBufferKey; + UnorderedMap inputRateByBufferKey; + Vector bindingBufferKeys; for (Uint32 location = 0; location < MG_State::GLState::VertexArrayObject::MAX_VERTEX_ATTRIBS; ++location) { const auto& attr = vao.GetAttribute(location); @@ -77,30 +78,30 @@ namespace MobileGL::MG_Backend::DirectVulkan { const VkVertexInputRate inputRate = (attr.Divisor == 0) ? VK_VERTEX_INPUT_RATE_VERTEX : VK_VERTEX_INPUT_RATE_INSTANCE; - if (!hasBinding) { - sourceBuffer = attr.Buffer.get(); - sourceStride = stride; - sourceInputRate = inputRate; - builder.AddBinding(0, sourceStride, sourceInputRate); - hasBinding = true; + const SizeT bufferKey = reinterpret_cast(attr.Buffer.get()); + Uint32 binding = 0; + auto itBinding = bindingByBufferKey.find(bufferKey); + if (itBinding == bindingByBufferKey.end()) { + binding = static_cast(bindingByBufferKey.size()); + bindingByBufferKey.emplace(bufferKey, binding); + strideByBufferKey.emplace(bufferKey, stride); + inputRateByBufferKey.emplace(bufferKey, inputRate); + bindingBufferKeys.push_back(bufferKey); + builder.AddBinding(binding, stride, inputRate); } else { - if (attr.Buffer.get() != sourceBuffer) { - MGLOG_W("Skipping vertex attribute at location %u: only single-buffer vertex input is supported for now", - location); + binding = itBinding->second; + if (strideByBufferKey[bufferKey] != stride) { + MGLOG_W("Skipping vertex attribute at location %u: stride mismatch (%u vs %u) on same buffer", + location, stride, strideByBufferKey[bufferKey]); continue; } - if (stride != sourceStride) { - MGLOG_W("Skipping vertex attribute at location %u: stride mismatch (%u vs %u) for single-binding path", - location, stride, sourceStride); - continue; - } - if (inputRate != sourceInputRate) { - MGLOG_W("Skipping vertex attribute at location %u: input-rate mismatch for single-binding path", location); + if (inputRateByBufferKey[bufferKey] != inputRate) { + MGLOG_W("Skipping vertex attribute at location %u: input-rate mismatch on same buffer", location); continue; } } - builder.AddAttribute(location, 0, vkFormat, static_cast(attr.Offset)); + builder.AddAttribute(location, binding, vkFormat, static_cast(attr.Offset)); } const auto& state = builder.Build(); @@ -109,6 +110,7 @@ namespace MobileGL::MG_Backend::DirectVulkan { entry.hash = hash; entry.bindings = builder.GetBindings(); entry.attributes = builder.GetAttributes(); + entry.bindingBufferKeys = std::move(bindingBufferKeys); entry.state = state; entry.state.pVertexBindingDescriptions = entry.bindings.empty() ? nullptr : entry.bindings.data(); entry.state.pVertexAttributeDescriptions = entry.attributes.empty() ? nullptr : entry.attributes.data(); diff --git a/MobileGL/MG_Backend/DirectVulkan/Renderer/VertexInputStateFactory.h b/MobileGL/MG_Backend/DirectVulkan/Renderer/VertexInputStateFactory.h index d6e37b46..e55cdee4 100644 --- a/MobileGL/MG_Backend/DirectVulkan/Renderer/VertexInputStateFactory.h +++ b/MobileGL/MG_Backend/DirectVulkan/Renderer/VertexInputStateFactory.h @@ -23,6 +23,7 @@ namespace MobileGL::MG_Backend::DirectVulkan { HashType hash = 0; Vector bindings; Vector attributes; + Vector bindingBufferKeys; VkPipelineVertexInputStateCreateInfo state{ VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO }; diff --git a/MobileGL/MG_Backend/DirectVulkan/Renderer/VulkanRenderer.cpp b/MobileGL/MG_Backend/DirectVulkan/Renderer/VulkanRenderer.cpp index e0a29432..2940c4f6 100644 --- a/MobileGL/MG_Backend/DirectVulkan/Renderer/VulkanRenderer.cpp +++ b/MobileGL/MG_Backend/DirectVulkan/Renderer/VulkanRenderer.cpp @@ -129,7 +129,12 @@ namespace MobileGL::MG_Backend::DirectVulkan { m_pipelineFactory.reset(); m_programFactory.reset(); m_vertexInputStateFactory.reset(); - m_vertexBuffer.Destroy(); + for (auto& vertexBuffer : m_vertexBuffers) { + if (vertexBuffer) { + vertexBuffer->Destroy(); + } + } + m_vertexBuffers.clear(); m_indexBuffer.Destroy(); m_frameContext.Destroy(m_device, m_commandPool); @@ -355,6 +360,83 @@ namespace MobileGL::MG_Backend::DirectVulkan { m_frameContext.EndCommandRecording(); } + Bool VulkanRenderer::UploadAndBindVertexStreams( + const VertexInputStateFactory::BackendVertexInputState& vertexInputState, + const MG_State::GLState::VertexArrayObject& vertexArray, + VkCommandBuffer commandBuffer) { + if (vertexInputState.bindings.empty()) { + return true; + } + + const auto bindingCount = vertexInputState.bindings.size(); + if (vertexInputState.bindingBufferKeys.size() != bindingCount) { + MGLOG_E("UploadAndBindVertexStreams failed: binding metadata mismatch"); + return false; + } + + if (m_vertexBuffers.size() < bindingCount) { + m_vertexBuffers.resize(bindingCount); + } + + Vector vkBuffers(bindingCount, VK_NULL_HANDLE); + Vector vkOffsets(bindingCount, 0); + + for (SizeT binding = 0; binding < bindingCount; ++binding) { + const SizeT bufferKey = vertexInputState.bindingBufferKeys[binding]; + const MG_State::GLState::BufferObject* sourceBuffer = nullptr; + for (Uint32 location = 0; location < MG_State::GLState::VertexArrayObject::MAX_VERTEX_ATTRIBS; ++location) { + const auto& attr = vertexArray.GetAttribute(location); + if (!attr.Enabled || !attr.Buffer) { + continue; + } + if (reinterpret_cast(attr.Buffer.get()) == bufferKey) { + sourceBuffer = attr.Buffer.get(); + break; + } + } + + if (!sourceBuffer) { + MGLOG_W("UploadAndBindVertexStreams skipped: no source buffer for binding %zu", binding); + return false; + } + + const auto sourceData = sourceBuffer->GetDataReadOnly(); + if (!sourceData || sourceData->empty()) { + MGLOG_W("UploadAndBindVertexStreams skipped: source buffer has no data for binding %zu", binding); + return false; + } + + if (!m_vertexBuffers[binding]) { + m_vertexBuffers[binding] = MakeUnique(); + } + + auto& backendBuffer = *m_vertexBuffers[binding]; + const SizeT sourceSize = sourceBuffer->GetSize(); + if (!backendBuffer.IsValid() || backendBuffer.GetSize() < sourceSize) { + backendBuffer.Destroy(); + const Bool created = backendBuffer.Create( + m_allocator, static_cast(sourceSize), + VK_BUFFER_USAGE_VERTEX_BUFFER_BIT, + VMA_MEMORY_USAGE_AUTO, + VMA_ALLOCATION_CREATE_HOST_ACCESS_SEQUENTIAL_WRITE_BIT); + if (!created) { + MGLOG_E("UploadAndBindVertexStreams skipped: failed to create backend buffer for binding %zu", binding); + return false; + } + } + + if (!backendBuffer.Upload(sourceData->data(), static_cast(sourceSize), 0)) { + MGLOG_E("UploadAndBindVertexStreams skipped: failed to upload binding %zu", binding); + return false; + } + + vkBuffers[binding] = backendBuffer.GetHandle(); + } + + vkCmdBindVertexBuffers(commandBuffer, 0, static_cast(bindingCount), vkBuffers.data(), vkOffsets.data()); + return true; + } + void VulkanRenderer::DrawArrays(const DrawArrayPayload& payload) { if (payload.mode != GL_TRIANGLES) { MGLOG_W("DrawArrays skipped: primitive mode %u is not supported yet", payload.mode); @@ -395,48 +477,16 @@ namespace MobileGL::MG_Backend::DirectVulkan { return; } - if (vertexInputState && !vertexInputState->bindings.empty()) { - if (!payload.hasPositionStream || payload.positionData == nullptr || payload.positionDataSizeBytes == 0) { - MGLOG_W("DrawArrays skipped: vertex input expects stream but payload has no position data"); - return; - } - - if (vertexInputState->bindings.size() != 1) { - MGLOG_W("DrawArrays skipped: only single-binding vertex input is supported for now (bindings=%zu)", - vertexInputState->bindings.size()); - return; - } - if (vertexInputState->bindings[0].binding != 0) { - MGLOG_W("DrawArrays skipped: only binding 0 is supported for now (binding=%u)", - vertexInputState->bindings[0].binding); - return; - } - - if (!m_vertexBuffer.IsValid() || m_vertexBuffer.GetSize() < payload.positionDataSizeBytes) { - m_vertexBuffer.Destroy(); - const Bool created = m_vertexBuffer.Create( - m_allocator, static_cast(payload.positionDataSizeBytes), - VK_BUFFER_USAGE_VERTEX_BUFFER_BIT, - VMA_MEMORY_USAGE_AUTO, - VMA_ALLOCATION_CREATE_HOST_ACCESS_SEQUENTIAL_WRITE_BIT); - if (!created) { - MGLOG_E("DrawArrays skipped: failed to create vertex buffer"); - return; - } - } - - if (!m_vertexBuffer.Upload(payload.positionData, static_cast(payload.positionDataSizeBytes), 0)) { - MGLOG_E("DrawArrays skipped: failed to upload vertex data"); - return; - } - } - vkCmdBindPipeline(commandBuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, pipelineToBind); if (vertexInputState && !vertexInputState->bindings.empty()) { - VkBuffer vertexBufferHandle = m_vertexBuffer.GetHandle(); - VkDeviceSize vertexBufferOffset = 0; - vkCmdBindVertexBuffers(commandBuffer, 0, 1, &vertexBufferHandle, &vertexBufferOffset); + if (!payload.vertexArray) { + MGLOG_W("DrawArrays skipped: vertex input requires VAO"); + return; + } + if (!UploadAndBindVertexStreams(*vertexInputState, *payload.vertexArray, commandBuffer)) { + return; + } } VkViewport viewport{}; @@ -509,44 +559,6 @@ namespace MobileGL::MG_Backend::DirectVulkan { return; } - if (vertexInputState && !vertexInputState->bindings.empty()) { - if (!payload.drawArray.hasPositionStream || payload.drawArray.positionData == nullptr || - payload.drawArray.positionDataSizeBytes == 0) { - MGLOG_W("DrawElements skipped: vertex input expects stream but payload has no position data"); - return; - } - - if (vertexInputState->bindings.size() != 1) { - MGLOG_W("DrawElements skipped: only single-binding vertex input is supported for now (bindings=%zu)", - vertexInputState->bindings.size()); - return; - } - if (vertexInputState->bindings[0].binding != 0) { - MGLOG_W("DrawElements skipped: only binding 0 is supported for now (binding=%u)", - vertexInputState->bindings[0].binding); - return; - } - - if (!m_vertexBuffer.IsValid() || m_vertexBuffer.GetSize() < payload.drawArray.positionDataSizeBytes) { - m_vertexBuffer.Destroy(); - const Bool created = m_vertexBuffer.Create( - m_allocator, static_cast(payload.drawArray.positionDataSizeBytes), - VK_BUFFER_USAGE_VERTEX_BUFFER_BIT, - VMA_MEMORY_USAGE_AUTO, - VMA_ALLOCATION_CREATE_HOST_ACCESS_SEQUENTIAL_WRITE_BIT); - if (!created) { - MGLOG_E("DrawElements skipped: failed to create vertex buffer"); - return; - } - } - - if (!m_vertexBuffer.Upload(payload.drawArray.positionData, - static_cast(payload.drawArray.positionDataSizeBytes), 0)) { - MGLOG_E("DrawElements skipped: failed to upload vertex data"); - return; - } - } - if (!m_indexBuffer.IsValid() || m_indexBuffer.GetSize() < payload.indexDataSizeBytes) { m_indexBuffer.Destroy(); const Bool created = m_indexBuffer.Create( @@ -571,9 +583,13 @@ namespace MobileGL::MG_Backend::DirectVulkan { vkCmdBindPipeline(commandBuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, pipelineToBind); if (vertexInputState && !vertexInputState->bindings.empty()) { - VkBuffer vertexBufferHandle = m_vertexBuffer.GetHandle(); - VkDeviceSize vertexBufferOffset = 0; - vkCmdBindVertexBuffers(commandBuffer, 0, 1, &vertexBufferHandle, &vertexBufferOffset); + if (!payload.drawArray.vertexArray) { + MGLOG_W("DrawElements skipped: vertex input requires VAO"); + return; + } + if (!UploadAndBindVertexStreams(*vertexInputState, *payload.drawArray.vertexArray, commandBuffer)) { + return; + } } VkViewport viewport{}; diff --git a/MobileGL/MG_Backend/DirectVulkan/Renderer/VulkanRenderer.h b/MobileGL/MG_Backend/DirectVulkan/Renderer/VulkanRenderer.h index 84a9b4e8..d398d815 100644 --- a/MobileGL/MG_Backend/DirectVulkan/Renderer/VulkanRenderer.h +++ b/MobileGL/MG_Backend/DirectVulkan/Renderer/VulkanRenderer.h @@ -12,6 +12,7 @@ #include "PipelineFactory.h" #include "ProgramFactory.h" #include "SwapchainObject.h" +#include "VertexInputStateFactory.h" #include "VkBufferObject.h" #include "MG_Util/Math/VectorTypes.h" #include @@ -25,8 +26,6 @@ namespace MobileGL::MG_State::GLState { } namespace MobileGL::MG_Backend::DirectVulkan { - class VertexInputStateFactory; - struct DrawArrayPayload { GLenum mode = GL_TRIANGLES; GLint first = 0; @@ -119,7 +118,7 @@ namespace MobileGL::MG_Backend::DirectVulkan { Vector m_depthStencilImageLayouts; VkPipelineLayout m_pipelineLayout = VK_NULL_HANDLE; - VkBufferObject m_vertexBuffer; + Vector> m_vertexBuffers; VkBufferObject m_indexBuffer; Uint m_imageIndexAcquired = 0; @@ -159,6 +158,10 @@ namespace MobileGL::MG_Backend::DirectVulkan { void RecordColorClear(VkCommandBuffer commandBuffer, const VkClearColorValue& clearColor); void RecordDepthStencilClear(VkCommandBuffer commandBuffer, GLbitfield mask, Float depth, Uint32 stencil); void EndFrameRecordingIfNeeded(); + Bool UploadAndBindVertexStreams( + const VertexInputStateFactory::BackendVertexInputState& vertexInputState, + const MG_State::GLState::VertexArrayObject& vertexArray, + VkCommandBuffer commandBuffer); void ShutdownSwapchain(); diff --git a/MobileGL/MG_Test/Backend/DirectVulkan/TestExec.cpp b/MobileGL/MG_Test/Backend/DirectVulkan/TestExec.cpp index e288f32f..f3bf624d 100644 --- a/MobileGL/MG_Test/Backend/DirectVulkan/TestExec.cpp +++ b/MobileGL/MG_Test/Backend/DirectVulkan/TestExec.cpp @@ -134,25 +134,40 @@ int main() { glGenVertexArrays(1, &vao); glBindVertexArray(vao); - static constexpr GLfloat kQuadVertices[] = { + static constexpr GLfloat kQuadPositions[] = { // triangle 1 - -0.6f, -0.6f, 1.0f, 0.0f, 0.0f, - 0.6f, -0.6f, 0.0f, 1.0f, 0.0f, - 0.6f, 0.6f, 0.0f, 0.0f, 1.0f, + -0.6f, -0.6f, + 0.6f, -0.6f, + 0.6f, 0.6f, // triangle 2 - 0.6f, 0.6f, 1.0f, 0.0f, 0.0f, - -0.6f, 0.6f, 0.0f, 1.0f, 0.0f, - -0.6f, -0.6f, 0.0f, 0.0f, 1.0f + 0.6f, 0.6f, + -0.6f, 0.6f, + -0.6f, -0.6f }; - GLuint vbo = 0; - glGenBuffers(1, &vbo); - glBindBuffer(GL_ARRAY_BUFFER, vbo); - glBufferData(GL_ARRAY_BUFFER, sizeof(kQuadVertices), kQuadVertices, GL_STATIC_DRAW); - glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, static_cast(5 * sizeof(GLfloat)), nullptr); + static constexpr GLfloat kQuadColors[] = { + // triangle 1: RGB + 1.0f, 0.0f, 0.0f, + 0.0f, 1.0f, 0.0f, + 0.0f, 0.0f, 1.0f, + // triangle 2: RGB + 1.0f, 0.0f, 0.0f, + 0.0f, 1.0f, 0.0f, + 0.0f, 0.0f, 1.0f + }; + + GLuint positionVbo = 0; + glGenBuffers(1, &positionVbo); + glBindBuffer(GL_ARRAY_BUFFER, positionVbo); + glBufferData(GL_ARRAY_BUFFER, sizeof(kQuadPositions), kQuadPositions, GL_STATIC_DRAW); + glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, static_cast(2 * sizeof(GLfloat)), nullptr); glEnableVertexAttribArray(0); - glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, static_cast(5 * sizeof(GLfloat)), - reinterpret_cast(2 * sizeof(GLfloat))); + + GLuint colorVbo = 0; + glGenBuffers(1, &colorVbo); + glBindBuffer(GL_ARRAY_BUFFER, colorVbo); + glBufferData(GL_ARRAY_BUFFER, sizeof(kQuadColors), kQuadColors, GL_STATIC_DRAW); + glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, static_cast(3 * sizeof(GLfloat)), nullptr); glEnableVertexAttribArray(1); static constexpr GLushort kQuadIndices[] = {0, 1, 2, 3, 4, 5}; @@ -219,7 +234,8 @@ void main() { } glDeleteProgram(program); - glDeleteBuffers(1, &vbo); + glDeleteBuffers(1, &positionVbo); + glDeleteBuffers(1, &colorVbo); glDeleteBuffers(1, &ebo); glDeleteVertexArrays(1, &vao);