Skip to content

feat: Route function calls through the shader generator - #2800

Open
iwoplaza wants to merge 1 commit into
fix/three-reentrancyfrom
feat/fn-call-through-gen
Open

feat: Route function calls through the shader generator#2800
iwoplaza wants to merge 1 commit into
fix/three-reentrancyfrom
feat/fn-call-through-gen

Conversation

@iwoplaza

@iwoplaza iwoplaza commented Aug 6, 2026

Copy link
Copy Markdown
Collaborator

No description provided.

@github-actions

github-actions Bot commented Aug 6, 2026

Copy link
Copy Markdown

pkg.pr.new

packages
Ready to be installed by your favorite package manager ⬇️

https://pkg.pr.new/software-mansion/TypeGPU/eslint-plugin-typegpu@601de134a349a92751b5f56e4841490275fbaf07
https://pkg.pr.new/software-mansion/TypeGPU/tgpu-gen@601de134a349a92751b5f56e4841490275fbaf07
https://pkg.pr.new/software-mansion/TypeGPU/tinyest-for-wgsl@601de134a349a92751b5f56e4841490275fbaf07
https://pkg.pr.new/software-mansion/TypeGPU/typegpu@601de134a349a92751b5f56e4841490275fbaf07
https://pkg.pr.new/software-mansion/TypeGPU/@typegpu/cli@601de134a349a92751b5f56e4841490275fbaf07
https://pkg.pr.new/software-mansion/TypeGPU/@typegpu/color@601de134a349a92751b5f56e4841490275fbaf07
https://pkg.pr.new/software-mansion/TypeGPU/@typegpu/gl@601de134a349a92751b5f56e4841490275fbaf07
https://pkg.pr.new/software-mansion/TypeGPU/@typegpu/noise@601de134a349a92751b5f56e4841490275fbaf07
https://pkg.pr.new/software-mansion/TypeGPU/@typegpu/radiance-cascades@601de134a349a92751b5f56e4841490275fbaf07
https://pkg.pr.new/software-mansion/TypeGPU/@typegpu/react@601de134a349a92751b5f56e4841490275fbaf07
https://pkg.pr.new/software-mansion/TypeGPU/@typegpu/sdf@601de134a349a92751b5f56e4841490275fbaf07
https://pkg.pr.new/software-mansion/TypeGPU/@typegpu/three@601de134a349a92751b5f56e4841490275fbaf07
https://pkg.pr.new/software-mansion/TypeGPU/unplugin-typegpu@601de134a349a92751b5f56e4841490275fbaf07

benchmark
view benchmark

commit
view commit

@github-actions

github-actions Bot commented Aug 6, 2026

Copy link
Copy Markdown

Resolution Time Benchmark

---
config:
  themeVariables:
    xyChart:
      plotColorPalette: "#E63946, #3B82F6, #059669"
---
xychart
  title "Random Branching (🔴 PR | 🔵 main | 🟢 release)"
  x-axis "max depth" [1, 2, 3, 4, 5, 6, 7, 8]
  y-axis "time (ms)"
  line [0.93, 1.87, 4.30, 6.34, 7.63, 12.61, 22.61, 25.36]
  line [0.97, 2.01, 4.13, 6.98, 7.30, 12.35, 24.10, 26.60]
  line [0.99, 2.04, 4.50, 7.24, 8.60, 14.33, 22.36, 26.40]
Loading
---
config:
  themeVariables:
    xyChart:
      plotColorPalette: "#E63946, #3B82F6, #059669"
---
xychart
  title "Linear Recursion (🔴 PR | 🔵 main | 🟢 release)"
  x-axis "max depth" [1, 2, 3, 4, 5, 6, 7, 8]
  y-axis "time (ms)"
  line [0.30, 0.50, 0.69, 0.80, 1.06, 1.10, 1.34, 1.49]
  line [0.31, 0.54, 0.68, 0.83, 1.23, 1.24, 1.43, 1.53]
  line [0.36, 0.53, 0.73, 0.90, 1.26, 1.27, 1.51, 1.69]
Loading
---
config:
  themeVariables:
    xyChart:
      plotColorPalette: "#E63946, #3B82F6, #059669"
---
xychart
  title "Full Tree (🔴 PR | 🔵 main | 🟢 release)"
  x-axis "max depth" [1, 2, 3, 4, 5, 6, 7, 8]
  y-axis "time (ms)"
  line [1.00, 2.11, 4.11, 6.83, 12.42, 27.86, 57.14, 114.01]
  line [0.83, 2.06, 4.67, 7.16, 12.48, 26.73, 56.18, 117.10]
  line [0.83, 2.14, 4.14, 7.35, 13.80, 28.25, 59.51, 122.76]
Loading

@github-actions

github-actions Bot commented Aug 6, 2026

Copy link
Copy Markdown

Bundle size comparison (import * as ... in PR vs import * as ... in target):

🟢 Decreased (max -0.28%) ➖ Unchanged 🔴 Increased (max 0.14%) ❔ Unknown
5 131 186 0

import { ... } in PR vs import * as ... in PR (is the library tree-Shakeable?):

Test tsdown
tgpu_init.ts 267.81 kB ($${\color{green}-3.3\%}$$)
tgpu_initFromDevice.ts 267.28 kB ($${\color{green}-3.5\%}$$)
tgpu_resolve.ts 168.58 kB ($${\color{green}-39.1\%}$$)
tgpu_resolveWithContext.ts 168.51 kB ($${\color{green}-39.1\%}$$)
tgpu_bindGroupLayout.ts 73.79 kB ($${\color{green}-73.3\%}$$)
tgpu_mutableAccessor.ts 68.52 kB ($${\color{green}-75.3\%}$$)
tgpu_accessor.ts 68.52 kB ($${\color{green}-75.3\%}$$)
tgpu_privateVar.ts 67.21 kB ($${\color{green}-75.7\%}$$)
tgpu_workgroupVar.ts 67.21 kB ($${\color{green}-75.7\%}$$)
tgpu_const.ts 66.63 kB ($${\color{green}-75.9\%}$$)
tgpu_lazy.ts 66.42 kB ($${\color{green}-76.0\%}$$)
tgpu_fragmentFn.ts 38.92 kB ($${\color{green}-85.9\%}$$)
tgpu_fn.ts 38.86 kB ($${\color{green}-86.0\%}$$)
tgpu_vertexFn.ts 38.73 kB ($${\color{green}-86.0\%}$$)
tgpu_computeFn.ts 38.44 kB ($${\color{green}-86.1\%}$$)
tgpu_vertexLayout.ts 27.57 kB ($${\color{green}-90.0\%}$$)
tgpu_comptime.ts 15.18 kB ($${\color{green}-94.5\%}$$)
tgpu_unroll.ts 1.75 kB ($${\color{green}-99.4\%}$$)
tgpu_slot.ts 1.70 kB ($${\color{green}-99.4\%}$$)

If you wish to run a comparison for other, slower bundlers, run the 'Tree-shake test' from the GitHub Actions menu.

@pullfrog pullfrog Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ℹ️ No critical issues — one GLSL coverage gap to note inline.

Reviewed changes

This PR adds a call() method to ShaderGenerator, routes standard function calls (select, bitcast, arrayLength) through it, and overrides the method in GlslGenerator to translate select and bitcast into valid GLSL ES 3.0 constructs. It also threads the concrete return type into dualImpl codegen callbacks and adds snapshot tests for the GLSL output.

  • Added ShaderGenerator.call() and implemented it in WgslGenerator.
  • Overrode call() in GlslGenerator to translate bitcast and select to GLSL equivalents.
  • Updated dualImpl to pass the concrete return type into codegenImpl.
  • Migrated arrayLength and the bitcast family to use ctx.gen.call(...).
  • Added GLSL snapshot tests for scalar/vector select and bitcast.

Pullfrog  | Fix all ➔Fix 👍s ➔View workflow run | Using Kimi K2 (free via Pullfrog for OSS) | 𝕏

throw new Error(`Invalid number of arguments for 'select'`);
}

if (falsy.dataType !== UnknownData && falsy.dataType.type.startsWith('vec')) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

GLSL ES 3.0 only supports mix(genType, genType, genBType) for floating-point vectors, not for genIType or genUType. This means integer-vector std.select will still emit invalid GLSL here (it was already invalid before this PR, just via select(...)). Consider guarding non-float vector selects or adding a dedicated integer/unsigned path.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

???

Image

@cieplypolar cieplypolar left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good. However, if someone ignores the select types, it will fail in the browser instead of during code generation.

@iwoplaza
iwoplaza force-pushed the feat/fn-call-through-gen branch from 333ec6b to a8d9f1c Compare August 7, 2026 07:31

@pullfrog pullfrog Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ℹ️ No new issues — prior coverage gap remains open.

Reviewed changes

Re-reviewed the single commit now at the head of this PR. The diff still adds ShaderGenerator.call(), routes standard function calls through it, and overrides the method in GlslGenerator for bitcast and select. No new issues were introduced in this push.

  • Added ShaderGenerator.call() and implemented it in WgslGenerator.
  • Overrode call() in GlslGenerator to translate bitcast and select to GLSL equivalents.
  • Updated dualImpl to pass the concrete return type into codegenImpl.
  • Migrated arrayLength and the bitcast family to use ctx.gen.call(...).
  • Added GLSL snapshot tests for scalar/vector select and bitcast.

