Skip to content

Untracked weave outputs transparent black (~22 frames every ~1983 ms) when the weave runs on the Intel scanout adapter #178

Description

@dfattal

Runtime-side counterpart: DisplayXR/displayxr-runtime#1134 (full evidence chain there).

What the runtime observes

On a hybrid box (render = NVIDIA dGPU, scanout = Intel iGPU), when the runtime's output-device
split (DXR_WEAVE_ON_SCANOUT=1) puts the weave on the scanout adapter, and no face is
tracked
, leia_dp_d3d11_process_atlas writes a fully (0,0,0,0) transparent-black output for
~22 consecutive frames every ~1983 ms — ~366 ms of black every 2 s.

On the render adapter (split off) the same untracked state is rendered correctly, every frame.
So this is adapter-dependent, not a general untracked-path defect.

What the runtime can rule out

The runtime instrumented the call site (DXR_SPLIT_COVER_DIAG, DisplayXR/displayxr-runtime@d627a9edb)
and can state the following about the frames that come out black:

  • The DP covers the target. The compositor clears the back buffer to magenta immediately before
    process_atlas; on the black frames not one of five sampled points is still magenta. The DP is
    not returning early — it draws, and what it draws is (0,0,0,0).
  • The atlas handed in is correct, on the correct device. Dumped and inspected on a black frame:
    a clean 1280x360 2x1 stereo atlas, opaque, dark-navy background, cube present in both tiles.
  • Every parameter is invariant across the good/black boundary. Same tile_columns/tile_rows
    (2x1), same view dims (640x360), same output dims, same atlas encoding, same transparency mode
    (client_presents=false, opaque), same context and device.
  • set_frame_timing is not implicated: measured R = 15.8 ms on the black frames and on the
    good ones alike, i.e. the split does not inflate the prediction horizon here.
  • Predicted eyes are constant: get_predicted_eye_positions returns valid=1 count=2 with the
    pair fully collapsed, L = R = (0.000, 0.100, 0.600) — the plug-in's own untracked signature
    (is_tracking = (dx²+dy²+dz²) > 1e-6 → false) — and it does not move across the boundary.

So nothing the runtime supplies changes at that ~2 s period. The cycle is internal DP / SR SDK
state.

Discriminating hint

Because the inputs are invariant, the ~2 s cycle has to be driven from inside. Two suspicions,
neither verified:

  1. The untracked pulse / "seeking viewer" animation's own render pass silently failing on Intel
    — a shader, blend, viewport or state assumption that holds on NVIDIA and not on the iGPU. An
    output of exactly (0,0,0,0) rather than garbage smells like a pass that ran and wrote nothing
    but transparent, or a clear that was not followed by its draw.

  2. An adapter-dependent resource in the untracked path. Worth checking that everything the
    untracked branch touches is created on the device that is actually weaving. Two asymmetries were
    noticed while reading (both fine when the runtime passes a matching pair, but worth confirming
    under this configuration):

    • the SR weaver renders on the create-time cached sr->d3d11_context
      (leia_sr_d3d11.cpp CreateDX11Weaver / srCreateWeaverDX11), while the DP's own passes use
      the per-call context; leiasr_d3d11_weave takes no context parameter at all;
    • every lazily-created D3D11 resource uses ldp->device cached at factory time
      (leia_display_processor_d3d11.cpp), never ctx->GetDevice().

    The runtime does pass a matching device/context pair here (the DP is created with the split's
    out device and process_atlas is called with the same out context), so neither is wrong in
    this configuration — but both mean "which adapter" is decided once at create time, which is the
    shape of bug that shows up only on the second adapter.

Also noticed while reading, unrelated to this bug but against the repo's own invariant:
w_weave() (leia_sr_d3d11.cpp) calls the v1 weaver->weave() with no try/catch, on the
per-frame path, into a C caller that cannot catch. Same omission in the D3D12 arm.

Repro

Any scanout-split session, no face in front of the camera, ~2 s period:

set DXR_WEAVE_ON_SCANOUT=1
test_apps\build\bin\cube_handle_d3d11_win.exe          REM in-process
REM or, for the service path: start the service with the same var, then
REM set XRT_FORCE_MODE=ipc and launch the same exe

Confirm weave placement: ... weave/present on the SCANOUT adapter in the log, then step out of
the camera's view for ~30 s.

Needs a hybrid render/scanout box; on the reference machine the panel is driven by the Intel UHD
while rendering happens on the RTX 3080.

The runtime can help

DXR_SPLIT_COVER_DIAG=2 (service direct path) reports, per tick, whether the DP covered the target,
whether its input was black, whether its output was black, the predicted eyes, the measured R, and
burst edges with wall-clock deltas. DXR_WEAVE_PROBE=1 plus a %TEMP%\dxr_woven_trigger file dumps
the in-process back buffer exactly as the DP left it. Happy to add whatever else would narrow this
from the runtime side — including dumping the DP input and output on the same frame, which is
already wired.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions