cairo-gl-composite.c [plain text]
#include "cairoint.h"
#include "cairo-error-private.h"
#include "cairo-gl-private.h"
static cairo_int_status_t
_cairo_gl_create_gradient_texture (cairo_gl_surface_t *dst,
const cairo_gradient_pattern_t *pattern,
cairo_gl_gradient_t **gradient)
{
cairo_gl_context_t *ctx;
cairo_status_t status;
status = _cairo_gl_context_acquire (dst->base.device, &ctx);
if (unlikely (status))
return status;
status = _cairo_gl_gradient_create (ctx, pattern->n_stops, pattern->stops, gradient);
return _cairo_gl_context_release (ctx, status);
}
static cairo_status_t
_cairo_gl_pattern_texture_setup (cairo_gl_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->texture.attributes;
status = _cairo_pattern_acquire_surface (src, &dst->base,
src_x, src_y,
width, height,
CAIRO_PATTERN_ACQUIRE_NONE,
(cairo_surface_t **)
&surface,
attributes);
if (unlikely (status))
return status;
if (_cairo_gl_device_requires_power_of_two_textures (dst->base.device) &&
(attributes->extend == CAIRO_EXTEND_REPEAT ||
attributes->extend == CAIRO_EXTEND_REFLECT))
{
_cairo_pattern_release_surface (src,
&surface->base,
attributes);
return UNSUPPORTED ("EXT_texture_rectangle with repeat/reflect");
}
assert (surface->base.backend == &_cairo_gl_surface_backend);
assert (_cairo_gl_surface_is_texture (surface));
operand->type = CAIRO_GL_OPERAND_TEXTURE;
operand->texture.surface = surface;
operand->texture.tex = surface->tex;
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);
if (_cairo_gl_device_requires_power_of_two_textures (dst->base.device)) {
cairo_matrix_init_scale (&m,
1.0,
1.0);
} else {
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_operand_t *operand,
const cairo_color_t *color)
{
operand->type = CAIRO_GL_OPERAND_CONSTANT;
operand->constant.color[0] = color->red * color->alpha;
operand->constant.color[1] = color->green * color->alpha;
operand->constant.color[2] = color->blue * color->alpha;
operand->constant.color[3] = color->alpha;
return CAIRO_STATUS_SUCCESS;
}
static cairo_status_t
_cairo_gl_gradient_operand_init (cairo_gl_operand_t *operand,
cairo_gl_surface_t *dst,
const cairo_pattern_t *pattern)
{
const cairo_gradient_pattern_t *gradient = (const cairo_gradient_pattern_t *)pattern;
cairo_status_t status;
if (! _cairo_gl_device_has_glsl (dst->base.device))
return CAIRO_INT_STATUS_UNSUPPORTED;
if (gradient->base.type == CAIRO_PATTERN_TYPE_LINEAR) {
cairo_linear_pattern_t *linear = (cairo_linear_pattern_t *) gradient;
double x0, y0, x1, y1;
x0 = _cairo_fixed_to_double (linear->p1.x);
x1 = _cairo_fixed_to_double (linear->p2.x);
y0 = _cairo_fixed_to_double (linear->p1.y);
y1 = _cairo_fixed_to_double (linear->p2.y);
status = _cairo_gl_create_gradient_texture (dst,
gradient,
&operand->linear.gradient);
if (unlikely (status))
return status;
cairo_matrix_init_translate (&operand->linear.m, -x0, -y0);
cairo_matrix_multiply (&operand->linear.m,
&pattern->matrix,
&operand->linear.m);
cairo_matrix_translate (&operand->linear.m, 0, dst->height);
cairo_matrix_scale (&operand->linear.m, 1.0, -1.0);
operand->linear.segment_x = x1 - x0;
operand->linear.segment_y = y1 - y0;
operand->linear.extend = pattern->extend;
operand->type = CAIRO_GL_OPERAND_LINEAR_GRADIENT;
return CAIRO_STATUS_SUCCESS;
} else {
cairo_radial_pattern_t *radial = (cairo_radial_pattern_t *) gradient;
double x0, y0, r0, x1, y1, r1;
x0 = _cairo_fixed_to_double (radial->c1.x);
x1 = _cairo_fixed_to_double (radial->c2.x);
y0 = _cairo_fixed_to_double (radial->c1.y);
y1 = _cairo_fixed_to_double (radial->c2.y);
r0 = _cairo_fixed_to_double (radial->r1);
r1 = _cairo_fixed_to_double (radial->r2);
status = _cairo_gl_create_gradient_texture (dst,
gradient,
&operand->radial.gradient);
if (unlikely (status))
return status;
cairo_matrix_init_translate (&operand->radial.m, -x0, -y0);
cairo_matrix_multiply (&operand->radial.m,
&pattern->matrix,
&operand->radial.m);
cairo_matrix_translate (&operand->radial.m, 0, dst->height);
cairo_matrix_scale (&operand->radial.m, 1.0, -1.0);
operand->radial.circle_1_x = x1 - x0;
operand->radial.circle_1_y = y1 - y0;
operand->radial.radius_0 = r0;
operand->radial.radius_1 = r1;
operand->radial.extend = pattern->extend;
operand->type = CAIRO_GL_OPERAND_RADIAL_GRADIENT;
return CAIRO_STATUS_SUCCESS;
}
return CAIRO_INT_STATUS_UNSUPPORTED;
}
static void
_cairo_gl_operand_destroy (cairo_gl_operand_t *operand)
{
switch (operand->type) {
case CAIRO_GL_OPERAND_CONSTANT:
break;
case CAIRO_GL_OPERAND_LINEAR_GRADIENT:
_cairo_gl_gradient_destroy (operand->linear.gradient);
break;
case CAIRO_GL_OPERAND_RADIAL_GRADIENT:
_cairo_gl_gradient_destroy (operand->radial.gradient);
break;
case CAIRO_GL_OPERAND_TEXTURE:
_cairo_pattern_release_surface (NULL,
&operand->texture.surface->base,
&operand->texture.attributes);
break;
default:
case CAIRO_GL_OPERAND_COUNT:
ASSERT_NOT_REACHED;
case CAIRO_GL_OPERAND_NONE:
case CAIRO_GL_OPERAND_SPANS:
break;
}
operand->type = CAIRO_GL_OPERAND_NONE;
}
static cairo_int_status_t
_cairo_gl_operand_init (cairo_gl_operand_t *operand,
const cairo_pattern_t *pattern,
cairo_gl_surface_t *dst,
int src_x, int src_y,
int dst_x, int dst_y,
int width, int height)
{
cairo_status_t status;
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:
status = _cairo_gl_gradient_operand_init (operand, dst, pattern);
if (status != CAIRO_INT_STATUS_UNSUPPORTED)
return status;
default:
case CAIRO_PATTERN_TYPE_SURFACE:
return _cairo_gl_pattern_texture_setup (operand,
pattern, dst,
src_x, src_y,
dst_x, dst_y,
width, height);
}
}
cairo_int_status_t
_cairo_gl_composite_set_source (cairo_gl_composite_t *setup,
const cairo_pattern_t *pattern,
int src_x, int src_y,
int dst_x, int dst_y,
int width, int height)
{
_cairo_gl_operand_destroy (&setup->src);
return _cairo_gl_operand_init (&setup->src, pattern,
setup->dst,
src_x, src_y,
dst_x, dst_y,
width, height);
}
cairo_int_status_t
_cairo_gl_composite_set_mask (cairo_gl_composite_t *setup,
const cairo_pattern_t *pattern,
int src_x, int src_y,
int dst_x, int dst_y,
int width, int height)
{
_cairo_gl_operand_destroy (&setup->mask);
if (pattern == NULL)
return CAIRO_STATUS_SUCCESS;
return _cairo_gl_operand_init (&setup->mask, pattern,
setup->dst,
src_x, src_y,
dst_x, dst_y,
width, height);
}
void
_cairo_gl_composite_set_mask_spans (cairo_gl_composite_t *setup)
{
_cairo_gl_operand_destroy (&setup->mask);
setup->mask.type = CAIRO_GL_OPERAND_SPANS;
}
void
_cairo_gl_composite_set_clip_region (cairo_gl_composite_t *setup,
cairo_region_t *clip_region)
{
setup->clip_region = clip_region;
}
static void
_cairo_gl_operand_bind_to_shader (cairo_gl_context_t *ctx,
cairo_gl_operand_t *operand,
cairo_gl_tex_t tex_unit)
{
char uniform_name[50];
char *custom_part;
static const char *names[] = { "source", "mask" };
strcpy (uniform_name, names[tex_unit]);
custom_part = uniform_name + strlen (names[tex_unit]);
switch (operand->type) {
default:
case CAIRO_GL_OPERAND_COUNT:
ASSERT_NOT_REACHED;
case CAIRO_GL_OPERAND_NONE:
case CAIRO_GL_OPERAND_SPANS:
break;
case CAIRO_GL_OPERAND_CONSTANT:
strcpy (custom_part, "_constant");
_cairo_gl_shader_bind_vec4 (ctx,
uniform_name,
operand->constant.color[0],
operand->constant.color[1],
operand->constant.color[2],
operand->constant.color[3]);
break;
case CAIRO_GL_OPERAND_LINEAR_GRADIENT:
strcpy (custom_part, "_matrix");
_cairo_gl_shader_bind_matrix (ctx,
uniform_name,
&operand->linear.m);
strcpy (custom_part, "_segment");
_cairo_gl_shader_bind_vec2 (ctx,
uniform_name,
operand->linear.segment_x,
operand->linear.segment_y);
strcpy (custom_part, "_sampler");
_cairo_gl_shader_bind_texture(ctx,
uniform_name,
tex_unit);
break;
case CAIRO_GL_OPERAND_RADIAL_GRADIENT:
strcpy (custom_part, "_matrix");
_cairo_gl_shader_bind_matrix (ctx,
uniform_name,
&operand->radial.m);
strcpy (custom_part, "_circle_1");
_cairo_gl_shader_bind_vec2 (ctx,
uniform_name,
operand->radial.circle_1_x,
operand->radial.circle_1_y);
strcpy (custom_part, "_radius_0");
_cairo_gl_shader_bind_float (ctx,
uniform_name,
operand->radial.radius_0);
strcpy (custom_part, "_radius_1");
_cairo_gl_shader_bind_float (ctx,
uniform_name,
operand->radial.radius_1);
strcpy (custom_part, "_sampler");
_cairo_gl_shader_bind_texture(ctx,
uniform_name,
tex_unit);
break;
case CAIRO_GL_OPERAND_TEXTURE:
strcpy (custom_part, "_sampler");
_cairo_gl_shader_bind_texture(ctx,
uniform_name,
tex_unit);
break;
}
}
static void
_cairo_gl_composite_bind_to_shader (cairo_gl_context_t *ctx,
cairo_gl_composite_t *setup)
{
if (ctx->current_shader == NULL)
return;
_cairo_gl_operand_bind_to_shader (ctx, &setup->src, CAIRO_GL_TEX_SOURCE);
_cairo_gl_operand_bind_to_shader (ctx, &setup->mask, CAIRO_GL_TEX_MASK);
}
static void
_cairo_gl_texture_set_extend (cairo_gl_context_t *ctx,
GLuint target,
cairo_extend_t extend)
{
assert (! _cairo_gl_device_requires_power_of_two_textures (&ctx->base) ||
(extend != CAIRO_EXTEND_REPEAT && extend != CAIRO_EXTEND_REFLECT));
switch (extend) {
case CAIRO_EXTEND_NONE:
glTexParameteri (target, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_BORDER);
glTexParameteri (target, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_BORDER);
break;
case CAIRO_EXTEND_PAD:
glTexParameteri (target, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glTexParameteri (target, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
break;
case CAIRO_EXTEND_REPEAT:
glTexParameteri (target, GL_TEXTURE_WRAP_S, GL_REPEAT);
glTexParameteri (target, GL_TEXTURE_WRAP_T, GL_REPEAT);
break;
case CAIRO_EXTEND_REFLECT:
glTexParameteri (target, GL_TEXTURE_WRAP_S, GL_MIRRORED_REPEAT);
glTexParameteri (target, GL_TEXTURE_WRAP_T, GL_MIRRORED_REPEAT);
break;
}
}
static void
_cairo_gl_texture_set_filter (cairo_gl_context_t *ctx,
GLuint target,
cairo_filter_t filter)
{
switch (filter) {
case CAIRO_FILTER_FAST:
case CAIRO_FILTER_NEAREST:
glTexParameteri (target, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
glTexParameteri (target, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
break;
case CAIRO_FILTER_GOOD:
case CAIRO_FILTER_BEST:
case CAIRO_FILTER_BILINEAR:
glTexParameteri (target, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameteri (target, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
break;
default:
case CAIRO_FILTER_GAUSSIAN:
ASSERT_NOT_REACHED;
}
}
static void
_cairo_gl_operand_setup_fixed (cairo_gl_operand_t *operand,
cairo_gl_tex_t tex_unit)
{
switch (operand->type) {
case CAIRO_GL_OPERAND_CONSTANT:
glTexEnvfv (GL_TEXTURE_ENV, GL_TEXTURE_ENV_COLOR, operand->constant.color);
glTexEnvi (GL_TEXTURE_ENV, GL_SRC0_RGB, GL_CONSTANT);
glTexEnvi (GL_TEXTURE_ENV, GL_SRC0_ALPHA, GL_CONSTANT);
break;
case CAIRO_GL_OPERAND_TEXTURE:
glTexEnvi (GL_TEXTURE_ENV, GL_SRC0_RGB, GL_TEXTURE0 + tex_unit);
glTexEnvi (GL_TEXTURE_ENV, GL_SRC0_ALPHA, GL_TEXTURE0 + tex_unit);
break;
case CAIRO_GL_OPERAND_SPANS:
glTexEnvi (GL_TEXTURE_ENV, GL_SRC0_RGB, GL_PRIMARY_COLOR);
glTexEnvi (GL_TEXTURE_ENV, GL_SRC0_ALPHA, GL_PRIMARY_COLOR);
break;
case CAIRO_GL_OPERAND_COUNT:
default:
ASSERT_NOT_REACHED;
case CAIRO_GL_OPERAND_LINEAR_GRADIENT:
case CAIRO_GL_OPERAND_RADIAL_GRADIENT:
case CAIRO_GL_OPERAND_NONE:
return;
}
switch (tex_unit) {
case CAIRO_GL_TEX_TEMP:
default:
ASSERT_NOT_REACHED;
break;
case CAIRO_GL_TEX_SOURCE:
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_OPERAND0_RGB, GL_SRC_COLOR);
glTexEnvi (GL_TEXTURE_ENV, GL_OPERAND0_ALPHA, GL_SRC_ALPHA);
break;
case CAIRO_GL_TEX_MASK:
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_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);
if (operand->type == CAIRO_GL_OPERAND_TEXTURE &&
operand->texture.attributes.has_component_alpha)
glTexEnvi (GL_TEXTURE_ENV, GL_OPERAND0_RGB, GL_SRC_COLOR);
else
glTexEnvi (GL_TEXTURE_ENV, GL_OPERAND0_RGB, GL_SRC_ALPHA);
glTexEnvi (GL_TEXTURE_ENV, GL_OPERAND0_ALPHA, GL_SRC_ALPHA);
break;
}
}
static cairo_bool_t
_cairo_gl_operand_needs_setup (cairo_gl_operand_t *dest,
cairo_gl_operand_t *source,
unsigned int vertex_offset)
{
if (dest->type != source->type)
return TRUE;
if (dest->vertex_offset != vertex_offset)
return TRUE;
switch (source->type) {
case CAIRO_GL_OPERAND_NONE:
case CAIRO_GL_OPERAND_SPANS:
return FALSE;
case CAIRO_GL_OPERAND_CONSTANT:
return dest->constant.color[0] != source->constant.color[0] ||
dest->constant.color[1] != source->constant.color[1] ||
dest->constant.color[2] != source->constant.color[2] ||
dest->constant.color[3] != source->constant.color[3];
case CAIRO_GL_OPERAND_TEXTURE:
return dest->texture.surface != source->texture.surface ||
dest->texture.attributes.extend != source->texture.attributes.extend ||
dest->texture.attributes.filter != source->texture.attributes.filter ||
dest->texture.attributes.has_component_alpha != source->texture.attributes.has_component_alpha;
case CAIRO_GL_OPERAND_LINEAR_GRADIENT:
case CAIRO_GL_OPERAND_RADIAL_GRADIENT:
return TRUE;
default:
case CAIRO_GL_OPERAND_COUNT:
ASSERT_NOT_REACHED;
break;
}
return TRUE;
}
static void
_cairo_gl_context_setup_operand (cairo_gl_context_t *ctx,
cairo_gl_tex_t tex_unit,
cairo_gl_operand_t *operand,
unsigned int vertex_size,
unsigned int vertex_offset,
cairo_bool_t use_shaders)
{
cairo_bool_t needs_setup;
needs_setup = ctx->vertex_size != vertex_size;
needs_setup |= _cairo_gl_operand_needs_setup (&ctx->operands[tex_unit],
operand,
vertex_offset);
if (needs_setup) {
_cairo_gl_composite_flush (ctx);
_cairo_gl_context_destroy_operand (ctx, tex_unit);
}
memcpy (&ctx->operands[tex_unit], operand, sizeof (cairo_gl_operand_t));
ctx->operands[tex_unit].vertex_offset = vertex_offset;
if (! needs_setup)
return;
switch (operand->type) {
default:
case CAIRO_GL_OPERAND_COUNT:
ASSERT_NOT_REACHED;
case CAIRO_GL_OPERAND_NONE:
break;
case CAIRO_GL_OPERAND_SPANS:
glColorPointer (4, GL_UNSIGNED_BYTE, vertex_size,
(void *) (uintptr_t) vertex_offset);
glEnableClientState (GL_COLOR_ARRAY);
case CAIRO_GL_OPERAND_CONSTANT:
if (! use_shaders) {
glActiveTexture (GL_TEXTURE0 + tex_unit);
glBindTexture (ctx->tex_target, ctx->dummy_tex);
glEnable (ctx->tex_target);
}
break;
case CAIRO_GL_OPERAND_TEXTURE:
glActiveTexture (GL_TEXTURE0 + tex_unit);
glBindTexture (ctx->tex_target, operand->texture.tex);
glEnable (ctx->tex_target);
_cairo_gl_texture_set_extend (ctx, ctx->tex_target,
operand->texture.attributes.extend);
_cairo_gl_texture_set_filter (ctx, ctx->tex_target,
operand->texture.attributes.filter);
glClientActiveTexture (GL_TEXTURE0 + tex_unit);
glTexCoordPointer (2, GL_FLOAT, vertex_size,
(void *) (uintptr_t) vertex_offset);
glEnableClientState (GL_TEXTURE_COORD_ARRAY);
break;
case CAIRO_GL_OPERAND_LINEAR_GRADIENT:
_cairo_gl_gradient_reference (operand->linear.gradient);
glActiveTexture (GL_TEXTURE0 + tex_unit);
glBindTexture (GL_TEXTURE_1D, operand->linear.gradient->tex);
_cairo_gl_texture_set_extend (ctx, GL_TEXTURE_1D, operand->linear.extend);
_cairo_gl_texture_set_filter (ctx, GL_TEXTURE_1D, CAIRO_FILTER_BILINEAR);
glEnable (GL_TEXTURE_1D);
break;
case CAIRO_GL_OPERAND_RADIAL_GRADIENT:
_cairo_gl_gradient_reference (operand->radial.gradient);
glActiveTexture (GL_TEXTURE0 + tex_unit);
glBindTexture (GL_TEXTURE_1D, operand->radial.gradient->tex);
_cairo_gl_texture_set_extend (ctx, GL_TEXTURE_1D, operand->radial.extend);
_cairo_gl_texture_set_filter (ctx, GL_TEXTURE_1D, CAIRO_FILTER_BILINEAR);
glEnable (GL_TEXTURE_1D);
break;
}
if (! use_shaders)
_cairo_gl_operand_setup_fixed (operand, tex_unit);
}
void
_cairo_gl_context_destroy_operand (cairo_gl_context_t *ctx,
cairo_gl_tex_t tex_unit)
{
assert (_cairo_gl_context_is_flushed (ctx));
switch (ctx->operands[tex_unit].type) {
default:
case CAIRO_GL_OPERAND_COUNT:
ASSERT_NOT_REACHED;
case CAIRO_GL_OPERAND_NONE:
break;
case CAIRO_GL_OPERAND_SPANS:
glDisableClientState (GL_COLOR_ARRAY);
case CAIRO_GL_OPERAND_CONSTANT:
if (ctx->current_shader == NULL) {
glActiveTexture (GL_TEXTURE0 + tex_unit);
glDisable (ctx->tex_target);
}
break;
case CAIRO_GL_OPERAND_TEXTURE:
glActiveTexture (GL_TEXTURE0 + tex_unit);
glDisable (ctx->tex_target);
glClientActiveTexture (GL_TEXTURE0 + tex_unit);
glDisableClientState (GL_TEXTURE_COORD_ARRAY);
break;
case CAIRO_GL_OPERAND_LINEAR_GRADIENT:
_cairo_gl_gradient_destroy (ctx->operands[tex_unit].linear.gradient);
glActiveTexture (GL_TEXTURE0 + tex_unit);
glDisable (GL_TEXTURE_1D);
break;
case CAIRO_GL_OPERAND_RADIAL_GRADIENT:
_cairo_gl_gradient_destroy (ctx->operands[tex_unit].radial.gradient);
glActiveTexture (GL_TEXTURE0 + tex_unit);
glDisable (GL_TEXTURE_1D);
break;
}
memset (&ctx->operands[tex_unit], 0, sizeof (cairo_gl_operand_t));
}
static void
_cairo_gl_set_src_alpha (cairo_gl_context_t *ctx,
cairo_bool_t activate)
{
if (ctx->current_shader)
return;
glActiveTexture (GL_TEXTURE0);
glTexEnvi (GL_TEXTURE_ENV, GL_OPERAND0_RGB, activate ? GL_SRC_ALPHA : GL_SRC_COLOR);
}
static void
_cairo_gl_set_operator (cairo_gl_context_t *ctx,
cairo_operator_t op,
cairo_bool_t component_alpha)
{
struct {
GLenum src;
GLenum dst;
} blend_factors[] = {
{ GL_ZERO, GL_ZERO },
{ GL_ONE, GL_ZERO },
{ GL_ONE, GL_ONE_MINUS_SRC_ALPHA },
{ GL_DST_ALPHA, GL_ZERO },
{ GL_ONE_MINUS_DST_ALPHA, GL_ZERO },
{ GL_DST_ALPHA, GL_ONE_MINUS_SRC_ALPHA },
{ GL_ZERO, GL_ONE },
{ GL_ONE_MINUS_DST_ALPHA, GL_ONE },
{ GL_ZERO, GL_SRC_ALPHA },
{ GL_ZERO, GL_ONE_MINUS_SRC_ALPHA },
{ GL_ONE_MINUS_DST_ALPHA, GL_SRC_ALPHA },
{ GL_ONE_MINUS_DST_ALPHA, GL_ONE_MINUS_SRC_ALPHA },
{ GL_ONE, GL_ONE },
};
GLenum src_factor, dst_factor;
assert (op < ARRAY_LENGTH (blend_factors));
if (ctx->current_operator != op)
_cairo_gl_composite_flush (ctx);
ctx->current_operator = op;
src_factor = blend_factors[op].src;
dst_factor = blend_factors[op].dst;
if (ctx->current_target->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;
}
if (component_alpha) {
if (dst_factor == GL_ONE_MINUS_SRC_ALPHA)
dst_factor = GL_ONE_MINUS_SRC_COLOR;
if (dst_factor == GL_SRC_ALPHA)
dst_factor = GL_SRC_COLOR;
}
if (ctx->current_target->base.content == CAIRO_CONTENT_ALPHA) {
glBlendFuncSeparate (GL_ZERO, GL_ZERO, src_factor, dst_factor);
} else if (ctx->current_target->base.content == CAIRO_CONTENT_COLOR) {
glBlendFuncSeparate (src_factor, dst_factor, GL_ONE, GL_ONE);
} else {
glBlendFunc (src_factor, dst_factor);
}
}
static unsigned int
_cairo_gl_operand_get_vertex_size (cairo_gl_operand_type_t type)
{
switch (type) {
default:
case CAIRO_GL_OPERAND_COUNT:
ASSERT_NOT_REACHED;
case CAIRO_GL_OPERAND_NONE:
case CAIRO_GL_OPERAND_CONSTANT:
case CAIRO_GL_OPERAND_LINEAR_GRADIENT:
case CAIRO_GL_OPERAND_RADIAL_GRADIENT:
return 0;
case CAIRO_GL_OPERAND_SPANS:
return 4 * sizeof (GLbyte);
case CAIRO_GL_OPERAND_TEXTURE:
return 2 * sizeof (GLfloat);
}
}
static cairo_status_t
_cairo_gl_composite_begin_component_alpha (cairo_gl_context_t *ctx,
cairo_gl_composite_t *setup)
{
cairo_gl_shader_t *pre_shader = NULL;
cairo_status_t status;
if (setup->op == CAIRO_OPERATOR_CLEAR) {
_cairo_gl_solid_operand_init (&setup->src, CAIRO_COLOR_WHITE);
setup->op = CAIRO_OPERATOR_DEST_OUT;
}
if (setup->op == CAIRO_OPERATOR_OVER) {
setup->op = CAIRO_OPERATOR_ADD;
status = _cairo_gl_get_shader_by_type (ctx,
setup->src.type,
setup->mask.type,
CAIRO_GL_SHADER_IN_CA_SOURCE_ALPHA,
&pre_shader);
if (unlikely (status))
return status;
}
if (ctx->pre_shader != pre_shader)
_cairo_gl_composite_flush (ctx);
ctx->pre_shader = pre_shader;
return CAIRO_STATUS_SUCCESS;
}
cairo_status_t
_cairo_gl_composite_begin (cairo_gl_composite_t *setup,
cairo_gl_context_t **ctx_out)
{
unsigned int dst_size, src_size, mask_size, vertex_size;
cairo_gl_context_t *ctx;
cairo_status_t status;
cairo_bool_t component_alpha;
cairo_gl_shader_t *shader;
assert (setup->dst);
status = _cairo_gl_context_acquire (setup->dst->base.device, &ctx);
if (unlikely (status))
return status;
glEnable (GL_BLEND);
component_alpha = ((setup->mask.type == CAIRO_GL_OPERAND_TEXTURE) &&
setup->mask.texture.attributes.has_component_alpha);
if (component_alpha) {
status = _cairo_gl_composite_begin_component_alpha (ctx, setup);
if (unlikely (status))
goto FAIL;
} else {
if (ctx->pre_shader) {
_cairo_gl_composite_flush (ctx);
ctx->pre_shader = NULL;
}
}
status = _cairo_gl_get_shader_by_type (ctx,
setup->src.type,
setup->mask.type,
component_alpha ? CAIRO_GL_SHADER_IN_CA_SOURCE
: CAIRO_GL_SHADER_IN_NORMAL,
&shader);
if (unlikely (status)) {
ctx->pre_shader = NULL;
goto FAIL;
}
if (ctx->current_shader != shader)
_cairo_gl_composite_flush (ctx);
status = CAIRO_STATUS_SUCCESS;
dst_size = 2 * sizeof (GLfloat);
src_size = _cairo_gl_operand_get_vertex_size (setup->src.type);
mask_size = _cairo_gl_operand_get_vertex_size (setup->mask.type);
vertex_size = dst_size + src_size + mask_size;
if (ctx->vertex_size != vertex_size) {
_cairo_gl_composite_flush (ctx);
}
_cairo_gl_context_set_destination (ctx, setup->dst);
if (_cairo_gl_context_is_flushed (ctx)) {
glBindBufferARB (GL_ARRAY_BUFFER_ARB, ctx->vbo);
glVertexPointer (2, GL_FLOAT, vertex_size, NULL);
glEnableClientState (GL_VERTEX_ARRAY);
}
_cairo_gl_context_setup_operand (ctx, CAIRO_GL_TEX_SOURCE, &setup->src, vertex_size, dst_size, shader != NULL);
_cairo_gl_context_setup_operand (ctx, CAIRO_GL_TEX_MASK, &setup->mask, vertex_size, dst_size + src_size, shader != NULL);
_cairo_gl_set_operator (ctx,
setup->op,
component_alpha);
ctx->vertex_size = vertex_size;
if (_cairo_gl_context_is_flushed (ctx)) {
if (ctx->pre_shader) {
_cairo_gl_set_shader (ctx, ctx->pre_shader);
_cairo_gl_composite_bind_to_shader (ctx, setup);
}
_cairo_gl_set_shader (ctx, shader);
_cairo_gl_composite_bind_to_shader (ctx, setup);
}
if (! _cairo_gl_context_is_flushed (ctx) &&
! cairo_region_equal (ctx->clip_region, setup->clip_region))
_cairo_gl_composite_flush (ctx);
cairo_region_destroy (ctx->clip_region);
ctx->clip_region = cairo_region_reference (setup->clip_region);
if (ctx->clip_region)
glEnable (GL_SCISSOR_TEST);
else
glDisable (GL_SCISSOR_TEST);
*ctx_out = ctx;
FAIL:
if (unlikely (status))
status = _cairo_gl_context_release (ctx, status);
return status;
}
static inline void
_cairo_gl_composite_draw (cairo_gl_context_t *ctx,
unsigned int count)
{
if (! ctx->pre_shader) {
glDrawArrays (GL_TRIANGLES, 0, count);
} else {
cairo_gl_shader_t *prev_shader = ctx->current_shader;
_cairo_gl_set_shader (ctx, ctx->pre_shader);
_cairo_gl_set_operator (ctx, CAIRO_OPERATOR_DEST_OUT, TRUE);
_cairo_gl_set_src_alpha (ctx, TRUE);
glDrawArrays (GL_TRIANGLES, 0, count);
_cairo_gl_set_src_alpha (ctx, FALSE);
_cairo_gl_set_shader (ctx, prev_shader);
_cairo_gl_set_operator (ctx, CAIRO_OPERATOR_ADD, TRUE);
glDrawArrays (GL_TRIANGLES, 0, count);
}
}
void
_cairo_gl_composite_flush (cairo_gl_context_t *ctx)
{
unsigned int count;
if (_cairo_gl_context_is_flushed (ctx))
return;
count = ctx->vb_offset / ctx->vertex_size;
glUnmapBufferARB (GL_ARRAY_BUFFER_ARB);
ctx->vb = NULL;
ctx->vb_offset = 0;
if (ctx->clip_region) {
int i, num_rectangles = cairo_region_num_rectangles (ctx->clip_region);
for (i = 0; i < num_rectangles; i++) {
cairo_rectangle_int_t rect;
cairo_region_get_rectangle (ctx->clip_region, i, &rect);
glScissor (rect.x, rect.y, rect.width, rect.height);
_cairo_gl_composite_draw (ctx, count);
}
} else {
_cairo_gl_composite_draw (ctx, count);
}
}
static void
_cairo_gl_composite_prepare_buffer (cairo_gl_context_t *ctx,
unsigned int n_vertices)
{
if (ctx->vb_offset + n_vertices * ctx->vertex_size > CAIRO_GL_VBO_SIZE)
_cairo_gl_composite_flush (ctx);
if (ctx->vb == NULL) {
glBufferDataARB (GL_ARRAY_BUFFER_ARB, CAIRO_GL_VBO_SIZE,
NULL, GL_STREAM_DRAW_ARB);
ctx->vb = glMapBufferARB (GL_ARRAY_BUFFER_ARB, GL_WRITE_ONLY_ARB);
}
}
static inline void
_cairo_gl_operand_emit (cairo_gl_operand_t *operand,
GLfloat ** vb,
GLfloat x,
GLfloat y,
uint8_t alpha)
{
switch (operand->type) {
default:
case CAIRO_GL_OPERAND_COUNT:
ASSERT_NOT_REACHED;
case CAIRO_GL_OPERAND_NONE:
case CAIRO_GL_OPERAND_CONSTANT:
case CAIRO_GL_OPERAND_LINEAR_GRADIENT:
case CAIRO_GL_OPERAND_RADIAL_GRADIENT:
break;
case CAIRO_GL_OPERAND_SPANS:
{
union fi {
float f;
GLbyte bytes[4];
} fi;
fi.bytes[0] = 0;
fi.bytes[1] = 0;
fi.bytes[2] = 0;
fi.bytes[3] = alpha;
*(*vb)++ = fi.f;
}
break;
case CAIRO_GL_OPERAND_TEXTURE:
{
cairo_surface_attributes_t *src_attributes = &operand->texture.attributes;
double s = x;
double t = y;
cairo_matrix_transform_point (&src_attributes->matrix, &s, &t);
*(*vb)++ = s;
*(*vb)++ = t;
}
break;
}
}
static inline void
_cairo_gl_composite_emit_vertex (cairo_gl_context_t *ctx,
GLfloat x,
GLfloat y,
uint8_t alpha)
{
GLfloat *vb = (GLfloat *) (void *) &ctx->vb[ctx->vb_offset];
*vb++ = x;
*vb++ = y;
_cairo_gl_operand_emit (&ctx->operands[CAIRO_GL_TEX_SOURCE], &vb, x, y, alpha);
_cairo_gl_operand_emit (&ctx->operands[CAIRO_GL_TEX_MASK ], &vb, x, y, alpha);
ctx->vb_offset += ctx->vertex_size;
}
void
_cairo_gl_composite_emit_rect (cairo_gl_context_t *ctx,
GLfloat x1,
GLfloat y1,
GLfloat x2,
GLfloat y2,
uint8_t alpha)
{
_cairo_gl_composite_prepare_buffer (ctx, 6);
_cairo_gl_composite_emit_vertex (ctx, x1, y1, alpha);
_cairo_gl_composite_emit_vertex (ctx, x2, y1, alpha);
_cairo_gl_composite_emit_vertex (ctx, x1, y2, alpha);
_cairo_gl_composite_emit_vertex (ctx, x2, y1, alpha);
_cairo_gl_composite_emit_vertex (ctx, x2, y2, alpha);
_cairo_gl_composite_emit_vertex (ctx, x1, y2, alpha);
}
static inline void
_cairo_gl_composite_emit_glyph_vertex (cairo_gl_context_t *ctx,
GLfloat x,
GLfloat y,
GLfloat glyph_x,
GLfloat glyph_y)
{
GLfloat *vb = (GLfloat *) (void *) &ctx->vb[ctx->vb_offset];
*vb++ = x;
*vb++ = y;
_cairo_gl_operand_emit (&ctx->operands[CAIRO_GL_TEX_SOURCE], &vb, x, y, 0);
*vb++ = glyph_x;
*vb++ = glyph_y;
ctx->vb_offset += ctx->vertex_size;
}
void
_cairo_gl_composite_emit_glyph (cairo_gl_context_t *ctx,
GLfloat x1,
GLfloat y1,
GLfloat x2,
GLfloat y2,
GLfloat glyph_x1,
GLfloat glyph_y1,
GLfloat glyph_x2,
GLfloat glyph_y2)
{
_cairo_gl_composite_prepare_buffer (ctx, 6);
_cairo_gl_composite_emit_glyph_vertex (ctx, x1, y1, glyph_x1, glyph_y1);
_cairo_gl_composite_emit_glyph_vertex (ctx, x2, y1, glyph_x2, glyph_y1);
_cairo_gl_composite_emit_glyph_vertex (ctx, x1, y2, glyph_x1, glyph_y2);
_cairo_gl_composite_emit_glyph_vertex (ctx, x2, y1, glyph_x2, glyph_y1);
_cairo_gl_composite_emit_glyph_vertex (ctx, x2, y2, glyph_x2, glyph_y2);
_cairo_gl_composite_emit_glyph_vertex (ctx, x1, y2, glyph_x1, glyph_y2);
}
void
_cairo_gl_composite_fini (cairo_gl_composite_t *setup)
{
_cairo_gl_operand_destroy (&setup->src);
_cairo_gl_operand_destroy (&setup->mask);
}
cairo_status_t
_cairo_gl_composite_init (cairo_gl_composite_t *setup,
cairo_operator_t op,
cairo_gl_surface_t *dst,
cairo_bool_t assume_component_alpha,
const cairo_rectangle_int_t *rect)
{
memset (setup, 0, sizeof (cairo_gl_composite_t));
if (assume_component_alpha) {
if (op != CAIRO_OPERATOR_CLEAR &&
op != CAIRO_OPERATOR_OVER &&
op != CAIRO_OPERATOR_ADD)
return UNSUPPORTED ("unsupported component alpha operator");
} else {
if (! _cairo_gl_operator_is_supported (op))
return UNSUPPORTED ("unsupported operator");
}
setup->dst = dst;
setup->op = op;
return CAIRO_STATUS_SUCCESS;
}