[gl] Add radial gradients acceleration.

This is significantly cribbed from Zach Laine's work, but reworked so
that gradients can be plugged in as either source or mask operands for
any of the paths.
This commit is contained in:
Eric Anholt 2010-02-03 21:34:24 -08:00
parent 297b0ab47f
commit 696a715702
3 changed files with 216 additions and 3 deletions

View file

@ -92,6 +92,7 @@ typedef enum cairo_gl_shader_source {
CAIRO_GL_SHADER_SOURCE_TEXTURE,
CAIRO_GL_SHADER_SOURCE_TEXTURE_ALPHA,
CAIRO_GL_SHADER_SOURCE_LINEAR_GRADIENT,
CAIRO_GL_SHADER_SOURCE_RADIAL_GRADIENT,
CAIRO_GL_SHADER_SOURCE_COUNT,
} cairo_gl_shader_source_t;
@ -100,6 +101,7 @@ typedef enum cairo_gl_shader_mask {
CAIRO_GL_SHADER_MASK_TEXTURE,
CAIRO_GL_SHADER_MASK_TEXTURE_ALPHA,
CAIRO_GL_SHADER_MASK_LINEAR_GRADIENT,
CAIRO_GL_SHADER_MASK_RADIAL_GRADIENT,
CAIRO_GL_SHADER_MASK_NONE,
CAIRO_GL_SHADER_MASK_SPANS,
CAIRO_GL_SHADER_MASK_COUNT,
@ -140,6 +142,7 @@ enum cairo_gl_composite_operand_type {
OPERAND_CONSTANT,
OPERAND_TEXTURE,
OPERAND_LINEAR_GRADIENT,
OPERAND_RADIAL_GRADIENT,
};
/* This union structure describes a potential source or mask operand to the
@ -166,6 +169,16 @@ typedef struct cairo_gl_composite_operand {
float first_stop_offset;
float last_stop_offset;
} linear;
struct {
GLuint tex;
cairo_matrix_t m;
float circle_1_x;
float circle_1_y;
float radius_0;
float radius_1;
float first_stop_offset;
float last_stop_offset;
} radial;
} operand;
const cairo_pattern_t *pattern;

View file

@ -738,6 +738,39 @@ static const char *fs_source_linear_gradient =
" t = (t - source_first_offset) / (source_last_offset - source_first_offset);\n"
" return texture1D (source_sampler, t);\n"
"}\n";
static const char *fs_source_radial_gradient =
"uniform sampler1D source_sampler;\n"
"uniform mat4 source_matrix;\n"
"uniform vec2 source_circle_1;\n"
"uniform float source_radius_0;\n"
"uniform float source_radius_1;\n"
"uniform float source_first_offset;\n"
"uniform float source_last_offset;\n"
"\n"
"vec4 get_source()\n"
"{\n"
" vec2 pos = (source_matrix * vec4 (gl_FragCoord.xy, 0.0, 1.0)).xy;\n"
" \n"
" float dr = source_radius_1 - source_radius_0;\n"
" float dot_circle_1 = dot (source_circle_1, source_circle_1);\n"
" float dot_pos_circle_1 = dot (pos, source_circle_1);\n"
" \n"
" float A = dot_circle_1 - dr * dr;\n"
" float B = -2.0 * (dot_pos_circle_1 + source_radius_0 * dr);\n"
" float C = dot (pos, pos) - source_radius_0 * source_radius_0;\n"
" float det = B * B - 4.0 * A * C;\n"
" det = max (det, 0.0);\n"
" \n"
" float sqrt_det = sqrt (det);\n"
" /* This complicated bit of logic acts as\n"
" * \"if (A < 0.0) sqrt_det = -sqrt_det\", without the branch.\n"
" */\n"
" sqrt_det *= 1.0 + 2.0 * sign (min (A, 0.0));\n"
" \n"
" float t = (-B + sqrt_det) / (2.0 * A);\n"
" t = (t - source_first_offset) / (source_last_offset - source_first_offset);\n"
" return texture1D (source_sampler, t);\n"
"}\n";
static const char *fs_mask_constant =
"uniform vec4 constant_mask;\n"
"vec4 get_mask()\n"
@ -772,6 +805,39 @@ static const char *fs_mask_linear_gradient =
" t = (t - mask_first_offset) / (mask_last_offset - mask_first_offset);\n"
" return texture1D (mask_sampler, t);\n"
"}\n";
static const char *fs_mask_radial_gradient =
"uniform sampler1D mask_sampler;\n"
"uniform mat4 mask_matrix;\n"
"uniform vec2 mask_circle_1;\n"
"uniform float mask_radius_0;\n"
"uniform float mask_radius_1;\n"
"uniform float mask_first_offset;\n"
"uniform float mask_last_offset;\n"
"\n"
"vec4 get_mask()\n"
"{\n"
" vec2 pos = (mask_matrix * vec4 (gl_FragCoord.xy, 0.0, 1.0)).xy;\n"
" \n"
" float dr = mask_radius_1 - mask_radius_0;\n"
" float dot_circle_1 = dot (mask_circle_1, mask_circle_1);\n"
" float dot_pos_circle_1 = dot (pos, mask_circle_1);\n"
" \n"
" float A = dot_circle_1 - dr * dr;\n"
" float B = -2.0 * (dot_pos_circle_1 + mask_radius_0 * dr);\n"
" float C = dot (pos, pos) - mask_radius_0 * mask_radius_0;\n"
" float det = B * B - 4.0 * A * C;\n"
" det = max (det, 0.0);\n"
" \n"
" float sqrt_det = sqrt (det);\n"
" /* This complicated bit of logic acts as\n"
" * \"if (A < 0.0) sqrt_det = -sqrt_det\", without the branch.\n"
" */\n"
" sqrt_det *= 1.0 + 2.0 * sign (min (A, 0.0));\n"
" \n"
" float t = (-B + sqrt_det) / (2.0 * A);\n"
" t = (t - mask_first_offset) / (mask_last_offset - mask_first_offset);\n"
" return texture1D (mask_sampler, t);\n"
"}\n";
static const char *fs_mask_none =
"vec4 get_mask()\n"
"{\n"
@ -835,12 +901,14 @@ _cairo_gl_get_program (cairo_gl_context_t *ctx,
fs_source_texture,
fs_source_texture_alpha,
fs_source_linear_gradient,
fs_source_radial_gradient,
};
const char *mask_sources[CAIRO_GL_SHADER_MASK_COUNT] = {
fs_mask_constant,
fs_mask_texture,
fs_mask_texture_alpha,
fs_mask_linear_gradient,
fs_mask_radial_gradient,
fs_mask_none,
fs_mask_spans,
};
@ -873,11 +941,13 @@ _cairo_gl_get_program (cairo_gl_context_t *ctx,
1);
if (source == CAIRO_GL_SHADER_SOURCE_CONSTANT ||
source == CAIRO_GL_SHADER_SOURCE_LINEAR_GRADIENT) {
source == CAIRO_GL_SHADER_SOURCE_LINEAR_GRADIENT ||
source == CAIRO_GL_SHADER_SOURCE_RADIAL_GRADIENT) {
if (mask == CAIRO_GL_SHADER_MASK_SPANS)
vs_source = vs_spans_no_coords;
else if (mask == CAIRO_GL_SHADER_MASK_CONSTANT ||
mask == CAIRO_GL_SHADER_MASK_LINEAR_GRADIENT)
mask == CAIRO_GL_SHADER_MASK_LINEAR_GRADIENT ||
mask == CAIRO_GL_SHADER_MASK_RADIAL_GRADIENT)
vs_source = vs_no_coords;
else
vs_source = vs_mask_coords;
@ -885,7 +955,8 @@ _cairo_gl_get_program (cairo_gl_context_t *ctx,
if (mask == CAIRO_GL_SHADER_MASK_SPANS)
vs_source = vs_spans_source_coords;
else if (mask == CAIRO_GL_SHADER_MASK_CONSTANT ||
mask == CAIRO_GL_SHADER_MASK_LINEAR_GRADIENT)
mask == CAIRO_GL_SHADER_MASK_LINEAR_GRADIENT ||
mask == CAIRO_GL_SHADER_MASK_RADIAL_GRADIENT)
vs_source = vs_source_coords;
else
vs_source = vs_source_mask_coords;

View file

@ -1242,6 +1242,46 @@ _cairo_gl_gradient_operand_init(cairo_gl_composite_operand_t *operand,
operand->source = CAIRO_GL_SHADER_SOURCE_LINEAR_GRADIENT;
operand->mask = CAIRO_GL_SHADER_MASK_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);
if ((unsigned int)ctx->max_texture_size / 2 <= gradient->n_stops)
return CAIRO_INT_STATUS_UNSUPPORTED;
_cairo_gl_create_gradient_texture (ctx,
dst,
gradient,
&operand->operand.radial.tex,
&operand->operand.radial.first_stop_offset,
&operand->operand.radial.last_stop_offset);
/* Translation matrix from the destination fragment coordinates
* (pixels from lower left = 0,0) to the coordinates in the
*/
cairo_matrix_init_translate (&operand->operand.radial.m, -x0, -y0);
cairo_matrix_multiply (&operand->operand.radial.m,
&operand->pattern->matrix,
&operand->operand.radial.m);
cairo_matrix_translate (&operand->operand.radial.m, 0, dst->height);
cairo_matrix_scale (&operand->operand.radial.m, 1.0, -1.0);
operand->operand.radial.circle_1_x = x1 - x0;
operand->operand.radial.circle_1_y = y1 - y0;
operand->operand.radial.radius_0 = r0;
operand->operand.radial.radius_1 = r1;
operand->type = OPERAND_RADIAL_GRADIENT;
operand->source = CAIRO_GL_SHADER_SOURCE_RADIAL_GRADIENT;
operand->mask = CAIRO_GL_SHADER_MASK_RADIAL_GRADIENT;
return CAIRO_STATUS_SUCCESS;
}
return CAIRO_INT_STATUS_UNSUPPORTED;
@ -1290,6 +1330,9 @@ _cairo_gl_operand_destroy (cairo_gl_composite_operand_t *operand)
case OPERAND_LINEAR_GRADIENT:
glDeleteTextures (1, &operand->operand.linear.tex);
break;
case OPERAND_RADIAL_GRADIENT:
glDeleteTextures (1, &operand->operand.radial.tex);
break;
case OPERAND_TEXTURE:
if (operand->operand.texture.surface != NULL) {
cairo_gl_surface_t *surface = operand->operand.texture.surface;
@ -1423,6 +1466,42 @@ _cairo_gl_set_src_operand (cairo_gl_context_t *ctx,
assert (!_cairo_status_is_error (status));
break;
case OPERAND_RADIAL_GRADIENT:
glActiveTexture (GL_TEXTURE0);
glBindTexture (GL_TEXTURE_1D, setup->src.operand.linear.tex);
glEnable (GL_TEXTURE_1D);
status = bind_matrix_to_shader (setup->shader->program,
"source_matrix",
&setup->src.operand.radial.m);
assert (!_cairo_status_is_error (status));
status = bind_vec2_to_shader (setup->shader->program,
"source_circle_1",
setup->src.operand.radial.circle_1_x,
setup->src.operand.radial.circle_1_y);
assert (!_cairo_status_is_error (status));
status = bind_float_to_shader (setup->shader->program,
"source_radius_0",
setup->src.operand.radial.radius_0);
assert (!_cairo_status_is_error (status));
status = bind_float_to_shader (setup->shader->program,
"source_radius_1",
setup->src.operand.radial.radius_1);
assert (!_cairo_status_is_error (status));
status = bind_float_to_shader (setup->shader->program,
"source_first_offset",
setup->src.operand.radial.first_stop_offset);
assert (!_cairo_status_is_error (status));
status = bind_float_to_shader (setup->shader->program,
"source_last_offset",
setup->src.operand.radial.last_stop_offset);
break;
}
}
@ -1464,6 +1543,7 @@ _cairo_gl_set_src_alpha_operand (cairo_gl_context_t *ctx,
}
break;
case OPERAND_LINEAR_GRADIENT:
case OPERAND_RADIAL_GRADIENT:
assert(0);
}
}
@ -1499,6 +1579,48 @@ _cairo_gl_set_linear_gradient_mask_operand (cairo_gl_composite_setup_t *setup)
assert (!_cairo_status_is_error (status));
}
static void
_cairo_gl_set_radial_gradient_mask_operand (cairo_gl_composite_setup_t *setup)
{
cairo_status_t status;
assert(setup->shader);
glActiveTexture (GL_TEXTURE1);
glBindTexture (GL_TEXTURE_1D, setup->mask.operand.radial.tex);
glEnable (GL_TEXTURE_1D);
status = bind_matrix_to_shader (setup->shader->program,
"mask_matrix",
&setup->mask.operand.radial.m);
assert (!_cairo_status_is_error (status));
status = bind_vec2_to_shader (setup->shader->program,
"mask_circle_1",
setup->mask.operand.radial.circle_1_x,
setup->mask.operand.radial.circle_1_y);
assert (!_cairo_status_is_error (status));
status = bind_float_to_shader (setup->shader->program,
"mask_radius_0",
setup->mask.operand.radial.radius_0);
assert (!_cairo_status_is_error (status));
status = bind_float_to_shader (setup->shader->program,
"mask_radius_1",
setup->mask.operand.radial.radius_1);
assert (!_cairo_status_is_error (status));
status = bind_float_to_shader (setup->shader->program,
"mask_first_offset",
setup->mask.operand.radial.first_stop_offset);
assert (!_cairo_status_is_error (status));
status = bind_float_to_shader (setup->shader->program,
"mask_last_offset",
setup->mask.operand.radial.last_stop_offset);
}
/* This is like _cairo_gl_set_src_alpha_operand, for component alpha setup
* of the mask part of IN to produce a "source alpha" value.
*/
@ -1571,6 +1693,10 @@ _cairo_gl_set_component_alpha_mask_operand (cairo_gl_context_t *ctx,
case OPERAND_LINEAR_GRADIENT:
_cairo_gl_set_linear_gradient_mask_operand (setup);
break;
case OPERAND_RADIAL_GRADIENT:
_cairo_gl_set_radial_gradient_mask_operand (setup);
break;
}
}
@ -1959,6 +2085,9 @@ _cairo_gl_surface_composite (cairo_operator_t op,
case OPERAND_LINEAR_GRADIENT:
_cairo_gl_set_linear_gradient_mask_operand (&setup);
break;
case OPERAND_RADIAL_GRADIENT:
_cairo_gl_set_radial_gradient_mask_operand (&setup);
break;
}
}