/* cairo - a vector graphics library with display and print output * * Copyright © 2009 Eric Anholt * Copyright © 2009 Chris Wilson * Copyright © 2005 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., 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 Red Hat, Inc. * * Contributor(s): * Carl Worth */ #include "cairoint.h" #include "cairo-gl-private.h" slim_hidden_proto (cairo_gl_context_reference); slim_hidden_proto (cairo_gl_context_destroy); #define BIAS .375 static inline float int_as_float (uint32_t val) { union fi { float f; uint32_t u; } fi; fi.u = val; return fi.f; } static const cairo_gl_context_t _nil_context = { CAIRO_REFERENCE_COUNT_INVALID, CAIRO_STATUS_NO_MEMORY }; static cairo_bool_t _cairo_surface_is_gl (cairo_surface_t *surface) { return surface->backend == &_cairo_gl_surface_backend; } cairo_gl_context_t * _cairo_gl_context_create_in_error (cairo_status_t status) { if (status == CAIRO_STATUS_NO_MEMORY) return (cairo_gl_context_t *) &_nil_context; ASSERT_NOT_REACHED; return NULL; } cairo_status_t _cairo_gl_context_init (cairo_gl_context_t *ctx) { int n; ctx->status = CAIRO_STATUS_SUCCESS; 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 */ if (! GLEW_EXT_framebuffer_object || ! GLEW_ARB_texture_env_combine || ! GLEW_ARB_texture_non_power_of_two) { fprintf (stderr, "Required GL extensions not available:\n"); if (! GLEW_EXT_framebuffer_object) fprintf (stderr, " GL_EXT_framebuffer_object\n"); if (! GLEW_ARB_texture_env_combine) fprintf (stderr, " GL_ARB_texture_env_combine\n"); if (! GLEW_ARB_texture_non_power_of_two) fprintf (stderr, " GL_ARB_texture_non_power_of_two\n"); return _cairo_error (CAIRO_STATUS_INVALID_FORMAT); /* XXX */ } /* Set up the dummy texture for tex_env_combine with constant color. */ glGenTextures (1, &ctx->dummy_tex); glBindTexture (GL_TEXTURE_2D, ctx->dummy_tex); glTexImage2D (GL_TEXTURE_2D, 0, GL_RGBA, 1, 1, 0, GL_RGBA, GL_UNSIGNED_BYTE, NULL); ctx->max_framebuffer_size = 0; glGetIntegerv (GL_MAX_RENDERBUFFER_SIZE, &ctx->max_framebuffer_size); ctx->max_texture_size = 0; glGetIntegerv (GL_MAX_TEXTURE_SIZE, &ctx->max_texture_size); for (n = 0; n < ARRAY_LENGTH (ctx->glyph_cache); n++) _cairo_gl_glyph_cache_init (&ctx->glyph_cache[n]); return CAIRO_STATUS_SUCCESS; } cairo_gl_context_t * cairo_gl_context_reference (cairo_gl_context_t *context) { if (context == NULL || CAIRO_REFERENCE_COUNT_IS_INVALID (&context->ref_count)) { return context; } assert (CAIRO_REFERENCE_COUNT_HAS_REFERENCE (&context->ref_count)); _cairo_reference_count_inc (&context->ref_count); return context; } 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)) { return; } assert (CAIRO_REFERENCE_COUNT_HAS_REFERENCE (&context->ref_count)); if (! _cairo_reference_count_dec_and_test (&context->ref_count)) return; 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); cairo_gl_context_t * _cairo_gl_context_acquire (cairo_gl_context_t *ctx) { CAIRO_MUTEX_LOCK (ctx->mutex); return ctx; } cairo_bool_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) { *has_alpha = TRUE; switch (pixman_format) { case PIXMAN_a8r8g8b8: *internal_format = GL_RGBA; *format = GL_BGRA; *type = GL_UNSIGNED_INT_8_8_8_8_REV; return TRUE; case PIXMAN_x8r8g8b8: *internal_format = GL_RGB; *format = GL_BGRA; *type = GL_UNSIGNED_INT_8_8_8_8_REV; *has_alpha = FALSE; return TRUE; case PIXMAN_a8b8g8r8: *internal_format = GL_RGBA; *format = GL_RGBA; *type = GL_UNSIGNED_INT_8_8_8_8_REV; return TRUE; case PIXMAN_x8b8g8r8: *internal_format = GL_RGB; *format = GL_RGBA; *type = GL_UNSIGNED_INT_8_8_8_8_REV; *has_alpha = FALSE; return TRUE; case PIXMAN_b8g8r8a8: *internal_format = GL_BGRA; *format = GL_BGRA; *type = GL_UNSIGNED_INT_8_8_8_8; return TRUE; case PIXMAN_b8g8r8x8: *internal_format = GL_RGB; *format = GL_BGRA; *type = GL_UNSIGNED_INT_8_8_8_8; *has_alpha = FALSE; return TRUE; case PIXMAN_r8g8b8: *internal_format = GL_RGB; *format = GL_RGB; *type = GL_UNSIGNED_BYTE; return TRUE; case PIXMAN_b8g8r8: *internal_format = GL_RGB; *format = GL_BGR; *type = GL_UNSIGNED_BYTE; return TRUE; case PIXMAN_r5g6b5: *internal_format = GL_RGB; *format = GL_RGB; *type = GL_UNSIGNED_SHORT_5_6_5; return TRUE; case PIXMAN_b5g6r5: *internal_format = GL_RGB; *format = GL_RGB; *type = GL_UNSIGNED_SHORT_5_6_5_REV; return TRUE; case PIXMAN_a1r5g5b5: *internal_format = GL_RGBA; *format = GL_BGRA; *type = GL_UNSIGNED_SHORT_1_5_5_5_REV; return TRUE; case PIXMAN_x1r5g5b5: *internal_format = GL_RGB; *format = GL_BGRA; *type = GL_UNSIGNED_SHORT_1_5_5_5_REV; *has_alpha = FALSE; return TRUE; case PIXMAN_a1b5g5r5: *internal_format = GL_RGBA; *format = GL_RGBA; *type = GL_UNSIGNED_SHORT_1_5_5_5_REV; return TRUE; case PIXMAN_x1b5g5r5: *internal_format = GL_RGB; *format = GL_RGBA; *type = GL_UNSIGNED_SHORT_1_5_5_5_REV; *has_alpha = FALSE; return TRUE; case PIXMAN_a8: *internal_format = GL_ALPHA; *format = GL_ALPHA; *type = GL_UNSIGNED_BYTE; return TRUE; case PIXMAN_a2b10g10r10: case PIXMAN_x2b10g10r10: case PIXMAN_a4r4g4b4: case PIXMAN_x4r4g4b4: case PIXMAN_a4b4g4r4: case PIXMAN_x4b4g4r4: case PIXMAN_r3g3b2: case PIXMAN_b2g3r3: case PIXMAN_a2r2g2b2: case PIXMAN_a2b2g2r2: case PIXMAN_c8: case PIXMAN_x4a4: /* case PIXMAN_x4c4: */ case PIXMAN_x4g4: case PIXMAN_a4: case PIXMAN_r1g2b1: case PIXMAN_b1g2r1: case PIXMAN_a1r1g1b1: case PIXMAN_a1b1g1r1: case PIXMAN_c4: case PIXMAN_g4: case PIXMAN_a1: case PIXMAN_g1: case PIXMAN_yuy2: case PIXMAN_yv12: #if PIXMAN_VERSION >= PIXMAN_VERSION_ENCODE(0,15,16) case PIXMAN_x2r10g10b10: case PIXMAN_a2r10g10b10: #endif default: return FALSE; } } void _cairo_gl_context_release (cairo_gl_context_t *ctx) { CAIRO_MUTEX_UNLOCK (ctx->mutex); } void _cairo_gl_set_destination (cairo_gl_surface_t *surface) { cairo_gl_context_t *ctx = surface->ctx; if (ctx->current_target != surface) { ctx->current_target = surface; if (surface->fb) { glBindFramebufferEXT (GL_FRAMEBUFFER_EXT, surface->fb); glDrawBuffer (GL_COLOR_ATTACHMENT0_EXT); glReadBuffer (GL_COLOR_ATTACHMENT0_EXT); } else { ctx->make_current (ctx, surface); glBindFramebufferEXT (GL_FRAMEBUFFER_EXT, 0); glDrawBuffer (GL_BACK_LEFT); glReadBuffer (GL_BACK_LEFT); } } glViewport (0, 0, surface->width, surface->height); glMatrixMode (GL_PROJECTION); glLoadIdentity (); if (surface->fb) glOrtho (0, surface->width, 0, surface->height, -1.0, 1.0); else glOrtho (0, surface->width, surface->height, 0, -1.0, 1.0); glMatrixMode (GL_MODELVIEW); glLoadIdentity (); } cairo_bool_t _cairo_gl_operator_is_supported (cairo_operator_t op) { return op < CAIRO_OPERATOR_SATURATE; } void _cairo_gl_set_operator (cairo_gl_surface_t *dst, cairo_operator_t op) { struct { GLenum src; GLenum dst; } blend_factors[] = { { GL_ZERO, GL_ZERO }, /* Clear */ { GL_ONE, GL_ZERO }, /* Source */ { GL_ONE, GL_ONE_MINUS_SRC_ALPHA }, /* Over */ { GL_DST_ALPHA, GL_ZERO }, /* In */ { GL_ONE_MINUS_DST_ALPHA, GL_ZERO }, /* Out */ { GL_DST_ALPHA, GL_ONE_MINUS_SRC_ALPHA }, /* Atop */ { GL_ZERO, GL_ONE }, /* Dest */ { GL_ONE_MINUS_DST_ALPHA, GL_ONE }, /* DestOver */ { GL_ZERO, GL_SRC_ALPHA }, /* DestIn */ { GL_ZERO, GL_ONE_MINUS_SRC_ALPHA }, /* DestOut */ { GL_ONE_MINUS_DST_ALPHA, GL_SRC_ALPHA }, /* DestAtop */ { GL_ONE_MINUS_DST_ALPHA, GL_ONE_MINUS_SRC_ALPHA }, /* Xor */ { GL_ONE, GL_ONE }, /* Add */ }; GLenum src_factor, dst_factor; assert (op < ARRAY_LENGTH (blend_factors)); src_factor = blend_factors[op].src; dst_factor = blend_factors[op].dst; /* We may have a visual with alpha bits despite the user requesting * CAIRO_CONTENT_COLOR. So clear out those bits in that case. */ if (dst->base.content == CAIRO_CONTENT_COLOR) { if (src_factor == GL_ONE_MINUS_DST_ALPHA) src_factor = GL_ZERO; if (src_factor == GL_DST_ALPHA) src_factor = GL_ONE; } glEnable (GL_BLEND); glBlendFunc (src_factor, dst_factor); } static void _cairo_gl_set_texture_surface (int tex_unit, GLuint tex, cairo_surface_attributes_t *attributes) { glActiveTexture (GL_TEXTURE0 + tex_unit); glBindTexture (GL_TEXTURE_2D, tex); switch (attributes->extend) { case CAIRO_EXTEND_NONE: glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_BORDER); glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_BORDER); break; case CAIRO_EXTEND_PAD: glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); break; case CAIRO_EXTEND_REPEAT: glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT); glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT); break; case CAIRO_EXTEND_REFLECT: glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_MIRRORED_REPEAT); glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_MIRRORED_REPEAT); break; } switch (attributes->filter) { case CAIRO_FILTER_FAST: case CAIRO_FILTER_NEAREST: glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); break; case CAIRO_FILTER_GOOD: case CAIRO_FILTER_BEST: case CAIRO_FILTER_BILINEAR: glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); break; default: case CAIRO_FILTER_GAUSSIAN: ASSERT_NOT_REACHED; } glEnable (GL_TEXTURE_2D); } void _cairo_gl_surface_init (cairo_gl_context_t *ctx, cairo_gl_surface_t *surface, cairo_content_t content, int width, int height) { _cairo_surface_init (&surface->base, &_cairo_gl_surface_backend, content); surface->ctx = cairo_gl_context_reference (ctx); surface->width = width; surface->height = height; } cairo_surface_t * cairo_gl_surface_create (cairo_gl_context_t *ctx, cairo_content_t content, int width, int height) { cairo_gl_surface_t *surface; GLenum err, format; cairo_status_t status; if (!CAIRO_CONTENT_VALID (content)) return _cairo_surface_create_in_error (_cairo_error (CAIRO_STATUS_INVALID_CONTENT)); if (ctx == NULL) { return cairo_image_surface_create (_cairo_format_from_content (content), width, height); } if (ctx->status) return _cairo_surface_create_in_error (ctx->status); if (width > ctx->max_framebuffer_size || height > ctx->max_framebuffer_size) return _cairo_surface_create_in_error (_cairo_error (CAIRO_STATUS_INVALID_SIZE)); surface = calloc (1, sizeof (cairo_gl_surface_t)); if (unlikely (surface == NULL)) return _cairo_surface_create_in_error (_cairo_error (CAIRO_STATUS_NO_MEMORY)); _cairo_gl_surface_init (ctx, surface, content, width, height); switch (content) { default: ASSERT_NOT_REACHED; case CAIRO_CONTENT_COLOR_ALPHA: format = GL_RGBA; break; case CAIRO_CONTENT_ALPHA: format = GL_RGBA; break; case CAIRO_CONTENT_COLOR: format = GL_RGB; break; } /* Create the texture used to store the surface's data. */ glGenTextures (1, &surface->tex); glBindTexture (GL_TEXTURE_2D, surface->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, format, width, height, 0, format, GL_UNSIGNED_BYTE, NULL); /* Create a framebuffer object wrapping the texture so that we can render * to it. */ glGenFramebuffersEXT (1, &surface->fb); glBindFramebufferEXT (GL_FRAMEBUFFER_EXT, surface->fb); glFramebufferTexture2DEXT (GL_FRAMEBUFFER_EXT, GL_COLOR_ATTACHMENT0_EXT, GL_TEXTURE_2D, surface->tex, 0); while ((err = glGetError ())) { fprintf (stderr, "GL error in surface create: 0x%08x\n", err); } status = glCheckFramebufferStatusEXT (GL_FRAMEBUFFER_EXT); if (status != GL_FRAMEBUFFER_COMPLETE_EXT) fprintf (stderr, "destination is framebuffer incomplete\n"); /* Cairo surfaces start out initialized to transparent (black) */ ctx = _cairo_gl_context_acquire (surface->ctx); _cairo_gl_set_destination (surface); glClearColor (0.0, 0.0, 0.0, 0.0); glClear (GL_COLOR_BUFFER_BIT); _cairo_gl_context_release (ctx); return &surface->base; } slim_hidden_def (cairo_gl_surface_create); void cairo_gl_surface_set_size (cairo_surface_t *abstract_surface, int width, int height) { cairo_gl_surface_t *surface = (cairo_gl_surface_t *) abstract_surface; cairo_status_t status; if (! _cairo_surface_is_gl (abstract_surface) || surface->fb) { status = _cairo_surface_set_error (abstract_surface, CAIRO_STATUS_SURFACE_TYPE_MISMATCH); return; } surface->width = width; surface->height = height; } int cairo_gl_surface_get_width (cairo_surface_t *abstract_surface) { cairo_gl_surface_t *surface = (cairo_gl_surface_t *) abstract_surface; if (! _cairo_surface_is_gl (abstract_surface)) return 0; return surface->width; } int cairo_gl_surface_get_height (cairo_surface_t *abstract_surface) { cairo_gl_surface_t *surface = (cairo_gl_surface_t *) abstract_surface; if (! _cairo_surface_is_gl (abstract_surface)) return 0; return surface->height; } void cairo_gl_surface_swapbuffers (cairo_surface_t *abstract_surface) { cairo_gl_surface_t *surface = (cairo_gl_surface_t *) abstract_surface; cairo_status_t status; if (! _cairo_surface_is_gl (abstract_surface)) { status = _cairo_surface_set_error (abstract_surface, CAIRO_STATUS_SURFACE_TYPE_MISMATCH); return; } if (! surface->fb) surface->ctx->swap_buffers (surface->ctx, surface); } static cairo_surface_t * _cairo_gl_surface_create_similar (void *abstract_surface, cairo_content_t content, int width, int height) { cairo_gl_surface_t *surface = abstract_surface; assert (CAIRO_CONTENT_VALID (content)); if (width > surface->ctx->max_framebuffer_size || height > surface->ctx->max_framebuffer_size) { return NULL; } if (width < 1) width = 1; if (height < 1) height = 1; return cairo_gl_surface_create (surface->ctx, content, width, 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) { GLenum internal_format, format, type; cairo_bool_t has_alpha; cairo_image_surface_t *clone = NULL; int cpp; if (! _cairo_gl_get_image_format_and_type (src->pixman_format, &internal_format, &format, &type, &has_alpha)) { cairo_bool_t is_supported; clone = _cairo_image_surface_coerce (src, _cairo_format_from_content (src->base.content)); if (unlikely (clone->base.status)) return clone->base.status; is_supported = _cairo_gl_get_image_format_and_type (clone->pixman_format, &internal_format, &format, &type, &has_alpha); assert (is_supported); src = clone; } cpp = PIXMAN_FORMAT_BPP (src->pixman_format) / 8; glBindTexture (GL_TEXTURE_2D, dst->tex); glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); glPixelStorei (GL_UNPACK_ALIGNMENT, 1); glPixelStorei (GL_UNPACK_ROW_LENGTH, src->stride / cpp); glTexSubImage2D (GL_TEXTURE_2D, 0, dst_x, dst_y, width, height, format, GL_UNSIGNED_BYTE, src->data + src_y * src->stride + src_x * cpp); glPixelStorei (GL_UNPACK_ROW_LENGTH, 0); cairo_surface_destroy (&clone->base); return CAIRO_STATUS_SUCCESS; } static cairo_status_t _cairo_gl_surface_get_image (cairo_gl_surface_t *surface, cairo_rectangle_int_t *interest, cairo_image_surface_t **image_out, cairo_rectangle_int_t *rect_out) { cairo_image_surface_t *image; GLenum err; GLenum format, type; cairo_format_t cairo_format; unsigned int cpp; /* Want to use a switch statement here but the compiler gets whiny. */ if (surface->base.content == CAIRO_CONTENT_COLOR_ALPHA) { format = GL_BGRA; cairo_format = CAIRO_FORMAT_ARGB32; type = GL_UNSIGNED_INT_8_8_8_8_REV; cpp = 4; } else if (surface->base.content == CAIRO_CONTENT_COLOR) { format = GL_BGRA; cairo_format = CAIRO_FORMAT_RGB24; type = GL_UNSIGNED_INT_8_8_8_8_REV; cpp = 4; } else if (surface->base.content == CAIRO_CONTENT_ALPHA) { format = GL_ALPHA; cairo_format = CAIRO_FORMAT_A8; type = GL_UNSIGNED_BYTE; cpp = 1; } else { ASSERT_NOT_REACHED; return CAIRO_INT_STATUS_UNSUPPORTED; } image = (cairo_image_surface_t*) cairo_image_surface_create (cairo_format, interest->width, interest->height); if (unlikely (image->base.status)) return image->base.status; /* This is inefficient, as we'd rather just read the thing without making * it the destination. But then, this is the fallback path, so let's not * fall back instead. */ _cairo_gl_set_destination (surface); glPixelStorei (GL_PACK_ALIGNMENT, 1); glPixelStorei (GL_PACK_ROW_LENGTH, image->stride / cpp); glReadPixels (interest->x, interest->y, interest->width, interest->height, format, type, image->data); while ((err = glGetError ())) fprintf (stderr, "GL error 0x%08x\n", (int) err); *image_out = image; if (rect_out != NULL) *rect_out = *interest; return CAIRO_STATUS_SUCCESS; } static cairo_status_t _cairo_gl_surface_finish (void *abstract_surface) { cairo_gl_surface_t *surface = abstract_surface; glDeleteFramebuffersEXT (1, &surface->fb); glDeleteTextures (1, &surface->tex); if (surface->ctx->current_target == surface) surface->ctx->current_target = NULL; cairo_gl_context_destroy (surface->ctx); return CAIRO_STATUS_SUCCESS; } static cairo_status_t _cairo_gl_surface_acquire_source_image (void *abstract_surface, cairo_image_surface_t **image_out, void **image_extra) { cairo_gl_surface_t *surface = abstract_surface; cairo_rectangle_int_t extents; *image_extra = NULL; extents.x = extents.y = 0; extents.width = surface->width; extents.height = surface->height; return _cairo_gl_surface_get_image (surface, &extents, image_out, NULL); } static void _cairo_gl_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_gl_surface_acquire_dest_image (void *abstract_surface, cairo_rectangle_int_t *interest_rect, cairo_image_surface_t **image_out, cairo_rectangle_int_t *image_rect_out, void **image_extra) { cairo_gl_surface_t *surface = abstract_surface; *image_extra = NULL; return _cairo_gl_surface_get_image (surface, interest_rect, image_out, image_rect_out); } static void _cairo_gl_surface_release_dest_image (void *abstract_surface, cairo_rectangle_int_t *interest_rect, cairo_image_surface_t *image, cairo_rectangle_int_t *image_rect, void *image_extra) { cairo_status_t status; status = _cairo_gl_surface_draw_image (abstract_surface, image, 0, 0, image->width, image->height, image_rect->x, image_rect->y); /* as we created the image, its format should be directly applicable */ assert (status == CAIRO_STATUS_SUCCESS); cairo_surface_destroy (&image->base); } static cairo_status_t _cairo_gl_surface_clone_similar (void *abstract_surface, cairo_surface_t *src, cairo_content_t content, int src_x, int src_y, int width, int height, int *clone_offset_x, int *clone_offset_y, cairo_surface_t **clone_out) { cairo_gl_surface_t *surface = abstract_surface; if (src->backend == surface->base.backend) { *clone_offset_x = 0; *clone_offset_y = 0; *clone_out = cairo_surface_reference (src); return CAIRO_STATUS_SUCCESS; } else if (_cairo_surface_is_image (src)) { cairo_image_surface_t *image_src = (cairo_image_surface_t *)src; cairo_gl_surface_t *clone; cairo_status_t status; clone = (cairo_gl_surface_t *) _cairo_gl_surface_create_similar (&surface->base, content, width, height); if (clone == NULL) return CAIRO_INT_STATUS_UNSUPPORTED; if (clone->base.status) return clone->base.status; status = _cairo_gl_surface_draw_image (clone, image_src, src_x, src_y, width, height, 0, 0); if (status) { cairo_surface_destroy (&clone->base); return status; } *clone_out = &clone->base; *clone_offset_x = src_x; *clone_offset_y = src_y; return CAIRO_STATUS_SUCCESS; } return CAIRO_INT_STATUS_UNSUPPORTED; } /** Creates a cairo-gl pattern surface for the given trapezoids */ static cairo_status_t _cairo_gl_get_traps_pattern (cairo_gl_surface_t *dst, int dst_x, int dst_y, int width, int height, cairo_trapezoid_t *traps, int num_traps, cairo_antialias_t antialias, cairo_surface_pattern_t *pattern) { pixman_format_code_t pixman_format; pixman_image_t *image; cairo_surface_t *surface; int i; pixman_format = antialias != CAIRO_ANTIALIAS_NONE ? PIXMAN_a8 : PIXMAN_a1, image = pixman_image_create_bits (pixman_format, width, height, NULL, 0); if (unlikely (image == NULL)) return _cairo_error (CAIRO_STATUS_NO_MEMORY); for (i = 0; i < num_traps; i++) { pixman_trapezoid_t trap; trap.top = _cairo_fixed_to_16_16 (traps[i].top); trap.bottom = _cairo_fixed_to_16_16 (traps[i].bottom); trap.left.p1.x = _cairo_fixed_to_16_16 (traps[i].left.p1.x); trap.left.p1.y = _cairo_fixed_to_16_16 (traps[i].left.p1.y); trap.left.p2.x = _cairo_fixed_to_16_16 (traps[i].left.p2.x); trap.left.p2.y = _cairo_fixed_to_16_16 (traps[i].left.p2.y); trap.right.p1.x = _cairo_fixed_to_16_16 (traps[i].right.p1.x); trap.right.p1.y = _cairo_fixed_to_16_16 (traps[i].right.p1.y); trap.right.p2.x = _cairo_fixed_to_16_16 (traps[i].right.p2.x); trap.right.p2.y = _cairo_fixed_to_16_16 (traps[i].right.p2.y); pixman_rasterize_trapezoid (image, &trap, -dst_x, -dst_y); } surface = _cairo_image_surface_create_for_pixman_image (image, pixman_format); if (unlikely (surface->status)) { pixman_image_unref (image); return surface->status; } _cairo_pattern_init_for_surface (pattern, surface); cairo_surface_destroy (surface); return CAIRO_STATUS_SUCCESS; } static cairo_status_t _cairo_gl_pattern_surface_texture_setup (cairo_gl_composite_operand_t *operand, const cairo_surface_pattern_t *src, cairo_gl_surface_t *dst, int src_x, int src_y, int dst_x, int dst_y) { cairo_surface_t *source = src->surface; cairo_gl_surface_t *gl_surface; cairo_status_t status; cairo_matrix_t m; cairo_surface_attributes_t *attributes; if (source->backend != &_cairo_gl_surface_backend) { gl_surface = (cairo_gl_surface_t *) _cairo_surface_has_snapshot (source, &_cairo_gl_surface_backend, source->content); if (gl_surface == NULL) { cairo_image_surface_t *image; void *image_extra; status = _cairo_surface_acquire_source_image (source, &image, &image_extra); if (unlikely (status)) return status; gl_surface = (cairo_gl_surface_t *) _cairo_gl_surface_create_similar (&dst->base, image->base.content, image->width, image->height); if (gl_surface == NULL) { _cairo_surface_release_source_image (source, image, image_extra); return CAIRO_INT_STATUS_UNSUPPORTED; } if (unlikely (gl_surface->base.status)) { _cairo_surface_release_source_image (source, image, image_extra); return gl_surface->base.status; } status = _cairo_gl_surface_draw_image (gl_surface, image, 0, 0, image->width, image->height, 0, 0); _cairo_surface_release_source_image (source, image, image_extra); if (unlikely (status)) { cairo_surface_destroy (&gl_surface->base); return status; } status = _cairo_surface_attach_snapshot (source, &gl_surface->base, cairo_surface_destroy); if (unlikely (status)) { cairo_surface_destroy (&gl_surface->base); return status; } /* XXX consider placing a bound on snapshot caches */ } } else { gl_surface = (cairo_gl_surface_t *) source; } operand->operand.texture.surface = NULL; operand->operand.texture.tex = gl_surface->tex; switch (gl_surface->base.content) { case CAIRO_CONTENT_ALPHA: case CAIRO_CONTENT_COLOR_ALPHA: operand->operand.texture.has_alpha = TRUE; break; case CAIRO_CONTENT_COLOR: operand->operand.texture.has_alpha = FALSE; break; } attributes = &operand->operand.texture.attributes; operand->type = OPERAND_TEXTURE; operand->operand.texture.tex = gl_surface->tex; operand->operand.texture.surface = NULL; attributes->matrix = src->base.matrix; attributes->extend = src->base.extend; attributes->filter = src->base.filter; /* Demote the filter if we're doing a 1:1 mapping of pixels. */ if ((attributes->filter == CAIRO_FILTER_GOOD || attributes->filter == CAIRO_FILTER_BEST || attributes->filter == CAIRO_FILTER_BILINEAR) && _cairo_matrix_is_pixel_exact (&attributes->matrix)) { attributes->filter = CAIRO_FILTER_NEAREST; } attributes->x_offset = 0; attributes->y_offset = 0; /* Set up translation matrix for * (unnormalized dst -> unnormalized src) */ cairo_matrix_init_translate (&m, src_x - dst_x, src_y - dst_y); cairo_matrix_multiply (&attributes->matrix, &m, &attributes->matrix); /* Translate the matrix from * (unnormalized src -> unnormalized src) to * (unnormalized dst -> normalized src) */ cairo_matrix_init_scale (&m, 1.0 / gl_surface->width, 1.0 / gl_surface->height); cairo_matrix_multiply (&attributes->matrix, &attributes->matrix, &m); return CAIRO_STATUS_SUCCESS; } /** * Like cairo_pattern_acquire_surface(), but returns a matrix that transforms * from dest to src coords. */ static cairo_status_t _cairo_gl_pattern_texture_setup (cairo_gl_composite_operand_t *operand, const cairo_pattern_t *src, cairo_gl_surface_t *dst, int src_x, int src_y, int dst_x, int dst_y, int width, int height) { cairo_status_t status; cairo_matrix_t m; cairo_gl_surface_t *surface; cairo_surface_attributes_t *attributes; attributes = &operand->operand.texture.attributes; /* First, try to just upload it to a texture if it's a surface. */ if (src->type == CAIRO_PATTERN_TYPE_SURFACE) { status = _cairo_gl_pattern_surface_texture_setup (operand, (cairo_surface_pattern_t *) src, dst, src_x, src_y, dst_x, dst_y); if (status != CAIRO_INT_STATUS_UNSUPPORTED) return status; } status = _cairo_pattern_acquire_surface (src, &dst->base, CAIRO_CONTENT_COLOR_ALPHA, src_x, src_y, width, height, CAIRO_PATTERN_ACQUIRE_NONE, (cairo_surface_t **) &surface, attributes); if (unlikely (status)) return status; assert (surface->base.backend == &_cairo_gl_surface_backend); operand->operand.texture.surface = surface; operand->operand.texture.tex = surface->tex; switch (surface->base.content) { case CAIRO_CONTENT_ALPHA: case CAIRO_CONTENT_COLOR_ALPHA: operand->operand.texture.has_alpha = TRUE; break; case CAIRO_CONTENT_COLOR: operand->operand.texture.has_alpha = FALSE; break; } /* Translate the matrix from * (unnormalized src -> unnormalized src) to * (unnormalized dst -> unnormalized src) */ cairo_matrix_init_translate (&m, src_x - dst_x + attributes->x_offset, src_y - dst_y + attributes->y_offset); cairo_matrix_multiply (&attributes->matrix, &m, &attributes->matrix); /* Translate the matrix from * (unnormalized src -> unnormalized src) to * (unnormalized dst -> normalized src) */ cairo_matrix_init_scale (&m, 1.0 / surface->width, 1.0 / surface->height); cairo_matrix_multiply (&attributes->matrix, &attributes->matrix, &m); return CAIRO_STATUS_SUCCESS; } static cairo_status_t _cairo_gl_solid_operand_init (cairo_gl_composite_operand_t *operand, const cairo_color_t *color) { operand->type = OPERAND_CONSTANT; operand->operand.constant.color[0] = color->red * color->alpha; operand->operand.constant.color[1] = color->green * color->alpha; operand->operand.constant.color[2] = color->blue * color->alpha; operand->operand.constant.color[3] = color->alpha; return CAIRO_STATUS_SUCCESS; } 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) { operand->pattern = pattern; switch (pattern->type) { case CAIRO_PATTERN_TYPE_SOLID: return _cairo_gl_solid_operand_init (operand, &((cairo_solid_pattern_t *) pattern)->color); case CAIRO_PATTERN_TYPE_LINEAR: case CAIRO_PATTERN_TYPE_RADIAL: { cairo_gradient_pattern_t *src = (cairo_gradient_pattern_t *) pattern; /* Fast path for gradients with less than 2 color stops. * Required to prevent _cairo_pattern_acquire_surface() returning * a solid color which is cached beyond the life of the context. */ if (src->n_stops < 2) { if (src->n_stops) { return _cairo_gl_solid_operand_init (operand, &src->stops->color); } else { return _cairo_gl_solid_operand_init (operand, CAIRO_COLOR_TRANSPARENT); } } else { unsigned int i; /* Is the gradient a uniform colour? * Happens more often than you would believe. */ for (i = 1; i < src->n_stops; i++) { if (! _cairo_color_equal (&src->stops[0].color, &src->stops[i].color)) { break; } } if (i == src->n_stops) { return _cairo_gl_solid_operand_init (operand, &src->stops->color); } } } /* fall through */ default: case CAIRO_PATTERN_TYPE_SURFACE: operand->type = OPERAND_TEXTURE; return _cairo_gl_pattern_texture_setup (operand, pattern, dst, src_x, src_y, dst_x, dst_y, width, height); } } void _cairo_gl_operand_destroy (cairo_gl_composite_operand_t *operand) { switch (operand->type) { case OPERAND_CONSTANT: break; case OPERAND_TEXTURE: if (operand->operand.texture.surface != NULL) { cairo_gl_surface_t *surface = operand->operand.texture.surface; _cairo_pattern_release_surface (operand->pattern, &surface->base, &operand->operand.texture.attributes); } break; } } static void _cairo_gl_set_tex_combine_constant_color (cairo_gl_context_t *ctx, int tex_unit, GLfloat *color) { glActiveTexture (GL_TEXTURE0 + tex_unit); /* Have to have a dummy texture bound in order to use the combiner unit. */ glBindTexture (GL_TEXTURE_2D, ctx->dummy_tex); glEnable (GL_TEXTURE_2D); glTexEnvfv (GL_TEXTURE_ENV, GL_TEXTURE_ENV_COLOR, color); glTexEnvi (GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_COMBINE); if (tex_unit == 0) { glTexEnvi (GL_TEXTURE_ENV, GL_COMBINE_RGB, GL_REPLACE); glTexEnvi (GL_TEXTURE_ENV, GL_COMBINE_ALPHA, GL_REPLACE); } else { 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_CONSTANT); glTexEnvi (GL_TEXTURE_ENV, GL_SRC0_ALPHA, GL_CONSTANT); if (tex_unit == 0) { glTexEnvi (GL_TEXTURE_ENV, GL_OPERAND0_RGB, GL_SRC_COLOR); glTexEnvi (GL_TEXTURE_ENV, GL_OPERAND0_ALPHA, GL_SRC_ALPHA); } else { glTexEnvi (GL_TEXTURE_ENV, GL_OPERAND0_RGB, GL_SRC_ALPHA); glTexEnvi (GL_TEXTURE_ENV, GL_OPERAND0_ALPHA, GL_SRC_ALPHA); glTexEnvi (GL_TEXTURE_ENV, GL_SRC1_RGB, GL_PREVIOUS); glTexEnvi (GL_TEXTURE_ENV, GL_SRC1_ALPHA, GL_PREVIOUS); glTexEnvi (GL_TEXTURE_ENV, GL_OPERAND1_RGB, GL_SRC_COLOR); glTexEnvi (GL_TEXTURE_ENV, GL_OPERAND1_ALPHA, GL_SRC_ALPHA); } } void _cairo_gl_set_src_operand (cairo_gl_context_t *ctx, cairo_gl_composite_setup_t *setup) { cairo_surface_attributes_t *src_attributes; GLfloat constant_color[4] = {0.0, 0.0, 0.0, 1.0}; src_attributes = &setup->src.operand.texture.attributes; switch (setup->src.type) { case OPERAND_CONSTANT: _cairo_gl_set_tex_combine_constant_color (ctx, 0, setup->src.operand.constant.color); break; case OPERAND_TEXTURE: _cairo_gl_set_texture_surface (0, setup->src.operand.texture.tex, src_attributes); /* Set up the constant color we use to set alpha to 1 if needed. */ glTexEnvfv (GL_TEXTURE_ENV, GL_TEXTURE_ENV_COLOR, constant_color); /* Set up the combiner to just set color to the sampled texture. */ glTexEnvi (GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_COMBINE); glTexEnvi (GL_TEXTURE_ENV, GL_COMBINE_RGB, GL_REPLACE); glTexEnvi (GL_TEXTURE_ENV, GL_COMBINE_ALPHA, GL_REPLACE); glTexEnvi (GL_TEXTURE_ENV, GL_SRC0_RGB, GL_TEXTURE0); /* Wire the src alpha to 1 if the surface doesn't have it. * We may have a teximage with alpha bits even though we didn't ask * for it and we don't pay attention to setting alpha to 1 in a dest * that has inadvertent alpha. */ if (setup->src.operand.texture.has_alpha) glTexEnvi (GL_TEXTURE_ENV, GL_SRC0_ALPHA, GL_TEXTURE0); else glTexEnvi (GL_TEXTURE_ENV, GL_SRC0_ALPHA, GL_CONSTANT); glTexEnvi (GL_TEXTURE_ENV, GL_OPERAND0_RGB, GL_SRC_COLOR); glTexEnvi (GL_TEXTURE_ENV, GL_OPERAND0_ALPHA, GL_SRC_ALPHA); break; } } static cairo_int_status_t _cairo_gl_surface_composite (cairo_operator_t op, const cairo_pattern_t *src, const cairo_pattern_t *mask, void *abstract_dst, int src_x, int src_y, int mask_x, int mask_y, int dst_x, int dst_y, unsigned int width, unsigned int height, cairo_region_t *clip_region) { cairo_gl_surface_t *dst = abstract_dst; cairo_surface_attributes_t *src_attributes, *mask_attributes = NULL; cairo_gl_context_t *ctx; struct gl_point { GLfloat x, y; } vertices_stack[8], texcoord_src_stack[8], texcoord_mask_stack[8]; struct gl_point *vertices = vertices_stack; struct gl_point *texcoord_src = texcoord_src_stack; struct gl_point *texcoord_mask = texcoord_mask_stack; cairo_status_t status; int num_vertices, i; GLenum err; cairo_gl_composite_setup_t setup; if (! _cairo_gl_operator_is_supported (op)) return CAIRO_INT_STATUS_UNSUPPORTED; memset (&setup, 0, sizeof (setup)); status = _cairo_gl_operand_init (&setup.src, src, dst, src_x, src_y, dst_x, dst_y, width, height); if (unlikely (status)) return status; src_attributes = &setup.src.operand.texture.attributes; if (mask != NULL && _cairo_pattern_is_opaque (mask)) mask = NULL; if (mask != NULL) { status = _cairo_gl_operand_init (&setup.mask, mask, dst, mask_x, mask_y, dst_x, dst_y, width, height); if (unlikely (status)) { _cairo_gl_operand_destroy (&setup.src); return status; } mask_attributes = &setup.mask.operand.texture.attributes; } ctx = _cairo_gl_context_acquire (dst->ctx); _cairo_gl_set_destination (dst); _cairo_gl_set_operator (dst, op); _cairo_gl_set_src_operand (ctx, &setup); if (mask != NULL) { switch (setup.mask.type) { case OPERAND_CONSTANT: _cairo_gl_set_tex_combine_constant_color (ctx, 1, setup.mask.operand.constant.color); break; case OPERAND_TEXTURE: _cairo_gl_set_texture_surface (1, setup.mask.operand.texture.tex, mask_attributes); /* 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); break; } } if (clip_region != NULL) { int num_rectangles; num_rectangles = cairo_region_num_rectangles (clip_region); if (num_rectangles * 4 > ARRAY_LENGTH (vertices_stack)) { vertices = _cairo_malloc_ab (num_rectangles, 4*3*sizeof (vertices[0])); if (unlikely (vertices == NULL)) { status = _cairo_error (CAIRO_STATUS_NO_MEMORY); goto CONTEXT_RELEASE; } texcoord_src = vertices + num_rectangles * 4; texcoord_mask = texcoord_src + num_rectangles * 4; } for (i = 0; i < num_rectangles; i++) { cairo_rectangle_int_t rect; cairo_region_get_rectangle (clip_region, i, &rect); vertices[4*i + 0].x = rect.x; vertices[4*i + 0].y = rect.y; vertices[4*i + 1].x = rect.x + rect.width; vertices[4*i + 1].y = rect.y; vertices[4*i + 2].x = rect.x + rect.width; vertices[4*i + 2].y = rect.y + rect.height; vertices[4*i + 3].x = rect.x; vertices[4*i + 3].y = rect.y + rect.height; } num_vertices = 4 * num_rectangles; } else { vertices[0].x = dst_x; vertices[0].y = dst_y; vertices[1].x = dst_x + width; vertices[1].y = dst_y; vertices[2].x = dst_x + width; vertices[2].y = dst_y + height; vertices[3].x = dst_x; vertices[3].y = dst_y + height; num_vertices = 4; } glVertexPointer (2, GL_FLOAT, sizeof (GLfloat) * 2, vertices); glEnableClientState (GL_VERTEX_ARRAY); if (setup.src.type == OPERAND_TEXTURE) { for (i = 0; i < num_vertices; i++) { double s, t; s = vertices[i].x; t = vertices[i].y; cairo_matrix_transform_point (&src_attributes->matrix, &s, &t); texcoord_src[i].x = s; texcoord_src[i].y = t; } glClientActiveTexture (GL_TEXTURE0); glTexCoordPointer (2, GL_FLOAT, sizeof (GLfloat)*2, texcoord_src); glEnableClientState (GL_TEXTURE_COORD_ARRAY); } if (mask != NULL) { if (setup.mask.type == OPERAND_TEXTURE) { for (i = 0; i < num_vertices; i++) { double s, t; s = vertices[i].x; t = vertices[i].y; cairo_matrix_transform_point (&mask_attributes->matrix, &s, &t); texcoord_mask[i].x = s; texcoord_mask[i].y = t; } glClientActiveTexture (GL_TEXTURE1); glTexCoordPointer (2, GL_FLOAT, sizeof (GLfloat)*2, texcoord_mask); glEnableClientState (GL_TEXTURE_COORD_ARRAY); } } glDrawArrays (GL_QUADS, 0, num_vertices); 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); while ((err = glGetError ())) fprintf (stderr, "GL error 0x%08x\n", (int) err); CONTEXT_RELEASE: _cairo_gl_context_release (ctx); _cairo_gl_operand_destroy (&setup.src); if (mask != NULL) _cairo_gl_operand_destroy (&setup.mask); if (vertices != vertices_stack) free (vertices); return status; } static cairo_int_status_t _cairo_gl_surface_composite_trapezoids (cairo_operator_t op, const cairo_pattern_t *pattern, void *abstract_dst, cairo_antialias_t antialias, int src_x, int src_y, int dst_x, int dst_y, unsigned int width, unsigned int height, cairo_trapezoid_t *traps, int num_traps, cairo_region_t *clip_region) { cairo_gl_surface_t *dst = abstract_dst; cairo_surface_pattern_t traps_pattern; cairo_int_status_t status; if (! _cairo_gl_operator_is_supported (op)) return CAIRO_INT_STATUS_UNSUPPORTED; if (_cairo_surface_check_span_renderer (op,pattern,&dst->base, antialias)) { status = _cairo_surface_composite_trapezoids_as_polygon (&dst->base, op, pattern, antialias, src_x, src_y, dst_x, dst_y, width, height, traps, num_traps, clip_region); if (status != CAIRO_INT_STATUS_UNSUPPORTED) return status; } status = _cairo_gl_get_traps_pattern (dst, dst_x, dst_y, width, height, traps, num_traps, antialias, &traps_pattern); if (unlikely (status)) return status; status = _cairo_gl_surface_composite (op, pattern, &traps_pattern.base, dst, src_x, src_y, 0, 0, dst_x, dst_y, width, height, clip_region); _cairo_pattern_fini (&traps_pattern.base); return status; } static cairo_int_status_t _cairo_gl_surface_fill_rectangles (void *abstract_surface, cairo_operator_t op, const cairo_color_t *color, cairo_rectangle_int_t *rects, int num_rects) { #define N_STACK_RECTS 4 cairo_gl_surface_t *surface = abstract_surface; GLfloat vertices_stack[N_STACK_RECTS*4*2]; GLfloat colors_stack[N_STACK_RECTS*4*4]; cairo_gl_context_t *ctx; int i; GLfloat *vertices; GLfloat *colors; if (! _cairo_gl_operator_is_supported (op)) return CAIRO_INT_STATUS_UNSUPPORTED; ctx = _cairo_gl_context_acquire (surface->ctx); _cairo_gl_set_destination (surface); _cairo_gl_set_operator (surface, op); if (num_rects > N_STACK_RECTS) { vertices = _cairo_malloc_ab (num_rects, sizeof (GLfloat) * 4 * 2); colors = _cairo_malloc_ab (num_rects, sizeof (GLfloat) * 4 * 4); if (!vertices || !colors) { _cairo_gl_context_release (ctx); free (vertices); free (colors); return _cairo_error (CAIRO_STATUS_NO_MEMORY); } } else { vertices = vertices_stack; colors = colors_stack; } /* This should be loaded in as either a blend constant and an operator * setup specific to this, or better, a fragment shader constant. */ colors[0] = color->red * color->alpha; colors[1] = color->green * color->alpha; colors[2] = color->blue * color->alpha; colors[3] = color->alpha; for (i = 1; i < num_rects * 4; i++) { colors[i*4 + 0] = colors[0]; colors[i*4 + 1] = colors[1]; colors[i*4 + 2] = colors[2]; colors[i*4 + 3] = colors[3]; } for (i = 0; i < num_rects; i++) { vertices[i * 8 + 0] = rects[i].x; vertices[i * 8 + 1] = rects[i].y; vertices[i * 8 + 2] = rects[i].x + rects[i].width; vertices[i * 8 + 3] = rects[i].y; vertices[i * 8 + 4] = rects[i].x + rects[i].width; vertices[i * 8 + 5] = rects[i].y + rects[i].height; vertices[i * 8 + 6] = rects[i].x; vertices[i * 8 + 7] = rects[i].y + rects[i].height; } glVertexPointer (2, GL_FLOAT, sizeof (GLfloat)*2, vertices); glEnableClientState (GL_VERTEX_ARRAY); glColorPointer (4, GL_FLOAT, sizeof (GLfloat)*4, colors); glEnableClientState (GL_COLOR_ARRAY); glDrawArrays (GL_QUADS, 0, 4 * num_rects); glDisableClientState (GL_COLOR_ARRAY); glDisableClientState (GL_VERTEX_ARRAY); glDisable (GL_BLEND); _cairo_gl_context_release (ctx); if (vertices != vertices_stack) free (vertices); if (colors != colors_stack) free (colors); return CAIRO_STATUS_SUCCESS; #undef N_STACK_RECTS } typedef struct _cairo_gl_surface_span_renderer { cairo_span_renderer_t base; cairo_gl_composite_setup_t setup; cairo_operator_t op; cairo_antialias_t antialias; cairo_gl_surface_t *dst; cairo_region_t *clip; cairo_composite_rectangles_t composite_rectangles; GLuint vbo; void *vbo_base; unsigned int vbo_size; unsigned int vbo_offset; unsigned int vertex_size; } cairo_gl_surface_span_renderer_t; static void _cairo_gl_span_renderer_flush (cairo_gl_surface_span_renderer_t *renderer) { int count; if (renderer->vbo_offset == 0) return; glUnmapBufferARB (GL_ARRAY_BUFFER_ARB); count = renderer->vbo_offset / renderer->vertex_size; renderer->vbo_offset = 0; if (renderer->clip) { int i, num_rectangles = cairo_region_num_rectangles (renderer->clip); glEnable (GL_SCISSOR_TEST); for (i = 0; i < num_rectangles; i++) { cairo_rectangle_int_t rect; cairo_region_get_rectangle (renderer->clip, i, &rect); glScissor (rect.x, rect.y, rect.width, rect.height); glDrawArrays (GL_LINES, 0, count); } glDisable (GL_SCISSOR_TEST); } else { glDrawArrays (GL_LINES, 0, count); } } static void * _cairo_gl_span_renderer_get_vbo (cairo_gl_surface_span_renderer_t *renderer, unsigned int num_vertices) { unsigned int offset; if (renderer->vbo == 0) { renderer->vbo_size = 16384; glGenBuffersARB (1, &renderer->vbo); glBindBufferARB (GL_ARRAY_BUFFER_ARB, renderer->vbo); if (renderer->setup.src.type == OPERAND_TEXTURE) renderer->vertex_size = 4 * sizeof (float) + sizeof (uint32_t); else renderer->vertex_size = 2 * sizeof (float) + sizeof (uint32_t); glVertexPointer (2, GL_FLOAT, renderer->vertex_size, 0); glEnableClientState (GL_VERTEX_ARRAY); glColorPointer (4, GL_UNSIGNED_BYTE, renderer->vertex_size, (void *) (uintptr_t) (2 * sizeof (float))); glEnableClientState (GL_COLOR_ARRAY); if (renderer->setup.src.type == OPERAND_TEXTURE) { glClientActiveTexture (GL_TEXTURE0); glTexCoordPointer (2, GL_FLOAT, renderer->vertex_size, (void *) (uintptr_t) (2 * sizeof (float) + sizeof (uint32_t))); glEnableClientState (GL_TEXTURE_COORD_ARRAY); } } if (renderer->vbo_offset + num_vertices * renderer->vertex_size > renderer->vbo_size) { _cairo_gl_span_renderer_flush (renderer); } if (renderer->vbo_offset == 0) { /* We'll only be using these vertices once. */ glBufferDataARB (GL_ARRAY_BUFFER_ARB, renderer->vbo_size, NULL, GL_STREAM_DRAW_ARB); renderer->vbo_base = glMapBufferARB (GL_ARRAY_BUFFER_ARB, GL_WRITE_ONLY_ARB); } offset = renderer->vbo_offset; renderer->vbo_offset += num_vertices * renderer->vertex_size; return (char *) renderer->vbo_base + offset; } static void _cairo_gl_emit_span_vertex (cairo_gl_surface_span_renderer_t *renderer, int dst_x, int dst_y, uint8_t alpha, float *vertices) { cairo_surface_attributes_t *src_attributes; int v = 0; src_attributes = &renderer->setup.src.operand.texture.attributes; vertices[v++] = dst_x + BIAS; vertices[v++] = dst_y + BIAS; vertices[v++] = int_as_float (alpha << 24); if (renderer->setup.src.type == OPERAND_TEXTURE) { double s, t; s = dst_x + BIAS; t = dst_y + BIAS; cairo_matrix_transform_point (&src_attributes->matrix, &s, &t); vertices[v++] = s; vertices[v++] = t; } } static void _cairo_gl_emit_span (cairo_gl_surface_span_renderer_t *renderer, int x1, int x2, int y, uint8_t alpha) { float *vertices = _cairo_gl_span_renderer_get_vbo (renderer, 2); _cairo_gl_emit_span_vertex (renderer, x1, y, alpha, vertices); _cairo_gl_emit_span_vertex (renderer, x2, y, alpha, vertices + renderer->vertex_size / 4); } /* Emits the contents of the span renderer rows as GL_LINES with the span's * alpha. * * Unlike the image surface, which is compositing into a temporary, we emit * coverage even for alpha == 0, in case we're using an unbounded operator. * But it means we avoid having to do the fixup. */ static cairo_status_t _cairo_gl_surface_span_renderer_render_row ( void *abstract_renderer, int y, const cairo_half_open_span_t *spans, unsigned num_spans) { cairo_gl_surface_span_renderer_t *renderer = abstract_renderer; int xmin = renderer->composite_rectangles.mask.x; int xmax = xmin + renderer->composite_rectangles.width; int prev_x = xmin; int prev_alpha = 0; unsigned i; int x_translate; /* Make sure we're within y-range. */ if (y < renderer->composite_rectangles.mask.y || y >= renderer->composite_rectangles.mask.y + renderer->composite_rectangles.height) return CAIRO_STATUS_SUCCESS; x_translate = renderer->composite_rectangles.dst.x - renderer->composite_rectangles.mask.x; y += renderer->composite_rectangles.dst.y - renderer->composite_rectangles.mask.y; /* Find the first span within x-range. */ for (i=0; i < num_spans && spans[i].x < xmin; i++) {} if (i>0) prev_alpha = spans[i-1].coverage; /* Set the intermediate spans. */ for (; i < num_spans; i++) { int x = spans[i].x; if (x >= xmax) break; _cairo_gl_emit_span (renderer, prev_x + x_translate, x + x_translate, y, prev_alpha); prev_x = x; prev_alpha = spans[i].coverage; } if (prev_x < xmax) { _cairo_gl_emit_span (renderer, prev_x + x_translate, xmax + x_translate, y, prev_alpha); } return CAIRO_STATUS_SUCCESS; } static void _cairo_gl_surface_span_renderer_destroy (void *abstract_renderer) { cairo_gl_surface_span_renderer_t *renderer = abstract_renderer; if (!renderer) return; _cairo_gl_operand_destroy (&renderer->setup.src); _cairo_gl_context_release (renderer->dst->ctx); free (renderer); } static cairo_status_t _cairo_gl_surface_span_renderer_finish (void *abstract_renderer) { cairo_gl_surface_span_renderer_t *renderer = abstract_renderer; _cairo_gl_span_renderer_flush (renderer); glBindBufferARB (GL_ARRAY_BUFFER_ARB, 0); glDeleteBuffersARB (1, &renderer->vbo); glDisableClientState (GL_VERTEX_ARRAY); glDisableClientState (GL_COLOR_ARRAY); glClientActiveTexture (GL_TEXTURE0); glDisableClientState (GL_TEXTURE_COORD_ARRAY); glActiveTexture (GL_TEXTURE0); glDisable (GL_TEXTURE_2D); glActiveTexture (GL_TEXTURE1); glDisable (GL_TEXTURE_2D); glDisable (GL_BLEND); return CAIRO_STATUS_SUCCESS; } static cairo_bool_t _cairo_gl_surface_check_span_renderer (cairo_operator_t op, const cairo_pattern_t *pattern, void *abstract_dst, cairo_antialias_t antialias) { if (! _cairo_gl_operator_is_supported (op)) return FALSE; if (! GLEW_ARB_vertex_buffer_object) return FALSE; return TRUE; (void) pattern; (void) abstract_dst; (void) antialias; } static cairo_span_renderer_t * _cairo_gl_surface_create_span_renderer (cairo_operator_t op, const cairo_pattern_t *src, void *abstract_dst, cairo_antialias_t antialias, const cairo_composite_rectangles_t *rects, cairo_region_t *clip_region) { cairo_gl_surface_t *dst = abstract_dst; cairo_gl_surface_span_renderer_t *renderer; cairo_status_t status; int width = rects->width; int height = rects->height; cairo_surface_attributes_t *src_attributes; GLenum err; renderer = calloc (1, sizeof (*renderer)); if (unlikely (renderer == NULL)) return _cairo_span_renderer_create_in_error (CAIRO_STATUS_NO_MEMORY); renderer->base.destroy = _cairo_gl_surface_span_renderer_destroy; renderer->base.finish = _cairo_gl_surface_span_renderer_finish; renderer->base.render_row = _cairo_gl_surface_span_renderer_render_row; renderer->op = op; renderer->antialias = antialias; renderer->dst = dst; renderer->clip = clip_region; renderer->composite_rectangles = *rects; status = _cairo_gl_operand_init (&renderer->setup.src, src, dst, rects->src.x, rects->src.y, rects->dst.x, rects->dst.y, width, height); if (unlikely (status)) { _cairo_gl_context_acquire (dst->ctx); _cairo_gl_surface_span_renderer_destroy (renderer); return _cairo_span_renderer_create_in_error (status); } _cairo_gl_context_acquire (dst->ctx); _cairo_gl_set_destination (dst); src_attributes = &renderer->setup.src.operand.texture.attributes; _cairo_gl_set_operator (dst, op); _cairo_gl_set_src_operand (dst->ctx, &renderer->setup); /* Set up the mask to source from the incoming vertex color. */ glActiveTexture (GL_TEXTURE1); /* Have to have a dummy texture bound in order to use the combiner unit. */ glBindTexture (GL_TEXTURE_2D, dst->ctx->dummy_tex); glEnable (GL_TEXTURE_2D); 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_PRIMARY_COLOR); glTexEnvi (GL_TEXTURE_ENV, GL_SRC1_ALPHA, GL_PRIMARY_COLOR); glTexEnvi (GL_TEXTURE_ENV, GL_OPERAND1_RGB, GL_SRC_ALPHA); glTexEnvi (GL_TEXTURE_ENV, GL_OPERAND1_ALPHA, GL_SRC_ALPHA); while ((err = glGetError ())) fprintf (stderr, "GL error 0x%08x\n", (int) err); return &renderer->base; } static cairo_bool_t _cairo_gl_surface_get_extents (void *abstract_surface, cairo_rectangle_int_t *rectangle) { cairo_gl_surface_t *surface = abstract_surface; rectangle->x = 0; rectangle->y = 0; rectangle->width = surface->width; rectangle->height = surface->height; return TRUE; } static void _cairo_gl_surface_get_font_options (void *abstract_surface, cairo_font_options_t *options) { _cairo_font_options_init_default (options); cairo_font_options_set_hint_metrics (options, CAIRO_HINT_METRICS_ON); } static cairo_int_status_t _cairo_gl_surface_paint (void *abstract_surface, cairo_operator_t op, const cairo_pattern_t *source, cairo_clip_t *clip) { /* simplify the common case of clearing the surface */ if (op == CAIRO_OPERATOR_CLEAR && clip == NULL) { cairo_gl_surface_t *surface = abstract_surface; cairo_gl_context_t *ctx; ctx = _cairo_gl_context_acquire (surface->ctx); _cairo_gl_set_destination (surface); glClear (GL_COLOR_BUFFER_BIT); _cairo_gl_context_release (ctx); return CAIRO_STATUS_SUCCESS; } return CAIRO_INT_STATUS_UNSUPPORTED; } const cairo_surface_backend_t _cairo_gl_surface_backend = { CAIRO_SURFACE_TYPE_GL, _cairo_gl_surface_create_similar, _cairo_gl_surface_finish, _cairo_gl_surface_acquire_source_image, _cairo_gl_surface_release_source_image, _cairo_gl_surface_acquire_dest_image, _cairo_gl_surface_release_dest_image, _cairo_gl_surface_clone_similar, _cairo_gl_surface_composite, _cairo_gl_surface_fill_rectangles, _cairo_gl_surface_composite_trapezoids, _cairo_gl_surface_create_span_renderer, _cairo_gl_surface_check_span_renderer, NULL, /* copy_page */ NULL, /* show_page */ _cairo_gl_surface_get_extents, NULL, /* old_show_glyphs */ _cairo_gl_surface_get_font_options, NULL, /* flush */ NULL, /* mark_dirty_rectangle */ NULL, /* scaled_font_fini */ _cairo_gl_surface_scaled_glyph_fini, _cairo_gl_surface_paint, NULL, /* mask */ NULL, /* stroke */ NULL, /* fill */ _cairo_gl_surface_show_glyphs, /* show_glyphs */ NULL /* snapshot */ }; /** Call glFinish(), used for accurate performance testing. */ cairo_status_t cairo_gl_surface_glfinish (cairo_surface_t *surface) { glFinish (); return CAIRO_STATUS_SUCCESS; }