diff --git a/CMakeLists.txt b/CMakeLists.txt index 5ba2cbd6..9f86eabd 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/PipelineFactory.cpp MobileGL/MG_Backend/DirectVulkan/Renderer/ProgramFactory.cpp + MobileGL/MG_Backend/DirectVulkan/Renderer/UniformDescriptorBinder.cpp MobileGL/MG_Backend/DirectVulkan/Renderer/VertexInputStateBuilder.cpp MobileGL/MG_Backend/DirectVulkan/Renderer/VertexInputStateFactory.cpp MobileGL/MG_Backend/DirectVulkan/Renderer/VkBufferObject.cpp diff --git a/MobileGL/MG_Backend/DirectVulkan/Renderer/UniformDescriptorBinder.cpp b/MobileGL/MG_Backend/DirectVulkan/Renderer/UniformDescriptorBinder.cpp new file mode 100644 index 00000000..e9228e73 --- /dev/null +++ b/MobileGL/MG_Backend/DirectVulkan/Renderer/UniformDescriptorBinder.cpp @@ -0,0 +1,298 @@ +// MobileGL - MobileGL/MG_Backend/DirectVulkan/Renderer/UniformDescriptorBinder.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 "UniformDescriptorBinder.h" + +#include "MG_State/GLState/Core.h" +#include "MG_State/GLState/ProgramState/ProgramObject.h" +#include "MG_Util/ShaderTranspiler/Types.h" + +namespace MobileGL::MG_Backend::DirectVulkan { + VkDeviceSize UniformDescriptorBinder::AlignUp(VkDeviceSize value, VkDeviceSize alignment) { + if (alignment == 0) { + return value; + } + return (value + alignment - 1) / alignment * alignment; + } + + Bool UniformDescriptorBinder::Initialize(VkDevice device, VmaAllocator allocator, + VkDeviceSize minUniformBufferOffsetAlignment, Uint32 frameCount, + Uint32 maxBindings, Uint32 setsPerFrame, VkDeviceSize perFrameUploadBytes) { + Shutdown(); + + MOBILEGL_ASSERT(device != VK_NULL_HANDLE, "UniformDescriptorBinder::Initialize requires valid VkDevice"); + MOBILEGL_ASSERT(allocator != nullptr, "UniformDescriptorBinder::Initialize requires valid VMA allocator"); + MOBILEGL_ASSERT(frameCount > 0, "UniformDescriptorBinder::Initialize requires frameCount > 0"); + MOBILEGL_ASSERT(maxBindings > 0, "UniformDescriptorBinder::Initialize requires maxBindings > 0"); + MOBILEGL_ASSERT(setsPerFrame > 0, "UniformDescriptorBinder::Initialize requires setsPerFrame > 0"); + + m_device = device; + m_allocator = allocator; + m_minDynamicOffsetAlignment = std::max(1, minUniformBufferOffsetAlignment); + m_perFrameUploadBytes = perFrameUploadBytes; + m_frameCount = frameCount; + m_maxBindings = maxBindings; + m_setsPerFrame = setsPerFrame; + + Vector bindings; + bindings.reserve(m_maxBindings); + for (Uint32 bindingIndex = 0; bindingIndex < m_maxBindings; ++bindingIndex) { + VkDescriptorSetLayoutBinding binding{}; + binding.binding = bindingIndex; + binding.descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER_DYNAMIC; + binding.descriptorCount = 1; + binding.stageFlags = VK_SHADER_STAGE_ALL_GRAPHICS; + binding.pImmutableSamplers = nullptr; + bindings.push_back(binding); + } + + VkDescriptorSetLayoutCreateInfo setLayoutInfo{}; + setLayoutInfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO; + setLayoutInfo.bindingCount = static_cast(bindings.size()); + setLayoutInfo.pBindings = bindings.data(); + VK_VERIFY(vkCreateDescriptorSetLayout(m_device, &setLayoutInfo, nullptr, &m_descriptorSetLayout), + "UniformDescriptorBinder::Initialize, vkCreateDescriptorSetLayout"); + + VkDescriptorPoolSize poolSize{}; + poolSize.type = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER_DYNAMIC; + poolSize.descriptorCount = m_frameCount * m_setsPerFrame * m_maxBindings; + + VkDescriptorPoolCreateInfo poolInfo{}; + poolInfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO; + poolInfo.maxSets = m_frameCount * m_setsPerFrame; + poolInfo.poolSizeCount = 1; + poolInfo.pPoolSizes = &poolSize; + VK_VERIFY(vkCreateDescriptorPool(m_device, &poolInfo, nullptr, &m_descriptorPool), + "UniformDescriptorBinder::Initialize, vkCreateDescriptorPool"); + + m_descriptorSets.resize(m_frameCount * m_setsPerFrame, VK_NULL_HANDLE); + Vector layouts(m_descriptorSets.size(), m_descriptorSetLayout); + VkDescriptorSetAllocateInfo allocInfo{}; + allocInfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO; + allocInfo.descriptorPool = m_descriptorPool; + allocInfo.descriptorSetCount = static_cast(layouts.size()); + allocInfo.pSetLayouts = layouts.data(); + VK_VERIFY(vkAllocateDescriptorSets(m_device, &allocInfo, m_descriptorSets.data()), + "UniformDescriptorBinder::Initialize, vkAllocateDescriptorSets"); + + m_frames.resize(m_frameCount); + for (Uint32 frameIndex = 0; frameIndex < m_frameCount; ++frameIndex) { + auto& frame = m_frames[frameIndex]; + frame.writeCursor = 0; + frame.descriptorCursor = 0; + const Bool created = frame.uploadBuffer.Create( + m_allocator, m_perFrameUploadBytes, VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT, VMA_MEMORY_USAGE_AUTO, + VMA_ALLOCATION_CREATE_HOST_ACCESS_SEQUENTIAL_WRITE_BIT); + if (!created) { + MGLOG_E("UniformDescriptorBinder::Initialize failed: cannot create frame upload buffer %u", frameIndex); + Shutdown(); + return false; + } + } + + return true; + } + + void UniformDescriptorBinder::Shutdown() { + for (auto& frame : m_frames) { + frame.uploadBuffer.Destroy(); + frame.writeCursor = 0; + frame.descriptorCursor = 0; + } + m_frames.clear(); + m_descriptorSets.clear(); + + if (m_device != VK_NULL_HANDLE && m_descriptorPool != VK_NULL_HANDLE) { + vkDestroyDescriptorPool(m_device, m_descriptorPool, nullptr); + } + m_descriptorPool = VK_NULL_HANDLE; + + if (m_device != VK_NULL_HANDLE && m_descriptorSetLayout != VK_NULL_HANDLE) { + vkDestroyDescriptorSetLayout(m_device, m_descriptorSetLayout, nullptr); + } + m_descriptorSetLayout = VK_NULL_HANDLE; + + m_allocator = nullptr; + m_device = VK_NULL_HANDLE; + m_minDynamicOffsetAlignment = 1; + m_perFrameUploadBytes = 0; + m_frameCount = 0; + m_maxBindings = 0; + m_setsPerFrame = 0; + } + + void UniformDescriptorBinder::BeginFrame(Uint32 frameIndex) { + MOBILEGL_ASSERT(frameIndex < m_frames.size(), "UniformDescriptorBinder::BeginFrame invalid frame index"); + auto& frame = m_frames[frameIndex]; + frame.writeCursor = 0; + frame.descriptorCursor = 0; + } + + Bool UniformDescriptorBinder::AllocateUploadRegion(FrameResources& frame, VkDeviceSize size, VkDeviceSize& outOffset) { + const VkDeviceSize alignedOffset = AlignUp(frame.writeCursor, m_minDynamicOffsetAlignment); + if (alignedOffset + size > m_perFrameUploadBytes) { + return false; + } + outOffset = alignedOffset; + frame.writeCursor = alignedOffset + size; + return true; + } + + Bool UniformDescriptorBinder::GatherBindingPayloads(const MG_State::GLState::ProgramObject& program, + Vector& outData, + Vector& outSizes) const { + outData.assign(m_maxBindings, nullptr); + outSizes.assign(m_maxBindings, 0); + + if (MG_State::pGLContext == nullptr) { + return false; + } + + const Uint32 activeUniformBlockCount = static_cast(program.GetActiveUniformBlocksCount()); + const Uint32 uniformBindingPointCount = + static_cast(MG_State::pGLContext->GetBufferBindingPointCount(BufferTarget::Uniform)); + + for (Uint32 blockIndex = 0; blockIndex < activeUniformBlockCount; ++blockIndex) { + const Uint32 binding = program.GetUniformBlockBinding(blockIndex); + if (binding >= m_maxBindings) { + continue; + } + + VkDeviceSize blockSize = static_cast(program.GetUBOSizeAt(blockIndex)); + if (blockSize == 0) { + continue; + } + + const auto& blockName = program.GetUniformBlockName(blockIndex); + if (blockName == MG_Util::ShaderTranspiler::GLOBAL_UBO_NAME) { + const void* globalUboData = program.GetUBOData(); + const VkDeviceSize globalUboSize = static_cast(program.GetUBOSize()); + if (globalUboData == nullptr || globalUboSize == 0) { + continue; + } + outData[binding] = globalUboData; + outSizes[binding] = std::min(blockSize, globalUboSize); + continue; + } + + if (binding >= uniformBindingPointCount) { + continue; + } + auto& bindingPoint = MG_State::pGLContext->GetBufferBindingPoint(BufferTarget::Uniform, binding); + const auto bufferObject = bindingPoint.GetBoundObject(); + if (!bufferObject) { + continue; + } + + const auto bufferData = bufferObject->GetDataReadOnly(); + if (!bufferData || bufferData->empty()) { + continue; + } + + const auto range = bindingPoint.GetRange(); + const VkDeviceSize bufferSize = static_cast(bufferObject->GetSize()); + VkDeviceSize rangeStart = static_cast(range.start); + VkDeviceSize rangeEnd = static_cast(range.end); + + if (rangeStart >= bufferSize) { + continue; + } + if (rangeEnd <= rangeStart || rangeEnd > bufferSize) { + rangeEnd = bufferSize; + } + + VkDeviceSize available = rangeEnd - rangeStart; + if (available == 0) { + continue; + } + + outData[binding] = bufferData->data() + static_cast(rangeStart); + outSizes[binding] = std::min(blockSize, available); + } + + return true; + } + + Bool UniformDescriptorBinder::BindProgramUniformBuffers(VkCommandBuffer commandBuffer, VkPipelineLayout pipelineLayout, + const MG_State::GLState::ProgramObject& program, + Uint32 frameIndex) { + if (m_descriptorSetLayout == VK_NULL_HANDLE || m_descriptorPool == VK_NULL_HANDLE) { + MGLOG_E("UniformDescriptorBinder::BindProgramUniformBuffers failed: binder is not initialized"); + return false; + } + if (frameIndex >= m_frames.size()) { + MGLOG_E("UniformDescriptorBinder::BindProgramUniformBuffers failed: invalid frame index %u", frameIndex); + return false; + } + + auto& frame = m_frames[frameIndex]; + if (frame.descriptorCursor >= m_setsPerFrame) { + MGLOG_E("UniformDescriptorBinder::BindProgramUniformBuffers failed: per-frame descriptor set budget exceeded"); + return false; + } + + const Uint32 descriptorSetIndex = frameIndex * m_setsPerFrame + frame.descriptorCursor; + const VkDescriptorSet descriptorSet = m_descriptorSets[descriptorSetIndex]; + ++frame.descriptorCursor; + + Vector bindingData; + Vector bindingSizes; + if (!GatherBindingPayloads(program, bindingData, bindingSizes)) { + MGLOG_E("UniformDescriptorBinder::BindProgramUniformBuffers failed: cannot gather UBO payloads"); + return false; + } + + static const Uint8 kFallbackData[16] = {}; + + Vector bufferInfos(m_maxBindings); + Vector writes; + writes.reserve(m_maxBindings); + Vector dynamicOffsets(m_maxBindings, 0); + + for (Uint32 binding = 0; binding < m_maxBindings; ++binding) { + const void* payload = bindingData[binding]; + VkDeviceSize payloadSize = bindingSizes[binding]; + if (payload == nullptr || payloadSize == 0) { + payload = kFallbackData; + payloadSize = sizeof(kFallbackData); + } + + VkDeviceSize payloadOffset = 0; + if (!AllocateUploadRegion(frame, payloadSize, payloadOffset)) { + MGLOG_E("UniformDescriptorBinder::BindProgramUniformBuffers failed: frame upload buffer exhausted"); + return false; + } + if (!frame.uploadBuffer.Upload(payload, payloadSize, payloadOffset)) { + MGLOG_E("UniformDescriptorBinder::BindProgramUniformBuffers failed: upload failed on binding %u", binding); + return false; + } + + bufferInfos[binding].buffer = frame.uploadBuffer.GetHandle(); + bufferInfos[binding].offset = 0; + bufferInfos[binding].range = payloadSize; + dynamicOffsets[binding] = static_cast(payloadOffset); + + VkWriteDescriptorSet write{}; + write.sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET; + write.dstSet = descriptorSet; + write.dstBinding = binding; + write.dstArrayElement = 0; + write.descriptorCount = 1; + write.descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER_DYNAMIC; + write.pBufferInfo = &bufferInfos[binding]; + writes.push_back(write); + } + + vkUpdateDescriptorSets(m_device, static_cast(writes.size()), writes.data(), 0, nullptr); + + vkCmdBindDescriptorSets(commandBuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, pipelineLayout, 0, 1, &descriptorSet, + static_cast(dynamicOffsets.size()), dynamicOffsets.data()); + return true; + } +} // namespace MobileGL::MG_Backend::DirectVulkan + diff --git a/MobileGL/MG_Backend/DirectVulkan/Renderer/UniformDescriptorBinder.h b/MobileGL/MG_Backend/DirectVulkan/Renderer/UniformDescriptorBinder.h new file mode 100644 index 00000000..47ad0b9f --- /dev/null +++ b/MobileGL/MG_Backend/DirectVulkan/Renderer/UniformDescriptorBinder.h @@ -0,0 +1,61 @@ +// MobileGL - MobileGL/MG_Backend/DirectVulkan/Renderer/UniformDescriptorBinder.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 "VkBufferObject.h" +#include "../VkIncludes.h" +#include +#include + +namespace MobileGL::MG_State::GLState { + class ProgramObject; +} + +namespace MobileGL::MG_Backend::DirectVulkan { + class UniformDescriptorBinder { + public: + Bool Initialize(VkDevice device, VmaAllocator allocator, VkDeviceSize minUniformBufferOffsetAlignment, + Uint32 frameCount, Uint32 maxBindings = 16, Uint32 setsPerFrame = 64, + VkDeviceSize perFrameUploadBytes = 4 * 1024 * 1024); + void Shutdown(); + + void BeginFrame(Uint32 frameIndex); + Bool BindProgramUniformBuffers(VkCommandBuffer commandBuffer, VkPipelineLayout pipelineLayout, + const MG_State::GLState::ProgramObject& program, Uint32 frameIndex); + + VkDescriptorSetLayout GetDescriptorSetLayout() const { return m_descriptorSetLayout; } + + private: + struct FrameResources { + VkBufferObject uploadBuffer; + VkDeviceSize writeCursor = 0; + Uint32 descriptorCursor = 0; + }; + + static VkDeviceSize AlignUp(VkDeviceSize value, VkDeviceSize alignment); + Bool AllocateUploadRegion(FrameResources& frame, VkDeviceSize size, VkDeviceSize& outOffset); + Bool GatherBindingPayloads(const MG_State::GLState::ProgramObject& program, Vector& outData, + Vector& outSizes) const; + + VkDevice m_device = VK_NULL_HANDLE; + VmaAllocator m_allocator = nullptr; + VkDescriptorSetLayout m_descriptorSetLayout = VK_NULL_HANDLE; + VkDescriptorPool m_descriptorPool = VK_NULL_HANDLE; + + Vector m_descriptorSets; + Vector m_frames; + + VkDeviceSize m_minDynamicOffsetAlignment = 1; + VkDeviceSize m_perFrameUploadBytes = 0; + Uint32 m_frameCount = 0; + Uint32 m_maxBindings = 0; + Uint32 m_setsPerFrame = 0; + }; +} // namespace MobileGL::MG_Backend::DirectVulkan + diff --git a/MobileGL/MG_Backend/DirectVulkan/Renderer/VkBufferObject.cpp b/MobileGL/MG_Backend/DirectVulkan/Renderer/VkBufferObject.cpp index 5ca4b8d0..dbcf7b19 100644 --- a/MobileGL/MG_Backend/DirectVulkan/Renderer/VkBufferObject.cpp +++ b/MobileGL/MG_Backend/DirectVulkan/Renderer/VkBufferObject.cpp @@ -9,6 +9,37 @@ #include "VkBufferObject.h" namespace MobileGL::MG_Backend::DirectVulkan { + VkBufferObject::VkBufferObject(VkBufferObject&& other) noexcept { + m_allocator = other.m_allocator; + m_buffer = other.m_buffer; + m_allocation = other.m_allocation; + m_size = other.m_size; + + other.m_allocator = nullptr; + other.m_buffer = VK_NULL_HANDLE; + other.m_allocation = nullptr; + other.m_size = 0; + } + + VkBufferObject& VkBufferObject::operator=(VkBufferObject&& other) noexcept { + if (this == &other) { + return *this; + } + + Destroy(); + + m_allocator = other.m_allocator; + m_buffer = other.m_buffer; + m_allocation = other.m_allocation; + m_size = other.m_size; + + other.m_allocator = nullptr; + other.m_buffer = VK_NULL_HANDLE; + other.m_allocation = nullptr; + other.m_size = 0; + return *this; + } + VkBufferObject::~VkBufferObject() { Destroy(); } @@ -77,4 +108,3 @@ namespace MobileGL::MG_Backend::DirectVulkan { return true; } } // namespace MobileGL::MG_Backend::DirectVulkan - diff --git a/MobileGL/MG_Backend/DirectVulkan/Renderer/VkBufferObject.h b/MobileGL/MG_Backend/DirectVulkan/Renderer/VkBufferObject.h index 58be1a7d..48a06152 100644 --- a/MobileGL/MG_Backend/DirectVulkan/Renderer/VkBufferObject.h +++ b/MobileGL/MG_Backend/DirectVulkan/Renderer/VkBufferObject.h @@ -20,6 +20,8 @@ namespace MobileGL::MG_Backend::DirectVulkan { VkBufferObject(const VkBufferObject&) = delete; VkBufferObject& operator=(const VkBufferObject&) = delete; + VkBufferObject(VkBufferObject&& other) noexcept; + VkBufferObject& operator=(VkBufferObject&& other) noexcept; Bool Create(VmaAllocator allocator, VkDeviceSize size, VkBufferUsageFlags usage, VmaMemoryUsage memoryUsage, VmaAllocationCreateFlags allocationFlags = 0); @@ -38,4 +40,3 @@ namespace MobileGL::MG_Backend::DirectVulkan { VkDeviceSize m_size = 0; }; } // namespace MobileGL::MG_Backend::DirectVulkan - diff --git a/MobileGL/MG_Backend/DirectVulkan/Renderer/VulkanRenderer.cpp b/MobileGL/MG_Backend/DirectVulkan/Renderer/VulkanRenderer.cpp index 3599e370..a61a9223 100644 --- a/MobileGL/MG_Backend/DirectVulkan/Renderer/VulkanRenderer.cpp +++ b/MobileGL/MG_Backend/DirectVulkan/Renderer/VulkanRenderer.cpp @@ -87,6 +87,14 @@ namespace MobileGL::MG_Backend::DirectVulkan { MGLOG_D("PrepareDemoPipeline called"); VkPipelineLayoutCreateInfo plci{VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO}; + VkDescriptorSetLayout descriptorSetLayouts[1] = {VK_NULL_HANDLE}; + if (m_uniformDescriptorBinder) { + descriptorSetLayouts[0] = m_uniformDescriptorBinder->GetDescriptorSetLayout(); + } + if (descriptorSetLayouts[0] != VK_NULL_HANDLE) { + plci.setLayoutCount = 1; + plci.pSetLayouts = descriptorSetLayouts; + } VK_VERIFY(vkCreatePipelineLayout(m_device, &plci, nullptr, &m_pipelineLayout), "vkCreatePipelineLayout"); MGLOG_I("PrepareDemoPipeline completed"); @@ -111,6 +119,13 @@ namespace MobileGL::MG_Backend::DirectVulkan { m_pipelineFactory = MakeUnique(m_device, m_config); m_programFactory = MakeUnique(m_device, m_config); + m_uniformDescriptorBinder = MakeUnique(); + if (!m_uniformDescriptorBinder->Initialize(m_device, m_allocator, + m_physicalDevice.properties.limits.minUniformBufferOffsetAlignment, + m_config.MaxFramesInFlight)) { + MGLOG_E("UniformDescriptorBinder initialization failed. UBO sync on Vulkan backend is disabled."); + m_uniformDescriptorBinder.reset(); + } m_vertexInputStateFactory = MakeUnique(m_config); PrepareDemoPipeline(); @@ -143,6 +158,10 @@ namespace MobileGL::MG_Backend::DirectVulkan { vkDestroyPipelineLayout(m_device, m_pipelineLayout, nullptr); m_pipelineLayout = VK_NULL_HANDLE; } + if (m_uniformDescriptorBinder) { + m_uniformDescriptorBinder->Shutdown(); + m_uniformDescriptorBinder.reset(); + } ShutdownSwapchain(); @@ -320,6 +339,9 @@ namespace MobileGL::MG_Backend::DirectVulkan { } VkCommandBuffer& commandBuffer = m_frameContext.BeginCommandRecording(); + if (m_uniformDescriptorBinder) { + m_uniformDescriptorBinder->BeginFrame(m_frameContext.GetCurrentFrameIndex()); + } TransitionSwapchainImageToColorAttachment(commandBuffer, m_imageIndexAcquired); TransitionDepthStencilImageToAttachment(commandBuffer, m_imageIndexAcquired); @@ -478,6 +500,12 @@ namespace MobileGL::MG_Backend::DirectVulkan { } vkCmdBindPipeline(commandBuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, pipelineToBind); + if (m_uniformDescriptorBinder && + !m_uniformDescriptorBinder->BindProgramUniformBuffers(commandBuffer, m_pipelineLayout, *payload.program, + m_frameContext.GetCurrentFrameIndex())) { + MGLOG_W("DrawArrays skipped: failed to bind uniform descriptors"); + return; + } if (vertexInputState && !vertexInputState->bindings.empty()) { if (!payload.vertexArray) { @@ -581,6 +609,13 @@ namespace MobileGL::MG_Backend::DirectVulkan { const auto swapchainExtent = m_swapchainObject.GetExtent(); vkCmdBindPipeline(commandBuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, pipelineToBind); + if (m_uniformDescriptorBinder && + !m_uniformDescriptorBinder->BindProgramUniformBuffers(commandBuffer, m_pipelineLayout, + *payload.drawArray.program, + m_frameContext.GetCurrentFrameIndex())) { + MGLOG_W("DrawElements skipped: failed to bind uniform descriptors"); + return; + } if (vertexInputState && !vertexInputState->bindings.empty()) { if (!payload.drawArray.vertexArray) { diff --git a/MobileGL/MG_Backend/DirectVulkan/Renderer/VulkanRenderer.h b/MobileGL/MG_Backend/DirectVulkan/Renderer/VulkanRenderer.h index d398d815..26f6f998 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 "UniformDescriptorBinder.h" #include "VertexInputStateFactory.h" #include "VkBufferObject.h" #include "MG_Util/Math/VectorTypes.h" @@ -131,6 +132,7 @@ namespace MobileGL::MG_Backend::DirectVulkan { UniquePtr m_pipelineFactory; UniquePtr m_programFactory; + UniquePtr m_uniformDescriptorBinder; UniquePtr m_vertexInputStateFactory; void CreateInstance(); diff --git a/MobileGL/MG_State/GLState/ProgramState/ProgramObject.h b/MobileGL/MG_State/GLState/ProgramState/ProgramObject.h index 0a40dacf..c1dad4eb 100644 --- a/MobileGL/MG_State/GLState/ProgramState/ProgramObject.h +++ b/MobileGL/MG_State/GLState/ProgramState/ProgramObject.h @@ -69,6 +69,7 @@ namespace MobileGL { GLenum GetAttribType(Uint index) const { return m_attribTypes[index]; } const String& GetAttribName(Uint index) const { return m_attribs[index]; } void* MapUBO() { return m_uboScratch.data(); } + const void* GetUBOData() const { return m_uboScratch.data(); } Uint GetUBOSize() const { return static_cast(m_uboScratch.size()); } void SetUniformSamplerOrImageUnitIndex(Uint location, Int unit) { diff --git a/MobileGL/MG_Test/Backend/DirectVulkan/TestExec.cpp b/MobileGL/MG_Test/Backend/DirectVulkan/TestExec.cpp index f3bf624d..52c88706 100644 --- a/MobileGL/MG_Test/Backend/DirectVulkan/TestExec.cpp +++ b/MobileGL/MG_Test/Backend/DirectVulkan/TestExec.cpp @@ -10,6 +10,8 @@ #include #include #include +#include +#include #include #define GLFW_INCLUDE_NONE @@ -180,15 +182,22 @@ static constexpr const char* kVertexShaderSource = R"(#version 330 core layout(location = 0) in vec2 aPos; layout(location = 1) in vec3 aColor; out vec3 vColor; +uniform float iTime; void main() { - gl_Position = vec4(aPos, 0.0, 1.0); + float wobble = 0.05 * sin(iTime); + gl_Position = vec4(aPos.x, aPos.y + wobble, 0.0, 1.0); vColor = aColor; })"; static constexpr const char* kFragmentShaderSource = R"(#version 330 core in vec3 vColor; layout(location = 0) out vec4 outColor; +layout(std140, binding = 1) uniform ColorBlock { + vec3 uColor; +}; +uniform float iTime; void main() { - outColor = vec4(vColor, 1.0); + float pulse = 0.5 + 0.5 * sin(iTime * 1.7); + outColor = vec4(vColor * uColor * pulse, 1.0); })"; const GLuint vs = glCreateShader(GL_VERTEX_SHADER); @@ -216,6 +225,29 @@ void main() { glDeleteShader(vs); glDeleteShader(fs); + const GLint iTimeLocation = glGetUniformLocation(program, "iTime"); + if (iTimeLocation < 0) { + std::cerr << "Failed to get uniform location for iTime" << std::endl; + return 1; + } + + const GLuint colorBlockIndex = glGetUniformBlockIndex(program, "ColorBlock"); + if (colorBlockIndex == GL_INVALID_INDEX) { + std::cerr << "Failed to get uniform block index for ColorBlock" << std::endl; + return 1; + } + glUniformBlockBinding(program, colorBlockIndex, 1); + + GLuint colorUbo = 0; + glGenBuffers(1, &colorUbo); + glBindBuffer(GL_UNIFORM_BUFFER, colorUbo); + alignas(16) GLfloat uboColorData[4] = {1.0f, 0.6f, 0.2f, 0.0f}; + glBufferData(GL_UNIFORM_BUFFER, sizeof(uboColorData), uboColorData, GL_DYNAMIC_DRAW); + glBindBufferBase(GL_UNIFORM_BUFFER, 1, colorUbo); + glBindBuffer(GL_UNIFORM_BUFFER, 0); + + const auto startTime = std::chrono::steady_clock::now(); + int i = 0; while(!glfwWindowShouldClose(window)) { glfwPollEvents(); @@ -227,6 +259,16 @@ void main() { glClear(GL_COLOR_BUFFER_BIT); if (i % 500 > 250) { glUseProgram(program); + const auto now = std::chrono::steady_clock::now(); + const float t = std::chrono::duration(now - startTime).count(); + glUniform1f(iTimeLocation, t); + + uboColorData[0] = 0.5f + 0.5f * std::sin(t * 0.7f); + uboColorData[1] = 0.5f + 0.5f * std::sin(t * 1.1f + 1.2f); + uboColorData[2] = 0.5f + 0.5f * std::sin(t * 1.5f + 2.4f); + glBindBuffer(GL_UNIFORM_BUFFER, colorUbo); + glBufferSubData(GL_UNIFORM_BUFFER, 0, sizeof(uboColorData), uboColorData); + glBindBuffer(GL_UNIFORM_BUFFER, 0); glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_SHORT, nullptr); } eglSwapBuffers(display, surface); @@ -234,6 +276,7 @@ void main() { } glDeleteProgram(program); + glDeleteBuffers(1, &colorUbo); glDeleteBuffers(1, &positionVbo); glDeleteBuffers(1, &colorVbo); glDeleteBuffers(1, &ebo);