@babylonjs/core
Version:
Getting started? Play directly with the Babylon.js API using our [playground](https://playground.babylonjs.com/). It also contains a lot of samples to learn how to use it.
148 lines (147 loc) • 8.75 kB
JavaScript
import { WebXRLayerRenderTargetTexture } from "./webXRLayerRenderTargetTexture.js";
import { WebXRLayerRenderTargetTextureProvider } from "./webXRRenderTargetTextureProvider.js";
/**
* Maps a WebGPU depth/stencil {@link GPUTextureFormat} to the matching Babylon `TEXTUREFORMAT_*` constant.
*
* The engine reads `InternalTexture.format` (a Babylon constant) when it builds the render pass depth
* attachment for a render target, so a depth texture wrapped from an external `GPUTexture` must carry the
* correct Babylon format or the depth attachment pipeline format will not match the sub-image's texture.
* @param format the WebGPU depth/stencil format requested when the layer was created
* @returns the matching Babylon texture format constant
*/
function GetBabylonDepthFormat(format) {
switch (format) {
case "depth16unorm":
return 15;
case "depth24plus":
return 16;
case "depth24plus-stencil8":
return 13;
case "depth32float":
return 14;
case "depth32float-stencil8":
return 18;
case "stencil8":
return 19;
default:
// Fall back to a combined depth/stencil format, which is the projection layer default.
return 13;
}
}
/**
* Provides render target textures for WebGPU-backed XR layers. Owns all WebGPU-specific texture wrapping
* (via {@link WebGPUEngine.wrapWebGPUTexture} / {@link WebGPUEngine.updateWrappedWebGPUTexture}) so the base
* provider can stay graphics-API-agnostic. Mirrors {@link WebXRWebGLRenderTargetTextureProvider} for the
* WebGPU (XRGPUBinding) backend.
* @internal
*/
export class WebXRWebGPURenderTargetTextureProvider extends WebXRLayerRenderTargetTextureProvider {
get _webgpuEngine() {
return this._engine;
}
_wrapColorTexture(texture) {
return this._webgpuEngine.wrapWebGPUTexture(texture);
}
_wrapDepthTexture(texture, depthStencilFormat) {
const internalTexture = this._webgpuEngine.wrapWebGPUTexture(texture);
// The engine derives the color attachment format automatically from the wrapped hardware texture,
// but the depth attachment format is read from the Babylon InternalTexture.format, which
// wrapWebGPUTexture does not set. Assign it here from the format the layer was created with.
internalTexture.format = GetBabylonDepthFormat(depthStencilFormat);
return internalTexture;
}
/**
* Builds the render target shell as a {@link WebXRLayerRenderTargetTexture} for the single-view path so
* each eye can bind its own array layer of a layered projection-layer texture. Multiview is not yet
* supported on this WebGPU path and is delegated to the base implementation.
* @param width the width of the render target
* @param height the height of the render target
* @param multiview whether the render target should be a multiview render target
* @returns the created (but not yet registered) render target texture
*/
_createRenderTargetTextureShell(width, height, multiview) {
if (multiview) {
return super._createRenderTargetTextureShell(width, height, multiview);
}
const renderTargetTexture = new WebXRLayerRenderTargetTexture("XR renderTargetTexture", { width, height }, this._scene);
renderTargetTexture.renderTarget._samples = renderTargetTexture.samples;
return renderTargetTexture;
}
/**
* Wraps the sub-image's color (and optional depth/stencil) GPUTextures and builds a new render target
* texture around them. Use this on first creation and whenever the sub-image texture size changes.
* @param width the width of the render target
* @param height the height of the render target
* @param colorTexture the sub-image color GPUTexture
* @param depthStencilTexture the sub-image depth/stencil GPUTexture, if any
* @param depthStencilFormat the WebGPU depth/stencil format the layer was created with, if depth is provided
* @param multiview whether the render target should be a multiview render target
* @returns the created render target texture
*/
_createRenderTargetTextureFromGPUTextures(width, height, colorTexture, depthStencilTexture, depthStencilFormat, multiview) {
const color = this._wrapColorTexture(colorTexture);
const depth = depthStencilTexture && depthStencilFormat ? this._wrapDepthTexture(depthStencilTexture, depthStencilFormat) : null;
// Single-view (the only path here) always builds a WebXRLayerRenderTargetTexture via the shell override above.
const renderTargetTexture = this._createRenderTargetTextureInternal(width, height, color, depth, multiview);
this._attachPerEyeClearObserver(renderTargetTexture);
return renderTargetTexture;
}
/**
* Ensures this render target is cleared every frame, including for the right eye.
*
* `Scene._clearFrameBuffer` skips clearing the right rig camera's framebuffer (`!camera.isRightCamera`).
* That is a valid optimization for WebGL2 stereo, where both eyes render into a single side-by-side
* texture and the left eye's full clear already covers the whole texture. WebGPU projection layers give
* each eye its OWN render target (a distinct array layer rendered in a separate render pass), so the
* right eye's color and — critically — depth would never be cleared, leaving its depth test to reject
* every fragment (a black right eye). The scene notifies `onClearObservable` regardless of
* `camera.isRightCamera`, so this observer clears both eyes' targets. WebGL providers never attach it,
* so the WebGL path is unchanged.
*
* The observer mirrors the clear semantics of `Scene._clearFrameBuffer` (minus the right-eye skip): it
* only clears when the scene has `autoClear` enabled, clears color at most once per frame (guarded by
* `RenderTargetTexture._cleared`, which the scene resets per frame in `_checkCameraRenderTarget`), always
* clears depth+stencil, and honors `skipInitialClear`. This keeps the "clear once per RTT per frame"
* contract instead of clearing color on every notification.
* @param renderTargetTexture the per-eye render target to clear each frame
*/
_attachPerEyeClearObserver(renderTargetTexture) {
renderTargetTexture.onClearObservable.add((engine) => {
// Honor skipInitialClear so this observer preserves the default clear semantics it replaces.
if (renderTargetTexture.skipInitialClear) {
return;
}
const scene = renderTargetTexture.getScene();
// When autoClear is disabled the scene does not clear render targets; match that so this observer
// does not force a clear the user opted out of.
if (scene && !scene.autoClear) {
return;
}
// Clear color only once per frame (!_cleared), always clear depth+stencil, then mark the target
// cleared for this frame so a second notification in the same frame does not re-clear color.
engine.clear(renderTargetTexture.clearColor ?? scene?.clearColor ?? null, !renderTargetTexture._cleared, true, true);
renderTargetTexture._cleared = true;
});
}
/**
* Repoints the wrapped color/depth textures of an existing render target at the current frame's
* GPUTextures, preserving the RenderTargetTexture / InternalTexture identity held by the XR camera's
* `outputRenderTarget`. The sub-image textures are only valid for the current frame and may rotate from
* a compositor pool, so this must run every frame. The new GPUTextures must have the same dimensions as
* the wrapped ones (guaranteed by the caller's size-change branch, which rebuilds instead).
* @param renderTargetTexture the render target whose wrapped textures should be repointed
* @param colorTexture the current frame's color GPUTexture
* @param depthStencilTexture the current frame's depth/stencil GPUTexture, if any
*/
_updateRenderTargetTextureFromGPUTextures(renderTargetTexture, colorTexture, depthStencilTexture) {
const color = renderTargetTexture._texture;
if (color) {
this._webgpuEngine.updateWrappedWebGPUTexture(color, colorTexture);
}
const depth = renderTargetTexture.renderTarget?._depthStencilTexture;
if (depth && depthStencilTexture) {
this._webgpuEngine.updateWrappedWebGPUTexture(depth, depthStencilTexture);
}
}
}
//# sourceMappingURL=webXRWebGPURenderTargetTextureProvider.js.map