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 <mitch@xxxxxxxx> --- 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 _______________________________________________ gimp-developer-list mailing list gimp-developer-list@xxxxxxxxx https://mail.gnome.org/mailman/listinfo/gimp-developer-list