// 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} */ 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} [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} 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} 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} 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} 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} 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} computeGroup - The group of compute nodes of a compute call. Can be a single compute node. * @param {Node} computeNode - The compute node. * @param {Array} bindings - The bindings. * @param {ComputePipeline} pipeline - The compute pipeline. * @param {number|Array|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} 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} bindings - The bind groups. * @param {Array} 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} 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} 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} bindings - The bindings. * @param {?Array} [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} 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} 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;