diff --git a/shape/src/Core.zig b/shape/src/Core.zig index cea9876..e3955ad 100644 --- a/shape/src/Core.zig +++ b/shape/src/Core.zig @@ -24,10 +24,12 @@ const apis: []const vk.ApiInfo = &(.{ else => .{}, }); -const Base = vk.BaseWrapper(apis); -const Instance = vk.InstanceProxy(apis); -const Device = vk.DeviceProxy(apis); -const Queue = vk.QueueProxy(apis); +pub const Base = vk.BaseWrapper(apis); +pub const Instance = vk.InstanceProxy(apis); +pub const Device = vk.DeviceProxy(apis); +pub const Queue = vk.QueueProxy(apis); + +pub const SwapChain = @import("swapchain.zig").SwapChain; const Core = @This(); diff --git a/shape/src/SwapChain.zig b/shape/src/SwapChain.zig new file mode 100644 index 0000000..4bce349 --- /dev/null +++ b/shape/src/SwapChain.zig @@ -0,0 +1,282 @@ +const std = @import("std"); +const vk = @import("vk"); + +const Core = @import("Core.zig"); + +fn _choose_format(core: *const Core, alloc: std.mem.Allocator) !vk.SurfaceFormatKHR { + const formats = try core.i.getPhysicalDeviceSurfaceFormatsAllocKHR( + core.pdev, + core.surface, + alloc, + ); + defer alloc.free(formats); + + for (formats) |format| { + if (format.color_space == .srgb_nonlinear_khr) return format; + } else { + return formats[0]; + } +} + +fn _choose_mode(core: *const Core, alloc: std.mem.Allocator) !vk.PresentModeKHR { + _ = core; + _ = alloc; + + return .fifo_khr; +} + +const Self = @This(); + +pub const Target = struct { + image_index: u32, + flight_index: u32, + image: vk.Image, + view: vk.ImageView, + acquired: vk.Semaphore, // this semaphore will be signaled when the target is acquired + complete: vk.Semaphore, // this semaphore should be signaled when the render is complete + available: vk.Fence, // this fence should be signaled when the target flight is available + + pub fn top_of_pipe_barrier(target: Target) vk.ImageMemoryBarrier { + return vk.ImageMemoryBarrier{ + .src_access_mask = .{}, + .dst_access_mask = .{ .color_attachment_write_bit = true }, + .old_layout = .undefined, + .new_layout = .color_attachment_optimal, + .src_queue_family_index = 0, + .dst_queue_family_index = 0, + .image = target.image, + .subresource_range = .{ + .aspect_mask = .{ .color_bit = true }, + .base_mip_level = 0, + .level_count = 1, + .base_array_layer = 0, + .layer_count = 1, + }, + }; + } + + pub fn bottom_of_pipe_barrier(target: Target) vk.ImageMemoryBarrier { + return vk.ImageMemoryBarrier{ + .src_access_mask = .{ .color_attachment_write_bit = true }, + .dst_access_mask = .{}, + .old_layout = .color_attachment_optimal, + .new_layout = .present_src_khr, + .src_queue_family_index = 0, + .dst_queue_family_index = 0, + .image = target.image, + .subresource_range = .{ + .aspect_mask = .{ .color_bit = true }, + .base_mip_level = 0, + .level_count = 1, + .base_array_layer = 0, + .layer_count = 1, + }, + }; + } +}; + +const Sync = struct { + acquired: vk.Semaphore = .null_handle, + complete: vk.Semaphore = .null_handle, + available: vk.Fence = .null_handle, +}; + +const View = struct { + image: vk.Image, + view: vk.ImageView, +}; + +core: *const Core, +alloc: std.mem.Allocator, + +frames_in_flight: u32, +flight_index: usize = 0, +flight_syncs: std.MultiArrayList(Sync) = .{}, + +cinfo: vk.SwapchainCreateInfoKHR = undefined, +handle: vk.SwapchainKHR = .null_handle, + +chain: std.MultiArrayList(View) = .{}, + +pub fn init(core: *const Core, alloc: std.mem.Allocator, frames_in_flight: usize) !Self { + var self: Self = .{ + .core = core, + .alloc = alloc, + .frames_in_flight = @intCast(frames_in_flight), + }; + errdefer self.deinit(); + + try self.flight_syncs.resize(alloc, frames_in_flight); + for (self.flight_syncs.items(.acquired)) |*sem| + sem.* = try core.d.createSemaphore(&.{}, null); + for (self.flight_syncs.items(.complete)) |*sem| + sem.* = try core.d.createSemaphore(&.{}, null); + for (self.flight_syncs.items(.available)) |*fnc| + fnc.* = try core.d.createFence(&.{ .flags = .{ .signaled_bit = true } }, null); + + const caps = try core.i.getPhysicalDeviceSurfaceCapabilitiesKHR(core.pdev, core.surface); + const format = try _choose_format(core, alloc); + const mode = try _choose_mode(core, alloc); + + self.cinfo = .{ + .surface = core.surface, + .min_image_count = std.math.clamp( + @min(3, caps.min_image_count + 1), + caps.min_image_count, + if (caps.max_image_count > 0) caps.max_image_count else 127, + ), + .image_format = format.format, + .image_color_space = format.color_space, + .image_extent = undefined, // set in rebuild + .image_array_layers = 1, + .image_usage = .{ .color_attachment_bit = true }, + .image_sharing_mode = .exclusive, + .pre_transform = .{ .identity_bit_khr = true }, + .composite_alpha = .{ .opaque_bit_khr = true }, + .present_mode = mode, + .clipped = vk.TRUE, + .old_swapchain = .null_handle, + }; + + // try self.rebuild(); + + return self; +} + +pub fn rebuild(self: *Self) !void { + const caps = try self.core.i.getPhysicalDeviceSurfaceCapabilitiesKHR(self.core.pdev, self.core.surface); + self.cinfo.image_extent = caps.current_extent; + + self.handle = try self.core.d.createSwapchainKHR(&self.cinfo, null); + self.core.d.destroySwapchainKHR(self.cinfo.old_swapchain, null); + self.cinfo.old_swapchain = self.handle; + + for (self.chain.items(.view)) |view| self.core.d.destroyImageView(view, null); + @memset(self.chain.items(.view), .null_handle); + @memset(self.chain.items(.image), .null_handle); + + var count: u32 = undefined; + std.debug.assert( + .success == try self.core.d.getSwapchainImagesKHR( + self.handle, + &count, + null, + ), + ); + try self.chain.resize(self.alloc, count); + std.debug.assert( + .success == try self.core.d.getSwapchainImagesKHR( + self.handle, + &count, + self.chain.items(.image).ptr, + ), + ); + + for (self.chain.items(.image), self.chain.items(.view)) |image, *view| { + view.* = try self.core.d.createImageView(&vk.ImageViewCreateInfo{ + .image = image, + .view_type = .@"2d", + .format = self.cinfo.image_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, + }, + }, null); + } +} + +pub fn deinit(self: *Self) void { + // The easiest way to ensure fences and semaphores are not in use for deletion. If it fails, the device must + // have failed somehow and sync is not necessary so just continue with cleanup. + self.core.d.deviceWaitIdle() catch {}; + + // images are owned by swapchain and not explicitly destroyed + for (self.chain.items(.view)) |view| self.core.d.destroyImageView(view, null); + self.chain.deinit(self.alloc); + + self.core.d.destroySwapchainKHR(self.handle, null); + + for (self.flight_syncs.items(.acquired)) |sem| self.core.d.destroySemaphore(sem, null); + for (self.flight_syncs.items(.complete)) |sem| self.core.d.destroySemaphore(sem, null); + for (self.flight_syncs.items(.available)) |fnc| self.core.d.destroyFence(fnc, null); + self.flight_syncs.deinit(self.alloc); +} + +pub fn acquire(self: *Self) !?Target { + var target: Target = .{ + .flight_index = @intCast(self.flight_index), + .acquired = self.flight_syncs.items(.acquired)[self.flight_index], + .complete = self.flight_syncs.items(.complete)[self.flight_index], + .available = self.flight_syncs.items(.available)[self.flight_index], + .image = undefined, + .view = undefined, + .image_index = undefined, + }; + + const timeout = std.math.maxInt(u64); + + std.debug.assert(.success == try self.core.d.waitForFences( + 1, + &.{target.available}, + vk.TRUE, + std.math.maxInt(u64), + )); + + // two attempts + + target.image_index = for (0..2) |_| { + if (self.handle == .null_handle) try self.rebuild(); + + if (self.core.d.acquireNextImageKHR( + self.handle, + timeout, + target.acquired, + .null_handle, + )) |res| switch (res.result) { + .success, .suboptimal_khr => break res.image_index, + else => unreachable, + } else |err| switch (err) { + error.OutOfDateKHR => { + self.handle = .null_handle; + }, + else => return err, + } + } else { + return null; + }; + + target.image = self.chain.items(.image)[target.image_index]; + target.view = self.chain.items(.view)[target.image_index]; + + try self.core.d.resetFences(1, &.{target.available}); + self.flight_index = @mod(self.flight_index + 1, self.flight_syncs.len); + + return target; +} + +pub fn present(self: *Self, target: Target) !void { + if (self.core.q.presentKHR(&vk.PresentInfoKHR{ + .wait_semaphore_count = 1, + .p_wait_semaphores = &.{target.complete}, + .swapchain_count = 1, + .p_swapchains = &.{self.handle}, + .p_image_indices = &.{target.image_index}, + .p_results = null, + })) |res| switch (res) { + .success => {}, + .suboptimal_khr => self.handle = .null_handle, + else => unreachable, + } else |err| switch (err) { + error.OutOfDateKHR => self.handle = .null_handle, + else => return err, + } +} diff --git a/shape/src/main.zig b/shape/src/main.zig index 1f85a61..7c5bdd8 100644 --- a/shape/src/main.zig +++ b/shape/src/main.zig @@ -17,6 +17,9 @@ pub fn main() !void { std.log.debug("Created queue: {any}", .{core.q.handle}); std.log.debug("Created surface: {any}", .{core.surface}); + var sc = try Core.SwapChain(void).init(alloc, &core, 3); + defer sc.deinit(); + // var caps = try I.getPhysicalDeviceSurfaceCapabilitiesKHR(pdev, surface); // const format = search: { // const formats = try I.getPhysicalDeviceSurfaceFormatsAllocKHR(pdev, surface, alloc);