Skip to content

Commit 6e6b659

Browse files
committed
Merge branch 'release'
2 parents 7d84965 + dd5cadc commit 6e6b659

File tree

14 files changed

+20154
-145
lines changed

14 files changed

+20154
-145
lines changed

apps/typegpu-docs/public/assets/phong/teapot.obj

Lines changed: 19332 additions & 0 deletions
Large diffs are not rendered by default.

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

Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -332,3 +332,82 @@ const indexBuffer = root
332332

333333
```
334334

335+
## Binding buffers
336+
337+
To use a buffer in a shader, it needs to be bound using a bind group.
338+
There are two approaches provided by TypeGPU.
339+
340+
### Manual binding
341+
342+
The default option is to create bind group layouts and bind your buffers via bind groups.
343+
Read more in the chapter dedicated to [bind groups](/TypeGPU/fundamentals/bind-groups).
344+
345+
```ts twoslash
346+
import tgpu from 'typegpu';
347+
import * as d from 'typegpu/data';
348+
349+
const root = await tgpu.init();
350+
// ---cut---
351+
const pointsBuffer = root
352+
.createBuffer(d.arrayOf(d.vec2i, 100))
353+
.$usage('storage');
354+
355+
const bindGroupLayout = tgpu.bindGroupLayout({
356+
points: { storage: d.arrayOf(d.vec2i, 100), access: 'mutable' },
357+
});
358+
359+
const bindGroup = root
360+
.createBindGroup(bindGroupLayout, { points: pointsBuffer });
361+
362+
const mainCompute = tgpu['~unstable'].computeFn({
363+
in: { gid: d.builtin.globalInvocationId },
364+
workgroupSize: [1],
365+
})((input) => {
366+
// Access and modify the bound buffer via the layout
367+
bindGroupLayout.$.points[input.gid[0]] = d.vec2i(1, 2);
368+
});
369+
370+
const pipeline = root['~unstable']
371+
.withCompute(mainCompute)
372+
.createPipeline();
373+
374+
pipeline
375+
.with(bindGroupLayout, bindGroup)
376+
.dispatchWorkgroups(100);
377+
```
378+
379+
### Using fixed resources
380+
381+
For scenarios where buffers remain consistent across multiple compute or render operations,
382+
TypeGPU offers fixed resources that appear bindless from a code perspective.
383+
Fixed buffers are created using dedicated root methods.
384+
385+
| WGSL type | TypeGPU constructor |
386+
|----------------------------|-------------------------|
387+
| `var<uniform>` | `root.createUniform()` |
388+
| `var<storage, read>` | `root.createReadonly()` |
389+
| `var<storage, read_write>` | `root.createMutable()` |
390+
391+
```ts twoslash
392+
import tgpu from 'typegpu';
393+
import * as d from 'typegpu/data';
394+
395+
const root = await tgpu.init();
396+
// ---cut---
397+
const pointsBuffer = root.createMutable(d.arrayOf(d.vec2i, 100));
398+
399+
const mainCompute = tgpu['~unstable'].computeFn({
400+
in: { gid: d.builtin.globalInvocationId },
401+
workgroupSize: [1],
402+
})((input) => {
403+
// Access and modify the fixed buffer directly
404+
pointsBuffer.$[input.gid[0]] = d.vec2i();
405+
});
406+
407+
const pipeline = root['~unstable'].withCompute(mainCompute).createPipeline();
408+
pipeline.dispatchWorkgroups(100);
409+
```
410+
411+
TypeGPU automatically generates a "catch-all" bind group and populates it with the fixed resources.
412+
413+
You can also use [`resolveWithContext`](/TypeGPU/fundamentals/resolve/#resolvewithcontext) to access the automatically generated bind group and layout containing your fixed resources.

apps/typegpu-docs/src/examples/rendering/disco/index.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -104,7 +104,6 @@ export const controls = {
104104
}[value];
105105
if (patternIndex !== undefined) {
106106
currentPipeline = pipelines[patternIndex];
107-
render();
108107
}
109108
},
110109
},
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
<canvas data-fit-to-container></canvas>
Lines changed: 210 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,210 @@
1+
import tgpu from 'typegpu';
2+
import * as d from 'typegpu/data';
3+
import * as std from 'typegpu/std';
4+
import * as p from './params.ts';
5+
import {
6+
ExampleControls,
7+
ModelVertexInput,
8+
modelVertexLayout,
9+
ModelVertexOutput,
10+
} from './schemas.ts';
11+
import { loadModel } from './load-model.ts';
12+
import { Camera, setupOrbitCamera } from './setup-orbit-camera.ts';
13+
14+
// setup
15+
const canvas = document.querySelector('canvas') as HTMLCanvasElement;
16+
const context = canvas.getContext('webgpu') as GPUCanvasContext;
17+
const presentationFormat = navigator.gpu.getPreferredCanvasFormat();
18+
const root = await tgpu.init();
19+
20+
context.configure({
21+
device: root.device,
22+
format: presentationFormat,
23+
alphaMode: 'premultiplied',
24+
});
25+
26+
// model (https://j5boom.itch.io/utah-teapot-obj)
27+
const model = await loadModel(root, '/TypeGPU/assets/phong/teapot.obj');
28+
29+
// camera
30+
const cameraUniform = root.createUniform(Camera);
31+
32+
const { cleanupCamera } = setupOrbitCamera(
33+
canvas,
34+
{
35+
initPos: d.vec4f(-10, 4, -8, 1),
36+
target: d.vec4f(0, 1, 0, 1),
37+
minZoom: 8,
38+
maxZoom: 40,
39+
},
40+
(updates) => cameraUniform.writePartial(updates),
41+
);
42+
43+
// shaders
44+
const exampleControlsUniform = root.createUniform(
45+
ExampleControls,
46+
p.initialControls,
47+
);
48+
49+
export const vertexShader = tgpu['~unstable'].vertexFn({
50+
in: { ...ModelVertexInput.propTypes, instanceIndex: d.builtin.instanceIndex },
51+
out: ModelVertexOutput,
52+
})((input) => {
53+
const worldPosition = d.vec4f(input.modelPosition, 1);
54+
const camera = cameraUniform.$;
55+
56+
const canvasPosition = camera.projection.mul(camera.view).mul(worldPosition);
57+
58+
return {
59+
worldPosition: input.modelPosition,
60+
worldNormal: input.modelNormal,
61+
canvasPosition: canvasPosition,
62+
};
63+
});
64+
65+
// see https://gist.github.com/chicio/d983fff6ff304bd55bebd6ff05a2f9dd
66+
export const fragmentShader = tgpu['~unstable'].fragmentFn({
67+
in: ModelVertexOutput,
68+
out: d.vec4f,
69+
})((input) => {
70+
const lightColor = std.normalize(exampleControlsUniform.$.lightColor);
71+
const lightDirection = std.normalize(exampleControlsUniform.$.lightDirection);
72+
// fixed color, can be replaced with texture sample
73+
const ambientColor = exampleControlsUniform.$.ambientColor;
74+
const ambientStrength = exampleControlsUniform.$.ambientStrength;
75+
const specularStrength = exampleControlsUniform.$.specularExponent;
76+
77+
// ambient component
78+
const ambient = ambientColor.mul(ambientStrength);
79+
80+
// diffuse component
81+
const cosTheta = std.dot(input.worldNormal, lightDirection);
82+
const diffuse = lightColor.mul(std.max(0, cosTheta));
83+
84+
// specular component
85+
const reflectionDirection = std.reflect(
86+
lightDirection.mul(-1),
87+
input.worldNormal,
88+
);
89+
const viewDirection = std.normalize(
90+
cameraUniform.$.position.xyz.sub(input.worldPosition),
91+
);
92+
const specular = lightColor.mul(
93+
std.pow(
94+
std.max(0, std.dot(reflectionDirection, viewDirection)),
95+
specularStrength,
96+
),
97+
);
98+
99+
// add the components up
100+
const color = ambient.add(diffuse).add(specular);
101+
return d.vec4f(color, 1);
102+
});
103+
104+
// pipelines
105+
const renderPipeline = root['~unstable']
106+
.withVertex(vertexShader, modelVertexLayout.attrib)
107+
.withFragment(fragmentShader, { format: presentationFormat })
108+
.withDepthStencil({
109+
format: 'depth24plus',
110+
depthWriteEnabled: true,
111+
depthCompare: 'less',
112+
})
113+
.withPrimitive({ topology: 'triangle-list' })
114+
.createPipeline();
115+
116+
let depthTexture = root.device.createTexture({
117+
size: [canvas.width, canvas.height, 1],
118+
format: 'depth24plus',
119+
usage: GPUTextureUsage.RENDER_ATTACHMENT,
120+
});
121+
122+
// frame
123+
let frameId: number;
124+
function frame() {
125+
renderPipeline
126+
.withColorAttachment({
127+
view: context.getCurrentTexture().createView(),
128+
clearValue: [
129+
p.backgroundColor.x,
130+
p.backgroundColor.y,
131+
p.backgroundColor.z,
132+
1,
133+
],
134+
loadOp: 'clear',
135+
storeOp: 'store',
136+
})
137+
.withDepthStencilAttachment({
138+
view: depthTexture.createView(),
139+
depthClearValue: 1,
140+
depthLoadOp: 'clear',
141+
depthStoreOp: 'store',
142+
})
143+
.with(modelVertexLayout, model.vertexBuffer)
144+
.draw(model.polygonCount);
145+
146+
frameId = requestAnimationFrame(frame);
147+
}
148+
frameId = requestAnimationFrame(frame);
149+
150+
// #region Example controls and cleanup
151+
export const controls = {
152+
'light color': {
153+
initial: [...p.initialControls.lightColor],
154+
onColorChange(value: readonly [number, number, number]) {
155+
exampleControlsUniform.writePartial({ lightColor: d.vec3f(...value) });
156+
},
157+
},
158+
'light direction': {
159+
min: [-10, -10, -10],
160+
max: [10, 10, 10],
161+
initial: [...p.initialControls.lightDirection],
162+
step: [0.01, 0.01, 0.01],
163+
onVectorSliderChange(v: [number, number, number]) {
164+
exampleControlsUniform.writePartial({ lightDirection: d.vec3f(...v) });
165+
},
166+
},
167+
'ambient color': {
168+
initial: [...p.initialControls.ambientColor],
169+
onColorChange(value: readonly [number, number, number]) {
170+
exampleControlsUniform.writePartial({ ambientColor: d.vec3f(...value) });
171+
},
172+
},
173+
'ambient strength': {
174+
min: 0,
175+
max: 1,
176+
initial: p.initialControls.ambientStrength,
177+
step: 0.01,
178+
onSliderChange(v: number) {
179+
exampleControlsUniform.writePartial({ ambientStrength: v });
180+
},
181+
},
182+
'specular exponent': {
183+
min: 0.5,
184+
max: 16,
185+
initial: p.initialControls.specularExponent,
186+
step: 0.1,
187+
onSliderChange(v: number) {
188+
exampleControlsUniform.writePartial({ specularExponent: v });
189+
},
190+
},
191+
};
192+
193+
const resizeObserver = new ResizeObserver(() => {
194+
depthTexture.destroy();
195+
depthTexture = root.device.createTexture({
196+
size: [canvas.width, canvas.height, 1],
197+
format: 'depth24plus',
198+
usage: GPUTextureUsage.RENDER_ATTACHMENT,
199+
});
200+
});
201+
resizeObserver.observe(canvas);
202+
203+
export function onCleanup() {
204+
cancelAnimationFrame(frameId);
205+
cleanupCamera();
206+
resizeObserver.unobserve(canvas);
207+
root.destroy();
208+
}
209+
210+
// #endregion
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
import { load } from '@loaders.gl/core';
2+
import { OBJLoader } from '@loaders.gl/obj';
3+
import type { TgpuRoot } from 'typegpu';
4+
import * as d from 'typegpu/data';
5+
import { modelVertexLayout } from './schemas.ts';
6+
7+
export async function loadModel(
8+
root: TgpuRoot,
9+
modelPath: string,
10+
) {
11+
const modelMesh = await load(modelPath, OBJLoader);
12+
const polygonCount = modelMesh.attributes.POSITION.value.length / 3;
13+
14+
const vertexBuffer = root
15+
.createBuffer(modelVertexLayout.schemaForCount(polygonCount))
16+
.$usage('vertex')
17+
.$name(`model vertices of ${modelPath}`);
18+
19+
const modelVertices = [];
20+
for (let i = 0; i < polygonCount; i++) {
21+
modelVertices.push({
22+
modelPosition: d.vec3f(
23+
modelMesh.attributes.POSITION.value[3 * i],
24+
modelMesh.attributes.POSITION.value[3 * i + 1],
25+
modelMesh.attributes.POSITION.value[3 * i + 2],
26+
),
27+
modelNormal: d.vec3f(
28+
modelMesh.attributes.NORMAL.value[3 * i],
29+
modelMesh.attributes.NORMAL.value[3 * i + 1],
30+
modelMesh.attributes.NORMAL.value[3 * i + 2],
31+
),
32+
});
33+
}
34+
modelVertices.reverse();
35+
36+
vertexBuffer.write(modelVertices);
37+
38+
return {
39+
vertexBuffer,
40+
polygonCount,
41+
};
42+
}
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
{
2+
"title": "Phong Reflection Model",
3+
"category": "rendering",
4+
"tags": ["experimental", "3d"]
5+
}
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
import * as d from 'typegpu/data';
2+
import { ExampleControls } from './schemas.ts';
3+
4+
export const backgroundColor = d.vec3f(28, 28, 28).div(255);
5+
6+
export const initialControls = ExampleControls({
7+
lightColor: d.vec3f(1, 0.7, 0),
8+
lightDirection: d.vec3f(0, 7, -7),
9+
ambientColor: d.vec3f(0.6, 0.6, 0.6),
10+
ambientStrength: 0.5,
11+
specularExponent: 8,
12+
});
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
import tgpu from 'typegpu';
2+
import * as d from 'typegpu/data';
3+
4+
// schemas
5+
6+
export const ModelVertexInput = d.struct({
7+
modelPosition: d.vec3f,
8+
modelNormal: d.vec3f,
9+
});
10+
11+
export const ModelVertexOutput = {
12+
worldPosition: d.vec3f,
13+
worldNormal: d.vec3f,
14+
canvasPosition: d.builtin.position,
15+
} as const;
16+
17+
export const ExampleControls = d.struct({
18+
lightColor: d.vec3f,
19+
lightDirection: d.vec3f,
20+
ambientColor: d.vec3f,
21+
ambientStrength: d.f32,
22+
specularExponent: d.f32,
23+
});
24+
25+
// layouts
26+
27+
export const modelVertexLayout = tgpu.vertexLayout(d.arrayOf(ModelVertexInput));

0 commit comments

Comments
 (0)