kitty/docs/custom-shaders.rst
2026-08-11 08:47:13 +05:30

424 lines
16 KiB
ReStructuredText

Custom Shaders
===================
.. versionadded:: 0.49.0
.. highlight:: conf
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 :opt:`custom_shaders` to :file:`kitty.conf`,
for example::
custom_shaders inside-the-matrix
You can see the code for the custom shaders that ship with kitty :repo_folder:`here
<kitty/shaders/custom>`.
.. role:: small-dim(raw)
:format: html
:class: sd-text-muted small code literal
.. highlight:: hlsl
Cursor trails
----------------
You must have enabled cursor trails with something like ``cursor_trail 1`` in :file:`kitty.conf` for
these shaders to take effect.
.. include:: generated/custom-shaders-cursor-trails.rst
Animated backgrounds
---------------------
.. include:: generated/custom-shaders-backgrounds.rst
Mouse effects
---------------
.. include:: generated/custom-shaders-mouse.rst
Navigation
---------------
.. include:: generated/custom-shaders-navigation.rst
Retro Terminals
-----------------
.. include:: generated/custom-shaders-retro.rst
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 :ref:`full detail below <custom_shader_pipeline>`, 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 <https://shader-slang.org/>`__
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.
.. _custom_shader_load_order:
How shaders are loaded
^^^^^^^^^^^^^^^^^^^^^^^^^^
When you set the :opt:`custom_shaders XXX <custom_shaders>` setting in :file:`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 :file:`shaders` sub-directory
inside the kitty config directory
#. It 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 :file:`.slang` file. It must define a function called
``fragment_main()`` whose signature is:
.. literalinclude:: ../kitty/shaders/custom/sample.slang
:start-after: START_FUNCTION_SIGNATURE
:end-before: END_FUNCTION_SIGNATURE
The two structs passed into this function have the definition shown below:
.. literalinclude:: ../kitty/shaders/custom/types.slang
:start-at: public struct KittyCustomShaderData {
:end-before: END_TYPES_DEFINITION
Shaders take their inputs and use them to transform the color as they see fit.
.. _custom_shader_pipeline:
The pipeline part
^^^^^^^^^^^^^^^^^^^^
A pipeline file (: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 :ref:`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.
.. _pipeline_groups:
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.
.. code-block:: none
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 :file:`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
:opt:`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
:opt:`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
_________________
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.
.. list-table::
:header-rows: 1
:widths: 30 70
* - 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 :opt:`cursor_trail` to be
enabled in :file:`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.