Repository navigation
feat(cursor): runtime-drawn depth cursor in display-zones frames (#1949) - #1950
Merged
Merged
Conversation
The runtime-drawn cursor (ADR-046 Amendment 2 B) refused zones frames and reported willDraw = false. Now one cursor per frame is drawn in the zone whose zone-scoped locate carried the hint (the zone under the pointer). - State tracker: the arming locate records its XrDisplayZoneDXR zoneId; in a zones frame xrEndFrame maps the placement through the views of the projection layer chained with that zone (zone-normalised map) and hands the compositor the zone rect as submitted. Missing zone / mismatched hint: not drawn, willDraw = false, one-time WARN (same fallback). - u_cursor_draw_into_zone(): re-express the zone map in the tile the way each compositor places the zone's own layer, and return the zone's clip; u_cursor_draw_canvas_px_h(): size against the zone's height. - Metal, vk_native, GL: draw zone requests, scissored to tile ∩ zone. - Tests: N-view (4 eyes) off-axis zone vs full-window oracle, clip and refusals; all 8 mutants killed. - Spec + ADR-046 as-built notes. No new struct, no spec bump. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Shared test_apps/common/cursor_depth_zones.h wraps dxr::CursorDepth for a zones app: pick the topmost 3D zone under the pointer, feed the pointer in that zone's UV, chain the hint only on that zone's locate. Pins displayxr-common v2.29.0 (as the cube_handle apps). DISPLAYXR_CURSOR_DEPTH=0 opts out; DISPLAYXR_CURSOR_DEPTH_UV=u,v scripts a window-normalised pointer. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
dfattal
marked this pull request as ready for review
October 11, 2026 12:35
dfattal
added a commit
that referenced
this pull request
Oct 11, 2026
…es (#1949) Adopt #1950 at the D3D resolve step, matching Metal / vk_native / GL: a zone request is re-expressed in the tile with u_cursor_draw_into_zone() (the window dims the renderers scale zone_3d.rect by), sized against the zone with u_cursor_draw_canvas_px_h(), and scissored to tile box ∩ the returned clip (new comp_d3d11/12_cursor_quads::clip). Skip only on a zones frame / zone request mismatch (one-time WARN). A non-zone request clips to the whole tile, so that path is unchanged. #1951's willDraw gate is state-tracker-side (u_cursor_draw_placement_drawable on located + submitted views); D3D goes through the same dispatch. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
dfattal
added a commit
that referenced
this pull request
Oct 11, 2026
…-046 Amendment 2 §B) (#1944) * feat(cursor): the runtime draws the depth cursor on D3D11 + D3D12 (ADR-046 Amendment 2 B) XR_DXR_cursor_depth v5 on the in-process D3D11 and D3D12 compositors, mirroring the Metal pass (#1941). The state tracker dispatch is additive: oxr_session_cursor_draw_supported / _hand_request now also route to comp_d3d11/12_compositor_set_cursor_draw_request, so these backends report willDraw = true instead of the one-time "cannot draw" WARN. Compositors: the one-frame request is consumed by the next commit (one field test otherwise); a cursor frame never zero-copies; the request is resolved into per-tile corners (u_cursor_draw_corners, sprite metres over canvas metres from the window metrics' pixel pitch) and handed to the renderer before the projection pass. Routed multi-screen frames draw nothing (one-time WARN). Renderers: drawn LAST in the window-space pass - after the projection, quad and window-space layers, before the compose publish and (D3D12) the atlas's final barrier, so before the DP. One straight-alpha strip per tile, depth off, no culling, scissored. Shared HLSL in d3d_shared/comp_cursor_shaders.h, D3DCompile'd on the first cursor frame only (DXR_LAYERED variant samples array layer 0). D3D12 reuses the quad root signature, four PSOs [layered][_SRGB target]. Colour per ADR-044: the frame's compose target + format-honest source view, or the encoded atlas + non-decoding view; a non-_SRGB sprite makes the frame compose. Default look (u_cursor_look, aux, API-agnostic): Windows mirrors the OS cursor - GetCursorInfo -> HCURSOR, DrawIconEx into a 32-bit DIB over black and over white, u_cursor_draw_unblend recovers straight RGBA (handles alpha, colour+mask and monochrome AND/XOR cursors; XOR pixels -> opaque white), at GetSystemMetricsForDpi(SM_CYCURSOR, window DPI), cached by HCURSOR + DPI. While the app has hidden its cursor (SetCursor(NULL)) the class cursor of the window under the pointer is used. Elsewhere or with DXR_CURSOR_DEFAULT_LOOK=arrow: the classic arrow, un-premultiplied. Pure helpers u_cursor_draw_unpremultiply / u_cursor_draw_unblend and the arrow path of u_cursor_look are unit-tested (mutants killed). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * test_apps: depth cursor (XR_DXR_cursor_depth v5) in cube_handle_d3d11/d3d12_win Reference adoption mirroring cube_handle_metal_macos: analytic cube footprint hit test -> XrCursorDepthHintDXR; DISPLAYXR_CURSOR_DEPTH_DRAW= runtime (default) | app; DISPLAYXR_CURSOR_DEPTH_SPRITE=custom (64x64 cyan ring sprite swapchain, filled via UpdateSubresource / CopyTextureRegion); DISPLAYXR_CURSOR_DEPTH_UV=u,v scripted pointer; DISPLAYXR_CURSOR_DEPTH=0 opt-out. The OS cursor is hidden over the client area exactly while willDraw (WM_SETCURSOR -> SetCursor(NULL); D3D12 posts the flip from its render thread to the window thread). App mode keeps the OS cursor and logs the placement (no in-app crosshair on these renderers). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * refactor(cursor): D3D11/D3D12 default look -> the shared comp_cursor_look Follow #1946, which moved the default look into compositor/util/comp_cursor_look (Metal, vk_native, GL): one entry point for every backend instead of a second module in aux. - The Windows GetCursorInfo/DrawIconEx mirror becomes the Windows platform half, comp_cursor_look_win.c (the stub is now !__APPLE__ && !_WIN32; links user32 + gdi32). Backing scale = DPI / 96; height_pt = drawn px / scale. Hidden-cursor fallback (class cursor under the pointer, else last, else IDC_ARROW) and the unreadable-handle memo kept (two additive fields). - aux u_cursor_look.{c,h} removed; u_cursor_draw_unblend now outputs PREMULTIPLIED (the look's contract), u_cursor_draw_unpremultiply dropped. - D3D11/D3D12 renderers blend the default look premultiplied (D3D11: a second blend state; D3D12: a third PSO variant), the app sprite straight. - comp_d3d11 / comp_d3d12 link comp_cursor_look. ADR section updated. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * feat(cursor): D3D11/D3D12 draw the depth cursor in display-zones frames (#1949) Adopt #1950 at the D3D resolve step, matching Metal / vk_native / GL: a zone request is re-expressed in the tile with u_cursor_draw_into_zone() (the window dims the renderers scale zone_3d.rect by), sized against the zone with u_cursor_draw_canvas_px_h(), and scissored to tile box ∩ the returned clip (new comp_d3d11/12_cursor_quads::clip). Skip only on a zones frame / zone request mismatch (one-time WARN). A non-zone request clips to the whole tile, so that path is unchanged. #1951's willDraw gate is state-tracker-side (u_cursor_draw_placement_drawable on located + submitted views); D3D goes through the same dispatch. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * test_apps: depth cursor on cube_zones_d3d11/d3d12_win (display zones, #1949) Same adoption as cube_zones_{metal,vk}_macos: the shared test_apps/common/cursor_depth_zones.h (dxr::CursorDepth, displayxr-common v2.29.0) picks the topmost 3D zone under the pointer and chains the hint on that zone's locate only. The header gains an additive Win32 path (initWin32: dxr::CursorDepth's Win32 window adapter for pointer UV and the WM_SETCURSOR hide; app mode attaches no adapter). The apps enable XR_DXR_cursor_depth unless DISPLAYXR_CURSOR_DEPTH=0; DISPLAYXR_CURSOR_DEPTH_DRAW=runtime|app, DISPLAYXR_CURSOR_DEPTH_UV=u,v (window-normalised). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(cursor): cap the runtime-drawn cursor to the ACTIVE views; log the first OS-cursor mirror (#1944 Windows verification) Two quirks from the Windows panel run of #1944: - The cursor request took proj->viewCount views. An app submitting for the worst-case mode under PRIMARY_MULTIVIEW_DXR (the D3D cube apps submit 4 while the active mode has 2) put inactive views into the request: the one-time notice read "4 views", and those views' poses fed u_cursor_draw_placement_drawable. The arming locate now records its active view count (sess->cursor_draw.view_count) and xrEndFrame caps the request to it. The renderers already clamped drawing to the layout's tiles, so pixels are unchanged. - "default look mirrors the OS cursor" was U_LOG_I, which never reaches the log file, so the mirror looked like it never ran. The first mirror is now a one-time WARN; later shape changes stay INFO. Verified on the win SR laptop: the notice reads "2 views", the mirror line logs "64x64 px at 240 DPI", and David's live mouse check still passes. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes #1949.
The runtime-drawn depth cursor (ADR-046 Amendment 2 §B, #1941 Metal / #1946 Vulkan+GL) skipped display-zones frames and reported
willDraw = false. With this PR it draws one cursor per frame, in the zone whose zone-scoped locate carried the hint (the zone under the pointer).Design
XrDisplayZoneDXR::zoneIdit carried. That locate's canvas is the zone rect, socursorUV, the placement and its unit are zone-relative already. In a zones frame,xrEndFramemaps the placement through the views of the projection layer chained with that zone: its submitted poses and fovs, with the same-space rule unchanged. The result is a zone-normalised map.u_cursor_draw_requestgainszone_valid,zone_idandzone_rect, wherezone_rectis the rect as submitted (authoritative per ADR-027). The fallback is unchanged in three cases: no zone with that id, a zone-scoped hint in a frame without zones, and a zones frame whose hint was not zone-scoped. In each, nothing is drawn, the next locates reportwillDraw = false, and a one-time WARN is logged.u_cursor_draw_into_zone()re-expresses the zone-normalised views in the tile exactly as each compositor places the zone's own layer (zone rect × tile / window). It also returns the zone's sub-rect as a clip.u_cursor_draw_canvas_px_h()sizes the sprite against the zone's height.willDrawalready reports the change to the app frame by frame. The spec text and the ADR-046 as-built notes are updated.Evidence (this Mac, sim-display, scripted pointer
DISPLAYXR_CURSOR_DEPTH_UV, atlas capture)tests_aux_cursor_depth): two new cases.cube_zones_metal_macos(Metal, 2 tiles of 1512×823):willDraw=TRUE, disparity −0.049. The arrow is on zone A's cube in both tiles at x ≈ 385 and ≈ 370, y ≈ 493 (expected 378, 494). It pops out (crossed disparity). Zone B is clean.cube_zones_vk_macos(vk_native, 2 tiles of 1280×720):cube_handle_metal_macos,cube_handle_vk_macosandcube_handle_gl_macos(DISPLAYXR_CURSOR_DEPTH=1) all still draw the cursor in front of the cube in both tiles.test_apps/common/cursor_depth_zones.h, a thin wrapper overdxr::CursorDepth(displayxr-common v2.29.0, the same pin as the cube_handle apps). It picks the topmost 3D zone under the pointer, feeds the pointer in that zone's UV, and chains the hint only on that zone's locate.Not tested
_handleGL zones app on macOS, and thecube_zones_texture_*apps were not adopted. The GL change is covered only by the shared aux math, the matching scissor arithmetic and the non-zones GL regression run.XrCursorDepthSourceDXR) in zones. Zone layers have no depth variant, so that source still reports no content there.🤖 Generated with Claude Code