Skip to content

hl2-vr: multiple fixes (fixes #266) (fixes #369) - #395

Closed
skryvel wants to merge 14 commits into
Supreeeme:mainfrom
skryvel:hl2-vr-mod-things
Closed

hl2-vr: multiple fixes (fixes #266) (fixes #369)#395
skryvel wants to merge 14 commits into
Supreeeme:mainfrom
skryvel:hl2-vr-mod-things

Conversation

@skryvel

@skryvel skryvel commented Aug 3, 2026

Copy link
Copy Markdown

Status: secomd iteration but I haven't retested and reviewed; dropped the tip commit

Played Half-Life 2: VR Mod (32-bit) end to end and fixed everything hit along the way. Each commit stands alone. Tested on Quest 3 via WiVRn, under both oculus/touch_controller and meta/touch_controller_plus (WiVRn picked different profiles across sessions). KDE/Wayland/Proton/D3D9. PR is a bit big, happy to split or whatever, just let me know.

Test session time: 15m + troubleshooting iterations (early chapter 2).

Grabbing did not work (#266)

HL2VR's bindings_touch.json binds grip with mode "button" and a nonstandard "grab" input slot instead of "click", which was silently dropped during deserialization.

Before: grabbing unbound, the in-game tutorial hints say as much.
After: the grab slot is translated like click (squeeze value + click thresholds on force-less controllers). Picking up and throwing objects works.

Binding hints showed "UNBOUND" for everything

HL2VR resolves its %+command% hint tokens through GetActionOrigins + GetOriginTrackedDeviceInfo, both stubs. Games read the hand from devicePath (compared against GetInputSourceHandle results) and the button from rchRenderModelComponentName.

Before: every hint reads "UNBOUND ..." (or renders uninitialized buffer contents).
After: GetActionOrigins, GetOriginLocalizedName and GetOriginTrackedDeviceInfo are implemented, backed by xrEnumerateBoundSourcesForAction / xrGetInputSourceLocalizedName, including the internal threshold/toggle/grab actions that custom bindings resolve to. Component names come from the SteamVR render model jsons matching each profile's render_model_name. Hints show the right buttons.

Laser pointer / menu cursor pointed ~40deg off

/pose/tip was a TODO returning the raw pose.

Before: the cursor is only usable by tilting the controller ~90deg (or at all, used left controller instead of right, difficult to make it respond).
After: each hand gets a tip space using the tip component transform from the controller's SteamVR render model json (Touch / Touch Plus). Profiles without a known tip transform keep the raw pose. Cursor moves with right controller just fine.

Crash with MSAA disabled (#369)

Games submit m_nSampleCount = 0 when MSAA is off; OpenXR requires at least 1, so swapchain creation failed and xrizer panicked.

Before: instant crash with antialiasing set to None.
After: sample count is clamped to 1, also before swapchain reuse comparisons so a 0 can't force per-frame recreation.

Smaller fixes

  • Concurrent xrizer processes (a launcher's VR probe alongside the game) truncated the same log file, destroying each other's output. Logs are now per-process (xrizer-<pid>.txt) and the crash dialog opens the right file.
  • The crash dialog's "Copy to clipboard" usually pasted empty: miniquad's clipboard only lives while the window is open (and is unimplemented on some backends). It now prefers wl-copy/xclip/xsel and falls back to miniquad.
  • The dynamic loader panic in a 32-bit build now says it needs a 32-bit OpenXR loader (or --features static-openxr) instead of the generic message.
  • fakexr implements xrEnumerateBoundSourcesForAction / xrGetInputSourceLocalizedName so the origin APIs are testable. New tests cover the grab slot, origin queries, tip pose, and zero sample count.
Screenshots

AA off is now fine.
image

Bound keys.
image

Grabbing with right hand.
image

Comment thread src/compositor.rs Outdated
let mut info =
backend.swapchain_info_for_texture(b_texture, bounds, texture.eColorSpace);
// 0 means MSAA off - OpenXR requires at least 1
info.sample_count = info.sample_count.max(1);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this is duplicated as swapchain_info_for_texture already does this check

Comment thread src/compositor.rs Outdated
.backend
.swapchain_info_for_texture(texture, bounds, color_space);
// 0 means MSAA off - OpenXR requires at least 1
new_info.sample_count = new_info.sample_count.max(1);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

same here as swapchain_info_for_texture will clamp to 1 already. can drop the mut as well

@Mr-Zero88 Mr-Zero88 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this needs a cleanup to not spam folder with logs

Comment thread src/input/profiles.rs Outdated
"x" => Some(Self::X),
"y" => Some(Self::Y),
"application_menu" => Some(Self::Menu),
// "menu"/"squeeze" and friends are the OpenXR spellings; accepting both

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

since this funciton is named from_openvr_str this is not realy acutrate. could you split this into a from_openvr_str and a from_openxr_str?

@Mr-Zero88

Mr-Zero88 commented Aug 3, 2026

Copy link
Copy Markdown
Contributor

i think it would be good to split this into multiple PRs.

  • clipboard
  • 32bit openxr loader
  • swapchain sample count
  • per process logging
  • pose offsets (this needs further discussion)
  • action origins and fakexr implementation combined
  • grab button binding support

@Mr-Zero88

Copy link
Copy Markdown
Contributor

also make shure to fix up all the commits with cargo fmt to have the correct format

@skryvel
skryvel force-pushed the hl2-vr-mod-things branch from 6c1539e to 2e03592 Compare August 3, 2026 21:48
Comment thread src/lib.rs Outdated
prune_old_logs(
&path,
std::time::SystemTime::now()
- std::time::Duration::from_secs(48 * 60 * 60),

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
- std::time::Duration::from_secs(48 * 60 * 60),
- std::time::Duration::from_hours(48),

@prikhi

prikhi commented Aug 4, 2026

Copy link
Copy Markdown
Contributor

Tip pose transformation should be fixed by #382

skryvel added 14 commits August 4, 2026 15:53
The locked dependency tree already requires 1.88 (home), and 1.91
allows Duration::from_hours.
…ceLocalizedName

Bound sources are derived from the suggested bindings for each hand's
current interaction profile. Localized names are built deterministically
from the source path so tests can assert on them.
Some games (e.g. Half-Life 2: VR) bind grip grab with mode "button" and
a "grab" input slot instead of "click". SteamVR treats it like click, so
translate it the same way, which on force-less controllers like Oculus
Touch resolves to squeeze value with the click thresholds.

Fixes Supreeeme#266
Implement GetActionOrigins (via xrEnumerateBoundSourcesForAction,
including the internal threshold/toggle/grab actions custom bindings
resolve to) and GetOriginLocalizedName (via
xrGetInputSourceLocalizedName with a path-derived fallback).

GetOriginTrackedDeviceInfo now resolves full source-path origins to the
owning hand, reports the render model component name for the origin
(what games like HL2VR use to label bindings; names taken from the
matching SteamVR render model jsons per profile), and fills devicePath
with the hand's device path handle rather than the origin itself, as
games compare it against GetInputSourceHandle results to tell hands
apart.

The input path parser now also accepts OpenXR-style names (squeeze,
menu, thumbrest, force) so runtime source paths can be parsed.
Multiple processes can run xrizer concurrently (e.g. a launcher's VR
probe alongside the game itself). They all truncated the same
xrizer.txt on init, clobbering each other's output - a probe could
punch a NUL hole over the game's entire early log. Give each process
its own xrizer-<pid>.txt instead.
Games submit a sample count of 0 when MSAA is disabled (e.g. Half-Life
2: VR with antialiasing set to None). OpenXR requires a sample count of
at least 1, so runtimes reject the swapchain and xrizer panics. Clamp
the value where the create info is built and before it's compared
against the current swapchain, so a 0 can't trigger per-frame
recreation either.

Fixes Supreeeme#369
A 32-bit xrizer build without the static-openxr feature dlopens the
system libopenxr_loader.so and dies with "wrong ELF class" on systems
that only have the 64-bit loader. Spell out the actual requirement in
the panic message.
miniquad's clipboard only serves pastes while the dialog window is
alive, and is unimplemented on some Linux backends, so pastes usually
came up empty. Prefer wl-copy/xclip/xsel, which keep the selection
alive, and tell the user when we had to fall back. Also include the
error message itself in the copied text.

The log file button now opens the per-process log file.
Some games read the HMD's TrackingSystemName/ModelNumber/ManufacturerName
strings to decide which motion controller scheme to enable. When these
come back empty, such games conclude no supported controllers are present
and ignore all controller input (menus still work via mouse) while
rendering their fallback wand models.

Report Oculus-style identity strings for the HMD, consistent with the
tracking system name already reported for the Touch controller profiles.
SUPERHOT VR binds its menu action to a long press of the X button. The
binding parser did not know the slot and silently dropped it, leaving
the menu unreachable on touch-style controllers. Approximate a long
press as a regular click rather than losing the binding.
Generic trackers identify as Vive Trackers, which some games sniff to
pick a controller scheme: SUPERHOT VR switches to its Vive scheme when
it sees one, where dropping held items requires a trackpad that
touch-style controllers do not have. Add a small quirks table keyed on
the game executable's name (Wine rewrites argv[0] to the .exe path, so
this also works under Proton) and suppress generic trackers for
SUPERHOT VR. XRIZER_MAX_TRACKERS overrides the default either way.
Steam launch options do not reliably deliver environment variables to
every game process (observed with SUPERHOT VR, where identical launch
options applied RUST_LOG on one run and not the next). When RUST_LOG is
absent, read the filter from <state dir>/xrizer/log_filter instead so
trace logging can be enabled regardless of how the game is launched.
Log analog/digital action state by name and the skeletal summary finger
curls (tagged with whether they came from hand tracking or the
estimated skeleton) at trace level. Entered-function traces alone can't
answer what data a game actually saw; these made it possible to rule
out binding and skeletal issues while debugging SUPERHOT VR.
/pose/tip was a TODO returning the raw pose, which made laser pointers
and menu cursors point along the controller body (~40 degrees off on
Touch controllers). Give each hand a dedicated tip space, offset from
the raw pose with the "tip" component transform from the controller's
SteamVR render model json. Profiles without a known tip transform keep
returning the raw pose.
@skryvel
skryvel force-pushed the hl2-vr-mod-things branch from 2e03592 to 07f37b1 Compare August 4, 2026 19:14
@skryvel skryvel closed this Aug 5, 2026
@skryvel
skryvel deleted the hl2-vr-mod-things branch August 5, 2026 23:39
@skryvel skryvel mentioned this pull request Aug 6, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants