Lines
0 %
Functions
Branches
/* 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-surface-observer-private.h"
#include "cairo-surface-observer-inline.h"
#include "cairo-array-private.h"
#include "cairo-combsort-inline.h"
#include "cairo-composite-rectangles-private.h"
#include "cairo-error-private.h"
#include "cairo-image-surface-private.h"
#include "cairo-list-inline.h"
#include "cairo-pattern-private.h"
#include "cairo-output-stream-private.h"
#include "cairo-recording-surface-private.h"
#include "cairo-surface-subsurface-inline.h"
#include "cairo-reference-count-private.h"
#if CAIRO_HAS_SCRIPT_SURFACE
#include "cairo-script-private.h"
#endif
/**
* SECTION:cairo-surface-observer
* @Title: Surface Observer
* @Short_Description: Observing other surfaces
* @See_Also: #cairo_surface_t
* A surface that exists solely to watch what another surface is doing.
**/
* CAIRO_HAS_OBSERVER_SURFACE:
* Defined if the observer surface backend is available.
* This macro can be used to conditionally compile backend-specific code.
* Since: 1.12
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 cairo_status_t
log_init (cairo_observation_t *log,
cairo_bool_t record)
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);
_cairo_array_init (&log->timings, sizeof (cairo_observation_record_t));
if (record) {
log->record = (cairo_recording_surface_t *)
cairo_recording_surface_create (CAIRO_CONTENT_COLOR_ALPHA, NULL);
if (unlikely (log->record->base.status))
return log->record->base.status;
log->record->optimize_clears = FALSE;
return CAIRO_STATUS_SUCCESS;
static void
log_fini (cairo_observation_t *log)
_cairo_array_fini (&log->timings);
cairo_surface_destroy (&log->record->base);
static cairo_surface_t*
get_pattern_surface (const cairo_pattern_t *pattern)
return ((cairo_surface_pattern_t *)pattern)->surface;
static int
classify_pattern (const cairo_pattern_t *pattern,
const cairo_surface_t *target)
int classify;
switch (pattern->type) {
case CAIRO_PATTERN_TYPE_SURFACE:
if (get_pattern_surface (pattern)->type == target->type)
classify = 0;
else if (get_pattern_surface (pattern)->type == CAIRO_SURFACE_TYPE_RECORDING)
classify = 1;
else
classify = 2;
break;
default:
case CAIRO_PATTERN_TYPE_SOLID:
classify = 3;
case CAIRO_PATTERN_TYPE_LINEAR:
classify = 4;
case CAIRO_PATTERN_TYPE_RADIAL:
classify = 5;
case CAIRO_PATTERN_TYPE_MESH:
classify = 6;
case CAIRO_PATTERN_TYPE_RASTER_SOURCE:
classify = 7;
return classify;
add_pattern (struct pattern *stats,
const cairo_pattern_t *pattern,
stats->type[classify_pattern(pattern, target)]++;
classify_path (const cairo_path_fixed_t *path,
cairo_bool_t is_fill)
/* XXX improve for stroke */
classify = -1;
if (is_fill) {
if (path->fill_is_empty)
else if (_cairo_path_fixed_fill_is_rectilinear (path))
classify = path->fill_maybe_region ? 1 : 2;
} else {
if (_cairo_path_fixed_stroke_is_rectilinear (path))
if (classify == -1)
classify = 3 + (path->has_curve_to != 0);
add_path (struct path *stats,
const cairo_path_fixed_t *path,
stats->type[classify_path(path, is_fill)]++;
classify_clip (const cairo_clip_t *clip)
if (clip == NULL)
else if (_cairo_clip_is_region (clip))
else if (clip->path == NULL)
else if (clip->path->prev == NULL)
else if (_cairo_clip_is_polygon (clip))
add_clip (struct clip *stats,
const cairo_clip_t *clip)
stats->type[classify_clip (clip)]++;
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++;
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 */
_cairo_device_observer_lock (void *_device)
cairo_device_observer_t *device = (cairo_device_observer_t *) _device;
cairo_status_t ignored;
/* cairo_device_acquire() can fail for nil and finished
* devices. We don't care about observing them. */
ignored = cairo_device_acquire (device->target);
_cairo_device_observer_unlock (void *_device)
cairo_device_release (device->target);
_cairo_device_observer_flush (void *_device)
if (device->target == NULL)
cairo_device_flush (device->target);
return device->target->status;
_cairo_device_observer_finish (void *_device)
log_fini (&device->log);
cairo_device_finish (device->target);
_cairo_device_observer_destroy (void *_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;
cairo_status_t status;
device = _cairo_calloc (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);
status = log_init (&device->log, record);
if (unlikely (status)) {
return _cairo_device_create_in_error (status);
device->target = cairo_device_reference (target);
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 = _cairo_calloc (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,
target->is_vector);
status = log_init (&surface->log,
((cairo_device_observer_t *)device)->log.record != NULL);
free (surface);
return _cairo_surface_create_in_error (status);
surface->target = cairo_surface_reference (target);
surface->base.type = surface->target->type;
surface->base.is_clear = surface->target->is_clear;
cairo_list_init (&surface->paint_callbacks);
cairo_list_init (&surface->mask_callbacks);
cairo_list_init (&surface->fill_callbacks);
cairo_list_init (&surface->stroke_callbacks);
cairo_list_init (&surface->glyphs_callbacks);
cairo_list_init (&surface->flush_callbacks);
cairo_list_init (&surface->finish_callbacks);
surface->log.num_surfaces++;
to_device (surface)->log.num_surfaces++;
return &surface->base;
static inline void
do_callbacks (cairo_surface_observer_t *surface, cairo_list_t *head)
struct callback_list *cb;
cairo_list_foreach_entry (cb, struct callback_list, head, link)
cb->func (&surface->base, surface->target, cb->data);
_cairo_surface_observer_finish (void *abstract_surface)
cairo_surface_observer_t *surface = abstract_surface;
do_callbacks (surface, &surface->finish_callbacks);
cairo_surface_destroy (surface->target);
log_fini (&surface->log);
_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,
surface = _cairo_surface_create_observer_internal (other->base.device,
target);
cairo_surface_destroy (target);
return surface;
_cairo_surface_observer_create_similar_image (void *other,
cairo_format_t format,
cairo_surface_observer_t *surface = other;
if (surface->target->backend->create_similar_image)
return surface->target->backend->create_similar_image (surface->target,
format,
return NULL;
static cairo_image_surface_t *
_cairo_surface_observer_map_to_image (void *abstract_surface,
const cairo_rectangle_int_t *extents)
return _cairo_surface_map_to_image (surface->target, extents);
static cairo_int_status_t
_cairo_surface_observer_unmap_image (void *abstract_surface,
cairo_image_surface_t *image)
return _cairo_surface_unmap_image (surface->target, image);
record_target (cairo_observation_record_t *r,
cairo_rectangle_int_t extents;
r->target_content = target->content;
if (_cairo_surface_get_extents (target, &extents)) {
r->target_width = extents.width;
r->target_height = extents.height;
r->target_width = -1;
r->target_height = -1;
static cairo_observation_record_t *
record_paint (cairo_observation_record_t *r,
cairo_surface_t *target,
cairo_operator_t op,
const cairo_pattern_t *source,
const cairo_clip_t *clip,
cairo_time_t elapsed)
record_target (r, target);
r->op = op;
r->source = classify_pattern (source, target);
r->mask = -1;
r->num_glyphs = -1;
r->path = -1;
r->fill_rule = -1;
r->tolerance = -1;
r->antialias = -1;
r->clip = classify_clip (clip);
r->elapsed = elapsed;
return r;
record_mask (cairo_observation_record_t *r,
const cairo_pattern_t *mask,
r->mask = classify_pattern (mask, target);
record_fill (cairo_observation_record_t *r,
cairo_fill_rule_t fill_rule,
double tolerance,
cairo_antialias_t antialias,
r->path = classify_path (path, TRUE);
r->fill_rule = fill_rule;
r->tolerance = tolerance;
r->antialias = antialias;
record_stroke (cairo_observation_record_t *r,
const cairo_stroke_style_t *style,
const cairo_matrix_t *ctm,
const cairo_matrix_t *ctm_inverse,
r->path = classify_path (path, FALSE);
record_glyphs (cairo_observation_record_t *r,
cairo_glyph_t *glyphs,
int num_glyphs,
cairo_scaled_font_t *scaled_font,
r->num_glyphs = num_glyphs;
add_record (cairo_observation_t *log,
cairo_observation_record_t *r)
cairo_int_status_t status;
r->index = log->record ? log->record->commands.num_elements : 0;
status = _cairo_array_append (&log->timings, r);
assert (status == CAIRO_INT_STATUS_SUCCESS);
_cairo_surface_sync (cairo_surface_t *target, int x, int y)
extents.x = x;
extents.y = y;
extents.width = 1;
extents.height = 1;
_cairo_surface_unmap_image (target,
_cairo_surface_map_to_image (target,
&extents));
midpt (const cairo_composite_rectangles_t *extents, int *x, int *y)
*x = extents->bounded.x + extents->bounded.width / 2;
*y = extents->bounded.y + extents->bounded.height / 2;
add_record_paint (cairo_observation_t *log,
cairo_observation_record_t record;
add_record (log,
record_paint (&record, target, op, source, clip, elapsed));
/* We have to bypass the high-level surface layer in case it tries to be
* too smart and discard operations; we need to record exactly what just
* happened on the target.
if (log->record) {
status = log->record->base.backend->paint (&log->record->base,
op, source, clip);
if (_cairo_time_gt (elapsed, log->paint.slowest.elapsed))
log->paint.slowest = record;
log->paint.elapsed = _cairo_time_add (log->paint.elapsed, elapsed);
_cairo_surface_observer_paint (void *abstract_surface,
cairo_device_observer_t *device = to_device (surface);
cairo_composite_rectangles_t composite;
cairo_time_t t;
int x, y;
/* 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);
status = _cairo_composite_rectangles_init_for_paint (&composite,
surface->target,
op, source,
clip);
surface->log.paint.noop++;
device->log.paint.noop++;
return status;
midpt (&composite, &x, &y);
add_extents (&surface->log.paint.extents, &composite);
add_extents (&device->log.paint.extents, &composite);
_cairo_composite_rectangles_fini (&composite);
t = _cairo_time_get ();
status = _cairo_surface_paint (surface->target,
if (unlikely (status))
_cairo_surface_sync (surface->target, x, y);
t = _cairo_time_get_delta (t);
add_record_paint (&surface->log, surface->target, op, source, clip, t);
add_record_paint (&device->log, surface->target, op, source, clip, t);
do_callbacks (surface, &surface->paint_callbacks);
add_record_mask (cairo_observation_t *log,
record_mask (&record, target, op, source, mask, clip, elapsed));
status = log->record->base.backend->mask (&log->record->base,
op, source, mask, clip);
if (_cairo_time_gt (elapsed, log->mask.slowest.elapsed))
log->mask.slowest = record;
log->mask.elapsed = _cairo_time_add (log->mask.elapsed, elapsed);
_cairo_surface_observer_mask (void *abstract_surface,
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);
status = _cairo_composite_rectangles_init_for_mask (&composite,
op, source, mask,
surface->log.mask.noop++;
device->log.mask.noop++;
add_extents (&surface->log.mask.extents, &composite);
add_extents (&device->log.mask.extents, &composite);
status = _cairo_surface_mask (surface->target,
add_record_mask (&surface->log,
surface->target, op, source, mask, clip,
t);
add_record_mask (&device->log,
do_callbacks (surface, &surface->mask_callbacks);
add_record_fill (cairo_observation_t *log,
record_fill (&record,
target, op, source,
path, fill_rule, tolerance, antialias,
clip, elapsed));
status = log->record->base.backend->fill (&log->record->base,
path, fill_rule,
tolerance, antialias,
if (_cairo_time_gt (elapsed, log->fill.slowest.elapsed))
log->fill.slowest = record;
log->fill.elapsed = _cairo_time_add (log->fill.elapsed, elapsed);
_cairo_surface_observer_fill (void *abstract_surface,
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);
status = _cairo_composite_rectangles_init_for_fill (&composite,
op, source, path,
surface->log.fill.noop++;
device->log.fill.noop++;
add_extents (&surface->log.fill.extents, &composite);
add_extents (&device->log.fill.extents, &composite);
status = _cairo_surface_fill (surface->target,
fill_rule, tolerance, antialias,
add_record_fill (&surface->log,
surface->target, op, source, path,
clip, t);
add_record_fill (&device->log,
do_callbacks (surface, &surface->fill_callbacks);
add_record_stroke (cairo_observation_t *log,
record_stroke (&record,
path, style, ctm,ctm_inverse,
status = log->record->base.backend->stroke (&log->record->base,
if (_cairo_time_gt (elapsed, log->stroke.slowest.elapsed))
log->stroke.slowest = record;
log->stroke.elapsed = _cairo_time_add (log->stroke.elapsed, elapsed);
_cairo_surface_observer_stroke (void *abstract_surface,
surface->log.stroke.count++;
surface->log.stroke.operators[op]++;
surface->log.stroke.antialias[antialias]++;
surface->log.stroke.caps[style->line_cap]++;
surface->log.stroke.joins[style->line_join]++;
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]++;
device->log.stroke.caps[style->line_cap]++;
device->log.stroke.joins[style->line_join]++;
add_pattern (&device->log.stroke.source, source, surface->target);
add_path (&device->log.stroke.path, path, FALSE);
add_clip (&device->log.stroke.clip, clip);
status = _cairo_composite_rectangles_init_for_stroke (&composite,
path, style, ctm,
surface->log.stroke.noop++;
device->log.stroke.noop++;
add_extents (&surface->log.stroke.extents, &composite);
add_extents (&device->log.stroke.extents, &composite);
status = _cairo_surface_stroke (surface->target,
style, ctm, ctm_inverse,
add_record_stroke (&surface->log,
style, ctm,ctm_inverse,
add_record_stroke (&device->log,
do_callbacks (surface, &surface->stroke_callbacks);
add_record_glyphs (cairo_observation_t *log,
const cairo_pattern_t*source,
record_glyphs (&record,
glyphs, num_glyphs, scaled_font,
status = log->record->base.backend->show_text_glyphs (&log->record->base,
NULL, 0,
glyphs, num_glyphs,
NULL, 0, 0,
scaled_font,
if (_cairo_time_gt (elapsed, log->glyphs.slowest.elapsed))
log->glyphs.slowest = record;
log->glyphs.elapsed = _cairo_time_add (log->glyphs.elapsed, elapsed);
_cairo_surface_observer_glyphs (void *abstract_surface,
cairo_glyph_t *dev_glyphs;
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);
status = _cairo_composite_rectangles_init_for_glyphs (&composite,
clip,
NULL);
surface->log.glyphs.noop++;
device->log.glyphs.noop++;
add_extents (&surface->log.glyphs.extents, &composite);
add_extents (&device->log.glyphs.extents, &composite);
/* XXX We have to copy the glyphs, because the backend is allowed to
* modify! */
dev_glyphs = _cairo_malloc_ab (num_glyphs, sizeof (cairo_glyph_t));
if (unlikely (dev_glyphs == NULL))
return _cairo_error (CAIRO_STATUS_NO_MEMORY);
memcpy (dev_glyphs, glyphs, num_glyphs * sizeof (cairo_glyph_t));
status = _cairo_surface_show_text_glyphs (surface->target, op, source,
dev_glyphs, num_glyphs,
free (dev_glyphs);
add_record_glyphs (&surface->log,
surface->target, op, source,
add_record_glyphs (&device->log,
do_callbacks (surface, &surface->glyphs_callbacks);
_cairo_surface_observer_flush (void *abstract_surface, unsigned flags)
do_callbacks (surface, &surface->flush_callbacks);
return _cairo_surface_flush (surface->target, flags);
_cairo_surface_observer_mark_dirty (void *abstract_surface,
int x, int y,
status = CAIRO_STATUS_SUCCESS;
if (surface->target->backend->mark_dirty_rectangle)
status = surface->target->backend->mark_dirty_rectangle (surface->target,
x,y, width,height);
_cairo_surface_observer_copy_page (void *abstract_surface)
if (surface->target->backend->copy_page)
status = surface->target->backend->copy_page (surface->target);
_cairo_surface_observer_show_page (void *abstract_surface)
if (surface->target->backend->show_page)
status = surface->target->backend->show_page (surface->target);
static cairo_bool_t
_cairo_surface_observer_get_extents (void *abstract_surface,
cairo_rectangle_int_t *extents)
return _cairo_surface_get_extents (surface->target, extents);
_cairo_surface_observer_get_font_options (void *abstract_surface,
cairo_font_options_t *options)
if (surface->target->backend->get_font_options != NULL)
surface->target->backend->get_font_options (surface->target, options);
_cairo_surface_observer_source (void *abstract_surface,
return _cairo_surface_get_source (surface->target, extents);
_cairo_surface_observer_acquire_source_image (void *abstract_surface,
cairo_image_surface_t **image_out,
void **image_extra)
surface->log.num_sources_acquired++;
to_device (surface)->log.num_sources_acquired++;
return _cairo_surface_acquire_source_image (surface->target,
image_out, image_extra);
_cairo_surface_observer_release_source_image (void *abstract_surface,
cairo_image_surface_t *image,
void *image_extra)
_cairo_surface_release_source_image (surface->target, image, image_extra);
_cairo_surface_observer_snapshot (void *abstract_surface)
/* XXX hook onto the snapshot so that we measure number of reads */
if (surface->target->backend->snapshot)
return surface->target->backend->snapshot (surface->target);
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_source,
_cairo_surface_observer_acquire_source_image,
_cairo_surface_observer_release_source_image,
_cairo_surface_observer_snapshot,
_cairo_surface_observer_copy_page,
_cairo_surface_observer_show_page,
_cairo_surface_observer_get_extents,
_cairo_surface_observer_get_font_options,
_cairo_surface_observer_flush,
_cairo_surface_observer_mark_dirty,
_cairo_surface_observer_paint,
_cairo_surface_observer_mask,
_cairo_surface_observer_stroke,
_cairo_surface_observer_fill,
NULL, /* fill-stroke */
_cairo_surface_observer_glyphs,
* cairo_surface_create_observer:
* @target: an existing surface for which the observer will watch
* @mode: sets the mode of operation (normal vs. record)
* 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.
* The @mode parameter can be set to either %CAIRO_SURFACE_OBSERVER_NORMAL
* or %CAIRO_SURFACE_OBSERVER_RECORD_OPERATIONS, to control whether or not
* the internal observer should record operations.
* 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.
cairo_surface_t *
cairo_surface_create_observer (cairo_surface_t *target,
cairo_surface_observer_mode_t mode)
cairo_device_t *device;
cairo_surface_t *surface;
cairo_bool_t record;
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));
record = mode & CAIRO_SURFACE_OBSERVER_RECORD_OPERATIONS;
device = _cairo_device_create_observer_internal (target->device, record);
if (unlikely (device->status))
return _cairo_surface_create_in_error (device->status);
surface = _cairo_surface_create_observer_internal (device, target);
cairo_device_destroy (device);
_cairo_surface_observer_add_callback (cairo_list_t *head,
cairo_surface_observer_callback_t func,
void *data)
cb = _cairo_calloc (sizeof (*cb));
if (unlikely (cb == NULL))
cairo_list_add (&cb->link, head);
cb->func = func;
cb->data = data;
* cairo_surface_observer_add_paint_callback:
* @abstract_surface: a #cairo_surface_observer_t
* @func: callback function for paint operations
* @data: closure to pass to the callback
* Adds a callback for paint operations on the observed surface.
* Returns: the status of the surface
cairo_status_t
cairo_surface_observer_add_paint_callback (cairo_surface_t *abstract_surface,
if (unlikely (CAIRO_REFERENCE_COUNT_IS_INVALID (&abstract_surface->ref_count)))
return abstract_surface->status;
if (! _cairo_surface_is_observer (abstract_surface))
return _cairo_error (CAIRO_STATUS_SURFACE_TYPE_MISMATCH);
surface = (cairo_surface_observer_t *)abstract_surface;
return _cairo_surface_observer_add_callback (&surface->paint_callbacks,
func, data);
* cairo_surface_observer_add_mask_callback:
* @func: callback function for mask operations
* Adds a callback for mask operations on the observed surface.
cairo_surface_observer_add_mask_callback (cairo_surface_t *abstract_surface,
return _cairo_surface_observer_add_callback (&surface->mask_callbacks,
* cairo_surface_observer_add_fill_callback:
* @func: callback function for fill operations
* Adds a callback for fill operations on the observed surface.
cairo_surface_observer_add_fill_callback (cairo_surface_t *abstract_surface,
return _cairo_surface_observer_add_callback (&surface->fill_callbacks,
* cairo_surface_observer_add_stroke_callback:
* @func: callback function for stroke operations
* Adds a callback for stroke operations on the observed surface.
cairo_surface_observer_add_stroke_callback (cairo_surface_t *abstract_surface,
return _cairo_surface_observer_add_callback (&surface->stroke_callbacks,
* cairo_surface_observer_add_glyphs_callback:
* @func: callback function for glyph operations
* Adds a callback for glyph operations on the observed surface.
* Since: 1.10
cairo_surface_observer_add_glyphs_callback (cairo_surface_t *abstract_surface,
return _cairo_surface_observer_add_callback (&surface->glyphs_callbacks,
* cairo_surface_observer_add_flush_callback:
* @func: callback for flush operations
* Adds a callback for flush operations on the observed surface.
cairo_surface_observer_add_flush_callback (cairo_surface_t *abstract_surface,
return _cairo_surface_observer_add_callback (&surface->flush_callbacks,
* cairo_surface_observer_add_finish_callback:
* @func: callback function for the finish operation
* Adds a callback for finish operations on the observed surface.
cairo_surface_observer_add_finish_callback (cairo_surface_t *abstract_surface,
return _cairo_surface_observer_add_callback (&surface->finish_callbacks,
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 inline int ordercmp (int a, int b, const unsigned int *array)
/* high to low */
return array[b] - array[a];
CAIRO_COMBSORT_DECLARE_WITH_DATA (sort_order, int, ordercmp)
print_array (cairo_output_stream_t *stream,
const unsigned int *array,
const char **names,
int count)
int order[64];
int i, j;
assert (count < ARRAY_LENGTH (order));
for (i = j = 0; i < count; i++) {
if (array[i] != 0)
order[j++] = i;
sort_order (order, j, (void *)array);
for (i = 0; i < j; i++)
_cairo_output_stream_printf (stream, " %d %s%s",
array[order[i]], names[order[i]],
i < j -1 ? "," : "");
static const char *operator_names[] = {
"CLEAR", /* CAIRO_OPERATOR_CLEAR */
"SOURCE", /* CAIRO_OPERATOR_SOURCE */
"OVER", /* CAIRO_OPERATOR_OVER */
"IN", /* CAIRO_OPERATOR_IN */
"OUT", /* CAIRO_OPERATOR_OUT */
"ATOP", /* CAIRO_OPERATOR_ATOP */
"DEST", /* CAIRO_OPERATOR_DEST */
"DEST_OVER", /* CAIRO_OPERATOR_DEST_OVER */
"DEST_IN", /* CAIRO_OPERATOR_DEST_IN */
"DEST_OUT", /* CAIRO_OPERATOR_DEST_OUT */
"DEST_ATOP", /* CAIRO_OPERATOR_DEST_ATOP */
"XOR", /* CAIRO_OPERATOR_XOR */
"ADD", /* CAIRO_OPERATOR_ADD */
"SATURATE", /* CAIRO_OPERATOR_SATURATE */
"MULTIPLY", /* CAIRO_OPERATOR_MULTIPLY */
"SCREEN", /* CAIRO_OPERATOR_SCREEN */
"OVERLAY", /* CAIRO_OPERATOR_OVERLAY */
"DARKEN", /* CAIRO_OPERATOR_DARKEN */
"LIGHTEN", /* CAIRO_OPERATOR_LIGHTEN */
"DODGE", /* CAIRO_OPERATOR_COLOR_DODGE */
"BURN", /* CAIRO_OPERATOR_COLOR_BURN */
"HARD_LIGHT", /* CAIRO_OPERATOR_HARD_LIGHT */
"SOFT_LIGHT", /* CAIRO_OPERATOR_SOFT_LIGHT */
"DIFFERENCE", /* CAIRO_OPERATOR_DIFFERENCE */
"EXCLUSION", /* CAIRO_OPERATOR_EXCLUSION */
"HSL_HUE", /* CAIRO_OPERATOR_HSL_HUE */
"HSL_SATURATION", /* CAIRO_OPERATOR_HSL_SATURATION */
"HSL_COLOR", /* CAIRO_OPERATOR_HSL_COLOR */
"HSL_LUMINOSITY" /* CAIRO_OPERATOR_HSL_LUMINOSITY */
print_operators (cairo_output_stream_t *stream, unsigned int *array)
_cairo_output_stream_printf (stream, " op:");
print_array (stream, array, operator_names, NUM_OPERATORS);
_cairo_output_stream_printf (stream, "\n");
static const char *fill_rule_names[] = {
"non-zero",
"even-odd",
print_fill_rule (cairo_output_stream_t *stream, unsigned int *array)
_cairo_output_stream_printf (stream, " fill rule:");
print_array (stream, array, fill_rule_names, ARRAY_LENGTH(fill_rule_names));
static const char *cap_names[] = {
"butt", /* CAIRO_LINE_CAP_BUTT */
"round", /* CAIRO_LINE_CAP_ROUND */
"square" /* CAIRO_LINE_CAP_SQUARE */
print_line_caps (cairo_output_stream_t *stream, unsigned int *array)
_cairo_output_stream_printf (stream, " caps:");
print_array (stream, array, cap_names, NUM_CAPS);
static const char *join_names[] = {
"miter", /* CAIRO_LINE_JOIN_MITER */
"round", /* CAIRO_LINE_JOIN_ROUND */
"bevel", /* CAIRO_LINE_JOIN_BEVEL */
print_line_joins (cairo_output_stream_t *stream, unsigned int *array)
_cairo_output_stream_printf (stream, " joins:");
print_array (stream, array, join_names, NUM_JOINS);
static const char *antialias_names[] = {
"default",
"none",
"gray",
"subpixel",
"fast",
"good",
"best"
print_antialias (cairo_output_stream_t *stream, unsigned int *array)
_cairo_output_stream_printf (stream, " antialias:");
print_array (stream, array, antialias_names, NUM_ANTIALIAS);
static const char *pattern_names[] = {
"native",
"record",
"other surface",
"solid",
"linear",
"radial",
"mesh",
"raster"
print_pattern (cairo_output_stream_t *stream,
const char *name,
const struct pattern *p)
_cairo_output_stream_printf (stream, " %s:", name);
print_array (stream, p->type, pattern_names, ARRAY_LENGTH (pattern_names));
static const char *path_names[] = {
"empty",
"pixel-aligned",
"rectliinear",
"straight",
"curved",
print_path (cairo_output_stream_t *stream,
const struct path *p)
_cairo_output_stream_printf (stream, " path:");
print_array (stream, p->type, path_names, ARRAY_LENGTH (path_names));
static const char *clip_names[] = {
"region",
"boxes",
"single path",
"polygon",
"general",
print_clip (cairo_output_stream_t *stream, const struct clip *c)
_cairo_output_stream_printf (stream, " clip:");
print_array (stream, c->type, clip_names, ARRAY_LENGTH (clip_names));
print_record (cairo_output_stream_t *stream,
_cairo_output_stream_printf (stream, " op: %s\n", operator_names[r->op]);
_cairo_output_stream_printf (stream, " source: %s\n",
pattern_names[r->source]);
if (r->mask != -1)
_cairo_output_stream_printf (stream, " mask: %s\n",
pattern_names[r->mask]);
if (r->num_glyphs != -1)
_cairo_output_stream_printf (stream, " num_glyphs: %d\n",
r->num_glyphs);
if (r->path != -1)
_cairo_output_stream_printf (stream, " path: %s\n",
path_names[r->path]);
if (r->fill_rule != -1)
_cairo_output_stream_printf (stream, " fill rule: %s\n",
fill_rule_names[r->fill_rule]);
if (r->antialias != -1)
_cairo_output_stream_printf (stream, " antialias: %s\n",
antialias_names[r->antialias]);
_cairo_output_stream_printf (stream, " clip: %s\n", clip_names[r->clip]);
_cairo_output_stream_printf (stream, " elapsed: %f ns\n",
_cairo_time_to_ns (r->elapsed));
static double percent (cairo_time_t a, cairo_time_t b)
/* Fake %.1f */
return _cairo_round (_cairo_time_to_s (a) * 1000 /
_cairo_time_to_s (b)) / 10;
replay_record (cairo_observation_t *log,
cairo_observation_record_t *r,
cairo_device_t *script)
if (log->record == NULL || script == NULL)
return FALSE;
surface = cairo_script_surface_create (script,
r->target_content,
r->target_width,
r->target_height);
status =
_cairo_recording_surface_replay_one (log->record, r->index, surface);
cairo_surface_destroy (surface);
return TRUE;
#else
static cairo_time_t
_cairo_observation_total_elapsed (cairo_observation_t *log)
cairo_time_t total;
total = log->paint.elapsed;
total = _cairo_time_add (total, log->mask.elapsed);
total = _cairo_time_add (total, log->fill.elapsed);
total = _cairo_time_add (total, log->stroke.elapsed);
total = _cairo_time_add (total, log->glyphs.elapsed);
return total;
_cairo_observation_print (cairo_output_stream_t *stream,
cairo_observation_t *log)
cairo_device_t *script;
script = _cairo_script_context_create_internal (stream);
_cairo_script_context_attach_snapshots (script, FALSE);
script = NULL;
total = _cairo_observation_total_elapsed (log);
_cairo_output_stream_printf (stream, "elapsed: %f\n",
_cairo_time_to_ns (total));
_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], elapsed %f [%f%%]\n",
log->paint.count, log->paint.noop,
_cairo_time_to_ns (log->paint.elapsed),
percent (log->paint.elapsed, total));
if (log->paint.count) {
print_extents (stream, &log->paint.extents);
print_operators (stream, log->paint.operators);
print_pattern (stream, "source", &log->paint.source);
print_clip (stream, &log->paint.clip);
_cairo_output_stream_printf (stream, "slowest paint: %f%%\n",
percent (log->paint.slowest.elapsed,
log->paint.elapsed));
print_record (stream, &log->paint.slowest);
if (replay_record (log, &log->paint.slowest, script))
_cairo_output_stream_printf (stream, "\n\n");
_cairo_output_stream_printf (stream, "mask: count %d [no-op %d], elapsed %f [%f%%]\n",
log->mask.count, log->mask.noop,
_cairo_time_to_ns (log->mask.elapsed),
percent (log->mask.elapsed, total));
if (log->mask.count) {
print_extents (stream, &log->mask.extents);
print_operators (stream, log->mask.operators);
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, "slowest mask: %f%%\n",
percent (log->mask.slowest.elapsed,
log->mask.elapsed));
print_record (stream, &log->mask.slowest);
if (replay_record (log, &log->mask.slowest, script))
_cairo_output_stream_printf (stream, "fill: count %d [no-op %d], elaspsed %f [%f%%]\n",
log->fill.count, log->fill.noop,
_cairo_time_to_ns (log->fill.elapsed),
percent (log->fill.elapsed, total));
if (log->fill.count) {
print_extents (stream, &log->fill.extents);
print_operators (stream, log->fill.operators);
print_pattern (stream, "source", &log->fill.source);
print_path (stream, &log->fill.path);
print_fill_rule (stream, log->fill.fill_rule);
print_antialias (stream, log->fill.antialias);
print_clip (stream, &log->fill.clip);
_cairo_output_stream_printf (stream, "slowest fill: %f%%\n",
percent (log->fill.slowest.elapsed,
log->fill.elapsed));
print_record (stream, &log->fill.slowest);
if (replay_record (log, &log->fill.slowest, script))
_cairo_output_stream_printf (stream, "stroke: count %d [no-op %d], elapsed %f [%f%%]\n",
log->stroke.count, log->stroke.noop,
_cairo_time_to_ns (log->stroke.elapsed),
percent (log->stroke.elapsed, total));
if (log->stroke.count) {
print_extents (stream, &log->stroke.extents);
print_operators (stream, log->stroke.operators);
print_pattern (stream, "source", &log->stroke.source);
print_path (stream, &log->stroke.path);
print_antialias (stream, log->stroke.antialias);
print_line_caps (stream, log->stroke.caps);
print_line_joins (stream, log->stroke.joins);
print_clip (stream, &log->stroke.clip);
_cairo_output_stream_printf (stream, "slowest stroke: %f%%\n",
percent (log->stroke.slowest.elapsed,
log->stroke.elapsed));
print_record (stream, &log->stroke.slowest);
if (replay_record (log, &log->stroke.slowest, script))
_cairo_output_stream_printf (stream, "glyphs: count %d [no-op %d], elasped %f [%f%%]\n",
log->glyphs.count, log->glyphs.noop,
_cairo_time_to_ns (log->glyphs.elapsed),
percent (log->glyphs.elapsed, total));
if (log->glyphs.count) {
print_extents (stream, &log->glyphs.extents);
print_operators (stream, log->glyphs.operators);
print_pattern (stream, "source", &log->glyphs.source);
print_clip (stream, &log->glyphs.clip);
_cairo_output_stream_printf (stream, "slowest glyphs: %f%%\n",
percent (log->glyphs.slowest.elapsed,
log->glyphs.elapsed));
print_record (stream, &log->glyphs.slowest);
if (replay_record (log, &log->glyphs.slowest, script))
cairo_device_destroy (script);
* cairo_surface_observer_print:
* @write_func: callback for writing on a stream
* @closure: data to pass to @write_func
* Prints the observer log using the given callback.
* Returns: the status of the print operation
cairo_surface_observer_print (cairo_surface_t *abstract_surface,
cairo_write_func_t write_func,
void *closure)
cairo_output_stream_t *stream;
if (unlikely (abstract_surface->status))
if (unlikely (! _cairo_surface_is_observer (abstract_surface)))
surface = (cairo_surface_observer_t *) abstract_surface;
stream = _cairo_output_stream_create (write_func, NULL, closure);
_cairo_observation_print (stream, &surface->log);
return _cairo_output_stream_destroy (stream);
* cairo_surface_observer_elapsed:
* Returns the total observation time.
* Returns: the elapsed time, in nanoseconds
double
cairo_surface_observer_elapsed (cairo_surface_t *abstract_surface)
return -1;
return _cairo_time_to_ns (_cairo_observation_total_elapsed (&surface->log));
* cairo_device_observer_print:
* @abstract_device: the observed #cairo_device_t
* @write_func: the write function
* @closure: data to pass to the @write_func
* Prints the device log using the given function.
* Returns: the status after the operation
cairo_device_observer_print (cairo_device_t *abstract_device,
if (unlikely (abstract_device->status))
return abstract_device->status;
if (unlikely (! _cairo_device_is_observer (abstract_device)))
return _cairo_error (CAIRO_STATUS_DEVICE_TYPE_MISMATCH);
device = (cairo_device_observer_t *) abstract_device;
_cairo_observation_print (stream, &device->log);
* cairo_device_observer_elapsed:
* Returns the total elapsed time of the observation.
* Returns: the elapsed time, in nanoseconds.
cairo_device_observer_elapsed (cairo_device_t *abstract_device)
if (unlikely (CAIRO_REFERENCE_COUNT_IS_INVALID (&abstract_device->ref_count)))
if (! _cairo_device_is_observer (abstract_device))
return _cairo_time_to_ns (_cairo_observation_total_elapsed (&device->log));
* cairo_device_observer_paint_elapsed:
* Returns the elapsed time of the paint operations.
cairo_device_observer_paint_elapsed (cairo_device_t *abstract_device)
return _cairo_time_to_ns (device->log.paint.elapsed);
* cairo_device_observer_mask_elapsed:
* Returns the elapsed time of the mask operations.
cairo_device_observer_mask_elapsed (cairo_device_t *abstract_device)
return _cairo_time_to_ns (device->log.mask.elapsed);
* cairo_device_observer_fill_elapsed:
* Returns the elapsed time of the fill operations.
cairo_device_observer_fill_elapsed (cairo_device_t *abstract_device)
return _cairo_time_to_ns (device->log.fill.elapsed);
* cairo_device_observer_stroke_elapsed:
* Returns the elapsed time of the stroke operations.
cairo_device_observer_stroke_elapsed (cairo_device_t *abstract_device)
return _cairo_time_to_ns (device->log.stroke.elapsed);
* cairo_device_observer_glyphs_elapsed:
* Returns the elapsed time of the glyph operations.
cairo_device_observer_glyphs_elapsed (cairo_device_t *abstract_device)
return _cairo_time_to_ns (device->log.glyphs.elapsed);