🔧 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
+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);
}
});