diff --git a/docs/architecture/comp-segments.md b/docs/architecture/comp-segments.md index f151139585..6cd55bf44e 100644 --- a/docs/architecture/comp-segments.md +++ b/docs/architecture/comp-segments.md @@ -153,6 +153,35 @@ or destroy is one WARN. - Capture: with the window split, the post-compose atlas capture also writes each segment's DP input as `.seg.png`. +## Windows / D3D11 (M6) + +The in-process D3D11 compositor carries the same model in `d3d11/comp_d3d11_segments.{h,cpp}`, +a line-for-line twin of the Vulkan manager with the GPU work in D3D11 terms +(`CopySubresourceRegion` crops, `ClearRenderTargetView`, a renderer rect blit for flat 2D, no +deferred-release list because the immediate context executes in order). What differs on Windows: + +- **Two appended ABI slots, no version bump** (ADR-020): `xrt_plugin_iface::create_dp_d3d11_for_screen` + (the D3D11 twin of the Vulkan per-screen factory) and `xrt_display_processor_d3d11::set_present_origin`. + A plug-in without them gets flat 2D on its other screens, exactly like Vulkan. +- **The window.** The session's primary DP keeps the real HWND and so the vendor's drag phase-snap; + every other screen's DP is created with a NULL window and phases from `set_present_origin` + the + canvas offset. The vendor weaver reads the current D3D11 viewport **and scissor** and writes only + inside them, so the segment path sets both to the segment before every `process_atlas`, and the + back buffer is re-bound before every DP. (ADR-047's "HWND follows the majority segment" is a + follow-up: today the HWND stays with the primary screen's DP.) +- **Units.** The window rect comes from `ClientToScreen` in a per-monitor-DPI-aware process, i.e. + device px — the same space as the registry's monitor rects. A DPI-virtualised origin would weave on + the wrong lattice and look plausible (`docs/reference/dpi-awareness.md`). +- **The untracked screen.** The SR tracker follows the runtime's active display only, so a weaver + bound to any other display sees no face and would fall back to 2D. The Leia plug-in pins that + weaver to its nominal viewer and leaves the lens to the active panel (David, 2026-10-07; SR D3/D4 + move both per device later). The runtime does nothing special: the DP owns it. +- **Not segmented on Windows:** zero-copy frames, the #918 output-adapter split, a zones / Local2D + frame (any non-whole-window canvas), the shared-texture path, a pinned session, `DXR_SEGMENTS=0`. + The service / hosted path is not segmented yet (ADR-035 amendment, M6 second half). +- **sim_display** implements both slots on D3D11 too, so a two-monitor Windows box with no vendor + hardware exercises the split path (anaglyph on both halves). + ## Per-segment views (M3) Under `PRIMARY_MULTIVIEW_DXR` each segment gets its **own** views instead of a crop of diff --git a/docs/roadmap/multi-screen.md b/docs/roadmap/multi-screen.md index 5aaca692ef..f835aa8a3c 100644 --- a/docs/roadmap/multi-screen.md +++ b/docs/roadmap/multi-screen.md @@ -208,7 +208,7 @@ multiview + display_info v22. An app may also pin itself to one display | **M3** Per-segment views | Same window: `xrLocateViews` returns 4 views, each half rendered from its own frustum | oxr: per-segment Kooima + eyes from the segment DP; `XrViewDisplayBindingsDXR`; `activeViewCount`; tile routing by segment; legacy stereo = majority segment + 2D blit | none | cube renders the correct perspective on each half; `check_displayxr_app.py` rule for v22 | | **M4 = T1** Mixed vendor on `ds1-linux` | **Leia weave + DS1 tracking on the DS1 half, sim anaglyph on the laptop half** | loader: two plug-ins resident, DPs created from each screen's factory; plug-in display info per screen | leia-linux: `window = 0` always (one line, `leia_sr_linux_sdk.c:1470`); per-DP display state instead of `g_ctx.display` / statics; `srLensDisable` right after `srCreateLens` if the lens must start off. Confirmed by the SR side: a windowless Linux Vulkan weaver fed the DS1 segment + present origin weaves exactly that viewport, votes the lens on once, then the DP owns it. Works on today's SDK for **one** Leia panel. With SR-P0 (verified on the DS1 2026-10-07, branch `ST-5481-weaver-external-routing`): check `SrWeaverRoutingCapabilities` (tag 24) chained on `SrRuntimeCapabilities.pNext`, then chain `SrWeaverRoutingInfo{mode=SR_WEAVER_ROUTING_EXTERNAL}` (tag 22) on `SrWeaverCreateInfoVulkan.pNext`, `window = 0`, present origin per frame, lens via `srLensEnable/Disable` | David eyeballs it; screenshot both halves | | **M5 = T2** Two Leia panels | Each panel tracked by its own camera | per-screen tracker plumbing already in place from M3 | **SR-P1** enumeration + identity, **SR-P2** bind display/weaver/lens/tracker by `displayId`, **SR-P3** N trackers + lens per display (SRService refactor, ST-3160 camera pairing). Plug-in: per-DP SR display + tracker, claims from `srEnumerateDisplays` | needs a second panel on a dev box | -| **M6 = T3** Service / hosted / Windows | Shell and hosted apps on N screens; Windows parity | ADR-035 amendment: one presenter per 3D screen in the service, clients segmented across presenters; hosted window per screen (#715 fix folds in); Windows: HWND to majority segment DP, windowless secondaries | SR-P0 **already ships on q3**, so no SDK gate: plug-in Windows DP chains `SrWeaverRoutingInfo{EXTERNAL, KEEP_DRAG_SNAP}` on the HWND DP and `{EXTERNAL}` + NULL HWND on secondaries, forwards `set_present_origin` → `srWeaverSetPresentOrigin` in **device px relative to that SR display, rotation applied** (a DPI-virtualised origin weaves on the wrong lattice and looks plausible — see `docs/reference/dpi-awareness.md`) | shell with a window straddling two panels | +| **M6 = T3** Service / hosted / Windows | Shell and hosted apps on N screens; Windows parity | ADR-035 amendment: one presenter per 3D screen in the service, clients segmented across presenters; hosted window per screen (#715 fix folds in); Windows: HWND to majority segment DP, windowless secondaries | SR-P0 **already ships on q3**, so no SDK gate: plug-in Windows DP chains `SrWeaverRoutingInfo{EXTERNAL, KEEP_DRAG_SNAP}` on the HWND DP and `{EXTERNAL}` + NULL HWND on secondaries, forwards `set_present_origin` → `srWeaverSetPresentOrigin` in **device px relative to that SR display, rotation applied** (a DPI-virtualised origin weaves on the wrong lattice and looks plausible — see `docs/reference/dpi-awareness.md`). **In-process D3D11 half: runtime `comp_d3d11_segments` + two appended slots (`create_dp_d3d11_for_screen`, D3D11 `set_present_origin`, no ABI bump) + sim_display D3D11; plug-in: screen-bound D3D11 DP chains EXTERNAL (+KEEP_DRAG_SNAP with the HWND) + `SrDisplayBindingInfo`, per-frame `srWeaverSetPresentOrigin` when windowless, scissor = canvas, and pins `srWeaverSetSimulatedViewer` on a non-active display (lens left to the active panel until SR D3). HWND stays with the primary screen's DP (majority hand-off is a follow-up); service/hosted half not started.** | shell with a window straddling two panels | Ordering rationale: M0–M3 are hardware-free beyond "two monitors" and exercise everything with sim_display, so the model is proven before any SR dependency. M4 is David's stated diff --git a/src/xrt/compositor/d3d11/CMakeLists.txt b/src/xrt/compositor/d3d11/CMakeLists.txt index 8e78fbfd63..6f2364ae9e 100644 --- a/src/xrt/compositor/d3d11/CMakeLists.txt +++ b/src/xrt/compositor/d3d11/CMakeLists.txt @@ -20,6 +20,8 @@ if(WIN32 AND XRT_HAVE_D3D11) comp_d3d11_outcomp.h comp_d3d11_renderer.cpp comp_d3d11_renderer.h + comp_d3d11_segments.cpp + comp_d3d11_segments.h comp_d3d11_state_guard.h comp_d3d11_swapchain.cpp comp_d3d11_swapchain.h diff --git a/src/xrt/compositor/d3d11/comp_d3d11_compositor.cpp b/src/xrt/compositor/d3d11/comp_d3d11_compositor.cpp index 6429954861..14b9ae0f63 100644 --- a/src/xrt/compositor/d3d11/comp_d3d11_compositor.cpp +++ b/src/xrt/compositor/d3d11/comp_d3d11_compositor.cpp @@ -14,6 +14,8 @@ #include "util/comp_display_refresh_win.h" #include "comp_d3d11_renderer.h" #include "comp_d3d11_outcomp.h" +#include "comp_d3d11_segments.h" // multi-screen M6 +#include "xrt/xrt_screen.h" #include "comp_d3d11_window.h" #include "comp_d3d11_state_guard.h" #include "comp_xbridge.h" @@ -488,6 +490,21 @@ struct comp_d3d11_compositor //! Generic D3D11 display processor (vendor-agnostic weaving). struct xrt_display_processor_d3d11 *display_processor; + /*! + * Multi-screen M6 (ADR-047 D2 on Windows): a window spanning monitors is + * woven per segment, each by its own screen's DP, into the one back + * buffer. The screen list + DP registry arrive at session creation + * (comp_d3d11_compositor_set_screens, under c->mutex); the manager is + * (re)built lazily on the weave thread, which also holds c->mutex. + */ + struct comp_d3d11_segments *segments; + struct xrt_screen_list *seg_screens; //!< heap copy, NULL = not handed over + struct xrt_system_compositor_info *seg_sys_info; //!< heap copy (the DP registry) + bool seg_rebuild; //!< a new list arrived: rebuild on the next weave + uint64_t seg_pinned_display_id; + struct comp_seg_rect seg_window; //!< the window's client area in desktop device px + bool have_seg_window; + //! System devices (for qwerty driver keyboard input and display mode toggle). struct xrt_system_devices *xsysd; @@ -2564,13 +2581,72 @@ d3d11_dp_weave(struct comp_d3d11_compositor *c, bool is_repaint) xrt_display_processor_d3d11_set_predicted_scanout( c->display_processor, comp_d3d11_target_predict_weave_to_scanout_ns(c->target)); - xrt_display_processor_d3d11_process_atlas( - c->display_processor, d3d11_out_context(c), atlas_srv, view_width, view_height, - tile_columns, tile_rows, DXGI_FORMAT_R8G8B8A8_UNORM, target_width, target_height, - eff_canvas.valid ? eff_canvas.x : 0, - eff_canvas.valid ? eff_canvas.y : 0, - eff_canvas.valid ? eff_canvas.w : 0, - eff_canvas.valid ? eff_canvas.h : 0); + /* + * Multi-screen M6 (ADR-047 D2): does this window span monitors? Only off + * the zero-copy path (the atlas must be ours to crop), off the #918 split + * (the output adapter owns that pipeline), and for a whole-window canvas + * (a zones / Local2D frame keeps the single-DP path — out of scope here). + * A window on the primary screen only also stays single-DP, byte for byte. + */ + bool seg_split = false; + if (!zero_copy && !c->split_active && !eff_canvas.valid && c->have_seg_window && c->seg_screens != nullptr && + atlas_srv != nullptr) { + if (c->seg_rebuild) { + comp_d3d11_segments_destroy(&c->segments); + c->seg_rebuild = false; + } + if (c->segments == nullptr) { + c->segments = comp_d3d11_segments_create(d3d11_out_device(c)); + if (c->segments != nullptr) { + comp_d3d11_segments_set_screens(c->segments, c->seg_screens, c->seg_sys_info, + c->seg_pinned_display_id); + } + } + if (comp_d3d11_segments_enabled(c->segments)) { + const struct comp_seg_rect canvas = {0, 0, c->seg_window.w, c->seg_window.h}; + const uint32_t mode_index = + (c->xdev != nullptr && c->xdev->hmd != nullptr) ? c->xdev->hmd->active_rendering_mode_index : 0; + seg_split = comp_d3d11_segments_update(c->segments, &c->seg_window, &canvas, d3d11_out_context(c), + mode_index); + // Every segment DP follows the session's 2D/3D mode, like the primary. + comp_d3d11_segments_set_display_mode(c->segments, c->hardware_display_3d); + } + } + + if (seg_split) { + struct comp_d3d11_segments_frame sf = {}; + sf.context = d3d11_out_context(c); + sf.src_srv = atlas_srv; + sf.view_width = view_width; + sf.view_height = view_height; + sf.tile_columns = tile_columns; + sf.tile_rows = tile_rows; + sf.src_format = (uint32_t)DXGI_FORMAT_R8G8B8A8_UNORM; + sf.target_rtv = comp_d3d11_target_get_rtv(c->target); + sf.target_width = target_width; + sf.target_height = target_height; + sf.canvas.x = 0; + sf.canvas.y = 0; + sf.canvas.w = c->seg_window.w; + sf.canvas.h = c->seg_window.h; + sf.transparent_background = c->transparent_background; + sf.atlas_encoding = -1; // the in-process D3D11 path declares none to the primary either + sf.primary_dp = c->display_processor; + sf.renderer = c->renderer; + comp_d3d11_segments_record(c->segments, &sf); + // The segment path leaves the back buffer bound at full viewport; + // re-bind through the target so everything after sees exactly the + // state the single-DP path leaves. + comp_d3d11_target_bind(c->target); + } else { + xrt_display_processor_d3d11_process_atlas( + c->display_processor, d3d11_out_context(c), atlas_srv, view_width, view_height, + tile_columns, tile_rows, DXGI_FORMAT_R8G8B8A8_UNORM, target_width, target_height, + eff_canvas.valid ? eff_canvas.x : 0, + eff_canvas.valid ? eff_canvas.y : 0, + eff_canvas.valid ? eff_canvas.w : 0, + eff_canvas.valid ? eff_canvas.h : 0); + } // XR_DXR_depth_budget: evaluate the rear budget from the background the // DP just composited under. AFTER process_atlas, because that is the point @@ -4017,6 +4093,14 @@ d3d11_compositor_destroy(struct xrt_compositor *xc) comp_d3d11_window_set_snap_provider(c->own_window, NULL, NULL); } + // Multi-screen M6: segment DPs + crop textures, before the primary DP + // (the repaint thread is already stopped, so no weave is in flight). + comp_d3d11_segments_destroy(&c->segments); + free(c->seg_screens); + c->seg_screens = nullptr; + free(c->seg_sys_info); + c->seg_sys_info = nullptr; + // Destroy display processor (handles all vendor cleanup internally) xrt_display_processor_d3d11_destroy(&c->display_processor); @@ -8207,6 +8291,15 @@ comp_d3d11_compositor_get_window_metrics(struct xrt_compositor *xc, struct xrt_w POINT client_origin = {0, 0}; ClientToScreen(metrics_hwnd, &client_origin); + // Multi-screen M6: the window's desktop rect, for the segment table. + // Device px — every DisplayXR executable is per-monitor DPI aware + // (#1201), the same space the screen registry's monitor rects are in. + c->seg_window.x = client_origin.x; + c->seg_window.y = client_origin.y; + c->seg_window.w = win_px_w; + c->seg_window.h = win_px_h; + c->have_seg_window = true; + // Compute pixel size (meters per pixel) float pixel_size_x = disp_w_m / (float)disp_px_w; float pixel_size_y = disp_h_m / (float)disp_px_h; @@ -8268,6 +8361,37 @@ comp_d3d11_compositor_request_display_mode(struct xrt_compositor *xc, bool enabl return false; } +extern "C" void +comp_d3d11_compositor_set_screens(struct xrt_compositor *xc, + const struct xrt_screen_list *list, + const struct xrt_system_compositor_info *info, + uint64_t pinned_display_id) +{ + if (xc == nullptr || list == nullptr || info == nullptr) { + return; + } + struct comp_d3d11_compositor *c = d3d11_comp(xc); + struct xrt_screen_list *copy = U_TYPED_CALLOC(struct xrt_screen_list); + struct xrt_system_compositor_info *info_copy = U_TYPED_CALLOC(struct xrt_system_compositor_info); + if (copy == nullptr || info_copy == nullptr) { + free(copy); + free(info_copy); + return; + } + *copy = *list; + *info_copy = *info; + + // The weave (app thread's layer_commit and the repaint thread) runs under + // c->mutex; the manager is rebuilt there on the next weave, never here. + std::lock_guard lock(c->mutex); + c->seg_rebuild = true; + free(c->seg_screens); + c->seg_screens = copy; + free(c->seg_sys_info); + c->seg_sys_info = info_copy; + c->seg_pinned_display_id = pinned_display_id; +} + extern "C" void comp_d3d11_compositor_set_eye_tracking_mode(struct xrt_compositor *xc, uint32_t mode) { diff --git a/src/xrt/compositor/d3d11/comp_d3d11_compositor.h b/src/xrt/compositor/d3d11/comp_d3d11_compositor.h index e9feb42adf..f9fc9e3e92 100644 --- a/src/xrt/compositor/d3d11/comp_d3d11_compositor.h +++ b/src/xrt/compositor/d3d11/comp_d3d11_compositor.h @@ -226,6 +226,24 @@ bool comp_d3d11_compositor_get_window_metrics(struct xrt_compositor *xc, struct xrt_window_metrics *out_metrics); +/*! + * Multi-screen M6 (ADR-047 D2 on Windows): hand the compositor the system's + * screen list, so a window that spans several monitors is woven per segment, + * each by its own screen's display processor, into the one back buffer. Call + * once at session creation. @p pinned_display_id is the session's + * `XrSessionDisplayBindingDXR` (0 = none); a pinned session is never + * segmented. A window on the primary screen only keeps the single-DP path + * byte for byte. + * + * @ingroup comp_d3d11 + */ +struct xrt_screen_list; +void +comp_d3d11_compositor_set_screens(struct xrt_compositor *xc, + const struct xrt_screen_list *list, + const struct xrt_system_compositor_info *info, + uint64_t pinned_display_id); + /*! * XR_DXR_depth_budget: whether this session asked for the rear depth budget. * diff --git a/src/xrt/compositor/d3d11/comp_d3d11_renderer.cpp b/src/xrt/compositor/d3d11/comp_d3d11_renderer.cpp index 66c3e25c34..b3b9731d72 100644 --- a/src/xrt/compositor/d3d11/comp_d3d11_renderer.cpp +++ b/src/xrt/compositor/d3d11/comp_d3d11_renderer.cpp @@ -2509,3 +2509,89 @@ comp_d3d11_renderer_blit_stretch(struct comp_d3d11_renderer *renderer, return XRT_SUCCESS; } +xrt_result_t +comp_d3d11_renderer_blit_rect(struct comp_d3d11_renderer *renderer, + void *src_srv, + int32_t src_x, + int32_t src_y, + uint32_t src_w, + uint32_t src_h, + void *rtv, + int32_t dst_x, + int32_t dst_y, + uint32_t dst_w, + uint32_t dst_h) +{ + if (renderer == nullptr || src_srv == nullptr || rtv == nullptr || src_w == 0 || src_h == 0 || dst_w == 0 || + dst_h == 0) { + return XRT_ERROR_D3D; + } + auto internals = get_internals(renderer->c); + ID3D11DeviceContext *ctx = internals.context; + auto *srv = static_cast(src_srv); + auto *target_rtv = static_cast(rtv); + + // The source rect in normalised texture space needs the texture's size. + ID3D11Resource *res = nullptr; + srv->GetResource(&res); + if (res == nullptr) { + return XRT_ERROR_D3D; + } + D3D11_TEXTURE2D_DESC desc = {}; + static_cast(res)->GetDesc(&desc); + res->Release(); + if (desc.Width == 0 || desc.Height == 0) { + return XRT_ERROR_D3D; + } + + ctx->OMSetRenderTargets(1, &target_rtv, nullptr); + D3D11_VIEWPORT vp = {}; + vp.TopLeftX = static_cast(dst_x); + vp.TopLeftY = static_cast(dst_y); + vp.Width = static_cast(dst_w); + vp.Height = static_cast(dst_h); + vp.MaxDepth = 1.0f; + ctx->RSSetViewports(1, &vp); + // The renderer's rasterizer state has scissor off; the viewport alone + // confines a fullscreen strip to the destination rect. + ctx->IASetPrimitiveTopology(D3D11_PRIMITIVE_TOPOLOGY_TRIANGLESTRIP); + ctx->IASetInputLayout(nullptr); + ctx->VSSetShader(renderer->projection_vs, nullptr, 0); + ctx->PSSetShader(renderer->projection_ps, nullptr, 0); + ctx->RSSetState(renderer->rasterizer_state); + ctx->OMSetDepthStencilState(renderer->depth_stencil_state, 0); + ctx->OMSetBlendState(renderer->blend_opaque, nullptr, 0xFFFFFFFF); + ctx->PSSetSamplers(0, 1, &renderer->sampler_linear); + ctx->PSSetShaderResources(0, 1, &srv); + + LayerConstants constants = {}; + constants.mvp[0] = 1.0f; + constants.mvp[5] = 1.0f; + constants.mvp[10] = 1.0f; + constants.mvp[15] = 1.0f; + constants.post_transform[0] = static_cast(src_x) / static_cast(desc.Width); + constants.post_transform[1] = static_cast(src_y) / static_cast(desc.Height); + constants.post_transform[2] = static_cast(src_w) / static_cast(desc.Width); + constants.post_transform[3] = static_cast(src_h) / static_cast(desc.Height); + constants.color_scale[0] = 1.0f; + constants.color_scale[1] = 1.0f; + constants.color_scale[2] = 1.0f; + constants.color_scale[3] = 1.0f; + + D3D11_MAPPED_SUBRESOURCE mapped; + HRESULT hr = ctx->Map(renderer->constant_buffer, 0, D3D11_MAP_WRITE_DISCARD, 0, &mapped); + if (FAILED(hr)) { + return XRT_ERROR_D3D; + } + memcpy(mapped.pData, &constants, sizeof(constants)); + ctx->Unmap(renderer->constant_buffer, 0); + ctx->VSSetConstantBuffers(0, 1, &renderer->constant_buffer); + ctx->PSSetConstantBuffers(0, 1, &renderer->constant_buffer); + + ctx->Draw(4, 0); + + ID3D11ShaderResourceView *null_srv = nullptr; + ctx->PSSetShaderResources(0, 1, &null_srv); + return XRT_SUCCESS; +} + diff --git a/src/xrt/compositor/d3d11/comp_d3d11_renderer.h b/src/xrt/compositor/d3d11/comp_d3d11_renderer.h index ccf376b2a9..3c589a72d4 100644 --- a/src/xrt/compositor/d3d11/comp_d3d11_renderer.h +++ b/src/xrt/compositor/d3d11/comp_d3d11_renderer.h @@ -352,6 +352,35 @@ comp_d3d11_renderer_blit_stretch(struct comp_d3d11_renderer *renderer, uint32_t target_width, uint32_t target_height); +/*! + * Multi-screen M6: linearly scale one rect of a texture into one rect of the + * currently relevant render target — the flat-2D fill of a segment no DP can + * weave (and of the canvas no screen covers), the D3D11 twin of the Vulkan + * segments' `vkCmdBlitImage`. Opaque, no colour transform. Binds @p rtv, a + * viewport = @p dst and the renderer's own quad pipeline; the caller re-binds + * whatever it needs afterwards. + * + * @param renderer The renderer. + * @param src_srv Source texture SRV (ID3D11ShaderResourceView*). + * @param src_x,src_y,src_w,src_h Source rect in source texels. + * @param rtv Destination render target view (ID3D11RenderTargetView*). + * @param dst_x,dst_y,dst_w,dst_h Destination rect in target pixels. + * + * @ingroup comp_d3d11 + */ +xrt_result_t +comp_d3d11_renderer_blit_rect(struct comp_d3d11_renderer *renderer, + void *src_srv, + int32_t src_x, + int32_t src_y, + uint32_t src_w, + uint32_t src_h, + void *rtv, + int32_t dst_x, + int32_t dst_y, + uint32_t dst_w, + uint32_t dst_h); + #ifdef __cplusplus } diff --git a/src/xrt/compositor/d3d11/comp_d3d11_segments.cpp b/src/xrt/compositor/d3d11/comp_d3d11_segments.cpp new file mode 100644 index 0000000000..27451c53cc --- /dev/null +++ b/src/xrt/compositor/d3d11/comp_d3d11_segments.cpp @@ -0,0 +1,718 @@ +// Copyright 2026, The DisplayXR Project +// SPDX-License-Identifier: BSL-1.0 +/*! + * @file + * @brief Per-screen segment display processors for the in-process D3D11 + * compositor (multi-screen M6, ADR-047 D2 on Windows). + * @ingroup comp_d3d11 + * + * Mirrors vk_native/comp_vk_native_segments.c: the screen-table join, the + * lifecycle with hysteresis, the decide loop and the two-pass weave order are + * the same by construction; only the GPU work (crop copies, the clear, the + * flat-2D blit) is D3D11. There is no deferred-release list: the immediate + * context executes in order and D3D11 objects are refcounted, so a retired DP + * or texture can be released the moment the weave that used it has been + * recorded. + */ + +#include "comp_d3d11_segments.h" + +#include "comp_d3d11_renderer.h" +#include "d3d/d3d_dxgi_formats.h" +#include "os/os_threading.h" +#include "util/u_logging.h" +#include "util/u_misc.h" +#include "xrt/xrt_plugin.h" + +#include + +#include +#include +#include + +/* + * + * Internal types. + * + */ + +//! One per-screen crop texture: the atlas tiles cut down to one segment. +struct seg_crop +{ + ID3D11Texture2D *texture; + ID3D11ShaderResourceView *srv; + uint32_t w, h; + DXGI_FORMAT format; +}; + +//! Per-screen runtime state (indexed like comp_d3d11_segments::screens). +struct seg_screen_state +{ + struct xrt_display_processor_d3d11 *dp; //!< NULL until the lifecycle creates it. + bool configured; //!< set_background_2d(NULL) sent once. + int encoding_latched; //!< last atlas encoding declared, -1 = none. + int transparent_latched; //!< last transparency declared, -1 = none. + int mode_sent; //!< last 2D/3D mode sent, -1 = none. + bool tolerates_resample; + struct seg_crop crop; +}; + +struct comp_d3d11_segments +{ + ID3D11Device *device; + + bool enabled; + uint32_t screen_count; + struct comp_segments_screen screens[COMP_SEGMENTS_MAX_SCREENS]; + struct xrt_screen_binding bindings[COMP_SEGMENTS_MAX_SCREENS]; + const struct xrt_plugin_iface *iface[COMP_SEGMENTS_MAX_SCREENS]; + struct xrt_plugin_instance *inst[COMP_SEGMENTS_MAX_SCREENS]; + struct seg_screen_state st[COMP_SEGMENTS_MAX_SCREENS]; + + struct comp_segment_table table; + struct comp_segments_lifecycle lc; + + //! The app thread reads st[i].dp for eyes; the weave creates/destroys it. + struct os_mutex dp_mutex; + + bool mode_3d; + uint32_t mode_index; + + // Change-only logging. + bool have_logged_table; + bool logged_split; + struct comp_segment_table logged_table; +}; + + +/* + * + * Helpers. + * + */ + +static int +screen_index_of(const struct comp_d3d11_segments *segs, uint64_t screen_id) +{ + for (uint32_t i = 0; i < segs->screen_count; i++) { + if (segs->screens[i].id == screen_id) { + return (int)i; + } + } + return -1; +} + +static void +crop_destroy(struct seg_crop *c) +{ + if (c->srv != nullptr) { + c->srv->Release(); + c->srv = nullptr; + } + if (c->texture != nullptr) { + c->texture->Release(); + c->texture = nullptr; + } + c->w = c->h = 0; +} + +/*! + * (Re)create the crop texture at @p w × @p h in the atlas's format. The SRV is + * typed UNORM (the in-process DP samples raw bytes, ADR-021 Model A), which is + * the same choice the compositor's own DP-input crop makes. + */ +static bool +crop_ensure(ID3D11Device *dev, struct seg_crop *c, uint32_t w, uint32_t h, DXGI_FORMAT format) +{ + if (c->texture != nullptr && c->w == w && c->h == h && c->format == format) { + return true; + } + crop_destroy(c); + D3D11_TEXTURE2D_DESC desc = {}; + desc.Width = w; + desc.Height = h; + desc.MipLevels = 1; + desc.ArraySize = 1; + desc.Format = format; + desc.SampleDesc.Count = 1; + desc.Usage = D3D11_USAGE_DEFAULT; + desc.BindFlags = D3D11_BIND_SHADER_RESOURCE; + HRESULT hr = dev->CreateTexture2D(&desc, nullptr, &c->texture); + if (FAILED(hr)) { + U_LOG_E("segments: crop texture %ux%u failed: 0x%lx", w, h, hr); + return false; + } + D3D11_SHADER_RESOURCE_VIEW_DESC sd = {}; + sd.Format = d3d_dxgi_format_to_unorm_sample(format); + sd.ViewDimension = D3D11_SRV_DIMENSION_TEXTURE2D; + sd.Texture2D.MostDetailedMip = 0; + sd.Texture2D.MipLevels = 1; + hr = dev->CreateShaderResourceView(c->texture, &sd, &c->srv); + if (FAILED(hr)) { + U_LOG_E("segments: crop SRV failed: 0x%lx", hr); + crop_destroy(c); + return false; + } + c->w = w; + c->h = h; + c->format = format; + return true; +} + +static void +dp_release(struct comp_d3d11_segments *segs, uint32_t i) +{ + struct seg_screen_state *st = &segs->st[i]; + if (st->dp != nullptr) { + U_LOG_I("segments: retiring the segment DP for screen 0x%016llx ('%s')", + (unsigned long long)segs->screens[i].id, segs->bindings[i].device_name); + os_mutex_lock(&segs->dp_mutex); + struct xrt_display_processor_d3d11 *dp = st->dp; + st->dp = nullptr; + os_mutex_unlock(&segs->dp_mutex); + xrt_display_processor_d3d11_destroy(&dp); + } + st->configured = false; + st->encoding_latched = -1; + st->transparent_latched = -1; + st->mode_sent = -1; + st->tolerates_resample = false; + crop_destroy(&st->crop); +} + +/*! + * Create screen @p i's segment DP through the plug-in's per-screen factory. + * Windowless (NULL window) on purpose: a segment DP's phase comes from + * set_present_origin; the session's real HWND stays with the primary DP (the + * vendor's drag phase-snap follows it). + * + * No fallback to the plain `create_dp_d3d11`, for the reason given on + * xrt_plugin_iface::create_dp_vk_for_screen: that DP describes the plug-in's + * process-wide panel and is not bound by the canvas / not-first-writer + * contract, so it could wipe the segment woven before it. + */ +static bool +dp_create(struct comp_d3d11_segments *segs, uint32_t i, ID3D11DeviceContext *ctx) +{ + struct seg_screen_state *st = &segs->st[i]; + const struct xrt_plugin_iface *iface = segs->iface[i]; + xrt_result_t xret = XRT_ERROR_DEVICE_CREATION_FAILED; + struct xrt_display_processor_d3d11 *dp = nullptr; + if (segs->screens[i].has_dp_factory && xrt_plugin_iface_has_create_dp_d3d11_for_screen(iface)) { + xret = iface->create_dp_d3d11_for_screen(segs->inst[i], segs->device, ctx, nullptr, &segs->bindings[i], + &dp); + } + if (xret != XRT_SUCCESS || dp == nullptr || dp->process_atlas == nullptr) { + if (dp != nullptr) { + xrt_display_processor_d3d11_destroy(&dp); + } + U_LOG_W( + "segments: could not create a DP for screen 0x%016llx ('%s', plug-in '%s') via " + "create_dp_d3d11_for_screen (%d) — that segment stays flat 2D", + (unsigned long long)segs->screens[i].id, segs->bindings[i].device_name, segs->screens[i].plugin_id, + (int)xret); + return false; + } + os_mutex_lock(&segs->dp_mutex); + st->dp = dp; + os_mutex_unlock(&segs->dp_mutex); + st->tolerates_resample = xrt_display_processor_d3d11_tolerates_resample(dp); + st->configured = false; + st->encoding_latched = -1; + st->transparent_latched = -1; + st->mode_sent = -1; + // Lifecycle event (hysteresis-gated), not per frame. + U_LOG_W( + "segments: created a segment DP for screen 0x%016llx ('%s', plug-in '%s') via " + "create_dp_d3d11_for_screen — %s", + (unsigned long long)segs->screens[i].id, segs->bindings[i].device_name, segs->screens[i].plugin_id, + st->tolerates_resample ? "tolerates a resample" : "needs 1:1 pixels"); + return true; +} + +static void +set_viewport_scissor(ID3D11DeviceContext *ctx, const struct comp_seg_rect *r) +{ + D3D11_VIEWPORT vp = {}; + vp.TopLeftX = (float)r->x; + vp.TopLeftY = (float)r->y; + vp.Width = (float)r->w; + vp.Height = (float)r->h; + vp.MaxDepth = 1.0f; + ctx->RSSetViewports(1, &vp); + D3D11_RECT sc = {r->x, r->y, r->x + (LONG)r->w, r->y + (LONG)r->h}; + ctx->RSSetScissorRects(1, &sc); +} + + +/* + * + * 'Exported' functions. + * + */ + +extern "C" struct comp_d3d11_segments * +comp_d3d11_segments_create(void *d3d11_device) +{ + if (d3d11_device == nullptr) { + return nullptr; + } + struct comp_d3d11_segments *segs = U_TYPED_CALLOC(struct comp_d3d11_segments); + if (segs == nullptr) { + return nullptr; + } + segs->device = static_cast(d3d11_device); + os_mutex_init(&segs->dp_mutex); + comp_segments_lifecycle_init(&segs->lc, 0, 0); + segs->mode_index = UINT32_MAX; + for (uint32_t i = 0; i < COMP_SEGMENTS_MAX_SCREENS; i++) { + segs->st[i].encoding_latched = -1; + segs->st[i].transparent_latched = -1; + segs->st[i].mode_sent = -1; + } + return segs; +} + +extern "C" void +comp_d3d11_segments_destroy(struct comp_d3d11_segments **segs_ptr) +{ + if (segs_ptr == nullptr || *segs_ptr == nullptr) { + return; + } + struct comp_d3d11_segments *segs = *segs_ptr; + for (uint32_t i = 0; i < COMP_SEGMENTS_MAX_SCREENS; i++) { + dp_release(segs, i); + } + os_mutex_destroy(&segs->dp_mutex); + free(segs); + *segs_ptr = nullptr; +} + +extern "C" void +comp_d3d11_segments_set_screens(struct comp_d3d11_segments *segs, + const struct xrt_screen_list *list, + const struct xrt_system_compositor_info *info, + uint64_t pinned_display_id) +{ + if (segs == nullptr) { + return; + } + for (uint32_t i = 0; i < COMP_SEGMENTS_MAX_SCREENS; i++) { + dp_release(segs, i); + } + comp_segments_lifecycle_init(&segs->lc, 0, 0); + segs->enabled = false; + segs->screen_count = 0; + segs->have_logged_table = false; + segs->logged_split = false; + memset(&segs->table, 0, sizeof(segs->table)); + + const char *env = getenv("DXR_SEGMENTS"); + if (env != nullptr && env[0] == '0') { + U_LOG_W("segments: DXR_SEGMENTS=0 — a window spanning screens keeps the single-DP path"); + return; + } + if (list == nullptr || info == nullptr || list->count < 2) { + return; + } + if (pinned_display_id != 0) { + U_LOG_I("segments: session pinned to display 0x%016llx (XrSessionDisplayBindingDXR) — no segmentation", + (unsigned long long)pinned_display_id); + return; + } + + // The primary: the system-default screen, woven by the session's own DP. + const struct xrt_dp_registry_entry *primary_entry = nullptr; + for (uint32_t i = 0; i < list->count && i < XRT_SCREEN_LIST_MAX; i++) { + if ((list->screens[i].flags & XRT_SCREEN_FLAG_SYSTEM_DEFAULT) == 0) { + continue; + } + for (uint32_t e = 0; e < info->dp_registry.entry_count; e++) { + if (info->dp_registry.entries[e].monitor_id == list->screens[i].id) { + primary_entry = &info->dp_registry.entries[e]; + break; + } + } + } + if (primary_entry == nullptr) { + U_LOG_I("segments: the DP registry does not know the system-default screen — no segmentation"); + return; + } + + uint32_t n = 0; + bool mixed = false; + char vendors[256] = {0}; + for (uint32_t i = 0; i < list->count && i < XRT_SCREEN_LIST_MAX && n < COMP_SEGMENTS_MAX_SCREENS; i++) { + const struct xrt_screen *s = &list->screens[i]; + if (s->desktop_width == 0 || s->desktop_height == 0) { + continue; + } + const struct xrt_dp_registry_entry *e = nullptr; + for (uint32_t k = 0; k < info->dp_registry.entry_count; k++) { + if (info->dp_registry.entries[k].monitor_id == s->id) { + e = &info->dp_registry.entries[k]; + break; + } + } + + struct comp_segments_screen *cs = &segs->screens[n]; + memset(cs, 0, sizeof(*cs)); + cs->id = s->id; + cs->desktop.x = s->desktop_left; + cs->desktop.y = s->desktop_top; + cs->desktop.w = s->desktop_width; + cs->desktop.h = s->desktop_height; + cs->native_w = s->native_width; + cs->native_h = s->native_height; + cs->is_primary = (e == primary_entry); + snprintf(cs->plugin_id, sizeof(cs->plugin_id), "%s", s->plugin_id); + + segs->iface[n] = nullptr; + segs->inst[n] = nullptr; + if (e != nullptr && !cs->is_primary) { + // A segment's DP comes from ITS screen's registry entry, + // whichever plug-in that is (mixed-vendor rigs, M4). + if (strcmp(e->plugin_id, primary_entry->plugin_id) != 0) { + mixed = true; + if (strstr(vendors, e->plugin_id) == nullptr) { + size_t used = strlen(vendors); + snprintf(vendors + used, sizeof(vendors) - used, "%s%s", used > 0 ? ", " : "", + e->plugin_id); + } + } + segs->iface[n] = (const struct xrt_plugin_iface *)e->owning_iface; + segs->inst[n] = (struct xrt_plugin_instance *)e->owning_instance; + // The per-screen slot, and only it (see dp_create). The registry's + // D3D11 factory being set says the claim covers D3D11. + cs->has_dp_factory = e->dp_factory_d3d11 != nullptr && + xrt_plugin_iface_has_create_dp_d3d11_for_screen(segs->iface[n]); + } + + struct xrt_screen_binding *b = &segs->bindings[n]; + memset(b, 0, sizeof(*b)); + b->struct_size = (uint32_t)sizeof(*b); + b->monitor_id = s->id; + b->desktop_left = s->desktop_left; + b->desktop_top = s->desktop_top; + b->desktop_width = s->desktop_width; + b->desktop_height = s->desktop_height; + b->native_pixel_width = s->native_width; + b->native_pixel_height = s->native_height; + b->physical_width_mm = s->physical_width_mm; + b->physical_height_mm = s->physical_height_mm; + b->desktop_scale = s->desktop_scale; + snprintf(b->device_name, sizeof(b->device_name), "%s", s->device_name); + if (e != nullptr) { + snprintf(b->serial, sizeof(b->serial), "%s", e->serial); + } + n++; + } + segs->screen_count = n; + + if (mixed) { + U_LOG_I( + "segments: mixed-vendor screen table — primary '%s', other screens by '%s'; each segment DP " + "comes from its own screen's plug-in", + primary_entry->plugin_id, vendors); + } + segs->enabled = n >= 2; + if (segs->enabled) { + U_LOG_W( + "segments: %u screens, plug-in '%s' — a window spanning monitors is woven per monitor " + "(multi-screen M6, D3D11)", + n, primary_entry->plugin_id); + } +} + +extern "C" bool +comp_d3d11_segments_enabled(const struct comp_d3d11_segments *segs) +{ + return segs != nullptr && segs->enabled; +} + +extern "C" bool +comp_d3d11_segments_update(struct comp_d3d11_segments *segs, + const struct comp_seg_rect *window_desktop, + const struct comp_seg_rect *canvas, + void *d3d11_context, + uint32_t mode_index) +{ + if (segs == nullptr || !segs->enabled || window_desktop == nullptr || d3d11_context == nullptr) { + return false; + } + auto *ctx = static_cast(d3d11_context); + + comp_segments_compute(window_desktop, canvas, segs->screens, segs->screen_count, &segs->table); + + struct comp_segments_actions act; + comp_segments_lifecycle_update(&segs->lc, &segs->table, &act); + for (uint32_t a = 0; a < act.count; a++) { + const int i = screen_index_of(segs, act.items[a].screen_id); + if (i < 0) { + continue; + } + if (act.items[a].action == COMP_SEG_ACTION_CREATE) { + const bool ok = dp_create(segs, (uint32_t)i, ctx); + comp_segments_lifecycle_set_created(&segs->lc, act.items[a].screen_id, ok); + } else if (act.items[a].action == COMP_SEG_ACTION_DESTROY) { + dp_release(segs, (uint32_t)i); + } + } + + const uint32_t cw = canvas != nullptr && canvas->w > 0 ? canvas->w : window_desktop->w; + const uint32_t ch = canvas != nullptr && canvas->h > 0 ? canvas->h : window_desktop->h; + const bool split = comp_segments_table_is_split(&segs->table, cw, ch); + + // A DP's resample tolerance can follow the rendering mode (sim_display: + // INTERLACED needs 1:1). Re-ask on a mode change or a table change — + // never per frame. + const bool table_changed = + split && (!segs->have_logged_table || !comp_segments_table_equal(&segs->table, &segs->logged_table)); + if (mode_index != segs->mode_index || table_changed) { + segs->mode_index = mode_index; + for (uint32_t i = 0; i < segs->screen_count; i++) { + if (segs->st[i].dp != nullptr) { + segs->st[i].tolerates_resample = + xrt_display_processor_d3d11_tolerates_resample(segs->st[i].dp); + } + } + } + + // One INFO line per table change while split, and one on leaving it. + if (table_changed) { + char buf[768]; + comp_segments_table_format(&segs->table, buf, sizeof(buf)); + U_LOG_I("segments: window %d,%d %ux%u -> %s", window_desktop->x, window_desktop->y, window_desktop->w, + window_desktop->h, buf); + segs->logged_table = segs->table; + segs->have_logged_table = true; + } else if (!split && segs->logged_split) { + U_LOG_I("segments: window %d,%d %ux%u is on the primary screen only — single-DP path", + window_desktop->x, window_desktop->y, window_desktop->w, window_desktop->h); + segs->have_logged_table = false; + } + segs->logged_split = split; + return split; +} + +extern "C" bool +comp_d3d11_segments_record(struct comp_d3d11_segments *segs, const struct comp_d3d11_segments_frame *f) +{ + if (segs == nullptr || f == nullptr || f->context == nullptr || f->src_srv == nullptr || + f->target_rtv == nullptr || f->tile_columns == 0 || f->tile_rows == 0 || f->view_width == 0 || + f->view_height == 0) { + return false; + } + auto *ctx = static_cast(f->context); + auto *src_srv = static_cast(f->src_srv); + auto *rtv = static_cast(f->target_rtv); + const struct comp_segment_table *t = &segs->table; + + // The atlas texture behind the SRV, for the crop copies. + ID3D11Resource *src_res = nullptr; + src_srv->GetResource(&src_res); + if (src_res == nullptr) { + return false; + } + auto *src_tex = static_cast(src_res); + D3D11_TEXTURE2D_DESC src_desc = {}; + src_tex->GetDesc(&src_desc); + + /* + * 1. Decide each segment: which DP (if any) and whether it may weave. + */ + struct xrt_display_processor_d3d11 *dp_of[COMP_SEGMENTS_MAX] = {}; + struct seg_screen_state *st_of[COMP_SEGMENTS_MAX] = {}; + bool weave[COMP_SEGMENTS_MAX] = {}; + struct comp_seg_rect tile[COMP_SEGMENTS_MAX] = {}; + bool have_tile[COMP_SEGMENTS_MAX] = {}; + + for (uint32_t k = 0; k < t->count; k++) { + const struct comp_segment *g = &t->seg[k]; + have_tile[k] = + comp_segments_tile_rect(&g->window_rect, &f->canvas, f->view_width, f->view_height, &tile[k]); + const int i = (int)g->screen_index; + st_of[k] = (i >= 0 && (uint32_t)i < segs->screen_count) ? &segs->st[i] : nullptr; + if (g->is_primary) { + dp_of[k] = f->primary_dp; + weave[k] = f->primary_dp != nullptr; + } else if (st_of[k] != nullptr) { + dp_of[k] = st_of[k]->dp; + weave[k] = comp_segments_decide(dp_of[k] != nullptr, st_of[k]->tolerates_resample, + g->screen_1to1) == COMP_SEG_RENDER_WEAVE; + } + if (!have_tile[k] || st_of[k] == nullptr) { + weave[k] = false; + } + } + + /* + * 2. Clear the target: a segment DP confines its draw to its canvas, so + * whatever no segment covers would otherwise be undefined. + */ + { + const float clear[4] = {0.0f, 0.0f, 0.0f, f->transparent_background ? 0.0f : 1.0f}; + ctx->ClearRenderTargetView(rtv, clear); + } + + /* + * 3. Crop each woven segment's views out of the atlas — crop before the + * DP is the law (ADR-030): a DP's atlas holds exactly its canvas. + */ + for (uint32_t k = 0; k < t->count; k++) { + if (!weave[k]) { + continue; + } + struct seg_crop *crop = &st_of[k]->crop; + const uint32_t cw = tile[k].w * f->tile_columns; + const uint32_t ch = tile[k].h * f->tile_rows; + if (!crop_ensure(segs->device, crop, cw, ch, src_desc.Format)) { + weave[k] = false; // no input image: flat 2D instead + continue; + } + for (uint32_t row = 0; row < f->tile_rows; row++) { + for (uint32_t col = 0; col < f->tile_columns; col++) { + D3D11_BOX box = {}; + box.left = col * f->view_width + (UINT)tile[k].x; + box.top = row * f->view_height + (UINT)tile[k].y; + box.right = box.left + tile[k].w; + box.bottom = box.top + tile[k].h; + box.front = 0; + box.back = 1; + ctx->CopySubresourceRegion(crop->texture, 0, col * tile[k].w, row * tile[k].h, 0, + src_tex, 0, &box); + } + } + } + + /* + * 4. Weave: the primary first (a DP built before M6 may still clear the + * whole target, so it must not run after a sibling), then left to right. + * Each gets canvas = its segment, with viewport AND scissor already set to + * it (the vendor weaver reads the current viewport/scissor and writes only + * inside them); the back buffer is re-bound before every DP because a DP + * may leave another target bound. + */ + bool any_woven = false; + for (uint32_t pass = 0; pass < 2; pass++) { + for (uint32_t k = 0; k < t->count; k++) { + const struct comp_segment *g = &t->seg[k]; + if (!weave[k] || g->is_primary != (pass == 0)) { + continue; + } + struct xrt_display_processor_d3d11 *dp = dp_of[k]; + struct seg_screen_state *st = st_of[k]; + if (!g->is_primary) { + if (!st->configured) { + st->configured = true; + xrt_display_processor_d3d11_set_background_2d(dp, nullptr, 0, 0); + } + if (f->atlas_encoding >= 0 && st->encoding_latched != f->atlas_encoding) { + st->encoding_latched = f->atlas_encoding; + xrt_display_processor_d3d11_set_atlas_encoding( + dp, (enum xrt_atlas_encoding)f->atlas_encoding); + } + if (st->transparent_latched != (int)f->transparent_background) { + st->transparent_latched = (int)f->transparent_background; + xrt_display_processor_d3d11_set_transparent_background( + dp, f->transparent_background, false); + } + if (st->mode_sent != (int)segs->mode_3d) { + st->mode_sent = (int)segs->mode_3d; + xrt_display_processor_d3d11_request_display_mode(dp, segs->mode_3d); + } + // Phase only where window px ARE device px; a resampled + // screen's origin is in the wrong units (and only a + // resample-tolerant DP weaves there, which has no phase). + if (g->screen_1to1 != COMP_SEG_1TO1_NO) { + xrt_display_processor_d3d11_set_present_origin(dp, g->present_origin_x, + g->present_origin_y); + } + } + ctx->OMSetRenderTargets(1, &rtv, nullptr); + set_viewport_scissor(ctx, &g->window_rect); + xrt_display_processor_d3d11_process_atlas(dp, ctx, st->crop.srv, tile[k].w, tile[k].h, + f->tile_columns, f->tile_rows, f->src_format, + f->target_width, f->target_height, g->window_rect.x, + g->window_rect.y, g->window_rect.w, g->window_rect.h); + any_woven = true; + } + } + + /* + * 5. Flat 2D for every segment that could not be woven, and for the + * canvas no segment covers (a monitor no plug-in claimed, or off every + * screen): one view (the middle one — the left eye of a stereo pair), + * linearly scaled from the view tile to the window rect (#1654 look). + */ + struct comp_seg_rect flat_dst[COMP_SEGMENTS_MAX + COMP_SEGMENTS_MAX_UNCOVERED]; + uint32_t flat_n = 0; + for (uint32_t k = 0; k < t->count; k++) { + if (!weave[k] && have_tile[k]) { + flat_dst[flat_n++] = t->seg[k].window_rect; + } + } + flat_n += comp_segments_uncovered(t, &f->canvas, &flat_dst[flat_n], COMP_SEGMENTS_MAX_UNCOVERED); + if (flat_n > 0 && f->renderer != nullptr) { + const uint32_t views = f->tile_columns * f->tile_rows; + const uint32_t vi = views > 0 ? (views - 1) / 2 : 0; + const int32_t base_x = (int32_t)((vi % f->tile_columns) * f->view_width); + const int32_t base_y = (int32_t)((vi / f->tile_columns) * f->view_height); + for (uint32_t k = 0; k < flat_n; k++) { + const struct comp_seg_rect *d = &flat_dst[k]; + struct comp_seg_rect s; + if (!comp_segments_tile_rect(d, &f->canvas, f->view_width, f->view_height, &s)) { + continue; // thinner than one source pixel + } + comp_d3d11_renderer_blit_rect(f->renderer, src_srv, base_x + s.x, base_y + s.y, s.w, s.h, rtv, + d->x, d->y, d->w, d->h); + } + } + + // Leave the back buffer bound, viewport = whole target, like the + // single-DP path does after process_atlas. + ctx->OMSetRenderTargets(1, &rtv, nullptr); + { + D3D11_VIEWPORT vp = {}; + vp.Width = (float)f->target_width; + vp.Height = (float)f->target_height; + vp.MaxDepth = 1.0f; + ctx->RSSetViewports(1, &vp); + D3D11_RECT sc = {0, 0, (LONG)f->target_width, (LONG)f->target_height}; + ctx->RSSetScissorRects(1, &sc); + } + src_res->Release(); + return any_woven; +} + +extern "C" void +comp_d3d11_segments_set_display_mode(struct comp_d3d11_segments *segs, bool enable_3d) +{ + if (segs == nullptr) { + return; + } + segs->mode_3d = enable_3d; + for (uint32_t i = 0; i < segs->screen_count; i++) { + struct seg_screen_state *st = &segs->st[i]; + if (st->dp != nullptr && st->mode_sent != (int)enable_3d) { + st->mode_sent = (int)enable_3d; + xrt_display_processor_d3d11_request_display_mode(st->dp, enable_3d); + } + } +} + +extern "C" bool +comp_d3d11_segments_get_eyes(struct comp_d3d11_segments *segs, uint64_t screen_id, struct xrt_eye_positions *out) +{ + if (segs == nullptr || out == nullptr) { + return false; + } + bool ok = false; + os_mutex_lock(&segs->dp_mutex); + const int i = screen_index_of(segs, screen_id); + if (i >= 0 && segs->st[i].dp != nullptr) { + ok = xrt_display_processor_d3d11_get_predicted_eye_positions(segs->st[i].dp, out); + } + os_mutex_unlock(&segs->dp_mutex); + return ok; +} diff --git a/src/xrt/compositor/d3d11/comp_d3d11_segments.h b/src/xrt/compositor/d3d11/comp_d3d11_segments.h new file mode 100644 index 0000000000..52b1bc1d32 --- /dev/null +++ b/src/xrt/compositor/d3d11/comp_d3d11_segments.h @@ -0,0 +1,157 @@ +// Copyright 2026, The DisplayXR Project +// SPDX-License-Identifier: BSL-1.0 +/*! + * @file + * @brief Per-screen segment display processors for the in-process D3D11 + * compositor (multi-screen M6, ADR-047 D2 on Windows). + * @ingroup comp_d3d11 + * + * The D3D11 twin of vk_native/comp_vk_native_segments.h. A window whose + * canvas covers several monitors is woven per segment: each segment (canvas ∩ + * screen, see util/comp_segments.h) gets the display processor of ITS screen, + * a pre-cropped atlas holding exactly that segment's views, `canvas = segment + * rect`, and its own present origin; the results land in the one DXGI back + * buffer. A segment with no DP for its screen (a plug-in without + * `create_dp_d3d11_for_screen`), or whose DP needs 1:1 pixels on a resampled + * screen, gets a flat 2D blit of one view instead. + * + * The session's primary DP (`comp_d3d11_compositor::display_processor`, the + * one holding the real HWND and so the vendor's drag phase-snap) keeps weaving + * the primary screen's segment and keeps owning everything view-related. A + * window entirely on the primary screen never enters this module's record + * path: that case stays byte-for-byte the single-DP path. + * + * Design note: docs/architecture/comp-segments.md (§ Windows / D3D11). + */ +#pragma once + +#include "xrt/xrt_compiler.h" +#include "xrt/xrt_compositor.h" +#include "xrt/xrt_display_processor_d3d11.h" +#include "xrt/xrt_screen.h" +#include "util/comp_segments.h" + +#ifdef __cplusplus +extern "C" { +#endif + +struct comp_d3d11_segments; +struct comp_d3d11_renderer; + +/*! + * Create the segment manager. Nothing is allocated on the GPU until a segment + * needs it. + * + * @param d3d11_device The device segment DPs and crop textures are created on + * (ID3D11Device*, borrowed; under the #918 split this is + * the OUTPUT device, the one the back buffer lives on). + */ +struct comp_d3d11_segments * +comp_d3d11_segments_create(void *d3d11_device); + +/*! + * Tear down every segment DP and crop texture. The caller guarantees no weave + * is in flight (the repaint thread is stopped). + */ +void +comp_d3d11_segments_destroy(struct comp_d3d11_segments **segs_ptr); + +/*! + * Hand over the system's screens and DP registry. Segmentation is enabled only + * when at least two screens are listed, the registry knows the system-default + * screen, and @p pinned_display_id is 0 (`XrSessionDisplayBindingDXR` pins a + * session to one display, which turns segmentation off). + */ +void +comp_d3d11_segments_set_screens(struct comp_d3d11_segments *segs, + const struct xrt_screen_list *list, + const struct xrt_system_compositor_info *info, + uint64_t pinned_display_id); + +//! Is segmentation possible at all for this session? +bool +comp_d3d11_segments_enabled(const struct comp_d3d11_segments *segs); + +/*! + * One metric update: recompute the segment table for the window, run the DP + * lifecycle (create/destroy secondary DPs with hysteresis), log a table change + * once at INFO. Call once per weave, before deciding which path records. + * + * @param window_desktop The window's client area in desktop device px. + * @param canvas The canvas in window px (NULL / empty = whole window). + * @param d3d11_context The context handed to new segment DPs. + * @param mode_index The head's active rendering-mode index; a change + * re-reads each segment DP's resample tolerance. + * @return true when this frame must take the split path + * (@ref comp_d3d11_segments_record); false = the single-DP path. + */ +bool +comp_d3d11_segments_update(struct comp_d3d11_segments *segs, + const struct comp_seg_rect *window_desktop, + const struct comp_seg_rect *canvas, + void *d3d11_context, + uint32_t mode_index); + +//! Everything the split path needs about this frame. +struct comp_d3d11_segments_frame +{ + //! The context to record on (ID3D11DeviceContext*; the output context under the split). + void *context; + //! The DP's input this frame: the atlas cropped to content (ID3D11ShaderResourceView*). + void *src_srv; + uint32_t view_width; + uint32_t view_height; + uint32_t tile_columns; + uint32_t tile_rows; + //! The format declared to the DPs (DXGI_FORMAT of the atlas). + uint32_t src_format; + //! The back buffer (ID3D11RenderTargetView*) and its size. + void *target_rtv; + uint32_t target_width; + uint32_t target_height; + //! The canvas the atlas holds, window px (whole-canvas view dims above). + struct comp_seg_rect canvas; + //! Clear alpha for the target outside every segment; also re-declared to + //! segment DPs when it changes. + bool transparent_background; + //! The atlas encoding to declare to segment DPs (`enum + //! xrt_atlas_encoding`), or -1 to declare nothing. + int atlas_encoding; + //! The session's primary DP (weaves the primary screen's segment). The + //! caller has already fed it this frame's background, timing and encoding. + struct xrt_display_processor_d3d11 *primary_dp; + //! For the flat-2D fill (comp_d3d11_renderer_blit_rect). + struct comp_d3d11_renderer *renderer; +}; + +/*! + * Record the split frame: clear the target, crop each segment's views out of + * the atlas, run each segment's DP over its canvas (primary first, then left + * to right; viewport + scissor = segment), and blit flat 2D into the segments + * that cannot be woven. Leaves the back buffer bound as the render target. + * + * @return true if at least one DP wove. + */ +bool +comp_d3d11_segments_record(struct comp_d3d11_segments *segs, const struct comp_d3d11_segments_frame *f); + +/*! + * The session-wide hardware 2D/3D mode. Every segment DP follows it: sent to + * each live DP on a change and to every newly created one (the primary DP + * gets it from the compositor's own request path). Weave thread. + */ +void +comp_d3d11_segments_set_display_mode(struct comp_d3d11_segments *segs, bool enable_3d); + +/*! + * The predicted eyes of screen @p screen_id's segment DP, in that screen's + * display space. Thread-safe against the weave creating or destroying segment + * DPs. False when the screen has no segment DP (the primary's eyes come from + * the session's own DP). + */ +bool +comp_d3d11_segments_get_eyes(struct comp_d3d11_segments *segs, uint64_t screen_id, struct xrt_eye_positions *out); + +#ifdef __cplusplus +} +#endif diff --git a/src/xrt/compositor/d3d11/comp_d3d11_target.cpp b/src/xrt/compositor/d3d11/comp_d3d11_target.cpp index 3e5e7f8eac..88cb254b50 100644 --- a/src/xrt/compositor/d3d11/comp_d3d11_target.cpp +++ b/src/xrt/compositor/d3d11/comp_d3d11_target.cpp @@ -1156,6 +1156,15 @@ comp_d3d11_target_get_back_buffer(struct comp_d3d11_target *target) return target->back_buffer; } +extern "C" void * +comp_d3d11_target_get_rtv(struct comp_d3d11_target *target) +{ + if (target == nullptr) { + return nullptr; + } + return target->rtv; +} + extern "C" xrt_result_t comp_d3d11_target_resize(struct comp_d3d11_target *target, uint32_t width, diff --git a/src/xrt/compositor/d3d11/comp_d3d11_target.h b/src/xrt/compositor/d3d11/comp_d3d11_target.h index 0b7c0bd262..ffebc23922 100644 --- a/src/xrt/compositor/d3d11/comp_d3d11_target.h +++ b/src/xrt/compositor/d3d11/comp_d3d11_target.h @@ -287,6 +287,16 @@ comp_d3d11_target_bind(struct comp_d3d11_target *target); void * comp_d3d11_target_get_back_buffer(struct comp_d3d11_target *target); +/*! + * Get the back buffer's render target view (ID3D11RenderTargetView*) — the + * view @ref comp_d3d11_target_bind binds. Multi-screen M6: the segment path + * clears it and re-binds it between segment DPs. + * + * @ingroup comp_d3d11 + */ +void * +comp_d3d11_target_get_rtv(struct comp_d3d11_target *target); + /*! * Resize the target swapchain. * diff --git a/src/xrt/drivers/sim_display/sim_display_interface.h b/src/xrt/drivers/sim_display/sim_display_interface.h index da7003b578..eeab51ce07 100644 --- a/src/xrt/drivers/sim_display/sim_display_interface.h +++ b/src/xrt/drivers/sim_display/sim_display_interface.h @@ -283,6 +283,22 @@ sim_display_dp_factory_d3d11(void *d3d11_device, void *window_handle, struct xrt_display_processor_d3d11 **out_xdp); +/*! + * Multi-screen M6: per-screen D3D11 factory, the Windows twin of + * `sim_display_dp_factory_vk_for_screen` — matches + * `xrt_plugin_iface::create_dp_d3d11_for_screen`. The DP describes the bound + * screen and confines its draw to the canvas it is handed. + * + * @ingroup drv_sim_display + */ +xrt_result_t +sim_display_dp_factory_d3d11_for_screen(struct xrt_plugin_instance *inst, + void *d3d11_device, + void *d3d11_context, + void *window_handle, + const struct xrt_screen_binding *binding, + struct xrt_display_processor_d3d11 **out_xdp); + /*! * Create a simulation D3D12 display processor. * diff --git a/src/xrt/drivers/sim_display/sim_display_plugin.c b/src/xrt/drivers/sim_display/sim_display_plugin.c index 464595d421..48ed73b690 100644 --- a/src/xrt/drivers/sim_display/sim_display_plugin.c +++ b/src/xrt/drivers/sim_display/sim_display_plugin.c @@ -371,6 +371,13 @@ static struct xrt_plugin_iface g_sim_display_iface = { #else .create_dp_vk_for_screen = NULL, #endif + + /* Multi-screen M6: one D3D11 DP per screen a spanning window covers. */ +#if defined(_WIN32) + .create_dp_d3d11_for_screen = sim_display_dp_factory_d3d11_for_screen, +#else + .create_dp_d3d11_for_screen = NULL, +#endif }; diff --git a/src/xrt/drivers/sim_display/sim_display_processor_d3d11.cpp b/src/xrt/drivers/sim_display/sim_display_processor_d3d11.cpp index 8380670064..f6999130c0 100644 --- a/src/xrt/drivers/sim_display/sim_display_processor_d3d11.cpp +++ b/src/xrt/drivers/sim_display/sim_display_processor_d3d11.cpp @@ -19,6 +19,7 @@ #include "xrt/xrt_display_processor_d3d11.h" #include "xrt/xrt_display_metrics.h" +#include "xrt/xrt_plugin.h" // xrt_screen_binding (multi-screen M6) #include "util/u_debug.h" #include "util/u_logging.h" @@ -249,6 +250,21 @@ struct sim_display_processor_d3d11_impl //! ADR-042: the FAKE lift module, only when SIM_DISPLAY_FAKE_LIFT=1 (else //! NULL and the five lift slots stay NULL — sim_display ships no module). struct sim_fake_lift *fake_lift; + + //! Multi-screen M6: this instance was created for ONE screen + //! (`create_dp_d3d11_for_screen`); then it describes THAT screen and + //! confines its draw to the canvas it is handed. The unbound DP — every + //! pre-M6 caller — keeps the process-wide panel and the full-target draw. + bool screen_bound; + uint64_t screen_monitor_id; + int32_t screen_left, screen_top; + uint32_t screen_px_w, screen_px_h; + float screen_w_m, screen_h_m; + //! set_present_origin: the window's client-area origin on the bound + //! screen (device px). Stored for parity with the Vulkan DP; sim's D3D11 + //! modes have no phase-sensitive interlace (#817), so it affects no pixel. + bool have_present_origin; + int32_t present_origin_x, present_origin_y; }; static inline struct sim_display_processor_d3d11_impl * @@ -363,7 +379,10 @@ sim_dp_d3d11_process_atlas(struct xrt_display_processor_d3d11 *xdp, } ctx->UpdateSubresource(sdp->tile_cb, 0, nullptr, &tile_data, 0, 0); - // Set viewport + // Set viewport. A screen-bound DP (multi-screen M6) is handed a canvas + // that is ITS segment of the target and must draw only there; the + // unbound DP keeps the pre-M6 full-target draw (its rasterizer state has + // scissor off, so the viewport alone confines the fullscreen strip). D3D11_VIEWPORT viewport = {}; viewport.TopLeftX = 0.0f; viewport.TopLeftY = 0.0f; @@ -371,6 +390,12 @@ sim_dp_d3d11_process_atlas(struct xrt_display_processor_d3d11 *xdp, viewport.Height = static_cast(target_height); viewport.MinDepth = 0.0f; viewport.MaxDepth = 1.0f; + if (sdp->screen_bound && canvas_width > 0 && canvas_height > 0) { + viewport.TopLeftX = static_cast(canvas_offset_x); + viewport.TopLeftY = static_cast(canvas_offset_y); + viewport.Width = static_cast(canvas_width); + viewport.Height = static_cast(canvas_height); + } ctx->RSSetViewports(1, &viewport); // Bind shaders, sampler, SRV, and constant buffer @@ -396,8 +421,102 @@ sim_dp_d3d11_process_atlas(struct xrt_display_processor_d3d11 *xdp, } -// #856: panel geometry -> compositor computes window-scoped Kooima. -SIM_ZONE_DEFINE_PANEL_METRIC_FNS(sim_dp_d3d11, xrt_display_processor_d3d11) +// #856: panel geometry -> compositor computes window-scoped Kooima. A +// screen-bound instance (multi-screen M6) answers for ITS screen; the unbound +// one for the process-wide panel at the desktop origin, as before. +static bool +sim_dp_d3d11_get_display_dimensions(struct xrt_display_processor_d3d11 *xdp, float *out_width_m, float *out_height_m) +{ + struct sim_display_processor_d3d11_impl *sdp = sim_dp_d3d11(xdp); + if (out_width_m == nullptr || out_height_m == nullptr) { + return false; + } + if (sdp->screen_bound) { + *out_width_m = sdp->screen_w_m; + *out_height_m = sdp->screen_h_m; + } else { + sim_display_get_panel_metrics(out_width_m, out_height_m, nullptr, nullptr); + } + return (*out_width_m > 0.0f && *out_height_m > 0.0f); +} + +static bool +sim_dp_d3d11_get_display_pixel_info(struct xrt_display_processor_d3d11 *xdp, + uint32_t *out_pixel_width, + uint32_t *out_pixel_height, + int32_t *out_screen_left, + int32_t *out_screen_top) +{ + struct sim_display_processor_d3d11_impl *sdp = sim_dp_d3d11(xdp); + if (out_pixel_width == nullptr || out_pixel_height == nullptr) { + return false; + } + int32_t left = 0, top = 0; + if (sdp->screen_bound) { + *out_pixel_width = sdp->screen_px_w; + *out_pixel_height = sdp->screen_px_h; + left = sdp->screen_left; + top = sdp->screen_top; + } else { + sim_display_get_panel_metrics(nullptr, nullptr, out_pixel_width, out_pixel_height); + } + if (out_screen_left != nullptr) { + *out_screen_left = left; + } + if (out_screen_top != nullptr) { + *out_screen_top = top; + } + return (*out_pixel_width > 0 && *out_pixel_height > 0); +} + +/*! + * Multi-screen M6: the window's client-area origin on the bound screen. Kept + * for the Vulkan DP's parity (and for a future phase-sensitive D3D11 mode); + * no D3D11 sim mode reads it today (#817). + */ +static void +sim_dp_d3d11_set_present_origin(struct xrt_display_processor_d3d11 *xdp, int32_t panel_x, int32_t panel_y) +{ + struct sim_display_processor_d3d11_impl *sdp = sim_dp_d3d11(xdp); + sdp->have_present_origin = true; + sdp->present_origin_x = panel_x; + sdp->present_origin_y = panel_y; +} + +/*! + * Seed the per-instance screen from a binding (multi-screen M6). Physical size + * from the EDID mm, else the process-wide panel's; pixels from the device + * mode, else the desktop size; origin = the screen's desktop origin. Same + * rules as the Vulkan DP's sim_dp_bind_screen. + */ +static void +sim_dp_d3d11_bind_screen(struct sim_display_processor_d3d11_impl *sdp, const struct xrt_screen_binding *b) +{ + if (b == nullptr || b->struct_size < offsetof(struct xrt_screen_binding, desktop_scale)) { + return; + } + float def_w_m = 0.0f, def_h_m = 0.0f; + uint32_t def_px_w = 0, def_px_h = 0; + sim_display_get_panel_metrics(&def_w_m, &def_h_m, &def_px_w, &def_px_h); + + sdp->screen_bound = true; + sdp->screen_monitor_id = b->monitor_id; + sdp->screen_left = b->desktop_left; + sdp->screen_top = b->desktop_top; + sdp->screen_px_w = b->native_pixel_width != 0 ? b->native_pixel_width : b->desktop_width; + sdp->screen_px_h = b->native_pixel_height != 0 ? b->native_pixel_height : b->desktop_height; + if (sdp->screen_px_w == 0 || sdp->screen_px_h == 0) { + sdp->screen_px_w = def_px_w; + sdp->screen_px_h = def_px_h; + } + if (b->physical_width_mm != 0 && b->physical_height_mm != 0) { + sdp->screen_w_m = (float)b->physical_width_mm / 1000.0f; + sdp->screen_h_m = (float)b->physical_height_mm / 1000.0f; + } else { + sdp->screen_w_m = def_w_m; + sdp->screen_h_m = def_h_m; + } +} static bool sim_dp_d3d11_get_predicted_eye_positions(struct xrt_display_processor_d3d11 *xdp, @@ -838,10 +957,11 @@ sim_dp_d3d11_lift_convert_blob(struct xrt_display_processor_d3d11 *xdp, out_format, out_bytes, out_size); } -extern "C" xrt_result_t -sim_display_processor_d3d11_create(enum sim_display_output_mode mode, - void *d3d11_device, - struct xrt_display_processor_d3d11 **out_xdp) +static xrt_result_t +sim_display_processor_d3d11_create_bound(enum sim_display_output_mode mode, + void *d3d11_device, + const struct xrt_screen_binding *binding, + struct xrt_display_processor_d3d11 **out_xdp) { if (out_xdp == nullptr) { return XRT_ERROR_DEVICE_CREATION_FAILED; @@ -874,6 +994,8 @@ sim_display_processor_d3d11_create(enum sim_display_output_mode mode, sdp->base.get_scanout_caps = sim_dp_d3d11_get_scanout_caps; sdp->base.set_background_2d = sim_dp_d3d11_set_background_2d; // #491 part 3 sdp->base.set_window = sim_dp_d3d11_set_window; // #1008 + sdp->base.set_present_origin = sim_dp_d3d11_set_present_origin; // multi-screen M6 + sim_dp_d3d11_bind_screen(sdp, binding); // #224 / ADR-027 local-zone test double config (shared parser — see // sim_display_zone_common.h for the SIM_DISPLAY_ZONE_GRID / @@ -997,6 +1119,14 @@ sim_display_processor_d3d11_create(enum sim_display_output_mode mode, * */ +extern "C" xrt_result_t +sim_display_processor_d3d11_create(enum sim_display_output_mode mode, + void *d3d11_device, + struct xrt_display_processor_d3d11 **out_xdp) +{ + return sim_display_processor_d3d11_create_bound(mode, d3d11_device, nullptr, out_xdp); +} + extern "C" xrt_result_t sim_display_dp_factory_d3d11(void *d3d11_device, void *d3d11_context, @@ -1010,3 +1140,25 @@ sim_display_dp_factory_d3d11(void *d3d11_device, return sim_display_processor_d3d11_create(mode, d3d11_device, out_xdp); } + +/* + * Multi-screen M6: one D3D11 DP per screen a spanning window covers. The + * window handle is ignored like the plain factory's (sim weaves against no + * window, #1008); the binding seeds the instance's screen. + */ +extern "C" xrt_result_t +sim_display_dp_factory_d3d11_for_screen(struct xrt_plugin_instance *inst, + void *d3d11_device, + void *d3d11_context, + void *window_handle, + const struct xrt_screen_binding *binding, + struct xrt_display_processor_d3d11 **out_xdp) +{ + (void)inst; + (void)d3d11_context; + (void)window_handle; + if (binding == nullptr) { + return XRT_ERROR_DEVICE_CREATION_FAILED; + } + return sim_display_processor_d3d11_create_bound(sim_display_get_output_mode(), d3d11_device, binding, out_xdp); +} diff --git a/src/xrt/include/xrt/xrt_display_processor_d3d11.h b/src/xrt/include/xrt/xrt_display_processor_d3d11.h index ec6b95b69c..4c14b8a4db 100644 --- a/src/xrt/include/xrt/xrt_display_processor_d3d11.h +++ b/src/xrt/include/xrt/xrt_display_processor_d3d11.h @@ -756,6 +756,30 @@ struct xrt_display_processor_d3d11 * @param strength [0,1], or negative for the DP's default. */ void (*set_overlay_2d_filter_strength)(struct xrt_display_processor_d3d11 *xdp, float strength); + + /*! + * Multi-screen M6 (ADR-047 D2 on Windows, the D3D11 twin of + * @ref xrt_display_processor_vk::set_present_origin): where the window's + * client area sits on the DP's screen, so a WINDOWLESS DP (created + * through `create_dp_d3d11_for_screen` with a NULL window) can phase its + * interlace to the panel. Phase = this origin + the canvas offset handed + * to @ref process_atlas, in that screen's device pixels relative to its + * top-left; either may be negative for a window that starts off-screen. + * The compositor calls it every frame it weaves (cheap; a drag moves it). + * + * A DP that has the real HWND derives its phase from the window itself + * and may ignore this call. + * + * Optional — an absent slot (older plug-in `struct_size`) or NULL ⟹ the + * DP has no windowed-phase support and weaves display-scoped. Appended + * after @ref set_overlay_2d_filter_strength per ADR-020 (append-only + * within a major; no version bump — gated by @ref XRT_DP_HAS_SLOT). + * + * @param xdp Pointer to self. + * @param panel_x Window client-area left edge, screen-relative device px. + * @param panel_y Window client-area top edge, screen-relative device px. + */ + void (*set_present_origin)(struct xrt_display_processor_d3d11 *xdp, int32_t panel_x, int32_t panel_y); }; @@ -838,7 +862,8 @@ XRT_DP_ABI_ASSERT(offsetof(struct xrt_display_processor_d3d11, lift_convert) XRT_DP_ABI_ASSERT(offsetof(struct xrt_display_processor_d3d11, lift_convert_blob) == XRT_DP_D3D11_BASE_OFF + 29 * sizeof(void *), XRT_DP_ABI_MSG); XRT_DP_ABI_ASSERT(offsetof(struct xrt_display_processor_d3d11, set_overlay_2d) == XRT_DP_D3D11_BASE_OFF + 30 * sizeof(void *), XRT_DP_ABI_MSG); XRT_DP_ABI_ASSERT(offsetof(struct xrt_display_processor_d3d11, set_overlay_2d_filter_strength) == XRT_DP_D3D11_BASE_OFF + 31 * sizeof(void *), XRT_DP_ABI_MSG); -XRT_DP_ABI_ASSERT(sizeof(struct xrt_display_processor_d3d11) == XRT_DP_D3D11_BASE_OFF + 32 * sizeof(void *), XRT_DP_ABI_MSG); +XRT_DP_ABI_ASSERT(offsetof(struct xrt_display_processor_d3d11, set_present_origin) == XRT_DP_D3D11_BASE_OFF + 32 * sizeof(void *), XRT_DP_ABI_MSG); +XRT_DP_ABI_ASSERT(sizeof(struct xrt_display_processor_d3d11) == XRT_DP_D3D11_BASE_OFF + 33 * sizeof(void *), XRT_DP_ABI_MSG); /*! * Defined when this header carries the five lift slots (lift_get_caps, @@ -892,6 +917,13 @@ XRT_DP_ABI_ASSERT(sizeof(struct xrt_display_processor_d3d11) */ #define XRT_DP_D3D11_HAS_OVERLAY_2D_FILTER_STRENGTH 1 +/*! + * Defined when this header carries the set_present_origin slot (multi-screen + * M6), so a plug-in built against an older runtime can #ifdef-guard its + * implementation. Purely additive (ADR-020). + */ +#define XRT_DP_D3D11_HAS_PRESENT_ORIGIN 1 + // clang-format on /*! @@ -1449,6 +1481,45 @@ xrt_display_processor_d3d11_has_lift_blob(struct xrt_display_processor_d3d11 *xd return XRT_DP_HAS_SLOT(xdp, lift_convert_blob) && xdp->lift_convert_blob != NULL; } +/*! + * @copydoc xrt_display_processor_d3d11::set_present_origin + * + * Helper for calling through the function pointer; a no-op when the slot is + * absent (older plug-in `struct_size`) or NULL. + * + * @public @memberof xrt_display_processor_d3d11 + */ +static inline void +xrt_display_processor_d3d11_set_present_origin(struct xrt_display_processor_d3d11 *xdp, + int32_t panel_x, + int32_t panel_y) +{ + if (!XRT_DP_HAS_SLOT(xdp, set_present_origin) || xdp->set_present_origin == NULL) { + return; + } + xdp->set_present_origin(xdp, panel_x, panel_y); +} + +/*! + * Does the DP's output survive a display-server resample + * (@ref XRT_DP_SCANOUT_FLAG_TOLERATES_RESAMPLE)? Every "didn't answer" case — + * absent slot, NULL, false return — is false: the conservative reading, under + * which the runtime's 1:1 gates behave exactly as they did before the flag. + * The D3D11 twin of @ref xrt_display_processor_vk_tolerates_resample. + * + * @public @memberof xrt_display_processor_d3d11 + */ +static inline bool +xrt_display_processor_d3d11_tolerates_resample(struct xrt_display_processor_d3d11 *xdp) +{ + struct xrt_dp_scanout_caps caps; + xrt_dp_scanout_caps_init(&caps); + if (!xrt_display_processor_d3d11_get_scanout_caps(xdp, &caps)) { + return false; + } + return (caps.flags & XRT_DP_SCANOUT_FLAG_TOLERATES_RESAMPLE) != 0; +} + #ifdef __cplusplus } #endif diff --git a/src/xrt/include/xrt/xrt_plugin.h b/src/xrt/include/xrt/xrt_plugin.h index d3e1034305..69e384879c 100644 --- a/src/xrt/include/xrt/xrt_plugin.h +++ b/src/xrt/include/xrt/xrt_plugin.h @@ -1275,6 +1275,48 @@ struct xrt_plugin_iface int32_t target_format, const struct xrt_screen_binding *binding, struct xrt_display_processor **out_xdp); + + /*! + * Create a D3D11 display processor for ONE screen this plug-in won in + * the per-monitor registry (multi-screen M6, ADR-047 D2 on Windows): the + * in-process D3D11 compositor weaves a window that spans several + * monitors per segment, each by its own screen's DP, into the one + * presented back buffer. + * + * Same contract as @ref create_dp_d3d11 (`xrt_dp_factory_d3d11_fn_t`), + * plus everything @ref create_dp_vk_for_screen promises: + * - @p binding names the screen; never NULL. Vendor DPs resolve it to + * their own display identity (`binding->display_id`, else their + * per-monitor claim) and bind their weaver to THAT display. + * - @p window_handle: the session's real HWND for the DP of the screen + * that holds the MAJORITY of the window (it keeps the vendor's drag + * phase-snap), NULL for every other screen's DP, which is windowless + * and gets its phase from + * @ref xrt_display_processor_d3d11::set_present_origin plus the + * canvas offset. A DP must weave with a NULL window. + * - The compositor binds the back buffer, then calls + * @ref xrt_display_processor_d3d11::process_atlas with + * `canvas = the segment` and a pre-cropped atlas holding exactly that + * segment's views; the DP MUST confine its output — viewport AND + * scissor — to the canvas, and must not assume it is the first + * writer to the back buffer this frame. + * - Several instances coexist in one process, one per screen. + * + * Optional. NULL (or a plug-in whose `struct_size` predates this field) + * ⟹ the plug-in's screens other than the session's primary one get a + * flat 2D view (never a @ref create_dp_d3d11 DP, for the reason given on + * @ref create_dp_vk_for_screen). Appended per ADR-020 (append-only + * within a major; gated by @ref struct_size; no + * XRT_PLUGIN_API_VERSION_CURRENT bump) after + * @ref create_dp_vk_for_screen. Announced by + * @ref XRT_PLUGIN_IFACE_HAS_CREATE_DP_D3D11_FOR_SCREEN. + */ + xrt_result_t (*create_dp_d3d11_for_screen)(struct xrt_plugin_instance *inst, + void *d3d11_device, + void *d3d11_context, + void *window_handle, + const struct xrt_screen_binding *binding, + struct xrt_display_processor_d3d11 **out_xdp); }; /*! @@ -1343,6 +1385,28 @@ xrt_plugin_iface_has_create_dp_vk_for_screen(const struct xrt_plugin_iface *ifac iface->create_dp_vk_for_screen != NULL; } +/*! + * Defined when @ref xrt_plugin_iface carries @ref + * xrt_plugin_iface::create_dp_d3d11_for_screen (multi-screen M6, Windows), so + * a plug-in built against an older runtime header can #ifdef-guard + * implementing it. + */ +#define XRT_PLUGIN_IFACE_HAS_CREATE_DP_D3D11_FOR_SCREEN 1 + +/*! + * True when @p iface implements @ref + * xrt_plugin_iface::create_dp_d3d11_for_screen (and its struct_size covers the + * slot). + */ +static inline bool +xrt_plugin_iface_has_create_dp_d3d11_for_screen(const struct xrt_plugin_iface *iface) +{ + return iface != NULL && + iface->struct_size >= offsetof(struct xrt_plugin_iface, create_dp_d3d11_for_screen) + + sizeof(iface->create_dp_d3d11_for_screen) && + iface->create_dp_d3d11_for_screen != NULL; +} + /*! * True when @p iface fills the whole stereo camera slot set (and its * struct_size covers it). diff --git a/src/xrt/state_trackers/oxr/oxr_session.c b/src/xrt/state_trackers/oxr/oxr_session.c index dd106cb3dd..083eb1c7e8 100644 --- a/src/xrt/state_trackers/oxr/oxr_session.c +++ b/src/xrt/state_trackers/oxr/oxr_session.c @@ -6200,6 +6200,25 @@ oxr_session_create(struct oxr_logger *log, } #endif +#if defined(XRT_HAVE_D3D11_NATIVE_COMPOSITOR) && defined(XRT_OS_WINDOWS) + // Multi-screen M6: the Windows twin — the in-process D3D11 compositor + // weaves a window that spans monitors per segment, each with its own + // screen's DP (windowless, phase from set_present_origin); the primary + // screen's DP keeps the real HWND. + if (sess->is_d3d11_native_compositor && sess->xcn != NULL) { + struct xrt_screen_list *screens = U_TYPED_CALLOC(struct xrt_screen_list); + if (screens != NULL) { + oxr_system_get_screens(sys, screens); + uint64_t pinned = 0; +#ifdef OXR_HAVE_DXR_display_info + pinned = bound_display_id; +#endif + comp_d3d11_compositor_set_screens(&sess->xcn->base, screens, &sys->xsysc->info, pinned); + free(screens); + } + } +#endif + #ifdef OXR_HAVE_DXR_depth_budget // XR_DXR_depth_budget: the create-info chain is gone by locate time, and // the rear-depth DEFAULT is a function of transparency, so remember it. diff --git a/tests/tests_stereo_camera.cpp b/tests/tests_stereo_camera.cpp index d784f11071..51c1db4190 100644 --- a/tests/tests_stereo_camera.cpp +++ b/tests/tests_stereo_camera.cpp @@ -313,13 +313,16 @@ TEST_CASE("plug-in iface: camera slots are appended after lift and platform-stat CHECK(offsetof(xrt_plugin_iface, get_platform_state) == lift_end); CHECK(offsetof(xrt_plugin_iface, stereo_camera_enumerate) == platform_state_end); // Multi-screen M1 appended get_display_info_for_monitor right after the - // camera block, and M2 create_dp_vk_for_screen right after that; it now - // ends the struct. + // camera block, M2 create_dp_vk_for_screen right after that, and M6 + // (Windows) create_dp_d3d11_for_screen right after that; it now ends the + // struct. CHECK(offsetof(xrt_plugin_iface, get_display_info_for_monitor) == offsetof(xrt_plugin_iface, stereo_camera_close) + sizeof(void *)); CHECK(offsetof(xrt_plugin_iface, create_dp_vk_for_screen) == offsetof(xrt_plugin_iface, get_display_info_for_monitor) + sizeof(void *)); - CHECK(offsetof(xrt_plugin_iface, create_dp_vk_for_screen) + sizeof(void *) == sizeof(xrt_plugin_iface)); + CHECK(offsetof(xrt_plugin_iface, create_dp_d3d11_for_screen) == + offsetof(xrt_plugin_iface, create_dp_vk_for_screen) + sizeof(void *)); + CHECK(offsetof(xrt_plugin_iface, create_dp_d3d11_for_screen) + sizeof(void *) == sizeof(xrt_plugin_iface)); xrt_plugin_iface iface{}; iface.struct_size = (uint32_t)offsetof(xrt_plugin_iface, stereo_camera_enumerate);