/ concept-collection / math-webgpu-sandbox
Sign in
concept-collection / math-webgpu-sandbox
math-webgpu-sandbox / src / main.ts
132 lines · 4.3 KBBlameHistoryRaw
1/**
2 * The sandbox page: editor on the left, output on the right. The tic/toc
3 * numbers the script prints ARE the timing report — compare them with a
4 * MATLAB run of the same script.
5 */
6import { CodeEditor } from './editor/codeEditor.ts';
7import { requestSandboxDevice, runScript, type SandboxGpu } from './mgpu/session.ts';
8import { formatFailure } from './mgpu/errors.ts';
9import fusedLoop from '../examples/fused_loop.m?raw';
10import matmul from '../examples/matmul.m?raw';
11import monteCarloPi from '../examples/monte_carlo_pi.m?raw';
12import logisticEnsemble from '../examples/logistic_ensemble.m?raw';
13import reductions from '../examples/reductions.m?raw';
15const EXAMPLES: { key: string; title: string; source: string }[] = [
16 { key: 'fused_loop', title: 'fused elementwise loop', source: fusedLoop },
17 { key: 'matmul', title: 'matrix multiply (GEMM)', source: matmul },
18 { key: 'monte_carlo_pi', title: 'Monte Carlo pi (masks)', source: monteCarloPi },
19 { key: 'logistic_ensemble', title: 'logistic map ensemble', source: logisticEnsemble },
20 { key: 'reductions', title: 'fused reductions', source: reductions },
21];
23const $ = <T extends HTMLElement>(id: string): T => {
24 const el = document.getElementById(id);
25 if (!el) throw new Error(`missing #${id}`);
26 return el as T;
27};
29const consoleEl = $<HTMLPreElement>('console');
30const runBtn = $<HTMLButtonElement>('run');
31const copyBtn = $<HTMLButtonElement>('copy');
32const exampleSel = $<HTMLSelectElement>('example');
33const planDetails = $<HTMLElement>('plandetails');
34const planEl = $<HTMLPreElement>('plan');
35const runState = $<HTMLElement>('runstate');
36const deviceEl = $<HTMLElement>('device');
37const gpuWarn = $<HTMLElement>('gpuwarn');
39const editor = new CodeEditor({
40 textarea: $<HTMLTextAreaElement>('source'),
41 overlay: $('highlight'),
42 onInput: () => {
43 $('editstate').textContent = '';
44 },
45});
47for (const ex of EXAMPLES) {
48 const opt = document.createElement('option');
49 opt.value = ex.key;
50 opt.textContent = ex.title;
51 exampleSel.appendChild(opt);
53exampleSel.addEventListener('change', () => {
54 const ex = EXAMPLES.find((e) => e.key === exampleSel.value);
55 if (ex) editor.value = ex.source;
56});
57editor.value = EXAMPLES[0].source;
59const append = (text: string, cls?: string): void => {
60 if (cls) {
61 const span = document.createElement('span');
62 span.className = cls;
63 span.textContent = text;
64 consoleEl.appendChild(span);
65 } else {
66 consoleEl.appendChild(document.createTextNode(text));
67 }
68 consoleEl.scrollTop = consoleEl.scrollHeight;
69};
71let gpu: SandboxGpu | null = null;
72(async () => {
73 try {
74 gpu = await requestSandboxDevice();
75 deviceEl.textContent = `GPU: ${gpu.description}`;
76 gpu.device.lost.then((info) => {
77 gpu = null;
78 gpuWarn.hidden = false;
79 gpuWarn.textContent = `The GPU device was lost (${info.message}); reload the page.`;
80 runBtn.disabled = true;
81 });
82 } catch (e) {
83 gpuWarn.hidden = false;
84 gpuWarn.innerHTML =
85 `<b>No WebGPU here.</b> ${e instanceof Error ? e.message : String(e)} — ` +
86 `Chrome/Edge have it on by default; Firefox and Safari are rolling it out.`;
87 runBtn.disabled = true;
88 }
89})();
91runBtn.addEventListener('click', () => {
92 void runGpu();
93});
94async function runGpu(): Promise<void> {
95 if (!gpu) return;
96 runBtn.disabled = true;
97 runState.textContent = 'compiling…';
98 consoleEl.textContent = '';
99 planDetails.hidden = true;
100 const source = editor.value;
101 try {
102 const run = await runScript(gpu.device, source, (text) => {
103 runState.textContent = 'running…';
104 append(text);
105 });
106 if (run.result.error) {
107 append(`\n${run.result.error}\n`, 'err');
108 }
109 append(
110 `\n[compile ${(run.compileSeconds * 1000).toFixed(0)} ms · ` +
111 `run ${run.result.totalSeconds.toFixed(3)} s · f32 · ${gpu.description}]\n`,
112 'meta',
113 );
114 planEl.textContent = run.planDescription.join('\n') || '(no GPU ops)';
115 planDetails.hidden = false;
116 runState.textContent = '';
117 } catch (e) {
118 append(formatFailure(e, source) + '\n', 'err');
119 runState.textContent = '';
120 } finally {
121 runBtn.disabled = !gpu;
122 }
125copyBtn.addEventListener('click', () => {
126 void navigator.clipboard.writeText(editor.value).then(() => {
127 copyBtn.textContent = 'Copied ✓';
128 setTimeout(() => {
129 copyBtn.textContent = 'Copy script';
130 }, 1200);
131 });
132});
moveopenescclose