From 444ec2484d0ab8cec7a3f1b32c6ae7601c8a9805 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Tue, 13 Jun 2023 12:55:00 +0530 Subject: [PATCH] Infrastructure for loading shaders from multiple source files Can eventually be used to share source code (functions) across many shaders --- kitty/fast_data_types.pyi | 2 +- kitty/shaders.c | 22 ++++++++++---- kitty/utils.py | 19 +++++++++++-- kitty/window.py | 60 +++++++++++++++++++++------------------ 4 files changed, 66 insertions(+), 37 deletions(-) diff --git a/kitty/fast_data_types.pyi b/kitty/fast_data_types.pyi index ece66bee4..4b50565b4 100644 --- a/kitty/fast_data_types.pyi +++ b/kitty/fast_data_types.pyi @@ -458,7 +458,7 @@ def add_window(os_window_id: int, tab_id: int, title: str) -> int: def compile_program( - which: int, vertex_shader: str, fragment_shader: str, allow_recompile: bool = False + which: int, vertex_shaders: Tuple[str, ...], fragment_shaders: Tuple[str, ...], allow_recompile: bool = False ) -> int: pass diff --git a/kitty/shaders.c b/kitty/shaders.c index f4f9d1ff8..f0611b807 100644 --- a/kitty/shaders.c +++ b/kitty/shaders.c @@ -1070,12 +1070,25 @@ draw_borders(ssize_t vao_idx, unsigned int num_border_rects, BorderRect *rect_bu // }}} // Python API {{{ + +static bool +attach_shaders(PyObject *sources, GLuint program_id, GLenum shader_type) { + for (Py_ssize_t i = 0; i < PyTuple_GET_SIZE(sources); i++) { + PyObject *temp = PyTuple_GET_ITEM(sources, i); + if (!PyUnicode_Check(temp)) { PyErr_SetString(PyExc_TypeError, "shaders must be strings"); return false; } + const char *vertex_shader = PyUnicode_AsUTF8(temp); + GLuint shader_id = compile_shader(shader_type, vertex_shader); + glAttachShader(program_id, shader_id); + } + return true; +} + static PyObject* compile_program(PyObject UNUSED *self, PyObject *args) { - const char *vertex_shader, *fragment_shader; + PyObject *vertex_shaders, *fragment_shaders; int which, allow_recompile = 0; GLuint vertex_shader_id = 0, fragment_shader_id = 0; - if (!PyArg_ParseTuple(args, "iss|p", &which, &vertex_shader, &fragment_shader, &allow_recompile)) return NULL; + if (!PyArg_ParseTuple(args, "iO!O!|p", &which, &PyTuple_Type, &vertex_shaders, &PyTuple_Type, &fragment_shaders, &allow_recompile)) return NULL; if (which < 0 || which >= NUM_PROGRAMS) { PyErr_Format(PyExc_ValueError, "Unknown program: %d", which); return NULL; } Program *program = program_ptr(which); if (program->id != 0) { @@ -1083,9 +1096,8 @@ compile_program(PyObject UNUSED *self, PyObject *args) { else { PyErr_SetString(PyExc_ValueError, "program already compiled"); return NULL; } } program->id = glCreateProgram(); - vertex_shader_id = compile_shader(GL_VERTEX_SHADER, vertex_shader); - fragment_shader_id = compile_shader(GL_FRAGMENT_SHADER, fragment_shader); - glAttachShader(program->id, vertex_shader_id); + if (!attach_shaders(vertex_shaders, program->id, GL_VERTEX_SHADER)) return NULL; + if (!attach_shaders(fragment_shaders, program->id, GL_FRAGMENT_SHADER)) return NULL; glAttachShader(program->id, fragment_shader_id); glLinkProgram(program->id); GLint ret = GL_FALSE; diff --git a/kitty/utils.py b/kitty/utils.py index 326fe356c..e70d92471 100644 --- a/kitty/utils.py +++ b/kitty/utils.py @@ -104,12 +104,25 @@ def platform_window_id(os_window_id: int) -> Optional[int]: return None -def load_shaders(name: str, vertex_name: str = '', fragment_name: str = '') -> Tuple[str, str]: +def load_shaders(name: str, vertex_name: str = '', fragment_name: str = '') -> Tuple[Tuple[str, ...], Tuple[str, ...]]: from .fast_data_types import GLSL_VERSION + pat = re.compile(r'^#pragma kitty_include_shader <(.+?)>', re.MULTILINE) - def load(which: str, lname: str = '') -> str: + def load_source(name: str) -> str: + return read_kitty_resource(name).decode('utf-8').replace('GLSL_VERSION', str(GLSL_VERSION), 1) + + def load_sources(name: str) -> Tuple[str, ...]: + src = load_source(name) + ans: Tuple[str, ...] = src, + for m in pat.finditer(src): + iname = m.group(1) + ans += load_sources(iname) + return ans + + def load(which: str, lname: str = '') -> Tuple[str, ...]: lname = lname or name - return read_kitty_resource(f'{lname}_{which}.glsl').decode('utf-8').replace('GLSL_VERSION', str(GLSL_VERSION), 1) + main = f'{lname}_{which}.glsl' + return load_sources(main) return load('vertex', vertex_name), load('fragment', fragment_name) diff --git a/kitty/window.py b/kitty/window.py index 9de56ed4e..9c943efba 100644 --- a/kitty/window.py +++ b/kitty/window.py @@ -393,7 +393,7 @@ class LoadShaderPrograms: self.text_old_gamma = opts.text_composition_strategy == 'legacy' self.text_fg_override_threshold = max(0, min(opts.text_fg_override_threshold, 100)) * 0.01 compile_program(BLIT_PROGRAM, *load_shaders('blit'), allow_recompile) - v, f = load_shaders('cell') + vs, fs = load_shaders('cell') for which, p in { 'SIMPLE': CELL_PROGRAM, @@ -401,41 +401,45 @@ class LoadShaderPrograms: 'SPECIAL': CELL_SPECIAL_PROGRAM, 'FOREGROUND': CELL_FG_PROGRAM, }.items(): - ff = f.replace('{WHICH_PROGRAM}', which) - vv = multi_replace( - v, - WHICH_PROGRAM=which, - REVERSE_SHIFT=REVERSE, - STRIKE_SHIFT=STRIKETHROUGH, - DIM_SHIFT=DIM, - DECORATION_SHIFT=DECORATION, - MARK_SHIFT=MARK, - MARK_MASK=MARK_MASK, - DECORATION_MASK=DECORATION_MASK, - STRIKE_SPRITE_INDEX=NUM_UNDERLINE_STYLES + 1, - ) - if self.text_fg_override_threshold != 0.: - ff = ff.replace('#define NO_FG_OVERRIDE', f'#define FG_OVERRIDE {self.text_fg_override_threshold}') - if self.text_old_gamma: - ff = ff.replace('#define TEXT_NEW_GAMMA', '#define TEXT_OLD_GAMMA') - if semi_transparent: - vv = vv.replace('#define NOT_TRANSPARENT', '#define TRANSPARENT') - ff = ff.replace('#define NOT_TRANSPARENT', '#define TRANSPARENT') - compile_program(p, vv, ff, allow_recompile) + vvs, ffs = [], [] + for v in vs: + vv = multi_replace( + v, + WHICH_PROGRAM=which, + REVERSE_SHIFT=REVERSE, + STRIKE_SHIFT=STRIKETHROUGH, + DIM_SHIFT=DIM, + DECORATION_SHIFT=DECORATION, + MARK_SHIFT=MARK, + MARK_MASK=MARK_MASK, + DECORATION_MASK=DECORATION_MASK, + STRIKE_SPRITE_INDEX=NUM_UNDERLINE_STYLES + 1, + ) + if semi_transparent: + vv = vv.replace('#define NOT_TRANSPARENT', '#define TRANSPARENT') + vvs.append(vv) + for f in fs: + ff = f.replace('{WHICH_PROGRAM}', which) + if self.text_fg_override_threshold != 0.: + ff = ff.replace('#define NO_FG_OVERRIDE', f'#define FG_OVERRIDE {self.text_fg_override_threshold}') + if self.text_old_gamma: + ff = ff.replace('#define TEXT_NEW_GAMMA', '#define TEXT_OLD_GAMMA') + if semi_transparent: + ff = ff.replace('#define NOT_TRANSPARENT', '#define TRANSPARENT') + ffs.append(ff) + compile_program(p, tuple(vvs), tuple(ffs), allow_recompile) - v, f = load_shaders('graphics') + vs, fs = load_shaders('graphics') for which, p in { 'SIMPLE': GRAPHICS_PROGRAM, 'PREMULT': GRAPHICS_PREMULT_PROGRAM, 'ALPHA_MASK': GRAPHICS_ALPHA_MASK_PROGRAM, }.items(): ff = f.replace('ALPHA_TYPE', which) - compile_program(p, v, ff, allow_recompile) + compile_program(p, vs, tuple(f.replace('ALPHA_TYPE', which) for f in fs), allow_recompile) - v, f = load_shaders('bgimage') - compile_program(BGIMAGE_PROGRAM, v, f, allow_recompile) - v, f = load_shaders('tint') - compile_program(TINT_PROGRAM, v, f, allow_recompile) + compile_program(BGIMAGE_PROGRAM, *load_shaders('bgimage'), allow_recompile) + compile_program(TINT_PROGRAM, *load_shaders('tint'), allow_recompile) init_cell_program()