mirror of
https://github.com/kovidgoyal/kitty.git
synced 2026-10-21 20:52:19 +00:00
Merge branch 'feature/inactive-shader-tint' of https://github.com/xiaobai050/kitty
This commit is contained in:
commit
d20a0acb3a
8 changed files with 119 additions and 5 deletions
|
|
@ -204,6 +204,10 @@ 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. Support static effects
|
||||
without periodic redraws, and fix re-enabling a previously disabled shader.
|
||||
|
||||
- Various throughput performance improvements for a 15-35% real world improvement depending on workload
|
||||
|
||||
- Add :opt:`window_border_radius` for rounded window borders (:pull:`10421`)
|
||||
|
|
|
|||
|
|
@ -79,6 +79,31 @@ mouse shader. In the first group we have the crt shader and its color has been
|
|||
customised. The second group defines the spotlight shader and specifies exactly
|
||||
when the spotlight animation should start and stop.
|
||||
|
||||
For example, to blend inactive windows toward gray instead of black, save the
|
||||
following as :file:`shaders/dim-gray.pipeline` in your kitty config directory and
|
||||
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
|
||||
var float3 INACTIVE_DIM_COLOR = float3(0.2158605)
|
||||
var bool DIM_CENTRAL_AREA_ONLY = true
|
||||
var bool INCLUDE_WINDOW_PADDING = true
|
||||
shaders focus-highlight
|
||||
endgroup
|
||||
|
||||
This uses the same shader as ``dim-inactive-windows``. ``INACTIVE_DIM`` is the
|
||||
fraction of the original color retained: ``0.74`` blends in 26% of the target
|
||||
color. ``INACTIVE_DIM_COLOR`` is in linear RGB; ``0.2158605`` corresponds to
|
||||
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. ``animation_step 0`` makes this static
|
||||
effect redraw only when content or shader events change.
|
||||
|
||||
|
||||
Anatomy of a custom shader
|
||||
----------------------------
|
||||
|
|
@ -246,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
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2433,6 +2433,7 @@ run_custom_end_shader(OSWindow *os_window, float sx, float sy, monotonic_t now)
|
|||
float mouse_pos[4];
|
||||
float mouse_button_pressed[4];
|
||||
float active_window_geometry[4];
|
||||
float active_window_padding[4];
|
||||
float bell_window_geometry[4];
|
||||
float central_area[4];
|
||||
float cursor_trail_corners_x[4];
|
||||
|
|
@ -2475,6 +2476,13 @@ run_custom_end_shader(OSWindow *os_window, float sx, float sy, monotonic_t now)
|
|||
if (t->num_windows) {
|
||||
Window *w = t->windows + t->active_window;
|
||||
active_win_geom = w->render_data.geometry;
|
||||
if (os_window->viewport_width && os_window->viewport_height) {
|
||||
const float width = (float)os_window->viewport_width, height = (float)os_window->viewport_height;
|
||||
d->active_window_padding[0] = (float)(w->padding.left + w->size_mismatch_padding.left) / width;
|
||||
d->active_window_padding[1] = (float)(w->padding.bottom + w->size_mismatch_padding.bottom) / height;
|
||||
d->active_window_padding[2] = (float)(w->padding.right + w->size_mismatch_padding.right) / width;
|
||||
d->active_window_padding[3] = (float)(w->padding.top + w->size_mismatch_padding.top) / height;
|
||||
}
|
||||
Screen *s = w->render_data.screen;
|
||||
if (s) {
|
||||
active_bg = colorprofile_to_color(s->color_profile, s->color_profile->overridden.default_bg, s->color_profile->configured.default_bg).rgb;
|
||||
|
|
@ -2990,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);
|
||||
|
|
|
|||
|
|
@ -16,14 +16,28 @@ static const float HIGHLIGHT_INTENSITY = 0.30;
|
|||
static const float BORDER_WIDTH = 0.04;
|
||||
// Peak additive brightness multiplier at the window border edge.
|
||||
static const float BORDER_GLOW = 0.55;
|
||||
// Dim factor applied to pixels outside the active window (1.0 = unchanged, 0.0 = black).
|
||||
// Fraction of the original color retained outside the active window (1.0 = unchanged, 0.0 = blend target).
|
||||
static const float INACTIVE_DIM = 0.3;
|
||||
// Blend target in linear RGB; black preserves the original dimming behavior.
|
||||
static const float3 INACTIVE_DIM_COLOR = float3(0.0);
|
||||
// Keep the tab bar outside the effect when requested by a pipeline.
|
||||
static const bool DIM_CENTRAL_AREA_ONLY = false;
|
||||
// Include the active window's padding in its unchanged/highlighted area.
|
||||
static const bool INCLUDE_WINDOW_PADDING = false;
|
||||
|
||||
public float4
|
||||
fragment_main(float4 color, KittyTextures t, KittyCustomShaderData d) {
|
||||
if (DIM_CENTRAL_AREA_ONLY) {
|
||||
float4 area = d.central_area;
|
||||
if (any(t.pos < area.xy) || any(t.pos > area.xy + area.zw)) return color;
|
||||
}
|
||||
float4 g = d.active_window_geometry;
|
||||
// Pass through unchanged if no active window geometry is known.
|
||||
if (g.z <= 0.0 || g.w <= 0.0) return color;
|
||||
if (INCLUDE_WINDOW_PADDING) {
|
||||
g.xy -= d.active_window_padding.xy;
|
||||
g.zw += d.active_window_padding.xy + d.active_window_padding.zw;
|
||||
}
|
||||
|
||||
float2 uv = t.pos;
|
||||
float2 winMin = g.xy;
|
||||
|
|
@ -48,5 +62,5 @@ fragment_main(float4 color, KittyTextures t, KittyCustomShaderData d) {
|
|||
}
|
||||
// Slightly dim inactive areas to draw the eye to the focused window.
|
||||
float dim = lerp(1.0, INACTIVE_DIM, strength);
|
||||
return float4(color.rgb * dim, color.a);
|
||||
return float4(lerp(INACTIVE_DIM_COLOR, color.rgb, dim), color.a);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -22,6 +22,9 @@ public struct KittyCustomShaderData {
|
|||
// .xy = lower-left corner of the window, .zw = width and height.
|
||||
// All components are zero when no active window was rendered this frame.
|
||||
public float4 active_window_geometry;
|
||||
// Effective padding of the active window, including cell-size compensation.
|
||||
// .xy = left/bottom, .zw = right/top, in viewport UV units.
|
||||
public float4 active_window_padding;
|
||||
// Geometry of the most recently belled window in UV co-ordinates (origin bottom-left).
|
||||
// .xy = lower-left corner of the window, .zw = width and height.
|
||||
// If the belled window is not currently visible in the active tab/layout, equals central_area.
|
||||
|
|
|
|||
|
|
@ -274,6 +274,7 @@ class LoadShaderPrograms:
|
|||
slot_pipelines = pmap.get(slot)
|
||||
if not slot_pipelines:
|
||||
compile_program(prog, (), (), {}, allow_recompile)
|
||||
self.last_built_custom_shaders.pop(prog, None)
|
||||
else:
|
||||
try:
|
||||
pipeline = merge_pipelines(slot_pipelines)
|
||||
|
|
@ -283,6 +284,7 @@ class LoadShaderPrograms:
|
|||
except Exception as e:
|
||||
log_error(f'Failed to build custom shader for slot {slot} with error: {e}')
|
||||
compile_program(prog, (), (), {}, allow_recompile)
|
||||
self.last_built_custom_shaders.pop(prog, None)
|
||||
else:
|
||||
try:
|
||||
if self.last_built_custom_shaders.get(prog) != (vert, frag, metadata):
|
||||
|
|
@ -291,6 +293,7 @@ class LoadShaderPrograms:
|
|||
except Exception as e:
|
||||
log_error(f'Failed to load custom shader for slot {slot} with error: {e}')
|
||||
compile_program(prog, (), (), {}, allow_recompile)
|
||||
self.last_built_custom_shaders.pop(prog, None)
|
||||
|
||||
do(CUSTOM_END_PROGRAM, 'end')
|
||||
compile_program(-2, (), (), {}) # initialize programs
|
||||
|
|
|
|||
|
|
@ -8,10 +8,12 @@ import os
|
|||
import shutil
|
||||
import subprocess
|
||||
import tempfile
|
||||
from unittest.mock import patch
|
||||
|
||||
from kitty.constants import slangc
|
||||
from kitty.shaders.slang import (
|
||||
EntryPoint,
|
||||
LoadShaderPrograms,
|
||||
SlangFile,
|
||||
Stage,
|
||||
build_custom_shader_pipeline_glsl,
|
||||
|
|
@ -33,6 +35,54 @@ _SUPPORT_SHADER_NAMES = frozenset(('types', 'pipeline'))
|
|||
|
||||
|
||||
class TestSlang(BaseTest):
|
||||
def test_inactive_window_tint_pipeline(self):
|
||||
if not shutil.which(slangc()[0]):
|
||||
self.skipTest(f'slangc ({slangc()[0]}) not found in PATH')
|
||||
|
||||
pipeline = parse_pipeline_definition(
|
||||
"""
|
||||
startgroup
|
||||
animation_step 0
|
||||
var float HIGHLIGHT_INTENSITY = 0
|
||||
var float BORDER_WIDTH = 0
|
||||
var float INACTIVE_DIM = 0.74
|
||||
var float3 INACTIVE_DIM_COLOR = float3(0.2158605)
|
||||
var bool DIM_CENTRAL_AREA_ONLY = true
|
||||
var bool INCLUDE_WINDOW_PADDING = true
|
||||
shaders focus-highlight
|
||||
endgroup
|
||||
""".splitlines(),
|
||||
'dim-gray',
|
||||
)
|
||||
with tempfile.TemporaryDirectory() as cache_dir:
|
||||
clear_caches()
|
||||
try:
|
||||
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_custom_shader_reenable(self):
|
||||
from kitty.fast_data_types import CUSTOM_END_PROGRAM
|
||||
from kitty.options.types import defaults
|
||||
|
||||
loader = LoadShaderPrograms()
|
||||
loader.last_built_custom_shaders = {}
|
||||
pipeline = parse_pipeline_definition(['startgroup', 'animation_step 0', 'shaders focus-highlight', 'endgroup'], 'test')
|
||||
self.ae(pipeline['groups'][0]['animation_step'], 0)
|
||||
with (
|
||||
patch('kitty.shaders.slang.parse_pipeline', return_value=pipeline),
|
||||
patch('kitty.shaders.slang.build_custom_shader_pipeline_glsl', return_value=('vertex', 'fragment', {})),
|
||||
patch('kitty.shaders.slang.compile_program') as compile_program,
|
||||
):
|
||||
for shaders in (('test',), (), ('test',)):
|
||||
loader.opts = defaults._replace(custom_shaders=shaders)
|
||||
loader.compile_custom_shaders(allow_recompile=True)
|
||||
operations = [c.args[1] for c in compile_program.call_args_list if c.args[0] == CUSTOM_END_PROGRAM]
|
||||
self.ae(operations, [('vertex',), (), ('vertex',)])
|
||||
|
||||
def test_slang_parser(self):
|
||||
def check(src: str, expected: SlangFile) -> None:
|
||||
actual = parse_slang_text(src)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue