[cairo-commit] 4 commits - boilerplate/Makefile.win32.features build/configure.ac.features build/Makefile.win32.features build/Makefile.win32.features-h configure.ac perf/cairo-analyse-trace.c perf/.gitignore perf/Makefile.am perf/Makefile.sources src/cairo.h src/cairo-surface.c src/cairo-surface-observer.c src/cairo-surface-observer-private.h src/cairo-tee-surface.c src/cairo-types-private.h src/cairo-xml-surface.c src/Makefile.sources src/Makefile.win32.features

Chris Wilson ickle at kemper.freedesktop.org
Sun Aug 14 13:03:06 PDT 2011


 boilerplate/Makefile.win32.features  |   12 
 build/Makefile.win32.features        |    2 
 build/Makefile.win32.features-h      |    1 
 build/configure.ac.features          |    1 
 configure.ac                         |    3 
 perf/.gitignore                      |    1 
 perf/Makefile.am                     |   35 -
 perf/Makefile.sources                |    3 
 perf/cairo-analyse-trace.c           |  646 ++++++++++++++++++++++
 src/Makefile.sources                 |    2 
 src/Makefile.win32.features          |   12 
 src/cairo-surface-observer-private.h |  168 +++++
 src/cairo-surface-observer.c         | 1009 +++++++++++++++++++++++++++++++++++
 src/cairo-surface.c                  |   12 
 src/cairo-tee-surface.c              |    1 
 src/cairo-types-private.h            |    6 
 src/cairo-xml-surface.c              |    1 
 src/cairo.h                          |   13 
 18 files changed, 1911 insertions(+), 17 deletions(-)

New commits:
commit eed1f2efdf36173e23b7177bb34ab9a5f015fb2a
Author: Chris Wilson <chris at chris-wilson.co.uk>
Date:   Sun Aug 14 21:02:08 2011 +0100

    xml: Include 'cairo-image-surface-private.h'
    
    Reported-by: James Cloos <cloos at jhcloos.com>
    Signed-off-by: Chris Wilson <chris at chris-wilson.co.uk>

diff --git a/src/cairo-xml-surface.c b/src/cairo-xml-surface.c
index d909a94..f15a767 100644
--- a/src/cairo-xml-surface.c
+++ b/src/cairo-xml-surface.c
@@ -47,6 +47,7 @@
 #include "cairo-clip-private.h"
 #include "cairo-device-private.h"
 #include "cairo-default-context-private.h"
+#include "cairo-image-surface-private.h"
 #include "cairo-error-private.h"
 #include "cairo-output-stream-private.h"
 #include "cairo-recording-surface-private.h"
commit 08627ed0f3992de44ed622dea5c4c76117ac24cc
Author: Chris Wilson <chris at chris-wilson.co.uk>
Date:   Sun Aug 14 21:01:42 2011 +0100

    tee: compile fix for migration of _cairo_is_recording_surface()
    
    Signed-off-by: Chris Wilson <chris at chris-wilson.co.uk>

diff --git a/src/cairo-tee-surface.c b/src/cairo-tee-surface.c
index 834f140..bd5d264 100644
--- a/src/cairo-tee-surface.c
+++ b/src/cairo-tee-surface.c
@@ -45,6 +45,7 @@
 #include "cairo-default-context-private.h"
 #include "cairo-error-private.h"
 #include "cairo-tee-surface-private.h"
