Make the Stage redraw cycle part of GTK+
authorEmmanuele Bassi <ebassi@linux.intel.com>
Sun, 8 Feb 2009 23:57:05 +0000 (23:57 +0000)
committerEmmanuele Bassi <ebassi@linux.intel.com>
Mon, 9 Feb 2009 00:02:35 +0000 (00:02 +0000)
Right now, the Stage is redrawn every time it requires, in parallel
to the GTK+ paint cycle. This generates a certain degree of collision,
as the embedding widget might request a redraw while a Stage redraw
is in progress, thus leading to a skipped frame.

The embedding toolkit should "drive" the Clutter paint cycle, instead.

The ClutterStage::queue-redraw signal was added exactly for this use
case. The GtkClutterEmbed widget should intercept every queued redraw
of the embedded stage, stop it and queue a redraw on the widget itself,
thus making it play along with the rest of the GTK+ paint cycle
machinery. The stage is only painted after the widget receives an
expose event, ensuring that the Clutter scenegraph is repainted only
when needed.

clutter-gtk/gtk-clutter-embed.c

index 87374e5..3019a87 100644 (file)
@@ -65,6 +65,8 @@ G_DEFINE_TYPE (GtkClutterEmbed, gtk_clutter_embed, GTK_TYPE_WIDGET);
 struct _GtkClutterEmbedPrivate
 {
   ClutterActor *stage;
+
+  guint queue_redraw_id;
 };
 
 static void
@@ -87,10 +89,33 @@ gtk_clutter_embed_send_configure (GtkClutterEmbed *embed)
 }
 
 static void
+on_stage_queue_redraw (ClutterStage *stage,
+                       gpointer      user_data)
+{
+  GtkWidget *embed = user_data;
+
+  /* we stop the emission of the Stage::queue-redraw signal to prevent
+   * the default handler from running; then we queue a redraw on the
+   * GtkClutterEmbed widget which will cause an expose event to be
+   * emitted. the Stage is redrawn in the expose event handler, thus
+   * "slaving" the Clutter redraw cycle to GTK+'s own
+   */
+  g_signal_stop_emission_by_name (stage, "queue-redraw");
+
+  gtk_widget_queue_draw (embed);
+}
+
+static void
 gtk_clutter_embed_dispose (GObject *gobject)
 {
   GtkClutterEmbedPrivate *priv = GTK_CLUTTER_EMBED (gobject)->priv;
 
+  if (priv->queue_redraw_id)
+    {
+      g_signal_handler_disconnect (priv->stage, priv->queue_redraw_id);
+      priv->queue_redraw_id = 0;
+    }
+
   if (priv->stage)
     {
       clutter_actor_destroy (priv->stage);
@@ -206,6 +231,18 @@ gtk_clutter_embed_size_allocate (GtkWidget     *widget,
 }
 
 static gboolean
+gtk_clutter_embed_expose_event (GtkWidget *widget,
+                                GdkEventExpose *event)
+{
+  GtkClutterEmbedPrivate *priv = GTK_CLUTTER_EMBED (widget)->priv;
+
+  /* force a redraw on expose */
+  clutter_redraw (CLUTTER_STAGE (priv->stage));
+
+  return FALSE;
+}
+
+static gboolean
 gtk_clutter_embed_motion_notify_event (GtkWidget      *widget,
                                        GdkEventMotion *event)
 {
@@ -300,18 +337,6 @@ gtk_clutter_embed_key_event (GtkWidget   *widget,
 }
 
 static gboolean
-gtk_clutter_embed_expose_event (GtkWidget      *widget,
-                                GdkEventExpose *event)
-{
-  GtkClutterEmbedPrivate *priv = GTK_CLUTTER_EMBED (widget)->priv;
-
-  if (CLUTTER_ACTOR_IS_VISIBLE (priv->stage))
-    clutter_actor_queue_redraw (priv->stage);
-
-  return FALSE;
-}
-
-static gboolean
 gtk_clutter_embed_map_event (GtkWidget  *widget,
                              GdkEventAny *event)
 {
@@ -389,6 +414,7 @@ gtk_clutter_embed_class_init (GtkClutterEmbedClass *klass)
   widget_class->realize = gtk_clutter_embed_realize;
   widget_class->show = gtk_clutter_embed_show;
   widget_class->hide = gtk_clutter_embed_hide;
+  widget_class->expose_event = gtk_clutter_embed_expose_event;
   widget_class->button_press_event = gtk_clutter_embed_button_event;
   widget_class->button_release_event = gtk_clutter_embed_button_event;
   widget_class->key_press_event = gtk_clutter_embed_key_event;
@@ -421,6 +447,15 @@ gtk_clutter_embed_init (GtkClutterEmbed *embed)
   /* we must realize the stage to get it ready for embedding */
   clutter_actor_realize (priv->stage);
 
+  /* intercept the queue-redraw signal of the stage to know when
+   * Clutter-side requests a redraw; this way we can also request
+   * a redraw GTK-side
+   */
+  priv->queue_redraw_id =
+    g_signal_connect (priv->stage,
+                      "queue-redraw", G_CALLBACK (on_stage_queue_redraw),
+                      embed);
+
 #ifdef HAVE_CLUTTER_GTK_X11
   {
     const XVisualInfo *xvinfo;