Skip to content
Open
Show file tree
Hide file tree
Changes from 17 commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
c82ea68
Add an error
Dec 16, 2025
77c5132
Refactor
Dec 16, 2025
8417be7
Remove public currentStage
Dec 16, 2025
84c0399
Keep entrypoint info on functions
Dec 16, 2025
9c108c0
Remove remaining isEntry
Dec 16, 2025
ed3d0ae
Remove does from entrypoints
Dec 16, 2025
10e27ab
Remove `resolveStage`
Dec 16, 2025
675697a
Rename Stage to ShaderStage
Dec 16, 2025
e3e24a5
Add more tests
Dec 16, 2025
a1060a9
Fix circular dep
Dec 16, 2025
2f31c53
smol
Dec 16, 2025
45bf547
Merge remote-tracking branch 'origin/main' into fix/better-error-when…
Dec 16, 2025
3428f04
Review fixes
Dec 18, 2025
14e6b7a
Merge remote-tracking branch 'origin/main' into fix/better-error-when…
Dec 18, 2025
1310713
Add a test
Dec 18, 2025
c820cd0
Merge branch 'main' into fix/better-error-when-logging-from-vertex
aleksanderkatan Dec 18, 2025
2ea9223
Merge branch 'main' into fix/better-error-when-logging-from-vertex
aleksanderkatan Dec 22, 2025
e42761a
Merge remote-tracking branch 'origin/main' into fix/better-error-when…
Jan 15, 2026
50c7f27
Merge fixes
Jan 15, 2026
23083d3
Add shaderStageSlot
Jan 15, 2026
3054814
Remove _currentStage
Jan 15, 2026
fd0f6a9
Update tests
Jan 15, 2026
f326bdf
Export type guards
Jan 15, 2026
7dc5e64
Update log test example
Jan 15, 2026
c51df35
smol
Jan 15, 2026
7579e3a
Update test
Jan 15, 2026
2e551d2
Update apps/typegpu-docs/src/examples/tests/log-test/index.ts
aleksanderkatan Jan 15, 2026
07c81be
Remove duplicated test
Jan 15, 2026
e41cc9f
Merge branch 'main' into fix/better-error-when-logging-from-vertex
aleksanderkatan Jan 20, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 10 additions & 21 deletions packages/typegpu/src/core/function/tgpuComputeFn.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ type TgpuComputeFnShellHeader<
readonly argTypes: [IOLayoutToSchema<ComputeIn>] | [];
readonly returnType: Void;
readonly workgroupSize: [number, number, number];
readonly isEntry: true;
readonly entryPoint: 'compute';
};

/**
Expand All @@ -54,22 +54,7 @@ export type TgpuComputeFnShell<
& ((
strings: TemplateStringsArray,
...values: unknown[]
) => TgpuComputeFn<ComputeIn>)
& {
/**
* @deprecated Invoke the shell as a function instead.
*/
does:
& ((
implementation: (input: InferIO<ComputeIn>) => undefined,
) => TgpuComputeFn<ComputeIn>)
& /**
* @param implementation
* Raw WGSL function implementation with header and body
* without `fn` keyword and function name
* e.g. `"(x: f32) -> f32 { return x; }"`;
*/ ((implementation: string) => TgpuComputeFn<ComputeIn>);
};
) => TgpuComputeFn<ComputeIn>);

export interface TgpuComputeFn<
// biome-ignore lint/suspicious/noExplicitAny: to allow assigning any compute fn to TgpuComputeFn (non-generic) type
Expand Down Expand Up @@ -122,7 +107,7 @@ export function computeFn<
options.workgroupSize[1] ?? 1,
options.workgroupSize[2] ?? 1,
],
isEntry: true,
entryPoint: 'compute',
};

const call = (
Expand All @@ -135,9 +120,13 @@ export function computeFn<
stripTemplate(arg, ...values),
);

return Object.assign(Object.assign(call, shell), {
does: call,
}) as TgpuComputeFnShell<ComputeIn>;
return Object.assign(call, shell);
}

export function isTgpuComputeFn<ComputeIn extends IORecord<AnyComputeBuiltin>>(
value: unknown | TgpuComputeFn<ComputeIn>,
): value is TgpuComputeFn<ComputeIn> {
return (value as TgpuComputeFn<ComputeIn>)?.shell?.entryPoint === 'compute';
}

// --------------
Expand Down
2 changes: 0 additions & 2 deletions packages/typegpu/src/core/function/tgpuFn.ts
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,6 @@ type TgpuFnShellHeader<
readonly [$internal]: true;
readonly argTypes: Args;
readonly returnType: Return;
readonly isEntry: false;
};

