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
8 changes: 7 additions & 1 deletion docs/adr/ADR-027-display-zones.md
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,13 @@ issues: [439, 396]
> - First producer: the `XR_DXR_weave` v4 overlay (`XrWeaveSubmitOverlaysDXR`) on the D3D11
> service's full-window weave paths (v3 batch, v6); the legacy single-rect path keeps the
> blit. Other APIs and the in-process compositors' Local2D-over path follow the same slot
> shape when needed. 2D-*under*-3D (`set_background_2d`) is unchanged.
> shape when needed. **Vulkan (2026-10):** the appended VK-variant slot
> `xrt_display_processor_vk::set_overlay_2d` (+ `set_overlay_2d_filter_strength`,
> `XRT_DP_VK_HAS_OVERLAY_2D[_FILTER_STRENGTH]`) carries the same contract with a
> `VkImageView` + `VkFormat`; the layer must be target-sized and in
> `SHADER_READ_ONLY_OPTIMAL` when process_atlas' command buffer executes. First producer: the
> desktop-Linux weave engine (`comp_multi_weave_linux.c`); off-panel bands and flat regions
> painted after the weave get the layer redrawn over them, so 2D stays readable across a seam. 2D-*under*-3D (`set_background_2d`) is unchanged.
> - Browser use (decided 2026-10): the layer is the **whole flat page** with the woven tiles
> as alpha-0 holes, not only the 2D lifted over tiles — the aliasing users saw was page text
> around the canvas. While any tile exists the browser submits every frame; a submit whose
Expand Down
12 changes: 7 additions & 5 deletions docs/specs/extensions/XR_DXR_weave.md
Original file line number Diff line number Diff line change
Expand Up @@ -230,8 +230,10 @@ typedef struct XrWeaveSubmitOverlayUnchangedDXR {
the viewer moves enough to matter.
- **Content-only.** The overlay is composited every frame either way; this only gates
re-deriving work from it.
- D3D11 service path today (`set_overlay_2d(..., layer_unchanged)`); the Vulkan weave engines
accept and ignore it until their DP slot exists.
- D3D11 service path (`set_overlay_2d(..., layer_unchanged)`) and the desktop-Linux Vulkan
weave engine (the appended VK DP slot `xrt_display_processor_vk::set_overlay_2d`, same
argument); the macOS / Android Vulkan weave engines accept and ignore it until they call that
slot.

### Filter strength (v15)

Expand All @@ -251,9 +253,9 @@ typedef struct XrWeaveSubmitOverlayFilterDXR {
vendor value tuned by eye on its panel (Leia SR: 0.6). The runtime passes "not set", never a
number of its own.
- `filterStrength` outside [0,1] or NaN is `XR_ERROR_VALIDATION_FAILURE`.
- Per submit, no latch. Carried to the DP by the appended D3D11 slot
`set_overlay_2d_filter_strength` (negative = default), called before `set_overlay_2d` each
frame the layer goes to the DP.
- Per submit, no latch. Carried to the DP by the appended slot
`set_overlay_2d_filter_strength` (D3D11, and the VK variant on desktop Linux; negative =
default), called before `set_overlay_2d` each frame the layer goes to the DP.



Expand Down
25 changes: 25 additions & 0 deletions src/xrt/compositor/multi/comp_multi_private.h
Original file line number Diff line number Diff line change
Expand Up @@ -935,6 +935,20 @@ struct multi_compositor
bool mono_in_2d;
bool mono_active;

/*!
* @name 2D under the lens (ADR-027 Amendment, XR_DXR_weave v14/v15)
* Per-submit overlay hints, set by comp_multi_weave_linux_set_overlay_hints
* right before each submit (never sticky), forwarded to the DP with the
* overlay when its set_overlay_2d slot takes it. @c overlay_in_dp is the
* last verdict (DP composites vs runtime post-weave blend) so the WARN
* fires on a change only.
* @{
*/
bool overlay_unchanged;
float overlay_filter_strength;
uint8_t overlay_in_dp; //!< 0 = no overlay seen yet, 1 = runtime blend, 2 = DP composites
//! @}

//! @name Flat regions (spec v8, browser#88, on desktop Linux)
//! The sticky screen-space latch (xrWeaveSetScreenFlatRegionsDXR),
//! absolute screen device px; a SET, count 0 clears. Unioned with
Expand Down Expand Up @@ -1572,6 +1586,17 @@ comp_multi_weave_linux_request_display_mode(struct multi_compositor *mc, bool en
*/
void
comp_multi_weave_linux_set_mono_in_2d(struct xrt_compositor *xc, bool mono_in_2d);

/*!
* XR_DXR_weave v14/v15 overlay hints of the NEXT submit (same per-submit shape
* as comp_multi_weave_linux_set_mono_in_2d): @p overlay_unchanged = the overlay
* holds the previous accepted submit's pixels; @p filter_strength = the app's
* lens-filter strength, [0,1], negative = the DP's default. Used only when the
* DP composites the overlay inside the weave (set_overlay_2d, ADR-027
* Amendment); the runtime's own post-weave blend ignores both.
*/
void
comp_multi_weave_linux_set_overlay_hints(struct xrt_compositor *xc, bool overlay_unchanged, float filter_strength);
/*! @} */
#endif // XRT_OS_LINUX_DESKTOP

Expand Down
124 changes: 105 additions & 19 deletions src/xrt/compositor/multi/comp_multi_weave_linux.c
Original file line number Diff line number Diff line change
Expand Up @@ -2126,6 +2126,43 @@ weave_run_frame(struct vk_bundle *vk,
skip_weave = true;
}

// 2D under the lens (ADR-027 Amendment): offer the v4 overlay to the DP so
// it composites the layer INSIDE the weave and band-limits it for the lens,
// instead of the post-weave blend below (which aliases per eye wherever the
// lens is on). Only when this frame weaves and the layer is exactly the
// output (= the process_atlas target) size — the slot's contract. The layer
// is taken from the producer and put in SHADER_READ_ONLY here, BEFORE the
// self-submitting split below, so it is ready when the DP's reads execute
// whichever command buffer they land in. A DP that declines (no slot, no SR
// compose, transparency, 2D content, ...) leaves the post-weave blend to do
// exactly what it did before; the layer is then already acquired.
struct xrt_display_processor_vk *dp_vk = (struct xrt_display_processor_vk *)mc->weave.dp;
bool ov_acquired = false; // ov taken from the producer and in SHADER_READ_ONLY_OPTIMAL
bool ov_in_dp = false; // the DP composites ov in this frame's process_atlas
if (ov != NULL && !skip_weave && ov->w == mc->weave.out_w && ov->h == mc->weave.out_h &&
xrt_display_processor_vk_supports_overlay_2d(dp_vk)) {
weave_acquire_slot(vk, cmd, ov, VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT, VK_ACCESS_SHADER_READ_BIT);
weave_layout_barrier(vk, cmd, ov->image, VK_IMAGE_LAYOUT_GENERAL,
VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL, 0, VK_ACCESS_SHADER_READ_BIT,
VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT, VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT);
ov->first_use = false;
ov_acquired = true;
// v15 strength first: the D3D11 order, and a DP may key its weave on both.
xrt_display_processor_vk_set_overlay_2d_filter_strength(dp_vk, mc->weave.overlay_filter_strength);
ov_in_dp =
xrt_display_processor_vk_set_overlay_2d(dp_vk, ov->view, (VkFormat_XDP)ov->format, ov->w, ov->h,
XRT_ATLAS_ENCODING_ENCODED, mc->weave.overlay_unchanged);
}
if (ov != NULL) {
const uint8_t verdict = ov_in_dp ? 2u : 1u;
if (verdict != mc->weave.overlay_in_dp) {
mc->weave.overlay_in_dp = verdict;
U_LOG_W("weave: 2D overlay (%ux%u) %s", ov->w, ov->h,
ov_in_dp ? "composited by the display processor INSIDE the weave (2D under the lens)"
: "blended post-weave by the runtime (the DP did not take it this frame)");
}
}

// SELF-SUBMITTING DP ORDERING (Android #1036's one-frame trail fix): a
// DP that submits its own batch during process_atlas would otherwise
// execute BEFORE this frame's blits (still unsubmitted in cmd). Flush
Expand Down Expand Up @@ -2233,9 +2270,9 @@ weave_run_frame(struct vk_bundle *vk,
// input is window-sized in register with the output; otherwise (v6 /
// legacy, where the input holds views, not a page) the flat view the
// off-panel bands use.
struct u_wl_rect_px flat[WEAVE_FLAT_MAX_RECTS];
const uint32_t flat_count = weave_flat_rects(mc, nview, flat_rect_count, flat_rects, flat);
{
struct u_wl_rect_px flat[WEAVE_FLAT_MAX_RECTS];
const uint32_t flat_count = weave_flat_rects(mc, nview, flat_rect_count, flat_rects, flat);
if (flat_count > 0) {
const bool page_source = !nview && rect_count > 0;
if (page_source) {
Expand Down Expand Up @@ -2269,16 +2306,34 @@ weave_run_frame(struct vk_bundle *vk,

// v4 overlay atlas (browser#18): composite the caller's window-sized
// premultiplied 2D atlas OVER the woven output — not woven, drawn after
// process_atlas onto the same attachment.
// process_atlas onto the same attachment. When the DP already composited it
// inside the weave (ov_in_dp), only the off-panel bands and flat regions
// painted over the weave above need it again: those were cleared and
// re-painted flat, so the layer is redrawn there (and only there), keeping
// 2D readable on both sides of a seam exactly as before.
if (ov != NULL) {
if (weave_blend_begin(vk, mc, &blend_begun)) {
weave_acquire_slot(vk, cmd, ov, VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT,
VK_ACCESS_SHADER_READ_BIT);
weave_layout_barrier(vk, cmd, ov->image, VK_IMAGE_LAYOUT_GENERAL,
VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL, 0, VK_ACCESS_SHADER_READ_BIT,
VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT,
VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT);
ov->first_use = false;
struct u_wl_rect_px redo[ARRAY_SIZE(offpanel) + WEAVE_FLAT_MAX_RECTS];
uint32_t redo_count = 0;
if (ov_in_dp) {
for (uint32_t i = 0; i < offpanel_count && redo_count < ARRAY_SIZE(redo); i++) {
redo[redo_count++] = offpanel[i];
}
for (uint32_t i = 0; i < flat_count && redo_count < ARRAY_SIZE(redo); i++) {
redo[redo_count++] = flat[i];
}
}
const bool draw = ov_in_dp ? redo_count > 0 : true;
if (draw && weave_blend_begin(vk, mc, &blend_begun)) {
if (!ov_acquired) {
weave_acquire_slot(vk, cmd, ov, VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT,
VK_ACCESS_SHADER_READ_BIT);
weave_layout_barrier(vk, cmd, ov->image, VK_IMAGE_LAYOUT_GENERAL,
VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL, 0,
VK_ACCESS_SHADER_READ_BIT, VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT,
VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT);
ov->first_use = false;
ov_acquired = true;
}

weave_layout_barrier(vk, cmd, mc->weave.out_image, VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL,
VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL,
Expand All @@ -2287,14 +2342,29 @@ weave_run_frame(struct vk_bundle *vk,
VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT,
VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT);

vk_local2d_composite_flatten_draw(&mc->weave.overlay_blend, vk, cmd, mc->weave.out_fb,
mc->weave.out_w, mc->weave.out_h,
ov->view, //
0, 0, mc->weave.out_w,
mc->weave.out_h, // dst = full window
0.0f, 0.0f, 1.0f, 1.0f, // src = whole atlas
/*unpremultiplied*/ false);

if (!ov_in_dp) {
vk_local2d_composite_flatten_draw(&mc->weave.overlay_blend, vk, cmd, mc->weave.out_fb,
mc->weave.out_w, mc->weave.out_h,
ov->view, //
0, 0, mc->weave.out_w,
mc->weave.out_h, // dst = full window
0.0f, 0.0f, 1.0f, 1.0f, // src = whole atlas
/*unpremultiplied*/ false);
} else {
// The layer is output-sized (checked before it went to the DP),
// so each rect samples it 1:1 at its own position.
for (uint32_t i = 0; i < redo_count; i++) {
const struct u_wl_rect_px r = redo[i];
vk_local2d_composite_flatten_draw(
&mc->weave.overlay_blend, vk, cmd, mc->weave.out_fb, mc->weave.out_w,
mc->weave.out_h, ov->view, r.x, r.y, (uint32_t)r.w, (uint32_t)r.h,
(float)r.x / (float)ov->w, (float)r.y / (float)ov->h,
(float)r.w / (float)ov->w, (float)r.h / (float)ov->h,
/*unpremultiplied*/ false);
}
}
}
if (ov_acquired) {
weave_layout_barrier(vk, cmd, ov->image, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL,
VK_IMAGE_LAYOUT_GENERAL, VK_ACCESS_SHADER_READ_BIT, 0,
VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT,
Expand Down Expand Up @@ -2885,6 +2955,22 @@ comp_multi_weave_linux_set_mono_in_2d(struct xrt_compositor *xc, bool mono_in_2d
os_mutex_unlock(&mc->weave.mutex);
}

void
comp_multi_weave_linux_set_overlay_hints(struct xrt_compositor *xc, bool overlay_unchanged, float filter_strength)
{
struct multi_compositor *mc = multi_compositor(xc);
if (mc == NULL || mc->msc == NULL) {
return;
}
weave_ensure_mutex(mc);
os_mutex_lock(&mc->weave.mutex);
mc->weave.overlay_unchanged = overlay_unchanged;
// Out of range (incl. NaN) = not set by the app: the DP's own default.
mc->weave.overlay_filter_strength =
(filter_strength >= 0.0f && filter_strength <= 1.0f) ? filter_strength : -1.0f;
os_mutex_unlock(&mc->weave.mutex);
}

void
comp_multi_weave_fini(struct multi_compositor *mc)
{
Expand Down
Loading
Loading