Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/xarblu/kwin-effects-better-blur-dx/llms.txt

Use this file to discover all available pages before exploring further.

Shader Files Overview

Better Blur DX uses separate shader programs for each stage of the blur pipeline. All shaders are located in src/shaders/ and compiled into the plugin as Qt resources.

Shader Variants

Each shader comes in two variants:
  • Legacy OpenGL (.vert, .frag): GLSL 1.20 for older systems
  • Core OpenGL (_core.vert, _core.frag): GLSL 1.40 for modern systems
KWin’s ShaderManager automatically selects the appropriate variant based on the OpenGL context.

Vertex Shaders

Standard Vertex Shader

Files: vertex.vert, vertex_core.vert Used by most passes (downsample, upsample, noise, onscreen):
uniform mat4 modelViewProjectionMatrix;

attribute vec2 position;
attribute vec2 texcoord;

varying vec2 uv;

void main(void)
{
    gl_Position = modelViewProjectionMatrix * vec4(position, 0.0, 1.0);
    uv = texcoord;
}
Purpose: Transforms vertex positions and passes texture coordinates to fragment shader.
modelViewProjectionMatrix
mat4
Combined transformation matrix for viewport projection. Set in blur.cpp via uniformLocation() calls.

Rounded Vertex Shaders

Files: onscreen_rounded.vert, rounded_corners.vert (+ _core variants) Used for rounded corner rendering:
uniform mat4 modelViewProjectionMatrix;

attribute vec2 position;
attribute vec2 texcoord;

varying vec2 uv;
varying vec2 vertex;  // Additional output for SDF calculations

void main(void)
{
    gl_Position = modelViewProjectionMatrix * vec4(position, 0.0, 1.0);
    vertex = position;
    uv = texcoord;
}
Difference: Passes vertex position to fragment shader for signed distance field (SDF) calculations.

Fragment Shaders

Onscreen Pass Shader

Files: onscreen.frag, onscreen_core.frag
Loaded in: blur.cpp:120-131
Final rendering of blurred texture with color correction:
uniform sampler2D texUnit;
uniform mat4 colorMatrix;
uniform float offset;
uniform vec2 halfpixel;

varying vec2 uv;

void main(void)
{
    // 8-sample tent filter (same as upsample)
    vec4 sum = texture2D(texUnit, uv + vec2(-halfpixel.x * 2.0, 0.0) * offset);
    sum += texture2D(texUnit, uv + vec2(-halfpixel.x, halfpixel.y) * offset) * 2.0;
    // ... 6 more samples ...
    
    gl_FragColor = (sum / 12.0) * colorMatrix;
}

Downsample Shader

Files: downsample.frag, downsample_core.frag
Loaded in: blur.cpp:149-159
Implements the Kawase downsample algorithm:
void main(void)
{
    vec4 sum = texture2D(texUnit, uv) * 4.0;  // Center pixel, weight 4
    sum += texture2D(texUnit, uv - halfpixel.xy * offset);               // Top-left
    sum += texture2D(texUnit, uv + halfpixel.xy * offset);               // Bottom-right  
    sum += texture2D(texUnit, uv + vec2(halfpixel.x, -halfpixel.y) * offset);   // Top-right
    sum += texture2D(texUnit, uv - vec2(halfpixel.x, -halfpixel.y) * offset);   // Bottom-left

    gl_FragColor = sum / 8.0;
}
Sampling Pattern:
    X
  X C X
    X
Where C = center (weight 4), X = diagonal samples (weight 1 each)

Upsample Shader

Files: upsample.frag, upsample_core.frag
Loaded in: blur.cpp:161-171
Implements the Kawase upsample with tent filter:
void main(void)
{
    vec4 sum = texture2D(texUnit, uv + vec2(-halfpixel.x * 2.0, 0.0) * offset);
    sum += texture2D(texUnit, uv + vec2(-halfpixel.x, halfpixel.y) * offset) * 2.0;
    sum += texture2D(texUnit, uv + vec2(0.0, halfpixel.y * 2.0) * offset);
    sum += texture2D(texUnit, uv + vec2(halfpixel.x, halfpixel.y) * offset) * 2.0;
    sum += texture2D(texUnit, uv + vec2(halfpixel.x * 2.0, 0.0) * offset);
    sum += texture2D(texUnit, uv + vec2(halfpixel.x, -halfpixel.y) * offset) * 2.0;
    sum += texture2D(texUnit, uv + vec2(0.0, -halfpixel.y * 2.0) * offset);
    sum += texture2D(texUnit, uv + vec2(-halfpixel.x, -halfpixel.y) * offset) * 2.0;

    gl_FragColor = sum / 12.0;
}
Sampling Pattern:
  1
