Folder: webgl-tauri-v2-ws-template
Stack: Tauri 2 + HTML/CSS/JavaScript + WebGL 1 + GLSL ES 1.0 + Rust WebSocket relay
Window model: Separate controls and renderer WebViews
This example separates a raw WebGL renderer from its user interface while keeping the complete graphics pipeline visible. It shows how to:
2727;controls and canvas roles;The visual remains the original domain-warped fractional Brownian motion study. The modernization improves the interface, reconnection behavior, diagnostics, and documentation without turning the example into a compositor, recorder, or media router.
Example 02 keeps controls and raw WebGL in one WebView. Example 03 adds one architectural variable: the controls and renderer now live in independent windows and communicate through a local WebSocket relay.
This pattern is useful for learning how an application can keep a dedicated output window independent from its control surface. It also demonstrates the basic message-routing idea later used by larger Junkpile and shared component layer systems.
controls.html / controls.js
│
│ JSON over ws://127.0.0.1:2727
│ { type: "param", name: "zoom", value: 1.5 }
▼
Embedded Rust WebSocket relay
│ broadcasts text to every other client
▼
canvas.html / canvas.js
│ updates local params object
│ uploads uniforms on next animation frame
▼
Linked GLSL program
│ fragments generated across fullscreen quad
▼
WebGL drawing buffer
▼
Canvas Tauri WebView window
Renderer diagnostics travel in the reverse direction:
Canvas FPS / resolution / GPU / shader status
│ { type: "telemetry", role: "canvas", ... }
▼
Rust WebSocket relay
▼
Controls diagnostics panel
Both windows automatically reconnect when the relay is temporarily unavailable.
The canvas sends:
{ "type": "state_request", "role": "canvas" }
The controls window responds with its complete current state:
{
"type": "state",
"role": "controls",
"params": {
"hue": 180,
"zoom": 1.5,
"speed": 0.5
},
"paused": false,
"resetTime": false
}
This means reopening or reconnecting the renderer does not silently revert it to stale defaults. The controls window remains the source of truth.
{ "type": "hello", "role": "controls" }
{ "type": "hello", "role": "canvas" }
{ "type": "param", "role": "controls", "name": "distortion", "value": 0.42 }
Checkboxes are transmitted as booleans. The canvas converts them to 1.0 or 0.0 before uploading float uniforms.
{
"type": "state",
"role": "controls",
"params": { "hue": 180, "saturation": 0.8 },
"paused": false,
"resetTime": false,
"reason": "canvas_requested_state"
}
{ "type": "action", "role": "controls", "name": "pause", "value": true }
{ "type": "action", "role": "controls", "name": "fullscreen" }
{
"type": "telemetry",
"role": "canvas",
"runtime": "running",
"vertexStatus": "Compiled",
"fragmentStatus": "Compiled",
"linkStatus": "Linked",
"renderer": "Apple GPU",
"resolution": "1920 × 1440",
"fps": 60.0,
"paused": false,
"error": ""
}
src/controls.html contains the Junkpile control surface and renderer diagnostics.
src/controls.js owns the authoritative parameter state. Slider input is coalesced with requestAnimationFrame() so a rapid drag sends only the most recent value for each parameter once per browser frame. It also handles reconnection, renderer liveness, complete-state synchronization, pause, reset, and fullscreen actions.
src/canvas.html contains only the canvas, minimal HUD, and visible error state.
src/canvas.js contains:
src-tauri/src/main.rs performs two focused jobs:
tauri::async_runtime::spawn();toggle_canvas_fullscreen, retrieving the configured renderer with Tauri 2’s get_webview_window("canvas") API.Text messages are broadcast to all clients except their sender. Binary messages remain supported and are routed only to clients whose role is canvas, preserving the original relay lesson for later experiments.
src-tauri/capabilities/main-capability.json explicitly grants core:default to both WebView labels.
src-tauri/Cargo.toml includes a local workspace boundary so Cargo does not walk upward into an unrelated repository workspace.
webgl-tauri-v2-ws-template/
├── README.md
├── package.json
├── package-lock.json
├── src/
│ ├── controls.html
│ ├── controls.js
│ ├── canvas.html
│ ├── canvas.js
│ └── styles.css
└── src-tauri/
├── build.rs
├── Cargo.toml
├── Cargo.lock
├── tauri.conf.json
├── capabilities/
│ └── main-capability.json
├── icons/
└── src/
└── main.rs
cd webgl-tauri-v2-ws-template
npm install
The Tauri CLI is installed locally through devDependencies; no global CLI is required.
npm run dev
Tauri opens two windows:
The Rust process listens on both:
ws://127.0.0.1:2727
ws://[::1]:2727
Only loopback interfaces are used. The relay is not exposed to the local network.
npm run build
Desktop bundles are written beneath:
src-tauri/target/release/bundle/
Signing, notarization, installer identity, and platform distribution requirements remain the responsibility of the final application.
| Control | Shader role |
|---|---|
| Hue Shift | Base HSB hue in degrees |
| Saturation | Color saturation |
| Brightness | Pattern output gain |
| Zoom | UV scale before noise evaluation |
| Speed | Multiplier applied to accumulated shader time |
| Distortion | Strength of the two-sample domain warp |
| Complexity | Number of active fBm octaves, 1–8 |
| Symmetry | Number of radial sectors |
| Glow | Center-weighted brightness gain |
| Invert | Mixes the color with its inverse |
| Pulse | Enables sinusoidal brightness modulation |
| Rotate | Enables time-based UV rotation |
| Key | Action |
|---|---|
Space |
Pause or resume animation |
R |
Reset parameters and renderer time |
F |
Toggle canvas-window fullscreen |
The canvas window also accepts F for native fullscreen.
The canvas returns the following to the controls window approximately twice per second:
The controls window also reports sent and received WebSocket message counts. A missing renderer is shown explicitly instead of appearing as unexplained inactive controls.
Both windows are WebView-rendered. The canvas uses WebGL 1 and GLSL ES 1.0 through the operating system’s embedded browser graphics stack. This is intentionally different from Junkpile’s native Rust + wgpu examples.
The frontend uses Tauri’s global vanilla-JavaScript API only in the canvas window for native fullscreen:
window.__TAURI__.core.invoke('toggle_canvas_fullscreen')
app.withGlobalTauri is enabled in tauri.conf.json, and both WebView labels are included in the capability.
2727 must be available. A second running copy of this example will not be able to bind the same loopback port.u_complexity.2 to prevent unusually large drawing buffers.canvas role.This project deliberately does not add camera input, recording, media routing, external shader replacement, or native wgpu rendering. Those concerns are covered by other Junkpile examples.