From ad7043bc206e458eb6875afd3d9a4bdae10f2dd9 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Wed, 29 Jul 2026 09:06:45 +0530 Subject: [PATCH] More work on custom shaders --- kitty/boss.py | 4 ++-- kitty/fast_data_types.pyi | 4 +--- kitty/shaders.c | 19 +++++++++++++++---- kitty/shaders/custom/pipeline.slang | 2 ++ kitty/shaders/slang.py | 29 +++++++++++++++++++++++++++-- 5 files changed, 47 insertions(+), 11 deletions(-) diff --git a/kitty/boss.py b/kitty/boss.py index ea90d8f99..8db000ebf 100644 --- a/kitty/boss.py +++ b/kitty/boss.py @@ -3460,9 +3460,9 @@ class Boss: clear_caches() - from .shaders.slang import custom_shader + from .shaders.slang import clear_caches - custom_shader.cache_clear() + clear_caches() def safe_delete_temp_file(self, path: str) -> None: if is_path_in_temp_dir(path): diff --git a/kitty/fast_data_types.pyi b/kitty/fast_data_types.pyi index 111fcc9a4..fdc7a08d7 100644 --- a/kitty/fast_data_types.pyi +++ b/kitty/fast_data_types.pyi @@ -283,6 +283,7 @@ LNM: int BGIMAGE_PROGRAM: int CELL_PROGRAM: int PADDING_PROGRAM: int +AFTER_WINDOW_BG_PROGRAM: int CELL_FG_PROGRAM: int CELL_BG_PROGRAM: int BLIT_PROGRAM: int @@ -521,9 +522,6 @@ def compile_program( ) -> int: pass -def init_cell_program() -> None: - pass - def set_os_window_chrome(os_window_id: int) -> bool: pass diff --git a/kitty/shaders.c b/kitty/shaders.c index a9d2f5751..cc192e324 100644 --- a/kitty/shaders.c +++ b/kitty/shaders.c @@ -12,6 +12,7 @@ #include #include #include "text-cache.h" +#include "tupleobject.h" #include "window_logo.h" #include "srgb_gamma.h" #include "state.h" @@ -27,6 +28,7 @@ enum { SCREENSHOT_PROGRAM, ROUNDED_RECT_PROGRAM, PADDING_PROGRAM, + AFTER_WINDOW_BG_PROGRAM, NUM_PROGRAMS }; enum { SPRITE_MAP_UNIT, GRAPHICS_UNIT, SPRITE_DECORATIONS_MAP_UNIT }; @@ -401,6 +403,10 @@ init_cell_program(void) { bind_shader_globals_to_current_context(); } +static void +init_custom_programs(void) { +} + void bind_shader_globals_to_current_context(void) { if (shader_globals_vao_idx == -1) return; @@ -1994,8 +2000,16 @@ compile_program(PyObject UNUSED *self, PyObject *args) { PyObject *vertex_shaders, *fragment_shaders, *metadata; int which, allow_recompile = 0; if (!PyArg_ParseTuple(args, "iO!O!O!|p", &which, &PyTuple_Type, &vertex_shaders, &PyTuple_Type, &fragment_shaders, &PyDict_Type, &metadata, &allow_recompile)) return NULL; + if (which == -1) { init_cell_program(); Py_RETURN_NONE; } + if (which == -2) { init_custom_programs(); Py_RETURN_NONE; } if (which < 0 || which >= NUM_PROGRAMS) { PyErr_Format(PyExc_ValueError, "Unknown program: %d", which); return NULL; } Program *program = program_ptr(which); + if (!PyTuple_GET_SIZE(vertex_shaders)) { + if (program->id != 0) { + glDeleteProgram(program->id); program->id = 0; + } + Py_RETURN_NONE; + } if (program->id != 0) { if (allow_recompile) { glDeleteProgram(program->id); program->id = 0; } else { PyErr_SetString(PyExc_ValueError, "program already compiled"); return NULL; } @@ -2041,8 +2055,6 @@ ONE_INT(bind_vertex_array) NO_ARG(unbind_vertex_array) TWO_INT(unmap_vao_buffer) -NO_ARG(init_cell_program) - static PyObject* sprite_map_set_limits(PyObject UNUSED *self, PyObject *args) { unsigned int w, h; @@ -2064,7 +2076,6 @@ static PyMethodDef module_methods[] = { MW(unmap_vao_buffer, METH_VARARGS), MW(bind_program, METH_O), MW(unbind_program, METH_NOARGS), - MW(init_cell_program, METH_NOARGS), {NULL, NULL, 0, NULL} /* Sentinel */ }; @@ -2099,7 +2110,7 @@ init_shaders(PyObject *module) { C(CELL_PROGRAM); C(CELL_FG_PROGRAM); C(CELL_BG_PROGRAM); C(BORDERS_PROGRAM); C(GRAPHICS_PROGRAM); C(GRAPHICS_PREMULT_PROGRAM); C(GRAPHICS_ALPHA_MASK_PROGRAM); C(BGIMAGE_PROGRAM); C(TINT_PROGRAM); C(TRAIL_PROGRAM); C(BLIT_PROGRAM); C(SCREENSHOT_PROGRAM); C(ROUNDED_RECT_PROGRAM); - C(PADDING_PROGRAM); + C(PADDING_PROGRAM); C(AFTER_WINDOW_BG_PROGRAM); C(GLSL_VERSION); C(GL_VERSION); C(GL_VENDOR); diff --git a/kitty/shaders/custom/pipeline.slang b/kitty/shaders/custom/pipeline.slang index f85f575a8..89916cf5d 100644 --- a/kitty/shaders/custom/pipeline.slang +++ b/kitty/shaders/custom/pipeline.slang @@ -36,12 +36,14 @@ public float4 pipeline_vertex_main(float4 src_rect, float4 dest_rect, uint verte } uniform Sampler2D backbuffer; +uniform uint timestamp; public float4 pipeline_fragment_main(float2 texcoord) { KittyCustomShaderData d; d.backbuffer = backbuffer; d.texcoord = texcoord; d.backbuffer = backbuffer; + d.timestamp = timestamp; float4 color = backbuffer.Sample(texcoord); // PIPELINE return color; diff --git a/kitty/shaders/slang.py b/kitty/shaders/slang.py index ef407c99c..b20087bb0 100644 --- a/kitty/shaders/slang.py +++ b/kitty/shaders/slang.py @@ -25,6 +25,7 @@ from typing import Any, Callable, Iterable, Iterator, Literal, NamedTuple from kitty.constants import read_kitty_resource, shaders_dir, slangc from kitty.fast_data_types import ( + AFTER_WINDOW_BG_PROGRAM, BGIMAGE_PROGRAM, BLINK, BLIT_PROGRAM, @@ -54,7 +55,6 @@ from kitty.fast_data_types import ( TRAIL_PROGRAM, compile_program, get_options, - init_cell_program, ) from kitty.options.types import Options, defaults from kitty.utils import lock_with_file, resolve_custom_file @@ -175,6 +175,7 @@ def glsl_shaders(name: str, variant_name: str = '') -> tuple[str, str]: class LoadShaderPrograms: text_fg_override_threshold: tuple[float, Literal['%', 'ratio']] = 0, '%' text_old_gamma: bool = False + custom_shaders: dict[str, tuple[str, ...]] = {} opts: Options | None = None @@ -196,6 +197,10 @@ class LoadShaderPrograms: def recompile_if_needed(self) -> None: if self.needs_recompile: self(allow_recompile=True) + else: + opts = self.get_options() + if opts.custom_shader != self.custom_shaders: + self.compile_custom_shaders(allow_recompile=True) def __call__(self, allow_recompile: bool = False) -> None: default_cell_variant = cell_variant() @@ -229,7 +234,22 @@ class LoadShaderPrograms: }.items(): vert, frag = glsl_shaders(name) compile_program(prog, (vert,), (frag,), metadata[name], allow_recompile) - init_cell_program() + compile_program(-1, (), (), {}) # initialize programs + self.compile_custom_shaders(allow_recompile) + + def compile_custom_shaders(self, allow_recompile: bool = False) -> None: + self.custom_shaders = {str(k): v for k, v in self.get_options().custom_shader.items()} + + def do(prog: int, slot: str) -> None: + shaders = self.custom_shaders.get(slot, ()) + if shaders: + vert, frag, metadata = build_custom_shader_pipeline_glsl(slot, shaders) + compile_program(prog, (vert,), (frag,), metadata, allow_recompile) + else: + compile_program(prog, (), (), {}, allow_recompile) + + do(AFTER_WINDOW_BG_PROGRAM, 'after-window-background') + compile_program(-2, (), (), {}) # initialize programs load_shader_programs = LoadShaderPrograms() @@ -1117,6 +1137,11 @@ def build_custom_shader_pipeline_glsl( return vf.read(), ff.read(), glsl_metadata_for_shader(metadata) +def clear_caches() -> None: + custom_shader.cache_clear() + build_custom_shader_pipeline_glsl.cache_clear() + + def test_slang_build() -> None: if shutil.which(slangc()[0]) is None: