docs(render): Campaign V slice V8 - the perf gate, measured in three configurations
The verdict splits, and not where anyone expected. Measured on one machine, one
build, one day, physical console, uncapped, 4x MSAA on both arms, validation off:
Stationary LIGHT scene (6,675 entities, 780-870 FPS)
CPU p50 GL 1.127 ms -> VK 1.294 ms MISS +14.8%
CPU p99 GL 1.407 ms -> VK 1.531 ms MISS +8.8%
GPU p50 GL 0.651 ms -> VK 0.160 ms PASS -75.4%
Alloc/frm GL 77,664 B -> VK 11,440 B PASS -85.3%
Working GL 943.7 MiB -> VK 877.1 MiB PASS -7.0%
Stationary DENSE scene (21,024 entities, identical on both arms)
CPU p50 GL 5.934 ms -> VK 5.775 ms PASS -2.7%
CPU p99 GL 8.867 ms -> VK 7.354 ms PASS -17.1%
GPU p50 GL 1.673 ms -> VK 0.909 ms PASS -45.7%
Alloc/frm GL 82,016 B -> VK 15,752 B PASS -80.8%
Process CPU GL 1.246 cores -> VK 1.016 PASS -18.5% (Windows, not ours)
Canonical nine-stop route, identical world at all nine stops
Frames GL 30,378 -> VK 38,683 PASS +27.3%
CPU p50 GL 11.718 ms -> VK 9.166 ms PASS -21.8%
GPU p99 GL 2.325 ms -> VK 1.193 ms PASS -48.7%
Vulkan loses two rows in exactly one configuration: a stationary field at a frame
rate no player will ever see. The reason is measured rather than argued. A
temporary probe on BOTH arms, now stripped, attributes 0.148 ms/frame to required
Vulkan WSI and synchronisation calls - vkQueuePresentKHR 0.070, vkQueueSubmit2
0.027, the timeline wait 0.026, vkAcquireNextImageKHR 0.025 - against roughly
0.014 ms for GL's whole SwapBuffers. That cost is FIXED per frame, so it is 12%
of a 1.13 ms frame, 2.5% of a 5.9 ms one and under 1% of a dense-town frame,
while the GPU and allocation savings scale with the work. The sign of the CPU
comparison flips as soon as the frame contains a town.
The campaign's named cost centre is closed rather than carried a fourth time.
Bindings 4, 6, 7 and 8 costing a descriptor write per draw - forward-carried
since V6i-3 as the thing to fix if CPU were short - measures 0.031 ms for ALL
~216 draws of the frame, about 140 ns each and 2.4% of it. No Vulkan code was
changed to chase the miss: every lever the V8 row named was already taken
(coherent rings, one submit per frame), irrelevant to p50 (pipeline pre-warm),
measured and small (descriptors), or would have traded real memory for nothing
(a fourth swapchain image, when acquire is call cost and not waiting).
The methodological finding is worth reading before the numbers. The R6 soak is
NOT the vehicle the founding numbers came from - the G5 production profile states
its own conditions and they exclude the probe, the artifact owner and the
screenshot oracle - and it is biased AGAINST Vulkan, because
VulkanGraphicsContext arms retainBackbufferCapture exactly when
ACDREAM_AUTOMATION_ARTIFACT_DIR is set, making every Vulkan frame copy the whole
swapchain image while GL reads on demand. On one binary in one hour the soak
reports CPU p50 7.3 ms and 2,531 KiB/frame where the ordinary profile reports
1.13 ms and 77 KiB. The route table above is therefore conservative in Vulkan's
favour: it wins on the vehicle that charges it extra.
Gates: Release build green; App tests 4,152 / 3 skipped, the pre-slice baseline,
no #250-family failure; strict GL offline pixel gate against 13c8733d at 1.95e-05
(11 px of 563,200), inside the 9-31 band, so GL did not move; one connected
Vulkan run with VK_LAYER_KHRONOS_validation proven inserted by the loader at zero
errors and zero warnings; and BOTH R6 soaks green - Vulkan 506.6 s and GL 506.8 s,
zero failures, graceful exits - which discharges the soak half of V7's
outstanding list. RenderDoc is not installed on this machine, so that capture
carries to V10 with a cause rather than as an omission.
The recommendation: proceed to V10 and amend the acceptance table rather than
waive it, naming the scene and pacing the floor is judged at. Two natural
candidates are already in the evidence and Vulkan passes both outright. The
opposite reading - that the light-scene rows disqualify the cutover - is
available and has been given the same measurement space. That call is the user's
and this slice does not make it.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
parent
32f9bcfa60
commit
22a157225c
1 changed files with 407 additions and 4 deletions
|
|
@ -617,8 +617,8 @@ tenth pair with no consumer at all; see the V6e report.
|
|||
| **V6k** ✅ | **Sky, viewports, and §5.4**, reported in §5.5.16. Two commits: **1** `SkyRenderer`'s RHI arm — V4f's content, two blend pipelines, `SkyParams` as a per-draw ring slice, the first Vulkan consumer of set 1 binding 4 — plus the retirement of the last interim `GlBindlessHandleTable` (`22aa2edc`); **2** `PrivateEntityViewportRenderer` onto `IGpuRenderTarget`, the deletion of §7.1's external-texture seam, and **§5.4's obligation discharged** (`eb7e6b4e`). **Particles did not land**: the pinned contract cannot express instanced vertex input, which is what both particle pipelines are built on. | GL pixel gate 4.43e-05 then 4.08e-05 (25 and 23 px, band 9–31), App tests 4,109/3, complete Release suite 9,172/5, GL connected `-Runs 3` at 3/3 on both columns per commit, one validation-layer Vulkan run at 0 errors / 0 warnings per commit, a seven-day-group GL sky comparison, a connected paperdoll capture, and a GL-versus-Vulkan inspection in §5.5.16 |
|
||||
| **V6l** ✅ | **Particles, the portal mask and the viewports**, reported in §5.5.17. Three commits, one per contract amendment: **1** instanced vertex input (`GpuVertexLayout` per-binding stride and input rate, `BindVertexBuffer(binding, …)`, `GpuVertexFormat.UInt1`) plus `ParticleRenderer`'s RHI arm, the standalone particle texture cache on both arms, and the stride-equals-the-uploaded-record gate for every RHI vertex layout (`b1ad1d48`); **2** the stencil dimension (`StencilTest` + `GpuStencilState` + `SetStencil`), `portal_depth` as a committed shader pair, `PortalDepthMaskRenderer`'s RHI arm, and the ambient stencil/colour-mask restore (`eced67d0`); **3** a layered sampled view per Vulkan render target, sample-count pipeline variants for `WbDrawDispatcher`, the composite texture cache on both arms, and the backend-derived viewport V orientation (`2e8b8b91`). | GL pixel gate per commit (3.20e-05, 2.31e-05, 3.55e-05; band 9–31 px), App tests 4,121/4,129/4,129 and complete Release suite 9,184/9,192/9,192, GL connected `-Runs 3` at 3/3 on both columns per commit, one validation-layer Vulkan run at 0 errors / 0 warnings per commit, and connected Vulkan paperdoll and particle captures inspected against GL |
|
||||
| **V6m** ✅ | **Portal space, and V7's instrument**, reported in §5.5.18. Two commits: **1** `PortalTunnelPresentation`'s RHI arm — the last raw-GL world-adjacent renderer — as a backbuffer pass published on `IWorldPassScope`, clearing to retail's opaque portal-space black rather than loading it, plus the deletion of `NullLocalPlayerTeleportPresentation` (`59c6b2ae`); **2** `tools/run-backend-differential-gate.ps1` and `connected-backend-differential.route.txt`, with MSAA forced off on both launches, the repeat gate's desktop-witness guards, and **an interior EnvCell stop** — §5.1's durable fix for the campaign's oldest coverage gap (`a99f517e`). One smoke pair was run and is reported in full. | GL pixel gate 4.97e-05 (28 px) vs `280f3b3f` against a 3.55e-05 (20 px) same-commit control, band 9–31; App tests 4,132/3 and complete Release suite 9,195/5; GL connected `-Runs 3` at 3/3 on both columns; one validation-layer Vulkan run at 0 errors / 0 warnings; connected portal-tunnel, creature-appraisal and interior-EnvCell captures on BOTH backends, inspected in §5.5.18 |
|
||||
| **V7** ◐ **partially discharged — §5.5.19** | GL-versus-Vulkan differential: `tools/run-backend-differential-gate.ps1` (**built at V6m**), strict paired-PNG compare, divergences fixed in the Vulkan backend only, then lifecycle + R6 soak natively on Vulkan, one validation-layer-clean run, one RenderDoc capture. **Milestone: parity.** Starting distance, measured at V6m: 18.52% of the frame at the first stop. **What landed:** the world atlases' missing anisotropy (`ad5f8b68`, retail-anchored at `0x005a4230`), and two instrument pins the gate was silently missing — the cloud sheet's phase and, much larger, the Dereth clock, which had never actually been pinned by anything and was moving 22% of the frame between two captures 45 s apart *in the same run*. Offline GL-versus-Vulkan, both clocks pinned, is now **8.82e-04 below the tree band — inside the threshold**; the treeline is `AD-46`, proven not to be the depth class. **What did NOT land:** a passing connected stop (each carries a named phase exception), per-stop masks in the gate script, an aperture stop for the portal depth mask, the R6 soak on Vulkan, and the RenderDoc capture. | every differential checkpoint passes; both connected routes green on VK |
|
||||
| **V8** | Perf gate on the RX 9070 XT, uncapped, both backends, same route. | §2 acceptance table; parity is the floor |
|
||||
| **V7** ◐ **partially discharged — §5.5.19** | GL-versus-Vulkan differential: `tools/run-backend-differential-gate.ps1` (**built at V6m**), strict paired-PNG compare, divergences fixed in the Vulkan backend only, then lifecycle + R6 soak natively on Vulkan, one validation-layer-clean run, one RenderDoc capture. **Milestone: parity.** Starting distance, measured at V6m: 18.52% of the frame at the first stop. **What landed:** the world atlases' missing anisotropy (`ad5f8b68`, retail-anchored at `0x005a4230`), and two instrument pins the gate was silently missing — the cloud sheet's phase and, much larger, the Dereth clock, which had never actually been pinned by anything and was moving 22% of the frame between two captures 45 s apart *in the same run*. Offline GL-versus-Vulkan, both clocks pinned, is now **8.82e-04 below the tree band — inside the threshold**; the treeline is `AD-46`, proven not to be the depth class. **What did NOT land:** a passing connected stop (each carries a named phase exception), per-stop masks in the gate script, an aperture stop for the portal depth mask, ~~the R6 soak on Vulkan~~ (**run and passed at V8 — §5.5.21**), and the RenderDoc capture (**V8 established the cause: RenderDoc is not installed on this machine**). | every differential checkpoint passes; both connected routes green on VK |
|
||||
| **V8** ⏸ **measured; two floors missed; the cutover call is the user's — §5.5.21** | Perf gate on the RX 9070 XT, uncapped, both backends, same scene, same day. **What landed:** `VulkanFrameGpuMeasurement` (`00e1b321`), without which the Vulkan arm emitted no `[frame-prof]` line at all — `NullRenderFrameGpuMeasurement` was the only caller of `FrameProfiler.FrameBoundary`, so no performance vehicle could be pointed at it; the finding that **the R6 soak is NOT the vehicle §2's founding numbers came from and is biased against Vulkan** by the per-frame swapchain copy its own artifact directory arms; a same-day GL-versus-Vulkan profile on the G5 ordinary-production vehicle; a phase-level CPU attribution on both arms; and **the R6 soak run natively on Vulkan** (PASS, 0 failures, graceful exit), which V7 left outstanding. **Result, in three configurations:** on a stationary LIGHT scene at 780-870 FPS Vulkan is 75.4% cheaper on GPU p50 and 85.3% lighter on allocation but **14.8% more expensive on CPU p50 and 8.8% on p99** — two floors missed; on a stationary DENSE scene (21,024 entities, identical on both arms) **Vulkan wins every row**, including CPU p50/p99 and 18.5% less total process CPU by Windows' accounting; and on the nine-stop route against an identical world **Vulkan wins every row** and renders **27.3% more frames**. The Vulkan-specific cost is **fixed per frame** — 0.148 ms of required WSI/sync calls (present 0.070, submit 0.027, timeline wait 0.026, acquire 0.025) against GL's ~0.014 ms of present — so it dominates an almost-empty frame and disappears into a full one. **The campaign's named cost centre is closed as measured-and-not-worth-it:** bindings 4/6/7/8 cost **0.031 ms for all ~216 draws of the frame**, 2.4% of it. No Vulkan code was changed to chase the miss. **Not taken:** the RenderDoc capture — RenderDoc is not installed on this machine; it carries to V10. | §2 acceptance table; parity is the floor |
|
||||
| **V9** ◐ **implemented, first CI run pending — §5.5.20** | Linux + CI: a `linux-vulkan` job on lavapipe (probe accepts on a real 1.3+ software device, a short real render under xvfb, forced-unsupported → exit 4, `.spv` freshness). **What landed:** the eleven-step job; `ACDREAM_VULKAN_PROBE_FRAMES`, without which the harness cannot terminate unattended; `tools/compile-shaders.ps1` made path-portable; and the report's jq contract pinned by App tests so a rename fails locally rather than in CI. lavapipe clears every gate requirement by source inspection, including the `samplerAnisotropy` V7 made load-bearing. **Deferred:** the physical Linux GPU row, post-cutover, as for Slice L; and Wayland, which no runner offers. **Not attempted, with cause:** a GL-versus-Vulkan pixel comparison — the GL job asserts exit 4 and so has no frame, and the probe renders synthetic scenes rather than the DAT world CI cannot have. | CI green including the new job |
|
||||
| **V10** | Cutover: Vulkan default, GL reachable by env var for one slice, gate scripts default to VK. | complete Release suite + retail expected PNGs **on VK** (baselines not regenerated) + both connected routes + **user visual sign-off** |
|
||||
| **V11** | GL deletion and closeout: delete `Gpu/Gl`, `OpenGLGraphicsDevice`, `ManagedGL*`, `GLSLShader`, `GLHelpers`, `GLStateScope`, `RenderStateCache`, `BindlessSupport`, `GraphicalGlFunctionProbe`, the GL branch in `GameWindow`, the ImGui project and Studio; drop the GL and (if the audit is clean) Chorizite packages; file the retained-UI dev-panels follow-up; swap CI assertions to VK; update the divergence register, architecture doc, code-structure doc, and rendering memory crib; re-measure memory. | complete Release suite + both connected routes + working-set re-measure |
|
||||
|
|
@ -2732,8 +2732,11 @@ individually, because "phase" is not an excuse unless it is specific:
|
|||
3. **Whether AD-46 is visible to a human.** It is 15% of the pixels in one band at
|
||||
an absolute tolerance of 2; nobody has yet looked at a Vulkan treeline beside a
|
||||
GL one and said whether they can tell. That is a user-stop question.
|
||||
4. **The R6 soak and a RenderDoc capture natively on Vulkan**, both named in the
|
||||
V7 row and neither run.
|
||||
4. ~~**The R6 soak and a RenderDoc capture natively on Vulkan**, both named in the
|
||||
V7 row and neither run.~~ **Half discharged at V8 (§5.5.21): the R6 soak ran
|
||||
natively on Vulkan and passed with zero failures and a graceful exit. The
|
||||
RenderDoc capture has a cause rather than an omission — RenderDoc is not
|
||||
installed on this machine — and carries to V10.**
|
||||
|
||||
**Gates.** Per commit: Release build green; App tests **4,133 / 3** then
|
||||
**4,134 / 3** against the 4,132/3 baseline (three new — the sky-phase parse, the
|
||||
|
|
@ -2855,6 +2858,406 @@ every extracted `run` block: 9/9 clean, no `actionlint` available locally and
|
|||
none downloaded. **The job itself has not run**: its first execution is the CI
|
||||
run this commit triggers, and the row stays ◐ until that is green.
|
||||
|
||||
#### 5.5.21 V8 (2026-07-28): the gate, and the instrument it needed first
|
||||
|
||||
**The headline is a split verdict, and the split is not where anyone expected.**
|
||||
Measured in three configurations on the same machine, the same build and the same
|
||||
day:
|
||||
|
||||
- **Stationary, light scene, 780-870 FPS** — Vulkan is 4.1x cheaper on GPU,
|
||||
allocates 6.8x less and holds 7% less working set, and **costs 14.8% more CPU
|
||||
per frame**. Two of seven floors missed.
|
||||
- **Stationary, dense scene, 165-170 FPS, identical 21,024-entity world** —
|
||||
**Vulkan wins every row**, CPU p50 included, and uses **18.5% less total process
|
||||
CPU** by Windows' own accounting.
|
||||
- **Canonical nine-stop route, identical world at all nine stops** — **Vulkan
|
||||
wins every row**, renders **27.3% more frames** in the same 506 seconds and
|
||||
halves GPU p99.
|
||||
|
||||
The three do not contradict each other. The Vulkan-specific cost is **fixed per
|
||||
frame**, so it dominates an almost-empty frame and disappears into a full one.
|
||||
The recommendation is at the end; the measurement comes first, because V7's
|
||||
lesson was that a number is worth what its instrument is worth.
|
||||
|
||||
---
|
||||
|
||||
**0. The Vulkan arm had no instrument at all** (`00e1b321`). V6h wired the
|
||||
Vulkan frame spine to `NullRenderFrameGpuMeasurement`, and that class's
|
||||
`BeginFrame` is the **only** caller of `FrameProfiler.FrameBoundary`. So a Vulkan
|
||||
run produced no `[frame-prof]` line, no CPU frame distribution, no
|
||||
allocation-per-frame column, no frame-history CSV and no GPU sample. The R6 soak
|
||||
waits on `[frame-prof]` boundaries to time its samples, so the campaign's own
|
||||
performance vehicle could not be pointed at the backend V8 exists to judge.
|
||||
|
||||
`VulkanFrameGpuMeasurement` closes it, and the bracket is deliberately the same
|
||||
one GL uses — resource preparation, world scene and private presentation, not the
|
||||
swapchain present — because two differently-bracketed numbers in one comparison
|
||||
table are worse than none. Vulkan timestamps resolve two or three frames late, so
|
||||
the sample carries the profiler frame index that ISSUED it, the pairing
|
||||
`GpuFrameTimer` already performs internally on GL. `VulkanGpuTimerPool` gained
|
||||
`TryTakeResolved`, which consumes what it reports: `TryResolve` reports the last
|
||||
known value forever, which is right for a readout and wrong for a percentile.
|
||||
|
||||
---
|
||||
|
||||
**1. The R6 soak is NOT the vehicle the §2 table was measured with, and using it
|
||||
would have produced a false verdict against Vulkan.** This is the slice's most
|
||||
important methodological finding and it was nearly missed.
|
||||
|
||||
The founding numbers in §2 come from
|
||||
`docs/research/2026-07-25-slice-g5-production-profile.md`, whose vehicle is
|
||||
stated there in as many words: "a separate Release client ran uncapped with **no
|
||||
UI probe or automation route; no automation artifact owner or
|
||||
screenshot/current-path oracle; no developer tools; no WB diagnostic
|
||||
comparison**; only the permanent frame profiler and the frame-history recorder."
|
||||
The R6 soak has every one of those. Measured rather than assumed — one uncapped
|
||||
GL soak run reported **CPU p50 7.3 ms, 2,531 KiB/frame allocated and a 1,709 MiB
|
||||
working set** at a stationary checkpoint, against the ordinary profile's 1.13 ms,
|
||||
77 KiB and 941 MiB on the same binary the same hour.
|
||||
|
||||
And the bias is not symmetric. `VulkanGraphicsContext` arms
|
||||
`retainBackbufferCapture` **exactly when `ACDREAM_AUTOMATION_ARTIFACT_DIR` is
|
||||
set**, which makes every Vulkan frame copy the whole swapchain image into a
|
||||
readback buffer (`RecordBackbufferCapture`). GL has no such per-frame cost: it
|
||||
reads on demand. A soak-versus-soak table would therefore have charged Vulkan for
|
||||
an instrument the gate itself switched on. That is V7's finding restated, and it
|
||||
is why the numbers below come from the G5 vehicle.
|
||||
|
||||
The G5 vehicle was reproduced with one addition — `ACDREAM_WORLD_TIME=0.5` and
|
||||
`ACDREAM_DAY_GROUP=0`, V7's two instrument pins, so both arms light the same
|
||||
scene the same way. **No probe script, no artifact directory, no developer tools
|
||||
and no movement**: the client logs in where the character stands, settles for 100
|
||||
seconds, and the final 60 are the window. Every launch variable is disclosed in
|
||||
each run's `report.json`, and what is absent from that list is as much of the
|
||||
method as what is present.
|
||||
|
||||
---
|
||||
|
||||
**2. Every run taken, and the conditions.** Physical console session (session 1,
|
||||
2560x1440 at 240 Hz, RX 9070 XT, driver 2.0.395 / 32.0.31021.5001), Release,
|
||||
windowed 1280x720, quality High, **4x MSAA on both arms** (confirmed in both
|
||||
logs), uncapped (`ACDREAM_UNCAPPED_RENDER=1`; Vulkan selects
|
||||
`PresentModeImmediateKhr`), live against local ACE, stationary at the Caul
|
||||
plateau where the previous route left the character, final 60 seconds of a
|
||||
100-second settle unless noted. Nothing is excluded from this list.
|
||||
|
||||
| Run | Backend | CPU p50 | p95 | p99 | GPU p50 | p99 | alloc p50 | WS MiB | Priv MiB | FPS |
|
||||
|---|---|---:|---:|---:|---:|---:|---:|---:|---:|---:|
|
||||
| gl-1 | GL | 1.127 | 1.256 | 1.390 | 0.651 | 0.707 | 77,664 | 941.4 | 1250.1 | 871.7 |
|
||||
| gl-2 | GL | 1.130 | 1.269 | 1.441 | 0.658 | 0.717 | 77,704 | 944.0 | 1322.7 | 867.7 |
|
||||
| gl-phase* | GL | 1.125 | 1.260 | 1.407 | 0.646 | 0.708 | 77,664 | 943.7 | 1340.4 | 872.1 |
|
||||
| vk-1 | Vulkan | 1.319 | 1.427 | 1.543 | 0.159 | 0.189 | 11,440 | 880.0 | 1237.7 | 760.5 |
|
||||
| vk-2* | Vulkan | 1.250 | 1.405 | 1.519 | 0.162 | 0.180 | 11,440 | 874.1 | 1233.6 | 778.0 |
|
||||
| vk-3* | Vulkan | 1.314 | 1.409 | 1.512 | 0.159 | 0.179 | 11,400 | 870.0 | 1230.4 | 767.8 |
|
||||
| vk-phase* | Vulkan | 1.273 | 1.432 | 1.546 | 0.160 | 0.185 | 11,440 | 880.3 | 1241.0 | 762.5 |
|
||||
| vk-validation | Vulkan | 2.609 | 3.195 | 3.536 | 0.158 | 0.190 | 11,440 | 897.3 | 1268.3 | 371.8 |
|
||||
| gl-dense† | GL | 5.934 | 6.972 | 8.867 | 1.673 | 1.858 | 82,016 | 1161.0 | 1476.0 | 165.2 |
|
||||
| vk-dense† | Vulkan | 5.775 | 6.563 | 7.354 | 0.909 | 0.963 | 15,752 | 1106.6 | 1278.5 | 169.5 |
|
||||
|
||||
`*` carried the temporary attribution probe described in item 4. It is not an
|
||||
outlier filter: the probed Vulkan runs are FASTER than the unprobed one, so the
|
||||
probe is not inflating anything and the 1.25-1.32 ms spread is run-to-run
|
||||
variance. GL's spread over three runs is 1.125-1.130 ms, which is 0.4%.
|
||||
|
||||
`†` is the **dense** stationary pair of item 3b — same spot and same camera as
|
||||
the rows above it, but taken after the two soaks left the character in a Caul
|
||||
ACE had grown to **21,024 entities**, so it is a different scene and belongs to
|
||||
its own comparison rather than to the medians below. Both arms met the identical
|
||||
population.
|
||||
|
||||
`vk-validation` is the required validation-layer run, reported in the same table
|
||||
rather than in a footnote because it is a real run. Its cost is the layer's:
|
||||
**zero validation errors and zero validation warnings** over a complete connected
|
||||
session, with the loader's own `Insert instance layer
|
||||
"VK_LAYER_KHRONOS_validation"` line as proof the layer was inserted rather than
|
||||
assumed. Measurement runs have it off.
|
||||
|
||||
---
|
||||
|
||||
**3. The acceptance table, judged against today's GL rather than the founding
|
||||
numbers.** Today's GL is faster than the founding profile on CPU and GPU and
|
||||
heavier on memory, because the scene is Caul rather than Aerlinthe — so per
|
||||
§2's own method requirement both are reported and the verdict is taken against
|
||||
today's.
|
||||
|
||||
| Dimension | Founding GL (G5, Aerlinthe) | GL today (median of 3) | Vulkan today (median of 4) | Floor | Verdict |
|
||||
|---|---:|---:|---:|---|---|
|
||||
| CPU frame p50 | 1.869 ms | **1.127 ms** | **1.294 ms** | <= GL today | **MISS**, +14.8% |
|
||||
| CPU frame p99 | 2.484 ms | **1.407 ms** | **1.531 ms** | <= GL today | **MISS**, +8.8% |
|
||||
| GPU frame p50 | 1.096 ms | **0.651 ms** | **0.160 ms** | <= GL today | **PASS**, -75.4% |
|
||||
| GPU frame p99 | 1.136 ms | **0.708 ms** | **0.182 ms** | <= GL today | **PASS**, -74.3% |
|
||||
| Working set | 652.1 MiB | **943.7 MiB** | **877.1 MiB** | <= GL today | **PASS**, -7.0% |
|
||||
| Private set | 928.3 MiB | **1322.7 MiB** | **1235.7 MiB** | <= GL today | **PASS**, -6.6% |
|
||||
| Frame-thread alloc p50 | 22,880 B | **77,664 B** | **11,440 B** | <= GL today | **PASS**, -85.3% |
|
||||
|
||||
Two rows of that table deserve a note rather than a silent pass. **The founding
|
||||
"~0 B/frame" row in §2 was never true of any whole-frame measurement**: the G5
|
||||
evidence it cites records 22,880 B/frame, and §2 appears to have borrowed the
|
||||
figure from the `#250` zero-allocation UNIT tests, which assert zero bytes across
|
||||
named hot paths and not across a frame. Judged as written, both backends fail a
|
||||
0 B whole-frame criterion; judged as a comparison, Vulkan allocates 15% of what
|
||||
GL does. And **memory is higher than the founding profile on both arms** for the
|
||||
same scene reason as the CPU numbers, which is why the comparison is GL-today
|
||||
versus Vulkan-today and not either against G5.
|
||||
|
||||
---
|
||||
|
||||
**3b. The same stationary measurement in a DENSE scene, and it reverses the CPU
|
||||
rows.** The two soaks left the character standing in a Caul that ACE had grown to
|
||||
**21,024 entities**, so the ordinary-production profile was taken again on both
|
||||
arms, same spot, same camera, same settings, populations identical to the entity
|
||||
— everything as above except that the frame now contains a town instead of a
|
||||
field.
|
||||
|
||||
| Dimension | GL (21,024 ent) | Vulkan (21,024 ent) | |
|
||||
|---|---:|---:|---|
|
||||
| CPU frame p50 | 5.934 ms | **5.775 ms** | **-2.7%** |
|
||||
| CPU frame p95 | 6.972 ms | **6.563 ms** | **-5.9%** |
|
||||
| CPU frame p99 | 8.867 ms | **7.354 ms** | **-17.1%** |
|
||||
| GPU frame p50 | 1.673 ms | **0.909 ms** | **-45.7%** |
|
||||
| GPU frame p99 | 1.858 ms | **0.963 ms** | **-48.2%** |
|
||||
| Frame-thread alloc p50 | 82,016 B | **15,752 B** | **-80.8%** |
|
||||
| **Process CPU, whole window** | **1.246 cores** | **1.016 cores** | **-18.5%** |
|
||||
| Working set | 1,161.0 MiB | **1,106.6 MiB** | **-4.7%** |
|
||||
| Private set | 1,476.0 MiB | **1,278.5 MiB** | **-13.4%** |
|
||||
| FPS | 165.2 | **169.5** | **+2.6%** |
|
||||
|
||||
**Vulkan wins every row.** This is the cleanest comparison in the slice — one
|
||||
stationary camera, one identical world, no route, no automation, no probe — and
|
||||
it is the direct test of the explanation in item 4: the Vulkan-specific per-frame
|
||||
cost is fixed, so it is 12% of an 1.13 ms frame and 2.5% of a 5.9 ms one, while
|
||||
the GPU and allocation savings scale with the work.
|
||||
|
||||
The process-CPU row is worth its own sentence because it comes from Windows
|
||||
rather than from acdream's own instrument: **1.016 cores against 1.246**, an
|
||||
18.5% reduction in total process CPU measured by `TotalProcessorTime` across the
|
||||
same 60-second window. Nothing in that number passes through `FrameProfiler`.
|
||||
|
||||
---
|
||||
|
||||
**4. Where the CPU difference is, measured rather than guessed.** A temporary
|
||||
env-gated probe (`ACDREAM_VK_CPU_PROBE=1`) bracketed the frame spine's phases on
|
||||
**both** arms and, on Vulkan, the four API calls that can block. It has been
|
||||
stripped; it was one file plus five call sites in `RenderFrameOrchestrator`, four
|
||||
in `VulkanGpuDevice` and three in `VulkanGpuPassEncoder`, and re-adding it is
|
||||
twenty minutes if V10 wants it again.
|
||||
|
||||
| Phase (mean ms/frame) | GL | Vulkan | delta |
|
||||
|---|---:|---:|---:|
|
||||
| open the GPU frame | 0.001 | 0.056 | **+0.055** |
|
||||
| resource preparation | 0.003 | 0.002 | -0.001 |
|
||||
| world scene | 0.737 | 0.828 | **+0.091** |
|
||||
| private presentation (retained UI, viewports) | 0.229 | 0.148 | **-0.081** |
|
||||
| close the GPU frame | 0.000 | 0.098 | **+0.098** |
|
||||
| sum of phases | 0.970 | 1.132 | +0.162 |
|
||||
| whole-frame CPU p50 | 1.125 | 1.273 | +0.148 |
|
||||
| remainder outside the phases | 0.155 | 0.141 | -0.014 |
|
||||
|
||||
The remainder matters and is easy to misread: **GL's `SwapBuffers` runs in the
|
||||
Silk window loop after the render callback**, so it sits inside `cpu_ms` but
|
||||
outside these brackets, while Vulkan's present sits inside "close the GPU frame".
|
||||
The two remainders are within 0.014 ms of each other, which puts GL's whole
|
||||
present at roughly that figure.
|
||||
|
||||
The Vulkan-only breakdown says the rest:
|
||||
|
||||
| Vulkan call, mean ms/frame | |
|
||||
|---|---:|
|
||||
| `vkQueuePresentKHR` | **0.070** |
|
||||
| `vkQueueSubmit2` | 0.027 |
|
||||
| timeline wait (`VulkanFrameFlightController.BeginFrame`) | 0.026 |
|
||||
| `vkAcquireNextImageKHR` | 0.025 |
|
||||
| descriptor resolve + bind + draw, **all 216 draws of the frame** | 0.031 |
|
||||
| end-of-frame recording (barriers, upload record) | 0.001 |
|
||||
|
||||
**So the campaign's named cost centre is not the problem.** §5.5.14 item 5 and
|
||||
§5.5.18 item 4 both carried "bindings 4, 6, 7 and 8 cost a descriptor write per
|
||||
draw" forward to V8 as the thing to fix if CPU were short. Measured, that whole
|
||||
population — the arena resolve, the `vkUpdateDescriptorSets` round and the
|
||||
`vkCmdBindDescriptorSets` for every one of the frame's ~216 draws — is **0.031
|
||||
ms, about 140 ns per draw and 2.4% of the frame**. Fixing it perfectly would
|
||||
recover a fifth of the gap, and §5.5.14 already records that fixing it means
|
||||
changing the shaders' indexing rather than the layout. **That item can be closed
|
||||
as measured-and-not-worth-it rather than carried again.**
|
||||
|
||||
What the gap actually is: **0.148 ms of per-frame WSI and synchronisation that
|
||||
Vulkan requires and GL does not expose.** Present, submit, acquire and the
|
||||
timeline wait total 0.148 ms against GL's ~0.014 ms of present, and none of the
|
||||
four is a call acdream chooses to make. The world phase's remaining +0.091 ms is
|
||||
the only part inside our code, and 0.031 ms of it is the descriptor population
|
||||
above.
|
||||
|
||||
---
|
||||
|
||||
**5. What was tried, and what was deliberately not.** Of the levers the V8 row
|
||||
names:
|
||||
|
||||
- **Coherent-versus-flush rings** — already taken. Every ring and staging
|
||||
allocation is `HOST_VISIBLE | HOST_COHERENT` (`VulkanMemoryModel`), so there is
|
||||
no per-allocation `vkFlushMappedMemoryRanges` to remove.
|
||||
- **Submit consolidation** — already taken. One `vkQueueSubmit2` per frame,
|
||||
measured at 0.027 ms.
|
||||
- **Pipeline pre-warm coverage** — irrelevant to p50 by construction; every
|
||||
pipeline is built at startup and the pipeline cache is reused from disk
|
||||
(confirmed in the run logs). It could only move max, and max is 2.65-2.99 ms
|
||||
on Vulkan against 2.48-2.77 ms on GL, which is the same population.
|
||||
- **Descriptor writes per draw** — measured at 0.031 ms and left alone, above.
|
||||
- **A fourth swapchain image / a third frame in flight** — considered and NOT
|
||||
attempted. Acquire and the timeline wait cost 0.025 and 0.026 ms while the GPU
|
||||
finishes its frame in **an eighth** of the CPU's time (0.160 ms against 1.294),
|
||||
so nothing is actually waiting and those figures are the cost of making the
|
||||
calls. A fourth image would buy nothing and cost real memory. Shipping it would
|
||||
have been exactly the unproven optimization the slice brief forbids.
|
||||
|
||||
**No change was made to the Vulkan backend in this slice beyond the instrument.**
|
||||
That is the honest outcome of the attribution: there was nothing to fix that was
|
||||
worth its risk, and inventing one to make a table go green would have been the
|
||||
worse failure.
|
||||
|
||||
---
|
||||
|
||||
**6. The route gates. The R6 soak has now run natively on Vulkan, which V7 left
|
||||
outstanding** (`logs/connected-r6-soak-20260728-210728.report.json`): **PASS, zero
|
||||
failures, exit code 0, graceful WM_CLOSE exit**, 506.6 s, all nine canonical
|
||||
checkpoints in order with every reveal materialized/completed/observed, every
|
||||
`streamingWork` backlog at zero and every named checkpoint artifact present.
|
||||
Its warnings are the documented benign set — the 25 world-edge landblock misses,
|
||||
19 DAT-driven VFX diagnostics, and ACE population drift at the Caul plateau
|
||||
oracle. **A complete connected session on Vulkan: nine teleports, three
|
||||
movement/jump/combat exercises, nine screenshots, and a clean shutdown.**
|
||||
|
||||
**The same route was then run on GL the same hour**
|
||||
(`logs/connected-r6-soak-20260728-211617.report.json`): **PASS, zero failures,
|
||||
exit code 0, graceful exit**, 506.8 s against Vulkan's 506.6 s. Its warnings are
|
||||
the same benign set. **And the two runs met an identical world**: 21,031 /
|
||||
6,675 / 9,404 / ~6,660 / 6,675 / 10,383 / 21,031 / 6,675 / 21,031 entities at
|
||||
the nine stops, the same number at every one. That makes them a matched pair
|
||||
rather than two runs that happen to share a script.
|
||||
|
||||
**Which produces the finding that most complicates this slice's verdict, and it
|
||||
complicates it in Vulkan's favour.** Over the whole route, from the frame
|
||||
histories:
|
||||
|
||||
| Route-wide, 506 s, matched populations | GL | Vulkan | |
|
||||
|---|---:|---:|---|
|
||||
| Frames rendered | 30,378 | **38,683** | **+27.3%** |
|
||||
| CPU p50 | 11.718 ms | **9.166 ms** | **-21.8%** |
|
||||
| CPU p99 | 51.089 ms | **42.826 ms** | **-16.2%** |
|
||||
| GPU p50 | 0.702 ms | **0.167 ms** | **-76.2%** |
|
||||
| GPU p99 | 2.325 ms | **1.193 ms** | **-48.7%** |
|
||||
| Allocation p50 | 4,318,088 B | **4,244,984 B** | -1.7% |
|
||||
| Peak working set | 2,318.5 MiB | **2,295.4 MiB** | -1.0% |
|
||||
|
||||
**On the canonical nine-stop route Vulkan is 21.8% FASTER on CPU p50, not 14.8%
|
||||
slower.** The two results are not in conflict; they are the same fixed cost
|
||||
divided by two very different frames. The stationary profile runs a light scene
|
||||
at 780-870 FPS, where 0.148 ms of per-frame WSI is 12% of the budget. The route
|
||||
moves, teleports, streams and stands in towns of ten and twenty thousand
|
||||
entities at 11-45 FPS, where the same 0.148 ms is under 1% and Vulkan's cheaper
|
||||
submission, cheaper GPU and lighter allocation carry the frame instead.
|
||||
|
||||
**And this comparison is conservative in Vulkan's favour**, because it is taken
|
||||
on the vehicle that charges Vulkan a full-resolution swapchain copy every frame
|
||||
and charges GL nothing. Vulkan wins it anyway.
|
||||
|
||||
Also worth reading twice: the route's GPU p99 is **2.325 ms on GL against 1.193
|
||||
on Vulkan**. Whatever headroom the campaign wanted for future content, that is
|
||||
where it is.
|
||||
|
||||
One observation worth keeping, because it bears on any future use of the soak
|
||||
for numbers and because the matched pair above was luck as much as method: the
|
||||
local ACE world is not a fixed fixture. An earlier aborted GL run met 6,675
|
||||
entities at Caul; the two runs compared above both met **21,031** there, forty
|
||||
minutes later. A pair taken an hour apart would not have been comparable at all.
|
||||
Check the populations before believing any two soak runs, and note that the
|
||||
stationary profile above is immune to this because it never leaves one spot.
|
||||
|
||||
---
|
||||
|
||||
**7. The RenderDoc capture the V7 list carried is still not taken**, and now with
|
||||
a stated cause rather than an omission: **RenderDoc is not installed on this
|
||||
machine** — not in either Program Files tree, not in `LOCALAPPDATA`, not on
|
||||
`PATH`, and not in the uninstall registry, alongside neither Nsight nor PIX. It
|
||||
carries to V10, which is the right place for it anyway: a capture of the backend
|
||||
that is about to become the client is worth more than a capture of one that is
|
||||
still dark.
|
||||
|
||||
---
|
||||
|
||||
**8. Gates.** Release build green. App tests **4,152 passed / 3 skipped**,
|
||||
exactly the pre-slice baseline, with no member of the `#250` family failing.
|
||||
Strict GL offline pixel gate against `13c8733d`: **1.95e-05 (11 differing pixels
|
||||
of 563,200)**, inside the documented 9-31 px band — **GL did not move**, which is
|
||||
what the two shared files the instrument touches (`FrameProfiler`,
|
||||
`FrameRootComposition`) owed. One connected Vulkan run with
|
||||
`VK_LAYER_KHRONOS_validation` proven inserted by the loader: **zero errors, zero
|
||||
warnings**. No divergence-register row is owed in either direction: the slice
|
||||
adds diagnostic apparatus and changes no rendered pixel.
|
||||
|
||||
---
|
||||
|
||||
**9. The verdict, and the recommendation.**
|
||||
|
||||
**Floors met: 5 of 7 measured dimensions. CPU frame p50 and CPU frame p99 are
|
||||
not met**, by 14.8% and 8.8%.
|
||||
|
||||
**The campaign's absolute targets** (§2's middle column) tell a different and
|
||||
sharper story. Vulkan **beats** the CPU p50 target of 1.60 ms at 1.294 ms and the
|
||||
GPU p50 target of 1.00 ms at 0.160 ms, and **misses** the working-set target of
|
||||
600 MiB at 877 MiB, the private-set target of 860 MiB at 1,236 MiB and the 0 B
|
||||
allocation target at 11,440 B. But GL misses all three of those too, by more —
|
||||
943 MiB, 1,323 MiB and 77,664 B — at this scene. **The absolute memory targets
|
||||
were written from a measurement at Aerlinthe and are out of reach for either
|
||||
backend at Caul**, which is a fact about the target, not about Vulkan.
|
||||
|
||||
So the relative floor is the only meaningful test, and it was applied to three
|
||||
independent configurations:
|
||||
|
||||
| Configuration | Rows missed by Vulkan |
|
||||
|---|---|
|
||||
| Stationary, light scene (6,675 entities, 780-870 FPS) | **CPU p50, CPU p99** |
|
||||
| Stationary, dense scene (21,024 entities, 165-170 FPS) | **none — Vulkan wins every row** |
|
||||
| Canonical nine-stop route, matched world | **none — Vulkan wins every row** |
|
||||
|
||||
**The only configuration in which Vulkan loses anything is a stationary field at
|
||||
a frame rate no player will ever see.** Add a town to the frame, or move through
|
||||
the world, and the sign flips on every dimension including total process CPU,
|
||||
which Windows measures rather than acdream.
|
||||
|
||||
**V8 therefore cannot declare a clean pass of the gate as literally written, and
|
||||
would be misrepresenting the evidence if it declared a failure.** What the one
|
||||
missing configuration is made of:
|
||||
|
||||
- It is **0.148 ms/frame of required Vulkan WSI and synchronisation calls**, not
|
||||
a defect, not a missing optimization, and not in acdream's code.
|
||||
- It is a **fixed** cost, and that is the whole explanation. It is 12% of a
|
||||
1.13 ms frame, 2.5% of a 5.9 ms one, and under 1% of the route's dense-town
|
||||
frames — measured, not modelled, in items 3b and 6. The GPU and allocation
|
||||
savings scale with the work instead, which is why the sign flips.
|
||||
- Against it stand: **every row on the dense stationary pair**, including 18.5%
|
||||
less total process CPU as measured by Windows; **every row on the route**,
|
||||
including 27.3% more frames and a halved GPU p99; a **75-46% GPU reduction**
|
||||
and an **85-81% allocation reduction** in every configuration tested; the
|
||||
compatibility case that started the campaign; and a zero-error validation run.
|
||||
|
||||
**The recommendation is to proceed to V10, and to amend §2's acceptance table
|
||||
rather than waive it.** A floor of "no worse than GL on any dimension" is met in
|
||||
both configurations that resemble playing the game and missed only in one that
|
||||
does not. The honest amendment names the scene and the pacing the floor is judged
|
||||
at — and a natural choice is already sitting in the evidence: the dense
|
||||
stationary pair and the nine-stop route, both of which Vulkan passes outright.
|
||||
The opposite reading, that the light-scene rows are disqualifying, is available
|
||||
and this slice has given it the same measurement space as the rest. **The call is
|
||||
the user's, and this slice does not make it.**
|
||||
|
||||
Three smaller corrections V8 recommends to §2 while it is open: the "~0 B/frame"
|
||||
allocation row should say what it means (the `#250` unit-test criterion, or a
|
||||
comparative frame-thread figure — it is not a whole-frame absolute); the table
|
||||
should name its measurement vehicle, because the obvious candidate is the wrong
|
||||
one and cost this slice an hour to establish; and the founding numbers should be
|
||||
labelled with their scene, because Caul and Aerlinthe differ by more than the
|
||||
entire GL-versus-Vulkan CPU gap.
|
||||
|
||||
### 5.4 The null-target `BeginPass` divergence (V4c) — ✅ DISCHARGED at V6k
|
||||
|
||||
> **Closed 2026-07-28 by V6k commit 2 (`eb7e6b4e`); see §5.5.16.** The answer is
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue