Skip to content

fix(weave/linux): ignore a stale-size v4 overlay instead of stretching it (XR_DXR_weave v17) - #1837

Merged
dfattal merged 1 commit into
mainfrom
fix/weave-linux-stale-overlay
Oct 7, 2026
Merged

dfattal merged 1 commit into
mainfrom
fix/weave-linux-stale-overlay

Conversation

@dfattal

@dfattal dfattal commented Oct 6, 2026

Copy link
Copy Markdown
Collaborator

Problem

During a resize, a present-owner keeps sending its old overlay buffer for a frame or more after the window has changed size. That buffer is still the old window's size, while the input and the woven output already have the new size. On the DS1, the browser's whole-page v4 overlay showed a black border and black 2D content on maximise and unmaximise.

Where the black margin comes from: the runtime's own post-weave blend, not the display processor. The DP offer (set_overlay_2d) only takes a layer that is exactly the output size. A mismatched layer is never offered to the DP. It falls through to the runtime's v4 overlay atlas post-weave blend (vk_local2d_composite_flatten_draw), which draws the smaller layer over the whole output as if it were window-sized.

The stage-A OPAQUE_FD path had a worse version of the problem. It imported the overlay at the window's size (have_geometry ? win_w : in_w) whatever the buffer's real size was:

Fix (comp_multi_weave_linux.c)

  • A mismatched overlay counts as absent for that frame. It gets no DP offer, no post-weave blend and no off-panel or flat-region redraw. The content is woven normally.
  • The submit is accepted, not refused. Refusing would hold frames through every resize. fix(weave/linux): an overlay that cannot be imported refuses the submit (XR_DXR_weave v16) #1825's rule is unchanged: a submit is refused only when its overlay cannot be imported.
  • Logging: one WARN when the overlay goes stale and one when it matches again:
    weave: stale overlay 960x540 vs output 1280x720 — ignored until it matches
    weave: overlay 1280x720 matches the output again — composited (2 frame(s) ignored)
    
  • Window size used as the reference:
    • batch submit: the input size;
    • otherwise: the bound geometry;
    • legacy single rect without geometry: rect offset + extent;
    • v6 without geometry: no reference, so the overlay is not checked. The v6 output is one content view, so it cannot serve as the reference.
  • v14 "layer unchanged": after a frame whose overlay was ignored, the next overlay offered to the DP is sent with layer_unchanged = false, because the DP's cached layer is older than the ignored frames. The DP contract is already stateless per frame (no set_overlay_2d call means no layer), so an ignored frame never composites the cached layer.

What the handle path (stage A) carries

No size at all. XrWeaveSubmitOverlaysDXR has no extent field and an fd carries no dimensions. Adding a field to ipc_arg_weave_submit would not help, because the app-facing stage-A call has nothing to fill it from. So the wire is unchanged, and tests_ipc_proto and the IPC size accounting are untouched. The engine recovers the size another way:

  • Identity: stage-A slots are keyed by the fd's inode, and the import cache keeps the allocation alive. An inode already imported at W1×H1 is therefore a W1×H1 buffer, because an allocation never changes size. This is exact for every buffer the engine has seen.
  • Allocation size: an fd the engine has never seen that is smaller than W×H×4 bytes cannot hold a window-sized BGRA8 image in any tiling.
  • Not covered: a never-seen stage-A buffer that is larger than the window cannot be detected. The spec documents this; a caller that needs that case covered uses dma-buf, whose descriptor declares the size.

The dma-buf path compares the descriptor's declared width × height and does not import a mismatched overlay at all.

Spec: XR_DXR_weave v17 (no new structure)

v16 told a caller whose overlay is its whole 2D that "accepted with an overlay chained" means "composited", and such a caller gates on that. v17 qualifies the guarantee: it holds only for a window-sized overlay. A mismatched overlay is now dropped (woven without it, still accepted). Older desktop-Linux runtimes stretch it instead. Callers need to tell those two behaviours apart, so this is a version bump rather than a silent change. The update is in the spec doc (§5d and the version history) and the header comment. The doc header's Spec Version field was stale at 13 and is now 17. The macOS, Android and D3D11 engines are unchanged.

Test

weave_probe_vk_linux now runs the resize-race case in both runs. Everything was headless on sim_display: DXR_PLUGIN_EXCLUSIVE=sim-display (active plug-in confirmed in the service log), with a private runtime-dir socket. The DS1 panel and the installed displayxr.socket were not used.

The stale overlay is opaque green everywhere, so any composite of it shows up at every probe point.

run step this branch main
stage A never-seen 960x540 under 1280x720 (allocation path) ignored, accepted REFUSED
stage A then the matching 1280x720 overlay magenta bar back —
stage A cached 1280x720 under 1600x900 (identity path) ignored, accepted —
stage A matching 1600x900, then back to 1280x720 composited —
dma-buf 2 frames with a declared 960x540 overlay ignored, accepted opaque green over the whole output (stretched)
dma-buf then the matching overlay, declared "unchanged" magenta bar back —

🤖 Generated with Claude Code

…g it (XR_DXR_weave v17)

In a resize race a present-owner keeps handing its old overlay buffer, still
the OLD window's size, for a frame or more after the window (input, woven
output) changed size. Seen on the DS1 with the browser's whole-page overlay as
a black border and black 2D on maximise / unmaximise.

The black margin is the runtime's own post-weave blend, not the display
processor: the DP offer (set_overlay_2d) is gated on the layer being exactly
the output size, so the mismatched layer fell through to the v4 post-weave
blend, which drew it over the whole output as if it were window-sized. The
stage-A OPAQUE_FD path was worse: it imported the overlay at the window's size
whatever the buffer's real size (a smaller allocation failed the import and,
since #1825, refused the submit; a larger one was read with the wrong layout).

Both submit paths now treat an overlay whose size is not this submit's window
size as ABSENT for that frame: no DP offer, no post-weave blend, no off-panel /
flat-region redraw, content woven normally. The submit is accepted, not
refused (refusing would hold frames through every resize); #1825's refusal of
an overlay that cannot be imported is unchanged. One WARN on entering the
stale state, one when the overlay matches again.

Window size: the input for a batch submit, else the bound geometry (legacy
without geometry: rect offset + extent; v6 without geometry: unchecked).

Real overlay size:
- dma-buf: the descriptor's declared width x height (not imported if stale).
- stage A: nothing carries a size (XrWeaveSubmitOverlaysDXR has no extent, an
  fd has none), so a wire field would have nothing to fill it from. The engine
  recovers it instead: an inode it already imported IS that import's size
  (the cache keeps the allocation alive; allocations never change size), and
  an unseen fd smaller than W x H x 4 bytes cannot be window-sized. An unseen
  stage-A buffer LARGER than the window remains undetectable (documented).

v14: a frame whose overlay was ignored forces layer_unchanged = false on the
next overlay offered to the DP, whose cached layer predates the ignored frames.

XR_DXR_weave SPEC_VERSION 17, no new structure: it qualifies v16's "accepted
with an overlay chained means composited" for a caller whose overlay is its
2D, which needs to know whether a mismatched overlay is dropped (v17) or
stretched (older desktop-Linux runtimes). Spec version field in the doc
header brought up from a stale 13.

weave_probe_vk_linux gains the case in both runs, headless on sim_display
(DXR_PLUGIN_EXCLUSIVE=sim-display, active plug-in confirmed in the log,
private runtime dir socket):
- stage A: never-seen 960x540 overlay under 1280x720 (allocation path),
  cached 1280x720 overlay under 1600x900 (identity path), each followed by
  a matching overlay: ignored -> composited, no refusal. PASS.
- dma-buf: two frames with a declared 960x540 overlay, then the matching
  one declared "unchanged": ignored -> composited, no refusal. PASS (also
  --linear); fd counts flat over 600 frames.
- main (negative control): dma-buf step 0 reads opaque green (the stale
  layer stretched over everything); stage A step 0 is REFUSED. FAIL.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
@dfattal
dfattal requested a review from a team as a code owner October 6, 2026 17:55
@dfattal

dfattal commented Oct 6, 2026

Copy link
Copy Markdown
Collaborator Author

