--- name: r3f-v10-webgpu-hooks description: Choose, implement, or debug React Three Fiber v10 WebGPU hooks for TSL uniforms, node graphs, buffers, storage textures, texture loading, and render pipelines. Use for R3F v10 hook migrations and resource lifecycle issues. --- # R3F v10 WebGPU hooks Use this reference in an existing R3F v10 project. Inspect the target's installed `@react-three/fiber`, `three`, and Three typings before changing code; alpha APIs and lifecycle behavior differ from upstream docs. Resolve dependencies from the target workspace, then inspect Fiber's `dist/webgpu/index.d.ts` and `index.mjs` for the relevant hook. Do not upgrade packages or migrate a WebGL canvas merely to apply this skill. Import scene hooks from `@react-three/fiber/webgpu`, Three classes/types from `three/webgpu`, and shader expressions from `three/tsl`. Call scene hooks beneath the matching Canvas. The WebGPU entry already types `state.renderer` as `WebGPURenderer`; no cast is needed. ## Choose the hook | Hook | Use and call shape | | ------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `useUniform` | One named root uniform: `useUniform('uTime', 0)`. `useUniform('uTime')` reads an existing uniform and throws if missing. Names share state; this is not a component-local uniform. | | `useUniforms` | Related or instance-scoped uniforms: `useUniforms({ uAmount: 0 }, scope)` or `useUniforms(creator, scope)`. Read with `useUniforms(scope)`; omit arguments to read the registry. | | `useNodes` | Register shared TSL graphs: `useNodes(creator, scope)`. Read with `useNodes(scope)` or `useNodes()`. Reserve it for graphs with actual shared consumers. | | `useLocalNodes` | Compose a component's material/compute nodes: `useLocalNodes(creator)`. Returns the creator's object without registering it. Can include non-node helpers when needed. | | `useBuffers` | Register buffer resources: `useBuffers(creator, scope)`; read with `useBuffers(scope)` or `useBuffers()`. Creator returns named attributes, typed arrays, or supported TSL storage nodes. | | `useGPUStorage` | Register storage textures: `useGPUStorage(creator, scope)`; read with `useGPUStorage(scope)` or `useGPUStorage()`. Use Three storage texture objects or supported storage nodes. | | `useTexture` | Suspense texture loading: `useTexture(urlOrArrayOrRecord, { onLoad, cache })`. Returns plain Three textures in the input shape, not TSL nodes. | | `useTextures` | Reactive texture registry: `useTextures(registry => registry.get(key))`. Does not load or suspend; a missing key returns `undefined`. | | `useRenderPipeline` | One declarative postprocessing pipeline per root: `useRenderPipeline(mainCB, setupCB)`. No arguments gives access to the root pipeline state. | Keep CPU layouts, scratch vectors, matrices, and static source arrays in ordinary React memoization. A memo is not a disposal mechanism. Do not move private, correctly owned `uniform()` objects into a shared registry without a reason. ## Uniforms and node composition Name uniforms with the `uName` convention, such as `uAmount` or `uTime`. Update hook-returned uniforms through `.value`; mutate vector/color values with `.set()` or `.copy()` when appropriate. Use effects for prop changes and `useFrame` for animation. Avoid React state per frame and refs that retain an obsolete uniform after a rebuild. For repeated instances, use a stable unique scope. When deriving one from `useId()`, prefix a letter and strip non-alphanumeric characters: `const scope = 'effect' + useId().replace(/[^a-zA-Z0-9]/g, '')`. Avoid generated WGSL names containing `__` or beginning with a digit. Creator callbacks receive `CreatorState`. Its `uniforms`, `nodes`, `buffers`, and `gpuStorage` are scoped wrappers: `uniforms.uTime` reads a root entry and `uniforms.scope('effect').uAmount` reads a scoped entry. `textures` is a Map of plain textures, so read with `textures.get(key)` and wrap with TSL `texture(...)` when sampling. Mount `Parent` beneath the Canvas. It creates and updates `uAmount` in the `scene` scope; `Child` reads that same uniform inside `useLocalNodes`, without passing it through props. This example assumes one parent; repeated independent parents need unique scopes. ```tsx 'use client' import { useFrame, useLocalNodes, useUniforms } from '@react-three/fiber/webgpu' import type { FC } from 'react' import { normalLocal, positionLocal } from 'three/tsl' import type { UniformNode } from 'three/webgpu' type Uniforms = { uAmount: UniformNode<'float', number> } const Parent: FC = () => { const { uAmount } = useUniforms({ uAmount: 0 }, 'scene') useFrame(({ elapsed }) => { uAmount.value = Math.sin(elapsed) * 0.1 }) return } const Child: FC = () => { const { positionNode } = useLocalNodes(({ uniforms }) => { const { uAmount } = uniforms.scope('scene') return { positionNode: positionLocal.add(normalLocal.mul(uAmount)) } }) return ( ) } ``` - Inline local creators are fine for small graphs; they may run again on each React render. Stabilize expensive creators with `useCallback` and include every captured graph input in its dependencies. - Changing a uniform value does not rebuild a graph. Compose with the uniform node, not a captured `.value`. Use TSL `select` or `If` inside `Fn` for GPU-time branching; a JavaScript `if` selects a graph at creation time. - Creator registration can be staged until a layout effect. Reader hooks in another component can initially return an empty scope. Check readiness before using external entries; a TypeScript generic does not prove registration happened. Keep hooks unconditional and establish the resource owner before consumers. - Infer types from creators. Use reader-mode schema generics and `.scope(name)` only at shared boundaries; import `CreatorState`, `UniformNode`, and other library types instead of redefining them. - `useNodes` is a registry, not a dependency-array memo. Verify rebuild semantics before expecting prop or callback changes to replace registered graphs. ## Buffers, storage, and rebuilds ```tsx // Inside a scene component; count and instanceKey are supplied by its owner: const scope = `particles${instanceKey}Count${count}` const createBuffers = useCallback( () => ({ positions: instancedArray(count, 'vec4'), }), [count], ) const { positions } = useBuffers(createBuffers, scope) // Import instancedArray from three/tsl and useBuffers from the WebGPU entry. // Compute: positions.element(instanceIndex); rendering: positions.toAttribute().xyz. const createStorage = useCallback( () => ({ field: new StorageTexture(width, height), }), [width, height], ) const storage = useGPUStorage(createStorage, `field${instanceKey}W${width}H${height}`) // Import StorageTexture from three/webgpu and useGPUStorage from the WebGPU entry. ``` These are registration examples, not complete ownership/cleanup implementations. Use scopes that distinguish independent owners and every allocation-defining input (count, dimensions, layout, and source generation when applicable). Initialize replacement buffers before compute or drawing reads them, and rebind dependent graphs to the replacement resources. | Hook family | Returned lifecycle utilities | | ---------------- | ---------------------------------------------------------------------------------------------------- | | Uniforms / nodes | `removeUniforms` / `removeNodes`, `clearUniforms` / `clearNodes`, `rebuildUniforms` / `rebuildNodes` | | Buffers | `removeBuffers`, `clearBuffers`, `rebuildBuffers`, `disposeBuffers` | | GPU storage | `removeStorage`, `clearStorage`, `rebuildStorage`, `disposeStorage` | Pass the scope explicitly to lifecycle utilities, e.g. `disposeStorage(['field'], scope)`; do not assume they inherit the creating hook's scope. Removal, cache invalidation, and GPU disposal are different operations. Unscoped clear/rebuild can affect other consumers. Do not dispose shared resources from an arbitrary reader. Rebuilds can replace resource identities. Refresh material/compute graphs and callbacks that captured the outgoing generation. A compute node's disposal does not imply its storage allocation was released; inspect the underlying attribute/texture and renderer ownership path. **For alpha.5 or uncertain lifecycle behavior, read [references/alpha-5.md](references/alpha-5.md) before implementing replacement or teardown.** It records implementation limits that the generic API descriptions miss. ## Texture loading and sharing `useTexture('/albedo.png', { onLoad: texture => { /* configure texture */ } })` loads through Suspense and enrolls by URL by default. Array and record inputs preserve their return shape. Configure shared textures consistently: changing color space or wrapping changes the object other consumers use. `cache: false` opts out of registry enrollment; it does not guarantee a fresh, uncached loader result. Use `useTexture.preload(url)` to preload and `useTexture.clear(url)` to clear the loader cache. Cache clearing is not GPU disposal. Registry access uses `get`, `has`, `add`, `dispose`, and `disposeAll`; `add` supports procedural/render-target textures and belongs in owner lifecycle code, not as an unconditional render side effect. Registry reads do not themselves load or guarantee an ownership reference. Mounted `useTexture` consumers do not subscribe to later registry replacements; use `useTextures(r => r.get(key))` for that. `dispose(key)` respects active loader consumers unless forced; `disposeAll()` is a whole-registry teardown operation. Coordinate resource ownership rather than forcing disposal from a reader. ## Render pipelines `useRenderPipeline(mainCB, setupCB)` calls **setup before main**. Configure MRT on `passes.scenePass` in setup; compose its texture nodes into `renderPipeline.outputNode` in main. The return includes `isReady`, nullable `renderPipeline`, `passes`, `rebuild`, `reset`, and `clearPasses`. - Use one configuring owner per root. R3F's default render job renders the pipeline; registering a `useFrame` job in the `render` phase takes over rendering and disables that default job. - Callback identity or captured prop changes alone do not rebuild the graph in alpha.5. Update numeric uniforms directly; call `rebuild()` for structural changes. Check `isReady` before using the returned pipeline. - The hook persists across unmount. In alpha.5, `reset()` clears root state and disposes the cached scene pass, but not the pipeline or custom passes/effects. The configuring owner must release what it owns; `clearPasses()` alone is not disposal. - Keep explicit Three `RenderPipeline` ownership when several independent pipelines or manual switching are required. Do not introduce competing root owners. ## Other application hooks and prop helpers | API | Relevant v10 behavior | | --------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `useFrame(callback, options)` | Timing is `state.delta` / `state.elapsed` in seconds, `state.time` in milliseconds, and `state.frame`; there is no `state.clock`. Options support phase, job ordering, FPS throttling, and enabled state; returns scheduler controls. Prefer explicit options to legacy numeric priority. | | `useThree(selector)` | Reactive root selection, e.g. `s => s.renderer`. Deep mutation of Three objects does not trigger React updates. In demand mode, request a frame with `invalidate()` after imperative visual changes. | | `useStore()` | The Canvas Zustand store for imperative reads/subscriptions. Unsubscribe listeners you create. | | `useLoader(Loader, input, extensions?, onProgress?, cacheKey?)` | Suspense asset loading with shared cache; supports preload/clear. Do not dispose a cached asset still used elsewhere. | | `useEnvironment(options)` | Loads environment textures using files/path/preset options; loading alone does not assign the scene environment. | | `useRenderTarget(options)` | Renderer-compatible target, canvas-sized by default; overloads accept a square size or width/height. Inspect resizing/disposal behavior before adding another owner. | | `useGraph(object)` | Memoized named object/material lookup from an Object3D; unrelated to TSL node registries. | | `useInstanceHandle(ref)` | Escape hatch into R3F reconciler internals; use only when public APIs cannot do the job. | | `useBridge`, `useMutableCallback`, `useIsomorphicLayoutEffect` | Infrastructure exports, not WebGPU resource hooks. Preserve existing integration usage; do not build resource ownership on these helpers. | `once` and `fromRef` are prop helpers, not hooks. `translate={once(0, 0, 1)}` on a declarative geometry applies a mount-only transform; reconstruction runs it again. It does not memoize allocations. `target={fromRef(targetRef)}` resolves a ref-dependent prop after the ref is populated; it is not a per-frame subscription. Check installed JSX typings, especially for generic `bufferGeometry` transforms. ## Verify the change Run the target's type-check and narrowest relevant lint/runtime check. For resource changes, exercise two independent instances, an unrelated re-render, allocation resize/replacement, unmount/remount, and fast refresh if supported. Verify both rendered/compute results and actual cleanup; type-checking alone cannot prove GPU memory was released. Avoid material keys based on newly created node UUIDs, which force avoidable remounts. Upstream references: [TSL hooks](https://github.com/pmndrs/react-three-fiber/blob/v10/docs/webgpu/tsl-hooks.mdx), [render pipeline](https://github.com/pmndrs/react-three-fiber/blob/v10/docs/webgpu/render-pipeline.mdx), [core hooks](https://github.com/pmndrs/react-three-fiber/blob/v10/docs/API/hooks.mdx), [v10 migration](https://github.com/pmndrs/react-three-fiber/blob/v10/docs/migration/v10.mdx), [TSL HMR](https://github.com/pmndrs/react-three-fiber/blob/v10/docs/webgpu/hmr.mdx). Prefer installed implementation and types when the moving `v10` docs disagree.