From 65462e5eff7d952d97d30bc3e3720d7f6d8a402e Mon Sep 17 00:00:00 2001 From: Danny Holman Date: Thu, 20 Mar 2025 14:10:08 -0500 Subject: render: vulkan: fix synchronization bugs Fix a host of synchronization bugs involving the Vulkan fence objects and the state of the command pool. Signed-off-by: Danny Holman --- include/rune/render/renderer.h | 10 +-- render/vulkan/renderer.c | 142 +++++++++++++++++++++++++++++++++++------ render/vulkan/renderpass.c | 57 +++++++++++++++-- 3 files changed, 180 insertions(+), 29 deletions(-) diff --git a/include/rune/render/renderer.h b/include/rune/render/renderer.h index ebf8602..7a1ea3b 100644 --- a/include/rune/render/renderer.h +++ b/include/rune/render/renderer.h @@ -23,15 +23,15 @@ #define RUNE_RENDER_RENDERER_H #include -#include +#include -struct rune_renderer { +typedef struct rune_renderer { void (*close)(void); void (*draw)(void); void (*clear)(void); -}; +} renderer_t; -RAPI struct rune_renderer* select_render_vulkan(struct rune_window *window); -RAPI struct rune_renderer* select_render_directx(struct rune_window *window); +RAPI renderer_t* select_render_vulkan(window_t *window); +RAPI renderer_t* select_render_directx(window_t *window); #endif diff --git a/render/vulkan/renderer.c b/render/vulkan/renderer.c index 0facddf..679abef 100644 --- a/render/vulkan/renderer.c +++ b/render/vulkan/renderer.c @@ -1,19 +1,46 @@ -#include -#include +/* + * Rune Game Engine + * Copyright 2024 Danny Holman + * + * This software is provided 'as-is', without any express or implied + * warranty. In no event will the authors be held liable for any damages + * arising from the use of this software. + * + * Permission is granted to anyone to use this software for any purpose, + * including commercial applications, and to alter it and redistribute it + * freely, subject to the following restrictions: + * + * 1. The origin of this software must not be misrepresented; you must not + * claim that you wrote the original software. If you use this software + * in a product, an acknowledgment in the product documentation would be + * appreciated but is not required. + * 2. Altered source versions must be plainly marked as such, and must not be + * misrepresented as being the original software. + * 3. This notice may not be removed or altered from any source distribution. + */ + +#include "vk_types.h" +#include "renderpass.h" +#include "framebuffer.h" +#include "swapchain.h" +#include "device.h" +#include "context.h" +#include "image.h" +#include "fence.h" +#include "vkassert.h" #include #include #include #include -#include "context.h" -#include "image.h" +#include #include -static struct vkcontext *context = NULL; +static vkcontext_t *context = NULL; void _init_cmdbuffers(void) { uint32_t num_buffers = context->swapchain->img_count; if (context->cmdbuffers == NULL) - context->cmdbuffers = rune_calloc(0, sizeof(struct vkcmdbuffer*) * num_buffers); + context->cmdbuffers = rune_calloc(0, sizeof(vkcmdbuffer_t*) * num_buffers); for (uint32_t i = 0; i < num_buffers; i++) { if (context->cmdbuffers[i] != NULL) @@ -37,7 +64,7 @@ void _destroy_cmdbuffers(void) { void _init_framebuffers(void) { uint32_t num_buffers = context->swapchain->img_count; if (context->framebuffers == NULL) - context->framebuffers = rune_calloc(0, sizeof(struct vkframebuffer*) * num_buffers); + context->framebuffers = rune_calloc(0, sizeof(vkframebuffer_t*) * num_buffers); uint32_t at_count = 2; for (uint32_t i = 0; i < num_buffers; i++) { @@ -67,16 +94,22 @@ void _destroy_framebuffers(void) { log_output(LOG_DEBUG, "Destroyed %d frame buffers", num_buffers); } -int _init_vulkan(struct rune_window *window) { +int _init_vulkan(window_t *window) { + log_output(LOG_DEBUG, "Initializing Vulkan"); struct timeval start; struct timeval stop; gettimeofday(&start, NULL); - struct ext_container ext; + ext_container_t ext; ext.extensions = glfwGetRequiredInstanceExtensions(&ext.ext_count); - struct vklayer_container *vklayers = init_vklayers(&ext); + + if (rune_get_vk_debug() == 1) { + vklayer_container_t *vklayers = init_vklayers(&ext); + context = create_vkcontext(vklayers, &ext); + } else { + context = create_vkcontext(NULL, &ext); + } - context = create_vkcontext(vklayers, &ext); if (context == NULL) return -1; @@ -111,7 +144,7 @@ int _init_vulkan(struct rune_window *window) { context->image_semaphores = rune_alloc(sizeof(VkSemaphore) * context->swapchain->max_frames); context->queue_semaphores = rune_alloc(sizeof(VkSemaphore) * context->swapchain->max_frames); - context->fences_in_flight = rune_calloc(0, sizeof(struct vkfence*) * context->swapchain->max_frames); + context->fences_in_flight = rune_calloc(0, sizeof(vkfence_t*) * context->swapchain->max_frames); VkSemaphoreCreateInfo scinfo; for (uint8_t i = 0; i < context->swapchain->max_frames; i++) { @@ -122,7 +155,7 @@ int _init_vulkan(struct rune_window *window) { vkCreateSemaphore(context->dev->ldev, &scinfo, NULL, &context->queue_semaphores[i]); context->fences_in_flight[i] = create_vkfence(context->dev, 1); } - context->images_in_flight = rune_calloc(0, sizeof(struct vkfence*) * context->swapchain->img_count); + context->images_in_flight = rune_calloc(0, sizeof(vkfence_t*) * context->swapchain->img_count); gettimeofday(&stop, NULL); log_output(LOG_INFO, "Finished initializing Vulkan in %lums", (stop.tv_sec - start.tv_sec) * 1000000 + stop.tv_usec - start.tv_usec); @@ -145,19 +178,92 @@ void _close_vulkan(void) { destroy_swapchain(context->swapchain, context->dev); destroy_vkdev(context->dev); destroy_vkcontext(context); - rune_free(renderer); +} + +int _begin_frame(float time) { + vkfence_t *frame_fence = context->fences_in_flight[context->swapchain->frame]; + if (fence_lock(frame_fence, context->dev, UINT64_MAX) == -1) { + log_output(LOG_WARN, "Error locking in-flight fence"); + return -1; + } + + uint32_t next_img = vkswapchain_get_next_img(context->swapchain, + context->dev, + UINT64_MAX, + NULL, + context->image_semaphores[context->swapchain->frame]); + + if (next_img == -1) + return -1; + + context->img_index = next_img; + vkcmdbuffer_t *cmdbuf = context->cmdbuffers[context->img_index]; + cmdbuf_begin(cmdbuf, 0, 0, 0); + + VkViewport vport; + vport.x = 0; + vport.y = (float)context->surface->height; + vport.width = (float)context->surface->width; + vport.height = (float)context->surface->height; + vport.minDepth = 0; + vport.maxDepth = 1; + + VkRect2D scissor; + scissor.offset.x = 0; + scissor.offset.y = 0; + scissor.extent.width = context->surface->width; + scissor.extent.height = context->surface->height; + + vkCmdSetViewport(cmdbuf->handle, 0, 1, &vport); + vkCmdSetScissor(cmdbuf->handle, 0, 1, &scissor); + + context->rendpass->area[2] = context->surface->width; + context->rendpass->area[3] = context->surface->height; + + VkFramebuffer framebuf = context->framebuffers[context->img_index]->handle; + renderpass_begin(cmdbuf, context->rendpass, framebuf); + return 0; +} + +int _end_frame(float time) { + vkcmdbuffer_t *cmdbuf = context->cmdbuffers[context->img_index]; + renderpass_end(cmdbuf, context->rendpass); + cmdbuf_end(cmdbuf); + + vkfence_t** img_in_flight = &context->images_in_flight[context->img_index]; + if (*img_in_flight != NULL) + fence_lock(*img_in_flight, context->dev, UINT64_MAX); + + context->images_in_flight[context->img_index] = context->fences_in_flight[context->swapchain->frame]; + *img_in_flight = context->fences_in_flight[context->swapchain->frame]; + fence_unlock(*img_in_flight, context->dev); + + cmdbuf_submit(cmdbuf, + &context->queue_semaphores[context->swapchain->frame], + &context->image_semaphores[context->swapchain->frame], + context->dev->queues[0].handle, + (*img_in_flight)->handle); + + vkswapchain_present(context->swapchain, + context->dev, + &context->queue_semaphores[context->swapchain->frame], + &context->img_index); + + cmdbuf_reset(cmdbuf); + + return 0; } void _draw_vulkan(void) { - if (fence_lock(context->fences_in_flight[context->swapchain->frame], context->dev, UINT64_MAX) != 0) - log_output(LOG_WARN, "In-flight fence locking failure"); + _begin_frame(0); + _end_frame(0); } void _clear_vulkan(void) { } -struct rune_renderer* select_render_vulkan(struct rune_window *window) { - struct rune_renderer *ret = rune_alloc(sizeof(struct rune_renderer)); +renderer_t* select_render_vulkan(window_t *window) { + renderer_t *ret = rune_alloc(sizeof(renderer_t)); ret->close = _close_vulkan; ret->draw = _draw_vulkan; ret->clear = _clear_vulkan; diff --git a/render/vulkan/renderpass.c b/render/vulkan/renderpass.c index ea3dd5b..981039a 100644 --- a/render/vulkan/renderpass.c +++ b/render/vulkan/renderpass.c @@ -46,7 +46,12 @@ void destroy_vkcmdbuffer(vkcmdbuffer_t *cmdbuffer, vkdev_t *dev) { rune_free(cmdbuffer); } -void cmdbuf_begin(struct vkcmdbuffer *cmdbuffer, int single, int rpass_cont, int sim_use) { +void cmdbuf_begin(vkcmdbuffer_t *cmdbuffer, int single, int rpass_cont, int sim_use) { + if (cmdbuffer->state != CMDBUF_INITIAL) { + log_output(LOG_FATAL, "Attempted to record to a command buffer not in initial state"); + rune_abort(); + } + VkCommandBufferBeginInfo binfo; binfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO; binfo.flags = 0; @@ -61,13 +66,43 @@ void cmdbuf_begin(struct vkcmdbuffer *cmdbuffer, int single, int rpass_cont, int cmdbuffer->state = CMDBUF_RECORDING; } -void cmdbuf_end(struct vkcmdbuffer *cmdbuffer) { +void cmdbuf_end(vkcmdbuffer_t *cmdbuffer) { + if (cmdbuffer->state != CMDBUF_RECORDING) { + log_output(LOG_FATAL, "Attempted to end command buffer not in recording state"); + rune_abort(); + } + vkassert(vkEndCommandBuffer(cmdbuffer->handle)); cmdbuffer->state = CMDBUF_READY; } -struct vkcmdbuffer* cmdbuf_begin_single_use(struct vkdev *dev) { - struct vkcmdbuffer *ret = create_vkcmdbuffer(dev, 1); +void cmdbuf_submit(vkcmdbuffer_t *cmdbuffer, VkSemaphore *signal, VkSemaphore *wait, VkQueue queue_handle, VkFence fence_handle) { + if (cmdbuffer->state != CMDBUF_READY) { + log_output(LOG_FATAL, "Attempted to submit command buffer not in ready state"); + return; + } + + VkSubmitInfo sinfo; + sinfo.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO; + sinfo.pNext = NULL; + sinfo.commandBufferCount = 1; + sinfo.pCommandBuffers = &cmdbuffer->handle; + sinfo.signalSemaphoreCount = 1; + sinfo.pSignalSemaphores = signal; + sinfo.waitSemaphoreCount = 1; + sinfo.pWaitSemaphores = wait; + VkPipelineStageFlags flags[1] = {VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT}; + sinfo.pWaitDstStageMask = flags; + vkassert(vkQueueSubmit(queue_handle, 1, &sinfo, fence_handle)); + cmdbuffer->state = CMDBUF_SUBMITTED; +} + +void cmdbuf_reset(vkcmdbuffer_t *cmdbuffer) { + cmdbuffer->state = CMDBUF_INITIAL; +} + +vkcmdbuffer_t* cmdbuf_begin_single_use(vkdev_t *dev) { + vkcmdbuffer_t *ret = create_vkcmdbuffer(dev, 1); cmdbuf_begin(ret, 1, 0, 0); return ret; } @@ -173,7 +208,12 @@ void destroy_vkrendpass(vkrendpass_t *rendpass, vkdev_t *dev) { rune_free(rendpass); } -void renderpass_begin(struct vkcmdbuffer *buf, struct vkrendpass *rendpass, VkFramebuffer framebuf) { +void renderpass_begin(vkcmdbuffer_t *buf, vkrendpass_t *rendpass, VkFramebuffer framebuf) { + if (buf->state != CMDBUF_RECORDING) { + log_output(LOG_FATAL, "Attempted to place command buffer not in recording state in a render pass"); + rune_abort(); + } + VkClearValue cvals[2]; cvals[0].color.float32[0] = rendpass->color[0]; cvals[0].color.float32[1] = rendpass->color[1]; @@ -198,7 +238,12 @@ void renderpass_begin(struct vkcmdbuffer *buf, struct vkrendpass *rendpass, VkFr buf->state = CMDBUF_IN_RENDERPASS; } -void renderpass_end(struct vkcmdbuffer *buf, struct vkrendpass *rendpass) { +void renderpass_end(vkcmdbuffer_t *buf, vkrendpass_t *rendpass) { + if (buf->state != CMDBUF_IN_RENDERPASS) { + log_output(LOG_FATAL, "Attempted to purge command buffer not in render pass"); + rune_abort(); + } + vkCmdEndRenderPass(buf->handle); buf->state = CMDBUF_RECORDING; } -- cgit v1.2.3