0,0 → 1,503 |
/* Cairo - a vector graphics library with display and print output |
* |
* Copyright © 2009 Chris Wilson |
* Copyright © 2010 Intel Corporation |
* Copyright © 2010 Red Hat, Inc |
* |
* This library is free software; you can redistribute it and/or |
* modify it either under the terms of the GNU Lesser General Public |
* License version 2.1 as published by the Free Software Foundation |
* (the "LGPL") or, at your option, under the terms of the Mozilla |
* Public License Version 1.1 (the "MPL"). If you do not alter this |
* notice, a recipient may use your version of this file under either |
* the MPL or the LGPL. |
* |
* You should have received a copy of the LGPL along with this library |
* in the file COPYING-LGPL-2.1; if not, write to the Free Software |
* Foundation, Inc., 51 Franklin Street, Suite 500, Boston, MA 02110-1335, USA |
* You should have received a copy of the MPL along with this library |
* in the file COPYING-MPL-1.1 |
* |
* The contents of this file are subject to the Mozilla Public License |
* Version 1.1 (the "License"); you may not use this file except in |
* compliance with the License. You may obtain a copy of the License at |
* http://www.mozilla.org/MPL/ |
* |
* This software is distributed on an "AS IS" basis, WITHOUT WARRANTY |
* OF ANY KIND, either express or implied. See the LGPL or the MPL for |
* the specific language governing rights and limitations. |
* |
* The Original Code is the cairo graphics library. |
* |
* The Initial Developer of the Original Code is Chris Wilson. |
* |
* Contributors: |
* Benjamin Otte <otte@gnome.org> |
* Chris Wilson <chris@chris-wilson.co.uk> |
*/ |
|
#include "cairoint.h" |
|
#include "cairo-gl-private.h" |
|
#include "cairo-compositor-private.h" |
#include "cairo-composite-rectangles-private.h" |
#include "cairo-error-private.h" |
#include "cairo-image-surface-private.h" |
#include "cairo-rtree-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 { |
cairo_rtree_node_t node; |
cairo_scaled_glyph_private_t base; |
cairo_scaled_glyph_t *glyph; |
cairo_gl_glyph_cache_t *cache; |
struct { float x, y; } p1, p2; |
} cairo_gl_glyph_t; |
|
static void |
_cairo_gl_node_destroy (cairo_rtree_node_t *node) |
{ |
cairo_gl_glyph_t *priv = cairo_container_of (node, cairo_gl_glyph_t, node); |
cairo_scaled_glyph_t *glyph; |
|
glyph = priv->glyph; |
if (glyph == NULL) |
return; |
|
if (glyph->dev_private_key == priv->cache) { |
glyph->dev_private = NULL; |
glyph->dev_private_key = NULL; |
} |
cairo_list_del (&priv->base.link); |
priv->glyph = NULL; |
} |
|
static void |
_cairo_gl_glyph_fini (cairo_scaled_glyph_private_t *glyph_private, |
cairo_scaled_glyph_t *scaled_glyph, |
cairo_scaled_font_t *scaled_font) |
{ |
cairo_gl_glyph_t *priv = cairo_container_of (glyph_private, |
cairo_gl_glyph_t, |
base); |
|
assert (priv->glyph); |
|
_cairo_gl_node_destroy (&priv->node); |
|
/* XXX thread-safety? Probably ok due to the frozen scaled-font. */ |
if (! priv->node.pinned) |
_cairo_rtree_node_remove (&priv->cache->rtree, &priv->node); |
|
assert (priv->glyph == NULL); |
} |
|
static cairo_int_status_t |
_cairo_gl_glyph_cache_add_glyph (cairo_gl_context_t *ctx, |
cairo_gl_glyph_cache_t *cache, |
cairo_scaled_glyph_t *scaled_glyph) |
{ |
cairo_image_surface_t *glyph_surface = scaled_glyph->surface; |
cairo_gl_glyph_t *glyph_private; |
cairo_rtree_node_t *node = NULL; |
cairo_int_status_t status; |
int width, height; |
|
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 = _cairo_rtree_insert (&cache->rtree, width, height, &node); |
/* search for an unlocked slot */ |
if (status == CAIRO_INT_STATUS_UNSUPPORTED) { |
status = _cairo_rtree_evict_random (&cache->rtree, |
width, height, &node); |
if (status == CAIRO_INT_STATUS_SUCCESS) { |
status = _cairo_rtree_node_insert (&cache->rtree, |
node, width, height, &node); |
} |
} |
if (status) |
return status; |
|
/* XXX: Make sure we use the mask texture. This should work automagically somehow */ |
glActiveTexture (GL_TEXTURE1); |
status = _cairo_gl_surface_draw_image (cache->surface, glyph_surface, |
0, 0, |
glyph_surface->width, glyph_surface->height, |
node->x, node->y, FALSE); |
if (unlikely (status)) |
return status; |
|
glyph_private = (cairo_gl_glyph_t *) node; |
glyph_private->cache = cache; |
glyph_private->glyph = scaled_glyph; |
_cairo_scaled_glyph_attach_private (scaled_glyph, |
&glyph_private->base, |
cache, |
_cairo_gl_glyph_fini); |
|
scaled_glyph->dev_private = glyph_private; |
scaled_glyph->dev_private_key = cache; |
|
/* compute tex coords */ |
glyph_private->p1.x = node->x; |
glyph_private->p1.y = node->y; |
glyph_private->p2.x = node->x + glyph_surface->width; |
glyph_private->p2.y = node->y + glyph_surface->height; |
if (! _cairo_gl_device_requires_power_of_two_textures (&ctx->base)) { |
glyph_private->p1.x /= GLYPH_CACHE_WIDTH; |
glyph_private->p2.x /= GLYPH_CACHE_WIDTH; |
glyph_private->p1.y /= GLYPH_CACHE_HEIGHT; |
glyph_private->p2.y /= GLYPH_CACHE_HEIGHT; |
} |
|
return CAIRO_STATUS_SUCCESS; |
} |
|
static cairo_gl_glyph_t * |
_cairo_gl_glyph_cache_lock (cairo_gl_glyph_cache_t *cache, |
cairo_scaled_glyph_t *scaled_glyph) |
{ |
return _cairo_rtree_pin (&cache->rtree, scaled_glyph->dev_private); |
} |
|
static cairo_status_t |
cairo_gl_context_get_glyph_cache (cairo_gl_context_t *ctx, |
cairo_format_t format, |
cairo_gl_glyph_cache_t **cache_out) |
{ |
cairo_gl_glyph_cache_t *cache; |
cairo_content_t content; |
|
switch (format) { |
case CAIRO_FORMAT_RGB30: |
case CAIRO_FORMAT_RGB16_565: |
case CAIRO_FORMAT_ARGB32: |
case CAIRO_FORMAT_RGB24: |
cache = &ctx->glyph_cache[0]; |
content = CAIRO_CONTENT_COLOR_ALPHA; |
break; |
case CAIRO_FORMAT_A8: |
case CAIRO_FORMAT_A1: |
cache = &ctx->glyph_cache[1]; |
content = CAIRO_CONTENT_ALPHA; |
break; |
default: |
case CAIRO_FORMAT_INVALID: |
ASSERT_NOT_REACHED; |
return _cairo_error (CAIRO_STATUS_INVALID_FORMAT); |
} |
|
if (unlikely (cache->surface == NULL)) { |
cairo_surface_t *surface; |
|
surface = _cairo_gl_surface_create_scratch_for_caching (ctx, |
content, |
GLYPH_CACHE_WIDTH, |
GLYPH_CACHE_HEIGHT); |
if (unlikely (surface->status)) |
return surface->status; |
|
_cairo_surface_release_device_reference (surface); |
|
cache->surface = (cairo_gl_surface_t *)surface; |
cache->surface->operand.texture.attributes.has_component_alpha = |
content == CAIRO_CONTENT_COLOR_ALPHA; |
} |
|
*cache_out = cache; |
return CAIRO_STATUS_SUCCESS; |
} |
|
static cairo_status_t |
render_glyphs (cairo_gl_surface_t *dst, |
int dst_x, int dst_y, |
cairo_operator_t op, |
cairo_surface_t *source, |
cairo_composite_glyphs_info_t *info, |
cairo_bool_t *has_component_alpha, |
cairo_clip_t *clip) |
{ |
cairo_format_t last_format = CAIRO_FORMAT_INVALID; |
cairo_gl_glyph_cache_t *cache = NULL; |
cairo_gl_context_t *ctx; |
cairo_gl_emit_glyph_t emit = NULL; |
cairo_gl_composite_t setup; |
cairo_int_status_t status; |
int i = 0; |
|
TRACE ((stderr, "%s (%d, %d)x(%d, %d)\n", __FUNCTION__, |
info->extents.x, info->extents.y, |
info->extents.width, info->extents.height)); |
|
*has_component_alpha = FALSE; |
|
status = _cairo_gl_context_acquire (dst->base.device, &ctx); |
if (unlikely (status)) |
return status; |
|
status = _cairo_gl_composite_init (&setup, op, dst, TRUE); |
if (unlikely (status)) |
goto FINISH; |
|
if (source == NULL) { |
_cairo_gl_composite_set_solid_source (&setup, CAIRO_COLOR_WHITE); |
} else { |
_cairo_gl_composite_set_source_operand (&setup, |
source_to_operand (source)); |
|
} |
|
_cairo_gl_composite_set_clip (&setup, clip); |
|
for (i = 0; i < info->num_glyphs; i++) { |
cairo_scaled_glyph_t *scaled_glyph; |
cairo_gl_glyph_t *glyph; |
double x_offset, y_offset; |
double x1, x2, y1, y2; |
|
status = _cairo_scaled_glyph_lookup (info->font, |
info->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->format != last_format) { |
status = cairo_gl_context_get_glyph_cache (ctx, |
scaled_glyph->surface->format, |
&cache); |
if (unlikely (status)) |
goto FINISH; |
|
last_format = scaled_glyph->surface->format; |
|
_cairo_gl_composite_set_mask_operand (&setup, &cache->surface->operand); |
*has_component_alpha |= cache->surface->operand.texture.attributes.has_component_alpha; |
|
/* XXX Shoot me. */ |
status = _cairo_gl_composite_begin (&setup, &ctx); |
status = _cairo_gl_context_release (ctx, status); |
if (unlikely (status)) |
goto FINISH; |
|
emit = _cairo_gl_context_choose_emit_glyph (ctx); |
} |
|
if (scaled_glyph->dev_private_key != cache) { |
cairo_scaled_glyph_private_t *priv; |
|
priv = _cairo_scaled_glyph_find_private (scaled_glyph, cache); |
if (priv) { |
scaled_glyph->dev_private_key = cache; |
scaled_glyph->dev_private = cairo_container_of (priv, |
cairo_gl_glyph_t, |
base); |
} else { |
status = _cairo_gl_glyph_cache_add_glyph (ctx, cache, scaled_glyph); |
|
if (status == CAIRO_INT_STATUS_UNSUPPORTED) { |
/* Cache is full, so flush existing prims and try again. */ |
_cairo_gl_composite_flush (ctx); |
_cairo_gl_glyph_cache_unlock (cache); |
status = _cairo_gl_glyph_cache_add_glyph (ctx, cache, scaled_glyph); |
} |
|
if (unlikely (_cairo_int_status_is_error (status))) |
goto FINISH; |
} |
} |
|
x_offset = scaled_glyph->surface->base.device_transform.x0; |
y_offset = scaled_glyph->surface->base.device_transform.y0; |
|
x1 = _cairo_lround (info->glyphs[i].x - x_offset - dst_x); |
y1 = _cairo_lround (info->glyphs[i].y - y_offset - dst_y); |
x2 = x1 + scaled_glyph->surface->width; |
y2 = y1 + scaled_glyph->surface->height; |
|
glyph = _cairo_gl_glyph_cache_lock (cache, scaled_glyph); |
assert (emit); |
emit (ctx, |
x1, y1, x2, y2, |
glyph->p1.x, glyph->p1.y, |
glyph->p2.x, glyph->p2.y); |
} |
|
status = CAIRO_STATUS_SUCCESS; |
FINISH: |
status = _cairo_gl_context_release (ctx, status); |
|
_cairo_gl_composite_fini (&setup); |
return status; |
} |
|
static cairo_int_status_t |
render_glyphs_via_mask (cairo_gl_surface_t *dst, |
int dst_x, int dst_y, |
cairo_operator_t op, |
cairo_surface_t *source, |
cairo_composite_glyphs_info_t *info, |
cairo_clip_t *clip) |
{ |
cairo_surface_t *mask; |
cairo_status_t status; |
cairo_bool_t has_component_alpha; |
|
TRACE ((stderr, "%s\n", __FUNCTION__)); |
|
/* XXX: For non-CA, this should be CAIRO_CONTENT_ALPHA to save memory */ |
mask = cairo_gl_surface_create (dst->base.device, |
CAIRO_CONTENT_COLOR_ALPHA, |
info->extents.width, |
info->extents.height); |
if (unlikely (mask->status)) |
return mask->status; |
|
status = render_glyphs ((cairo_gl_surface_t *) mask, |
info->extents.x, info->extents.y, |
CAIRO_OPERATOR_ADD, NULL, |
info, &has_component_alpha, NULL); |
if (likely (status == CAIRO_STATUS_SUCCESS)) { |
cairo_surface_pattern_t mask_pattern; |
cairo_surface_pattern_t source_pattern; |
cairo_rectangle_int_t clip_extents; |
|
mask->is_clear = FALSE; |
_cairo_pattern_init_for_surface (&mask_pattern, mask); |
mask_pattern.base.has_component_alpha = has_component_alpha; |
mask_pattern.base.filter = CAIRO_FILTER_NEAREST; |
mask_pattern.base.extend = CAIRO_EXTEND_NONE; |
|
cairo_matrix_init_translate (&mask_pattern.base.matrix, |
dst_x-info->extents.x, dst_y-info->extents.y); |
|
_cairo_pattern_init_for_surface (&source_pattern, source); |
cairo_matrix_init_translate (&source_pattern.base.matrix, |
dst_x-info->extents.x, dst_y-info->extents.y); |
|
clip = _cairo_clip_copy (clip); |
clip_extents.x = info->extents.x - dst_x; |
clip_extents.y = info->extents.y - dst_y; |
clip_extents.width = info->extents.width; |
clip_extents.height = info->extents.height; |
clip = _cairo_clip_intersect_rectangle (clip, &clip_extents); |
|
status = _cairo_surface_mask (&dst->base, op, |
&source_pattern.base, |
&mask_pattern.base, |
clip); |
|
_cairo_clip_destroy (clip); |
|
_cairo_pattern_fini (&mask_pattern.base); |
_cairo_pattern_fini (&source_pattern.base); |
} |
|
cairo_surface_destroy (mask); |
|
return status; |
} |
|
cairo_int_status_t |
_cairo_gl_check_composite_glyphs (const cairo_composite_rectangles_t *extents, |
cairo_scaled_font_t *scaled_font, |
cairo_glyph_t *glyphs, |
int *num_glyphs) |
{ |
if (! _cairo_gl_operator_is_supported (extents->op)) |
return UNSUPPORTED ("unsupported operator"); |
|
/* XXX use individual masks for large glyphs? */ |
if (ceil (scaled_font->max_scale) >= GLYPH_CACHE_MAX_SIZE) |
return UNSUPPORTED ("glyphs too large"); |
|
return CAIRO_STATUS_SUCCESS; |
} |
|
cairo_int_status_t |
_cairo_gl_composite_glyphs_with_clip (void *_dst, |
cairo_operator_t op, |
cairo_surface_t *_src, |
int src_x, |
int src_y, |
int dst_x, |
int dst_y, |
cairo_composite_glyphs_info_t *info, |
cairo_clip_t *clip) |
{ |
cairo_gl_surface_t *dst = _dst; |
cairo_bool_t has_component_alpha; |
|
TRACE ((stderr, "%s\n", __FUNCTION__)); |
|
/* If any of the glyphs require component alpha, we have to go through |
* a mask, since only _cairo_gl_surface_composite() currently supports |
* component alpha. |
*/ |
if (!dst->base.is_clear && ! info->use_mask && op != CAIRO_OPERATOR_OVER && |
(info->font->options.antialias == CAIRO_ANTIALIAS_SUBPIXEL || |
info->font->options.antialias == CAIRO_ANTIALIAS_BEST)) |
{ |
info->use_mask = TRUE; |
} |
|
if (info->use_mask) { |
return render_glyphs_via_mask (dst, dst_x, dst_y, |
op, _src, info, clip); |
} else { |
return render_glyphs (dst, dst_x, dst_y, |
op, _src, info, |
&has_component_alpha, |
clip); |
} |
|
} |
|
cairo_int_status_t |
_cairo_gl_composite_glyphs (void *_dst, |
cairo_operator_t op, |
cairo_surface_t *_src, |
int src_x, |
int src_y, |
int dst_x, |
int dst_y, |
cairo_composite_glyphs_info_t *info) |
{ |
return _cairo_gl_composite_glyphs_with_clip (_dst, op, _src, src_x, src_y, |
dst_x, dst_y, info, NULL); |
} |
|
void |
_cairo_gl_glyph_cache_init (cairo_gl_glyph_cache_t *cache) |
{ |
_cairo_rtree_init (&cache->rtree, |
GLYPH_CACHE_WIDTH, |
GLYPH_CACHE_HEIGHT, |
GLYPH_CACHE_MIN_SIZE, |
sizeof (cairo_gl_glyph_t), |
_cairo_gl_node_destroy); |
} |
|
void |
_cairo_gl_glyph_cache_fini (cairo_gl_context_t *ctx, |
cairo_gl_glyph_cache_t *cache) |
{ |
_cairo_rtree_fini (&cache->rtree); |
cairo_surface_destroy (&cache->surface->base); |
} |