Core Concepts

History

History is one of ShaderPad’s more unique features, and can assist with snapshots, feedback effects, and creative coding. It lets you sample earlier frames without building the ring buffer yourself.

Shader History

Enable history of the shader’s output in the constructor:

const shader = new ShaderPad(fragmentShaderSrc, {
  canvas,
  history: 10,
})

In GLSL, that gives you access to:

uniform highp sampler2DArray u_history;
uniform int u_historyFrameOffset;

Texture History

You can also keep history for an individual texture:

shader.initializeTexture('u_webcam', videoElement, { history: 30 })

That creates a history texture plus a matching frame-offset uniform such as u_webcamFrameOffset.

Sampling Previous Frames

The helpers plugin provides historyZ() for sampling previous frames:

import ShaderPad from 'shaderpad'
import helpers from 'shaderpad/plugins/helpers'

const FRAME_DELAY = 10;

const fragmentShaderSrc = `#version 300 es
precision highp float;

in vec2 v_uv;
uniform sampler2DArray u_history;
uniform int u_historyFrameOffset;
out vec4 outColor;

void main() {
  // Get the output color from FRAME_DELAY frames ago.
  float z = historyZ(u_history, u_historyFrameOffset, ${FRAME_DELAY});
  vec4 previous = texture(u_history, vec3(v_uv, z));
  outColor = previous;
}`

const canvas = document.createElement('canvas')
const shader = new ShaderPad(fragmentShaderSrc, {
  canvas,
  history: FRAME_DELAY,
  plugins: [helpers()],
})

Use historyZ(..., 1) to sample the previous stored frame. Larger values move further back in time. For texture and plugin history, historyZ(..., 0) is also valid and refers to the current/latest value.

Skipping History Writes

Each frame is written to the history buffer by default. You can prevent a step from updating history with:

{ skipHistoryWrite: true }

This option is either returned from the play() callback or passed to step() as an argument:

// Only write every 10th frame to history.
shader.play((time, frame) => {
  return { skipHistoryWrite: !!(frame % 10) }
})

// Skip writing this frame to history.
shader.step({ skipHistoryWrite: true })

Plugin History

Some plugins can maintain history for the textures they generate. For example, MediaPipe-backed plugins such as face, hands, pose, and segmenter expose a history option so their output textures can be sampled across earlier frames too.

import face from 'shaderpad/plugins/face'

const shader = new ShaderPad(fragmentShaderSrc, {
  plugins: [face({ textureName: 'u_webcam', options: { history: 20 } })],
})

When plugin history is enabled, historyZ(..., 1) refers to the previous stored plugin output, and larger values move further back. As with normal texture history, historyZ(..., 0) is also valid and refers to the current/latest value. If you skip a history write on the watched texture, the plugin history skips that frame as well.

History Precision

By default, history uses 8-bit storage. For float pipelines, history will follow the precision of the internal render texture.

const shader = new ShaderPad(fragmentShaderSrc, {
  history: 60,
  internalFormat: 'RGBA32F',
  type: 'FLOAT',
})

draw does not advance history

draw() renders the current state only. Use step() when a pass should count as a new frame in history.

Previous
Textures