diff --git a/packages/typegpu/src/core/function/autoIO.ts b/packages/typegpu/src/core/function/autoIO.ts index d4ba229b20..6feece51d5 100644 --- a/packages/typegpu/src/core/function/autoIO.ts +++ b/packages/typegpu/src/core/function/autoIO.ts @@ -7,7 +7,7 @@ import type { BaseData, v4f } from '../../data/wgslTypes.ts'; import { getName, setName } from '../../shared/meta.ts'; import type { InferGPU, InferGPURecord, InferRecord } from '../../shared/repr.ts'; import { $internal, $resolve } from '../../shared/symbols.ts'; -import type { Assume } from '../../shared/utilityTypes.ts'; +import type { Assume, RemoveIndexSignature } from '../../shared/utilityTypes.ts'; import type { TgpuVertexAttrib } from '../../shared/vertexFormat.ts'; import type { ResolutionCtx, SelfResolvable } from '../../types.ts'; import { shaderStageSlot } from '../slot/internalSlots.ts'; @@ -21,7 +21,8 @@ const builtinVertexIn = { $instanceIndex: builtin.instanceIndex, } as const; -export type AutoVertexIn = T & InferRecord; +export type AutoVertexIn = RemoveIndexSignature & + InferRecord; export type _AutoVertexIn = AutoVertexIn<{ [Key in keyof T]: T[Key] extends TgpuVertexAttrib @@ -47,7 +48,8 @@ const builtinFragmentIn = { $subgroupSize: builtin.subgroupSize, } as const; -export type AutoFragmentIn = T & InferRecord; +export type AutoFragmentIn = RemoveIndexSignature & + InferRecord; const builtinFragmentOut = { $fragDepth: builtin.fragDepth, diff --git a/packages/typegpu/src/core/function/tgpuFragmentFn.ts b/packages/typegpu/src/core/function/tgpuFragmentFn.ts index 3b6a0009b6..631617bcdc 100644 --- a/packages/typegpu/src/core/function/tgpuFragmentFn.ts +++ b/packages/typegpu/src/core/function/tgpuFragmentFn.ts @@ -121,6 +121,8 @@ export declare namespace TgpuFragmentFn { type Out = Record | BaseData; type AutoIn = AutoFragmentIn; type AutoOut = AutoFragmentOut; + + type AutoInEmpty = AutoFragmentIn>; } export function fragmentFn(options: { diff --git a/packages/typegpu/src/core/function/tgpuVertexFn.ts b/packages/typegpu/src/core/function/tgpuVertexFn.ts index 36cf8143eb..a261634b3d 100644 --- a/packages/typegpu/src/core/function/tgpuVertexFn.ts +++ b/packages/typegpu/src/core/function/tgpuVertexFn.ts @@ -86,6 +86,8 @@ export declare namespace TgpuVertexFn { type Out = Record; type AutoIn = _AutoVertexIn; type AutoOut = AutoVertexOut; + + type AutoInEmpty = _AutoVertexIn>; } export function vertexFn(options: { diff --git a/packages/typegpu/src/shared/utilityTypes.ts b/packages/typegpu/src/shared/utilityTypes.ts index 2ec56eff14..5aa21cf869 100644 --- a/packages/typegpu/src/shared/utilityTypes.ts +++ b/packages/typegpu/src/shared/utilityTypes.ts @@ -56,7 +56,7 @@ export type Mutable = { }; /** - * Source: https://code.lol/post/programming/higher-kinded-types/ + * Source: https://code.lol/post/programming/higher-kinded-types */ export type Assume = T extends U ? T : U; @@ -74,3 +74,16 @@ export type TypedArray = export function assertExhaustive(x: never, location: string): never { throw new Error(`Failed to handle ${x} at ${location}`); } + +/** + * Source: https://futurestud.io/tutorials/typescript-how-to-remove-index-signature-from-a-type + */ +export type RemoveIndexSignature = { + [K in keyof T as string extends K + ? never + : number extends K + ? never + : symbol extends K + ? never + : K]: T[K]; +}; diff --git a/packages/typegpu/tests/renderPipeline.test.ts b/packages/typegpu/tests/renderPipeline.test.ts index f24dc708ec..59aa4ec567 100644 --- a/packages/typegpu/tests/renderPipeline.test.ts +++ b/packages/typegpu/tests/renderPipeline.test.ts @@ -6,8 +6,10 @@ import tgpu, { common, d, MissingBindGroupsError, + type TgpuFragmentFn, type TgpuFragmentFnShell, type TgpuRenderPipeline, + type TgpuVertexFn, type TgpuVertexFnShell, } from '../src/index.js'; import { $internal } from '../src/shared/symbols.ts'; @@ -1591,7 +1593,7 @@ describe('root.createRenderPipeline', () => { attribs: { vertexIndex: vertexLayout.attrib }, vertex: ({ vertexIndex, $vertexIndex }) => { 'use gpu'; - return { $position: d.vec4f() }; + return { $position: d.vec4f(vertexIndex, $vertexIndex, 0, 1) }; }, fragment: () => { 'use gpu'; @@ -1619,7 +1621,7 @@ describe('root.createRenderPipeline', () => { }, fragment: ({ position }) => { 'use gpu'; - return d.vec4f(); + return position; }, targets: { format: 'rgba8unorm' }, }); @@ -1641,7 +1643,10 @@ describe('root.createRenderPipeline', () => { }, fragment: ({ $frontFacing, frontFacing }) => { 'use gpu'; - return d.vec4f(); + if ($frontFacing && frontFacing === 1) { + return d.vec4f(0, 1, 0, 1); + } + return d.vec4f(1, 0, 0, 1); }, targets: { format: 'rgba8unorm' }, }); @@ -1774,6 +1779,54 @@ describe('root.createRenderPipeline', () => { ] `); }); + + it('accepts entry functions with no attributes or varyings', ({ root }) => { + const positions = tgpu.const(d.arrayOf(d.vec2f, 3), [ + d.vec2f(0, 0), + d.vec2f(1, 0), + d.vec2f(0, 1), + ]); + const vertex = ({ $vertexIndex }: TgpuVertexFn.AutoInEmpty) => { + 'use gpu'; + return { + $position: d.vec4f(positions.$[$vertexIndex]!, 0, 1), + } satisfies TgpuVertexFn.AutoOut; + }; + + const fragment = ({ $position }: TgpuFragmentFn.AutoInEmpty) => { + 'use gpu'; + return $position; + }; + + const pipeline = root.createRenderPipeline({ + vertex, + fragment, + }); + + expect(tgpu.resolve([pipeline])).toMatchInlineSnapshot(` + "const positions: array = array(vec2f(), vec2f(1, 0), vec2f(0, 1)); + + struct VertexOut { + @builtin(position) position: vec4f, + } + + struct VertexIn { + @builtin(vertex_index) vertexIndex: u32, + } + + @vertex fn vertex(_arg_0: VertexIn) -> VertexOut { + return VertexOut(vec4f(positions[_arg_0.vertexIndex], 0f, 1f)); + } + + struct FragmentIn { + @builtin(position) position: vec4f, + } + + @fragment fn fragment(_arg_0: FragmentIn) -> @location(0) vec4f { + return _arg_0.position; + }" + `); + }); }); describe('matchUpVaryingLocations', () => {