diff --git a/shaders/square.vert b/shaders/square.vert index cd5443a..0ced442 100644 --- a/shaders/square.vert +++ b/shaders/square.vert @@ -1,7 +1,11 @@ #version 450 -layout(location = 0) in vec2 in_position; +vec2 positions[3] = vec2[]( + vec2(0.0, -0.5), + vec2(0.5, 0.5), + vec2(-0.5, 0.5) +); void main() { - gl_Position = vec4(in_position, 0.0, 1.0); + gl_Position = vec4(positions[gl_VertexIndex], 0.0, 1.0); } diff --git a/src/main.zig b/src/main.zig index d0b6c63..8bcc287 100644 --- a/src/main.zig +++ b/src/main.zig @@ -2,54 +2,22 @@ const std = @import("std"); const glfw = @import("zglfw"); const vk = @import("vulkan"); +const window_mod = @import("window.zig"); +const commands_mod = @import("vulkan/commands.zig"); +const context_mod = @import("vulkan/context.zig"); +const device_mod = @import("vulkan/device.zig"); +const frame_mod = @import("vulkan/frame.zig"); +const framebuffers_mod = @import("vulkan/framebuffers.zig"); +const render_pass_mod = @import("vulkan/render_pass.zig"); +const swapchain_mod = @import("vulkan/swapchain.zig"); +const sync_mod = @import("vulkan/sync.zig"); + // The build script compiles these GLSL files to SPIR-V and exposes them as // anonymous imports. They are currently only loaded and printed; the program // does not create shader modules or a graphics pipeline yet. const square_vert_spv = @embedFile("square_vertex_shader"); const square_frag_spv = @embedFile("square_fragment_shader"); -const VulkanContext = struct { - base: vk.BaseWrapper, - instance: vk.Instance, - vki: vk.InstanceWrapper, - - fn destroy(self: *const VulkanContext) void { - self.vki.destroyInstance(self.instance, null); - } -}; - -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}); @@ -58,8 +26,7 @@ pub fn main() !void { // --------------------------------------------------------------------- // Window bootstrap // --------------------------------------------------------------------- - - const window = try initWindow(800, 600, "zig-chess"); + const window = try window_mod.initWindow(800, 600, "zig-chess"); defer glfw.terminate(); defer window.destroy(); @@ -69,16 +36,11 @@ pub fn main() !void { // --------------------------------------------------------------------- // Vulkan instance setup // --------------------------------------------------------------------- - - const vc = try initInstance("zig-chess"); + const vc = try context_mod.initInstance("zig-chess"); defer vc.destroy(); // --------------------------------------------------------------------- // Window surface - // - // This connects the platform window to Vulkan presentation. Swapchain - // support and present-capable queue families are queried against this - // surface. // --------------------------------------------------------------------- var surface: vk.SurfaceKHR = undefined; try glfw.createWindowSurface(vc.instance, window, null, &surface); @@ -88,10 +50,9 @@ pub fn main() !void { // --------------------------------------------------------------------- // Physical device and queue-family discovery // --------------------------------------------------------------------- - const physical_devices = try vc.vki.enumeratePhysicalDevicesAlloc(vc.instance, std.heap.page_allocator); defer std.heap.page_allocator.free(physical_devices); - //try debugPhysicalGPUs(vc, physical_devices, surface); + // try device_mod.debugPhysicalGPUs(vc, physical_devices, surface); // TODO(refactor): this is intentionally temporary and machine-specific. // Replace it with selection logic that searches for a device/queue pair @@ -112,642 +73,133 @@ pub fn main() !void { // --------------------------------------------------------------------- // Logical device and queue // --------------------------------------------------------------------- - const ldc = try initLogicalDevice(vc, selected_physical_device, graphics_queue_family_index); + const ldc = try device_mod.initLogicalDevice(vc, selected_physical_device, graphics_queue_family_index); defer ldc.destroy(); // --------------------------------------------------------------------- - // Swapchain setup + // Swapchain-dependent rendering resources // --------------------------------------------------------------------- - const swapchain_context = try initSwapchain(vc, ldc, surface, window, std.heap.page_allocator); + var swapchain_context = try swapchain_mod.initSwapchain(vc, ldc, surface, window, std.heap.page_allocator); defer swapchain_context.destroy(&ldc); - // --------------------------------------------------------------------- - // Render pass - // - // This render pass has one color attachment: the current swapchain image. - // load_op=.clear means each frame starts by clearing the image; final - // layout present_src_khr means the image is ready for presentation. - // - // Refactor direction: this can become createRenderPass(device, format). - // Later, when drawing pieces or UI, this may gain depth/stencil or change - // if we move to dynamic rendering. - // --------------------------------------------------------------------- - const color_attachment = vk.AttachmentDescription{ - .format = swapchain_context.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, - }; + var render_pass_context = try render_pass_mod.initRenderPass(ldc, swapchain_context.format.format); + defer render_pass_context.destroy(&ldc); - 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); - defer ldc.vkd.destroyRenderPass(ldc.device, render_pass, null); - - std.debug.print("created render pass\n", .{}); - - // --------------------------------------------------------------------- - // Framebuffers - // - // A framebuffer binds the render pass attachment description to a concrete - // image view. We need one framebuffer for each swapchain image view. - // --------------------------------------------------------------------- - const framebuffers = try std.heap.page_allocator.alloc( - vk.Framebuffer, - swapchain_context.image_views.len, + var framebuffer_context = try framebuffers_mod.initFramebuffers( + ldc, + render_pass_context, + swapchain_context, + std.heap.page_allocator, ); - defer std.heap.page_allocator.free(framebuffers); + defer framebuffer_context.destroy(&ldc); - 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 = swapchain_context.extent.width, - .height = swapchain_context.extent.height, - .layers = 1, - }; - - framebuffers[i] = try ldc.vkd.createFramebuffer( - ldc.device, - &framebuffer_create_info, - null, - ); - } - - defer { - for (framebuffers) |framebuffer| { - ldc.vkd.destroyFramebuffer(ldc.device, framebuffer, null); - } - } - - std.debug.print("created framebuffers: {}\n", .{framebuffers.len}); - - // --------------------------------------------------------------------- - // Command pool and command buffers - // - // Command buffers record GPU work. Right now each swapchain image gets one - // pre-recorded command buffer that only clears the image. - // - // Refactor direction: for frame generation, introduce recordCommandBuffer() - // and call it per frame after acquiring the image. That will make dynamic - // board drawing, highlights, and resize handling easier to reason about. - // --------------------------------------------------------------------- - 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, + var command_context = try commands_mod.initCommandBuffers( + ldc, + render_pass_context, + framebuffer_context, + swapchain_context, + std.heap.page_allocator, ); - defer ldc.vkd.destroyCommandPool(ldc.device, command_pool, null); - - std.debug.print("created command pool\n", .{}); - - const command_buffers = try std.heap.page_allocator.alloc( - vk.CommandBuffer, - framebuffers.len, - ); - defer std.heap.page_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, - .framebuffer = 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", .{}); + defer command_context.destroy(&ldc); // --------------------------------------------------------------------- // Synchronization objects - // - // The image-available semaphore is signaled when acquireNextImageKHR has a - // swapchain image ready. The render-finished semaphore is signaled when GPU - // rendering completes and presentation may wait on it. The fence lets the - // CPU wait until submitted GPU work for this frame is done. - // - // Refactor direction: use arrays for 2 frames in flight, e.g. - // image_available[2], render_finished[2], in_flight_fences[2]. // --------------------------------------------------------------------- - const semaphore_create_info = vk.SemaphoreCreateInfo{}; - - const image_available_semaphore = try ldc.vkd.createSemaphore( - ldc.device, - &semaphore_create_info, - null, - ); - defer ldc.vkd.destroySemaphore(ldc.device, image_available_semaphore, null); - - const render_finished_semaphore = try ldc.vkd.createSemaphore( - ldc.device, - &semaphore_create_info, - null, - ); - defer 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, - ); - defer ldc.vkd.destroyFence(ldc.device, in_flight_fence, null); - - std.debug.print("created synchronization objects\n", .{}); - - // --------------------------------------------------------------------- - // Single-frame acquire/submit/present - // - // This renders exactly one frame before entering the event loop. To turn - // this into frame generation, move this whole block into drawFrame() and - // call it from the window loop below. - // - // Per-frame shape: - // 1. wait/reset the in-flight fence - // 2. acquire the next swapchain image - // 3. submit the command buffer for that image - // 4. present that image - // - // Later this block must handle out-of-date/suboptimal swapchains and call - // recreateSwapchainResources(). - // --------------------------------------------------------------------- - const wait_fences = [_]vk.Fence{in_flight_fence}; - _ = 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 ldc.vkd.acquireNextImageKHR( - ldc.device, - swapchain_context.swapchain, - std.math.maxInt(u64), - image_available_semaphore, - .null_handle, - ); - - const image_index = acquire_result.image_index; - std.debug.print("acquired swapchain image: {}\n", .{image_index}); - - const wait_semaphores = [_]vk.Semaphore{image_available_semaphore}; - const wait_stages = [_]vk.PipelineStageFlags{ - .{ - .color_attachment_output_bit = true, - }, - }; - const signal_semaphores = [_]vk.Semaphore{render_finished_semaphore}; - const submit_command_buffers = [_]vk.CommandBuffer{ - 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}, 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, - }; - - _ = try ldc.vkd.queuePresentKHR(ldc.graphics_queue, &present_info); - - std.debug.print("presented one frame\n", .{}); + const sync_context = try sync_mod.initSyncObjects(ldc); + defer sync_context.destroy(&ldc); // --------------------------------------------------------------------- // Event loop - // - // Currently this only keeps the window alive after the one presented frame. - // Next rendering milestone: call drawFrame() each iteration after polling - // events, then wait for the device to be idle before cleanup on exit. // --------------------------------------------------------------------- while (!window.shouldClose()) { glfw.pollEvents(); + const result = try frame_mod.drawFrame(ldc, swapchain_context, command_context, sync_context); + if (result == .Recreate) { + try recreateSwapchain( + vc, + ldc, + surface, + window, + &swapchain_context, + &render_pass_context, + &framebuffer_context, + &command_context, + std.heap.page_allocator, + ); + } } + + try ldc.vkd.deviceWaitIdle(ldc.device); } -fn initWindow(x: c_int, y: c_int, name: [:0]const u8) !*glfw.Window { - try glfw.init(); - errdefer glfw.terminate(); - - glfw.windowHint(.client_api, .no_api); - const window = try glfw.Window.create( - x, - y, - name, - null, - null, - ); - - std.debug.print("GLFW platform: {any}\n", .{glfw.getPlatform()}); - std.debug.print("Vulkan supported by GLFW: {}\n", .{glfw.isVulkanSupported()}); - - const size = window.getSize(); - const fb_size = window.getFramebufferSize(); - - std.debug.print("Window size: {}x{}\n", .{ size[0], size[1] }); - std.debug.print("Framebuffer size: {}x{}\n", .{ fb_size[0], fb_size[1] }); - std.debug.print("Window visible: {}\n", .{window.getAttribute(.visible)}); - - return window; -} - -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", .{}); - - const vki = vk.InstanceWrapper.load(instance, base.dispatch.vkGetInstanceProcAddr.?); - - return .{ - .instance = instance, - .base = base, - .vki = vki, - }; -} - -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, +fn recreateSwapchain( + vc: context_mod.VulkanContext, + ldc: device_mod.LogicalDeviceContext, surface: vk.SurfaceKHR, window: *glfw.Window, + swapchain_context: *swapchain_mod.SwapchainContext, + render_pass_context: *render_pass_mod.RenderPassContext, + framebuffer_context: *framebuffers_mod.FramebufferContext, + command_context: *commands_mod.CommandContext, 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; +) !void { + // A minimized window can report a zero-sized framebuffer. Vulkan swapchain + // extents must be non-zero, so wait here until the window has drawable size + // again before trying to recreate swapchain-dependent resources. + while (!window.shouldClose()) { + const framebuffer_size = window.getFramebufferSize(); + if (framebuffer_size[0] != 0 and framebuffer_size[1] != 0) { break; } + glfw.waitEvents(); } - var chosen_present_mode: vk.PresentModeKHR = .fifo_khr; - for (present_modes) |mode| { - if (mode == .fifo_khr) { - chosen_present_mode = mode; - break; - } + if (window.shouldClose()) { + return; } - const framebuffer_size = window.getFramebufferSize(); + // Make sure the GPU is no longer using the old swapchain, framebuffers, or + // command buffers before we destroy them. + try ldc.vkd.deviceWaitIdle(ldc.device); - 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]), - }; + // Build the replacement resource chain first. If any allocation/creation + // fails, the old contexts are still intact and their existing defers remain + // valid during error unwinding. + const new_swapchain_context = try swapchain_mod.initSwapchain(vc, ldc, surface, window, allocator); + errdefer new_swapchain_context.destroy(&ldc); - 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; - } + const new_render_pass_context = try render_pass_mod.initRenderPass(ldc, new_swapchain_context.format.format); + errdefer new_render_pass_context.destroy(&ldc); - 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, + const new_framebuffer_context = try framebuffers_mod.initFramebuffers( + ldc, + new_render_pass_context, + new_swapchain_context, allocator, ); - errdefer allocator.free(swapchain_images); - std.debug.print("swapchain images: {}\n", .{swapchain_images.len}); + errdefer new_framebuffer_context.destroy(&ldc); - const swapchain_image_views = try allocator.alloc(vk.ImageView, swapchain_images.len); - errdefer allocator.free(swapchain_image_views); + const new_command_context = try commands_mod.initCommandBuffers( + ldc, + new_render_pass_context, + new_framebuffer_context, + new_swapchain_context, + allocator, + ); + errdefer new_command_context.destroy(&ldc); - 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); - } - } + // Now that the full replacement chain exists, release the old chain in + // dependency order: command buffers/pool -> framebuffers -> render pass -> + // swapchain/image views/images. + command_context.destroy(&ldc); + framebuffer_context.destroy(&ldc); + render_pass_context.destroy(&ldc); + swapchain_context.destroy(&ldc); - 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_context.* = new_swapchain_context; + render_pass_context.* = new_render_pass_context; + framebuffer_context.* = new_framebuffer_context; + command_context.* = new_command_context; - 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}); - - 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, - }, - ); - } - } + std.debug.print("recreated swapchain resources\n", .{}); } diff --git a/src/vulkan/commands.zig b/src/vulkan/commands.zig new file mode 100644 index 0000000..ff5afc8 --- /dev/null +++ b/src/vulkan/commands.zig @@ -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, + }; +} diff --git a/src/vulkan/context.zig b/src/vulkan/context.zig new file mode 100644 index 0000000..1d44d48 --- /dev/null +++ b/src/vulkan/context.zig @@ -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, + }; +} diff --git a/src/vulkan/device.zig b/src/vulkan/device.zig new file mode 100644 index 0000000..d7cdb7b --- /dev/null +++ b/src/vulkan/device.zig @@ -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, + }, + ); + } + } +} diff --git a/src/vulkan/frame.zig b/src/vulkan/frame.zig new file mode 100644 index 0000000..a2122d9 --- /dev/null +++ b/src/vulkan/frame.zig @@ -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; +} diff --git a/src/vulkan/framebuffers.zig b/src/vulkan/framebuffers.zig new file mode 100644 index 0000000..97774b3 --- /dev/null +++ b/src/vulkan/framebuffers.zig @@ -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, + }; +} diff --git a/src/vulkan/pipeline.zig b/src/vulkan/pipeline.zig new file mode 100644 index 0000000..b2e47bb --- /dev/null +++ b/src/vulkan/pipeline.zig @@ -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); +} diff --git a/src/vulkan/render_pass.zig b/src/vulkan/render_pass.zig new file mode 100644 index 0000000..6a31520 --- /dev/null +++ b/src/vulkan/render_pass.zig @@ -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 }; +} diff --git a/src/vulkan/swapchain.zig b/src/vulkan/swapchain.zig new file mode 100644 index 0000000..c69b5a6 --- /dev/null +++ b/src/vulkan/swapchain.zig @@ -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, + }; +} diff --git a/src/vulkan/sync.zig b/src/vulkan/sync.zig new file mode 100644 index 0000000..211bb9d --- /dev/null +++ b/src/vulkan/sync.zig @@ -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, + }; +} diff --git a/src/window.zig b/src/window.zig new file mode 100644 index 0000000..7ca0856 --- /dev/null +++ b/src/window.zig @@ -0,0 +1,28 @@ +const std = @import("std"); +const glfw = @import("zglfw"); + +pub fn initWindow(x: c_int, y: c_int, name: [:0]const u8) !*glfw.Window { + try glfw.init(); + errdefer glfw.terminate(); + + glfw.windowHint(.client_api, .no_api); + const window = try glfw.Window.create( + x, + y, + name, + null, + null, + ); + + std.debug.print("GLFW platform: {any}\n", .{glfw.getPlatform()}); + std.debug.print("Vulkan supported by GLFW: {}\n", .{glfw.isVulkanSupported()}); + + const size = window.getSize(); + const fb_size = window.getFramebufferSize(); + + std.debug.print("Window size: {}x{}\n", .{ size[0], size[1] }); + std.debug.print("Framebuffer size: {}x{}\n", .{ fb_size[0], fb_size[1] }); + std.debug.print("Window visible: {}\n", .{window.getAttribute(.visible)}); + + return window; +}