The previously noted Pullfrog thread about GLSL integer-vector select emitting invalid mix(...) calls remains unresolved in this commit; it is still a documented coverage gap.

Pullfrog  | Fix it ➔View workflow run | Using Kimi K2 (free via Pullfrog for OSS) | 𝕏

@iwoplaza

iwoplaza commented Aug 7, 2026

Copy link
Copy Markdown
Collaborator Author

However, if someone ignores the select types, it will fail in the browser instead of during code generation.

@cieplypolar How so? 👀

@iwoplaza
iwoplaza force-pushed the feat/fn-call-through-gen branch from a8d9f1c to 3a162b9 Compare August 7, 2026 12:23
@pullfrog

pullfrog Bot commented Aug 7, 2026

Copy link
Copy Markdown
Contributor

no API key found — this repo is configured to use moonshotai/kimi-k2.7-code, which needs MOONSHOT_API_KEY, but the runner has no key for it.

To fix: add the key as a GitHub Actions secret (referenced from your workflow's env: block) or as a Pullfrog secret in the console — or switch this repo to a different model (free models need no key).

Open repo secrets → · Configure model → · Setup docs → · Ask in Discord →

Pullfrog  | Rerun failed job ➔View workflow run | via Pullfrog | Using Kimi K2 (free via Pullfrog for OSS) | 𝕏

@cieplypolar

Copy link
Copy Markdown
Collaborator

However, if someone ignores the select types, it will fail in the browser instead of during code generation.

@cieplypolar How so? 👀

it('...', () => {
  function foo() {
    'use gpu';
    const cond = d.vec3b(false, true, false);
    // @ts-ignore
    return std.select(0, 1, cond);
  }

  expect(tgpu.resolve([foo], glOptions())).toMatchInlineSnapshot(`
    "int foo() {
      bvec3 cond = bvec3(false, true, false);
      return (cond ? 1i : 0i);
    }"
  `);
});

Base automatically changed from feat/custom-language-key-in-shader-generator to main August 9, 2026 07:26
@iwoplaza
iwoplaza force-pushed the feat/fn-call-through-gen branch from 3a162b9 to 79e09df Compare August 9, 2026 07:32
Copilot AI lite review requested due to automatic review settings August 9, 2026 07:41
@iwoplaza
iwoplaza force-pushed the feat/fn-call-through-gen branch from 79e09df to 0fa5849 Compare August 9, 2026 07:41
@iwoplaza
iwoplaza changed the base branch from main to fix/three-reentrancy August 9, 2026 07:41

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR introduces a ShaderGenerator.call(...) hook and routes selected standard-library function codegen through it, enabling backend-specific lowering (notably WGSL→GLSL) of calls like select, saturate, and bitcast.

Changes:

  • Added call(...) to the ShaderGenerator API and implemented it in WgslGenerator.
  • Updated dualImpl to pass the (concretized) return type into codegenImpl, enabling call generation that depends on the return type (e.g. bitcast<T>(...)).
  • Refactored several std functions to use ctx.gen.call(...), and added GLSL-specific lowering plus tests for standard function calls.

Reviewed changes

Copilot reviewed 9 out of 9 changed files in this pull request and generated 1 comment.

Show a summary per file
File Description
packages/typegpu/src/tgsl/wgslGenerator.ts Adds default call(...) emission for WGSL-style function calls (with optional template params).
packages/typegpu/src/tgsl/shaderGenerator.ts Extends generator interface with call(...) to support backend-specific call routing.
packages/typegpu/src/std/numeric.ts Routes std.saturate codegen through ctx.gen.call(...).
packages/typegpu/src/std/boolean.ts Routes std.select codegen through ctx.gen.call(...) so backends can lower it differently.
packages/typegpu/src/std/bitcast.ts Routes bitcast-related codegen through ctx.gen.call(...) and passes return type as a template param.
packages/typegpu/src/std/array.ts Routes arrayLength(...) call emission through ctx.gen.call(...).
packages/typegpu/src/core/function/dualImpl.ts Extends codegen signature to include return type and ensures it’s concretized once and reused.
packages/typegpu-gl/tests/glslGenerator.test.ts Adds GLSL snapshot tests covering select, saturate, and bitcast lowering.
packages/typegpu-gl/src/glslGenerator.ts Overrides call(...) to lower select/saturate/bitcast into GLSL equivalents.

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment on lines +178 to +180
// Generating a ternary expression, which is supported in GLSL
return `(${this.ctx.resolveSnippet(cond).value} ? ${this.ctx.resolveSnippet(truthy).value} : ${this.ctx.resolveSnippet(falsy).value})`;
}

