♻️ Make it possible to reuse the fire shader

This commit is contained in:
Simon Schneegans
2022-05-04 21:10:04 +02:00
parent fa9941ce5b
commit 27d2a43c99
3 changed files with 135 additions and 82 deletions
+22 -16
View File
@@ -33,17 +33,17 @@ const utils = Me.imports.src.utils;
// New effects must be registered here and in prefs.js. // New effects must be registered here and in prefs.js.
const ALL_EFFECTS = [ const ALL_EFFECTS = [
Me.imports.src.Apparition.Apparition, // Me.imports.src.Apparition.Apparition,
Me.imports.src.BrokenGlass.BrokenGlass, // Me.imports.src.BrokenGlass.BrokenGlass,
Me.imports.src.EnergizeA.EnergizeA, // Me.imports.src.EnergizeA.EnergizeA,
Me.imports.src.EnergizeB.EnergizeB, // Me.imports.src.EnergizeB.EnergizeB,
Me.imports.src.Fire.Fire, Me.imports.src.Fire.Fire,
Me.imports.src.Hexagon.Hexagon, // Me.imports.src.Hexagon.Hexagon,
Me.imports.src.Matrix.Matrix, // Me.imports.src.Matrix.Matrix,
Me.imports.src.SnapOfDisintegration.SnapOfDisintegration, // Me.imports.src.SnapOfDisintegration.SnapOfDisintegration,
Me.imports.src.TRexAttack.TRexAttack, // Me.imports.src.TRexAttack.TRexAttack,
Me.imports.src.TVEffect.TVEffect, // Me.imports.src.TVEffect.TVEffect,
Me.imports.src.Wisps.Wisps, // Me.imports.src.Wisps.Wisps,
]; ];
////////////////////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////////////////
@@ -291,6 +291,9 @@ class Extension {
// Tweaks, when you log out or when the screen locks. // Tweaks, when you log out or when the screen locks.
disable() { disable() {
// Free all effect resources.
ALL_EFFECTS.forEach(Effect => Effect.cleanUp());
// Unregister our resources. // Unregister our resources.
Gio.resources_unregister(this._resources); Gio.resources_unregister(this._resources);
@@ -445,7 +448,7 @@ class Extension {
// -------------------------------------------------------------------- add the shader // -------------------------------------------------------------------- add the shader
// Now add a cool shader to our window actor! // 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) { if (shader) {
// There should always be an opacity transition going on... // There should always be an opacity transition going on...
@@ -463,11 +466,14 @@ class Extension {
// Update uniforms at each frame. // Update uniforms at each frame.
transition.connect('new-frame', (t) => { transition.connect('new-frame', (t) => {
shader.set_uniform_value('uProgress', testMode ? 0.5 : t.get_progress()); shader.set_uniform_float(shader.get_uniform_location('uProgress'), 1,
shader.set_uniform_value('uTime', [testMode ? 0.5 : t.get_progress()]);
testMode ? duration / 2 : 0.001 * t.get_elapsed_time()); shader.set_uniform_float(
shader.set_uniform_value('uSizeX', actor.width); shader.get_uniform_location('uTime'), 1,
shader.set_uniform_value('uSizeY', actor.height); [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. // Remove the effect if the animation finished or was interrupted.
+10 -10
View File
@@ -32,17 +32,17 @@ const utils = Me.imports.src.utils;
// New effects must be registered here and in extension.js. // New effects must be registered here and in extension.js.
const ALL_EFFECTS = [ const ALL_EFFECTS = [
Me.imports.src.Apparition.Apparition, // Me.imports.src.Apparition.Apparition,
Me.imports.src.BrokenGlass.BrokenGlass, // Me.imports.src.BrokenGlass.BrokenGlass,
Me.imports.src.EnergizeA.EnergizeA, // Me.imports.src.EnergizeA.EnergizeA,
Me.imports.src.EnergizeB.EnergizeB, // Me.imports.src.EnergizeB.EnergizeB,
Me.imports.src.Fire.Fire, Me.imports.src.Fire.Fire,
Me.imports.src.Hexagon.Hexagon, // Me.imports.src.Hexagon.Hexagon,
Me.imports.src.Matrix.Matrix, // Me.imports.src.Matrix.Matrix,
Me.imports.src.SnapOfDisintegration.SnapOfDisintegration, // Me.imports.src.SnapOfDisintegration.SnapOfDisintegration,
Me.imports.src.TRexAttack.TRexAttack, // Me.imports.src.TRexAttack.TRexAttack,
Me.imports.src.TVEffect.TVEffect, // Me.imports.src.TVEffect.TVEffect,
Me.imports.src.Wisps.Wisps, // Me.imports.src.Wisps.Wisps,
]; ];
// This template widget class is defined at the bottom of this file. // This template widget class is defined at the bottom of this file.
+103 -56
View File
@@ -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 // // 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 // // 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, // // 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. // // 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. // The shader class for this effect is registered further down in this file. When this
let Shader = null; // 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 // 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 // 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 // ---------------------------------------------------------------- API for extension.js
// This is called from extension.js whenever a window is closed with this effect. // This is called from extension.js whenever a window is opened or closed with this
static createShader(actor, settings, forOpening) { // effect.
return new Shader(settings, forOpening); 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 // 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 // ----------------------------------------------------------------------- private stuff
// This populates the preset dropdown menu for the fire options. // This populates the preset dropdown menu for the fire options.
@@ -219,48 +235,81 @@ var Fire = class Fire {
if (utils.isInShellProcess()) { if (utils.isInShellProcess()) {
const Clutter = imports.gi.Clutter; const {Clutter, Shell} = imports.gi;
const shaderSnippets = Me.imports.src.shaderSnippets; const shaderSnippets = Me.imports.src.shaderSnippets;
Shader = GObject.registerClass({}, class Shader extends Clutter.ShaderEffect { ShaderClass = GObject.registerClass({}, class ShaderClass extends Shell.GLSLEffect {
_init(settings, forOpening) { // This is called when the effect is used for the first time. This can be used to
super._init({shader_type: Clutter.ShaderType.FRAGMENT_SHADER}); // store all required uniform locations.
_init() {
super._init();
// Load the gradient values from the settings. We directly inject the values in the this._uGradient = [
// GLSL code below. The shader is compiled once for each window-closing anyways. In this.get_uniform_location('uGradient1'),
// the future, we may want to prevent this frequent recompilations of shaders, this.get_uniform_location('uGradient2'),
// though. this.get_uniform_location('uGradient3'),
const gradient = []; 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++) { for (let i = 1; i <= 5; i++) {
const color = const c = Clutter.Color.from_string(settings.get_string('fire-color-' + i))[1];
Clutter.Color.from_string(settings.get_string('fire-color-' + i))[1]; this.set_uniform_float(this._uGradient[i - 1], 4,
gradient.push(`vec4(${color.red / 255}, ${color.green / 255}, ${ [c.red / 255, c.green / 255, c.blue / 255, c.alpha / 255]);
color.blue / 255}, ${color.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. // Inject some common shader snippets.
${shaderSnippets.standardUniforms()} ${shaderSnippets.standardUniforms()}
${shaderSnippets.noise()} ${shaderSnippets.noise()}
${shaderSnippets.edgeMask()} ${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. // These may be configurable in the future.
const float EDGE_FADE = 70; const float EDGE_FADE = 70;
const float FADE_WIDTH = 0.1; const float FADE_WIDTH = 0.1;
const float HIDE_TIME = 0.4; 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. // This maps the input value from [0..1] to a color from the gradient.
vec4 getFireColor(float v) { vec4 getFireColor(float v) {
const float steps[5] = float[](0.0, 0.2, 0.35, 0.5, 0.8); const float steps[5] = float[](0.0, 0.2, 0.35, 0.5, 0.8);
const vec4 colors[5] = vec4[]( vec4 colors[5] = vec4[](
${gradient[0]}, uGradient1,
${gradient[1]}, uGradient2,
${gradient[2]}, uGradient3,
${gradient[3]}, uGradient4,
${gradient[4]} uGradient5
); );
if (v < steps[0]) { if (v < steps[0]) {
@@ -308,45 +357,43 @@ if (utils.isInShellProcess()) {
// Fade at window borders. // Fade at window borders.
effectMask *= getAbsoluteEdgeMask(edgeFadeWidth); effectMask *= getAbsoluteEdgeMask(edgeFadeWidth);
#if ${forOpening ? '1' : '0'} if (uForOpening) {
windowMask = 1.0 - windowMask; windowMask = 1.0 - windowMask;
#endif }
return vec2(windowMask, effectMask); 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. float noise = u3DNoise ? simplex3DFractal(vec3(uv*4.0, uTime*uMovementSpeed*1.5))
vec2 uv = cogl_tex_coord_in[0].st * vec2(uSizeX, uSizeY) / FIRE_SCALE; : simplex2DFractal(uv * 4.0);
uv.y += uTime * FIRE_SPEED;
#if ${settings.get_boolean('flame-3d-noise') ? 1 : 0} // Modulate noise by effect mask.
float noise = simplex3DFractal(vec3(uv*4.0, uTime*FIRE_SPEED*1.5)); vec2 effectMask = effectMask(HIDE_TIME, FADE_WIDTH, EDGE_FADE);
#else noise *= effectMask.y;
float noise = simplex2DFractal(uv * 4.0);
#endif
// Modulate noise by effect mask. // Map noise value to color.
vec2 effectMask = effectMask(HIDE_TIME, FADE_WIDTH, EDGE_FADE); vec4 fire = getFireColor(noise);
noise *= effectMask.y; fire.rgb *= fire.a;
// Map noise value to color. // Get the window texture and fade it according to the effect mask.
vec4 fire = getFireColor(noise); cogl_color_out = texture2D(uTexture, cogl_tex_coord_in[0].st) * effectMask.x;
fire.rgb *= fire.a;
// Get the window texture and fade it according to the effect mask. // Add the fire to the window.
cogl_color_out = texture2D(uTexture, cogl_tex_coord_in[0].st) * effectMask.x; cogl_color_out = cogl_color_out * (1.0 - fire.a) + fire;
// Add the fire to the window. // These are pretty useful for understanding how this works.
cogl_color_out = cogl_color_out * (1.0 - fire.a) + fire; // 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. this.add_glsl_snippet(Shell.SnippetHook.FRAGMENT, declarations, code, true);
// cogl_color_out = vec4(vec3(noise), 1);
// cogl_color_out = vec4(vec3(effectMask.x), 1);
// cogl_color_out = vec4(vec3(effectMask.y), 1);
}
`);
}; };
}); });
} }