007d305736
A generic, introspectable Shader effect is not only more flexible than a shader actor, it will also make it much easier to turn Lightbox into an actor subclass and replace Tweener with Clutter's own animation support. https://gitlab.gnome.org/GNOME/gnome-shell/merge_requests/651
241 lines
7.7 KiB
C
241 lines
7.7 KiB
C
/* -*- 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)
|
|
{
|
|
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);
|
|
|
|
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)
|
|
{
|
|
ShellGLSLEffect *self = SHELL_GLSL_EFFECT (effect);
|
|
ShellGLSLEffectPrivate *priv;
|
|
ClutterActor *actor;
|
|
guint8 paint_opacity;
|
|
|
|
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);
|
|
cogl_framebuffer_draw_rectangle (cogl_get_draw_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: wheter 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);
|
|
}
|