Files
voxel-game/src/engine/CommandBuffer.zig
2026-05-22 23:34:25 +02:00

295 lines
8.9 KiB
Zig

//! A thin wrapper around `@import("vk").CommandBufferProxy` that ziggifies
//! arguments by turning pointer-length pairs into slices and integrates with
//! different queues from `Engine` struct.
//!
//! The wrapper retains information about the queue type, such that it can
//! submit to the correct queue preallocated in the `Engine` struct when calling
//! `CommandBuffer.submit`.
const CommandBuffer = @This();
const std = @import("std");
const ctx = @import("../AppContext.zig");
const vk = @import("vulkan");
const Engine = @import("Engine.zig");
const QueueType = @import("QueueType.zig").QueueType;
proxy: vk.CommandBufferProxy,
queue_type: QueueType,
pub fn init(queue_type: QueueType) !CommandBuffer {
const engine = ctx.engine;
const handle = try engine.allocateCommandBuffer(.{ .queue_type = queue_type });
return .{
.proxy = .init(handle, engine.device.wrapper),
.queue_type = queue_type,
};
}
pub fn deinit(self: *CommandBuffer) void {
const engine = ctx.engine;
engine.freeCommandBuffer(.{
.queue_type = self.queue_type,
.command_buffer = self.proxy.handle,
});
self.* = undefined;
}
pub fn submit(self: CommandBuffer, submit_info: Engine.SubmitInfo) !void {
const engine = ctx.engine;
try engine.queueSubmit(self.queue_type, self.proxy.handle, submit_info);
}
// --- VULKAN WRAPPERS ---------------------------------------------------------
fn ToVkType(comptime T: type) type {
if (!std.meta.hasFn(T, "toVk")) {
@compileError("Type " ++ T ++ " has no toVk method.");
}
const to_vk_type_info = @typeInfo(@TypeOf(@field(T, "toVk")));
return to_vk_type_info.@"fn".return_type.?;
}
fn toVkOptional(comptime T: type, maybe_value: ?T) !?*const ToVkType(T) {
const allocator_frame = ctx.allocator_frame;
if (maybe_value) |value| {
const ptr = try allocator_frame.create(ToVkType(T));
if (std.meta.hasFn(T, "toVk")) {
ptr.* = value.toVk();
}
return ptr;
} else {
return null;
}
}
fn toVkMulti(comptime T: type, values: []const T) ![*]const ToVkType(T) {
const allocator_frame = ctx.allocator_frame;
const slice = try allocator_frame.alloc(ToVkType(T), values.len);
for (slice, values) |*y, x| {
y.* = x.toVk();
}
return slice.ptr;
}
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 RenderingAttachmentInfo = struct {
image_view: vk.ImageView = .null_handle,
image_layout: vk.ImageLayout,
resolve_mode: vk.ResolveModeFlags = .{},
resolve_image_view: vk.ImageView = .null_handle,
resolve_image_layout: vk.ImageLayout = .undefined,
load_op: vk.AttachmentLoadOp,
store_op: vk.AttachmentStoreOp,
clear_value: vk.ClearValue,
pub fn toVk(self: RenderingAttachmentInfo) vk.RenderingAttachmentInfo {
return .{
.image_view = self.image_view,
.image_layout = self.image_layout,
.resolve_mode = self.resolve_mode,
.resolve_image_view = self.resolve_image_view,
.resolve_image_layout = self.resolve_image_layout,
.load_op = self.load_op,
.store_op = self.store_op,
.clear_value = self.clear_value,
};
}
};
pub const RenderingInfo = struct {
flags: vk.RenderingFlags = .{},
render_area: vk.Rect2D,
layer_count: u32,
view_mask: u32,
color_attachments: []const RenderingAttachmentInfo = &.{},
depth_attachment: ?RenderingAttachmentInfo = null,
stencil_attachment: ?RenderingAttachmentInfo = null,
pub fn toVk(self: RenderingInfo) !vk.RenderingInfo {
return .{
.flags = self.flags,
.render_area = self.render_area,
.layer_count = self.layer_count,
.view_mask = self.view_mask,
.color_attachment_count = @intCast(self.color_attachments.len),
.p_color_attachments = try toVkMulti(RenderingAttachmentInfo, self.color_attachments),
.p_depth_attachment = try toVkOptional(RenderingAttachmentInfo, self.depth_attachment),
.p_stencil_attachment = try toVkOptional(RenderingAttachmentInfo, self.stencil_attachment),
};
}
};
pub const VertexBufferBinding = struct {
buffer: vk.Buffer,
offset: usize,
pub fn transpose(vertex_buffer_bindings: []const VertexBufferBinding) !struct { []const vk.Buffer, []const usize } {
const allocator_frame = ctx.allocator_frame;
var t: std.MultiArrayList(VertexBufferBinding) = .empty;
try t.ensureTotalCapacity(allocator_frame, vertex_buffer_bindings.len);
for (vertex_buffer_bindings) |binding| {
t.appendAssumeCapacity(binding);
}
return .{
t.items(.buffer),
t.items(.offset),
};
}
};
pub fn beginCommandBuffer(self: CommandBuffer) !void {
try self.proxy.beginCommandBuffer(&.{
.flags = .{
.one_time_submit_bit = true,
},
});
}
pub fn beginRendering(self: CommandBuffer, rendering_info: RenderingInfo) !void {
const rendering_info_vk = try rendering_info.toVk();
self.proxy.beginRendering(&rendering_info_vk);
}
pub fn bindDescriptorSet(
self: CommandBuffer,
pipeline_bind_point: vk.PipelineBindPoint,
layout: vk.PipelineLayout,
set: u32,
descriptor_set: vk.DescriptorSet,
dynamic_offset: ?u32,
) void {
self.bindDescriptorSets(
pipeline_bind_point,
layout,
set,
&.{descriptor_set},
if (dynamic_offset) |x| &.{x} else &.{},
);
}
pub fn bindDescriptorSets(
self: CommandBuffer,
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,
descriptor_sets,
dynamic_offsets,
);
}
pub fn bindIndexBuffer(self: CommandBuffer, buffer: vk.Buffer, offset: u64, index_type: vk.IndexType) void {
self.proxy.bindIndexBuffer(buffer, offset, index_type);
}
pub fn bindPipeline(self: CommandBuffer, pipeline_bind_point: vk.PipelineBindPoint, pipeline: vk.Pipeline) void {
self.proxy.bindPipeline(pipeline_bind_point, pipeline);
}
pub fn bindVertexBuffer(self: CommandBuffer, index: u32, binding: VertexBufferBinding) !void {
self.proxy.bindVertexBuffers(index, &.{binding.buffer}, &.{binding.offset});
}
pub fn bindVertexBuffers(self: CommandBuffer, first_binding: u32, bindings: []const VertexBufferBinding) !void {
const buffers, const offsets = try VertexBufferBinding.transpose(bindings);
self.proxy.bindVertexBuffers(first_binding, buffers, offsets);
}
pub fn copyBuffer(
self: CommandBuffer,
src_buffer: vk.Buffer,
dst_buffer: vk.Buffer,
regions: []const vk.BufferCopy,
) void {
self.proxy.copyBuffer(src_buffer, dst_buffer, regions);
}
pub fn copyBufferToImage(
self: CommandBuffer,
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, regions);
}
pub fn drawIndexed(self: CommandBuffer, 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 fn endCommandBuffer(self: CommandBuffer) !void {
try self.proxy.endCommandBuffer();
}
pub fn endRendering(self: CommandBuffer) void {
self.proxy.endRendering();
}
pub fn pipelineBarrier(self: CommandBuffer, barrier: PipelineBarrier) void {
self.proxy.pipelineBarrier(
barrier.src_stage_mask,
barrier.dst_stage_mask,
barrier.dependency_flags,
barrier.memory_barriers,
barrier.buffer_memory_barriers,
barrier.image_memory_barriers,
);
}
pub fn setScissor(self: CommandBuffer, index: u32, scissor: vk.Rect2D) void {
self.proxy.setScissor(index, &.{scissor});
}
pub fn setScissors(self: CommandBuffer, first_scissor: u32, scissors: []const vk.Rect2D) void {
self.proxy.setScissor(first_scissor, scissors);
}
pub fn setViewport(self: CommandBuffer, index: u32, viewports: vk.Viewport) void {
self.proxy.setViewport(index, &.{viewports});
}
pub fn setViewports(self: CommandBuffer, first_viewport: u32, viewports: []const vk.Viewport) void {
self.proxy.setViewport(first_viewport, viewports);
}