/*
 * Clutter.
 *
 * An OpenGL based 'interactive canvas' library.
 *
 * 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 <http://www.gnu.org/licenses/>.
 *
 * Author:
 *   Emmanuele Bassi <ebassi@linux.intel.com>
 */

/**
 * SECTION:clutter-shader-effect
 * @short_description: Base class for fragment shader effects
 * @See_Also: #ClutterEffect, #ClutterOffscreenEffect
 *
 * #ClutterShaderEffect is an abstract class that implements all the
 * plumbing for creating #ClutterEffect<!-- -->s using GLSL fragment
 * shaders.
 *
 * #ClutterShaderEffect creates an offscreen buffer and then applies the GLSL
 * fragment shader (after checking whether the compilation and linking were
 * successfull) to the buffer before painting it on screen.
 *
 * <refsect2 id="ClutterShaderEffect-implementing">
 *   <title>Implementing a ClutterShaderEffect</title>
 *   <para>Creating a sub-class of #ClutterShaderEffect requires the
 *   overriding of the <function>pre_paint()</function> virtual function
 *   from the #ClutterEffect class.</para>
 *   <para>The <function>pre_paint()</function> should set the shader's
 *   source and eventually set the uniforms. The sub-class should call
 *   clutter_shader_effect_set_shader_source() to set the shader source
 *   code, and  clutter_shader_effect_set_uniform_value() or
 *   clutter_shader_effect_set_uniform() to set the values of the shader
 *   uniforms, if any; the sub-class should then chain up to the
 *   #ClutterShaderEffect implementation.</para>
 *   <example id="ClutterShaderEffect-example-uniforms">
 *     <title>Setting uniforms on a ClutterShaderEffect</title>
 *     <para>The example below shows a typical implementation of the
 *     <function>pre_paint()</function> phase of a #ClutterShaderEffect
 *     sub-class.</para>
 *     <programlisting>
 *  static gboolean
 *  my_effect_pre_paint (ClutterEffect *effect)
 *  {
 *    MyEffect *self = MY_EFFECT (effect);
 *    ClutterShaderEffect *shader = CLUTTER_SHADER_EFFECT (effect);
 *    ClutterEffectClass *parent_class;
 *    gfloat component_r, component_g, component_b;
 *
 *    /&ast; if the effect is not enabled we can bail out now &ast;/
 *    if (!clutter_actor_meta_get_enabled (CLUTTER_ACTOR_META (effect)))
 *      return FALSE;
 *
 *    /&ast; this function is a no-op after the first call &ast;/
 *    clutter_shader_effect_set_shader_source (shader, shader_source);
 *
 *    /&ast; the "tex" uniform is declared in the shader as:
 *     &ast;
 *     &ast;   uniform int tex;
 *     &ast;
 *     &ast; and it is passed a constant value of 0
 *     &ast;/
 *    clutter_shader_effect_set_uniform (shader, "tex", G_TYPE_INT, 1, 0);
 *
 *    /&ast; the "component" uniform is declared in the shader as:
 *     &ast;
 *     &ast;   uniform vec3 component;
 *     &ast;
 *     &ast; and it's defined to contain the normalized components
 *     &ast; of a #ClutterColor
 *     &ast;/
 *    component_r = self->color.red   / 255.0f;
 *    component_g = self->color.green / 255.0f;
 *    component_b = self->color.blue  / 255.0f;
 *    clutter_shader_effect_set_uniform (shader, "component",
 *                                       G_TYPE_FLOAT, 3,
 *                                       component_r,
 *                                       component_g,
 *                                       component_b);
 *
 *    /&ast; chain up to the parent's implementation &ast;/
 *    parent_class = CLUTTER_EFFECT_CLASS (my_effect_parent_class);
 *    return parent_class->pre_paint (effect);
 *  }
 *     </programlisting>
 *   </example>
 * </refsect2>
 *
 * #ClutterShaderEffect is available since Clutter 1.4
 */

#ifdef HAVE_CONFIG_H
#include "config.h"
#endif

#include "clutter-shader-effect.h"

#include "cogl/cogl.h"

#include "clutter-debug.h"
#include "clutter-enum-types.h"
#include "clutter-feature.h"
#include "clutter-private.h"
#include "clutter-shader-types.h"

typedef struct _ShaderUniform
{
  gchar *name;
  GType type;
  GValue value;
  GLint location;
} ShaderUniform;

struct _ClutterShaderEffectPrivate
{
  ClutterActor *actor;

