Skip to content

Commit c29d569

Browse files
feat: New resolve api (#1897)
1 parent 28ce667 commit c29d569

File tree

26 files changed

+562
-341
lines changed

26 files changed

+562
-341
lines changed

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ const main = () => {
3131
return neighborhood(1.1, 0.5);
3232
};
3333

34-
const wgsl = tgpu.resolve({ externals: { main } });
34+
const wgsl = tgpu.resolve([main]);
3535
// ^? string
3636

3737
//

apps/bun-example/index.ts

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,6 @@ const createRandomBoid = tgpu.fn([], Boid)(() => {
1010
return { pos: randf.inUnitCube() };
1111
});
1212

13-
const shaderCode = tgpu.resolve({
14-
externals: { createRandomBoid },
15-
});
13+
const shaderCode = tgpu.resolve([createRandomBoid]);
1614

1715
console.log(shaderCode);

apps/typegpu-docs/src/content/docs/fundamentals/functions/index.mdx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,7 @@ const range = main();
6161
// ^?
6262

6363
// #2) Used to generate WGSL
64-
const wgsl = tgpu.resolve({ externals: { main } });
64+
const wgsl = tgpu.resolve([main]);
6565
// ^?
6666

6767
// #3) Executed on the GPU (generates WGSL underneath)
@@ -433,7 +433,7 @@ const getGradientColor = tgpu.fn([d.f32], d.vec4f)`(ratio) {
433433
You can see for yourself what `getGradientColor` resolves to by calling [`tgpu.resolve`](/TypeGPU/fundamentals/resolve), all relevant definitions will be automatically included:
434434

435435
```wgsl
436-
// results of calling tgpu.resolve({ externals: { getGradientColor } })
436+
// results of calling tgpu.resolve([getGradientColor])
437437
438438
fn getBlue_1() -> vec4f{
439439
return vec4f(0.114, 0.447, 0.941, 1);

apps/typegpu-docs/src/content/docs/fundamentals/resolve.mdx

Lines changed: 175 additions & 80 deletions
Original file line numberDiff line numberDiff line change
@@ -4,24 +4,170 @@ description: Resolve API can be used to extend shader code with WGSL definitions
44
---
55

66
Defining shader schemas and objects in JS/TS has lots of benefits, but having to keep them in sync with the corresponding WGSL code is hard to maintain.
7-
The `tgpu.resolve` API takes in a WGSL template, all TypeGPU schemas that you want to use in the shader, and generates a ready-to-use WGSL bundle.
7+
The `tgpu.resolve` API takes in TypeGPU resources (and optionally a WGSL template) and generates a ready-to-use WGSL bundle containing all transitive dependencies of the passed in objects.
88

99
:::note
1010
`tgpu.resolve` is essentially a dedicated TypeGPU linker.
1111
:::
1212

13+
## Resolving resources
14+
15+
The first `tgpu.resolve` overload takes in an array of items to resolve, and an optional argument with options. Here's a simple example:
16+
17+
```ts twoslash
18+
import tgpu from 'typegpu';
19+
import * as d from 'typegpu/data';
20+
// ---cut---
21+
const Boid = d.struct({
22+
size: d.f32,
23+
color: d.vec4f,
24+
});
25+
26+
const UnrelatedStruct = d.struct({ id: d.u32 });
27+
28+
const createSmallBoid = tgpu.fn([], Boid)(() => {
29+
return Boid({ size: 1, color: d.vec4f(0, 1, 0, 1) });
30+
});
31+
32+
const createBigBoid = tgpu.fn([], Boid)(() => {
33+
return Boid({ size: 10, color: d.vec4f(1, 0, 0, 1) });
34+
});
35+
36+
const resolved = tgpu.resolve([createSmallBoid, createBigBoid]);
37+
```
38+
39+
Resolved WGSL shader code is as follows:
40+
41+
```wgsl
42+
struct Boid_1 {
43+
size: f32,
44+
color: vec4f,
45+
}
46+
47+
fn createSmallBoid_0() -> Boid_1 {
48+
return Boid_1(1f, vec4f(0, 1, 0, 1));
49+
}
50+
51+
fn createBigBoid_2() -> Boid_1 {
52+
return Boid_1(10f, vec4f(1, 0, 0, 1));
53+
}
54+
```
55+
56+
As you may note, the resolved WGSL code contains exactly the set of definitions required for the objects passed in the argument to be valid.
57+
58+
You can also resolve other resources, like consts, buffers, textures, entry point functions or even entire pipelines. Here's an example with a pipeline:
59+
60+
```ts twoslash
61+
import tgpu from 'typegpu';
62+
import * as d from 'typegpu/data';
63+
// ---cut---
64+
const root = await tgpu.init();
65+
66+
const dataMutable = root
67+
.createMutable(d.arrayOf(d.u32, 8), [1, 2, 3, 4, 5, 6, 7, 8]);
68+
69+
const computeMain = tgpu['~unstable'].computeFn({
70+
workgroupSize: [1],
71+
in: { gid: d.builtin.globalInvocationId },
72+
})((input) => {
73+
const index = input.gid.x;
74+
dataMutable.$[index] *= 2;
75+
});
76+
77+
const multiplyPipeline = root['~unstable']
78+
.withCompute(computeMain)
79+
.createPipeline();
80+
81+
const resolved = tgpu.resolve([multiplyPipeline]);
82+
```
83+
84+
Resolved WGSL shader code is as follows:
85+
86+
```wgsl
87+
@group(0) @binding(0) var<storage, read_write> dataMutable_1: array<u32, 8>;
88+
89+
struct computeMain_Input_2 {
90+
@builtin(global_invocation_id) gid: vec3u,
91+
}
92+
93+
@compute @workgroup_size(1) fn computeMain_0(input: computeMain_Input_2) {
94+
var index = input.gid.x;
95+
dataMutable_1[index] *= 2u;
96+
}
97+
```
98+
99+
:::note
100+
If you already are using TypeGPU pipelines,
101+
the `.draw()` and `.dispatchWorkgroups()` methods perform the resolution and shader compilation under the hood,
102+
eliminating the need for manual resolve calls.
103+
:::
104+
105+
:::tip
106+
To resolve all items from a layout, you can use `tgpu.resolve([...Object.values(layout.bound)])`.
107+
:::
108+
109+
110+
## Resolving with a template
111+
112+
The second `tgpu.resolve` overload has only one argument -- an object with options, that requires two extra props: `template` and `externals`.
113+
The goal of this overload is to extend the existing WGSL code provided as `template`, with additional definitions from `externals`.
114+
115+
:::tip
116+
This variant of `tgpu.resolve` can often be omitted by using [WGSL-implemented functions](/TypeGPU/fundamentals/functions/#implementing-functions-in-wgsl).
117+
Use it when you are already provided with existing WGSL code and want to extend it.
118+
:::
119+
13120
Here's an example:
14121

15122
```ts twoslash
16123
import tgpu from 'typegpu';
17124
import * as d from 'typegpu/data';
18125

19-
const LightSource = d
20-
.struct({
21-
ambientColor: d.vec3f,
22-
intensity: d.f32,
23-
})
24-
.$name('Source');
126+
const root = await tgpu.init();
127+
// ---cut---
128+
const timeUniform = root.createUniform(d.u32);
129+
const rangeUniform = root.createUniform(d.f32);
130+
const offsetConst = tgpu.const(d.f32, 7);
131+
132+
const resolved = tgpu.resolve({
133+
template: /* wgsl */ `
134+
fn calculateEffectStrength() -> f32 {
135+
return sin(buffers.time) * buffers.range + offset;
136+
}`,
137+
externals: {
138+
offset: offsetConst,
139+
buffers: {
140+
time: timeUniform,
141+
range: rangeUniform,
142+
},
143+
},
144+
});
145+
```
146+
147+
Resolved WGSL shader code is as follows:
148+
149+
```wgsl
150+
const offsetConst_0: f32 = 7f;
151+
@group(0) @binding(0) var<uniform> timeUniform_1: u32;
152+
@group(0) @binding(1) var<uniform> rangeUniform_2: f32;
153+
154+
fn calculateEffectStrength() -> f32 {
155+
return sin(timeUniform_1) * rangeUniform_2 + offsetConst_0;
156+
}
157+
```
158+
159+
As you may note, in this case `tgpu.resolve` uses the structure of the `externals` property to detect that `buffers.time`, `buffers.range` and `offset` need to be replaced, and that their definitions should be included.
160+
161+
Here is another example. Assume, that the bind group index `0` is already taken by some existing code:
162+
163+
```ts twoslash
164+
import tgpu from 'typegpu';
165+
import * as d from 'typegpu/data';
166+
// ---cut---
167+
const LightSource = d.struct({
168+
ambientColor: d.vec3f,
169+
intensity: d.f32,
170+
}).$name('Source');
25171
// ^ giving the struct an explicit name (optional)
26172

27173
const layout = tgpu
@@ -30,29 +176,30 @@ const layout = tgpu
30176
sampling: { sampler: 'filtering' },
31177
bgTexture: { externalTexture: d.textureExternal() },
32178
})
33-
.$idx(0);
34-
// ^ forces code-gen to assign `0` as the group index (optional)
179+
.$idx(1);
180+
// ^ forces code-gen to assign `1` as the group index (optional)
35181

36182
const rawShader = /* wgsl */ `
37-
@fragment
38-
fn main(@location(0) uv: vec2f) -> @location(0) vec4f {
39-
var bgColor = textureSampleBaseClampToEdge(bgTexture, sampling, uv).rgb;
183+
<existing code>
40184
41-
var newSource: LightSource;
42-
newSource.ambientColor = (bgColor + lightSource.ambientColor) * factor;
43-
newSource.intensity = 0.6;
185+
@fragment
186+
fn main(@location(0) uv: vec2f) -> @location(0) vec4f {
187+
var bgColor = textureSampleBaseClampToEdge(layout.$.bgTexture, layout.$.sampling, uv).rgb;
44188
45-
return vec4f(newSource.ambientColor, newSource.intensity);
46-
}
189+
var newSource: LightSource;
190+
newSource.ambientColor = (bgColor + layout.$.lightSource.ambientColor) * factor;
191+
newSource.intensity = 0.6;
192+
193+
return vec4f(newSource.ambientColor, newSource.intensity);
194+
}
47195
`;
48196

49197
const resolved = tgpu.resolve({
50198
template: rawShader,
51199
externals: {
52-
// mapping names in the template to corresponding resources/values
53200
LightSource,
54201
factor: d.vec3f(0.4, 0.6, 1.0),
55-
...layout.bound,
202+
layout,
56203
},
57204
});
58205
```
@@ -65,79 +212,27 @@ struct Source_0 {
65212
intensity: f32,
66213
}
67214
68-
@group(0) @binding(0) var<uniform> lightSource_1: Source_0;
69-
@group(0) @binding(1) var sampling_2: sampler;
70-
@group(0) @binding(2) var bgTexture_3: texture_external;
215+
@group(1) @binding(2) var bgTexture_1: texture_external;
216+
@group(1) @binding(1) var sampling_2: sampler;
217+
@group(1) @binding(0) var<uniform> lightSource_3: Source_0;
218+
219+
<existing code>
71220
72221
@fragment
73222
fn main(@location(0) uv: vec2f) -> @location(0) vec4f {
74-
var bgColor = textureSampleBaseClampToEdge(bgTexture_3, sampling_2, uv).rgb;
223+
var bgColor = textureSampleBaseClampToEdge(bgTexture_1, sampling_2, uv).rgb;
75224
76-
var newSource: Source_0; // identifiers for references are generated based on the chosen naming scheme
77-
newSource.ambientColor = (bgColor + lightSource_1.ambientColor) * vec3f(0.4, 0.6, 1);
225+
var newSource: Source_0;
226+
newSource.ambientColor = (bgColor + lightSource_3.ambientColor) * vec3f(0.4000000059604645, 0.6000000238418579, 1);
78227
newSource.intensity = 0.6;
79228
80229
return vec4f(newSource.ambientColor, newSource.intensity);
81230
}
82231
```
83232

84-
:::tip
85-
If you wish to resolve just a single TGSL function and its dependencies, you can call resolve with that function in the externals and with no template.
86-
87-
```ts twoslash
88-
import tgpu from 'typegpu';
89-
import * as d from 'typegpu/data';
90-
// ---cut---
91-
const multiply = tgpu.fn([d.u32], d.u32)((n) => 2 * n);
92-
93-
const resolved = tgpu.resolve({ externals: { multiply } });
94-
95-
console.log(resolved);
96-
// fn multiply_0(n: u32) -> u32{
97-
// return (2 * n);
98-
// }
99-
```
100-
101-
You can also resolve TypeGPU schemas or even entire pipelines the same way.
102-
:::
103-
104-
## Template
105-
106-
This optional property of the `tgpu.resolve` function argument is a string containing WGSL code, that is meant to be extended with additional definitions.
107-
It can contain references to objects passed in the `externals` record.
108-
109-
## Externals
110-
111-
This is a record with TypeGPU objects that are to be included in the final resolved shader code.
112-
The values in the mapping are the objects themselves, while the keys are the names by which they are referenced in the template code.
113-
Each object is resolved to its WGSL declaration, which is included in the final shader code.
114-
Moreover, each reference to the object in the template is replaced with the name used in its newly generated declaration.
115-
116-
If an object is being referenced only by another TypeGPU object in *externals*, it doesn't have to be included in the record.
117-
Any passed-in object's dependencies are automatically resolved and included in the final result.
118-
119-
:::note
120-
To resolve bindings you can access each entry of a [bindGroupLayout](/TypeGPU/fundamentals/bind-groups) via the `layout.bound` property.
121-
122-
```ts
123-
externals: {
124-
lightSource: layout.bound.lightSource,
125-
sampling: layout.bound.sampling,
126-
bgTexture: layout.bound.bgTexture,
127-
}
128-
129-
// As long as the names in the shader match the
130-
// layout keys, it can be shortened to:
131-
externals: {
132-
...layout.bound,
133-
}
134-
```
135-
136-
:::
137-
138233
## Naming scheme
139234

140-
When externals are being resolved, they are given new names based on the specified naming scheme (`names` parameter).
235+
When items are being resolved, they are given new unique names based on their name in code and the specified naming scheme (`names` parameter in options).
141236

142237
The default naming scheme is `"random"`.
143238
It uses labels assigned to the objects via `.$name("foo")` method or, if they aren't present, the keys in the *externals* record.

apps/typegpu-docs/src/content/docs/fundamentals/utils.mdx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -124,7 +124,7 @@ Those might be useful for `tgpu.resolve`, since you cannot resolve a guarded pip
124124

125125
```ts
126126
const innerPipeline = doubleUpPipeline.with(bindGroup1).pipeline;
127-
tgpu.resolve({ externals: { innerPipeline } });
127+
tgpu.resolve([innerPipeline]);
128128
```
129129
:::
130130

apps/typegpu-docs/src/content/docs/fundamentals/variables.mdx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ const increment = tgpu['~unstable'].computeFn({
3030

3131
const pipeline = root['~unstable'].withCompute(increment).createPipeline();
3232

33-
console.log(tgpu.resolve({ externals: { pipeline } }));
33+
console.log(tgpu.resolve([pipeline]));
3434
```
3535

3636
```wgsl

apps/typegpu-docs/src/examples/algorithms/mnist-inference/index.ts

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -425,12 +425,7 @@ export const controls = {
425425
'Test Resolution': import.meta.env.DEV && {
426426
onButtonClick: () =>
427427
[defaultCompute, subgroupCompute]
428-
.map((fn) =>
429-
tgpu.resolve({
430-
externals: { fn },
431-
enableExtensions: ['subgroups'],
432-
})
433-
)
428+
.map((fn) => tgpu.resolve([fn], { enableExtensions: ['subgroups'] }))
434429
.map((r) => root.device.createShaderModule({ code: r })),
435430
},
436431
};

apps/typegpu-docs/src/examples/algorithms/probability/index.ts

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -96,11 +96,7 @@ export const controls = {
9696
onButtonClick() {
9797
c.distributions
9898
.map((dist) =>
99-
tgpu.resolve({
100-
externals: {
101-
f: executor.cachedPipeline(getPRNG(dist).prng),
102-
},
103-
})
99+
tgpu.resolve([executor.cachedPipeline(getPRNG(dist).prng)])
104100
)
105101
.map((r) => root.device.createShaderModule({ code: r }));
106102
},

0 commit comments

Comments
 (0)