summaryrefslogtreecommitdiff
path: root/raveos-gnome-theme/theme-data/extensions/installed/burn-my-windows@schneegans.github.com/src/Shader.js
blob: b7a63777c10c6fc7b555880f64684745057f961d (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
//////////////////////////////////////////////////////////////////////////////////////////
//          )                                                   (                       //
//       ( /(   (  (               )    (       (  (  (         )\ )    (  (            //
//       )\()) ))\ )(   (         (     )\ )    )\))( )\  (    (()/( (  )\))(  (        //
//      ((_)\ /((_|()\  )\ )      )\  '(()/(   ((_)()((_) )\ )  ((_)))\((_)()\ )\       //
//      | |(_|_))( ((_)_(_/(    _((_))  )(_))  _(()((_|_)_(_/(  _| |((_)(()((_|(_)      //
//      | '_ \ || | '_| ' \))  | '  \()| || |  \ V  V / | ' \)) _` / _ \ V  V (_-<      //
//      |_.__/\_,_|_| |_||_|   |_|_|_|  \_, |   \_/\_/|_|_||_|\__,_\___/\_/\_//__/      //
//                                 |__/                                                 //
//////////////////////////////////////////////////////////////////////////////////////////

// SPDX-FileCopyrightText: Simon Schneegans <code@simonschneegans.de>
// SPDX-License-Identifier: GPL-3.0-or-later

'use strict';

import Gio from 'gi://Gio';
import Shell from 'gi://Shell';
import Cogl from 'gi://Cogl';
import GObject from 'gi://GObject';
import Clutter from 'gi://Clutter';
import Meta from 'gi://Meta';

import * as utils from './utils.js';

//////////////////////////////////////////////////////////////////////////////////////////
// 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    //
// ensures that some standard uniforms are always updated.                              //
// Using Shell.GLSLEffect as a base class has some benefits and some drawbacks. The     //
// main benefit when compared to Clutter.ShaderEffect is that setting uniforms of types //
// vec2, vec3 or vec4 is supported via the API (with Clutter.ShaderEffect the GJS       //
// binding does not work properly). However, there are two drawbacks: On the one hand,  //
// the shader source code is cached statically - this means if we want to have a        //
// different shader, we have to derive a new class. Therefore, each effect has to       //
// derive its own class from the class below. This is encapsulated in the               //
// 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:                                                      //
//   * begin-animation:    This is called each time a new animation is started. It can  //
//                         be used to set uniform values which do not change during the //
//                         animation.                                                   //
//   * update-animation:   This is called at each frame during the animation. It can be //
//                         used to set uniforms which change during the animation.      //
//   * end-animation:      This is called when the animation is stopped. This can be    //
//                         used to clean up any resources.
//////////////////////////////////////////////////////////////////////////////////////////

export var Shader = GObject.registerClass({
  Signals: {
    'begin-animation': {
      param_types: [
        Gio.Settings.$gtype, GObject.TYPE_BOOLEAN, GObject.TYPE_BOOLEAN,
        Clutter.Actor.$gtype
      ]
    },
    'update-animation': {param_types: [GObject.TYPE_DOUBLE]},
    'end-animation': {}
  }
},

                                          class Shader 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();

    // These will be updated during the animation.
    this._progress = 0;
    this._time     = 0;

    // Store standard uniform locations.
    this._uForOpening   = this.get_uniform_location('uForOpening');
    this._uIsFullscreen = this.get_uniform_location('uIsFullscreen');
    this._uProgress     = this.get_uniform_location('uProgress');
    this._uDuration     = this.get_uniform_location('uDuration');
    this._uSize         = this.get_uniform_location('uSize');
    this._uPadding      = this.get_uniform_location('uPadding');

    // Create a timeline to drive the animation.
    this._timeline = new Clutter.Timeline();

    // Call updateAnimation() once a frame.
    this._timeline.connect('new-frame', (t) => {
      if (this._testMode) {
        this.updateAnimation(0.5);
      } else {
        this.updateAnimation(t.get_progress());
      }
    });

    // Clean up if the animation finished or was interrupted.
    this._timeline.connect('stopped', (t, finished) => {
      this.endAnimation();
    });
  }

  // This is called once each time the shader is used.
  beginAnimation(settings, forOpening, testMode, duration, actor) {
    if (this._timeline.is_playing()) {
      this._timeline.stop();
    }

    // On GNOME 3.36 this method was not yet available.
    if (this._timeline.set_actor) {
      this._timeline.set_actor(actor);
    }

    this._timeline.set_duration(duration);
    this._timeline.start();

    // Make sure that no fullscreen window is drawn over our animations. Since GNOME 48
    // this is a "global" method.
    if (Meta.disable_unredirect_for_display) {
      Meta.disable_unredirect_for_display(global.display);
    } else {
      global.compositor.disable_unredirect();
    }

    global.begin_work();

    // Reset progress value.
    this._progress = 0;
    this._testMode = testMode;

    // This is not necessarily symmetric, but I haven't figured out a way to
    // get the actual values...
    const padding    = (actor.width - actor.meta_window.get_frame_rect().width) / 2;
    let isFullscreen = actor.meta_window.fullscreen;

    // is_maximized has been added in GNOME 49.
    if (actor.meta_window.is_maximized) {
      isFullscreen |= actor.meta_window.is_maximized();
    } else {
      isFullscreen |= actor.meta_window.get_maximized() === Meta.MaximizeFlags.BOTH;
    }

    this.set_uniform_float(this._uPadding, 1, [padding]);
    this.set_uniform_float(this._uForOpening, 1, [forOpening]);
    this.set_uniform_float(this._uIsFullscreen, 1, [isFullscreen]);
    this.set_uniform_float(this._uDuration, 1, [duration * 0.001]);
    this.set_uniform_float(this._uSize, 2, [actor.width, actor.height]);

    this.emit('begin-animation', settings, forOpening, testMode, actor);
  }

  // This is called at each frame during the animation.
  updateAnimation(progress) {
    // Store the current progress value. The corresponding signal is emitted each frame
    // in vfunc_paint_target. We do not emit it here, as the pipeline which may be used
    // by handlers must not have been created yet.
    this._progress = progress;

    this.queue_repaint();
  }

  // This will stop any running animation and emit the end-animation signal.
  endAnimation() {
    // This will call endAnimation() again, so we can return for now.
    if (this._timeline.is_playing()) {
      this._timeline.stop();
      return;
    }

    // Restore unredirecting behavior for fullscreen windows. Since GNOME 48 this is a
    // "global" method.
    if (Meta.disable_unredirect_for_display) {
      Meta.enable_unredirect_for_display(global.display);
    } else {
      global.compositor.enable_unredirect();
    }
    global.end_work();

    this.emit('end-animation');
  }

  // 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`);

    // Match anything between the curly brackets of "void main() {...}".
    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

  // This loads a GLSL file from the extension's resources to a JavaScript string. The
  // code from "common.glsl" is prepended automatically.
  _loadShaderResource(path) {
    let common = utils.getStringResource('/shaders/common.glsl');
    let code   = utils.getStringResource(path);

    // Add a trailing newline. Else the GLSL compiler complains...
    return common + '\n' + code + '\n';
  }
});