+#include "cairo-recording-surface-private.h"
 #include "cairo-surface-wrapper-private.h"
 
 typedef struct _cairo_tee_surface {
commit 62e48b01b456ee07081c14ed8f3a1f5475db3b57
Author: Chris Wilson <chris at chris-wilson.co.uk>
Date:   Sun Aug 14 20:56:15 2011 +0100

    script: enable by default
    
    I'm willing to make this a supported backend as I find it to be an
    invaluable debugging tool...
    
    Signed-off-by: Chris Wilson <chris at chris-wilson.co.uk>

diff --git a/boilerplate/Makefile.win32.features b/boilerplate/Makefile.win32.features
index cb33fe5..793d1f4 100644
--- a/boilerplate/Makefile.win32.features
+++ b/boilerplate/Makefile.win32.features
@@ -307,7 +307,7 @@ enabled_cairo_boilerplate_cxx_sources += $(cairo_boilerplate_wgl_cxx_sources)
 enabled_cairo_boilerplate_sources += $(cairo_boilerplate_wgl_sources)
 endif
 
-unsupported_cairo_boilerplate_headers += $(cairo_boilerplate_script_headers)
+supported_cairo_boilerplate_headers += $(cairo_boilerplate_script_headers)
 all_cairo_boilerplate_headers += $(cairo_boilerplate_script_headers)
 all_cairo_boilerplate_private += $(cairo_boilerplate_script_private)
 all_cairo_boilerplate_cxx_sources += $(cairo_boilerplate_script_cxx_sources)
diff --git a/build/Makefile.win32.features b/build/Makefile.win32.features
index 5d75ba3..650b224 100644
--- a/build/Makefile.win32.features
+++ b/build/Makefile.win32.features
@@ -24,7 +24,7 @@ CAIRO_HAS_VG_SURFACE=0
 CAIRO_HAS_EGL_FUNCTIONS=0
 CAIRO_HAS_GLX_FUNCTIONS=0
 CAIRO_HAS_WGL_FUNCTIONS=0
-CAIRO_HAS_SCRIPT_SURFACE=0
+CAIRO_HAS_SCRIPT_SURFACE=1
 CAIRO_HAS_FT_FONT=0
 CAIRO_HAS_FC_FONT=0
 CAIRO_HAS_PS_SURFACE=1
diff --git a/configure.ac b/configure.ac
index 50efecc..fc14341 100644
--- a/configure.ac
+++ b/configure.ac
@@ -407,7 +407,7 @@ CAIRO_ENABLE_FUNCTIONS(wgl, WGL, auto, [
 dnl ===========================================================================
 
 any2ppm_cs=no
-CAIRO_ENABLE_SURFACE_BACKEND(script, script, no, [
+CAIRO_ENABLE_SURFACE_BACKEND(script, script, yes, [
   any2ppm_cs=yes
 ])
 
diff --git a/src/Makefile.win32.features b/src/Makefile.win32.features
index 8cfd6e7..db769ef 100644
--- a/src/Makefile.win32.features
+++ b/src/Makefile.win32.features
@@ -405,7 +405,7 @@ ifeq ($(CAIRO_HAS_WGL_FUNCTIONS),1)
 enabled_cairo_pkgconf += cairo-wgl.pc
 endif
 
-unsupported_cairo_headers += $(cairo_script_headers)
+supported_cairo_headers += $(cairo_script_headers)
 all_cairo_headers += $(cairo_script_headers)
 all_cairo_private += $(cairo_script_private)
 all_cairo_cxx_sources += $(cairo_script_cxx_sources)
commit eee66899cdbd2d431b08b468ac2b285bb855e6da
Author: Chris Wilson <chris at chris-wilson.co.uk>
Date:   Sun Aug 14 18:11:26 2011 +0100

    Introduce cairo_surface_observer_t for performance analysis
    
    Another logging passthrough surface that records the style of operations
    performed trying to categorise what is slow/fast/important.
    
    In combination with perf/cairo-analyse-trace it is very useful for
    understanding what a trace does. The next steps for this tool would be
    to identify the slow operations that the trace does. Baby steps.
    
    This should be generally useful in similar situations outside of perf/
    and should be extensible to become an online performance probe.
    
    Signed-off-by: Chris Wilson <chris at chris-wilson.co.uk>

diff --git a/boilerplate/Makefile.win32.features b/boilerplate/Makefile.win32.features
index 0cf7095..cb33fe5 100644
--- a/boilerplate/Makefile.win32.features
+++ b/boilerplate/Makefile.win32.features
@@ -408,6 +408,16 @@ enabled_cairo_boilerplate_private += $(cairo_boilerplate_recording_private)
 enabled_cairo_boilerplate_cxx_sources += $(cairo_boilerplate_recording_cxx_sources)
 enabled_cairo_boilerplate_sources += $(cairo_boilerplate_recording_sources)
 
+supported_cairo_boilerplate_headers += $(cairo_boilerplate_observer_headers)
+all_cairo_boilerplate_headers += $(cairo_boilerplate_observer_headers)
+all_cairo_boilerplate_private += $(cairo_boilerplate_observer_private)
+all_cairo_boilerplate_cxx_sources += $(cairo_boilerplate_observer_cxx_sources)
+all_cairo_boilerplate_sources += $(cairo_boilerplate_observer_sources)
+enabled_cairo_boilerplate_headers += $(cairo_boilerplate_observer_headers)
+enabled_cairo_boilerplate_private += $(cairo_boilerplate_observer_private)
+enabled_cairo_boilerplate_cxx_sources += $(cairo_boilerplate_observer_cxx_sources)
+enabled_cairo_boilerplate_sources += $(cairo_boilerplate_observer_sources)
+
 unsupported_cairo_boilerplate_headers += $(cairo_boilerplate_tee_headers)
 all_cairo_boilerplate_headers += $(cairo_boilerplate_tee_headers)
 all_cairo_boilerplate_private += $(cairo_boilerplate_tee_private)
diff --git a/build/Makefile.win32.features-h b/build/Makefile.win32.features-h
index e35cc6c..e49ad6e 100644
--- a/build/Makefile.win32.features-h
+++ b/build/Makefile.win32.features-h
@@ -100,6 +100,7 @@ ifeq ($(CAIRO_HAS_TEST_SURFACES),1)
 endif
 	@echo "#define CAIRO_HAS_IMAGE_SURFACE 1" >> $(top_srcdir)/src/cairo-features.h
 	@echo "#define CAIRO_HAS_RECORDING_SURFACE 1" >> $(top_srcdir)/src/cairo-features.h
+	@echo "#define CAIRO_HAS_OBSERVER_SURFACE 1" >> $(top_srcdir)/src/cairo-features.h
 ifeq ($(CAIRO_HAS_TEE_SURFACE),1)
 	@echo "#define CAIRO_HAS_TEE_SURFACE 1" >> $(top_srcdir)/src/cairo-features.h
 endif
diff --git a/build/configure.ac.features b/build/configure.ac.features
index 4cccd9c..4f774e3 100644
--- a/build/configure.ac.features
+++ b/build/configure.ac.features
@@ -365,6 +365,7 @@ AC_DEFUN([CAIRO_REPORT],
 	echo "The following surface backends:"
 	echo "  Image:         yes (always builtin)"
 	echo "  Recording:     yes (always builtin)"
+	echo "  Observer:      yes (always builtin)"
 	echo "  Tee:           $use_tee"
 	echo "  XML:           $use_xml"
 	echo "  Skia:          $use_skia"
diff --git a/configure.ac b/configure.ac
index f659729..50efecc 100644
--- a/configure.ac
+++ b/configure.ac
@@ -621,6 +621,7 @@ CAIRO_ENABLE_SURFACE_BACKEND(image, image, always, [
 dnl ===========================================================================
 
 CAIRO_ENABLE_SURFACE_BACKEND(recording, recording, always)
+CAIRO_ENABLE_SURFACE_BACKEND(observer, observer, always)
 CAIRO_ENABLE_SURFACE_BACKEND(tee, tee, no)
 CAIRO_ENABLE_SURFACE_BACKEND(xml, xml, no, [
     use_xml=$have_libz
diff --git a/perf/.gitignore b/perf/.gitignore
index bc8b9bf..3ac12bb 100644
--- a/perf/.gitignore
+++ b/perf/.gitignore
@@ -1,5 +1,6 @@
 TAGS
 tags
+cairo-analyse-trace
 cairo-perf
 cairo-perf-micro
 cairo-perf-print
diff --git a/perf/Makefile.am b/perf/Makefile.am
index d41891f..3ac60da 100644
--- a/perf/Makefile.am
+++ b/perf/Makefile.am
@@ -14,15 +14,22 @@ AM_LDFLAGS = $(CAIRO_LDFLAGS)
 
 SUBDIRS = micro
 
-noinst_PROGRAMS = cairo-perf-trace cairo-perf-micro
-
-EXTRA_PROGRAMS += cairo-perf-micro \
-		  cairo-perf-trace \
-		  cairo-perf-diff-files \
-		  cairo-perf-print \
-		  cairo-perf-chart \
-		  cairo-perf-compare-backends \
-		  cairo-perf-graph-files
+noinst_PROGRAMS = \
+	cairo-analyse-trace \
+	cairo-perf-trace \
+	cairo-perf-micro \
+	$(NULL)
+
+EXTRA_PROGRAMS += \
+	cairo-analyse-trace \
+	cairo-perf-micro \
+	cairo-perf-trace \
+	cairo-perf-diff-files \
+	cairo-perf-print \
+	cairo-perf-chart \
+	cairo-perf-compare-backends \
+	cairo-perf-graph-files \
+	$(NULL)
 EXTRA_DIST += cairo-perf-diff COPYING
 EXTRA_LTLIBRARIES += libcairoperf.la
 
@@ -43,6 +50,16 @@ libcairoperf_la_SOURCES = \
 	$(libcairoperf_headers)
 libcairoperf_la_LIBADD = $(CAIROPERF_LIBS)
 
+cairo_analyse_trace_SOURCES = \
+	$(cairo_analyse_trace_sources)	\
+	$(cairo_analyse_trace_external_sources)
+cairo_analyse_trace_LDADD =		\
+	$(top_builddir)/util/cairo-script/libcairo-script-interpreter.la \
+	$(LDADD)
+cairo_analyse_trace_DEPENDENCIES = \
+	$(top_builddir)/util/cairo-script/libcairo-script-interpreter.la \
+	$(LDADD)
+
 cairo_perf_trace_SOURCES = \
 	$(cairo_perf_trace_sources)	\
 	$(cairo_perf_trace_external_sources)
diff --git a/perf/Makefile.sources b/perf/Makefile.sources
index 9867ea5..0c15ae9 100644
--- a/perf/Makefile.sources
+++ b/perf/Makefile.sources
@@ -11,6 +11,9 @@ libcairoperf_headers = \
 	cairo-stats.h  \
 	$(NULL)
 
+cairo_analyse_trace_sources = cairo-analyse-trace.c
+cairo_analyse_trace_external_sources = ../src/cairo-error.c
+
 cairo_perf_trace_sources = cairo-perf-trace.c
 cairo_perf_trace_external_sources = \
 	../src/cairo-error.c \
diff --git a/perf/cairo-analyse-trace.c b/perf/cairo-analyse-trace.c
new file mode 100644
index 0000000..206ff86
--- /dev/null
+++ b/perf/cairo-analyse-trace.c
@@ -0,0 +1,646 @@
+/* -*- Mode: c; c-basic-offset: 4; indent-tabs-mode: t; tab-width: 8; -*- */
+/*
+ * Copyright © 2006 Mozilla Corporation
+ * Copyright © 2006 Red Hat, Inc.
+ * Copyright © 2009 Chris Wilson
+ * Copyright © 2011 Intel Corporation
+ *
+ * Permission to use, copy, modify, distribute, and sell this software
+ * and its documentation for any purpose is hereby granted without
+ * fee, provided that the above copyright notice appear in all copies
+ * and that both that copyright notice and this permission notice
+ * appear in supporting documentation, and that the name of
+ * the authors not be used in advertising or publicity pertaining to
+ * distribution of the software without specific, written prior
+ * permission. The authors make no representations about the
+ * suitability of this software for any purpose.  It is provided "as
+ * is" without express or implied warranty.
+ *
+ * THE AUTHORS DISCLAIM ALL WARRANTIES WITH REGARD TO THIS
+ * SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND
+ * FITNESS, IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY SPECIAL,
+ * INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER
+ * RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION
+ * OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR
+ * IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ *
+ * Authors: Vladimir Vukicevic <vladimir at pobox.com>
+ *	    Carl Worth <cworth at cworth.org>
+ *	    Chris Wilson <chris at chris-wilson.co.uk>
+ */
+
+#define _GNU_SOURCE 1	/* for sched_getaffinity() and getline() */
+
+#include "../cairo-version.h" /* for the real version */
+
+#include "cairo-perf.h"
+#include "cairo-stats.h"
+
+#include "cairo-boilerplate-getopt.h"
+#include <cairo-script-interpreter.h>
+
+/* rudely reuse bits of the library... */
+#include "../src/cairo-error-private.h"
+
+/* For basename */
+#ifdef HAVE_LIBGEN_H
+#include <libgen.h>
+#endif
+#include <ctype.h> /* isspace() */
+
+#include <sys/types.h>
+#include <sys/stat.h>
+
+#ifdef _MSC_VER
+#include "dirent-win32.h"
+
+typedef SSIZE_T ssize_t;
+
+static char *
+basename_no_ext (char *path)
+{
+    static char name[_MAX_FNAME + 1];
+
+    _splitpath (path, NULL, NULL, name, NULL);
+
+    name[_MAX_FNAME] = '\0';
+
+    return name;
+}
+
+
+#else
+#include <dirent.h>
+
+static char *
+basename_no_ext (char *path)
+{
+    char *dot, *name;
+
+    name = basename (path);
+
+    dot = strchr (name, '.');
+    if (dot)
+	*dot = '\0';
+
+    return name;
+}
+
+#endif
+
+#if HAVE_UNISTD_H
+#include <unistd.h>
+#endif
+
+#include <signal.h>
+
+#if HAVE_FCFINI
+#include <fontconfig/fontconfig.h>
+#endif
+
+struct trace {
+    const cairo_boilerplate_target_t *target;
+    void            *closure;
+    cairo_surface_t *surface;
+};
+
+cairo_bool_t
+cairo_perf_can_run (cairo_perf_t *perf,
+		    const char	 *name,
+		    cairo_bool_t *is_explicit)
+{
+    unsigned int i;
+    char *copy, *dot;
+    cairo_bool_t ret;
+
+    if (is_explicit)
+	*is_explicit = FALSE;
+
+    if (perf->exact_names) {
+	if (is_explicit)
+	    *is_explicit = TRUE;
+	return TRUE;
+    }
+
+    if (perf->num_names == 0 && perf->num_exclude_names == 0)
+	return TRUE;
+
+    copy = xstrdup (name);
+    dot = strchr (copy, '.');
+    if (dot != NULL)
+	*dot = '\0';
+
+    if (perf->num_names) {
+	ret = TRUE;
+	for (i = 0; i < perf->num_names; i++)
+	    if (strstr (copy, perf->names[i])) {
+		if (is_explicit)
+		    *is_explicit = strcmp (copy, perf->names[i]) == 0;
+		goto check_exclude;
+	    }
+
+	ret = FALSE;
+	goto done;
+    }
+
+check_exclude:
+    if (perf->num_exclude_names) {
+	ret = FALSE;
+	for (i = 0; i < perf->num_exclude_names; i++)
+	    if (strstr (copy, perf->exclude_names[i])) {
+		if (is_explicit)
+		    *is_explicit = strcmp (copy, perf->exclude_names[i]) == 0;
+		goto done;
+	    }
+
+	ret = TRUE;
+	goto done;
+    }
+
+done:
+    free (copy);
+
+    return ret;
+}
+
+static cairo_surface_t *
+surface_create (void		 *closure,
+		cairo_content_t  content,
+		double		  width,
+		double		  height,
+		long		  uid)
+{
+    struct trace *args = closure;
+    return cairo_surface_create_similar (args->surface, content, width, height);
+}
+
+static int user_interrupt;
+
+static void
+interrupt (int sig)
+{
+    if (user_interrupt) {
+	signal (sig, SIG_DFL);
+	raise (sig);
+    }
+
+    user_interrupt = 1;
+}
+
+static void
+describe (cairo_perf_t *perf,
+          void *closure)
+{
+    char *description = NULL;
+
+    if (perf->has_described_backend)
+	    return;
+    perf->has_described_backend = TRUE;
+
+    if (perf->target->describe)
+        description = perf->target->describe (closure);
+
+    if (description == NULL)
+        return;
+
+    free (description);
+}
+
+static void
+execute (cairo_perf_t	 *perf,
+	 struct trace	 *args,
+	 const char	 *trace)
+{
+    char *trace_cpy, *name;
+    const cairo_script_interpreter_hooks_t hooks = {
+	.closure = args,
+	.surface_create = surface_create,
+    };
+
+    trace_cpy = xstrdup (trace);
+    name = basename_no_ext (trace_cpy);
+
+    if (perf->list_only) {
+	printf ("%s\n", name);
+	free (trace_cpy);
+	return;
+    }
+
+    describe (perf, args->closure);
+
+    {
+	cairo_script_interpreter_t *csi;
+	cairo_status_t status;
+	unsigned int line_no;
+
+	csi = cairo_script_interpreter_create ();
+	cairo_script_interpreter_install_hooks (csi, &hooks);
+
+	cairo_script_interpreter_run (csi, trace);
+
+	cairo_script_interpreter_finish (csi);
+
+	line_no = cairo_script_interpreter_get_line_number (csi);
+	status = cairo_script_interpreter_destroy (csi);
+	if (status) {
+	    /* XXXX cairo_status_to_string is just wrong! */
+	    fprintf (stderr, "Error during replay, line %d: %s\n",
+		     line_no, cairo_status_to_string (status));
+	}
+    }
+    user_interrupt = 0;
+
+    free (trace_cpy);
+}
+
+static void
+usage (const char *argv0)
+{
+    fprintf (stderr,
+"Usage: %s [-l] [-r] [-v] [-i iterations] [test-names ... | traces ...]\n"
+"       %s -l\n"
+"\n"
+"Run the cairo performance test suite over the given tests (all by default)\n"
+"The command-line arguments are interpreted as follows:\n"
+"\n"
+"  -v	verbose\n"
+"  -x   exclude; specify a file to read a list of traces to exclude\n"
+"  -l	list only; just list selected test case names without executing\n"
+"\n"
+"If test names are given they are used as sub-string matches so a command\n"
+"such as \"cairo-perf-trace firefox\" can be used to run all firefox traces.\n"
+"Alternatively, you can specify a list of filenames to execute.\n",
+	     argv0, argv0);
+}
+
+#ifndef __USE_GNU
+#define POORMANS_GETLINE_BUFFER_SIZE (65536)
+static ssize_t
+getline (char	**lineptr,
+	 size_t  *n,
+	 FILE	 *stream)
+{
+    if (!*lineptr)
+    {
+	*n = POORMANS_GETLINE_BUFFER_SIZE;
+	*lineptr = (char *) malloc (*n);
+    }
+
+    if (!fgets (*lineptr, *n, stream))
+	return -1;
+
+    if (!feof (stream) && !strchr (*lineptr, '\n'))
+    {
+	fprintf (stderr, "The poor man's implementation of getline in "
+			  __FILE__ " needs a bigger buffer. Perhaps it's "
+			 "time for a complete implementation of getline.\n");
+	exit (0);
+    }
+
+    return strlen (*lineptr);
+}
+#undef POORMANS_GETLINE_BUFFER_SIZE
+
+static char *
+strndup (const char *s,
+	 size_t      n)
+{
+    size_t len;
+    char *sdup;
+
+    if (!s)
+	return NULL;
+
+    len = strlen (s);
+    len = (n < len ? n : len);
+    sdup = (char *) malloc (len + 1);
+    if (sdup)
+    {
+	memcpy (sdup, s, len);
+	sdup[len] = '\0';
+    }
+
+    return sdup;
+}
+#endif /* ifndef __USE_GNU */
+
+static cairo_bool_t
+read_excludes (cairo_perf_t *perf,
+	       const char   *filename)
+{
+    FILE *file;
+    char *line = NULL;
+    size_t line_size = 0;
+    char *s, *t;
+
+    file = fopen (filename, "r");
+    if (file == NULL)
+	return FALSE;
+
+    while (getline (&line, &line_size, file) != -1) {
+	/* terminate the line at a comment marker '#' */
+	s = strchr (line, '#');
+	if (s)
+	    *s = '\0';
+
+	/* whitespace delimits */
+	s = line;
+	while (*s != '\0' && isspace (*s))
+	    s++;
+
+	t = s;
+	while (*t != '\0' && ! isspace (*t))
+	    t++;
+
+	if (s != t) {
+	    int i = perf->num_exclude_names;
+	    perf->exclude_names = xrealloc (perf->exclude_names,
+					    sizeof (char *) * (i+1));
+	    perf->exclude_names[i] = strndup (s, t-s);
+	    perf->num_exclude_names++;
+	}
+    }
+    free (line);
+
+    fclose (file);
+
+    return TRUE;
+}
+
+static void
+parse_options (cairo_perf_t *perf,
+	       int	     argc,
+	       char	    *argv[])
+{
+    char *end;
+    int c;
+
+    perf->list_only = FALSE;
+    perf->names = NULL;
+    perf->num_names = 0;
+    perf->exclude_names = NULL;
+    perf->num_exclude_names = 0;
+
+    while (1) {
+	c = _cairo_getopt (argc, argv, "i:x:lrvc");
+	if (c == -1)
+	    break;
+
+	switch (c) {
+	case 'i':
+	    perf->exact_iterations = TRUE;
+	    perf->iterations = strtoul (optarg, &end, 10);
+	    if (*end != '\0') {
+		fprintf (stderr, "Invalid argument for -i (not an integer): %s\n",
+			 optarg);
+		exit (1);
+	    }
+	    break;
+	case 'l':
+	    perf->list_only = TRUE;
+	    break;
+	case 'x':
+	    if (! read_excludes (perf, optarg)) {
+		fprintf (stderr, "Invalid argument for -x (not readable file): %s\n",
+			 optarg);
+		exit (1);
+	    }
+	    break;
+	default:
+	    fprintf (stderr, "Internal error: unhandled option: %c\n", c);
+	    /* fall-through */
+	case '?':
+	    usage (argv[0]);
+	    exit (1);
+	}
+    }
+
+    if (optind < argc) {
+	perf->names = &argv[optind];
+	perf->num_names = argc - optind;
+    }
+}
+
+static void
+cairo_perf_fini (cairo_perf_t *perf)
+{
+    cairo_boilerplate_free_targets (perf->targets);
+    cairo_boilerplate_fini ();
+
+    free (perf->times);
+    cairo_debug_reset_static_data ();
+#if HAVE_FCFINI
+    FcFini ();
+#endif
+}
+
+static cairo_bool_t
+have_trace_filenames (cairo_perf_t *perf)
+{
+    unsigned int i;
+
+    if (perf->num_names == 0)
+	return FALSE;
+
+#if HAVE_UNISTD_H
+    for (i = 0; i < perf->num_names; i++)
+	if (access (perf->names[i], R_OK) == 0)
+	    return TRUE;
+#endif
+
+    return FALSE;
+}
+
+static cairo_status_t
+print (void *closure, const unsigned char *data, unsigned int length)
+{
+    fwrite (data, length, 1, closure);
+    return CAIRO_STATUS_SUCCESS;
+}
+
+static void
+cairo_perf_trace (cairo_perf_t			   *perf,
+		  const cairo_boilerplate_target_t *target,
+		  const char			   *trace)
+{
+    struct trace args;
+    cairo_surface_t *real;
+
+    args.target = target;
+    real = target->create_surface (NULL,
+				   CAIRO_CONTENT_COLOR_ALPHA,
+				   1, 1,
+				   1, 1,
+				   CAIRO_BOILERPLATE_MODE_PERF,
+				   0,
+				   &args.closure);
+    args.surface = cairo_surface_create_observer (real);
+    cairo_surface_destroy (real);
+    if (cairo_surface_status (args.surface)) {
+	fprintf (stderr,
+		 "Error: Failed to create target surface: %s\n",
+		 target->name);
+	return;
+    }
+
+    printf ("Observing '%s'...", trace);
+    fflush (stdout);
+
+    execute (perf, &args, trace);
+
+    printf ("\n");
+    cairo_device_observer_print (cairo_surface_get_device (args.surface),
+				 print, stdout);
+    fflush (stdout);
+
+    cairo_surface_destroy (args.surface);
+
+    if (target->cleanup)
+	target->cleanup (args.closure);
+}
+
+static void
+warn_no_traces (const char *message,
+		const char *trace_dir)
+{
+    fprintf (stderr,
+"Error: %s '%s'.\n"
+"Have you cloned the cairo-traces repository and uncompressed the traces?\n"
+"  git clone git://anongit.freedesktop.org/cairo-traces\n"
+"  cd cairo-traces && make\n"
+"Or set the env.var CAIRO_TRACE_DIR to point to your traces?\n",
+	    message, trace_dir);
+}
+
+static int
+cairo_perf_trace_dir (cairo_perf_t		       *perf,
+		      const cairo_boilerplate_target_t *target,
+		      const char		       *dirname)
+{
+    DIR *dir;
+    struct dirent *de;
+    int num_traces = 0;
+    cairo_bool_t force;
+    cairo_bool_t is_explicit;
+
+    dir = opendir (dirname);
+    if (dir == NULL)
+	return 0;
+
+    force = FALSE;
+    if (cairo_perf_can_run (perf, dirname, &is_explicit))
+	force = is_explicit;
+
+    while ((de = readdir (dir)) != NULL) {
+	char *trace;
+	struct stat st;
+
+	if (de->d_name[0] == '.')
+	    continue;
+
+	xasprintf (&trace, "%s/%s", dirname, de->d_name);
+	if (stat (trace, &st) != 0)
+	    goto next;
+
+	if (S_ISDIR(st.st_mode)) {
+	    num_traces += cairo_perf_trace_dir (perf, target, trace);
+	} else {
+	    const char *dot;
+
+	    dot = strrchr (de->d_name, '.');
+	    if (dot == NULL)
+		goto next;
+	    if (strcmp (dot, ".trace"))
+		goto next;
+
+	    num_traces++;
+	    if (!force && ! cairo_perf_can_run (perf, de->d_name, NULL))
+		goto next;
+
+	    cairo_perf_trace (perf, target, trace);
+	}
+next:
+	free (trace);
+
+    }
+    closedir (dir);
+
+    return num_traces;
+}
+
+int
+main (int   argc,
+      char *argv[])
+{
+    cairo_perf_t perf;
+    const char *trace_dir = "cairo-traces:/usr/src/cairo-traces:/usr/share/cairo-traces";
+    unsigned int n;
+    int i;
+
+    parse_options (&perf, argc, argv);
+
+    signal (SIGINT, interrupt);
+
+    if (getenv ("CAIRO_TRACE_DIR") != NULL)
+	trace_dir = getenv ("CAIRO_TRACE_DIR");
+
+    perf.targets = cairo_boilerplate_get_targets (&perf.num_targets, NULL);
+    perf.times = xmalloc (perf.iterations * sizeof (cairo_perf_ticks_t));
+
+    /* do we have a list of filenames? */
+    perf.exact_names = have_trace_filenames (&perf);
+
+    for (i = 0; i < perf.num_targets; i++) {
+	const cairo_boilerplate_target_t *target = perf.targets[i];
+
+	if (! perf.list_only && ! target->is_measurable)
+	    continue;
+
+	perf.target = target;
+	perf.has_described_backend = FALSE;
+
+	if (perf.exact_names) {
+	    for (n = 0; n < perf.num_names; n++) {
+		struct stat st;
+
+		if (stat (perf.names[n], &st) == 0) {
+		    if (S_ISDIR (st.st_mode)) {
+			cairo_perf_trace_dir (&perf, target, perf.names[n]);
+		    } else
+			cairo_perf_trace (&perf, target, perf.names[n]);
+		}
+	    }
+	} else {
+	    int num_traces = 0;
+	    const char *dir;
+
+	    dir = trace_dir;
+	    do {
+		char buf[1024];
+		const char *end = strchr (dir, ':');
+		if (end != NULL) {
+		    memcpy (buf, dir, end-dir);
+		    buf[end-dir] = '\0';
+		    end++;
+
+		    dir = buf;
+		}
+
+		num_traces += cairo_perf_trace_dir (&perf, target, dir);
+		dir = end;
+	    } while (dir != NULL);
+
+	    if (num_traces == 0) {
+		warn_no_traces ("Found no traces in", trace_dir);
+		return 1;
+	    }
+	}
+
+	if (perf.list_only)
+	    break;
+    }
+
+    cairo_perf_fini (&perf);
+
+    return 0;
+}
diff --git a/src/Makefile.sources b/src/Makefile.sources
index c007d18..a47a010 100644
--- a/src/Makefile.sources
+++ b/src/Makefile.sources
@@ -100,6 +100,7 @@ cairo_private = \
 	cairo-surface-fallback-private.h \
 	cairo-surface-private.h \
 	cairo-surface-clipper-private.h \
+	cairo-surface-observer-private.h \
 	cairo-surface-offset-private.h \
 	cairo-surface-subsurface-private.h \
 	cairo-surface-snapshot-private.h \
@@ -181,6 +182,7 @@ cairo_sources = \
 	cairo-surface.c \
 	cairo-surface-fallback.c \
 	cairo-surface-clipper.c \
+	cairo-surface-observer.c \
 	cairo-surface-offset.c \
 	cairo-surface-snapshot.c \
 	cairo-surface-subsurface.c \
diff --git a/src/Makefile.win32.features b/src/Makefile.win32.features
index 640df73..8cfd6e7 100644
--- a/src/Makefile.win32.features
+++ b/src/Makefile.win32.features
@@ -530,6 +530,16 @@ enabled_cairo_private += $(cairo_recording_private)
 enabled_cairo_cxx_sources += $(cairo_recording_cxx_sources)
 enabled_cairo_sources += $(cairo_recording_sources)
 
+supported_cairo_headers += $(cairo_observer_headers)
+all_cairo_headers += $(cairo_observer_headers)
+all_cairo_private += $(cairo_observer_private)
+all_cairo_cxx_sources += $(cairo_observer_cxx_sources)
+all_cairo_sources += $(cairo_observer_sources)
+enabled_cairo_headers += $(cairo_observer_headers)
+enabled_cairo_private += $(cairo_observer_private)
+enabled_cairo_cxx_sources += $(cairo_observer_cxx_sources)
+enabled_cairo_sources += $(cairo_observer_sources)
+
 unsupported_cairo_headers += $(cairo_tee_headers)
 all_cairo_headers += $(cairo_tee_headers)
 all_cairo_private += $(cairo_tee_private)
diff --git a/src/cairo-surface-observer-private.h b/src/cairo-surface-observer-private.h
new file mode 100644
index 0000000..522be6a
--- /dev/null
+++ b/src/cairo-surface-observer-private.h
@@ -0,0 +1,168 @@
+/* cairo - a vector graphics library with display and print output
+ *
+ * Copyright © 2011 Intel Corporation
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it either under the terms of the GNU Lesser General Public
+ * License version 2.1 as published by the Free Software Foundation
+ * (the "LGPL") or, at your option, under the terms of the Mozilla
+ * Public License Version 1.1 (the "MPL"). If you do not alter this
+ * notice, a recipient may use your version of this file under either
+ * the MPL or the LGPL.
+ *
+ * You should have received a copy of the LGPL along with this library
+ * in the file COPYING-LGPL-2.1; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Suite 500, Boston, MA 02110-1335, USA
+ * You should have received a copy of the MPL along with this library
+ * in the file COPYING-MPL-1.1
+ *
+ * The contents of this file are subject to the Mozilla Public License
+ * Version 1.1 (the "License"); you may not use this file except in
+ * compliance with the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY
+ * OF ANY KIND, either express or implied. See the LGPL or the MPL for
+ * the specific language governing rights and limitations.
+ *
+ * The Original Code is the cairo graphics library.
+ *
+ * The Initial Developer of the Original Code is Intel Corporation.
+ *
+ * Contributor(s):
+ *      Chris Wilson <chris at chris-wilson.co.uk>
+ */
+
+#ifndef CAIRO_SURFACE_SNAPSHOT_PRIVATE_H
+#define CAIRO_SURFACE_SNAPSHOT_PRIVATE_H
+
+#include "cairo-device-private.h"
+#include "cairo-surface-private.h"
+
+struct stat {
+    double min, max, sum, sum_sq;
+    unsigned count;
+};
+
+#define NUM_OPERATORS (CAIRO_OPERATOR_HSL_LUMINOSITY+1)
+#define NUM_CAPS (CAIRO_LINE_CAP_SQUARE+1)
+#define NUM_JOINS (CAIRO_LINE_JOIN_BEVEL+1)
+#define NUM_ANTIALIAS (CAIRO_ANTIALIAS_SUBPIXEL+1)
+#define NUM_FILL_RULE (CAIRO_FILL_RULE_EVEN_ODD+1)
+
+struct extents {
+    struct stat area;
+    unsigned int bounded, unbounded;
+};
+
+struct pattern {
+    unsigned int type[7]; /* native/record/other surface/gradients */
+};
+
+struct path {
+    unsigned int type[5]; /* empty/pixel/rectilinear/straight/curved */
+};
+
+struct clip {
+    unsigned int type[4]; /* none, region, boxes, general */
+};
+
+typedef struct _cairo_observation cairo_observation_t;
+typedef struct _cairo_device_observer cairo_device_observer_t;
+
+struct _cairo_observation {
+    int num_surfaces;
+    int num_contexts;
+    int num_sources_acquired;
+
+    /* XXX put interesting stats here! */
+
+    struct paint {
+	unsigned int count;
+	struct extents extents;
+	unsigned int operators[NUM_OPERATORS];
+	struct pattern source;
+	struct clip clip;
+	unsigned int noop;
+    } paint;
+
+    struct mask {
+	unsigned int count;
+	struct extents extents;
+	unsigned int operators[NUM_OPERATORS];
+	struct pattern source;
+	struct pattern mask;
+	struct clip clip;
+	unsigned int noop;
+    } mask;
+
+    struct fill {
+	unsigned int count;
+	struct extents extents;
+	unsigned int operators[NUM_OPERATORS];
+	struct pattern source;
+	struct path path;
+	unsigned int antialias[NUM_ANTIALIAS];
+	unsigned int fill_rule[NUM_FILL_RULE];
+	struct clip clip;
+	unsigned int noop;
+    } fill;
+
+    struct stroke {
+	unsigned int count;
+	struct extents extents;
+	unsigned int operators[NUM_OPERATORS];
+	unsigned int caps[NUM_CAPS];
+	unsigned int joins[NUM_CAPS];
+	unsigned int antialias[NUM_ANTIALIAS];
+	struct pattern source;
+	struct path path;
+	struct stat line_width;
+	struct clip clip;
+	unsigned int noop;
+    } stroke;
+
+    struct glyphs {
+	unsigned int count;
+	struct extents extents;
+	unsigned int operators[NUM_OPERATORS];
+	struct pattern source;
+	struct clip clip;
+	unsigned int noop;
+    } glyphs;
+};
+
+struct _cairo_device_observer {
+    cairo_device_t base;
+    cairo_device_t *target;
+
+    cairo_observation_t log;
+};
+
+struct _cairo_surface_observer {
+    cairo_surface_t base;
+    cairo_surface_t *target;
+
+    cairo_observation_t log;
+};
+
+static inline cairo_surface_t *
+_cairo_surface_observer_get_target (cairo_surface_t *surface)
+{
+    return ((cairo_surface_observer_t *) surface)->target;
+}
+
+static inline cairo_bool_t
+_cairo_surface_is_observer (cairo_surface_t *surface)
+{
+    return surface->backend->type == (cairo_surface_type_t)CAIRO_INTERNAL_SURFACE_TYPE_OBSERVER;
+}
+
+static inline cairo_bool_t
+_cairo_device_is_observer (cairo_device_t *device)
+{
+    return device->backend->type == (cairo_device_type_t)CAIRO_INTERNAL_DEVICE_TYPE_OBSERVER;
+}
+
+
+#endif /* CAIRO_SURFACE_SNAPSHOT_PRIVATE_H */
diff --git a/src/cairo-surface-observer.c b/src/cairo-surface-observer.c
new file mode 100644
index 0000000..ca11909
--- /dev/null
+++ b/src/cairo-surface-observer.c
@@ -0,0 +1,1009 @@
+/* cairo - a vector graphics library with display and print output
+ *
+ * Copyright © 2011 Intel Corporation
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it either under the terms of the GNU Lesser General Public
+ * License version 2.1 as published by the Free Software Foundation
+ * (the "LGPL") or, at your option, under the terms of the Mozilla
+ * Public License Version 1.1 (the "MPL"). If you do not alter this
+ * notice, a recipient may use your version of this file under either
+ * the MPL or the LGPL.
+ *
+ * You should have received a copy of the LGPL along with this library
+ * in the file COPYING-LGPL-2.1; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Suite 500, Boston, MA 02110-1335, USA
+ * You should have received a copy of the MPL along with this library
+ * in the file COPYING-MPL-1.1
+ *
+ * The contents of this file are subject to the Mozilla Public License
+ * Version 1.1 (the "License"); you may not use this file except in
+ * compliance with the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY
+ * OF ANY KIND, either express or implied. See the LGPL or the MPL for
+ * the specific language governing rights and limitations.
+ *
+ * The Original Code is the cairo graphics library.
+ *
+ * The Initial Developer of the Original Code is Intel Corporation.
+ *
+ * Contributor(s):
+ *      Chris Wilson <chris at chris-wilson.co.uk>
+ */
+
+#include "cairoint.h"
+
+#include "cairo-composite-rectangles-private.h"
+#include "cairo-error-private.h"
+#include "cairo-image-surface-private.h"
+#include "cairo-pattern-private.h"
+#include "cairo-output-stream-private.h"
+#include "cairo-surface-observer-private.h"
+#include "cairo-surface-subsurface-private.h"
+#include "cairo-reference-count-private.h"
+
+static const cairo_surface_backend_t _cairo_surface_observer_backend;
+
+/* observation/stats */
+
+static void init_stats (struct stat *s)
+{
+    s->min = HUGE_VAL;
+    s->max = -HUGE_VAL;
+}
+
+static void init_extents (struct extents *e)
+{
+    init_stats (&e->area);
+}
+
+static void init_pattern (struct pattern *p)
+{
+}
+
+static void init_path (struct path *p)
+{
+}
+
+static void init_clip (struct clip *c)
+{
+}
+
+static void init_paint (struct paint *p)
+{
+    init_extents (&p->extents);
+    init_pattern (&p->source);
+    init_clip (&p->clip);
+}
+
+static void init_mask (struct mask *m)
+{
+    init_extents (&m->extents);
+    init_pattern (&m->source);
+    init_pattern (&m->mask);
+    init_clip (&m->clip);
+}
+
+static void init_fill (struct fill *f)
+{
+    init_extents (&f->extents);
+    init_pattern (&f->source);
+    init_path (&f->path);
+    init_clip (&f->clip);
+}
+
+static void init_stroke (struct stroke *s)
+{
+    init_extents (&s->extents);
+    init_pattern (&s->source);
+    init_path (&s->path);
+    init_clip (&s->clip);
+}
+
+static void init_glyphs (struct glyphs *g)
+{
+    init_extents (&g->extents);
+    init_pattern (&g->source);
+    init_clip (&g->clip);
+}
+
+static void
+log_init (cairo_observation_t *log)
+{
+    memset (log, 0, sizeof(*log));
+
+    init_paint (&log->paint);
+    init_mask (&log->mask);
+    init_fill (&log->fill);
+    init_stroke (&log->stroke);
+    init_glyphs (&log->glyphs);
+}
+
+static cairo_surface_t*
+get_pattern_surface (const cairo_pattern_t *pattern)
+{
+    return ((cairo_surface_pattern_t *)pattern)->surface;
+}
+
+static void
+add_pattern (struct pattern *stats,
+	     const cairo_pattern_t *source,
+	     const cairo_surface_t *target)
+{
+    int classify;
+
+    switch (source->type) {
+    case CAIRO_PATTERN_TYPE_SURFACE:
+	if (get_pattern_surface (source)->type == target->type)
+	    classify = 0;
+	else if (get_pattern_surface (source)->type == CAIRO_SURFACE_TYPE_RECORDING)
+	    classify = 1;
+	else
+	    classify = 2;
+	break;
+    default:
+    case CAIRO_PATTERN_TYPE_SOLID:
+	classify = 3;
+	break;
+    case CAIRO_PATTERN_TYPE_LINEAR:
+	classify = 4;
+	break;
+    case CAIRO_PATTERN_TYPE_RADIAL:
+	classify = 5;
+	break;
+    case CAIRO_PATTERN_TYPE_MESH:
+	classify = 6;
+	break;
+    }
+    stats->type[classify]++;
+}
+
+static void
+add_path (struct path *stats,
+	  const cairo_path_fixed_t *path,
+	  cairo_bool_t is_fill)
+{
+    int classify;
+
+    /* XXX improve for stroke */
+    classify = -1;
+    if (is_fill) {
+	if (path->fill_is_empty)
+	    classify = 0;
+	else if (_cairo_path_fixed_fill_is_rectilinear (path))
+	    classify = 1;
+    } else {
+	if (_cairo_path_fixed_stroke_is_rectilinear (path))
+	    classify = 1;
+    }
+    if (classify == 1 && ! path->fill_maybe_region)
+	classify = 2;
+    classify = 3 + path->has_curve_to != 0;
+    stats->type[classify]++;
+}
+
+static void
+add_clip (struct clip *stats,
+	  const cairo_clip_t *clip)
+{
+    int classify;
+
+    if (clip == NULL)
+	classify = 0;
+    else if (_cairo_clip_is_region (clip))
+	classify = 1;
+    else if (clip->path == NULL)
+	classify = 2;
+    else
+	classify = 3;
+
+    stats->type[classify]++;
+
+}
+
+static void
+stats_add (struct stat *s, double v)
+{
+    if (v < s->min)
+	s->min = v;
+    if (v > s->max)
+	s->max = v;
+    s->sum += v;
+    s->sum_sq += v*v;
+    s->count++;
+}
+
+static void
+add_extents (struct extents *stats,
+	     const cairo_composite_rectangles_t *extents)
+{
+    const cairo_rectangle_int_t *r = extents->is_bounded ? &extents->bounded :&extents->unbounded;
+    stats_add (&stats->area, r->width * r->height);
+    stats->bounded += extents->is_bounded != 0;
+    stats->unbounded += extents->is_bounded == 0;
+}
+
+/* device interface */
+
+static void
+_cairo_device_observer_lock (void *_device)
+{
+    cairo_device_observer_t *device = (cairo_device_observer_t *) _device;
+    cairo_device_acquire (device->target);
+}
+
+static void
+_cairo_device_observer_unlock (void *_device)
+{
+    cairo_device_observer_t *device = (cairo_device_observer_t *) _device;
+    cairo_device_release (device->target);
+}
+
+static cairo_status_t
+_cairo_device_observer_flush (void *_device)
+{
+    cairo_device_observer_t *device = (cairo_device_observer_t *) _device;
+
+    if (device->target == NULL)
+	return CAIRO_STATUS_SUCCESS;
+
+    cairo_device_flush (device->target);
+    return device->target->status;
+}
+
+static void
+_cairo_device_observer_finish (void *_device)
+{
+    cairo_device_observer_t *device = (cairo_device_observer_t *) _device;
+    cairo_device_finish (device->target);
+}
+
+static void
+_cairo_device_observer_destroy (void *_device)
+{
+    cairo_device_observer_t *device = (cairo_device_observer_t *) _device;
+    cairo_device_destroy (device->target);
+    free (device);
+}
+
+static const cairo_device_backend_t _cairo_device_observer_backend = {
+    CAIRO_INTERNAL_DEVICE_TYPE_OBSERVER,
+
+    _cairo_device_observer_lock,
+    _cairo_device_observer_unlock,
+
+    _cairo_device_observer_flush,
+    _cairo_device_observer_finish,
+    _cairo_device_observer_destroy,
+};
+
+static cairo_device_t *
+_cairo_device_create_observer_internal (cairo_device_t *target)
+{
+    cairo_device_observer_t *device;
+
+    device = malloc (sizeof (cairo_device_observer_t));
+    if (unlikely (device == NULL))
+	return _cairo_device_create_in_error (_cairo_error (CAIRO_STATUS_NO_MEMORY));
+
+    _cairo_device_init (&device->base, &_cairo_device_observer_backend);
+    device->target = cairo_device_reference (target);
+
+    log_init (&device->log);
+
+    return &device->base;
+}
+
+/* surface interface */
+
+static cairo_device_observer_t *
+to_device (cairo_surface_observer_t *suface)
+{
+    return (cairo_device_observer_t *)suface->base.device;
+}
+
+static cairo_surface_t *
+_cairo_surface_create_observer_internal (cairo_device_t *device,
+					 cairo_surface_t *target)
+{
+    cairo_surface_observer_t *surface;
+
+    surface = malloc (sizeof (cairo_surface_observer_t));
+    if (unlikely (surface == NULL))
+	return _cairo_surface_create_in_error (_cairo_error (CAIRO_STATUS_NO_MEMORY));
+
+    _cairo_surface_init (&surface->base,
+			 &_cairo_surface_observer_backend, device,
+			 target->content);
+
+    log_init (&surface->log);
+
+    surface->target = cairo_surface_reference (target);
+    surface->base.type = surface->target->type;
+    surface->base.is_clear = surface->target->is_clear;
+
+    surface->log.num_surfaces++;
+    to_device (surface)->log.num_surfaces++;
+
+    return &surface->base;
+}
+
+
+static cairo_status_t
+_cairo_surface_observer_finish (void *abstract_surface)
+{
+    cairo_surface_observer_t *surface = abstract_surface;
+
+    cairo_surface_destroy (surface->target);
+
+    return CAIRO_STATUS_SUCCESS;
+}
+
+static cairo_surface_t *
+_cairo_surface_observer_create_similar (void *abstract_other,
+					cairo_content_t content,
+					int width, int height)
+{
+    cairo_surface_observer_t *other = abstract_other;
+    cairo_surface_t *target, *surface;
+
+    target = NULL;
+    if (other->target->backend->create_similar)
+	target = other->target->backend->create_similar (other->target, content,
+							 width, height);
+    if (target == NULL)
+	target = _cairo_image_surface_create_with_content (content,
+							   width, height);
+
+    surface = _cairo_surface_create_observer_internal (other->base.device,
+						       target);
+    cairo_surface_destroy (target);
+
+    return surface;
+}
+
+static cairo_surface_t *
+_cairo_surface_observer_create_similar_image (void *other,
+					      cairo_format_t format,
+					      int width, int height)
+{
+    cairo_surface_observer_t *surface = other;
+
+    if (surface->target->backend->create_similar_image)
+	return surface->target->backend->create_similar_image (surface->target,
+							       format,
+							       width, height);
+
+    return NULL;
+}
+
+static cairo_surface_t *
+_cairo_surface_observer_map_to_image (void *abstract_surface,
+				      const cairo_rectangle_int_t *extents)
+{
+    cairo_surface_observer_t *surface = abstract_surface;
+
+    if (surface->target->backend->map_to_image == NULL)
+	return NULL;
+
+    return surface->target->backend->map_to_image (surface->target, extents);
+}
+
+static cairo_int_status_t
+_cairo_surface_observer_unmap_image (void *abstract_surface,
+				     cairo_image_surface_t *image)
+{
+    cairo_surface_observer_t *surface = abstract_surface;
+
+    if (surface->target->backend->unmap_image == NULL)
+	return CAIRO_INT_STATUS_UNSUPPORTED;
+
+    return surface->target->backend->unmap_image (surface->target, image);
+}
+
+static cairo_int_status_t
+_cairo_surface_observer_paint (void *abstract_surface,
+			       cairo_operator_t op,
+			       const cairo_pattern_t *source,
+			       const cairo_clip_t *clip)
+{
+    cairo_surface_observer_t *surface = abstract_surface;
+    cairo_device_observer_t *device = to_device (surface);
+    cairo_composite_rectangles_t composite;
+    cairo_rectangle_int_t extents;
+    cairo_int_status_t status;
+
+    /* XXX device locking */
+
+    surface->log.paint.count++;
+    surface->log.paint.operators[op]++;
+    add_pattern (&surface->log.paint.source, source, surface->target);
+    add_clip (&surface->log.paint.clip, clip);
+
+    device->log.paint.count++;
+    device->log.paint.operators[op]++;
+    add_pattern (&device->log.paint.source, source, surface->target);
+    add_clip (&device->log.paint.clip, clip);
+
+    _cairo_surface_get_extents (surface->target, &extents);
+    status = _cairo_composite_rectangles_init_for_paint (&composite,
+							 &extents,
+							 op, source,
+							 clip);
+    if (unlikely (status)) {
+	surface->log.paint.noop++;
+	device->log.paint.noop++;
+	return status;
+    }
+
+    add_extents (&surface->log.paint.extents, &composite);
+    add_extents (&device->log.paint.extents, &composite);
+    _cairo_composite_rectangles_fini (&composite);
+
+    /* XXX time? */
+    return _cairo_surface_paint (surface->target,
+				 op, source,
+				 clip);
+}
+
+static cairo_int_status_t
+_cairo_surface_observer_mask (void *abstract_surface,
+			      cairo_operator_t op,
+			      const cairo_pattern_t *source,
+			      const cairo_pattern_t *mask,
+			      const cairo_clip_t *clip)
+{
+    cairo_surface_observer_t *surface = abstract_surface;
+    cairo_device_observer_t *device = to_device (surface);
+    cairo_composite_rectangles_t composite;
+    cairo_rectangle_int_t extents;
+    cairo_int_status_t status;
+
+    surface->log.mask.count++;
+    surface->log.mask.operators[op]++;
+    add_pattern (&surface->log.mask.source, source, surface->target);
+    add_pattern (&surface->log.mask.mask, mask, surface->target);
+    add_clip (&surface->log.mask.clip, clip);
+
+    device->log.mask.count++;
+    device->log.mask.operators[op]++;
+    add_pattern (&device->log.mask.source, source, surface->target);
+    add_pattern (&device->log.mask.mask, mask, surface->target);
+    add_clip (&device->log.mask.clip, clip);
+
+    _cairo_surface_get_extents (surface->target, &extents);
+    status = _cairo_composite_rectangles_init_for_mask (&composite,
+							&extents,
+							op, source, mask,
+							clip);
+    if (unlikely (status)) {
+	surface->log.mask.noop++;
+	device->log.mask.noop++;
+	return status;
+    }
+
+    add_extents (&surface->log.mask.extents, &composite);
+    add_extents (&device->log.mask.extents, &composite);
+    _cairo_composite_rectangles_fini (&composite);
+
+    return _cairo_surface_mask (surface->target,
+				op, source, mask,
+				clip);
+}
+
+static cairo_int_status_t
+_cairo_surface_observer_fill (void			*abstract_surface,
+			      cairo_operator_t		op,
+			      const cairo_pattern_t	*source,
+			      const cairo_path_fixed_t	*path,
+			      cairo_fill_rule_t		fill_rule,
+			      double			 tolerance,
+			      cairo_antialias_t		antialias,
+			      const cairo_clip_t	*clip)
+{
+    cairo_surface_observer_t *surface = abstract_surface;
+    cairo_device_observer_t *device = to_device (surface);
+    cairo_composite_rectangles_t composite;
+    cairo_rectangle_int_t extents;
+    cairo_int_status_t status;
+
+    surface->log.fill.count++;
+    surface->log.fill.operators[op]++;
+    surface->log.fill.fill_rule[fill_rule]++;
+    surface->log.fill.antialias[antialias]++;
+    add_pattern (&surface->log.fill.source, source, surface->target);
+    add_path (&surface->log.fill.path, path, TRUE);
+    add_clip (&surface->log.fill.clip, clip);
+
+    device->log.fill.count++;
+    device->log.fill.operators[op]++;
+    device->log.fill.fill_rule[fill_rule]++;
+    device->log.fill.antialias[antialias]++;
+    add_pattern (&device->log.fill.source, source, surface->target);
+    add_path (&device->log.fill.path, path, TRUE);
+    add_clip (&device->log.fill.clip, clip);
+
+    _cairo_surface_get_extents (surface->target, &extents);
+    status = _cairo_composite_rectangles_init_for_fill (&composite,
+							&extents,
+							op, source, path,
+							clip);
+    if (unlikely (status)) {
+	surface->log.fill.noop++;
+	device->log.fill.noop++;
+	return status;
+    }
+
+    add_extents (&surface->log.fill.extents, &composite);
+    add_extents (&device->log.fill.extents, &composite);
+    _cairo_composite_rectangles_fini (&composite);
+
+    return _cairo_surface_fill (surface->target,
+				op, source, path,
+				fill_rule, tolerance, antialias,
+				clip);
+}
+
+static cairo_int_status_t
+_cairo_surface_observer_stroke (void				*abstract_surface,
+				cairo_operator_t		 op,
+				const cairo_pattern_t		*source,
+				const cairo_path_fixed_t	*path,
+				const cairo_stroke_style_t	*style,
+				const cairo_matrix_t		*ctm,
+				const cairo_matrix_t		*ctm_inverse,
+				double				 tolerance,
+				cairo_antialias_t		 antialias,
+				const cairo_clip_t		*clip)
+{
+    cairo_surface_observer_t *surface = abstract_surface;
+    cairo_device_observer_t *device = to_device (surface);
+    cairo_composite_rectangles_t composite;
+    cairo_rectangle_int_t extents;
+    cairo_int_status_t status;
+
+    surface->log.stroke.count++;
+    surface->log.stroke.operators[op]++;
+    surface->log.stroke.antialias[antialias]++;
+    add_pattern (&surface->log.stroke.source, source, surface->target);
+    add_path (&surface->log.stroke.path, path, FALSE);
+    add_clip (&surface->log.stroke.clip, clip);
+
+    device->log.stroke.count++;
+    device->log.stroke.operators[op]++;
+    device->log.stroke.antialias[antialias]++;
+    add_pattern (&device->log.stroke.source, source, surface->target);
+    add_path (&device->log.stroke.path, path, FALSE);
+    add_clip (&device->log.stroke.clip, clip);
+
+    _cairo_surface_get_extents (surface->target, &extents);
+    status = _cairo_composite_rectangles_init_for_stroke (&composite,
+							  &extents,
+							  op, source,
+							  path, style, ctm,
+							  clip);
+    if (unlikely (status)) {
+	surface->log.stroke.noop++;
+	device->log.stroke.noop++;
+	return status;
+    }
+
+    add_extents (&surface->log.stroke.extents, &composite);
+    add_extents (&device->log.stroke.extents, &composite);
+    _cairo_composite_rectangles_fini (&composite);
+
+    return _cairo_surface_stroke (surface->target,
+				  op, source, path,
+				  style, ctm, ctm_inverse,
+				  tolerance, antialias,
+				  clip);
+}
+
+static cairo_int_status_t
+_cairo_surface_observer_glyphs (void			*abstract_surface,
+				cairo_operator_t	 op,
+				const cairo_pattern_t	*source,
+				cairo_glyph_t		*glyphs,
+				int			 num_glyphs,
+				cairo_scaled_font_t	*scaled_font,
+				const cairo_clip_t		*clip,
+				int *remaining_glyphs)
+{
+    cairo_surface_observer_t *surface = abstract_surface;
+    cairo_device_observer_t *device = to_device (surface);
+    cairo_composite_rectangles_t composite;
+    cairo_rectangle_int_t extents;
+    cairo_int_status_t status;
+
+    surface->log.glyphs.count++;
+    surface->log.glyphs.operators[op]++;
+    add_pattern (&surface->log.glyphs.source, source, surface->target);
+    add_clip (&surface->log.glyphs.clip, clip);
+
+    device->log.glyphs.count++;
+    device->log.glyphs.operators[op]++;
+    add_pattern (&device->log.glyphs.source, source, surface->target);
+    add_clip (&device->log.glyphs.clip, clip);
+
+    _cairo_surface_get_extents (surface->target, &extents);
+    status = _cairo_composite_rectangles_init_for_glyphs (&composite,
+							  &extents,
+							  op, source,
+							  scaled_font,
+							  glyphs, num_glyphs,
+							  clip,
+							  NULL);
+    if (unlikely (status)) {
+	surface->log.glyphs.noop++;
+	device->log.glyphs.noop++;
+	return status;
+    }
+
+    add_extents (&surface->log.glyphs.extents, &composite);
+    add_extents (&device->log.glyphs.extents, &composite);
+    _cairo_composite_rectangles_fini (&composite);
+
+    *remaining_glyphs = 0;
+    return _cairo_surface_show_text_glyphs (surface->target, op, source,
+					    NULL, 0,
+					    glyphs, num_glyphs,
+					    NULL, 0, 0,
+					    scaled_font,
+					    clip);
+}
+
+static cairo_status_t
+_cairo_surface_observer_flush (void *abstract_surface)
+{
+    cairo_surface_observer_t *surface = abstract_surface;
+
+    cairo_surface_flush (surface->target);
+    return surface->target->status;
+}
+
+static cairo_status_t
+_cairo_surface_observer_mark_dirty (void *abstract_surface,
+				      int x, int y,
+				      int width, int height)
+{
+    cairo_surface_observer_t *surface = abstract_surface;
+    cairo_status_t status;
+
+    printf ("mark-dirty (%d, %d) x (%d, %d)\n", x, y, width, height);
+
+    status = CAIRO_STATUS_SUCCESS;
+    if (surface->target->backend->mark_dirty_rectangle)
+	status = surface->target->backend->mark_dirty_rectangle (surface->target,
+						       x,y, width,height);
+
+    return status;
+}
+
+static cairo_int_status_t
+_cairo_surface_observer_copy_page (void *abstract_surface)
+{
+    cairo_surface_observer_t *surface = abstract_surface;
+    cairo_status_t status;
+
+    status = CAIRO_STATUS_SUCCESS;
+    if (surface->target->backend->copy_page)
+	status = surface->target->backend->copy_page (surface->target);
+
+    return status;
+}
+
+static cairo_int_status_t
+_cairo_surface_observer_show_page (void *abstract_surface)
+{
+    cairo_surface_observer_t *surface = abstract_surface;
+    cairo_status_t status;
+
+    status = CAIRO_STATUS_SUCCESS;
+    if (surface->target->backend->show_page)
+	status = surface->target->backend->show_page (surface->target);
+
+    return status;
+}
+
+static cairo_bool_t
+_cairo_surface_observer_get_extents (void *abstract_surface,
+				     cairo_rectangle_int_t *extents)
+{
+    cairo_surface_observer_t *surface = abstract_surface;
+    return _cairo_surface_get_extents (surface->target, extents);
+}
+
+static void
+_cairo_surface_observer_get_font_options (void *abstract_surface,
+					  cairo_font_options_t *options)
+{
+    cairo_surface_observer_t *surface = abstract_surface;
+
+    if (surface->target->backend->get_font_options != NULL)
+	surface->target->backend->get_font_options (surface->target, options);
+}
+
+static cairo_status_t
+_cairo_surface_observer_acquire_source_image (void                    *abstract_surface,
+						cairo_image_surface_t  **image_out,
+						void                   **image_extra)
+{
+    cairo_surface_observer_t *surface = abstract_surface;
+
+    surface->log.num_sources_acquired++;
+    to_device (surface)->log.num_sources_acquired++;
+
+    return _cairo_surface_acquire_source_image (surface->target,
+						image_out, image_extra);
+}
+
+static void
+_cairo_surface_observer_release_source_image (void                   *abstract_surface,
+						cairo_image_surface_t  *image,
+						void                   *image_extra)
+{
+    cairo_surface_observer_t *surface = abstract_surface;
+
+    return _cairo_surface_release_source_image (surface->target, image, image_extra);
+}
+
+static cairo_surface_t *
+_cairo_surface_observer_snapshot (void *abstract_surface)
+{
+    cairo_surface_observer_t *surface = abstract_surface;
+
+    printf ("taking snapshot\n");
+
+    /* XXX hook onto the snapshot so that we measure number of reads */
+
+    if (surface->target->backend->snapshot)
+	return surface->target->backend->snapshot (surface->target);
+
+    return NULL;
+}
+
+static cairo_t *
+_cairo_surface_observer_create_context(void *target)
+{
+    cairo_surface_observer_t *surface = target;
+
+    if (_cairo_surface_is_subsurface (&surface->base))
+	surface = (cairo_surface_observer_t *)
+	    _cairo_surface_subsurface_get_target (&surface->base);
+
+    surface->log.num_contexts++;
+    to_device (surface)->log.num_contexts++;
+
+    return surface->target->backend->create_context (target);
+}
+
+static const cairo_surface_backend_t _cairo_surface_observer_backend = {
+    CAIRO_INTERNAL_SURFACE_TYPE_OBSERVER,
+    _cairo_surface_observer_finish,
+
+    _cairo_surface_observer_create_context,
+
+    _cairo_surface_observer_create_similar,
+    _cairo_surface_observer_create_similar_image,
+    _cairo_surface_observer_map_to_image,
+    _cairo_surface_observer_unmap_image,
+
+    _cairo_surface_observer_acquire_source_image,
+    _cairo_surface_observer_release_source_image,
+
+    NULL, NULL, /* acquire, release dest */
+    NULL, /* clone similar */
+    NULL, /* composite */
+    NULL, /* fill rectangles */
+    NULL, /* composite trapezoids */
+    NULL, /* create span renderer */
+    NULL, /* check span renderer */
+
+    _cairo_surface_observer_copy_page,
+    _cairo_surface_observer_show_page,
+
+    _cairo_surface_observer_get_extents,
+    NULL, /* old_show_glyphs */
+    _cairo_surface_observer_get_font_options,
+    _cairo_surface_observer_flush,
+    _cairo_surface_observer_mark_dirty,
+    NULL, /* font_fini */
+    NULL, /* glyph_fini */
+
+    _cairo_surface_observer_paint,
+    _cairo_surface_observer_mask,
+    _cairo_surface_observer_stroke,
+    _cairo_surface_observer_fill,
+    _cairo_surface_observer_glyphs,
+
+    _cairo_surface_observer_snapshot,
+};
+
+/**
+ * cairo_surface_create_observer:
+ * @target: an existing surface for which the observer will watch
+ *
+ * Create a new surface that exists solely to watch another is doing. In
+ * the process it will log operations and times, which are fast, which are
+ * slow, which are frequent, etc.
+ *
+ * Return value: a pointer to the newly allocated surface. The caller
+ * owns the surface and should call cairo_surface_destroy() when done
+ * with it.
+ *
+ * This function always returns a valid pointer, but it will return a
+ * pointer to a "nil" surface if @other is already in an error state
+ * or any other error occurs.
+ *
+ * Since: 1.12
+ **/
+cairo_surface_t *
+cairo_surface_create_observer (cairo_surface_t *target)
+{
+    cairo_device_t *device;
+    cairo_surface_t *surface;
+
+    if (unlikely (target->status))
+	return _cairo_surface_create_in_error (target->status);
+    if (unlikely (target->finished))
+	return _cairo_surface_create_in_error (_cairo_error (CAIRO_STATUS_SURFACE_FINISHED));
+
+    device = _cairo_device_create_observer_internal (target->device);
+    if (unlikely (device->status))
+	return _cairo_surface_create_in_error (device->status);
+
+    surface = _cairo_surface_create_observer_internal (device, target);
+    cairo_device_destroy (device);
+
+    return surface;
+}
+
+static void
+print_extents (cairo_output_stream_t *stream, const struct extents *e)
+{
+    _cairo_output_stream_printf (stream,
+				 "  extents: total %g, avg %g [unbounded %d]\n",
+				 e->area.sum,
+				 e->area.sum / e->area.count,
+				 e->unbounded);
+}
+
+static void
+print_pattern (cairo_output_stream_t *stream,
+	       const char *name,
+	       const struct pattern *p)
+{
+    _cairo_output_stream_printf (stream,
+				 "  %s: %d native, %d record, %d other surface, %d solid, %d linear, %d radial, %d mesh\n",
+				 name,
+				 p->type[0],
+				 p->type[1],
+				 p->type[2],
+				 p->type[3],
+				 p->type[4],
+				 p->type[5],
+				 p->type[6]);
+}
+
+static void
+print_path (cairo_output_stream_t *stream,
+	    const struct path *p)
+{
+    _cairo_output_stream_printf (stream,
+				 "  path: %d empty, %d pixel-aligned, %d rectilinear, %d straight, %d curved\n",
+				 p->type[0],
+				 p->type[1],
+				 p->type[2],
+				 p->type[3],
+				 p->type[4]);
+}
+
+static void
+print_clip (cairo_output_stream_t *stream, const struct clip *c)
+{
+    _cairo_output_stream_printf (stream,
+				 "  clip: %d none, %d region, %d boxes, %d general path\n",
+				 c->type[0],
+				 c->type[1],
+				 c->type[2],
+				 c->type[3]);
+}
+
+static void
+_cairo_observation_print (cairo_output_stream_t *stream,
+				  cairo_observation_t *log)
+{
+    _cairo_output_stream_printf (stream, "surfaces: %d\n",
+				 log->num_surfaces);
+    _cairo_output_stream_printf (stream, "contexts: %d\n",
+				 log->num_contexts);
+    _cairo_output_stream_printf (stream, "sources acquired: %d\n",
+				 log->num_sources_acquired);
+
+    _cairo_output_stream_printf (stream, "paint: count %d [no-op %d]\n",
+				 log->paint.count, log->paint.noop);
+    if (log->paint.count) {
+	print_extents (stream, &log->paint.extents);
+	print_pattern (stream, "source", &log->paint.source);
+	print_clip (stream, &log->paint.clip);
+    }
+
+    _cairo_output_stream_printf (stream, "mask: count %d [no-op %d]\n",
+				 log->mask.count, log->mask.noop);
+    if (log->mask.count) {
+	print_extents (stream, &log->mask.extents);
+	print_pattern (stream, "source", &log->mask.source);
+	print_pattern (stream, "mask", &log->mask.mask);
+	print_clip (stream, &log->mask.clip);
+    }
+
+    _cairo_output_stream_printf (stream, "fill: count %d [no-op %d]\n",
+				 log->fill.count, log->fill.noop);
+    if (log->fill.count) {
+	print_extents (stream, &log->fill.extents);
+	print_pattern (stream, "source", &log->fill.source);
+	print_path (stream, &log->fill.path);
+	print_clip (stream, &log->fill.clip);
+    }
+
+    _cairo_output_stream_printf (stream, "stroke: count %d [no-op %d]\n",
+				 log->stroke.count, log->stroke.noop);
+    if (log->stroke.count) {
+	print_extents (stream, &log->stroke.extents);
+	print_pattern (stream, "source", &log->stroke.source);
+	print_path (stream, &log->stroke.path);
+	print_clip (stream, &log->stroke.clip);
+    }
+
+    _cairo_output_stream_printf (stream, "glyphs: count %d [no-op %d]\n",
+				 log->glyphs.count, log->glyphs.noop);
+    if (log->glyphs.count) {
+	print_extents (stream, &log->glyphs.extents);
+	print_pattern (stream, "source", &log->glyphs.source);
+	print_clip (stream, &log->glyphs.clip);
+    }
+}
+
+void
+cairo_surface_observer_print (cairo_surface_t *abstract_surface,
+			      cairo_write_func_t write_func,
+			      void *closure)
+{
+    cairo_output_stream_t *stream;
+    cairo_surface_observer_t *surface;
+
+    if (unlikely (CAIRO_REFERENCE_COUNT_IS_INVALID (&abstract_surface->ref_count)))
+	return;
+
+    if (! _cairo_surface_is_observer (abstract_surface))
+	return;
+
+    surface = (cairo_surface_observer_t *) abstract_surface;
+
+    stream = _cairo_output_stream_create (write_func, NULL, closure);
+    _cairo_observation_print (stream, &surface->log);
+    _cairo_output_stream_destroy (stream);
+}
+
+void
+cairo_device_observer_print (cairo_device_t *abstract_device,
+			     cairo_write_func_t write_func,
+			     void *closure)
+{
+    cairo_output_stream_t *stream;
+    cairo_device_observer_t *device;
+
+    if (unlikely (CAIRO_REFERENCE_COUNT_IS_INVALID (&abstract_device->ref_count)))
+	return;
+
+    if (! _cairo_device_is_observer (abstract_device))
+	return;
+
+    device = (cairo_device_observer_t *) abstract_device;
+
+    stream = _cairo_output_stream_create (write_func, NULL, closure);
+    _cairo_observation_print (stream, &device->log);
+    _cairo_output_stream_destroy (stream);
+}
diff --git a/src/cairo-surface.c b/src/cairo-surface.c
index 236ab42..6851ca9 100644
--- a/src/cairo-surface.c
+++ b/src/cairo-surface.c
@@ -562,6 +562,8 @@ cairo_surface_create_similar_image (cairo_surface_t  *other,
 				    int		width,
 				    int		height)
 {
+    cairo_surface_t *image;
+
     if (other->status)
 	return _cairo_surface_create_in_error (other->status);
     if (unlikely (other->finished))
@@ -572,11 +574,13 @@ cairo_surface_create_similar_image (cairo_surface_t  *other,
     if (unlikely (! CAIRO_FORMAT_VALID (format)))
 	return _cairo_surface_create_in_error (_cairo_error (CAIRO_STATUS_INVALID_FORMAT));
 
+    image = NULL;
     if (other->backend->create_similar_image)
-	return other->backend->create_similar_image (other,
-						    format, width, height);
-
-    return cairo_image_surface_create (format, width, height);
+	image = other->backend->create_similar_image (other,
+						      format, width, height);
+    if (image == NULL)
+	image = cairo_image_surface_create (format, width, height);
+    return image;
 }
 
 /**
diff --git a/src/cairo-types-private.h b/src/cairo-types-private.h
index 4933cf2..0b57ec2 100644
--- a/src/cairo-types-private.h
+++ b/src/cairo-types-private.h
@@ -82,6 +82,7 @@ typedef struct _cairo_scaled_font_subsets cairo_scaled_font_subsets_t;
 typedef struct _cairo_solid_pattern cairo_solid_pattern_t;
 typedef struct _cairo_surface_attributes cairo_surface_attributes_t;
 typedef struct _cairo_surface_backend cairo_surface_backend_t;
+typedef struct _cairo_surface_observer cairo_surface_observer_t;
 typedef struct _cairo_surface_snapshot cairo_surface_snapshot_t;
 typedef struct _cairo_surface_subsurface cairo_surface_subsurface_t;
 typedef struct _cairo_surface_wrapper cairo_surface_wrapper_t;
@@ -231,6 +232,7 @@ typedef enum _cairo_internal_surface_type {
     CAIRO_INTERNAL_SURFACE_TYPE_SNAPSHOT = 0x1000,
     CAIRO_INTERNAL_SURFACE_TYPE_PAGINATED,
     CAIRO_INTERNAL_SURFACE_TYPE_ANALYSIS,
+    CAIRO_INTERNAL_SURFACE_TYPE_OBSERVER,
     CAIRO_INTERNAL_SURFACE_TYPE_TEST_FALLBACK,
     CAIRO_INTERNAL_SURFACE_TYPE_TEST_PAGINATED,
     CAIRO_INTERNAL_SURFACE_TYPE_TEST_WRAPPING,
@@ -238,6 +240,10 @@ typedef enum _cairo_internal_surface_type {
     CAIRO_INTERNAL_SURFACE_TYPE_TYPE3_GLYPH
 } cairo_internal_surface_type_t;
 
+typedef enum _cairo_internal_device_type {
+    CAIRO_INTERNAL_DEVICE_TYPE_OBSERVER = 0x1000,
+} cairo_device_surface_type_t;
+
 #define CAIRO_HAS_TEST_PAGINATED_SURFACE 1
 #define CAIRO_HAS_TEST_NULL_SURFACE 1
 #define CAIRO_HAS_TEST_WRAPPING_SURFACE 1
diff --git a/src/cairo.h b/src/cairo.h
index 9d788cc..bf2e232 100644
--- a/src/cairo.h
+++ b/src/cairo.h
@@ -2096,6 +2096,19 @@ cairo_surface_create_for_rectangle (cairo_surface_t	*target,
                                     double		 height);
 
 cairo_public cairo_surface_t *
+cairo_surface_create_observer (cairo_surface_t *target);
+
+cairo_public void
+cairo_surface_observer_print (cairo_surface_t *surface,
+			      cairo_write_func_t write_func,
+			      void *closure);
+
+cairo_public void
+cairo_device_observer_print (cairo_device_t *device,
+			     cairo_write_func_t write_func,
+			     void *closure);
+
+cairo_public cairo_surface_t *
 cairo_surface_reference (cairo_surface_t *surface);
 
 cairo_public void


More information about the cairo-commit mailing list