Skip to content
Merged
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
20 changes: 18 additions & 2 deletions .github/workflows/build-linux.yml
Original file line number Diff line number Diff line change
Expand Up @@ -69,8 +69,10 @@ env:
# vk_bundle ABI fingerprint fields; no feature macro, an older ref fails to
# compile). v2.17.1: first line with the rear-depth-budget preview
# (XRT_DP_VK_HAS_BACKGROUND_PREVIEW), and a vk_bundle layout that matches
# runtimes v2.16.0..main — see the CMakeLists comment.
RUNTIME_REF: v2.27.0
# runtimes v2.16.0..main — see the CMakeLists comment. v2.29.0: past
# v2.27.1, which owns the resampled-X11 refusal the windowless weaver
# (multi-screen M4) no longer gets from the SR SDK.
RUNTIME_REF: v2.29.0

jobs:
# Classify the change. Same rule as displayxr-runtime's build-linux.yml:
Expand Down Expand Up @@ -207,6 +209,20 @@ jobs:
if: needs.DetectChanges.outputs.docs_only != 'true'
run: ./build/src/drv_leia_linux/test_display_claims_linux

# Multi-screen M4: one DP per screen resolves its own screen (binding +
# EDID list + SR query) into state it owns — no statics shared between
# two DPs — and the SR weaver chain plan (EXTERNAL routing + display
# binding, gated on the SR runtime's caps). The chain half of the second
# test compiles only against SDK headers that carry the structs; this
# stub build checks the plan.
- name: Unit test — per-DP screen state (multi-screen M4)
if: needs.DetectChanges.outputs.docs_only != 'true'
run: ./build/src/drv_leia_linux/test_screen_linux

- name: Unit test — SR weaver routing/binding plan (multi-screen M4)
if: needs.DetectChanges.outputs.docs_only != 'true'
run: ./build/src/drv_leia_linux/test_sr_routing_linux

