Skip to content

v5.0.0 - Move to Direct Presentation with Native 3D Output Modes#26

Merged
oneup03 merged 129 commits into
mainfrom
direct_experiment
Jul 18, 2026
Merged

v5.0.0 - Move to Direct Presentation with Native 3D Output Modes#26
oneup03 merged 129 commits into
mainfrom
direct_experiment

Conversation

@oneup03

@oneup03 oneup03 commented May 11, 2026

Copy link
Copy Markdown
Owner

VRto3D — Direct Mode Release

The big one: VRto3D now renders and presents stereo 3D natively/in direct mode, instead of relying on ReShade + external tools. This unlocks a full menu of output formats, an in-3D settings UI, auto-depth, proper screenshots, and native Linux support.

🎬 Native 3D output modes

All modes are produced directly by the driver (no ReShade required):

  • Side-by-Side and Top-and-Bottom
  • Row Interlaced / Column Interlaced
  • Checkerboard
  • All Anaglyph flavors (Red/Cyan, Green/Magenta, Blue/Amber, + Dubois/deghosted/half-color variants)
  • Dual Display / Dual Display Flip — for dual-projector or dual-monitor setups
  • Frame-Packed modes — switches the display into a frame-packed HDMI mode
  • LeiaSR — autostereoscopic weaving with tracking built in (Midlife Crisis / Eff's tracking incorporated — no separate app needed)
  • NvidiaDX9 — 3D Vision output, rebuilt on top of the new NV3D-Lib
  • WibbleWobble — frame-sequential output (installer fetches the required WibbleWobble beta automatically)
  • VirtualDesktop mode (replaces the old vd_fsbs_hack)
  • Mono mode

🐧 Linux support (new)

  • Full Linux port with Wayland and X11 presenters and a Vulkan rendering path
  • evdev-based input and OSD on Linux
  • Frame-packed modeline wiring on X11; WibbleWobble streaming to wwserver
  • Linux install script + CI build workflow
  • Not extensively tested

🖥️ On-Screen Display (OSD)

  • ImGUI-based in-headset menu to edit every VRto3D setting — no more hand-editing config files (Ctrl+Home or Start + D-Pad Down)
  • Gamepad navigation for the OSD
  • Download the latest game profiles directly from the OSD
  • Display-correction shader: a final-pass color correction applied to the composited image — per-channel Lift / Gamma / Gain plus an adjustable contrast curve — to tame ghosting/crosstalk and match brightness on displays that need it
  • Buttons to take a screenshot, open the profile/screenshot folders, and reset to factory defaults
  • Stereo cursor: optional per-eye software cursor drawn at adjustable depth and size, plus hide-cursor and lock/confine-cursor options — all saved per profile
  • Auto Exit SteamVR: optionally shut SteamVR down automatically when the game closes

🔎 Auto-Depth

  • Auto-Depth feature that works in any game (behaves like auto-convergence, depth-buffer driven)
  • Comfort-target and smoothing controls, plus optional sample logging for tuning

🎯 Head tracking

  • LeiaSR built-in head tracking — sensitivity, deadzone, per-axis limits, One-Euro filter smoothing, and the track mode (Yaw/Pitch up to Full 6DOF) are all tunable, so no separate tracking app is required
  • OpenTrack tuning — position deadzone, zoom smoothing, and max-zoom controls for XInput/OpenTrack head tracking; you can also disable the built-in SR tracking to feed the OpenTrack port from an external source (OpenTrack, VertoXR, …)

📸 Screenshots

  • Captures a normal and a cross-view image each time
  • Trigger from the OSD (Misc → Take Screenshot) or the Ctrl+F12 hotkey
  • Fixed black/blank screenshots

⌨️ Hotkeys & profiles

  • Reworked user hotkeys; "Toggle" mode now supports multiple presets
  • Start + D-Pad Up now mirrors the Ctrl + F8 focus hotkey
  • Hotkey changes apply live without a restart
  • All shipped game profiles migrated to portable key names (cross-platform)

📦 Installer

  • New Windows installer (Inno Setup): picks release / pre-release / local build, detects SteamVR, optional cleanup of legacy ReShade and third-party drivers, optional WibbleWobble + LeiaSR setup

🛠️ Fixes & polish

  • Overhauled focus / always-on-top handling (unified ComputeWantOnTop focus policy; fixes overlay stuck-on-top after game exit; consistent auto-focus)
  • Numerous DX9 stability, color, and freeze fixes
  • Fixed screen tearing, multi-layer sizing, UEVR rendering/stutter, RealVR reconnect, recenter reliability, DPI-scaling bug
  • sRGB present chain fixes for correct (non-dark) output

oneup03 and others added 27 commits July 17, 2026 18:56
…op peek, hide dead OSD controls

- Presenters: overlay is click-through by default (X11 empty XShape input
  region + input=False; Wayland empty wl_surface input region + kb NONE) so
  mouse+keyboard reach the flat game. SetInputCapture(true) on OSD-open takes
  pointer+keyboard so GUI clicks/typing don't leak to the game (OSD still
  reads evdev). SetAlwaysOnTop(false) lowers the overlay (X11 XLowerWindow +
  drop _NET_WM_STATE_ABOVE; Wayland layer BACKGROUND) to peek at the game.
