From 60dc159dbf40e7f4412597bec4df1e88f9452eac Mon Sep 17 00:00:00 2001
From: Renaud Rohlinger <renaud.rohlinger@gmail.com>
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 <renaud.rohlinger@gmail.com>
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';
 
 /**