Skip to content

fix(oxr): 2D/mono locate must build the Kooima FOV from the CENTRE eye - #1148

Open
dfattal wants to merge 1 commit into
mainfrom
fix/mono-fov-centroid
Open

fix(oxr): 2D/mono locate must build the Kooima FOV from the CENTRE eye#1148
dfattal wants to merge 1 commit into
mainfrom
fix/mono-fov-centroid

Conversation

@dfattal

@dfattal dfattal commented Aug 22, 2026

Copy link
Copy Markdown
Collaborator

Problem

In a single-view (2D) rendering mode, xrLocateViews sets XrView.pose to the centroid of the DP's eyes (oxr_session.c:2724 for the override path, :2746 for the tracked path) but the FOV is taken verbatim from fovs[0] (:2797:2803) — the Kooima frustum built from adj_eyes[0], i.e. the left eye's off-axis frustum. A centred pose paired with an off-axis frustum shifts the rendered image laterally.

The service path has collapsed the mono render input to a centred eye since #521/#575 (ipc_server_handler.c:690-711 and the active_view_count == 1 branch at :1507). The in-process path only did it for legacy apps (legacy_app_tile_scaling-gated block at oxr_session.c:2001-2022), so extension apps running in-process never got it.

That is why this surfaced on Android now: displayxr-demo-modelviewer moved in-process in its PR #97, and the same app that was correct over IPC started shifting left in-process — with the runtime unchanged. Windows never showed it because the Leia SR DP already reports a single centred eye in 2D, so the centroid is a no-op there.

Evidence (NP02J, in-process, iface=leia-cnsdk, runtime v1.21.1-785-g9c83df864)

Numbers from a kill-switched probe in the demo (setprop debug.dxr.mv.viewprobe 1); hcen = (angleLeft+angleRight)/2 in degrees, i.e. the frustum's horizontal skew (0 = centred). rawEyes = 2 and tracking = 0 (nominal ±32.5 mm at z = 0.5 m) in both modes.

3D (mode 1 "LeiaSR", views=2, tiles 2x1)
  view[0] pose=(-0.2365,0.0000,3.6392) fov L=-9.648  R=16.699 hcen=+3.5256
  view[1] pose=(+0.2365,0.0000,3.6392) fov L=-16.699 R=9.648  hcen=-3.5256

2D (mode 0 "2D", views=1, tiles 1x1)  — BEFORE
  view[0] pose=( 0.0000,0.0000,3.6392) fov L=-9.648  R=16.699 hcen=+3.5256
                                            ^ identical to the 3D LEFT eye

Pose centroided ✔, FOV not ✘. Predicted shift: tan(3.5256°)/tan(13.1735°) = 0.277 of the half width → 13.8 % of the canvas to the left (~354 px of 2560). The on-device capture matches (model centre ≈ 0.53 of width in 3D, ≈ 0.33 in 2D).

Fix

Generalise the legacy-only centre override to every app whose active mode has view_count == 1, collapsing adj_eyes[] to their centroid before the Kooima compute, so pose and FOV are derived from the same eye.

  • DP-agnostic — a DP that already reports one centred eye in 2D makes the centroid that same eye (no-op; the Windows path).
  • No change when active_view_count > 1 (per-eye off-axis frusta are the point of a 3D mode).
  • The XR_DXR_view_rig raw channel is captured before this point, so rawEyes[] still reports the DP's verbatim per-eye set (app face-dot HUDs unaffected).
  • Subsumes the old legacy_app_tile_scaling block — legacy 2D apps reach the same centroid through the new gate, so their behaviour is unchanged.

Testing

  • macOS host build (cmake -G Ninja -DBUILD_TESTING=ON + ninja) green; ctest 23/23 pass.
  • Android runtime APK build-only green (./scripts/build-android.sh build debug) — not installed on any device.

NOT tested: no on-device verification of the fixed runtime. The pad's runtime APK was deliberately left untouched (v1.21.1-785-g9c83df864); the before-numbers above come from that stock runtime. Device verification of the after state needs a coordinated runtime swap on NP02J. Windows/macOS/Linux regression of 2D mode (expected no-op) also unverified on hardware.

Fixes DisplayXR/displayxr-demo-modelviewer#100 (runtime side)

In a single-view (2D) rendering mode xrLocateViews already sets XrView.pose
to the centroid of the DP's eyes, but the FOV is taken verbatim from
fovs[0] — the Kooima frustum built from adj_eyes[0], i.e. the LEFT eye's
off-axis frustum. A centred pose paired with an off-axis frustum shifts the
rendered image laterally: on a 235 mm panel at a 65 mm nominal IPD the
frustum skew is 3.53 deg against a 13.17 deg half-FOV, i.e. tan(3.53)/
tan(13.17) = 27 % of the half width -> ~14 % of the canvas, to the left.

The service path has collapsed the mono render input to the centre eye
since #521/#575 (ipc_server_handler.c), so this only ever bit in-process
sessions. It surfaced when displayxr-demo-modelviewer moved in-process on
Android (its PR #97): the same app was correct over IPC and shifted left
in-process, with the runtime unchanged.

Generalise the existing legacy_app_tile_scaling-only centre override to
every app whose ACTIVE mode has view_count == 1. DP-agnostic: a DP that
already reports one centred eye in 2D (the Windows Leia SR path) makes the
centroid that same eye, so this is a no-op there — which is why Windows
never showed the bug. No change for view_count > 1, and the
XR_DXR_view_rig raw channel is captured before this point, so the app's
face-dot HUD still sees the DP's verbatim per-eye set.

Measured on NP02J (nubia pad, in-process, iface=leia-cnsdk, runtime
v1.21.1-785-g9c83df864, modelviewer probe debug.dxr.mv.viewprobe=1):

  3D (LeiaSR, 2 views)
    view[0] pose=(-0.2365,0,3.6392) fov L=-9.648 R=16.699 hcen=+3.526
    view[1] pose=(+0.2365,0,3.6392) fov L=-16.699 R=9.648 hcen=-3.526
  2D (mono, 1 view) — BEFORE
    view[0] pose=( 0.0000,0,3.6392) fov L=-9.648 R=16.699 hcen=+3.526
                                        ^ the LEFT eye's frustum on a centred pose

Fixes DisplayXR/displayxr-demo-modelviewer#100 (runtime side)
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.

Android: switching to 2D mode shifts the asset left

1 participant