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>
A one-member struct is not a struct type in the IR, so the stereo flags
push data is declared and loaded as a plain u32 (spirv-val rejected the
extra access chain index). The lowering-failure warning and its reason
are emitted in one Logger call so they stay together across compiler
threads.
Validated with the M2-C harness (all cases maxdiff 0) and spirv-val on
the dumped _sps1 shaders.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
D3D9ShaderOptions::stereoMode selects the stereo variant for vertex
shaders: cache name gets the _sps1 suffix, the static CBV always uses the
uvec4 array layout with the 8-vec4 bank appended, every c0..c3 read
(static and relative) becomes armed ? bank[view*4+i] : c[i], and a
prologue writes ViewportIndex = armed ? (InstanceId & 1) : 0. Lowering
failure falls back to the mono variant and is logged once; the device
asks D3D9CommonShader::IsStereoCapable().
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
IDirect3DVRStereo9 is implemented on the existing D3D9VR object (one
refcount, QueryInterface returns either interface; IUnknown identity is the
IDirect3DVR9 pointer). Device side, per DESIGN 5.1:
- EnableSinglePassStereo: needs multiViewport + shaderOutputViewportIndex,
the shader-side D3D9ShaderOptions::stereoMode (detected at compile time,
so this branch builds and stays off without M2-A), d3d9.trlvrSinglePass,
and no vertex shader created yet. From then on every VS is created with
stereoMode = 1 (PS options and keys unchanged). Mode 0 = caps query.
- SetStereoDraw is sticky: a bitwise-identical bank and eye size cost no
upload; ClearStereoDraw disarms; Reset (ResetState) disarms.
- UpdateShaderConstants<VS>: once enabled every VS static CBV gets 128 B
more at 16 * (Fs + I + ceil(B/4)); the bank (L c0..c3, R c0..c3) is
written there while armed.
- BindViewportAndScissor: viewport building factored into BuildViewport;
armed draws bind 2 viewports {eye*w, 0, w, h} with the game's MinZ/MaxZ,
the 0.5 offset and the scissor clip per eye, as two SetViewport calls do.
- Draw, DrawIndexed, DrawUP, DrawIndexedUP: instanceCount 2 while active;
VS push flags at offset 20 (PushDataStereo dirty flag).
- An armed draw that cannot be single-pass (fixed-function VS, stream
instancing, or a VS whose lowering failed per IsStereoCapable) is counted
as rejected, logged once, and drawn by DXVK per eye (eye viewport and
that eye's c0..c3, then c0..c3 restored), i.e. the two-pass result
instead of a missing right eye.
- GetStereoStats: stereo draws and rejections since device creation.
Log lines: "D3D9VR: single-pass stereo enabled (viewport index, bank 8
vec4, key _sps1)", "D3D9VR: stereo draw rejected (instanced) -- reported
once". Mono draws while not armed: one viewport, flag 0, instance 1; the
only difference once enabled is the 128 B CBV tail. TransferSurface and
the submit paths are untouched.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
New D3D9Options::trlvrSinglePass, read from d3d9.trlvrSinglePass, and
stated explicitly in the trl_vr.exe profile. False makes
IDirect3DVRStereo9::EnableSinglePassStereo fail with D3DERR_NOTAVAILABLE
and log "D3D9VR: single-pass disabled by d3d9.trlvrSinglePass", so the
proxy stays on two-pass (PLAN rollback level 3, via DXVK_CONFIG/dxvk.conf).
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>
d3d9_vr.h: IDirect3DVRStereo9 (IID d77445fd-6e00-4860-9a84-4020d143004c),
reached by QueryInterface on the IDirect3DVR9 object: EnableSinglePassStereo
(Mode 1 = viewport index, Mode 0 = caps query), SetStereoDraw(c0L[16],
c0R[16], eyeW, eyeH) sticky, ClearStereoDraw, GetStereoStats; caps struct
with Version 1. Constants for the shader and harness lanes: VS push flags at
offset 20 (bit 0 = armed), bank of 8 vec4 (L c0..c3, R c0..c3) appended to
the VS static CBV at 16 * (Fs + I + ceil(B/4)), key suffix "_sps1",
D3D9ShaderOptions.stereoMode.
d3d9_state.h: D3D9StereoPushData at sizeof(D3D9VsPushData) +
sizeof(D3D9FfvsPushData) = 20, inside the 40 B VS push block.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
install.sh backs up and copies the payload with a manifest, seeds trlvr.ini,
the game registry and SteamVR per-app settings, and queues the Steam shortcut
through a self-deleting steam.service drop-in. uninstall.sh reverses it.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
bindings_frame_controller.json is derived from bindings_oculus_touch.json via
SteamVR's oculus_touch -> frame_controller remapping rules, validated against
the Frame input profile; registered in actions.json default_bindings. Left
menu is the Frame's /input/view button. Other headsets are unaffected.
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).