diff --git a/src/FireEffect.js b/src/FireEffect.js index 108e47c..e814dee 100644 --- a/src/FireEffect.js +++ b/src/FireEffect.js @@ -29,140 +29,6 @@ const utils = Me.imports.src.utils; let FireShader = null; -if (utils.isInShellProcess()) { - - const Clutter = imports.gi.Clutter; - const shaderSnippets = Me.imports.src.shaderSnippets; - - FireShader = GObject.registerClass({Properties: {}, Signals: {}}, - class FireShader extends Clutter.ShaderEffect { - _init(settings) { - super._init({shader_type: Clutter.ShaderType.FRAGMENT_SHADER}); - - // Load the gradient values from the settings. We directly inject the values in the - // GLSL code below. The shader is compiled once for each window-closing anyways. In - // the future, we may want to prevent this frequent recompilations of shaders, - // though. - const gradient = []; - for (let i = 1; i <= 5; i++) { - const color = - Clutter.Color.from_string(settings.get_string('fire-color-' + i))[1]; - gradient.push(`vec4(${color.red / 255}, ${color.green / 255}, ${ - color.blue / 255}, ${color.alpha / 255})`); - } - - this.set_shader_source(` - - // Inject some common shader snippets. - ${shaderSnippets.standardUniforms()} - ${shaderSnippets.noise()} - - // These may be configurable in the future. - const float EDGE_FADE = 90; - const float FADE_WIDTH = 0.1; - const float HIDE_TIME = 0.4; - const vec2 FIRE_SCALE = vec2(400, 600) * ${settings.get_double('flame-scale')}; - const float FIRE_SPEED = ${settings.get_double('flame-movement-speed')}; - - // This maps the input value from [0..1] to a color from the gradient. - vec4 getFireColor(float v) { - const float steps[5] = float[](0.0, 0.2, 0.35, 0.5, 0.8); - const vec4 colors[5] = vec4[]( - ${gradient[0]}, - ${gradient[1]}, - ${gradient[2]}, - ${gradient[3]}, - ${gradient[4]} - ); - - if (v < steps[0]) { - return colors[0]; - } - - for (int i=0; i<4; ++i) { - if (v <= steps[i+1]) { - return mix(colors[i], colors[i+1], vec4(v - steps[i])/(steps[i+1]-steps[i])); - } - } - - return colors[4]; - } - - // This method requires the uniforms from standardUniforms() to be available. - // It returns two values: The first is an alpha value which can be used for the window - // texture. This gradually dissolves the window from top to bottom. The second can be used - // to mask any effect, it will be most opaque where the window is currently fading and - // gradually dissolve to zero over time. - // hideTime: A value in [0..1]. It determines the percentage of the animation which - // is spent for hiding the window. 1-hideTime will be spent thereafter for - // dissolving the effect mask. - // fadeWidth: The relative size of the window-hiding gradient in [0..1]. - // edgeFadeWidth: The pixel width of the effect fading range at the edges of the window. - vec2 effectMask(float hideTime, float fadeWidth, float edgeFadeWidth) { - float burnProgress = clamp(uProgress/hideTime, 0, 1); - float afterBurnProgress = clamp((uProgress-hideTime)/(1-hideTime), 0, 1); - - // Gradient from top to bottom. - float t = cogl_tex_coord_in[0].t * (1 - fadeWidth); - - // Visible part of the window. Gradually dissolves towards the bottom. - float windowMask = 1 - clamp((burnProgress - t) / fadeWidth, 0, 1); - - // Gradient from top burning window. - float effectMask = clamp(t*(1-windowMask)/burnProgress, 0, 1); - - // Fade-out when the window burned down. - if (uProgress > hideTime) { - float fade = sqrt(1-afterBurnProgress*afterBurnProgress); - effectMask *= mix(1, 1-t, afterBurnProgress) * fade; - } - - // Fade at window borders. - vec2 pos = cogl_tex_coord_in[0].st * vec2(uSizeX, uSizeY); - effectMask *= smoothstep(0, 1, clamp(pos.x / edgeFadeWidth, 0, 1)); - effectMask *= smoothstep(0, 1, clamp(pos.y / edgeFadeWidth, 0, 1)); - effectMask *= smoothstep(0, 1, clamp((uSizeX - pos.x) / edgeFadeWidth, 0, 1)); - effectMask *= smoothstep(0, 1, clamp((uSizeY - pos.y) / edgeFadeWidth, 0, 1)); - - return vec2(windowMask, effectMask); - } - - void main() { - - // Get a noise value which moves vertically in time. - vec2 uv = cogl_tex_coord_in[0].st * vec2(uSizeX, uSizeY) / FIRE_SCALE; - uv.y += uTime * FIRE_SPEED; - - #if ${settings.get_boolean('flame-3d-noise') ? 1 : 0} - float noise = noise3D(vec3(uv*7.5, uTime*FIRE_SPEED*3.0), 5); - #else - float noise = noise2D(uv * 7.5, 5); - #endif - - // Modulate noise by effect mask. - vec2 effectMask = effectMask(HIDE_TIME, FADE_WIDTH, EDGE_FADE); - noise *= effectMask.y; - - // Map noise value to color. - vec4 fire = getFireColor(noise); - fire.rgb *= fire.a; - - // Get the window texture and fade it according to the effect mask. - cogl_color_out = texture2D(uTexture, cogl_tex_coord_in[0].st) * effectMask.x; - - // Add the fire to the window. - cogl_color_out += 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); - } - `); - }; - }); -} - var FireEffect = class FireEffect { // ---------------------------------------------------------------------- static methods @@ -321,4 +187,139 @@ var FireEffect = class FireEffect { root.insert_action_group(groupName, group); }); } +} + + +if (utils.isInShellProcess()) { + + const Clutter = imports.gi.Clutter; + const shaderSnippets = Me.imports.src.shaderSnippets; + + FireShader = GObject.registerClass({Properties: {}, Signals: {}}, + class FireShader extends Clutter.ShaderEffect { + _init(settings) { + super._init({shader_type: Clutter.ShaderType.FRAGMENT_SHADER}); + + // Load the gradient values from the settings. We directly inject the values in the + // GLSL code below. The shader is compiled once for each window-closing anyways. In + // the future, we may want to prevent this frequent recompilations of shaders, + // though. + const gradient = []; + for (let i = 1; i <= 5; i++) { + const color = + Clutter.Color.from_string(settings.get_string('fire-color-' + i))[1]; + gradient.push(`vec4(${color.red / 255}, ${color.green / 255}, ${ + color.blue / 255}, ${color.alpha / 255})`); + } + + this.set_shader_source(` + + // Inject some common shader snippets. + ${shaderSnippets.standardUniforms()} + ${shaderSnippets.noise()} + + // These may be configurable in the future. + const float EDGE_FADE = 90; + const float FADE_WIDTH = 0.1; + const float HIDE_TIME = 0.4; + const vec2 FIRE_SCALE = vec2(400, 600) * ${settings.get_double('flame-scale')}; + const float FIRE_SPEED = ${settings.get_double('flame-movement-speed')}; + + // This maps the input value from [0..1] to a color from the gradient. + vec4 getFireColor(float v) { + const float steps[5] = float[](0.0, 0.2, 0.35, 0.5, 0.8); + const vec4 colors[5] = vec4[]( + ${gradient[0]}, + ${gradient[1]}, + ${gradient[2]}, + ${gradient[3]}, + ${gradient[4]} + ); + + if (v < steps[0]) { + return colors[0]; + } + + for (int i=0; i<4; ++i) { + if (v <= steps[i+1]) { + return mix(colors[i], colors[i+1], vec4(v - steps[i])/(steps[i+1]-steps[i])); + } + } + + return colors[4]; + } + + // This method requires the uniforms from standardUniforms() to be available. + // It returns two values: The first is an alpha value which can be used for the window + // texture. This gradually dissolves the window from top to bottom. The second can be used + // to mask any effect, it will be most opaque where the window is currently fading and + // gradually dissolve to zero over time. + // hideTime: A value in [0..1]. It determines the percentage of the animation which + // is spent for hiding the window. 1-hideTime will be spent thereafter for + // dissolving the effect mask. + // fadeWidth: The relative size of the window-hiding gradient in [0..1]. + // edgeFadeWidth: The pixel width of the effect fading range at the edges of the window. + vec2 effectMask(float hideTime, float fadeWidth, float edgeFadeWidth) { + float burnProgress = clamp(uProgress/hideTime, 0, 1); + float afterBurnProgress = clamp((uProgress-hideTime)/(1-hideTime), 0, 1); + + // Gradient from top to bottom. + float t = cogl_tex_coord_in[0].t * (1 - fadeWidth); + + // Visible part of the window. Gradually dissolves towards the bottom. + float windowMask = 1 - clamp((burnProgress - t) / fadeWidth, 0, 1); + + // Gradient from top burning window. + float effectMask = clamp(t*(1-windowMask)/burnProgress, 0, 1); + + // Fade-out when the window burned down. + if (uProgress > hideTime) { + float fade = sqrt(1-afterBurnProgress*afterBurnProgress); + effectMask *= mix(1, 1-t, afterBurnProgress) * fade; + } + + // Fade at window borders. + vec2 pos = cogl_tex_coord_in[0].st * vec2(uSizeX, uSizeY); + effectMask *= smoothstep(0, 1, clamp(pos.x / edgeFadeWidth, 0, 1)); + effectMask *= smoothstep(0, 1, clamp(pos.y / edgeFadeWidth, 0, 1)); + effectMask *= smoothstep(0, 1, clamp((uSizeX - pos.x) / edgeFadeWidth, 0, 1)); + effectMask *= smoothstep(0, 1, clamp((uSizeY - pos.y) / edgeFadeWidth, 0, 1)); + + return vec2(windowMask, effectMask); + } + + void main() { + + // Get a noise value which moves vertically in time. + vec2 uv = cogl_tex_coord_in[0].st * vec2(uSizeX, uSizeY) / FIRE_SCALE; + uv.y += uTime * FIRE_SPEED; + + #if ${settings.get_boolean('flame-3d-noise') ? 1 : 0} + float noise = noise3D(vec3(uv*7.5, uTime*FIRE_SPEED*3.0), 5); + #else + float noise = noise2D(uv * 7.5, 5); + #endif + + // Modulate noise by effect mask. + vec2 effectMask = effectMask(HIDE_TIME, FADE_WIDTH, EDGE_FADE); + noise *= effectMask.y; + + // Map noise value to color. + vec4 fire = getFireColor(noise); + fire.rgb *= fire.a; + + // Get the window texture and fade it according to the effect mask. + cogl_color_out = texture2D(uTexture, cogl_tex_coord_in[0].st) * effectMask.x; + + // Add the fire to the window. + cogl_color_out += 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); + } + `); + }; + }); } \ No newline at end of file diff --git a/src/MatrixEffect.js b/src/MatrixEffect.js index ebae347..5184513 100644 --- a/src/MatrixEffect.js +++ b/src/MatrixEffect.js @@ -30,132 +30,6 @@ const utils = Me.imports.src.utils; let MatrixShader = null; -if (utils.isInShellProcess()) { - - const {Clutter, GdkPixbuf, Cogl} = imports.gi; - const shaderSnippets = Me.imports.src.shaderSnippets; - - - MatrixShader = GObject.registerClass({Properties: {}, Signals: {}}, - class MatrixShader extends Clutter.ShaderEffect { - _init(settings) { - super._init({shader_type: Clutter.ShaderType.FRAGMENT_SHADER}); - - // Load the font texture. As the shader is re-created for each window animation, - // this texture is also re-created each time. This could be improved in the future! - const fontData = GdkPixbuf.Pixbuf.new_from_resource('/img/matrixFont.png'); - this._fontTexture = new Clutter.Image(); - this._fontTexture.set_data( - fontData.get_pixels(), - fontData.has_alpha ? Cogl.PixelFormat.RGBA_8888 : Cogl.PixelFormat.RGB_888, - fontData.width, fontData.height, fontData.rowstride); - - const trailColor = - Clutter.Color.from_string(settings.get_string('matrix-trail-color'))[1]; - const tipColor = - Clutter.Color.from_string(settings.get_string('matrix-tip-color'))[1]; - - // 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. - this.set_shader_source(` - -// Inject some common shader snippets. -${shaderSnippets.standardUniforms()} -${shaderSnippets.noise()} - -uniform sampler2D uFontTexture; - -// These may be configurable in the future. -const float EDGE_FADE = 30; -const float FADE_WIDTH = 150; -const float TRAIL_LENGTH = 0.2; -const float FINAL_FADE_START_TIME = 0.8; -const float LETTER_TILES = 16.0; -const float LETTER_SIZE = ${settings.get_int('matrix-scale')}; -const float LETTER_FLICKER_SPEED = 2.0; -const float RANDOMNESS = ${settings.get_double('matrix-randomness')}; - -// This returns a flickering grid of random letters. -float getText(vec2 fragCoord) { -vec2 pixelCoords = fragCoord * vec2(uSizeX, uSizeY); -vec2 uv = mod(pixelCoords.xy, LETTER_SIZE)/LETTER_SIZE; -vec2 block = pixelCoords/LETTER_SIZE - uv; - -// Choose random letter. -uv += floor(hash2D(floor(hash2D(block)*vec2(12.9898,78.233) + LETTER_FLICKER_SPEED*uTime + 42.254))*LETTER_TILES); - -return texture2D(uFontTexture, uv/LETTER_TILES).r; -} - -// This returns two values: The first are gradients for the "raindrops" which move -// from top to bottom. This is used for fading the letters. The second value is set -// to one below each drop and to zero above it. This second value is used for fading -// the window texture. -vec2 getRain(vec2 fragCoord) { -vec2 coords = fragCoord * vec2(uSizeX, uSizeY); -coords.x -= mod(coords.x, LETTER_SIZE); - -float delay = fract(sin(coords.x)*78.233) * mix(0.0, 1.0, RANDOMNESS); -float speed = fract(cos(coords.x)*12.989) * mix(0.0, 0.3, RANDOMNESS) + 1.5; - -float distToDrop = (uProgress*2-delay)*speed - fragCoord.y; -float rainAlpha = distToDrop >= 0 ? exp(-distToDrop/TRAIL_LENGTH) : 0; -float windowAlpha = 1 - clamp(uSizeY*distToDrop, 0, FADE_WIDTH) / FADE_WIDTH; -return vec2(rainAlpha, windowAlpha); -} - -void main() { - -// Get a cool matrix effect. See comments for those methods above. -vec2 rain = getRain(cogl_tex_coord_in[0].st); -float text = getText(cogl_tex_coord_in[0].st); - -// Get the window texture and fade it according to the effect mask. -cogl_color_out = texture2D(uTexture, cogl_tex_coord_in[0].st) * rain.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 * rain.x; - -// Fade at window borders. -vec2 pos = cogl_tex_coord_in[0].st * vec2(uSizeX, uSizeY); -rainAlpha *= clamp(pos.x / EDGE_FADE, 0, 1); -rainAlpha *= clamp(pos.y / EDGE_FADE, 0, 1); -rainAlpha *= clamp((uSizeX - pos.x) / EDGE_FADE, 0, 1); -rainAlpha *= clamp((uSizeY - pos.y) / EDGE_FADE, 0, 1); - -// 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); -} -`); - }; - - // 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 - // get_target() back then but this is not wrapped in GJS. - // https://gitlab.gnome.org/GNOME/mutter/-/blob/gnome-3-36/clutter/clutter/clutter-offscreen-effect.c#L598 - vfunc_paint_target(node, paint_context) { - const pipeline = this.get_pipeline(); - pipeline.set_layer_texture(1, this._fontTexture.get_texture()); - this.set_uniform_value('uFontTexture', 1); - super.vfunc_paint_target(node, paint_context); - } - }); -} - var MatrixEffect = class MatrixEffect { // ---------------------------------------------------------------------- static methods @@ -208,4 +82,133 @@ var MatrixEffect = class MatrixEffect { tweakTransition('scale-x', 1); tweakTransition('scale-y', 1); } +} + + +if (utils.isInShellProcess()) { + + const {Clutter, GdkPixbuf, Cogl} = imports.gi; + const shaderSnippets = Me.imports.src.shaderSnippets; + + + MatrixShader = GObject.registerClass({Properties: {}, Signals: {}}, + class MatrixShader extends Clutter.ShaderEffect { + _init(settings) { + super._init({shader_type: Clutter.ShaderType.FRAGMENT_SHADER}); + + // Load the font texture. As the shader is re-created for each window animation, + // this texture is also re-created each time. This could be improved in the future! + const fontData = GdkPixbuf.Pixbuf.new_from_resource('/img/matrixFont.png'); + this._fontTexture = new Clutter.Image(); + this._fontTexture.set_data( + fontData.get_pixels(), + fontData.has_alpha ? Cogl.PixelFormat.RGBA_8888 : Cogl.PixelFormat.RGB_888, + fontData.width, fontData.height, fontData.rowstride); + + const trailColor = + Clutter.Color.from_string(settings.get_string('matrix-trail-color'))[1]; + const tipColor = + Clutter.Color.from_string(settings.get_string('matrix-tip-color'))[1]; + + // 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. + this.set_shader_source(` + + // Inject some common shader snippets. + ${shaderSnippets.standardUniforms()} + ${shaderSnippets.noise()} + + uniform sampler2D uFontTexture; + + // These may be configurable in the future. + const float EDGE_FADE = 30; + const float FADE_WIDTH = 150; + const float TRAIL_LENGTH = 0.2; + const float FINAL_FADE_START_TIME = 0.8; + const float LETTER_TILES = 16.0; + const float LETTER_SIZE = ${settings.get_int('matrix-scale')}; + const float LETTER_FLICKER_SPEED = 2.0; + const float RANDOMNESS = ${settings.get_double('matrix-randomness')}; + + // This returns a flickering grid of random letters. + float getText(vec2 fragCoord) { + vec2 pixelCoords = fragCoord * vec2(uSizeX, uSizeY); + vec2 uv = mod(pixelCoords.xy, LETTER_SIZE)/LETTER_SIZE; + vec2 block = pixelCoords/LETTER_SIZE - uv; + + // Choose random letter. + uv += floor(hash2D(floor(hash2D(block)*vec2(12.9898,78.233) + LETTER_FLICKER_SPEED*uTime + 42.254))*LETTER_TILES); + + return texture2D(uFontTexture, uv/LETTER_TILES).r; + } + + // This returns two values: The first are gradients for the "raindrops" which move + // from top to bottom. This is used for fading the letters. The second value is set + // to one below each drop and to zero above it. This second value is used for fading + // the window texture. + vec2 getRain(vec2 fragCoord) { + vec2 coords = fragCoord * vec2(uSizeX, uSizeY); + coords.x -= mod(coords.x, LETTER_SIZE); + + float delay = fract(sin(coords.x)*78.233) * mix(0.0, 1.0, RANDOMNESS); + float speed = fract(cos(coords.x)*12.989) * mix(0.0, 0.3, RANDOMNESS) + 1.5; + + float distToDrop = (uProgress*2-delay)*speed - fragCoord.y; + + float rainAlpha = distToDrop >= 0 ? exp(-distToDrop/TRAIL_LENGTH) : 0; + float windowAlpha = 1 - clamp(uSizeY*distToDrop, 0, FADE_WIDTH) / FADE_WIDTH; + + return vec2(rainAlpha, windowAlpha); + } + + void main() { + + // Get a cool matrix effect. See comments for those methods above. + vec2 rain = getRain(cogl_tex_coord_in[0].st); + float text = getText(cogl_tex_coord_in[0].st); + + // Get the window texture and fade it according to the effect mask. + cogl_color_out = texture2D(uTexture, cogl_tex_coord_in[0].st) * rain.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 * rain.x; + + // Fade at window borders. + vec2 pos = cogl_tex_coord_in[0].st * vec2(uSizeX, uSizeY); + rainAlpha *= clamp(pos.x / EDGE_FADE, 0, 1); + rainAlpha *= clamp(pos.y / EDGE_FADE, 0, 1); + rainAlpha *= clamp((uSizeX - pos.x) / EDGE_FADE, 0, 1); + rainAlpha *= clamp((uSizeY - pos.y) / EDGE_FADE, 0, 1); + + // 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); + } + `); + }; + + // 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 + // get_target() back then but this is not wrapped in GJS. + // https://gitlab.gnome.org/GNOME/mutter/-/blob/gnome-3-36/clutter/clutter/clutter-offscreen-effect.c#L598 + vfunc_paint_target(node, paint_context) { + const pipeline = this.get_pipeline(); + pipeline.set_layer_texture(1, this._fontTexture.get_texture()); + this.set_uniform_value('uFontTexture', 1); + super.vfunc_paint_target(node, paint_context); + } + }); } \ No newline at end of file diff --git a/src/TRexEffect.js b/src/TRexEffect.js index 6d650f5..2687498 100644 --- a/src/TRexEffect.js +++ b/src/TRexEffect.js @@ -27,145 +27,6 @@ const utils = Me.imports.src.utils; let TRexShader = null; -if (utils.isInShellProcess()) { - - const {Clutter, GdkPixbuf, Cogl} = imports.gi; - const shaderSnippets = Me.imports.src.shaderSnippets; - - - TRexShader = GObject.registerClass({Properties: {}, Signals: {}}, - class TRexShader extends Clutter.ShaderEffect { - _init(settings) { - super._init({shader_type: Clutter.ShaderType.FRAGMENT_SHADER}); - - // Load the claw texture. As the shader is re-created for each window animation, - // this texture is also re-created each time. This could be improved in the future! - // See assets/README.md for how this texture was created. - const clawData = GdkPixbuf.Pixbuf.new_from_resource('/img/claws.png'); - this._clawTexture = new Clutter.Image(); - this._clawTexture.set_data( - clawData.get_pixels(), Cogl.PixelFormat.RGB_888, clawData.width, - clawData.height, clawData.rowstride); - - const color = - Clutter.Color.from_string(settings.get_string('claw-scratch-color'))[1]; - - this.set_shader_source(` - -// Inject some common shader snippets. -${shaderSnippets.standardUniforms()} -${shaderSnippets.noise()} - -// See assets/README.md for how this texture was created. -uniform sampler2D uClawTexture; - -const vec2 SEED = vec2(${Math.random()}, ${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 MAX_SPAWN_TIME = 0.5; // Scratches will only start in the first half of the animation. -const float FF_TIME = 0.5; // Relative time for the final fade to transparency. - -// This method generates a grid of randomly rotated, slightly shifted and scaled -// UV squares. It returns the texture coords of the UV square at the given actor -// coordinates. If these do not fall into one of the UV grids, the coordinates of -// the closest UV grid will be clamped and returned. -vec2 getClawUV(vec2 texCoords, float gridScale, vec2 seed) { - -// Shift coordinates by a random offset and make sure the have a 1:1 aspect ratio. -vec2 coords = texCoords + hash2D(seed); -coords *= uSizeX < uSizeY ? vec2(1.0, 1.0 * uSizeY / uSizeX) : vec2(1.0 * uSizeX / uSizeY, 1.0); - -// Apply global scale. -coords *= gridScale; - -// Get grid cell coordinates in [0..1]. -vec2 cellUV = mod(coords, vec2(1)); - -// This is unique for each cell. -vec2 cellID = coords-cellUV + vec2(362.456); - -// Add random rotation, scale and offset to each grid cell. -float scale = mix(0.8, 1.0, hash(cellID*seed*134.451)); -float offsetX = mix(0.0, 1.0 - scale, hash(cellID*seed*54.4129)); -float offsetY = mix(0.0, 1.0 - scale, hash(cellID*seed*25.3089)); -float rotation = mix(0.0, 2.0 * 3.141, hash(cellID*seed*2.99837)); - -cellUV -= vec2(offsetX, offsetY); -cellUV /= scale; - -cellUV -= 0.5; -cellUV = vec2(cellUV.x * cos(rotation) - cellUV.y * sin(rotation), -cellUV.x * sin(rotation) + cellUV.y * cos(rotation)); -cellUV += 0.5; - -// Clamp resulting coordinates. -return clamp(cellUV, vec2(0), vec2(1)); -} - -void main() { - -// 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, WARP_INTENSITY)) * 0.5 + 0.5; -coords = mix(cogl_tex_coord_in[0].st, coords, uProgress); - -// Accumulate several random scratches. The color in the scratch map refers to the -// relative time when the respective part will become invisible. Therefore we can -// add a value to make the scratch appear later. -float scratchMap = 1.0; -for (int i=0; i uProgress ? 1 : 0); - -// Add colorful flashes. -float flashIntensity = 1.0 / FLASH_INTENSITY * (scratchMap - uProgress) + 1; -if (flashIntensity < 0 || flashIntensity >= 1) { -flashIntensity = 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), uProgress); - -// Fade out the remaining shards. -float fade = smoothstep(0, 1, 1 - clamp((uProgress - 1.0 + FF_TIME)/FF_TIME, 0, 1)); -cogl_color_out *= fade; - -// These are pretty useful for understanding how this works. -// cogl_color_out = vec4(vec3(flashIntensity), 1); -// cogl_color_out = vec4(vec3(scratchMap), 1); -} -`); - }; - - // 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 - // get_target() back then but this is not wrapped in GJS. - // https://gitlab.gnome.org/GNOME/mutter/-/blob/gnome-3-36/clutter/clutter/clutter-offscreen-effect.c#L598 - vfunc_paint_target(node, paint_context) { - const pipeline = this.get_pipeline(); - pipeline.set_layer_texture(1, this._clawTexture.get_texture()); - this.set_uniform_value('uClawTexture', 1); - super.vfunc_paint_target(node, paint_context); - } - }); -} - var TRexEffect = class TRexEffect { // ---------------------------------------------------------------------- static methods @@ -221,4 +82,144 @@ var TRexEffect = class TRexEffect { tweakTransition('scale-x', 1.0 + warp); tweakTransition('scale-y', 1.0 + warp); } +} + + +if (utils.isInShellProcess()) { + + const {Clutter, GdkPixbuf, Cogl} = imports.gi; + const shaderSnippets = Me.imports.src.shaderSnippets; + + + TRexShader = GObject.registerClass({Properties: {}, Signals: {}}, + class TRexShader extends Clutter.ShaderEffect { + _init(settings) { + super._init({shader_type: Clutter.ShaderType.FRAGMENT_SHADER}); + + // Load the claw texture. As the shader is re-created for each window animation, + // this texture is also re-created each time. This could be improved in the future! + // See assets/README.md for how this texture was created. + const clawData = GdkPixbuf.Pixbuf.new_from_resource('/img/claws.png'); + this._clawTexture = new Clutter.Image(); + this._clawTexture.set_data( + clawData.get_pixels(), Cogl.PixelFormat.RGB_888, clawData.width, + clawData.height, clawData.rowstride); + + const color = + Clutter.Color.from_string(settings.get_string('claw-scratch-color'))[1]; + + this.set_shader_source(` + + // Inject some common shader snippets. + ${shaderSnippets.standardUniforms()} + ${shaderSnippets.noise()} + + // See assets/README.md for how this texture was created. + uniform sampler2D uClawTexture; + + const vec2 SEED = vec2(${Math.random()}, ${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 MAX_SPAWN_TIME = 0.5; // Scratches will only start in the first half of the animation. + const float FF_TIME = 0.5; // Relative time for the final fade to transparency. + + // This method generates a grid of randomly rotated, slightly shifted and scaled + // UV squares. It returns the texture coords of the UV square at the given actor + // coordinates. If these do not fall into one of the UV grids, the coordinates of + // the closest UV grid will be clamped and returned. + vec2 getClawUV(vec2 texCoords, float gridScale, vec2 seed) { + + // Shift coordinates by a random offset and make sure the have a 1:1 aspect ratio. + vec2 coords = texCoords + hash2D(seed); + coords *= uSizeX < uSizeY ? vec2(1.0, 1.0 * uSizeY / uSizeX) : vec2(1.0 * uSizeX / uSizeY, 1.0); + + // Apply global scale. + coords *= gridScale; + + // Get grid cell coordinates in [0..1]. + vec2 cellUV = mod(coords, vec2(1)); + + // This is unique for each cell. + vec2 cellID = coords-cellUV + vec2(362.456); + + // Add random rotation, scale and offset to each grid cell. + float scale = mix(0.8, 1.0, hash(cellID*seed*134.451)); + float offsetX = mix(0.0, 1.0 - scale, hash(cellID*seed*54.4129)); + float offsetY = mix(0.0, 1.0 - scale, hash(cellID*seed*25.3089)); + float rotation = mix(0.0, 2.0 * 3.141, hash(cellID*seed*2.99837)); + + cellUV -= vec2(offsetX, offsetY); + cellUV /= scale; + + cellUV -= 0.5; + cellUV = vec2(cellUV.x * cos(rotation) - cellUV.y * sin(rotation), + cellUV.x * sin(rotation) + cellUV.y * cos(rotation)); + cellUV += 0.5; + + // Clamp resulting coordinates. + return clamp(cellUV, vec2(0), vec2(1)); + } + + void main() { + + // 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, WARP_INTENSITY)) * 0.5 + 0.5; + coords = mix(cogl_tex_coord_in[0].st, coords, uProgress); + + // Accumulate several random scratches. The color in the scratch map refers to the + // relative time when the respective part will become invisible. Therefore we can + // add a value to make the scratch appear later. + float scratchMap = 1.0; + for (int i=0; i uProgress ? 1 : 0); + + // Add colorful flashes. + float flashIntensity = 1.0 / FLASH_INTENSITY * (scratchMap - uProgress) + 1; + if (flashIntensity < 0 || flashIntensity >= 1) { + flashIntensity = 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), uProgress); + + // Fade out the remaining shards. + float fade = smoothstep(0, 1, 1 - clamp((uProgress - 1.0 + FF_TIME)/FF_TIME, 0, 1)); + cogl_color_out *= fade; + + // These are pretty useful for understanding how this works. + // cogl_color_out = vec4(vec3(flashIntensity), 1); + // cogl_color_out = vec4(vec3(scratchMap), 1); + } + `); + }; + + // 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 + // get_target() back then but this is not wrapped in GJS. + // https://gitlab.gnome.org/GNOME/mutter/-/blob/gnome-3-36/clutter/clutter/clutter-offscreen-effect.c#L598 + vfunc_paint_target(node, paint_context) { + const pipeline = this.get_pipeline(); + pipeline.set_layer_texture(1, this._clawTexture.get_texture()); + this.set_uniform_value('uClawTexture', 1); + super.vfunc_paint_target(node, paint_context); + } + }); } \ No newline at end of file diff --git a/src/TVEffect.js b/src/TVEffect.js index d8c8de7..f8e4a6b 100644 --- a/src/TVEffect.js +++ b/src/TVEffect.js @@ -20,28 +20,81 @@ const Me = imports.misc.extensionUtils.getCurrentExtension(); const utils = Me.imports.src.utils; ////////////////////////////////////////////////////////////////////////////////////////// -// The TVEffectShader hides the actor by making it first transparent from top and // +// The TVShader hides the actor by making it first transparent from top and // // bottom towards the middle and then hiding the resulting line from left and right // // towards the center. // ////////////////////////////////////////////////////////////////////////////////////////// -let TVEffectShader = null; +let TVShader = null; + + +var TVEffect = class TVEffect { + + // ---------------------------------------------------------------------- static methods + + static getMinShellVersion() { + return [3, 36]; + } + + static getSettingsPrefix() { + return 'tv'; + } + + static getLabel() { + return 'TV Effect'; + } + + static initPreferences(dialog) { + + dialog.getBuilder().add_from_resource( + `/ui/${utils.isGTK4() ? 'gtk4' : 'gtk3'}/tvPage.ui`); + + // Bind all properties. + dialog.bindAdjustment('tv-animation-time'); + dialog.bindColorButton('tv-effect-color'); + + const stack = dialog.getBuilder().get_object('main-stack'); + stack.add_titled(dialog.getBuilder().get_object('tv-prefs'), 'tv', 'TV Effect'); + } + + static createShader(settings) { + return new TVShader(settings); + } + + // If there's a transition in progress, we re-target these transitions so that the + // window is neither scaled nor faded. + static tweakTransitions(actor, settings) { + const animationTime = settings.get_int('tv-animation-time'); + + const tweakTransition = (property, value) => { + const transition = actor.get_transition(property); + if (transition) { + transition.set_to(value); + transition.set_duration(animationTime); + } + }; + + tweakTransition('opacity', 255); + tweakTransition('scale-x', 1); + tweakTransition('scale-y', 0.5); + } +} + if (utils.isInShellProcess()) { const Clutter = imports.gi.Clutter; const shaderSnippets = Me.imports.src.shaderSnippets; - TVEffectShader = GObject.registerClass( - {Properties: {}, Signals: {}}, class TVEffectShader extends Clutter.ShaderEffect { - _init(settings) { - super._init({shader_type: Clutter.ShaderType.FRAGMENT_SHADER}); + TVShader = GObject.registerClass({Properties: {}, Signals: {}}, + class TVShader extends Clutter.ShaderEffect { + _init(settings) { + super._init({shader_type: Clutter.ShaderType.FRAGMENT_SHADER}); - const color = - Clutter.Color.from_string(settings.get_string('tv-effect-color'))[1]; + const color = Clutter.Color.from_string(settings.get_string('tv-effect-color'))[1]; - this.set_shader_source(` + this.set_shader_source(` // Inject some common shader snippets. ${shaderSnippets.standardUniforms()} @@ -94,58 +147,6 @@ if (utils.isInShellProcess()) { // cogl_color_out = vec4(vec3(mask), 1); } `); - }; - }); -} - -var TVEffect = class TVEffect { - - // ---------------------------------------------------------------------- static methods - - static getMinShellVersion() { - return [3, 36]; - } - - static getSettingsPrefix() { - return 'tv'; - } - - static getLabel() { - return 'TV Effect'; - } - - static initPreferences(dialog) { - - dialog.getBuilder().add_from_resource( - `/ui/${utils.isGTK4() ? 'gtk4' : 'gtk3'}/tvPage.ui`); - - // Bind all properties. - dialog.bindAdjustment('tv-animation-time'); - dialog.bindColorButton('tv-effect-color'); - - const stack = dialog.getBuilder().get_object('main-stack'); - stack.add_titled(dialog.getBuilder().get_object('tv-prefs'), 'tv', 'TV Effect'); - } - - static createShader(settings) { - return new TVEffectShader(settings); - } - - // If there's a transition in progress, we re-target these transitions so that the - // window is neither scaled nor faded. - static tweakTransitions(actor, settings) { - const animationTime = settings.get_int('tv-animation-time'); - - const tweakTransition = (property, value) => { - const transition = actor.get_transition(property); - if (transition) { - transition.set_to(value); - transition.set_duration(animationTime); - } }; - - tweakTransition('opacity', 255); - tweakTransition('scale-x', 1); - tweakTransition('scale-y', 0.5); - } + }); } \ No newline at end of file