From e92b864dff1c40a0ec51f1dfab674bef85b35fbc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=98yvind=20Kol=C3=A5s?= Date: Mon, 3 Dec 2007 16:29:18 +0000 Subject: [PATCH] Support for shaders in clutter. At the moment limited to drivers providing GLSL support. * clutter/cogl/cogl.h: added rather direct binding of needed for glsl shaders. * clutter/cogl/gl/cogl-defines.h.in: * clutter/cogl/gl/cogl.c: * clutter/cogl/gles/cogl-defines.h: added stubs. * clutter/cogl/gles/cogl.c: added stubs. * clutter/glx/clutter-stage-glx.c: (clutter_stage_glx_realize): unrelated memory management sanity fix. (clutter_stage_glx_unrealize): unbind all shaders on stage unrealize. * clutter/Makefile.am: added clutter-shader.[ch] * clutter/clutter-actor.[ch]: adding shader capability to actors. * clutter/clutter-feature.h: added CLUTTER_FEATURE_SHADERS_GLSL * clutter/clutter-private.h: added stack of shaders to context. * clutter/clutter-shader.[ch]: new. * tests/Makefile.am: added shader test. * tests/test-shader.c: (frame_cb), (main): simple shader test, cycle through the inline shader with right/left mouse buttons. --- ChangeLog | 27 ++ clutter/Makefile.am | 2 + clutter/clutter-actor.c | 186 +++++++++++++ clutter/clutter-actor.h | 12 +- clutter/clutter-feature.h | 4 +- clutter/clutter-private.h | 1 + clutter/clutter-shader.c | 433 ++++++++++++++++++++++++++++++ clutter/clutter-shader.h | 80 ++++++ clutter/cogl/cogl.h | 52 ++++ clutter/cogl/gl/cogl-defines.h.in | 6 + clutter/cogl/gl/cogl.c | 137 ++++++++++ clutter/cogl/gles/cogl-defines.h | 18 ++ clutter/cogl/gles/cogl.c | 68 ++++- clutter/glx/clutter-stage-glx.c | 4 + tests/Makefile.am | 3 +- tests/test-shader.c | 246 +++++++++++++++++ 16 files changed, 1274 insertions(+), 5 deletions(-) create mode 100644 clutter/clutter-shader.c create mode 100644 clutter/clutter-shader.h create mode 100644 tests/test-shader.c diff --git a/ChangeLog b/ChangeLog index 215c9c787..dc54ec2db 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,30 @@ +2007-12-03 Øyvind Kolås + + Support for shaders in clutter. At the moment limited to drivers + providing GLSL support. + + * clutter/cogl/cogl.h: added rather direct binding of needed for glsl + shaders. + * clutter/cogl/gl/cogl-defines.h.in: + * clutter/cogl/gl/cogl.c: + * clutter/cogl/gles/cogl-defines.h: added stubs. + * clutter/cogl/gles/cogl.c: added stubs. + + * clutter/glx/clutter-stage-glx.c: + (clutter_stage_glx_realize): unrelated memory management sanity fix. + (clutter_stage_glx_unrealize): unbind all shaders on stage unrealize. + + * clutter/Makefile.am: added clutter-shader.[ch] + * clutter/clutter-actor.[ch]: adding shader capability to + actors. + * clutter/clutter-feature.h: added CLUTTER_FEATURE_SHADERS_GLSL + * clutter/clutter-private.h: added stack of shaders to context. + * clutter/clutter-shader.[ch]: new. + + * tests/Makefile.am: added shader test. + * tests/test-shader.c: (frame_cb), (main): simple shader test, + cycle through the inline shader with right/left mouse buttons. + 2007-12-03 Øyvind Kolås * clutter/glx/clutter-stage-glx.c: for extra sanity, set diff --git a/clutter/Makefile.am b/clutter/Makefile.am index f260c1d2c..bc1c566ca 100644 --- a/clutter/Makefile.am +++ b/clutter/Makefile.am @@ -69,6 +69,7 @@ source_h = \ $(srcdir)/clutter-score.h \ $(srcdir)/clutter-script.h \ $(srcdir)/clutter-scriptable.h \ + $(srcdir)/clutter-shader.h \ $(srcdir)/clutter-stage.h \ $(srcdir)/clutter-texture.h \ $(srcdir)/clutter-timeline.h \ @@ -153,6 +154,7 @@ source_c = \ clutter-script.c \ clutter-script-parser.c \ clutter-scriptable.c \ + clutter-shader.c \ clutter-stage.c \ clutter-texture.c \ clutter-timeline.c \ diff --git a/clutter/clutter-actor.c b/clutter/clutter-actor.c index 2a467ee5f..8e48d651e 100644 --- a/clutter/clutter-actor.c +++ b/clutter/clutter-actor.c @@ -156,6 +156,8 @@ static guint32 __id = 0; #define CLUTTER_ACTOR_GET_PRIVATE(obj) \ (G_TYPE_INSTANCE_GET_PRIVATE ((obj), CLUTTER_TYPE_ACTOR, ClutterActorPrivate)) +typedef struct _ShaderData ShaderData; + struct _ClutterActorPrivate { ClutterActorBox coords; @@ -170,6 +172,8 @@ struct _ClutterActorPrivate gchar *name; ClutterFixed scale_x, scale_y; guint32 id; /* Unique ID */ + + ShaderData *shader_data; ClutterUnit anchor_x, anchor_y; }; @@ -221,6 +225,11 @@ static void clutter_scriptable_iface_init (ClutterScriptableIface *iface); static void _clutter_actor_apply_modelview_transform (ClutterActor *self); static void _clutter_actor_apply_modelview_transform_recursive (ClutterActor *self); +static void clutter_actor_shader_pre_paint (ClutterActor *actor, + gboolean repeat); +static void clutter_actor_shader_post_paint (ClutterActor *actor); +static void destroy_shader_data (ClutterActor *self); + G_DEFINE_ABSTRACT_TYPE_WITH_CODE (ClutterActor, clutter_actor, G_TYPE_INITIALLY_UNOWNED, @@ -907,8 +916,10 @@ clutter_actor_paint (ClutterActor *self) } else { + clutter_actor_shader_pre_paint (self, FALSE); if (G_LIKELY(klass->paint)) (klass->paint) (self); + clutter_actor_shader_post_paint (self); } if (priv->has_clip) @@ -1175,6 +1186,8 @@ clutter_actor_dispose (GObject *object) g_type_name (G_OBJECT_TYPE (self)), object->ref_count); + destroy_shader_data (self); + if (!(CLUTTER_PRIVATE_FLAGS (self) & CLUTTER_ACTOR_IN_DESTRUCTION)) { CLUTTER_SET_PRIVATE_FLAGS (self, CLUTTER_ACTOR_IN_DESTRUCTION); @@ -1730,6 +1743,7 @@ clutter_actor_init (ClutterActor *self) priv->id = __id++; priv->scale_x = CFX_ONE; priv->scale_y = CFX_ONE; + priv->shader_data = NULL; clutter_actor_request_coords (self, &box); } @@ -4036,3 +4050,175 @@ clutter_actor_box_get_type (void) (GBoxedFreeFunc) clutter_actor_box_free); return our_type; } + +/******************************************************************************/ + +typedef struct _BoxedFloat BoxedFloat; +struct _BoxedFloat +{ + gfloat value; +}; + +struct _ShaderData +{ + ClutterShader *shader; + GHashTable *float1f_hash; /*< list of values that should be set + * on the shader before each paint cycle + */ +}; + +static void +destroy_shader_data (ClutterActor *self) +{ + ClutterActorPrivate *actor_priv = self->priv; + ShaderData *shader_data = actor_priv->shader_data; + + if (!shader_data) + return; + + if (shader_data->shader) + { + g_object_unref (shader_data->shader); + } + shader_data->shader = NULL; + if (shader_data->float1f_hash) + { + g_hash_table_destroy (shader_data->float1f_hash); + shader_data->float1f_hash = NULL; + } + g_free (shader_data); + actor_priv->shader_data = NULL; +} + +gboolean clutter_actor_apply_shader (ClutterActor *self, + ClutterShader *shader) +{ + ClutterActorPrivate *actor_priv; + ShaderData *shader_data; + + g_return_val_if_fail (CLUTTER_IS_ACTOR (self), FALSE); + + actor_priv = self->priv; + shader_data = actor_priv->shader_data; + + if (shader_data == NULL) + { + actor_priv->shader_data = shader_data = g_new0 (ShaderData, 1); + shader_data->float1f_hash = g_hash_table_new_full ( + g_str_hash, g_str_equal, + g_free, g_free); + } + if (shader_data->shader) + { + g_object_unref (shader_data->shader); + } + if (shader) + { + shader_data->shader = g_object_ref (shader); + } + else + { + shader_data->shader = NULL; + } + return TRUE; +} + +static void +each_param (gpointer key, + gpointer value, + gpointer user_data) +{ + ClutterShader *shader = CLUTTER_SHADER (user_data); + BoxedFloat *box = value; + clutter_shader_set_uniform_1f (shader, key, box->value); +} + +static void +clutter_actor_shader_pre_paint (ClutterActor *actor, + gboolean repeat) +{ + ClutterActorPrivate *actor_priv; + ShaderData *shader_data; + ClutterShader *shader; + ClutterMainContext *context; + + g_return_if_fail (CLUTTER_IS_ACTOR (actor)); + + actor_priv = actor->priv; + shader_data = actor_priv->shader_data; + + if (!shader_data) + return; + + context = clutter_context_get_default (); + shader = shader_data->shader; + + if (shader) + { + clutter_shader_enable (shader); + + g_hash_table_foreach (shader_data->float1f_hash, each_param, shader); + + if (!repeat) + { + context->shaders = g_slist_prepend (context->shaders, actor); + } + } +} + +static void +clutter_actor_shader_post_paint (ClutterActor *actor) +{ + ClutterActorPrivate *actor_priv; + ShaderData *shader_data; + ClutterShader *shader; + ClutterMainContext *context; + + g_return_if_fail (CLUTTER_IS_ACTOR (actor)); + + actor_priv = actor->priv; + shader_data = actor_priv->shader_data; + + if (!shader_data) + return; + + context = clutter_context_get_default (); + shader = shader_data->shader; + + if (shader) + { + clutter_shader_disable (shader); + + context->shaders = g_slist_remove (context->shaders, actor); + if (context->shaders) + { + /* call pre-paint again, this time with the second argument being + * TRUE, indicating that we are reapplying the shader and thus + * should not be prepended to the stack + */ + clutter_actor_shader_pre_paint (context->shaders->data, TRUE); + } + } +} + +void +clutter_actor_set_shader_param (ClutterActor *actor, + const gchar *param, + gfloat value) +{ + ClutterActorPrivate *actor_priv; + ShaderData *shader_data; + BoxedFloat *box; + + g_return_if_fail (CLUTTER_IS_ACTOR (actor)); + + actor_priv = actor->priv; + shader_data = actor_priv->shader_data; + + if (!shader_data) + return; + + box = g_malloc (sizeof (BoxedFloat)); + box->value = value; + g_hash_table_insert (shader_data->float1f_hash, g_strdup (param), box); +} diff --git a/clutter/clutter-actor.h b/clutter/clutter-actor.h index a0b139fdb..96769b04e 100644 --- a/clutter/clutter-actor.h +++ b/clutter/clutter-actor.h @@ -34,6 +34,7 @@ #include #include #include +#include G_BEGIN_DECLS @@ -216,9 +217,10 @@ struct _ClutterActorClass void (* focus_in) (ClutterActor *actor); void (* focus_out) (ClutterActor *actor); + gboolean shadable; /*< private >*/ /* padding for future expansion */ - gpointer _padding_dummy[32]; + gpointer _padding_dummy[31]; }; GType clutter_actor_get_type (void) G_GNUC_CONST; @@ -390,6 +392,14 @@ ClutterActor * clutter_get_actor_by_gid (guint32 id); gboolean clutter_actor_should_pick_paint (ClutterActor *self); +gboolean clutter_actor_apply_shader (ClutterActor *self, + ClutterShader *shader); + +void clutter_actor_set_shader_param (ClutterActor *self, + const gchar *param, + gfloat value); + + void clutter_actor_set_anchor_point (ClutterActor *self, gint anchor_x, gint anchor_y); diff --git a/clutter/clutter-feature.h b/clutter/clutter-feature.h index 9820e518d..d9d225e38 100644 --- a/clutter/clutter-feature.h +++ b/clutter/clutter-feature.h @@ -46,6 +46,7 @@ G_BEGIN_DECLS * @CLUTTER_FEATURE_STAGE_STATIC: Set if stage size if fixed (i.e framebuffer) * @CLUTTER_FEATURE_STAGE_USER_RESIZE: Set if stage is able to be user resized. * @CLUTTER_FEATURE_STAGE_CURSOR: Set if stage has a graphical cursor. + * @CLUTTER_FEATURE_SHADER_GLSL: Set if the backend supports GLSL shaders. * * Runtime flags indicating specific features available via Clutter window * sysytem and graphics backend. @@ -60,7 +61,8 @@ typedef enum CLUTTER_FEATURE_TEXTURE_READ_PIXELS = (1 << 4), CLUTTER_FEATURE_STAGE_STATIC = (1 << 5), CLUTTER_FEATURE_STAGE_USER_RESIZE = (1 << 6), - CLUTTER_FEATURE_STAGE_CURSOR = (1 << 7) + CLUTTER_FEATURE_STAGE_CURSOR = (1 << 7), + CLUTTER_FEATURE_SHADERS_GLSL = (1 << 8) } ClutterFeatureFlags; gboolean clutter_feature_available (ClutterFeatureFlags feature); diff --git a/clutter/clutter-private.h b/clutter/clutter-private.h index 67738fba4..d3ca933ae 100644 --- a/clutter/clutter-private.h +++ b/clutter/clutter-private.h @@ -93,6 +93,7 @@ struct _ClutterMainContext ClutterActor *keyboard_grab_actor; /* The actor having the pointer grab (or NULL if there is no pointer grab) */ + GSList *shaders; /* stack of overridden shaders */ }; #define CLUTTER_CONTEXT() (clutter_context_get_default ()) diff --git a/clutter/clutter-shader.c b/clutter/clutter-shader.c new file mode 100644 index 000000000..70eb81491 --- /dev/null +++ b/clutter/clutter-shader.c @@ -0,0 +1,433 @@ +/* + * Clutter. + * + * An OpenGL based 'interactive canvas' library. + * + * Authored By Matthew Allum + * Øyvind Kolås + * + * Copyright (C) 2007 OpenedHand + * + * 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, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#include "clutter.h" +#include "clutter-private.h" +#include "clutter-shader.h" + +#include +#include +#include +#include +#include + +#define CLUTTER_SHADER_GET_PRIVATE(obj) \ + (G_TYPE_INSTANCE_GET_PRIVATE ((obj), \ + CLUTTER_TYPE_SHADER, ClutterShaderPrivate)) + +static GList *shader_list; + +static void clutter_shader_class_init (ClutterShaderClass *klass); +static void clutter_shader_init (ClutterShader *sp); +static void clutter_shader_finalize (GObject *object); +static GObject *clutter_shader_constructor (GType type, + guint n_params, + GObjectConstructParam *params); +static void clutter_shader_set_property (GObject *object, + guint prop_id, + const GValue *value, + GParamSpec *pspec); +static void clutter_shader_get_property (GObject *object, + guint prop_id, + GValue *value, + GParamSpec *pspec); + +struct _ClutterShaderPrivate +{ + gboolean glsl; /* The shader is a GLSL shader */ + gboolean bound; /* The shader is bound to the GL context */ + + gchar *vertex_shader_source; /* source (or asm) for vertex shader */ + gchar *fragment_shader_source;/* source (or asm) for fragment shader*/ + + GLhandleARB program; + + GLhandleARB vertex_shader; + GLhandleARB fragment_shader; +}; + +enum +{ + PROP_0, + PROP_VERTEX_SOURCE, + PROP_FRAGMENT_SOURCE +}; + +G_DEFINE_TYPE (ClutterShader, clutter_shader, G_TYPE_OBJECT); + +static void +clutter_shader_class_init (ClutterShaderClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + + clutter_shader_parent_class = g_type_class_peek_parent (klass); + 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)); + + g_object_class_install_property (object_class, + PROP_VERTEX_SOURCE, + g_param_spec_string ("vertex-source", + "Vertex Source", + "Source of vertex shader", + NULL, + CLUTTER_PARAM_READWRITE| + G_PARAM_CONSTRUCT_ONLY)); + g_object_class_install_property (object_class, + PROP_FRAGMENT_SOURCE, + g_param_spec_string ("fragment-source", + "Fragment Source", + "Source of fragment shader", + NULL, + CLUTTER_PARAM_READWRITE| + G_PARAM_CONSTRUCT_ONLY)); +} + +static void +clutter_shader_init (ClutterShader *self) +{ + ClutterShaderPrivate *priv; + + priv = self->priv = CLUTTER_SHADER_GET_PRIVATE (self); + + priv->glsl = FALSE; + priv->bound = FALSE; + priv->vertex_shader_source = NULL; + priv->fragment_shader_source = NULL; + priv->program = 0; + priv->vertex_shader = 0; + priv->fragment_shader = 0; +} + +static gboolean bind_glsl_shader (ClutterShader *self) +{ + ClutterShaderPrivate *priv; + priv = self->priv; + + cogl_enable (CGL_FRAGMENT_SHADER); + cogl_enable (CGL_VERTEX_SHADER); + + priv->glsl = TRUE; + priv->program = cogl_create_program (); + + if (priv->vertex_shader_source) + { + priv->vertex_shader = cogl_create_shader (CGL_VERTEX_SHADER); + cogl_shader_source (priv->vertex_shader, priv->vertex_shader_source); + cogl_shader_compile (priv->vertex_shader); + cogl_program_attach_shader (priv->program, priv->vertex_shader); + } + if (priv->fragment_shader_source) + { + GLint compiled = CGL_FALSE; + priv->fragment_shader = cogl_create_shader (CGL_FRAGMENT_SHADER); + cogl_shader_source (priv->fragment_shader, priv->fragment_shader_source); + cogl_shader_compile (priv->fragment_shader); + + cogl_shader_get_parameteriv (priv->fragment_shader, + CGL_OBJECT_COMPILE_STATUS, + &compiled); + if (compiled != CGL_TRUE) + { + GLcharARB *buffer; + gint max_length = 512; + buffer = g_malloc (max_length); + cogl_shader_get_info_log (priv->fragment_shader, max_length, buffer); + g_print ("Shader compilation failed:\n%s", buffer); + g_free (buffer); + g_object_unref (self); + return FALSE; + } + cogl_program_attach_shader (priv->program, priv->fragment_shader); + } + cogl_program_link (priv->program); + return TRUE; +} + +gboolean +clutter_shader_bind (ClutterShader *self) +{ + ClutterShaderPrivate *priv; + + priv = self->priv; + if (priv->bound) + return priv->bound; + + if (priv->glsl) + { + priv->bound = bind_glsl_shader (self); + } + + return priv->bound; +} + +void +clutter_shader_release (ClutterShader *self) +{ + ClutterShaderPrivate *priv; + + priv = self->priv; + if (!priv->bound) + return; + + g_assert (priv->program); + + if (priv->glsl) + { + if (priv->vertex_shader) + cogl_shader_destroy (priv->vertex_shader); + if (priv->fragment_shader) + cogl_shader_destroy (priv->fragment_shader); + if (priv->program) + cogl_program_destroy (priv->program); + priv->vertex_shader = 0; + priv->fragment_shader = 0; + priv->program = 0; + } + priv->bound = FALSE; +} + +static void +clutter_shader_finalize (GObject *object) +{ + ClutterShader *shader; + ClutterShaderPrivate *priv; + + shader = CLUTTER_SHADER (object); + priv = shader->priv; + + clutter_shader_release (shader); + + shader_list = g_list_remove (shader_list, object); + + if (priv->fragment_shader_source) + g_free (priv->fragment_shader_source); + if (priv->vertex_shader_source) + g_free (priv->vertex_shader_source); + + G_OBJECT_CLASS (clutter_shader_parent_class)->finalize (object); +} + + +void +clutter_shader_enable (ClutterShader *self) +{ + ClutterShaderPrivate *priv = self->priv; + + clutter_shader_bind (self); + + cogl_program_use (priv->program); +} + +void +clutter_shader_disable (ClutterShader *self) +{ + cogl_program_use (0); +} + +void +clutter_shader_set_uniform_1f (ClutterShader *self, + const gchar *name, + gfloat value) +{ + ClutterShaderPrivate *priv = self->priv; + GLint location = 0; + GLfloat foo = value; + + location =cogl_program_get_uniform_location (priv->program, name); + cogl_program_uniform_1f (location, foo); +} + +void +clutter_shader_release_all (void) +{ + GList *iter; + for (iter = shader_list; iter; iter = g_list_next (iter)) + { + clutter_shader_release (iter->data); + } +} + +static void +clutter_shader_set_property (GObject *object, + guint prop_id, + const GValue *value, + GParamSpec *pspec) +{ + ClutterShader *shader; + ClutterShaderPrivate *priv; + + shader = CLUTTER_SHADER(object); + priv = shader->priv; + + switch (prop_id) + { + case PROP_VERTEX_SOURCE: + if (priv->vertex_shader_source) + { + g_free (priv->vertex_shader_source); + priv->vertex_shader_source = NULL; + } + priv->vertex_shader_source = g_value_dup_string (value); + break; + case PROP_FRAGMENT_SOURCE: + if (priv->fragment_shader_source) + { + g_free (priv->fragment_shader_source); + priv->fragment_shader_source = NULL; + } + priv->fragment_shader_source = g_value_dup_string (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_shader_source); + break; + case PROP_FRAGMENT_SOURCE: + g_value_set_string (value, priv->fragment_shader_source); + 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) +{ + GObject *object; + ClutterShader *shader; + ClutterShaderPrivate *priv; + + object = G_OBJECT_CLASS (clutter_shader_parent_class)->constructor ( + type, n_params, params); + shader = CLUTTER_SHADER (object); + priv = shader->priv; + + priv->glsl = !((priv->vertex_shader_source && + g_str_has_prefix (priv->vertex_shader_source, "!!ARBvp")) || + (priv->fragment_shader_source && + g_str_has_prefix (priv->fragment_shader_source, "!!ARBfp"))); + if (!priv->glsl) + { + g_warning ("ASM shader support not available"); + g_object_unref (object); + return NULL; + } + if (priv->glsl && !clutter_feature_available (CLUTTER_FEATURE_SHADERS_GLSL)) + { + g_warning ("GLSL shaders not supported\n"); + g_object_unref (object); + return NULL; + } + + shader_list = g_list_prepend (shader_list, object); + return object; +} + +ClutterShader * +clutter_shader_new_from_strings (const gchar *vertex_shader_program, + const gchar *fragment_shader_program) +{ + /* evil hack, since g_object_new would interpret a NULL passed as + * a argument termination + */ + if (vertex_shader_program && + fragment_shader_program) + return g_object_new (CLUTTER_TYPE_SHADER, + "vertex-source", vertex_shader_program, + "fragment-source", fragment_shader_program, + NULL); + else if (fragment_shader_program) + return g_object_new (CLUTTER_TYPE_SHADER, + "fragment-source", fragment_shader_program, + NULL); + else if (vertex_shader_program) + return g_object_new (CLUTTER_TYPE_SHADER, + "vertex-source", vertex_shader_program, + NULL); + else { + g_warning ("neither fragment nor vertex shader provided"); + return NULL; + } +} + +ClutterShader * +clutter_shader_new_from_files (const gchar *vertex_file, + const gchar *fragment_file) +{ + ClutterShader *shader; + gchar *vertex_shader_program = NULL; + gchar *fragment_shader_program = NULL; + + g_assert (vertex_file != NULL || + fragment_file != NULL); + + if (vertex_file) + { + g_file_get_contents (vertex_file, &vertex_shader_program, + NULL, NULL); + } + if (fragment_file) + { + g_file_get_contents (fragment_file, &fragment_shader_program, + NULL, NULL); + } + shader = clutter_shader_new_from_strings (vertex_shader_program, + fragment_shader_program); + + if (vertex_shader_program) + { + g_free (vertex_shader_program); + } + if (fragment_shader_program) + { + g_free (fragment_shader_program); + } + return shader; +} diff --git a/clutter/clutter-shader.h b/clutter/clutter-shader.h new file mode 100644 index 000000000..2071749f2 --- /dev/null +++ b/clutter/clutter-shader.h @@ -0,0 +1,80 @@ +/* + * Clutter. + * + * An OpenGL based 'interactive canvas' library. + * + * Authored By Matthew Allum + * Øyvind Kolås + * + * Copyright (C) 2007 OpenedHand + * + * 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, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + + +#ifndef CLUTTER_SHADER_H +#define CLUTTER_SHADER_H + +#include +#include +#include + +G_BEGIN_DECLS + +#define CLUTTER_TYPE_SHADER (clutter_shader_get_type ()) +#define CLUTTER_SHADER(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), CLUTTER_TYPE_SHADER, ClutterShader)) +#define CLUTTER_SHADER_CLASS(k) (G_TYPE_CHECK_CLASS_CAST((k), CLUTTER_TYPE_SHADER, ClutterShaderClass)) +#define CLUTTER_IS_SHADER(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), CLUTTER_TYPE_SHADER)) +#define CLUTTER_IS_SHADER_CLASS(k) (G_TYPE_CHECK_CLASS_TYPE ((k), CLUTTER_TYPE_SHADER)) +#define CLUTTER_SHADER_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), CLUTTER_TYPE_SHADER, ClutterShaderClass)) + +typedef struct _ClutterShader ClutterShader; +typedef struct _ClutterShaderPrivate ClutterShaderPrivate; +typedef struct _ClutterShaderClass ClutterShaderClass; + +struct _ClutterShader +{ + GObject parent; + ClutterShaderPrivate *priv; +}; + +struct _ClutterShaderClass +{ + GObjectClass parent_class; +}; + +GType clutter_shader_get_type (); + +ClutterShader * clutter_shader_new_from_files (const gchar *vertex_file, + const gchar *fragment_file); +ClutterShader * clutter_shader_new_from_strings (const gchar *vertex_file, + const gchar *fragment_file); +void clutter_shader_enable (ClutterShader *self); +void clutter_shader_disable (ClutterShader *self); + +gboolean clutter_shader_bind (ClutterShader *self); +void clutter_shader_release (ClutterShader *self); +void clutter_shader_set_uniform_1f (ClutterShader *self, + const gchar *name, + gfloat value); +/* should be private and internal */ +void clutter_shader_release_all (void); +gboolean clutter_shader_has_glsl (void); + + +G_END_DECLS + +#endif /* CLUTTER_SHADER_H */ diff --git a/clutter/cogl/cogl.h b/clutter/cogl/cogl.h index 12b23dd53..5ebf991c6 100644 --- a/clutter/cogl/cogl.h +++ b/clutter/cogl/cogl.h @@ -207,6 +207,58 @@ cogl_fog_set (const ClutterColor *fog_color, ClutterFixed z_near, ClutterFixed z_far); + +COGLint +cogl_create_shader (COGLenum shaderType); + +void +cogl_shader_destroy (COGLint handle); + + +void +cogl_shader_source (COGLint shader, + const gchar *source); +void +cogl_shader_compile (COGLint shader_handle); + +void +cogl_shader_get_info_log (COGLint handle, + guint size, + gchar *buffer); + +void +cogl_shader_get_parameteriv (COGLint handle, + COGLenum pname, + COGLint *dest); + + +COGLint +cogl_create_program (void); + +void +cogl_program_destroy (COGLint handle); + +void +cogl_program_attach_shader (COGLint program_handle, + COGLint shader_handle); + +/* 0 to use none */ +void +cogl_program_link (COGLint program_handle); + +void +cogl_program_use (COGLint program_handle); + +COGLint +cogl_program_get_uniform_location (COGLint program_int, + const gchar *uniform_name); + + +void +cogl_program_uniform_1f (COGLint uniform_no, + gfloat value); + + G_END_DECLS #endif /* __COGL_H__ */ diff --git a/clutter/cogl/gl/cogl-defines.h.in b/clutter/cogl/gl/cogl-defines.h.in index a8fbdd3ec..93abe436c 100644 --- a/clutter/cogl/gl/cogl-defines.h.in +++ b/clutter/cogl/gl/cogl-defines.h.in @@ -698,6 +698,12 @@ typedef GLuint COGLuint; #define CGL_UNSIGNED_SHORT_8_8_MESA 0 #endif +#define CGL_FRAGMENT_SHADER GL_FRAGMENT_SHADER_ARB +#define CGL_VERTEX_SHADER GL_VERTEX_SHADER_ARB + +#define CGL_OBJECT_COMPILE_STATUS GL_OBJECT_COMPILE_STATUS_ARB + + G_END_DECLS #endif diff --git a/clutter/cogl/gl/cogl.c b/clutter/cogl/gl/cogl.c index d96aff61f..4a586341d 100644 --- a/clutter/cogl/gl/cogl.c +++ b/clutter/cogl/gl/cogl.c @@ -621,6 +621,12 @@ cogl_get_features () } #endif + if (cogl_check_extension ("GL_ARB_vertex_shader", gl_extensions) && + cogl_check_extension ("GL_ARB_fragment_shader", gl_extensions)) + { + flags |= CLUTTER_FEATURE_SHADERS_GLSL; + } + return flags; } @@ -746,3 +752,134 @@ cogl_fog_set (const ClutterColor *fog_color, glFogf (GL_FOG_START, CLUTTER_FIXED_TO_FLOAT (start)); glFogf (GL_FOG_END, CLUTTER_FIXED_TO_FLOAT (stop)); } + +#ifdef __GNUC__ + +#define PROC(rettype, retval, procname, args...) \ + static rettype (*proc) (args) = NULL; \ + if (proc == NULL) \ + { \ + proc = (void*)cogl_get_proc_address (#procname);\ + if (!proc)\ + {\ + g_warning ("failed to lookup proc: %s", #procname);\ + return retval;\ + }\ + } +#else + +#define PROC(rettype, retval, procname, ...) \ + static rettype (*proc) (__VA_ARGS__) = NULL; \ + if (proc == NULL) \ + { \ + proc = (void*)cogl_get_proc_address (#procname);\ + if (!proc)\ + {\ + g_warning ("failed to lookup proc: %s", #procname);\ + return retval;\ + }\ + } + +#endif + +COGLint +cogl_create_program (void) +{ + PROC (GLhandleARB, 0, glCreateProgramObjectARB, void); + return proc (); +} + +COGLint +cogl_create_shader (COGLenum shaderType) +{ + PROC (GLhandleARB, 0, glCreateShaderObjectARB, GLenum); + return proc (shaderType); +} + +void +cogl_shader_source (COGLint shader, + const gchar *source) +{ + PROC (GLvoid,, glShaderSourceARB, GLhandleARB, GLsizei, const GLcharARB **, const GLint *) + proc (shader, 1, &source, NULL); +} + +void +cogl_shader_compile (COGLint shader_handle) +{ + PROC (GLvoid,, glCompileShaderARB, GLhandleARB); + proc (shader_handle); +} + +void +cogl_program_attach_shader (COGLint program_handle, + COGLint shader_handle) +{ + PROC (GLvoid,, glAttachObjectARB, GLhandleARB, GLhandleARB); + proc (program_handle, shader_handle); +} + +void +cogl_program_link (COGLint program_handle) +{ + PROC (GLvoid,, glLinkProgramARB, GLhandleARB); + proc (program_handle); +} + +void +cogl_program_use (COGLint program_handle) +{ + PROC (GLvoid,, glUseProgramObjectARB, GLhandleARB); + proc (program_handle); +} + +COGLint +cogl_program_get_uniform_location (COGLint program_handle, + const gchar *uniform_name) +{ + PROC (GLint,0, glGetUniformLocationARB, GLhandleARB, const GLcharARB *) + return proc (program_handle, uniform_name); +} + +void +cogl_program_destroy (COGLint handle) +{ + PROC (GLvoid,, glDeleteObjectARB, GLhandleARB); + proc (handle); +} + +void +cogl_shader_destroy (COGLint handle) +{ + PROC (GLvoid,, glDeleteObjectARB, GLhandleARB); + proc (handle); +} + +void +cogl_shader_get_info_log (COGLint handle, + guint size, + gchar *buffer) +{ + gint len; + PROC (GLvoid,, glGetInfoLogARB, GLhandleARB, GLsizei, GLsizei *, GLcharARB *); + proc (handle, size-1, &len, buffer); + buffer[len]='\0'; +} + +void +cogl_shader_get_parameteriv (COGLint handle, + COGLenum pname, + COGLint *dest) +{ + PROC (GLvoid,, glGetObjectParameterivARB, GLhandleARB, GLenum, GLint*) + proc (handle, pname, dest); +} + + +void +cogl_program_uniform_1f (COGLint uniform_no, + gfloat value) +{ + PROC (GLvoid,, glUniform1fARB, GLint, GLfloat); + proc (uniform_no, value); +} diff --git a/clutter/cogl/gles/cogl-defines.h b/clutter/cogl/gles/cogl-defines.h index 41eac1532..c8ad851e5 100644 --- a/clutter/cogl/gles/cogl-defines.h +++ b/clutter/cogl/gles/cogl-defines.h @@ -460,6 +460,24 @@ typedef GLuint COGLuint; #define CGL_UNSIGNED_SHORT_8_8_REV_MESA 0 #define CGL_UNSIGNED_SHORT_8_8_MESA 0 +#ifdef GL_FRAGMENT_SHADER +#define CGL_FRAGMENT_SHADER GL_FRAGMENT_SHADER +#else +#define CGL_FRAGMENT_SHADER 0 +#endif + +#ifdef GL_VERTEX_SHADER +#define CGL_VERTEX_SHADER GL_VERTEX_SHADER +#else +#define CGL_VERTEX_SHADER 0 +#endif + +#ifdef GL_OBJECT_COMPILE_STATUS +#define CGL_OBJECT_COMPILE_STATUS GL_OBJECT_COMPILE_STATUS +#else +#define CGL_OBJECT_COMPILE_STATUS 0 +#endif + G_END_DECLS #endif diff --git a/clutter/cogl/gles/cogl.c b/clutter/cogl/gles/cogl.c index 20021c6da..e10efc52c 100644 --- a/clutter/cogl/gles/cogl.c +++ b/clutter/cogl/gles/cogl.c @@ -214,13 +214,13 @@ cogl_enable (gulong flags) if (flags & CGL_ENABLE_TEXTURE_RECT) { if (!(__enable_flags & CGL_ENABLE_TEXTURE_RECT)) - glEnable (GL_TEXTURE_RECTANGLE_ARB); + glEnable (GL_TEXTURE_RECTANGLE_); __enable_flags |= CGL_ENABLE_TEXTURE_RECT; } else if (__enable_flags & CGL_ENABLE_TEXTURE_RECT) { - glDisable (GL_TEXTURE_RECTANGLE_ARB); + glDisable (GL_TEXTURE_RECTANGLE_); __enable_flags &= ~CGL_ENABLE_TEXTURE_RECT; } #endif @@ -630,3 +630,67 @@ cogl_fog_set (const ClutterColor *fog_color, glFogx (GL_FOG_START, (GLfixed) z_near); glFogx (GL_FOG_END, (GLfixed) z_far); } + +COGLint cogl_create_program (void) +{ + return 0; +} + +COGLint cogl_create_shader (COGLenum shaderType) +{ + return 0; +} + +void cogl_shader_source (COGLint shader, + const gchar *source) +{ +} + +void cogl_shader_compile (COGLint shader_handle) +{ +} + +void cogl_program_attach_shader (COGLint program_handle, + COGLint shader_handle) +{ +} + +void cogl_program_link (COGLint program_handle) +{ +} + +void cogl_program_use (COGLint program_handle) +{ +} + +COGLint cogl_program_get_uniform_location (COGLint program_handle, + const gchar *uniform_name) +{ + return 0; +} + +void cogl_program_destroy (COGLint handle) +{ +} + +void cogl_shader_destroy (COGLint handle) +{ +} + +void cogl_shader_get_info_log (COGLint handle, + guint size, + gchar *buffer) +{ +} + +void cogl_shader_get_parameteriv (COGLint handle, + COGLenum pname, + COGLint *dest) +{ +} + + +void cogl_program_uniform_1f (COGLint uniform_no, + gfloat value) +{ +} diff --git a/clutter/glx/clutter-stage-glx.c b/clutter/glx/clutter-stage-glx.c index eec87593b..31d2fbe71 100644 --- a/clutter/glx/clutter-stage-glx.c +++ b/clutter/glx/clutter-stage-glx.c @@ -36,6 +36,7 @@ #include "../clutter-private.h" #include "../clutter-debug.h" #include "../clutter-units.h" +#include "../clutter-shader.h" #include "cogl.h" @@ -60,6 +61,9 @@ clutter_stage_glx_unrealize (ClutterActor *actor) clutter_x11_trap_x_errors (); + /* Unrealize all shaders, since the GL context is going away */ + clutter_shader_release_all (); + if (G_UNLIKELY (was_offscreen)) { if (stage_glx->glxpixmap) diff --git a/tests/Makefile.am b/tests/Makefile.am index 328326384..8aef6bec8 100644 --- a/tests/Makefile.am +++ b/tests/Makefile.am @@ -2,7 +2,7 @@ noinst_PROGRAMS = test-textures test-events test-offscreen test-scale \ test-actors test-behave test-text test-entry test-project \ test-perspective test-rotate test-depth \ test-threads test-timeline test-score test-script \ - test-model test-grab test-effects test-fullscreen + test-model test-grab test-effects test-fullscreen test-shader INCLUDES = -I$(top_srcdir)/ LDADD = $(top_builddir)/clutter/libclutter-@CLUTTER_FLAVOUR@-@CLUTTER_MAJORMINOR@.la @@ -24,6 +24,7 @@ test_rotate_SOURCES = test-rotate.c test_depth_SOURCES = test-depth.c test_threads_SOURCES = test-threads.c test_timeline_SOURCES = test-timeline.c +test_shader_SOURCES = test-shader.c test_score_SOURCES = test-score.c test_script_SOURCES = test-script.c test_model_SOURCES = test-model.c diff --git a/tests/test-shader.c b/tests/test-shader.c new file mode 100644 index 000000000..d4e3eccff --- /dev/null +++ b/tests/test-shader.c @@ -0,0 +1,246 @@ +/* #define TEST_GROUP 1 */ + +#include + +#include +#include +#include + +typedef struct +{ + gchar *name; + gchar *source; +} ShaderSource; + +static ShaderSource shaders[]= + { + {"brightness-contrast", + + "uniform float brightness;" + "uniform float contrast;" + "uniform sampler2DRect pend_s3_tex;" + "" + "void main()" + "{" + " vec4 pend_s4_result;" + " pend_s4_result = texture2DRect(pend_s3_tex, gl_TexCoord[0].xy);" + " pend_s4_result.x = (pend_s4_result.x - 0.5)*contrast + brightness + 0.5;" + " pend_s4_result.y = (pend_s4_result.y - 0.5)*contrast + brightness + 0.5;" + " pend_s4_result.z = (pend_s4_result.z - 0.5)*contrast + brightness + 0.5;" + " gl_FragColor = pend_s4_result;" + "}", + }, + {"box-blur", + + "uniform float radius ;" + "uniform sampler2DRect rectTexture;" + "" + "void main()" + "{" + " vec4 color = texture2DRect(rectTexture, gl_TexCoord[0].st);" + " float u;" + " float v;" + " int count = 1;" + " for (u=-radius;ubutton.button == 1) + { + new_no = shader_no-1; + } + else + { + new_no = shader_no+1; + } + + if (new_no >= 0 && shaders[new_no].name) + { + ClutterShader *shader; + shader_no = new_no; + + g_print ("setting shaders[%i] named '%s'\n", shader_no, shaders[shader_no].name); + shader = clutter_shader_new_from_strings (NULL, shaders[shader_no].source); + clutter_actor_apply_shader (actor, shader); + + clutter_actor_set_shader_param (actor, "radius", 3.0); + clutter_redraw(); + } + return FALSE; +} + + +gint +main (gint argc, + gchar *argv[]) +{ + ClutterTimeline *timeline; + ClutterAlpha *alpha; + ClutterActor *actor; + ClutterActor *stage; + ClutterColor stage_color = { 0x61, 0x64, 0x8c, 0xff }; + GdkPixbuf *pixbuf; + GError *error; + ClutterShader *shader; + + error = NULL; + + clutter_init (&argc, &argv); + g_print ("applying shaders[%i] named '%s'\n", shader_no, shaders[shader_no].name); + shader = clutter_shader_new_from_strings (NULL, shaders[shader_no].source); + + stage = clutter_stage_get_default (); + clutter_actor_set_size (stage, 512, 384); + + pixbuf = gdk_pixbuf_new_from_file ("redhand.png", NULL); + + if (!pixbuf) + g_error("pixbuf load failed"); + + clutter_stage_set_title (CLUTTER_STAGE (stage), "Shader Test"); + clutter_stage_set_color (CLUTTER_STAGE (stage), &stage_color); + + /* Create a timeline to manage animation */ + timeline = clutter_timeline_new (360, 60); /* num frames, fps */ + g_object_set (timeline, "loop", TRUE, NULL); /* have it loop */ + +#ifndef TEST_GROUP + actor = clutter_texture_new_from_pixbuf (pixbuf); +#else + actor = clutter_group_new (); + { + ClutterActor *child1, *child2, *child3, *child4; + ClutterColor color={0xff, 0x22, 0x66, 0x99}; + + child1 = clutter_texture_new_from_pixbuf (pixbuf); + child2 = clutter_texture_new_from_pixbuf (pixbuf); + child3 = clutter_rectangle_new (); + child4 = clutter_label_new_with_text ("Sans 20px", "Shady stuff"); + + clutter_rectangle_set_color (child3, &color); + clutter_actor_set_size (child3, 50, 50); + clutter_actor_set_position (child1, 0, 0); + clutter_actor_set_position (child2, 50, 100); + clutter_actor_set_position (child3, 30, -30); + clutter_actor_set_position (child4, -50, 20); + + clutter_group_add (CLUTTER_GROUP (actor), child1); + clutter_group_add (CLUTTER_GROUP (actor), child2); + clutter_group_add (CLUTTER_GROUP (actor), child3); + clutter_group_add (CLUTTER_GROUP (actor), child4); + clutter_actor_show_all (actor); + } +#endif + clutter_actor_set_position (actor, 100, 100); + + clutter_container_add_actor (CLUTTER_CONTAINER (stage), actor); + + clutter_actor_apply_shader (actor, shader); + + clutter_actor_set_shader_param (actor, "brightness", 0.4); + clutter_actor_set_shader_param (actor, "contrast", -1.9); + + clutter_actor_set_reactive (actor, TRUE); + g_signal_connect (actor, "button-release-event", + G_CALLBACK (button_release_cb), NULL); + + /* Show everying ( and map window ) */ + clutter_actor_show_all (stage); + + /* and start it */ + clutter_timeline_start (timeline); + + clutter_main (); + + return 0; +}