submit_wait = 1 (default) keeps TransferSurface(surface, TRUE): the CPU
waits for the GPU to finish the frame before IVRCompositor::Submit.
submit_wait = 0 passes FALSE: the TRANSFER_SRC_OPTIMAL transition is still
a barrier in DXVK's command list, and submit_openvr's LockSubmissionQueue
(Flush + SynchronizeCsThread + lockSubmission, which drains DXVK's submit
queue into vkQueueSubmit) runs before Submit, so the compositor's copy is
queued behind the frame on the same VkQueue and the next frame's writes
behind the copy. This is DXVK's own D3D11 interop pattern for OpenVR.
OpenXR is unchanged (its mirror already exists as xr_copy_wait).
Startup log: "submit: GPU wait before Submit on/off (submit_wait=N)".
Menu A/B (singlepass, OpenVR, 2 runs each): 43-45 fps / submit 4.2-4.5 ms
with 1, 67-70 fps / submit 0.4-0.5 ms with 0; no errors, no crashes.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Static c0..c3 reads now apply the same def mask as the dynamic path. Also
fixes the mask itself: getStereoDynamicBankMask compared the def range's
srcIndex (index into the def data) against register numbers; it must use
dstIndex (the register). Before, a relative read landing on a def'd c0..c3
register was replaced by the per-eye bank in single-pass. Adds a harness
case (defconst) that fails on the old DLL (maxdiff 228) and passes now.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
mathtest_sync_config now copies the two M3-P2 Config fields, and the
s and lg copies run vr_init with stereo_shared_math = 0 (checked through
the startup log line), so the legacy path is exercised from init. The
reference defaults to 3104624 (pre-M4), since steam-frame now contains
M4. Startup line: "... zoom EMA per call: bit-exact)".
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- stereo_single_begin: one vr_substitute_c0_stereo call in one
PerfDrawMath bracket; both substituted flags from its result (on
failure neither output is written and the carry-over fills both).
- Two-pass: pass 0 makes both eyes' c0 with vr_substitute_c0_stereo and
pass 1 uploads the kept right eye (M4-M: nothing between the passes
changes the inputs).
- Both gated on vr_stereo_shared_math(): [vr] stereo_shared_math = 0
restores the legacy per-eye calls exactly.
- config: [vr] stereo_shared_math (default 1) and [vr] vr_capture_c0
(default 0, debug, writes c0_capture.bin), with ini comments and
migration defaults; vr_session.cpp read_shared_math_settings now reads
them from config().
Built with toolchain/build.sh (mingw, proxy): ok.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
vr_substitute_c0_stereo(in, outL, outR) returns exactly the 32 floats,
result and side effects of vr_substitute_c0_for_eye L then R. One body
(substitute_c0_eyes) now serves one or two eyes: state checks, projection
recovery, P^-1, paused_head, the sky origin, UI anchors and the shared
left factors are done once; binocular_zoom and sky_pinned_draw still run
per eye, left then right. Every shared product is the left-associative
prefix of the original, so the floats are unchanged.
vr_eye_matrix is cached per frame, keyed on the eye, every input the
matrix is built from and the generations of g_eyes and g_head_view.
paused_head (identity in gameplay) is skipped by an exact shortcut, and
the sky origin computes only the row it reads.
[vr] stereo_shared_math (default 1; 0 = legacy calls and no cache) and
[vr] vr_capture_c0 (debug: c0_capture.bin for replay) are read from
trlvr.ini in vr_init.
mathtest.sh session builds the session six times in separate namespaces
(the steam-frame original as reference) and compares them bit for bit
over 8500 synthetic draws of every kind plus a capture replay: identical.
Host instruction count per stereo draw: -47% against the original.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Stub windows.h/d3d9.h under src/test/host and a prelude that keeps
libstdc++'s __try out of the way, so the existing vr_math, gesture and
physics tests build and pass with g++ on the Frame (aarch64), with
-ffp-contract=off to keep float results free of fused multiply-adds.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
With stereo_same_frame = 1 and our DXVK present, EnableSinglePassStereo is
called at CreateDevice regardless of [vr] stereo_mode, so every vertex
shader gets the stereo variant and Shift+F6 can A/B from either start.
stereo_mode now only picks the starting path (default twopass until M5
sign-off); the enable line is followed by
"stereo: starting in two-pass (stereo_mode=twopass)" or
"stereo: starting in single-pass (stereo_mode=singlepass)".
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
dispatch_draw: a scene draw (stereo_passes() == 2) goes out once, armed
with both eyes' c0, when single-pass is active and the draw is eligible
(not the reflection pass, no per-eye textures, a vertex shader bound, no
stream instancing). Diorama hands-in-front squeezes the game viewport's
depth range around the one draw (DXVK gives both eye viewports the
current MinZ/MaxZ), so it needs no fallback. Every other draw is
disarmed first: two-pass fallback, mono, reflection pass, ImGui overlay;
EndScene, Present and Reset disarm as well.
Tracking for eligibility: own vertex-shader-bound flag (SetVertexShader)
and instance-data streams (SetStreamSourceFreq, as DXVK tracks them),
both reset by Reset. SetVertexShaderConstantF notes whether c0..c3 hold
the noted upload, for the carry-over. CreateDevice enables single-pass
right after vr_submit_init; the device destructor drops the interface.
With stereo_mode = twopass nothing armed ever exists, so the two-pass
path issues exactly the device calls it did before.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- stereo_device_created: QI IDirect3DVR9 for IDirect3DVRStereo9 and call
EnableSinglePassStereo(1) at CreateDevice when [vr] stereo_mode =
singlepass, with the three PLAN log lines.
- stereo_single_begin/_end: both eyes' c0 from vr_substitute_c0_for_eye,
left then right (sky-pinning call count and zoom EMA unchanged), with
the two-pass carry-over (a failed eye keeps what c0 held: left the
register before the draw, right the left's). Sticky SetStereoDraw:
skipped when both matrices and the eye size are bitwise unchanged.
After the draw c0 ends as two-pass leaves it.
- stereo_single_pass_eligible: eye textures, fixed-function, instancing,
counted by reason.
- stereo_disarm / stereo_end_scene: ClearStereoDraw when leaving stereo
draws; 5 s "stereo stats:" line with [vr] stereo_stats = 1.
- stereo_set_single_pass / stereo_single_pass_active implemented (live
toggle logs "stereo: live toggle -> single-pass|two-pass (Shift+F6)").
- stereo_begin_pass split into viewport and c0 helpers (same calls, same
order) so a failed arm can fall back to two-pass with the matrices
already made.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
True when reflection_stereo_eye_textures would swap a texture for the draw
about to be issued: a stage holds the reflection while its twin is drawn
per eye, or the WaterFX screen copy while it is split by eye. Same tests,
no side effects. Such draws need a texture per eye and stay two-pass under
single-pass stereo.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
[vr] stereo_mode = twopass|singlepass (default twopass until M5 sign-off)
and [vr] stereo_stats = 0|1, with ini comments and migration defaults.
The vr_runtime comment notes that the Steam Frame uses openvr.
Agreed with lane M3-K (hotkey/panel), declared in src/vr/stereo.h:
void stereo_set_single_pass(bool on);
bool stereo_single_pass_active();
Stubbed here; the single-pass path follows.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
stereotest.exe loads d3d9_dxvk.dll directly, renders a clipped, perspective
coloured scene into a 2W x H RT two-pass and single-pass (IDirect3DVRStereo9),
and compares readbacks. Cases: static c0, relative a0, call/ret, MSAA 2x,
scissor, mono after ClearStereoDraw, instanced rejection. shaders are emitted
by an in-file token builder (no d3dx). toolchain/stereotest.sh builds in the
legendframe-mingw image and runs headless under Proton 11 ARM64.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
- OpenXR by default (openxr_loader.dll, Khronos 1.1.63), falling back to
OpenVR / SteamVR; [vr] vr_runtime = auto|openxr|openvr. OpenXR grip poses
converted to the OpenVR grip so the hands sit the same under both.
- Settings panel in the headset (both sticks held, or the Touch menu
button), worked with a main-hand pointer; TR7-Menu-Hook's options built
in when it is installed.
- God mode pick-up, carry and throw; third-person chest and rear physics;
body push.
- Smooth view transitions; picture brightness and contrast; 4:3 movies;
version and runtime on the main menu.
- Palm sliders: third "side" slider, per-preset scale/zoom/side, notches
at the defaults, index-fingertip grab from Lara's drawn finger.
- Per-model caching of the body measurements (stutter with some player
model mods).
- Docs, release notes and notices for 1.1.0.
Switch view any time: with immersive controls on, long-press the right stick (or press \) in gameplay to swap between first and third person. Set third_person_mode = all to cycle classic → shoulder → board → first person instead.
Hand Calibration (Pause → VR Settings → Hand Calibration): Lara holds her hands out with guns drawn, wire boxes show your real controllers, and you squeeze each grip to lock your hands in. Fixes hands not lining up with your controllers (common on Quest).
PDA is now on the right stick double-click, and tutorial prompts show it.
One crosshair everywhere: the game's flat lock-on rings are replaced by the VR crosshair. First person aims straight from your controller; third person puts it on the game's auto-target.
One-hand catches: a clear yellow GRIP pill fixed in your view (higher for swing bars), and about 6 seconds to grab instead of 1.5 (catch_extra_time). auto_secure_catch now takes off, bars or all.
Quest and other headsets with an uneven vertical field of view: fixed the world warping with head movement.
Ledge pull-ups in first person always use the quick animation.
The shoulder camera now turns like classic (follows camera_auto_center).
The pause menu's Display page is now VR Settings; holster setup has more reliable menu entries and on-screen messages when it can't start.
Pausing no longer leaves your pistols floating beside your hands.
Fixed a black slab that could appear in front of you in third person.
Holster setup is cancelled with the menu button (left stick click).