well-behaved swapchain

This commit is contained in:
David Allemang
2024-11-25 12:06:27 -05:00
parent ae37fc2ad3
commit 3e4b70573c
6 changed files with 83 additions and 61 deletions

View File

@@ -4,7 +4,7 @@ const vkgen = @import("vulkan-zig");
pub fn build(b: *std.Build) void { pub fn build(b: *std.Build) void {
const target = b.standardTargetOptions(.{}); const target = b.standardTargetOptions(.{});
const optimize = b.standardOptimizeOption(.{ const optimize = b.standardOptimizeOption(.{
.preferred_optimize_mode = .ReleaseSafe, // .preferred_optimize_mode = .ReleaseSafe,
}); });
const vk = b.dependency("vulkan-zig", .{ const vk = b.dependency("vulkan-zig", .{

View File

@@ -18,6 +18,8 @@ pub const engine = Engine(Window, Render, root.nu_modules);
// Hooks: setup, teardown, fixed, frame, present // Hooks: setup, teardown, fixed, frame, present
pub fn main() void { pub fn main() void {
std.log.info("use_debug_messenger: {}", .{config.render.use_debug_messenger});
var gpa = std.heap.GeneralPurposeAllocator(.{}){}; var gpa = std.heap.GeneralPurposeAllocator(.{}){};
const alloc = gpa.allocator(); const alloc = gpa.allocator();

View File

@@ -7,7 +7,6 @@ const builtin = @import("builtin");
const vk = @import("vk"); const vk = @import("vk");
const nu = @import("../nu.zig"); const nu = @import("../nu.zig");
// const au = @import("Render/au.zig");
const ctx = @import("Render/ctx.zig"); const ctx = @import("Render/ctx.zig");
const SwapChain = @import("Render/SwapChain.zig"); const SwapChain = @import("Render/SwapChain.zig");
@@ -85,6 +84,12 @@ pub fn setup(alloc: std.mem.Allocator) !void {
_sc = try SwapChain.init(alloc, _flights.len); _sc = try SwapChain.init(alloc, _flights.len);
errdefer _sc.deinit(); errdefer _sc.deinit();
nu.Window.add_resize_callback(&on_resize);
}
fn on_resize(_: u32, _: u32) void {
_sc.rebuild() catch @panic("rebuild on resize failed");
} }
pub fn teardown() void { pub fn teardown() void {
@@ -94,7 +99,7 @@ pub fn teardown() void {
} }
pub fn render() !void { pub fn render() !void {
const target = try _sc.acquire(); const target = try _sc.acquire() orelse return;
const flight = &_flights[target.flight_index]; const flight = &_flights[target.flight_index];
const render_area: vk.Rect2D = .{ const render_area: vk.Rect2D = .{
@@ -116,7 +121,7 @@ pub fn render() !void {
0, 0,
null, null,
1, 1,
&.{target.top_of_pipe()}, &.{target.top_of_pipe_barrier()},
); );
cmd.beginRendering(&vk.RenderingInfo{ cmd.beginRendering(&vk.RenderingInfo{
@@ -133,7 +138,7 @@ pub fn render() !void {
.resolve_image_layout = .undefined, .resolve_image_layout = .undefined,
.load_op = .clear, .load_op = .clear,
.store_op = .store, .store_op = .store,
.clear_value = .{ .color = .{ .float_32 = .{ 1, 0, 0, 1 } } }, .clear_value = .{ .color = .{ .float_32 = .{ 0.1, 0.1, 0.1, 1 } } },
}, },
}, },
}); });
@@ -149,7 +154,7 @@ pub fn render() !void {
0, 0,
null, null,
1, 1,
&.{target.bottom_of_pipe()}, &.{target.bottom_of_pipe_barrier()},
); );
} }
try cmd.endCommandBuffer(); try cmd.endCommandBuffer();

View File

@@ -44,7 +44,7 @@ pub const Target = struct {
complete: vk.Semaphore, // this semaphore should be signaled when the render is complete 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 available: vk.Fence, // this fence should be signaled when the target flight is available
pub fn top_of_pipe(target: Target) vk.ImageMemoryBarrier { pub fn top_of_pipe_barrier(target: Target) vk.ImageMemoryBarrier {
return vk.ImageMemoryBarrier{ return vk.ImageMemoryBarrier{
.src_access_mask = .{}, .src_access_mask = .{},
.dst_access_mask = .{ .color_attachment_write_bit = true }, .dst_access_mask = .{ .color_attachment_write_bit = true },
@@ -63,7 +63,7 @@ pub const Target = struct {
}; };
} }
pub fn bottom_of_pipe(target: Target) vk.ImageMemoryBarrier { pub fn bottom_of_pipe_barrier(target: Target) vk.ImageMemoryBarrier {
return vk.ImageMemoryBarrier{ return vk.ImageMemoryBarrier{
.src_access_mask = .{ .color_attachment_write_bit = true }, .src_access_mask = .{ .color_attachment_write_bit = true },
.dst_access_mask = .{}, .dst_access_mask = .{},
@@ -142,15 +142,16 @@ pub fn init(alloc: std.mem.Allocator, flight_count: usize) !Self {
.old_swapchain = .null_handle, .old_swapchain = .null_handle,
}; };
// try self.rebuild();
return self; return self;
} }
fn rebuild(self: *Self) !void { pub 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(); const caps = try ctx.getPhysicalDeviceSurfaceCapabilities();
self.cinfo.image_extent = caps.current_extent; self.cinfo.image_extent = caps.current_extent;
try ctx.D.queueWaitIdle(ctx.queue.*);
self.handle = try ctx.D.createSwapchainKHR(&self.cinfo, null); self.handle = try ctx.D.createSwapchainKHR(&self.cinfo, null);
ctx.D.destroySwapchainKHR(self.cinfo.old_swapchain, null); ctx.D.destroySwapchainKHR(self.cinfo.old_swapchain, null);
self.cinfo.old_swapchain = self.handle; self.cinfo.old_swapchain = self.handle;
@@ -215,7 +216,7 @@ pub fn deinit(self: *Self) void {
self.flight_syncs.deinit(self.alloc); self.flight_syncs.deinit(self.alloc);
} }
pub fn acquire(self: *Self) !Target { pub fn acquire(self: *Self) !?Target {
var target: Target = .{ var target: Target = .{
.flight_index = @intCast(self.flight_index), .flight_index = @intCast(self.flight_index),
.acquired = self.flight_syncs.items(.acquired)[self.flight_index], .acquired = self.flight_syncs.items(.acquired)[self.flight_index],
@@ -228,74 +229,59 @@ pub fn acquire(self: *Self) !Target {
const timeout = std.math.maxInt(u64); 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( std.debug.assert(.success == try ctx.D.waitForFences(
1, 1,
&fences, &.{target.available},
vk.TRUE, vk.TRUE,
std.math.maxInt(u64), std.math.maxInt(u64),
)); ));
// two attempts
target.image_index = for (0..2) |_| {
if (self.handle == .null_handle) try self.rebuild();
if (ctx.D.acquireNextImageKHR( if (ctx.D.acquireNextImageKHR(
self.handle, self.handle,
timeout, timeout,
target.acquired, target.acquired,
.null_handle, .null_handle,
)) |res| { )) |res| switch (res.result) {
switch (res.result) { .success, .suboptimal_khr => break res.image_index,
.success, .suboptimal_khr => {},
else => unreachable, else => unreachable,
} else |err| switch (err) {
error.OutOfDateKHR => {
self.handle = .null_handle;
},
else => return err,
} }
} else {
return null;
};
target.image_index = res.image_index; target.image = self.chain.items(.image)[target.image_index];
target.image = self.chain.items(.image)[res.image_index]; target.view = self.chain.items(.view)[target.image_index];
target.view = self.chain.items(.view)[res.image_index];
try ctx.D.resetFences(1, &.{target.available}); try ctx.D.resetFences(1, &.{target.available});
self.flight_index = @mod(self.flight_index + 1, self.flight_syncs.len); self.flight_index = @mod(self.flight_index + 1, self.flight_syncs.len);
return target; 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 { pub fn present(self: *Self, target: Target) !void {
if (ctx.Q.presentKHR(&vk.PresentInfoKHR{ if (ctx.Q.presentKHR(&vk.PresentInfoKHR{
.wait_semaphore_count = 1, // todo extra semaphores? .wait_semaphore_count = 1,
.p_wait_semaphores = &.{target.complete}, .p_wait_semaphores = &.{target.complete},
.swapchain_count = 1, .swapchain_count = 1,
.p_swapchains = &.{self.handle}, .p_swapchains = &.{self.handle},
.p_image_indices = &.{target.image_index}, .p_image_indices = &.{target.image_index},
.p_results = null, .p_results = null,
})) |res| { })) |res| switch(res) {
switch (res) {
.success => {}, .success => {},
.suboptimal_khr => { .suboptimal_khr => self.handle = .null_handle,
self.handle = .null_handle;
return;
},
else => unreachable, else => unreachable,
}
} else |err| switch(err) { } else |err| switch(err) {
error.OutOfDateKHR => { error.OutOfDateKHR => self.handle = .null_handle,
self.handle = .null_handle;
std.log.debug("Dropped frame", .{});
return;
},
else => return err, else => return err,
} }
} }

View File

@@ -17,7 +17,9 @@ pub const versions: []const vk.ApiInfo = &.{
pub const instance_exts: []const vk.ApiInfo = if (config.use_debug_messenger) &.{ pub const instance_exts: []const vk.ApiInfo = if (config.use_debug_messenger) &.{
vk.extensions.ext_debug_utils, vk.extensions.ext_debug_utils,
vk.extensions.khr_surface, vk.extensions.khr_surface,
} else &.{}; } else &.{
vk.extensions.khr_surface,
};
pub const device_exts: []const vk.ApiInfo = &.{ pub const device_exts: []const vk.ApiInfo = &.{
vk.extensions.khr_swapchain, vk.extensions.khr_swapchain,
@@ -70,10 +72,16 @@ var _queue: vk.Queue = undefined;
pub const queue: *const vk.Queue = &_queue; pub const queue: *const vk.Queue = &_queue;
pub fn init(alloc: std.mem.Allocator) !void { pub fn init(alloc: std.mem.Allocator) !void {
_B = try BaseWrapper.load(glfwGetInstanceProcAddress); _B = if (config.use_debug_messenger)
try BaseWrapper.load(glfwGetInstanceProcAddress)
else
BaseWrapper.loadNoFail(glfwGetInstanceProcAddress);
_instance = try _create_instance(alloc); _instance = try _create_instance(alloc);
_iw = try InstanceWrapper.load(_instance, glfwGetInstanceProcAddress); _iw = if (config.use_debug_messenger)
try InstanceWrapper.load(_instance, glfwGetInstanceProcAddress)
else
InstanceWrapper.loadNoFail(_instance, glfwGetInstanceProcAddress);
errdefer _destroy_instance(); errdefer _destroy_instance();
_I = InstanceProxy.init(_instance, iw); _I = InstanceProxy.init(_instance, iw);
@@ -86,7 +94,10 @@ pub fn init(alloc: std.mem.Allocator) !void {
_pdevice = try _select_pdevice(alloc); _pdevice = try _select_pdevice(alloc);
_family = try _select_queue_family_index(alloc); // only one queue supported _family = try _select_queue_family_index(alloc); // only one queue supported
_device = try _create_device(alloc); _device = try _create_device(alloc);
_dw = try DeviceWrapper.load(_device, iw.dispatch.vkGetDeviceProcAddr); _dw = if (config.use_debug_messenger)
try DeviceWrapper.load(_device, iw.dispatch.vkGetDeviceProcAddr)
else
DeviceWrapper.loadNoFail(_device, iw.dispatch.vkGetDeviceProcAddr);
errdefer _destroy_device(); errdefer _destroy_device();
_D = DeviceProxy.init(_device, dw); _D = DeviceProxy.init(_device, dw);
_queue = D.getDeviceQueue(_family, 0); // only one queue supported _queue = D.getDeviceQueue(_family, 0); // only one queue supported

View File

@@ -54,6 +54,8 @@ pub fn setup(_: std.mem.Allocator) !void {
null, null,
) orelse std.debug.panic("GLFW Create Window Failed", .{}); ) orelse std.debug.panic("GLFW Create Window Failed", .{});
_ = c.glfwSetFramebufferSizeCallback(handle, &resize_callback);
// bus.connect(handle); // bus.connect(handle);
// errdefer bus.disconnect(handle); // errdefer bus.disconnect(handle);
} }
@@ -75,3 +77,19 @@ pub fn next() bool {
return true; return true;
} }
var _resize_callbacks: std.BoundedArray(*const fn (u32, u32) void, 16) = .{};
fn resize_callback(_: ?*c.GLFWwindow, w: c_int, h: c_int) callconv(.C) void {
for (_resize_callbacks.slice()) |cb| {
cb(@intCast(w), @intCast(h));
}
}
pub fn add_resize_callback(cb: *const fn (u32, u32) void) void {
_resize_callbacks.appendAssumeCapacity(cb);
}
pub fn set_title(title: [:0]const u8) void {
c.glfwSetWindowTitle(handle, title);
}