summaryrefslogtreecommitdiffstats
path: root/src/shell-glsl-effect.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/shell-glsl-effect.c')
-rw-r--r--src/shell-glsl-effect.c245
1 files changed, 245 insertions, 0 deletions
diff --git a/src/shell-glsl-effect.c b/src/shell-glsl-effect.c
new file mode 100644
index 0000000..1f62380
--- /dev/null
+++ b/src/shell-glsl-effect.c
@@ -0,0 +1,245 @@
+/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
+
+/**
+ * SECTION:shell-glsl-effect
+ * @short_description: An offscreen effect using GLSL
+ *
+ * A #ShellGLSLEffect is a #ClutterOffscreenEffect that allows
+ * running custom GLSL to the vertex and fragment stages of the
+ * graphic pipeline.
+ */
+
+#include "config.h"
+
+#include <cogl/cogl.h>
+#include "shell-glsl-effect.h"
+
+typedef struct _ShellGLSLEffectPrivate ShellGLSLEffectPrivate;
+struct _ShellGLSLEffectPrivate
+{
+ CoglPipeline *pipeline;
+
+ gint tex_width;
+ gint tex_height;
+};
+
+G_DEFINE_TYPE_WITH_PRIVATE (ShellGLSLEffect, shell_glsl_effect, CLUTTER_TYPE_OFFSCREEN_EFFECT);
+
+static gboolean
+shell_glsl_effect_pre_paint (ClutterEffect *effect,
+ ClutterPaintContext *paint_context)
+{
+ ShellGLSLEffect *self = SHELL_GLSL_EFFECT (effect);
+ ClutterOffscreenEffect *offscreen_effect = CLUTTER_OFFSCREEN_EFFECT (effect);
+ ShellGLSLEffectPrivate *priv = shell_glsl_effect_get_instance_private (self);
+ ClutterEffectClass *parent_class;
+ CoglHandle texture;
+ gboolean success;
+
+ if (!clutter_actor_meta_get_enabled (CLUTTER_ACTOR_META (effect)))
+ return FALSE;
+
+ 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 (CLUTTER_ACTOR_META (effect), FALSE);
+ return FALSE;
+ }
+
+ parent_class = CLUTTER_EFFECT_CLASS (shell_glsl_effect_parent_class);
+ success = parent_class->pre_paint (effect, paint_context);
+
+ if (!success)
+ return FALSE;
+
+ texture = clutter_offscreen_effect_get_texture (offscreen_effect);
+ priv->tex_width = cogl_texture_get_width (texture);
+ priv->tex_height = cogl_texture_get_height (texture);
+
+ cogl_pipeline_set_layer_texture (priv->pipeline, 0, texture);
+
+ return TRUE;
+}
+
+static void
+shell_glsl_effect_paint_target (ClutterOffscreenEffect *effect,
+ ClutterPaintContext *paint_context)
+{
+ ShellGLSLEffect *self = SHELL_GLSL_EFFECT (effect);
+ ShellGLSLEffectPrivate *priv;
+ ClutterActor *actor;
+ guint8 paint_opacity;
+ CoglFramebuffer *framebuffer;
+
+ priv = shell_glsl_effect_get_instance_private (self);
+
+ actor = clutter_actor_meta_get_actor (CLUTTER_ACTOR_META (effect));
+ paint_opacity = clutter_actor_get_paint_opacity (actor);
+
+ cogl_pipeline_set_color4ub (priv->pipeline,
+ paint_opacity,
+ paint_opacity,
+ paint_opacity,
+ paint_opacity);
+
+ framebuffer = clutter_paint_context_get_framebuffer (paint_context);
+ cogl_framebuffer_draw_rectangle (framebuffer,
+ priv->pipeline,
+ 0, 0,
+ priv->tex_width, priv->tex_height);
+}
+
+
+/**
+ * shell_glsl_effect_add_glsl_snippet:
+ * @effect: a #ShellGLSLEffect
+ * @hook: where to insert the code
+ * @declarations: GLSL declarations
+ * @code: GLSL code
+ * @is_replace: whether Cogl code should be replaced by the custom shader
+ *
+ * Adds a GLSL snippet to the pipeline used for drawing the effect texture.
+ * See #CoglSnippet for details.
+ *
+ * This is only valid inside the a call to the build_pipeline() virtual
+ * function.
+ */
+void
+shell_glsl_effect_add_glsl_snippet (ShellGLSLEffect *effect,
+ ShellSnippetHook hook,
+ const char *declarations,
+ const char *code,
+ gboolean is_replace)
+{
+ ShellGLSLEffectClass *klass = SHELL_GLSL_EFFECT_GET_CLASS (effect);
+ CoglSnippet *snippet;
+
+ g_return_if_fail (klass->base_pipeline != NULL);
+
+ if (is_replace)
+ {
+ snippet = cogl_snippet_new ((CoglSnippetHook)hook, declarations, NULL);
+ cogl_snippet_set_replace (snippet, code);
+ }
+ else
+ {
+ snippet = cogl_snippet_new ((CoglSnippetHook)hook, declarations, code);
+ }
+
+ if (hook == SHELL_SNIPPET_HOOK_VERTEX ||
+ hook == SHELL_SNIPPET_HOOK_FRAGMENT)
+ cogl_pipeline_add_snippet (klass->base_pipeline, snippet);
+ else
+ cogl_pipeline_add_layer_snippet (klass->base_pipeline, 0, snippet);
+
+ cogl_object_unref (snippet);
+}
+
+static void
+shell_glsl_effect_dispose (GObject *gobject)
+{
+ ShellGLSLEffect *self = SHELL_GLSL_EFFECT (gobject);
+ ShellGLSLEffectPrivate *priv;
+
+ priv = shell_glsl_effect_get_instance_private (self);
+
+ g_clear_pointer (&priv->pipeline, cogl_object_unref);
+
+ G_OBJECT_CLASS (shell_glsl_effect_parent_class)->dispose (gobject);
+}
+
+static void
+shell_glsl_effect_init (ShellGLSLEffect *effect)
+{
+}
+
+static void
+shell_glsl_effect_constructed (GObject *object)
+{
+ ShellGLSLEffect *self;
+ ShellGLSLEffectClass *klass;
+ ShellGLSLEffectPrivate *priv;
+ CoglContext *ctx =
+ clutter_backend_get_cogl_context (clutter_get_default_backend ());
+
+ G_OBJECT_CLASS (shell_glsl_effect_parent_class)->constructed (object);
+
+ /* Note that, differently from ClutterBlurEffect, we are calling
+ this inside constructed, not init, so klass points to the most-derived
+ GTypeClass, not ShellGLSLEffectClass.
+ */
+ klass = SHELL_GLSL_EFFECT_GET_CLASS (object);
+ self = SHELL_GLSL_EFFECT (object);
+ priv = shell_glsl_effect_get_instance_private (self);
+
+ if (G_UNLIKELY (klass->base_pipeline == NULL))
+ {
+ klass->base_pipeline = cogl_pipeline_new (ctx);
+ cogl_pipeline_set_blend (klass->base_pipeline, "RGBA = ADD (SRC_COLOR * (SRC_COLOR[A]), DST_COLOR * (1-SRC_COLOR[A]))", NULL);
+
+ if (klass->build_pipeline != NULL)
+ klass->build_pipeline (self);
+ }
+
+ priv->pipeline = cogl_pipeline_copy (klass->base_pipeline);
+
+ cogl_pipeline_set_layer_null_texture (klass->base_pipeline, 0);
+}
+
+static void
+shell_glsl_effect_class_init (ShellGLSLEffectClass *klass)
+{
+ ClutterEffectClass *effect_class = CLUTTER_EFFECT_CLASS (klass);
+ GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
+ ClutterOffscreenEffectClass *offscreen_class;
+
+ offscreen_class = CLUTTER_OFFSCREEN_EFFECT_CLASS (klass);
+ offscreen_class->paint_target = shell_glsl_effect_paint_target;
+
+ effect_class->pre_paint = shell_glsl_effect_pre_paint;
+
+ gobject_class->constructed = shell_glsl_effect_constructed;
+ gobject_class->dispose = shell_glsl_effect_dispose;
+}
+
+/**
+ * shell_glsl_effect_get_uniform_location:
+ * @effect: a #ShellGLSLEffect
+ * @name: the uniform name
+ *
+ * Returns: the location of the uniform named @name, that can be
+ * passed to shell_glsl_effect_set_uniform_float().
+ */
+int
+shell_glsl_effect_get_uniform_location (ShellGLSLEffect *effect,
+ const char *name)
+{
+ ShellGLSLEffectPrivate *priv = shell_glsl_effect_get_instance_private (effect);
+ return cogl_pipeline_get_uniform_location (priv->pipeline, name);
+}
+
+/**
+ * shell_glsl_effect_set_uniform_float:
+ * @effect: a #ShellGLSLEffect
+ * @uniform: the uniform location (as returned by shell_glsl_effect_get_uniform_location())
+ * @n_components: the number of components in the uniform (eg. 3 for a vec3)
+ * @total_count: the total number of floats in @value
+ * @value: (array length=total_count): the array of floats to set @uniform
+ */
+void
+shell_glsl_effect_set_uniform_float (ShellGLSLEffect *effect,
+ int uniform,
+ int n_components,
+ int total_count,
+ const float *value)
+{
+ ShellGLSLEffectPrivate *priv = shell_glsl_effect_get_instance_private (effect);
+ cogl_pipeline_set_uniform_float (priv->pipeline, uniform,
+ n_components, total_count / n_components,
+ value);
+}