Folder: feedback-tauri-v2-single-template
Collection: Tauri v2 Essentials
Renderer: Raw WebGL 1 / GLSL ES 1.0
Window model: One Tauri v2 WebView window
Example 08 is a focused study of persistent GPU feedback driven by a live camera texture. It demonstrates how to:
navigator.mediaDevices.getUserMedia();The example is intentionally not a recorder, compositor, router, native camera driver, or general node system. Its lesson is persistent texture state.
A normal WebGL pass is stateless: the shader receives textures and uniforms, writes pixels, and the next frame begins again. Feedback changes that model by making the prior output one of the next frame’s inputs.
Current webcam texture ───────────────┐
▼
Previous feedback texture → Simulation program → Next feedback texture
│
▼
Display program
│
▼
Visible canvas
WebGL cannot sample from the same texture currently attached as the render destination. Two targets are therefore required:
Frame N
Read target B → write target A → display A → swap roles
Frame N + 1
Read target A → write target B → display B → swap roles
That read/write exchange is the central concept of the example.
Operating-system camera
↓
getUserMedia MediaStream
↓
hidden HTMLVideoElement
↓
new decoded frame detected
↓
gl.texImage2D(..., video)
↓
u_webcam sampler2D
The camera and renderer have separate lifecycles. The WebGL pipeline initializes before permission, remains active when the camera is stopped, and uses a one-pixel black placeholder whenever no stream is available.
Texture unit 0: previous feedback target
Texture unit 1: current camera texture
↓
Simulation fragment shader
↓
write framebuffer
↓
Display fragment shader
↓
palette mapping + tone mapping
↓
window canvas
The simulation program stores state. The display program colors that state without feeding the palette conversion back into the simulation.
Pointer position on canvas
↓
normalized UV coordinate
↓
u_mouse + u_mouseDown + u_brushSize
↓
localized state injection in simulation shader
In Reaction-Diffusion mode, the brush injects the V activator. In the other modes it injects a time-varying color.
| Mode | Stored-state behavior | Camera behavior |
|---|---|---|
| Echo Trail | Previous RGB is multiplied by Decay | Camera RGB is added each frame |
| Fluid Smear | Previous state is advected through curl noise | Camera edges increase flow and camera RGB is injected |
| Reaction-Diffusion | Red and green channels store Gray-Scott U and V chemicals | Camera luminance injects V and removes U |
| Thermal | A nine-tap diffusion pass spreads stored energy | Camera luminance acts as a heat source |
| Mirror Echo | Previous state is sampled through a contracting symmetry fold | Camera is sampled through a six-way mirror fold |
| Glitch Memory | Previous RGB is block-shifted with chromatic offsets | Camera is injected with matching aberration |
Changing modes clears the persistent state because each mode interprets the texture channels differently. Reaction-Diffusion initializes both targets with U = 1, while the image modes initialize to near-black.
The example prefers floating-point feedback because several modes can store values outside the normalized byte range. WebGL 1 support varies by platform, so startup performs an actual framebuffer completeness test:
Try RGBA + FLOAT target
↓ complete? yes → use float target
↓ no
Use RGBA8 unsigned-byte target
The selected target format appears in Diagnostics and in the renderer HUD. RGBA8 remains functional, but very subtle feedback gradients and reaction-diffusion state may quantize more visibly.
src/index.html defines the Junkpile application shell, camera controls, six mode buttons, simulation parameters, color parameters, brush control, diagnostics, action buttons, renderer canvas, camera notice, and fatal renderer error layer.
src/styles.css provides the scroll-safe two-panel interface. The application grid and both child panels use min-height: 0, so the controls panel scrolls immediately in WKWebView rather than only after the window is resized.
src/app.js contains:
src-tauri/src/main.rs registers toggle_fullscreen and retrieves the configured Tauri 2 WebView with get_webview_window("main").
src-tauri/capabilities/main-capability.json grants core:default to the main WebView label.
src-tauri/Cargo.toml contains a local workspace boundary:
[workspace]
resolver = "2"
src-tauri/Info.plist supplies NSCameraUsageDescription for the macOS permission prompt. src-tauri/entitlements.plist contains the camera entitlement referenced by tauri.conf.json.
feedback-tauri-v2-single-template/
├── README.md
├── package.json
├── package-lock.json
├── src/
│ ├── index.html
│ ├── styles.css
│ └── app.js
└── src-tauri/
├── build.rs
├── Cargo.toml
├── Cargo.lock
├── tauri.conf.json
├── Info.plist
├── entitlements.plist
├── capabilities/
│ └── main-capability.json
├── icons/
└── src/
└── main.rs
cd feedback-tauri-v2-single-template
npm install
npm run dev
npm run build
On macOS, production bundles are written below:
src-tauri/target/release/bundle/
Unsigned or unnotarized applications may be blocked by Gatekeeper when moved to another computer.
MediaStream lifecycle.| Key | Action |
|---|---|
C |
Start or stop camera |
Space |
Pause or resume simulation |
X |
Clear feedback state |
R |
Reset parameters and time |
F |
Toggle native fullscreen |
1–6 |
Select feedback mode |
app.withGlobalTauri exposes window.__TAURI__.core.invoke() to vanilla JavaScript.main WebView label.get_webview_window().u_prev from Echo Trail to compare a stateless camera pass with temporal feedback.This example teaches camera-driven persistent GPU state inside one Tauri v2 WebView. Later applications may add routing, recording, native capture, or shared I/O contracts, but those concerns are deliberately excluded here so the ping-pong feedback mechanism remains explicit.