Skip to content

Commit 96ade4a

Browse files
committed
Better DX when creating pipelines
Awesome Blossom Works maybe? A few tweaks to make things work a bit more
1 parent c6f1ad1 commit 96ade4a

32 files changed

+711
-539
lines changed

apps/typegpu-docs/src/examples/image-processing/background-segmentation/index.ts

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -169,7 +169,7 @@ const drawWithMaskPipeline = root['~unstable']
169169
.with(paramsAccess, paramsUniform)
170170
.createRenderPipeline({
171171
vertex: common.fullScreenTriangle,
172-
fragment: (({ uv }) => {
172+
fragment: ({ uv }) => {
173173
'use gpu';
174174
const originalColor = std.textureSampleBaseClampToEdge(
175175
drawWithMaskLayout.$.inputTexture,
@@ -178,7 +178,7 @@ const drawWithMaskPipeline = root['~unstable']
178178
);
179179

180180
let blurredColor = d.vec4f();
181-
if (paramsAccessor.$.useGaussian === 1) {
181+
if (paramsAccess.$.useGaussian === 1) {
182182
blurredColor = std.textureSampleBaseClampToEdge(
183183
drawWithMaskLayout.$.inputBlurredTexture,
184184
drawWithMaskLayout.$.sampler,
@@ -189,11 +189,11 @@ const drawWithMaskPipeline = root['~unstable']
189189
drawWithMaskLayout.$.inputBlurredTexture,
190190
drawWithMaskLayout.$.sampler,
191191
uv,
192-
paramsAccessor.$.sampleBias,
192+
paramsAccess.$.sampleBias,
193193
);
194194
}
195195

196-
const cropBounds = paramsAccessor.$.cropBounds;
196+
const cropBounds = paramsAccess.$.cropBounds;
197197
const uvMin = cropBounds.xy;
198198
const uvMax = cropBounds.zw;
199199
const maskUV = d.vec2f(uv).sub(uvMin).div(uvMax.sub(uvMin));
@@ -211,7 +211,7 @@ const drawWithMaskPipeline = root['~unstable']
211211
const mask = std.select(0, sampledMask, inCropRegion);
212212

213213
return std.mix(blurredColor, originalColor, mask);
214-
}),
214+
},
215215
targets: { format: presentationFormat },
216216
});
217217

apps/typegpu-docs/src/examples/simple/oklab/index.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -129,7 +129,7 @@ function setPipeline({
129129
pipeline = root['~unstable']
130130
.with(patternSlot, outOfGamutPattern)
131131
.with(oklabGamutClipSlot, gamutClip)
132-
.with(oklabGamutClipAlphaAccess, alphaFromUniforms)
132+
.with(oklabGamutClipAlphaAccess, () => uniforms.$.alpha)
133133
.createRenderPipeline({
134134
vertex: common.fullScreenTriangle,
135135
fragment: mainFragment,

packages/typegpu/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,7 @@
6565
},
6666
"homepage": "https://typegpu.com",
6767
"devDependencies": {
68-
"@ark/attest": "^0.53.0",
68+
"@ark/attest": "^0.56.0",
6969
"@typegpu/tgpu-dev-cli": "workspace:*",
7070
"@webgpu/types": "catalog:types",
7171
"arktype": "catalog:",

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

Lines changed: 7 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,8 @@
1-
import { builtin } from '../../builtin.ts';
1+
import { builtin, type OmitBuiltins } from '../../builtin.ts';
22
import { AutoStruct } from '../../data/autoStruct.ts';
3-
import type { AnyData } from '../../data/dataTypes.ts';
43
import type { ResolvedSnippet } from '../../data/snippet.ts';
54
import { vec4f } from '../../data/vector.ts';
6-
import type { v4f } from '../../data/wgslTypes.ts';
5+
import type { BaseData, v4f } from '../../data/wgslTypes.ts';
76
import { getName, setName } from '../../shared/meta.ts';
87
import type {
98
InferGPU,
@@ -35,7 +34,7 @@ const builtinVertexOut = {
3534
} as const;
3635

3736
export type AutoVertexOut<T extends AnyAutoCustoms> =
38-
& T
37+
& OmitBuiltins<T>
3938
& Partial<InferGPURecord<typeof builtinVertexOut>>;
4039

4140
const builtinFragmentIn = {
@@ -62,7 +61,7 @@ export type AutoFragmentOut<T extends undefined | v4f | AnyAutoCustoms> =
6261
: T & Partial<InferGPURecord<typeof builtinFragmentOut>>;
6362

6463
type AutoFragmentFnImpl = (
65-
input: AutoFragmentIn<AnyAutoCustoms>,
64+
input: AutoFragmentIn<Record<string, never>>,
6665
) => AutoFragmentOut<undefined | v4f | AnyAutoCustoms>;
6766

6867
/**
@@ -80,7 +79,7 @@ export class AutoFragmentFn implements SelfResolvable {
8079

8180
constructor(
8281
impl: AutoFragmentFnImpl,
83-
varyings: Record<string, AnyData>,
82+
varyings: Record<string, BaseData>,
8483
locations?: Record<string, number> | undefined,
8584
) {
8685
this.impl = impl;
@@ -108,7 +107,7 @@ AutoFragmentFn.prototype[$internal] = true;
108107
AutoFragmentFn.prototype.resourceType = 'auto-fragment-fn';
109108

110109
type AutoVertexFnImpl = (
111-
input: AutoVertexIn<AnyAutoCustoms>,
110+
input: AutoVertexIn<Record<string, never>>,
112111
) => AutoVertexOut<AnyAutoCustoms>;
113112

114113
/**
@@ -126,7 +125,7 @@ export class AutoVertexFn implements SelfResolvable {
126125

127126
constructor(
128127
impl: AutoVertexFnImpl,
129-
attribs: Record<string, AnyData>,
128+
attribs: Record<string, BaseData>,
130129
locations?: Record<string, number> | undefined,
131130
) {
132131
this.impl = impl;

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

Lines changed: 15 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -6,32 +6,31 @@ import {
66
} from '../../data/attributes.ts';
77
import { isBuiltin } from '../../data/attributes.ts';
88
import { getCustomLocation, isData } from '../../data/dataTypes.ts';
9-
import { struct } from '../../data/struct.ts';
9+
import { INTERNAL_createStruct } from '../../data/struct.ts';
1010
import {
1111
type BaseData,
1212
isVoid,
1313
type Location,
1414
type WgslStruct,
1515
} from '../../data/wgslTypes.ts';
16-
import type { IOData, IOLayout, IORecord } from './fnTypes.ts';
1716

18-
export type WithLocations<T extends IORecord> = {
17+
export type WithLocations<T extends Record<string, BaseData>> = {
1918
[Key in keyof T]: IsBuiltin<T[Key]> extends true ? T[Key]
2019
: HasCustomLocation<T[Key]> extends true ? T[Key]
2120
: Decorate<T[Key], Location>;
2221
};
2322

24-
export type IOLayoutToSchema<T extends IOLayout> = T extends BaseData
25-
? Decorate<T, Location<0>>
26-
: T extends IORecord ? WgslStruct<WithLocations<T>>
23+
export type IOLayoutToSchema<T> = T extends BaseData
24+
? HasCustomLocation<T> extends true ? T : Decorate<T, Location<0>>
25+
: T extends Record<string, BaseData> ? WgslStruct<WithLocations<T>>
2726
// biome-ignore lint/suspicious/noConfusingVoidType: <it actually is void>
2827
: T extends { type: 'void' } ? void
2928
: never;
3029

31-
export function withLocations<T extends IOData>(
32-
members: IORecord<T> | undefined,
30+
export function withLocations<T extends BaseData>(
31+
members: Record<string, T> | undefined,
3332
locations: Record<string, number> = {},
34-
): WithLocations<IORecord<T>> {
33+
): Record<string, BaseData> {
3534
let nextLocation = 0;
3635
const usedCustomLocations = new Set<number>();
3736

@@ -69,9 +68,8 @@ export function withLocations<T extends IOData>(
6968
}
7069

7170
export function createIoSchema<
72-
T extends IOData,
73-
Layout extends IORecord<T> | IOLayout<T>,
74-
>(layout: Layout, locations: Record<string, number> = {}) {
71+
T extends BaseData | Record<string, BaseData>,
72+
>(layout: T, locations: Record<string, number> = {}) {
7573
return (
7674
isData(layout)
7775
? isVoid(layout)
@@ -81,6 +79,9 @@ export function createIoSchema<
8179
: getCustomLocation(layout) !== undefined
8280
? layout
8381
: location(0, layout)
84-
: struct(withLocations(layout, locations) as Record<string, T>)
85-
) as IOLayoutToSchema<Layout>;
82+
: INTERNAL_createStruct(
83+
withLocations(layout as Record<string, BaseData>, locations),
84+
/* isAbstruct */ false,
85+
)
86+
) as IOLayoutToSchema<T>;
8687
}

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

Lines changed: 51 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,13 @@ import type {
33
AnyFragmentOutputBuiltin,
44
OmitBuiltins,
55
} from '../../builtin.ts';
6+
import type { InstanceToSchema } from '../../data/instanceToSchema.ts';
67
import type { ResolvedSnippet } from '../../data/snippet.ts';
78
import type {
9+
BaseData,
810
Decorated,
911
Interpolate,
1012
Location,
11-
v4f,
1213
Vec4f,
1314
Vec4i,
1415
Vec4u,
@@ -20,7 +21,6 @@ import {
2021
setName,
2122
type TgpuNamable,
2223
} from '../../shared/meta.ts';
23-
import type { InferGPU } from '../../shared/repr.ts';
2424
import { $getNameForward, $internal, $resolve } from '../../shared/symbols.ts';
2525
import type { ResolutionCtx, SelfResolvable } from '../../types.ts';
2626
import { addReturnTypeToExternals } from '../resolve/externals.ts';
@@ -45,6 +45,10 @@ export type FragmentInConstrained = IORecord<
4545
| AnyFragmentInputBuiltin
4646
>;
4747

48+
export type FragmentInFromVertexOut<T> =
49+
& OmitBuiltins<{ [K in keyof T]: InstanceToSchema<T[K]> }>
50+
& Record<string, AnyFragmentInputBuiltin>;
51+
4852
type FragmentColorValue = Vec4f | Vec4i | Vec4u;
4953

5054
export type FragmentOutConstrained = IOLayout<
@@ -53,17 +57,12 @@ export type FragmentOutConstrained = IOLayout<
5357
| AnyFragmentOutputBuiltin
5458
>;
5559

56-
export type FragmentOutInferred =
57-
| undefined
58-
| v4f
59-
| Record<string, v4f | InferGPU<AnyFragmentOutputBuiltin>>;
60-
6160
/**
6261
* Describes a fragment entry function signature (its arguments, return type and targets)
6362
*/
6463
type TgpuFragmentFnShellHeader<
65-
FragmentIn extends FragmentInConstrained,
66-
FragmentOut extends FragmentOutConstrained,
64+
FragmentIn extends TgpuFragmentFn.In = TgpuFragmentFn.In,
65+
FragmentOut extends TgpuFragmentFn.Out = TgpuFragmentFn.Out,
6766
> = {
6867
readonly in: FragmentIn | undefined;
6968
readonly out: FragmentOut;
@@ -76,35 +75,42 @@ type TgpuFragmentFnShellHeader<
7675
* Allows creating tgpu fragment functions by calling this shell
7776
* and passing the implementation (as WGSL string or JS function) as the argument.
7877
*/
79-
export type TgpuFragmentFnShell<
80-
FragmentIn extends FragmentInConstrained,
81-
FragmentOut extends FragmentOutConstrained,
82-
> =
83-
& TgpuFragmentFnShellHeader<FragmentIn, FragmentOut> /**
78+
export interface TgpuFragmentFnShell<
79+
// We force the variance to be covariant, since shells are just containers of
80+
// schemas that conincidentally can be called to create a fragment function.
81+
// @ts-expect-error: We override the variance
82+
out TIn extends TgpuFragmentFn.In = TgpuFragmentFn.In,
83+
// @ts-expect-error: We override the variance
84+
out TOut extends TgpuFragmentFn.Out = TgpuFragmentFn.Out,
85+
> extends TgpuFragmentFnShellHeader<TIn, TOut> {
86+
/**
8487
* Creates a type-safe implementation of this signature
8588
*/
86-
& ((
89+
(
8790
implementation: (
88-
input: InferIO<FragmentIn>,
89-
out: FragmentOut extends IORecord ? WgslStruct<FragmentOut> : FragmentOut,
90-
) => InferIO<FragmentOut>,
91-
) => TgpuFragmentFn<OmitBuiltins<FragmentIn>, FragmentOut>)
92-
& /**
91+
input: InferIO<TIn>,
92+
out: TOut extends IORecord ? WgslStruct<TOut> : TOut,
93+
) => InferIO<TOut>,
94+
): TgpuFragmentFn<OmitBuiltins<TIn>, TOut>;
95+
/**
9396
* @param implementation
9497
* Raw WGSL function implementation with header and body
9598
* without `fn` keyword and function name
9699
* e.g. `"(x: f32) -> f32 { return x; }"`;
97-
*/ ((
100+
*/
101+
(
98102
implementation: string,
99-
) => TgpuFragmentFn<OmitBuiltins<FragmentIn>, FragmentOut>)
100-
& ((
103+
): TgpuFragmentFn<OmitBuiltins<TIn>, TOut>;
104+
(
101105
strings: TemplateStringsArray,
102106
...values: unknown[]
103-
) => TgpuFragmentFn<OmitBuiltins<FragmentIn>, FragmentOut>);
107+
): TgpuFragmentFn<OmitBuiltins<TIn>, TOut>;
108+
}
104109

105110
export interface TgpuFragmentFn<
106-
Varying extends FragmentInConstrained = FragmentInConstrained,
107-
Output extends FragmentOutConstrained = FragmentOutConstrained,
111+
// @ts-expect-error: We override the variance
112+
in Varying extends TgpuFragmentFn.In = Record<string, never>,
113+
out Output extends TgpuFragmentFn.Out = TgpuFragmentFn.Out,
108114
> extends TgpuNamable {
109115
readonly [$internal]: true;
110116
readonly shell: TgpuFragmentFnShellHeader<Varying, Output>;
@@ -113,6 +119,13 @@ export interface TgpuFragmentFn<
113119
$uses(dependencyMap: Record<string, unknown>): this;
114120
}
115121

122+
export declare namespace TgpuFragmentFn {
123+
// Not allowing single-value input, as using objects here is more
124+
// readable, and refactoring to use a builtin argument is too much hassle.
125+
type In = Record<string, BaseData>;
126+
type Out = Record<string, BaseData> | BaseData;
127+
}
128+
116129
export function fragmentFn<
117130
FragmentOut extends FragmentOutConstrained,
118131
>(options: {
@@ -140,10 +153,8 @@ export function fragmentFn<
140153
* A `vec4f`, signaling this function outputs a color for one target, or a record containing colors for multiple targets.
141154
*/
142155
export function fragmentFn<
143-
// Not allowing single-value input, as using objects here is more
144-
// readable, and refactoring to use a builtin argument is too much hassle.
145-
FragmentIn extends FragmentInConstrained,
146-
FragmentOut extends FragmentOutConstrained,
156+
FragmentIn extends TgpuFragmentFn.In,
157+
FragmentOut extends TgpuFragmentFn.Out,
147158
>(options: {
148159
in?: FragmentIn;
149160
out: FragmentOut;
@@ -160,7 +171,7 @@ export function fragmentFn<
160171
...values: unknown[]
161172
) => createFragmentFn(shell, stripTemplate(arg, ...values));
162173

163-
return Object.assign(call, shell) as TgpuFragmentFnShell<
174+
return Object.assign(call, shell) as unknown as TgpuFragmentFnShell<
164175
FragmentIn,
165176
FragmentOut
166177
>;
@@ -171,16 +182,16 @@ export function fragmentFn<
171182
// --------------
172183

173184
function createFragmentFn(
174-
shell: TgpuFragmentFnShellHeader<
175-
FragmentInConstrained,
176-
FragmentOutConstrained
177-
>,
185+
shell: TgpuFragmentFnShellHeader,
178186
implementation: Implementation,
179187
): TgpuFragmentFn {
180-
type This = TgpuFragmentFn & SelfResolvable & {
181-
[$internal]: true;
182-
[$getNameForward]: FnCore;
183-
};
188+
type This =
189+
& TgpuFragmentFn<TgpuFragmentFn.In, TgpuFragmentFn.Out>
190+
& SelfResolvable
191+
& {
192+
[$internal]: true;
193+
[$getNameForward]: FnCore;
194+
};
184195

185196
const core = createFnCore(implementation, '@fragment ');
186197
const outputType = shell.returnType;
@@ -214,10 +225,10 @@ function createFragmentFn(
214225
[$resolve](ctx: ResolutionCtx): ResolvedSnippet {
215226
const inputWithLocation = shell.in
216227
? createIoSchema(shell.in, ctx.varyingLocations)
217-
.$name(`${getName(this) ?? ''}_Input`)
218228
: undefined;
219229

220230
if (inputWithLocation) {
231+
setName(inputWithLocation, `${getName(this) ?? ''}_Input`);
221232
core.applyExternals({ In: inputWithLocation });
222233
}
223234
core.applyExternals({ Out: outputType });

0 commit comments

Comments
 (0)