widget: Cache the render node
authorBenjamin Otte <otte@redhat.com>
Tue, 20 Mar 2018 17:55:33 +0000 (18:55 +0100)
committerBenjamin Otte <otte@redhat.com>
Thu, 5 Apr 2018 12:56:38 +0000 (14:56 +0200)
This requires a bunch of refactorings:

1. Don't pass the current clip region to gtk_widget_snapshot()
   so we don't create full widget contents
3. Have a widget->priv->draw_needed that we invalidate on every
   queue_draw() call and set on every snapshot()
2. In queue_draw(), walk the widget chain to invalidate the
   render nodes of all parents

gtk/gtksnapshot.c
gtk/gtksnapshotprivate.h
gtk/gtkwidget.c
gtk/gtkwidgetprivate.h

index 828f8287990bee43a2046c57710701b5ffe5251c..05b07aa5f7c5d5f90ad5523711deefe803c4573a 100644 (file)
@@ -81,12 +81,28 @@ gtk_snapshot_init (GtkSnapshot *self)
 {
 }
 
+static cairo_region_t *
+create_offset_region (const cairo_region_t *region,
+                      int                   dx,
+                      int                   dy)
+{
+  cairo_region_t *result;
+
+  if (region == NULL)
+    return NULL;
+
+  result = cairo_region_copy (region);
+  cairo_region_translate (result, -dx, -dy);
+
+  return result;
+}
+
 static GskRenderNode *
-gtk_snapshot_collect_default (GtkSnapshot      *snapshot,
-                              GtkSnapshotState *state,
-                              GskRenderNode **nodes,
-                              guint           n_nodes,
-                              const char     *name)
+gtk_snapshot_collect_default (GtkSnapshot       *snapshot,
+                              GtkSnapshotState  *state,
+                              GskRenderNode    **nodes,
+                              guint              n_nodes,
+                              const char        *name)
 {
   GskRenderNode *node;
 
@@ -156,6 +172,29 @@ gtk_snapshot_state_clear (GtkSnapshotState *state)
   g_clear_pointer (&state->name, g_free);
 }
 
+static GtkSnapshot *
+gtk_snapshot_new_internal (gboolean        record_names,
+                           cairo_region_t *clip,
+                           char           *name)
+{
+  GtkSnapshot *snapshot;
+
+  snapshot = g_object_new (GTK_TYPE_SNAPSHOT, NULL);
+
+  snapshot->record_names = record_names;
+  snapshot->state_stack = g_array_new (FALSE, TRUE, sizeof (GtkSnapshotState));
+  g_array_set_clear_func (snapshot->state_stack, (GDestroyNotify)gtk_snapshot_state_clear);
+  snapshot->nodes = g_ptr_array_new_with_free_func ((GDestroyNotify)gsk_render_node_unref);
+
+  gtk_snapshot_push_state (snapshot,
+                           name,
+                           clip,
+                           0, 0,
+                           gtk_snapshot_collect_default);
+
+  return snapshot;
+}
+
 /**
  * gtk_snapshot_new:
  * @record_names: whether to keep node names (for debugging purposes)
@@ -173,7 +212,6 @@ gtk_snapshot_new (gboolean              record_names,
                   const char           *name,
                   ...)
 {
-  GtkSnapshot *snapshot;
   char *str;
 
   if (name && record_names)
@@ -187,18 +225,43 @@ gtk_snapshot_new (gboolean              record_names,
   else
     str = NULL;
 
-  snapshot = g_object_new (GTK_TYPE_SNAPSHOT, NULL);
+  return gtk_snapshot_new_internal (record_names,
+                                    (cairo_region_t *) clip,
+                                    str);
+}
 
-  snapshot->record_names = record_names;
-  snapshot->state_stack = g_array_new (FALSE, TRUE, sizeof (GtkSnapshotState));
-  g_array_set_clear_func (snapshot->state_stack, (GDestroyNotify)gtk_snapshot_state_clear);
-  snapshot->nodes = g_ptr_array_new_with_free_func ((GDestroyNotify)gsk_render_node_unref);
+GtkSnapshot *
+gtk_snapshot_new_child (GtkSnapshot *parent,
+                        const char  *name,
+                        ...)
+{
+  GtkSnapshotState *parent_state;
+  GtkSnapshot *snapshot;
+  cairo_region_t *clip;
+  char *str;
 
-  gtk_snapshot_push_state (snapshot,
-                           str,
-                           (cairo_region_t *) clip,
-                           0, 0,
-                           gtk_snapshot_collect_default);
+  if (name && parent->record_names)
+    {
+      va_list args;
+
+      va_start (args, name);
+      str = g_strdup_vprintf (name, args);
+      va_end (args);
+    }
+  else
+    str = NULL;
+
+  parent_state = gtk_snapshot_get_current_state (parent);
+  clip = create_offset_region (parent_state->clip_region,
+                               parent_state->translate_x,
+                               parent_state->translate_y);
+
+  snapshot = gtk_snapshot_new_internal (parent->record_names,
+                                        clip,
+                                        str);
+
+  if (clip)
+    cairo_region_destroy (clip);
 
   return snapshot;
 }
@@ -401,35 +464,10 @@ gtk_snapshot_push_offset (GtkSnapshot *snapshot)
   else
     str = NULL;
 
-  if (state->clip_region)
-    {
-      offset_clip = cairo_region_copy (state->clip_region);
-      if (state->translate_x != floor (state->translate_x))
-        {
-          cairo_region_translate (offset_clip, -1, 0);
-          cairo_region_union (offset_clip, state->clip_region);
-          if (state->translate_y != floor (state->translate_y))
-            {
-              cairo_region_t *tmp = cairo_region_copy (offset_clip);
-              cairo_region_translate (offset_clip, 0, -1);
-              cairo_region_union (offset_clip, tmp);
-              cairo_region_destroy (tmp);
-            }
-        }
-      else if (state->translate_y != floor (state->translate_y))
-        {
-          cairo_region_translate (offset_clip, 0, -1);
-          cairo_region_union (offset_clip, state->clip_region);
-        }
-      cairo_region_translate (offset_clip, 
-                              - floor (state->translate_x),
-                              - floor (state->translate_y));
+  offset_clip = create_offset_region (state->clip_region,
+                                      state->translate_x,
+                                      state->translate_y);
 
-    }
-  else
-    {
-      offset_clip = NULL;
-    }
   state = gtk_snapshot_push_state (snapshot,
                                    str,
                                    offset_clip,
index b1501f6307a12bce06550624d209a6af1951e17b..3b1a145ae4b25c790e576ff825d3cc5ff8c1df48 100644 (file)
@@ -97,6 +97,9 @@ struct _GtkSnapshotClass {
   GObjectClass           parent_class; /* it's really GdkSnapshotClass, but don't tell anyone! */
 };
 
+GtkSnapshot *   gtk_snapshot_new_child                  (GtkSnapshot            *parent,
+                                                         const char             *name,
+                                                         ...) G_GNUC_PRINTF (2, 3);
 void            gtk_snapshot_append_node_internal       (GtkSnapshot            *snapshot,
                                                          GskRenderNode          *node);
 
index fec5fd8c487e1a134b8fa10b271c0cd404999277..c6f895c64cc516416d57782c7f5648daa847d31a 100644 (file)
@@ -60,7 +60,7 @@
 #include "gtkselection.h"
 #include "gtksettingsprivate.h"
 #include "gtksizegroup-private.h"
-#include "gtksnapshot.h"
+#include "gtksnapshotprivate.h"
 #include "gtkstylecontextprivate.h"
 #include "gtktooltipprivate.h"
 #include "gtktypebuiltins.h"
@@ -3011,6 +3011,7 @@ gtk_widget_init (GTypeInstance *instance, gpointer g_class)
   priv->sensitive = TRUE;
   priv->alloc_needed = TRUE;
   priv->alloc_needed_on_child = TRUE;
+  priv->draw_needed = TRUE;
   priv->focus_on_click = TRUE;
 #ifdef G_ENABLE_DEBUG
   priv->highlight_resize = FALSE;
@@ -3570,8 +3571,7 @@ gtk_widget_unmap (GtkWidget *widget)
       g_object_ref (widget);
       gtk_widget_push_verify_invariants (widget);
 
-      if (!_gtk_widget_get_has_surface (widget))
-       gtk_widget_queue_draw (widget);
+      gtk_widget_queue_draw (widget);
       _gtk_tooltip_hide (widget);
 
       g_signal_emit (widget, widget_signals[UNMAP], 0);
@@ -4020,6 +4020,21 @@ gtk_widget_queue_draw_area (GtkWidget *widget,
   cairo_region_destroy (region);
 }
 
