Custom Shaders

Added in version 0.49.0.

Custom shaders in kitty are event driven and allow for all manner of visual effects from the fun and blingy to useful visual aides. Users can write their own custom shaders or use some from the large set that ship with kitty. Browse the categories below to watch some custom shaders that ship with kitty in action. To use a custom shader add custom_shaders to kitty.conf, for example:

custom_shaders inside-the-matrix

You can see the code for the custom shaders that ship with kitty here.

Cursor trails

You must have enabled cursor trails with something like cursor_trail 1 in kitty.conf for these shaders to take effect.

Blaze

Set your cursor on fire as it moves around.

cursor-trail-blaze

https://download.calibre-ebook.com/videos/cursor-trail-blaze.webm
Lightning

Make your cursor shoot lightning as it moves around.

cursor-trail-lightning

https://download.calibre-ebook.com/videos/cursor-trail-lightning.webm

Animated backgrounds

Inside The Matrix

See the bones of reality.

inside-the-matrix

https://download.calibre-ebook.com/videos/inside-the-matrix.webm
Northern Lights

The ethereal Aurora Borealis.

northern-lights

https://download.calibre-ebook.com/videos/northern-lights.webm
Fireworks

Celebrate the sheer awesomeness of your terminal.

fireworks

https://download.calibre-ebook.com/videos/fireworks.webm
Water

Pretend you are cool enough to code underwater.

water

https://download.calibre-ebook.com/videos/water.webm

Mouse effects

Pond Ripple

Clicking is like throwing stones in a pond.

pond-ripple

https://download.calibre-ebook.com/videos/pond-ripple.webm
Spotlight

Spotlight your mouse pointer as it moves around.

spotlight

https://download.calibre-ebook.com/videos/spotlight.webm

Retro Terminals

Cathode Ray Tube

Your terminal deserves to have curves.

crt

https://download.calibre-ebook.com/videos/crt.webm
CRT Blue

Do you have the blues?

crt-blue

https://download.calibre-ebook.com/videos/crt-blue.webm
Thin Film LCD

You are too modern for CRT

tft

https://download.calibre-ebook.com/videos/tft.webm

Customising and composing shaders

While you can always customise the shader by editing the shader itself, this is overkill. Often shader declare parameters that you can tune to customise their appearance. If you open the shader file such parameters are often declared near the top as static const variables. You can customise these using pipeline files without needing to edit the shader itself. You can also compose multiple shaders to create sophisticated effects via pipeline files. Pipeline files are explored in full detail below, here we will see a quick example to get you started:

startgroup
    var float4 TINT = float4(0, 0.8, 0.6, 1)
    shaders crt
endgroup

startgroup
    animation_start os-window-focus-in | user-activity
    animation_stop os-window-focus-out | user-idle
    shaders spotlight
endgroup

This pipeline combines two shaders, the crt shader and the spotlight on mouse shader. In the first group we have the crt shader and its color has been customised. The second group defines the spotlight shader and specifies exactly when the spotlight animation should start and stop.

Anatomy of a custom shader

Shaders in kitty are written in slang which is a shading language that compiles down to the actual shading language used by the underlying platform. A custom shader consists of two parts: the actual shader, which is basically a single function that takes an input color and some input uniforms and textures and outputs the resulting color. Secondly, a pipeline file, which is responsible for specifying how different custom shaders are grouped together, how they render, what animation trigger events they respond to, etc. The two parts are described below.

Custom shaders are run at the very end of the kitty rendering pipeline, when everything else has already been rendered. Note that all colors are in the linear RGB color space and co-ordinates are in the traditional UV co-ordinate system with its origin in the lower left corner and Y increasing upwards.

How shaders are loaded

When you set the custom_shaders XXX setting in kitty.conf kitty tries to load a pipeline file with that name and if no pipeline file is found but a shader with that name is found instead, it is loaded with a default pipeline file. Loading of pipeline files takes place in the following order:

  1. If the specified pipeline file is an absolute path, it is loaded directly

  2. If a relative path it is loaded from the shaders sub-directory inside the kitty config directory

  3. It is looked for amongst the custom shaders shipped with kitty

  4. A default pipeline file is constructed

The shaders that a pipeline references are first looked for in the directory from which the pipeline file is loaded, then they follow the same order as for pipeline files.

The shader part

This is in a .slang file. It must define a function called fragment_main() whose signature is:

public float4
fragment_main(
    // The color from the previous custom shader in this group or from the
    // backbuffer if this is the first shader in the group
    float4 color,

    // See types.slang for details on the data in this structure
    KittyTextures t,

    // See types.slang for details on the data in this structure
    KittyCustomShaderData d) {

The two structs passed into this function have the definition shown below:

public struct KittyCustomShaderData {
    public float4 src_rect, dest_rect; // used by the vertex shader internally

    // global background/foreground colors from kitty options (linear RGB, alpha=1)
    public float4 background, foreground;
    // background color of the active window in the active tab (linear RGB, alpha=1)
    public float4 active_window_background;
    // Mouse position in UV coordinates (origin at lower-left).
    // Values outside [0, 1] indicate the mouse is outside the OS window.
    // The first two co-ordinates are current position, the last two the
    // position of the last left mouse press.
    public float4 mouse_pos;
    // Press state of the first four mouse buttons (left, right, middle, button4).
    // Each component is 1.0 if the button is currently held, 0.0 otherwise.
    public float4 mouse_button_pressed;
    // Geometry of the currently active window in UV co-ordinates (origin bottom-left).
    // .xy = lower-left corner of the window, .zw = width and height.
    // All components are zero when no active window was rendered this frame.
    public float4 active_window_geometry;
    // Geometry of the most recently belled window in UV co-ordinates (origin bottom-left).
    // .xy = lower-left corner of the window, .zw = width and height.
    // If the belled window is not currently visible in the active tab/layout, equals central_area.
    // All components are zero until the first bell-in-window event occurs.
    public float4 bell_window_geometry;
    // Geometry of the central content area of the OS window (the tab area, excluding any tab bar)
    // in UV co-ordinates (origin bottom-left).
    // .xy = lower-left corner, .zw = width and height.
    // Equals the full viewport when no tab bar is visible.
    public float4 central_area;

    // Cursor trail state (UV coordinates, origin bottom-left, (1,1) top-right).
    // All fields are zero when the cursor_trail kitty option is disabled or the trail is inactive.
    // The trail is a morphing quadrilateral whose four corners animate toward the cursor position.
    // Corner order: 0=top-right, 1=bottom-right, 2=bottom-left, 3=top-left.
    // Use animation_start cursor-trail-move / animation_stop cursor-trail-stop to activate.
    // animation_progress is NOT tied to trail duration; use cursor_trail_state.x for opacity instead.
    public float4 cursor_trail_corners_x; // UV x-coordinates of the 4 animated trail quad corners
    public float4 cursor_trail_corners_y; // UV y-coordinates of the 4 animated trail quad corners
    // .x=left, .y=right, .z=top, .w=bottom UV edges of the current cursor rectangle
    public float4 cursor_trail_edge;
    // .x=left, .y=right, .z=top, .w=bottom UV edges of the cursor rectangle before the most recent move.
    // All zeros until the cursor has moved at least twice (first move establishes current; second move sets this).
    public float4 cursor_trail_prev_edge;
    // Effective cursor color (linear RGB) and opacity for the active window.
    // .rgb = cursor background color, .a = cursor opacity (0..1).
    // Both are zero when no active window was rendered this frame.
    public float4 cursor_color;
    // Effective cursor trail color (linear RGB) and opacity.
    // .rgb = trail color (falls back to cursor_color.rgb when cursor_trail_color option is "none").
    // .a = trail opacity (0..1); zero when the trail is inactive.
    public float4 cursor_trail_color;

    // the size in pixels of the full underlying viewport we are rendering too,
    // before applying any viewport transforms from the current group.
    // You can use t.viewport to derive the actual size and position of the
    // viewport currently being rendered too.
    public uint2 viewport_size_pixels;

    public float mouse_pointer_hidden; // 1.0 if the mouse pointer is currently hidden, 0.0 if visible
    public float cursor_trail_state;   // 1.0 if the cursor trail is actively animating, 0.0 otherwise

    public float timestamp;        // time in seconds since kitty was started (millisecond precision)
    public float last_rendered_at; // time of previous render in seconds since kitty was started (millisecond precision)
    public uint frame_counter;     // cumulative frame counter increments by one every time a frame is fully rendered, first frame is zero
    // Time (same epoch as `timestamp`) of the most recent cursor position change that triggered a trail.
    // Zero until the cursor has moved at least twice. Pair with cursor_trail_prev_edge.
    public float cursor_trail_change_time;
}

public struct KittyTextures {
    // the buffer containing the original rendered pixels from kitty's own rendering. This will
    // be updated with the output color only after all custom shaders in this group have run
    public Sampler2D backbuffer;
    // The following are named textures that can be sampled from in custom
    // shaders. The persist texture will persist across frames, anything
    // written to it in render number N can be read from it in subsequent
    // renders.
    public Sampler2D a;
    public Sampler2D b;
    public Sampler2D persist;

    public float2 pos; // the position of this pixel in the viewport (UV coordinates, [0,1] range, origin at bottom-left)

    // Some group related uniforms

    // The viewport for this group in the form (x, y, width, height) where (x, y) is the
    // bottom-left corner, with (0, 0) at the bottom-left of the screen and y increasing upwards
    // (UV coordinates). Defaults to (0, 0, 1, 1) when no viewport is defined for the group.
    public float4 viewport;
    // The value after applying the animation easing curve to time since animation start.
    // Zero when no animation is active.
    public float animation_progress;
    // The index of the current group in the pipeline.
    public int group;
}

Shaders take their inputs and use them to transform the color as they see fit.

The pipeline part

A pipeline file (.pipeline) is a plain text file that controls how one or more shaders are assembled into a rendering pipeline. Lines starting with # are comments; blank lines are ignored.

Top-level directives (outside any group):

slot end

The rendering slot. Currently only end is supported (the shaders run after all of kitty’s own rendering is complete). This is the default and can be omitted.

textures a b persist

Declares which named textures the pipeline uses. List only the names you need — each one causes an off-screen framebuffer to be allocated. The available names are a, b, and persist; see Named textures below for their semantics.

var <type> <name> <value>

Sets a pipeline-level shader variable. Any static const declaration in a shader whose name matches <name> is replaced with static const <type> <name> = <value>; before compilation, baking the value in. This is the mechanism for tuning shader parameters without touching the shader source. Supported types are int, uint, float, double, bool and their vector variants (float2, float3, float4, int2, etc.). Pipeline-level variables apply to every group; group-level var directives (see below) take precedence within that group.

Groups

A group is the unit of rendering. Each group runs a chain of shaders in sequence, with the output color of one shader feeding as the input color to the next. A pipeline may contain up to 16 groups.

startgroup
    shaders <name1> [name2 …]
    # optional per-group settings
endgroup

Directives inside a group:

shaders <name> [name …]

One or more shader names to run in order. Shaders are searched for first in the shaders/ subdirectory of the kitty config directory, then among the shaders shipped with kitty.

var <type> <name> <value>

Same as the top-level var but scoped to this group. Merged with pipeline-level vars; the group value wins on conflict.

viewport_pos <x> <y>

The bottom-left corner of this group’s rendering region, expressed as unit floats in UV coordinates (0,0 = bottom-left of the screen, 1,1 = top-right). Defaults to 0 0. Ignored for the final group, which always covers the full screen.

viewport_size <w> <h>

The width and height of the rendering region as unit floats. Defaults to 1 1. Ignored for the final group.

output_texture <name>

Where the group writes its output. The default (default) ping-pongs the backbuffer: the rendered result becomes the new backbuffer that subsequent groups read from t.backbuffer. Setting this to a, b, or persist instead writes into the corresponding named texture; the backbuffer is left unchanged for the next group. The final group must always use the default output.

animation_start <event>[|<event> …]

Events that trigger the start of this group’s animation. Multiple events are separated by |. If omitted the group runs on every frame unconditionally. See Animation events below for the full list.

animation_stop <token>[|<token> …]

When to stop the animation. Tokens are separated by | and may be:

  • An event name — the animation stops when that event fires.

  • An integer — a duration in milliseconds after which the animation automatically stops. The animation_progress value will smoothly reach 1.0 at that point.

  • never — the animation runs indefinitely until an explicit stop event.

When animation_stop is omitted kitty uses cursor_stop_blinking_after as the duration.

animation_curve <curve>

A CSS easing function that maps raw elapsed time to the animation_progress value delivered to the shader. Supported values:

  • Named curves: linear, ease, ease-in, ease-out, ease-in-out, step-start, step-end

  • cubic-bezier(x1, y1, x2, y2)

  • linear(p0, p1[, …])

  • steps(n, start|end)

Defaults to a linear mapping (no easing).

animation_step <ms>

How many milliseconds between animation frames. Lower values produce smoother animation at the cost of more GPU draws. Defaults to 50 ms (20 fps). The value is clamped to be no smaller than repaint_delay.

attach

Mark this group as attached to the preceding group. An attached group is automatically active whenever its preceding group is active, without needing to duplicate the animation_start / animation_stop directives. This is the primary way to build multi-pass effects where several groups must activate and deactivate together. The first group in a pipeline cannot use attach. An attached group may also carry its own animation_start — in that case it becomes active when either condition is true (its own events or the preceding group being active). If an attached group has no animation_start of its own, it mirrors the preceding group exactly: it activates when that group activates and deactivates when that group deactivates. animation_progress for an attached group is computed from the same started_at timestamp as the preceding group, so both groups animate in perfect sync.

Named textures

Named textures are additional off-screen buffers accessible to all shaders in the pipeline via the KittyTextures struct (t.a, t.b, t.persist).

a, b

Scratch textures that exist for the lifetime of the pipeline. They start uninitialized at the start of each draw call. Their typical use is for intermediate rendering: a group renders into a via output_texture a, and a later group reads t.a to combine it with the backbuffer.

persist

Like a/b but survives across frames. Whatever is written to t.persist during frame N can be read back from t.persist during frame N+1. This allows effects that accumulate state over time (e.g. simulation steps, trails, fluid simulations).

All three named textures have the same pixel dimensions as the OS window viewport. They must be declared in the top-level textures directive before they can be referenced. A named texture that is the output target for the current group cannot simultaneously be sampled as input; kitty automatically substitutes the backbuffer in that case.

How groups chain together

The backbuffer starts out as kitty’s fully rendered terminal frame. Each group reads from t.backbuffer and writes somewhere:

  • output_texture default (the default): the group’s output becomes the new backbuffer. The next group reads the modified pixels. This is the standard way to chain post-processing passes.

  • output_texture a|b|persist: the group writes into the named texture instead, leaving the backbuffer intact for the next group.

Groups that have animation_start set are skipped entirely when their animation is not active, saving GPU work. Groups without any animation_start run on every frame.

A minimal pipeline — one shader, event-driven animation:

startgroup
    animation_start pointer-left-button-press
    animation_stop 1500
    animation_curve ease-out
    animation_step 16
    shaders pond-ripple
endgroup

A two-group pipeline using named textures:

textures a

startgroup
    shaders bloom-prepass
    output_texture a
endgroup

startgroup
    shaders bloom-composite
endgroup

Here the first group renders a glow pre-pass into texture a, and the second group reads both the original backbuffer (t.backbuffer) and the glow data (t.a) to composite the final image. Note that these shaders aren’t shipped with kitty, this is jut an illustrative example.

A multi-pass event-driven effect using attach:

textures a

startgroup
    animation_start pointer-left-button-press
    animation_stop 1500
    animation_curve ease-out
    animation_step 16
    shaders ripple-prepass
    output_texture a
endgroup

startgroup
    attach
    animation_step 16
    shaders ripple-composite
endgroup

The second group uses attach so it activates and deactivates in lockstep with the first group — no need to repeat the animation_start / animation_stop / animation_curve directives. Both groups also share the same animation_progress value so they animate in perfect sync.

Animation events

Animation events drive when groups start and stop animating. They are used in the animation_start and animation_stop directives. Multiple events can be combined with | — any one of them will trigger the action.

Event name

When it fires

pointer-left-button-press

The left mouse button is pressed inside the OS window. The click position is available in d.mouse_pos.zw.

os-window-focus-in

The OS window gains keyboard focus. Also fires on the first frame immediately after the pipeline is loaded.

os-window-focus-out

The OS window loses keyboard focus.

window-focus-in

The active kitty window (pane) changes and a new window gains focus. The focused window’s geometry is in d.active_window_geometry.

window-focus-out

A kitty window (pane) loses focus.

tab-change

The active tab changes.

bell-in-window

A bell (BEL character) is received in any kitty window. The geometry of the window in which the most recent bell occurred is available in d.bell_window_geometry (UV co-ordinates, origin bottom-left; .xy = lower-left corner, .zw = width and height). If that window is not currently visible in the active tab or layout, d.bell_window_geometry equals d.central_area.

user-activity

Any keyboard or mouse input is received.

user-idle

The user has been idle (no keyboard or mouse input) long enough to trigger the idle threshold.

cursor-trail-move

The cursor trail begins moving (requires cursor_trail to be enabled in kitty.conf). The trail geometry is available via d.cursor_trail_corners_x, d.cursor_trail_corners_y, and d.cursor_trail_edge. When any group subscribes to this event, kitty’s built-in cursor trail rendering is suppressed so the shader takes over completely.

cursor-trail-stop

The cursor trail stops moving. Typically used as an animation_stop event paired with cursor-trail-move as the start event.

Note that if both a start event and a focus-out event fire in the same frame (for example, a mouse click that simultaneously moves focus away), the focus-out takes priority and the animation is not (re-)started.