diff --git a/examples/screenshots/webgpu_custom_fog_background.jpg b/examples/screenshots/webgpu_custom_fog_background.jpg index 78ef6f3b3bbca2..82c410bd71fa75 100644 Binary files a/examples/screenshots/webgpu_custom_fog_background.jpg and b/examples/screenshots/webgpu_custom_fog_background.jpg differ diff --git a/examples/screenshots/webgpu_loader_gltf.jpg b/examples/screenshots/webgpu_loader_gltf.jpg index c9536939b9951a..e7fa73f25d536e 100644 Binary files a/examples/screenshots/webgpu_loader_gltf.jpg and b/examples/screenshots/webgpu_loader_gltf.jpg differ diff --git a/examples/screenshots/webgpu_materials_envmaps_bpcem.jpg b/examples/screenshots/webgpu_materials_envmaps_bpcem.jpg index e3b3bc897a96f6..265bdfc8d002d6 100644 Binary files a/examples/screenshots/webgpu_materials_envmaps_bpcem.jpg and b/examples/screenshots/webgpu_materials_envmaps_bpcem.jpg differ diff --git a/src/renderers/webgpu/WebGPUBackend.js b/src/renderers/webgpu/WebGPUBackend.js index cfbb7256027796..37badf56831a46 100644 --- a/src/renderers/webgpu/WebGPUBackend.js +++ b/src/renderers/webgpu/WebGPUBackend.js @@ -17,7 +17,24 @@ import { WebGPUCoordinateSystem, TimestampQuery, REVISION, HalfFloatType, Compat import WebGPUTimestampQueryPool from './utils/WebGPUTimestampQueryPool.js'; import { error } from '../../utils.js'; +import GPUCommandEncoderDescriptor from './descriptors/GPUCommandEncoderDescriptor.js'; +import GPUComputePassDescriptor from './descriptors/GPUComputePassDescriptor.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 GPUTextureViewDescriptor from './descriptors/GPUTextureViewDescriptor.js'; +import GPUExtent3D from './descriptors/GPUExtent3D.js'; + const _clearValue = { r: 0, g: 0, b: 0, a: 1 }; +const _commandEncoderDescriptor = new GPUCommandEncoderDescriptor(); +const _computePassDescriptor = new GPUComputePassDescriptor(); +const _renderPassTimestampWrites = new GPURenderPassTimestampWrites(); +const _texelCopyTextureInfoSrc = new GPUTexelCopyTextureInfo(); +const _texelCopyTextureInfoDst = new GPUTexelCopyTextureInfo(); +const _viewDescriptor = new GPUTextureViewDescriptor(); +const _extent3D = new GPUExtent3D(); /** * A backend implementation targeting WebGPU. @@ -388,17 +405,14 @@ class WebGPUBackend extends Backend { if ( descriptor === undefined || canvasData.samples !== samples ) { - descriptor = { - colorAttachments: [ { - view: null - } ] - }; + descriptor = new GPURenderPassDescriptor(); + descriptor.colorAttachments.push( new GPURenderPassColorAttachment() ); if ( renderer.depth === true || renderer.stencil === true ) { - descriptor.depthStencilAttachment = { - view: this.textureUtils.getDepthBuffer( renderer.depth, renderer.stencil ).createView() - }; + const depthStencilAttachment = new GPURenderPassDepthStencilAttachment(); + depthStencilAttachment.view = this.textureUtils.getDepthBuffer( renderer.depth, renderer.stencil ).createView(); + descriptor.depthStencilAttachment = depthStencilAttachment; } @@ -494,22 +508,19 @@ class WebGPUBackend extends Backend { const textureData = this.get( textures[ i ] ); - const viewDescriptor = { - label: `colorAttachment_${ i }`, - baseMipLevel: renderContext.activeMipmapLevel, - mipLevelCount: 1, - baseArrayLayer: renderContext.activeCubeFace, - arrayLayerCount: 1, - dimension: GPUTextureViewDimension.TwoD - }; + _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; - viewDescriptor.depthOrArrayLayers = textures[ i ].image.depth; + _viewDescriptor.baseArrayLayer = 0; + _viewDescriptor.dimension = GPUTextureViewDimension.ThreeD; } else if ( renderTarget.isRenderTarget && textures[ i ].image.depth > 1 ) { @@ -518,13 +529,11 @@ class WebGPUBackend extends Backend { const cameras = renderContext.camera.cameras; for ( let layer = 0; layer < cameras.length; layer ++ ) { - const layerViewDescriptor = { - ...viewDescriptor, - baseArrayLayer: layer, - arrayLayerCount: 1, - dimension: GPUTextureViewDimension.TwoD - }; - const textureView = textureData.texture.createView( layerViewDescriptor ); + _viewDescriptor.baseArrayLayer = layer; + _viewDescriptor.arrayLayerCount = 1; + _viewDescriptor.dimension = GPUTextureViewDimension.TwoD; + + const textureView = textureData.texture.createView( _viewDescriptor ); textureViews.push( { view: textureView, resolveTarget: undefined, @@ -535,8 +544,7 @@ class WebGPUBackend extends Backend { } else { - viewDescriptor.dimension = GPUTextureViewDimension.TwoDArray; - viewDescriptor.depthOrArrayLayers = textures[ i ].image.depth; + _viewDescriptor.dimension = GPUTextureViewDimension.TwoDArray; } @@ -544,7 +552,7 @@ class WebGPUBackend extends Backend { if ( isRenderCameraDepthArray !== true ) { - const textureView = textureData.texture.createView( viewDescriptor ); + const textureView = textureData.texture.createView( _viewDescriptor ); let view, resolveTarget; @@ -568,23 +576,46 @@ class WebGPUBackend extends Backend { } + _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 }; + descriptorBase = { + textureViews, + colorAttachments, + descriptor: new GPURenderPassDescriptor() + }; if ( renderContext.depth ) { const depthTextureData = this.get( renderContext.depthTexture ); - const options = {}; + if ( renderContext.depthTexture.isArrayTexture || renderContext.depthTexture.isCubeTexture ) { - options.dimension = GPUTextureViewDimension.TwoD; - options.arrayLayerCount = 1; - options.baseArrayLayer = renderContext.activeCubeFace; + _viewDescriptor.dimension = GPUTextureViewDimension.TwoD; + _viewDescriptor.arrayLayerCount = 1; + _viewDescriptor.baseArrayLayer = renderContext.activeCubeFace; } - descriptorBase.depthStencilView = depthTextureData.texture.createView( options ); + const depthStencilAttachment = new GPURenderPassDepthStencilAttachment(); + depthStencilAttachment.view = depthTextureData.texture.createView( _viewDescriptor ); + descriptorBase.depthStencilAttachment = depthStencilAttachment; + + _viewDescriptor.reset(); } @@ -598,14 +629,14 @@ class WebGPUBackend extends Backend { } - const descriptor = { - colorAttachments: [] - }; + const descriptor = descriptorBase.descriptor; - // Apply dynamic properties to cached views - for ( let i = 0; i < descriptorBase.textureViews.length; i ++ ) { + descriptor.reset(); - const viewInfo = descriptorBase.textureViews[ i ]; + // 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 ) { @@ -614,22 +645,17 @@ class WebGPUBackend extends Backend { } - descriptor.colorAttachments.push( { - view: viewInfo.view, - depthSlice: viewInfo.depthSlice, - resolveTarget: viewInfo.resolveTarget, - loadOp: colorAttachmentsConfig.loadOp || GPULoadOp.Load, - storeOp: colorAttachmentsConfig.storeOp || GPUStoreOp.Store, - clearValue: clearValue - } ); + attachment.loadOp = colorAttachmentsConfig.loadOp || GPULoadOp.Load; + attachment.storeOp = colorAttachmentsConfig.storeOp || GPUStoreOp.Store; + attachment.clearValue = clearValue; + + descriptor.colorAttachments.push( attachment ); } - if ( descriptorBase.depthStencilView ) { + if ( descriptorBase.depthStencilAttachment ) { - descriptor.depthStencilAttachment = { - view: descriptorBase.depthStencilView - }; + descriptor.depthStencilAttachment = descriptorBase.depthStencilAttachment; } @@ -789,7 +815,9 @@ class WebGPUBackend extends Backend { // - const encoder = device.createCommandEncoder( { label: 'renderContext_' + renderContext.id } ); + _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. @@ -887,13 +915,21 @@ class WebGPUBackend extends Backend { for ( let i = 0; i < cameras.length; i ++ ) { - const layerDescriptor = { - ...descriptor, - colorAttachments: [ { - ...descriptor.colorAttachments[ 0 ], - view: descriptor.colorAttachments[ i ].view - } ] - }; + const sourceAttachment = descriptor.colorAttachments[ 0 ]; + + const layerColorAttachment = new GPURenderPassColorAttachment(); + layerColorAttachment.view = descriptor.colorAttachments[ i ].view; + layerColorAttachment.depthSlice = sourceAttachment.depthSlice; + layerColorAttachment.resolveTarget = sourceAttachment.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 ) { @@ -901,32 +937,45 @@ class WebGPUBackend extends Backend { if ( ! depthTextureData.viewCache[ layerIndex ] ) { - depthTextureData.viewCache[ layerIndex ] = depthTextureData.texture.createView( { - dimension: GPUTextureViewDimension.TwoD, - baseArrayLayer: i, - arrayLayerCount: 1 - } ); + _viewDescriptor.dimension = GPUTextureViewDimension.TwoD; + _viewDescriptor.baseArrayLayer = i; + _viewDescriptor.arrayLayerCount = 1; + + depthTextureData.viewCache[ layerIndex ] = depthTextureData.texture.createView( _viewDescriptor ); + + _viewDescriptor.reset(); } - layerDescriptor.depthStencilAttachment = { - view: depthTextureData.viewCache[ layerIndex ], - depthLoadOp: depthStencilAttachment.depthLoadOp || GPULoadOp.Clear, - depthStoreOp: depthStencilAttachment.depthStoreOp || GPUStoreOp.Store, - depthClearValue: depthStencilAttachment.depthClearValue || 1.0 - }; + 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 ) { - layerDescriptor.depthStencilAttachment.stencilLoadOp = depthStencilAttachment.stencilLoadOp; - layerDescriptor.depthStencilAttachment.stencilStoreOp = depthStencilAttachment.stencilStoreOp; - layerDescriptor.depthStencilAttachment.stencilClearValue = depthStencilAttachment.stencilClearValue; + layerDepthStencilAttachment.stencilLoadOp = depthStencilAttachment.stencilLoadOp; + layerDepthStencilAttachment.stencilStoreOp = depthStencilAttachment.stencilStoreOp; + layerDepthStencilAttachment.stencilClearValue = depthStencilAttachment.stencilClearValue; } + layerDescriptor.depthStencilAttachment = layerDepthStencilAttachment; + } else { - layerDescriptor.depthStencilAttachment = { ...depthStencilAttachment }; + 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; } @@ -1370,7 +1419,10 @@ class WebGPUBackend extends Backend { // - const encoder = device.createCommandEncoder( { label: 'clear' } ); + _commandEncoderDescriptor.label = 'clear'; + const encoder = device.createCommandEncoder( _commandEncoderDescriptor ); + _commandEncoderDescriptor.reset(); + const currentPass = encoder.beginRenderPass( { colorAttachments, depthStencilAttachment @@ -1396,15 +1448,18 @@ class WebGPUBackend extends Backend { // - const descriptor = { - label: 'computeGroup_' + computeGroup.id - }; + const label = 'computeGroup_' + computeGroup.id; + + _computePassDescriptor.label = label; + _commandEncoderDescriptor.label = label; - this.initTimestampQuery( TimestampQuery.COMPUTE, this.getTimestampUID( computeGroup ), descriptor ); + this.initTimestampQuery( TimestampQuery.COMPUTE, this.getTimestampUID( computeGroup ), _computePassDescriptor ); - groupGPU.cmdEncoderGPU = this.device.createCommandEncoder( { label: 'computeGroup_' + computeGroup.id } ); + groupGPU.cmdEncoderGPU = this.device.createCommandEncoder( _commandEncoderDescriptor ); + groupGPU.passEncoderGPU = groupGPU.cmdEncoderGPU.beginComputePass( _computePassDescriptor ); - groupGPU.passEncoderGPU = groupGPU.cmdEncoderGPU.beginComputePass( descriptor ); + _commandEncoderDescriptor.reset(); + _computePassDescriptor.reset(); } @@ -2062,11 +2117,11 @@ class WebGPUBackend extends Backend { const baseOffset = timestampQueryPool.allocateQueriesForContext( uid ); - descriptor.timestampWrites = { - querySet: timestampQueryPool.querySet, - beginningOfPassWriteIndex: baseOffset, - endOfPassWriteIndex: baseOffset + 1, - }; + _renderPassTimestampWrites.querySet = timestampQueryPool.querySet; + _renderPassTimestampWrites.beginningOfPassWriteIndex = baseOffset; + _renderPassTimestampWrites.endOfPassWriteIndex = baseOffset + 1; + + descriptor.timestampWrites = _renderPassTimestampWrites; } @@ -2471,29 +2526,39 @@ class WebGPUBackend extends Backend { } - const encoder = this.device.createCommandEncoder( { label: 'copyTextureToTexture_' + srcTexture.id + '_' + dstTexture.id } ); + _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( - { - texture: sourceGPU, - mipLevel: srcLevel, - origin: { x: srcX, y: srcY, z: srcZ } - }, - { - texture: destinationGPU, - mipLevel: dstLevel, - origin: { x: dstX, y: dstY, z: dstZ } - }, - [ - srcWidth, - srcHeight, - srcDepth - ] + _texelCopyTextureInfoSrc, + _texelCopyTextureInfoDst, + _extent3D ); + _texelCopyTextureInfoSrc.reset(); + _texelCopyTextureInfoDst.reset(); + _extent3D.reset(); + submit( this.device, encoder.finish() ); if ( dstLevel === 0 && dstTexture.generateMipmaps ) { @@ -2563,24 +2628,31 @@ class WebGPUBackend extends Backend { } else { - encoder = this.device.createCommandEncoder( { label: 'copyFramebufferToTexture_' + texture.id } ); + _commandEncoderDescriptor.label = 'copyFramebufferToTexture_' + texture.id; + encoder = this.device.createCommandEncoder( _commandEncoderDescriptor ); + _commandEncoderDescriptor.reset(); } + _texelCopyTextureInfoSrc.texture = sourceGPU; + _texelCopyTextureInfoSrc.origin.x = rectangle.x; + _texelCopyTextureInfoSrc.origin.y = rectangle.y; + + _texelCopyTextureInfoDst.texture = destinationGPU; + + _extent3D.width = rectangle.z; + _extent3D.height = rectangle.w; + encoder.copyTextureToTexture( - { - texture: sourceGPU, - origin: [ rectangle.x, rectangle.y, 0 ], - }, - { - texture: destinationGPU - }, - [ - rectangle.z, - rectangle.w - ] + _texelCopyTextureInfoSrc, + _texelCopyTextureInfoDst, + _extent3D ); + _texelCopyTextureInfoSrc.reset(); + _texelCopyTextureInfoDst.reset(); + _extent3D.reset(); + // mipmaps must be genereated with the same encoder otherwise the copied texture data // might be out-of-sync, see #31768 diff --git a/src/renderers/webgpu/descriptors/GPUCommandEncoderDescriptor.js b/src/renderers/webgpu/descriptors/GPUCommandEncoderDescriptor.js new file mode 100644 index 00000000000000..0fc3a4ab5e321c --- /dev/null +++ b/src/renderers/webgpu/descriptors/GPUCommandEncoderDescriptor.js @@ -0,0 +1,30 @@ +/** + * Reusable descriptor for `GPUDevice.createCommandEncoder()`. + * + * @private + */ +class GPUCommandEncoderDescriptor { + + constructor() { + + /** + * The label of the command encoder. + * + * @type {string} + */ + this.label = ''; + + } + + /** + * Resets the descriptor to its default state. + */ + reset() { + + this.label = ''; + + } + +} + +export default GPUCommandEncoderDescriptor; diff --git a/src/renderers/webgpu/descriptors/GPUComputePassDescriptor.js b/src/renderers/webgpu/descriptors/GPUComputePassDescriptor.js new file mode 100644 index 00000000000000..f076a2b08a4abe --- /dev/null +++ b/src/renderers/webgpu/descriptors/GPUComputePassDescriptor.js @@ -0,0 +1,38 @@ +/** + * Reusable descriptor for `GPUCommandEncoder.beginComputePass()`. + * + * @private + */ +class GPUComputePassDescriptor { + + constructor() { + + /** + * The label of the compute pass. + * + * @type {string} + */ + this.label = ''; + + /** + * Defines which timestamp values are written and where. + * + * @type {Object|undefined} + */ + this.timestampWrites = undefined; + + } + + /** + * Resets the descriptor to its default state. + */ + reset() { + + this.label = ''; + this.timestampWrites = undefined; + + } + +} + +export default GPUComputePassDescriptor; diff --git a/src/renderers/webgpu/descriptors/GPUCopyExternalImageDestInfo.js b/src/renderers/webgpu/descriptors/GPUCopyExternalImageDestInfo.js new file mode 100644 index 00000000000000..5198b7f41efa81 --- /dev/null +++ b/src/renderers/webgpu/descriptors/GPUCopyExternalImageDestInfo.js @@ -0,0 +1,47 @@ +import GPUTexelCopyTextureInfo from './GPUTexelCopyTextureInfo.js'; + +/** + * Reusable descriptor for `GPUCopyExternalImageDestInfo`, the destination + * argument to `GPUQueue.copyExternalImageToTexture()`. + * + * @private + * @augments GPUTexelCopyTextureInfo + */ +class GPUCopyExternalImageDestInfo extends GPUTexelCopyTextureInfo { + + constructor() { + + super(); + + /** + * The predefined color space the destination texture is interpreted in. + * + * @type {string} + * @default 'srgb' + */ + this.colorSpace = 'srgb'; + + /** + * Whether the destination texture has premultiplied alpha. + * + * @type {boolean} + * @default false + */ + this.premultipliedAlpha = false; + + } + + /** + * Resets the descriptor to its default state. + */ + reset() { + + super.reset(); + this.colorSpace = 'srgb'; + this.premultipliedAlpha = false; + + } + +} + +export default GPUCopyExternalImageDestInfo; diff --git a/src/renderers/webgpu/descriptors/GPUCopyExternalImageSourceInfo.js b/src/renderers/webgpu/descriptors/GPUCopyExternalImageSourceInfo.js new file mode 100644 index 00000000000000..a3f3632195c9ee --- /dev/null +++ b/src/renderers/webgpu/descriptors/GPUCopyExternalImageSourceInfo.js @@ -0,0 +1,50 @@ +/** + * Reusable descriptor for `GPUCopyExternalImageSourceInfo`, the source argument + * to `GPUQueue.copyExternalImageToTexture()`. + * + * @private + */ +class GPUCopyExternalImageSourceInfo { + + constructor() { + + /** + * The image-like source. + * + * @type {?(ImageBitmap|ImageData|HTMLImageElement|HTMLVideoElement|VideoFrame|HTMLCanvasElement|OffscreenCanvas)} + * @default null + */ + this.source = null; + + /** + * The origin offset within the source. + * + * @type {{x: number, y: number}} + */ + this.origin = { x: 0, y: 0 }; + + /** + * Whether the source is flipped vertically before copying. + * + * @type {boolean} + * @default false + */ + this.flipY = false; + + } + + /** + * Resets the descriptor to its default state. + */ + reset() { + + this.source = null; + this.origin.x = 0; + this.origin.y = 0; + this.flipY = false; + + } + +} + +export default GPUCopyExternalImageSourceInfo; diff --git a/src/renderers/webgpu/descriptors/GPUExtent3D.js b/src/renderers/webgpu/descriptors/GPUExtent3D.js new file mode 100644 index 00000000000000..aaa6d6f6eb5125 --- /dev/null +++ b/src/renderers/webgpu/descriptors/GPUExtent3D.js @@ -0,0 +1,51 @@ +/** + * Reusable descriptor for `GPUExtent3D` in its dictionary form, used by + * `GPUQueue.writeTexture()`, `GPUQueue.copyExternalImageToTexture()` and + * the various `GPUCommandEncoder` copy methods. + * + * @private + */ +class GPUExtent3D { + + constructor() { + + /** + * The width of the extent. + * + * @type {number} + * @default 0 + */ + this.width = 0; + + /** + * The height of the extent. + * + * @type {number} + * @default 1 + */ + this.height = 1; + + /** + * The depth (for 3D textures) or number of array layers. + * + * @type {number} + * @default 1 + */ + this.depthOrArrayLayers = 1; + + } + + /** + * Resets the descriptor to its default state. + */ + reset() { + + this.width = 0; + this.height = 1; + this.depthOrArrayLayers = 1; + + } + +} + +export default GPUExtent3D; diff --git a/src/renderers/webgpu/descriptors/GPURenderBundleEncoderDescriptor.js b/src/renderers/webgpu/descriptors/GPURenderBundleEncoderDescriptor.js new file mode 100644 index 00000000000000..6866664c0e9bb6 --- /dev/null +++ b/src/renderers/webgpu/descriptors/GPURenderBundleEncoderDescriptor.js @@ -0,0 +1,74 @@ +/** + * Reusable descriptor for `GPUDevice.createRenderBundleEncoder()`. + * + * @private + */ +class GPURenderBundleEncoderDescriptor { + + constructor() { + + /** + * The label of the render bundle encoder. + * + * @type {string} + */ + this.label = ''; + + /** + * The formats of the color attachments the bundle is compatible with. + * + * @type {?Array} + * @default null + */ + this.colorFormats = null; + + /** + * The format of the depth/stencil attachment the bundle is compatible with. + * + * @type {string|undefined} + */ + this.depthStencilFormat = undefined; + + /** + * The number of samples per pixel the bundle is compatible with. + * + * @type {number} + * @default 1 + */ + this.sampleCount = 1; + + /** + * Whether the depth attachment is read-only. + * + * @type {boolean} + * @default false + */ + this.depthReadOnly = false; + + /** + * Whether the stencil attachment is read-only. + * + * @type {boolean} + * @default false + */ + this.stencilReadOnly = false; + + } + + /** + * Resets the descriptor to its default state. + */ + reset() { + + this.label = ''; + this.colorFormats = null; + this.depthStencilFormat = undefined; + this.sampleCount = 1; + this.depthReadOnly = false; + this.stencilReadOnly = false; + + } + +} + +export default GPURenderBundleEncoderDescriptor; diff --git a/src/renderers/webgpu/descriptors/GPURenderPassColorAttachment.js b/src/renderers/webgpu/descriptors/GPURenderPassColorAttachment.js new file mode 100644 index 00000000000000..376f4d37de4019 --- /dev/null +++ b/src/renderers/webgpu/descriptors/GPURenderPassColorAttachment.js @@ -0,0 +1,72 @@ +/** + * Reusable descriptor for `GPURenderPassColorAttachment`, the type of each + * entry in `GPURenderPassDescriptor.colorAttachments`. + * + * @private + */ +class GPURenderPassColorAttachment { + + constructor() { + + /** + * The texture view the pass renders into. + * + * @type {?GPUTextureView} + * @default null + */ + this.view = null; + + /** + * The depth slice the pass renders into. + * + * @type {number|undefined} + */ + this.depthSlice = undefined; + + /** + * The texture view that receives the resolved output of multisampled rendering. + * + * @type {?GPUTextureView|undefined} + */ + this.resolveTarget = undefined; + + /** + * The clear value used when `loadOp` is `'clear'`. + * + * @type {Object|undefined} + */ + this.clearValue = undefined; + + /** + * The load operation performed at the start of the pass. + * + * @type {string|undefined} + */ + this.loadOp = undefined; + + /** + * The store operation performed at the end of the pass. + * + * @type {string|undefined} + */ + this.storeOp = undefined; + + } + + /** + * Resets the descriptor to its default state. + */ + reset() { + + this.view = null; + this.depthSlice = undefined; + this.resolveTarget = undefined; + this.clearValue = undefined; + this.loadOp = undefined; + this.storeOp = undefined; + + } + +} + +export default GPURenderPassColorAttachment; diff --git a/src/renderers/webgpu/descriptors/GPURenderPassDepthStencilAttachment.js b/src/renderers/webgpu/descriptors/GPURenderPassDepthStencilAttachment.js new file mode 100644 index 00000000000000..fc88a200778209 --- /dev/null +++ b/src/renderers/webgpu/descriptors/GPURenderPassDepthStencilAttachment.js @@ -0,0 +1,99 @@ +/** + * Reusable descriptor for `GPURenderPassDepthStencilAttachment`, the + * `depthStencilAttachment` field of `GPURenderPassDescriptor`. + * + * @private + */ +class GPURenderPassDepthStencilAttachment { + + constructor() { + + /** + * The depth/stencil texture view the pass renders into. + * + * @type {?GPUTextureView} + * @default null + */ + this.view = null; + + /** + * The load operation applied to the depth aspect at the start of the pass. + * + * @type {string|undefined} + */ + this.depthLoadOp = undefined; + + /** + * The store operation applied to the depth aspect at the end of the pass. + * + * @type {string|undefined} + */ + this.depthStoreOp = undefined; + + /** + * The clear value used when `depthLoadOp` is `'clear'`. + * + * @type {number|undefined} + */ + this.depthClearValue = undefined; + + /** + * Whether the depth aspect is read-only. + * + * @type {boolean} + * @default false + */ + this.depthReadOnly = false; + + /** + * The load operation applied to the stencil aspect at the start of the pass. + * + * @type {string|undefined} + */ + this.stencilLoadOp = undefined; + + /** + * The store operation applied to the stencil aspect at the end of the pass. + * + * @type {string|undefined} + */ + this.stencilStoreOp = undefined; + + /** + * The clear value used when `stencilLoadOp` is `'clear'`. + * + * @type {number} + * @default 0 + */ + this.stencilClearValue = 0; + + /** + * Whether the stencil aspect is read-only. + * + * @type {boolean} + * @default false + */ + this.stencilReadOnly = false; + + } + + /** + * Resets the descriptor to its default state. + */ + reset() { + + this.view = null; + this.depthLoadOp = undefined; + this.depthStoreOp = undefined; + this.depthClearValue = undefined; + this.depthReadOnly = false; + this.stencilLoadOp = undefined; + this.stencilStoreOp = undefined; + this.stencilClearValue = 0; + this.stencilReadOnly = false; + + } + +} + +export default GPURenderPassDepthStencilAttachment; diff --git a/src/renderers/webgpu/descriptors/GPURenderPassDescriptor.js b/src/renderers/webgpu/descriptors/GPURenderPassDescriptor.js new file mode 100644 index 00000000000000..e0933657157c3a --- /dev/null +++ b/src/renderers/webgpu/descriptors/GPURenderPassDescriptor.js @@ -0,0 +1,72 @@ +/** + * Reusable descriptor for `GPUCommandEncoder.beginRenderPass()`. + * + * @private + */ +class GPURenderPassDescriptor { + + constructor() { + + /** + * The label of the render pass. + * + * @type {string} + */ + this.label = ''; + + /** + * The color attachments of the render pass. + * + * @type {Array} + */ + this.colorAttachments = []; + + /** + * The depth-stencil attachment of the render pass. + * + * @type {Object|undefined} + */ + this.depthStencilAttachment = undefined; + + /** + * The query set used for occlusion queries during the pass. + * + * @type {?GPUQuerySet|undefined} + */ + this.occlusionQuerySet = undefined; + + /** + * Defines which timestamp values are written and where. + * + * @type {Object|undefined} + */ + this.timestampWrites = undefined; + + /** + * The maximum number of draw calls that can be issued during the pass. + * + * @type {number} + * @default 50000000 + */ + this.maxDrawCount = 50000000; + + } + + /** + * Resets the descriptor to its default state. The internal `colorAttachments` + * array is emptied without releasing its backing storage. + */ + reset() { + + this.label = ''; + this.colorAttachments.length = 0; + this.depthStencilAttachment = undefined; + this.occlusionQuerySet = undefined; + this.timestampWrites = undefined; + this.maxDrawCount = 50000000; + + } + +} + +export default GPURenderPassDescriptor; diff --git a/src/renderers/webgpu/descriptors/GPURenderPassTimestampWrites.js b/src/renderers/webgpu/descriptors/GPURenderPassTimestampWrites.js new file mode 100644 index 00000000000000..78d71d7816e802 --- /dev/null +++ b/src/renderers/webgpu/descriptors/GPURenderPassTimestampWrites.js @@ -0,0 +1,49 @@ +/** + * Reusable descriptor for `GPURenderPassTimestampWrites`, the + * `timestampWrites` field of `GPURenderPassDescriptor`. The same shape is + * also accepted as `GPUComputePassTimestampWrites`. + * + * @private + */ +class GPURenderPassTimestampWrites { + + constructor() { + + /** + * The query set the timestamps are written to. + * + * @type {?GPUQuerySet} + * @default null + */ + this.querySet = null; + + /** + * The index in the query set the beginning timestamp is written to. + * + * @type {number|undefined} + */ + this.beginningOfPassWriteIndex = undefined; + + /** + * The index in the query set the ending timestamp is written to. + * + * @type {number|undefined} + */ + this.endOfPassWriteIndex = undefined; + + } + + /** + * Resets the descriptor to its default state. + */ + reset() { + + this.querySet = null; + this.beginningOfPassWriteIndex = undefined; + this.endOfPassWriteIndex = undefined; + + } + +} + +export default GPURenderPassTimestampWrites; diff --git a/src/renderers/webgpu/descriptors/GPUTexelCopyBufferInfo.js b/src/renderers/webgpu/descriptors/GPUTexelCopyBufferInfo.js new file mode 100644 index 00000000000000..742c5130af3f07 --- /dev/null +++ b/src/renderers/webgpu/descriptors/GPUTexelCopyBufferInfo.js @@ -0,0 +1,57 @@ +/** + * Reusable descriptor for `GPUTexelCopyBufferInfo`, the buffer side of + * `GPUCommandEncoder.copyTextureToBuffer()` and `copyBufferToTexture()`. + * + * @private + */ +class GPUTexelCopyBufferInfo { + + constructor() { + + /** + * The target buffer. + * + * @type {?GPUBuffer} + * @default null + */ + this.buffer = null; + + /** + * The byte offset within the buffer where the texel data begins. + * + * @type {number} + * @default 0 + */ + this.offset = 0; + + /** + * The stride, in bytes, between rows of texel blocks. + * + * @type {number|undefined} + */ + this.bytesPerRow = undefined; + + /** + * The number of texel block rows per single image of the texture. + * + * @type {number|undefined} + */ + this.rowsPerImage = undefined; + + } + + /** + * Resets the descriptor to its default state. + */ + reset() { + + this.buffer = null; + this.offset = 0; + this.bytesPerRow = undefined; + this.rowsPerImage = undefined; + + } + +} + +export default GPUTexelCopyBufferInfo; diff --git a/src/renderers/webgpu/descriptors/GPUTexelCopyBufferLayout.js b/src/renderers/webgpu/descriptors/GPUTexelCopyBufferLayout.js new file mode 100644 index 00000000000000..80895df89d9031 --- /dev/null +++ b/src/renderers/webgpu/descriptors/GPUTexelCopyBufferLayout.js @@ -0,0 +1,48 @@ +/** + * Reusable descriptor for `GPUTexelCopyBufferLayout`, the data-layout argument + * to `GPUQueue.writeTexture()`. + * + * @private + */ +class GPUTexelCopyBufferLayout { + + constructor() { + + /** + * The byte offset within the source data where the texel data begins. + * + * @type {number} + * @default 0 + */ + this.offset = 0; + + /** + * The stride, in bytes, between rows of texel blocks. + * + * @type {number|undefined} + */ + this.bytesPerRow = undefined; + + /** + * The number of texel block rows per single image of the texture. + * + * @type {number|undefined} + */ + this.rowsPerImage = undefined; + + } + + /** + * Resets the descriptor to its default state. + */ + reset() { + + this.offset = 0; + this.bytesPerRow = undefined; + this.rowsPerImage = undefined; + + } + +} + +export default GPUTexelCopyBufferLayout; diff --git a/src/renderers/webgpu/descriptors/GPUTexelCopyTextureInfo.js b/src/renderers/webgpu/descriptors/GPUTexelCopyTextureInfo.js new file mode 100644 index 00000000000000..f659e3adfc0c85 --- /dev/null +++ b/src/renderers/webgpu/descriptors/GPUTexelCopyTextureInfo.js @@ -0,0 +1,61 @@ +/** + * Reusable descriptor for `GPUTexelCopyTextureInfo`, the texture side of + * `GPUCommandEncoder.copyTextureToTexture()`, `copyTextureToBuffer()` and + * `GPUQueue.writeTexture()`. + * + * @private + */ +class GPUTexelCopyTextureInfo { + + constructor() { + + /** + * The target texture. + * + * @type {?GPUTexture} + * @default null + */ + this.texture = null; + + /** + * The mipmap level of the texture. + * + * @type {number} + * @default 0 + */ + this.mipLevel = 0; + + /** + * The origin offset within the texture. + * + * @type {{x: number, y: number, z: number}} + */ + this.origin = { x: 0, y: 0, z: 0 }; + + /** + * Which aspect of the texture is referenced. + * + * @type {string} + * @default 'all' + */ + this.aspect = 'all'; + + } + + /** + * Resets the descriptor to its default state. + */ + reset() { + + this.texture = null; + this.mipLevel = 0; + this.origin.x = 0; + this.origin.y = 0; + this.origin.z = 0; + this.aspect = 'all'; + + } + +} + +export default GPUTexelCopyTextureInfo; diff --git a/src/renderers/webgpu/descriptors/GPUTextureViewDescriptor.js b/src/renderers/webgpu/descriptors/GPUTextureViewDescriptor.js new file mode 100644 index 00000000000000..a5fbb0e25b1a59 --- /dev/null +++ b/src/renderers/webgpu/descriptors/GPUTextureViewDescriptor.js @@ -0,0 +1,98 @@ +/** + * Reusable descriptor for `GPUTexture.createView()`. + * + * @private + */ +class GPUTextureViewDescriptor { + + constructor() { + + /** + * The label of the texture view. + * + * @type {string} + */ + this.label = ''; + + /** + * The format of the texture view. + * + * @type {string|undefined} + */ + this.format = undefined; + + /** + * The dimension of the texture view. + * + * @type {string|undefined} + */ + this.dimension = undefined; + + /** + * The allowed usages for the texture view. + * + * @type {number} + * @default 0 + */ + this.usage = 0; + + /** + * Which aspect of the texture is referenced. + * + * @type {string} + * @default 'all' + */ + this.aspect = 'all'; + + /** + * The first mip level accessible to the texture view. + * + * @type {number} + * @default 0 + */ + this.baseMipLevel = 0; + + /** + * The number of mip levels accessible to the texture view. + * + * @type {number|undefined} + */ + this.mipLevelCount = undefined; + + /** + * The first array layer accessible to the texture view. + * + * @type {number} + * @default 0 + */ + this.baseArrayLayer = 0; + + /** + * The number of array layers accessible to the texture view. + * + * @type {number|undefined} + */ + this.arrayLayerCount = undefined; + + } + + /** + * Resets the descriptor to its default state. + */ + reset() { + + this.label = ''; + this.format = undefined; + this.dimension = undefined; + this.usage = 0; + this.aspect = 'all'; + this.baseMipLevel = 0; + this.mipLevelCount = undefined; + this.baseArrayLayer = 0; + this.arrayLayerCount = undefined; + + } + +} + +export default GPUTextureViewDescriptor; diff --git a/src/renderers/webgpu/utils/WebGPUAttributeUtils.js b/src/renderers/webgpu/utils/WebGPUAttributeUtils.js index 7e4f208c0ed2b8..4011d99fc85556 100644 --- a/src/renderers/webgpu/utils/WebGPUAttributeUtils.js +++ b/src/renderers/webgpu/utils/WebGPUAttributeUtils.js @@ -1,9 +1,12 @@ import { GPUInputStepMode } from './WebGPUConstants.js'; import { submit } from './WebGPUUtils.js'; +import GPUCommandEncoderDescriptor from '../descriptors/GPUCommandEncoderDescriptor.js'; import { Float16BufferAttribute } from '../../../core/BufferAttribute.js'; import { isTypedArray, error } from '../../../utils.js'; +const _commandEncoderDescriptor = new GPUCommandEncoderDescriptor(); + const typedArraysToVertexFormatPrefix = new Map( [ [ Int8Array, [ 'sint8', 'snorm8' ]], [ Uint8Array, [ 'uint8', 'unorm8' ]], @@ -406,9 +409,9 @@ class WebGPUAttributeUtils { } // copy the data - const cmdEncoder = device.createCommandEncoder( { - label: `readback_encoder_${ attribute.name }` - } ); + _commandEncoderDescriptor.label = `readback_encoder_${ attribute.name }`; + const cmdEncoder = device.createCommandEncoder( _commandEncoderDescriptor ); + _commandEncoderDescriptor.reset(); cmdEncoder.copyBufferToBuffer( bufferGPU, diff --git a/src/renderers/webgpu/utils/WebGPUBindingUtils.js b/src/renderers/webgpu/utils/WebGPUBindingUtils.js index f873f543585a29..cdd4a0c4e0fb8e 100644 --- a/src/renderers/webgpu/utils/WebGPUBindingUtils.js +++ b/src/renderers/webgpu/utils/WebGPUBindingUtils.js @@ -8,6 +8,10 @@ import { NodeAccess } from '../../../nodes/core/constants.js'; import { isTypedArray, error } from '../../../utils.js'; import { hashString } from '../../../nodes/core/NodeUtils.js'; +import GPUTextureViewDescriptor from '../descriptors/GPUTextureViewDescriptor.js'; + +const _viewDescriptor = new GPUTextureViewDescriptor(); + /** * Class representing a WebGPU bind group layout. * @@ -343,7 +347,14 @@ class WebGPUBindingUtils { } - resourceGPU = textureData[ propertyName ] = textureData.texture.createView( { aspect: aspectGPU, dimension: dimensionViewGPU, mipLevelCount, baseMipLevel } ); + _viewDescriptor.aspect = aspectGPU; + _viewDescriptor.dimension = dimensionViewGPU; + _viewDescriptor.mipLevelCount = mipLevelCount; + _viewDescriptor.baseMipLevel = baseMipLevel; + + resourceGPU = textureData[ propertyName ] = textureData.texture.createView( _viewDescriptor ); + + _viewDescriptor.reset(); } diff --git a/src/renderers/webgpu/utils/WebGPUPipelineUtils.js b/src/renderers/webgpu/utils/WebGPUPipelineUtils.js index f5cb377fd40b91..04696a075813e0 100644 --- a/src/renderers/webgpu/utils/WebGPUPipelineUtils.js +++ b/src/renderers/webgpu/utils/WebGPUPipelineUtils.js @@ -17,6 +17,10 @@ import { import { error, ReversedDepthFuncs, warn, warnOnce } from '../../../utils.js'; +import GPURenderBundleEncoderDescriptor from '../descriptors/GPURenderBundleEncoderDescriptor.js'; + +const _renderBundleEncoderDescriptor = new GPURenderBundleEncoderDescriptor(); + /** * A WebGPU backend utility module for managing pipelines. * @@ -359,14 +363,16 @@ class WebGPUPipelineUtils { const colorFormats = utils.getCurrentColorFormats( renderContext ); const sampleCount = this._getSampleCount( renderContext ); - const descriptor = { - label, - colorFormats, - depthStencilFormat, - sampleCount - }; + _renderBundleEncoderDescriptor.label = label; + _renderBundleEncoderDescriptor.colorFormats = colorFormats; + _renderBundleEncoderDescriptor.depthStencilFormat = depthStencilFormat; + _renderBundleEncoderDescriptor.sampleCount = sampleCount; + + const bundleEncoder = device.createRenderBundleEncoder( _renderBundleEncoderDescriptor ); + + _renderBundleEncoderDescriptor.reset(); - return device.createRenderBundleEncoder( descriptor ); + return bundleEncoder; } diff --git a/src/renderers/webgpu/utils/WebGPUTexturePassUtils.js b/src/renderers/webgpu/utils/WebGPUTexturePassUtils.js index b76223858227a8..067ccc095394f4 100644 --- a/src/renderers/webgpu/utils/WebGPUTexturePassUtils.js +++ b/src/renderers/webgpu/utils/WebGPUTexturePassUtils.js @@ -1,6 +1,17 @@ import DataMap from '../../common/DataMap.js'; import { GPUFilterMode, GPULoadOp, GPUStoreOp } from './WebGPUConstants.js'; import { submit } from './WebGPUUtils.js'; +import GPUCommandEncoderDescriptor from '../descriptors/GPUCommandEncoderDescriptor.js'; +import GPURenderBundleEncoderDescriptor from '../descriptors/GPURenderBundleEncoderDescriptor.js'; +import GPURenderPassColorAttachment from '../descriptors/GPURenderPassColorAttachment.js'; +import GPURenderPassDescriptor from '../descriptors/GPURenderPassDescriptor.js'; +import GPUTextureViewDescriptor from '../descriptors/GPUTextureViewDescriptor.js'; + +const _commandEncoderDescriptor = new GPUCommandEncoderDescriptor(); +const _renderBundleEncoderDescriptor = new GPURenderBundleEncoderDescriptor(); +const _renderPassDescriptor = new GPURenderPassDescriptor(); +const _colorAttachment = new GPURenderPassColorAttachment(); +const _viewDescriptor = new GPUTextureViewDescriptor(); /** * A WebGPU backend utility module used by {@link WebGPUTextureUtils}. @@ -212,12 +223,19 @@ fn main_cube( Varys: VarysStruct ) -> @location( 0 ) vec4 { const copyTransferPipeline = this.getTransferPipeline( format, textureGPU.textureBindingViewDimension ); const flipTransferPipeline = this.getTransferPipeline( format, tempTexture.textureBindingViewDimension ); - const commandEncoder = this.device.createCommandEncoder( {} ); + const commandEncoder = this.device.createCommandEncoder( _commandEncoderDescriptor ); const pass = ( pipeline, sourceTexture, sourceArrayLayer, destinationTexture, destinationArrayLayer, flipY ) => { const bindGroupLayout = pipeline.getBindGroupLayout( 0 ); // @TODO: Consider making this static. + _viewDescriptor.dimension = sourceTexture.textureBindingViewDimension || '2d-array'; + _viewDescriptor.mipLevelCount = 1; + + const sourceView = sourceTexture.createView( _viewDescriptor ); + + _viewDescriptor.reset(); + const bindGroup = this.device.createBindGroup( { layout: bindGroupLayout, entries: [ { @@ -225,30 +243,32 @@ fn main_cube( Varys: VarysStruct ) -> @location( 0 ) vec4 { resource: this.flipYSampler }, { binding: 1, - resource: sourceTexture.createView( { - dimension: sourceTexture.textureBindingViewDimension || '2d-array', - baseMipLevel: 0, - mipLevelCount: 1, - } ), + resource: sourceView, }, { binding: 2, resource: { buffer: flipY ? this.flipUniformBuffer : this.noFlipUniformBuffer } } ] } ); - const passEncoder = commandEncoder.beginRenderPass( { - colorAttachments: [ { - view: destinationTexture.createView( { - dimension: '2d', - baseMipLevel: 0, - mipLevelCount: 1, - baseArrayLayer: destinationArrayLayer, - arrayLayerCount: 1, - } ), - loadOp: GPULoadOp.Clear, - storeOp: GPUStoreOp.Store, - } ] - } ); + _viewDescriptor.dimension = '2d'; + _viewDescriptor.mipLevelCount = 1; + _viewDescriptor.baseArrayLayer = destinationArrayLayer; + _viewDescriptor.arrayLayerCount = 1; + + const destinationView = destinationTexture.createView( _viewDescriptor ); + + _viewDescriptor.reset(); + + _colorAttachment.view = destinationView; + _colorAttachment.loadOp = GPULoadOp.Clear; + _colorAttachment.storeOp = GPUStoreOp.Store; + + _renderPassDescriptor.colorAttachments.push( _colorAttachment ); + + const passEncoder = commandEncoder.beginRenderPass( _renderPassDescriptor ); + + _renderPassDescriptor.reset(); + _colorAttachment.reset(); passEncoder.setPipeline( pipeline ); passEncoder.setBindGroup( 0, bindGroup ); @@ -278,7 +298,15 @@ fn main_cube( Varys: VarysStruct ) -> @location( 0 ) vec4 { const passes = textureData.layers || this._mipmapCreateBundles( textureGPU ); - const commandEncoder = encoder || this.device.createCommandEncoder( { label: 'mipmapEncoder' } ); + let commandEncoder = encoder; + + if ( commandEncoder === null ) { + + _commandEncoderDescriptor.label = 'mipmapEncoder'; + commandEncoder = this.device.createCommandEncoder( _commandEncoderDescriptor ); + _commandEncoderDescriptor.reset(); + + } this._mipmapRunBundles( commandEncoder, passes ); @@ -308,6 +336,14 @@ fn main_cube( Varys: VarysStruct ) -> @location( 0 ) vec4 { for ( let baseArrayLayer = 0; baseArrayLayer < textureGPU.depthOrArrayLayers; baseArrayLayer ++ ) { + _viewDescriptor.dimension = textureBindingViewDimension; + _viewDescriptor.baseMipLevel = baseMipLevel - 1; + _viewDescriptor.mipLevelCount = 1; + + const sourceView = textureGPU.createView( _viewDescriptor ); + + _viewDescriptor.reset(); + const bindGroup = this.device.createBindGroup( { layout: bindGroupLayout, entries: [ { @@ -315,34 +351,36 @@ fn main_cube( Varys: VarysStruct ) -> @location( 0 ) vec4 { resource: this.mipmapSampler }, { binding: 1, - resource: textureGPU.createView( { - dimension: textureBindingViewDimension, - baseMipLevel: baseMipLevel - 1, - mipLevelCount: 1, - } ), + resource: sourceView, }, { binding: 2, resource: { buffer: this.noFlipUniformBuffer } } ] } ); - const passDescriptor = { - colorAttachments: [ { - view: textureGPU.createView( { - dimension: '2d', - baseMipLevel, - mipLevelCount: 1, - baseArrayLayer, - arrayLayerCount: 1, - } ), - loadOp: GPULoadOp.Clear, - storeOp: GPUStoreOp.Store, - } ] - }; + _viewDescriptor.dimension = '2d'; + _viewDescriptor.baseMipLevel = baseMipLevel; + _viewDescriptor.mipLevelCount = 1; + _viewDescriptor.baseArrayLayer = baseArrayLayer; + _viewDescriptor.arrayLayerCount = 1; - const passEncoder = this.device.createRenderBundleEncoder( { - colorFormats: [ textureGPU.format ] - } ); + const destinationView = textureGPU.createView( _viewDescriptor ); + + _viewDescriptor.reset(); + + const passColorAttachment = new GPURenderPassColorAttachment(); + passColorAttachment.view = destinationView; + passColorAttachment.loadOp = GPULoadOp.Clear; + passColorAttachment.storeOp = GPUStoreOp.Store; + + const passDescriptor = new GPURenderPassDescriptor(); + passDescriptor.colorAttachments.push( passColorAttachment ); + + _renderBundleEncoderDescriptor.colorFormats = [ textureGPU.format ]; + + const passEncoder = this.device.createRenderBundleEncoder( _renderBundleEncoderDescriptor ); + + _renderBundleEncoderDescriptor.reset(); passEncoder.setPipeline( pipeline ); passEncoder.setBindGroup( 0, bindGroup ); diff --git a/src/renderers/webgpu/utils/WebGPUTextureUtils.js b/src/renderers/webgpu/utils/WebGPUTextureUtils.js index 4fbbefa933e40b..a12de85f250cde 100644 --- a/src/renderers/webgpu/utils/WebGPUTextureUtils.js +++ b/src/renderers/webgpu/utils/WebGPUTextureUtils.js @@ -5,10 +5,25 @@ import { ColorManagement } from '../../../math/ColorManagement.js'; import WebGPUTexturePassUtils from './WebGPUTexturePassUtils.js'; import { submit } from './WebGPUUtils.js'; +import GPUCommandEncoderDescriptor from '../descriptors/GPUCommandEncoderDescriptor.js'; +import GPUTexelCopyTextureInfo from '../descriptors/GPUTexelCopyTextureInfo.js'; +import GPUTexelCopyBufferInfo from '../descriptors/GPUTexelCopyBufferInfo.js'; +import GPUTexelCopyBufferLayout from '../descriptors/GPUTexelCopyBufferLayout.js'; +import GPUCopyExternalImageSourceInfo from '../descriptors/GPUCopyExternalImageSourceInfo.js'; +import GPUCopyExternalImageDestInfo from '../descriptors/GPUCopyExternalImageDestInfo.js'; +import GPUExtent3D from '../descriptors/GPUExtent3D.js'; + +const _commandEncoderDescriptor = new GPUCommandEncoderDescriptor(); +const _texelCopyTextureInfo = new GPUTexelCopyTextureInfo(); +const _texelCopyBufferInfo = new GPUTexelCopyBufferInfo(); +const _texelCopyBufferLayout = new GPUTexelCopyBufferLayout(); +const _copyExternalImageSourceInfo = new GPUCopyExternalImageSourceInfo(); +const _copyExternalImageDestInfo = new GPUCopyExternalImageDestInfo(); +const _extent3D = new GPUExtent3D(); import { ByteType, ShortType, - NearestFilter, NearestMipmapNearestFilter, NearestMipmapLinearFilter, + NearestFilter, NearestMipmapNearestFilter, NearestMipmapLinearFilter, LinearMipmapLinearFilter, RepeatWrapping, MirroredRepeatWrapping, RGB_ETC2_Format, RGBA_ETC2_EAC_Format, RGBAFormat, RGBFormat, RedFormat, RGFormat, RGBA_S3TC_DXT1_Format, RGBA_S3TC_DXT3_Format, RGBA_S3TC_DXT5_Format, UnsignedByteType, FloatType, HalfFloatType, SRGBTransfer, DepthFormat, DepthStencilFormat, @@ -38,25 +53,28 @@ const _flipMap = [ 0, 1, 3, 2, 4, 5 ]; function writeTextureLayer( device, textureGPU, mipLevel, layerIndex, mipmap, bytesPerImage, bytesPerRow, rowsPerImage, textureWidth, textureHeight ) { + _texelCopyTextureInfo.texture = textureGPU; + _texelCopyTextureInfo.mipLevel = mipLevel; + _texelCopyTextureInfo.origin.z = layerIndex; + + _texelCopyBufferLayout.offset = layerIndex * bytesPerImage; + _texelCopyBufferLayout.bytesPerRow = bytesPerRow; + _texelCopyBufferLayout.rowsPerImage = rowsPerImage; + + _extent3D.width = textureWidth; + _extent3D.height = textureHeight; + device.queue.writeTexture( - { - texture: textureGPU, - mipLevel, - origin: { x: 0, y: 0, z: layerIndex } - }, + _texelCopyTextureInfo, mipmap.data, - { - offset: layerIndex * bytesPerImage, - bytesPerRow, - rowsPerImage - }, - { - width: textureWidth, - height: textureHeight, - depthOrArrayLayers: 1 - } + _texelCopyBufferLayout, + _extent3D ); + _texelCopyTextureInfo.reset(); + _texelCopyBufferLayout.reset(); + _extent3D.reset(); + } /** @@ -147,7 +165,7 @@ class WebGPUTextureUtils { addressModeW: this._convertAddressMode( texture.wrapR ), magFilter: this._convertFilterMode( texture.magFilter ), minFilter: this._convertFilterMode( texture.minFilter ), - mipmapFilter: this._convertFilterMode( texture.minFilter ), + mipmapFilter: this._convertMipmapFilterMode( texture.minFilter ), maxAnisotropy: 1 }; @@ -691,24 +709,29 @@ class WebGPUTextureUtils { } ); - const encoder = device.createCommandEncoder(); + const encoder = device.createCommandEncoder( _commandEncoderDescriptor ); - encoder.copyTextureToBuffer( - { - texture: textureGPU, - origin: { x, y, z: faceIndex }, - }, - { - buffer: readBuffer, - bytesPerRow: bytesPerRow - }, - { - width: width, - height: height - } + _texelCopyTextureInfo.texture = textureGPU; + _texelCopyTextureInfo.origin.x = x; + _texelCopyTextureInfo.origin.y = y; + _texelCopyTextureInfo.origin.z = faceIndex; + _texelCopyBufferInfo.buffer = readBuffer; + _texelCopyBufferInfo.bytesPerRow = bytesPerRow; + + _extent3D.width = width; + _extent3D.height = height; + + encoder.copyTextureToBuffer( + _texelCopyTextureInfo, + _texelCopyBufferInfo, + _extent3D ); + _texelCopyTextureInfo.reset(); + _texelCopyBufferInfo.reset(); + _extent3D.reset(); + const typedArrayType = this._getTypedArrayType( format ); submit( device, encoder.finish() ); @@ -856,27 +879,36 @@ class WebGPUTextureUtils { const width = ( mipLevel > 0 ) ? image.width : textureDescriptorGPU.size.width; const height = ( mipLevel > 0 ) ? image.height : textureDescriptorGPU.size.height; + _copyExternalImageSourceInfo.source = image; + _copyExternalImageSourceInfo.flipY = flipY; + + _copyExternalImageDestInfo.texture = textureGPU; + _copyExternalImageDestInfo.mipLevel = mipLevel; + _copyExternalImageDestInfo.origin.z = originDepth; + _copyExternalImageDestInfo.premultipliedAlpha = premultiplyAlpha; + + _extent3D.width = width; + _extent3D.height = height; + try { device.queue.copyExternalImageToTexture( - { - source: image, - flipY: flipY - }, { - texture: textureGPU, - mipLevel: mipLevel, - origin: { x: 0, y: 0, z: originDepth }, - premultipliedAlpha: premultiplyAlpha - }, { - width: width, - height: height, - depthOrArrayLayers: 1 - } + _copyExternalImageSourceInfo, + _copyExternalImageDestInfo, + _extent3D ); // try/catch has been added to fix bad video frame data on certain devices, see #32391 - } catch ( _ ) {} + } catch ( _ ) { + + } finally { + + _copyExternalImageSourceInfo.reset(); + _copyExternalImageDestInfo.reset(); + _extent3D.reset(); + + } } @@ -951,22 +983,26 @@ class WebGPUTextureUtils { const bytesPerTexel = this._getBytesPerTexel( textureDescriptorGPU.format ); const bytesPerRow = image.width * bytesPerTexel; + _texelCopyTextureInfo.texture = textureGPU; + _texelCopyTextureInfo.mipLevel = mipLevel; + _texelCopyTextureInfo.origin.z = originDepth; + + _texelCopyBufferLayout.offset = image.width * image.height * bytesPerTexel * depth; + _texelCopyBufferLayout.bytesPerRow = bytesPerRow; + + _extent3D.width = image.width; + _extent3D.height = image.height; + device.queue.writeTexture( - { - texture: textureGPU, - mipLevel: mipLevel, - origin: { x: 0, y: 0, z: originDepth } - }, + _texelCopyTextureInfo, data, - { - offset: image.width * image.height * bytesPerTexel * depth, - bytesPerRow - }, - { - width: image.width, - height: image.height, - depthOrArrayLayers: 1 - } ); + _texelCopyBufferLayout, + _extent3D + ); + + _texelCopyTextureInfo.reset(); + _texelCopyBufferLayout.reset(); + _extent3D.reset(); if ( flipY === true ) { @@ -1120,6 +1156,27 @@ class WebGPUTextureUtils { } + /** + * Converts the three.js filter constants to a GPU mipmap filter constant. + * Unlike `_convertFilterMode`, this extracts the between-mip-level filtering + * axis from the combined three.js constant rather than the within-level axis. + * + * @private + * @param {number} value - The three.js constant defining a filter mode. + * @return {string} The GPU mipmap filter mode. + */ + _convertMipmapFilterMode( value ) { + + if ( value === NearestMipmapLinearFilter || value === LinearMipmapLinearFilter ) { + + return GPUFilterMode.Linear; + + } + + return GPUFilterMode.Nearest; + + } + /** * Returns the bytes-per-texel value for the given GPU texture format. * diff --git a/src/renderers/webgpu/utils/WebGPUTimestampQueryPool.js b/src/renderers/webgpu/utils/WebGPUTimestampQueryPool.js index 6d442588779379..123faf669f899f 100644 --- a/src/renderers/webgpu/utils/WebGPUTimestampQueryPool.js +++ b/src/renderers/webgpu/utils/WebGPUTimestampQueryPool.js @@ -1,6 +1,9 @@ import { error, warnOnce } from '../../../utils.js'; import TimestampQueryPool from '../../common/TimestampQueryPool.js'; import { submit } from './WebGPUUtils.js'; +import GPUCommandEncoderDescriptor from '../descriptors/GPUCommandEncoderDescriptor.js'; + +const _commandEncoderDescriptor = new GPUCommandEncoderDescriptor(); /** * Manages a pool of WebGPU timestamp queries for performance measurement. @@ -137,7 +140,7 @@ class WebGPUTimestampQueryPool extends TimestampQueryPool { this.currentQueryIndex = 0; this.queryOffsets.clear(); - const commandEncoder = this.device.createCommandEncoder(); + const commandEncoder = this.device.createCommandEncoder( _commandEncoderDescriptor ); commandEncoder.resolveQuerySet( this.querySet,