390 lines
13 KiB
Zig
390 lines
13 KiB
Zig
const Swapchain = @This();
|
||
const std = @import("std");
|
||
|
||
const ctx = @import("../AppContext.zig");
|
||
const vk = @import("vulkan");
|
||
|
||
const CommandBuffer = @import("CommandBuffer.zig");
|
||
const Engine = @import("Engine.zig");
|
||
const Texture = @import("Texture.zig");
|
||
const WaitSemaphore = @import("WaitSemaphore.zig");
|
||
|
||
params: Params,
|
||
|
||
extent: vk.Extent2D = .{ .width = 0, .height = 0 },
|
||
swapchain: vk.SwapchainKHR = .null_handle,
|
||
/// Allocated with `allocator_general`.
|
||
swapchain_images: []SwapchainImage = &.{},
|
||
depth_texture: ?Texture = null,
|
||
image_index: ?u32 = null,
|
||
fence: vk.Fence = .null_handle,
|
||
semaphore_image_acquired: vk.Semaphore = .null_handle,
|
||
|
||
pub const PresentResult = enum {
|
||
optimal,
|
||
suboptimal,
|
||
};
|
||
|
||
pub fn init() !*Swapchain {
|
||
const allocator_persistent = ctx.allocator_persistent;
|
||
|
||
const swapchain = try allocator_persistent.create(Swapchain);
|
||
errdefer allocator_persistent.destroy(swapchain);
|
||
|
||
swapchain.* = .{
|
||
.params = try .init(),
|
||
};
|
||
|
||
try swapchain.recreate();
|
||
return swapchain;
|
||
}
|
||
|
||
pub fn deinit(self: *Swapchain) void {
|
||
const allocator_general = ctx.allocator_general;
|
||
const engine = ctx.engine;
|
||
|
||
std.log.scoped(.deinit).debug("Deinitializing {*}", .{self});
|
||
|
||
for (self.swapchain_images) |*swapchain_image| {
|
||
swapchain_image.deinit();
|
||
}
|
||
allocator_general.free(self.swapchain_images);
|
||
|
||
if (self.depth_texture) |*depth_texture| {
|
||
depth_texture.deinit();
|
||
}
|
||
|
||
if (self.swapchain != .null_handle) {
|
||
engine.destroySwapchain(self.swapchain);
|
||
}
|
||
|
||
engine.destroySemaphore(self.semaphore_image_acquired);
|
||
engine.destroyFence(self.fence);
|
||
|
||
self.* = undefined;
|
||
}
|
||
|
||
pub fn recreate(self: *Swapchain) !void {
|
||
const allocator_general = ctx.allocator_general;
|
||
const allocator_frame = ctx.allocator_frame;
|
||
const engine = ctx.engine;
|
||
|
||
// TODO This is very "LMAO just wait for all" way to synchronize. It might
|
||
// be the only viable option, though. Either way, we should research what
|
||
// is "the way".
|
||
try engine.deviceWaitIdle();
|
||
|
||
const old_swapchain = self.swapchain;
|
||
const old_swapchain_images = self.swapchain_images;
|
||
const old_depth_texture = &self.depth_texture;
|
||
const old_semaphore_image_acquired = self.semaphore_image_acquired;
|
||
const old_fence = self.fence;
|
||
|
||
const extent = try getCurrentExtent();
|
||
|
||
std.log.debug("Recreating swapchain with extent of {d}×{d}...", .{ extent.width, extent.height });
|
||
|
||
// --- CREATE NEW SWAPCHAIN ------------------------------------------------
|
||
|
||
const surface_capabilities = try engine.getPhysicalDeviceSurfaceCapabilities();
|
||
const new_swapchain = try engine.createSwapchain(.{
|
||
.surface = engine.surface,
|
||
.min_image_count = self.params.image_count,
|
||
.image_format = self.params.surface_format.format,
|
||
.image_color_space = self.params.surface_format.color_space,
|
||
.image_extent = extent,
|
||
.image_array_layers = 1,
|
||
.image_usage = .{ .color_attachment_bit = true },
|
||
.queue_family_indices = &.{
|
||
engine.graphics_queue.allocation.family,
|
||
engine.presentation_queue.allocation.family,
|
||
},
|
||
.pre_transform = surface_capabilities.current_transform,
|
||
.composite_alpha = .{ .opaque_bit_khr = true },
|
||
.present_mode = .fifo_khr,
|
||
.clipped = .true,
|
||
.old_swapchain = old_swapchain,
|
||
});
|
||
errdefer engine.destroySwapchain(new_swapchain);
|
||
engine.setObjectName(new_swapchain, "SC {d}×{d}", .{ extent.width, extent.height });
|
||
|
||
// --- DESTROY OLD SWAPCHAIN AND ITS IMAGES --------------------------------
|
||
|
||
// NOTE At this point, the old swapchain has been recycled. There is no way
|
||
// to revert on error. Instead, we set the current swapchain and images to
|
||
// null and deinit the old swapchain and images.
|
||
|
||
for (old_swapchain_images) |*swapchain_image| {
|
||
swapchain_image.deinit();
|
||
}
|
||
allocator_general.free(self.swapchain_images);
|
||
self.swapchain_images = &.{};
|
||
|
||
if (old_depth_texture.*) |*depth_texture| {
|
||
depth_texture.deinit();
|
||
}
|
||
|
||
if (old_swapchain != .null_handle) {
|
||
engine.destroySwapchain(old_swapchain);
|
||
self.swapchain = .null_handle;
|
||
}
|
||
|
||
self.extent = .{ .width = 0, .height = 0 };
|
||
self.image_index = null;
|
||
|
||
if (old_semaphore_image_acquired != .null_handle) {
|
||
engine.destroySemaphore(old_semaphore_image_acquired);
|
||
self.semaphore_image_acquired = .null_handle;
|
||
}
|
||
|
||
if (old_fence != .null_handle) {
|
||
engine.destroyFence(old_fence);
|
||
self.fence = .null_handle;
|
||
}
|
||
|
||
// --- CREATE DEPTH TEXTURE ------------------------------------------------
|
||
|
||
var new_depth_texture = try Texture.init(.{
|
||
.width = extent.width,
|
||
.height = extent.height,
|
||
.target_queue = .graphics,
|
||
.usage = .depth,
|
||
.name = "@Depth",
|
||
});
|
||
errdefer new_depth_texture.deinit();
|
||
|
||
// --- CREATE NEW SWAPCHAIN IMAGES -----------------------------------------
|
||
|
||
const new_swapchain_images = blk: {
|
||
const images = try engine.getSwapchainImagesAlloc(new_swapchain, allocator_frame);
|
||
|
||
var swapchain_images: std.ArrayList(SwapchainImage) = try .initCapacity(allocator_general, images.len);
|
||
errdefer swapchain_images.deinit(allocator_general);
|
||
|
||
errdefer for (swapchain_images.items) |*x| x.deinit();
|
||
|
||
for (images, 0..) |image, index| {
|
||
swapchain_images.appendAssumeCapacity(try SwapchainImage.init(.{
|
||
.image = image,
|
||
.format = self.params.surface_format.format,
|
||
.extent = extent,
|
||
.depth_stencil_image_view = new_depth_texture.image_view,
|
||
.index = index,
|
||
}));
|
||
}
|
||
|
||
break :blk try swapchain_images.toOwnedSlice(allocator_general);
|
||
};
|
||
errdefer {
|
||
for (new_swapchain_images) |*swapchain_image| {
|
||
swapchain_image.deinit();
|
||
}
|
||
allocator_general.free(new_swapchain_images);
|
||
}
|
||
|
||
// --- CREATE SEMAPHORES AND FENCES ----------------------------------------
|
||
|
||
const semaphore_image_acquired = try engine.createSemaphore();
|
||
errdefer engine.destroySemaphore(semaphore_image_acquired);
|
||
engine.setObjectName(semaphore_image_acquired, "S Acquired[E]", .{});
|
||
|
||
const fence = try engine.createFence(.{
|
||
.flags = .{ .signaled_bit = true },
|
||
});
|
||
errdefer engine.destroyFence(fence);
|
||
engine.setObjectName(fence, "F", .{});
|
||
|
||
// --- COMMIT --------------------------------------------------------------
|
||
|
||
self.extent = extent;
|
||
self.swapchain = new_swapchain;
|
||
self.swapchain_images = new_swapchain_images;
|
||
self.depth_texture = new_depth_texture;
|
||
self.image_index = null;
|
||
self.semaphore_image_acquired = semaphore_image_acquired;
|
||
self.fence = fence;
|
||
}
|
||
|
||
pub fn acquire(self: *Swapchain) !void {
|
||
const engine = ctx.engine;
|
||
|
||
try engine.waitForFence(self.fence);
|
||
try engine.resetFence(self.fence);
|
||
|
||
const res = try engine.acquireNextImage(self.swapchain, .{
|
||
.semaphore = self.semaphore_image_acquired,
|
||
});
|
||
std.mem.swap(vk.Semaphore, &self.swapchain_images[res.image_index].semaphore_image_acquired, &self.semaphore_image_acquired);
|
||
self.image_index = res.image_index;
|
||
}
|
||
|
||
pub fn getCurrentSwapchainImage(self: *Swapchain) *const SwapchainImage {
|
||
return &self.swapchain_images[self.image_index.?];
|
||
}
|
||
|
||
pub const PresentInfo = struct {
|
||
command_buffer: CommandBuffer,
|
||
wait_semaphores: []const WaitSemaphore = &.{},
|
||
};
|
||
|
||
pub fn present(self: *Swapchain, present_info: PresentInfo) !void {
|
||
const allocator_frame = ctx.allocator_frame;
|
||
const engine = ctx.engine;
|
||
|
||
const current_swapchain_image = self.getCurrentSwapchainImage();
|
||
|
||
// --- SUBMIT COMMAND BUFFER -----------------------------------------------
|
||
|
||
var wait_semaphores: std.ArrayList(WaitSemaphore) = try .initCapacity(allocator_frame, 1 + present_info.wait_semaphores.len);
|
||
|
||
wait_semaphores.appendAssumeCapacity(.{
|
||
.semaphore = current_swapchain_image.semaphore_image_acquired,
|
||
.stage_flags = .{ .top_of_pipe_bit = true },
|
||
});
|
||
wait_semaphores.appendSliceAssumeCapacity(present_info.wait_semaphores);
|
||
|
||
try present_info.command_buffer.submit(.{
|
||
.wait_semaphores = wait_semaphores.items,
|
||
.signal_semaphores = &.{current_swapchain_image.semaphore_render_finished},
|
||
.fence = self.fence,
|
||
});
|
||
|
||
// --- PRESENT CURRENT FRAME -----------------------------------------------
|
||
|
||
_ = try engine.queuePresent(.{
|
||
.wait_semaphores = &.{current_swapchain_image.semaphore_render_finished},
|
||
.swapchain = self.swapchain,
|
||
.image_index = self.image_index.?,
|
||
});
|
||
}
|
||
|
||
fn getCurrentExtent() !vk.Extent2D {
|
||
const window = ctx.window;
|
||
const engine = ctx.engine;
|
||
|
||
const surface_capabilities = try engine.getPhysicalDeviceSurfaceCapabilities();
|
||
|
||
if (surface_capabilities.current_extent.width != std.math.maxInt(u32) and surface_capabilities.current_extent.height != std.math.maxInt(u32)) {
|
||
return surface_capabilities.current_extent;
|
||
}
|
||
|
||
const framebuffer_width, const framebuffer_height = window.getFramebufferSize();
|
||
|
||
return .{
|
||
.width = std.math.clamp(
|
||
@as(u32, @intCast(framebuffer_width)),
|
||
surface_capabilities.min_image_extent.width,
|
||
surface_capabilities.max_image_extent.width,
|
||
),
|
||
.height = std.math.clamp(
|
||
@as(u32, @intCast(framebuffer_height)),
|
||
surface_capabilities.min_image_extent.height,
|
||
surface_capabilities.max_image_extent.height,
|
||
),
|
||
};
|
||
}
|
||
|
||
const Params = struct {
|
||
surface_format: vk.SurfaceFormatKHR,
|
||
image_count: u32,
|
||
|
||
pub fn init() !Params {
|
||
const allocator_frame = ctx.allocator_frame;
|
||
const engine = ctx.engine;
|
||
|
||
const surface_capabilities = try engine.getPhysicalDeviceSurfaceCapabilities();
|
||
|
||
const surface_format = blk: {
|
||
const surface_formats = try engine.getPhysicalDeviceSurfaceFormatsAlloc(allocator_frame);
|
||
|
||
// Look for 8-bit BGRA sRGB surface format.
|
||
|
||
for (surface_formats) |surface_format| {
|
||
if (surface_format.format == .b8g8r8a8_srgb and surface_format.color_space == .srgb_nonlinear_khr) {
|
||
break :blk surface_format;
|
||
}
|
||
}
|
||
|
||
if (surface_formats.len == 0) {
|
||
return error.NoSurfaceFormats;
|
||
}
|
||
|
||
break :blk surface_formats[0];
|
||
};
|
||
|
||
const image_count = @min(
|
||
surface_capabilities.min_image_count + 1,
|
||
if (surface_capabilities.max_image_count > 0) surface_capabilities.max_image_count else std.math.maxInt(u32),
|
||
);
|
||
|
||
std.log.debug("Using surface format \"{s}\" and color space \"{s}\" with {d} images.", .{ @tagName(surface_format.format), @tagName(surface_format.color_space), image_count });
|
||
|
||
return .{
|
||
.surface_format = surface_format,
|
||
.image_count = image_count,
|
||
};
|
||
}
|
||
};
|
||
|
||
const SwapchainImage = struct {
|
||
image: vk.Image,
|
||
image_view: vk.ImageView,
|
||
semaphore_image_acquired: vk.Semaphore,
|
||
semaphore_render_finished: vk.Semaphore,
|
||
|
||
const InitProps = struct {
|
||
image: vk.Image,
|
||
format: vk.Format,
|
||
extent: vk.Extent2D,
|
||
depth_stencil_image_view: vk.ImageView,
|
||
|
||
index: usize,
|
||
};
|
||
|
||
fn init(props: InitProps) !SwapchainImage {
|
||
const engine = ctx.engine;
|
||
|
||
const image_view = try engine.createImageView(.{
|
||
.image = props.image,
|
||
.view_type = .@"2d",
|
||
.format = props.format,
|
||
.subresource_range = .{
|
||
.aspect_mask = .{ .color_bit = true },
|
||
.base_mip_level = 0,
|
||
.level_count = 1,
|
||
.base_array_layer = 0,
|
||
.layer_count = 1,
|
||
},
|
||
});
|
||
errdefer engine.destroyImageView(image_view);
|
||
engine.setObjectName(image_view, "IV Swapchain[{d}]", .{props.index});
|
||
|
||
const semaphore_image_acquired = try engine.createSemaphore();
|
||
errdefer engine.destroySemaphore(semaphore_image_acquired);
|
||
engine.setObjectName(image_view, "S Acquired[{d}]", .{props.index});
|
||
|
||
const semaphore_render_finished = try engine.createSemaphore();
|
||
errdefer engine.destroySemaphore(semaphore_render_finished);
|
||
engine.setObjectName(image_view, "S Rendered[{d}]", .{props.index});
|
||
|
||
return .{
|
||
.image = props.image,
|
||
.image_view = image_view,
|
||
.semaphore_image_acquired = semaphore_image_acquired,
|
||
.semaphore_render_finished = semaphore_render_finished,
|
||
};
|
||
}
|
||
|
||
fn deinit(self: *SwapchainImage) void {
|
||
const engine = ctx.engine;
|
||
|
||
std.log.scoped(.deinit).debug("Deinitializing {*}", .{self});
|
||
|
||
engine.destroySemaphore(self.semaphore_render_finished);
|
||
engine.destroySemaphore(self.semaphore_image_acquired);
|
||
engine.destroyImageView(self.image_view);
|
||
|
||
self.* = undefined;
|
||
}
|
||
};
|