m= is the wake-up in CLOCK_MONOTONIC ms (the end is m + e), r= the eye releases
after the wake-up (-1 on a repeat), t= the predicted display time converted with
xrConvertTimeToTimespecTimeKHR; an 'XRF clock:' line says how. Lines the app's
timeline up with vrframes.log (frame/tools/phase.py). DEV.md: lane W section.
Mock (END_DELAY 4000, MOCK_XR_TIME_OFFSET_S=5): ok, offset 5000 ms removed,
r = e - 0.01 ms; ctest 32/32.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Default unchanged. With idle, vkQueueWaitIdle under the queue guard right before
xrReleaseSwapchainImage of the eye pairs and before xrEndFrame, so SteamVR's
client submissions there never wait behind our next set's GPU work (the probe,
clean on the headset, had nothing queued at those points). Logs the drain's
cost every 10 s. DEV.md lines for the switch and the mock's new lines.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A frame whose only layer would be the projection gets a 16x16 transparent quad
(alpha blended, 1 cm, 2 m ahead) after it; its images are cleared once by the
bridge's own command buffer, waited for and released. Before each set's first
writes the bridge submits a barrier ALL_COMMANDS/MEMORY_WRITE to
ALL_COMMANDS/MEMORY_READ|WRITE on its images (layout kept), on Dawn's queue under
its lock, ahead of Aurora's set. XRF lines carry n=<layers>; xrmock counts the
layer lists by shape and flags quads on never-released swapchains.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
KARTFRAME_XR_FORMAT=bgra, KARTFRAME_XR_USAGE=kart and KARTFRAME_END_LOCATE=0 restore the old way;
KARTFRAME_XR_EXTENSIONS=galaxy asks for GalaxyFrame's extension list. The sRGB sink draw writes the
RGBA image (and the menus' screen image) from Aurora's BGRA eyes. The clock fallback without
XR_KHR_convert_timespec_time is decided once, at the first frame.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The panel recording of 20261014-010727 showed one eye at a time displaying an image it had shown 3-5
refreshes before (race: left 10.4 %, right 7.0 %, both together 0.02 %, eyes out of step 19.4 %; menus 0),
at the old pixels while the image moved: SteamVR resolves each eye's image when it composes, after our
alternating pair was already acquired again. The mock's compositor model (lazyacq) reproduces it with two
pairs (left 24.6 %, right 15.1 %, eyes from different sets 18.8 %) and shows none with six.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The eyes were copied raw into a UNORM view of the sRGB swapchain images, which needed
MUTABLE_FORMAT; without a format list the driver lays such an image out uncompressed, and the copy
of both 1728 eyes cost 2.3 ms of every refresh set (mock GP race, SteamVR's compositor running, GPU
at its top clock, SoC 93 C). Now a full-screen draw decodes the eyes' sRGB-encoded values and writes
them through an sRGB view: the same bytes (the mock's stamps read back exactly), no mutable flag, and
the images compress. Two A/B pairs, same clock and temperature: sink 2.35/2.28 -> 0.47/0.48 ms, refresh
set 7.16/7.08 -> 5.14/5.10 ms, race 21.3/19.4 -> 1.6/1.6 % late, 94.8/97.0 -> 118.4/118.5 sets/s.
KARTFRAME_SINK=copy|blit restores the copy (and the flag). The direct-to-swapchain trial stays for
those modes; with the default it never applies. Menus: copies as before (non-mutable images).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The backend's submit-to-done time could not tell the two ways apart (queue backlog carried from one
block into the next; the first try picked direct on host-visible mock images, 25 % late). Aurora now
times the eye passes through the sink of each trial set itself (first eye to the end of the copy or
stamp), blocks of 16 alternating after the first 600 refresh sets, and keeps the cheaper way for the
process (KARTFRAME_DIRECT_EYES=0/1 forces). The bridge no longer takes a per-set permission.
frame/perf/run.sh records the GPU devfreq clock and the hottest thermal zone (gpufreq.tsv).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
SteamVR on the Frame showed no ray or mark over the screen with the environment layer submitted over
the quad (fourth headset session): it evidently composites the projection layer under the quad
whatever the submission order. The marks now lie on the screen's own surface (both hands), and the
environment layer goes under the quad again (KARTFRAME_MENU_ENV=over: lane H's order). H4.md.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Lane H's R1 (f127ea4) mapped every blended full-frame 2D rectangle drawn after the scene across the
whole eye, head-locked from frame space. Textured ones are composites of a picture (the Time Trial
ghost from its offscreen copy, the player's character): pinned to the head they doubled against the
world on every head turn (90 Hz headset session). A fade now needs no texgens (the respawn black,
white flashes, wipes, the scene fader); textured full-frame quads go back to their pre-lane-H path.
'Full-frame 2D draw class: ... -> ...' names each class and its path once.
Also: the backend links the bridge's allow_direct weakly (test binaries stub the bridge).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
KARTFRAME_FPSLOG=2 now times every set (game-frame and refresh sets) with in-pass timestamps per
draw class (scene, HUD, fades, EFB effects, sink copy); frame/perf/gpuprof.py tabulates them.
Eyes may render straight into the swapchain images (the bridge's DirectTargets); the Vulkan backend
measures both ways on the first race sets and keeps the cheaper (KARTFRAME_DIRECT_EYES=0/1 forces).
The native render of an immersive race frame stops after its last copy for the eyes on Linux too
(KARTFRAME_MONO_FULL=1 keeps it whole) and is clipped to the copies' rectangles
(KARTFRAME_NATIVE_CLIP=0). DisplayTimeNanos returns the converted CLOCK_MONOTONIC time directly
(two clock reads put two stamps 0.24 ms off in the orchestrator's soak); the mock counts such
offsets as 'between', honours MUTABLE_FORMAT. Not yet soaked.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Owner, third session: the laser ray reached the screen's edge and stopped; its part over the screen
and its target mark were hidden. Lane G's environment projection layer (controllers, rays, marks)
was under the screen's quad. It now goes over it, blended by its alpha
(XR_COMPOSITION_LAYER_BLEND_TEXTURE_SOURCE_ALPHA_BIT, the frame's only projection layer): the screen's
area is (0,0,0,0) except where a ray, a target mark or a controller is drawn, all opaque, so
premultiplied and straight alpha give the same result. A controller held in front of the screen
stays in front of it. KARTFRAME_MENU_ENV=under restores the old order.
xrmock: MOCK_XR_DUMP_ALPHA=1 dumps the alpha channel too (layers blended by alpha can be composited
from the dumps), MOCK_XR_HANDS=high raises the right controller in front of the menu screen;
hudrace.sh converts the alpha dumps.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Mock race without other load: 60.0 game fps and 75 slow frames in 74 s with the clamp, 58.2 fps and
376 without (nice -15 alone), 58.8 fps and 286 with neither: the game thread's 11 to 14 ms of logic
a frame wants the Frame's one 1024-capacity core. sched_setattr needs no privilege for this.
KARTFRAME_UCLAMP=0 leaves the clamps alone; the "thread priority:" lines show what was set.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
H3: a PrimeFrame build during the session starved the game thread (2095 slow game frames, runq
waits). The pacing thread raises itself, the game thread, the GX thread, Aurora's frame worker and
the GPU watcher to the lowest nice RealtimeKit grants (at most -15), else what RLIMIT_NICE allows
(KARTFRAME_THREADPRIO=high|nice|off; "thread priority:" log lines). Aurora publishes the frame
worker's thread id and names it on Linux.
frame/perf/run.sh takes PrimeFrame's build lock as well as KartFrame's; frame/perf/bgload.sh is a
background compile load for runs; hudrace.sh keeps its runs under kf-laneH.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
H3: lane G's end-at-locate rule gave every set shown a refresh late (24 % of the race) a display
time one refresh older than its cycle's; SteamVR's mispresented frames rose from 116 to 1284 and
reused app frames from 0.2 to 4.4 %. Every xrEndFrame now carries its cycle's predicted time
(KARTFRAME_END_TIME=locate restores lane G's rule). The "vr display times" line counts sets shown a
refresh or more late apart from SteamVR's +-0.02 ms prediction jitter (which made nearly every set
"located for another time").
xrmock: the Frame's own per-eye FOVs by default, predictedDisplayTime 3 refreshes after the wake
(MOCK_XR_LEAD; SteamVR's lead was 25 to 27 ms), 0.02 ms prediction jitter by default, an ERROR for
every xrEndFrame whose display time is not its frame's predicted time ("xrmock: end times"), and
MOCK_XR_DUMP_STEP for full-resolution dumps. The GPU watcher publishes its thread id.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Second headset session: 'the pointer and controller models didn't render when it was a 2D screen'.
Since lane E the menus are a room-fixed quad and nothing was drawn into the eyes. Now a menu set also
renders the room around the screen into a packed two-eye image (3456x1728, left eye left half): plain
background, the Frame controller models, the laser pointers and their target marks, and the screen's
area left transparent black. The backend shows it as one projection layer with the set's own poses,
under the screen's quad (the quad stays the sharp, reprojected picture), plus the panel quad; never
two projection layers. KARTFRAME_MENU_ENV=over puts it over the quad, alpha-blended (a controller in
front of the screen then stays in front where the compositor keeps the layer order), =0 turns it off.
Same release-after-GPU rule (separate swapchain pair, swapped like the eyes').
Plumbing without new public API: target 1 of a virtual-screen set is the environment image
(OpenXRPresentation::menu_environment), the packet's 'window' flag (meaningless outside immersive
replay) asks Aurora for it, vrui::render draws one eye into its half (viewport) with the hole.
Mock (MOCK_XR_HANDS=1, Frame profile, warm caches, menus 30-90 s): 120.5 sets/s, 0 late, GPU 2.60 /
2.99 ms a set (off: 1.25 / 5.89), encode 0.52 / 1.04 ms (off 0.15 / 0.60); 0 mock errors, every
release checked finished; the dump shows both eyes' controllers and lasers ending on the screen's
hole. Menus to race to menus in one run: 0 errors, 12866 race eye images with matching stamps.
The frame stamp is written for immersive sets only.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Second headset session: images doubled on head motion although every rate was clean.
- KARTFRAME_FRAME_STAMP=1 (tests): the Vulkan bridge writes the display time an eye set was located
for and the eye into a corner block of each eye image; the mock (MOCK_XR_STAMP) reads it back at
every release and compares it with the locate of the poses each projection layer submits.
Pipelined and inline, with and without prediction jitter: 10064-14318 eye images per run, all
rendered for the locate of their submitted poses. The image/pose pairing is not the cause.
- A layer is ended with the display time its poses were located for (not this cycle's prediction,
which on SteamVR need not be one period after the last), keeping display times increasing; repeats
keep the cycle's time. KARTFRAME_END_TIME=cycle restores the old behaviour. Mock with
MOCK_XR_PREDICT_JITTER=2: before 0 of 7155 fresh layers carried their poses' time, after 7157 of
7159. New log line every 10 s: 'vr display times' (prediction step error, display lead, sets shown
for another time than their cycle's); the eye fields of view are logged once.
- VR interpolation never interpolated with pipelined pacing: a set's display time lies 2-3 refreshes
after the newest game frame's boundary, so the weight was clamped to 1 for 98 % of race sets (mock:
between 2-54 of ~1180 per 10 s) and each game frame showed on two refreshes: 60 Hz scenery on a
120 Hz display, moving things doubled. stereo::SceneClock samples the scene at the display time
minus a learnt delay (running mean of display - boundary, minus half an interval): mock race now
80-87 % of sets between two frames. KARTFRAME_SCENE_CLOCK=0 restores the old rule. New aurora line
'VR interpolation weights (10 s)'.
- xrmock: MOCK_XR_STAMP, MOCK_XR_PREDICT_JITTER, increasing display time check, asymmetric
Frame-like FOV by default (MOCK_XR_FOV), MOCK_XR_HANDS, scripted presses (MOCK_XR_PRESS).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
After H1 (projected menus, half-rate flapping 16 down / 15 up in 4 minutes, a 90 Hz request):
- Linux desktop takes the PC rule for OpenXRProjectedMenus: with menu_shader_quality 0 the game
screen is a quad layer fixed in the room; the first-run guide stays projected. The quad shows only
the picture (OpenXRVirtualScreenContentRect, as the Android backend), not the square eye image
with black bands. Mock: menus 12869 quad frames, 0 errors, 0 releases before the GPU finished.
- Half rate: never by default (a late set repeats the retained layer; SteamVR reprojects it).
KARTFRAME_HALF_RATE=1 always, =auto drops after more than 5 % late over 30 s and never returns
within the session. The 10 s return rule, the GPU gate and the retry wait are gone.
- xrRequestDisplayRefreshRateFB only for a rate SteamVR offers exactly (H1 offered only 90 and the
game asked for 90); otherwise the per-app setting decides, logged.
- session-report.py / pacing-parts.py: the presentation lines (immersive-race, virtual-screen) mark
races too; a race restart logs no new "entered RaceScene" and H1's report counted 150 s of race
as menus.
- DEV.md knobs paragraph updated.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Lateness measured at half rate says nothing about full rate: at 2160 per eye and 120 Hz (GPU 9 ms a
set) the loop cycled 10 s at half rate and 3 s at full rate with 40 % of sets late. It now returns
early only when the GPU time per set (p90 of the sets shown at half rate) is under 60 % of a
refresh, and otherwise after a retry wait that doubles (10, 20, 40 ... 160 s) whenever a return
dropped again within 10 s. Mock, 2160 at 120 Hz: race late 13.5 % -> 4.2 %, back to full rate in
the menus after the race. The shipped default never drops.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Dawn enables VK_EXT_swapchain_maintenance1 on the device whenever the device offers it; the instance
needs VK_EXT_surface_maintenance1 (and VK_KHR_get_surface_capabilities2) for that
(VUID-vkCreateDevice-ppEnabledExtensionNames-01387). With this and the TexCopyConv fix a validation
run through menus and the demo race (200 s, mock, sysmem, pipelined) reports nothing.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- aurora: one 'hitch:' line per game frame over 20 ms (end_frame to end_frame) with the process's
pipeline waits and inline compiles, texture conversion and uploads, and the frame-worker wait in it
(gfx/hitch_stats.hpp counters).
- pacing: 'vr pacing clock' line (session start on CLOCK_MONOTONIC); KARTFRAME_PACING_SAMPLES=<file>
writes every set's GPU and encode ms per second for exact per-part percentiles.
- frame/perf: run.sh (a measured mock run under the build lock with an I/O sampler), pacing-parts.py,
hitches.py, iosample.py. mockrun: KF_MOCKRUN_RENDER_SCALE.
- The display-copy source region is logged once per distinct region (the race alternates between two
every frame: 30 lines a second); the pacing banner is written in one piece (another thread's output
landed inside it: 'pacing: 0per-refresh').
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
MOCK_XR_BUSY_MS now samples the released image's (host-visible) memory at the
release and again once an empty submission made there completed; a change
means the GPU still wrote it. Unrelated queued work no longer counts.
Negative test (KARTFRAME_NO_GPU_WAIT=1): 7078 of 7110 releases flagged;
with the wait: 0 of 6986. mockrun seeds welcome_complete/tutorial_completed
so the attract demo race starts headless.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Aurora's frame worker sat out the producer's prepare permit (about 10 ms of
every game frame) and an eye set published meanwhile waited with it, so
inline sets were late 86 % of the time. With the stereo provider and
interpolation active the sealed frame is now encoded before the permit wait
and the wait serves retained replays. The VR menu sorted ~9000 controller
triangles per eye by moving them and switched material almost every
triangle; it now sorts keys in 5 mm slices by material (encode 6.5 -> 3 ms,
GPU 5 -> 2.6 ms per set in menus). Pacing: 5 s warm-up before the half-rate
rule counts, KARTFRAME_HALF_RATE=0|1, KARTFRAME_PACING_DEBUG timelines.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
[vr] frame_pacing = auto|inline|pipelined (KARTFRAME_PACING wins): one
xrLocateViews per set for the time it is displayed, screens, panel and
pointer from those poses, late sets never released early (the retained
layer is repeated), half rate only on real misses (>12 of 240, back at <=6
of 600 after 10 s). VR interpolation available on desktop Linux with the
CLOCK_MONOTONIC fallback; projected menus on the Frame; 120 Hz requested
through XR_FB_display_refresh_rate; focus blips under 1 s ignored; the
Frame controller extension enabled. One 'vr pacing:' line a second.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
After Aurora's queue.Submit and the bridge's release barrier, an empty
vkQueueSubmit under Dawn's device lock signals a timeline semaphore; a
watcher thread records each set's completion (submit-to-complete time) and
WaitForSubmissionUntil succeeds only once the GPU finished. FinishFrame
refuses to release an unfinished set. KARTFRAME_NO_GPU_WAIT=1 is the
negative test. Adds per-refresh eye-set primitives (BeginCycle, PrepareSet,
EndCycleWithSet, EndCycleRetained, CancelSet, DropSet) for inline and
pipelined pacing, logs the Vulkan layer chain, and reports sets Aurora
rendered from a retained frame. Replay test covers the sets.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
kartframe_kit_probe is WiiCompiled without the game (like the Quest probe); data_sections_init
and guest_symbol_table stay out of the unity batches on Linux too. aurora/extern builds abseil,
zlib, libpng and freetype from the pinned sources as static libraries on Linux
(AURORA_VENDOR_THIRD_PARTY) so the binary needs only stock libraries. make-sysroot.sh copies the
glibc/libstdc++/libgcc/kernel headers and crt objects from this Frame's packages.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Build aurora's Vulkan interop on Linux with the patched Dawn linked statically, extend the Vulkan OpenXR backend guards to desktop Linux, and route openxr_integration to it with XR_KHR_vulkan_enable2 and timespec display-time conversion. Re-applied from the earlier native Linux commit.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>