Refactor Vulkan setup phase 3

This commit is contained in:
2026-05-15 12:39:32 -08:00
parent 6e18542b2b
commit ed1b9db61e
12 changed files with 860 additions and 651 deletions
+101
View File
@@ -0,0 +1,101 @@
const std = @import("std");
const vk = @import("vulkan");
const device = @import("device.zig");
const framebuffers = @import("framebuffers.zig");
const render_pass = @import("render_pass.zig");
const swapchain = @import("swapchain.zig");
pub const CommandContext = struct {
command_pool: vk.CommandPool,
command_buffers: []vk.CommandBuffer,
allocator: std.mem.Allocator,
pub fn destroy(self: *const CommandContext, ldc: *const device.LogicalDeviceContext) void {
self.allocator.free(self.command_buffers);
ldc.vkd.destroyCommandPool(ldc.device, self.command_pool, null);
}
};
pub fn initCommandBuffers(
ldc: device.LogicalDeviceContext,
render_pass_context: render_pass.RenderPassContext,
framebuffer_context: framebuffers.FramebufferContext,
swapchain_context: swapchain.SwapchainContext,
allocator: std.mem.Allocator,
) !CommandContext {
const command_pool_create_info = vk.CommandPoolCreateInfo{
.flags = .{
.reset_command_buffer_bit = true,
},
.queue_family_index = ldc.graphics_queue_family_index,
};
const command_pool = try ldc.vkd.createCommandPool(
ldc.device,
&command_pool_create_info,
null,
);
errdefer ldc.vkd.destroyCommandPool(ldc.device, command_pool, null);
std.debug.print("created command pool\n", .{});
const command_buffers = try allocator.alloc(vk.CommandBuffer, framebuffer_context.framebuffers.len);
errdefer allocator.free(command_buffers);
const command_buffer_allocate_info = vk.CommandBufferAllocateInfo{
.command_pool = command_pool,
.level = .primary,
.command_buffer_count = @intCast(command_buffers.len),
};
try ldc.vkd.allocateCommandBuffers(
ldc.device,
&command_buffer_allocate_info,
command_buffers.ptr,
);
std.debug.print("allocated command buffers: {}\n", .{command_buffers.len});
for (command_buffers, 0..) |command_buffer, i| {
const begin_info = vk.CommandBufferBeginInfo{};
try ldc.vkd.beginCommandBuffer(command_buffer, &begin_info);
// This is the only "drawing" currently happening: begin a render pass
// and clear the swapchain image. The embedded shaders are not used yet.
const clear_color = vk.ClearValue{
.color = .{
.float_32 = .{ 0.02, 0.02, 0.08, 1.0 },
},
};
const render_pass_begin_info = vk.RenderPassBeginInfo{
.render_pass = render_pass_context.render_pass,
.framebuffer = framebuffer_context.framebuffers[i],
.render_area = .{
.offset = .{ .x = 0, .y = 0 },
.extent = swapchain_context.extent,
},
.clear_value_count = 1,
.p_clear_values = @ptrCast(&clear_color),
};
ldc.vkd.cmdBeginRenderPass(
command_buffer,
&render_pass_begin_info,
.@"inline",
);
ldc.vkd.cmdEndRenderPass(command_buffer);
try ldc.vkd.endCommandBuffer(command_buffer);
}
std.debug.print("recorded command buffers\n", .{});
return .{
.command_pool = command_pool,
.command_buffers = command_buffers,
.allocator = allocator,
};
}
+47
View File
@@ -0,0 +1,47 @@
const std = @import("std");
const glfw = @import("zglfw");
const vk = @import("vulkan");
pub const VulkanContext = struct {
base: vk.BaseWrapper,
instance: vk.Instance,
vki: vk.InstanceWrapper,
pub fn destroy(self: *const VulkanContext) void {
self.vki.destroyInstance(self.instance, null);
}
};
pub fn initInstance(name: [:0]const u8) !VulkanContext {
const base = vk.BaseWrapper.load(glfw.getInstanceProcAddress);
const required_extensions = try glfw.getRequiredInstanceExtensions();
std.debug.print("Required instance extensions:\n", .{});
for (required_extensions) |extension| {
std.debug.print(" {s}\n", .{extension});
}
const app_info = vk.ApplicationInfo{
.p_application_name = name,
.application_version = 1,
.p_engine_name = name,
.engine_version = 1,
.api_version = @bitCast(vk.makeApiVersion(0, 1, 2, 0)),
};
const instance_create_info = vk.InstanceCreateInfo{
.p_application_info = &app_info,
.enabled_extension_count = @intCast(required_extensions.len),
.pp_enabled_extension_names = required_extensions.ptr,
};
const instance = try base.createInstance(&instance_create_info, null);
std.debug.print("Created Vulkan Instance\n", .{});
const vki = vk.InstanceWrapper.load(instance, base.dispatch.vkGetInstanceProcAddr.?);
return .{
.instance = instance,
.base = base,
.vki = vki,
};
}
+99
View File
@@ -0,0 +1,99 @@
const std = @import("std");
const vk = @import("vulkan");
const context = @import("context.zig");
pub const LogicalDeviceContext = struct {
physical_device: vk.PhysicalDevice,
graphics_queue_family_index: u32,
device: vk.Device,
vkd: vk.DeviceWrapper,
graphics_queue: vk.Queue,
pub fn destroy(self: *const LogicalDeviceContext) void {
self.vkd.destroyDevice(self.device, null);
}
};
pub fn initLogicalDevice(
vc: context.VulkanContext,
physical_device: vk.PhysicalDevice,
graphics_queue_family_index: u32,
) !LogicalDeviceContext {
const queue_priority: f32 = 1.0;
const queue_create_info = vk.DeviceQueueCreateInfo{
.queue_family_index = graphics_queue_family_index,
.queue_count = 1,
.p_queue_priorities = @ptrCast(&queue_priority),
};
const device_extensions = [_][*:0]const u8{
"VK_KHR_swapchain",
};
const device_create_info = vk.DeviceCreateInfo{
.queue_create_info_count = 1,
.p_queue_create_infos = @ptrCast(&queue_create_info),
.enabled_extension_count = device_extensions.len,
.pp_enabled_extension_names = &device_extensions,
};
const device = try vc.vki.createDevice(physical_device, &device_create_info, null);
errdefer vc.vki.destroyDevice(device, null);
std.debug.print("created logical device\n", .{});
const vkd = vk.DeviceWrapper.load(device, vc.vki.dispatch.vkGetDeviceProcAddr.?);
const graphics_queue = vkd.getDeviceQueue(device, graphics_queue_family_index, 0);
std.debug.print("retrieved graphics queue\n", .{});
return .{
.physical_device = physical_device,
.graphics_queue_family_index = graphics_queue_family_index,
.device = device,
.vkd = vkd,
.graphics_queue = graphics_queue,
};
}
pub fn debugPhysicalGPUs(
vc: context.VulkanContext,
physical_devices: []vk.PhysicalDevice,
surface: vk.SurfaceKHR,
) !void {
std.debug.print("physical devices: {}\n", .{physical_devices.len});
for (physical_devices, 0..) |physical_device, i| {
const props = vc.vki.getPhysicalDeviceProperties(physical_device);
std.debug.print("device {}: {s}\n", .{ i, std.mem.sliceTo(&props.device_name, 0) });
const queue_families = try vc.vki.getPhysicalDeviceQueueFamilyPropertiesAlloc(
physical_device,
std.heap.page_allocator,
);
defer std.heap.page_allocator.free(queue_families);
for (queue_families, 0..) |queue_family, queue_index| {
const supports_graphics = queue_family.queue_flags.graphics_bit;
const supports_compute = queue_family.queue_flags.compute_bit;
const supports_transfer = queue_family.queue_flags.transfer_bit;
const supports_present = try vc.vki.getPhysicalDeviceSurfaceSupportKHR(
physical_device,
@intCast(queue_index),
surface,
);
std.debug.print(
" queue {}: count={}, graphics={}, compute={}, transfer={}, present={}\n",
.{
queue_index,
queue_family.queue_count,
supports_graphics,
supports_compute,
supports_transfer,
supports_present,
},
);
}
}
}
+86
View File
@@ -0,0 +1,86 @@
const std = @import("std");
const vk = @import("vulkan");
const commands = @import("commands.zig");
const device = @import("device.zig");
const swapchain = @import("swapchain.zig");
const sync = @import("sync.zig");
pub const FrameResult = enum {
OK,
Recreate,
};
pub fn drawFrame(
ldc: device.LogicalDeviceContext,
swapchain_context: swapchain.SwapchainContext,
command_context: commands.CommandContext,
sync_context: sync.SyncContext,
) !FrameResult {
const wait_fences = [_]vk.Fence{sync_context.in_flight_fence};
_ = try ldc.vkd.waitForFences(ldc.device, &wait_fences, .true, std.math.maxInt(u64));
const image_result = ldc.vkd.acquireNextImageKHR(
ldc.device,
swapchain_context.swapchain,
std.math.maxInt(u64),
sync_context.image_available_semaphore,
.null_handle,
) catch |err| switch (err) {
error.OutOfDateKHR => return .Recreate,
else => return err,
};
const image_index = image_result.image_index;
const should_recreate_after_present = image_result.result == .suboptimal_khr;
std.debug.print("Acquired swapchain image: {}\n", .{image_index});
try ldc.vkd.resetFences(ldc.device, &wait_fences);
const wait_semaphores = [_]vk.Semaphore{sync_context.image_available_semaphore};
const wait_stages = [_]vk.PipelineStageFlags{
.{
.color_attachment_output_bit = true,
},
};
const signal_semaphores = [_]vk.Semaphore{sync_context.render_finished_semaphore};
const submit_command_buffers = [_]vk.CommandBuffer{
command_context.command_buffers[image_index],
};
const submit_info = vk.SubmitInfo{
.wait_semaphore_count = wait_semaphores.len,
.p_wait_semaphores = &wait_semaphores,
.p_wait_dst_stage_mask = &wait_stages,
.command_buffer_count = submit_command_buffers.len,
.p_command_buffers = &submit_command_buffers,
.signal_semaphore_count = signal_semaphores.len,
.p_signal_semaphores = &signal_semaphores,
};
try ldc.vkd.queueSubmit(ldc.graphics_queue, &[_]vk.SubmitInfo{submit_info}, sync_context.in_flight_fence);
const present_swapchains = [_]vk.SwapchainKHR{swapchain_context.swapchain};
const present_image_indices = [_]u32{image_index};
const present_info = vk.PresentInfoKHR{
.wait_semaphore_count = signal_semaphores.len,
.p_wait_semaphores = &signal_semaphores,
.swapchain_count = present_swapchains.len,
.p_swapchains = &present_swapchains,
.p_image_indices = &present_image_indices,
};
const present_result = ldc.vkd.queuePresentKHR(ldc.graphics_queue, &present_info) catch |err| switch (err) {
error.OutOfDateKHR => return .Recreate,
else => return err,
};
std.debug.print("Presented one frame\n", .{});
if (should_recreate_after_present or present_result == .suboptimal_khr) {
return FrameResult.Recreate;
}
return FrameResult.OK;
}
+62
View File
@@ -0,0 +1,62 @@
const std = @import("std");
const vk = @import("vulkan");
const device = @import("device.zig");
const render_pass = @import("render_pass.zig");
const swapchain = @import("swapchain.zig");
pub const FramebufferContext = struct {
framebuffers: []vk.Framebuffer,
allocator: std.mem.Allocator,
pub fn destroy(self: *const FramebufferContext, ldc: *const device.LogicalDeviceContext) void {
for (self.framebuffers) |framebuffer| {
ldc.vkd.destroyFramebuffer(ldc.device, framebuffer, null);
}
self.allocator.free(self.framebuffers);
}
};
pub fn initFramebuffers(
ldc: device.LogicalDeviceContext,
render_pass_context: render_pass.RenderPassContext,
swapchain_context: swapchain.SwapchainContext,
allocator: std.mem.Allocator,
) !FramebufferContext {
const framebuffers = try allocator.alloc(vk.Framebuffer, swapchain_context.image_views.len);
errdefer allocator.free(framebuffers);
var created_framebuffer_count: usize = 0;
errdefer {
for (framebuffers[0..created_framebuffer_count]) |framebuffer| {
ldc.vkd.destroyFramebuffer(ldc.device, framebuffer, null);
}
}
for (swapchain_context.image_views, 0..) |image_view, i| {
const attachments = [_]vk.ImageView{image_view};
const framebuffer_create_info = vk.FramebufferCreateInfo{
.render_pass = render_pass_context.render_pass,
.attachment_count = attachments.len,
.p_attachments = &attachments,
.width = swapchain_context.extent.width,
.height = swapchain_context.extent.height,
.layers = 1,
};
framebuffers[i] = try ldc.vkd.createFramebuffer(
ldc.device,
&framebuffer_create_info,
null,
);
created_framebuffer_count += 1;
}
std.debug.print("created framebuffers: {}\n", .{framebuffers.len});
return .{
.framebuffers = framebuffers,
.allocator = allocator,
};
}
+14
View File
@@ -0,0 +1,14 @@
const vk = @import("vulkan");
const device = @import("device.zig");
pub fn createShaderModule(ldc: device.LogicalDeviceContext, spv: []const u8) !vk.ShaderModule {
if (spv.len % 4 != 0) return error.InvalidSpirVSize;
const create_info = vk.ShaderModuleCreateInfo{
.code_size = spv.len,
.p_code = @ptrCast(@alignCast(spv.ptr)),
};
return try ldc.vkd.createShaderModule(ldc.device, &create_info, null);
}
+65
View File
@@ -0,0 +1,65 @@
const std = @import("std");
const vk = @import("vulkan");
const device = @import("device.zig");
pub const RenderPassContext = struct {
render_pass: vk.RenderPass,
pub fn destroy(self: *const RenderPassContext, ldc: *const device.LogicalDeviceContext) void {
ldc.vkd.destroyRenderPass(ldc.device, self.render_pass, null);
}
};
pub fn initRenderPass(ldc: device.LogicalDeviceContext, format: vk.Format) !RenderPassContext {
const color_attachment = vk.AttachmentDescription{
.format = format,
.samples = .{ .@"1_bit" = true },
.load_op = .clear,
.store_op = .store,
.stencil_load_op = .dont_care,
.stencil_store_op = .dont_care,
.initial_layout = .undefined,
.final_layout = .present_src_khr,
};
const color_attachment_ref = vk.AttachmentReference{
.attachment = 0,
.layout = .color_attachment_optimal,
};
const subpass = vk.SubpassDescription{
.pipeline_bind_point = .graphics,
.color_attachment_count = 1,
.p_color_attachments = @ptrCast(&color_attachment_ref),
};
const subpass_dependency = vk.SubpassDependency{
.src_subpass = vk.SUBPASS_EXTERNAL,
.dst_subpass = 0,
.src_stage_mask = .{
.color_attachment_output_bit = true,
},
.src_access_mask = .{},
.dst_stage_mask = .{
.color_attachment_output_bit = true,
},
.dst_access_mask = .{
.color_attachment_write_bit = true,
},
};
const render_pass_create_info = vk.RenderPassCreateInfo{
.attachment_count = 1,
.p_attachments = @ptrCast(&color_attachment),
.subpass_count = 1,
.p_subpasses = @ptrCast(&subpass),
.dependency_count = 1,
.p_dependencies = @ptrCast(&subpass_dependency),
};
const render_pass = try ldc.vkd.createRenderPass(ldc.device, &render_pass_create_info, null);
std.debug.print("created render pass\n", .{});
return .{ .render_pass = render_pass };
}
+197
View File
@@ -0,0 +1,197 @@
const std = @import("std");
const glfw = @import("zglfw");
const vk = @import("vulkan");
const context = @import("context.zig");
const device = @import("device.zig");
pub const SwapchainContext = struct {
swapchain: vk.SwapchainKHR,
images: []vk.Image,
image_views: []vk.ImageView,
format: vk.SurfaceFormatKHR,
present_mode: vk.PresentModeKHR,
extent: vk.Extent2D,
image_count: u32,
allocator: std.mem.Allocator,
pub fn destroy(self: *const SwapchainContext, ldc: *const device.LogicalDeviceContext) void {
for (self.image_views) |image_view| {
ldc.vkd.destroyImageView(ldc.device, image_view, null);
}
self.allocator.free(self.image_views);
self.allocator.free(self.images);
ldc.vkd.destroySwapchainKHR(ldc.device, self.swapchain, null);
}
};
pub fn initSwapchain(
vc: context.VulkanContext,
ldc: device.LogicalDeviceContext,
surface: vk.SurfaceKHR,
window: *glfw.Window,
allocator: std.mem.Allocator,
) !SwapchainContext {
const surface_caps = try vc.vki.getPhysicalDeviceSurfaceCapabilitiesKHR(
ldc.physical_device,
surface,
);
std.debug.print(
"surface current extent: {}x{}\n",
.{
surface_caps.current_extent.width,
surface_caps.current_extent.height,
},
);
std.debug.print(
"surface min/max image count: {}/{}\n",
.{
surface_caps.min_image_count,
surface_caps.max_image_count,
},
);
const surface_formats = try vc.vki.getPhysicalDeviceSurfaceFormatsAllocKHR(
ldc.physical_device,
surface,
allocator,
);
defer allocator.free(surface_formats);
const present_modes = try vc.vki.getPhysicalDeviceSurfacePresentModesAllocKHR(
ldc.physical_device,
surface,
allocator,
);
defer allocator.free(present_modes);
var chosen_surface_format = surface_formats[0];
for (surface_formats) |format| {
if (format.format == .b8g8r8a8_srgb and
format.color_space == .srgb_nonlinear_khr)
{
chosen_surface_format = format;
break;
}
}
var chosen_present_mode: vk.PresentModeKHR = .fifo_khr;
for (present_modes) |mode| {
if (mode == .fifo_khr) {
chosen_present_mode = mode;
break;
}
}
const framebuffer_size = window.getFramebufferSize();
const chosen_extent = if (surface_caps.current_extent.width != std.math.maxInt(u32))
surface_caps.current_extent
else
vk.Extent2D{
.width = @intCast(framebuffer_size[0]),
.height = @intCast(framebuffer_size[1]),
};
var chosen_image_count = surface_caps.min_image_count + 1;
if (surface_caps.max_image_count != 0 and chosen_image_count > surface_caps.max_image_count) {
chosen_image_count = surface_caps.max_image_count;
}
std.debug.print(
"chosen swapchain format={any}, color_space={any}\n",
.{ chosen_surface_format.format, chosen_surface_format.color_space },
);
std.debug.print("chosen present mode={any}\n", .{chosen_present_mode});
std.debug.print(
"chosen extent={}x{}\n",
.{ chosen_extent.width, chosen_extent.height },
);
std.debug.print("chosen image count={}\n", .{chosen_image_count});
const swapchain_create_info = vk.SwapchainCreateInfoKHR{
.surface = surface,
.min_image_count = chosen_image_count,
.image_format = chosen_surface_format.format,
.image_color_space = chosen_surface_format.color_space,
.image_extent = chosen_extent,
.image_array_layers = 1,
.image_usage = .{
.color_attachment_bit = true,
},
.image_sharing_mode = .exclusive,
.pre_transform = surface_caps.current_transform,
.composite_alpha = .{
.opaque_bit_khr = true,
},
.present_mode = chosen_present_mode,
.clipped = .true,
};
const swapchain = try ldc.vkd.createSwapchainKHR(ldc.device, &swapchain_create_info, null);
errdefer ldc.vkd.destroySwapchainKHR(ldc.device, swapchain, null);
std.debug.print("created swapchain\n", .{});
const swapchain_images = try ldc.vkd.getSwapchainImagesAllocKHR(
ldc.device,
swapchain,
allocator,
);
errdefer allocator.free(swapchain_images);
std.debug.print("swapchain images: {}\n", .{swapchain_images.len});
const swapchain_image_views = try allocator.alloc(vk.ImageView, swapchain_images.len);
errdefer allocator.free(swapchain_image_views);
var created_image_view_count: usize = 0;
errdefer {
for (swapchain_image_views[0..created_image_view_count]) |image_view| {
ldc.vkd.destroyImageView(ldc.device, image_view, null);
}
}
for (swapchain_images, 0..) |image, i| {
const image_view_create_info = vk.ImageViewCreateInfo{
.image = image,
.view_type = .@"2d",
.format = chosen_surface_format.format,
.components = .{
.r = .identity,
.g = .identity,
.b = .identity,
.a = .identity,
},
.subresource_range = .{
.aspect_mask = .{
.color_bit = true,
},
.base_mip_level = 0,
.level_count = 1,
.base_array_layer = 0,
.layer_count = 1,
},
};
swapchain_image_views[i] = try ldc.vkd.createImageView(
ldc.device,
&image_view_create_info,
null,
);
created_image_view_count += 1;
}
std.debug.print("created swapchain image views: {}\n", .{swapchain_image_views.len});
return .{
.swapchain = swapchain,
.images = swapchain_images,
.image_views = swapchain_image_views,
.format = chosen_surface_format,
.present_mode = chosen_present_mode,
.extent = chosen_extent,
.image_count = chosen_image_count,
.allocator = allocator,
};
}
+54
View File
@@ -0,0 +1,54 @@
const std = @import("std");
const vk = @import("vulkan");
const device = @import("device.zig");
pub const SyncContext = struct {
image_available_semaphore: vk.Semaphore,
render_finished_semaphore: vk.Semaphore,
in_flight_fence: vk.Fence,
pub fn destroy(self: *const SyncContext, ldc: *const device.LogicalDeviceContext) void {
ldc.vkd.destroyFence(ldc.device, self.in_flight_fence, null);
ldc.vkd.destroySemaphore(ldc.device, self.render_finished_semaphore, null);
ldc.vkd.destroySemaphore(ldc.device, self.image_available_semaphore, null);
}
};
pub fn initSyncObjects(ldc: device.LogicalDeviceContext) !SyncContext {
const semaphore_create_info = vk.SemaphoreCreateInfo{};
const image_available_semaphore = try ldc.vkd.createSemaphore(
ldc.device,
&semaphore_create_info,
null,
);
errdefer ldc.vkd.destroySemaphore(ldc.device, image_available_semaphore, null);
const render_finished_semaphore = try ldc.vkd.createSemaphore(
ldc.device,
&semaphore_create_info,
null,
);
errdefer ldc.vkd.destroySemaphore(ldc.device, render_finished_semaphore, null);
const fence_create_info = vk.FenceCreateInfo{
.flags = .{
.signaled_bit = true,
},
};
const in_flight_fence = try ldc.vkd.createFence(
ldc.device,
&fence_create_info,
null,
);
std.debug.print("created synchronization objects\n", .{});
return .{
.image_available_semaphore = image_available_semaphore,
.render_finished_semaphore = render_finished_semaphore,
.in_flight_fence = in_flight_fence,
};
}