Skip to content

refactor(capture): migrate atlas capture to xrCaptureAtlasEXT [#396 W6]#141

Open
dfattal wants to merge 1 commit into
mainfrom
feat/396-w6-atlas-capture
Open

refactor(capture): migrate atlas capture to xrCaptureAtlasEXT [#396 W6]#141
dfattal wants to merge 1 commit into
mainfrom
feat/396-w6-atlas-capture

Conversation

@dfattal
Copy link
Copy Markdown
Collaborator

@dfattal dfattal commented Jun 4, 2026

Migrate the plugin's I-key atlas/screenshot capture from app-side GPU readback to the runtime-owned xrCaptureAtlasEXT (XR_EXT_atlas_capture). W6 of #396 (unified capture). Resolves #140.

A live OpenXR session now hands the runtime a path prefix; the runtime reads back the compositor's own composited atlas and writes <prefix>_atlas.png. The plugin no longer does an AsyncGPUReadback or a hidden-camera Kooima re-render for live sessions — and the runtime can now capture the true post-compose atlas (window-space / quad layers) which the old swapchain readback physically couldn't see.

Three layers

  • native~/displayxr_extensions.h — vendor the XR_EXT_atlas_capture defs (XrAtlasCaptureInfoEXT/ResultEXT, XrAtlasCaptureStageEXT, PFN_xrCaptureAtlasEXT, struct-type values 1000999120/121, path-max 256), matching the existing in-file style. Source of truth: displayxr-runtime/.../XR_EXT_atlas_capture.h.
  • native~/displayxr_hooks.{cpp,h}static PFN_xrCaptureAtlasEXT s_pfn_capture_atlas, resolved via the existing s_next_gipa(s_instance, "xrCaptureAtlasEXT", &fn) chain at the same call site as xrSetSharedTextureOutputRectEXT. New export displayxr_capture_atlas(prefix, stage) builds XrAtlasCaptureInfoEXT (strncpy prefix, cap 255) and calls the runtime, returning 1 on XR_SUCCEEDED.
  • Runtime/DisplayXRFeature.cs"XR_EXT_atlas_capture" added to ExtensionStrings; CaptureAtlas(string pathPrefix, bool projectionOnly = true) guarded on m_HooksInstalled like RequestDisplayMode.
  • Runtime/DisplayXRNative.cs[DllImport] displayxr_capture_atlas([MarshalAs(LPStr)] string, int) near displayxr_request_display_mode.
  • Runtime/DisplayXRScreenshot.cs — gutted BeginCapture/CaptureOnDemand/s_CaptureCam/EnsureCaptureCamera/GetStereoMatrices/FlipViewZ for the live path. Capture() now computes the numbered <stem>-<N>_<cols>x<rows> prefix (mirrors the demos' MakeCaptureAtlasPrefix; number against ..._atlas.png, return prefix without extension) and calls DisplayXRFeature.CaptureAtlas(prefix, true). Flash overlay + filename numbering kept.

Scope notes

  • Editor-preview path out of scope: _OnAtlasReady / displayxr_standalone_is_running() path left fully intact (it still encodes app-side — there is no runtime OpenXR session in pure-editor preview). The app-side BeginCapture readback is kept only for that path.
  • native~/displayxr_readback.{cpp,h} untouched (unrelated macOS offscreen preview).
  • Samples~/.../DisplayXRInputController.cs I-key binding untouched (it calls DisplayXRScreenshot.Capture()).
  • Stage default is projectionOnly = true (matches the native test apps / demos).

Build / verify

  • Native DLL: builds + links clean. displayxr_capture_atlas export confirmed via dumpbin /exports. Committed Runtime/Plugins/Windows/x64/displayxr_unity.dll rebuilt.
  • C# NOT compiled here — no headless Unity Editor available. The C# mirrors the established RequestDisplayMode C#→native flow; needs Unity Editor batchmode (BuildScript.BuildWindows64) + a live I-press test against a displayxr-unity-test* project.

🤖 Generated with Claude Code

Migrate the plugin's 'I'-key screenshot from app-side GPU readback to the
runtime-owned xrCaptureAtlasEXT (XR_EXT_atlas_capture). A live OpenXR session
now hands the runtime a path prefix; the runtime reads back the compositor's
own composited atlas and writes "<prefix>_atlas.png". The plugin no longer
does an AsyncGPUReadback or a hidden-camera Kooima re-render for live sessions.

- native~/displayxr_extensions.h: vendor the XR_EXT_atlas_capture types
  (XrAtlasCaptureInfoEXT/ResultEXT, XrAtlasCaptureStageEXT, PFN_xrCaptureAtlasEXT,
  struct-type values, path-max).
- native~/displayxr_hooks.{cpp,h}: resolve s_pfn_capture_atlas via the existing
  s_next_gipa chain (mirrors xrSetSharedTextureOutputRectEXT); add exported
  displayxr_capture_atlas(prefix, stage).
- Runtime/DisplayXRNative.cs + DisplayXRFeature.cs: opt into the extension,
  add CaptureAtlas(pathPrefix, projectionOnly=true) P/Invoke wrapper.
- Runtime/DisplayXRScreenshot.cs: gut BeginCapture/CaptureOnDemand/s_CaptureCam/
  GetStereoMatrices/FlipViewZ for the live path; rewire Capture() to compute the
  numbered "<stem>-<N>_<cols>x<rows>" prefix (mirrors the demos'
  MakeCaptureAtlasPrefix) and call CaptureAtlas. Flash overlay + filename
  numbering kept. The editor-preview _OnAtlasReady readback path is unchanged
  (no runtime OpenXR session in pure-editor preview — out of scope).

native~/displayxr_readback.{cpp,h} untouched. The 'I'-key binding in
Samples~/DisplayXRInputController.cs is unchanged.

Native DLL builds + links clean (displayxr_capture_atlas export verified via
dumpbin). The C# was not compiled here (no headless Unity Editor) — it mirrors
the existing RequestDisplayMode C#->native flow and needs Unity Editor batchmode
verification.

Resolves #140.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Migrate atlas/screenshot capture to xrCaptureAtlasEXT (XR_EXT_atlas_capture) [#396 W6]

1 participant