Repository navigation
feat(cursor): the runtime draws the depth cursor — Vulkan + GL (ADR-046 Amendment 2 §B) - #1946
Merged
Merged
Conversation
…R-046 Amendment 2 B) XR_DXR_cursor_depth v5 on the in-process vk_native and GL compositors; no new structs, no spec bump. - Shared default look: the Metal compositor's OS-cursor mirror moved to compositor/util/comp_cursor_look (+ _macos.m), a small lib Metal, vk_native and GL link. Mirrored [NSCursor currentCursor] on the main thread on macOS, else the last snapshot, else the procedural arrow; DXR_CURSOR_DEFAULT_LOOK=arrow forces it. Other platforms: the arrow. - vk_native: request consumed by the next commit, resolved to per-view tile corners; a cursor frame never zero-copies and always takes the renderer's compose pass, where the cursor is the last draw (QUAD vertex path, affine MVP, scissored per tile) into the private _SRGB target, published by the pass's raw copy - linear blend, sprite sampled through the single compose_source_view(), atlas stays ENCODED. Drawn under window-space layers (stamped later, at weave time) - documented. - GL: drawn after the window-space layers into whatever the atlas pass paints (compose target or encoded atlas), source bound through gl_bind_layer_source(); a cursor frame counts as a sub-rect layer in the fast-path predicate; GL sprites are sampled bottom-up. - State tracker: additive is_vk_native / is_gl_native arms in oxr_session_cursor_draw_supported/_hand_request. - ADR-046 "B as built on Vulkan and GL", spec backends note, DXR_* census. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…or via dxr::CursorDepth (XR_DXR_cursor_depth v5) Same adoption as cube_handle_metal_macos (#1943): the app keeps only its cube ray test (RayHitCube) and env knobs; pointer UV, footprint rays, the struct chains and OS-cursor hide/show are displayxr-common's dxr::CursorDepth (v2.29.0, the aggregate test_apps pin). DISPLAYXR_CURSOR_DEPTH_DRAW=runtime (default) | app, DISPLAYXR_CURSOR_DEPTH_SPRITE=custom (64x64 cyan ring sprite swapchain; GL uploads it bottom row first), DISPLAYXR_CURSOR_DEPTH_UV, DISPLAYXR_CURSOR_DEPTH=0 opt-out. The GL cube had no cursor-depth code: it gains RayHitCube, the helper, and an app-mode crosshair. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
dfattal
marked this pull request as ready for review
October 11, 2026 10:32
…9.0 standalone pins
- cube_handle_gl_macos: XR_DXR_cursor_depth is now OPT-IN
(DISPLAYXR_CURSOR_DEPTH=1). With the OS cursor hidden the GL cube
crashes ~1 in 5 launches in Apple's GL driver (GL compositor repaint
thread vs AppKit, runtime#1947). Metal and Vulkan cubes stay default-on.
- cube_handle_{metal,vk,gl}_macos: standalone FetchContent pin of
displayxr-common -> v2.29.0 (dxr::CursorDepth), matching the aggregate
test_apps pin; each app builds standalone.
- ADR-046: link runtime#1947 and runtime#1948.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
dfattal
added a commit
that referenced
this pull request
Oct 11, 2026
…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>
This was referenced Oct 11, 2026
Merged
dfattal
added a commit
that referenced
this pull request
Oct 11, 2026
…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>
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>
dfattal
added a commit
that referenced
this pull request
Oct 12, 2026
Undo the #1946 opt-in (DISPLAYXR_CURSOR_DEPTH=1) now that the crash it worked around is fixed in the GL compositor: like the Metal and Vulkan cubes, the cursor is on by default and DISPLAYXR_CURSOR_DEPTH=0 opts out. 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.
Extends ADR-046 Amendment 2 §B (the runtime draws the depth cursor) from Metal (#1941) to the in-process vk_native and GL compositors. XR_DXR_cursor_depth stays at spec v6 — no new structs, no new XrStructureType, no version bump. This implements the existing v5
XrCursorDepthDrawInfoDXR/XrCursorDepthDrawStateDXR.What changed
compositor/util/comp_cursor_look.{h,c}+comp_cursor_look_macos.m, a small library that Metal, vk_native and GL all link. It uses the mirrored[NSCursor currentCursor]on the main thread, otherwise the last snapshot, otherwise the procedural arrow fromu_cursor_draw_arrow_rgba().DXR_CURSOR_DEFAULT_LOOK=arrowstill forces the arrow. Metal now uses the shared module, and its behaviour is unchanged (re-measured below). OS-cursor mirroring works on macOS only. Linux (XFixesGetCursorImage) and Windows draw the arrow, and that is noted in the ADR as future work._SRGBtarget and publishes to the atlas with a raw copy, so the sprite blends in linear light and the atlas stays ENCODED (no double encode).compose_source_view(), the same source-view choice every layer uses.tests_comp_color_policyholds that choice to one code line.gl_bind_layer_source().gl_frame_takes_fast_path().is_vk_native_compositor/is_gl_native_compositorinoxr_session_cursor_draw_supported/_hand_request.oxr_session_frame_end.cis untouched.cube_handle_vk_macosandcube_handle_gl_macosnow adopt the cursor throughdxr::CursorDepth(displayxr-common v2.29.0, the same way as test_apps(cube_handle_metal_macos): adopt the depth cursor through dxr::CursorDepth (needs displayxr-common tag) #1943). Supported env vars:DISPLAYXR_CURSOR_DEPTH_DRAW=runtime|app,DISPLAYXR_CURSOR_DEPTH_SPRITE=custom,DISPLAYXR_CURSOR_DEPTH_UV,DISPLAYXR_CURSOR_DEPTH=0(on the GL cube the cursor is opt-in withDISPLAYXR_CURSOR_DEPTH=1, see GL compositor repaint thread vs AppKit cursor hide: ~1/5 crash in Apple's GL driver (cube_handle_gl_macos) #1947). The GL cube had no cursor-depth code before; it now has the cube ray test and an app-mode crosshair.DXR_CURSOR_DEFAULT_LOOKcensus row is updated.Evidence (this Mac, sim-display Cropped SBS,
cursorUV0.5,0.5 over the cube)DISPLAYXR_CURSOR_DEPTH_DRAW=app: the app's amber crosshair only, and no runtime pixels.DISPLAYXR_CURSOR_DEPTH=0: nothing.$TMPDIR/displayxr_atlas_trigger. The GL capture comes from/tmp/dxr_atlas_trigger, written after the cursor and the compose publish.tests_aux_cursor_depthandtests_comp_color_policypass. ctest is 105/108; the 3 failures are not caused by this change:tests_service_client_class: path comparison on a case-insensitive filesystem.tests_oxr_view_space_mode_pinned: CI already excludes this suite.tests_stereo_camera_manager: a load flake that passes on rerun.check_displayxr_app.pyreports nothing new. The vk app's INV-5.9 (enable1) and the missing manifest warnings were already there.Known issue found (not caused by the runtime draw)
cube_handle_gl_macoscrashes in about 1 of 5 launches once the depth cursor is active. The crash is inside Apple's GL driver, either on the #868 repaint thread (gl_repaint_thread→glBlitFramebuffer) or in the app's own draw.It follows the app's
[NSCursor hide], not the runtime draw:DXR_WEAVE_REPAINT=0So this looks like a latent race between the GL repaint thread and AppKit, which surfaces now that the GL cube hides the OS cursor. It is tracked as #1947. Until that is fixed, the GL cube's depth cursor is opt-in (
DISPLAYXR_CURSOR_DEPTH=1); the Metal and Vulkan cubes stay default-on. vk_native showed no crashes in about 30 launches.Also found in passing: #1948. On the Vulkan-app-on-Metal fallback, a custom sprite is handed to the Metal compositor as the client swapchain and cast to the native Metal type (#1941; found by reading the code, not reproduced).
Standalone pins: the displayxr-common FetchContent pin of cube_handle_metal/vk/gl_macos is now v2.29.0, the same as the aggregate pin. Each app was configured and built standalone and builds.
Not tested
glCopyImageSubData, so every macOS GL frame composes in encoded space. The Windows GL path is untested.arraySize > 1) sprites: vk handles them through the array pipeline, but that path was not run. GL skips them with a one-time WARN.DXR_COLOR_LEGACY_UNORM_ENCODED=1).🤖 Generated with Claude Code