Port rewrite

This commit is contained in:
2025-11-19 14:43:57 +01:00
parent 444d20243c
commit bbafc55f6f
6 changed files with 928 additions and 256 deletions

319
src/engine/Swapchain.zig Normal file
View File

@@ -0,0 +1,319 @@
const Swapchain = @This();
const std = @import("std");
const vk = @import("vulkan");
const Engine = @import("Engine.zig");
engine: *Engine,
params: Params,
render_pass: vk.RenderPass,
swapchain: vk.SwapchainKHR = .null_handle,
swapchain_images: []SwapchainImage = &.{},
pub fn init(engine: *Engine) !Swapchain {
const params: Params = try .init(engine);
const render_pass = blk: {
const attachments = [_]vk.AttachmentDescription{
.{
.format = params.surface_format.format,
.samples = .{ .@"1_bit" = true },
.load_op = .dont_care,
.store_op = .store,
.stencil_load_op = .dont_care,
.stencil_store_op = .dont_care,
.initial_layout = .undefined,
.final_layout = .present_src_khr,
},
};
const color_attachments = [_]vk.AttachmentReference{
.{
.attachment = 0,
.layout = .color_attachment_optimal,
},
};
const subpasses = [_]vk.SubpassDescription{
.{
.pipeline_bind_point = .graphics,
.color_attachment_count = color_attachments.len,
.p_color_attachments = &color_attachments,
},
};
break :blk try engine.device.createRenderPass(&.{
.attachment_count = attachments.len,
.p_attachments = &attachments,
.subpass_count = subpasses.len,
.p_subpasses = &subpasses,
}, &engine.vk_allocator.interface);
};
errdefer engine.device.destroyRenderPass(render_pass, &engine.vk_allocator.interface);
var swapchain: Swapchain = .{
.engine = engine,
.params = params,
.render_pass = render_pass,
};
try recreate(&swapchain);
return swapchain;
}
pub fn deinit(self: *Swapchain) void {
const allocator = self.engine.vk_allocator.allocator;
for (self.swapchain_images) |swapchain_image| {
swapchain_image.deinit(self.engine);
}
allocator.free(self.swapchain_images);
if (self.swapchain != .null_handle) {
self.engine.device.destroySwapchainKHR(self.swapchain, &self.engine.vk_allocator.interface);
}
self.engine.device.destroyRenderPass(self.render_pass, &self.engine.vk_allocator.interface);
self.* = undefined;
}
pub fn recreate(self: *Swapchain) !void {
const mode = &self.engine.mode.surface;
const allocator = self.engine.vk_allocator.allocator;
const old_swapchain = self.swapchain;
const old_swapchain_images = self.swapchain_images;
const extent = try getCurrentExtent(self.engine);
// --- CREATE NEW SWAPCHAIN ------------------------------------------------
const surface_capabilities = try self.engine.instance.getPhysicalDeviceSurfaceCapabilitiesKHR(self.engine.physical_device, mode.surface);
const graphics_queue_family = self.engine.graphics_queue.allocation.family;
const presentation_queue_family = mode.presentation_queue.allocation.family;
const single_queue_family = graphics_queue_family == presentation_queue_family;
const queue_family_indices: []const u32 = if (single_queue_family) &.{} else &.{ graphics_queue_family, presentation_queue_family };
const new_swapchain = try self.engine.device.createSwapchainKHR(&.{
.surface = mode.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 },
.image_sharing_mode = if (single_queue_family) .exclusive else .concurrent,
.queue_family_index_count = @intCast(queue_family_indices.len),
.p_queue_family_indices = queue_family_indices.ptr,
.pre_transform = surface_capabilities.current_transform,
.composite_alpha = .{ .opaque_bit_khr = true },
.present_mode = .fifo_khr,
.clipped = .true,
.old_swapchain = old_swapchain,
}, &self.engine.vk_allocator.interface);
errdefer self.engine.device.destroySwapchainKHR(new_swapchain, &self.engine.vk_allocator.interface);
// --- 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(self.engine);
}
allocator.free(self.swapchain_images);
self.swapchain_images = &.{};
if (old_swapchain != .null_handle) {
self.engine.device.destroySwapchainKHR(old_swapchain, &self.engine.vk_allocator.interface);
self.swapchain = .null_handle;
}
// --- CREATE NEW SWAPCHAIN IMAGES -----------------------------------------
const new_swapchain_images = blk: {
const images = try self.engine.device.getSwapchainImagesAllocKHR(new_swapchain, allocator);
defer allocator.free(images);
var swapchain_images: std.ArrayList(SwapchainImage) = try .initCapacity(allocator, images.len);
errdefer swapchain_images.deinit(allocator);
errdefer for (swapchain_images.items) |x| x.deinit(self.engine);
for (images) |image| {
swapchain_images.appendAssumeCapacity(try SwapchainImage.init(self.engine, .{
.image = image,
.format = self.params.surface_format.format,
.render_pass = self.render_pass,
.extent = extent,
}));
}
break :blk try swapchain_images.toOwnedSlice(allocator);
};
errdefer {
for (new_swapchain_images) |swapchain_image| {
swapchain_image.deinit(self.engine);
}
allocator.free(new_swapchain_images);
}
// --- COMMIT --------------------------------------------------------------
self.swapchain = new_swapchain;
self.swapchain_images = new_swapchain_images;
}
fn getCurrentExtent(self: *Engine) !vk.Extent2D {
const mode = &self.mode.surface;
const surface_capabilities = try self.instance.getPhysicalDeviceSurfaceCapabilitiesKHR(self.physical_device, mode.surface);
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 = mode.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(engine: *Engine) !Params {
const mode = &engine.mode.surface;
const allocator = engine.vk_allocator.allocator;
const surface_capabilities = try engine.instance.getPhysicalDeviceSurfaceCapabilitiesKHR(engine.physical_device, mode.surface);
const surface_format = blk: {
const surface_formats = try engine.instance.getPhysicalDeviceSurfaceFormatsAllocKHR(engine.physical_device, mode.surface, allocator);
defer allocator.free(surface_formats);
// 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),
);
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,
fence: vk.Fence,
framebuffer: vk.Framebuffer,
const InitProps = struct {
image: vk.Image,
format: vk.Format,
render_pass: vk.RenderPass,
extent: vk.Extent2D,
};
fn init(engine: *Engine, props: InitProps) !SwapchainImage {
const image_view = try engine.device.createImageView(&.{
.image = props.image,
.view_type = .@"2d",
.format = props.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,
},
}, &engine.vk_allocator.interface);
errdefer engine.device.destroyImageView(image_view, &engine.vk_allocator.interface);
const semaphore_image_acquired = try engine.device.createSemaphore(&.{}, &engine.vk_allocator.interface);
errdefer engine.device.destroySemaphore(semaphore_image_acquired, &engine.vk_allocator.interface);
const semaphore_render_finished = try engine.device.createSemaphore(&.{}, &engine.vk_allocator.interface);
errdefer engine.device.destroySemaphore(semaphore_render_finished, &engine.vk_allocator.interface);
const fence = try engine.device.createFence(&.{
.flags = .{ .signaled_bit = true },
}, &engine.vk_allocator.interface);
errdefer engine.device.destroyFence(fence, &engine.vk_allocator.interface);
const attachments = [_]vk.ImageView{image_view};
const framebuffer = try engine.device.createFramebuffer(&.{
.render_pass = props.render_pass,
.attachment_count = attachments.len,
.p_attachments = &attachments,
.width = props.extent.width,
.height = props.extent.height,
.layers = 1,
}, &engine.vk_allocator.interface);
errdefer engine.device.destroyFramebuffer(framebuffer, &engine.vk_allocator.interface);
return .{
.image = props.image,
.image_view = image_view,
.semaphore_image_acquired = semaphore_image_acquired,
.semaphore_render_finished = semaphore_render_finished,
.fence = fence,
.framebuffer = framebuffer,
};
}
fn deinit(self: SwapchainImage, engine: *Engine) void {
self.waitForFence(engine) catch return;
engine.device.destroyFramebuffer(self.framebuffer, &engine.vk_allocator.interface);
engine.device.destroyFence(self.fence, &engine.vk_allocator.interface);
engine.device.destroySemaphore(self.semaphore_render_finished, &engine.vk_allocator.interface);
engine.device.destroySemaphore(self.semaphore_image_acquired, &engine.vk_allocator.interface);
engine.device.destroyImageView(self.image_view, &engine.vk_allocator.interface);
}
fn waitForFence(self: SwapchainImage, engine: *Engine) !void {
const fences = [_]vk.Fence{self.fence};
_ = try engine.device.waitForFences(fences.len, &fences, .true, std.math.maxInt(u64));
}
};