Skip to content

Commit f512419

Browse files
authored
docs: Refactor the translator and add TGSL mode (#1529)
1 parent 8799e4f commit f512419

File tree

16 files changed

+664
-389
lines changed

16 files changed

+664
-389
lines changed

apps/typegpu-docs/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
"@astrojs/starlight": "^0.35.1",
1616
"@astrojs/starlight-tailwind": "^4.0.1",
1717
"@astrojs/tailwind": "^6.0.2",
18+
"@babel/standalone": "^7.27.0",
1819
"@loaders.gl/core": "^4.3.4",
1920
"@loaders.gl/obj": "^4.3.4",
2021
"@monaco-editor/react": "^4.7.0",
Lines changed: 132 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,132 @@
1+
import { Editor } from '@monaco-editor/react';
2+
import { useAtom, useAtomValue, useSetAtom } from 'jotai';
3+
import { useEffect, useId } from 'react';
4+
import { TranslatorHeader } from './components/TranslatorHeader.tsx';
5+
import { TRANSLATOR_MODES } from './lib/constants.ts';
6+
import {
7+
editableEditorOptions,
8+
LANGUAGE_MAP,
9+
readOnlyEditorOptions,
10+
setupMonacoEditor,
11+
} from './lib/editorConfig.ts';
12+
import {
13+
editorLoadingAtom,
14+
formatAtom,
15+
initializeAtom,
16+
modeAtom,
17+
outputAtom,
18+
tgslCodeAtom,
19+
wgslCodeAtom,
20+
} from './lib/translatorStore.ts';
21+
import { useAutoCompile } from './lib/useAutoCompile.ts';
22+
23+
interface EditorSectionProps {
24+
id: string;
25+
title: string;
26+
language: string;
27+
value: string;
28+
onChange?: (value: string) => void;
29+
readOnly?: boolean;
30+
onMount?: () => void;
31+
}
32+
33+
function EditorSection({
34+
id,
35+
title,
36+
language,
37+
value,
38+
onChange,
39+
readOnly,
40+
onMount,
41+
}: EditorSectionProps) {
42+
return (
43+
<section className='flex min-h-[24rem] flex-col lg:min-h-0'>
44+
<h2
45+
id={id}
46+
className='mb-2 font-medium text-gray-300 text-sm'
47+
>
48+
{title}
49+
</h2>
50+
<Editor
51+
language={language}
52+
value={value}
53+
onChange={onChange ? (v) => onChange(v || '') : undefined}
54+
theme='vs-dark'
55+
beforeMount={language === 'typescript' ? setupMonacoEditor : undefined}
56+
onMount={onMount}
57+
loading={
58+
<div className='flex items-center justify-center text-gray-400'>
59+
Loading editor...
60+
</div>
61+
}
62+
options={readOnly ? readOnlyEditorOptions : editableEditorOptions}
63+
/>
64+
</section>
65+
);
66+
}
67+
68+
export default function TranslatorApp() {
69+
const mode = useAtomValue(modeAtom);
70+
const format = useAtomValue(formatAtom);
71+
const output = useAtomValue(outputAtom);
72+
const [tgslCode, setTgslCode] = useAtom(tgslCodeAtom);
73+
const [wgslCode, setWgslCode] = useAtom(wgslCodeAtom);
74+
const setEditorLoaded = useSetAtom(editorLoadingAtom);
75+
const initialize = useSetAtom(initializeAtom);
76+
77+
const tgslInputLabelId = useId();
78+
const wgslInputLabelId = useId();
79+
const compiledOutputLabelId = useId();
80+
81+
useAutoCompile();
82+
83+
useEffect(() => {
84+
initialize();
85+
}, [initialize]);
86+
87+
const handleEditorLoaded = () => setEditorLoaded(false);
88+
89+
const isTgslMode = mode === TRANSLATOR_MODES.TGSL;
90+
const gridCols = isTgslMode
91+
? 'grid-cols-1 lg:grid-cols-3'
92+
: 'grid-cols-1 lg:grid-cols-2';
93+
94+
return (
95+
<div className='flex flex-1 flex-col overflow-hidden'>
96+
<TranslatorHeader />
97+
98+
<main
99+
className={`grid flex-1 gap-4 overflow-y-auto p-4 ${gridCols}`}
100+
>
101+
{isTgslMode && (
102+
<EditorSection
103+
id={tgslInputLabelId}
104+
title='TypeScript Shader Code (TGSL):'
105+
language='typescript'
106+
value={tgslCode}
107+
onChange={setTgslCode}
108+
onMount={handleEditorLoaded}
109+
/>
110+
)}
111+
112+
<EditorSection
113+
id={wgslInputLabelId}
114+
title={isTgslMode ? 'Generated WGSL:' : 'WGSL Shader Code:'}
115+
language='wgsl'
116+
value={wgslCode}
117+
onChange={!isTgslMode ? setWgslCode : undefined}
118+
readOnly={isTgslMode}
119+
onMount={!isTgslMode ? handleEditorLoaded : undefined}
120+
/>
121+
122+
<EditorSection
123+
id={compiledOutputLabelId}
124+
title={`Compiled Output (${format.toUpperCase()}):`}
125+
language={LANGUAGE_MAP[format] || 'plaintext'}
126+
value={output || '// Compiled output will appear here...'}
127+
readOnly
128+
/>
129+
</main>
130+
</div>
131+
);
132+
}
Lines changed: 124 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,124 @@
1+
import { useId } from 'react';
2+
import { useAtomValue, useSetAtom } from 'jotai';
3+
import {
4+
canCompileAtom,
5+
canConvertTgslAtom,
6+
clearOutputOnFormatChangeAtom,
7+
clearOutputOnModeChangeAtom,
8+
compileAtom,
9+
convertTgslToWgslAtom,
10+
formatAtom,
11+
formatsAtom,
12+
modeAtom,
13+
statusAtom,
14+
} from '../lib/translatorStore.ts';
15+
import { TRANSLATOR_MODES, type TranslatorMode } from '../lib/constants.ts';
16+
17+
const STATUS_CONFIG = {
18+
initializing: { color: 'text-violet-400', text: 'Initializing…' },
19+
ready: { color: 'text-emerald-400', text: 'Ready to compile!' },
20+
compiling: { color: 'text-violet-400', text: 'Compiling…' },
21+
success: { color: 'text-emerald-400', text: 'Compilation successful!' },
22+
error: { color: 'text-red-400', text: '' },
23+
} as const;
24+
25+
export function TranslatorHeader() {
26+
const mode = useAtomValue(modeAtom);
27+
const format = useAtomValue(formatAtom);
28+
const { state: status, error: errorMessage } = useAtomValue(statusAtom);
29+
const formats = useAtomValue(formatsAtom);
30+
const canCompile = useAtomValue(canCompileAtom);
31+
const canConvertTgsl = useAtomValue(canConvertTgslAtom);
32+
33+
const setMode = useSetAtom(clearOutputOnModeChangeAtom);
34+
const setFormat = useSetAtom(clearOutputOnFormatChangeAtom);
35+
const handleTgslToWgsl = useSetAtom(convertTgslToWgslAtom);
36+
const handleCompile = useSetAtom(compileAtom);
37+
38+
const modeSelectId = useId();
39+
const formatSelectId = useId();
40+
41+
const statusConfig = STATUS_CONFIG[status];
42+
const statusText = status === 'error'
43+
? errorMessage || 'An unexpected error happened.'
44+
: statusConfig.text;
45+
46+
return (
47+
<header className='border-b px-2 pb-2'>
48+
<div className='mb-4 grid grid-cols-[1fr_auto] items-center gap-4'>
49+
<h1 className='font-bold text-xl'>
50+
{mode === TRANSLATOR_MODES.TGSL ? 'TGSL' : 'WGSL'} Translator
51+
</h1>
52+
53+
<div className='rounded border bg-gray-800 p-1'>
54+
<div
55+
className={`max-h-20 overflow-y-auto text-sm ${statusConfig.color}`}
56+
>
57+
{statusText}
58+
</div>
59+
</div>
60+
</div>
61+
62+
<div className='grid min-w-3xs grid-cols-2 items-center gap-4 sm:grid-cols-[auto_auto_1fr_auto]'>
63+
<div className='grid grid-cols-[auto_1fr] items-center gap-2'>
64+
<label htmlFor={modeSelectId} className='text-sm'>
65+
Mode:
66+
</label>
67+
<select
68+
id={modeSelectId}
69+
value={mode}
70+
onChange={(e) => setMode(e.target.value as TranslatorMode)}
71+
className='rounded border bg-gray-700 p-2 text-white'
72+
>
73+
<option value={TRANSLATOR_MODES.WGSL}>WGSL</option>
74+
<option value={TRANSLATOR_MODES.TGSL}>TGSL</option>
75+
</select>
76+
</div>
77+
78+
<div className='grid grid-cols-[auto_1fr] items-center gap-2'>
79+
<label htmlFor={formatSelectId} className='text-sm'>
80+
Target:
81+
</label>
82+
<select
83+
id={formatSelectId}
84+
value={format}
85+
onChange={(e) => setFormat(e.target.value)}
86+
disabled={!formats.length}
87+
className='rounded border bg-gray-700 p-2 text-white disabled:opacity-50'
88+
title={!formats.length
89+
? 'Loading available formats...'
90+
: 'Select target format'}
91+
>
92+
{formats.map((f) => (
93+
<option key={f} value={f}>
94+
{f.toUpperCase()}
95+
</option>
96+
))}
97+
</select>
98+
</div>
99+
100+
<div className='col-span-2 grid grid-flow-col gap-2 sm:col-span-1 sm:col-start-4'>
101+
{mode === TRANSLATOR_MODES.TGSL && (
102+
<button
103+
type='button'
104+
onClick={() => handleTgslToWgsl()}
105+
disabled={!canConvertTgsl}
106+
className='rounded bg-indigo-600 px-4 py-2 text-white hover:bg-indigo-500 disabled:opacity-50'
107+
>
108+
{status === 'compiling' ? 'Converting…' : 'Convert'}
109+
</button>
110+
)}
111+
112+
<button
113+
type='button'
114+
onClick={() => handleCompile()}
115+
disabled={!canCompile}
116+
className='rounded bg-purple-600 px-4 py-2 text-white hover:bg-purple-500 disabled:opacity-50'
117+
>
118+
{status === 'compiling' ? 'Compiling…' : 'Compile Now'}
119+
</button>
120+
</div>
121+
</div>
122+
</header>
123+
);
124+
}
Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
export const TRANSLATOR_MODES = {
2+
WGSL: 'wgsl',
3+
TGSL: 'tgsl',
4+
} as const;
5+
6+
export type TranslatorMode =
7+
typeof TRANSLATOR_MODES[keyof typeof TRANSLATOR_MODES];
8+
9+
export const DEFAULT_WGSL = `@vertex
10+
fn vs_main(@builtin(vertex_index) vertex_index: u32) -> @builtin(position) vec4<f32> {
11+
let pos = array<vec2<f32>, 3>(
12+
vec2<f32>(-0.5, -0.5),
13+
vec2<f32>( 0.5, -0.5),
14+
vec2<f32>( 0.0, 0.5)
15+
);
16+
return vec4<f32>(pos[vertex_index], 0.0, 1.0);
17+
}
18+
19+
@fragment
20+
fn fs_main() -> @location(0) vec4<f32> {
21+
return vec4<f32>(1.0, 0.0, 0.0, 1.0);
22+
}`;
23+
24+
export const DEFAULT_TGSL = `import tgpu from 'typegpu';
25+
import * as d from 'typegpu/data';
26+
import * as std from 'typegpu/std';
27+
28+
const Particle = d.struct({
29+
position: d.vec3f,
30+
velocity: d.vec3f,
31+
});
32+
33+
const SystemData = d.struct({
34+
particles: d.arrayOf(Particle, 100),
35+
gravity: d.vec3f,
36+
deltaTime: d.f32,
37+
});
38+
39+
const layout = tgpu.bindGroupLayout({
40+
systemData: { storage: SystemData },
41+
});
42+
43+
export const updateParicle = tgpu.fn([Particle, d.vec3f, d.f32], Particle)(
44+
(particle, gravity, deltaTime) => {
45+
const newVelocity = std.mul(
46+
particle.velocity,
47+
std.mul(gravity, deltaTime),
48+
);
49+
const newPosition = std.add(
50+
particle.position,
51+
std.mul(newVelocity, deltaTime),
52+
);
53+
return Particle({
54+
position: newPosition,
55+
velocity: newVelocity,
56+
});
57+
},
58+
);
59+
60+
export const main = tgpu.fn([])(() => {
61+
for (let i = 0; i < layout.$.systemData.particles.length; i++) {
62+
const particle = layout.$.systemData.particles[i];
63+
layout.$.systemData.particles[i] = updateParicle(
64+
particle,
65+
layout.$.systemData.gravity,
66+
layout.$.systemData.deltaTime,
67+
);
68+
}
69+
});`;

0 commit comments

Comments
 (0)