Skip to content
Open
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
82 changes: 59 additions & 23 deletions src/nodes/accessors/InstanceNode.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,10 @@ import { varyingProperty } from '../core/PropertyNode.js';
import { instancedBufferAttribute, instancedDynamicBufferAttribute } from './BufferAttributeNode.js';
import { normalLocal, transformNormal } from './Normal.js';
import { positionLocal } from './Position.js';
import { nodeProxy, vec3 } from '../tsl/TSLBase.js';
import { nodeProxy, vec3, mat4 } from '../tsl/TSLBase.js';
import { NodeUpdateType } from '../core/constants.js';
import { buffer } from '../accessors/BufferNode.js';
import { storage } from './StorageBufferNode.js';
import { instanceIndex } from '../core/IndexNode.js';

import { InstancedInterleavedBuffer } from '../../core/InstancedInterleavedBuffer.js';
Expand All @@ -32,8 +33,8 @@ class InstanceNode extends Node {
* Constructs a new instance node.
*
* @param {number} count - The number of instances.
* @param {InstancedBufferAttribute} instanceMatrix - Instanced buffer attribute representing the instance transformations.
* @param {?InstancedBufferAttribute} instanceColor - Instanced buffer attribute representing the instance colors.
* @param {InstancedBufferAttribute|StorageInstancedBufferAttribute} instanceMatrix - Instanced buffer attribute representing the instance transformations.
* @param {?InstancedBufferAttribute|StorageInstancedBufferAttribute} instanceColor - Instanced buffer attribute representing the instance colors.
*/
constructor( count, instanceMatrix, instanceColor = null ) {

Expand All @@ -60,6 +61,20 @@ class InstanceNode extends Node {
*/
this.instanceColor = instanceColor;

/**
* Tracks whether the matrix data is provided via a storage buffer.
*
* @type {boolean}
*/
this.isStorageMatrix = instanceMatrix && instanceMatrix.isStorageInstancedBufferAttribute === true;

/**
* Tracks whether the color data is provided via a storage buffer.
*
* @type {boolean}
*/
this.isStorageColor = instanceColor && instanceColor.isStorageInstancedBufferAttribute === true;

/**
* The node that represents the instance matrix data.
*
Expand Down Expand Up @@ -109,29 +124,44 @@ class InstanceNode extends Node {
*/
setup( builder ) {

const { instanceMatrix, instanceColor } = this;
const { instanceMatrix, instanceColor, isStorageMatrix, isStorageColor } = this;

const { count } = instanceMatrix;

let { instanceMatrixNode, instanceColorNode } = this;

if ( instanceMatrixNode === null ) {

// Both WebGPU and WebGL backends have UBO max limited to 64kb. Matrix count number bigger than 1000 ( 16 * 4 * 1000 = 64kb ) will fallback to attribute.

if ( count <= 1000 ) {
if ( isStorageMatrix ) {

instanceMatrixNode = buffer( instanceMatrix.array, 'mat4', Math.max( count, 1 ) ).element( instanceIndex );
instanceMatrixNode = storage( instanceMatrix, 'mat4', Math.max( count, 1 ) ).element( instanceIndex );

} else {

const buffer = new InstancedInterleavedBuffer( instanceMatrix.array, 16, 1 );
// Both backends have ~64kb UBO limit; fallback to attributes above 1000 matrices.
Copy link
Collaborator

@Mugen87 Mugen87 Nov 11, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unrelated side note: This comment isn't 100% correct since the WebGL 2 backend has a guaranteed size of only 16KB, see #30560 (comment). 64KB is supported on most platforms though. It would still be safer to set count to a smaller value.


if ( count <= 1000 ) {

instanceMatrixNode = buffer( instanceMatrix.array, 'mat4', Math.max( count, 1 ) ).element( instanceIndex );

const instancedBufferAttributeFn = instanceMatrix.usage === DynamicDrawUsage ? instancedDynamicBufferAttribute : instancedBufferAttribute;
} else {

instanceMatrixNode = instancedBufferAttributeFn( buffer, 'mat4' );
const interleaved = new InstancedInterleavedBuffer( instanceMatrix.array, 16, 1 );

this.buffer = buffer;
this.buffer = interleaved;

const bufferFn = instanceMatrix.usage === DynamicDrawUsage ? instancedDynamicBufferAttribute : instancedBufferAttribute;

const instanceBuffers = [
bufferFn( interleaved, 'vec4', 16, 0 ),
bufferFn( interleaved, 'vec4', 16, 4 ),
bufferFn( interleaved, 'vec4', 16, 8 ),
bufferFn( interleaved, 'vec4', 16, 12 )
];

instanceMatrixNode = mat4( ...instanceBuffers );

}

}

Expand All @@ -141,13 +171,21 @@ class InstanceNode extends Node {

if ( instanceColor && instanceColorNode === null ) {

const buffer = new InstancedBufferAttribute( instanceColor.array, 3 );
if ( isStorageColor ) {

instanceColorNode = storage( instanceColor, 'vec3', Math.max( instanceColor.count, 1 ) ).element( instanceIndex );

const bufferFn = instanceColor.usage === DynamicDrawUsage ? instancedDynamicBufferAttribute : instancedBufferAttribute;
} else {

this.bufferColor = buffer;
const bufferAttribute = new InstancedBufferAttribute( instanceColor.array, 3 );

instanceColorNode = vec3( bufferFn( buffer, 'vec3', 3, 0 ) );
const bufferFn = instanceColor.usage === DynamicDrawUsage ? instancedDynamicBufferAttribute : instancedBufferAttribute;

this.bufferColor = bufferAttribute;

instanceColorNode = vec3( bufferFn( bufferAttribute, 'vec3', 3, 0 ) );

}

this.instanceColorNode = instanceColorNode;

Expand Down Expand Up @@ -181,15 +219,13 @@ class InstanceNode extends Node {
}

/**
* Checks if the internal buffers required an update.
* Checks if the internal buffers require an update.
*
* @param {NodeFrame} frame - The current node frame.
*/
update( /*frame*/ ) {

if ( this.buffer !== null ) {

// keep update ranges in sync
if ( this.buffer !== null && this.isStorageMatrix !== true ) {

this.buffer.clearUpdateRanges();
this.buffer.updateRanges.push( ... this.instanceMatrix.updateRanges );
Expand All @@ -204,7 +240,7 @@ class InstanceNode extends Node {

}

if ( this.instanceColor && this.bufferColor !== null ) {
if ( this.instanceColor && this.bufferColor !== null && this.isStorageColor !== true ) {

this.bufferColor.clearUpdateRanges();
this.bufferColor.updateRanges.push( ... this.instanceColor.updateRanges );
Expand All @@ -229,8 +265,8 @@ export default InstanceNode;
* @tsl
* @function
* @param {number} count - The number of instances.
* @param {InstancedBufferAttribute} instanceMatrix - Instanced buffer attribute representing the instance transformations.
* @param {?InstancedBufferAttribute} instanceColor - Instanced buffer attribute representing the instance colors.
* @param {InstancedBufferAttribute|StorageInstancedBufferAttribute} instanceMatrix - Instanced buffer attribute representing the instance transformations.
* @param {?InstancedBufferAttribute|StorageInstancedBufferAttribute} instanceColor - Instanced buffer attribute representing the instance colors.
* @returns {InstanceNode}
*/
export const instance = /*@__PURE__*/ nodeProxy( InstanceNode ).setParameterLength( 2, 3 );