diff --git a/clutter/cogl/cogl/Makefile.am b/clutter/cogl/cogl/Makefile.am index 2b6924a4c..f11a07d0c 100644 --- a/clutter/cogl/cogl/Makefile.am +++ b/clutter/cogl/cogl/Makefile.am @@ -240,6 +240,8 @@ cogl_sources_c = \ $(srcdir)/cogl-pipeline-fragend-arbfp-private.h \ $(srcdir)/cogl-pipeline-fragend-fixed.c \ $(srcdir)/cogl-pipeline-fragend-fixed-private.h \ + $(srcdir)/cogl-pipeline-progend-glsl.c \ + $(srcdir)/cogl-pipeline-progend-glsl-private.h \ $(srcdir)/cogl-material-compat.c \ $(srcdir)/cogl-program.c \ $(srcdir)/cogl-program-private.h \ diff --git a/clutter/cogl/cogl/cogl-pipeline-fragend-glsl-private.h b/clutter/cogl/cogl/cogl-pipeline-fragend-glsl-private.h index b430da3a8..3542d6cd1 100644 --- a/clutter/cogl/cogl/cogl-pipeline-fragend-glsl-private.h +++ b/clutter/cogl/cogl/cogl-pipeline-fragend-glsl-private.h @@ -32,5 +32,8 @@ extern const CoglPipelineFragend _cogl_pipeline_glsl_fragend; +GLuint +_cogl_pipeline_fragend_glsl_get_shader (CoglPipeline *pipeline); + #endif /* __COGL_PIPELINE_FRAGEND_GLSL_PRIVATE_H */ diff --git a/clutter/cogl/cogl/cogl-pipeline-fragend-glsl.c b/clutter/cogl/cogl/cogl-pipeline-fragend-glsl.c index dd50ddfce..a06455b98 100644 --- a/clutter/cogl/cogl/cogl-pipeline-fragend-glsl.c +++ b/clutter/cogl/cogl/cogl-pipeline-fragend-glsl.c @@ -23,6 +23,7 @@ * * Authors: * Robert Bragg + * Neil Roberts */ #ifdef HAVE_CONFIG_H @@ -46,23 +47,12 @@ #ifndef HAVE_COGL_GLES2 -#define glCreateProgram ctx->drv.pf_glCreateProgram -#define glAttachShader ctx->drv.pf_glAttachShader -#define glUseProgram ctx->drv.pf_glUseProgram -#define glLinkProgram ctx->drv.pf_glLinkProgram -#define glDeleteProgram ctx->drv.pf_glDeleteProgram -#define glGetProgramInfoLog ctx->drv.pf_glGetProgramInfoLog -#define glGetProgramiv ctx->drv.pf_glGetProgramiv #define glCreateShader ctx->drv.pf_glCreateShader #define glGetShaderiv ctx->drv.pf_glGetShaderiv #define glGetShaderInfoLog ctx->drv.pf_glGetShaderInfoLog #define glCompileShader ctx->drv.pf_glCompileShader #define glShaderSource ctx->drv.pf_glShaderSource #define glDeleteShader ctx->drv.pf_glDeleteShader -#define glGetUniformLocation ctx->drv.pf_glGetUniformLocation -#define glUniform1i ctx->drv.pf_glUniform1i -#define glUniform1f ctx->drv.pf_glUniform1f -#define glUniform4fv ctx->drv.pf_glUniform4fv #endif /* HAVE_COGL_GLES2 */ @@ -85,63 +75,34 @@ typedef struct _UnitState { unsigned int sampled:1; unsigned int combine_constant_used:1; - unsigned int dirty_combine_constant:1; - - GLint combine_constant_uniform; } UnitState; -typedef struct _GlslProgramState +typedef struct _GlslShaderState { int ref_count; - /* Age of the user program that was current when the gl_program was - linked. This is used to detect when we need to relink a new - program */ - unsigned int user_program_age; - GLuint gl_program; + GLuint gl_shader; GString *header, *source; UnitState *unit_state; - /* To allow writing shaders that are portable between GLES 2 and - * OpenGL Cogl prepends a number of boilerplate #defines and - * declarations to user shaders. One of those declarations is an - * array of texture coordinate varyings, but to know how to emit the - * declaration we need to know how many texture coordinate - * attributes are in use. The boilerplate also needs to be changed - * if this increases. */ - int n_tex_coord_attribs; - -#ifdef HAVE_COGL_GLES2 - /* The GLES2 generated program that was generated from the user - program. This is used to detect when the GLES2 backend generates - a different program which would mean we need to flush all of the - custom uniforms. This is a massive hack but it can go away once - this GLSL backend starts generating its own shaders */ - GLuint gles2_program; - - /* Under GLES2 the alpha test is implemented in the shader. We need - a uniform for the reference value */ - gboolean alpha_test_reference_used; - gboolean dirty_alpha_test_reference; - GLint alpha_test_reference_uniform; -#endif - - /* We need to track the last pipeline that the program was used with - * so know if we need to update all of the uniforms */ - CoglPipeline *last_used_for_pipeline; -} GlslProgramState; + /* Age of the user program that was current when the shader was + generated. We need to keep track of this because if the user + program changes then we may need to redecide whether to generate + a shader at all */ + unsigned int user_program_age; +} GlslShaderState; typedef struct _CoglPipelineFragendGlslPrivate { - GlslProgramState *glsl_program_state; + GlslShaderState *glsl_shader_state; } CoglPipelineFragendGlslPrivate; const CoglPipelineFragend _cogl_pipeline_glsl_backend; -static GlslProgramState * -glsl_program_state_new (int n_layers) +static GlslShaderState * +glsl_shader_state_new (int n_layers) { - GlslProgramState *state = g_slice_new0 (GlslProgramState); + GlslShaderState *state = g_slice_new0 (GlslShaderState); state->ref_count = 1; state->unit_state = g_new0 (UnitState, n_layers); @@ -149,29 +110,15 @@ glsl_program_state_new (int n_layers) return state; } -static GlslProgramState * -glsl_program_state_ref (GlslProgramState *state) +static GlslShaderState * +glsl_shader_state_ref (GlslShaderState *state) { state->ref_count++; return state; } -static void -delete_program (GLuint program) -{ -#ifdef HAVE_COGL_GLES2 - /* This hack can go away once this GLSL backend replaces the GLES2 - wrapper */ - _cogl_gles2_clear_cache_for_program (program); -#else - _COGL_GET_CONTEXT (ctx, NO_RETVAL); -#endif - - GE (glDeleteProgram (program)); -} - void -glsl_program_state_unref (GlslProgramState *state) +glsl_shader_state_unref (GlslShaderState *state) { _COGL_GET_CONTEXT (ctx, NO_RETVAL); @@ -180,15 +127,12 @@ glsl_program_state_unref (GlslProgramState *state) state->ref_count--; if (state->ref_count == 0) { - if (state->gl_program) - { - delete_program (state->gl_program); - state->gl_program = 0; - } + if (state->gl_shader) + GE( glDeleteShader (state->gl_shader) ); g_free (state->unit_state); - g_slice_free (GlslProgramState, state); + g_slice_free (GlslShaderState, state); } } @@ -213,17 +157,28 @@ set_glsl_priv (CoglPipeline *pipeline, CoglPipelineFragendGlslPrivate *priv) pipeline->fragend_priv_set_mask &= ~COGL_PIPELINE_FRAGEND_GLSL_MASK; } -static GlslProgramState * -get_glsl_program_state (CoglPipeline *pipeline) +static GlslShaderState * +get_glsl_shader_state (CoglPipeline *pipeline) { CoglPipelineFragendGlslPrivate *priv = get_glsl_priv (pipeline); if (!priv) return NULL; - return priv->glsl_program_state; + return priv->glsl_shader_state; +} + +GLuint +_cogl_pipeline_fragend_glsl_get_shader (CoglPipeline *pipeline) +{ + GlslShaderState *glsl_shader_state = get_glsl_shader_state (pipeline); + + if (glsl_shader_state) + return glsl_shader_state->gl_shader; + else + return 0; } static void -dirty_glsl_program_state (CoglPipeline *pipeline) +dirty_glsl_shader_state (CoglPipeline *pipeline) { CoglPipelineFragendGlslPrivate *priv; @@ -233,50 +188,13 @@ dirty_glsl_program_state (CoglPipeline *pipeline) if (!priv) return; - if (priv->glsl_program_state) + if (priv->glsl_shader_state) { - glsl_program_state_unref (priv->glsl_program_state); - priv->glsl_program_state = NULL; + glsl_shader_state_unref (priv->glsl_shader_state); + priv->glsl_shader_state = NULL; } } -static void -link_program (GLint gl_program) -{ - /* On GLES2 we'll let the backend link the program. This hack can go - away once this backend replaces the GLES2 wrapper */ -#ifndef HAVE_COGL_GLES2 - - GLint link_status; - - _COGL_GET_CONTEXT (ctx, NO_RETVAL); - - GE( glLinkProgram (gl_program) ); - - GE( glGetProgramiv (gl_program, GL_LINK_STATUS, &link_status) ); - - if (!link_status) - { - GLint log_length; - GLsizei out_log_length; - char *log; - - GE( glGetProgramiv (gl_program, GL_INFO_LOG_LENGTH, &log_length) ); - - log = g_malloc (log_length); - - GE( glGetProgramInfoLog (gl_program, log_length, - &out_log_length, log) ); - - g_warning ("Failed to link GLSL program:\n%.*s\n", - log_length, log); - - g_free (log); - } - -#endif /* HAVE_COGL_GLES2 */ -} - static gboolean _cogl_pipeline_fragend_glsl_start (CoglPipeline *pipeline, int n_layers, @@ -287,7 +205,6 @@ _cogl_pipeline_fragend_glsl_start (CoglPipeline *pipeline, CoglPipeline *authority; CoglPipelineFragendGlslPrivate *authority_priv; CoglProgram *user_program; - GSList *l; int i; _COGL_GET_CONTEXT (ctx, FALSE); @@ -296,6 +213,9 @@ _cogl_pipeline_fragend_glsl_start (CoglPipeline *pipeline, return FALSE; user_program = cogl_pipeline_get_user_program (pipeline); + + /* If the program isn't GLSL then we should let another backend + handle it */ if (user_program && _cogl_program_get_language (user_program) != COGL_SHADER_LANGUAGE_GLSL) return FALSE; @@ -309,13 +229,13 @@ _cogl_pipeline_fragend_glsl_start (CoglPipeline *pipeline, set_glsl_priv (pipeline, priv); } - if (!priv->glsl_program_state) + if (!priv->glsl_shader_state) { - /* If we don't have an associated glsl program yet then find the + /* If we don't have an associated glsl shader yet then find the * glsl-authority (the oldest ancestor whose state will result in - * the same program being generated as for this pipeline). + * the same shader being generated as for this pipeline). * - * We always make sure to associate new programs with the + * We always make sure to associate new shader with the * glsl-authority to maximize the chance that other pipelines can * share it. */ @@ -334,78 +254,49 @@ _cogl_pipeline_fragend_glsl_start (CoglPipeline *pipeline, } /* If we don't have an existing program associated with the - * glsl-authority then start generating code for a new program... + * glsl-authority then start generating code for a new shader... */ - if (!authority_priv->glsl_program_state) + if (!authority_priv->glsl_shader_state) { - GlslProgramState *glsl_program_state = - glsl_program_state_new (n_layers); - authority_priv->glsl_program_state = glsl_program_state; + GlslShaderState *glsl_shader_state = + glsl_shader_state_new (n_layers); + authority_priv->glsl_shader_state = glsl_shader_state; } /* If the pipeline isn't actually its own glsl-authority * then take a reference to the program state associated * with the glsl-authority... */ if (authority != pipeline) - priv->glsl_program_state = - glsl_program_state_ref (authority_priv->glsl_program_state); + priv->glsl_shader_state = + glsl_shader_state_ref (authority_priv->glsl_shader_state); } - if (priv->glsl_program_state->gl_program) + if (priv->glsl_shader_state->gl_shader) { - /* If we already have a valid GLSL program then we don't need to - * relink a new one. However if the program has changed since - * the last link then we do need to relink - * - * Also if the number of texture coordinate attributes in use has - * increased, then delete the program so we can prepend a new - * _cogl_tex_coord[] varying array declaration. */ + /* If we already have a valid GLSL shader then we don't need to + generate a new one. However if there's a user program and it + has changed since the last link then we do need a new shader */ if (user_program == NULL || - (priv->glsl_program_state->user_program_age == user_program->age -#ifdef HAVE_COGL_GLES2 - && (priv->glsl_program_state->n_tex_coord_attribs >= - n_tex_coord_attribs) -#endif - )) + (priv->glsl_shader_state->user_program_age == user_program->age)) return TRUE; - /* Destroy the existing program. We can't just dirty the whole - glsl state because otherwise if we are not the authority on - the user program then we'll just find the same state again */ - delete_program (priv->glsl_program_state->gl_program); - priv->glsl_program_state->gl_program = 0; + /* We need to recreate the shader so destroy the existing one */ + GE( glDeleteShader (priv->glsl_shader_state->gl_shader) ); + priv->glsl_shader_state->gl_shader = 0; } - /* If we make it here then we have a glsl_program_state struct - without a gl_program either because this is the first time we've - encountered it or because the user program has changed since it - was last linked */ + /* If we make it here then we have a glsl_shader_state struct + without a gl_shader either because this is the first time we've + encountered it or because the user program has changed */ -#ifdef HAVE_COGL_GLES2 - /* Find the largest count of texture coordinate attributes - * associated with each of the shaders so we can ensure a consistent - * _cogl_tex_coord[] array declaration across all of the shaders.*/ if (user_program) - for (l = user_program->attached_shaders; l; l = l->next) - { - CoglShader *shader = l->data; - n_tex_coord_attribs = MAX (shader->n_tex_coord_attribs, - n_tex_coord_attribs); - } -#endif + priv->glsl_shader_state->user_program_age = user_program->age; - priv->glsl_program_state->n_tex_coord_attribs = n_tex_coord_attribs; - - /* Check whether the user program contains a fragment - shader. Otherwise we need to generate one */ - if (user_program) - for (l = user_program->attached_shaders; l; l = l->next) - { - CoglShader *shader = l->data; - - if (shader->type == COGL_SHADER_TYPE_FRAGMENT) - goto no_fragment_shader_needed; - } + /* If the user program contains a fragment shader then we don't need + to generate one */ + if (user_program && + _cogl_program_has_fragment_shader (user_program)) + return TRUE; /* We reuse two grow-only GStrings for code-gen. One string contains the uniform and attribute declarations while the @@ -414,34 +305,25 @@ _cogl_pipeline_fragend_glsl_start (CoglPipeline *pipeline, add_layer callback is invoked */ g_string_set_size (ctx->fragment_header_buffer, 0); g_string_set_size (ctx->fragment_source_buffer, 0); - priv->glsl_program_state->header = ctx->fragment_header_buffer; - priv->glsl_program_state->source = ctx->fragment_source_buffer; + priv->glsl_shader_state->header = ctx->fragment_header_buffer; + priv->glsl_shader_state->source = ctx->fragment_source_buffer; - g_string_append (priv->glsl_program_state->source, + g_string_append (priv->glsl_shader_state->source, "void\n" "main ()\n" "{\n"); -#ifdef HAVE_COGL_GLES2 - priv->glsl_program_state->alpha_test_reference_uniform = -1; - priv->glsl_program_state->alpha_test_reference_used = FALSE; - priv->glsl_program_state->dirty_alpha_test_reference = FALSE; -#endif - for (i = 0; i < n_layers; i++) { - priv->glsl_program_state->unit_state[i].sampled = FALSE; - priv->glsl_program_state->unit_state[i].combine_constant_used = FALSE; - priv->glsl_program_state->unit_state[i].dirty_combine_constant = FALSE; + priv->glsl_shader_state->unit_state[i].sampled = FALSE; + priv->glsl_shader_state->unit_state[i].combine_constant_used = FALSE; } - no_fragment_shader_needed: - return TRUE; } static void -add_constant_lookup (GlslProgramState *glsl_program_state, +add_constant_lookup (GlslShaderState *glsl_shader_state, CoglPipeline *pipeline, CoglPipelineLayer *layer, const char *swizzle) @@ -449,22 +331,21 @@ add_constant_lookup (GlslProgramState *glsl_program_state, int unit_index = _cogl_pipeline_layer_get_unit_index (layer); /* Create a sampler uniform for this layer if we haven't already */ - if (!glsl_program_state->unit_state[unit_index].combine_constant_used) + if (!glsl_shader_state->unit_state[unit_index].combine_constant_used) { - g_string_append_printf (glsl_program_state->header, + g_string_append_printf (glsl_shader_state->header, "uniform vec4 _cogl_layer_constant_%i;\n", unit_index); - glsl_program_state->unit_state[unit_index].combine_constant_used = TRUE; - glsl_program_state->unit_state[unit_index].dirty_combine_constant = TRUE; + glsl_shader_state->unit_state[unit_index].combine_constant_used = TRUE; } - g_string_append_printf (glsl_program_state->source, + g_string_append_printf (glsl_shader_state->source, "_cogl_layer_constant_%i.%s", unit_index, swizzle); } static void -add_texture_lookup (GlslProgramState *glsl_program_state, +add_texture_lookup (GlslShaderState *glsl_shader_state, CoglPipeline *pipeline, CoglPipelineLayer *layer, const char *swizzle) @@ -517,16 +398,16 @@ add_texture_lookup (GlslProgramState *glsl_program_state, } /* Create a sampler uniform for this layer if we haven't already */ - if (!glsl_program_state->unit_state[unit_index].sampled) + if (!glsl_shader_state->unit_state[unit_index].sampled) { - g_string_append_printf (glsl_program_state->header, + g_string_append_printf (glsl_shader_state->header, "uniform sampler%s _cogl_sampler_%i;\n", target_string, unit_index); - glsl_program_state->unit_state[unit_index].sampled = TRUE; + glsl_shader_state->unit_state[unit_index].sampled = TRUE; } - g_string_append_printf (glsl_program_state->source, + g_string_append_printf (glsl_shader_state->source, "texture%s (_cogl_sampler_%i, ", target_string, unit_index); @@ -541,16 +422,16 @@ add_texture_lookup (GlslProgramState *glsl_program_state, #ifdef HAVE_COGL_GLES2 if (cogl_pipeline_get_layer_point_sprite_coords_enabled (pipeline, layer->index)) - g_string_append_printf (glsl_program_state->source, + g_string_append_printf (glsl_shader_state->source, "gl_PointCoord.%s", tex_coord_swizzle); else #endif - g_string_append_printf (glsl_program_state->source, + g_string_append_printf (glsl_shader_state->source, "cogl_tex_coord_in[%d].%s", unit_index, tex_coord_swizzle); - g_string_append_printf (glsl_program_state->source, ").%s", swizzle); + g_string_append_printf (glsl_shader_state->source, ").%s", swizzle); } typedef struct @@ -578,14 +459,14 @@ find_pipeline_layer_cb (CoglPipelineLayer *layer, } static void -add_arg (GlslProgramState *glsl_program_state, +add_arg (GlslShaderState *glsl_shader_state, CoglPipeline *pipeline, CoglPipelineLayer *layer, GLint src, GLenum operand, const char *swizzle) { - GString *shader_source = glsl_program_state->source; + GString *shader_source = glsl_shader_state->source; char alpha_swizzle[5] = "aaaa"; g_string_append_c (shader_source, '('); @@ -606,14 +487,14 @@ add_arg (GlslProgramState *glsl_program_state, switch (src) { case GL_TEXTURE: - add_texture_lookup (glsl_program_state, + add_texture_lookup (glsl_shader_state, pipeline, layer, swizzle); break; case GL_CONSTANT: - add_constant_lookup (glsl_program_state, + add_constant_lookup (glsl_shader_state, pipeline, layer, swizzle); @@ -642,7 +523,7 @@ add_arg (GlslProgramState *glsl_program_state, find_pipeline_layer_cb, &data); - add_texture_lookup (glsl_program_state, + add_texture_lookup (glsl_shader_state, pipeline, data.layer, swizzle); @@ -661,40 +542,40 @@ append_masked_combine (CoglPipeline *pipeline, GLint *src, GLint *op) { - GlslProgramState *glsl_program_state = get_glsl_program_state (pipeline); - GString *shader_source = glsl_program_state->source; + GlslShaderState *glsl_shader_state = get_glsl_shader_state (pipeline); + GString *shader_source = glsl_shader_state->source; - g_string_append_printf (glsl_program_state->source, + g_string_append_printf (glsl_shader_state->source, " cogl_color_out.%s = ", swizzle); switch (function) { case GL_REPLACE: - add_arg (glsl_program_state, pipeline, layer, + add_arg (glsl_shader_state, pipeline, layer, src[0], op[0], swizzle); break; case GL_MODULATE: - add_arg (glsl_program_state, pipeline, layer, + add_arg (glsl_shader_state, pipeline, layer, src[0], op[0], swizzle); g_string_append (shader_source, " * "); - add_arg (glsl_program_state, pipeline, layer, + add_arg (glsl_shader_state, pipeline, layer, src[1], op[1], swizzle); break; case GL_ADD: - add_arg (glsl_program_state, pipeline, layer, + add_arg (glsl_shader_state, pipeline, layer, src[0], op[0], swizzle); g_string_append (shader_source, " + "); - add_arg (glsl_program_state, pipeline, layer, + add_arg (glsl_shader_state, pipeline, layer, src[1], op[1], swizzle); break; case GL_ADD_SIGNED: - add_arg (glsl_program_state, pipeline, layer, + add_arg (glsl_shader_state, pipeline, layer, src[0], op[0], swizzle); g_string_append (shader_source, " + "); - add_arg (glsl_program_state, pipeline, layer, + add_arg (glsl_shader_state, pipeline, layer, src[1], op[1], swizzle); g_string_append_printf (shader_source, " - vec4(0.5, 0.5, 0.5, 0.5).%s", @@ -702,26 +583,26 @@ append_masked_combine (CoglPipeline *pipeline, break; case GL_SUBTRACT: - add_arg (glsl_program_state, pipeline, layer, + add_arg (glsl_shader_state, pipeline, layer, src[0], op[0], swizzle); g_string_append (shader_source, " - "); - add_arg (glsl_program_state, pipeline, layer, + add_arg (glsl_shader_state, pipeline, layer, src[1], op[1], swizzle); break; case GL_INTERPOLATE: - add_arg (glsl_program_state, pipeline, layer, + add_arg (glsl_shader_state, pipeline, layer, src[0], op[0], swizzle); g_string_append (shader_source, " * "); - add_arg (glsl_program_state, pipeline, layer, + add_arg (glsl_shader_state, pipeline, layer, src[2], op[2], swizzle); g_string_append (shader_source, " + "); - add_arg (glsl_program_state, pipeline, layer, + add_arg (glsl_shader_state, pipeline, layer, src[1], op[1], swizzle); g_string_append_printf (shader_source, " * (vec4(1.0, 1.0, 1.0, 1.0).%s - ", swizzle); - add_arg (glsl_program_state, pipeline, layer, + add_arg (glsl_shader_state, pipeline, layer, src[2], op[2], swizzle); g_string_append_c (shader_source, ')'); break; @@ -729,22 +610,22 @@ append_masked_combine (CoglPipeline *pipeline, case GL_DOT3_RGB: case GL_DOT3_RGBA: g_string_append (shader_source, "vec4(4 * (("); - add_arg (glsl_program_state, pipeline, layer, + add_arg (glsl_shader_state, pipeline, layer, src[0], op[0], "r"); g_string_append (shader_source, " - 0.5) * ("); - add_arg (glsl_program_state, pipeline, layer, + add_arg (glsl_shader_state, pipeline, layer, src[1], op[1], "r"); g_string_append (shader_source, " - 0.5) + ("); - add_arg (glsl_program_state, pipeline, layer, + add_arg (glsl_shader_state, pipeline, layer, src[0], op[0], "g"); g_string_append (shader_source, " - 0.5) * ("); - add_arg (glsl_program_state, pipeline, layer, + add_arg (glsl_shader_state, pipeline, layer, src[1], op[1], "g"); g_string_append (shader_source, " - 0.5) + ("); - add_arg (glsl_program_state, pipeline, layer, + add_arg (glsl_shader_state, pipeline, layer, src[0], op[0], "b"); g_string_append (shader_source, " - 0.5) * ("); - add_arg (glsl_program_state, pipeline, layer, + add_arg (glsl_shader_state, pipeline, layer, src[1], op[1], "b"); g_string_append_printf (shader_source, " - 0.5))).%s", swizzle); break; @@ -758,13 +639,13 @@ _cogl_pipeline_fragend_glsl_add_layer (CoglPipeline *pipeline, CoglPipelineLayer *layer, unsigned long layers_difference) { - GlslProgramState *glsl_program_state = get_glsl_program_state (pipeline); + GlslShaderState *glsl_shader_state = get_glsl_shader_state (pipeline); CoglPipelineLayer *combine_authority = _cogl_pipeline_layer_get_authority (layer, COGL_PIPELINE_LAYER_STATE_COMBINE); CoglPipelineLayerBigState *big_state = combine_authority->big_state; - if (!glsl_program_state->source) + if (!glsl_shader_state->source) return TRUE; if (!_cogl_pipeline_need_texture_combine_separate (combine_authority) || @@ -800,103 +681,17 @@ _cogl_pipeline_fragend_glsl_add_layer (CoglPipeline *pipeline, gboolean _cogl_pipeline_fragend_glsl_passthrough (CoglPipeline *pipeline) { - GlslProgramState *glsl_program_state = get_glsl_program_state (pipeline); + GlslShaderState *glsl_shader_state = get_glsl_shader_state (pipeline); - if (!glsl_program_state->source) + if (!glsl_shader_state->source) return TRUE; - g_string_append (glsl_program_state->source, + g_string_append (glsl_shader_state->source, " cogl_color_out = cogl_color_in;\n"); return TRUE; } -typedef struct -{ - int unit; - GLuint gl_program; - gboolean update_all; - GlslProgramState *glsl_program_state; -} UpdateUniformsState; - -static gboolean -get_uniform_cb (CoglPipeline *pipeline, - int layer_index, - void *user_data) -{ - UpdateUniformsState *state = user_data; - GlslProgramState *glsl_program_state = state->glsl_program_state; - UnitState *unit_state = &glsl_program_state->unit_state[state->unit]; - GLint uniform_location; - - _COGL_GET_CONTEXT (ctx, FALSE); - - if (unit_state->sampled) - { - /* We can reuse the source buffer to create the uniform name because - the program has now been linked */ - g_string_set_size (ctx->fragment_source_buffer, 0); - g_string_append_printf (ctx->fragment_source_buffer, - "_cogl_sampler_%i", state->unit); - - GE_RET( uniform_location, - glGetUniformLocation (state->gl_program, - ctx->fragment_source_buffer->str) ); - - g_return_val_if_fail (uniform_location != -1, TRUE); - - /* We can set the uniform immediately because the samplers are - the unit index not the texture object number so it will never - change. Unfortunately GL won't let us use a constant instead - of a uniform */ - GE( glUniform1i (uniform_location, state->unit) ); - } - - if (unit_state->combine_constant_used) - { - g_string_set_size (ctx->fragment_source_buffer, 0); - g_string_append_printf (ctx->fragment_source_buffer, - "_cogl_layer_constant_%i", state->unit); - - GE_RET( uniform_location, - glGetUniformLocation (state->gl_program, - ctx->fragment_source_buffer->str) ); - - g_return_val_if_fail (uniform_location != -1, TRUE); - - unit_state->combine_constant_uniform = uniform_location; - } - - state->unit++; - - return TRUE; -} - -static gboolean -update_constants_cb (CoglPipeline *pipeline, - int layer_index, - void *user_data) -{ - UpdateUniformsState *state = user_data; - GlslProgramState *glsl_program_state = state->glsl_program_state; - UnitState *unit_state = &glsl_program_state->unit_state[state->unit++]; - - _COGL_GET_CONTEXT (ctx, FALSE); - - if (unit_state->combine_constant_used && - (state->update_all || unit_state->dirty_combine_constant)) - { - float constant[4]; - _cogl_pipeline_get_layer_combine_constant (pipeline, - layer_index, - constant); - GE (glUniform4fv (unit_state->combine_constant_uniform, - 1, constant)); - unit_state->dirty_combine_constant = FALSE; - } - return TRUE; -} - /* GLES2 doesn't have alpha testing so we need to implement it in the shader */ @@ -904,7 +699,7 @@ update_constants_cb (CoglPipeline *pipeline, static void add_alpha_test_snippet (CoglPipeline *pipeline, - GlslProgramState *glsl_program_state) + GlslShaderState *glsl_shader_state) { CoglPipelineAlphaFunc alpha_func; @@ -917,7 +712,7 @@ add_alpha_test_snippet (CoglPipeline *pipeline, if (alpha_func == COGL_PIPELINE_ALPHA_FUNC_NEVER) { /* Always discard the fragment */ - g_string_append (glsl_program_state->source, + g_string_append (glsl_shader_state->source, " discard;\n"); return; } @@ -925,34 +720,31 @@ add_alpha_test_snippet (CoglPipeline *pipeline, /* For all of the other alpha functions we need a uniform for the reference */ - glsl_program_state->alpha_test_reference_used = TRUE; - glsl_program_state->dirty_alpha_test_reference = TRUE; - - g_string_append (glsl_program_state->header, + g_string_append (glsl_shader_state->header, "uniform float _cogl_alpha_test_ref;\n"); - g_string_append (glsl_program_state->source, + g_string_append (glsl_shader_state->source, " if (cogl_color_out.a "); switch (alpha_func) { case COGL_PIPELINE_ALPHA_FUNC_LESS: - g_string_append (glsl_program_state->source, ">="); + g_string_append (glsl_shader_state->source, ">="); break; case COGL_PIPELINE_ALPHA_FUNC_EQUAL: - g_string_append (glsl_program_state->source, "!="); + g_string_append (glsl_shader_state->source, "!="); break; case COGL_PIPELINE_ALPHA_FUNC_LEQUAL: - g_string_append (glsl_program_state->source, ">"); + g_string_append (glsl_shader_state->source, ">"); break; case COGL_PIPELINE_ALPHA_FUNC_GREATER: - g_string_append (glsl_program_state->source, "<="); + g_string_append (glsl_shader_state->source, "<="); break; case COGL_PIPELINE_ALPHA_FUNC_NOTEQUAL: - g_string_append (glsl_program_state->source, "=="); + g_string_append (glsl_shader_state->source, "=="); break; case COGL_PIPELINE_ALPHA_FUNC_GEQUAL: - g_string_append (glsl_program_state->source, "< "); + g_string_append (glsl_shader_state->source, "< "); break; case COGL_PIPELINE_ALPHA_FUNC_ALWAYS: @@ -961,203 +753,85 @@ add_alpha_test_snippet (CoglPipeline *pipeline, break; } - g_string_append (glsl_program_state->source, + g_string_append (glsl_shader_state->source, " _cogl_alpha_test_ref)\n discard;\n"); } -static void -update_alpha_test_reference (CoglPipeline *pipeline, - GLuint gl_program, - GlslProgramState *glsl_program_state) -{ - float alpha_reference; - - if (glsl_program_state->dirty_alpha_test_reference) - { - if (glsl_program_state->alpha_test_reference_uniform == -1) - { - GE_RET( glsl_program_state->alpha_test_reference_uniform, - glGetUniformLocation (gl_program, - "_cogl_alpha_test_ref") ); - g_return_if_fail (glsl_program_state-> - alpha_test_reference_uniform != -1); - } - - alpha_reference = cogl_pipeline_get_alpha_test_reference (pipeline); - - GE( glUniform1f (glsl_program_state->alpha_test_reference_uniform, - alpha_reference) ); - - glsl_program_state->dirty_alpha_test_reference = FALSE; - } -} - #endif /* HAVE_COGL_GLES2 */ gboolean _cogl_pipeline_fragend_glsl_end (CoglPipeline *pipeline, unsigned long pipelines_difference) { - GlslProgramState *glsl_program_state = get_glsl_program_state (pipeline); - CoglProgram *user_program; - GLuint gl_program; - gboolean gl_program_changed = FALSE; - UpdateUniformsState state; + GlslShaderState *glsl_shader_state = get_glsl_shader_state (pipeline); _COGL_GET_CONTEXT (ctx, FALSE); - gl_program = glsl_program_state->gl_program; - user_program = cogl_pipeline_get_user_program (pipeline); - - if (gl_program == 0) + if (glsl_shader_state->source) { - gl_program_changed = TRUE; + const char *source_strings[2]; + GLint lengths[2]; + GLint compile_status; + GLuint shader; + int n_tex_coord_attribs = 0; + int i, n_layers; - GE_RET( gl_program, glCreateProgram () ); + COGL_STATIC_COUNTER (fragend_glsl_compile_counter, + "glsl compile counter", + "Increments each time a new GLSL " + "program is compiled", + 0 /* no application private data */); + COGL_COUNTER_INC (_cogl_uprof_context, fragend_glsl_compile_counter); - if (user_program) +#ifdef HAVE_COGL_GLES2 + add_alpha_test_snippet (pipeline, glsl_shader_state); +#endif + + g_string_append (glsl_shader_state->source, "}\n"); + + if (G_UNLIKELY (cogl_debug_flags & COGL_DEBUG_SHOW_SOURCE)) + g_message ("pipeline program:\n%s%s", + glsl_shader_state->header->str, + glsl_shader_state->source->str); + + GE_RET( shader, glCreateShader (GL_FRAGMENT_SHADER) ); + + lengths[0] = glsl_shader_state->header->len; + source_strings[0] = glsl_shader_state->header->str; + lengths[1] = glsl_shader_state->source->len; + source_strings[1] = glsl_shader_state->source->str; + + /* Find the highest texture unit that is sampled to pass as the + number of texture coordinate attributes */ + n_layers = cogl_pipeline_get_n_layers (pipeline); + for (i = 0; i < n_layers; i++) + if (glsl_shader_state->unit_state[i].sampled) + n_tex_coord_attribs = i + 1; + + _cogl_shader_set_source_with_boilerplate (shader, GL_FRAGMENT_SHADER, + n_tex_coord_attribs, + 2, /* count */ + source_strings, lengths); + + GE( glCompileShader (shader) ); + GE( glGetShaderiv (shader, GL_COMPILE_STATUS, &compile_status) ); + + if (!compile_status) { - GSList *l; + GLint len = 0; + char *shader_log; - /* Add all of the shaders from the user program */ - for (l = user_program->attached_shaders; l; l = l->next) - { - CoglShader *shader = l->data; - - g_assert (shader->language == COGL_SHADER_LANGUAGE_GLSL); - - _cogl_shader_compile_real (shader, - glsl_program_state-> - n_tex_coord_attribs); - - GE( glAttachShader (gl_program, shader->gl_handle) ); - } - - glsl_program_state->user_program_age = user_program->age; + GE( glGetShaderiv (shader, GL_INFO_LOG_LENGTH, &len) ); + shader_log = g_alloca (len); + GE( glGetShaderInfoLog (shader, len, &len, shader_log) ); + g_warning ("Shader compilation failed:\n%s", shader_log); } - if (glsl_program_state->source) - { - const char *source_strings[2]; - GLint lengths[2]; - GLint compile_status; - GLuint shader; - - COGL_STATIC_COUNTER (fragend_glsl_compile_counter, - "glsl compile counter", - "Increments each time a new GLSL " - "program is compiled", - 0 /* no application private data */); - COGL_COUNTER_INC (_cogl_uprof_context, fragend_glsl_compile_counter); - -#ifdef HAVE_COGL_GLES2 - add_alpha_test_snippet (pipeline, glsl_program_state); -#endif - - g_string_append (glsl_program_state->source, "}\n"); - - if (G_UNLIKELY (cogl_debug_flags & COGL_DEBUG_SHOW_SOURCE)) - g_message ("pipeline program:\n%s%s", - glsl_program_state->header->str, - glsl_program_state->source->str); - - GE_RET( shader, glCreateShader (GL_FRAGMENT_SHADER) ); - - lengths[0] = glsl_program_state->header->len; - source_strings[0] = glsl_program_state->header->str; - lengths[1] = glsl_program_state->source->len; - source_strings[1] = glsl_program_state->source->str; - - _cogl_shader_set_source_with_boilerplate (shader, GL_FRAGMENT_SHADER, - glsl_program_state-> - n_tex_coord_attribs, - 2, /* count */ - source_strings, lengths); - - GE( glCompileShader (shader) ); - GE( glGetShaderiv (shader, GL_COMPILE_STATUS, &compile_status) ); - - if (!compile_status) - { - GLint len = 0; - char *shader_log; - - GE( glGetShaderiv (shader, GL_INFO_LOG_LENGTH, &len) ); - shader_log = g_alloca (len); - GE( glGetShaderInfoLog (shader, len, &len, shader_log) ); - g_warning ("Shader compilation failed:\n%s", shader_log); - } - - GE( glAttachShader (gl_program, shader) ); - - /* We can delete the shader now, but it won't actually be - destroyed until the program is also desroyed */ - GE( glDeleteShader (shader) ); - - glsl_program_state->header = NULL; - glsl_program_state->source = NULL; - } - - link_program (gl_program); - - glsl_program_state->gl_program = gl_program; + glsl_shader_state->header = NULL; + glsl_shader_state->source = NULL; + glsl_shader_state->gl_shader = shader; } -#ifdef HAVE_COGL_GLES2 - /* This function is a massive hack to get the GLES2 backend to - work. It should only be neccessary until we move the GLSL - generation into this file instead of the GLES2 driver backend */ - gl_program = _cogl_gles2_use_program (gl_program); - /* We need to detect when the GLES2 backend gives us a different - program from last time */ - if (gl_program != glsl_program_state->gles2_program) - { - glsl_program_state->gles2_program = gl_program; - gl_program_changed = TRUE; - } -#else - _cogl_use_program (gl_program, COGL_PIPELINE_PROGRAM_TYPE_GLSL); -#endif - - state.unit = 0; - state.gl_program = gl_program; - state.glsl_program_state = glsl_program_state; - - if (gl_program_changed) - cogl_pipeline_foreach_layer (pipeline, - get_uniform_cb, - &state); - - state.unit = 0; - state.update_all = (gl_program_changed || - glsl_program_state->last_used_for_pipeline != pipeline); - - cogl_pipeline_foreach_layer (pipeline, - update_constants_cb, - &state); - -#ifdef HAVE_COGL_GLES2 - if (glsl_program_state->alpha_test_reference_used) - { - if (gl_program_changed) - glsl_program_state->alpha_test_reference_uniform = -1; - if (gl_program_changed || - glsl_program_state->last_used_for_pipeline != pipeline) - glsl_program_state->dirty_alpha_test_reference = TRUE; - - update_alpha_test_reference (pipeline, gl_program, glsl_program_state); - } -#endif - - if (user_program) - _cogl_program_flush_uniforms (user_program, - gl_program, - gl_program_changed); - - /* We need to track the last pipeline that the program was used with - * so know if we need to update all of the uniforms */ - glsl_program_state->last_used_for_pipeline = pipeline; - return TRUE; } @@ -1166,24 +840,8 @@ _cogl_pipeline_fragend_glsl_pre_change_notify (CoglPipeline *pipeline, CoglPipelineState change, const CoglColor *new_color) { - static const unsigned long fragment_op_changes = - COGL_PIPELINE_STATE_LAYERS | -#ifdef COGL_HAS_GLES2 - COGL_PIPELINE_STATE_ALPHA_FUNC | -#endif - COGL_PIPELINE_STATE_USER_SHADER; - /* TODO: COGL_PIPELINE_STATE_FOG */ - - if ((change & fragment_op_changes)) - dirty_glsl_program_state (pipeline); -#ifdef COGL_HAS_GLES2 - else if ((change & COGL_PIPELINE_STATE_ALPHA_FUNC_REFERENCE)) - { - GlslProgramState *glsl_program_state = - get_glsl_program_state (pipeline); - glsl_program_state->dirty_alpha_test_reference = TRUE; - } -#endif + if ((change & COGL_PIPELINE_STATE_AFFECTS_FRAGMENT_CODEGEN)) + dirty_glsl_shader_state (pipeline); } /* NB: layers are considered immutable once they have any dependants @@ -1201,35 +859,20 @@ _cogl_pipeline_fragend_glsl_layer_pre_change_notify ( CoglPipelineLayerState change) { CoglPipelineFragendGlslPrivate *priv; - static const unsigned long not_fragment_op_changes = - COGL_PIPELINE_LAYER_STATE_COMBINE_CONSTANT | - COGL_PIPELINE_LAYER_STATE_TEXTURE | - COGL_PIPELINE_LAYER_STATE_FILTERS | - COGL_PIPELINE_LAYER_STATE_WRAP_MODES | - COGL_PIPELINE_LAYER_STATE_USER_MATRIX; priv = get_glsl_priv (owner); if (!priv) return; - if (!(change & not_fragment_op_changes)) + if ((change & COGL_PIPELINE_LAYER_STATE_AFFECTS_FRAGMENT_CODEGEN)) { - dirty_glsl_program_state (owner); + dirty_glsl_shader_state (owner); return; } - if (change & COGL_PIPELINE_LAYER_STATE_COMBINE_CONSTANT) - { - GlslProgramState *glsl_program_state = - get_glsl_program_state (owner); - int unit_index = _cogl_pipeline_layer_get_unit_index (layer); - glsl_program_state->unit_state[unit_index].dirty_combine_constant = TRUE; - } - /* TODO: we could be saving snippets of texture combine code along * with each layer and then when a layer changes we would just free * the snippet. */ - return; } static void @@ -1238,8 +881,8 @@ _cogl_pipeline_fragend_glsl_free_priv (CoglPipeline *pipeline) CoglPipelineFragendGlslPrivate *priv = get_glsl_priv (pipeline); if (priv) { - if (priv->glsl_program_state) - glsl_program_state_unref (priv->glsl_program_state); + if (priv->glsl_shader_state) + glsl_shader_state_unref (priv->glsl_shader_state); g_slice_free (CoglPipelineFragendGlslPrivate, priv); set_glsl_priv (pipeline, NULL); } diff --git a/clutter/cogl/cogl/cogl-pipeline-opengl.c b/clutter/cogl/cogl/cogl-pipeline-opengl.c index d5e3af066..88f9fc1db 100644 --- a/clutter/cogl/cogl/cogl-pipeline-opengl.c +++ b/clutter/cogl/cogl/cogl-pipeline-opengl.c @@ -1094,6 +1094,11 @@ _cogl_pipeline_flush_gl_state (CoglPipeline *pipeline, if (G_UNLIKELY (i >= G_N_ELEMENTS (_cogl_pipeline_fragends))) g_warning ("No usable pipeline fragment backend was found!"); + for (i = 0; i < COGL_PIPELINE_N_PROGENDS; i++) + if (_cogl_pipeline_progends[i]->end) + _cogl_pipeline_progends[i]->end (pipeline, pipelines_difference, + n_tex_coord_attribs); + /* FIXME: This reference is actually resulting in lots of * copy-on-write reparenting because one-shot pipelines end up * living for longer than necessary and so any later modification of diff --git a/clutter/cogl/cogl/cogl-pipeline-private.h b/clutter/cogl/cogl/cogl-pipeline-private.h index 13354c50a..32644934a 100644 --- a/clutter/cogl/cogl/cogl-pipeline-private.h +++ b/clutter/cogl/cogl/cogl-pipeline-private.h @@ -71,6 +71,20 @@ typedef struct _CoglPipelineLayer CoglPipelineLayer; #define COGL_PIPELINE_FRAGEND_DEFAULT 0 #define COGL_PIPELINE_FRAGEND_UNDEFINED 3 +/* If we have either of the GLSL backends then we also need a GLSL + progend to combine the shaders generated into a single + program. Currently there is only one progend but if we ever add + other languages they would likely need their own progend too. The + progends are different from the other backends because there can be + more than one in use for each pipeline. All of the progends are + invoked whenever a pipeline is flushed. */ +#ifdef COGL_PIPELINE_FRAGEND_GLSL +#define COGL_PIPELINE_PROGEND_GLSL 0 +#define COGL_PIPELINE_N_PROGENDS 1 +#else +#define COGL_PIPELINE_N_PROGENDS 0 +#endif + /* XXX: should I rename these as * COGL_PIPELINE_LAYER_STATE_INDEX_XYZ... ? */ @@ -660,6 +674,19 @@ typedef struct _CoglPipelineFragend void (*free_priv) (CoglPipeline *pipeline); } CoglPipelineFragend; +typedef struct +{ + void (*end) (CoglPipeline *pipeline, + unsigned long pipelines_difference, + int n_tex_coord_attribs); + void (*pipeline_pre_change_notify) (CoglPipeline *pipeline, + CoglPipelineState change, + const CoglColor *new_color); + void (*layer_pre_change_notify) (CoglPipeline *owner, + CoglPipelineLayer *layer, + CoglPipelineLayerState change); +} CoglPipelineProgend; + typedef enum { COGL_PIPELINE_PROGRAM_TYPE_GLSL = 1, @@ -669,6 +696,8 @@ typedef enum extern const CoglPipelineFragend * _cogl_pipeline_fragends[COGL_PIPELINE_N_FRAGENDS]; +extern const CoglPipelineProgend * +_cogl_pipeline_progends[]; void _cogl_pipeline_init_default_pipeline (void); diff --git a/clutter/cogl/cogl/cogl-pipeline-progend-glsl-private.h b/clutter/cogl/cogl/cogl-pipeline-progend-glsl-private.h new file mode 100644 index 000000000..5dabc6684 --- /dev/null +++ b/clutter/cogl/cogl/cogl-pipeline-progend-glsl-private.h @@ -0,0 +1,36 @@ +/* + * Cogl + * + * An object oriented GL/GLES Abstraction/Utility Layer + * + * Copyright (C) 2010 Intel Corporation. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library. If not, see + * . + * + * + * + * Authors: + * Neil Roberts + */ + +#ifndef __COGL_PIPELINE_PROGEND_GLSL_PRIVATE_H +#define __COGL_PIPELINE_PROGEND_GLSL_PRIVATE_H + +#include "cogl-pipeline-private.h" + +extern const CoglPipelineProgend _cogl_pipeline_glsl_progend; + +#endif /* __COGL_PIPELINE_PROGEND_GLSL_PRIVATE_H */ + diff --git a/clutter/cogl/cogl/cogl-pipeline-progend-glsl.c b/clutter/cogl/cogl/cogl-pipeline-progend-glsl.c new file mode 100644 index 000000000..8062e0d84 --- /dev/null +++ b/clutter/cogl/cogl/cogl-pipeline-progend-glsl.c @@ -0,0 +1,542 @@ +/* + * Cogl + * + * An object oriented GL/GLES Abstraction/Utility Layer + * + * Copyright (C) 2010 Intel Corporation. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library. If not, see + * . + * + * + * + * Authors: + * Neil Roberts + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "cogl-pipeline-private.h" +#include "cogl-pipeline-opengl-private.h" + +#ifdef COGL_PIPELINE_PROGEND_GLSL + +#include "cogl.h" +#include "cogl-internal.h" +#include "cogl-context.h" +#include "cogl-handle.h" +#include "cogl-program-private.h" +#include "cogl-pipeline-fragend-glsl-private.h" + +#ifndef HAVE_COGL_GLES2 + +#define glCreateProgram ctx->drv.pf_glCreateProgram +#define glAttachShader ctx->drv.pf_glAttachShader +#define glUseProgram ctx->drv.pf_glUseProgram +#define glLinkProgram ctx->drv.pf_glLinkProgram +#define glDeleteProgram ctx->drv.pf_glDeleteProgram +#define glGetProgramInfoLog ctx->drv.pf_glGetProgramInfoLog +#define glGetProgramiv ctx->drv.pf_glGetProgramiv +#define glGetUniformLocation ctx->drv.pf_glGetUniformLocation +#define glUniform1i ctx->drv.pf_glUniform1i +#define glUniform1f ctx->drv.pf_glUniform1f +#define glUniform4fv ctx->drv.pf_glUniform4fv + +#endif /* HAVE_COGL_GLES2 */ + +const CoglPipelineProgend _cogl_pipeline_glsl_progend; + +typedef struct _UnitState +{ + unsigned int dirty_combine_constant:1; + + GLint combine_constant_uniform; +} UnitState; + +typedef struct +{ + unsigned int ref_count; + + /* Age that the user program had last time we generated a GL + program. If it's different then we need to relink the program */ + unsigned int user_program_age; + + GLuint program; + + /* To allow writing shaders that are portable between GLES 2 and + * OpenGL Cogl prepends a number of boilerplate #defines and + * declarations to user shaders. One of those declarations is an + * array of texture coordinate varyings, but to know how to emit the + * declaration we need to know how many texture coordinate + * attributes are in use. The boilerplate also needs to be changed + * if this increases. */ + int n_tex_coord_attribs; + +#ifdef HAVE_COGL_GLES2 + /* The GLES2 generated program that was generated from the user + program. This is used to detect when the GLES2 backend generates + a different program which would mean we need to flush all of the + custom uniforms. This is a massive hack but it can go away once + this GLSL backend starts generating its own shaders */ + GLuint gles2_program; + + /* Under GLES2 the alpha test is implemented in the shader. We need + a uniform for the reference value */ + gboolean dirty_alpha_test_reference; + GLint alpha_test_reference_uniform; +#endif + + /* We need to track the last pipeline that the program was used with + * so know if we need to update all of the uniforms */ + CoglPipeline *last_used_for_pipeline; + + UnitState *unit_state; +} CoglPipelineProgendPrivate; + +static CoglUserDataKey glsl_priv_key; + +static void +delete_program (GLuint program) +{ +#ifdef HAVE_COGL_GLES2 + /* This hack can go away once this GLSL backend replaces the GLES2 + wrapper */ + _cogl_gles2_clear_cache_for_program (program); +#else + _COGL_GET_CONTEXT (ctx, NO_RETVAL); +#endif + + GE (glDeleteProgram (program)); +} + +static CoglPipelineProgendPrivate * +get_glsl_priv (CoglPipeline *pipeline) +{ + return cogl_object_get_user_data (COGL_OBJECT (pipeline), &glsl_priv_key); +} + +static void +destroy_glsl_priv (void *user_data) +{ + CoglPipelineProgendPrivate *priv = user_data; + + if (--priv->ref_count == 0) + { + if (priv->program) + delete_program (priv->program); + + g_free (priv->unit_state); + + g_slice_free (CoglPipelineProgendPrivate, priv); + } +} + +static void +set_glsl_priv (CoglPipeline *pipeline, CoglPipelineProgendPrivate *priv) +{ + cogl_object_set_user_data (COGL_OBJECT (pipeline), + &glsl_priv_key, + priv, + destroy_glsl_priv); +} + +static void +dirty_glsl_program_state (CoglPipeline *pipeline) +{ + cogl_object_set_user_data (COGL_OBJECT (pipeline), + &glsl_priv_key, + NULL, + destroy_glsl_priv); +} + +static void +link_program (GLint gl_program) +{ + /* On GLES2 we'll let the backend link the program. This hack can go + away once this backend replaces the GLES2 wrapper */ +#ifndef HAVE_COGL_GLES2 + + GLint link_status; + + _COGL_GET_CONTEXT (ctx, NO_RETVAL); + + GE( glLinkProgram (gl_program) ); + + GE( glGetProgramiv (gl_program, GL_LINK_STATUS, &link_status) ); + + if (!link_status) + { + GLint log_length; + GLsizei out_log_length; + char *log; + + GE( glGetProgramiv (gl_program, GL_INFO_LOG_LENGTH, &log_length) ); + + log = g_malloc (log_length); + + GE( glGetProgramInfoLog (gl_program, log_length, + &out_log_length, log) ); + + g_warning ("Failed to link GLSL program:\n%.*s\n", + log_length, log); + + g_free (log); + } + +#endif /* HAVE_COGL_GLES2 */ +} + +typedef struct +{ + int unit; + GLuint gl_program; + gboolean update_all; + CoglPipelineProgendPrivate *priv; +} UpdateUniformsState; + +static gboolean +get_uniform_cb (CoglPipeline *pipeline, + int layer_index, + void *user_data) +{ + UpdateUniformsState *state = user_data; + CoglPipelineProgendPrivate *priv = state->priv; + UnitState *unit_state = &priv->unit_state[state->unit]; + GLint uniform_location; + + _COGL_GET_CONTEXT (ctx, FALSE); + + /* We can reuse the source buffer to create the uniform name because + the program has now been linked */ + g_string_set_size (ctx->fragment_source_buffer, 0); + g_string_append_printf (ctx->fragment_source_buffer, + "_cogl_sampler_%i", state->unit); + + GE_RET( uniform_location, + glGetUniformLocation (state->gl_program, + ctx->fragment_source_buffer->str) ); + + /* We can set the uniform immediately because the samplers are the + unit index not the texture object number so it will never + change. Unfortunately GL won't let us use a constant instead of a + uniform */ + if (uniform_location != -1) + GE( glUniform1i (uniform_location, state->unit) ); + + g_string_set_size (ctx->fragment_source_buffer, 0); + g_string_append_printf (ctx->fragment_source_buffer, + "_cogl_layer_constant_%i", state->unit); + + GE_RET( uniform_location, + glGetUniformLocation (state->gl_program, + ctx->fragment_source_buffer->str) ); + + unit_state->combine_constant_uniform = uniform_location; + + state->unit++; + + return TRUE; +} + +static gboolean +update_constants_cb (CoglPipeline *pipeline, + int layer_index, + void *user_data) +{ + UpdateUniformsState *state = user_data; + CoglPipelineProgendPrivate *priv = state->priv; + UnitState *unit_state = &priv->unit_state[state->unit++]; + + _COGL_GET_CONTEXT (ctx, FALSE); + + if (unit_state->combine_constant_uniform != -1 && + (state->update_all || unit_state->dirty_combine_constant)) + { + float constant[4]; + _cogl_pipeline_get_layer_combine_constant (pipeline, + layer_index, + constant); + GE (glUniform4fv (unit_state->combine_constant_uniform, + 1, constant)); + unit_state->dirty_combine_constant = FALSE; + } + return TRUE; +} + +#ifdef HAVE_COGL_GLES2 + +static void +update_alpha_test_reference (CoglPipeline *pipeline, + GLuint gl_program, + CoglPipelineProgendPrivate *priv) +{ + float alpha_reference; + + if (priv->dirty_alpha_test_reference && + priv->alpha_test_reference_uniform != -1) + { + alpha_reference = cogl_pipeline_get_alpha_test_reference (pipeline); + + GE( glUniform1f (priv->alpha_test_reference_uniform, + alpha_reference) ); + + priv->dirty_alpha_test_reference = FALSE; + } +} + +#endif /* HAVE_COGL_GLES2 */ + +static void +_cogl_pipeline_progend_glsl_end (CoglPipeline *pipeline, + unsigned long pipelines_difference, + int n_tex_coord_attribs) +{ + CoglPipelineProgendPrivate *priv; + GLuint gl_program; + gboolean program_changed = FALSE; + UpdateUniformsState state; + CoglProgram *user_program; + + _COGL_GET_CONTEXT (ctx, NO_RETVAL); + + /* If neither of the glsl fragend or vertends are used then we don't + need to do anything */ + if (pipeline->fragend != COGL_PIPELINE_FRAGEND_GLSL) + return; + + priv = get_glsl_priv (pipeline); + + user_program = cogl_pipeline_get_user_program (pipeline); + + if (priv == NULL) + { + CoglPipeline *authority; + + /* Get the authority for anything affecting program state. This + should include both fragment codegen state and vertex codegen + state */ + authority = _cogl_pipeline_find_equivalent_parent + (pipeline, + COGL_PIPELINE_STATE_AFFECTS_FRAGMENT_CODEGEN & + ~COGL_PIPELINE_STATE_LAYERS, + COGL_PIPELINE_LAYER_STATE_AFFECTS_FRAGMENT_CODEGEN, + COGL_PIPELINE_FIND_EQUIVALENT_COMPARE_TEXTURE_TARGET); + + priv = get_glsl_priv (authority); + + if (priv == NULL) + { + priv = g_slice_new (CoglPipelineProgendPrivate); + priv->ref_count = 1; + priv->program = 0; + priv->n_tex_coord_attribs = 0; + priv->unit_state = g_new (UnitState, + cogl_pipeline_get_n_layers (pipeline)); +#ifdef HAVE_COGL_GLES2 + priv->gles2_program = 0; +#endif + set_glsl_priv (authority, priv); + } + + if (authority != pipeline) + { + priv->ref_count++; + set_glsl_priv (pipeline, priv); + } + } + + /* If the program has changed since the last link then we do + * need to relink + * + * Also if the number of texture coordinate attributes in use has + * increased, then delete the program so we can prepend a new + * _cogl_tex_coord[] varying array declaration. */ + if (priv->program && user_program && + (user_program->age != priv->user_program_age || + n_tex_coord_attribs > priv->n_tex_coord_attribs)) + { + delete_program (priv->program); + priv->program = 0; + } + + if (priv->program == 0) + { + GLuint backend_shader; + GSList *l; + + GE_RET( priv->program, glCreateProgram () ); + + /* Attach all of the shader from the user program */ + if (user_program) + { + if (priv->n_tex_coord_attribs > n_tex_coord_attribs) + n_tex_coord_attribs = priv->n_tex_coord_attribs; + +#ifdef HAVE_COGL_GLES2 + /* Find the largest count of texture coordinate attributes + * associated with each of the shaders so we can ensure a consistent + * _cogl_tex_coord[] array declaration across all of the shaders.*/ + if (user_program) + for (l = user_program->attached_shaders; l; l = l->next) + { + CoglShader *shader = l->data; + n_tex_coord_attribs = MAX (shader->n_tex_coord_attribs, + n_tex_coord_attribs); + } +#endif + + for (l = user_program->attached_shaders; l; l = l->next) + { + CoglShader *shader = l->data; + + _cogl_shader_compile_real (shader, n_tex_coord_attribs); + + g_assert (shader->language == COGL_SHADER_LANGUAGE_GLSL); + + GE( glAttachShader (priv->program, + shader->gl_handle) ); + } + + priv->user_program_age = user_program->age; + } + + /* Attach any shaders from the GLSL backends */ + if (pipeline->fragend == COGL_PIPELINE_FRAGEND_GLSL && + (backend_shader = _cogl_pipeline_fragend_glsl_get_shader (pipeline))) + GE( glAttachShader (priv->program, backend_shader) ); + + link_program (priv->program); + + program_changed = TRUE; + + priv->n_tex_coord_attribs = n_tex_coord_attribs; + } + + gl_program = priv->program; + +#ifdef HAVE_COGL_GLES2 + /* This function is a massive hack to get the GLES2 backend to + work. It should only be neccessary until we move the GLSL vertex + shader generation into a vertend instead of the GLES2 driver + backend */ + gl_program = _cogl_gles2_use_program (gl_program); + /* We need to detect when the GLES2 backend gives us a different + program from last time */ + if (gl_program != priv->gles2_program) + { + priv->gles2_program = gl_program; + program_changed = TRUE; + } +#else + _cogl_use_program (gl_program, COGL_PIPELINE_PROGRAM_TYPE_GLSL); +#endif + + state.unit = 0; + state.gl_program = gl_program; + state.priv = priv; + + if (program_changed) + cogl_pipeline_foreach_layer (pipeline, + get_uniform_cb, + &state); + + state.unit = 0; + state.update_all = (program_changed || + priv->last_used_for_pipeline != pipeline); + + cogl_pipeline_foreach_layer (pipeline, + update_constants_cb, + &state); + +#ifdef HAVE_COGL_GLES2 + if (program_changed) + GE_RET( priv->alpha_test_reference_uniform, + glGetUniformLocation (gl_program, + "_cogl_alpha_test_ref") ); + if (program_changed || + priv->last_used_for_pipeline != pipeline) + priv->dirty_alpha_test_reference = TRUE; + + update_alpha_test_reference (pipeline, gl_program, priv); +#endif + + if (user_program) + _cogl_program_flush_uniforms (user_program, + gl_program, + program_changed); + + /* We need to track the last pipeline that the program was used with + * so know if we need to update all of the uniforms */ + priv->last_used_for_pipeline = pipeline; +} + +static void +_cogl_pipeline_progend_glsl_pre_change_notify (CoglPipeline *pipeline, + CoglPipelineState change, + const CoglColor *new_color) +{ + if ((change & COGL_PIPELINE_STATE_AFFECTS_FRAGMENT_CODEGEN)) + dirty_glsl_program_state (pipeline); +#ifdef COGL_HAS_GLES2 + else if ((change & COGL_PIPELINE_STATE_ALPHA_FUNC_REFERENCE)) + { + CoglPipelineProgendPrivate *priv = get_glsl_priv (pipeline); + if (priv) + priv->dirty_alpha_test_reference = TRUE; + } +#endif +} + +/* NB: layers are considered immutable once they have any dependants + * so although multiple pipelines can end up depending on a single + * static layer, we can guarantee that if a layer is being *changed* + * then it can only have one pipeline depending on it. + * + * XXX: Don't forget this is *pre* change, we can't read the new value + * yet! + */ +static void +_cogl_pipeline_progend_glsl_layer_pre_change_notify ( + CoglPipeline *owner, + CoglPipelineLayer *layer, + CoglPipelineLayerState change) +{ + if ((change & COGL_PIPELINE_LAYER_STATE_AFFECTS_FRAGMENT_CODEGEN)) + { + dirty_glsl_program_state (owner); + return; + } + + if (change & COGL_PIPELINE_LAYER_STATE_COMBINE_CONSTANT) + { + CoglPipelineProgendPrivate *priv = get_glsl_priv (owner); + if (priv) + { + int unit_index = _cogl_pipeline_layer_get_unit_index (layer); + priv->unit_state[unit_index].dirty_combine_constant = TRUE; + } + } +} + +const CoglPipelineProgend _cogl_pipeline_glsl_progend = + { + _cogl_pipeline_progend_glsl_end, + _cogl_pipeline_progend_glsl_pre_change_notify, + _cogl_pipeline_progend_glsl_layer_pre_change_notify + }; + +#endif /* COGL_PIPELINE_PROGEND_GLSL */ + diff --git a/clutter/cogl/cogl/cogl-pipeline.c b/clutter/cogl/cogl/cogl-pipeline.c index d829e8278..75208d7e2 100644 --- a/clutter/cogl/cogl/cogl-pipeline.c +++ b/clutter/cogl/cogl/cogl-pipeline.c @@ -64,6 +64,10 @@ static void recursively_free_layer_caches (CoglPipeline *pipeline); static gboolean _cogl_pipeline_is_weak (CoglPipeline *pipeline); const CoglPipelineFragend *_cogl_pipeline_fragends[COGL_PIPELINE_N_FRAGENDS]; +/* The 'MAX' here is so that we don't define an empty array when there + are no progends */ +const CoglPipelineProgend * +_cogl_pipeline_progends[MAX (COGL_PIPELINE_N_PROGENDS, 1)]; #ifdef COGL_PIPELINE_FRAGEND_GLSL #include "cogl-pipeline-fragend-glsl-private.h" @@ -74,6 +78,9 @@ const CoglPipelineFragend *_cogl_pipeline_fragends[COGL_PIPELINE_N_FRAGENDS]; #ifdef COGL_PIPELINE_FRAGEND_FIXED #include "cogl-pipeline-fragend-fixed-private.h" #endif +#ifdef COGL_PIPELINE_PROGEND_GLSL +#include "cogl-pipeline-progend-glsl-private.h" +#endif COGL_OBJECT_DEFINE (Pipeline, pipeline); /* This type was made deprecated before the cogl_is_pipeline_layer @@ -199,7 +206,7 @@ _cogl_pipeline_init_default_pipeline (void) _COGL_GET_CONTEXT (ctx, NO_RETVAL); - /* Take this opportunity to setup the fragment processing backends... */ + /* Take this opportunity to setup the backends... */ #ifdef COGL_PIPELINE_FRAGEND_GLSL _cogl_pipeline_fragends[COGL_PIPELINE_FRAGEND_GLSL] = &_cogl_pipeline_glsl_fragend; @@ -212,6 +219,10 @@ _cogl_pipeline_init_default_pipeline (void) _cogl_pipeline_fragends[COGL_PIPELINE_FRAGEND_FIXED] = &_cogl_pipeline_fixed_fragend; #endif +#ifdef COGL_PIPELINE_PROGEND_GLSL + _cogl_pipeline_progends[COGL_PIPELINE_PROGEND_GLSL] = + &_cogl_pipeline_glsl_progend; +#endif _cogl_pipeline_node_init (COGL_PIPELINE_NODE (pipeline)); @@ -1129,6 +1140,7 @@ _cogl_pipeline_pre_change_notify (CoglPipeline *pipeline, gboolean from_layer_change) { CoglPipeline *authority; + int i; _COGL_GET_CONTEXT (ctx, NO_RETVAL); @@ -1201,6 +1213,14 @@ _cogl_pipeline_pre_change_notify (CoglPipeline *pipeline, fragend->pipeline_pre_change_notify (pipeline, change, new_color); } + /* Notify all of the progends */ + if (!from_layer_change) + for (i = 0; i < COGL_PIPELINE_N_PROGENDS; i++) + if (_cogl_pipeline_progends[i]->pipeline_pre_change_notify) + _cogl_pipeline_progends[i]->pipeline_pre_change_notify (pipeline, + change, + new_color); + /* There may be an arbitrary tree of descendants of this pipeline; * any of which may indirectly depend on this pipeline as the * authority for some set of properties. (Meaning for example that @@ -1504,6 +1524,22 @@ _cogl_pipeline_fragend_layer_change_notify (CoglPipeline *owner, } } +static void +_cogl_pipeline_progend_layer_change_notify (CoglPipeline *owner, + CoglPipelineLayer *layer, + CoglPipelineLayerState change) +{ + int i; + + /* Give all of the progends a chance to notice that the layer has + changed */ + for (i = 0; i < COGL_PIPELINE_N_PROGENDS; i++) + if (_cogl_pipeline_progends[i]->layer_pre_change_notify) + _cogl_pipeline_progends[i]->layer_pre_change_notify (owner, + layer, + change); +} + unsigned int _cogl_get_n_args_for_combine_func (GLint func) { @@ -1661,6 +1697,7 @@ _cogl_pipeline_layer_pre_change_notify (CoglPipeline *required_owner, * dependant on this layer so it's ok to modify it. */ _cogl_pipeline_fragend_layer_change_notify (required_owner, layer, change); + _cogl_pipeline_progend_layer_change_notify (required_owner, layer, change); /* If the layer being changed is the same as the last layer we * flushed to the corresponding texture unit then we keep a track of