cairo/src/cairo-gl-shaders.c
Martin Robinson 9741099093 gl: Remove the shader language version abstraction
Cairo only needs to support one version of the shader language API,
thanks to the dispatch table. This seems unlikely to change any time
soon. This makes the addition of new features, such as a uniform
location cache, simpler.
2012-08-22 10:42:18 -07:00

1042 lines
32 KiB
C

/* cairo - a vector graphics library with display and print output
*
* Copyright © 2009 T. Zachary Laine
* Copyright © 2010 Eric Anholt
* Copyright © 2010 Red Hat, Inc
* Copyright © 2010 Linaro Limited
*
* 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 T. Zachary Laine.
*
* Contributor(s):
* Benjamin Otte <otte@gnome.org>
* Eric Anholt <eric@anholt.net>
* T. Zachary Laine <whatwasthataddress@gmail.com>
* Alexandros Frantzis <alexandros.frantzis@linaro.org>
*/
#include "cairoint.h"
#include "cairo-gl-private.h"
#include "cairo-error-private.h"
#include "cairo-output-stream-private.h"
static cairo_status_t
_cairo_gl_shader_compile_and_link (cairo_gl_context_t *ctx,
cairo_gl_shader_t *shader,
cairo_gl_var_type_t src,
cairo_gl_var_type_t mask,
cairo_bool_t use_coverage,
const char *fragment_text);
typedef struct _cairo_shader_cache_entry {
cairo_cache_entry_t base;
cairo_gl_operand_type_t src;
cairo_gl_operand_type_t mask;
cairo_gl_operand_type_t dest;
cairo_bool_t use_coverage;
cairo_gl_shader_in_t in;
GLint src_gl_filter;
cairo_bool_t src_border_fade;
cairo_extend_t src_extend;
GLint mask_gl_filter;
cairo_bool_t mask_border_fade;
cairo_extend_t mask_extend;
cairo_gl_context_t *ctx; /* XXX: needed to destroy the program */
cairo_gl_shader_t shader;
} cairo_shader_cache_entry_t;
static cairo_bool_t
_cairo_gl_shader_cache_equal_desktop (const void *key_a, const void *key_b)
{
const cairo_shader_cache_entry_t *a = key_a;
const cairo_shader_cache_entry_t *b = key_b;
cairo_bool_t both_have_npot_repeat =
a->ctx->has_npot_repeat && b->ctx->has_npot_repeat;
return a->src == b->src &&
a->mask == b->mask &&
a->dest == b->dest &&
a->use_coverage == b->use_coverage &&
a->in == b->in &&
(both_have_npot_repeat || a->src_extend == b->src_extend) &&
(both_have_npot_repeat || a->mask_extend == b->mask_extend);
}
/*
* For GLES2 we use more complicated shaders to implement missing GL
* features. In this case we need more parameters to uniquely identify
* a shader (vs _cairo_gl_shader_cache_equal_desktop()).
*/
static cairo_bool_t
_cairo_gl_shader_cache_equal_gles2 (const void *key_a, const void *key_b)
{
const cairo_shader_cache_entry_t *a = key_a;
const cairo_shader_cache_entry_t *b = key_b;
cairo_bool_t both_have_npot_repeat =
a->ctx->has_npot_repeat && b->ctx->has_npot_repeat;
return a->src == b->src &&
a->mask == b->mask &&
a->dest == b->dest &&
a->use_coverage == b->use_coverage &&
a->in == b->in &&
a->src_gl_filter == b->src_gl_filter &&
a->src_border_fade == b->src_border_fade &&
(both_have_npot_repeat || a->src_extend == b->src_extend) &&
a->mask_gl_filter == b->mask_gl_filter &&
a->mask_border_fade == b->mask_border_fade &&
(both_have_npot_repeat || a->mask_extend == b->mask_extend);
}
static unsigned long
_cairo_gl_shader_cache_hash (const cairo_shader_cache_entry_t *entry)
{
return (entry->src << 24) | (entry->mask << 16) | (entry->dest << 8) | (entry->in << 1) | entry->use_coverage;
}
static void
_cairo_gl_shader_cache_destroy (void *data)
{
cairo_shader_cache_entry_t *entry = data;
_cairo_gl_shader_fini (entry->ctx, &entry->shader);
if (entry->ctx->current_shader == &entry->shader)
entry->ctx->current_shader = NULL;
free (entry);
}
static void
_cairo_gl_shader_init (cairo_gl_shader_t *shader)
{
shader->fragment_shader = 0;
shader->program = 0;
}
cairo_status_t
_cairo_gl_context_init_shaders (cairo_gl_context_t *ctx)
{
static const char *fill_fs_source =
"#ifdef GL_ES\n"
"precision mediump float;\n"
"#endif\n"
"uniform vec4 color;\n"
"void main()\n"
"{\n"
" gl_FragColor = color;\n"
"}\n";
cairo_status_t status;
if (_cairo_gl_get_version () >= CAIRO_GL_VERSION_ENCODE (2, 0) ||
(_cairo_gl_has_extension ("GL_ARB_shader_objects") &&
_cairo_gl_has_extension ("GL_ARB_fragment_shader") &&
_cairo_gl_has_extension ("GL_ARB_vertex_shader"))) {
ctx->has_shader_support = TRUE;
} else {
ctx->has_shader_support = FALSE;
fprintf (stderr, "Error: The cairo gl backend requires shader support!\n");
return CAIRO_STATUS_DEVICE_ERROR;
}
memset (ctx->vertex_shaders, 0, sizeof (ctx->vertex_shaders));
status = _cairo_cache_init (&ctx->shaders,
ctx->gl_flavor == CAIRO_GL_FLAVOR_DESKTOP ?
_cairo_gl_shader_cache_equal_desktop :
_cairo_gl_shader_cache_equal_gles2,
NULL,
_cairo_gl_shader_cache_destroy,
CAIRO_GL_MAX_SHADERS_PER_CONTEXT);
if (unlikely (status))
return status;
_cairo_gl_shader_init (&ctx->fill_rectangles_shader);
status = _cairo_gl_shader_compile_and_link (ctx,
&ctx->fill_rectangles_shader,
CAIRO_GL_VAR_NONE,
CAIRO_GL_VAR_NONE,
FALSE,
fill_fs_source);
if (unlikely (status))
return status;
return CAIRO_STATUS_SUCCESS;
}
void
_cairo_gl_context_fini_shaders (cairo_gl_context_t *ctx)
{
int i;
for (i = 0; i <= CAIRO_GL_VAR_TYPE_MAX; i++) {
if (ctx->vertex_shaders[i])
ctx->dispatch.DeleteShader (ctx->vertex_shaders[i]);
}
_cairo_cache_fini (&ctx->shaders);
}
void
_cairo_gl_shader_fini (cairo_gl_context_t *ctx,
cairo_gl_shader_t *shader)
{
if (shader->fragment_shader)
ctx->dispatch.DeleteShader (shader->fragment_shader);
if (shader->program)
ctx->dispatch.DeleteProgram (shader->program);
}
static const char *operand_names[] = { "source", "mask", "dest" };
static cairo_gl_var_type_t
cairo_gl_operand_get_var_type (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:
return CAIRO_GL_VAR_NONE;
case CAIRO_GL_OPERAND_LINEAR_GRADIENT:
case CAIRO_GL_OPERAND_RADIAL_GRADIENT_A0:
case CAIRO_GL_OPERAND_RADIAL_GRADIENT_NONE:
case CAIRO_GL_OPERAND_RADIAL_GRADIENT_EXT:
case CAIRO_GL_OPERAND_TEXTURE:
return CAIRO_GL_VAR_TEXCOORDS;
}
}
static void
cairo_gl_shader_emit_variable (cairo_output_stream_t *stream,
cairo_gl_var_type_t type,
cairo_gl_tex_t name)
{
switch (type) {
default:
ASSERT_NOT_REACHED;
case CAIRO_GL_VAR_NONE:
break;
case CAIRO_GL_VAR_TEXCOORDS:
_cairo_output_stream_printf (stream,
"varying vec2 %s_texcoords;\n",
operand_names[name]);
break;
}
}
static void
cairo_gl_shader_emit_vertex (cairo_output_stream_t *stream,
cairo_gl_var_type_t type,
cairo_gl_tex_t name)
{
switch (type) {
default:
ASSERT_NOT_REACHED;
case CAIRO_GL_VAR_NONE:
break;
case CAIRO_GL_VAR_TEXCOORDS:
_cairo_output_stream_printf (stream,
" %s_texcoords = MultiTexCoord%d.xy;\n",
operand_names[name], name);
break;
}
}
static void
cairo_gl_shader_dcl_coverage (cairo_output_stream_t *stream)
{
_cairo_output_stream_printf (stream, "varying float coverage;\n");
}
static void
cairo_gl_shader_def_coverage (cairo_output_stream_t *stream)
{
_cairo_output_stream_printf (stream, " coverage = Color.a;\n");
}
static cairo_status_t
cairo_gl_shader_get_vertex_source (cairo_gl_var_type_t src,
cairo_gl_var_type_t mask,
cairo_bool_t use_coverage,
cairo_gl_var_type_t dest,
char **out)
{
cairo_output_stream_t *stream = _cairo_memory_stream_create ();
unsigned char *source;
unsigned long length;
cairo_status_t status;
cairo_gl_shader_emit_variable (stream, src, CAIRO_GL_TEX_SOURCE);
cairo_gl_shader_emit_variable (stream, mask, CAIRO_GL_TEX_MASK);
if (use_coverage)
cairo_gl_shader_dcl_coverage (stream);
_cairo_output_stream_printf (stream,
"attribute vec4 Vertex;\n"
"attribute vec4 Color;\n"
"attribute vec4 MultiTexCoord0;\n"
"attribute vec4 MultiTexCoord1;\n"
"uniform mat4 ModelViewProjectionMatrix;\n"
"void main()\n"
"{\n"
" gl_Position = ModelViewProjectionMatrix * Vertex;\n");
cairo_gl_shader_emit_vertex (stream, src, CAIRO_GL_TEX_SOURCE);
cairo_gl_shader_emit_vertex (stream, mask, CAIRO_GL_TEX_MASK);
if (use_coverage)
cairo_gl_shader_def_coverage (stream);
_cairo_output_stream_write (stream,
"}\n\0", 3);
status = _cairo_memory_stream_destroy (stream, &source, &length);
if (unlikely (status))
return status;
*out = (char *) source;
return CAIRO_STATUS_SUCCESS;
}
/*
* Returns whether an operand needs a special border fade fragment shader
* to simulate the GL_CLAMP_TO_BORDER wrapping method that is missing in GLES2.
*/
static cairo_bool_t
_cairo_gl_shader_needs_border_fade (cairo_gl_operand_t *operand)
{
cairo_extend_t extend =_cairo_gl_operand_get_extend (operand);
return extend == CAIRO_EXTEND_NONE &&
(operand->type == CAIRO_GL_OPERAND_TEXTURE ||
operand->type == CAIRO_GL_OPERAND_LINEAR_GRADIENT ||
operand->type == CAIRO_GL_OPERAND_RADIAL_GRADIENT_NONE ||
operand->type == CAIRO_GL_OPERAND_RADIAL_GRADIENT_A0);
}
static void
cairo_gl_shader_emit_color (cairo_output_stream_t *stream,
cairo_gl_context_t *ctx,
cairo_gl_operand_t *op,
cairo_gl_tex_t name)
{
const char *namestr = operand_names[name];
const char *rectstr = (ctx->tex_target == GL_TEXTURE_RECTANGLE ? "Rect" : "");
switch (op->type) {
case CAIRO_GL_OPERAND_COUNT:
default:
ASSERT_NOT_REACHED;
break;
case CAIRO_GL_OPERAND_NONE:
_cairo_output_stream_printf (stream,
"vec4 get_%s()\n"
"{\n"
" return vec4 (0, 0, 0, 1);\n"
"}\n",
namestr);
break;
case CAIRO_GL_OPERAND_CONSTANT:
_cairo_output_stream_printf (stream,
"uniform vec4 %s_constant;\n"
"vec4 get_%s()\n"
"{\n"
" return %s_constant;\n"
"}\n",
namestr, namestr, namestr);
break;
case CAIRO_GL_OPERAND_TEXTURE:
_cairo_output_stream_printf (stream,
"uniform sampler2D%s %s_sampler;\n"
"uniform vec2 %s_texdims;\n"
"varying vec2 %s_texcoords;\n"
"vec4 get_%s()\n"
"{\n",
rectstr, namestr, namestr, namestr, namestr);
if (ctx->gl_flavor == CAIRO_GL_FLAVOR_ES &&
_cairo_gl_shader_needs_border_fade (op))
{
_cairo_output_stream_printf (stream,
" vec2 border_fade = %s_border_fade (%s_texcoords, %s_texdims);\n"
" vec4 texel = texture2D%s (%s_sampler, %s_texcoords);\n"
" return texel * border_fade.x * border_fade.y;\n"
"}\n",
namestr, namestr, namestr, rectstr, namestr, namestr);
}
else
{
_cairo_output_stream_printf (stream,
" return texture2D%s (%s_sampler, %s_wrap (%s_texcoords));\n"
"}\n",
rectstr, namestr, namestr, namestr);
}
break;
case CAIRO_GL_OPERAND_LINEAR_GRADIENT:
_cairo_output_stream_printf (stream,
"varying vec2 %s_texcoords;\n"
"uniform vec2 %s_texdims;\n"
"uniform sampler2D%s %s_sampler;\n"
"\n"
"vec4 get_%s()\n"
"{\n",
namestr, namestr, rectstr, namestr, namestr);
if (ctx->gl_flavor == CAIRO_GL_FLAVOR_ES &&
_cairo_gl_shader_needs_border_fade (op))
{
_cairo_output_stream_printf (stream,
" float border_fade = %s_border_fade (%s_texcoords.x, %s_texdims.x);\n"
" vec4 texel = texture2D%s (%s_sampler, vec2 (%s_texcoords.x, 0.5));\n"
" return texel * border_fade;\n"
"}\n",
namestr, namestr, namestr, rectstr, namestr, namestr);
}
else
{
_cairo_output_stream_printf (stream,
" return texture2D%s (%s_sampler, %s_wrap (vec2 (%s_texcoords.x, 0.5)));\n"
"}\n",
rectstr, namestr, namestr, namestr);
}
break;
case CAIRO_GL_OPERAND_RADIAL_GRADIENT_A0:
_cairo_output_stream_printf (stream,
"varying vec2 %s_texcoords;\n"
"uniform vec2 %s_texdims;\n"
"uniform sampler2D%s %s_sampler;\n"
"uniform vec3 %s_circle_d;\n"
"uniform float %s_radius_0;\n"
"\n"
"vec4 get_%s()\n"
"{\n"
" vec3 pos = vec3 (%s_texcoords, %s_radius_0);\n"
" \n"
" float B = dot (pos, %s_circle_d);\n"
" float C = dot (pos, vec3 (pos.xy, -pos.z));\n"
" \n"
" float t = 0.5 * C / B;\n"
" float is_valid = step (-%s_radius_0, t * %s_circle_d.z);\n",
namestr, namestr, rectstr, namestr, namestr, namestr, namestr,
namestr, namestr, namestr, namestr, namestr);
if (ctx->gl_flavor == CAIRO_GL_FLAVOR_ES &&
_cairo_gl_shader_needs_border_fade (op))
{
_cairo_output_stream_printf (stream,
" float border_fade = %s_border_fade (t, %s_texdims.x);\n"
" vec4 texel = texture2D%s (%s_sampler, vec2 (t, 0.5));\n"
" return mix (vec4 (0.0), texel * border_fade, is_valid);\n"
"}\n",
namestr, namestr, rectstr, namestr);
}
else
{
_cairo_output_stream_printf (stream,
" vec4 texel = texture2D%s (%s_sampler, %s_wrap (vec2 (t, 0.5)));\n"
" return mix (vec4 (0.0), texel, is_valid);\n"
"}\n",
rectstr, namestr, namestr);
}
break;
case CAIRO_GL_OPERAND_RADIAL_GRADIENT_NONE:
_cairo_output_stream_printf (stream,
"varying vec2 %s_texcoords;\n"
"uniform vec2 %s_texdims;\n"
"uniform sampler2D%s %s_sampler;\n"
"uniform vec3 %s_circle_d;\n"
"uniform float %s_a;\n"
"uniform float %s_radius_0;\n"
"\n"
"vec4 get_%s()\n"
"{\n"
" vec3 pos = vec3 (%s_texcoords, %s_radius_0);\n"
" \n"
" float B = dot (pos, %s_circle_d);\n"
" float C = dot (pos, vec3 (pos.xy, -pos.z));\n"
" \n"
" float det = dot (vec2 (B, %s_a), vec2 (B, -C));\n"
" float sqrtdet = sqrt (abs (det));\n"
" vec2 t = (B + vec2 (sqrtdet, -sqrtdet)) / %s_a;\n"
" \n"
" vec2 is_valid = step (vec2 (0.0), t) * step (t, vec2(1.0));\n"
" float has_color = step (0., det) * max (is_valid.x, is_valid.y);\n"
" \n"
" float upper_t = mix (t.y, t.x, is_valid.x);\n",
namestr, namestr, rectstr, namestr, namestr, namestr, namestr,
namestr, namestr, namestr, namestr, namestr, namestr);
if (ctx->gl_flavor == CAIRO_GL_FLAVOR_ES &&
_cairo_gl_shader_needs_border_fade (op))
{
_cairo_output_stream_printf (stream,
" float border_fade = %s_border_fade (upper_t, %s_texdims.x);\n"
" vec4 texel = texture2D%s (%s_sampler, vec2 (upper_t, 0.5));\n"
" return mix (vec4 (0.0), texel * border_fade, has_color);\n"
"}\n",
namestr, namestr, rectstr, namestr);
}
else
{
_cairo_output_stream_printf (stream,
" vec4 texel = texture2D%s (%s_sampler, %s_wrap (vec2(upper_t, 0.5)));\n"
" return mix (vec4 (0.0), texel, has_color);\n"
"}\n",
rectstr, namestr, namestr);
}
break;
case CAIRO_GL_OPERAND_RADIAL_GRADIENT_EXT:
_cairo_output_stream_printf (stream,
"varying vec2 %s_texcoords;\n"
"uniform sampler2D%s %s_sampler;\n"
"uniform vec3 %s_circle_d;\n"
"uniform float %s_a;\n"
"uniform float %s_radius_0;\n"
"\n"
"vec4 get_%s()\n"
"{\n"
" vec3 pos = vec3 (%s_texcoords, %s_radius_0);\n"
" \n"
" float B = dot (pos, %s_circle_d);\n"
" float C = dot (pos, vec3 (pos.xy, -pos.z));\n"
" \n"
" float det = dot (vec2 (B, %s_a), vec2 (B, -C));\n"
" float sqrtdet = sqrt (abs (det));\n"
" vec2 t = (B + vec2 (sqrtdet, -sqrtdet)) / %s_a;\n"
" \n"
" vec2 is_valid = step (vec2 (-%s_radius_0), t * %s_circle_d.z);\n"
" float has_color = step (0., det) * max (is_valid.x, is_valid.y);\n"
" \n"
" float upper_t = mix (t.y, t.x, is_valid.x);\n"
" vec4 texel = texture2D%s (%s_sampler, %s_wrap (vec2(upper_t, 0.5)));\n"
" return mix (vec4 (0.0), texel, has_color);\n"
"}\n",
namestr, rectstr, namestr, namestr, namestr, namestr,
namestr, namestr, namestr, namestr, namestr,
namestr, namestr, namestr, rectstr, namestr, namestr);
break;
}
}
/*
* Emits the border fade functions used by an operand.
*
* If bilinear filtering is used, the emitted function performs a linear
* fade to transparency effect in the intervals [-1/2n, 1/2n] and
* [1 - 1/2n, 1 + 1/2n] (n: texture size).
*
* If nearest filtering is used, the emitted function just returns
* 0.0 for all values outside [0, 1).
*/
static void
_cairo_gl_shader_emit_border_fade (cairo_output_stream_t *stream,
cairo_gl_operand_t *operand,
cairo_gl_tex_t name)
{
const char *namestr = operand_names[name];
GLint gl_filter = _cairo_gl_operand_get_gl_filter (operand);
/* 2D version */
_cairo_output_stream_printf (stream,
"vec2 %s_border_fade (vec2 coords, vec2 dims)\n"
"{\n",
namestr);
if (gl_filter == GL_LINEAR)
_cairo_output_stream_printf (stream,
" return clamp(-abs(dims * (coords - 0.5)) + (dims + vec2(1.0)) * 0.5, 0.0, 1.0);\n");
else
_cairo_output_stream_printf (stream,
" bvec2 in_tex1 = greaterThanEqual (coords, vec2 (0.0));\n"
" bvec2 in_tex2 = lessThan (coords, vec2 (1.0));\n"
" return vec2 (float (all (in_tex1) && all (in_tex2)));\n");
_cairo_output_stream_printf (stream, "}\n");
/* 1D version */
_cairo_output_stream_printf (stream,
"float %s_border_fade (float x, float dim)\n"
"{\n",
namestr);
if (gl_filter == GL_LINEAR)
_cairo_output_stream_printf (stream,
" return clamp(-abs(dim * (x - 0.5)) + (dim + 1.0) * 0.5, 0.0, 1.0);\n");
else
_cairo_output_stream_printf (stream,
" bool in_tex = x >= 0.0 && x < 1.0;\n"
" return float (in_tex);\n");
_cairo_output_stream_printf (stream, "}\n");
}
/*
* Emits the wrap function used by an operand.
*
* In OpenGL ES 2.0, repeat wrap modes (GL_REPEAT and GL_MIRRORED REPEAT) are
* only available for NPOT textures if the GL_OES_texture_npot is supported.
* If GL_OES_texture_npot is not supported, we need to implement the wrapping
* functionality in the shader.
*/
static void
_cairo_gl_shader_emit_wrap (cairo_gl_context_t *ctx,
cairo_output_stream_t *stream,
cairo_gl_operand_t *operand,
cairo_gl_tex_t name)
{
const char *namestr = operand_names[name];
cairo_extend_t extend = _cairo_gl_operand_get_extend (operand);
_cairo_output_stream_printf (stream,
"vec2 %s_wrap(vec2 coords)\n"
"{\n",
namestr);
if (! ctx->has_npot_repeat &&
(extend == CAIRO_EXTEND_REPEAT || extend == CAIRO_EXTEND_REFLECT))
{
if (extend == CAIRO_EXTEND_REPEAT) {
_cairo_output_stream_printf (stream,
" return fract(coords);\n");
} else { /* CAIRO_EXTEND_REFLECT */
_cairo_output_stream_printf (stream,
" return mix(fract(coords), 1.0 - fract(coords), floor(mod(coords, 2.0)));\n");
}
}
else
{
_cairo_output_stream_printf (stream, " return coords;\n");
}
_cairo_output_stream_printf (stream, "}\n");
}
static cairo_status_t
cairo_gl_shader_get_fragment_source (cairo_gl_context_t *ctx,
cairo_gl_shader_in_t in,
cairo_gl_operand_t *src,
cairo_gl_operand_t *mask,
cairo_bool_t use_coverage,
cairo_gl_operand_type_t dest_type,
char **out)
{
cairo_output_stream_t *stream = _cairo_memory_stream_create ();
unsigned char *source;
unsigned long length;
cairo_status_t status;
const char *coverage_str;
_cairo_output_stream_printf (stream,
"#ifdef GL_ES\n"
"precision mediump float;\n"
"#endif\n");
_cairo_gl_shader_emit_wrap (ctx, stream, src, CAIRO_GL_TEX_SOURCE);
_cairo_gl_shader_emit_wrap (ctx, stream, mask, CAIRO_GL_TEX_MASK);
if (ctx->gl_flavor == CAIRO_GL_FLAVOR_ES) {
if (_cairo_gl_shader_needs_border_fade (src))
_cairo_gl_shader_emit_border_fade (stream, src, CAIRO_GL_TEX_SOURCE);
if (_cairo_gl_shader_needs_border_fade (mask))
_cairo_gl_shader_emit_border_fade (stream, mask, CAIRO_GL_TEX_MASK);
}
cairo_gl_shader_emit_color (stream, ctx, src, CAIRO_GL_TEX_SOURCE);
cairo_gl_shader_emit_color (stream, ctx, mask, CAIRO_GL_TEX_MASK);
coverage_str = "";
if (use_coverage) {
_cairo_output_stream_printf (stream, "varying float coverage;\n");
coverage_str = " * coverage";
}
_cairo_output_stream_printf (stream,
"void main()\n"
"{\n");
switch (in) {
case CAIRO_GL_SHADER_IN_COUNT:
default:
ASSERT_NOT_REACHED;
case CAIRO_GL_SHADER_IN_NORMAL:
_cairo_output_stream_printf (stream,
" gl_FragColor = get_source() * get_mask().a%s;\n",
coverage_str);
break;
case CAIRO_GL_SHADER_IN_CA_SOURCE:
_cairo_output_stream_printf (stream,
" gl_FragColor = get_source() * get_mask()%s;\n",
coverage_str);
break;
case CAIRO_GL_SHADER_IN_CA_SOURCE_ALPHA:
_cairo_output_stream_printf (stream,
" gl_FragColor = get_source().a * get_mask()%s;\n",
coverage_str);
break;
}
_cairo_output_stream_write (stream,
"}\n\0", 3);
status = _cairo_memory_stream_destroy (stream, &source, &length);
if (unlikely (status))
return status;
*out = (char *) source;
return CAIRO_STATUS_SUCCESS;
}
static void
compile_shader (cairo_gl_context_t *ctx,
GLuint *shader,
GLenum type,
const char *source)
{
cairo_gl_dispatch_t *dispatch = &ctx->dispatch;
GLint success, log_size, num_chars;
char *log;
*shader = dispatch->CreateShader (type);
dispatch->ShaderSource (*shader, 1, &source, 0);
dispatch->CompileShader (*shader);
dispatch->GetShaderiv (*shader, GL_COMPILE_STATUS, &success);
if (success)
return;
dispatch->GetShaderiv (*shader, GL_INFO_LOG_LENGTH, &log_size);
if (log_size < 0) {
printf ("OpenGL shader compilation failed.\n");
ASSERT_NOT_REACHED;
return;
}
log = _cairo_malloc (log_size + 1);
dispatch->GetShaderInfoLog (*shader, log_size, &num_chars, log);
log[num_chars] = '\0';
printf ("OpenGL shader compilation failed. Shader:\n%s\n", source);
printf ("OpenGL compilation log:\n%s\n", log);
free (log);
ASSERT_NOT_REACHED;
}
static void
link_shader_program (cairo_gl_context_t *ctx,
GLuint *program,
GLuint vert,
GLuint frag)
{
cairo_gl_dispatch_t *dispatch = &ctx->dispatch;
GLint success, log_size, num_chars;
char *log;
*program = dispatch->CreateProgram ();
dispatch->AttachShader (*program, vert);
dispatch->AttachShader (*program, frag);
dispatch->BindAttribLocation (*program, CAIRO_GL_VERTEX_ATTRIB_INDEX,
"Vertex");
dispatch->BindAttribLocation (*program, CAIRO_GL_COLOR_ATTRIB_INDEX,
"Color");
dispatch->BindAttribLocation (*program, CAIRO_GL_TEXCOORD0_ATTRIB_INDEX,
"MultiTexCoord0");
dispatch->BindAttribLocation (*program, CAIRO_GL_TEXCOORD1_ATTRIB_INDEX,
"MultiTexCoord1");
dispatch->LinkProgram (*program);
dispatch->GetProgramiv (*program, GL_LINK_STATUS, &success);
if (success)
return;
dispatch->GetProgramiv (*program, GL_INFO_LOG_LENGTH, &log_size);
if (log_size < 0) {
printf ("OpenGL shader link failed.\n");
ASSERT_NOT_REACHED;
return;
}
log = _cairo_malloc (log_size + 1);
dispatch->GetProgramInfoLog (*program, log_size, &num_chars, log);
log[num_chars] = '\0';
printf ("OpenGL shader link failed:\n%s\n", log);
free (log);
ASSERT_NOT_REACHED;
}
static cairo_status_t
_cairo_gl_shader_compile_and_link (cairo_gl_context_t *ctx,
cairo_gl_shader_t *shader,
cairo_gl_var_type_t src,
cairo_gl_var_type_t mask,
cairo_bool_t use_coverage,
const char *fragment_text)
{
unsigned int vertex_shader;
cairo_status_t status;
assert (shader->program == 0);
vertex_shader = cairo_gl_var_type_hash (src, mask, use_coverage,
CAIRO_GL_VAR_NONE);
if (ctx->vertex_shaders[vertex_shader] == 0) {
char *source;
status = cairo_gl_shader_get_vertex_source (src,
mask,
use_coverage,
CAIRO_GL_VAR_NONE,
&source);
if (unlikely (status))
goto FAILURE;
compile_shader (ctx, &ctx->vertex_shaders[vertex_shader],
GL_VERTEX_SHADER, source);
free (source);
}
compile_shader (ctx, &shader->fragment_shader,
GL_FRAGMENT_SHADER, fragment_text);
link_shader_program (ctx, &shader->program,
ctx->vertex_shaders[vertex_shader],
shader->fragment_shader);
return CAIRO_STATUS_SUCCESS;
FAILURE:
_cairo_gl_shader_fini (ctx, shader);
shader->fragment_shader = 0;
shader->program = 0;
return status;
}
/* We always bind the source to texture unit 0 if present, and mask to
* texture unit 1 if present, so we can just initialize these once at
* compile time.
*/
static void
_cairo_gl_shader_set_samplers (cairo_gl_context_t *ctx,
cairo_gl_shader_t *shader)
{
cairo_gl_dispatch_t *dispatch = &ctx->dispatch;
GLint location;
GLint saved_program;
/* We have to save/restore the current program because we might be
* asked for a different program while a shader is bound. This shouldn't
* be a performance issue, since this is only called once per compile.
*/
glGetIntegerv (GL_CURRENT_PROGRAM, &saved_program);
dispatch->UseProgram (shader->program);
location = dispatch->GetUniformLocation (shader->program, "source_sampler");
if (location != -1) {
dispatch->Uniform1i (location, CAIRO_GL_TEX_SOURCE);
}
location = dispatch->GetUniformLocation (shader->program, "mask_sampler");
if (location != -1) {
dispatch->Uniform1i (location, CAIRO_GL_TEX_MASK);
}
dispatch->UseProgram (saved_program);
}
void
_cairo_gl_shader_bind_float (cairo_gl_context_t *ctx,
const char *name,
float value)
{
cairo_gl_dispatch_t *dispatch = &ctx->dispatch;
GLint location = dispatch->GetUniformLocation (ctx->current_shader->program,
name);
assert (location != -1);
dispatch->Uniform1f (location, value);
}
void
_cairo_gl_shader_bind_vec2 (cairo_gl_context_t *ctx,
const char *name,
float value0,
float value1)
{
cairo_gl_dispatch_t *dispatch = &ctx->dispatch;
GLint location = dispatch->GetUniformLocation (ctx->current_shader->program,
name);
assert (location != -1);
dispatch->Uniform2f (location, value0, value1);
}
void
_cairo_gl_shader_bind_vec3 (cairo_gl_context_t *ctx,
const char *name,
float value0,
float value1,
float value2)
{
cairo_gl_dispatch_t *dispatch = &ctx->dispatch;
GLint location = dispatch->GetUniformLocation (ctx->current_shader->program,
name);
assert (location != -1);
dispatch->Uniform3f (location, value0, value1, value2);
}
void
_cairo_gl_shader_bind_vec4 (cairo_gl_context_t *ctx,
const char *name,
float value0, float value1,
float value2, float value3)
{
cairo_gl_dispatch_t *dispatch = &ctx->dispatch;
GLint location = dispatch->GetUniformLocation (ctx->current_shader->program,
name);
assert (location != -1);
dispatch->Uniform4f (location, value0, value1, value2, value3);
}
void
_cairo_gl_shader_bind_matrix (cairo_gl_context_t *ctx,
const char *name, cairo_matrix_t* m)
{
cairo_gl_dispatch_t *dispatch = &ctx->dispatch;
GLint location = dispatch->GetUniformLocation (ctx->current_shader->program,
name);
float gl_m[9] = {
m->xx, m->xy, m->x0,
m->yx, m->yy, m->y0,
0, 0, 1
};
assert (location != -1);
dispatch->UniformMatrix3fv (location, 1, GL_TRUE, gl_m);
}
void
_cairo_gl_shader_bind_matrix4f (cairo_gl_context_t *ctx,
const char *name, GLfloat* gl_m)
{
cairo_gl_dispatch_t *dispatch = &ctx->dispatch;
GLint location = dispatch->GetUniformLocation (ctx->current_shader->program,
name);
assert (location != -1);
dispatch->UniformMatrix4fv (location, 1, GL_FALSE, gl_m);
}
void
_cairo_gl_set_shader (cairo_gl_context_t *ctx,
cairo_gl_shader_t *shader)
{
if (ctx->current_shader == shader)
return;
if (shader)
ctx->dispatch.UseProgram (shader->program);
else
ctx->dispatch.UseProgram (0);
ctx->current_shader = shader;
}
cairo_status_t
_cairo_gl_get_shader_by_type (cairo_gl_context_t *ctx,
cairo_gl_operand_t *source,
cairo_gl_operand_t *mask,
cairo_bool_t use_coverage,
cairo_gl_shader_in_t in,
cairo_gl_shader_t **shader)
{
cairo_shader_cache_entry_t lookup, *entry;
char *fs_source;
cairo_status_t status;
lookup.ctx = ctx;
lookup.src = source->type;
lookup.mask = mask->type;
lookup.dest = CAIRO_GL_OPERAND_NONE;
lookup.use_coverage = use_coverage;
lookup.in = in;
lookup.src_gl_filter = _cairo_gl_operand_get_gl_filter (source);
lookup.src_border_fade = _cairo_gl_shader_needs_border_fade (source);
lookup.src_extend = _cairo_gl_operand_get_extend (source);
lookup.mask_gl_filter = _cairo_gl_operand_get_gl_filter (mask);
lookup.mask_border_fade = _cairo_gl_shader_needs_border_fade (mask);
lookup.mask_extend = _cairo_gl_operand_get_extend (mask);
lookup.base.hash = _cairo_gl_shader_cache_hash (&lookup);
lookup.base.size = 1;
entry = _cairo_cache_lookup (&ctx->shaders, &lookup.base);
if (entry) {
assert (entry->shader.program);
*shader = &entry->shader;
return CAIRO_STATUS_SUCCESS;
}
status = cairo_gl_shader_get_fragment_source (ctx,
in,
source,
mask,
use_coverage,
CAIRO_GL_OPERAND_NONE,
&fs_source);
if (unlikely (status))
return status;
entry = malloc (sizeof (cairo_shader_cache_entry_t));
if (unlikely (entry == NULL)) {
free (fs_source);
return _cairo_error (CAIRO_STATUS_NO_MEMORY);
}
memcpy (entry, &lookup, sizeof (cairo_shader_cache_entry_t));
entry->ctx = ctx;
_cairo_gl_shader_init (&entry->shader);
status = _cairo_gl_shader_compile_and_link (ctx,
&entry->shader,
cairo_gl_operand_get_var_type (source->type),
cairo_gl_operand_get_var_type (mask->type),
use_coverage,
fs_source);
free (fs_source);
if (unlikely (status)) {
free (entry);
return status;
}
_cairo_gl_shader_set_samplers (ctx, &entry->shader);
status = _cairo_cache_insert (&ctx->shaders, &entry->base);
if (unlikely (status)) {
_cairo_gl_shader_fini (ctx, &entry->shader);
free (entry);
return status;
}
*shader = &entry->shader;
return CAIRO_STATUS_SUCCESS;
}