@pullfrog pullfrog Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Important

The new saturateclamp translation emits 0f/1f, which are not valid GLSL ES 3.0 float literals — std.saturate through @typegpu/gl will fail to compile in the browser.

Reviewed changes

Re-reviewed the delta since the prior Pullfrog review (a8d9f1cb). The branch was rebased onto a1a6d7e4; the only net change to the PR's own files is the new saturate routing.

  • Routed std.saturate through ctx.gen.call('saturate', ...) in std/numeric.ts (WGSL output unchanged: saturate(x)).
  • Added a saturate case to GlslGenerator.call() that rewrites saturate(x) as clamp(x, 0f, 1f).
  • Added a GLSL snapshot test covering scalar and vector saturate.

Pullfrog  | Fix all ➔Fix 👍s ➔View workflow run | Using DeepSeek Flash (free via Pullfrog for OSS) | 𝕏

if (!arg) {
throw new Error(`Invalid number of arguments for 'saturate'`);
}
return super.call('clamp', [], [arg, snip(0, d.f32, 'constant'), snip(1, d.f32, 'constant')]);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This emits clamp(x, 0f, 1f), but 0f/1f are not valid GLSL ES 3.0 literals — GLSL float constants require a decimal point (or exponent). Verified with glslang (Khronos reference): both clamp(float(v), 0f, 1f) and clamp(vec3, 0f, 1f) fail with "float literal needs a decimal point or exponent"; with 0.0/1.0 the same calls compile. The new snapshot test pins exactly this broken output, so std.saturate via @typegpu/gl will fail at shader-compile time at runtime.

Suggested change
return super.call('clamp', [], [arg, snip(0, d.f32, 'constant'), snip(1, d.f32, 'constant')]);
const [arg] = args;
if (!arg) {
throw new Error(`Invalid number of arguments for 'saturate'`);
}
return super.call(
'clamp',
[],
[arg, snip(0.0, d.f32, 'constant'), snip(1.0, d.f32, 'constant')],
);

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

frog is right about 0f and 1f constants, but sadly its fix won't work

signature: unifyRestrictedSignature(anyFloat),
normalImpl: cpuSaturate,
codegenImpl: (_ctx, [value]) => stitch`saturate(${value})`,
codegenImpl: (ctx, [value]) => ctx.gen.call('saturate', [], [value]),

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Routing saturate through ctx.gen.call('saturate', ...) is correct for the WGSL generator, but it newly enables the GLSL path above to emit invalid literals. If the GLSL translation stays this commit, the WGSL snapshots are unaffected (still saturate(x)), so the regression is confined to the GLSL generator.

@iwoplaza
iwoplaza force-pushed the feat/fn-call-through-gen branch from 0fa5849 to d7d7a14 Compare August 9, 2026 18:55
@iwoplaza
iwoplaza force-pushed the feat/fn-call-through-gen branch from d7d7a14 to 59691a7 Compare August 10, 2026 10:06
@iwoplaza
iwoplaza force-pushed the feat/fn-call-through-gen branch from 59691a7 to 601de13 Compare August 10, 2026 10:16
if (dataType.type.includes('3')) {
return d.vec3b;
}
return d.vec4b;

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit (just in case we accidentally use it in a function that accepts numbers)

Suggested change
return d.vec4b;
if (dataType.type.includes('4')) {
return vec4b;
}
throw new Error(
`Internal error: schema of type '${dataType.type}' does not have a corresponding boolean vector.`,
);

return snip(base, schema, /* origin */ 'constant', false);
}

public call(name: string, templateParams: readonly Snippet[], args: readonly Snippet[]): string {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we plan on routing all calls through here? Is there an issue for tracking that?

normalImpl: cpuSelect,
codegenImpl: (ctx, [f, t, cond]) => {
const result = stitch`select(${f}, ${t}, ${cond})`;
const result = ctx.gen.call('select', [], [f, t, cond]);

@aleksanderkatan aleksanderkatan Aug 10, 2026

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wonder if this is the best approach.

How about we aim for an api like this?:

// numeric.ts
export const dot = 1//...;

// numericGl.ts
polyfillGl(dot, (fn) => ({
  signature: (...args) => { 
    if (isNumeric(args[0])) { throw new Error(...) } 
    return fn.signature(args);
  },
  codegenImpl: (_ctx, [lhs, rhs]) => stitch`dot(${lhs}, ${rhs})`, // or a ctx.gen.call
}))

Or we could also try to extract the logic straight from glslgenerator to a list/map of polyfills.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants