summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/Makefile.sources2
-rw-r--r--src/cairo-gl-glyphs.c829
-rw-r--r--src/cairo-gl-private.h115
-rw-r--r--src/cairo-gl-surface.c69
4 files changed, 965 insertions, 50 deletions
diff --git a/src/Makefile.sources b/src/Makefile.sources
index dd8501ec..ca8d51df 100644
--- a/src/Makefile.sources
+++ b/src/Makefile.sources
@@ -264,7 +264,7 @@ cairo_beos_headers = cairo-beos.h
cairo_gl_headers = cairo-gl.h
cairo_gl_private = cairo-gl-private.h
-cairo_gl_sources = cairo-gl-surface.c
+cairo_gl_sources = cairo-gl-surface.c cairo-gl-glyphs.c
cairo_glx_sources += cairo-glx-context.c
cairo_eagle_sources += cairo-eagle-context.c
diff --git a/src/cairo-gl-glyphs.c b/src/cairo-gl-glyphs.c
new file mode 100644
index 00000000..7fe0123c
--- /dev/null
+++ b/src/cairo-gl-glyphs.c
@@ -0,0 +1,829 @@
+/* Cairo - a vector graphics library with display and print output
+ *
+ * Copyright © 2009 Chris Wilson
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it either under the terms of the GNU Lesser General Public
+ * License version 2.1 as published by the Free Software Foundation
+ * (the "LGPL") or, at your option, under the terms of the Mozilla
+ * Public License Version 1.1 (the "MPL"). If you do not alter this
+ * notice, a recipient may use your version of this file under either
+ * the MPL or the LGPL.
+ *
+ * You should have received a copy of the LGPL along with this library
+ * in the file COPYING-LGPL-2.1; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ * You should have received a copy of the MPL along with this library
+ * in the file COPYING-MPL-1.1
+ *
+ * The contents of this file are subject to the Mozilla Public License
+ * Version 1.1 (the "License"); you may not use this file except in
+ * compliance with the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY
+ * OF ANY KIND, either express or implied. See the LGPL or the MPL for
+ * the specific language governing rights and limitations.
+ *
+ * The Original Code is the cairo graphics library.
+ *
+ * The Initial Developer of the Original Code is Chris Wilson.
+ */
+
+#include "cairoint.h"
+
+#include "cairo-gl-private.h"
+#include "cairo-freelist-private.h"
+
+#define GLYPH_CACHE_WIDTH 1024
+#define GLYPH_CACHE_HEIGHT 1024
+#define GLYPH_CACHE_MIN_SIZE 4
+#define GLYPH_CACHE_MAX_SIZE 128
+
+typedef struct _cairo_gl_glyph_private {
+ rtree_node_t node;
+ void **owner;
+ struct { float x, y; } p1, p2;
+} cairo_gl_glyph_private_t;
+
+static void
+_rtree_node_evict (rtree_t *rtree, rtree_node_t *node)
+{
+ rtree->evict (node);
+ node->state = RTREE_NODE_AVAILABLE;
+}
+
+static rtree_node_t *
+_rtree_node_create (rtree_t *rtree,
+ rtree_node_t *parent,
+ int x,
+ int y,
+ int width,
+ int height)
+{
+ rtree_node_t *node;
+
+ /* XXX chunked freelist */
+ node = _cairo_freelist_alloc (&rtree->node_freelist);
+ if (node == NULL) {
+ _cairo_error_throw (CAIRO_STATUS_NO_MEMORY);
+ return NULL;
+ }
+
+ memset (node->children, 0, sizeof (node->children));
+ node->parent = parent;
+ node->state = RTREE_NODE_AVAILABLE;
+ node->locked = FALSE;
+ node->x = x;
+ node->y = y;
+ node->width = width;
+ node->height = height;
+
+ return node;
+}
+
+static void
+_rtree_node_destroy (rtree_t *rtree, rtree_node_t *node)
+{
+ int i;
+
+ if (node == NULL)
+ return;
+
+ if (node->state == RTREE_NODE_OCCUPIED) {
+ _rtree_node_evict (rtree, node);
+ } else {
+ for (i = 0; i < 4 && node->children[i] != NULL; i++)
+ _rtree_node_destroy (rtree, node->children[i]);
+ }
+
+ _cairo_freelist_free (&rtree->node_freelist, node);
+}
+
+static cairo_int_status_t
+_rtree_insert (rtree_t *rtree,
+ rtree_node_t *node,
+ int width,
+ int height,
+ rtree_node_t **out)
+{
+ cairo_status_t status;
+ int i;
+
+ switch (node->state) {
+ case RTREE_NODE_DIVIDED:
+ for (i = 0; i < 4 && node->children[i] != NULL; i++) {
+ if (node->children[i]->width >= width &&
+ node->children[i]->height >= height)
+ {
+ status = _rtree_insert (rtree, node->children[i],
+ width, height,
+ out);
+ if (status != CAIRO_INT_STATUS_UNSUPPORTED)
+ return status;
+ }
+ }
+
+ default:
+ case RTREE_NODE_OCCUPIED:
+ return CAIRO_INT_STATUS_UNSUPPORTED;
+
+ case RTREE_NODE_AVAILABLE:
+ if (node->width - width > GLYPH_CACHE_MIN_SIZE ||
+ node->height - height > GLYPH_CACHE_MIN_SIZE)
+ {
+ int w, h;
+
+ w = node->width - width;
+ h = node->height - height;
+
+ i = 0;
+ node->children[i] = _rtree_node_create (rtree, node,
+ node->x, node->y,
+ width, height);
+ if (unlikely (node->children[i] == NULL))
+ return _cairo_error (CAIRO_STATUS_NO_MEMORY);
+ i++;
+
+ if (w > GLYPH_CACHE_MIN_SIZE) {
+ node->children[i] = _rtree_node_create (rtree, node,
+ node->x + width,
+ node->y,
+ w, height);
+ if (unlikely (node->children[i] == NULL))
+ return _cairo_error (CAIRO_STATUS_NO_MEMORY);
+ i++;
+ }
+
+ if (h > GLYPH_CACHE_MIN_SIZE) {
+ node->children[i] = _rtree_node_create (rtree, node,
+ node->x,
+ node->y + height,
+ width, h);
+ if (unlikely (node->children[i] == NULL))
+ return _cairo_error (CAIRO_STATUS_NO_MEMORY);
+ i++;
+
+ if (w > GLYPH_CACHE_MIN_SIZE) {
+ node->children[i] = _rtree_node_create (rtree, node,
+ node->x + width,
+ node->y + height,
+ w, h);
+ if (unlikely (node->children[i] == NULL))
+ return _cairo_error (CAIRO_STATUS_NO_MEMORY);
+ i++;
+ }
+ }
+
+ node->state = RTREE_NODE_DIVIDED;
+ node = node->children[0];
+ }
+
+ node->state = RTREE_NODE_OCCUPIED;
+ *out = node;
+ return CAIRO_STATUS_SUCCESS;
+ }
+}
+
+static cairo_int_status_t
+_rtree_add_evictable_nodes (rtree_t *rtree,
+ rtree_node_t *node,
+ int width,
+ int height,
+ cairo_array_t *evictable_nodes)
+{
+ cairo_int_status_t status;
+ cairo_bool_t child_added = FALSE;
+ int i;
+
+ switch (node->state) {
+ case RTREE_NODE_DIVIDED:
+ for (i = 0; i < 4 && node->children[i] != NULL; i++) {
+ if (node->children[i]->width >= width &&
+ node->children[i]->height >= height)
+ {
+ status = _rtree_add_evictable_nodes (rtree, node->children[i],
+ width, height,
+ evictable_nodes);
+ if (_cairo_status_is_error (status))
+ return status;
+
+ child_added |= status == CAIRO_STATUS_SUCCESS;
+ }
+ }
+ if (child_added)
+ return CAIRO_STATUS_SUCCESS;
+
+ /* fall through */
+ case RTREE_NODE_AVAILABLE:
+ case RTREE_NODE_OCCUPIED:
+ if (node->locked)
+ return CAIRO_INT_STATUS_UNSUPPORTED;
+ }
+
+ return _cairo_array_append (evictable_nodes, &node);
+}
+
+static uint32_t
+hars_petruska_f54_1_random (void)
+{
+#define rol(x,k) ((x << k) | (x >> (32-k)))
+ static uint32_t x;
+ return x = (x ^ rol (x, 5) ^ rol (x, 24)) + 0x37798849;
+#undef rol
+}
+
+static cairo_int_status_t
+_rtree_evict_random (rtree_t *rtree,
+ rtree_node_t *root,
+ int width,
+ int height,
+ rtree_node_t **out)
+{
+ cairo_array_t evictable_nodes;
+ cairo_status_t status;
+ int i;
+
+ _cairo_array_init (&evictable_nodes, sizeof (rtree_node_t *));
+
+ status = _rtree_add_evictable_nodes (rtree, root,
+ width, height,
+ &evictable_nodes);
+ if (status == CAIRO_STATUS_SUCCESS) {
+ rtree_node_t *node;
+
+ node = *(rtree_node_t **)
+ _cairo_array_index (&evictable_nodes,
+ hars_petruska_f54_1_random () % evictable_nodes.num_elements);
+ if (node->state == RTREE_NODE_OCCUPIED) {
+ _rtree_node_evict (rtree, node);
+ } else {
+ for (i = 0; i < 4 && node->children[i] != NULL; i++) {
+ _rtree_node_destroy (rtree, node->children[i]);
+ node->children[i] = NULL;
+ }
+ }
+
+ node->state = RTREE_NODE_AVAILABLE;
+ *out = node;
+ }
+
+ _cairo_array_fini (&evictable_nodes);
+
+ return status;
+}
+
+static void *
+_rtree_lock (rtree_t *rtree, rtree_node_t *node)
+{
+ void *ptr = node;
+
+ while (node != NULL && ! node->locked) {
+ node->locked = TRUE;
+ node = node->parent;
+ }
+
+ return ptr;
+}
+
+static void
+_rtree_unlock (rtree_t *rtree, rtree_node_t *node)
+{
+ int i;
+
+ if (! node->locked)
+ return;
+
+ node->locked = FALSE;
+ if (node->state == RTREE_NODE_DIVIDED) {
+ for (i = 0; i < 4 && node->children[i] != NULL; i++)
+ _rtree_unlock (rtree, node->children[i]);
+ }
+}
+
+static void
+_rtree_init (rtree_t *rtree,
+ int width,
+ int height,
+ int node_size,
+ void (*evict) (void *node))
+{
+ rtree->evict = evict;
+
+ assert (node_size >= (int) sizeof (rtree_node_t));
+ _cairo_freelist_init (&rtree->node_freelist, node_size);
+
+ memset (&rtree->root, 0, sizeof (rtree->root));
+ rtree->root.width = width;
+ rtree->root.height = height;
+}
+
+static void
+_rtree_fini (rtree_t *rtree)
+{
+ int i;
+
+ if (rtree->root.state == RTREE_NODE_OCCUPIED) {
+ _rtree_node_evict (rtree, &rtree->root);
+ } else {
+ for (i = 0; i < 4 && rtree->root.children[i] != NULL; i++)
+ _rtree_node_destroy (rtree, rtree->root.children[i]);
+ }
+
+ _cairo_freelist_fini (&rtree->node_freelist);
+}
+
+static void
+_glyph_evict (void *node)
+{
+ cairo_gl_glyph_private_t *glyph_private = node;
+
+ if (glyph_private->owner != NULL)
+ *glyph_private->owner = NULL;
+}
+
+static cairo_status_t
+_cairo_gl_glyph_cache_add_glyph (cairo_gl_glyph_cache_t *cache,
+ cairo_scaled_glyph_t *scaled_glyph)
+{
+ cairo_image_surface_t *glyph_surface = scaled_glyph->surface;
+ cairo_gl_glyph_private_t *glyph_private;
+ rtree_node_t *node = NULL;
+ cairo_status_t status;
+ int width, height;
+ GLenum internal_format, format, type;
+ cairo_bool_t has_alpha;
+
+ status = _cairo_gl_get_image_format_and_type (glyph_surface->pixman_format,
+ &internal_format,
+ &format,
+ &type,
+ &has_alpha);
+ if (status != CAIRO_STATUS_SUCCESS)
+ return status;
+
+ width = glyph_surface->width;
+ if (width < GLYPH_CACHE_MIN_SIZE)
+ width = GLYPH_CACHE_MIN_SIZE;
+ height = glyph_surface->height;
+ if (height < GLYPH_CACHE_MIN_SIZE)
+ height = GLYPH_CACHE_MIN_SIZE;
+
+ /* search for an available slot */
+ status = _rtree_insert (&cache->rtree, &cache->rtree.root,
+ width, height, &node);
+ /* search for an unlocked slot */
+ if (status == CAIRO_INT_STATUS_UNSUPPORTED) {
+ status = _rtree_evict_random (&cache->rtree, &cache->rtree.root,
+ width, height, &node);
+ if (status == CAIRO_STATUS_SUCCESS)
+ status = _rtree_insert (&cache->rtree, node, width, height, &node);
+ }
+ if (status)
+ return status;
+
+ glPixelStorei (GL_UNPACK_ALIGNMENT, 1);
+ glPixelStorei (GL_UNPACK_ROW_LENGTH,
+ glyph_surface->stride /
+ (PIXMAN_FORMAT_BPP (glyph_surface->pixman_format) / 8));
+ glTexSubImage2D (GL_TEXTURE_2D, 0,
+ node->x, node->y,
+ glyph_surface->width, glyph_surface->height,
+ format, type,
+ glyph_surface->data);
+ glPixelStorei (GL_UNPACK_ROW_LENGTH, 0);
+
+ scaled_glyph->surface_private = node;
+
+ glyph_private = (cairo_gl_glyph_private_t *) node;
+ glyph_private->owner = &scaled_glyph->surface_private;
+
+ /* compute tex coords */
+ glyph_private->p1.x = node->x / (double) cache->width;
+ glyph_private->p1.y = node->y / (double) cache->height;
+ glyph_private->p2.x =
+ (node->x + glyph_surface->width) / (double) cache->width;
+ glyph_private->p2.y =
+ (node->y + glyph_surface->height) / (double) cache->height;
+
+ return CAIRO_STATUS_SUCCESS;
+}
+
+static cairo_gl_glyph_private_t *
+_cairo_gl_glyph_cache_lock (cairo_gl_glyph_cache_t *cache,
+ cairo_scaled_glyph_t *scaled_glyph)
+{
+ return _rtree_lock (&cache->rtree, scaled_glyph->surface_private);
+}
+
+static cairo_status_t
+cairo_gl_glyph_cache_init (cairo_gl_glyph_cache_t *cache,
+ cairo_gl_context_t *ctx,
+ cairo_format_t format,
+ int width, int height)
+{
+ cairo_content_t content;
+ GLenum internal_format;
+
+ assert ((width & 3) == 0);
+ assert ((height & 1) == 0);
+ cache->width = width;
+ cache->height = height;
+
+ switch (format) {
+ case CAIRO_FORMAT_A1:
+ case CAIRO_FORMAT_RGB24:
+ ASSERT_NOT_REACHED;
+ case CAIRO_FORMAT_ARGB32:
+ content = CAIRO_CONTENT_COLOR_ALPHA;
+ internal_format = GL_RGBA;
+ break;
+ case CAIRO_FORMAT_A8:
+ content = CAIRO_CONTENT_ALPHA;
+ internal_format = GL_ALPHA;
+ break;
+ }
+
+ glGenTextures (1, &cache->tex);
+ glBindTexture (GL_TEXTURE_2D, cache->tex);
+ glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
+ glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
+ glTexImage2D (GL_TEXTURE_2D, 0, internal_format,
+ width, height, 0, internal_format, GL_FLOAT, NULL);
+
+ _rtree_init (&cache->rtree,
+ width, height,
+ sizeof (cairo_gl_glyph_private_t),
+ _glyph_evict);
+
+ return CAIRO_STATUS_SUCCESS;
+}
+
+static cairo_status_t
+cairo_gl_context_get_glyph_cache (cairo_gl_context_t *ctx,
+ cairo_format_t format,
+ cairo_gl_glyph_cache_t **out)
+{
+ cairo_gl_glyph_cache_t *cache;
+ cairo_status_t status;
+
+ switch (format) {
+ case CAIRO_FORMAT_ARGB32:
+ case CAIRO_FORMAT_RGB24:
+ cache = &ctx->glyph_cache[0];
+ format = CAIRO_FORMAT_ARGB32;
+ break;
+ case CAIRO_FORMAT_A8:
+ case CAIRO_FORMAT_A1:
+ cache = &ctx->glyph_cache[1];
+ format = CAIRO_FORMAT_A8;
+ break;
+ }
+
+ if (unlikely (cache->tex == 0)) {
+ status = cairo_gl_glyph_cache_init (cache, ctx, format,
+ GLYPH_CACHE_WIDTH,
+ GLYPH_CACHE_HEIGHT);
+ if (unlikely (status))
+ return status;
+ }
+
+ *out = cache;
+ return CAIRO_STATUS_SUCCESS;
+}
+
+static cairo_bool_t
+_cairo_gl_surface_owns_font (cairo_gl_surface_t *surface,
+ cairo_scaled_font_t *scaled_font)
+{
+ cairo_gl_context_t *font_private;
+
+ font_private = scaled_font->surface_private;
+ if ((scaled_font->surface_backend != NULL &&
+ scaled_font->surface_backend != &_cairo_gl_surface_backend) ||
+ (font_private != NULL && font_private != surface->ctx))
+ {
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+void
+_cairo_gl_surface_scaled_glyph_fini (cairo_scaled_glyph_t *scaled_glyph,
+ cairo_scaled_font_t *scaled_font)
+{
+ cairo_gl_glyph_private_t *glyph_private;
+
+ glyph_private = scaled_glyph->surface_private;
+ if (glyph_private != NULL)
+ glyph_private->owner = NULL;
+}
+
+static void
+_cairo_gl_emit_glyph_rectangle (cairo_gl_context_t *ctx,
+ cairo_gl_composite_setup_t *setup,
+ int x1, int y1,
+ int x2, int y2,
+ cairo_gl_glyph_private_t *glyph,
+ float *vertices, float *texcoord_src,
+ float *texcoord_mask)
+{
+ double s, t;
+ int i;
+ cairo_surface_attributes_t *src_attributes;
+
+ src_attributes = &setup->src.operand.texture.attributes;
+
+ vertices[0] = x1;
+ vertices[1] = y1;
+ vertices[2] = x2;
+ vertices[3] = y1;
+ vertices[4] = x2;
+ vertices[5] = y2;
+ vertices[6] = x1;
+ vertices[7] = y2;
+
+ if (setup->src.type == OPERAND_TEXTURE) {
+ for (i = 0; i < 4; i++) {
+ s = vertices[i * 2];
+ t = vertices[i * 2 + 1];
+ cairo_matrix_transform_point (&src_attributes->matrix, &s, &t);
+ texcoord_src[i * 2] = s;
+ texcoord_src[i * 2 + 1] = t;
+ }
+ }
+
+ texcoord_mask[0] = glyph->p1.x;
+ texcoord_mask[1] = glyph->p1.y;
+ texcoord_mask[2] = glyph->p2.x;
+ texcoord_mask[3] = glyph->p1.y;
+ texcoord_mask[4] = glyph->p2.x;
+ texcoord_mask[5] = glyph->p2.y;
+ texcoord_mask[6] = glyph->p1.x;
+ texcoord_mask[7] = glyph->p2.y;
+
+ glDrawArrays (GL_QUADS, 0, 4);
+}
+
+cairo_int_status_t
+_cairo_gl_surface_show_glyphs (void *abstract_dst,
+ cairo_operator_t op,
+ const cairo_pattern_t *source,
+ cairo_glyph_t *glyphs,
+ int num_glyphs,
+ cairo_scaled_font_t *scaled_font,
+ cairo_clip_t *clip,
+ int *remaining_glyphs)
+{
+ cairo_gl_surface_t *dst = abstract_dst;
+ cairo_gl_context_t *ctx;
+ cairo_rectangle_int_t extents;
+ cairo_format_t last_format = (cairo_format_t) -1;
+ cairo_gl_glyph_cache_t *cache = NULL;
+ cairo_status_t status;
+ int i = 0;
+ cairo_gl_composite_setup_t setup;
+ float vertices[4 * 2], texcoord_src[4 * 2], texcoord_mask[4 * 2];
+ cairo_region_t *clip_region = NULL;
+ cairo_solid_pattern_t solid_pattern;
+
+ /* Just let unbounded operators go through the fallback code
+ * instead of trying to do the fixups here */
+ if (! _cairo_operator_bounded_by_mask (op))
+ return CAIRO_INT_STATUS_UNSUPPORTED;
+
+ /* For CLEAR, cairo's rendering equation (quoting Owen's description in:
+ * http://lists.cairographics.org/archives/cairo/2005-August/004992.html)
+ * is:
+ * mask IN clip ? src OP dest : dest
+ * or more simply:
+ * mask IN CLIP ? 0 : dest
+ *
+ * where the ternary operator A ? B : C is (A * B) + ((1 - A) * C).
+ *
+ * The model we use in _cairo_gl_set_operator() is Render's:
+ * src IN mask IN clip OP dest
+ * which would boil down to:
+ * 0 (bounded by the extents of the drawing).
+ *
+ * However, we can do a Render operation using an opaque source
+ * and DEST_OUT to produce:
+ * 1 IN mask IN clip DEST_OUT dest
+ * which is
+ * mask IN clip ? 0 : dest
+ */
+ if (op == CAIRO_OPERATOR_CLEAR) {
+ _cairo_pattern_init_solid (&solid_pattern, CAIRO_COLOR_WHITE,
+ CAIRO_CONTENT_COLOR);
+ source = &solid_pattern.base;
+ op = CAIRO_OPERATOR_DEST_OUT;
+ }
+
+ /* For SOURCE, cairo's rendering equation is:
+ * (mask IN clip) ? src OP dest : dest
+ * or more simply:
+ * (mask IN clip) ? src : dest.
+ *
+ * If we just used the Render equation, we would get:
+ * (src IN mask IN clip) OP dest
+ * or:
+ * (src IN mask IN clip) bounded by extents of the drawing.
+ *
+ * The trick is that for GL blending, we only get our 4 source values
+ * into the blender, and since we need all 4 components of source, we
+ * can't also get the mask IN clip into the blender. But if we did
+ * two passes we could make it work:
+ * dest = (mask IN clip) DEST_OUT dest
+ * dest = src IN mask IN clip ADD dest
+ *
+ * But for now, fall back :)
+ */
+ if (op == CAIRO_OPERATOR_SOURCE)
+ return CAIRO_INT_STATUS_UNSUPPORTED;
+
+ /* XXX we don't need ownership of the font as we use a global
+ * glyph cache -- but we do need scaled_glyph eviction notification. :-(
+ */
+ if (! _cairo_gl_surface_owns_font (dst, scaled_font))
+ return CAIRO_INT_STATUS_UNSUPPORTED;
+
+ /* XXX If glyphs overlap, build tmp mask and composite.
+ * Could we use the stencil instead but only write if alpha !=0 ?
+ * TEXKILL? PIXELKILL?
+ * Antialiasing issues - but using glyph images cause their own anyway.
+ */
+
+ status = _cairo_scaled_font_glyph_device_extents (scaled_font,
+ glyphs, num_glyphs,
+ &extents);
+ if (unlikely (status))
+ return status;
+
+ status = _cairo_gl_operand_init (&setup.src, source, dst,
+ extents.x, extents.y,
+ extents.x, extents.y,
+ extents.width, extents.height);
+ if (unlikely (status))
+ return status;
+
+ ctx = _cairo_gl_context_acquire (dst->ctx);
+
+ /* Set up the mask to source from the incoming vertex color. */
+ glActiveTexture (GL_TEXTURE1);
+ glEnable (GL_TEXTURE_2D);
+ /* IN: dst.argb = src.argb * mask.aaaa */
+ glTexEnvi (GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_COMBINE);
+ glTexEnvi (GL_TEXTURE_ENV, GL_COMBINE_RGB, GL_MODULATE);
+ glTexEnvi (GL_TEXTURE_ENV, GL_COMBINE_ALPHA, GL_MODULATE);
+
+ glTexEnvi (GL_TEXTURE_ENV, GL_SRC0_RGB, GL_PREVIOUS);
+ glTexEnvi (GL_TEXTURE_ENV, GL_SRC0_ALPHA, GL_PREVIOUS);
+ glTexEnvi (GL_TEXTURE_ENV, GL_OPERAND0_RGB, GL_SRC_COLOR);
+ glTexEnvi (GL_TEXTURE_ENV, GL_OPERAND0_ALPHA, GL_SRC_ALPHA);
+
+ glTexEnvi (GL_TEXTURE_ENV, GL_SRC1_RGB, GL_TEXTURE1);
+ glTexEnvi (GL_TEXTURE_ENV, GL_SRC1_ALPHA, GL_TEXTURE1);
+ glTexEnvi (GL_TEXTURE_ENV, GL_OPERAND1_RGB, GL_SRC_ALPHA);
+ glTexEnvi (GL_TEXTURE_ENV, GL_OPERAND1_ALPHA, GL_SRC_ALPHA);
+
+ _cairo_gl_set_destination (dst);
+
+ status = _cairo_gl_set_operator (dst, op);
+ if (status != CAIRO_STATUS_SUCCESS)
+ goto CLEANUP_CONTEXT;
+
+ _cairo_gl_set_src_operand (ctx, &setup);
+
+ _cairo_scaled_font_freeze_cache (scaled_font);
+ if (! _cairo_gl_surface_owns_font (dst, scaled_font))
+ goto CLEANUP_CONTEXT;
+
+ if (scaled_font->surface_private == NULL) {
+ /* XXX couple into list to remove on context destruction */
+ scaled_font->surface_private = ctx;
+ scaled_font->surface_backend = &_cairo_gl_surface_backend;
+ }
+
+ glVertexPointer (2, GL_FLOAT, sizeof (GLfloat) * 2, vertices);
+ glEnableClientState (GL_VERTEX_ARRAY);
+ if (setup.src.type == OPERAND_TEXTURE) {
+ glClientActiveTexture (GL_TEXTURE0);
+ glTexCoordPointer (2, GL_FLOAT, sizeof (GLfloat) * 2, texcoord_src);
+ glEnableClientState (GL_TEXTURE_COORD_ARRAY);
+ }
+ glClientActiveTexture (GL_TEXTURE1);
+ glTexCoordPointer (2, GL_FLOAT, sizeof (GLfloat) * 2, texcoord_mask);
+ glEnableClientState (GL_TEXTURE_COORD_ARRAY);
+
+ for (i = 0; i < num_glyphs; i++) {
+ cairo_scaled_glyph_t *scaled_glyph;
+ double x_offset, y_offset;
+ double x1, x2, y1, y2;
+
+ status = _cairo_scaled_glyph_lookup (scaled_font,
+ glyphs[i].index,
+ CAIRO_SCALED_GLYPH_INFO_SURFACE,
+ &scaled_glyph);
+ if (unlikely (status))
+ goto FINISH;
+
+ if (scaled_glyph->surface->width == 0 ||
+ scaled_glyph->surface->height == 0)
+ {
+ continue;
+ }
+ if (scaled_glyph->surface->width > GLYPH_CACHE_MAX_SIZE ||
+ scaled_glyph->surface->height > GLYPH_CACHE_MAX_SIZE)
+ {
+ status = CAIRO_INT_STATUS_UNSUPPORTED;
+ goto FINISH;
+ }
+
+ if (scaled_glyph->surface->format != last_format) {
+ glActiveTexture (GL_TEXTURE1);
+ status = cairo_gl_context_get_glyph_cache (ctx,
+ scaled_glyph->surface->format,
+ &cache);
+ if (unlikely (status))
+ goto FINISH;
+
+ glBindTexture (GL_TEXTURE_2D, cache->tex);
+
+ last_format = scaled_glyph->surface->format;
+ }
+
+ if (scaled_glyph->surface_private == NULL) {
+ status = _cairo_gl_glyph_cache_add_glyph (cache, scaled_glyph);
+ if (unlikely (_cairo_status_is_error (status)))
+ goto FINISH;
+
+ if (status == CAIRO_INT_STATUS_UNSUPPORTED) {
+ int n;
+
+ /* If we were accumulating VBOs like sensible people, this is where
+ * we'd flush because the cache was full. But we're slackers.
+ */
+
+ for (n = 0; n < ARRAY_LENGTH (ctx->glyph_cache); n++) {
+ _rtree_unlock (&ctx->glyph_cache[n].rtree,
+ &ctx->glyph_cache[n].rtree.root);
+ }
+ }
+
+ status = _cairo_gl_glyph_cache_add_glyph (cache, scaled_glyph);
+ if (unlikely (status))
+ goto FINISH;
+ }
+
+ x_offset = scaled_glyph->surface->base.device_transform.x0;
+ y_offset = scaled_glyph->surface->base.device_transform.y0;
+
+ x1 = _cairo_lround (glyphs[i].x - x_offset);
+ y1 = _cairo_lround (glyphs[i].y - y_offset);
+ x2 = x1 + scaled_glyph->surface->width;
+ y2 = y1 + scaled_glyph->surface->height;
+
+ _cairo_gl_emit_glyph_rectangle (ctx, &setup,
+ x1, y1, x2, y2,
+ _cairo_gl_glyph_cache_lock (cache, scaled_glyph),
+ vertices, texcoord_src, texcoord_mask);
+ }
+
+ status = CAIRO_STATUS_SUCCESS;
+ FINISH:
+ _cairo_scaled_font_thaw_cache (scaled_font);
+
+ CLEANUP_CONTEXT:
+ glDisable (GL_BLEND);
+
+ glDisableClientState (GL_VERTEX_ARRAY);
+
+ glClientActiveTexture (GL_TEXTURE0);
+ glDisableClientState (GL_TEXTURE_COORD_ARRAY);
+ glActiveTexture (GL_TEXTURE0);
+ glDisable (GL_TEXTURE_2D);
+
+ glClientActiveTexture (GL_TEXTURE1);
+ glDisableClientState (GL_TEXTURE_COORD_ARRAY);
+ glActiveTexture (GL_TEXTURE1);
+ glDisable (GL_TEXTURE_2D);
+
+ _cairo_gl_context_release (ctx);
+
+ _cairo_gl_operand_destroy (&setup.src);
+
+ *remaining_glyphs = num_glyphs - i;
+ return status;
+}
+
+void
+_cairo_gl_glyph_cache_fini (cairo_gl_glyph_cache_t *cache)
+{
+ if (cache->tex == 0)
+ return;
+
+ glDeleteTextures (1, &cache->tex);
+
+ _rtree_fini (&cache->rtree);
+}
diff --git a/src/cairo-gl-private.h b/src/cairo-gl-private.h
index 5d8ff603..5ca48ade 100644
--- a/src/cairo-gl-private.h
+++ b/src/cairo-gl-private.h
@@ -40,6 +40,7 @@
#define CAIRO_GL_PRIVATE_H
#include "cairoint.h"
+#include "cairo-freelist-private.h"
#include <GL/glew.h>
@@ -60,6 +61,31 @@ typedef struct _cairo_gl_surface {
GLuint fb; /* GL framebuffer object wrapping our data. */
} cairo_gl_surface_t;
+enum {
+ RTREE_NODE_AVAILABLE,
+ RTREE_NODE_DIVIDED,
+ RTREE_NODE_OCCUPIED,
+};
+typedef struct _rtree_node {
+ struct _rtree_node *children[4], *parent;
+ uint16_t state;
+ uint16_t locked;
+ uint16_t x, y;
+ uint16_t width, height;
+} rtree_node_t;
+
+typedef struct _rtree {
+ rtree_node_t root;
+ void (*evict) (void *node);
+ cairo_freelist_t node_freelist;
+} rtree_t;
+
+typedef struct cairo_gl_glyph_cache {
+ rtree_t rtree;
+ GLuint tex;
+ unsigned int width, height;
+} cairo_gl_glyph_cache_t;
+
struct _cairo_gl_context {
cairo_reference_count_t ref_count;
cairo_status_t status;
@@ -70,12 +96,45 @@ struct _cairo_gl_context {
GLint max_texture_size;
cairo_gl_surface_t *current_target;
+ cairo_gl_glyph_cache_t glyph_cache[2];
void (*make_current)(void *ctx, cairo_gl_surface_t *surface);
void (*swap_buffers)(void *ctx, cairo_gl_surface_t *surface);
void (*destroy) (void *ctx);
};
+enum cairo_gl_composite_operand_type {
+ OPERAND_CONSTANT,
+ OPERAND_TEXTURE,
+};
+
+/* This union structure describes a potential source or mask operand to the
+ * compositing equation.
+ */
+typedef struct cairo_gl_composite_operand {
+ enum cairo_gl_composite_operand_type type;
+ union {
+ struct {
+ GLuint tex;
+ cairo_gl_surface_t *surface;
+ cairo_surface_attributes_t attributes;
+ cairo_bool_t has_alpha;
+ } texture;
+ struct {
+ GLfloat color[4];
+ } constant;
+ } operand;
+
+ const cairo_pattern_t *pattern;
+} cairo_gl_composite_operand_t;
+
+typedef struct _cairo_gl_composite_setup {
+ cairo_gl_composite_operand_t src;
+ cairo_gl_composite_operand_t mask;
+} cairo_gl_composite_setup_t;
+
+extern const cairo_surface_backend_t _cairo_gl_surface_backend;
+
cairo_private cairo_gl_context_t *
_cairo_gl_context_create_in_error (cairo_status_t status);
@@ -88,4 +147,60 @@ _cairo_gl_surface_init (cairo_gl_context_t *ctx,
cairo_content_t content,
int width, int height);
+cairo_status_t
+_cairo_gl_surface_draw_image (cairo_gl_surface_t *dst,
+ cairo_image_surface_t *src,
+ int src_x, int src_y,
+ int width, int height,
+ int dst_x, int dst_y);
+
+cairo_int_status_t
+_cairo_gl_operand_init (cairo_gl_composite_operand_t *operand,
+ const cairo_pattern_t *pattern,
+ cairo_gl_surface_t *dst,
+ int src_x, int src_y,
+ int dst_x, int dst_y,
+ int width, int height);
+
+cairo_gl_context_t *
+_cairo_gl_context_acquire (cairo_gl_context_t *ctx);
+
+void
+_cairo_gl_context_release (cairo_gl_context_t *ctx);
+
+void
+_cairo_gl_set_destination (cairo_gl_surface_t *surface);
+
+int
+_cairo_gl_set_operator (cairo_gl_surface_t *dst, cairo_operator_t op);
+
+void
+_cairo_gl_set_src_operand (cairo_gl_context_t *ctx,
+ cairo_gl_composite_setup_t *setup);
+
+void
+_cairo_gl_operand_destroy (cairo_gl_composite_operand_t *operand);
+
+cairo_status_t
+_cairo_gl_get_image_format_and_type (pixman_format_code_t pixman_format,
+ GLenum *internal_format, GLenum *format,
+ GLenum *type, cairo_bool_t *has_alpha);
+
+void
+_cairo_gl_surface_scaled_glyph_fini (cairo_scaled_glyph_t *scaled_glyph,
+ cairo_scaled_font_t *scaled_font);
+
+cairo_int_status_t
+_cairo_gl_surface_show_glyphs (void *abstract_dst,
+ cairo_operator_t op,
+ const cairo_pattern_t *source,
+ cairo_glyph_t *glyphs,
+ int num_glyphs,
+ cairo_scaled_font_t *scaled_font,
+ cairo_clip_t *clip,
+ int *remaining_glyphs);
+
+void
+_cairo_gl_glyph_cache_fini (cairo_gl_glyph_cache_t *cache);
+
#endif /* CAIRO_GL_PRIVATE_H */
diff --git a/src/cairo-gl-surface.c b/src/cairo-gl-surface.c
index 92c670d3..7055bc7f 100644
--- a/src/cairo-gl-surface.c
+++ b/src/cairo-gl-surface.c
@@ -59,38 +59,6 @@ int_as_float (uint32_t val)
return fi.f;
}
-enum cairo_gl_composite_operand_type {
- OPERAND_CONSTANT,
- OPERAND_TEXTURE,
-};
-
-/* This union structure describes a potential source or mask operand to the
- * compositing equation.
- */
-typedef struct cairo_gl_composite_operand {
- enum cairo_gl_composite_operand_type type;
- union {
- struct {
- GLuint tex;
- cairo_gl_surface_t *surface;
- cairo_surface_attributes_t attributes;
- cairo_bool_t has_alpha;
- } texture;
- struct {
- GLfloat color[4];
- } constant;
- } operand;
-
- const cairo_pattern_t *pattern;
-} cairo_gl_composite_operand_t;
-
-typedef struct _cairo_gl_composite_setup {
- cairo_gl_composite_operand_t src;
- cairo_gl_composite_operand_t mask;
-} cairo_gl_composite_setup_t;
-
-static const cairo_surface_backend_t _cairo_gl_surface_backend;
-
static const cairo_gl_context_t _nil_context = {
CAIRO_REFERENCE_COUNT_INVALID,
CAIRO_STATUS_NO_MEMORY
@@ -118,6 +86,8 @@ _cairo_gl_context_init (cairo_gl_context_t *ctx)
CAIRO_REFERENCE_COUNT_INIT (&ctx->ref_count, 1);
CAIRO_MUTEX_INIT (ctx->mutex);
+ memset (ctx->glyph_cache, 0, sizeof (ctx->glyph_cache));
+
if (glewInit () != GLEW_OK)
return _cairo_error (CAIRO_STATUS_INVALID_FORMAT); /* XXX */
@@ -173,6 +143,8 @@ slim_hidden_def (cairo_gl_context_reference);
void
cairo_gl_context_destroy (cairo_gl_context_t *context)
{
+ int n;
+
if (context == NULL ||
CAIRO_REFERENCE_COUNT_IS_INVALID (&context->ref_count))
{
@@ -185,20 +157,23 @@ cairo_gl_context_destroy (cairo_gl_context_t *context)
glDeleteTextures (1, &context->dummy_tex);
+ for (n = 0; n < ARRAY_LENGTH (context->glyph_cache); n++)
+ _cairo_gl_glyph_cache_fini (&context->glyph_cache[n]);
+
context->destroy (context);
free (context);
}
slim_hidden_def (cairo_gl_context_destroy);
-static cairo_gl_context_t *
+cairo_gl_context_t *
_cairo_gl_context_acquire (cairo_gl_context_t *ctx)
{
CAIRO_MUTEX_LOCK (ctx->mutex);
return ctx;
}
-static cairo_status_t
+cairo_status_t
_cairo_gl_get_image_format_and_type (pixman_format_code_t pixman_format,
GLenum *internal_format, GLenum *format,
GLenum *type, cairo_bool_t *has_alpha)
@@ -321,13 +296,13 @@ _cairo_gl_get_image_format_and_type (pixman_format_code_t pixman_format,
}
}
-static void
+void
_cairo_gl_context_release (cairo_gl_context_t *ctx)
{
CAIRO_MUTEX_UNLOCK (ctx->mutex);
}
-static void
+void
_cairo_gl_set_destination (cairo_gl_surface_t *surface)
{
cairo_gl_context_t *ctx = surface->ctx;
@@ -360,7 +335,7 @@ _cairo_gl_set_destination (cairo_gl_surface_t *surface)
glLoadIdentity ();
}
-static int
+int
_cairo_gl_set_operator (cairo_gl_surface_t *dst, cairo_operator_t op)
{
struct {
@@ -401,6 +376,7 @@ _cairo_gl_set_operator (cairo_gl_surface_t *dst, cairo_operator_t op)
src_factor = GL_ONE;
}
+ glEnable (GL_BLEND);
glBlendFunc (src_factor, dst_factor);
return CAIRO_STATUS_SUCCESS;
@@ -626,7 +602,7 @@ _cairo_gl_surface_create_similar (void *abstract_surface,
return cairo_gl_surface_create (surface->ctx, content, width, height);
}
-static cairo_status_t
+cairo_status_t
_cairo_gl_surface_draw_image (cairo_gl_surface_t *dst,
cairo_image_surface_t *src,
int src_x, int src_y,
@@ -1131,7 +1107,7 @@ _cairo_gl_pattern_texture_setup (cairo_gl_composite_operand_t *operand,
return CAIRO_STATUS_SUCCESS;
}
-static cairo_int_status_t
+cairo_int_status_t
_cairo_gl_operand_init (cairo_gl_composite_operand_t *operand,
const cairo_pattern_t *pattern,
cairo_gl_surface_t *dst,
@@ -1164,7 +1140,7 @@ _cairo_gl_operand_init (cairo_gl_composite_operand_t *operand,
}
}
-static void
+void
_cairo_gl_operand_destroy (cairo_gl_composite_operand_t *operand)
{
switch (operand->type) {
@@ -1218,7 +1194,7 @@ _cairo_gl_set_tex_combine_constant_color (cairo_gl_context_t *ctx, int tex_unit,
}
}
-static void
+void
_cairo_gl_set_src_operand (cairo_gl_context_t *ctx,
cairo_gl_composite_setup_t *setup)
{
@@ -1323,8 +1299,6 @@ _cairo_gl_surface_composite (cairo_operator_t op,
return status;
}
- glEnable (GL_BLEND);
-
_cairo_gl_set_src_operand (ctx, &setup);
if (mask != NULL) {
@@ -1558,7 +1532,6 @@ _cairo_gl_surface_fill_rectangles (void *abstract_surface,
vertices[i * 8 + 7] = rects[i].y + rects[i].height;
}
- glEnable (GL_BLEND);
glVertexPointer (2, GL_FLOAT, sizeof (GLfloat)*2, vertices);
glEnableClientState (GL_VERTEX_ARRAY);
glColorPointer (4, GL_FLOAT, sizeof (GLfloat)*4, colors);
@@ -1952,8 +1925,6 @@ _cairo_gl_surface_create_span_renderer (cairo_operator_t op,
return _cairo_span_renderer_create_in_error (status);
}
- glEnable (GL_BLEND);
-
_cairo_gl_set_src_operand (dst->ctx, &renderer->setup);
/* Set up the mask to source from the incoming vertex color. */
@@ -2027,7 +1998,7 @@ _cairo_gl_surface_paint (void *abstract_surface,
return CAIRO_INT_STATUS_UNSUPPORTED;
}
-static const cairo_surface_backend_t _cairo_gl_surface_backend = {
+const cairo_surface_backend_t _cairo_gl_surface_backend = {
CAIRO_SURFACE_TYPE_GL,
_cairo_gl_surface_create_similar,
_cairo_gl_surface_finish,
@@ -2052,12 +2023,12 @@ static const cairo_surface_backend_t _cairo_gl_surface_backend = {
NULL, /* flush */
NULL, /* mark_dirty_rectangle */
NULL, /* scaled_font_fini */
- NULL, /* scaled_glyph_fini */
+ _cairo_gl_surface_scaled_glyph_fini,
_cairo_gl_surface_paint,
NULL, /* mask */
NULL, /* stroke */
NULL, /* fill */
- NULL, /* show_glyphs */
+ _cairo_gl_surface_show_glyphs, /* show_glyphs */
NULL /* snapshot */
};