Refactor frame state to allow access to the destroy queue from asset manager

This commit is contained in:
sergeypdev 2024-12-30 23:55:13 +04:00
parent 05f2deb5df
commit afa00106e4
5 changed files with 190 additions and 125 deletions

View File

@ -30,6 +30,9 @@ const tracy = @import("tracy");
const vk = @import("vk"); const vk = @import("vk");
const GraphicsContext = @import("GraphicsContext.zig"); const GraphicsContext = @import("GraphicsContext.zig");
const DescriptorManager = @import("DescriptorManager.zig"); const DescriptorManager = @import("DescriptorManager.zig");
const render_common = @import("render_common.zig");
const DeferredDestroyQueue = render_common.DeferredDestroyQueue;
const RenderFrameState = render_common.RenderFrameState;
pub const AssetId = assets.AssetId; pub const AssetId = assets.AssetId;
pub const Handle = assets.Handle; pub const Handle = assets.Handle;
@ -64,6 +67,8 @@ texture_heap_memory: vk.DeviceMemory = .null_handle,
vertex_heap: VertexBufferHeap, vertex_heap: VertexBufferHeap,
gc: *GraphicsContext, gc: *GraphicsContext,
descriptorman: *DescriptorManager, descriptorman: *DescriptorManager,
// A way to access destroy queue
frame_state: *RenderFrameState,
// For uploads // For uploads
queue: *GraphicsContext.QueueInstance, queue: *GraphicsContext.QueueInstance,
@ -157,7 +162,7 @@ const AssetWatcher = struct {
} }
}; };
pub fn init(allocator: std.mem.Allocator, frame_arena: std.mem.Allocator, gc: *GraphicsContext, descriptorman: *DescriptorManager) !AssetManager { pub fn init(allocator: std.mem.Allocator, frame_arena: std.mem.Allocator, gc: *GraphicsContext, descriptorman: *DescriptorManager, frame_state: *RenderFrameState) !AssetManager {
var buf: [std.fs.MAX_PATH_BYTES]u8 = undefined; var buf: [std.fs.MAX_PATH_BYTES]u8 = undefined;
const exe_dir_path = std.fs.selfExeDirPath(&buf) catch @panic("can't find self exe dir path"); const exe_dir_path = std.fs.selfExeDirPath(&buf) catch @panic("can't find self exe dir path");
const exe_dir = std.fs.openDirAbsolute(exe_dir_path, .{}) catch @panic("can't open self exe dir path"); const exe_dir = std.fs.openDirAbsolute(exe_dir_path, .{}) catch @panic("can't open self exe dir path");
@ -180,6 +185,7 @@ pub fn init(allocator: std.mem.Allocator, frame_arena: std.mem.Allocator, gc: *G
.vertex_heap = VertexBufferHeap.init(allocator) catch @panic("OOM"), .vertex_heap = VertexBufferHeap.init(allocator) catch @panic("OOM"),
.gc = gc, .gc = gc,
.descriptorman = descriptorman, .descriptorman = descriptorman,
.frame_state = frame_state,
.queue = queue, .queue = queue,
.command_pool = try queue.createCommandPool(.{ .transient_bit = true }), .command_pool = try queue.createCommandPool(.{ .transient_bit = true }),
}; };
@ -1873,6 +1879,7 @@ fn deleteDependees(self: *AssetManager, id: AssetId) void {
} }
fn freeAsset(self: *AssetManager, asset: *LoadedAsset) void { fn freeAsset(self: *AssetManager, asset: *LoadedAsset) void {
const destroy_queue = &self.frame_state.frame_data[self.frame_state.frame].destroy_queue;
switch (asset.*) { switch (asset.*) {
.mesh => |*mesh| { .mesh => |*mesh| {
self.vertex_heap.free(mesh.heap_handle); self.vertex_heap.free(mesh.heap_handle);
@ -1881,18 +1888,12 @@ fn freeAsset(self: *AssetManager, asset: *LoadedAsset) void {
self.allocator.free(shader.source); self.allocator.free(shader.source);
}, },
.shaderProgram => |*program| { .shaderProgram => |*program| {
self.gc.queues.graphics.mu.lock(); destroy_queue.queueDestroyPipeline(program.pipeline);
defer self.gc.queues.graphics.mu.unlock();
// TODO: wait for async compute idle to
self.gc.device.queueWaitIdle(self.gc.queues.graphics.handle) catch @panic("Wait Idle failed");
self.gc.device.destroyPipeline(program.pipeline, null);
}, },
.texture => |*texture| { .texture => |*texture| {
_ = texture; // autofix destroy_queue.queueDestroyImageDescriptor(texture.descriptor_handle);
// gl.GL_ARB_bindless_texture.makeTextureHandleNonResidentARB(texture.handle); destroy_queue.queueDestroyImageView(texture.view);
// gl.deleteTextures(1, &texture.name); destroy_queue.queueDestroyImage(texture.image);
}, },
.scene => |*scene| { .scene => |*scene| {
self.allocator.free(scene.buf); self.allocator.free(scene.buf);
@ -1950,9 +1951,6 @@ const VertexBufferHeap = struct {
indices: Buffer, indices: Buffer,
pub fn init(allocator: std.mem.Allocator) !Self { pub fn init(allocator: std.mem.Allocator) !Self {
// 256 mega vertices :)
// memory usage for vertices (- indices) = n * 11 * 4
// 4096, 12 will take 704 mb for vertices
var vertex_buddy = try BuddyAllocator.init(allocator, 4096, 13); var vertex_buddy = try BuddyAllocator.init(allocator, 4096, 13);
errdefer vertex_buddy.deinit(); errdefer vertex_buddy.deinit();

View File

@ -8,6 +8,11 @@ const za = @import("zalgebra");
const Vec3 = za.Vec3; const Vec3 = za.Vec3;
const Mat4 = za.Mat4; const Mat4 = za.Mat4;
const common = @import("common.zig"); const common = @import("common.zig");
const render_common = @import("render_common.zig");
const MAX_FRAME_LAG = render_common.MAX_FRAME_LAG;
const DeferredDestroyQueue = render_common.DeferredDestroyQueue;
const FrameData = render_common.FrameData;
const RenderFrameState = render_common.RenderFrameState;
const Render2 = @This(); const Render2 = @This();
@ -29,7 +34,6 @@ pub const Camera = struct {
var default_camera: Camera = .{}; var default_camera: Camera = .{};
const MAX_FRAME_LAG = 3;
const PER_FRAME_ARENA_SIZE = 256 * common.MB; const PER_FRAME_ARENA_SIZE = 256 * common.MB;
frame_allocator: std.mem.Allocator, frame_allocator: std.mem.Allocator,
@ -40,13 +44,12 @@ command_pool: GraphicsContext.CommandPool,
vulkan_frame_arena: VulkanPerFrameArena, vulkan_frame_arena: VulkanPerFrameArena,
camera: *Camera = &default_camera, camera: *Camera = &default_camera,
frame: u32 = 0,
frame_data: [MAX_FRAME_LAG]FrameData = undefined,
// Global sampler to use for reading screen color in post processing // Global sampler to use for reading screen color in post processing
screen_color_sampler: vk.Sampler = .null_handle, screen_color_sampler: vk.Sampler = .null_handle,
screen_color_sampler_descriptor_handle: DescriptorManager.DescriptorHandle = .{}, screen_color_sampler_descriptor_handle: DescriptorManager.DescriptorHandle = .{},
frame_state: *RenderFrameState,
// Ring buffer/arena for per frame data // Ring buffer/arena for per frame data
pub const VulkanPerFrameArena = struct { pub const VulkanPerFrameArena = struct {
const Self = @This(); const Self = @This();
@ -204,7 +207,7 @@ pub const VulkanPerFrameArena = struct {
try device.bindBufferMemory(buffer, self.memory, out_addr.*); try device.bindBufferMemory(buffer, self.memory, out_addr.*);
frame.queueDestroyBuffer(buffer); frame.destroy_queue.queueDestroyBuffer(buffer);
return buffer; return buffer;
} }
@ -222,7 +225,7 @@ pub const VulkanPerFrameArena = struct {
try gc.device.bindImageMemory(image, self.memory, out_addr.*); try gc.device.bindImageMemory(image, self.memory, out_addr.*);
frame.queueDestroyImage(image); frame.destroy_queue.queueDestroyImage(image);
return image; return image;
} }
@ -232,7 +235,7 @@ pub const VulkanPerFrameArena = struct {
} }
}; };
pub fn init(self: *Render2, frame_allocator: std.mem.Allocator, gc: *GraphicsContext, descriptorman: *DescriptorManager, assetman: *AssetManager) !void { pub fn init(self: *Render2, frame_allocator: std.mem.Allocator, gc: *GraphicsContext, frame_state: *RenderFrameState, descriptorman: *DescriptorManager, assetman: *AssetManager) !void {
// Allocated in device local mem // Allocated in device local mem
const per_frame_upload_memory = try gc.device.allocateMemory(&.{ const per_frame_upload_memory = try gc.device.allocateMemory(&.{
.memory_type_index = gc.memory_config.gpu.type_index, .memory_type_index = gc.memory_config.gpu.type_index,
@ -245,6 +248,7 @@ pub fn init(self: *Render2, frame_allocator: std.mem.Allocator, gc: *GraphicsCon
.descriptorman = descriptorman, .descriptorman = descriptorman,
.assetman = assetman, .assetman = assetman,
.command_pool = try gc.queues.graphics.createCommandPool(.{ .reset_command_buffer_bit = true }), .command_pool = try gc.queues.graphics.createCommandPool(.{ .reset_command_buffer_bit = true }),
.frame_state = frame_state,
.vulkan_frame_arena = VulkanPerFrameArena{ .vulkan_frame_arena = VulkanPerFrameArena{
.memory_type_index = gc.memory_config.gpu.type_index, .memory_type_index = gc.memory_config.gpu.type_index,
.memory = per_frame_upload_memory, .memory = per_frame_upload_memory,
@ -273,9 +277,8 @@ pub fn init(self: *Render2, frame_allocator: std.mem.Allocator, gc: *GraphicsCon
}, null); }, null);
self.screen_color_sampler_descriptor_handle = self.descriptorman.sampler_descriptor_array.alloc(self.screen_color_sampler).handle; self.screen_color_sampler_descriptor_handle = self.descriptorman.sampler_descriptor_array.alloc(self.screen_color_sampler).handle;
for (0..MAX_FRAME_LAG) |i| { // TODO: Kinda strange to initialize here
self.frame_data[i] = try FrameData.init(gc, self.command_pool); try self.frame_state.init(gc, self.command_pool);
}
} }
pub const MainRenderTarget = struct { pub const MainRenderTarget = struct {
@ -370,15 +373,15 @@ fn allocateRenderTarget(self: *Render2) !MainRenderTarget {
fn createPerFrameBuffer(self: *Render2, usage: vk.BufferUsageFlags, size: u64, out_addr: *u64) !vk.Buffer { fn createPerFrameBuffer(self: *Render2, usage: vk.BufferUsageFlags, size: u64, out_addr: *u64) !vk.Buffer {
while (true) { while (true) {
if (self.vulkan_frame_arena.createBufferRaw(&self.frame_data[self.frame], self.gc.device, usage, size, out_addr)) |buffer| { if (self.vulkan_frame_arena.createBufferRaw(&self.frame_state.frame_data[self.frame_state.frame], self.gc.device, usage, size, out_addr)) |buffer| {
return buffer; return buffer;
} else |err| switch (err) { } else |err| switch (err) {
error.OverlapsPreviousFrame => { error.OverlapsPreviousFrame => {
const overlapped_frame = (self.frame + 1) % MAX_FRAME_LAG; const overlapped_frame = (self.frame_state.frame + 1) % MAX_FRAME_LAG;
std.debug.print("Vulkan Frame Allocator Overlapped frame {}, waiting for it to finish...\n", .{overlapped_frame}); std.debug.print("Vulkan Frame Allocator Overlapped frame {}, waiting for it to finish...\n", .{overlapped_frame});
try self.frame_data[overlapped_frame].waitForDrawAndReset(self); try self.frame_state.frame_data[overlapped_frame].waitForDrawAndReset(self.gc.device, self.descriptorman);
self.vulkan_frame_arena.frame_regions[overlapped_frame] = null; self.vulkan_frame_arena.frame_regions[overlapped_frame] = null;
}, },
else => return err, else => return err,
@ -388,15 +391,15 @@ fn createPerFrameBuffer(self: *Render2, usage: vk.BufferUsageFlags, size: u64, o
fn createPerFrameImage(self: *Render2, create_info: *const vk.ImageCreateInfo, out_addr: *u64) !vk.Image { fn createPerFrameImage(self: *Render2, create_info: *const vk.ImageCreateInfo, out_addr: *u64) !vk.Image {
while (true) { while (true) {
if (self.vulkan_frame_arena.createImageRaw(&self.frame_data[self.frame], self.gc, create_info, out_addr)) |image| { if (self.vulkan_frame_arena.createImageRaw(&self.frame_state.frame_data[self.frame_state.frame], self.gc, create_info, out_addr)) |image| {
return image; return image;
} else |err| switch (err) { } else |err| switch (err) {
error.OverlapsPreviousFrame => { error.OverlapsPreviousFrame => {
const overlapped_frame = (self.frame + 1) % MAX_FRAME_LAG; const overlapped_frame = (self.frame_state.frame + 1) % MAX_FRAME_LAG;
std.debug.print("Vulkan Frame Allocator Overlapped frame {}, waiting for it to finish...\n", .{overlapped_frame}); std.debug.print("Vulkan Frame Allocator Overlapped frame {}, waiting for it to finish...\n", .{overlapped_frame});
try self.frame_data[overlapped_frame].waitForDrawAndReset(self); try self.frame_state.frame_data[overlapped_frame].waitForDrawAndReset(self.gc.device, self.descriptorman);
self.vulkan_frame_arena.frame_regions[overlapped_frame] = null; self.vulkan_frame_arena.frame_regions[overlapped_frame] = null;
}, },
else => return err, else => return err,
@ -406,14 +409,14 @@ fn createPerFrameImage(self: *Render2, create_info: *const vk.ImageCreateInfo, o
fn createPerFrameImageView(self: *Render2, create_info: *const vk.ImageViewCreateInfo) !vk.ImageView { fn createPerFrameImageView(self: *Render2, create_info: *const vk.ImageViewCreateInfo) !vk.ImageView {
const view = try self.gc.device.createImageView(create_info, null); const view = try self.gc.device.createImageView(create_info, null);
self.frame_data[self.frame].queueDestroyImageView(view); self.frame_state.frame_data[self.frame_state.frame].destroy_queue.queueDestroyImageView(view);
return view; return view;
} }
fn createPerFrameImageDescriptor(self: *Render2, view: vk.ImageView, layout: vk.ImageLayout) *DescriptorManager.DescriptorT(DescriptorManager.SampledImageDescriptorData) { fn createPerFrameImageDescriptor(self: *Render2, view: vk.ImageView, layout: vk.ImageLayout) *DescriptorManager.DescriptorT(DescriptorManager.SampledImageDescriptorData) {
const result = self.descriptorman.image_descriptor_array_2d.alloc(.{ .view = view, .layout = layout }); const result = self.descriptorman.image_descriptor_array_2d.alloc(.{ .view = view, .layout = layout });
self.frame_data[self.frame].queueDestroyImageDescriptor(result.handle); self.frame_state.frame_data[self.frame_state.frame].destroy_queue.queueDestroyImageDescriptor(result.handle);
return result; return result;
} }
@ -424,13 +427,13 @@ fn pushConstants(self: *Render2, cmds: GraphicsContext.CommandBuffer, stage_flag
pub fn draw(self: *Render2) !void { pub fn draw(self: *Render2) !void {
const gc = self.gc; const gc = self.gc;
const device = gc.device; const device = gc.device;
const frame = &self.frame_data[self.frame]; const frame = &self.frame_state.frame_data[self.frame_state.frame];
try frame.waitForDrawAndReset(self); try frame.waitForDrawAndReset(self.gc.device, self.descriptorman);
const swapchain_image_index: u32 = try gc.acquireSwapchainImage(frame.acquire_swapchain_image); const swapchain_image_index: u32 = try gc.acquireSwapchainImage(frame.acquire_swapchain_image);
self.vulkan_frame_arena.startFrame(self.frame); self.vulkan_frame_arena.startFrame(self.frame_state.frame);
var global_buffer_addr: u64 = 0; var global_buffer_addr: u64 = 0;
const global_uniform_buffer_handle = try self.createPerFrameBuffer(.{ .uniform_buffer_bit = true, .transfer_dst_bit = true }, @sizeOf(GlobalUniform), &global_buffer_addr); const global_uniform_buffer_handle = try self.createPerFrameBuffer(.{ .uniform_buffer_bit = true, .transfer_dst_bit = true }, @sizeOf(GlobalUniform), &global_buffer_addr);
@ -684,7 +687,7 @@ pub fn draw(self: *Render2) !void {
.p_image_indices = &.{swapchain_image_index}, .p_image_indices = &.{swapchain_image_index},
}); });
self.frame = (self.frame + 1) % MAX_FRAME_LAG; self.frame_state.frame = (self.frame_state.frame + 1) % MAX_FRAME_LAG;
} }
fn uploadData(self: *Render2, cmds: GraphicsContext.CommandBuffer, dst: GraphicsContext.Buffer, dst_offset: usize, len: usize) !void { fn uploadData(self: *Render2, cmds: GraphicsContext.CommandBuffer, dst: GraphicsContext.Buffer, dst_offset: usize, len: usize) !void {
@ -702,93 +705,6 @@ fn uploadData(self: *Render2, cmds: GraphicsContext.CommandBuffer, dst: Graphics
self.upload_buffer_cursor += len; self.upload_buffer_cursor += len;
} }
// Per frame stuff
pub const FrameData = struct {
// Sync
acquire_swapchain_image: vk.Semaphore,
draw_sema: vk.Semaphore,
draw_fence: vk.Fence,
draw_submitted: bool = true,
command_buffer: GraphicsContext.CommandBuffer,
// Store references to per frame allocated objects here, they will be cleaned up after frame finished rendering
buffers: [1024]vk.Buffer = undefined,
buffer_count: u32 = 0,
images: [1024]vk.Image = undefined,
image_count: u32 = 0,
image_views: [1024]vk.ImageView = undefined,
image_view_count: u32 = 0,
image_descriptors: [1024]DescriptorManager.DescriptorHandle = undefined,
image_descriptor_count: u32 = 0,
pub fn init(gc: *GraphicsContext, command_pool: GraphicsContext.CommandPool) !FrameData {
return FrameData{
.acquire_swapchain_image = try gc.device.createSemaphore(&.{}, null),
.draw_sema = try gc.device.createSemaphore(&.{}, null),
.draw_fence = try gc.device.createFence(&.{ .flags = .{ .signaled_bit = true } }, null),
.command_buffer = try command_pool.allocateCommandBuffer(),
};
}
pub fn queueDestroyBuffer(self: *FrameData, buffer: vk.Buffer) void {
self.buffers[self.buffer_count] = buffer;
self.buffer_count += 1;
}
pub fn queueDestroyImage(self: *FrameData, image: vk.Image) void {
self.images[self.image_count] = image;
self.image_count += 1;
}
pub fn queueDestroyImageView(self: *FrameData, view: vk.ImageView) void {
self.image_views[self.image_view_count] = view;
self.image_view_count += 1;
}
pub fn queueDestroyImageDescriptor(self: *FrameData, descriptor: DescriptorManager.DescriptorHandle) void {
self.image_descriptors[self.image_descriptor_count] = descriptor;
self.image_descriptor_count += 1;
}
pub fn waitForDrawAndReset(self: *FrameData, render: *Render2) !void {
if (!self.draw_submitted) return;
self.draw_submitted = false;
const device = render.gc.device;
_ = try device.waitForFences(1, &.{self.draw_fence}, vk.TRUE, std.math.maxInt(u64));
try device.resetFences(1, &.{self.draw_fence});
try self.command_buffer.resetCommandBuffer(.{ .release_resources_bit = true });
for (self.image_descriptors[0..self.image_descriptor_count]) |desc| {
render.descriptorman.image_descriptor_array_2d.free(desc);
}
self.image_descriptor_count = 0;
try render.descriptorman.commitDescriptorArrayUpdates();
for (self.buffers[0..self.buffer_count]) |buf| {
device.destroyBuffer(buf, null);
}
self.buffer_count = 0;
for (self.image_views[0..self.image_view_count]) |view| {
device.destroyImageView(view, null);
}
self.image_view_count = 0;
for (self.images[0..self.image_count]) |img| {
device.destroyImage(img, null);
}
self.image_count = 0;
}
};
const GlobalUniform = extern struct { const GlobalUniform = extern struct {
pub const View = extern struct { pub const View = extern struct {
world_to_clip: Mat4, world_to_clip: Mat4,

View File

@ -176,11 +176,11 @@ export fn game_init(global_allocator: *std.mem.Allocator) void {
.global_allocator = global_allocator.*, .global_allocator = global_allocator.*,
.frame_fba = std.heap.FixedBufferAllocator.init(frame_arena_buffer), .frame_fba = std.heap.FixedBufferAllocator.init(frame_arena_buffer),
.descriptorman = DescriptorManager.init(&globals.g_init.gc, globals.g_mem.frame_fba.allocator()) catch @panic("DescriptorManager.init"), .descriptorman = DescriptorManager.init(&globals.g_init.gc, globals.g_mem.frame_fba.allocator()) catch @panic("DescriptorManager.init"),
.assetman = AssetManager.init(global_allocator.*, globals.g_mem.frame_fba.allocator(), &globals.g_init.gc, &globals.g_mem.descriptorman) catch @panic("AssetManager.init"), .assetman = AssetManager.init(global_allocator.*, globals.g_mem.frame_fba.allocator(), &globals.g_init.gc, &globals.g_mem.descriptorman, &globals.g_mem.frame_state) catch @panic("AssetManager.init"),
// .render = Render.init(global_allocator.*, globals.g_mem.frame_fba.allocator(), &globals.g_mem.assetman), // .render = Render.init(global_allocator.*, globals.g_mem.frame_fba.allocator(), &globals.g_mem.assetman),
.world = .{ .frame_arena = globals.g_mem.frame_fba.allocator() }, .world = .{ .frame_arena = globals.g_mem.frame_fba.allocator() },
}; };
globals.g_mem.render2.init(globals.g_mem.frame_fba.allocator(), &globals.g_init.gc, &globals.g_mem.descriptorman, &globals.g_mem.assetman) catch @panic("OOM"); globals.g_mem.render2.init(globals.g_mem.frame_fba.allocator(), &globals.g_init.gc, &globals.g_mem.frame_state, &globals.g_mem.descriptorman, &globals.g_mem.assetman) catch @panic("OOM");
globals.g_mem.render2.camera = &globals.g_mem.free_cam.camera; globals.g_mem.render2.camera = &globals.g_mem.free_cam.camera;
std.log.debug("actual ptr: {}, correct ptr {}", .{ globals.g_mem.assetman.frame_arena.ptr, globals.g_mem.frame_fba.allocator().ptr }); std.log.debug("actual ptr: {}, correct ptr {}", .{ globals.g_mem.assetman.frame_arena.ptr, globals.g_mem.frame_fba.allocator().ptr });
globals.g_assetman = &globals.g_mem.assetman; globals.g_assetman = &globals.g_mem.assetman;

View File

@ -4,6 +4,7 @@ const Render = @import("Render.zig");
const Render2 = @import("Render2.zig"); const Render2 = @import("Render2.zig");
const AssetManager = @import("AssetManager.zig"); const AssetManager = @import("AssetManager.zig");
const DescriptorManager = @import("DescriptorManager.zig"); const DescriptorManager = @import("DescriptorManager.zig");
const RenderFrameState = @import("render_common.zig").RenderFrameState;
const World = @import("entity.zig").World; const World = @import("entity.zig").World;
const GraphicsContext = @import("GraphicsContext.zig"); const GraphicsContext = @import("GraphicsContext.zig");
@ -37,6 +38,7 @@ pub const GameMemory = struct {
global_allocator: std.mem.Allocator, global_allocator: std.mem.Allocator,
frame_fba: std.heap.FixedBufferAllocator, frame_fba: std.heap.FixedBufferAllocator,
descriptorman: DescriptorManager, descriptorman: DescriptorManager,
frame_state: RenderFrameState = .{},
assetman: AssetManager, assetman: AssetManager,
render: Render = undefined, render: Render = undefined,
render2: Render2 = undefined, render2: Render2 = undefined,

149
src/render_common.zig Normal file
View File

@ -0,0 +1,149 @@
const std = @import("std");
const vk = @import("vk");
const GraphicsContext = @import("GraphicsContext.zig");
const DescriptorManager = @import("DescriptorManager.zig");
pub const MAX_FRAME_LAG = 3;
/// Destroy queue attached to a fence. Useful for freeing buffers and stuff after render has finished for example.
/// Call destroy only when fence is signaled
pub const DeferredDestroyQueue = struct {
const Self = @This();
fence: vk.Fence,
// TODO: replace with dynamic arrays?
buffers: [1024]vk.Buffer = undefined,
buffer_count: u32 = 0,
images: [1024]vk.Image = undefined,
image_count: u32 = 0,
image_views: [1024]vk.ImageView = undefined,
image_view_count: u32 = 0,
image_descriptors: [1024]DescriptorManager.DescriptorHandle = undefined,
image_descriptor_count: u32 = 0,
pipelines: [1024]vk.Pipeline = undefined,
pipeline_count: u32 = 0,
pub fn init(fence: vk.Fence) Self {
return Self{
.fence = fence,
};
}
pub fn queueDestroyBuffer(self: *Self, buffer: vk.Buffer) void {
self.buffers[self.buffer_count] = buffer;
self.buffer_count += 1;
}
pub fn queueDestroyImage(self: *Self, image: vk.Image) void {
self.images[self.image_count] = image;
self.image_count += 1;
}
pub fn queueDestroyImageView(self: *Self, view: vk.ImageView) void {
self.image_views[self.image_view_count] = view;
self.image_view_count += 1;
}
pub fn queueDestroyImageDescriptor(self: *Self, descriptor: DescriptorManager.DescriptorHandle) void {
self.image_descriptors[self.image_descriptor_count] = descriptor;
self.image_descriptor_count += 1;
}
pub fn queueDestroyPipeline(self: *Self, pipeline: vk.Pipeline) void {
self.pipelines[self.image_descriptor_count] = pipeline;
self.pipeline_count += 1;
}
pub fn isSignaled(self: *const Self, device: GraphicsContext.Device) !bool {
return (try device.getFenceStatus(self.fence)) == .success;
}
pub fn wait(self: *const Self, device: GraphicsContext.Device, timeout: u64) void {
_ = try device.waitForFences(1, &.{self.fence}, vk.TRUE, timeout);
}
pub fn destroy(self: *Self, device: GraphicsContext.Device, descriptorman: *DescriptorManager) !void {
std.debug.assert(try self.isSignaled(device)); // fence is not signaled yet, not safe to destroy
for (self.image_descriptors[0..self.image_descriptor_count]) |desc| {
descriptorman.image_descriptor_array_2d.free(desc);
}
self.image_descriptor_count = 0;
try descriptorman.commitDescriptorArrayUpdates();
for (self.buffers[0..self.buffer_count]) |buf| {
device.destroyBuffer(buf, null);
}
self.buffer_count = 0;
for (self.image_views[0..self.image_view_count]) |view| {
device.destroyImageView(view, null);
}
self.image_view_count = 0;
for (self.images[0..self.image_count]) |img| {
device.destroyImage(img, null);
}
self.image_count = 0;
for (self.pipelines[0..self.pipeline_count]) |pipeline| {
device.destroyPipeline(pipeline, null);
}
self.pipeline_count = 0;
}
};
// Per frame stuff
pub const FrameData = struct {
// Sync
acquire_swapchain_image: vk.Semaphore,
draw_sema: vk.Semaphore,
draw_fence: vk.Fence,
draw_submitted: bool = true,
destroy_queue: DeferredDestroyQueue,
command_buffer: GraphicsContext.CommandBuffer,
pub fn init(gc: *GraphicsContext, command_pool: GraphicsContext.CommandPool) !FrameData {
const fence = try gc.device.createFence(&.{ .flags = .{ .signaled_bit = true } }, null);
return FrameData{
.acquire_swapchain_image = try gc.device.createSemaphore(&.{}, null),
.draw_sema = try gc.device.createSemaphore(&.{}, null),
.draw_fence = fence,
.command_buffer = try command_pool.allocateCommandBuffer(),
.destroy_queue = DeferredDestroyQueue.init(fence),
};
}
pub fn waitForDrawAndReset(self: *FrameData, device: GraphicsContext.Device, descriptorman: *DescriptorManager) !void {
if (!self.draw_submitted) return;
self.draw_submitted = false;
_ = try device.waitForFences(1, &.{self.draw_fence}, vk.TRUE, std.math.maxInt(u64));
try self.destroy_queue.destroy(device, descriptorman);
try device.resetFences(1, &.{self.draw_fence});
try self.command_buffer.resetCommandBuffer(.{ .release_resources_bit = true });
}
};
pub const RenderFrameState = struct {
frame: u32 = 0,
frame_data: [MAX_FRAME_LAG]FrameData = undefined,
pub fn init(self: *RenderFrameState, gc: *GraphicsContext, command_pool: GraphicsContext.CommandPool) !void {
for (0..MAX_FRAME_LAG) |i| {
self.frame_data[i] = try FrameData.init(gc, command_pool);
}
}
};