2   2
  1
2   2
Total weight: 12

Noise Shader

Files: noise.frag, noise_core.frag
Loaded in: blur.cpp:173-182
Adds dithering to prevent banding:
uniform sampler2D texUnit;
uniform vec2 noiseTextureSize;

varying vec2 uv;

void main(void)
{
    vec2 uvNoise = vec2(gl_FragCoord.xy / noiseTextureSize);
    gl_FragColor = vec4(texture2D(texUnit, uvNoise).rrr, 0);
}
Uses gl_FragCoord (screen coordinates) instead of uv to ensure noise pattern doesn’t scale with window size.
Rendering: Applied with additive blending (GL_ONE, GL_ONE) over the blurred result (blur.cpp:1142-1147).

Rounded Corners Shaders

Rounded Onscreen Shader

Files: onscreen_rounded.frag, onscreen_rounded_core.frag
Loaded in: blur.cpp:133-147
Combines blur rendering with rounded corner masking:
#extension GL_OES_standard_derivatives : enable
#include "sdf.glsl"

uniform sampler2D texUnit;
uniform mat4 colorMatrix;
uniform vec4 box;           // Window bounds (center_x, center_y, half_width, half_height)
uniform vec4 cornerRadius;  // Per-corner radii (top-left, top-right, bottom-left, bottom-right)
uniform float opacity;

varying vec2 uv;
varying vec2 vertex;

void main(void)
{
    // Apply blur (8-sample tent filter)
    vec4 sum = texture2D(texUnit, uv + vec2(-halfpixel.x * 2.0, 0.0) * offset);
    // ... 7 more samples ...
    vec4 fragColor = (sum / 12.0) * colorMatrix * opacity;

    // Apply rounded corner mask using SDF
    float f = sdfRoundedBox(vertex, box.xy, box.zw, cornerRadius);
    float df = fwidth(f);  // Derivative for anti-aliasing
    fragColor *= 1.0 - clamp(0.5 + f / df, 0.0, 1.0);

    gl_FragColor = fragColor;
}
Used when: Window has native border radius (currently disabled, see BETTERBLUR_NOT_NEEDED in blur.cpp:1051)

Rounded Corners Pass Shader

Files: rounded_corners.frag, rounded_corners_core.frag
Referenced in: rounded_corners_pass.hpp
Applies rounded corners as a post-process:
void main(void)
{
    vec4 fragColor = texture2D(texUnit, uv);

    float f = sdfRoundedBox(vertex, box.xy, box.zw, cornerRadius);
    float df = fwidth(f);
    float inv_alpha = clamp(0.5 + f / df, 0.0, 1.0);
    fragColor *= inv_alpha;

    gl_FragColor = fragColor;
}
Used by: RoundedCornersPass::apply() for user-configured rounded corners (blur.cpp:1168-1170)

Refraction Shaders

Files: refraction.frag, refraction_rounded.frag (+ _core variants)
Referenced in: refraction_pass.hpp
Advanced experimental shaders for glass-like refraction effects.

Basic Refraction

uniform float refractionStrength;
uniform float refractionEdgeSizePixels;
uniform float refractionCornerRadiusPixels;
uniform int refractionMode;  // 0: Basic, 1: Concave

void main(void) {
    // Calculate distance to rounded rectangle edge
    vec2 position = uv * refractionRectSize - halfRefractionRectSize;
    float dist = roundedRectangleDist(position, halfRefractionRectSize, cornerR);
    
    // Compute edge proximity for refraction strength
    float edgeProximity = clamp(1.0 + dist / refractionEdgeSizePixels, 0.0, 1.0);
    
    // Apply refraction offset based on surface normal
    vec2 normal = computeNormal(position);
    vec2 refractOffset = normal * refractionStrength * edgeProximity;
    
    // Sample with offset
    gl_FragColor = texture2D(texUnit, uv - refractOffset) * colorMatrix;
}
Features:
  • Edge-based refraction (stronger near borders)
  • RGB chromatic aberration/fringing
  • Two modes: convex (bulge) and concave (lens)
  • Texture repeat mode options (clamp/mirror)
