three
Version:
JavaScript 3D library
3,282 lines • 94 kB
JavaScript
// debugger tools
// import 'https://greggman.github.io/webgpu-avoid-redundant-state-setting/webgpu-check-redundant-state-setting.js';
import { GPUFeatureName, GPULoadOp, GPUStoreOp, GPUIndexFormat, GPUTextureViewDimension, GPUFeatureMap, GPUShaderStage } from './utils/WebGPUConstants.js';
import WGSLNodeBuilder from './nodes/WGSLNodeBuilder.js';
import Backend from '../common/Backend.js';
import WebGPUUtils, { submit } from './utils/WebGPUUtils.js';
import WebGPUAttributeUtils from './utils/WebGPUAttributeUtils.js';
import WebGPUBindingUtils from './utils/WebGPUBindingUtils.js';
import WebGPUCapabilities from './utils/WebGPUCapabilities.js';
import WebGPUPipelineUtils from './utils/WebGPUPipelineUtils.js';
import WebGPUTextureUtils from './utils/WebGPUTextureUtils.js';
import { WebGPUCoordinateSystem, TimestampQuery, REVISION, HalfFloatType, Compatibility, CustomBlending } from '../../constants.js';
import { Color } from '../../math/Color.js';
import WebGPUTimestampQueryPool from './utils/WebGPUTimestampQueryPool.js';
import { error, warnOnce } from '../../utils.js';
import GPUBufferDescriptor from './descriptors/GPUBufferDescriptor.js';
import GPUCommandEncoderDescriptor from './descriptors/GPUCommandEncoderDescriptor.js';
import GPUComputePassDescriptor from './descriptors/GPUComputePassDescriptor.js';
import GPUQuerySetDescriptor from './descriptors/GPUQuerySetDescriptor.js';
import GPUShaderModuleDescriptor from './descriptors/GPUShaderModuleDescriptor.js';
import GPURenderPassColorAttachment from './descriptors/GPURenderPassColorAttachment.js';
import GPURenderPassDepthStencilAttachment from './descriptors/GPURenderPassDepthStencilAttachment.js';
import GPURenderPassDescriptor from './descriptors/GPURenderPassDescriptor.js';
import GPURenderPassTimestampWrites from './descriptors/GPURenderPassTimestampWrites.js';
import GPUTexelCopyTextureInfo from './descriptors/GPUTexelCopyTextureInfo.js';
import GPUTextureDescriptor from './descriptors/GPUTextureDescriptor.js';
import GPUTextureViewDescriptor from './descriptors/GPUTextureViewDescriptor.js';
import GPUExtent3D from './descriptors/GPUExtent3D.js';
const _clearValue = { r: 0, g: 0, b: 0, a: 1 };
const _blendConstant = { r: 0, g: 0, b: 0, a: 0 };
const _bufferDescriptor = new GPUBufferDescriptor();
const _commandEncoderDescriptor = new GPUCommandEncoderDescriptor();
const _computePassDescriptor = new GPUComputePassDescriptor();
const _querySetDescriptor = new GPUQuerySetDescriptor();
const _shaderModuleDescriptor = new GPUShaderModuleDescriptor();
const _renderPassTimestampWrites = new GPURenderPassTimestampWrites();
const _texelCopyTextureInfoSrc = new GPUTexelCopyTextureInfo();
const _texelCopyTextureInfoDst = new GPUTexelCopyTextureInfo();
const _textureDescriptor = new GPUTextureDescriptor();
const _viewDescriptor = new GPUTextureViewDescriptor();
const _extent3D = new GPUExtent3D();
/**
* A backend implementation targeting WebGPU.
*
* @private
* @augments Backend
*/
class WebGPUBackend extends Backend {
/**
* WebGPUBackend options.
*
* @typedef {Object} WebGPUBackend~Options
* @property {boolean} [logarithmicDepthBuffer=false] - Whether logarithmic depth buffer is enabled or not.
* @property {boolean} [reversedDepthBuffer=false] - Whether reversed depth buffer is enabled or not.
* @property {boolean} [alpha=true] - Whether the default framebuffer (which represents the final contents of the canvas) should be transparent or opaque.
* @property {boolean} [depth=true] - Whether the default framebuffer should have a depth buffer or not.
* @property {boolean} [stencil=false] - Whether the default framebuffer should have a stencil buffer or not.
* @property {boolean} [antialias=false] - Whether MSAA as the default anti-aliasing should be enabled or not.
* @property {number} [samples=0] - When `antialias` is `true`, `4` samples are used by default. Set this parameter to any other integer value than 0 to overwrite the default.
* @property {boolean} [forceWebGL=false] - If set to `true`, the renderer uses a WebGL 2 backend no matter if WebGPU is supported or not.
* @property {boolean} [trackTimestamp=false] - Whether to track timestamps with a Timestamp Query API or not.
* @property {string} [powerPreference=undefined] - The power preference.
* @property {Object} [requiredLimits=undefined] - Specifies the limits that are required by the device request. The request will fail if the adapter cannot provide these limits.
* @property {GPUDevice} [device=undefined] - If there is an existing GPU device on app level, it can be passed to the renderer as a parameter.
* @property {number} [outputType=undefined] - Texture type for output to canvas. By default, device's preferred format is used; other formats may incur overhead.
*/
/**
* Constructs a new WebGPU backend.
*
* @param {WebGPUBackend~Options} [parameters] - The configuration parameter.
*/
constructor( parameters = {} ) {
super( parameters );
/**
* This flag can be used for type testing.
*
* @type {boolean}
* @readonly
* @default true
*/
this.isWebGPUBackend = true;
// some parameters require default values other than "undefined"
this.parameters.alpha = ( parameters.alpha === undefined ) ? true : parameters.alpha;
this.parameters.requiredLimits = ( parameters.requiredLimits === undefined ) ? {} : parameters.requiredLimits;
/**
* Indicates whether the backend is in WebGPU compatibility mode or not.
* The backend must be initialized before the property can be evaluated.
*
* @type {?boolean}
* @readonly
* @default null
*/
this.compatibilityMode = null;
/**
* A reference to the device.
*
* @type {?GPUDevice}
* @default null
*/
this.device = null;
/**
* A reference to the default render pass descriptor.
*
* @type {?Object}
* @default null
*/
this.defaultRenderPassdescriptor = null;
/**
* A reference to a backend module holding common utility functions.
*
* @type {WebGPUUtils}
*/
this.utils = new WebGPUUtils( this );
/**
* A reference to a backend module holding shader attribute-related
* utility functions.
*
* @type {WebGPUAttributeUtils}
*/
this.attributeUtils = new WebGPUAttributeUtils( this );
/**
* A reference to a backend module holding shader binding-related
* utility functions.
*
* @type {WebGPUBindingUtils}
*/
this.bindingUtils = new WebGPUBindingUtils( this );
/**
* A reference to a backend module holding device capability related
* utility functions.
*
* @type {WebGPUCapabilities}
*/
this.capabilities = new WebGPUCapabilities( this );
/**
* A reference to a backend module holding shader pipeline-related
* utility functions.
*
* @type {WebGPUPipelineUtils}
*/
this.pipelineUtils = new WebGPUPipelineUtils( this );
/**
* A reference to a backend module holding shader texture-related
* utility functions.
*
* @type {WebGPUTextureUtils}
*/
this.textureUtils = new WebGPUTextureUtils( this );
/**
* A map that manages the resolve buffers for occlusion queries.
*
* @type {Map<number,GPUBuffer>}
*/
this.occludedResolveCache = new Map();
// compatibility checks
const compatibilityTextureCompare = typeof navigator === 'undefined' ? true : /Android/.test( navigator.userAgent ) === false;
/**
* A map of compatibility checks.
*
* @type {Object}
*/
this._compatibility = {
[ Compatibility.TEXTURE_COMPARE ]: compatibilityTextureCompare
};
}
/**
* Initializes the backend so it is ready for usage.
*
* @async
* @param {Renderer} renderer - The renderer.
* @return {Promise} A Promise that resolves when the backend has been initialized.
*/
async init( renderer ) {
await super.init( renderer );
//
const parameters = this.parameters;
// create the device if it is not passed with parameters
let device;
if ( parameters.device === undefined ) {
const adapterOptions = {
powerPreference: parameters.powerPreference,
featureLevel: 'compatibility',
xrCompatible: renderer.xr.enabled
};
const adapter = ( typeof navigator !== 'undefined' ) ? await navigator.gpu.requestAdapter( adapterOptions ) : null;
if ( adapter === null ) {
throw new Error( 'THREE.WebGPUBackend: Unable to create WebGPU adapter.' );
}
// feature support
const features = Object.values( GPUFeatureName );
const supportedFeatures = [];
for ( const name of features ) {
if ( adapter.features.has( name ) ) {
supportedFeatures.push( name );
}
}
const deviceDescriptor = {
requiredFeatures: supportedFeatures,
requiredLimits: parameters.requiredLimits
};
device = await adapter.requestDevice( deviceDescriptor );
} else {
device = parameters.device;
}
this.compatibilityMode = ! device.features.has( 'core-features-and-limits' );
if ( this.compatibilityMode ) {
renderer._samples = 0;
}
device.lost.then( ( info ) => {
if ( info.reason === 'destroyed' ) return;
const deviceLossInfo = {
api: 'WebGPU',
message: info.message || 'Unknown reason',
reason: info.reason || null,
originalEvent: info
};
renderer.onDeviceLost( deviceLossInfo );
} );
device.onuncapturederror = ( event ) => {
const gpuError = event.error;
const type = gpuError && gpuError.constructor ? gpuError.constructor.name : 'GPUError';
const message = ( gpuError && gpuError.message ) || 'Unknown uncaptured GPU error';
renderer.onError( {
api: 'WebGPU',
type,
message,
originalEvent: event
} );
};
this.device = device;
this.trackTimestamp = this.trackTimestamp && this.hasFeature( GPUFeatureName.TimestampQuery );
this.updateSize();
}
/**
* Registers external GPU textures from `XRGPUBinding` for use in rendering.
*
* @param {RenderTarget} renderTarget - The render target to register the textures for.
* @param {GPUTexture} colorTexture - The shared XR color GPUTexture.
* @param {?Array<Object>} [viewDescriptors=null] - Optional view descriptors, one per XR view.
*/
setXRRenderTargetTextures( renderTarget, colorTexture, viewDescriptors = null ) {
// Update the external XR texture without replacing the cached MSAA attachments.
const textureData = this.get( renderTarget.texture );
textureData.texture = colorTexture;
textureData.format = colorTexture.format;
textureData.externalTexture = true;
textureData.xrViewDescriptors = viewDescriptors;
textureData.initialized = true;
}
/**
* A reference to the context.
*
* @type {?GPUCanvasContext}
* @default null
*/
get context() {
const canvasTarget = this.renderer.getCanvasTarget();
const canvasData = this.get( canvasTarget );
let context = canvasData.context;
if ( context === undefined ) {
const parameters = this.parameters;
if ( canvasTarget.isDefaultCanvasTarget === true && parameters.context !== undefined ) {
context = parameters.context;
} else {
context = canvasTarget.domElement.getContext( 'webgpu' );
}
// OffscreenCanvas does not have setAttribute, see #22811
if ( 'setAttribute' in canvasTarget.domElement ) canvasTarget.domElement.setAttribute( 'data-engine', `three.js r${ REVISION } webgpu` );
const alphaMode = parameters.alpha ? 'premultiplied' : 'opaque';
const toneMappingMode = parameters.outputType === HalfFloatType ? 'extended' : 'standard';
context.configure( {
device: this.device,
format: this.utils.getPreferredCanvasFormat(),
usage: GPUTextureUsage.RENDER_ATTACHMENT | GPUTextureUsage.COPY_SRC,
alphaMode: alphaMode,
toneMapping: {
mode: toneMappingMode
}
} );
canvasData.context = context;
}
return context;
}
/**
* The coordinate system of the backend.
*
* @type {number}
* @readonly
*/
get coordinateSystem() {
return WebGPUCoordinateSystem;
}
/**
* Whether the backend supports query timestamps or not.
*
* @type {boolean}
* @readonly
*/
get hasTimestamp() {
return true;
}
/**
* This method performs a readback operation by moving buffer data from
* a storage buffer attribute from the GPU to the CPU. ReadbackBuffer can
* be used to retain and reuse handles to the intermediate buffers and prevent
* new allocation.
*
* @async
* @param {BufferAttribute} attribute - The storage buffer attribute to read frm.
* @param {number} count - The offset from which to start reading the
* @param {number} offset - The storage buffer attribute.
* @param {ReadbackBuffer|ArrayBuffer} target - The storage buffer attribute.
* @return {Promise<ArrayBuffer|ReadbackBuffer>} A promise that resolves with the buffer data when the data are ready.
*/
async getArrayBufferAsync( attribute, target = null, offset = 0, count = - 1 ) {
return await this.attributeUtils.getArrayBufferAsync( attribute, target, offset, count );
}
/**
* Returns the backend's rendering context.
*
* @return {GPUCanvasContext} The rendering context.
*/
getContext() {
return this.context;
}
/**
* Returns the default render pass descriptor.
*
* In WebGPU, the default framebuffer must be configured
* like custom framebuffers so the backend needs a render
* pass descriptor even when rendering directly to screen.
*
* @private
* @return {GPURenderPassDescriptor} The render pass descriptor.
*/
_getDefaultRenderPassDescriptor() {
const renderer = this.renderer;
const canvasTarget = renderer.getCanvasTarget();
const canvasData = this.get( canvasTarget );
const samples = renderer.currentSamples;
let descriptor = canvasData.descriptor;
if ( descriptor === undefined || canvasData.samples !== samples ) {
descriptor = new GPURenderPassDescriptor();
descriptor.colorAttachments.push( new GPURenderPassColorAttachment() );
if ( renderer.depth === true || renderer.stencil === true ) {
const depthStencilAttachment = new GPURenderPassDepthStencilAttachment();
depthStencilAttachment.view = this.textureUtils.getDepthBuffer( renderer.depth, renderer.stencil ).createView();
descriptor.depthStencilAttachment = depthStencilAttachment;
}
const colorAttachment = descriptor.colorAttachments[ 0 ];
if ( samples > 0 ) {
colorAttachment.view = this.textureUtils.getColorBuffer().createView();
} else {
colorAttachment.resolveTarget = undefined;
}
canvasData.descriptor = descriptor;
canvasData.samples = samples;
}
const colorAttachment = descriptor.colorAttachments[ 0 ];
if ( samples > 0 ) {
colorAttachment.resolveTarget = this.context.getCurrentTexture().createView();
} else {
colorAttachment.view = this.context.getCurrentTexture().createView();
}
return descriptor;
}
/**
* Returns whether the render target is a render target array with depth 2D array texture.
*
* @param {RenderContext} renderContext - The render context.
* @return {boolean} Whether the render target is a render target array with depth 2D array texture.
*
* @private
*/
_isRenderCameraDepthArray( renderContext ) {
const camera = renderContext.camera;
return renderContext.depthTexture && renderContext.depthTexture.isArrayTexture === true && camera !== null && camera.isArrayCamera === true;
}
/**
* Returns whether the current render context references external textures.
*
* External textures can change every frame, so their descriptors must not be cached.
*
* @private
* @param {RenderContext} renderContext - The render context.
* @return {boolean} Whether the render context uses external textures.
*/
_hasExternalTexture( renderContext ) {
const textures = renderContext.textures;
if ( textures === null ) return false;
for ( let i = 0; i < textures.length; i ++ ) {
if ( this.get( textures[ i ] ).externalTexture === true ) return true;
}
return false;
}
/**
* Returns multisampled color textures for an external render target.
*
* @private
* @param {RenderContext} renderContext - The render context.
* @param {Object} textureData - The backend data for the external texture.
* @param {number} count - The number of textures to create.
* @return {?Array<GPUTexture>} The multisampled textures.
*/
_getExternalMSAATextures( renderContext, textureData, count ) {
const samples = this.utils.getSampleCount( renderContext.sampleCount );
if ( samples === 1 ) {
if ( textureData.msaaTextures !== undefined ) {
for ( const texture of textureData.msaaTextures ) texture.destroy();
textureData.msaaTextures = undefined;
}
return null;
}
const renderTarget = renderContext.renderTarget;
const width = renderTarget.width;
const height = renderTarget.height;
const format = textureData.format;
if ( textureData.msaaTextures === undefined ||
textureData.msaaTextures.length !== count ||
textureData.msaaWidth !== width ||
textureData.msaaHeight !== height ||
textureData.msaaSamples !== samples ||
textureData.msaaFormat !== format ) {
if ( textureData.msaaTextures !== undefined ) {
for ( const texture of textureData.msaaTextures ) texture.destroy();
}
_textureDescriptor.size.width = width;
_textureDescriptor.size.height = height;
_textureDescriptor.sampleCount = samples;
_textureDescriptor.format = format;
// Layered rendering can resume after a framebuffer copy,
// so these attachments must support loading.
_textureDescriptor.usage = GPUTextureUsage.RENDER_ATTACHMENT;
textureData.msaaTextures = [];
for ( let i = 0; i < count; i ++ ) {
_textureDescriptor.label = renderTarget.texture.name + '-msaa-' + i;
textureData.msaaTextures.push( this.device.createTexture( _textureDescriptor ) );
}
_textureDescriptor.reset();
textureData.msaaWidth = width;
textureData.msaaHeight = height;
textureData.msaaSamples = samples;
textureData.msaaFormat = format;
}
return textureData.msaaTextures;
}
/**
* Creates attachment views for an external texture render target.
*
* @private
* @param {RenderContext} renderContext - The render context.
* @param {Object} textureData - The backend data for the texture.
* @return {Array<Object>} The attachment view descriptors.
*/
_createExternalTextureViews( renderContext, textureData ) {
const textureViews = [];
const camera = renderContext.camera;
const viewDescriptors = textureData.xrViewDescriptors;
const viewCount = Math.max( viewDescriptors?.length || 0, renderContext.activeCubeFace + 1, 1 );
const msaaTextures = this._getExternalMSAATextures( renderContext, textureData, viewCount );
if ( viewDescriptors && camera !== null && camera.isArrayCamera === true ) {
for ( let i = 0; i < viewDescriptors.length; i ++ ) {
const textureView = textureData.texture.createView( viewDescriptors[ i ] );
textureViews.push( {
view: msaaTextures !== null ? msaaTextures[ i ].createView() : textureView,
resolveTarget: msaaTextures !== null && renderContext.renderTarget.resolveColorBuffer === true ? textureView : undefined,
depthSlice: undefined
} );
}
} else {
const layer = renderContext.activeCubeFace;
const textureView = textureData.texture.createView( {
dimension: GPUTextureViewDimension.TwoD,
baseArrayLayer: layer,
arrayLayerCount: 1
} );
textureViews.push( {
view: msaaTextures !== null ? msaaTextures[ layer ].createView() : textureView,
resolveTarget: msaaTextures !== null && renderContext.renderTarget.resolveColorBuffer === true ? textureView : undefined,
depthSlice: undefined
} );
}
return textureViews;
}
/**
* Returns the render pass descriptor for the given render context.
*
* @private
* @param {RenderContext} renderContext - The render context.
* @param {Object} colorAttachmentsConfig - Configuration object for the color attachments.
* @return {Object} The render pass descriptor.
*/
_getRenderPassDescriptor( renderContext, colorAttachmentsConfig = {} ) {
const renderTarget = renderContext.renderTarget;
const renderTargetData = this.get( renderTarget );
const hasExternalTexture = this._hasExternalTexture( renderContext );
let descriptors = renderTargetData.descriptors;
if ( descriptors === undefined ||
renderTargetData.width !== renderTarget.width ||
renderTargetData.height !== renderTarget.height ||
renderTargetData.samples !== renderTarget.samples ||
hasExternalTexture
) {
descriptors = {};
renderTargetData.descriptors = descriptors;
}
const cacheKey = renderContext.getCacheKey();
let descriptorBase = descriptors[ cacheKey ];
if ( descriptorBase === undefined || hasExternalTexture ) {
const textures = renderContext.textures;
const textureViews = [];
let sliceIndex;
const isRenderCameraDepthArray = this._isRenderCameraDepthArray( renderContext );
for ( let i = 0; i < textures.length; i ++ ) {
const textureData = this.get( textures[ i ] );
if ( textureData.externalTexture === true ) {
textureViews.push( ...this._createExternalTextureViews( renderContext, textureData ) );
continue;
}
_viewDescriptor.label = `colorAttachment_${ i }`;
_viewDescriptor.baseMipLevel = renderContext.activeMipmapLevel;
_viewDescriptor.mipLevelCount = 1;
_viewDescriptor.baseArrayLayer = renderContext.activeCubeFace;
_viewDescriptor.arrayLayerCount = 1;
_viewDescriptor.dimension = GPUTextureViewDimension.TwoD;
if ( renderTarget.isRenderTarget3D ) {
sliceIndex = renderContext.activeCubeFace;
_viewDescriptor.baseArrayLayer = 0;
_viewDescriptor.dimension = GPUTextureViewDimension.ThreeD;
} else if ( renderTarget.isRenderTarget && textures[ i ].image.depth > 1 ) {
if ( isRenderCameraDepthArray === true ) {
const cameras = renderContext.camera.cameras;
for ( let layer = 0; layer < cameras.length; layer ++ ) {
_viewDescriptor.baseArrayLayer = layer;
_viewDescriptor.arrayLayerCount = 1;
_viewDescriptor.dimension = GPUTextureViewDimension.TwoD;
const textureView = textureData.texture.createView( _viewDescriptor );
const msaaTexture = textureData.msaaTextures?.[ layer ];
textureViews.push( {
view: msaaTexture !== undefined ? msaaTexture.createView() : textureView,
resolveTarget: msaaTexture !== undefined && renderTarget.resolveColorBuffer === true ? textureView : undefined,
depthSlice: undefined
} );
}
} else {
_viewDescriptor.dimension = GPUTextureViewDimension.TwoDArray;
}
}
if ( isRenderCameraDepthArray !== true ) {
const textureView = textureData.texture.createView( _viewDescriptor );
let view, resolveTarget;
if ( textureData.msaaTexture !== undefined ) {
view = textureData.msaaTexture.createView();
resolveTarget = renderTarget.resolveColorBuffer === true ? textureView : undefined;
} else if ( textureData.msaaTextures !== undefined ) {
view = textureData.msaaTextures[ renderContext.activeCubeFace ].createView();
resolveTarget = renderTarget.resolveColorBuffer === true ? textureView : undefined;
} else {
view = textureView;
resolveTarget = undefined;
}
textureViews.push( {
view,
resolveTarget,
depthSlice: sliceIndex
} );
}
_viewDescriptor.reset();
}
const colorAttachments = [];
for ( let i = 0; i < textureViews.length; i ++ ) {
const viewInfo = textureViews[ i ];
const attachment = new GPURenderPassColorAttachment();
attachment.view = viewInfo.view;
attachment.depthSlice = viewInfo.depthSlice;
attachment.resolveTarget = viewInfo.resolveTarget;
colorAttachments.push( attachment );
}
descriptorBase = {
textureViews,
colorAttachments,
descriptor: new GPURenderPassDescriptor()
};
if ( renderContext.depth ) {
const depthTextureData = this.get( renderContext.depthTexture );
if ( renderContext.depthTexture.isArrayTexture || renderContext.depthTexture.isCubeTexture ) {
_viewDescriptor.dimension = GPUTextureViewDimension.TwoD;
_viewDescriptor.arrayLayerCount = 1;
_viewDescriptor.baseArrayLayer = renderContext.activeCubeFace;
}
const depthStencilAttachment = new GPURenderPassDepthStencilAttachment();
const msaaDepthTexture = depthTextureData.msaaTextures?.[ renderContext.activeCubeFace ];
depthStencilAttachment.view = msaaDepthTexture !== undefined ? msaaDepthTexture.createView() : depthTextureData.texture.createView( _viewDescriptor );
descriptorBase.depthStencilAttachment = depthStencilAttachment;
_viewDescriptor.reset();
}
descriptors[ cacheKey ] = descriptorBase;
renderTargetData.width = renderTarget.width;
renderTargetData.height = renderTarget.height;
renderTargetData.samples = renderTarget.samples;
renderTargetData.activeMipmapLevel = renderContext.activeMipmapLevel;
renderTargetData.activeCubeFace = renderContext.activeCubeFace;
}
const descriptor = descriptorBase.descriptor;
descriptor.reset();
// Apply dynamic properties to cached attachments
for ( let i = 0; i < descriptorBase.colorAttachments.length; i ++ ) {
const attachment = descriptorBase.colorAttachments[ i ];
let clearValue = { r: 0, g: 0, b: 0, a: 1 };
if ( i === 0 && colorAttachmentsConfig.clearValue ) {
clearValue = colorAttachmentsConfig.clearValue;
}
attachment.loadOp = colorAttachmentsConfig.loadOp || GPULoadOp.Load;
attachment.storeOp = colorAttachmentsConfig.storeOp || GPUStoreOp.Store;
attachment.clearValue = clearValue;
descriptor.colorAttachments.push( attachment );
}
if ( descriptorBase.depthStencilAttachment ) {
descriptor.depthStencilAttachment = descriptorBase.depthStencilAttachment;
}
return descriptor;
}
/**
* This method is executed at the beginning of a render call and prepares
* the WebGPU state for upcoming render calls
*
* @param {RenderContext} renderContext - The render context.
*/
beginRender( renderContext ) {
const renderContextData = this.get( renderContext );
//
const device = this.device;
const occlusionQueryCount = renderContext.occlusionQueryCount;
let occlusionQuerySet;
if ( occlusionQueryCount > 0 ) {
if ( renderContextData.currentOcclusionQuerySet ) renderContextData.currentOcclusionQuerySet.destroy();
if ( renderContextData.currentOcclusionQueryBuffer ) renderContextData.currentOcclusionQueryBuffer.destroy();
// Get a reference to the array of objects with queries. The renderContextData property
// can be changed by another render pass before the buffer.mapAsyc() completes.
renderContextData.currentOcclusionQuerySet = renderContextData.occlusionQuerySet;
renderContextData.currentOcclusionQueryBuffer = renderContextData.occlusionQueryBuffer;
renderContextData.currentOcclusionQueryObjects = renderContextData.occlusionQueryObjects;
//
_querySetDescriptor.label = `occlusionQuerySet_${ renderContext.id }`;
_querySetDescriptor.type = 'occlusion';
_querySetDescriptor.count = occlusionQueryCount;
occlusionQuerySet = device.createQuerySet( _querySetDescriptor );
_querySetDescriptor.reset();
renderContextData.occlusionQuerySet = occlusionQuerySet;
renderContextData.occlusionQueryIndex = 0;
renderContextData.occlusionQueryObjects = new Array( occlusionQueryCount );
renderContextData.lastOcclusionObject = null;
} else if ( renderContextData.lastOcclusionObject !== undefined ) {
// invalidate if there is a stale query
renderContextData.lastOcclusionObject = undefined;
renderContextData.occlusionQuerySet.destroy();
renderContextData.occlusionQuerySet = undefined;
}
let descriptor;
if ( renderContext.textures === null ) {
descriptor = this._getDefaultRenderPassDescriptor();
} else {
descriptor = this._getRenderPassDescriptor( renderContext, { loadOp: GPULoadOp.Load } );
}
this.initTimestampQuery( TimestampQuery.RENDER, this.getTimestampUID( renderContext ), descriptor );
descriptor.occlusionQuerySet = occlusionQuerySet;
const depthStencilAttachment = descriptor.depthStencilAttachment;
const renderTarget = renderContext.renderTarget;
const discardColor = renderContext.sampleCount > 1 && renderTarget?.storeMultisampledColorBuffer === false;
const discardDepth = renderContext.sampleCount > 1 && renderTarget?.storeMultisampledDepthBuffer === false;
const discardStencil = renderContext.sampleCount > 1 && renderTarget?.storeMultisampledStencilBuffer === false;
// A fullscreen pass overwrites the external texture, so its previous contents do not need to be loaded.
const clearExternalColor = renderContext.fullscreenPass === true && this._hasExternalTexture( renderContext );
if ( renderContext.textures !== null ) {
const colorAttachments = descriptor.colorAttachments;
for ( let i = 0; i < colorAttachments.length; i ++ ) {
const colorAttachment = colorAttachments[ i ];
if ( renderContext.clearColor || discardColor || clearExternalColor ) {
const clearColor = renderContext.mrt ? renderContext.mrt.getClearColor( renderContext.textures[ i ].name ) : null;
if ( clearColor !== null ) {
colorAttachment.clearValue = clearColor;
} else if ( i === 0 ) {
colorAttachment.clearValue = renderContext.clearColorValue;
} else {
_clearValue.r = 0;
_clearValue.g = 0;
_clearValue.b = 0;
_clearValue.a = 1;
colorAttachment.clearValue = _clearValue;
}
colorAttachment.loadOp = GPULoadOp.Clear;
} else {
colorAttachment.loadOp = GPULoadOp.Load;
}
if ( discardColor ) {
colorAttachment.storeOp = GPUStoreOp.Discard;
} else {
colorAttachment.storeOp = GPUStoreOp.Store;
}
}
} else {
const colorAttachment = descriptor.colorAttachments[ 0 ];
if ( renderContext.clearColor ) {
colorAttachment.clearValue = renderContext.clearColorValue;
colorAttachment.loadOp = GPULoadOp.Clear;
} else {
colorAttachment.loadOp = GPULoadOp.Load;
}
colorAttachment.storeOp = GPUStoreOp.Store;
}
//
if ( renderContext.depth ) {
if ( renderContext.clearDepth || discardDepth ) {
depthStencilAttachment.depthClearValue = renderContext.clearDepthValue;
depthStencilAttachment.depthLoadOp = GPULoadOp.Clear;
} else {
depthStencilAttachment.depthLoadOp = GPULoadOp.Load;
}
if ( discardDepth ) {
depthStencilAttachment.depthStoreOp = GPUStoreOp.Discard;
} else {
depthStencilAttachment.depthStoreOp = GPUStoreOp.Store;
}
}
if ( renderContext.stencil ) {
if ( renderContext.clearStencil || discardStencil ) {
depthStencilAttachment.stencilClearValue = renderContext.clearStencilValue;
depthStencilAttachment.stencilLoadOp = GPULoadOp.Clear;
} else {
depthStencilAttachment.stencilLoadOp = GPULoadOp.Load;
}
if ( discardStencil ) {
depthStencilAttachment.stencilStoreOp = GPUStoreOp.Discard;
} else {
depthStencilAttachment.stencilStoreOp = GPUStoreOp.Store;
}
}
//
_commandEncoderDescriptor.label = 'renderContext_' + renderContext.id;
const encoder = device.createCommandEncoder( _commandEncoderDescriptor );
_commandEncoderDescriptor.reset();
// Layered render targets: prepare bundle encoders for each camera in the array camera.
if ( this._isRenderCameraDepthArray( renderContext ) === true ) {
const cameras = renderContext.camera.cameras;
if ( ! renderContextData.layerDescriptors || renderContextData.layerDescriptors.length !== cameras.length ) {
this._createArrayCameraLayerDescriptors( renderContext, renderContextData, descriptor, cameras );
} else {
this._updateArrayCameraLayerDescriptors( renderContext, renderContextData, descriptor, cameras );
}
this._createArrayCameraBundleEncoders( renderContext, renderContextData );
renderContextData.arrayCameraRenderStages = [];
// We'll complete the bundles in finishRender
renderContextData.currentPass = null;
} else {
renderContextData.bundleEncoders = undefined;
renderContextData.bundleSets = undefined;
renderContextData.arrayCameraRenderStages = undefined;
const currentPass = encoder.beginRenderPass( descriptor );
renderContextData.currentPass = currentPass;
if ( renderContext.viewport ) {
this.updateViewport( renderContext );
}
if ( renderContext.scissor ) {
this.updateScissor( renderContext );
}
}
//
renderContextData.descriptor = descriptor;
renderContextData.encoder = encoder;
renderContextData.renderBundles = [];
this._resetRenderContextData( renderContextData );
}
/**
* Resets the state cache of the given render context data.
*
* A new render pass encoder starts with a blend constant and a stencil reference
* of zero so the cached values must be reset as well.
*
* @private
* @param {Object} renderContextData - The render context data.
*/
_resetRenderContextData( renderContextData ) {
renderContextData.currentSets = { attributes: {}, bindingGroups: [], pipeline: null, index: null };
if ( renderContextData.currentBlendColor === undefined ) renderContextData.currentBlendColor = new Color();
renderContextData.currentBlendColor.setRGB( 0, 0, 0 );
renderContextData.currentBlendAlpha = 0;
renderContextData.currentStencilRef = 0;
}
/**
* Creates a render bundle encoder and state cache for each camera layer.
*
* @param {RenderContext} renderContext - The render context.
* @param {Object} renderContextData - The render context data.
* @private
*/
_createArrayCameraBundleEncoders( renderContext, renderContextData ) {
const cameras = renderContext.camera.cameras;
renderContextData.bundleEncoders = [];
renderContextData.bundleSets = [];
for ( let i = 0; i < cameras.length; i ++ ) {
const bundleEncoder = this.pipelineUtils.createBundleEncoder(
renderContext,
'renderBundleArrayCamera_' + i
);
const bundleSets = {
attributes: {},
bindingGroups: [],
pipeline: null,
index: null
};
renderContextData.bundleEncoders.push( bundleEncoder );
renderContextData.bundleSets.push( bundleSets );
}
}
/**
* Finishes the render bundle encoders for all camera layers.
*
* @param {Object} renderContextData - The render context data.
* @return {Array<GPURenderBundle>} The completed render bundles.
* @private
*/
_finishArrayCameraBundleEncoders( renderContextData ) {
const bundles = [];
for ( const bundleEncoder of renderContextData.bundleEncoders ) {
bundles.push( bundleEncoder.finish() );
}
return bundles;
}
/**
* Creates render pass descriptors for each camera in an array camera.
*
* @param {RenderContext} renderContext - The render context.
* @param {Object} renderContextData - The render context data.
* @param {Object} descriptor - The render pass descriptor.
* @param {ArrayCamera} cameras - The array camera.
*
* @private
*/
_createArrayCameraLayerDescriptors( renderContext, renderContextData, descriptor, cameras ) {
const depthStencilAttachment = descriptor.depthStencilAttachment;
renderContextData.layerDescriptors = [];
const depthTextureData = this.get( renderContext.depthTexture );
if ( ! depthTextureData.viewCache ) {
depthTextureData.viewCache = [];
}
for ( let i = 0; i < cameras.length; i ++ ) {
const sourceAttachment = descriptor.colorAttachments[ 0 ];
const layerAttachment = descriptor.colorAttachments[ i ];
const layerColorAttachment = new GPURenderPassColorAttachment();
layerColorAttachment.view = layerAttachment.view;
layerColorAttachment.depthSlice = layerAttachment.depthSlice;
layerColorAttachment.resolveTarget = layerAttachment.resolveTarget;
layerColorAttachment.loadOp = sourceAttachment.loadOp;
layerColorAttachment.storeOp = sourceAttachment.storeOp;
layerColorAttachment.clearValue = sourceAttachment.clearValue;
const layerDescriptor = new GPURenderPassDescriptor();
layerDescriptor.label = descriptor.label;
layerDescriptor.occlusionQuerySet = descriptor.occlusionQuerySet;
layerDescriptor.timestampWrites = descriptor.timestampWrites;
layerDescriptor.colorAttachments.push( layerColorAttachment );
if ( descriptor.depthStencilAttachment ) {
const layerIndex = i;
if ( ! depthTextureData.viewCache[ layerIndex ] ) {
const msaaTexture = depthTextureData.msaaTextures?.[ layerIndex ];
if ( msaaTexture !== undefined ) {
depthTextureData.viewCache[ layerIndex ] = msaaTexture.createView();
} else {
_viewDescriptor.dimension = GPUTextureViewDimension.TwoD;
_viewDescriptor.baseArrayLayer = i;
_viewDescriptor.arrayLayerCount = 1;
depthTextureData.viewCache[ layerIndex ] = depthTextureData.texture.createView( _viewDescriptor );
}
_viewDescriptor.reset();
}
const layerDepthStencilAttachment = new GPURenderPassDepthStencilAttachment();
layerDepthStencilAttachment.view = depthTextureData.viewCache[ layerIndex ];
layerDepthStencilAttachment.depthLoadOp = depthStencilAttachment.depthLoadOp || GPULoadOp.Clear;
layerDepthStencilAttachment.depthStoreOp = depthStencilAttachment.depthStoreOp || GPUStoreOp.Store;
layerDepthStencilAttachment.depthClearValue = depthStencilAttachment.depthClearValue || 1.0;
if ( renderContext.stencil ) {
layerDepthStencilAttachment.stencilLoadOp = depthStencilAttachment.stencilLoadOp;
layerDepthStencilAttachment.stencilStoreOp = depthStencilAttachment.stencilStoreOp;
layerDepthStencilAttachment.stencilClearValue = depthStencilAttachment.stencilClearValue;
}
layerDescriptor.depthStencilAttachment = layerDepthStencilAttachment;
} else {
const layerDepthStencilAttachment = new GPURenderPassDepthStencilAttachment();
layerDepthStencilAttachment.view = depthStencilAttachment.view;
layerDepthStencilAttachment.depthLoadOp = depthStencilAttachment.depthLoadOp;
layerDepthStencilAttachment.depthStoreOp = depthStencilAttachment.depthStoreOp;
layerDepthStencilAttachment.depthClearValue = depthStencilAttachment.depthClearValue;
layerDepthStencilAttachment.depthReadOnly = depthStencilAttachment.depthReadOnly;
layerDepthStencilAttachment.stencilLoadOp = depthStencilAttachment.stencilLoadOp;
layerDepthStencilAttachment.stencilStoreOp = depthStencilAttachment.stencilStoreOp;
layerDepthStencilAttachment.stencilClearValue = depthStencilAttachment.stencilClearValue;
layerDepthStencilAttachment.stencilReadOnly = depthStencilAttachment.stencilReadOnly;
layerDescriptor.depthStencilAttachment = layerDepthStencilAttachment;
}
renderContextData.layerDescriptors.push( layerDescriptor );
}
}
/**
* Updates render pass descriptors for each camera in an array camera.
*
* @param {RenderContext} renderContext - The render context.
* @param {Object} renderContextData - The render context data.
* @param {Object} descriptor - The render pass descriptor.
* @param {ArrayCamera} cameras - The array camera.
*
*/
_updateArrayCameraLayerDescriptors( renderContext, renderContextData, descriptor, cameras ) {
const renderTarget = renderContext.renderTarget;
const discardDepth = renderContext.sampleCount > 1 && renderTarget?.storeMultisampledDepthBuffer === false;
const discardStencil = renderContext.sampleCount > 1 && renderTarget?.storeMultisampledStencilBuffer === false;
for ( let i = 0; i < cameras.length; i ++ ) {
const layerDescriptor = renderContextData.layerDescriptors[ i ];
const sourceColorAttachment = descriptor.colorAttachments[ 0 ];
const layerColorAttachment = descriptor.colorAttachments[ i ];
const colorAttachment = layerDescriptor.colorAttachments[ 0 ];
colorAttachment.view = layerColorAttachment.view;
colorAttachment.resolveTarget = layerColorAttachment.resolveTarget;
colorAttachment.depthSlice = layerColorAttachment.depthSlice;
colorAttachment.loadOp = sourceColorAttachment.loadOp;
colorAttachment.storeOp = sourceColorAttachment.storeOp;
colorAttachment.clearValue = sourceColorAttachment.clearValue;
if ( layerDescriptor.depthStencilAttachment ) {
const depthAttachment = layerDescriptor.depthStencilAttachment;
if ( renderContext.depth ) {
if ( renderContext.clearDepth || discardDepth ) {
depthAttachment.depthClearValue = renderContext.clearDepthValue;
depthAttachment.depthLoadOp = GPULoadOp.Clear;
} else {
depthAttachment.depthLoadOp = GPULoadOp.Load;
}
}
if ( renderContext.stencil ) {
if ( renderContext.clearStencil || discardStencil ) {
depthAttachment.stencilClearValue = renderContext.clearStencilValue;
depthAttachment.stencilLoadOp = GPULoadOp.Clear;
} else {
depthAttachment.stencilLoadOp = GPULoadOp.Load;
}
}
}
}
}
/**
* This method is executed at the end of a render call and finalizes work
* after draw calls.
*
* @param {RenderContext} renderContext - The render context.
*/
finishRender( renderContext ) {
const renderContextData = this.get( renderContext );
const occlusionQueryCount = renderContext.occlusionQueryCount;
if ( renderContextData.renderBundles.length > 0 ) {
renderContextData.currentPass.executeBundles( renderContextData.renderBundles );
}
const lastOcclusionObject = renderContextData.lastOcclusionObject;
if ( lastOcclusionObject && lastOcclusionObject.occlusionTest === true ) {
renderContextData.currentPass.endOcclusionQuery();
}
// Layered render targets: execute the bundle for each layer.
const encoder = renderContextData.encoder;
if ( this._isRenderCameraDepthArray( renderContext ) === true ) {
const bundles = this._finishArrayCameraBundleEncoders( renderContextData );
const renderStages = renderContextData.arrayCameraRenderStages;
renderStages.push( { bundles } );
// A viewport texture is 2D, so each layer must be captured immediately before that layer samples it.
for ( let i = 0; i < renderContextData.layerDescriptors.length; i ++ ) {
const layerDescriptor = renderContextData.layerDescriptors[ i ];
const colorLoadOps = layerDescriptor.colorAttachments.map( attachment => attachment.loadOp );
const colorStoreOps = layerDescriptor.colorAttachments.map( attachment => attachment.storeOp );
const depthLoadOp = layerDescriptor.depthStencilAttachment?.depthLoadOp;
const depthStoreOp = layerDescriptor.depthStencilAttachment?.depthStoreOp;
const stencilLoadOp = layerDescriptor.depthStencilAttachment?.stencilLoadOp;
const stencilStoreOp = layerDescriptor.depthStencilAttachment?.stencilStoreOp;
for ( let stageIndex = 0; stageIndex < renderStages.length; stageIndex ++ ) {
const renderStage = renderStages[ stageIndex ];
const bundle = renderStage.bundles[ i ];
const isLastStage = stageIndex === renderStages.length - 1;
for ( let j = 0; j < layerDescriptor.colorAttachments.length; j ++ ) {
const attachment = layerDescriptor.colorAttachments[ j ];
attachment.loadOp = stageIndex === 0 ? colorLoadOps[ j ] : GPULoadOp.Load;
attachment.storeOp = isLastStage ? colorStoreOps[ j ] : GPUStoreOp.Store;
}
if ( renderContext.depth ) {
layerDescriptor.depthStencilAttachment.depthLoadOp = stageIndex === 0 ? depthLoadOp : GPULoadOp.Load;
layerDescriptor.depthStencilAttachment.depthStoreOp = isLastStage ? depthStoreOp : GPUStoreOp.Store;
}
if ( renderContext.stencil ) {
layerDescriptor.depthStencilAttachment.stencilLoadOp = stageIndex === 0 ? stencilLoadOp : GPULoadOp.Load;
layerDescriptor.depthStencilAttachment.stencilStoreOp = isLastStage ? stencilStoreOp : GPUStoreOp.Store;
}
const renderPass = encoder.beginRenderPass( layerDescriptor );
if ( renderContext.viewport ) {
const { x, y, width, height, minDepth, maxDepth } = renderContext.viewportValue;
renderPass.setViewport( x, y, width, height, minDepth, maxDepth );
}
if ( renderContext.scissor ) {
const { x, y, width, height } = renderContext.scissorValue;
renderPass.setScissorRect( x, y, width, height );
}
renderPass.executeBundles( [ bundle ] );
renderPass.end();
if ( renderStage.framebufferCopy !== undefined ) {
const { texture, sourceGPU, destinationGPU, rectangle, generateMipmaps } = renderStage.framebufferCopy;
this._copyFramebufferToTexture( encoder, texture, sourceGPU, destinationGPU, rectangle, i, generateMipmaps );
}
}
for ( let j = 0; j < layerDescriptor.colorAttachments.length; j ++ ) {
layerDescriptor.colorAttachments[ j ].loadOp = colorLoadOps[ j ];
layerDescriptor.colorAttachments[ j ].storeOp = colorStoreOps[ j ];
}
if ( renderContext.depth ) {
layerDescriptor.depthStencilAttachment.depthLoadOp = depthLoadOp;
layerDescriptor.depthStencilAttachment.depthStoreOp = depthStoreOp;
}
if ( renderContext.stencil ) {
layerDescriptor.depthStencilAttachment.stencilLoadOp = stencilLoadOp;
layerDescriptor.depthStencilAttachment.stencilStoreOp = stencilStoreOp;
}
}
} else if ( renderContextData.currentPass ) {
renderContextData.currentPass.end();
}
if ( occlusionQueryCount > 0 ) {
const bufferSize = occlusionQueryCount * 8; // 8 byte entries for query results
//
let queryResolveBuffer = this.occludedResolveCache.get( bufferSize );
if ( queryResolveBuffer === undefined ) {
_bufferDescriptor.size = bufferSize;
_bufferDescriptor.usage = GPUBufferUsage.QUERY_RESOLVE | GPUBufferUsage.COPY_SRC;
queryResolveBuffer = this.device.createBuffer( _bufferDescriptor );
_bufferDescriptor.reset();
this.occludedResolveCache.set( bufferSize, queryResolveBuffer );
}
//
_bufferDescriptor.size = bufferSize;
_bufferDescriptor.usage = GPUBufferUsage.COPY_DST | GPUBufferUsage.MAP_READ;
const readBuffer = this.device.createBuffer( _bufferDescriptor );
_bufferDescriptor.reset();
// two buffers required here - WebGPU doesn't allow usage of QUERY_RESOLVE & MAP_READ to be combined
renderContextData.encoder.resolveQuerySet( renderContextData.occlusionQuerySet, 0, occlusionQueryCount, queryResolveBuffer, 0 );
renderContextData.encoder.copyBufferToBuffer( queryResolveBuffer, 0, readBuffer, 0, bufferSize );
renderContextData.occlusionQueryBuffer = readBuffer;
//
this.resolveOccludedAsync( renderContext );
}
submit( this.device, renderContextData.encoder.finish() );
//
if ( renderContext.textures !== null ) {
const textures = renderContext.textures;
for ( let i = 0; i < textures.length; i ++ ) {
const texture = textures[ i ];
if ( texture.generateMipmaps === true ) {
this.textureUtils.generateMipmaps( texture );
}
}
}
}
/**
* Returns `true` if the given 3D object is fully occluded by other
* 3D objects in the scene.
*
* @param {RenderContext} renderContext - The render context.
* @param {Object3D} object - The 3D object to test.
* @return {boolean} Whether the 3D object is fully occluded or not.
*/
isOccluded( renderContext, object ) {
const renderContextData = this.get( renderContext );
return renderContextData.occluded && renderContextData.occluded.has( object );
}
/**
* This method processes the result of occlusion queries and writes it
* into render context data.
*
* @async
* @param {RenderContext} renderContext - The render context.
* @return {Promise} A Promise that resolves when the occlusion query results have been processed.
*/
async resolveOccludedAsync( renderContext ) {
const renderContextData = this.get( renderContext );
// handle occlusion query results
const { currentOcclusionQueryBuffer, currentOcclusionQueryObjects } = renderContextData;
if ( currentOcclusionQueryBuffer && currentOcclusionQueryObjects ) {
const occluded = new WeakSet();
renderContextData.currentOcclusionQueryObjects = null;
renderContextData.currentOcclusionQueryBuffer = null;
await currentOcclusionQueryBuffer.mapAsync( GPUMapMode.READ );
const buffer = currentOcclusionQueryBuffer.getMappedRange();
const results = new BigUint64Array( buffer );
for ( let i = 0; i < currentOcclusionQueryObjects.length; i ++ ) {
const object = currentOcclusionQueryObjects[ i ];
if ( object !== undefined && results[ i ] === BigInt( 0 ) ) {
occluded.add( object );
}
}
currentOcclusionQueryBuffer.destroy();
renderContextData.occluded = occluded;
}
}
/**
* Updates the viewport with the values from the given render context.
*
* @param {RenderContext} renderContext - The render context.
*/
updateViewport( renderContext ) {
const { currentPass } = this.get( renderContext );
const { x, y, width, height, minDepth, maxDepth } = renderContext.viewportValue;
currentPass.setViewport( x, y, width, height, minDepth, maxDepth );
}
/**
* Updates the scissor with the values from the given render context.
*
* @param {RenderContext} renderContext - The render context.
*/
updateScissor( renderContext ) {
const { currentPass } = this.get( renderContext );
const { x, y, width, height } = renderContext.scissorValue;
currentPass.setScissorRect( x, y, width, height );
}
/**
* Returns the clear color and alpha into a single
* color object.
*
* @return {Color4} The clear color.
*/
getClearColor() {
const clearColor = super.getClearColor();
// only premultiply alpha when alphaMode is "premultiplied"
if ( this.renderer.alpha === true ) {
clearColor.r *= clearColor.a;
clearColor.g *= clearColor.a;
clearColor.b *= clearColor.a;
}
return clearColor;
}
/**
* Performs a clear operation.
*
* @param {boolean} color - Whether the color buffer should be cleared or not.
* @param {boolean} depth - Whether the depth buffer should be cleared or not.
* @param {boolean} stencil - Whether the stencil buffer should be cleared or not.
* @param {?RenderContext} [renderTargetContext=null] - The render context of the current set render target.
*/
clear( color, depth, stencil, renderTargetContext = null ) {
const device = this.device;
const renderer = this.renderer;
let colorAttachments = [];
let depthStencilAttachment;
let supportsDepth;
let supportsStencil;
if ( color ) {
const clearColor = this.getClearColor();
_clearValue.r = clearColor.r;
_clearValue.g = clearColor.g;
_clearValue.b = clearColor.b;
_clearValue.a = clearColor.a;
}
if ( renderTargetContext === null ) {
supportsDepth = renderer.depth;
supportsStencil = renderer.stencil;
const descriptor = this._getDefaultRenderPassDescriptor();
if ( color ) {
colorAttachments = descriptor.colorAttachments;
const colorAttachment = colorAttachments[ 0 ];
colorAttachment.clearValue = _clearValue;
colorAttachment.loadOp = GPULoadOp.Clear;
colorAttachment.storeOp = GPUStoreOp.Store;
}
if ( supportsDepth || supportsStencil ) {
depthStencilAttachment = descriptor.depthStencilAttachment;
}
} else {
supportsDepth = renderTargetContext.depth;
supportsStencil = renderTargetContext.stencil;
const clearConfig = {
loadOp: color ? GPULoadOp.Clear : GPULoadOp.Load,
clearValue: color ? _clearValue : undefined
};
if ( supportsDepth ) {
clearConfig.depthLoadOp = depth ? GPULoadOp.Clear : GPULoadOp.Load;
clearConfig.depthClearValue = depth ? renderer.getClearDepth() : undefined;
clearConfig.depthStoreOp = GPUStoreOp.Store;
}
if ( supportsStencil ) {
clearConfig.stencilLoadOp = stencil ? GPULoadOp.Clear : GPULoadOp.Load;
clearConfig.stencilClearValue = stencil ? renderer.getClearStencil() : undefined;
clearConfig.stencilStoreOp = GPUStoreOp.Store;
}
const descriptor = this._getRenderPassDescriptor( renderTargetContext, clearConfig );
colorAttachments = descriptor.colorAttachments;
depthStencilAttachment = descriptor.depthStencilAttachment;
}
if ( supportsDepth && depthStencilAttachment ) {
if ( depth ) {
depthStencilAttachment.depthLoadOp = GPULoadOp.Clear;
depthStencilAttachment.depthClearValue = renderer.getClearDepth();
depthStencilAttachment.depthStoreOp = GPUStoreOp.Store;
} else {
depthStencilAttachment.depthLoadOp = GPULoadOp.Load;
depthStencilAttachment.depthStoreOp = GPUStoreOp.Store;
}
}
//
if ( supportsStencil && depthStencilAttachment ) {
if ( stencil ) {
depthStencilAttachment.stencilLoadOp = GPULoadOp.Clear;
depthStencilAttachment.stencilClearValue = renderer.getClearStencil();
depthStencilAttachment.stencilStoreOp = GPUStoreOp.Store;
} else {
depthStencilAttachment.stencilLoadOp = GPULoadOp.Load;
depthStencilAttachment.stencilStoreOp = GPUStoreOp.Store;
}
}
//
_commandEncoderDescriptor.label = 'clear';
const encoder = device.createCommandEncoder( _commandEncoderDescriptor );
_commandEncoderDescriptor.reset();
const currentPass = encoder.beginRenderPass( {
colorAttachments,
depthStencilAttachment
} );
currentPass.end();
submit( device, encoder.finish() );
}
// compute
/**
* This method is executed at the beginning of a compute call and
* prepares the state for upcoming compute tasks.
*
* @param {Node|Array<Node>} computeGroup - The compute node(s).
*/
beginCompute( computeGroup ) {
const groupGPU = this.get( computeGroup );
//
const label = 'computeGroup_' + computeGroup.id;
_computePassDescriptor.label = label;
_commandEncoderDescriptor.label = label;
this.initTimestampQuery( TimestampQuery.COMPUTE, this.getTimestampUID( computeGroup ), _computePassDescriptor );
groupGPU.cmdEncoderGPU = this.device.createCommandEncoder( _commandEncoderDescriptor );
groupGPU.passEncoderGPU = groupGPU.cmdEncoderGPU.beginComputePass( _computePassDescriptor );
groupGPU.currentPipeline = null;
_commandEncoderDescriptor.reset();
_computePassDescriptor.reset();
}
/**
* Executes a compute command for the given compute node.
*
* @param {Node|Array<Node>} computeGroup - The group of compute nodes of a compute call. Can be a single compute node.
* @param {Node} computeNode - The compute node.
* @param {Array<BindGroup>} bindings - The bindings.
* @param {ComputePipeline} pipeline - The compute pipeline.
* @param {number|Array<number>|IndirectStorageBufferAttribute} [dispatchSize=null]
* - A single number representing count, or
* - An array [x, y, z] representing dispatch size, or
* - A IndirectStorageBufferAttribute for indirect dispatch size.
*/
compute( computeGroup, computeNode, bindings, pipeline, dispatchSize = null ) {
const computeNodeData = this.get( computeNode );
const groupGPU = this.get( computeGroup );
const { passEncoderGPU } = groupGPU;
// pipeline
const pipelineGPU = this.get( pipeline ).pipeline;
if ( groupGPU.currentPipeline !== pipelineGPU ) {
passEncoderGPU.setPipeline( pipelineGPU );
groupGPU.currentPipeline = pipelineGPU;
}
// bind groups
for ( let i = 0, l = bindings.length; i < l; i ++ ) {
const bindGroup = bindings[ i ];
const bindingsData = this.get( bindGroup );
passEncoderGPU.setBindGroup( i, bindingsData.group );
}
if ( dispatchSize === null ) {
dispatchSize = computeNode.dispatchSize || computeNode.count;
}
// When the dispatchSize is set with a StorageBuffer from the GPU.
if ( dispatchSize && dispatchSize.isIndirectStorageBufferAttribute ) {
const dispatchBuffer = this.get( dispatchSize ).buffer;
passEncoderGPU.dispatchWorkgroupsIndirect( dispatchBuffer, 0 );
return;
}
if ( typeof dispatchSize === 'number' ) {
// If a single number is given, we calculate the dispatch size based on the workgroup size
const count = dispatchSize;
if ( computeNodeData.dispatchSize === undefined || computeNodeData.count !== count ) {
// cache dispatch size to avoid recalculating it every time
computeNodeData.dispatchSize = [ 0, 1, 1 ];
computeNodeData.count = count;
const workgroupSize = computeNode.workgroupSize;
let size = workgroupSize[ 0 ];
for ( let i = 1; i < workgroupSize.length; i ++ )
size *= workgroupSize[ i ];
const dispatchCount = Math.ceil( count / size );
//
const maxComputeWorkgroupsPerDimension = this.device.limits.maxComputeWorkgroupsPerDimension;
dispatchSize = [ dispatchCount, 1, 1 ];
if ( dispatchCount > maxComputeWorkgroupsPerDimension ) {
dispatchSize[ 0 ] = Math.min( dispatchCount, maxComputeWorkgroupsPerDimension );
dispatchSize[ 1 ] = Math.ceil( dispatchCount / maxComputeWorkgroupsPerDimension );
}
computeNodeData.dispatchSize = dispatchSize;
}
dispatchSize = computeNodeData.dispatchSize;
}
//
passEncoderGPU.dispatchWorkgroups(
dispatchSize[ 0 ],
dispatchSize[ 1 ] || 1,
dispatchSize[ 2 ] || 1
);
}
/**
* This method is executed at the end of a compute call and
* finalizes work after compute tasks.
*
* @param {Node|Array<Node>} computeGroup - The compute node(s).
*/
finishCompute( computeGroup ) {
const groupData = this.get( computeGroup );
groupData.passEncoderGPU.end();
submit( this.device, groupData.cmdEncoderGPU.finish() );
}
/**
* Internal draw function that performs the draw with the given pass encoder.
*
* @private
* @param {RenderObject} renderObject - The render object.
* @param {Info} info - Holds a series of statistical information about the GPU memory and the rendering process.
* @param {Object} renderContextData - The render context data object, holding current pass state and occlusion query tracking.
* @param {GPURenderPipeline} pipelineGPU - The GPU render pipeline.
* @param {Array<BindGroup>} bindings - The bind groups.
* @param {Array<BufferAttribute>} vertexBuffers - The vertex buffers.
* @param {{vertexCount: number, firstVertex: number, instanceCount: number, firstInstance: number}} drawParams - The draw parameters.
* @param {GPURenderPassEncoder|GPURenderBundleEncoder} passEncoderGPU - The GPU pass encoder used for recording draw commands.
* @param {Object} currentSets - Tracking object for currently set pipeline, attributes, bind groups, and index state.
* @param {number} [cameraIndexSlot=-1] - Index of the binding slot holding the camera index bind group.
*/
_draw( renderObject, info, renderContextData, pipelineGPU, bindings, vertexBuffers, drawParams, passEncoderGPU, currentSets, cameraIndexSlot = - 1 ) {
const { object, material, context } = renderObject;
const index = renderObject.getIndex();
const hasIndex = ( index !== null );
// pipeline
if ( currentSets.pipeline !== pipelineGPU ) {
passEncoderGPU.setPipeline( pipelineGPU );
currentSets.pipeline = pipelineGPU;
}
// bind groups
const currentBindingGroups = currentSets.bindingGroups;
for ( let i = 0, l = bindings.length; i < l; i ++ ) {
// the camera index slot is bound per sub-camera in draw()
if ( i === cameraIndexSlot ) continue;
// compare native bind groups
const bindGroup = bindings[ i ];
const bindGroupGPU = this.get( bindGroup ).group;
if ( currentBindingGroups[ i ] !== bindGroupGPU ) {
passEncoderGPU.setBindGroup( i, bindGroupGPU );
currentBindingGroups[ i ] = bindGroupGPU;
}
}
// attributes
// index
if ( hasIndex === true ) {
if ( currentSets.index !== index ) {
const buffer = this.get( index ).buffer;
const indexFormat = ( index.array instanceof Uint16Array ) ? GPUIndexFormat.Uint16 : GPUIndexFormat.Uint32;
passEncoderGPU.setIndexBuffer( buffer, indexFormat );
currentSets.index = index;
}
}
for ( let i = 0, l = vertexBuffers.length; i < l; i ++ ) {
const vertexBuffer = vertexBuffers[ i ];
if ( currentSets.attributes[ i ] !== vertexBuffer ) {
const buffer = this.get( vertexBuffer ).buffer;
passEncoderGPU.setVertexBuffer( i, buffer );
currentSets.attributes[ i ] = vertexBuffer;
}
}
// stencil
if ( context.stencil === true && material.stencilWrite === true && renderContextData.currentStencilRef !== material.stencilRef ) {
passEncoderGPU.setStencilReference( material.stencilRef );
renderContextData.currentStencilRef = material.stencilRef;
}
// blend constant
if ( material.blending === CustomBlending && passEncoderGPU.setBlendConstant !== undefined ) {
const blendColor = material.blendColor;
const blendAlpha = material.blendAlpha;
if ( blendColor.equals( renderContextData.currentBlendColor ) === false || blendAlpha !== renderContextData.currentBlendAlpha ) {
_blendConstant.r = blendColor.r;
_blendConstant.g = blendColor.g;
_blendConstant.b = blendColor.b;
_blendConstant.a = blendAlpha;
passEncoderGPU.setBlendConstant( _blendConstant );
renderContextData.currentBlendColor.copy( blendColor );
renderContextData.currentBlendAlpha = blendAlpha;
}
}
if ( object.isBatchedMesh === true ) {
const starts = object._multiDrawStarts;
const counts = object._multiDrawCounts;
const drawCount = object._multiDrawCount;
const bytesPerElement = object._multiDrawBytesPerElement;
for ( let i = 0; i < drawCount; i ++ ) {
if ( hasIndex === true ) {
passEncoderGPU.drawIndexed( counts[ i ], 1, starts[ i ] / bytesPerElement, 0, i );
} else {
passEncoderGPU.draw( counts[ i ], 1, starts[ i ], i );
}
info.update( object, counts[ i ], 1 );
}
} else if ( hasIndex === true ) {
const { vertexCount: indexCount, instanceCount, firstVertex: firstIndex } = drawParams;
const indirect = renderObject.getIndirect();
if ( indirect !== null ) {
const buffer = this.get( indirect ).buffer;
const indirectOffset = renderObject.getIndirectOffset();
const indirectOffsets = Array.isArray( indirectOffset ) ? indirectOffset : [ indirectOffset ];
for ( let i = 0; i < indirectOffsets.length; i ++ ) {
passEncoderGPU.drawIndexedIndirect( buffer, indirectOffsets[ i ] );
}
} else {
passEncoderGPU.drawIndexed( indexCount, instanceCount, firstIndex, 0, 0 );
}
info.update( object, indexCount, instanceCount );
} else {
const { vertexCount, instanceCount, firstVertex } = drawParams;
const indirect = renderObject.getIndirect();
if ( indirect !== null ) {
const buffer = this.get( indirect ).buffer;
const indirectOffset = renderObject.getIndirectOffset();
const indirectOffsets = Array.isArray( indirectOffset ) ? indirectOffset : [ indirectOffset ];
for ( let i = 0; i < indirectOffsets.length; i ++ ) {
passEncoderGPU.drawIndirect( buffer, indirectOffsets[ i ] );
}
} else {
passEncoderGPU.draw( vertexCount, instanceCount, firstVertex, 0 );
}
info.update( object, vertexCount, instanceCount );
}
}
// render object
/**
* Executes a draw command for the given render object.
*
* @param {RenderObject} renderObject - The render object to draw.
* @param {Info} info - Holds a series of statistical information about the GPU memory and the rendering process.
*/
draw( renderObject, info ) {
const { object, context, pipeline } = renderObject;
const renderContextData = this.get( context );
const pipelineData = this.get( pipeline );
const pipelineGPU = pipelineData.pipeline;
// Skip if pipeline has error
if ( pipelineData.error === true ) return;
const drawParams = renderObject.getDrawParameters();
if ( drawParams === null ) return;
const bindings = renderObject.getBindings();
// vertex buffers
const vertexBuffers = renderObject.getVertexBuffers();
if ( renderObject.camera.isArrayCamera && renderObject.camera.cameras.length > 0 ) {
const cameraData = this.get( renderObject.camera );
const cameras = renderObject.camera.cameras;
const cameraIndex = renderObject.getBindingGroup( 'cameraIndex' );
if ( cameraData.indexesGPU === undefined || cameraData.indexesGPU.length !== cameras.length ) {
const bindingsData = this.get( cameraIndex );
const indexesGPU = [];
const data = new Uint32Array( [ 0, 0, 0, 0 ] );
for ( let i = 0, len = cameras.length; i < len; i ++ ) {
data[ 0 ] = i;
const { layoutGPU } = bindingsData.layout;
const bindGroupIndex = this.bindingUtils.createBindGroupIndex( data, layoutGPU );
indexesGPU.push( bindGroupIndex );
}
cameraData.indexesGPU = indexesGPU; // TODO: Create a global library for this
}
const renderTarget = context.renderTarget;
const pixelRatio = ( renderTarget === null || renderTarget.isPostProcessingRenderTarget === true ) ? this.renderer.getPixelRatio() : 1;
const indexPos = cameraIndex ? bindings.indexOf( cameraIndex ) : - 1;
for ( let i = 0, len = cameras.length; i < len; i ++ ) {
const subCamera = cameras[ i ];
if ( object.layers.test( subCamera.layers ) ) {
const vp = subCamera.viewport;
let pass = renderContextData.currentPass;
let sets = renderContextData.currentSets;
const isBundleEncoder = renderContextData.bundleEncoders !== undefined;
if ( isBundleEncoder ) {
const bundleEncoder = renderContextData.bundleEncoders[ i ];
const bundleSets = renderContextData.bundleSets[ i ];
pass = bundleEncoder;
sets = bundleSets;
}
// GPURenderBundleEncoder does not support setViewport, only GPURenderPassEncoder does
if ( vp && ! isBundleEncoder ) {
pass.setViewport(
Math.floor( vp.x * pixelRatio ),
Math.floor( vp.y * pixelRatio ),
Math.floor( vp.width * pixelRatio ),
Math.floor( vp.height * pixelRatio ),
context.viewportValue.minDepth,
context.viewportValue.maxDepth
);
}
// Set camera index binding for this layer
if ( indexPos !== - 1 && cameraData.indexesGPU ) {
const cameraIndexGPU = cameraData.indexesGPU[ i ];
if ( sets.bindingGroups[ indexPos ] !== cameraIndexGPU ) {
pass.setBindGroup( indexPos, cameraIndexGPU );
sets.bindingGroups[ indexPos ] = cameraIndexGPU;
}
}
this._draw( renderObject, info, renderContextData, pipelineGPU, bindings, vertexBuffers, drawParams, pass, sets, indexPos );
}
}
} else {
// Regular single camera rendering
if ( renderContextData.currentPass ) {
// Handle occlusion queries
if ( renderContextData.currentPass instanceof GPURenderBundleEncoder ) {
if ( object.occlusionTest === true ) {
warnOnce( 'WebGPUBackend: Occlusion queries can not be recorded into render bundles.' );
}
} else if ( renderContextData.occlusionQuerySet !== undefined ) {
const lastObject = renderContextData.lastOcclusionObject;
if ( lastObject !== object ) {
if ( lastObject !== null && lastObject.occlusionTest === true ) {
renderContextData.currentPass.endOcclusionQuery();
renderContextData.occlusionQueryIndex ++;
}
if ( object.occlusionTest === true ) {
renderContextData.currentPass.beginOcclusionQuery( renderContextData.occlusionQueryIndex );
renderContextData.occlusionQueryObjects[ renderContextData.occlusionQueryIndex ] = object;
}
renderContextData.lastOcclusionObject = object;
}
}
this._draw( renderObject, info, renderContextData, pipelineGPU, bindings, vertexBuffers, drawParams, renderContextData.currentPass, renderContextData.currentSets );
}
}
}
// cache key
/**
* Returns `true` if the render pipeline requires an update.
*
* @param {RenderObject} renderObject - The render object.
* @return {boolean} Whether the render pipeline requires an update or not.
*/
needsRenderUpdate( renderObject ) {
const data = this.get( renderObject );
const { object, material } = renderObject;
const utils = this.utils;
const sampleCount = utils.getSampleCountRenderContext( renderObject.context );
const colorSpace = utils.getCurrentColorSpace( renderObject.context );
const colorFormat = utils.getCurrentColorFormat( renderObject.context );
const depthStencilFormat = utils.getCurrentDepthStencilFormat( renderObject.context );
const primitiveTopology = utils.getPrimitiveTopology( object, material );
const frontFaceCW = ( object.isMesh && object.matrixWorld.determinantAffine() < 0 );
let needsUpdate = false;
if ( data.material !== material || data.materialVersion !== material.version ||
data.transparent !== material.transparent || data.blending !== material.blending || data.premultipliedAlpha !== material.premultipliedAlpha ||
data.blendSrc !== material.blendSrc || data.blendDst !== material.blendDst || data.blendEquation !== material.blendEquation ||
data.blendSrcAlpha !== material.blendSrcAlpha || data.blendDstAlpha !== material.blendDstAlpha || data.blendEquationAlpha !== material.blendEquationAlpha ||
data.colorWrite !== material.colorWrite || data.depthWrite !== material.depthWrite || data.depthTest !== material.depthTest || data.depthFunc !== material.depthFunc ||
data.stencilWrite !== material.stencilWrite || data.stencilFunc !== material.stencilFunc ||
data.stencilFail !== material.stencilFail || data.stencilZFail !== material.stencilZFail || data.stencilZPass !== material.stencilZPass ||
data.stencilFuncMask !== material.stencilFuncMask || data.stencilWriteMask !== material.stencilWriteMask ||
data.polygonOffset !== material.polygonOffset || data.polygonOffsetFactor !== material.polygonOffsetFactor || data.polygonOffsetUnits !== material.polygonOffsetUnits ||
data.side !== material.side || data.alphaToCoverage !== material.alphaToCoverage ||
data.sampleCount !== sampleCount || data.colorSpace !== colorSpace ||
data.colorFormat !== colorFormat || data.depthStencilFormat !== depthStencilFormat ||
data.primitiveTopology !== primitiveTopology ||
data.frontFaceCW !== frontFaceCW ||
data.clippingContextCacheKey !== renderObject.clippingContextCacheKey
) {
data.material = material; data.materialVersion = material.version;
data.transparent = material.transparent; data.blending = material.blending; data.premultipliedAlpha = material.premultipliedAlpha;
data.blendSrc = material.blendSrc; data.blendDst = material.blendDst; data.blendEquation = material.blendEquation;
data.blendSrcAlpha = material.blendSrcAlpha; data.blendDstAlpha = material.blendDstAlpha; data.blendEquationAlpha = material.blendEquationAlpha;
data.colorWrite = material.colorWrite;
data.depthWrite = material.depthWrite; data.depthTest = material.depthTest; data.depthFunc = material.depthFunc;
data.stencilWrite = material.stencilWrite; data.stencilFunc = material.stencilFunc;
data.stencilFail = material.stencilFail; data.stencilZFail = material.stencilZFail; data.stencilZPass = material.stencilZPass;
data.stencilFuncMask = material.stencilFuncMask; data.stencilWriteMask = material.stencilWriteMask;
data.polygonOffset = material.polygonOffset; data.polygonOffsetFactor = material.polygonOffsetFactor; data.polygonOffsetUnits = material.polygonOffsetUnits;
data.side = material.side; data.alphaToCoverage = material.alphaToCoverage;
data.sampleCount = sampleCount;
data.colorSpace = colorSpace;
data.colorFormat = colorFormat;
data.depthStencilFormat = depthStencilFormat;
data.primitiveTopology = primitiveTopology;
data.frontFaceCW = frontFaceCW;
data.clippingContextCacheKey = renderObject.clippingContextCacheKey;
needsUpdate = true;
}
return needsUpdate;
}
/**
* Returns a cache key that is used to identify render pipelines.
*
* @param {RenderObject} renderObject - The render object.
* @return {string} The cache key.
*/
getRenderCacheKey( renderObject ) {
const { object, material } = renderObject;
const utils = this.utils;
const renderContext = renderObject.context;
// meshes with negative scale have a different frontFace render pipeline
// descriptor value so the following must be honored in the cache key
const frontFaceCW = ( object.isMesh && object.matrixWorld.determinantAffine() < 0 );
return [
material.transparent, material.blending, material.premultipliedAlpha,
material.blendSrc, material.blendDst, material.blendEquation,
material.blendSrcAlpha, material.blendDstAlpha, material.blendEquationAlpha,
material.colorWrite,
material.depthWrite, material.depthTest, material.depthFunc,
material.stencilWrite, material.stencilFunc,
material.stencilFail, material.stencilZFail, material.stencilZPass,
material.stencilFuncMask, material.stencilWriteMask,
material.polygonOffset, material.polygonOffsetFactor, material.polygonOffsetUnits,
material.side, material.alphaToCoverage,
frontFaceCW,
utils.getSampleCountRenderContext( renderContext ),
utils.getCurrentColorSpace( renderContext ), utils.getCurrentColorFormat( renderContext ), utils.getCurrentDepthStencilFormat( renderContext ),
utils.getPrimitiveTopology( object, material ),
renderObject.getGeometryCacheKey(),
renderObject.clippingContextCacheKey
].join();
}
// textures
/**
* Updates a GPU sampler for the given texture.
*
* @param {Sampler} binding - The sampler binding to update.
* @return {string} The current sampler key.
*/
updateSampler( binding ) {
return this.textureUtils.updateSampler( binding );
}
/**
* Frees the GPU sampler for the given sampler binding.
*
* @param {Sampler} binding - The sampler binding to free.
*/
destroySampler( binding ) {
this.textureUtils.destroySampler( binding );
}
/**
* Creates a default texture for the given texture that can be used
* as a placeholder until the actual texture is ready for usage.
*
* @param {Texture} texture - The texture to create a default texture for.
* @return {boolean} Whether the sampler has been updated or not.
*/
createDefaultTexture( texture ) {
return this.textureUtils.createDefaultTexture( texture );
}
/**
* Defines a texture on the GPU for the given texture object.
*
* @param {Texture} texture - The texture.
* @param {Object} [options={}] - Optional configuration parameter.
*/
createTexture( texture, options ) {
this.textureUtils.createTexture( texture, options );
}
/**
* Uploads the updated texture data to the GPU.
*
* @param {Texture} texture - The texture.
* @param {Object} [options={}] - Optional configuration parameter.
*/
updateTexture( texture, options ) {
this.textureUtils.updateTexture( texture, options );
}
/**
* Generates mipmaps for the given texture.
*
* @param {Texture} texture - The texture.
*/
generateMipmaps( texture ) {
this.textureUtils.generateMipmaps( texture );
}
/**
* Destroys the GPU data for the given texture object.
*
* @param {Texture} texture - The texture.
* @param {boolean} [isDefaultTexture=false] - Whether the texture uses a default GPU texture or not.
*/
destroyTexture( texture, isDefaultTexture = false ) {
this.textureUtils.destroyTexture( texture, isDefaultTexture );
}
/**
* Returns texture data as a typed array.
*
* @async
* @param {Texture} texture - The texture to copy.
* @param {number} x - The x coordinate of the copy origin.
* @param {number} y - The y coordinate of the copy origin.
* @param {number} width - The width of the copy.
* @param {number} height - The height of the copy.
* @param {number} faceIndex - The cube face, depth slice or array layer index.
* @return {Promise<TypedArray>} A Promise that resolves with a typed array when the copy operation has finished.
*/
async copyTextureToBuffer( texture, x, y, width, height, faceIndex ) {
return this.textureUtils.copyTextureToBuffer( texture, x, y, width, height, faceIndex );
}
/**
* Inits a time stamp query for the given render context.
*
* @param {string} type - The type of the timestamp query (e.g. 'render', 'compute').
* @param {number} uid - Unique id for the context (e.g. render context id).
* @param {Object} descriptor - The query descriptor.
*/
initTimestampQuery( type, uid, descriptor ) {
if ( ! this.trackTimestamp ) return;
if ( ! this.timestampQueryPool[ type ] ) {
// TODO: Variable maxQueries?
this.timestampQueryPool[ type ] = new WebGPUTimestampQueryPool( this.device, type, 2048 );
}
const timestampQueryPool = this.timestampQueryPool[ type ];
const baseOffset = timestampQueryPool.allocateQueriesForContext( uid );
_renderPassTimestampWrites.querySet = timestampQueryPool.querySet;
_renderPassTimestampWrites.beginningOfPassWriteIndex = baseOffset;
_renderPassTimestampWrites.endOfPassWriteIndex = baseOffset + 1;
descriptor.timestampWrites = _renderPassTimestampWrites;
}
// node builder
/**
* Returns a node builder for the given render object.
*
* @param {RenderObject} object - The render object.
* @param {Renderer} renderer - The renderer.
* @return {WGSLNodeBuilder} The node builder.
*/
createNodeBuilder( object, renderer ) {
return new WGSLNodeBuilder( object, renderer );
}
// program
/**
* Creates a shader program from the given programmable stage.
*
* @param {ProgrammableStage} program - The programmable stage.
*/
createProgram( program ) {
const programGPU = this.get( program );
_shaderModuleDescriptor.label = program.stage + ( program.name !== '' ? `_${ program.name }` : '' );
_shaderModuleDescriptor.code = program.code;
programGPU.module = {
module: this.device.createShaderModule( _shaderModuleDescriptor ),
entryPoint: 'main'
};
_shaderModuleDescriptor.reset();
}
/**
* Destroys the shader program of the given programmable stage.
*
* @param {ProgrammableStage} program - The programmable stage.
*/
destroyProgram( program ) {
this.delete( program );
}
// pipelines
/**
* Creates a render pipeline for the given render object.
*
* @param {RenderObject} renderObject - The render object.
* @param {Array<Promise>} promises - An array of compilation promises which are used in `compileAsync()`.
*/
createRenderPipeline( renderObject, promises ) {
this.pipelineUtils.createRenderPipeline( renderObject, promises );
}
/**
* Creates a compute pipeline for the given compute node.
*
* @param {ComputePipeline} computePipeline - The compute pipeline.
* @param {Array<BindGroup>} bindings - The bindings.
* @param {?Array<Promise>} [promises=null] - Optional compilation promises.
*/
createComputePipeline( computePipeline, bindings, promises = null ) {
this.pipelineUtils.createComputePipeline( computePipeline, bindings, promises );
}
/**
* Prepares the state for encoding render bundles.
*
* @param {RenderContext} renderContext - The render context.
*/
beginBundle( renderContext ) {
const renderContextData = this.get( renderContext );
renderContextData._currentPass = renderContextData.currentPass;
renderContextData._currentSets = renderContextData.currentSets;
renderContextData.currentSets = { attributes: {}, bindingGroups: [], pipeline: null, index: null };
renderContextData.currentPass = this.pipelineUtils.createBundleEncoder( renderContext );
}
/**
* After processing render bundles this method finalizes related work.
*
* @param {RenderContext} renderContext - The render context.
* @param {RenderBundle} bundle - The render bundle.
*/
finishBundle( renderContext, bundle ) {
const renderContextData = this.get( renderContext );
const bundleEncoder = renderContextData.currentPass;
const bundleGPU = bundleEncoder.finish();
this.get( bundle ).bundleGPU = bundleGPU;
// restore render pass state
renderContextData.currentSets = renderContextData._currentSets;
renderContextData.currentPass = renderContextData._currentPass;
renderContextData._currentPass = null;
renderContextData._currentSets = null;
}
/**
* Adds a render bundle to the render context data.
*
* @param {RenderContext} renderContext - The render context.
* @param {RenderBundle} bundle - The render bundle to add.
*/
addBundle( renderContext, bundle ) {
const renderContextData = this.get( renderContext );
renderContextData.renderBundles.push( this.get( bundle ).bundleGPU );
}
// bindings
/**
* Creates a uniform buffer.
*
* @param {Buffer} uniformBuffer - The uniform buffer.
*/
createUniformBuffer( uniformBuffer ) {
const uniformBufferData = this.get( uniformBuffer );
if ( uniformBufferData.buffer === undefined ) {
const byteLength = uniformBuffer.byteLength;
const usage = GPUBufferUsage.UNIFORM | GPUBufferUsage.COPY_DST;
const visibilities = [];
if ( uniformBuffer.visibility & GPUShaderStage.VERTEX ) {
visibilities.push( 'vertex' );
}
if ( uniformBuffer.visibility & GPUShaderStage.FRAGMENT ) {
visibilities.push( 'fragment' );
}
if ( uniformBuffer.visibility & GPUShaderStage.COMPUTE ) {
visibilities.push( 'compute' );
}
const bufferVisibility = `(${visibilities.join( ',' )})`;
_bufferDescriptor.label = `bindingBuffer${uniformBuffer.id}_${uniformBuffer.name}_${bufferVisibility}`;
_bufferDescriptor.size = byteLength;
_bufferDescriptor.usage = usage;
const bufferGPU = this.device.createBuffer( _bufferDescriptor );
_bufferDescriptor.reset();
uniformBufferData.buffer = bufferGPU;
}
}
/**
* Destroys the GPU data for the given uniform buffer.
*
* @param {Buffer} uniformBuffer - The uniform buffer.
*/
destroyUniformBuffer( uniformBuffer ) {
const uniformBufferData = this.get( uniformBuffer );
uniformBufferData.buffer.destroy();
this.delete( uniformBuffer );
}
/**
* Creates bindings from the given bind group definition.
*
* @param {BindGroup} bindGroup - The bind group.
* @param {Array<BindGroup>} bindings - Array of bind groups.
* @param {string} cacheKey - The cache key.
* @param {number} version - The version.
*/
createBindings( bindGroup, bindings, cacheKey, version ) {
this.bindingUtils.createBindings( bindGroup, bindings, cacheKey, version );
}
/**
* Updates the given bind group definition.
*
* @param {BindGroup} bindGroup - The bind group.
* @param {Array<BindGroup>} bindings - Array of bind groups.
* @param {string} cacheKey - The cache key.
* @param {number} version - The version.
*/
updateBindings( bindGroup, bindings, cacheKey, version ) {
this.bindingUtils.createBindings( bindGroup, bindings, cacheKey, version );
}
/**
* Updates a buffer binding.
*
* @param {Buffer} binding - The buffer binding to update.
*/
updateBinding( binding ) {
this.bindingUtils.updateBinding( binding );
}
/**
* Delete data associated with the current bind group.
*
* @param {BindGroup} bindGroup - The bind group.
*/
deleteBindGroupData( bindGroup ) {
this.bindingUtils.deleteBindGroupData( bindGroup );
}
// attributes
/**
* Creates the buffer of an indexed shader attribute.
*
* @param {BufferAttribute} attribute - The indexed buffer attribute.
*/
createIndexAttribute( attribute ) {
let usage = GPUBufferUsage.INDEX | GPUBufferUsage.COPY_SRC | GPUBufferUsage.COPY_DST;
if ( attribute.isStorageBufferAttribute || attribute.isStorageInstancedBufferAttribute ) {
usage |= GPUBufferUsage.STORAGE;
}
this.attributeUtils.createAttribute( attribute, usage );
}
/**
* Creates the GPU buffer of a shader attribute.
*
* @param {BufferAttribute} attribute - The buffer attribute.
*/
createAttribute( attribute ) {
this.attributeUtils.createAttribute( attribute, GPUBufferUsage.VERTEX | GPUBufferUsage.COPY_SRC | GPUBufferUsage.COPY_DST );
}
/**
* Creates the GPU buffer of a storage attribute.
*
* @param {BufferAttribute} attribute - The buffer attribute.
*/
createStorageAttribute( attribute ) {
this.attributeUtils.createAttribute( attribute, GPUBufferUsage.STORAGE | GPUBufferUsage.VERTEX | GPUBufferUsage.COPY_SRC | GPUBufferUsage.COPY_DST );
}
/**
* Creates the GPU buffer of an indirect storage attribute.
*
* @param {BufferAttribute} attribute - The buffer attribute.
*/
createIndirectStorageAttribute( attribute ) {
this.attributeUtils.createAttribute( attribute, GPUBufferUsage.STORAGE | GPUBufferUsage.INDIRECT | GPUBufferUsage.COPY_SRC | GPUBufferUsage.COPY_DST );
}
/**
* Updates the GPU buffer of a shader attribute.
*
* @param {BufferAttribute} attribute - The buffer attribute to update.
*/
updateAttribute( attribute ) {
this.attributeUtils.updateAttribute( attribute );
}
/**
* Destroys the GPU buffer of a shader attribute.
*
* @param {BufferAttribute} attribute - The buffer attribute to destroy.
*/
destroyAttribute( attribute ) {
this.attributeUtils.destroyAttribute( attribute );
}
// canvas
/**
* Triggers an update of the default render pass descriptor.
*/
updateSize() {
this.delete( this.renderer.getCanvasTarget() );
}
// utils public
/**
* Checks if the given feature is supported by the backend.
*
* @param {string} name - The feature's name.
* @return {boolean} Whether the feature is supported or not.
*/
hasFeature( name ) {
if ( GPUFeatureMap[ name ] !== undefined ) name = GPUFeatureMap[ name ];
return this.device.features.has( name );
}
/**
* Copies data of the given source texture to the given destination texture.
*
* @param {Texture} srcTexture - The source texture.
* @param {Texture} dstTexture - The destination texture.
* @param {?(Box3|Box2)} [srcRegion=null] - The region of the source texture to copy.
* @param {?(Vector2|Vector3)} [dstPosition=null] - The destination position of the copy.
* @param {number} [srcLevel=0] - The mipmap level to copy.
* @param {number} [dstLevel=0] - The destination mip level to copy to.
*/
copyTextureToTexture( srcTexture, dstTexture, srcRegion = null, dstPosition = null, srcLevel = 0, dstLevel = 0 ) {
let dstX = 0;
let dstY = 0;
let dstZ = 0;
let srcX = 0;
let srcY = 0;
let srcZ = 0;
let srcWidth = srcTexture.image.width;
let srcHeight = srcTexture.image.height;
let srcDepth = 1;
if ( srcRegion !== null ) {
if ( srcRegion.isBox3 === true ) {
srcX = srcRegion.min.x;
srcY = srcRegion.min.y;
srcZ = srcRegion.min.z;
srcWidth = srcRegion.max.x - srcRegion.min.x;
srcHeight = srcRegion.max.y - srcRegion.min.y;
srcDepth = srcRegion.max.z - srcRegion.min.z;
} else {
// Assume it's a Box2
srcX = srcRegion.min.x;
srcY = srcRegion.min.y;
srcWidth = srcRegion.max.x - srcRegion.min.x;
srcHeight = srcRegion.max.y - srcRegion.min.y;
srcDepth = 1;
}
}
if ( dstPosition !== null ) {
dstX = dstPosition.x;
dstY = dstPosition.y;
dstZ = dstPosition.z || 0;
}
_commandEncoderDescriptor.label = 'copyTextureToTexture_' + srcTexture.id + '_' + dstTexture.id;
const encoder = this.device.createCommandEncoder( _commandEncoderDescriptor );
_commandEncoderDescriptor.reset();
const sourceGPU = this.get( srcTexture ).texture;
const destinationGPU = this.get( dstTexture ).texture;
_texelCopyTextureInfoSrc.texture = sourceGPU;
_texelCopyTextureInfoSrc.mipLevel = srcLevel;
_texelCopyTextureInfoSrc.origin.x = srcX;
_texelCopyTextureInfoSrc.origin.y = srcY;
_texelCopyTextureInfoSrc.origin.z = srcZ;
_texelCopyTextureInfoDst.texture = destinationGPU;
_texelCopyTextureInfoDst.mipLevel = dstLevel;
_texelCopyTextureInfoDst.origin.x = dstX;
_texelCopyTextureInfoDst.origin.y = dstY;
_texelCopyTextureInfoDst.origin.z = dstZ;
_extent3D.width = srcWidth;
_extent3D.height = srcHeight;
_extent3D.depthOrArrayLayers = srcDepth;
encoder.copyTextureToTexture(
_texelCopyTextureInfoSrc,
_texelCopyTextureInfoDst,
_extent3D
);
_texelCopyTextureInfoSrc.reset();
_texelCopyTextureInfoDst.reset();
_extent3D.reset();
submit( this.device, encoder.finish() );
if ( dstLevel === 0 && dstTexture.generateMipmaps ) {
this.textureUtils.generateMipmaps( dstTexture );
}
}
/**
* Copies the current bound framebuffer to the given texture.
*
* @param {Texture} texture - The destination texture.
* @param {RenderContext} renderContext - The render context.
* @param {Vector4} rectangle - A four dimensional vector defining the origin and dimension of the copy.
*/
copyFramebufferToTexture( texture, renderContext, rectangle ) {
const renderContextData = this.get( renderContext );
let sourceGPU = null;
if ( renderContext.renderTarget ) {
if ( texture.isDepthTexture ) {
sourceGPU = this.get( renderContext.depthTexture ).texture;
} else {
sourceGPU = this.get( renderContext.textures[ 0 ] ).texture;
}
} else {
if ( texture.isDepthTexture ) {
sourceGPU = this.textureUtils.getDepthBuffer( renderContext.depth, renderContext.stencil );
} else {
sourceGPU = this.context.getCurrentTexture();
}
}
const destinationGPU = this.get( texture ).texture;
if ( sourceGPU.format !== destinationGPU.format ) {
error( 'WebGPUBackend: copyFramebufferToTexture: Source and destination formats do not match.', sourceGPU.format, destinationGPU.format );
return;
}
if ( this._isRenderCameraDepthArray( renderContext ) === true ) {
// Layered draws are only executed in finishRender(), so preserve this copy as a render-stage boundary.
const bundles = this._finishArrayCameraBundleEncoders( renderContextData );
renderContextData.arrayCameraRenderStages.push( {
bundles,
framebufferCopy: {
texture,
sourceGPU,
destinationGPU,
rectangle: { x: rectangle.x, y: rectangle.y, z: rectangle.z, w: rectangle.w },
// ViewportTextureNode restores this flag before the deferred copy executes.
generateMipmaps: texture.generateMipmaps
}
} );
this._createArrayCameraBundleEncoders( renderContext, renderContextData );
return;
}
let encoder;
if ( renderContextData.currentPass ) {
renderContextData.currentPass.end();
encoder = renderContextData.encoder;
} else {
_commandEncoderDescriptor.label = 'copyFramebufferToTexture_' + texture.id;
encoder = this.device.createCommandEncoder( _commandEncoderDescriptor );
_commandEncoderDescriptor.reset();
}
// mipmaps must be genereated with the same encoder otherwise the copied texture data
// might be out-of-sync, see #31768
this._copyFramebufferToTexture( encoder, texture, sourceGPU, destinationGPU, rectangle );
if ( renderContextData.currentPass ) {
const { descriptor } = renderContextData;
for ( let i = 0; i < descriptor.colorAttachments.length; i ++ ) {
descriptor.colorAttachments[ i ].loadOp = GPULoadOp.Load;
}
if ( renderContext.depth ) descriptor.depthStencilAttachment.depthLoadOp = GPULoadOp.Load;
if ( renderContext.stencil ) descriptor.depthStencilAttachment.stencilLoadOp = GPULoadOp.Load;
renderContextData.currentPass = encoder.beginRenderPass( descriptor );
this._resetRenderContextData( renderContextData );
if ( renderContext.viewport ) {
this.updateViewport( renderContext );
}
if ( renderContext.scissor ) {
this.updateScissor( renderContext );
}
} else {
submit( this.device, encoder.finish() );
}
}
/**
* Encodes a framebuffer copy from a specific texture-array layer.
*
* @param {GPUCommandEncoder} encoder - The command encoder.
* @param {Texture} texture - The destination texture.
* @param {GPUTexture} sourceGPU - The source GPU texture.
* @param {GPUTexture} destinationGPU - The destination GPU texture.
* @param {Object} rectangle - The source rectangle.
* @param {number} [sourceLayer=0] - The source array layer.
* @param {boolean} [generateMipmaps=texture.generateMipmaps] - Whether mipmaps should be generated.
* @private
*/
_copyFramebufferToTexture( encoder, texture, sourceGPU, destinationGPU, rectangle, sourceLayer = 0, generateMipmaps = texture.generateMipmaps ) {
_texelCopyTextureInfoSrc.texture = sourceGPU;
_texelCopyTextureInfoSrc.origin.x = rectangle.x;
_texelCopyTextureInfoSrc.origin.y = rectangle.y;
_texelCopyTextureInfoSrc.origin.z = sourceLayer;
_texelCopyTextureInfoDst.texture = destinationGPU;
_extent3D.width = rectangle.z;
_extent3D.height = rectangle.w;
encoder.copyTextureToTexture(
_texelCopyTextureInfoSrc,
_texelCopyTextureInfoDst,
_extent3D
);
_texelCopyTextureInfoSrc.reset();
_texelCopyTextureInfoDst.reset();
_extent3D.reset();
if ( generateMipmaps ) {
this.textureUtils.generateMipmaps( texture, encoder );
}
}
/**
* Checks if the given compatibility is supported by the backend.
*
* @param {string} name - The compatibility name.
* @return {boolean} Whether the compatibility is supported or not.
*/
hasCompatibility( name ) {
if ( this._compatibility[ name ] !== undefined ) {
return this._compatibility[ name ];
}
return super.hasCompatibility( name );
}
async dispose() {
await super.dispose();
this.bindingUtils.dispose();
this.textureUtils.dispose();
if ( this.occludedResolveCache ) {
for ( const buffer of this.occludedResolveCache.values() ) {
buffer.destroy();
}
this.occludedResolveCache.clear();
}
if ( this.parameters.device === undefined && this.device !== null ) {
this.device.destroy();
}
}
}
export default WebGPUBackend;