0,0 → 1,1451 |
/* cairo - a vector graphics library with display and print output |
* |
* Copyright © 2009 Eric Anholt |
* Copyright © 2009 Chris Wilson |
* Copyright © 2005,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 Red Hat, Inc. |
* |
* Contributor(s): |
* Benjamin Otte <otte@gnome.org> |
* Carl Worth <cworth@cworth.org> |
* Chris Wilson <chris@chris-wilson.co.uk> |
* Eric Anholt <eric@anholt.net> |
*/ |
|
#include "cairoint.h" |
|
#include "cairo-gl-private.h" |
|
#include "cairo-composite-rectangles-private.h" |
#include "cairo-compositor-private.h" |
#include "cairo-default-context-private.h" |
#include "cairo-error-private.h" |
#include "cairo-image-surface-inline.h" |
#include "cairo-surface-backend-private.h" |
|
static const cairo_surface_backend_t _cairo_gl_surface_backend; |
|
static cairo_status_t |
_cairo_gl_surface_flush (void *abstract_surface, unsigned flags); |
|
static cairo_bool_t _cairo_surface_is_gl (cairo_surface_t *surface) |
{ |
return surface->backend == &_cairo_gl_surface_backend; |
} |
|
static cairo_bool_t |
_cairo_gl_get_image_format_and_type_gles2 (pixman_format_code_t pixman_format, |
GLenum *internal_format, GLenum *format, |
GLenum *type, cairo_bool_t *has_alpha, |
cairo_bool_t *needs_swap) |
{ |
cairo_bool_t is_little_endian = _cairo_is_little_endian (); |
|
*has_alpha = TRUE; |
|
switch ((int) pixman_format) { |
case PIXMAN_a8r8g8b8: |
*internal_format = GL_BGRA; |
*format = GL_BGRA; |
*type = GL_UNSIGNED_BYTE; |
*needs_swap = !is_little_endian; |
return TRUE; |
|
case PIXMAN_x8r8g8b8: |
*internal_format = GL_BGRA; |
*format = GL_BGRA; |
*type = GL_UNSIGNED_BYTE; |
*has_alpha = FALSE; |
*needs_swap = !is_little_endian; |
return TRUE; |
|
case PIXMAN_a8b8g8r8: |
*internal_format = GL_RGBA; |
*format = GL_RGBA; |
*type = GL_UNSIGNED_BYTE; |
*needs_swap = !is_little_endian; |
return TRUE; |
|
case PIXMAN_x8b8g8r8: |
*internal_format = GL_RGBA; |
*format = GL_RGBA; |
*type = GL_UNSIGNED_BYTE; |
*has_alpha = FALSE; |
*needs_swap = !is_little_endian; |
return TRUE; |
|
case PIXMAN_b8g8r8a8: |
*internal_format = GL_BGRA; |
*format = GL_BGRA; |
*type = GL_UNSIGNED_BYTE; |
*needs_swap = is_little_endian; |
return TRUE; |
|
case PIXMAN_b8g8r8x8: |
*internal_format = GL_BGRA; |
*format = GL_BGRA; |
*type = GL_UNSIGNED_BYTE; |
*has_alpha = FALSE; |
*needs_swap = is_little_endian; |
return TRUE; |
|
case PIXMAN_r8g8b8: |
*internal_format = GL_RGB; |
*format = GL_RGB; |
*type = GL_UNSIGNED_BYTE; |
*needs_swap = is_little_endian; |
return TRUE; |
|
case PIXMAN_b8g8r8: |
*internal_format = GL_RGB; |
*format = GL_RGB; |
*type = GL_UNSIGNED_BYTE; |
*needs_swap = !is_little_endian; |
return TRUE; |
|
case PIXMAN_r5g6b5: |
*internal_format = GL_RGB; |
*format = GL_RGB; |
*type = GL_UNSIGNED_SHORT_5_6_5; |
*needs_swap = FALSE; |
return TRUE; |
|
case PIXMAN_b5g6r5: |
*internal_format = GL_RGB; |
*format = GL_RGB; |
*type = GL_UNSIGNED_SHORT_5_6_5; |
*needs_swap = TRUE; |
return TRUE; |
|
case PIXMAN_a1b5g5r5: |
*internal_format = GL_RGBA; |
*format = GL_RGBA; |
*type = GL_UNSIGNED_SHORT_5_5_5_1; |
*needs_swap = TRUE; |
return TRUE; |
|
case PIXMAN_x1b5g5r5: |
*internal_format = GL_RGBA; |
*format = GL_RGBA; |
*type = GL_UNSIGNED_SHORT_5_5_5_1; |
*has_alpha = FALSE; |
*needs_swap = TRUE; |
return TRUE; |
|
case PIXMAN_a8: |
*internal_format = GL_ALPHA; |
*format = GL_ALPHA; |
*type = GL_UNSIGNED_BYTE; |
*needs_swap = FALSE; |
return TRUE; |
|
default: |
return FALSE; |
} |
} |
|
static cairo_bool_t |
_cairo_gl_get_image_format_and_type_gl (pixman_format_code_t pixman_format, |
GLenum *internal_format, GLenum *format, |
GLenum *type, cairo_bool_t *has_alpha, |
cairo_bool_t *needs_swap) |
{ |
*has_alpha = TRUE; |
*needs_swap = FALSE; |
|
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_RGBA; |
*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: |
case PIXMAN_x2r10g10b10: |
case PIXMAN_a2r10g10b10: |
case PIXMAN_r8g8b8x8: |
case PIXMAN_r8g8b8a8: |
case PIXMAN_x14r6g6b6: |
default: |
return FALSE; |
} |
} |
|
/* |
* Extracts pixel data from an image surface. |
*/ |
static cairo_status_t |
_cairo_gl_surface_extract_image_data (cairo_image_surface_t *image, |
int x, int y, |
int width, int height, |
void **output) |
{ |
int cpp = PIXMAN_FORMAT_BPP (image->pixman_format) / 8; |
char *data = _cairo_malloc_ab (width * height, cpp); |
char *dst = data; |
unsigned char *src = image->data + y * image->stride + x * cpp; |
int i; |
|
if (unlikely (data == NULL)) |
return CAIRO_STATUS_NO_MEMORY; |
|
for (i = 0; i < height; i++) { |
memcpy (dst, src, width * cpp); |
src += image->stride; |
dst += width * cpp; |
} |
|
*output = data; |
|
return CAIRO_STATUS_SUCCESS; |
} |
|
cairo_bool_t |
_cairo_gl_get_image_format_and_type (cairo_gl_flavor_t flavor, |
pixman_format_code_t pixman_format, |
GLenum *internal_format, GLenum *format, |
GLenum *type, cairo_bool_t *has_alpha, |
cairo_bool_t *needs_swap) |
{ |
if (flavor == CAIRO_GL_FLAVOR_DESKTOP) |
return _cairo_gl_get_image_format_and_type_gl (pixman_format, |
internal_format, format, |
type, has_alpha, |
needs_swap); |
else |
return _cairo_gl_get_image_format_and_type_gles2 (pixman_format, |
internal_format, format, |
type, has_alpha, |
needs_swap); |
|
} |
|
cairo_bool_t |
_cairo_gl_operator_is_supported (cairo_operator_t op) |
{ |
return op < CAIRO_OPERATOR_SATURATE; |
} |
|
static void |
_cairo_gl_surface_embedded_operand_init (cairo_gl_surface_t *surface) |
{ |
cairo_gl_operand_t *operand = &surface->operand; |
cairo_surface_attributes_t *attributes = &operand->texture.attributes; |
|
memset (operand, 0, sizeof (cairo_gl_operand_t)); |
|
operand->type = CAIRO_GL_OPERAND_TEXTURE; |
operand->texture.surface = surface; |
operand->texture.tex = surface->tex; |
|
if (_cairo_gl_device_requires_power_of_two_textures (surface->base.device)) { |
cairo_matrix_init_identity (&attributes->matrix); |
} else { |
cairo_matrix_init_scale (&attributes->matrix, |
1.0 / surface->width, |
1.0 / surface->height); |
} |
|
attributes->extend = CAIRO_EXTEND_NONE; |
attributes->filter = CAIRO_FILTER_NEAREST; |
} |
|
void |
_cairo_gl_surface_init (cairo_device_t *device, |
cairo_gl_surface_t *surface, |
cairo_content_t content, |
int width, int height) |
{ |
assert (width > 0 && height > 0); |
|
_cairo_surface_init (&surface->base, |
&_cairo_gl_surface_backend, |
device, |
content); |
|
surface->width = width; |
surface->height = height; |
surface->needs_update = FALSE; |
|
_cairo_gl_surface_embedded_operand_init (surface); |
} |
|
static cairo_bool_t |
_cairo_gl_surface_size_valid_for_context (cairo_gl_context_t *ctx, |
int width, int height) |
{ |
return width > 0 && height > 0 && |
width <= ctx->max_framebuffer_size && |
height <= ctx->max_framebuffer_size; |
} |
|
static cairo_bool_t |
_cairo_gl_surface_size_valid (cairo_gl_surface_t *surface, |
int width, int height) |
{ |
cairo_gl_context_t *ctx = (cairo_gl_context_t *)surface->base.device; |
return _cairo_gl_surface_size_valid_for_context (ctx, width, height); |
} |
|
static cairo_surface_t * |
_cairo_gl_surface_create_scratch_for_texture (cairo_gl_context_t *ctx, |
cairo_content_t content, |
GLuint tex, |
int width, |
int height) |
{ |
cairo_gl_surface_t *surface; |
|
surface = calloc (1, sizeof (cairo_gl_surface_t)); |
if (unlikely (surface == NULL)) |
return _cairo_surface_create_in_error (_cairo_error (CAIRO_STATUS_NO_MEMORY)); |
|
surface->tex = tex; |
_cairo_gl_surface_init (&ctx->base, surface, content, width, height); |
|
surface->supports_msaa = ctx->supports_msaa; |
surface->supports_stencil = TRUE; |
|
/* Create the texture used to store the surface's data. */ |
_cairo_gl_context_activate (ctx, CAIRO_GL_TEX_TEMP); |
glBindTexture (ctx->tex_target, surface->tex); |
glTexParameteri (ctx->tex_target, GL_TEXTURE_MIN_FILTER, GL_NEAREST); |
glTexParameteri (ctx->tex_target, GL_TEXTURE_MAG_FILTER, GL_NEAREST); |
|
return &surface->base; |
} |
|
static cairo_surface_t * |
_create_scratch_internal (cairo_gl_context_t *ctx, |
cairo_content_t content, |
int width, |
int height, |
cairo_bool_t for_caching) |
{ |
cairo_gl_surface_t *surface; |
GLenum format; |
GLuint tex; |
|
glGenTextures (1, &tex); |
surface = (cairo_gl_surface_t *) |
_cairo_gl_surface_create_scratch_for_texture (ctx, content, |
tex, width, height); |
if (unlikely (surface->base.status)) |
return &surface->base; |
|
surface->owns_tex = TRUE; |
|
/* adjust the texture size after setting our real extents */ |
if (width < 1) |
width = 1; |
if (height < 1) |
height = 1; |
|
switch (content) { |
default: |
ASSERT_NOT_REACHED; |
case CAIRO_CONTENT_COLOR_ALPHA: |
format = GL_RGBA; |
break; |
case CAIRO_CONTENT_ALPHA: |
/* When using GL_ALPHA, compositing doesn't work properly, but for |
* caching surfaces, we are just uploading pixel data, so it isn't |
* an issue. */ |
if (for_caching) |
format = GL_ALPHA; |
else |
format = GL_RGBA; |
break; |
case CAIRO_CONTENT_COLOR: |
/* GL_RGB is almost what we want here -- sampling 1 alpha when |
* texturing, using 1 as destination alpha factor in blending, |
* etc. However, when filtering with GL_CLAMP_TO_BORDER, the |
* alpha channel of the border color will also be clamped to |
* 1, when we actually want the border color we explicitly |
* specified. So, we have to store RGBA, and fill the alpha |
* channel with 1 when blending. |
*/ |
format = GL_RGBA; |
break; |
} |
|
glTexImage2D (ctx->tex_target, 0, format, width, height, 0, |
format, GL_UNSIGNED_BYTE, NULL); |
|
return &surface->base; |
} |
|
cairo_surface_t * |
_cairo_gl_surface_create_scratch (cairo_gl_context_t *ctx, |
cairo_content_t content, |
int width, |
int height) |
{ |
return _create_scratch_internal (ctx, content, width, height, FALSE); |
} |
|
cairo_surface_t * |
_cairo_gl_surface_create_scratch_for_caching (cairo_gl_context_t *ctx, |
cairo_content_t content, |
int width, |
int height) |
{ |
return _create_scratch_internal (ctx, content, width, height, TRUE); |
} |
|
static cairo_status_t |
_cairo_gl_surface_clear (cairo_gl_surface_t *surface, |
const cairo_color_t *color) |
{ |
cairo_gl_context_t *ctx; |
cairo_status_t status; |
double r, g, b, a; |
|
status = _cairo_gl_context_acquire (surface->base.device, &ctx); |
if (unlikely (status)) |
return status; |
|
_cairo_gl_context_set_destination (ctx, surface, surface->msaa_active); |
if (surface->base.content & CAIRO_CONTENT_COLOR) { |
r = color->red * color->alpha; |
g = color->green * color->alpha; |
b = color->blue * color->alpha; |
} else { |
r = g = b = 0; |
} |
if (surface->base.content & CAIRO_CONTENT_ALPHA) { |
a = color->alpha; |
} else { |
a = 1.0; |
} |
|
glDisable (GL_SCISSOR_TEST); |
glClearColor (r, g, b, a); |
glClear (GL_COLOR_BUFFER_BIT); |
|
if (a == 0) |
surface->base.is_clear = TRUE; |
|
return _cairo_gl_context_release (ctx, status); |
} |
|
static cairo_surface_t * |
_cairo_gl_surface_create_and_clear_scratch (cairo_gl_context_t *ctx, |
cairo_content_t content, |
int width, |
int height) |
{ |
cairo_gl_surface_t *surface; |
cairo_int_status_t status; |
|
surface = (cairo_gl_surface_t *) |
_cairo_gl_surface_create_scratch (ctx, content, width, height); |
if (unlikely (surface->base.status)) |
return &surface->base; |
|
/* Cairo surfaces start out initialized to transparent (black) */ |
status = _cairo_gl_surface_clear (surface, CAIRO_COLOR_TRANSPARENT); |
if (unlikely (status)) { |
cairo_surface_destroy (&surface->base); |
return _cairo_surface_create_in_error (status); |
} |
|
return &surface->base; |
} |
|
cairo_surface_t * |
cairo_gl_surface_create (cairo_device_t *abstract_device, |
cairo_content_t content, |
int width, |
int height) |
{ |
cairo_gl_context_t *ctx; |
cairo_gl_surface_t *surface; |
cairo_status_t status; |
|
if (! CAIRO_CONTENT_VALID (content)) |
return _cairo_surface_create_in_error (_cairo_error (CAIRO_STATUS_INVALID_CONTENT)); |
|
if (abstract_device == NULL) |
return _cairo_image_surface_create_with_content (content, width, height); |
|
if (abstract_device->status) |
return _cairo_surface_create_in_error (abstract_device->status); |
|
if (abstract_device->backend->type != CAIRO_DEVICE_TYPE_GL) |
return _cairo_surface_create_in_error (_cairo_error (CAIRO_STATUS_SURFACE_TYPE_MISMATCH)); |
|
status = _cairo_gl_context_acquire (abstract_device, &ctx); |
if (unlikely (status)) |
return _cairo_surface_create_in_error (status); |
|
if (! _cairo_gl_surface_size_valid_for_context (ctx, width, height)) { |
status = _cairo_gl_context_release (ctx, status); |
return _cairo_surface_create_in_error (_cairo_error (CAIRO_STATUS_INVALID_SIZE)); |
} |
|
surface = (cairo_gl_surface_t *) |
_cairo_gl_surface_create_and_clear_scratch (ctx, content, width, height); |
if (unlikely (surface->base.status)) { |
status = _cairo_gl_context_release (ctx, surface->base.status); |
cairo_surface_destroy (&surface->base); |
return _cairo_surface_create_in_error (status); |
} |
|
status = _cairo_gl_context_release (ctx, status); |
if (unlikely (status)) { |
cairo_surface_destroy (&surface->base); |
return _cairo_surface_create_in_error (status); |
} |
|
return &surface->base; |
} |
slim_hidden_def (cairo_gl_surface_create); |
|
/** |
* cairo_gl_surface_create_for_texture: |
* @content: type of content in the surface |
* @tex: name of texture to use for storage of surface pixels |
* @width: width of the surface, in pixels |
* @height: height of the surface, in pixels |
* |
* Creates a GL surface for the specified texture with the specified |
* content and dimensions. The texture must be kept around until the |
* #cairo_surface_t is destroyed or cairo_surface_finish() is called |
* on the surface. The initial contents of @tex will be used as the |
* initial image contents; you must explicitly clear the buffer, |
* using, for example, cairo_rectangle() and cairo_fill() if you want |
* it cleared. The format of @tex should be compatible with @content, |
* in the sense that it must have the color components required by |
* @content. |
* |
* Return value: a pointer to the newly created surface. The caller |
* owns the surface and should call cairo_surface_destroy() when done |
* with it. |
* |
* This function always returns a valid pointer, but it will return a |
* pointer to a "nil" surface if an error such as out of memory |
* occurs. You can use cairo_surface_status() to check for this. |
* |
* Since: TBD |
**/ |
cairo_surface_t * |
cairo_gl_surface_create_for_texture (cairo_device_t *abstract_device, |
cairo_content_t content, |
unsigned int tex, |
int width, |
int height) |
{ |
cairo_gl_context_t *ctx; |
cairo_gl_surface_t *surface; |
cairo_status_t status; |
|
if (! CAIRO_CONTENT_VALID (content)) |
return _cairo_surface_create_in_error (_cairo_error (CAIRO_STATUS_INVALID_CONTENT)); |
|
if (abstract_device == NULL) |
return _cairo_surface_create_in_error (_cairo_error (CAIRO_STATUS_NULL_POINTER)); |
|
if (abstract_device->status) |
return _cairo_surface_create_in_error (abstract_device->status); |
|
if (abstract_device->backend->type != CAIRO_DEVICE_TYPE_GL) |
return _cairo_surface_create_in_error (_cairo_error (CAIRO_STATUS_DEVICE_TYPE_MISMATCH)); |
|
status = _cairo_gl_context_acquire (abstract_device, &ctx); |
if (unlikely (status)) |
return _cairo_surface_create_in_error (status); |
|
surface = (cairo_gl_surface_t *) |
_cairo_gl_surface_create_scratch_for_texture (ctx, content, |
tex, width, height); |
status = _cairo_gl_context_release (ctx, status); |
|
return &surface->base; |
} |
slim_hidden_def (cairo_gl_surface_create_for_texture); |
|
|
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; |
|
if (unlikely (abstract_surface->status)) |
return; |
if (unlikely (abstract_surface->finished)) { |
_cairo_surface_set_error (abstract_surface, |
_cairo_error (CAIRO_STATUS_SURFACE_FINISHED)); |
return; |
} |
|
if (! _cairo_surface_is_gl (abstract_surface) || |
_cairo_gl_surface_is_texture (surface)) { |
_cairo_surface_set_error (abstract_surface, |
_cairo_error (CAIRO_STATUS_SURFACE_TYPE_MISMATCH)); |
return; |
} |
|
if (surface->width != width || surface->height != height) { |
surface->needs_update = TRUE; |
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; |
|
if (unlikely (abstract_surface->status)) |
return; |
if (unlikely (abstract_surface->finished)) { |
_cairo_surface_set_error (abstract_surface, |
_cairo_error (CAIRO_STATUS_SURFACE_FINISHED)); |
return; |
} |
|
if (! _cairo_surface_is_gl (abstract_surface)) { |
_cairo_surface_set_error (abstract_surface, |
CAIRO_STATUS_SURFACE_TYPE_MISMATCH); |
return; |
} |
|
if (! _cairo_gl_surface_is_texture (surface)) { |
cairo_gl_context_t *ctx; |
cairo_status_t status; |
|
status = _cairo_gl_context_acquire (surface->base.device, &ctx); |
if (unlikely (status)) |
return; |
|
/* For swapping on EGL, at least, we need a valid context/target. */ |
_cairo_gl_context_set_destination (ctx, surface, FALSE); |
/* And in any case we should flush any pending operations. */ |
_cairo_gl_composite_flush (ctx); |
|
ctx->swap_buffers (ctx, surface); |
|
status = _cairo_gl_context_release (ctx, status); |
if (status) |
status = _cairo_surface_set_error (abstract_surface, status); |
} |
} |
|
static cairo_surface_t * |
_cairo_gl_surface_create_similar (void *abstract_surface, |
cairo_content_t content, |
int width, |
int height) |
{ |
cairo_surface_t *surface = abstract_surface; |
cairo_gl_context_t *ctx; |
cairo_status_t status; |
|
if (! _cairo_gl_surface_size_valid (abstract_surface, width, height)) |
return _cairo_image_surface_create_with_content (content, width, height); |
|
status = _cairo_gl_context_acquire (surface->device, &ctx); |
if (unlikely (status)) |
return _cairo_surface_create_in_error (status); |
|
surface = _cairo_gl_surface_create_and_clear_scratch (ctx, content, width, height); |
|
status = _cairo_gl_context_release (ctx, status); |
if (unlikely (status)) { |
cairo_surface_destroy (surface); |
return _cairo_surface_create_in_error (status); |
} |
|
return surface; |
} |
|
static cairo_int_status_t |
_cairo_gl_surface_fill_alpha_channel (cairo_gl_surface_t *dst, |
cairo_gl_context_t *ctx, |
int x, int y, |
int width, int height) |
{ |
cairo_gl_composite_t setup; |
cairo_status_t status; |
|
_cairo_gl_composite_flush (ctx); |
glColorMask(GL_FALSE, GL_FALSE, GL_FALSE, GL_TRUE); |
|
status = _cairo_gl_composite_init (&setup, CAIRO_OPERATOR_SOURCE, |
dst, FALSE); |
if (unlikely (status)) |
goto CLEANUP; |
|
_cairo_gl_composite_set_solid_source (&setup, CAIRO_COLOR_BLACK); |
|
status = _cairo_gl_composite_begin (&setup, &ctx); |
if (unlikely (status)) |
goto CLEANUP; |
|
_cairo_gl_context_emit_rect (ctx, x, y, x + width, y + height); |
|
status = _cairo_gl_context_release (ctx, status); |
|
CLEANUP: |
_cairo_gl_composite_fini (&setup); |
|
_cairo_gl_composite_flush (ctx); |
glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE); |
|
return status; |
} |
|
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_bool_t force_flush) |
{ |
GLenum internal_format, format, type; |
cairo_bool_t has_alpha, needs_swap; |
cairo_image_surface_t *clone = NULL; |
cairo_gl_context_t *ctx; |
int cpp; |
cairo_int_status_t status = CAIRO_INT_STATUS_SUCCESS; |
|
status = _cairo_gl_context_acquire (dst->base.device, &ctx); |
if (unlikely (status)) |
return status; |
|
if (! _cairo_gl_get_image_format_and_type (ctx->gl_flavor, |
src->pixman_format, |
&internal_format, |
&format, |
&type, |
&has_alpha, |
&needs_swap)) |
{ |
cairo_bool_t is_supported; |
|
clone = _cairo_image_surface_coerce (src); |
if (unlikely (status = clone->base.status)) |
goto FAIL; |
|
is_supported = |
_cairo_gl_get_image_format_and_type (ctx->gl_flavor, |
clone->pixman_format, |
&internal_format, |
&format, |
&type, |
&has_alpha, |
&needs_swap); |
assert (is_supported); |
assert (!needs_swap); |
src = clone; |
} |
|
cpp = PIXMAN_FORMAT_BPP (src->pixman_format) / 8; |
|
if (force_flush) { |
status = _cairo_gl_surface_flush (&dst->base, 0); |
if (unlikely (status)) |
goto FAIL; |
} |
|
if (_cairo_gl_surface_is_texture (dst)) { |
void *data_start = src->data + src_y * src->stride + src_x * cpp; |
void *data_start_gles2 = NULL; |
|
/* |
* Due to GL_UNPACK_ROW_LENGTH missing in GLES2 we have to extract the |
* image data ourselves in some cases. In particular, we must extract |
* the pixels if: |
* a. we don't want full-length lines or |
* b. the row stride cannot be handled by GL itself using a 4 byte |
* alignment constraint |
*/ |
if (src->stride < 0 || |
(ctx->gl_flavor == CAIRO_GL_FLAVOR_ES && |
(src->width * cpp < src->stride - 3 || |
width != src->width))) |
{ |
glPixelStorei (GL_UNPACK_ALIGNMENT, 1); |
status = _cairo_gl_surface_extract_image_data (src, src_x, src_y, |
width, height, |
&data_start_gles2); |
if (unlikely (status)) |
goto FAIL; |
|
data_start = data_start_gles2; |
} |
else |
{ |
glPixelStorei (GL_UNPACK_ALIGNMENT, 4); |
if (ctx->gl_flavor == CAIRO_GL_FLAVOR_DESKTOP) |
glPixelStorei (GL_UNPACK_ROW_LENGTH, src->stride / cpp); |
} |
|
_cairo_gl_context_activate (ctx, CAIRO_GL_TEX_TEMP); |
glBindTexture (ctx->tex_target, dst->tex); |
glTexParameteri (ctx->tex_target, GL_TEXTURE_MIN_FILTER, GL_NEAREST); |
glTexParameteri (ctx->tex_target, GL_TEXTURE_MAG_FILTER, GL_NEAREST); |
glTexSubImage2D (ctx->tex_target, 0, |
dst_x, dst_y, width, height, |
format, type, data_start); |
|
free (data_start_gles2); |
|
/* If we just treated some rgb-only data as rgba, then we have to |
* go back and fix up the alpha channel where we filled in this |
* texture data. |
*/ |
if (!has_alpha) { |
_cairo_gl_surface_fill_alpha_channel (dst, ctx, |
dst_x, dst_y, |
width, height); |
} |
} else { |
cairo_surface_t *tmp; |
|
tmp = _cairo_gl_surface_create_scratch (ctx, |
dst->base.content, |
width, height); |
if (unlikely (tmp->status)) |
goto FAIL; |
|
status = _cairo_gl_surface_draw_image ((cairo_gl_surface_t *) tmp, |
src, |
src_x, src_y, |
width, height, |
0, 0, force_flush); |
if (status == CAIRO_INT_STATUS_SUCCESS) { |
cairo_surface_pattern_t tmp_pattern; |
cairo_rectangle_int_t r; |
cairo_clip_t *clip; |
|
_cairo_pattern_init_for_surface (&tmp_pattern, tmp); |
cairo_matrix_init_translate (&tmp_pattern.base.matrix, |
-dst_x, -dst_y); |
tmp_pattern.base.filter = CAIRO_FILTER_NEAREST; |
tmp_pattern.base.extend = CAIRO_EXTEND_NONE; |
|
r.x = dst_x; |
r.y = dst_y; |
r.width = width; |
r.height = height; |
clip = _cairo_clip_intersect_rectangle (NULL, &r); |
status = _cairo_surface_paint (&dst->base, |
CAIRO_OPERATOR_SOURCE, |
&tmp_pattern.base, |
clip); |
_cairo_clip_destroy (clip); |
_cairo_pattern_fini (&tmp_pattern.base); |
} |
|
cairo_surface_destroy (tmp); |
} |
|
FAIL: |
status = _cairo_gl_context_release (ctx, status); |
|
if (clone) |
cairo_surface_destroy (&clone->base); |
|
return status; |
} |
|
static int _cairo_gl_surface_flavor (cairo_gl_surface_t *surface) |
{ |
cairo_gl_context_t *ctx = (cairo_gl_context_t *)surface->base.device; |
return ctx->gl_flavor; |
} |
|
static cairo_status_t |
_cairo_gl_surface_finish (void *abstract_surface) |
{ |
cairo_gl_surface_t *surface = abstract_surface; |
cairo_status_t status; |
cairo_gl_context_t *ctx; |
|
status = _cairo_gl_context_acquire (surface->base.device, &ctx); |
if (unlikely (status)) |
return status; |
|
if (ctx->operands[CAIRO_GL_TEX_SOURCE].type == CAIRO_GL_OPERAND_TEXTURE && |
ctx->operands[CAIRO_GL_TEX_SOURCE].texture.surface == surface) |
_cairo_gl_context_destroy_operand (ctx, CAIRO_GL_TEX_SOURCE); |
if (ctx->operands[CAIRO_GL_TEX_MASK].type == CAIRO_GL_OPERAND_TEXTURE && |
ctx->operands[CAIRO_GL_TEX_MASK].texture.surface == surface) |
_cairo_gl_context_destroy_operand (ctx, CAIRO_GL_TEX_MASK); |
if (ctx->current_target == surface) |
ctx->current_target = NULL; |
|
if (surface->fb) |
ctx->dispatch.DeleteFramebuffers (1, &surface->fb); |
if (surface->depth_stencil) |
ctx->dispatch.DeleteRenderbuffers (1, &surface->depth_stencil); |
if (surface->owns_tex) |
glDeleteTextures (1, &surface->tex); |
|
if (surface->msaa_depth_stencil) |
ctx->dispatch.DeleteRenderbuffers (1, &surface->msaa_depth_stencil); |
|
#if CAIRO_HAS_GL_SURFACE |
if (surface->msaa_fb) |
ctx->dispatch.DeleteFramebuffers (1, &surface->msaa_fb); |
if (surface->msaa_rb) |
ctx->dispatch.DeleteRenderbuffers (1, &surface->msaa_rb); |
#endif |
|
_cairo_clip_destroy (surface->clip_on_stencil_buffer); |
|
return _cairo_gl_context_release (ctx, status); |
} |
|
static cairo_image_surface_t * |
_cairo_gl_surface_map_to_image (void *abstract_surface, |
const cairo_rectangle_int_t *extents) |
{ |
cairo_gl_surface_t *surface = abstract_surface; |
cairo_image_surface_t *image; |
cairo_gl_context_t *ctx; |
GLenum format, type; |
pixman_format_code_t pixman_format; |
unsigned int cpp; |
cairo_bool_t flipped, mesa_invert; |
cairo_status_t status; |
int y; |
|
status = _cairo_gl_context_acquire (surface->base.device, &ctx); |
if (unlikely (status)) { |
return _cairo_image_surface_create_in_error (status); |
} |
|
/* Want to use a switch statement here but the compiler gets whiny. */ |
if (surface->base.content == CAIRO_CONTENT_COLOR_ALPHA) { |
format = GL_BGRA; |
pixman_format = PIXMAN_a8r8g8b8; |
type = GL_UNSIGNED_INT_8_8_8_8_REV; |
cpp = 4; |
} else if (surface->base.content == CAIRO_CONTENT_COLOR) { |
format = GL_BGRA; |
pixman_format = PIXMAN_x8r8g8b8; |
type = GL_UNSIGNED_INT_8_8_8_8_REV; |
cpp = 4; |
} else if (surface->base.content == CAIRO_CONTENT_ALPHA) { |
format = GL_ALPHA; |
pixman_format = PIXMAN_a8; |
type = GL_UNSIGNED_BYTE; |
cpp = 1; |
} else { |
ASSERT_NOT_REACHED; |
return NULL; |
} |
|
if (_cairo_gl_surface_flavor (surface) == CAIRO_GL_FLAVOR_ES) { |
/* If only RGBA is supported, we must download data in a compatible |
* format. This means that pixman will convert the data on the CPU when |
* interacting with other image surfaces. For ALPHA, GLES2 does not |
* support GL_PACK_ROW_LENGTH anyway, and this makes sure that the |
* pixman image that is created has row_stride = row_width * bpp. */ |
if (surface->base.content == CAIRO_CONTENT_ALPHA || !ctx->can_read_bgra) { |
cairo_bool_t little_endian = _cairo_is_little_endian (); |
format = GL_RGBA; |
|
if (surface->base.content == CAIRO_CONTENT_COLOR) { |
pixman_format = little_endian ? |
PIXMAN_x8b8g8r8 : PIXMAN_r8g8b8x8; |
} else { |
pixman_format = little_endian ? |
PIXMAN_a8b8g8r8 : PIXMAN_r8g8b8a8; |
} |
} |
|
/* GLES2 only supports GL_UNSIGNED_BYTE. */ |
type = GL_UNSIGNED_BYTE; |
cpp = 4; |
} |
|
image = (cairo_image_surface_t*) |
_cairo_image_surface_create_with_pixman_format (NULL, |
pixman_format, |
extents->width, |
extents->height, |
-1); |
if (unlikely (image->base.status)) { |
status = _cairo_gl_context_release (ctx, status); |
return image; |
} |
|
cairo_surface_set_device_offset (&image->base, -extents->x, -extents->y); |
|
/* If the original surface has not been modified or |
* is clear, we can avoid downloading data. */ |
if (surface->base.is_clear || surface->base.serial == 0) { |
status = _cairo_gl_context_release (ctx, status); |
return image; |
} |
|
/* 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_composite_flush (ctx); |
_cairo_gl_context_set_destination (ctx, surface, FALSE); |
|
flipped = ! _cairo_gl_surface_is_texture (surface); |
mesa_invert = flipped && ctx->has_mesa_pack_invert; |
|
glPixelStorei (GL_PACK_ALIGNMENT, 4); |
if (ctx->gl_flavor == CAIRO_GL_FLAVOR_DESKTOP) |
glPixelStorei (GL_PACK_ROW_LENGTH, image->stride / cpp); |
if (mesa_invert) |
glPixelStorei (GL_PACK_INVERT_MESA, 1); |
|
y = extents->y; |
if (flipped) |
y = surface->height - extents->y - extents->height; |
|
glReadPixels (extents->x, y, |
extents->width, extents->height, |
format, type, image->data); |
if (mesa_invert) |
glPixelStorei (GL_PACK_INVERT_MESA, 0); |
|
status = _cairo_gl_context_release (ctx, status); |
if (unlikely (status)) { |
cairo_surface_destroy (&image->base); |
return _cairo_image_surface_create_in_error (status); |
} |
|
/* We must invert the image manualy if we lack GL_MESA_pack_invert */ |
if (flipped && ! mesa_invert) { |
uint8_t stack[1024], *row = stack; |
uint8_t *top = image->data; |
uint8_t *bot = image->data + (image->height-1)*image->stride; |
|
if (image->stride > (int)sizeof(stack)) { |
row = malloc (image->stride); |
if (unlikely (row == NULL)) { |
cairo_surface_destroy (&image->base); |
return _cairo_image_surface_create_in_error (_cairo_error (CAIRO_STATUS_NO_MEMORY)); |
} |
} |
|
while (top < bot) { |
memcpy (row, top, image->stride); |
memcpy (top, bot, image->stride); |
memcpy (bot, row, image->stride); |
top += image->stride; |
bot -= image->stride; |
} |
|
if (row != stack) |
free(row); |
} |
|
image->base.is_clear = FALSE; |
return image; |
} |
|
static cairo_surface_t * |
_cairo_gl_surface_source (void *abstract_surface, |
cairo_rectangle_int_t *extents) |
{ |
cairo_gl_surface_t *surface = abstract_surface; |
|
if (extents) { |
extents->x = extents->y = 0; |
extents->width = surface->width; |
extents->height = surface->height; |
} |
|
return &surface->base; |
} |
|
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; |
|
*image_out = (cairo_image_surface_t *) |
_cairo_gl_surface_map_to_image (surface, &extents); |
return (*image_out)->base.status; |
} |
|
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_int_status_t |
_cairo_gl_surface_unmap_image (void *abstract_surface, |
cairo_image_surface_t *image) |
{ |
cairo_int_status_t status; |
|
status = _cairo_gl_surface_draw_image (abstract_surface, image, |
0, 0, |
image->width, image->height, |
image->base.device_transform_inverse.x0, |
image->base.device_transform_inverse.y0, |
TRUE); |
|
cairo_surface_finish (&image->base); |
cairo_surface_destroy (&image->base); |
|
return status; |
} |
|
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 cairo_status_t |
_cairo_gl_surface_flush (void *abstract_surface, unsigned flags) |
{ |
cairo_gl_surface_t *surface = abstract_surface; |
cairo_status_t status; |
cairo_gl_context_t *ctx; |
|
if (flags) |
return CAIRO_STATUS_SUCCESS; |
|
status = _cairo_gl_context_acquire (surface->base.device, &ctx); |
if (unlikely (status)) |
return status; |
|
if ((ctx->operands[CAIRO_GL_TEX_SOURCE].type == CAIRO_GL_OPERAND_TEXTURE && |
ctx->operands[CAIRO_GL_TEX_SOURCE].texture.surface == surface) || |
(ctx->operands[CAIRO_GL_TEX_MASK].type == CAIRO_GL_OPERAND_TEXTURE && |
ctx->operands[CAIRO_GL_TEX_MASK].texture.surface == surface) || |
(ctx->current_target == surface)) |
_cairo_gl_composite_flush (ctx); |
|
status = _cairo_gl_surface_resolve_multisampling (surface); |
|
return _cairo_gl_context_release (ctx, status); |
} |
|
cairo_int_status_t |
_cairo_gl_surface_resolve_multisampling (cairo_gl_surface_t *surface) |
{ |
cairo_gl_context_t *ctx; |
cairo_int_status_t status; |
|
if (! surface->msaa_active) |
return CAIRO_INT_STATUS_SUCCESS; |
|
if (surface->base.device == NULL) |
return CAIRO_INT_STATUS_SUCCESS; |
|
/* GLES surfaces do not need explicit resolution. */ |
if (((cairo_gl_context_t *) surface->base.device)->gl_flavor == CAIRO_GL_FLAVOR_ES) |
return CAIRO_INT_STATUS_SUCCESS; |
|
if (! _cairo_gl_surface_is_texture (surface)) |
return CAIRO_INT_STATUS_SUCCESS; |
|
status = _cairo_gl_context_acquire (surface->base.device, &ctx); |
if (unlikely (status)) |
return status; |
|
ctx->current_target = surface; |
|
#if CAIRO_HAS_GL_SURFACE |
_cairo_gl_context_bind_framebuffer (ctx, surface, FALSE); |
#endif |
|
status = _cairo_gl_context_release (ctx, status); |
return status; |
} |
|
static const cairo_compositor_t * |
get_compositor (cairo_gl_surface_t *surface) |
{ |
cairo_gl_context_t *ctx = (cairo_gl_context_t *)surface->base.device; |
return ctx->compositor; |
} |
|
static cairo_int_status_t |
_cairo_gl_surface_paint (void *surface, |
cairo_operator_t op, |
const cairo_pattern_t *source, |
const cairo_clip_t *clip) |
{ |
/* simplify the common case of clearing the surface */ |
if (clip == NULL) { |
if (op == CAIRO_OPERATOR_CLEAR) |
return _cairo_gl_surface_clear (surface, CAIRO_COLOR_TRANSPARENT); |
else if (source->type == CAIRO_PATTERN_TYPE_SOLID && |
(op == CAIRO_OPERATOR_SOURCE || |
(op == CAIRO_OPERATOR_OVER && _cairo_pattern_is_opaque_solid (source)))) { |
return _cairo_gl_surface_clear (surface, |
&((cairo_solid_pattern_t *) source)->color); |
} |
} |
|
return _cairo_compositor_paint (get_compositor (surface), surface, |
op, source, clip); |
} |
|
static cairo_int_status_t |
_cairo_gl_surface_mask (void *surface, |
cairo_operator_t op, |
const cairo_pattern_t *source, |
const cairo_pattern_t *mask, |
const cairo_clip_t *clip) |
{ |
return _cairo_compositor_mask (get_compositor (surface), surface, |
op, source, mask, clip); |
} |
|
static cairo_int_status_t |
_cairo_gl_surface_stroke (void *surface, |
cairo_operator_t op, |
const cairo_pattern_t *source, |
const cairo_path_fixed_t *path, |
const cairo_stroke_style_t *style, |
const cairo_matrix_t *ctm, |
const cairo_matrix_t *ctm_inverse, |
double tolerance, |
cairo_antialias_t antialias, |
const cairo_clip_t *clip) |
{ |
return _cairo_compositor_stroke (get_compositor (surface), surface, |
op, source, path, style, |
ctm, ctm_inverse, tolerance, antialias, |
clip); |
} |
|
static cairo_int_status_t |
_cairo_gl_surface_fill (void *surface, |
cairo_operator_t op, |
const cairo_pattern_t *source, |
const cairo_path_fixed_t*path, |
cairo_fill_rule_t fill_rule, |
double tolerance, |
cairo_antialias_t antialias, |
const cairo_clip_t *clip) |
{ |
return _cairo_compositor_fill (get_compositor (surface), surface, |
op, source, path, |
fill_rule, tolerance, antialias, |
clip); |
} |
|
static cairo_int_status_t |
_cairo_gl_surface_glyphs (void *surface, |
cairo_operator_t op, |
const cairo_pattern_t *source, |
cairo_glyph_t *glyphs, |
int num_glyphs, |
cairo_scaled_font_t *font, |
const cairo_clip_t *clip) |
{ |
return _cairo_compositor_glyphs (get_compositor (surface), surface, |
op, source, glyphs, num_glyphs, font, |
clip); |
} |
|
static const cairo_surface_backend_t _cairo_gl_surface_backend = { |
CAIRO_SURFACE_TYPE_GL, |
_cairo_gl_surface_finish, |
_cairo_default_context_create, |
|
_cairo_gl_surface_create_similar, |
NULL, /* similar image */ |
_cairo_gl_surface_map_to_image, |
_cairo_gl_surface_unmap_image, |
|
_cairo_gl_surface_source, |
_cairo_gl_surface_acquire_source_image, |
_cairo_gl_surface_release_source_image, |
NULL, /* snapshot */ |
|
NULL, /* copy_page */ |
NULL, /* show_page */ |
|
_cairo_gl_surface_get_extents, |
_cairo_image_surface_get_font_options, |
|
_cairo_gl_surface_flush, |
NULL, /* mark_dirty_rectangle */ |
|
_cairo_gl_surface_paint, |
_cairo_gl_surface_mask, |
_cairo_gl_surface_stroke, |
_cairo_gl_surface_fill, |
NULL, /* fill/stroke */ |
_cairo_gl_surface_glyphs, |
}; |