Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 6 additions & 1 deletion src/platform/graphics/bind-group-format.js
Original file line number Diff line number Diff line change
Expand Up @@ -132,8 +132,10 @@ class BindTextureFormat extends BindBaseFormat {
* sampler is used, it will take up an additional slot, directly following the texture slot.
* Defaults to true.
* @param {string|null} [samplerName] - Optional name of the sampler. Defaults to null.
* @param {boolean} [multisampled] - True if the texture binding is multisampled. Defaults to
* false.
*/
constructor(name, visibility, textureDimension = TEXTUREDIMENSION_2D, sampleType = SAMPLETYPE_FLOAT, hasSampler = true, samplerName = null) {
constructor(name, visibility, textureDimension = TEXTUREDIMENSION_2D, sampleType = SAMPLETYPE_FLOAT, hasSampler = true, samplerName = null, multisampled = false) {
super(name, visibility);

// TEXTUREDIMENSION_***
Expand All @@ -147,6 +149,9 @@ class BindTextureFormat extends BindBaseFormat {

// optional name of the sampler (its automatically generated if not provided)
this.samplerName = samplerName ?? `${name}_sampler`;

// whether the texture is multisampled
this.multisampled = multisampled;
}
}

Expand Down
3 changes: 3 additions & 0 deletions src/platform/graphics/shader-definition-utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -221,6 +221,9 @@ class ShaderDefinitionUtils {
if (shaderType === 'fragment' && device.supportsPrimitiveIndex) {
code += 'enable primitive_index;\n';
}
if (device.supportsMultisampledArrayTextures) {
code += 'enable multisampled_array_textures;\n';
}
if (device.supportsSubgroups) {
code += 'enable subgroups;\n';
}
Expand Down
25 changes: 21 additions & 4 deletions src/platform/graphics/shader-processor-options.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,20 +22,26 @@ class ShaderProcessorOptions {
/** @type {VertexFormat[]} */
vertexFormat;

/** @type {boolean} */
viewInstancing = false;

/**
* Constructs shader processing options, used to process the shader for uniform buffer support.
*
* @param {UniformBufferFormat} [viewUniformFormat] - Format of the uniform buffer.
* @param {BindGroupFormat} [viewBindGroupFormat] - Format of the bind group.
* @param {VertexFormat} [vertexFormat] - Format of the vertex buffer.
* @param {boolean} [viewInstancing] - True to process the shader for WebGPU native view
* instancing. Defaults to false.
*/
constructor(viewUniformFormat, viewBindGroupFormat, vertexFormat) {
constructor(viewUniformFormat, viewBindGroupFormat, vertexFormat, viewInstancing = false) {

// construct a sparse array
this.uniformFormats[BINDGROUP_VIEW] = viewUniformFormat;
this.bindGroupFormats[BINDGROUP_VIEW] = viewBindGroupFormat;

this.vertexFormat = vertexFormat;
this.viewInstancing = viewInstancing;
}

/**
Expand All @@ -45,15 +51,25 @@ class ShaderProcessorOptions {
* @returns {boolean} - Returns true if the uniform exists, false otherwise.
*/
hasUniform(name) {
return !!this.getUniform(name);
}

/**
* Get the uniform format for the uniform name.
*
* @param {string} name - The name of the uniform.
* @returns {UniformFormat|undefined} - Returns the uniform format if it exists.
*/
getUniform(name) {
for (let i = 0; i < this.uniformFormats.length; i++) {
const uniformFormat = this.uniformFormats[i];
if (uniformFormat?.get(name)) {
return true;
const format = uniformFormat?.get(name) ?? uniformFormat?.get(`${name}[0]`);
if (format) {
return format;
}
}

return false;
return undefined;
}

/**
Expand Down Expand Up @@ -92,6 +108,7 @@ class ShaderProcessorOptions {
// WebGPU shaders are processed per vertex format
if (device.isWebGPU) {
key += this.vertexFormat?.shaderProcessingHashString;
key += `#viewInstancing:${this.viewInstancing ? 1 : 0}`;
}

return key;
Expand Down
2 changes: 1 addition & 1 deletion src/platform/graphics/webgpu/webgpu-bind-group-format.js
Original file line number Diff line number Diff line change
Expand Up @@ -123,7 +123,7 @@ class WebgpuBindGroupFormat {
// texture
const sampleType = textureFormat.sampleType;
const viewDimension = textureFormat.textureDimension;
const multisampled = false;
const multisampled = textureFormat.multisampled;

const gpuSampleType = sampleTypes[sampleType];
Debug.assert(gpuSampleType);
Expand Down
83 changes: 82 additions & 1 deletion src/platform/graphics/webgpu/webgpu-graphics-device.js
Original file line number Diff line number Diff line change
Expand Up @@ -181,6 +181,30 @@ class WebgpuGraphicsDevice extends GraphicsDevice {
*/
xrColorTextureViewDescriptor = null;

/**
* True when the active XR frame is rendered using WebGPU view instancing into a texture array.
*
* @type {boolean}
* @ignore
*/
xrNativeViewInstancing = false;

/**
* Number of XR views rendered by a native view-instanced render pass.
*
* @type {number}
* @ignore
*/
xrViewCount = 1;

/**
* View count used to allocate the current WebGPU backbuffer attachments.
*
* @type {number}
* @private
*/
_backBufferViewCount = 1;

/**
* Per-view XR sub-image entries populated each frame by the WebGPU XR bridge. Each entry
* describes one XR view: the underlying GPU color texture, the view descriptor that selects the
Expand All @@ -200,6 +224,38 @@ class WebgpuGraphicsDevice extends GraphicsDevice {
*/
xrCurrentViewIndex = -1;

/**
* True when the WebGPU `view-instancing` feature is available on the device.
*
* @type {boolean}
* @ignore
*/
supportsViewInstancing = false;

/**
* True when the WebGPU `multisampled-array-textures` feature is available on the device.
*
* @type {boolean}
* @ignore
*/
supportsMultisampledArrayTextures = false;

/**
* Maximum view count requested for WebGPU view instancing.
*
* @type {number}
* @ignore
*/
maxViewInstanceCount = 1;

/**
* Fixed shader uniform array size for XR stereo views.
*
* @type {number}
* @ignore
*/
maxXrViews = 2;

/**
* When set, used as the main color attachment in {@link WebgpuGraphicsDevice#frameStart} if there is
* no XR color texture and no canvas {@link GPUCanvasContext#getCurrentTexture} (for example headless
Expand Down Expand Up @@ -285,6 +341,8 @@ class WebgpuGraphicsDevice extends GraphicsDevice {
this.xrColorTexture = null;
this.xrColorTextureViewFormat = null;
this.xrColorTextureViewDescriptor = null;
this.xrNativeViewInstancing = false;
this.xrViewCount = 1;
this.xrSubImages.length = 0;
this.xrCurrentViewIndex = -1;
}
Expand Down Expand Up @@ -405,6 +463,20 @@ class WebgpuGraphicsDevice extends GraphicsDevice {
this.supportsTextureFormatTier1 ||= this.supportsTextureFormatTier2;
this.supportsPrimitiveIndex = requireFeature('primitive-index');
this.supportsSubgroups = requireFeature('subgroups');
this.supportsMultisampledArrayTextures = requireFeature('multisampled-array-textures');

const adapterLimits = this.gpuAdapter?.limits;
const maxViewInstanceCount = adapterLimits?.maxViewInstanceCount ?? 1;
this.supportsViewInstancing = !bare &&
this.gpuAdapter.features.has('view-instancing') &&
maxViewInstanceCount >= this.maxXrViews;
if (this.supportsViewInstancing) {
requiredFeatures.push('view-instancing');
this.maxViewInstanceCount = maxViewInstanceCount;
} else {
this.maxViewInstanceCount = 1;
}

this.maxSubgroupSize = this.supportsSubgroups ? (this.gpuAdapter?.info?.subgroupMaxSize ?? 0) : 0;
this.minSubgroupSize = this.supportsSubgroups ? (this.gpuAdapter?.info?.subgroupMinSize ?? 0) : 0;
Debug.log(
Expand All @@ -428,6 +500,9 @@ class WebgpuGraphicsDevice extends GraphicsDevice {
}
}
}
if (this.supportsViewInstancing && requiredLimits.maxViewInstanceCount === undefined) {
requiredLimits.maxViewInstanceCount = this.maxXrViews;
}

/** @type {GPUDeviceDescriptor} */
const deviceDescr = {
Expand Down Expand Up @@ -591,9 +666,15 @@ class WebgpuGraphicsDevice extends GraphicsDevice {
// Reallocate framebuffer if dimensions change, to match the output texture. For WebXR
// WebGPU projection color targets that are 2d-array textures, width/height are the per-layer
// extent (same for every view), which matches what the render pass and internal depth need.
if (this.backBufferSize.x !== outColorBuffer.width || this.backBufferSize.y !== outColorBuffer.height) {
const viewCount = this.xrNativeViewInstancing ? this.xrViewCount : 1;
if (
this.backBufferSize.x !== outColorBuffer.width ||
this.backBufferSize.y !== outColorBuffer.height ||
this._backBufferViewCount !== viewCount
) {

this.backBufferSize.set(outColorBuffer.width, outColorBuffer.height);
this._backBufferViewCount = viewCount;

this.backBuffer.destroy();
this.backBuffer = null;
Expand Down
44 changes: 36 additions & 8 deletions src/platform/graphics/webgpu/webgpu-render-target.js
Original file line number Diff line number Diff line change
Expand Up @@ -235,6 +235,15 @@ class WebgpuRenderTarget {
}
}

/**
* @param {WebgpuGraphicsDevice} device - The graphics device.
* @returns {number} Number of array layers rendered by a native view-instanced backbuffer pass.
* @private
*/
getViewCount(device) {
return this.isBackbuffer && device.xrNativeViewInstancing ? device.xrViewCount : 1;
}

/**
* Initialize render target for rendering one time.
*
Expand Down Expand Up @@ -277,6 +286,13 @@ class WebgpuRenderTarget {

this.updateKey();

const viewCount = this.getViewCount(device);
if (viewCount > 1) {
this.renderPassDescriptor.viewCount = viewCount;
} else {
delete this.renderPassDescriptor.viewCount;
}

this.initialized = true;

WebgpuDebug.end(device, 'RenderTarget initialization', { renderTarget });
Expand All @@ -286,6 +302,12 @@ class WebgpuRenderTarget {
initDepthStencil(device, wgpu, renderTarget) {

const { samples, width, height, depth, depthBuffer } = renderTarget;
const viewCount = this.getViewCount(device);
const viewDesc = viewCount > 1 ? {
dimension: '2d-array',
baseArrayLayer: 0,
arrayLayerCount: viewCount
} : undefined;

// depth buffer that we render to (single or multi-sampled). We don't create resolve
// depth buffer as we don't currently resolve it. This might need to change in the future.
Expand All @@ -302,7 +324,7 @@ class WebgpuRenderTarget {

/** @type {GPUTextureDescriptor} */
const depthTextureDesc = {
size: [width, height, 1],
size: [width, height, viewCount],
dimension: '2d',
sampleCount: samples,
format: this.depthAttachment.format,
Expand All @@ -325,7 +347,7 @@ class WebgpuRenderTarget {
this.depthAttachment.depthTexture = depthTexture;
this.depthAttachment.depthTextureInternal = true;

renderingView = depthTexture.createView();
renderingView = depthTexture.createView(viewDesc);
DebugHelper.setLabel(renderingView, `${renderTarget.name}.autoDepthView`);

} else { // use provided depth buffer
Expand All @@ -341,7 +363,7 @@ class WebgpuRenderTarget {
this.depthAttachment.hasStencil = depthFormat === 'depth24plus-stencil8';

// key for matching multi-sampled depth buffer
const key = `${depthBuffer.id}:${width}:${height}:${samples}:${depthFormat}`;
const key = `${depthBuffer.id}:${width}:${height}:${viewCount}:${samples}:${depthFormat}`;

// check if we have already allocated a multi-sampled depth buffer for the depth buffer
const msTextures = getMultisampledTextureCache(device);
Expand All @@ -350,7 +372,7 @@ class WebgpuRenderTarget {

/** @type {GPUTextureDescriptor} */
const multisampledDepthDesc = {
size: [width, height, 1],
size: [width, height, viewCount],
dimension: '2d',
sampleCount: samples,
format: depthFormat,
Expand All @@ -370,7 +392,7 @@ class WebgpuRenderTarget {
this.depthAttachment.multisampledDepthBuffer = msDepthTexture;
this.depthAttachment.multisampledDepthBufferKey = key;

renderingView = msDepthTexture.createView();
renderingView = msDepthTexture.createView(viewDesc);
DebugHelper.setLabel(renderingView, `${renderTarget.name}.multisampledDepthView`);

} else {
Expand All @@ -379,7 +401,7 @@ class WebgpuRenderTarget {
const depthTexture = depthBuffer.impl.gpuTexture;
this.depthAttachment.depthTexture = depthTexture;

renderingView = depthTexture.createView();
renderingView = depthTexture.createView(viewDesc);
DebugHelper.setLabel(renderingView, `${renderTarget.name}.depthView`);
}
}
Expand Down Expand Up @@ -409,6 +431,12 @@ class WebgpuRenderTarget {

const { samples, width, height, mipLevel } = renderTarget;
const colorBuffer = renderTarget.getColorBuffer(index);
const viewCount = this.getViewCount(device);
const viewDesc = viewCount > 1 ? {
dimension: '2d-array',
baseArrayLayer: 0,
arrayLayerCount: viewCount
} : undefined;

// view used to write to the color buffer (either by rendering to it, or resolving to it)
let colorView = null;
Expand Down Expand Up @@ -446,7 +474,7 @@ class WebgpuRenderTarget {

/** @type {GPUTextureDescriptor} */
const multisampledTextureDesc = {
size: [width, height, 1],
size: [width, height, viewCount],
dimension: '2d',
sampleCount: samples,
format: format,
Expand All @@ -458,7 +486,7 @@ class WebgpuRenderTarget {
DebugHelper.setLabel(multisampledColorBuffer, `${renderTarget.name}.multisampledColor`);
this.setColorAttachment(index, multisampledColorBuffer, multisampledTextureDesc.format);

colorAttachment.view = multisampledColorBuffer.createView();
colorAttachment.view = multisampledColorBuffer.createView(viewDesc);
DebugHelper.setLabel(colorAttachment.view, `${renderTarget.name}.multisampledColorView`);

colorAttachment.resolveTarget = colorView;
Expand Down
Loading