summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorChris Wilson <chris@chris-wilson.co.uk>2011-08-14 18:11:26 +0100
committerChris Wilson <chris@chris-wilson.co.uk>2011-08-14 20:54:53 +0100
commiteee66899cdbd2d431b08b468ac2b285bb855e6da (patch)
tree08deff81060ee06329c1d5b77989713e21207502
parentf6fc6f1ad0315d51b6b395749f8035fb7dcccbbc (diff)
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@chris-wilson.co.uk>
-rw-r--r--boilerplate/Makefile.win32.features10
-rw-r--r--build/Makefile.win32.features-h1
-rw-r--r--build/configure.ac.features1
-rw-r--r--configure.ac1
-rw-r--r--perf/.gitignore1
-rw-r--r--perf/Makefile.am35
-rw-r--r--perf/Makefile.sources3
-rw-r--r--perf/cairo-analyse-trace.c646
-rw-r--r--src/Makefile.sources2
-rw-r--r--src/Makefile.win32.features10
-rw-r--r--src/cairo-surface-observer-private.h168
-rw-r--r--src/cairo-surface-observer.c1009
-rw-r--r--src/cairo-surface.c12
-rw-r--r--src/cairo-types-private.h6
-rw-r--r--src/cairo.h13
15 files changed, 1905 insertions, 13 deletions
diff --git a/boilerplate/Makefile.win32.features b/boilerplate/Makefile.win32.features
index 0cf7095b..cb33fe53 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 e35cc6c5..e49ad6e6 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 4cccd9cf..4f774e30 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 f659729d..50efecc9 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 bc8b9bf1..3ac12bbc 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 d41891f6..3ac60da5 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 9867ea52..0c15ae9c 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 00000000..206ff861
--- /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@pobox.com>
+ * Carl Worth <cworth@cworth.org>
+ * Chris Wilson <chris@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 c007d18f..a47a0108 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 640df730..8cfd6e76 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 00000000..522be6a8
--- /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@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 00000000..ca119095
--- /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@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 236ab425..6851ca92 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 4933cf26..0b57ec28 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 9d788cc8..bf2e232b 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