Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
56 changes: 56 additions & 0 deletions docs/adr/ADR-047-multi-screen-segments-and-per-screen-views.md
Original file line number Diff line number Diff line change
Expand Up @@ -471,3 +471,59 @@ Vulkan manager's `set_screens` are in place. Both: `DXR_DISPLAY_HOTPLUG=1` runs
there. The service / IPC path: the service's own registry is rebuilt by the sweep, but the
generation is not forwarded to clients (macOS IPC sessions do not segment). Detail:
`docs/architecture/comp-segments.md` § *Display hotplug*.

## Amendment 9 (2026-10-12): a zone is framed from its own screen on every segmenting session

The Amendment 4 addendum re-frames a present-owner's element that lies wholly on a non-primary
screen. A session whose own compositor segments its window (in-process D3D11 / D3D12 / GL /
Vulkan / Metal, or a direct client of the D3D11 service, Amendment 3) had the same gap one layer
up: `oxr_session_locate_views` never splits a zone-scoped locate (`locate_can_split`), and its one
view set was framed from the majority screen. A zone on the other screen of a straddling window
(an `XR_DXR_display_zones` app, e.g. a multi-zone Unity app on the in-process D3D12 compositor)
was therefore rendered from the wrong viewer and framed against the wrong panel, though each
screen's DP wove its pixels (displayxr-browser-pvt#272 is the browser's face of the same gap).
Decisions:

- **The zone's screen frames it when the zone lies wholly on one screen.** The locate asks
`oxr_segment_views_zone_segment` (the table's segment that holds the whole zone rect, its
screen's sizes known) and frames that screen's whole-window metrics
(`oxr_segment_views_whole_window_metrics`) with that screen's eyes and nominal viewer, then
applies the zone rebase as before. That screen may be the primary: a zone on the primary of a
window mostly on screen 2 now takes the single-screen path (the session DP's own eyes and
window metrics), where it took screen 2's before. The present-owner rule never needs that
case, because a present-owner's single set is already framed from the primary.
- **A straddling zone** is superseded by Amendment 10 (its one set is framed from the screen
holding most of the zone, and its parts can get a view set each).
- **Same answer as the server.** Wherever `u_weave_zone_screen_segment` re-frames, the state
tracker's rule picks the same segment (swept in `tests/tests_weave_segments.cpp`).
- **No routing, no extension revision.** A zone locate still records no
`xrt_segment_view_routing`, so the compositor crops the frame per segment as in M2, and the
zone's pixels, wholly on one screen, are woven by that screen's DP. The locate API is unchanged;
only which screen frames a single-screen zone moves.

## Amendment 10 (2026-10-12): a straddling zone gets a view set per part

Design and David's decisions: `docs/roadmap/multi-screen.md` § 8 (displayxr-browser-pvt#272).

- **One majority rule for a zone's one view set, on every path.** A zone (or inline-3D element)
that straddles a seam is framed from the screen holding the largest part of the ZONE (ties to
the primary, then the leftmost): `u_weave_rect_majority_segment`, used by both
`u_weave_zone_screen_segment` (present-owners, which used the primary for a straddling element)
and `oxr_segment_views_zone_segment` (every other segmenting session, which used the window's
majority).
- **Per-part views: `XrDisplayZonePartsDXR`** (`XR_DXR_display_zones` v4), chained on
`XrViewState` of a zone-scoped locate. The runtime cuts the zone at the seam
(`u_weave_zone_part_table`) and locates every part like a window segment over the parts' own
table (`oxr_segment_views_zone_parts` → `oxr_segment_views_layout`): that screen's eyes carried
into the frame of the zone's majority screen, the part as the Kooima canvas on its own screen,
and, for a display rig, one m2v for the whole zone (`vdh_scale` and the rig offset with
"window" read as "zone"), so the parts meet at the seam. `XrView[]` keeps the one set, so an
unaware caller is unchanged; `PRIMARY_MULTIVIEW_DXR` gets per-part zone views only through
the struct. Camera-centric locates are never split.
- **Budget.** At most `DXR_ZONE_PARTS_MAX` (default 4) zones per display time are split, first
located first served; past it a zone keeps its one set.
- **The seam is accepted** as for windows: two viewers, no blending.
- **Where it runs.** In-process segmenting compositors and direct clients of the D3D11 service
(Amendment 3) locate the parts client-side from the segment table, so no wire change. A
present-owner's element locates are framed server-side (Amendment 4 addendum); its parts, and
the weave crop at the painted partition, are the stacked follow-up.
11 changes: 9 additions & 2 deletions docs/architecture/comp-segments.md
Original file line number Diff line number Diff line change
Expand Up @@ -493,6 +493,12 @@ views*; API: `XrViewDisplayBindingsDXR` in `XR_DXR_display_info` v22).
What stays one view set (cropped per segment, as in M2): `PRIMARY_STEREO`/`MONO`
sessions (framed from the screen holding most of the window), camera-rig and
zone-scoped locates, a window covering more than `XRT_MAX_SEGMENTS` (2) screens. A
zone-scoped locate is framed from the **zone's** screen when the zone lies wholly on one
(`oxr_segment_views_zone_segment`: its eyes, the whole window relative to it, then the zone
rebase; ADR-047 Amendment 9), from the screen holding most of the ZONE when it straddles a
seam, and a caller chaining `XrDisplayZonePartsDXR` gets one view set per part on top
(Amendment 10: the parts are located client-side from this table, the frame stays unrouted and
is cropped at the live seam). A
frame whose routing no longer matches (the mode changed between the locate and the
commit) is drawn unrouted for that frame. Under per-segment views, canvas that lies on
no screen is not covered by any view set and stays black (M2 painted it from the shared
Expand Down Expand Up @@ -603,5 +609,6 @@ pixels into a handback texture on its own IPC thread. That submit is segmented b
latch since nothing is torn down), and falls back to the panel DP's snap.
- **Not covered:** the v8 hardware wish is still published to the panel DP only (the primary
screen's lens); a window across more than `XRT_MAX_SEGMENTS` screens is woven per screen but
reported unsegmented (one eye set); a straddling element's views stay the primary's
(per-part views need a view set per part, displayxr-browser-pvt#272).
reported unsegmented (one eye set); a straddling element's one view set is framed from the
screen holding most of it (ADR-047 Amendment 10), and its per-part views
(`XrDisplayZonePartsDXR`) are not filled server-side yet (`docs/roadmap/multi-screen.md` § 8).
3 changes: 2 additions & 1 deletion docs/roadmap/control-panel-performance-settings.md
Original file line number Diff line number Diff line change
Expand Up @@ -325,7 +325,7 @@ never claim a mode the runtime is not in, and "Custom" falls out for free.
## Appendix A — census

Every `DXR_*` name read at runtime under `src/xrt`, with its read site, mechanism, default
and tier. **107 distinct names**; the two `DXR_BG2D_*` knobs reach the environment through
and tier. **108 distinct names**; the two `DXR_BG2D_*` knobs reach the environment through
`bg2d_int_knob()` rather than a literal `getenv` at the listed line.

Process column: **App** = the runtime DLL, loaded into the OpenXR app's process ·
Expand Down Expand Up @@ -410,6 +410,7 @@ library linked into both · **CLI** = `displayxr-cli.exe` (reporting only, contr
| `DXR_LAZY_TRANSPARENCY` | `compositor/vk_native/comp_vk_native_compositor.c` (`vk_lazy_transparency_setup`) | `DEBUG_GET_ONCE_BOOL` | **on** | App | 4 | Lazy transparency (desktop Linux): a transparency-capable session runs the DP's desktop capture, compose-under and alpha-gate only while its content carries alpha < 1. `=0` keeps them on for the whole session (the pre-slot behaviour), for A/B. Only engages when the DP implements `set_transparency_active` |
| `DXR_LINUX_DIRECT_SCANOUT` | `compositor/vk_native/comp_vk_native_compositor.c:5845` | `getenv` | off | App | 1 | Opt-in RandR direct-scanout present path on Linux |
| `DXR_WINDOW_POS` | `compositor/vk_native/comp_vk_native_compositor.c:5248` | `getenv` | plug-in reported | App | 1 | `"x,y"` override for the self-owned window position (#715) |
| `DXR_ZONE_PARTS_MAX` | `state_trackers/oxr/oxr_session.c` (`DEBUG_GET_ONCE_NUM_OPTION`, read in `locate_zone_parts`) | `DEBUG_GET_ONCE_NUM` | 4 | App | 1 | `XrDisplayZonePartsDXR` budget (multi-screen § 8): how many zones per display time get a view set per part when they straddle a seam; a split zone renders its views once per part, so past the cap a zone keeps its one set. `0` = never split; clamped to 16 |
| `DXR_WINDOW_FULLSCREEN` | `compositor/vk_native/comp_vk_native_window_xcb.c:273` | `getenv` | on | App | 1 | `=0` opts out of EWMH fullscreen placement (Linux XCB) |

### Native-Wayland window drag (#1748)
Expand Down
51 changes: 51 additions & 0 deletions docs/roadmap/multi-screen.md
Original file line number Diff line number Diff line change
Expand Up @@ -305,3 +305,54 @@ that configuration is accepted; it disappears when every screen is tracked.
ST-5598 → SR-P1; ST-3160 → SR-P3.
- New: ADR "Multi-screen: segments and per-screen views" (supersedes ADR-015 §3-§4),
`XR_DXR_display_info` v22 spec section, `comp_segments` design note.

## 8. Per-part views for a zone that straddles a seam (displayxr-browser-pvt#272)

**Decided (David, 2026-10-12).** Runtime record: ADR-047 Amendment 10; API:
`XR_DXR_display_zones.md` § 3.3.2.

A zone (an `XR_DXR_display_zones` zone, or an inline-3D element, which the browser locates as
one) that straddles a seam is woven per screen, but one view set renders both parts from one
screen's viewer. The fix gives each part its own view set.

### 8.1 Runtime: the locate returns the parts

- **Surface.** A zone-scoped `xrLocateViews` returns the parts through
`XrDisplayZonePartsDXR`, chained on `XrViewState` (`XR_DXR_display_zones` v4). The caller does
not locate each part: the browser locates in its browser process and weaves in its GPU process,
so the weave output's parts never reach the code that renders views, and pairing a locate's
views with a weave's parts would mix two tables a frame apart during a drag (the #1883 sliver).
- **Per part:** that screen's eyes, Kooima against that screen with the part as the canvas, and
for a display rig ONE m2v for the whole zone (`vdh_scale` / rig offset with "window" read as
"zone"), so the parts meet at the seam. Implemented as the per-segment math over the parts' own
table (`u_weave_zone_part_table`, `oxr_segment_views_zone_parts`).
- **Unaware callers.** `XrView[]` keeps one set, framed from the screen holding most of the ZONE
on every path (`u_weave_rect_majority_segment`). This changed the present-owner path, which
used the primary, and every other session, which used the window's majority.
- **Seam.** Two viewers can make the zone discontinuous at the seam. Accepted, no blending, as for
windows (decided 2026-10-07).
- **Budget.** At most 4 split zones per display time, first located first served
(`DXR_ZONE_PARTS_MAX`, 0 = never split, max 16). Past it a zone keeps its one set.
- **`PRIMARY_MULTIVIEW_DXR`** gets per-part zone views only through the struct, never through
`XrView[]`.
- **Where it runs today.** In-process segmenting compositors and direct D3D11-service clients:
the parts are located client-side from the segment table, no wire change; those frames stay
unrouted and are cropped at the live seam.
- **Still to land: present-owners.** Their element locates are framed server-side
(`ipc_try_get_sr_view_poses`), so the locate reply gains an appended, fixed-size parts block
(≤ `XRT_MAX_SEGMENTS` parts × the active views of pose + fov), versioned by append-only fields
under the `u_git_tag` gate. The weave then crops each part where it was **painted**: the batch /
v6 submit carries the partition the frame was rendered with (zone-relative rects per screen,
appended to the rect), and `weave_seg_record` crops with it.

### 8.2 Browser and web API (displayxr-browser-pvt)

- **Shape:** each part is an extra view pair, every `XRView` carrying a **sub-viewport** (the
part's rect relative to the element, normalised 0..1). DisplayXR-only for now.
- Blink installs the parts with their views per element (today one view pair per element, patch
0036); `GetLatestViews` grows a parts list. An element with zero or one part keeps the current
shape and code path.
- The page renders each pair into that sub-rect of each eye's half of the element's side-by-side
submission (a part occupies `rect.x / w ÷ 2` inside each half, `XR_DXR_weave.md` §5g item 4). A
page that reads only the first pair renders the element from its majority screen.
- The weave submit is unchanged apart from carrying the painted partition (8.1).
93 changes: 83 additions & 10 deletions docs/specs/extensions/XR_DXR_display_zones.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@
| Property | Value |
|----------|-------|
| Extension Name | `XR_DXR_display_zones` |
| Spec Version | 1 |
| Type Values | `1004999150–1004999153` |
| Spec Version | 4 |
| Type Values | `1004999150–1004999155` |
| Author | Leia Inc. |
| Platform | All (extension-app classes only) |
| Requires | `XR_DXR_local_3d_zone` (≥ v3), `XR_DXR_view_rig` (≥ v2) |
Expand Down Expand Up @@ -60,13 +60,15 @@ physically *switches* (the wish). Full rationale: ADR-027.

```c
#define XR_DXR_display_zones 1
#define XR_DXR_display_zones_SPEC_VERSION 1
#define XR_DXR_display_zones_SPEC_VERSION 4
#define XR_DXR_DISPLAY_ZONES_EXTENSION_NAME "XR_DXR_display_zones"

#define XR_TYPE_DISPLAY_ZONE_CAPABILITIES_DXR ((XrStructureType)1004999150)
#define XR_TYPE_DISPLAY_ZONE_DXR ((XrStructureType)1004999151)
#define XR_TYPE_DISPLAY_ZONES_FRAME_END_INFO_DXR ((XrStructureType)1004999152)
#define XR_TYPE_EVENT_DATA_DISPLAY_ZONE_METRICS_CHANGED_DXR ((XrStructureType)1004999153)
#define XR_TYPE_DISPLAY_ZONE_FEATHER_DXR ((XrStructureType)1004999154) // v3
#define XR_TYPE_DISPLAY_ZONE_PARTS_DXR ((XrStructureType)1004999155) // v4

typedef XrFlags64 XrDisplayZonesFrameEndFlagsDXR;
//! Cross-check zone/locate/mask consistency this frame: zone rects vs wish
Expand Down Expand Up @@ -127,13 +129,16 @@ typedef struct XrDisplayZoneDXR {
} XrDisplayZoneDXR;
```

**Multi-screen (Windows, `XR_DXR_weave` present-owners).** When the session's
window is woven per screen (ADR-047 Amendment 4), a zone-scoped locate whose
`rect` lies wholly on a non-primary screen is framed from that screen: its
eyes, its nominal viewer, and the zone placed on its physical rectangle
(Amendment 4 addendum). The raw channel then reports that screen's eyes and the
zone relative to that screen. A zone that straddles a seam is located from the
primary screen, as before.
**Multi-screen.** When the session's window is woven per screen (ADR-047), a
zone-scoped locate whose `rect` lies wholly on one screen is framed from that
screen: its eyes, its nominal viewer, and the zone placed on its physical
rectangle. The raw channel then reports that screen's eyes and the zone relative
to that screen. A zone that straddles a seam gets ONE view set in `XrView[]`,
framed from the screen holding most of the zone (ties to the primary), on every
session (spec v4; a present-owner used the primary before, every other session
the screen holding most of the window). `XrView[]` is never split per segment,
under `PRIMARY_MULTIVIEW_DXR` either: a straddling zone's per-part view sets
ride `XrDisplayZonePartsDXR` (§ 3.3.2).

`zoneId` exists for locate↔submit pairing (validate mode), debug
logs/captures, and as the stable referent for the reserved effective-mask
Expand Down Expand Up @@ -164,6 +169,74 @@ typedef struct XrDisplayZoneFeatherDXR {
} XrDisplayZoneFeatherDXR;
```

### 3.3.2 XrDisplayZonePartsDXR — per-part views of a straddling zone (spec v4)

```c
typedef struct XrDisplayZonePartDXR {
uint64_t displayId; // the screen (XrDisplayDXR::displayId)
XrRect2Di rect; // the part, ZONE-relative px, cut at the seam
uint32_t firstView; // its first view in XrDisplayZonePartsDXR::views
uint32_t viewCount; // = the locate's active view count
} XrDisplayZonePartDXR;

typedef struct XrDisplayZonePartsDXR {
XrStructureType type; // XR_TYPE_DISPLAY_ZONE_PARTS_DXR (1004999155)
void* XR_MAY_ALIAS next;
uint64_t generation; // changes with the screen table the parts are cut from
uint32_t partCapacityInput;
uint32_t partCountOutput; // 0 = not split: use XrView[] as before
XrDisplayZonePartDXR* parts;
uint32_t viewCapacityInput;
uint32_t viewCountOutput;
XrView* views; // every part's set, contiguous
} XrDisplayZonePartsDXR;
```

**Chain point.** OUTPUT, on `XrViewState::next` of a zone-scoped `xrLocateViews`
(an `XrDisplayZoneDXR` on `XrViewLocateInfo`). Without a zone it is left at 0.

**What it returns.** When the session's window is woven per screen and the zone
straddles a seam, one view set per part, left to right:

- **Each part from its own screen.** That screen's eyes (its nominal viewer when
untracked, ADR-047), Kooima against that screen with the part as the canvas.
The two screens' viewers may disagree, so the zone can be visibly
discontinuous at the seam; that is accepted, as for windows (no blending).
- **One scale per zone.** Under a display rig (`XrDisplayRigDXR`) the rig's
virtual display height maps the ZONE: each part gets its share of it and its
offset from the zone's centre, so the parts meet at the seam at the same
metres-to-virtual scale, also across panels of different pixel pitch.
- **One frame.** Every part's poses are in the locate's base space; the parts are
placed continuously in the display space of the screen holding most of the
zone, the screen that also frames `XrView[]`.

**How to render.** Part p's view at index v fills `p.rect` (scaled by image size /
zone size) of view v's image of the zone's projection layer; each part's FOV
covers exactly its rect. Submit the zone's projection layer as usual: each
screen's display processor weaves its part.

**When it is 0** (render `XrView[]`): the zone lies on one screen, the window is
not woven per screen, the locate is camera-centric (`XrCameraRigDXR`), or the
runtime's per-frame budget of split zones is spent. A split zone renders its
views once per part, so at most 4 zones per display time are split, first
located first served (`DXR_ZONE_PARTS_MAX`, 0 = never). Locate the zones that
matter most first. `XrView[]` is always the zone's one set, so an app that
ignores this struct renders as before.

**Two-call idiom, never an error.** `partCountOutput` and `viewCountOutput` are
always written; `parts` and `views` only when both capacities suffice (0 = count
query). Today at most 2 parts (`XRT_MAX_SEGMENTS`) × the per-set view count.

**`PRIMARY_MULTIVIEW_DXR`.** A zone-scoped locate is never split into
`XrView[]`; its per-part sets come through this struct only, the same mechanism
for every view configuration.

**Where it is filled.** In-process segmenting compositors and direct clients of
the D3D11 service (the parts are located client-side from the service's segment
table). For an `XR_DXR_weave` present-owner (the DisplayXR Browser), whose
element locates are framed by the service, `partCountOutput` stays 0 until the
service-side parts land (`docs/roadmap/multi-screen.md` § 8.1).

### 3.4 XrDisplayZonesFrameEndInfoDXR — per-frame wish reference

```c
Expand Down
2 changes: 1 addition & 1 deletion docs/specs/extensions/index.json
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@
"XR_DXR_display_zones": {
"group": "rendering",
"title": "Display Zones",
"summary": "A layout of independent 3D zones and flat 2D zones across one display, each 3D zone with its own view rig, plus the wish mask a switchable-lens panel honours; poll xrGetDisplayZoneRecommendedViewSizeDXR for per-zone sizes — XrEventDataDisplayZoneMetricsChangedDXR is deprecated and never emitted"
"summary": "A layout of independent 3D zones and flat 2D zones across one display, each 3D zone with its own view rig, plus the wish mask a switchable-lens panel honours; a zone straddling two screens can get a view set per part (XrDisplayZonePartsDXR); poll xrGetDisplayZoneRecommendedViewSizeDXR for per-zone sizes — XrEventDataDisplayZoneMetricsChangedDXR is deprecated and never emitted"
},
"XR_DXR_local_3d_zone": {
"group": "rendering",
Expand Down
Loading
Loading