[gtk+/wip/matthiasc/shadertoy: 4/5] A shadertoy frontend



commit d850ca03f82df52f6949e50392fb6f284ea967f9
Author: Matthias Clasen <mclasen redhat com>
Date:   Sat Sep 23 13:14:03 2017 -0400

    A shadertoy frontend
    
    This adds a very preliminary shadertoy example to gtk4-demo.
    You can write a shader, and start and stop running it (this
    is animated, since we pass a time parameter to the shader).

 demos/gtk-demo/demo.gresource.xml |    4 +
 demos/gtk-demo/meson.build        |    1 +
 demos/gtk-demo/shadertoy.c        |  567 +++++++++++++++++++++++++++++++++++++
 demos/gtk-demo/shadertoy.ui       |   64 +++++
 4 files changed, 636 insertions(+), 0 deletions(-)
---
diff --git a/demos/gtk-demo/demo.gresource.xml b/demos/gtk-demo/demo.gresource.xml
index 17eb5c4..90628dc 100644
--- a/demos/gtk-demo/demo.gresource.xml
+++ b/demos/gtk-demo/demo.gresource.xml
@@ -187,6 +187,7 @@
     <file>scale.c</file>
     <file>search_entry.c</file>
     <file>search_entry2.c</file>
+    <file>shadertoy.c</file>
     <file>shortcuts.c</file>
     <file>sizegroup.c</file>
     <file>sidebar.c</file>
@@ -241,4 +242,7 @@
   <gresource prefix="/modelbutton">
     <file>modelbutton.ui</file>
   </gresource>
+  <gresource prefix="/shadertoy">
+    <file>shadertoy.ui</file>
+  </gresource>
 </gresources>
