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.
Set your cursor on fire as it moves around.
cursor-trail-blaze
Make your cursor shoot lightning as it moves around.
cursor-trail-lightning
Animated backgrounds¶
See the bones of reality.
inside-the-matrix
The ethereal Aurora Borealis.
northern-lights
Celebrate the sheer awesomeness of your terminal.
fireworks
Pretend you are cool enough to code underwater.
water
Mouse effects¶
Clicking is like throwing stones in a pond.
pond-ripple
Spotlight your mouse pointer as it moves around.
spotlight
Retro Terminals¶
Your terminal deserves to have curves.
crt
Do you have the blues?
crt-blue
You are too modern for CRT
tft
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:
If the specified pipeline file is an absolute path, it is loaded directly
If a relative path it is loaded from the
shaderssub-directory inside the kitty config directoryIt is looked for amongst the custom shaders shipped with kitty
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 endThe rendering slot. Currently only
endis supported (the shaders run after all of kitty’s own rendering is complete). This is the default and can be omitted.textures a b persistDeclares 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, andpersist; see Named textures below for their semantics.var <type> <name> <value>Sets a pipeline-level shader variable. Any
static constdeclaration in a shader whose name matches<name>is replaced withstatic 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 areint,uint,float,double,booland their vector variants (float2,float3,float4,int2, etc.). Pipeline-level variables apply to every group; group-levelvardirectives (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
varbut 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 fromt.backbuffer. Setting this toa,b, orpersistinstead 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_progressvalue will smoothly reach1.0at that point.never— the animation runs indefinitely until an explicit stop event.
When
animation_stopis omitted kitty usescursor_stop_blinking_afteras the duration.animation_curve <curve>A CSS easing function that maps raw elapsed time to the
animation_progressvalue delivered to the shader. Supported values:Named curves:
linear,ease,ease-in,ease-out,ease-in-out,step-start,step-endcubic-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
50ms (20 fps). The value is clamped to be no smaller thanrepaint_delay.attachMark 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_stopdirectives. 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 useattach. An attached group may also carry its ownanimation_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 noanimation_startof its own, it mirrors the preceding group exactly: it activates when that group activates and deactivates when that group deactivates.animation_progressfor an attached group is computed from the samestarted_attimestamp 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,bScratch 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
aviaoutput_texture a, and a later group readst.ato combine it with the backbuffer.persistLike
a/bbut survives across frames. Whatever is written tot.persistduring frame N can be read back fromt.persistduring 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 |
|---|---|
|
The left mouse button is pressed inside the OS window. The click
position is available in |
|
The OS window gains keyboard focus. Also fires on the first frame immediately after the pipeline is loaded. |
|
The OS window loses keyboard focus. |
|
The active kitty window (pane) changes and a new window gains focus.
The focused window’s geometry is in |
|
A kitty window (pane) loses focus. |
|
The active tab changes. |
|
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 |
|
Any keyboard or mouse input is received. |
|
The user has been idle (no keyboard or mouse input) long enough to trigger the idle threshold. |
|
The cursor trail begins moving (requires |
|
The cursor trail stops moving. Typically used as an |
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.