fix(provider): submit the ACTIVE mode's view count + mirror display_info spec v19 (runtime#1486) - #328
Merged
Conversation
… latch (DisplayXR/displayxr-runtime#1486) Runtime #1486 tightened xrEndFrame: under PRIMARY_STEREO — the type this provider begins with, because Unity's stereo topology is fixed at 2 — a projection layer must carry exactly 2 views, or 1 ONLY while the ACTIVE rendering mode is itself 1-view and the instance enabled XR_DXR_display_info (the provider always does). A 1-view submission in a 2-view mode is now XR_ERROR_VALIDATION_FAILURE where the runtime used to silently flat-blit it. dxr_prov_submit_frame derived that count from s_ps.sc_view_count, a shadow of the mode latched at swapchain create and refreshed only by ps_reconcile_primary(). That refresh is skippable and can lag by frames: - ps_reconcile_primary() early-returns on a 0-size target (minimised window, unresolved zone rect) BEFORE it compares the view count; - dxr_prov_reconcile_size() returns early while a frame is begun; - a failed ps_recreate_primary_swapchain() leaves the old value in place; - on macOS the mode event is polled from the MAIN thread (DisplayXRProviderDriver.LateUpdate) while submit runs on the render thread. So a 1-view submission could outlive its 1-view mode. Read the active mode at submit instead and clamp to the slices actually allocated (arraySize, always 2): 2 is always legal, so it is the failure direction, and a 1 is emitted only while the mode really says 1-view. Raising the count is safe content-wise — GfxPopulateNextFrameDesc fills both SPI slices / both MultiPass passes every frame whatever the mode, so slice 1 is never stale. A count/latch disagreement logs at most 8 times, never per frame. Also reconciles the hand-written mirror native~/displayxr_extensions.h against the runtime header: XR_DXR_DISPLAY_INFO_SPEC_VERSION 12 -> 19, plus XR_VIEW_CONFIGURATION_TYPE_PRIMARY_MULTIVIEW_DXR (1004999212) with a note that the provider deliberately does not use it. Documentation value only — audited by diffing every XR_*_DXR identifier referenced in native~/ against what the mirror defines; it is otherwise complete for everything the provider uses. Co-Authored-By: Claude Fable 5.1 <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.
What
Two things, both downstream of the runtime's view-configuration change
(
displayxr-runtime#1486,runtime
main@c1e4fe00d):dxr_prov_submit_framenow derives the projection layer'sviewCountfrom the ACTIVErendering mode at submit time, clamped to the slices actually allocated — instead of
inheriting
s_ps.sc_view_count, the mode shadow latched when the swapchain was created.native~/displayxr_extensions.hreconciled with the runtime header:XR_DXR_DISPLAY_INFO_SPEC_VERSION12 → 19, plusXR_VIEW_CONFIGURATION_TYPE_PRIMARY_MULTIVIEW_DXR(1004999212) with a comment sayingthe provider deliberately does not use it.
Why — the runtime contract
PRIMARY_STEREOnow reports exactly 2 views, andxrEndFrameunder it accepts exactly 2,or 1 only while the active rendering mode is itself 1-view AND the instance enabled
XR_DXR_display_info(this provider always does —displayxr_provider_session.cpp:4145).A 1-view submission in a 2-view mode is
XR_ERROR_VALIDATION_FAILURE; the runtime used tosilently flat-blit it. The check reads the runtime's live
active_rendering_mode_index(
oxr_session_frame_end.c:verify_projection_view_count), not the mode the app last heard about.The plugin stays on
PRIMARY_STEREOand that is correct: Unity's stereo topology is fixed at 2(
DXR_PROV_MAX_VIEWS, onearraySize=2swapchain, two render passes/params), soPRIMARY_MULTIVIEW_DXRbuys it nothing. The constant is mirrored for documentation value andfor a future N-view/quilt path (
docs~/adr/ADR-007-render-path-by-view-count.md).The race — reachable, and not only as a thread race
submit_ncame froms_ps.sc_view_count, set atdisplayxr_provider_session.cpp:1305fromps_active_view_count()and re-checked only inps_reconcile_primary()at:2260. Thatrefresh is skippable and can lag by frames:
ps_reconcile_primary()returns on a 0-size target (minimised window, unresolved zone rect) before it ever compares the view count:2255-2258dxr_prov_reconcile_size()returns early while a frame is begun:2295ps_recreate_primary_swapchain()leaves the stale value in place —sc_view_countis only assigned after a successfulxrCreateSwapchain:1300-1305,:2177Runtime/Provider/DisplayXRProviderDriver.cs:81vsdisplayxr_display_provider.cpp:1367/1409On Windows/Linux the poll → reconcile → begin_frame sequence is single-threaded inside
GfxPopulateNextFrameDesc(displayxr_display_provider.cpp:1012,:1035), so the orderingis sound there — but the rows above are enough on their own, and the mode shadow is in any case
only as fresh as the last
XR_TYPE_EVENT_DATA_RENDERING_MODE_CHANGED_DXRthe provider drained(
displayxr_provider_session.cpp:5268).Direction that matters: mode flips 1-view → 2-view, latch still says 1, provider submits
viewCount = 1→ rejected. 2 is always legal underPRIMARY_STEREOand 1 never is unless themode says so, so the fix makes 2 the failure direction. Reading the mode at submit closes every
row above; the residual window is the event-delivery latency itself, which no app-side change can
close.
Raising the count is safe content-wise:
GfxPopulateNextFrameDescfills both SPI slices /both MultiPass passes every frame regardless of mode (
:1232/:1269), so slice 1 is neverstale, and every per-eye bridge array is sized 2. Lowering it to 1 in a genuine 1-view mode is the
existing
#172 P4behaviour, unchanged.A count/latch disagreement logs to the provider log, hard-capped at 8 lines — never per frame.
Header audit (task 1)
The mirror is complete for everything the provider uses. Diffed every
Xr*DXR/XR_*_DXRidentifier referenced acrossnative~/**.{c,cpp,h,mm}against whatdisplayxr_extensions.hdefines: the only residue is extension-name strings inside comments andXrLocal3DZoneMaskDXR, which the header does define viaXR_DEFINE_HANDLEat:694. No v13–v18constant is referenced-but-missing — v13's
isActive/isRequestable, v16'sXrDisplayDesktopPositionDXRand v18'sXrDisplayDesktopInfoDXRare all already present. Theversion macro was simply never bumped past 12 while the structs kept being added.
Binary (task 3, per CLAUDE.md)
CLAUDE.md: "after modifying any file in
native~/, run the build script for the current platform… then commit source + your platform's binary … CI builds all three platforms." Native code
changed, so the rebuilt macOS Universal bundle (
Runtime/Plugins/macOS/displayxr_unity.bundle,x86_64 + arm64) is committed. The Windows MSVC DLL and the Linux
.socome from CI on this PR —the
.sois gitignored by rule, and no maintainer box here builds the MSVC DLL.Verification
native~/build-mac.sh— green, Universal (x86_64 + arm64); the rebuilt bundle contains the newcode path (verified by string match on the new log line).
native~/build-win.sh(MinGW cross-compile check on macOS) — green.NOT tested
Play Mode session. The 2D↔3D mode-flip transition — the exact window this changes — is
unverified end-to-end; it wants a Leia SR box with runtime
main(≥c1e4fe00d), a2D/3D toggle (shell
V, orxrRequestDisplayModeDXR), and a check that the provider log showsno rejected
xrEndFrameand no ghost-weave in 2D.Windows DLL is CI's.
ubuntu:22.04container check in CLAUDE.md was skipped — CI'sbuild-linuxjob covers it. Thechanged code is platform-neutral (no
#ifdeftouched), so the risk is nil.package.jsonnot bumped, and the CHANGELOG entry is under## [Unreleased]. Per repoconvention the release flow owns the bump; note that
/releasestep 2.2 prepends a new versionsection above
[Unreleased]rather than folding it in, so whoever cuts the next release shouldmerge the two by hand.
🤖 Generated with Claude Code
Release ordering (do not release before the runtime does)
This change re-vendors
XR_DXR_display_info.hat spec 19, which landed on runtimemainwith displayxr-runtime#1500 and is not in any released runtime yet. Merging this PR is fine at any time; releasing it must wait for a runtime release that contains #1500, otherwisedrift_audit::check_consumer_floorswill (correctly) flag this consumer as under-declared. Against an older runtime the code degrades toPRIMARY_STEREOand keeps working; the constraint is about the declared floor, not runtime behaviour.