[gtk: 12/15] gtk-demo: Add GskGLShaderNode demo




commit 8bcb031418a2676a2f64080ae74c51cd9e9fbf30
Author: Alexander Larsson <alexl redhat com>
Date:   Mon Sep 21 21:05:04 2020 +0200

    gtk-demo: Add GskGLShaderNode demo
    
    Add adds a demo showing off GskGLShaderNode in various ways.
    
    It has a transistion widget, using some examples from
    gl-transitions.com, with child widgets being both images, a GL area
    and real widgets (that let you edit the transition shaders
    themselves.
    
    It also has a fancy fire effect on hove on the buttons.

 demos/gtk-demo/cogs2.glsl           | 226 +++++++++++++++++++++++
 demos/gtk-demo/demo.gresource.xml   |  13 ++
 demos/gtk-demo/fire.glsl            |  72 ++++++++
 demos/gtk-demo/gltransition.c       | 329 +++++++++++++++++++++++++++++++++
 demos/gtk-demo/gskshaderpaintable.c | 337 ++++++++++++++++++++++++++++++++++
 demos/gtk-demo/gskshaderpaintable.h |  53 ++++++
 demos/gtk-demo/gtkshaderbin.c       | 241 ++++++++++++++++++++++++
 demos/gtk-demo/gtkshaderbin.h       |  22 +++
 demos/gtk-demo/gtkshaderstack.c     | 352 ++++++++++++++++++++++++++++++++++++
 demos/gtk-demo/gtkshaderstack.h     |  21 +++
 demos/gtk-demo/meson.build          |   4 +
 demos/gtk-demo/transition1.glsl     |  33 ++++
 demos/gtk-demo/transition2.glsl     |  34 ++++
 demos/gtk-demo/transition3.glsl     |  27 +++
 demos/gtk-demo/transition4.glsl     |  41 +++++
 15 files changed, 1805 insertions(+)
---
diff --git a/demos/gtk-demo/cogs2.glsl b/demos/gtk-demo/cogs2.glsl
new file mode 100644
index 0000000000..8d131eca8d
--- /dev/null
+++ b/demos/gtk-demo/cogs2.glsl
@@ -0,0 +1,226 @@
+uniform float iTime;
+
+// 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, in vec2 fragCoord, in vec2 resolution, in vec2 uv) {
+  vec2 q = fragCoord/resolution.xy;
+  vec2 p = -1.0 + 2.0*q;
+  p.x *= resolution.x/resolution.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 / resolution.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 39d6ad2db8..cc9727481c 100644
--- a/demos/gtk-demo/demo.gresource.xml
+++ b/demos/gtk-demo/demo.gresource.xml
@@ -133,6 +133,18 @@
     <file>cogs.glsl</file>
     <file>glowingstars.glsl</file>
   </gresource>
+  <gresource prefix="/gltransition">
+    <file>gtkshaderstack.c</file>
+    <file>gtkshaderstack.h</file>
+    <file>gtkshaderbin.h</file>
+    <file>gtkshaderbin.c</file>
+    <file>fire.glsl</file>
+    <file>transition1.glsl</file>
+    <file>transition2.glsl</file>
+    <file>transition3.glsl</file>
+    <file>transition4.glsl</file>
+    <file>cogs2.glsl</file>
+  </gresource>
   <gresource prefix="/iconscroll">
     <file>iconscroll.ui</file>
   </gresource>
@@ -247,6 +259,7 @@
     <file>gears.c</file>
     <file>gestures.c</file>
     <file>glarea.c</file>
+    <file>gltransition.c</file>
     <file>headerbar.c</file>
     <file>hypertext.c</file>
     <file>iconscroll.c</file>
diff --git a/demos/gtk-demo/fire.glsl b/demos/gtk-demo/fire.glsl
new file mode 100644
index 0000000000..e62fe1bd39
--- /dev/null
+++ b/demos/gtk-demo/fire.glsl
@@ -0,0 +1,72 @@
+uniform float u_time;
+uniform sampler2D u_texture1;
+
+/* 2D -> [0..1] random number generator */
+float random(vec2 st) {
+  return fract(sin(dot(st.xy,
+                       vec2(12.9898,78.233))) *
+               43758.5453123);
+}
+
+/* Generate a smoothed 2d noise based on random() */
+float noise(vec2 v) {
+  /* Round point v to integer grid grid */
+  vec2 grid_point = floor(v);
+  /* Randomize in grid corners */
+  float corner1 = random(grid_point);
+  float corner2 = random(grid_point + vec2(1, 0));
+  float corner3 = random(grid_point + vec2(0, 1));
+  float corner4 = random(grid_point + vec2(1, 1));
+  /* Interpolate smoothly between grid points */
+  vec2 fraction = smoothstep(vec2(0.0), vec2(1.0), fract(v));
+  return mix(mix(corner1, corner2, fraction.x),
+             mix(corner3, corner4, fraction.x),
+             fraction.y);
+}
+
+/* fractal brownian motion noice, see https://www.iquilezles.org/www/articles/fbm/fbm.htm */
+float fbm(in vec2 x)
+{
+  const float octaveScale = 1.9;
+  const float G = 0.5;
+  float f = 1.0;
+  float a = 1.0;
+  float t = 0.0;
+  int numOctaves = 5;
+  for (int i = 0; i < numOctaves; i++) {
+    t += a*noise(f*x);
+    f *= octaveScale;
+    a *= G;
+  }
+
+  return t;
+}
+
+void mainImage(out vec4 fragColor, in vec2 fragCoord, in vec2 resolution, in vec2 uv)
+{
+  vec2 xy = fragCoord / resolution;
+
+  float zoom = 3.0 - sin(u_time*0.5)*0.3;
+
+  // Normalize coord to height of widget
+  vec2 p = (vec2 (-resolution.x/2.0 + fragCoord.x, resolution.y - fragCoord.y) / resolution.yy)* zoom;
+
+    // Use recursive incantations of fbm
+  float q1 = fbm(p - vec2(0.8, 0.3) * u_time);
+  float q2 = fbm(p - vec2(0.5, 1.3) * u_time);
+  float r = fbm(2.0*p + vec2(q1,q2) - vec2(0.0, 1.0)*u_time*10.0 *0.4);
+
+  // Compute intensity, mostly on the bottom
+  float w = 2.0 * r * p.y;
+
+  // Smooth out left/right side and fade in at start
+  w /= smoothstep(0.0,0.1, xy.x)* smoothstep(0.0,0.1, 1.0-xy.x) *  smoothstep(0.0,0.4, u_time);
+
+  // Compute colors
+  vec3 c = vec3(1.0,.2,.05);
+  vec3 color = 1.0 / (w*w/c + 1.0);
+
+  // Mix in widget
+  vec4 widget = GskTexture(u_texture1,uv);
+  fragColor = gsk_premultiply(mix(vec4(color,1), widget, 1.0-color.x));
+}
diff --git a/demos/gtk-demo/gltransition.c b/demos/gtk-demo/gltransition.c
new file mode 100644
index 0000000000..24b6f96f30
--- /dev/null
+++ b/demos/gtk-demo/gltransition.c
@@ -0,0 +1,329 @@
+/* OpenGL/Transitions
+ * #Keywords: OpenGL, shader
+ *
+ * Create transitions between pages using a custom fragment shader.
+ *
+ * The example transitions here are taken from gl-transitions.com, and you
+ * can edit the shader code itself on the last page of the stack.
+ *
+ * The transitions work with arbitrary content. We use images, shaders
+ * GL areas and plain old widgets to demonstrate this.
+ *
+ * The demo also shows some sample fire effects on the buttons.
+ */
+
+#include <math.h>
+#include <gtk/gtk.h>
+#include "gtkshaderstack.h"
+#include "gtkshaderbin.h"
+#include "gtkshadertoy.h"
+#include "gskshaderpaintable.h"
+
+static GtkWidget *demo_window = NULL;
+
+static void
+close_window (GtkWidget *widget)
+{
+  /* Reset the state */
+  demo_window = NULL;
+}
+
+static void
+text_changed (GtkTextBuffer *buffer,
+              GtkWidget     *button)
+{
+  gtk_widget_show (button);
+}
+
+static void
+apply_text (GtkWidget     *button,
+            GtkTextBuffer *buffer)
+{
+  GtkWidget *stack;
+  GskGLShader *shader;
+  GtkTextIter start, end;
+  char *text;
+
+  stack = g_object_get_data (G_OBJECT (button), "the-stack");
+
+  gtk_text_buffer_get_bounds (buffer, &start, &end);
+  text = gtk_text_buffer_get_text (buffer, &start, &end, TRUE);
+
+  GBytes *bytes = g_bytes_new_take (text, strlen (text));
+  shader = gsk_gl_shader_new_from_bytes (bytes);
+
+  gtk_shader_stack_set_shader (GTK_SHADER_STACK (stack), shader);
+
+  g_object_unref (shader);
+  g_bytes_unref (bytes);
+
+  gtk_widget_hide (button);
+}
+
+static void
+go_back (GtkWidget     *button,
+         GtkWidget     *stack)
+{
+  gtk_shader_stack_transition (GTK_SHADER_STACK (stack), FALSE);
+}
+
+static void
+go_forward (GtkWidget     *button,
+            GtkWidget     *stack)
+{
+  gtk_shader_stack_transition (GTK_SHADER_STACK (stack), TRUE);
+}
+
+static void
+clicked_cb (GtkGestureClick *gesture,
+            guint            n_pressed,
+            double           x,
+            double           y,
+            gpointer         data)
+{
+  gtk_gesture_set_state (GTK_GESTURE (gesture), GTK_EVENT_SEQUENCE_CLAIMED);
+}
+
+static GtkWidget *
+fire_bin_new (void)
+{
+  GtkWidget *bin = gtk_shader_bin_new ();
+  static GskGLShader *shader = NULL;
+
+  if (shader == NULL)
+    shader = gsk_gl_shader_new_from_resource ("/gltransition/fire.glsl");
+
+  gtk_shader_bin_add_shader (GTK_SHADER_BIN (bin), shader, GTK_STATE_FLAG_PRELIGHT, GTK_STATE_FLAG_PRELIGHT);
+
+  return bin;
+}
+
+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 gboolean
+update_paintable (GtkWidget     *widget,
+                  GdkFrameClock *frame_clock,
+                  gpointer       user_data)
+{
+  GskShaderPaintable *paintable;
+  gint64 frame_time;
+
+  paintable = GSK_SHADER_PAINTABLE (gtk_picture_get_paintable (GTK_PICTURE (widget)));
+  frame_time = gdk_frame_clock_get_frame_time (frame_clock);
+  gsk_shader_paintable_update_time (paintable, 0, frame_time);
+
+  return G_SOURCE_CONTINUE;
+}
+
+static GtkWidget *
+make_shader_stack (const char *name,
+                   const char *resource_path,
+                   GtkWidget  *scale)
+{
+  GtkWidget *stack, *child, *widget, *vbox, *hbox, *bin;
+  GtkWidget *label, *button, *tv;
+  GskGLShader *shader;
+  GObjectClass *class;
+  GParamSpecFloat *pspec;
+  GtkAdjustment *adjustment;
+  GtkTextBuffer *buffer;
+  GBytes *bytes;
+  GtkEventController *controller;
+  GtkCssProvider *provider;
+  GdkPaintable *paintable;
+
+  stack = gtk_shader_stack_new ();
+  shader = gsk_gl_shader_new_from_resource (resource_path);
+  gtk_shader_stack_set_shader (GTK_SHADER_STACK (stack), shader);
+  g_object_unref (shader);
+
+  child = gtk_picture_new_for_resource ("/css_pixbufs/background.jpg");
+  gtk_picture_set_can_shrink (GTK_PICTURE (child), TRUE);
+  gtk_shader_stack_add_child (GTK_SHADER_STACK (stack), child);
+
+  shader = gsk_gl_shader_new_from_resource ("/gltransition/cogs2.glsl");
+  paintable = gsk_shader_paintable_new (shader, NULL);
+
+  child = gtk_picture_new_for_paintable (paintable);
+  gtk_widget_add_tick_callback (child, update_paintable, NULL, NULL);
+  gtk_picture_set_can_shrink (GTK_PICTURE (child), TRUE);
+  gtk_shader_stack_add_child (GTK_SHADER_STACK (stack), child);
+
+  child = gtk_picture_new_for_resource ("/transparent/portland-rose.jpg");
+  gtk_picture_set_can_shrink (GTK_PICTURE (child), TRUE);
+  gtk_shader_stack_add_child (GTK_SHADER_STACK (stack), child);
+
+  child = new_shadertoy ("/shadertoy/neon.glsl");
+  gtk_shader_stack_add_child (GTK_SHADER_STACK (stack), child);
+
+  child = gtk_box_new (GTK_ORIENTATION_VERTICAL, 6);
+
+  class = g_type_class_ref (GTK_TYPE_SHADER_STACK);
+  pspec = G_PARAM_SPEC_FLOAT (g_object_class_find_property (class, "duration"));
+
+  adjustment = gtk_range_get_adjustment (GTK_RANGE (scale));
+  if (gtk_adjustment_get_lower (adjustment) == 0.0 &&
+      gtk_adjustment_get_upper (adjustment) == 0.0)
+    {
+      gtk_adjustment_configure (adjustment,
+                                pspec->default_value,
+                                pspec->minimum,
+                                pspec->maximum,
+                                0.1, 0.5, 0);
+    }
+
+  g_type_class_unref (class);
+
+  g_object_bind_property (adjustment, "value",
+                          stack, "duration",
+                          G_BINDING_DEFAULT);
+
+  widget = gtk_scrolled_window_new ();
+  gtk_scrolled_window_set_has_frame (GTK_SCROLLED_WINDOW (widget), TRUE);
+  gtk_widget_set_hexpand (widget, TRUE);
+  gtk_widget_set_vexpand (widget, TRUE);
+
+  controller = GTK_EVENT_CONTROLLER (gtk_gesture_click_new ());
+  gtk_gesture_single_set_button (GTK_GESTURE_SINGLE (controller), 0);
+  g_signal_connect (controller, "released", G_CALLBACK (clicked_cb), NULL);
+  gtk_event_controller_set_propagation_phase (controller, GTK_PHASE_BUBBLE);
+  gtk_widget_add_controller (GTK_WIDGET (widget), controller);
+
+  tv = gtk_text_view_new ();
+  gtk_text_view_set_left_margin (GTK_TEXT_VIEW (tv), 4);
+  gtk_text_view_set_right_margin (GTK_TEXT_VIEW (tv), 4);
+  gtk_text_view_set_top_margin (GTK_TEXT_VIEW (tv), 4);
+  gtk_text_view_set_bottom_margin (GTK_TEXT_VIEW (tv), 4);
+  buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (tv));
+  bytes = g_resources_lookup_data (resource_path, 0, NULL);
+  gtk_text_buffer_set_text (buffer,
+                            (const char *)g_bytes_get_data (bytes, NULL),
+                            g_bytes_get_size (bytes));
+  g_bytes_unref (bytes);
+  gtk_scrolled_window_set_child (GTK_SCROLLED_WINDOW (widget), tv);
+
+  gtk_box_append (GTK_BOX (child), widget);
+
+  gtk_shader_stack_add_child (GTK_SHADER_STACK (stack), child);
+
+  vbox = gtk_box_new (GTK_ORIENTATION_VERTICAL, 6);
+
+  widget = gtk_center_box_new ();
+  label = gtk_label_new (name);
+  gtk_widget_add_css_class (label, "title-4");
+  gtk_widget_set_size_request (label, -1, 26);
+  gtk_center_box_set_center_widget (GTK_CENTER_BOX (widget), label);
+
+  button = gtk_button_new_from_icon_name ("view-refresh-symbolic");
+  g_signal_connect (buffer, "changed", G_CALLBACK (text_changed), button);
+  g_object_set_data (G_OBJECT (button), "the-stack", stack);
+  g_signal_connect (button, "clicked", G_CALLBACK (apply_text), buffer);
+  provider = gtk_css_provider_new ();
+  gtk_css_provider_load_from_data (provider, "button.small { padding: 0; }", -1);
+  gtk_style_context_add_provider (gtk_widget_get_style_context (button),
+                                  GTK_STYLE_PROVIDER (provider),
+                                  GTK_STYLE_PROVIDER_PRIORITY_APPLICATION);
+  g_object_unref (provider);
+  gtk_widget_set_halign (button, GTK_ALIGN_CENTER);
+  gtk_widget_set_valign (button, GTK_ALIGN_CENTER);
+  gtk_widget_add_css_class (button, "small");
+  gtk_widget_hide (button);
+  gtk_center_box_set_end_widget (GTK_CENTER_BOX (widget), button);
+
+  gtk_box_append (GTK_BOX (vbox), widget);
+
+  gtk_box_append (GTK_BOX (vbox), stack);
+
+  hbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 6);
+  gtk_widget_set_halign (hbox, GTK_ALIGN_CENTER);
+
+  gtk_box_append (GTK_BOX (vbox), hbox);
+
+  button = gtk_button_new_from_icon_name ("go-previous");
+  g_signal_connect (button, "clicked", G_CALLBACK (go_back), stack);
+  bin = fire_bin_new ();
+  gtk_shader_bin_set_child (GTK_SHADER_BIN (bin), button);
+  gtk_box_append (GTK_BOX (hbox), bin);
+
+  button = gtk_button_new_from_icon_name ("go-next");
+  g_signal_connect (button, "clicked", G_CALLBACK (go_forward), stack);
+  bin = fire_bin_new ();
+  gtk_shader_bin_set_child (GTK_SHADER_BIN (bin), button);
+  gtk_box_append (GTK_BOX (hbox), bin);
+
+  return vbox;
+}
+
+static GtkWidget *
+create_gltransition_window (GtkWidget *do_widget)
+{
+  GtkWidget *window, *headerbar, *scale, *grid;
+
+  window = gtk_window_new ();
+  gtk_window_set_display (GTK_WINDOW (window),  gtk_widget_get_display (do_widget));
+  gtk_window_set_title (GTK_WINDOW (window), "Transitions");
+  headerbar = gtk_header_bar_new ();
+  scale = gtk_scale_new (GTK_ORIENTATION_HORIZONTAL, NULL);
+  gtk_scale_set_draw_value (GTK_SCALE (scale), FALSE);
+  gtk_widget_set_size_request (scale, 100, -1);
+  gtk_header_bar_pack_end (GTK_HEADER_BAR (headerbar), scale);
+  gtk_window_set_titlebar (GTK_WINDOW (window), headerbar);
+  gtk_window_set_default_size (GTK_WINDOW (window), 800, 600);
+  g_signal_connect (window, "destroy", G_CALLBACK (close_window), NULL);
+
+  grid = gtk_grid_new ();
+  gtk_window_set_child (GTK_WINDOW (window), grid);
+
+  gtk_widget_set_halign (grid, GTK_ALIGN_CENTER);
+  gtk_widget_set_valign (grid, GTK_ALIGN_CENTER);
+  gtk_widget_set_margin_start (grid, 12);
+  gtk_widget_set_margin_end (grid, 12);
+  gtk_widget_set_margin_top (grid, 12);
+  gtk_widget_set_margin_bottom (grid, 12);
+  gtk_grid_set_row_spacing (GTK_GRID (grid), 6);
+  gtk_grid_set_column_spacing (GTK_GRID (grid), 6);
+  gtk_grid_set_row_homogeneous (GTK_GRID (grid), TRUE);
+  gtk_grid_set_column_homogeneous (GTK_GRID (grid), TRUE);
+
+  gtk_grid_attach (GTK_GRID (grid),
+                   make_shader_stack ("Wind", "/gltransition/transition1.glsl", scale),
+                   0, 0, 1, 1);
+  gtk_grid_attach (GTK_GRID (grid),
+                   make_shader_stack ("Radial", "/gltransition/transition2.glsl", scale),
+                   1, 0, 1, 1);
+  gtk_grid_attach (GTK_GRID (grid),
+                   make_shader_stack ("Crosswarp", "/gltransition/transition3.glsl", scale),
+                   0, 1, 1, 1);
+  gtk_grid_attach (GTK_GRID (grid),
+                   make_shader_stack ("Kaleidoscope", "/gltransition/transition4.glsl", scale),
+                   1, 1, 1, 1);
+
+  return window;
+}
+
+GtkWidget *
+do_gltransition (GtkWidget *do_widget)
+{
+  if (!demo_window)
+    demo_window = create_gltransition_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;
+}
diff --git a/demos/gtk-demo/gskshaderpaintable.c b/demos/gtk-demo/gskshaderpaintable.c
new file mode 100644
index 0000000000..3eb0cbc89f
--- /dev/null
+++ b/demos/gtk-demo/gskshaderpaintable.c
@@ -0,0 +1,337 @@
+/*
+ * Copyright © 2020 Red Hat, Inc
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Authors: Matthias Clasen <mclasen redhat com>
+ */
+
+#include "config.h"
+
+#include <gtk/gtk.h>
+#include "gskshaderpaintable.h"
+
+/**
+ * SECTION:gskshaderpaintable
+ * @Short_description: Drawing with shaders
+ * @Title: GskShaderPaintable
+ * @see_also: #GdkPaintable
+ *
+ * GskShaderPaintable is an implementation of the #GdkPaintable interface
+ * that uses a #GskGLShader to create pixels.
+ *
+ * You can set the uniform data that the shader needs for rendering
+ * using gsk_shader_paintable_set_args(). This function can
+ * be called repeatedly to change the uniform data for the next
+ * snapshot.
+ *
+ * Commonly, time is passed to shaders as a float uniform containing
+ * the elapsed time in seconds. The convenience API
+ * gsk_shader_paintable_update_time() can be called from a #GtkTickCallback
+ * to update the time based on the frame time of the frame clock.
+ */
+
+
+struct _GskShaderPaintable
+{
+  GObject parent_instance;
+
+  GskGLShader *shader;
+  GBytes *args;
+
+  gint64 start_time;
+};
+
+struct _GskShaderPaintableClass
+{
+  GObjectClass parent_class;
+};
+
+enum {
+  PROP_0,
+  PROP_SHADER,
+  PROP_ARGS,
+
+  N_PROPS,
+};
+
+static GParamSpec *properties[N_PROPS] = { NULL, };
+
+static void
+gsk_shader_paintable_paintable_snapshot (GdkPaintable *paintable,
+                                         GdkSnapshot  *snapshot,
+                                         double        width,
+                                         double        height)
+{
+  GskShaderPaintable *self = GSK_SHADER_PAINTABLE (paintable);
+
+  gtk_snapshot_push_gl_shader (snapshot, self->shader, &GRAPHENE_RECT_INIT(0, 0, width, height), g_bytes_ref 
(self->args));
+  gtk_snapshot_pop (snapshot);
+}
+
+static void
+gsk_shader_paintable_paintable_init (GdkPaintableInterface *iface)
+{
+  iface->snapshot = gsk_shader_paintable_paintable_snapshot;
+}
+
+G_DEFINE_TYPE_EXTENDED (GskShaderPaintable, gsk_shader_paintable, G_TYPE_OBJECT, 0,
+                        G_IMPLEMENT_INTERFACE (GDK_TYPE_PAINTABLE,
+                                               gsk_shader_paintable_paintable_init))
+
+static void
+gsk_shader_paintable_set_property (GObject      *object,
+                                   guint         prop_id,
+                                   const GValue *value,
+                                   GParamSpec   *pspec)
+
+{
+  GskShaderPaintable *self = GSK_SHADER_PAINTABLE (object);
+
+  switch (prop_id)
+    {
+    case PROP_SHADER:
+      gsk_shader_paintable_set_shader (self, g_value_get_object (value));
+      break;
+
+    case PROP_ARGS:
+      gsk_shader_paintable_set_args (self, g_value_get_boxed (value));
+      break;
+
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+      break;
+    }
+}
+
+static void
+gsk_shader_paintable_get_property (GObject    *object,
+                                   guint       prop_id,
+                                   GValue     *value,
+                                   GParamSpec *pspec)
+{
+  GskShaderPaintable *self = GSK_SHADER_PAINTABLE (object);
+
+  switch (prop_id)
+    {
+    case PROP_SHADER:
+      g_value_set_object (value, self->shader);
+      break;
+
+    case PROP_ARGS:
+      g_value_set_boxed (value, self->args);
+      break;
+
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+      break;
+    }
+}
+
+static void
+gsk_shader_paintable_finalize (GObject *object)
+{
+  GskShaderPaintable *self = GSK_SHADER_PAINTABLE (object);
+
+  g_clear_pointer (&self->args, g_bytes_unref);
+  g_clear_object (&self->shader);
+
+  G_OBJECT_CLASS (gsk_shader_paintable_parent_class)->finalize (object);
+}
+
+static void
+gsk_shader_paintable_class_init (GskShaderPaintableClass *klass)
+{
+  GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
+
+  gobject_class->get_property = gsk_shader_paintable_get_property;
+  gobject_class->set_property = gsk_shader_paintable_set_property;
+  gobject_class->finalize = gsk_shader_paintable_finalize;
+
+  properties[PROP_SHADER] =
+    g_param_spec_object ("shader", "Shader", "The shader",
+                         GSK_TYPE_GL_SHADER,
+                         G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS);
+
+  properties[PROP_ARGS] =
+    g_param_spec_boxed ("args", "Arguments", "The uniform arguments",
+                        G_TYPE_BYTES,
+                        G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS);
+
+  g_object_class_install_properties (gobject_class, N_PROPS, properties);
+}
+
+static void
+gsk_shader_paintable_init (GskShaderPaintable *self)
+{
+}
+
+/**
+ * gsk_shader_paintable_new:
+ * @shader: (transfer full) (nullable): the shader to use
+ * @data: (transfer full) (nullable): uniform data
+ *
+ * Creates a paintable that uses the @shader to create
+ * pixels. The shader must not require input textures.
+ * If @data is %NULL, all uniform values are set to zero.
+ *
+ * Returns: (transfer full): a new #GskShaderPaintable
+ */
+GdkPaintable *
+gsk_shader_paintable_new (GskGLShader *shader,
+                          GBytes      *data)
+{
+  GdkPaintable *ret;
+
+  g_return_val_if_fail (shader == NULL || GSK_IS_GL_SHADER (shader), NULL);
+
+  if (shader && !data)
+    {
+      int size = gsk_gl_shader_get_args_size (shader);
+      data = g_bytes_new_take (g_new0 (guchar, size), size);
+    }
+
+  ret = g_object_new (GSK_TYPE_SHADER_PAINTABLE,
+                      "shader", shader,
+                      "args", data,
+                      NULL);
+
+  g_clear_object (&shader);
+  g_clear_pointer (&data, g_bytes_unref);
+
+  return ret;
+}
+
+/**
+ * gsk_shader_paintable_set_shader:
+ * @self: a #GskShaderPaintable
+ * @shader: the #GskGLShader to use
+ *
+ * Sets the shader that the paintable will use
+ * to create pixels. The shader must not require
+ * input textures.
+ */
+void
+gsk_shader_paintable_set_shader (GskShaderPaintable *self,
+                                 GskGLShader        *shader)
+{
+  g_return_if_fail (GSK_IS_SHADER_PAINTABLE (self));
+  g_return_if_fail (shader == NULL || GSK_IS_GL_SHADER (shader));
+  g_return_if_fail (shader == NULL || gsk_gl_shader_get_n_textures (shader) == 0);
+
+  if (!g_set_object (&self->shader, shader))
+    return;
+
+  g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_SHADER]);
+  gdk_paintable_invalidate_contents (GDK_PAINTABLE (self));
+
+  g_clear_pointer (&self->args, g_bytes_unref);
+}
+
+/**
+ * gsk_shader_paintable_get_shader:
+ * @self: a #GskShaderPaintable
+ *
+ * Returns the shader that the paintable is using.
+ *
+ * Returns: (transfer none): the #GskGLShader that is used
+ */
+GskGLShader *
+gsk_shader_paintable_get_shader (GskShaderPaintable *self)
+{
+  g_return_val_if_fail (GSK_IS_SHADER_PAINTABLE (self), NULL);
+
+  return self->shader;
+}
+
+/**
+ * gsk_shader_paintable_set_args:
+ * @self: a #GskShaderPaintable
+ * @data: Data block with uniform data for the shader
+ *
+ * Sets the uniform data that will be passed to the
+ * shader when rendering. The @data will typically
+ * be produced by a #GskUniformDataBuilder.
+ *
+ * Note that the @data should be considered immutable
+ * after it has been passed to this function.
+ */
+void
+gsk_shader_paintable_set_args (GskShaderPaintable *self,
+                                       GBytes             *data)
+{
+  g_return_if_fail (GSK_IS_SHADER_PAINTABLE (self));
+  g_return_if_fail (data == NULL || g_bytes_get_size (data) == gsk_gl_shader_get_args_size (self->shader));
+
+  g_clear_pointer (&self->args, g_bytes_unref);
+  if (data)
+    self->args = g_bytes_ref (data);
+
+  g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_ARGS]);
+  gdk_paintable_invalidate_contents (GDK_PAINTABLE (self));
+}
+
+/**
+ * gsk_shader_paintable_get_args:
+ * @self: a #GskShaderPaintable
+ *
+ * Returns the uniform data set with
+ * gsk_shader_paintable_get_args().
+ *
+ * Returns: (transfer none): the uniform data
+ */
+GBytes *
+gsk_shader_paintable_get_args (GskShaderPaintable *self)
+{
+  g_return_val_if_fail (GSK_IS_SHADER_PAINTABLE (self), NULL);
+
+  return self->args;
+}
+
+/**
+ * gsk_shader_paintable_update_time:
+ * @self: a #GskShaderPaintable
+ * @time_idx: the index of the uniform for time in seconds as float
+ * @frame_time: the current frame time, as returned by #GdkFrameClock
+ *
+ * This function is a convenience wrapper for
+ * gsk_shader_paintable_set_args() that leaves all
+ * uniform values unchanged, except for the uniform with
+ * index @time_idx, which will be set to the elapsed time
+ * in seconds, since the first call to this function.
+ *
+ * This function is usually called from a #GtkTickCallback.
+ */
+void
+gsk_shader_paintable_update_time (GskShaderPaintable *self,
+                                  int                 time_idx,
+                                  gint64              frame_time)
+{
+  GskShaderArgsBuilder *builder;
+  GBytes *args;
+  float time;
+
+  if (self->start_time == 0)
+    self->start_time = frame_time;
+
+  time = (frame_time - self->start_time) / (float)G_TIME_SPAN_SECOND;
+
+  builder = gsk_shader_args_builder_new (self->shader, self->args);
+  gsk_shader_args_builder_set_float (builder, time_idx, time);
+  args = gsk_shader_args_builder_free_to_args (builder);
+
+  gsk_shader_paintable_set_args (self, args);
+
+  g_bytes_unref (args);
+}
diff --git a/demos/gtk-demo/gskshaderpaintable.h b/demos/gtk-demo/gskshaderpaintable.h
new file mode 100644
index 0000000000..9b8f7db83f
--- /dev/null
+++ b/demos/gtk-demo/gskshaderpaintable.h
@@ -0,0 +1,53 @@
+/*
+ * Copyright © 2020 Red Hat, Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Authors: Matthias Clasen <mclasen redhat com>
+ */
+
+#ifndef __GSK_SHADER_PAINTABLE_H__
+#define __GSK_SHADER_PAINTABLE_H__
+
+#include <gdk/gdk.h>
+#include <gsk/gsk.h>
+
+G_BEGIN_DECLS
+
+#define GSK_TYPE_SHADER_PAINTABLE (gsk_shader_paintable_get_type ())
+
+GDK_AVAILABLE_IN_ALL
+G_DECLARE_FINAL_TYPE (GskShaderPaintable, gsk_shader_paintable, GSK, SHADER_PAINTABLE, GObject)
+
+GDK_AVAILABLE_IN_ALL
+GdkPaintable *   gsk_shader_paintable_new              (GskGLShader        *shader,
+                                                        GBytes             *data);
+
+GDK_AVAILABLE_IN_ALL
+GskGLShader *    gsk_shader_paintable_get_shader       (GskShaderPaintable *self);
+GDK_AVAILABLE_IN_ALL
+void             gsk_shader_paintable_set_shader       (GskShaderPaintable *self,
+                                                        GskGLShader        *shader);
+GDK_AVAILABLE_IN_ALL
+GBytes *         gsk_shader_paintable_get_args         (GskShaderPaintable *self);
+GDK_AVAILABLE_IN_ALL
+void             gsk_shader_paintable_set_args         (GskShaderPaintable *self,
+                                                        GBytes             *data);
+GDK_AVAILABLE_IN_ALL
+void             gsk_shader_paintable_update_time      (GskShaderPaintable *self,
+                                                        int                 time_idx,
+                                                        gint64              frame_time);
+G_END_DECLS
+
+#endif /* __GSK_SHADER_PAINTABLE_H__ */
diff --git a/demos/gtk-demo/gtkshaderbin.c b/demos/gtk-demo/gtkshaderbin.c
new file mode 100644
index 0000000000..3a97311c3e
--- /dev/null
+++ b/demos/gtk-demo/gtkshaderbin.c
@@ -0,0 +1,241 @@
+#include "gtkshaderbin.h"
+
+typedef struct {
+  GskGLShader *shader;
+  GtkStateFlags state;
+  GtkStateFlags state_mask;
+  gboolean compiled;
+  gboolean compiled_ok;
+} ShaderInfo;
+
+struct _GtkShaderBin
+{
+  GtkWidget parent_instance;
+  GtkWidget *child;
+  ShaderInfo *active_shader;
+  GPtrArray *shaders;
+  guint tick_id;
+  float time;
+  gint64 first_frame_time;
+};
+
+struct _GtkShaderBinClass
+{
+  GtkWidgetClass parent_class;
+};
+
+G_DEFINE_TYPE (GtkShaderBin, gtk_shader_bin, GTK_TYPE_WIDGET)
+
+static void
+shader_info_free (ShaderInfo *info)
+{
+  g_object_unref (info->shader);
+  g_free (info);
+}
+
+static void
+gtk_shader_bin_finalize (GObject *object)
+{
+  GtkShaderBin *self = GTK_SHADER_BIN (object);
+
+  g_ptr_array_free (self->shaders, TRUE);
+
+  G_OBJECT_CLASS (gtk_shader_bin_parent_class)->finalize (object);
+}
+
+static void
+gtk_shader_bin_dispose (GObject *object)
+{
+  GtkShaderBin *self = GTK_SHADER_BIN (object);
+
+  g_clear_pointer (&self->child, gtk_widget_unparent);
+
+  G_OBJECT_CLASS (gtk_shader_bin_parent_class)->dispose (object);
+}
+
+static gboolean
+gtk_shader_bin_tick (GtkWidget     *widget,
+                     GdkFrameClock *frame_clock,
+                     gpointer       unused)
+{
+  GtkShaderBin *self = GTK_SHADER_BIN (widget);
+  gint64 frame_time;
+
+  frame_time = gdk_frame_clock_get_frame_time (frame_clock);
+  if (self->first_frame_time == 0)
+    self->first_frame_time = frame_time;
+  self->time = (frame_time - self->first_frame_time) / (float)G_USEC_PER_SEC;
+
+  gtk_widget_queue_draw (widget);
+
+  return G_SOURCE_CONTINUE;
+}
+
+static void
+gtk_shader_bin_init (GtkShaderBin *self)
+{
+  self->shaders = g_ptr_array_new_with_free_func ((GDestroyNotify)shader_info_free);
+}
+
+void
+gtk_shader_bin_update_active_shader (GtkShaderBin *self)
+{
+  GtkStateFlags new_state = gtk_widget_get_state_flags (GTK_WIDGET (self));
+  ShaderInfo *new_shader = NULL;
+
+  for (int i = 0; i < self->shaders->len; i++)
+    {
+      ShaderInfo *info = g_ptr_array_index (self->shaders, i);
+
+      if ((info->state_mask & new_state) == info->state)
+        {
+          new_shader = info;
+          break;
+        }
+    }
+
+  if (self->active_shader == new_shader)
+    return;
+
+  self->active_shader = new_shader;
+  self->first_frame_time = 0;
+
+  if (self->active_shader)
+    {
+      if (self->tick_id == 0)
+        self->tick_id = gtk_widget_add_tick_callback (GTK_WIDGET (self),
+                                                      gtk_shader_bin_tick,
+                                                      NULL, NULL);
+    }
+  else
+    {
+      if (self->tick_id != 0)
+        {
+          gtk_widget_remove_tick_callback (GTK_WIDGET (self), self->tick_id);
+          self->tick_id = 0;
+        }
+    }
+
+  gtk_widget_queue_draw (GTK_WIDGET (self));
+}
+
+static void
+gtk_shader_bin_state_flags_changed (GtkWidget        *widget,
+                                    GtkStateFlags     previous_state_flags)
+{
+  GtkShaderBin *self = GTK_SHADER_BIN (widget);
+
+  gtk_shader_bin_update_active_shader (self);
+}
+
+void
+gtk_shader_bin_add_shader (GtkShaderBin *self,
+                           GskGLShader  *shader,
+                           GtkStateFlags state,
+                           GtkStateFlags state_mask)
+{
+  ShaderInfo *info = g_new0 (ShaderInfo, 1);
+  info->shader = g_object_ref (shader);
+  info->state = state;
+  info->state_mask = state_mask;
+
+  g_ptr_array_add (self->shaders, info);
+
+  gtk_shader_bin_update_active_shader (self);
+}
+
+void
+gtk_shader_bin_set_child (GtkShaderBin *self,
+                          GtkWidget    *child)
+{
+
+  if (self->child == child)
+    return;
+
+  g_clear_pointer (&self->child, gtk_widget_unparent);
+
+  if (child)
+    {
+      self->child = child;
+      gtk_widget_set_parent (child, GTK_WIDGET (self));
+    }
+}
+
+GtkWidget *
+gtk_shader_bin_get_child   (GtkShaderBin *self)
+{
+  return self->child;
+}
+
+static void
+gtk_shader_bin_snapshot (GtkWidget   *widget,
+                         GtkSnapshot *snapshot)
+{
+  GtkShaderBin *self = GTK_SHADER_BIN (widget);
+  int width, height;
+
+  width = gtk_widget_get_width (widget);
+  height = gtk_widget_get_height (widget);
+
+  if (self->active_shader)
+    {
+      if (!self->active_shader->compiled)
+        {
+          GtkNative *native = gtk_widget_get_native (widget);
+          GskRenderer *renderer = gtk_native_get_renderer (native);
+          GError *error = NULL;
+
+          self->active_shader->compiled = TRUE;
+          self->active_shader->compiled_ok =
+            gsk_gl_shader_compile (self->active_shader->shader,
+                                   renderer, &error);
+          if (!self->active_shader->compiled_ok)
+            {
+              g_warning ("GtkShaderBin failed to compile shader: %s", error->message);
+              g_error_free (error);
+            }
+        }
+
+      if (self->active_shader->compiled_ok)
+        {
+          gtk_snapshot_push_gl_shader (snapshot, self->active_shader->shader,
+                                       &GRAPHENE_RECT_INIT(0, 0, width, height),
+                                       gsk_gl_shader_format_args (self->active_shader->shader,
+                                                                  "u_time", self->time,
+                                                                  NULL));
+          gtk_widget_snapshot_child (widget, self->child, snapshot);
+          gtk_snapshot_gl_shader_pop_texture (snapshot);
+          gtk_snapshot_pop (snapshot);
+
+          return;
+        }
+    }
+
+  /* Non-shader fallback */
+  gtk_widget_snapshot_child (widget, self->child, snapshot);
+}
+
+static void
+gtk_shader_bin_class_init (GtkShaderBinClass *class)
+{
+  GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (class);
+  GObjectClass *object_class = G_OBJECT_CLASS (class);
+
+  object_class->finalize = gtk_shader_bin_finalize;
+  object_class->dispose = gtk_shader_bin_dispose;
+
+  gtk_widget_class_set_layout_manager_type (widget_class, GTK_TYPE_BIN_LAYOUT);
+
+  widget_class->snapshot = gtk_shader_bin_snapshot;
+  widget_class->state_flags_changed = gtk_shader_bin_state_flags_changed;
+}
+
+GtkWidget *
+gtk_shader_bin_new (void)
+{
+  GtkShaderBin *self;
+
+  self = g_object_new (GTK_TYPE_SHADER_BIN, NULL);
+
+  return GTK_WIDGET (self);
+}
diff --git a/demos/gtk-demo/gtkshaderbin.h b/demos/gtk-demo/gtkshaderbin.h
new file mode 100644
index 0000000000..eabac8f0d6
--- /dev/null
+++ b/demos/gtk-demo/gtkshaderbin.h
@@ -0,0 +1,22 @@
+#ifndef __GTK_SHADER_BIN_H__
+#define __GTK_SHADER_BIN_H__
+
+#include <gtk/gtk.h>
+
+G_BEGIN_DECLS
+
+#define GTK_TYPE_SHADER_BIN      (gtk_shader_bin_get_type ())
+G_DECLARE_FINAL_TYPE (GtkShaderBin, gtk_shader_bin, GTK, SHADER_BIN, GtkWidget)
+
+GtkWidget  *gtk_shader_bin_new        (void);
+void        gtk_shader_bin_add_shader (GtkShaderBin *self,
+                                       GskGLShader  *shader,
+                                       GtkStateFlags state,
+                                       GtkStateFlags state_mask);
+void       gtk_shader_bin_set_child   (GtkShaderBin *self,
+                                       GtkWidget    *child);
+GtkWidget *gtk_shader_bin_get_child   (GtkShaderBin *self);
+
+G_END_DECLS
+
+#endif /* __GTK_SHADER_BIN_H__ */
diff --git a/demos/gtk-demo/gtkshaderstack.c b/demos/gtk-demo/gtkshaderstack.c
new file mode 100644
index 0000000000..27194df3b1
--- /dev/null
+++ b/demos/gtk-demo/gtkshaderstack.c
@@ -0,0 +1,352 @@
+#include "gtkshaderstack.h"
+
+struct _GtkShaderStack
+{
+  GtkWidget parent_instance;
+
+  GskGLShader *shader;
+  GPtrArray *children;
+  int current;
+  int next;
+  gboolean backwards;
+
+  guint tick_id;
+  float time;
+  float duration;
+  gint64 start_time;
+};
+
+struct _GtkShaderStackClass
+{
+  GtkWidgetClass parent_class;
+};
+
+
+enum {
+  PROP_DURATION = 1,
+  NUM_PROPERTIES
+};
+
+static GParamSpec *properties[NUM_PROPERTIES] = { NULL };
+
+G_DEFINE_TYPE (GtkShaderStack, gtk_shader_stack, GTK_TYPE_WIDGET)
+
+static void
+gtk_shader_stack_finalize (GObject *object)
+{
+  GtkShaderStack *self = GTK_SHADER_STACK (object);
+
+  g_object_unref (self->shader);
+
+  G_OBJECT_CLASS (gtk_shader_stack_parent_class)->finalize (object);
+}
+
+static void
+update_child_visible (GtkShaderStack *self)
+{
+  int i;
+
+  for (i = 0; i < self->children->len; i++)
+    {
+      GtkWidget *child = g_ptr_array_index (self->children, i);
+
+      gtk_widget_set_child_visible (child,
+                                    i == self->current || i == self->next);
+    }
+}
+
+static gboolean
+transition_cb (GtkWidget     *widget,
+               GdkFrameClock *clock,
+               gpointer       unused)
+{
+  GtkShaderStack *self = GTK_SHADER_STACK (widget);
+  gint64 frame_time;
+
+  frame_time = gdk_frame_clock_get_frame_time (clock);
+
+  if (self->start_time == 0)
+    self->start_time = frame_time;
+
+  self->time = (frame_time - self->start_time) / (float)G_USEC_PER_SEC;
+
+  gtk_widget_queue_draw (widget);
+
+  if (self->time >= self->duration)
+    {
+      self->current = self->next;
+      self->next = -1;
+
+      update_child_visible (self);
+
+      return G_SOURCE_REMOVE;
+    }
+  else
+    return G_SOURCE_CONTINUE;
+}
+
+static void
+start_transition (GtkShaderStack *self)
+{
+  self->start_time = 0;
+  self->tick_id = gtk_widget_add_tick_callback (GTK_WIDGET (self),
+                                                transition_cb,
+                                                NULL, NULL);
+}
+
+static void
+stop_transition (GtkShaderStack *self)
+{
+  if (self->tick_id != 0)
+    {
+      gtk_widget_remove_tick_callback (GTK_WIDGET (self), self->tick_id);
+      self->tick_id = 0;
+    }
+
+  if (self->next != -1)
+    self->current = self->next;
+  self->next = -1;
+
+  update_child_visible (self);
+}
+
+static void
+gtk_shader_stack_dispose (GObject *object)
+{
+  GtkShaderStack *self = GTK_SHADER_STACK (object);
+
+  stop_transition (self);
+
+  g_clear_pointer (&self->children, g_ptr_array_unref);
+
+  G_OBJECT_CLASS (gtk_shader_stack_parent_class)->dispose (object);
+}
+
+void
+gtk_shader_stack_transition (GtkShaderStack *self,
+                             gboolean forward)
+{
+  stop_transition (self);
+
+  self->backwards = !forward;
+  if (self->backwards)
+    self->next = (self->current + self->children->len - 1) % self->children->len;
+  else
+    self->next = (self->current + 1) % self->children->len;
+
+  update_child_visible (self);
+
+  start_transition (self);
+}
+
+static void
+gtk_shader_stack_init (GtkShaderStack *self)
+{
+  self->children = g_ptr_array_new_with_free_func ((GDestroyNotify)gtk_widget_unparent);
+  self->current = -1;
+  self->next = -1;
+  self->backwards = FALSE;
+  self->duration = 1.0;
+}
+
+static void
+gtk_shader_stack_measure (GtkWidget      *widget,
+                          GtkOrientation  orientation,
+                          int             for_size,
+                          int            *minimum,
+                          int            *natural,
+                          int            *minimum_baseline,
+                          int            *natural_baseline)
+{
+  GtkShaderStack *self = GTK_SHADER_STACK (widget);
+  int i;
+
+  *minimum = 0;
+  *natural = 0;
+
+  for (i = 0; i < self->children->len; i++)
+    {
+      GtkWidget *child = g_ptr_array_index (self->children, i);
+      int child_min, child_nat;
+
+      if (gtk_widget_get_visible (child))
+        {
+          gtk_widget_measure (child, orientation, for_size, &child_min, &child_nat, NULL, NULL);
+
+          *minimum = MAX (*minimum, child_min);
+          *natural = MAX (*natural, child_nat);
+        }
+    }
+}
+
+static void
+gtk_shader_stack_size_allocate (GtkWidget *widget,
+                                int        width,
+                                int        height,
+                                int        baseline)
+{
+  GtkShaderStack *self = GTK_SHADER_STACK (widget);
+  GtkAllocation child_allocation;
+  GtkWidget *child;
+  int i;
+
+  child_allocation.x = 0;
+  child_allocation.y = 0;
+  child_allocation.width = width;
+  child_allocation.height = height;
+
+  for (i = 0; i < self->children->len; i++)
+    {
+      child = g_ptr_array_index (self->children, i);
+      if (gtk_widget_get_visible (child))
+        gtk_widget_size_allocate (child, &child_allocation, -1);
+    }
+}
+
+static void
+gtk_shader_stack_snapshot (GtkWidget   *widget,
+                           GtkSnapshot *snapshot)
+{
+  GtkShaderStack *self = GTK_SHADER_STACK (widget);
+  int width, height;
+  GtkWidget *current, *next;
+
+  width = gtk_widget_get_width (widget);
+  height = gtk_widget_get_height (widget);
+
+  current = g_ptr_array_index (self->children, self->current);
+
+  if (self->next == -1)
+    {
+      gtk_widget_snapshot_child (widget, current, snapshot);
+    }
+  else
+    {
+      GtkNative *native = gtk_widget_get_native (widget);
+      GskRenderer *renderer = gtk_native_get_renderer (native);
+      float progress;
+
+      next = g_ptr_array_index (self->children, self->next);
+
+      progress = self->time / self->duration;
+
+      if (self->backwards)
+        {
+          GtkWidget *tmp = next;
+          next = current;
+          current = tmp;
+          progress = 1. - progress;
+        }
+
+      if (gsk_gl_shader_compile (self->shader, renderer, NULL))
+        {
+          gtk_snapshot_push_gl_shader (snapshot,
+                                       self->shader,
+                                       &GRAPHENE_RECT_INIT(0, 0, width, height),
+                                       gsk_gl_shader_format_args (self->shader,
+                                                                  "progress", progress,
+                                                                  NULL));
+
+          gtk_widget_snapshot_child (widget, current, snapshot);
+          gtk_snapshot_gl_shader_pop_texture (snapshot); /* current child */
+          gtk_widget_snapshot_child (widget, next, snapshot);
+          gtk_snapshot_gl_shader_pop_texture (snapshot); /* next child */
+          gtk_snapshot_pop (snapshot);
+        }
+      else
+        {
+          /* Non-shader fallback */
+          gtk_widget_snapshot_child (widget, current, snapshot);
+        }
+    }
+}
+
+static void
+gtk_shader_stack_get_property (GObject      *object,
+                               guint         prop_id,
+                               GValue       *value,
+                               GParamSpec   *pspec)
+{
+  GtkShaderStack *self = GTK_SHADER_STACK (object);
+
+  switch (prop_id)
+    {
+    case PROP_DURATION:
+      g_value_set_float (value, self->duration);
+      break;
+
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+      break;
+    }
+}
+
+static void
+gtk_shader_stack_set_property (GObject      *object,
+                               guint         prop_id,
+                               const GValue *value,
+                               GParamSpec   *pspec)
+{
+  GtkShaderStack *self = GTK_SHADER_STACK (object);
+
+  switch (prop_id)
+    {
+    case PROP_DURATION:
+      self->duration = g_value_get_float (value);
+      break;
+
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+      break;
+    }
+}
+
+static void
+gtk_shader_stack_class_init (GtkShaderStackClass *class)
+{
+  GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (class);
+  GObjectClass *object_class = G_OBJECT_CLASS (class);
+
+  object_class->finalize = gtk_shader_stack_finalize;
+  object_class->dispose = gtk_shader_stack_dispose;
+  object_class->get_property = gtk_shader_stack_get_property;
+  object_class->set_property = gtk_shader_stack_set_property;
+
+  widget_class->snapshot = gtk_shader_stack_snapshot;
+  widget_class->measure = gtk_shader_stack_measure;
+  widget_class->size_allocate = gtk_shader_stack_size_allocate;
+
+  properties[PROP_DURATION] =
+      g_param_spec_float ("duration", "Duration", "Duration",
+                          0.1, 3.0, 1.0,
+                          G_PARAM_READWRITE);
+
+  g_object_class_install_properties (object_class, NUM_PROPERTIES, properties);
+}
+
+GtkWidget *
+gtk_shader_stack_new (void)
+{
+  return g_object_new (GTK_TYPE_SHADER_STACK, NULL);
+}
+
+void
+gtk_shader_stack_set_shader (GtkShaderStack *self,
+                             GskGLShader    *shader)
+{
+  g_set_object (&self->shader, shader);
+}
+
+void
+gtk_shader_stack_add_child (GtkShaderStack *self,
+                            GtkWidget      *child)
+{
+  g_ptr_array_add (self->children, child);
+  gtk_widget_set_parent (child, GTK_WIDGET (self));
+  gtk_widget_queue_resize (GTK_WIDGET (self));
+
+  if (self->current == -1)
+    self->current = 0;
+  else
+    gtk_widget_set_child_visible (child, FALSE);
+}
diff --git a/demos/gtk-demo/gtkshaderstack.h b/demos/gtk-demo/gtkshaderstack.h
new file mode 100644
index 0000000000..9f8e4e45e5
--- /dev/null
+++ b/demos/gtk-demo/gtkshaderstack.h
@@ -0,0 +1,21 @@
+#ifndef __GTK_SHADER_STACK_H__
+#define __GTK_SHADER_STACK_H__
+
+#include <gtk/gtk.h>
+
+G_BEGIN_DECLS
+
+#define GTK_TYPE_SHADER_STACK     (gtk_shader_stack_get_type ())
+G_DECLARE_FINAL_TYPE (GtkShaderStack, gtk_shader_stack, GTK, SHADER_STACK, GtkWidget)
+
+GtkWidget * gtk_shader_stack_new          (void);
+void        gtk_shader_stack_set_shader   (GtkShaderStack *self,
+                                           GskGLShader    *shader);
+void        gtk_shader_stack_add_child    (GtkShaderStack *self,
+                                           GtkWidget      *child);
+void        gtk_shader_stack_transition   (GtkShaderStack *self,
+                                           gboolean       forward);
+
+G_END_DECLS
+
+#endif /* __GTK_SHADER_STACK_H__ */
diff --git a/demos/gtk-demo/meson.build b/demos/gtk-demo/meson.build
index a32fee88e5..ecb08ebaac 100644
--- a/demos/gtk-demo/meson.build
+++ b/demos/gtk-demo/meson.build
@@ -32,6 +32,7 @@ demos = files([
   'gears.c',
   'gestures.c',
   'glarea.c',
+  'gltransition.c',
   'headerbar.c',
   'hypertext.c',
   'iconscroll.c',
@@ -102,7 +103,10 @@ extra_demo_sources = files(['main.c',
                             'gtkfishbowl.c',
                             'fontplane.c',
                             'gtkgears.c',
+                            'gtkshaderbin.c',
                             'gtkshadertoy.c',
+                            'gtkshaderstack.c',
+                            'gskshaderpaintable.c',
                             'puzzlepiece.c',
                             'bluroverlay.c',
                             'demoimage.c',
diff --git a/demos/gtk-demo/transition1.glsl b/demos/gtk-demo/transition1.glsl
new file mode 100644
index 0000000000..169bdcbd06
--- /dev/null
+++ b/demos/gtk-demo/transition1.glsl
@@ -0,0 +1,33 @@
+uniform float progress;
+uniform sampler2D u_texture1;
+uniform sampler2D u_texture2;
+
+vec4 getFromColor (vec2 uv) {
+  return GskTexture(u_texture1, uv);
+}
+
+vec4 getToColor (vec2 uv) {
+  return GskTexture(u_texture2, uv);
+}
+
+// Source: https://gl-transitions.com/editor/wind
+// Author: gre
+// License: MIT
+
+const float size = 0.2;
+
+float rand(vec2 co) {
+  return fract(sin(dot(co.xy ,vec2(12.9898,78.233))) * 43758.5453);
+}
+
+vec4 transition(vec2 p) {
+  float r = rand(vec2(0, p.y));
+  float m = smoothstep(0.0, -size, p.x*(1.0-size) + size*r - (progress * (1.0 + size)));
+  return mix(getFromColor(p), getToColor(p), m);
+}
+
+
+void mainImage(out vec4 fragColor, in vec2 fragCoord, in vec2 resolution, in vec2 uv)
+{
+  fragColor = transition(uv);
+}
diff --git a/demos/gtk-demo/transition2.glsl b/demos/gtk-demo/transition2.glsl
new file mode 100644
index 0000000000..11757f4e60
--- /dev/null
+++ b/demos/gtk-demo/transition2.glsl
@@ -0,0 +1,34 @@
+uniform float progress;
+uniform sampler2D u_texture1;
+uniform sampler2D u_texture2;
+
+vec4 getFromColor (vec2 uv) {
+  return GskTexture(u_texture1, uv);
+}
+
+vec4 getToColor (vec2 uv) {
+  return GskTexture(u_texture2, uv);
+}
+
+// Source: https://gl-transitions.com/editor/Radial
+// License: MIT
+// Author: Xaychru
+
+const float smoothness = 1.0;
+
+const float PI = 3.141592653589;
+
+vec4 transition(vec2 p) {
+  vec2 rp = p*2.-1.;
+  return mix(
+    getToColor(p),
+    getFromColor(p),
+    smoothstep(0., smoothness, atan(rp.y,rp.x) - (progress-.5) * PI * 2.5)
+  );
+}
+
+
+void mainImage(out vec4 fragColor, in vec2 fragCoord, in vec2 resolution, in vec2 uv)
+{
+  fragColor = transition(uv);
+}
diff --git a/demos/gtk-demo/transition3.glsl b/demos/gtk-demo/transition3.glsl
new file mode 100644
index 0000000000..e1cc5f9afc
--- /dev/null
+++ b/demos/gtk-demo/transition3.glsl
@@ -0,0 +1,27 @@
+uniform float progress;
+uniform sampler2D u_texture1;
+uniform sampler2D u_texture2;
+
+vec4 getFromColor (vec2 uv) {
+  return GskTexture(u_texture1, uv);
+}
+
+vec4 getToColor (vec2 uv) {
+  return GskTexture(u_texture2, uv);
+}
+
+// Source: https://gl-transitions.com/editor/crosswarp
+// Author: Eke Péter <peterekepeter gmail com>
+// License: MIT
+
+vec4 transition(vec2 p) {
+  float x = progress;
+  x=smoothstep(.0,1.0,(x*2.0+p.x-1.0));
+  return mix(getFromColor((p-.5)*(1.-x)+.5), getToColor((p-.5)*x+.5), x);
+}
+
+
+void mainImage(out vec4 fragColor, in vec2 fragCoord, in vec2 resolution, in vec2 uv)
+{
+  fragColor = transition(uv);
+}
diff --git a/demos/gtk-demo/transition4.glsl b/demos/gtk-demo/transition4.glsl
new file mode 100644
index 0000000000..d516b3de59
--- /dev/null
+++ b/demos/gtk-demo/transition4.glsl
@@ -0,0 +1,41 @@
+uniform float progress;
+uniform sampler2D u_texture1;
+uniform sampler2D u_texture2;
+
+vec4 getFromColor (vec2 uv) {
+  return GskTexture(u_texture1, uv);
+}
+
+vec4 getToColor (vec2 uv) {
+  return GskTexture(u_texture2, uv);
+}
+
+// Source: https://gl-transitions.com/editor/kaleidoscope
+// Author: nwoeanhinnogaehr
+// License: MIT
+
+const float speed = 1.0;
+const float angle = 1.0;
+const float power = 1.5;
+
+vec4 transition(vec2 uv) {
+  vec2 p = uv.xy / vec2(1.0).xy;
+  vec2 q = p;
+  float t = pow(progress, power)*speed;
+  p = p -0.5;
+  for (int i = 0; i < 7; i++) {
+    p = vec2(sin(t)*p.x + cos(t)*p.y, sin(t)*p.y - cos(t)*p.x);
+    t += angle;
+    p = abs(mod(p, 2.0) - 1.0);
+  }
+  abs(mod(p, 1.0));
+  return mix(
+    mix(getFromColor(q), getToColor(q), progress),
+    mix(getFromColor(p), getToColor(p), progress), 1.0 - 2.0*abs(progress - 0.5));
+}
+
+
+void mainImage(out vec4 fragColor, in vec2 fragCoord, in vec2 resolution, in vec2 uv)
+{
+  fragColor = transition(uv);
+}


[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]