diff --git a/demos/gtk-demo/meson.build b/demos/gtk-demo/meson.build
index ea34ee3..fef9abe 100644
--- a/demos/gtk-demo/meson.build
+++ b/demos/gtk-demo/meson.build
@@ -54,6 +54,7 @@ demos = files([
   'scale.c',
   'search_entry.c',
   'search_entry2.c',
+  'shadertoy.c',
   'shortcuts.c',
   'sidebar.c',
   'sizegroup.c',
diff --git a/demos/gtk-demo/shadertoy.c b/demos/gtk-demo/shadertoy.c
new file mode 100644
index 0000000..69652fa
--- /dev/null
+++ b/demos/gtk-demo/shadertoy.c
@@ -0,0 +1,567 @@
+/* Shadertoy
+ *
+ * Play with shaders. Everybody does it.
+ */
+
+#include <gtk/gtk.h>
+
+enum {
+  PROP_0,
+  PROP_TIME,
+  PROP_RUNNING,
+  N_PROPS
+};
+
+G_DECLARE_FINAL_TYPE (GtkShadertoy, gtk_shadertoy, GTK, SHADERTOY, GtkWidget)
+
+struct _GtkShadertoy {
+  GtkWidget parent_instance;
+
+  GBytes *fragment_shader;
+
+  GtkWidget *child1;
+  GtkWidget *child2;
+
+  guint tick;
+  guint64 starttime;
+
+  float time;
+  float base;
+};
+
+G_DEFINE_TYPE (GtkShadertoy, gtk_shadertoy, GTK_TYPE_WIDGET);
+
+static void
+gtk_shadertoy_snapshot (GtkWidget   *widget,
+                        GtkSnapshot *snapshot)
+{
+  GtkShadertoy *self = GTK_SHADERTOY (widget);
+  GtkAllocation alloc;
+  graphene_rect_t bounds;
+  int offset_x, offset_y;
+  float time = (float)(g_get_monotonic_time () - self->starttime)/1000000.0;
+  GskRenderNode *node, *child1, *child2;
+  GtkAllocation child_alloc;
+  GtkSnapshot *child_snapshot;
+
+
+  if (!self->fragment_shader)
+    {
+       GTK_WIDGET_CLASS (gtk_shadertoy_parent_class)->snapshot (widget, snapshot);
+       return;
+    }
+
+  gtk_snapshot_get_offset (snapshot, &offset_x, &offset_y);
+  gtk_widget_get_allocation (widget, &alloc);
+
+  bounds.origin.x = offset_x;
+  bounds.origin.y = offset_y;
+  bounds.size.width = alloc.width;
+  bounds.size.height = alloc.height;
+
+  gtk_widget_get_allocation (self->child1, &child_alloc);
+  child_snapshot = gtk_snapshot_new (snapshot, "shadertoy child1");
+  gtk_snapshot_offset (child_snapshot, offset_x + child_alloc.x, offset_y + child_alloc.y);
+  gtk_widget_snapshot (self->child1, child_snapshot);
+  gtk_snapshot_offset (child_snapshot, - offset_x - child_alloc.x, - offset_y - child_alloc.y);
+  child1 = gtk_snapshot_free (child_snapshot);
+
+  gtk_widget_get_allocation (self->child2, &child_alloc);
+  child_snapshot = gtk_snapshot_new (snapshot, "shadertoy child2");
+  gtk_snapshot_offset (child_snapshot, offset_x + child_alloc.x, offset_y + child_alloc.y);
+  gtk_widget_snapshot (self->child2, child_snapshot);
+  gtk_snapshot_offset (child_snapshot, - offset_x - child_alloc.x, - offset_y - child_alloc.y);
+  child2 = gtk_snapshot_free (child_snapshot);
+
+  node = gsk_pixel_shader_node_new (&bounds,
+                                    child1, child2,
+                                    self->fragment_shader,
+                                    time);
+  gsk_render_node_set_name (node, "shader");
+  gtk_snapshot_append_node (snapshot, node);
+  gsk_render_node_unref (child1);
+  gsk_render_node_unref (child2);
+}
+
+
+static void
+gtk_shadertoy_finalize (GObject *object)
+{
+  GtkShadertoy *self = GTK_SHADERTOY (object);
+
+  if (self->fragment_shader)
+    g_bytes_unref (self->fragment_shader);
+
+  G_OBJECT_CLASS (gtk_shadertoy_parent_class)->finalize (object);
+}
+
+
+static void
+gtk_shadertoy_get_property (GObject    *object,
+                            guint       prop_id,
+                            GValue     *value,
+                            GParamSpec *pspec)
+{
+  GtkShadertoy *self = GTK_SHADERTOY (object);
+
+  switch (prop_id)
+    {
+    case PROP_TIME:
+      g_value_set_float (value, self->time);
+      break;
+
+    case PROP_RUNNING:
+      g_value_set_boolean (value, self->tick != 0);
+      break;
+
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+    }
+}
+
+static void gtk_shadertoy_set_running (GtkShadertoy *self,
+                                       gboolean      running);
+static void gtk_shadertoy_reset_time (GtkShadertoy *self);
+
+static void
+gtk_shadertoy_set_property (GObject      *object,
+                            guint         prop_id,
+                            const GValue *value,
+                            GParamSpec   *pspec)
+{
+  GtkShadertoy *self = GTK_SHADERTOY (object);
+
+  switch (prop_id)
+    {
+    case PROP_RUNNING:
+      gtk_shadertoy_set_running (self, g_value_get_boolean (value));
+      break;
+
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+    }
+}
+
+static void
+gtk_shadertoy_measure (GtkWidget      *widget,
+                   GtkOrientation  orientation,
+                   int             for_size,
+                   int            *minimum,
+                   int            *natural,
+                   int            *minimum_baseline,
+                   int            *natural_baseline)
+{
+  GtkShadertoy *self = GTK_SHADERTOY (widget);
+  gint child1_min, child1_nat;
+  gint child2_min, child2_nat;
+
+  *minimum = 0;
+  *natural = 0;
+
+   gtk_widget_measure (self->child1, orientation, -1, &child1_min, &child1_nat, NULL, NULL);
+   gtk_widget_measure (self->child2, orientation, -1, &child2_min, &child2_nat, NULL, NULL);
+
+   if (orientation == GTK_ORIENTATION_HORIZONTAL)
+     {
+       *minimum = MAX (*minimum, MAX (child1_min, child2_min));
+       *natural = MAX (*natural, MAX (child1_nat, child2_nat));
+     }
+   else /* VERTICAL */
+     {
+       *minimum = MAX (*minimum, MAX (child1_min, child2_min));
+       *natural = MAX (*natural, MAX (child1_nat, child2_nat));
+     }
+}
+
+static void
+gtk_shadertoy_size_allocate (GtkWidget           *widget,
+                         const GtkAllocation *allocation,
+                         int                  baseline,
+                         GtkAllocation       *out_clip)
+{
+  GtkShadertoy *self = GTK_SHADERTOY (widget);
+  GtkAllocation child_allocation;
+  GtkRequisition child_requisition;
+
+  gtk_widget_get_preferred_size (self->child1, &child_requisition, NULL);
+  gtk_widget_get_preferred_size (self->child2, &child_requisition, NULL);
+  child_allocation.x = 0;
+  child_allocation.y = 0;
+  child_allocation.width = allocation->width;
+  child_allocation.height = allocation->height;
+
+  gtk_widget_size_allocate (self->child1, &child_allocation, -1, out_clip);
+  gtk_widget_size_allocate (self->child2, &child_allocation, -1, out_clip);
+}
+
+static void
+gtk_shadertoy_class_init (GtkShadertoyClass *klass)
+{
+  GParamSpec *pspec;
+
+  GObjectClass *object_class = G_OBJECT_CLASS (klass);
+  GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
+
+  object_class->finalize = gtk_shadertoy_finalize;
+  object_class->get_property = gtk_shadertoy_get_property;
+  object_class->set_property = gtk_shadertoy_set_property;
+  widget_class->snapshot = gtk_shadertoy_snapshot;
+  widget_class->measure = gtk_shadertoy_measure;
+  widget_class->size_allocate = gtk_shadertoy_size_allocate;
+
+  pspec = g_param_spec_float ("time", NULL, NULL,
+                              0.0, G_MAXFLOAT, 0.0,
+                              G_PARAM_READABLE);
+  g_object_class_install_property (object_class, PROP_TIME, pspec);
+
+  pspec = g_param_spec_boolean ("running", NULL, NULL,
+                                FALSE,
+                                G_PARAM_READWRITE);
+  g_object_class_install_property (object_class, PROP_RUNNING, pspec);
+}
+
+static void
+gtk_shadertoy_init (GtkShadertoy *self)
+{
+  GdkPixbuf *pixbuf;
+
+  gtk_widget_set_has_window (GTK_WIDGET (self), FALSE);
+
+  self->child2 = gtk_entry_new ();
+  gtk_entry_set_text (GTK_ENTRY (self->child2), "Stat rosa prisina nomine, nomina nuda tenemus");
+  gtk_widget_set_parent (self->child2, GTK_WIDGET (self));
+
+  pixbuf = gdk_pixbuf_new_from_file_at_scale ("tests/portland-rose.jpg", 400, 400, TRUE, NULL);
+  self->child1 = gtk_image_new_from_pixbuf (pixbuf);
+  g_object_unref (pixbuf);
+  gtk_widget_set_parent (self->child1, GTK_WIDGET (self));
+}
+
+static GtkWidget *
+gtk_shadertoy_new (void)
+{
+  return g_object_new (gtk_shadertoy_get_type (), NULL);
+}
+
+static void
+gtk_shadertoy_set_fragment_shader (GtkShadertoy *self,
+                                   GBytes       *shader)
+{
+  if (self->fragment_shader)
+    g_bytes_unref (self->fragment_shader);
+  self->fragment_shader = shader;
+  if (self->fragment_shader)
+    g_bytes_ref (self->fragment_shader);
+}
+
+static gboolean
+tick_cb (GtkWidget *widget, GdkFrameClock *clock, gpointer data)
+{
+  GtkShadertoy *self = GTK_SHADERTOY (widget);
+
+  self->time = self->base + (g_get_monotonic_time () - self->starttime) / 1000000.0;
+  g_object_notify (G_OBJECT (widget), "time");
+
+  gtk_widget_queue_draw (widget);
+  return G_SOURCE_CONTINUE;
+}
+
+static void
+gtk_shadertoy_set_running (GtkShadertoy *self,
+                           gboolean      running)
+{
+  if (running && self->tick == 0)
+    {
+      self->tick = gtk_widget_add_tick_callback (GTK_WIDGET (self), tick_cb, NULL, NULL);
+      self->starttime = g_get_monotonic_time ();
+      self->time = 0;
+    }
+  else if (!running && self->tick != 0)
+    {
+      gtk_widget_remove_tick_callback (GTK_WIDGET (self), self->tick);
+      self->base += (g_get_monotonic_time () - self->starttime) / 1000000.0;
+      self->tick = 0;
+    }
+  else
+    return;
+
+  gtk_widget_queue_draw (GTK_WIDGET (self));
+  g_object_notify (G_OBJECT (self), "running");
+}
+
+static gboolean
+gtk_shadertoy_is_running (GtkShadertoy *self)
+{
+  return self->tick != 0;
+}
+
+static void
+gtk_shadertoy_reset_time (GtkShadertoy *self)
+{
+  self->base = 0;
+  self->time = 0;
+  self->starttime = g_get_monotonic_time ();
+
+  gtk_widget_queue_draw (GTK_WIDGET (self));
+  g_object_notify (G_OBJECT (self), "time");
+}
+
+static GtkWidget *window = NULL;
+static GtkWidget *textview;
+static GtkWidget *toy;
+
+static const char *frag_text =
+"#version 420 core\n"
+"\n"
+"layout(location = 0) in vec2 iPos;\n"
+"layout(location = 1) in vec2 iTexCoord1;\n"
+"layout(location = 2) in vec2 iTexCoord2;\n"
+"layout(location = 3) in float iTime;\n"
+"layout(location = 4) in vec2 iResolution;\n"
+"\n"
+"layout(set = 0, binding = 0) uniform sampler2D iTexture1;\n"
+"layout(set = 1, binding = 0) uniform sampler2D iTexture2;\n"
+"\n"
+"layout(location = 0) out vec4 color;\n"
+"\n"
+"void\n"
+"mainImage (out vec4 fragColor, in vec2 fragCoord)\n"
+"{\n"
+"  vec2 uv = fragCoord.xy / iResolution.xy;\n"
+"  fragColor = texture(iTexture1,iTexCoord1*(0.3*sin(iTime) + 0.5));\n"
+"}\n"
+"\n"
+"void main()\n"
+"{\n"
+"  mainImage (color, iPos);\n"
+"}";
+
+static gboolean
+update_shader (void)
+{
+  GError *error = NULL;
+  int status;
+  char *spv;
+  gsize length;
+  GBytes *shader;
+  GtkTextBuffer *buffer;
+  GtkTextIter start, end;
+  char *text;
+
+  buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (textview));
+  gtk_text_buffer_get_bounds (buffer, &start, &end);
+  text = gtk_text_buffer_get_text (buffer, &start, &end, FALSE);
+
+  if (!g_file_set_contents ("gtk4-demo-shader.frag", text, -1, &error))
+    {
+      g_print ("Failed to write shader file: %s\n", error->message);
+      g_error_free (error);
+      g_free (text);
+      return FALSE;
+    }
+
+  g_free (text);
+
+  if (!g_spawn_command_line_sync ("glslc -fshader-stage=fragment -DCLIP_NONE gtk4-demo-shader.frag -o 
gtk4-demo-shader.frag.spv", NULL, NULL, &status, &error))
+    {
+      g_print ("Running glslc failed (%d): %s\n", status, error->message);
+      g_error_free (error);
+      return FALSE;
+    }
+
+  if (!g_file_get_contents ("gtk4-demo-shader.frag.spv", &spv, &length, &error))
+    {
+      g_print ("Reading compiled shader failed: %s\n", error->message);
+      g_error_free (error);
+      return FALSE;
+    }
+
+  shader = g_bytes_new_take (spv, length);
+  gtk_shadertoy_set_fragment_shader (GTK_SHADERTOY (toy), shader);
+  g_bytes_unref (shader);
+
+  return TRUE;
+}
+
+static void
+play_cb (GtkButton *button)
+{
+  if (gtk_shadertoy_is_running (GTK_SHADERTOY (toy)))
+    {
+      gtk_shadertoy_set_running (GTK_SHADERTOY (toy), FALSE);
+      gtk_button_set_icon_name (button, "media-playback-start-symbolic");
+    }
+  else
+    {
+      if (update_shader ())
+        {
+          gtk_shadertoy_set_running (GTK_SHADERTOY (toy), TRUE);
+          gtk_button_set_icon_name (button, "media-playback-stop-symbolic");
+        }
+    }
+}
+
+static void
+rewind_cb (GtkButton *button)
+{
+  gtk_shadertoy_reset_time (GTK_SHADERTOY (toy));
+}
+
+static gboolean
+format_time (GBinding     *binding,
+             const GValue *from_value,
+             GValue       *to_value,
+             gpointer      data)
+{
+  char buffer[256];
+
+  g_snprintf (buffer, sizeof (buffer), "%.2f", g_value_get_float (from_value));
+  g_value_set_string (to_value, buffer);
+
+  return TRUE;
+}
+
+static void
+save_cb (GtkButton *button)
+{
+  GtkFileChooserNative *fs;
+  int response;
+
+  fs = gtk_file_chooser_native_new ("Save Shader",
+                                    GTK_WINDOW (window),
+                                    GTK_FILE_CHOOSER_ACTION_SAVE,
+                                    "Save", "Cancel");
+  gtk_native_dialog_set_modal (GTK_NATIVE_DIALOG (fs), TRUE);
+
+  response = gtk_native_dialog_run (GTK_NATIVE_DIALOG (fs));
+
+  if (response == GTK_RESPONSE_ACCEPT)
+    {
+      GtkTextBuffer *buffer;
+      GtkTextIter start, end;
+      char *text;
+      char *filename;
+      GError *error = NULL;
+
+      filename = gtk_file_chooser_get_filename (GTK_FILE_CHOOSER (fs));
+
+      buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (textview));
+      gtk_text_buffer_get_bounds (buffer, &start, &end);
+      text = gtk_text_buffer_get_text (buffer, &start, &end, FALSE);
+
+      if (!g_file_set_contents (filename, text, -1, &error))
+        {
+          g_print ("Failed to save :-( %s\n", error->message);
+          g_error_free (error);
+        }
+
+      g_free (filename);
+      g_free (text);
+    }
+
+  g_object_unref (fs);
+}
+
+static void
+load_cb (GtkButton *button)
+{
+  GtkFileChooserNative *fs;
+  int response;
+
+  fs = gtk_file_chooser_native_new ("Load Shader",
+                                    GTK_WINDOW (window),
+                                    GTK_FILE_CHOOSER_ACTION_OPEN,
+                                    "Open", "Cancel");
+  gtk_native_dialog_set_modal (GTK_NATIVE_DIALOG (fs), TRUE);
+
+  response = gtk_native_dialog_run (GTK_NATIVE_DIALOG (fs));
+
+  if (response == GTK_RESPONSE_ACCEPT)
+    {
+      GtkTextBuffer *buffer;
+      char *text;
+      gsize len;
+      char *filename;
+      GError *error = NULL;
+
+      filename = gtk_file_chooser_get_filename (GTK_FILE_CHOOSER (fs));
+
+      if (!g_file_get_contents (filename, &text, &len, &error))
+        {
+          g_print ("Failed to load :-( %s\n", error->message);
+          g_error_free (error);
+        }
+      else
+        {
+          buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (textview));
+          gtk_text_buffer_set_text (buffer, text, len);
+          g_free (text);
+        }
+
+      g_free (filename);
+    }
+
+  g_object_unref (fs);
+}
+
+GtkWidget *
+do_shadertoy (GtkWidget *do_widget)
+{
+
+  if (!window)
+    {
+      GtkBuilder *builder;
+      GtkWidget *content;
+      GtkWidget *rewind;
+      GtkWidget *play;
+      GtkWidget *time;
+      GtkWidget *load;
+      GtkWidget *save;
+      GtkTextBuffer *buffer;
+
+      builder = gtk_builder_new_from_resource ("/shadertoy/shadertoy.ui");
+
+      window = GTK_WIDGET (gtk_builder_get_object (builder, "window"));
+      content = GTK_WIDGET (gtk_builder_get_object (builder, "content"));
+      textview = GTK_WIDGET (gtk_builder_get_object (builder, "text"));
+      rewind = GTK_WIDGET (gtk_builder_get_object (builder, "rewind"));
+      play = GTK_WIDGET (gtk_builder_get_object (builder, "play"));
+      time = GTK_WIDGET (gtk_builder_get_object (builder, "time"));
+      load = GTK_WIDGET (gtk_builder_get_object (builder, "load"));
+      save = GTK_WIDGET (gtk_builder_get_object (builder, "save"));
+
+      gtk_window_set_screen (GTK_WINDOW (window),
+                             gtk_widget_get_screen (do_widget));
+      g_signal_connect (window, "destroy",
+                        G_CALLBACK (gtk_widget_destroyed), &window);
+
+      toy = gtk_shadertoy_new ();
+      buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (textview));
+      gtk_text_buffer_set_text (buffer, frag_text, -1);
+
+      gtk_widget_set_hexpand (toy, TRUE);
+      gtk_widget_set_vexpand (toy, TRUE);
+      gtk_container_add (GTK_CONTAINER (content), toy);
+
+      g_signal_connect (play, "clicked", G_CALLBACK (play_cb), NULL);
+      g_signal_connect (rewind, "clicked", G_CALLBACK (rewind_cb), NULL);
+      g_signal_connect (load, "clicked", G_CALLBACK (load_cb), NULL);
+      g_signal_connect (save, "clicked", G_CALLBACK (save_cb), NULL);
+      g_object_bind_property_full (toy, "time",
+                                   time, "label",
+                                   G_BINDING_SYNC_CREATE,
+                                   format_time,
+                                   NULL,
+                                   NULL,
+                                   NULL);
+
+      g_object_unref (builder);
+    }
+
+  if (!gtk_widget_get_visible (window))
+    gtk_widget_show (window);
+  else
+    gtk_widget_destroy (window);
+
+  return window;
+}
diff --git a/demos/gtk-demo/shadertoy.ui b/demos/gtk-demo/shadertoy.ui
new file mode 100644
index 0000000..2109995
--- /dev/null
+++ b/demos/gtk-demo/shadertoy.ui
@@ -0,0 +1,64 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<interface>
+  <!-- interface-requires gtk+ 3.6 -->
+  <object class="GtkWindow" id="window">
+    <property name="default-width">640</property>
+    <property name="default-height">480</property>
+    <child type="titlebar">
+      <object class="GtkHeaderBar">
+        <property name="title" translatable="yes">Shadertoy</property>
+        <property name="show-close-button">1</property>
+        <child>
+          <object class="GtkButton" id="rewind">
+            <property name="icon-name">media-skip-backward-symbolic</property>
+          </object>
+        </child>
+        <child>
+          <object class="GtkButton" id="play">
+            <property name="icon-name">media-playback-start-symbolic</property>
+          </object>
+        </child>
+        <child>
+          <object class="GtkLabel" id="time">
+            <property name="margin-start">20</property>
+          </object>
+        </child>
+        <child>
+          <object class="GtkButton" id="save">
+            <property name="icon-name">document-save-as-symbolic</property>
+          </object>
+          <packing>
+            <property name="pack-type">end</property>
+          </packing>
+        </child>
+        <child>
+          <object class="GtkButton" id="load">
+            <property name="icon-name">document-open-symbolic</property>
+          </object>
+          <packing>
+            <property name="pack-type">end</property>
+          </packing>
+        </child>
+      </object>
+    </child>
+    <child>
+      <object class="GtkPaned" id="content">
+        <property name="orientation">vertical</property>
+        <property name="position">240</property>
+        <child>
+          <object class="GtkScrolledWindow">
+            <property name="expand">1</property>
+            <child>
+              <object class="GtkTextView" id="text">
+                <property name="top-margin">10</property>
+                <property name="bottom-margin">10</property>
+                <property name="left-margin">10</property>
+                <property name="right-margin">10</property>
+              </object>
+            </child>
+          </object>
+        </child>
+      </object>
+    </child>
+  </object>
+</interface>


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