/**
Expand Down Expand Up @@ -125,7 +124,6 @@ export function fn<
[$internal]: true,
argTypes,
returnType: returnType ?? Void as Return,
isEntry: false,
};

const call = (
Expand Down
41 changes: 18 additions & 23 deletions packages/typegpu/src/core/function/tgpuFragmentFn.ts
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ type TgpuFragmentFnShellHeader<
readonly in: FragmentIn | undefined;
readonly out: FragmentOut;
readonly returnType: IOLayoutToSchema<FragmentOut>;
readonly isEntry: true;
readonly entryPoint: 'fragment';
};

/**
Expand Down Expand Up @@ -93,24 +93,7 @@ export type TgpuFragmentFnShell<
& ((
strings: TemplateStringsArray,
...values: unknown[]
) => TgpuFragmentFn<OmitBuiltins<FragmentIn>, FragmentOut>)
& {
/**
* @deprecated Invoke the shell as a function instead.
*/
does:
& ((
implementation: (input: InferIO<FragmentIn>) => InferIO<FragmentOut>,
) => TgpuFragmentFn<OmitBuiltins<FragmentIn>, FragmentOut>)
& /**
* @param implementation
* Raw WGSL function implementation with header and body
* without `fn` keyword and function name
* e.g. `"(x: f32) -> f32 { return x; }"`;
*/ ((
implementation: string,
) => TgpuFragmentFn<OmitBuiltins<FragmentIn>, FragmentOut>);
};
) => TgpuFragmentFn<OmitBuiltins<FragmentIn>, FragmentOut>);

export interface TgpuFragmentFn<
Varying extends FragmentInConstrained = FragmentInConstrained,
Expand Down Expand Up @@ -162,17 +145,29 @@ export function fragmentFn<
in: options.in,
out: options.out,
returnType: createIoSchema(options.out),
isEntry: true,
entryPoint: 'fragment',
};

const call = (
arg: Implementation | TemplateStringsArray,
...values: unknown[]
) => createFragmentFn(shell, stripTemplate(arg, ...values));

return Object.assign(Object.assign(call, shell), {
does: call,
}) as TgpuFragmentFnShell<FragmentIn, FragmentOut>;
return Object.assign(call, shell) as TgpuFragmentFnShell<
FragmentIn,
FragmentOut
>;
}

export function isTgpuFragmentFn<
FragmentIn extends FragmentInConstrained,
FragmentOut extends FragmentOutConstrained,
>(
value: unknown | TgpuFragmentFn<FragmentIn, FragmentOut>,
): value is TgpuFragmentFn<FragmentIn, FragmentOut> {
return (value as TgpuFragmentFn<FragmentIn, FragmentOut>)?.shell
?.entryPoint ===
'fragment';
}

// --------------
Expand Down
32 changes: 14 additions & 18 deletions packages/typegpu/src/core/function/tgpuVertexFn.ts
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ type TgpuVertexFnShellHeader<
readonly in: VertexIn | undefined;
readonly out: VertexOut;
readonly argTypes: [IOLayoutToSchema<VertexIn>] | [];
readonly isEntry: true;
readonly entryPoint: 'vertex';
};

/**
Expand All @@ -77,19 +77,7 @@ export type TgpuVertexFnShell<
& ((
strings: TemplateStringsArray,
...values: unknown[]
) => TgpuVertexFn<OmitBuiltins<VertexIn>, OmitBuiltins<VertexOut>>)
& {
/**
* @deprecated Invoke the shell as a function instead.
*/
does:
& ((
implementation: (input: InferIO<VertexIn>) => InferIO<VertexOut>,
) => TgpuVertexFn<OmitBuiltins<VertexIn>, OmitBuiltins<VertexOut>>)
& ((
implementation: string,
) => TgpuVertexFn<OmitBuiltins<VertexIn>, OmitBuiltins<VertexOut>>);
};
) => TgpuVertexFn<OmitBuiltins<VertexIn>, OmitBuiltins<VertexOut>>);

export interface TgpuVertexFn<
VertexIn extends VertexInConstrained = VertexInConstrained,
Expand Down Expand Up @@ -146,17 +134,25 @@ export function vertexFn<
argTypes: options.in && Object.keys(options.in).length !== 0
? [createIoSchema(options.in)]
: [],
isEntry: true,
entryPoint: 'vertex',
};

