Custom shaders: Redraw static effects only on demand

This commit is contained in:
白鸽 2026-09-14 22:41:26 +08:00
parent cf33820973
commit 7b41e33399
5 changed files with 18 additions and 6 deletions

View file

@ -205,7 +205,8 @@ Detailed list of changes
- Support for :doc:`/custom-shaders` for adding various graphical effects (:iss:`10344`)
- Custom shaders: Allow tinting inactive windows toward a configurable color,
preserving the tab bar and active window padding.
preserving the tab bar and active window padding. Support static effects
without periodic redraws.
- Various throughput performance improvements for a 15-35% real world improvement depending on workload

View file

@ -84,6 +84,7 @@ following as :file:`shaders/dim-gray.pipeline` in your kitty config directory an
set ``custom_shaders dim-gray`` and ``inactive_text_alpha 1`` in :file:`kitty.conf`::
startgroup
animation_step 0
var float HIGHLIGHT_INTENSITY = 0
var float BORDER_WIDTH = 0
var float INACTIVE_DIM = 0.74
@ -100,7 +101,8 @@ sRGB ``#808080``. The default target is black, preserving the existing dimming
effect. The two optional flags preserve the tab bar and include the active
window's padding in its unchanged area. Explicit cell backgrounds and images
are blended together with text, while transparency and colors reported to
terminal applications are preserved.
terminal applications are preserved. ``animation_step 0`` makes this static
effect redraw only when content or shader events change.
Anatomy of a custom shader
@ -269,7 +271,9 @@ Directives inside a group:
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`.
:opt:`repaint_delay`. Set this to ``0`` for static effects that should
redraw only when content or shader events change, without periodic frames.
Animation stop events and duration limits still apply.
``attach``
Mark this group as *attached* to the preceding group. An attached group

View file

@ -957,13 +957,18 @@ prepare_to_render_os_window(
events |= (1u << SHADER_ANIM_EVENT_TAB_CHANGE) | (1u << SHADER_ANIM_EVENT_WINDOW_FOCUS_IN) | (1u << SHADER_ANIM_EVENT_WINDOW_FOCUS_OUT);
if (*active_window_id && *active_window_id != os_window->last_active_window_id)
events |= (1u << SHADER_ANIM_EVENT_WINDOW_FOCUS_IN) | (1u << SHADER_ANIM_EVENT_WINDOW_FOCUS_OUT);
const bool was_active = os_window->has_active_custom_shaders;
const bool was_animating = os_window->shader_anim_min_step < MONOTONIC_T_MAX;
const bool animation_ended = now >= os_window->shader_anim_next_end_at;
monotonic_t min_step = update_custom_shader_animations(events, now, os_window);
os_window->shader_anim_event_registry = 0;
if (os_window->has_active_custom_shaders) {
os_window->needs_layers = true;
needs_render = true;
if (events || min_step < MONOTONIC_T_MAX) needs_render = true;
}
if (was_active != os_window->has_active_custom_shaders || was_animating || animation_ended) needs_render = true;
if (min_step < MONOTONIC_T_MAX) set_maximum_wait(min_step);
if (os_window->shader_anim_next_end_at < MONOTONIC_T_MAX) set_maximum_wait(os_window->shader_anim_next_end_at - now);
}
return needs_render || was_previously_rendered_with_layers != os_window->needs_layers;
}

View file

@ -2998,7 +2998,7 @@ transfer_pipeline_to_struct(PyObject *pg, CustomShaderPipeline *p) {
PyObject *astep = PyDict_GetItemString(g, "animation_step");
cg->animation_step = (astep && PyLong_Check(astep)) ? (monotonic_t)PyLong_AsLong(astep) : ANIMATION_SAMPLE_WAIT;
cg->animation_step = MAX(cg->animation_step, OPT(repaint_delay));
cg->animation_step = cg->animation_step == 0 ? MONOTONIC_T_MAX : MAX(cg->animation_step, OPT(repaint_delay));
PyObject *ae_events = PyDict_GetItemString(g, "animation_end_events");
if (is_seq(ae_events)) cg->animation_end_events = parse_anim_event_sequence(ae_events);

View file

@ -40,6 +40,7 @@ class TestSlang(BaseTest):
pipeline = parse_pipeline_definition(
"""
startgroup
animation_step 0
var float HIGHLIGHT_INTENSITY = 0
var float BORDER_WIDTH = 0
var float INACTIVE_DIM = 0.74
@ -54,11 +55,12 @@ class TestSlang(BaseTest):
with tempfile.TemporaryDirectory() as cache_dir:
clear_caches()
try:
vertex, fragment, _ = build_custom_shader_pipeline_glsl(pipeline, cache_dir=cache_dir)
vertex, fragment, metadata = build_custom_shader_pipeline_glsl(pipeline, cache_dir=cache_dir)
finally:
clear_caches()
self.assertTrue(vertex)
self.assertTrue(fragment)
self.ae(metadata['pipeline']['groups'][0]['animation_step'], 0)
def test_slang_parser(self):
def check(src: str, expected: SlangFile) -> None: