v10 deployment.Compute (Buffers & Storage)
Experimental.
useBuffersanduseGPUStoragecover GPU compute workflows that are still evolving. APIs may change between releases. Pin your version and expect churn.
useBuffers and useGPUStorage manage GPU storage for compute operations. They follow the same create-if-not-exists pattern and scoping model as useNodes and useUniforms, and their creators receive a CreatorState with ScopedStore wrappers.
import { useBuffers, useGPUStorage } from '@react-three/tsl'
useBuffers
Manages buffer data: CPU TypedArrays, GPU buffer attributes, and TSL buffer nodes.
// Root level
const buffers = useBuffers(() => ({
positions: instancedArray(count, 'vec3'),
velocities: new Float32Array(count * 3),
}))
// Scoped
const buffers = useBuffers(() => ({ particlePos: new StorageBufferAttribute(count, 4) }), 'particles')
// Read all / read a scope
const all = useBuffers()
const particleBuffers = useBuffers('particles')
Parameters
Return Value
Returns BuffersWithUtils<BufferRecord> - your buffers by name, plus four util functions:
{
// ...your buffers
removeBuffers: (names: string | string[], scope?: string) => void
clearBuffers: (scope?: string) => void
rebuildBuffers: (scope?: string) => void
disposeBuffers: (names: string | string[], scope?: string) => void
}
removeBuffers- remove buffers by name from root or a scope (store only).clearBuffers- clear a scope,'root'for root only, or no argument for everything.rebuildBuffers- invalidate the cache and bump the HMR version to force re-creation (all,'root', or a scope). See TSL HMR for the rebuild guarantees.disposeBuffers- releases GPU resources (.dispose()) and removes from the store.
What Can Be Stored
useBuffers(() => ({
// TypedArrays (CPU-side data)
cpuData: new Float32Array(count * 3),
indices: new Uint32Array(count),
// Three.js buffer attributes
storageAttr: new StorageBufferAttribute(count, 4),
instancedAttr: new StorageInstancedBufferAttribute(count, 4),
indirectAttr: new IndirectStorageBufferAttribute(data, 1),
// TSL buffer nodes
positions: instancedArray(count, 'vec3'),
custom: storage(bufferAttribute, 'vec4', count),
}))
useGPUStorage
Manages GPU storage textures for compute operations.
// Root level
const storage = useGPUStorage(() => ({ heightMap: new StorageTexture(512, 512) }))
// Scoped
const storage = useGPUStorage(() => ({ normalMap: new StorageTexture(1024, 1024) }), 'terrain')
// Read all / read a scope
const all = useGPUStorage()
const terrainStorage = useGPUStorage('terrain')
Parameters
Return Value
Returns StorageWithUtils<StorageRecord> - your storage by name, plus four util functions:
{
// ...your storage
removeStorage: (names: string | string[], scope?: string) => void
clearStorage: (scope?: string) => void
rebuildStorage: (scope?: string) => void
disposeStorage: (names: string | string[], scope?: string) => void
}
These mirror the useBuffers utils. disposeStorage releases GPU texture memory and removes the entry from the store.
What Can Be Stored
useGPUStorage(() => ({
// Three.js storage textures
heightMap: new StorageTexture(512, 512),
voxelData: new Storage3DTexture(64, 64, 64),
// TSL storage texture nodes
storageNode: storageTexture(texture, uvNode, storeNode),
}))
Accessing in Node Creators
Both hooks expose their data to useNodes and useLocalNodes through the creator state's ScopedStore wrappers:
const { positions } = useBuffers(() => ({ positions: instancedArray(count, 'vec3') }), 'particles')
const { heightMap } = useGPUStorage(() => ({ heightMap: new StorageTexture(512, 512) }), 'terrain')
const { positionNode, heightNode } = useNodes(({ buffers, gpuStorage }) => ({
positionNode: buffers.scope('particles').positions.element(instanceIndex),
heightNode: texture(gpuStorage.scope('terrain').heightMap),
}))
Example: Particle System
import { useBuffers, useGPUStorage, useNodes, useLocalNodes } from '@react-three/tsl'
import { instancedArray, Fn, vec3, instanceIndex, texture } from 'three/tsl'
import { StorageTexture } from 'three/webgpu'
const PARTICLE_COUNT = 10000
function ParticleSystem() {
const { positions, velocities } = useBuffers(
() => ({
positions: instancedArray(PARTICLE_COUNT, 'vec3'),
velocities: instancedArray(PARTICLE_COUNT, 'vec3'),
}),
'particles',
)
const { forceField } = useGPUStorage(() => ({ forceField: new StorageTexture(256, 256) }), 'particles')
const { updateParticles } = useNodes(
({ buffers, gpuStorage }) => ({
updateParticles: Fn(() => {
const pos = buffers.scope('particles').positions.element(instanceIndex)
const vel = buffers.scope('particles').velocities.element(instanceIndex)
const uv = pos.xz.div(100).add(0.5)
const force = texture(gpuStorage.scope('particles').forceField, uv).xyz
vel.addAssign(force.mul(0.01))
pos.addAssign(vel)
})(),
}),
'compute',
)
const { positionNode } = useLocalNodes(
({ buffers }) => ({
positionNode: buffers.particles.positions.element(instanceIndex),
}),
[],
)
return (
<instancedMesh args={[undefined, undefined, PARTICLE_COUNT]}>
<sphereGeometry args={[0.1, 8, 8]} />
<meshBasicNodeMaterial positionNode={positionNode} />
</instancedMesh>
)
}
Best Practices
- Use scopes to group buffers/storage by system (
'particles','terrain'). - Call
disposeBuffers/disposeStoragein cleanup to free GPU resources. - Use
.scope('name')for safe access (returns an empty wrapper if the scope is missing); directbuffers.nameaccess works when you know the scope exists.
Related
- TSL Hooks - nodes and uniforms, plus the ScopedStore wrapper
- WebGPU Overview - concepts and architecture
- TSL HMR - rebuild behavior for buffers and storage