Skip to content

Commit 79aacfd

Browse files
authored
fix: Shellless attribs (#2241)
1 parent 3d59036 commit 79aacfd

6 files changed

Lines changed: 252 additions & 153 deletions

File tree

apps/typegpu-docs/src/examples/simulation/confetti/index.ts

Lines changed: 44 additions & 85 deletions
Original file line numberDiff line numberDiff line change
@@ -21,15 +21,10 @@ const context = root.configureContext({ canvas, alphaMode: 'premultiplied' });
2121

2222
// data types
2323

24-
const VertexOutput = {
25-
position: d.builtin.position,
26-
color: d.vec4f,
27-
};
28-
2924
const ParticleGeometry = d.struct({
25+
color: d.vec4f,
3026
tilt: d.f32,
3127
angle: d.f32,
32-
color: d.vec4f,
3328
});
3429

3530
const ParticleData = d.struct({
@@ -43,120 +38,80 @@ const ParticleData = d.struct({
4338
const particleGeometryBuffer = root
4439
.createBuffer(
4540
d.arrayOf(ParticleGeometry, PARTICLE_AMOUNT),
46-
Array(PARTICLE_AMOUNT)
47-
.fill(0)
48-
.map(() => ({
49-
angle: Math.floor(Math.random() * 50) - 10,
50-
tilt: Math.floor(Math.random() * 10) - 10 - 10,
51-
color: COLOR_PALETTE[Math.floor(Math.random() * COLOR_PALETTE.length)],
52-
})),
41+
Array.from({ length: PARTICLE_AMOUNT }, () => ({
42+
color: COLOR_PALETTE[Math.floor(Math.random() * COLOR_PALETTE.length)],
43+
tilt: Math.floor(Math.random() * 10) - 10 - 10,
44+
angle: Math.floor(Math.random() * 50) - 10,
45+
})),
5346
)
5447
.$usage('vertex');
5548

5649
const particleDataBuffer = root
5750
.createBuffer(d.arrayOf(ParticleData, PARTICLE_AMOUNT))
5851
.$usage('storage', 'uniform', 'vertex');
5952

53+
let elapsedTime = 0;
6054
const aspectRatio = root.createUniform(d.f32, canvas.width / canvas.height);
6155
const deltaTime = root.createUniform(d.f32);
62-
const time = root.createMutable(d.f32);
56+
const time = root.createUniform(d.f32);
6357

6458
const particleDataStorage = particleDataBuffer.as('mutable');
6559

6660
// layouts
6761

6862
const geometryLayout = tgpu.vertexLayout(d.arrayOf(ParticleGeometry), 'instance');
69-
7063
const dataLayout = tgpu.vertexLayout(d.arrayOf(ParticleData), 'instance');
7164

7265
// functions
7366

74-
const rotate = tgpu.fn(
75-
[d.vec2f, d.f32],
76-
d.vec2f,
77-
)((v, angle) => {
78-
const pos = d.vec2f(
67+
const rotate = (v: d.v2f, angle: number) => {
68+
'use gpu';
69+
return d.vec2f(
7970
v.x * std.cos(angle) - v.y * std.sin(angle),
8071
v.x * std.sin(angle) + v.y * std.cos(angle),
8172
);
82-
83-
return pos;
84-
});
85-
86-
const mainVert = tgpu.vertexFn({
87-
in: {
88-
tilt: d.f32,
89-
angle: d.f32,
90-
color: d.vec4f,
91-
center: d.vec2f,
92-
index: d.builtin.vertexIndex,
93-
},
94-
out: VertexOutput,
95-
}) /* wgsl */ `{
96-
let width = in.tilt;
97-
let height = in.tilt / 2;
98-
99-
var pos = rotate(array<vec2f, 4>(
100-
vec2f(0, 0),
101-
vec2f(width, 0),
102-
vec2f(0, height),
103-
vec2f(width, height),
104-
)[in.index] / 350, in.angle) + in.center;
105-
106-
if (aspectRatio < 1) {
107-
pos.x /= aspectRatio;
108-
} else {
109-
pos.y *= aspectRatio;
110-
}
111-
112-
return Out(vec4f(pos, 0.0, 1.0), in.color);
113-
}`.$uses({
114-
rotate,
115-
aspectRatio,
116-
});
117-
118-
const mainFrag = tgpu.fragmentFn({
119-
in: VertexOutput,
120-
out: d.vec4f,
121-
}) /* wgsl */ `{ return in.color; }`;
122-
123-
const mainCompute = tgpu.computeFn({
124-
in: { gid: d.builtin.globalInvocationId },
125-
workgroupSize: [1],
126-
}) /* wgsl */ `{
127-
let index = in.gid.x;
128-
if index == 0 {
129-
time += deltaTime;
130-
}
131-
let phase = (time / 300) + particleData[index].seed;
132-
particleData[index].position += particleData[index].velocity * deltaTime / 20 + vec2f(sin(phase) / 600, cos(phase) / 500);
133-
}`.$uses({
134-
particleData: particleDataStorage,
135-
deltaTime,
136-
time,
137-
});
73+
};
13874

13975
// pipelines
14076

14177
const renderPipeline = root
14278
.createRenderPipeline({
143-
vertex: mainVert,
144-
fragment: mainFrag,
14579
attribs: {
146-
tilt: geometryLayout.attrib.tilt,
147-
angle: geometryLayout.attrib.angle,
148-
color: geometryLayout.attrib.color,
80+
...geometryLayout.attrib,
14981
center: dataLayout.attrib.position,
15082
},
83+
vertex: ({ tilt, angle, color, center, $vertexIndex }) => {
84+
'use gpu';
85+
const width = tilt / 350;
86+
const height = width / 2;
87+
88+
const local = [d.vec2f(0, 0), d.vec2f(width, 0), d.vec2f(0, height), d.vec2f(width, height)];
89+
const pos = rotate(local[$vertexIndex], angle) + center;
90+
91+
if (aspectRatio.$ < 1) {
92+
pos.x /= aspectRatio.$;
93+
} else {
94+
pos.y *= aspectRatio.$;
95+
}
15196

152-
primitive: {
153-
topology: 'triangle-strip',
97+
return { $position: d.vec4f(pos, 0, 1), color };
15498
},
99+
fragment: ({ color }) => {
100+
'use gpu';
101+
return color;
102+
},
103+
primitive: { topology: 'triangle-strip' },
155104
})
156105
.with(geometryLayout, particleGeometryBuffer)
157106
.with(dataLayout, particleDataBuffer);
158107

159-
const computePipeline = root.createComputePipeline({ compute: mainCompute });
108+
const computePipeline = root.createGuardedComputePipeline((index) => {
109+
'use gpu';
110+
const phase = time.$ / 300 + particleDataStorage.$[index].seed;
111+
particleDataStorage.$[index].position +=
112+
(particleDataStorage.$[index].velocity * deltaTime.$) / 20 +
113+
d.vec2f(std.sin(phase) / 600, std.cos(phase) / 500);
114+
});
160115

161116
// compute and draw
162117

@@ -192,11 +147,15 @@ function onFrame(loop: (deltaTime: number) => unknown) {
192147
}
193148

194149
onFrame((dt) => {
150+
elapsedTime += dt;
151+
time.write(elapsedTime);
195152
deltaTime.write(dt);
196153
aspectRatio.write(canvas.width / canvas.height);
197154

198-
computePipeline.dispatchWorkgroups(PARTICLE_AMOUNT);
155+
// Simulating the physics
156+
computePipeline.dispatchThreads(PARTICLE_AMOUNT);
199157

158+
// Drawing the particles
200159
renderPipeline.withColorAttachment({ view: context }).draw(4, PARTICLE_AMOUNT);
201160
});
202161

packages/typegpu/src/core/function/autoIO.ts

Lines changed: 16 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -54,16 +54,16 @@ type AutoFragmentFnImpl = (
5454
) => AutoFragmentOut<undefined | v4f | AnyAutoCustoms>;
5555

5656
/**
57-
* Only used internally
57+
* Only used internally. Meant to be recreated on every resolution.
5858
*/
5959
export class AutoFragmentFn implements SelfResolvable {
6060
// Prototype properties
6161
declare [$internal]: true;
6262
declare resourceType: 'auto-fragment-fn';
6363

6464
#core: FnCore;
65-
#autoIn: AutoStruct;
66-
#autoOut: AutoStruct;
65+
autoIn: AutoStruct;
66+
autoOut: AutoStruct;
6767

6868
constructor(
6969
impl: AutoFragmentFnImpl,
@@ -75,10 +75,10 @@ export class AutoFragmentFn implements SelfResolvable {
7575
setName(impl, 'fragmentFn');
7676
}
7777
this.#core = createFnCore(impl, '@fragment ');
78-
this.#autoIn = new AutoStruct({ ...builtinFragmentIn, ...varyings }, undefined, locations);
79-
setName(this.#autoIn, 'FragmentIn');
80-
this.#autoOut = new AutoStruct(builtinFragmentOut, vec4f);
81-
setName(this.#autoOut, 'FragmentOut');
78+
this.autoIn = new AutoStruct({ ...builtinFragmentIn, ...varyings }, undefined, locations);
79+
setName(this.autoIn, 'FragmentIn');
80+
this.autoOut = new AutoStruct(builtinFragmentOut, vec4f);
81+
setName(this.autoOut, 'FragmentOut');
8282
}
8383

8484
toString(): string {
@@ -87,7 +87,7 @@ export class AutoFragmentFn implements SelfResolvable {
8787

8888
[$resolve](ctx: ResolutionCtx): ResolvedSnippet {
8989
return ctx.withSlots([[shaderStageSlot, 'fragment']], () =>
90-
this.#core.resolve(ctx, [this.#autoIn], this.#autoOut),
90+
this.#core.resolve(ctx, [this.autoIn], this.autoOut),
9191
);
9292
}
9393
}
@@ -100,16 +100,16 @@ type AutoVertexFnImpl = (
100100
) => AutoVertexOut<AnyAutoCustoms>;
101101

102102
/**
103-
* Only used internally
103+
* Only used internally. Meant to be recreated on every resolution.
104104
*/
105105
export class AutoVertexFn implements SelfResolvable {
106106
// Prototype properties
107107
declare [$internal]: true;
108108
declare resourceType: 'auto-vertex-fn';
109109

110110
#core: FnCore;
111-
#autoIn: AutoStruct;
112-
#autoOut: AutoStruct;
111+
autoIn: AutoStruct;
112+
autoOut: AutoStruct;
113113

114114
constructor(
115115
impl: AutoVertexFnImpl,
@@ -121,10 +121,10 @@ export class AutoVertexFn implements SelfResolvable {
121121
setName(impl, 'vertexFn');
122122
}
123123
this.#core = createFnCore(impl, '@vertex ');
124-
this.#autoIn = new AutoStruct({ ...builtinVertexIn, ...attribs }, undefined, locations);
125-
setName(this.#autoIn, 'VertexIn');
126-
this.#autoOut = new AutoStruct(builtinVertexOut, undefined);
127-
setName(this.#autoOut, 'VertexOut');
124+
this.autoIn = new AutoStruct({ ...builtinVertexIn, ...attribs }, undefined, locations);
125+
setName(this.autoIn, 'VertexIn');
126+
this.autoOut = new AutoStruct(builtinVertexOut, undefined);
127+
setName(this.autoOut, 'VertexOut');
128128
}
129129

130130
toString(): string {
@@ -133,7 +133,7 @@ export class AutoVertexFn implements SelfResolvable {
133133

134134
[$resolve](ctx: ResolutionCtx): ResolvedSnippet {
135135
return ctx.withSlots([[shaderStageSlot, 'vertex']], () =>
136-
this.#core.resolve(ctx, [this.#autoIn], this.#autoOut),
136+
this.#core.resolve(ctx, [this.autoIn], this.autoOut),
137137
);
138138
}
139139
}

packages/typegpu/src/core/pipeline/renderPipeline.ts

Lines changed: 20 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import type { TgpuQuerySet } from '../../core/querySet/querySet.ts';
44
import { isBuiltin } from '../../data/attributes.ts';
55
import { type Disarray, getCustomLocation, type UndecorateRecord } from '../../data/dataTypes.ts';
66
import { sizeOf } from '../../data/sizeOf.ts';
7-
import { type ResolvedSnippet, snip, type Snippet } from '../../data/snippet.ts';
7+
import { type ResolvedSnippet, snip } from '../../data/snippet.ts';
88
import type {
99
WgslTexture,
1010
WgslTextureDepth2d,
@@ -951,14 +951,16 @@ class RenderPipelineCore implements SelfResolvable {
951951
readonly [$internal] = true;
952952
private _memo: Memo | undefined;
953953

954-
#latestFragmentOut: BaseData | undefined;
954+
#latestAutoVertexIn: TgpuVertexFn.In | undefined;
955+
#latestAutoFragmentOut: BaseData | undefined;
955956

956957
constructor(public readonly options: RenderPipelineCoreOptions) {}
957958

958959
[$resolve](ctx: ResolutionCtx): ResolvedSnippet {
959960
const { slotBindings } = this.options;
960961
const { vertex, fragment, attribs = {} } = this.options.descriptor;
961-
this.#latestFragmentOut = undefined;
962+
this.#latestAutoVertexIn = undefined;
963+
this.#latestAutoFragmentOut = undefined;
962964

963965
const locations = matchUpVaryingLocations(
964966
(vertex as TgpuVertexFn | undefined)?.shell?.out,
@@ -977,24 +979,24 @@ class RenderPipelineCore implements SelfResolvable {
977979
formatToWGSLType[value.format],
978980
]),
979981
);
980-
vertexOut = ctx.resolve(new AutoVertexFn(vertex, defaultAttribData, locations))
981-
.dataType as WgslStruct;
982+
const autoFn = new AutoVertexFn(vertex, defaultAttribData, locations);
983+
ctx.resolve(autoFn);
984+
this.#latestAutoVertexIn = autoFn.autoIn.completeStruct.propTypes;
985+
vertexOut = autoFn.autoOut.completeStruct;
982986
} else {
983987
vertexOut = ctx.resolve(vertex).dataType as WgslStruct;
984988
}
985989

986990
if (fragment) {
987-
let fragOut: Snippet;
988991
if (typeof fragment === 'function') {
989992
const varyings = Object.fromEntries(
990993
Object.entries(vertexOut.propTypes).filter(([, dataType]) => !isBuiltin(dataType)),
991994
);
992-
fragOut = ctx.resolve(new AutoFragmentFn(fragment, varyings, locations));
995+
const fragOut = ctx.resolve(new AutoFragmentFn(fragment, varyings, locations));
996+
this.#latestAutoFragmentOut = fragOut.dataType;
993997
} else {
994-
fragOut = ctx.resolve(fragment);
998+
ctx.resolve(fragment);
995999
}
996-
997-
this.#latestFragmentOut = fragOut.dataType as BaseData;
9981000
}
9991001
return snip('', Void, /* origin */ 'runtime');
10001002
}),
@@ -1056,11 +1058,16 @@ class RenderPipelineCore implements SelfResolvable {
10561058

10571059
const { vertex, fragment, attribs = {}, targets } = this.options.descriptor;
10581060
const connectedAttribs = connectAttributesToShader(
1059-
(vertex as TgpuVertexFn | undefined)?.shell?.in ?? {},
1061+
(vertex as TgpuVertexFn)?.shell?.in ?? this.#latestAutoVertexIn ?? {},
10601062
attribs,
10611063
);
10621064

1063-
const fragmentOut = (fragment as TgpuFragmentFn)?.shell?.returnType ?? this.#latestFragmentOut;
1065+
// If the fragment output is a single builtin, then this.#latestAutoFragmentOut doesn't
1066+
// retain that information, that's why we use that here and fallback to this.#latestAutoFragmentOut.
1067+
// One other reason is that if the shelled fragment has already been resolved in this namespace,
1068+
// then this.#latestAutoFragmentOut will be undefined.
1069+
const fragmentOut =
1070+
(fragment as TgpuFragmentFn)?.shell?.returnType ?? this.#latestAutoFragmentOut;
10641071
const connectedTargets = fragmentOut ? connectTargetsToShader(fragmentOut, targets) : [null];
10651072

10661073
const descriptor: GPURenderPipelineDescriptor = {
@@ -1114,7 +1121,7 @@ class RenderPipelineCore implements SelfResolvable {
11141121
catchall,
11151122
logResources,
11161123
usedVertexLayouts: connectedAttribs.usedVertexLayouts,
1117-
fragmentOut: this.#latestFragmentOut as BaseData,
1124+
fragmentOut: this.#latestAutoFragmentOut as BaseData,
11181125
};
11191126

11201127
if (PERF?.enabled) {

0 commit comments

Comments
 (0)