Cross-session review (browser-linux): no blocking findings. Treating a size-mismatched overlay as absent for the frame (no DP offer, no post-weave blend, submit still accepted, one WARN per transition) is the right contract — it is exactly the failure the first Linux browser panel run hit (an overlay staged at the pre-resize size blended as if window-sized → black margin; see browser-pvt#238's panel-run-1 notes). The v14 reset after an ignored run is correct: the DP's cached layer predates it. The handle-path inference (first-import size kept exact; smaller allocation caught; larger undetectable) is acceptable since the browser uses dma-buf only, and the spec says so. Spec v17 bump is warranted. From the browser side: the Linux page mode in #238 already validates the overlay against the frame's window size in Viz and in the provider before sending, so it never relies on this drop; I will gate nothing on v17 but will document that an older runtime stretches a mismatched overlay.

@dfattal
dfattal merged commit c5f5b69 into main Oct 7, 2026
42 of 43 checks passed
@dfattal
dfattal deleted the fix/weave-linux-stale-overlay branch October 7, 2026 06:56
dfattal added a commit that referenced this pull request Oct 7, 2026
…refuses (XR_DXR_weave v18)

Two gaps in the Android weave engine's v4 overlay handling, found while
porting the browser's whole-page overlay (browser-pvt#226) to Android:

1. A cached overlay was a STANDING overlay. The engine caches the overlay
   import by AHardwareBuffer identity (the caller re-sends the same buffer
   every frame), and composited mc->weave.overlay_image whenever it was set
   - so after one submit that chained an overlay, every later submit was
   composited with it, chained or not. The browser's whole-page overlay
   switches itself off while the page scrolls (its 0351 safety floor); on
   those frames the last page it sent would be composited over a page that
   has moved. The import is still cached (no re-import for the same
   buffer), but the overlay is now composited only on a submit that chains
   one (mc->weave.overlay_this_submit).

2. An overlay that could not be imported was DROPPED and the frame woven
   anyway, returning success. A caller that hands its whole 2D over as the
   overlay reads "accepted with the overlay chained" as "composited" and
   presents the woven output as the complete frame. The submit is now
   refused (XRT_ERROR_WEAVE_REFUSED -> XR_ERROR_RUNTIME_FAILURE) before
   anything is recorded, so the woven output is left exactly as the
   previous accepted submit left it: the desktop-Linux v16 rule (#1825) and
   the D3D11 service's. One WARN on the first refusal, then 1 in 300.

XR_DXR_weave SPEC_VERSION 18: no new structure, the Android gate for the
one kind of caller that needs it (overlay == its 2D), exactly as v16 is on
desktop Linux. The macOS engine still drops an unreadable overlay.

Callers whose overlay decorates content they present anyway (0099's
over-plane mode) see no difference except on a frame that chains no
overlay, which no longer shows the previous frame's.

Not tested on hardware: the NP02J is off the bus right now. Compiled by CI
(build-android). The branch feat/android-overlay-2d-dp (DP-side overlay
offer) touches the same block; its offer must also require
overlay_this_submit once both land.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

Renumbered v17 -> v18 on rebase: main took v17 for the desktop-Linux stale-size
overlay rule (#1837).
dfattal added a commit that referenced this pull request Oct 7, 2026
…refuses (XR_DXR_weave v18) (#1835)

Two gaps in the Android weave engine's v4 overlay handling, found while
porting the browser's whole-page overlay (browser-pvt#226) to Android:

1. A cached overlay was a STANDING overlay. The engine caches the overlay
   import by AHardwareBuffer identity (the caller re-sends the same buffer
   every frame), and composited mc->weave.overlay_image whenever it was set
   - so after one submit that chained an overlay, every later submit was
   composited with it, chained or not. The browser's whole-page overlay
   switches itself off while the page scrolls (its 0351 safety floor); on
   those frames the last page it sent would be composited over a page that
   has moved. The import is still cached (no re-import for the same
   buffer), but the overlay is now composited only on a submit that chains
   one (mc->weave.overlay_this_submit).

2. An overlay that could not be imported was DROPPED and the frame woven
   anyway, returning success. A caller that hands its whole 2D over as the
   overlay reads "accepted with the overlay chained" as "composited" and
   presents the woven output as the complete frame. The submit is now
   refused (XRT_ERROR_WEAVE_REFUSED -> XR_ERROR_RUNTIME_FAILURE) before
   anything is recorded, so the woven output is left exactly as the
   previous accepted submit left it: the desktop-Linux v16 rule (#1825) and
   the D3D11 service's. One WARN on the first refusal, then 1 in 300.

XR_DXR_weave SPEC_VERSION 18: no new structure, the Android gate for the
one kind of caller that needs it (overlay == its 2D), exactly as v16 is on
desktop Linux. The macOS engine still drops an unreadable overlay.

Callers whose overlay decorates content they present anyway (0099's
over-plane mode) see no difference except on a frame that chains no
overlay, which no longer shows the previous frame's.

Not tested on hardware: the NP02J is off the bus right now. Compiled by CI
(build-android). The branch feat/android-overlay-2d-dp (DP-side overlay
offer) touches the same block; its offer must also require
overlay_this_submit once both land.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

Renumbered v17 -> v18 on rebase: main took v17 for the desktop-Linux stale-size
overlay rule (#1837).
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant