/* -*- 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 #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); }