diff --git a/src/main.zig b/src/main.zig index d8cfaa9..d0b6c63 100644 --- a/src/main.zig +++ b/src/main.zig @@ -18,6 +18,38 @@ const VulkanContext = struct { } }; +const LogicalDeviceContext = struct { + physical_device: vk.PhysicalDevice, + graphics_queue_family_index: u32, + device: vk.Device, + vkd: vk.DeviceWrapper, + graphics_queue: vk.Queue, + + fn destroy(self: *const LogicalDeviceContext) void { + self.vkd.destroyDevice(self.device, null); + } +}; + +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, + + fn destroy(self: *const SwapchainContext, ldc: *const 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 main() !void { std.debug.print("zig-chess bootstrap\n", .{}); std.debug.print("vertex shader bytes: {}\n", .{square_vert_spv.len}); @@ -79,230 +111,15 @@ pub fn main() !void { // --------------------------------------------------------------------- // Logical device and queue - // - // The logical device enables VK_KHR_swapchain so we can present rendered - // images. We request one queue from the selected queue family. - // - // Refactor direction: createLogicalDevice() should return the device, - // DeviceWrapper, and graphics/present queue handles or indices. // --------------------------------------------------------------------- - 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(selected_physical_device, &device_create_info, null); - std.debug.print("created logical device\n", .{}); - - const vkd = vk.DeviceWrapper.load(device, vc.vki.dispatch.vkGetDeviceProcAddr.?); - defer vkd.destroyDevice(device, null); - - const graphics_queue = vkd.getDeviceQueue(device, graphics_queue_family_index, 0); - - std.debug.print("retrieved graphics queue\n", .{}); + const ldc = try initLogicalDevice(vc, selected_physical_device, graphics_queue_family_index); + defer ldc.destroy(); // --------------------------------------------------------------------- - // Swapchain support query and choice of format/present mode/extent - // - // These values describe how the surface can be presented to. FIFO is the - // safe baseline because Vulkan requires it to be supported. - // - // Refactor direction: create a chooseSwapchainSettings() helper returning - // the chosen format, present mode, extent, and image count. + // Swapchain setup // --------------------------------------------------------------------- - const surface_caps = try vc.vki.getPhysicalDeviceSurfaceCapabilitiesKHR( - selected_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( - selected_physical_device, - surface, - std.heap.page_allocator, - ); - defer std.heap.page_allocator.free(surface_formats); - - std.debug.print("surface formats: {}\n", .{surface_formats.len}); - for (surface_formats, 0..) |format, i| { - std.debug.print( - " format {}: format={any}, color_space={any}\n", - .{ i, format.format, format.color_space }, - ); - } - - const present_modes = try vc.vki.getPhysicalDeviceSurfacePresentModesAllocKHR( - selected_physical_device, - surface, - std.heap.page_allocator, - ); - defer std.heap.page_allocator.free(present_modes); - - std.debug.print("present modes: {}\n", .{present_modes.len}); - for (present_modes, 0..) |mode, i| { - std.debug.print(" present mode {}: {any}\n", .{ i, mode }); - } - - 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, - }; - - // --------------------------------------------------------------------- - // Swapchain creation - // - // The swapchain owns the presentable images. Anything tied to its image - // format or extent must be recreated when the window is resized or Vulkan - // reports the swapchain is out of date/suboptimal. - // - // Refactor direction: group swapchain, images, image views, framebuffers, - // format, and extent into a SwapchainResources struct. - // --------------------------------------------------------------------- - const swapchain = try vkd.createSwapchainKHR(device, &swapchain_create_info, null); - defer vkd.destroySwapchainKHR(device, swapchain, null); - - std.debug.print("created swapchain\n", .{}); - - const swapchain_images = try vkd.getSwapchainImagesAllocKHR( - device, - swapchain, - std.heap.page_allocator, - ); - defer std.heap.page_allocator.free(swapchain_images); - - std.debug.print("swapchain images: {}\n", .{swapchain_images.len}); - - // Each swapchain image needs an image view so it can be used as a render - // pass attachment. - const swapchain_image_views = try std.heap.page_allocator.alloc( - vk.ImageView, - swapchain_images.len, - ); - defer std.heap.page_allocator.free(swapchain_image_views); - - 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 vkd.createImageView( - device, - &image_view_create_info, - null, - ); - } - - defer { - for (swapchain_image_views) |image_view| { - vkd.destroyImageView(device, image_view, null); - } - } - - std.debug.print("created swapchain image views: {}\n", .{swapchain_image_views.len}); + const swapchain_context = try initSwapchain(vc, ldc, surface, window, std.heap.page_allocator); + defer swapchain_context.destroy(&ldc); // --------------------------------------------------------------------- // Render pass @@ -316,7 +133,7 @@ pub fn main() !void { // if we move to dynamic rendering. // --------------------------------------------------------------------- const color_attachment = vk.AttachmentDescription{ - .format = chosen_surface_format.format, + .format = swapchain_context.format.format, .samples = .{ .@"1_bit" = true }, .load_op = .clear, .store_op = .store, @@ -361,8 +178,8 @@ pub fn main() !void { .p_dependencies = @ptrCast(&subpass_dependency), }; - const render_pass = try vkd.createRenderPass(device, &render_pass_create_info, null); - defer vkd.destroyRenderPass(device, render_pass, null); + const render_pass = try ldc.vkd.createRenderPass(ldc.device, &render_pass_create_info, null); + defer ldc.vkd.destroyRenderPass(ldc.device, render_pass, null); std.debug.print("created render pass\n", .{}); @@ -374,24 +191,24 @@ pub fn main() !void { // --------------------------------------------------------------------- const framebuffers = try std.heap.page_allocator.alloc( vk.Framebuffer, - swapchain_image_views.len, + swapchain_context.image_views.len, ); defer std.heap.page_allocator.free(framebuffers); - for (swapchain_image_views, 0..) |image_view, i| { + 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, .attachment_count = attachments.len, .p_attachments = &attachments, - .width = chosen_extent.width, - .height = chosen_extent.height, + .width = swapchain_context.extent.width, + .height = swapchain_context.extent.height, .layers = 1, }; - framebuffers[i] = try vkd.createFramebuffer( - device, + framebuffers[i] = try ldc.vkd.createFramebuffer( + ldc.device, &framebuffer_create_info, null, ); @@ -399,7 +216,7 @@ pub fn main() !void { defer { for (framebuffers) |framebuffer| { - vkd.destroyFramebuffer(device, framebuffer, null); + ldc.vkd.destroyFramebuffer(ldc.device, framebuffer, null); } } @@ -419,15 +236,15 @@ pub fn main() !void { .flags = .{ .reset_command_buffer_bit = true, }, - .queue_family_index = graphics_queue_family_index, + .queue_family_index = ldc.graphics_queue_family_index, }; - const command_pool = try vkd.createCommandPool( - device, + const command_pool = try ldc.vkd.createCommandPool( + ldc.device, &command_pool_create_info, null, ); - defer vkd.destroyCommandPool(device, command_pool, null); + defer ldc.vkd.destroyCommandPool(ldc.device, command_pool, null); std.debug.print("created command pool\n", .{}); @@ -443,8 +260,8 @@ pub fn main() !void { .command_buffer_count = @intCast(command_buffers.len), }; - try vkd.allocateCommandBuffers( - device, + try ldc.vkd.allocateCommandBuffers( + ldc.device, &command_buffer_allocate_info, command_buffers.ptr, ); @@ -454,7 +271,7 @@ pub fn main() !void { for (command_buffers, 0..) |command_buffer, i| { const begin_info = vk.CommandBufferBeginInfo{}; - try vkd.beginCommandBuffer(command_buffer, &begin_info); + 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. @@ -469,21 +286,21 @@ pub fn main() !void { .framebuffer = framebuffers[i], .render_area = .{ .offset = .{ .x = 0, .y = 0 }, - .extent = chosen_extent, + .extent = swapchain_context.extent, }, .clear_value_count = 1, .p_clear_values = @ptrCast(&clear_color), }; - vkd.cmdBeginRenderPass( + ldc.vkd.cmdBeginRenderPass( command_buffer, &render_pass_begin_info, .@"inline", ); - vkd.cmdEndRenderPass(command_buffer); + ldc.vkd.cmdEndRenderPass(command_buffer); - try vkd.endCommandBuffer(command_buffer); + try ldc.vkd.endCommandBuffer(command_buffer); } std.debug.print("recorded command buffers\n", .{}); @@ -501,19 +318,19 @@ pub fn main() !void { // --------------------------------------------------------------------- const semaphore_create_info = vk.SemaphoreCreateInfo{}; - const image_available_semaphore = try vkd.createSemaphore( - device, + const image_available_semaphore = try ldc.vkd.createSemaphore( + ldc.device, &semaphore_create_info, null, ); - defer vkd.destroySemaphore(device, image_available_semaphore, null); + defer ldc.vkd.destroySemaphore(ldc.device, image_available_semaphore, null); - const render_finished_semaphore = try vkd.createSemaphore( - device, + const render_finished_semaphore = try ldc.vkd.createSemaphore( + ldc.device, &semaphore_create_info, null, ); - defer vkd.destroySemaphore(device, render_finished_semaphore, null); + defer ldc.vkd.destroySemaphore(ldc.device, render_finished_semaphore, null); const fence_create_info = vk.FenceCreateInfo{ .flags = .{ @@ -521,12 +338,12 @@ pub fn main() !void { }, }; - const in_flight_fence = try vkd.createFence( - device, + const in_flight_fence = try ldc.vkd.createFence( + ldc.device, &fence_create_info, null, ); - defer vkd.destroyFence(device, in_flight_fence, null); + defer ldc.vkd.destroyFence(ldc.device, in_flight_fence, null); std.debug.print("created synchronization objects\n", .{}); @@ -547,12 +364,12 @@ pub fn main() !void { // recreateSwapchainResources(). // --------------------------------------------------------------------- const wait_fences = [_]vk.Fence{in_flight_fence}; - _ = try vkd.waitForFences(device, &wait_fences, .true, std.math.maxInt(u64)); - try vkd.resetFences(device, &wait_fences); + _ = try ldc.vkd.waitForFences(ldc.device, &wait_fences, .true, std.math.maxInt(u64)); + try ldc.vkd.resetFences(ldc.device, &wait_fences); - const acquire_result = try vkd.acquireNextImageKHR( - device, - swapchain, + const acquire_result = try ldc.vkd.acquireNextImageKHR( + ldc.device, + swapchain_context.swapchain, std.math.maxInt(u64), image_available_semaphore, .null_handle, @@ -582,9 +399,9 @@ pub fn main() !void { .p_signal_semaphores = &signal_semaphores, }; - try vkd.queueSubmit(graphics_queue, &[_]vk.SubmitInfo{submit_info}, in_flight_fence); + try ldc.vkd.queueSubmit(ldc.graphics_queue, &[_]vk.SubmitInfo{submit_info}, in_flight_fence); - const present_swapchains = [_]vk.SwapchainKHR{swapchain}; + const present_swapchains = [_]vk.SwapchainKHR{swapchain_context.swapchain}; const present_image_indices = [_]u32{image_index}; const present_info = vk.PresentInfoKHR{ @@ -595,7 +412,7 @@ pub fn main() !void { .p_image_indices = &present_image_indices, }; - _ = try vkd.queuePresentKHR(graphics_queue, &present_info); + _ = try ldc.vkd.queuePresentKHR(ldc.graphics_queue, &present_info); std.debug.print("presented one frame\n", .{}); @@ -672,6 +489,231 @@ fn initInstance(name: [:0]const u8) !VulkanContext { }; } +fn initLogicalDevice( + vc: 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, + }; +} + +fn initSwapchain( + vc: VulkanContext, + ldc: 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); + + std.debug.print("surface formats: {}\n", .{surface_formats.len}); + for (surface_formats, 0..) |format, i| { + std.debug.print( + " format {}: format={any}, color_space={any}\n", + .{ i, format.format, format.color_space }, + ); + } + + const present_modes = try vc.vki.getPhysicalDeviceSurfacePresentModesAllocKHR( + ldc.physical_device, + surface, + allocator, + ); + defer allocator.free(present_modes); + + std.debug.print("present modes: {}\n", .{present_modes.len}); + for (present_modes, 0..) |mode, i| { + std.debug.print(" present mode {}: {any}\n", .{ i, mode }); + } + + 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, + }; +} + fn debugPhysicalGPUs(vc: VulkanContext, physical_devices: []vk.PhysicalDevice, surface: vk.SurfaceKHR) !void { std.debug.print("physical devices: {}\n", .{physical_devices.len});