Most GLSL shader editors are standalone web apps or separate desktop tools. They give you a canvas, a code editor, and maybe a few uniforms. If you want to animate values, sync to audio, load your own textures, or record the result as video, you're mostly on your own.

DASCA's Shader Playground is different. It's a full real-time GLSL development environment built into the same app where you process images, video, and 3D. You write shaders directly against your loaded media, animate any uniform on a keyframe timeline, react to live audio, and export the result as a video file, all without leaving the application.

## What Is the DASCA Shader Playground?

The Playground is a fragment shader editor that runs on a full-screen quad over your canvas. Your shader receives the current frame of any loaded video or image as `tDiffuse` and outputs a processed result. There's no scene geometry to manage, just a flat post-processing pass where you control every pixel.

Open it with `Ctrl + Tab`. While the Playground is active, it takes full focus. All main-app shortcuts are suspended so the keyboard is free for the code editor.

## Writing Your First GLSL Shader

The default fragment shader is a passthrough that also supports a custom texture override. Every built-in preset is built on this pattern:

```glsl
uniform sampler2D tDiffuse;
uniform sampler2D tCustom1;
varying vec2 vUv;

vec4 sp_input_at(vec2 uv) {
  vec4 a = texture2D(tCustom1, uv);
  if (a.a > 0.001) return a;
  return texture2D(tDiffuse, uv);
}
vec4 sp_input() { return sp_input_at(vUv); }

void main() {
  gl_FragColor = sp_input();
}
```

From here you can do anything: distort UVs, sample noise, accumulate feedback, or run a full convolution kernel. The engine handles WebGL 1/2 compatibility automatically. Write in WebGL1 style (`texture2D`, `gl_FragColor`, `varying`) and the preprocessor upgrades it when running on a WebGL2 context.

## System Uniforms: Everything You Need Without Setup

The engine automatically provides a set of system uniforms every frame. Declare the ones you need and they're bound to your shader. No manual setup needed.

| Uniform | What it does |
| --- | --- |
| `tDiffuse` | Current input frame (video or image) |
| `tPrevFrame` | Last frame's rendered output — for feedback and trails |
| `u_time` | Elapsed time in seconds |
| `u_resolution` | Canvas size in pixels |
| `u_mouse` / `u_mouseNorm` | Mouse position (pixel and normalized) |
| `u_mouseDown` | `1.0` while left mouse button is held |
| `u_frame` | Integer frame counter |
| `tNoise` | Built-in 256×256 white noise texture |
| `tBlueNoise` | Built-in 64×64 blue noise (Bayer pattern) |

A minimal animated warp shader looks like this:

```glsl
uniform sampler2D tDiffuse;
uniform float u_time;
uniform vec2 u_resolution;
uniform vec2 u_mouseNorm;
varying vec2 vUv;

void main() {
  vec2 uv = vUv;
  uv += sin(uv * 10.0 + u_time) * 0.01 * u_mouseNorm;
  gl_FragColor = texture2D(tDiffuse, uv);
}
```

Only declare what you use. The engine detects which uniforms are present in your source and only binds those.

## Custom Uniforms with Interactive Controls

Any `uniform float`, `uniform int`, `uniform bool`, `vec2`, `vec3`, or `vec4` that isn't a system uniform automatically appears as an interactive slider, toggle, or vector input in the Uniforms Panel below the code editor.

The engine infers sensible ranges from the uniform name. A uniform called `u_intensity` gets range `0.0–2.0`. One called `u_decay` gets `0.0–1.0`. You can override with an inline comment:

```glsl
uniform float u_intensity; // 1.5
uniform float u_decay;     // 0.88
```

Name a `vec3` or `vec4` with `color` or `colour` in the name and it shows a color picker instead of numeric inputs:

```glsl
uniform vec3 u_tintColor;    // color picker
uniform vec4 u_shadowColour; // vec4 color picker
```

Slider values persist across recompiles. Fixing a syntax error doesn't reset your parameters.

## Feedback Buffers: Trails, Accumulation, and Echo Effects

The Playground maintains three ping-pong buffer pairs that let shaders read and write across frames:

- **`tPrevFrame`**: the full rendered output of the previous frame
- **`tMotionBuffer`**: motion accumulation buffer
- **`tResidualBuffer`**: residual / accumulation buffer

A motion trail shader that accumulates over time:

```glsl
uniform sampler2D tDiffuse;
uniform sampler2D tPrevFrame;
uniform float u_decay; // 0.88

varying vec2 vUv;

void main() {
  vec4 current  = texture2D(tDiffuse, vUv);
  vec4 previous = texture2D(tPrevFrame, vUv);
  gl_FragColor  = max(current, previous * u_decay);
}
```

Adjust `u_decay` to control how fast old frames fade. Hit the **Reset** button in the canvas toolbar to clear all feedback history when testing.

## Custom Texture Slots

Eight user texture slots (`tCustom1` through `tCustom8`) appear as small tiles below the canvas. Click an empty tile to load an image or video. Both static images and video textures update every frame automatically.

