1. Introduction
This specification describes a mechanism for rendering WebXR content using WebGPU, instead of WebGL.
It adds support for creation of XRCompositionLayers, as described in the WebXR Layers API, which are rendered using the WebGPU API.
WebGPU is an API for utilizing the graphics and compute capabilities of a device’s GPU more efficiently than WebGL allows, with an API that better matches both GPU hardware architecture and the modern native APIs that interface with them, such as Vulkan, Direct3D 12, and Metal.
1.1. Terminology
This specification uses the terms XR device, XRSession, XRFrame, XRView, and feature descriptor as defined in the WebXR Device API specification.
It uses the terms XRCompositionLayer, XRProjectionLayer, XRQuadLayer, XRCylinderLayer, XREquirectLayer, XRCubeLayer, XRSubImage, and XRWebGLBinding as defined in the WebXR Layers API specification.
It uses the terms GPUDevice, GPUAdapter, GPUTexture, GPUTextureViewDescriptor, GPUTextureFormat, and GPUTextureUsageFlags as defined in the WebGPU specification.
1.2. Application flow
If an author wants to use WebGPU to render content for a WebXR Session, they must perform the following steps:
In no particular order
-
Create a
GPUDevicefrom anGPUAdapterwhich was requested with thexrCompatibleoption set totrue.
Then
-
Create an
XRGPUBindingwith both the XR-compatibleGPUDeviceand WebGPU-compatible session. -
Create one or more
XRCompositionLayers with theXRGPUBinding -
Add the layers to
XRRenderStateInitand callupdateRenderState(). -
During
requestAnimationFrame()for each WebGPU layer:-
For each
XRGPUSubImageexposed by the layer:-
Draw the contents of the subimage using the
GPUDevicetheXRGPUBindingwas created with.
-
-
2. Initialization
2.1. Feature Descriptor
The string "webgpu" is introduced by this module as a new valid feature descriptor for the WebXR/WebGPU Binding feature.
If a user agent wants to use WebGPU for rendering during a session, the session MUST be requested with the webgpu feature descriptor. XRSessions created with this feature are referred to as WebGPU-compatible sessions.
A WebGPU-compatible session MUST have the following behavioral differences from a WebGL-compatible session:
-
"inline"sessions are NOT supported. -
XRWebGLBindingandXRWebGLLayerinstances MUST NOT be created with the session. -
XRGPUBindinginstances CAN be created for the session. -
baseLayerMUST NOT be set inupdateRenderState().layersMUST be used instead. -
The projection matrix returned by
XRViewfor this session MUST use a clip-space depth range of [0, 1] instead of the WebGL default [-1, 1].
const session= await navigator. xr. requestSession( 'immersive-vr' , { requiredFeatures: [ 'webgpu' ] });
NOTE: The webgpu feature may be passed to either requiredFeatures or optionalFeatures. If passed to optionalFeatures, the author MUST check enabledFeatures after the session is created and use either WebGPU or WebGL to render the session’s content depending on whether webgpu is present.
2.2. GPUAdapter Integration
To create a GPUDevice that is compatible with an XR device, the GPUAdapter used to create it must have been requested with the xrCompatible option set to true.
partial dictionary GPURequestAdapterOptions {boolean xrCompatible =false ; };
The xrCompatible option, when set to true, indicates that the returned GPUAdapter MUST be compatible with the XR device selected by the user agent. If no GPUAdapter can satisfy this constraint, the request MUST return null.
NOTE: There is no WebGPU equivalent to the WebGLRenderingContextBase.makeXRCompatible() method. If a user agent needs to ensure XR compatibility, the GPUAdapter MUST be requested with xrCompatible set to true from the start.
An XR-compatible adapter is a GPUAdapter that was successfully returned from a requestAdapter() call with xrCompatible set to true.
An XR-compatible device is a GPUDevice that was created from an XR-compatible adapter.
2.3. XRGPUBinding
The XRGPUBinding interface is the entry point for using WebGPU with a WebGPU-compatible session. It provides methods for creating WebGPU-backed XRCompositionLayers and obtaining XRGPUSubImages for rendering.
[Exposed =(Window ),SecureContext ]interface {XRGPUBinding constructor (XRSession ,session GPUDevice );device readonly attribute double nativeProjectionScaleFactor ;XRProjectionLayer createProjectionLayer (optional XRGPUProjectionLayerInit = {});init XRQuadLayer createQuadLayer (optional XRGPUQuadLayerInit = {});init XRCylinderLayer createCylinderLayer (optional XRGPUCylinderLayerInit = {});init XREquirectLayer createEquirectLayer (optional XRGPUEquirectLayerInit = {});init XRCubeLayer createCubeLayer (optional XRGPUCubeLayerInit = {});init XRGPUSubImage getSubImage (XRCompositionLayer ,layer XRFrame ,frame optional XREye = "none");eye XRGPUSubImage getViewSubImage (XRProjectionLayer ,layer XRView );view GPUTextureFormat getPreferredColorFormat (); };
Each XRGPUBinding has an associated session which is the XRSession it was created with, and an associated device which is the GPUDevice it was created with.
2.3.1. Constructor
The XRGPUBinding(session, device) constructor MUST perform the following steps when invoked:
-
If session’s ended value is
true, throw anInvalidStateErrorDOMException. -
If session is NOT a WebGPU-compatible session, throw an
InvalidStateErrorDOMException. -
If device has been destroyed, throw an
InvalidStateErrorDOMException. -
If device was NOT created from an XR-compatible adapter, throw an
InvalidStateErrorDOMException. -
Let binding be a new
XRGPUBinding. -
Set binding’s session to session.
-
Set binding’s device to device.
-
Return binding.
XRGPUBinding:
const adapter= await navigator. gpu. requestAdapter({ xrCompatible: true }); const device= await adapter. requestDevice(); const binding= new XRGPUBinding( session, device);
2.3.2. Attributes
The nativeProjectionScaleFactor attribute returns the scale factor that, when applied to the recommended WebGPU texture resolution, would result in a 1:1 texel-to-pixel ratio at the center of the user’s view. This value MAY change over the lifetime of the session.
2.3.3. Recommended WebGPU Texture Resolution
Each XR device has a recommended WebGPU texture resolution, which represents the per-view dimensions that the user agent considers a good balance between rendering quality and performance for that device. The recommended resolution is determined by taking the maximum width and height across all of the session’s views, scaled by a user agent-defined default scale factor.
NOTE: Unlike the recommended WebGL framebuffer resolution defined in the WebXR spec, which concatenates views side-by-side into a single framebuffer, the recommended WebGPU texture resolution describes the size of a single view. When creating projection layers, the user agent allocates a texture array where each layer corresponds to one view, with each layer having the recommended resolution.
The nativeProjectionScaleFactor attribute can be used to determine the scale factor needed to achieve the native 1:1 resolution. A scaleFactor of 1.0 in createProjectionLayer() uses the recommended resolution directly.
2.3.4. getPreferredColorFormat
The getPreferredColorFormat() method returns the GPUTextureFormat that the user agent recommends for the color attachments of layers created with this binding.
When invoked, the user agent MUST return the preferred GPUTextureFormat for the session’s XR device.
NOTE: The preferred color format is typically "rgba8unorm" or "bgra8unorm" depending on the platform. The preferred format for WebXR may differ from the format reported by navigator.gpu.getPreferredCanvasFormat(). Authors SHOULD use this method rather than getPreferredCanvasFormat() to determine the format for their XR projection layers.
2.3.5. createProjectionLayer
The createProjectionLayer(init) method creates a new XRProjectionLayer backed by WebGPU textures.
When invoked, the user agent MUST run the following steps:
-
If the session has ended, throw an
InvalidStateErrorDOMException. -
If the device has been destroyed, throw an
InvalidStateErrorDOMException. -
Let colorFormat be init’s
colorFormat. -
If colorFormat is not a supported color format, throw an
InvalidStateErrorDOMException. -
If init’s
depthStencilFormatis present and is not a supported depth-stencil format, throw anInvalidStateErrorDOMException. -
Let scaleFactor be init’s
scaleFactor, clamped to the range [0.2, max(nativeProjectionScaleFactor, 1.0)]. -
Let recommendedSize be the recommended WebGPU texture resolution for the session’s XR device.
-
Let textureSize be recommendedSize scaled by scaleFactor.
-
Let maxDimension be the device’s
maxTextureDimension2Dlimit. -
If either dimension of textureSize exceeds maxDimension, scale textureSize proportionally so the largest dimension equals maxDimension.
-
Create a new
XRProjectionLayerwith color textures of size textureSize and format colorFormat. -
If init’s
depthStencilFormatis present, let depthStencilFormat be init’sdepthStencilFormatand allocate depth/stencil textures of size textureSize and format depthStencilFormat. -
Return the
XRProjectionLayer.
NOTE: The textures allocated for projection layers are typically texture arrays, with one layer per view (e.g., 2 layers for stereoscopic VR). The number of layers is determined by the session’s view count.
const layer= binding. createProjectionLayer({ colorFormat: binding. getPreferredColorFormat(), depthStencilFormat: 'depth24plus' , }); session. updateRenderState({ layers: [ layer] });
2.3.6. createQuadLayer / createCylinderLayer / createEquirectLayer / createCubeLayer
NOTE: Non-projection layer types are still in development and are not yet supported by any user agent. The interfaces described in this section and the associated layer init dictionaries are subject to change.
The createQuadLayer(init),
createCylinderLayer(init),
createEquirectLayer(init), and
createCubeLayer(init) methods each create a new layer of the corresponding type backed by WebGPU textures.
These methods MUST only succeed if the "layers" feature descriptor was requested and enabled for the session. If "layers" is not enabled, the user agent MUST throw a NotSupportedError DOMException.
When any of these methods are invoked, the user agent MUST run the following steps:
-
If the session has ended, throw an
InvalidStateErrorDOMException. -
If the device has been destroyed, throw an
InvalidStateErrorDOMException. -
If the
"layers"feature descriptor is not enabled for the session, throw aNotSupportedErrorDOMException. -
If init’s color format is not a supported color format, throw an
InvalidStateErrorDOMException. -
If init’s depth/stencil format is present and is not a supported depth-stencil format, throw an
InvalidStateErrorDOMException. -
Create and return a new layer of the appropriate type using the remaining fields of init.
2.3.7. getSubImage
The getSubImage(layer, frame, eye) method returns an XRGPUSubImage for non-projection layers.
This method MUST only succeed if the "layers" feature descriptor was requested and enabled for the session. If "layers" is not enabled, the user agent MUST throw a NotSupportedError DOMException.
When invoked, the user agent MUST run the following steps:
-
If the
"layers"feature descriptor is not enabled for the session, throw aNotSupportedErrorDOMException. -
If layer was not created by this
XRGPUBinding, throw anInvalidStateErrorDOMException. -
If frame’s session is not the session, throw an
InvalidStateErrorDOMException. -
If frame is not an active XR animation frame, throw an
InvalidStateErrorDOMException. -
Let subImage be a new
XRGPUSubImage. -
Set subImage’s
colorTextureto the layer’s current color texture. -
Set subImage’s
depthStencilTextureto the layer’s stencil texture, ornullif no depth/stencil format was specified during layer creation. -
Set subImage’s viewport based on the eye parameter and the layer’s layout.
-
Set subImage’s array layer index based on the eye parameter.
-
Return subImage.
2.3.8. getViewSubImage
The getViewSubImage(layer, view) method returns an XRGPUSubImage for a specific view of a projection layer.
When invoked, the user agent MUST run the following steps:
-
If layer was not created by this
XRGPUBinding, throw anInvalidStateErrorDOMException. -
If view’s session is not the session, throw an
InvalidStateErrorDOMException. -
Let subImage be a new
XRGPUSubImage. -
Set subImage’s
colorTextureto the layer’s current color texture. -
Set subImage’s
depthStencilTextureto the layer’s stencil texture, ornullif no depth/stencil format was specified during layer creation. -
Set subImage’s array layer index to the view’s index.
-
Set the viewport to the full texture dimensions, adjusted by the current viewport scale.
-
Return subImage.
function onXRFrame( time, frame) { session. requestAnimationFrame( onXRFrame); const pose= frame. getViewerPose( refSpace); if ( ! pose) return ; const commandEncoder= device. createCommandEncoder(); for ( const viewof pose. views) { const subImage= binding. getViewSubImage( layer, view); const viewDesc= subImage. getViewDescriptor(); const passEncoder= commandEncoder. beginRenderPass({ colorAttachments: [{ view: subImage. colorTexture. createView( viewDesc), loadOp: 'clear' , storeOp: 'store' , clearValue: { r: 0 , g: 0 , b: 0 , a: 1 }, }], depthStencilAttachment: { view: subImage. depthStencilTexture. createView( viewDesc), depthLoadOp: 'clear' , depthClearValue: 1.0 , depthStoreOp: 'store' , }, }); const vp= subImage. viewport; passEncoder. setViewport( vp. x, vp. y, vp. width, vp. height, 0.0 , 1.0 ); // Render scene from the viewpoint of view... passEncoder. end(); } device. queue. submit([ commandEncoder. finish()]); }
3. Rendering
3.1. XRGPUSubImage
An XRGPUSubImage represents a view into a WebGPU-backed composition layer’s textures. It provides the GPUTextures to render into and a GPUTextureViewDescriptor that describes which portion of the texture corresponds to the requested view.
Each XRCompositionLayer created with an XRGPUBinding has an associated current color texture and an optional current depth/stencil texture. These are GPUTexture objects allocated and managed by the user agent’s swap chain for the layer. At the beginning of each XR animation frame, the user agent provides new textures for rendering. The new textures MUST be cleared to zero before being provided to the application. The textures from the previous frame are submitted to the compositor and are no longer available for rendering.
[Exposed =(Window ),SecureContext ]interface :XRGPUSubImage XRSubImage { [SameObject ]readonly attribute GPUTexture colorTexture ; [SameObject ]readonly attribute GPUTexture ?depthStencilTexture ;GPUTextureViewDescriptor getViewDescriptor (); };
3.1.1. Attributes
The colorTexture attribute returns the GPUTexture to be used as the color attachment when rendering this sub image. This texture is allocated by the user agent and its lifetime is managed by the layer’s swap chain. The same GPUTexture object is returned for all sub images of the same layer within a single frame — use the result of getViewDescriptor() to determine which array layer of the texture to render to.
The returned texture has the following properties:
-
dimension:
"2d" -
format: The
colorFormatspecified during layer creation. -
size: The width and height are determined by the recommended WebGPU texture resolution, scaled by the
scaleFactor. ThedepthOrArrayLayersis equal to the number of views in the session (typically 2 for stereoscopic rendering). -
usage: The
textureUsageflags specified during layer creation. The default value includesGPUTextureUsage.RENDER_ATTACHMENT; if overriding, developers must explicitly includeRENDER_ATTACHMENTif it is needed. -
mipLevelCount: 1
The depthStencilTexture attribute returns the GPUTexture to be used as the depth/stencil attachment when rendering this sub image, or null if no depth/stencil format was specified when creating the layer. When provided, the user agent MAY use the depth information to improve composition quality (for example, for reprojection).
When present, the returned texture has the following properties:
-
dimension:
"2d" -
format: The
depthStencilFormatspecified during layer creation. -
size: Same width, height, and
depthOrArrayLayersas thecolorTexture. -
usage: The
textureUsageflags specified during layer creation. The default value includesGPUTextureUsage.RENDER_ATTACHMENT; if overriding, developers must explicitly includeRENDER_ATTACHMENTif it is needed. -
mipLevelCount: 1
NOTE: If a depthStencilFormat was provided during layer creation, it is implied that the user agent will populate it with an accurate representation of the scene’s depth. If the depth information is not representative of the rendered scene, the user agent SHOULD allocate its own depth/stencil textures rather than use the layer-provided one.
3.1.2. getViewDescriptor
The getViewDescriptor() method returns a GPUTextureViewDescriptor configured for creating a texture view of this sub image’s portion of the layer’s textures. The returned descriptor can be passed to GPUTexture.createView() on both the colorTexture and depthStencilTexture.
When invoked, the user agent MUST run the following steps:
-
Let descriptor be a new
GPUTextureViewDescriptor. -
Set descriptor’s
dimensionto"2d". -
Set descriptor’s
mipLevelCountto 1. -
Set descriptor’s
arrayLayerCountto 1. -
Set descriptor’s
baseArrayLayerto the array layer index corresponding to this sub image’s view (e.g., 0 for the left eye, 1 for the right eye). -
Return descriptor.
NOTE: The returned descriptor selects a single 2D slice from the texture array via baseArrayLayer paired with an arrayLayerCount of 1. The viewport still needs to be applied via setViewport() on the render pass encoder.
const subImage= binding. getViewSubImage( layer, view); const viewDesc= subImage. getViewDescriptor(); const colorView= subImage. colorTexture. createView( viewDesc); const depthView= subImage. depthStencilTexture. createView( viewDesc);
4. Layer Creation
4.1. Supported Texture Formats
The supported color formats for XRGPUBinding layer creation are:
-
"rgba8unorm" -
"bgra8unorm" -
"rgba16float"
The supported depth/stencil formats for XRGPUBinding layer creation are:
-
"stencil8" -
"depth16unorm" -
"depth24plus" -
"depth24plus-stencil8" -
"depth32float" -
"depth32float-stencil8"
NOTE: The formats listed above are the only formats that MUST be accepted for layer creation. User agents MUST NOT accept formats outside of these lists.
4.2. XRGPUProjectionLayerInit
The XRGPUProjectionLayerInit dictionary is used to configure projection layers created with createProjectionLayer().
dictionary {XRGPUProjectionLayerInit required GPUTextureFormat colorFormat ;GPUTextureFormat ?depthStencilFormat ;GPUTextureUsageFlags textureUsage = 0x10; // GPUTextureUsage.RENDER_ATTACHMENTdouble scaleFactor = 1.0; };
const layer= binding. createProjectionLayer({ colorFormat: binding. getPreferredColorFormat(), depthStencilFormat: 'depth24plus-stencil8' , });
The colorFormat member specifies the GPUTextureFormat for the layer’s color textures. This MUST be a supported color format.
The depthStencilFormat member, when present, specifies the GPUTextureFormat for the layer’s depth/stencil textures. This MUST be a supported depth-stencil format. When not present, no depth/stencil texture is allocated.
The textureUsage member specifies the GPUTextureUsageFlags to be set on the allocated textures. The default value is GPUTextureUsage.RENDER_ATTACHMENT. If overriding this value, developers MUST explicitly include RENDER_ATTACHMENT if they intend to use the textures as render attachments.
The scaleFactor member specifies a scale factor to apply to the recommended WebGPU texture resolution. A value of 1.0 uses the recommended resolution; values less than 1.0 reduce quality for improved performance; values greater than 1.0 increase quality at the cost of performance. The value is clamped to the range [0.2, max(nativeProjectionScaleFactor, 1.0)].
4.3. XRGPULayerInit
The XRGPULayerInit dictionary is the base dictionary for configuring non-projection composition layers. Non-projection layers require the "layers" feature descriptor to be enabled for the session.
dictionary {XRGPULayerInit required GPUTextureFormat colorFormat ;GPUTextureFormat ?depthStencilFormat ;GPUTextureUsageFlags textureUsage = 0x10; // GPUTextureUsage.RENDER_ATTACHMENTrequired XRSpace space ;unsigned long mipLevels = 1;required unsigned long viewPixelWidth ;required unsigned long viewPixelHeight ;XRLayerLayout layout = "mono";boolean isStatic =false ; };
The colorFormat member specifies the GPUTextureFormat for the layer’s color textures.
The depthStencilFormat member, when present, specifies the GPUTextureFormat for the layer’s depth/stencil textures.
The textureUsage member specifies additional GPUTextureUsageFlags for the allocated textures.
The space member specifies the XRSpace in which the layer is positioned.
The mipLevels member specifies the number of mip levels to allocate for the layer’s textures.
The viewPixelWidth member specifies the width, in pixels, of each view’s texture.
The viewPixelHeight member specifies the height, in pixels, of each view’s texture.
The layout member specifies the XRLayerLayout of the layer.
The isStatic member, when set to true, indicates that the layer’s content will rarely change. This allows the user agent to optimize for this scenario.
4.4. XRGPUQuadLayerInit
dictionary :XRGPUQuadLayerInit XRGPULayerInit {XRRigidTransform ?transform ;float width = 1.0;float height = 1.0; };
The transform member specifies the initial position and orientation of the quad layer relative to the space.
The width member specifies the width of the quad in meters.
The height member specifies the height of the quad in meters.
4.5. XRGPUCylinderLayerInit
dictionary :XRGPUCylinderLayerInit XRGPULayerInit {XRRigidTransform ?transform ;float radius = 2.0;float centralAngle = 0.78539;float aspectRatio = 2.0; };
The transform member specifies the initial position and orientation of the cylinder layer.
The radius member specifies the radius of the cylinder in meters.
The centralAngle member specifies the central angle of the cylinder in radians. The default value of 0.78539 corresponds to approximately 45 degrees.
The aspectRatio member specifies the aspect ratio (width / height) of the visible portion of the cylinder.
4.6. XRGPUEquirectLayerInit
dictionary :XRGPUEquirectLayerInit XRGPULayerInit {XRRigidTransform ?transform ;float radius = 0;float centralHorizontalAngle = 6.28318;float upperVerticalAngle = 1.570795;float lowerVerticalAngle = -1.570795; };
The transform member specifies the initial position and orientation of the equirect layer.
The radius member specifies the radius of the sphere in meters. A value of 0 indicates an infinite sphere (the equirect is rendered as a skybox).
The centralHorizontalAngle member specifies the horizontal angular extent of the sphere in radians. The default value of 6.28318 corresponds to a full 360 degrees.
The upperVerticalAngle member specifies the upper vertical angle of the visible portion in radians, measured from the horizon.
The lowerVerticalAngle member specifies the lower vertical angle of the visible portion in radians, measured from the horizon.
4.7. XRGPUCubeLayerInit
dictionary :XRGPUCubeLayerInit XRGPULayerInit {DOMPointReadOnly ?orientation ; };
The orientation member specifies the initial orientation of the cube layer as a quaternion.
5. Security and Privacy Considerations
This specification does not introduce any new security or privacy considerations beyond those described in the WebXR Device API, WebXR Layers API, and WebGPU specifications.
The textures provided by XRGPUSubImage are allocated by the user agent and do not expose any additional information about the user’s environment beyond what the underlying XR session already provides. The user agent MUST ensure that textures returned by the binding do not contain data from previous frames or other origins.
The xrCompatible flag does not expose any new fingerprinting surface beyond what is already available through the requestAdapter() API, as the returned adapter capabilities are the same regardless of whether XR compatibility is requested.
6. Conformance
As well as sections marked as non-normative, all authoring guidelines, diagrams, examples, and notes in this specification are non-normative. Everything else in this specification is normative.
The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", "SHOULD NOT", "RECOMMENDED", "NOT RECOMMENDED", "MAY", and "OPTIONAL" in this document are to be interpreted as described in BCP 14 when, and only when, they appear in all capitals, as shown here.