♻️ 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.
+88 -41
View File
@@ -29,8 +29,11 @@ const utils = Me.imports.src.utils;
// 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'),
for (let i = 1; i <= 5; i++) { this.get_uniform_location('uGradient5'),
const color = ];
Clutter.Color.from_string(settings.get_string('fire-color-' + i))[1];
gradient.push(`vec4(${color.red / 255}, ${color.green / 255}, ${ this._uForOpening = this.get_uniform_location('uForOpening');
color.blue / 255}, ${color.alpha / 255})`); this._u3DNoise = this.get_uniform_location('u3DNoise');
this._uScale = this.get_uniform_location('uScale');
this._uMovementSpeed = this.get_uniform_location('uMovementSpeed');
} }
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(settings, forOpening) {
// Load the gradient values from the settings.
for (let i = 1; i <= 5; i++) {
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_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,24 +357,21 @@ 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. // Get a noise value which moves vertically in time.
vec2 uv = cogl_tex_coord_in[0].st * vec2(uSizeX, uSizeY) / FIRE_SCALE; vec2 uv = cogl_tex_coord_in[0].st * vec2(uSizeX, uSizeY) / vec2(400, 600) / uScale;
uv.y += uTime * FIRE_SPEED; uv.y += uTime * uMovementSpeed;
#if ${settings.get_boolean('flame-3d-noise') ? 1 : 0} float noise = u3DNoise ? simplex3DFractal(vec3(uv*4.0, uTime*uMovementSpeed*1.5))
float noise = simplex3DFractal(vec3(uv*4.0, uTime*FIRE_SPEED*1.5)); : simplex2DFractal(uv * 4.0);
#else
float noise = simplex2DFractal(uv * 4.0);
#endif
// Modulate noise by effect mask. // Modulate noise by effect mask.
vec2 effectMask = effectMask(HIDE_TIME, FADE_WIDTH, EDGE_FADE); vec2 effectMask = effectMask(HIDE_TIME, FADE_WIDTH, EDGE_FADE);
@@ -345,8 +391,9 @@ if (utils.isInShellProcess()) {
// cogl_color_out = vec4(vec3(noise), 1); // cogl_color_out = vec4(vec3(noise), 1);
// cogl_color_out = vec4(vec3(effectMask.x), 1); // cogl_color_out = vec4(vec3(effectMask.x), 1);
// cogl_color_out = vec4(vec3(effectMask.y), 1); // cogl_color_out = vec4(vec3(effectMask.y), 1);
} `;
`);
this.add_glsl_snippet(Shell.SnippetHook.FRAGMENT, declarations, code, true);
}; };
}); });
} }