🎉 Add support for GNOME 51
This commit is contained in:
committed by
Simon Schneegans
parent
f71ed24ae0
commit
a66f9ca71f
+1
-1
@@ -9,7 +9,7 @@
|
|||||||
},
|
},
|
||||||
"gettext-domain": "burn-my-windows",
|
"gettext-domain": "burn-my-windows",
|
||||||
"settings-schema": "org.gnome.shell.extensions.burn-my-windows",
|
"settings-schema": "org.gnome.shell.extensions.burn-my-windows",
|
||||||
"shell-version": ["45", "46", "47", "48", "49", "50"],
|
"shell-version": ["45", "46", "47", "48", "49", "50", "51"],
|
||||||
"url": "https://github.com/Schneegans/Burn-My-Windows",
|
"url": "https://github.com/Schneegans/Burn-My-Windows",
|
||||||
"version": 48
|
"version": 48
|
||||||
}
|
}
|
||||||
|
|||||||
+175
-87
@@ -27,16 +27,13 @@ import * as utils from './utils.js';
|
|||||||
// This is the base class for all shaders of Burn-My-Windows. It automagically loads //
|
// This is the base class for all shaders of Burn-My-Windows. It automagically loads //
|
||||||
// the shader's source code from the resource file resources/shaders/<nick>.glsl and //
|
// the shader's source code from the resource file resources/shaders/<nick>.glsl and //
|
||||||
// ensures that some standard uniforms are always updated. //
|
// ensures that some standard uniforms are always updated. //
|
||||||
// Using Shell.GLSLEffect as a base class has some benefits and some drawbacks. The //
|
// Since GNOME 51, Shell.GLSLEffect has been removed from gnome-shell. On newer //
|
||||||
// main benefit when compared to Clutter.ShaderEffect is that setting uniforms of types //
|
// versions, we therefore use Clutter.ShaderEffect instead. It provides similar //
|
||||||
// vec2, vec3 or vec4 is supported via the API (with Clutter.ShaderEffect the GJS //
|
// functionality: The GLSL snippet is cached per class (not per instance) and float //
|
||||||
// binding does not work properly). However, there are two drawbacks: On the one hand, //
|
// uniforms can be set. The main difference is that uniforms are addressed by name //
|
||||||
// the shader source code is cached statically - this means if we want to have a //
|
// instead of by location. To keep the rest of the code base unchanged, //
|
||||||
// different shader, we have to derive a new class. Therefore, each effect has to //
|
// get_uniform_location() returns the uniform's name as an opaque handle in this case, //
|
||||||
// derive its own class from the class below. This is encapsulated in the //
|
// which is understood by set_uniform_float() and setUniform1i(). //
|
||||||
// ShaderFactory, however it is some really awkward code. The other drawback is the //
|
|
||||||
// hard-coded use of straight alpha (as opposed to premultiplied). This makes the //
|
|
||||||
// shaders a bit more complicated than required. //
|
|
||||||
// //
|
// //
|
||||||
// The Shader fires three signals: //
|
// The Shader fires three signals: //
|
||||||
// * begin-animation: This is called each time a new animation is started. It can //
|
// * begin-animation: This is called each time a new animation is started. It can //
|
||||||
@@ -44,11 +41,13 @@ import * as utils from './utils.js';
|
|||||||
// animation. //
|
// animation. //
|
||||||
// * update-animation: This is called at each frame during the animation. It can be //
|
// * update-animation: This is called at each frame during the animation. It can be //
|
||||||
// used to set uniforms which change during the animation. //
|
// used to set uniforms which change during the animation. //
|
||||||
// * end-animation: This is called when the animation is stopped. This can be //
|
// * end-animation: This is called when the animation is stopped. It can be //
|
||||||
// used to clean up any resources.
|
// used to clean up any resources. //
|
||||||
//////////////////////////////////////////////////////////////////////////////////////////
|
//////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
export var Shader = GObject.registerClass({
|
const _useShaderEffect = !Shell.GLSLEffect;
|
||||||
|
|
||||||
|
const _annotations = {
|
||||||
Signals: {
|
Signals: {
|
||||||
'begin-animation': {
|
'begin-animation': {
|
||||||
param_types: [
|
param_types: [
|
||||||
@@ -59,49 +58,69 @@ export var Shader = GObject.registerClass({
|
|||||||
'update-animation': {param_types: [GObject.TYPE_DOUBLE]},
|
'update-animation': {param_types: [GObject.TYPE_DOUBLE]},
|
||||||
'end-animation': {}
|
'end-animation': {}
|
||||||
}
|
}
|
||||||
},
|
};
|
||||||
|
|
||||||
class Shader extends Shell.GLSLEffect {
|
// This is called from vfunc_paint_target() on both backends. It emits the update-
|
||||||
// --------------------------------------------
|
// animation signal, sets the blend mode of the pipeline and writes the progress uniform.
|
||||||
// The constructor automagically loads the shader's source code (in
|
function _paintTarget(self) {
|
||||||
// vfunc_build_pipeline()) from the resource file resources/shaders/<nick>.glsl
|
self.emit('update-animation', self._progress);
|
||||||
// resolving any #includes in this file.
|
|
||||||
_init(nick) {
|
|
||||||
this._nick = nick;
|
|
||||||
|
|
||||||
// This will call vfunc_build_pipeline().
|
// Starting with GNOME 44.2, the alpha channel is not written to by default. We need
|
||||||
super._init();
|
// to undo this. It is a pity that we have to do this here, as it is not really
|
||||||
|
// required to be done each frame. But it's the only place where we can do it.
|
||||||
|
// https://gitlab.gnome.org/GNOME/gnome-shell/-/merge_requests/2650
|
||||||
|
self.get_pipeline().set_blend(
|
||||||
|
'RGBA = ADD (SRC_COLOR * (SRC_COLOR[A]), DST_COLOR * (1-SRC_COLOR[A]))');
|
||||||
|
|
||||||
// These will be updated during the animation.
|
self.set_uniform_float(self._uProgress, 1, [self._progress]);
|
||||||
this._progress = 0;
|
}
|
||||||
this._time = 0;
|
|
||||||
|
|
||||||
// Store standard uniform locations.
|
// Splits the given GLSL source code into the declarations (everything before "void main")
|
||||||
this._uForOpening = this.get_uniform_location('uForOpening');
|
// and the body of the main function.
|
||||||
this._uIsFullscreen = this.get_uniform_location('uIsFullscreen');
|
function _splitShaderCode(code) {
|
||||||
this._uProgress = this.get_uniform_location('uProgress');
|
// Match anything between the curly brackets of "void main() {...}".
|
||||||
this._uDuration = this.get_uniform_location('uDuration');
|
const regex = RegExp('void main *\\(\\) *\\{([\\S\\s]+)\\}');
|
||||||
this._uSize = this.get_uniform_location('uSize');
|
const match = regex.exec(code);
|
||||||
this._uPadding = this.get_uniform_location('uPadding');
|
|
||||||
|
|
||||||
// Create a timeline to drive the animation.
|
return [code.substr(0, match.index), match[1]];
|
||||||
this._timeline = new Clutter.Timeline();
|
}
|
||||||
|
|
||||||
// Call updateAnimation() once a frame.
|
// This is called from the constructor of both backends after super._init() has been
|
||||||
this._timeline.connect('new-frame', (t) => {
|
// called. It sets up the standard uniforms and the timeline which drives the animation.
|
||||||
if (this._testMode) {
|
function _initRest(self) {
|
||||||
this.updateAnimation(0.5);
|
// These will be updated during the animation.
|
||||||
} else {
|
self._progress = 0;
|
||||||
this.updateAnimation(t.get_progress());
|
self._time = 0;
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// Clean up if the animation finished or was interrupted.
|
// Store standard uniform locations.
|
||||||
this._timeline.connect('stopped', (t, finished) => {
|
self._uForOpening = self.get_uniform_location('uForOpening');
|
||||||
this.endAnimation();
|
self._uIsFullscreen = self.get_uniform_location('uIsFullscreen');
|
||||||
});
|
self._uProgress = self.get_uniform_location('uProgress');
|
||||||
}
|
self._uDuration = self.get_uniform_location('uDuration');
|
||||||
|
self._uSize = self.get_uniform_location('uSize');
|
||||||
|
self._uPadding = self.get_uniform_location('uPadding');
|
||||||
|
|
||||||
|
// Create a timeline to drive the animation.
|
||||||
|
self._timeline = new Clutter.Timeline();
|
||||||
|
|
||||||
|
// Call updateAnimation() once a frame.
|
||||||
|
self._timeline.connect('new-frame', (t) => {
|
||||||
|
if (self._testMode) {
|
||||||
|
self.updateAnimation(0.5);
|
||||||
|
} else {
|
||||||
|
self.updateAnimation(t.get_progress());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Clean up if the animation finished or was interrupted.
|
||||||
|
self._timeline.connect('stopped', (t, finished) => {
|
||||||
|
self.endAnimation();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// The methods shared by both backends. They are added to the prototype of the class
|
||||||
|
// below before it is registered with GObject.
|
||||||
|
const _methods = {
|
||||||
// This is called once each time the shader is used.
|
// This is called once each time the shader is used.
|
||||||
beginAnimation(settings, forOpening, testMode, duration, actor) {
|
beginAnimation(settings, forOpening, testMode, duration, actor) {
|
||||||
if (this._timeline.is_playing()) {
|
if (this._timeline.is_playing()) {
|
||||||
@@ -149,7 +168,7 @@ export var Shader = GObject.registerClass({
|
|||||||
this.set_uniform_float(this._uSize, 2, [actor.width, actor.height]);
|
this.set_uniform_float(this._uSize, 2, [actor.width, actor.height]);
|
||||||
|
|
||||||
this.emit('begin-animation', settings, forOpening, testMode, actor);
|
this.emit('begin-animation', settings, forOpening, testMode, actor);
|
||||||
}
|
},
|
||||||
|
|
||||||
// This is called at each frame during the animation.
|
// This is called at each frame during the animation.
|
||||||
updateAnimation(progress) {
|
updateAnimation(progress) {
|
||||||
@@ -159,7 +178,7 @@ export var Shader = GObject.registerClass({
|
|||||||
this._progress = progress;
|
this._progress = progress;
|
||||||
|
|
||||||
this.queue_repaint();
|
this.queue_repaint();
|
||||||
}
|
},
|
||||||
|
|
||||||
// This will stop any running animation and emit the end-animation signal.
|
// This will stop any running animation and emit the end-animation signal.
|
||||||
endAnimation() {
|
endAnimation() {
|
||||||
@@ -179,43 +198,19 @@ export var Shader = GObject.registerClass({
|
|||||||
global.end_work();
|
global.end_work();
|
||||||
|
|
||||||
this.emit('end-animation');
|
this.emit('end-animation');
|
||||||
}
|
},
|
||||||
|
|
||||||
// This is called by the constructor. This means, it's only called when the
|
// Sets an integer uniform on the pipeline. This is used by some effects to bind texture
|
||||||
// effect is used for the first time.
|
// samplers. On newer versions, where uniforms are addressed by name, the location has
|
||||||
vfunc_build_pipeline() {
|
// to be looked up first.
|
||||||
// Shell.GLSLEffect requires the declarations and the main source code as separate
|
setUniform1i(handle, value) {
|
||||||
// strings. As it's more convenient to store the in one GLSL file, we use a regex
|
if (_useShaderEffect) {
|
||||||
// here to split the source code in two parts.
|
const pipeline = this.get_pipeline();
|
||||||
const code = this._loadShaderResource(`/shaders/${this._nick}.frag`);
|
pipeline.set_uniform_1i(pipeline.get_uniform_location(handle), value);
|
||||||
|
} else {
|
||||||
// Match anything between the curly brackets of "void main() {...}".
|
this.get_pipeline().set_uniform_1i(handle, value);
|
||||||
const regex = RegExp('void main *\\(\\) *\\{([\\S\\s]+)\\}');
|
}
|
||||||
const match = regex.exec(code);
|
},
|
||||||
|
|
||||||
const declarations = code.substr(0, match.index);
|
|
||||||
const main = match[1];
|
|
||||||
|
|
||||||
this.add_glsl_snippet(
|
|
||||||
Cogl.SnippetHook ? Cogl.SnippetHook.FRAGMENT : Shell.SnippetHook.FRAGMENT,
|
|
||||||
declarations, main, true);
|
|
||||||
}
|
|
||||||
|
|
||||||
// We use this vfunc to trigger the update as it allows calling this.get_pipeline() in
|
|
||||||
// the handler. This could still be null if called from the updateAnimation() above.
|
|
||||||
vfunc_paint_target(...params) {
|
|
||||||
this.emit('update-animation', this._progress);
|
|
||||||
|
|
||||||
// Starting with GNOME 44.2, the alpha channel is not written to by default. We need
|
|
||||||
// to undo this. It is a pity that we have to do this here, as it is not really
|
|
||||||
// required to be done each frame. But it's the only place where we can do it.
|
|
||||||
// https://gitlab.gnome.org/GNOME/gnome-shell/-/merge_requests/2650
|
|
||||||
this.get_pipeline().set_blend(
|
|
||||||
'RGBA = ADD (SRC_COLOR * (SRC_COLOR[A]), DST_COLOR * (1-SRC_COLOR[A]))');
|
|
||||||
|
|
||||||
this.set_uniform_float(this._uProgress, 1, [this._progress]);
|
|
||||||
super.vfunc_paint_target(...params);
|
|
||||||
}
|
|
||||||
|
|
||||||
// --------------------------------------------------------------------- private stuff
|
// --------------------------------------------------------------------- private stuff
|
||||||
|
|
||||||
@@ -228,4 +223,97 @@ export var Shader = GObject.registerClass({
|
|||||||
// Add a trailing newline. Else the GLSL compiler complains...
|
// Add a trailing newline. Else the GLSL compiler complains...
|
||||||
return common + '\n' + code + '\n';
|
return common + '\n' + code + '\n';
|
||||||
}
|
}
|
||||||
});
|
};
|
||||||
|
|
||||||
|
// On newer versions, uniforms are addressed by name. We return the name as an opaque
|
||||||
|
// handle which is understood by set_uniform_float() and setUniform1i().
|
||||||
|
if (_useShaderEffect) {
|
||||||
|
_methods.get_uniform_location = function(name) {
|
||||||
|
return name;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
let Shader;
|
||||||
|
if (_useShaderEffect) {
|
||||||
|
class ShaderImpl extends Clutter.ShaderEffect {
|
||||||
|
// The GLSL snippet (see vfunc_get_static_snippet()) is created lazily when the
|
||||||
|
// effect is used for the first time.
|
||||||
|
_init(nick) {
|
||||||
|
this._nick = nick;
|
||||||
|
|
||||||
|
super._init();
|
||||||
|
|
||||||
|
_initRest(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
// This is called once per class (not per instance) to create the GLSL snippet which
|
||||||
|
// is then shared by all instances of this class.
|
||||||
|
vfunc_get_static_snippet() {
|
||||||
|
const code = this._loadShaderResource(`/shaders/${this._nick}.frag`);
|
||||||
|
const [declarations, main] = _splitShaderCode(code);
|
||||||
|
|
||||||
|
const snippet = Cogl.Snippet.new(Cogl.SnippetHook.FRAGMENT, declarations, null);
|
||||||
|
snippet.set_replace(main);
|
||||||
|
return snippet;
|
||||||
|
}
|
||||||
|
|
||||||
|
// We use this vfunc to trigger the update as it allows calling this.get_pipeline() in
|
||||||
|
// the handlers. This could still be null if called from the updateAnimation() above.
|
||||||
|
vfunc_paint_target(...params) {
|
||||||
|
_paintTarget(this);
|
||||||
|
super.vfunc_paint_target(...params);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Clutter.ShaderEffect.set_uniform_float() ignores the component count and derives
|
||||||
|
// the uniform's size from the length of the values array. Since parseColor() returns
|
||||||
|
// four values, vec3 uniforms would be uploaded with four components, which is an
|
||||||
|
// invalid glUniform4fv() call. The uniform would then be left at its default value
|
||||||
|
// (typically black). We therefore cut the array to the declared size.
|
||||||
|
set_uniform_float(name, n_components, values) {
|
||||||
|
super.set_uniform_float(name, n_components, values.slice(0, n_components));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Object.assign(ShaderImpl.prototype, _methods);
|
||||||
|
Shader = GObject.registerClass(_annotations, ShaderImpl);
|
||||||
|
} else {
|
||||||
|
class ShaderImpl extends Shell.GLSLEffect {
|
||||||
|
// The constructor automagically loads the shader's source code (in
|
||||||
|
// vfunc_build_pipeline()) from the resource file resources/shaders/<nick>.glsl
|
||||||
|
// resolving any #includes in this file.
|
||||||
|
_init(nick) {
|
||||||
|
this._nick = nick;
|
||||||
|
|
||||||
|
// This will call vfunc_build_pipeline().
|
||||||
|
super._init();
|
||||||
|
|
||||||
|
_initRest(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() {
|
||||||
|
// Shell.GLSLEffect requires the declarations and the main source code as separate
|
||||||
|
// strings. As it's more convenient to store the in one GLSL file, we use a regex
|
||||||
|
// here to split the source code in two parts.
|
||||||
|
const code = this._loadShaderResource(`/shaders/${this._nick}.frag`);
|
||||||
|
const [declarations, main] = _splitShaderCode(code);
|
||||||
|
|
||||||
|
this.add_glsl_snippet(
|
||||||
|
Cogl.SnippetHook ? Cogl.SnippetHook.FRAGMENT : Shell.SnippetHook.FRAGMENT,
|
||||||
|
declarations, main, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
// We use this vfunc to trigger the update as it allows calling this.get_pipeline() in
|
||||||
|
// the handlers. This could still be null if called from the updateAnimation() above.
|
||||||
|
vfunc_paint_target(...params) {
|
||||||
|
_paintTarget(this);
|
||||||
|
super.vfunc_paint_target(...params);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Object.assign(ShaderImpl.prototype, _methods);
|
||||||
|
Shader = GObject.registerClass(_annotations, ShaderImpl);
|
||||||
|
}
|
||||||
|
|
||||||
|
export {Shader};
|
||||||
|
|||||||
@@ -105,7 +105,7 @@ export default class Effect {
|
|||||||
// Bind the shard texture.
|
// Bind the shard texture.
|
||||||
pipeline.set_layer_texture(1, this._shardTexture.get_texture());
|
pipeline.set_layer_texture(1, this._shardTexture.get_texture());
|
||||||
pipeline.set_layer_wrap_mode(1, Cogl.PipelineWrapMode.REPEAT);
|
pipeline.set_layer_wrap_mode(1, Cogl.PipelineWrapMode.REPEAT);
|
||||||
pipeline.set_uniform_1i(shader._uShardTexture, 1);
|
shader.setUniform1i(shader._uShardTexture, 1);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -75,7 +75,7 @@ export default class Effect {
|
|||||||
|
|
||||||
// Bind the font texture.
|
// Bind the font texture.
|
||||||
pipeline.set_layer_texture(1, this._fontTexture.get_texture());
|
pipeline.set_layer_texture(1, this._fontTexture.get_texture());
|
||||||
pipeline.set_uniform_1i(shader._uFontTexture, 1);
|
shader.setUniform1i(shader._uFontTexture, 1);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -69,7 +69,7 @@ export default class Effect {
|
|||||||
|
|
||||||
// Bind the brush texture.
|
// Bind the brush texture.
|
||||||
pipeline.set_layer_texture(1, this._brushTexture.get_texture());
|
pipeline.set_layer_texture(1, this._brushTexture.get_texture());
|
||||||
pipeline.set_uniform_1i(shader._uBrushTexture, 1);
|
shader.setUniform1i(shader._uBrushTexture, 1);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -79,7 +79,7 @@ export default class Effect {
|
|||||||
// Bind the dust texture.
|
// Bind the dust texture.
|
||||||
pipeline.set_layer_texture(1, this._dustTexture.get_texture());
|
pipeline.set_layer_texture(1, this._dustTexture.get_texture());
|
||||||
pipeline.set_layer_wrap_mode(1, Cogl.PipelineWrapMode.REPEAT);
|
pipeline.set_layer_wrap_mode(1, Cogl.PipelineWrapMode.REPEAT);
|
||||||
pipeline.set_uniform_1i(shader._uDustTexture, 1);
|
shader.setUniform1i(shader._uDustTexture, 1);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -78,7 +78,7 @@ export default class Effect {
|
|||||||
|
|
||||||
// Bind the claw texture.
|
// Bind the claw texture.
|
||||||
pipeline.set_layer_texture(1, this._clawTexture.get_texture());
|
pipeline.set_layer_texture(1, this._clawTexture.get_texture());
|
||||||
pipeline.set_uniform_1i(shader._uClawTexture, 1);
|
shader.setUniform1i(shader._uClawTexture, 1);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user