[gtk+/wip/simple-draw4: 17/20] Add GtkPixelCache



commit 1d178b5464b0a1362e593f652a104334d8136967
Author: Alexander Larsson <alexl redhat com>
Date:   Thu May 2 16:23:42 2013 +0200

    Add GtkPixelCache
    
    GtkPixelCache is a helper utility that lets you implement
    faster scrolling of a viewport of a canvas by using an
    offscreen pixmap cache.
    
    You call _gtk_pixel_cache_draw with a callback function that
    does the drawing, and additionally you specify the size and the
    position of the viewport in the widget, and the position and size
    of the canvas wrt the viewport. The callback will be called to
    draw on an offscreen surface, and the surface will be drawn
    on the window. The next time you do the same, any already drawn
    pieces of the surface are re-used from the offscreen and need
    not be rendered again.
    
    If things inside the canvas change you need to call
    _gtk_pixel_cache_invalidate to tell the cache about this.
    
    Some other details:
    
    * The offscreen surface is generally a bit larger than
      the viewport, so scrolling a small amount can often
      be done without redrawing children.
    * If the canvas is not larger than the viewport no
      offscreen surface is used.
    
    GtkPixelCache: Make sure we always copy using SOURCE
    
    We were using OVER for the first copy (from source to group surface.
    
    GtkPixelCache: Fix x/y typos
    
    GtkPixelCache: Allow NULL for invalidate region
    
    gtkpixelcache: Use CONTENT_COLOR for solid bg windows

 gtk/Makefile.am            |    2 +
 gtk/gtkpixelcache.c        |  341 ++++++++++++++++++++++++++++++++++++++++++++
 gtk/gtkpixelcacheprivate.h |   48 ++++++
 3 files changed, 391 insertions(+), 0 deletions(-)
---
diff --git a/gtk/Makefile.am b/gtk/Makefile.am
index bfb0983..ece94aa 100644
--- a/gtk/Makefile.am
+++ b/gtk/Makefile.am
@@ -519,6 +519,7 @@ gtk_private_h_sources =             \
        gtkprintoperation-private.h \
        gtkprintutils.h         \
        gtkprivate.h            \
+       gtkpixelcacheprivate.h  \
        gtkquery.h              \
        gtkrbtree.h             \
        gtkrecentchooserdefault.h \
@@ -800,6 +801,7 @@ gtk_base_c_sources =                \
        gtkprivate.c            \
        gtkprivatetypebuiltins.c \
        gtkprogressbar.c        \
+       gtkpixelcache.c         \
        gtkradioaction.c        \
        gtkradiobutton.c        \
        gtkradiomenuitem.c      \
diff --git a/gtk/gtkpixelcache.c b/gtk/gtkpixelcache.c
new file mode 100644
index 0000000..1a86308
--- /dev/null
+++ b/gtk/gtkpixelcache.c
@@ -0,0 +1,341 @@
+/* GTK - The GIMP Toolkit
+ * Copyright (C) 2013 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 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/>.
+ */
+
+#include "config.h"
+
+#include "gtkpixelcacheprivate.h"
+
+/* The extra size of the offscreen surface we allocate
+   to make scrolling more efficient */
+#define EXTRA_SIZE 64
+
+/* When resizing viewport to smaller we allow this extra
+   size to avoid constantly reallocating when resizing */
+#define ALLOW_LARGER_SIZE 32
+
+struct _GtkPixelCache {
+  cairo_surface_t *surface;
+
+  /* Valid if surface != NULL */
+  int surface_x;
+  int surface_y;
+  int surface_w;
+  int surface_h;
+
+  /* may be null if not dirty */
+  cairo_region_t *surface_dirty;
+};
+
+GtkPixelCache *
+_gtk_pixel_cache_new ()
+{
+  GtkPixelCache *cache;
+
+  cache = g_new0 (GtkPixelCache, 1);
+
+  return cache;
+}
+
+void
+_gtk_pixel_cache_free (GtkPixelCache *cache)
+{
+  if (cache == NULL)
+    return;
+
+  if (cache->surface != NULL)
+    cairo_surface_destroy (cache->surface);
+
+  if (cache->surface_dirty != NULL)
+    cairo_region_destroy (cache->surface_dirty);
+
+  g_free (cache);
+}
+
+/* Region is in canvas coordinates */
+void
+_gtk_pixel_cache_invalidate (GtkPixelCache *cache,
+                            cairo_region_t *region)
+{
+  cairo_rectangle_int_t r;
+  cairo_region_t *free_region = NULL;
+
+  if (cache->surface == NULL ||
+      (region != NULL && cairo_region_is_empty (region)))
+    return;
+
+  if (region == NULL)
+    {
+      r.x = cache->surface_x;
+      r.y = cache->surface_y;
+      r.width = cache->surface_w;
+      r.height = cache->surface_h;
+
+      free_region = region =
+       cairo_region_create_rectangle (&r);
+    }
+
+  if (cache->surface_dirty == NULL)
+    {
+      cache->surface_dirty = cairo_region_copy (region);
+      cairo_region_translate (cache->surface_dirty,
+                             -cache->surface_x,
+                             -cache->surface_y);
+    }
+  else
+    {
+      cairo_region_translate (region,
+                             -cache->surface_x,
+                             -cache->surface_y);
+      cairo_region_union (cache->surface_dirty, region);
+      cairo_region_translate (region,
+                             cache->surface_x,
+                             cache->surface_y);
+    }
+
+  if (free_region)
+    cairo_region_destroy (free_region);
+
+  r.x = 0;
+  r.y = 0;
+  r.width = cache->surface_w;
+  r.height = cache->surface_h;
+
+  cairo_region_intersect_rectangle (cache->surface_dirty, &r);
+}
+
+static void
+_gtk_pixel_cache_create_surface_if_needed (GtkPixelCache         *cache,
+                                          GdkWindow             *window,
+                                          cairo_rectangle_int_t *view_rect,
+                                          cairo_rectangle_int_t *canvas_rect)
+{
+  cairo_rectangle_int_t rect;
+  int surface_w, surface_h;
+  cairo_content_t content;
+  cairo_pattern_t *bg;
+  double red, green, blue, alpha;
+
+  content = CAIRO_CONTENT_COLOR_ALPHA;
+  bg = gdk_window_get_background_pattern (window);
+  if (bg != NULL &&
+      cairo_pattern_get_type (bg) == CAIRO_PATTERN_TYPE_SOLID &&
+      cairo_pattern_get_rgba (bg, &red, &green, &blue, &alpha) == CAIRO_STATUS_SUCCESS &&
+      alpha == 1.0)
+    content = CAIRO_CONTENT_COLOR;
+
+  surface_w = view_rect->width;
+  if (canvas_rect->width > surface_w)
+    surface_w = MIN (surface_w + EXTRA_SIZE, canvas_rect->width);
+
+  surface_h = view_rect->height;
+  if (canvas_rect->height > surface_h)
+    surface_h = MIN (surface_h + EXTRA_SIZE, canvas_rect->height);
+
+  /* If current surface can't fit view_rect or is too large, kill it */
+  if (cache->surface != NULL &&
+      (cairo_surface_get_content (cache->surface) != content ||
+       cache->surface_w < view_rect->width ||
+       cache->surface_w > surface_w + ALLOW_LARGER_SIZE ||
+       cache->surface_h < view_rect->height ||
+       cache->surface_h > surface_h + ALLOW_LARGER_SIZE))
+    {
+      cairo_surface_destroy (cache->surface);
+      cache->surface = NULL;
+      if (cache->surface_dirty)
+       cairo_region_destroy (cache->surface_dirty);
+      cache->surface_dirty = NULL;
+    }
+
+  /* Don't allocate a surface if view >= canvas, as we won't
+     be scrolling then anyway */
+  if (cache->surface == NULL &&
+      (view_rect->width < canvas_rect->width ||
+       view_rect->height < canvas_rect->height))
+    {
+      cache->surface_x = -canvas_rect->x;
+      cache->surface_y = -canvas_rect->y;
+      cache->surface_w = surface_w;
+      cache->surface_h = surface_h;
+
+      cache->surface =
+       gdk_window_create_similar_surface (window, content,
+                                          surface_w, surface_h);
+      rect.x = 0;
+      rect.y = 0;
+      rect.width = surface_w;
+      rect.height = surface_h;
+      cache->surface_dirty =
+       cairo_region_create_rectangle (&rect);
+    }
+}
+
+void
+_gtk_pixel_cache_set_position (GtkPixelCache         *cache,
+                              cairo_rectangle_int_t *view_rect,
+                              cairo_rectangle_int_t *canvas_rect)
+{
+  cairo_rectangle_int_t r, view_pos;
+  cairo_region_t *copy_region;
+  int new_surf_x, new_surf_y;
+  cairo_t *backing_cr;
+
+  if (cache->surface == NULL)
+    return;
+
+  /* Position of view inside canvas */
+  view_pos.x = -canvas_rect->x;
+  view_pos.y = -canvas_rect->y;
+  view_pos.width = view_rect->width;
+  view_pos.height = view_rect->height;
+
+  /* Reposition so all is visible */
+  if (view_pos.x < cache->surface_x ||
+      view_pos.x + view_pos.width >
+      cache->surface_x + cache->surface_w ||
+      view_pos.y < cache->surface_y ||
+      view_pos.y + view_pos.height >
+      cache->surface_y + cache->surface_h)
+    {
+      new_surf_x = cache->surface_x;
+      if (view_pos.x < cache->surface_x)
+       new_surf_x = MAX (view_pos.x + view_pos.width - cache->surface_w, 0);
+      else if (view_pos.x + view_pos.width >
+              cache->surface_x + cache->surface_w)
+       new_surf_x = MIN (view_pos.x, canvas_rect->width - cache->surface_w);
+
+      new_surf_y = cache->surface_y;
+      if (view_pos.y < cache->surface_y)
+       new_surf_y = MAX (view_pos.y + view_pos.height - cache->surface_h, 0);
+      else if (view_pos.y + view_pos.height >
+              cache->surface_y + cache->surface_h)
+       new_surf_y = MIN (view_pos.y, canvas_rect->height - cache->surface_h);
+
+      r.x = 0;
+      r.y = 0;
+      r.width = cache->surface_w;
+      r.height = cache->surface_h;
+      copy_region = cairo_region_create_rectangle (&r);
+
+      if (cache->surface_dirty)
+       {
+         cairo_region_subtract (copy_region, cache->surface_dirty);
+         cairo_region_destroy (cache->surface_dirty);
+         cache->surface_dirty = NULL;
+       }
+
+      cairo_region_translate (copy_region,
+                             cache->surface_x - new_surf_x,
+                             cache->surface_y - new_surf_y);
+      cairo_region_intersect_rectangle (copy_region, &r);
+
+      backing_cr = cairo_create (cache->surface);
+      gdk_cairo_region (backing_cr, copy_region);
+      cairo_set_operator (backing_cr, CAIRO_OPERATOR_SOURCE);
+      cairo_clip (backing_cr);
+      cairo_push_group (backing_cr);
+      cairo_set_source_surface (backing_cr, cache->surface,
+                               cache->surface_x - new_surf_x,
+                               cache->surface_y - new_surf_y);
+      cairo_paint (backing_cr);
+      cairo_pop_group_to_source (backing_cr);
+      cairo_paint (backing_cr);
+      cairo_destroy (backing_cr);
+
+      cache->surface_x = new_surf_x;
+      cache->surface_y = new_surf_y;
+
+      cairo_region_xor_rectangle (copy_region, &r);
+      cache->surface_dirty = copy_region;
+    }
+}
+
+void
+_gtk_pixel_cache_repaint (GtkPixelCache *cache,
+                         GtkPixelCacheDrawFunc draw,
+                         cairo_rectangle_int_t *view_rect,
+                         cairo_rectangle_int_t *canvas_rect,
+                         gpointer user_data)
+{
+  cairo_t *backing_cr;
+
+  if (cache->surface &&
+      cache->surface_dirty &&
+      !cairo_region_is_empty (cache->surface_dirty))
+    {
+      backing_cr = cairo_create (cache->surface);
+      gdk_cairo_region (backing_cr, cache->surface_dirty);
+      cairo_clip (backing_cr);
+      cairo_translate (backing_cr,
+                      -cache->surface_x - canvas_rect->x - view_rect->x,
+                      -cache->surface_y - canvas_rect->y - view_rect->y);
+      cairo_set_source_rgba (backing_cr,
+                            0.0, 0, 0, 0.0);
+      cairo_set_operator (backing_cr, CAIRO_OPERATOR_SOURCE);
+      cairo_paint (backing_cr);
+
+      cairo_set_operator (backing_cr, CAIRO_OPERATOR_OVER);
+
+      draw (backing_cr, user_data);
+
+      cairo_destroy (backing_cr);
+    }
+
+  if (cache->surface_dirty)
+    {
+      cairo_region_destroy (cache->surface_dirty);
+      cache->surface_dirty = NULL;
+    }
+}
+
+void
+_gtk_pixel_cache_draw (GtkPixelCache *cache,
+                      cairo_t *cr,
+                      GdkWindow *window,
+                      /* View position in widget coords */
+                      cairo_rectangle_int_t *view_rect,
+                      /* Size and position of canvas in view coords */
+                      cairo_rectangle_int_t *canvas_rect,
+                      GtkPixelCacheDrawFunc draw,
+                      gpointer user_data)
+{
+  _gtk_pixel_cache_create_surface_if_needed (cache, window,
+                                            view_rect, canvas_rect);
+  _gtk_pixel_cache_set_position (cache, view_rect, canvas_rect);
+  _gtk_pixel_cache_repaint (cache, draw, view_rect, canvas_rect, user_data);
+
+  if (cache->surface &&
+      /* Don't use backing surface if rendering elsewhere */
+      cairo_surface_get_type (cache->surface) == cairo_surface_get_type (cairo_get_target (cr)))
+    {
+      cairo_save (cr);
+      cairo_set_source_surface (cr, cache->surface,
+                               cache->surface_x + view_rect->x + canvas_rect->x,
+                               cache->surface_y + view_rect->y + canvas_rect->y);
+      cairo_rectangle (cr, view_rect->x, view_rect->y,
+                      view_rect->width, view_rect->height);
+      cairo_fill (cr);
+      cairo_restore (cr);
+    }
+  else
+    {
+      cairo_rectangle (cr,
+                      view_rect->x, view_rect->y,
+                      view_rect->width, view_rect->height);
+      cairo_clip (cr);
+      draw (cr, user_data);
+    }
+}
diff --git a/gtk/gtkpixelcacheprivate.h b/gtk/gtkpixelcacheprivate.h
new file mode 100644
index 0000000..2b916d0
--- /dev/null
+++ b/gtk/gtkpixelcacheprivate.h
@@ -0,0 +1,48 @@
+/*
+ * Copyright © 2013 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: Alexander Larsson <alexl gnome org>
+ */
+
+#ifndef __GTK_PIXEL_CACHE_PRIVATE_H__
+#define __GTK_PIXEL_CACHE_PRIVATE_H__
+
+#include <glib-object.h>
+#include <gtkwidget.h>
+
+G_BEGIN_DECLS
+
+typedef struct _GtkPixelCache           GtkPixelCache;
+
+typedef void (*GtkPixelCacheDrawFunc) (cairo_t *cr,
+                                      gpointer user_data);
+
+GtkPixelCache *_gtk_pixel_cache_new        (void);
+void           _gtk_pixel_cache_free       (GtkPixelCache         *cache);
+void           _gtk_pixel_cache_invalidate (GtkPixelCache         *cache,
+                                           cairo_region_t        *region);
+void           _gtk_pixel_cache_draw       (GtkPixelCache         *cache,
+                                           cairo_t               *cr,
+                                           GdkWindow             *window,
+                                           cairo_rectangle_int_t *view_rect,
+                                           cairo_rectangle_int_t *canvas_rect,
+                                           GtkPixelCacheDrawFunc  draw,
+                                           gpointer               user_data);
+
+
+G_END_DECLS
+
+#endif /* __GTK_PIXEL_CACHE_PRIVATE_H__ */


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