[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 "MG_Util/Converters/MGToStr/DataTypeConverter.h"
#include <utility>
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<SizeT>(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<SizeT, Uint32> bindingByBufferKey;
UnorderedMap<SizeT, Uint32> strideByBufferKey;
UnorderedMap<SizeT, VkVertexInputRate> inputRateByBufferKey;
Vector<SizeT> 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<SizeT>(attr.Buffer.get());
Uint32 binding = 0;
auto itBinding = bindingByBufferKey.find(bufferKey);
if (itBinding == bindingByBufferKey.end()) {
binding = static_cast<Uint32>(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<Uint32>(attr.Offset));
builder.AddAttribute(location, binding, vkFormat, static_cast<Uint32>(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();
@@ -23,6 +23,7 @@ namespace MobileGL::MG_Backend::DirectVulkan {
HashType hash = 0;
Vector<VkVertexInputBindingDescription> bindings;
Vector<VkVertexInputAttributeDescription> attributes;
Vector<SizeT> bindingBufferKeys;
VkPipelineVertexInputStateCreateInfo state{
VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO
};
@@ -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<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) {
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<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);
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<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) {
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{};
@@ -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 <Includes.h>
@@ -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<VkImageLayout> m_depthStencilImageLayouts;
VkPipelineLayout m_pipelineLayout = VK_NULL_HANDLE;
VkBufferObject m_vertexBuffer;
Vector<UniquePtr<VkBufferObject>> 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();
@@ -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<GLsizei>(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<GLsizei>(2 * sizeof(GLfloat)), nullptr);
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);
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);