buffer retire and unloack container fix

main
noah metz 2026-07-22 13:16:06 -07:00
parent 9ac8c44b04
commit 7981274c42
4 changed files with 109 additions and 19 deletions

@ -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);

@ -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;
}

@ -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,

@ -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;
}