The stretch (vertices left at the pick-up spot) lines up with her instance
being drawn with a second model each frame: in both owner logs the hips
were re-found on every carried draw ("board: dangle legs" ~70-144/s) from
the first carry after first person / combat on, and not in the carries
before it. carry_apply moved only the passed model's bones + blended
joints; a draw whose vertices read more of her matrices than that range
skins the rest at her real (un-carried) place.
god_carry_fix = 1:
- carry (and pose on) her own model's full matrix range when another,
smaller model is drawn on her instance (carry_matrix_range);
- a stack of 4 saves instead of one static save: nested draws (her gear)
are carried with their own save, a nested draw of matrices already
carried is left alone (not carried twice, not snapshotted moved);
- kept soft-part / wall-touch writes from an earlier frame are put back
before the snapshot; the soft keep made on the carried pose is dropped
after carry_restore;
- the controller-hand copies (which re-skin her whole model) are drawn
before carry_restore, on the carried pose;
- hips found once per model (no per-draw re-find / log spam).
god_carry_fix = 0 is TRLVR 1.1.0's carry.
Diagnostics (both settings, once per carry): carry start/end summary,
"her instance drawn with model X ... its vertices use segments up to N",
"segments rewritten inside the game's draw" (A), "nested draw of ... inside
a carried draw" (B), "bone VB re-skin during the carry" (C, from the
D3D_GetVB hook). Pure helpers + mathtest cases in godmode/god_carry.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Buckets and frame phases accumulate raw TSC ticks (a pair of rdtsc per
timed span instead of QPC, 120-170 ns each under FEX); the report converts
to ms with a tick rate measured against QPC over the interval. Log line
format unchanged. perf_cpu_set_enabled(bool), default true, makes the
hooks one branch when off; the report logs nothing while off.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
- Sticky compare: inline bitwise 32-word compare instead of msvcrt memcmp
(an emulated cross-DLL call under FEX, ~25-35 ns vs ~7-10 ns measured);
same bitwise equality, so the same draws arm.
- [vr] stereo_stats cached once per frame at EndScene; with stats off the
begin path makes no config() call and no timer call at all.
- Stats timing: rdtsc (~40 ns under FEX) instead of QueryPerformanceCounter
(~150 ns, a wow64 syscall), one pair per draw after the maths instead of
four QPCs; ticks converted against QPC over each 5 s interval.
- The substitution still runs every draw (learner/EMA side effects).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
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>
i686 GCC defaults to x87 floating point; MSVC x86 defaults to SSE2 and x87 is the
slow path under FEX. Add -msse2 -mfpmath=sse -mstackrealign (the game calls in
with 4-byte stack alignment) to the proxy and launcher flags. The DXVK fork's
meson.build already passes -msse -msse2 -msse3 -mfpmath=sse. d3d9.dll x87
instructions 80809 -> 3627, SSE 0 -> 67940.
Co-Authored-By: Claude Sonnet 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>
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>