[Feat] (MG_Backend/DirectVulkan): implements uniform, ubo

This commit is contained in:
2026-02-17 17:16:08 +08:00
parent f7ea6cd1d9
commit fe6e7902d3
9 changed files with 476 additions and 4 deletions
+1
View File
@@ -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
@@ -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<VkDeviceSize>(1, minUniformBufferOffsetAlignment);
m_perFrameUploadBytes = perFrameUploadBytes;
m_frameCount = frameCount;
m_maxBindings = maxBindings;
m_setsPerFrame = setsPerFrame;
Vector<VkDescriptorSetLayoutBinding> 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<Uint32>(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<VkDescriptorSetLayout> 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<Uint32>(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<const void*>& outData,
Vector<VkDeviceSize>& outSizes) const {
outData.assign(m_maxBindings, nullptr);
outSizes.assign(m_maxBindings, 0);
if (MG_State::pGLContext == nullptr) {
return false;
}
const Uint32 activeUniformBlockCount = static_cast<Uint32>(program.GetActiveUniformBlocksCount());
const Uint32 uniformBindingPointCount =
static_cast<Uint32>(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<VkDeviceSize>(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<VkDeviceSize>(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<VkDeviceSize>(bufferObject->GetSize());
VkDeviceSize rangeStart = static_cast<VkDeviceSize>(range.start);
VkDeviceSize rangeEnd = static_cast<VkDeviceSize>(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<SizeT>(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<const void*> bindingData;
Vector<VkDeviceSize> bindingSizes;
if (!GatherBindingPayloads(program, bindingData, bindingSizes)) {
MGLOG_E("UniformDescriptorBinder::BindProgramUniformBuffers failed: cannot gather UBO payloads");
return false;
}
static const Uint8 kFallbackData[16] = {};
Vector<VkDescriptorBufferInfo> bufferInfos(m_maxBindings);
Vector<VkWriteDescriptorSet> writes;
writes.reserve(m_maxBindings);
Vector<Uint32> 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<Uint32>(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<Uint32>(writes.size()), writes.data(), 0, nullptr);
vkCmdBindDescriptorSets(commandBuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, pipelineLayout, 0, 1, &descriptorSet,
static_cast<Uint32>(dynamicOffsets.size()), dynamicOffsets.data());
return true;
}
} // namespace MobileGL::MG_Backend::DirectVulkan
@@ -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 <Includes.h>
#include <vk_mem_alloc.h>
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<const void*>& outData,
Vector<VkDeviceSize>& 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<VkDescriptorSet> m_descriptorSets;
Vector<FrameResources> 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
@@ -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
@@ -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
@@ -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<PipelineFactory>(m_device, m_config);
m_programFactory = MakeUnique<ProgramFactory>(m_device, m_config);
m_uniformDescriptorBinder = MakeUnique<UniformDescriptorBinder>();
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<VertexInputStateFactory>(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) {
@@ -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<PipelineFactory> m_pipelineFactory;
UniquePtr<ProgramFactory> m_programFactory;
UniquePtr<UniformDescriptorBinder> m_uniformDescriptorBinder;
UniquePtr<VertexInputStateFactory> m_vertexInputStateFactory;
void CreateInstance();
@@ -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<Uint>(m_uboScratch.size()); }
void SetUniformSamplerOrImageUnitIndex(Uint location, Int unit) {
@@ -10,6 +10,8 @@
#include <vector>
#include <cassert>
#include <string>
#include <chrono>
#include <cmath>
#include <vulkan/vulkan.h>
#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<float>(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);