Skip to content

[WebGPU-XR][Phase 2] WebGPU projection layer + render target provider (#18640)#18655

Draft
RaananW wants to merge 4 commits into
masterfrom
raananw-webgpu-xr-phase-2
Draft

[WebGPU-XR][Phase 2] WebGPU projection layer + render target provider (#18640)#18655
RaananW wants to merge 4 commits into
masterfrom
raananw-webgpu-xr-phase-2

Conversation

@RaananW

@RaananW RaananW commented Jul 3, 2026

Copy link
Copy Markdown
Member

Phase 2 — WebGPU XR projection layer + render target provider

Tracking issue: #18640 · Epic: #18635 · Follows Phase 0 (#18645) and Phase 1 (#18650).

Goal

Implement the core WebGPU XR rendering path: render the stereo scene into an XRProjectionLayer's per‑view GPUTextures via XRGPUBinding, so a WebGPU XR session on Quest reaches IN_XR and actually renders. Phase 1 enters the session but stays blank because no layer is attached and no RTT provider produces textures — this PR closes that gap.

WebGL2 XR remains byte‑for‑byte unchanged. Everything new is gated behind isWebGPU / binding‑type branches, is @internal, and is kept out of the public barrels. Zero net public‑API additions.

What's in this PR (two commits for reviewability, one mergeable unit)

Commit 1 — WebGPU RTT providers (additive, inert until commit 2):

  • XR/webXRWebGPURenderTargetTextureProvider.ts — abstract @internal base extending the API‑agnostic WebXRLayerRenderTargetTextureProvider. Wraps per‑view color/depth GPUTextures via WebGPUEngine.wrapWebGPUTexture, sets the correct Babylon depth format on the wrapped depth texture, repoints per‑frame via updateWrappedWebGPUTexture (rebuilds only on size change), and builds RTTs via _createRenderTargetTextureInternal.
  • XR/features/Layers/WebXRWebGPUCompositionLayer.ts — WebGPU composition provider + wrapper mirroring the WebGL provider over XRGPUSubImage (dimensions from colorTexture.width/height), size‑keyed cache with repoint fast‑path and viewport normalization.
  • XR/features/Layers/WebXRWebGPUProjectionLayer.ts — WebGPU projection wrapper/provider using getViewSubImage, plus the default XRGPUProjectionLayerInit factory.
  • Unit tests (test/unit/XR/webXRWebGPUProjectionLayer.test.ts).

Commit 2 — WebXRLayers activation:

  • XR/features/WebXRLayers.pure.ts — branches attach() / isCompatible() on engine.isWebGPU to create an XRGPUBinding projection layer (via the Phase 1 graphics binding) and instantiate the WebGPU wrapper. The WebGL branch is only bypassed when isWebGPU and an XRGPUBinding is present.

Key implementation notes / fork resolutions

  • Depth‑format gap (fixed in the provider): wrapWebGPUTexture doesn't set the Babylon InternalTexture.format; the WebGPU depth attachment path reads it. The provider sets the mapped Babylon depth format on the wrapped depth texture. wrapWebGPUTexture is left untouched (minimal general‑purpose primitive).
  • Per‑frame texture rotation: updateWrappedWebGPUTexture fast path preserves RTT/InternalTexture identity (held by Camera.outputRenderTarget) and bumps uniqueId to invalidate bind‑group caches; full re‑wrap only on size change (the update guard requires matching dims).
  • External‑texture targeting: relies on the existing Camera.outputRenderTarget → bindFramebuffer → _startRenderTargetRenderPass path. The swapchain (getCurrentTexture) is only used when no render target is bound, so it is untouched during XR.
  • MSAA (ready-to-enable, deferred): ships with samples=1 for Phase 2. Babylon‑managed MSAA resolve into the external single‑sample color texture already works via the existing getMSAATexture/resolveTarget path and needs only RTT samples>1 (no engine work). Deliberately deferred to isolate the first hardware validation — enabling it will be a small fast-follow after RaananW confirms the base stereo render works on Quest.
  • Multiview: single‑view only (two eyes / two sub‑images via getViewSubImage); multiview=false for WebGPU. Multiview is deferred (Phase 4+).

Scope boundaries

Testing

  • tsc -b (core): clean.
  • Unit tests: 7/7 new pass; full XR unit suite 266/266 pass.
  • npm run format:check, npm run lint:check (incl. 16 tree‑shaking checks + side‑effects‑sync): all green.
  • ⚠️ Actual stereo WebGPU‑XR rendering can only be validated on Quest hardware by @RaananW — no WebGPU‑XR browser is available in CI/dev here. Please smoke‑test the built snapshot on device.

RaananW and others added 2 commits July 3, 2026 14:23
Add @internal, un-barrelled WebGPU render target texture providers that mirror
the WebGL layer providers but extend the API-agnostic base
WebXRLayerRenderTargetTextureProvider. They wrap the per-view XRGPUSubImage
color/depth GPUTextures via WebGPUEngine.wrapWebGPUTexture, set the correct
Babylon depth format on the wrapped depth texture, repoint per-frame via
updateWrappedWebGPUTexture (rebuilding only on size change), and build RTTs via
_createRenderTargetTextureInternal. Includes unit tests. Inert until WebXRLayers
wiring is activated.

Part of Phase 2 (#18640), epic #18635.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Branch WebXRLayers attach()/isCompatible() on engine.isWebGPU to create an
XRGPUBinding projection layer (via the Phase 1 graphics binding) and instantiate
the WebGPU projection layer wrapper. The WebGL2 XR path is unchanged; the new
branch is only taken when isWebGPU and an XRGPUBinding is present.

Part of Phase 2 (#18640), epic #18635.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
@bjsplat

bjsplat commented Jul 3, 2026

Copy link
Copy Markdown
Collaborator

Please make sure to label your PR with "bug", "new feature" or "breaking change" label(s).
To prevent this PR from going to the changelog marked it with the "skip changelog" label.

@bjsplat

bjsplat commented Jul 3, 2026

Copy link
Copy Markdown
Collaborator

Snapshot stored with reference name:
refs/pull/18655/merge

Test environment:
https://snapshots-cvgtc2eugrd3cgfd.z01.azurefd.net/refs/pull/18655/merge/index.html

To test a playground add it to the URL, for example:

https://snapshots-cvgtc2eugrd3cgfd.z01.azurefd.net/refs/pull/18655/merge/index.html#WGZLGJ#4600

Links to test your changes to core in the published versions of the Babylon tools (does not contain changes you made to the tools themselves):

https://playground.babylonjs.com/?snapshot=refs/pull/18655/merge
https://sandbox.babylonjs.com/?snapshot=refs/pull/18655/merge
https://gui.babylonjs.com/?snapshot=refs/pull/18655/merge
https://nme.babylonjs.com/?snapshot=refs/pull/18655/merge

To test the snapshot in the playground with a playground ID add it after the snapshot query string:

https://playground.babylonjs.com/?snapshot=refs/pull/18655/merge#BCU1XR#0

If you made changes to the sandbox or playground in this PR, additional comments will be generated soon containing links to the dev versions of those tools.

@bjsplat

bjsplat commented Jul 3, 2026

Copy link
Copy Markdown
Collaborator

@bjsplat

bjsplat commented Jul 3, 2026

Copy link
Copy Markdown
Collaborator

@bjsplat

bjsplat commented Jul 3, 2026

Copy link
Copy Markdown
Collaborator

@bjsplat

bjsplat commented Jul 3, 2026

Copy link
Copy Markdown
Collaborator

⚡ Performance Test Results

🟢 All performance tests passed — no regressions detected.

@bjsplat

bjsplat commented Jul 3, 2026

Copy link
Copy Markdown
Collaborator

🟢 Memory Leak Test Results

4 passed, 0 leaked out of 4 scenarios

🟢 All memory leak tests passed — no leaks detected.

Passed Scenarios (4)
Scenario Package
Core Playground #2FDQT5#1508 @babylonjs/core
Core Playground #T90MQ4#14 @babylonjs/core
Core Playground #8EDB5N#2 @babylonjs/core
Core Playground #LL5BIQ#636 @babylonjs/core

RaananW and others added 2 commits July 3, 2026 19:02
WebGPUHardwareTexture.format defaulted to RGBA8Unorm and wrapWebGPUTexture
never overwrote it. The RTT color-attachment view and pipeline color target
are built from hardwareTexture.format (_setColorFormat / bindFramebuffer),
so an external texture created with any other format (e.g. bgra8unorm or an
*-srgb variant, as returned by XRGPUBinding.getPreferredColorFormat()) got a
mismatched view and failed to render (black in WebGPU-XR).

Set both format and originalFormat from the wrapped GPUTexture's own format.
General correctness for any external-texture wrap; rgba8unorm callers are
unchanged. Orthogonal to the depth-format fix (that sets InternalTexture.format).

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
…r (GAP 2)

WebGPU/Quest projection layers hand out a texture ARRAY with one layer per
eye (depthOrArrayLayers=2, baseArrayLayer 0=left / 1=right). The render path
hard-coded baseArrayLayer=0, so the right eye's layer was never written and
both views composited into layer 0 -> black/broken stereo despite correct
per-eye resolution and IN_XR.

- RenderTargetTexture: add @internal _bindFrameBufferLayer (default 0) that
  _bindFrameBuffer defaults its layer arg from. WebGL2 + non-XR paths are
  byte-identical (field stays 0; explicit-layer callers unchanged).
- WebGPU XR provider: set _bindFrameBufferLayer per eye from the authoritative
  subImage.getViewDescriptor().baseArrayLayer. Depth attachment inherits the
  same layer via the existing baseArrayLayer=layer wiring.
- Add unit test asserting left->layer 0, right->layer 1.

Zero net public API (@internal). Confirmed on Quest by RaananW: readout
baseArrayLayer 0/1 per eye.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
@bjsplat

bjsplat commented Jul 3, 2026

Copy link
Copy Markdown
Collaborator

@bjsplat

bjsplat commented Jul 3, 2026

Copy link
Copy Markdown
Collaborator

🟢 Memory Leak Test Results

4 passed, 0 leaked out of 4 scenarios

🟢 All memory leak tests passed — no leaks detected.

Passed Scenarios (4)
Scenario Package
Core Playground #2FDQT5#1508 @babylonjs/core
Core Playground #T90MQ4#14 @babylonjs/core
Core Playground #8EDB5N#2 @babylonjs/core
Core Playground #LL5BIQ#636 @babylonjs/core

@bjsplat

bjsplat commented Jul 3, 2026

Copy link
Copy Markdown
Collaborator

@bjsplat

bjsplat commented Jul 3, 2026

Copy link
Copy Markdown
Collaborator

@bjsplat

bjsplat commented Jul 3, 2026

Copy link
Copy Markdown
Collaborator

⚡ Performance Test Results

🟢 All performance tests passed — no regressions detected.

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.

2 participants