  ClutterShaderType shader_type;

  CoglHandle program;
  CoglHandle shader;

  GHashTable *uniforms;

  guint is_compiled : 1;
  guint source_set  : 1;
};

enum
{
  PROP_0,

  PROP_SHADER_TYPE,

  PROP_LAST
};

static GParamSpec *obj_props[PROP_LAST];

G_DEFINE_ABSTRACT_TYPE (ClutterShaderEffect,
                        clutter_shader_effect,
                        CLUTTER_TYPE_OFFSCREEN_EFFECT);

static inline void
clutter_shader_effect_clear (ClutterShaderEffect *self,
                             gboolean             reset_uniforms)
{
  ClutterShaderEffectPrivate *priv = self->priv;

  if (priv->shader != COGL_INVALID_HANDLE && !priv->is_compiled)
    cogl_handle_unref (priv->shader);

  if (priv->program != COGL_INVALID_HANDLE)
    {
      cogl_handle_unref (priv->program);

      priv->program = COGL_INVALID_HANDLE;
      priv->shader = COGL_INVALID_HANDLE;
    }

  if (reset_uniforms && priv->uniforms != NULL)
    {
      g_hash_table_destroy (priv->uniforms);
      priv->uniforms = NULL;
    }

  priv->actor = NULL;
  priv->is_compiled = FALSE;
  priv->source_set = FALSE;
}

static void
clutter_shader_effect_reset_uniforms (ClutterShaderEffect *effect)
{
  ClutterShaderEffectPrivate *priv = effect->priv;
  GHashTableIter iter;
  gpointer key, value;

  if (priv->uniforms == NULL)
    return;

  key = value = NULL;
  g_hash_table_iter_init (&iter, priv->uniforms);
  while (g_hash_table_iter_next (&iter, &key, &value))
    {
      ShaderUniform *uniform = value;

      uniform->location = -1;
    }
}

static void
clutter_shader_effect_update_uniforms (ClutterShaderEffect *effect)
{
  ClutterShaderEffectPrivate *priv = effect->priv;
  GHashTableIter iter;
  gpointer key, value;
  gsize size;

  if (priv->program == COGL_INVALID_HANDLE)
    return;

  if (priv->uniforms == NULL)
    return;

  key = value = NULL;
  g_hash_table_iter_init (&iter, priv->uniforms);
  while (g_hash_table_iter_next (&iter, &key, &value))
    {
      ShaderUniform *uniform = value;

      if (uniform->location == -1)
        uniform->location = cogl_program_get_uniform_location (priv->program,
                                                               uniform->name);

      if (CLUTTER_VALUE_HOLDS_SHADER_FLOAT (&uniform->value))
        {
          const GLfloat *floats;

          floats = clutter_value_get_shader_float (&uniform->value, &size);
          cogl_program_set_uniform_float (priv->program, uniform->location,
                                          size, 1,
                                          floats);
        }
      else if (CLUTTER_VALUE_HOLDS_SHADER_INT (&uniform->value))
        {
          const GLint *ints;

          ints = clutter_value_get_shader_int (&uniform->value, &size);
          cogl_program_set_uniform_int (priv->program, uniform->location,
                                        size, 1,
                                        ints);
        }
      else if (CLUTTER_VALUE_HOLDS_SHADER_MATRIX (&uniform->value))
        {
          const GLfloat *matrix;

          matrix = clutter_value_get_shader_matrix (&uniform->value, &size);
          cogl_program_set_uniform_matrix (priv->program, uniform->location,
                                           size, 1,
                                           FALSE,
                                           matrix);
        }
      else if (G_VALUE_HOLDS_FLOAT (&uniform->value))
        {
          const GLfloat float_val = g_value_get_float (&uniform->value);

          cogl_program_set_uniform_float (priv->program, uniform->location,
                                          1, 1,
                                          &float_val);
        }
      else if (G_VALUE_HOLDS_INT (&uniform->value))
        {
          const GLint int_val = g_value_get_int (&uniform->value);

          cogl_program_set_uniform_int (priv->program, uniform->location,
                                        1, 1,
                                        &int_val);
        }
      else
        g_warning ("Invalid uniform of type '%s' for name '%s'",
                   g_type_name (G_VALUE_TYPE (&uniform->value)),
                   uniform->name);
    }
}

