/* * Clutter. * * An OpenGL based 'interactive canvas' library. * * Authored By: Matthew Allum * Øyvind Kolås * Emmanuele Bassi * * Copyright (C) 2007, 2008 OpenedHand * Copyright (C) 2009 Intel Corp * * 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 . * * */ /** * SECTION:clutter-shader * @short_description: Programmable pipeline abstraction * * #ClutterShader is an object providing an abstraction over the * OpenGL programmable pipeline. By using #ClutterShaders is * possible to override the drawing pipeline by using small programs * also known as "shaders". * * #ClutterShader is available since Clutter 0.6 */ #ifdef HAVE_CONFIG_H #include "config.h" #endif #include #include #ifdef HAVE_UNISTD_H #include #endif #include #include #include "cogl/cogl.h" #include "clutter-debug.h" #include "clutter-private.h" #include "clutter-shader.h" /* global list of shaders */ static GList *clutter_shaders_list = NULL; #define CLUTTER_SHADER_GET_PRIVATE(obj) (G_TYPE_INSTANCE_GET_PRIVATE ((obj), CLUTTER_TYPE_SHADER, ClutterShaderPrivate)) struct _ClutterShaderPrivate { guint compiled : 1; /* Shader is bound to the GL context */ guint is_enabled : 1; guint vertex_is_glsl : 1; guint fragment_is_glsl : 1; gchar *vertex_source; /* GLSL source for vertex shader */ gchar *fragment_source; /* GLSL source for fragment shader */ CoglHandle program; CoglHandle vertex_shader; CoglHandle fragment_shader; }; enum { PROP_0, PROP_VERTEX_SOURCE, PROP_FRAGMENT_SOURCE, PROP_COMPILED, PROP_ENABLED, PROP_LAST }; static GParamSpec *obj_props[PROP_LAST]; G_DEFINE_TYPE (ClutterShader, clutter_shader, G_TYPE_OBJECT); static void clutter_shader_finalize (GObject *object) { ClutterShader *shader; ClutterShaderPrivate *priv; shader = CLUTTER_SHADER (object); priv = shader->priv; clutter_shader_release (shader); clutter_shaders_list = g_list_remove (clutter_shaders_list, object); g_free (priv->fragment_source); g_free (priv->vertex_source); G_OBJECT_CLASS (clutter_shader_parent_class)->finalize (object); } static void clutter_shader_set_property (GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec) { ClutterShader *shader = CLUTTER_SHADER(object); switch (prop_id) { case PROP_VERTEX_SOURCE: clutter_shader_set_vertex_source (shader, g_value_get_string (value), -1); break; case PROP_FRAGMENT_SOURCE: clutter_shader_set_fragment_source (shader, g_value_get_string (value), -1); break; case PROP_ENABLED: clutter_shader_set_is_enabled (shader, g_value_get_boolean (value)); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); break; } } static void clutter_shader_get_property (GObject *object, guint prop_id, GValue *value, GParamSpec *pspec) { ClutterShader *shader; ClutterShaderPrivate *priv; shader = CLUTTER_SHADER(object); priv = shader->priv; switch (prop_id) { case PROP_VERTEX_SOURCE: g_value_set_string (value, priv->vertex_source); break; case PROP_FRAGMENT_SOURCE: g_value_set_string (value, priv->fragment_source); break; case PROP_COMPILED: g_value_set_boolean (value, priv->compiled); break; case PROP_ENABLED: g_value_set_boolean (value, priv->is_enabled); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); break; } } static GObject * clutter_shader_constructor (GType type, guint n_params, GObjectConstructParam *params) { GObjectClass *parent_class; GObject *object; parent_class = G_OBJECT_CLASS (clutter_shader_parent_class); object = parent_class->constructor (type, n_params, params); /* add this instance to the global list of shaders */ clutter_shaders_list = g_list_prepend (clutter_shaders_list, object); return object; } static void clutter_shader_class_init (ClutterShaderClass *klass) { GObjectClass *object_class = G_OBJECT_CLASS (klass); GParamSpec *pspec = NULL; object_class->finalize = clutter_shader_finalize; object_class->set_property = clutter_shader_set_property; object_class->get_property = clutter_shader_get_property; object_class->constructor = clutter_shader_constructor; g_type_class_add_private (klass, sizeof (ClutterShaderPrivate)); /** * ClutterShader:vertex-source: * * GLSL source code for the vertex shader part of the shader * program, if any * * Since: 0.6 */ pspec = g_param_spec_string ("vertex-source", P_("Vertex Source"), P_("Source of vertex shader"), NULL, CLUTTER_PARAM_READWRITE); obj_props[PROP_VERTEX_SOURCE] = pspec; g_object_class_install_property (object_class, PROP_VERTEX_SOURCE, pspec); /** * ClutterShader:fragment-source: * * GLSL source code for the fragment shader part of the shader program. * * Since: 0.6 */ pspec = g_param_spec_string ("fragment-source", P_("Fragment Source"), P_("Source of fragment shader"), NULL, CLUTTER_PARAM_READWRITE); obj_props[PROP_FRAGMENT_SOURCE] = pspec; g_object_class_install_property (object_class, PROP_FRAGMENT_SOURCE, pspec); /** * ClutterShader:compiled: * * Whether the shader is compiled and linked, ready for use * in the GL context. * * Since: 0.8 */ pspec = g_param_spec_boolean ("compiled", P_("Compiled"), P_("Whether the shader is compiled and linked"), FALSE, CLUTTER_PARAM_READABLE); obj_props[PROP_COMPILED] = pspec; g_object_class_install_property (object_class, PROP_COMPILED, pspec); /** * ClutterShader:enabled: * * Whether the shader is currently used in the GL rendering pipeline. * * Since: 0.6 */ pspec = g_param_spec_boolean ("enabled", P_("Enabled"), P_("Whether the shader is enabled"), FALSE, CLUTTER_PARAM_READWRITE); obj_props[PROP_ENABLED] = pspec; g_object_class_install_property (object_class, PROP_ENABLED, pspec); } static void clutter_shader_init (ClutterShader *self) { ClutterShaderPrivate *priv; priv = self->priv = CLUTTER_SHADER_GET_PRIVATE (self); priv->compiled = FALSE; priv->vertex_source = NULL; priv->fragment_source = NULL; priv->program = COGL_INVALID_HANDLE; priv->vertex_shader = COGL_INVALID_HANDLE; priv->fragment_shader = COGL_INVALID_HANDLE; } /** * clutter_shader_new: * * Create a new #ClutterShader instance. * * Return value: a new #ClutterShader. * * Since: 0.6 */ ClutterShader * clutter_shader_new (void) { return g_object_new (CLUTTER_TYPE_SHADER, NULL); } static inline void clutter_shader_set_source (ClutterShader *shader, ClutterShaderType shader_type, const gchar *data, gssize length) { ClutterShaderPrivate *priv = shader->priv; gboolean is_glsl = FALSE; if (length < 0) length = strlen (data); g_object_freeze_notify (G_OBJECT (shader)); /* release shader if bound when changing the source, the shader will * automatically be rebound on the next use. */ if (clutter_shader_is_compiled (shader)) clutter_shader_release (shader); is_glsl = !g_str_has_prefix (data, "!!ARBfp"); CLUTTER_NOTE (SHADER, "setting %s shader (GLSL:%s, len:%" G_GSSIZE_FORMAT ")", shader_type == CLUTTER_VERTEX_SHADER ? "vertex" : "fragment", is_glsl ? "yes" : "no", length); switch (shader_type) { case CLUTTER_FRAGMENT_SHADER: g_free (priv->fragment_source); priv->fragment_source = g_strndup (data, length); priv->fragment_is_glsl = is_glsl; _clutter_notify_by_pspec (G_OBJECT (shader), obj_props[PROP_FRAGMENT_SOURCE]); break; case CLUTTER_VERTEX_SHADER: g_free (priv->vertex_source); priv->vertex_source = g_strndup (data, length); priv->vertex_is_glsl = is_glsl; _clutter_notify_by_pspec (G_OBJECT (shader), obj_props[PROP_VERTEX_SOURCE]); break; } g_object_thaw_notify (G_OBJECT (shader)); } /** * clutter_shader_set_fragment_source: * @shader: a #ClutterShader * @data: GLSL source code. * @length: length of source buffer (currently ignored) * * Sets the GLSL source code to be used by a #ClutterShader for the fragment * program. * * Since: 0.6 */ void clutter_shader_set_fragment_source (ClutterShader *shader, const gchar *data, gssize length) { g_return_if_fail (CLUTTER_IS_SHADER (shader)); g_return_if_fail (data != NULL); clutter_shader_set_source (shader, CLUTTER_FRAGMENT_SHADER, data, length); } /** * clutter_shader_set_vertex_source: * @shader: a #ClutterShader * @data: GLSL source code. * @length: length of source buffer (currently ignored) * * Sets the GLSL source code to be used by a #ClutterShader for the vertex * program. * * Since: 0.6 */ void clutter_shader_set_vertex_source (ClutterShader *shader, const gchar *data, gssize length) { g_return_if_fail (CLUTTER_IS_SHADER (shader)); g_return_if_fail (data != NULL); clutter_shader_set_source (shader, CLUTTER_VERTEX_SHADER, data, length); } static const gchar * clutter_shader_get_source (ClutterShader *shader, ClutterShaderType shader_type) { switch (shader_type) { case CLUTTER_FRAGMENT_SHADER: return shader->priv->fragment_source; case CLUTTER_VERTEX_SHADER: return shader->priv->vertex_source; } return NULL; } static CoglHandle clutter_shader_get_cogl_shader (ClutterShader *shader, ClutterShaderType shader_type) { switch (shader_type) { case CLUTTER_FRAGMENT_SHADER: return shader->priv->fragment_shader; case CLUTTER_VERTEX_SHADER: return shader->priv->vertex_shader; } return COGL_INVALID_HANDLE; } static gboolean clutter_shader_glsl_bind (ClutterShader *self, ClutterShaderType shader_type, GError **error) { ClutterShaderPrivate *priv = self->priv; CoglHandle shader = COGL_INVALID_HANDLE; switch (shader_type) { case CLUTTER_VERTEX_SHADER: shader = cogl_create_shader (COGL_SHADER_TYPE_VERTEX); cogl_shader_source (shader, priv->vertex_source); priv->vertex_shader = shader; break; case CLUTTER_FRAGMENT_SHADER: shader = cogl_create_shader (COGL_SHADER_TYPE_FRAGMENT); cogl_shader_source (shader, priv->fragment_source); priv->fragment_shader = shader; break; } g_assert (shader != COGL_INVALID_HANDLE); cogl_shader_compile (shader); if (!cogl_shader_is_compiled (shader)) { gchar *log_buf; log_buf = cogl_shader_get_info_log (shader); /* translators: the first %s is the type of the shader, either * Vertex shader or Fragment shader; the second %s is the actual * error as reported by COGL */ g_set_error (error, CLUTTER_SHADER_ERROR, CLUTTER_SHADER_ERROR_COMPILE, _("%s compilation failed: %s"), shader_type == CLUTTER_VERTEX_SHADER ? _("Vertex shader") : _("Fragment shader"), log_buf); g_free (log_buf); return FALSE; } cogl_program_attach_shader (priv->program, shader); return TRUE; } static gboolean bind_glsl_shader (ClutterShader *self, GError **error) { ClutterShaderPrivate *priv = self->priv; GError *bind_error = NULL; gboolean res; priv->program = cogl_create_program (); if (priv->vertex_is_glsl && priv->vertex_source != COGL_INVALID_HANDLE) { res = clutter_shader_glsl_bind (self, CLUTTER_VERTEX_SHADER, &bind_error); if (!res) { g_propagate_error (error, bind_error); return FALSE; } } if (priv->fragment_is_glsl && priv->fragment_source != COGL_INVALID_HANDLE) { res = clutter_shader_glsl_bind (self, CLUTTER_FRAGMENT_SHADER, &bind_error); if (!res) { g_propagate_error (error, bind_error); return FALSE; } } cogl_program_link (priv->program); return TRUE; } /** * clutter_shader_compile: * @shader: a #ClutterShader * @error: return location for a #GError, or %NULL * * Compiles and links GLSL sources set for vertex and fragment shaders for * a #ClutterShader. If the compilation fails and a #GError return location is * provided the error will contain the errors from the compiler, if any. * * Return value: returns TRUE if the shader was succesfully compiled. * * Since: 0.8 */ gboolean clutter_shader_compile (ClutterShader *shader, GError **error) { ClutterShaderPrivate *priv; g_return_val_if_fail (CLUTTER_IS_SHADER (shader), FALSE); priv = shader->priv; if (priv->compiled) return priv->compiled; if ((priv->vertex_source != COGL_INVALID_HANDLE && !priv->vertex_is_glsl) || (priv->fragment_source != COGL_INVALID_HANDLE && !priv->fragment_is_glsl)) { /* XXX: Could remove this check, since we only advertise support for GLSL * shaders anyways. */ g_set_error (error, CLUTTER_SHADER_ERROR, CLUTTER_SHADER_ERROR_NO_ASM, "ASM shaders not supported"); priv->compiled = FALSE; return priv->compiled; } if (!clutter_feature_available (CLUTTER_FEATURE_SHADERS_GLSL)) { g_set_error (error, CLUTTER_SHADER_ERROR, CLUTTER_SHADER_ERROR_NO_GLSL, "GLSL shaders not supported"); priv->compiled = FALSE; return priv->compiled; } priv->compiled = bind_glsl_shader (shader, error); _clutter_notify_by_pspec (G_OBJECT (shader), obj_props[PROP_COMPILED]); return priv->compiled; } /** * clutter_shader_release: * @shader: a #ClutterShader * * Frees up any GL context resources held by the shader. * * Since: 0.6 */ void clutter_shader_release (ClutterShader *shader) { ClutterShaderPrivate *priv; g_return_if_fail (CLUTTER_IS_SHADER (shader)); priv = shader->priv; if (!priv->compiled) return; g_assert (priv->program != COGL_INVALID_HANDLE); if (priv->vertex_is_glsl && priv->vertex_shader != COGL_INVALID_HANDLE) cogl_handle_unref (priv->vertex_shader); if (priv->fragment_is_glsl && priv->fragment_shader != COGL_INVALID_HANDLE) cogl_handle_unref (priv->fragment_shader); if (priv->program != COGL_INVALID_HANDLE) cogl_handle_unref (priv->program); priv->vertex_shader = COGL_INVALID_HANDLE; priv->fragment_shader = COGL_INVALID_HANDLE; priv->program = COGL_INVALID_HANDLE; priv->compiled = FALSE; _clutter_notify_by_pspec (G_OBJECT (shader), obj_props[PROP_COMPILED]); } /** * clutter_shader_is_compiled: * @shader: a #ClutterShader * * Checks whether @shader is is currently compiled, linked and bound * to the GL context. * * Return value: %TRUE if the shader is compiled, linked and ready for use. * * Since: 0.8 */ gboolean clutter_shader_is_compiled (ClutterShader *shader) { g_return_val_if_fail (CLUTTER_IS_SHADER (shader), FALSE); return shader->priv->compiled; } /** * clutter_shader_set_is_enabled: * @shader: a #ClutterShader * @enabled: The new state of the shader. * * Enables a shader. This function will attempt to compile and link * the shader, if it isn't already. * * When @enabled is %FALSE the default state of the GL pipeline will be * used instead. * * Since: 0.6 */ void clutter_shader_set_is_enabled (ClutterShader *shader, gboolean enabled) { ClutterShaderPrivate *priv; g_return_if_fail (CLUTTER_IS_SHADER (shader)); priv = shader->priv; if (priv->is_enabled != enabled) { GError *error = NULL; gboolean res; res = clutter_shader_compile (shader, &error); if (!res) { g_warning ("Unable to bind the shader: %s", error ? error->message : "unknown error"); if (error) g_error_free (error); return; } priv->is_enabled = enabled; if (priv->is_enabled) cogl_program_use (priv->program); else cogl_program_use (COGL_INVALID_HANDLE); _clutter_notify_by_pspec (G_OBJECT (shader), obj_props[PROP_ENABLED]); } } /** * clutter_shader_get_is_enabled: * @shader: a #ClutterShader * * Checks whether @shader is enabled. * * Return value: %TRUE if the shader is enabled. * * Since: 0.6 */ gboolean clutter_shader_get_is_enabled (ClutterShader *shader) { g_return_val_if_fail (CLUTTER_IS_SHADER (shader), FALSE); return shader->priv->is_enabled; } /** * clutter_shader_set_uniform: * @shader: a #ClutterShader. * @name: name of uniform in GLSL shader program to set. * @value: a #ClutterShaderFloat, #ClutterShaderInt or #ClutterShaderMatrix * #GValue. * * Sets a user configurable variable in the GLSL shader programs attached to * a #ClutterShader. * * Since: 1.0 */ void clutter_shader_set_uniform (ClutterShader *shader, const gchar *name, const GValue *value) { ClutterShaderPrivate *priv; GLint location = 0; gsize size; g_return_if_fail (CLUTTER_IS_SHADER (shader)); g_return_if_fail (name != NULL); g_return_if_fail (value != NULL); g_return_if_fail (CLUTTER_VALUE_HOLDS_SHADER_FLOAT (value) || CLUTTER_VALUE_HOLDS_SHADER_INT (value) || CLUTTER_VALUE_HOLDS_SHADER_MATRIX (value) || G_VALUE_HOLDS_FLOAT (value) || G_VALUE_HOLDS_INT (value)); priv = shader->priv; g_return_if_fail (priv->program != COGL_INVALID_HANDLE); location = cogl_program_get_uniform_location (priv->program, name); if (CLUTTER_VALUE_HOLDS_SHADER_FLOAT (value)) { const GLfloat *floats; floats = clutter_value_get_shader_float (value, &size); cogl_program_set_uniform_float (priv->program, location, size, 1, floats); } else if (CLUTTER_VALUE_HOLDS_SHADER_INT (value)) { const int *ints; ints = clutter_value_get_shader_int (value, &size); cogl_program_set_uniform_int (priv->program, location, size, 1, ints); } else if (CLUTTER_VALUE_HOLDS_SHADER_MATRIX (value)) { const GLfloat *matrix; matrix = clutter_value_get_shader_matrix (value, &size); cogl_program_set_uniform_matrix (priv->program, location, size, 1, FALSE, matrix); } else if (G_VALUE_HOLDS_FLOAT (value)) { GLfloat float_val = g_value_get_float (value); cogl_program_set_uniform_float (priv->program, location, 1, 1, &float_val); } else if (G_VALUE_HOLDS_INT (value)) { int int_val = g_value_get_int (value); cogl_program_set_uniform_int (priv->program, location, 1, 1, &int_val); } else g_assert_not_reached (); } /* * _clutter_shader_release_all: * * Iterate through all #ClutterShaders and tell them to release GL context * related sources. */ void _clutter_shader_release_all (void) { g_list_foreach (clutter_shaders_list, (GFunc) clutter_shader_release, NULL); } /** * clutter_shader_get_fragment_source: * @shader: a #ClutterShader * * Query the current GLSL fragment source set on @shader. * * Return value: the source of the fragment shader for this * ClutterShader object or %NULL. The returned string is owned by the * shader object and should never be modified or freed * * Since: 0.6 */ G_CONST_RETURN gchar * clutter_shader_get_fragment_source (ClutterShader *shader) { g_return_val_if_fail (CLUTTER_IS_SHADER (shader), NULL); return clutter_shader_get_source (shader, CLUTTER_FRAGMENT_SHADER); } /** * clutter_shader_get_vertex_source: * @shader: a #ClutterShader * * Query the current GLSL vertex source set on @shader. * * Return value: the source of the vertex shader for this * ClutterShader object or %NULL. The returned string is owned by the * shader object and should never be modified or freed * * Since: 0.6 */ G_CONST_RETURN gchar * clutter_shader_get_vertex_source (ClutterShader *shader) { g_return_val_if_fail (CLUTTER_IS_SHADER (shader), NULL); return clutter_shader_get_source (shader, CLUTTER_VERTEX_SHADER); } /** * clutter_shader_get_cogl_program: * @shader: a #ClutterShader * * Retrieves the underlying #CoglHandle for the shader program. * * Return value: A #CoglHandle for the shader program, or %NULL * * Since: 1.0 */ CoglHandle clutter_shader_get_cogl_program (ClutterShader *shader) { g_return_val_if_fail (CLUTTER_IS_SHADER (shader), NULL); return shader->priv->program; } /** * clutter_shader_get_cogl_fragment_shader: * @shader: a #ClutterShader * * Retrieves the underlying #CoglHandle for the fragment shader. * * Return value: A #CoglHandle for the fragment shader, or %NULL * * Since: 1.0 */ CoglHandle clutter_shader_get_cogl_fragment_shader (ClutterShader *shader) { g_return_val_if_fail (CLUTTER_IS_SHADER (shader), NULL); return clutter_shader_get_cogl_shader (shader, CLUTTER_FRAGMENT_SHADER); } /** * clutter_shader_get_cogl_vertex_shader: * @shader: a #ClutterShader * * Retrieves the underlying #CoglHandle for the vertex shader. * * Return value: A #CoglHandle for the vertex shader, or %NULL * * Since: 1.0 */ CoglHandle clutter_shader_get_cogl_vertex_shader (ClutterShader *shader) { g_return_val_if_fail (CLUTTER_IS_SHADER (shader), NULL); return clutter_shader_get_cogl_shader (shader, CLUTTER_VERTEX_SHADER); } GQuark clutter_shader_error_quark (void) { return g_quark_from_static_string ("clutter-shader-error"); }