const call = (
arg: Implementation | TemplateStringsArray,
...values: unknown[]
) => createVertexFn(shell, stripTemplate(arg, ...values));

return Object.assign(Object.assign(call, shell), {
does: call,
}) as TgpuVertexFnShell<VertexIn, VertexOut>;
return Object.assign(call, shell) as TgpuVertexFnShell<VertexIn, VertexOut>;
}

export function isTgpuVertexFn<
VertexIn extends VertexInConstrained,
VertexOut extends VertexOutConstrained,
>(
value: unknown | TgpuVertexFn<VertexIn, VertexOut>,
): value is TgpuVertexFn<VertexIn, VertexOut> {
return (value as TgpuVertexFn<VertexIn, VertexOut>)?.shell?.entryPoint ===
'vertex';
}

// --------------
Expand Down
44 changes: 42 additions & 2 deletions packages/typegpu/src/resolutionCtx.ts
Original file line number Diff line number Diff line change
Expand Up @@ -65,12 +65,16 @@ import type {
ItemLayer,
ItemStateStack,
ResolutionCtx,
ShaderStage,
TgpuShaderStage,
Wgsl,
} from './types.ts';
import { CodegenState, isSelfResolvable, NormalState } from './types.ts';
import type { WgslExtension } from './wgslExtensions.ts';
import { hasTinyestMetadata } from './shared/meta.ts';
import { isTgpuComputeFn } from './core/function/tgpuComputeFn.ts';
import { isTgpuVertexFn } from './core/function/tgpuVertexFn.ts';
import { isTgpuFragmentFn } from './core/function/tgpuFragmentFn.ts';

/**
* Inserted into bind group entry definitions that belong
Expand Down Expand Up @@ -334,6 +338,12 @@ export class ResolutionCtxImpl implements ResolutionCtx {
readonly #namespace: NamespaceInternal;
readonly #shaderGenerator: ShaderGenerator;

/**
* Holds info about the currently resolved shader stage (if there is any).
* Note that if a function is used both in vertex and fragment stage,
* then it will only go through the process during the vertex stage.
*/
private _currentStage: ShaderStage;
private readonly _indentController = new IndentController();
private readonly _itemStateStack = new ItemStateStackImpl();
readonly #modeStack: ExecState[] = [];
Expand Down Expand Up @@ -444,6 +454,11 @@ export class ResolutionCtxImpl implements ResolutionCtx {
}

generateLog(op: string, args: Snippet[]): Snippet {
if (this._currentStage === 'vertex') {
throw new Error(
`'console.log' is not supported during vertex shader stage.`,
);
}
return this.#logGenerator.generateLog(this, op, args);
}

Expand Down Expand Up @@ -732,11 +747,24 @@ export class ResolutionCtxImpl implements ResolutionCtx {
}

if (isMarkedInternal(item) || hasTinyestMetadata(item)) {
// if we're resolving an entrypoint function, we want to update this._currentStage
const stage = getItemStage(item);
const resolutionAction = () => {
if (stage) {
this._currentStage = stage;
}
const result = this._getOrInstantiate(item);
if (stage) {
this._currentStage = undefined;
}
return result;
};

// Top-level resolve
if (this._itemStateStack.itemDepth === 0) {
try {
this.pushMode(new CodegenState());
const result = provideCtx(this, () => this._getOrInstantiate(item));
const result = provideCtx(this, resolutionAction);
return snip(
`${[...this._declarations].join('\n\n')}${result.value}`,
Void,
Expand All @@ -747,7 +775,7 @@ export class ResolutionCtxImpl implements ResolutionCtx {
}
}

return this._getOrInstantiate(item);
return resolutionAction();
}

// This is a value that comes from the outside, maybe we can coerce it
Expand Down Expand Up @@ -972,3 +1000,15 @@ export function resolveFunctionHeader(
} `
: `(${argList}) `;
}

function getItemStage(item: unknown): ShaderStage {
if (isTgpuComputeFn(item)) {
return 'compute';
}
if (isTgpuVertexFn(item)) {
return 'vertex';
}
if (isTgpuFragmentFn(item)) {
return 'fragment';
}
}
6 changes: 6 additions & 0 deletions packages/typegpu/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -217,6 +217,12 @@ export type ExecState =
| CodegenState
| SimulationState;

export type ShaderStage =
| 'vertex'
| 'fragment'
| 'compute'
| undefined;

/**
* Passed into each resolvable item. All items in a tree share a resolution ctx,
* but there can be layers added and removed from the item stack when going down
Expand Down
Loading