/* 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_fragend_arbfp_layer_pre_change_notify (
                                                CoglPipeline *owner,
                                                CoglPipelineLayer *layer,
                                                CoglPipelineLayerState change)
{
  CoglPipelineShaderState *shader_state = get_shader_state (owner);

  _COGL_GET_CONTEXT (ctx, NO_RETVAL);

  if (!shader_state)
    return;

  if ((change & _cogl_pipeline_get_layer_state_for_fragment_codegen (ctx)))
    {
      dirty_shader_state (owner);
      return;
    }

  if (change & COGL_PIPELINE_LAYER_STATE_COMBINE_CONSTANT)
    {
      int unit_index = _cogl_pipeline_layer_get_unit_index (layer);
      shader_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 gboolean
_cogl_pipeline_vertend_glsl_add_layer (CoglPipeline *pipeline,
                                       CoglPipelineLayer *layer,
                                       unsigned long layers_difference)
{
  CoglPipelineShaderState *shader_state;
  int unit_index;

  _COGL_GET_CONTEXT (ctx, FALSE);

  shader_state = get_shader_state (pipeline);

  unit_index = _cogl_pipeline_layer_get_unit_index (layer);

  if (ctx->driver != COGL_DRIVER_GLES2)
    {
      /* We are using the fixed function uniforms for the user matrices
         and the only way to set them is with the fixed function API so we
         still need to flush them here */
      if (layers_difference & COGL_PIPELINE_LAYER_STATE_USER_MATRIX)
        {
          CoglPipelineLayerState state = COGL_PIPELINE_LAYER_STATE_USER_MATRIX;
          CoglPipelineLayer *authority =
            _cogl_pipeline_layer_get_authority (layer, state);
          CoglTextureUnit *unit = _cogl_get_texture_unit (unit_index);

          _cogl_matrix_stack_set (unit->matrix_stack,
                                  &authority->big_state->matrix);

          _cogl_set_active_texture_unit (unit_index);

          _cogl_matrix_stack_flush_to_gl (unit->matrix_stack,
                                          COGL_MATRIX_TEXTURE);
        }
    }

  if (shader_state->source == NULL)
    return TRUE;

  /* Transform the texture coordinates by the layer's user matrix.
   *
   * FIXME: this should avoid doing the transform if there is no user
   * matrix set. This might need a separate layer state flag for
   * whether there is a user matrix
   *
   * FIXME: we could be more clever here and try to detect if the
   * fragment program is going to use the texture coordinates and
   * avoid setting them if not
   */

  g_string_append_printf (shader_state->source,
                          "  cogl_tex_coord_out[%i] = "
                          "cogl_texture_matrix[%i] * cogl_tex_coord%i_in;\n",
                          unit_index, unit_index, unit_index);

  return TRUE;
}
GLuint
_cogl_pipeline_vertend_glsl_get_shader (CoglPipeline *pipeline)
{
  CoglPipelineShaderState *shader_state = get_shader_state (pipeline);

  if (shader_state)
    return shader_state->gl_shader;
  else
    return 0;
}
static CoglBool
_cogl_pipeline_fragend_arbfp_passthrough (CoglPipeline *pipeline)
{
  CoglPipelineShaderState *shader_state = get_shader_state (pipeline);

  if (!shader_state->source)
    return TRUE;

  g_string_append (shader_state->source,
                   "MOV output, fragment.color.primary;\n");
  return TRUE;
}
/* 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_vertend_glsl_layer_pre_change_notify (
                                                CoglPipeline *owner,
                                                CoglPipelineLayer *layer,
                                                CoglPipelineLayerState change)
{
  CoglPipelineShaderState *shader_state;

  shader_state = get_shader_state (owner);
  if (!shader_state)
    return;

  if ((change & COGL_PIPELINE_LAYER_STATE_AFFECTS_VERTEX_CODEGEN))
    {
      dirty_shader_state (owner);
      return;
    }

  /* 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. */
}
static gboolean
_cogl_pipeline_vertend_glsl_end (CoglPipeline *pipeline,
                                 unsigned long pipelines_difference)
{
  CoglPipelineShaderState *shader_state;

  _COGL_GET_CONTEXT (ctx, FALSE);

  shader_state = get_shader_state (pipeline);

  if (shader_state->source)
    {
      const char *source_strings[2];
      GLint lengths[2];
      GLint compile_status;
      GLuint shader;
      int n_layers;

      COGL_STATIC_COUNTER (vertend_glsl_compile_counter,
                           "glsl vertex compile counter",
                           "Increments each time a new GLSL "
                           "vertex shader is compiled",
                           0 /* no application private data */);
      COGL_COUNTER_INC (_cogl_uprof_context, vertend_glsl_compile_counter);

      g_string_append (shader_state->source,
                       "  cogl_position_out = "
                       "cogl_modelview_projection_matrix * "
                       "cogl_position_in;\n"
                       "  cogl_color_out = cogl_color_in;\n"
                       "}\n");

      GE_RET( shader, ctx, glCreateShader (GL_VERTEX_SHADER) );

      lengths[0] = shader_state->header->len;
      source_strings[0] = shader_state->header->str;
      lengths[1] = shader_state->source->len;
      source_strings[1] = shader_state->source->str;

      n_layers = cogl_pipeline_get_n_layers (pipeline);

      _cogl_shader_set_source_with_boilerplate (shader, GL_VERTEX_SHADER,
                                                n_layers,
                                                2, /* count */
                                                source_strings, lengths);

      GE( ctx, glCompileShader (shader) );
      GE( ctx, glGetShaderiv (shader, GL_COMPILE_STATUS, &compile_status) );

      if (!compile_status)
        {
          GLint len = 0;
          char *shader_log;

          GE( ctx, glGetShaderiv (shader, GL_INFO_LOG_LENGTH, &len) );
          shader_log = g_alloca (len);
          GE( ctx, glGetShaderInfoLog (shader, len, &len, shader_log) );
          g_warning ("Shader compilation failed:\n%s", shader_log);
        }

      shader_state->header = NULL;
      shader_state->source = NULL;
      shader_state->gl_shader = shader;
    }

  return TRUE;
}
static gboolean
_cogl_pipeline_vertend_glsl_start (CoglPipeline *pipeline,
                                   int n_layers,
                                   unsigned long pipelines_difference)
{
  CoglPipelineShaderState *shader_state;
  CoglPipeline *template_pipeline = NULL;
  CoglProgram *user_program;

  _COGL_GET_CONTEXT (ctx, FALSE);

  if (!cogl_features_available (COGL_FEATURE_SHADERS_GLSL))
    return FALSE;

  user_program = cogl_pipeline_get_user_program (pipeline);

  /* If the user program has a vertex shader that isn't GLSL then the
     appropriate vertend for that language should handle it */
  if (user_program &&
      _cogl_program_has_vertex_shader (user_program) &&
      _cogl_program_get_language (user_program) != COGL_SHADER_LANGUAGE_GLSL)
    return FALSE;

  /* Now lookup our glsl backend private state (allocating if
   * necessary) */
  shader_state = get_shader_state (pipeline);

  if (shader_state == NULL)
    {
      CoglPipeline *authority;

      /* Get the authority for anything affecting vertex shader
         state */
      authority = _cogl_pipeline_find_equivalent_parent
        (pipeline,
         COGL_PIPELINE_STATE_AFFECTS_VERTEX_CODEGEN &
         ~COGL_PIPELINE_STATE_LAYERS,
         COGL_PIPELINE_LAYER_STATE_AFFECTS_VERTEX_CODEGEN);

      shader_state = get_shader_state (authority);

      if (shader_state == NULL)
        {
          /* Check if there is already a similar cached pipeline whose
             shader state we can share */
          if (G_LIKELY (!(COGL_DEBUG_ENABLED
                          (COGL_DEBUG_DISABLE_PROGRAM_CACHES))))
            {
              template_pipeline =
                _cogl_pipeline_cache_get_vertex_template (ctx->pipeline_cache,
                                                          authority);

              shader_state = get_shader_state (template_pipeline);
            }

          if (shader_state)
            shader_state->ref_count++;
          else
            shader_state = shader_state_new ();

          set_shader_state (authority, shader_state);

          if (template_pipeline)
            {
              shader_state->ref_count++;
              set_shader_state (template_pipeline, shader_state);
            }
        }

      if (authority != pipeline)
        {
          shader_state->ref_count++;
          set_shader_state (pipeline, shader_state);
        }
    }

  if (shader_state->gl_shader)
    {
      /* 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 ||
          shader_state->user_program_age == user_program->age)
        return TRUE;

      /* We need to recreate the shader so destroy the existing one */
      GE( ctx, glDeleteShader (shader_state->gl_shader) );
      shader_state->gl_shader = 0;
    }

  /* If we make it here then we have a 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 */

  if (user_program)
    shader_state->user_program_age = user_program->age;

  /* If the user program contains a vertex shader then we don't need
     to generate one */
  if (user_program &&
      _cogl_program_has_vertex_shader (user_program))
    return TRUE;

  /* 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->codegen_header_buffer, 0);
  g_string_set_size (ctx->codegen_source_buffer, 0);
  shader_state->header = ctx->codegen_header_buffer;
  shader_state->source = ctx->codegen_source_buffer;

  g_string_append (shader_state->source,
                   "void\n"
                   "main ()\n"
                   "{\n");

  if (ctx->driver == COGL_DRIVER_GLES2)
    /* There is no builtin uniform for the pointsize on GLES2 so we need
       to copy it from the custom uniform in the vertex shader */
    g_string_append (shader_state->source,
                     "  cogl_point_size_out = cogl_point_size_in;\n");
  /* On regular OpenGL we'll just flush the point size builtin */
  else if (pipelines_difference & COGL_PIPELINE_STATE_POINT_SIZE)
    {
      CoglPipeline *authority =
        _cogl_pipeline_get_authority (pipeline, COGL_PIPELINE_STATE_POINT_SIZE);

      if (ctx->point_size_cache != authority->big_state->point_size)
        {
          GE( ctx, glPointSize (authority->big_state->point_size) );
          ctx->point_size_cache = authority->big_state->point_size;
        }
    }

  return TRUE;
}
static CoglBool
_cogl_pipeline_fragend_arbfp_end (CoglPipeline *pipeline,
                                  unsigned long pipelines_difference)
{
  CoglPipelineShaderState *shader_state = get_shader_state (pipeline);
  GLuint gl_program;
  UpdateConstantsState state;

  _COGL_GET_CONTEXT (ctx, FALSE);

  if (shader_state->source)
    {
      GLenum gl_error;
      COGL_STATIC_COUNTER (fragend_arbfp_compile_counter,
                           "arbfp compile counter",
                           "Increments each time a new ARBfp "
                           "program is compiled",
                           0 /* no application private data */);

      COGL_COUNTER_INC (_cogl_uprof_context, fragend_arbfp_compile_counter);

      g_string_append (shader_state->source,
                       "MOV result.color,output;\n");
      g_string_append (shader_state->source, "END\n");

      if (G_UNLIKELY (COGL_DEBUG_ENABLED (COGL_DEBUG_SHOW_SOURCE)))
        g_message ("pipeline program:\n%s", shader_state->source->str);

      GE (ctx, glGenPrograms (1, &shader_state->gl_program));

      GE (ctx, glBindProgram (GL_FRAGMENT_PROGRAM_ARB,
                              shader_state->gl_program));

      while ((gl_error = ctx->glGetError ()) != GL_NO_ERROR)
        ;
      ctx->glProgramString (GL_FRAGMENT_PROGRAM_ARB,
                            GL_PROGRAM_FORMAT_ASCII_ARB,
                            shader_state->source->len,
                            shader_state->source->str);
      if (ctx->glGetError () != GL_NO_ERROR)
        {
          g_warning ("\n%s\n%s",
                     shader_state->source->str,
                     ctx->glGetString (GL_PROGRAM_ERROR_STRING_ARB));
        }

      shader_state->source = NULL;
    }

  gl_program = shader_state->gl_program;

  GE (ctx, glBindProgram (GL_FRAGMENT_PROGRAM_ARB, gl_program));
  _cogl_use_fragment_program (0, COGL_PIPELINE_PROGRAM_TYPE_ARBFP);

  state.unit = 0;
  state.shader_state = shader_state;
  /* If this arbfp program was last used with a different pipeline
   * then we need to ensure we update all program.local params */
  state.update_all =
    pipeline != shader_state->last_used_for_pipeline;
  cogl_pipeline_foreach_layer (pipeline,
                               update_constants_cb,
                               &state);

  /* We need to track what pipeline used this arbfp program last since
   * we will need to update program.local params when switching
   * between different pipelines. */
  shader_state->last_used_for_pipeline = pipeline;

  return TRUE;
}
static CoglBool
_cogl_pipeline_fragend_arbfp_add_layer (CoglPipeline *pipeline,
                                        CoglPipelineLayer *layer,
                                        unsigned long layers_difference)
{
  CoglPipelineShaderState *shader_state = get_shader_state (pipeline);
  CoglPipelineLayer *combine_authority =
    _cogl_pipeline_layer_get_authority (layer,
                                        COGL_PIPELINE_LAYER_STATE_COMBINE);
  CoglPipelineLayerBigState *big_state = combine_authority->big_state;

  /* Notes...
   *
   * We are ignoring the issue of texture indirection limits until
   * someone complains (Ref Section 3.11.6 in the ARB_fragment_program
   * spec)
   *
   * There always five TEMPs named tmp0, tmp1 and tmp2, tmp3 and tmp4
   * available and these constants: 'one' = {1, 1, 1, 1}, 'half'
   * {.5, .5, .5, .5}, 'two' = {2, 2, 2, 2}, 'minus_one' = {-1, -1,
   * -1, -1}
   *
   * tmp0-2 are intended for dealing with some of the texture combine
   * operands (e.g. GL_ONE_MINUS_SRC_COLOR) tmp3/4 are for dealing
   * with the GL_ADD_SIGNED texture combine and the GL_DOT3_RGB[A]
   * functions.
   *
   * Each layer outputs to the TEMP called "output", and reads from
   * output if it needs to refer to GL_PREVIOUS. (we detect if we are
   * layer0 so we will read fragment.color for GL_PREVIOUS in that
   * case)
   *
   * We aim to do all the channels together if the same function is
   * used for RGB as for A.
   *
   * We aim to avoid string duplication / allocations during codegen.
   *
   * We are careful to only saturate when writing to output.
   */

  if (!shader_state->source)
    return TRUE;

  if (!_cogl_pipeline_layer_needs_combine_separate (combine_authority))
    {
      append_masked_combine (pipeline,
                             layer,
                             COGL_BLEND_STRING_CHANNEL_MASK_RGBA,
                             big_state->texture_combine_rgb_func,
                             big_state->texture_combine_rgb_src,
                             big_state->texture_combine_rgb_op);
    }
  else if (big_state->texture_combine_rgb_func ==
           COGL_PIPELINE_COMBINE_FUNC_DOT3_RGBA)
    {
      /* GL_DOT3_RGBA Is a bit weird as a GL_COMBINE_RGB function
       * since if you use it, it overrides your ALPHA function...
       */
      append_masked_combine (pipeline,
                             layer,
                             COGL_BLEND_STRING_CHANNEL_MASK_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,
                             COGL_BLEND_STRING_CHANNEL_MASK_RGB,
                             big_state->texture_combine_rgb_func,
                             big_state->texture_combine_rgb_src,
                             big_state->texture_combine_rgb_op);
      append_masked_combine (pipeline,
                             layer,
                             COGL_BLEND_STRING_CHANNEL_MASK_ALPHA,
                             big_state->texture_combine_alpha_func,
                             big_state->texture_combine_alpha_src,
                             big_state->texture_combine_alpha_op);
    }

  return TRUE;
}
/* Note: we are trying to avoid duplicating strings during codegen
 * which is why we have the slightly awkward
 * CoglPipelineFragendARBfpArg mechanism. */
