From 88e18a985dccce91c6b345be98cad6a5121dff75 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Thu, 6 Aug 2026 21:37:46 +0530 Subject: [PATCH] Dont calculate animation easing values multiple times for indentical easing curves --- kitty/animation.c | 23 +++++++++++++++++++++++ kitty/animation.h | 1 + kitty/shaders.c | 44 ++++++++++++++++++++++++++++++++++++++------ 3 files changed, 62 insertions(+), 6 deletions(-) diff --git a/kitty/animation.c b/kitty/animation.c index 3b29bbf8c..b59babc10 100644 --- a/kitty/animation.c +++ b/kitty/animation.c @@ -182,6 +182,29 @@ identity_easing_curve(void *p_ UNUSED, double t, monotonic_t duration UNUSED) { return t; } +bool +animations_equal(const Animation *a, const Animation *b) { + if (a == b) return true; + if (!a || !b) return false; + if (a->count != b->count) return false; + for (size_t i = 0; i < a->count; i++) { + const animation_function *fa = &a->functions[i]; + const animation_function *fb = &b->functions[i]; + if (fa->curve != fb->curve || fa->y_at_start != fb->y_at_start || fa->y_size != fb->y_size) return false; + if (fa->curve == cubic_bezier_easing_curve) { + if (memcmp(fa->params, fb->params, sizeof(BezierParameters)) != 0) return false; + } else if (fa->curve == linear_easing_curve) { + const LinearParameters *pa = fa->params, *pb = fb->params; + if (pa->count != pb->count) return false; + if (memcmp(pa->buf, pb->buf, 2 * pa->count * sizeof(double)) != 0) return false; + } else if (fa->curve == step_easing_curve) { + if (memcmp(fa->params, fb->params, sizeof(StepsParameters)) != 0) return false; + } + // identity_easing_curve has no params; function-pointer equality above is sufficient + } + return true; +} + double apply_easing_curve(const Animation *a, double val, monotonic_t duration) { val = unit_value(val); diff --git a/kitty/animation.h b/kitty/animation.h index 0eaefde22..eb109a3f9 100644 --- a/kitty/animation.h +++ b/kitty/animation.h @@ -26,3 +26,4 @@ void add_cubic_bezier_animation(Animation *a, double y_at_start, double y_at_end void add_linear_animation(Animation *a, double y_at_start, double y_at_end, size_t count, const double *x, const double *y); void add_steps_animation(Animation *a, double y_at_start, double y_at_end, size_t count, EasingStep step); Animation *free_animation(Animation *a); +bool animations_equal(const Animation *a, const Animation *b); diff --git a/kitty/shaders.c b/kitty/shaders.c index c4940177a..088c4dd8f 100644 --- a/kitty/shaders.c +++ b/kitty/shaders.c @@ -401,6 +401,7 @@ typedef struct CustomShaderGroup { monotonic_t animation_end_duration; // nanoseconds; 0 = no time limit monotonic_t animation_step; // nanoseconds between animation samples Animation *animation_curve; // parsed easing curve; NULL = no animation + bool animation_curve_is_shared; // true if animation_curve is owned by an earlier group } CustomShaderGroup; typedef struct CustomShaderPipeline { @@ -2364,6 +2365,13 @@ run_custom_end_shader(OSWindow *os_window, float sx, float sy, monotonic_t now) bool backbuffer_is_main = true; bool last_group_rendered = false; + struct AnimCacheEntry { + Animation *curve; + monotonic_t started_at, duration; + float progress; + } anim_cache[MAX_CUSTOM_SHADER_GROUPS]; + size_t num_anim_cached = 0; + for (unsigned g = 0; g < num_groups; g++) { const CustomShaderGroup *cg = &custom_shaders.end.groups[g]; const bool is_last = (g == num_groups - 1); @@ -2436,11 +2444,24 @@ run_custom_end_shader(OSWindow *os_window, float sx, float sy, monotonic_t now) float anim_progress = 0.0f; if (cg->animation_start_events != 0 && cg->animation_end_duration > 0 && os_window->shader_group_anim[g].active) { - double elapsed = (double)(now - os_window->shader_group_anim[g].started_at); - double t = elapsed / (double)cg->animation_end_duration; - if (t < 0.0) t = 0.0; - if (t > 1.0) t = 1.0; - anim_progress = (float)(cg->animation_curve ? apply_easing_curve(cg->animation_curve, t, cg->animation_end_duration) : t); + monotonic_t started_at = os_window->shader_group_anim[g].started_at; + bool cached = false; + for (size_t c = 0; c < num_anim_cached; c++) { + if (anim_cache[c].curve == cg->animation_curve && anim_cache[c].started_at == started_at && + anim_cache[c].duration == cg->animation_end_duration) { + anim_progress = anim_cache[c].progress; + cached = true; + break; + } + } + if (!cached) { + double elapsed = (double)(now - started_at); + double t = elapsed / (double)cg->animation_end_duration; + if (t < 0.0) t = 0.0; + if (t > 1.0) t = 1.0; + anim_progress = (float)(cg->animation_curve ? apply_easing_curve(cg->animation_curve, t, cg->animation_end_duration) : t); + anim_cache[num_anim_cached++] = (struct AnimCacheEntry){cg->animation_curve, started_at, cg->animation_end_duration, anim_progress}; + } } glUniform1i(group_loc, (GLint)g); glUniform4f(viewport_loc, vp_x, vp_y, vp_w, vp_h); @@ -2618,7 +2639,10 @@ pygpu_driver_version_string(PyObject *self UNUSED, PyObject *args UNUSED) { static void free_custom_shader_pipeline(CustomShaderPipeline *p) { - for (size_t i = 0; i < p->num_groups; i++) p->groups[i].animation_curve = free_animation(p->groups[i].animation_curve); + for (size_t i = 0; i < p->num_groups; i++) { + if (p->groups[i].animation_curve_is_shared) p->groups[i].animation_curve = NULL; + else p->groups[i].animation_curve = free_animation(p->groups[i].animation_curve); + } } static NamedTexture @@ -2702,6 +2726,14 @@ transfer_pipeline_to_struct(PyObject *pg, CustomShaderPipeline *p) { PyObject *acurve = PyDict_GetItemString(g, "animation_curve"); if (acurve && PyObject_IsTrue(acurve)) { if ((cg->animation_curve = alloc_animation()) != NULL) add_easing_function(cg->animation_curve, acurve, 0.0, 1.0); + for (Py_ssize_t j = 0; j < i; j++) { + if (animations_equal(p->groups[j].animation_curve, cg->animation_curve)) { + free_animation(cg->animation_curve); + cg->animation_curve = p->groups[j].animation_curve; + cg->animation_curve_is_shared = true; + break; + } + } } PyObject *astep = PyDict_GetItemString(g, "animation_step");