Skip to content

feat(d3d11): the window handle follows the majority segment (multi-screen M6, ADR-047 Amendment 2) — stacked on #1878 - #1880

Merged
dfattal merged 3 commits into
mainfrom
feat/win-segments-hwnd-majority
Oct 9, 2026
Merged

dfattal merged 3 commits into
mainfrom
feat/win-segments-hwnd-majority

Conversation

@dfattal

@dfattal dfattal commented Oct 9, 2026

Copy link
Copy Markdown
Collaborator

Stacked on #1878. Merge #1878 first, then this. The base is main so the PR lands there, which means the diff below also shows #1878's three commits until #1878 merges. The commits that belong to this PR are the top three.

What

This is the follow-up to David's 2026-10-07 call ("HWND stays with the primary DP for now, majority hand-off = follow-up"), and it ships ADR-047 Decision 2's "the real HWND goes to the majority-area segment's DP" on Windows D3D11. When most of a straddling window sits on another screen, that screen's segment DP gets the real HWND, so the vendor's drag phase-snap (KEEP_DRAG_SNAP on Leia) and the window anchor follow it. The former owner becomes windowless and gets its phase from set_present_origin.

Design

  • Majority rule: the segment with the largest window-px area. On an exact tie the primary wins, otherwise the leftmost. This is the same rule as oxr_segment_views_majority, now also available as comp_segments_majority.

  • Hysteresis (comp_segments_owner_*, pure, unit-tested): a hand-off is due only when the challenger beats the current owner by ≥ 20 % of the window's on-screen area, which is a 60/40 dead band between two screens, continuously for 0.5 s. A dip back into the dead band restarts the clock. A target whose hand-off failed is not retried until the majority leaves it. A screen with no DP factory is never a target.

  • Swap sequence (one weaver per HWND, and a live DP's window is never re-pointed). It runs on the weave thread under c->mutex, between two weaves:

    1. Make a windowless replacement for the old owner. The primary always gets one; another screen gets one only while the window still covers it. If this fails, abort with nothing changed.
    2. Swap the replacement in and destroy the old owner's DP. This frees the HWND.
    3. Make the new owner's DP with the HWND through create_dp_d3d11_for_screen and swap it in. If that fails, roll back so the old owner takes the HWND again.

    Each frame weaves with either the old pair or the new pair, never with a gap.

  • The primary screen always keeps a DP, because it is the session's own DP (eyes, mode, background, timing). The compositor swaps display_processor through a swap_primary hook:

    • The hook re-sends transparency, shared-texture-present, the 2D/3D mode, the eye-tracking mode, and this weave's timing and backdrop.
    • The exchange is guarded against the unlocked app-thread readers (eyes, dimensions, window metrics, mode, eye-tracking mode, segment eyes) by a leaf dp_swap_mutex.
    • A bracket hook detaches the own window's snap provider across the hand-off and re-arms it on the DP that now holds the HWND.
  • While the primary DP is windowless, the split path stays engaged, even if the window is back on the primary alone, until the hand-back. That way the primary DP keeps getting its present origin. Lifecycle retires never destroy the owner's DP.

  • Unchanged:

  • Logs:

    • One WARN per flip: segments: window handle handed from '<dev>' to '<dev>' (screen 0x… holds the majority) in N ms …, or … hand-off to '<dev>' FAILED after N ms — rolled back ….
    • The existing set-screens WARN now ends with the window handle follows the majority screen (or says why it stays).

Tests

  • scripts\build_windows.bat build exited 0.
  • ctest -R "tests_comp_segments|tests_oxr_segment_views" exited 0, 2/2 passed. tests_comp_segments adds two cases: the majority rule, and the owner hysteresis (11 sections).
  • A full ctest run has 100/103 passing. The 3 failures are the known tests_oxr_view_space_mode_* cases, which need a DP with more than 2 views and fail on this box with or without this change.
  • Not rig-verified. The box had one panel attached.

Rig check (two Leia panels: laptop 0x1 at (0,0), DS1 0x2 at (3840,0))

  1. Run cube_handle_d3d11_win (or a hosted app) on the laptop panel. Confirm the service/app log has segments: 2 screens … the window handle follows the majority screen.
  2. Drag the window so it straddles the seam at about 50/50. Hold it there for several seconds. Expected: no hand-off line.
  3. Drag it until at least about 60 % is on the DS1 and hold for more than 0.5 s. Expected: exactly one segments: window handle handed from '\\.\DISPLAY1' to '\\.\DISPLAY2' … WARN. Both halves keep weaving with no black frame. Drag snap now acts on the DS1 lattice: release mid-drag and the window lands phase-aligned for the DS1.
  4. Drag back and forth across the seam quickly (each pass shorter than 0.5 s past the 60/40 line). Expected: no further hand-off lines.
  5. Drag fully back onto the laptop and hold. Expected: one … handed from '\\.\DISPLAY2' to '\\.\DISPLAY1' … WARN, and the laptop half is phase-correct before and after.
  6. Optionally, with DXR_SEGMENTS=0, expect no segmentation and no hand-off lines.

Plug-in

No plug-in change is needed. The Leia leiasr_d3d11_create_for_screen already takes the HWND (EXTERNAL + KEEP_DRAG_SNAP) or NULL (EXTERNAL plus per-frame set_present_origin), and xrt_plugin.h already documented "the real HWND for the DP of the screen that holds the MAJORITY". One thing for the rig run to confirm: the primary screen's DP is now re-created through create_dp_d3d11_for_screen (windowless, and later with the HWND on hand-back) instead of the session-start create_dp_d3d11. On Leia it should be the same weaver config, bound to the primary display.

🤖 Generated with Claude Code

@dfattal
dfattal requested a review from a team as a code owner October 9, 2026 07:40
dfattal and others added 3 commits October 9, 2026 02:41
… Amendment 2)

comp_segments_majority (the oxr_segment_views_majority rule over a segment
table) and comp_segments_owner_*: which screen's DP holds the window handle.
A hand-off is due only when another screen holds the majority, beats the
owner by >= 20 % of the on-screen area (a 60/40 dead band between two
screens) and has done so continuously for 0.5 s; a failed target is not
retried until the majority leaves it; a screen without a DP factory is never
a target. Pure and host-tested: dead band, hold, dip-restarts-the-clock, a
drag swept across the seam never flaps, hand-back, failure latch, no-owner
recovery.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…reen M6, ADR-047 Amendment 2)

The segment DP of the screen holding the majority of a straddling window now
owns the real HWND (the vendor's drag phase-snap, KEEP_DRAG_SNAP on Leia);
every other DP is windowless and phased by set_present_origin. Before, the
HWND stayed with the primary screen's DP.

A weaver is one per HWND and a live DP's window is never re-pointed, so a
hand-off (between two weaves, under c->mutex) makes a windowless replacement
for the old owner first (abort if that fails), swaps it in and destroys the
old owner's DP, then makes the new owner's DP with the HWND through
create_dp_d3d11_for_screen, rolling back to the old owner on failure. A frame
weaves with the old pair or the new pair, never with a gap.

The primary screen keeps a DP as the session's own: the compositor swaps
display_processor through a swap_primary hook that re-sends transparency,
2D/3D mode, eye-tracking mode and this weave's timing/backdrop, and guards
the exchange against its unlocked app-thread readers with a leaf
dp_swap_mutex. A bracket hook detaches the own window's snap provider across
the hand-off and re-arms it on the DP that now holds the window. A windowless
primary gets its present origin on the split path, which stays engaged until
the hand-back. Lifecycle retires never touch the owner's DP. One WARN per
flip. Sessions without a real window (shared texture) keep the old rule; a
single screen, or a window that never leaves the primary, is unchanged.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…endment 2)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@dfattal
dfattal force-pushed the feat/win-segments-hwnd-majority branch from 26caa4d to 66eaef0 Compare October 9, 2026 09:41
dfattal added a commit that referenced this pull request Oct 9, 2026
Amendment 2 is the window handle following the majority segment (#1880),
which lands first; the service / IPC path becomes Amendment 3 everywhere
this branch names it.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
@dfattal
dfattal merged commit a1189e6 into main Oct 9, 2026
40 checks passed
@dfattal
dfattal deleted the feat/win-segments-hwnd-majority branch October 9, 2026 09:58
dfattal added a commit that referenced this pull request Oct 9, 2026
Amendment 2 is the window handle following the majority segment (#1880),
which lands first; the service / IPC path becomes Amendment 3 everywhere
this branch names it.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
dfattal added a commit that referenced this pull request Oct 9, 2026
Amendment 2 is the window handle following the majority segment (#1880),
which lands first; the service / IPC path becomes Amendment 3 everywhere
this branch names it.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
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