Repository navigation
feat(vk_native): per-screen segment DPs for spanning windows (multi-screen M2) - #1853
Merged
Merged
Conversation
This was referenced Oct 7, 2026
dfattal
force-pushed
the
feat/multi-screen-m1-display-api
branch
from
October 7, 2026 21:40
f1c474f to
9b454aa
Compare
…i-screen M2) A segment is canvas ∩ screen: the part of a window's canvas that lands on one screen, woven by that screen's own display processor (ADR-047 D2). comp_segments is the backend-agnostic half, pure integer math: - comp_segments_compute: the segment table (window px canvas rect, screen px rect, present origin = window origin relative to the screen, 1:1 state), left to right, reusing u_multi_display_compute_slices. - comp_segments_decide: per-segment refuse-rather-than-resample — flat 2D when there is no DP, or when the screen is resampled and the DP needs 1:1; unknown never degrades. - comp_segments_lifecycle: create a secondary DP after N updates, destroy after M empty ones, never retry a failed create while the segment lasts. tests_comp_segments pins the table math (ds1-linux geometry), the policy and the hysteresis. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ag (multi-screen M2) - xrt_plugin_iface::create_dp_vk_for_screen, appended after get_display_info_for_monitor per ADR-020 (struct_size-gated, ABI stays 5), announced by XRT_PLUGIN_IFACE_HAS_CREATE_DP_FOR_SCREEN. Takes an xrt_screen_binding (monitor id, desktop rect, native px, mm, scale, serial, vendor display id) so a plug-in can make one DP per screen a window spans. A plug-in without it falls back to create_dp_vk. - xrt_dp_scanout_caps::flags, carved out of reserved[0] (same size and offsets), with XRT_DP_SCANOUT_FLAG_TOLERATES_RESAMPLE: the DP's output survives a display-server resample (anaglyph yes, a lenticular weave no). Clear = needs 1:1, which is what every existing plug-in wrote there. xrt_display_processor_vk_tolerates_resample() reads it. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…reen M2) - create_dp_vk_for_screen: a DP bound to one screen answers get_display_dimensions / get_display_pixel_info for that screen (EDID mm, native px, desktop origin), seeded from the binding with the SIM_DISPLAY_* panel as the fallback. The unbound (primary) DP is unchanged. - process_atlas confines viewport AND scissor to the canvas; a full-target canvas is the pre-M2 pass byte for byte. A screen-bound DP with a sub-rect canvas uses a render-pass variant that starts in COLOR_ATTACHMENT_OPTIMAL and clears only its render area, so sibling segments survive. - set_present_origin: the interlaced output's phase (present origin; the canvas offset proxy stands when none was fed). - get_scanout_caps: weave scope from SIM_DISPLAY_WEAVE_SCOPE and TOLERATES_RESAMPLE for every output except INTERLACED. Output mode and view count stay session state (one atlas, one rendering mode, switched by the 1/2/3 keys); segment DPs follow it. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The part of a window's canvas that lands on no listed screen (a monitor no plug-in claimed, or off the desktop) gets the same flat 2D view as a segment that cannot be woven. Disjoint row-band rectangles; tested for coverage, gaps, a short screen and area conservation. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…creen M2) A window whose canvas covers several screens is woven per segment (canvas ∩ screen, ADR-047 D2) by the in-process Vulkan compositor on desktop Linux X11/XWayland: - comp_vk_native_segments owns the secondary DPs: created from each screen's registry plug-in (create_dp_vk_for_screen with an xrt_screen_binding, else create_dp_vk), windowless, with comp_segments hysteresis (create after 2 updates, destroy after 30). - Split frame: clear, crop each woven segment's views out of the atlas (crop before the DP), process_atlas per segment with canvas = segment (the DP sets viewport + scissor from it) and its own present origin, primary first, then flat 2D (the #1654 view) for unweavable segments and uncovered canvas. Leaves the target in PRESENT_SRC_KHR like the single-DP path, so everything after the weave is unchanged. - The primary DP keeps the system-default screen's segment and everything view-related (eyes, metrics) — per-segment views are M3. A window on the primary screen only never leaves the single-DP call. - 1:1: per segment via comp_segments_decide; the X11 (#1831) and Wayland resample (#1595 b/c) session gates no longer degrade a DP declaring XRT_DP_SCANOUT_FLAG_TOLERATES_RESAMPLE (read once at DP creation). - Gated off for native Wayland (the geometry payload cannot place the other monitor), zero-copy, self-submitting DPs, pinned sessions (XrSessionDisplayBindingDXR), and mixed vendors (M4). DXR_SEGMENTS=0. - The post-compose atlas capture also writes each segment's DP input as <stem>.seg<i>.png. - oxr hands the compositor the screen list after session create. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…nd follow-up (multi-screen M2) - docs/architecture/comp-segments.md: what a segment is, who weaves what, the split frame, who sets viewport/scissor, the 1:1 policy, lifecycle, scope, what M3 adds. - xrt_plugin_iface.md: create_dp_vk_for_screen and the resample flag. - linux-support.md: where #1654's 2D band is replaced by a real segment. - wayland-window-geometry.md §5: the payload change native Wayland needs (every monitor's stage rect, device_scale and connector). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…een M2 review) A non-primary screen whose plug-in lacks create_dp_vk_for_screen got an ordinary create_dp_vk DP: describing the plug-in's one panel, not bound by the canvas / not-first-writer contract (so free to clear the segment the primary had just woven), and created on the app queue. has_dp_factory is now true only with the per-screen slot (and a VK-capable, ABI-matched registry entry); otherwise that segment is flat 2D. The per-screen factory call also gets the #868 runtime-owned queue swap vk_make_dp_vk does. Slot doc, iface reference and design note updated. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…input images (multi-screen M2 review) The repaint's #1264 S1 fence-park releases c->mutex across vkWaitForFences, so an app frame could run the segment metric update and destroy a segment DP (dp_release) or recreate a crop image (crop_ensure) that the fill's in-flight command buffer still references; both frame classes also wrote the same crop image unsynchronised. - c->fill_parked marks the park (set/cleared under c->mutex). - Segment DPs and images are retired onto a comp_segments_retire list and handed back to Vulkan only from an update with no fill parked (and in destroy). release_now() asserts that in debug builds. - One DP input image per frame class (app / repaint): each class only touches its own and waits its own previous submission, so it may recreate it immediately; the capture reads the app class's. - set_screens() no longer destroys the manager under the lock; it marks a rebuild that the next weave performs once no fill is parked. - comp_segments_retire (pure) + test: nothing moves while unsafe, oldest first, capped, a full list refuses. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…nt canvas confinement (multi-screen M2 review) - The interlace phase uses set_present_origin only on a screen-bound (segment) DP. The unbound DP — every pre-M2 caller, including the single-monitor path — keeps phase = canvas_offset_x byte for byte: it reports its panel at (0,0), so a fed origin relative to a panel elsewhere would land the phase on the wrong lattice. - Viewport/scissor confinement to the canvas is KEPT for every caller and now stated as deliberate: it is the process_atlas contract and the split path needs it from the unbound primary. It differs from pre-M2 only when canvas < target (window-shrink transient, shared-texture path, comp_multi zone canvas). Spelled out in the code and in docs/architecture/comp-segments.md. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…mable X11 1:1 gate (multi-screen M2 review) sim_display's TOLERATES_RESAMPLE follows its output mode (INTERLACED needs 1:1), but the compositor read it once at DP creation: switching to interlaced on a scaled desktop kept weaving through the resample (the #1595/#1831 double image), and starting interlaced tripped the one-shot X11 gate with no recovery after switching to anaglyph. - vk_dp_refresh_resample_caps(): on a head rendering-mode change (never per frame) re-query the primary DP; a changed answer re-arms the X11 one-shot. The Wayland arm already reads the flag per evaluation. - The X11 gate now also clears its own degrade when the DP has become tolerant (vk_linux_apply_not_1to1(false)), and does not re-log while already degraded. - Segment DPs re-read their tolerance on a mode change too. - Nit: the X11 gate waits for the DP create ATTEMPT, not success, so a failed factory no longer leaves it unevaluated forever. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- Segment DPs get the same per-change declarations as the primary: the atlas encoding, honouring DXR_VK_ATLAS_ENCODING (off = never call), and the session's transparency when it toggles. - Segment capture honours DXR_ATLAS_CAPTURE_RAW_ALPHA, and the .seg<i> suffix splits on the extension's dot, not one in a directory name. - comp_segments_compute drops mirrored outputs (identical desktop rects): one segment per region, the primary winning a mirror; tested. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
dfattal
force-pushed
the
feat/multi-screen-m2-segments
branch
from
October 7, 2026 21:57
d137c45 to
1be22f0
Compare
This was referenced Oct 7, 2026
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.
What / why
Multi-screen M2 — segments, one vendor (ADR-047 D2). A window whose canvas covers several screens is woven per segment (canvas ∩ screen) by the display processor of that screen, each with its own canvas and present origin, composited into the one presented surface. Stacked on #1852 (M1); do not merge before it.
Target demo on
ds1-linux:cube_handle_vk_linuxacross eDP-1 ↔ DS1 withDXR_PLUGIN_EXCLUSIVE=sim-displayis anaglyph on both halves — two sim DPs, two canvases, no 2D band.The segment model (10 lines)
window_rect(window px, = the canvas handed toprocess_atlas),screen_rect(that screen's px),present_origin(window origin relative to that screen; the DP adds the canvas offset, so phase =screen_rectorigin).util/comp_segments.{h,c}— backend-agnostic, pure math: the table (viau_multi_display_compute_slices), the 1:1 policy, uncovered canvas, DP lifecycle hysteresis.create_dp_vk_for_screenonly (no plaincreate_dp_vkfallback — a plug-in without the slot gets flat 2D there), windowless, created on the Decouple weave rate from app render rate: re-weave the last atlas at panel rate with fresh eye positions #868 runtime-owned queue, phase viaset_present_origin.vk_native/comp_vk_native_segments.c): clear → crop each woven segment's views out of the atlas (crop before the DP) →process_atlasper segment withcanvas = segment(primary first, then left to right) → flat 2D view for unweavable segments + uncovered canvas → target left inPRESENT_SRC_KHR, so everything after the weave is unchanged.process_atlascall (byte-for-byte).wayland-window-geometry.md§5); also off for zero-copy, self-submitting DPs, pinned sessions (XrSessionDisplayBindingDXR), mixed vendors (M4). Kill switchDXR_SEGMENTS=0.<stem>.seg<i>.png.ABI (ADR-020 append-only;
XRT_PLUGIN_API_VERSION_CURRENTstays 5)xrt_plugin_iface::create_dp_vk_for_screen(inst, vk_bundle, cmd_pool, window_handle, target_format, const struct xrt_screen_binding *, out)appended afterget_display_info_for_monitor;XRT_PLUGIN_IFACE_HAS_CREATE_DP_FOR_SCREEN+xrt_plugin_iface_has_create_dp_vk_for_screen().xrt_screen_binding{struct_size, monitor_id, desktop rect, native px, mm, scale, vendor display_id, serial, device_name}, passed by pointer.xrt_dp_scanout_caps::flagscarved out ofreserved[0](same size/offsets) withXRT_DP_SCANOUT_FLAG_TOLERATES_RESAMPLE; read via the existingget_scanout_capsslot (no new DP vtable slot). Clear = needs 1:1 = what every existing plug-in wrote there.The 1:1 policy
Per segment and per DP capability (
comp_segments_decide): no DP → flat 2D; DP needs 1:1 on a resampled screen → flat 2D; 1:1 / unknown / resample-tolerant DP → woven ("unknown never degrades"). The session-level gates (X11 #1831, Wayland #1595 resample reasons b/c) no longer degrade a primary DP that declares the flag. sim_display declares it for every output except INTERLACED. Behaviour change: a sim_display anaglyph session on a fractionally scaled panel now weaves anaglyph instead of presenting 2D; lenticular weaves are unaffected.sim_display
Per-instance screen state seeded from the binding (EDID mm / native px / desktop origin; the
SIM_DISPLAY_*panel is the fallback);process_atlasconfines viewport + scissor to the canvas (full-target canvas = pre-M2 pass exactly); a screen-bound DP uses a render-pass variant that starts inCOLOR_ATTACHMENT_OPTIMALand clears only its render area;set_present_originfeeds the interlaced phase;get_scanout_capsimplemented. Output mode / view count stay session state (one atlas, one rendering mode, 1/2/3 keys) — segment DPs follow it.Tests
tests_comp_segments(new): table math on ds1-linux geometry, seam ownership, canvas sub-rect, uncovered canvas (coverage, gaps, area conservation), the 1:1 policy, lifecycle hysteresis (N/M, one-frame touch, failed create, no-factory, drain).tests_dp_weave_scope:flagsdefault + offsets.tests_stereo_camera: iface now ends withcreate_dp_vk_for_screen../scripts/build_linux.sh --hybrid --appsgreen + selftest PASSED;ctest: all pass except the pre-existingtests_rig_composer/tests_oxr_view_space*failures on this box.Windows.hcasing,TIME_UTCin the mcp dep); onlycomp_segments.cwas cross-compiled cleanly by hand.Gate (hub runs; X11/XWayland, sim-display only, dev plug-in dir)
Optional drag across the seam (hysteresis + table log): start at
DXR_CUBE_WINDOW=1600x900+800+400withDXR_X11_TEST_DRAG=2400,0,24.Review fixes (after the gate)
has_dp_factoryrequirescreate_dp_vk_for_screen; otherwise flat 2D. The per-screen call gets the Decouple weave rate from app render rate: re-weave the last atlas at panel rate with fresh eye positions #868 queue swap.c->mutex; segment DPs/images now go on a retire list (comp_segments_retire, tested) drained only when no fill is parked (c->fill_parked), with a debug assert in the release path. DP input images are per frame class (app / repaint), so the two never write each other's.set_screensdefers the rebuild to a weave with no fill parked.canvas_offset_x); only screen-bound segment DPs takeset_present_origin. Viewport/scissor confinement to the canvas is kept for every caller on purpose (it is theprocess_atlascontract and the split path needs it from the unbound primary). It is identical to before whenever canvas == target, and differs only when canvas < target: the window-shrink transient, the shared-texture path, acomp_multizone canvas (where sim used to stretch the canvas-sized atlas across the whole target). Documented incomp-segments.md.DXR_VK_ATLAS_ENCODING) and transparency on change; segment capture honoursDXR_ATLAS_CAPTURE_RAW_ALPHAand splits the suffix on the extension's dot; mirrored outputs are de-duplicated (primary wins).Gate result — PASSED on ds1-linux (2026-10-07)
Run by the hub with the exact gate command above (X11/XWayland,
DXR_PLUGIN_EXCLUSIVE=sim-display, anaglyph, window1600x900+2656+400, i.e. 800 px on eDP-1 and 800 px on HDMI-1).Log lines:
Captures (described;
ghcannot attach images from the CLI):/tmp/displayxr_atlas.png, 1600x450: the whole-window atlas, two 800x450 view tiles (left and right eye)./tmp/displayxr_atlas.seg0.png, 800x450: the eDP-1 segment's DP input, the left 400 px of each view (two 400x450 tiles)./tmp/displayxr_atlas.seg1.png, 800x450: the HDMI-1 (DS1) segment's DP input, the right 400 px of each view.So the window was split into two segments, each with its own sim DP (the secondary built through
create_dp_vk_for_screen, describing the DS1's own geometry and desktop origin), and the per-segment atlas crop is correct: each DP received exactly its half of every view.Part of #69 (M2). Addresses #546 items 2 (several DPs per compositor) and 4 (DP lifecycle with hysteresis) and the re-scoped item 3 (segments instead of an atlas split) for the in-process Vulkan compositor on Linux X11 — not closed: item 1 (real monitor id on the
COMP_DP_PRIMARY_MONITORcall sites), the other APIs and Wayland remain. Refs #1849 #1851 #1852.🤖 Generated with Claude Code