Skip to content

Commit

Permalink
refactor!: refactor the effect directory
Browse files Browse the repository at this point in the history
Most of the actual code is left unchanged, since there wasn't much to
refactor. However, this commit adds a LOT of comments to the code of the
fragment shader, as well as some extra documentation in other places.

BREAKING CHANGE: GLSLEffect overrides the blending mode, which seemed to
cause issues in the past, so the mode had to be manually set to the
default value. However, removing this doesn't seem to cause any issues
anymore, so it's no longer done. Perhaps this breaks something in the
future though.
  • Loading branch information
flexagoon committed Oct 22, 2024
1 parent 7723e53 commit eebd5b8
Show file tree
Hide file tree
Showing 7 changed files with 329 additions and 219 deletions.
27 changes: 27 additions & 0 deletions src/effect/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
# `effect`

This directory contains the code for applying GLSL effects to windows.

## `clip_shadow_effect.ts`

Due to a bug in GNOME, window shadows are drawn behind window contents. This
effect loads a simple Fragment shader that clips the shadow behind the window.

## `linear_filter_effect.ts`

This effect applies linear interpolation to the window, which makes windows
in the overview look less blurry.

## `rounded_corners_effect.ts`

This effect loads the actual Fragment shader that rounds the corners and draws
custom borders for the window. The class applies the effect and provides a
function to change uniforms passed to the effect.

## `shader`

This is the directory where the Fragment shaders are stored.

If you're interested in implementation details of the shader, you can read the
`shader/rounded_corners.frag` file, which is well commented and explains how
it works in great detail.
28 changes: 13 additions & 15 deletions src/effect/clip_shadow_effect.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,15 @@
// imports.gi
import type Clutter from 'gi://Clutter';
/**
* @file Clips shadows for windows.
*
* Needed because of this issue:
* https://gitlab.gnome.org/GNOME/gnome-shell/-/issues/4474
*/

import GObject from 'gi://GObject';
import Shell from 'gi://Shell';

// local modules
import {readShader} from '../utils/file.js';

// ------------------------------------------------------------------- [imports]

