feat(window): multi-frame-in-flight frame pacing (#40)
The renderer was effectively single-buffered despite allocating triple-buffered infrastructure: Render() ended with an unconditional vkQueueWaitIdle, and a single (presentComplete, renderComplete) semaphore pair with zero fences was shared for the Window's lifetime. Rework the pacing model so up to numFrames frames overlap: - Per-swapchain-image VkFence, signaled by the submit and waited+reset before that image's command buffer / descriptor-heap slot is re-recorded. Keyed by acquired image index (drawCmdBuffers, the heap slots, and the swapchain images are all image-indexed — see WriteSwapchainDescriptors). Created signaled so first use passes. - Per-image render-finished (present) semaphore: the presentation engine holds it until the image is re-acquired, so per-image is the only safe key (per-CPU-frame trips VUID-vkQueueSubmit-pSignalSemaphores-00067). - Per-CPU-frame acquire semaphores (image index unknown until acquire returns), sized numFrames+1: in-flight depth is bounded by the per-image fences, so numFrames+1 distinct acquire semaphores guarantee the reused one has no pending op (VUID-vkAcquireNextImageKHR-01779). - Drop the steady-state vkQueueWaitIdle; keep it on resize / OUT_OF_DATE / teardown. Add tests/FrameLoopSync: drives the real frame loop against a live Wayland compositor for 60 frames (>> in-flight slots) and asserts the CPU frame counter advanced, the swapchain rotated across multiple images, and the validation layer stayed silent — the load-bearing check, since the old single-pair design only avoided being an active race because the wait-idle masked it. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
parent
050a44d118
commit
621016f264
4 changed files with 254 additions and 30 deletions
31
project.cpp
31
project.cpp
|
|
@ -264,6 +264,37 @@ extern "C" Configuration CrafterBuildProject(std::span<const std::string_view> a
|
|||
scrollImpls.emplace_back("tests/MouseScroll/main");
|
||||
sc.GetInterfacesAndImplementations(ifaces, scrollImpls);
|
||||
cfg.tests.push_back(std::move(scrollTest));
|
||||
|
||||
// Issue #40: multi-frame-in-flight frame pacing (per-image fences
|
||||
// + per-frame semaphores, no steady-state wait-idle). Drives the
|
||||
// real frame loop against a live Wayland compositor for many more
|
||||
// frames than there are in-flight slots and asserts the validation
|
||||
// layer stays silent — the old singleton-semaphore design only
|
||||
// avoided being an active race because the wait-idle masked it, so
|
||||
// a clean multi-frame run is the regression guard. Needs the
|
||||
// Wayland backend + a real compositor, hence inside the !windows
|
||||
// block alongside MouseScroll.
|
||||
Test frameLoopTest;
|
||||
Configuration& fl = frameLoopTest.config;
|
||||
fl.path = cfg.path;
|
||||
fl.name = "FrameLoopSync";
|
||||
fl.outputName = "FrameLoopSync";
|
||||
fl.type = ConfigurationType::Executable;
|
||||
fl.target = cfg.target;
|
||||
fl.march = cfg.march;
|
||||
fl.mtune = cfg.mtune;
|
||||
fl.debug = cfg.debug;
|
||||
fl.sysroot = cfg.sysroot;
|
||||
fl.dependencies = cfg.dependencies;
|
||||
fl.externalDependencies = cfg.externalDependencies;
|
||||
fl.compileFlags = cfg.compileFlags;
|
||||
fl.linkFlags = cfg.linkFlags;
|
||||
fl.defines = cfg.defines;
|
||||
fl.cFiles = cfg.cFiles;
|
||||
std::vector<fs::path> frameLoopImpls(impls.begin(), impls.end());
|
||||
frameLoopImpls.emplace_back("tests/FrameLoopSync/main");
|
||||
fl.GetInterfacesAndImplementations(ifaces, frameLoopImpls);
|
||||
cfg.tests.push_back(std::move(frameLoopTest));
|
||||
}
|
||||
|
||||
// Issue #36: BLAS build options. Drives the real hardware AS-build
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue