Files
yawn/server/web/playground.html
T
Ampandheaust 6153223fd5 Add FxNode render graph playground
Add persistent render graph loadouts, grouped node authoring, code-appending apply behavior, explicit playground lighting, and resilient worker startup.

Amp-Thread-ID: https://ampcode.com/threads/T-01a033d4-0e44-708e-af0e-6166283416ff
Co-authored-by: Heaust Azure <heaust.azure@gmail.com>
2026-08-24 17:45:48 +00:00

374 lines
13 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<meta name="theme-color" content="#f7fbff" />
<meta
name="description"
content="Write, run, profile, and share Yawn WebGPU scenes."
/>
<title>Yawn Playground</title>
<script src="/assets/theme.js"></script>
<link rel="stylesheet" href="/assets/playground.css" />
<link rel="stylesheet" href="/assets/theme.css" />
<script type="importmap">
{
"imports": {
"@yawn/core": "/pkg/core.js",
"@yawn/handles": "/pkg/handles.js",
"@yawn/fxnode-rg": "/pkg/fxnode-rg.js"
}
}
</script>
<script src="/assets/monaco/vs/loader.js"></script>
<script type="module" src="/assets/playground.js"></script>
</head>
<body>
<main class="playground-app">
<header class="playground-header">
<a class="playground-brand" href="/" aria-label="Yawn home">
<span class="brand-mark" aria-hidden="true"
><i></i><i></i><i></i></span
><strong>Yawn</strong>
</a>
<div class="header-divider"></div>
<div class="project-name">
<input
id="project-title"
maxlength="80"
value="Three-light icosphere"
aria-label="Playground title"
/>
<span id="dirty-indicator" title="Unsaved changes"></span>
<span id="revision-label">Draft</span>
</div>
<nav class="playground-nav" aria-label="Playground links">
<a href="/docs">Docs</a>
<a href="https://git.heaust.org/heaust/yawn">Source</a>
</nav>
<select class="theme-select" data-theme-select aria-label="Color theme">
<option value="auto">Auto</option>
<option value="light">Light</option>
<option value="dark">Dark</option>
</select>
<button id="share-button" class="tool-button" type="button" hidden>
<span aria-hidden="true">↗</span> Share
</button>
<button id="save-button" class="tool-button" type="button">
<span aria-hidden="true">◇</span> Save
</button>
<button id="run-button" class="run-button" type="button">
<span aria-hidden="true">▶</span> Run <kbd>Ctrl ↵</kbd>
</button>
</header>
<div class="playground-tools">
<div class="tool-group">
<button id="snippets-button" type="button" data-code-tool>
<span class="braces">{ }</span> Snippets
</button>
<button id="reset-button" type="button" data-code-tool>
<span aria-hidden="true">↶</span> Reset
</button>
<button id="format-button" type="button" data-code-tool>
<span aria-hidden="true">≡</span> Format
</button>
<span class="graph-tool-hint" data-graph-tool hidden
><b>Right click</b> the graph to add a node</span
>
</div>
<div class="tool-context">
<span class="language-dot"></span>
<span id="workspace-language">TypeScript</span>
<span class="context-divider"></span>
<span id="type-status">Starting language service…</span>
</div>
<div class="tool-group tool-group-right">
<button id="inspector-button" type="button" aria-pressed="true">
<span aria-hidden="true">▤</span> Inspector
</button>
<button
id="fullscreen-button"
type="button"
aria-label="Toggle fullscreen"
>
<span aria-hidden="true">⛶</span>
</button>
</div>
</div>
<div class="workbench" id="workbench">
<section class="editor-pane" aria-label="Coding workspace">
<div class="pane-tabs" role="tablist" aria-label="Coding workspace">
<button
class="pane-tab active"
id="scene-tab"
type="button"
role="tab"
aria-selected="true"
aria-controls="editor"
data-editor-tab="scene"
>
<span class="ts-icon">TS</span> scene.ts <i id="tab-dirty"></i>
</button>
<button
class="pane-tab"
id="render-graph-tab"
type="button"
role="tab"
aria-selected="false"
aria-controls="render-graph-editor"
data-editor-tab="render-graph"
>
<span class="rg-tab-icon" aria-hidden="true">◇</span>
render graph
<i id="graph-tab-dirty"></i>
</button>
</div>
<div
id="editor"
class="editor coding-panel active"
role="tabpanel"
aria-labelledby="scene-tab"
>
<div class="editor-loading">Loading TypeScript editor…</div>
</div>
<section
id="render-graph-editor"
class="render-graph-editor coding-panel"
role="tabpanel"
aria-labelledby="render-graph-tab"
hidden
>
<div class="render-graph-toolbar">
<label>
<span>Loadout</span>
<select
id="loadout-select"
aria-label="Render graph loadout"
></select>
</label>
<label class="loadout-name-field">
<span>Name</span>
<input
id="loadout-name"
maxlength="64"
placeholder="My loadout"
aria-label="Loadout name"
/>
</label>
<button
id="apply-loadout-button"
type="button"
title="Apply in memory; use the playground Save button to keep it"
>
<span aria-hidden="true">◇</span> Apply
</button>
<button
id="copy-loadout-code-button"
class="secondary"
type="button"
title="Copy this loadout's compiled S-expression as Scene code"
>
<span aria-hidden="true">⧉</span> Copy code
</button>
<output id="loadout-status" aria-live="polite"
><i></i> Standard active</output
>
</div>
<div class="render-graph-canvas-wrap">
<canvas
id="render-graph-canvas"
aria-label="Render graph node editor"
></canvas>
<div class="render-graph-empty-hint" id="render-graph-hint">
<span>+</span>
<strong>Add another pass</strong>
<small>Right click anywhere to browse the node catalog</small>
</div>
</div>
</section>
<footer class="editor-status">
<span id="cursor-status">Ln 1, Col 1</span>
<span id="editor-mode-status">Spaces: 2</span>
<span id="encoding-status">UTF-8</span>
<span class="status-spacer"></span>
<span id="editor-diagnostics"><i></i> No problems</span>
</footer>
</section>
<div
class="resize-handle"
id="resize-handle"
role="separator"
aria-orientation="vertical"
aria-label="Resize editor and preview"
></div>
<section class="stage-pane" aria-label="Yawn output">
<div class="stage-toolbar">
<div class="stage-tabs">
<button type="button" class="active">Preview</button>
</div>
<div class="stage-meta">
<span id="run-status"><i></i> Ready</span>
<span class="stage-divider"></span>
<span id="fps-status">0 FPS</span>
<span class="stage-divider"></span>
<span id="resolution-status">—</span>
</div>
</div>
<div class="preview" id="preview">
<canvas id="canvas" aria-label="Yawn WebGPU preview"></canvas>
<div class="preview-message" id="preview-message">
<span class="preview-message-icon">Y</span>
<strong>Preparing your scene</strong>
<small>The first run compiles the render graph.</small>
</div>
</div>
<div class="inspector" id="inspector">
<div class="inspector-tabs" role="tablist">
<button
type="button"
class="active"
role="tab"
aria-selected="true"
data-panel="console"
>
Console <span id="console-count">0</span>
</button>
<button
type="button"
role="tab"
aria-selected="false"
data-panel="profile"
>
GPU profiler
</button>
<button id="clear-console" class="clear-console" type="button">
Clear
</button>
</div>
<div
class="console-panel inspector-panel active"
id="console-panel"
role="tabpanel"
>
<div class="console-empty" id="console-empty">
<span>›_</span> Output from <code>log()</code> appears here.
</div>
<ol id="console-lines"></ol>
</div>
<div
class="profile-panel inspector-panel"
id="profile-panel"
role="tabpanel"
>
<div class="profile-empty" id="profile-empty">
<span class="pulse-icon">⌁</span>
<div>
<strong>Waiting for GPU timestamps</strong
><small>Run the scene to inspect its physical passes.</small>
</div>
</div>
<div class="profile-results" id="profile-results" hidden>
<div class="profile-summary">
<div>
<small>Total GPU time</small
><strong id="profile-total">—</strong>
</div>
<div>
<small>Readback latency</small
><strong id="profile-latency">—</strong>
</div>
<div>
<small>Adapter</small
><strong id="profile-adapter">—</strong>
</div>
</div>
<table>
<thead>
<tr>
<th>Physical pass</th>
<th>GPU time</th>
</tr>
</thead>
<tbody id="profile-passes"></tbody>
</table>
</div>
</div>
</div>
</section>
</div>
</main>
<dialog id="snippets-dialog" class="snippets-dialog">
<div class="dialog-heading">
<div>
<span>Code library</span>
<h2>Snippets</h2>
<p>Insert a focused building block at your cursor.</p>
</div>
<button type="button" data-close-dialog aria-label="Close snippets">
×
</button>
</div>
<div class="snippet-search">
<span>⌕</span
><input
type="search"
placeholder="Find a snippet"
aria-label="Find a snippet"
/>
</div>
<div class="snippet-grid">
<button type="button" data-snippet="camera">
<span class="snippet-icon">◎</span>
<div>
<strong>Orbit camera</strong
><small>Pointer orbit and wheel zoom</small
><code>ArcRotateCamera</code>
</div>
<b>+</b>
</button>
<button type="button" data-snippet="light">
<span class="snippet-icon">☀</span>
<div>
<strong>Point light</strong><small>Warm, local illumination</small
><code>PointLight</code>
</div>
<b>+</b>
</button>
<button type="button" data-snippet="instances">
<span class="snippet-icon">◇</span>
<div>
<strong>Mesh instances</strong><small>Clone shared geometry</small
><code>mesh.clone()</code>
</div>
<b>+</b>
</button>
<button type="button" data-snippet="rows">
<span class="snippet-icon">≋</span>
<div>
<strong>Shared rows</strong
><small>Add application-owned hot data</small
><code>scene.ensureRows()</code>
</div>
<b>+</b>
</button>
<button type="button" data-snippet="post">
<span class="snippet-icon">✦</span>
<div>
<strong>Post processing</strong><small>Color grading and FXAA</small
><code>batchGraphUpdates()</code>
</div>
<b>+</b>
</button>
</div>
</dialog>
<div class="toast" id="toast" role="status" aria-live="polite"></div>
</body>
</html>