RSS/Atom feed Twitter
Site is read-only, email is disabled

[PATCH] app: Use SHM transport for data transfer for display

This discussion is connected to the gimp-developer-list.gnome.org mailing list which is provided by the GIMP developers and not related to gimpusers.com.

This is a read-only list on gimpusers.com so this discussion thread is read-only, too.

4 of 4 messages available
Toggle history

Please log in to manage your subscriptions.

[PATCH] app: Use SHM transport for data transfer for display Chris Wilson 29 Jan 12:21
  [PATCH] app: Use SHM transport for data transfer for display Chris Wilson 29 Jan 13:32
   [PATCH] app: Use SHM transport for data transfer for display Chris Wilson 30 Jan 09:50
    [PATCH] app: Use SHM transport for data transfer for display Michael Natterer 30 Jan 15:55
Chris Wilson
2013-01-29 12:21:33 UTC (about 12 years ago)

[PATCH] app: Use SHM transport for data transfer for display

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)

Cc: Michael Natterer ---
app/display/gimpdisplayshell-callbacks.c | 3 + app/display/gimpdisplayshell-render.c | 223 +++++++++++++++++++++++++++--- app/display/gimpdisplayshell-render.h | 3 + app/display/gimpdisplayshell.c | 16 +-- app/display/gimpdisplayshell.h | 7 + configure.ac | 2 +- 6 files changed, 220 insertions(+), 34 deletions(-)

diff --git a/app/display/gimpdisplayshell-callbacks.c b/app/display/gimpdisplayshell-callbacks.c index e8cf44d..4aa25f7 100644
--- a/app/display/gimpdisplayshell-callbacks.c +++ b/app/display/gimpdisplayshell-callbacks.c @@ -38,6 +38,7 @@
#include "gimpdisplayshell-appearance.h" #include "gimpdisplayshell-callbacks.h" #include "gimpdisplayshell-draw.h"
+#include "gimpdisplayshell-render.h" #include "gimpdisplayshell-scroll.h" #include "gimpdisplayshell-selection.h" #include "gimpdisplayshell-title.h" @@ -109,6 +110,8 @@ gimp_display_shell_canvas_realize (GtkWidget *canvas,
/* allow shrinking */
gtk_widget_set_size_request (GTK_WIDGET (shell), 0, 0); +
+ gimp_display_shell_xfer_realize (shell); }

void
diff --git a/app/display/gimpdisplayshell-render.c b/app/display/gimpdisplayshell-render.c index 67d1c50..38eec27 100644
--- a/app/display/gimpdisplayshell-render.c +++ b/app/display/gimpdisplayshell-render.c @@ -42,6 +42,199 @@
#include "gimpdisplayshell-render.h" #include "gimpdisplayshell-scroll.h"
+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;
+
+ node = g_slice_alloc (sizeof (*node)); + if (node == NULL)
+ return NULL;
+
+ node->children[0] = 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 < 4 && node->children[i] != NULL; 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;
+ int i = 0;
+
+ node->children[i] = rtree_node_create (rtree, prev, + node->x, node->y,
+ w, h);
+ if (node->children[i] == NULL)
+ return node;
+ i++;
+
+ if (ww > 1) {
+ node->children[i] = rtree_node_create (rtree, prev, + node->x + w, node->y,
+ ww, h);
+ if (node->children[i])
+ i++;
+ }
+
+ if (hh > 1) {
+ node->children[i] = rtree_node_create (rtree, prev, + node->x, node->y + h,
+ ww, hh);
+ if (node->children[i])
+ i++;
+
+ if (w > 1) {
+ node->children[i] = rtree_node_create (rtree, prev, + node->x + w,
+ node->y + h,
+ ww, hh);
+ if (node->children[i])
+ i++;
+ }
+ }
+
+ if (i < 4)
+ node->children[i] = NULL;
+
+ node = node->children[0];
+ }
+
+ 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.w = w;
+ rtree->root.h = h;
+ rtree->root.children[0] = NULL; + rtree->root.next = NULL;
+ rtree->available = &rtree->root; +}
+
+static void
+rtree_reset (struct rtree *rtree)
+{
+ int i;
+
+ for (i = 0; i < 4 && rtree->root.children[i] != NULL; i++) + rtree_node_destroy (rtree, rtree->root.children[i]); + rtree->root.children[0] = NULL; +
+ rtree->root.next = NULL;
+ rtree->available = &rtree->root; +}
+
+static void
+rtree_full (struct rtree *rtree)
+{
+ rtree->available = NULL;
+}
+
+void gimp_display_shell_xfer_init (GimpDisplayShell *shell, int w, int h) +{
+ rtree_init (&shell->xfer, w, h); +}
+
+void gimp_display_shell_xfer_realize (GimpDisplayShell *shell) +{
+ cairo_t *cr;
+
+ gimp_display_shell_xfer_fini (shell); +
+ cr = gdk_cairo_create (gtk_widget_get_window (GTK_WIDGET (shell->canvas))); + shell->render_surface =
+ cairo_surface_create_similar_image (cairo_get_target (cr), + CAIRO_FORMAT_ARGB32,
+ shell->xfer.root.w,
+ shell->xfer.root.h);
+ cairo_destroy (cr);
+}
+
+void gimp_display_shell_xfer_fini (GimpDisplayShell *shell) +{
+ if (shell->render_surface)
+ {
+ cairo_surface_destroy (shell->render_surface); + shell->render_surface = NULL; + }
+ rtree_reset (&shell->xfer);
+}
+
+static cairo_surface_t *
+get_transport_surface (GimpDisplayShell *shell, + gint w, gint h)
+{
+ struct rtree_node *node;
+ cairo_surface_t *xfer;
+ unsigned char *data;
+ int stride;
+
+ node = rtree_insert (&shell->xfer, w, h); + if (node == NULL) {
+ cairo_surface_flush (shell->render_surface); + rtree_reset (&shell->xfer);
+ cairo_surface_mark_dirty (shell->render_surface); /* XXX */ + node = rtree_insert (&shell->xfer, w, h); + }
+
+ stride = cairo_image_surface_get_stride (shell->render_surface); + data = cairo_image_surface_get_data (shell->render_surface); + data += node->y * stride + node->x * 4; +
+ xfer = cairo_image_surface_create_for_data (data, CAIRO_FORMAT_ARGB32, + w, h, stride);
+ if (cairo_surface_status (xfer)) { + cairo_surface_flush (shell->render_surface); + rtree_reset (&shell->xfer);
+ cairo_surface_mark_dirty (shell->render_surface); + rtree_full (&shell->xfer);
+ return cairo_surface_reference (shell->render_surface); + }
+
+ return xfer;
+}

void
gimp_display_shell_render (GimpDisplayShell *shell, @@ -59,6 +252,7 @@ gimp_display_shell_render (GimpDisplayShell *shell, gint viewport_offset_y; gint viewport_width;
gint viewport_height;
+ cairo_surface_t *xfer;

g_return_if_fail (GIMP_IS_DISPLAY_SHELL (shell)); g_return_if_fail (cr != NULL);
@@ -80,6 +274,7 @@ gimp_display_shell_render (GimpDisplayShell *shell, &viewport_offset_y, &viewport_width, &viewport_height); + xfer = get_transport_surface (shell, w * window_scale, h * window_scale);
gegl_buffer_get (buffer,
GEGL_RECTANGLE ((x + viewport_offset_x) * window_scale, @@ -88,32 +283,13 @@ 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), + cairo_image_surface_get_data (xfer), + cairo_image_surface_get_stride (xfer), 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_mark_dirty_rectangle (shell->render_surface, - 0, 0, - w * window_scale, h * window_scale); + gimp_color_display_stack_convert_surface (shell->filter_stack, xfer);
#if 0
if (shell->mask)
@@ -151,7 +327,7 @@ 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, + cairo_set_source_surface (cr, xfer, x * window_scale, y * window_scale);
@@ -167,4 +343,5 @@ gimp_display_shell_render (GimpDisplayShell *shell, #endif

cairo_restore (cr);
+ cairo_surface_destroy (xfer);
}
diff --git a/app/display/gimpdisplayshell-render.h b/app/display/gimpdisplayshell-render.h index 652cddd..aad7f0a 100644
--- a/app/display/gimpdisplayshell-render.h +++ b/app/display/gimpdisplayshell-render.h @@ -38,5 +38,8 @@ void gimp_display_shell_render (GimpDisplayShell *shell, gint w, gint h);
+void gimp_display_shell_xfer_init (GimpDisplayShell *shell, int w, int h); +void gimp_display_shell_xfer_realize (GimpDisplayShell *shell); +void gimp_display_shell_xfer_fini (GimpDisplayShell *shell);
#endif /* __GIMP_DISPLAY_SHELL_RENDER_H__ */ diff --git a/app/display/gimpdisplayshell.c b/app/display/gimpdisplayshell.c index 3e99799..029a024 100644
--- a/app/display/gimpdisplayshell.c +++ b/app/display/gimpdisplayshell.c @@ -299,11 +299,11 @@ 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_xfer_init (shell, + 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);
@@ -783,11 +783,7 @@ 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; - }
+ gimp_display_shell_xfer_fini (shell);
if (shell->mask_surface)
{
diff --git a/app/display/gimpdisplayshell.h b/app/display/gimpdisplayshell.h index af825f8..6788189 100644
--- a/app/display/gimpdisplayshell.h +++ b/app/display/gimpdisplayshell.h @@ -114,6 +114,13 @@ struct _GimpDisplayShell
GtkWidget *statusbar; /* statusbar */
+ struct rtree {
+ struct rtree_node {
+ struct rtree_node *children[4];
+ struct rtree_node *next;
+ int x, y, w, h;
+ } root, *available;
+ } xfer; /* track subregions of render_surface for efficient uploads */ cairo_surface_t *render_surface; /* buffer for rendering the image */ 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])

1.7.10.4
Chris Wilson
2013-01-29 13:32:48 UTC (about 12 years ago)

[PATCH] app: Use SHM transport for data transfer for display

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 share the transport surface between all canvases by attaching it to the common screen.

Cc: Michael Natterer
---
app/display/Makefile.am | 2 + app/display/gimpdisplay-transport.c | 256 ++++++++++++++++++++++++++++++ app/display/gimpdisplay-transport.h | 42 +++++ app/display/gimpdisplayshell-callbacks.c | 3 + app/display/gimpdisplayshell-render.c | 33 ++-- app/display/gimpdisplayshell-render.h | 14 -- app/display/gimpdisplayshell.c | 12 -- app/display/gimpdisplayshell.h | 3 +- configure.ac | 2 +- 9 files changed, 315 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..79709db
--- /dev/null
+++ b/app/display/gimpdisplay-transport.c @@ -0,0 +1,256 @@
+/* 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 . + */
+
+#include "config.h"
+
+#include
+#include
+
+#include "display-types.h"
+
+#include "core/gimp.h"
+#include "core/gimpcontainer.h"
+#include "core/gimpcontext.h"
+#include "core/gimpimage.h"
+#include "core/gimplist.h"
+
+#include "gimpdisplay.h"
+#include "gimpdisplay-transport.h"
+
+struct GimpDisplayXfer {
+ struct rtree {
+ struct rtree_node {
+ struct rtree_node *children[4];
+ 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; /* buffer for rendering the image */ +};
+
+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;
+
+ node = g_slice_alloc (sizeof (*node)); + if (node == NULL)
+ return NULL;
+
+ node->children[0] = 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 < 4 && node->children[i] != NULL; 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;
+ int i = 0;
+
+ node->children[i] = rtree_node_create (rtree, prev, + node->x, node->y,
+ w, h);
+ if (node->children[i] == NULL) + return node;
+ i++;
+
+ if (ww > 1)
+ {
+ node->children[i] = rtree_node_create (rtree, prev, + node->x + w, node->y,
+ ww, h);
+ if (node->children[i])
+ i++;
+ }
+
+ if (hh > 1)
+ {
+ node->children[i] = rtree_node_create (rtree, prev, + node->x, node->y + h,
+ ww, hh);
+ if (node->children[i])
+ i++;
+
+ if (w > 1) {
+ node->children[i] = rtree_node_create (rtree, prev, + node->x + w,
+ node->y + h,
+ ww, hh);
+ if (node->children[i])
+ i++;
+ }
+ }
+
+ if (i < 4)
+ node->children[i] = NULL;
+
+ node = node->children[0];
+ }
+
+ 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.w = w;
+ rtree->root.h = h;
+ rtree->root.children[0] = NULL;
+ rtree->root.next = NULL;
+ rtree->available = &rtree->root;
+}
+
+static void
+rtree_reset (struct rtree *rtree)
+{
+ int i;
+
+ for (i = 0; i < 4 && rtree->root.children[i] != NULL; i++) + rtree_node_destroy (rtree, rtree->root.children[i]); + rtree->root.children[0] = NULL;
+
+ rtree->root.next = NULL;
+ rtree->available = &rtree->root;
+}
+
+static void
+rtree_full (struct rtree *rtree)
+{
+ rtree->available = NULL;
+}
+
+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;
+
+ xfer = g_new (GimpDisplayXfer, 1); + rtree_init (&xfer->rtree, w, h); +
+ cr = gdk_cairo_create (gtk_widget_get_window (widget)); + xfer->render_surface =
+ cairo_surface_create_similar_image (cairo_get_target (cr), + CAIRO_FORMAT_ARGB32,
+ xfer->rtree.root.w,
+ xfer->rtree.root.h);
+ cairo_destroy (cr);
+
+ 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) +{
+ struct rtree_node *node;
+ cairo_surface_t *surface;
+ unsigned char *data;
+ int stride;
+
+ g_assert (w rtree, w, h);
+ if (node == NULL)
+ {
+ cairo_surface_flush (xfer->render_surface); + rtree_reset (&xfer->rtree);
+ cairo_surface_mark_dirty (xfer->render_surface); /* XXX */ + node = rtree_insert (&xfer->rtree, w, h); + g_assert (node != NULL);
+ }
+
+ stride = cairo_image_surface_get_stride (xfer->render_surface); + data = cairo_image_surface_get_data (xfer->render_surface); + data += node->y * stride + node->x * 4; +
+ surface = cairo_image_surface_create_for_data (data, CAIRO_FORMAT_ARGB32, + w, h, stride);
+ if (cairo_surface_status (surface)) + {
+ cairo_surface_flush (xfer->render_surface); + rtree_reset (&xfer->rtree);
+ cairo_surface_mark_dirty (xfer->render_surface); + rtree_full (&xfer->rtree);
+ return cairo_surface_reference (xfer->render_surface); + }
+
+ return surface;
+}
diff --git a/app/display/gimpdisplay-transport.h b/app/display/gimpdisplay-transport.h new file mode 100644
index 0000000..7272951
--- /dev/null
+++ b/app/display/gimpdisplay-transport.h @@ -0,0 +1,42 @@
+/* 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 . + */
+
+#ifndef __GIMP_DISPLAY_TRANSPORT_H__ +#define __GIMP_DISPLAY_TRANSPORT_H__ +
+#include
+
+/* #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); +
+#endif /* __GIMP_DISPLAY_TRANSPORT_H__ */ diff --git a/app/display/gimpdisplayshell-callbacks.c b/app/display/gimpdisplayshell-callbacks.c index e8cf44d..93ea990 100644
--- a/app/display/gimpdisplayshell-callbacks.c +++ b/app/display/gimpdisplayshell-callbacks.c @@ -38,6 +38,7 @@
#include "gimpdisplayshell-appearance.h" #include "gimpdisplayshell-callbacks.h" #include "gimpdisplayshell-draw.h"
+#include "gimpdisplayshell-render.h" #include "gimpdisplayshell-scroll.h" #include "gimpdisplayshell-selection.h" #include "gimpdisplayshell-title.h" @@ -109,6 +110,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..0bfc2f8 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,7 @@ gimp_display_shell_render (GimpDisplayShell *shell, gint viewport_offset_y; gint viewport_width;
gint viewport_height;
+ cairo_surface_t *xfer;

g_return_if_fail (GIMP_IS_DISPLAY_SHELL (shell)); g_return_if_fail (cr != NULL);
@@ -80,6 +80,9 @@ 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);

gegl_buffer_get (buffer,
GEGL_RECTANGLE ((x + viewport_offset_x) * window_scale, @@ -88,32 +91,13 @@ 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), + cairo_image_surface_get_data (xfer), + cairo_image_surface_get_stride (xfer), 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_mark_dirty_rectangle (shell->render_surface, - 0, 0, - w * window_scale, h * window_scale); + gimp_color_display_stack_convert_surface (shell->filter_stack, xfer);
#if 0
if (shell->mask)
@@ -151,7 +135,7 @@ 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, + cairo_set_source_surface (cr, xfer, x * window_scale, y * window_scale);
@@ -167,4 +151,5 @@ gimp_display_shell_render (GimpDisplayShell *shell, #endif

cairo_restore (cr);
+ cairo_surface_destroy (xfer);
}
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])

1.7.10.4
Chris Wilson
2013-01-30 09:50:54 UTC (about 12 years ago)

[PATCH] app: Use SHM transport for data transfer for display

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 ---
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 . + */
+
+#include "config.h"
+
+#include
+#include
+
+#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 root.w);
+ g_assert(y >= 0 && y+h 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 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 . + */
+
+#ifndef __GIMP_DISPLAY_TRANSPORT_H__ +#define __GIMP_DISPLAY_TRANSPORT_H__ +
+#include
+
+/* #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])

1.7.10.4
Michael Natterer
2013-01-30 15:55:37 UTC (about 12 years ago)

[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 ---
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 . + */
+
+#include "config.h"
+
+#include
+#include
+
+#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 root.w);
+ g_assert(y >= 0 && y+h 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 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 . + */
+
+#ifndef __GIMP_DISPLAY_TRANSPORT_H__ +#define __GIMP_DISPLAY_TRANSPORT_H__ +
+#include
+
+/* #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])