diff --git a/client/include/gpu.h b/client/include/gpu.h index 0092254..b0cdc1d 100644 --- a/client/include/gpu.h +++ b/client/include/gpu.h @@ -100,6 +100,12 @@ typedef struct TransferBufferStruct { uint32_t count; } TransferBuffer; +typedef struct RetiredBufferStruct { + VkBuffer buffer; + VmaAllocation memory; + uint64_t frame; +} RetiredBuffer; + typedef struct FrameContextStruct { VkFence ready; VkSemaphore image; @@ -150,8 +156,13 @@ typedef struct RenderContextStruct { FrameContext frame[MAX_FRAMES_IN_FLIGHT]; uint32_t current_frame; + uint64_t frame_number; vec2 window_scale; + RetiredBuffer* retired; + uint32_t retired_count; + uint32_t retired_max; + bool framebuffer_recreated; } RenderContext; @@ -180,6 +191,13 @@ VkResult create_storage_buffer( VkBuffer* buffer, VmaAllocation* memory); +void retire_buffer( + VkBuffer buffer, + VmaAllocation memory, + RenderContext* gpu); + +void destroy_retired(RenderContext* gpu); + VkDeviceAddress buffer_address( VkDevice device, VkBuffer buffer); diff --git a/client/src/draw.c b/client/src/draw.c index 7a7dbce..834b761 100644 --- a/client/src/draw.c +++ b/client/src/draw.c @@ -83,6 +83,10 @@ VkResult draw_frame( VK_RESULT(vkWaitForFences(context->device, 1, fences, VK_TRUE, UINT64_MAX)); VK_RESULT(vkResetFences(context->device, 1, fences)); + // This frame slot's previous submission has completed, so buffers retired + // MAX_FRAMES_IN_FLIGHT frames ago can no longer be referenced by the GPU + destroy_retired(context); + VkCommandBufferBeginInfo begin_info = { .sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO, }; @@ -337,6 +341,7 @@ VkResult draw_frame( } context->current_frame = (context->current_frame + 1) % MAX_FRAMES_IN_FLIGHT; + context->frame_number += 1; return VK_SUCCESS; } diff --git a/client/src/gpu.c b/client/src/gpu.c index d826cc0..12424e4 100644 --- a/client/src/gpu.c +++ b/client/src/gpu.c @@ -1,6 +1,7 @@ #include "gpu.h" #include "GLFW/glfw3.h" #include "stdio.h" +#include "stdlib.h" #include "string.h" #include "vk_mem_alloc.h" #include "vulkan/vulkan_core.h" @@ -1012,6 +1013,38 @@ VkResult create_storage_buffer( return vmaCreateBuffer(allocator, &buffer_info, &memory_info, buffer, memory, NULL); }; +void retire_buffer( + VkBuffer buffer, + VmaAllocation memory, + RenderContext* gpu) { + if(buffer == VK_NULL_HANDLE) return; + + if(gpu->retired_count == gpu->retired_max) { + gpu->retired_max = gpu->retired_max == 0 ? 16 : gpu->retired_max*2; + gpu->retired = realloc(gpu->retired, gpu->retired_max*sizeof(RetiredBuffer)); + } + + gpu->retired[gpu->retired_count].buffer = buffer; + gpu->retired[gpu->retired_count].memory = memory; + gpu->retired[gpu->retired_count].frame = gpu->frame_number; + gpu->retired_count += 1; +} + +void destroy_retired(RenderContext* gpu) { + // Entries are appended in frame order, so eligible entries are a prefix + uint32_t i = 0; + while(i < gpu->retired_count + && gpu->frame_number - gpu->retired[i].frame >= MAX_FRAMES_IN_FLIGHT) { + vmaDestroyBuffer(gpu->allocator, gpu->retired[i].buffer, gpu->retired[i].memory); + i += 1; + } + + if(i > 0) { + memmove(gpu->retired, gpu->retired + i, (gpu->retired_count - i)*sizeof(RetiredBuffer)); + gpu->retired_count -= i; + } +} + void destroy_transfer_buffer( VmaAllocator allocator, VkBuffer buffer, diff --git a/client/src/ui.c b/client/src/ui.c index 4f2668e..904b028 100644 --- a/client/src/ui.c +++ b/client/src/ui.c @@ -234,40 +234,74 @@ VkResult create_ui_pipeline( return VK_SUCCESS; } +// Layer structs come from uninitialized malloc, so cleanup is gated on the +// data.* count fields create_layer always sets, not on pointer NULL checks +static void destroy_layer(Layer* layer, RenderContext* gpu) { + for(uint32_t f = 0; f < MAX_FRAMES_IN_FLIGHT; f++) { + retire_buffer(layer->layer[f], layer->layer_memory[f], gpu); + if(layer->data.max_strings > 0) { + retire_buffer(layer->strings[f], layer->strings_memory[f], gpu); + } + if(layer->data.max_drawables > 0) { + retire_buffer(layer->drawables[f], layer->drawables_memory[f], gpu); + } + } + + if(layer->data.max_strings > 0) { + for(uint32_t s = 0; s < layer->data.max_strings; s++) { + if(layer->string_resources[s].codes_buffer == NULL) continue; + for(uint32_t f = 0; f < MAX_FRAMES_IN_FLIGHT; f++) { + retire_buffer(layer->string_resources[s].codes[f], layer->string_resources[s].codes_memory[f], gpu); + } + free(layer->string_resources[s].codes_buffer); + } + free(layer->string_resources); + free(layer->strings_buffer); + } + + if(layer->data.num_drawables > 0) { + free(layer->drawables_buffer); + } +} + VkResult unload_container( uint32_t id, RenderContext* gpu, UIContext* context) { - uint32_t index = 0xFFFFFFFF; - for(uint32_t i = 0; i < context->max_containers; i++) { - if(context->containers[i].id == id) { - index = i; - break; - } - } - - if(index == 0xFFFFFFFF) { + Container* container = context_container(id, context); + if(container == NULL) { return VK_ERROR_VALIDATION_FAILED_EXT; } - context->containers[index].id = 0; + for(uint32_t l = 0; l < container->layer_count; l++) { + destroy_layer(&container->layers[l], gpu); + } + free(container->layers); + container->layers = NULL; + container->layer_count = 0; for(uint32_t i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) { - vmaDestroyBuffer(gpu->allocator, context->containers[index].container[i], context->containers[index].container_memory[i]); - context->containers[index].address[i] = 0; + retire_buffer(container->container[i], container->container_memory[i], gpu); + container->address[i] = 0; } - context->containers[index].layer_count = 0; + // Drop focus without dispatching on_deselect: the script is being unloaded + if(context->active_container == container) { + context->active_container = NULL; + context->active_element = 0; + } - if(context->containers[index].script_env != 0) { - luaL_unref(context->lua, LUA_REGISTRYINDEX, context->containers[index].script_env); - context->containers[index].script_env = 0; + if(container->script_env != 0) { + luaL_unref(context->lua, LUA_REGISTRYINDEX, container->script_env); + container->script_env = 0; } - if(context->containers[index].script_path != NULL) { - free(context->containers[index].script_path); - context->containers[index].script_path = NULL; + if(container->script_path != NULL) { + free(container->script_path); + container->script_path = NULL; } + container->id = 0; + return VK_SUCCESS; }