diff --git a/src/cairo-gl-private.h b/src/cairo-gl-private.h index b0f60a605..e4fe0006b 100644 --- a/src/cairo-gl-private.h +++ b/src/cairo-gl-private.h @@ -34,6 +34,7 @@ * Contributor(s): * Carl Worth * Chris Wilson + * T. Zachary Laine */ #ifndef CAIRO_GL_PRIVATE_H @@ -79,6 +80,13 @@ typedef struct cairo_gl_glyph_cache { unsigned int width, height; } cairo_gl_glyph_cache_t; +typedef struct cairo_gl_shader_program { + GLuint vertex_shader; + GLuint fragment_shader; + GLuint program; + cairo_bool_t build_failure; +} cairo_gl_shader_program_t; + typedef struct _cairo_gl_context { cairo_device_t base; @@ -241,6 +249,47 @@ _cairo_gl_y_flip (cairo_gl_surface_t *surface, int y) return (surface->height - 1) - y; } +void +init_shader_program (cairo_gl_shader_program_t *program); + +void +destroy_shader_program (cairo_gl_shader_program_t *program); + +cairo_status_t +create_shader_program (cairo_gl_shader_program_t *program, + const char *vertex_text, + const char *fragment_text); + +cairo_status_t +create_linear_gradient_shader_program (cairo_gl_shader_program_t *program); + +cairo_status_t +create_radial_gradient_shader_program (cairo_gl_shader_program_t *program); + +cairo_status_t +bind_float_to_shader (GLuint program, const char *name, + float value); + +cairo_status_t +bind_vec2_to_shader (GLuint program, const char *name, + float value0, float value1); + +cairo_status_t +bind_vec3_to_shader (GLuint program, const char *name, + float value0, float value1, + float value2); + +cairo_status_t +bind_vec4_to_shader (GLuint program, const char *name, + float value0, float value1, + float value2, float value3); + +cairo_status_t +bind_matrix_to_shader (GLuint program, const char *name, cairo_matrix_t* m); + +cairo_status_t +bind_texture_to_shader (GLuint program, const char *name, GLuint tex_unit); + slim_hidden_proto (cairo_gl_surface_create); #endif /* CAIRO_GL_PRIVATE_H */ diff --git a/src/cairo-gl-shaders.c b/src/cairo-gl-shaders.c index 5eb8e6877..8d3e4e809 100644 --- a/src/cairo-gl-shaders.c +++ b/src/cairo-gl-shaders.c @@ -1,6 +1,7 @@ /* cairo - a vector graphics library with display and print output * * Copyright © 2010 Eric Anholt + * Copyright © 2009 T. Zachary Laine * * This library is free software; you can redistribute it and/or * modify it either under the terms of the GNU Lesser General Public @@ -27,7 +28,7 @@ * * The Original Code is the cairo graphics library. * - * The Initial Developer of the Original Code is Eric Anholt. + * The Initial Developer of the Original Code is T. Zachary Laine. */ #include "cairoint.h" @@ -116,3 +117,623 @@ fail: glDeleteObjectARB (shader); return status; } + +typedef struct _shader_impl { + cairo_status_t + (*compile_shader) (GLuint *shader, GLenum type, const char *text); + + cairo_status_t + (*link_shader) (GLuint *program, GLuint vert, GLuint frag); + + void + (*destroy_shader_program) (cairo_gl_shader_program_t *program); + + cairo_status_t + (*create_linear_gradient_shader_program) (cairo_gl_shader_program_t *program); + + cairo_status_t + (*create_radial_gradient_shader_program) (cairo_gl_shader_program_t *program); + + cairo_status_t + (*bind_float_to_shader) (GLuint program, const char *name, + float value); + + cairo_status_t + (*bind_vec2_to_shader) (GLuint program, const char *name, + float value0, float value1); + + cairo_status_t + (*bind_vec3_to_shader) (GLuint program, const char *name, + float value0, float value1, + float value2); + + cairo_status_t + (*bind_vec4_to_shader) (GLuint program, const char *name, + float value0, float value1, + float value2, float value3); + + cairo_status_t + (*bind_matrix_to_shader) (GLuint program, const char *name, cairo_matrix_t* m); + + cairo_status_t + (*bind_texture_to_shader) (GLuint program, const char *name, GLuint tex_unit); + + GLenum + (*vertex_enumerator) (void); + + GLenum + (*fragment_enumerator) (void); +} shader_impl_t; + +static const shader_impl_t* +get_impl (void); + +static const char * const minimal_vert_text_110 = + "#version 110\n" + "\n" + "void main ()\n" + "{ gl_Position = ftransform(); }\n"; + +/* This fragment shader was adapted from Argiris Kirtzidis' cairo-gral + * library, found at git://github.com/akyrtzi/cairo-gral.git. Argiris' shader + * was adapted from the original algorithm in pixman. + */ +static const char * const radial_gradient_frag_text_110 = + "#version 110\n" + "\n" + "uniform sampler1D tex;\n" + "uniform mat4 matrix;\n" + "uniform vec2 circle_1;\n" + "uniform float radius_0;\n" + "uniform float radius_1;\n" + "uniform float first_offset;\n" + "uniform float last_offset;\n" + "\n" + "void main ()\n" + "{\n" + " vec2 pos = (matrix * vec4 (gl_FragCoord.xy, 0.0, 1.0)).xy;\n" + " \n" + " float dr = radius_1 - radius_0;\n" + " float dot_circle_1 = dot (circle_1, circle_1);\n" + " float dot_pos_circle_1 = dot (pos, circle_1);\n" + " \n" + " float A = dot_circle_1 - dr * dr;\n" + " float B = -2.0 * (dot_pos_circle_1 + radius_0 * dr);\n" + " float C = dot (pos, pos) - radius_0 * 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 - first_offset) / (last_offset - first_offset);\n" + " gl_FragColor = texture1D (tex, t);\n" + "}\n"; + +static const char * const linear_gradient_frag_text_110 = + "#version 110\n" + "\n" + "uniform sampler1D tex;\n" + "uniform mat4 matrix;\n" + "uniform vec2 segment;\n" + "uniform float first_offset;\n" + "uniform float last_offset;\n" + "\n" + "void main ()\n" + "{\n" + " vec2 pos = (matrix * vec4 (gl_FragCoord.xy, 0.0, 1.0)).xy;\n" + " float t = dot (pos, segment) / dot (segment, segment);\n" + " t = (t - first_offset) / (last_offset - first_offset);\n" + " gl_FragColor = texture1D (tex, t);\n" + "}\n"; + +/* ARB_shader_objects / ARB_vertex_shader / ARB_fragment_shader extensions + API. */ +static cairo_status_t +compile_shader_arb (GLuint *shader, GLenum type, const char *text) +{ + const char* strings[1] = { text }; + GLint gl_status; + + *shader = glCreateShaderObjectARB (type); + glShaderSourceARB (*shader, 1, strings, 0); + glCompileShaderARB (*shader); + glGetObjectParameterivARB (*shader, GL_OBJECT_COMPILE_STATUS_ARB, &gl_status); + if (gl_status == GL_FALSE) { + GLint log_size; + glGetObjectParameterivARB (*shader, GL_OBJECT_INFO_LOG_LENGTH_ARB, &log_size); + if (0 < log_size) { + char *log = _cairo_malloc (log_size); + GLint chars; + + log[log_size - 1] = '\0'; + glGetInfoLogARB (*shader, log_size, &chars, log); + printf ("OpenGL shader compilation failed. Shader:\n" + "%s\n" + "OpenGL compilation log:\n" + "%s\n", + text, log); + + free (log); + } else { + printf ("OpenGL shader compilation failed.\n"); + } + + return CAIRO_INT_STATUS_UNSUPPORTED; + } + + return CAIRO_STATUS_SUCCESS; +} + +static cairo_status_t +link_shader_arb (GLuint *program, GLuint vert, GLuint frag) +{ + GLint gl_status; + + *program = glCreateProgramObjectARB (); + glAttachObjectARB (*program, vert); + glAttachObjectARB (*program, frag); + glLinkProgramARB (*program); + glGetObjectParameterivARB (*program, GL_OBJECT_LINK_STATUS_ARB, &gl_status); + if (gl_status == GL_FALSE) { + GLint log_size; + glGetObjectParameterivARB (*program, GL_OBJECT_INFO_LOG_LENGTH_ARB, &log_size); + if (0 < log_size) { + char *log = _cairo_malloc (log_size); + GLint chars; + + log[log_size - 1] = '\0'; + glGetInfoLogARB (*program, log_size, &chars, log); + printf ("OpenGL shader link failed:\n%s\n", log); + + free (log); + } else { + printf ("OpenGL shader link failed.\n"); + } + + return CAIRO_INT_STATUS_UNSUPPORTED; + } + + return CAIRO_STATUS_SUCCESS; +} + +static void +destroy_shader_program_arb (cairo_gl_shader_program_t *program) +{ + if (program->vertex_shader) + glDeleteObjectARB (program->vertex_shader); + if (program->fragment_shader) + glDeleteObjectARB (program->fragment_shader); + if (program->program) + glDeleteObjectARB (program->program); +} + +static cairo_status_t +create_linear_gradient_shader_program_arb (cairo_gl_shader_program_t *program) +{ + return create_shader_program (program, + minimal_vert_text_110, + linear_gradient_frag_text_110); +} + +static cairo_status_t +create_radial_gradient_shader_program_arb (cairo_gl_shader_program_t *program) +{ + return create_shader_program (program, + minimal_vert_text_110, + radial_gradient_frag_text_110); +} + +static cairo_status_t +bind_float_to_shader_arb (GLuint program, const char *name, + float value) +{ + GLint location = glGetUniformLocationARB (program, name); + if (location == -1) + return CAIRO_INT_STATUS_UNSUPPORTED; + glUniform1fARB (location, value); + return CAIRO_STATUS_SUCCESS; +} + +static cairo_status_t +bind_vec2_to_shader_arb (GLuint program, const char *name, + float value0, float value1) +{ + GLint location = glGetUniformLocationARB (program, name); + if (location == -1) + return CAIRO_INT_STATUS_UNSUPPORTED; + glUniform2fARB (location, value0, value1); + return CAIRO_STATUS_SUCCESS; +} + +static cairo_status_t +bind_vec3_to_shader_arb (GLuint program, const char *name, + float value0, float value1, + float value2) +{ + GLint location = glGetUniformLocationARB (program, name); + if (location == -1) + return CAIRO_INT_STATUS_UNSUPPORTED; + glUniform3fARB (location, value0, value1, value2); + return CAIRO_STATUS_SUCCESS; +} + +static cairo_status_t +bind_vec4_to_shader_arb (GLuint program, const char *name, + float value0, float value1, + float value2, float value3) +{ + GLint location = glGetUniformLocationARB (program, name); + if (location == -1) + return CAIRO_INT_STATUS_UNSUPPORTED; + glUniform4fARB (location, value0, value1, value2, value3); + return CAIRO_STATUS_SUCCESS; +} + +static cairo_status_t +bind_matrix_to_shader_arb (GLuint program, const char *name, cairo_matrix_t* m) +{ + GLint location = glGetUniformLocationARB (program, name); + if (location == -1) + return CAIRO_INT_STATUS_UNSUPPORTED; + float gl_m[16] = { + m->xx, m->xy, 0, m->x0, + m->yx, m->yy, 0, m->y0, + 0, 0, 1, 0, + 0, 0, 0, 1 + }; + glUniformMatrix4fvARB (location, 1, GL_TRUE, gl_m); + return CAIRO_STATUS_SUCCESS; +} + +static cairo_status_t +bind_texture_to_shader_arb (GLuint program, const char *name, GLuint tex_unit) +{ + GLint location = glGetUniformLocationARB (program, name); + if (location == -1) + return CAIRO_INT_STATUS_UNSUPPORTED; + glUniform1iARB (location, tex_unit); + return CAIRO_STATUS_SUCCESS; +} + +static GLenum +vertex_enumerator_arb (void) +{ + return GL_VERTEX_SHADER_ARB; +} + +static GLenum +fragment_enumerator_arb (void) +{ + return GL_FRAGMENT_SHADER_ARB; +} + +/* OpenGL Core 2.0 API. */ +static cairo_status_t +compile_shader_core_2_0 (GLuint *shader, GLenum type, const char *text) +{ + const char* strings[1] = { text }; + GLint gl_status; + + *shader = glCreateShader (type); + glShaderSource (*shader, 1, strings, 0); + glCompileShader (*shader); + glGetShaderiv (*shader, GL_COMPILE_STATUS, &gl_status); + if (gl_status == GL_FALSE) { + GLint log_size; + glGetShaderiv (*shader, GL_INFO_LOG_LENGTH, &log_size); + if (0 < log_size) { + char *log = _cairo_malloc (log_size); + GLint chars; + + log[log_size - 1] = '\0'; + glGetShaderInfoLog (*shader, log_size, &chars, log); + printf ("OpenGL shader compilation failed. Shader:\n" + "%s\n" + "OpenGL compilation log:\n" + "%s\n", + text, log); + + free (log); + } else { + printf ("OpenGL shader compilation failed.\n"); + } + + return CAIRO_INT_STATUS_UNSUPPORTED; + } + + return CAIRO_STATUS_SUCCESS; +} + +static cairo_status_t +link_shader_core_2_0 (GLuint *program, GLuint vert, GLuint frag) +{ + GLint gl_status; + + *program = glCreateProgram (); + glAttachShader (*program, vert); + glAttachShader (*program, frag); + glLinkProgram (*program); + glGetProgramiv (*program, GL_LINK_STATUS, &gl_status); + if (gl_status == GL_FALSE) { + GLint log_size; + glGetProgramiv (*program, GL_INFO_LOG_LENGTH, &log_size); + if (0 < log_size) { + char *log = _cairo_malloc (log_size); + GLint chars; + + log[log_size - 1] = '\0'; + glGetProgramInfoLog (*program, log_size, &chars, log); + printf ("OpenGL shader link failed:\n%s\n", log); + + free (log); + } else { + printf ("OpenGL shader link failed.\n"); + } + + return CAIRO_INT_STATUS_UNSUPPORTED; + } + + return CAIRO_STATUS_SUCCESS; +} + +static void +destroy_shader_program_core_2_0 (cairo_gl_shader_program_t *program) +{ + glDeleteShader (program->vertex_shader); + glDeleteShader (program->fragment_shader); + glDeleteProgram (program->program); +} + +static cairo_status_t +create_linear_gradient_shader_program_core_2_0 (cairo_gl_shader_program_t *program) +{ + return create_shader_program (program, + minimal_vert_text_110, + linear_gradient_frag_text_110); +} + +static cairo_status_t +create_radial_gradient_shader_program_core_2_0 (cairo_gl_shader_program_t *program) +{ + return create_shader_program (program, + minimal_vert_text_110, + radial_gradient_frag_text_110); +} + +static cairo_status_t +bind_float_to_shader_core_2_0 (GLuint program, const char *name, + float value) +{ + GLint location = glGetUniformLocation (program, name); + if (location == -1) + return CAIRO_INT_STATUS_UNSUPPORTED; + glUniform1f (location, value); + return CAIRO_STATUS_SUCCESS; +} + +static cairo_status_t +bind_vec2_to_shader_core_2_0 (GLuint program, const char *name, + float value0, float value1) +{ + GLint location = glGetUniformLocation (program, name); + if (location == -1) + return CAIRO_INT_STATUS_UNSUPPORTED; + glUniform2f (location, value0, value1); + return CAIRO_STATUS_SUCCESS; +} + +static cairo_status_t +bind_vec3_to_shader_core_2_0 (GLuint program, const char *name, + float value0, float value1, + float value2) +{ + GLint location = glGetUniformLocation (program, name); + if (location == -1) + return CAIRO_INT_STATUS_UNSUPPORTED; + glUniform3f (location, value0, value1, value2); + return CAIRO_STATUS_SUCCESS; +} + +static cairo_status_t +bind_vec4_to_shader_core_2_0 (GLuint program, const char *name, + float value0, float value1, + float value2, float value3) +{ + GLint location = glGetUniformLocation (program, name); + if (location == -1) + return CAIRO_INT_STATUS_UNSUPPORTED; + glUniform4f (location, value0, value1, value2, value3); + return CAIRO_STATUS_SUCCESS; +} + +static cairo_status_t +bind_matrix_to_shader_core_2_0 (GLuint program, const char *name, cairo_matrix_t* m) +{ + GLint location = glGetUniformLocation (program, name); + if (location == -1) + return CAIRO_INT_STATUS_UNSUPPORTED; + float gl_m[16] = { + m->xx, m->xy, 0, m->x0, + m->yx, m->yy, 0, m->y0, + 0, 0, 1, 0, + 0, 0, 0, 1 + }; + glUniformMatrix4fv (location, 1, GL_TRUE, gl_m); + return CAIRO_STATUS_SUCCESS; +} + +static cairo_status_t +bind_texture_to_shader_core_2_0 (GLuint program, const char *name, GLuint tex_unit) +{ + GLint location = glGetUniformLocation (program, name); + if (location == -1) + return CAIRO_INT_STATUS_UNSUPPORTED; + glUniform1i (location, tex_unit); + return CAIRO_STATUS_SUCCESS; +} + +static GLenum +vertex_enumerator_core_2_0 (void) +{ + return GL_VERTEX_SHADER; +} + +static GLenum +fragment_enumerator_core_2_0 (void) +{ + return GL_FRAGMENT_SHADER; +} + +#define SHADER_IMPL_DECL(x) \ + static const shader_impl_t shader_impl_ ## x = { \ + compile_shader_ ## x, \ + link_shader_ ## x, \ + destroy_shader_program_ ## x, \ + create_linear_gradient_shader_program_ ## x, \ + create_radial_gradient_shader_program_ ## x, \ + bind_float_to_shader_ ## x, \ + bind_vec2_to_shader_ ## x, \ + bind_vec3_to_shader_ ## x, \ + bind_vec4_to_shader_ ## x, \ + bind_matrix_to_shader_ ## x, \ + bind_texture_to_shader_ ## x, \ + vertex_enumerator_ ## x, \ + fragment_enumerator_ ## x \ + } + +SHADER_IMPL_DECL(core_2_0); +SHADER_IMPL_DECL(arb); + +#undef SHADER_IMPL_DECL + +static const shader_impl_t* +get_impl (void) +{ + if (GLEW_VERSION_2_0) { + return &shader_impl_core_2_0; + } else if (GLEW_ARB_shader_objects && + GLEW_ARB_fragment_shader && + GLEW_ARB_vertex_program) { + return &shader_impl_arb; + } + + ASSERT_NOT_REACHED; + return NULL; +} + +void +init_shader_program (cairo_gl_shader_program_t *program) +{ + program->vertex_shader = 0; + program->fragment_shader = 0; + program->program = 0; + program->build_failure = FALSE; +} + +void +destroy_shader_program (cairo_gl_shader_program_t *program) +{ + return get_impl()->destroy_shader_program(program); +} + +cairo_status_t +create_shader_program (cairo_gl_shader_program_t *program, + const char *vertex_text, + const char *fragment_text) +{ + cairo_status_t status; + + if (program->program != 0) + return CAIRO_STATUS_SUCCESS; + + if (program->build_failure) + return CAIRO_INT_STATUS_UNSUPPORTED; + + status = get_impl()->compile_shader (&program->vertex_shader, + get_impl()->vertex_enumerator(), + vertex_text); + if (unlikely (status)) + goto FAILURE; + + status = get_impl()->compile_shader (&program->fragment_shader, + get_impl()->fragment_enumerator(), + fragment_text); + if (unlikely (status)) + goto FAILURE; + + status = get_impl()->link_shader (&program->program, + program->vertex_shader, + program->fragment_shader); + if (unlikely (status)) + goto FAILURE; + + return CAIRO_STATUS_SUCCESS; + + FAILURE: + destroy_shader_program (program); + program->vertex_shader = 0; + program->fragment_shader = 0; + program->program = 0; + program->build_failure = TRUE; + + return CAIRO_INT_STATUS_UNSUPPORTED; +} + +cairo_status_t +create_linear_gradient_shader_program (cairo_gl_shader_program_t *program) +{ + return get_impl()->create_linear_gradient_shader_program(program); +} + +cairo_status_t +create_radial_gradient_shader_program (cairo_gl_shader_program_t *program) +{ + return get_impl()->create_radial_gradient_shader_program(program); +} + +cairo_status_t +bind_float_to_shader (GLuint program, const char *name, + float value) +{ + return get_impl()->bind_float_to_shader(program, name, value); +} + +cairo_status_t +bind_vec2_to_shader (GLuint program, const char *name, + float value0, float value1) +{ + return get_impl()->bind_vec2_to_shader(program, name, value0, value1); +} + +cairo_status_t +bind_vec3_to_shader (GLuint program, const char *name, + float value0, float value1, + float value2) +{ + return get_impl()->bind_vec3_to_shader(program, name, value0, value1, value2); +} + +cairo_status_t +bind_vec4_to_shader (GLuint program, const char *name, + float value0, float value1, + float value2, float value3) +{ + return get_impl()->bind_vec4_to_shader(program, name, value0, value1, value2, value3); +} + +cairo_status_t +bind_matrix_to_shader (GLuint program, const char *name, cairo_matrix_t* m) +{ + return get_impl()->bind_matrix_to_shader(program, name, m); +} + +cairo_status_t +bind_texture_to_shader (GLuint program, const char *name, GLuint tex_unit) +{ + return get_impl()->bind_texture_to_shader(program, name, tex_unit); +}