+static void
+gtk_widget_real_queue_draw (GtkWidget *widget)
+{
+  for (; widget; widget = _gtk_widget_get_parent (widget))
+    {
+      GtkWidgetPrivate *priv = gtk_widget_get_instance_private (widget);
+
+      if (priv->draw_needed)
+        break;
+
+      priv->draw_needed = TRUE;
+      g_clear_pointer (&priv->render_node, gsk_render_node_unref);
+    }
+}
+
 /**
  * gtk_widget_queue_draw:
  * @widget: a #GtkWidget
@@ -4035,6 +4050,8 @@ gtk_widget_queue_draw (GtkWidget *widget)
 
   g_return_if_fail (GTK_IS_WIDGET (widget));
 
+  gtk_widget_real_queue_draw (widget);
+
   parent = _gtk_widget_get_parent (widget);
   rect = &widget->priv->clip;
 
@@ -4288,6 +4305,8 @@ gtk_widget_queue_draw_region (GtkWidget            *widget,
   if (!_gtk_widget_get_mapped (widget))
     return;
 
+  gtk_widget_real_queue_draw (widget);
+
   if (!_gtk_widget_get_parent (widget))
     {
       g_assert (_gtk_widget_get_has_surface (widget));
@@ -4627,6 +4646,7 @@ check_clip:
            * are relative to */
           gtk_widget_queue_draw_region (parent ? parent : widget, invalidate);
           cairo_region_destroy (invalidate);
+          gtk_widget_real_queue_draw (widget);
         }
     }
 
@@ -13811,49 +13831,27 @@ gtk_widget_maybe_add_debug_render_nodes (GtkWidget             *widget,
 #endif
 }
 