Refraction shaders are optional and controlled by RefractionPass mixin. See refraction_pass.hpp for implementation details.

Shader Loading

Shaders are loaded in the BlurEffect constructor (blur.cpp:115-189):
m_onscreenPass.shader = ShaderManager::instance()->generateShaderFromFile(
    ShaderTrait::MapTexture,
    QStringLiteral(":/effects/better_blur_dx/shaders/vertex.vert"),
    QStringLiteral(":/effects/better_blur_dx/shaders/onscreen.frag")
);

if (!m_onscreenPass.shader) {
    qCWarning(KWIN_BLUR) << BBDX::LOG_PREFIX << "Failed to load onscreen pass shader";
    return;
}

// Cache uniform locations for performance
m_onscreenPass.mvpMatrixLocation = 
    m_onscreenPass.shader->uniformLocation("modelViewProjectionMatrix");
m_onscreenPass.colorMatrixLocation = 
    m_onscreenPass.shader->uniformLocation("colorMatrix");
m_onscreenPass.offsetLocation = 
    m_onscreenPass.shader->uniformLocation("offset");
m_onscreenPass.halfpixelLocation = 
    m_onscreenPass.shader->uniformLocation("halfpixel");

Shader Structs

Each render pass stores its shader and uniform locations in a struct (blur.h:130-176):
struct {
    std::unique_ptr<GLShader> shader;
    int mvpMatrixLocation;
    int colorMatrixLocation;
    int offsetLocation;
    int halfpixelLocation;
} m_onscreenPass;
This avoids expensive uniformLocation() lookups during rendering.

Core vs Legacy OpenGL

Version Detection

KWin’s ShaderManager automatically chooses:
  • Legacy: OpenGL 2.1 / GLSL 1.20 (default on older systems)
  • Core: OpenGL 3.1+ / GLSL 1.40 (modern systems)

Key Differences

Legacy:
attribute vec2 position;
varying vec2 uv;
Core:
in vec2 position;
out vec2 uv;

Compatibility

Better Blur DX maintains identical shader logic in both variants, only syntax differs. This ensures the effect works on:
  • Modern Linux desktops (core profile)
  • Older hardware (legacy profile)
  • X11 and Wayland sessions
  • Virtual machines with limited GL support

Shader Pipeline Flow

Performance Optimizations

Uniform Location Caching

Uniform locations are cached at shader load time (blur.cpp:127-130, etc.):
m_onscreenPass.mvpMatrixLocation = 
    m_onscreenPass.shader->uniformLocation("modelViewProjectionMatrix");
This avoids string lookups every frame.

Shader State Management

Shaders are pushed/popped from ShaderManager stack (blur.cpp:995, 1018, etc.):
ShaderManager::instance()->pushShader(m_downsamplePass.shader.get());
// ... render ...
ShaderManager::instance()->popShader();
This allows KWin to restore previous shader state for other effects.

Texture Filtering

All textures use GL_LINEAR filtering (blur.cpp:854):
texture->setFilter(GL_LINEAR);
texture->setWrapMode(GL_CLAMP_TO_EDGE);
  • GL_LINEAR: Hardware-accelerated bilinear interpolation
  • GL_CLAMP_TO_EDGE: Prevents edge artifacts when sampling near borders

Debugging Shaders

Shader compilation errors are logged via KWin’s logging system:
if (!m_downsamplePass.shader) {
    qCWarning(KWIN_BLUR) << BBDX::LOG_PREFIX << "Failed to load downsampling pass shader";
    return;
}
View shader errors with:
QT_LOGGING_RULES="kwin_effect_better_blur_dx=true" kwin_wayland --replace
If any shader fails to load, the entire effect becomes inactive (m_valid = false). Check ~/.xsession-errors or journalctl for shader compilation errors.

Build docs developers (and LLMs) love