diff --git a/src/Apparition.js b/src/Apparition.js index 981fb16..003ac68 100644 --- a/src/Apparition.js +++ b/src/Apparition.js @@ -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); + } }); } \ No newline at end of file diff --git a/src/BrokenGlass.js b/src/BrokenGlass.js index a56b98a..6ffe8e8 100644 --- a/src/BrokenGlass.js +++ b/src/BrokenGlass.js @@ -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 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); } diff --git a/src/Fire.js b/src/Fire.js index 8498305..c84bcd0 100644 --- a/src/Fire.js +++ b/src/Fire.js @@ -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); - }; + } }); } \ No newline at end of file diff --git a/src/Hexagon.js b/src/Hexagon.js index 3ac2fb9..a0aaeeb 100644 --- a/src/Hexagon.js +++ b/src/Hexagon.js @@ -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); - }; + } }); } \ No newline at end of file diff --git a/src/Matrix.js b/src/Matrix.js index 1c3a718..c4740aa 100644 --- a/src/Matrix.js +++ b/src/Matrix.js @@ -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); } }); diff --git a/src/SnapOfDisintegration.js b/src/SnapOfDisintegration.js index 0bd8410..aba06e7 100644 --- a/src/SnapOfDisintegration.js +++ b/src/SnapOfDisintegration.js @@ -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 0) { - direction *= -1; - } + for (float i=0; i 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); } }); diff --git a/src/TRexAttack.js b/src/TRexAttack.js index a529b6d..8fc1fcd 100644 --- a/src/TRexAttack.js +++ b/src/TRexAttack.js @@ -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= 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= 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); } });