summaryrefslogtreecommitdiff
path: root/src/cairo-cogl-surface.c
diff options
context:
space:
mode:
authorRobert Bragg <robert@linux.intel.com>2011-07-21 12:15:05 +0100
committerChris Wilson <chris@chris-wilson.co.uk>2011-10-11 09:05:45 +0100
commit571a27b4fcfe11fec2b33c31525c353eb30c0ad7 (patch)
tree9b338440d2f1366e585a10e6da1b869ce7469772 /src/cairo-cogl-surface.c
parent5783bbfc750c73a4b36ad30a054de59a7ee99f34 (diff)
backends: Adds a new Cogl based backend
This adds a new GPU accelerated backend for Cairo based on the Cogl 3D graphics API. This backend aims to support Cairo in a way that translates as naturally as possible to using a GPU, it does not strive to compete with the anti-aliasing quality of the image backend if it can't be done efficiently using the GPU - raw performance isn't the only metric of concern, so is power usage. As an overview of how the backend works: - fills are handled by tessellating paths into triangles - the backend has an extra fill_rectangle drawing operation so we have a fast-path for drawing rectangles which are so common. - strokes are also tessellated into triangles. - stroke and fill tessellations are cached to avoid the cpu overhead of tessellation and cost of upload given that its common for apps to re-draw the same path multiple times. The tessellations can survive translations and rotations increasing the probability that they can be re-used. - sources and masks are handled using multi-texturing. - clipping is handled with a scissor and the stencil buffer which we're careful to only update when they really change. - linear gradients are rendered to a 1d texture using a triangle strip + interpolating color attributes. All cairo extend modes are handled by corresponding texture sampler wrap modes without needing programmable fragment processing. - antialiasing should be handled using Cogl's multisampling API XXX: This is a work in progress!! TODO: - handle at least basic radial gradients (No need to handle full pdf semantics, since css, svg and canvas only allow radial gradients defined as one circle + a point that must lie within the first circle.) - currently we fall back to pixman for radial gradients. - support glyph rendering with a decent glyph cache design. The current plan is a per scaled-font growable cache texture + a scratch cache for one-shot/short-lived glyphs. - decide how to handle npot textures when lacking hardware support. Current plan is to add a transparent border to npot textures and use CLAMP_TO_EDGE for the default EXTEND_NONE semantics. For anything else we can allocate a shadow npot texture and scale the original to fit that so we can map extend modes to texture sampler modes.
Diffstat (limited to 'src/cairo-cogl-surface.c')
-rw-r--r--src/cairo-cogl-surface.c2802
1 files changed, 2802 insertions, 0 deletions
diff --git a/src/cairo-cogl-surface.c b/src/cairo-cogl-surface.c
new file mode 100644
index 000000000..0d990b3a8
--- /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);