mirror of
https://github.com/MobileGL-Dev/MobileGL
synced 2026-09-08 04:08:32 +09:00
[Feat] (MG_Backend/DirectVulkan): implement implement multiple vbo in vao binding
This commit is contained in:
@@ -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);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user