/* * 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 . * * Author: * Emmanuele Bassi */ /** * SECTION:clutter-shader-effect * @short_description: Base class for shader effects * @See_Also: #ClutterEffect, #ClutterOffscreenEffect * * #ClutterShaderEffect is a class that implements all the plumbing for * creating #ClutterEffects using GLSL shaders. * * #ClutterShaderEffect creates an offscreen buffer and then applies the * GLSL shader (after checking whether the compilation and linking were * successfull) to the buffer before painting it on screen. * * #ClutterShaderEffect is available since Clutter 1.4 * * ## Implementing a ClutterShaderEffect * * Creating a sub-class of #ClutterShaderEffect requires the * overriding of the #ClutterOffscreenEffectClass.paint_target() virtual * function from the #ClutterOffscreenEffect class. It is also convenient * to implement the #ClutterShaderEffectClass.get_static_shader_source() * virtual function in case you are planning to create more than one * instance of the effect. * * The #ClutterShaderEffectClass.get_static_shader_source() * function should return a copy of the shader source to use. This * function is only called once per subclass of #ClutterShaderEffect * regardless of how many instances of the effect are created. The * source for the shader is typically stored in a static const * string which is returned from this function via * g_strdup(). * * The #ClutterOffscreenEffectClass.paint_target() should set the * shader's uniforms if any. This is done by calling * clutter_shader_effect_set_uniform_value() or * clutter_shader_effect_set_uniform(). The sub-class should then * chain up to the #ClutterShaderEffect implementation. * * ## Setting uniforms on a ClutterShaderEffect * * The example below shows a typical implementation of the * #ClutterShaderEffectClass.get_static_shader_source() and * #ClutterOffscreenEffectClass.paint_target() virtual functions * for a #ClutterShaderEffect subclass. * * |[ * static gchar * * my_effect_get_static_shader_source (ClutterShaderEffect *effect) * { * // shader_source is set elsewhere * return g_strdup (shader_source); * } * * static gboolean * my_effect_paint_target (ClutterOffscreenEffect *effect) * { * MyEffect *self = MY_EFFECT (effect); * ClutterShaderEffect *shader = CLUTTER_SHADER_EFFECT (effect); * ClutterEffectClass *parent_class; * gfloat component_r, component_g, component_b; * * // the "tex" uniform is declared in the shader as: * // * // uniform int tex; * // * // and it is passed a constant value of 0 * clutter_shader_effect_set_uniform (shader, "tex", G_TYPE_INT, 1, 0); * * // the "component" uniform is declared in the shader as: * // * // uniform vec3 component; * // * // and it's defined to contain the normalized components * // of a #ClutterColor * 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); * * // chain up to the parent's implementation * parent_class = CLUTTER_OFFSCREEN_EFFECT_CLASS (my_effect_parent_class); * return parent_class->paint_target (effect); * } * ]| */ #include "clutter-build-config.h" #include "cogl/cogl.h" #include "clutter-shader-effect.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; int location; } ShaderUniform; struct _ClutterShaderEffectPrivate { ClutterActor *actor; ClutterShaderType shader_type; CoglHandle program; CoglHandle shader; GHashTable *uniforms; }; typedef struct _ClutterShaderEffectClassPrivate { /* These are the per-class pre-compiled shader and program which is used when the class implements get_static_shader_source without calling set_shader_source. They will be shared by all instances of this class */ CoglHandle program; CoglHandle shader; } ClutterShaderEffectClassPrivate; enum { PROP_0, PROP_SHADER_TYPE, PROP_LAST }; static GParamSpec *obj_props[PROP_LAST]; G_DEFINE_TYPE_WITH_CODE (ClutterShaderEffect, clutter_shader_effect, CLUTTER_TYPE_OFFSCREEN_EFFECT, G_ADD_PRIVATE (ClutterShaderEffect) g_type_add_class_private (g_define_type_id, sizeof (ClutterShaderEffectClassPrivate))) static inline void clutter_shader_effect_clear (ClutterShaderEffect *self, gboolean reset_uniforms) { ClutterShaderEffectPrivate *priv = self->priv; if (priv->shader != COGL_INVALID_HANDLE) { cogl_handle_unref (priv->shader); priv->shader = COGL_INVALID_HANDLE; } if (priv->program != COGL_INVALID_HANDLE) { cogl_handle_unref (priv->program); priv->program = COGL_INVALID_HANDLE; } if (reset_uniforms && priv->uniforms != NULL) { g_hash_table_destroy (priv->uniforms); priv->uniforms = NULL; } priv->actor = NULL; } 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 float *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 int *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 float *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 float 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_DOUBLE (&uniform->value)) { const float float_val = (float) g_value_get_double (&uniform->value); cogl_program_set_uniform_float (priv->program, uniform->location, 1, 1, &float_val); } else if (G_VALUE_HOLDS_INT (&uniform->value)) { const int 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; } 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)); } static CoglHandle clutter_shader_effect_create_shader (ClutterShaderEffect *self) { ClutterShaderEffectPrivate *priv = self->priv; switch (priv->shader_type) { case CLUTTER_FRAGMENT_SHADER: return cogl_create_shader (COGL_SHADER_TYPE_FRAGMENT); break; case CLUTTER_VERTEX_SHADER: return cogl_create_shader (COGL_SHADER_TYPE_VERTEX); break; default: g_assert_not_reached (); return COGL_INVALID_HANDLE; } } static void clutter_shader_effect_try_static_source (ClutterShaderEffect *self) { ClutterShaderEffectPrivate *priv = self->priv; ClutterShaderEffectClass *shader_effect_class = CLUTTER_SHADER_EFFECT_GET_CLASS (self); if (shader_effect_class->get_static_shader_source != NULL) { ClutterShaderEffectClassPrivate *class_priv; class_priv = G_TYPE_CLASS_GET_PRIVATE (shader_effect_class, CLUTTER_TYPE_SHADER_EFFECT, ClutterShaderEffectClassPrivate); if (class_priv->shader == COGL_INVALID_HANDLE) { gchar *source; class_priv->shader = clutter_shader_effect_create_shader (self); source = shader_effect_class->get_static_shader_source (self); cogl_shader_source (class_priv->shader, source); g_free (source); CLUTTER_NOTE (SHADER, "Compiling shader effect"); cogl_shader_compile (class_priv->shader); if (cogl_shader_is_compiled (class_priv->shader)) { class_priv->program = cogl_create_program (); cogl_program_attach_shader (class_priv->program, class_priv->shader); cogl_program_link (class_priv->program); } else { gchar *log_buf = cogl_shader_get_info_log (class_priv->shader); g_warning (G_STRLOC ": Unable to compile the GLSL shader: %s", log_buf); g_free (log_buf); } } priv->shader = cogl_handle_ref (class_priv->shader); if (class_priv->program != COGL_INVALID_HANDLE) priv->program = cogl_handle_ref (class_priv->program); } } static void clutter_shader_effect_paint_target (ClutterOffscreenEffect *effect) { ClutterShaderEffect *self = CLUTTER_SHADER_EFFECT (effect); ClutterShaderEffectPrivate *priv = self->priv; ClutterOffscreenEffectClass *parent; CoglHandle material; /* If the source hasn't been set then we'll try to get it from the static source instead */ if (priv->shader == COGL_INVALID_HANDLE) clutter_shader_effect_try_static_source (self); /* we haven't been prepared or we don't have support for * GLSL shaders in Clutter */ if (priv->program == COGL_INVALID_HANDLE) goto out; 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_pipeline_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; offscreen_class = CLUTTER_OFFSCREEN_EFFECT_CLASS (klass); /** * 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 */ obj_props[PROP_SHADER_TYPE] = 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); gobject_class->set_property = clutter_shader_effect_set_property; gobject_class->finalize = clutter_shader_effect_finalize; g_object_class_install_properties (gobject_class, PROP_LAST, obj_props); 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 = clutter_shader_effect_get_instance_private (effect); effect->priv->shader_type = CLUTTER_FRAGMENT_SHADER; } /** * clutter_shader_effect_new: * @shader_type: the type of the shader, either %CLUTTER_FRAGMENT_SHADER, * or %CLUTTER_VERTEX_SHADER * * Creates a new #ClutterShaderEffect, to be applied to an actor using * clutter_actor_add_effect(). * * The effect will be empty until clutter_shader_effect_set_shader_source() * is called. * * Return value: the newly created #ClutterShaderEffect. * Use g_object_unref() when done. * * Since: 1.8 */ ClutterEffect * clutter_shader_effect_new (ClutterShaderType shader_type) { return g_object_new (CLUTTER_TYPE_SHADER_EFFECT, "shader-type", shader_type, NULL); } /** * 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_new0 (ShaderUniform); retval->name = g_strdup (name); retval->type = G_VALUE_TYPE (value); retval->location = -1; 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->actor != NULL && !CLUTTER_ACTOR_IN_PAINT (priv->actor)) clutter_effect_queue_repaint (CLUTTER_EFFECT (effect)); } /** * 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. It also accepts %G_TYPE_DOUBLE for compatibility with other * languages than C. * * Since: 1.4 */ void clutter_shader_effect_set_uniform_value (ClutterShaderEffect *effect, const gchar *name, const GValue *value) { g_return_if_fail (CLUTTER_IS_SHADER_EFFECT (effect)); g_return_if_fail (name != NULL); g_return_if_fail (value != NULL); 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 = G_VALUE_INIT; 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 * @...: 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->shader != COGL_INVALID_HANDLE) return TRUE; priv->shader = clutter_shader_effect_create_shader (effect); cogl_shader_source (priv->shader, source); CLUTTER_NOTE (SHADER, "Compiling shader effect"); cogl_shader_compile (priv->shader); if (cogl_shader_is_compiled (priv->shader)) { priv->program = cogl_create_program (); cogl_program_attach_shader (priv->program, priv->shader); cogl_program_link (priv->program); } else { gchar *log_buf = cogl_shader_get_info_log (priv->shader); g_warning (G_STRLOC ": Unable to compile the GLSL shader: %s", log_buf); g_free (log_buf); } return TRUE; }