7.4 KiB
| title | description |
|---|---|
| Shader Transitions | 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
- Drop a
.fragfile in~/.config/mango/shaders/, e.g.dissolve.frag. - Reference it by filename (without the
.fragextension) in your config:
effect_open=dissolve
effect_close=burn
- Restart mangowm. Shader files are scanned once at startup and again after a GPU reset. Adding or editing a
.fragfile 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:
#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).
| 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 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), nottexture2D(...). - Write the output to
fragColor, notgl_FragColor. Theoutvariable is already declared in the header, so do not declare your own. - Read the interpolated coordinate from
v_texcoord(declaredinby the header). Inputs from the vertex stage usein, notvarying. - 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:
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 runs0 -> 1as the window closes: present, then gone. - Open: progress is driven from
1.0 - min(animation_passed, 1.0), so it runs1 -> 0as 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. |
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:
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
.fragfile fails to compile or link, the loader logs an error (visible withWLR_ERRORlogging) 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 atINFOlevel 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. Clone it and copy whichever .frag files you want into your shader directory:
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).