diff --git a/src/nu/Render.zig b/src/nu/Render.zig index ed243a4..b74c49a 100644 --- a/src/nu/Render.zig +++ b/src/nu/Render.zig @@ -10,7 +10,7 @@ const nu = @import("../nu.zig"); // const au = @import("Render/au.zig"); const ctx = @import("Render/ctx.zig"); -const swap_chain = @import("Render/swap_chain.zig"); +const SwapChain = @import("Render/SwapChain.zig"); pub const Config = struct { app_name: [*:0]const u8 = "nu-au-app", @@ -36,7 +36,6 @@ const config = nu.config.render; pub const depends = .{nu.Window}; -const SwapChain = swap_chain.SwapChain(Flight); const Flight = struct { pool: vk.CommandPool = .null_handle, cmd: vk.CommandBuffer = .null_handle, @@ -84,7 +83,7 @@ pub fn setup(alloc: std.mem.Allocator) !void { errdefer for (_flights) |flight| flight.deinit(); for (_flights) |*flight| flight.* = try Flight.init(); - _sc = try SwapChain.init(alloc, _flights); + _sc = try SwapChain.init(alloc, _flights.len); errdefer _sc.deinit(); } @@ -96,14 +95,15 @@ pub fn teardown() void { pub fn render() !void { const target = try _sc.acquire(); + const flight = &_flights[target.flight_index]; const render_area: vk.Rect2D = .{ .offset = .{ .x = 0, .y = 0 }, .extent = _sc.cinfo.image_extent, }; - try ctx.D.resetCommandPool(target.flight.pool, .{}); - var cmd = ctx.CommandBufferProxy.init(target.flight.cmd, ctx.dw); + try ctx.D.resetCommandPool(flight.pool, .{}); + var cmd = ctx.CommandBufferProxy.init(flight.cmd, ctx.dw); try cmd.beginCommandBuffer(&.{ .flags = .{ .one_time_submit_bit = true } }); { @@ -116,24 +116,7 @@ pub fn render() !void { 0, null, 1, - &.{ - 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, // values are the same; no transfer occurs - .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, - }, - }, - }, + &.{target.top_of_pipe()}, ); cmd.beginRendering(&vk.RenderingInfo{ @@ -166,24 +149,7 @@ pub fn render() !void { 0, null, 1, - &.{ - 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, // values are the same; no transfer occurs. - .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, - }, - }, - }, + &.{target.bottom_of_pipe()}, ); } try cmd.endCommandBuffer(); @@ -201,7 +167,7 @@ pub fn render() !void { vk.PipelineStageFlags{ .color_attachment_output_bit = true }, }, .command_buffer_count = 1, - .p_command_buffers = &.{target.flight.cmd}, + .p_command_buffers = &.{flight.cmd}, .signal_semaphore_count = 1, .p_signal_semaphores = &.{target.complete}, }, diff --git a/src/nu/Render/SwapChain.zig b/src/nu/Render/SwapChain.zig new file mode 100644 index 0000000..b2df68d --- /dev/null +++ b/src/nu/Render/SwapChain.zig @@ -0,0 +1,301 @@ +const std = @import("std"); +const vk = @import("vk"); +const ctx = @import("ctx.zig"); + +fn _choose_format(alloc: std.mem.Allocator) !vk.SurfaceFormatKHR { + var count: u32 = undefined; + std.debug.assert(.success == try ctx.I.getPhysicalDeviceSurfaceFormatsKHR( + ctx.pdevice.*, + ctx.surface.*, + &count, + null, + )); + const formats = try alloc.alloc(vk.SurfaceFormatKHR, count); + defer alloc.free(formats); + std.debug.assert(.success == try ctx.I.getPhysicalDeviceSurfaceFormatsKHR( + ctx.pdevice.*, + ctx.surface.*, + &count, + formats.ptr, + )); + + for (formats) |format| { + if (format.color_space == .srgb_nonlinear_khr) return format; + } else { + return formats[0]; + } +} + +fn _choose_mode(alloc: std.mem.Allocator) !vk.PresentModeKHR { + _ = ctx; + _ = 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(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(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, +}; + +alloc: std.mem.Allocator, + +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(alloc: std.mem.Allocator, flight_count: usize) !Self { + var self: Self = .{ + .alloc = alloc, + }; + errdefer self.deinit(); + + try self.flight_syncs.resize(alloc, flight_count); + for (self.flight_syncs.items(.acquired)) |*sem| + sem.* = try ctx.D.createSemaphore(&.{}, null); + for (self.flight_syncs.items(.complete)) |*sem| + sem.* = try ctx.D.createSemaphore(&.{}, null); + for (self.flight_syncs.items(.available)) |*fnc| + fnc.* = try ctx.D.createFence(&.{ .flags = .{ .signaled_bit = true } }, null); + + const caps = try ctx.getPhysicalDeviceSurfaceCapabilities(); + const format = try _choose_format(alloc); + const mode = try _choose_mode(alloc); + + self.cinfo = .{ + .surface = ctx.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, + }; + + return self; +} + +fn rebuild(self: *Self) !void { + std.debug.assert(self.handle == .null_handle); // don't rebuild if we weren't marked + + const caps = try ctx.getPhysicalDeviceSurfaceCapabilities(); + self.cinfo.image_extent = caps.current_extent; + + self.handle = try ctx.D.createSwapchainKHR(&self.cinfo, null); + ctx.D.destroySwapchainKHR(self.cinfo.old_swapchain, null); + self.cinfo.old_swapchain = self.handle; + + for (self.chain.items(.view)) |view| ctx.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 ctx.D.getSwapchainImagesKHR( + self.handle, + &count, + null, + ), + ); + try self.chain.resize(self.alloc, count); + std.debug.assert( + .success == try ctx.D.getSwapchainImagesKHR( + self.handle, + &count, + self.chain.items(.image).ptr, + ), + ); + + for (self.chain.items(.image), self.chain.items(.view)) |image, *view| { + view.* = try ctx.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. + ctx.D.deviceWaitIdle() catch {}; + + // images are owned by swapchain and not explicitly destroyed + for (self.chain.items(.view)) |view| ctx.D.destroyImageView(view, null); + self.chain.deinit(self.alloc); + + ctx.D.destroySwapchainKHR(self.handle, null); + + for (self.flight_syncs.items(.acquired)) |sem| ctx.D.destroySemaphore(sem, null); + for (self.flight_syncs.items(.complete)) |sem| ctx.D.destroySemaphore(sem, null); + for (self.flight_syncs.items(.available)) |fnc| ctx.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); + + for (0..5) |_| { + if (self.handle == .null_handle) { + try self.rebuild(); + std.debug.assert(self.handle != .null_handle); + } + + const fences: [1]vk.Fence = .{target.available}; + std.debug.assert(.success == try ctx.D.waitForFences( + 1, + &fences, + vk.TRUE, + std.math.maxInt(u64), + )); + + if (ctx.D.acquireNextImageKHR( + self.handle, + timeout, + target.acquired, + .null_handle, + )) |res| { + switch (res.result) { + .success, .suboptimal_khr => {}, + else => unreachable, + } + + target.image_index = res.image_index; + target.image = self.chain.items(.image)[res.image_index]; + target.view = self.chain.items(.view)[res.image_index]; + + try ctx.D.resetFences(1, &.{target.available}); + self.flight_index = @mod(self.flight_index + 1, self.flight_syncs.len); + + return target; + } else |err| switch (err) { + error.OutOfDateKHR => { + self.handle = .null_handle; + continue; + }, + else => return err, + } + } else { + return error.CannotRecreateSwapchain; + } +} + +pub fn present(self: *Self, target: Target) !void { + if (ctx.Q.presentKHR(&vk.PresentInfoKHR{ + .wait_semaphore_count = 1, // todo extra semaphores? + .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; + return; + }, + else => unreachable, + } + } else |err| switch (err) { + error.OutOfDateKHR => { + self.handle = .null_handle; + std.log.debug("Dropped frame", .{}); + return; + }, + else => return err, + } +} diff --git a/src/nu/Render/au/SwapChain.zig b/src/nu/Render/au/SwapChain.zig deleted file mode 100644 index 17c2bd8..0000000 --- a/src/nu/Render/au/SwapChain.zig +++ /dev/null @@ -1,213 +0,0 @@ -const std = @import("std"); -const au = @import("../au.zig"); -const vk = @import("vk"); - -const Self = @This(); - -alloc: std.mem.Allocator, -cinfo: vk.SwapchainCreateInfoKHR, -handle: vk.SwapchainKHR = .null_handle, -images: std.ArrayListUnmanaged(vk.Image) = .{}, -views: std.ArrayListUnmanaged(vk.ImageView) = .{}, - -pub fn init(alloc: std.mem.Allocator) !Self { - const caps = try au.I.getPhysicalDeviceSurfaceCapabilitiesKHR(au.device_config.pdev, au.S.*); - - var min_image_count = @max(3, caps.min_image_count + 1); // todo magic numbers - if (caps.max_image_count > 0) { - min_image_count = @min(min_image_count, caps.max_image_count); - } - - // determine format - const format = au.device_config.format; - - return .{ - .alloc = alloc, - .cinfo = .{ - .surface = au.S.*, - .min_image_count = min_image_count, - .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 = au.device_config.mode, - .clipped = vk.TRUE, - .old_swapchain = .null_handle, - }, - }; -} - -pub fn deinit(self: *Self) void { - for (self.views.items) |view| { - au.D.destroyImageView(view, null); - } - self.views.deinit(self.alloc); - - self.images.deinit(self.alloc); - - au.D.destroySwapchainKHR(self.handle, null); -} - -/// mark that the swapchain _should_ be rebuilt with the given extent -/// this function is reentrant, so the swapchain can be marked multiple times -/// and only one rebuild occur -pub fn mark(self: *Self) void { - self.handle = .null_handle; -} - -/// rebuild the swapchain only if it is marked. return true if the swapchain was rebuilt. -pub fn rebuild(self: *Self) !bool { - if (self.handle != .null_handle) return false; - - const caps = try au.I.getPhysicalDeviceSurfaceCapabilitiesKHR(au.device_config.pdev, self.cinfo.surface); - self.cinfo.image_extent = caps.current_extent; - - self.handle = try au.D.createSwapchainKHR(&self.cinfo, null); - au.D.destroySwapchainKHR(self.cinfo.old_swapchain, null); - self.cinfo.old_swapchain = self.handle; - - var count: u32 = undefined; - _ = try au.D.getSwapchainImagesKHR(self.handle, &count, null); - try self.images.resize(self.alloc, count); - _ = try au.D.getSwapchainImagesKHR(self.handle, &count, self.images.items.ptr); - - for (self.views.items) |view| { - au.D.destroyImageView(view, null); - } - try self.views.resize(self.alloc, count); - for (self.images.items, self.views.items) |image, *view| { - view.* = try au.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); - } - - return true; -} - -pub fn acquire(self: Self, semaphore: vk.Semaphore, fence: vk.Fence) !Target { - const acq = try au.D.acquireNextImageKHR( - self.handle, - std.math.maxInt(u64), - semaphore, - fence, - ); - return .{ - .idx = acq.image_index, - .image = self.images.items[acq.image_index], - .view = self.views.items[acq.image_index], - }; -} - -const Target = struct { - idx: u32, - image: vk.Image, - view: vk.ImageView, - - pub fn begin_rendering(self: Target, cmd: au.CommandBufferProxy, area: vk.Rect2D) void { - cmd.pipelineBarrier( - .{ .top_of_pipe_bit = true }, - .{ .color_attachment_output_bit = true }, - .{}, - 0, - null, - 0, - null, - 1, - &.{ - 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 = self.image, - .subresource_range = .{ - .aspect_mask = .{ .color_bit = true }, - .base_mip_level = 0, - .level_count = 1, - .base_array_layer = 0, - .layer_count = 1, - }, - }, - }, - ); - - cmd.beginRendering(&vk.RenderingInfo{ - .render_area = area, - .layer_count = 1, - .view_mask = 0, - .color_attachment_count = 1, - .p_color_attachments = &.{ - vk.RenderingAttachmentInfo{ - .image_view = self.view, - .image_layout = .color_attachment_optimal, - .resolve_mode = .{}, - .resolve_image_view = .null_handle, - .resolve_image_layout = .undefined, - .load_op = .clear, - .store_op = .store, - .clear_value = .{ .color = .{ .float_32 = .{ 0, 0, 0, 1 } } }, - }, - }, - }); - } - - pub fn end_rendering(self: Target, cmd: au.CommandBufferProxy) void { - cmd.endRendering(); - - cmd.pipelineBarrier( - .{ .color_attachment_output_bit = true }, - .{ .bottom_of_pipe_bit = true }, - .{}, - 0, - null, - 0, - null, - 1, - &.{ - 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 = self.image, - .subresource_range = .{ - .aspect_mask = .{ .color_bit = true }, - .base_mip_level = 0, - .level_count = 1, - .base_array_layer = 0, - .layer_count = 1, - }, - }, - }, - ); - } -}; - -pub fn present(self: Self, wait_semaphores: []const vk.Semaphore, target: Target) !vk.Result { - return try au.Q.presentKHR(&vk.PresentInfoKHR{ - .wait_semaphore_count = @intCast(wait_semaphores.len), - .p_wait_semaphores = wait_semaphores.ptr, - .swapchain_count = 1, - .p_swapchains = &.{self.handle}, - .p_image_indices = &.{target.idx}, - .p_results = null, - }); -} diff --git a/src/nu/Render/ctx.zig b/src/nu/Render/ctx.zig index 170331d..e3a2397 100644 --- a/src/nu/Render/ctx.zig +++ b/src/nu/Render/ctx.zig @@ -275,6 +275,10 @@ fn _destroy_device() void { D.destroyDevice(null); } +pub fn getPhysicalDeviceSurfaceCapabilities() !vk.SurfaceCapabilitiesKHR { + return try I.getPhysicalDeviceSurfaceCapabilitiesKHR(_pdevice, _surface); +} + pub extern fn glfwGetInstanceProcAddress( instance: vk.Instance, procname: [*:0]const u8,