/** * The sandbox page: editor on the left, output on the right. The tic/toc * numbers the script prints ARE the timing report — compare them with a * MATLAB run of the same script. */ import { CodeEditor } from './editor/codeEditor.ts'; import { requestSandboxDevice, runScript, type SandboxGpu } from './mgpu/session.ts'; import { formatFailure } from './mgpu/errors.ts'; import fusedLoop from '../examples/fused_loop.m?raw'; import matmul from '../examples/matmul.m?raw'; import monteCarloPi from '../examples/monte_carlo_pi.m?raw'; import logisticEnsemble from '../examples/logistic_ensemble.m?raw'; import reductions from '../examples/reductions.m?raw'; const EXAMPLES: { key: string; title: string; source: string }[] = [ { key: 'fused_loop', title: 'fused elementwise loop', source: fusedLoop }, { key: 'matmul', title: 'matrix multiply (GEMM)', source: matmul }, { key: 'monte_carlo_pi', title: 'Monte Carlo pi (masks)', source: monteCarloPi }, { key: 'logistic_ensemble', title: 'logistic map ensemble', source: logisticEnsemble }, { key: 'reductions', title: 'fused reductions', source: reductions }, ]; const $ = (id: string): T => { const el = document.getElementById(id); if (!el) throw new Error(`missing #${id}`); return el as T; }; const consoleEl = $('console'); const runBtn = $('run'); const copyBtn = $('copy'); const exampleSel = $('example'); const planDetails = $('plandetails'); const planEl = $('plan'); const runState = $('runstate'); const deviceEl = $('device'); const gpuWarn = $('gpuwarn'); const editor = new CodeEditor({ textarea: $('source'), overlay: $('highlight'), onInput: () => { $('editstate').textContent = ''; }, }); for (const ex of EXAMPLES) { const opt = document.createElement('option'); opt.value = ex.key; opt.textContent = ex.title; exampleSel.appendChild(opt); } exampleSel.addEventListener('change', () => { const ex = EXAMPLES.find((e) => e.key === exampleSel.value); if (ex) editor.value = ex.source; }); editor.value = EXAMPLES[0].source; const append = (text: string, cls?: string): void => { if (cls) { const span = document.createElement('span'); span.className = cls; span.textContent = text; consoleEl.appendChild(span); } else { consoleEl.appendChild(document.createTextNode(text)); } consoleEl.scrollTop = consoleEl.scrollHeight; }; let gpu: SandboxGpu | null = null; (async () => { try { gpu = await requestSandboxDevice(); deviceEl.textContent = `GPU: ${gpu.description}`; gpu.device.lost.then((info) => { gpu = null; gpuWarn.hidden = false; gpuWarn.textContent = `The GPU device was lost (${info.message}); reload the page.`; runBtn.disabled = true; }); } catch (e) { gpuWarn.hidden = false; gpuWarn.innerHTML = `No WebGPU here. ${e instanceof Error ? e.message : String(e)} — ` + `Chrome/Edge have it on by default; Firefox and Safari are rolling it out.`; runBtn.disabled = true; } })(); runBtn.addEventListener('click', () => { void runGpu(); }); async function runGpu(): Promise { if (!gpu) return; runBtn.disabled = true; runState.textContent = 'compiling…'; consoleEl.textContent = ''; planDetails.hidden = true; const source = editor.value; try { const run = await runScript(gpu.device, source, (text) => { runState.textContent = 'running…'; append(text); }); if (run.result.error) { append(`\n${run.result.error}\n`, 'err'); } append( `\n[compile ${(run.compileSeconds * 1000).toFixed(0)} ms · ` + `run ${run.result.totalSeconds.toFixed(3)} s · f32 · ${gpu.description}]\n`, 'meta', ); planEl.textContent = run.planDescription.join('\n') || '(no GPU ops)'; planDetails.hidden = false; runState.textContent = ''; } catch (e) { append(formatFailure(e, source) + '\n', 'err'); runState.textContent = ''; } finally { runBtn.disabled = !gpu; } } copyBtn.addEventListener('click', () => { void navigator.clipboard.writeText(editor.value).then(() => { copyBtn.textContent = 'Copied ✓'; setTimeout(() => { copyBtn.textContent = 'Copy script'; }, 1200); }); });