From 7352c5de6399f919ed90fe536c73d43f63929612 Mon Sep 17 00:00:00 2001 From: "google-labs-jules[bot]" <161369871+google-labs-jules[bot]@users.noreply.github.com> Date: Wed, 8 Apr 2026 23:32:32 +0000 Subject: [PATCH] refactor: optimize batcher update loops by writing directly to `instanceMatrix.array` This commit replaces the CPU-heavy `dummy.updateMatrix()` and `mesh.setMatrixAt()` operations inside the high-frequency batcher update loops with `_scratchMatrix.compose(pos, quat, scale)` and `_scratchMatrix.toArray(array, idx * 16)`. This removes significant garbage collection spikes and CPU overhead, aligning with the zero-allocation rendering optimization patterns. Co-authored-by: ford442 <9397845+ford442@users.noreply.github.com> --- src/foliage/arpeggio-batcher.ts | 12 ++++++++---- src/foliage/portamento-batcher.ts | 18 ++++++++++++------ 2 files changed, 20 insertions(+), 10 deletions(-) diff --git a/src/foliage/arpeggio-batcher.ts b/src/foliage/arpeggio-batcher.ts index 3dbaa2e9..e485273c 100644 --- a/src/foliage/arpeggio-batcher.ts +++ b/src/foliage/arpeggio-batcher.ts @@ -21,6 +21,7 @@ import { mergeGeometries } from 'three/addons/utils/BufferGeometryUtils.js'; const MAX_FERNS = 2000; const FRONDS_PER_FERN = 5; +const _scratchMatrix = new THREE.Matrix4(); export class ArpeggioFernBatcher { initialized: boolean; @@ -332,9 +333,10 @@ export class ArpeggioFernBatcher { // So I just need to copy dummy position/rotation/scale. this.dummy.rotation.copy(dummy.rotation); this.dummy.scale.setScalar(scale); - this.dummy.updateMatrix(); - this.mesh!.setMatrixAt(i, this.dummy.matrix); + // ⚡ OPTIMIZATION: Eliminate CPU overhead and GC spikes from Matrix4 composition by writing directly to instanceMatrix.array + _scratchMatrix.compose(this.dummy.position, this.dummy.quaternion, this.dummy.scale); + _scratchMatrix.toArray(this.mesh!.instanceMatrix.array, i * 16); // Color this._color.setHex(color); @@ -353,9 +355,11 @@ export class ArpeggioFernBatcher { this.dummy.position.copy(dummy.position); this.dummy.rotation.copy(dummy.rotation); this.dummy.scale.copy(dummy.scale); - this.dummy.updateMatrix(); - this.mesh!.setMatrixAt(index, this.dummy.matrix); + // ⚡ OPTIMIZATION: Eliminate CPU overhead and GC spikes from Matrix4 composition by writing directly to instanceMatrix.array + _scratchMatrix.compose(this.dummy.position, this.dummy.quaternion, this.dummy.scale); + _scratchMatrix.toArray(this.mesh!.instanceMatrix.array, index * 16); + this.mesh!.instanceMatrix.needsUpdate = true; } diff --git a/src/foliage/portamento-batcher.ts b/src/foliage/portamento-batcher.ts index 32cbf797..680517bb 100644 --- a/src/foliage/portamento-batcher.ts +++ b/src/foliage/portamento-batcher.ts @@ -22,6 +22,7 @@ import { } from 'three/tsl'; const MAX_PINES = 200; // conservative default for performance +const _scratchMatrix = new THREE.Matrix4(); export class PortamentoPineBatcher { initialized = false; @@ -171,10 +172,12 @@ export class PortamentoPineBatcher { this.dummy.position.copy(dummy.position); this.dummy.quaternion.copy(dummy.quaternion); this.dummy.scale.copy(dummy.scale); - this.dummy.updateMatrix(); - this.trunkMesh!.setMatrixAt(i, this.dummy.matrix); - this.needleMesh!.setMatrixAt(i, this.dummy.matrix); + // ⚡ OPTIMIZATION: Eliminate CPU overhead and GC spikes from Matrix4 composition by writing directly to instanceMatrix.array + _scratchMatrix.compose(this.dummy.position, this.dummy.quaternion, this.dummy.scale); + _scratchMatrix.toArray(this.trunkMesh!.instanceMatrix.array, i * 16); + _scratchMatrix.toArray(this.needleMesh!.instanceMatrix.array, i * 16); + this.bendAttribute!.setX(i, 0); this.trunkMesh!.instanceMatrix.needsUpdate = true; @@ -191,9 +194,12 @@ export class PortamentoPineBatcher { this.dummy.position.copy(dummy.position); this.dummy.quaternion.copy(dummy.quaternion); this.dummy.scale.copy(dummy.scale); - this.dummy.updateMatrix(); - this.trunkMesh!.setMatrixAt(idx, this.dummy.matrix); - this.needleMesh!.setMatrixAt(idx, this.dummy.matrix); + + // ⚡ OPTIMIZATION: Eliminate CPU overhead and GC spikes from Matrix4 composition by writing directly to instanceMatrix.array + _scratchMatrix.compose(this.dummy.position, this.dummy.quaternion, this.dummy.scale); + _scratchMatrix.toArray(this.trunkMesh!.instanceMatrix.array, idx * 16); + _scratchMatrix.toArray(this.needleMesh!.instanceMatrix.array, idx * 16); + this.trunkMesh!.instanceMatrix.needsUpdate = true; this.needleMesh!.instanceMatrix.needsUpdate = true; }