- VkRenderer edge-tracks menu-visible + man_on_top on the present thread and
  drives the two presenter calls; on-top defaults true on Linux so the OSD
  'Always on Top' checkbox is meaningful (uncheck = peek).
- Gamepad GUI nav confirmed already wired (buttons/dpad/sticks/triggers ->
  ImGui nav, NavEnableGamepad + HasGamepad).
- OSD: hide Linux-inert controls (Auto Focus, Hide/Lock Cursor, LeiaSR head-
  tracking section) and filter LeiaSR/NvidiaDX9 out of the output-mode
  dropdown while preserving enum mapping. Link libXext for XShape.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…onnect)

Corrects the earlier always-on-top-by-default. VkRenderer's focus block now
replicates WindowPresenter::FocusThreadLoop: overlay starts lowered; when an
app connects to SteamVR it auto-raises (once per app PID, latching
is_on_top+man_on_top so Ctrl+F8 can still drop it); man_on_top overrides; OSD
open forces on-top+capture. Linux Activate no longer defaults on-top true.
Dropped the unconditional BringToTop at present-thread start.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Byte-preserving UNORM-everywhere was still dark: the SteamVR Linux compositor
hands direct-mode eye images as LINEAR data, so presenting them raw through a
UNORM swapchain makes KWin interpret linear values as sRGB and darken them.
Prefer an _SRGB swapchain so the final present store sRGB-encodes exactly once
(imports/out_sbs stay UNORM/raw). Needs on-hardware visual confirmation.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Converted all 108 vrto3d_profiles/*_config.json from legacy VK_*/XINPUT_*
to the portable Key_*/Pad_* vocabulary via tools/migrate_profiles.cpp (reuses
the loader's vrto3d::keys::MigrateName, handles + combos), matching the
migrated default_config. No behavioral change — the loader already accepted
both spellings; this makes the shipped canonical format consistent and avoids
a silent rewrite on each user's first save.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
#1: Wayland/X11 presenters had identical AcquireNext/Present/RenderPass/
Extent/Format delegations + ctx_/swapchain_ members. Hoisted into
SwapchainPresenterBase; each presenter now implements only its native
windowing surface. WibbleWobblePresenter stays a direct IVkPresenter (no
swapchain).

#3: The want-on-top state machine existed in both WindowPresenter::
FocusThreadLoop (Windows) and VkRenderer's Linux focus block and had to be
hand-synced. Extracted to focus_policy.h ComputeWantOnTop() (pure, per-caller
FocusLatchState); both call it. Behavior identical.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Refactor #2: hoist the repeated 'create image -> get mem reqs -> find
memory type -> allocate -> bind -> make view' sequence into vk_context:
Image2D + AllocateBindImageView (with alloc_pnext / extra_type_bits for
dmabuf import/export chains) + CreateImage2D for the plain device-local
case. vk_renderer's out_sbs target and the wibblewobble render target
now use CreateImage2D; the dmabuf import/export paths keep their bespoke
memory-type logic and call AllocateBindImageView directly.

Also bumps the VRto3DLib submodule to pick up the ApplyUserSettingsHotkeys
dedup (refactor #4).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Steam Deck usability (no keyboard):
- Add an always-polled Start+DPad-Up gamepad chord mirroring Ctrl+F8
  (toggle always-on-top). This is the direct fix for a lowered/buried
  overlay 'disappearing' with no keyboard way back — it counterparts the
  existing Start+DPad-Down menu-open chord and is polled regardless of
  disable_hotkeys so recovery always works.
- Harden the X11 re-raise: SetAlwaysOnTop(true) now also sends a
  _NET_ACTIVE_WINDOW request with source=pager, which KWin honors even
  from a non-focusable background window (a plain XRaiseWindow can be
  swallowed by focus-stealing prevention, leaving the overlay stuck
  behind the Plasma desktop). input=False keeps it from grabbing keys.
- Add a 'Reset Defaults' button in System > Misc that restores VRto3D's
  built-in factory stereo/shader/tracking tunables while preserving the
  display/output hardware fields. Distinct from 'Reload Default Cfg',
  which re-reads the user's possibly-customized default_config.json.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The runtime keybind loader already migrates legacy VK_*/XINPUT_* names
to the portable vocabulary transparently and canonicalizes them in place
on the next profile save (VRto3DLib key_names MigrateName + ParseBindName).
That makes this dev-only bulk converter — used once to migrate the 108
shipped profiles (commit 0d1395b) — redundant. Dropping it keeps the
migration story to a single self-healing path.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…-top)

Investigating 'overlay stays on top after the game exits' when SteamVR
survives the game (returns to Home/void). The disconnect->lower path is
correct and fires (Unload logged), but nothing logs the actual focus
raise/lower, and only accepted app connects are logged — so we can't see
what becomes the scene app after the game exits and whether we spuriously
auto-raise for it (on Linux the game filter is just 'non-empty name').

- vk_renderer focus block: log every RAISE/LOWER with the FocusInputs
  snapshot (is_on_top/man/auto/pid/running/menu).
- device_provider: include event type + pid on accepted connects, and log
  connect/scene-app-change events that are ignored (skip-list or filter)
  with the resolved process name — this reveals the fallback scene app.

Diagnostic only; no behavior change.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…e gate

Root cause of 'went on top for the game but never lowered after exit':
the present-thread focus/z-order block sat *below* the 'if (!have_frame)
continue;' gate. On app disconnect the driver both queues the LOWER
(LoadSettings clears is_on_top_/man_on_top_) and pauses the renderer
(OnAppDisconnect -> paused_for_disconnect_ -> OnDirectModeFrame drops all
frames). With no frames, have_frame stays false and the loop continues
before ever reaching the focus block, so SetAlwaysOnTop(false) is never
called and the window stays in its last state (raised).

Move the focus/z-order/input-capture block above the have_frame gate so
it runs every loop iteration (incl. the 50ms no-frame timeouts while
paused), applying the queued lower promptly. The block touches only the
display connection and focus atoms — no frame data — so it's safe to run
frameless. Also makes 'start lowered' apply before the first frame.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…inhook

Replace the in-tree D3D9Ex/NVAPI 3D Vision presenter and its MinHook-based
OSD suppressor with the external NV3D-Lib (submodule at external/NV3D-Lib,
nv3d-glass branch). The library owns the FSE popup + window thread, D3D9Ex
device, stereo bring-up, LightBoost, the in-process suppressor (MinHook is
compiled into NV3DLib.lib), the DX11->D3D9 import, and an async present
worker. NvStereoDx9Presenter is now a thin wrapper that keeps VRto3D's focus
policy (ComputeWantOnTop -> SetVisible, joinable game-focus watcher) and owns
a 3-deep MISC_SHARED staging ring.

- tracked_game_pid = own PID so popup visibility is purely SetVisible-driven
  and our focus policy stays authoritative.
- Host-side device-removed watch + 40-frame present-failure streak ->
  NotifyDeviceLost before Delete() (non-blocking teardown).
- MyDeviceProvider::Cleanup TerminateProcess when NV3D ran this session, to
  skip the NVIDIA DLL_PROCESS_DETACH freeze (NV3D-Glass pattern).
- Remove external/minhook submodule (only the deleted suppressor used it;
  keeping it would LNK2005 against NV3DLib.lib's embedded MinHook).
- vcxproj: PreBuildEvent builds the NV3DLib static lib (Config-MD), link
  NV3DLib-md/-mdd.lib. CMake: separate nv3dlib STATIC target (DX11 backend
  only) so its vendored nvapi/json/minhook headers can't shadow VRto3D's.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…268f594)

Three fixes for the NV3D-Lib wrapper found in first hardware testing:

- Startup visibility sync: the library creates its popup visible while our
  policy starts lowered; the transitions-only focus loop never reconciled
  them, so the popup was stuck up and the first Ctrl+F8 was a no-op. Force
  SetVisible on the first iteration.
- OSD clicks: the OSD's WH_MOUSE_LL hook is installed from the presenter's
  render thread, and LL-hook callbacks only fire while the installing thread
  pumps messages. The old presenter pumped via its window thread; the new
  render loop owned no window and never pumped, so clicks were dead. Drain
  the thread message queue every loop iteration.
- OSD usability: hand the library popup HWND to the OSD for cursor mapping
  (its FindWindow fallback looks for the old VRto3D_PresentWindow class),
  drive the popup solid/click-through from menu visibility via the new
  InterfaceDX11::SetInteractive, and force the popup visible while the menu
  is open (mirrors the Linux VkRenderer force_on_top) so opening the menu
  with the popup hidden can't softlock hotkeys. ApplyMenuVisibility now
  leaves the FSE window styles to the library in NvidiaDX9 mode.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Both presenters carried hand-rolled copies of the on-top policy that
focus_policy.h exists to centralize. Replace the inline blocks with
ComputeWantOnTop + FocusLatchState (verified semantically identical:
same precedence, same auto-raise-once-per-PID latch, same reset when the
app exits). All five presenter focus loops now share the single rule.

No behavior change; watchers, reassert counters and WibbleWobble's
per-tick style reconciliation are untouched.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@oneup03
oneup03 force-pushed the direct_experiment branch from 19f8c5b to 33ce6cb Compare July 17, 2026 23:58
@oneup03
oneup03 merged commit 7b1bddb into main Jul 18, 2026
3 checks passed
@oneup03
oneup03 deleted the direct_experiment branch July 18, 2026 00:04
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.

1 participant