test(android): discriminator proves the in-process 'eye swap' is an interlace-PHASE error, not a view-order bug - #1089
Open
dfattal wants to merge 1 commit into
Open
test(android): discriminator proves the in-process 'eye swap' is an interlace-PHASE error, not a view-order bug#1089dfattal wants to merge 1 commit into
dfattal wants to merge 1 commit into
Conversation
…nes_vk_android The "in-process Android weave shows SWAPPED left/right eyes" report cannot be settled by eyeballing the panel: a genuine view->tile swap and an interlace phase error of an odd pixel look IDENTICAL (both read pseudoscopic). These two knobs separate them, on-device, without a human. * `debug.dxr.tintviews 1` overwrites each atlas tile with a flat colour keyed to the SOURCE VIEW (0 = red, 1 = blue, 2+ = green) after the scene draw. A screencap of the WOVEN output then shows, per subpixel, which view the weaver selected -- so `adb exec-out screencap` becomes a quantitative phase probe (cross-correlate the R-B signal along a row between two runs: +1 = same phase, negative = polarity inverted = eyes swapped). One vkCmdClearAttachments per tile, off by default. * `EYEORDER` logs the sign of (views[1].x - views[0].x) from the runtime's render-ready rig -- the eye-order question answered independently of the weave and of where the face happens to be. Logged once, then only when the sign flips. Measured on NP02J with the face frozen (`debug.dxr.overlay 1` + camera privacy => NoFaceMode off but no tracking, so the weave is deterministic; self-correlation 1.0000): fullscreen, in-proc vs OOP corr@0 = +0.99..+1.00 (bit-identical) freeform @ WM(0,0), vs OOP corr@0 = +0.68 (same polarity) freeform @ WM(300,500) vs OOP corr@0 = -0.55..-0.64 (polarity INVERTED) and EYEORDER is bit-identical across both routes. So it is NOT an eye-order bug: it is the window-origin term in the interlace phase, which only the in-process leg applies (`android_globals` is process-local, so the app's `xrSetAndroidWindowGeometryDXR` publish reaches the DP in-process and never reaches the service). Refs #1031 #1074 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01N3kzh1GTPNrm62TbEhFmqP
Collaborator
Author
|
Human eyeball (2026-08-20): PASS. Fresh freeform launch, in-process: correct depth (crate toward viewer, grid recedes), clean weave, tracking live — confirming the phase-inversion was entirely the #1087 |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
TL;DR — it is not an eye swap
cube_zones_vk_androidin-process on the NP02J is not feeding the wrong view tothe wrong atlas tile. The view→tile order and the runtime's render-ready view poses
are bit-identical between the in-process and OOP routes. What differs is the
interlace phase, by an odd number of pixels — which inverts the per-pixel view
selection and therefore reads on the panel as a perfect left/right swap. The human's
own caveat ("small possibility of a 0.5 phase shift") was right.
The discriminator (this PR)
A genuine view→tile swap and an odd-pixel phase error look identical on glass, so
this PR adds two knobs to the zones test app that separate them, hardware-in-the-loop,
with no human:
debug.dxr.tintviews 1— after the scene draw, overwrite each atlas tile with aflat colour keyed to the source view (0 = red, 1 = blue, 2+ = green). A
adb exec-out screencapof the woven output then reveals, per subpixel, whichview the weaver picked. Cross-correlating the R−B signal along a row between two runs
gives a quantitative answer:
+1= same phase, negative = polarity inverted =eyes swapped. One
vkCmdClearAttachmentsper tile; off by default.EYEORDER— logs the sign ofviews[1].x − views[0].xfrom the runtime'srender-ready rig. Answers the eye-order question independently of the weave and of
where the face happens to be. Logged once, then only on a sign flip.
Making the weave deterministic is the trick that makes screencaps comparable at
all: the interlace phase tracks the tracked face, which jitters ~±0.05 m ≈ ±0.8 of a
lens period, randomising every capture.
debug.dxr.overlay 1(forces NoFaceMode off,3D on) plus
cmd sensor_privacy enable 0 cameragives a live weave with a frozendefault viewer — self-correlation
1.0000across repeat captures.Measured on NP02J (runtime
v1.21.1-753-g839a4ea90, display-config 0.10.62)corr@0EYEORDERis byte-identical on both routes in every case(
v0.x=+0.0141 v1.x=+0.2407 dx=+0.2266), confirming no eye-order bug anywhere.Root cause
The only route-dependent input to the interlace phase is the window screen rect:
vk_update_present_origin()(
src/xrt/compositor/vk_native/comp_vk_native_compositor.c:5857-5881) readsandroid_globals_get_window_screen_rect()and callsxrt_display_processor_vk_set_window_screen_rect(x, y, …). The publisher is theapp's own
xrSetAndroidWindowGeometryDXR(Architecture A spike: in-app Android compositor (real XR_DXR_android_surface_binding, async main-thread DP creation, host classloader, AAR <queries><intent>) #1037), fed fromView.getLocationOnScreen(). Plug-in then doesset_viewport_screen_position(win_x, win_y)→ log lineweave full-target screen-pos 300,500 target 1000x1500 (#150).android_globalsis process-local. The app's publish lands in the app'sprocess and never reaches the service, so
comp_multi'supdate_window_screen_rectleaves the origin at
(0,0); the plug-in logsweave band 332,374 500x750 screen-pos 332,374 (zonephase=1) target 2560x1600 (#53).Fullscreen hides the divergence because the origin is
(0,0)on both sides — which isexactly why the human's fullscreen eyeball looked good.
…and why the repro itself is invalid
The staged
am task resizerecipe (staged715.sh/ff.sh/android-sidebyside.sh)moves the task bounds but not the composited window on this device. Both
authorities agree, and neither changes when the WM bounds do:
displayFrame=[0 1560 1500 2560] sourceCrop=[0 0 1000 1500] bufferTransform=ROT_270frame=[0,1560][1500,2560]…identical at WM
(300,500)and at WM(900,100). The woven content lands atframebuffer
(332,374)— i.e.window(0,0) + zone(332,374)— in both cases, verifiedby the tint bbox (
x=332..830 y=374..1122).So under this recipe the app correctly reports the WM bounds, the WM bounds are not
where the window is, and the in-process leg anchors the lens phase to a place the window
isn't. OOP looked correct only because it applies no window origin at all and the
window really was at
(0,0).No weave fix is shipped here, deliberately: with a real drag (WM bounds and
SurfaceFlinger agreeing) the in-process leg is the one that is right by design and the
OOP leg is the one that would weave at a stale
(0,0). Picking a side needs ahand-dragged repro, not a
am task resizeone.Latent defect this surfaced
android_globalsbeing process-local means the OOP route never receives the app'swindow rect at all — any non-fullscreen OOP client weaves (and places its zone band)
at panel origin
(0,0). Fullscreen masks it. Worth its own issue against #1033.Eyeball recipe for the human
Confirm the reported
frame=actually tracks the drag, then judge depth polarity: thecrate must come toward you and the floor grid recede. If it is inverted, capture the
HW_DBG_CNSDK: weave … screen-posline at that moment — that is the phase origin inquestion.
Refs #1031 #1074
🤖 Generated with Claude Code
Session: https://claude.ai/code/session_01N3kzh1GTPNrm62TbEhFmqP