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 <mitch@gimp.org>
This commit is contained in:
Chris Wilson 2013-01-30 09:50:54 +00:00 committed by Michael Natterer
parent e68f56be19
commit 4a81849e36
10 changed files with 310 additions and 54 deletions

View File

@ -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 \

View File

@ -0,0 +1,234 @@
/* 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;
gint i;
for (i = 0; i < NUM_PAGES; i++)
cairo_surface_destroy (xfer->render_surface[i]);
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)
{
cairo_t *cr;
gint w = GIMP_DISPLAY_RENDER_BUF_WIDTH * GIMP_DISPLAY_RENDER_MAX_SCALE;
gint h = GIMP_DISPLAY_RENDER_BUF_HEIGHT * GIMP_DISPLAY_RENDER_MAX_SCALE;
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];
}

View File

@ -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__ */

View File

@ -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

View File

@ -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);

View File

@ -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__ */

View File

@ -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);

View File

@ -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 */

View File

@ -163,7 +163,7 @@ static gchar *
sanity_check_cairo (void)
{
#define CAIRO_REQUIRED_MAJOR 1
#define CAIRO_REQUIRED_MINOR 10
#define CAIRO_REQUIRED_MINOR 12
#define CAIRO_REQUIRED_MICRO 2
if (cairo_version () < CAIRO_VERSION_ENCODE (CAIRO_REQUIRED_MAJOR,

View File

@ -46,8 +46,8 @@ 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_pdf_required_version], [1.10.2])
m4_define([cairo_required_version], [1.12.2])
m4_define([cairo_pdf_required_version], [1.12.2])
m4_define([pangocairo_required_version], [1.29.4])
m4_define([fontconfig_required_version], [2.2.0])
m4_define([gtkdoc_required_version], [1.0])