From 9f377c5ccbee90d463f293bae8527b82249f04c1 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Tue, 13 Jun 2023 17:58:17 +0530 Subject: [PATCH] Cache shader source code loading --- kitty/borders.py | 6 +-- kitty/shaders.py | 47 ++++++++++++++----- kitty/window.py | 94 +++++++++++++++++++++----------------- kitty_tests/check_build.py | 4 +- 4 files changed, 91 insertions(+), 60 deletions(-) diff --git a/kitty/borders.py b/kitty/borders.py index 9c3d9578d..62992d8f6 100644 --- a/kitty/borders.py +++ b/kitty/borders.py @@ -4,8 +4,8 @@ from enum import IntFlag from typing import Iterable, NamedTuple, Sequence -from .fast_data_types import BORDERS_PROGRAM, add_borders_rect, compile_program, get_options, init_borders_program, os_window_has_background_image -from .shaders import load_shaders +from .fast_data_types import BORDERS_PROGRAM, add_borders_rect, get_options, init_borders_program, os_window_has_background_image +from .shaders import program_for from .typing import LayoutType from .window_list import WindowGroup, WindowList @@ -61,7 +61,7 @@ def draw_edges(os_window_id: int, tab_id: int, colors: Sequence[int], wg: Window def load_borders_program() -> None: - compile_program(BORDERS_PROGRAM, *load_shaders('border')) + program_for('border').compile(BORDERS_PROGRAM) init_borders_program() diff --git a/kitty/shaders.py b/kitty/shaders.py index 65d552841..f5eab8df5 100644 --- a/kitty/shaders.py +++ b/kitty/shaders.py @@ -2,33 +2,56 @@ # License: GPLv3 Copyright: 2023, Kovid Goyal import re -from typing import Iterator, Tuple +from functools import lru_cache +from typing import Callable, Iterator, Optional from .constants import read_kitty_resource -from .fast_data_types import GLSL_VERSION +from .fast_data_types import GLSL_VERSION, compile_program -def load_shaders(name: str, vertex_name: str = '', fragment_name: str = '') -> Tuple[Tuple[str, ...], Tuple[str, ...]]: - pat = re.compile(r'^#pragma\s+kitty_include_shader\s+<(.+?)>', re.MULTILINE) +def identity(x: str) -> str: + return x - def load_sources(name: str, level: int = 0) -> Iterator[str]: + +class Program: + + include_pat: Optional['re.Pattern[str]'] = None + + def __init__(self, name: str, vertex_name: str = '', fragment_name: str = '') -> None: + self.name = name + if Program.include_pat is None: + Program.include_pat = re.compile(r'^#pragma\s+kitty_include_shader\s+<(.+?)>', re.MULTILINE) + self.vertex_name = vertex_name or f'{name}_vertex.glsl' + self.fragment_name = fragment_name or f'{name}_fragment.glsl' + self.original_vertex_sources = tuple(self._load_sources(self.vertex_name)) + self.original_fragment_sources = tuple(self._load_sources(self.fragment_name)) + self.vertex_sources = self.original_vertex_sources + self.fragment_sources = self.original_fragment_sources + + def _load_sources(self, name: str, level: int = 0) -> Iterator[str]: if level == 0: yield f'#version {GLSL_VERSION}\n' src = read_kitty_resource(name).decode('utf-8') pos = 0 - for m in pat.finditer(src): + assert Program.include_pat is not None + for m in Program.include_pat.finditer(src): prefix = src[pos:m.start()] if prefix: yield prefix iname = m.group(1) - yield from load_sources(iname, level+1) + yield from self._load_sources(iname, level+1) pos = m.start() if pos < len(src): yield src[pos:] - def load(which: str, lname: str = '') -> Tuple[str, ...]: - lname = lname or name - main = f'{lname}_{which}.glsl' - return tuple(load_sources(main)) + def apply_to_sources(self, vertex: Callable[[str], str] = identity, frag: Callable[[str], str] = identity) -> None: + self.vertex_sources = self.original_vertex_sources if vertex is identity else tuple(map(vertex, self.original_vertex_sources)) + self.fragment_sources = self.original_fragment_sources if frag is identity else tuple(map(frag, self.original_fragment_sources)) - return load('vertex', vertex_name), load('fragment', fragment_name) + def compile(self, program_id: int, allow_recompile: bool = False) -> None: + compile_program(program_id, self.vertex_sources, self.fragment_sources, allow_recompile) + + +@lru_cache(maxsize=64) +def program_for(name: str) -> Program: + return Program(name) diff --git a/kitty/window.py b/kitty/window.py index e062bd4af..6601fd48a 100644 --- a/kitty/window.py +++ b/kitty/window.py @@ -78,7 +78,6 @@ from .fast_data_types import ( cell_size_for_window, click_mouse_cmd_output, click_mouse_url, - compile_program, current_focused_os_window_id, encode_key_for_tty, get_boss, @@ -107,7 +106,7 @@ from .notify import ( ) from .options.types import Options from .rgb import to_color -from .shaders import load_shaders +from .shaders import program_for from .terminfo import get_capabilities from .types import MouseEvent, OverlayType, WindowGeometry, ac, run_once from .typing import BossType, ChildType, EdgeLiteral, TabType, TypedDict @@ -392,54 +391,63 @@ class LoadShaderPrograms: opts = get_options() 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) - vs, fs = load_shaders('cell') + program_for('blit').compile(BLIT_PROGRAM, allow_recompile) + cell = program_for('cell') + + def resolve_cell_vertex_defines(which: str, v: str) -> str: + v = 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: + v = v.replace('#define NOT_TRANSPARENT', '#define TRANSPARENT') + return v + + def resolve_cell_fragment_defines(which: str, f: str) -> str: + f = f.replace('{WHICH_PROGRAM}', which) + if self.text_fg_override_threshold != 0.: + f = f.replace('#define NO_FG_OVERRIDE', f'#define FG_OVERRIDE {self.text_fg_override_threshold}') + if self.text_old_gamma: + f = f.replace('#define TEXT_NEW_GAMMA', '#define TEXT_OLD_GAMMA') + if semi_transparent: + f = f.replace('#define NOT_TRANSPARENT', '#define TRANSPARENT') + return f for which, p in { - 'SIMPLE': CELL_PROGRAM, - 'BACKGROUND': CELL_BG_PROGRAM, - 'SPECIAL': CELL_SPECIAL_PROGRAM, - 'FOREGROUND': CELL_FG_PROGRAM, + 'SIMPLE': CELL_PROGRAM, + 'BACKGROUND': CELL_BG_PROGRAM, + 'SPECIAL': CELL_SPECIAL_PROGRAM, + 'FOREGROUND': CELL_FG_PROGRAM, }.items(): - 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) + cell.apply_to_sources( + vertex=partial(resolve_cell_vertex_defines, which), + frag=partial(resolve_cell_fragment_defines, which), + ) + cell.compile(p, allow_recompile) + + graphics = program_for('graphics') + + def resolve_graphics_fragment_defines(which: str, f: str) -> str: + return f.replace('ALPHA_TYPE', which) - vs, fs = load_shaders('graphics') for which, p in { - 'SIMPLE': GRAPHICS_PROGRAM, - 'PREMULT': GRAPHICS_PREMULT_PROGRAM, - 'ALPHA_MASK': GRAPHICS_ALPHA_MASK_PROGRAM, + 'SIMPLE': GRAPHICS_PROGRAM, + 'PREMULT': GRAPHICS_PREMULT_PROGRAM, + 'ALPHA_MASK': GRAPHICS_ALPHA_MASK_PROGRAM, }.items(): - ff = f.replace('ALPHA_TYPE', which) - compile_program(p, vs, tuple(f.replace('ALPHA_TYPE', which) for f in fs), allow_recompile) + graphics.apply_to_sources(frag=partial(resolve_cell_fragment_defines, which)) + graphics.compile(p, allow_recompile) - compile_program(BGIMAGE_PROGRAM, *load_shaders('bgimage'), allow_recompile) - compile_program(TINT_PROGRAM, *load_shaders('tint'), allow_recompile) + program_for('bgimage').compile(BGIMAGE_PROGRAM, allow_recompile) + program_for('tint').compile(TINT_PROGRAM) init_cell_program() diff --git a/kitty_tests/check_build.py b/kitty_tests/check_build.py index bec256ab5..543e742f4 100644 --- a/kitty_tests/check_build.py +++ b/kitty_tests/check_build.py @@ -31,9 +31,9 @@ class TestBuild(BaseTest): del fdt, rsync def test_loading_shaders(self) -> None: - from kitty.utils import load_shaders + from kitty.shaders import Program for name in 'cell border bgimage tint blit graphics'.split(): - load_shaders(name) + Program(name) def test_glfw_modules(self) -> None: from kitty.constants import glfw_path, is_macos