/* * 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 fragment shader effects * @See_Also: #ClutterEffect, #ClutterOffscreenEffect * * #ClutterShaderEffect is an abstract class that implements all the * plumbing for creating #ClutterEffects 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. * * * Implementing a ClutterShaderEffect * Creating a sub-class of #ClutterShaderEffect requires the * overriding of the pre_paint() virtual function * from the #ClutterEffect class. * The pre_paint() 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. * * Setting uniforms on a ClutterShaderEffect * The example below shows a typical implementation of the * pre_paint() phase of a #ClutterShaderEffect * sub-class. * * 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; * * /* if the effect is not enabled we can bail out now */ * if (!clutter_actor_meta_get_enabled (CLUTTER_ACTOR_META (effect))) * return FALSE; * * /* this function is a no-op after the first call */ * clutter_shader_effect_set_shader_source (shader, shader_source); * * /* 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_EFFECT_CLASS (my_effect_parent_class); * return parent_class->pre_paint (effect); * } * * * * * #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)); /** * 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; _clutter_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 = 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; }