From 3e116e6e43e0d9296bb96e3783eabbeacfb3e497 Mon Sep 17 00:00:00 2001 From: catbot Date: Thu, 18 Jun 2026 19:35:54 +0000 Subject: [PATCH 1/2] feat(window): env-gated uncapped present mode for benchmarking CRAFTER_PRESENT_IMMEDIATE selects IMMEDIATE (then MAILBOX) when the surface offers it, instead of the default FIFO. Needed to measure steady-state frame throughput without the compositor's vblank cap; FIFO remains the default when the variable is unset. Co-Authored-By: Claude Opus 4.8 --- implementations/Crafter.Graphics-Window.cpp | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/implementations/Crafter.Graphics-Window.cpp b/implementations/Crafter.Graphics-Window.cpp index bee7f82..3af8c7b 100644 --- a/implementations/Crafter.Graphics-Window.cpp +++ b/implementations/Crafter.Graphics-Window.cpp @@ -1072,8 +1072,24 @@ void Window::CreateSwapchain() swapchainCI.imageSharingMode = VK_SHARING_MODE_EXCLUSIVE; swapchainCI.queueFamilyIndexCount = 0; // VK_PRESENT_MODE_FIFO_KHR ("v-sync") is guaranteed available per spec, so - // it is used unconditionally — no present-mode enumeration is needed. + // it is the default. For benchmarking we want frame pacing uncapped from + // the display refresh, so when CRAFTER_PRESENT_IMMEDIATE is set in the + // environment we enumerate the surface's present modes and prefer + // IMMEDIATE (then MAILBOX) when offered, falling back to FIFO otherwise. swapchainCI.presentMode = VK_PRESENT_MODE_FIFO_KHR; + if (std::getenv("CRAFTER_PRESENT_IMMEDIATE")) { + std::uint32_t presentModeCount = 0; + vkGetPhysicalDeviceSurfacePresentModesKHR(Device::physDevice, vulkanSurface, &presentModeCount, nullptr); + std::vector presentModes(presentModeCount); + vkGetPhysicalDeviceSurfacePresentModesKHR(Device::physDevice, vulkanSurface, &presentModeCount, presentModes.data()); + bool haveImmediate = false, haveMailbox = false; + for (VkPresentModeKHR m : presentModes) { + if (m == VK_PRESENT_MODE_IMMEDIATE_KHR) haveImmediate = true; + if (m == VK_PRESENT_MODE_MAILBOX_KHR) haveMailbox = true; + } + if (haveImmediate) swapchainCI.presentMode = VK_PRESENT_MODE_IMMEDIATE_KHR; + else if (haveMailbox) swapchainCI.presentMode = VK_PRESENT_MODE_MAILBOX_KHR; + } // Setting oldSwapChain to the saved handle of the previous swapchain aids in resource reuse and makes sure that we can still present already acquired images swapchainCI.oldSwapchain = oldSwapchain; // Setting clipped to VK_TRUE allows the implementation to discard rendering outside of the surface area -- 2.54.0 From 619e39369d944a3a478a737e2e96db00ffe3d85c Mon Sep 17 00:00:00 2001 From: catbot Date: Thu, 18 Jun 2026 19:35:54 +0000 Subject: [PATCH 2/2] =?UTF-8?q?test(bench):=20SponzaBench=20harness=20+=20?= =?UTF-8?q?#40=E2=86=92HEAD=20perf=20measurement=20(#155)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Headless benchmark around the native Sponza RT scene: times setup and a measured Render() loop over the full multi-mesh atrium, prints BENCH metrics, and exits. Includes run-bench.sh and a README documenting the methodology and the measured net gain from #40 to current master. Co-Authored-By: Claude Opus 4.8 --- examples/SponzaBench/README.md | 131 ++++++++++++ examples/SponzaBench/closesthit.glsl | 16 ++ examples/SponzaBench/main.cpp | 306 +++++++++++++++++++++++++++ examples/SponzaBench/miss.glsl | 11 + examples/SponzaBench/project.cpp | 60 ++++++ examples/SponzaBench/raygen.glsl | 52 +++++ examples/SponzaBench/run-bench.sh | 40 ++++ 7 files changed, 616 insertions(+) create mode 100644 examples/SponzaBench/README.md create mode 100644 examples/SponzaBench/closesthit.glsl create mode 100644 examples/SponzaBench/main.cpp create mode 100644 examples/SponzaBench/miss.glsl create mode 100644 examples/SponzaBench/project.cpp create mode 100644 examples/SponzaBench/raygen.glsl create mode 100755 examples/SponzaBench/run-bench.sh diff --git a/examples/SponzaBench/README.md b/examples/SponzaBench/README.md new file mode 100644 index 0000000..fdf3426 --- /dev/null +++ b/examples/SponzaBench/README.md @@ -0,0 +1,131 @@ +# SponzaBench — measuring the post-#40 performance work + +This example exists to answer issue **#155**: *starting with #40 a lot of +performance-related issues were merged — what is the net performance gain, +measured in a representative scene (Sponza)?* + +It is a headless benchmark harness built around the **native Vulkan** +Sponza ray-tracing scene. Same asset bundle and camera as +[`examples/Sponza`](../Sponza), but instead of opening an interactive +window it times the work and prints machine-readable `BENCH …` lines, then +exits. + +## What it measures + +* **setup** — process start through the first command submission: asset + decompression, BLAS build per mesh group, the multi-instance TLAS, the + RT pipeline, GPU memory placement and the descriptor writes. This is the + window most of the post-#40 *native* perf work acts on. +* **frames** — a warmup followed by a measured loop calling + `Window::Render()`, reporting per-frame wall-clock stats and throughput. + +Unlike the interactive Sponza example — which is single-material on native +because of the hit-shader dynamic-`descriptor_heap`-index driver fault +(see `examples/Sponza/README.md`) — SponzaBench's closest-hit shades from +barycentric coordinates and samples **no** texture, so it can build the +**full multi-mesh atrium** (25 mesh groups, ~262 k triangles) as one +multi-instance TLAS. The albedo is still decompressed and uploaded during +setup (to keep that path in the measurement) but is not bound. + +## Running + +```bash +cd examples/SponzaBench +crafter-build # native Vulkan +# from the produced bin dir: +VK_LOADER_LAYERS_DISABLE='~all~' \ +CRAFTER_PRESENT_IMMEDIATE=1 \ +BENCH_WARMUP=200 BENCH_FRAMES=2000 BENCH_MESHES=25 ./SponzaBench +``` + +`run-bench.sh