0c5b68af
Migrate 10 Vulkan wait/acquire sites to bounded helpers
a73x 2026-04-18 15:12
Commit message
src/main.zig
| Old | New | ||
|---|---|---|---|
| @@ -7,6 +7,7 @@ const renderer = @import("renderer"); | |||
| 7 | const font = @import("font"); | 7 | const font = @import("font"); |
| 8 | const config = @import("config"); | 8 | const config = @import("config"); |
| 9 | const vk = @import("vulkan"); | 9 | const vk = @import("vulkan"); |
| 10 | const vk_sync = @import("vk_sync"); | ||
| 10 | const bench_stats = @import("bench_stats"); | 11 | const bench_stats = @import("bench_stats"); |
| 11 | const cell_instance = @import("cell_instance"); | 12 | const cell_instance = @import("cell_instance"); |
| 12 | const appendCellInstances = cell_instance.appendCellInstances; | 13 | const appendCellInstances = cell_instance.appendCellInstances; |
| @@ -592,12 +593,19 @@ fn runTerminal(alloc: std.mem.Allocator) !void { | |||
| 592 | const y_start = atlas.last_uploaded_y; | 593 | const y_start = atlas.last_uploaded_y; |
| 593 | const y_end = atlas.cursor_y + atlas.row_height; | 594 | const y_end = atlas.cursor_y + atlas.row_height; |
| 594 | if (y_start < y_end) { | 595 | if (y_start < y_end) { |
| 595 | try ctx.uploadAtlasRegion( | 596 | ctx.uploadAtlasRegion( |
| 596 | atlas.pixels, | 597 | atlas.pixels, |
| 597 | y_start, | 598 | y_start, |
| 598 | y_end, | 599 | y_end, |
| 599 | atlas.needs_full_upload, | 600 | atlas.needs_full_upload, |
| 600 | ); | 601 | ) catch |err| switch (err) { |
| 602 | error.VkWaitTimeout => { | ||
| 603 | vk_sync.logVkTimeout(@src(), .atlas); | ||
| 604 | render_pending = true; | ||
| 605 | continue; | ||
| 606 | }, | ||
| 607 | else => return err, | ||
| 608 | }; | ||
| 601 | atlas.last_uploaded_y = atlas.cursor_y; | 609 | atlas.last_uploaded_y = atlas.cursor_y; |
| 602 | atlas.needs_full_upload = false; | 610 | atlas.needs_full_upload = false; |
| 603 | render_cache.layout_dirty = true; | 611 | render_cache.layout_dirty = true; |
| @@ -686,6 +694,11 @@ fn runTerminal(alloc: std.mem.Allocator) !void { | |||
| 686 | render_pending = true; | 694 | render_pending = true; |
| 687 | continue; | 695 | continue; |
| 688 | }, | 696 | }, |
| 697 | error.VkWaitTimeout, error.VkAcquireTimeout => { | ||
| 698 | vk_sync.logVkTimeout(@src(), .fence); | ||
| 699 | render_pending = true; | ||
| 700 | continue; | ||
| 701 | }, | ||
| 689 | else => return err, | 702 | else => return err, |
| 690 | }; | 703 | }; |
| 691 | frame_timing.gpu_submit_us = usFromTimer(§ion_timer); | 704 | frame_timing.gpu_submit_us = usFromTimer(§ion_timer); |
| @@ -1361,27 +1374,11 @@ fn drawTextCoverageCompareFrame( | |||
| 1361 | cell_h_px: u32, | 1374 | cell_h_px: u32, |
| 1362 | clear_color: [4]f32, | 1375 | clear_color: [4]f32, |
| 1363 | ) !void { | 1376 | ) !void { |
| 1364 | _ = try ctx.vkd.waitForFences( | 1377 | try vk_sync.waitFenceBounded(ctx.vkd, ctx.device, ctx.in_flight_fence); |
| 1365 | ctx.device, | ||
| 1366 | 1, | ||
| 1367 | @ptrCast(&ctx.in_flight_fence), | ||
| 1368 | .true, | ||
| 1369 | std.math.maxInt(u64), | ||
| 1370 | ); | ||
| 1371 | try ctx.vkd.resetFences(ctx.device, 1, @ptrCast(&ctx.in_flight_fence)); | ||
| 1372 | 1378 | ||
| 1373 | const acquire = ctx.vkd.acquireNextImageKHR( | 1379 | const image_index = try vk_sync.acquireImageBounded(ctx.vkd, ctx.device, ctx.swapchain, ctx.image_available); |
| 1374 | ctx.device, | 1380 | |
| 1375 | ctx.swapchain, | 1381 | try ctx.vkd.resetFences(ctx.device, 1, @ptrCast(&ctx.in_flight_fence)); |
| 1376 | std.math.maxInt(u64), | ||
| 1377 | ctx.image_available, | ||
| 1378 | .null_handle, | ||
| 1379 | ) catch |err| switch (err) { | ||
| 1380 | error.OutOfDateKHR => return error.OutOfDateKHR, | ||
| 1381 | else => return err, | ||
| 1382 | }; | ||
| 1383 | if (acquire.result == .suboptimal_khr) return error.OutOfDateKHR; | ||
| 1384 | const image_index = acquire.image_index; | ||
| 1385 | 1382 | ||
| 1386 | try ctx.vkd.resetCommandBuffer(ctx.command_buffer, .{}); | 1383 | try ctx.vkd.resetCommandBuffer(ctx.command_buffer, .{}); |
| 1387 | try ctx.vkd.beginCommandBuffer(ctx.command_buffer, &vk.CommandBufferBeginInfo{ | 1384 | try ctx.vkd.beginCommandBuffer(ctx.command_buffer, &vk.CommandBufferBeginInfo{ |
src/renderer.zig
| Old | New | ||
|---|---|---|---|
| @@ -1,5 +1,6 @@ | |||
| 1 | const std = @import("std"); | 1 | const std = @import("std"); |
| 2 | const vk = @import("vulkan"); | 2 | const vk = @import("vulkan"); |
| 3 | const vk_sync = @import("vk_sync"); | ||
| 3 | 4 | ||
| 4 | const dl = @cImport({ | 5 | const dl = @cImport({ |
| 5 | @cInclude("dlfcn.h"); | 6 | @cInclude("dlfcn.h"); |
| @@ -1287,23 +1288,8 @@ pub const Context = struct { | |||
| 1287 | /// Does not bind the pipeline or draw — just clear + present. | 1288 | /// Does not bind the pipeline or draw — just clear + present. |
| 1288 | /// Blocks until the previous frame's fence signals. | 1289 | /// Blocks until the previous frame's fence signals. |
| 1289 | pub fn drawClear(self: *Context, clear_color: [4]f32) !void { | 1290 | pub fn drawClear(self: *Context, clear_color: [4]f32) !void { |
| 1290 | // Wait for previous frame to finish | 1291 | try vk_sync.waitFenceBounded(self.vkd, self.device, self.in_flight_fence); |
| 1291 | _ = try self.vkd.waitForFences(self.device, 1, @ptrCast(&self.in_flight_fence), .true, std.math.maxInt(u64)); | 1292 | const image_index = try vk_sync.acquireImageBounded(self.vkd, self.device, self.swapchain, self.image_available); |
| 1292 | |||
| 1293 | // Acquire next image BEFORE reset, so an acquire failure leaves the | ||
| 1294 | // fence in a safe state (signaled from the prior frame). | ||
| 1295 | const acquire = self.vkd.acquireNextImageKHR( | ||
| 1296 | self.device, | ||
| 1297 | self.swapchain, | ||
| 1298 | std.math.maxInt(u64), | ||
| 1299 | self.image_available, | ||
| 1300 | .null_handle, | ||
| 1301 | ) catch |err| switch (err) { | ||
| 1302 | error.OutOfDateKHR => return error.OutOfDateKHR, | ||
| 1303 | else => return err, | ||
| 1304 | }; | ||
| 1305 | if (swapchainNeedsRebuild(acquire.result)) return error.OutOfDateKHR; | ||
| 1306 | const image_index = acquire.image_index; | ||
| 1307 | 1293 | ||
| 1308 | try self.vkd.resetFences(self.device, 1, @ptrCast(&self.in_flight_fence)); | 1294 | try self.vkd.resetFences(self.device, 1, @ptrCast(&self.in_flight_fence)); |
| 1309 | errdefer self.resignalFence(self.in_flight_fence); | 1295 | errdefer self.resignalFence(self.in_flight_fence); |
| @@ -1494,8 +1480,9 @@ pub const Context = struct { | |||
| 1494 | const byte_len: usize = @as(usize, y_end - y_start) * self.atlas_width; | 1480 | const byte_len: usize = @as(usize, y_end - y_start) * self.atlas_width; |
| 1495 | 1481 | ||
| 1496 | // Wait for any prior atlas transfer to finish before reusing staging buffer | 1482 | // Wait for any prior atlas transfer to finish before reusing staging buffer |
| 1497 | _ = try self.vkd.waitForFences(self.device, 1, @ptrCast(&self.atlas_transfer_fence), .true, std.math.maxInt(u64)); | 1483 | try vk_sync.waitFenceBounded(self.vkd, self.device, self.atlas_transfer_fence); |
| 1498 | try self.vkd.resetFences(self.device, 1, @ptrCast(&self.atlas_transfer_fence)); | 1484 | try self.vkd.resetFences(self.device, 1, @ptrCast(&self.atlas_transfer_fence)); |
| 1485 | errdefer self.resignalFence(self.atlas_transfer_fence); | ||
| 1499 | 1486 | ||
| 1500 | // Copy dirty band into staging buffer | 1487 | // Copy dirty band into staging buffer |
| 1501 | const mapped = try self.vkd.mapMemory(self.device, self.atlas_staging_memory, 0, @intCast(byte_len), .{}); | 1488 | const mapped = try self.vkd.mapMemory(self.device, self.atlas_staging_memory, 0, @intCast(byte_len), .{}); |
| @@ -1725,27 +1712,13 @@ pub const Context = struct { | |||
| 1725 | } | 1712 | } |
| 1726 | }.read; | 1713 | }.read; |
| 1727 | 1714 | ||
| 1728 | // Wait for previous frame to finish | 1715 | try vk_sync.waitFenceBounded(self.vkd, self.device, self.in_flight_fence); |
| 1729 | _ = try self.vkd.waitForFences(self.device, 1, @ptrCast(&self.in_flight_fence), .true, std.math.maxInt(u64)); | ||
| 1730 | if (timing_out) |t| { | 1716 | if (timing_out) |t| { |
| 1731 | t.wait_fences_us = readTimer(&timer); | 1717 | t.wait_fences_us = readTimer(&timer); |
| 1732 | timer.reset(); | 1718 | timer.reset(); |
| 1733 | } | 1719 | } |
| 1734 | 1720 | ||
| 1735 | // Acquire next image BEFORE reset, so an acquire failure leaves the | 1721 | const image_index = try vk_sync.acquireImageBounded(self.vkd, self.device, self.swapchain, self.image_available); |
| 1736 | // fence in a safe state (signaled from the prior frame). | ||
| 1737 | const acquire = self.vkd.acquireNextImageKHR( | ||
| 1738 | self.device, | ||
| 1739 | self.swapchain, | ||
| 1740 | std.math.maxInt(u64), | ||
| 1741 | self.image_available, | ||
| 1742 | .null_handle, | ||
| 1743 | ) catch |err| switch (err) { | ||
| 1744 | error.OutOfDateKHR => return error.OutOfDateKHR, | ||
| 1745 | else => return err, | ||
| 1746 | }; | ||
| 1747 | if (swapchainNeedsRebuild(acquire.result)) return error.OutOfDateKHR; | ||
| 1748 | const image_index = acquire.image_index; | ||
| 1749 | if (timing_out) |t| { | 1722 | if (timing_out) |t| { |
| 1750 | t.acquire_us = readTimer(&timer); | 1723 | t.acquire_us = readTimer(&timer); |
| 1751 | timer.reset(); | 1724 | timer.reset(); |
| @@ -1837,14 +1810,15 @@ pub const Context = struct { | |||
| 1837 | // shared instance buffer. (drawCells and renderToOffscreen share | 1810 | // shared instance buffer. (drawCells and renderToOffscreen share |
| 1838 | // self.instance_memory; without this wait the host would overwrite bytes | 1811 | // self.instance_memory; without this wait the host would overwrite bytes |
| 1839 | // the GPU is still reading.) | 1812 | // the GPU is still reading.) |
| 1840 | _ = try self.vkd.waitForFences(self.device, 1, @ptrCast(&self.in_flight_fence), .true, std.math.maxInt(u64)); | 1813 | try vk_sync.waitFenceBounded(self.vkd, self.device, self.in_flight_fence); |
| 1841 | 1814 | ||
| 1842 | // 1. Upload instances (same path drawCells uses) | 1815 | // 1. Upload instances (same path drawCells uses) |
| 1843 | try self.uploadInstances(instance_data); | 1816 | try self.uploadInstances(instance_data); |
| 1844 | 1817 | ||
| 1845 | // 2. Reset + begin capture command buffer | 1818 | // 2. Reset + begin capture command buffer |
| 1846 | _ = try self.vkd.waitForFences(self.device, 1, @ptrCast(&self.capture_fence), .true, std.math.maxInt(u64)); | 1819 | try vk_sync.waitFenceBounded(self.vkd, self.device, self.capture_fence); |
| 1847 | try self.vkd.resetFences(self.device, 1, @ptrCast(&self.capture_fence)); | 1820 | try self.vkd.resetFences(self.device, 1, @ptrCast(&self.capture_fence)); |
| 1821 | errdefer self.resignalFence(self.capture_fence); | ||
| 1848 | 1822 | ||
| 1849 | try self.vkd.resetCommandBuffer(self.capture_cmd, .{}); | 1823 | try self.vkd.resetCommandBuffer(self.capture_cmd, .{}); |
| 1850 | try self.vkd.beginCommandBuffer(self.capture_cmd, &vk.CommandBufferBeginInfo{ | 1824 | try self.vkd.beginCommandBuffer(self.capture_cmd, &vk.CommandBufferBeginInfo{ |
| @@ -1960,7 +1934,7 @@ pub const Context = struct { | |||
| 1960 | .command_buffer_count = 1, | 1934 | .command_buffer_count = 1, |
| 1961 | .p_command_buffers = @ptrCast(&self.capture_cmd), | 1935 | .p_command_buffers = @ptrCast(&self.capture_cmd), |
| 1962 | }), self.capture_fence); | 1936 | }), self.capture_fence); |
| 1963 | _ = try self.vkd.waitForFences(self.device, 1, @ptrCast(&self.capture_fence), .true, std.math.maxInt(u64)); | 1937 | try vk_sync.waitFenceBounded(self.vkd, self.device, self.capture_fence); |
| 1964 | } | 1938 | } |
| 1965 | 1939 | ||
| 1966 | /// Read the offscreen target's readback buffer into `out_rgba`. | 1940 | /// Read the offscreen target's readback buffer into `out_rgba`. |