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.W.surface); 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.W.surface, .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, }); }