-void
-gtk_widget_snapshot (GtkWidget   *widget,
-                     GtkSnapshot *snapshot)
+static GskRenderNode *
+gtk_widget_create_render_node (GtkWidget   *widget,
+                               GtkSnapshot *parent_snapshot)
 {
   GtkWidgetClass *klass = GTK_WIDGET_GET_CLASS (widget);
-  GtkWidgetPrivate *priv;
+  GtkWidgetPrivate *priv = widget->priv;
   GtkCssValue *filter_value;
   RenderMode mode;
   double opacity;
-  cairo_rectangle_int_t offset_clip;
   GtkCssStyle *style;
   GtkAllocation allocation;
   GtkBorder margin, border, padding;
+  GtkSnapshot *snapshot;
 
-  if (!_gtk_widget_is_drawable (widget))
-    return;
-
-  if (_gtk_widget_get_alloc_needed (widget))
-    {
-      g_warning ("Trying to snapshot %s %p without a current allocation", G_OBJECT_TYPE_NAME (widget), widget);
-      return;
-    }
-
-  priv = widget->priv;
-  offset_clip = priv->clip;
-  offset_clip.x -= priv->allocation.x;
-  offset_clip.y -= priv->allocation.y;
-
-  if (gtk_snapshot_clips_rect (snapshot, &offset_clip))
-    return;
-
-  opacity = widget->priv->alpha / 255.0;
-  if (opacity <= 0.0)
-    return;
+  snapshot = gtk_snapshot_new_child (parent_snapshot, "%s<%p>", gtk_widget_get_name (widget), widget);
 
   /* Compatibility mode: if the widget does not have a render node, we draw
    * using gtk_widget_draw() on a temporary node
    */
   mode = get_render_mode (klass);
 
-  if (GTK_DEBUG_CHECK (SNAPSHOT))
-    gtk_snapshot_push (snapshot, TRUE, "%s<%p>", gtk_widget_get_name (widget), widget);
-
   filter_value = _gtk_style_context_peek_property (_gtk_widget_get_style_context (widget), GTK_CSS_PROPERTY_FILTER);
   gtk_css_filter_value_push_snapshot (filter_value, snapshot);
 
@@ -13861,6 +13859,7 @@ gtk_widget_snapshot (GtkWidget   *widget,
   get_box_margin (style, &margin);
   get_box_border (style, &border);
   get_box_padding (style, &padding);
+  opacity = widget->priv->alpha / 255.0;
 
   _gtk_widget_get_allocation (widget, &allocation);
 
@@ -13868,6 +13867,11 @@ gtk_widget_snapshot (GtkWidget   *widget,
     {
       cairo_t *cr;
       graphene_rect_t bounds;
+      cairo_rectangle_int_t offset_clip;
+
+      offset_clip = priv->clip;
+      offset_clip.x -= priv->allocation.x;
+      offset_clip.y -= priv->allocation.y;
 
       graphene_rect_init (&bounds,
                           offset_clip.x,
@@ -13914,6 +13918,11 @@ gtk_widget_snapshot (GtkWidget   *widget,
           gboolean result;
           cairo_t *cr;
           graphene_rect_t bounds;
+          cairo_rectangle_int_t offset_clip;
+
+          offset_clip = priv->clip;
+          offset_clip.x -= priv->allocation.x;
+          offset_clip.y -= priv->allocation.y;
 
           graphene_rect_init (&bounds,
                               offset_clip.x,
@@ -13939,16 +13948,53 @@ gtk_widget_snapshot (GtkWidget   *widget,
         gtk_snapshot_pop (snapshot);
     }
 
-
   gtk_css_filter_value_pop_snapshot (filter_value, snapshot);
 
 #ifdef G_ENABLE_DEBUG
   gtk_widget_maybe_add_debug_render_nodes (widget, snapshot);
 #endif
 
+  return gtk_snapshot_free_to_node (snapshot);
+}
+
+void
+gtk_widget_snapshot (GtkWidget   *widget,
+                     GtkSnapshot *snapshot)
+{
+  GtkWidgetPrivate *priv = widget->priv;
+  cairo_rectangle_int_t offset_clip;
+  double opacity;
+
+  if (!_gtk_widget_is_drawable (widget))
+    return;
+
+  if (_gtk_widget_get_alloc_needed (widget))
+    {
+      g_warning ("Trying to snapshot %s %p without a current allocation", G_OBJECT_TYPE_NAME (widget), widget);
+      return;
+    }
+
+  priv = widget->priv;
+  offset_clip = priv->clip;
+  offset_clip.x -= priv->allocation.x;
+  offset_clip.y -= priv->allocation.y;
+
+  if (gtk_snapshot_clips_rect (snapshot, &offset_clip))
+    return;
+
+  opacity = widget->priv->alpha / 255.0;
+  if (opacity <= 0.0)
+    return;
+
+  if (priv->draw_needed)
+    {
+      g_assert (priv->render_node == NULL);
+      priv->render_node = gtk_widget_create_render_node (widget, snapshot);
+      priv->draw_needed = FALSE;
+    }
 
-  if (GTK_DEBUG_CHECK (SNAPSHOT))
-    gtk_snapshot_pop (snapshot);
+  if (priv->render_node)
+    gtk_snapshot_append_node (snapshot, priv->render_node);
 }
 
 static gboolean
@@ -13968,7 +14014,6 @@ gtk_widget_render (GtkWidget            *widget,
   GtkSnapshot *snapshot;
   GskRenderer *renderer;
   GskRenderNode *root;
-  cairo_region_t *clip;
 
   /* We only render double buffered on native windows */
   if (!gdk_surface_has_native (surface))
@@ -13979,12 +14024,10 @@ gtk_widget_render (GtkWidget            *widget,
     return;
 
   context = gsk_renderer_begin_draw_frame (renderer, region);
-  clip = gdk_drawing_context_get_clip (context);
 
   snapshot = gtk_snapshot_new (should_record_names (widget, renderer),
-                               clip,
+                               NULL,
                                "Render<%s>", G_OBJECT_TYPE_NAME (widget));
-  cairo_region_destroy (clip);
   gtk_widget_snapshot (widget, snapshot);
   root = gtk_snapshot_free_to_node (snapshot);
   if (root != NULL)
index 0c50bb2ca8eb7da6f7c8646459fc5c12be77893c..2c9286b12c3de5a6b8cfccad244356174de5a19c 100644 (file)
@@ -76,6 +76,9 @@ struct _GtkWidgetPrivate
   guint alloc_needed          : 1; /* this widget needs a size_allocate() call */
   guint alloc_needed_on_child : 1; /* 0 or more children - or this widget - need a size_allocate() call */
 
+  /* Queue-draw related flags */
+  guint draw_needed           : 1;
+
   /* Expand-related flags */
   guint need_compute_expand   : 1; /* Need to recompute computed_[hv]_expand */
   guint computed_hexpand      : 1; /* computed results (composite of child flags) */
@@ -142,7 +145,10 @@ struct _GtkWidgetPrivate
   /* The widget's requested sizes */
   SizeRequestCache requests;
 
-  /* The widget's surface or its surface window if it does
+  /* The render node we draw or %NULL if not yet created. */
+  GskRenderNode *render_node;
+
+  /* The widget's surface or its parent surface if it does
    * not have a surface. (Which will be indicated by the
    * no_surface field being set).
    */