static void
setup_arg (CoglPipeline *pipeline,
           CoglPipelineLayer *layer,
           CoglBlendStringChannelMask mask,
           int arg_index,
           CoglPipelineCombineSource src,
           GLint op,
           CoglPipelineFragendARBfpArg *arg)
{
  CoglPipelineShaderState *shader_state = get_shader_state (pipeline);
  static const char *tmp_name[3] = { "tmp0", "tmp1", "tmp2" };

  switch (src)
    {
    case COGL_PIPELINE_COMBINE_SOURCE_TEXTURE:
      arg->type = COGL_PIPELINE_FRAGEND_ARBFP_ARG_TYPE_TEXTURE;
      arg->name = "texel%d";
      arg->texture_unit = _cogl_pipeline_layer_get_unit_index (layer);
      setup_texture_source (shader_state,
                            arg->texture_unit,
                            _cogl_pipeline_layer_get_texture_type (layer));
      break;
    case COGL_PIPELINE_COMBINE_SOURCE_CONSTANT:
      {
        int unit_index = _cogl_pipeline_layer_get_unit_index (layer);
        UnitState *unit_state = &shader_state->unit_state[unit_index];

        unit_state->constant_id = shader_state->next_constant_id++;
        unit_state->has_combine_constant = TRUE;
        unit_state->dirty_combine_constant = TRUE;

        arg->type = COGL_PIPELINE_FRAGEND_ARBFP_ARG_TYPE_CONSTANT;
        arg->name = "program.local[%d]";
        arg->constant_id = unit_state->constant_id;
        break;
      }
    case COGL_PIPELINE_COMBINE_SOURCE_PRIMARY_COLOR:
      arg->type = COGL_PIPELINE_FRAGEND_ARBFP_ARG_TYPE_SIMPLE;
      arg->name = "fragment.color.primary";
      break;
    case COGL_PIPELINE_COMBINE_SOURCE_PREVIOUS:
      arg->type = COGL_PIPELINE_FRAGEND_ARBFP_ARG_TYPE_SIMPLE;
      if (_cogl_pipeline_layer_get_unit_index (layer) == 0)
        arg->name = "fragment.color.primary";
      else
        arg->name = "output";
      break;
    default: /* Sample the texture attached to a specific layer */
      {
        int layer_num = src - COGL_PIPELINE_COMBINE_SOURCE_TEXTURE0;
        CoglPipelineGetLayerFlags flags = COGL_PIPELINE_GET_LAYER_NO_CREATE;
        CoglPipelineLayer *other_layer =
          _cogl_pipeline_get_layer_with_flags (pipeline, layer_num, flags);

        if (other_layer == NULL)
          {
            static CoglBool warning_seen = FALSE;
            if (!warning_seen)
              {
                g_warning ("The application is trying to use a texture "
                           "combine with a layer number that does not exist");
                warning_seen = TRUE;
              }
            arg->type = COGL_PIPELINE_FRAGEND_ARBFP_ARG_TYPE_SIMPLE;
            arg->name = "output";
          }
        else
          {
            CoglTextureType texture_type;

            arg->type = COGL_PIPELINE_FRAGEND_ARBFP_ARG_TYPE_TEXTURE;
            arg->name = "texture[%d]";
            arg->texture_unit =
              _cogl_pipeline_layer_get_unit_index (other_layer);
            texture_type = _cogl_pipeline_layer_get_texture_type (other_layer);
            setup_texture_source (shader_state,
                                  arg->texture_unit,
                                  texture_type);
          }
      }
      break;
    }

  arg->swizzle = "";

  switch (op)
    {
    case COGL_PIPELINE_COMBINE_OP_SRC_COLOR:
      break;
    case COGL_PIPELINE_COMBINE_OP_ONE_MINUS_SRC_COLOR:
      g_string_append_printf (shader_state->source,
                              "SUB tmp%d, one, ",
                              arg_index);
      append_arg (shader_state->source, arg);
      g_string_append_printf (shader_state->source, ";\n");
      arg->type = COGL_PIPELINE_FRAGEND_ARBFP_ARG_TYPE_SIMPLE;
      arg->name = tmp_name[arg_index];
      arg->swizzle = "";
      break;
    case COGL_PIPELINE_COMBINE_OP_SRC_ALPHA:
      /* avoid a swizzle if we know RGB are going to be masked
       * in the end anyway */
      if (mask != COGL_BLEND_STRING_CHANNEL_MASK_ALPHA)
        arg->swizzle = ".a";
      break;
    case COGL_PIPELINE_COMBINE_OP_ONE_MINUS_SRC_ALPHA:
      g_string_append_printf (shader_state->source,
                              "SUB tmp%d, one, ",
                              arg_index);
      append_arg (shader_state->source, arg);
      /* avoid a swizzle if we know RGB are going to be masked
       * in the end anyway */
      if (mask != COGL_BLEND_STRING_CHANNEL_MASK_ALPHA)
        g_string_append_printf (shader_state->source, ".a;\n");
      else
        g_string_append_printf (shader_state->source, ";\n");
      arg->type = COGL_PIPELINE_FRAGEND_ARBFP_ARG_TYPE_SIMPLE;
      arg->name = tmp_name[arg_index];
      break;
    default:
      g_error ("Unknown texture combine operator %d", op);
      break;
    }
}
static void
append_function (CoglPipeline *pipeline,
                 CoglBlendStringChannelMask mask,
                 GLint function,
                 CoglPipelineFragendARBfpArg *args,
                 int n_args)
{
  CoglPipelineShaderState *shader_state = get_shader_state (pipeline);
  const char *mask_name;

  switch (mask)
    {
    case COGL_BLEND_STRING_CHANNEL_MASK_RGB:
      mask_name = ".rgb";
      break;
    case COGL_BLEND_STRING_CHANNEL_MASK_ALPHA:
      mask_name = ".a";
      break;
    case COGL_BLEND_STRING_CHANNEL_MASK_RGBA:
      mask_name = "";
      break;
    default:
      g_error ("Unknown channel mask %d", mask);
      mask_name = "";
    }

  switch (function)
    {
    case COGL_PIPELINE_COMBINE_FUNC_ADD:
      g_string_append_printf (shader_state->source,
                              "ADD_SAT output%s, ",
                              mask_name);
      break;
    case COGL_PIPELINE_COMBINE_FUNC_MODULATE:
      /* Note: no need to saturate since we can assume operands
       * have values in the range [0,1] */
      g_string_append_printf (shader_state->source, "MUL output%s, ",
                              mask_name);
      break;
    case COGL_PIPELINE_COMBINE_FUNC_REPLACE:
      /* Note: no need to saturate since we can assume operand
       * has a value in the range [0,1] */
      g_string_append_printf (shader_state->source, "MOV output%s, ",
                              mask_name);
      break;
    case COGL_PIPELINE_COMBINE_FUNC_SUBTRACT:
      g_string_append_printf (shader_state->source,
                              "SUB_SAT output%s, ",
                              mask_name);
      break;
    case COGL_PIPELINE_COMBINE_FUNC_ADD_SIGNED:
      g_string_append_printf (shader_state->source, "ADD tmp3%s, ",
                              mask_name);
      append_arg (shader_state->source, &args[0]);
      g_string_append (shader_state->source, ", ");
      append_arg (shader_state->source, &args[1]);
      g_string_append (shader_state->source, ";\n");
      g_string_append_printf (shader_state->source,
                              "SUB_SAT output%s, tmp3, half",
                              mask_name);
      n_args = 0;
      break;
    case COGL_PIPELINE_COMBINE_FUNC_DOT3_RGB:
    /* These functions are the same except that GL_DOT3_RGB never
     * updates the alpha channel.
     *
     * NB: GL_DOT3_RGBA is a bit special because it effectively forces
     * an RGBA mask and we end up ignoring any separate alpha channel
     * function.
     */
    case COGL_PIPELINE_COMBINE_FUNC_DOT3_RGBA:
      {
        const char *tmp4 = "tmp4";

        /* The maths for this was taken from Mesa;
         * apparently:
         *
         * tmp3 = 2*src0 - 1
         * tmp4 = 2*src1 - 1
         * output = DP3 (tmp3, tmp4)
         *
         * is the same as:
         *
         * output = 4 * DP3 (src0 - 0.5, src1 - 0.5)
         */

        g_string_append (shader_state->source, "MAD tmp3, two, ");
        append_arg (shader_state->source, &args[0]);
        g_string_append (shader_state->source, ", minus_one;\n");

        if (!fragend_arbfp_args_equal (&args[0], &args[1]))
          {
            g_string_append (shader_state->source, "MAD tmp4, two, ");
            append_arg (shader_state->source, &args[1]);
            g_string_append (shader_state->source, ", minus_one;\n");
          }
        else
          tmp4 = "tmp3";

        g_string_append_printf (shader_state->source,
                                "DP3_SAT output%s, tmp3, %s",
                                mask_name, tmp4);
        n_args = 0;
      }
      break;
    case COGL_PIPELINE_COMBINE_FUNC_INTERPOLATE:
      /* Note: no need to saturate since we can assume operands
       * have values in the range [0,1] */

      /* NB: GL_INTERPOLATE = arg0*arg2 + arg1*(1-arg2)
       * but LRP dst, a, b, c = b*a + c*(1-a) */
      g_string_append_printf (shader_state->source, "LRP output%s, ",
                              mask_name);
      append_arg (shader_state->source, &args[2]);
      g_string_append (shader_state->source, ", ");
      append_arg (shader_state->source, &args[0]);
      g_string_append (shader_state->source, ", ");
      append_arg (shader_state->source, &args[1]);
      n_args = 0;
      break;
    default:
      g_error ("Unknown texture combine function %d", function);
      g_string_append_printf (shader_state->source, "MUL_SAT output%s, ",
                              mask_name);
      n_args = 2;
      break;
    }

  if (n_args > 0)
    append_arg (shader_state->source, &args[0]);
  if (n_args > 1)
    {
      g_string_append (shader_state->source, ", ");
      append_arg (shader_state->source, &args[1]);
    }
  g_string_append (shader_state->source, ";\n");
}
static CoglBool
_cogl_pipeline_fragend_arbfp_start (CoglPipeline *pipeline,
                                    int n_layers,
                                    unsigned long pipelines_difference,
                                    int n_tex_coord_attribs)
{
  CoglPipelineShaderState *shader_state;
  CoglPipeline *authority;
  CoglPipeline *template_pipeline = NULL;

  _COGL_GET_CONTEXT (ctx, FALSE);

  /* First validate that we can handle the current state using ARBfp
   */

  if (!cogl_has_feature (ctx, COGL_FEATURE_ID_ARBFP))
    return FALSE;

  /* TODO: support fog */
  if (_cogl_pipeline_get_fog_enabled (pipeline))
    return FALSE;

  /* Fragment snippets are only supported in the GLSL fragend */
  if (_cogl_pipeline_has_fragment_snippets (pipeline))
    return FALSE;

  /* Now lookup our ARBfp backend private state */
  shader_state = get_shader_state (pipeline);

  /* If we have a valid shader_state then we are all set and don't
   * need to generate a new program. */
  if (shader_state)
    return TRUE;

  /* If we don't have an associated arbfp program yet then find the
   * arbfp-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
   * arbfp-authority to maximize the chance that other pipelines can
   * share it.
   */
  authority = _cogl_pipeline_find_equivalent_parent
    (pipeline,
     _cogl_pipeline_get_state_for_fragment_codegen (ctx) &
     ~COGL_PIPELINE_STATE_LAYERS,
     _cogl_pipeline_get_layer_state_for_fragment_codegen (ctx));
  shader_state = get_shader_state (authority);
  if (shader_state)
    {
      /* If we are going to share our program state with an arbfp-authority
       * then add a reference to the program state associated with that
       * arbfp-authority... */
      shader_state->ref_count++;
      set_shader_state (pipeline, shader_state);
      return TRUE;
    }

  /* If we haven't yet found an existing program then before we resort to
   * generating a new arbfp program we see if we can find a suitable
   * program in the pipeline_cache. */
  if (G_LIKELY (!(COGL_DEBUG_ENABLED (COGL_DEBUG_DISABLE_PROGRAM_CACHES))))
    {
      template_pipeline =
        _cogl_pipeline_cache_get_fragment_template (ctx->pipeline_cache,
                                                    authority);

      shader_state = get_shader_state (template_pipeline);

      if (shader_state)
        shader_state->ref_count++;
    }

  /* If we still haven't got a shader state then we'll have to create
     a new one */
  if (shader_state == NULL)
    {
      shader_state = shader_state_new (n_layers);

      /* We reuse a single grow-only GString for code-gen */
      g_string_set_size (ctx->codegen_source_buffer, 0);
      shader_state->source = ctx->codegen_source_buffer;
      g_string_append (shader_state->source,
                       "!!ARBfp1.0\n"
                       "TEMP output;\n"
                       "TEMP tmp0, tmp1, tmp2, tmp3, tmp4;\n"
                       "PARAM half = {.5, .5, .5, .5};\n"
                       "PARAM one = {1, 1, 1, 1};\n"
                       "PARAM two = {2, 2, 2, 2};\n"
                       "PARAM minus_one = {-1, -1, -1, -1};\n");
    }

  set_shader_state (pipeline, shader_state);

  /* Since we have already resolved the arbfp-authority at this point
   * we might as well also associate any program we find from the cache
   * with the authority too... */
  if (authority != pipeline)
    {
      shader_state->ref_count++;
      set_shader_state (authority, shader_state);
    }

  /* If we found a template then we'll attach it to that too so that
     next time a similar pipeline is used it can use the same state */
  if (template_pipeline)
    {
      shader_state->ref_count++;
      set_shader_state (template_pipeline, shader_state);
    }

  return TRUE;
}
static CoglBool
_cogl_pipeline_vertend_glsl_end (CoglPipeline *pipeline,
                                 unsigned long pipelines_difference)
{
  CoglPipelineShaderState *shader_state;

  _COGL_GET_CONTEXT (ctx, FALSE);

  shader_state = get_shader_state (pipeline);

  if (shader_state->source)
    {
      const char *source_strings[2];
      GLint lengths[2];
      GLint compile_status;
      GLuint shader;
      CoglPipelineSnippetData snippet_data;
      CoglPipelineSnippetList *vertex_snippets;

      COGL_STATIC_COUNTER (vertend_glsl_compile_counter,
                           "glsl vertex compile counter",
                           "Increments each time a new GLSL "
                           "vertex shader is compiled",
                           0 /* no application private data */);
      COGL_COUNTER_INC (_cogl_uprof_context, vertend_glsl_compile_counter);

      g_string_append (shader_state->header,
                       "void\n"
                       "cogl_real_vertex_transform ()\n"
                       "{\n"
                       "  cogl_position_out = "
                       "cogl_modelview_projection_matrix * "
                       "cogl_position_in;\n"
                       "}\n");

      g_string_append (shader_state->source,
                       "  cogl_vertex_transform ();\n"
                       "  cogl_color_out = cogl_color_in;\n"
                       "}\n");

      vertex_snippets = get_vertex_snippets (pipeline);

      /* Add hooks for the vertex transform part */
      memset (&snippet_data, 0, sizeof (snippet_data));
      snippet_data.snippets = vertex_snippets;
      snippet_data.hook = COGL_SNIPPET_HOOK_VERTEX_TRANSFORM;
      snippet_data.chain_function = "cogl_real_vertex_transform";
      snippet_data.final_name = "cogl_vertex_transform";
      snippet_data.function_prefix = "cogl_vertex_transform";
      snippet_data.source_buf = shader_state->header;
      _cogl_pipeline_snippet_generate_code (&snippet_data);

      /* Add all of the hooks for vertex processing */
      memset (&snippet_data, 0, sizeof (snippet_data));
      snippet_data.snippets = vertex_snippets;
      snippet_data.hook = COGL_SNIPPET_HOOK_VERTEX;
      snippet_data.chain_function = "cogl_generated_source";
      snippet_data.final_name = "cogl_vertex_hook";
      snippet_data.function_prefix = "cogl_vertex_hook";
      snippet_data.source_buf = shader_state->source;
      _cogl_pipeline_snippet_generate_code (&snippet_data);

      g_string_append (shader_state->source,
                       "void\n"
                       "main ()\n"
                       "{\n"
                       "  cogl_vertex_hook ();\n");

      /* If there are any snippets then we can't rely on the
         projection matrix to flip the rendering for offscreen buffers
         so we'll need to flip it using an extra statement and a
         uniform */
      if (_cogl_pipeline_has_vertex_snippets (pipeline))
        {
          g_string_append (shader_state->header,
                           "uniform vec4 _cogl_flip_vector;\n");
          g_string_append (shader_state->source,
                           "  cogl_position_out *= _cogl_flip_vector;\n");
        }

      g_string_append (shader_state->source,
                       "}\n");

      GE_RET( shader, ctx, glCreateShader (GL_VERTEX_SHADER) );

      lengths[0] = shader_state->header->len;
      source_strings[0] = shader_state->header->str;
      lengths[1] = shader_state->source->len;
      source_strings[1] = shader_state->source->str;

      _cogl_glsl_shader_set_source_with_boilerplate (ctx,
                                                     NULL,
                                                     shader, GL_VERTEX_SHADER,
                                                     2, /* count */
                                                     source_strings, lengths);

      GE( ctx, glCompileShader (shader) );
      GE( ctx, glGetShaderiv (shader, GL_COMPILE_STATUS, &compile_status) );

      if (!compile_status)
        {
          GLint len = 0;
          char *shader_log;

          GE( ctx, glGetShaderiv (shader, GL_INFO_LOG_LENGTH, &len) );
          shader_log = g_alloca (len);
          GE( ctx, glGetShaderInfoLog (shader, len, &len, shader_log) );
          g_warning ("Shader compilation failed:\n%s", shader_log);
        }

      shader_state->header = NULL;
      shader_state->source = NULL;
      shader_state->gl_shader = shader;
    }

  if ((ctx->private_feature_flags &
       COGL_PRIVATE_FEATURE_BUILTIN_POINT_SIZE_UNIFORM) &&
      (pipelines_difference & COGL_PIPELINE_STATE_POINT_SIZE))
    {
      CoglPipeline *authority =
        _cogl_pipeline_get_authority (pipeline, COGL_PIPELINE_STATE_POINT_SIZE);

      GE( ctx, glPointSize (authority->big_state->point_size) );
    }

  return TRUE;
}
static CoglBool
_cogl_pipeline_vertend_glsl_add_layer (CoglPipeline *pipeline,
                                       CoglPipelineLayer *layer,
                                       unsigned long layers_difference,
                                       CoglFramebuffer *framebuffer)
{
  CoglPipelineShaderState *shader_state;
  CoglPipelineSnippetData snippet_data;
  int layer_index = layer->index;

  _COGL_GET_CONTEXT (ctx, FALSE);

  shader_state = get_shader_state (pipeline);

  if (shader_state->source == NULL)
    return TRUE;

  /* Hook to transform the texture coordinates. By default this just
   * directly uses the texture coordinates.
   */

  g_string_append_printf (shader_state->header,
                          "vec4\n"
                          "cogl_real_transform_layer%i (vec4 tex_coord)\n"
                          "{\n"
                          "  return tex_coord;\n"
                          "}\n",
                          layer_index);

  /* Wrap the layer code in any snippets that have been hooked */
  memset (&snippet_data, 0, sizeof (snippet_data));
  snippet_data.snippets = get_layer_vertex_snippets (layer);
  snippet_data.hook = COGL_SNIPPET_HOOK_TEXTURE_COORD_TRANSFORM;
  snippet_data.chain_function = g_strdup_printf ("cogl_real_transform_layer%i",
                                                 layer_index);
  snippet_data.final_name = g_strdup_printf ("cogl_transform_layer%i",
                                             layer_index);
  snippet_data.function_prefix = g_strdup_printf ("cogl_transform_layer%i",
                                                  layer_index);
  snippet_data.return_type = "vec4";
  snippet_data.return_variable = "cogl_tex_coord";
  snippet_data.return_variable_is_argument = TRUE;
  snippet_data.arguments = "cogl_tex_coord";
  snippet_data.argument_declarations = "vec4 cogl_tex_coord";
  snippet_data.source_buf = shader_state->header;

  _cogl_pipeline_snippet_generate_code (&snippet_data);

  g_free ((char *) snippet_data.chain_function);
  g_free ((char *) snippet_data.final_name);
  g_free ((char *) snippet_data.function_prefix);

  g_string_append_printf (shader_state->source,
                          "  cogl_tex_coord%i_out = "
                          "cogl_transform_layer%i (cogl_tex_coord%i_in);\n",
                          layer_index,
                          layer_index,
                          layer_index);

  return TRUE;
}
static void
_cogl_pipeline_vertend_glsl_start (CoglPipeline *pipeline,
                                   int n_layers,
                                   unsigned long pipelines_difference)
{
  CoglPipelineShaderState *shader_state;
  CoglPipeline *template_pipeline = NULL;

  _COGL_GET_CONTEXT (ctx, NO_RETVAL);

  /* Now lookup our glsl backend private state (allocating if
   * necessary) */
  shader_state = get_shader_state (pipeline);

  if (shader_state == NULL)
    {
      CoglPipeline *authority;

      /* Get the authority for anything affecting vertex shader
         state */
      authority = _cogl_pipeline_find_equivalent_parent
        (pipeline,
         COGL_PIPELINE_STATE_AFFECTS_VERTEX_CODEGEN &
         ~COGL_PIPELINE_STATE_LAYERS,
         COGL_PIPELINE_LAYER_STATE_AFFECTS_VERTEX_CODEGEN);

      shader_state = get_shader_state (authority);

      if (shader_state == NULL)
        {
          /* Check if there is already a similar cached pipeline whose
             shader state we can share */
          if (G_LIKELY (!(COGL_DEBUG_ENABLED
                          (COGL_DEBUG_DISABLE_PROGRAM_CACHES))))
            {
              template_pipeline =
                _cogl_pipeline_cache_get_vertex_template (ctx->pipeline_cache,
                                                          authority);

              shader_state = get_shader_state (template_pipeline);
            }

          if (shader_state)
            shader_state->ref_count++;
          else
            shader_state = shader_state_new ();

          set_shader_state (authority, shader_state);

          if (template_pipeline)
            {
              shader_state->ref_count++;
              set_shader_state (template_pipeline, shader_state);
            }
        }

      if (authority != pipeline)
        {
          shader_state->ref_count++;
          set_shader_state (pipeline, shader_state);
        }
    }

  if (shader_state->gl_shader)
    return;

  /* If we make it here then we have a shader_state struct without a gl_shader
     because this is the first time we've encountered it */

  /* 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->codegen_header_buffer, 0);
  g_string_set_size (ctx->codegen_source_buffer, 0);
  shader_state->header = ctx->codegen_header_buffer;
  shader_state->source = ctx->codegen_source_buffer;

  add_layer_declarations (pipeline, shader_state);
  add_global_declarations (pipeline, shader_state);

  g_string_append (shader_state->source,
                   "void\n"
                   "cogl_generated_source ()\n"
                   "{\n");

  if (!(ctx->private_feature_flags &
        COGL_PRIVATE_FEATURE_BUILTIN_POINT_SIZE_UNIFORM))
    /* There is no builtin uniform for the pointsize on GLES2 so we need
       to copy it from the custom uniform in the vertex shader */
    g_string_append (shader_state->source,
                     "  cogl_point_size_out = cogl_point_size_in;\n");
}
static void
_cogl_pipeline_fragend_arbfp_start (CoglPipeline *pipeline,
                                    int n_layers,
                                    unsigned long pipelines_difference)
{
  CoglPipelineShaderState *shader_state;
  CoglPipeline *authority;
  CoglPipelineCacheEntry *cache_entry = NULL;
  CoglProgram *user_program = cogl_pipeline_get_user_program (pipeline);

  _COGL_GET_CONTEXT (ctx, NO_RETVAL);

  /* Now lookup our ARBfp backend private state */
  shader_state = get_shader_state (pipeline);

  /* If we have a valid shader_state then we are all set and don't
   * need to generate a new program. */
  if (shader_state)
    return;

  /* If we don't have an associated arbfp program yet then find the
   * arbfp-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
   * arbfp-authority to maximize the chance that other pipelines can
   * share it.
   */
  authority = _cogl_pipeline_find_equivalent_parent
    (pipeline,
     _cogl_pipeline_get_state_for_fragment_codegen (ctx) &
     ~COGL_PIPELINE_STATE_LAYERS,
     _cogl_pipeline_get_layer_state_for_fragment_codegen (ctx));
  shader_state = get_shader_state (authority);
  if (shader_state)
    {
      /* If we are going to share our program state with an arbfp-authority
       * then add a reference to the program state associated with that
       * arbfp-authority... */
      set_shader_state (pipeline, shader_state);
      return;
    }

  /* If we haven't yet found an existing program then before we resort to
   * generating a new arbfp program we see if we can find a suitable
   * program in the pipeline_cache. */
  if (G_LIKELY (!(COGL_DEBUG_ENABLED (COGL_DEBUG_DISABLE_PROGRAM_CACHES))))
    {
      cache_entry =
        _cogl_pipeline_cache_get_fragment_template (ctx->pipeline_cache,
                                                    authority);

      shader_state = get_shader_state (cache_entry->pipeline);

      if (shader_state)
        shader_state->ref_count++;
    }

  /* If we still haven't got a shader state then we'll have to create
     a new one */
  if (shader_state == NULL)
    {
      shader_state = shader_state_new (n_layers, cache_entry);

      shader_state->user_program = user_program;
      if (user_program == COGL_INVALID_HANDLE)
        {
          /* We reuse a single grow-only GString for code-gen */
          g_string_set_size (ctx->codegen_source_buffer, 0);
          shader_state->source = ctx->codegen_source_buffer;
          g_string_append (shader_state->source,
                           "!!ARBfp1.0\n"
                           "TEMP output;\n"
                           "TEMP tmp0, tmp1, tmp2, tmp3, tmp4;\n"
                           "PARAM half = {.5, .5, .5, .5};\n"
                           "PARAM one = {1, 1, 1, 1};\n"
                           "PARAM two = {2, 2, 2, 2};\n"
                           "PARAM minus_one = {-1, -1, -1, -1};\n");
        }
    }

  set_shader_state (pipeline, shader_state);

  shader_state->ref_count--;

  /* Since we have already resolved the arbfp-authority at this point
   * we might as well also associate any program we find from the cache
   * with the authority too... */
  if (authority != pipeline)
    set_shader_state (authority, shader_state);

  /* If we found a template then we'll attach it to that too so that
     next time a similar pipeline is used it can use the same state */
  if (cache_entry)
    set_shader_state (cache_entry->pipeline, shader_state);
}