diff --git a/CMakeLists.txt b/CMakeLists.txt index 0a2a1a34..766c00b2 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -215,6 +215,7 @@ set(SOURCE_FILES MobileGL/MG_Backend/DirectVulkan/Renderer/FrameContext.cpp MobileGL/MG_Backend/DirectVulkan/Renderer/ProgramFactory.cpp MobileGL/MG_Backend/DirectVulkan/Renderer/VertexInputStateBuilder.cpp + MobileGL/MG_Backend/DirectVulkan/Renderer/VertexInputStateFactory.cpp MobileGL/MG_Backend/DirectVulkan/Renderer/VkBufferObject.cpp MobileGL/MG_State/GLState/Core.cpp diff --git a/MobileGL/MG_Backend/DirectVulkan/DirectVulkan.cpp b/MobileGL/MG_Backend/DirectVulkan/DirectVulkan.cpp index 62957b56..ebdd3bfc 100644 --- a/MobileGL/MG_Backend/DirectVulkan/DirectVulkan.cpp +++ b/MobileGL/MG_Backend/DirectVulkan/DirectVulkan.cpp @@ -112,6 +112,7 @@ namespace MobileGL::MG_Backend::DirectVulkan { payload.count = count; const auto vao = MG_State::pGLContext->GetBoundVertexArray(); + payload.vertexArray = vao ? vao.get() : nullptr; if (vao) { const auto& attr0 = vao->GetAttribute(0); if (attr0.Enabled && attr0.Buffer) { diff --git a/MobileGL/MG_Backend/DirectVulkan/Renderer/VertexInputStateBuilder.cpp b/MobileGL/MG_Backend/DirectVulkan/Renderer/VertexInputStateBuilder.cpp index 67087d42..08f14617 100644 --- a/MobileGL/MG_Backend/DirectVulkan/Renderer/VertexInputStateBuilder.cpp +++ b/MobileGL/MG_Backend/DirectVulkan/Renderer/VertexInputStateBuilder.cpp @@ -53,4 +53,12 @@ namespace MobileGL::MG_Backend::DirectVulkan { m_state.pVertexAttributeDescriptions = m_attributes.empty() ? nullptr : m_attributes.data(); return m_state; } + + const Vector& VertexInputStateBuilder::GetBindings() const { + return m_bindings; + } + + const Vector& VertexInputStateBuilder::GetAttributes() const { + return m_attributes; + } } // namespace MobileGL::MG_Backend::DirectVulkan diff --git a/MobileGL/MG_Backend/DirectVulkan/Renderer/VertexInputStateBuilder.h b/MobileGL/MG_Backend/DirectVulkan/Renderer/VertexInputStateBuilder.h index 43417a06..6a3874f4 100644 --- a/MobileGL/MG_Backend/DirectVulkan/Renderer/VertexInputStateBuilder.h +++ b/MobileGL/MG_Backend/DirectVulkan/Renderer/VertexInputStateBuilder.h @@ -20,6 +20,8 @@ namespace MobileGL::MG_Backend::DirectVulkan { VertexInputStateBuilder& AddAttribute(Uint32 location, Uint32 binding, VkFormat format, Uint32 offset); const VkPipelineVertexInputStateCreateInfo& Build(); + const Vector& GetBindings() const; + const Vector& GetAttributes() const; private: VkPipelineVertexInputStateCreateInfo m_state{}; diff --git a/MobileGL/MG_Backend/DirectVulkan/Renderer/VertexInputStateFactory.cpp b/MobileGL/MG_Backend/DirectVulkan/Renderer/VertexInputStateFactory.cpp new file mode 100644 index 00000000..402cdf91 --- /dev/null +++ b/MobileGL/MG_Backend/DirectVulkan/Renderer/VertexInputStateFactory.cpp @@ -0,0 +1,176 @@ +// MobileGL - MobileGL/MG_Backend/DirectVulkan/Renderer/VertexInputStateFactory.cpp +// Copyright (c) 2025-2026 MobileGL-Dev +// Licensed under the GNU Lesser General Public License v3.0: +// https://www.gnu.org/licenses/gpl-3.0.txt +// https://www.gnu.org/licenses/lgpl-3.0.txt +// SPDX-License-Identifier: LGPL-3.0-only +// End of Source File Header + +#include "VertexInputStateFactory.h" + +namespace MobileGL::MG_Backend::DirectVulkan { + VertexInputStateFactory::HashType VertexInputStateFactory::ComputeHash( + const MG_State::GLState::VertexArrayObject& vao) const { + XXHASH_VERIFY(XXH64_reset(m_hashState, m_config.CacheVersion)); + + for (Int i = 0; i < MG_State::GLState::VertexArrayObject::MAX_VERTEX_ATTRIBS; ++i) { + const auto& attr = vao.GetAttribute(i); + + XXHASH_VERIFY(XXH64_update(m_hashState, &attr.Enabled, sizeof(attr.Enabled))); + if (!attr.Enabled) { + continue; + } + + XXHASH_VERIFY(XXH64_update(m_hashState, &attr.Size, sizeof(attr.Size))); + XXHASH_VERIFY(XXH64_update(m_hashState, &attr.Type, sizeof(attr.Type))); + XXHASH_VERIFY(XXH64_update(m_hashState, &attr.Normalized, sizeof(attr.Normalized))); + XXHASH_VERIFY(XXH64_update(m_hashState, &attr.Stride, sizeof(attr.Stride))); + XXHASH_VERIFY(XXH64_update(m_hashState, &attr.Offset, sizeof(attr.Offset))); + 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))); + } + + return XXH64_digest(m_hashState); + } + + const VertexInputStateFactory::BackendVertexInputState& VertexInputStateFactory::GetOrCreateVertexInputState( + const MG_State::GLState::VertexArrayObject& vao) { + const HashType hash = ComputeHash(vao); + auto it = m_cache.find(hash); + if (it != m_cache.end()) { + return it->second; + } + + VertexInputStateBuilder builder; + for (Uint32 location = 0; location < MG_State::GLState::VertexArrayObject::MAX_VERTEX_ATTRIBS; ++location) { + const auto& attr = vao.GetAttribute(location); + if (!attr.Enabled || !attr.Buffer) { + continue; + } + + const auto vkFormat = ToVkVertexFormat(attr.Type, attr.Size, attr.Normalized, attr.IsInteger); + if (vkFormat != VK_FORMAT_UNDEFINED) { + MGLOG_W("Skipping unsupported vertex attribute layout (location=%u, type=%d, size=%d)", + location, static_cast(attr.Type), attr.Size); + continue; + } + + const SizeT componentSize = GetComponentSize(attr.Type); + if (componentSize == 0) { + MGLOG_W("Skipping vertex attribute with unknown component size (location=%u, type=%d)", + location, static_cast(attr.Type)); + continue; + } + + const Uint32 stride = attr.Stride > 0 + ? static_cast(attr.Stride) + : static_cast(componentSize * static_cast(attr.Size)); + const VkVertexInputRate inputRate = + (attr.Divisor == 0) ? VK_VERTEX_INPUT_RATE_VERTEX : VK_VERTEX_INPUT_RATE_INSTANCE; + + builder + .AddBinding(location, stride, inputRate) + .AddAttribute(location, location, vkFormat, 0); + } + + const auto& state = builder.Build(); + + auto& entry = m_cache[hash]; + entry.hash = hash; + entry.bindings = builder.GetBindings(); + entry.attributes = builder.GetAttributes(); + entry.state = state; + entry.state.pVertexBindingDescriptions = entry.bindings.empty() ? nullptr : entry.bindings.data(); + entry.state.pVertexAttributeDescriptions = entry.attributes.empty() ? nullptr : entry.attributes.data(); + return entry; + } + + VkFormat VertexInputStateFactory::ToVkVertexFormat(DataType type, Int size, Bool normalized, Bool isInteger) { + switch (type) { + case DataType::Float32: + switch (size) { + case 1: return VK_FORMAT_R32_SFLOAT; + case 2: return VK_FORMAT_R32G32_SFLOAT; + case 3: return VK_FORMAT_R32G32B32_SFLOAT; + case 4: return VK_FORMAT_R32G32B32A32_SFLOAT; + default: return VK_FORMAT_UNDEFINED; + } + case DataType::Int32: + if (!isInteger || normalized) return VK_FORMAT_UNDEFINED; + switch (size) { + case 1: return VK_FORMAT_R32_SINT; + case 2: return VK_FORMAT_R32G32_SINT; + case 3: return VK_FORMAT_R32G32B32_SINT; + case 4: return VK_FORMAT_R32G32B32A32_SINT; + default: return VK_FORMAT_UNDEFINED; + } + case DataType::Uint32: + if (!isInteger || normalized) return VK_FORMAT_UNDEFINED; + switch (size) { + case 1: return VK_FORMAT_R32_UINT; + case 2: return VK_FORMAT_R32G32_UINT; + case 3: return VK_FORMAT_R32G32B32_UINT; + case 4: return VK_FORMAT_R32G32B32A32_UINT; + default: return VK_FORMAT_UNDEFINED; + } + case DataType::Int16: + switch (size) { + case 1: return normalized ? VK_FORMAT_R16_SNORM : (isInteger ? VK_FORMAT_R16_SINT : VK_FORMAT_UNDEFINED); + case 2: return normalized ? VK_FORMAT_R16G16_SNORM : (isInteger ? VK_FORMAT_R16G16_SINT : VK_FORMAT_UNDEFINED); + case 3: return VK_FORMAT_UNDEFINED; + case 4: return normalized ? VK_FORMAT_R16G16B16A16_SNORM : (isInteger ? VK_FORMAT_R16G16B16A16_SINT : VK_FORMAT_UNDEFINED); + default: return VK_FORMAT_UNDEFINED; + } + case DataType::Uint16: + switch (size) { + case 1: return normalized ? VK_FORMAT_R16_UNORM : (isInteger ? VK_FORMAT_R16_UINT : VK_FORMAT_UNDEFINED); + case 2: return normalized ? VK_FORMAT_R16G16_UNORM : (isInteger ? VK_FORMAT_R16G16_UINT : VK_FORMAT_UNDEFINED); + case 3: return VK_FORMAT_UNDEFINED; + case 4: return normalized ? VK_FORMAT_R16G16B16A16_UNORM : (isInteger ? VK_FORMAT_R16G16B16A16_UINT : VK_FORMAT_UNDEFINED); + default: return VK_FORMAT_UNDEFINED; + } + case DataType::Int8: + switch (size) { + case 1: return normalized ? VK_FORMAT_R8_SNORM : (isInteger ? VK_FORMAT_R8_SINT : VK_FORMAT_UNDEFINED); + case 2: return normalized ? VK_FORMAT_R8G8_SNORM : (isInteger ? VK_FORMAT_R8G8_SINT : VK_FORMAT_UNDEFINED); + case 3: return VK_FORMAT_UNDEFINED; + case 4: return normalized ? VK_FORMAT_R8G8B8A8_SNORM : (isInteger ? VK_FORMAT_R8G8B8A8_SINT : VK_FORMAT_UNDEFINED); + default: return VK_FORMAT_UNDEFINED; + } + case DataType::Uint8: + switch (size) { + case 1: return normalized ? VK_FORMAT_R8_UNORM : (isInteger ? VK_FORMAT_R8_UINT : VK_FORMAT_UNDEFINED); + case 2: return normalized ? VK_FORMAT_R8G8_UNORM : (isInteger ? VK_FORMAT_R8G8_UINT : VK_FORMAT_UNDEFINED); + case 3: return VK_FORMAT_UNDEFINED; + case 4: return normalized ? VK_FORMAT_R8G8B8A8_UNORM : (isInteger ? VK_FORMAT_R8G8B8A8_UINT : VK_FORMAT_UNDEFINED); + default: return VK_FORMAT_UNDEFINED; + } + default: + return VK_FORMAT_UNDEFINED; + } + } + + SizeT VertexInputStateFactory::GetComponentSize(DataType type) { + switch (type) { + case DataType::Int8: + case DataType::Uint8: + return 1; + case DataType::Int16: + case DataType::Uint16: + case DataType::Float16: + return 2; + case DataType::Int32: + case DataType::Uint32: + case DataType::Float32: + case DataType::Fixed32: + return 4; + case DataType::Float64: + return 8; + default: + return 0; + } + } +} // namespace MobileGL::MG_Backend::DirectVulkan diff --git a/MobileGL/MG_Backend/DirectVulkan/Renderer/VertexInputStateFactory.h b/MobileGL/MG_Backend/DirectVulkan/Renderer/VertexInputStateFactory.h new file mode 100644 index 00000000..d6e37b46 --- /dev/null +++ b/MobileGL/MG_Backend/DirectVulkan/Renderer/VertexInputStateFactory.h @@ -0,0 +1,47 @@ +// MobileGL - MobileGL/MG_Backend/DirectVulkan/Renderer/VertexInputStateFactory.h +// Copyright (c) 2025-2026 MobileGL-Dev +// Licensed under the GNU Lesser General Public License v3.0: +// https://www.gnu.org/licenses/gpl-3.0.txt +// https://www.gnu.org/licenses/lgpl-3.0.txt +// SPDX-License-Identifier: LGPL-3.0-only +// End of Source File Header + +#pragma once + +#include "Config.h" +#include "VertexInputStateBuilder.h" +#include "MG_State/GLState/VertexArrayState/VertexArrayObject.h" +#include +#include "../VkIncludes.h" + +namespace MobileGL::MG_Backend::DirectVulkan { + class VertexInputStateFactory { + public: + using HashType = Uint64; + + struct BackendVertexInputState { + HashType hash = 0; + Vector bindings; + Vector attributes; + VkPipelineVertexInputStateCreateInfo state{ + VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO + }; + }; + + explicit VertexInputStateFactory(const VulkanRendererConfig& config): + m_config(config) {} + ~VertexInputStateFactory() = default; + VertexInputStateFactory(const VertexInputStateFactory&) = delete; + + HashType ComputeHash(const MG_State::GLState::VertexArrayObject& vao) const; + const BackendVertexInputState& GetOrCreateVertexInputState(const MG_State::GLState::VertexArrayObject& vao); + + private: + static VkFormat ToVkVertexFormat(DataType type, Int size, Bool normalized, Bool isInteger); + static SizeT GetComponentSize(DataType type); + + const VulkanRendererConfig& m_config; + UnorderedMap m_cache; + static inline XXH64_state_t* m_hashState = XXH64_createState(); + }; +} // namespace MobileGL::MG_Backend::DirectVulkan diff --git a/MobileGL/MG_Backend/DirectVulkan/Renderer/VulkanRenderer.cpp b/MobileGL/MG_Backend/DirectVulkan/Renderer/VulkanRenderer.cpp index 98f76928..1ad3b460 100644 --- a/MobileGL/MG_Backend/DirectVulkan/Renderer/VulkanRenderer.cpp +++ b/MobileGL/MG_Backend/DirectVulkan/Renderer/VulkanRenderer.cpp @@ -7,6 +7,7 @@ // End of Source File Header #include "VulkanRenderer.h" +#include "VertexInputStateFactory.h" #include "VertexInputStateBuilder.h" #include "MG_State/GLState/ProgramState/ProgramObject.h" @@ -201,6 +202,7 @@ namespace MobileGL::MG_Backend::DirectVulkan { RecreateSwapchain(); m_programFactory = MakeUnique(m_device, m_config); + m_vertexInputStateFactory = MakeUnique(m_config); PrepareDemoPipeline(); CreateFrameContexts(); @@ -216,6 +218,7 @@ namespace MobileGL::MG_Backend::DirectVulkan { VK_VERIFY(vkDeviceWaitIdle(m_device)); m_programFactory.reset(); + m_vertexInputStateFactory.reset(); m_indexBuffer.Destroy(); m_frameContext.Destroy(m_device, m_commandPool); @@ -450,6 +453,10 @@ namespace MobileGL::MG_Backend::DirectVulkan { return; } + if (payload.vertexArray && m_vertexInputStateFactory) { + (void)m_vertexInputStateFactory->GetOrCreateVertexInputState(*payload.vertexArray); + } + EnsureFrameRecordingStarted(); auto& frame = m_frameContext.GetCurrent(); if (!frame.isCommandRecording || !m_isMainRenderPassActive) { diff --git a/MobileGL/MG_Backend/DirectVulkan/Renderer/VulkanRenderer.h b/MobileGL/MG_Backend/DirectVulkan/Renderer/VulkanRenderer.h index 6988fd51..9710da5a 100644 --- a/MobileGL/MG_Backend/DirectVulkan/Renderer/VulkanRenderer.h +++ b/MobileGL/MG_Backend/DirectVulkan/Renderer/VulkanRenderer.h @@ -18,11 +18,18 @@ #include "../VkIncludes.h" +namespace MobileGL::MG_State::GLState { + class VertexArrayObject; +} + namespace MobileGL::MG_Backend::DirectVulkan { + class VertexInputStateFactory; + struct DrawArrayPayload { GLenum mode = GL_TRIANGLES; GLint first = 0; GLsizei count = 0; + const MG_State::GLState::VertexArrayObject* vertexArray = nullptr; Bool hasPositionStream = false; const void* positionData = nullptr; SizeT positionDataSizeBytes = 0; @@ -114,6 +121,7 @@ namespace MobileGL::MG_Backend::DirectVulkan { Bool m_isMainRenderPassActive = false; UniquePtr m_programFactory; + UniquePtr m_vertexInputStateFactory; void CreateInstance(); VkResult SetupDebugMessenger();