/* 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 */ #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-inline.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-inline.h" #include "cairo-surface-subsurface-inline.h" #include "cairo-surface-fallback-private.h" #include "cairo-surface-offset-private.h" #include "cairo-cogl.h" #include #include #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 tessellate 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 tessellate 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 throw 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 tessellating 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, unsigned flags) { cairo_cogl_surface_t *surface = (cairo_cogl_surface_t *)abstract_surface; if (flags) return CAIRO_STATUS_SUCCESS; _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 * optimizations 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, 0); 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 = _cairo_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: free (meta_path); 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_polygon_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 = _cairo_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: free (meta_path); 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 acquire 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_surface_default_source, _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 = _cairo_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, FALSE); /* is_vector */ 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 _cairo_device_create_in_error (CAIRO_STATUS_DEVICE_ERROR); } 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);