[Feat] (MG_Backend/DirectVulkan): implement implement multiple vbo in vao binding

This commit is contained in:
2026-02-17 11:45:17 +08:00
parent 058ec6d26e
commit cf229c4a5b
5 changed files with 160 additions and 122 deletions
@@ -8,6 +8,7 @@
#include "VertexInputStateFactory.h" #include "VertexInputStateFactory.h"
#include "MG_Util/Converters/MGToStr/DataTypeConverter.h" #include "MG_Util/Converters/MGToStr/DataTypeConverter.h"
#include <utility>
namespace MobileGL::MG_Backend::DirectVulkan { namespace MobileGL::MG_Backend::DirectVulkan {
VertexInputStateFactory::HashType VertexInputStateFactory::ComputeHash( 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.IsInteger, sizeof(attr.IsInteger)));
XXHASH_VERIFY(XXH64_update(m_hashState, &attr.Divisor, sizeof(attr.Divisor))); XXHASH_VERIFY(XXH64_update(m_hashState, &attr.Divisor, sizeof(attr.Divisor)));
const Uint bufferIndex = attr.Buffer ? attr.Buffer->GetExternalIndex() : 0; const SizeT bufferKey = reinterpret_cast<SizeT>(attr.Buffer.get());
XXHASH_VERIFY(XXH64_update(m_hashState, &bufferIndex, sizeof(bufferIndex))); XXHASH_VERIFY(XXH64_update(m_hashState, &bufferKey, sizeof(bufferKey)));
} }
return XXH64_digest(m_hashState); return XXH64_digest(m_hashState);
@@ -46,10 +47,10 @@ namespace MobileGL::MG_Backend::DirectVulkan {
} }
VertexInputStateBuilder builder; VertexInputStateBuilder builder;
const void* sourceBuffer = nullptr; UnorderedMap<SizeT, Uint32> bindingByBufferKey;
Uint32 sourceStride = 0; UnorderedMap<SizeT, Uint32> strideByBufferKey;
VkVertexInputRate sourceInputRate = VK_VERTEX_INPUT_RATE_VERTEX; UnorderedMap<SizeT, VkVertexInputRate> inputRateByBufferKey;
Bool hasBinding = false; Vector<SizeT> bindingBufferKeys;
for (Uint32 location = 0; location < MG_State::GLState::VertexArrayObject::MAX_VERTEX_ATTRIBS; ++location) { for (Uint32 location = 0; location < MG_State::GLState::VertexArrayObject::MAX_VERTEX_ATTRIBS; ++location) {
const auto& attr = vao.GetAttribute(location); const auto& attr = vao.GetAttribute(location);
@@ -77,30 +78,30 @@ namespace MobileGL::MG_Backend::DirectVulkan {
const VkVertexInputRate inputRate = const VkVertexInputRate inputRate =
(attr.Divisor == 0) ? VK_VERTEX_INPUT_RATE_VERTEX : VK_VERTEX_INPUT_RATE_INSTANCE; (attr.Divisor == 0) ? VK_VERTEX_INPUT_RATE_VERTEX : VK_VERTEX_INPUT_RATE_INSTANCE;
if (!hasBinding) { const SizeT bufferKey = reinterpret_cast<SizeT>(attr.Buffer.get());
sourceBuffer = attr.Buffer.get(); Uint32 binding = 0;
sourceStride = stride; auto itBinding = bindingByBufferKey.find(bufferKey);
sourceInputRate = inputRate; if (itBinding == bindingByBufferKey.end()) {
builder.AddBinding(0, sourceStride, sourceInputRate); binding = static_cast<Uint32>(bindingByBufferKey.size());
hasBinding = true; bindingByBufferKey.emplace(bufferKey, binding);
strideByBufferKey.emplace(bufferKey, stride);
inputRateByBufferKey.emplace(bufferKey, inputRate);
bindingBufferKeys.push_back(bufferKey);
builder.AddBinding(binding, stride, inputRate);
} else { } else {
if (attr.Buffer.get() != sourceBuffer) { binding = itBinding->second;
MGLOG_W("Skipping vertex attribute at location %u: only single-buffer vertex input is supported for now", if (strideByBufferKey[bufferKey] != stride) {
location); MGLOG_W("Skipping vertex attribute at location %u: stride mismatch (%u vs %u) on same buffer",
location, stride, strideByBufferKey[bufferKey]);
continue; continue;
} }
if (stride != sourceStride) { if (inputRateByBufferKey[bufferKey] != inputRate) {
MGLOG_W("Skipping vertex attribute at location %u: stride mismatch (%u vs %u) for single-binding path", MGLOG_W("Skipping vertex attribute at location %u: input-rate mismatch on same buffer", location);
location, stride, sourceStride);
continue;
}
if (inputRate != sourceInputRate) {
MGLOG_W("Skipping vertex attribute at location %u: input-rate mismatch for single-binding path", location);
continue; continue;
} }
} }
builder.AddAttribute(location, 0, vkFormat, static_cast<Uint32>(attr.Offset)); builder.AddAttribute(location, binding, vkFormat, static_cast<Uint32>(attr.Offset));
} }
const auto& state = builder.Build(); const auto& state = builder.Build();
@@ -109,6 +110,7 @@ namespace MobileGL::MG_Backend::DirectVulkan {
entry.hash = hash; entry.hash = hash;
entry.bindings = builder.GetBindings(); entry.bindings = builder.GetBindings();
entry.attributes = builder.GetAttributes(); entry.attributes = builder.GetAttributes();
entry.bindingBufferKeys = std::move(bindingBufferKeys);
entry.state = state; entry.state = state;
entry.state.pVertexBindingDescriptions = entry.bindings.empty() ? nullptr : entry.bindings.data(); entry.state.pVertexBindingDescriptions = entry.bindings.empty() ? nullptr : entry.bindings.data();
entry.state.pVertexAttributeDescriptions = entry.attributes.empty() ? nullptr : entry.attributes.data(); entry.state.pVertexAttributeDescriptions = entry.attributes.empty() ? nullptr : entry.attributes.data();
@@ -23,6 +23,7 @@ namespace MobileGL::MG_Backend::DirectVulkan {
HashType hash = 0; HashType hash = 0;
Vector<VkVertexInputBindingDescription> bindings; Vector<VkVertexInputBindingDescription> bindings;
Vector<VkVertexInputAttributeDescription> attributes; Vector<VkVertexInputAttributeDescription> attributes;
Vector<SizeT> bindingBufferKeys;
VkPipelineVertexInputStateCreateInfo state{ VkPipelineVertexInputStateCreateInfo state{
VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO
}; };
@@ -129,7 +129,12 @@ namespace MobileGL::MG_Backend::DirectVulkan {
m_pipelineFactory.reset(); m_pipelineFactory.reset();
m_programFactory.reset(); m_programFactory.reset();
m_vertexInputStateFactory.reset(); m_vertexInputStateFactory.reset();
m_vertexBuffer.Destroy(); for (auto& vertexBuffer : m_vertexBuffers) {
if (vertexBuffer) {
vertexBuffer->Destroy();
}
}
m_vertexBuffers.clear();
m_indexBuffer.Destroy(); m_indexBuffer.Destroy();
m_frameContext.Destroy(m_device, m_commandPool); m_frameContext.Destroy(m_device, m_commandPool);
@@ -355,6 +360,83 @@ namespace MobileGL::MG_Backend::DirectVulkan {
m_frameContext.EndCommandRecording(); 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<VkBuffer> vkBuffers(bindingCount, VK_NULL_HANDLE);
Vector<VkDeviceSize> 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<SizeT>(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<VkBufferObject>();
}
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<VkDeviceSize>(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<VkDeviceSize>(sourceSize), 0)) {
MGLOG_E("UploadAndBindVertexStreams skipped: failed to upload binding %zu", binding);
return false;
}
vkBuffers[binding] = backendBuffer.GetHandle();
}
vkCmdBindVertexBuffers(commandBuffer, 0, static_cast<Uint32>(bindingCount), vkBuffers.data(), vkOffsets.data());
return true;
}
void VulkanRenderer::DrawArrays(const DrawArrayPayload& payload) { void VulkanRenderer::DrawArrays(const DrawArrayPayload& payload) {
if (payload.mode != GL_TRIANGLES) { if (payload.mode != GL_TRIANGLES) {
MGLOG_W("DrawArrays skipped: primitive mode %u is not supported yet", payload.mode); MGLOG_W("DrawArrays skipped: primitive mode %u is not supported yet", payload.mode);
@@ -395,48 +477,16 @@ namespace MobileGL::MG_Backend::DirectVulkan {
return; 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<VkDeviceSize>(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<VkDeviceSize>(payload.positionDataSizeBytes), 0)) {
MGLOG_E("DrawArrays skipped: failed to upload vertex data");
return;
}
}
vkCmdBindPipeline(commandBuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, pipelineToBind); vkCmdBindPipeline(commandBuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, pipelineToBind);
if (vertexInputState && !vertexInputState->bindings.empty()) { if (vertexInputState && !vertexInputState->bindings.empty()) {
VkBuffer vertexBufferHandle = m_vertexBuffer.GetHandle(); if (!payload.vertexArray) {
VkDeviceSize vertexBufferOffset = 0; MGLOG_W("DrawArrays skipped: vertex input requires VAO");
vkCmdBindVertexBuffers(commandBuffer, 0, 1, &vertexBufferHandle, &vertexBufferOffset); return;
}
if (!UploadAndBindVertexStreams(*vertexInputState, *payload.vertexArray, commandBuffer)) {
return;
}
} }
VkViewport viewport{}; VkViewport viewport{};
@@ -509,44 +559,6 @@ namespace MobileGL::MG_Backend::DirectVulkan {
return; 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<VkDeviceSize>(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<VkDeviceSize>(payload.drawArray.positionDataSizeBytes), 0)) {
MGLOG_E("DrawElements skipped: failed to upload vertex data");
return;
}
}
if (!m_indexBuffer.IsValid() || m_indexBuffer.GetSize() < payload.indexDataSizeBytes) { if (!m_indexBuffer.IsValid() || m_indexBuffer.GetSize() < payload.indexDataSizeBytes) {
m_indexBuffer.Destroy(); m_indexBuffer.Destroy();
const Bool created = m_indexBuffer.Create( const Bool created = m_indexBuffer.Create(
@@ -571,9 +583,13 @@ namespace MobileGL::MG_Backend::DirectVulkan {
vkCmdBindPipeline(commandBuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, pipelineToBind); vkCmdBindPipeline(commandBuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, pipelineToBind);
if (vertexInputState && !vertexInputState->bindings.empty()) { if (vertexInputState && !vertexInputState->bindings.empty()) {
VkBuffer vertexBufferHandle = m_vertexBuffer.GetHandle(); if (!payload.drawArray.vertexArray) {
VkDeviceSize vertexBufferOffset = 0; MGLOG_W("DrawElements skipped: vertex input requires VAO");
vkCmdBindVertexBuffers(commandBuffer, 0, 1, &vertexBufferHandle, &vertexBufferOffset); return;
}
if (!UploadAndBindVertexStreams(*vertexInputState, *payload.drawArray.vertexArray, commandBuffer)) {
return;
}
} }
VkViewport viewport{}; VkViewport viewport{};
@@ -12,6 +12,7 @@
#include "PipelineFactory.h" #include "PipelineFactory.h"
#include "ProgramFactory.h" #include "ProgramFactory.h"
#include "SwapchainObject.h" #include "SwapchainObject.h"
#include "VertexInputStateFactory.h"
#include "VkBufferObject.h" #include "VkBufferObject.h"
#include "MG_Util/Math/VectorTypes.h" #include "MG_Util/Math/VectorTypes.h"
#include <Includes.h> #include <Includes.h>
@@ -25,8 +26,6 @@ namespace MobileGL::MG_State::GLState {
} }
namespace MobileGL::MG_Backend::DirectVulkan { namespace MobileGL::MG_Backend::DirectVulkan {
class VertexInputStateFactory;
struct DrawArrayPayload { struct DrawArrayPayload {
GLenum mode = GL_TRIANGLES; GLenum mode = GL_TRIANGLES;
GLint first = 0; GLint first = 0;
@@ -119,7 +118,7 @@ namespace MobileGL::MG_Backend::DirectVulkan {
Vector<VkImageLayout> m_depthStencilImageLayouts; Vector<VkImageLayout> m_depthStencilImageLayouts;
VkPipelineLayout m_pipelineLayout = VK_NULL_HANDLE; VkPipelineLayout m_pipelineLayout = VK_NULL_HANDLE;
VkBufferObject m_vertexBuffer; Vector<UniquePtr<VkBufferObject>> m_vertexBuffers;
VkBufferObject m_indexBuffer; VkBufferObject m_indexBuffer;
Uint m_imageIndexAcquired = 0; Uint m_imageIndexAcquired = 0;
@@ -159,6 +158,10 @@ namespace MobileGL::MG_Backend::DirectVulkan {
void RecordColorClear(VkCommandBuffer commandBuffer, const VkClearColorValue& clearColor); void RecordColorClear(VkCommandBuffer commandBuffer, const VkClearColorValue& clearColor);
void RecordDepthStencilClear(VkCommandBuffer commandBuffer, GLbitfield mask, Float depth, Uint32 stencil); void RecordDepthStencilClear(VkCommandBuffer commandBuffer, GLbitfield mask, Float depth, Uint32 stencil);
void EndFrameRecordingIfNeeded(); void EndFrameRecordingIfNeeded();
Bool UploadAndBindVertexStreams(
const VertexInputStateFactory::BackendVertexInputState& vertexInputState,
const MG_State::GLState::VertexArrayObject& vertexArray,
VkCommandBuffer commandBuffer);
void ShutdownSwapchain(); void ShutdownSwapchain();
@@ -134,25 +134,40 @@ int main() {
glGenVertexArrays(1, &vao); glGenVertexArrays(1, &vao);
glBindVertexArray(vao); glBindVertexArray(vao);
static constexpr GLfloat kQuadVertices[] = { static constexpr GLfloat kQuadPositions[] = {
// triangle 1 // triangle 1
-0.6f, -0.6f, 1.0f, 0.0f, 0.0f, -0.6f, -0.6f,
0.6f, -0.6f, 0.0f, 1.0f, 0.0f, 0.6f, -0.6f,
0.6f, 0.6f, 0.0f, 0.0f, 1.0f, 0.6f, 0.6f,
// triangle 2 // triangle 2
0.6f, 0.6f, 1.0f, 0.0f, 0.0f, 0.6f, 0.6f,
-0.6f, 0.6f, 0.0f, 1.0f, 0.0f, -0.6f, 0.6f,
-0.6f, -0.6f, 0.0f, 0.0f, 1.0f -0.6f, -0.6f
}; };
GLuint vbo = 0; static constexpr GLfloat kQuadColors[] = {
glGenBuffers(1, &vbo); // triangle 1: RGB
glBindBuffer(GL_ARRAY_BUFFER, vbo); 1.0f, 0.0f, 0.0f,
glBufferData(GL_ARRAY_BUFFER, sizeof(kQuadVertices), kQuadVertices, GL_STATIC_DRAW); 0.0f, 1.0f, 0.0f,
glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, static_cast<GLsizei>(5 * sizeof(GLfloat)), nullptr); 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<GLsizei>(2 * sizeof(GLfloat)), nullptr);
glEnableVertexAttribArray(0); glEnableVertexAttribArray(0);
glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, static_cast<GLsizei>(5 * sizeof(GLfloat)),
reinterpret_cast<const void*>(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<GLsizei>(3 * sizeof(GLfloat)), nullptr);
glEnableVertexAttribArray(1); glEnableVertexAttribArray(1);
static constexpr GLushort kQuadIndices[] = {0, 1, 2, 3, 4, 5}; static constexpr GLushort kQuadIndices[] = {0, 1, 2, 3, 4, 5};
@@ -219,7 +234,8 @@ void main() {
} }
glDeleteProgram(program); glDeleteProgram(program);
glDeleteBuffers(1, &vbo); glDeleteBuffers(1, &positionVbo);
glDeleteBuffers(1, &colorVbo);
glDeleteBuffers(1, &ebo); glDeleteBuffers(1, &ebo);
glDeleteVertexArrays(1, &vao); glDeleteVertexArrays(1, &vao);