Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
16 changes: 16 additions & 0 deletions apps/typegpu-docs/src/content/docs/apis/functions/index.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -483,6 +483,22 @@ Another thing to note is that there is no need to specify locations of the argum
as TypeGPU tries to assign locations automatically.
If you wish to, you can assign the locations manually with the `d.location` decorator.

:::note
WGSL requires integer inter-stage values (`i32`, `u32`, `vec*i`, `vec*u`) to use flat interpolation.
When an entry function is defined with a shell, its vertex outputs and fragment inputs have to specify it explicitly,
otherwise resolution fails with an error.

```ts
const vertexOutput = {
position: d.builtin.position,
instanceIndex: d.interpolate('flat', d.u32),
};
```

Entry functions passed directly to `root.createRenderPipeline` without a shell infer their varyings,
so TypeGPU adds `@interpolate(flat)` to integer varyings automatically.
:::

During WGSL generation, TypeGPU automatically generates structs corresponding to the passed `IORecord`s.
In WGSL-implemented functions, input and output structs of the given function can be referenced as `In` and `Out` respectively.
Headers in WGSL implementations must be omitted, all input values are accessible through the struct named `in`.
Expand Down
12 changes: 6 additions & 6 deletions packages/typegpu-gl/tests/glslGenerator.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -782,8 +782,8 @@ describe('GlslGenerator - entry point generation with JS functions', () => {
const vertFn = tgpu.vertexFn({
out: {
position: d.builtin.position,
x: d.u32,
y: d.u32,
x: d.interpolate('flat', d.u32),
y: d.interpolate('flat', d.u32),
},
})(() => {
'use gpu';
Expand Down Expand Up @@ -822,8 +822,8 @@ describe('GlslGenerator - entry point generation with JS functions', () => {
const vertFn = tgpu.vertexFn({
out: {
position: d.builtin.position,
x: d.u32,
y: d.u32,
x: d.interpolate('flat', d.u32),
y: d.interpolate('flat', d.u32),
},
})(() => {
'use gpu';
Expand Down Expand Up @@ -869,8 +869,8 @@ describe('GlslGenerator - entry point generation with JS functions', () => {
const vertFn = tgpu.vertexFn({
out: {
position: d.builtin.position,
x: d.u32,
y: d.u32,
x: d.interpolate('flat', d.u32),
y: d.interpolate('flat', d.u32),
},
})(() => {
'use gpu';
Expand Down
14 changes: 12 additions & 2 deletions packages/typegpu/src/core/function/autoIO.ts
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,12 @@ export class AutoFragmentFn implements SelfResolvable {
setName(impl, 'fragmentFn');
}
this.#core = createFnCore(impl, 'fragment');
this.autoIn = new AutoStruct({ ...builtinFragmentIn, ...varyings }, undefined, locations);
this.autoIn = new AutoStruct(
{ ...builtinFragmentIn, ...varyings },
undefined,
locations,
/* autoInterpolateIntegers */ true,
);
setName(this.autoIn, 'FragmentIn');
this.autoOut = new AutoStruct(builtinFragmentOut, vec4f);
setName(this.autoOut, 'FragmentOut');
Expand Down Expand Up @@ -134,7 +139,12 @@ export class AutoVertexFn implements SelfResolvable {
this.#core = createFnCore(impl, 'vertex');
this.autoIn = new AutoStruct({ ...builtinVertexIn, ...attribs }, undefined, locations);
setName(this.autoIn, 'VertexIn');
this.autoOut = new AutoStruct(builtinVertexOut, undefined);
this.autoOut = new AutoStruct(
builtinVertexOut,
undefined,
undefined,
/* autoInterpolateIntegers */ true,
);
setName(this.autoOut, 'VertexOut');
}

Expand Down
81 changes: 66 additions & 15 deletions packages/typegpu/src/core/function/ioSchema.ts
Comment thread
iwoplaza marked this conversation as resolved.
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,23 @@ import {
type Decorate,
type HasCustomLocation,
type IsBuiltin,
interpolate,
location,
} from '../../data/attributes.ts';
import { isBuiltin } from '../../data/attributes.ts';
import { getCustomLocation, isData } from '../../data/dataTypes.ts';
import { INTERNAL_createStruct } from '../../data/struct.ts';
import { type BaseData, isVoid, type Location, type WgslStruct } from '../../data/wgslTypes.ts';
import {
type BaseData,
type FlatInterpolatableData,
isDecorated,
isInteger,
isIntegerVec,
isInterpolateAttrib,
isVoid,
type Location,
type WgslStruct,
} from '../../data/wgslTypes.ts';
import type { SeparatedEntryArgs } from './fnTypes.ts';

export type WithLocations<T extends Record<string, BaseData>> = {
Expand All @@ -31,6 +42,7 @@ export type IOLayoutToSchema<T> = T extends BaseData
export function withLocations<T extends BaseData>(
members: Record<string, T> | undefined,
locations: Record<string, number> = {},
autoInterpolateIntegers = false,
): Record<string, BaseData> {
let nextLocation = 0;
const usedCustomLocations = new Set<number>();
Expand All @@ -47,7 +59,10 @@ export function withLocations<T extends BaseData>(
usedCustomLocations.add(customLocation);
}

return [key, member] as const;
return [
key,
autoInterpolateIntegers ? withFlatInterpolationForInteger(member) : member,
] as const;
})
.map(([key, member]) => {
if (isBuiltin(member)) {
Expand Down Expand Up @@ -105,19 +120,55 @@ export function separateAllAsPositional(schema: Record<string, BaseData>): Separ
export function createIoSchema<T extends BaseData | Record<string, BaseData>>(
layout: T,
locations: Record<string, number> = {},
autoInterpolateIntegers = false,
) {
return (
isData(layout)
? isVoid(layout)
? layout
: isBuiltin(layout)
? layout
: getCustomLocation(layout) !== undefined
? layout
: location(0, layout)
: INTERNAL_createStruct(
withLocations(layout as Record<string, BaseData>, locations),
/* isAbstruct */ false,
)
if (isData(layout)) {
if (isVoid(layout) || isBuiltin(layout)) {
return layout as unknown as IOLayoutToSchema<T>;
}

return (
getCustomLocation(layout) !== undefined ? layout : location(0, layout)
) as IOLayoutToSchema<T>;
}

return INTERNAL_createStruct(
withLocations(layout as Record<string, BaseData>, locations, autoInterpolateIntegers),
/* isAbstruct */ false,
) as IOLayoutToSchema<T>;
}

function needsFlatInterpolation(data: BaseData): boolean {
if (isBuiltin(data) || (isDecorated(data) && data.attribs.some(isInterpolateAttrib))) {
return false;
}

const inner = isDecorated(data) ? data.inner : data;
return isInteger(inner) || isIntegerVec(inner);
}

function withFlatInterpolationForInteger(data: BaseData): BaseData {
return needsFlatInterpolation(data) ? interpolate('flat', data as FlatInterpolatableData) : data;
}

/**
* WGSL requires integer inter-stage values to be flat interpolated. Interpolation is only
* inferred for shellless entry functions, so shells have to specify it explicitly.
*/
export function assertIntegerVaryingsInterpolated(
layout: BaseData | Record<string, BaseData> | undefined,
location: string,
) {
if (layout === undefined || isData(layout)) {
// A single value is either a builtin or a fragment output, neither are varyings
return;
}

for (const [key, member] of Object.entries(layout)) {
if (needsFlatInterpolation(member)) {
throw new Error(
`Integer value "${key}" in ${location} requires flat interpolation. Wrap its schema in d.interpolate('flat', ...) or d.interpolate('flat, either', ...).`,
);
}
}
}
11 changes: 10 additions & 1 deletion packages/typegpu/src/core/function/tgpuFragmentFn.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,12 @@ import { shaderStageSlot } from '../slot/internalSlots.ts';
import type { AnyAutoCustoms, AutoFragmentIn, AutoFragmentOut } from './autoIO.ts';
import { createFnCore, type FnCore } from './fnCore.ts';
import type { BaseIOData, Implementation, InferIO, IOLayout, IORecord } from './fnTypes.ts';
import { createIoSchema, type IOLayoutToSchema, separateBuiltins } from './ioSchema.ts';
import {
assertIntegerVaryingsInterpolated,
createIoSchema,
type IOLayoutToSchema,
separateBuiltins,
} from './ioSchema.ts';
import { stripTemplate } from './templateUtils.ts';

// ----------
Expand Down Expand Up @@ -209,6 +214,10 @@ function createFragmentFn(
},

[$resolve](ctx: ResolutionCtx): ResolvedSnippet {
assertIntegerVaryingsInterpolated(
shell.in,
`fragmentFn (${getName(this) ?? '<unnamed>'}) input`,
);
const entryInput = separateBuiltins(shell.in ?? {}, ctx.varyingLocations ?? {});

if (entryInput.dataSchema && isNamable(entryInput.dataSchema)) {
Expand Down
11 changes: 10 additions & 1 deletion packages/typegpu/src/core/function/tgpuVertexFn.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,12 @@ import type {
IORecord,
SeparatedEntryArgs,
} from './fnTypes.ts';
import { createIoSchema, type IOLayoutToSchema, separateAllAsPositional } from './ioSchema.ts';
import {
assertIntegerVaryingsInterpolated,
createIoSchema,
type IOLayoutToSchema,
separateAllAsPositional,
} from './ioSchema.ts';
import { stripTemplate } from './templateUtils.ts';

// ----------
Expand Down Expand Up @@ -177,6 +182,10 @@ function createVertexFn(
},

[$resolve](ctx: ResolutionCtx): ResolvedSnippet {
assertIntegerVaryingsInterpolated(
shell.out,
`vertexFn (${getName(this) ?? '<unnamed>'}) output`,
);
const outputWithLocation = createIoSchema(shell.out, ctx.varyingLocations).$name(
`${getName(this) ?? ''}_Output`,
);
Expand Down
4 changes: 4 additions & 0 deletions packages/typegpu/src/data/autoStruct.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ export class AutoStruct implements BaseData, SelfResolvable {
#locations: Record<string, number> | undefined;
#cachedStruct: WgslStruct | undefined;
#typeForExtraProps: BaseData | undefined;
readonly #autoInterpolateIntegers: boolean;

static {
AutoStruct.prototype[$internal] = {};
Expand All @@ -44,11 +45,13 @@ export class AutoStruct implements BaseData, SelfResolvable {
validProps: Record<string, BaseData>,
typeForExtraProps: BaseData | undefined,
locations?: Record<string, number>,
autoInterpolateIntegers = false,
) {
this.#validProps = validProps;
this.#typeForExtraProps = typeForExtraProps;
this.#allocated = {};
this.#locations = locations;
this.#autoInterpolateIntegers = autoInterpolateIntegers;
this.#usedWgslKeys = new Set();
}

Expand Down Expand Up @@ -102,6 +105,7 @@ export class AutoStruct implements BaseData, SelfResolvable {
}),
),
this.#locations,
this.#autoInterpolateIntegers,
);
const ownName = getName(this);
// Passing the given name forward
Expand Down
Loading
Loading