static void
clutter_shader_effect_set_actor (ClutterActorMeta *meta,
                                 ClutterActor     *actor)
{
  ClutterShaderEffect *self = CLUTTER_SHADER_EFFECT (meta);
  ClutterShaderEffectPrivate *priv = self->priv;
  ClutterActorMetaClass *parent;

  if (!clutter_feature_available (CLUTTER_FEATURE_SHADERS_GLSL))
    {
      /* if we don't have support for GLSL shaders then we
       * forcibly disable the ActorMeta
       */
      g_warning ("Unable to use the ShaderEffect: the graphics hardware "
                 "or the current GL driver does not implement support "
                 "for the GLSL shading language.");
      clutter_actor_meta_set_enabled (meta, FALSE);
      return;
    }

  clutter_shader_effect_clear (self, FALSE);
  clutter_shader_effect_reset_uniforms (self);

  parent = CLUTTER_ACTOR_META_CLASS (clutter_shader_effect_parent_class);
  parent->set_actor (meta, actor);

  /* we keep a back pointer here */
  priv->actor = clutter_actor_meta_get_actor (meta);
  if (priv->actor == NULL)
    return;

  CLUTTER_NOTE (SHADER, "Preparing shader effect of type '%s'",
                G_OBJECT_TYPE_NAME (meta));

  priv->program = cogl_create_program ();

  switch (priv->shader_type)
    {
    case CLUTTER_FRAGMENT_SHADER:
      priv->shader = cogl_create_shader (COGL_SHADER_TYPE_FRAGMENT);
      break;

    case CLUTTER_VERTEX_SHADER:
      priv->shader = cogl_create_shader (COGL_SHADER_TYPE_VERTEX);
      break;

    default:
      priv->shader = COGL_INVALID_HANDLE;
      break;
    }

  g_assert (priv->shader != COGL_INVALID_HANDLE);
}

static void
clutter_shader_effect_paint_target (ClutterOffscreenEffect *effect)
{
  ClutterShaderEffectPrivate *priv = CLUTTER_SHADER_EFFECT (effect)->priv;
  ClutterOffscreenEffectClass *parent;
  CoglHandle material;

  /* we haven't been prepared or we don't have support for
   * GLSL shaders in Clutter
   */
  if (priv->program == COGL_INVALID_HANDLE ||
      priv->shader == COGL_INVALID_HANDLE)
    goto out;

  if (!priv->source_set)
    goto out;

  if (!priv->is_compiled)
    {
      CLUTTER_NOTE (SHADER, "Compiling shader effect");

      cogl_shader_compile (priv->shader);
      if (!cogl_shader_is_compiled (priv->shader))
        {
          gchar *log_buf = cogl_shader_get_info_log (priv->shader);

          g_warning ("Unable to compile the GLSL shader: %s", log_buf);
          g_free (log_buf);

          cogl_handle_unref (priv->shader);
          priv->shader = COGL_INVALID_HANDLE;

          cogl_handle_unref (priv->program);
          priv->shader = COGL_INVALID_HANDLE;

          goto out;
        }

      cogl_program_attach_shader (priv->program, priv->shader);
      cogl_handle_unref (priv->shader);

      cogl_program_link (priv->program);

      priv->is_compiled = TRUE;
    }

  CLUTTER_NOTE (SHADER, "Applying the shader effect of type '%s'",
                G_OBJECT_TYPE_NAME (effect));

  clutter_shader_effect_update_uniforms (CLUTTER_SHADER_EFFECT (effect));

  /* associate the program to the offscreen target material */
  material = clutter_offscreen_effect_get_target (effect);
  cogl_material_set_user_program (material, priv->program);

out:
  /* paint the offscreen buffer */
  parent = CLUTTER_OFFSCREEN_EFFECT_CLASS (clutter_shader_effect_parent_class);
  parent->paint_target (effect);

}

static void
clutter_shader_effect_set_property (GObject      *gobject,
                                    guint         prop_id,
                                    const GValue *value,
                                    GParamSpec   *pspec)
{
  ClutterShaderEffectPrivate *priv = CLUTTER_SHADER_EFFECT (gobject)->priv;

  switch (prop_id)
    {
    case PROP_SHADER_TYPE:
      priv->shader_type = g_value_get_enum (value);
      break;

    default:
      G_OBJECT_WARN_INVALID_PROPERTY_ID (gobject, prop_id, pspec);
      break;
    }
}

static void
clutter_shader_effect_finalize (GObject *gobject)
{
  ClutterShaderEffect *effect = CLUTTER_SHADER_EFFECT (gobject);

  clutter_shader_effect_clear (effect, TRUE);

  G_OBJECT_CLASS (clutter_shader_effect_parent_class)->finalize (gobject);
}

static void
clutter_shader_effect_class_init (ClutterShaderEffectClass *klass)
{
  ClutterActorMetaClass *meta_class = CLUTTER_ACTOR_META_CLASS (klass);
  GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
  ClutterOffscreenEffectClass *offscreen_class;
  GParamSpec *pspec;

  offscreen_class = CLUTTER_OFFSCREEN_EFFECT_CLASS (klass);

  g_type_class_add_private (klass, sizeof (ClutterShaderEffectPrivate));

  gobject_class->set_property = clutter_shader_effect_set_property;
  gobject_class->finalize = clutter_shader_effect_finalize;

  /**
   * ClutterShaderEffect:shader-type:
   *
   * The type of shader that is used by the effect. This property
   * should be set by the constructor of #ClutterShaderEffect
   * sub-classes.
   *
   * Since: 1.4
   */
  pspec = g_param_spec_enum ("shader-type",
                             P_("Shader Type"),
                             P_("The type of shader used"),
                             CLUTTER_TYPE_SHADER_TYPE,
                             CLUTTER_FRAGMENT_SHADER,
                             CLUTTER_PARAM_WRITABLE |
                             G_PARAM_CONSTRUCT_ONLY);
  obj_props[PROP_SHADER_TYPE] = pspec;
  g_object_class_install_property (gobject_class, PROP_SHADER_TYPE, pspec);

  meta_class->set_actor = clutter_shader_effect_set_actor;

  offscreen_class->paint_target = clutter_shader_effect_paint_target;
}

static void
clutter_shader_effect_init (ClutterShaderEffect *effect)
{
  effect->priv = G_TYPE_INSTANCE_GET_PRIVATE (effect,
                                              CLUTTER_TYPE_SHADER_EFFECT,
                                              ClutterShaderEffectPrivate);
}

/**
 * clutter_shader_effect_get_shader:
 * @effect: a #ClutterShaderEffect
 *
 * Retrieves a pointer to the shader's handle
 *
 * Return value: (transfer none): a pointer to the shader's handle,
 *   or %COGL_INVALID_HANDLE
 *
 * Since: 1.4
 */
CoglHandle
clutter_shader_effect_get_shader (ClutterShaderEffect *effect)
{
  g_return_val_if_fail (CLUTTER_IS_SHADER_EFFECT (effect),
                        COGL_INVALID_HANDLE);

  return effect->priv->shader;
}

/**
 * clutter_shader_effect_get_program:
 * @effect: a #ClutterShaderEffect
 *
 * Retrieves a pointer to the program's handle
 *
 * Return value: (transfer none): a pointer to the program's handle,
 *   or %COGL_INVALID_HANDLE
 *
 * Since: 1.4
 */
CoglHandle
clutter_shader_effect_get_program (ClutterShaderEffect *effect)
{
  g_return_val_if_fail (CLUTTER_IS_SHADER_EFFECT (effect),
                        COGL_INVALID_HANDLE);

  return effect->priv->program;
}

static void
shader_uniform_free (gpointer data)
{
  if (data != NULL)
    {
      ShaderUniform *uniform = data;

      g_value_unset (&uniform->value);
      g_free (uniform->name);

      g_slice_free (ShaderUniform, uniform);
    }
}

static ShaderUniform *
shader_uniform_new (const gchar  *name,
                    const GValue *value)
{
  ShaderUniform *retval;

  retval = g_slice_new (ShaderUniform);
  retval->name = g_strdup (name);
  retval->type = G_VALUE_TYPE (value);
  retval->location = -1;
  retval->value = (GValue){0};

  g_value_init (&retval->value, retval->type);
  g_value_copy (value, &retval->value);

  return retval;
}

static void
shader_uniform_update (ShaderUniform *uniform,
                       const GValue  *value)
{
  g_value_unset (&uniform->value);

  g_value_init (&uniform->value, G_VALUE_TYPE (value));
  g_value_copy (value, &uniform->value);
}

static inline void
clutter_shader_effect_add_uniform (ClutterShaderEffect *effect,
                                   const gchar         *name,
                                   const GValue        *value)
{
  ClutterShaderEffectPrivate *priv = effect->priv;
  ShaderUniform *uniform;

  if (priv->uniforms == NULL)
    {
      priv->uniforms = g_hash_table_new_full (g_str_hash, g_str_equal,
                                              NULL,
                                              shader_uniform_free);
    }

  uniform = g_hash_table_lookup (priv->uniforms, name);
  if (uniform == NULL)
    {
      uniform = shader_uniform_new (name, value);
      g_hash_table_insert (priv->uniforms, uniform->name, uniform);
    }
  else
    shader_uniform_update (uniform, value);

  if (priv->is_compiled)
    {
      uniform->location =
        cogl_program_get_uniform_location (priv->program, uniform->name);
    }
}

/**
 * clutter_shader_effect_set_uniform_value:
 * @effect: a #ClutterShaderEffect
 * @name: the name of the uniform to set
 * @value: a #GValue with the value of the uniform to set
 *
 * Sets @value as the payload for the uniform @name inside the shader
 * effect
 *
 * The #GType of the @value must be one of: %G_TYPE_INT, for a single
 * integer value; %G_TYPE_FLOAT, for a single floating point value;
 * %CLUTTER_TYPE_SHADER_INT, for an array of integer values;
 * %CLUTTER_TYPE_SHADER_FLOAT, for an array of floating point values;
 * and %CLUTTER_TYPE_SHADER_MATRIX, for a matrix of floating point
 * values
 *
 * Since: 1.4
 */
void
clutter_shader_effect_set_uniform_value (ClutterShaderEffect *effect,
                                         const gchar         *name,
                                         const GValue        *value)
{
  ClutterShaderEffectPrivate *priv;

  g_return_if_fail (CLUTTER_IS_SHADER_EFFECT (effect));
  g_return_if_fail (name != NULL);
  g_return_if_fail (value != NULL);

  priv = effect->priv;

  clutter_shader_effect_add_uniform (effect, name, value);
}

static void
clutter_shader_effect_set_uniform_valist (ClutterShaderEffect *effect,
                                          const gchar         *name,
                                          GType                value_type,
                                          gsize                n_values,
                                          va_list             *args)
{
  GValue value = { 0, };

  if (value_type == CLUTTER_TYPE_SHADER_INT)
    {
      gint *int_values = va_arg (*args, gint*);

      g_value_init (&value, CLUTTER_TYPE_SHADER_INT);
      clutter_value_set_shader_int (&value, n_values, int_values);

      goto add_uniform;
    }

  if (value_type == CLUTTER_TYPE_SHADER_FLOAT)
    {
      gfloat *float_values = va_arg (*args, gfloat*);

      g_value_init (&value, CLUTTER_TYPE_SHADER_FLOAT);
      clutter_value_set_shader_float (&value, n_values, float_values);

      goto add_uniform;
    }

  if (value_type == CLUTTER_TYPE_SHADER_MATRIX)
    {
      gfloat *float_values = va_arg (*args, gfloat*);

      g_value_init (&value, CLUTTER_TYPE_SHADER_MATRIX);
      clutter_value_set_shader_matrix (&value, n_values, float_values);

      goto add_uniform;
    }

  if (value_type == G_TYPE_INT)
    {
      g_return_if_fail (n_values <= 4);

      /* if we only have one value we can go through the fast path
       * of using G_TYPE_INT, otherwise we create a vector of integers
       * from the passed values
       */
      if (n_values == 1)
        {
          gint int_val = va_arg (*args, gint);

          g_value_init (&value, G_TYPE_INT);
          g_value_set_int (&value, int_val);
        }
      else
        {
          gint *int_values = g_new (gint, n_values);
          gint i;

          for (i = 0; i < n_values; i++)
            int_values[i] = va_arg (*args, gint);

          g_value_init (&value, CLUTTER_TYPE_SHADER_INT);
          clutter_value_set_shader_int (&value, n_values, int_values);

          g_free (int_values);
        }

      goto add_uniform;
    }

  if (value_type == G_TYPE_FLOAT)
    {
      g_return_if_fail (n_values <= 4);

      /* if we only have one value we can go through the fast path
       * of using G_TYPE_FLOAT, otherwise we create a vector of floats
       * from the passed values
       */
      if (n_values == 1)
        {
          gfloat float_val = (gfloat) va_arg (*args, gdouble);

          g_value_init (&value, G_TYPE_FLOAT);
          g_value_set_float (&value, float_val);
        }
      else
        {
          gfloat *float_values = g_new (gfloat, n_values);
          gint i;

          for (i = 0; i < n_values; i++)
            float_values[i] = (gfloat) va_arg (*args, double);

          g_value_init (&value, CLUTTER_TYPE_SHADER_FLOAT);
          clutter_value_set_shader_float (&value, n_values, float_values);

          g_free (float_values);
        }

      goto add_uniform;
    }

  g_warning ("Unrecognized type '%s' (values: %d) for uniform name '%s'",
             g_type_name (value_type),
             (int) n_values,
             name);
  return;

add_uniform:
  clutter_shader_effect_add_uniform (effect, name, &value);
  g_value_unset (&value);
}

