diff --git a/demos/gtk-demo/alienplanet.glsl b/demos/gtk-demo/alienplanet.glsl
new file mode 100644
index 0000000000..3e3aea3ac8
--- /dev/null
+++ b/demos/gtk-demo/alienplanet.glsl
@@ -0,0 +1,345 @@
+// Originally from: https://www.shadertoy.com/view/wsjBD3
+// License CC0: A battered alien planet
+// Been experimenting with space inspired shaders
+
+#define PI 3.141592654
+#define TAU (2.0*PI)
+
+#define TOLERANCE 0.00001
+#define MAX_ITER 65
+#define MIN_DISTANCE 0.01
+#define MAX_DISTANCE 9.0
+
+const vec3 skyCol1 = vec3(0.35, 0.45, 0.6);
+const vec3 skyCol2 = vec3(0.4, 0.7, 1.0);
+const vec3 skyCol3 = pow(skyCol1, vec3(0.25));
+const vec3 sunCol1 = vec3(1.0,0.6,0.4);
+const vec3 sunCol2 = vec3(1.0,0.9,0.7);
+const vec3 smallSunCol1 = vec3(1.0,0.5,0.25)*0.5;
+const vec3 smallSunCol2 = vec3(1.0,0.5,0.25)*0.5;
+const vec3 mountainColor = 1.0*sqrt(vec3(0.95, 0.65, 0.45));
+const float cellWidth = 1.0;
+const vec4 planet = vec4(80.0, -20.0, 100.0, 50.0)*1000.0;
+
+void rot(inout vec2 p, float a) {
+ float c = cos(a);
+ float s = sin(a);
+ p = vec2(p.x*c + p.y*s, -p.x*s + p.y*c);
+}
+
+vec2 mod2(inout vec2 p, vec2 size) {
+ vec2 c = floor((p + size*0.5)/size);
+ p = mod(p + size*0.5,size) - size*0.5;
+ return c;
+}
+
+float circle(vec2 p, float r) {
+ return length(p) - r;
+}
+
+float egg(vec2 p, float ra, float rb) {
+ const float k = sqrt(3.0);
+ p.x = abs(p.x);
+ float r = ra - rb;
+ return ((p.y<0.0) ? length(vec2(p.x, p.y )) - r :
+ (k*(p.x+r)
0.0 && b > 0.0) return vec2(-1.0, -1.0);
+ float discr = b * b - c;
+ if(discr < 0.0) return vec2(-1.0);
+ float normalMultiplier = 1.0;
+ float s = sqrt(discr);
+ float t0 = -b - s;
+ float t1 = -b + s;;
+ return vec2(t0, t1);
+}
+
+float noise1(vec2 p) {
+ vec2 n = mod2(p, vec2(cellWidth));
+ vec2 hh = hash(sqrt(2.0)*(n+1000.0));
+ hh.x *= hh.y;
+
+ float r = 0.225*cellWidth;
+
+ float d = circle(p, 2.0*r);
+
+ float h = hh.x*smoothstep(0.0, r, -d);
+
+ return h*0.25;
+}
+
+float noise2(vec2 p) {
+ vec2 n = mod2(p, vec2(cellWidth));
+ vec2 hh = hash(sqrt(2.0)*(n+1000.0));
+ hh.x *= hh.y;
+
+ rot(p, TAU*hh.y);
+ float r = 0.45*cellWidth;
+
+// float d = circle(p, 1.0*r);
+ float d = egg(p, 0.75*r, 0.5*r*abs(hh.y));
+
+ float h = (hh.x)*smoothstep(0.0, r, -2.0*d);
+
+ return h*0.275;
+}
+
+
+float height(vec2 p, float dd, int mx) {
+ const float aa = 0.45;
+ const float ff = 2.03;
+ const float tt = 1.2;
+ const float oo = 3.93;
+ const float near = 0.25;
+ const float far = 0.65;
+
+ float a = 1.0;
+ float o = 0.2;
+ float s = 0.0;
+ float d = 0.0;
+
+ int i = 0;
+
+ for (; i < 4;++i) {
+ float nn = a*noise2(p);
+ s += nn;
+ d += abs(a);
+ p += o;
+ a *= aa;
+ p *= ff;
+ o *= oo;
+ rot(p, tt);
+ }
+
+ float lod = s/d;
+
+ float rdd = dd/MAX_DISTANCE;
+ mx = int(mix(float(4), float(mx), step(rdd, far)));
+
+ for (; i < mx; ++i) {
+ float nn = a*noise1(p);
+ s += nn;
+ d += abs(a);
+ p += o;
+ a *= aa;
+ p *= ff;
+ o *= oo;
+ rot(p, tt);
+ }
+
+ float hid = (s/d);
+
+ return mix(hid, lod, smoothstep(near, far, rdd));
+}
+
+float loheight(vec2 p, float d) {
+ return height(p, d, 0);
+}
+
+float height(vec2 p, float d) {
+ return height(p, d, 6);
+}
+
+float hiheight(vec2 p, float d) {
+ return height(p, d, 8);
+}
+
+vec3 normal(vec2 p, float d) {
+ vec2 eps = vec2(0.00125, 0.0);
+
+ vec3 n;
+
+ n.x = (hiheight(p - eps.xy, d) - hiheight(p + eps.xy, d));
+ n.y = 2.0*eps.x;
+ n.z = (hiheight(p - eps.yx, d) - hiheight(p + eps.yx, d));
+
+ return normalize(n);
+}
+
+const float stepLength[] = float[](0.9, 0.25);
+
+
+float march(vec3 ro, vec3 rd, out int max_iter) {
+ float dt = 0.1;
+ float d = MIN_DISTANCE;
+ int currentStep = 0;
+ float lastd = d;
+ for (int i = 0; i < MAX_ITER; ++i)
+ {
+ vec3 p = ro + d*rd;
+ float h = height(p.xz, d);
+
+ if (d > MAX_DISTANCE) {
+ max_iter = i;
+ return MAX_DISTANCE;
+ }
+
+ float hd = p.y - h;
+
+ if (hd < TOLERANCE) {
+ ++currentStep;
+ if (currentStep >= stepLength.length()) {
+ max_iter = i;
+ return d;
+ }
+
+ d = lastd;
+ continue;
+ }
+
+ float sl = stepLength[currentStep];
+
+ dt = max(hd, TOLERANCE)*sl + 0.0025*d;
+ lastd = d;
+ d += dt;
+ }
+
+ max_iter = MAX_ITER;
+ return MAX_DISTANCE;
+}
+
+vec3 sunDirection() {
+ return normalize(vec3(-0.5, 0.085, 1.0));
+}
+
+vec3 smallSunDirection() {
+ return normalize(vec3(-0.2, -0.05, 1.0));
+}
+
+float psin(float f) {
+ return 0.5 + 0.5*sin(f);
+}
+
+vec3 skyColor(vec3 ro, vec3 rd) {
+ vec3 sunDir = sunDirection();
+ vec3 smallSunDir = smallSunDirection();
+
+ float sunDot = max(dot(rd, sunDir), 0.0);
+ float smallSunDot = max(dot(rd, smallSunDir), 0.0);
+
+ float angle = atan(rd.y, length(rd.xz))*2.0/PI;
+
+ vec3 skyCol = mix(mix(skyCol1, skyCol2, max(0.0, angle)), skyCol3, clamp(-angle*2.0, 0.0, 1.0));
+
+ vec3 sunCol = 0.5*sunCol1*pow(sunDot, 20.0) + 8.0*sunCol2*pow(sunDot, 2000.0);
+ vec3 smallSunCol = 0.5*smallSunCol1*pow(smallSunDot, 200.0) + 8.0*smallSunCol2*pow(smallSunDot, 20000.0);
+
+ vec3 dust = pow(sunCol2*mountainColor, vec3(1.75))*smoothstep(0.05, -0.1, rd.y)*0.5;
+
+ vec2 si = raySphere(ro, rd, planet);
+
+ vec3 planetSurface = ro + si.x*rd;
+ vec3 planetNormal = normalize(planetSurface - planet.xyz);
+ float planetDiff = max(dot(planetNormal, sunDir), 0.0);
+ float planetBorder = max(dot(planetNormal, -rd), 0.0);
+ float planetLat = (planetSurface.x+planetSurface.y)*0.0005;
+ vec3 planetCol = mix(1.3*vec3(0.9, 0.8, 0.7), 0.3*vec3(0.9, 0.8, 0.7), pow(psin(planetLat+1.0)*psin(sqrt(2.0)*planetLat+2.0)*psin(sqrt(3.5)*planetLat+3.0), 0.5));
+
+ vec3 final = vec3(0.0);
+
+ final += step(0.0, si.x)*pow(planetDiff, 0.75)*planetCol*smoothstep(-0.075, 0.0, rd.y)*smoothstep(0.0, 0.1, planetBorder);
+
+ final += skyCol + sunCol + smallSunCol + dust;
+
+ return final;
+}
+
+vec3 getColor(vec3 ro, vec3 rd) {
+ int max_iter = 0;
+ vec3 skyCol = skyColor(ro, rd);
+ vec3 col = vec3(0);
+
+ float d = march(ro, rd, max_iter);
+
+ if (d < MAX_DISTANCE) {
+ vec3 sunDir = sunDirection();
+ vec3 osunDir = sunDir*vec3(-1.0, .0, -1.0);
+ vec3 p = ro + d*rd;
+
+ vec3 normal = normal(p.xz, d);
+
+ float amb = 0.2;
+
+ float dif1 = max(0.0, dot(sunDir, normal));
+ vec3 shd1 = sunCol2*mix(amb, 1.0, pow(dif1, 0.75));
+
+ float dif2 = max(0.0, dot(osunDir, normal));
+ vec3 shd2 = sunCol1*mix(amb, 1.0, pow(dif2, 0.75));
+
+ vec3 ref = reflect(rd, normal);
+ vec3 rcol = skyColor(p, ref);
+
+ col = mountainColor*amb*skyCol3;
+ col += mix(shd1, shd2, -0.5)*mountainColor;
+ float fre = max(dot(normal, -rd), 0.0);
+ fre = pow(1.0 - fre, 5.0);
+ col += rcol*fre*0.5;
+ col += (1.0*p.y);
+ col = tanh(col);
+ col = mix(col, skyCol, smoothstep(0.5*MAX_DISTANCE, 1.0*MAX_DISTANCE, d));
+
+ } else {
+ col = skyCol;
+ }
+
+// col += vec3(1.1, 0.0, 0.0)* smoothstep(0.25, 1.0,(float(max_iter)/float(MAX_ITER)));
+ return col;
+}
+
+vec3 getSample1(vec2 p, float time) {
+ float off = 0.5*iTime;
+
+ vec3 ro = vec3(0.5, 1.0-0.25, -2.0 + off);
+ vec3 la = ro + vec3(0.0, -0.30, 2.0);
+
+ vec3 ww = normalize(la - ro);
+ vec3 uu = normalize(cross(vec3(0.0,1.0,0.0), ww));
+ vec3 vv = normalize(cross(ww, uu));
+ vec3 rd = normalize(p.x*uu + p.y*vv + 2.0*ww);
+
+ vec3 col = getColor(ro, rd) ;
+
+ return col;
+}
+
+vec3 getSample2(vec2 p, float time) {
+ p.y-=time*0.25;
+ float h = height(p, 0.0);
+ vec3 n = normal(p, 0.0);
+
+ vec3 lp = vec3(10.0, -1.2, 0.0);
+
+ vec3 ld = normalize(vec3(p.x, h, p.y)- lp);
+
+ float d = max(dot(ld, n), 0.0);
+
+ vec3 col = vec3(0.0);
+
+ col = vec3(1.0)*(h+0.1);
+ col += vec3(1.5)*pow(d, 0.75);
+
+ return col;
+}
+
+void mainImage(out vec4 fragColor, vec2 fragCoord) {
+ vec2 q = fragCoord.xy/iResolution.xy;
+ vec2 p = -1.0 + 2.0*q;
+ p.x *= iResolution.x/iResolution.y;
+
+ vec3 col = getSample1(p, iTime);
+
+ fragColor = vec4(col, 1.0);
+}
diff --git a/demos/gtk-demo/cogs.glsl b/demos/gtk-demo/cogs.glsl
new file mode 100644
index 0000000000..a0768bdb39
--- /dev/null
+++ b/demos/gtk-demo/cogs.glsl
@@ -0,0 +1,224 @@
+// Originally from: https://www.shadertoy.com/view/3ljyDD
+// License CC0: Hexagonal tiling + cog wheels
+// Nothing fancy, just hexagonal tiling + cog wheels
+
+#define PI 3.141592654
+#define TAU (2.0*PI)
+#define MROT(a) mat2(cos(a), sin(a), -sin(a), cos(a))
+
+float hash(in vec2 co) {
+ return fract(sin(dot(co.xy ,vec2(12.9898,58.233))) * 13758.5453);
+}
+
+float pcos(float a) {
+ return 0.5 + 0.5*cos(a);
+}
+
+void rot(inout vec2 p, float a) {
+ float c = cos(a);
+ float s = sin(a);
+ p = vec2(c*p.x + s*p.y, -s*p.x + c*p.y);
+}
+
+float modPolar(inout vec2 p, float repetitions) {
+ float angle = 2.0*PI/repetitions;
+ float a = atan(p.y, p.x) + angle/2.;
+ float r = length(p);
+ float c = floor(a/angle);
+ a = mod(a,angle) - angle/2.;
+ p = vec2(cos(a), sin(a))*r;
+ // For an odd number of repetitions, fix cell index of the cell in -x direction
+ // (cell index would be e.g. -5 and 5 in the two halves of the cell):
+ if (abs(c) >= (repetitions/2.0)) c = abs(c);
+ return c;
+}
+
+float pmin(float a, float b, float k) {
+ float h = clamp( 0.5+0.5*(b-a)/k, 0.0, 1.0 );
+ return mix( b, a, h ) - k*h*(1.0-h);
+}
+
+const vec2 sz = vec2(1.0, sqrt(3.0));
+const vec2 hsz = 0.5*sz;
+const float smallCount = 16.0;
+
+vec2 hextile(inout vec2 p) {
+ // See Art of Code: Hexagonal Tiling Explained!
+ // https://www.youtube.com/watch?v=VmrIDyYiJBA
+
+ vec2 p1 = mod(p, sz)-hsz;
+ vec2 p2 = mod(p - hsz*1.0, sz)-hsz;
+ vec2 p3 = mix(p2, p1, vec2(length(p1) < length(p2)));
+ vec2 n = p3 - p;
+ p = p3;
+
+ return n;
+}
+
+float circle(vec2 p, float r) {
+ return length(p) - r;
+}
+
+float box(vec2 p, vec2 b) {
+ vec2 d = abs(p)-b;
+ return length(max(d,0.0)) + min(max(d.x,d.y),0.0);
+}
+
+float unevenCapsule(vec2 p, float r1, float r2, float h) {
+ p.x = abs(p.x);
+ float b = (r1-r2)/h;
+ float a = sqrt(1.0-b*b);
+ float k = dot(p,vec2(-b,a));
+ if( k < 0.0 ) return length(p) - r1;
+ if( k > a*h ) return length(p-vec2(0.0,h)) - r2;
+ return dot(p, vec2(a,b) ) - r1;
+}
+
+float cogwheel(vec2 p, float innerRadius, float outerRadius, float cogs, float holes) {
+ float cogWidth = 0.25*innerRadius*TAU/cogs;
+
+ float d0 = circle(p, innerRadius);
+
+ vec2 icp = p;
+ modPolar(icp, holes);
+ icp -= vec2(innerRadius*0.55, 0.0);
+ float d1 = circle(icp, innerRadius*0.25);
+
+ vec2 cp = p;
+ modPolar(cp, cogs);
+ cp -= vec2(innerRadius, 0.0);
+ float d2 = unevenCapsule(cp.yx, cogWidth, cogWidth*0.75, (outerRadius-innerRadius));
+
+ float d3 = circle(p, innerRadius*0.20);
+
+ float d = 1E6;
+ d = min(d, d0);
+ d = pmin(d, d2, 0.5*cogWidth);
+ d = min(d, d2);
+ d = max(d, -d1);
+ d = max(d, -d3);
+
+ return d;
+}
+
+float ccell1(vec2 p, float r) {
+ float d = 1E6;
+ const float bigCount = 60.0;
+
+ vec2 cp0 = p;
+ rot(cp0, -iTime*TAU/bigCount);
+ float d0 = cogwheel(cp0, 0.36, 0.38, bigCount, 5.0);
+
+ vec2 cp1 = p;
+ float nm = modPolar(cp1, 6.0);
+
+ cp1 -= vec2(0.5, 0.0);
+ rot(cp1, 0.2+TAU*nm/2.0 + iTime*TAU/smallCount);
+ float d1 = cogwheel(cp1, 0.11, 0.125, smallCount, 5.0);
+
+ d = min(d, d0);
+ d = min(d, d1);
+ return d;
+}
+
+float ccell2(vec2 p, float r) {
+ float d = 1E6;
+ vec2 cp0 = p;
+ float nm = modPolar(cp0, 6.0);
+ vec2 cp1 = cp0;
+ const float off = 0.275;
+ const float count = smallCount + 2.0;
+ cp0 -= vec2(off, 0.0);
+ rot(cp0, 0.+TAU*nm/2.0 - iTime*TAU/count);
+ float d0 = cogwheel(cp0, 0.09, 0.105, count, 5.0);
+
+
+ cp1 -= vec2(0.5, 0.0);
+ rot(cp1, 0.2+TAU*nm/2.0 + iTime*TAU/smallCount);
+ float d1 = cogwheel(cp1, 0.11, 0.125, smallCount, 5.0);
+
+ float l = length(p);
+ float d2 = l - (off+0.055);
+ float d3 = d2 + 0.020;;
+
+ vec2 tp0 = p;
+ modPolar(tp0, 60.0);
+ tp0.x -= off;
+ float d4 = box(tp0, vec2(0.0125, 0.005));
+
+ float ctime = -(iTime*0.05 + r)*TAU;
+
+ vec2 tp1 = p;
+ rot(tp1, ctime*12.0);
+ tp1.x -= 0.13;
+ float d5 = box(tp1, vec2(0.125, 0.005));
+
+ vec2 tp2 = p;
+ rot(tp2, ctime);
+ tp2.x -= 0.13*0.5;
+ float d6 = box(tp2, vec2(0.125*0.5, 0.0075));
+
+ float d7 = l - 0.025;
+ float d8 = l - 0.0125;
+
+ d = min(d, d0);
+ d = min(d, d1);
+ d = min(d, d2);
+ d = max(d, -d3);
+ d = min(d, d4);
+ d = min(d, d5);
+ d = min(d, d6);
+ d = min(d, d7);
+ d = max(d, -d8);
+
+ return d;
+}
+
+float df(vec2 p, float scale, inout vec2 nn) {
+ p /= scale;
+ nn = hextile(p);
+ nn = round(nn);
+ float r = hash(nn);
+
+ float d;;
+
+ if (r < 0.5) {
+ d = ccell1(p, r);
+ } else {
+ d = ccell2(p, r);
+ }
+
+ return d*scale;
+}
+
+vec3 postProcess(vec3 col, vec2 q) {
+ //col = saturate(col);
+ col=pow(clamp(col,0.0,1.0),vec3(0.75));
+ col=col*0.6+0.4*col*col*(3.0-2.0*col); // contrast
+ col=mix(col, vec3(dot(col, vec3(0.33))), -0.4); // satuation
+ col*=0.5+0.5*pow(19.0*q.x*q.y*(1.0-q.x)*(1.0-q.y),0.7); // vigneting
+ return col;
+}
+
+void mainImage(out vec4 fragColor, vec2 fragCoord) {
+ vec2 q = fragCoord/iResolution.xy;
+ vec2 p = -1.0 + 2.0*q;
+ p.x *= iResolution.x/iResolution.y;
+ float tm = iTime*0.1;
+ p += vec2(cos(tm), sin(tm*sqrt(0.5)));
+ float z = mix(0.5, 1.0, pcos(tm*sqrt(0.3)));
+ float aa = 4.0 / iResolution.y;
+
+ vec2 nn = vec2(0.0);
+ float d = df(p, z, nn);
+
+ vec3 col = vec3(160.0)/vec3(255.0);
+ vec3 baseCol = vec3(0.3);
+ vec4 logoCol = vec4(baseCol, 1.0)*smoothstep(-aa, 0.0, -d);
+ col = mix(col, logoCol.xyz, pow(logoCol.w, 8.0));
+ col += 0.4*pow(abs(sin(20.0*d)), 0.6);
+
+ col = postProcess(col, q);
+
+ fragColor = vec4(col, 1.0);
+}
diff --git a/demos/gtk-demo/demo.gresource.xml b/demos/gtk-demo/demo.gresource.xml
index 7c9c657f5e..f179d9819b 100644
--- a/demos/gtk-demo/demo.gresource.xml
+++ b/demos/gtk-demo/demo.gresource.xml
@@ -121,6 +121,15 @@
gtkgears.c
gtkgears.h
+
+ gtkshadertoy.c
+ gtkshadertoy.h
+ alienplanet.glsl
+ mandelbrot.glsl
+ neon.glsl
+ cogs.glsl
+ glowingstars.glsl
+
iconscroll.ui
@@ -260,6 +269,7 @@
scale.c
search_entry.c
search_entry2.c
+ shadertoy.c
shortcuts.c
shortcut_triggers.c
sizegroup.c
diff --git a/demos/gtk-demo/glowingstars.glsl b/demos/gtk-demo/glowingstars.glsl
new file mode 100644
index 0000000000..3d73131024
--- /dev/null
+++ b/demos/gtk-demo/glowingstars.glsl
@@ -0,0 +1,174 @@
+// Originally from: https://www.shadertoy.com/view/ttBcRV
+// License CC0: Flying through glowing stars
+// The result of playing around trying to improve an old shader
+
+#define PI 3.141592654
+#define TAU (2.0*PI)
+#define TIME iTime
+#define RESOLUTION iResolution
+
+#define LESS(a,b,c) mix(a,b,step(0.,c))
+#define SABS(x,k) LESS((.5/(k))*(x)*(x)+(k)*.5,abs(x),abs(x)-(k))
+
+#define MROT(a) mat2(cos(a), sin(a), -sin(a), cos(a))
+
+vec3 hsv2rgb(vec3 c) {
+ const vec4 K = vec4(1.0, 2.0 / 3.0, 1.0 / 3.0, 3.0);
+ vec3 p = abs(fract(c.xxx + K.xyz) * 6.0 - K.www);
+ return c.z * mix(K.xxx, clamp(p - K.xxx, 0.0, 1.0), c.y);
+}
+
+float hash(in vec3 co) {
+ return fract(sin(dot(co, vec3(12.9898,58.233, 12.9898+58.233))) * 13758.5453);
+}
+
+float starn(vec2 p, float r, int n, float m) {
+ // From IQ: https://www.shadertoy.com/view/3tSGDy
+ // https://iquilezles.org/www/articles/distfunctions2d/distfunctions2d.htm
+
+ // Minor tweak to use SABS over abs to smooth inner corners
+ // SABS: https://www.shadertoy.com/view/Ws2SDK
+
+ // next 4 lines can be precomputed for a given shape
+ float an = 3.141593/float(n);
+ float en = 3.141593/m; // m is between 2 and n
+ vec2 acs = vec2(cos(an),sin(an));
+ vec2 ecs = vec2(cos(en),sin(en)); // ecs=vec2(0,1) for regular polygon,
+
+ float bn = mod(atan(p.x,p.y),2.0*an) - an;
+ p = length(p)*vec2(cos(bn),SABS(sin(bn), 0.15));
+ p -= r*acs;
+ p += ecs*clamp( -dot(p,ecs), 0.0, r*acs.y/ecs.y);
+ return length(p)*sign(p.x);
+}
+
+vec4 alphaBlend(vec4 back, vec4 front) {
+ vec3 xyz = mix(back.xyz*back.w, front.xyz, front.w);
+ float w = mix(back.w, 1.0, front.w);
+ return vec4(xyz, w);
+}
+
+void rot(inout vec2 p, float a) {
+ float c = cos(a);
+ float s = sin(a);
+ p = vec2(c*p.x + s*p.y, -s*p.x + c*p.y);
+}
+
+vec3 offset(float z) {
+ float a = z;
+ vec2 p = -0.075*(vec2(cos(a), sin(a*sqrt(2.0))) + vec2(cos(a*sqrt(0.75)), sin(a*sqrt(0.5))));
+ return vec3(p, z);
+}
+
+vec3 doffset(float z) {
+ float eps = 0.05;
+ return 0.5*(offset(z + eps) - offset(z - eps))/eps;
+}
+
+vec3 ddoffset(float z) {
+ float eps = 0.05;
+ return 0.5*(doffset(z + eps) - doffset(z - eps))/eps;
+}
+
+vec4 planeCol(vec3 ro, vec3 rd, float n, vec3 pp) {
+ const float s = 0.5;
+
+ vec2 p = pp.xy;
+ float z = pp.z;
+ vec2 dpy = dFdy(p);
+ float aa = length(dpy);
+
+ p -= (1.0+5.0*(pp.z - ro.z))*offset(z).xy;
+
+ p *= s;
+ float r = hash(vec3(floor(p+0.5), n));
+ p = fract(p+0.5)-0.5;
+ rot(p, ((TAU*r+n)*0.25));
+ float d = starn(p, 0.20, 3 + 2*int(3.0*r), 3.0);
+ d -= 0.06;
+ d/=s;
+
+ float ds = -d+0.03;
+ vec3 cols = hsv2rgb(vec3(337.0/360.0+0.1*sin(n*0.3), 0.8, 0.54+0.2*sin(n*0.3)));
+ float ts = 1.0 - smoothstep(-aa, 0.0, ds);
+ vec4 cs = vec4(cols, ts*0.93);
+
+ float db = abs(d) - (0.06);
+ db = abs(db) - 0.03;
+ db = abs(db) - 0.00;
+ db = max(db, -d+0.03);
+ vec3 colb = vec3(1.0, 0.7, 0.5);
+ float tb = exp(-(db)*30.0*(1.0 - 10.0*aa));
+ vec4 cb = vec4(1.5*colb, tb);
+
+ vec4 ct = alphaBlend(cs, cb);
+
+ return ct;
+}
+
+vec3 color(vec3 ww, vec3 uu, vec3 vv, vec3 ro, vec2 p) {
+ vec3 rd = normalize(p.x*uu + p.y*vv + (2.0-tanh(length(p)))*ww);
+
+ vec4 col = vec4(vec3(0.0), 1.0);
+
+ const float planeDist = 1.0;
+ const int furthest = 6;
+ const int fadeFrom = furthest-3;
+
+ float nz = floor(ro.z / planeDist);
+
+ for (int i = furthest; i >= 1; --i) {
+ float pz = planeDist*nz + planeDist*float(i);
+
+ float pd = (pz - ro.z)/rd.z;
+
+ if (pd > 0.0) {
+ vec3 pp = ro + rd*pd;
+
+ vec4 pcol = planeCol(ro, rd, nz+float(i), pp);
+ float fadeIn = 1.0-smoothstep(planeDist*float(fadeFrom), planeDist*float(furthest), pp.z-ro.z);
+ pcol.xyz *= sqrt(fadeIn);
+
+ col = alphaBlend(col, pcol);
+ }
+ }
+
+ return col.xyz*col.w;
+}
+
+vec3 postProcess(vec3 col, vec2 q) {
+ col=pow(clamp(col,0.0,1.0),vec3(0.75));
+ col=col*0.6+0.4*col*col*(3.0-2.0*col);
+ col=mix(col, vec3(dot(col, vec3(0.33))), -0.4);
+ col*=0.5+0.5*pow(19.0*q.x*q.y*(1.0-q.x)*(1.0-q.y),0.7);
+ return col;
+}
+
+vec3 effect(vec2 p, vec2 q) {
+ float tm = TIME*0.65;
+
+ vec3 ro = offset(tm);
+ vec3 dro = doffset(tm);
+ vec3 ddro = ddoffset(tm);
+
+ vec3 ww = normalize(dro);
+ vec3 uu = normalize(cross(vec3(0.0,1.0,0.0)+1.5*ddro, ww));
+ vec3 vv = normalize(cross(ww, uu));
+
+ vec3 col = color(ww, uu, vv, ro, p);
+ col = postProcess(col, q);
+
+ const float fadeIn = 2.0;
+
+ return col*smoothstep(0.0, fadeIn, TIME);
+}
+
+void mainImage(out vec4 fragColor, vec2 fragCoord) {
+ vec2 q = fragCoord/RESOLUTION.xy;
+ vec2 p = -1. + 2. * q;
+ p.x *= RESOLUTION.x/RESOLUTION.y;
+
+ vec3 col = effect(p, q);
+
+ fragColor = vec4(col, 1.0);
+}
diff --git a/demos/gtk-demo/gtkshadertoy.c b/demos/gtk-demo/gtkshadertoy.c
new file mode 100644
index 0000000000..866bd69481
--- /dev/null
+++ b/demos/gtk-demo/gtkshadertoy.c
@@ -0,0 +1,524 @@
+#include
+#include
+#include
+#include
+
+#include "gtkshadertoy.h"
+
+const char *default_image_shader =
+ "void mainImage(out vec4 fragColor, in vec2 fragCoord) {\n"
+ " // Normalized pixel coordinates (from 0 to 1)\n"
+ " vec2 uv = fragCoord/iResolution.xy;\n"
+ "\n"
+ " // Time varying pixel color\n"
+ " vec3 col = 0.5 + 0.5*cos(iTime+uv.xyx+vec3(0,2,4));\n"
+ "\n"
+ " if (distance(iMouse.xy, fragCoord.xy) <= 10.0) {\n"
+ " col = vec3(0.0);\n"
+ " }\n"
+ "\n"
+ " // Output to screen\n"
+ " fragColor = vec4(col,1.0);\n"
+ "}\n";
+
+const char *shadertoy_vertex_shader =
+ "#version 150 core\n"
+ "\n"
+ "uniform vec3 iResolution;\n"
+ "\n"
+ "in vec2 position;\n"
+ "out vec2 fragCoord;\n"
+ "\n"
+ "void main() {\n"
+ " gl_Position = vec4(position, 0.0, 1.0);\n"
+ "\n"
+ " // Convert from OpenGL coordinate system (with origin in center\n"
+ " // of screen) to Shadertoy/texture coordinate system (with origin\n"
+ " // in lower left corner)\n"
+ " fragCoord = (gl_Position.xy + vec2(1.0)) / vec2(2.0) * iResolution.xy;\n"
+ "}\n";
+
+const char *fragment_prefix =
+ "#version 150 core\n"
+ "\n"
+ "uniform vec3 iResolution; // viewport resolution (in pixels)\n"
+ "uniform float iTime; // shader playback time (in seconds)\n"
+ "uniform float iTimeDelta; // render time (in seconds)\n"
+ "uniform int iFrame; // shader playback frame\n"
+ "uniform float iChannelTime[4]; // channel playback time (in seconds)\n"
+ "uniform vec3 iChannelResolution[4]; // channel resolution (in pixels)\n"
+ "uniform vec4 iMouse; // mouse pixel coords. xy: current (if MLB down), zw: click\n"
+ "uniform sampler2D iChannel0;\n"
+ "uniform sampler2D iChannel1;\n"
+ "uniform sampler2D iChannel2;\n"
+ "uniform sampler2D iChannel3;\n"
+ "uniform vec4 iDate; // (year, month, day, time in seconds)\n"
+ "uniform float iSampleRate; // sound sample rate (i.e., 44100)\n"
+ "\n"
+ "in vec2 fragCoord;\n"
+ "out vec4 fragColor;\n";
+
+
+// Fragment shader suffix
+const char *fragment_suffix =
+ " void main() {\n"
+ " mainImage(fragColor, fragCoord);\n"
+ " }\n";
+
+typedef struct {
+ char *image_shader;
+ gboolean image_shader_dirty;
+
+ gboolean error_set;
+
+ /* Vertex buffers */
+ GLuint vao;
+ GLuint buffer;
+
+ /* Active program */
+ GLuint program;
+
+ /* Location of uniforms for program */
+ GLuint resolution_location;
+ GLuint time_location;
+ GLuint timedelta_location;
+ GLuint frame_location;
+ GLuint mouse_location;
+
+ /* Current uniform values */
+ float resolution[3];
+ float time;
+ float timedelta;
+ float mouse[4];
+ int frame;
+
+ /* Animation data */
+ gint64 first_frame_time;
+ gint64 first_frame;
+ guint tick;
+} GtkShadertoyPrivate;
+
+G_DEFINE_TYPE_WITH_PRIVATE (GtkShadertoy, gtk_shadertoy, GTK_TYPE_GL_AREA)
+
+static gboolean gtk_shadertoy_render (GtkGLArea *area,
+ GdkGLContext *context);
+static void gtk_shadertoy_reshape (GtkGLArea *area,
+ int width,
+ int height);
+static void gtk_shadertoy_realize (GtkWidget *widget);
+static void gtk_shadertoy_unrealize (GtkWidget *widget);
+static gboolean gtk_shadertoy_tick (GtkWidget *widget,
+ GdkFrameClock *frame_clock,
+ gpointer user_data);
+
+GtkWidget *
+gtk_shadertoy_new (void)
+{
+ return g_object_new (gtk_shadertoy_get_type (), NULL);
+}
+
+static void
+drag_begin_cb (GtkGestureDrag *drag,
+ double x,
+ double y,
+ gpointer user_data)
+{
+ GtkShadertoy *shadertoy = GTK_SHADERTOY (user_data);
+ GtkShadertoyPrivate *priv = gtk_shadertoy_get_instance_private (shadertoy);
+ int height = gtk_widget_get_height (GTK_WIDGET (shadertoy));
+ int scale = gtk_widget_get_scale_factor (GTK_WIDGET (shadertoy));
+
+ priv->mouse[0] = x * scale;
+ priv->mouse[1] = (height - y) * scale;
+ priv->mouse[2] = priv->mouse[0];
+ priv->mouse[3] = priv->mouse[1];
+}
+
+static void
+drag_update_cb (GtkGestureDrag *drag,
+ double dx,
+ double dy,
+ gpointer user_data)
+{
+ GtkShadertoy *shadertoy = GTK_SHADERTOY (user_data);
+ GtkShadertoyPrivate *priv = gtk_shadertoy_get_instance_private (shadertoy);
+ int width = gtk_widget_get_width (GTK_WIDGET (shadertoy));
+ int height = gtk_widget_get_height (GTK_WIDGET (shadertoy));
+ int scale = gtk_widget_get_scale_factor (GTK_WIDGET (shadertoy));
+ double x, y;
+
+ gtk_gesture_drag_get_start_point (drag, &x, &y);
+ x += dx;
+ y += dy;
+
+ if (x >= 0 && x < width &&
+ y >= 0 && y < height)
+ {
+ priv->mouse[0] = x * scale;
+ priv->mouse[1] = (height - y) * scale;
+ }
+}
+
+static void
+drag_end_cb (GtkGestureDrag *drag,
+ gdouble dx,
+ gdouble dy,
+ gpointer user_data)
+{
+ GtkShadertoy *shadertoy = GTK_SHADERTOY (user_data);
+ GtkShadertoyPrivate *priv = gtk_shadertoy_get_instance_private (shadertoy);
+
+ priv->mouse[2] = -priv->mouse[2];
+ priv->mouse[3] = -priv->mouse[3];
+}
+
+static void
+gtk_shadertoy_init (GtkShadertoy *shadertoy)
+{
+ GtkShadertoyPrivate *priv = gtk_shadertoy_get_instance_private (shadertoy);
+ GtkGesture *drag;
+
+ priv->image_shader = g_strdup (default_image_shader);
+ priv->tick = gtk_widget_add_tick_callback (GTK_WIDGET (shadertoy), gtk_shadertoy_tick, shadertoy, NULL);
+
+ drag = gtk_gesture_drag_new ();
+ gtk_widget_add_controller (GTK_WIDGET (shadertoy), GTK_EVENT_CONTROLLER (drag));
+ g_signal_connect (drag, "drag-begin", (GCallback)drag_begin_cb, shadertoy);
+ g_signal_connect (drag, "drag-update", (GCallback)drag_update_cb, shadertoy);
+ g_signal_connect (drag, "drag-end", (GCallback)drag_end_cb, shadertoy);
+}
+
+static void
+gtk_shadertoy_finalize (GObject *obj)
+{
+ GtkShadertoy *shadertoy = GTK_SHADERTOY (obj);
+ GtkShadertoyPrivate *priv = gtk_shadertoy_get_instance_private (shadertoy);
+
+ gtk_widget_remove_tick_callback (GTK_WIDGET (shadertoy), priv->tick);
+ g_free (priv->image_shader);
+
+ G_OBJECT_CLASS (gtk_shadertoy_parent_class)->finalize (obj);
+}
+
+static void
+gtk_shadertoy_class_init (GtkShadertoyClass *klass)
+{
+ GTK_GL_AREA_CLASS (klass)->render = gtk_shadertoy_render;
+ GTK_GL_AREA_CLASS (klass)->resize = gtk_shadertoy_reshape;
+
+ GTK_WIDGET_CLASS (klass)->realize = gtk_shadertoy_realize;
+ GTK_WIDGET_CLASS (klass)->unrealize = gtk_shadertoy_unrealize;
+
+ G_OBJECT_CLASS (klass)->finalize = gtk_shadertoy_finalize;
+}
+
+/* new window size or exposure */
+static void
+gtk_shadertoy_reshape (GtkGLArea *area, int width, int height)
+{
+ GtkShadertoyPrivate *priv = gtk_shadertoy_get_instance_private ((GtkShadertoy *) area);
+
+ priv->resolution[0] = width;
+ priv->resolution[1] = height;
+ priv->resolution[2] = 1.0; /* screen aspect ratio */
+
+ /* Set the viewport */
+ glViewport (0, 0, (GLint) width, (GLint) height);
+}
+
+static GLuint
+create_shader (int type,
+ const char *src,
+ GError **error)
+{
+ GLuint shader;
+ int status;
+
+ shader = glCreateShader (type);
+ glShaderSource (shader, 1, &src, NULL);
+ glCompileShader (shader);
+
+ glGetShaderiv (shader, GL_COMPILE_STATUS, &status);
+ if (status == GL_FALSE)
+ {
+ int log_len;
+ char *buffer;
+
+ glGetShaderiv (shader, GL_INFO_LOG_LENGTH, &log_len);
+
+ buffer = g_malloc (log_len + 1);
+ glGetShaderInfoLog (shader, log_len, NULL, buffer);
+
+ g_set_error (error, GDK_GL_ERROR, GDK_GL_ERROR_COMPILATION_FAILED,
+ "Compile failure in %s shader:\n%s",
+ type == GL_VERTEX_SHADER ? "vertex" : "fragment",
+ buffer);
+
+ g_free (buffer);
+
+ glDeleteShader (shader);
+
+ return 0;
+ }
+
+ return shader;
+}
+
+static gboolean
+init_shaders (GtkShadertoy *shadertoy,
+ const char *vertex_source,
+ const char *fragment_source,
+ GError **error)
+{
+ GtkShadertoyPrivate *priv = gtk_shadertoy_get_instance_private (shadertoy);
+ GLuint vertex, fragment;
+ GLuint program = 0;
+ int status;
+ gboolean res = TRUE;
+
+ vertex = create_shader (GL_VERTEX_SHADER, vertex_source, error);
+ if (vertex == 0)
+ return FALSE;
+
+ fragment = create_shader (GL_FRAGMENT_SHADER, fragment_source, error);
+ if (fragment == 0)
+ {
+ glDeleteShader (vertex);
+ return FALSE;
+ }
+
+ program = glCreateProgram ();
+ glAttachShader (program, vertex);
+ glAttachShader (program, fragment);
+
+ glLinkProgram (program);
+
+ glGetProgramiv (program, GL_LINK_STATUS, &status);
+ if (status == GL_FALSE)
+ {
+ int log_len;
+ char *buffer;
+
+ glGetProgramiv (program, GL_INFO_LOG_LENGTH, &log_len);
+
+ buffer = g_malloc (log_len + 1);
+ glGetProgramInfoLog (program, log_len, NULL, buffer);
+
+ g_set_error (error, GDK_GL_ERROR, GDK_GL_ERROR_LINK_FAILED,
+ "Linking failure:\n%s", buffer);
+ res = FALSE;
+
+ g_free (buffer);
+
+ glDeleteProgram (program);
+
+ goto out;
+ }
+
+ if (priv->program != 0)
+ glDeleteProgram (priv->program);
+
+ priv->program = program;
+ priv->resolution_location = glGetUniformLocation (program, "iResolution");
+ priv->time_location = glGetUniformLocation (program, "iTime");
+ priv->timedelta_location = glGetUniformLocation (program, "iTimeDelta");
+ priv->frame_location = glGetUniformLocation (program, "iFrame");
+ priv->mouse_location = glGetUniformLocation (program, "iMouse");
+
+ glDetachShader (program, vertex);
+ glDetachShader (program, fragment);
+
+out:
+ /* These are now owned by the program and can be deleted */
+ glDeleteShader (vertex);
+ glDeleteShader (fragment);
+
+ return res;
+}
+
+static void
+gtk_shadertoy_realize_shader (GtkShadertoy *shadertoy)
+{
+ GtkShadertoyPrivate *priv = gtk_shadertoy_get_instance_private (shadertoy);
+ char *fragment_shader;
+ GError *error = NULL;
+
+ fragment_shader = g_strconcat (fragment_prefix, priv->image_shader, fragment_suffix, NULL);
+ if (!init_shaders (shadertoy, shadertoy_vertex_shader, fragment_shader, &error))
+ {
+ priv->error_set = TRUE;
+ gtk_gl_area_set_error (GTK_GL_AREA (shadertoy), error);
+ g_error_free (error);
+ }
+ g_free (fragment_shader);
+
+ /* Start new shader at time zero */
+ priv->first_frame_time = 0;
+ priv->first_frame = 0;
+
+ priv->image_shader_dirty = FALSE;
+}
+
+static gboolean
+gtk_shadertoy_render (GtkGLArea *area,
+ GdkGLContext *context)
+{
+ GtkShadertoy *shadertoy = GTK_SHADERTOY (area);
+ GtkShadertoyPrivate *priv = gtk_shadertoy_get_instance_private (shadertoy);
+
+ if (gtk_gl_area_get_error (area) != NULL)
+ return FALSE;
+
+ if (priv->image_shader_dirty)
+ gtk_shadertoy_realize_shader (shadertoy);
+
+ /* Clear the viewport */
+ glClearColor (0.0, 0.0, 0.0, 1.0);
+ glClear (GL_COLOR_BUFFER_BIT);
+
+ glUseProgram (priv->program);
+
+ /* Update uniforms */
+ if (priv->resolution_location != -1)
+ glUniform3fv (priv->resolution_location, 1, priv->resolution);
+ if (priv->time_location != -1)
+ glUniform1f (priv->time_location, priv->time);
+ if (priv->timedelta_location != -1)
+ glUniform1f (priv->timedelta_location, priv->timedelta);
+ if (priv->frame_location != -1)
+ glUniform1i (priv->frame_location, priv->frame);
+ if (priv->mouse_location != -1)
+ glUniform4fv (priv->mouse_location, 1, priv->mouse);
+
+ /* Use the vertices in our buffer */
+ glBindBuffer (GL_ARRAY_BUFFER, priv->buffer);
+ glEnableVertexAttribArray (0);
+ glVertexAttribPointer (0, 4, GL_FLOAT, GL_FALSE, 0, 0);
+
+ glDrawArrays (GL_TRIANGLES, 0, 6);
+
+ /* We finished using the buffers and program */
+ glDisableVertexAttribArray (0);
+ glBindBuffer (GL_ARRAY_BUFFER, 0);
+ glUseProgram (0);
+
+ /* Flush the contents of the pipeline */
+ glFlush ();
+
+ return TRUE;
+}
+
+const char *
+gtk_shadertoy_get_image_shader (GtkShadertoy *shadertoy)
+{
+ GtkShadertoyPrivate *priv = gtk_shadertoy_get_instance_private (shadertoy);
+
+ return priv->image_shader;
+}
+
+void
+gtk_shadertoy_set_image_shader (GtkShadertoy *shadertoy,
+ const char *shader)
+{
+ GtkShadertoyPrivate *priv = gtk_shadertoy_get_instance_private (shadertoy);
+
+ g_free (priv->image_shader);
+ priv->image_shader = g_strdup (shader);
+
+ /* Don't override error we didn't set it ourselves */
+ if (priv->error_set)
+ {
+ gtk_gl_area_set_error (GTK_GL_AREA (shadertoy), NULL);
+ priv->error_set = FALSE;
+ }
+ priv->image_shader_dirty = TRUE;
+}
+
+static void
+gtk_shadertoy_realize (GtkWidget *widget)
+{
+ GtkGLArea *glarea = GTK_GL_AREA (widget);
+ GtkShadertoy *shadertoy = GTK_SHADERTOY (widget);
+ GtkShadertoyPrivate *priv = gtk_shadertoy_get_instance_private (shadertoy);
+
+ /* Draw two triangles across whole screen */
+ const GLfloat vertex_data[] = {
+ -1.0f, -1.0f, 0.f, 1.f,
+ -1.0f, 1.0f, 0.f, 1.f,
+ 1.0f, 1.0f, 0.f, 1.f,
+
+ -1.0f, -1.0f, 0.f, 1.f,
+ 1.0f, 1.0f, 0.f, 1.f,
+ 1.0f, -1.0f, 0.f, 1.f,
+ };
+
+ GTK_WIDGET_CLASS (gtk_shadertoy_parent_class)->realize (widget);
+
+ gtk_gl_area_make_current (glarea);
+ if (gtk_gl_area_get_error (glarea) != NULL)
+ return;
+
+ glGenVertexArrays (1, &priv->vao);
+ glBindVertexArray (priv->vao);
+
+ glGenBuffers (1, &priv->buffer);
+ glBindBuffer (GL_ARRAY_BUFFER, priv->buffer);
+ glBufferData (GL_ARRAY_BUFFER, sizeof (vertex_data), vertex_data, GL_STATIC_DRAW);
+ glBindBuffer (GL_ARRAY_BUFFER, 0);
+
+ gtk_shadertoy_realize_shader (shadertoy);
+}
+
+static void
+gtk_shadertoy_unrealize (GtkWidget *widget)
+{
+ GtkGLArea *glarea = GTK_GL_AREA (widget);
+ GtkShadertoyPrivate *priv = gtk_shadertoy_get_instance_private ((GtkShadertoy *) widget);
+
+ gtk_gl_area_make_current (glarea);
+ if (gtk_gl_area_get_error (glarea) == NULL)
+ {
+ if (priv->buffer != 0)
+ glDeleteBuffers (1, &priv->buffer);
+
+ if (priv->vao != 0)
+ glDeleteVertexArrays (1, &priv->vao);
+
+ if (priv->program != 0)
+ glDeleteProgram (priv->program);
+ }
+
+ GTK_WIDGET_CLASS (gtk_shadertoy_parent_class)->unrealize (widget);
+}
+
+static gboolean
+gtk_shadertoy_tick (GtkWidget *widget,
+ GdkFrameClock *frame_clock,
+ gpointer user_data)
+{
+ GtkShadertoy *shadertoy = GTK_SHADERTOY (widget);
+ GtkShadertoyPrivate *priv = gtk_shadertoy_get_instance_private (shadertoy);
+ gint64 frame_time;
+ gint64 frame;
+ float previous_time;
+
+ frame = gdk_frame_clock_get_frame_counter (frame_clock);
+ frame_time = gdk_frame_clock_get_frame_time (frame_clock);
+
+ if (priv->first_frame_time == 0)
+ {
+ priv->first_frame_time = frame_time;
+ priv->first_frame = frame;
+ previous_time = 0;
+ }
+ else
+ previous_time = priv->time;
+
+ priv->time = (frame_time - priv->first_frame_time) / 1000000.0f;
+ priv->frame = frame - priv->first_frame;
+ priv->timedelta = priv->time - previous_time;
+
+ gtk_widget_queue_draw (widget);
+
+ return G_SOURCE_CONTINUE;
+}
diff --git a/demos/gtk-demo/gtkshadertoy.h b/demos/gtk-demo/gtkshadertoy.h
new file mode 100644
index 0000000000..38f48995b8
--- /dev/null
+++ b/demos/gtk-demo/gtkshadertoy.h
@@ -0,0 +1,34 @@
+#ifndef __GTK_SHADERTOY_H__
+#define __GTK_SHADERTOY_H__
+
+#include
+
+G_BEGIN_DECLS
+
+#define GTK_TYPE_SHADERTOY (gtk_shadertoy_get_type ())
+#define GTK_SHADERTOY(inst) (G_TYPE_CHECK_INSTANCE_CAST ((inst), \
+ GTK_TYPE_SHADERTOY, \
+ GtkShadertoy))
+#define GTK_IS_SHADERTOY(inst) (G_TYPE_CHECK_INSTANCE_TYPE ((inst), \
+ GTK_TYPE_SHADERTOY))
+
+typedef struct _GtkShadertoy GtkShadertoy;
+typedef struct _GtkShadertoyClass GtkShadertoyClass;
+
+struct _GtkShadertoy {
+ GtkGLArea parent;
+};
+
+struct _GtkShadertoyClass {
+ GtkGLAreaClass parent_class;
+};
+
+GType gtk_shadertoy_get_type (void) G_GNUC_CONST;
+GtkWidget *gtk_shadertoy_new (void);
+const char *gtk_shadertoy_get_image_shader (GtkShadertoy *shadertoy);
+void gtk_shadertoy_set_image_shader (GtkShadertoy *shadertoy,
+ const char *shader);
+
+G_END_DECLS
+
+#endif /* __GTK_SHADERTOY_H__ */
diff --git a/demos/gtk-demo/mandelbrot.glsl b/demos/gtk-demo/mandelbrot.glsl
new file mode 100644
index 0000000000..0a0c1ee3fd
--- /dev/null
+++ b/demos/gtk-demo/mandelbrot.glsl
@@ -0,0 +1,95 @@
+// Originally from: https://www.shadertoy.com/view/wdBfDK
+// License: CC0
+
+#define MANDELBROT_ZOOM_START 0.0
+#define MANDELBROT_ITER 240
+
+void pR(inout vec2 p, in float a) {
+ p = cos(a)*p + sin(a)*vec2(p.y, -p.x);
+}
+
+vec2 pMod2(inout vec2 p, in vec2 size) {
+ vec2 c = floor((p + size*0.5)/size);
+ p = mod(p + size*0.5,size) - size*0.5;
+ return c;
+}
+
+
+vec3 mandelbrot(float time, vec2 p, out float ii) {
+ vec3 col = vec3(0.0);
+
+ float ztime = (time - MANDELBROT_ZOOM_START)*step(MANDELBROT_ZOOM_START, time);
+
+ float zoo = 0.64 + 0.36*cos(.07*ztime);
+ float coa = cos(0.15*(1.0-zoo)*ztime);
+ float sia = sin(0.15*(1.0-zoo)*ztime);
+ zoo = pow(zoo,8.0);
+ vec2 xy = vec2( p.x*coa-p.y*sia, p.x*sia+p.y*coa);
+ vec2 c = vec2(-.745,.186) + xy*zoo;
+
+ const float B = 10.0;
+ float l = 0.0;
+ vec2 z = vec2(0.0);
+
+ vec2 zc = vec2(1.0);
+
+ pR(zc, ztime);
+
+ float d = 1e20;
+
+ int i = 0;
+
+ for(int j = 0; j < MANDELBROT_ITER; ++j) {
+ float re2 = z.x*z.x;
+ float im2 = z.y*z.y;
+ float reim= z.x*z.y;
+
+ if(re2 + im2 > (B*B)) break;
+
+ z = vec2(re2 - im2, 2.0*reim) + c;
+
+ vec2 zm = z;
+ vec2 n = pMod2(zm, vec2(4));
+ vec2 pp = zm - zc;
+ float dd = dot(pp, pp);
+
+ d = min(d, dd);
+
+ l += 1.0;
+
+ i = j;
+ }
+
+ ii = float(i)/float(MANDELBROT_ITER);
+
+ float sl = l - log2(log2(dot(z,z))) + 4.0;
+
+ vec3 dc = vec3(pow(max(1.0 - d, 0.0), 20.0));
+ vec3 gc = 0.5 + 0.5*cos(3.0 + sl*0.15 + vec3(0.1,0.5,0.9));
+ return gc + dc*smoothstep(28.8, 29.0, ztime);
+}
+
+void mainImage(out vec4 fragColor, in vec2 fragCoord) {
+ float s = 2.0/iResolution.y;
+
+ vec2 o1 = vec2(1.0/8.0, 3.0/8.0)*s;
+ vec2 o2 = vec2(-3.0/8.0, 1.0/8.0)*s;
+
+ vec2 p = (-iResolution.xy + 2.0*fragCoord.xy)/iResolution.y;
+ float ii = 0.0;
+ vec3 col = mandelbrot(iTime, p+o1, ii);
+
+ // "smart" AA? Is that a good idea?
+ vec2 dii2 = vec2(dFdx(ii), dFdy(ii));
+ float dii = length(dii2);
+
+ if(abs(dii) > 0.01) {
+ col += mandelbrot(iTime, p-o1, ii);
+ col += mandelbrot(iTime, p+o2, ii);
+ col += mandelbrot(iTime, p-o2, ii);
+ col *=0.25;
+// col = vec3(1.0, 0.0, 0.0);
+ }
+
+ fragColor = vec4(col, 1.0);
+}
diff --git a/demos/gtk-demo/meson.build b/demos/gtk-demo/meson.build
index 05b32c6f46..9ee9777d13 100644
--- a/demos/gtk-demo/meson.build
+++ b/demos/gtk-demo/meson.build
@@ -70,6 +70,7 @@ demos = files([
'scale.c',
'search_entry.c',
'search_entry2.c',
+ 'shadertoy.c',
'shortcuts.c',
'shortcut_triggers.c',
'sidebar.c',
@@ -98,6 +99,7 @@ extra_demo_sources = files(['main.c',
'gtkfishbowl.c',
'fontplane.c',
'gtkgears.c',
+ 'gtkshadertoy.c',
'puzzlepiece.c',
'bluroverlay.c',
'demoimage.c',
diff --git a/demos/gtk-demo/neon.glsl b/demos/gtk-demo/neon.glsl
new file mode 100644
index 0000000000..b42790efe9
--- /dev/null
+++ b/demos/gtk-demo/neon.glsl
@@ -0,0 +1,220 @@
+// Originally from: https://www.shadertoy.com/view/WlByzy
+// License CC0: Neonwave style road, sun and city
+// The result of a bit of experimenting with neonwave style colors.
+
+#define PI 3.141592654
+#define TAU (2.0*PI)
+
+#define TIME iTime
+#define RESOLUTION iResolution
+
+vec3 hsv2rgb(vec3 c) {
+ const vec4 K = vec4(1.0, 2.0 / 3.0, 1.0 / 3.0, 3.0);
+ vec3 p = abs(fract(c.xxx + K.xyz) * 6.0 - K.www);
+ return c.z * mix(K.xxx, clamp(p - K.xxx, 0.0, 1.0), c.y);
+}
+
+float hash(in float co) {
+ return fract(sin(co*12.9898) * 13758.5453);
+}
+
+float hash(in vec2 co) {
+ return fract(sin(dot(co.xy ,vec2(12.9898,58.233))) * 13758.5453);
+}
+
+float psin(float a) {
+ return 0.5 + 0.5*sin(a);
+}
+
+float mod1(inout float p, float size) {
+ float halfsize = size*0.5;
+ float c = floor((p + halfsize)/size);
+ p = mod(p + halfsize, size) - halfsize;
+ return c;
+}
+
+float circle(vec2 p, float r) {
+ return length(p) - r;
+}
+
+float box(vec2 p, vec2 b) {
+ vec2 d = abs(p)-b;
+ return length(max(d,0.0)) + min(max(d.x,d.y),0.0);
+}
+
+float planex(vec2 p, float w) {
+ return abs(p.y) - w;
+}
+
+float planey(vec2 p, float w) {
+ return abs(p.x) - w;
+}
+
+float pmin(float a, float b, float k) {
+ float h = clamp( 0.5+0.5*(b-a)/k, 0.0, 1.0 );
+ return mix( b, a, h ) - k*h*(1.0-h);
+}
+
+float pmax(float a, float b, float k) {
+ return -pmin(-a, -b, k);
+}
+
+float sun(vec2 p) {
+ const float ch = 0.0125;
+ vec2 sp = p;
+ vec2 cp = p;
+ mod1(cp.y, ch*6.0);
+
+ float d0 = circle(sp, 0.5);
+ float d1 = planex(cp, ch);
+ float d2 = p.y+ch*3.0;
+
+ float d = d0;
+ d = pmax(d, -max(d1, d2), ch*2.0);
+
+ return d;
+}
+
+float city(vec2 p) {
+ float sd = circle(p, 0.5);
+ float cd = 1E6;
+
+ const float count = 5.0;
+ const float width = 0.1;
+
+ for (float i = 0.0; i < count; ++i) {
+ vec2 pp = p;
+ pp.x += i*width/count;
+ float nn = mod1(pp.x, width);
+ float rr = hash(nn+sqrt(3.0)*i);
+ float dd = box(pp-vec2(0.0, -0.5), vec2(0.02, 0.35*(1.0-smoothstep(0.0, 5.0, abs(nn)))*rr+0.1));
+ cd = min(cd, dd);
+ }
+
+ return max(sd,cd);
+}
+vec3 sunEffect(vec2 p) {
+ float aa = 4.0 / RESOLUTION.y;
+
+ vec3 col = vec3(0.1);
+ vec3 skyCol1 = hsv2rgb(vec3(283.0/360.0, 0.83, 0.16));
+ vec3 skyCol2 = hsv2rgb(vec3(297.0/360.0, 0.79, 0.43));
+ col = mix(skyCol1, skyCol2, pow(clamp(0.5*(1.0+p.y+0.1*sin(4.0*p.x+TIME*0.5)), 0.0, 1.0), 4.0));
+
+ p.y -= 0.375;
+ float ds = sun(p);
+ float dc = city(p);
+
+ float dd = circle(p, 0.5);
+
+ vec3 sunCol = mix(vec3(1.0, 1.0, 0.0), vec3(1.0, 0.0, 1.0), clamp(0.5 - 1.0*p.y, 0.0, 1.0));
+ vec3 glareCol = sqrt(sunCol);
+ vec3 cityCol = sunCol*sunCol;
+
+ col += glareCol*(exp(-30.0*ds))*step(0.0, ds);
+
+
+ float t1 = smoothstep(0.0, 0.075, -dd);
+ float t2 = smoothstep(0.0, 0.3, -dd);
+ col = mix(col, sunCol, smoothstep(-aa, 0.0, -ds));
+ col = mix(col, glareCol, smoothstep(-aa, 0.0, -dc)*t1);
+ col += vec3(0.0, 0.25, 0.0)*(exp(-90.0*dc))*step(0.0, dc)*t2;
+
+// col += 0.3*psin(d*400);
+
+ return col;
+}
+
+float ground(vec2 p) {
+ p.y += TIME*80.0;
+ p *= 0.075;
+ vec2 gp = p;
+ gp = fract(gp) - vec2(0.5);
+ float d0 = abs(gp.x);
+ float d1 = abs(gp.y);
+ float d2 = circle(gp, 0.05);
+
+ const float rw = 2.5;
+ const float sw = 0.0125;
+
+ vec2 rp = p;
+ mod1(rp.y, 12.0);
+ float d3 = abs(rp.x) - rw;
+ float d4 = abs(d3) - sw*2.0;
+ float d5 = box(rp, vec2(sw*2.0, 2.0));
+ vec2 sp = p;
+ mod1(sp.y, 4.0);
+ sp.x = abs(sp.x);
+ sp -= vec2(rw - 0.125, 0.0);
+ float d6 = box(sp, vec2(sw, 1.0));
+
+ float d = d0;
+ d = pmin(d, d1, 0.1);
+ d = max(d, -d3);
+ d = min(d, d4);
+ d = min(d, d5);
+ d = min(d, d6);
+
+ return d;
+}
+
+vec3 groundEffect(vec2 p) {
+ vec3 ro = vec3(0.0, 20.0, 0.0);
+ vec3 ww = normalize(vec3(0.0, -0.025, 1.0));
+ vec3 uu = normalize(cross(vec3(0.0,1.0,0.0), ww));
+ vec3 vv = normalize(cross(ww,uu));
+ vec3 rd = normalize(p.x*uu + p.y*vv + 2.5*ww);
+
+ float distg = (-9.0 - ro.y)/rd.y;
+
+ const vec3 shineCol = 0.75*vec3(0.5, 0.75, 1.0);
+ const vec3 gridCol = vec3(1.0);
+
+ vec3 col = vec3(0.0);
+ if (distg > 0.0) {
+ vec3 pg = ro + rd*distg;
+ float aa = length(dFdx(pg))*0.0002*RESOLUTION.x;
+
+ float dg = ground(pg.xz);
+
+ col = mix(col, gridCol, smoothstep(-aa, 0.0, -(dg+0.0175)));
+ col += shineCol*(exp(-10.0*clamp(dg, 0.0, 1.0)));
+ col = clamp(col, 0.0, 1.0);
+
+// col += 0.3*psin(dg*100);
+ col *= pow(1.0-smoothstep(ro.y*3.0, 220.0+ro.y*2.0, distg), 2.0);
+ }
+
+ return col;
+}
+
+vec3 postProcess(vec3 col, vec2 q) {
+ col = clamp(col,0.0,1.0);
+// col=pow(col,vec3(0.75));
+ col=col*0.6+0.4*col*col*(3.0-2.0*col);
+ col=mix(col, vec3(dot(col, vec3(0.33))), -0.4);
+ col*=0.5+0.5*pow(19.0*q.x*q.y*(1.0-q.x)*(1.0-q.y),0.7);
+ return col;
+}
+
+vec3 effect(vec2 p, vec2 q) {
+ vec3 col = vec3(0.0);
+
+ vec2 off = vec2(0.0, 0.0);
+
+ col += sunEffect(p+off);
+ col += groundEffect(p+off);
+
+ col = postProcess(col, q);
+ return col;
+}
+
+void mainImage(out vec4 fragColor, vec2 fragCoord) {
+ vec2 q = fragCoord/iResolution.xy;
+ vec2 p = -1. + 2. * q;
+ p.x *= RESOLUTION.x / RESOLUTION.y;
+
+ vec3 col = effect(p, q);
+
+ fragColor = vec4(col, 1.0);
+}
diff --git a/demos/gtk-demo/shadertoy.c b/demos/gtk-demo/shadertoy.c
new file mode 100644
index 0000000000..f6cad85ad3
--- /dev/null
+++ b/demos/gtk-demo/shadertoy.c
@@ -0,0 +1,191 @@
+/* OpenGL/Shadertoy
+ *
+ * Generate pixels using a custom fragment shader.
+ *
+ * The names of the uniforms are compatible with the shaders on shadertoy.com, so
+ * many of the shaders there work here too.
+ */
+#include
+#include
+#include
+#include "gtkshadertoy.h"
+
+static GtkWidget *demo_window = NULL;
+static GtkWidget *shadertoy = NULL;
+static GtkTextBuffer *textbuffer = NULL;
+
+static void
+run (void)
+{
+ GtkTextIter start, end;
+ char *text;
+
+ gtk_text_buffer_get_bounds (textbuffer, &start, &end);
+ text = gtk_text_buffer_get_text (textbuffer, &start, &end, FALSE);
+
+ gtk_shadertoy_set_image_shader (GTK_SHADERTOY (shadertoy), text);
+ g_free (text);
+}
+
+static void
+run_clicked_cb (GtkWidget *button,
+ gpointer user_data)
+{
+ run ();
+}
+
+static void
+load_clicked_cb (GtkWidget *button,
+ gpointer user_data)
+{
+ const char *path = user_data;
+ GBytes *initial_shader;
+
+ initial_shader = g_resources_lookup_data (path, 0, NULL);
+ gtk_text_buffer_set_text (textbuffer, g_bytes_get_data (initial_shader, NULL), -1);
+ g_bytes_unref (initial_shader);
+
+ run ();
+}
+
+static void
+clear_clicked_cb (GtkWidget *button,
+ gpointer user_data)
+{
+ gtk_text_buffer_set_text (textbuffer, "", 0);
+}
+
+static void
+close_window (GtkWidget *widget)
+{
+ /* Reset the state */
+ demo_window = NULL;
+ shadertoy = NULL;
+ textbuffer = NULL;
+}
+
+static GtkWidget *
+new_shadertoy (const char *path)
+{
+ GBytes *shader;
+ GtkWidget *toy;
+
+ toy = gtk_shadertoy_new ();
+ shader = g_resources_lookup_data (path, 0, NULL);
+ gtk_shadertoy_set_image_shader (GTK_SHADERTOY (toy),
+ g_bytes_get_data (shader, NULL));
+ g_bytes_unref (shader);
+
+ return toy;
+}
+
+static GtkWidget *
+new_button (const char *path)
+{
+ GtkWidget *button, *toy;
+
+ button = gtk_button_new ();
+ g_signal_connect (button, "clicked", G_CALLBACK (load_clicked_cb), (char *)path);
+
+ toy = new_shadertoy (path);
+ gtk_widget_set_size_request (toy, 64, 36);
+ gtk_button_set_child (GTK_BUTTON (button), toy);
+
+ return button;
+}
+
+static GtkWidget *
+create_shadertoy_window (GtkWidget *do_widget)
+{
+ GtkWidget *window, *box, *hbox, *button, *textview, *sw, *aspect, *centerbox;
+
+ window = gtk_window_new ();
+ gtk_window_set_display (GTK_WINDOW (window), gtk_widget_get_display (do_widget));
+ gtk_window_set_title (GTK_WINDOW (window), "Shadertoy");
+ gtk_window_set_default_size (GTK_WINDOW (window), 640, 600);
+ g_signal_connect (window, "destroy", G_CALLBACK (close_window), NULL);
+
+ box = gtk_box_new (GTK_ORIENTATION_VERTICAL, FALSE);
+ gtk_widget_set_margin_start (box, 12);
+ gtk_widget_set_margin_end (box, 12);
+ gtk_widget_set_margin_top (box, 12);
+ gtk_widget_set_margin_bottom (box, 12);
+ gtk_box_set_spacing (GTK_BOX (box), 6);
+ gtk_window_set_child (GTK_WINDOW (window), box);
+
+ aspect = gtk_aspect_frame_new (0.5, 0.5, 1.77777, FALSE);
+ gtk_widget_set_hexpand (aspect, TRUE);
+ gtk_widget_set_vexpand (aspect, TRUE);
+ gtk_box_append (GTK_BOX (box), aspect);
+
+ shadertoy = new_shadertoy ("/shadertoy/alienplanet.glsl");
+ gtk_aspect_frame_set_child (GTK_ASPECT_FRAME (aspect), shadertoy);
+
+ sw = gtk_scrolled_window_new ();
+ gtk_scrolled_window_set_min_content_height (GTK_SCROLLED_WINDOW (sw), 250);
+ gtk_scrolled_window_set_has_frame (GTK_SCROLLED_WINDOW (sw), TRUE);
+ gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (sw),
+ GTK_POLICY_AUTOMATIC,
+ GTK_POLICY_AUTOMATIC);
+ gtk_widget_set_hexpand (sw, TRUE);
+ gtk_box_append (GTK_BOX (box), sw);
+
+ textview = gtk_text_view_new ();
+ gtk_text_view_set_monospace (GTK_TEXT_VIEW (textview), TRUE);
+ gtk_scrolled_window_set_child (GTK_SCROLLED_WINDOW (sw), textview);
+
+ textbuffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (textview));
+ gtk_text_buffer_set_text (textbuffer,
+ gtk_shadertoy_get_image_shader (GTK_SHADERTOY (shadertoy)),
+ -1);
+
+ centerbox = gtk_center_box_new ();
+ gtk_box_append (GTK_BOX (box), centerbox);
+
+ hbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, FALSE);
+ gtk_box_set_spacing (GTK_BOX (hbox), 6);
+ gtk_center_box_set_start_widget (GTK_CENTER_BOX (centerbox), hbox);
+
+ button = gtk_button_new_from_icon_name ("media-playback-start-symbolic");
+ g_signal_connect (button, "clicked", G_CALLBACK (run_clicked_cb), NULL);
+ gtk_box_append (GTK_BOX (hbox), button);
+
+ button = gtk_button_new_from_icon_name ("edit-clear-all-symbolic");
+ g_signal_connect (button, "clicked", G_CALLBACK (clear_clicked_cb), NULL);
+ gtk_box_append (GTK_BOX (hbox), button);
+
+ hbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, FALSE);
+ gtk_box_set_spacing (GTK_BOX (hbox), 6);
+ gtk_center_box_set_end_widget (GTK_CENTER_BOX (centerbox), hbox);
+
+ button = new_button ("/shadertoy/alienplanet.glsl");
+ gtk_box_append (GTK_BOX (hbox), button);
+
+ button = new_button ("/shadertoy/mandelbrot.glsl");
+ gtk_box_append (GTK_BOX (hbox), button);
+
+ button = new_button ("/shadertoy/neon.glsl");
+ gtk_box_append (GTK_BOX (hbox), button);
+
+ button = new_button ("/shadertoy/cogs.glsl");
+ gtk_box_append (GTK_BOX (hbox), button);
+
+ button = new_button ("/shadertoy/glowingstars.glsl");
+ gtk_box_append (GTK_BOX (hbox), button);
+
+ return window;
+}
+
+GtkWidget *
+do_shadertoy (GtkWidget *do_widget)
+{
+ if (!demo_window)
+ demo_window = create_shadertoy_window (do_widget);
+
+ if (!gtk_widget_get_visible (demo_window))
+ gtk_widget_show (demo_window);
+ else
+ gtk_window_destroy (GTK_WINDOW (demo_window));
+
+ return demo_window;
+}