Skip to content

Ghostty/Shadertoy Shader Compatibility

RoyalTerminal can load Ghostty-style or Shadertoy-style fragment shaders that expose a mainImage entry point. The source is translated into Skia Runtime Effect source and applied through the managed terminal framebuffer shader pipeline.

This is source compatibility for post-process effects. It does not inject shader source into the native Ghostty renderer.

Apply a mainImage shader

csharp
using RoyalTerminal.Shaders;

Terminal.ShaderSources =
[
    new TerminalShaderSource(
        "Ghostty Compatible Scanline",
        """
        void mainImage(out vec4 fragColor, in vec2 fragCoord) {
            vec2 uv = fragCoord / iResolution.xy;
            vec4 color = texture(iChannel0, uv);
            float scanline = 1.0 - mod(floor(fragCoord.y), 2.0) * 0.18;
            fragColor = vec4(color.rgb * scanline, color.a);
        }
        """,
        TerminalShaderLanguage.GhosttyShadertoy)
];

The translated shader samples the rendered terminal frame through iChannel0.

Supported source shape

PatternStatus
void mainImage(out vec4 fragColor, in vec2 fragCoord)Supported.
iResolution, iTime, iTimeDelta, iFrameSupported as optional uniforms.
iChannelResolutionSupported. Channel 0 matches the terminal frame size.
iChannel0Supported and bound to the terminal frame.
texture(iChannel0, uv)Rewritten to the Skia child shader sampler, including nested coordinate expressions.
texture2D(iChannel0, uv)Rewritten to the Skia child shader sampler, including nested coordinate expressions.
GLSL vec2, vec3, vec4Rewritten to Skia float2, float3, float4.
GLSL ivec2, ivec3, ivec4Rewritten to Skia int2, int3, int4.
GLSL mat2, mat3, mat4Rewritten to Skia float2x2, float3x3, float4x4.
#version, #ifdef GL_ES, precision mediump float;Removed for common portable GLSL/Shadertoy sources.

Known Ghostty/Shadertoy uniforms declared in the source are removed before the RoyalTerminal prelude is added. This includes common cursor and channel-resolution uniforms, so portable Ghostty sources can keep their original declarations.

RoyalTerminal-specific uniforms

In addition to Shadertoy-style uniforms, the runtime exposes terminal state:

UniformMeaning
iBackgroundColorTerminal background RGB.
iForegroundColorTerminal foreground RGB.
iCursorColorCursor color RGB.
iCurrentCursorCursor rectangle as left, top, width, height.
iCurrentCursorColorCursor color RGBA.
iCurrentCursorStyleCursor style id in the first component.
iCursorVisibleCursor visibility flag in the first component.

These uniforms are useful for effects that treat text, background, or cursor regions differently.

Coordinate rules

fragCoord is a framebuffer pixel coordinate. For normalized texture coordinates, divide by iResolution.xy:

glsl
vec2 uv = fragCoord / iResolution.xy;
vec4 color = texture(iChannel0, uv);

The compatibility sampler clamps UV values to the terminal frame.

Limitations

This compatibility mode is designed for single-pass framebuffer effects. It does not provide a full Shadertoy runtime or a full Ghostty renderer integration.

Not currently supported:

  • multi-pass buffers
  • additional image or cube-map channels
  • audio channels
  • arbitrary GLSL preprocessor/include flows
  • custom sampler states
  • native Ghostty renderer custom-shader injection

For effects that need those capabilities, port the final pass to Skia Runtime Effect Shaders or use a future native renderer integration.

Relationship to Ghostty VT

The compatibility mode is independent from the selected VT processor. You can use a Ghostty-compatible shader while running the managed VT engine, and you can use direct Skia shaders while running the Ghostty-backed VT engine.

The reason is architectural: RoyalTerminal's Ghostty VT binding produces terminal state, while framebuffer shaders are applied after RoyalTerminal has rendered that state into a Skia surface.

MIT Licensed