♻️ 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.
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.
+10 -10
View File
@@ -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.
+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 //
// 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);
};
});
}