Skip to content

Roots

Roots are responsible for resource allocation and management. Whether you’d like to wrap an existing WebGPU buffer with a typed shell or create a brand new buffer, roots are the place to start.

You can create a root using the tgpu.init function.

const root = await tgpu.init();

It requests a GPU device with default requirements. An optional parameter can be passed in with special requirements for the GPU device. If you already have a device that you want to use, you can pass it into tgpu.initFromDevice instead.

const root = tgpu.initFromDevice({ device });

To retrieve the device that is associated with a root, you can use the root.device property.

const device = root.device; // => GPUDevice
context.configure({
device,
format: presentationFormat,
alphaMode: 'premultiplied',
});

Creating resources

Every root.create* function creates a typed resource.

FunctionDescription
root.createBuffer
Creates a typed buffer with a given data-type and, optionally, an initial value. More information in the next chapter.

Unwrapping resources

There are times where a typed resource needs to be used by a vanilla WebGPU API. To retrieve the raw untyped value of a typed resource, use the root.unwrap function.

FunctionDescription
root.unwrap(resource: TgpuBuffer<AnyTgpuData>)Returns a GPUBuffer.
root.unwrap(resource: TgpuBindGroupLayout)Returns a GPUBindGroupLayout.
root.unwrap(resource: TgpuBindGroup)Returns a GPUBindGroup.

Destroying resources

Calling root.destroy() will destroy all resources created with it.

root.destroy(); // <- frees up all the resources

Best practices

Treat roots as their own separate universes, meaning resources created from the same root can interact with each other, while resources created by seperate roots can have a hard time interacting. This usually means creating just one root at the start of the program is the safest bet, but there are exceptions.

If you do not own the GPU device

If you cannot control the lifetime of the GPU device you are to use for computing/rendering, but are instead given the device in a lifecycle hook (e.g., react-native-wgpu), you can create a new root each time, as long as you recreate every resource as well.

example.tsx
import React from 'react';
function SceneView() {
const ref = useWebGPU(({ context, device, presentationFormat }) => {
const root = tgpu.initFromDevice({ device });
// create all resources...
});
// ...
}

If you pass the GPU device everywhere

It is common practice to pass a GPUDevice to classes or functions for them to allocate their required resources. At first glance, this poses a problem when trying to incorporate TypeGPU, since we would need to pass a root around instead of a device for all functionality that wants to move towards a typed API. We can create a global mapping between devices and roots to solve this.

You can copy and paste the utility below that implements a basic global cache for roots.

roots.ts
const deviceToRootMap = new WeakMap<GPUDevice, TgpuRoot>();
function getOrInitRoot(device: GPUDevice): TgpuRoot {
let root = deviceToRootMap.get(device);
if (!root) {
root = tgpu.initFromDevice({ device });
deviceToRootMap.set(device, root);
}
return root;
}

If you reuse the same getOrInitRoot function across code that has to create resources, the root will be shared across them.

class GameObject {
constructor(device: GPUDevice) {
const root = getOrInitRoot(device);
// create all resources...
}
}