From a425166018a10fc4986f0dafe487a343ced97478 Mon Sep 17 00:00:00 2001 From: Abdelrahman Date: Sun, 21 Jun 2026 17:02:09 +0100 Subject: [PATCH] Render loop --- main.cpp | 260 ++++++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 257 insertions(+), 3 deletions(-) diff --git a/main.cpp b/main.cpp index 34306be..85332b7 100644 --- a/main.cpp +++ b/main.cpp @@ -5,6 +5,8 @@ #include "vulkan_profiles/vulkan_profiles.h" #include "ktx.h" #include "ktxvulkan.h" +#include +#include #include #include #include @@ -95,6 +97,8 @@ typedef slang::CompilerOptionEntry *SlangCompOptEntryArray; typedef VkVertexInputAttributeDescription *VkVertexInputAttributeDescriptionArray; typedef VkPipelineShaderStageCreateInfo *VkPipelineShaderStageCreateInfoArray; typedef VkDynamicState *VkDynamicStateArray; +typedef glm::vec3 *GlmVec3Array; +typedef VkImageMemoryBarrier2 *VkImageMemoryBarrier2Array; // }}} // {{{ Helper Function Declarations @@ -104,7 +108,10 @@ wapp_intern inline void check(bool result, i32 code); // }}} // {{{ Global Variables +wapp_intern u32 frame_index = 0; +wapp_intern u32 image_index = 0; wapp_intern constexpr u32 max_frames_in_flight = 2; +wapp_intern constexpr u32 instance_count = 3; wapp_intern constexpr u32 texture_count = 3; wapp_intern bool running = true; wapp_intern bool update_swapchain = false; @@ -137,7 +144,7 @@ wapp_intern VmaAllocation vert_index_buf_alloc = VK_NULL_HANDLE; wapp_intern ShaderData shader_data = {}; wapp_intern ShaderDataBufferArray shader_data_bufs; wapp_intern VkFenceArray fences; -wapp_intern VkSemaphoreArray image_acquired_semaphores; +wapp_intern VkSemaphoreArray image_acquired_semaphores = nullptr; wapp_intern VkSemaphoreArray render_completed_semaphores = nullptr; wapp_intern VkCommandPool command_pool = VK_NULL_HANDLE; wapp_intern VkCommandBufferArray command_buffers; @@ -150,6 +157,8 @@ wapp_intern Slang::ComPtr slang_global_session; wapp_intern VkShaderModule shader_module = VK_NULL_HANDLE; wapp_intern VkPipelineLayout graphics_pipeline_layout = VK_NULL_HANDLE; wapp_intern VkPipeline graphics_pipeline = VK_NULL_HANDLE; +wapp_intern glm::vec3 camera_position = {0.0f, 0.0f, -6.0f}; +wapp_intern GlmVec3Array object_rotations; // }}} int main() { @@ -528,7 +537,7 @@ int main() { // {{{ Upload to GPU VkDeviceSize vertex_buf_size = sizeof(Vertex) * wapp_array_count(vertices); - VkDeviceSize index_buf_size = sizeof(u16) * wapp_array_count(vertices); + VkDeviceSize index_buf_size = sizeof(u16) * wapp_array_count(indices); VkBufferCreateInfo buf_create_info = {}; buf_create_info.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO; @@ -1104,8 +1113,229 @@ int main() { // }}} // {{{ Render Loop - SDL_Event event = {}; + /** + * Render loop overview: + * - Wait on fence + * - Acquire next image + * - Update shader data + * - Record command buffer + * - Submit command buffer + * - Present image + * - Poll events + */ + + object_rotations = wapp_array_with_capacity(glm::vec3, 3, ARRAY_INIT_FILLED); + u64 last_time = SDL_GetTicks(); + SDL_Event event = {}; + while (running) { + // {{{ Wait On Fence + check(vkWaitForFences(device, 1, &fences[frame_index], VK_TRUE, UINT64_MAX)); + check(vkResetFences(device, 1, &fences[frame_index])); + // }}} + + // {{{ Acquire Next Swapchain Image + check_swapchain(vkAcquireNextImageKHR(device, swapchain, UINT64_MAX, + image_acquired_semaphores[frame_index], VK_NULL_HANDLE, + &image_index)); + // }}} + + // {{{ Update Shader Data + shader_data.projection = glm::perspective(glm::radians(45.0f), (f32)window_size.x / (f32)window_size.y, + 0.1f, 32.0f); + shader_data.view = glm::translate(glm::mat4(1.0f), camera_position); + for (u32 i = 0; i < instance_count; ++i) { + glm::vec3 instance_pos = glm::vec3((f32)(i - 1) * 3.0f, 0.0f, 0.0f); + shader_data.model[i] = glm::translate(glm::mat4(1.0f), instance_pos) * + glm::mat4_cast(glm::quat(object_rotations[i])); + } + + memcpy(shader_data_bufs[frame_index].allocation_info.pMappedData, &shader_data, sizeof(ShaderData)); + // }}} + + // {{{ Record Command Buffer + VkCommandBuffer cb = command_buffers[frame_index]; + + check(vkResetCommandBuffer(cb, 0)); + + VkCommandBufferBeginInfo cmd_begin_info = {}; + cmd_begin_info.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO; + cmd_begin_info.flags = VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT; + check(vkBeginCommandBuffer(cb, &cmd_begin_info)); + + VkImageMemoryBarrier2Array memory_barriers = wapp_array_with_capacity(VkImageMemoryBarrier2, 2, ARRAY_INIT_FILLED); + + // Color Attachment + memory_barriers[0].sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER_2; + memory_barriers[0].srcStageMask = VK_PIPELINE_STAGE_2_COLOR_ATTACHMENT_OUTPUT_BIT; + memory_barriers[0].srcAccessMask = 0; + memory_barriers[0].dstStageMask = VK_PIPELINE_STAGE_2_COLOR_ATTACHMENT_OUTPUT_BIT; + memory_barriers[0].dstAccessMask = VK_ACCESS_2_COLOR_ATTACHMENT_READ_BIT | VK_ACCESS_2_COLOR_ATTACHMENT_WRITE_BIT; + memory_barriers[0].oldLayout = VK_IMAGE_LAYOUT_UNDEFINED; + memory_barriers[0].newLayout = VK_IMAGE_LAYOUT_ATTACHMENT_OPTIMAL; + memory_barriers[0].image = swapchain_images[image_index]; + memory_barriers[0].subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; + memory_barriers[0].subresourceRange.levelCount = 1; + memory_barriers[0].subresourceRange.layerCount = 1; + + // Depth Attachment + memory_barriers[0].sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER_2; + memory_barriers[0].srcStageMask = VK_PIPELINE_STAGE_2_LATE_FRAGMENT_TESTS_BIT; + memory_barriers[0].srcAccessMask = VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT; + memory_barriers[0].dstStageMask = VK_PIPELINE_STAGE_2_EARLY_FRAGMENT_TESTS_BIT; + memory_barriers[0].dstAccessMask = VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT; + memory_barriers[0].oldLayout = VK_IMAGE_LAYOUT_UNDEFINED; + memory_barriers[0].newLayout = VK_IMAGE_LAYOUT_ATTACHMENT_OPTIMAL; + memory_barriers[0].image = depth_image; + memory_barriers[0].subresourceRange.aspectMask = VK_IMAGE_ASPECT_DEPTH_BIT | VK_IMAGE_ASPECT_STENCIL_BIT; + memory_barriers[0].subresourceRange.levelCount = 1; + memory_barriers[0].subresourceRange.layerCount = 1; + + // {{{ Transition Layout Color And Depth Images + VkDependencyInfo barrier_dependency_info = {}; + barrier_dependency_info.sType = VK_STRUCTURE_TYPE_DEPENDENCY_INFO; + barrier_dependency_info.imageMemoryBarrierCount = wapp_array_count(memory_barriers); + barrier_dependency_info.pImageMemoryBarriers = memory_barriers; + + vkCmdPipelineBarrier2(cb, &barrier_dependency_info); + // }}} + + // {{{ Dynamic Rendering Commands + VkClearValue color_clear_value = {}; + color_clear_value.color = { 0.0f, 0.0f, 0.2f, 1.0f }; + VkClearValue depth_clear_value = {}; + depth_clear_value.depthStencil = { 1.0f, 0 }; + + VkRenderingAttachmentInfo color_attachment = {}; + color_attachment.sType = VK_STRUCTURE_TYPE_RENDERING_ATTACHMENT_INFO; + color_attachment.imageView = swapchain_views[image_index]; + color_attachment.imageLayout = VK_IMAGE_LAYOUT_ATTACHMENT_OPTIMAL; + color_attachment.loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR; + color_attachment.storeOp = VK_ATTACHMENT_STORE_OP_STORE; + color_attachment.clearValue = color_clear_value; + + VkRenderingAttachmentInfo depth_attachment = {}; + depth_attachment.sType = VK_STRUCTURE_TYPE_RENDERING_ATTACHMENT_INFO; + depth_attachment.imageView = depth_view; + depth_attachment.imageLayout = VK_IMAGE_LAYOUT_ATTACHMENT_OPTIMAL; + depth_attachment.loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR; + depth_attachment.storeOp = VK_ATTACHMENT_STORE_OP_DONT_CARE; + depth_attachment.clearValue = depth_clear_value; + + VkRect2D render_area = {}; + render_area.extent = { (u32)window_size.x, (u32)window_size.y }; + + VkRenderingInfo render_info = {}; + render_info.sType = VK_STRUCTURE_TYPE_RENDERING_INFO; + render_info.renderArea = render_area; + render_info.layerCount = 1; + render_info.colorAttachmentCount = 1; + render_info.pColorAttachments = &color_attachment; + render_info.pDepthAttachment = &depth_attachment; + vkCmdBeginRendering(cb, &render_info); + + // {{{ Set Viewport And Scissor Dynamic States + VkViewport viewport = {}; + viewport.width = window_size.x; + viewport.height = window_size.y; + viewport.minDepth = 0.0f; + viewport.maxDepth = 1.0f; + vkCmdSetViewport(cb, 0, 1, &viewport); + + VkRect2D scissor = {}; + scissor.extent = render_area.extent; + vkCmdSetScissor(cb, 0, 1, &scissor); + // }}} + + // {{{ Bind Resources + vkCmdBindPipeline(cb, VK_PIPELINE_BIND_POINT_GRAPHICS, graphics_pipeline); + vkCmdBindDescriptorSets(cb, VK_PIPELINE_BIND_POINT_GRAPHICS, graphics_pipeline_layout, 0, 1, &desc_set_tex, 0, NULL); + + VkDeviceSize vertex_offset = {}; + vkCmdBindVertexBuffers(cb, 0, 1, &vert_index_buf, &vertex_offset); + vkCmdBindIndexBuffer(cb, vert_index_buf, vertex_buf_size, VK_INDEX_TYPE_UINT16); + // }}} + + // {{{ Send ShaderData Buffer Device Address To Shader + vkCmdPushConstants(cb, graphics_pipeline_layout, VK_SHADER_STAGE_VERTEX_BIT, 0, + sizeof(VkDeviceAddress), &shader_data_bufs[frame_index].device_address); + // }}} + + // {{{ Draw Commands + vkCmdDrawIndexed(cb, index_count, instance_count, 0, 0, 0); + // }}} + + vkCmdEndRendering(cb); + // }}} + + // {{{ Transition Color Attachment For Presentation + VkImageMemoryBarrier2 present_barrier = {}; + present_barrier.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER_2; + present_barrier.srcStageMask = VK_PIPELINE_STAGE_2_COLOR_ATTACHMENT_OUTPUT_BIT; + present_barrier.srcAccessMask = VK_ACCESS_2_COLOR_ATTACHMENT_WRITE_BIT; + present_barrier.dstStageMask = VK_PIPELINE_STAGE_2_COLOR_ATTACHMENT_OUTPUT_BIT; + present_barrier.dstAccessMask = 0; + present_barrier.oldLayout = VK_IMAGE_LAYOUT_ATTACHMENT_OPTIMAL; + present_barrier.newLayout = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR; + present_barrier.image = swapchain_images[image_index]; + present_barrier.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; + present_barrier.subresourceRange.levelCount = 1; + present_barrier.subresourceRange.layerCount = 1; + + VkDependencyInfo present_dependency_info = {}; + present_dependency_info.sType = VK_STRUCTURE_TYPE_DEPENDENCY_INFO; + present_dependency_info.imageMemoryBarrierCount = 1; + present_dependency_info.pImageMemoryBarriers = &present_barrier; + vkCmdPipelineBarrier2(cb, &present_dependency_info); + // }}} + + check(vkEndCommandBuffer(cb)); + // }}} + + // {{{ Submit Command Buffer + VkPipelineStageFlags wait_stages = VK_PIPELINE_STAGE_2_COLOR_ATTACHMENT_OUTPUT_BIT; + + // The wait semaphore in pWaitSemaphores ensures that execution of the command buffer does not + // begin until the swapchain image we want to render to has been acquired. This means + // presentation has finished and the image has been released by the presentation engine. This is + // required because swapchain images are owned by the presentation engine rather than by our + // application. The pipeline stage specified in pWaitDstStageMask makes that wait happen at the + // color attachment output stage, so in theory the GPU may already begin work on earlier + // pipeline stages, such as vertex fetching. The signal semaphore in pSignalSemaphores, on the + // other hand, is signaled by the GPU once command buffer execution has completed and ensures + // that presentation does not begin until the command buffer has finished execution. Together, + // these guarantees prevent read/write hazards that could cause the GPU to read from or write + // to resources that are still in use. + VkSubmitInfo submit_info = {}; + submit_info.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO; + submit_info.waitSemaphoreCount = 1; + submit_info.pWaitSemaphores = &image_acquired_semaphores[frame_index]; + submit_info.pWaitDstStageMask = &wait_stages; + submit_info.commandBufferCount = 1; + submit_info.pCommandBuffers = &cb; + submit_info.signalSemaphoreCount = 1; + submit_info.pSignalSemaphores = &render_completed_semaphores[image_index]; + + check(vkQueueSubmit(queue, 1, &submit_info, fences[frame_index])); + // }}} + + frame_index = (frame_index + 1) % max_frames_in_flight; + + // {{{ Present Image + VkPresentInfoKHR present_info = {}; + present_info.sType = VK_STRUCTURE_TYPE_PRESENT_INFO_KHR; + present_info.waitSemaphoreCount = 1; + present_info.pWaitSemaphores = &render_completed_semaphores[image_index]; + present_info.swapchainCount = 1; + present_info.pSwapchains = &swapchain; + present_info.pImageIndices = &image_index; + + check_swapchain(vkQueuePresentKHR(queue, &present_info)); + // }}} + + // {{{ Poll Events + f32 elapsed_time = (SDL_GetTicks() - last_time) / 1000.0f; + last_time = SDL_GetTicks(); while (SDL_PollEvent(&event)) { switch (event.type) { case SDL_EVENT_QUIT: @@ -1114,10 +1344,34 @@ int main() { case SDL_EVENT_KEY_DOWN: if (event.key.key == SDLK_ESCAPE) { running = false; + break; } + // Select active model instance + if (event.key.key == SDLK_PLUS || event.key.key == SDLK_KP_PLUS) { + shader_data.selected = (shader_data.selected < 2) ? shader_data.selected + 1 : 0; + } + if (event.key.key == SDLK_MINUS || event.key.key == SDLK_KP_MINUS) { + shader_data.selected = (shader_data.selected > 0) ? shader_data.selected - 1 : 2; + } + break; + // Rotate the selected object with mouse drag + case SDL_EVENT_MOUSE_MOTION: + if (event.button.button == SDL_BUTTON_LEFT) { + object_rotations[shader_data.selected].x -= (float)event.motion.yrel * elapsed_time; + object_rotations[shader_data.selected].y += (float)event.motion.xrel * elapsed_time; + } + break; + // Zooming with the mouse wheel + case SDL_EVENT_MOUSE_WHEEL: + camera_position.z += (float)event.wheel.y * elapsed_time * 10.0f; + break; + // Window resize + case SDL_EVENT_WINDOW_RESIZED: + update_swapchain = true; break; } } + // }}} } // }}}