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.

When the cursor moves quickly, the trail can appear to hop from place to place instead of gliding. The cursor-trail-motion-blur shader smooths this out the way a camera does, by blurring the trail slightly along the direction it is moving, and it also smooths the trail’s jagged edges. Enable it with custom_shaders cursor-trail-motion-blur. The options it supports are explained at the top of the shader file.

The cursor-trail-railgun, cursor-trail-torpedo and cursor-trail-pixiedust shaders make particles fly off the cursor as it moves, in the style of neovide. You can change the size, speed, color, lifetime, etc. of the particles, as explained at the top of the shader file.

Motion Blur

Make the cursor trail glide smoothly, like a fast moving object caught on camera.

cursor-trail-motion-blur

https://download.calibre-ebook.com/videos/cursor-trail-motion-blur.webm
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
Railgun

Fire a spiral of particles as your cursor moves around.

cursor-trail-railgun

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

Leave a wake of particles behind your cursor as it moves.

cursor-trail-torpedo

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

Sprinkle glittering pixie dust as your cursor moves around.

cursor-trail-pixiedust

https://download.calibre-ebook.com/videos/cursor-trail-pixiedust.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 shaders 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.

Controlling shaders with key presses

Groups can respond to events you send from a mapping in kitty.conf using the shader_event action. Such events are named in the pipeline with a user: prefix. For example, to show the spotlight for a couple of seconds when you press F1, so that you can find the mouse pointer, create the pipeline file shaders/find-pointer.pipeline in the kitty config directory with:

startgroup
    animation_start user:find-pointer
    animation_stop 2000
    shaders spotlight
endgroup

and in kitty.conf:

custom_shaders find-pointer
map f1 shader_event find-pointer

Use animation_toggle to turn an effect on with one press and off with the next:

startgroup
    animation_toggle user:crt
    shaders crt
endgroup

Here map f2 shader_event crt toggles the crt effect. Events are sent to the active OS window, use map f2 shader_event crt all to send them to all OS windows. You can also send them from scripts with remote control, for example: kitten @ action shader_event crt. Groups that are not active are not drawn at all, so an effect that is turned off costs nothing.

As another example, to blend inactive windows toward gray instead of black, save the following as shaders/dim-gray.pipeline in your kitty config directory and set custom_shaders dim-gray and inactive_text_alpha 1 in kitty.conf:

startgroup
    animation_step 0
    var float HIGHLIGHT_INTENSITY = 0
    var float BORDER_WIDTH = 0
    var float INACTIVE_DIM = 0.74
    var float3 INACTIVE_DIM_COLOR = float3(0.2158605)
    var bool DIM_CENTRAL_AREA_ONLY = true
    var bool INCLUDE_WINDOW_PADDING = true
    shaders focus-highlight
endgroup

