diff --git a/boilerplate/Makefile.win32.features b/boilerplate/Makefile.win32.features index 0cf7095b7..cb33fe539 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 e35cc6c5e..e49ad6e6d 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 4cccd9cf3..4f774e308 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 f659729d1..50efecc9c 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 bc8b9bf10..3ac12bbc2 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 d41891f6c..3ac60da58 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 +noinst_PROGRAMS = \ + cairo-analyse-trace \ + cairo-perf-trace \ + cairo-perf-micro \ + $(NULL) -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 +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 9867ea526..0c15ae9c7 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 000000000..206ff8612 --- /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 + * Carl Worth + * Chris Wilson + */ + +#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 + +/* rudely reuse bits of the library... */ +#include "../src/cairo-error-private.h" + +/* For basename */ +#ifdef HAVE_LIBGEN_H +#include +#endif +#include /* isspace() */ + +#include +#include + +#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 + +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 +#endif + +#include + +#if HAVE_FCFINI +#include +#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 c007d18f8..a47a01087 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 640df730b..8cfd6e76a 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 000000000..522be6a80 --- /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 + */ + +#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 000000000..ca1190954 --- /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 + */ + +#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 236ab425b..6851ca92d 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 4933cf260..0b57ec28c 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 9d788cc88..bf2e232bc 100644 --- a/src/cairo.h +++ b/src/cairo.h @@ -2095,6 +2095,19 @@ cairo_surface_create_for_rectangle (cairo_surface_t *target, double width, 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);