- name: Assert single export (ADR-019 / #496)
if: needs.DetectChanges.outputs.docs_only != 'true'
run: |
Expand Down
2 changes: 2 additions & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,8 @@ ADR-020 spec: [`displayxr-runtime/docs/adr/ADR-020-plugin-abi-policy.md`](https:
| `src/drv_leia_linux/leia_sr_linux_sdk.c` | Track B backend: the real srSDK (C99, API 1.0.0) behind the same seam — instance/display/lens/weaver + event latching. Selected by `-DDXR_LEIA_LINUX_WEAVER=sdk`. |
| `src/drv_leia_linux/leia_plugin_linux.c` | Linux `xrtPluginNegotiate` + iface (VK-only factories; env-gated probe). |
| `src/drv_leia_linux/leia_display_claims_linux.{c,h}` | Linux `probe_displays` matching (multi-screen M0): runtime monitors × EDID panel list × SR display enumeration → per-monitor claims + private `displayId` table. Pure logic, unit-tested. |
| `src/drv_leia_linux/leia_screen_linux.{c,h}` | Per-DP screen state (multi-screen M4): a `create_dp_vk_for_screen` binding + EDID list + SR query → the `struct leia_lnx_screen` one DP owns and answers its display getters from. Pure logic, unit-tested. |
| `src/drv_leia_linux/leia_sr_routing_linux.h`, `leia_sr_chain_sdk_linux.h` | SR weaver create chain (multi-screen M4): the plan (EXTERNAL routing + display binding, gated on the SR runtime's caps; pure) and its SR-typed `pNext` builder. Both unit-tested; the chain half only against SDK headers that carry the structs. |
| `src/drv_leia_linux/leia_display_processor_linux.c` | Linux VK DP — 1×1 grid blits, multi-view goes through the seam. Reuses `../drv_leia/leia_device.c`. |
| `installer/DisplayXRLeiaSRInstaller.nsi` | NSIS installer. Drops DLL at `C:\Program Files\DisplayXR\Plugins\LeiaSR\`; writes registry entry `HKLM\Software\DisplayXR\DisplayProcessors\leia-sr` with values `Binary`, `ProbeOrder`, `Version`, `DisplayName`, `Vendor`, `UninstallString`. No prerequisites; see `docs/installer.md`. |
| `installer/rm-helper/` | `dxr-rm-close.exe` — generic Restart Manager driver the installer/uninstaller use to close and restart whatever maps the plug-in's files (one `session` process spans both phases). `rm_test_holder.cpp` is a test-only holder (EXCLUDE_FROM_ALL). |
Expand Down
10 changes: 9 additions & 1 deletion CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -212,7 +212,15 @@ set(DXR_RUNTIME_GIT_TAG "v2.27.0" CACHE STRING
# Not in v2.17.1: the VK snap_window_rect slot (runtime#1592,
# XRT_DP_VK_HAS_SNAP_WINDOW_RECT), so drag phase-snap compiles out at this pin
# (expected; it compiles in automatically at the first pin that carries it).
set(DXR_RUNTIME_GIT_TAG_LINUX "v2.27.0" CACHE STRING
# v2.29.0 (from v2.27.0), multi-screen M4 review: the Vulkan weaver is now
# windowless on every path (window = 0), which drops the LeiaSR SDK's own
# refusal to weave into a resampled X11 window. The runtime took that refusal
# over in v2.27.1 (#1831: X11 on a fractionally scaled panel presents 2D), so
# the Linux pin moves past it (to the current tag), and the .deb now Depends
# on displayxr-runtime (>= 2.27.1) (scripts/package_deb_leia.sh) — an older
# runtime would weave a scaled X11 window into a double image. vk_bundle /
# os_mutex / xrt_plugin.h are unchanged v2.27.0..v2.29.0.
set(DXR_RUNTIME_GIT_TAG_LINUX "v2.29.0" CACHE STRING
"Runtime git ref for Linux builds. Must equal build-linux.yml's RUNTIME_REF (rule-5 CI check enforces).")

# Android carries its own pin too, and as of the get_last_frame_dropped repin it
Expand Down
20 changes: 19 additions & 1 deletion docs/leia-linux-sdk-contract.md
Original file line number Diff line number Diff line change
Expand Up @@ -200,12 +200,30 @@ ask on the C99-API PR (send new asks to **LeiaInc/LeiaSR#75**, which supersedes
| R-T4 MANAGED | ◐ | Grace/collapse/auto-2D semantics now understood **from source**: the weaver internally decides weave-vs-blit per frame by `eyeSeparation > 1 mm` (the collapse animation drives the 2D drop), and flips the lens hint on that transition. Still no *observable* grace-period state for the plug-in, so `is_tracking` flips at raw `USER_LOST`, earlier than R-T4 prefers ❌ doc/API ask. |
| R-T5 MANUAL | ❌ | No stand-down toggle. MANAGED-only (acceptable for v1 per contract). |
| R-T6 threading | ✅ | `srEyeTrackerPredict` documented safe from any thread incl. render thread; callbacks on SDK thread. |
| R-D1 display enumeration | ◐ | `srCreateDisplay({window:0})` + `GetPhysicalSize(cm)/GetPhysicalResolution/GetLocation/GetRecommendedTextureSize/GetDefaultViewingPosition(mm)/GetIdentifier` map 1:1 ✅, readable pre-weaver ✅; **no refresh-rate getter** ❌ (weaver knows it internally for `SetLatencyInFrames`); `GetRecommendedTextureSize` = **per-view, settled**: it wraps the same legacy `getRecommendedViewsTextureWidth/Height` the Windows arm already consumes and logs as "per eye". |
| R-D1 display enumeration | ◐ | `srCreateDisplay({window:0})` + `GetPhysicalSize(cm)/GetPhysicalResolution/GetLocation/GetRecommendedTextureSize/GetDefaultViewingPosition(mm)/GetIdentifier` map 1:1 ✅, readable pre-weaver ✅; **no refresh-rate getter** ❌ (weaver knows it internally for `SetLatencyInFrames`) — **answered by the 876620d62 line:** `srDisplayGetRefreshRate`, used when present (§8a); `GetRecommendedTextureSize` = **per-view, settled**: it wraps the same legacy `getRecommendedViewsTextureWidth/Height` the Windows arm already consumes and logs as "per eye". |
| R-D2 2D/3D switch | ✅ | `sr_lens.h`: `srLensEnable/Disable/IsEnabled` (+ `lensPreference` at instance create, `SR_EVENT_TYPE_LENS_ON/OFF` events). |
| R-D3 calibration internal | ✅ | No interlace parameters exposed anywhere. |
| R-D4 headless-tolerant | ✅ | `srCreateDisplay` succeeds with no panel; getters return `SR_ERROR_DISPLAY_NOT_FOUND`; `srDisplayIsValid` is the probe primitive. |
| §7 packaging | ✅ | C API ✅; `find_package(srSDK CONFIG)` package, relocatable ✅; loader-stub dlopen model ✅ (static `libsrSDK_loader.a` → `libLeiaSR_runtime.so`, search order: `/etc/leia/sr/1/active_runtime.json` → `SR_RUNTIME_PATH` → plain dlopen — and the `leiasr-runtime` .deb now registers `active_runtime.json` itself). **The .deb *is* the dev package** (headers + `libsrSDK_loader.a` + `lib/cmake/srSDK/` under `/opt/leiasr`); no separate SDK download. Support matrix (26.04/Mesa ANV): headless build+run confirmed, **fullscreen on-panel weave validated 2026-09-20**, windowed weave pending (→ Q1). |

### 8a. Multi-screen calls (LeiaSR Linux line 876620d62, phases A-C)

The SR side of the DisplayXR multi-screen plan (runtime `docs/roadmap/multi-screen.md`,
SR-P0/P1/P2). Each row is compiled only when the SDK headers (and, for calls, the loader
archive) carry it, and used only when the **installed** runtime reports it. The installed
1.38 `.deb` has none of them: every row compiles out against `/opt/leiasr`, and a
new-header build running on a 1.38 runtime falls back at run time.

| Call / struct | Plug-in use | Build gate | Run-time gate / fallback |
|---|---|---|---|
| `srEnumerateDisplays` (slot 97) + `SrDisplayDescriptor` (25) | `probe_displays` claims (multi-screen M0): FPC confidence, serial, `displayId` per monitor, into the plug-in-private claim table. `leia_lnx_sr_active_display_id()` = the first FPC-verified entry. | `DXR_LEIA_LNX_HAVE_SR_DISPLAY_ENUM` (compile + link) | `SR_ERROR_FUNCTION_UNSUPPORTED` → SR 1.38 evidence (lens serial). Never creates a context. |
| `srGetRuntimeCapabilities` + `SrWeaverRoutingCapabilities` (24) + `SrDisplayBindingCapabilities` (26) on `pNext` | Once per context: decides whether routing and binding are chained. Logged as `SR multi-display caps: …`. | `DXR_LEIA_LNX_HAVE_SR_ROUTING` (type-only compile check) | A runtime that predates the structs leaves them "not supported", so nothing is chained. |
| `SrWeaverRoutingInfo{EXTERNAL, flags 0}` (22) on `SrWeaverCreateInfoVulkan.pNext` | Every weaver (multi-screen M4): no `canWeave` gate, one full-input region, phase = present origin + viewport only, no lens vote, no polling. Because the weaver no longer votes, the plug-in turns the lens on at creation and off after the last one is destroyed (`leia_lens_owner_linux.h`). No `KEEP_DRAG_SNAP`: the weaver is windowless. | `DXR_LEIA_LNX_HAVE_SR_ROUTING` | `externalRouting`; a refused create retries SDK-routed (one INFO line). Opt-out: `DXR_LEIA_SR_EXTERNAL_ROUTING=0`. SR's `SR_WEAVER_ROUTING` env overrides either way. |
| `SrDisplayBindingInfo{displayId}` (23) on `SrWeaverCreateInfoVulkan.pNext` | A segment DP's weaver, bound to its screen's SR display (the runtime binding's `display_id`, else the claim table's). | `DXR_LEIA_LNX_HAVE_SR_ROUTING` | `displayBinding && maxBoundDisplays >= 1` and a non-zero id. `SR_ERROR_DEVICE_NOT_AVAILABLE` (EDID-only) or `SR_ERROR_DISPLAY_NOT_FOUND` (unknown id) → retry unbound (active display), one INFO line. |
| `SrDisplayBindingInfo` on `SrDisplayCreateInfo` / `SrEyeTrackerCreateInfo` / `SrLensCreateInfo` | **Not chained.** The context's display, tracker and lens stay process-wide and follow the active display. The tracker must exist before `srInitialize`, while display ids come from enumeration on an initialised context. With `maxBoundDisplays = 1` the only bindable display IS the active one. Two Leia panels per process wait on LeiaSR phase D. | — | — |
| `srDisplayGetRefreshRate` (slot 98) | `refresh_mhz` in the display query (leia-plugin #184), replacing the 60 Hz constant. | `DXR_LEIA_LNX_HAVE_SR_REFRESH` (compile + link) | Failure or an implausible value keeps 60 Hz (one INFO line). |
| `SrWeaverCreateInfoVulkan.window` | **Always 0** since M4 (R-W3 / ADR-033): the runtime's `set_present_origin` → `srWeaverSetPresentOrigin` supplies the phase origin. An X11 id made the SDK refuse a resampled panel (2D under scaled XWayland). | — | — |

## 9. Open questions for ratification

Post-reconciliation status per question — answered ones kept for the record:
Expand Down
2 changes: 1 addition & 1 deletion docs/linux-deb-packaging.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ fallback; see `displayxr-runtime/scripts/package_deb_linux.sh`).
| Discovery | Drops `DisplayXR-LeiaSR.so` + `050-leia-sr.json` (probe_order **50**) into `/usr/lib/displayxr/plugins/` — the dir the runtime searches by default (runtime #781 Phase 1). No registry, no env. |
| Claim vs fallback | `probe_order=50` beats sim-display's `200`. The **presence probe** (leia-plugin #99, on `main`) claims when the SR runtime is reachable and declines otherwise, so sim-display drives apps on a box without the SR stack. No `DXR_LEIA_FORCE_PROBE`. |
| SR runtime discovery | **No baked rpath.** The srSDK loader resolves `libLeiaSR_runtime.so` via `/etc/leia/sr/1/active_runtime.json` (registered by the SR runtime installer) → `$SR_RUNTIME_PATH` → plain dlopen. See `docs/leia-linux-sdk-contract.md` §7. The packager strips any `DT_RUNPATH` with `patchelf` so no build-machine path ships. |
| Dependencies | `Depends: displayxr-runtime` (hard prereq) + **versioned** system-library dependencies derived with `dpkg-shlibdeps`. `Recommends:` the SR runtime package — without it the plug-in still installs and its probe declines. |
| Dependencies | `Depends: displayxr-runtime (>= 2.27.1)` (hard prereq; 2.27.1 is the first runtime that refuses to weave a resampled X11 window itself, which the windowless weaver relies on) + **versioned** system-library dependencies derived with `dpkg-shlibdeps`. `Recommends:` the SR runtime package — without it the plug-in still installs and its probe declines. |
| Portability | **One package for Ubuntu 22.04, 24.04 and 26.04.** See below. |

## One package for 22.04, 24.04 and 26.04
Expand Down
Loading
Loading