From 60dc159dbf40e7f4412597bec4df1e88f9452eac Mon Sep 17 00:00:00 2001 From: Renaud Rohlinger Date: Tue, 4 Mar 2025 20:57:17 +0900 Subject: [PATCH 1/2] WebGPURenderer: BatchedMesh via drawIndexedIndirect --- src/nodes/accessors/BatchNode.js | 84 +++++++++++++++++++++++++-- src/renderers/webgpu/WebGPUBackend.js | 23 +++++++- 2 files changed, 101 insertions(+), 6 deletions(-) diff --git a/src/nodes/accessors/BatchNode.js b/src/nodes/accessors/BatchNode.js index 5c1bd92889b70e..89d13a57d62219 100644 --- a/src/nodes/accessors/BatchNode.js +++ b/src/nodes/accessors/BatchNode.js @@ -7,6 +7,8 @@ import { textureSize } from './TextureSizeNode.js'; import { tangentLocal } from './Tangent.js'; import { instanceIndex, drawIndex } from '../core/IndexNode.js'; import { varyingProperty } from '../core/PropertyNode.js'; +import { NodeUpdateType } from '../Nodes.js'; +import IndirectStorageBufferAttribute from '../../renderers/common/IndirectStorageBufferAttribute.js'; /** * This node implements the vertex shader logic which is required @@ -46,6 +48,14 @@ class BatchNode extends Node { * @default null */ this.batchingIdNode = null; + this.updateBeforeType = NodeUpdateType.FRAME; + + /** + * A reference of the indirect version to prevent unnecessary updates. + * @type {Number} + * @default 0 + */ + this.indirectVersion = 0; } @@ -72,12 +82,43 @@ class BatchNode extends Node { } + + if ( builder.isFlipY() === false ) { + + const object = this.batchMesh; + const geometry = object.geometry; + + const uint32 = new Uint32Array( 5 * object._maxInstanceCount ); + const starts = object._multiDrawStarts; + const counts = object._multiDrawCounts; + const drawCount = object._multiDrawCount; + const drawInstances = object._multiDrawInstances; + + for ( let i = 0; i < drawCount; i ++ ) { + + const count = drawInstances ? drawInstances[ i ] : 1; + + uint32[ i * 5 ] = counts[ i ]; // indexCount + uint32[ i * 5 + 1 ] = count; // instanceCount + uint32[ i * 5 + 2 ] = starts[ i ] / object.geometry.index.array.BYTES_PER_ELEMENT; // firstIndex + uint32[ i * 5 + 3 ] = 0; // baseVertex + uint32[ i * 5 + 4 ] = i; // firstInstance + + } + + const indirectAttribute = new IndirectStorageBufferAttribute( uint32, 5 ); + geometry.setIndirect( indirectAttribute ); + + } + const getIndirectIndex = Fn( ( [ id ] ) => { - const size = int( textureSize( textureLoad( this.batchMesh._indirectTexture ), 0 ) ); - const x = int( id ).modInt( size ); - const y = int( id ).div( size ); - return textureLoad( this.batchMesh._indirectTexture, ivec2( x, y ) ).x; + const size = textureLoad( this.batchMesh._indirectTexture ).size( 0 ).x.toInt().toConst( 'size' ); + const x = int( id ).modInt( size ).toConst( 'x' ); + const y = int( id ).div( size ).toConst( 'y' ); + const index = textureLoad( this.batchMesh._indirectTexture, ivec2( x, y ) ).x.toFloat().toConst( 'index' ); + + return index; } ).setLayout( { name: 'getIndirectIndex', @@ -148,6 +189,41 @@ class BatchNode extends Node { } + updateBefore() { + + const object = this.batchMesh; + + const indirect = object.geometry.getIndirect(); + + if ( indirect !== null && object._indirectTexture.version > this.indirectVersion ) { + + const uint32 = new Uint32Array( 5 * object._maxInstanceCount ); + const starts = object._multiDrawStarts; + const counts = object._multiDrawCounts; + const drawCount = object._multiDrawCount; + const drawInstances = object._multiDrawInstances; + + for ( let i = 0; i < drawCount; i ++ ) { + + const count = drawInstances ? drawInstances[ i ] : 1; + + uint32[ i * 5 ] = counts[ i ]; // indexCount + uint32[ i * 5 + 1 ] = count; // instanceCount + uint32[ i * 5 + 2 ] = starts[ i ] / object.geometry.index.array.BYTES_PER_ELEMENT; // firstIndex + uint32[ i * 5 + 3 ] = 0; // baseVertex + uint32[ i * 5 + 4 ] = i; // firstInstance + + } + + indirect.array = uint32; + indirect.needsUpdate = true; + + this.indirectVersion = object._indirectTexture.version; + + } + + } + } export default BatchNode; diff --git a/src/renderers/webgpu/WebGPUBackend.js b/src/renderers/webgpu/WebGPUBackend.js index 377bf9cbf67c30..c4d8ba74e44a7e 100644 --- a/src/renderers/webgpu/WebGPUBackend.js +++ b/src/renderers/webgpu/WebGPUBackend.js @@ -1230,6 +1230,8 @@ class WebGPUBackend extends Backend { const draw = () => { + const indirect = renderObject.getIndirect(); + if ( object.isBatchedMesh === true ) { const starts = object._multiDrawStarts; @@ -1244,6 +1246,14 @@ class WebGPUBackend extends Backend { } + let indirectBuffer = null; + + if ( indirect !== null ) { + + indirectBuffer = this.get( indirect ).buffer; + + } + for ( let i = 0; i < drawCount; i ++ ) { const count = drawInstances ? drawInstances[ i ] : 1; @@ -1251,7 +1261,17 @@ class WebGPUBackend extends Backend { if ( hasIndex === true ) { - passEncoderGPU.drawIndexed( counts[ i ], count, starts[ i ] / index.array.BYTES_PER_ELEMENT, 0, firstInstance ); + + if ( indirect !== null ) { + + passEncoderGPU.drawIndexedIndirect( indirectBuffer, i * 5 * Uint32Array.BYTES_PER_ELEMENT ); + + } else { + + passEncoderGPU.drawIndexed( counts[ i ], count, starts[ i ] / index.array.BYTES_PER_ELEMENT, 0, firstInstance ); + + } + } else { @@ -1267,7 +1287,6 @@ class WebGPUBackend extends Backend { const { vertexCount: indexCount, instanceCount, firstVertex: firstIndex } = drawParams; - const indirect = renderObject.getIndirect(); if ( indirect !== null ) { From f190a44d6b0201b869babf9439d83f8cf86cfbb0 Mon Sep 17 00:00:00 2001 From: Renaud Rohlinger Date: Tue, 4 Mar 2025 21:39:00 +0900 Subject: [PATCH 2/2] fix circular dep --- src/nodes/accessors/BatchNode.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/nodes/accessors/BatchNode.js b/src/nodes/accessors/BatchNode.js index 89d13a57d62219..9b4ca9062c2cbd 100644 --- a/src/nodes/accessors/BatchNode.js +++ b/src/nodes/accessors/BatchNode.js @@ -7,7 +7,7 @@ import { textureSize } from './TextureSizeNode.js'; import { tangentLocal } from './Tangent.js'; import { instanceIndex, drawIndex } from '../core/IndexNode.js'; import { varyingProperty } from '../core/PropertyNode.js'; -import { NodeUpdateType } from '../Nodes.js'; +import { NodeUpdateType } from '../core/constants.js'; import IndirectStorageBufferAttribute from '../../renderers/common/IndirectStorageBufferAttribute.js'; /**