diff --git a/src/AssetManager.zig b/src/AssetManager.zig index a1d6972..5755299 100644 --- a/src/AssetManager.zig +++ b/src/AssetManager.zig @@ -30,6 +30,9 @@ const tracy = @import("tracy"); const vk = @import("vk"); const GraphicsContext = @import("GraphicsContext.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 Handle = assets.Handle; @@ -64,6 +67,8 @@ texture_heap_memory: vk.DeviceMemory = .null_handle, vertex_heap: VertexBufferHeap, gc: *GraphicsContext, descriptorman: *DescriptorManager, +// A way to access destroy queue +frame_state: *RenderFrameState, // For uploads 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; 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"); @@ -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"), .gc = gc, .descriptorman = descriptorman, + .frame_state = frame_state, .queue = queue, .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 { + const destroy_queue = &self.frame_state.frame_data[self.frame_state.frame].destroy_queue; switch (asset.*) { .mesh => |*mesh| { self.vertex_heap.free(mesh.heap_handle); @@ -1881,18 +1888,12 @@ fn freeAsset(self: *AssetManager, asset: *LoadedAsset) void { self.allocator.free(shader.source); }, .shaderProgram => |*program| { - self.gc.queues.graphics.mu.lock(); - 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); + destroy_queue.queueDestroyPipeline(program.pipeline); }, .texture => |*texture| { - _ = texture; // autofix - // gl.GL_ARB_bindless_texture.makeTextureHandleNonResidentARB(texture.handle); - // gl.deleteTextures(1, &texture.name); + destroy_queue.queueDestroyImageDescriptor(texture.descriptor_handle); + destroy_queue.queueDestroyImageView(texture.view); + destroy_queue.queueDestroyImage(texture.image); }, .scene => |*scene| { self.allocator.free(scene.buf); @@ -1950,9 +1951,6 @@ const VertexBufferHeap = struct { indices: Buffer, 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); errdefer vertex_buddy.deinit(); diff --git a/src/Render2.zig b/src/Render2.zig index 1c614ae..3995c7f 100644 --- a/src/Render2.zig +++ b/src/Render2.zig @@ -8,6 +8,11 @@ const za = @import("zalgebra"); const Vec3 = za.Vec3; const Mat4 = za.Mat4; 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(); @@ -29,7 +34,6 @@ pub const Camera = struct { var default_camera: Camera = .{}; -const MAX_FRAME_LAG = 3; const PER_FRAME_ARENA_SIZE = 256 * common.MB; frame_allocator: std.mem.Allocator, @@ -40,13 +44,12 @@ command_pool: GraphicsContext.CommandPool, vulkan_frame_arena: VulkanPerFrameArena, 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 screen_color_sampler: vk.Sampler = .null_handle, screen_color_sampler_descriptor_handle: DescriptorManager.DescriptorHandle = .{}, +frame_state: *RenderFrameState, + // Ring buffer/arena for per frame data pub const VulkanPerFrameArena = struct { const Self = @This(); @@ -204,7 +207,7 @@ pub const VulkanPerFrameArena = struct { try device.bindBufferMemory(buffer, self.memory, out_addr.*); - frame.queueDestroyBuffer(buffer); + frame.destroy_queue.queueDestroyBuffer(buffer); return buffer; } @@ -222,7 +225,7 @@ pub const VulkanPerFrameArena = struct { try gc.device.bindImageMemory(image, self.memory, out_addr.*); - frame.queueDestroyImage(image); + frame.destroy_queue.queueDestroyImage(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 const per_frame_upload_memory = try gc.device.allocateMemory(&.{ .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, .assetman = assetman, .command_pool = try gc.queues.graphics.createCommandPool(.{ .reset_command_buffer_bit = true }), + .frame_state = frame_state, .vulkan_frame_arena = VulkanPerFrameArena{ .memory_type_index = gc.memory_config.gpu.type_index, .memory = per_frame_upload_memory, @@ -273,9 +277,8 @@ pub fn init(self: *Render2, frame_allocator: std.mem.Allocator, gc: *GraphicsCon }, null); self.screen_color_sampler_descriptor_handle = self.descriptorman.sampler_descriptor_array.alloc(self.screen_color_sampler).handle; - for (0..MAX_FRAME_LAG) |i| { - self.frame_data[i] = try FrameData.init(gc, self.command_pool); - } + // TODO: Kinda strange to initialize here + try self.frame_state.init(gc, self.command_pool); } 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 { 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; } else |err| switch (err) { 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}); - 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; }, 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 { 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; } else |err| switch (err) { 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}); - 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; }, 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 { 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; } 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 }); - self.frame_data[self.frame].queueDestroyImageDescriptor(result.handle); + self.frame_state.frame_data[self.frame_state.frame].destroy_queue.queueDestroyImageDescriptor(result.handle); return result; } @@ -424,13 +427,13 @@ fn pushConstants(self: *Render2, cmds: GraphicsContext.CommandBuffer, stage_flag pub fn draw(self: *Render2) !void { const gc = self.gc; 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); - self.vulkan_frame_arena.startFrame(self.frame); + self.vulkan_frame_arena.startFrame(self.frame_state.frame); 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); @@ -684,7 +687,7 @@ pub fn draw(self: *Render2) !void { .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 { @@ -702,93 +705,6 @@ fn uploadData(self: *Render2, cmds: GraphicsContext.CommandBuffer, dst: Graphics 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 { pub const View = extern struct { world_to_clip: Mat4, diff --git a/src/game.zig b/src/game.zig index 82a6fe1..bfe363d 100644 --- a/src/game.zig +++ b/src/game.zig @@ -176,11 +176,11 @@ export fn game_init(global_allocator: *std.mem.Allocator) void { .global_allocator = global_allocator.*, .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"), - .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), .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; 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; diff --git a/src/globals.zig b/src/globals.zig index 82c3b66..aedbdc5 100644 --- a/src/globals.zig +++ b/src/globals.zig @@ -4,6 +4,7 @@ const Render = @import("Render.zig"); const Render2 = @import("Render2.zig"); const AssetManager = @import("AssetManager.zig"); const DescriptorManager = @import("DescriptorManager.zig"); +const RenderFrameState = @import("render_common.zig").RenderFrameState; const World = @import("entity.zig").World; const GraphicsContext = @import("GraphicsContext.zig"); @@ -37,6 +38,7 @@ pub const GameMemory = struct { global_allocator: std.mem.Allocator, frame_fba: std.heap.FixedBufferAllocator, descriptorman: DescriptorManager, + frame_state: RenderFrameState = .{}, assetman: AssetManager, render: Render = undefined, render2: Render2 = undefined, diff --git a/src/render_common.zig b/src/render_common.zig new file mode 100644 index 0000000..7bdef02 --- /dev/null +++ b/src/render_common.zig @@ -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); + } + } +};