From b8fc36910f081df0781f5c0e16092ed6e07639ae Mon Sep 17 00:00:00 2001 From: Piotr Krygier Date: Tue, 10 Feb 2026 14:34:11 +0100 Subject: [PATCH] Replace MVP uniform buffer with push constants UBO should be used for large data. MVP should be push constants, since it's a small amount of data updated every frame Signed-off-by: Piotr Krygier --- graphics/shaders/shader.vert | 4 +- graphics/shaders/solid_objects.frag | 2 +- graphics/src/graphics_context.h | 1 + graphics/src/pipeline_builder.c | 2 +- graphics/src/pipeline_builder.h | 12 +++ graphics/src/rse_graphics.c | 154 ++++++++++++++++------------ graphics/src/vulkan_commons.h | 2 +- 7 files changed, 106 insertions(+), 71 deletions(-) diff --git a/graphics/shaders/shader.vert b/graphics/shaders/shader.vert index 5fef9f39..da23d7a0 100644 --- a/graphics/shaders/shader.vert +++ b/graphics/shaders/shader.vert @@ -1,6 +1,6 @@ #version 450 -layout(binding = 0) uniform UniformBufferObject { +layout(push_constant) uniform pc { mat4 model; mat4 view; mat4 proj; @@ -16,7 +16,7 @@ struct InstanceData uint texture_id; }; -layout(binding = 1) uniform InstanceUniformBuffer { +layout(binding = 0) uniform InstanceUniformBuffer { InstanceData instances[MAX_INSTANCES]; }; diff --git a/graphics/shaders/solid_objects.frag b/graphics/shaders/solid_objects.frag index 0e823043..7b4cada2 100644 --- a/graphics/shaders/solid_objects.frag +++ b/graphics/shaders/solid_objects.frag @@ -1,7 +1,7 @@ #version 450 #extension GL_EXT_nonuniform_qualifier : enable -layout(set = 0, binding = 2) uniform sampler2D texSampler[]; +layout(set = 0, binding = 1) uniform sampler2D texSampler[]; layout(location = 0) in vec3 fragColor; layout(location = 1) in vec2 fragTexCoord; diff --git a/graphics/src/graphics_context.h b/graphics/src/graphics_context.h index 9e223997..955a905e 100644 --- a/graphics/src/graphics_context.h +++ b/graphics/src/graphics_context.h @@ -281,6 +281,7 @@ struct graphics_context_t uint32_t current_frame; uint32_t swapchain_images_count; SDL_Window* window_handle; + struct mvp_data_t mvp_data; struct renderer_data_t renderer_data; struct debug_overlay_t debug_overlay; struct vulkan_handles vulkan_handles; diff --git a/graphics/src/pipeline_builder.c b/graphics/src/pipeline_builder.c index 7796ded6..f4c86817 100644 --- a/graphics/src/pipeline_builder.c +++ b/graphics/src/pipeline_builder.c @@ -341,7 +341,7 @@ rse_err_t pipeline_add_vertex_input_attribute(struct pipeline_t* pipeline, return RSE_ERROR_NO_ERROR; } -rse_err_t pipeline_addd_push_constants(struct pipeline_t* pipeline, +rse_err_t pipeline_add_push_constants(struct pipeline_t* pipeline, VkShaderStageFlagBits shader_stage, uint32_t offset, uint32_t size) diff --git a/graphics/src/pipeline_builder.h b/graphics/src/pipeline_builder.h index fbdeca20..8c6723a7 100644 --- a/graphics/src/pipeline_builder.h +++ b/graphics/src/pipeline_builder.h @@ -92,6 +92,18 @@ rse_err_t pipeline_add_dynamic_state(struct pipeline_t* pipeline, const VkDynami rse_err_t pipeline_add_descriptor_sets(struct graphics_context_t* context, struct pipeline_t* pipeline, uint32_t descriptor_set_id); + +/** + * @brief Add push constants to pipeline + * + * @return RSE_ERROR_NO_ERROR on success + * + */ +rse_err_t pipeline_add_push_constants(struct pipeline_t* pipeline, + VkShaderStageFlagBits shader_stage, + uint32_t offset, + uint32_t size); + /** * @brief Build pipeline from added components. This function should be called after adding all necessary components. * After call, pipeline is ready to be used and all added components are cleared, ready for next pipeline to be built. diff --git a/graphics/src/rse_graphics.c b/graphics/src/rse_graphics.c index 1b5a2099..7cd9d2ae 100644 --- a/graphics/src/rse_graphics.c +++ b/graphics/src/rse_graphics.c @@ -7,7 +7,6 @@ #include #include #include -#include #include #include "descriptor_builder.h" @@ -29,15 +28,62 @@ struct rse_graphics_context_t struct graphics_context_t* context; }; +static void model_view_projection_update(struct graphics_context_t* context) +{ + vec3 eye = {0}; + vec3 center = {0}; + vec3 up = {0}; + vec3 rotation_vec = {1.0f, 0.0f, 0.0f}; + float fovy; + float z_near; + float z_far; + + eye[2] = -5.0f; + + up[1] = 1.0f; + + fovy = glm_rad(45.0f); + z_near = 0.1f; + z_far = 20.0f; + + glm_mat4_identity(context->mvp_data.model); + + glm_rotate(context->mvp_data.model, glm_rad(0.0f), rotation_vec); + glm_lookat(eye, center, up, context->mvp_data.view); + glm_perspective( + fovy, + context->swapchain_data.swapchain_extent.width / (float)context->swapchain_data.swapchain_extent.height, + z_near, + z_far, + context->mvp_data.proj); +} + static rse_err_t render_static_mesh(struct graphics_context_t* context, uint32_t renderer_id) { VkCommandBuffer command_buffer = context->vulkan_handles.command_buffers[context->current_frame]; VkDeviceSize offsets[] = {0}; + uint32_t mvp_offset = 0; + uint32_t mvp_size = sizeof(struct mvp_data_t); (void)renderer_id; + + vkCmdPushConstants(command_buffer, + context->pipelines_data.pipeline_layouts[0], + VK_SHADER_STAGE_VERTEX_BIT, + mvp_offset, + mvp_size, + &context->mvp_data); + model_view_projection_update(context); vkCmdBindPipeline(command_buffer, VK_PIPELINE_BIND_POINT_GRAPHICS, context->pipelines_data.pipelines[0]); - vkCmdBindIndexBuffer(command_buffer, context->render_targets[RENDER_TARGET_3D].index_buffer.buffer, 0, VK_INDEX_TYPE_UINT16); - vkCmdBindVertexBuffers(command_buffer, 0, 1, &context->render_targets[RENDER_TARGET_3D].vertex_buffer.buffer, offsets); + vkCmdBindIndexBuffer(command_buffer, + context->render_targets[RENDER_TARGET_3D].index_buffer.buffer, + 0, + VK_INDEX_TYPE_UINT16); + vkCmdBindVertexBuffers(command_buffer, + 0, + 1, + &context->render_targets[RENDER_TARGET_3D].vertex_buffer.buffer, + offsets); /* FIXME: Currently we can only bind one descriptor set */ vkCmdBindDescriptorSets(command_buffer, VK_PIPELINE_BIND_POINT_GRAPHICS, @@ -57,38 +103,6 @@ static rse_err_t render_static_mesh(struct graphics_context_t* context, uint32_t return RSE_ERROR_NO_ERROR; } -static void model_view_projection_update(struct graphics_context_t* context, const struct vulkan_buffer_t* buffer) -{ - struct uniform_buffer_object_t ubo = {0}; - vec3 eye = {0}; - vec3 center = {0}; - vec3 up = {0}; - vec3 rotation_vec = {1.0f, 0.0f, 0.0f}; - float fovy; - float z_near; - float z_far; - - eye[2] = -5.0f; - - up[1] = 1.0f; - - fovy = glm_rad(45.0f); - z_near = 0.1f; - z_far = 20.0f; - - glm_mat4_identity(ubo.model); - - glm_rotate(ubo.model, glm_rad(0.0f), rotation_vec); - glm_lookat(eye, center, up, ubo.view); - glm_perspective( - fovy, - context->swapchain_data.swapchain_extent.width / (float)context->swapchain_data.swapchain_extent.height, - z_near, - z_far, - ubo.proj); - memcpy(buffer->allocation_info.pMappedData, &ubo, sizeof(ubo)); -} - rse_err_t rse_graphics_init(struct rse_graphics_context_t** context) { rse_err_t status = RSE_ERROR_NO_ERROR; @@ -110,23 +124,23 @@ rse_err_t rse_graphics_init(struct rse_graphics_context_t** context) return status; } +#define TEXTURES_COUNT (2U) rse_err_t rse_graphics_test_function(struct rse_graphics_context_t* rse_context) { rse_err_t status = RSE_ERROR_NO_ERROR; struct graphics_context_t* context = rse_context->context; - rse_id_t textures[2] = {0}; + rse_id_t textures[TEXTURES_COUNT] = {0}; struct pipeline_t pipeline; rse_id_t descriptor_set_handle = 0; struct descriptor_set_layout_bindings_t layout_bindings = {0}; struct pipeline_infos_t pipeline_infos = {0}; - struct vulkan_buffer_t model_view_projection_buffer = {0}; rse_id_t mesh_id_1, mesh_id_2 = 0; rse_id_t font_id = 0; rse_id_t static_mesh_renderer = 0U; rse_id_t vertex_shader_id, fragment_shader_id; - texture_load_from_file(context, "../../test_image.jpg", &textures[0]); - texture_load_from_file(context, "../../test_image_2.jpg", &textures[1]); + STATUS_CHECK(texture_load_from_file(context, "../../test_image.jpg", &textures[0])); + STATUS_CHECK(texture_load_from_file(context, "../../test_image_2.jpg", &textures[1])); STATUS_CHECK(fonts_init()); STATUS_CHECK(fonts_load_from_file(context, "../../NotoSansMono-Regular.ttf", &font_id)); @@ -146,7 +160,7 @@ rse_err_t rse_graphics_test_function(struct rse_graphics_context_t* rse_context) // FIXME: Temporary array of vertices, for testing purposes uint16_t indices[] = {0, 1, 2, 2, 3, 0}; - mesh_create(context, vertices, indices, 4, 6, &mesh_id_1); + STATUS_CHECK(mesh_create(context, vertices, indices, 4, 6, &mesh_id_1)); if (RSE_ERROR_NO_ERROR != mesh_create_instance(context, mesh_id_1, @@ -162,7 +176,7 @@ rse_err_t rse_graphics_test_function(struct rse_graphics_context_t* rse_context) exit(1); } - mesh_create(context, vertices2, indices, 4, 6, &mesh_id_2); + STATUS_CHECK(mesh_create(context, vertices2, indices, 4, 6, &mesh_id_2)); if (RSE_ERROR_NO_ERROR != mesh_create_instance( @@ -172,36 +186,38 @@ rse_err_t rse_graphics_test_function(struct rse_graphics_context_t* rse_context) exit(1); } - buffer_create_uniform(context, &model_view_projection_buffer, sizeof(struct uniform_buffer_object_t)); - model_view_projection_update(context, &model_view_projection_buffer); - buffers_update(context); + STATUS_CHECK(buffers_update(context)); - sampler_create(context, &context->vulkan_handles.sampler); + STATUS_CHECK(sampler_create(context, &context->vulkan_handles.sampler)); - descriptor_pool_add_type(context, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, 2); - descriptor_pool_add_type(context, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, 2); - descriptor_pool_initialize(context); + STATUS_CHECK(descriptor_pool_add_type(context, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, 1)); + STATUS_CHECK(descriptor_pool_add_type(context, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, TEXTURES_COUNT)); + STATUS_CHECK(descriptor_pool_initialize(context)); /* Normal rendering */ - descriptor_create_new_set(context, &descriptor_set_handle); - descriptor_add_layout(&layout_bindings, 0, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, 1, VK_SHADER_STAGE_VERTEX_BIT); - descriptor_add_layout(&layout_bindings, 1, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, 1, VK_SHADER_STAGE_VERTEX_BIT); - descriptor_add_layout(&layout_bindings, 2, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, 2, VK_SHADER_STAGE_FRAGMENT_BIT); - descriptor_set_finish(context, &layout_bindings, descriptor_set_handle); - descriptor_build_sets(context); + STATUS_CHECK(descriptor_create_new_set(context, &descriptor_set_handle)); + STATUS_CHECK( + descriptor_add_layout(&layout_bindings, 0, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, 1, VK_SHADER_STAGE_VERTEX_BIT)); + STATUS_CHECK(descriptor_add_layout(&layout_bindings, + 1, + VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, + TEXTURES_COUNT, + VK_SHADER_STAGE_FRAGMENT_BIT)); + STATUS_CHECK(descriptor_set_finish(context, &layout_bindings, descriptor_set_handle)); + STATUS_CHECK(descriptor_build_sets(context)); - descriptor_attach_buffer(context, - descriptor_set_handle, - &model_view_projection_buffer, - 0, - VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER); - descriptor_attach_buffer(context, - descriptor_set_handle, - &context->render_targets[RENDER_TARGET_3D].instance_buffer, - 1, - VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER); + STATUS_CHECK(descriptor_attach_buffer(context, + descriptor_set_handle, + &context->render_targets[RENDER_TARGET_3D].instance_buffer, + 0, + VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER)); - descriptor_attach_images(context, descriptor_set_handle, textures, 2, context->vulkan_handles.sampler, 2); + STATUS_CHECK(descriptor_attach_images(context, + descriptor_set_handle, + textures, + TEXTURES_COUNT, + context->vulkan_handles.sampler, + 1)); /* Initialize pipeline */ pipeline_builder_init(&pipeline); @@ -215,12 +231,18 @@ rse_err_t rse_graphics_test_function(struct rse_graphics_context_t* rse_context) pipeline_add_vertex_input_attribute(&pipeline, 0, 0, VK_FORMAT_R32G32B32_SFLOAT, offsetof(struct vertex_t, pos)); pipeline_add_vertex_input_attribute(&pipeline, 0, 1, VK_FORMAT_R32G32B32_SFLOAT, offsetof(struct vertex_t, color)); - pipeline_add_vertex_input_attribute(&pipeline, 0, 2, VK_FORMAT_R32G32_SFLOAT, offsetof(struct vertex_t, tex_coords)); + pipeline_add_vertex_input_attribute(&pipeline, + 0, + 2, + VK_FORMAT_R32G32_SFLOAT, + offsetof(struct vertex_t, tex_coords)); pipeline_add_dynamic_state(&pipeline, VK_DYNAMIC_STATE_VIEWPORT); pipeline_add_dynamic_state(&pipeline, VK_DYNAMIC_STATE_SCISSOR); + pipeline_add_push_constants(&pipeline, VK_SHADER_STAGE_VERTEX_BIT, 0, sizeof(struct mvp_data_t)); pipeline_add_descriptor_sets(context, &pipeline, descriptor_set_handle); + pipeline_add(context, &pipeline, &pipeline_infos); pipelines_build(context, &pipeline_infos); diff --git a/graphics/src/vulkan_commons.h b/graphics/src/vulkan_commons.h index 9e2969b8..8ad3c007 100644 --- a/graphics/src/vulkan_commons.h +++ b/graphics/src/vulkan_commons.h @@ -44,7 +44,7 @@ struct vulkan_buffer_t VmaAllocationInfo allocation_info; }; -struct uniform_buffer_object_t +struct mvp_data_t { alignas(16) mat4 model; alignas(16) mat4 view;