Aurora's own debug groups only exist when AURORA_GFX_DEBUG_GROUPS is
defined, but the three ImGui passes (desktop overlay, host-frame overlay
and the headset settings panel) pushed and popped theirs in every build.
An Adreno 740 Android driver (512.676) faults in
vkCmdEndDebugUtilsLabelEXT on the first overlay pass (KartPad #321); the
Quest 3 uses the same GPU. Gate all three like the rest of Aurora.
Ported from chrissotraidis/wiicompiled (KartPad) 420b828, extended to the
two extra passes this fork has.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019rLZ24cFcqTNBmb4w2dpSq
On the hardware (and in Dolphin) GXSetZMode with the compare disabled
leaves the Z buffer alone even when the update bit is set. Aurora still
wrote depth, so a full-screen quad drawn that way (the darkening during
Stormship's lightning, for one) marked the whole screen nearest and
every depth-tested draw after it failed until it faded.
Ported from BlackAndBlue95/Strikers-WiiCompiled 369ec992. The pipeline
cache key already includes depthUpdate, so no cache version bump.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018NKSMYZU43wUfYtm1sxGsC
When the GPU is two frames behind, begin_frame waits for the next
staging buffer's MapAsync. It used to spin on ProcessEvents, holding a
whole core busy for as long as the GPU took. It now calls ProcessEvents,
checks for device loss, then sleeps on a condition variable until the
map callback lands or 1 ms passes.
The map state also carries a generation, so a callback from a request
that shutdown or a rotation already retired can no longer mark the
current buffer mapped or unmapped.
From upstream patchzyy/Wiicompiled 6f14bde (#244, KartPad batch).
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Wg7mB8ogCWmp9GH19Uc82B
When no backend could create a window and a WebGPU device, aurora hit
an ASSERT and the process aborted with only SDL's last error in the log.
Keep the first adapter, device or window error, return it from
aurora_initialize with a new initializationStatus, and have the runtime
throw it so the existing fatal popup and fatal log record the reason.
ImGui shutdown now skips backends that never initialized, so the
shutdown that follows a failed start does not crash.
From upstream patchzyy/Wiicompiled 6f14bde (#244, KartPad batch).
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Wg7mB8ogCWmp9GH19Uc82B
When a copy with clear snapshots its source, the plain-copy path reads
the snapshot with CopyTextureToTexture, but the snapshot texture was
created without CopySrc usage.
From upstream patchzyy/Wiicompiled 6f14bde (#244, KartPad batch).
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Wg7mB8ogCWmp9GH19Uc82B
The shared blit shader clamps the sampled Y to flags.z..w, but the
native readback uniform set flags.w to 0, so every row sampled row 0.
The blit pipeline was also built only for the surface format, while the
readback draws into an RGBA8 texture; on a BGRA surface (the usual
Linux/Vulkan choice) the pipeline and attachment formats disagree, which
release builds no longer catch because Dawn validation is skipped.
Build blit pipelines for RGBA8, BGRA8 and the surface format, and pick
one by destination format. The Quest 1 simple blit path is unchanged.
From upstream patchzyy/Wiicompiled 6f14bde (#244, KartPad batch).
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Wg7mB8ogCWmp9GH19Uc82B
The test expected one extra TEV register to grow the uniform by exactly
16 bytes, but the allocation is alignment-rounded, so it failed on the
fork's baseline. Build the uniform and check that the register and the
K color after it are packed next to each other instead.
From upstream patchzyy/Wiicompiled 6f14bde (#244, KartPad batch).
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Wg7mB8ogCWmp9GH19Uc82B
The BP register cache skips writes equal to the cached value, and the
cache starts at zero. A cold GEN_MODE write of zero (one TEV stage, no
culling, no channels or texgens) was therefore dropped, leaving the
decoded state at zero TEV stages until some other GEN_MODE value arrived.
Seed the cache's register ID byte so the first write always decodes.
From upstream patchzyy/Wiicompiled 6f14bde (#244, KartPad batch).
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Wg7mB8ogCWmp9GH19Uc82B
Incomplete primitives no longer emit indices: quads drop a one- or
two-vertex tail and draw a three-vertex tail as a triangle, triangle
lists ignore leftover vertices, and fans or strips under three vertices
emit nothing. Draws with no complete primitive are skipped. Previously an
incomplete quad indexed vertices that belonged to the next merged draw,
or past the end of the buffer.
Merging now stops before the 16-bit index offset would wrap, never folds
triangles into a single-instance line or point draw, and breaks after
GXInvalidateVtxCache or a vertex-format switch, so the next draw uploads
fresh arrays and uses its own format's shader.
From upstream patchzyy/Wiicompiled 6f14bde (#244, KartPad batch).
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Wg7mB8ogCWmp9GH19Uc82B
Dawn asks for a blob's size before allocating and copying it. A row whose
stored length, declared size or zstd frame size disagree, or whose
compression flag is unknown, made it allocate the declared size and copy
or decompress past the stored blob. Such rows are now logged and skipped.
From upstream patchzyy/Wiicompiled 6f14bde (#244, KartPad 70951022).
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Wg7mB8ogCWmp9GH19Uc82B
VIConfigure writes the mode on the game side while the GX thread, the
frame worker and the window code read its size. The resize request is
made after the lock is released.
From upstream patchzyy/Wiicompiled 6f14bde (#244).
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Wg7mB8ogCWmp9GH19Uc82B
load_u24 and raw_fetch_u8_3 read a whole 32-bit word pair even when the
three bytes sit in the first word, so an attribute at the end of its
binding read past it. Release builds disable Dawn's robustness, so that
read isn't clamped.
From upstream patchzyy/Wiicompiled 6f14bde (#244, KartPad 0f6b274).
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Wg7mB8ogCWmp9GH19Uc82B
Compiler workers and renderer waiters share g_pipelineCv. notify_one could
wake a renderer thread instead of a compiler, leaving a queued pipeline
until some later wake-up; with skip_unready_pipelines its draws stay
missing that much longer. Promoting a background build to priority now
wakes the workers too.
From upstream patchzyy/Wiicompiled 6f14bde (#244, KartPad 956d811e).
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Wg7mB8ogCWmp9GH19Uc82B
* Fix macOS controller platform, sync player indices, and preserve socket send error
- Preserve captured socket send error across diagnostic logging in network_socket.cpp
- Use SDL_GetPlatform() instead of hardcoded Windows in controller mapping wizard
- Synchronize Aurora and SDL player indices on port assignment and clear in pad.cpp
- Auto-assign Player 1 on macOS when unconfigured in input.cpp
* fix(input): refine macOS port 0 assignment and persistence logic
- Persist port 0 preference for newly connected controller even if already assigned player index 0
- Verify controller has no preference on other ports before assigning to port 0
(cherry picked from commit 85f250155f8f397c1c463dc51dd4dc2707371d06)
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011tcyLz63pXjoYEwsFjfg8F
[vr] adaptive_resolution (off by default, in the VR tab) lowers the
race's eye resolution by 10% steps, down to 70%, while new eye frames
fall below 85% of the game's 60 FPS, and raises it again after three
seconds on time. Adapted from heurazy/Wiicompiled_VR-PLUS.
The eyes render into the top-left corner of the full swapchain image
and the projection layer shows only that corner, so the compositor
upscales them and no swapchain is recreated, unlike render_scale. The
Linux same-device bridge now copies an eye smaller than its target;
the Quest's bridge and layer already did. The PC's D3D12 path copies
whole eyes, so the setting is not offered on Windows.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Sabhzu6VgoWcHtpSk4C9KZ
Nintendo's GXClearVtxDesc, and Aurora's, keep GX_VA_POS at GX_DIRECT. The
HLE mirror cleared it to GX_NONE, so the next draw could publish no
position attribute and abort shader generation while a scene loads.
Keep the mirror in step, and give the shader a zero position if a
stream still arrives without one.
Ported from heurazy/Wiicompiled_VR-PLUS (c2b2500).
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Sabhzu6VgoWcHtpSk4C9KZ
On the Steam Frame the full-density region was too small and lagged the
eyes. Gaze-centred maps now widen both of the level's rings by 8 degrees,
covering a saccade until the next frame's map is bound, the tracker's error
and Turnip's per-bin density, and each eye keeps 128 maps instead of 32 so
fewer glances wait for an upload. The fixed, forward maps are unchanged.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019HBRGKTE1GnN2ah8gcZKr3
On the Steam Frame the game crashed in Turnip while Dawn recorded an eye
render pass, reading an unmapped address. Turnip reads a density map through
a host mapping of its memory taken at bind time, and the maps were
sub-allocated from blocks that Dawn's buffer upload fast path maps and
unmaps. A dedicated allocation is never mapped or unmapped by Dawn.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019HBRGKTE1GnN2ah8gcZKr3
A SIGSEGV on the Steam Frame logged only the guest state and 'Host stack
trace unavailable on this platform'. With glibc the handler now names the
faulting thread, its pc and lr, and a backtrace, each as module + offset for
addr2line. The Dawn provider also says when a local package (--dawn-package)
replaces the download instead of printing the URL it does not fetch.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019HBRGKTE1GnN2ah8gcZKr3
MKW_VR_STANDALONE (runtime_config.h) names the standalone headsets: the
Quest, and the Steam Frame in its Android flavour and its native SteamOS
build. They share render scale 0.8, foveation medium, the GX thread,
the game's object culling, and the headset panel's foveation and
tracked-hands rows; passthrough stays Quest-only.
The Frame's native build also sets AuroraConfig::xrHeadsetOnly, so
Aurora neither presents the desktop window nobody watches nor renders
it past the last pass the eyes sample, which Android does always (4 to
6 ms of a 12 ms GPU frame on a Quest 3).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019HBRGKTE1GnN2ah8gcZKr3
Lepton's Vulkan driver has no external memory or sync fd extensions,
so the Steam Frame gets a native SteamOS build instead. Its backend is
the PC's: OpenXR creates Dawn's own Vulkan instance and device, and the
eyes are copied into the swapchain on Dawn's queue, with nothing shared
between devices.
- openxr_vulkan_win32 and Aurora's vulkan_win32_interop now compile on
desktop Linux. Dawn links statically there, so the bridge calls the
patched package's hooks directly, and exists only when the package's
aurora-dawn.json declares the Vulkan hook ABI (AuroraDawnProvider
turns AuroraVulkanAbi into AURORA_DAWN_VULKAN_HOOKS); a stock Dawn
keeps the stubs and VR falls back to the desktop.
- openxr_integration.cpp gains a Linux branch: XR_KHR_vulkan_enable2,
the timespec clock, refresh rate, performance level, the Frame
controllers and eye gaze; fragment density maps are requested on
Linux as on the Quest (fdm.cpp and gpu.cpp, AURORA_FDM=0/1 overrides).
- Controller motion uses XR_KHR_convert_timespec_time on any Linux.
- CMake: Vulkan headers are fetched for Linux OpenXR builds, the
headset option is MKW_HEADSET on Android and Linux, and Linux aarch64
builds take MKW_LINUX_CPU (cortex-x4 for the Steam Frame, else native).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019HBRGKTE1GnN2ah8gcZKr3
With [vr] eye_tracked_foveation (on by default on the Steam Frame, off
elsewhere) the runtime asks for XR_EXT_eye_gaze_interaction. When the
system reports an eye tracker, OpenXRInput binds the gaze pose and
locates it for each packet's display time, in the space the eye views
are located in; vr/eye_gaze.h turns it into tangents of each eye's own
view, which AuroraStereoFrame now carries (appended, after the existing
prefix).
Aurora centres the eye's fragment density map on the gaze snapped to a
cell of two map texels (about 3 degrees). Each eye keeps up to 32 maps,
one per cell looked at, so a glance back reuses its map; a new map is
bound once its upload completes, and until then the eye keeps the map
it had. Without a tracked gaze (a blink, no tracker, the setting off)
foveation centres on the forward direction exactly as before: the
forward maps are byte-identical.
Also logs every extension the OpenXR runtime offers at startup, so the
first Steam Frame session shows what SteamVR's Android runtime has.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019HBRGKTE1GnN2ah8gcZKr3
- Updated stereo_frame_worker_smoke.cpp to allow dynamic headset rates and prediction lead time.
- Improved logging to include motion diagnostics and adjusted frame submission logic based on headset frequency.
- Enhanced stereo_interpolation_test.cpp with additional tests for camera motion separation and playback cadence.
- Introduced MkwVRReadSceneView function to read the camera view matrix for improved scene rendering.
- Modified VR first-person logic to support scene view reading and validation.
- Added scene_camera.hpp to encapsulate camera motion handling and inverse view calculations.
- Ensured that the VR integration layer correctly logs motion diagnostics and handles scene playback accurately.
- Implemented DVDReadVrAsset function to read mapped disc paths for VR assets.
- Created HeldItem structure and ReadHeldItem function for managing held items in the game.
- Developed unit tests for ReadHeldItem to ensure correct functionality and edge case handling.
- Added cockpit item data tests to validate model indexing and parsing of archives.
- Added support for hiding model arrays in the rendering pipeline to optimize performance during VR gameplay.
- Introduced new functions in `gx_model_visibility` to manage hidden model arrays and their visibility based on game state.
- Enhanced cockpit recentering logic to ensure accurate seat measurements and eye positioning during gameplay.
- Updated `FirstPersonState` to track cockpit height and forward direction, improving VR experience.
- Added tests for Bullet Bill model visibility and cockpit height adjustments to ensure functionality and stability.
- Refactored existing code to accommodate new features and improve overall code organization.
Adds the modernQuest and quest1 headset flavours (Kryo CPU target, direct-VR
library entry on Quest 1), records the CPU target in the game kit and checks
it wherever a kit or game package is used, and gates the Quest 1 EFB-copy and
pipeline-scheduling workarounds on the monterey device.
Resolved docs/quest-port.md by keeping both sides: the foveated rendering
section and the Quest 1 renderer compatibility section, and both Build-Quest.ps1
command lines.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- Added SetRenderScale method to both D3D12 and Vulkan backends to adjust the render scale dynamically.
- Updated eye size calculations to reflect the new render scale in both backends.
- Implemented ResizeWritablePair method to handle resizing of swapchains based on the requested eye sizes.
- Modified BeginFrame methods to ensure swapchains are resized appropriately before rendering.
- Enhanced error handling to maintain current eye sizes when the requested sizes cannot be allocated.
- Added tests to validate render scale functionality, ensuring correct behavior when scaling up and down, including handling of refused sizes.
- New [vr] hand_tracking (default off, Quest only for now): the Quest launcher's Settings > VR and
the headset panel's VR tab, under hand steering. Two XR_EXT_hand_tracking trackers are located
every XR frame: with the controllers held the Quest builds the joints from their touch sensors
(XR_EXT_hand_tracking_data_source's controller source), once they are put down from its
cameras. The trackers exist only while the option and hand steering are on and also serve the
runtime hand mesh; the extensions (plus XR_FB_hand_tracking_aim) are asked for when either is
on at launch.
- Aurora skins the runtime mesh with the joints themselves (tracked pose times inverse bind pose,
no curl, no grip); runtimes with joints but no mesh get a skeleton; non-finite joints put only
that hand back on its grip curl. AuroraCockpitHand carries the 26 seated-frame joints and radii.
- The manifest declares horizonos.permission.HAND_TRACKING (and the deprecated
com.oculus.permission.HAND_TRACKING), both normal permissions with no prompt on a Quest 3, and
oculus.software.handtracking as optional; without it Horizon OS keeps the app controllers-only.
Bare hands then drive khr/simple_controller, so on Android a hand whose squeeze action is
inactive and select active is treated as bare: with the option off it presses nothing but the
menu gesture, is not drawn and feeds no Wii Remote motion.
- Interaction-profile changes and tracker sources are logged. The pure rules (grasp from finger
flexion, bare latch, pinch gate, bare-hand buttons, flick) live in the OpenXR-free
vr/openxr_hand_tracking.h with mkw_vr_hand_tracking_tests; the driving and flick parts are
wired in the next commits. Docs: OPENXR.md, docs/quest-port.md, README.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- The pacing thread aims each immersive window eye through the window itself (AimEyesThroughWindow):
it keeps its position but looks square-on at the window's plane through an off-axis frustum just
around it, so the eye image is the window, at the display's pixel density (about 680x380 per eye
at render_scale 0.8 instead of 1344x1408), with a two-pixel border the mask leaves transparent.
The frame's views carry that pose and field of view to the projection layer.
- vulkan_interop.cpp copies an eye smaller than its AHardwareBuffer into the buffer's corner, and
the Quest layer's imageRect is the rendered part of the swapchain image.
- These eyes are not foveated: their field of view follows the head, which would rebuild the
density map every frame.
- Quest only (kWindowShapedEyesSupported); the PC backends copy whole eyes and keep masking them.
debug.wiicompiled.window_eyes 0 renders them whole and masked again for A/B timing.
- Quest 3, paused Retro Rewind race, render_scale 1.0: Immersive window runs the GPU at level 1
(456 MHz, app GPU 11.0 ms, eyes 8.4 ms) where Immersive needs level 3 (599-640 MHz, 13.4 ms,
10.3 ms), about 42% fewer GPU cycles. No edge artifacts, image as sharp. Docs: OPENXR.md.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- Introduced a new configuration option for immersive window mode in runtime_config.h.
- Updated the parsing and setting functions to handle the immersive window state.
- Modified the OpenXR backend to support rendering with the immersive window, blending the race view with the surrounding environment.
- Enhanced the settings overlay to allow users to select between immersive, immersive window, and flat screen race views.
- Implemented GPU rendering logic for the immersive window mask, ensuring correct visual output in various rendering paths.
- Added tests to validate the immersive window functionality and its interaction with existing race view settings.
- Eyes render under a VK_EXT_fragment_density_map: full rate around each eye's forward direction,
2x2 then 4x4 pixel blocks towards the edges ([vr] foveation = off|low|medium|high, default off).
XR_FB_foveation cannot help here: the runtime's maps only shape passes drawing into its
swapchain, and the eyes reach it through a copy.
- aurora-main/patches/dawn/aurora_fdm.inc: Dawn enables the extension only on request and for
dynamic rendering, flags every render pipeline, and chains an immutable RG8 map into any pass
whose first color attachment is a view bound to one (ABI: include/aurora/dawn_fdm_abi.h).
- android/Build-QuestDawn.ps1 builds the pinned Dawn revision with those patches for arm64
(dawn-build CI flags, protobuf off) into a cached package; Build-Quest.ps1 links it
(-StockDawn opts out) and AuroraDawnProvider.cmake enables the ABI from its manifest.
- lib/gfx/foveation.hpp generates the maps (32 px per texel, densities 255/127/63); an eye is
foveated only when single_pass_eyes draws it in one render pass. Menus never are.
- Live level from the headset panel's VR tab and the launcher; the launch decides whether the
device has maps. debug.wiicompiled.foveation and debug.wiicompiled.fdm for A/B.
- Tests: Foveation cases in gx_fifo_tests, mkw_vr_config_tests. Docs: OPENXR.md, quest-port.md.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- Added eye_pass_plan.hpp: an eye keeps drawing in the render pass it has open across the frame's
GX copies (only the mono render performs them), skips passes a later clear of the whole color and
depth erases, and splits only for a clear of color alone or depth alone.
- render_stereo_eye follows that plan; the cockpit fallback is drawn inside the last pass instead of
a render pass of its own that loaded the eye back.
- The stencil is now cleared where the eye actually starts, so a cockpit mask drawn in an erased pass
can no longer punch holes in the HUD.
- [vr] single_pass_eyes (default on, live from F10) and debug.wiicompiled.eye_passes 0/1 on the Quest
switch back to one render pass per recorded pass for A/B timing.
- Each new plan shape is logged once ("Eye replay plan: ...").
- Tests: EyePassPlan cases in gx_fifo_tests; stereo_multiplayer_smoke and stereo_frame_worker_smoke
pass on D3D12.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
OpenXR's grip +X is normal to the palm but points away from it on the left hand
and into it on the right, which is exactly what makes both grips carry the same
orientation when the hands hold a wheel symmetrically. The fingers therefore run
along -Y on both hands, and it is the geometry across the palm that mirrors.
Building the right hand's fingers on +Y instead left them pointing at the player
while the real hand faced forward.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The animated copy is registered for the vehicle's whole position array, and a
race frame has 228 primitives that bind it. Suppressing draw merging for all of
them turned the kart's display list back into hundreds of separate draw calls,
each recorded once and replayed in the mono pass and both eyes: on a Quest 3 the
app frame went from 15.7 ms to 21.7 ms with the GPU pinned at 97 %, which past
the 72 Hz deadline reads as 56 FPS against 42.
Merging is unsafe only between draws that resolved the array differently, since
the merged whole renders through the binding of the draw it folds into. So
resolve the replacement once per draw, before the merge test, and merge into a
draw that reached the same decision. Draws of an opponent sharing the asset
merge with each other again too, and with no set registered the test is exactly
what it was before the feature.
Measured at the same place on a Quest 3: 60 FPS against 42, the app frame back
to 13.9 ms, 564 draw calls a frame with 11808 primitives merged away.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The Android frame-rate line already says where the wall clock went; what it
could not say is how many draw commands the recorded frame holds, which is what
the mono pass and both eye replays each pay for. An overlay that stops draws
merging shows up here long before it shows up as a frame rate.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The Quest offers XR_FB_hand_tracking_mesh without the app declaring hand
tracking, so a Quest 3 draws the runtime's own hand mesh rather than the
procedural gloves, and its fingers bent backwards on grip: the mesh's
joints point -Z towards the fingertip and +Y out of the back of the hand,
so flexion is negative about the joint's own X, on both hands. Fix and
test by the repository owner; OPENXR.md said the Quest always drew the
gloves, which was wrong.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
On a Quest 3 the gloves' fingers pointed up out of the fist and bent out
of the back of the hand as the grip closed: they were built along the
grip's -Z, which runs up the tube the curled fingers form towards the
thumb, and curled towards -Y, which is backwards on the right hand.
The fingers now run along Y, forwards out of the palm (+Y on the right
hand, -Y on the left, the frame being right-handed), and close towards
+X, the palm's outward normal, with the thumb on the -Z side of both
hands. A test pins the reach, the closing direction and that nothing
bends out of the back of the hand.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The panel was drawn into the eye images, so below a 1.00x OpenXR
resolution its 1440x1080 canvas was minified into a small eye region and
the text became hard to read. It is now submitted as a quad layer of its
own over the scene's projection or menu quad, which the compositor samples
directly at any render scale.
Each backend (D3D12, Windows Vulkan, Quest) makes the panel's swapchain
pair (plus two shared buffers on the Quest) the first time the panel
opens. While it is open, the frame hands Aurora one more stereo target
after the eyes, which the bridge fills with the panel texture or a
transparent image. The panel image follows the eyes' displayed/retained
pairing, so a cancelled frame never shows an unwritten panel. The quad
hangs where the pointer's hits are tested. Aurora leaves the panel out of
the eyes in layer mode, and a backend that cannot make the layer falls
back to drawing it into the eyes.
On the Quest, debug.wiicompiled.panel_layer 0 selects the old path at
run time. Measured there at a race start (render_scale 0.8), the layer
costs nothing while the panel is closed; while it is open, app GPU time is
10.5 ms against 9.7 ms drawn into the eyes, with unchanged frame rates.
The replay tests cover lazy creation, cancelled frames, closed and
unplaced panels, and render-first pacing on D3D12 and Windows Vulkan.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Found on a Quest 3 through Virtual Desktop (VirtualDesktopXR, Vulkan):
the race camera's view matched the scene camera exactly and the kart's
own wheel animated (228 draws a frame took the copy), but during the
race's opening pan no draw took it, the 30-frame fallback latched the VR
wheel for that vehicle, and it stayed until the scene changed.
The fallback now heals: copies keep being published, the VR wheel stands
in only while draws are not taking them, and the vehicle's own wheel
returns as soon as they are. Both switches are logged, a few per race.
With first_person_rotation "yaw_pitch" or "full" the wheel copy and the
hands' wheel geometry now ride in the view's own frame (the kart's
orientation around the seat) instead of the level seat, so the wheel no
longer tilts against the view on slopes.
Aurora logs, after about half a second, five seconds and a minute of
copies, how many draws bound a copied array (inside and outside its
window), how many matched, and the closest position matrix against the
expected one, so a mismatch says whether the array or the matrix differs.
first_person_seat = "cockpit" (the new default) places the head at the
driver's own eyes, measured once from the character's head bone while the
kart drives straight and undamaged, kept behind the steering wheel, at a
life-size scale (cockpit_units_per_meter, grown with the character's
height and the player's size). "yaw" takes the kart's driving direction
from a level seat frame that follows the simulation's position and
direction, so damage spins and tricks do not turn the seat.
first_person_seat = "custom" keeps the previous offset placement.
At the race draw boundary the vehicle's steering wheel disc (karts) or
handle part (bikes, re-seated level while the bike banks) is decoded from
its MDL0, turned on a copy, and posted to the GX thread for Aurora to
substitute into the player's own draws; the copies are dropped after the
frame's draws. The anchor carries the wheel or handlebar geometry in the
seated frame and the frame's exact scale, which reaches the policy and
aurora_set_stereo_scene_anchor_scaled. If no draw takes the copies for 30
frames the vehicle falls back to the VR wheel, and the log says so.
The wheel copy is matched against RaceCamera::GetViewMtx with no dolly
offset, since the scene camera is only set once the draws run; the log
reports its distance from the scene camera once a second.
Nothing turns the wheel yet: the angle comes from the XR side's driving
snapshot, which the next change publishes.
Guest offsets ported from heurazy's mario-kart-wii-VR-port, each
re-checked against the generated leaf getters and the decompilation.
A stereo packet can now carry an AuroraCockpit: tracked hands and, when the
vehicle's own wheel cannot be animated, a synthetic steering wheel or
handlebar, all in metres in the seated frame. Each eye draws it inside the
scene's pass just before the first virtual-screen draw, depth-tested with
the world's own depth mapping (captured from a full-view world draw), so
the kart and track occlude the hands and the 2D layer cannot hide them.
Hands use a runtime-provided hand mesh when one is supplied and a
procedural glove otherwise.
aurora_set_stereo_scene_anchor_scaled lets the sealed frame own its world
scale: each eye's head translation is rescaled from the packet's scale to
the frame's. A non-finite cockpit is dropped with one warning; the frame
still renders.
Ported from heurazy's mario-kart-wii-VR-port (GPL-3.0-or-later).