Skip to content

Commit 0d1419f

Browse files
committed
Merge branch 'main' into fix/fn-infer-gpu
2 parents be1b20f + 8ff367f commit 0d1419f

348 files changed

Lines changed: 15999 additions & 8350 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

apps/bun-example/preload.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
11
import { plugin } from 'bun';
22
import typegpu from 'unplugin-typegpu/bun';
33

4-
void plugin(typegpu({ include: /\.(ts)$/ }));
4+
void plugin(typegpu());

apps/resolution-time/README.md

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
# Procedural TypeGPU function generator for benchmarking resolution time
2+
3+
## Adding a new instruction
4+
5+
Each instruction is a `tgpu.comptime` that returns a `tgpu.fn(() => void)`. There are two kinds: **leaf** (no function calls) and **recursive** (branches into other instructions).
6+
7+
### Leaf instruction
8+
9+
```ts
10+
const myLeafFn = tgpu.comptime(() => {
11+
return tgpu.fn(() => {
12+
'use gpu';
13+
// ...
14+
popDepth(); // REQUIRED — always call at the end
15+
}).$name('myLeafFn');
16+
});
17+
```
18+
19+
### Recursive instruction
20+
21+
Use `tgpu.unroll` over `arrayForUnroll(BRANCHING)` and call `instructions[choice()]()()` to branch into other instructions. The `choice()` function handles depth tracking and picks a leaf when at max depth.
22+
23+
```ts
24+
const myRecursiveFn = tgpu.comptime(() => {
25+
return tgpu.fn(() => {
26+
'use gpu';
27+
// ...
28+
for (const _i of tgpu.unroll(arrayForUnroll(BRANCHING))) {
29+
instructions[choice()]()();
30+
}
31+
popDepth(); // REQUIRED — always call at the end, after the unroll
32+
}).$name('myRecursiveFn');
33+
});
34+
```
35+
36+
## Generating Mermaid charts
37+
38+
`generateChart.ts` reads benchmark result JSON files and outputs a Mermaid xychart comparing up to 3 datasets.
39+
40+
### Usage
41+
42+
```sh
43+
node generateChart.ts \
44+
--input "PR:pr-branch/results-max-depth.json" \
45+
--input "main:main-branch/results-max-depth.json" \
46+
--title "Resolution Time vs Max Depth" \
47+
--xAxisTitle "max depth" \
48+
--yAxisTitle "time (ms)" \
49+
--output chart.md
50+
```
51+
52+
### Arguments
53+
54+
| Argument | Required | Description |
55+
|---|---|---|
56+
| `--input <label>:<path>` | yes (1-3) | `label` appears in the legend, `path` points to a benchmark results JSON file generated by `procedural.ts` |
57+
| `--title <text>` | yes | chart title |
58+
| `--xAxisTitle <text>` | no | x-axis title |
59+
| `--yAxisTitle <text>` | no | y-axis title |
60+
| `--output <path>` | no | write markdown to a file instead of stdout |
61+
62+
Each `--input` dataset is plotted as a separate line on the same chart. Colors are assigned in order: red, blue, green.

apps/resolution-time/bunfig.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
preload = ["./preload.ts"]
Lines changed: 112 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,112 @@
1+
import { readFileSync, writeFileSync } from 'node:fs';
2+
import type { BenchmarkResult } from './procedural.ts';
3+
4+
interface Dataset {
5+
label: string;
6+
data: BenchmarkResult[];
7+
}
8+
9+
const args = process.argv.slice(2);
10+
11+
const inputs: Dataset[] = [];
12+
let title = '';
13+
let output = '';
14+
let xAxisTitle = '';
15+
let yAxisTitle = '';
16+
17+
// parsing arguments
18+
for (let i = 0; i < args.length; i++) {
19+
if (args[i] === '--input') {
20+
// oxlint-disable-next-line typescript/no-non-null-assertion
21+
const [label, filepath] = args[++i]!.split(':', 2);
22+
inputs.push({
23+
label: label as string,
24+
data: JSON.parse(readFileSync(filepath as string, 'utf8')),
25+
});
26+
} else if (args[i] === '--title') {
27+
if (title !== '') {
28+
throw new Error('Ambiguous title');
29+
}
30+
// oxlint-disable-next-line typescript/no-non-null-assertion
31+
title = args[++i]!;
32+
} else if (args[i] === '--output') {
33+
if (output !== '') {
34+
throw new Error('Ambiguous output');
35+
}
36+
// oxlint-disable-next-line typescript/no-non-null-assertion
37+
output = args[++i]!;
38+
} else if (args[i] === '--xAxisTitle') {
39+
if (xAxisTitle !== '') {
40+
throw new Error('Ambiguous xAxisTitle');
41+
}
42+
// oxlint-disable-next-line typescript/no-non-null-assertion
43+
xAxisTitle = args[++i]!;
44+
} else if (args[i] === '--yAxisTitle') {
45+
if (yAxisTitle !== '') {
46+
throw new Error('Ambiguous yAxisTitle');
47+
}
48+
// oxlint-disable-next-line typescript/no-non-null-assertion
49+
yAxisTitle = args[++i]!;
50+
}
51+
}
52+
53+
if (inputs.length === 0) {
54+
throw new Error('No input path provided');
55+
}
56+
57+
if (inputs.length > 3) {
58+
throw new Error('Inputs are limited to 3');
59+
}
60+
61+
if (title === '') {
62+
throw new Error('No title provided');
63+
}
64+
65+
function median(values: number[]): number {
66+
// oxlint-disable-next-line eslint-plugin-unicorn(no-array-sort)
67+
const sorted = values.sort((a, b) => a - b);
68+
const mid = Math.floor(sorted.length / 2);
69+
// oxlint-disable-next-line typescript/no-non-null-assertion
70+
return sorted.length % 2 ? sorted[mid]! : (sorted[mid - 1]! + sorted[mid]!) / 2;
71+
}
72+
73+
// oxlint-disable-next-line eslint-plugin-unicorn(no-array-sort)
74+
const allDepths = [...new Set(inputs.flatMap((ds) => ds.data.map((d) => d.maxDepth)))].sort(
75+
(a, b) => a - b,
76+
);
77+
78+
// mermaid chart
79+
const lines: string[] = [];
80+
81+
const symbols = ['\u{1f534}', '\u{1f535}', '\u{1f7e2}'];
82+
const legend = inputs.map((ds, i) => `${symbols[i]} ${ds.label}`).join(' | ');
83+
84+
lines.push('```mermaid');
85+
lines.push(`---
86+
config:
87+
themeVariables:
88+
xyChart:
89+
plotColorPalette: "#E63946, #3B82F6, #059669"
90+
---`);
91+
lines.push('xychart');
92+
lines.push(` title "${title} (${legend})"`);
93+
lines.push(` x-axis "${xAxisTitle ? xAxisTitle : ' '}" [${allDepths.join(', ')}]`);
94+
lines.push(` y-axis "${yAxisTitle ? yAxisTitle : ' '}"`);
95+
96+
for (const ds of inputs) {
97+
const medians = allDepths.map((depth) => {
98+
const times = ds.data.filter((d) => d.maxDepth === depth).map((d) => d.timeMs);
99+
return times.length ? median(times).toFixed(2) : '0';
100+
});
101+
lines.push(` line [${medians.join(', ')}]`);
102+
}
103+
104+
lines.push('```');
105+
106+
const md = lines.join('\n');
107+
108+
if (output) {
109+
writeFileSync(output, md);
110+
} else {
111+
console.log(md);
112+
}

apps/resolution-time/package.json

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
{
2+
"name": "resolution-time",
3+
"version": "0.0.0",
4+
"private": true,
5+
"description": "Resolution time benchmark app for TypeGPU",
6+
"type": "module",
7+
"scripts": {
8+
"resolution-time": "bun procedural.ts"
9+
},
10+
"dependencies": {
11+
"typegpu": "workspace:*"
12+
},
13+
"devDependencies": {
14+
"bun": "^1.3.10",
15+
"unplugin-typegpu": "workspace:*"
16+
}
17+
}

apps/resolution-time/preload.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
import { plugin } from 'bun';
2+
import typegpu from 'unplugin-typegpu/bun';
3+
4+
void plugin(typegpu({}));

0 commit comments

Comments
 (0)