Fixes for quest - games can launch in immersive mode and we can use touch controllers as xbox (still some bugs) (#1831)
* feat: add Meta Quest immersive launch mode with Xbox-mapped Touch controllers * Gate immersive mode to the modernXr build and keep flat-mode behavior unchanged - libxrimmersive.so, the Khronos OpenXR loader dependency, ImmersiveXrActivity, and the VR uses-features now live in the modernXr source set only - immersive checkbox and launch branch behind BuildConfig.MODERN_XR - SteamService.onTaskRemoved keeps the original stop-on-swipe behavior outside modernXr - QuickMenu/ScreenEffectsPanel keep pre-immersive input behavior when not hosted by ImmersiveXrActivity (tab select-on-focus, A-toggles-lock, BACK closes the menu, focus only re-requested on open) - XServerScreen's nine immersive params bundled into one ImmersiveSessionHooks (register-limit protection) - hardcoded French onboarding/indicator strings moved to string resources * Default immersive launch mode on for the modernXr build Container.launchImmersiveMode now defaults to BuildConfig.MODERN_XR: immersive sessions get SUSTAINED_HIGH clocks, the XR render-thread hint, and 72Hz from the runtime, which a 2D panel app never gets. Existing containers without the key pick up the new default; an explicit uncheck persists as before. * Fix XServerScreen VerifyError: bundle QuickMenu args to get under the dex register limit Reproduced on device: verifier rejected XServerScreen (copy1 v2<-v263) at class load. Peak register pressure is the QuickMenu call's argument block, so the HUD/FPS-limiter and LSFG groups are now single holder parameters (PerformanceQuickMenuState, LsfgQuickMenuState), the resume overlay moved to its own composable, and the toggle registration was inlined. Measured via dexdump: 270 -> 254 registers. * Fix immersive first-boot issues found on device - Game rendered vertically flipped on the Vulkan direct path: pass contentScaleY=-1 so the shader's UV remap unflips it (no native rebuild) - Black screen during boot: render BootingSplash in ImmersiveXrActivity, carried onto the quad by the overlay capture - Game booted paused behind the Resume button: set isActivityInForeground in onCreate — Horizon's immersive handoff delays onResume past wine env setup, which read the flag as backgrounded and paused the game * Gate the immersive booting splash on mapped-window count The splash flag is re-armed by every boot-text event, and the final 'Launching game...' re-arm can land after the game's window already mapped, leaving the splash stuck over the running game (confirmed via device trace — it only cleared when a later window-map event happened to fire). Count map/unmap events in the activity and show the splash only while no wine window is on screen. * Force wall-clock present pacing for immersive sessions so the FPS limiter works Both halves of the DXVK limiter die once the OpenXR compositor owns the display: the SurfaceControl frame-rate hint never reaches a composited surface, and the Choreographer that schedules pixmap idle releases stops being a reliable clock. PresentExtension.setForceCpuPacer switches release scheduling to the existing wall-clock CPU pacer; XServerScreen arms it whenever the session is hosted by ImmersiveXrActivity. * Pace the PresentCompleteNotify for the immersive FPS limiter; never mask the resume overlay Device test falsified the idle-notify premise: with the wall-clock pacer active and a persisted 60 cap, the game still ran ~72 — DXVK does not block on idle releases on this path. What its frame-latency window does wait on is the CompleteNotify, so in immersive paced mode both notifications are withheld until the pace slot (display itself is not delayed). Flat path unchanged, including its complete-before-update ordering. Also hide the immersive booting splash while the manual-resume overlay is up — a pre-window-map pause (e.g. headset off during a redist install) left the opaque splash covering the only control that can unstick the boot. * Include POWER and INVITE tabs in the LB/RB cycle list, in rail order * Pace immersive presents by client read-suspension, not notification timing Live-debugged on device (jdb against the running session): frameRateLimit=60 was armed, presents flowed through presentPixmap, the pacer released both CompleteNotify and IdleNotify on a 60fps timeline — and the game still ran at ~72. The guest vortek WSI waits on neither notification, and the flat path's real cap (the SurfaceControl refresh hint) doesn't exist under the OpenXR compositor. Suspending the presenting client's socket reads for the pace delay (the same mechanism MITSHM/ShmFramePacer already use, sharing the same timeline) back-pressures through the X socket itself, which no WSI can ignore. Reverts the notification-delay machinery from the previous attempt; flat-mode behavior is untouched. * Strip the experimental immersive FPS-limiter pacing Device testing proved none of the X-side levers can cap this present stack: the guest WSI ignores idle and complete notifications, and read-suspension adds only latency because the client never blocks on the X socket. PresentExtension is restored to master byte-for-byte. The splash fixes, the flip fix, the boot-pause fix, and the register-limit work all stay. * Gate the remaining shared-UI interaction changes to the immersive session Audit before merge found five quick-menu/pause-overlay changes that were still reaching flat-mode users on every build. Each is now conditional on the session being hosted by ImmersiveXrActivity, so flat mode behaves exactly as master: - six .focusable() de-duplications (restored for flat) - three .focusGroup() row groupings - dismiss backdrop tap-gesture (flat keeps master's clickable) - LB/RB tab cycling - manual-resume button focusability and focus-on-appear modernXr keeps all of them. * Remove narrative comments Strips the session-history and rationale commentary (what an earlier attempt did, what testing confirmed, why something was misdiagnosed) across the immersive code and the shared files this branch touched. Keeps comments that document a real constraint: the dex register limit, thread/EGL-context requirements, buffer ownership, and the immersive-vs-flat gating conditions. Master's own comments are untouched. * added translations * Fix QuickMenu build break from the master merge Upstream's power-control rework dropped isPowerControlAvailable and now shows the POWER rail button unconditionally; the tab-cycle list still referenced the old flag. Match the rail. * Fix issues from immersive-mode review Native (source only — requires a libxrimmersive.so rebuild to reach a device): - guard every JNI entry point against use-after-free on teardown, holding the handle lock across the session call rather than just the lookup - hold an AHardwareBuffer reference across the whole EGL import - skip the per-frame EGLImage recreate when the buffer identity is unchanged - honour AndroidBitmapInfo.stride when copying submitted frames - latch pointerModeToggled until polled, matching quickMenuClicked - do not submit a quad layer for a swapchain image that was never acquired GL renderer: - capture the volatile bridge in a local so teardown cannot NPE endFrame - clear the render-target override when detaching the bridge - skip the direct bridge while effects are active; the composer needs its own scene target, and the bridge was blanking it Immersive activity: - fall back to PixelCopy when the GL shared buffer fails to allocate - only redraw and submit the overlay when there is overlay content - reset the virtual gamepad when entering menu or pointer mode - handle onNewIntent so a different game cannot be silently ignored - interrupt the polling thread instead of blocking teardown for 500ms - theme colours for the immersive overlays Quick menu: - give focus-report registrations row ownership so one row cannot clear another's action; add the missing registrations for toggle rows Library and misc: - serialize immersive-checkbox saves and drop the wrong-value flash - release pointer capture and hide the IME when Start opens the quick menu - preserve the scaling mode in resetScreenEffects - drop the unused context parameter from isMetaQuest - formal register for the French immersive strings * Rebuild libxrimmersive.so with the native review fixes Built with the pinned NDK 27.3.13750724 and CMake 3.22.1 by temporarily enabling the externalNativeBuild block, per the procedure in xrimmersive/CMakeLists.txt; the block is left commented out as before. Debug, unstripped, arm64-v8a — same profile as the previous binary. 1572960 -> 1771744 bytes. Verified the fixes are in the binary: submitFrame now takes the stride parameter. --------- Co-authored-by: moi952 <52632400+moi952@users.noreply.github.com> Co-authored-by: Utkarsh Dalal <utkarsh.dalal@toptal.com>
U
Utkarsh Dalal committed
58874f8ad1843a39f0e918b9efbb5b694c52d4f0
Parent: 44a8f19
Committed by GitHub <noreply@github.com>
on 8/18/2026, 5:56:33 PM