From 2541dee18da17f0976b9d8776fb62a3e122202bf Mon Sep 17 00:00:00 2001 From: Szymon Nowakowski Date: Fri, 28 Nov 2025 17:09:37 +0100 Subject: [PATCH] CommandBuffer wrapper --- src/Game.zig | 90 ++++++------ src/engine/CommandBuffer.zig | 272 +++++++++++++++++++++++++++++++++++ src/engine/Engine.zig | 98 +++---------- src/engine/GenericBuffer.zig | 41 +++--- src/engine/QueueType.zig | 5 + src/engine/Swapchain.zig | 63 ++++---- src/engine/Texture.zig | 173 +++++++++------------- src/engine/Transient.zig | 4 + src/engine/WaitSemaphore.zig | 4 + 9 files changed, 461 insertions(+), 289 deletions(-) create mode 100644 src/engine/CommandBuffer.zig create mode 100644 src/engine/QueueType.zig create mode 100644 src/engine/Transient.zig create mode 100644 src/engine/WaitSemaphore.zig diff --git a/src/Game.zig b/src/Game.zig index 32ff80b..da4a217 100644 --- a/src/Game.zig +++ b/src/Game.zig @@ -8,6 +8,7 @@ const math = @import("math.zig"); const Materials = @import("assets/Materials.zig"); const Textures = @import("assets/Textures.zig"); +const CommandBuffer = @import("engine/CommandBuffer.zig").CommandBuffer; const Engine = @import("engine/Engine.zig"); const GenericBuffer = @import("engine/GenericBuffer.zig").GenericBuffer; const StagingBuffer = @import("engine/StagingBuffer.zig"); @@ -115,7 +116,7 @@ index_buffer: IndexBuffer, global_uniforms: GlobalUniformsBuffer, global_uniforms_staging_buffer: StagingBuffer, -global_uniforms_transfer_command_buffer: vk.CommandBuffer, +global_uniforms_transfer_command_buffer: CommandBuffer(.graphics, .persistent), global_uniforms_transfer_semaphores: []vk.Semaphore, point_lights: PointLightBuffer, directional_lights: DirectionalLightBuffer, @@ -305,14 +306,13 @@ pub fn init(allocator: std.mem.Allocator, engine: *Engine, swapchain: *Swapchain }); errdefer global_uniforms_staging_buffer.deinit(engine); - const global_uniforms_transfer_command_buffer = try engine.allocateTransferCommandBuffer(); - errdefer engine.freeTransferCommandBuffer(global_uniforms_transfer_command_buffer); + var global_uniforms_transfer_command_buffer: CommandBuffer(.graphics, .persistent) = try .init(engine); + errdefer global_uniforms_transfer_command_buffer.deinit(engine); - try global_uniforms_transfer_command_buffer.beginCommandBuffer(&.{}); + try global_uniforms_transfer_command_buffer.beginCommandBuffer(.{}); global_uniforms_transfer_command_buffer.copyBuffer( global_uniforms_staging_buffer.buffer, global_uniforms.buffer, - 1, &.{ .{ .src_offset = 0, @@ -685,7 +685,7 @@ pub fn init(allocator: std.mem.Allocator, engine: *Engine, swapchain: *Swapchain .global_uniforms = global_uniforms, .global_uniforms_staging_buffer = global_uniforms_staging_buffer, - .global_uniforms_transfer_command_buffer = global_uniforms_transfer_command_buffer.handle, + .global_uniforms_transfer_command_buffer = global_uniforms_transfer_command_buffer, .global_uniforms_transfer_semaphores = global_uniforms_transfer_semaphores, .point_lights = point_lights, .directional_lights = directional_lights, @@ -706,6 +706,7 @@ pub fn deinit(self: *Game) void { self.global_uniforms.deinit(self.engine); self.global_uniforms_staging_buffer.deinit(self.engine); + self.global_uniforms_transfer_command_buffer.deinit(self.engine); self.point_lights.deinit(self.engine); self.directional_lights.deinit(self.engine); self.object_uniforms.deinit(self.engine); @@ -782,16 +783,9 @@ pub fn update(self: *Game, dt: f32) void { @memcpy(staging_memory, std.mem.asBytes(&global_uniforms_data)); self.global_uniforms_staging_buffer.unmap(self.engine); - const submits = [_]vk.SubmitInfo{ - .{ - .command_buffer_count = 1, - .p_command_buffers = @ptrCast(&self.global_uniforms_transfer_command_buffer), - .signal_semaphore_count = 1, - .p_signal_semaphores = @ptrCast(&self.global_uniforms_transfer_semaphores[self.swapchain.image_index]), - }, - }; - - self.engine.device.queueSubmit(self.engine.transfer_queue.handle, submits.len, &submits, .null_handle) catch |err| { + self.global_uniforms_transfer_command_buffer.submit(self.engine, .{ + .signal_semaphores = &.{self.global_uniforms_transfer_semaphores[self.swapchain.image_index]}, + }) catch |err| { std.log.err("Failed to submit global uniforms transfer: {s}", .{@errorName(err)}); @panic("Frame update failed"); }; @@ -877,41 +871,37 @@ pub fn onMouseUp(self: *Game, button: glfw.MouseButton, mods: glfw.Mods) void { } fn render(self: *Game) !void { - const engine = self.engine; const extent = self.swapchain.extent; - const command_buffer = try engine.allocateGraphicsCommandBuffer(); + const command_buffer: CommandBuffer(.graphics, .transient) = try .init(self.engine); // NOTE Do not free command buffer yet - try command_buffer.beginCommandBuffer(&.{ .flags = .{ .one_time_submit_bit = true } }); + try command_buffer.beginCommandBuffer(.{}); { - const clear_values = [_]vk.ClearValue{ - .{ - .color = .{ - .float_32 = .{ 0, 0, 0, 0 }, - }, - }, - .{ - .depth_stencil = .{ - .depth = 0, - .stencil = 0, - }, - }, - }; - - command_buffer.beginRenderPass(&.{ + command_buffer.beginRenderPass(.{ .render_pass = self.swapchain.render_pass, .framebuffer = self.swapchain.swapchain_images[self.swapchain.image_index].framebuffer, .render_area = .{ .offset = .{ .x = 0, .y = 0 }, .extent = extent, }, - .clear_value_count = clear_values.len, - .p_clear_values = &clear_values, + .clear_values = &.{ + .{ + .color = .{ + .float_32 = .{ 0, 0, 0, 0 }, + }, + }, + .{ + .depth_stencil = .{ + .depth = 0, + .stencil = 0, + }, + }, + }, }, .@"inline"); defer command_buffer.endRenderPass(); - const viewports = [_]vk.Viewport{ + command_buffer.setViewport(0, &.{ .{ .x = 0, .y = 0, @@ -920,29 +910,33 @@ fn render(self: *Game) !void { .min_depth = 0, .max_depth = 1, }, - }; - command_buffer.setViewport(0, viewports.len, &viewports); - - const scissors = [_]vk.Rect2D{ + }); + command_buffer.setScissor(0, &.{ .{ .offset = .{ .x = 0, .y = 0 }, .extent = extent, }, - }; - command_buffer.setScissor(0, scissors.len, &scissors); + }); command_buffer.bindPipeline(.graphics, self.pipeline); - - command_buffer.bindVertexBuffers(0, 1, @ptrCast(&self.vertex_buffer.buffer), &.{0}); + try command_buffer.bindVertexBuffers(0, &.{ + .{ + .buffer = self.vertex_buffer.buffer, + .offset = 0, + }, + }); command_buffer.bindIndexBuffer(self.index_buffer.buffer, 0, .uint16); - command_buffer.bindDescriptorSets(.graphics, self.pipeline_layout, 0, self.descriptor_sets.len, &self.descriptor_sets, 0, null); + command_buffer.bindDescriptorSets(.graphics, self.pipeline_layout, 0, &self.descriptor_sets, &.{}); - command_buffer.drawIndexed(@intCast(self.index_buffer.array_capacity), self.object_count, 0, 0, 0); + command_buffer.drawIndexed(.{ + .index_count = self.index_buffer.array_capacity, + .instance_count = self.object_count, + }); } try command_buffer.endCommandBuffer(); const res = try self.swapchain.present(self.engine, .{ - .command_buffer = command_buffer.handle, + .command_buffer = command_buffer, .wait_semaphores = &.{ .{ .semaphore = self.global_uniforms_transfer_semaphores[self.swapchain.image_index], diff --git a/src/engine/CommandBuffer.zig b/src/engine/CommandBuffer.zig new file mode 100644 index 0000000..3f78284 --- /dev/null +++ b/src/engine/CommandBuffer.zig @@ -0,0 +1,272 @@ +const std = @import("std"); + +const vk = @import("vulkan"); + +const Engine = @import("Engine.zig"); +const QueueType = @import("QueueType.zig").QueueType; +const Transient = @import("Transient.zig").Transient; +const WaitSemaphore = @import("WaitSemaphore.zig"); + +pub const DrawIndexed = struct { + index_count: u32, + instance_count: u32 = 1, + first_index: u32 = 0, + vertex_offset: i32 = 0, + first_instance: u32 = 0, +}; + +pub const PipelineBarrier = struct { + src_stage_mask: vk.PipelineStageFlags, + dst_stage_mask: vk.PipelineStageFlags, + dependency_flags: vk.DependencyFlags = .{}, + memory_barriers: []const vk.MemoryBarrier = &.{}, + buffer_memory_barriers: []const vk.BufferMemoryBarrier = &.{}, + image_memory_barriers: []const vk.ImageMemoryBarrier = &.{}, +}; + +pub const RenderPassBeginInfo = struct { + render_pass: vk.RenderPass, + framebuffer: vk.Framebuffer, + render_area: vk.Rect2D, + clear_values: []const vk.ClearValue = &.{}, +}; + +pub const SubmitInfo = struct { + wait_semaphores: []const WaitSemaphore = &.{}, + signal_semaphores: []const vk.Semaphore = &.{}, + fence: vk.Fence = .null_handle, +}; + +pub const VertexBufferBinding = struct { + buffer: vk.Buffer, + offset: usize, +}; + +pub fn CommandBuffer(comptime queue_type: QueueType, comptime transient: Transient) type { + return struct { + const Self = @This(); + + proxy: vk.CommandBufferProxy, + allocator: std.mem.Allocator, + + pub fn init(engine: *const Engine) !Self { + const handle = try allocateCommandBuffer(engine, queue_type, transient); + const proxy: vk.CommandBufferProxy = .init(handle, engine.device.wrapper); + const allocator = engine.vk_allocator.allocator; + return .{ .proxy = proxy, .allocator = allocator }; + } + + pub fn deinit(self: *Self, engine: *const Engine) void { + freeCommandBuffer(engine, queue_type, transient, self.proxy.handle); + self.* = undefined; + } + + pub fn submit(self: Self, engine: *const Engine, submit_info: SubmitInfo) !void { + try submitCommandBuffer(engine, queue_type, self.proxy.handle, submit_info); + } + + pub const CommandBufferBeginInfo = struct { + flags: vk.CommandBufferUsageFlags = .{ + .one_time_submit_bit = switch (transient) { + .persistent => false, + .transient => true, + }, + }, + inheritance_info: ?vk.CommandBufferInheritanceInfo = null, + }; + + pub fn beginCommandBuffer(self: Self, begin_info: CommandBufferBeginInfo) !void { + try self.proxy.beginCommandBuffer(&.{ + .flags = begin_info.flags, + .p_inheritance_info = if (begin_info.inheritance_info) |*x| x else null, + }); + } + + pub inline fn beginRenderPass(self: Self, render_pass_begin: RenderPassBeginInfo, contents: vk.SubpassContents) void { + self.proxy.beginRenderPass(&.{ + .render_pass = render_pass_begin.render_pass, + .framebuffer = render_pass_begin.framebuffer, + .render_area = render_pass_begin.render_area, + .clear_value_count = @intCast(render_pass_begin.clear_values.len), + .p_clear_values = render_pass_begin.clear_values.ptr, + }, contents); + } + + pub inline fn bindDescriptorSets( + self: Self, + pipeline_bind_point: vk.PipelineBindPoint, + layout: vk.PipelineLayout, + first_set: u32, + descriptor_sets: []const vk.DescriptorSet, + dynamic_offsets: []const u32, + ) void { + self.proxy.bindDescriptorSets( + pipeline_bind_point, + layout, + first_set, + @intCast(descriptor_sets.len), + descriptor_sets.ptr, + @intCast(dynamic_offsets.len), + dynamic_offsets.ptr, + ); + } + + pub inline fn bindPipeline(self: Self, pipeline_bind_point: vk.PipelineBindPoint, pipeline: vk.Pipeline) void { + self.proxy.bindPipeline(pipeline_bind_point, pipeline); + } + + pub inline fn bindIndexBuffer(self: Self, buffer: vk.Buffer, offset: u64, index_type: vk.IndexType) void { + self.proxy.bindIndexBuffer(buffer, offset, index_type); + } + + pub inline fn bindVertexBuffers(self: Self, first_binding: u32, bindings: []const VertexBufferBinding) !void { + var vertex_buffer_bindings = std.MultiArrayList(VertexBufferBinding){}; + defer vertex_buffer_bindings.deinit(self.allocator); + try vertex_buffer_bindings.ensureTotalCapacity(self.allocator, bindings.len); + + for (bindings) |binding| { + vertex_buffer_bindings.appendAssumeCapacity(binding); + } + + self.proxy.bindVertexBuffers( + first_binding, + @intCast(vertex_buffer_bindings.len), + vertex_buffer_bindings.items(.buffer).ptr, + vertex_buffer_bindings.items(.offset).ptr, + ); + } + + pub inline fn drawIndexed(self: Self, draw_indexed: DrawIndexed) void { + self.proxy.drawIndexed( + draw_indexed.index_count, + draw_indexed.instance_count, + draw_indexed.first_index, + draw_indexed.vertex_offset, + draw_indexed.first_instance, + ); + } + + pub inline fn endCommandBuffer(self: Self) !void { + try self.proxy.endCommandBuffer(); + } + + pub inline fn endRenderPass(self: Self) void { + self.proxy.endRenderPass(); + } + + pub inline fn copyBuffer( + self: Self, + src_buffer: vk.Buffer, + dst_buffer: vk.Buffer, + regions: []const vk.BufferCopy, + ) void { + self.proxy.copyBuffer(src_buffer, dst_buffer, @intCast(regions.len), regions.ptr); + } + + pub inline fn copyBufferToImage( + self: Self, + src_buffer: vk.Buffer, + dst_image: vk.Image, + dst_image_layout: vk.ImageLayout, + regions: []const vk.BufferImageCopy, + ) void { + self.proxy.copyBufferToImage(src_buffer, dst_image, dst_image_layout, @intCast(regions.len), regions.ptr); + } + + pub inline fn pipelineBarrier(self: Self, barrier: PipelineBarrier) void { + self.proxy.pipelineBarrier( + barrier.src_stage_mask, + barrier.dst_stage_mask, + barrier.dependency_flags, + @intCast(barrier.memory_barriers.len), + barrier.memory_barriers.ptr, + @intCast(barrier.buffer_memory_barriers.len), + barrier.buffer_memory_barriers.ptr, + @intCast(barrier.image_memory_barriers.len), + barrier.image_memory_barriers.ptr, + ); + } + + pub inline fn setScissor(self: Self, first_scissor: u32, scissors: []const vk.Rect2D) void { + self.proxy.setScissor(first_scissor, @intCast(scissors.len), scissors.ptr); + } + + pub inline fn setViewport(self: Self, first_viewport: u32, viewports: []const vk.Viewport) void { + self.proxy.setViewport(first_viewport, @intCast(viewports.len), viewports.ptr); + } + }; +} + +inline fn resolveCommandPool(self: *const Engine, comptime queue_type: QueueType, comptime transient: Transient) vk.CommandPool { + return switch (queue_type) { + .graphics => switch (transient) { + .persistent => self.graphics_command_pool, + .transient => self.transient_graphics_command_pool, + }, + .compute => switch (transient) { + .persistent => self.compute_command_pool, + .transient => self.transient_compute_command_pool, + }, + .transfer => switch (transient) { + .persistent => self.transfer_command_pool, + .transient => self.transient_transfer_command_pool, + }, + }; +} + +fn allocateCommandBuffer(engine: *const Engine, comptime queue_type: QueueType, comptime transient: Transient) !vk.CommandBuffer { + const command_pool = resolveCommandPool(engine, queue_type, transient); + + var command_buffers: [1]vk.CommandBuffer = undefined; + try engine.device.allocateCommandBuffers(&.{ + .command_pool = command_pool, + .level = .primary, + .command_buffer_count = command_buffers.len, + }, &command_buffers); + + return command_buffers[0]; +} + +fn freeCommandBuffer(engine: *const Engine, comptime queue_type: QueueType, comptime transient: Transient, command_buffer: vk.CommandBuffer) void { + const command_pool = resolveCommandPool(engine, queue_type, transient); + const command_buffers = [_]vk.CommandBuffer{command_buffer}; + + engine.device.freeCommandBuffers(command_pool, command_buffers.len, &command_buffers); +} + +fn submitCommandBuffer( + engine: *const Engine, + comptime queue_type: QueueType, + command_buffer: vk.CommandBuffer, + submit_info: SubmitInfo, +) !void { + const queue = switch (queue_type) { + .graphics => engine.graphics_queue.handle, + .compute => engine.compute_queue.handle, + .transfer => engine.transfer_queue.handle, + }; + + const command_buffers = [_]vk.CommandBuffer{command_buffer}; + + var wait_semaphores = std.MultiArrayList(WaitSemaphore){}; + defer wait_semaphores.deinit(engine.vk_allocator.allocator); + try wait_semaphores.ensureTotalCapacity(engine.vk_allocator.allocator, submit_info.wait_semaphores.len); + + for (submit_info.wait_semaphores) |wait_semaphore| { + wait_semaphores.appendAssumeCapacity(wait_semaphore); + } + + const submits = [_]vk.SubmitInfo{ + .{ + .wait_semaphore_count = @intCast(wait_semaphores.len), + .p_wait_semaphores = wait_semaphores.items(.semaphore).ptr, + .p_wait_dst_stage_mask = wait_semaphores.items(.stage_flags).ptr, + .command_buffer_count = command_buffers.len, + .p_command_buffers = &command_buffers, + .signal_semaphore_count = @intCast(submit_info.signal_semaphores.len), + .p_signal_semaphores = submit_info.signal_semaphores.ptr, + }, + }; + + try engine.device.queueSubmit(queue, submits.len, &submits, submit_info.fence); +} diff --git a/src/engine/Engine.zig b/src/engine/Engine.zig index 8ad5a0e..205439e 100644 --- a/src/engine/Engine.zig +++ b/src/engine/Engine.zig @@ -6,6 +6,7 @@ const glfw = @import("zglfw"); const vk = @import("vulkan"); const Queue = @import("Queue.zig"); +const QueueType = @import("QueueType.zig").QueueType; const VkAllocator = @import("VkAllocator.zig"); pub const Mode = union(enum) { @@ -15,18 +16,6 @@ pub const Mode = union(enum) { surface: vk.SurfaceKHR, presentation_queue: Queue, }, - - pub fn deinit(self: *Mode, instance: vk.InstanceProxy, vk_allocator: *VkAllocator) void { - std.log.debug("Deinitializing {*} with InstanceProxy@{{{X},{*}}} and {*}", .{ self, instance.handle, instance.wrapper, vk_allocator }); - switch (self.*) { - .headless => {}, - .surface => |x| { - instance.destroySurfaceKHR(x.surface, &vk_allocator.interface); - }, - } - - self.* = undefined; - } }; pub const ModeTag = std.meta.Tag(Mode); @@ -104,6 +93,9 @@ transfer_queue: Queue, graphics_command_pool: vk.CommandPool, compute_command_pool: vk.CommandPool, transfer_command_pool: vk.CommandPool, + +transient_graphics_command_pool: vk.CommandPool, +transient_compute_command_pool: vk.CommandPool, transient_transfer_command_pool: vk.CommandPool, prng_ptr: *std.Random.Pcg, @@ -337,6 +329,18 @@ pub fn init(allocator: std.mem.Allocator, maybe_window: ?*glfw.Window) !Engine { }, &vk_allocator.interface); errdefer device.destroyCommandPool(transfer_command_pool, &vk_allocator.interface); + const transient_graphics_command_pool = try device.createCommandPool(&.{ + .flags = .{ .transient_bit = true }, + .queue_family_index = queue_allocations.graphics_queue.family, + }, &vk_allocator.interface); + errdefer device.destroyCommandPool(transient_graphics_command_pool, &vk_allocator.interface); + + const transient_compute_command_pool = try device.createCommandPool(&.{ + .flags = .{ .transient_bit = true }, + .queue_family_index = queue_allocations.compute_queue.family, + }, &vk_allocator.interface); + errdefer device.destroyCommandPool(transient_compute_command_pool, &vk_allocator.interface); + const transient_transfer_command_pool = try device.createCommandPool(&.{ .flags = .{ .transient_bit = true }, .queue_family_index = queue_allocations.transfer_queue.family, @@ -383,6 +387,9 @@ pub fn init(allocator: std.mem.Allocator, maybe_window: ?*glfw.Window) !Engine { .graphics_command_pool = graphics_command_pool, .compute_command_pool = compute_command_pool, .transfer_command_pool = transfer_command_pool, + + .transient_graphics_command_pool = transient_graphics_command_pool, + .transient_compute_command_pool = transient_compute_command_pool, .transient_transfer_command_pool = transient_transfer_command_pool, .prng_ptr = prng_ptr, @@ -400,6 +407,9 @@ pub fn deinit(self: *Engine) void { self.device.destroyCommandPool(self.graphics_command_pool, &self.vk_allocator.interface); self.device.destroyCommandPool(self.compute_command_pool, &self.vk_allocator.interface); self.device.destroyCommandPool(self.transfer_command_pool, &self.vk_allocator.interface); + + self.device.destroyCommandPool(self.transient_graphics_command_pool, &self.vk_allocator.interface); + self.device.destroyCommandPool(self.transient_compute_command_pool, &self.vk_allocator.interface); self.device.destroyCommandPool(self.transient_transfer_command_pool, &self.vk_allocator.interface); self.device.destroyDevice(&self.vk_allocator.interface); @@ -437,70 +447,6 @@ pub fn allocate(self: *const Engine, memory_requirements: vk.MemoryRequirements, return error.NoSuitableMemoryType; } -pub fn allocateTransientTransferCommandBuffer(self: *const Engine) !vk.CommandBufferProxy { - var command_buffer: vk.CommandBuffer = undefined; - try self.device.allocateCommandBuffers(&.{ - .command_pool = self.transient_transfer_command_pool, - .level = .primary, - .command_buffer_count = 1, - }, @ptrCast(&command_buffer)); - return .init(command_buffer, self.device.wrapper); -} - -pub fn allocateTransferCommandBuffer(self: *const Engine) !vk.CommandBufferProxy { - var command_buffer: vk.CommandBuffer = undefined; - try self.device.allocateCommandBuffers(&.{ - .command_pool = self.transfer_command_pool, - .level = .primary, - .command_buffer_count = 1, - }, @ptrCast(&command_buffer)); - return .init(command_buffer, self.device.wrapper); -} - -pub fn allocateGraphicsCommandBuffer(self: *const Engine) !vk.CommandBufferProxy { - var command_buffer: vk.CommandBuffer = undefined; - try self.device.allocateCommandBuffers(&.{ - .command_pool = self.graphics_command_pool, - .level = .primary, - .command_buffer_count = 1, - }, @ptrCast(&command_buffer)); - return .init(command_buffer, self.device.wrapper); -} - -pub fn freeTransferCommandBuffer(self: *const Engine, command_buffer: vk.CommandBufferProxy) void { - self.device.freeCommandBuffers(self.transfer_command_pool, 1, @ptrCast(&command_buffer)); -} - -pub fn freeTransientTransferCommandBuffer(self: *const Engine, command_buffer: vk.CommandBufferProxy) void { - self.device.freeCommandBuffers(self.transient_transfer_command_pool, 1, @ptrCast(&command_buffer)); -} - -pub fn freeGraphicsCommandBuffer(self: *const Engine, command_buffer: vk.CommandBufferProxy) void { - self.device.freeCommandBuffers(self.graphics_command_pool, 1, @ptrCast(&command_buffer)); -} - -pub fn submitTransferCommandBuffer(self: *const Engine, command_buffer: vk.CommandBufferProxy, fence: vk.Fence) !void { - const submits = [_]vk.SubmitInfo{ - .{ - .command_buffer_count = 1, - .p_command_buffers = @ptrCast(&command_buffer.handle), - }, - }; - - try self.device.queueSubmit(self.transfer_queue.handle, submits.len, &submits, fence); -} - -pub fn submitGraphicsCommandBuffer(self: *const Engine, command_buffer: vk.CommandBufferProxy, fence: vk.Fence) !void { - const submits = [_]vk.SubmitInfo{ - .{ - .command_buffer_count = 1, - .p_command_buffers = @ptrCast(&command_buffer.handle), - }, - }; - - try self.device.queueSubmit(self.graphics_queue.handle, submits.len, &submits, fence); -} - fn debugUtilsMessengerCallback( severity: vk.DebugUtilsMessageSeverityFlagsEXT, message_type: vk.DebugUtilsMessageTypeFlagsEXT, diff --git a/src/engine/GenericBuffer.zig b/src/engine/GenericBuffer.zig index 12f9c44..65a5821 100644 --- a/src/engine/GenericBuffer.zig +++ b/src/engine/GenericBuffer.zig @@ -2,6 +2,7 @@ const std = @import("std"); const vk = @import("vulkan"); +const CommandBuffer = @import("CommandBuffer.zig").CommandBuffer; const Engine = @import("Engine.zig"); const StagingBuffer = @import("StagingBuffer.zig"); const TargetQueue = @import("TargetQueue.zig").TargetQueue; @@ -146,22 +147,17 @@ pub fn GenericBuffer(comptime Header: type, comptime Element: type) type { @memcpy(staging_memory[array_write_offset..write_size], std.mem.sliceAsBytes(write_info.elements)); staging_buffer.unmap(engine); - const command_buffer = try engine.allocateTransientTransferCommandBuffer(); - defer engine.freeTransientTransferCommandBuffer(command_buffer); + var command_buffer: CommandBuffer(.transfer, .transient) = try .init(engine); + defer command_buffer.deinit(engine); - try command_buffer.beginCommandBuffer(&.{ .flags = .{ .one_time_submit_bit = true } }); - command_buffer.copyBuffer( - staging_buffer.buffer, - self.buffer, - @intCast(regions.items.len), - regions.items.ptr, - ); + try command_buffer.beginCommandBuffer(.{}); + command_buffer.copyBuffer(staging_buffer.buffer, self.buffer, regions.items); try command_buffer.endCommandBuffer(); const fence = try engine.createFence(.{}); defer engine.destroyFence(fence); - try engine.submitTransferCommandBuffer(command_buffer, fence); + try command_buffer.submit(engine, .{ .fence = fence }); try engine.waitForFence(fence); } @@ -172,14 +168,6 @@ pub fn GenericBuffer(comptime Header: type, comptime Element: type) type { const data_size = std.math.cast(u32, data.len) orelse return error.Overflow; std.debug.assert(data_offset + data_size <= size); - const regions = [_]vk.BufferCopy{ - .{ - .src_offset = 0, - .dst_offset = data_offset, - .size = data_size, - }, - }; - var staging_buffer = try StagingBuffer.init(engine, .{ .target_queue = self.target_queue, .capacity = data_size, @@ -190,22 +178,27 @@ pub fn GenericBuffer(comptime Header: type, comptime Element: type) type { @memcpy(staging_memory, data); staging_buffer.unmap(engine); - const command_buffer = try engine.allocateTransientTransferCommandBuffer(); - defer engine.freeTransientTransferCommandBuffer(command_buffer); + const command_buffer: CommandBuffer(.tranfer, .transient) = try .init(engine); + defer command_buffer.deinit(engine); - try command_buffer.beginCommandBuffer(&.{ .flags = .{ .one_time_submit_bit = true } }); + try command_buffer.beginCommandBuffer(.{}); command_buffer.copyBuffer( staging_buffer.buffer, self.buffer, - @intCast(regions.items.len), - regions.items.ptr, + &.{ + .{ + .src_offset = 0, + .dst_offset = data_offset, + .size = data_size, + }, + }, ); try command_buffer.endCommandBuffer(); const fence = try engine.createFence(.{}); defer engine.destroyFence(fence); - try engine.submitTransferCommandBuffer(command_buffer, fence); + try command_buffer.submit(engine, .{ .fence = fence }); try engine.waitForFence(fence); } }; diff --git a/src/engine/QueueType.zig b/src/engine/QueueType.zig new file mode 100644 index 0000000..fdaccc4 --- /dev/null +++ b/src/engine/QueueType.zig @@ -0,0 +1,5 @@ +pub const QueueType = enum { + graphics, + compute, + transfer, +}; diff --git a/src/engine/Swapchain.zig b/src/engine/Swapchain.zig index f7d4259..fc05659 100644 --- a/src/engine/Swapchain.zig +++ b/src/engine/Swapchain.zig @@ -3,9 +3,11 @@ const std = @import("std"); const vk = @import("vulkan"); +const CommandBuffer = @import("CommandBuffer.zig").CommandBuffer; const Engine = @import("Engine.zig"); const QSM = @import("QueueSharingMode.zig"); const Texture = @import("Texture.zig"); +const WaitSemaphore = @import("WaitSemaphore.zig"); params: Params, render_pass: vk.RenderPass, @@ -117,7 +119,7 @@ pub fn deinit(self: *Swapchain, engine: *Engine) void { const allocator = engine.vk_allocator.allocator; - for (self.swapchain_images) |swapchain_image| { + for (self.swapchain_images) |*swapchain_image| { swapchain_image.deinit(engine); } allocator.free(self.swapchain_images); @@ -178,7 +180,7 @@ pub fn recreate(self: *Swapchain, engine: *Engine) !void { // to revert on error. Instead, we set the current swapchain and images to // null and deinit the old swapchain and images. - for (old_swapchain_images) |swapchain_image| { + for (old_swapchain_images) |*swapchain_image| { swapchain_image.deinit(engine); } allocator.free(self.swapchain_images); @@ -219,7 +221,7 @@ pub fn recreate(self: *Swapchain, engine: *Engine) !void { var swapchain_images: std.ArrayList(SwapchainImage) = try .initCapacity(allocator, images.len); errdefer swapchain_images.deinit(allocator); - errdefer for (swapchain_images.items) |x| x.deinit(engine); + errdefer for (swapchain_images.items) |*x| x.deinit(engine); for (images) |image| { swapchain_images.appendAssumeCapacity(try SwapchainImage.init(engine, .{ @@ -234,7 +236,7 @@ pub fn recreate(self: *Swapchain, engine: *Engine) !void { break :blk try swapchain_images.toOwnedSlice(allocator); }; errdefer { - for (new_swapchain_images) |swapchain_image| { + for (new_swapchain_images) |*swapchain_image| { swapchain_image.deinit(engine); } allocator.free(new_swapchain_images); @@ -262,14 +264,9 @@ pub fn recreate(self: *Swapchain, engine: *Engine) !void { self.semaphore_image_acquired = semaphore_image_acquired; } -pub const WaitSemaphore = struct { - semaphore: vk.Semaphore, - stage_flags: vk.PipelineStageFlags = .{ .top_of_pipe_bit = true }, -}; - pub const PresentInfo = struct { - command_buffer: vk.CommandBuffer, - wait_semaphores: []const WaitSemaphore, + command_buffer: CommandBuffer(.graphics, .transient), + wait_semaphores: []const WaitSemaphore = &.{}, }; pub fn present(self: *Swapchain, engine: *Engine, present_info: PresentInfo) !PresentResult { @@ -281,42 +278,29 @@ pub fn present(self: *Swapchain, engine: *Engine, present_info: PresentInfo) !Pr const current_swapchain_image = &self.swapchain_images[self.image_index]; try engine.waitForFence(current_swapchain_image.fence); - if (current_swapchain_image.command_buffer != .null_handle) { - device.freeCommandBuffers(engine.graphics_command_pool, 1, @ptrCast(¤t_swapchain_image.command_buffer)); - current_swapchain_image.command_buffer = .null_handle; + if (current_swapchain_image.command_buffer) |*command_buffer| { + command_buffer.deinit(engine); + current_swapchain_image.command_buffer = null; } try engine.resetFence(current_swapchain_image.fence); // --- SUBMIT COMMAND BUFFER ----------------------------------------------- - var wait_semaphores = std.MultiArrayList(WaitSemaphore){}; + var wait_semaphores: std.ArrayList(WaitSemaphore) = try .initCapacity(allocator, 1 + present_info.wait_semaphores.len); defer wait_semaphores.deinit(allocator); - try wait_semaphores.ensureTotalCapacity(allocator, 1 + present_info.wait_semaphores.len); wait_semaphores.appendAssumeCapacity(.{ .semaphore = current_swapchain_image.semaphore_image_acquired, .stage_flags = .{ .top_of_pipe_bit = true }, }); - for (present_info.wait_semaphores) |wait_semaphore| { - wait_semaphores.appendAssumeCapacity(wait_semaphore); - } - - const signal_semaphores = [_]vk.Semaphore{ - current_swapchain_image.semaphore_render_finished, - }; + wait_semaphores.appendSliceAssumeCapacity(present_info.wait_semaphores); current_swapchain_image.command_buffer = present_info.command_buffer; - try device.queueSubmit(engine.graphics_queue.handle, 1, &.{ - .{ - .wait_semaphore_count = @intCast(wait_semaphores.len), - .p_wait_semaphores = wait_semaphores.items(.semaphore).ptr, - .p_wait_dst_stage_mask = wait_semaphores.items(.stage_flags).ptr, - .command_buffer_count = 1, - .p_command_buffers = @ptrCast(&present_info.command_buffer), - .signal_semaphore_count = signal_semaphores.len, - .p_signal_semaphores = &signal_semaphores, - }, - }, current_swapchain_image.fence); + try present_info.command_buffer.submit(engine, .{ + .wait_semaphores = wait_semaphores.items, + .signal_semaphores = &.{current_swapchain_image.semaphore_render_finished}, + .fence = current_swapchain_image.fence, + }); // --- PRESENT CURRENT FRAME ----------------------------------------------- @@ -415,7 +399,7 @@ const SwapchainImage = struct { semaphore_render_finished: vk.Semaphore, fence: vk.Fence, framebuffer: vk.Framebuffer, - command_buffer: vk.CommandBuffer, + command_buffer: ?CommandBuffer(.graphics, .transient), const InitProps = struct { image: vk.Image, @@ -467,18 +451,23 @@ const SwapchainImage = struct { .semaphore_render_finished = semaphore_render_finished, .fence = fence, .framebuffer = framebuffer, - .command_buffer = .null_handle, + .command_buffer = null, }; } - fn deinit(self: SwapchainImage, engine: *Engine) void { + fn deinit(self: *SwapchainImage, engine: *Engine) void { std.log.debug("Deinitializing {*} with {*}", .{ &self, engine }); engine.waitForFence(self.fence) catch {}; + if (self.command_buffer) |*command_buffer| { + command_buffer.deinit(engine); + } engine.destroyFramebuffer(self.framebuffer); engine.destroyFence(self.fence); engine.destroySemaphore(self.semaphore_render_finished); engine.destroySemaphore(self.semaphore_image_acquired); engine.destroyImageView(self.image_view); + + self.* = undefined; } }; diff --git a/src/engine/Texture.zig b/src/engine/Texture.zig index 9a5a34e..6265beb 100644 --- a/src/engine/Texture.zig +++ b/src/engine/Texture.zig @@ -3,6 +3,7 @@ const std = @import("std"); const vk = @import("vulkan"); +const CommandBuffer = @import("CommandBuffer.zig").CommandBuffer; const Engine = @import("Engine.zig"); const StagingBuffer = @import("StagingBuffer.zig"); const TargetQueue = @import("TargetQueue.zig").TargetQueue; @@ -194,64 +195,53 @@ pub fn writeRaw(self: Texture, engine: *Engine, data: []const u8) !void { // --- TRANSITION TO TRANSFER_DST_OPTIMAL AND COPY ----------------- - const transfer_command_buffer = try engine.allocateTransientTransferCommandBuffer(); - defer engine.freeTransientTransferCommandBuffer(transfer_command_buffer); + var transfer_command_buffer: CommandBuffer(.transfer, .transient) = try .init(engine); + defer transfer_command_buffer.deinit(engine); - try transfer_command_buffer.beginCommandBuffer(&.{ .flags = .{ .one_time_submit_bit = true } }); + try transfer_command_buffer.beginCommandBuffer(.{}); - const transfer_transition_barriers = [_]vk.ImageMemoryBarrier{ - .{ - .src_access_mask = .{}, - .dst_access_mask = .{ .transfer_write_bit = true }, - .old_layout = .undefined, - .new_layout = .transfer_dst_optimal, - .src_queue_family_index = vk.QUEUE_FAMILY_IGNORED, - .dst_queue_family_index = vk.QUEUE_FAMILY_IGNORED, - .image = self.image, - .subresource_range = .{ - .aspect_mask = .{ .color_bit = true }, - .base_mip_level = 0, - .level_count = 1, - .base_array_layer = 0, - .layer_count = 1, + transfer_command_buffer.pipelineBarrier(.{ + .src_stage_mask = .{ .top_of_pipe_bit = true }, + .dst_stage_mask = .{ .transfer_bit = true }, + .image_memory_barriers = &.{ + .{ + .src_access_mask = .{}, + .dst_access_mask = .{ .transfer_write_bit = true }, + .old_layout = .undefined, + .new_layout = .transfer_dst_optimal, + .src_queue_family_index = vk.QUEUE_FAMILY_IGNORED, + .dst_queue_family_index = vk.QUEUE_FAMILY_IGNORED, + .image = self.image, + .subresource_range = .{ + .aspect_mask = .{ .color_bit = true }, + .base_mip_level = 0, + .level_count = 1, + .base_array_layer = 0, + .layer_count = 1, + }, }, }, - }; - - transfer_command_buffer.pipelineBarrier( - .{ .top_of_pipe_bit = true }, - .{ .transfer_bit = true }, - .{}, - 0, - null, - 0, - null, - transfer_transition_barriers.len, - &transfer_transition_barriers, - ); - - const regions = [_]vk.BufferImageCopy{ - .{ - .buffer_offset = 0, - .buffer_row_length = self.width, - .buffer_image_height = self.height, - .image_subresource = .{ - .aspect_mask = .{ .color_bit = true }, - .mip_level = 0, - .base_array_layer = 0, - .layer_count = 1, - }, - .image_offset = .{ .x = 0, .y = 0, .z = 0 }, - .image_extent = .{ .width = self.width, .height = self.height, .depth = 1 }, - }, - }; + }); transfer_command_buffer.copyBufferToImage( staging_buffer.buffer, self.image, .transfer_dst_optimal, - regions.len, - ®ions, + &.{ + .{ + .buffer_offset = 0, + .buffer_row_length = self.width, + .buffer_image_height = self.height, + .image_subresource = .{ + .aspect_mask = .{ .color_bit = true }, + .mip_level = 0, + .base_array_layer = 0, + .layer_count = 1, + }, + .image_offset = .{ .x = 0, .y = 0, .z = 0 }, + .image_extent = .{ .width = self.width, .height = self.height, .depth = 1 }, + }, + }, ); try transfer_command_buffer.endCommandBuffer(); @@ -259,73 +249,48 @@ pub fn writeRaw(self: Texture, engine: *Engine, data: []const u8) !void { const semaphore = try engine.createSemaphore(); defer engine.destroySemaphore(semaphore); - const transfer_submits = [_]vk.SubmitInfo{ - .{ - .command_buffer_count = 1, - .p_command_buffers = @ptrCast(&transfer_command_buffer.handle), - .signal_semaphore_count = 1, - .p_signal_semaphores = @ptrCast(&semaphore), - }, - }; - try engine.device.queueSubmit(engine.transfer_queue.handle, transfer_submits.len, &transfer_submits, .null_handle); + try transfer_command_buffer.submit(engine, .{ + .signal_semaphores = &.{semaphore}, + }); // --- TRANSITION TO SHADER_READ_ONLY_OPTIMAL ---------------------- - const graphics_command_buffer = try engine.allocateGraphicsCommandBuffer(); - defer engine.freeGraphicsCommandBuffer(graphics_command_buffer); + var graphics_command_buffer: CommandBuffer(.graphics, .transient) = try .init(engine); + defer graphics_command_buffer.deinit(engine); - try graphics_command_buffer.beginCommandBuffer(&.{ .flags = .{ .one_time_submit_bit = true } }); + try graphics_command_buffer.beginCommandBuffer(.{}); - const graphics_transition_barriers = [_]vk.ImageMemoryBarrier{ - .{ - .src_access_mask = .{ .transfer_write_bit = true }, - .dst_access_mask = .{ .shader_read_bit = true }, - .old_layout = .transfer_dst_optimal, - .new_layout = .shader_read_only_optimal, - .src_queue_family_index = vk.QUEUE_FAMILY_IGNORED, - .dst_queue_family_index = vk.QUEUE_FAMILY_IGNORED, - .image = self.image, - .subresource_range = .{ - .aspect_mask = .{ .color_bit = true }, - .base_mip_level = 0, - .level_count = 1, - .base_array_layer = 0, - .layer_count = 1, + graphics_command_buffer.pipelineBarrier(.{ + .src_stage_mask = .{ .transfer_bit = true }, + .dst_stage_mask = .{ .fragment_shader_bit = true }, + .image_memory_barriers = &.{ + .{ + .src_access_mask = .{ .transfer_write_bit = true }, + .dst_access_mask = .{ .shader_read_bit = true }, + .old_layout = .transfer_dst_optimal, + .new_layout = .shader_read_only_optimal, + .src_queue_family_index = vk.QUEUE_FAMILY_IGNORED, + .dst_queue_family_index = vk.QUEUE_FAMILY_IGNORED, + .image = self.image, + .subresource_range = .{ + .aspect_mask = .{ .color_bit = true }, + .base_mip_level = 0, + .level_count = 1, + .base_array_layer = 0, + .layer_count = 1, + }, }, }, - }; - - graphics_command_buffer.pipelineBarrier( - .{ .transfer_bit = true }, - .{ .fragment_shader_bit = true }, - .{}, - 0, - null, - 0, - null, - graphics_transition_barriers.len, - &graphics_transition_barriers, - ); + }); try graphics_command_buffer.endCommandBuffer(); - const wait_stage_masks = [_]vk.PipelineStageFlags{ - .{ .top_of_pipe_bit = true }, - }; - - const graphics_submits = [_]vk.SubmitInfo{ - .{ - .command_buffer_count = 1, - .p_command_buffers = @ptrCast(&graphics_command_buffer.handle), - .wait_semaphore_count = 1, - .p_wait_semaphores = @ptrCast(&semaphore), - .p_wait_dst_stage_mask = &wait_stage_masks, - }, - }; - const fence = try engine.createFence(.{}); defer engine.destroyFence(fence); - try engine.device.queueSubmit(engine.graphics_queue.handle, graphics_submits.len, &graphics_submits, fence); + try graphics_command_buffer.submit(engine, .{ + .wait_semaphores = &.{.{ .semaphore = semaphore }}, + .fence = fence, + }); try engine.waitForFence(fence); } diff --git a/src/engine/Transient.zig b/src/engine/Transient.zig new file mode 100644 index 0000000..6a9b7ba --- /dev/null +++ b/src/engine/Transient.zig @@ -0,0 +1,4 @@ +pub const Transient = enum { + persistent, + transient, +}; diff --git a/src/engine/WaitSemaphore.zig b/src/engine/WaitSemaphore.zig new file mode 100644 index 0000000..5945d85 --- /dev/null +++ b/src/engine/WaitSemaphore.zig @@ -0,0 +1,4 @@ +const vk = @import("vulkan"); + +semaphore: vk.Semaphore, +stage_flags: vk.PipelineStageFlags = .{ .top_of_pipe_bit = true },