Sharing a device with an engine

Building and tracing on the device your renderer already uses, in three.js, Babylon.js and plain WebGPU.

Buffers belong to one GPUDevice. To trace from the engine's shaders, or from compute passes beside them, build the trees on the engine's own device. The core takes any GPUDevice and holds no engine reference.

three.js

three's WebGPURenderer makes its device in init(). deviceFromRenderer(renderer) returns renderer.backend.device, and throws for the WebGL backend or before init(). deviceFromRendererAsync(renderer) awaits init() first.

import { WebGPURenderer } from 'three/webgpu';
import { deviceFromRenderer } from '@pmndrs/bvh/three';

const adapter = await navigator.gpu.requestAdapter();
// three asks for every adapter feature (subgroups included), but WebGPU's default limits:
// a scene past 128 MiB of triangles needs the adapter's limits.
const renderer = new WebGPURenderer({
    requiredLimits: adapter ? { maxStorageBufferBindingSize: adapter.limits.maxStorageBufferBindingSize, maxBufferSize: adapter.limits.maxBufferSize } : undefined,
});
await renderer.init();
const device = deviceFromRenderer(renderer); // renderer.backend.device; throws on the WebGL backend
  • three requests every adapter feature, subgroups included, so the GPU builders work on three's device where the adapter has subgroups.
  • three asks for WebGPU's default limits. A scene past 128 MiB of triangles needs requiredLimits, as above.
  • Record SceneTrees.update(encoder) in your own encoder and submit it before three's frame uses the trees.
  • To read the trees from TSL or a three wgslFn, wrap them in BvhTraceNodes. It binds SceneTrees' own GPUBuffers to three's storage nodes, with no copy (BvhTraceNodes). That relies on three's backend internals, which is why three is pinned to 0.187.

Vanilla WebGPU

Make the device yourself (Installation), and bind SceneTrees.buffers (or uploadTraceScene's buffers) to your passes. The walk's bind group layout entries come from traceModuleEntries(). Tracing in WGSL has a full kernel.

Another library on the same device (a GI technique, a denoiser) can bind the same buffers. SceneTrees.version tells every user when to rebind.

Babylon.js

WebGPUEngine has no public accessor for its device: deviceFromEngine(engine) from @pmndrs/bvh/babylon reads engine._device (checked), and bindTraceBuffers binds SceneTrees' buffers into a Babylon ComputeShader with no copy. That reaches into Babylon's internals, which is why it is pinned to Babylon 9 and kept thin: see Babylon.js. Babylon asks for WebGPU's default limits and no optional features, so create the engine with { enableAllFeatures: true, setMaximumLimits: true } to get subgroups and big storage buffers.

Other engines

Anything that gives you a GPUDevice and the vertex positions works: SceneTrees.addMesh({ positions, indices }) and addInstance({ mesh, objectToWorld }) with a column-major 4x4 matrix, as most engines store one.