Folder: webcam-tauri-v2-single-template
Collection: Tauri v2 Essentials
Renderer: Raw WebGL 1 / GLSL ES 1.0
Window model: One Tauri v2 WebView window
Example 06 connects a browser-managed camera stream to an explicit raw WebGL pipeline. It demonstrates how to:
navigator.mediaDevices.getUserMedia();<video> element;gl.texImage2D();sampler2D;The example remains intentionally focused. It is not a recorder, compositor, camera driver, routing system, or native capture implementation.
WebGL does not open a webcam itself. Camera capture belongs to the browser media layer, while texture processing belongs to the GPU layer. A useful webcam application must connect those systems explicitly:
Operating-system camera
↓
getUserMedia MediaStream
↓
HTMLVideoElement decoder
↓
texImage2D upload
↓
WebGL sampler2D
↓
GLSL effect
This example makes that boundary visible. The camera and WebGL renderer have separate lifecycles: the GPU pipeline can initialize and remain responsive before permission is granted, after a camera is stopped, or when a camera request fails.
Camera selector
↓
navigator.mediaDevices.getUserMedia()
↓
MediaStream video track
↓
hidden <video autoplay playsinline muted>
↓
new decoded frame detected by video.currentTime
↓
gl.texImage2D(..., video)
↓
u_webcam sampler2D
Only a newly decoded frame is uploaded. If the render loop runs faster than the camera, the previous GPU texture is reused rather than uploading the same frame repeatedly.
Camera texture ───────────────┐
▼
Previous feedback texture → Effect GLSL program
↓
Write framebuffer
↓
Simple blit program
↓
Visible canvas
↓
Write/read targets exchange roles
The display pass uses a separate blit shader. This matters because rendering the effect framebuffer through the effect shader a second time would apply zoom, color processing, and other transforms twice.
HTML sliders and switches
↓
JavaScript params object
↓
Explicit uniform uploads per rendered frame
↓
Effect GLSL program
Fullscreen button or F key
↓
window.__TAURI__.core.invoke("toggle_fullscreen")
↓
Rust command
↓
app.get_webview_window("main")
↓
Native fullscreen state
| Mode | Processing |
|---|---|
| Passthrough | Camera texture with only zoom and color controls |
| Wave Distort | Time-driven horizontal and vertical sine displacement |
| Radial Warp | Polar-coordinate radius modulation from the image center |
| Kaleidoscope | Six-sector mirrored rotational mapping |
| Edge Detect | Sobel luminance edge extraction using camera-pixel dimensions |
| Glitch | Time-stepped horizontal bands with independent RGB displacement |
src/index.html defines:
src/styles.css provides the current Junkpile shell. The application uses a scrollable controls panel and renderer panel inside a fixed application grid. min-height: 0 is applied to grid children so WKWebView activates scrolling immediately rather than only after a resize.
src/app.js contains:
texImage2D() uploads;src-tauri/src/main.rs registers one command, toggle_fullscreen, and retrieves the configured Tauri 2 WebView with get_webview_window("main").
src-tauri/capabilities/main-capability.json explicitly grants core:default to the main WebView label.
src-tauri/Cargo.toml includes a local workspace boundary:
[workspace]
resolver = "2"
This prevents Cargo from walking upward into an unrelated repository workspace.
src-tauri/Info.plist includes NSCameraUsageDescription, which supplies the text displayed by the macOS camera permission prompt.
src-tauri/entitlements.plist includes the camera entitlement used by the application bundle configuration.
The Tauri configuration points to the entitlement file through bundle.macOS.entitlements.
webcam-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 webcam-tauri-v2-single-template
npm install
The Tauri CLI is a local development dependency. A global Tauri CLI installation is not required.
npm run dev
The WebGL renderer starts immediately. Press Start Camera to trigger the operating-system permission request and begin camera texture uploads.
npm run build
Desktop bundles are written beneath:
src-tauri/target/release/bundle/
A distributed macOS build still requires the appropriate signing and notarization workflow for the final application identity.
The device list is requested once during startup. Before permission is granted, the browser may:
Camera 1;The selector and Refresh Devices button remain available before permission. After a successful camera request, the example enumerates again so real device labels can replace generic names.
Requests a video-only stream with these preferences:
1280 × 720 ideal resolution
30 fps ideal frame rate
60 fps maximum preference
These are preferences, not guarantees. The actual track settings are shown in Diagnostics.
If the selected device becomes stale or cannot satisfy its constraints, the example refreshes the device list and makes one fallback request for the default camera.
Stops every active media track, clears the camera texture to a dark placeholder pixel, and clears both feedback framebuffers. The WebGL renderer remains active.
Selecting another device while a camera is running stops only the old media stream and requests the new source. The WebGL programs, buffers, textures, and UI remain in place.
The example listens for the browser devicechange event. Connecting or disconnecting a camera refreshes the selector without automatically seizing a new source.
| Control | Role |
|---|---|
| Mode | Selects one of the six fragment-shader branches |
| Distortion | Controls wave, radial, kaleidoscope, or glitch strength |
| Feedback | Mixes the current result with the previous framebuffer |
| Zoom | Scales camera UV coordinates around the center |
| Warp Speed | Advances accumulated effect time without phase jumps |
| Hue Shift | Rotates RGB hue in degrees |
| Saturation | Mixes luminance and color |
| Brightness | Multiplies processed output |
| Contrast | Expands or compresses values around 0.5 |
| Mirror X | Reverses the camera texture horizontally |
| Invert | Replaces color with 1.0 - color |
| Greyscale | Replaces RGB with luminance |
| Key | Action |
|---|---|
C |
Start or stop the camera |
Space |
Pause or resume rendering |
R |
Reset parameters, shader time, and feedback |
F |
Toggle native fullscreen |
Shortcuts are ignored while an input, select, or button has focus.
The interface reports:
WEBGL_debug_renderer_info when available;A camera error is nonfatal to WebGL. Permission denial, missing hardware, a busy device, or an ended track produces a visible camera notice while the renderer continues with its placeholder texture.
A WebGL initialization, shader, linker, framebuffer, or context-loss error is fatal to the renderer and displays a full renderer error layer.
This project uses:
main WebView;window.__TAURI__.core.invoke() from vanilla JavaScript;get_webview_window() in Rust;@tauri-apps/cli dependency;Info.plist extension data for macOS camera permission text.It does not use Tauri v1 allowlists or window APIs.
texImage2D(video) performs a browser-to-WebGL upload; it is not a zero-copy native texture bridge.contain / cover aspect-ratio mode before applying effects.texImage2D() polling with requestVideoFrameCallback() where the target WebView supports it.