This uses the same shader as dim-inactive-windows. INACTIVE_DIM is the fraction of the original color retained: 0.74 blends in 26% of the target color. INACTIVE_DIM_COLOR is in linear RGB; 0.2158605 corresponds to sRGB #808080. The default target is black, preserving the existing dimming effect. The two optional flags preserve the tab bar and include the active window’s title bar, margin, border and padding in its unchanged area. Explicit cell backgrounds and images are blended together with text, while transparency and colors reported to terminal applications are preserved. animation_step 0 makes this static effect redraw only when content or shader events change.

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. A leading ~ and environment variables are expanded.

  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 .pipeline extension is added automatically if not present. If the name ends with .slang, no pipeline file is looked for and the shader is loaded directly, which is useful when a pipeline and a shader have the same name.

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. For shaders, the name is first tried exactly as specified and then with the .slang extension added.

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;
    // Extents beyond the active content, including a visible title bar, margin,
    // border, padding and cell-size compensation.
    // .xy = left/bottom, .zw = right/top, in viewport UV units.
    public float4 active_window_padding;
    // 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
    // Previous-frame animated trail quad corners. These are available for
    // custom shaders that perform temporal effects.
    public float4 cursor_trail_prev_corners_x;
    public float4 cursor_trail_prev_corners_y;
    // .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 most recent cursor moves that started a cursor trail, most recent
    // first. Unlike the other cursor_trail_* fields these remain valid after
    // the trail has finished, so effects started by a move can outlive the
    // trail and effects from several moves can overlap.
    // .x=left, .y=right, .z=top, .w=bottom UV edges of the cursor rectangle
    // before (from) and after (to) the move.
    public float4 cursor_trail_history_from[8];
    public float4 cursor_trail_history_to[8];
    // Seconds since each move, move i is in cursor_trail_history_age[i / 4][i % 4].
    // Negative for unused entries.
    public float4 cursor_trail_history_age[2];
    // 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

    // Time in seconds since kitty was started. Note that this loses precision
    // the longer kitty runs: it is accurate to 1ms only for the first ~4.7 hours
    // and to ~64ms after a week. So for animations use wrapped_time() below,
    // and for the time elapsed since an event, use the various *_age fields.
    public float timestamp;
    public float last_rendered_at; // time of previous render, same epoch and precision as timestamp
    public uint frame_counter;     // cumulative frame counter increments by one every time a frame is fully rendered, first frame is zero
    // Time (same epoch and precision 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.
    // Use cursor_trail_age instead to find how long ago this was.
    public float cursor_trail_change_time;

    // timestamp split into whole seconds and the fraction of the current
    // second, which together are exact however long kitty runs. Usually you
    // want to use wrapped_time() below instead of these.
    public uint timestamp_seconds;
    public float timestamp_fraction; // in [0, 1)
    // Seconds between the previous render and this one, zero for the first render.
    public float time_since_last_render;
    // Seconds since cursor_trail_change_time, negative when cursor_trail_change_time is zero.
    public float cursor_trail_age;

    // Seconds since kitty was started, wrapped to [0, period_seconds). Unlike
    // timestamp, this keeps sub-millisecond precision however long kitty runs,
    // as long as period_seconds is at most an hour or so. The value jumps back
    // to zero once every period, so either use a period over which your
    // animation repeats seamlessly, or a long one so that the jump is rare.
    public float
    wrapped_time(uint period_seconds) {
        return float(timestamp_seconds % period_seconds) + timestamp_fraction;
    }
}

// A texture kitty renders into, addressed in the same UV co-ordinate space as
// KittyTextures.pos. kitty renders into textures that are at least as large as
// the largest OS Window viewport, so the pixels for the current OS Window
// occupy only the lower-left corner of the texture after the window is made
// smaller. Sampling via the Sample() method below hides that detail, so
// shaders never need to know the size of the underlying texture.
public struct KittyTexture {
    // The underlying texture. Use this only if you need raw texture UV,
    // remember to multiply your co-ordinates by scale below when you do.
    public Sampler2D tex;
    // The fraction of tex occupied by the OS Window viewport. Always in (0, 1].
    public float2 scale;
    // The largest usable co-ordinate for Sample() below. Half a texel less than
    // one, so that sampling the top/right edge does not blend in texels from
    // outside the viewport.
    public float2 max_pos;

    // Sample this texture at pos, which is in the same UV co-ordinate space as
    // KittyTextures.pos, that is, [0, 1] with the origin at the bottom-left.
    // Co-ordinates outside that range are clamped to the edge of the viewport.
    public float4
    Sample(float2 pos) {
        return tex.Sample(clamp(pos, float2(0.f), max_pos) * scale);
    }
}

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 KittyTexture 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 KittyTexture a;
    public KittyTexture b;
    public KittyTexture 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. These are here rather than in
    // KittyCustomShaderData as that is shared by all groups, while these can
    // differ from group to group.

    // 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;
    // Goes from zero to one over animation_duration after the group's
    // animation starts, with the animation_curve applied, then stays at one.
    // If animation_reverse is set, it goes back down to zero after the
    // animation stops, going up and down again animation_loops - 1 more
    // times. Always zero for groups that run on every frame and for
    // groups that have neither animation_duration nor a stop duration.
    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.

Shaders that move content

A shader that draws content somewhere other than where kitty put it, such as the crt shader curving the screen, makes the mouse pointer and the text under it disagree, so that clicks and selections land on the wrong cells. To fix that, the shader can also define a function that tells kitty where the content shown at a given position actually is:

public float2
pointer_map(float2 pos, KittyCustomShaderData d) {
    return warp(pos);  // the same function fragment_main() uses to sample the backbuffer
}

pos and the return value are in the same UV co-ordinates as t.pos. The function must be public and return float2. kitty evaluates it on a grid only when the pipeline is loaded and when the OS Window is resized, and uses it to move all pointer events, so the only field of d it may use is d.viewport_size_pixels. Only shaders in groups that draw to the whole screen, that is, without attach, output_texture, viewport_pos or viewport_size, and that are either always active or active until an event turns them off, that is, without an animation_stop duration, are used, since only they move content for any length of time. For a group that is turned on and off by events, such as one using animation_toggle, the function is used only while the group is active, so a shader that moves content can be turned on and off with a key press. When several shaders define it, they are applied starting from the last one to run.

Note that d.mouse_pos then contains the mapped position, that is, the position of the content under the mouse before it was moved. Effects that draw at the mouse position line up with the pointer when they run before the shader that moves content, not after it.

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, as is everything after a # that is preceded by whitespace; 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 directory containing the pipeline file, then in the shaders/ subdirectory of the kitty config directory, then among the shaders shipped with kitty. Absolute paths, including ones starting with ~, are loaded directly.

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. The final group must use this default, since it always covers the full screen. Non-default values are rejected.

viewport_size <w> <h>

The width and height of the rendering region as unit floats. Defaults to 1 1. The final group must use this default; non-default values are rejected.

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 |. A group with neither this nor animation_toggle nor attach 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. Unless animation_duration is set, 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_duration <ms>

How many milliseconds the animation_progress value takes to go from 0.0 to 1.0 after the animation starts. It then stays at 1.0 until the animation stops. This is useful for effects that should fade in and then stay on, for example ones turned on by animation_toggle. Defaults to the animation_stop duration. Note that groups with no stop duration, such as those using animation_stop never or only animation_toggle, always have an animation_progress of 0.0 unless this is set.

animation_reverse yes|no|<ms>

Whether to play the animation in reverse when it stops, for any reason, instead of turning the group off immediately. The group then stays active while animation_progress goes back down to 0.0, using the same animation_curve, and turns off once it gets there. With yes this takes as long as animation_duration, a number sets the time in milliseconds instead. If the animation is stopped before animation_progress reaches 1.0 it goes back down from wherever it was, and if it is started again while going back down, it goes up again from wherever it was. Stop events are ignored while playing in reverse. Defaults to no. For example, to fade the crt effect in and out over half a second when toggling it:

startgroup
    animation_toggle user:crt
    animation_duration 500
    animation_reverse yes
    shaders crt
endgroup

Note that the crt shader does not actually use animation_progress, you would need to modify it to do so.

animation_loops <n>

How many times to play the animation forwards and in reverse when it stops. Can only be used together with animation_reverse. After the first reverse finishes, animation_progress goes from 0.0 to 1.0 over animation_duration and immediately back down again, without staying at 1.0, until it has gone up and down n times in all, after which the group turns off. Stop events are ignored while looping, start and toggle events turn the group back on, continuing from the current progress. Defaults to 1. For example, to blink three times when a key is pressed, with an animation that stops as soon as it has risen to 1.0:

startgroup
    animation_start user:blink
    animation_duration 150
    animation_stop 150
    animation_reverse yes
    animation_loops 3
    shaders my-blink-shader
endgroup
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. Set this to 0 for static effects that should redraw only when content or shader events change, without periodic frames. Animation stop events and duration limits still apply.

animation_toggle <event>[|<event> …]

Events that start this group’s animation when it is not running and stop it when it is. This is mostly useful with user: events sent by the shader_event action to turn effects on and off from a mapping. A toggle takes precedence over start and stop events in the same frame. A group that has animation_toggle but neither animation_start nor animation_stop keeps running until it is toggled off, rather than stopping after cursor_stop_blinking_after.

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 or animation_toggle — 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 neither animation_start nor animation_toggle of its own, it mirrors the preceding group exactly: it activates when that group activates and deactivates when that group deactivates, and has the same animation_progress as that group, so both groups animate in perfect sync. Its own animation_curve, animation_duration and animation_reverse are ignored.

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 cover the same area as the OS Window viewport and are sampled in the same UV co-ordinate space as t.pos, via the Sample() method on KittyTexture. Note that the underlying GPU textures can be larger than the viewport, since kitty grows them to fit the largest OS Window and never shrinks them again, so always sample via Sample() rather than using the raw tex sampler. 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 with animation_start, animation_toggle or attach are skipped entirely when their animation is not active, saving GPU work. Groups without any of these directives 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, animation_stop and animation_toggle 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.

user:<name>

Sent by the shader_event action, typically from a mapping, see Controlling shaders with key presses. <name> can be any combination of letters, digits, underscores, hyphens and periods, up to 63 characters. A pipeline can use up to 48 different user events.

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.