/* * Cogl * * An object oriented GL/GLES Abstraction/Utility Layer * * Copyright (C) 2008,2009,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: * Robert Bragg */ #ifdef HAVE_CONFIG_H #include "config.h" #endif #include #include "cogl-pipeline-private.h" #include "cogl-shader-private.h" #include "cogl-blend-string.h" #ifdef COGL_PIPELINE_BACKEND_GLSL #include "cogl.h" #include "cogl-internal.h" #include "cogl-context.h" #include "cogl-handle.h" #include "cogl-shader-private.h" #include "cogl-program-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 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 */ #include /* * GL/GLES compatability defines for pipeline thingies: */ #ifdef HAVE_COGL_GLES2 #include "../gles/cogl-gles2-wrapper.h" #endif /* This might not be defined on GLES */ #ifndef GL_TEXTURE_3D #define GL_TEXTURE_3D 0x806F #endif 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 { 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; GString *header, *source; UnitState *unit_state; #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; typedef struct _CoglPipelineBackendGlslPrivate { GlslProgramState *glsl_program_state; } CoglPipelineBackendGlslPrivate; const CoglPipelineBackend _cogl_pipeline_glsl_backend; static int _cogl_pipeline_backend_glsl_get_max_texture_units (void) { return _cogl_get_max_texture_image_units (); } static GlslProgramState * glsl_program_state_new (int n_layers) { GlslProgramState *state = g_slice_new0 (GlslProgramState); state->ref_count = 1; state->unit_state = g_new0 (UnitState, n_layers); return state; } static GlslProgramState * glsl_program_state_ref (GlslProgramState *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) { _COGL_GET_CONTEXT (ctx, NO_RETVAL); g_return_if_fail (state->ref_count > 0); state->ref_count--; if (state->ref_count == 0) { if (state->gl_program) { delete_program (state->gl_program); state->gl_program = 0; } g_free (state->unit_state); g_slice_free (GlslProgramState, state); } } static CoglPipelineBackendGlslPrivate * get_glsl_priv (CoglPipeline *pipeline) { if (!(pipeline->backend_priv_set_mask & COGL_PIPELINE_BACKEND_GLSL_MASK)) return NULL; return pipeline->backend_privs[COGL_PIPELINE_BACKEND_GLSL]; } static void set_glsl_priv (CoglPipeline *pipeline, CoglPipelineBackendGlslPrivate *priv) { if (priv) { pipeline->backend_privs[COGL_PIPELINE_BACKEND_GLSL] = priv; pipeline->backend_priv_set_mask |= COGL_PIPELINE_BACKEND_GLSL_MASK; } else pipeline->backend_priv_set_mask &= ~COGL_PIPELINE_BACKEND_GLSL_MASK; } static GlslProgramState * get_glsl_program_state (CoglPipeline *pipeline) { CoglPipelineBackendGlslPrivate *priv = get_glsl_priv (pipeline); if (!priv) return NULL; return priv->glsl_program_state; } static void dirty_glsl_program_state (CoglPipeline *pipeline) { CoglPipelineBackendGlslPrivate *priv; _COGL_GET_CONTEXT (ctx, NO_RETVAL); priv = get_glsl_priv (pipeline); if (!priv) return; if (priv->glsl_program_state) { glsl_program_state_unref (priv->glsl_program_state); priv->glsl_program_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_backend_glsl_start (CoglPipeline *pipeline, int n_layers, unsigned long pipelines_difference) { CoglPipelineBackendGlslPrivate *priv; CoglPipeline *authority; CoglPipelineBackendGlslPrivate *authority_priv; CoglProgram *user_program; GSList *l; int i; _COGL_GET_CONTEXT (ctx, FALSE); if (!cogl_features_available (COGL_FEATURE_SHADERS_GLSL)) return FALSE; user_program = cogl_pipeline_get_user_program (pipeline); if (user_program && _cogl_program_get_language (user_program) != COGL_SHADER_LANGUAGE_GLSL) return FALSE; /* Now lookup our glsl backend private state (allocating if * necessary) */ priv = get_glsl_priv (pipeline); if (!priv) { priv = g_slice_new0 (CoglPipelineBackendGlslPrivate); set_glsl_priv (pipeline, priv); } if (!priv->glsl_program_state) { /* If we don't have an associated glsl program yet then find the * glsl-authority (the oldest ancestor whose state will result in * the same program being generated as for this pipeline). * * We always make sure to associate new programs with the * glsl-authority to maximize the chance that other pipelines can * share it. */ authority = _cogl_pipeline_find_codegen_authority (pipeline, user_program); authority_priv = get_glsl_priv (authority); if (!authority_priv) { authority_priv = g_slice_new0 (CoglPipelineBackendGlslPrivate); set_glsl_priv (authority, authority_priv); } /* If we don't have an existing program associated with the * glsl-authority then start generating code for a new program... */ if (!authority_priv->glsl_program_state) { GlslProgramState *glsl_program_state = glsl_program_state_new (n_layers); authority_priv->glsl_program_state = glsl_program_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); } if (priv->glsl_program_state->gl_program) { /* 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 */ if (user_program == NULL || (priv->glsl_program_state->user_program_age == user_program->age)) return TRUE; /* We need to recreate the program so destroy the existing one */ delete_program (priv->glsl_program_state->gl_program); priv->glsl_program_state->gl_program = 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 */ /* 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; } /* We reuse two grow-only GStrings for code-gen. One string contains the uniform and attribute declarations while the other contains the main function. We need two strings because we need to dynamically declare attributes as the 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; g_string_append (priv->glsl_program_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; } no_fragment_shader_needed: return TRUE; } static void add_constant_lookup (GlslProgramState *glsl_program_state, CoglPipeline *pipeline, CoglPipelineLayer *layer, const char *swizzle) { 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) { g_string_append_printf (glsl_program_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; } g_string_append_printf (glsl_program_state->source, "_cogl_layer_constant_%i.%s", unit_index, swizzle); } static void add_texture_lookup (GlslProgramState *glsl_program_state, CoglPipeline *pipeline, CoglPipelineLayer *layer, const char *swizzle) { CoglHandle texture; int unit_index = _cogl_pipeline_layer_get_unit_index (layer); const char *target_string, *tex_coord_swizzle; texture = _cogl_pipeline_layer_get_texture (layer); if (texture == COGL_INVALID_HANDLE) { target_string = "2D"; tex_coord_swizzle = "st"; } else { GLenum gl_target; cogl_texture_get_gl_texture (texture, NULL, &gl_target); switch (gl_target) { #ifndef HAVE_COGL_GLES2 case GL_TEXTURE_1D: target_string = "1D"; tex_coord_swizzle = "s"; break; #endif case GL_TEXTURE_2D: target_string = "2D"; tex_coord_swizzle = "st"; break; #ifdef GL_ARB_texture_rectangle case GL_TEXTURE_RECTANGLE_ARB: target_string = "2DRect"; tex_coord_swizzle = "st"; break; #endif case GL_TEXTURE_3D: target_string = "3D"; tex_coord_swizzle = "stp"; break; default: g_assert_not_reached (); } } /* Create a sampler uniform for this layer if we haven't already */ if (!glsl_program_state->unit_state[unit_index].sampled) { g_string_append_printf (glsl_program_state->header, "uniform sampler%s _cogl_sampler_%i;\n", target_string, unit_index); glsl_program_state->unit_state[unit_index].sampled = TRUE; } g_string_append_printf (glsl_program_state->source, "texture%s (_cogl_sampler_%i, ", target_string, unit_index); /* If point sprite coord generation is being used then divert to the built-in varying var for that instead of the texture coordinates. We don't want to do this under GL because in that case we will instead use glTexEnv(GL_COORD_REPLACE) to replace the texture coords with the point sprite coords. Although GL also supports the gl_PointCoord variable, it requires GLSL 1.2 which would mean we would have to declare the GLSL version and check for it */ #ifdef HAVE_COGL_GLES2 if (cogl_pipeline_get_layer_point_sprite_coords_enabled (pipeline, layer->index)) g_string_append_printf (glsl_program_state->source, "gl_PointCoord.%s", tex_coord_swizzle); else #endif g_string_append_printf (glsl_program_state->source, "cogl_tex_coord_in[%d].%s", unit_index, tex_coord_swizzle); g_string_append_printf (glsl_program_state->source, ").%s", swizzle); } typedef struct { int unit_index; CoglPipelineLayer *layer; } FindPipelineLayerData; static gboolean find_pipeline_layer_cb (CoglPipelineLayer *layer, void *user_data) { FindPipelineLayerData *data = user_data; int unit_index; unit_index = _cogl_pipeline_layer_get_unit_index (layer); if (unit_index == data->unit_index) { data->layer = layer; return FALSE; } return TRUE; } static void add_arg (GlslProgramState *glsl_program_state, CoglPipeline *pipeline, CoglPipelineLayer *layer, GLint src, GLenum operand, const char *swizzle) { GString *shader_source = glsl_program_state->source; char alpha_swizzle[5] = "aaaa"; g_string_append_c (shader_source, '('); if (operand == GL_ONE_MINUS_SRC_COLOR || operand == GL_ONE_MINUS_SRC_ALPHA) g_string_append_printf (shader_source, "vec4(1.0, 1.0, 1.0, 1.0).%s - ", swizzle); /* If the operand is reading from the alpha then replace the swizzle with the same number of copies of the alpha */ if (operand == GL_SRC_ALPHA || operand == GL_ONE_MINUS_SRC_ALPHA) { alpha_swizzle[strlen (swizzle)] = '\0'; swizzle = alpha_swizzle; } switch (src) { case GL_TEXTURE: add_texture_lookup (glsl_program_state, pipeline, layer, swizzle); break; case GL_CONSTANT: add_constant_lookup (glsl_program_state, pipeline, layer, swizzle); break; case GL_PREVIOUS: if (_cogl_pipeline_layer_get_unit_index (layer) > 0) { g_string_append_printf (shader_source, "cogl_color_out.%s", swizzle); break; } /* flow through */ case GL_PRIMARY_COLOR: g_string_append_printf (shader_source, "cogl_color_in.%s", swizzle); break; default: if (src >= GL_TEXTURE0 && src < GL_TEXTURE0 + 32) { FindPipelineLayerData data; data.unit_index = src - GL_TEXTURE0; data.layer = layer; _cogl_pipeline_foreach_layer_internal (pipeline, find_pipeline_layer_cb, &data); add_texture_lookup (glsl_program_state, pipeline, data.layer, swizzle); } break; } g_string_append_c (shader_source, ')'); } static void append_masked_combine (CoglPipeline *pipeline, CoglPipelineLayer *layer, const char *swizzle, GLint function, GLint *src, GLint *op) { GlslProgramState *glsl_program_state = get_glsl_program_state (pipeline); GString *shader_source = glsl_program_state->source; g_string_append_printf (glsl_program_state->source, " cogl_color_out.%s = ", swizzle); switch (function) { case GL_REPLACE: add_arg (glsl_program_state, pipeline, layer, src[0], op[0], swizzle); break; case GL_MODULATE: add_arg (glsl_program_state, pipeline, layer, src[0], op[0], swizzle); g_string_append (shader_source, " * "); add_arg (glsl_program_state, pipeline, layer, src[1], op[1], swizzle); break; case GL_ADD: add_arg (glsl_program_state, pipeline, layer, src[0], op[0], swizzle); g_string_append (shader_source, " + "); add_arg (glsl_program_state, pipeline, layer, src[1], op[1], swizzle); break; case GL_ADD_SIGNED: add_arg (glsl_program_state, pipeline, layer, src[0], op[0], swizzle); g_string_append (shader_source, " + "); add_arg (glsl_program_state, pipeline, layer, src[1], op[1], swizzle); g_string_append_printf (shader_source, " - vec4(0.5, 0.5, 0.5, 0.5).%s", swizzle); break; case GL_SUBTRACT: add_arg (glsl_program_state, pipeline, layer, src[0], op[0], swizzle); g_string_append (shader_source, " - "); add_arg (glsl_program_state, pipeline, layer, src[1], op[1], swizzle); break; case GL_INTERPOLATE: add_arg (glsl_program_state, pipeline, layer, src[0], op[0], swizzle); g_string_append (shader_source, " * "); add_arg (glsl_program_state, pipeline, layer, src[2], op[2], swizzle); g_string_append (shader_source, " + "); add_arg (glsl_program_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, src[2], op[2], swizzle); g_string_append_c (shader_source, ')'); break; case GL_DOT3_RGB: case GL_DOT3_RGBA: g_string_append (shader_source, "vec4(4 * (("); add_arg (glsl_program_state, pipeline, layer, src[0], op[0], "r"); g_string_append (shader_source, " - 0.5) * ("); add_arg (glsl_program_state, pipeline, layer, src[1], op[1], "r"); g_string_append (shader_source, " - 0.5) + ("); add_arg (glsl_program_state, pipeline, layer, src[0], op[0], "g"); g_string_append (shader_source, " - 0.5) * ("); add_arg (glsl_program_state, pipeline, layer, src[1], op[1], "g"); g_string_append (shader_source, " - 0.5) + ("); add_arg (glsl_program_state, pipeline, layer, src[0], op[0], "b"); g_string_append (shader_source, " - 0.5) * ("); add_arg (glsl_program_state, pipeline, layer, src[1], op[1], "b"); g_string_append_printf (shader_source, " - 0.5))).%s", swizzle); break; } g_string_append_printf (shader_source, ";\n"); } static gboolean _cogl_pipeline_backend_glsl_add_layer (CoglPipeline *pipeline, CoglPipelineLayer *layer, unsigned long layers_difference) { GlslProgramState *glsl_program_state = get_glsl_program_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) return TRUE; if (!_cogl_pipeline_need_texture_combine_separate (combine_authority) || /* GL_DOT3_RGBA Is a bit weird as a GL_COMBINE_RGB function * since if you use it, it overrides your ALPHA function... */ big_state->texture_combine_rgb_func == GL_DOT3_RGBA) append_masked_combine (pipeline, layer, "rgba", big_state->texture_combine_rgb_func, big_state->texture_combine_rgb_src, big_state->texture_combine_rgb_op); else { append_masked_combine (pipeline, layer, "rgb", big_state->texture_combine_rgb_func, big_state->texture_combine_rgb_src, big_state->texture_combine_rgb_op); append_masked_combine (pipeline, layer, "a", big_state->texture_combine_alpha_func, big_state->texture_combine_alpha_src, big_state->texture_combine_alpha_op); } return TRUE; } gboolean _cogl_pipeline_backend_glsl_passthrough (CoglPipeline *pipeline) { GlslProgramState *glsl_program_state = get_glsl_program_state (pipeline); if (!glsl_program_state->source) return TRUE; g_string_append (glsl_program_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 */ #ifdef HAVE_COGL_GLES2 static void add_alpha_test_snippet (CoglPipeline *pipeline, GlslProgramState *glsl_program_state) { CoglPipelineAlphaFunc alpha_func; alpha_func = cogl_pipeline_get_alpha_test_function (pipeline); if (alpha_func == COGL_PIPELINE_ALPHA_FUNC_ALWAYS) /* Do nothing */ return; if (alpha_func == COGL_PIPELINE_ALPHA_FUNC_NEVER) { /* Always discard the fragment */ g_string_append (glsl_program_state->source, " discard;\n"); return; } /* 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, "uniform float _cogl_alpha_test_ref;\n"); g_string_append (glsl_program_state->source, " if (cogl_color_out.a "); switch (alpha_func) { case COGL_PIPELINE_ALPHA_FUNC_LESS: g_string_append (glsl_program_state->source, ">="); break; case COGL_PIPELINE_ALPHA_FUNC_EQUAL: g_string_append (glsl_program_state->source, "!="); break; case COGL_PIPELINE_ALPHA_FUNC_LEQUAL: g_string_append (glsl_program_state->source, ">"); break; case COGL_PIPELINE_ALPHA_FUNC_GREATER: g_string_append (glsl_program_state->source, "<="); break; case COGL_PIPELINE_ALPHA_FUNC_NOTEQUAL: g_string_append (glsl_program_state->source, "=="); break; case COGL_PIPELINE_ALPHA_FUNC_GEQUAL: g_string_append (glsl_program_state->source, "< "); break; case COGL_PIPELINE_ALPHA_FUNC_ALWAYS: case COGL_PIPELINE_ALPHA_FUNC_NEVER: g_assert_not_reached (); break; } g_string_append (glsl_program_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_backend_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; _COGL_GET_CONTEXT (ctx, FALSE); gl_program = glsl_program_state->gl_program; user_program = cogl_pipeline_get_user_program (pipeline); if (gl_program == 0) { gl_program_changed = TRUE; GE_RET( gl_program, glCreateProgram () ); if (user_program) { GSList *l; /* 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 (shader); GE( glAttachShader (gl_program, shader->gl_handle) ); } glsl_program_state->user_program_age = user_program->age; } if (glsl_program_state->source) { const char *source_strings[2]; GLint lengths[2]; GLint compile_status; GLuint shader; COGL_STATIC_COUNTER (backend_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, backend_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, 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; } #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; } static void _cogl_pipeline_backend_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 } /* 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_backend_glsl_layer_pre_change_notify ( CoglPipeline *owner, CoglPipelineLayer *layer, CoglPipelineLayerState change) { CoglPipelineBackendGlslPrivate *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)) { dirty_glsl_program_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 _cogl_pipeline_backend_glsl_free_priv (CoglPipeline *pipeline) { CoglPipelineBackendGlslPrivate *priv = get_glsl_priv (pipeline); if (priv) { if (priv->glsl_program_state) glsl_program_state_unref (priv->glsl_program_state); g_slice_free (CoglPipelineBackendGlslPrivate, priv); set_glsl_priv (pipeline, NULL); } } const CoglPipelineBackend _cogl_pipeline_glsl_backend = { _cogl_pipeline_backend_glsl_get_max_texture_units, _cogl_pipeline_backend_glsl_start, _cogl_pipeline_backend_glsl_add_layer, _cogl_pipeline_backend_glsl_passthrough, _cogl_pipeline_backend_glsl_end, _cogl_pipeline_backend_glsl_pre_change_notify, NULL, /* pipeline_set_parent_notify */ _cogl_pipeline_backend_glsl_layer_pre_change_notify, _cogl_pipeline_backend_glsl_free_priv, NULL /* free_layer_priv */ }; #endif /* COGL_PIPELINE_BACKEND_GLSL */