feat: custom GLSL shader transitions for window open/close

This commit is contained in:
Ernesto Cruz 2026-07-03 03:49:58 +01:00
parent 65637b77a5
commit 8b4dd043ed
9 changed files with 970 additions and 8 deletions

View file

@ -1,4 +1,4 @@
{ {
"title": "Visuals", "title": "Visuals",
"pages": ["theming", "status-bar", "effects", "animations"] "pages": ["theming", "status-bar", "effects", "animations", "shaders"]
} }

121
docs/visuals/shaders.md Normal file
View file

@ -0,0 +1,121 @@
---
title: Shader Transitions
description: Write custom GLSL fragment shaders for window open/close animations.
---
mangowm can drive a window's open and close animation with a custom GLSL fragment shader instead of (or alongside) the built-in parametric fades and zooms. Each window is snapshotted to a texture once, then your shader runs every animation tick against that texture to produce the visible frame.
This is a small, focused feature. It only covers the open and close transitions (not move/tag/focus animations), and shader files are only discovered at startup and after a GPU reset. There is no hot-reload while the compositor is running.
## Quick start
1. Drop a `.frag` file in `~/.config/mango/shaders/`, e.g. `dissolve.frag`.
2. Reference it by filename (without the `.frag` extension) in your config:
```ini
effect_open=dissolve
effect_close=burn
```
3. Restart mangowm. Shader files are scanned once at startup and again after a GPU reset. Adding or editing a `.frag` file while the compositor is already running has no effect until the next restart.
## Writing a shader
A shader file is just the body of a GLSL ES 3.00 fragment shader, typically a single `void main() { ... }` function. The loader wraps your body with a fixed header before compiling it, equivalent to:
```glsl
#version 300 es
precision highp float;
uniform sampler2D u_texture;
uniform float u_progress;
uniform float u_time;
uniform vec2 u_size;
in vec2 v_texcoord;
out vec4 fragColor;
```
Do not redeclare any of these names in your file: `u_texture`, `u_progress`, `u_time`, `u_size`, `v_texcoord`, or `fragColor`. The header already declares them. Redeclaring any of them is a GLSL compile error and your shader will fail to load (see [Fail-safe behavior](#fail-safe-behavior)).
| Name | Type | Meaning |
| :--- | :--- | :--- |
| `u_texture` | `sampler2D` | A snapshot of the window's contents, sampled with `texture(u_texture, v_texcoord)`. |
| `u_progress` | `float` | Animation progress, `0.0` to `1.0`. See [Progress convention](#progress-convention) below. This is the one thing every shader must get right. |
| `u_time` | `float` | Seconds elapsed since the animation started. Useful for time-based effects (e.g. a glitch shimmer) independent of progress. |
| `u_size` | `vec2` | Output size in pixels (the window width and height being rendered into). |
| `v_texcoord` | `vec2` | Interpolated texture coordinate, `0.0` to `1.0` across the window. |
| `fragColor` | `out vec4` | Your shader's output color for the pixel. Write to this instead of `gl_FragColor`. |
Your shader writes its output to `fragColor`, which the header declares as `out vec4`. Since the engine composites the result with premultiplied alpha, the RGB channels you write must already be multiplied by the alpha you write. That means `fragColor = vec4(color.rgb * alpha, alpha)`, not `vec4(color.rgb, alpha)`. Forgetting this produces a white or light halo around fading-out windows.
### GLSL ES 3.00
The shader is compiled as GLSL ES 3.00. The loader prepends the `#version 300 es` line and the header above, so you write only the body:
- Sample textures with `texture(sampler, uv)`, not `texture2D(...)`.
- Write the output to `fragColor`, not `gl_FragColor`. The `out` variable is already declared in the header, so do not declare your own.
- Read the interpolated coordinate from `v_texcoord` (declared `in` by the header). Inputs from the vertex stage use `in`, not `varying`.
- No redeclaring `u_texture` / `u_progress` / `u_time` / `u_size` / `v_texcoord` / `fragColor` (see above).
### Precision: highp is guaranteed
GLSL ES 3.00 fragment shaders are guaranteed `highp` (fp32) floats. The classic noise hash works with no workaround:
```glsl
float hash12(vec2 p) {
return fract(sin(dot(p, vec2(127.1, 311.7))) * 43758.5453);
}
```
You do not need the multi-round fp16-safe hashes found in older GLES2 shader code, and you do not need `GL_FRAGMENT_PRECISION_HIGH` guards. `mediump` is still available if you want it for a hot loop, but the default `highp` is fine everywhere.
## Progress convention
Write your shader so `u_progress == 0.0` means the window is fully present and `u_progress == 1.0` means the window is fully gone. The compositor runs progress in the right direction for both open and close, so a single shader body works for both:
- **Close**: progress is driven directly from the close animation's elapsed fraction (`progress = min(animation_passed, 1.0)`), so it runs `0 -> 1` as the window closes: present, then gone.
- **Open**: progress is driven from `1.0 - min(animation_passed, 1.0)`, so it runs `1 -> 0` as the window opens: gone, then present.
The engine inverts progress for you on open. You never need two different shaders (or an `if` on direction) for the open vs. close case. One body covering `progress: 0 = present, 1 = gone` dissolves a window away on close and dissolves it in on open.
## Configuration
| Setting | Scope | Description |
| :--- | :--- | :--- |
| `effect_open` | global | Name (no `.frag`) of the shader to use for window open animations. |
| `effect_close` | global | Name (no `.frag`) of the shader to use for window close animations. |
```ini
effect_open=dissolve
effect_close=burn
```
Both can also be set per-window via a `windowrule`, which takes precedence over the global setting for matching windows:
```ini
windowrule=appid:firefox,effect_close:glitch
```
If `effect_open`/`effect_close` is left unset (globally and per-window), or the window doesn't match a rule, the window uses the existing parametric animation (`animation_type_open`/`animation_type_close`, fade, zoom, etc.) exactly as before this feature existed. Shaders are strictly opt-in.
## Fail-safe behavior
Shader loading and selection are designed to never break your session:
- **Compile/link failure**: if a `.frag` file fails to compile or link, the loader logs an error (visible with `WLR_ERROR` logging) and skips that file. It is never registered. Other shaders in the directory still load normally.
- **Unknown shader name**: if `effect_open`/`effect_close` (global or per-window) names a shader that was never successfully loaded (typo, compile failure, or you just haven't created the file), that window silently falls back to the normal parametric open/close animation. No crash, no missing window content. It just behaves as if the shader setting weren't there.
- **Missing shader directory**: if `~/.config/mango/shaders/` doesn't exist, it's logged at `INFO` level and skipped. Having no custom shaders at all is a normal, supported configuration.
## Restart required for new files
`~/.config/mango/shaders/` is scanned for `*.frag` files once at startup, and again automatically after a GPU reset (which also clears all previously compiled shader programs). There is no file-watching or hot-reload. If you add a new `.frag` file or rename one while mangowm is running, you need to restart the compositor before `effect_open` / `effect_close` can reference it. Editing the contents of an already-loaded shader also requires a restart to take effect.
## Example shaders
Example shaders live in a separate repo, [ernestoCruz05/shader_examples](https://github.com/ernestoCruz05/shader_examples). Clone it and copy whichever `.frag` files you want into your shader directory:
```sh
git clone https://github.com/ernestoCruz05/shader_examples.git
cp shader_examples/*.frag ~/.config/mango/shaders/
```
Then restart mangowm and reference them by basename (without the `.frag` extension) in your config (see [Quick start](#quick-start)).

View file

@ -39,6 +39,8 @@ libscenefx_dep = dependency('scenefx-0.5',version: '>=0.5.0')
pixman_dep = dependency('pixman-1') pixman_dep = dependency('pixman-1')
cjson_dep = dependency('libcjson') cjson_dep = dependency('libcjson')
pangocairo_dep = dependency('pangocairo') pangocairo_dep = dependency('pangocairo')
egl_dep = dependency('egl')
glesv2_dep = dependency('glesv2')
# version info # version info
git = find_program('git', required : false) git = find_program('git', required : false)
@ -97,7 +99,7 @@ executable('mango',
'src/mango.c', 'src/mango.c',
'src/common/util.c', 'src/common/util.c',
'src/draw/text-node.c', 'src/draw/text-node.c',
'src/draw/effect_pass.c',
wayland_sources, wayland_sources,
dependencies : [ dependencies : [
libm, libm,
@ -114,6 +116,8 @@ executable('mango',
pixman_dep, pixman_dep,
cjson_dep, cjson_dep,
pangocairo_dep, pangocairo_dep,
egl_dep,
glesv2_dep,
], ],
install : true, install : true,
c_args : c_args, c_args : c_args,

View file

@ -9,6 +9,11 @@ void set_rect_size(struct wlr_scene_rect *rect, int32_t width, int32_t height) {
wlr_scene_rect_set_size(rect, GEZERO(width), GEZERO(height)); wlr_scene_rect_set_size(rect, GEZERO(width), GEZERO(height));
} }
static void client_surface_set_enabled(Client *c, bool enabled) {
if (!c->fx.active && c->scene_surface)
wlr_scene_node_set_enabled(&c->scene_surface->node, enabled);
}
struct fx_corner_radii set_client_corner_location(Client *c) { struct fx_corner_radii set_client_corner_location(Client *c) {
struct fx_corner_radii current_corner_location = struct fx_corner_radii current_corner_location =
corner_radii_all(config.border_radius); corner_radii_all(config.border_radius);
@ -1049,6 +1054,7 @@ void client_apply_clip(Client *c, float factor) {
set_client_corner_location(c); set_client_corner_location(c);
if (!config.animations && !c->overview_scene_surface) { if (!config.animations && !c->overview_scene_surface) {
client_fx_settle(c);
c->animation.running = false; c->animation.running = false;
c->need_output_flush = false; c->need_output_flush = false;
c->animainit_geom = c->current = c->pending = c->animation.current = c->animainit_geom = c->current = c->pending = c->animation.current =
@ -1119,10 +1125,10 @@ void client_apply_clip(Client *c, float factor) {
// 如果窗口剪切区域已经剪切到0则不渲染窗口表面 // 如果窗口剪切区域已经剪切到0则不渲染窗口表面
if (clip_box.width <= 0 || clip_box.height <= 0) { if (clip_box.width <= 0 || clip_box.height <= 0) {
should_render_client_surface = false; should_render_client_surface = false;
wlr_scene_node_set_enabled(&c->scene_surface->node, false); client_surface_set_enabled(c, false);
} else { } else {
should_render_client_surface = true; should_render_client_surface = true;
wlr_scene_node_set_enabled(&c->scene_surface->node, true); client_surface_set_enabled(c, true);
} }
// 不用在执行下面的窗口表面剪切和缩放等效果操作 // 不用在执行下面的窗口表面剪切和缩放等效果操作
@ -1223,7 +1229,10 @@ void fadeout_client_animation_next_tick(Client *c) {
&c->scene->node, snap_scene_buffer_apply_effect, &buffer_data); &c->scene->node, snap_scene_buffer_apply_effect, &buffer_data);
} }
client_fx_tick(c, MANGO_MIN(animation_passed, 1.0), passed_time);
if (animation_passed >= 1.0) { if (animation_passed >= 1.0) {
client_fx_settle(c);
wl_list_remove(&c->fadeout_link); wl_list_remove(&c->fadeout_link);
wlr_scene_node_destroy(&c->scene->node); wlr_scene_node_destroy(&c->scene->node);
free(c); free(c);
@ -1231,6 +1240,99 @@ void fadeout_client_animation_next_tick(Client *c) {
} }
} }
static void client_fx_settle(Client *c) {
if (!c || !c->fx.active)
return;
if (c->fx.scene_buffer)
wlr_scene_node_destroy(&c->fx.scene_buffer->node);
if (c->fx.src)
wlr_texture_destroy(c->fx.src);
if (c->fx.swap)
wlr_swapchain_destroy(c->fx.swap);
c->fx.scene_buffer = NULL;
c->fx.src = NULL;
c->fx.swap = NULL;
c->fx.shader = NULL;
c->fx.active = false;
if (c->fx.fadeout) {
if (c->scene)
wlr_scene_node_set_enabled(&c->scene->node, true);
return;
}
if (c->scene_surface && !c->is_clip_to_hide && !c->is_logic_hide)
wlr_scene_node_set_enabled(&c->scene_surface->node, true);
}
static bool client_fx_begin(Client *c, struct effect_shader *shader,
struct wlr_scene_node *flatten_root,
struct wlr_scene_tree *parent, int width,
int height, bool include_rects) {
struct wlr_buffer *flat =
effect_pass_flatten(flatten_root, width, height, include_rects);
if (!flat) {
wlr_log(WLR_ERROR,
"effect_pass: fx setup failed (effect_pass_flatten), "
"falling back to parametric animation");
return false;
}
struct wlr_texture *src = wlr_texture_from_buffer(drw, flat);
if (src && !effect_pass_texture_usable(src)) {
wlr_texture_destroy(src);
wlr_buffer_drop(flat);
wlr_log(WLR_ERROR,
"effect_pass: snapshot texture is not shader-compatible "
"(external-only import), falling back to parametric animation");
return false;
}
struct wlr_swapchain *swap =
src ? effect_pass_create_swapchain(width, height) : NULL;
struct wlr_scene_buffer *scene_buffer =
swap ? wlr_scene_buffer_create(parent, NULL) : NULL;
wlr_buffer_drop(flat);
if (!scene_buffer) {
if (src)
wlr_texture_destroy(src);
if (swap)
wlr_swapchain_destroy(swap);
wlr_log(WLR_ERROR,
"effect_pass: fx setup failed (%s), falling back to "
"parametric animation",
!src ? "wlr_texture_from_buffer"
: !swap ? "effect_pass_create_swapchain"
: "wlr_scene_buffer_create");
return false;
}
c->fx.active = true;
c->fx.shader = shader;
c->fx.src = src;
c->fx.swap = swap;
c->fx.scene_buffer = scene_buffer;
c->fx.width = width;
c->fx.height = height;
return true;
}
static void client_fx_tick(Client *c, float progress, int32_t passed_time) {
if (!c->fx.active || !c->fx.swap)
return;
struct wlr_buffer *dst = wlr_swapchain_acquire(c->fx.swap);
if (!dst)
return;
struct effect_uniforms u = {
.progress = progress,
.time = (float)passed_time / 1000.0f,
};
bool ok = effect_pass_run(c->fx.shader, c->fx.src, dst, u);
if (ok)
wlr_scene_buffer_set_buffer(c->fx.scene_buffer, dst);
wlr_buffer_unlock(dst);
if (!ok) {
wlr_log(WLR_ERROR, "effect_pass: shader pass failed, falling back to "
"parametric animation");
client_fx_settle(c);
}
}
void client_animation_next_tick(Client *c) { void client_animation_next_tick(Client *c) {
struct timespec now; struct timespec now;
clock_gettime(CLOCK_MONOTONIC, &now); clock_gettime(CLOCK_MONOTONIC, &now);
@ -1268,9 +1370,37 @@ void client_animation_next_tick(Client *c) {
c->is_pending_open_animation = false; c->is_pending_open_animation = false;
if (c->animation.action == OPEN && c->animation.running &&
!c->fx.open_setup_done && !c->fx.active && c->scene &&
c->scene->node.enabled && c->scene_surface) {
struct wlr_box fx_sgeom;
client_get_geometry(c, &fx_sgeom);
bool fx_size_ready = fx_sgeom.width >= c->geom.width - 2 * (int)c->bw &&
fx_sgeom.height >= c->geom.height - 2 * (int)c->bw;
if (fx_size_ready || animation_passed > 0.33)
c->fx.open_setup_done = true;
struct effect_shader *fx_shader =
fx_size_ready ? pick_effect(c->effect_open, config.effect_open)
: NULL;
if (fx_shader) {
struct wlr_box fx_clip;
client_get_clip(c, &fx_clip);
wlr_scene_subsurface_tree_set_clip(&c->scene_surface->node,
&fx_clip);
if (client_fx_begin(c, fx_shader, &c->scene->node, c->scene,
c->geom.width, c->geom.height, false)) {
wlr_scene_node_set_enabled(&c->scene_surface->node, false);
wlr_log(WLR_DEBUG,
"effect_pass: open-fade shader active for client");
}
}
}
client_apply_clip(c, factor); client_apply_clip(c, factor);
client_fx_tick(c, 1.0 - MANGO_MIN(animation_passed, 1.0), passed_time);
if (animation_passed >= 1.0) { if (animation_passed >= 1.0) {
client_fx_settle(c);
// clear the open action state // clear the open action state
// To prevent him from being mistaken that // To prevent him from being mistaken that
@ -1304,10 +1434,9 @@ void client_animation_next_tick(Client *c) {
} }
void init_fadeout_client(Client *c) { void init_fadeout_client(Client *c) {
client_fx_settle(c);
if (!c->mon || client_is_unmanaged(c)) if (!c->mon || client_is_unmanaged(c))
return; return;
if (!c->scene) { if (!c->scene) {
return; return;
} }
@ -1340,6 +1469,19 @@ void init_fadeout_client(Client *c) {
return; return;
} }
struct effect_shader *fx_shader =
pick_effect(c->effect_close, config.effect_close);
fadeout_client->fx.fadeout = true;
if (fx_shader &&
client_fx_begin(fadeout_client, fx_shader, &fadeout_client->scene->node,
layers[LyrFadeOut], c->geom.width, c->geom.height,
true)) {
wlr_scene_node_set_enabled(&fadeout_client->scene->node, false);
wlr_scene_node_set_position(&fadeout_client->fx.scene_buffer->node,
c->geom.x, c->geom.y);
wlr_log(WLR_DEBUG, "effect_pass: close-fade shader active for client");
}
fadeout_client->animation.duration = config.animation_duration_close; fadeout_client->animation.duration = config.animation_duration_close;
fadeout_client->geom = fadeout_client->current = fadeout_client->geom = fadeout_client->current =
fadeout_client->animainit_geom = fadeout_client->animation.initial = fadeout_client->animainit_geom = fadeout_client->animation.initial =
@ -1390,7 +1532,8 @@ void init_fadeout_client(Client *c) {
} }
fadeout_client->animation.time_started = get_now_in_ms(); fadeout_client->animation.time_started = get_now_in_ms();
wlr_scene_node_set_enabled(&fadeout_client->scene->node, true); if (!fadeout_client->fx.active)
wlr_scene_node_set_enabled(&fadeout_client->scene->node, true);
wl_list_insert(&fadeout_clients, &fadeout_client->fadeout_link); wl_list_insert(&fadeout_clients, &fadeout_client->fadeout_link);
// 请求刷新屏幕 // 请求刷新屏幕
@ -1474,6 +1617,10 @@ void resize(Client *c, struct wlr_box geo, int32_t interact) {
if (!c->mon) if (!c->mon)
return; return;
if (c->fx.active &&
(geo.width != c->fx.width || geo.height != c->fx.height))
client_fx_settle(c);
c->need_output_flush = true; c->need_output_flush = true;
c->dirty = true; c->dirty = true;
@ -1553,6 +1700,8 @@ void resize(Client *c, struct wlr_box geo, int32_t interact) {
} }
if (c == grabc) { if (c == grabc) {
client_fx_settle(c);
c->animation.running = false; c->animation.running = false;
c->need_output_flush = false; c->need_output_flush = false;
@ -1767,6 +1916,9 @@ bool client_draw_frame(Client *c) {
if (!c || !client_surface(c)->mapped) if (!c || !client_surface(c)->mapped)
return false; return false;
if (c->fx.active && !c->animation.running)
client_fx_settle(c);
if (!c->need_output_flush) { if (!c->need_output_flush) {
return client_apply_focus_opacity(c); return client_apply_focus_opacity(c);
} }
@ -1774,6 +1926,7 @@ bool client_draw_frame(Client *c) {
if (config.animations && c->animation.running) { if (config.animations && c->animation.running) {
client_animation_next_tick(c); client_animation_next_tick(c);
} else { } else {
client_fx_settle(c);
wlr_scene_node_set_position(&c->scene->node, c->pending.x, wlr_scene_node_set_position(&c->scene->node, c->pending.x,
c->pending.y); c->pending.y);
c->animation.current = c->animainit_geom = c->animation.initial = c->animation.current = c->animainit_geom = c->animation.initial =

View file

@ -49,7 +49,7 @@ void set_arrange_visible(Monitor *m, Client *c, bool want_animation) {
c->is_clip_to_hide = false; c->is_clip_to_hide = false;
c->is_logic_hide = false; c->is_logic_hide = false;
wlr_scene_node_set_enabled(&c->scene->node, true); wlr_scene_node_set_enabled(&c->scene->node, true);
wlr_scene_node_set_enabled(&c->scene_surface->node, true); client_surface_set_enabled(c, true);
} }
if (!c->animation.tag_from_rule && want_animation && if (!c->animation.tag_from_rule && want_animation &&
@ -118,6 +118,7 @@ void set_arrange_hidden(Monitor *m, Client *c, bool want_animation) {
c->animation.tagining = false; c->animation.tagining = false;
set_tagout_animation(m, c); set_tagout_animation(m, c);
} else { } else {
client_fx_settle(c);
c->animation.running = false; c->animation.running = false;
wlr_scene_node_set_enabled(&c->scene->node, false); wlr_scene_node_set_enabled(&c->scene->node, false);
c->animainit_geom = c->current = c->pending = c->animation.current = c->animainit_geom = c->current = c->pending = c->animation.current =

View file

@ -67,6 +67,8 @@ typedef struct {
float scroller_proportion; float scroller_proportion;
const char *animation_type_open; const char *animation_type_open;
const char *animation_type_close; const char *animation_type_close;
const char *effect_open;
const char *effect_close;
const char *layer_animation_type_open; const char *layer_animation_type_open;
const char *layer_animation_type_close; const char *layer_animation_type_close;
int32_t isnoborder; int32_t isnoborder;
@ -201,6 +203,8 @@ typedef struct {
char animation_type_close[10]; char animation_type_close[10];
char layer_animation_type_open[10]; char layer_animation_type_open[10];
char layer_animation_type_close[10]; char layer_animation_type_close[10];
char effect_open[64];
char effect_close[64];
int32_t animation_fade_in; int32_t animation_fade_in;
int32_t animation_fade_out; int32_t animation_fade_out;
int32_t tag_animation_direction; int32_t tag_animation_direction;
@ -1362,6 +1366,12 @@ bool parse_option(Config *config, char *key, char *value) {
snprintf(config->animation_type_close, snprintf(config->animation_type_close,
sizeof(config->animation_type_close), "%.9s", sizeof(config->animation_type_close), "%.9s",
value); // string limit to 9 char value); // string limit to 9 char
} else if (strcmp(key, "effect_open") == 0) {
snprintf(config->effect_open, sizeof(config->effect_open), "%.63s",
value);
} else if (strcmp(key, "effect_close") == 0) {
snprintf(config->effect_close, sizeof(config->effect_close), "%.63s",
value);
} else if (strcmp(key, "layer_animation_type_open") == 0) { } else if (strcmp(key, "layer_animation_type_open") == 0) {
snprintf(config->layer_animation_type_open, snprintf(config->layer_animation_type_open,
sizeof(config->layer_animation_type_open), "%.9s", sizeof(config->layer_animation_type_open), "%.9s",
@ -2411,6 +2421,8 @@ bool parse_option(Config *config, char *key, char *value) {
// string rule value, relay to a client property // string rule value, relay to a client property
rule->animation_type_open = NULL; rule->animation_type_open = NULL;
rule->animation_type_close = NULL; rule->animation_type_close = NULL;
rule->effect_open = NULL;
rule->effect_close = NULL;
// float rule value, relay to a client property // float rule value, relay to a client property
rule->focused_opacity = 0; rule->focused_opacity = 0;
@ -2452,6 +2464,10 @@ bool parse_option(Config *config, char *key, char *value) {
rule->animation_type_open = strdup(val); rule->animation_type_open = strdup(val);
} else if (strcmp(key, "animation_type_close") == 0) { } else if (strcmp(key, "animation_type_close") == 0) {
rule->animation_type_close = strdup(val); rule->animation_type_close = strdup(val);
} else if (strcmp(key, "effect_open") == 0) {
rule->effect_open = strdup(val);
} else if (strcmp(key, "effect_close") == 0) {
rule->effect_close = strdup(val);
} else if (strcmp(key, "tags") == 0) { } else if (strcmp(key, "tags") == 0) {
rule->tags = 1 << (atoi(val) - 1); rule->tags = 1 << (atoi(val) - 1);
} else if (strcmp(key, "monitor") == 0) { } else if (strcmp(key, "monitor") == 0) {
@ -3221,12 +3237,18 @@ void free_config(void) {
free((void *)rule->animation_type_open); free((void *)rule->animation_type_open);
if (rule->animation_type_close) if (rule->animation_type_close)
free((void *)rule->animation_type_close); free((void *)rule->animation_type_close);
if (rule->effect_open)
free((void *)rule->effect_open);
if (rule->effect_close)
free((void *)rule->effect_close);
if (rule->monitor) if (rule->monitor)
free((void *)rule->monitor); free((void *)rule->monitor);
rule->id = NULL; rule->id = NULL;
rule->title = NULL; rule->title = NULL;
rule->animation_type_open = NULL; rule->animation_type_open = NULL;
rule->animation_type_close = NULL; rule->animation_type_close = NULL;
rule->effect_open = NULL;
rule->effect_close = NULL;
rule->monitor = NULL; rule->monitor = NULL;
// 释放 globalkeybinding 的 arg.v如果动态分配 // 释放 globalkeybinding 的 arg.v如果动态分配
if (rule->globalkeybinding.arg.v) { if (rule->globalkeybinding.arg.v) {

562
src/draw/effect_pass.c Normal file
View file

@ -0,0 +1,562 @@
#include "effect_pass.h"
#include <EGL/egl.h>
#include <GLES2/gl2.h>
#include <dirent.h>
#include <drm_fourcc.h>
#include <errno.h>
#include <limits.h>
#include <scenefx/render/fx_renderer/fx_renderer.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <wlr/render/drm_format_set.h>
#include <wlr/render/egl.h>
#include <wlr/render/pass.h>
#include <wlr/render/wlr_texture.h>
#include <wlr/types/wlr_buffer.h>
#include <wlr/util/log.h>
bool wlr_texture_is_fx(struct wlr_texture *texture);
bool wlr_renderer_is_fx(struct wlr_renderer *renderer);
static struct wlr_buffer *effect_alloc_render_buffer(int w, int h);
static struct {
bool ready;
struct wlr_renderer *renderer;
struct wlr_allocator *alloc;
EGLDisplay egl_display;
EGLContext egl_context;
} state;
bool effect_pass_init(struct wlr_renderer *renderer,
struct wlr_allocator *alloc) {
if (!wlr_renderer_is_fx(renderer)) {
wlr_log(WLR_ERROR, "effect_pass: renderer is not a SceneFX renderer, "
"shaders disabled");
return false;
}
state.renderer = renderer;
state.alloc = alloc;
state.egl_display = EGL_NO_DISPLAY;
state.egl_context = EGL_NO_CONTEXT;
state.ready = true;
struct wlr_buffer *probe = effect_alloc_render_buffer(1, 1);
if (probe) {
struct wlr_render_pass *pass =
wlr_renderer_begin_buffer_pass(renderer, probe, NULL);
if (pass) {
state.egl_display = eglGetCurrentDisplay();
state.egl_context = eglGetCurrentContext();
wlr_render_pass_submit(pass);
}
wlr_buffer_drop(probe);
}
if (state.egl_display == EGL_NO_DISPLAY) {
wlr_log(WLR_ERROR,
"effect_pass: could not acquire EGL context, shaders disabled");
state.ready = false;
return false;
}
return true;
}
struct effect_shader {
GLuint program;
GLint loc_tex, loc_progress, loc_time, loc_size, loc_pos, loc_uv;
};
static const char *VERT_SRC =
"#version 300 es\n"
"in vec2 a_pos;\n"
"in vec2 a_uv;\n"
"out vec2 v_texcoord;\n"
"void main() { v_texcoord = a_uv; gl_Position = vec4(a_pos, 0.0, 1.0); }\n";
static const char *INJECT_HEADER = "#version 300 es\n"
"precision highp float;\n"
"uniform sampler2D u_texture;\n"
"uniform float u_progress;\n"
"uniform float u_time;\n"
"uniform vec2 u_size;\n"
"in vec2 v_texcoord;\n"
"out vec4 fragColor;\n";
static GLuint effect_compile_shader(GLenum type, const char *src1,
const char *src2) {
GLuint s = glCreateShader(type);
const char *srcs[2] = {src1, src2 ? src2 : ""};
glShaderSource(s, src2 ? 2 : 1, srcs, NULL);
glCompileShader(s);
GLint ok = 0;
glGetShaderiv(s, GL_COMPILE_STATUS, &ok);
if (!ok) {
char log[1024];
glGetShaderInfoLog(s, sizeof(log), NULL, log);
wlr_log(WLR_ERROR, "effect_pass: shader compile failed: %s", log);
glDeleteShader(s);
return 0;
}
return s;
}
static struct effect_shader *make_shader(const char *frag_body) {
GLuint vs = effect_compile_shader(GL_VERTEX_SHADER, VERT_SRC, NULL);
GLuint fs =
effect_compile_shader(GL_FRAGMENT_SHADER, INJECT_HEADER, frag_body);
if (!vs || !fs) {
if (vs)
glDeleteShader(vs);
if (fs)
glDeleteShader(fs);
return NULL;
}
GLuint prog = glCreateProgram();
glAttachShader(prog, vs);
glAttachShader(prog, fs);
glLinkProgram(prog);
glDeleteShader(vs);
glDeleteShader(fs);
GLint ok = 0;
glGetProgramiv(prog, GL_LINK_STATUS, &ok);
if (!ok) {
glDeleteProgram(prog);
return NULL;
}
struct effect_shader *sh = calloc(1, sizeof(*sh));
sh->program = prog;
sh->loc_tex = glGetUniformLocation(prog, "u_texture");
sh->loc_progress = glGetUniformLocation(prog, "u_progress");
sh->loc_time = glGetUniformLocation(prog, "u_time");
sh->loc_size = glGetUniformLocation(prog, "u_size");
sh->loc_pos = glGetAttribLocation(prog, "a_pos");
sh->loc_uv = glGetAttribLocation(prog, "a_uv");
return sh;
}
struct registry_entry {
char name[64];
struct effect_shader *shader;
};
static struct registry_entry *registry;
static size_t registry_count;
static size_t registry_cap;
static void registry_put(const char *name, struct effect_shader *shader) {
for (size_t i = 0; i < registry_count; i++) {
if (strcmp(registry[i].name, name) == 0) {
wlr_log(WLR_INFO,
"effect_pass: shader '%s' redefined, replacing previous",
name);
glDeleteProgram(registry[i].shader->program);
free(registry[i].shader);
registry[i].shader = shader;
return;
}
}
if (registry_count == registry_cap) {
size_t new_cap = registry_cap ? registry_cap * 2 : 8;
struct registry_entry *grown =
realloc(registry, new_cap * sizeof(*grown));
if (!grown) {
wlr_log(WLR_ERROR,
"effect_pass: out of memory growing shader registry, "
"dropping '%s'",
name);
glDeleteProgram(shader->program);
free(shader);
return;
}
registry = grown;
registry_cap = new_cap;
}
snprintf(registry[registry_count].name,
sizeof(registry[registry_count].name), "%s", name);
registry[registry_count].shader = shader;
registry_count++;
}
void effect_pass_load_dir(const char *dir) {
if (!state.ready || !dir) {
return;
}
DIR *d = opendir(dir);
if (!d) {
wlr_log(
WLR_INFO,
"effect_pass: shader directory '%s' not available (%s), skipping",
dir, strerror(errno));
return;
}
EGLContext prev_ctx = eglGetCurrentContext();
EGLSurface prev_draw = eglGetCurrentSurface(EGL_DRAW);
EGLSurface prev_read = eglGetCurrentSurface(EGL_READ);
EGLDisplay prev_dpy = eglGetCurrentDisplay();
eglMakeCurrent(state.egl_display, EGL_NO_SURFACE, EGL_NO_SURFACE,
state.egl_context);
static const char ext[] = ".frag";
const size_t ext_len = sizeof(ext) - 1;
struct dirent *ent;
while ((ent = readdir(d)) != NULL) {
const char *fname = ent->d_name;
size_t len = strlen(fname);
if (len <= ext_len || strcmp(fname + len - ext_len, ext) != 0) {
continue;
}
size_t name_len = len - ext_len;
if (name_len >= sizeof(registry[0].name)) {
wlr_log(WLR_ERROR,
"effect_pass: shader filename '%s' too long, skipping",
fname);
continue;
}
char path[PATH_MAX];
int n = snprintf(path, sizeof(path), "%s/%s", dir, fname);
if (n < 0 || (size_t)n >= sizeof(path)) {
wlr_log(WLR_ERROR,
"effect_pass: shader path '%s/%s' too long, skipping", dir,
fname);
continue;
}
FILE *f = fopen(path, "rb");
if (!f) {
wlr_log(WLR_ERROR,
"effect_pass: failed to open '%s' (%s), skipping", path,
strerror(errno));
continue;
}
if (fseek(f, 0, SEEK_END) != 0) {
wlr_log(WLR_ERROR, "effect_pass: failed to read '%s', skipping",
path);
fclose(f);
continue;
}
long size = ftell(f);
if (size < 0 || fseek(f, 0, SEEK_SET) != 0) {
wlr_log(WLR_ERROR, "effect_pass: failed to read '%s', skipping",
path);
fclose(f);
continue;
}
char *buf = malloc((size_t)size + 1);
if (!buf) {
wlr_log(WLR_ERROR,
"effect_pass: out of memory reading '%s', skipping", path);
fclose(f);
continue;
}
size_t got = fread(buf, 1, (size_t)size, f);
fclose(f);
buf[got] = '\0';
char name[sizeof(registry[0].name)];
snprintf(name, sizeof(name), "%.*s", (int)name_len, fname);
struct effect_shader *sh = make_shader(buf);
free(buf);
if (!sh) {
wlr_log(WLR_ERROR,
"effect_pass: shader '%s' failed to compile, skipping",
name);
continue;
}
registry_put(name, sh);
wlr_log(WLR_INFO, "effect_pass: registered shader '%s'", name);
}
closedir(d);
wlr_log(WLR_INFO, "effect_pass: %zu shader(s) registered from '%s'",
registry_count, dir);
eglMakeCurrent(prev_dpy == EGL_NO_DISPLAY ? state.egl_display : prev_dpy,
prev_draw, prev_read, prev_ctx);
}
struct effect_shader *effect_pass_get(const char *name) {
if (!name) {
return NULL;
}
for (size_t i = 0; i < registry_count; i++) {
if (strcmp(registry[i].name, name) == 0) {
return registry[i].shader;
}
}
return NULL;
}
bool effect_pass_texture_usable(struct wlr_texture *src) {
if (!src || !wlr_texture_is_fx(src))
return false;
struct fx_texture_attribs sa;
fx_texture_get_attribs(src, &sa);
return sa.target == GL_TEXTURE_2D;
}
bool effect_pass_run(struct effect_shader *shader, struct wlr_texture *src,
struct wlr_buffer *dst, struct effect_uniforms u) {
if (!state.ready || !shader || !src || !dst)
return false;
struct fx_texture_attribs sa;
if (!effect_pass_texture_usable(src))
return false;
fx_texture_get_attribs(src, &sa);
GLuint fbo = fx_renderer_get_buffer_fbo(state.renderer, dst);
if (!fbo)
return false;
EGLContext prev_ctx = eglGetCurrentContext();
EGLSurface prev_draw = eglGetCurrentSurface(EGL_DRAW);
EGLSurface prev_read = eglGetCurrentSurface(EGL_READ);
EGLDisplay prev_dpy = eglGetCurrentDisplay();
eglMakeCurrent(state.egl_display, EGL_NO_SURFACE, EGL_NO_SURFACE,
state.egl_context);
while (glGetError() != GL_NO_ERROR)
;
glBindFramebuffer(GL_FRAMEBUFFER, fbo);
glViewport(0, 0, dst->width, dst->height);
glDisable(GL_BLEND);
glDisable(GL_SCISSOR_TEST);
glDisable(GL_STENCIL_TEST);
glDisable(GL_DEPTH_TEST);
glDisable(GL_CULL_FACE);
glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE);
glClearColor(0, 0, 0, 0);
glClear(GL_COLOR_BUFFER_BIT);
glUseProgram(shader->program);
static const GLfloat pos[] = {-1, -1, 1, -1, -1, 1, 1, 1};
static const GLfloat uv[] = {0, 0, 1, 0, 0, 1, 1, 1};
if (shader->loc_pos >= 0) {
glVertexAttribPointer(shader->loc_pos, 2, GL_FLOAT, GL_FALSE, 0, pos);
glEnableVertexAttribArray(shader->loc_pos);
}
if (shader->loc_uv >= 0) {
glVertexAttribPointer(shader->loc_uv, 2, GL_FLOAT, GL_FALSE, 0, uv);
glEnableVertexAttribArray(shader->loc_uv);
}
glActiveTexture(GL_TEXTURE0);
glBindTexture(sa.target, sa.tex);
glTexParameteri(sa.target, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameteri(sa.target, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glUniform1i(shader->loc_tex, 0);
glUniform1f(shader->loc_progress, u.progress);
glUniform1f(shader->loc_time, u.time);
glUniform2f(shader->loc_size, (float)dst->width, (float)dst->height);
glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
if (shader->loc_pos >= 0)
glDisableVertexAttribArray(shader->loc_pos);
if (shader->loc_uv >= 0)
glDisableVertexAttribArray(shader->loc_uv);
glBindTexture(sa.target, 0);
glBindFramebuffer(GL_FRAMEBUFFER, 0);
glUseProgram(0);
glFlush();
GLenum err = glGetError();
eglMakeCurrent(prev_dpy == EGL_NO_DISPLAY ? state.egl_display : prev_dpy,
prev_draw, prev_read, prev_ctx);
return err == GL_NO_ERROR;
}
static const struct wlr_drm_format *effect_render_format(void) {
const struct wlr_drm_format_set *texture_formats =
wlr_renderer_get_texture_formats(state.renderer, WLR_BUFFER_CAP_DMABUF);
if (!texture_formats) {
return NULL;
}
return wlr_drm_format_set_get(texture_formats, DRM_FORMAT_ABGR8888);
}
static struct wlr_buffer *effect_alloc_render_buffer(int w, int h) {
const struct wlr_drm_format *fmt = effect_render_format();
if (!fmt) {
return NULL;
}
return wlr_allocator_create_buffer(state.alloc, w, h, fmt);
}
struct wlr_swapchain *effect_pass_create_swapchain(int width, int height) {
if (!state.ready || width <= 0 || height <= 0) {
return NULL;
}
const struct wlr_drm_format *fmt = effect_render_format();
if (!fmt) {
return NULL;
}
return wlr_swapchain_create(state.alloc, width, height, fmt);
}
static void effect_flatten_bounds(struct wlr_scene_node *node, int *min_x,
int *min_y, bool *found) {
if (!node->enabled) {
return;
}
if (node->type == WLR_SCENE_NODE_BUFFER ||
node->type == WLR_SCENE_NODE_RECT) {
int ax = 0, ay = 0;
wlr_scene_node_coords(node, &ax, &ay);
if (!*found) {
*min_x = ax;
*min_y = ay;
*found = true;
} else {
if (ax < *min_x)
*min_x = ax;
if (ay < *min_y)
*min_y = ay;
}
} else if (node->type == WLR_SCENE_NODE_TREE) {
struct wlr_scene_tree *tree = wlr_scene_tree_from_node(node);
struct wlr_scene_node *child;
wl_list_for_each(child, &tree->children, link) {
effect_flatten_bounds(child, min_x, min_y, found);
}
}
}
static void effect_flatten_paint(struct wlr_scene_node *node,
struct wlr_render_pass *pass, int ox, int oy,
bool include_rects) {
if (!node->enabled) {
return;
}
int abs_x = 0, abs_y = 0;
wlr_scene_node_coords(node, &abs_x, &abs_y);
int nx = abs_x - ox, ny = abs_y - oy;
switch (node->type) {
case WLR_SCENE_NODE_BUFFER: {
struct wlr_scene_buffer *sb = wlr_scene_buffer_from_node(node);
if (sb->buffer) {
struct wlr_texture *t =
wlr_texture_from_buffer(state.renderer, sb->buffer);
if (t) {
int dw = sb->dst_width > 0 ? sb->dst_width : t->width;
int dh = sb->dst_height > 0 ? sb->dst_height : t->height;
wlr_render_pass_add_texture(
pass, &(struct wlr_render_texture_options){
.texture = t,
.dst_box =
{
.x = nx,
.y = ny,
.width = dw,
.height = dh,
},
});
wlr_texture_destroy(t);
} else {
wlr_log(WLR_ERROR,
"effect_pass: flatten failed to create texture from "
"buffer, snapshot will be incomplete");
}
}
break;
}
case WLR_SCENE_NODE_RECT: {
if (!include_rects)
break;
struct wlr_scene_rect *r = wlr_scene_rect_from_node(node);
wlr_render_pass_add_rect(pass, &(struct wlr_render_rect_options){
.box = {.x = nx,
.y = ny,
.width = r->width,
.height = r->height},
.color = {r->color[0], r->color[1],
r->color[2], r->color[3]},
});
break;
}
case WLR_SCENE_NODE_TREE: {
struct wlr_scene_tree *tree = wlr_scene_tree_from_node(node);
struct wlr_scene_node *child;
wl_list_for_each(child, &tree->children, link) {
effect_flatten_paint(child, pass, ox, oy, include_rects);
}
break;
}
case WLR_SCENE_NODE_SHADOW:
case WLR_SCENE_NODE_OPTIMIZED_BLUR:
case WLR_SCENE_NODE_BLUR:
break;
}
}
struct wlr_buffer *effect_pass_flatten(struct wlr_scene_node *node, int width,
int height, bool include_rects) {
if (!state.ready || !node || width <= 0 || height <= 0) {
return NULL;
}
struct wlr_buffer *dst = effect_alloc_render_buffer(width, height);
if (!dst) {
wlr_log(WLR_ERROR, "effect_pass: failed to allocate flatten buffer");
return NULL;
}
struct wlr_render_pass *pass =
wlr_renderer_begin_buffer_pass(state.renderer, dst, NULL);
if (!pass) {
wlr_buffer_drop(dst);
return NULL;
}
wlr_render_pass_add_rect(
pass, &(struct wlr_render_rect_options){
.box = {.x = 0, .y = 0, .width = width, .height = height},
.color = {0, 0, 0, 0},
.blend_mode = WLR_RENDER_BLEND_MODE_NONE,
});
int ox = 0, oy = 0;
bool found = false;
effect_flatten_bounds(node, &ox, &oy, &found);
if (!found) {
wlr_scene_node_coords(node, &ox, &oy);
}
effect_flatten_paint(node, pass, ox, oy, include_rects);
if (!wlr_render_pass_submit(pass)) {
wlr_buffer_drop(dst);
return NULL;
}
return dst;
}
void effect_pass_finish(void) {
if (registry_count > 0) {
EGLContext prev_ctx = eglGetCurrentContext();
EGLSurface prev_draw = eglGetCurrentSurface(EGL_DRAW);
EGLSurface prev_read = eglGetCurrentSurface(EGL_READ);
EGLDisplay prev_dpy = eglGetCurrentDisplay();
eglMakeCurrent(state.egl_display, EGL_NO_SURFACE, EGL_NO_SURFACE,
state.egl_context);
for (size_t i = 0; i < registry_count; i++) {
if (registry[i].shader) {
glDeleteProgram(registry[i].shader->program);
free(registry[i].shader);
}
}
eglMakeCurrent(prev_dpy == EGL_NO_DISPLAY ? state.egl_display
: prev_dpy,
prev_draw, prev_read, prev_ctx);
}
free(registry);
registry = NULL;
registry_count = 0;
registry_cap = 0;
state.ready = false;
}

29
src/draw/effect_pass.h Normal file
View file

@ -0,0 +1,29 @@
#ifndef MANGO_EFFECT_PASS_H
#define MANGO_EFFECT_PASS_H
#include <scenefx/types/wlr_scene.h>
#include <stdbool.h>
#include <wlr/render/allocator.h>
#include <wlr/render/swapchain.h>
#include <wlr/render/wlr_renderer.h>
#include <wlr/render/wlr_texture.h>
bool effect_pass_init(struct wlr_renderer *renderer,
struct wlr_allocator *alloc);
void effect_pass_finish(void);
struct effect_uniforms {
float progress;
float time;
};
struct effect_shader;
void effect_pass_load_dir(const char *dir);
struct effect_shader *effect_pass_get(const char *name);
bool effect_pass_run(struct effect_shader *shader, struct wlr_texture *src,
struct wlr_buffer *dst, struct effect_uniforms u);
bool effect_pass_texture_usable(struct wlr_texture *src);
struct wlr_buffer *effect_pass_flatten(struct wlr_scene_node *node, int width,
int height, bool include_rects);
struct wlr_swapchain *effect_pass_create_swapchain(int width, int height);
#endif

View file

@ -99,7 +99,9 @@
#include <xcb/xcb_icccm.h> #include <xcb/xcb_icccm.h>
#endif #endif
#include "common/util.h" #include "common/util.h"
#include "draw/effect_pass.h"
#include "draw/text-node.h" #include "draw/text-node.h"
#include <math.h>
/* macros */ /* macros */
#define MANGO_MAX(A, B) ((A) > (B) ? (A) : (B)) #define MANGO_MAX(A, B) ((A) > (B) ? (A) : (B))
@ -357,6 +359,16 @@ struct Client {
struct wl_list link; struct wl_list link;
struct wl_list flink; struct wl_list flink;
struct wl_list fadeout_link; struct wl_list fadeout_link;
struct {
bool active;
bool open_setup_done;
bool fadeout;
struct effect_shader *shader;
struct wlr_scene_buffer *scene_buffer;
struct wlr_swapchain *swap;
struct wlr_texture *src;
int width, height;
} fx;
union { union {
struct wlr_xdg_surface *xdg; struct wlr_xdg_surface *xdg;
struct wlr_xwayland_surface *xwayland; struct wlr_xwayland_surface *xwayland;
@ -406,6 +418,8 @@ struct Client {
const char *animation_type_open; const char *animation_type_open;
const char *animation_type_close; const char *animation_type_close;
char effect_open[64];
char effect_close[64];
int32_t is_in_scratchpad; int32_t is_in_scratchpad;
int32_t iscustomsize; int32_t iscustomsize;
int32_t iscustompos; int32_t iscustompos;
@ -973,6 +987,36 @@ static bool mango_scene_output_commit(struct wlr_scene_output *scene_output,
static bool mango_output_commit(Monitor *m); static bool mango_output_commit(Monitor *m);
static bool check_tearing_frame_allow(Monitor *m); static bool check_tearing_frame_allow(Monitor *m);
static void client_set_group_config(Client *c); static void client_set_group_config(Client *c);
static void client_fx_settle(Client *c);
static void client_surface_set_enabled(Client *c, bool enabled);
static bool client_fx_begin(Client *c, struct effect_shader *shader,
struct wlr_scene_node *flatten_root,
struct wlr_scene_tree *parent, int width,
int height, bool include_rects);
static void client_fx_tick(Client *c, float progress, int32_t passed_time);
static struct effect_shader *pick_effect(const char *per_client,
const char *global) {
const char *name = (per_client && per_client[0]) ? per_client
: (global && global[0]) ? global
: NULL;
struct effect_shader *sh = name ? effect_pass_get(name) : NULL;
if (name && !sh)
wlr_log(WLR_DEBUG,
"effect_pass: shader '%s' not found, using "
"parametric animation",
name);
return sh;
}
static void load_user_shader_dir(void) {
const char *home = getenv("HOME");
if (!home) {
return;
}
char shader_dir[512];
snprintf(shader_dir, sizeof(shader_dir), "%s/.config/mango/shaders", home);
effect_pass_load_dir(shader_dir);
}
#include "data/static_keymap.h" #include "data/static_keymap.h"
#include "dispatch/bind_declare.h" #include "dispatch/bind_declare.h"
@ -1353,6 +1397,8 @@ void client_replace(Client *c, Client *w, bool is_group_change_member,
wl_list_safe_reinsert_next(&w->link, &c->link); wl_list_safe_reinsert_next(&w->link, &c->link);
wl_list_safe_reinsert_prev(&w->flink, &c->flink); wl_list_safe_reinsert_prev(&w->flink, &c->flink);
client_fx_settle(w);
client_fx_settle(c);
wlr_scene_node_set_enabled(&w->scene->node, false); wlr_scene_node_set_enabled(&w->scene->node, false);
if (!c->is_logic_hide) { if (!c->is_logic_hide) {
@ -1497,6 +1543,8 @@ void gpureset(struct wl_listener *listener, void *data) {
struct wlr_renderer *old_drw = drw; struct wlr_renderer *old_drw = drw;
struct wlr_allocator *old_alloc = alloc; struct wlr_allocator *old_alloc = alloc;
struct Monitor *m = NULL; struct Monitor *m = NULL;
Client *fxc = NULL, *fxtmp = NULL;
Client *oc = NULL, *octmp = NULL;
wlr_log(WLR_DEBUG, "gpu reset"); wlr_log(WLR_DEBUG, "gpu reset");
@ -1506,6 +1554,15 @@ void gpureset(struct wl_listener *listener, void *data) {
if (!(alloc = wlr_allocator_autocreate(backend, drw))) if (!(alloc = wlr_allocator_autocreate(backend, drw)))
die("couldn't recreate allocator"); die("couldn't recreate allocator");
wl_list_for_each_safe(fxc, fxtmp, &fadeout_clients, fadeout_link)
client_fx_settle(fxc);
wl_list_for_each_safe(oc, octmp, &clients, link) client_fx_settle(oc);
effect_pass_finish();
effect_pass_init(drw, alloc);
load_user_shader_dir();
wl_list_remove(&gpu_reset.link); wl_list_remove(&gpu_reset.link);
wl_signal_add(&drw->events.lost, &gpu_reset); wl_signal_add(&drw->events.lost, &gpu_reset);
@ -1637,6 +1694,11 @@ static void apply_rule_properties(Client *c, const ConfigWinRule *r) {
APPLY_STRING_PROP(c, r, animation_type_open); APPLY_STRING_PROP(c, r, animation_type_open);
APPLY_STRING_PROP(c, r, animation_type_close); APPLY_STRING_PROP(c, r, animation_type_close);
if (r->effect_open)
snprintf(c->effect_open, sizeof(c->effect_open), "%s", r->effect_open);
if (r->effect_close)
snprintf(c->effect_close, sizeof(c->effect_close), "%s",
r->effect_close);
} }
void set_float_malposition(Client *tc) { void set_float_malposition(Client *tc) {
@ -2654,6 +2716,8 @@ void cleanup(void) {
dwl_im_relay_finish(dwl_input_method_relay); dwl_im_relay_finish(dwl_input_method_relay);
effect_pass_finish();
/* If it's not destroyed manually it will cause a use-after-free of /* If it's not destroyed manually it will cause a use-after-free of
* wlr_seat. Destroy it until it's fixed in the wlroots side */ * wlr_seat. Destroy it until it's fixed in the wlroots side */
wlr_backend_destroy(backend); wlr_backend_destroy(backend);
@ -6241,6 +6305,9 @@ void setup(void) {
if (!(alloc = wlr_allocator_autocreate(backend, drw))) if (!(alloc = wlr_allocator_autocreate(backend, drw)))
die("couldn't create allocator"); die("couldn't create allocator");
effect_pass_init(drw, alloc);
load_user_shader_dir();
/* This creates some hands-off wlroots interfaces. The compositor is /* This creates some hands-off wlroots interfaces. The compositor is
* necessary for clients to allocate surfaces and the data device * necessary for clients to allocate surfaces and the data device
* manager handles the clipboard. Each of these wlroots interfaces has * manager handles the clipboard. Each of these wlroots interfaces has
@ -6558,6 +6625,8 @@ void overview_backup_surface(Client *c) {
return; return;
} }
client_fx_settle(c);
struct wlr_box geometry; struct wlr_box geometry;
client_get_geometry(c, &geometry); client_get_geometry(c, &geometry);
struct wlr_box clip_box = (struct wlr_box){ struct wlr_box clip_box = (struct wlr_box){
@ -6782,6 +6851,7 @@ void unmapnotify(struct wl_listener *listener, void *data) {
Client *c = wl_container_of(listener, c, unmap); Client *c = wl_container_of(listener, c, unmap);
Monitor *m = NULL; Monitor *m = NULL;
Client *nextfocus = NULL; Client *nextfocus = NULL;
client_fx_settle(c);
c->iskilling = 1; c->iskilling = 1;
struct ScrollerStackNode *target_node = struct ScrollerStackNode *target_node =
c->mon ? find_scroller_node( c->mon ? find_scroller_node(