Currently I’m mimicking ShaderToy style buffer pass using an extension, where I need to get texture data from an Effect and apply it to another Effect through Cogl.Pipeline.set_layer_texture(), below I’ve implemented a demo extension to test
import { Extension } from 'resource:///org/gnome/shell/extensions/extension.js';
import Clutter from 'gi://Clutter';
import Cogl from 'gi://Cogl';
import GLib from 'gi://GLib';
import GObject from 'gi://GObject';
import * as Main from 'resource:///org/gnome/shell/ui/main.js';
const DemoEffect = GObject.registerClass(
class DemoEffect extends Clutter.ShaderEffect {
_init(params = {}) {
super._init();
if (params.name)
this.set_name(params.name);
this._bufferA = params.buff
this.set_shader_source(this.transformShader(this._bufferA));
this._startTime = GLib.get_monotonic_time() / 1e6;
}
vfunc_paint(node, paintContext, flags) {
super.vfunc_paint(node, paintContext, flags);
if(this._bufferA){
return;
}
if(!this.texturesLoaded) {
this.get_pipeline().set_layer_texture(0, this._texture);
this.texturesLoaded = true;
}
let buffer = Main.layoutManager._backgroundGroup.get_children()
.find(a => a.get_effect('buffer-a'));
let bufferTex = buffer.get_effect('buffer-a').get_texture();
bufferTex?.get_data(
bufferTex.get_format(),
this._rowstride,
this._pixelData
);
this._texture.set_data(
bufferTex?.get_format(),
this._rowstride,
this._pixelData,
0
);
}
vfunc_set_actor(actor) {
super.vfunc_set_actor(actor);
if (this._tickId) {
GLib.Source.remove(this._tickId);
this._tickId = null;
}
if (this._sizeId && this._actor) {
this._actor.disconnect(this._sizeId);
this._sizeId = null;
}
this._actor = actor;
if(actor) {
const interval = 1000/60;
if(!this._bufferA) {
let ctx = actor.get_context().get_backend().get_cogl_context();
let width = actor.width;
let height = actor.height;
this._rowstride = width * 4;
this._pixelData = new Uint8Array(this._rowstride * height);
this._texture = Cogl.Texture2D.new_with_size(ctx, width, height);
console.log('width: '+width+' height: '+height);
}
this._updateResolution(actor);
this._sizeId = actor.connect('notify::size', () => {
this._updateResolution(actor);
this.texturesLoaded = false;
});
for (let i = 0; i < 4; i++) {
this.set_uniform_value(`iChannel${i}`, parseInt(i));
}
this._tickId = GLib.timeout_add(GLib.PRIORITY_DEFAULT, interval, () => {
let now = (GLib.get_monotonic_time() / 1e6) - this._startTime;
this.set_uniform_value('iTime', parseFloat(now));
return GLib.SOURCE_CONTINUE;
});
}
}
_updateResolution(actor) {
this.set_uniform_value('res[0]', parseFloat(actor.get_width()));
this.set_uniform_value('res[1]', parseFloat(actor.get_height()));
}
transformShader(bufferA) {
const header = `uniform float iTime;
uniform float res[2];
uniform sampler2D iChannel0;
uniform sampler2D iChannel1;
uniform sampler2D iChannel2;
uniform sampler2D iChannel3;
vec3 iResolution = vec3(res[0], res[1], 0.0);
vec2 fragCoord = cogl_tex_coord_in[0].st * iResolution.xy;
`;
const footer = `
void main() {
vec4 color = vec4(0.0);
mainImage(color, fragCoord);
cogl_color_out = color;
}`;
const source = bufferA ?
`void mainImage( out vec4 fragColor, in vec2 fragCoord )
{
vec2 uv = fragCoord / iResolution.xy;
float x = uv.x;
float y = uv.y;
float r = sin(x * 10.0 + iTime) * 0.5 + 0.5;
float g = sin(y * 10.0 - iTime) * 0.5 + 0.5;
fragColor = vec4(r, g, 0.0, 1.0);
}`
:
`void mainImage( out vec4 fragColor, in vec2 fragCoord )
{
vec2 uv = fragCoord / iResolution.xy;
vec4 a = texture(iChannel0, uv);
fragColor = vec4(a.b, a.g, a.r, 1.0);
}`;
return header + source + footer;
}
});
export default class ShaderExtension extends Extension {
enable() {
this._bufferA = new Clutter.Actor({
width: 100,
height: 100,
effect: new DemoEffect({
name: 'buffer-a',
buff: true
})
});
this._final = new Clutter.Actor({
width: 100,
height: 100,
effect: new DemoEffect({
name: 'final-effect',
buff: false
})
});
this._final.set_position(global.stage.width/2, global.stage.height/2);
Main.layoutManager._backgroundGroup.add_child(this._bufferA);
Main.layoutManager._backgroundGroup.add_child(this._final);
}
disable() {
Main.layoutManager._backgroundGroup.remove_child(this._bufferA);
Main.layoutManager._backgroundGroup.remove_child(this._final);
this._bufferA.destroy();
this._bufferA = null;
this._final.destroy();
this._final = null;
}
}
I’m trying to get texture data from existing effect from below code,
let buffer = Main.layoutManager._backgroundGroup.get_children()
.find(a => a.get_effect('buffer-a'));
let bufferTex = buffer.get_effect('buffer-a').get_texture();
bufferTex?.get_data(
bufferTex.get_format(),
this._rowstride,
this._pixelData
);
this._texture.set_data(
bufferTex?.get_format(),
this._rowstride,
this._pixelData,
0
);
it is giving like
== Stack trace for context 0x56355274b440 ==
#0 563552843438 i file:///home/zombie/.local/share/gnome-shell/extensions/test@shader/extension.js:190 (4fc4b0c19c0 @ 290)
#1 5635528433a8 i resource:///org/gnome/shell/ui/init.js:21 (2bf1e5a7ea60 @ 48)
Can anyone give suggestions to get texture data from an existing effect or any other way to do this type of low-level operation?