diff --git a/docs/adr/ADR-047-multi-screen-segments-and-per-screen-views.md b/docs/adr/ADR-047-multi-screen-segments-and-per-screen-views.md index b3f9e110a9..399d24cbea 100644 --- a/docs/adr/ADR-047-multi-screen-segments-and-per-screen-views.md +++ b/docs/adr/ADR-047-multi-screen-segments-and-per-screen-views.md @@ -72,3 +72,16 @@ detail, not the model. service. The first three shipped on the LeiaSR Linux line on 2026-10-07; the fourth needs two panels. - Milestones, gates and the SR work items: `docs/roadmap/multi-screen.md`. + +## Amendment 1 (2026-10-09): segments under the weave-on-scanout split + +The first Windows implementation refused to segment a window whenever the D3D11 #918 +output-device split was engaged, which ADR-039 makes the default on every hybrid box and +keeps even on one adapter, so the shipping configuration never segmented. Decision: **a +segment DP lives on the device that presents.** Under the split that is the output +(scanout) device, where the session's own DP already is: the segment DPs are created there +through the unchanged `create_dp_d3d11_for_screen` slot (it is handed the output device and +context), they weave the output-side copy of the composed atlas (the egress slot), and each +segment keeps its own viewport, scissor and present origin exactly as off the split. The +split is not a separate segment path; the primary screen's DP keeps the real HWND. Detail: +`docs/architecture/comp-segments.md` § *Windows / D3D11*. diff --git a/docs/architecture/comp-segments.md b/docs/architecture/comp-segments.md index 3769848493..700d732884 100644 --- a/docs/architecture/comp-segments.md +++ b/docs/architecture/comp-segments.md @@ -176,8 +176,19 @@ deferred-release list because the immediate context executes in order). What dif 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`. +- **Under the #918 weave-on-scanout split** (ADR-039 — the default on hybrid boxes, and engaged even + on one adapter) the window is segmented the same way, on the device that presents: the segment + manager, its DPs and its crop textures live on the OUTPUT device (`d3d11_out_device()` / + `d3d11_out_context()`, which off the split are the app's), the DPs' input is the egress slot being + woven (the output-side copy of the composed atlas, already cropped to the box that slot was painted + at), and the flat-2D fill draws through the output composite unit (`comp_d3d11_outcomp_blit_rect`, + the renderer's blit re-made on that device). Same function, same table, same per-segment viewport / + scissor / present origin, same #1863 routing (the mosaic is painted on the app device and crosses the + bridge like any atlas). A slot one frame behind a resizing window is cropped at its own view size + against the live canvas — the single-DP split weave's one-frame lag, no more. A split session logs + one WARN when its segment manager is made (`segments: under the #918 output-device split …`). +- **Not segmented on Windows:** zero-copy frames, 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). diff --git a/docs/roadmap/multi-screen.md b/docs/roadmap/multi-screen.md index f950c79e54..6eb06415bc 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`). **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); M3 per-segment views ported too (D3D11 mosaic routing, `get_segment_metrics` / `set_view_routing`), so a straddling window's second panel renders from its own viewer; service/hosted half not started.** | 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); M3 per-segment views ported too (D3D11 mosaic routing, `get_segment_metrics` / `set_view_routing`), so a straddling window's second panel renders from its own viewer; works under the #918 weave-on-scanout split (ADR-039, the hybrid-box default — segment DPs on the output device, fed from the egress slot; ADR-047 Amendment 1), so no `DXR_D3D_FORCE_GPU` / `DXR_SPLIT_SAME_ADAPTER` knobs are needed; 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/comp_d3d11_compositor.cpp b/src/xrt/compositor/d3d11/comp_d3d11_compositor.cpp index 35490c29b2..c611d3e172 100644 --- a/src/xrt/compositor/d3d11/comp_d3d11_compositor.cpp +++ b/src/xrt/compositor/d3d11/comp_d3d11_compositor.cpp @@ -2672,13 +2672,23 @@ d3d11_dp_weave(struct comp_d3d11_compositor *c, bool is_repaint) /* * 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. + * the zero-copy path (the atlas must be ours to crop) 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 stays single-DP, + * byte for byte. + * + * The #918 weave-on-scanout split (ADR-039, the default on hybrid boxes) + * takes the same path: everything here is already on the device that + * presents. The primary DP was made on it, the segment manager is created + * on d3d11_out_device() and hands its DPs d3d11_out_context(), and + * atlas_srv is the egress slot being woven — the output-side copy of the + * composed atlas, already cropped to the box that slot was painted at. A + * slot one frame behind the window (the R2 resize lag) is cropped per + * segment at its own view size against the live canvas, the same + * one-frame lag the single-DP split weave has. */ bool seg_split = false; - if (!zero_copy && !c->split_active && !eff_canvas.valid && c->have_seg_window && c->seg_screens != nullptr && + if (!zero_copy && !eff_canvas.valid && c->have_seg_window && c->seg_screens != nullptr && atlas_srv != nullptr) { if (c->seg_rebuild || c->segments == nullptr) { // The app thread reads c->segments for eyes (M3): swap under the lock. @@ -2693,6 +2703,17 @@ d3d11_dp_weave(struct comp_d3d11_compositor *c, bool is_repaint) comp_d3d11_segments_set_screens(c->segments, c->seg_screens, c->seg_sys_info, c->seg_pinned_display_id); } + // Lifecycle (once per manager), split sessions only so the + // split-off WARN set is unchanged: names the device the + // segment DPs are made on, the rig's proof they present. + if (c->segments != nullptr && c->split_active && + comp_d3d11_segments_enabled(c->segments)) { + U_LOG_W( + "segments: under the #918 output-device split — segment DPs are created on " + "the OUTPUT device (LUID=%08lx:%08lx, device %p) and weave the egress slot", + (unsigned long)c->out_luid.HighPart, (unsigned long)c->out_luid.LowPart, + (void *)c->out_dev); + } } } if (comp_d3d11_segments_enabled(c->segments)) { @@ -2748,7 +2769,7 @@ d3d11_dp_weave(struct comp_d3d11_compositor *c, bool is_repaint) 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; + sf.outcomp = c->outcomp; // on the output device, like the target 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 diff --git a/src/xrt/compositor/d3d11/comp_d3d11_outcomp.cpp b/src/xrt/compositor/d3d11/comp_d3d11_outcomp.cpp index ff3f1aa616..28c71e532a 100644 --- a/src/xrt/compositor/d3d11/comp_d3d11_outcomp.cpp +++ b/src/xrt/compositor/d3d11/comp_d3d11_outcomp.cpp @@ -19,6 +19,7 @@ #include "comp_d3d11_outcomp.h" #include "d3d_shared/comp_masked_composite_shaders.h" +#include "d3d11_layer_shaders.h" // multi-screen M6: the rect blit reuses the renderer's shaders #include "util/u_logging.h" #include "util/u_misc.h" @@ -169,6 +170,18 @@ struct comp_d3d11_outcomp uint32_t weave_scratch_w, weave_scratch_h; //! Grow events, reported once at destroy — the number #918 F10 is about. uint64_t weave_scratch_reallocs; + + /*! + * Multi-screen M6: the scaled rect blit (@ref comp_d3d11_outcomp_blit_rect), + * the renderer's projection shaders + linear sampler re-made on this + * device. Created on first use; @ref blit_failed latches a failed create + * so it is not retried every frame. + */ + ID3D11VertexShader *blit_vs; + ID3D11PixelShader *blit_ps; + ID3D11Buffer *blit_cb; + ID3D11SamplerState *sampler_linear; + bool blit_failed; }; #define SAFE_RELEASE(p) \ @@ -400,6 +413,10 @@ comp_d3d11_outcomp_destroy(struct comp_d3d11_outcomp **outcomp_ptr) } } + SAFE_RELEASE(oc->sampler_linear); + SAFE_RELEASE(oc->blit_cb); + SAFE_RELEASE(oc->blit_ps); + SAFE_RELEASE(oc->blit_vs); SAFE_RELEASE(oc->weave_scratch_srv); SAFE_RELEASE(oc->weave_scratch); SAFE_RELEASE(oc->depth_stencil_state); @@ -620,3 +637,156 @@ comp_d3d11_outcomp_composite_2d_masked(struct comp_d3d11_outcomp *outcomp, rtv->Release(); return XRT_SUCCESS; } + +/*! + * Create the blit's shaders, constant buffer and linear sampler on first use. + * The same HLSL and sampler desc as comp_d3d11_renderer's, so a flat-2D fill + * on the output device matches the one the renderer draws off the split. + */ +static bool +outcomp_ensure_blit(struct comp_d3d11_outcomp *oc) +{ + if (oc->blit_vs != nullptr && oc->blit_ps != nullptr && oc->blit_cb != nullptr && + oc->sampler_linear != nullptr) { + return true; + } + if (oc->blit_failed) { + return false; + } + oc->blit_failed = true; // cleared on success below + + ID3DBlob *blob = nullptr; + if (outcomp_compile_shader(projection_vs_source, "VSMain", "vs_5_0", &blob) != XRT_SUCCESS) { + U_LOG_E("D3D11 outcomp: blit vertex shader compile failed"); + return false; + } + HRESULT hr = + oc->dev->CreateVertexShader(blob->GetBufferPointer(), blob->GetBufferSize(), nullptr, &oc->blit_vs); + blob->Release(); + if (FAILED(hr)) { + U_LOG_E("D3D11 outcomp: blit vertex shader create failed: 0x%08x", hr); + return false; + } + if (outcomp_compile_shader(projection_ps_source, "PSMain", "ps_5_0", &blob) != XRT_SUCCESS) { + U_LOG_E("D3D11 outcomp: blit pixel shader compile failed"); + return false; + } + hr = oc->dev->CreatePixelShader(blob->GetBufferPointer(), blob->GetBufferSize(), nullptr, &oc->blit_ps); + blob->Release(); + if (FAILED(hr)) { + U_LOG_E("D3D11 outcomp: blit pixel shader create failed: 0x%08x", hr); + return false; + } + + D3D11_BUFFER_DESC cb = {}; + cb.ByteWidth = sizeof(LayerConstants); + cb.Usage = D3D11_USAGE_DYNAMIC; + cb.BindFlags = D3D11_BIND_CONSTANT_BUFFER; + cb.CPUAccessFlags = D3D11_CPU_ACCESS_WRITE; + hr = oc->dev->CreateBuffer(&cb, nullptr, &oc->blit_cb); + if (FAILED(hr)) { + U_LOG_E("D3D11 outcomp: blit constant buffer create failed: 0x%08x", hr); + return false; + } + + D3D11_SAMPLER_DESC sd = {}; + sd.Filter = D3D11_FILTER_MIN_MAG_MIP_LINEAR; + sd.AddressU = D3D11_TEXTURE_ADDRESS_CLAMP; + sd.AddressV = D3D11_TEXTURE_ADDRESS_CLAMP; + sd.AddressW = D3D11_TEXTURE_ADDRESS_CLAMP; + hr = oc->dev->CreateSamplerState(&sd, &oc->sampler_linear); + if (FAILED(hr)) { + U_LOG_E("D3D11 outcomp: linear sampler create failed: 0x%08x", hr); + return false; + } + oc->blit_failed = false; + return true; +} + +extern "C" xrt_result_t +comp_d3d11_outcomp_blit_rect(struct comp_d3d11_outcomp *outcomp, + 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 (outcomp == nullptr || src_srv == nullptr || rtv == nullptr || src_w == 0 || src_h == 0 || dst_w == 0 || + dst_h == 0) { + return XRT_ERROR_D3D; + } + if (!outcomp_ensure_blit(outcomp)) { + return XRT_ERROR_D3D; + } + ID3D11DeviceContext *ctx = outcomp->ctx; + 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); + // Scissor is off in this rasterizer state; the viewport alone confines a + // fullscreen strip to the destination rect. + ctx->IASetPrimitiveTopology(D3D11_PRIMITIVE_TOPOLOGY_TRIANGLESTRIP); + ctx->IASetInputLayout(nullptr); + ctx->VSSetShader(outcomp->blit_vs, nullptr, 0); + ctx->PSSetShader(outcomp->blit_ps, nullptr, 0); + ctx->RSSetState(outcomp->rasterizer_state); + ctx->OMSetDepthStencilState(outcomp->depth_stencil_state, 0); + ctx->OMSetBlendState(outcomp->blend_opaque, nullptr, 0xFFFFFFFF); + ctx->PSSetSamplers(0, 1, &outcomp->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(outcomp->blit_cb, 0, D3D11_MAP_WRITE_DISCARD, 0, &mapped); + if (FAILED(hr)) { + return XRT_ERROR_D3D; + } + memcpy(mapped.pData, &constants, sizeof(constants)); + ctx->Unmap(outcomp->blit_cb, 0); + ctx->VSSetConstantBuffers(0, 1, &outcomp->blit_cb); + ctx->PSSetConstantBuffers(0, 1, &outcomp->blit_cb); + + 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_outcomp.h b/src/xrt/compositor/d3d11/comp_d3d11_outcomp.h index 1ff54967a4..b55122ef8c 100644 --- a/src/xrt/compositor/d3d11/comp_d3d11_outcomp.h +++ b/src/xrt/compositor/d3d11/comp_d3d11_outcomp.h @@ -177,6 +177,37 @@ comp_d3d11_outcomp_composite_2d_masked(struct comp_d3d11_outcomp *outcomp, uint32_t composite_mode, bool opaque_present); +/*! + * Scaled rect blit on the unit's device: sample the @p src_w × @p src_h + * sub-rect at (@p src_x, @p src_y) of @p src_srv and write it, linearly + * scaled, into the @p dst_w × @p dst_h rect at (@p dst_x, @p dst_y) of + * @p rtv. Opaque, no scissor, no depth. + * + * The output-device twin of comp_d3d11_renderer_blit_rect, with the same + * shaders, sampler and fixed-function state, so the pixels are identical; it + * exists because the destination is the weave target, which under the #918 + * split lives on the scanout device the renderer knows nothing about. + * Multi-screen M6 uses it for the flat-2D fill of segments that cannot be + * woven. The shaders are compiled on first use, so a session that never + * blits pays nothing. + * + * Leaves @p rtv bound and the viewport at the destination rect. + * + * @ingroup comp_d3d11 + */ +xrt_result_t +comp_d3d11_outcomp_blit_rect(struct comp_d3d11_outcomp *outcomp, + 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 } #endif diff --git a/src/xrt/compositor/d3d11/comp_d3d11_renderer.cpp b/src/xrt/compositor/d3d11/comp_d3d11_renderer.cpp index 1ba61f1cd1..c42eeb7ab4 100644 --- a/src/xrt/compositor/d3d11/comp_d3d11_renderer.cpp +++ b/src/xrt/compositor/d3d11/comp_d3d11_renderer.cpp @@ -2616,90 +2616,3 @@ 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 e1a1a28f73..11ddb1effb 100644 --- a/src/xrt/compositor/d3d11/comp_d3d11_renderer.h +++ b/src/xrt/compositor/d3d11/comp_d3d11_renderer.h @@ -372,36 +372,6 @@ 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 } #endif diff --git a/src/xrt/compositor/d3d11/comp_d3d11_segments.cpp b/src/xrt/compositor/d3d11/comp_d3d11_segments.cpp index 6964b2236f..07ee20a6ba 100644 --- a/src/xrt/compositor/d3d11/comp_d3d11_segments.cpp +++ b/src/xrt/compositor/d3d11/comp_d3d11_segments.cpp @@ -17,7 +17,7 @@ #include "comp_d3d11_segments.h" -#include "comp_d3d11_renderer.h" +#include "comp_d3d11_outcomp.h" #include "d3d/d3d_dxgi_formats.h" #include "os/os_threading.h" #include "util/u_logging.h" @@ -655,7 +655,7 @@ comp_d3d11_segments_record(struct comp_d3d11_segments *segs, const struct comp_d } } flat_n += comp_segments_uncovered(t, &f->canvas, &flat_dst[flat_n], COMP_SEGMENTS_MAX_UNCOVERED); - if (flat_n > 0 && f->renderer != nullptr) { + if (flat_n > 0 && f->outcomp != 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); @@ -666,8 +666,8 @@ comp_d3d11_segments_record(struct comp_d3d11_segments *segs, const struct comp_d 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); + comp_d3d11_outcomp_blit_rect(f->outcomp, src_srv, base_x + s.x, base_y + s.y, s.w, s.h, rtv, + d->x, d->y, d->w, d->h); } } diff --git a/src/xrt/compositor/d3d11/comp_d3d11_segments.h b/src/xrt/compositor/d3d11/comp_d3d11_segments.h index 187e39814b..bd47bfab28 100644 --- a/src/xrt/compositor/d3d11/comp_d3d11_segments.h +++ b/src/xrt/compositor/d3d11/comp_d3d11_segments.h @@ -37,15 +37,17 @@ extern "C" { #endif struct comp_d3d11_segments; -struct comp_d3d11_renderer; +struct comp_d3d11_outcomp; /*! * 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). + * (ID3D11Device*, borrowed): the device that PRESENTS, + * i.e. the one the back buffer lives on — under the #918 + * weave-on-scanout split (ADR-039) that is the OUTPUT + * device, off it the app device. */ struct comp_d3d11_segments * comp_d3d11_segments_create(void *d3d11_device); @@ -99,6 +101,8 @@ 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*). + //! Under the #918 split this is the output-side copy of the composed atlas + //! (the egress slot being woven), so it lives on the same device as the DPs. void *src_srv; uint32_t view_width; uint32_t view_height; @@ -121,8 +125,9 @@ struct comp_d3d11_segments_frame //! 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; + //! For the flat-2D fill (comp_d3d11_outcomp_blit_rect): the output + //! composite unit, which lives on the same device as the target. + struct comp_d3d11_outcomp *outcomp; }; /*! diff --git a/tests/tests_comp_segments.cpp b/tests/tests_comp_segments.cpp index 4bec2395be..2fe54312a9 100644 --- a/tests/tests_comp_segments.cpp +++ b/tests/tests_comp_segments.cpp @@ -10,6 +10,7 @@ #include "catch_amalgamated.hpp" +#include #include namespace { @@ -451,3 +452,35 @@ TEST_CASE("tile rects: adjacent segments share the seam column (M3 routing == M2 const comp_seg_rect zero = {0, 0, 0, 0}; CHECK_FALSE(comp_segments_tile_rect(&left, &zero, 800, 900, &a)); } + +TEST_CASE("tile rects: a split egress slot one frame behind the window still partitions its own tile", + "[comp_segments]") +{ + // Under the #918 weave-on-scanout split the D3D11 segments crop the egress + // slot being woven, which during a resize drag was painted for the + // PREVIOUS window size (the R2 lag): the tile is the slot's own, the canvas + // the live window. The crop must still cover that tile exactly, with + // neighbours sharing the seam column, whichever way the window moved. + // Live window: 1649 px wide, straddling a seam at window x = 825 (odd, so + // the scaled seam is fractional). + const comp_seg_rect canvas = {0, 0, 1649, 1788}; + const comp_seg_rect left = {0, 0, 825, 1788}; + const comp_seg_rect right = {825, 0, 824, 1788}; + // Slot tiles one frame behind a growing (800) and a shrinking (840) + // window, plus the matched one (824/825 ~ 1649 / 2). + const uint32_t tile_ws[] = {800, 824, 840}; + for (uint32_t tw : tile_ws) { + CAPTURE(tw); + comp_seg_rect a, b; + REQUIRE(comp_segments_tile_rect(&left, &canvas, tw, 894, &a)); + REQUIRE(comp_segments_tile_rect(&right, &canvas, tw, 894, &b)); + CHECK(a.x == 0); + CHECK(a.x + (int32_t)a.w == b.x); // no gap, no overlap + CHECK(b.x + (int32_t)b.w == (int32_t)tw); + CHECK(a.h == 894); + CHECK(b.h == 894); + // The seam sits where the live window puts it, scaled to the slot. + const double seam = 825.0 * tw / 1649.0; + CHECK(std::abs((double)b.x - seam) <= 0.5); + } +}