Skip to content

Commit 7bd4563

Browse files
committed
docs: TypeGPU 0.11 Blog Post
1 parent 826c689 commit 7bd4563

2 files changed

Lines changed: 329 additions & 0 deletions

File tree

145 KB
Loading
Lines changed: 329 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,329 @@
1+
---
2+
title: TypeGPU 0.11
3+
date: 2026-04-14
4+
tags:
5+
- Release notes
6+
authors:
7+
- name: Iwo Plaza
8+
title: TypeGPU developer
9+
picture: https://avatars.githubusercontent.com/u/7166752?s=200
10+
cover:
11+
alt: A collage of examples introduced alongside TypeGPU 0.11
12+
image: banner.webp
13+
---
14+
15+
Hello fellow GPU enthusiast!
16+
17+
Over the past 2 months, my team and I have been pulling on a few threads that we thought would improve TypeGPU in terms of efficiency, and as a byproduct, we actually made the APIs more convenient. We are also introducing a _lint plugin_ to further improve the diagnostics and feedback you receive while writing TypeGPU shaders, on top of the type safety we already provide.
18+
19+
- [New examples](#new-examples)
20+
- [Migration guide](#migration-guide)
21+
- [New and improved Write APIs](#efficient-write-apis)
22+
- [Efficient write APIs](#efficient-write-apis)
23+
- [A better partial write](#a-better-partial-write)
24+
25+
26+
We have been pulling a few more threads than I mentioned here.. but for those, you'll have to wait for the next blog post 🤐.
27+
28+
## New examples
29+
30+
My teammate Konrad Reczko [(@reczkok)](https://github.com/reczkok) has outdone himself again, and delivered 3 new examples that push TypeGPU APIs to their limits:
31+
32+
* ["Genetic Racing"](https://typegpu.com/examples/#example=algorithms--genetic-racing) - watch a swarm of cars learn to traverse a procedurally generated race track.
33+
* ["Mesh Skinning"](https://typegpu.com/examples/#example=simple--mesh-skinning) - an animation and skinning system built from scratch in TypeGPU.
34+
* ["Parallax Occlusion Mapping"](https://typegpu.com/examples/#example=rendering--pom) - squeezing amazing depth out of just two triangles and a set of textures.
35+
36+
## Migration guide
37+
38+
### Deprecated APIs
39+
40+
The `buffer.writePartial` API is being deprecated in favor of `buffer.patch` [(and here are the reasons why)](#a-better-partial-write).
41+
To migrate, simply replace any partial write of arrays in the form of `[{ idx: 2, value: foo }, /* ... */]` with `{ 2: foo, /* ... */ }`.
42+
43+
```diff lang=ts
44+
const buffer = root.createBuffer(d.arrayOf(d.vec3f, 5)).$usage('storage');
45+
46+
- buffer.writePartial([{ idx: 2, value: d.vec3f(1, 2, 3) }]);
47+
+ buffer.patch({ 2: d.vec3f(1, 2, 3) });
48+
```
49+
50+
### Stabilizing textures and samplers
51+
52+
One by one, we're making our APIs available without the `['~unstable']` prefix, and this time around, it's **textures** and **samplers**.
53+
Just drop the unstable prefix, and you're good to go.
54+
55+
```diff lang=ts
56+
- const sampler = root['~unstable'].createSampler({
57+
+ const sampler = root.createSampler({
58+
magFilter: 'linear',
59+
minFilter: 'linear',
60+
});
61+
62+
- const texture = root['~unstable'].createTexture({
63+
+ const texture = root.createTexture({
64+
size: [256, 256],
65+
format: 'rgba8unorm' as const,
66+
}).$usage('sampled');
67+
```
68+
69+
## New and improved write APIs
70+
71+
### Efficient data
72+
73+
When writing to a buffer with an array of vectors, it's no longer required to create vector instances (e.g. `d.vec3f()`).
74+
```ts
75+
const positionsMutable = root.createMutable(d.arrayOf(d.vec3f, 3));
76+
77+
// existing overload
78+
positionsMutable.write([d.vec3f(0, 1, 2), d.vec3f(3, 4, 5), d.vec3f(6, 7, 8)]);
79+
// new overloads ⚡
80+
positionsMutable.write([[0, 1, 2], [3, 4, 5], [6, 7, 8]]); // tuples
81+
positionsMutable.write(new Float32Array([0, 1, 2, 0, 3, 4, 5, 0, 6, 7, 8, 0])); // typed arrays (mind the padding)
82+
// and more...
83+
```
84+
Each one is more efficient than the previous, so you can choose the appropriate API for your efficiency needs.
85+
[More about these new APIs here](https://docs.swmansion.com/TypeGPU/fundamentals/buffers/#writing-to-a-buffer).
86+
87+
### A better partial write
88+
89+
When writing to a buffer, we require the passed in value to exactly match the schema. This specifically means that updating a single field of a single array item was very costly. The `buffer.partialWrite` API remedied that by accepting partial records
90+
for structs, and a list of indices and values to update in arrays. This works fine, but doesn't compose well with more complex data structures:
91+
92+
```ts
93+
const Node = d.struct({
94+
color: d.vec3f,
95+
// Indices of neighboring nodes
96+
neighbors: d.arrayOf(d.u32, 4),
97+
});
98+
99+
const nodes = root.createUniform(d.arrayOf(Node, 100));
100+
101+
// Updating the 50th node
102+
nodes.writePartial([
103+
{
104+
idx: 50,
105+
value: {
106+
color: d.vec3f(1, 0, 1),
107+
// We cannot pass [48, 49, 51, 52], as we could with nodes.write()
108+
neighbors: [{ idx: 0, value: 48 }, { idx: 0, value: 49 }, { idx: 0, value: 51 }, { idx: 0, value: 52 }],
109+
},
110+
}
111+
]);
112+
```
113+
114+
If we loosened the type to accept either partial arrays or full arrays, then we would reach an ambiguity in the following case:
115+
116+
```ts
117+
const Foo = d.struct({
118+
idx: d.u32,
119+
value: d.f32,
120+
});
121+
122+
const foos = root.createUniform(d.arrayOf(Foo, 2));
123+
124+
foos.writePartial([{ idx: 1, value: /* ... */ }, { idx: 0, value: /* ... */ }]);
125+
```
126+
127+
We could traverse the value deeper to disambiguate, but for the sake of efficiency and being able to reuse optimizations added to `buffer.write` by [Konrad](https://github.com/reczkok), we chose to add a new API:
128+
129+
```diff lang=ts
130+
- foos.writePartial([{ idx: 1, value: /* ... */ }, { idx: 0, value: /* ... */ }]);
131+
+ foos.patch({ 1: /* ... */, 0: /* ... */ });
132+
```
133+
134+
[You can read more about .patch in the Buffers guide](/TypeGPU/fundamentals/buffers/#patching-buffers).
135+
136+
### Writing struct-of-arrays (SoA) data
137+
138+
When the buffer schema is an `array<struct<...>>`, you can write the data in a struct-of-arrays form with `writeSoA` from `typegpu/common`. This is useful when your CPU-side data is already stored per-field, such as simulation attributes kept in separate typed arrays.
139+
140+
```ts
141+
const Particle = d.struct({
142+
pos: d.vec3f,
143+
vel: d.f32,
144+
});
145+
146+
const particleBuffer = root.createBuffer(d.arrayOf(Particle, 2));
147+
148+
common.writeSoA(particleBuffer, {
149+
pos: new Float32Array([
150+
1, 2, 3,
151+
4, 5, 6,
152+
]),
153+
vel: new Float32Array([10, 20]),
154+
});
155+
```
156+
157+
[More about this API can be found in the Buffers guide.](/TypeGPU/fundamentals/buffers/#writing-struct-of-arrays-soa-data)
158+
159+
## Shader code ergonomics
160+
161+
There's been a lot of improvements to our shader generation, mainly in regards to compile-time execution and pruning of unreachable branches.
162+
I will highlight some of them in the following sections.
163+
164+
### std.range
165+
166+
The new `std.range` function works similarly to `range()` in Python, and returns an array that can be iterated over.
167+
When combined with `tgpu.unroll`, it's now very easy to produce a set amount of code blocks.
168+
169+
```ts
170+
let result = d.u32();
171+
for (const i of tgpu.unroll(std.range(3))) {
172+
// this block will be inlined 3 times
173+
result += i * 10;
174+
}
175+
```
176+
177+
Generates:
178+
```wgsl
179+
var result = 0u;
180+
// unrolled iteration #0
181+
{
182+
result += 0u;
183+
}
184+
// unrolled iteration #1
185+
{
186+
result += 10u;
187+
}
188+
// unrolled iteration #2
189+
{
190+
result += 20u;
191+
}
192+
````
193+
194+
Because `i` is known at compile-time, the `i * 10` get evaluated and injected into the generated code in each block.
195+
For more, refer to [tgpu.unroll documentation.](/TypeGPU/fundamentals/utils/#tgpuunroll).
196+
197+
### Boolean logic
198+
199+
Logical expressions are now short-circuited if we can determine the result early.
200+
```ts
201+
const clampingEnabled = tgpu.accessor(d.bool);
202+
203+
function lerp(a: number, b: number, t: number) {
204+
'use gpu';
205+
let value = a + (b - a) * t;
206+
if (clampingEnabled.$ && (value > 1 || value < 0)) {
207+
value = std.saturate(value);
208+
}
209+
return value;
210+
}
211+
```
212+
213+
Generated WGSL depending on the value of `clampingEnabled`:
214+
215+
```wgsl
216+
// clampingEnabled.$ === false
217+
fn lerp(a: f32, b: f32, t: f32) -> f32 {
218+
var value = a + (b - a) * t;
219+
return value;
220+
}
221+
222+
// clampingEnabled.$ === true
223+
fn lerp(a: f32, b: f32, t: f32) -> f32 {
224+
var value = a + (b - a) * t;
225+
if (value > 1 || value < 0) {
226+
value = std.saturate(value);
227+
}
228+
return value;
229+
}
230+
```
231+
232+
### Convenience overload for `tgpu.const` API
233+
234+
If you're defining a WGSL constant using an array schema, you no longer have to duplicate the array length both in the value and in the schema.
235+
The `tgpu.const` function now accepts dynamically-sized schemas.
236+
237+
```diff lang=ts
238+
const ColorStops = d.arrayOf(d.vec3f);
239+
240+
const colorStops = tgpu.const(
241+
- ColorStops(3),
242+
+ ColorStops,
243+
[d.vec3f(1, 0, 0), d.vec3f(0, 1, 0), d.vec3f(0, 0, 1)],
244+
);
245+
```
246+
247+
## Ecosystem updates
248+
249+
There have been a lot of work outside of the `typegpu` package, both internally and from the community.
250+
251+
### Lint plugin
252+
253+
Aleksander Katan ([@aleksanderkatan](https://github.com/aleksanderkatan)) has been working behind the scenes on an ESLint/Oxlint plugin, capable of catching user errors that types cannot.
254+
255+
```ts
256+
import tgpu, { d } from 'typegpu';
257+
258+
function increment(n: number) {
259+
'use gpu';
260+
return n++;
261+
// ^^^
262+
// Cannot assign to 'n' since WGSL parameters are immutable.
263+
// If you're using d.ref, please either use '.$' or disable this rule
264+
}
265+
266+
function createBoid() {
267+
'use gpu';
268+
const boid = { pos: d.vec2f(), size: 1 };
269+
// ^^^^^^^^^^^^^^^^^^^^^^^^^^^
270+
// { pos: d.vec2f(), size: 1 } must be wrapped in a schema call
271+
return boid;
272+
}
273+
274+
function clampTo0(n: number) {
275+
'use gpu';
276+
let result;
277+
// ^^^^^^
278+
// 'result' must have an initial value
279+
if (n < 0) {
280+
result = 0;
281+
} else {
282+
result = n;
283+
}
284+
return result;
285+
}
286+
```
287+
288+
[For setup instructions and available rules, refer to the documentation](/TypeGPU/tooling/eslint-plugin-typegpu/)
289+
290+
### New @typegpu/color helpers
291+
292+
There are three new helper functions importable from `@typegpu/color` which can be called at compile-time to create
293+
color vectors from hexadecimal strings: `hexToRgb`, `hexToRgba` and `hexToOklab`.
294+
295+
```ts
296+
import { hexToRgb } from '@typegpu/color';
297+
298+
function getGradientColor(t: number) {
299+
'use gpu';
300+
const from = hexToRgb('#FF00FF');
301+
const to = hexToRgb('#00FF00');
302+
return std.mix(from, to, t);
303+
}
304+
```
305+
306+
Generated WGSL:
307+
```wgsl
308+
fn getGradientColor(t: f32) -> f32 {
309+
var from = d.vec3f(1, 0, 1);
310+
var to = d.vec3f(0, 1, 0);
311+
return mix(from, to, t);
312+
}
313+
````
314+
315+
### Bundler plugin rewrite
316+
317+
The `unplugin-typegpu` package is what enables TypeScript shaders, and to support its continued development, we rewrote it from
318+
the ground up. It should now support more bundlers than ever before, out of the box, including `esbuild`.
319+
320+
### Motion GPU
321+
322+
A minimalist WebGPU framework called [Motion GPU](https://motion-gpu.dev/) introduced a way to integrate with TypeGPU, and wrote about it
323+
in their documentation [(Integrations / TypeGPU)](https://motion-gpu.dev/docs/integrations-typegpu). It's awesome to see the continued adoption
324+
of TypeGPU in other ecosystems and communities 🎉
325+
326+
## What's next?
327+
328+
There are many more things introduced in TypeGPU 0.11 that I haven't mentioned. If you're curious, you can
329+
read [the full 0.11.0 changelog](https://github.com/software-mansion/TypeGPU/compare/v0.10.2...v0.11.0).

0 commit comments

Comments
 (0)