🔀 Merge pull request #139 from Schneegans/feature/reuse-shaders
@@ -6,6 +6,8 @@
|
|||||||
|
|
||||||
#### Bug Fixes
|
#### Bug Fixes
|
||||||
|
|
||||||
|
* **Fixed a major memory leak**. Now, shaders are not re-created anymore whenever a window is opened or closed. Instead, previously created shaders are re-used as often as possible. This requires that all settings are set as uniform values (previously they were injected into the shader source) which in turn requires that `Shell.GLSLEffect` is used instead of `Clutter.ShaderEffect`. This change also requires that all shaders now work with straight instead of premultiplied alpha. Anyways, a lot needed to be refactored for this fix and I hope that it did not bring too many visual changes.
|
||||||
|
* Fixed the window-close animation of windows which were opened before the session was started (e.g. before GNOME Shell has been restarted).
|
||||||
* There seem to be cases were `libadwaita` is not available on GNOME 42 (e.g. Pop!_OS 22.04 beta). The preferences dialog now tries to fallback to the GTK4-only variant if `libadwaita` is not available.
|
* There seem to be cases were `libadwaita` is not available on GNOME 42 (e.g. Pop!_OS 22.04 beta). The preferences dialog now tries to fallback to the GTK4-only variant if `libadwaita` is not available.
|
||||||
|
|
||||||
## [Burn My Windows 15](https://github.com/schneegans/Burn-My-Windows/releases/tag/v15)
|
## [Burn My Windows 15](https://github.com/schneegans/Burn-My-Windows/releases/tag/v15)
|
||||||
|
|||||||
@@ -101,6 +101,8 @@ Please study this code carefully, all of it is explained with inline comments.
|
|||||||
|
|
||||||
const GObject = imports.gi.GObject;
|
const GObject = imports.gi.GObject;
|
||||||
|
|
||||||
|
const _ = imports.gettext.domain('burn-my-windows').gettext;
|
||||||
|
|
||||||
const ExtensionUtils = imports.misc.extensionUtils;
|
const ExtensionUtils = imports.misc.extensionUtils;
|
||||||
const Me = imports.misc.extensionUtils.getCurrentExtension();
|
const Me = imports.misc.extensionUtils.getCurrentExtension();
|
||||||
const utils = Me.imports.src.utils;
|
const utils = Me.imports.src.utils;
|
||||||
@@ -110,8 +112,13 @@ const utils = Me.imports.src.utils;
|
|||||||
// <- Please add a description of your effect here -> //
|
// <- Please add a description of your effect here -> //
|
||||||
//////////////////////////////////////////////////////////////////////////////////////////
|
//////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
// 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, 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
|
// 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
|
||||||
@@ -136,7 +143,7 @@ var SimpleFade = class SimpleFade {
|
|||||||
// This will be shown in the sidebar of the preferences dialog as well as in the
|
// This will be shown in the sidebar of the preferences dialog as well as in the
|
||||||
// drop-down menus where the user can choose the effect.
|
// drop-down menus where the user can choose the effect.
|
||||||
static getLabel() {
|
static getLabel() {
|
||||||
return 'Simple Fade Effect';
|
return _('Simple Fade Effect');
|
||||||
}
|
}
|
||||||
|
|
||||||
// -------------------------------------------------------------------- API for prefs.js
|
// -------------------------------------------------------------------- API for prefs.js
|
||||||
@@ -151,9 +158,21 @@ var SimpleFade = class SimpleFade {
|
|||||||
|
|
||||||
// ---------------------------------------------------------------- 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. It returns an instance of the shader class, trying to reuse previously
|
||||||
return new Shader(settings, forOpening);
|
// 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
|
// The tweakTransition() is called from extension.js to tweak a window's open / close
|
||||||
@@ -184,15 +203,35 @@ var SimpleFade = class SimpleFade {
|
|||||||
|
|
||||||
if (utils.isInShellProcess()) {
|
if (utils.isInShellProcess()) {
|
||||||
|
|
||||||
const Clutter = imports.gi.Clutter;
|
const Shell = imports.gi.Shell;
|
||||||
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) {
|
_init(settings, forOpening) {
|
||||||
super._init({shader_type: Clutter.ShaderType.FRAGMENT_SHADER});
|
super._init({shader_type: Clutter.ShaderType.FRAGMENT_SHADER});
|
||||||
|
|
||||||
this.set_shader_source(`
|
this._uForOpening = this.get_uniform_location('uForOpening');
|
||||||
|
this._uFadeWidth = this.get_uniform_location('uFadeWidth');
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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) {
|
||||||
|
this.set_uniform_float(this._uForOpening, 1, [forOpening]);
|
||||||
|
this.set_uniform_float(this._uFadeWidth, 1, [settings.get_double('simple-fade-width')]);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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 means, it's only called when the effect
|
||||||
|
// is used for the first time.
|
||||||
|
vfunc_build_pipeline() {
|
||||||
|
|
||||||
|
const declarations = `
|
||||||
// The code below injects some standard uniforms which will be updated during the
|
// The code below injects some standard uniforms which will be updated during the
|
||||||
// animation. This includes:
|
// animation. This includes:
|
||||||
// uTexture: Contains the texture of the window.
|
// uTexture: Contains the texture of the window.
|
||||||
@@ -202,29 +241,30 @@ if (utils.isInShellProcess()) {
|
|||||||
// uSizeY: The vertical size of uTexture in pixels.
|
// uSizeY: The vertical size of uTexture in pixels.
|
||||||
${shaderSnippets.standardUniforms()}
|
${shaderSnippets.standardUniforms()}
|
||||||
|
|
||||||
// The width of the fading effect is directly loaded from the settings.
|
// The width of the fading effect is loaded from the settings.
|
||||||
const float FADE_WIDTH = ${settings.get_double('simple-fade-width')};
|
uniform float uFadeWidth;
|
||||||
|
`;
|
||||||
|
|
||||||
void main() {
|
const code = `
|
||||||
|
// Get the color from the window texture.
|
||||||
|
vec4 windowColor = texture2D(uTexture, cogl_tex_coord_in[0].st);
|
||||||
|
|
||||||
// Get the color from the window texture.
|
// Radial distance from window edge to the window's center.
|
||||||
vec4 windowColor = texture2D(uTexture, cogl_tex_coord_in[0].st);
|
float dist = length(cogl_tex_coord_in[0].st - 0.5) * 2.0 / sqrt(2.0);
|
||||||
|
|
||||||
// Radial distance from window edge to the window's center.
|
// This gradually dissolves from [1..0] from the outside to the center. We
|
||||||
float dist = length(cogl_tex_coord_in[0].st - 0.5) * 2.0 / sqrt(2.0);
|
// switch the direction for opening and closing.
|
||||||
|
float progress = uForOpening ? 1.0 - uProgress : uProgress;
|
||||||
|
float mask = (1.0 - progress * (1.0 + uFadeWidth) - dist + uFadeWidth) / uFadeWidth;
|
||||||
|
|
||||||
// This gradually dissolves from [1..0] from the outside to the center. We
|
// Make the mask smoother.
|
||||||
// switch the direction for opening and closing.
|
mask = smoothstep(0, 1, mask);
|
||||||
float progress = ${forOpening ? '1.0 - uProgress' : 'uProgress'};
|
|
||||||
float mask = (1.0 - progress * (1.0 + FADE_WIDTH) - dist + FADE_WIDTH) / FADE_WIDTH;
|
|
||||||
|
|
||||||
// Make the mask smoother.
|
// Set the final output color. This uses premultiplied alpha.
|
||||||
mask = smoothstep(0, 1, mask);
|
cogl_color_out = windowColor * mask;
|
||||||
|
`;
|
||||||
|
|
||||||
// Set the final output color. This uses premultiplied alpha.
|
this.add_glsl_snippet(Shell.SnippetHook.FRAGMENT, declarations, code, true);
|
||||||
cogl_color_out = windowColor * mask;
|
|
||||||
}
|
|
||||||
`);
|
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -70,10 +70,6 @@ class Extension {
|
|||||||
// Store a reference to the settings object.
|
// Store a reference to the settings object.
|
||||||
this._settings = ExtensionUtils.getSettings();
|
this._settings = ExtensionUtils.getSettings();
|
||||||
|
|
||||||
// This will store an item of ALL_EFFECTS array which was used the last time a window
|
|
||||||
// was opened / closed.
|
|
||||||
this._currentEffect = 0;
|
|
||||||
|
|
||||||
// We will use extensionThis to refer to the extension inside the patched methods.
|
// We will use extensionThis to refer to the extension inside the patched methods.
|
||||||
const extensionThis = this;
|
const extensionThis = this;
|
||||||
|
|
||||||
@@ -83,6 +79,7 @@ class Extension {
|
|||||||
this._origDoRemoveWindow = Workspace.prototype._doRemoveWindow;
|
this._origDoRemoveWindow = Workspace.prototype._doRemoveWindow;
|
||||||
this._origShouldAnimateActor = WindowManager.prototype._shouldAnimateActor;
|
this._origShouldAnimateActor = WindowManager.prototype._shouldAnimateActor;
|
||||||
this._origWaitForOverviewToHide = WindowManager.prototype._waitForOverviewToHide;
|
this._origWaitForOverviewToHide = WindowManager.prototype._waitForOverviewToHide;
|
||||||
|
this._origDestroyWindowDone = WindowManager.prototype._destroyWindowDone;
|
||||||
|
|
||||||
// We will also override these animation times.
|
// We will also override these animation times.
|
||||||
this._origWindowTime = imports.ui.windowManager.DESTROY_WINDOW_ANIMATION_TIME;
|
this._origWindowTime = imports.ui.windowManager.DESTROY_WINDOW_ANIMATION_TIME;
|
||||||
@@ -204,9 +201,9 @@ class Extension {
|
|||||||
|
|
||||||
// ----------------------------------------------- patching the window-close animation
|
// ----------------------------------------------- patching the window-close animation
|
||||||
|
|
||||||
// The signal handler below is all which is required outside of the overview. All
|
// The signal handler below and the following patch are all which is required outside
|
||||||
// other hacks further below are just required to defer the window-hiding in the
|
// of the overview. All other hacks further below are just required to defer the
|
||||||
// overview until the effect is finished.
|
// window-hiding in the overview until the effect is finished.
|
||||||
|
|
||||||
// The close animation is set up in WindowManager's _destroyWindow:
|
// The close animation is set up in WindowManager's _destroyWindow:
|
||||||
// https://gitlab.gnome.org/GNOME/gnome-shell/-/blob/main/js/ui/windowManager.js#L1541
|
// https://gitlab.gnome.org/GNOME/gnome-shell/-/blob/main/js/ui/windowManager.js#L1541
|
||||||
@@ -216,6 +213,23 @@ class Extension {
|
|||||||
this._setupEffect(actor, false);
|
this._setupEffect(actor, false);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Once the window-close animation is is finished, the window manager's
|
||||||
|
// _destroyWindowDone is called. We use this to free the effect so that it can be
|
||||||
|
// re-used in future.
|
||||||
|
// https://gitlab.gnome.org/GNOME/gnome-shell/-/blob/main/js/ui/windowManager.js#L1541
|
||||||
|
WindowManager.prototype._destroyWindowDone = function(shellwm, actor) {
|
||||||
|
if (this._destroying.has(actor)) {
|
||||||
|
const shader = actor.get_effect('burn-my-windows-effect');
|
||||||
|
if (shader) {
|
||||||
|
actor.remove_effect(shader);
|
||||||
|
shader.free();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Call the original method.
|
||||||
|
extensionThis._origDestroyWindowDone.apply(this, [shellwm, actor]);
|
||||||
|
};
|
||||||
|
|
||||||
// These three method overrides are mega-hacky! Usually, windows are not faded when
|
// These three method overrides are mega-hacky! Usually, windows are not faded when
|
||||||
// closed from the overview (why?). With these overrides we make sure that they are
|
// closed from the overview (why?). With these overrides we make sure that they are
|
||||||
// actually faded out. To do this, _windowRemoved and _doRemoveWindow now check
|
// actually faded out. To do this, _windowRemoved and _doRemoveWindow now check
|
||||||
@@ -291,6 +305,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);
|
||||||
|
|
||||||
@@ -303,6 +320,7 @@ class Extension {
|
|||||||
Workspace.prototype._doRemoveWindow = this._origDoRemoveWindow;
|
Workspace.prototype._doRemoveWindow = this._origDoRemoveWindow;
|
||||||
WindowManager.prototype._shouldAnimateActor = this._origShouldAnimateActor;
|
WindowManager.prototype._shouldAnimateActor = this._origShouldAnimateActor;
|
||||||
WindowManager.prototype._waitForOverviewToHide = this._origWaitForOverviewToHide;
|
WindowManager.prototype._waitForOverviewToHide = this._origWaitForOverviewToHide;
|
||||||
|
WindowManager.prototype._destroyWindowDone = this._origDestroyWindowDone;
|
||||||
|
|
||||||
imports.ui.windowManager.DESTROY_WINDOW_ANIMATION_TIME = this._origWindowTime;
|
imports.ui.windowManager.DESTROY_WINDOW_ANIMATION_TIME = this._origWindowTime;
|
||||||
imports.ui.windowManager.DIALOG_DESTROY_WINDOW_ANIMATION_TIME = this._origDialogTime;
|
imports.ui.windowManager.DIALOG_DESTROY_WINDOW_ANIMATION_TIME = this._origDialogTime;
|
||||||
@@ -337,7 +355,7 @@ class Extension {
|
|||||||
// We do nothing if a dialog got closed and we should not burn them.
|
// We do nothing if a dialog got closed and we should not burn them.
|
||||||
const shouldDestroyDialogs = this._settings.get_boolean('destroy-dialogs');
|
const shouldDestroyDialogs = this._settings.get_boolean('destroy-dialogs');
|
||||||
|
|
||||||
// If an effect is to be previewed however, we have to affect dialogs es well. This is
|
// If an effect is to be previewed, we have to affect dialogs es well. This is
|
||||||
// because the preview window is a dialog window...
|
// because the preview window is a dialog window...
|
||||||
const action = forOpening ? 'open' : 'close';
|
const action = forOpening ? 'open' : 'close';
|
||||||
const previewNick = this._settings.get_string(action + '-preview-effect');
|
const previewNick = this._settings.get_string(action + '-preview-effect');
|
||||||
@@ -347,14 +365,23 @@ class Extension {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// There is the weird case where an animation is already. This happens when a window
|
||||||
|
// is closed which has been created before the session was started (e.g. when GNOME
|
||||||
|
// Shell has been restarted in the meantime).
|
||||||
|
const oldShader = actor.get_effect('burn-my-windows-effect');
|
||||||
|
if (oldShader) {
|
||||||
|
actor.remove_effect(oldShader);
|
||||||
|
oldShader.free();
|
||||||
|
}
|
||||||
|
|
||||||
// ------------------------------------------------------------------ choose an effect
|
// ------------------------------------------------------------------ choose an effect
|
||||||
|
|
||||||
// Now we chose a random effect from all enabled effects.
|
// Now we chose a random effect from all enabled effects.
|
||||||
this._currentEffect = null;
|
let effect = null;
|
||||||
|
|
||||||
// First we check if an effect is to be previewed.
|
// First we check if an effect is to be previewed.
|
||||||
if (previewNick != '') {
|
if (previewNick != '') {
|
||||||
this._currentEffect = ALL_EFFECTS.find(Effect => {
|
effect = ALL_EFFECTS.find(Effect => {
|
||||||
return Effect.getNick() == previewNick;
|
return Effect.getNick() == previewNick;
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -372,12 +399,12 @@ class Extension {
|
|||||||
|
|
||||||
// And then choose a random effect.
|
// And then choose a random effect.
|
||||||
if (enabled.length > 0) {
|
if (enabled.length > 0) {
|
||||||
this._currentEffect = enabled[Math.floor(Math.random() * enabled.length)];
|
effect = enabled[Math.floor(Math.random() * enabled.length)];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// If nothing was enabled, we have to do nothing :)
|
// If nothing was enabled, we have to do nothing :)
|
||||||
if (this._currentEffect == null) {
|
if (effect == null) {
|
||||||
this._fixAnimationTimes(isDialogWindow, forOpening, null);
|
this._fixAnimationTimes(isDialogWindow, forOpening, null);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -391,10 +418,9 @@ class Extension {
|
|||||||
// The following is used to tweak the ongoing transitions of a window actor. Usually
|
// The following is used to tweak the ongoing transitions of a window actor. Usually
|
||||||
// windows are faded in / out scaled up / down slightly by GNOME Shell. Here, we allow
|
// windows are faded in / out scaled up / down slightly by GNOME Shell. Here, we allow
|
||||||
// modifications to this behavior by the effects.
|
// modifications to this behavior by the effects.
|
||||||
const config = this._currentEffect.tweakTransition(actor, this._settings, forOpening);
|
const config = effect.tweakTransition(actor, this._settings, forOpening);
|
||||||
const duration = testMode ?
|
const duration =
|
||||||
5000 :
|
testMode ? 5000 : this._settings.get_int(effect.getNick() + '-animation-time');
|
||||||
this._settings.get_int(this._currentEffect.getNick() + '-animation-time');
|
|
||||||
|
|
||||||
// All animations are relative to the window's center.
|
// All animations are relative to the window's center.
|
||||||
actor.set_pivot_point(0.5, 0.5);
|
actor.set_pivot_point(0.5, 0.5);
|
||||||
@@ -445,7 +471,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 = effect.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...
|
||||||
@@ -457,23 +483,28 @@ class Extension {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// First remove any old effect.
|
actor.add_effect_with_name('burn-my-windows-effect', shader);
|
||||||
actor.remove_effect_by_name(`burn-my-windows-effect`);
|
|
||||||
actor.add_effect_with_name(`burn-my-windows-effect`, shader);
|
|
||||||
|
|
||||||
// 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.
|
||||||
if (forOpening) {
|
if (forOpening) {
|
||||||
transition.connect('stopped', () => {
|
transition.connect('stopped', () => {
|
||||||
actor.remove_effect_by_name(`burn-my-windows-effect`);
|
const oldShader = actor.get_effect('burn-my-windows-effect');
|
||||||
|
if (oldShader) {
|
||||||
|
actor.remove_effect(oldShader);
|
||||||
|
oldShader.free();
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -27,8 +27,13 @@ const utils = Me.imports.src.utils;
|
|||||||
// the center. //
|
// the center. //
|
||||||
//////////////////////////////////////////////////////////////////////////////////////////
|
//////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
// 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, 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
|
// 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
|
||||||
@@ -80,9 +85,21 @@ var Apparition = class Apparition {
|
|||||||
|
|
||||||
// ---------------------------------------------------------------- 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. It returns an instance of the shader class, trying to reuse previously
|
||||||
return new Shader(actor, settings, forOpening);
|
// 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
|
// 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}
|
'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,98 @@ var Apparition = class Apparition {
|
|||||||
|
|
||||||
if (utils.isInShellProcess()) {
|
if (utils.isInShellProcess()) {
|
||||||
|
|
||||||
const Clutter = imports.gi.Clutter;
|
const Shell = imports.gi.Shell;
|
||||||
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(actor, 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();
|
||||||
|
|
||||||
|
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.
|
// If we are currently performing integration test, the animation uses a fixed seed.
|
||||||
const testMode = settings.get_boolean('test-mode');
|
const testMode = settings.get_boolean('test-mode');
|
||||||
|
|
||||||
// The math for the whirling is inspired by this post:
|
// clang-format off
|
||||||
// http://www.geeks3d.com/20110428/shader-library-swirl-post-processing-filter-in-glsl
|
this.set_uniform_float(this._uForOpening, 1, [forOpening]);
|
||||||
this.set_shader_source(`
|
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 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()}
|
||||||
|
|
||||||
const vec2 SEED = vec2(${testMode ? 0 : Math.random()},
|
uniform bool uForOpening;
|
||||||
${testMode ? 0 : Math.random()});
|
uniform vec2 uSeed;
|
||||||
const float SHAKE = ${settings.get_double('apparition-shake-intensity')};
|
uniform float uShake;
|
||||||
const float TWIRL = ${settings.get_double('apparition-twirl-intensity')};
|
uniform float uTwirl;
|
||||||
const float SUCTION = ${settings.get_double('apparition-suction-intensity')};
|
uniform float uSuction;
|
||||||
const float RANDOMNESS = ${settings.get_double('apparition-randomness')};
|
uniform float uRandomness;
|
||||||
|
|
||||||
const float ACTOR_SCALE = 2.0;
|
const float ACTOR_SCALE = 2.0;
|
||||||
const float PADDING = ACTOR_SCALE / 2.0 - 0.5;
|
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.
|
// Choose a random suction center.
|
||||||
float progress = ${forOpening ? '1.0-uProgress' : 'uProgress'};
|
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.
|
// Add some shaking.
|
||||||
vec2 center = SEED * RANDOMNESS + 0.5 * (1.0 - RANDOMNESS);
|
coords.x += progress * 0.05 * uShake * sin((progress + uSeed.x) * (1.0 + uSeed.x) * uShake);
|
||||||
vec2 coords = cogl_tex_coord_in[0].st * ACTOR_SCALE - PADDING - center;
|
coords.y += progress * 0.05 * uShake * cos((progress + uSeed.y) * (1.0 + uSeed.y) * uShake);
|
||||||
|
|
||||||
// Add some shaking.
|
// "Suck" the texture into the center.
|
||||||
coords.x += progress * 0.05 * SHAKE * sin((progress + SEED.x) * (1.0 + SEED.x) * SHAKE);
|
float dist = length(coords) / sqrt(2);
|
||||||
coords.y += progress * 0.05 * SHAKE * cos((progress + SEED.y) * (1.0 + SEED.y) * SHAKE);
|
coords += progress * coords / dist * 0.5 * uSuction;
|
||||||
|
|
||||||
// "Suck" the texture into the center.
|
// Apply some whirling.
|
||||||
float dist = length(coords) / sqrt(2);
|
float angle = pow(1.0 - dist, 2.0) * uTwirl * progress;
|
||||||
coords += progress * coords / dist * 0.5 * SUCTION;
|
float s = sin(angle);
|
||||||
|
float c = cos(angle);
|
||||||
|
coords = vec2(dot(coords, vec2(c, -s)), dot(coords, vec2(s, c)));
|
||||||
|
|
||||||
// Apply some whirling.
|
// Shell.GLSLEffect uses straight alpha. So we have to convert from premultiplied.
|
||||||
float angle = pow(1.0 - dist, 2.0) * TWIRL * progress;
|
cogl_color_out = texture2D(uTexture, coords + center);
|
||||||
float s = sin(angle);
|
if (cogl_color_out.a > 0) {
|
||||||
float c = cos(angle);
|
cogl_color_out.rgb /= cogl_color_out.a;
|
||||||
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.a *= 1.0 - progress;
|
||||||
|
`;
|
||||||
|
|
||||||
|
this.add_glsl_snippet(Shell.SnippetHook.FRAGMENT, declarations, code, true);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -28,8 +28,16 @@ const utils = Me.imports.src.utils;
|
|||||||
// of vfunc_paint_target further down in this file. //
|
// of vfunc_paint_target further down in this file. //
|
||||||
//////////////////////////////////////////////////////////////////////////////////////////
|
//////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
// 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, 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
|
// 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
|
||||||
@@ -80,9 +88,21 @@ var BrokenGlass = class BrokenGlass {
|
|||||||
|
|
||||||
// ---------------------------------------------------------------- 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. It returns an instance of the shader class, trying to reuse previously
|
||||||
return new Shader(actor, settings, forOpening);
|
// 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
|
// 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}
|
'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()) {
|
if (utils.isInShellProcess()) {
|
||||||
|
|
||||||
const {Clutter, GdkPixbuf, Cogl} = imports.gi;
|
const {Clutter, GdkPixbuf, Cogl, Shell} = imports.gi;
|
||||||
const shaderSnippets = Me.imports.src.shaderSnippets;
|
const shaderSnippets = Me.imports.src.shaderSnippets;
|
||||||
|
|
||||||
// This shader creates a complex-looking effect with rather simple means. Here is how it
|
// 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
|
// 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
|
// 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
|
// 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.
|
// shard edges. This information is used to fade out the shards.
|
||||||
Shader = GObject.registerClass({}, class Shader extends Clutter.ShaderEffect {
|
ShaderClass = GObject.registerClass({}, class ShaderClass extends Shell.GLSLEffect {
|
||||||
_init(actor, 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 shards texture. As the shader is re-created for each window animation,
|
// Load the shards texture.
|
||||||
// this texture is also re-created each time. This could be improved in the future!
|
if (shardTexture == null) {
|
||||||
const shardData = GdkPixbuf.Pixbuf.new_from_resource('/img/shards.png');
|
const shardData = GdkPixbuf.Pixbuf.new_from_resource('/img/shards.png');
|
||||||
this._shardTexture = new Clutter.Image();
|
shardTexture = new Clutter.Image();
|
||||||
this._shardTexture.set_data(shardData.get_pixels(), Cogl.PixelFormat.RGB_888,
|
shardTexture.set_data(shardData.get_pixels(), Cogl.PixelFormat.RGB_888,
|
||||||
shardData.width, shardData.height, shardData.rowstride);
|
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.
|
// Usually, the shards fly away from the center of the window.
|
||||||
let epicenterX = 0.5;
|
let epicenterX = 0.5;
|
||||||
let epicenterY = 0.5;
|
let epicenterY = 0.5;
|
||||||
@@ -155,76 +197,95 @@ if (utils.isInShellProcess()) {
|
|||||||
// If we are currently performing integration test, the animation uses a fixed seed.
|
// If we are currently performing integration test, the animation uses a fixed seed.
|
||||||
const testMode = settings.get_boolean('test-mode');
|
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 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()}
|
||||||
|
|
||||||
|
uniform bool uForOpening;
|
||||||
uniform sampler2D uShardTexture;
|
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 SHARD_LAYERS = 5;
|
||||||
const float BLOW_FORCE = ${settings.get_double('broken-glass-blow-force')};
|
|
||||||
const float ACTOR_SCALE = 2.0;
|
const float ACTOR_SCALE = 2.0;
|
||||||
const float PADDING = ACTOR_SCALE / 2.0 - 0.5;
|
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<SHARD_LAYERS; ++i) {
|
||||||
|
|
||||||
// Draw the individual shard layers.
|
// To enable drawing shards outside of the window bounds, the actor was scaled
|
||||||
for (float i=0; i<SHARD_LAYERS; ++i) {
|
// by ACTOR_SCALE. Here we scale and move the texture coordinates so that the
|
||||||
|
// window gets drawn at the correct position again.
|
||||||
|
vec2 coords = cogl_tex_coord_in[0].st * ACTOR_SCALE - PADDING;
|
||||||
|
|
||||||
// To enable drawing shards outside of the window bounds, the actor was scaled
|
// Scale and rotate around our epicenter.
|
||||||
// by ACTOR_SCALE. Here we scale and move the texture coordinates so that the
|
coords -= uEpicenter;
|
||||||
// window gets drawn at the correct position again.
|
|
||||||
vec2 coords = cogl_tex_coord_in[0].st * ACTOR_SCALE - PADDING;
|
|
||||||
|
|
||||||
// Scale and rotate around our epicenter.
|
// Scale each layer a bit differently.
|
||||||
coords -= EPICENTER;
|
coords /= mix(1.0, 1.0 + uBlowForce*(i+2)/SHARD_LAYERS, progress);
|
||||||
|
|
||||||
|
// Rotate each layer a bit differently.
|
||||||
|
float rotation = (mod(i, 2.0)-0.5)*0.2*progress;
|
||||||
|
coords = vec2(coords.x * cos(rotation) - coords.y * sin(rotation),
|
||||||
|
coords.x * sin(rotation) + coords.y * cos(rotation));
|
||||||
|
|
||||||
|
// Move down each layer a bit.
|
||||||
|
float gravity = (uForOpening ? -1.0 : 1.0) * uGravity*0.1*(i+1)*progress*progress;
|
||||||
|
coords += vec2(0, gravity);
|
||||||
|
|
||||||
// Scale each layer a bit differently.
|
// Restore correct position.
|
||||||
coords /= mix(1.0, 1.0 + BLOW_FORCE*(i+2)/SHARD_LAYERS, progress);
|
coords += uEpicenter;
|
||||||
|
|
||||||
// Rotate each layer a bit differently.
|
|
||||||
float rotation = (mod(i, 2.0)-0.5)*0.2*progress;
|
|
||||||
coords = vec2(coords.x * cos(rotation) - coords.y * sin(rotation),
|
|
||||||
coords.x * sin(rotation) + coords.y * cos(rotation));
|
|
||||||
|
|
||||||
// Move down each layer a bit.
|
|
||||||
float gravity = GRAVITY*0.1*(i+1)*progress*progress;
|
|
||||||
coords += vec2(0, gravity);
|
|
||||||
|
|
||||||
// Restore correct position.
|
// Retrieve information from the shard texture for our layer.
|
||||||
coords += EPICENTER;
|
vec2 shardCoords = (coords + uSeed) * vec2(uSizeX, uSizeY) / uShardScale / 500.0;
|
||||||
|
vec2 shardMap = texture2D(uShardTexture, shardCoords).rg;
|
||||||
// Retrieve information from the shard texture for our layer.
|
|
||||||
vec2 shardCoords = (coords + SEED) * vec2(uSizeX, uSizeY) / SHARD_SCALE / 500.0;
|
|
||||||
vec2 shardMap = texture2D(uShardTexture, shardCoords).rg;
|
|
||||||
|
|
||||||
// The green channel contains a random value in [0..1] for each shard. We
|
// The green channel contains a random value in [0..1] for each shard. We
|
||||||
// discretize this into SHARD_LAYERS bins and check if our layer falls into
|
// discretize this into SHARD_LAYERS bins and check if our layer falls into
|
||||||
// the bin of the current shard.
|
// the bin of the current shard.
|
||||||
float shardGroup = floor(shardMap.g * SHARD_LAYERS * 0.999);
|
float shardGroup = floor(shardMap.g * SHARD_LAYERS * 0.999);
|
||||||
|
|
||||||
if (shardGroup == i) {
|
if (shardGroup == i && (shardMap.x - pow(progress+0.1, 2)) > 0) {
|
||||||
vec4 windowColor = texture2D(uTexture, coords);
|
cogl_color_out = texture2D(uTexture, coords);
|
||||||
|
|
||||||
// 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);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
`);
|
|
||||||
};
|
// Shell.GLSLEffect uses straight alpha. So we have to convert from premultiplied.
|
||||||
|
if (cogl_color_out.a > 0) {
|
||||||
|
cogl_color_out.rgb /= cogl_color_out.a;
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
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
|
// 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
|
// impossible under GNOME 3.3x as this.get_pipeline() is not available. It was called
|
||||||
@@ -238,9 +299,9 @@ if (utils.isInShellProcess()) {
|
|||||||
Cogl.PipelineFilter.LINEAR);
|
Cogl.PipelineFilter.LINEAR);
|
||||||
|
|
||||||
// Bind the shard texture.
|
// 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_layer_wrap_mode(1, Cogl.PipelineWrapMode.REPEAT);
|
||||||
this.set_uniform_value('uShardTexture', 1);
|
pipeline.set_uniform_1i(this._uShardTexture, 1);
|
||||||
|
|
||||||
super.vfunc_paint_target(node, paint_context);
|
super.vfunc_paint_target(node, paint_context);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -25,8 +25,13 @@ const utils = Me.imports.src.utils;
|
|||||||
// This effect looks a bit like the transporter effect from TOS. //
|
// This effect looks a bit like the transporter effect from TOS. //
|
||||||
//////////////////////////////////////////////////////////////////////////////////////////
|
//////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
// 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, 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
|
// 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
|
||||||
@@ -75,9 +80,21 @@ var EnergizeA = class EnergizeA {
|
|||||||
|
|
||||||
// ---------------------------------------------------------------- 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. It returns an instance of the shader class, trying to reuse previously
|
||||||
return new Shader(settings, forOpening);
|
// 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
|
// The tweakTransition() is called from extension.js to tweak a window's open / close
|
||||||
@@ -97,6 +114,12 @@ var EnergizeA = class EnergizeA {
|
|||||||
'scale-y': {from: 1.0, to: 1.0, mode: 3}
|
'scale-y': {from: 1.0, to: 1.0, mode: 3}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// This is called from extension.js if the extension is disabled. This should free all
|
||||||
|
// static resources.
|
||||||
|
static cleanUp() {
|
||||||
|
freeShaders = [];
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -108,79 +131,106 @@ var EnergizeA = class EnergizeA {
|
|||||||
|
|
||||||
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();
|
||||||
|
|
||||||
const color = Clutter.Color.from_string(settings.get_string('energize-a-color'))[1];
|
this._uForOpening = this.get_uniform_location('uForOpening');
|
||||||
|
this._uColor = this.get_uniform_location('uColor');
|
||||||
|
this._uScale = this.get_uniform_location('uScale');
|
||||||
|
}
|
||||||
|
|
||||||
// If we are currently performing integration test, the animation uses a fixed seed.
|
// This is called each time the effect is used. This can be used to retrieve the
|
||||||
const testMode = settings.get_boolean('test-mode');
|
// configuration from the settings and update all uniforms accordingly.
|
||||||
|
setUniforms(actor, settings, forOpening) {
|
||||||
|
const c = Clutter.Color.from_string(settings.get_string('energize-a-color'))[1];
|
||||||
|
|
||||||
this.set_shader_source(`
|
// clang-format off
|
||||||
|
this.set_uniform_float(this._uForOpening, 1, [forOpening]);
|
||||||
|
this.set_uniform_float(this._uColor, 3, [c.red / 255, c.green / 255, c.blue / 255]);
|
||||||
|
this.set_uniform_float(this._uScale, 1, [settings.get_double('energize-a-scale')]);
|
||||||
|
// clang-format on
|
||||||
|
}
|
||||||
|
|
||||||
// Inject some common shader snippets.
|
// This is called by extension.js when the shader is not used anymore. We will store
|
||||||
${shaderSnippets.standardUniforms()}
|
// this instance of the shader so that it can be re-used in th future.
|
||||||
${shaderSnippets.noise()}
|
free() {
|
||||||
${shaderSnippets.edgeMask()}
|
freeShaders.push(this);
|
||||||
|
}
|
||||||
|
|
||||||
const vec2 SEED = vec2(${testMode ? 0 : Math.random()},
|
// This is called by the constructor. This means, it's only called when the effect
|
||||||
${testMode ? 0 : Math.random()});
|
// is used for the first time.
|
||||||
const float FADE_IN_TIME = 0.3;
|
vfunc_build_pipeline() {
|
||||||
const float FADE_OUT_TIME = 0.6;
|
const declarations = `
|
||||||
const float HEART_FADE_TIME = 0.3;
|
// Inject some common shader snippets.
|
||||||
const float EDGE_FADE_WIDTH = 50;
|
${shaderSnippets.standardUniforms()}
|
||||||
|
${shaderSnippets.noise()}
|
||||||
|
${shaderSnippets.edgeMask()}
|
||||||
|
|
||||||
// This method returns two values:
|
uniform bool uForOpening;
|
||||||
// result.x: A mask for the particles.
|
uniform vec3 uColor;
|
||||||
// result.y: The opacity of the fading window.
|
uniform float uScale;
|
||||||
vec2 getMasks() {
|
|
||||||
float fadeInProgress = clamp(uProgress/FADE_IN_TIME, 0, 1);
|
|
||||||
float fadeOutProgress = clamp((uProgress-FADE_IN_TIME)/FADE_OUT_TIME, 0, 1);
|
|
||||||
float heartProgress = clamp((uProgress-(1.0-HEART_FADE_TIME))/HEART_FADE_TIME, 0, 1);
|
|
||||||
|
|
||||||
// Compute mask for the "atom" particles.
|
const float FADE_IN_TIME = 0.3;
|
||||||
float dist = length(cogl_tex_coord_in[0].st - 0.5) * 4.0;
|
const float FADE_OUT_TIME = 0.6;
|
||||||
float atomMask = smoothstep(0.0, 1.0, (fadeInProgress * 2.0 - dist + 1.0));
|
const float HEART_FADE_TIME = 0.3;
|
||||||
atomMask *= fadeInProgress;
|
const float EDGE_FADE_WIDTH = 50;
|
||||||
atomMask *= smoothstep(1.0, 0.0, fadeOutProgress);
|
|
||||||
|
|
||||||
// Fade-out the masks at the window edges.
|
// This method returns two values:
|
||||||
float edgeFade = getAbsoluteEdgeMask(EDGE_FADE_WIDTH);
|
// result.x: A mask for the particles.
|
||||||
atomMask *= edgeFade;
|
// result.y: The opacity of the fading window.
|
||||||
|
vec2 getMasks() {
|
||||||
|
float fadeInProgress = clamp(uProgress/FADE_IN_TIME, 0, 1);
|
||||||
|
float fadeOutProgress = clamp((uProgress-FADE_IN_TIME)/FADE_OUT_TIME, 0, 1);
|
||||||
|
float heartProgress = clamp((uProgress-(1.0-HEART_FADE_TIME))/HEART_FADE_TIME, 0, 1);
|
||||||
|
|
||||||
float heartMask = getRelativeEdgeMask(0.5);
|
// Compute mask for the "atom" particles.
|
||||||
heartMask = 3.0 * pow(heartMask, 5);
|
float dist = length(cogl_tex_coord_in[0].st - 0.5) * 4.0;
|
||||||
heartMask *= fadeOutProgress;
|
float atomMask = smoothstep(0.0, 1.0, (fadeInProgress * 2.0 - dist + 1.0));
|
||||||
heartMask *= 1.0 - heartProgress;
|
atomMask *= fadeInProgress;
|
||||||
atomMask = clamp(heartMask+atomMask, 0, 1);
|
atomMask *= smoothstep(1.0, 0.0, fadeOutProgress);
|
||||||
|
|
||||||
// Compute fading window opacity.
|
// Fade-out the masks at the window edges.
|
||||||
float windowMask = pow(1.0 - fadeOutProgress, 2.0);
|
float edgeFade = getAbsoluteEdgeMask(EDGE_FADE_WIDTH);
|
||||||
|
atomMask *= edgeFade;
|
||||||
|
|
||||||
#if ${forOpening ? '1' : '0'}
|
float heartMask = getRelativeEdgeMask(0.5);
|
||||||
windowMask = 1.0 - windowMask;
|
heartMask = 3.0 * pow(heartMask, 5);
|
||||||
#endif
|
heartMask *= fadeOutProgress;
|
||||||
|
heartMask *= 1.0 - heartProgress;
|
||||||
|
atomMask = clamp(heartMask+atomMask, 0, 1);
|
||||||
|
|
||||||
return vec2(atomMask, windowMask);
|
// Compute fading window opacity.
|
||||||
}
|
float windowMask = pow(1.0 - fadeOutProgress, 2.0);
|
||||||
|
|
||||||
void main() {
|
if (uForOpening) {
|
||||||
|
windowMask = 1.0 - windowMask;
|
||||||
|
}
|
||||||
|
|
||||||
|
return vec2(atomMask, windowMask);
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
const code = `
|
||||||
vec2 masks = getMasks();
|
vec2 masks = getMasks();
|
||||||
vec4 windowColor = texture2D(uTexture, cogl_tex_coord_in[0].st);
|
vec4 windowColor = texture2D(uTexture, cogl_tex_coord_in[0].st);
|
||||||
vec3 effectColor = vec3(${color.red / 255},
|
|
||||||
${color.green / 255},
|
// Shell.GLSLEffect uses straight alpha. So we have to convert from premultiplied.
|
||||||
${color.blue / 255});
|
if (windowColor.a > 0) {
|
||||||
|
windowColor.rgb /= windowColor.a;
|
||||||
|
}
|
||||||
|
|
||||||
// Dissolve window to effect color / transparency.
|
// Dissolve window to effect color / transparency.
|
||||||
cogl_color_out = mix(vec4(effectColor, 1.0) * windowColor.a, windowColor, 0.2 * masks.y + 0.8) * masks.y;
|
cogl_color_out.rgb = mix(uColor, windowColor.rgb, 0.2 * masks.y + 0.8);
|
||||||
|
cogl_color_out.a = windowColor.a * masks.y;
|
||||||
|
|
||||||
vec2 scaledUV = (cogl_tex_coord_in[0].st-0.5) * (1.0 + 0.1*uProgress);
|
vec2 scaledUV = (cogl_tex_coord_in[0].st-0.5) * (1.0 + 0.1*uProgress);
|
||||||
scaledUV /= ${settings.get_double('energize-a-scale')};
|
scaledUV /= uScale;
|
||||||
|
|
||||||
// Add molecule particles.
|
// Add molecule particles.
|
||||||
vec2 uv = scaledUV + vec2(0, 0.1*uTime);
|
vec2 uv = scaledUV + vec2(0, 0.1*uTime);
|
||||||
@@ -194,15 +244,17 @@ if (utils.isInShellProcess()) {
|
|||||||
particles += 0.5 * pow(0.2 * (1.0 / (1.0 - atoms)-1.0), 2);
|
particles += 0.5 * pow(0.2 * (1.0 / (1.0 - atoms)-1.0), 2);
|
||||||
}
|
}
|
||||||
|
|
||||||
cogl_color_out.rgb += effectColor * particles * masks.x;
|
cogl_color_out.rgb += uColor * particles * masks.x;
|
||||||
|
cogl_color_out.a += particles * masks.x;
|
||||||
|
|
||||||
// These are pretty useful for understanding how this works.
|
// These are pretty useful for understanding how this works.
|
||||||
// cogl_color_out = vec4(masks, 0.0, 1.0);
|
// cogl_color_out = vec4(masks, 0.0, 1.0);
|
||||||
// cogl_color_out = vec4(vec3(masks.x), 1.0);
|
// cogl_color_out = vec4(vec3(masks.x), 1.0);
|
||||||
// cogl_color_out = vec4(vec3(masks.y), 1.0);
|
// cogl_color_out = vec4(vec3(masks.y), 1.0);
|
||||||
// cogl_color_out = vec4(vec3(particles), 1.0);
|
// cogl_color_out = vec4(vec3(particles), 1.0);
|
||||||
}
|
`;
|
||||||
`);
|
|
||||||
};
|
this.add_glsl_snippet(Shell.SnippetHook.FRAGMENT, declarations, code, true);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -25,8 +25,13 @@ const utils = Me.imports.src.utils;
|
|||||||
// This effect looks a bit like the transporter effect from TNG. //
|
// This effect looks a bit like the transporter effect from TNG. //
|
||||||
//////////////////////////////////////////////////////////////////////////////////////////
|
//////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
// 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, 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
|
// 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
|
||||||
@@ -75,9 +80,21 @@ var EnergizeB = class EnergizeB {
|
|||||||
|
|
||||||
// ---------------------------------------------------------------- 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. It returns an instance of the shader class, trying to reuse previously
|
||||||
return new Shader(settings, forOpening);
|
// 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
|
// The tweakTransition() is called from extension.js to tweak a window's open / close
|
||||||
@@ -97,6 +114,12 @@ var EnergizeB = class EnergizeB {
|
|||||||
'scale-y': {from: 1.0, to: 1.0, mode: 3}
|
'scale-y': {from: 1.0, to: 1.0, mode: 3}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// This is called from extension.js if the extension is disabled. This should free all
|
||||||
|
// static resources.
|
||||||
|
static cleanUp() {
|
||||||
|
freeShaders = [];
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -108,112 +131,141 @@ var EnergizeB = class EnergizeB {
|
|||||||
|
|
||||||
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();
|
||||||
|
|
||||||
const color = Clutter.Color.from_string(settings.get_string('energize-b-color'))[1];
|
this._uForOpening = this.get_uniform_location('uForOpening');
|
||||||
|
this._uColor = this.get_uniform_location('uColor');
|
||||||
|
this._uScale = this.get_uniform_location('uScale');
|
||||||
|
}
|
||||||
|
|
||||||
// If we are currently performing integration test, the animation uses a fixed seed.
|
// This is called each time the effect is used. This can be used to retrieve the
|
||||||
const testMode = settings.get_boolean('test-mode');
|
// configuration from the settings and update all uniforms accordingly.
|
||||||
|
setUniforms(actor, settings, forOpening) {
|
||||||
|
const c = Clutter.Color.from_string(settings.get_string('energize-b-color'))[1];
|
||||||
|
|
||||||
this.set_shader_source(`
|
// clang-format off
|
||||||
|
this.set_uniform_float(this._uForOpening, 1, [forOpening]);
|
||||||
|
this.set_uniform_float(this._uColor, 3, [c.red / 255, c.green / 255, c.blue / 255]);
|
||||||
|
this.set_uniform_float(this._uScale, 1, [settings.get_double('energize-b-scale')]);
|
||||||
|
// clang-format on
|
||||||
|
}
|
||||||
|
|
||||||
// Inject some common shader snippets.
|
// This is called by extension.js when the shader is not used anymore. We will store
|
||||||
${shaderSnippets.standardUniforms()}
|
// this instance of the shader so that it can be re-used in th future.
|
||||||
${shaderSnippets.noise()}
|
free() {
|
||||||
${shaderSnippets.edgeMask()}
|
freeShaders.push(this);
|
||||||
|
}
|
||||||
|
|
||||||
const vec2 SEED = vec2(${testMode ? 0 : Math.random()},
|
// This is called by the constructor. This means, it's only called when the effect
|
||||||
${testMode ? 0 : Math.random()});
|
// is used for the first time.
|
||||||
const float SHOWER_TIME = 0.3;
|
vfunc_build_pipeline() {
|
||||||
const float SHOWER_WIDTH = 0.3;
|
const declarations = `
|
||||||
const float STREAK_TIME = 0.6;
|
// Inject some common shader snippets.
|
||||||
const float EDGE_FADE = 50;
|
${shaderSnippets.standardUniforms()}
|
||||||
const float SCALE = ${settings.get_double('energize-b-scale')};
|
${shaderSnippets.noise()}
|
||||||
|
${shaderSnippets.edgeMask()}
|
||||||
|
|
||||||
// This method returns four values:
|
uniform bool uForOpening;
|
||||||
// result.x: A mask for the particles which lead the shower.
|
uniform vec3 uColor;
|
||||||
// result.y: A mask for the streaks which follow the shower particles.
|
uniform float uScale;
|
||||||
// result.z: A mask for the final "atom" particles.
|
|
||||||
// result.w: The opacity of the fading window.
|
|
||||||
vec4 getMasks() {
|
|
||||||
float showerProgress = uProgress/SHOWER_TIME;
|
|
||||||
float streakProgress = clamp((uProgress-SHOWER_TIME)/STREAK_TIME, 0, 1);
|
|
||||||
float fadeProgress = clamp((uProgress-SHOWER_TIME)/(1.0 - SHOWER_TIME), 0, 1);
|
|
||||||
|
|
||||||
// Gradient from top to bottom.
|
const float SHOWER_TIME = 0.3;
|
||||||
float t = cogl_tex_coord_in[0].t;
|
const float SHOWER_WIDTH = 0.3;
|
||||||
|
const float STREAK_TIME = 0.6;
|
||||||
|
const float EDGE_FADE = 50;
|
||||||
|
|
||||||
// A smooth gradient which moves to the bottom within the showerProgress.
|
// This method returns four values:
|
||||||
float showerMask = smoothstep(1, 0, abs(showerProgress - t - SHOWER_WIDTH) / SHOWER_WIDTH);
|
// result.x: A mask for the particles which lead the shower.
|
||||||
|
// result.y: A mask for the streaks which follow the shower particles.
|
||||||
|
// result.z: A mask for the final "atom" particles.
|
||||||
|
// result.w: The opacity of the fading window.
|
||||||
|
vec4 getMasks() {
|
||||||
|
float showerProgress = uProgress/SHOWER_TIME;
|
||||||
|
float streakProgress = clamp((uProgress-SHOWER_TIME)/STREAK_TIME, 0, 1);
|
||||||
|
float fadeProgress = clamp((uProgress-SHOWER_TIME)/(1.0 - SHOWER_TIME), 0, 1);
|
||||||
|
|
||||||
// This is 1 above the streak mask.
|
// Gradient from top to bottom.
|
||||||
float streakMask = (showerProgress - t - SHOWER_WIDTH) > 0 ? 1 : 0;
|
float t = cogl_tex_coord_in[0].t;
|
||||||
|
|
||||||
// Compute mask for the "atom" particles.
|
// A smooth gradient which moves to the bottom within the showerProgress.
|
||||||
float atomMask = getRelativeEdgeMask(0.2);
|
float showerMask = smoothstep(1, 0, abs(showerProgress - t - SHOWER_WIDTH) / SHOWER_WIDTH);
|
||||||
atomMask = max(0, atomMask - showerMask);
|
|
||||||
atomMask *= streakMask;
|
|
||||||
atomMask *= sqrt(1-fadeProgress*fadeProgress);
|
|
||||||
|
|
||||||
// Make some particles visible in the streaks.
|
// This is 1 above the streak mask.
|
||||||
showerMask += 0.05 * streakMask;
|
float streakMask = (showerProgress - t - SHOWER_WIDTH) > 0 ? 1 : 0;
|
||||||
|
|
||||||
// Add shower mask to streak mask.
|
// Compute mask for the "atom" particles.
|
||||||
streakMask = max(streakMask, showerMask);
|
float atomMask = getRelativeEdgeMask(0.2);
|
||||||
|
atomMask = max(0, atomMask - showerMask);
|
||||||
|
atomMask *= streakMask;
|
||||||
|
atomMask *= sqrt(1-fadeProgress*fadeProgress);
|
||||||
|
|
||||||
// Fade-out the masks at the window edges.
|
// Make some particles visible in the streaks.
|
||||||
float edgeFade = getAbsoluteEdgeMask(EDGE_FADE);
|
showerMask += 0.05 * streakMask;
|
||||||
streakMask *= edgeFade;
|
|
||||||
showerMask *= edgeFade;
|
|
||||||
|
|
||||||
// Fade-out the masks from top to bottom.
|
|
||||||
float fade = smoothstep(0.0, 1.0, 1.0 + t - 2.0 * streakProgress);
|
|
||||||
streakMask *= fade;
|
|
||||||
showerMask *= fade;
|
|
||||||
|
|
||||||
// Compute fading window opacity.
|
// Add shower mask to streak mask.
|
||||||
float windowMask = pow(1.0 - fadeProgress, 2.0);
|
streakMask = max(streakMask, showerMask);
|
||||||
|
|
||||||
#if ${forOpening ? '1' : '0'}
|
// Fade-out the masks at the window edges.
|
||||||
windowMask = 1.0 - windowMask;
|
float edgeFade = getAbsoluteEdgeMask(EDGE_FADE);
|
||||||
#endif
|
streakMask *= edgeFade;
|
||||||
|
showerMask *= edgeFade;
|
||||||
|
|
||||||
|
// Fade-out the masks from top to bottom.
|
||||||
|
float fade = smoothstep(0.0, 1.0, 1.0 + t - 2.0 * streakProgress);
|
||||||
|
streakMask *= fade;
|
||||||
|
showerMask *= fade;
|
||||||
|
|
||||||
return vec4(showerMask, streakMask, atomMask, windowMask);
|
// Compute fading window opacity.
|
||||||
}
|
float windowMask = pow(1.0 - fadeProgress, 2.0);
|
||||||
|
|
||||||
void main() {
|
if (uForOpening) {
|
||||||
|
windowMask = 1.0 - windowMask;
|
||||||
|
}
|
||||||
|
|
||||||
|
return vec4(showerMask, streakMask, atomMask, windowMask);
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
const code = `
|
||||||
vec4 masks = getMasks();
|
vec4 masks = getMasks();
|
||||||
vec4 windowColor = texture2D(uTexture, cogl_tex_coord_in[0].st);
|
vec4 windowColor = texture2D(uTexture, cogl_tex_coord_in[0].st);
|
||||||
vec3 effectColor = vec3(${color.red / 255},
|
|
||||||
${color.green / 255},
|
// Shell.GLSLEffect uses straight alpha. So we have to convert from premultiplied.
|
||||||
${color.blue / 255});
|
if (windowColor.a > 0) {
|
||||||
|
windowColor.rgb /= windowColor.a;
|
||||||
|
}
|
||||||
|
|
||||||
// Dissolve window to effect color / transparency.
|
// Dissolve window to effect color / transparency.
|
||||||
cogl_color_out = mix(vec4(effectColor, 1.0) * windowColor.a, windowColor, 0.5 * masks.w + 0.5) * masks.w;
|
cogl_color_out.rgb = mix(uColor, windowColor.rgb, 0.5 * masks.w + 0.5);
|
||||||
|
cogl_color_out.a = windowColor.a * masks.w;
|
||||||
|
|
||||||
// Add leading shower particles.
|
// Add leading shower particles.
|
||||||
vec2 showerUV = cogl_tex_coord_in[0].st + vec2(0, -0.7*uProgress/SHOWER_TIME);
|
vec2 showerUV = cogl_tex_coord_in[0].st + vec2(0, -0.7*uProgress/SHOWER_TIME);
|
||||||
showerUV *= 0.02 * vec2(uSizeX, uSizeY) / SCALE;
|
showerUV *= 0.02 * vec2(uSizeX, uSizeY) / uScale;
|
||||||
float shower = pow(simplex2D(showerUV), 10.0);
|
float shower = pow(simplex2D(showerUV), 10.0);
|
||||||
cogl_color_out.rgb += effectColor * shower * masks.x;
|
cogl_color_out.rgb += uColor * shower * masks.x;
|
||||||
|
cogl_color_out.a += shower * masks.x;
|
||||||
|
|
||||||
// Add trailing streak lines.
|
// Add trailing streak lines.
|
||||||
vec2 streakUV = cogl_tex_coord_in[0].st + vec2(0, -uProgress/SHOWER_TIME);
|
vec2 streakUV = cogl_tex_coord_in[0].st + vec2(0, -uProgress/SHOWER_TIME);
|
||||||
streakUV *= vec2(0.05 * uSizeX, 0.001 * uSizeY) / SCALE;
|
streakUV *= vec2(0.05 * uSizeX, 0.001 * uSizeY) / uScale;
|
||||||
float streaks = simplex2DFractal(streakUV) * 0.5;
|
float streaks = simplex2DFractal(streakUV) * 0.5;
|
||||||
cogl_color_out.rgb += effectColor * streaks * masks.y;
|
cogl_color_out.rgb += uColor * streaks * masks.y;
|
||||||
|
cogl_color_out.a += streaks * masks.y;
|
||||||
|
|
||||||
// Add glimmering atoms.
|
// Add glimmering atoms.
|
||||||
vec2 atomUV = cogl_tex_coord_in[0].st + vec2(0, -0.025*uProgress/SHOWER_TIME);
|
vec2 atomUV = cogl_tex_coord_in[0].st + vec2(0, -0.025*uProgress/SHOWER_TIME);
|
||||||
atomUV *= 0.2 * vec2(uSizeX, uSizeY) / SCALE;
|
atomUV *= 0.2 * vec2(uSizeX, uSizeY) / uScale;
|
||||||
float atoms = pow((simplex3D(vec3(atomUV, uTime))), 5.0);
|
float atoms = pow((simplex3D(vec3(atomUV, uTime))), 5.0);
|
||||||
cogl_color_out.rgb += effectColor * atoms * masks.z;
|
cogl_color_out.rgb += uColor * atoms * masks.z;
|
||||||
|
cogl_color_out.a += atoms * masks.z;
|
||||||
|
|
||||||
// These are pretty useful for understanding how this works.
|
// These are pretty useful for understanding how this works.
|
||||||
// cogl_color_out = vec4(masks.rgb, 1.0);
|
// cogl_color_out = vec4(masks.rgb, 1.0);
|
||||||
@@ -224,8 +276,9 @@ if (utils.isInShellProcess()) {
|
|||||||
// cogl_color_out = vec4(vec3(shower), 1.0);
|
// cogl_color_out = vec4(vec3(shower), 1.0);
|
||||||
// cogl_color_out = vec4(vec3(streaks), 1.0);
|
// cogl_color_out = vec4(vec3(streaks), 1.0);
|
||||||
// cogl_color_out = vec4(vec3(atoms), 1.0);
|
// cogl_color_out = vec4(vec3(atoms), 1.0);
|
||||||
}
|
`;
|
||||||
`);
|
|
||||||
};
|
this.add_glsl_snippet(Shell.SnippetHook.FRAGMENT, declarations, code, true);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -24,13 +24,18 @@ 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, 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
|
// 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 +102,21 @@ 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. It returns an instance of the shader class, trying to reuse previously
|
||||||
return new Shader(settings, forOpening);
|
// 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
|
// The tweakTransition() is called from extension.js to tweak a window's open / close
|
||||||
@@ -120,6 +137,12 @@ var Fire = class Fire {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// This is called from extension.js if the extension is disabled. This should free all
|
||||||
|
// static resources.
|
||||||
|
static cleanUp() {
|
||||||
|
freeShaders = [];
|
||||||
|
}
|
||||||
|
|
||||||
// ----------------------------------------------------------------------- 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 +242,89 @@ 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(actor, 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(`
|
// clang-format off
|
||||||
|
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')]);
|
||||||
|
// 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 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()}
|
||||||
|
${shaderSnippets.compositing()}
|
||||||
|
|
||||||
|
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 +372,50 @@ 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;
|
|
||||||
|
|
||||||
// Map noise value to color.
|
// Get the window texture.
|
||||||
vec4 fire = getFireColor(noise);
|
cogl_color_out = texture2D(uTexture, cogl_tex_coord_in[0].st);
|
||||||
fire.rgb *= fire.a;
|
|
||||||
|
|
||||||
// Get the window texture and fade it according to the effect mask.
|
// Shell.GLSLEffect uses straight alpha. So we have to convert from premultiplied.
|
||||||
cogl_color_out = texture2D(uTexture, cogl_tex_coord_in[0].st) * effectMask.x;
|
if (cogl_color_out.a > 0) {
|
||||||
|
cogl_color_out.rgb /= cogl_color_out.a;
|
||||||
// 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);
|
|
||||||
}
|
}
|
||||||
`);
|
|
||||||
};
|
// Fade the window according to the effect mask.
|
||||||
|
cogl_color_out.a *= effectMask.x;
|
||||||
|
|
||||||
|
// Add the fire to the window.
|
||||||
|
cogl_color_out = alphaOver(cogl_color_out, 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);
|
||||||
|
`;
|
||||||
|
|
||||||
|
this.add_glsl_snippet(Shell.SnippetHook.FRAGMENT, declarations, code, true);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -26,8 +26,13 @@ const utils = Me.imports.src.utils;
|
|||||||
// gradually shrink until the window is fully dissolved. //
|
// gradually shrink until the window is fully dissolved. //
|
||||||
//////////////////////////////////////////////////////////////////////////////////////////
|
//////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
// 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, 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
|
// 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
|
||||||
@@ -79,9 +84,21 @@ var Hexagon = class Hexagon {
|
|||||||
|
|
||||||
// ---------------------------------------------------------------- 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. It returns an instance of the shader class, trying to reuse previously
|
||||||
return new Shader(settings, forOpening);
|
// 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
|
// The tweakTransition() is called from extension.js to tweak a window's open / close
|
||||||
@@ -101,6 +118,12 @@ var Hexagon = class Hexagon {
|
|||||||
'scale-y': {from: 1.0, to: 1.0, mode: 1}
|
'scale-y': {from: 1.0, to: 1.0, mode: 1}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// This is called from extension.js if the extension is disabled. This should free all
|
||||||
|
// static resources.
|
||||||
|
static cleanUp() {
|
||||||
|
freeShaders = [];
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -112,34 +135,71 @@ var Hexagon = class Hexagon {
|
|||||||
|
|
||||||
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();
|
||||||
|
|
||||||
|
this._uForOpening = this.get_uniform_location('uForOpening');
|
||||||
|
this._uAdditiveBlending = this.get_uniform_location('uAdditiveBlending');
|
||||||
|
this._uSeed = this.get_uniform_location('uSeed');
|
||||||
|
this._uScale = this.get_uniform_location('uScale');
|
||||||
|
this._uLineWidth = this.get_uniform_location('uLineWidth');
|
||||||
|
this._uGlowColor = this.get_uniform_location('uGlowColor');
|
||||||
|
this._uLineColor = this.get_uniform_location('uLineColor');
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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) {
|
||||||
|
|
||||||
// Get the two configurable colors. They are directly injected into the shader code
|
// Get the two configurable colors. They are directly injected into the shader code
|
||||||
// below.
|
// below.
|
||||||
const glowColor =
|
const gc = Clutter.Color.from_string(settings.get_string('hexagon-glow-color'))[1];
|
||||||
Clutter.Color.from_string(settings.get_string('hexagon-glow-color'))[1];
|
const lc = Clutter.Color.from_string(settings.get_string('hexagon-line-color'))[1];
|
||||||
const lineColor =
|
|
||||||
Clutter.Color.from_string(settings.get_string('hexagon-line-color'))[1];
|
|
||||||
|
|
||||||
// If we are currently performing integration test, the animation uses a fixed seed.
|
// If we are currently performing integration test, the animation uses a fixed seed.
|
||||||
const testMode = settings.get_boolean('test-mode');
|
const testMode = settings.get_boolean('test-mode');
|
||||||
|
|
||||||
|
// clang-format off
|
||||||
|
this.set_uniform_float(this._uForOpening, 1, [forOpening]);
|
||||||
|
this.set_uniform_float(this._uAdditiveBlending, 1, [settings.get_boolean('hexagon-additive-blending')]);
|
||||||
|
this.set_uniform_float(this._uSeed, 2, [testMode ? 0 : Math.random(), testMode ? 0 : Math.random()]);
|
||||||
|
this.set_uniform_float(this._uScale, 1, [settings.get_double('hexagon-scale')]);
|
||||||
|
this.set_uniform_float(this._uLineWidth, 1, [settings.get_double('hexagon-line-width')]);
|
||||||
|
this.set_uniform_float(this._uGlowColor, 4, [gc.red / 255, gc.green / 255, gc.blue / 255, gc.alpha / 255]);
|
||||||
|
this.set_uniform_float(this._uLineColor, 4, [lc.red / 255, lc.green / 255, lc.blue / 255, lc.alpha / 255]);
|
||||||
|
// 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 means, it's only called when the effect
|
||||||
|
// is used for the first time.
|
||||||
|
vfunc_build_pipeline() {
|
||||||
|
|
||||||
// Feel free to read the inline comments below to learn how this shader works.
|
// Feel free to read the inline comments below to learn how this shader works.
|
||||||
this.set_shader_source(`
|
const declarations = `
|
||||||
|
|
||||||
// Inject some common shader snippets.
|
// Inject some common shader snippets.
|
||||||
${shaderSnippets.standardUniforms()}
|
${shaderSnippets.standardUniforms()}
|
||||||
${shaderSnippets.noise()}
|
${shaderSnippets.noise()}
|
||||||
|
|
||||||
const vec2 SEED = vec2(${testMode ? 0 : Math.random()},
|
uniform bool uForOpening;
|
||||||
${testMode ? 0 : Math.random()});
|
uniform bool uAdditiveBlending;
|
||||||
const float SCALE = 10.0 * ${settings.get_double('hexagon-scale')};
|
uniform vec2 uSeed;
|
||||||
const float LINE_WIDTH = 0.02 * ${settings.get_double('hexagon-line-width')};
|
uniform float uScale;
|
||||||
|
uniform float uLineWidth;
|
||||||
|
uniform vec4 uGlowColor;
|
||||||
|
uniform vec4 uLineColor;
|
||||||
|
|
||||||
// This methods generates a procedural hexagonal pattern. It returns four values:
|
// This methods generates a procedural hexagonal pattern. It returns four values:
|
||||||
// result.xy: This contains cell-relative coordinates for the given point.
|
// result.xy: This contains cell-relative coordinates for the given point.
|
||||||
@@ -182,69 +242,73 @@ if (utils.isInShellProcess()) {
|
|||||||
|
|
||||||
return vec4(cellCoords, dist, glow);
|
return vec4(cellCoords, dist, glow);
|
||||||
}
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
void main() {
|
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.
|
// Add some smooth noise to the progress so that not every tile behaves the
|
||||||
float progress = ${forOpening ? '1.0-uProgress' : 'uProgress'};
|
// same.
|
||||||
|
float noise = simplex2D(cogl_tex_coord_in[0].st + uSeed);
|
||||||
|
progress = clamp(mix(noise - 1.0, noise + 1.0, progress), 0.0, 1.0);
|
||||||
|
|
||||||
// Add some smooth noise to the progress so that not every tile behaves the
|
// glowProgress fades in in the first half of the animation, tileProgress fades
|
||||||
// same.
|
// in in the second half.
|
||||||
float noise = simplex2D(cogl_tex_coord_in[0].st + SEED);
|
float glowProgress = smoothstep(0, 1, clamp(progress / 0.5, 0, 1));
|
||||||
progress = clamp(mix(noise - 1.0, noise + 1.0, progress), 0.0, 1.0);
|
float tileProgress = smoothstep(0, 1, clamp((progress - 0.5) / 0.5, 0, 1));
|
||||||
|
|
||||||
// glowProgress fades in in the first half of the animation, tileProgress fades
|
vec2 texScale = 0.1 * vec2(uSizeX, uSizeY) / uScale;
|
||||||
// in in the second half.
|
vec4 hex = getHexagons(cogl_tex_coord_in[0].st * texScale);
|
||||||
float glowProgress = smoothstep(0, 1, clamp(progress / 0.5, 0, 1));
|
|
||||||
float tileProgress = smoothstep(0, 1, clamp((progress - 0.5) / 0.5, 0, 1));
|
|
||||||
|
|
||||||
vec2 texScale = vec2(uSizeX, uSizeY) / SCALE;
|
if (tileProgress > hex.z) {
|
||||||
vec4 hex = getHexagons(cogl_tex_coord_in[0].st * texScale);
|
|
||||||
|
|
||||||
if (tileProgress > hex.z) {
|
// Crop outer parts of the shrinking tiles.
|
||||||
|
cogl_color_out.a = 0.0;
|
||||||
|
|
||||||
// Crop outer parts of the shrinking tiles.
|
} else {
|
||||||
cogl_color_out *= vec4(0.0);
|
|
||||||
|
|
||||||
|
// Make the tiles shrink by offsetting the texture lookup towards the edge
|
||||||
|
// of the cell.
|
||||||
|
vec2 lookupOffset = tileProgress * hex.xy / texScale / (1.0 - tileProgress);
|
||||||
|
cogl_color_out = texture2D(uTexture, cogl_tex_coord_in[0].st + lookupOffset);
|
||||||
|
|
||||||
|
// Shell.GLSLEffect uses straight alpha. So we have to convert from premultiplied.
|
||||||
|
if (cogl_color_out.a > 0) {
|
||||||
|
cogl_color_out.rgb /= cogl_color_out.a;
|
||||||
|
}
|
||||||
|
|
||||||
|
vec4 glow = uGlowColor;
|
||||||
|
vec4 line = uLineColor;
|
||||||
|
|
||||||
|
// For the glow, we accumulate a few exponentially scaled versions of hex.w.
|
||||||
|
glow.a *= pow(hex.w, 20.0) * 10.0 +
|
||||||
|
pow(hex.w, 10.0) * 5.0 +
|
||||||
|
pow(hex.w, 2.0) * 0.5;
|
||||||
|
|
||||||
|
// Using step(uLineWidth, hex.z) would be simpler, but the below creates some
|
||||||
|
// fake antialiasing.
|
||||||
|
line.a *= 1.0 - smoothstep(uLineWidth*0.02*0.5, uLineWidth*0.02, hex.z);
|
||||||
|
|
||||||
|
// Fade in the glowing lines.
|
||||||
|
glow.a *= glowProgress;
|
||||||
|
line.a *= glowProgress;
|
||||||
|
|
||||||
|
// Do not add the hexagon lines onto transparent parts of the window.
|
||||||
|
glow *= cogl_color_out.a;
|
||||||
|
line *= cogl_color_out.a;
|
||||||
|
|
||||||
|
if (uAdditiveBlending) {
|
||||||
|
cogl_color_out.rgb += glow.rgb * glow.a;
|
||||||
|
cogl_color_out.rgb += line.rgb * line.a;
|
||||||
} else {
|
} else {
|
||||||
|
cogl_color_out.rgb = mix(cogl_color_out.rgb, glow.rgb, glow.a);
|
||||||
// Make the tiles shrink by offsetting the texture lookup towards the edge
|
cogl_color_out.rgb = mix(cogl_color_out.rgb, line.rgb, line.a);
|
||||||
// of the cell.
|
|
||||||
vec2 lookupOffset = tileProgress * hex.xy / texScale / (1.0 - tileProgress);
|
|
||||||
cogl_color_out = texture2D(uTexture, cogl_tex_coord_in[0].st + lookupOffset);
|
|
||||||
|
|
||||||
vec4 glow = vec4(${glowColor.red / 255}, ${glowColor.green / 255},
|
|
||||||
${glowColor.blue / 255}, ${glowColor.alpha / 255});
|
|
||||||
vec4 line = vec4(${lineColor.red / 255}, ${lineColor.green / 255},
|
|
||||||
${lineColor.blue / 255}, ${lineColor.alpha / 255});
|
|
||||||
|
|
||||||
// For the glow, we accumulate a few exponentially scaled versions of hex.w.
|
|
||||||
glow.a *= pow(hex.w, 20.0) * 10.0 +
|
|
||||||
pow(hex.w, 10.0) * 5.0 +
|
|
||||||
pow(hex.w, 2.0) * 0.5;
|
|
||||||
|
|
||||||
// Using step(LINE_WIDTH, hex.z) would be simpler, but the below creates some
|
|
||||||
// fake antialiasing.
|
|
||||||
line.a *= 1.0 - smoothstep(LINE_WIDTH*0.5, LINE_WIDTH, hex.z);
|
|
||||||
|
|
||||||
// Fade in the glowing lines.
|
|
||||||
glow.a *= glowProgress;
|
|
||||||
line.a *= glowProgress;
|
|
||||||
|
|
||||||
// Do not add the hexagon lines onto transparent parts of the window.
|
|
||||||
glow *= cogl_color_out.a;
|
|
||||||
line *= cogl_color_out.a;
|
|
||||||
|
|
||||||
if (${settings.get_boolean('hexagon-additive-blending')}) {
|
|
||||||
cogl_color_out.rgb += glow.rgb * glow.a;
|
|
||||||
cogl_color_out.rgb += line.rgb * line.a;
|
|
||||||
} else {
|
|
||||||
cogl_color_out.rgb = mix(cogl_color_out.rgb, glow.rgb, glow.a);
|
|
||||||
cogl_color_out.rgb = mix(cogl_color_out.rgb, line.rgb, line.a);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
`);
|
`;
|
||||||
};
|
|
||||||
|
this.add_glsl_snippet(Shell.SnippetHook.FRAGMENT, declarations, code, true);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -29,8 +29,16 @@ const utils = Me.imports.src.utils;
|
|||||||
// documentation of vfunc_paint_target further down in this file. //
|
// documentation of vfunc_paint_target further down in this file. //
|
||||||
//////////////////////////////////////////////////////////////////////////////////////////
|
//////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
// 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, 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
|
// 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
|
||||||
@@ -82,9 +90,21 @@ var Matrix = class Matrix {
|
|||||||
|
|
||||||
// ---------------------------------------------------------------- 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. It returns an instance of the shader class, trying to reuse previously
|
||||||
return new Shader(settings, forOpening);
|
// 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
|
// 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}
|
'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,75 @@ var Matrix = class Matrix {
|
|||||||
|
|
||||||
if (utils.isInShellProcess()) {
|
if (utils.isInShellProcess()) {
|
||||||
|
|
||||||
const {Clutter, GdkPixbuf, Cogl} = imports.gi;
|
const {Clutter, GdkPixbuf, Cogl, 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 font texture. As the shader is re-created for each window animation,
|
// Load the font texture.
|
||||||
// this texture is also re-created each time. This could be improved in the future!
|
if (fontTexture == null) {
|
||||||
const fontData = GdkPixbuf.Pixbuf.new_from_resource('/img/matrixFont.png');
|
const fontData = GdkPixbuf.Pixbuf.new_from_resource('/img/matrixFont.png');
|
||||||
this._fontTexture = new Clutter.Image();
|
fontTexture = new Clutter.Image();
|
||||||
this._fontTexture.set_data(
|
fontTexture.set_data(
|
||||||
fontData.get_pixels(),
|
fontData.get_pixels(),
|
||||||
fontData.has_alpha ? Cogl.PixelFormat.RGBA_8888 : Cogl.PixelFormat.RGB_888,
|
fontData.has_alpha ? Cogl.PixelFormat.RGBA_8888 : Cogl.PixelFormat.RGB_888,
|
||||||
fontData.width, fontData.height, fontData.rowstride);
|
fontData.width, fontData.height, fontData.rowstride);
|
||||||
|
}
|
||||||
|
|
||||||
const trailColor =
|
this._uForOpening = this.get_uniform_location('uForOpening');
|
||||||
Clutter.Color.from_string(settings.get_string('matrix-trail-color'))[1];
|
this._uFontTexture = this.get_uniform_location('uFontTexture');
|
||||||
const tipColor =
|
this._uTrailColor = this.get_uniform_location('uTrailColor');
|
||||||
Clutter.Color.from_string(settings.get_string('matrix-tip-color'))[1];
|
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
|
// This is called each time the effect is used. This can be used to retrieve the
|
||||||
// https://www.shadertoy.com/view/ldccW4, however the implementation is quite
|
// configuration from the settings and update all uniforms accordingly.
|
||||||
// different as the letters drop only once and there is no need for a noise texture.
|
setUniforms(actor, settings, forOpening) {
|
||||||
this.set_shader_source(`
|
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 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.
|
// Inject some common shader snippets.
|
||||||
${shaderSnippets.standardUniforms()}
|
${shaderSnippets.standardUniforms()}
|
||||||
${shaderSnippets.noise()}
|
${shaderSnippets.noise()}
|
||||||
${shaderSnippets.edgeMask()}
|
${shaderSnippets.edgeMask()}
|
||||||
|
${shaderSnippets.compositing()}
|
||||||
|
|
||||||
|
uniform bool uForOpening;
|
||||||
uniform sampler2D uFontTexture;
|
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.
|
// These may be configurable in the future.
|
||||||
const float EDGE_FADE = 30;
|
const float EDGE_FADE = 30;
|
||||||
@@ -156,16 +220,13 @@ if (utils.isInShellProcess()) {
|
|||||||
const float TRAIL_LENGTH = 0.2;
|
const float TRAIL_LENGTH = 0.2;
|
||||||
const float FINAL_FADE_START_TIME = 0.8;
|
const float FINAL_FADE_START_TIME = 0.8;
|
||||||
const float LETTER_TILES = 16.0;
|
const float LETTER_TILES = 16.0;
|
||||||
const float LETTER_SIZE = ${settings.get_int('matrix-scale')};
|
|
||||||
const float LETTER_FLICKER_SPEED = 2.0;
|
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.
|
// This returns a flickering grid of random letters.
|
||||||
float getText(vec2 fragCoord) {
|
float getText(vec2 fragCoord) {
|
||||||
vec2 pixelCoords = fragCoord * vec2(uSizeX, uSizeY);
|
vec2 pixelCoords = fragCoord * vec2(uSizeX, uSizeY);
|
||||||
vec2 uv = mod(pixelCoords.xy, LETTER_SIZE)/LETTER_SIZE;
|
vec2 uv = mod(pixelCoords.xy, uLetterSize)/uLetterSize;
|
||||||
vec2 block = pixelCoords/LETTER_SIZE - uv;
|
vec2 block = pixelCoords/uLetterSize - uv;
|
||||||
|
|
||||||
// Choose random letter.
|
// Choose random letter.
|
||||||
uv += floor(hash22(floor(hash22(block)*vec2(12.9898,78.233) + LETTER_FLICKER_SPEED*uTime + 42.254))*LETTER_TILES);
|
uv += floor(hash22(floor(hash22(block)*vec2(12.9898,78.233) + LETTER_FLICKER_SPEED*uTime + 42.254))*LETTER_TILES);
|
||||||
@@ -179,10 +240,10 @@ if (utils.isInShellProcess()) {
|
|||||||
// the window texture.
|
// the window texture.
|
||||||
vec2 getRain(vec2 fragCoord) {
|
vec2 getRain(vec2 fragCoord) {
|
||||||
float column = cogl_tex_coord_in[0].x * uSizeX;
|
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 delay = fract(sin(column)*78.233) * mix(0.0, 1.0, uRandomness);
|
||||||
float speed = fract(cos(column)*12.989) * mix(0.0, 0.3, RANDOMNESS) + 1.5;
|
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;
|
float distToDrop = (uProgress*2-delay)*speed - cogl_tex_coord_in[0].y;
|
||||||
|
|
||||||
@@ -193,50 +254,54 @@ if (utils.isInShellProcess()) {
|
|||||||
rainAlpha *= getAbsoluteEdgeMask(EDGE_FADE);
|
rainAlpha *= getAbsoluteEdgeMask(EDGE_FADE);
|
||||||
|
|
||||||
// Add some variation to the drop start and end position.
|
// 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(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));
|
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;
|
windowAlpha = 1.0 - windowAlpha;
|
||||||
#endif
|
}
|
||||||
|
|
||||||
return vec2(rainAlpha, windowAlpha);
|
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;
|
// Get a cool matrix effect. See comments for those methods above.
|
||||||
coords.y = coords.y * (OVERSHOOT + 1.0) - OVERSHOOT * 0.5;
|
vec2 rainMask = getRain(coords);
|
||||||
|
float textMask = getText(coords);
|
||||||
|
|
||||||
// Get a cool matrix effect. See comments for those methods above.
|
// Get the window texture.
|
||||||
vec2 rain = getRain(coords);
|
cogl_color_out = texture2D(uTexture, coords);
|
||||||
float text = getText(coords);
|
|
||||||
|
// Shell.GLSLEffect uses straight alpha. So we have to convert from premultiplied.
|
||||||
// Get the window texture and fade it according to the effect mask.
|
if (cogl_color_out.a > 0) {
|
||||||
cogl_color_out = texture2D(uTexture, coords) * rain.y;
|
cogl_color_out.rgb /= cogl_color_out.a;
|
||||||
|
|
||||||
// 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.
|
|
||||||
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);
|
|
||||||
}
|
}
|
||||||
`);
|
|
||||||
};
|
// Fade the window according to the effect mask.
|
||||||
|
cogl_color_out.a *= rainMask.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 * rainMask.x;
|
||||||
|
|
||||||
|
// Add the matrix effect to the window.
|
||||||
|
vec4 text = vec4(mix(uTrailColor, uTipColor, min(1, pow(rainAlpha+0.1, 4))), rainAlpha * textMask);
|
||||||
|
cogl_color_out = alphaOver(cogl_color_out, text);
|
||||||
|
|
||||||
|
// These are pretty useful for understanding how this works.
|
||||||
|
// cogl_color_out = vec4(vec3(textMask), 1);
|
||||||
|
// cogl_color_out = vec4(vec3(rainMask.x), 1);
|
||||||
|
// cogl_color_out = vec4(vec3(rainMask.y), 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
|
// 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
|
// impossible under GNOME 3.3x as this.get_pipeline() is not available. It was called
|
||||||
@@ -244,8 +309,9 @@ if (utils.isInShellProcess()) {
|
|||||||
// https://gitlab.gnome.org/GNOME/mutter/-/blob/gnome-3-36/clutter/clutter/clutter-offscreen-effect.c#L598
|
// https://gitlab.gnome.org/GNOME/mutter/-/blob/gnome-3-36/clutter/clutter/clutter-offscreen-effect.c#L598
|
||||||
vfunc_paint_target(node, paint_context) {
|
vfunc_paint_target(node, paint_context) {
|
||||||
const pipeline = this.get_pipeline();
|
const pipeline = this.get_pipeline();
|
||||||
pipeline.set_layer_texture(1, this._fontTexture.get_texture());
|
pipeline.set_layer_texture(1, fontTexture.get_texture());
|
||||||
this.set_uniform_value('uFontTexture', 1);
|
pipeline.set_uniform_1i(this._uFontTexture, 1);
|
||||||
|
|
||||||
super.vfunc_paint_target(node, paint_context);
|
super.vfunc_paint_target(node, paint_context);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -31,8 +31,16 @@ const utils = Me.imports.src.utils;
|
|||||||
// documentation of vfunc_paint_target further down in this file. //
|
// documentation of vfunc_paint_target further down in this file. //
|
||||||
//////////////////////////////////////////////////////////////////////////////////////////
|
//////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
// 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, 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
|
// 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
|
||||||
@@ -81,9 +89,21 @@ var SnapOfDisintegration = class SnapOfDisintegration {
|
|||||||
|
|
||||||
// ---------------------------------------------------------------- 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. It returns an instance of the shader class, trying to reuse previously
|
||||||
return new Shader(actor, settings, forOpening);
|
// 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
|
// 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}
|
'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,116 +142,151 @@ var SnapOfDisintegration = class SnapOfDisintegration {
|
|||||||
|
|
||||||
if (utils.isInShellProcess()) {
|
if (utils.isInShellProcess()) {
|
||||||
|
|
||||||
const {Clutter, GdkPixbuf, Cogl} = imports.gi;
|
const {Clutter, GdkPixbuf, Cogl, 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(actor, 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 dust texture. As the shader is re-created for each window animation,
|
// Load the dust texture.
|
||||||
// this texture is also re-created each time. This could be improved in the future!
|
if (dustTexture == null) {
|
||||||
const dustData = GdkPixbuf.Pixbuf.new_from_resource('/img/dust.png');
|
const dustData = GdkPixbuf.Pixbuf.new_from_resource('/img/dust.png');
|
||||||
this._dustTexture = new Clutter.Image();
|
dustTexture = new Clutter.Image();
|
||||||
this._dustTexture.set_data(dustData.get_pixels(), Cogl.PixelFormat.RGB_888,
|
dustTexture.set_data(dustData.get_pixels(), Cogl.PixelFormat.RGB_888,
|
||||||
dustData.width, dustData.height, dustData.rowstride);
|
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.
|
// 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.
|
// If we are currently performing integration test, the animation uses a fixed seed.
|
||||||
const testMode = settings.get_boolean('test-mode');
|
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 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.math2D()}
|
${shaderSnippets.math2D()}
|
||||||
|
|
||||||
|
uniform bool uForOpening;
|
||||||
uniform sampler2D uDustTexture;
|
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 DUST_LAYERS = 4;
|
||||||
const float GROW_INTENSITY = 0.05;
|
const float GROW_INTENSITY = 0.05;
|
||||||
const float SHRINK_INTENSITY = 0.05;
|
const float SHRINK_INTENSITY = 0.05;
|
||||||
const float WIND_INTENSITY = 0.05;
|
const float WIND_INTENSITY = 0.05;
|
||||||
const float ACTOR_SCALE = 1.2;
|
const float ACTOR_SCALE = 1.2;
|
||||||
const float PADDING = ACTOR_SCALE / 2.0 - 0.5;
|
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.
|
const code = `
|
||||||
float progress = ${forOpening ? 'uProgress' : '1.0 - uProgress'};
|
// 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;
|
float gradient = cogl_tex_coord_in[0].t * ACTOR_SCALE - PADDING;
|
||||||
progress = 2.0 - gradient - 2.0 * progress;
|
progress = 2.0 - gradient - 2.0 * progress;
|
||||||
progress = progress + 0.25 - 0.5 * simplex2D((cogl_tex_coord_in[0].st + SEED) * 2.0);
|
progress = progress + 0.25 - 0.5 * simplex2D((cogl_tex_coord_in[0].st + uSeed) * 2.0);
|
||||||
progress = pow(max(0, progress), 2.0);
|
progress = pow(max(0, progress), 2.0);
|
||||||
|
|
||||||
// This may help you to understand how this effect works.
|
// This may help you to understand how this effect works.
|
||||||
// cogl_color_out = vec4(progress, 0, 0, 0);
|
// cogl_color_out = vec4(progress, 0, 0, 0);
|
||||||
// return;
|
// return;
|
||||||
|
|
||||||
cogl_color_out = vec4(0, 0, 0, 0);
|
cogl_color_out = vec4(0, 0, 0, 0);
|
||||||
|
|
||||||
for (float i=0; i<DUST_LAYERS; ++i) {
|
for (float i=0; i<DUST_LAYERS; ++i) {
|
||||||
|
|
||||||
|
// Create a random direction.
|
||||||
|
float factor = DUST_LAYERS == 1 ? 0 : i/(DUST_LAYERS-1);
|
||||||
|
float angle = 123.123 * (uSeed.x + factor);
|
||||||
|
vec2 direction = vec2(1.0, 0.0);
|
||||||
|
direction = rotate(direction, angle);
|
||||||
|
|
||||||
|
// Flip direction for one side of the window.
|
||||||
|
vec2 coords = cogl_tex_coord_in[0].st * ACTOR_SCALE - PADDING - 0.5;
|
||||||
|
if (getWinding(direction, coords) > 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;
|
||||||
|
|
||||||
|
// Add some wind.
|
||||||
|
coords.x += WIND_INTENSITY * 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) {
|
||||||
|
|
||||||
|
// Get the window color.
|
||||||
|
vec4 windowColor = texture2D(uTexture, coords + 0.5);
|
||||||
|
|
||||||
// Create a random direction.
|
// Shell.GLSLEffect uses straight alpha. So we have to convert from premultiplied.
|
||||||
float factor = DUST_LAYERS == 1 ? 0 : i/(DUST_LAYERS-1);
|
if (windowColor.a > 0) {
|
||||||
float angle = 123.123 * (SEED.x + factor);
|
windowColor.rgb /= windowColor.a;
|
||||||
vec2 direction = vec2(1.0, 0.0);
|
|
||||||
direction = rotate(direction, angle);
|
|
||||||
|
|
||||||
// Flip direction for one side of the window.
|
|
||||||
vec2 coords = cogl_tex_coord_in[0].st * ACTOR_SCALE - PADDING - 0.5;
|
|
||||||
if (getWinding(direction, coords) > 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
|
// Fade the window color to uDustColor.
|
||||||
// and scale it up slightly.
|
vec3 dustColor = mix(windowColor.rgb, uDustColor.rgb, uDustColor.a);
|
||||||
float dist = distToLine(vec2(0.0), direction, coords);
|
windowColor.rgb = mix(windowColor.rgb, dustColor, progress);
|
||||||
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.
|
// Dissolve and blend the layers.
|
||||||
coords.x = coords.x ${forOpening ? ' + ' : ' - '} progress * WIND_INTENSITY;
|
if (dustMap.x - progress > 0) {
|
||||||
|
cogl_color_out = windowColor;
|
||||||
// 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);
|
|
||||||
|
|
||||||
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);
|
|
||||||
|
|
||||||
// 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);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
`);
|
`;
|
||||||
};
|
|
||||||
|
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
|
// 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
|
// impossible under GNOME 3.3x as this.get_pipeline() is not available. It was called
|
||||||
@@ -234,9 +296,9 @@ if (utils.isInShellProcess()) {
|
|||||||
const pipeline = this.get_pipeline();
|
const pipeline = this.get_pipeline();
|
||||||
pipeline.set_layer_filters(0, Cogl.PipelineFilter.LINEAR,
|
pipeline.set_layer_filters(0, Cogl.PipelineFilter.LINEAR,
|
||||||
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_layer_wrap_mode(1, Cogl.PipelineWrapMode.REPEAT);
|
||||||
this.set_uniform_value('uDustTexture', 1);
|
pipeline.set_uniform_1i(this._uDustTexture, 1);
|
||||||
super.vfunc_paint_target(node, paint_context);
|
super.vfunc_paint_target(node, paint_context);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -27,8 +27,16 @@ const utils = Me.imports.src.utils;
|
|||||||
// documentation of vfunc_paint_target further down in this file. //
|
// documentation of vfunc_paint_target further down in this file. //
|
||||||
//////////////////////////////////////////////////////////////////////////////////////////
|
//////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
// 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, 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
|
// 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
|
||||||
@@ -79,11 +87,22 @@ var TRexAttack = class TRexAttack {
|
|||||||
|
|
||||||
// ---------------------------------------------------------------- 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. It returns an instance of the shader class, trying to reuse previously
|
||||||
return new Shader(settings, forOpening);
|
// 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
|
// 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.
|
// 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
|
// 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}
|
'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,74 @@ var TRexAttack = class TRexAttack {
|
|||||||
|
|
||||||
if (utils.isInShellProcess()) {
|
if (utils.isInShellProcess()) {
|
||||||
|
|
||||||
const {Clutter, GdkPixbuf, Cogl} = imports.gi;
|
const {Clutter, GdkPixbuf, Cogl, 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 claw texture. As the shader is re-created for each window animation,
|
// Load the claw texture.
|
||||||
// this texture is also re-created each time. This could be improved in the future!
|
if (clawTexture == null) {
|
||||||
// See assets/README.md for how this texture was created.
|
const clawData = GdkPixbuf.Pixbuf.new_from_resource('/img/claws.png');
|
||||||
const clawData = GdkPixbuf.Pixbuf.new_from_resource('/img/claws.png');
|
clawTexture = new Clutter.Image();
|
||||||
this._clawTexture = new Clutter.Image();
|
clawTexture.set_data(clawData.get_pixels(), Cogl.PixelFormat.RGB_888,
|
||||||
this._clawTexture.set_data(clawData.get_pixels(), Cogl.PixelFormat.RGB_888,
|
clawData.width, clawData.height, clawData.rowstride);
|
||||||
clawData.width, clawData.height, clawData.rowstride);
|
}
|
||||||
|
|
||||||
const color =
|
this._uForOpening = this.get_uniform_location('uForOpening');
|
||||||
Clutter.Color.from_string(settings.get_string('claw-scratch-color'))[1];
|
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.
|
// If we are currently performing integration test, the animation uses a fixed seed.
|
||||||
const testMode = settings.get_boolean('test-mode');
|
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 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.compositing()}
|
||||||
|
|
||||||
// See assets/README.md for how this texture was created.
|
// See assets/README.md for how this texture was created.
|
||||||
|
uniform bool uForOpening;
|
||||||
uniform sampler2D uClawTexture;
|
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 FLASH_INTENSITY = 0.1;
|
||||||
const float MAX_SPAWN_TIME = 0.6; // Scratches will only start in the first half of the animation.
|
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.
|
const float FF_TIME = 0.6; // Relative time for the final fade to transparency.
|
||||||
@@ -188,60 +247,64 @@ if (utils.isInShellProcess()) {
|
|||||||
// Clamp resulting coordinates.
|
// Clamp resulting coordinates.
|
||||||
return clamp(cellUV, vec2(0), vec2(1));
|
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.
|
// Accumulate several random scratches. The color in the scratch map refers to the
|
||||||
vec2 coords = cogl_tex_coord_in[0].st * 2.0 - 1.0;
|
// relative time when the respective part will become invisible. Therefore we can
|
||||||
float dist = length(coords);
|
// add a value to make the scratch appear later.
|
||||||
coords = (coords/dist * pow(dist, WARP_INTENSITY)) * 0.5 + 0.5;
|
float scratchMap = 1.0;
|
||||||
coords = mix(cogl_tex_coord_in[0].st, coords, progress);
|
for (int i=0; i<uNumClaws; ++i) {
|
||||||
|
vec2 uv = getClawUV(coords, 1.0/uClawSize, uSeed*(i+1));
|
||||||
// Accumulate several random scratches. The color in the scratch map refers to the
|
float delay = i/uNumClaws * MAX_SPAWN_TIME;
|
||||||
// relative time when the respective part will become invisible. Therefore we can
|
scratchMap = min(scratchMap, clamp(texture2D(uClawTexture, uv).r + delay, 0, 1));
|
||||||
// add a value to make the scratch appear later.
|
|
||||||
float scratchMap = 1.0;
|
|
||||||
for (int i=0; i<NUM_CLAWS; ++i) {
|
|
||||||
vec2 uv = getClawUV(coords, 1.0/CLAW_SIZE, SEED*(i+1));
|
|
||||||
float delay = i/NUM_CLAWS * MAX_SPAWN_TIME;
|
|
||||||
scratchMap = min(scratchMap, clamp(texture2D(uClawTexture, uv).r + delay, 0, 1));
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get the window texture. We shift the texture lookup by the local derivative of
|
|
||||||
// the claw texture in order to mimic some folding distortion.
|
|
||||||
vec2 offset = vec2(dFdx(scratchMap), dFdy(scratchMap)) * progress * 0.5;
|
|
||||||
cogl_color_out = texture2D(uTexture, coords + offset);
|
|
||||||
|
|
||||||
// Add colorful flashes.
|
|
||||||
float flashIntensity = 1.0 / FLASH_INTENSITY * (scratchMap - progress) + 1;
|
|
||||||
if (flashIntensity < 0 || flashIntensity >= 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);
|
|
||||||
}
|
}
|
||||||
`);
|
|
||||||
};
|
// Get the window texture. We shift the texture lookup by the local derivative of
|
||||||
|
// the claw texture in order to mimic some folding distortion.
|
||||||
|
vec2 offset = vec2(dFdx(scratchMap), dFdy(scratchMap)) * progress * 0.5;
|
||||||
|
cogl_color_out = texture2D(uTexture, coords + offset);
|
||||||
|
|
||||||
|
// Shell.GLSLEffect uses straight alpha. So we have to convert from premultiplied.
|
||||||
|
if (cogl_color_out.a > 0) {
|
||||||
|
cogl_color_out.rgb /= cogl_color_out.a;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add colorful flashes.
|
||||||
|
float flashIntensity = 1.0 / FLASH_INTENSITY * (scratchMap - progress) + 1;
|
||||||
|
if (flashIntensity < 0 || flashIntensity >= 1) {
|
||||||
|
flashIntensity = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Hide flashes where there is now window.
|
||||||
|
vec4 flash = uFlashColor;
|
||||||
|
flash.a *= flashIntensity * cogl_color_out.a * (1.0 - progress);
|
||||||
|
|
||||||
|
// Hide scratched out parts.
|
||||||
|
cogl_color_out.a *= (scratchMap > progress ? 1 : 0);
|
||||||
|
|
||||||
|
// Add flash color.
|
||||||
|
cogl_color_out = alphaOver(cogl_color_out, flash);
|
||||||
|
|
||||||
|
// Fade out the remaining shards.
|
||||||
|
float fadeProgress = smoothstep(0, 1, (progress - 1.0 + FF_TIME)/FF_TIME);
|
||||||
|
cogl_color_out.a *= 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
|
// 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
|
// impossible under GNOME 3.3x as this.get_pipeline() is not available. It was called
|
||||||
@@ -249,8 +312,9 @@ if (utils.isInShellProcess()) {
|
|||||||
// https://gitlab.gnome.org/GNOME/mutter/-/blob/gnome-3-36/clutter/clutter/clutter-offscreen-effect.c#L598
|
// https://gitlab.gnome.org/GNOME/mutter/-/blob/gnome-3-36/clutter/clutter/clutter-offscreen-effect.c#L598
|
||||||
vfunc_paint_target(node, paint_context) {
|
vfunc_paint_target(node, paint_context) {
|
||||||
const pipeline = this.get_pipeline();
|
const pipeline = this.get_pipeline();
|
||||||
pipeline.set_layer_texture(1, this._clawTexture.get_texture());
|
pipeline.set_layer_texture(1, clawTexture.get_texture());
|
||||||
this.set_uniform_value('uClawTexture', 1);
|
pipeline.set_uniform_1i(this._uClawTexture, 1);
|
||||||
|
|
||||||
super.vfunc_paint_target(node, paint_context);
|
super.vfunc_paint_target(node, paint_context);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -27,8 +27,13 @@ const utils = Me.imports.src.utils;
|
|||||||
// the center. //
|
// the center. //
|
||||||
//////////////////////////////////////////////////////////////////////////////////////////
|
//////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
// 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, 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
|
// 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
|
||||||
@@ -76,9 +81,21 @@ var TVEffect = class TVEffect {
|
|||||||
|
|
||||||
// ---------------------------------------------------------------- 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. It returns an instance of the shader class, trying to reuse previously
|
||||||
return new Shader(settings, forOpening);
|
// 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
|
// The tweakTransition() is called from extension.js to tweak a window's open / close
|
||||||
@@ -98,6 +115,12 @@ var TVEffect = class TVEffect {
|
|||||||
'scale-y': {from: forOpening ? 0.5 : 1.0, to: forOpening ? 1.0 : 0.5, mode: 3}
|
'scale-y': {from: forOpening ? 0.5 : 1.0, to: forOpening ? 1.0 : 0.5, mode: 3}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// This is called from extension.js if the extension is disabled. This should free all
|
||||||
|
// static resources.
|
||||||
|
static cleanUp() {
|
||||||
|
freeShaders = [];
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -109,29 +132,55 @@ var TVEffect = class TVEffect {
|
|||||||
|
|
||||||
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();
|
||||||
|
|
||||||
const color = Clutter.Color.from_string(settings.get_string('tv-effect-color'))[1];
|
this._uForOpening = this.get_uniform_location('uForOpening');
|
||||||
|
this._uColor = this.get_uniform_location('uColor');
|
||||||
|
}
|
||||||
|
|
||||||
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 c = Clutter.Color.from_string(settings.get_string('tv-effect-color'))[1];
|
||||||
|
|
||||||
// Inject some common shader snippets.
|
// clang-format off
|
||||||
${shaderSnippets.standardUniforms()}
|
this.set_uniform_float(this._uForOpening, 1, [forOpening]);
|
||||||
|
this.set_uniform_float(this._uColor, 3, [c.red / 255, c.green / 255, c.blue / 255]);
|
||||||
|
// clang-format on
|
||||||
|
}
|
||||||
|
|
||||||
const float BLUR_WIDTH = 0.01; // Width of the gradients.
|
// This is called by extension.js when the shader is not used anymore. We will store
|
||||||
const float TB_TIME = 0.7; // Relative time for the top/bottom animation.
|
// this instance of the shader so that it can be re-used in th future.
|
||||||
const float LR_TIME = 0.4; // Relative time for the left/right animation.
|
free() {
|
||||||
const float LR_DELAY = 0.6; // Delay after which the left/right animation starts.
|
freeShaders.push(this);
|
||||||
const float FF_TIME = 0.1; // Relative time for the final fade to transparency.
|
}
|
||||||
|
|
||||||
void main() {
|
// This is called by the constructor. This 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()}
|
||||||
|
|
||||||
float progress = ${forOpening ? '1.0-uProgress' : 'uProgress'};
|
uniform bool uForOpening;
|
||||||
|
uniform vec3 uColor;
|
||||||
|
|
||||||
|
const float BLUR_WIDTH = 0.01; // Width of the gradients.
|
||||||
|
const float TB_TIME = 0.7; // Relative time for the top/bottom animation.
|
||||||
|
const float LR_TIME = 0.4; // Relative time for the left/right animation.
|
||||||
|
const float LR_DELAY = 0.6; // Delay after which the left/right animation starts.
|
||||||
|
const float FF_TIME = 0.1; // Relative time for the final fade to transparency.
|
||||||
|
`;
|
||||||
|
|
||||||
|
const code = `
|
||||||
|
float progress = uForOpening ? 1.0-uProgress : uProgress;
|
||||||
|
|
||||||
// All of these are in [0..1] during the different stages of the animation.
|
// All of these are in [0..1] during the different stages of the animation.
|
||||||
// tb refers to the top-bottom animation.
|
// tb refers to the top-bottom animation.
|
||||||
@@ -157,22 +206,24 @@ if (utils.isInShellProcess()) {
|
|||||||
// Assemble the final alpha value.
|
// Assemble the final alpha value.
|
||||||
float mask = tbMask * lrMask * ffMask;
|
float mask = tbMask * lrMask * ffMask;
|
||||||
|
|
||||||
vec4 windowColor = texture2D(uTexture, cogl_tex_coord_in[0].st);
|
cogl_color_out = texture2D(uTexture, cogl_tex_coord_in[0].st);
|
||||||
vec4 effectColor = vec4(${color.red / 255},
|
|
||||||
${color.green / 255},
|
// Shell.GLSLEffect uses straight alpha. So we have to convert from premultiplied.
|
||||||
${color.blue / 255}, 1.0) * windowColor.a;
|
if (cogl_color_out.a > 0) {
|
||||||
|
cogl_color_out.rgb /= cogl_color_out.a;
|
||||||
|
}
|
||||||
|
|
||||||
windowColor.rgb = mix(windowColor.rgb, effectColor.rgb, smoothstep(0, 1, progress));
|
cogl_color_out.rgb = mix(cogl_color_out.rgb, uColor * cogl_color_out.a, smoothstep(0, 1, progress));
|
||||||
|
cogl_color_out.a *= mask;
|
||||||
cogl_color_out = windowColor * mask;
|
|
||||||
|
|
||||||
// These are pretty useful for understanding how this works.
|
// These are pretty useful for understanding how this works.
|
||||||
// cogl_color_out = vec4(vec3(tbMask), 1);
|
// cogl_color_out = vec4(vec3(tbMask), 1);
|
||||||
// cogl_color_out = vec4(vec3(lrMask), 1);
|
// cogl_color_out = vec4(vec3(lrMask), 1);
|
||||||
// cogl_color_out = vec4(vec3(ffMask), 1);
|
// cogl_color_out = vec4(vec3(ffMask), 1);
|
||||||
// cogl_color_out = vec4(vec3(mask), 1);
|
// cogl_color_out = vec4(vec3(mask), 1);
|
||||||
}
|
`;
|
||||||
`);
|
|
||||||
};
|
this.add_glsl_snippet(Shell.SnippetHook.FRAGMENT, declarations, code, true);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -26,8 +26,13 @@ const utils = Me.imports.src.utils;
|
|||||||
// fairies. It's implemented with several overlaid grids of randomly moving points. //
|
// fairies. It's implemented with several overlaid grids of randomly moving points. //
|
||||||
//////////////////////////////////////////////////////////////////////////////////////////
|
//////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
// 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, 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 will be called in various places where a unique identifier for this effect is
|
// This will be called in various places where a unique identifier for this effect is
|
||||||
// required. It should match the prefix of the settings keys which store whether the
|
// required. It should match the prefix of the settings keys which store whether the
|
||||||
@@ -76,9 +81,21 @@ var Wisps = class Wisps {
|
|||||||
|
|
||||||
// ---------------------------------------------------------------- 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. It returns an instance of the shader class, trying to reuse previously
|
||||||
return new Shader(settings, forOpening);
|
// 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
|
// The tweakTransition() is called from extension.js to tweak a window's open / close
|
||||||
@@ -98,6 +115,12 @@ var Wisps = class Wisps {
|
|||||||
'scale-y': {from: forOpening ? 0.9 : 1.0, to: forOpening ? 1.0 : 0.9, mode: 3}
|
'scale-y': {from: forOpening ? 0.9 : 1.0, to: forOpening ? 1.0 : 0.9, mode: 3}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// This is called from extension.js if the extension is disabled. This should free all
|
||||||
|
// static resources.
|
||||||
|
static cleanUp() {
|
||||||
|
freeShaders = [];
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -109,88 +132,121 @@ var Wisps = class Wisps {
|
|||||||
|
|
||||||
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();
|
||||||
|
|
||||||
const color = Clutter.Color.from_string(settings.get_string('wisps-color'))[1];
|
this._uForOpening = this.get_uniform_location('uForOpening');
|
||||||
|
this._uSeed = this.get_uniform_location('uSeed');
|
||||||
|
this._uColor = this.get_uniform_location('uColor');
|
||||||
|
this._uScale = this.get_uniform_location('uScale');
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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('wisps-color'))[1];
|
||||||
|
|
||||||
// If we are currently performing integration test, the animation uses a fixed seed.
|
// If we are currently performing integration test, the animation uses a fixed seed.
|
||||||
const testMode = settings.get_boolean('test-mode');
|
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._uColor, 3, [c.red / 255, c.green / 255, c.blue / 255]);
|
||||||
|
this.set_uniform_float(this._uScale, 1, [settings.get_double('wisps-scale')]);
|
||||||
|
// clang-format on
|
||||||
|
}
|
||||||
|
|
||||||
// Inject some common shader snippets.
|
// This is called by extension.js when the shader is not used anymore. We will store
|
||||||
${shaderSnippets.standardUniforms()}
|
// this instance of the shader so that it can be re-used in th future.
|
||||||
${shaderSnippets.noise()}
|
free() {
|
||||||
${shaderSnippets.edgeMask()}
|
freeShaders.push(this);
|
||||||
|
}
|
||||||
|
|
||||||
const vec2 SEED = vec2(${testMode ? 0 : Math.random()},
|
// This is called by the constructor. This means, it's only called when the effect
|
||||||
${testMode ? 0 : Math.random()});
|
// is used for the first time.
|
||||||
const float WISPS_RADIUS = 20.0;
|
vfunc_build_pipeline() {
|
||||||
const float WISPS_SPEED = 10.0;
|
const declarations = `
|
||||||
const float WISPS_SPACING = 40 + WISPS_RADIUS;
|
// Inject some common shader snippets.
|
||||||
const int WISPS_LAYERS = 8;
|
${shaderSnippets.standardUniforms()}
|
||||||
const float WISPS_IN_TIME = 0.5;
|
${shaderSnippets.noise()}
|
||||||
const float WINDOW_OUT_TIME = 1.0;
|
${shaderSnippets.edgeMask()}
|
||||||
|
${shaderSnippets.compositing()}
|
||||||
|
|
||||||
// Returns a grid of randomly moving points. Each grid cell contains one point which
|
uniform bool uForOpening;
|
||||||
// moves on an ellipse.
|
uniform vec2 uSeed;
|
||||||
float getWisps(vec2 texCoords, float gridSize, vec2 seed) {
|
uniform vec3 uColor;
|
||||||
|
uniform float uScale;
|
||||||
|
|
||||||
// Shift coordinates by a random offset and make sure the have a 1:1 aspect ratio.
|
const float WISPS_RADIUS = 20.0;
|
||||||
vec2 coords = (texCoords + hash22(seed)) * vec2(uSizeX, uSizeY);
|
const float WISPS_SPEED = 10.0;
|
||||||
|
const float WISPS_SPACING = 40 + WISPS_RADIUS;
|
||||||
|
const int WISPS_LAYERS = 8;
|
||||||
|
const float WISPS_IN_TIME = 0.5;
|
||||||
|
const float WINDOW_OUT_TIME = 1.0;
|
||||||
|
|
||||||
// Apply global scale.
|
// Returns a grid of randomly moving points. Each grid cell contains one point which
|
||||||
coords /= gridSize;
|
// moves on an ellipse.
|
||||||
|
float getWisps(vec2 texCoords, float gridSize, vec2 seed) {
|
||||||
|
|
||||||
// Get grid cell coordinates in [0..1].
|
// Shift coordinates by a random offset and make sure the have a 1:1 aspect ratio.
|
||||||
vec2 cellUV = mod(coords, vec2(1));
|
vec2 coords = (texCoords + hash22(seed)) * vec2(uSizeX, uSizeY);
|
||||||
|
|
||||||
// This is unique for each cell.
|
// Apply global scale.
|
||||||
vec2 cellID = coords-cellUV + vec2(362.456);
|
coords /= gridSize;
|
||||||
|
|
||||||
// Add random rotation, scale and offset to each grid cell.
|
// Get grid cell coordinates in [0..1].
|
||||||
float speed = mix(10.0, 15.0, hash12(cellID*seed*134.451)) / gridSize * WISPS_SPEED;
|
vec2 cellUV = mod(coords, vec2(1));
|
||||||
float rotation = mix( 0.0, 6.283, hash12(cellID*seed*54.4129));
|
|
||||||
float radius = mix( 0.5, 1.0, hash12(cellID*seed*19.1249)) * WISPS_RADIUS;
|
|
||||||
float roundness = mix(-1.0, 1.0, hash12(cellID*seed*7.51949));
|
|
||||||
|
|
||||||
vec2 offset = vec2(sin(speed * (uTime+1)) * roundness, cos(speed * (uTime+1)));
|
// This is unique for each cell.
|
||||||
offset *= 0.5 - 0.5 * radius / gridSize;
|
vec2 cellID = coords-cellUV + vec2(362.456);
|
||||||
offset = vec2(offset.x * cos(rotation) - offset.y * sin(rotation),
|
|
||||||
offset.x * sin(rotation) + offset.y * cos(rotation));
|
|
||||||
|
|
||||||
cellUV += offset;
|
// Add random rotation, scale and offset to each grid cell.
|
||||||
|
float speed = mix(10.0, 15.0, hash12(cellID*seed*134.451)) / gridSize * WISPS_SPEED;
|
||||||
|
float rotation = mix( 0.0, 6.283, hash12(cellID*seed*54.4129));
|
||||||
|
float radius = mix( 0.5, 1.0, hash12(cellID*seed*19.1249)) * WISPS_RADIUS;
|
||||||
|
float roundness = mix(-1.0, 1.0, hash12(cellID*seed*7.51949));
|
||||||
|
|
||||||
// Use distance to center of shifted / rotated UV coordinates to draw a glaring point.
|
vec2 offset = vec2(sin(speed * (uTime+1)) * roundness, cos(speed * (uTime+1)));
|
||||||
float dist = length(cellUV - 0.5) * gridSize / radius;
|
offset *= 0.5 - 0.5 * radius / gridSize;
|
||||||
if (dist < 1.0) {
|
offset = vec2(offset.x * cos(rotation) - offset.y * sin(rotation),
|
||||||
return min(10, 0.01 / pow(dist, 2.0));
|
offset.x * sin(rotation) + offset.y * cos(rotation));
|
||||||
|
|
||||||
|
cellUV += offset;
|
||||||
|
|
||||||
|
// Use distance to center of shifted / rotated UV coordinates to draw a glaring point.
|
||||||
|
float dist = length(cellUV - 0.5) * gridSize / radius;
|
||||||
|
if (dist < 1.0) {
|
||||||
|
return min(5, 0.01 / pow(dist, 2.0));
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0.0;
|
||||||
}
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
return 0.0;
|
const code = `
|
||||||
}
|
float progress = uForOpening ? 1.0-uProgress : uProgress;
|
||||||
|
|
||||||
void main() {
|
|
||||||
|
|
||||||
float progress = ${forOpening ? '1.0-uProgress' : 'uProgress'};
|
|
||||||
|
|
||||||
// Get the color of the window.
|
// Get the color of the window.
|
||||||
vec4 windowColor = texture2D(uTexture, cogl_tex_coord_in[0].st);
|
cogl_color_out = texture2D(uTexture, cogl_tex_coord_in[0].st);
|
||||||
vec4 effectColor = vec4(${color.red / 255},
|
|
||||||
${color.green / 255},
|
// Shell.GLSLEffect uses straight alpha. So we have to convert from premultiplied.
|
||||||
${color.blue / 255}, 1.0) * windowColor.a;
|
if (cogl_color_out.a > 0) {
|
||||||
|
cogl_color_out.rgb /= cogl_color_out.a;
|
||||||
|
}
|
||||||
|
|
||||||
// Compute several layers of moving wisps.
|
// Compute several layers of moving wisps.
|
||||||
vec2 uv = (cogl_tex_coord_in[0].st-0.5) / mix(1.0, 0.5, progress) + 0.5;
|
vec2 uv = (cogl_tex_coord_in[0].st-0.5) / mix(1.0, 0.5, progress) + 0.5;
|
||||||
uv /= ${settings.get_double('wisps-scale')};
|
uv /= uScale;
|
||||||
float wisps = 0;
|
float wisps = 0;
|
||||||
for (int i=0; i<WISPS_LAYERS; ++i) {
|
for (int i=0; i<WISPS_LAYERS; ++i) {
|
||||||
wisps += getWisps(uv*0.3, WISPS_SPACING, SEED * (i+1));
|
wisps += getWisps(uv*0.3, WISPS_SPACING, uSeed * (i+1));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Compute shrinking edge mask.
|
// Compute shrinking edge mask.
|
||||||
@@ -204,18 +260,20 @@ if (utils.isInShellProcess()) {
|
|||||||
// Use a noise function to dissolve the window.
|
// Use a noise function to dissolve the window.
|
||||||
float noise = smoothstep(1.0, 0.0, abs(2.0 * simplex2DFractal(uv * vec2(uSizeX, uSizeY) / 250) - 1.0));
|
float noise = smoothstep(1.0, 0.0, abs(2.0 * simplex2DFractal(uv * vec2(uSizeX, uSizeY) / 250) - 1.0));
|
||||||
float windowMask = 1.0 - (windowOut < 0.5 ? mix(0.0, noise, windowOut * 2.0) : mix(noise, 1.0, windowOut * 2.0 - 1.0));
|
float windowMask = 1.0 - (windowOut < 0.5 ? mix(0.0, noise, windowOut * 2.0) : mix(noise, 1.0, windowOut * 2.0 - 1.0));
|
||||||
cogl_color_out = windowColor * windowMask * mask;
|
cogl_color_out.a *= windowMask * mask;
|
||||||
|
|
||||||
// Add the wisps.
|
// Add the wisps.
|
||||||
cogl_color_out += min(wispsIn, 1.0 - wispsOut) * wisps * effectColor * mask;
|
vec4 wispColor = wisps * vec4(uColor, min(wispsIn, 1.0 - wispsOut)*mask);
|
||||||
|
cogl_color_out = alphaOver(cogl_color_out, wispColor);
|
||||||
|
|
||||||
// These are pretty useful for understanding how this works.
|
// These are pretty useful for understanding how this works.
|
||||||
// cogl_color_out = vec4(vec3(windowMask), 1.0);
|
// cogl_color_out = vec4(vec3(windowMask), 1.0);
|
||||||
// cogl_color_out = vec4(vec3(wisps), 1.0);
|
// cogl_color_out = vec4(vec3(wisps), 1.0);
|
||||||
// cogl_color_out = vec4(vec3(noise), 1.0);
|
// cogl_color_out = vec4(vec3(noise), 1.0);
|
||||||
// cogl_color_out = vec4(vec3(mask*min(wispsIn, 1.0 - wispsOut)), 1.0);
|
// cogl_color_out = vec4(vec3(mask*min(wispsIn, 1.0 - wispsOut)), 1.0);
|
||||||
}
|
`;
|
||||||
`);
|
|
||||||
};
|
this.add_glsl_snippet(Shell.SnippetHook.FRAGMENT, declarations, code, true);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -28,8 +28,8 @@ function standardUniforms() {
|
|||||||
uniform sampler2D uTexture;
|
uniform sampler2D uTexture;
|
||||||
uniform float uProgress;
|
uniform float uProgress;
|
||||||
uniform float uTime;
|
uniform float uTime;
|
||||||
uniform int uSizeX;
|
uniform float uSizeX;
|
||||||
uniform int uSizeY;
|
uniform float uSizeY;
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -51,6 +51,17 @@ function math2D() {
|
|||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// The Shell.GLSLEffect uses straight alpha blending. This helper method allows
|
||||||
|
// compositing color values in the shader in the same way.
|
||||||
|
function compositing() {
|
||||||
|
return `
|
||||||
|
vec4 alphaOver(vec4 under, vec4 over) {
|
||||||
|
float alpha = over.a + under.a * (1.0 - over.a);
|
||||||
|
return vec4((over.rgb * over.a + under.rgb * under.a * (1.0 - over.a)) / alpha, alpha);
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
// This method returns a mask which smoothly transitions towards zero when approaching
|
// This method returns a mask which smoothly transitions towards zero when approaching
|
||||||
// the window's borders. There is a variant which takes the transition area width in
|
// the window's borders. There is a variant which takes the transition area width in
|
||||||
// pixels and one which takes this as a percentage.
|
// pixels and one which takes this as a percentage.
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 815 B After Width: | Height: | Size: 1.6 KiB |
|
Before Width: | Height: | Size: 1.2 KiB After Width: | Height: | Size: 1.2 KiB |
|
Before Width: | Height: | Size: 1.2 KiB After Width: | Height: | Size: 1.2 KiB |
|
Before Width: | Height: | Size: 967 B After Width: | Height: | Size: 967 B |
|
Before Width: | Height: | Size: 815 B After Width: | Height: | Size: 1.6 KiB |
|
Before Width: | Height: | Size: 685 B After Width: | Height: | Size: 685 B |
|
Before Width: | Height: | Size: 685 B After Width: | Height: | Size: 685 B |
|
Before Width: | Height: | Size: 1021 B After Width: | Height: | Size: 1021 B |
|
Before Width: | Height: | Size: 815 B After Width: | Height: | Size: 1.6 KiB |
|
Before Width: | Height: | Size: 1.2 KiB After Width: | Height: | Size: 1.2 KiB |
|
Before Width: | Height: | Size: 1.2 KiB After Width: | Height: | Size: 1.2 KiB |
|
Before Width: | Height: | Size: 967 B After Width: | Height: | Size: 967 B |
|
Before Width: | Height: | Size: 815 B After Width: | Height: | Size: 1.6 KiB |
|
Before Width: | Height: | Size: 685 B After Width: | Height: | Size: 685 B |
|
Before Width: | Height: | Size: 685 B After Width: | Height: | Size: 685 B |
|
Before Width: | Height: | Size: 1021 B After Width: | Height: | Size: 1021 B |
|
Before Width: | Height: | Size: 13 KiB After Width: | Height: | Size: 13 KiB |
|
Before Width: | Height: | Size: 13 KiB After Width: | Height: | Size: 13 KiB |
|
Before Width: | Height: | Size: 13 KiB After Width: | Height: | Size: 13 KiB |
|
Before Width: | Height: | Size: 13 KiB After Width: | Height: | Size: 13 KiB |
|
Before Width: | Height: | Size: 12 KiB After Width: | Height: | Size: 13 KiB |
|
Before Width: | Height: | Size: 13 KiB After Width: | Height: | Size: 13 KiB |
|
Before Width: | Height: | Size: 13 KiB After Width: | Height: | Size: 13 KiB |
|
Before Width: | Height: | Size: 13 KiB After Width: | Height: | Size: 13 KiB |
|
Before Width: | Height: | Size: 13 KiB After Width: | Height: | Size: 13 KiB |
|
Before Width: | Height: | Size: 13 KiB After Width: | Height: | Size: 13 KiB |
|
Before Width: | Height: | Size: 13 KiB After Width: | Height: | Size: 13 KiB |
|
Before Width: | Height: | Size: 13 KiB After Width: | Height: | Size: 14 KiB |
|
Before Width: | Height: | Size: 13 KiB After Width: | Height: | Size: 14 KiB |
|
Before Width: | Height: | Size: 13 KiB After Width: | Height: | Size: 14 KiB |
|
Before Width: | Height: | Size: 12 KiB After Width: | Height: | Size: 13 KiB |
|
Before Width: | Height: | Size: 13 KiB After Width: | Height: | Size: 13 KiB |
|
Before Width: | Height: | Size: 13 KiB After Width: | Height: | Size: 14 KiB |
|
Before Width: | Height: | Size: 13 KiB After Width: | Height: | Size: 14 KiB |
|
Before Width: | Height: | Size: 13 KiB After Width: | Height: | Size: 14 KiB |
|
Before Width: | Height: | Size: 13 KiB After Width: | Height: | Size: 14 KiB |
|
Before Width: | Height: | Size: 16 KiB After Width: | Height: | Size: 17 KiB |
|
Before Width: | Height: | Size: 16 KiB After Width: | Height: | Size: 17 KiB |
|
Before Width: | Height: | Size: 16 KiB After Width: | Height: | Size: 17 KiB |
|
Before Width: | Height: | Size: 16 KiB After Width: | Height: | Size: 17 KiB |
|
Before Width: | Height: | Size: 16 KiB After Width: | Height: | Size: 17 KiB |
|
Before Width: | Height: | Size: 16 KiB After Width: | Height: | Size: 17 KiB |
|
Before Width: | Height: | Size: 16 KiB After Width: | Height: | Size: 17 KiB |
|
Before Width: | Height: | Size: 16 KiB After Width: | Height: | Size: 17 KiB |
|
Before Width: | Height: | Size: 16 KiB After Width: | Height: | Size: 17 KiB |
|
Before Width: | Height: | Size: 16 KiB After Width: | Height: | Size: 18 KiB |
|
Before Width: | Height: | Size: 16 KiB After Width: | Height: | Size: 17 KiB |
|
Before Width: | Height: | Size: 16 KiB After Width: | Height: | Size: 17 KiB |
|
Before Width: | Height: | Size: 16 KiB After Width: | Height: | Size: 17 KiB |
|
Before Width: | Height: | Size: 16 KiB After Width: | Height: | Size: 17 KiB |
|
Before Width: | Height: | Size: 16 KiB After Width: | Height: | Size: 17 KiB |
|
Before Width: | Height: | Size: 16 KiB After Width: | Height: | Size: 17 KiB |
|
Before Width: | Height: | Size: 16 KiB After Width: | Height: | Size: 17 KiB |
|
Before Width: | Height: | Size: 16 KiB After Width: | Height: | Size: 17 KiB |
|
Before Width: | Height: | Size: 16 KiB After Width: | Height: | Size: 17 KiB |
|
Before Width: | Height: | Size: 17 KiB After Width: | Height: | Size: 18 KiB |
|
Before Width: | Height: | Size: 7.6 KiB After Width: | Height: | Size: 7.1 KiB |
|
Before Width: | Height: | Size: 7.7 KiB After Width: | Height: | Size: 6.8 KiB |
|
Before Width: | Height: | Size: 7.9 KiB After Width: | Height: | Size: 6.4 KiB |
|
Before Width: | Height: | Size: 7.9 KiB After Width: | Height: | Size: 6.4 KiB |
|
Before Width: | Height: | Size: 7.6 KiB After Width: | Height: | Size: 6.9 KiB |
|
Before Width: | Height: | Size: 7.6 KiB After Width: | Height: | Size: 7.1 KiB |
|
Before Width: | Height: | Size: 7.7 KiB After Width: | Height: | Size: 6.8 KiB |
|
Before Width: | Height: | Size: 7.9 KiB After Width: | Height: | Size: 6.6 KiB |
|
Before Width: | Height: | Size: 7.9 KiB After Width: | Height: | Size: 6.6 KiB |
|
Before Width: | Height: | Size: 8.1 KiB After Width: | Height: | Size: 6.9 KiB |
|
Before Width: | Height: | Size: 8.9 KiB After Width: | Height: | Size: 9.4 KiB |
|
Before Width: | Height: | Size: 9.4 KiB After Width: | Height: | Size: 9.6 KiB |
|
Before Width: | Height: | Size: 9.6 KiB After Width: | Height: | Size: 9.6 KiB |
|
Before Width: | Height: | Size: 9.6 KiB After Width: | Height: | Size: 9.6 KiB |
|
Before Width: | Height: | Size: 8.0 KiB After Width: | Height: | Size: 8.5 KiB |
|
Before Width: | Height: | Size: 8.9 KiB After Width: | Height: | Size: 9.4 KiB |
|
Before Width: | Height: | Size: 9.4 KiB After Width: | Height: | Size: 9.6 KiB |
|
Before Width: | Height: | Size: 9.8 KiB After Width: | Height: | Size: 9.6 KiB |
|
Before Width: | Height: | Size: 9.8 KiB After Width: | Height: | Size: 9.6 KiB |
|
Before Width: | Height: | Size: 9.1 KiB After Width: | Height: | Size: 9.4 KiB |
|
Before Width: | Height: | Size: 4.0 KiB After Width: | Height: | Size: 3.8 KiB |
|
Before Width: | Height: | Size: 4.0 KiB After Width: | Height: | Size: 3.8 KiB |
|
Before Width: | Height: | Size: 3.9 KiB After Width: | Height: | Size: 3.7 KiB |
|
Before Width: | Height: | Size: 3.7 KiB After Width: | Height: | Size: 3.4 KiB |
|
Before Width: | Height: | Size: 3.7 KiB After Width: | Height: | Size: 3.4 KiB |
|
Before Width: | Height: | Size: 3.4 KiB After Width: | Height: | Size: 3.1 KiB |
|
Before Width: | Height: | Size: 5.4 KiB After Width: | Height: | Size: 5.8 KiB |
|
Before Width: | Height: | Size: 5.3 KiB After Width: | Height: | Size: 5.9 KiB |
|
Before Width: | Height: | Size: 5.2 KiB After Width: | Height: | Size: 6.1 KiB |