🔧 Fix memory leaks of more effects

This commit is contained in:
Simon Schneegans
2022-05-08 12:31:20 +02:00
parent 6ebc975a20
commit 61bea04018
7 changed files with 624 additions and 340 deletions
+97 -41
View File
@@ -27,8 +27,13 @@ const utils = Me.imports.src.utils;
// the center. //
//////////////////////////////////////////////////////////////////////////////////////////
// The shader class for this effect is registered further down in this file.
let Shader = null;
// The shader class for this effect is registered further down in this file. When this
// effect is used for the first time, an instance of this shader class is created. Once
// the effect is finished, the shader will be stored in the freeShaders array and will
// then be reused if a new shader is requested. ShaderClass which will be used whenever
// this effect is used.
let ShaderClass = null;
let freeShaders = [];
// The effect class is completely static. It can be used to get some metadata (like the
// effect's name or supported GNOME Shell versions), to initialize the respective page of
@@ -80,9 +85,21 @@ var Apparition = class Apparition {
// ---------------------------------------------------------------- API for extension.js
// This is called from extension.js whenever a window is closed with this effect.
static createShader(actor, settings, forOpening) {
return new Shader(actor, settings, forOpening);
// This is called from extension.js whenever a window is opened or closed with this
// effect. It returns an instance of the shader class, trying to reuse previously
// created shaders.
static getShader(actor, settings, forOpening) {
let shader;
if (freeShaders.length == 0) {
shader = new ShaderClass();
} else {
shader = freeShaders.pop();
}
shader.setUniforms(actor, settings, forOpening);
return shader;
}
// The tweakTransition() is called from extension.js to tweak a window's open / close
@@ -103,6 +120,12 @@ var Apparition = class Apparition {
'scale-y': {from: 2.0, to: 2.0, mode: 1}
};
}
// This is called from extension.js if the extension is disabled. This should free all
// static resources.
static cleanUp() {
freeShaders = [];
}
}
@@ -114,59 +137,92 @@ var Apparition = class Apparition {
if (utils.isInShellProcess()) {
const Clutter = imports.gi.Clutter;
const Shell = imports.gi.Shell;
const shaderSnippets = Me.imports.src.shaderSnippets;
Shader = GObject.registerClass({}, class Shader extends Clutter.ShaderEffect {
_init(actor, settings, forOpening) {
super._init({shader_type: Clutter.ShaderType.FRAGMENT_SHADER});
ShaderClass = GObject.registerClass({}, class ShaderClass extends Shell.GLSLEffect {
// This is called when the effect is used for the first time. This can be used to
// store all required uniform locations.
_init() {
super._init();
this._uForOpening = this.get_uniform_location('uForOpening');
this._uSeed = this.get_uniform_location('uSeed');
this._uShake = this.get_uniform_location('uShake');
this._uTwirl = this.get_uniform_location('uTwirl');
this._uSuction = this.get_uniform_location('uSuction');
this._uRandomness = this.get_uniform_location('uRandomness');
}
// This is called each time the effect is used. This can be used to retrieve the
// configuration from the settings and update all uniforms accordingly.
setUniforms(actor, settings, forOpening) {
// If we are currently performing integration test, the animation uses a fixed seed.
const testMode = settings.get_boolean('test-mode');
// The math for the whirling is inspired by this post:
// http://www.geeks3d.com/20110428/shader-library-swirl-post-processing-filter-in-glsl
this.set_shader_source(`
// clang-format off
this.set_uniform_float(this._uForOpening, 1, [forOpening]);
this.set_uniform_float(this._uSeed, 2, [testMode ? 0 : Math.random(), testMode ? 0 : Math.random()]);
this.set_uniform_float(this._uShake, 1, [settings.get_double('apparition-shake-intensity')]);
this.set_uniform_float(this._uTwirl, 1, [settings.get_double('apparition-twirl-intensity')]);
this.set_uniform_float(this._uSuction, 1, [settings.get_double('apparition-suction-intensity')]);
this.set_uniform_float(this._uRandomness, 1, [settings.get_double('apparition-randomness')]);
// clang-format on
}
// This is called by extension.js when the shader is not used anymore. We will store
// this instance of the shader so that it can be re-used in th future.
free() {
freeShaders.push(this);
}
// This is called by the constructor. This is means it's only called when the effect
// is used for the first time.
vfunc_build_pipeline() {
const declarations = `
// Inject some common shader snippets.
${shaderSnippets.standardUniforms()}
const vec2 SEED = vec2(${testMode ? 0 : Math.random()},
${testMode ? 0 : Math.random()});
const float SHAKE = ${settings.get_double('apparition-shake-intensity')};
const float TWIRL = ${settings.get_double('apparition-twirl-intensity')};
const float SUCTION = ${settings.get_double('apparition-suction-intensity')};
const float RANDOMNESS = ${settings.get_double('apparition-randomness')};
uniform bool uForOpening;
uniform vec2 uSeed;
uniform float uShake;
uniform float uTwirl;
uniform float uSuction;
uniform float uRandomness;
const float ACTOR_SCALE = 2.0;
const float PADDING = ACTOR_SCALE / 2.0 - 0.5;
`;
void main() {
// The math for the whirling is inspired by this post:
// http://www.geeks3d.com/20110428/shader-library-swirl-post-processing-filter-in-glsl
const code = `
// We simply inverse the progress for opening windows.
float progress = uForOpening ? 1.0-uProgress : uProgress;
// We simply inverse the progress for opening windows.
float progress = ${forOpening ? '1.0-uProgress' : 'uProgress'};
// Choose a random suction center.
vec2 center = uSeed * uRandomness + 0.5 * (1.0 - uRandomness);
vec2 coords = cogl_tex_coord_in[0].st * ACTOR_SCALE - PADDING - center;
// Choose a random suction center.
vec2 center = SEED * RANDOMNESS + 0.5 * (1.0 - RANDOMNESS);
vec2 coords = cogl_tex_coord_in[0].st * ACTOR_SCALE - PADDING - center;
// Add some shaking.
coords.x += progress * 0.05 * uShake * sin((progress + uSeed.x) * (1.0 + uSeed.x) * uShake);
coords.y += progress * 0.05 * uShake * cos((progress + uSeed.y) * (1.0 + uSeed.y) * uShake);
// Add some shaking.
coords.x += progress * 0.05 * SHAKE * sin((progress + SEED.x) * (1.0 + SEED.x) * SHAKE);
coords.y += progress * 0.05 * SHAKE * cos((progress + SEED.y) * (1.0 + SEED.y) * SHAKE);
// "Suck" the texture into the center.
float dist = length(coords) / sqrt(2);
coords += progress * coords / dist * 0.5 * uSuction;
// "Suck" the texture into the center.
float dist = length(coords) / sqrt(2);
coords += progress * coords / dist * 0.5 * SUCTION;
// Apply some whirling.
float angle = pow(1.0 - dist, 2.0) * uTwirl * progress;
float s = sin(angle);
float c = cos(angle);
coords = vec2(dot(coords, vec2(c, -s)), dot(coords, vec2(s, c)));
// Apply some whirling.
float angle = pow(1.0 - dist, 2.0) * TWIRL * progress;
float s = sin(angle);
float c = cos(angle);
coords = vec2(dot(coords, vec2(c, -s)), dot(coords, vec2(s, c)));
// Fade out the window texture.
cogl_color_out = texture2D(uTexture, coords + center) * (1.0 - progress);
`;
// Fade out the window texture.
cogl_color_out = texture2D(uTexture, coords + center) * (1.0 - progress);
}
`);
};
this.add_glsl_snippet(Shell.SnippetHook.FRAGMENT, declarations, code, true);
}
});
}
+125 -66
View File
@@ -28,8 +28,16 @@ const utils = Me.imports.src.utils;
// of vfunc_paint_target further down in this file. //
//////////////////////////////////////////////////////////////////////////////////////////
// The shader class for this effect is registered further down in this file.
let Shader = null;
// The shader class for this effect is registered further down in this file. When this
// effect is used for the first time, an instance of this shader class is created. Once
// the effect is finished, the shader will be stored in the freeShaders array and will
// then be reused if a new shader is requested. ShaderClass which will be used whenever
// this effect is used.
let ShaderClass = null;
let freeShaders = [];
// This texture will be loaded when the effect is used for the first time.
let shardTexture = null;
// The effect class is completely static. It can be used to get some metadata (like the
// effect's name or supported GNOME Shell versions), to initialize the respective page of
@@ -80,9 +88,21 @@ var BrokenGlass = class BrokenGlass {
// ---------------------------------------------------------------- API for extension.js
// This is called from extension.js whenever a window is closed with this effect.
static createShader(actor, settings, forOpening) {
return new Shader(actor, settings, forOpening);
// This is called from extension.js whenever a window is opened or closed with this
// effect. It returns an instance of the shader class, trying to reuse previously
// created shaders.
static getShader(actor, settings, forOpening) {
let shader;
if (freeShaders.length == 0) {
shader = new ShaderClass();
} else {
shader = freeShaders.pop();
}
shader.setUniforms(actor, settings, forOpening);
return shader;
}
// The tweakTransition() is called from extension.js to tweak a window's open / close
@@ -103,6 +123,13 @@ var BrokenGlass = class BrokenGlass {
'scale-y': {from: 2.0, to: 2.0, mode: 1}
};
}
// This is called from extension.js if the extension is disabled. This should free all
// static resources.
static cleanUp() {
freeShaders = [];
shardTexture = null;
}
}
@@ -114,8 +141,8 @@ var BrokenGlass = class BrokenGlass {
if (utils.isInShellProcess()) {
const {Clutter, GdkPixbuf, Cogl} = imports.gi;
const shaderSnippets = Me.imports.src.shaderSnippets;
const {Clutter, GdkPixbuf, Cogl, Shell} = imports.gi;
const shaderSnippets = Me.imports.src.shaderSnippets;
// This shader creates a complex-looking effect with rather simple means. Here is how it
// works: The window is drawn five times on top of each other (see the SHARD_LAYERS
@@ -126,17 +153,32 @@ if (utils.isInShellProcess()) {
// belongs to which layer is defined by the green channel of the texture
// resources/img/shards.png. The red channel of the texture contains the distance to the
// shard edges. This information is used to fade out the shards.
Shader = GObject.registerClass({}, class Shader extends Clutter.ShaderEffect {
_init(actor, settings, forOpening) {
super._init({shader_type: Clutter.ShaderType.FRAGMENT_SHADER});
ShaderClass = GObject.registerClass({}, class ShaderClass extends Shell.GLSLEffect {
// This is called when the effect is used for the first time. This can be used to
// store all required uniform locations.
_init() {
super._init();
// Load the shards texture. As the shader is re-created for each window animation,
// this texture is also re-created each time. This could be improved in the future!
const shardData = GdkPixbuf.Pixbuf.new_from_resource('/img/shards.png');
this._shardTexture = new Clutter.Image();
this._shardTexture.set_data(shardData.get_pixels(), Cogl.PixelFormat.RGB_888,
shardData.width, shardData.height, shardData.rowstride);
// Load the shards texture.
if (shardTexture == null) {
const shardData = GdkPixbuf.Pixbuf.new_from_resource('/img/shards.png');
shardTexture = new Clutter.Image();
shardTexture.set_data(shardData.get_pixels(), Cogl.PixelFormat.RGB_888,
shardData.width, shardData.height, shardData.rowstride);
}
this._uForOpening = this.get_uniform_location('uForOpening');
this._uShardTexture = this.get_uniform_location('uShardTexture');
this._uSeed = this.get_uniform_location('uSeed');
this._uEpicenter = this.get_uniform_location('uEpicenter');
this._uShardScale = this.get_uniform_location('uShardScale');
this._uBlowForce = this.get_uniform_location('uBlowForce');
this._uGravity = this.get_uniform_location('uGravity');
}
// This is called each time the effect is used. This can be used to retrieve the
// configuration from the settings and update all uniforms accordingly.
setUniforms(actor, settings, forOpening) {
// Usually, the shards fly away from the center of the window.
let epicenterX = 0.5;
let epicenterY = 0.5;
@@ -155,78 +197,94 @@ if (utils.isInShellProcess()) {
// If we are currently performing integration test, the animation uses a fixed seed.
const testMode = settings.get_boolean('test-mode');
this.set_shader_source(`
// clang-format off
this.set_uniform_float(this._uForOpening, 1, [forOpening]);
this.set_uniform_float(this._uSeed, 2, [testMode ? 0 : Math.random(), testMode ? 0 : Math.random()]);
this.set_uniform_float(this._uEpicenter, 2, [epicenterX, epicenterY]);
this.set_uniform_float(this._uShardScale, 1, [settings.get_double('broken-glass-scale')]);
this.set_uniform_float(this._uBlowForce, 1, [settings.get_double('broken-glass-blow-force')]);
this.set_uniform_float(this._uGravity, 1, [settings.get_double('broken-glass-gravity')]);
// clang-format on
}
// This is called by extension.js when the shader is not used anymore. We will store
// this instance of the shader so that it can be re-used in th future.
free() {
freeShaders.push(this);
}
// This is called by the constructor. This is means it's only called when the effect
// is used for the first time.
vfunc_build_pipeline() {
const declarations = `
// Inject some common shader snippets.
${shaderSnippets.standardUniforms()}
uniform bool uForOpening;
uniform sampler2D uShardTexture;
uniform vec2 uSeed;
uniform vec2 uEpicenter;
uniform float uShardScale;
uniform float uBlowForce;
uniform float uGravity;
const vec2 SEED = vec2(${testMode ? 0 : Math.random()},
${testMode ? 0 : Math.random()});
const float SHARD_SCALE = ${settings.get_double('broken-glass-scale')};
const float SHARD_LAYERS = 5;
const float BLOW_FORCE = ${settings.get_double('broken-glass-blow-force')};
const float ACTOR_SCALE = 2.0;
const float PADDING = ACTOR_SCALE / 2.0 - 0.5;
const vec2 EPICENTER = vec2(${epicenterX}, ${epicenterY});
const float GRAVITY = ${forOpening ? '-1.0' : '1.0'} *
${settings.get_double('broken-glass-gravity')};
`;
void main() {
const code = `
cogl_color_out = vec4(0, 0, 0, 0);
cogl_color_out = vec4(0, 0, 0, 0);
float progress = uForOpening ? 1.0-uProgress : uProgress;
float progress = ${forOpening ? '1.0-uProgress' : 'uProgress'};
// Draw the individual shard layers.
for (float i=0; i<SHARD_LAYERS; ++i) {
// Draw the individual shard layers.
for (float i=0; i<SHARD_LAYERS; ++i) {
// To enable drawing shards outside of the window bounds, the actor was scaled
// by ACTOR_SCALE. Here we scale and move the texture coordinates so that the
// window gets drawn at the correct position again.
vec2 coords = cogl_tex_coord_in[0].st * ACTOR_SCALE - PADDING;
// To enable drawing shards outside of the window bounds, the actor was scaled
// by ACTOR_SCALE. Here we scale and move the texture coordinates so that the
// window gets drawn at the correct position again.
vec2 coords = cogl_tex_coord_in[0].st * ACTOR_SCALE - PADDING;
// Scale and rotate around our epicenter.
coords -= uEpicenter;
// Scale and rotate around our epicenter.
coords -= EPICENTER;
// Scale each layer a bit differently.
coords /= mix(1.0, 1.0 + uBlowForce*(i+2)/SHARD_LAYERS, progress);
// Rotate each layer a bit differently.
float rotation = (mod(i, 2.0)-0.5)*0.2*progress;
coords = vec2(coords.x * cos(rotation) - coords.y * sin(rotation),
coords.x * sin(rotation) + coords.y * cos(rotation));
// Move down each layer a bit.
float gravity = (uForOpening ? -1.0 : 1.0) * uGravity*0.1*(i+1)*progress*progress;
coords += vec2(0, gravity);
// Scale each layer a bit differently.
coords /= mix(1.0, 1.0 + BLOW_FORCE*(i+2)/SHARD_LAYERS, progress);
// Rotate each layer a bit differently.
float rotation = (mod(i, 2.0)-0.5)*0.2*progress;
coords = vec2(coords.x * cos(rotation) - coords.y * sin(rotation),
coords.x * sin(rotation) + coords.y * cos(rotation));
// Move down each layer a bit.
float gravity = GRAVITY*0.1*(i+1)*progress*progress;
coords += vec2(0, gravity);
// Restore correct position.
coords += uEpicenter;
// Restore correct position.
coords += EPICENTER;
// Retrieve information from the shard texture for our layer.
vec2 shardCoords = (coords + SEED) * vec2(uSizeX, uSizeY) / SHARD_SCALE / 500.0;
vec2 shardMap = texture2D(uShardTexture, shardCoords).rg;
// Retrieve information from the shard texture for our layer.
vec2 shardCoords = (coords + uSeed) * vec2(uSizeX, uSizeY) / uShardScale / 500.0;
vec2 shardMap = texture2D(uShardTexture, shardCoords).rg;
// The green channel contains a random value in [0..1] for each shard. We
// discretize this into SHARD_LAYERS bins and check if our layer falls into
// the bin of the current shard.
float shardGroup = floor(shardMap.g * SHARD_LAYERS * 0.999);
// The green channel contains a random value in [0..1] for each shard. We
// discretize this into SHARD_LAYERS bins and check if our layer falls into
// the bin of the current shard.
float shardGroup = floor(shardMap.g * SHARD_LAYERS * 0.999);
if (shardGroup == i) {
vec4 windowColor = texture2D(uTexture, coords);
if (shardGroup == i) {
vec4 windowColor = texture2D(uTexture, coords);
// Dissolve the shard by using distance information from the red channel.
float dissolve = (shardMap.x - pow(progress+0.1, 2)) > 0 ? 1: 0;
cogl_color_out = mix(cogl_color_out, windowColor, dissolve);
}
// Dissolve the shard by using distance information from the red channel.
float dissolve = (shardMap.x - pow(progress+0.1, 2)) > 0 ? 1 : 0;
cogl_color_out = mix(cogl_color_out, windowColor, dissolve);
}
}
`);
`;
this.set_uniform_value('uShardTexture', 1);
};
this.add_glsl_snippet(Shell.SnippetHook.FRAGMENT, declarations, code, true);
}
// This is overridden to bind the shard texture for drawing. Sadly, this seems to be
// impossible under GNOME 3.3x as this.get_pipeline() is not available. It was called
@@ -240,8 +298,9 @@ if (utils.isInShellProcess()) {
Cogl.PipelineFilter.LINEAR);
// Bind the shard texture.
pipeline.set_layer_texture(1, this._shardTexture.get_texture());
pipeline.set_layer_texture(1, shardTexture.get_texture());
pipeline.set_layer_wrap_mode(1, Cogl.PipelineWrapMode.REPEAT);
pipeline.set_uniform_1i(this._uShardTexture, 1);
super.vfunc_paint_target(node, paint_context);
}
+3 -3
View File
@@ -114,7 +114,7 @@ var Fire = class Fire {
shader = freeShaders.pop();
}
shader.setUniforms(settings, forOpening);
shader.setUniforms(actor, settings, forOpening);
return shader;
}
@@ -267,7 +267,7 @@ if (utils.isInShellProcess()) {
// This is called each time the effect is used. This can be used to retrieve the
// configuration from the settings and update all uniforms accordingly.
setUniforms(settings, forOpening) {
setUniforms(actor, settings, forOpening) {
// Load the gradient values from the settings.
for (let i = 1; i <= 5; i++) {
@@ -408,6 +408,6 @@ if (utils.isInShellProcess()) {
`;
this.add_glsl_snippet(Shell.SnippetHook.FRAGMENT, declarations, code, true);
};
}
});
}
+3 -3
View File
@@ -96,7 +96,7 @@ var Hexagon = class Hexagon {
shader = freeShaders.pop();
}
shader.setUniforms(settings, forOpening);
shader.setUniforms(actor, settings, forOpening);
return shader;
}
@@ -155,7 +155,7 @@ if (utils.isInShellProcess()) {
// This is called each time the effect is used. This can be used to retrieve the
// configuration from the settings and update all uniforms accordingly.
setUniforms(settings, forOpening) {
setUniforms(actor, settings, forOpening) {
// Get the two configurable colors. They are directly injected into the shader code
// below.
@@ -304,6 +304,6 @@ if (utils.isInShellProcess()) {
`;
this.add_glsl_snippet(Shell.SnippetHook.FRAGMENT, declarations, code, true);
};
}
});
}
+123 -67
View File
@@ -29,8 +29,16 @@ const utils = Me.imports.src.utils;
// documentation of vfunc_paint_target further down in this file. //
//////////////////////////////////////////////////////////////////////////////////////////
// The shader class for this effect is registered further down in this file.
let Shader = null;
// The shader class for this effect is registered further down in this file. When this
// effect is used for the first time, an instance of this shader class is created. Once
// the effect is finished, the shader will be stored in the freeShaders array and will
// then be reused if a new shader is requested. ShaderClass which will be used whenever
// this effect is used.
let ShaderClass = null;
let freeShaders = [];
// This texture will be loaded when the effect is used for the first time.
let fontTexture = null;
// The effect class is completely static. It can be used to get some metadata (like the
// effect's name or supported GNOME Shell versions), to initialize the respective page of
@@ -82,9 +90,21 @@ var Matrix = class Matrix {
// ---------------------------------------------------------------- API for extension.js
// This is called from extension.js whenever a window is closed with this effect.
static createShader(actor, settings, forOpening) {
return new Shader(settings, forOpening);
// This is called from extension.js whenever a window is opened or closed with this
// effect. It returns an instance of the shader class, trying to reuse previously
// created shaders.
static getShader(actor, settings, forOpening) {
let shader;
if (freeShaders.length == 0) {
shader = new ShaderClass();
} else {
shader = freeShaders.pop();
}
shader.setUniforms(actor, settings, forOpening);
return shader;
}
// The tweakTransition() is called from extension.js to tweak a window's open / close
@@ -106,6 +126,13 @@ var Matrix = class Matrix {
'scale-y': {from: yScale, to: yScale, mode: 1}
};
}
// This is called from extension.js if the extension is disabled. This should free all
// static resources.
static cleanUp() {
freeShaders = [];
fontTexture = null;
}
}
@@ -117,38 +144,74 @@ var Matrix = class Matrix {
if (utils.isInShellProcess()) {
const {Clutter, GdkPixbuf, Cogl} = imports.gi;
const shaderSnippets = Me.imports.src.shaderSnippets;
const {Clutter, GdkPixbuf, Cogl, Shell} = imports.gi;
const shaderSnippets = Me.imports.src.shaderSnippets;
Shader = GObject.registerClass({}, class Shader extends Clutter.ShaderEffect {
_init(settings, forOpening) {
super._init({shader_type: Clutter.ShaderType.FRAGMENT_SHADER});
ShaderClass = GObject.registerClass({}, class ShaderClass extends Shell.GLSLEffect {
// This is called when the effect is used for the first time. This can be used to
// store all required uniform locations.
_init() {
super._init();
// Load the font texture. As the shader is re-created for each window animation,
// this texture is also re-created each time. This could be improved in the future!
const fontData = GdkPixbuf.Pixbuf.new_from_resource('/img/matrixFont.png');
this._fontTexture = new Clutter.Image();
this._fontTexture.set_data(
fontData.get_pixels(),
fontData.has_alpha ? Cogl.PixelFormat.RGBA_8888 : Cogl.PixelFormat.RGB_888,
fontData.width, fontData.height, fontData.rowstride);
// Load the font texture.
if (fontTexture == null) {
const fontData = GdkPixbuf.Pixbuf.new_from_resource('/img/matrixFont.png');
fontTexture = new Clutter.Image();
fontTexture.set_data(
fontData.get_pixels(),
fontData.has_alpha ? Cogl.PixelFormat.RGBA_8888 : Cogl.PixelFormat.RGB_888,
fontData.width, fontData.height, fontData.rowstride);
}
const trailColor =
Clutter.Color.from_string(settings.get_string('matrix-trail-color'))[1];
const tipColor =
Clutter.Color.from_string(settings.get_string('matrix-tip-color'))[1];
this._uForOpening = this.get_uniform_location('uForOpening');
this._uFontTexture = this.get_uniform_location('uFontTexture');
this._uTrailColor = this.get_uniform_location('uTrailColor');
this._uTipColor = this.get_uniform_location('uTipColor');
this._uLetterSize = this.get_uniform_location('uLetterSize');
this._uRandomness = this.get_uniform_location('uRandomness');
this._uOverShoot = this.get_uniform_location('uOverShoot');
}
// The technique for this effect was inspired by
// https://www.shadertoy.com/view/ldccW4, however the implementation is quite
// different as the letters drop only once and there is no need for a noise texture.
this.set_shader_source(`
// This is called each time the effect is used. This can be used to retrieve the
// configuration from the settings and update all uniforms accordingly.
setUniforms(actor, settings, forOpening) {
const c1 = Clutter.Color.from_string(settings.get_string('matrix-trail-color'))[1];
const c2 = Clutter.Color.from_string(settings.get_string('matrix-tip-color'))[1];
// clang-format off
this.set_uniform_float(this._uForOpening, 1, [forOpening]);
this.set_uniform_float(this._uTrailColor, 3, [c1.red / 255, c1.green / 255, c1.blue / 255]);
this.set_uniform_float(this._uTipColor, 3, [c2.red / 255, c2.green / 255, c2.blue / 255]);
this.set_uniform_float(this._uLetterSize, 1, [settings.get_int('matrix-scale')]);
this.set_uniform_float(this._uRandomness, 1, [settings.get_double('matrix-randomness')]);
this.set_uniform_float(this._uOverShoot, 1, [settings.get_double('matrix-overshoot')]);
// clang-format on
}
// This is called by extension.js when the shader is not used anymore. We will store
// this instance of the shader so that it can be re-used in th future.
free() {
freeShaders.push(this);
}
// This is called by the constructor. This is means it's only called when the effect
// is used for the first time. The technique for this effect was inspired by
// https://www.shadertoy.com/view/ldccW4, however the implementation is quite
// different as the letters drop only once and there is no need for a noise texture.
vfunc_build_pipeline() {
const declarations = `
// Inject some common shader snippets.
${shaderSnippets.standardUniforms()}
${shaderSnippets.noise()}
${shaderSnippets.edgeMask()}
uniform bool uForOpening;
uniform sampler2D uFontTexture;
uniform vec3 uTrailColor;
uniform vec3 uTipColor;
uniform float uLetterSize;
uniform float uRandomness;
uniform float uOverShoot;
// These may be configurable in the future.
const float EDGE_FADE = 30;
@@ -156,16 +219,13 @@ if (utils.isInShellProcess()) {
const float TRAIL_LENGTH = 0.2;
const float FINAL_FADE_START_TIME = 0.8;
const float LETTER_TILES = 16.0;
const float LETTER_SIZE = ${settings.get_int('matrix-scale')};
const float LETTER_FLICKER_SPEED = 2.0;
const float RANDOMNESS = ${settings.get_double('matrix-randomness')};
const float OVERSHOOT = ${settings.get_double('matrix-overshoot')};
// This returns a flickering grid of random letters.
float getText(vec2 fragCoord) {
vec2 pixelCoords = fragCoord * vec2(uSizeX, uSizeY);
vec2 uv = mod(pixelCoords.xy, LETTER_SIZE)/LETTER_SIZE;
vec2 block = pixelCoords/LETTER_SIZE - uv;
vec2 uv = mod(pixelCoords.xy, uLetterSize)/uLetterSize;
vec2 block = pixelCoords/uLetterSize - uv;
// Choose random letter.
uv += floor(hash22(floor(hash22(block)*vec2(12.9898,78.233) + LETTER_FLICKER_SPEED*uTime + 42.254))*LETTER_TILES);
@@ -179,10 +239,10 @@ if (utils.isInShellProcess()) {
// the window texture.
vec2 getRain(vec2 fragCoord) {
float column = cogl_tex_coord_in[0].x * uSizeX;
column -= mod(column, LETTER_SIZE);
column -= mod(column, uLetterSize);
float delay = fract(sin(column)*78.233) * mix(0.0, 1.0, RANDOMNESS);
float speed = fract(cos(column)*12.989) * mix(0.0, 0.3, RANDOMNESS) + 1.5;
float delay = fract(sin(column)*78.233) * mix(0.0, 1.0, uRandomness);
float speed = fract(cos(column)*12.989) * mix(0.0, 0.3, uRandomness) + 1.5;
float distToDrop = (uProgress*2-delay)*speed - cogl_tex_coord_in[0].y;
@@ -193,59 +253,55 @@ if (utils.isInShellProcess()) {
rainAlpha *= getAbsoluteEdgeMask(EDGE_FADE);
// Add some variation to the drop start and end position.
float shorten = fract(sin(column+42.0)*33.423) * mix(0.0, OVERSHOOT*0.25, RANDOMNESS);
float shorten = fract(sin(column+42.0)*33.423) * mix(0.0, uOverShoot*0.25, uRandomness);
rainAlpha *= smoothstep(0, 1, clamp(cogl_tex_coord_in[0].y / shorten, 0, 1));
rainAlpha *= smoothstep(0, 1, clamp((1.0 - cogl_tex_coord_in[0].y) / shorten, 0, 1));
#if ${forOpening ? '1' : '0'}
if (uForOpening) {
windowAlpha = 1.0 - windowAlpha;
#endif
}
return vec2(rainAlpha, windowAlpha);
}
`;
void main() {
const code = `
vec2 coords = cogl_tex_coord_in[0].st;
coords.y = coords.y * (uOverShoot + 1.0) - uOverShoot * 0.5;
vec2 coords = cogl_tex_coord_in[0].st;
coords.y = coords.y * (OVERSHOOT + 1.0) - OVERSHOOT * 0.5;
// Get a cool matrix effect. See comments for those methods above.
vec2 rain = getRain(coords);
float text = getText(coords);
// Get a cool matrix effect. See comments for those methods above.
vec2 rain = getRain(coords);
float text = getText(coords);
// Get the window texture and fade it according to the effect mask.
cogl_color_out = texture2D(uTexture, coords) * rain.y;
// Get the window texture and fade it according to the effect mask.
cogl_color_out = texture2D(uTexture, coords) * rain.y;
// This is used to fade out the remaining trails in the end.
float finalFade = 1-clamp((uProgress-FINAL_FADE_START_TIME)/
(1-FINAL_FADE_START_TIME), 0, 1);
float rainAlpha = finalFade * rain.x;
// This is used to fade out the remaining trails in the end.
float finalFade = 1-clamp((uProgress-FINAL_FADE_START_TIME)/
(1-FINAL_FADE_START_TIME), 0, 1);
float rainAlpha = finalFade * rain.x;
// Add the matrix effect to the window.
cogl_color_out.rgb += mix(uTrailColor, uTipColor, min(1, pow(rainAlpha+0.1, 4))) * rainAlpha * text;
// Add the matrix effect to the window.
vec3 trailColor = vec3(${trailColor.red / 255},
${trailColor.green / 255},
${trailColor.blue / 255});
vec3 tipColor = vec3(${tipColor.red / 255},
${tipColor.green / 255},
${tipColor.blue / 255});
cogl_color_out.rgb += mix(trailColor, tipColor, min(1, pow(rainAlpha+0.1, 4))) * rainAlpha * text;
// These are pretty useful for understanding how this works.
// cogl_color_out = vec4(vec3(text), 1);
// cogl_color_out = vec4(vec3(rain.x), 1);
// cogl_color_out = vec4(vec3(rain.y), 1);
`;
// These are pretty useful for understanding how this works.
// cogl_color_out = vec4(vec3(text), 1);
// cogl_color_out = vec4(vec3(rain.x), 1);
// cogl_color_out = vec4(vec3(rain.y), 1);
}
`);
this.set_uniform_value('uFontTexture', 1);
};
this.add_glsl_snippet(Shell.SnippetHook.FRAGMENT, declarations, code, true);
}
// This is overridden to bind the font texture for drawing. Sadly, this seems to be
// impossible under GNOME 3.3x as this.get_pipeline() is not available. It was called
// get_target() back then but this is not wrapped in GJS.
// https://gitlab.gnome.org/GNOME/mutter/-/blob/gnome-3-36/clutter/clutter/clutter-offscreen-effect.c#L598
vfunc_paint_target(node, paint_context) {
this.get_pipeline().set_layer_texture(1, this._fontTexture.get_texture());
const pipeline = this.get_pipeline();
pipeline.set_layer_texture(1, fontTexture.get_texture());
pipeline.set_uniform_1i(this._uFontTexture, 1);
super.vfunc_paint_target(node, paint_context);
}
});
+138 -82
View File
@@ -31,8 +31,16 @@ const utils = Me.imports.src.utils;
// documentation of vfunc_paint_target further down in this file. //
//////////////////////////////////////////////////////////////////////////////////////////
// The shader class for this effect is registered further down in this file.
let Shader = null;
// The shader class for this effect is registered further down in this file. When this
// effect is used for the first time, an instance of this shader class is created. Once
// the effect is finished, the shader will be stored in the freeShaders array and will
// then be reused if a new shader is requested. ShaderClass which will be used whenever
// this effect is used.
let ShaderClass = null;
let freeShaders = [];
// This texture will be loaded when the effect is used for the first time.
let dustTexture = null;
// The effect class is completely static. It can be used to get some metadata (like the
// effect's name or supported GNOME Shell versions), to initialize the respective page of
@@ -81,9 +89,21 @@ var SnapOfDisintegration = class SnapOfDisintegration {
// ---------------------------------------------------------------- API for extension.js
// This is called from extension.js whenever a window is closed with this effect.
static createShader(actor, settings, forOpening) {
return new Shader(actor, settings, forOpening);
// This is called from extension.js whenever a window is opened or closed with this
// effect. It returns an instance of the shader class, trying to reuse previously
// created shaders.
static getShader(actor, settings, forOpening) {
let shader;
if (freeShaders.length == 0) {
shader = new ShaderClass();
} else {
shader = freeShaders.pop();
}
shader.setUniforms(actor, settings, forOpening);
return shader;
}
// The tweakTransition() is called from extension.js to tweak a window's open / close
@@ -104,6 +124,13 @@ var SnapOfDisintegration = class SnapOfDisintegration {
'scale-y': {from: 1.2, to: 1.2, mode: 1}
};
}
// This is called from extension.js if the extension is disabled. This should free all
// static resources.
static cleanUp() {
freeShaders = [];
dustTexture = null;
}
}
@@ -115,118 +142,146 @@ var SnapOfDisintegration = class SnapOfDisintegration {
if (utils.isInShellProcess()) {
const {Clutter, GdkPixbuf, Cogl} = imports.gi;
const shaderSnippets = Me.imports.src.shaderSnippets;
const {Clutter, GdkPixbuf, Cogl, Shell} = imports.gi;
const shaderSnippets = Me.imports.src.shaderSnippets;
Shader = GObject.registerClass({}, class Shader extends Clutter.ShaderEffect {
_init(actor, settings, forOpening) {
super._init({shader_type: Clutter.ShaderType.FRAGMENT_SHADER});
ShaderClass = GObject.registerClass({}, class ShaderClass extends Shell.GLSLEffect {
// This is called when the effect is used for the first time. This can be used to
// store all required uniform locations.
_init() {
super._init();
// Load the dust texture. As the shader is re-created for each window animation,
// this texture is also re-created each time. This could be improved in the future!
const dustData = GdkPixbuf.Pixbuf.new_from_resource('/img/dust.png');
this._dustTexture = new Clutter.Image();
this._dustTexture.set_data(dustData.get_pixels(), Cogl.PixelFormat.RGB_888,
dustData.width, dustData.height, dustData.rowstride);
// Load the dust texture.
if (dustTexture == null) {
const dustData = GdkPixbuf.Pixbuf.new_from_resource('/img/dust.png');
dustTexture = new Clutter.Image();
dustTexture.set_data(dustData.get_pixels(), Cogl.PixelFormat.RGB_888,
dustData.width, dustData.height, dustData.rowstride);
}
this._uForOpening = this.get_uniform_location('uForOpening');
this._uDustTexture = this.get_uniform_location('uDustTexture');
this._uDustColor = this.get_uniform_location('uDustColor');
this._uSeed = this.get_uniform_location('uSeed');
this._uDustScale = this.get_uniform_location('uDustScale');
}
// This is called each time the effect is used. This can be used to retrieve the
// configuration from the settings and update all uniforms accordingly.
setUniforms(actor, settings, forOpening) {
// The dust particles will fade to this color over time.
const color = Clutter.Color.from_string(settings.get_string('snap-color'))[1];
const c = Clutter.Color.from_string(settings.get_string('snap-color'))[1];
// If we are currently performing integration test, the animation uses a fixed seed.
const testMode = settings.get_boolean('test-mode');
this.set_shader_source(`
// clang-format off
this.set_uniform_float(this._uForOpening, 1, [forOpening]);
this.set_uniform_float(this._uDustColor, 4, [c.red / 255, c.green / 255, c.blue / 255, c.alpha / 255]);
this.set_uniform_float(this._uSeed, 2, [testMode ? 0 : Math.random(), testMode ? 0 : Math.random()]);
this.set_uniform_float(this._uDustScale, 1, [settings.get_double('snap-scale')]);
// clang-format on
}
// This is called by extension.js when the shader is not used anymore. We will store
// this instance of the shader so that it can be re-used in th future.
free() {
freeShaders.push(this);
}
// This is called by the constructor. This is means it's only called when the effect
// is used for the first time.
vfunc_build_pipeline() {
const declarations = `
// Inject some common shader snippets.
${shaderSnippets.standardUniforms()}
${shaderSnippets.noise()}
${shaderSnippets.math2D()}
uniform bool uForOpening;
uniform sampler2D uDustTexture;
uniform vec4 uDustColor;
uniform vec2 uSeed;
uniform float uDustScale;
const vec2 SEED = vec2(${testMode ? 0 : Math.random()},
${testMode ? 0 : Math.random()});
const float DUST_SCALE = ${settings.get_double('snap-scale')};
const float DUST_LAYERS = 4;
const float GROW_INTENSITY = 0.05;
const float SHRINK_INTENSITY = 0.05;
const float WIND_INTENSITY = 0.05;
const float ACTOR_SCALE = 1.2;
const float PADDING = ACTOR_SCALE / 2.0 - 0.5;
const vec4 DUST_COLOR = vec4(${color.red / 255}, ${color.green / 255},
${color.blue / 255}, ${color.alpha / 255});
void main() {
`;
// We simply inverse the progress for opening windows.
float progress = ${forOpening ? 'uProgress' : '1.0 - uProgress'};
const code = `
// We simply inverse the progress for opening windows.
float progress = uForOpening ? uProgress : 1.0 - uProgress;
float gradient = cogl_tex_coord_in[0].t * ACTOR_SCALE - PADDING;
progress = 2.0 - gradient - 2.0 * progress;
progress = progress + 0.25 - 0.5 * simplex2D((cogl_tex_coord_in[0].st + SEED) * 2.0);
progress = pow(max(0, progress), 2.0);
float gradient = cogl_tex_coord_in[0].t * ACTOR_SCALE - PADDING;
progress = 2.0 - gradient - 2.0 * progress;
progress = progress + 0.25 - 0.5 * simplex2D((cogl_tex_coord_in[0].st + uSeed) * 2.0);
progress = pow(max(0, progress), 2.0);
// This may help you to understand how this effect works.
// cogl_color_out = vec4(progress, 0, 0, 0);
// return;
// This may help you to understand how this effect works.
// cogl_color_out = vec4(progress, 0, 0, 0);
// return;
cogl_color_out = vec4(0, 0, 0, 0);
cogl_color_out = vec4(0, 0, 0, 0);
for (float i=0; i<DUST_LAYERS; ++i) {
// Create a random direction.
float factor = DUST_LAYERS == 1 ? 0 : i/(DUST_LAYERS-1);
float angle = 123.123 * (SEED.x + factor);
vec2 direction = vec2(1.0, 0.0);
direction = rotate(direction, angle);
// Flip direction for one side of the window.
vec2 coords = cogl_tex_coord_in[0].st * ACTOR_SCALE - PADDING - 0.5;
if (getWinding(direction, coords) > 0) {
direction *= -1;
}
for (float i=0; i<DUST_LAYERS; ++i) {
// Create a random direction.
float factor = DUST_LAYERS == 1 ? 0 : i/(DUST_LAYERS-1);
float angle = 123.123 * (uSeed.x + factor);
vec2 direction = vec2(1.0, 0.0);
direction = rotate(direction, angle);
// Flip direction for one side of the window.
vec2 coords = cogl_tex_coord_in[0].st * ACTOR_SCALE - PADDING - 0.5;
if (getWinding(direction, coords) > 0) {
direction *= -1;
}
// Flip direction for half the layers.
if (factor > 0.5) {
direction *= -1;
}
// We grow the layer along the random direction, shrink it orthogonally to it
// and scale it up slightly.
float dist = distToLine(vec2(0.0), direction, coords);
vec2 grow = direction * dist * mix(0, GROW_INTENSITY, progress);
vec2 shrink = vec2(direction.y, -direction.x) * dist * mix(0, SHRINK_INTENSITY, progress);
float scale = mix(1.0, 1.05, factor * progress);
coords = (coords + grow + shrink) / scale;
// Flip direction for half the layers.
if (factor > 0.5) {
direction *= -1;
}
// We grow the layer along the random direction, shrink it orthogonally to it
// and scale it up slightly.
float dist = distToLine(vec2(0.0), direction, coords);
vec2 grow = direction * dist * mix(0, GROW_INTENSITY, progress);
vec2 shrink = vec2(direction.y, -direction.x) * dist * mix(0, SHRINK_INTENSITY, progress);
float scale = mix(1.0, 1.05, factor * progress);
coords = (coords + grow + shrink) / scale;
// Add some wind.
coords.x = coords.x ${forOpening ? ' + ' : ' - '} progress * WIND_INTENSITY;
// Now check wether there is actually something in the current dust layer at
// the coords position.
vec2 dustCoords = (coords + SEED) * vec2(uSizeX, uSizeY) / DUST_SCALE / 100.0;
vec2 dustMap = texture2D(uDustTexture, dustCoords).rg;
float dustGroup = floor(dustMap.g * DUST_LAYERS * 0.999);
// Add some wind.
coords.x = WIND_INTENSITY * coords.x * progress * (uForOpening ? 1.0 : -1.0);
// Now check wether there is actually something in the current dust layer at
// the coords position.
vec2 dustCoords = (coords + uSeed) * vec2(uSizeX, uSizeY) / uDustScale / 100.0;
vec2 dustMap = texture2D(uDustTexture, dustCoords).rg;
float dustGroup = floor(dustMap.g * DUST_LAYERS * 0.999);
if (dustGroup == i) {
if (dustGroup == i) {
// Fade the window color to DUST_COLOR.
vec4 windowColor = texture2D(uTexture, coords + 0.5);
vec3 dustColor = mix(windowColor.rgb, DUST_COLOR.rgb, DUST_COLOR.a);
windowColor.rgb = mix(windowColor.rgb, dustColor*windowColor.a, progress);
// Fade the window color to uDustColor.
vec4 windowColor = texture2D(uTexture, coords + 0.5);
vec3 dustColor = mix(windowColor.rgb, uDustColor.rgb, uDustColor.a);
windowColor.rgb = mix(windowColor.rgb, dustColor*windowColor.a, progress);
// Dissolve the dust particles.
float dissolve = (dustMap.x - progress) > 0 ? 1 : 0;
windowColor *= dissolve;
// Dissolve the dust particles.
float dissolve = (dustMap.x - progress) > 0 ? 1 : 0;
windowColor *= dissolve;
// Blend the layers.
cogl_color_out = mix(cogl_color_out, windowColor, windowColor.a);
}
// Blend the layers.
cogl_color_out = mix(cogl_color_out, windowColor, windowColor.a);
}
}
`);
`;
this.set_uniform_value('uDustTexture', 1);
};
this.add_glsl_snippet(Shell.SnippetHook.FRAGMENT, declarations, code, true);
}
// This is overridden to bind the dust texture for drawing. Sadly, this seems to be
// impossible under GNOME 3.3x as this.get_pipeline() is not available. It was called
@@ -236,8 +291,9 @@ if (utils.isInShellProcess()) {
const pipeline = this.get_pipeline();
pipeline.set_layer_filters(0, Cogl.PipelineFilter.LINEAR,
Cogl.PipelineFilter.LINEAR);
pipeline.set_layer_texture(1, this._dustTexture.get_texture());
pipeline.set_layer_texture(1, dustTexture.get_texture());
pipeline.set_layer_wrap_mode(1, Cogl.PipelineWrapMode.REPEAT);
pipeline.set_uniform_1i(this._uDustTexture, 1);
super.vfunc_paint_target(node, paint_context);
}
});
+135 -78
View File
@@ -27,8 +27,16 @@ const utils = Me.imports.src.utils;
// documentation of vfunc_paint_target further down in this file. //
//////////////////////////////////////////////////////////////////////////////////////////
// The shader class for this effect is registered further down in this file.
let Shader = null;
// The shader class for this effect is registered further down in this file. When this
// effect is used for the first time, an instance of this shader class is created. Once
// the effect is finished, the shader will be stored in the freeShaders array and will
// then be reused if a new shader is requested. ShaderClass which will be used whenever
// this effect is used.
let ShaderClass = null;
let freeShaders = [];
// This texture will be loaded when the effect is used for the first time.
let clawTexture = null;
// The effect class is completely static. It can be used to get some metadata (like the
// effect's name or supported GNOME Shell versions), to initialize the respective page of
@@ -79,11 +87,22 @@ var TRexAttack = class TRexAttack {
// ---------------------------------------------------------------- API for extension.js
// This is called from extension.js whenever a window is closed with this effect.
static createShader(actor, settings, forOpening) {
return new Shader(settings, forOpening);
}
// This is called from extension.js whenever a window is opened or closed with this
// effect. It returns an instance of the shader class, trying to reuse previously
// created shaders.
static getShader(actor, settings, forOpening) {
let shader;
if (freeShaders.length == 0) {
shader = new ShaderClass();
} else {
shader = freeShaders.pop();
}
shader.setUniforms(actor, settings, forOpening);
return shader;
}
// The tweakTransition() is called from extension.js to tweak a window's open / close
// transitions - usually windows are faded in / out and scaled up / down by GNOME Shell.
// The parameter 'forOpening' is set to true if this is called for a window-open
@@ -102,6 +121,13 @@ var TRexAttack = class TRexAttack {
'scale-y': {from: forOpening ? warp : 1.0, to: forOpening ? 1.0 : warp, mode: 3}
};
}
// This is called from extension.js if the extension is disabled. This should free all
// static resources.
static cleanUp() {
freeShaders = [];
clawTexture = null;
}
}
@@ -113,41 +139,73 @@ var TRexAttack = class TRexAttack {
if (utils.isInShellProcess()) {
const {Clutter, GdkPixbuf, Cogl} = imports.gi;
const shaderSnippets = Me.imports.src.shaderSnippets;
const {Clutter, GdkPixbuf, Cogl, Shell} = imports.gi;
const shaderSnippets = Me.imports.src.shaderSnippets;
Shader = GObject.registerClass({}, class Shader extends Clutter.ShaderEffect {
_init(settings, forOpening) {
super._init({shader_type: Clutter.ShaderType.FRAGMENT_SHADER});
ShaderClass = GObject.registerClass({}, class ShaderClass extends Shell.GLSLEffect {
// This is called when the effect is used for the first time. This can be used to
// store all required uniform locations.
_init() {
super._init();
// Load the claw texture. As the shader is re-created for each window animation,
// this texture is also re-created each time. This could be improved in the future!
// See assets/README.md for how this texture was created.
const clawData = GdkPixbuf.Pixbuf.new_from_resource('/img/claws.png');
this._clawTexture = new Clutter.Image();
this._clawTexture.set_data(clawData.get_pixels(), Cogl.PixelFormat.RGB_888,
clawData.width, clawData.height, clawData.rowstride);
// Load the claw texture.
if (clawTexture == null) {
const clawData = GdkPixbuf.Pixbuf.new_from_resource('/img/claws.png');
clawTexture = new Clutter.Image();
clawTexture.set_data(clawData.get_pixels(), Cogl.PixelFormat.RGB_888,
clawData.width, clawData.height, clawData.rowstride);
}
const color =
Clutter.Color.from_string(settings.get_string('claw-scratch-color'))[1];
this._uForOpening = this.get_uniform_location('uForOpening');
this._uClawTexture = this.get_uniform_location('uClawTexture');
this._uFlashColor = this.get_uniform_location('uFlashColor');
this._uSeed = this.get_uniform_location('uSeed');
this._uClawSize = this.get_uniform_location('uClawSize');
this._uNumClaws = this.get_uniform_location('uNumClaws');
this._uWarpIntensity = this.get_uniform_location('uWarpIntensity');
}
// This is called each time the effect is used. This can be used to retrieve the
// configuration from the settings and update all uniforms accordingly.
setUniforms(actor, settings, forOpening) {
const c = Clutter.Color.from_string(settings.get_string('claw-scratch-color'))[1];
// If we are currently performing integration test, the animation uses a fixed seed.
const testMode = settings.get_boolean('test-mode');
this.set_shader_source(`
// clang-format off
this.set_uniform_float(this._uForOpening, 1, [forOpening]);
this.set_uniform_float(this._uFlashColor, 4, [c.red / 255, c.green / 255, c.blue / 255, c.alpha / 255]);
this.set_uniform_float(this._uSeed, 2, [testMode ? 0 : Math.random(), testMode ? 0 : Math.random()]);
this.set_uniform_float(this._uClawSize, 1, [settings.get_double('claw-scratch-scale')]);
this.set_uniform_float(this._uNumClaws, 1, [settings.get_int('claw-scratch-count')]);
this.set_uniform_float(this._uWarpIntensity, 1, [settings.get_double('claw-scratch-warp')]);
// clang-format on
}
// This is called by extension.js when the shader is not used anymore. We will store
// this instance of the shader so that it can be re-used in th future.
free() {
freeShaders.push(this);
}
// This is called by the constructor. This is means it's only called when the effect
// is used for the first time.
vfunc_build_pipeline() {
const declarations = `
// Inject some common shader snippets.
${shaderSnippets.standardUniforms()}
${shaderSnippets.noise()}
// See assets/README.md for how this texture was created.
uniform bool uForOpening;
uniform sampler2D uClawTexture;
uniform vec4 uFlashColor;
uniform vec2 uSeed;
uniform float uClawSize;
uniform float uNumClaws;
uniform float uWarpIntensity;
const vec2 SEED = vec2(${testMode ? 0 : Math.random()},
${testMode ? 0 : Math.random()});
const float CLAW_SIZE = ${settings.get_double('claw-scratch-scale')};
const float NUM_CLAWS = ${settings.get_int('claw-scratch-count')};
const float WARP_INTENSITY = 1.0 + ${settings.get_double('claw-scratch-warp')};
const float FLASH_INTENSITY = 0.1;
const float MAX_SPAWN_TIME = 0.6; // Scratches will only start in the first half of the animation.
const float FF_TIME = 0.6; // Relative time for the final fade to transparency.
@@ -188,69 +246,68 @@ if (utils.isInShellProcess()) {
// Clamp resulting coordinates.
return clamp(cellUV, vec2(0), vec2(1));
}
`;
void main() {
const code = `
float progress = uForOpening ? 1.0-uProgress : uProgress;
float progress = ${forOpening ? '1.0-uProgress' : 'uProgress'};
// Warp the texture coordinates to create a blow-up effect.
vec2 coords = cogl_tex_coord_in[0].st * 2.0 - 1.0;
float dist = length(coords);
coords = (coords/dist * pow(dist, 1.0 + uWarpIntensity)) * 0.5 + 0.5;
coords = mix(cogl_tex_coord_in[0].st, coords, progress);
// Warp the texture coordinates to create a blow-up effect.
vec2 coords = cogl_tex_coord_in[0].st * 2.0 - 1.0;
float dist = length(coords);
coords = (coords/dist * pow(dist, WARP_INTENSITY)) * 0.5 + 0.5;
coords = mix(cogl_tex_coord_in[0].st, coords, progress);
// Accumulate several random scratches. The color in the scratch map refers to the
// relative time when the respective part will become invisible. Therefore we can
// add a value to make the scratch appear later.
float scratchMap = 1.0;
for (int i=0; i<NUM_CLAWS; ++i) {
vec2 uv = getClawUV(coords, 1.0/CLAW_SIZE, SEED*(i+1));
float delay = i/NUM_CLAWS * MAX_SPAWN_TIME;
scratchMap = min(scratchMap, clamp(texture2D(uClawTexture, uv).r + delay, 0, 1));
}
// Get the window texture. We shift the texture lookup by the local derivative of
// the claw texture in order to mimic some folding distortion.
vec2 offset = vec2(dFdx(scratchMap), dFdy(scratchMap)) * progress * 0.5;
cogl_color_out = texture2D(uTexture, coords + offset);
// Add colorful flashes.
float flashIntensity = 1.0 / FLASH_INTENSITY * (scratchMap - progress) + 1;
if (flashIntensity < 0 || flashIntensity >= 1) {
flashIntensity = 0;
}
// Hide flashes where there is now window.
flashIntensity *= cogl_color_out.a;
// Hide scratched out parts.
cogl_color_out *= (scratchMap > progress ? 1 : 0);
vec3 flashColor = vec3(${color.red / 255},
${color.green / 255},
${color.blue / 255}) * ${color.alpha / 255};
cogl_color_out.rgb += flashIntensity * mix(flashColor, vec3(0), progress);
// Fade out the remaining shards.
float fadeProgress = smoothstep(0, 1, (progress - 1.0 + FF_TIME)/FF_TIME);
cogl_color_out *= sqrt(1-fadeProgress*fadeProgress);
// These are pretty useful for understanding how this works.
// cogl_color_out = vec4(vec3(flashIntensity), 1);
// cogl_color_out = vec4(vec3(scratchMap), 1);
// Accumulate several random scratches. The color in the scratch map refers to the
// relative time when the respective part will become invisible. Therefore we can
// add a value to make the scratch appear later.
float scratchMap = 1.0;
for (int i=0; i<uNumClaws; ++i) {
vec2 uv = getClawUV(coords, 1.0/uClawSize, uSeed*(i+1));
float delay = i/uNumClaws * MAX_SPAWN_TIME;
scratchMap = min(scratchMap, clamp(texture2D(uClawTexture, uv).r + delay, 0, 1));
}
`);
this.set_uniform_value('uClawTexture', 1);
};
// Get the window texture. We shift the texture lookup by the local derivative of
// the claw texture in order to mimic some folding distortion.
vec2 offset = vec2(dFdx(scratchMap), dFdy(scratchMap)) * progress * 0.5;
cogl_color_out = texture2D(uTexture, coords + offset);
// Add colorful flashes.
float flashIntensity = 1.0 / FLASH_INTENSITY * (scratchMap - progress) + 1;
if (flashIntensity < 0 || flashIntensity >= 1) {
flashIntensity = 0;
}
// Hide flashes where there is now window.
flashIntensity *= cogl_color_out.a;
// Hide scratched out parts.
cogl_color_out *= (scratchMap > progress ? 1 : 0);
// Add flash color.
cogl_color_out.rgb += flashIntensity * mix(uFlashColor.rgb * uFlashColor.a, vec3(0), progress);
// Fade out the remaining shards.
float fadeProgress = smoothstep(0, 1, (progress - 1.0 + FF_TIME)/FF_TIME);
cogl_color_out *= sqrt(1-fadeProgress*fadeProgress);
// These are pretty useful for understanding how this works.
// cogl_color_out = vec4(vec3(flashIntensity), 1);
// cogl_color_out = vec4(vec3(scratchMap), 1);
`;
this.add_glsl_snippet(Shell.SnippetHook.FRAGMENT, declarations, code, true);
}
// This is overridden to bind the claw texture for drawing. Sadly, this seems to be
// impossible under GNOME 3.3x as this.get_pipeline() is not available. It was called
// get_target() back then but this is not wrapped in GJS.
// https://gitlab.gnome.org/GNOME/mutter/-/blob/gnome-3-36/clutter/clutter/clutter-offscreen-effect.c#L598
vfunc_paint_target(node, paint_context) {
this.get_pipeline().set_layer_texture(1, this._clawTexture.get_texture());
const pipeline = this.get_pipeline();
pipeline.set_layer_texture(1, clawTexture.get_texture());
pipeline.set_uniform_1i(this._uClawTexture, 1);
super.vfunc_paint_target(node, paint_context);
}
});