Folder: glsl-tauri-v2-single-template
Stack: Tauri 2 + HTML/CSS/JavaScript + WebGL 1 + external GLSL ES 1.0
Window model: One Tauri WebView window containing controls, compiler diagnostics, and renderer
This example extends the explicit raw WebGL pipeline from Example 02 by moving the fragment shader into a real external file. It demonstrates how to:
src/shader.frag at application startup;.frag or .glsl file with the browser File API;The default visual remains the original domain-warped fractional Brownian motion study. The modernization adds the current Junkpile interface and failure handling without turning the project into a full shader editor, compositor, recorder, or media application.
Embedding shader source in JavaScript is convenient for a small demonstration, but production shader work is usually easier when GLSL lives in its own file. A separate .frag file provides editor syntax highlighting, cleaner source organization, and a natural path toward runtime replacement.
The educational focus is the shader lifecycle:
Shader source
↓
Compile stages
↓
Link candidate program
↓
Validate required attribute
↓
Activate only if valid
A shader compile error should not destroy an already working renderer. Example 04 therefore treats every replacement as a candidate and swaps it in only after compilation and linking succeed.
src/shader.frag
│ fetch("shader.frag")
▼
JavaScript source string
│
├── compile fixed vertex shader
├── compile external fragment shader
└── link candidate program
▼
Validated WebGL program
│
├── bind fullscreen quad
├── cache uniforms lazily
└── render each animation frame
▼
Tauri v2 WebView canvas
Load .frag button
▼
Browser file picker
▼
File.text()
▼
Candidate compile + link
├── success → replace active program
└── failure → reject candidate and preserve previous program
HTML sliders and toggles
▼
JavaScript params object
▼
Uniform uploads on each frame
▼
Active GLSL program
The fullscreen button is the only frontend-to-Rust path:
Fullscreen button or F key
▼
window.__TAURI__.core.invoke("toggle_fullscreen")
▼
Rust command
▼
app.get_webview_window("main")
▼
Native window fullscreen state
src/index.html defines:
src/styles.css provides the shared Junkpile shell. The layout includes the required min-height: 0 behavior on grid children, so the controls panel scrolls immediately in WKWebView rather than only after the window is resized.
src/app.js contains:
src/shader.frag is the default external fragment shader. It is copied into the application assets and fetched at runtime.
src-tauri/src/main.rs intentionally remains small. It registers a toggle_fullscreen command and retrieves the configured WebView using Tauri 2’s get_webview_window() API.
src-tauri/capabilities/main-capability.json explicitly grants core:default to the main WebView label.
src-tauri/Cargo.toml includes a local workspace boundary so Cargo cannot walk upward into an unrelated parent workspace.
glsl-tauri-v2-single-template/
├── README.md
├── package.json
├── package-lock.json
├── src/
│ ├── index.html
│ ├── styles.css
│ ├── app.js
│ └── shader.frag
└── src-tauri/
├── build.rs
├── Cargo.toml
├── Cargo.lock
├── tauri.conf.json
├── capabilities/
│ └── main-capability.json
├── icons/
└── src/
└── main.rs
cd glsl-tauri-v2-single-template
npm install
The Tauri CLI is installed locally through devDependencies; no global CLI is required.
npm run dev
The default shader is fetched from the static frontend assets when the window starts.
npm run build
Desktop bundles are written beneath:
src-tauri/target/release/bundle/
Signing, notarization, installer identity, and distribution requirements remain the responsibility of the final application.
.fragOpens a browser file picker and reads the selected file into memory. The file path is not required and is not exposed to the application. Files up to 1 MiB are accepted in this focused example.
The source is compiled into a candidate program. The candidate becomes active only after:
a_position attribute is available.Fetches shader.frag again and treats it as another candidate. This allows recovery after loading an experimental shader without restarting the application.
A failed replacement does not stop the animation or delete the current program. The compiler terminal shows the WebGL log, a warning appears over the renderer, and the previous shader remains active.
This behavior is particularly important for live visual tools, where an editing mistake should not create a blank output.
The default render loop attempts to upload these uniforms:
uniform float u_time;
uniform vec2 u_resolution;
uniform float u_hue;
uniform float u_saturation;
uniform float u_brightness;
uniform float u_zoom;
uniform float u_distortion;
uniform float u_rotate;
uniform float u_complexity;
uniform float u_symmetry;
uniform float u_glow;
uniform float u_invert;
uniform float u_pulse;
A replacement shader does not have to use every uniform. WebGL optimizes unused uniforms away, and the JavaScript upload helper safely ignores locations that return null.
The fixed vertex shader provides:
varying vec2 vTexCoord;
A replacement fragment shader may use vTexCoord for normalized coordinates. It must remain compatible with WebGL 1 / GLSL ES 1.0 and include a fragment precision declaration such as:
precision highp float;
| 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 |
shader.frag and replaces the active shader only if compilation succeeds.| Key | Action |
|---|---|
Space |
Pause or resume animation |
R |
Reset parameters and shader time |
O |
Open the shader file picker |
D |
Restore the default external shader |
F |
Toggle native fullscreen |
Keyboard shortcuts are ignored while an input or button has focus.
The controls panel reports:
WEBGL_debug_renderer_info when available;There are two distinct error states:
The distinction prevents recoverable shader mistakes from appearing as unexplained blank output.
This remains a WebView-rendered WebGL example. Tauri 2 owns the desktop application window, but GLSL runs through the embedded browser graphics stack. It is intentionally different from the native Rust + wgpu examples, where WGSL renders to a native GPU surface.
The frontend uses Tauri’s global vanilla-JavaScript API only for fullscreen. app.withGlobalTauri is enabled in tauri.conf.json, and the call is made with:
window.__TAURI__.core.invoke('toggle_fullscreen')
Shader selection uses the standard browser File API and does not require a Tauri filesystem capability because the user explicitly selects the file and its text is read within the WebView.
2 to prevent unusually large drawing buffers.shader.frag again.shader.frag, then hot-swap it..vert and .frag program.This project deliberately does not add camera input, video textures, recording, routing, multiple renderer windows, a complete code editor, or native filesystem watching. Those concerns belong to later Junkpile examples.