const [declarations, code] = readShader(
import.meta.url,
'shader/clip_shadow.frag',
Expand All @@ -16,17 +18,13 @@ const [declarations, code] = readShader(
export const ClipShadowEffect = GObject.registerClass(
{},
class extends Shell.GLSLEffect {
vfunc_build_pipeline(): void {
const hook = Shell.SnippetHook.FRAGMENT;
this.add_glsl_snippet(hook, declarations, code, false);
}

vfunc_paint_target(node: Clutter.PaintNode, ctx: Clutter.PaintContext) {
// Reset to default blend string.
this.get_pipeline()?.set_blend(
'RGBA = ADD(SRC_COLOR, DST_COLOR*(1-SRC_COLOR[A]))',
vfunc_build_pipeline() {
this.add_glsl_snippet(
Shell.SnippetHook.FRAGMENT,
declarations,
code,
false,
);
super.vfunc_paint_target(node, ctx);
}
},
);
8 changes: 7 additions & 1 deletion src/effect/linear_filter_effect.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,14 @@
import type Clutter from 'gi://Clutter';
/**
* @file Applies linear interpolation to a window. This is used to make windows
* in the overview look better.
*/

import Cogl from 'gi://Cogl';
import GObject from 'gi://GObject';
import Shell from 'gi://Shell';

import type Clutter from 'gi://Clutter';

export const LinearFilterEffect = GObject.registerClass(
{},
class extends Shell.GLSLEffect {
Expand Down
201 changes: 95 additions & 106 deletions src/effect/rounded_corners_effect.ts
Original file line number Diff line number Diff line change
@@ -1,57 +1,41 @@
// imports.gi
/** @file Binds the actual corner rounding shader to the windows. */

import GObject from 'gi://GObject';
import Meta from 'gi://Meta';
import Shell from 'gi://Shell';

// local modules
import {readShader} from '../utils/file.js';
import type * as types from '../utils/types.js';

// types
import type Clutter from 'gi://Clutter';
import {getPref} from '../utils/settings.js';

// --------------------------------------------------------------- [end imports]
import type {Bounds, RoundedCornerSettings} from '../utils/types.js';

// Load fragment shader of rounded corners effect.
const [declarations, code] = readShader(
import.meta.url,
'shader/rounded_corners.frag',
);

/** Location of uniform variants of rounded corners effect */
class Uniforms {
bounds = 0;
clip_radius = 0;
clipRadius = 0;
borderWidth = 0;
borderColor = 0;
borderedAreaBounds = 0;
borderedAreaClipRadius = 0;
exponent = 0;
inner_bounds = 0;
inner_clip_radius = 0;
pixel_step = 0;
border_width = 0;
border_color = 0;
pixelStep = 0;
}

export const RoundedCornersEffect = GObject.registerClass(
{},
class Effect extends Shell.GLSLEffect {
/**
* Location of uniforms variants in shader, Cache those location
* when shader has been setup in `vfunc_build_pipeline()`, sot that
* avoid to yse `this.get_uniform_location()` to query too much times.
* To store a uniform value, we need to know its location in the shader,
* which is done by calling `this.get_uniform_location()`. This is
* expensive, so we cache the location of uniforms when the shader is
* created.
*/
static uniforms: Uniforms = new Uniforms();

constructor() {
Effect.uniforms = {
bounds: 0,
clip_radius: 0,
exponent: 0,
inner_bounds: 0,
inner_clip_radius: 0,
pixel_step: 0,
border_width: 0,
border_color: 0,
};

super();

for (const k in Effect.uniforms) {
Expand All @@ -60,103 +44,108 @@ export const RoundedCornersEffect = GObject.registerClass(
}
}

vfunc_build_pipeline(): void {
const type = Shell.SnippetHook.FRAGMENT;
this.add_glsl_snippet(type, declarations, code, false);
}

vfunc_paint_target(node: Clutter.PaintNode, ctx: Clutter.PaintContext) {
// Reset to default blend string.
this.get_pipeline()?.set_blend(
'RGBA = ADD(SRC_COLOR, DST_COLOR*(1-SRC_COLOR[A]))',
vfunc_build_pipeline() {
this.add_glsl_snippet(
Shell.SnippetHook.FRAGMENT,
declarations,
code,
false,
);
super.vfunc_paint_target(node, ctx);
}

/**
* Used to update uniform variants of shader
* @param corners_cfg - Rounded corners settings of window
* @param bounds_cfg - Outer bounds of rounded corners
* Update uniforms of the shader.
* For more information, see the comments in the shader file.
*
* @param scaleFactor - Desktop scaling factor
* @param config - Rounded corners configuration
* @param windowBounds - Bounds of the window without padding
*/
update_uniforms(
scale_factor: number,
corners_cfg: types.RoundedCornerSettings,
outer_bounds: types.Bounds,
border: {
width: number;
color: [number, number, number, number];
} = {width: 0, color: [0, 0, 0, 0]},
pixel_step_raw?: [number, number],
updateUniforms(
scaleFactor: number,
config: RoundedCornerSettings,
windowBounds: Bounds,
) {
const border_width = border.width * scale_factor;
const border_color = border.color;
const borderWidth = getPref('border-width') * scaleFactor;
const borderColor = getPref('border-color');

const outer_radius = corners_cfg.borderRadius * scale_factor;
const {padding, smoothing} = corners_cfg;
const outerRadius = config.borderRadius * scaleFactor;
const {padding, smoothing} = config;

const bounds = [
outer_bounds.x1 + padding.left * scale_factor,
outer_bounds.y1 + padding.top * scale_factor,
outer_bounds.x2 - padding.right * scale_factor,
outer_bounds.y2 - padding.bottom * scale_factor,
windowBounds.x1 + padding.left * scaleFactor,
windowBounds.y1 + padding.top * scaleFactor,
windowBounds.x2 - padding.right * scaleFactor,
windowBounds.y2 - padding.bottom * scaleFactor,
];

const inner_bounds = [
bounds[0] + border_width,
bounds[1] + border_width,
bounds[2] - border_width,
bounds[3] - border_width,
const borderedAreaBounds = [
bounds[0] + borderWidth,
bounds[1] + borderWidth,
bounds[2] - borderWidth,
bounds[3] - borderWidth,
];

let inner_radius = outer_radius - border_width;
if (inner_radius < 0.001) {
inner_radius = 0.0;
let borderedAreaRadius = outerRadius - borderWidth;
if (borderedAreaRadius < 0.001) {
borderedAreaRadius = 0.0;
}

let pixel_step = pixel_step_raw;
if (!pixel_step) {
const actor = this.actor;
pixel_step = [1 / actor.get_width(), 1 / actor.get_height()];

// For wayland clients in Gnome 43.1, we can't get correct buffer size
// from Meta.WindowActor to calculate pixel step, but its first child
// offers correct one.
if (
actor instanceof Meta.WindowActor &&
actor.firstChild?.firstChild
) {
const {width, height} = actor.firstChild.firstChild;
pixel_step = [
1 / (width * scale_factor),
1 / (height * scale_factor),
];
}
}
const pixelStep = [
1 / this.actor.get_width(),
1 / this.actor.get_height(),
];

// Setup with squircle shape
let exponent = smoothing * 10.0 + 2.0;
let radius = outer_radius * 0.5 * exponent;
const max_radius = Math.min(
// This is needed for squircle corners
let exponent = smoothing * 10 + 2;
let radius = outerRadius * 0.5 * exponent;
const maxRadius = Math.min(
bounds[3] - bounds[0],
bounds[4] - bounds[1],
);
if (radius > max_radius) {
exponent *= max_radius / radius;
radius = max_radius;
if (radius > maxRadius) {
exponent *= maxRadius / radius;
radius = maxRadius;
}
inner_radius *= radius / outer_radius;

const location = Effect.uniforms;
this.set_uniform_float(location.bounds, 4, bounds);
this.set_uniform_float(location.inner_bounds, 4, inner_bounds);
this.set_uniform_float(location.pixel_step, 2, pixel_step);
this.set_uniform_float(location.border_width, 1, [border_width]);
this.set_uniform_float(location.exponent, 1, [exponent]);
this.set_uniform_float(location.clip_radius, 1, [radius]);
this.set_uniform_float(location.border_color, 4, border_color);
this.set_uniform_float(location.inner_clip_radius, 1, [
inner_radius,
borderedAreaRadius *= radius / outerRadius;

this.#setUniforms(
bounds,
radius,
borderWidth,
borderColor,
borderedAreaBounds,
borderedAreaRadius,
pixelStep,
exponent,
);
}

#setUniforms(
bounds: number[],
radius: number,
borderWidth: number,
borderColor: [number, number, number, number],
borderedAreaBounds: number[],
borderedAreaRadius: number,
pixelStep: number[],
exponent: number,
) {
const uniforms = Effect.uniforms;
this.set_uniform_float(uniforms.bounds, 4, bounds);
this.set_uniform_float(uniforms.clipRadius, 1, [radius]);
this.set_uniform_float(uniforms.borderWidth, 1, [borderWidth]);
this.set_uniform_float(uniforms.borderColor, 4, borderColor);
this.set_uniform_float(
uniforms.borderedAreaBounds,
4,
borderedAreaBounds,
);
this.set_uniform_float(uniforms.borderedAreaClipRadius, 1, [
borderedAreaRadius,
]);
this.set_uniform_float(uniforms.pixelStep, 2, pixelStep);
this.set_uniform_float(uniforms.exponent, 1, [exponent]);
this.queue_repaint();
}
},
Expand Down
8 changes: 4 additions & 4 deletions src/effect/shader/clip_shadow.frag
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
// clip shadow in a simple way
// Clip shadows to avoid showing them behind the window contents.
void main() {
vec4 color = cogl_color_out;
float gray = (color.r + color.g + color.b) / 3.0;
cogl_color_out *= (1.0 - smoothstep(0.4, 1.0, gray)) * color.a;
vec4 color = cogl_color_out;
float gray = (color.r + color.g + color.b) / 3.0;
cogl_color_out *= (1.0 - smoothstep(0.4, 1.0, gray)) * color.a;
}
Loading

0 comments on commit eebd5b8

Please sign in to comment.