Use custom textures for masking, overlays, displacement maps, or any technique that needs a second input alongside the main video.

```glsl
uniform sampler2D tCustom2;
varying vec2 vUv;

void main() {
  vec4 overlay = texture2D(tCustom2, vUv);
  // blend, multiply, screen, etc.
}
```

## Audio-Reactive Shaders

Load an audio file into the audio slot and the engine analyzes it in real time via the Web Audio API. Four uniforms and one FFT texture are available:

| Uniform | What it gives you |
| --- | --- |
| `u_audioBass` | Average bass band energy (0–2 kHz) |
| `u_audioMid` | Average midrange energy (2–12 kHz) |
| `u_audioHigh` | Average high frequency energy (12–22 kHz) |
| `u_audioAmplitude` | Overall loudness (all bins averaged) |
| `tAudioFFT` | 256×1 texture, R channel = per-bin magnitude |

A bass-reactive camera zoom:

```glsl
uniform float u_audioAmplitude;
uniform float u_audioBass;
uniform sampler2D tDiffuse;
varying vec2 vUv;

void main() {
  float zoom = 1.0 + u_audioBass * 0.15;
  vec2 uv = (vUv - 0.5) / zoom + 0.5;
  vec4 c  = texture2D(tDiffuse, uv);
  c.rgb  *= 0.8 + u_audioAmplitude * 0.8;
  gl_FragColor = c;
}
```

When any audio uniform is declared, the preprocessor injects `#define HAS_AUDIO 1` automatically, so you can write conditional code for shaders that work with or without audio.

## Keyframe Timeline: Animate Any Uniform

The Timeline panel lets you animate any custom `float` or `int` uniform over time. No scripting, no expression language, just a keyframe editor.

Move the playhead to a time, adjust a slider in the Uniforms Panel, and press **K** to keyframe all tracked uniforms at once. Easing options include linear, ease-in, ease-out, ease-in-out, step, and a custom cubic Bézier mode with draggable tangent handles.

During timeline playback, uniform values are written directly to the GPU without any React state updates, so 60+ fps is achievable even with many animated uniforms.

## Built-in Presets to Get Started

The preset dropdown includes ready-to-use shaders organized by category:

- **Color Correction** — Hue/Saturation, Split Toning, Color Curves
- **Distortion** — Chromatic Aberration, Liquid Warp
- **Noise / Film** — Film Grain + Vignette, CRT Scanlines
- **Temporal / Feedback** — Motion Trail
- **Edge / Style** — Edge Detection (Sobel)
- **Noise Texture** — Blue Noise Dither
- **Audio / Reactive** — OSC Sound (Lissajous oscilloscope visualizer with phosphor colors)

Loading a preset replaces both vertex and fragment shaders and switches to Play mode automatically. Timeline tracks for uniforms that no longer exist are cleaned up.

## Shader Library: Save, Browse, and Share

Press `Ctrl + S` to save the current shader to the Library. A 128×128 JPEG thumbnail is captured automatically. Load saved shaders by clicking their card, search by name, and export/import in a portable JSON format for sharing between DASCA installations.

```json
{
  "version": 1,
  "shaders": [
    {
      "name": "My Shader",
      "fragment": "...",
      "thumbnail": "data:image/jpeg;base64,..."
    }
  ]
}
```

## Recording: Export Shaders as Video

Click **Rec** in the TopBar to open recording controls. Set frame rate (24, 30, or 60 fps), choose quality (2, 5, or 10 Mbps), and record. The canvas output is captured using `canvas.captureStream` and `MediaRecorder`. Timeline playback works during recording, so animated uniform changes are included in the exported file.

## Auto-Compile and Error Highlighting

In **Play mode**, the shader recompiles automatically 500 ms after any code change. Errors appear in three places: a red banner above the canvas, inline in the editor on the affected line, and as a gutter dot for quick scanning. The last successfully compiled shader stays on the canvas while you type.

Switch to **Stop mode** when making large edits — the engine runs a syntax-only validation pass so you see errors without intermediate compile stutters.

## GPU Performance Overlay

Press **`G`** to toggle the GPU overlay: FPS, CPU frame time, GPU draw time average, WebGL version, unmasked GPU name, max texture size, available texture units, and max uniform count. A live chart shows GPU frame time history for the last 120 frames.

## The Preprocessor and `#pragma unroll`

The engine preprocesses GLSL source before compilation, injecting `#define` flags for detected features (`HAS_PREV_FRAME`, `HAS_AUDIO`, `HAS_NOISE`, etc.) and handling WebGL1/2 compatibility upgrades. It also supports `#pragma unroll` for loops that the GPU driver won't unroll automatically:

```glsl
#pragma unroll
for (int i = 0; i < 8; i++) {
  color += texture2D(tNoise, vUv + float(i) * offset);
}
```

* * *

The DASCA Shader Playground combines a real-time fragment shader editor with keyframe animation, audio reactivity, custom texture inputs, feedback buffers, a shader library, and video recording — all in one place. No switching tools to animate a value or capture the output.
