Skip to content
Open
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
1 change: 1 addition & 0 deletions packages/typegpu-testing-utility/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
"test:types": "pnpm tsc --p ./tsconfig.json --noEmit"
},
"dependencies": {
"tinyest": "workspace:*",
"typegpu": "workspace:*"
},
"devDependencies": {
Expand Down
45 changes: 45 additions & 0 deletions packages/typegpu-testing-utility/src/capture.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import { UnknownData, WgslGenerator, type Snippet, dualImpl } from 'typegpu/~internal';
import * as tinyest from 'tinyest';
Comment thread
pullfrog[bot] marked this conversation as resolved.
import { tgpu, type TgpuFn } from 'typegpu';

const { NodeTypeCatalog: NODE } = tinyest;

export class CapturingGenerator extends WgslGenerator {
public capturedSnippets: Snippet[] = [];

protected _expression(expression: tinyest.Expression): Snippet {
if (Array.isArray(expression) && expression[0] === NODE.call) {
const [_, calleeNode, argNodes] = expression;
const callee = this._expression(calleeNode);
if (callee.value === CAPTURE) {
const snippet = this._expression(argNodes[0]);
this.capturedSnippets.push(snippet);
return snippet;
}
}
return super._expression(expression);
}
}

export const CAPTURE = dualImpl({
name: 'CAPTURE',
signature: (arg) => ({ argTypes: [arg], returnType: arg }),
normalImpl: <T>(expr: T): T => expr,
codegenImpl: (ctx, [expr]) => ctx.resolveSnippet(expr).value,
sideEffects: false,
});

export function captureSnippets(fn: TgpuFn | (() => unknown)) {
const generator = new CapturingGenerator();

tgpu.resolve([fn], { unstable_shaderGenerator: generator });

return generator.capturedSnippets;
}

export function simplifyType(snippet: Snippet) {
return {
...snippet,
dataType: snippet.dataType === UnknownData ? 'UnknownData' : snippet.dataType.type,
};
}
1 change: 1 addition & 0 deletions packages/typegpu-testing-utility/src/index.ts
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
export { it, test } from './extendedIt.ts';
export { CAPTURE, captureSnippets, simplifyType } from './capture.ts';
1 change: 1 addition & 0 deletions packages/typegpu/src/internal.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ export { UnknownData } from './data/dataTypes.ts';
export { getName } from './shared/meta.ts';
export { WgslGenerator } from './tgsl/wgslGenerator.ts';
export { snip } from './data/snippet.ts';
export { dualImpl } from './core/function/dualImpl.ts';

// types
export type { ResolutionCtx, FunctionArgument, TgpuShaderStage } from './types.ts';
Expand Down
122 changes: 122 additions & 0 deletions packages/typegpu/tests/internal/capturedSnippets.test.ts

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.

  it('works with typedExpression - struct', () => {
    const Boid = d.struct({
      pos: d.vec3f,
    });

    const fn = tgpu.fn(
      [],
      Boid,
    )(() => {
      'use gpu';
      return CAPTURE({ pos: d.vec3f() });
    });

    const captured = captureSnippets(fn);
    expect(captured[0]?.value).toMatchInlineSnapshot(`"Boid(vec3f())"`);
  });

  it('works with typedExpression - numeric', () => {
    const fn = tgpu.fn(
      [],
      d.u32,
    )(() => {
      'use gpu';
      return CAPTURE(d.f32(1.67));
    });

    const captured = captureSnippets(fn);
    expect(captured[0]?.value).toMatchInlineSnapshot(`1.6699999570846558`);
  });

Could you add these tests?
The second test needs to be fixed.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

So I think we decided that this is intended behavior, and that we want to remove expected type stack, right? I created an issue for that: #2726

Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
import { describe, expect } from 'vitest';
import { tgpu, d } from 'typegpu';
import { CAPTURE, captureSnippets, it, simplifyType } from 'typegpu-testing-utility';

describe('CAPTURE', () => {
it('is a no-op in regular resolves', () => {
const fn = tgpu.fn([d.u32])((x) => {
'use gpu';
const a = CAPTURE(1 + 2);
const b = CAPTURE(a + 1);
const c = CAPTURE(x);
const d = CAPTURE(CAPTURE(1));
});

expect(tgpu.resolve([fn])).toMatchInlineSnapshot(`
"fn fn_1(x: u32) {
const a = 3;
let b = (a + 1i);
let c = x;
const d = 1;
}"
`);
});

it('allows snippet extraction', () => {
const fn = tgpu.fn([d.u32])((x) => {
'use gpu';
const a = CAPTURE(1 + 2);
const b = CAPTURE(a + 1);
const c = CAPTURE(x);
const d = CAPTURE(CAPTURE(1) + (c + x));
});

expect(captureSnippets(fn).map(simplifyType)).toMatchInlineSnapshot(`
[
{
"dataType": "abstractInt",
"origin": "constant",
"possibleSideEffects": false,
"value": 3,
},
{
"dataType": "i32",
"origin": "runtime",
"possibleSideEffects": false,
"value": "(a + 1i)",
},
{
"dataType": "u32",
"origin": "argument",
"possibleSideEffects": false,
"value": "x",
},
{
"dataType": "abstractInt",
"origin": "constant",
"possibleSideEffects": false,
"value": 1,
},
{
"dataType": "u32",
"origin": "runtime",
"possibleSideEffects": false,
"value": "(1u + (c + x))",
},
]
`);
});

it('recaptures when called a second time', () => {
let count = 0;
const lazy = tgpu.lazy(() => count++);
const fn = () => {
'use gpu';
return CAPTURE(lazy.$);
};

expect(captureSnippets(fn)[0]?.value).toBe(0);
expect(captureSnippets(fn)[0]?.value).toBe(1);
expect(captureSnippets(fn)[0]?.value).toBe(2);
});

it('captures inner to outer', () => {
const fn = () => {
'use gpu';
return CAPTURE(CAPTURE(1) + 2);
};

const captured = captureSnippets(fn);
expect(captured[0]?.value).toBe(1);
expect(captured[1]?.value).toBe(3);
});

it('captures structs after casting', () => {
const Boid = d.struct({
pos: d.vec3f,
});

const fn = tgpu.fn(
[],
Boid,
)(() => {
'use gpu';
return CAPTURE({ pos: d.vec3f() });
});

const captured = captureSnippets(fn);
expect(captured[0]?.dataType).toBe(Boid);
});

it('captures before type casting', () => {
const fn = tgpu.fn(
[],
d.u32,
)(() => {
'use gpu';
return CAPTURE(1.5);
});

expect(captureSnippets(fn)[0]?.value).toBe(1.5);
});
});
3 changes: 3 additions & 0 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading