Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 6 additions & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ jobs:
run: cmake -S . -B build -G Ninja -DCMAKE_BUILD_TYPE=Release

- name: Build
run: cmake --build build --target displayxr_linux_window_test displayxr_linux_window_lattice_test displayxr_linux_window_drag_test displayxr_linux_window_input_test displayxr_url_fetch_test displayxr_launch_args_test
run: cmake --build build --target displayxr_linux_window_test displayxr_linux_window_lattice_test displayxr_linux_window_drag_test displayxr_linux_window_input_test displayxr_url_fetch_test displayxr_launch_args_test displayxr_cursor_depth_test displayxr_cursor_depth_x11_compile

- name: Test (launch contract + SHA-1 cache key)
run: ./build/tests/displayxr_launch_args_test
Expand All @@ -93,6 +93,11 @@ jobs:
- name: Test (probe + arg parsing, no display)
run: ./build/tests/displayxr_linux_window_test

- name: Test (dxr::CursorDepth core + the X11 cursor adapter under Xvfb)
run: |
./build/tests/displayxr_cursor_depth_test
xvfb-run -a -s "-screen 0 1280x800x24" ./build/tests/displayxr_cursor_depth_x11_compile

- name: Test (X11 windows under Xvfb)
run: xvfb-run -a -s "-screen 0 1920x1080x24" env DXR_LW_TEST_X11=1 ./build/tests/displayxr_linux_window_test

Expand Down
16 changes: 15 additions & 1 deletion CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -207,7 +207,11 @@ else()
# XR_DXR_wayland_surface_binding.h SPEC_VERSION 2 (the surface-geometry
# struct + transparentBackgroundEnabled that displayxr::linux_window
# chains; the previous pin had spec 1).
GIT_TAG 4c3fa8e816f0224844e12e59374f01f9d8d50f9a
# Bumped 2026-10-11 to displayxr-extensions@main (runtime c4a9d06b5):
# XR_DXR_cursor_depth.h SPEC_VERSION 5 (the runtime-drawn cursor's
# XrCursorDepthDrawInfoDXR / XrCursorDepthDrawStateDXR), which
# common/cursor_depth.h needs.
GIT_TAG af2de88348ab062a21344bf4d27c19eba40dfd8a
)
FetchContent_GetProperties(displayxr_extensions_headers)
if(NOT displayxr_extensions_headers_POPULATED)
Expand Down Expand Up @@ -279,6 +283,7 @@ set(DISPLAYXR_COMMON_SOURCES
common/content_bounds.h # header-only: ProjectAabbToCanvasBounds + ChainContentBounds (XR_DXR_depth_budget v2)
common/content_mask.h # header-only: ContentMaskFromCoverage + ChainContentMask (XR_DXR_depth_budget v3)
common/dxr_view_config.h # header-only: DxrSelectViewConfigType() PRIMARY_MULTIVIEW_DXR opt-in (runtime #1486)
common/cursor_depth.h # header-only core: dxr::CursorDepth, XR_DXR_cursor_depth v5 (ADR-046 Amendment 2)
)
if(WIN32)
list(APPEND DISPLAYXR_COMMON_SOURCES
Expand All @@ -299,13 +304,15 @@ if(WIN32)
common/text_overlay.h
common/hud_renderer.cpp
common/hud_renderer.h
common/cursor_depth_win32.cpp # dxr::cursorPlatformWin32 (pointer + WM_SETCURSOR hide)
)
elseif(APPLE)
list(APPEND DISPLAYXR_COMMON_SOURCES
common/atlas_capture_macos.mm
common/hud_renderer_macos.mm
common/hud_renderer_macos.h
common/stb_image_impl_macos.cpp
common/cursor_depth_macos.mm # dxr::cursorPlatformCocoa (pointer + NSCursor hide)
)
endif()

Expand Down Expand Up @@ -337,6 +344,13 @@ endif()
# frame ring most (Adreno DVFS, modelviewer #170). They need <vulkan/vulkan.h>
# on the include path and the consumer's existing Vulkan link; nothing is added
# here.
#
# common/cursor_depth.h (dxr::CursorDepth) likewise: its core is header-only
# and needs only <openxr/XR_DXR_cursor_depth.h> (SPEC_VERSION >= 5), so an
# Android leg gets the footprint rays + struct chaining through this target
# and drives it with setPointerUV(). Its Win32 / Cocoa window adapters are
# compiled into displayxr::common; the X11 one is header-only
# (cursor_depth_x11.h) because this library does not link libX11.
add_library(displayxr_rules INTERFACE)
add_library(displayxr::rules ALIAS displayxr_rules)
target_include_directories(displayxr_rules INTERFACE
Expand Down
56 changes: 56 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -141,6 +141,7 @@ The `common/` directory is the lib's second target (epic #396 W4, re-scoped [#39
| View-configuration opt-in: `DxrSelectViewConfigType()` — begin the session with `PRIMARY_MULTIVIEW_DXR` when the runtime advertises it; `DxrAliasInactiveViews()` — submit every located view, aliasing the ones the active mode does not use (ADR-041) | `dxr_view_config.h` | both (also `displayxr::rules`, so Linux/Android legs get it) |
| Vulkan **frame ring**: N frames in flight (default 3), ONE `vkQueueSubmit` per frame, per-slot command buffer + fence + dynamic-offset UBO slice allocated once, tile barriers, deferred deletion, one named blocking escape hatch (`submitImmediate`) — see *Vulkan frame discipline* below | `vk_frame_ring.h` | all (also `displayxr::rules`, so Linux/Android legs get it; needs `<vulkan/vulkan.h>`) |
| Vulkan overlay kit: content-hash-gated layer uploads (`CachedLayerUploader`) + fence-pipelined readbacks (`PipelinedReadback`), each with ONE reused command buffer ([runtime#837](https://github.com/DisplayXR/displayxr-runtime/issues/837)) | `vk_overlay_kit.h` | all (needs `<vulkan/vulkan.h>`) |
| Depth-aware cursor (`XR_DXR_cursor_depth` v5, ADR-046 Amendment 2): `dxr::CursorDepth` — pointer -> canvas UV, footprint rays through last frame's views, nearest hit, struct chaining, OS cursor hidden exactly while the runtime draws; `dxr::cursorFootprintRays()` | `cursor_depth.h` (+ `cursor_depth_win32.cpp`, `cursor_depth_macos.mm`, header-only `cursor_depth_x11.h`) | core: all (also `displayxr::rules`); adapters: Win32, macOS, X11 |

**Divergence policy:** behavior differences between consumers are parameterized at the call site (e.g. `InputState::hudToggleRequiresShift`, `EndFrame(..., projectionLayerFlags)`) — never `#ifdef APP` in the lib. Request-flag fields that only one app consumes (file picker, clip playback, transparency toggle) are fine: unconsumed flags are inert.

Expand Down Expand Up @@ -685,6 +686,61 @@ if (const uint8_t* cov = punch.coverage()) {
}
```

## Depth-aware cursor (`cursor_depth.h`, `XR_DXR_cursor_depth` v5)

The runtime **draws** the depth cursor (ADR-046 Amendment 2 §B): a stereo sprite lifted just in
front of whatever content pops out of the screen under the pointer, and nothing at all (the OS
cursor is the cursor) over content on or behind the glass. The app only owns the depth under the
pointer. `dxr::CursorDepth` does the rest of the bookkeeping:

```cpp
#include "cursor_depth.h"

dxr::CursorDepth cursor; // inert until enable()
cursor.enable(cursorDepthSpecVersion); // extensionVersion if you enabled XR_DXR_cursor_depth, else 0
cursor.setWindowCocoa((__bridge void *)view); // or setWindowWin32(hwnd), or
// setPlatform(dxr::cursorPlatformX11(dpy, win))
cursor.setRayCast([&](const dxr::CursorRay &r) -> std::optional<float> {
return scene.raycast(r.origin, r.dir); // t of the first hit, or std::nullopt
});

// every frame:
cursor.chain(locateInfo, viewState); // pointer UV + hit test + struct chaining
xrLocateViews(session, &locateInfo, &viewState, n, &n, views.data());
cursor.afterLocate(views.data(), activeViewCount);// hides the OS cursor exactly while willDraw
```

- **Zero cost when not enabled:** `enable(0)` (or never calling it) leaves both struct chains
untouched and never touches the OS cursor. Enabled, a frame allocates nothing.
- **Depth source:** `setRayCast` (ray -> nearest hit; the helper fires rays from the two outer
views through the cursor's footprint and keeps the hit nearest the viewer), `setHitTest` (full
control: you get the views, the UV and the ready-made rays), or `useDepthLayer(true)` (spec v2:
the runtime reads the depth you submit with `XR_KHR_composition_layer_depth`).
- **Look:** `setSprite(swapchain, hotSpot, sizeMeters, aspect)`; the default (`XR_NULL_HANDLE`)
is the runtime's own look, the OS cursor's shape or the classic arrow. The footprint follows
the sprite: the arrow's is the tip plus a ring around its body, the same rule as the runtime
and the web SDK (`cursorArrowFootprint`).
- **Options:** `setAnchor()`, `setMargin()` (spec v3), `setCursorHeight()` (footprint size,
default 0.03 of the canvas height), `setWantPlacement(true)` for diagnostics.
- **App-drawn cursor** (v1-v4 style): `setDrawMode(DrawMode::App)`, then draw your sprite at
`placement().pose` / `placement().height` while `appShouldDraw()`.
- **Never no cursor:** the OS cursor is hidden only while the runtime reports `willDraw`, so a
backend that cannot draw yet (spec v5: Metal draws first) leaves the flat OS cursor.

**Platforms.** The core is header-only and platform-neutral (it ships in `displayxr::rules`, so
Android legs can use it). Window adapters:

| Window | Adapter | Pointer | OS-cursor hide |
|---|---|---|---|
| Win32 `HWND` | `setWindowWin32(hwnd)` (`cursor_depth_win32.cpp`) | `GetCursorPos` -> client rect | answers `WM_SETCURSOR`/`HTCLIENT` with `SetCursor(NULL)` via a chained window proc; client area only |
| Cocoa `NSView*` | `setWindowCocoa(view)` (`cursor_depth_macos.mm`) | `mouseLocationOutsideOfEventStream` -> view bounds | `NSCursor hide/unhide`, balanced, on the main thread |
| Xlib `(Display*, Window)` | `setPlatform(dxr::cursorPlatformX11(dpy, win))` (header-only `cursor_depth_x11.h`; the app links libX11) | `XQueryPointer` | an invisible pixmap cursor on the window |
| Anything else (Wayland, Android, `displayxr::linux_window`, a custom toolkit) | manual: `setPointerUV(u, v)` / `clearPointerUV()` + `onDepthCursorShownChanged(cb)` | yours | yours, e.g. `win.set_cursor(shown ? DxrCursor::Hidden : DxrCursor::Default)` |

`dxr::cursorFootprintRays(views, activeCount, uv, heightFrac)` is usable on its own for an app that
keeps its own chaining. Reference adoption: the runtime's
`test_apps/handle/cube_handle_metal_macos`.

## Client-side window chrome (`csd_titlebar.h`)

A windowed 3D app on Linux draws its own title bar. On X11 that is how the app
Expand Down
Loading
Loading