Skip to content

feat: Match up vertex and fragment locations in render pipeline #1377

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 30 commits into from
Jul 3, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
0d8e46e
feat: Match-up vertex output and fragment location indices in render …
mhawryluk Jun 17, 2025
2840148
Make compute pipeline resolvable
mhawryluk Jun 17, 2025
f7b99d8
Fixes and refactor
mhawryluk Jun 18, 2025
38751d0
Fix In and Out externals
mhawryluk Jun 18, 2025
f66c3e8
Warn on conflicting locations
mhawryluk Jun 18, 2025
91e39b3
Bump vitest
mhawryluk Jun 18, 2025
36d3aa5
Merge branch 'main' into fix/match-vertex-fragment
mhawryluk Jun 18, 2025
edab6ed
Fix after merge
mhawryluk Jun 18, 2025
d766f0d
Update lock
mhawryluk Jun 18, 2025
cde1a86
Fix stable fluids
mhawryluk Jun 18, 2025
9d4c2e2
Merge branch 'main' into fix/match-vertex-fragment
mhawryluk Jun 23, 2025
3cd39c6
Improve pipeline resolvable toString
mhawryluk Jun 23, 2025
c1d9a98
Use createIoSchema for jit varying structs
mhawryluk Jun 23, 2025
1d378af
Refactor
mhawryluk Jun 23, 2025
16d82bd
Update tests
mhawryluk Jun 23, 2025
e9edf7b
Simplify
mhawryluk Jun 23, 2025
cbd2dc3
Tests for matchUpVaryingLocations
mhawryluk Jun 23, 2025
f39b3a1
Merge branch 'main' into fix/match-vertex-fragment
mhawryluk Jun 23, 2025
f239e56
Fix pnpm-lock
mhawryluk Jun 23, 2025
033ce13
Review fix
mhawryluk Jun 23, 2025
929a343
Merge branch 'main' into fix/match-vertex-fragment
mhawryluk Jun 24, 2025
b200d11
Merge branch 'main' into fix/match-vertex-fragment
mhawryluk Jun 26, 2025
ffa5f7f
Review fixes
mhawryluk Jun 26, 2025
830f2a8
Merge branch 'main' into fix/match-vertex-fragment
mhawryluk Jun 27, 2025
2f3eb1b
Apply suggestions from code review
mhawryluk Jun 27, 2025
4876a3f
More review fixes
mhawryluk Jun 27, 2025
8126ccd
Apply suggestions from code review
iwoplaza Jul 3, 2025
2892e61
Merge branch 'main' into fix/match-vertex-fragment
iwoplaza Jul 3, 2025
dcb9c0a
Update packages/typegpu/src/core/pipeline/computePipeline.ts
iwoplaza Jul 3, 2025
5f2ccb4
Simpler resolution logic
iwoplaza Jul 3, 2025
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
Original file line number Diff line number Diff line change
Expand Up @@ -285,7 +285,7 @@ const vertex = tgpu['~unstable'].vertexFn({

const fragment = tgpu['~unstable'].fragmentFn({
in: { cell: d.f32 },
out: d.location(0, d.vec4f),
out: d.vec4f,
})((input) => {
if (input.cell === -1) {
return d.vec4f(0.5, 0.5, 0.5, 1);
Expand Down
100 changes: 37 additions & 63 deletions packages/typegpu/src/core/function/fnCore.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
import { FuncParameterType } from 'tinyest';
import { getAttributesString } from '../../data/attributes.ts';
import { snip } from '../../data/dataTypes.ts';
import { type AnyData, snip } from '../../data/dataTypes.ts';
import {
type AnyWgslData,
type AnyWgslStruct,
isWgslData,
isWgslStruct,
Expand All @@ -12,29 +11,25 @@ import { MissingLinksError } from '../../errors.ts';
import { getMetaData, getName, setName } from '../../shared/meta.ts';
import type { ResolutionCtx } from '../../types.ts';
import {
addArgTypesToExternals,
addReturnTypeToExternals,
applyExternals,
type ExternalMap,
replaceExternalsInWgsl,
} from '../resolve/externals.ts';
import { extractArgs } from './extractArgs.ts';
import type { Implementation } from './fnTypes.ts';

export interface TgpuFnShellBase<Args extends unknown[], Return> {
readonly argTypes: Args;
readonly returnType: Return;
readonly isEntry: boolean;
}

export interface FnCore {
applyExternals(newExternals: ExternalMap): void;
resolve(ctx: ResolutionCtx, fnAttribute?: string): string;
resolve(
ctx: ResolutionCtx,
argTypes: AnyData[],
returnType: AnyData,
): string;
}

export function createFnCore(
shell: TgpuFnShellBase<unknown[], unknown>,
implementation: Implementation,
fnAttribute = '',
): FnCore {
/**
* External application has to be deferred until resolution because
Expand All @@ -44,35 +39,16 @@ export function createFnCore(
*/
const externalsToApply: ExternalMap[] = [];

if (typeof implementation === 'string') {
if (!shell.isEntry) {
addArgTypesToExternals(
implementation,
shell.argTypes,
(externals) => externalsToApply.push(externals),
);
addReturnTypeToExternals(
implementation,
shell.returnType,
(externals) => externalsToApply.push(externals),
);
} else {
if (isWgslStruct(shell.argTypes[0])) {
externalsToApply.push({ In: shell.argTypes[0] });
}

if (isWgslStruct(shell.returnType)) {
externalsToApply.push({ Out: shell.returnType });
}
}
}

const core = {
applyExternals(newExternals: ExternalMap): void {
externalsToApply.push(newExternals);
},

resolve(ctx: ResolutionCtx, fnAttribute = ''): string {
resolve(
ctx: ResolutionCtx,
argTypes: AnyData[],
returnType: AnyData,
): string {
const externalMap: ExternalMap = {};

for (const externals of externalsToApply) {
Expand All @@ -91,19 +67,19 @@ export function createFnCore(
let header = '';
let body = '';

if (shell.isEntry) {
const input = isWgslStruct(shell.argTypes[0])
? `(in: ${ctx.resolve(shell.argTypes[0])})`
if (fnAttribute !== '') {
const input = isWgslStruct(argTypes[0])
? `(in: ${ctx.resolve(argTypes[0])})`
: '()';

const attributes = isWgslData(shell.returnType)
? getAttributesString(shell.returnType)
const attributes = isWgslData(returnType)
? getAttributesString(returnType)
: '';
const output = shell.returnType !== Void
? isWgslStruct(shell.returnType)
? `-> ${ctx.resolve(shell.returnType)}`
const output = returnType !== Void
? isWgslStruct(returnType)
? `-> ${ctx.resolve(returnType)}`
: `-> ${attributes !== '' ? attributes : '@location(0)'} ${
ctx.resolve(shell.returnType)
ctx.resolve(returnType)
}`
: '';

Expand All @@ -112,9 +88,9 @@ export function createFnCore(
} else {
const providedArgs = extractArgs(replacedImpl);

if (providedArgs.args.length !== shell.argTypes.length) {
if (providedArgs.args.length !== argTypes.length) {
throw new Error(
`WGSL implementation has ${providedArgs.args.length} arguments, while the shell has ${shell.argTypes.length} arguments.`,
`WGSL implementation has ${providedArgs.args.length} arguments, while the shell has ${argTypes.length} arguments.`,
);
}

Expand All @@ -124,21 +100,19 @@ export function createFnCore(
ctx,
`parameter ${argInfo.identifier}`,
argInfo.type,
shell.argTypes[i],
argTypes[i],
)
}`
).join(', ');

const output = shell.returnType === Void
? ''
: `-> ${
checkAndReturnType(
ctx,
'return type',
providedArgs.ret?.type,
shell.returnType,
)
}`;
const output = returnType === Void ? '' : `-> ${
checkAndReturnType(
ctx,
'return type',
providedArgs.ret?.type,
returnType,
)
}`;

header = `(${input}) ${output}`;

Expand Down Expand Up @@ -177,12 +151,12 @@ export function createFnCore(

// generate wgsl string
const { head, body } = ctx.fnToWgsl({
args: shell.argTypes.map((arg, i) =>
args: argTypes.map((arg, i) =>
snip(
ast.params[i]?.type === FuncParameterType.identifier
? ast.params[i].name
: `_arg_${i}`,
arg as AnyWgslData,
arg,
)
),
argAliases: Object.fromEntries(
Expand All @@ -192,14 +166,14 @@ export function createFnCore(
alias,
snip(
`_arg_${i}.${name}`,
(shell.argTypes[i] as AnyWgslStruct)
.propTypes[name] as AnyWgslData,
(argTypes[i] as AnyWgslStruct)
.propTypes[name],
),
])
: []
),
),
returnType: shell.returnType as AnyWgslData,
returnType,
body: ast.body,
externalMap,
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,24 +29,40 @@ export type IOLayoutToSchema<T extends IOLayout> = T extends BaseData
: never;

export function withLocations<T extends IOData>(
members: IORecord<T>,
members: IORecord<T> | undefined,
locations: Record<string, number> = {},
): WithLocations<IORecord<T>> {
let nextLocation = 0;
const usedCustomLocations = new Set<number>();

return Object.fromEntries(
Object.entries(members).map(([key, member]) => {
if (isBuiltin(member)) {
// Skipping builtins
Object.entries(members ?? {}).map(([key, member]) => {
const customLocation = getCustomLocation(member);

if (customLocation !== undefined) {
if (usedCustomLocations.has(customLocation)) {
throw new Error('Duplicate custom location attributes found');
}
usedCustomLocations.add(customLocation);
}

return [key, member] as const;
}).map(([key, member]) => {
if (isBuiltin(member)) { // skipping builtins
return [key, member];
}

const customLocation = getCustomLocation(member);
if (customLocation !== undefined) {
// This member is already marked, start counting from the next location over.
nextLocation = customLocation + 1;
if (getCustomLocation(member) !== undefined) { // this member is already marked
return [key, member];
}

if (locations[key]) { // location has been determined by a previous procedure
return [key, location(locations[key], member)];
}

while (usedCustomLocations.has(nextLocation)) {
nextLocation++;
}
return [key, location(nextLocation++, member)];
}),
);
Expand All @@ -55,14 +71,14 @@ export function withLocations<T extends IOData>(
export function createIoSchema<
T extends IOData,
Layout extends IORecord<T> | IOLayout<T>,
>(returnType: Layout) {
>(layout: Layout, locations: Record<string, number> = {}) {
return (
isData(returnType)
? isVoid(returnType)
? returnType
: getCustomLocation(returnType) !== undefined
? returnType
: location(0, returnType)
: struct(withLocations(returnType) as Record<string, T>)
isData(layout)
? isVoid(layout)
? layout
: getCustomLocation(layout) !== undefined
? layout
: location(0, layout)
: struct(withLocations(layout, locations) as Record<string, T>)
) as IOLayoutToSchema<Layout>;
}
10 changes: 7 additions & 3 deletions packages/typegpu/src/core/function/tgpuComputeFn.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import { $getNameForward, $internal } from '../../shared/symbols.ts';
import type { ResolutionCtx, SelfResolvable } from '../../types.ts';
import { createFnCore, type FnCore } from './fnCore.ts';
import type { Implementation, InferIO, IORecord } from './fnTypes.ts';
import { createIoSchema, type IOLayoutToSchema } from './ioOutputType.ts';
import { createIoSchema, type IOLayoutToSchema } from './ioSchema.ts';
import { stripTemplate } from './templateUtils.ts';

// ----------
Expand Down Expand Up @@ -153,7 +153,10 @@ function createComputeFn<ComputeIn extends IORecord<AnyComputeBuiltin>>(
[$getNameForward]: FnCore;
};

const core = createFnCore(shell, implementation);
const core = createFnCore(
implementation,
`@compute @workgroup_size(${workgroupSize.join(', ')}) `,
);
const inputType = shell.argTypes[0];

const result: This = {
Expand All @@ -177,7 +180,8 @@ function createComputeFn<ComputeIn extends IORecord<AnyComputeBuiltin>>(
'~resolve'(ctx: ResolutionCtx): string {
return core.resolve(
ctx,
`@compute @workgroup_size(${workgroupSize.join(', ')}) `,
shell.argTypes,
shell.returnType,
);
},

Expand Down
25 changes: 20 additions & 5 deletions packages/typegpu/src/core/function/tgpuFn.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,15 @@
import { type AnyData, snip, UnknownData } from '../../data/dataTypes.ts';
import { Void } from '../../data/wgslTypes.ts';
import { createDualImpl } from '../../shared/generators.ts';
import type { TgpuNamable } from '../../shared/meta.ts';
import { getName, setName } from '../../shared/meta.ts';
import { createDualImpl } from '../../shared/generators.ts';
import type { Infer } from '../../shared/repr.ts';
import {
$getNameForward,
$internal,
$providing,
} from '../../shared/symbols.ts';
import type { Prettify } from '../../shared/utilityTypes.ts';
import type { GenerationCtx } from '../../tgsl/generationHelpers.ts';
import type {
FnArgsConversionHint,
Expand All @@ -17,6 +18,10 @@ import type {
Wgsl,
} from '../../types.ts';
import type { TgpuBufferUsage } from '../buffer/bufferUsage.ts';
import {
addArgTypesToExternals,
addReturnTypeToExternals,
} from '../resolve/externals.ts';
import {
type Eventual,
isAccessor,
Expand All @@ -34,7 +39,6 @@ import type {
InheritArgNames,
} from './fnTypes.ts';
import { stripTemplate } from './templateUtils.ts';
import type { Prettify } from '../../shared/utilityTypes.ts';

// ----------
// Public API
Expand Down Expand Up @@ -157,7 +161,7 @@ function createFn<ImplSchema extends AnyFn>(
[$getNameForward]: FnCore;
};

const core = createFnCore(shell, implementation as Implementation);
const core = createFnCore(implementation as Implementation, '');

const fnBase: This = {
[$internal]: {
Expand Down Expand Up @@ -189,7 +193,18 @@ function createFn<ImplSchema extends AnyFn>(

'~resolve'(ctx: ResolutionCtx): string {
if (typeof implementation === 'string') {
return core.resolve(ctx);
addArgTypesToExternals(
implementation,
shell.argTypes,
core.applyExternals,
);
addReturnTypeToExternals(
implementation,
shell.returnType,
core.applyExternals,
);

return core.resolve(ctx, shell.argTypes, shell.returnType);
}

const generationCtx = ctx as GenerationCtx;
Expand All @@ -201,7 +216,7 @@ function createFn<ImplSchema extends AnyFn>(

try {
generationCtx.callStack.push(shell.returnType);
return core.resolve(ctx);
return core.resolve(ctx, shell.argTypes, shell.returnType);
} finally {
generationCtx.callStack.pop();
}
Expand Down
Loading