diff options
author | Chris Wilson <chris@chris-wilson.co.uk> | 2011-10-27 19:00:58 +0100 |
---|---|---|
committer | Chris Wilson <chris@chris-wilson.co.uk> | 2011-10-27 19:00:58 +0100 |
commit | 2a453ee7df543441986b0a52ec2c2de72005c091 (patch) | |
tree | c80a4795804c7bf4d88feb48b4116eaf0cbc224a | |
parent | ba855a12e8d686f2137f82d317791f3ec4a68fc0 (diff) | |
parent | 8ddecc08a5e4fc43368c01e4e85215962b009b92 (diff) |
Merge branch 'master' of git://cairographics.org/git/cairo
33 files changed, 5246 insertions, 50 deletions
diff --git a/boilerplate/Makefile.sources b/boilerplate/Makefile.sources index 63ef34e4..101e9971 100644 --- a/boilerplate/Makefile.sources +++ b/boilerplate/Makefile.sources @@ -38,3 +38,4 @@ cairo_boilerplate_xcb_sources = cairo-boilerplate-xcb.c cairo_boilerplate_xlib_headers = cairo-boilerplate-xlib.h cairo_boilerplate_xlib_sources = cairo-boilerplate-xlib.c cairo_boilerplate_vg_sources = cairo-boilerplate-vg.c +cairo_boilerplate_cogl_sources = cairo-boilerplate-cogl.c diff --git a/boilerplate/Makefile.win32.features b/boilerplate/Makefile.win32.features index 8b6a6733..e60a95ba 100644 --- a/boilerplate/Makefile.win32.features +++ b/boilerplate/Makefile.win32.features @@ -247,6 +247,18 @@ enabled_cairo_boilerplate_cxx_sources += $(cairo_boilerplate_glesv2_cxx_sources) enabled_cairo_boilerplate_sources += $(cairo_boilerplate_glesv2_sources) endif +unsupported_cairo_boilerplate_headers += $(cairo_boilerplate_cogl_headers) +all_cairo_boilerplate_headers += $(cairo_boilerplate_cogl_headers) +all_cairo_boilerplate_private += $(cairo_boilerplate_cogl_private) +all_cairo_boilerplate_cxx_sources += $(cairo_boilerplate_cogl_cxx_sources) +all_cairo_boilerplate_sources += $(cairo_boilerplate_cogl_sources) +ifeq ($(CAIRO_HAS_COGL_SURFACE),1) +enabled_cairo_boilerplate_headers += $(cairo_boilerplate_cogl_headers) +enabled_cairo_boilerplate_private += $(cairo_boilerplate_cogl_private) +enabled_cairo_boilerplate_cxx_sources += $(cairo_boilerplate_cogl_cxx_sources) +enabled_cairo_boilerplate_sources += $(cairo_boilerplate_cogl_sources) +endif + unsupported_cairo_boilerplate_headers += $(cairo_boilerplate_directfb_headers) all_cairo_boilerplate_headers += $(cairo_boilerplate_directfb_headers) all_cairo_boilerplate_private += $(cairo_boilerplate_directfb_private) diff --git a/boilerplate/cairo-boilerplate-cogl.c b/boilerplate/cairo-boilerplate-cogl.c new file mode 100644 index 00000000..8dda3172 --- /dev/null +++ b/boilerplate/cairo-boilerplate-cogl.c @@ -0,0 +1,208 @@ +/* Cairo - a vector graphics library with display and print output + * + * Copyright © 2009 Chris Wilson + * + * 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 Chris Wilson. + */ + +#include "cairo-boilerplate-private.h" + +#include <cairo-cogl.h> +#include <cogl/cogl2-experimental.h> + +typedef struct _cogl_closure { + cairo_device_t *device; + CoglFramebuffer *fb; + cairo_surface_t *surface; +} cogl_closure_t; + +static const cairo_user_data_key_t cogl_closure_key; + +static CoglContext *context = NULL; + +static void +_cairo_boilerplate_cogl_cleanup (void *abstract_closure) +{ + cogl_closure_t *closure = abstract_closure; + + cogl_object_unref (closure->fb); + + cairo_device_finish (closure->device); + cairo_device_destroy (closure->device); + + free (closure); +} + +static cairo_surface_t * +_cairo_boilerplate_cogl_create_offscreen_color_surface (const char *name, + cairo_content_t content, + double width, + double height, + double max_width, + double max_height, + cairo_boilerplate_mode_t mode, + int id, + void **abstract_closure) +{ + cairo_device_t *device; + CoglTexture *tex; + CoglHandle offscreen; + CoglFramebuffer *fb; + cogl_closure_t *closure; + cairo_status_t status; + + if (!context) + context = cogl_context_new (NULL, NULL); + + device = cairo_cogl_device_create (context); + tex = cogl_texture_new_with_size (width, height, + COGL_TEXTURE_NO_SLICING, + COGL_PIXEL_FORMAT_BGRA_8888_PRE); + offscreen = cogl_offscreen_new_to_texture (tex); + fb = COGL_FRAMEBUFFER (offscreen); + + cogl_framebuffer_allocate (fb, NULL); + cogl_push_framebuffer (fb); + cogl_ortho (0, cogl_framebuffer_get_width (fb), + cogl_framebuffer_get_height (fb), 0, + -1, 100); + cogl_pop_framebuffer (); + + closure = malloc (sizeof (cogl_closure_t)); + *abstract_closure = closure; + closure->device = device; + closure->fb = fb; + closure->surface = cairo_cogl_surface_create (device, fb); + + status = cairo_surface_set_user_data (closure->surface, + &cogl_closure_key, closure, NULL); + if (status == CAIRO_STATUS_SUCCESS) + return closure->surface; + + _cairo_boilerplate_cogl_cleanup (closure); + return cairo_boilerplate_surface_create_in_error (status); +} + +static cairo_surface_t * +_cairo_boilerplate_cogl_create_onscreen_color_surface (const char *name, + cairo_content_t content, + double width, + double height, + double max_width, + double max_height, + cairo_boilerplate_mode_t mode, + int id, + void **abstract_closure) +{ + cairo_device_t *device; + CoglOnscreen *onscreen; + CoglFramebuffer *fb; + cogl_closure_t *closure; + cairo_status_t status; + + if (!context) + context = cogl_context_new (NULL, NULL); + + device = cairo_cogl_device_create (context); + onscreen = cogl_onscreen_new (context, width, height); + fb = COGL_FRAMEBUFFER (onscreen); + + cogl_onscreen_show (onscreen); + + cogl_push_framebuffer (fb); + cogl_ortho (0, cogl_framebuffer_get_width (fb), + cogl_framebuffer_get_height (fb), 0, + -1, 100); + cogl_pop_framebuffer (); + + closure = malloc (sizeof (cogl_closure_t)); + *abstract_closure = closure; + closure->device = device; + closure->fb = fb; + closure->surface = cairo_cogl_surface_create (device, fb); + + status = cairo_surface_set_user_data (closure->surface, + &cogl_closure_key, closure, NULL); + if (status == CAIRO_STATUS_SUCCESS) + return closure->surface; + + _cairo_boilerplate_cogl_cleanup (closure); + return cairo_boilerplate_surface_create_in_error (status); +} + +static cairo_status_t +_cairo_boilerplate_cogl_finish_onscreen (cairo_surface_t *surface) +{ + cogl_closure_t *closure = cairo_surface_get_user_data (surface, &cogl_closure_key); + + cairo_cogl_surface_end_frame (surface); + + cogl_framebuffer_swap_buffers (closure->fb); + + return CAIRO_STATUS_SUCCESS; +} + +static void +_cairo_boilerplate_cogl_synchronize (void *abstract_closure) +{ + cogl_closure_t *closure = abstract_closure; + cogl_framebuffer_finish (closure->fb); +} + +static const cairo_boilerplate_target_t targets[] = { + { + "cogl-offscreen-color", "cogl", NULL, NULL, + CAIRO_SURFACE_TYPE_COGL, CAIRO_CONTENT_COLOR_ALPHA, 1, + "cairo_cogl_device_create", + _cairo_boilerplate_cogl_create_offscreen_color_surface, + cairo_surface_create_similar, + NULL, NULL, + _cairo_boilerplate_get_image_surface, + cairo_surface_write_to_png, + _cairo_boilerplate_cogl_cleanup, + _cairo_boilerplate_cogl_synchronize, + NULL, + TRUE, FALSE, FALSE + }, + { + "cogl-onscreen-color", "cogl", NULL, NULL, + CAIRO_SURFACE_TYPE_COGL, CAIRO_CONTENT_COLOR_ALPHA, 1, + "cairo_cogl_device_create", + _cairo_boilerplate_cogl_create_onscreen_color_surface, + cairo_surface_create_similar, + NULL, + _cairo_boilerplate_cogl_finish_onscreen, + _cairo_boilerplate_get_image_surface, + cairo_surface_write_to_png, + _cairo_boilerplate_cogl_cleanup, + _cairo_boilerplate_cogl_synchronize, + NULL, + TRUE, FALSE, FALSE + } +}; +CAIRO_BOILERPLATE (cogl, targets) diff --git a/build/Makefile.win32.features b/build/Makefile.win32.features index 650b224c..8cb155dc 100644 --- a/build/Makefile.win32.features +++ b/build/Makefile.win32.features @@ -19,6 +19,7 @@ CAIRO_HAS_GALLIUM_SURFACE=0 CAIRO_HAS_PNG_FUNCTIONS=1 CAIRO_HAS_GL_SURFACE=0 CAIRO_HAS_GLESV2_SURFACE=0 +CAIRO_HAS_COGL_SURFACE=0 CAIRO_HAS_DIRECTFB_SURFACE=0 CAIRO_HAS_VG_SURFACE=0 CAIRO_HAS_EGL_FUNCTIONS=0 diff --git a/build/Makefile.win32.features-h b/build/Makefile.win32.features-h index 84d65f4f..13904cfa 100644 --- a/build/Makefile.win32.features-h +++ b/build/Makefile.win32.features-h @@ -62,6 +62,9 @@ endif ifeq ($(CAIRO_HAS_GLESV2_SURFACE),1) @echo "#define CAIRO_HAS_GLESV2_SURFACE 1" >> $(top_srcdir)/src/cairo-features.h endif +ifeq ($(CAIRO_HAS_COGL_SURFACE),1) + @echo "#define CAIRO_HAS_COGL_SURFACE 1" >> $(top_srcdir)/src/cairo-features.h +endif ifeq ($(CAIRO_HAS_DIRECTFB_SURFACE),1) @echo "#define CAIRO_HAS_DIRECTFB_SURFACE 1" >> $(top_srcdir)/src/cairo-features.h endif diff --git a/build/configure.ac.features b/build/configure.ac.features index faa762ca..e4a2aafc 100644 --- a/build/configure.ac.features +++ b/build/configure.ac.features @@ -388,6 +388,7 @@ AC_DEFUN([CAIRO_REPORT], echo " DirectFB: $use_directfb" echo " OpenVG: $use_vg" echo " DRM: $use_drm" + echo " Cogl: $use_cogl" echo "" echo "The following font backends:" echo " User: yes (always builtin)" diff --git a/configure.ac b/configure.ac index 2d3fd368..3a438ee3 100644 --- a/configure.ac +++ b/configure.ac @@ -328,6 +328,12 @@ CAIRO_ENABLE_SURFACE_BACKEND(glesv2, OpenGLESv2, no, [ ]) dnl =========================================================================== +CAIRO_ENABLE_SURFACE_BACKEND(cogl, Cogl, no, [ + cogl_REQUIRES="cogl-2.0-experimental" + PKG_CHECK_MODULES(cogl, $cogl_REQUIRES,, [use_cogl="no"]) +]) + +dnl =========================================================================== CAIRO_ENABLE_SURFACE_BACKEND(directfb, directfb, no, [ directfb_REQUIRES=directfb diff --git a/perf/cairo-perf-chart.c b/perf/cairo-perf-chart.c index 70ff3538..0ecf8b47 100644 --- a/perf/cairo-perf-chart.c +++ b/perf/cairo-perf-chart.c @@ -54,7 +54,7 @@ struct color { }; #define FONT_SIZE 12 -#define PAD (FONT_SIZE/2+1) +#define PAD (4) #define MAX(a,b) ((a) > (b) ? (a) : (b)) @@ -108,13 +108,13 @@ trim_outliers (double *values, qsort (values, num_values, sizeof (double), _double_cmp); - q1 = values[1*num_values / 4]; - q3 = values[3*num_values / 4]; + q1 = values[1*num_values / 6]; + q3 = values[5*num_values / 6]; iqr = q3 - q1; - outlier_min = q1 - 1.5 * iqr; - outlier_max = q3 + 1.5 * iqr; + outlier_min = q1 - 3 * iqr; + outlier_max = q3 + 3 * iqr; i = 0; while (i < num_values && values[i] < outlier_min) @@ -140,6 +140,8 @@ find_ranges (struct chart *chart) double test_time; int seen_non_null; int num_tests = 0; + double slow_sum = 0, fast_sum = 0, sum; + int slow_count = 0, fast_count = 0; int i; num_values = 0; @@ -201,17 +203,25 @@ find_ranges (struct chart *chart) test_time = report_time; if (chart->relative) { - double v = to_factor (test_time / report_time); - if (num_values == size_values) { - size_values *= 2; - values = xrealloc (values, - size_values * sizeof (double)); + if (test_time != report_time) { + double v = to_factor (test_time / report_time); + if (num_values == size_values) { + size_values *= 2; + values = xrealloc (values, + size_values * sizeof (double)); + } + values[num_values++] = v; + if (v < min) + min = v; + if (v > max) + max = v; + if (v > 0) + fast_sum += v/100, fast_count++; + else + slow_sum += v/100, slow_count++; + sum += v/100; + printf ("%s %d: %f\n", min_test->name, num_values, v); } - values[num_values++] = v; - if (v < min) - min = v; - if (v > max) - max = v; } else { if (report_time < min) min = report_time; @@ -230,6 +240,9 @@ find_ranges (struct chart *chart) free (values); free (tests); + + printf ("%d: slow[%d] average: %f, fast[%d] average: %f, %f\n", + num_values, slow_count, slow_sum / slow_count, fast_count, fast_sum / fast_count, sum / num_values); } #define SET_COLOR(C, R, G, B) (C)->red = (R), (C)->green = (G), (C)->blue = (B) @@ -390,19 +403,29 @@ add_label (struct chart *c, cairo_save (c->cr); dx = c->width / (double) c->num_tests; - if (dx / 2 - PAD < 6) + if (dx / 2 - PAD < 4) return; cairo_set_font_size (c->cr, dx / 2 - PAD); cairo_text_extents (c->cr, label, &extents); + cairo_set_source_rgb (c->cr, .5, .5, .5); + x = (test + .5) * dx; - cairo_translate (c->cr, x, PAD / 2); + cairo_save (c->cr); + cairo_translate (c->cr, x, c->height - PAD / 2); cairo_rotate (c->cr, -M_PI/2); + cairo_move_to (c->cr, 0, -extents.y_bearing/2); + cairo_show_text (c->cr, label); + cairo_restore (c->cr); - cairo_set_source_rgb (c->cr, .5, .5, .5); + cairo_save (c->cr); + cairo_translate (c->cr, x, PAD / 2); + cairo_rotate (c->cr, -M_PI/2); cairo_move_to (c->cr, -extents.width, -extents.y_bearing/2); cairo_show_text (c->cr, label); cairo_restore (c->cr); + + cairo_restore (c->cr); } static void @@ -840,7 +863,7 @@ main (int argc, chart.names[chart.num_reports] = argv[i] + 7; } else { cairo_perf_report_load (&chart.reports[chart.num_reports++], - argv[i], + argv[i], i, test_report_cmp_name); } } diff --git a/src/Makefile.sources b/src/Makefile.sources index 84df00a9..867cc35d 100644 --- a/src/Makefile.sources +++ b/src/Makefile.sources @@ -428,3 +428,13 @@ cairo_xml_sources = cairo-xml-surface.c cairo_vg_headers = cairo-vg.h cairo_vg_sources = cairo-vg-surface.c + +cairo_cogl_headers = cairo-cogl.h +cairo_cogl_private = cairo-cogl-private.h \ + cairo-cogl-gradient-private.h \ + cairo-cogl-context-private.h \ + cairo-cogl-utils-private.h +cairo_cogl_sources = cairo-cogl-surface.c \ + cairo-cogl-gradient.c \ + cairo-cogl-context.c \ + cairo-cogl-utils.c diff --git a/src/Makefile.win32.features b/src/Makefile.win32.features index 4b90d649..2274f4ad 100644 --- a/src/Makefile.win32.features +++ b/src/Makefile.win32.features @@ -325,6 +325,22 @@ ifeq ($(CAIRO_HAS_GLESV2_SURFACE),1) enabled_cairo_pkgconf += cairo-glesv2.pc endif +unsupported_cairo_headers += $(cairo_cogl_headers) +all_cairo_headers += $(cairo_cogl_headers) +all_cairo_private += $(cairo_cogl_private) +all_cairo_cxx_sources += $(cairo_cogl_cxx_sources) +all_cairo_sources += $(cairo_cogl_sources) +ifeq ($(CAIRO_HAS_COGL_SURFACE),1) +enabled_cairo_headers += $(cairo_cogl_headers) +enabled_cairo_private += $(cairo_cogl_private) +enabled_cairo_cxx_sources += $(cairo_cogl_cxx_sources) +enabled_cairo_sources += $(cairo_cogl_sources) +endif +all_cairo_pkgconf += cairo-cogl.pc +ifeq ($(CAIRO_HAS_COGL_SURFACE),1) +enabled_cairo_pkgconf += cairo-cogl.pc +endif + unsupported_cairo_headers += $(cairo_directfb_headers) all_cairo_headers += $(cairo_directfb_headers) all_cairo_private += $(cairo_directfb_private) diff --git a/src/cairo-clip-private.h b/src/cairo-clip-private.h index aaf16f3a..2c2aa957 100644 --- a/src/cairo-clip-private.h +++ b/src/cairo-clip-private.h @@ -73,6 +73,9 @@ struct _cairo_clip { cairo_private cairo_clip_t * _cairo_clip_create (void); +cairo_private cairo_clip_path_t * +_cairo_clip_path_reference (cairo_clip_path_t *clip_path); + cairo_private void _cairo_clip_path_destroy (cairo_clip_path_t *clip_path); diff --git a/src/cairo-clip.c b/src/cairo-clip.c index c89e8be0..d32bdec6 100644 --- a/src/cairo-clip.c +++ b/src/cairo-clip.c @@ -74,7 +74,7 @@ _cairo_clip_path_create (cairo_clip_t *clip) return clip_path; } -static cairo_clip_path_t * +cairo_clip_path_t * _cairo_clip_path_reference (cairo_clip_path_t *clip_path) { assert (CAIRO_REFERENCE_COUNT_HAS_REFERENCE (&clip_path->ref_count)); diff --git a/src/cairo-cogl-context-private.h b/src/cairo-cogl-context-private.h new file mode 100644 index 00000000..0a7185ef --- /dev/null +++ b/src/cairo-cogl-context-private.h @@ -0,0 +1,52 @@ +/* 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.og/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. + * + * Contributor(s): + * Robert Bragg <robert@linux.intel.com> + */ + +#ifndef CAIRO_COGL_CONTEXT_PRIVATE_H +#define CAIRO_COGL_CONTEXT_PRIVATE_H + +#include "cairo-default-context-private.h" +#include "cairo-cogl-private.h" + +typedef struct _cairo_cogl_context { + cairo_default_context_t base; + + cairo_cogl_device_t *dev; + int path_ctm_age; + cairo_path_fixed_t user_path; + + cairo_bool_t path_is_rectangle; + double x, y, width, height; +} cairo_cogl_context_t; + +cairo_t * +_cairo_cogl_context_create (void *target); + +#endif /* CAIRO_COGL_CONTEXT_PRIVATE_H */ diff --git a/src/cairo-cogl-context.c b/src/cairo-cogl-context.c new file mode 100644 index 00000000..e2031250 --- /dev/null +++ b/src/cairo-cogl-context.c @@ -0,0 +1,820 @@ +/* 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.og/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. + * + * Contributor(s): + * Robert Bragg <robert@linux.intel.com> + */ + +/* so long as we can verify that the ctm doesn't change multiple times + * during the construction of a path we can build a shadow + * cairo_path_fixed_t in user coordinates that we can use to create a + * hash value for caching tessellations of that path. + * + * We need to hook into all the points where the ctm can be changed + * so we can bump a cr->path_ctm_age counter. + * + * We need to hook into all the points where the path can be modified + * so we can catch the start of a path and reset the cr->path_ctm_age + * counter at that point. + * + * When a draw operation is hit we can then check that the + * path_ctm_age == 0 and if so we create a hash of the path. + * + * We use this hash to lookup a cairo_cogl_path_meta_t struct which + * may contain tessellated triangles for the path or may just contain + * a count of how many times the path has been re-seen (we only cache + * tessellated triangles if there is evidence that the path is being + * used multiple times because there is a cost involved in allocating + * a separate buffer for the triangles). + */ + +#include "cairo-cogl-context-private.h" +#include "cairo-freed-pool-private.h" +#include "cairo-arc-private.h" +#include "cairo-path-fixed-private.h" + +#include <glib.h> + +static freed_pool_t context_pool; + +void +_cairo_cogl_context_reset_static_data (void) +{ + _freed_pool_reset (&context_pool); +} + +static cairo_status_t +_cairo_cogl_context_rectangle_real (cairo_cogl_context_t *cr, + double x, double y, + double width, double height) +{ + cairo_status_t status; + status = cr->dev->backend_parent.rectangle (cr, x, y, width, height); + if (unlikely (status)) + return status; + + return _cairo_cogl_path_fixed_rectangle (&cr->user_path, + _cairo_fixed_from_double (x), + _cairo_fixed_from_double (y), + _cairo_fixed_from_double (width), + _cairo_fixed_from_double (height)); +} + +/* The idea here is that we have a simplified way of tracking rectangle paths + * because rectangles are perhaps the most common shape drawn with cairo. + * + * Basically we have a speculative store for a rectangle path that doesn't + * need to use the cairo_path_fixed_t api to describe a rectangle in terms of + * (move_to,rel_line_to,rel_line_to,_rel_line_to,close) because if you profile + * heavy rectangle drawing with Cairo that process can be overly expensive. + * + * If the user asks to add more than just a rectangle to their current path + * then we "flush" any speculative rectangle stored into the current path + * before continuing to append their operations. + * + * In addition to the speculative store cairo-cogl also has a fast-path + * fill_rectangle drawing operation that futher aims to minimize the cost + * of drawing rectangles. + */ +static cairo_status_t +_flush_cr_rectangle (cairo_cogl_context_t *cr) +{ + if (!cr->path_is_rectangle) + return CAIRO_STATUS_SUCCESS; + + cr->path_is_rectangle = FALSE; + return _cairo_cogl_context_rectangle_real (cr, cr->x, cr->y, cr->width, cr->height); +} + +static cairo_status_t +_cairo_cogl_context_restore (void *abstract_cr) +{ + cairo_cogl_context_t *cr = abstract_cr; + + if (cr->path_is_rectangle) { + cairo_status_t status = _flush_cr_rectangle (cr); + if (unlikely (status)) + return status; + } + + cr->path_ctm_age++; + return cr->dev->backend_parent.restore (abstract_cr); +} + +static cairo_status_t +_cairo_cogl_context_translate (void *abstract_cr, double tx, double ty) +{ + cairo_cogl_context_t *cr = abstract_cr; + + if (cr->path_is_rectangle) { + cairo_status_t status = _flush_cr_rectangle (cr); + if (unlikely (status)) + return status; + } + + cr->path_ctm_age++; + return cr->dev->backend_parent.translate (abstract_cr, tx, ty); +} + +static cairo_status_t +_cairo_cogl_context_scale (void *abstract_cr, double sx, double sy) +{ + cairo_cogl_context_t *cr = abstract_cr; + + if (cr->path_is_rectangle) { + cairo_status_t status = _flush_cr_rectangle (cr); + if (unlikely (status)) + return status; + } + + cr->path_ctm_age++; + return cr->dev->backend_parent.scale (abstract_cr, sx, sy); +} + +static cairo_status_t +_cairo_cogl_context_rotate (void *abstract_cr, double theta) +{ + cairo_cogl_context_t *cr = abstract_cr; + + if (cr->path_is_rectangle) { + cairo_status_t status = _flush_cr_rectangle (cr); + if (unlikely (status)) + return status; + } + + cr->path_ctm_age++; + return cr->dev->backend_parent.rotate (abstract_cr, theta); +} + +static cairo_status_t +_cairo_cogl_context_transform (void *abstract_cr, const cairo_matrix_t *matrix) +{ + cairo_cogl_context_t *cr = abstract_cr; + + if (cr->path_is_rectangle) { + cairo_status_t status = _flush_cr_rectangle (cr); + if (unlikely (status)) + return status; + } + + cr->path_ctm_age++; + return cr->dev->backend_parent.transform (abstract_cr, matrix); +} + +static cairo_status_t +_cairo_cogl_context_set_matrix (void *abstract_cr, const cairo_matrix_t *matrix) +{ + cairo_cogl_context_t *cr = abstract_cr; + + if (cr->path_is_rectangle) { + cairo_status_t status = _flush_cr_rectangle (cr); + if (unlikely (status)) + return status; + } + + cr->path_ctm_age++; + return cr->dev->backend_parent.set_matrix (abstract_cr, matrix); +} + +static cairo_status_t +_cairo_cogl_context_set_identity_matrix (void *abstract_cr) +{ + cairo_cogl_context_t *cr = abstract_cr; + + if (cr->path_is_rectangle) { + cairo_status_t status = _flush_cr_rectangle (cr); + if (unlikely (status)) + return status; + } + + cr->path_ctm_age++; + return cr->dev->backend_parent.set_identity_matrix (abstract_cr); +} + +static cairo_status_t +_cairo_cogl_context_new_path (void *abstract_cr) +{ + cairo_cogl_context_t *cr = abstract_cr; + cairo_status_t status; + + if (cr->path_is_rectangle) { + status = _flush_cr_rectangle (cr); + if (unlikely (status)) + return status; + } + + status = cr->dev->backend_parent.new_path (abstract_cr); + if (unlikely (status)) + return status; + + _cairo_path_fixed_fini (&cr->user_path); + _cairo_path_fixed_init (&cr->user_path); + cr->path_is_rectangle = FALSE; + + return CAIRO_STATUS_SUCCESS; +} + +static cairo_status_t +_cairo_cogl_context_new_sub_path (void *abstract_cr) +{ + cairo_cogl_context_t *cr = abstract_cr; + cairo_status_t status; + + if (cr->path_is_rectangle) { + status = _flush_cr_rectangle (cr); + if (unlikely (status)) + return status; + } + + status = cr->dev->backend_parent.new_sub_path (abstract_cr); + if (unlikely (status)) + return status; + + _cairo_path_fixed_new_sub_path (&cr->user_path); + + return CAIRO_STATUS_SUCCESS; +} + +static cairo_status_t +_cairo_cogl_context_move_to (void *abstract_cr, double x, double y) +{ + cairo_cogl_context_t *cr = abstract_cr; + cairo_status_t status; + cairo_fixed_t x_fixed, y_fixed; + + if (cr->path_is_rectangle) { + status = _flush_cr_rectangle (cr); + if (unlikely (status)) + return status; + } + + status = cr->dev->backend_parent.move_to (abstract_cr, x, y); + if (unlikely (status)) + return status; + + x_fixed = _cairo_fixed_from_double (x); + y_fixed = _cairo_fixed_from_double (y); + + return _cairo_path_fixed_move_to (&cr->user_path, x_fixed, y_fixed); +} + +static cairo_status_t +_cairo_cogl_context_line_to (void *abstract_cr, double x, double y) +{ + cairo_cogl_context_t *cr = abstract_cr; + cairo_status_t status; + cairo_fixed_t x_fixed, y_fixed; + + if (cr->path_is_rectangle) { + status = _flush_cr_rectangle (cr); + if (unlikely (status)) + return status; + } + + status = cr->dev->backend_parent.line_to (abstract_cr, x, y); + if (unlikely (status)) + return status; + + x_fixed = _cairo_fixed_from_double (x); + y_fixed = _cairo_fixed_from_double (y); + + if (cr->user_path.buf.base.num_ops == 0) + cr->path_ctm_age = 0; + + return _cairo_path_fixed_line_to (&cr->user_path, x_fixed, y_fixed); +} + +static cairo_status_t +_cairo_cogl_context_curve_to (void *abstract_cr, + double x1, double y1, + double x2, double y2, + double x3, double y3) +{ + cairo_cogl_context_t *cr = abstract_cr; + cairo_status_t status; + cairo_fixed_t x1_fixed, y1_fixed; + cairo_fixed_t x2_fixed, y2_fixed; + cairo_fixed_t x3_fixed, y3_fixed; + + if (cr->path_is_rectangle) { + status = _flush_cr_rectangle (cr); + if (unlikely (status)) + return status; + } + + status = cr->dev->backend_parent.curve_to (abstract_cr, x1, y1, x2, y2, x3, y3); + if (unlikely (status)) + return status; + + x1_fixed = _cairo_fixed_from_double (x1); + y1_fixed = _cairo_fixed_from_double (y1); + + x2_fixed = _cairo_fixed_from_double (x2); + y2_fixed = _cairo_fixed_from_double (y2); + + x3_fixed = _cairo_fixed_from_double (x3); + y3_fixed = _cairo_fixed_from_double (y3); + + if (cr->user_path.buf.base.num_ops == 0) + cr->path_ctm_age = 0; + + return _cairo_path_fixed_curve_to (&cr->user_path, + x1_fixed, y1_fixed, + x2_fixed, y2_fixed, + x3_fixed, y3_fixed); +} + +static cairo_status_t +_cairo_cogl_context_arc (void *abstract_cr, + double xc, double yc, double radius, + double angle1, double angle2, + cairo_bool_t forward) +{ + cairo_cogl_context_t *cr = abstract_cr; + cairo_status_t status; + + if (cr->path_is_rectangle) { + status = _flush_cr_rectangle (cr); + if (unlikely (status)) + return status; + } + + status = cr->dev->backend_parent.arc (abstract_cr, xc, yc, radius, angle1, angle2, forward); + if (unlikely (status)) + return status; + + if (cr->user_path.buf.base.num_ops == 0) + cr->path_ctm_age = 0; + + /* Do nothing, successfully, if radius is <= 0 */ + if (radius <= 0.0) { + cairo_fixed_t x_fixed, y_fixed; + + x_fixed = _cairo_fixed_from_double (xc); + y_fixed = _cairo_fixed_from_double (yc); + status = _cairo_path_fixed_line_to (&cr->user_path, x_fixed, y_fixed); + if (unlikely (status)) + return status; + + status = _cairo_path_fixed_line_to (&cr->user_path, x_fixed, y_fixed); + if (unlikely (status)) + return status; + + return CAIRO_STATUS_SUCCESS; + } + + status = _cairo_cogl_context_line_to (cr, + xc + radius * cos (angle1), + yc + radius * sin (angle1)); + + if (unlikely (status)) + return status; + + if (forward) + _cairo_arc_path (&cr->base.base, xc, yc, radius, angle1, angle2); + else + _cairo_arc_path_negative (&cr->base.base, xc, yc, radius, angle1, angle2); + + return CAIRO_STATUS_SUCCESS; /* any error will have already been set on cr */ +} + +static cairo_status_t +_cairo_cogl_context_rel_move_to (void *abstract_cr, double dx, double dy) +{ + cairo_cogl_context_t *cr = abstract_cr; + cairo_status_t status; + cairo_fixed_t dx_fixed, dy_fixed; + + if (cr->path_is_rectangle) { + status = _flush_cr_rectangle (cr); + if (unlikely (status)) + return status; + } + + status = cr->dev->backend_parent.rel_move_to (abstract_cr, dx, dy); + if (unlikely (status)) + return status; + + dx_fixed = _cairo_fixed_from_double (dx); + dy_fixed = _cairo_fixed_from_double (dy); + + return _cairo_path_fixed_rel_move_to (&cr->user_path, dx_fixed, dy_fixed); +} + +static cairo_status_t +_cairo_cogl_context_rel_line_to (void *abstract_cr, double dx, double dy) +{ + cairo_cogl_context_t *cr = abstract_cr; + cairo_status_t status; + cairo_fixed_t dx_fixed, dy_fixed; + + if (cr->path_is_rectangle) { + status = _flush_cr_rectangle (cr); + if (unlikely (status)) + return status; + } + + status = cr->dev->backend_parent.rel_line_to (abstract_cr, dx, dy); + if (unlikely (status)) + return status; + + dx_fixed = _cairo_fixed_from_double (dx); + dy_fixed = _cairo_fixed_from_double (dy); + + if (cr->user_path.buf.base.num_ops == 0) + cr->path_ctm_age = 0; + + return _cairo_path_fixed_rel_line_to (&cr->user_path, dx_fixed, dy_fixed); +} + + +static cairo_status_t +_cairo_cogl_context_rel_curve_to (void *abstract_cr, + double dx1, double dy1, + double dx2, double dy2, + double dx3, double dy3) +{ + cairo_cogl_context_t *cr = abstract_cr; + cairo_status_t status; + cairo_fixed_t dx1_fixed, dy1_fixed; + cairo_fixed_t dx2_fixed, dy2_fixed; + cairo_fixed_t dx3_fixed, dy3_fixed; + + if (cr->path_is_rectangle) { + status = _flush_cr_rectangle (cr); + if (unlikely (status)) + return status; + } + + status = cr->dev->backend_parent.rel_curve_to (abstract_cr, dx1, dy1, dx2, dy2, dx3, dy3); + if (unlikely (status)) + return status; + + dx1_fixed = _cairo_fixed_from_double (dx1); + dy1_fixed = _cairo_fixed_from_double (dy1); + + dx2_fixed = _cairo_fixed_from_double (dx2); + dy2_fixed = _cairo_fixed_from_double (dy2); + + dx3_fixed = _cairo_fixed_from_double (dx3); + dy3_fixed = _cairo_fixed_from_double (dy3); + + if (cr->user_path.buf.base.num_ops == 0) + cr->path_ctm_age = 0; + + return _cairo_path_fixed_rel_curve_to (&cr->user_path, + dx1_fixed, dy1_fixed, + dx2_fixed, dy2_fixed, + dx3_fixed, dy3_fixed); +} + +#if 0 +static cairo_status_t +_cairo_cogl_context_arc_to (void *abstract_cr, + double x1, double y1, + double x2, double y2, + double radius) +{ + cairo_cogl_context_t *cr = abstract_cr; + cairo_status_t status; + + if (cr->path_is_rectangle) { + status = _flush_cr_rectangle (cr); + if (unlikely (status)) + return status; + } + + status = cr->dev->backend_parent.arc_to (abstract_cr, x1, y1, x2, y2, radius); + if (unlikely (status)) + return status; +#warning "FIXME" +} + +static cairo_status_t +_cairo_cogl_rel_arc_to (void *cr, + double dx1, double dy1, + double dx2, double dy2, + double radius) +{ + cairo_cogl_context_t *cr = abstract_cr; + cairo_status_t status; + + if (cr->path_is_rectangle) { + status = _flush_cr_rectangle (cr); + if (unlikely (status)) + return status; + } + + status = cr->dev->backend_parent.rel_arc_to (abstract_cr, dx1, dy2, dx2, dy2, radius); + if (unlikely (status)) + return status; +#warning "FIXME" +} +#endif + +static cairo_status_t +_cairo_cogl_context_close_path (void *abstract_cr) +{ + cairo_cogl_context_t *cr = abstract_cr; + cairo_status_t status; + + if (cr->path_is_rectangle) { + status = _flush_cr_rectangle (cr); + if (unlikely (status)) + return status; + } + + status = cr->dev->backend_parent.close_path (abstract_cr); + if (unlikely (status)) + return status; + + if (cr->user_path.buf.base.num_ops == 0) + cr->path_ctm_age = 0; + + return _cairo_path_fixed_close_path (&cr->user_path); +} + +static cairo_status_t +_cairo_cogl_context_rectangle (void *abstract_cr, + double x, double y, + double width, double height) +{ + cairo_cogl_context_t *cr = abstract_cr; + + if (cr->user_path.buf.base.num_ops == 0) { + cr->path_ctm_age = 0; + +#if 1 + /* XXX: Since drawing rectangles is so common we have a + * fast-path for drawing a single rectangle. */ + cr->x = x; + cr->y = y; + cr->width = width; + cr->height = height; + cr->path_is_rectangle = TRUE; + return CAIRO_STATUS_SUCCESS; +#endif + } + + if (cr->path_is_rectangle) { + cairo_status_t status = _flush_cr_rectangle (cr); + if (unlikely (status)) + return status; + } + + return _cairo_cogl_context_rectangle_real (cr, x, y, width, height); +} + +/* Since the surface backend drawing operator functions don't get + * passed the current cairo_t context we don't have a good way + * to get our user-coordinates path into our surface operator + * functions. + * + * For now we use this function to set side band data on the surface + * itself. + */ +static void +_cairo_cogl_surface_set_side_band_state (cairo_cogl_surface_t *surface, + cairo_cogl_context_t *cr) +{ + + if (cr->path_ctm_age <= 1) { + surface->user_path = &cr->user_path; + surface->ctm = &cr->base.gstate->ctm; + surface->ctm_inverse = &cr->base.gstate->ctm_inverse; + surface->path_is_rectangle = cr->path_is_rectangle; + if (surface->path_is_rectangle) { + surface->path_rectangle_x = cr->x; + surface->path_rectangle_y = cr->y; + surface->path_rectangle_width = cr->width; + surface->path_rectangle_height = cr->height; + } + } else { + surface->user_path = NULL; + surface->path_is_rectangle = FALSE; + } +} + +static cairo_status_t +_cairo_cogl_context_fill (void *abstract_cr) +{ + cairo_cogl_context_t *cr = abstract_cr; + cairo_status_t status; + cairo_cogl_surface_t *surface = (cairo_cogl_surface_t *)cr->base.gstate->target; + + if (cr->path_is_rectangle) { + status = _cairo_cogl_surface_fill_rectangle (cr->base.gstate->target, + cr->base.gstate->op, + cr->base.gstate->source, + cr->x, + cr->y, + cr->width, + cr->height, + &cr->base.gstate->ctm, + cr->base.gstate->clip); + if (status == CAIRO_STATUS_SUCCESS) + goto DONE; + _flush_cr_rectangle (cr); + } + + _cairo_cogl_surface_set_side_band_state (surface, cr); + + status = cr->dev->backend_parent.fill (abstract_cr); + if (unlikely (status)) + return status; + +DONE: + _cairo_path_fixed_fini (&cr->user_path); + _cairo_path_fixed_init (&cr->user_path); + cr->path_is_rectangle = FALSE; + + return CAIRO_STATUS_SUCCESS; +} + +static cairo_status_t +_cairo_cogl_context_fill_preserve (void *abstract_cr) +{ + cairo_cogl_context_t *cr = abstract_cr; + cairo_status_t status; + cairo_cogl_surface_t *surface = (cairo_cogl_surface_t *)cr->base.gstate->target; + + _cairo_cogl_surface_set_side_band_state (surface, cr); + + status = cr->dev->backend_parent.fill_preserve (abstract_cr); + if (unlikely (status)) + return status; + + return CAIRO_STATUS_SUCCESS; +} + +static cairo_status_t +_cairo_cogl_context_stroke (void *abstract_cr) +{ + cairo_cogl_context_t *cr = abstract_cr; + cairo_status_t status; + cairo_cogl_surface_t *surface = (cairo_cogl_surface_t *)cr->base.gstate->target; + + _cairo_cogl_surface_set_side_band_state (surface, cr); + + status = cr->dev->backend_parent.stroke (abstract_cr); + if (unlikely (status)) + return status; + + _cairo_path_fixed_fini (&cr->user_path); + _cairo_path_fixed_init (&cr->user_path); + cr->path_is_rectangle = FALSE; + + return CAIRO_STATUS_SUCCESS; +} + +static cairo_status_t +_cairo_cogl_context_stroke_preserve (void *abstract_cr) +{ + cairo_cogl_context_t *cr = abstract_cr; + cairo_status_t status; + cairo_cogl_surface_t *surface = (cairo_cogl_surface_t *)cr->base.gstate->target; + + _cairo_cogl_surface_set_side_band_state (surface, cr); + + status = cr->dev->backend_parent.stroke_preserve (abstract_cr); + if (unlikely (status)) + return status; + + return CAIRO_STATUS_SUCCESS; +} + +static cairo_status_t +_cairo_cogl_context_clip (void *abstract_cr) +{ + cairo_cogl_context_t *cr = abstract_cr; + cairo_status_t status; + + status = cr->dev->backend_parent.clip (abstract_cr); + if (unlikely (status)) + return status; + + _cairo_path_fixed_fini (&cr->user_path); + _cairo_path_fixed_init (&cr->user_path); + cr->path_is_rectangle = FALSE; + + return CAIRO_STATUS_SUCCESS; +} + +static void +_cairo_cogl_context_destroy (void *abstract_cr) +{ + cairo_cogl_context_t *cr = abstract_cr; + + _cairo_default_context_fini (&cr->base); + + _cairo_path_fixed_fini (&cr->user_path); + + /* mark the context as invalid to protect against misuse */ + cr->base.base.status = CAIRO_STATUS_NULL_POINTER; + _freed_pool_put (&context_pool, cr); +} + +/* We want to hook into the frontend of the path construction APIs so + * we can build up a path description in user coordinates instead of + * backend coordinates so that we can recognize user coordinate + * rectangles and so we can hash a user path independent of its + * transform. (With some care to catch unusual cases where the ctm + * changes mid-path) */ +cairo_t * +_cairo_cogl_context_create (void *target) +{ + cairo_cogl_surface_t *surface = target; + cairo_cogl_context_t *cr; + cairo_status_t status; + + cr = _freed_pool_get (&context_pool); + if (unlikely (cr == NULL)) { + cr = malloc (sizeof (cairo_cogl_context_t)); + if (unlikely (cr == NULL)) + return _cairo_create_in_error (_cairo_error (CAIRO_STATUS_NO_MEMORY)); + } + + status = _cairo_default_context_init (&cr->base, target); + if (unlikely (status)) { + _freed_pool_put (&context_pool, cr); + return _cairo_create_in_error (status); + } + + cr->dev = (cairo_cogl_device_t *)surface->base.device; + + if (unlikely (cr->dev->backend_vtable_initialized == FALSE)) { + cairo_backend_t *backend = &cr->dev->backend; + memcpy (backend, cr->base.base.backend, sizeof (cairo_backend_t)); + memcpy (&cr->dev->backend_parent, cr->base.base.backend, sizeof (cairo_backend_t)); + + backend->destroy = _cairo_cogl_context_destroy; + + backend->restore = _cairo_cogl_context_restore; + backend->translate = _cairo_cogl_context_translate; + backend->scale = _cairo_cogl_context_scale; + backend->rotate = _cairo_cogl_context_rotate; + backend->transform = _cairo_cogl_context_transform; + backend->set_matrix = _cairo_cogl_context_set_matrix; + backend->set_identity_matrix = _cairo_cogl_context_set_identity_matrix; + + backend->new_path = _cairo_cogl_context_new_path; + backend->new_sub_path = _cairo_cogl_context_new_sub_path; + backend->move_to = _cairo_cogl_context_move_to; + backend->rel_move_to = _cairo_cogl_context_rel_move_to; + backend->line_to = _cairo_cogl_context_line_to; + backend->rel_line_to = _cairo_cogl_context_rel_line_to; + backend->curve_to = _cairo_cogl_context_curve_to; + backend->rel_curve_to = _cairo_cogl_context_rel_curve_to; +#if 0 + backend->arc_to = _cairo_cogl_context_arc_to; + backend->rel_arc_to = _cairo_cogl_context_rel_arc_to; +#endif + backend->close_path = _cairo_cogl_context_close_path; + //backend->arc = _cairo_cogl_context_arc; + backend->rectangle = _cairo_cogl_context_rectangle; + + /* Try to automatically catch if any new path APIs are added that mean + * we may need to overload more functions... */ + assert (((char *)&backend->path_extents - (char *)&backend->device_to_user_distance) + == (sizeof (void *) * 14)); + + backend->fill = _cairo_cogl_context_fill; + backend->fill_preserve = _cairo_cogl_context_fill_preserve; + backend->stroke = _cairo_cogl_context_stroke; + backend->stroke_preserve = _cairo_cogl_context_stroke_preserve; + backend->clip = _cairo_cogl_context_clip; + + cr->dev->backend_vtable_initialized = TRUE; + } + + cr->base.base.backend = &cr->dev->backend; + + _cairo_path_fixed_init (&cr->user_path); + cr->path_is_rectangle = FALSE; + + return &cr->base.base; +} diff --git a/src/cairo-cogl-gradient-private.h b/src/cairo-cogl-gradient-private.h new file mode 100644 index 00000000..9589b078 --- /dev/null +++ b/src/cairo-cogl-gradient-private.h @@ -0,0 +1,89 @@ +/* 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.og/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. + * + * Contributor(s): + * Robert Bragg <robert@linux.intel.com> + */ + +#ifndef CAIRO_COGL_GRADIENT_PRIVATE_H +#define CAIRO_COGL_GRADIENT_PRIVATE_H + +#include "cairoint.h" +#include "cairo-pattern-private.h" + +#include <cogl/cogl2-experimental.h> + +#define CAIRO_COGL_LINEAR_GRADIENT_CACHE_SIZE (1024 * 1024) + +typedef enum _cairo_cogl_gradient_compatability { + CAIRO_COGL_GRADIENT_CAN_EXTEND_PAD = 1<<0, + CAIRO_COGL_GRADIENT_CAN_EXTEND_REPEAT = 1<<1, + CAIRO_COGL_GRADIENT_CAN_EXTEND_REFLECT = 1<<2, + CAIRO_COGL_GRADIENT_CAN_EXTEND_NONE = 1<<3 +} cairo_cogl_gradient_compatability_t; +#define CAIRO_COGL_GRADIENT_CAN_EXTEND_ALL (CAIRO_COGL_GRADIENT_CAN_EXTEND_PAD |\ + CAIRO_COGL_GRADIENT_CAN_EXTEND_REPEAT|\ + CAIRO_COGL_GRADIENT_CAN_EXTEND_REFLECT|\ + CAIRO_COGL_GRADIENT_CAN_EXTEND_NONE) + +typedef struct _cairo_cogl_linear_texture_entry { + cairo_cogl_gradient_compatability_t compatability; + CoglTexture *texture; + float translate_x; + float scale_x; +} cairo_cogl_linear_texture_entry_t; + +typedef struct _cairo_cogl_linear_gradient { + cairo_cache_entry_t cache_entry; + cairo_reference_count_t ref_count; + GList *textures; + int n_stops; + const cairo_gradient_stop_t *stops; + cairo_gradient_stop_t stops_embedded[1]; +} cairo_cogl_linear_gradient_t; + +cairo_int_status_t +_cairo_cogl_get_linear_gradient (cairo_cogl_device_t *context, + cairo_extend_t extend_mode, + int n_stops, + const cairo_gradient_stop_t *stops, + cairo_cogl_linear_gradient_t **gradient_out); + +cairo_cogl_linear_texture_entry_t * +_cairo_cogl_linear_gradient_texture_for_extend (cairo_cogl_linear_gradient_t *gradient, + cairo_extend_t extend_mode); + +cairo_cogl_linear_gradient_t * +_cairo_cogl_linear_gradient_reference (cairo_cogl_linear_gradient_t *gradient); + +void +_cairo_cogl_linear_gradient_destroy (cairo_cogl_linear_gradient_t *gradient); + +cairo_bool_t +_cairo_cogl_linear_gradient_equal (const void *key_a, const void *key_b); + +#endif /* CAIRO_COGL_GRADIENT_PRIVATE_H */ diff --git a/src/cairo-cogl-gradient.c b/src/cairo-cogl-gradient.c new file mode 100644 index 00000000..2dc07072 --- /dev/null +++ b/src/cairo-cogl-gradient.c @@ -0,0 +1,643 @@ +/* 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.og/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. + * + * Contributor(s): + * Robert Bragg <robert@linux.intel.com> + */ +//#include "cairoint.h" + +#include "cairo-cogl-private.h" +#include "cairo-cogl-gradient-private.h" +#include "cairo-image-surface-private.h" + +#include <cogl/cogl2-experimental.h> +#include <glib.h> + +#define DUMP_GRADIENTS_TO_PNG + +static unsigned long +_cairo_cogl_linear_gradient_hash (unsigned int n_stops, + const cairo_gradient_stop_t *stops) +{ + return _cairo_hash_bytes (n_stops, stops, + sizeof (cairo_gradient_stop_t) * n_stops); +} + +static cairo_cogl_linear_gradient_t * +_cairo_cogl_linear_gradient_lookup (cairo_cogl_device_t *ctx, + unsigned long hash, + unsigned int n_stops, + const cairo_gradient_stop_t *stops) +{ + cairo_cogl_linear_gradient_t lookup; + + lookup.cache_entry.hash = hash, + lookup.n_stops = n_stops; + lookup.stops = stops; + + return _cairo_cache_lookup (&ctx->linear_cache, &lookup.cache_entry); +} + +cairo_bool_t +_cairo_cogl_linear_gradient_equal (const void *key_a, const void *key_b) +{ + const cairo_cogl_linear_gradient_t *a = key_a; + const cairo_cogl_linear_gradient_t *b = key_b; + + if (a->n_stops != b->n_stops) + return FALSE; + + return memcmp (a->stops, b->stops, a->n_stops * sizeof (cairo_gradient_stop_t)) == 0; +} + +cairo_cogl_linear_gradient_t * +_cairo_cogl_linear_gradient_reference (cairo_cogl_linear_gradient_t *gradient) +{ + assert (CAIRO_REFERENCE_COUNT_HAS_REFERENCE (&gradient->ref_count)); + + _cairo_reference_count_inc (&gradient->ref_count); + + return gradient; +} + +void +_cairo_cogl_linear_gradient_destroy (cairo_cogl_linear_gradient_t *gradient) +{ + GList *l; + + assert (CAIRO_REFERENCE_COUNT_HAS_REFERENCE (&gradient->ref_count)); + + if (! _cairo_reference_count_dec_and_test (&gradient->ref_count)) + return; + + for (l = gradient->textures; l; l = l->next) { + cairo_cogl_linear_texture_entry_t *entry = l->data; + cogl_object_unref (entry->texture); + free (entry); + } + g_list_free (gradient->textures); + + free (gradient); +} + +static int +_cairo_cogl_util_next_p2 (int a) +{ + int rval = 1; + + while (rval < a) + rval <<= 1; + + return rval; +} + +static float +get_max_color_component_range (const cairo_color_stop_t *color0, const cairo_color_stop_t *color1) +{ + float range; + float max = 0; + + range = fabs (color0->red - color1->red); + max = MAX (range, max); + range = fabs (color0->green - color1->green); + max = MAX (range, max); + range = fabs (color0->blue - color1->blue); + max = MAX (range, max); + range = fabs (color0->alpha - color1->alpha); + max = MAX (range, max); + + return max; +} + +static int +_cairo_cogl_linear_gradient_width_for_stops (cairo_extend_t extend, + unsigned int n_stops, + const cairo_gradient_stop_t *stops) +{ + unsigned int n; + float max_texels_per_unit_offset = 0; + float total_offset_range; + + /* Find the stop pair demanding the most precision because we are + * interpolating the largest color-component range. + * + * From that we can define the relative sizes of all the other + * stop pairs within our texture and thus the overall size. + * + * To determine the maximum number of texels for a given gap we + * look at the range of colors we are expected to interpolate (so + * long as the stop offsets are not degenerate) and we simply + * assume we want one texel for each unique color value possible + * for a one byte-per-component representation. + * XXX: maybe this is overkill and just allowing 128 levels + * instead of 256 would be enough and then we'd rely on the + * bilinear filtering to give the full range. + * + * XXX: potentially we could try and map offsets to pixels to come + * up with a more precise mapping, but we are aiming to cache + * the gradients so we can't make assumptions about how it will be + * scaled in the future. + */ + for (n = 1; n < n_stops; n++) { + float color_range; + float offset_range; + float texels; + float texels_per_unit_offset; + + /* note: degenerate stops don't need to be represented in the + * texture but we want to be sure that solid gaps get at least + * one texel and all other gaps get at least 2 texels. + */ + + if (stops[n].offset == stops[n-1].offset) + continue; + + color_range = get_max_color_component_range (&stops[n].color, &stops[n-1].color); + if (color_range == 0) + texels = 1; + else + texels = MAX (2, 256.0f * color_range); + + /* So how many texels would we need to map over the full [0,1] + * gradient range so this gap would have enough texels? ... */ + offset_range = stops[n].offset - stops[n - 1].offset; + texels_per_unit_offset = texels / offset_range; + + if (texels_per_unit_offset > max_texels_per_unit_offset) + max_texels_per_unit_offset = texels_per_unit_offset; + } + + total_offset_range = fabs (stops[n_stops - 1].offset - stops[0].offset); + return max_texels_per_unit_offset * total_offset_range; +} + +/* Aim to create gradient textures without an alpha component so we can avoid + * needing to use blending... */ +static CoglPixelFormat +_cairo_cogl_linear_gradient_format_for_stops (cairo_extend_t extend, + unsigned int n_stops, + const cairo_gradient_stop_t *stops) +{ + unsigned int n; + + /* We have to add extra transparent texels to the end of the gradient to + * handle CAIRO_EXTEND_NONE... */ + if (extend == CAIRO_EXTEND_NONE) + return COGL_PIXEL_FORMAT_BGRA_8888_PRE; + + for (n = 1; n < n_stops; n++) { + if (stops[n].color.alpha != 1.0) + return COGL_PIXEL_FORMAT_BGRA_8888_PRE; + } + + return COGL_PIXEL_FORMAT_BGR_888; +} + +static cairo_cogl_gradient_compatability_t +_cairo_cogl_compatability_from_extend_mode (cairo_extend_t extend_mode) +{ + switch (extend_mode) + { + case CAIRO_EXTEND_NONE: + return CAIRO_COGL_GRADIENT_CAN_EXTEND_NONE; + case CAIRO_EXTEND_PAD: + return CAIRO_COGL_GRADIENT_CAN_EXTEND_PAD; + case CAIRO_EXTEND_REPEAT: + return CAIRO_COGL_GRADIENT_CAN_EXTEND_REPEAT; + case CAIRO_EXTEND_REFLECT: + return CAIRO_COGL_GRADIENT_CAN_EXTEND_REFLECT; + } + + assert (0); /* not reached */ + return CAIRO_EXTEND_NONE; +} + +cairo_cogl_linear_texture_entry_t * +_cairo_cogl_linear_gradient_texture_for_extend (cairo_cogl_linear_gradient_t *gradient, + cairo_extend_t extend_mode) +{ + GList *l; + cairo_cogl_gradient_compatability_t compatability = + _cairo_cogl_compatability_from_extend_mode (extend_mode); + for (l = gradient->textures; l; l = l->next) { + cairo_cogl_linear_texture_entry_t *entry = l->data; + if (entry->compatability & compatability) + return entry; + } + return NULL; +} + +static void +color_stop_lerp (const cairo_color_stop_t *c0, + const cairo_color_stop_t *c1, + float factor, + cairo_color_stop_t *dest) +{ + /* NB: we always ignore the short members in this file so we don't need to + * worry about initializing them here. */ + dest->red = c0->red * (1.0f-factor) + c1->red * factor; + dest->green = c0->green * (1.0f-factor) + c1->green * factor; + dest->blue = c0->blue * (1.0f-factor) + c1->blue * factor; + dest->alpha = c0->alpha * (1.0f-factor) + c1->alpha * factor; +} + +static size_t +_cairo_cogl_linear_gradient_size (cairo_cogl_linear_gradient_t *gradient) +{ + GList *l; + size_t size = 0; + for (l = gradient->textures; l; l = l->next) { + cairo_cogl_linear_texture_entry_t *entry = l->data; + size += cogl_texture_get_width (entry->texture) * 4; + } + return size; +} + +static void +emit_stop (CoglVertexP2C4 **position, + float left, + float right, + const cairo_color_stop_t *left_color, + const cairo_color_stop_t *right_color) +{ + CoglVertexP2C4 *p = *position; + + guint8 lr = left_color->red * 255; + guint8 lg = left_color->green * 255; + guint8 lb = left_color->blue * 255; + guint8 la = left_color->alpha * 255; + + guint8 rr = right_color->red * 255; + guint8 rg = right_color->green * 255; + guint8 rb = right_color->blue * 255; + guint8 ra = right_color->alpha * 255; + + p[0].x = left; + p[0].y = 0; + p[0].r = lr; p[0].g = lg; p[0].b = lb; p[0].a = la; + p[1].x = left; + p[1].y = 1; + p[1].r = lr; p[1].g = lg; p[1].b = lb; p[1].a = la; + p[2].x = right; + p[2].y = 1; + p[2].r = rr; p[2].g = rg; p[2].b = rb; p[2].a = ra; + + p[3].x = left; + p[3].y = 0; + p[3].r = lr; p[3].g = lg; p[3].b = lb; p[3].a = la; + p[4].x = right; + p[4].y = 1; + p[4].r = rr; p[4].g = rg; p[4].b = rb; p[4].a = ra; + p[5].x = right; + p[5].y = 0; + p[5].r = rr; p[5].g = rg; p[5].b = rb; p[5].a = ra; + + *position = &p[6]; +} + +#ifdef DUMP_GRADIENTS_TO_PNG +static void +dump_gradient_to_png (CoglTexture *texture) +{ + cairo_image_surface_t *image = (cairo_image_surface_t *) + cairo_image_surface_create (CAIRO_FORMAT_ARGB32, + cogl_texture_get_width (texture), + cogl_texture_get_height (texture)); + CoglPixelFormat format; + static int gradient_id = 0; + char *gradient_name; + + if (image->base.status) + return; + +#if G_BYTE_ORDER == G_LITTLE_ENDIAN + format = COGL_PIXEL_FORMAT_BGRA_8888_PRE; +#else + format = COGL_PIXEL_FORMAT_ARGB_8888_PRE; +#endif + cogl_texture_get_data (texture, + format, + 0, + image->data); + gradient_name = g_strdup_printf ("./gradient%d.png", gradient_id++); + g_print ("writing gradient: %s\n", gradient_name); + cairo_surface_write_to_png ((cairo_surface_t *)image, gradient_name); + g_free (gradient_name); +} +#endif + +cairo_int_status_t +_cairo_cogl_get_linear_gradient (cairo_cogl_device_t *device, + cairo_extend_t extend_mode, + int n_stops, + const cairo_gradient_stop_t *stops, + cairo_cogl_linear_gradient_t **gradient_out) +{ + unsigned long hash; + cairo_cogl_linear_gradient_t *gradient; + cairo_cogl_linear_texture_entry_t *entry; + cairo_gradient_stop_t *internal_stops; + int stop_offset; + int n_internal_stops; + int n; + cairo_cogl_gradient_compatability_t compatabilities; + int width; + int left_padding = 0; + cairo_color_stop_t left_padding_color; + int right_padding = 0; + cairo_color_stop_t right_padding_color; + CoglPixelFormat format; + CoglTexture2D *tex; + GError *error = NULL; + int un_padded_width; + CoglHandle offscreen; + cairo_int_status_t status; + int n_quads; + int n_vertices; + float prev; + float right; + CoglVertexP2C4 *vertices; + CoglVertexP2C4 *p; + CoglPrimitive *prim; + + hash = _cairo_cogl_linear_gradient_hash (n_stops, stops); + + gradient = _cairo_cogl_linear_gradient_lookup (device, hash, n_stops, stops); + if (gradient) { + cairo_cogl_linear_texture_entry_t *entry = + _cairo_cogl_linear_gradient_texture_for_extend (gradient, extend_mode); + if (entry) { + *gradient_out = _cairo_cogl_linear_gradient_reference (gradient); + return CAIRO_INT_STATUS_SUCCESS; + } + } + + if (!gradient) { + gradient = malloc (sizeof (cairo_cogl_linear_gradient_t) + + sizeof (cairo_gradient_stop_t) * (n_stops - 1)); + if (!gradient) + return CAIRO_INT_STATUS_NO_MEMORY; + + CAIRO_REFERENCE_COUNT_INIT (&gradient->ref_count, 1); + /* NB: we update the cache_entry size at the end before + * [re]adding it to the cache. */ + gradient->cache_entry.hash = hash; + gradient->textures = NULL; + gradient->n_stops = n_stops; + gradient->stops = gradient->stops_embedded; + memcpy (gradient->stops_embedded, stops, sizeof (cairo_gradient_stop_t) * n_stops); + } else + _cairo_cogl_linear_gradient_reference (gradient); + + entry = malloc (sizeof (cairo_cogl_linear_texture_entry_t)); + if (!entry) { + status = CAIRO_INT_STATUS_NO_MEMORY; + goto BAIL; + } + + compatabilities = _cairo_cogl_compatability_from_extend_mode (extend_mode); + + n_internal_stops = n_stops; + stop_offset = 0; + + /* We really need stops covering the full [0,1] range for repeat/reflect + * if we want to use sampler REPEAT/MIRROR wrap modes so we may need + * to add some extra stops... */ + if (extend_mode == CAIRO_EXTEND_REPEAT || extend_mode == CAIRO_EXTEND_REFLECT) + { + /* If we don't need any extra stops then actually the texture + * will be shareable for repeat and reflect... */ + compatabilities = (CAIRO_COGL_GRADIENT_CAN_EXTEND_REPEAT | + CAIRO_COGL_GRADIENT_CAN_EXTEND_REFLECT); + + if (stops[0].offset != 0) { + n_internal_stops++; + stop_offset++; + } + + if (stops[n_stops - 1].offset != 1) + n_internal_stops++; + } + + internal_stops = alloca (n_internal_stops * sizeof (cairo_gradient_stop_t)); + memcpy (&internal_stops[stop_offset], stops, sizeof (cairo_gradient_stop_t) * n_stops); + + /* cairo_color_stop_t values are all unpremultiplied but we need to + * interpolate premultiplied colors so we premultiply all the double + * components now. (skipping any extra stops added for repeat/reflect) + * + * Anothing thing to note is that by premultiplying the colors + * early we'll also reduce the range of colors to interpolate + * which can result in smaller gradient textures. + */ + for (n = stop_offset; n < n_stops; n++) { + cairo_color_stop_t *color = &internal_stops[n].color; + color->red *= color->alpha; + color->green *= color->alpha; + color->blue *= color->alpha; + } + + if (n_internal_stops != n_stops) + { + if (extend_mode == CAIRO_EXTEND_REPEAT) { + compatabilities &= ~CAIRO_COGL_GRADIENT_CAN_EXTEND_REFLECT; + if (stops[0].offset != 0) { + /* what's the wrap-around distance between the user's end-stops? */ + double dx = (1.0 - stops[n_stops - 1].offset) + stops[0].offset; + internal_stops[0].offset = 0; + color_stop_lerp (&stops[0].color, + &stops[n_stops - 1].color, + stops[0].offset / dx, + &internal_stops[0].color); + } + if (stops[n_stops - 1].offset != 1) { + internal_stops[n_internal_stops - 1].offset = 1; + internal_stops[n_internal_stops - 1].color = internal_stops[0].color; + } + } else if (extend_mode == CAIRO_EXTEND_REFLECT) { + compatabilities &= ~CAIRO_COGL_GRADIENT_CAN_EXTEND_REPEAT; + if (stops[0].offset != 0) { + internal_stops[0].offset = 0; + internal_stops[0].color = stops[n_stops - 1].color; + } + if (stops[n_stops - 1].offset != 1) { + internal_stops[n_internal_stops - 1].offset = 1; + internal_stops[n_internal_stops - 1].color = stops[0].color; + } + } + } + + stops = internal_stops; + n_stops = n_internal_stops; + + width = _cairo_cogl_linear_gradient_width_for_stops (extend_mode, n_stops, stops); + + if (extend_mode == CAIRO_EXTEND_PAD) { + + /* Here we need to guarantee that the edge texels of our + * texture correspond to the desired padding color so we + * can use CLAMP_TO_EDGE. + * + * For short stop-gaps and especially for degenerate stops + * it's possible that without special consideration the + * user's end stop colors would not be present in our final + * texture. + * + * To handle this we forcibly add two extra padding texels + * at the edges which extend beyond the [0,1] range of the + * gradient itself and we will later report a translate and + * scale transform to compensate for this. + */ + + /* XXX: If we consider generating a mipmap for our 1d texture + * at some point then we also need to consider how much + * padding to add to be sure lower mipmap levels still have + * the desired edge color (as opposed to a linear blend with + * other colors of the gradient). + */ + + left_padding = 1; + left_padding_color = stops[0].color; + right_padding = 1; + right_padding_color = stops[n_stops - 1].color; + } else if (extend_mode == CAIRO_EXTEND_NONE) { + /* We handle EXTEND_NONE by adding two extra, transparent, texels at + * the ends of the texture and use CLAMP_TO_EDGE. + * + * We add a scale and translate transform so to account for our texels + * extending beyond the [0,1] range. */ + + left_padding = 1; + left_padding_color.red = 0; + left_padding_color.green = 0; + left_padding_color.blue = 0; + left_padding_color.alpha = 0; + right_padding = 1; + right_padding_color = left_padding_color; + } + + /* If we still have stops that don't cover the full [0,1] range + * then we need to define a texture-coordinate scale + translate + * transform to account for that... */ + if (stops[n_stops - 1].offset - stops[0].offset < 1) { + float range = stops[n_stops - 1].offset - stops[0].offset; + entry->scale_x = 1.0 / range; + entry->translate_x = -(stops[0].offset * entry->scale_x); + } + + width += left_padding + right_padding; + + width = _cairo_cogl_util_next_p2 (width); + width = MIN (4096, width); /* lets not go too stupidly big! */ + format = _cairo_cogl_linear_gradient_format_for_stops (extend_mode, n_stops, stops); + + do { + tex = cogl_texture_2d_new_with_size (device->cogl_context, + width, + 1, + format, + &error); + if (!tex) + g_error_free (error); + } while (tex == NULL && width >> 1); + + if (!tex) { + status = CAIRO_INT_STATUS_NO_MEMORY; + goto BAIL; + } + + entry->texture = COGL_TEXTURE (tex); + entry->compatability = compatabilities; + + un_padded_width = width - left_padding - right_padding; + + /* XXX: only when we know the final texture width can we calculate the + * scale and translate factors needed to account for padding... */ + if (un_padded_width != width) + entry->scale_x *= (float)un_padded_width / (float)width; + if (left_padding) + entry->translate_x += (entry->scale_x / (float)un_padded_width) * (float)left_padding; + + offscreen = cogl_offscreen_new_to_texture (tex); + cogl_push_framebuffer (COGL_FRAMEBUFFER (offscreen)); + cogl_ortho (0, width, 1, 0, -1, 100); + cogl_framebuffer_clear4f (COGL_FRAMEBUFFER (offscreen), + COGL_BUFFER_BIT_COLOR, + 0, 0, 0, 0); + + n_quads = n_stops - 1 + !!left_padding + !!right_padding; + n_vertices = 6 * n_quads; + vertices = alloca (sizeof (CoglVertexP2C4) * n_vertices); + p = vertices; + if (left_padding) + emit_stop (&p, 0, left_padding, &left_padding_color, &left_padding_color); + prev = (float)left_padding; + for (n = 1; n < n_stops; n++) { + right = (float)left_padding + (float)un_padded_width * stops[n].offset; + emit_stop (&p, prev, right, &stops[n-1].color, &stops[n].color); + prev = right; + } + if (right_padding) + emit_stop (&p, prev, width, &right_padding_color, &right_padding_color); + + prim = cogl_primitive_new_p2c4 (COGL_VERTICES_MODE_TRIANGLES, + n_vertices, + vertices); + /* Just use this as the simplest way to setup a default pipeline... */ + cogl_set_source_color4f (0, 0, 0, 0); + cogl_primitive_draw (prim); + cogl_object_unref (prim); + + cogl_pop_framebuffer (); + cogl_object_unref (offscreen); + + gradient->textures = g_list_prepend (gradient->textures, entry); + gradient->cache_entry.size = _cairo_cogl_linear_gradient_size (gradient); + +#ifdef DUMP_GRADIENTS_TO_PNG + dump_gradient_to_png (COGL_TEXTURE (tex)); +#endif + +#warning "FIXME:" + /* XXX: it seems the documentation of _cairo_cache_insert isn't true - it + * doesn't handle re-adding the same entry gracefully - the cache will + * just keep on growing and then it will start randomly evicting things + * pointlessly */ + /* we ignore errors here and just return an uncached gradient */ + if (likely (! _cairo_cache_insert (&device->linear_cache, &gradient->cache_entry))) + _cairo_cogl_linear_gradient_reference (gradient); + + *gradient_out = gradient; + return CAIRO_INT_STATUS_SUCCESS; + +BAIL: + if (entry) + free (entry); + if (gradient) + _cairo_cogl_linear_gradient_destroy (gradient); + return status; +} diff --git a/src/cairo-cogl-private.h b/src/cairo-cogl-private.h new file mode 100644 index 00000000..13fe5a8d --- /dev/null +++ b/src/cairo-cogl-private.h @@ -0,0 +1,164 @@ +/* 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.og/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. + * + * Contributor(s): + * Robert Bragg <robert@linux.intel.com> + */ + +#ifndef CAIRO_COGL_PRIVATE_H +#define CAIRO_COGL_PRIVATE_H + +#include "cairo-device-private.h" +#include "cairo-cache-private.h" +#include "cairo-backend-private.h" +#include "cairo-default-context-private.h" +#include "cairo-surface-private.h" + +#include <cogl/cogl2-experimental.h> + +typedef enum _cairo_cogl_template_type { + CAIRO_COGL_TEMPLATE_TYPE_SOLID, + CAIRO_COGL_TEMPLATE_TYPE_TEXTURE, + CAIRO_COGL_TEMPLATE_TYPE_MASK_SOLID, + CAIRO_COGL_TEMPLATE_TYPE_MASK_TEXTURE, + CAIRO_COGL_TEMPLATE_TYPE_COUNT +} cairo_cogl_template_type; + +typedef struct _cairo_cogl_device { + cairo_device_t base; + + cairo_bool_t backend_vtable_initialized; + cairo_backend_t backend; + + /* We save a copy of all the original backend methods that we override so + * we can chain up... + */ + cairo_backend_t backend_parent; + + CoglContext *cogl_context; + + CoglTexture *dummy_texture; + + /* This is a sparsely filled set of templates because we don't support + * the full range of operators that cairo has. All entries corresponding + * to unsupported operators are NULL. + * + * The CAIRO_OPERATOR_ADD is the operator enum with the highest value that + * we support so we at least cap the size of the array by that. + * + * For each operator we have a template for when we have a solid source + * and another for each texture format that could be used as a source. + */ + CoglPipeline *template_pipelines[CAIRO_OPERATOR_ADD + 1][CAIRO_COGL_TEMPLATE_TYPE_COUNT]; + + CoglMatrix identity; + + /* Caches 1d linear gradient textures */ + cairo_cache_t linear_cache; + + cairo_cache_t path_fill_staging_cache; + cairo_cache_t path_fill_prim_cache; + cairo_cache_t path_stroke_staging_cache; + cairo_cache_t path_stroke_prim_cache; +} cairo_cogl_device_t; + +typedef struct _cairo_cogl_clip_primitives { + cairo_t *clip; + CoglPrimitive **primitives; +} cairo_cogl_clip_primitives_t; + +typedef struct _cairo_cogl_surface { + cairo_surface_t base; + + CoglPixelFormat cogl_format; + cairo_bool_t ignore_alpha; + + /* We currently have 3 basic kinds of Cogl surfaces: + * 1) A light surface simply wrapping a CoglTexture + * 2) A CoglOffscreen framebuffer that implicitly also wraps a CoglTexture + * 3) A CoglOnscreen framebuffer which could potentially be mapped to + * a CoglTexture (e.g. via tfp on X11) but we don't currently do + * that. + */ + + CoglTexture *texture; + CoglFramebuffer *framebuffer; + + int width; + int height; + + GQueue *journal; + + CoglAttributeBuffer *buffer_stack; + size_t buffer_stack_size; + size_t buffer_stack_offset; + guint8 *buffer_stack_pointer; + + cairo_clip_t *last_clip; + + /* A small fifo of recently used cairo_clip_ts paired with CoglPrimitives + * that can be used to mask the stencil buffer. */ + GList *clips_fifo; + + int n_clip_updates_per_frame; + + /* Since the surface backend drawing operator functions don't get + * passed the current cairo_t context we don't have a good way + * to get our user-coordinates path into our surface_fill function. + * + * For now we use our _cairo_cogl_context_fill() wrapper to set this + * side band data on the surface... + */ + cairo_path_fixed_t *user_path; + cairo_matrix_t *ctm; + cairo_matrix_t *ctm_inverse; + cairo_bool_t path_is_rectangle; + double path_rectangle_x; + double path_rectangle_y; + double path_rectangle_width; + double path_rectangle_height; +} cairo_cogl_surface_t; + +cairo_status_t +_cairo_cogl_path_fixed_rectangle (cairo_path_fixed_t *path, + cairo_fixed_t x, + cairo_fixed_t y, + cairo_fixed_t width, + cairo_fixed_t height); + +cairo_int_status_t +_cairo_cogl_surface_fill_rectangle (void *abstract_surface, + cairo_operator_t op, + const cairo_pattern_t *source, + double x, + double y, + double width, + double height, + cairo_matrix_t *ctm, + const cairo_clip_t *clip); + +#endif /* CAIRO_COGL_PRIVATE_H */ diff --git a/src/cairo-cogl-surface.c b/src/cairo-cogl-surface.c new file mode 100644 index 00000000..0d990b3a --- /dev/null +++ b/src/cairo-cogl-surface.c @@ -0,0 +1,2802 @@ +/* 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.og/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. + * + * Contributor(s): + * Robert Bragg <robert@linux.intel.com> + */ +#include "cairoint.h" + +#include "cairo-cache-private.h" +#include "cairo-error-private.h" +#include "cairo-path-fixed-private.h" +#include "cairo-recording-surface-private.h" +#include "cairo-surface-clipper-private.h" +#include "cairo-fixed-private.h" +#include "cairo-device-private.h" +#include "cairo-composite-rectangles-private.h" +#include "cairo-image-surface-private.h" +#include "cairo-cogl-private.h" +#include "cairo-cogl-gradient-private.h" +#include "cairo-arc-private.h" +#include "cairo-traps-private.h" +#include "cairo-cogl-context-private.h" +#include "cairo-cogl-utils-private.h" +#include "cairo-box-private.h" +#include "cairo-surface-subsurface-private.h" +#include "cairo-surface-fallback-private.h" +#include "cairo-surface-offset-private.h" + +#include "cairo-cogl.h" + +#include <cogl/cogl2-experimental.h> +#include <glib.h> + +#define CAIRO_COGL_DEBUG 0 +//#define FILL_WITH_COGL_PATH +//#define USE_CAIRO_PATH_FLATTENER +#define ENABLE_PATH_CACHE +//#define DISABLE_BATCHING +#define USE_COGL_RECTANGLE_API +#define ENABLE_RECTANGLES_FASTPATH + +#if defined (USE_COGL_RECTANGLE_API) || defined (ENABLE_PATH_CACHE) +#define NEED_COGL_CONTEXT +#endif + +#if CAIRO_COGL_DEBUG && __GNUC__ +#define UNSUPPORTED(reason) ({ \ + g_warning ("cairo-cogl: hit unsupported operation: %s", reason); \ + CAIRO_INT_STATUS_UNSUPPORTED; \ +}) +#else +#define UNSUPPORTED(reason) CAIRO_INT_STATUS_UNSUPPORTED +#endif + +#define CAIRO_COGL_PATH_META_CACHE_SIZE (1024 * 1024) + +typedef struct _cairo_cogl_texture_attributes { + /* nabbed from cairo_surface_attributes_t... */ + cairo_matrix_t matrix; + cairo_extend_t extend; + cairo_filter_t filter; + cairo_bool_t has_component_alpha; + + CoglPipelineWrapMode s_wrap; + CoglPipelineWrapMode t_wrap; +} cairo_cogl_texture_attributes_t; + +typedef enum _cairo_cogl_journal_entry_type { + CAIRO_COGL_JOURNAL_ENTRY_TYPE_RECTANGLE, + CAIRO_COGL_JOURNAL_ENTRY_TYPE_PRIMITIVE, + CAIRO_COGL_JOURNAL_ENTRY_TYPE_PATH, + CAIRO_COGL_JOURNAL_ENTRY_TYPE_CLIP +} cairo_cogl_journal_entry_type_t; + +typedef struct _cairo_cogl_journal_entry { + cairo_cogl_journal_entry_type_t type; +} cairo_cogl_journal_entry_t; + +typedef struct _cairo_cogl_journal_clip_entry { + cairo_cogl_journal_entry_t base; + cairo_clip_t *clip; +} cairo_cogl_journal_clip_entry_t; + +typedef struct _cairo_cogl_journal_rect_entry { + cairo_cogl_journal_entry_t base; + CoglPipeline *pipeline; + float x; + float y; + float width; + float height; + int n_layers; + cairo_matrix_t ctm; +} cairo_cogl_journal_rect_entry_t; + +typedef struct _cairo_cogl_journal_prim_entry { + cairo_cogl_journal_entry_t base; + CoglPipeline *pipeline; + CoglPrimitive *primitive; + gboolean has_transform; + cairo_matrix_t transform; +} cairo_cogl_journal_prim_entry_t; + +typedef struct _cairo_cogl_journal_path_entry { + cairo_cogl_journal_entry_t base; + CoglPipeline *pipeline; + CoglPath *path; +} cairo_cogl_journal_path_entry_t; + +typedef struct _cairo_cogl_path_fill_meta { + cairo_cache_entry_t cache_entry; + cairo_reference_count_t ref_count; + int counter; + cairo_path_fixed_t *user_path; + cairo_matrix_t ctm_inverse; + + /* TODO */ +#if 0 + /* A cached path tessellation should be re-usable with different rotations + * and translations but not for different scales. + * + * one idea is to track the diagonal lenghts of a unit rectangle + * transformed through the original ctm use to tesselate the geometry + * so we can check what the lengths are for any new ctm to know if + * this geometry is compatible. + */ +#endif + + CoglPrimitive *prim; +} cairo_cogl_path_fill_meta_t; + +typedef struct _cairo_cogl_path_stroke_meta { + cairo_cache_entry_t cache_entry; + cairo_reference_count_t ref_count; + int counter; + cairo_path_fixed_t *user_path; + cairo_matrix_t ctm_inverse; + cairo_stroke_style_t style; + double tolerance; + + /* TODO */ +#if 0 + /* A cached path tessellation should be re-usable with different rotations + * and translations but not for different scales. + * + * one idea is to track the diagonal lenghts of a unit rectangle + * transformed through the original ctm use to tesselate the geometry + * so we can check what the lengths are for any new ctm to know if + * this geometry is compatible. + */ +#endif + + CoglPrimitive *prim; +} cairo_cogl_path_stroke_meta_t; + +static cairo_surface_t * +_cairo_cogl_surface_create_full (cairo_cogl_device_t *dev, + cairo_bool_t ignore_alpha, + CoglFramebuffer *framebuffer, + CoglTexture *texture); + +static cairo_int_status_t +_cairo_cogl_surface_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); + +static void +_cairo_cogl_journal_flush (cairo_cogl_surface_t *surface); + +cairo_private extern const cairo_surface_backend_t _cairo_cogl_surface_backend; + +slim_hidden_proto (cairo_cogl_device_create); +slim_hidden_proto (cairo_cogl_surface_create); +slim_hidden_proto (cairo_cogl_surface_get_framebuffer); +slim_hidden_proto (cairo_cogl_surface_get_texture); +slim_hidden_proto (cairo_cogl_surface_end_frame); + +static cairo_cogl_device_t * +to_device (cairo_device_t *device) +{ + return (cairo_cogl_device_t *)device; +} + +/* moves trap points such that they become the actual corners of the trapezoid */ +static void +_sanitize_trap (cairo_trapezoid_t *t) +{ + cairo_trapezoid_t s = *t; + +#define FIX(lr, tb, p) \ + if (t->lr.p.y != t->tb) { \ + t->lr.p.x = s.lr.p2.x + _cairo_fixed_mul_div_floor (s.lr.p1.x - s.lr.p2.x, s.tb - s.lr.p2.y, s.lr.p1.y - s.lr.p2.y); \ + t->lr.p.y = s.tb; \ + } + FIX (left, top, p1); + FIX (left, bottom, p2); + FIX (right, top, p1); + FIX (right, bottom, p2); +} + +static cairo_status_t +_cairo_cogl_surface_ensure_framebuffer (cairo_cogl_surface_t *surface) +{ + GError *error = NULL; + + if (surface->framebuffer) + return CAIRO_STATUS_SUCCESS; + + surface->framebuffer = COGL_FRAMEBUFFER (cogl_offscreen_new_to_texture (surface->texture)); + if (!cogl_framebuffer_allocate (surface->framebuffer, &error)) { + g_error_free (error); + cogl_object_unref (surface->framebuffer); + surface->framebuffer = NULL; + return CAIRO_STATUS_NO_MEMORY; + } + + cogl_push_framebuffer (surface->framebuffer); + cogl_ortho (0, surface->width, + surface->height, 0, + -1, 100); + cogl_pop_framebuffer (); + + return CAIRO_STATUS_SUCCESS; +} + +static cairo_surface_t * +_cairo_cogl_surface_create_similar (void *abstract_surface, + cairo_content_t content, + int width, + int height) +{ + cairo_cogl_surface_t *reference_surface = abstract_surface; + cairo_cogl_surface_t *surface; + CoglTexture *texture; + cairo_status_t status; + + texture = cogl_texture_new_with_size (width, height, + COGL_TEXTURE_NO_SLICING, + (content & CAIRO_CONTENT_COLOR) ? + COGL_PIXEL_FORMAT_BGRA_8888_PRE : + COGL_PIXEL_FORMAT_A_8); + if (!texture) + return _cairo_surface_create_in_error (_cairo_error (CAIRO_STATUS_NO_MEMORY)); + + surface = (cairo_cogl_surface_t *) + _cairo_cogl_surface_create_full (to_device(reference_surface->base.device), + (content & CAIRO_CONTENT_ALPHA) == 0, + NULL, + texture); + if (unlikely (surface->base.status)) + return &surface->base; + + status = _cairo_cogl_surface_ensure_framebuffer (surface); + if (unlikely (status)) { + cairo_surface_destroy (&surface->base); + return _cairo_surface_create_in_error (status); + } + + return &surface->base; +} + +static cairo_bool_t +_cairo_cogl_surface_get_extents (void *abstract_surface, + cairo_rectangle_int_t *extents) +{ + cairo_cogl_surface_t *surface = abstract_surface; + + extents->x = 0; + extents->y = 0; + extents->width = surface->width; + extents->height = surface->height; + + return TRUE; +} + +static void +_cairo_cogl_journal_free (cairo_cogl_surface_t *surface) +{ + GList *l; + + for (l = surface->journal->head; l; l = l->next) { + cairo_cogl_journal_entry_t *entry = l->data; + + if (entry->type == CAIRO_COGL_JOURNAL_ENTRY_TYPE_PRIMITIVE) { + cairo_cogl_journal_prim_entry_t *prim_entry = + (cairo_cogl_journal_prim_entry_t *)entry; + cogl_object_unref (prim_entry->primitive); + } else if (entry->type == CAIRO_COGL_JOURNAL_ENTRY_TYPE_PATH) { + cairo_cogl_journal_path_entry_t *path_entry = + (cairo_cogl_journal_path_entry_t *)entry; + cogl_object_unref (path_entry->path); + } + } + + g_queue_free (surface->journal); + surface->journal = NULL; +} + +#ifdef FILL_WITH_COGL_PATH +static void +_cairo_cogl_journal_log_path (cairo_cogl_surface_t *surface, + CoglPipeline *pipeline, + CoglPath *path) +{ + cairo_cogl_journal_path_entry_t *entry; + + if (unlikely (surface->journal == NULL)) + surface->journal = g_queue_new (); + + /* FIXME: Instead of a GList here we should stack allocate the journal + * entries so it would be cheaper to allocate and they can all be freed in + * one go after flushing! */ + entry = g_slice_new (cairo_cogl_journal_path_entry_t); + entry->base.type = CAIRO_COGL_JOURNAL_ENTRY_TYPE_PATH; + + entry->pipeline = cogl_object_ref (pipeline); + entry->path = cogl_object_ref (path); + + g_queue_push_tail (surface->journal, entry); + +#ifdef DISABLE_BATCHING + _cairo_cogl_journal_flush (surface); +#endif +} +#endif /* FILL_WITH_COGL_PATH */ + +static void +_cairo_cogl_journal_log_primitive (cairo_cogl_surface_t *surface, + CoglPipeline *pipeline, + CoglPrimitive *primitive, + cairo_matrix_t *transform) +{ + cairo_cogl_journal_prim_entry_t *entry; + + if (unlikely (surface->journal == NULL)) + surface->journal = g_queue_new (); + + /* FIXME: Instead of a GList here we should stack allocate the journal + * entries so it would be cheaper to allocate and they can all be freed in + * one go after flushing! */ + entry = g_slice_new (cairo_cogl_journal_prim_entry_t); + entry->base.type = CAIRO_COGL_JOURNAL_ENTRY_TYPE_PRIMITIVE; + + entry->pipeline = cogl_object_ref (pipeline); + + if (transform) { + entry->transform = *transform; + entry->has_transform = TRUE; + } else + entry->has_transform = FALSE; + + entry->primitive = cogl_object_ref (primitive); + + g_queue_push_tail (surface->journal, entry); + +#ifdef DISABLE_BATCHING + _cairo_cogl_journal_flush (surface); +#endif +} + +static void +_cairo_cogl_journal_log_rectangle (cairo_cogl_surface_t *surface, + CoglPipeline *pipeline, + float x, + float y, + float width, + float height, + int n_layers, + cairo_matrix_t *ctm) +{ + cairo_cogl_journal_rect_entry_t *entry; + + if (unlikely (surface->journal == NULL)) + surface->journal = g_queue_new (); + + /* FIXME: Instead of a GList here we should stack allocate the journal + * entries so it would be cheaper to allocate and they can all be freed in + * one go after flushing! */ + entry = g_slice_new (cairo_cogl_journal_rect_entry_t); + entry->base.type = CAIRO_COGL_JOURNAL_ENTRY_TYPE_RECTANGLE; + + entry->pipeline = cogl_object_ref (pipeline); + + entry->x = x; + entry->y = y; + entry->width = width; + entry->height = height; + entry->ctm = *ctm; + + entry->n_layers = n_layers; + + g_queue_push_tail (surface->journal, entry); + +#ifdef DISABLE_BATCHING + _cairo_cogl_journal_flush (surface); +#endif +} + +static void +_cairo_cogl_journal_log_clip (cairo_cogl_surface_t *surface, + const cairo_clip_t *clip) +{ + cairo_cogl_journal_clip_entry_t *entry; + + if (unlikely (surface->journal == NULL)) + surface->journal = g_queue_new (); + + /* FIXME: Instead of a GList here we should stack allocate the journal + * entries so it would be cheaper to allocate and they can all be freed in + * one go after flushing! */ + entry = g_slice_new (cairo_cogl_journal_clip_entry_t); + entry->base.type = CAIRO_COGL_JOURNAL_ENTRY_TYPE_CLIP; + entry->clip = _cairo_clip_copy (clip); + + g_queue_push_tail (surface->journal, entry); +} + +static void +_cairo_cogl_journal_discard (cairo_cogl_surface_t *surface) +{ + GList *l; + + if (!surface->journal) { + assert (surface->last_clip == NULL); + return; + } + + if (surface->buffer_stack && surface->buffer_stack_offset) { + cogl_buffer_unmap (COGL_BUFFER (surface->buffer_stack)); + cogl_object_unref (surface->buffer_stack); + surface->buffer_stack = NULL; + } + + for (l = surface->journal->head; l; l = l->next) { + cairo_cogl_journal_entry_t *entry = l->data; + gsize entry_size; + + switch (entry->type) + { + case CAIRO_COGL_JOURNAL_ENTRY_TYPE_CLIP: { + cairo_cogl_journal_clip_entry_t *clip_entry = + (cairo_cogl_journal_clip_entry_t *)entry; + _cairo_clip_destroy (clip_entry->clip); + entry_size = sizeof (cairo_cogl_journal_clip_entry_t); + break; + } + case CAIRO_COGL_JOURNAL_ENTRY_TYPE_RECTANGLE: { + cairo_cogl_journal_rect_entry_t *rect_entry = + (cairo_cogl_journal_rect_entry_t *)entry; + cogl_object_unref (rect_entry->pipeline); + entry_size = sizeof (cairo_cogl_journal_rect_entry_t); + break; + } + case CAIRO_COGL_JOURNAL_ENTRY_TYPE_PRIMITIVE: { + cairo_cogl_journal_prim_entry_t *prim_entry = + (cairo_cogl_journal_prim_entry_t *)entry; + cogl_object_unref (prim_entry->pipeline); + cogl_object_unref (prim_entry->primitive); + entry_size = sizeof (cairo_cogl_journal_prim_entry_t); + break; + } + case CAIRO_COGL_JOURNAL_ENTRY_TYPE_PATH: { + cairo_cogl_journal_path_entry_t *path_entry = + (cairo_cogl_journal_path_entry_t *)entry; + cogl_object_unref (path_entry->pipeline); + cogl_object_unref (path_entry->path); + entry_size = sizeof (cairo_cogl_journal_path_entry_t); + break; + } + default: + assert (0); /* not reached! */ + entry_size = 0; /* avoid compiler warning */ + } + g_slice_free1 (entry_size, entry); + } + + g_queue_clear (surface->journal); + + if (surface->last_clip) { + _cairo_clip_destroy (surface->last_clip); + surface->last_clip = NULL; + } +} + +static CoglAttributeBuffer * +_cairo_cogl_surface_allocate_buffer_space (cairo_cogl_surface_t *surface, + size_t size, + size_t *offset, + void **pointer) +{ + /* XXX: In the Cogl journal we found it more efficient to have a pool of + * buffers that we re-cycle but for now we simply thow away our stack + * buffer each time we flush. */ + if (unlikely (surface->buffer_stack && + (surface->buffer_stack_size - surface->buffer_stack_offset) < size)) { + cogl_buffer_unmap (COGL_BUFFER (surface->buffer_stack)); + cogl_object_unref (surface->buffer_stack); + surface->buffer_stack = NULL; + surface->buffer_stack_size *= 2; + } + + if (unlikely (surface->buffer_stack_size < size)) + surface->buffer_stack_size = size * 2; + + if (unlikely (surface->buffer_stack == NULL)) { + surface->buffer_stack = cogl_attribute_buffer_new (surface->buffer_stack_size, NULL); + surface->buffer_stack_pointer = + cogl_buffer_map (COGL_BUFFER (surface->buffer_stack), + COGL_BUFFER_ACCESS_WRITE, + COGL_BUFFER_MAP_HINT_DISCARD); + surface->buffer_stack_offset = 0; + } + + *pointer = surface->buffer_stack_pointer + surface->buffer_stack_offset; + *offset = surface->buffer_stack_offset; + + surface->buffer_stack_offset += size; + return cogl_object_ref (surface->buffer_stack); +} + + +static CoglAttributeBuffer * +_cairo_cogl_traps_to_triangles_buffer (cairo_cogl_surface_t *surface, + cairo_traps_t *traps, + size_t *offset, + gboolean one_shot) +{ + CoglAttributeBuffer *buffer; + int n_traps = traps->num_traps; + int i; + CoglVertexP2 *triangles; + + if (one_shot) { + buffer = _cairo_cogl_surface_allocate_buffer_space (surface, + n_traps * sizeof (CoglVertexP2) * 6, + offset, + (void **)&triangles); + if (!buffer) + return NULL; + } else { + buffer = cogl_attribute_buffer_new (n_traps * sizeof (CoglVertexP2) * 6, NULL); + if (!buffer) + return NULL; + triangles = cogl_buffer_map (COGL_BUFFER (buffer), + COGL_BUFFER_ACCESS_WRITE, + COGL_BUFFER_MAP_HINT_DISCARD); + if (!triangles) + return NULL; + *offset = 0; + } + + /* XXX: This is can be very expensive. I'm not sure a.t.m if it's + * predominantly the bandwidth required or the cost of the fixed_to_float + * conversions but either way we should try using an index buffer to + * reduce the amount we upload by 1/3 (offset by allocating and uploading + * indices though) sadly though my experience with the intel mesa drivers + * is that slow paths can easily be hit when starting to use indices. + */ + for (i = 0; i < n_traps; i++) + { + CoglVertexP2 *p = &triangles[i * 6]; + cairo_trapezoid_t *trap = &traps->traps[i]; + + p[0].x = _cairo_cogl_util_fixed_to_float (trap->left.p1.x); + p[0].y = _cairo_cogl_util_fixed_to_float (trap->left.p1.y); + + p[1].x = _cairo_cogl_util_fixed_to_float (trap->left.p2.x); + p[1].y = _cairo_cogl_util_fixed_to_float (trap->left.p2.y); + + p[2].x = _cairo_cogl_util_fixed_to_float (trap->right.p2.x); + p[2].y = _cairo_cogl_util_fixed_to_float (trap->right.p2.y); + + p[3].x = _cairo_cogl_util_fixed_to_float (trap->left.p1.x); + p[3].y = _cairo_cogl_util_fixed_to_float (trap->left.p1.y); + + p[4].x = _cairo_cogl_util_fixed_to_float (trap->right.p2.x); + p[4].y = _cairo_cogl_util_fixed_to_float (trap->right.p2.y); + + p[5].x = _cairo_cogl_util_fixed_to_float (trap->right.p1.x); + p[5].y = _cairo_cogl_util_fixed_to_float (trap->right.p1.y); + } + + if (!one_shot) + cogl_buffer_unmap (COGL_BUFFER (buffer)); + + return buffer; +} + +/* Used for solid fills, in this case we just need a mesh made of + * a single (2-component) position attribute. */ +static CoglPrimitive * +_cairo_cogl_traps_to_composite_prim_p2 (cairo_cogl_surface_t *surface, + cairo_traps_t *traps, + gboolean one_shot) +{ + size_t offset; + CoglAttributeBuffer *buffer = _cairo_cogl_traps_to_triangles_buffer (surface, traps, &offset, one_shot); + CoglAttribute *pos = cogl_attribute_new (buffer, + "cogl_position_in", + sizeof (CoglVertexP2), + offset, + 2, + COGL_ATTRIBUTE_TYPE_FLOAT); + CoglPrimitive *prim; + + /* The attribute will have taken a reference on the buffer */ + cogl_object_unref (buffer); + + prim = cogl_primitive_new (COGL_VERTICES_MODE_TRIANGLES, + traps->num_traps * 6, pos, NULL); + + /* The primitive will now keep the attribute alive... */ + cogl_object_unref (pos); + + return prim; +} + +/* Used for surface fills, in this case we need a mesh made of a single + * (2-component) position attribute + we also alias the same attribute as + * (2-component) texture coordinates */ +static CoglPrimitive * +_cairo_cogl_traps_to_composite_prim_p2t2 (cairo_cogl_surface_t *surface, + cairo_traps_t *traps, + gboolean one_shot) +{ + size_t offset; + CoglAttributeBuffer *buffer = _cairo_cogl_traps_to_triangles_buffer (surface, traps, &offset, one_shot); + CoglAttribute *pos = cogl_attribute_new (buffer, + "cogl_position_in", + sizeof (CoglVertexP2), + offset, + 2, + COGL_ATTRIBUTE_TYPE_FLOAT); + CoglAttribute *tex_coords = cogl_attribute_new (buffer, + "cogl_tex_coord0_in", + sizeof (CoglVertexP2), + 0, + 2, + COGL_ATTRIBUTE_TYPE_FLOAT); + CoglPrimitive *prim; + + /* The attributes will have taken references on the buffer */ + cogl_object_unref (buffer); + + prim = cogl_primitive_new (COGL_VERTICES_MODE_TRIANGLES, + traps->num_traps * 6, pos, tex_coords, NULL); + + /* The primitive will now keep the attributes alive... */ + cogl_object_unref (pos); + cogl_object_unref (tex_coords); + + return prim; +} + +static CoglPrimitive * +_cairo_cogl_traps_to_composite_prim (cairo_cogl_surface_t *surface, + cairo_traps_t *traps, + int n_layers, + gboolean one_shot) +{ + int n_traps = traps->num_traps; + int i; + + /* XXX: Ideally we would skip tessellating to traps entirely since + * given their representation, conversion to triangles is quite expensive. + * + * This simplifies the conversion to triangles by making the end points of + * the two side lines actually just correspond to the corners of the + * traps. + */ + for (i = 0; i < n_traps; i++) + _sanitize_trap (&traps->traps[i]); + + if (n_layers == 0) + return _cairo_cogl_traps_to_composite_prim_p2 (surface, traps, one_shot); + else { + assert (n_layers == 1); + return _cairo_cogl_traps_to_composite_prim_p2t2 (surface, traps, one_shot); + } +} + +static cairo_int_status_t +_cairo_cogl_fill_to_primitive (cairo_cogl_surface_t *surface, + const cairo_path_fixed_t *path, + cairo_fill_rule_t fill_rule, + double tolerance, + int n_layers, + cairo_bool_t one_shot, + CoglPrimitive **primitive, + size_t *size) +{ + cairo_traps_t traps; + cairo_int_status_t status; + + _cairo_traps_init (&traps); + status = _cairo_path_fixed_fill_to_traps (path, fill_rule, tolerance, &traps); + if (unlikely (status)) + goto BAIL; + + if (traps.num_traps == 0) { + status = CAIRO_INT_STATUS_NOTHING_TO_DO; + goto BAIL; + } + + *size = traps.num_traps * sizeof (CoglVertexP2) * 6; + + *primitive = _cairo_cogl_traps_to_composite_prim (surface, &traps, n_layers, one_shot); + if (!*primitive) { + status = CAIRO_INT_STATUS_NO_MEMORY; + goto BAIL; + } + +BAIL: + _cairo_traps_fini (&traps); + return status; +} + +static void +_cairo_cogl_clip_push_box (const cairo_box_t *box) +{ + if (_cairo_box_is_pixel_aligned (box)) { + cairo_rectangle_int_t rect; + _cairo_box_round_to_rectangle (box, &rect); + cogl_clip_push_window_rectangle (rect.x, rect.y, + rect.width, rect.height); + } else { + double x1, y1, x2, y2; + _cairo_box_to_doubles (box, &x1, &y1, &x2, &y2); + cogl_clip_push_rectangle (x1, y1, x2, y2); + } +} + +static void +_cairo_cogl_journal_flush (cairo_cogl_surface_t *surface) +{ + GList *l; + int clip_stack_depth = 0; + int i; + + if (!surface->journal) + return; + + if (surface->buffer_stack && surface->buffer_stack_offset) { + cogl_buffer_unmap (COGL_BUFFER (surface->buffer_stack)); + cogl_object_unref (surface->buffer_stack); + surface->buffer_stack = NULL; + } + + cogl_set_framebuffer (surface->framebuffer); + + cogl_push_matrix (); + + for (l = surface->journal->head; l; l = l->next) { + cairo_cogl_journal_entry_t *entry = l->data; + + switch (entry->type) + { + case CAIRO_COGL_JOURNAL_ENTRY_TYPE_CLIP: { + cairo_cogl_journal_clip_entry_t *clip_entry = + (cairo_cogl_journal_clip_entry_t *)entry; + cairo_clip_path_t *path; +#if 0 + cairo_bool_t checked_for_primitives = FALSE; + cairo_cogl_clip_primitives_t *clip_primitives; +#endif + + for (i = 0; i < clip_stack_depth; i++) + cogl_clip_pop (); + clip_stack_depth = 0; + + for (path = clip_entry->clip->path, i = 0; path; path = path->prev, i++) { + cairo_rectangle_int_t extents; + cairo_int_status_t status; + CoglPrimitive *prim; + size_t prim_size; + + _cairo_path_fixed_approximate_clip_extents (&path->path, &extents); + + /* TODO - maintain a fifo of the last 10 used clips with cached + * primitives to see if we can avoid tesselating the path and + * uploading the vertices... + */ +#if 0 + if (!checked_for_primitives) { + clip_primitives = find_clip_primitives (clip); + checked_for_primitives = TRUE; + } + if (clip_primitives) + prim = clip_primitives->primitives[i]; +#endif + status = _cairo_cogl_fill_to_primitive (surface, + &path->path, + path->fill_rule, + path->tolerance, + 0, + TRUE, + &prim, + &prim_size); + if (unlikely (status)) { + g_warning ("Failed to get primitive for clip path while flushing journal"); + continue; + } + clip_stack_depth++; + cogl_clip_push_primitive (prim, + extents.x, extents.y, + extents.x + extents.width, + extents.y + extents.height); + cogl_object_unref (prim); + } + + for (i = 0; i < clip_entry->clip->num_boxes; i++) { + clip_stack_depth++; + _cairo_cogl_clip_push_box (&clip_entry->clip->boxes[i]); + } + + surface->n_clip_updates_per_frame++; + break; + } + case CAIRO_COGL_JOURNAL_ENTRY_TYPE_RECTANGLE: { + cairo_cogl_journal_rect_entry_t *rect_entry = + (cairo_cogl_journal_rect_entry_t *)entry; + float tex_coords[8]; + float x1 = rect_entry->x; + float y1 = rect_entry->y; + float x2 = rect_entry->x + rect_entry->width; + float y2 = rect_entry->y + rect_entry->height; + cairo_matrix_t *ctm = &rect_entry->ctm; + float ctmfv[16] = { + ctm->xx, ctm->yx, 0, 0, + ctm->xy, ctm->yy, 0, 0, + 0, 0, 1, 0, + ctm->x0, ctm->y0, 0, 1 + }; + CoglMatrix transform; + + cogl_matrix_init_from_array (&transform, ctmfv); + + if (rect_entry->n_layers) { + g_assert (rect_entry->n_layers <= 2); + tex_coords[0] = x1; + tex_coords[1] = y1; + tex_coords[2] = x2; + tex_coords[3] = y2; + if (rect_entry->n_layers > 1) + memcpy (&tex_coords[4], tex_coords, sizeof (float) * 4); + } + + cogl_set_source (rect_entry->pipeline); + cogl_push_matrix (); + cogl_transform (&transform); + cogl_rectangle_with_multitexture_coords (x1, y1, x2, y2, + tex_coords, 4 * rect_entry->n_layers); + cogl_pop_matrix (); + break; + } + case CAIRO_COGL_JOURNAL_ENTRY_TYPE_PRIMITIVE: { + cairo_cogl_journal_prim_entry_t *prim_entry = + (cairo_cogl_journal_prim_entry_t *)entry; + CoglMatrix transform; + + cogl_push_matrix (); + if (prim_entry->has_transform) { + cairo_matrix_t *ctm = &prim_entry->transform; + float ctmfv[16] = { + ctm->xx, ctm->yx, 0, 0, + ctm->xy, ctm->yy, 0, 0, + 0, 0, 1, 0, + ctm->x0, ctm->y0, 0, 1 + }; + cogl_matrix_init_from_array (&transform, ctmfv); + cogl_transform (&transform); + } else { + cogl_matrix_init_identity (&transform); + cogl_set_modelview_matrix (&transform); + } + + cogl_set_source (prim_entry->pipeline); + cogl_primitive_draw (prim_entry->primitive); + cogl_pop_matrix (); + break; + } + case CAIRO_COGL_JOURNAL_ENTRY_TYPE_PATH: { + cairo_cogl_journal_path_entry_t *path_entry = + (cairo_cogl_journal_path_entry_t *)entry; + + cogl_set_source (path_entry->pipeline); + cogl_path_fill (path_entry->path); + break; + } + default: + assert (0); /* not reached! */ + } + } + + cogl_pop_matrix (); + + for (i = 0; i < clip_stack_depth; i++) + cogl_clip_pop (); + + _cairo_cogl_journal_discard (surface); +} + +static cairo_status_t +_cairo_cogl_surface_flush (void *abstract_surface) +{ + cairo_cogl_surface_t *surface = (cairo_cogl_surface_t *)abstract_surface; + + _cairo_cogl_journal_flush (surface); + + return CAIRO_STATUS_SUCCESS; +} + +static cairo_status_t +_cairo_cogl_surface_finish (void *abstract_surface) +{ + cairo_cogl_surface_t *surface = abstract_surface; + + if (surface->texture) + cogl_object_unref (surface->texture); + + if (surface->framebuffer) + cogl_object_unref (surface->framebuffer); + + if (surface->journal) + _cairo_cogl_journal_free (surface); + + /*XXX wtf */ + cairo_device_release (surface->base.device); + + return CAIRO_STATUS_SUCCESS; +} + +static CoglPixelFormat +get_cogl_format_from_cairo_format (cairo_format_t cairo_format); + +/* XXX: We often use RGBA format for onscreen framebuffers so make sure + * to handle CAIRO_FORMAT_INVALID sensibly */ +static cairo_format_t +get_cairo_format_from_cogl_format (CoglPixelFormat format) +{ + switch ((int)format) + { + case COGL_PIXEL_FORMAT_A_8: + return CAIRO_FORMAT_A8; + case COGL_PIXEL_FORMAT_RGB_565: + return CAIRO_FORMAT_RGB16_565; + + case COGL_PIXEL_FORMAT_BGRA_8888_PRE: + case COGL_PIXEL_FORMAT_ARGB_8888_PRE: + case COGL_PIXEL_FORMAT_RGBA_8888_PRE: + /* Note: this is ambiguous since CAIRO_FORMAT_RGB24 + * would also map to the same CoglPixelFormat */ + return CAIRO_FORMAT_ARGB32; + + default: + g_warning("bad format: %x a? %d, bgr? %d, pre %d, format: %d\n", + format, + format & COGL_A_BIT, + format & COGL_BGR_BIT, + format & COGL_PREMULT_BIT, + format & ~(COGL_A_BIT | COGL_BGR_BIT | COGL_PREMULT_BIT)); + return CAIRO_FORMAT_INVALID; + } +} + +static CoglPixelFormat +get_cogl_format_from_cairo_format (cairo_format_t cairo_format) +{ + switch (cairo_format) + { + case CAIRO_FORMAT_ARGB32: + case CAIRO_FORMAT_RGB24: +#if G_BYTE_ORDER == G_LITTLE_ENDIAN + return COGL_PIXEL_FORMAT_BGRA_8888_PRE; +#else + return COGL_PIXEL_FORMAT_ARGB_8888_PRE; +#endif + case CAIRO_FORMAT_A8: + return COGL_PIXEL_FORMAT_A_8; + case CAIRO_FORMAT_RGB16_565: + return COGL_PIXEL_FORMAT_RGB_565; + case CAIRO_FORMAT_INVALID: + case CAIRO_FORMAT_A1: + case CAIRO_FORMAT_RGB30: + return 0; + } + + g_warn_if_reached (); + return 0; +} + +static cairo_status_t +_cairo_cogl_surface_read_rect_to_image_surface (cairo_cogl_surface_t *surface, + cairo_rectangle_int_t *interest, + cairo_image_surface_t **image_out) +{ + cairo_image_surface_t *image; + cairo_status_t status; + cairo_format_t cairo_format; + CoglPixelFormat cogl_format; + + /* TODO: Add cogl_texture_get_region() API so we don't have to ensure the + * surface is bound to an fbo to read back pixels */ + status = _cairo_cogl_surface_ensure_framebuffer (surface); + if (unlikely (status)) + return status; + + cairo_format = get_cairo_format_from_cogl_format (surface->cogl_format); + if (cairo_format == CAIRO_FORMAT_INVALID) { + cairo_format = CAIRO_FORMAT_ARGB32; + cogl_format = get_cogl_format_from_cairo_format (cairo_format); + } else { + cogl_format = cogl_framebuffer_get_color_format (surface->framebuffer); + } + + image = (cairo_image_surface_t *) + cairo_image_surface_create (cairo_format, surface->width, surface->height); + if (image->base.status) + return image->base.status; + + /* TODO: Add cogl_framebuffer_read_pixels() API */ + cogl_push_framebuffer (surface->framebuffer); + cogl_read_pixels (0, 0, surface->width, surface->height, + COGL_READ_PIXELS_COLOR_BUFFER, + cogl_format, + image->data); + cogl_pop_framebuffer (); + + *image_out = image; + + return CAIRO_STATUS_SUCCESS; +} + +static cairo_status_t +_cairo_cogl_surface_acquire_source_image (void *abstract_surface, + cairo_image_surface_t **image_out, + void **image_extra) +{ + cairo_cogl_surface_t *surface = abstract_surface; + cairo_status_t status; + + if (surface->texture) { + cairo_format_t format = get_cairo_format_from_cogl_format (surface->cogl_format); + cairo_image_surface_t *image = (cairo_image_surface_t *) + cairo_image_surface_create (format, surface->width, surface->height); + if (image->base.status) + return image->base.status; + + cogl_texture_get_data (surface->texture, + cogl_texture_get_format (surface->texture), + 0, + image->data); + + image->base.is_clear = FALSE; + *image_out = image; + } else { + cairo_rectangle_int_t extents = { + 0, 0, surface->width, surface->height + }; + status = _cairo_cogl_surface_read_rect_to_image_surface (surface, &extents, + image_out); + if (unlikely (status)) + return status; + } + + *image_extra = NULL; + + return CAIRO_STATUS_SUCCESS; +} + +static void +_cairo_cogl_surface_release_source_image (void *abstract_surface, + cairo_image_surface_t *image, + void *image_extra) +{ + cairo_surface_destroy (&image->base); +} + +static cairo_status_t +_cairo_cogl_surface_clear (cairo_cogl_surface_t *surface, + const cairo_color_t *color) +{ + /* Anything batched in the journal up until now is redundant... */ + _cairo_cogl_journal_discard (surface); + + /* XXX: we currently implicitly clear the depth and stencil buffer here + * but since we use the framebuffer_discard extension when available I + * suppose this doesn't matter too much. + * + * The main concern is that we want to avoid re-loading an external z + * buffer at the start of each frame, but also many gpu architectures have + * optmizations for how they handle the depth/stencil buffers and can get + * upset if they aren't cleared together at the start of the frame. + * + * FIXME: we need a way to assert that the clip stack currently isn't + * using the stencil buffer before clearing it here! + */ + cogl_framebuffer_clear4f (surface->framebuffer, + COGL_BUFFER_BIT_COLOR | + COGL_BUFFER_BIT_DEPTH | + COGL_BUFFER_BIT_STENCIL, + color->red * color->alpha, + color->green * color->alpha, + color->blue * color->alpha, + color->alpha); + return CAIRO_STATUS_SUCCESS; +} + +cairo_status_t +_cairo_cogl_path_fixed_rectangle (cairo_path_fixed_t *path, + cairo_fixed_t x, + cairo_fixed_t y, + cairo_fixed_t width, + cairo_fixed_t height) +{ + cairo_status_t status; + + status = _cairo_path_fixed_move_to (path, x, y); + if (unlikely (status)) + return status; + + status = _cairo_path_fixed_rel_line_to (path, width, 0); + if (unlikely (status)) + return status; + + status = _cairo_path_fixed_rel_line_to (path, 0, height); + if (unlikely (status)) + return status; + + status = _cairo_path_fixed_rel_line_to (path, -width, 0); + if (unlikely (status)) + return status; + + status = _cairo_path_fixed_close_path (path); + if (unlikely (status)) + return status; + + return CAIRO_STATUS_SUCCESS; +} + +static cairo_int_status_t +_cairo_cogl_surface_paint (void *abstract_surface, + cairo_operator_t op, + const cairo_pattern_t *source, + const cairo_clip_t *clip) +{ + cairo_cogl_surface_t *surface; + cairo_path_fixed_t path; + cairo_status_t status; + cairo_matrix_t identity; + + if (clip == NULL) { + if (op == CAIRO_OPERATOR_CLEAR) + return _cairo_cogl_surface_clear (abstract_surface, CAIRO_COLOR_TRANSPARENT); + else if (source->type == CAIRO_PATTERN_TYPE_SOLID && + (op == CAIRO_OPERATOR_SOURCE || + (op == CAIRO_OPERATOR_OVER && (((cairo_surface_t *)abstract_surface)->is_clear || _cairo_pattern_is_opaque_solid (source))))) { + return _cairo_cogl_surface_clear (abstract_surface, + &((cairo_solid_pattern_t *) source)->color); + } + } + + /* fallback to handling the paint in terms of a fill... */ + + surface = abstract_surface; + + _cairo_path_fixed_init (&path); + + status = _cairo_cogl_path_fixed_rectangle (&path, 0, 0, surface->width, surface->height); + if (unlikely (status)) + goto BAIL; + +#ifdef NEED_COGL_CONTEXT + /* XXX: in cairo-cogl-context.c we set some sideband data on the + * surface before issuing a fill so we need to do that here too... */ + surface->user_path = &path; + cairo_matrix_init_identity (&identity); + surface->ctm = &identity; + surface->ctm_inverse = &identity; + surface->path_is_rectangle = TRUE; + surface->path_rectangle_x = 0; + surface->path_rectangle_y = 0; + surface->path_rectangle_width = surface->width; + surface->path_rectangle_height = surface->height; +#endif + + status = _cairo_cogl_surface_fill (abstract_surface, + op, + source, + &path, + CAIRO_FILL_RULE_WINDING, + 1, + CAIRO_ANTIALIAS_DEFAULT, + clip); +BAIL: + _cairo_path_fixed_fini (&path); + return status; +} + +static CoglPipelineWrapMode +get_cogl_wrap_mode_for_extend (cairo_extend_t extend_mode) +{ + switch (extend_mode) + { + case CAIRO_EXTEND_NONE: + return COGL_PIPELINE_WRAP_MODE_CLAMP_TO_EDGE; + case CAIRO_EXTEND_PAD: + return COGL_PIPELINE_WRAP_MODE_CLAMP_TO_EDGE; + case CAIRO_EXTEND_REPEAT: + return COGL_PIPELINE_WRAP_MODE_REPEAT; + case CAIRO_EXTEND_REFLECT: + /* TODO: return COGL_PIPELINE_WRAP_MODE_MIRROR; */ + return CAIRO_EXTEND_REPEAT; + } + assert (0); /* not reached */ + return COGL_PIPELINE_WRAP_MODE_CLAMP_TO_EDGE; +} + +#if 0 +/* Given an arbitrary texture, check if it's already a pot texture and simply + * return it back if so. If not create a new pot texture, scale the old to + * fill it, unref the old and return a pointer to the new pot texture. */ +static cairo_int_status_t +_cairo_cogl_get_pot_texture (CoglContext *context, + CoglTexture *texture, + CoglTexture **pot_texture) +{ + int width = cogl_texture_get_width (texture); + int height = cogl_texture_get_height (texture); + int pot_width; + int pot_height; + CoglHandle offscreen = NULL; + CoglTexture2D *pot = NULL; + GError *error; + + pot_width = _cairo_cogl_util_next_p2 (width); + pot_height = _cairo_cogl_util_next_p2 (height); + + if (pot_width == width && pot_height == height) + return CAIRO_INT_STATUS_SUCCESS; + + for (;;) { + error = NULL; + pot = cogl_texture_2d_new_with_size (context, + pot_width, + pot_height, + cogl_texture_get_format (texture), + &error); + if (pot) + break; + else + g_error_free (error); + + if (pot_width > pot_height) + pot_width >>= 1; + else + pot_height >>= 1; + + if (!pot_width || !pot_height) + break; + } + + *pot_texture = COGL_TEXTURE (pot); + + if (!pot) + return CAIRO_INT_STATUS_NO_MEMORY; + + /* Use the GPU to do a bilinear filtered scale from npot to pot... */ + offscreen = cogl_offscreen_new_to_texture (COGL_TEXTURE (pot)); + error = NULL; + if (!cogl_framebuffer_allocate (COGL_FRAMEBUFFER (offscreen), &error)) { + /* NB: if we don't pass an error then Cogl is allowed to simply abort + * automatically. */ + g_error_free (error); + cogl_object_unref (pot); + *pot_texture = NULL; + return CAIRO_INT_STATUS_NO_MEMORY; + } + + cogl_push_framebuffer (COGL_FRAMEBUFFER (offscreen)); + cogl_set_source_texture (texture); + cogl_rectangle (-1, 1, 1, -1); + cogl_pop_framebuffer (); + + cogl_object_unref (offscreen); +} +#endif + +/* NB: a reference for the texture is transferred to the caller which should + * be unrefed */ +static CoglTexture * +_cairo_cogl_acquire_surface_texture (cairo_cogl_surface_t *reference_surface, + cairo_surface_t *abstract_surface) +{ + cairo_image_surface_t *image; + cairo_image_surface_t *acquired_image = NULL; + void *image_extra; + CoglPixelFormat format; + cairo_image_surface_t *image_clone = NULL; + CoglTexture2D *texture; + GError *error = NULL; + cairo_surface_t *clone; + + if (abstract_surface->device == reference_surface->base.device) { + cairo_cogl_surface_t *surface = (cairo_cogl_surface_t *)abstract_surface; + _cairo_cogl_surface_flush (surface); + return surface->texture ? cogl_object_ref (surface->texture) : NULL; + } + + if (abstract_surface->type == CAIRO_SURFACE_TYPE_COGL) { + if (_cairo_surface_is_subsurface (abstract_surface)) { + cairo_cogl_surface_t *surface; + + surface = (cairo_cogl_surface_t *) + _cairo_surface_subsurface_get_target (abstract_surface); + if (surface->base.device == reference_surface->base.device) + return surface->texture ? cogl_object_ref (surface->texture) : NULL; + } + } + + clone = _cairo_surface_has_snapshot (abstract_surface, &_cairo_cogl_surface_backend); + if (clone) { + cairo_cogl_surface_t *surface = (cairo_cogl_surface_t *)clone; + return surface->texture ? cogl_object_ref (surface->texture) : NULL; + } + + g_warning ("Uploading image surface to texture"); + + if (_cairo_surface_is_image (abstract_surface)) { + image = (cairo_image_surface_t *)abstract_surface; + } else { + cairo_status_t status = _cairo_surface_acquire_source_image (abstract_surface, + &acquired_image, &image_extra); + if (unlikely (status)) { + g_warning ("acquire_source_image failed: %s [%d]\n", + cairo_status_to_string (status), status); + return NULL; + } + image = acquired_image; + } + + format = get_cogl_format_from_cairo_format (image->format); + if (!format) + { + image_clone = _cairo_image_surface_coerce (image); + if (unlikely (image_clone->base.status)) { + g_warning ("image_surface_coerce failed"); + texture = NULL; + goto BAIL; + } + + format = get_cogl_format_from_cairo_format (image_clone->format); + assert (format); + } + + texture = cogl_texture_2d_new_from_data (to_device(reference_surface->base.device)->cogl_context, + image->width, + image->height, + format, /* incoming */ + format, /* desired */ + image->stride, + image->data, + &error); + if (!texture) { + g_warning ("Failed to allocate texture: %s", error->message); + g_error_free (error); + goto BAIL; + } + + clone = _cairo_cogl_surface_create_full (to_device(reference_surface->base.device), + reference_surface->ignore_alpha, + NULL, COGL_TEXTURE (texture)); + + _cairo_surface_attach_snapshot (abstract_surface, clone, NULL); + + /* Attaching the snapshot will take a reference on the clone surface... */ + cairo_surface_destroy (clone); + +BAIL: + if (image_clone) + cairo_surface_destroy (&image_clone->base); + if (acquired_image) + _cairo_surface_release_source_image (abstract_surface, acquired_image, image_extra); + + return COGL_TEXTURE (texture); +} + +/* NB: a reference for the texture is transferred to the caller which should + * be unrefed */ +static CoglTexture * +_cairo_cogl_acquire_pattern_texture (const cairo_pattern_t *pattern, + cairo_cogl_surface_t *destination, + const cairo_rectangle_int_t *extents, + const cairo_rectangle_int_t *sample, + cairo_cogl_texture_attributes_t *attributes) +{ + CoglTexture *texture = NULL; + + switch ((int)pattern->type) + { + case CAIRO_PATTERN_TYPE_SURFACE: { + cairo_surface_t *surface = ((cairo_surface_pattern_t *)pattern)->surface; + texture = _cairo_cogl_acquire_surface_texture (destination, surface); + if (!texture) + return NULL; + + /* XXX: determine if it would have no effect to change the + * extend mode to EXTEND_PAD instead since we can simply map + * EXTEND_PAD to CLAMP_TO_EDGE without needing fragment shader + * tricks or extra border texels. */ +#if 0 + /* TODO: We still need to consider HW such as SGX which doesn't have + * full support for NPOT textures. */ + if (pattern->extend == CAIRO_EXTEND_REPEAT || pattern->extend == CAIRO_EXTEND_REFLECT) { + _cairo_cogl_get_pot_texture (); + } +#endif + + cairo_matrix_init_identity (&attributes->matrix); + + /* Convert from un-normalized source coordinates in backend + * coordinates to normalized texture coordinates */ + cairo_matrix_scale (&attributes->matrix, + 1.0f / cogl_texture_get_width (texture), + 1.0f / cogl_texture_get_height (texture)); + + /* XXX: need to multiply in the pattern->matrix */ + + attributes->extend = pattern->extend; + attributes->filter = CAIRO_FILTER_BILINEAR; + attributes->has_component_alpha = pattern->has_component_alpha; + + attributes->s_wrap = get_cogl_wrap_mode_for_extend (pattern->extend); + attributes->t_wrap = attributes->s_wrap; + + return texture; + } + case CAIRO_PATTERN_TYPE_RADIAL: + case CAIRO_PATTERN_TYPE_MESH: { + cairo_surface_t *surface; + cairo_matrix_t texture_matrix; + + surface = cairo_image_surface_create (CAIRO_FORMAT_ARGB32, + extents->width, extents->height); + if (_cairo_surface_offset_paint (surface, + extents->x, extents->y, + CAIRO_OPERATOR_SOURCE, + pattern, NULL)) { + cairo_surface_destroy (surface); + return NULL; + } + + texture = _cairo_cogl_acquire_surface_texture (destination, surface); + if (!texture) + goto BAIL; + + cairo_matrix_init_identity (&texture_matrix); + + /* Convert from un-normalized source coordinates in backend + * coordinates to normalized texture coordinates */ + cairo_matrix_scale (&texture_matrix, + 1.0f / cogl_texture_get_width (texture), + 1.0f / cogl_texture_get_height (texture)); + + cairo_matrix_translate (&texture_matrix, -extents->x, -extents->y); + + attributes->matrix = texture_matrix; + attributes->extend = pattern->extend; + attributes->filter = CAIRO_FILTER_NEAREST; + attributes->has_component_alpha = pattern->has_component_alpha; + + /* any pattern extend modes have already been dealt with... */ + attributes->s_wrap = COGL_PIPELINE_WRAP_MODE_CLAMP_TO_EDGE; + attributes->t_wrap = attributes->s_wrap; + +BAIL: + cairo_surface_destroy (surface); + + return texture; + } + case CAIRO_PATTERN_TYPE_LINEAR: { + cairo_linear_pattern_t *linear_pattern = (cairo_linear_pattern_t *)pattern; + cairo_cogl_linear_gradient_t *gradient; + cairo_cogl_linear_texture_entry_t *linear_texture; + cairo_int_status_t status; + float a, b; + float dist; + float scale; + float angle; + + status = _cairo_cogl_get_linear_gradient (to_device(destination->base.device), + pattern->extend, + linear_pattern->base.n_stops, + linear_pattern->base.stops, + &gradient); + if (unlikely (status)) + return NULL; + + linear_texture = _cairo_cogl_linear_gradient_texture_for_extend (gradient, pattern->extend); + + attributes->extend = pattern->extend; + attributes->filter = CAIRO_FILTER_BILINEAR; + attributes->has_component_alpha = pattern->has_component_alpha; + attributes->s_wrap = get_cogl_wrap_mode_for_extend (pattern->extend); + attributes->t_wrap = COGL_PIPELINE_WRAP_MODE_REPEAT; + + cairo_matrix_init_identity (&attributes->matrix); + + a = linear_pattern->pd2.x - linear_pattern->pd1.x; + b = linear_pattern->pd2.y - linear_pattern->pd1.y; + dist = sqrtf (a*a + b*b); + scale = 1.0f / dist; + angle = - atan2f (b, a); + + cairo_matrix_rotate (&attributes->matrix, angle); + cairo_matrix_scale (&attributes->matrix, scale, scale); + + cairo_matrix_translate (&attributes->matrix, + -linear_pattern->pd1.x, + -linear_pattern->pd1.y); + + /* XXX: this caught me out: cairo doesn't follow the standard + * maths convention for multiplying two matrices A x B - cairo + * does B x A so the final matrix is as if A's transforms were + * applied first. + */ + cairo_matrix_multiply (&attributes->matrix, + &pattern->matrix, + &attributes->matrix); + + return cogl_object_ref (linear_texture->texture); + } + default: + g_warning ("Un-supported source type"); + return NULL; + } +} + +static void +set_layer_texture_with_attributes (CoglPipeline *pipeline, + int layer_index, + CoglTexture *texture, + cairo_cogl_texture_attributes_t *attributes) +{ + cogl_pipeline_set_layer_texture (pipeline, layer_index, texture); + + if (!_cairo_matrix_is_identity (&attributes->matrix)) { + cairo_matrix_t *m = &attributes->matrix; + float texture_matrixfv[16] = { + m->xx, m->yx, 0, 0, + m->xy, m->yy, 0, 0, + 0, 0, 1, 0, + m->x0, m->y0, 0, 1 + }; + CoglMatrix texture_matrix; + cogl_matrix_init_from_array (&texture_matrix, texture_matrixfv); + cogl_pipeline_set_layer_matrix (pipeline, layer_index, &texture_matrix); + } + + if (attributes->s_wrap != attributes->t_wrap) { + cogl_pipeline_set_layer_wrap_mode_s (pipeline, layer_index, attributes->s_wrap); + cogl_pipeline_set_layer_wrap_mode_t (pipeline, layer_index, attributes->t_wrap); + } else + cogl_pipeline_set_layer_wrap_mode (pipeline, layer_index, attributes->s_wrap); +} + +static CoglPipeline * +get_source_mask_operator_destination_pipeline (const cairo_pattern_t *mask, + const cairo_pattern_t *source, + cairo_operator_t op, + cairo_cogl_surface_t *destination, + cairo_composite_rectangles_t *extents) +{ + cairo_cogl_template_type template_type; + CoglPipeline *pipeline; + + switch ((int)source->type) + { + case CAIRO_PATTERN_TYPE_SOLID: + template_type = mask ? + CAIRO_COGL_TEMPLATE_TYPE_MASK_SOLID : CAIRO_COGL_TEMPLATE_TYPE_SOLID; + break; + case CAIRO_PATTERN_TYPE_SURFACE: + case CAIRO_PATTERN_TYPE_LINEAR: + case CAIRO_PATTERN_TYPE_RADIAL: + case CAIRO_PATTERN_TYPE_MESH: + template_type = mask ? + CAIRO_COGL_TEMPLATE_TYPE_MASK_TEXTURE : CAIRO_COGL_TEMPLATE_TYPE_TEXTURE; + break; + default: + g_warning ("Un-supported source type"); + return NULL; + } + + pipeline = cogl_pipeline_copy (to_device(destination->base.device)->template_pipelines[op][template_type]); + + if (source->type == CAIRO_PATTERN_TYPE_SOLID) { + cairo_solid_pattern_t *solid_pattern = (cairo_solid_pattern_t *)source; + cogl_pipeline_set_color4f (pipeline, + solid_pattern->color.red * solid_pattern->color.alpha, + solid_pattern->color.green * solid_pattern->color.alpha, + solid_pattern->color.blue * solid_pattern->color.alpha, + solid_pattern->color.alpha); + } else { + cairo_cogl_texture_attributes_t attributes; + CoglTexture *texture = + _cairo_cogl_acquire_pattern_texture (source, destination, + &extents->bounded, + &extents->source_sample_area, + &attributes); + if (!texture) + goto BAIL; + set_layer_texture_with_attributes (pipeline, 0, texture, &attributes); + cogl_object_unref (texture); + } + + if (mask) { + if (mask->type == CAIRO_PATTERN_TYPE_SOLID) { + cairo_solid_pattern_t *solid_pattern = (cairo_solid_pattern_t *)mask; + CoglColor color; + cogl_color_init_from_4f (&color, + solid_pattern->color.red * solid_pattern->color.alpha, + solid_pattern->color.green * solid_pattern->color.alpha, + solid_pattern->color.blue * solid_pattern->color.alpha, + solid_pattern->color.alpha); + cogl_pipeline_set_layer_combine_constant (pipeline, 1, &color); + } else { + cairo_cogl_texture_attributes_t attributes; + CoglTexture *texture = + _cairo_cogl_acquire_pattern_texture (mask, destination, + &extents->bounded, + &extents->mask_sample_area, + &attributes); + if (!texture) + goto BAIL; + set_layer_texture_with_attributes (pipeline, 1, texture, &attributes); + cogl_object_unref (texture); + } + } + + return pipeline; + +BAIL: + cogl_object_unref (pipeline); + return NULL; +} + +#if 0 +CoglPrimitive * +_cairo_cogl_rectangle_new_p2t2t2 (float x, + float y, + float width, + float height) +{ + CoglVertexP2 vertices[] = { + {x, y}, {x, y + height}, {x + width, y + height}, + {x, y}, {x + width, y + height}, {x + width, y} + }; + CoglAttributeBuffer *buffer = cogl_attribute_buffer_new (sizeof (vertices)); + CoglAttribute *pos = cogl_attribute_new (buffer, + "cogl_position_in", + sizeof (CoglVertexP2), + 0, + 2, + COGL_ATTRIBUTE_TYPE_FLOAT); + CoglAttribute *tex_coords0 = cogl_attribute_new (buffer, + "cogl_tex_coord0_in", + sizeof (CoglVertexP2), + 0, + 2, + COGL_ATTRIBUTE_TYPE_FLOAT); + CoglAttribute *tex_coords0 = cogl_attribute_new (buffer, + "cogl_tex_coord0_in", + sizeof (CoglVertexP2), + 0, + 2, + COGL_ATTRIBUTE_TYPE_FLOAT); + CoglPrimitive *prim; + + cogl_buffer_set_data (COGL_BUFFER (buffer), 0, vertices, sizeof (vertices)); + + /* The attributes will now keep the buffer alive... */ + cogl_object_unref (buffer); + + prim = cogl_primitive_new (COGL_VERTICES_MODE_TRIANGLES, + 6, pos, tex_coords, NULL); + + /* The primitive will now keep the attribute alive... */ + cogl_object_unref (pos); + + return prim; +} +#endif + +static void +_cairo_cogl_log_clip (cairo_cogl_surface_t *surface, + const cairo_clip_t *clip) +{ + if (!_cairo_clip_equal (clip, surface->last_clip)) { + _cairo_cogl_journal_log_clip (surface, clip); + _cairo_clip_destroy (surface->last_clip); + surface->last_clip = _cairo_clip_copy (clip); + } +} + +static void +_cairo_cogl_maybe_log_clip (cairo_cogl_surface_t *surface, + cairo_composite_rectangles_t *composite) +{ + cairo_clip_t *clip = composite->clip; + + if (_cairo_composite_rectangles_can_reduce_clip (composite, clip)) + clip = NULL; + + if (clip == NULL) { + if (_cairo_composite_rectangles_can_reduce_clip (composite, + surface->last_clip)) + return; + } + + _cairo_cogl_log_clip (surface, clip); +} + +static cairo_bool_t +is_operator_supported (cairo_operator_t op) +{ + switch ((int)op) { + case CAIRO_OPERATOR_SOURCE: + case CAIRO_OPERATOR_OVER: + case CAIRO_OPERATOR_IN: + case CAIRO_OPERATOR_DEST_OVER: + case CAIRO_OPERATOR_DEST_IN: + case CAIRO_OPERATOR_ADD: + return TRUE; + + default: + return FALSE; + } +} + +static cairo_int_status_t +_cairo_cogl_surface_mask (void *abstract_surface, + cairo_operator_t op, + const cairo_pattern_t *source, + const cairo_pattern_t *mask, + const cairo_clip_t *clip) +{ + cairo_cogl_surface_t *surface = abstract_surface; + cairo_composite_rectangles_t extents; + cairo_status_t status; + CoglPipeline *pipeline; + cairo_matrix_t identity; + + /* XXX: Use this to smoke test the acquire_source/dest_image fallback + * paths... */ + //return CAIRO_INT_STATUS_UNSUPPORTED; + + if (!is_operator_supported (op)) + return CAIRO_INT_STATUS_UNSUPPORTED; + + status = _cairo_composite_rectangles_init_for_mask (&extents, + &surface->base, + op, source, mask, clip); + if (unlikely (status)) + return status; + + pipeline = get_source_mask_operator_destination_pipeline (mask, source, + op, surface, &extents); + if (!pipeline){ + status = CAIRO_INT_STATUS_UNSUPPORTED; + goto BAIL; + } + + _cairo_cogl_maybe_log_clip (surface, &extents); + + cairo_matrix_init_identity (&identity); + _cairo_cogl_journal_log_rectangle (surface, pipeline, + extents.bounded.x, + extents.bounded.y, + extents.bounded.width, + extents.bounded.height, + 2, + &identity); + + /* The journal will take a reference on the pipeline and clip_path... */ + cogl_object_unref (pipeline); + +BAIL: + return status; +} + +static int +_cairo_cogl_source_n_layers (const cairo_pattern_t *source) +{ + switch ((int)source->type) + { + case CAIRO_PATTERN_TYPE_SOLID: + return 0; + case CAIRO_PATTERN_TYPE_LINEAR: + case CAIRO_PATTERN_TYPE_RADIAL: + case CAIRO_PATTERN_TYPE_MESH: + case CAIRO_PATTERN_TYPE_SURFACE: + return 1; + default: + g_warning ("Unsupported source type"); + return 0; + } +} + +static cairo_bool_t +_cairo_cogl_path_fill_meta_equal (const void *key_a, const void *key_b) +{ + const cairo_cogl_path_fill_meta_t *meta0 = key_a; + const cairo_cogl_path_fill_meta_t *meta1 = key_b; + + return _cairo_path_fixed_equal (meta0->user_path, meta1->user_path); +} + +static cairo_bool_t +_cairo_cogl_stroke_style_equal (const cairo_stroke_style_t *a, + const cairo_stroke_style_t *b) +{ + if (a->line_width == b->line_width && + a->line_cap == b->line_cap && + a->line_join == b->line_join && + a->miter_limit == b->miter_limit && + a->num_dashes == b->num_dashes && + a->dash_offset == b->dash_offset) + { + unsigned int i; + for (i = 0; i < a->num_dashes; i++) { + if (a->dash[i] != b->dash[i]) + return FALSE; + } + } + return TRUE; +} + +static cairo_bool_t +_cairo_cogl_path_stroke_meta_equal (const void *key_a, const void *key_b) +{ + const cairo_cogl_path_stroke_meta_t *meta0 = key_a; + const cairo_cogl_path_stroke_meta_t *meta1 = key_b; + + return _cairo_cogl_stroke_style_equal (&meta0->style, &meta1->style) && + _cairo_path_fixed_equal (meta0->user_path, meta1->user_path); +} + +static cairo_cogl_path_stroke_meta_t * +_cairo_cogl_path_stroke_meta_reference (cairo_cogl_path_stroke_meta_t *meta) +{ + assert (CAIRO_REFERENCE_COUNT_HAS_REFERENCE (&meta->ref_count)); + + _cairo_reference_count_inc (&meta->ref_count); + + return meta; +} + +static void +_cairo_cogl_path_stroke_meta_destroy (cairo_cogl_path_stroke_meta_t *meta) +{ + assert (CAIRO_REFERENCE_COUNT_HAS_REFERENCE (&meta->ref_count)); + + if (! _cairo_reference_count_dec_and_test (&meta->ref_count)) + return; + + _cairo_path_fixed_fini (meta->user_path); + free (meta->user_path); + + _cairo_stroke_style_fini (&meta->style); + + if (meta->prim) + cogl_object_unref (meta->prim); + + free (meta); +} + +static cairo_cogl_path_stroke_meta_t * +_cairo_cogl_path_stroke_meta_lookup (cairo_cogl_device_t *ctx, + unsigned long hash, + cairo_path_fixed_t *user_path, + const cairo_stroke_style_t *style, + double tolerance) +{ + cairo_cogl_path_stroke_meta_t *ret; + cairo_cogl_path_stroke_meta_t lookup; + + lookup.cache_entry.hash = hash; + lookup.user_path = user_path; + lookup.style = *style; + lookup.tolerance = tolerance; + + ret = _cairo_cache_lookup (&ctx->path_stroke_staging_cache, &lookup.cache_entry); + if (!ret) + ret = _cairo_cache_lookup (&ctx->path_stroke_prim_cache, &lookup.cache_entry); + return ret; +} + +static void +_cairo_cogl_path_stroke_meta_set_prim_size (cairo_cogl_surface_t *surface, + cairo_cogl_path_stroke_meta_t *meta, + size_t size) +{ + /* now that we know the meta structure is associated with a primitive + * we promote it from the staging cache into the primitive cache. + */ + + /* XXX: _cairo_cache borks if you try and remove an entry that's already + * been evicted so we explicitly look it up first... */ + if (_cairo_cache_lookup (&to_device(surface->base.device)->path_stroke_staging_cache, &meta->cache_entry)) { + _cairo_cogl_path_stroke_meta_reference (meta); + _cairo_cache_remove (&to_device(surface->base.device)->path_stroke_staging_cache, &meta->cache_entry); + } + + meta->cache_entry.size = size; + if (_cairo_cache_insert (&to_device(surface->base.device)->path_stroke_prim_cache, &meta->cache_entry) != + CAIRO_STATUS_SUCCESS) + _cairo_cogl_path_stroke_meta_destroy (meta); +} + +static unsigned int +_cairo_cogl_stroke_style_hash (unsigned int hash, + const cairo_stroke_style_t *style) +{ + unsigned int i; + hash = _cairo_hash_bytes (hash, &style->line_width, sizeof (style->line_width)); + hash = _cairo_hash_bytes (hash, &style->line_cap, sizeof (style->line_cap)); + hash = _cairo_hash_bytes (hash, &style->line_join, sizeof (style->line_join)); + hash = _cairo_hash_bytes (hash, &style->miter_limit, sizeof (style->miter_limit)); + hash = _cairo_hash_bytes (hash, &style->num_dashes, sizeof (style->num_dashes)); + hash = _cairo_hash_bytes (hash, &style->dash_offset, sizeof (style->dash_offset)); + for (i = 0; i < style->num_dashes; i++) + hash = _cairo_hash_bytes (hash, &style->dash[i], sizeof (double)); + return hash; +} + +static cairo_cogl_path_stroke_meta_t * +_cairo_cogl_get_path_stroke_meta (cairo_cogl_surface_t *surface, + const cairo_stroke_style_t *style, + double tolerance) +{ + unsigned long hash; + cairo_cogl_path_stroke_meta_t *meta = NULL; + cairo_path_fixed_t *meta_path = NULL; + cairo_status_t status; + + if (!surface->user_path) + return NULL; + + hash = _cairo_path_fixed_hash (surface->user_path); + hash = _cairo_cogl_stroke_style_hash (hash, style); + hash = _cairo_hash_bytes (hash, &tolerance, sizeof (tolerance)); + + meta = _cairo_cogl_path_stroke_meta_lookup (to_device(surface->base.device), hash, + surface->user_path, style, tolerance); + if (meta) + return meta; + + meta = calloc (1, sizeof (cairo_cogl_path_stroke_meta_t)); + if (!meta) + goto BAIL; + CAIRO_REFERENCE_COUNT_INIT (&meta->ref_count, 1); + meta->cache_entry.hash = hash; + meta->counter = 0; + meta_path = malloc (sizeof (cairo_path_fixed_t)); + if (!meta_path) + goto BAIL; + /* FIXME: we should add a ref-counted wrapper for our user_paths + * so we don't have to keep copying them here! */ + status = _cairo_path_fixed_init_copy (meta_path, surface->user_path); + if (unlikely (status)) + goto BAIL; + meta->user_path = meta_path; + meta->ctm_inverse = *surface->ctm_inverse; + + status = _cairo_stroke_style_init_copy (&meta->style, style); + if (unlikely (status)) { + _cairo_path_fixed_fini (meta_path); + goto BAIL; + } + meta->tolerance = tolerance; + + return meta; + +BAIL: + if (meta_path) + free (meta_path); + if (meta) + free (meta); + return NULL; +} + +static cairo_int_status_t +_cairo_cogl_stroke_to_primitive (cairo_cogl_surface_t *surface, + 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, + int n_layers, + cairo_bool_t one_shot, + CoglPrimitive **primitive, + size_t *size) +{ + cairo_traps_t traps; + cairo_int_status_t status; + + _cairo_traps_init (&traps); + + status = _cairo_path_fixed_stroke_to_traps (path, style, ctm, ctm_inverse, tolerance, + &traps); + if (unlikely (status)) + goto BAIL; + + if (traps.num_traps == 0) { + status = CAIRO_INT_STATUS_NOTHING_TO_DO; + goto BAIL; + } + + *size = traps.num_traps * sizeof (CoglVertexP2) * 6; + + //g_print ("new stroke prim\n"); + *primitive = _cairo_cogl_traps_to_composite_prim (surface, &traps, n_layers, one_shot); + if (!*primitive) { + status = CAIRO_INT_STATUS_NO_MEMORY; + goto BAIL; + } + +BAIL: + _cairo_traps_fini (&traps); + return status; +} + +static cairo_int_status_t +_cairo_cogl_surface_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_cogl_surface_t *surface = (cairo_cogl_surface_t *)abstract_surface; + cairo_composite_rectangles_t extents; + CoglPipeline *pipeline; + cairo_status_t status; +#ifdef ENABLE_PATH_CACHE + cairo_cogl_path_stroke_meta_t *meta = NULL; + cairo_matrix_t transform_matrix; +#endif + cairo_matrix_t *transform = NULL; + gboolean one_shot = TRUE; + CoglPrimitive *prim = NULL; + cairo_bool_t new_prim = FALSE; + + if (! is_operator_supported (op)) + return CAIRO_INT_STATUS_UNSUPPORTED; + + /* FIXME - support unbounded operators */ + if (!_cairo_operator_bounded_by_mask (op)) { + /* Currently IN this is the only unbounded operator we aim to support + * in cairo-cogl. */ + assert (op == CAIRO_OPERATOR_IN); + g_warning ("FIXME: handle stroking with unbounded operators!"); + return CAIRO_INT_STATUS_UNSUPPORTED; + } + + status = _cairo_composite_rectangles_init_for_stroke (&extents, + &surface->base, + op, source, path, + style, + ctm, + clip); + if (unlikely (status)) + return status; + +#ifdef ENABLE_PATH_CACHE + /* FIXME: we are currently leaking the meta state if we don't reach + * the cache_insert at the end. */ + meta = _cairo_cogl_get_path_stroke_meta (surface, style, tolerance); + if (meta) { + prim = meta->prim; + if (prim) { + cairo_matrix_multiply (&transform_matrix, &meta->ctm_inverse, surface->ctm); + transform = &transform_matrix; + } else if (meta->counter++ > 10) + one_shot = FALSE; + } +#endif + + if (!prim) { + int n_layers = _cairo_cogl_source_n_layers (source); + size_t prim_size = 0; + status = _cairo_cogl_stroke_to_primitive (surface, path, style, + ctm, ctm_inverse, tolerance, + n_layers, one_shot, + &prim, &prim_size); + if (unlikely (status)) + return status; + new_prim = TRUE; +#if defined (ENABLE_PATH_CACHE) + if (meta) { + meta->prim = cogl_object_ref (prim); + _cairo_cogl_path_stroke_meta_set_prim_size (surface, meta, prim_size); + } +#endif + } + + pipeline = get_source_mask_operator_destination_pipeline (NULL, source, + op, surface, &extents); + if (!pipeline) + return CAIRO_INT_STATUS_UNSUPPORTED; + + _cairo_cogl_maybe_log_clip (surface, &extents); + + _cairo_cogl_journal_log_primitive (surface, pipeline, prim, transform); + + /* The journal will take a reference on the pipeline and primitive... */ + cogl_object_unref (pipeline); + if (new_prim) + cogl_object_unref (prim); + + return CAIRO_INT_STATUS_SUCCESS; +} + +static cairo_cogl_path_fill_meta_t * +_cairo_cogl_path_fill_meta_reference (cairo_cogl_path_fill_meta_t *meta) +{ + assert (CAIRO_REFERENCE_COUNT_HAS_REFERENCE (&meta->ref_count)); + + _cairo_reference_count_inc (&meta->ref_count); + + return meta; +} + +static void +_cairo_cogl_path_fill_meta_destroy (cairo_cogl_path_fill_meta_t *meta) +{ + assert (CAIRO_REFERENCE_COUNT_HAS_REFERENCE (&meta->ref_count)); + + if (! _cairo_reference_count_dec_and_test (&meta->ref_count)) + return; + + _cairo_path_fixed_fini (meta->user_path); + free (meta->user_path); + + if (meta->prim) + cogl_object_unref (meta->prim); + + free (meta); +} + +static cairo_cogl_path_fill_meta_t * +_cairo_cogl_path_fill_meta_lookup (cairo_cogl_device_t *ctx, + unsigned long hash, + cairo_path_fixed_t *user_path) +{ + cairo_cogl_path_fill_meta_t *ret; + cairo_cogl_path_fill_meta_t lookup; + + lookup.cache_entry.hash = hash; + lookup.user_path = user_path; + + ret = _cairo_cache_lookup (&ctx->path_fill_staging_cache, &lookup.cache_entry); + if (!ret) + ret = _cairo_cache_lookup (&ctx->path_fill_prim_cache, &lookup.cache_entry); + return ret; +} + +static void +_cairo_cogl_path_fill_meta_set_prim_size (cairo_cogl_surface_t *surface, + cairo_cogl_path_fill_meta_t *meta, + size_t size) +{ + /* now that we know the meta structure is associated with a primitive + * we promote it from the staging cache into the primitive cache. + */ + + /* XXX: _cairo_cache borks if you try and remove an entry that's already + * been evicted so we explicitly look it up first... */ + if (_cairo_cache_lookup (&to_device(surface->base.device)->path_fill_staging_cache, &meta->cache_entry)) { + _cairo_cogl_path_fill_meta_reference (meta); + _cairo_cache_remove (&to_device(surface->base.device)->path_fill_staging_cache, &meta->cache_entry); + } + + meta->cache_entry.size = size; + if (_cairo_cache_insert (&to_device(surface->base.device)->path_fill_prim_cache, &meta->cache_entry) != + CAIRO_STATUS_SUCCESS) + _cairo_cogl_path_fill_meta_destroy (meta); +} + +static cairo_cogl_path_fill_meta_t * +_cairo_cogl_get_path_fill_meta (cairo_cogl_surface_t *surface) +{ + unsigned long hash; + cairo_cogl_path_fill_meta_t *meta = NULL; + cairo_path_fixed_t *meta_path = NULL; + cairo_status_t status; + + if (!surface->user_path) + return NULL; + + hash = _cairo_path_fixed_hash (surface->user_path); + + meta = _cairo_cogl_path_fill_meta_lookup (to_device(surface->base.device), + hash, surface->user_path); + if (meta) + return meta; + + meta = calloc (1, sizeof (cairo_cogl_path_fill_meta_t)); + if (!meta) + goto BAIL; + meta->cache_entry.hash = hash; + meta->counter = 0; + CAIRO_REFERENCE_COUNT_INIT (&meta->ref_count, 1); + meta_path = malloc (sizeof (cairo_path_fixed_t)); + if (!meta_path) + goto BAIL; + /* FIXME: we should add a ref-counted wrapper for our user_paths + * so we don't have to keep copying them here! */ + status = _cairo_path_fixed_init_copy (meta_path, surface->user_path); + if (unlikely (status)) + goto BAIL; + meta->user_path = meta_path; + meta->ctm_inverse = *surface->ctm_inverse; + + /* To start with - until we associate a CoglPrimitive with the meta + * structure - we keep the meta in a staging structure until we + * see whether it actually gets re-used. */ + meta->cache_entry.size = 1; + if (_cairo_cache_insert (&to_device(surface->base.device)->path_fill_staging_cache, &meta->cache_entry) != + CAIRO_STATUS_SUCCESS) + _cairo_cogl_path_fill_meta_destroy (meta); + + return meta; + +BAIL: + if (meta_path) + free (meta_path); + if (meta) + free (meta); + return NULL; +} + +static cairo_int_status_t +_cairo_cogl_surface_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_cogl_surface_t *surface = abstract_surface; + cairo_composite_rectangles_t extents; + cairo_status_t status; +#ifdef ENABLE_PATH_CACHE + cairo_cogl_path_fill_meta_t *meta = NULL; + cairo_matrix_t transform_matrix; +#endif + cairo_matrix_t *transform = NULL; + cairo_bool_t one_shot = TRUE; + CoglPrimitive *prim = NULL; + cairo_bool_t new_prim = FALSE; + CoglPipeline *pipeline; + + if (! is_operator_supported (op)) + return CAIRO_INT_STATUS_UNSUPPORTED; + + /* FIXME - support unbounded operators */ + if (!_cairo_operator_bounded_by_mask (op)) { + /* Currently IN this is the only unbounded operator we aim to support + * in cairo-cogl. */ + assert (op == CAIRO_OPERATOR_IN); + g_warning ("FIXME: handle filling with unbounded operators!"); + return CAIRO_INT_STATUS_UNSUPPORTED; + } + + status = _cairo_composite_rectangles_init_for_fill (&extents, + &surface->base, + op, source, path, + clip); + if (unlikely (status)) + return status; + +#ifndef FILL_WITH_COGL_PATH +#ifdef ENABLE_PATH_CACHE + meta = _cairo_cogl_get_path_fill_meta (surface); + if (meta) { + prim = meta->prim; + if (prim) { + cairo_matrix_multiply (&transform_matrix, &meta->ctm_inverse, surface->ctm); + transform = &transform_matrix; + } else if (meta->counter++ > 10) + one_shot = FALSE; + } +#endif /* ENABLE_PATH_CACHE */ + + if (!prim) { + int n_layers = _cairo_cogl_source_n_layers (source); + size_t prim_size; + status = _cairo_cogl_fill_to_primitive (surface, path, fill_rule, tolerance, + one_shot, n_layers, &prim, &prim_size); + if (unlikely (status)) + return status; + new_prim = TRUE; +#ifdef ENABLE_PATH_CACHE + if (meta) { + meta->prim = cogl_object_ref (prim); + _cairo_cogl_path_fill_meta_set_prim_size (surface, meta, prim_size); + } +#endif /* ENABLE_PATH_CACHE */ + } + +#endif /* !FILL_WITH_COGL_PATH */ + + pipeline = get_source_mask_operator_destination_pipeline (NULL, source, + op, surface, &extents); + if (!pipeline) + return CAIRO_INT_STATUS_UNSUPPORTED; + + _cairo_cogl_maybe_log_clip (surface, &extents); + +#ifndef FILL_WITH_COGL_PATH + _cairo_cogl_journal_log_primitive (surface, pipeline, prim, transform); + /* The journal will take a reference on the prim */ + if (new_prim) + cogl_object_unref (prim); +#else + CoglPath * cogl_path = _cairo_cogl_util_path_from_cairo (path, fill_rule, tolerance); + _cairo_cogl_journal_log_path (surface, pipeline, cogl_path); + cogl_object_unref (cogl_path); +#endif + + /* The journal will take a reference on the pipeline... */ + cogl_object_unref (pipeline); + + return CAIRO_INT_STATUS_SUCCESS; +} + +cairo_int_status_t +_cairo_cogl_surface_fill_rectangle (void *abstract_surface, + cairo_operator_t op, + const cairo_pattern_t *source, + double x, + double y, + double width, + double height, + cairo_matrix_t *ctm, + const cairo_clip_t *clip) +{ + cairo_cogl_surface_t *surface = abstract_surface; + CoglPipeline *pipeline; + + if (! is_operator_supported (op)) + return CAIRO_INT_STATUS_UNSUPPORTED; + + /* FIXME - support unbounded operators */ + if (!_cairo_operator_bounded_by_mask (op)) { + /* Currently IN this is the only unbounded operator we aim to support + * in cairo-cogl. */ + assert (op == CAIRO_OPERATOR_IN); + g_warning ("FIXME: handle filling with unbounded operators!"); + return CAIRO_INT_STATUS_UNSUPPORTED; + } + + /* FIXME */ +#if 0 + status = _cairo_composite_rectangles_init_for_fill_rectangle (&extents, + &surface->base, + op, source, path, + clip); + if (unlikely (status)) + return status; +#endif + + if (source->type == CAIRO_PATTERN_TYPE_SOLID) { + double x1 = x; + double y1 = y; + double x2 = x1 + width; + double y2 = y1 + height; + + pipeline = get_source_mask_operator_destination_pipeline (NULL, source, + op, surface, NULL); + if (!pipeline) + return CAIRO_INT_STATUS_UNSUPPORTED; + + _cairo_cogl_log_clip (surface, clip); + + _cairo_cogl_journal_log_rectangle (surface, + pipeline, + x1, y1, x2, y2, + 0, + ctm); + return CAIRO_INT_STATUS_SUCCESS; + } else + return CAIRO_INT_STATUS_UNSUPPORTED; + + /* TODO: + * We need to aquire the textures here, look at the corresponding + * attributes and see if this can be trivially handled by logging + * a textured rectangle only needing simple scaling or translation + * of texture coordinates. + * + * At this point we should also aim to remap the default + * EXTEND_NONE mode to EXTEND_PAD which is more efficient if we + * know it makes no difference either way since we can map that to + * CLAMP_TO_EDGE. + */ +} + +static cairo_int_status_t +_cairo_cogl_surface_show_glyphs (void *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) +{ + return CAIRO_INT_STATUS_UNSUPPORTED; +} + +const cairo_surface_backend_t _cairo_cogl_surface_backend = { + CAIRO_SURFACE_TYPE_COGL, + _cairo_cogl_surface_finish, +#ifdef NEED_COGL_CONTEXT + _cairo_cogl_context_create, +#else + _cairo_default_context_create, +#endif + + _cairo_cogl_surface_create_similar, + NULL, /* create similar image */ + NULL, /* map to image */ + NULL, /* unmap image */ + + _cairo_cogl_surface_acquire_source_image, + _cairo_cogl_surface_release_source_image, + NULL, /* snapshot */ + + NULL, /* copy_page */ + NULL, /* show_page */ + + _cairo_cogl_surface_get_extents, + NULL, /* get_font_options */ + + _cairo_cogl_surface_flush, /* flush */ + NULL, /* mark_dirty_rectangle */ + + _cairo_cogl_surface_paint, + _cairo_cogl_surface_mask, + _cairo_cogl_surface_stroke, + _cairo_cogl_surface_fill, + NULL, /* fill_stroke*/ + _cairo_surface_fallback_glyphs, +}; + +static cairo_surface_t * +_cairo_cogl_surface_create_full (cairo_cogl_device_t *dev, + cairo_bool_t ignore_alpha, + CoglFramebuffer *framebuffer, + CoglTexture *texture) +{ + cairo_cogl_surface_t *surface; + cairo_status_t status; + + status = cairo_device_acquire (&dev->base); + if (unlikely (status)) + return _cairo_surface_create_in_error (status); + + surface = malloc (sizeof (cairo_cogl_surface_t)); + if (unlikely (surface == NULL)) + return _cairo_surface_create_in_error (_cairo_error (CAIRO_STATUS_NO_MEMORY)); + + surface->ignore_alpha = ignore_alpha; + + surface->framebuffer = framebuffer; + if (framebuffer) { + surface->width = cogl_framebuffer_get_width (framebuffer); + surface->height = cogl_framebuffer_get_height (framebuffer); + surface->cogl_format = cogl_framebuffer_get_color_format (framebuffer); + cogl_object_ref (framebuffer); + } + + /* FIXME: If texture == NULL and we are given an offscreen framebuffer + * then we want a way to poke inside the framebuffer to get a texture */ + surface->texture = texture; + if (texture) { + if (!framebuffer) { + surface->width = cogl_texture_get_width (texture); + surface->height = cogl_texture_get_height (texture); + surface->cogl_format = cogl_texture_get_format (texture); + } + cogl_object_ref (texture); + } + + assert(surface->width && surface->height); + + surface->journal = NULL; + + surface->buffer_stack = NULL; + surface->buffer_stack_size = 4096; + + surface->last_clip = NULL; + + surface->n_clip_updates_per_frame = 0; + + _cairo_surface_init (&surface->base, + &_cairo_cogl_surface_backend, + &dev->base, + CAIRO_CONTENT_COLOR_ALPHA); + + return &surface->base; +} + +cairo_surface_t * +cairo_cogl_surface_create (cairo_device_t *abstract_device, + CoglFramebuffer *framebuffer) +{ + cairo_cogl_device_t *dev = (cairo_cogl_device_t *)abstract_device; + + if (abstract_device == NULL) + return _cairo_surface_create_in_error (CAIRO_STATUS_DEVICE_ERROR); + + if (abstract_device->status) + return _cairo_surface_create_in_error (abstract_device->status); + + if (abstract_device->backend->type != CAIRO_DEVICE_TYPE_COGL) + return _cairo_surface_create_in_error (_cairo_error (CAIRO_STATUS_SURFACE_TYPE_MISMATCH)); + + return _cairo_cogl_surface_create_full (dev, FALSE, framebuffer, NULL); +} +slim_hidden_def (cairo_cogl_surface_create); + +CoglFramebuffer * +cairo_cogl_surface_get_framebuffer (cairo_surface_t *abstract_surface) +{ + cairo_cogl_surface_t *surface; + + if (abstract_surface->backend != &_cairo_cogl_surface_backend) { + _cairo_error_throw (CAIRO_STATUS_SURFACE_TYPE_MISMATCH); + return NULL; + } + + surface = (cairo_cogl_surface_t *) abstract_surface; + + return surface->framebuffer; +} +slim_hidden_def (cairo_cogl_surface_get_framebuffer); + +CoglTexture * +cairo_cogl_surface_get_texture (cairo_surface_t *abstract_surface) +{ + cairo_cogl_surface_t *surface; + + if (abstract_surface->backend != &_cairo_cogl_surface_backend) { + _cairo_error_throw (CAIRO_STATUS_SURFACE_TYPE_MISMATCH); + return NULL; + } + + surface = (cairo_cogl_surface_t *) abstract_surface; + + return surface->texture; +} +slim_hidden_def (cairo_cogl_surface_get_texture); + +static cairo_status_t +_cairo_cogl_device_flush (void *device) +{ + cairo_status_t status; + + status = cairo_device_acquire (device); + if (unlikely (status)) + return status; + + /* XXX: we don't need to flush Cogl here, we just need to flush + * any batching we do of compositing primitives. */ + + cairo_device_release (device); + + return CAIRO_STATUS_SUCCESS; +} + +static void +_cairo_cogl_device_finish (void *device) +{ + cairo_status_t status; + + status = cairo_device_acquire (device); + if (unlikely (status)) + return; + + /* XXX: Drop references to external resources */ + + cairo_device_release (device); +} + +static void +_cairo_cogl_device_destroy (void *device) +{ + cairo_cogl_device_t *dev = device; + + /* FIXME: Free stuff! */ + + g_free (dev); +} + +static const cairo_device_backend_t _cairo_cogl_device_backend = { + CAIRO_DEVICE_TYPE_COGL, + + NULL, /* lock */ + NULL, /* unlock */ + + _cairo_cogl_device_flush, + _cairo_cogl_device_finish, + _cairo_cogl_device_destroy, +}; + +static cairo_bool_t +set_blend (CoglPipeline *pipeline, const char *blend_string) +{ + GError *error = NULL; + if (!cogl_pipeline_set_blend (pipeline, blend_string, &error)) { + g_warning ("Unsupported blend string with current gpu/driver: %s", blend_string); + g_error_free (error); + return FALSE; + } + return TRUE; +} + +static cairo_bool_t +_cairo_cogl_setup_op_state (CoglPipeline *pipeline, cairo_operator_t op) +{ + cairo_bool_t status = FALSE; + + switch ((int)op) + { + case CAIRO_OPERATOR_SOURCE: + status = set_blend (pipeline, "RGBA = ADD (SRC_COLOR, 0)"); + break; + case CAIRO_OPERATOR_OVER: + status = set_blend (pipeline, "RGBA = ADD (SRC_COLOR, DST_COLOR * (1 - SRC_COLOR[A]))"); + break; + case CAIRO_OPERATOR_IN: + status = set_blend (pipeline, "RGBA = ADD (SRC_COLOR * DST_COLOR[A], 0)"); + break; + case CAIRO_OPERATOR_DEST_OVER: + status = set_blend (pipeline, "RGBA = ADD (SRC_COLOR * (1 - DST_COLOR[A]), DST_COLOR)"); + break; + case CAIRO_OPERATOR_DEST_IN: + status = set_blend (pipeline, "RGBA = ADD (0, DST_COLOR * SRC_COLOR[A])"); + break; + case CAIRO_OPERATOR_ADD: + status = set_blend (pipeline, "RGBA = ADD (SRC_COLOR, DST_COLOR)"); + break; + } + + return status; +} + +static void +create_templates_for_op (cairo_cogl_device_t *dev, cairo_operator_t op) +{ + CoglPipeline *base = cogl_pipeline_new (); + CoglPipeline *pipeline; + CoglColor color; + + if (!_cairo_cogl_setup_op_state (base, op)) { + cogl_object_unref (base); + return; + } + + dev->template_pipelines[op][CAIRO_COGL_TEMPLATE_TYPE_SOLID] = base; + + pipeline = cogl_pipeline_copy (base); + cogl_pipeline_set_layer_texture (pipeline, 0, dev->dummy_texture); + dev->template_pipelines[op][CAIRO_COGL_TEMPLATE_TYPE_TEXTURE] = pipeline; + + pipeline = cogl_pipeline_copy (base); + cogl_pipeline_set_layer_combine (pipeline, 1, + "RGBA = MODULATE (PREVIOUS, CONSTANT[A])", + NULL); + cogl_pipeline_set_layer_combine_constant (pipeline, 1, &color); + cogl_pipeline_set_layer_texture (pipeline, 1, dev->dummy_texture); + dev->template_pipelines[op][CAIRO_COGL_TEMPLATE_TYPE_MASK_SOLID] = pipeline; + + pipeline = cogl_pipeline_copy (base); + cogl_pipeline_set_layer_combine (pipeline, 1, + "RGBA = MODULATE (PREVIOUS, TEXTURE[A])", + NULL); + cogl_pipeline_set_layer_texture (pipeline, 1, dev->dummy_texture); + dev->template_pipelines[op][CAIRO_COGL_TEMPLATE_TYPE_MASK_TEXTURE] = pipeline; +} + +cairo_device_t * +cairo_cogl_device_create (CoglContext *cogl_context) +{ + cairo_cogl_device_t *dev = g_new0 (cairo_cogl_device_t, 1); + cairo_status_t status; + + dev->backend_vtable_initialized = FALSE; + + dev->cogl_context = cogl_context; + + dev->dummy_texture = cogl_texture_new_with_size (1, 1, + COGL_TEXTURE_NO_SLICING, + COGL_PIXEL_FORMAT_ANY); + if (!dev->dummy_texture) + goto ERROR; + + memset (dev->template_pipelines, 0, sizeof (dev->template_pipelines)); + create_templates_for_op (dev, CAIRO_OPERATOR_SOURCE); + create_templates_for_op (dev, CAIRO_OPERATOR_OVER); + create_templates_for_op (dev, CAIRO_OPERATOR_IN); + create_templates_for_op (dev, CAIRO_OPERATOR_DEST_OVER); + create_templates_for_op (dev, CAIRO_OPERATOR_DEST_IN); + create_templates_for_op (dev, CAIRO_OPERATOR_ADD); + + status = _cairo_cache_init (&dev->linear_cache, + _cairo_cogl_linear_gradient_equal, + NULL, + (cairo_destroy_func_t) _cairo_cogl_linear_gradient_destroy, + CAIRO_COGL_LINEAR_GRADIENT_CACHE_SIZE); + if (unlikely (status)) + return _cairo_device_create_in_error(status); + + status = _cairo_cache_init (&dev->path_fill_staging_cache, + _cairo_cogl_path_fill_meta_equal, + NULL, + (cairo_destroy_func_t) _cairo_cogl_path_fill_meta_destroy, + 1000); + + status = _cairo_cache_init (&dev->path_stroke_staging_cache, + _cairo_cogl_path_stroke_meta_equal, + NULL, + (cairo_destroy_func_t) _cairo_cogl_path_stroke_meta_destroy, + 1000); + + status = _cairo_cache_init (&dev->path_fill_prim_cache, + _cairo_cogl_path_fill_meta_equal, + NULL, + (cairo_destroy_func_t) _cairo_cogl_path_fill_meta_destroy, + CAIRO_COGL_PATH_META_CACHE_SIZE); + + status = _cairo_cache_init (&dev->path_stroke_prim_cache, + _cairo_cogl_path_stroke_meta_equal, + NULL, + (cairo_destroy_func_t) _cairo_cogl_path_stroke_meta_destroy, + CAIRO_COGL_PATH_META_CACHE_SIZE); + + _cairo_device_init (&dev->base, &_cairo_cogl_device_backend); + return &dev->base; + +ERROR: + g_free (dev); + return NULL; +} +slim_hidden_def (cairo_cogl_device_create); + +void +cairo_cogl_surface_end_frame (cairo_surface_t *abstract_surface) +{ + cairo_cogl_surface_t *surface = (cairo_cogl_surface_t *)abstract_surface; + cairo_surface_flush (abstract_surface); + + //g_print ("n_clip_update_per_frame = %d\n", surface->n_clip_updates_per_frame); + surface->n_clip_updates_per_frame = 0; +} +slim_hidden_def (cairo_cogl_surface_end_frame); diff --git a/src/cairo-cogl-utils-private.h b/src/cairo-cogl-utils-private.h new file mode 100644 index 00000000..ee77f303 --- /dev/null +++ b/src/cairo-cogl-utils-private.h @@ -0,0 +1,54 @@ +/* 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.og/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. + * + * Contributor(s): + * Robert Bragg <robert@linux.intel.com> + */ + +#ifndef CAIRO_COGL_UTILS_PRIVATE_H +#define CAIRO_COGL_UTILS_PRIVATE_H + +#include "cairo-path-fixed-private.h" +#include <cogl/cogl2-experimental.h> + +CoglPath * +_cairo_cogl_util_path_from_cairo (const cairo_path_fixed_t *path, + cairo_fill_rule_t fill_rule, + float tolerance); + +int +_cairo_cogl_util_next_p2 (int a); + +#define CAIRO_FIXED_ONE_FLOAT ((float)(1 << CAIRO_FIXED_FRAC_BITS)) + +static inline float +_cairo_cogl_util_fixed_to_float (cairo_fixed_t f) +{ + return ((float) f) / CAIRO_FIXED_ONE_FLOAT; +} + +#endif /* CAIRO_COGL_UTILS_PRIVATE_H */ diff --git a/src/cairo-cogl-utils.c b/src/cairo-cogl-utils.c new file mode 100644 index 00000000..4f02aaa5 --- /dev/null +++ b/src/cairo-cogl-utils.c @@ -0,0 +1,126 @@ +/* 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.og/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. + * + * Contributor(s): + * Robert Bragg <robert@linux.intel.com> + */ + +#include "cairoint.h" +#include "cairo-cogl-utils-private.h" + +#include <cogl/cogl.h> +#include <glib.h> + +static cairo_status_t +_cogl_move_to (void *closure, + const cairo_point_t *point) +{ + cogl_path_move_to (closure, + _cairo_cogl_util_fixed_to_float (point->x), + _cairo_cogl_util_fixed_to_float (point->y)); + return CAIRO_STATUS_SUCCESS; +} + +static cairo_status_t +_cogl_line_to (void *closure, + const cairo_point_t *point) +{ + cogl_path_line_to (closure, + _cairo_cogl_util_fixed_to_float (point->x), + _cairo_cogl_util_fixed_to_float (point->y)); + return CAIRO_STATUS_SUCCESS; +} + +static cairo_status_t +_cogl_curve_to (void *closure, + const cairo_point_t *p0, + const cairo_point_t *p1, + const cairo_point_t *p2) +{ + cogl_path_curve_to (closure, + _cairo_cogl_util_fixed_to_float (p0->x), + _cairo_cogl_util_fixed_to_float (p0->y), + _cairo_cogl_util_fixed_to_float (p1->x), + _cairo_cogl_util_fixed_to_float (p1->y), + _cairo_cogl_util_fixed_to_float (p2->x), + _cairo_cogl_util_fixed_to_float (p2->y)); + return CAIRO_STATUS_SUCCESS; +} + +static cairo_status_t +_cogl_close_path (void *closure) +{ + cogl_path_close (closure); + return CAIRO_STATUS_SUCCESS; +} + +CoglPath * +_cairo_cogl_util_path_from_cairo (const cairo_path_fixed_t *path, + cairo_fill_rule_t fill_rule, + float tolerance) +{ + CoglPath *cogl_path = cogl_path_new (); + cairo_status_t status; + + if (fill_rule == CAIRO_FILL_RULE_EVEN_ODD) + cogl_path_set_fill_rule (cogl_path, COGL_PATH_FILL_RULE_EVEN_ODD); + else + cogl_path_set_fill_rule (cogl_path, COGL_PATH_FILL_RULE_NON_ZERO); + +#ifdef USE_CAIRO_PATH_FLATTENER + /* XXX: rely on cairo to do path flattening, since it seems Cogl's + * curve_to flattening is much slower */ + status = _cairo_path_fixed_interpret_flat (path, + _cogl_move_to, + _cogl_line_to, + _cogl_close_path, + cogl_path, + tolerance); +#else + status = _cairo_path_fixed_interpret (path, + _cogl_move_to, + _cogl_line_to, + _cogl_curve_to, + _cogl_close_path, + cogl_path); +#endif + + assert (status == CAIRO_STATUS_SUCCESS); + return cogl_path; +} + +int +_cairo_cogl_util_next_p2 (int a) +{ + int rval = 1; + + while (rval < a) + rval <<= 1; + + return rval; +} + diff --git a/src/cairo-cogl.h b/src/cairo-cogl.h new file mode 100644 index 00000000..f270d74d --- /dev/null +++ b/src/cairo-cogl.h @@ -0,0 +1,69 @@ +/* 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 Mozilla Corporation. + * + * Contributor(s): + * Robert Bragg <robert@linux.intel.com> + */ + +#ifndef CAIRO_VG_H +#define CAIRO_VG_H + +#include "cairo.h" + +#if CAIRO_HAS_COGL_SURFACE + +#include <cogl/cogl2-experimental.h> + +CAIRO_BEGIN_DECLS + +cairo_public cairo_device_t * +cairo_cogl_device_create (CoglContext *context); + +cairo_public cairo_surface_t * +cairo_cogl_surface_create (cairo_device_t *device, + CoglFramebuffer *framebuffer); + +cairo_public CoglFramebuffer * +cairo_cogl_surface_get_framebuffer (cairo_surface_t *surface); + +cairo_public CoglTexture * +cairo_cogl_surface_get_texture (cairo_surface_t *surface); + +cairo_public void +cairo_cogl_surface_end_frame (cairo_surface_t *surface); + +CAIRO_END_DECLS + +#else /* CAIRO_HAS_COGL_SURFACE*/ +# error Cairo was not compiled with support for the Cogl backend +#endif /* CAIRO_HAS_COGL_SURFACE*/ + +#endif /* CAIRO_COGL_H */ diff --git a/src/cairo-compiler-private.h b/src/cairo-compiler-private.h index dfe114bf..f3e971eb 100644 --- a/src/cairo-compiler-private.h +++ b/src/cairo-compiler-private.h @@ -208,9 +208,11 @@ #endif #ifdef _MSC_VER +#ifndef __cplusplus #undef inline #define inline __inline #endif +#endif #if defined(_MSC_VER) && defined(_M_IX86) /* When compiling with /Gy and /OPT:ICF identical functions will be folded in together. diff --git a/src/cairo-debug.c b/src/cairo-debug.c index b4cde7de..ebae0723 100644 --- a/src/cairo-debug.c +++ b/src/cairo-debug.c @@ -90,6 +90,10 @@ cairo_debug_reset_static_data (void) _cairo_default_context_reset_static_data (); +#if CAIRO_HAS_COGL_SURFACE + _cairo_cogl_context_reset_static_data (); +#endif + CAIRO_MUTEX_FINALIZE (); } diff --git a/src/cairo-default-context-private.h b/src/cairo-default-context-private.h index 32fd12de..b8d1a9af 100644 --- a/src/cairo-default-context-private.h +++ b/src/cairo-default-context-private.h @@ -55,4 +55,10 @@ struct _cairo_default_context { cairo_private cairo_t * _cairo_default_context_create (void *target); +cairo_status_t +_cairo_default_context_init (cairo_default_context_t *cr, void *target); + +void +_cairo_default_context_fini (cairo_default_context_t *cr); + #endif /* CAIRO_DEFAULT_CONTEXT_PRIVATE_H */ diff --git a/src/cairo-default-context.c b/src/cairo-default-context.c index 98cc6684..b58a7661 100644 --- a/src/cairo-default-context.c +++ b/src/cairo-default-context.c @@ -63,11 +63,9 @@ _cairo_default_context_reset_static_data (void) _freed_pool_reset (&context_pool); } -static void -_cairo_default_context_destroy (void *abstract_cr) +void +_cairo_default_context_fini (cairo_default_context_t *cr) { - cairo_default_context_t *cr = abstract_cr; - while (cr->gstate != &cr->gstate_tail[0]) { if (_cairo_gstate_restore (&cr->gstate, &cr->gstate_freelist)) break; @@ -84,6 +82,14 @@ _cairo_default_context_destroy (void *abstract_cr) _cairo_path_fixed_fini (cr->path); _cairo_fini (&cr->base); +} + +static void +_cairo_default_context_destroy (void *abstract_cr) +{ + cairo_default_context_t *cr = abstract_cr; + + _cairo_default_context_fini (cr); /* mark the context as invalid to protect against misuse */ cr->base.status = CAIRO_STATUS_NULL_POINTER; @@ -1381,6 +1387,19 @@ static const cairo_backend_t _cairo_default_context_backend = { _cairo_default_context_show_page, }; +cairo_status_t +_cairo_default_context_init (cairo_default_context_t *cr, void *target) +{ + _cairo_init (&cr->base, &_cairo_default_context_backend); + _cairo_path_fixed_init (cr->path); + + cr->gstate = &cr->gstate_tail[0]; + cr->gstate_freelist = &cr->gstate_tail[1]; + cr->gstate_tail[1].next = NULL; + + return _cairo_gstate_init (cr->gstate, target); +} + cairo_t * _cairo_default_context_create (void *target) { @@ -1394,14 +1413,7 @@ _cairo_default_context_create (void *target) return _cairo_create_in_error (_cairo_error (CAIRO_STATUS_NO_MEMORY)); } - _cairo_init (&cr->base, &_cairo_default_context_backend); - _cairo_path_fixed_init (cr->path); - - cr->gstate = &cr->gstate_tail[0]; - cr->gstate_freelist = &cr->gstate_tail[1]; - cr->gstate_tail[1].next = NULL; - - status = _cairo_gstate_init (cr->gstate, target); + status = _cairo_default_context_init (cr, target); if (unlikely (status)) { _freed_pool_put (&context_pool, cr); return _cairo_create_in_error (status); diff --git a/src/cairo-gl-msaa-compositor.c b/src/cairo-gl-msaa-compositor.c index 7a0e8288..2210da6e 100644 --- a/src/cairo-gl-msaa-compositor.c +++ b/src/cairo-gl-msaa-compositor.c @@ -247,6 +247,16 @@ _stroke_shaper_add_quad (void *closure, quad); } +static void +_scissor_to_rectangle (cairo_gl_surface_t *surface, + const cairo_rectangle_int_t *r) +{ + int y = _cairo_gl_y_flip (surface, r->y); + + glScissor (r->x, y, r->width, r->height); + glEnable (GL_SCISSOR_TEST); +} + static cairo_int_status_t _cairo_gl_msaa_compositor_stroke (const cairo_compositor_t *compositor, cairo_composite_rectangles_t *composite, @@ -289,10 +299,7 @@ _cairo_gl_msaa_compositor_stroke (const cairo_compositor_t *compositor, if (unlikely (status)) goto finish; - glScissor (composite->unbounded.x, composite->unbounded.y, - composite->unbounded.width, composite->unbounded.height); - glEnable (GL_SCISSOR_TEST); - + _scissor_to_rectangle (dst, &composite->unbounded); if (! _cairo_composite_rectangles_can_reduce_clip (composite, composite->clip)) { @@ -372,10 +379,7 @@ _cairo_gl_msaa_compositor_fill (const cairo_compositor_t *compositor, if (unlikely (status)) goto cleanup_setup; - glScissor (composite->unbounded.x, composite->unbounded.y, - composite->unbounded.width, composite->unbounded.height); - glEnable (GL_SCISSOR_TEST); - + _scissor_to_rectangle (dst, &composite->unbounded); if (! _cairo_composite_rectangles_can_reduce_clip (composite, composite->clip)) { diff --git a/src/cairo-image-source.c b/src/cairo-image-source.c index 943790dc..fde4475d 100644 --- a/src/cairo-image-source.c +++ b/src/cairo-image-source.c @@ -962,7 +962,7 @@ _cairo_image_source_finish (void *abstract_surface) return CAIRO_STATUS_SUCCESS; } -static const cairo_surface_backend_t cairo_image_source_backend = { +const cairo_surface_backend_t _cairo_image_source_backend = { CAIRO_SURFACE_TYPE_IMAGE, _cairo_image_source_finish, NULL, /* read-only wrapper */ @@ -993,7 +993,7 @@ _cairo_image_source_create_for_pattern (cairo_surface_t *dst, } _cairo_surface_init (&source->base, - &cairo_image_source_backend, + &_cairo_image_source_backend, NULL, /* device */ CAIRO_CONTENT_COLOR_ALPHA); diff --git a/src/cairo-image-surface-private.h b/src/cairo-image-surface-private.h index 227a447c..2777e2f3 100644 --- a/src/cairo-image-surface-private.h +++ b/src/cairo-image-surface-private.h @@ -74,6 +74,7 @@ typedef struct _cairo_image_source { } cairo_image_source_t; cairo_private extern const cairo_surface_backend_t _cairo_image_surface_backend; +cairo_private extern const cairo_surface_backend_t _cairo_image_source_backend; cairo_private const cairo_compositor_t * _cairo_image_mask_compositor_get (void); @@ -163,6 +164,20 @@ _cairo_surface_is_image (const cairo_surface_t *surface) return surface->backend == &_cairo_image_surface_backend; } +/** + * _cairo_surface_is_image_source: + * @surface: a #cairo_surface_t + * + * Checks if a surface is an #cairo_image_source_t + * + * Return value: %TRUE if the surface is an image source + **/ +static inline cairo_bool_t +_cairo_surface_is_image_source (const cairo_surface_t *surface) +{ + return surface->backend == &_cairo_image_source_backend; +} + CAIRO_END_DECLS #endif /* CAIRO_IMAGE_SURFACE_PRIVATE_H */ diff --git a/src/cairo-rectangle.c b/src/cairo-rectangle.c index edad369b..aafec907 100644 --- a/src/cairo-rectangle.c +++ b/src/cairo-rectangle.c @@ -272,7 +272,8 @@ _cairo_box_intersects_line_segment (cairo_box_t *box, cairo_line_t *line) static cairo_status_t _cairo_box_add_spline_point (void *closure, - const cairo_point_t *point) + const cairo_point_t *point, + const cairo_slope_t *tangent) { _cairo_box_add_point (closure, point); diff --git a/src/cairo-traps.c b/src/cairo-traps.c index a6e7f94e..30e626fa 100644 --- a/src/cairo-traps.c +++ b/src/cairo-traps.c @@ -715,3 +715,38 @@ _cairo_traps_path (const cairo_traps_t *traps, return CAIRO_STATUS_SUCCESS; } + +void +_cairo_debug_print_traps (FILE *file, const cairo_traps_t *traps) +{ + cairo_box_t extents; + int n; + +#if 0 + if (traps->has_limits) { + printf ("%s: limits=(%d, %d, %d, %d)\n", + filename, + traps->limits.p1.x, traps->limits.p1.y, + traps->limits.p2.x, traps->limits.p2.y); + } +#endif + + _cairo_traps_extents (traps, &extents); + fprintf (file, "extents=(%d, %d, %d, %d)\n", + extents.p1.x, extents.p1.y, + extents.p2.x, extents.p2.y); + + for (n = 0; n < traps->num_traps; n++) { + fprintf (file, "%d %d L:(%d, %d), (%d, %d) R:(%d, %d), (%d, %d)\n", + traps->traps[n].top, + traps->traps[n].bottom, + traps->traps[n].left.p1.x, + traps->traps[n].left.p1.y, + traps->traps[n].left.p2.x, + traps->traps[n].left.p2.y, + traps->traps[n].right.p1.x, + traps->traps[n].right.p1.y, + traps->traps[n].right.p2.x, + traps->traps[n].right.p2.y); + } +} diff --git a/src/cairo-xlib-surface.c b/src/cairo-xlib-surface.c index 1806e64b..0ec12646 100644 --- a/src/cairo-xlib-surface.c +++ b/src/cairo-xlib-surface.c @@ -687,12 +687,15 @@ _get_image_surface (cairo_xlib_surface_t *surface, xlib_masks.red_mask = surface->r_mask; xlib_masks.green_mask = surface->g_mask; xlib_masks.blue_mask = surface->b_mask; - _pixman_format_from_masks (&xlib_masks, &pixman_format); - return _cairo_image_surface_create_with_pixman_format (NULL, - pixman_format, - extents->width, - extents->height, - 0); + if (_pixman_format_from_masks (&xlib_masks, &pixman_format) && + _cairo_format_from_pixman_format (pixman_format) != CAIRO_FORMAT_INVALID) + { + return _cairo_image_surface_create_with_pixman_format (NULL, + pixman_format, + extents->width, + extents->height, + 0); + } } status = _cairo_xlib_display_acquire (surface->base.device, &display); diff --git a/src/cairo.h b/src/cairo.h index f5450fec..aa4eb2bc 100644 --- a/src/cairo.h +++ b/src/cairo.h @@ -2068,6 +2068,7 @@ typedef enum _cairo_device_type { CAIRO_DEVICE_TYPE_XCB, CAIRO_DEVICE_TYPE_XLIB, CAIRO_DEVICE_TYPE_XML, + CAIRO_DEVICE_TYPE_COGL, CAIRO_DEVICE_TYPE_INVALID = -1 } cairo_device_type_t; @@ -2260,6 +2261,7 @@ cairo_surface_status (cairo_surface_t *surface); * @CAIRO_SURFACE_TYPE_SUBSURFACE: The surface is a subsurface created with * cairo_surface_create_for_rectangle(), since 1.10 * @CAIRO_SURFACE_TYPE_MIME: The surface is a callback (mime) surface, since 1.12 + * @CAIRO_SURFACE_TYPE_COGL: This surface is of type Cogl * * #cairo_surface_type_t is used to describe the type of a given * surface. The surface types are also known as "backends" or "surface @@ -2310,7 +2312,8 @@ typedef enum _cairo_surface_type { CAIRO_SURFACE_TYPE_XML, CAIRO_SURFACE_TYPE_SKIA, CAIRO_SURFACE_TYPE_SUBSURFACE, - CAIRO_SURFACE_TYPE_MIME + CAIRO_SURFACE_TYPE_MIME, + CAIRO_SURFACE_TYPE_COGL } cairo_surface_type_t; cairo_public cairo_surface_type_t diff --git a/src/cairoint.h b/src/cairoint.h index 7121d091..1885a5b9 100644 --- a/src/cairoint.h +++ b/src/cairoint.h @@ -401,6 +401,11 @@ _cairo_ft_font_reset_static_data (void); cairo_private void _cairo_win32_font_reset_static_data (void); +#if CAIRO_HAS_COGL_SURFACE +void +_cairo_cogl_context_reset_static_data (void); +#endif + /* the font backend interface */ struct _cairo_unscaled_font_backend { @@ -1960,6 +1965,9 @@ cairo_private void _cairo_debug_print_polygon (FILE *stream, cairo_polygon_t *polygon); cairo_private void +_cairo_debug_print_traps (FILE *file, const cairo_traps_t *traps); + +cairo_private void _cairo_debug_print_clip (FILE *stream, const cairo_clip_t *clip); #endif |