2
Making Shaders
Aiden edited this page 2026-05-08 19:03:26 +10:00

Making Shaders

Shader packages are folders loaded from the shader library:

shaders/my-effect/
  shader.json
  shader.slang
  optional-texture-or-font-assets

The runtime reads the manifest, generates a wrapper, includes your Slang code, compiles it to GLSL, and exposes the shader in the operator UI. Most shaders are single-pass. Advanced shaders can declare explicit render passes.

Minimal Shader

shader.json:

{
  "id": "my-effect",
  "name": "My Effect",
  "description": "A simple starter shader.",
  "category": "Custom",
  "entryPoint": "shadeVideo",
  "parameters": [
    {
      "id": "strength",
      "label": "Strength",
      "type": "float",
      "default": 0.5,
      "min": 0.0,
      "max": 1.0,
      "step": 0.01,
      "description": "Blend amount for the effect."
    }
  ]
}

shader.slang:

float4 shadeVideo(ShaderContext context)
{
    float4 color = context.sourceColor;
    color.rgb = lerp(color.rgb, 1.0 - color.rgb, strength);
    return saturate(color);
}

With autoReload enabled, edits should reload automatically. You can also use Reload shaders in the UI.

Manifest Fields

Field Required Purpose
id Yes Stable package ID used by state, presets, API, and OSC. Hyphens are OK here.
name Yes Human-readable name in the UI.
parameters Yes Exposed controls. Use [] when there are none.
description No Short help text in shader listings.
category No UI grouping label.
entryPoint No Function to call. Defaults to shadeVideo.
passes No Advanced render-pass declarations. Omit for normal single-pass shaders.
textures No Texture assets to bind as samplers.
fonts No Font assets used by text parameters.
temporal No Previous-frame history request.

Parameter objects may include description; the UI shows it as short helper text. Shader-visible identifiers must be valid Slang identifiers: letters, numbers, and underscores only, starting with a letter or underscore. This applies to entryPoint, parameter IDs, texture IDs, font IDs, and pass IDs.

Render Passes

Most packages omit passes; the runtime creates one implicit pass that calls shadeVideo and writes layerOutput.

Use explicit passes for effects that need intermediate render targets, such as separable blur, matte refinement, or diagnostics:

{
  "passes": [
    {
      "id": "mask",
      "source": "shader.slang",
      "entryPoint": "makeMask",
      "output": "maskBuffer"
    },
    {
      "id": "final",
      "source": "shader.slang",
      "entryPoint": "finish",
      "inputs": ["maskBuffer"],
      "output": "layerOutput"
    }
  ]
}

layerInput means the input to this layer. previousPass means the previous pass output. Any earlier pass ID or output name can be used as an input.

Entry Point And Context

Your shader implements the function named by entryPoint.

float4 shadeVideo(ShaderContext context)
{
    return context.sourceColor;
}

Do not write your own fragment entry point. The runtime owns that. Your function returns the fully effected color, and the wrapper handles runtime mix and bypass behavior.

The current ShaderContext includes:

struct ShaderContext
{
    float2 uv;
    float4 sourceColor;
    float2 inputResolution;
    float2 outputResolution;
    float time;
    float utcTimeSeconds;
    float utcOffsetSeconds;
    float startupRandom;
    float frameCount;
    float mixAmount;
    float bypass;
    int sourceHistoryLength;
    int temporalHistoryLength;
};

Use uv for normalized coordinates, sourceColor for the current layer input, time for animation, startupRandom for stable per-launch variation, UTC fields for clock displays, and outputResolution or inputResolution for pixel-sized effects.

The current pipeline uses display-referred Rec.709-like RGB for sourceColor and sampling helpers. Internal render targets are 16-bit floating point.

Sampling Helpers

float4 sampleVideo(float2 uv);
float4 sampleSourceHistory(int framesAgo, float2 uv);
float4 sampleTemporalHistory(int framesAgo, float2 uv);

sampleVideo samples the current decoded source. The history functions safely clamp requested frame numbers and fall back when no history is ready.

Parameters

Manifest parameters become shader globals or runtime-provided helpers.

Manifest Type Slang Type JSON/API Value
float float number
vec2 float2 [x, y]
color float4 [r, g, b, a]
bool bool true or false
enum int string in JSON, zero-based index in Slang
text generated texture/helper string
trigger int <id>, float <id>Time pulse/count

Float values are clamped to min and max when present. vec2 needs exactly two numbers. color needs exactly four numbers. Enum defaults must match an option value.

Textures

Declare every texture in shader.json:

{
  "textures": [
    {
      "id": "logoTexture",
      "path": "logo.png"
    }
  ]
}

The path is relative to the shader package folder. The texture ID becomes a sampler:

float4 logo = logoTexture.Sample(logoUv);

For overlays or sprites, return useful alpha. DVD Bounce is a good texture-driven example.

Fonts And Text

Declare packaged font assets for text parameters:

{
  "fonts": [
    { "id": "roboto", "path": "fonts/Roboto-Regular.ttf" }
  ],
  "parameters": [
    {
      "id": "titleText",
      "label": "Text",
      "type": "text",
      "default": "VIDEO SHADER",
      "font": "roboto",
      "maxLength": 64
    }
  ]
}

Text is rendered by the runtime into a single-line SDF mask texture. The wrapper exposes helpers based on the parameter ID, such as sampleTitleText(textUv) and drawTitleText(textUv, color).

Text is currently printable ASCII. maxLength defaults to 64 and is clamped to 1..256.

Triggers

trigger parameters appear as momentary buttons in the UI. Pressing one increments an integer uniform and records the host runtime time in <id>Time.

{
  "id": "drop",
  "label": "Drop",
  "type": "trigger"
}

Shader-side:

float age = context.time - dropTime;
float pulse = drop > 0 ? exp(-age * 4.0) : 0.0;

Triggers are useful for one-shot reactions such as flashes, ripples, cuts, or randomized looks.

Temporal Shaders

Temporal shaders request previous frames:

{
  "temporal": {
    "enabled": true,
    "historySource": "preLayerInput",
    "historyLength": 12
  }
}

Supported history sources:

Source Meaning
source Previous decoded source frames.
preLayerInput Previous frames arriving at this layer before the shader runs.

The runtime clamps requested history by maxTemporalHistoryFrames. Temporal history resets when layers are added, removed, reordered, bypassed, changed, reloaded, or when render dimensions change.

Debugging

Generated shader files are written under:

runtime/shader_cache/

Useful files:

File Use
active_shader_wrapper.slang See the exact generated Slang wrapper.
active_shader.raw.frag Raw GLSL from slangc.
active_shader.frag Patched GLSL used by OpenGL.

For multipass shaders, these files reflect the most recently compiled pass. If compilation fails, start with the UI compiler panel and the generated wrapper.

Checklist

  • shader.json is valid JSON.
  • id is unique.
  • entryPoint, parameter IDs, texture IDs, font IDs, and pass IDs are valid shader identifiers.
  • shader.slang implements the configured entry point or pass entry points.
  • Texture files exist.
  • Font files exist.
  • Enum defaults are valid option values.
  • Temporal shaders handle empty or short history.
  • Multipass packages write the final visible result to layerOutput, or make the final declared pass the visible output.
  • The shader compiles after reload.