Skip to content

Commit bf31165

Browse files
committed
feat(@typegpu/gl): Generate GLSL function signatures
1 parent 8a474d6 commit bf31165

File tree

9 files changed

+445
-231
lines changed

9 files changed

+445
-231
lines changed
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
import glslGenerator from './glslGenerator.ts';
2+
3+
export function GLOptions() {
4+
return {
5+
unstable_shaderGenerator: glslGenerator,
6+
};
7+
}

packages/typegpu-gl/src/glslGenerator.ts

Lines changed: 83 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,16 @@
1-
import { d, ShaderGenerator, WgslGenerator } from 'typegpu';
1+
import { NodeTypeCatalog as NODE } from 'tinyest';
2+
import type { Return } from 'tinyest';
3+
import tgpu, { d, ShaderGenerator, WgslGenerator } from 'typegpu';
4+
import { setName } from '../../typegpu/src/shared/meta';
5+
6+
const UnknownData: typeof ShaderGenerator.UnknownData = ShaderGenerator.UnknownData;
27

38
// ----------
49
// WGSL → GLSL type name mapping
510
// ----------
611

712
const WGSL_TO_GLSL_TYPE: Record<string, string> = {
13+
void: 'void',
814
f32: 'float',
915
u32: 'uint',
1016
i32: 'int',
@@ -37,13 +43,18 @@ export function translateWgslTypeToGlsl(wgslType: string): string {
3743
return WGSL_TO_GLSL_TYPE[wgslType] ?? wgslType;
3844
}
3945

46+
const versionMacro = tgpu['~unstable'].declare('#version 300 es');
47+
const gl_PositionSnippet = tgpu['~unstable'].rawCodeSnippet('gl_Position', d.vec4f, 'private');
48+
4049
/**
4150
* A GLSL ES 3.0 shader generator that extends WgslGenerator.
4251
* Overrides `dataType` to emit GLSL type names instead of WGSL ones,
4352
* and overrides variable declaration emission to use `type name = rhs` syntax.
4453
*/
4554
export class GlslGenerator extends WgslGenerator {
46-
public override typeAnnotation(data: d.BaseData): string {
55+
#functionType: ShaderGenerator.TgpuShaderStage | 'normal' | undefined;
56+
57+
override typeAnnotation(data: d.BaseData): string {
4758
// For WGSL identity types (scalars, vectors, common matrices), map to GLSL directly.
4859
if (!d.isLooseData(data)) {
4960
const glslName = WGSL_TO_GLSL_TYPE[data.type];
@@ -56,16 +67,81 @@ export class GlslGenerator extends WgslGenerator {
5667
return super.typeAnnotation(data);
5768
}
5869

59-
protected override emitVarDecl(
60-
pre: string,
70+
override _emitVarDecl(
6171
_keyword: 'var' | 'let' | 'const',
6272
name: string,
6373
dataType: d.BaseData | ShaderGenerator.UnknownData,
6474
rhsStr: string,
6575
): string {
66-
const glslTypeName =
67-
dataType !== ShaderGenerator.UnknownData ? this.typeAnnotation(dataType) : 'auto';
68-
return `${pre}${glslTypeName} ${name} = ${rhsStr};`;
76+
const glslTypeName = dataType !== UnknownData ? this.typeAnnotation(dataType) : 'auto';
77+
return `${this.ctx.pre}${glslTypeName} ${name} = ${rhsStr};`;
78+
}
79+
80+
override _returnStatement(statement: Return): string {
81+
if (this.#functionType !== 'normal') {
82+
const exprNode = statement[1];
83+
if (exprNode !== undefined) {
84+
// Resolving the expression to inspect it's type
85+
// We will resolve it again as part of the modifed statement
86+
const expr = this._expression(exprNode);
87+
if (expr.dataType !== UnknownData && expr.dataType.type.startsWith('vec')) {
88+
const block = super._block(
89+
[NODE.block, [[NODE.assignmentExpr, 'gl_Position', '=', exprNode], [NODE.return]]],
90+
{ gl_Position: gl_PositionSnippet.$ },
91+
);
92+
93+
return `${this.ctx.pre}${block}`;
94+
95+
// return (
96+
// super._statement([NODE.assignmentExpr, 'gl_Position', '=', exprNode]) +
97+
// '\n' +
98+
// super._returnStatement([NODE.return])
99+
// );
100+
}
101+
}
102+
}
103+
104+
return super._returnStatement(statement);
105+
}
106+
107+
override functionDefinition(options: ShaderGenerator.FunctionDefinitionOptions): string {
108+
// Ensuring the version macro is present in the shader output
109+
this.ctx.resolve(versionMacro);
110+
111+
if (options.functionType !== 'normal') {
112+
// Reserving a global name for gl_Position to avoid conflicts
113+
// TODO: This needs a better API, like a `reservedIdentifiers` array
114+
// passed to tgpu.resolve
115+
const gl_Position = {};
116+
setName(gl_Position, 'gl_Position');
117+
console.log(this.ctx.getUniqueName(gl_Position));
118+
}
119+
120+
// Function body
121+
let lastFunctionType = this.#functionType;
122+
this.#functionType = options.functionType;
123+
try {
124+
const body = this._block(options.body);
125+
126+
// Only after generating the body can we determine the return type
127+
const returnType = options.determineReturnType();
128+
129+
if (options.functionType !== 'normal') {
130+
return `void main() ${body}`;
131+
}
132+
133+
const argList = options.args
134+
// Stripping out unused arguments in entry functions
135+
.filter((arg) => arg.used || options.functionType === 'normal')
136+
.map((arg) => {
137+
return `${this.ctx.resolve(arg.decoratedType).value} ${arg.name}`;
138+
})
139+
.join(', ');
140+
141+
return `${this.ctx.resolve(returnType).value} ${options.name}(${argList}) ${body}`;
142+
} finally {
143+
this.#functionType = lastFunctionType;
144+
}
69145
}
70146
}
71147

packages/typegpu-gl/src/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,3 @@
11
export { initWithGL } from './initWithGL.ts';
22
export { initWithGLFallback } from './initWithGLFallback.ts';
3+
export { GLOptions } from './glOptions.ts';

packages/typegpu-gl/tests/glslGenerator.test.ts

Lines changed: 72 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { describe, expect, it } from 'vitest';
22
import tgpu, { d } from 'typegpu';
3+
import { GLOptions } from '@typegpu/gl';
34
import glslGenerator, { translateWgslTypeToGlsl } from '../src/glslGenerator.ts';
45

56
describe('translateWgslTypeToGlsl', () => {
@@ -48,18 +49,14 @@ describe('translateWgslTypeToGlsl', () => {
4849

4950
describe('GlslGenerator - variable declarations', () => {
5051
it('generates GLSL-style variable declarations for JS function', () => {
51-
// 'use gpu' function - uses the TGSL code generation path, which calls functionDefinition
52-
const fragFn = tgpu.fragmentFn({
53-
in: { uv: d.vec2f },
54-
out: d.vec4f,
55-
})((input) => {
52+
const main = () => {
5653
'use gpu';
5754
// A variable that uses a vector type
58-
const color = d.vec4f(input.uv[0], input.uv[1], 0, 1);
55+
const color = d.vec4f(1, 0, 0, 1);
5956
return color;
60-
});
57+
};
6158

62-
const result = tgpu.resolveWithContext([fragFn], { unstable_shaderGenerator: glslGenerator });
59+
const result = tgpu.resolveWithContext([main], GLOptions());
6360
// Should contain the resolved function code
6461
expect(result.code).toBeDefined();
6562
expect(result.code.length).toBeGreaterThan(0);
@@ -78,28 +75,38 @@ describe('GlslGenerator - variable declarations', () => {
7875
return d.vec4f(x, 0, 0, 1);
7976
});
8077

81-
const result = tgpu.resolveWithContext([fragFn], { unstable_shaderGenerator: glslGenerator });
78+
const result = tgpu.resolveWithContext([fragFn], GLOptions());
8279
expect(result.code).toBeDefined();
8380
// Variable declaration for f32 should be `float`
8481
expect(result.code).toContain('float ');
8582
});
8683
});
8784

88-
describe('GlslGenerator - functionDefinition post-processing', () => {
89-
it('translates WGSL type constructor calls in JS function body to GLSL', () => {
90-
const fragFn = tgpu.fragmentFn({
91-
in: { uv: d.vec2f },
92-
out: d.vec4f,
93-
})((input) => {
85+
describe('GlslGenerator - function definitions', () => {
86+
it('generates proper function signatures', () => {
87+
function add(a: number, b: number) {
9488
'use gpu';
95-
return d.vec4f(input.uv[0], 0.0, 0.0, 1.0);
96-
});
89+
return a + b;
90+
}
9791

98-
const result = tgpu.resolveWithContext([fragFn], { unstable_shaderGenerator: glslGenerator });
99-
// vec4f(...) in the body should become vec4(...)
100-
expect(result.code).toContain('vec4(');
101-
// Should not contain the WGSL-style constructor in the body
102-
expect(result.code).not.toMatch(/\bvec4f\s*\(/);
92+
function main() {
93+
'use gpu';
94+
return add(1.5, 1.2);
95+
}
96+
97+
const result = tgpu.resolveWithContext([main], GLOptions());
98+
99+
expect(result.code).toMatchInlineSnapshot(`
100+
"#version 300 es
101+
102+
float add(float a, float b) {
103+
return (a + b);
104+
}
105+
106+
float main() {
107+
return add(1.5f, 1.2f);
108+
}"
109+
`);
103110
});
104111

105112
it('translates vec3f to vec3 in function body', () => {
@@ -111,7 +118,7 @@ describe('GlslGenerator - functionDefinition post-processing', () => {
111118
return d.vec4f(color[0], color[1], color[2], 1.0);
112119
});
113120

114-
const result = tgpu.resolveWithContext([fragFn], { unstable_shaderGenerator: glslGenerator });
121+
const result = tgpu.resolveWithContext([fragFn], GLOptions());
115122
expect(result.code).toContain('vec3(');
116123
expect(result.code).not.toMatch(/\bvec3f\s*\(/);
117124
expect(result.code).toContain('vec4(');
@@ -127,41 +134,76 @@ describe('GlslGenerator - entry point generation with JS functions', () => {
127134
return Out({ pos: d.vec4f(0.0, 0.0, 0.0, 1.0) });
128135
});
129136

130-
const result = tgpu.resolveWithContext([vertFn], { unstable_shaderGenerator: glslGenerator });
137+
const result = tgpu.resolveWithContext([vertFn], GLOptions());
131138
expect(result.code).toBeDefined();
132139
expect(result.code.length).toBeGreaterThan(0);
133140
// The body should have translated type names
134141
expect(result.code).toContain('vec4(');
135142
expect(result.code).not.toMatch(/\bvec4f\s*\(/);
136143

137144
expect(result.code).toMatchInlineSnapshot(`
138-
"struct vertFn_Output {
145+
"#version 300 es
146+
147+
struct vertFn_Output {
139148
@builtin(position) pos: vec4,
140149
}
141150
142-
@vertex fn vertFn() -> vertFn_Output {
151+
void main() {
143152
return vertFn_Output(vec4(0, 0, 0, 1));
144153
}"
145154
`);
146155
});
147156

157+
// it('resolves a vertex function returning a position', () => {
158+
// const vertFn = tgpu.vertexFn({
159+
// out: { position: d.builtin.position },
160+
// })(() => {
161+
// 'use gpu';
162+
// return {
163+
// position: d.vec4f(1.0, 0.0, 0.0, 1.0),
164+
// };
165+
// });
166+
167+
// const result = tgpu.resolveWithContext([vertFn], GLOptions());
168+
// expect(result.code).toBeDefined();
169+
// expect(result.code).toContain('vec4(');
170+
// expect(result.code).not.toMatch(/\bvec4f\s*\(/);
171+
172+
// expect(result.code).toMatchInlineSnapshot(`
173+
// "#version 300 es
174+
175+
// void main() {
176+
// gl_Position = vec4(1, 0, 0, 1);
177+
// return;
178+
// }"
179+
// `);
180+
// });
181+
148182
it('resolves a fragment function returning a color using GLSL generator', () => {
149183
const fragFn = tgpu.fragmentFn({
150184
out: d.vec4f,
151185
})(() => {
152186
'use gpu';
187+
// This variable should get renamed to not conflict with
188+
// the global.
189+
const gl_Position = 1;
153190
return d.vec4f(1.0, 0.0, 0.0, 1.0);
154191
});
155192

156-
const result = tgpu.resolveWithContext([fragFn], { unstable_shaderGenerator: glslGenerator });
193+
const result = tgpu.resolveWithContext([fragFn], GLOptions());
157194
expect(result.code).toBeDefined();
158-
// The body should have translated vec4f → vec4
159195
expect(result.code).toContain('vec4(');
160196
expect(result.code).not.toMatch(/\bvec4f\s*\(/);
161197

162198
expect(result.code).toMatchInlineSnapshot(`
163-
"@fragment fn fragFn() -> @location(0) vec4 {
164-
return vec4(1, 0, 0, 1);
199+
"#version 300 es
200+
201+
void main() {
202+
int gl_Position_1 = 1;
203+
{
204+
gl_Position = vec4(1, 0, 0, 1);
205+
return;
206+
}
165207
}"
166208
`);
167209
});

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

Lines changed: 4 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -201,8 +201,10 @@ export function createFnCore(
201201

202202
// generate wgsl string
203203

204-
const { code, returnType: actualReturnType } = ctx.fnToWgsl({
204+
const { code, returnType: actualReturnType } = ctx.resolveFunction({
205205
functionType,
206+
name: id,
207+
workgroupSize,
206208
argTypes,
207209
entryInput,
208210
params: ast.params,
@@ -211,16 +213,7 @@ export function createFnCore(
211213
externalMap,
212214
});
213215

214-
let attributes = '';
215-
if (functionType === 'compute') {
216-
attributes = `@compute @workgroup_size(${workgroupSize?.join(', ')}) `;
217-
} else if (functionType === 'vertex') {
218-
attributes = `@vertex `;
219-
} else if (functionType === 'fragment') {
220-
attributes = `@fragment `;
221-
}
222-
223-
ctx.addDeclaration(`${attributes}fn ${id}${code}`);
216+
ctx.addDeclaration(code);
224217

225218
return snip(id, actualReturnType, /* origin */ 'runtime');
226219
},

packages/typegpu/src/resolutionCtx.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ import wgslGenerator from './tgsl/wgslGenerator.ts';
3939
import type {
4040
ExecMode,
4141
ExecState,
42-
FnToWgslOptions,
42+
ResolveFunctionOptions,
4343
FunctionArgumentAccess,
4444
FunctionScopeLayer,
4545
ItemLayer,
@@ -466,7 +466,7 @@ export class ResolutionCtxImpl implements ResolutionCtx {
466466
return this.#logGenerator.logResources;
467467
}
468468

469-
fnToWgsl(options: FnToWgslOptions): { code: string; returnType: BaseData } {
469+
resolveFunction(options: ResolveFunctionOptions): { code: string; returnType: BaseData } {
470470
let fnScopePushed = false;
471471

472472
try {
@@ -584,6 +584,8 @@ export class ResolutionCtxImpl implements ResolutionCtx {
584584

585585
const code = this.gen.functionDefinition({
586586
functionType: options.functionType,
587+
name: options.name,
588+
workgroupSize: options.workgroupSize,
587589
args,
588590
body: options.body,
589591
determineReturnType: () => {

packages/typegpu/src/tgsl/shaderGenerator_members.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,12 @@ import type { FunctionArgument, TgpuShaderStage } from '../types.ts';
55
export { UnknownData } from '../data/dataTypes.ts';
66

77
// types
8-
export type { ResolutionCtx, FunctionArgument } from '../types.ts';
8+
export type { ResolutionCtx, FunctionArgument, TgpuShaderStage } from '../types.ts';
99

1010
export interface FunctionDefinitionOptions {
1111
readonly functionType: 'normal' | TgpuShaderStage;
12+
readonly name: string;
13+
readonly workgroupSize?: readonly number[] | undefined;
1214
readonly args: readonly FunctionArgument[];
1315
readonly body: Block;
1416

0 commit comments

Comments
 (0)