2010-04-13 15:40:31 +00:00
|
|
|
/*
|
|
|
|
* 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
|
2010-11-23 16:06:52 +00:00
|
|
|
* @short_description: Base class for shader effects
|
2010-04-13 15:40:31 +00:00
|
|
|
* @See_Also: #ClutterEffect, #ClutterOffscreenEffect
|
|
|
|
*
|
2011-05-25 14:37:45 +00:00
|
|
|
* #ClutterShaderEffect is a class that implements all the plumbing for
|
|
|
|
* creating #ClutterEffect<!-- -->s using GLSL shaders.
|
2010-04-13 15:40:31 +00:00
|
|
|
*
|
2011-05-25 14:37:45 +00:00
|
|
|
* #ClutterShaderEffect creates an offscreen buffer and then applies the
|
|
|
|
* GLSL shader (after checking whether the compilation and linking were
|
2010-04-13 15:40:31 +00:00
|
|
|
* 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
|
2011-09-29 19:05:39 +00:00
|
|
|
* overriding of the <function>paint_target()</function> virtual
|
|
|
|
* function from the #ClutterOffscreenEffect class as well as the
|
|
|
|
* <function>get_static_shader_source()</function> virtual from the
|
|
|
|
* #ClutterShaderEffect class.</para>
|
|
|
|
* <para>The <function>get_static_shader_source()</function>
|
|
|
|
* 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().</para>
|
|
|
|
* <para>The <function>paint_target()</function> 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.</para>
|
2010-04-13 15:40:31 +00:00
|
|
|
* <example id="ClutterShaderEffect-example-uniforms">
|
|
|
|
* <title>Setting uniforms on a ClutterShaderEffect</title>
|
|
|
|
* <para>The example below shows a typical implementation of the
|
2011-09-29 19:05:39 +00:00
|
|
|
* <function>get_static_shader_source()</function> and
|
|
|
|
* <function>paint_target()</function> phases of a
|
|
|
|
* #ClutterShaderEffect sub-class.</para>
|
2010-04-13 15:40:31 +00:00
|
|
|
* <programlisting>
|
2011-09-29 19:05:39 +00:00
|
|
|
* static gchar *
|
|
|
|
* my_effect_get_static_shader_source (ClutterShaderEffect *effect)
|
|
|
|
* {
|
|
|
|
* return g_strdup (shader_source);
|
|
|
|
* }
|
|
|
|
*
|
2010-04-13 15:40:31 +00:00
|
|
|
* static gboolean
|
2011-09-29 19:05:39 +00:00
|
|
|
* my_effect_paint_target (ClutterOffscreenEffect *effect)
|
2010-04-13 15:40:31 +00:00
|
|
|
* {
|
|
|
|
* 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);
|
|
|
|
*
|
2010-05-17 11:55:46 +00:00
|
|
|
* /* chain up to the parent's implementation */
|
2011-09-29 19:05:39 +00:00
|
|
|
* parent_class = CLUTTER_OFFSCREEN_EFFECT_CLASS (my_effect_parent_class);
|
|
|
|
* return parent_class->paint_target (effect);
|
2010-04-13 15:40:31 +00:00
|
|
|
* }
|
|
|
|
* </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"
|
2010-06-11 13:47:48 +00:00
|
|
|
#include "clutter-enum-types.h"
|
2010-04-13 15:40:31 +00:00
|
|
|
#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;
|
|
|
|
|
2010-06-11 13:47:48 +00:00
|
|
|
ClutterShaderType shader_type;
|
|
|
|
|
2010-04-13 15:40:31 +00:00
|
|
|
CoglHandle program;
|
|
|
|
CoglHandle shader;
|
|
|
|
|
|
|
|
GHashTable *uniforms;
|
|
|
|
};
|
|
|
|
|
2011-09-29 19:05:39 +00:00
|
|
|
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;
|
|
|
|
|
2010-06-11 13:47:48 +00:00
|
|
|
enum
|
|
|
|
{
|
|
|
|
PROP_0,
|
|
|
|
|
2010-06-21 09:20:32 +00:00
|
|
|
PROP_SHADER_TYPE,
|
|
|
|
|
|
|
|
PROP_LAST
|
2010-06-11 13:47:48 +00:00
|
|
|
};
|
|
|
|
|
2010-06-21 09:20:32 +00:00
|
|
|
static GParamSpec *obj_props[PROP_LAST];
|
|
|
|
|
2011-09-29 19:05:39 +00:00
|
|
|
G_DEFINE_TYPE_WITH_CODE (ClutterShaderEffect,
|
|
|
|
clutter_shader_effect,
|
|
|
|
CLUTTER_TYPE_OFFSCREEN_EFFECT,
|
|
|
|
g_type_add_class_private (g_define_type_id,
|
|
|
|
sizeof (ClutterShaderEffectClassPrivate)))
|
2010-04-13 15:40:31 +00:00
|
|
|
|
|
|
|
static inline void
|
2010-08-11 12:58:29 +00:00
|
|
|
clutter_shader_effect_clear (ClutterShaderEffect *self,
|
2010-04-13 15:40:31 +00:00
|
|
|
gboolean reset_uniforms)
|
|
|
|
{
|
2010-08-11 12:58:29 +00:00
|
|
|
ClutterShaderEffectPrivate *priv = self->priv;
|
2010-04-13 15:40:31 +00:00
|
|
|
|
2011-09-29 19:05:39 +00:00
|
|
|
if (priv->shader != COGL_INVALID_HANDLE)
|
|
|
|
{
|
|
|
|
cogl_handle_unref (priv->shader);
|
|
|
|
|
|
|
|
priv->shader = COGL_INVALID_HANDLE;
|
|
|
|
}
|
2010-04-13 15:40:31 +00:00
|
|
|
|
|
|
|
if (priv->program != COGL_INVALID_HANDLE)
|
|
|
|
{
|
|
|
|
cogl_handle_unref (priv->program);
|
2010-08-11 12:58:29 +00:00
|
|
|
|
2010-04-13 15:40:31 +00:00
|
|
|
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;
|
|
|
|
|
2010-08-12 16:06:29 +00:00
|
|
|
if (priv->program == COGL_INVALID_HANDLE)
|
2010-04-13 15:40:31 +00:00
|
|
|
return;
|
|
|
|
|
2010-08-12 16:06:29 +00:00
|
|
|
if (priv->uniforms == NULL)
|
|
|
|
return;
|
2010-08-11 12:58:29 +00:00
|
|
|
|
2010-04-13 15:40:31 +00:00
|
|
|
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);
|
2010-08-12 16:06:29 +00:00
|
|
|
cogl_program_set_uniform_float (priv->program, uniform->location,
|
|
|
|
size, 1,
|
|
|
|
floats);
|
2010-04-13 15:40:31 +00:00
|
|
|
}
|
|
|
|
else if (CLUTTER_VALUE_HOLDS_SHADER_INT (&uniform->value))
|
|
|
|
{
|
|
|
|
const GLint *ints;
|
|
|
|
|
|
|
|
ints = clutter_value_get_shader_int (&uniform->value, &size);
|
2010-08-12 16:06:29 +00:00
|
|
|
cogl_program_set_uniform_int (priv->program, uniform->location,
|
|
|
|
size, 1,
|
|
|
|
ints);
|
2010-04-13 15:40:31 +00:00
|
|
|
}
|
|
|
|
else if (CLUTTER_VALUE_HOLDS_SHADER_MATRIX (&uniform->value))
|
|
|
|
{
|
|
|
|
const GLfloat *matrix;
|
|
|
|
|
|
|
|
matrix = clutter_value_get_shader_matrix (&uniform->value, &size);
|
2010-08-12 16:06:29 +00:00
|
|
|
cogl_program_set_uniform_matrix (priv->program, uniform->location,
|
|
|
|
size, 1,
|
|
|
|
FALSE,
|
|
|
|
matrix);
|
2010-04-13 15:40:31 +00:00
|
|
|
}
|
|
|
|
else if (G_VALUE_HOLDS_FLOAT (&uniform->value))
|
|
|
|
{
|
|
|
|
const GLfloat float_val = g_value_get_float (&uniform->value);
|
|
|
|
|
2010-12-07 14:29:05 +00:00
|
|
|
cogl_program_set_uniform_float (priv->program, uniform->location,
|
|
|
|
1, 1,
|
|
|
|
&float_val);
|
|
|
|
}
|
|
|
|
else if (G_VALUE_HOLDS_DOUBLE (&uniform->value))
|
|
|
|
{
|
|
|
|
const GLfloat float_val =
|
|
|
|
(GLfloat) g_value_get_double (&uniform->value);
|
|
|
|
|
2010-08-12 16:06:29 +00:00
|
|
|
cogl_program_set_uniform_float (priv->program, uniform->location,
|
|
|
|
1, 1,
|
|
|
|
&float_val);
|
2010-04-13 15:40:31 +00:00
|
|
|
}
|
|
|
|
else if (G_VALUE_HOLDS_INT (&uniform->value))
|
|
|
|
{
|
|
|
|
const GLint int_val = g_value_get_int (&uniform->value);
|
|
|
|
|
2010-08-12 16:06:29 +00:00
|
|
|
cogl_program_set_uniform_int (priv->program, uniform->location,
|
|
|
|
1, 1,
|
|
|
|
&int_val);
|
2010-04-13 15:40:31 +00:00
|
|
|
}
|
|
|
|
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));
|
|
|
|
}
|
|
|
|
|
2011-09-29 19:05:39 +00:00
|
|
|
static CoglHandle
|
|
|
|
clutter_shader_effect_create_shader (ClutterShaderEffect *self)
|
2010-04-13 15:40:31 +00:00
|
|
|
{
|
2011-09-29 19:05:39 +00:00
|
|
|
ClutterShaderEffectPrivate *priv = self->priv;
|
2010-04-13 15:40:31 +00:00
|
|
|
|
2011-09-29 19:05:39 +00:00
|
|
|
switch (priv->shader_type)
|
|
|
|
{
|
|
|
|
case CLUTTER_FRAGMENT_SHADER:
|
|
|
|
return cogl_create_shader (COGL_SHADER_TYPE_FRAGMENT);
|
|
|
|
break;
|
2010-04-13 15:40:31 +00:00
|
|
|
|
2011-09-29 19:05:39 +00:00
|
|
|
case CLUTTER_VERTEX_SHADER:
|
|
|
|
return cogl_create_shader (COGL_SHADER_TYPE_VERTEX);
|
|
|
|
break;
|
2010-05-17 11:55:46 +00:00
|
|
|
|
2011-09-29 19:05:39 +00:00
|
|
|
default:
|
|
|
|
g_assert_not_reached ();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
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)
|
2010-04-13 15:40:31 +00:00
|
|
|
{
|
2011-09-29 19:05:39 +00:00
|
|
|
ClutterShaderEffectClassPrivate *class_priv;
|
2010-04-13 15:40:31 +00:00
|
|
|
|
2011-09-29 19:05:39 +00:00
|
|
|
class_priv =
|
|
|
|
G_TYPE_CLASS_GET_PRIVATE (shader_effect_class,
|
|
|
|
CLUTTER_TYPE_SHADER_EFFECT,
|
|
|
|
ClutterShaderEffectClassPrivate);
|
|
|
|
|
|
|
|
if (class_priv->shader == COGL_INVALID_HANDLE)
|
2010-04-13 15:40:31 +00:00
|
|
|
{
|
2011-09-29 19:05:39 +00:00
|
|
|
gchar *source;
|
2010-04-13 15:40:31 +00:00
|
|
|
|
2011-09-29 19:05:39 +00:00
|
|
|
class_priv->shader = clutter_shader_effect_create_shader (self);
|
2010-04-13 15:40:31 +00:00
|
|
|
|
2011-09-29 19:05:39 +00:00
|
|
|
source = shader_effect_class->get_static_shader_source (self);
|
2010-04-13 15:40:31 +00:00
|
|
|
|
2011-09-29 19:05:39 +00:00
|
|
|
cogl_shader_source (class_priv->shader, source);
|
2010-04-13 15:40:31 +00:00
|
|
|
|
2011-09-29 19:05:39 +00:00
|
|
|
g_free (source);
|
2010-04-13 15:40:31 +00:00
|
|
|
|
2011-09-29 19:05:39 +00:00
|
|
|
CLUTTER_NOTE (SHADER, "Compiling shader effect");
|
2010-08-11 12:58:29 +00:00
|
|
|
|
2011-09-29 19:05:39 +00:00
|
|
|
cogl_shader_compile (class_priv->shader);
|
2010-04-13 15:40:31 +00:00
|
|
|
|
2011-09-29 19:05:39 +00:00
|
|
|
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 ("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);
|
2010-04-13 15:40:31 +00:00
|
|
|
}
|
2011-09-29 19:05:39 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
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;
|
2010-04-13 15:40:31 +00:00
|
|
|
|
|
|
|
CLUTTER_NOTE (SHADER, "Applying the shader effect of type '%s'",
|
|
|
|
G_OBJECT_TYPE_NAME (effect));
|
|
|
|
|
|
|
|
clutter_shader_effect_update_uniforms (CLUTTER_SHADER_EFFECT (effect));
|
|
|
|
|
2010-08-11 12:58:29 +00:00
|
|
|
/* associate the program to the offscreen target material */
|
|
|
|
material = clutter_offscreen_effect_get_target (effect);
|
|
|
|
cogl_material_set_user_program (material, priv->program);
|
|
|
|
|
|
|
|
out:
|
2010-04-13 15:40:31 +00:00
|
|
|
/* paint the offscreen buffer */
|
|
|
|
parent = CLUTTER_OFFSCREEN_EFFECT_CLASS (clutter_shader_effect_parent_class);
|
|
|
|
parent->paint_target (effect);
|
|
|
|
|
|
|
|
}
|
|
|
|
|
2010-06-11 13:47:48 +00:00
|
|
|
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;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2010-04-13 15:40:31 +00:00
|
|
|
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);
|
|
|
|
|
2010-06-11 13:47:48 +00:00
|
|
|
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
|
|
|
|
*/
|
2010-10-15 14:24:27 +00:00
|
|
|
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;
|
2011-03-03 11:35:46 +00:00
|
|
|
g_object_class_install_properties (gobject_class,
|
|
|
|
PROP_LAST,
|
|
|
|
obj_props);
|
2010-06-11 13:47:48 +00:00
|
|
|
|
2010-04-13 15:40:31 +00:00
|
|
|
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);
|
|
|
|
}
|
|
|
|
|
2011-05-25 14:37:45 +00:00
|
|
|
/**
|
|
|
|
* 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: (transfer full): 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);
|
|
|
|
}
|
|
|
|
|
2010-04-13 15:40:31 +00:00
|
|
|
/**
|
|
|
|
* 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;
|
|
|
|
|
2010-11-29 08:55:13 +00:00
|
|
|
retval = g_slice_new0 (ShaderUniform);
|
2010-04-13 15:40:31 +00:00
|
|
|
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);
|
|
|
|
|
2011-05-25 14:37:45 +00:00
|
|
|
if (priv->actor != NULL && !CLUTTER_ACTOR_IN_PAINT (priv->actor))
|
|
|
|
clutter_actor_queue_redraw (priv->actor);
|
2010-04-13 15:40:31 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* 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
|
2010-12-07 14:29:05 +00:00
|
|
|
* values. It also accepts %G_TYPE_DOUBLE for compatibility with other
|
|
|
|
* languages than C.
|
2010-04-13 15:40:31 +00:00
|
|
|
*
|
|
|
|
* 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 = { 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)
|
|
|
|
{
|
2010-06-16 16:17:27 +00:00
|
|
|
g_return_if_fail (n_values <= 4);
|
|
|
|
|
2010-04-13 15:40:31 +00:00
|
|
|
/* 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)
|
|
|
|
{
|
2010-06-16 16:17:27 +00:00
|
|
|
g_return_if_fail (n_values <= 4);
|
|
|
|
|
2010-04-13 15:40:31 +00:00
|
|
|
/* 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
|
2010-06-03 13:34:41 +00:00
|
|
|
* @n_values: the number of values
|
2011-07-26 12:43:37 +00:00
|
|
|
* @...: a list of values
|
2010-04-13 15:40:31 +00:00
|
|
|
*
|
|
|
|
* 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);
|
|
|
|
}
|
2010-05-17 11:55:46 +00:00
|
|
|
|
|
|
|
/**
|
|
|
|
* 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;
|
|
|
|
|
2011-09-29 19:05:39 +00:00
|
|
|
if (priv->shader != COGL_INVALID_HANDLE)
|
2010-05-17 11:55:46 +00:00
|
|
|
return TRUE;
|
|
|
|
|
2011-09-29 19:05:39 +00:00
|
|
|
priv->shader = clutter_shader_effect_create_shader (effect);
|
2010-11-27 16:26:20 +00:00
|
|
|
|
2011-09-29 19:05:39 +00:00
|
|
|
cogl_shader_source (priv->shader, source);
|
2010-11-29 10:59:16 +00:00
|
|
|
|
2011-09-29 19:05:39 +00:00
|
|
|
CLUTTER_NOTE (SHADER, "Compiling shader effect");
|
2010-11-29 10:59:16 +00:00
|
|
|
|
2011-09-29 19:05:39 +00:00
|
|
|
cogl_shader_compile (priv->shader);
|
2010-11-27 16:26:20 +00:00
|
|
|
|
2011-09-29 19:05:39 +00:00
|
|
|
if (cogl_shader_is_compiled (priv->shader))
|
|
|
|
{
|
|
|
|
priv->program = cogl_create_program ();
|
2010-05-17 11:55:46 +00:00
|
|
|
|
2011-09-29 19:05:39 +00:00
|
|
|
cogl_program_attach_shader (priv->program, priv->shader);
|
|
|
|
|
|
|
|
cogl_program_link (priv->program);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
gchar *log_buf = cogl_shader_get_info_log (priv->shader);
|
2010-05-17 11:55:46 +00:00
|
|
|
|
2011-09-29 19:05:39 +00:00
|
|
|
g_warning ("Unable to compile the GLSL shader: %s", log_buf);
|
|
|
|
g_free (log_buf);
|
|
|
|
}
|
2010-05-17 11:55:46 +00:00
|
|
|
|
|
|
|
return TRUE;
|
|
|
|
}
|