Repository navigation
feat(lift): NeurD-backed 2D→3D lift slots on the D3D11 DP - #283
Merged
Merged
Conversation
Dynamic NeurD.dll loader (PATH -> NEURD_PATH -> HKLM\SOFTWARE\LeiaInc\NeurD), one process-wide NeurD instance activated on a background thread (licence network failure reported as ACTIVATING and retried), per-DP handle with ref-count + stream table, LeiaMeet-style device bridge (shared textures on NeurD's device, pack/unpack compute shaders, event-query drains), mode -> NeurD property mapping, tracked-eye -> NeurD viewpoint mapping. Plug-in-owned types only; the DP maps the runtime's lift contract onto it. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
lift_get_caps / lift_stream_create / lift_stream_destroy / lift_convert, compiled only when the runtime headers announce XRT_DP_D3D11_HAS_LIFT (runtime feat/lift-ext); struct_size-bounded reads of every versioned struct. Tracked eyes come from the existing SR eye path. The lift handle is created at DP create (reads DXR_LEIA_LIFT* once, loads nothing) and destroyed before the weaver. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…obs, verification Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
- NVIEW is one row, view 0 leftmost (was a square grid); max_views 8 so the row fits D3D11's 16384 limit at 1440p. - DEPTH returns a single-channel R8_UNORM texture (R8 unpack shader). - strength 0 = flat (only negative falls back to 1.0); inpaint non-zero = blur fill. - input_scale 1 = native; DXR_LEIA_LIFT_SCALE, when set, overrides streams. - lift_convert_blob (GAUSSIANS) left NULL. - docs: contract deltas, convergence-units gap, lift-DP weaver caveat. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…ivate media_sdk Remove src/drv_leia/leia_neurd_abi.h. It was a hand-written mirror of NeurD's public header — enum values, struct layouts and the function-table order only; no NeurD code was ever included. Leia's NeurD header is proprietary, so the public plug-in now fetches the REAL headers at build time instead, the same way it fetches the SR SDK: - NEURD_SDK_REF (v0.4.6, a media_sdk release tag; headers byte-identical to dev@25a713d93) + NEURD_SDK_REPO pinned in scripts/build-windows.bat and build-windows.yml, kept equal by check_sr_pins.py. - scripts/fetch-neurd-headers.ps1 pulls NeurD.h, _NeurD_detail.h, _NeurD_table.h via gh (CI: LEIALOFT_GITHUB_TOKEN) and generates NeurD_version.h from NeurD_version.h.in + the ref's project VERSION, into the gitignored NeurD-SDK-<ref>/include. - Fetch failure is SOFT everywhere: CMake compiles the lift module out (DXR_LEIA_HAS_NEURD undefined), the DP's lift slots stay NULL, the runtime reports lift unavailable, the rest of the plug-in is unchanged. - leia_lift_neurd.cpp still loads NeurD.dll dynamically (GetProcAddress NeurD_load, PFN_NeurD_load) and calls through the returned table using the real header's version-gated inline wrappers — no import lib, and any NeurD signature drift is now a compile error. - docs/lift-neurd.md + CLAUDE.md: private-repo access prerequisite, pins, soft-failure behaviour, win-box build steps. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
- create_dp_d3d11_lift (xrt_plugin.h, ADR-042): a DP with NO SR weaver, window, tracker or lens control — only the NeurD lift slots; every other slot NULL. Filled when the iface header has XRT_PLUGIN_IFACE_HAS_D3D11_LIFT_FACTORY and the NeurD headers were fetched; otherwise NULL (runtime falls back). - convergence is now the runtime's relative depth in [0,1] (<0 = auto), mapped to NeurD's [-0.2,0.2] as K*(c-0.5), K = DXR_LEIA_LIFT_CONV_GAIN (default 0.4; uncalibrated, calibration recipe in docs/lift-neurd.md). - focal_px (appended) ignored; lift_convert_blob stays NULL. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…EADY — the only place NeurD says why init failed was dropped at the default log level
… device fallback NeurD 0.3.12 is the officially supported 2D->3D line but lacks NeurD_get_dx_device (introduced 0.4.3), which the lift module hard-gated on. ABI facts (media_sdk tags v0.3.12 / v0.4.6): - The function table (_NeurD_table.h) is append-only: 0.3.12's 27 entries are an exact prefix of 0.4.6's 36. NeurD_load in 0.3.12 does no version check and returns its table with version = 0.3.12, so a plug-in built against the 0.4.6 header loads it, and LEIA_NEURD_HAS (version test first) correctly reports the 0.4.x entries absent. - 0.3.12 fills create_stream, destroy_stream, convert_stream_dx, set_backend, get_backend, shrink_memory_pool, set_logger_callback, set_prop_1i/1f, init. Changes: - activation gate: drop get_dx_device; need 0.3.11+ (the stream API). - setup_nd_device_locked: use get_dx_device when present; otherwise create the plug-in's own D3D11 device on the default adapter (as NeurD's example does), flagged nd_dev_owned so the setup-failure path Releases it (release_nd_dev_locked). One WARN names the case + LUID. - caps: drop NVIEW once READY on a NeurD without interactive convert (< 0.4.5); interactive_unavailable is now atomic since caps reads it lock-free. Caveat: in 0.3.12, media_sdk_convert_dx is compiled only under USE_TRT (CUDA): it GetDevice()s the caller's input buffer, binds CUDA-D3D11 interop to that device's adapter, and returns an R8G8B8A8_UNORM texture on the same device — which the existing stage_output texture branch already copies into the bridge. The DirectML build's DX path is unverified, so on 0.3.x the service must run with DXR_LEIA_LIFT_BACKEND=cuda, and the default adapter must be the NVIDIA GPU. 0.3.x is SBS/DEPTH only, default models (NeurD_init). Documented in docs/lift-neurd.md. Not compiled or run on Windows yet. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…or the 0.4.4 internal-interactive NeurD package The NeurD-Runtime-0.4.4-internal-interactive package reports version 0.4.4 but carries convert_stream_dx_interactive. In media_sdk the interactive API landed in 5b4faa8 (NEURDW-80) as a 35-entry table that is an exact prefix of 0.4.6's, with the entries tagged 0.4.5; neither the v0.4.4 nor the v0.4.5 tag has them, so this is a dev-branch build and the header's 0.4.5 INTRODUCED_IN blocks it. Only NeurD_load is exported, so GetProcAddress cannot discriminate, and the slot cannot be probed (a stock 0.4.4 table has 31 entries; index 33 is past it). Hence an explicit, demo-only opt-in: DXR_LEIA_LIFT_INTERACTIVE_MIN=0.4.4 (service env; clamped to >= 0.4.4) When set, the interactive availability test uses it instead of NEURD_convert_stream_dx_interactive_INTRODUCED_IN, with one WARN at activation. On a stock 0.4.4 this reads past the table and crashes — the WARN and docs say so. The NEURD_UNAVAILABLE_OUTDATED_RUNTIME fallback in the convert path is unchanged; no other gate is touched. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…s larger=farther; flip in the R8 unpack Panel-verified: DEPTH results came out inverted downstream (background in front in the browser's explore mode). The plug-in reports depth_semantics 0 (RELATIVE), which XR_DXR_lift defines as "larger = FARTHER, no unit", but NeurD's DEPTH output is disparity-like, near = HIGH (measured: foreground mean 236, background 1 in the R8 result). Consumers trusting the label (browser SendDepth flips RELATIVE to nearness) therefore inverted it. Flip at the bridge: kUnpackR8Cs now writes 1 - v into the R8_UNORM UAV, so the result obeys the spec. SBS/N-view unpack untouched. The convergence map uses the runtime's RELATIVE parameter, not NeurD's texture, so it is unaffected; nothing else in the plug-in reads the DEPTH texture. Known gap: the texture-result branch of stage_output (only NeurD 0.3.x's CUDA DX path) copies DEPTH as-is and is NOT flipped; commented + documented. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…y — the header's inline wrapper re-checks 0.4.5 Panel result on bdb0e0f: the override activated ("interactive viewpoints available"), but the first convert logged "NeurD interactive convert unavailable" and N-view failed 5/5. The v0.4.6 header's inline NeurD_convert_stream_dx_interactive does its own NEURD_FUNC_IS_UNAVAILABLE check (version >= 0.4.5) before touching the slot, so a 0.4.4-reporting runtime got NEURD_UNAVAILABLE_OUTDATED_RUNTIME and the plug-in latched interactive_unavailable. Add nd_convert_stream_dx_interactive(): when the override admitted a runtime older than NEURD_convert_stream_dx_interactive_INTRODUCED_IN (g.interactive_direct_slot, set at activation) it calls nd->convert_stream_dx_interactive(stream, input, xyz, size, fmt, output) directly — the PFN signature from _NeurD_detail.h, i.e. the wrapper's arguments minus nd; otherwise the wrapper, so the OUTDATED_RUNTIME latch on the normal path is unchanged. Docs: the runtime polls lift_get_caps only until READY, so a convert-time latch does not remove NVIEW from the runtime's cached caps — known limit. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
… NVIEW from caps, derive view_count from the output Panel result on bd6b52b: an N-view probe (--views 4) came back as a plain 2-tile SBS (4096x1362) stamped view_count=4. In media_sdk (v0.4.6, same as the dev build) Config::adjust() runs on every convert via adjust_cfg_for_image() — including the interactive path — and re-derives tiled_cols/rows from output_type (SBS 2x1, TB 1x2, DEPTH 1x1), and NeurD_set_prop_1i(OUTPUT_TYPE) accepts only 0..2. So OUTPUT_TILES_W/H are dead through the public API and N > 2 tiles per convert is impossible until a tiled output type exists. - caps: never advertise NVIEW (pre-READY or READY). - stream_create(NVIEW): refused with a WARN naming the reason. - convert: check NeurD's actual layout (output aspect / input aspect = tile count) against the mode's expected tiles and drop a mismatching frame, so a result can never be delivered under the wrong label. - Interactive SBS viewpoint path unchanged. - docs: N-view section + asks for Leia media_sdk. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…ill wins) — a respawned service no longer loses them silently The lift knobs lived only in displayxr-service.exe's environment. Any respawn (tray relaunch, HKLM Run at logon, crash restart) starts the service with the logon env, and the knobs vanished silently — the only tell was "interactive viewpoints UNAVAILABLE". Registry gates, not env vars. - Each knob now resolves env > HKLM\SOFTWARE\DisplayXR\Leia\Lift (REG_SZ, opened with KEY_WOW64_64KEY) > default. Values: Backend, InteractiveMin, Scale, ViewGain, ConvGain — same grammar as DXR_LEIA_LIFT_BACKEND / _INTERACTIVE_MIN / _SCALE / _VIEW_GAIN / _CONV_GAIN. DXR_LEIA_LIFT (the kill switch) stays env-only. - One parser per knob shared by both sources (knob_lookup returns the raw UTF-8 string + its source; parse_backend/parse_scale/parse_float_in/ parse_interactive_min own the grammar). InteractiveMin is now read with the other knobs at DP create instead of ad hoc at activation. - kick() takes the first acquirer's knobs; activation logs ONE WARN with every knob's effective value and source. - scripts/set-lift-knobs.bat (elevated): Backend=directml + InteractiveMin=0.4.4, --clear deletes the key. - docs: Registry value column, why, exact reg add lines. No behaviour change with no key and no env. The gain knobs now parse strictly (strtof, whole string) where atof accepted trailing junk. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
DXR_LEIA_LIFT_VIDEO_MODEL / HKLM\SOFTWARE\DisplayXR\Leia\Lift VideoModel: 'fast' (default, NEURD_MODEL_VIDEO_RELATIVE_FAST, unchanged) or 'metric' (NEURD_MODEL_VIDEO_METRIC_QUALITY) for NeurD_init_with_options' video slot, so the metric real-time model can be A/B'd for fps and quality without a rebuild. Photo streams keep the relative photo model (the only one that slot accepts). The activation knobs WARN reports it with its source. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… default) SBS only sends viewpoints when the eyes are tracked (or given explicitly); otherwise NeurD renders its default pattern and DXR_LEIA_LIFT_VIEW_GAIN is never applied. A ViewGain A/B run with nobody in front of the tracker therefore produced byte-identical output on NeurD 0.4.6 and read as 'the interactive path ignores our viewpoints'. One WARN per change of source makes the tell explicit. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…pthGain NeurD's GAIN_MULTIPLIER 1 read as too weak on the Leia panel; strength 2 (set live via dxr-lift-strength) was judged right as the default (David, 2026-09-26). The element's strength keeps meaning 1 = nominal, and the calibration lives here with NeurD's units: DXR_LEIA_LIFT_DEPTH_GAIN / HKLM\SOFTWARE\DisplayXR\Leia\Lift DepthGain (default 2.0), reported in the activation knobs WARN. (ViewGain is not a depth control: NeurD bounds the viewpoint excursion by its own disparity budget, so gain 2 = gain 1.) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…lax)
leia_lift_neurd_map_viewpoint mapped the eye y offset into NeurD's
viewpoint, so with the viewer above the display centre NeurD rendered the
lifted frame as seen from above: the image shifted vertically with head
height ('the converted asset jumps up/down') and a mirror-filled band
appeared at the top edge. Weave dumps on the panel: both eyes shifted
+7 px (top) / -7 px (bottom) vs the element, where the untracked probe
shows NeurD's own +/-4 px. A lenticular panel has horizontal parallax only,
so y is zeroed. (x keeps the eye midpoint = horizontal look-around.)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
NEURD_PROP_DILATE_RADIO was never set, so NeurD dilated the disparity map by its default 3 px and the foreground visibly grew past its silhouette on the panel (David, 2026-09-26: 'the disparity map dilation is a bit too much, reduce a bit'). Now set every convert (cached like the other props) from DXR_LEIA_LIFT_DILATE / HKLM\SOFTWARE\DisplayXR\Leia\Lift Dilate, default 2; reported in the activation knobs WARN. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…3D11 lift slots The NeurD lift module compiles only against runtime headers that define XRT_DP_D3D11_HAS_LIFT (runtime#1781, released in v2.22.0). The slots are appended and struct_size-gated, so the plug-in ABI stays v5; the installer's derived minimum-runtime floor moves to 2.22.0. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
dfattal
marked this pull request as ready for review
October 1, 2026 21:49
dfattal
force-pushed
the
feat/lift-main
branch
from
October 1, 2026 21:49
d41ec57 to
f86015d
Compare
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.
NeurD-backed 2D→3D lift on the D3D11 display processor
Lands the plug-in half of
XR_DXR_lift(runtime PR DisplayXR/displayxr-runtime#1781, ADR-042). 20 lift commits, rebased ontomain, plus the Windows runtime re-pin.What it adds
leia_lift_neurd.{cpp,h}: the five D3D11 DP lift slots and a lift-only DP factory, backed by a dynamically loadedNeurD.dll(located viaHKLM\SOFTWARE\LeiaInc\NeurD). NeurD absent ⇒ caps UNAVAILABLE and nothing is loaded.HKLM\SOFTWARE\DisplayXR\Leia\Lift(env wins): Backend (default directml), VideoModel, DepthGain (default 2.0), Dilate (default 2), InteractiveMin, ViewGain, ConvGain.LeiaInc/media_sdkatNEURD_SDK_REF(v0.4.6); nothing private is committed. Without them the plug-in builds without lift.docs/lift-neurd.md.Compatibility
struct_size-gated, so this DLL loads on older runtimes (they never read the slots).XRT_DP_D3D11_HAS_LIFT, so the Windows pin moves from v2.16.9 to v2.22.0 (the release carrying runtime#1781). The installer's derived minimum-runtime floor becomes 2.22.0.LEIALOFT_GITHUB_TOKENcannot readLeiaInc/media_sdk(404), so the GitHub-hosted build compiles lift OUT (warning only). The signing runner can read it: a signed build of this branch (codesign-runner run 36930017941) carrieslift slots WIRED. Releases ship the signed installer; fixing the token is a follow-up.Verification
lift slots WIRED;displayxr-cli selftestPASS incl.lift_caps(neurd-directml),lift capsREADY with NeurD 0.4.6.lift_convertis wrapped).🤖 Generated with Claude Code