Re: [Gimp-developer] [PATCH] app: Use SHM transport for data transfer for display



Thanks a lot Chris!

I will take care of applying the patch.

Regards,
Mitch

On Wed, 2013-01-30 at 09:50 +0000, Chris Wilson wrote:
> Recent Cairo uses SHM transports when available, and exposes the ability
> for its users to manage images shared between it and the display.
> This allows us to eliminate copies, and if the architecture supports it
> even to upload directly into GPU addressable memory without any copies
> (all in normal system memory so we suffer no performance penalty when
> applying the filters). The caveat is that we need to be aware of the
> synchronize requirements, the cairo_surface_flush and
> cairo_surface_mark_dirty, around access to the transport image. To
> reduce the frequency of these barriers, we can subdivide the transport
> image into small chunks as to satisfy individual updates and delay the
> synchronisation barrier until we are forced to reuse earlier pixels.
> 
> Note this bumps the required Cairo version to 1.12, and please be aware
> that the XSHM transport requires bug fixes from cairo.git (will be
> 1.12.12)
> 
> v2: After further reflections with Mitch, we realized we can share the
> transport surface between all canvases by attaching it to the common
> screen.
> 
> v3: Fix a couple of typos in insert_node() introduced when switching
> variables names.
> 
> v4: Encapsulating within an image surface rather than a subsurface was
> hiding the backing SHM segment from cairo, causing it to allocate
> further SHM resources to stream the upload. We should be able to use a
> sub-surface here, but it is more convenient to wrap the pixels in an
> image surface for rendering the filters (and conveniently masking the
> callee flushes from invalidating our parent transport surface).
> 
> Cc: Michael Natterer <mitch gimp org>
> ---
>  app/display/Makefile.am                  |    2 +
>  app/display/gimpdisplay-transport.c      |  228 ++++++++++++++++++++++++++++++
>  app/display/gimpdisplay-transport.h      |   44 ++++++
>  app/display/gimpdisplayshell-callbacks.c |    2 +
>  app/display/gimpdisplayshell-render.c    |   47 +++---
>  app/display/gimpdisplayshell-render.h    |   14 --
>  app/display/gimpdisplayshell.c           |   12 --
>  app/display/gimpdisplayshell.h           |    3 +-
>  configure.ac                             |    2 +-
>  9 files changed, 302 insertions(+), 52 deletions(-)
>  create mode 100644 app/display/gimpdisplay-transport.c
>  create mode 100644 app/display/gimpdisplay-transport.h
> 
> diff --git a/app/display/Makefile.am b/app/display/Makefile.am
> index d0b5413..3756b5b 100644
> --- a/app/display/Makefile.am
> +++ b/app/display/Makefile.am
> @@ -75,6 +75,8 @@ libappdisplay_a_sources = \
>  	gimpdisplay-foreach.h			\
>  	gimpdisplay-handlers.c			\
>  	gimpdisplay-handlers.h			\
> +	gimpdisplay-transport.c			\
> +	gimpdisplay-transport.h			\
>  	gimpdisplayshell.c			\
>  	gimpdisplayshell.h			\
>  	gimpdisplayshell-appearance.c		\
> diff --git a/app/display/gimpdisplay-transport.c b/app/display/gimpdisplay-transport.c
> new file mode 100644
> index 0000000..dde86ee
> --- /dev/null
> +++ b/app/display/gimpdisplay-transport.c
> @@ -0,0 +1,228 @@
> +/* GIMP - The GNU Image Manipulation Program
> + * Copyright (C) 1995 Spencer Kimball and Peter Mattis
> + *
> + * This program is free software: you can redistribute it and/or modify
> + * it under the terms of the GNU General Public License as published by
> + * the Free Software Foundation; either version 3 of the License, or
> + * (at your option) any later version.
> + *
> + * This program 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 General Public License for more details.
> + *
> + * You should have received a copy of the GNU General Public License
> + * along with this program.  If not, see <http://www.gnu.org/licenses/>.
> + */
> +
> +#include "config.h"
> +
> +#include <gegl.h>
> +#include <gtk/gtk.h>
> +
> +#include "gimpdisplay-transport.h"
> +
> +#define NUM_PAGES 2
> +
> +struct GimpDisplayXfer {
> +  struct rtree {
> +      struct rtree_node {
> +	  struct rtree_node *children[2];
> +	  struct rtree_node *next;
> +	  int x, y, w, h;
> +      } root, *available;
> +  } rtree; /* track subregions of render_surface for efficient uploads */
> +  cairo_surface_t *render_surface[NUM_PAGES];
> +  int page;
> +};
> +
> +static struct rtree_node *
> +rtree_node_create (struct rtree *rtree, struct rtree_node **prev,
> +		   int x, int y, int w, int h)
> +{
> +  struct rtree_node *node;
> +
> +  g_assert(x >= 0 && x+w <= rtree->root.w);
> +  g_assert(y >= 0 && y+h <= rtree->root.h);
> +
> +  node = g_slice_alloc (sizeof (*node));
> +  if (node == NULL)
> +    return NULL;
> +
> +  node->children[0] = NULL;
> +  node->children[1] = NULL;
> +  node->x = x;
> +  node->y = y;
> +  node->w = w;
> +  node->h = h;
> +
> +  node->next = *prev;
> +  *prev = node;
> +
> +  return node;
> +}
> +
> +static void
> +rtree_node_destroy (struct rtree *rtree, struct rtree_node *node)
> +{
> +  int i;
> +
> +  for (i = 0; i < 2; i++)
> +    {
> +      if (node->children[i])
> +	rtree_node_destroy (rtree, node->children[i]);
> +    }
> +
> +  g_slice_free (struct rtree_node, node);
> +}
> +
> +static struct rtree_node *
> +rtree_node_insert (struct rtree *rtree, struct rtree_node **prev,
> +		   struct rtree_node *node, int w, int h)
> +{
> +  *prev = node->next;
> +
> +  if (((node->w - w) | (node->h - h)) > 1)
> +    {
> +      int ww = node->w - w;
> +      int hh = node->h - h;
> +
> +      if (ww >= hh)
> +	{
> +	  node->children[0] = rtree_node_create (rtree, prev,
> +						 node->x + w, node->y,
> +						 ww, node->h);
> +	  node->children[1] = rtree_node_create (rtree, prev,
> +						 node->x, node->y + h,
> +						 w, hh);
> +	}
> +      else
> +	{
> +	  node->children[0] = rtree_node_create (rtree, prev,
> +						 node->x, node->y + h,
> +						 node->w, hh);
> +	  node->children[1] = rtree_node_create (rtree, prev,
> +						 node->x + w, node->y,
> +						 ww, h);
> +	}
> +    }
> +
> +  return node;
> +}
> +
> +static struct rtree_node *
> +rtree_insert (struct rtree *rtree, int w, int h)
> +{
> +  struct rtree_node *node, **prev;
> +
> +  for (prev = &rtree->available; (node = *prev); prev = &node->next)
> +    if (node->w >= w && w < 2 * node->w && node->h >= h && h < 2 * node->h)
> +      return rtree_node_insert (rtree, prev, node, w, h);
> +
> +  for (prev = &rtree->available; (node = *prev); prev = &node->next)
> +    if (node->w >= w && node->h >= h)
> +      return rtree_node_insert (rtree, prev, node, w, h);
> +
> +  return NULL;
> +}
> +
> +static void
> +rtree_init (struct rtree *rtree, int w, int h)
> +{
> +  rtree->root.x = 0;
> +  rtree->root.y = 0;
> +  rtree->root.w = w;
> +  rtree->root.h = h;
> +  rtree->root.children[0] = NULL;
> +  rtree->root.children[1] = NULL;
> +  rtree->root.next = NULL;
> +  rtree->available = &rtree->root;
> +}
> +
> +static void
> +rtree_reset (struct rtree *rtree)
> +{
> +  int i;
> +
> +  for (i = 0; i < 2; i++)
> +    {
> +      if (rtree->root.children[i] == NULL)
> +	continue;
> +
> +      rtree_node_destroy (rtree, rtree->root.children[i]);
> +      rtree->root.children[i] = NULL;
> +    }
> +
> +  rtree->root.next = NULL;
> +  rtree->available = &rtree->root;
> +}
> +
> +static void xfer_destroy (void *data)
> +{
> +  GimpDisplayXfer *xfer = data;
> +
> +  cairo_surface_destroy (xfer->render_surface);
> +  rtree_reset (&xfer->rtree);
> +  g_free (xfer);
> +}
> +
> +GimpDisplayXfer *gimp_display_xfer_realize (GtkWidget *widget)
> +{
> +  GdkScreen *screen;
> +  GimpDisplayXfer *xfer;
> +
> +  screen = gtk_widget_get_screen (widget);
> +  xfer = g_object_get_data (G_OBJECT (screen), "gimpdisplay-transport");
> +  if (xfer == NULL)
> +    {
> +      gint w = GIMP_DISPLAY_RENDER_BUF_WIDTH * GIMP_DISPLAY_RENDER_MAX_SCALE;
> +      gint h = GIMP_DISPLAY_RENDER_BUF_HEIGHT * GIMP_DISPLAY_RENDER_MAX_SCALE;
> +      cairo_t *cr;
> +      int n;
> +
> +      xfer = g_new (GimpDisplayXfer, 1);
> +      rtree_init (&xfer->rtree, w, h);
> +
> +      cr = gdk_cairo_create (gtk_widget_get_window (widget));
> +      for (n = 0; n < NUM_PAGES; n++) {
> +	xfer->render_surface[n] =
> +	  cairo_surface_create_similar_image (cairo_get_target (cr),
> +					      CAIRO_FORMAT_ARGB32, w, h);
> +	cairo_surface_mark_dirty (xfer->render_surface[n]);
> +      }
> +      cairo_destroy (cr);
> +      xfer->page = 0;
> +
> +      g_object_set_data_full (G_OBJECT (screen),
> +			      "gimpdisplay-transport",
> +			      xfer, xfer_destroy);
> +    }
> +
> +  return xfer;
> +}
> +
> +cairo_surface_t *
> +gimp_display_xfer_get_surface (GimpDisplayXfer *xfer,
> +			       gint w, gint h,
> +			       gint *src_x, gint *src_y)
> +{
> +  struct rtree_node *node;
> +
> +  g_assert (w <= GIMP_DISPLAY_RENDER_BUF_WIDTH * GIMP_DISPLAY_RENDER_MAX_SCALE &&
> +	    h <= GIMP_DISPLAY_RENDER_BUF_HEIGHT * GIMP_DISPLAY_RENDER_MAX_SCALE);
> +
> +  node = rtree_insert (&xfer->rtree, w, h);
> +  if (node == NULL)
> +    {
> +      xfer->page = (xfer->page + 1) % NUM_PAGES;
> +      cairo_surface_flush (xfer->render_surface[xfer->page]);
> +      rtree_reset (&xfer->rtree);
> +      cairo_surface_mark_dirty (xfer->render_surface[xfer->page]); /* XXX */
> +      node = rtree_insert (&xfer->rtree, w, h);
> +      g_assert (node != NULL);
> +    }
> +
> +  *src_x = node->x;
> +  *src_y = node->y;
> +  return xfer->render_surface[xfer->page];
> +}
> diff --git a/app/display/gimpdisplay-transport.h b/app/display/gimpdisplay-transport.h
> new file mode 100644
> index 0000000..6c7b21f
> --- /dev/null
> +++ b/app/display/gimpdisplay-transport.h
> @@ -0,0 +1,44 @@
> +/* GIMP - The GNU Image Manipulation Program
> + * Copyright (C) 1995 Spencer Kimball and Peter Mattis
> + *
> + * This program is free software: you can redistribute it and/or modify
> + * it under the terms of the GNU General Public License as published by
> + * the Free Software Foundation; either version 3 of the License, or
> + * (at your option) any later version.
> + *
> + * This program 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 General Public License for more details.
> + *
> + * You should have received a copy of the GNU General Public License
> + * along with this program.  If not, see <http://www.gnu.org/licenses/>.
> + */
> +
> +#ifndef __GIMP_DISPLAY_TRANSPORT_H__
> +#define __GIMP_DISPLAY_TRANSPORT_H__
> +
> +#include <cairo.h>
> +
> +/* #define GIMP_DISPLAY_RENDER_ENABLE_SCALING 1 */
> +
> +#define GIMP_DISPLAY_RENDER_BUF_WIDTH  256
> +#define GIMP_DISPLAY_RENDER_BUF_HEIGHT 256
> +
> +#ifdef GIMP_DISPLAY_RENDER_ENABLE_SCALING
> +#define GIMP_DISPLAY_RENDER_MAX_SCALE 2.0
> +#else
> +#define GIMP_DISPLAY_RENDER_MAX_SCALE 1.0
> +#endif
> +
> +typedef struct GimpDisplayXfer GimpDisplayXfer;
> +
> +GimpDisplayXfer *
> +gimp_display_xfer_realize (GtkWidget *widget);
> +
> +cairo_surface_t *
> +gimp_display_xfer_get_surface (GimpDisplayXfer *xfer,
> +			       gint w, gint h,
> +			       gint *src_x, gint *src_y);
> +
> +#endif  /*  __GIMP_DISPLAY_TRANSPORT_H__  */
> diff --git a/app/display/gimpdisplayshell-callbacks.c b/app/display/gimpdisplayshell-callbacks.c
> index e8cf44d..8a55932 100644
> --- a/app/display/gimpdisplayshell-callbacks.c
> +++ b/app/display/gimpdisplayshell-callbacks.c
> @@ -109,6 +109,8 @@ gimp_display_shell_canvas_realize (GtkWidget        *canvas,
>  
>    /*  allow shrinking  */
>    gtk_widget_set_size_request (GTK_WIDGET (shell), 0, 0);
> +
> +  shell->xfer = gimp_display_xfer_realize (GTK_WIDGET(shell));
>  }
>  
>  void
> diff --git a/app/display/gimpdisplayshell-render.c b/app/display/gimpdisplayshell-render.c
> index 67d1c50..ed4024b 100644
> --- a/app/display/gimpdisplayshell-render.c
> +++ b/app/display/gimpdisplayshell-render.c
> @@ -42,7 +42,6 @@
>  #include "gimpdisplayshell-render.h"
>  #include "gimpdisplayshell-scroll.h"
>  
> -
>  void
>  gimp_display_shell_render (GimpDisplayShell *shell,
>                             cairo_t          *cr,
> @@ -59,6 +58,10 @@ gimp_display_shell_render (GimpDisplayShell *shell,
>    gint            viewport_offset_y;
>    gint            viewport_width;
>    gint            viewport_height;
> +  cairo_surface_t *xfer;
> +  gint            src_x, src_y;
> +  gint            stride;
> +  unsigned char  *data;
>  
>    g_return_if_fail (GIMP_IS_DISPLAY_SHELL (shell));
>    g_return_if_fail (cr != NULL);
> @@ -80,6 +83,14 @@ gimp_display_shell_render (GimpDisplayShell *shell,
>                                                   &viewport_offset_y,
>                                                   &viewport_width,
>                                                   &viewport_height);
> +  xfer = gimp_display_xfer_get_surface (shell->xfer,
> +					w * window_scale,
> +					h * window_scale,
> +					&src_x, &src_y);
> +
> +  stride =cairo_image_surface_get_stride (xfer);
> +  data = cairo_image_surface_get_data (xfer);
> +  data += src_y * stride + src_x * 4;
>  
>    gegl_buffer_get (buffer,
>                     GEGL_RECTANGLE ((x + viewport_offset_x) * window_scale,
> @@ -88,33 +99,21 @@ gimp_display_shell_render (GimpDisplayShell *shell,
>                                     h * window_scale),
>                     shell->scale_x * window_scale,
>                     babl_format ("cairo-ARGB32"),
> -                   cairo_image_surface_get_data (shell->render_surface),
> -                   cairo_image_surface_get_stride (shell->render_surface),
> +		   data, stride,
>                     GEGL_ABYSS_NONE);
>  
>    /*  apply filters to the rendered projection  */
>    if (shell->filter_stack)
>      {
> -      cairo_surface_t *sub = shell->render_surface;
> -
> -      if (w != GIMP_DISPLAY_RENDER_BUF_WIDTH ||
> -          h != GIMP_DISPLAY_RENDER_BUF_HEIGHT)
> -        sub = cairo_image_surface_create_for_data (cairo_image_surface_get_data (sub),
> -                                                   CAIRO_FORMAT_ARGB32,
> -                                                   w * window_scale,
> -                                                   h * window_scale,
> -                                                   GIMP_DISPLAY_RENDER_BUF_WIDTH * 4);
> -
> -      gimp_color_display_stack_convert_surface (shell->filter_stack, sub);
> -
> -      if (sub != shell->render_surface)
> -        cairo_surface_destroy (sub);
> +      cairo_surface_t *image =
> +	cairo_image_surface_create_for_data (data, CAIRO_FORMAT_ARGB32,
> +					     w * window_scale,
> +					     h * window_scale,
> +					     stride);
> +      gimp_color_display_stack_convert_surface (shell->filter_stack, image);
> +      cairo_surface_destroy (image);
>      }
>  
> -  cairo_surface_mark_dirty_rectangle (shell->render_surface,
> -                                      0, 0,
> -                                      w * window_scale, h * window_scale);
> -
>  #if 0
>    if (shell->mask)
>      {
> @@ -151,9 +150,9 @@ gimp_display_shell_render (GimpDisplayShell *shell,
>  
>    cairo_scale (cr, 1.0 / window_scale, 1.0 / window_scale);
>  
> -  cairo_set_source_surface (cr, shell->render_surface,
> -                            x * window_scale,
> -                            y * window_scale);
> +  cairo_set_source_surface (cr, xfer,
> +                            (x - src_x) * window_scale,
> +                            (y - src_y) * window_scale);
>  
>    cairo_paint (cr);
>  
> diff --git a/app/display/gimpdisplayshell-render.h b/app/display/gimpdisplayshell-render.h
> index 652cddd..a84bc21 100644
> --- a/app/display/gimpdisplayshell-render.h
> +++ b/app/display/gimpdisplayshell-render.h
> @@ -18,19 +18,6 @@
>  #ifndef __GIMP_DISPLAY_SHELL_RENDER_H__
>  #define __GIMP_DISPLAY_SHELL_RENDER_H__
>  
> -
> -/* #define GIMP_DISPLAY_RENDER_ENABLE_SCALING 1 */
> -
> -#define GIMP_DISPLAY_RENDER_BUF_WIDTH  256
> -#define GIMP_DISPLAY_RENDER_BUF_HEIGHT 256
> -
> -#ifdef GIMP_DISPLAY_RENDER_ENABLE_SCALING
> -#define GIMP_DISPLAY_RENDER_MAX_SCALE 2.0
> -#else
> -#define GIMP_DISPLAY_RENDER_MAX_SCALE 1.0
> -#endif
> -
> -
>  void  gimp_display_shell_render (GimpDisplayShell *shell,
>                                   cairo_t          *cr,
>                                   gint              x,
> @@ -38,5 +25,4 @@ void  gimp_display_shell_render (GimpDisplayShell *shell,
>                                   gint              w,
>                                   gint              h);
>  
> -
>  #endif  /*  __GIMP_DISPLAY_SHELL_RENDER_H__  */
> diff --git a/app/display/gimpdisplayshell.c b/app/display/gimpdisplayshell.c
> index 3e99799..5d17c7c 100644
> --- a/app/display/gimpdisplayshell.c
> +++ b/app/display/gimpdisplayshell.c
> @@ -299,12 +299,6 @@ gimp_display_shell_init (GimpDisplayShell *shell)
>    shell->x_src_dec   = 1;
>    shell->y_src_dec   = 1;
>  
> -  shell->render_surface = cairo_image_surface_create (CAIRO_FORMAT_ARGB32,
> -                                                      GIMP_DISPLAY_RENDER_BUF_WIDTH *
> -                                                      GIMP_DISPLAY_RENDER_MAX_SCALE,
> -                                                      GIMP_DISPLAY_RENDER_BUF_HEIGHT *
> -                                                      GIMP_DISPLAY_RENDER_MAX_SCALE);
> -
>    gimp_display_shell_items_init (shell);
>  
>    shell->icon_size  = 32;
> @@ -783,12 +777,6 @@ gimp_display_shell_dispose (GObject *object)
>        shell->filter_idle_id = 0;
>      }
>  
> -  if (shell->render_surface)
> -    {
> -      cairo_surface_destroy (shell->render_surface);
> -      shell->render_surface = NULL;
> -    }
> -
>    if (shell->mask_surface)
>      {
>        cairo_surface_destroy (shell->mask_surface);
> diff --git a/app/display/gimpdisplayshell.h b/app/display/gimpdisplayshell.h
> index af825f8..f3d68a5 100644
> --- a/app/display/gimpdisplayshell.h
> +++ b/app/display/gimpdisplayshell.h
> @@ -18,6 +18,7 @@
>  #ifndef __GIMP_DISPLAY_SHELL_H__
>  #define __GIMP_DISPLAY_SHELL_H__
>  
> +#include "gimpdisplay-transport.h"
>  
>  /* Apply to a float the same rounding mode used in the renderer */
>  #define  PROJ_ROUND(coord)   ((gint) RINT (coord))
> @@ -114,7 +115,7 @@ struct _GimpDisplayShell
>  
>    GtkWidget         *statusbar;        /*  statusbar                          */
>  
> -  cairo_surface_t   *render_surface;   /*  buffer for rendering the image     */
> +  GimpDisplayXfer   *xfer;
>    cairo_surface_t   *mask_surface;     /*  buffer for rendering the mask      */
>    cairo_pattern_t   *checkerboard;     /*  checkerboard pattern               */
>  
> diff --git a/configure.ac b/configure.ac
> index 4792277..d8176f7 100644
> --- a/configure.ac
> +++ b/configure.ac
> @@ -46,7 +46,7 @@ m4_define([glib_required_version], [2.32.0])
>  m4_define([atk_required_version], [2.2.0])
>  m4_define([gtk_required_version], [2.24.10])
>  m4_define([gdk_pixbuf_required_version], [2.24.1])
> -m4_define([cairo_required_version], [1.10.2])
> +m4_define([cairo_required_version], [1.12.0])
>  m4_define([cairo_pdf_required_version], [1.10.2])
>  m4_define([pangocairo_required_version], [1.29.4])
>  m4_define([fontconfig_required_version], [2.2.0])




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