/**
 * clutter_shader_effect_set_uniform:
 * @effect: a #ClutterShaderEffect
 * @name: the name of the uniform to set
 * @gtype: the type of the uniform to set
 * @n_values: the number of values
 * @Varargs: a list of values
 *
 * Sets a list of values as the payload for the uniform @name inside
 * the shader effect
 *
 * The @gtype must be one of: %G_TYPE_INT, for 1 or more integer values;
 * %G_TYPE_FLOAT, for 1 or more floating point values;
 * %CLUTTER_TYPE_SHADER_INT, for a pointer to an array of integer values;
 * %CLUTTER_TYPE_SHADER_FLOAT, for a pointer to an array of floating point
 * values; and %CLUTTER_TYPE_SHADER_MATRIX, for a pointer to an array of
 * floating point values mapping a matrix
 *
 * The number of values interepreted is defined by the @n_value
 * argument, and by the @gtype argument. For instance, a uniform named
 * "sampler0" and containing a single integer value is set using:
 *
 * |[
 *   clutter_shader_effect_set_uniform (effect, "sampler0",
 *                                      G_TYPE_INT, 1,
 *                                      0);
 * ]|
 *
 * While a uniform named "components" and containing a 3-elements vector
 * of floating point values (a "vec3") can be set using:
 *
 * |[
 *   gfloat component_r, component_g, component_b;
 *
 *   clutter_shader_effect_set_uniform (effect, "components",
 *                                      G_TYPE_FLOAT, 3,
 *                                      component_r,
 *                                      component_g,
 *                                      component_b);
 * ]|
 *
 * or can be set using:
 *
 * |[
 *   gfloat component_vec[3];
 *
 *   clutter_shader_effect_set_uniform (effect, "components",
 *                                      CLUTTER_TYPE_SHADER_FLOAT, 3,
 *                                      component_vec);
 * ]|
 *
 * Finally, a uniform named "map" and containing a matrix can be set using:
 *
 * |[
 *   clutter_shader_effect_set_uniform (effect, "map",
 *                                      CLUTTER_TYPE_SHADER_MATRIX, 1,
 *                                      cogl_matrix_get_array (&matrix));
 * ]|
 *
 * Since: 1.4
 */
void
clutter_shader_effect_set_uniform (ClutterShaderEffect *effect,
                                   const gchar         *name,
                                   GType                gtype,
                                   gsize                n_values,
                                   ...)
{
  va_list args;

  g_return_if_fail (CLUTTER_IS_SHADER_EFFECT (effect));
  g_return_if_fail (name != NULL);
  g_return_if_fail (gtype != G_TYPE_INVALID);
  g_return_if_fail (n_values > 0);

  va_start (args, n_values);
  clutter_shader_effect_set_uniform_valist (effect, name,
                                            gtype,
                                            n_values,
                                            &args);
  va_end (args);
}

/**
 * clutter_shader_effect_set_shader_source:
 * @effect: a #ClutterShaderEffect
 * @source: the source of a GLSL shader
 *
 * Sets the source of the GLSL shader used by @effect
 *
 * This function should only be called by implementations of
 * the #ClutterShaderEffect class, and not by application code.
 *
 * This function can only be called once; subsequent calls will
 * yield no result.
 *
 * Return value: %TRUE if the source was set
 *
 * Since: 1.4
 */
gboolean
clutter_shader_effect_set_shader_source (ClutterShaderEffect *effect,
                                         const gchar         *source)
{
  ClutterShaderEffectPrivate *priv;

  g_return_val_if_fail (CLUTTER_IS_SHADER_EFFECT (effect), FALSE);
  g_return_val_if_fail (source != NULL && *source != '\0', FALSE);

  priv = effect->priv;

  if (priv->source_set)
    return TRUE;

  if (priv->shader == COGL_INVALID_HANDLE)
    return FALSE;

  cogl_shader_source (priv->shader, source);

  priv->source_set = TRUE;

  return TRUE;
}
