This guide will show you how to use the TypeGPU typed buffers to allow for type-safe and convenient read and write operations on WebGPU resources.
We will explore how to adapt existing WebGPU code to use the buffer API, and how to use it to create new WebGPU resources.
What are typed buffers?
TypeGPU provides a set of tools for WebGPU that allow you to create and manage typed buffers.
It enables reading and writing data to and from WebGPU buffers without having to manually translate the values to and from the raw byte arrays that WebGPU uses.
In the example above, we have a PlayerInfo struct that we want to store in a buffer. Using just the WebGPU API alone, we need to manually copy the data to the buffer,
create a staging buffer to read the data, and then manually copy the data back to a JavaScript object.
This process involves a lot of boilerplate code and introduces room for error.
Moreover, the example given is relatively simple. If we were dealing with a more complex nested structure,
manually calculating the offsets and sizes would be even more error-prone and time-consuming.
TypeGPU addresses this issue by providing a way to define the structure of the data we want to store in the buffer and then read and write the data using a type-safe API.
Using typed buffers
Creating a buffer
To create a typed buffer, you will need to define the buffer type using one of the data types provided by the typegpu/data module.
You can then create a buffer using the root.createBuffer function as demonstrated in the example above. However, that is not the only way to create a buffer.
Using buffer type
As in the example above, you can create a buffer using only the buffer type.
This will create a zero-initialized buffer (similar to creating a WebGPU buffer)
when the buffer is first accessed or used in TypeGPU read or write operations.
Using buffer type and initial value
You can also pass an initial value to the root.createBuffer function.
When the buffer is created, it will be mapped at creation, and the initial value will be written to the buffer.
Using an existing buffer
You can also create a buffer using an existing WebGPU buffer. This is useful when you have existing logic but want to introduce type-safe data operations.
Adding usage flags
When creating a buffer using the TypeGPU API, you can use the .$usage() builder method to add usage flags to the buffer.
You can also add all flags in a single $usage().
If you want to add specific usage flags, you can use the $addFlags(flags: GPUBufferUsageFlags) method.
Writing to a buffer
To write data to a buffer, you can use the write method on the typed buffer object. It takes as an argument the data that is to be written to the buffer.
Because the buffer is typed, the type hints will help you write the correct data.
If you pass a mapped buffer, the data will be written directly to the buffer (the buffer will not be unmapped).
If you pass an unmapped buffer, the data will be written to the buffer using GPUQueue.writeBuffer.
Reading from a buffer
To read data from a buffer, you can use the read method on the typed buffer.
It returns a promise that resolves to the data read from the buffer.
Adapting existing code
Let’s take a look at how we can supercharge our existing WebGPU code with typed buffers.
Starting point
The above code is a simple example of how we might update a buffer holding two RGB colors.
The size of the data type vec3f is 12 bytes, so one might think that a structure containing two vec3f would be 24 bytes.
However, the size of the buffer is 32 bytes because of the alignment requirements of the WebGPU API.
This is something the user needs to know and calculate manually.
Let’s see how we can improve this code using typed buffers.
Soft migration
In this approach we will not remove the existing buffer, but wrap it with root.createBuffer() to allow for type-safe read and write operations.
This approach is fully backwards compatible and allows you to gradually migrate your code.
Add the necessary imports and wrap the existing buffer:
The exisitng logic still works the same way but we can now take advantage of TypeGPU to read and write data to the buffer.
Full migration
In this approach we will remove the existing buffer and replace it with a typed buffer created using the TypeGPU API.
Keep in mind that root.createBuffer() will return a TgpuBuffer object, when you need a GPUBuffer object you can access it using the root.unwrap method.
If you want a more in-depth look at how to create your own data types, check out the chapter on Data Schemas.