From 27d2a43c99451d949327854126ef02f236a8c438 Mon Sep 17 00:00:00 2001 From: Simon Schneegans Date: Wed, 4 May 2022 21:10:04 +0200 Subject: [PATCH] :recycle: Make it possible to reuse the fire shader --- extension.js | 38 ++++++------ prefs.js | 20 +++---- src/Fire.js | 159 +++++++++++++++++++++++++++++++++------------------ 3 files changed, 135 insertions(+), 82 deletions(-) diff --git a/extension.js b/extension.js index f989c95..d92dafb 100644 --- a/extension.js +++ b/extension.js @@ -33,17 +33,17 @@ const utils = Me.imports.src.utils; // New effects must be registered here and in prefs.js. const ALL_EFFECTS = [ - Me.imports.src.Apparition.Apparition, - Me.imports.src.BrokenGlass.BrokenGlass, - Me.imports.src.EnergizeA.EnergizeA, - Me.imports.src.EnergizeB.EnergizeB, + // Me.imports.src.Apparition.Apparition, + // Me.imports.src.BrokenGlass.BrokenGlass, + // Me.imports.src.EnergizeA.EnergizeA, + // Me.imports.src.EnergizeB.EnergizeB, Me.imports.src.Fire.Fire, - Me.imports.src.Hexagon.Hexagon, - Me.imports.src.Matrix.Matrix, - Me.imports.src.SnapOfDisintegration.SnapOfDisintegration, - Me.imports.src.TRexAttack.TRexAttack, - Me.imports.src.TVEffect.TVEffect, - Me.imports.src.Wisps.Wisps, + // Me.imports.src.Hexagon.Hexagon, + // Me.imports.src.Matrix.Matrix, + // Me.imports.src.SnapOfDisintegration.SnapOfDisintegration, + // Me.imports.src.TRexAttack.TRexAttack, + // Me.imports.src.TVEffect.TVEffect, + // Me.imports.src.Wisps.Wisps, ]; ////////////////////////////////////////////////////////////////////////////////////////// @@ -291,6 +291,9 @@ class Extension { // Tweaks, when you log out or when the screen locks. disable() { + // Free all effect resources. + ALL_EFFECTS.forEach(Effect => Effect.cleanUp()); + // Unregister our resources. Gio.resources_unregister(this._resources); @@ -445,7 +448,7 @@ class Extension { // -------------------------------------------------------------------- add the shader // Now add a cool shader to our window actor! - const shader = this._currentEffect.createShader(actor, this._settings, forOpening); + const shader = this._currentEffect.getShader(actor, this._settings, forOpening); if (shader) { // There should always be an opacity transition going on... @@ -463,11 +466,14 @@ class Extension { // Update uniforms at each frame. transition.connect('new-frame', (t) => { - shader.set_uniform_value('uProgress', testMode ? 0.5 : t.get_progress()); - shader.set_uniform_value('uTime', - testMode ? duration / 2 : 0.001 * t.get_elapsed_time()); - shader.set_uniform_value('uSizeX', actor.width); - shader.set_uniform_value('uSizeY', actor.height); + shader.set_uniform_float(shader.get_uniform_location('uProgress'), 1, + [testMode ? 0.5 : t.get_progress()]); + shader.set_uniform_float( + shader.get_uniform_location('uTime'), 1, + [testMode ? duration / 2 : 0.001 * t.get_elapsed_time()]); + shader.set_uniform_float(shader.get_uniform_location('uSizeX'), 1, [actor.width]); + shader.set_uniform_float(shader.get_uniform_location('uSizeY'), 1, + [actor.height]); }); // Remove the effect if the animation finished or was interrupted. diff --git a/prefs.js b/prefs.js index d5809b5..79cf248 100644 --- a/prefs.js +++ b/prefs.js @@ -32,17 +32,17 @@ const utils = Me.imports.src.utils; // New effects must be registered here and in extension.js. const ALL_EFFECTS = [ - Me.imports.src.Apparition.Apparition, - Me.imports.src.BrokenGlass.BrokenGlass, - Me.imports.src.EnergizeA.EnergizeA, - Me.imports.src.EnergizeB.EnergizeB, + // Me.imports.src.Apparition.Apparition, + // Me.imports.src.BrokenGlass.BrokenGlass, + // Me.imports.src.EnergizeA.EnergizeA, + // Me.imports.src.EnergizeB.EnergizeB, Me.imports.src.Fire.Fire, - Me.imports.src.Hexagon.Hexagon, - Me.imports.src.Matrix.Matrix, - Me.imports.src.SnapOfDisintegration.SnapOfDisintegration, - Me.imports.src.TRexAttack.TRexAttack, - Me.imports.src.TVEffect.TVEffect, - Me.imports.src.Wisps.Wisps, + // Me.imports.src.Hexagon.Hexagon, + // Me.imports.src.Matrix.Matrix, + // Me.imports.src.SnapOfDisintegration.SnapOfDisintegration, + // Me.imports.src.TRexAttack.TRexAttack, + // Me.imports.src.TVEffect.TVEffect, + // Me.imports.src.Wisps.Wisps, ]; // This template widget class is defined at the bottom of this file. diff --git a/src/Fire.js b/src/Fire.js index 46a2b23..9b04c09 100644 --- a/src/Fire.js +++ b/src/Fire.js @@ -24,13 +24,16 @@ const utils = Me.imports.src.utils; ////////////////////////////////////////////////////////////////////////////////////////// // This effect is a homage to the good old Compiz days. However, it is implemented // // quite differently. While Compiz used a particle system, this effect uses a noise // -// shader. The noise is moved vertically over time and mapped to a configurable color // +// shader. The noise is moved vertically over time and mapped to a configurable color // // gradient. It is faded to transparency towards the edges of the window. In addition, // // there are a couple of moving gradients which fade-in or fade-out the fire effect. // ////////////////////////////////////////////////////////////////////////////////////////// -// 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, shaderInstance will store one instance of +// ShaderClass which will be used whenever this effect is used. +let ShaderClass = null; +let shaderInstance = 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 @@ -97,9 +100,16 @@ var Fire = class Fire { // ---------------------------------------------------------------- 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. + static getShader(actor, settings, forOpening) { + if (shaderInstance == null) { + shaderInstance = new ShaderClass(); + } + + shaderInstance.setUniforms(settings, forOpening); + + return shaderInstance; } // The tweakTransition() is called from extension.js to tweak a window's open / close @@ -120,6 +130,12 @@ var Fire = class Fire { }; } + // This is called from extension.js if the extension is disabled. This should free all + // static resources. + static cleanUp() { + shaderInstance = null; + } + // ----------------------------------------------------------------------- private stuff // This populates the preset dropdown menu for the fire options. @@ -219,48 +235,81 @@ var Fire = class Fire { if (utils.isInShellProcess()) { - const Clutter = imports.gi.Clutter; - const shaderSnippets = Me.imports.src.shaderSnippets; + const {Clutter, 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 gradient values from the settings. We directly inject the values in the - // GLSL code below. The shader is compiled once for each window-closing anyways. In - // the future, we may want to prevent this frequent recompilations of shaders, - // though. - const gradient = []; + this._uGradient = [ + this.get_uniform_location('uGradient1'), + this.get_uniform_location('uGradient2'), + this.get_uniform_location('uGradient3'), + this.get_uniform_location('uGradient4'), + this.get_uniform_location('uGradient5'), + ]; + + this._uForOpening = this.get_uniform_location('uForOpening'); + this._u3DNoise = this.get_uniform_location('u3DNoise'); + this._uScale = this.get_uniform_location('uScale'); + this._uMovementSpeed = this.get_uniform_location('uMovementSpeed'); + } + + // 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) { + + // Load the gradient values from the settings. for (let i = 1; i <= 5; i++) { - const color = - Clutter.Color.from_string(settings.get_string('fire-color-' + i))[1]; - gradient.push(`vec4(${color.red / 255}, ${color.green / 255}, ${ - color.blue / 255}, ${color.alpha / 255})`); + const c = Clutter.Color.from_string(settings.get_string('fire-color-' + i))[1]; + this.set_uniform_float(this._uGradient[i - 1], 4, + [c.red / 255, c.green / 255, c.blue / 255, c.alpha / 255]); } - this.set_shader_source(` + this.set_uniform_float(this._uForOpening, 1, [forOpening]); + this.set_uniform_float(this._u3DNoise, 1, [settings.get_boolean('flame-3d-noise')]); + this.set_uniform_float(this._uScale, 1, [settings.get_double('flame-scale')]); + this.set_uniform_float(this._uMovementSpeed, 1, + [settings.get_double('flame-movement-speed')]); + } + // 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.edgeMask()} + uniform bool uForOpening; + uniform bool u3DNoise; + uniform float uScale; + uniform float uMovementSpeed; + uniform vec4 uGradient1; + uniform vec4 uGradient2; + uniform vec4 uGradient3; + uniform vec4 uGradient4; + uniform vec4 uGradient5; + // These may be configurable in the future. const float EDGE_FADE = 70; const float FADE_WIDTH = 0.1; const float HIDE_TIME = 0.4; - const vec2 FIRE_SCALE = vec2(400, 600) * ${settings.get_double('flame-scale')}; - const float FIRE_SPEED = ${settings.get_double('flame-movement-speed')}; // This maps the input value from [0..1] to a color from the gradient. vec4 getFireColor(float v) { const float steps[5] = float[](0.0, 0.2, 0.35, 0.5, 0.8); - const vec4 colors[5] = vec4[]( - ${gradient[0]}, - ${gradient[1]}, - ${gradient[2]}, - ${gradient[3]}, - ${gradient[4]} + vec4 colors[5] = vec4[]( + uGradient1, + uGradient2, + uGradient3, + uGradient4, + uGradient5 ); if (v < steps[0]) { @@ -308,45 +357,43 @@ if (utils.isInShellProcess()) { // Fade at window borders. effectMask *= getAbsoluteEdgeMask(edgeFadeWidth); - #if ${forOpening ? '1' : '0'} + if (uForOpening) { windowMask = 1.0 - windowMask; - #endif + } return vec2(windowMask, effectMask); } + `; - void main() { + const code = ` + // Get a noise value which moves vertically in time. + vec2 uv = cogl_tex_coord_in[0].st * vec2(uSizeX, uSizeY) / vec2(400, 600) / uScale; + uv.y += uTime * uMovementSpeed; - // Get a noise value which moves vertically in time. - vec2 uv = cogl_tex_coord_in[0].st * vec2(uSizeX, uSizeY) / FIRE_SCALE; - uv.y += uTime * FIRE_SPEED; + float noise = u3DNoise ? simplex3DFractal(vec3(uv*4.0, uTime*uMovementSpeed*1.5)) + : simplex2DFractal(uv * 4.0); - #if ${settings.get_boolean('flame-3d-noise') ? 1 : 0} - float noise = simplex3DFractal(vec3(uv*4.0, uTime*FIRE_SPEED*1.5)); - #else - float noise = simplex2DFractal(uv * 4.0); - #endif + // Modulate noise by effect mask. + vec2 effectMask = effectMask(HIDE_TIME, FADE_WIDTH, EDGE_FADE); + noise *= effectMask.y; - // Modulate noise by effect mask. - vec2 effectMask = effectMask(HIDE_TIME, FADE_WIDTH, EDGE_FADE); - noise *= effectMask.y; + // Map noise value to color. + vec4 fire = getFireColor(noise); + fire.rgb *= fire.a; - // Map noise value to color. - vec4 fire = getFireColor(noise); - fire.rgb *= fire.a; + // Get the window texture and fade it according to the effect mask. + cogl_color_out = texture2D(uTexture, cogl_tex_coord_in[0].st) * effectMask.x; - // Get the window texture and fade it according to the effect mask. - cogl_color_out = texture2D(uTexture, cogl_tex_coord_in[0].st) * effectMask.x; + // Add the fire to the window. + cogl_color_out = cogl_color_out * (1.0 - fire.a) + fire; - // Add the fire to the window. - cogl_color_out = cogl_color_out * (1.0 - fire.a) + fire; + // These are pretty useful for understanding how this works. + // cogl_color_out = vec4(vec3(noise), 1); + // cogl_color_out = vec4(vec3(effectMask.x), 1); + // cogl_color_out = vec4(vec3(effectMask.y), 1); + `; - // These are pretty useful for understanding how this works. - // cogl_color_out = vec4(vec3(noise), 1); - // cogl_color_out = vec4(vec3(effectMask.x), 1); - // cogl_color_out = vec4(vec3(effectMask.y), 1); - } - `); + this.add_glsl_snippet(Shell.SnippetHook.FRAGMENT, declarations, code, true); }; }); } \ No newline at end of file