mutter/clutter/clutter/clutter-paint-nodes.c
Georges Basile Stavracas Neto 6654053250 clutter/paint-nodes: Make ClutterLayerNode always push/pop
ClutterLayerNode currently skips pushing the offscreen framebuffer when
no operations are set. This was added at the time because pushing and
popping was a synchronization point in Cogl, slow enough to force the
layer node to have this protective measure. Nowadays, pushing and
popping on the paint context is free.

Make ClutterLayerNode always push and pop in pre and post paint.

Part-of: <https://gitlab.gnome.org/GNOME/mutter/-/merge_requests/1355>
2020-12-15 10:36:34 -03:00

1958 lines
53 KiB
C

/*
* Clutter.
*
* An OpenGL based 'interactive canvas' library.
*
* Copyright (C) 2011 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-paint-nodes
* @Title: Paint Nodes
* @Short_Description: ClutterPaintNode implementations
*
* Clutter provides a set of predefined #ClutterPaintNode implementations
* that cover all the state changes available.
*/
#include "clutter-build-config.h"
#define CLUTTER_ENABLE_EXPERIMENTAL_API
#include "clutter-paint-node-private.h"
#include <pango/pango.h>
#include <cogl/cogl.h>
#include "clutter-actor-private.h"
#include "clutter-blur-private.h"
#include "clutter-color.h"
#include "clutter-debug.h"
#include "clutter-private.h"
#include "clutter-paint-context-private.h"
#include "clutter-paint-nodes.h"
static CoglPipeline *default_color_pipeline = NULL;
static CoglPipeline *default_texture_pipeline = NULL;
/*< private >
* _clutter_paint_node_init_types:
*
* Initializes the required types for ClutterPaintNode subclasses
*/
void
_clutter_paint_node_init_types (void)
{
CoglContext *ctx;
CoglColor cogl_color;
GType node_type G_GNUC_UNUSED;
if (G_LIKELY (default_color_pipeline != NULL))
return;
ctx = clutter_backend_get_cogl_context (clutter_get_default_backend ());
node_type = clutter_paint_node_get_type ();
cogl_color_init_from_4f (&cogl_color, 1.0, 1.0, 1.0, 1.0);
default_color_pipeline = cogl_pipeline_new (ctx);
cogl_pipeline_set_color (default_color_pipeline, &cogl_color);
default_texture_pipeline = cogl_pipeline_new (ctx);
cogl_pipeline_set_layer_null_texture (default_texture_pipeline, 0);
cogl_pipeline_set_color (default_texture_pipeline, &cogl_color);
cogl_pipeline_set_layer_wrap_mode (default_texture_pipeline, 0,
COGL_PIPELINE_WRAP_MODE_AUTOMATIC);
}
/*
* Root node
*
* any frame can only have a since RootNode instance for each
* top-level actor.
*/
#define clutter_root_node_get_type clutter_root_node_get_type
struct _ClutterRootNode
{
ClutterPaintNode parent_instance;
CoglFramebuffer *framebuffer;
CoglBufferBit clear_flags;
CoglColor clear_color;
};
G_DEFINE_TYPE (ClutterRootNode, clutter_root_node, CLUTTER_TYPE_PAINT_NODE)
static gboolean
clutter_root_node_pre_draw (ClutterPaintNode *node,
ClutterPaintContext *paint_context)
{
ClutterRootNode *rnode = (ClutterRootNode *) node;
clutter_paint_context_push_framebuffer (paint_context, rnode->framebuffer);
cogl_framebuffer_clear (rnode->framebuffer,
rnode->clear_flags,
&rnode->clear_color);
return TRUE;
}
static void
clutter_root_node_post_draw (ClutterPaintNode *node,
ClutterPaintContext *paint_context)
{
clutter_paint_context_pop_framebuffer (paint_context);
}
static void
clutter_root_node_finalize (ClutterPaintNode *node)
{
ClutterRootNode *rnode = (ClutterRootNode *) node;
g_object_unref (rnode->framebuffer);
CLUTTER_PAINT_NODE_CLASS (clutter_root_node_parent_class)->finalize (node);
}
static CoglFramebuffer *
clutter_root_node_get_framebuffer (ClutterPaintNode *node)
{
ClutterRootNode *rnode = (ClutterRootNode *) node;
return rnode->framebuffer;
}
static void
clutter_root_node_class_init (ClutterRootNodeClass *klass)
{
ClutterPaintNodeClass *node_class = CLUTTER_PAINT_NODE_CLASS (klass);
node_class->pre_draw = clutter_root_node_pre_draw;
node_class->post_draw = clutter_root_node_post_draw;
node_class->finalize = clutter_root_node_finalize;
node_class->get_framebuffer = clutter_root_node_get_framebuffer;
}
static void
clutter_root_node_init (ClutterRootNode *self)
{
}
ClutterPaintNode *
clutter_root_node_new (CoglFramebuffer *framebuffer,
const ClutterColor *clear_color,
CoglBufferBit clear_flags)
{
ClutterRootNode *res;
g_return_val_if_fail (framebuffer, NULL);
res = _clutter_paint_node_create (CLUTTER_TYPE_ROOT_NODE);
cogl_color_init_from_4ub (&res->clear_color,
clear_color->red,
clear_color->green,
clear_color->blue,
clear_color->alpha);
cogl_color_premultiply (&res->clear_color);
res->framebuffer = g_object_ref (framebuffer);
res->clear_flags = clear_flags;
return (ClutterPaintNode *) res;
}
/*
* ClutterTransformNode
*/
struct _ClutterTransformNode
{
ClutterPaintNode parent_instance;
graphene_matrix_t transform;
};
struct _ClutterTransformNodeClass
{
ClutterPaintNodeClass parent_class;
};
G_DEFINE_TYPE (ClutterTransformNode, clutter_transform_node, CLUTTER_TYPE_PAINT_NODE)
static gboolean
clutter_transform_node_pre_draw (ClutterPaintNode *node,
ClutterPaintContext *paint_context)
{
ClutterTransformNode *transform_node = (ClutterTransformNode *) node;
CoglFramebuffer *fb =
clutter_paint_context_get_framebuffer (paint_context);
cogl_framebuffer_push_matrix (fb);
cogl_framebuffer_transform (fb, &transform_node->transform);
return TRUE;
}
static void
clutter_transform_node_post_draw (ClutterPaintNode *node,
ClutterPaintContext *paint_context)
{
CoglFramebuffer *fb =
clutter_paint_context_get_framebuffer (paint_context);
cogl_framebuffer_pop_matrix (fb);
}
static void
clutter_transform_node_class_init (ClutterTransformNodeClass *klass)
{
ClutterPaintNodeClass *node_class;
node_class = CLUTTER_PAINT_NODE_CLASS (klass);
node_class->pre_draw = clutter_transform_node_pre_draw;
node_class->post_draw = clutter_transform_node_post_draw;
}
static void
clutter_transform_node_init (ClutterTransformNode *self)
{
graphene_matrix_init_identity (&self->transform);
}
/*
* clutter_transform_node_new:
* @transform: (nullable): the transform matrix to apply
*
* Return value: (transfer full): the newly created #ClutterTransformNode.
* Use clutter_paint_node_unref() when done.
*/
ClutterPaintNode *
clutter_transform_node_new (const graphene_matrix_t *transform)
{
ClutterTransformNode *res;
res = _clutter_paint_node_create (CLUTTER_TYPE_TRANSFORM_NODE);
if (transform)
graphene_matrix_init_from_matrix (&res->transform, transform);
return (ClutterPaintNode *) res;
}
/*
* Dummy node, private
*
* an empty node, used temporarily until we can drop API compatibility,
* and we'll be able to build a full render tree for each frame.
*/
#define clutter_dummy_node_get_type _clutter_dummy_node_get_type
typedef struct _ClutterDummyNode ClutterDummyNode;
typedef struct _ClutterPaintNodeClass ClutterDummyNodeClass;
struct _ClutterDummyNode
{
ClutterPaintNode parent_instance;
ClutterActor *actor;
CoglFramebuffer *framebuffer;
};
G_DEFINE_TYPE (ClutterDummyNode, clutter_dummy_node, CLUTTER_TYPE_PAINT_NODE)
static gboolean
clutter_dummy_node_pre_draw (ClutterPaintNode *node,
ClutterPaintContext *paint_context)
{
return TRUE;
}
static JsonNode *
clutter_dummy_node_serialize (ClutterPaintNode *node)
{
ClutterDummyNode *dnode = (ClutterDummyNode *) node;
JsonBuilder *builder;
JsonNode *res;
if (dnode->actor == NULL)
return json_node_new (JSON_NODE_NULL);
builder = json_builder_new ();
json_builder_begin_object (builder);
json_builder_set_member_name (builder, "actor");
json_builder_add_string_value (builder, _clutter_actor_get_debug_name (dnode->actor));
json_builder_end_object (builder);
res = json_builder_get_root (builder);
g_object_unref (builder);
return res;
}
static CoglFramebuffer *
clutter_dummy_node_get_framebuffer (ClutterPaintNode *node)
{
ClutterDummyNode *dnode = (ClutterDummyNode *) node;
return dnode->framebuffer;
}
static void
clutter_dummy_node_finalize (ClutterPaintNode *node)
{
ClutterDummyNode *dnode = (ClutterDummyNode *) node;
g_clear_object (&dnode->framebuffer);
CLUTTER_PAINT_NODE_CLASS (clutter_dummy_node_parent_class)->finalize (node);
}
static void
clutter_dummy_node_class_init (ClutterDummyNodeClass *klass)
{
ClutterPaintNodeClass *node_class = CLUTTER_PAINT_NODE_CLASS (klass);
node_class->pre_draw = clutter_dummy_node_pre_draw;
node_class->serialize = clutter_dummy_node_serialize;
node_class->get_framebuffer = clutter_dummy_node_get_framebuffer;
node_class->finalize = clutter_dummy_node_finalize;
}
static void
clutter_dummy_node_init (ClutterDummyNode *self)
{
}
ClutterPaintNode *
_clutter_dummy_node_new (ClutterActor *actor,
CoglFramebuffer *framebuffer)
{
ClutterPaintNode *res;
ClutterDummyNode *dnode;
res = _clutter_paint_node_create (_clutter_dummy_node_get_type ());
dnode = (ClutterDummyNode *) res;
dnode->actor = actor;
dnode->framebuffer = g_object_ref (framebuffer);
return res;
}
/*
* Pipeline node
*/
struct _ClutterPipelineNode
{
ClutterPaintNode parent_instance;
CoglPipeline *pipeline;
};
/**
* ClutterPipelineNodeClass:
*
* The `ClutterPipelineNodeClass` structure is an opaque
* type whose members cannot be directly accessed.
*
* Since: 1.10
*/
struct _ClutterPipelineNodeClass
{
ClutterPaintNodeClass parent_class;
};
G_DEFINE_TYPE (ClutterPipelineNode, clutter_pipeline_node, CLUTTER_TYPE_PAINT_NODE)
static void
clutter_pipeline_node_finalize (ClutterPaintNode *node)
{
ClutterPipelineNode *pnode = CLUTTER_PIPELINE_NODE (node);
if (pnode->pipeline != NULL)
cogl_object_unref (pnode->pipeline);
CLUTTER_PAINT_NODE_CLASS (clutter_pipeline_node_parent_class)->finalize (node);
}
static gboolean
clutter_pipeline_node_pre_draw (ClutterPaintNode *node,
ClutterPaintContext *paint_context)
{
ClutterPipelineNode *pnode = CLUTTER_PIPELINE_NODE (node);
if (node->operations != NULL &&
pnode->pipeline != NULL)
return TRUE;
return FALSE;
}
static CoglFramebuffer *
get_target_framebuffer (ClutterPaintNode *node,
ClutterPaintContext *paint_context)
{
CoglFramebuffer *framebuffer;
framebuffer = clutter_paint_node_get_framebuffer (node);
if (framebuffer)
return framebuffer;
return clutter_paint_context_get_framebuffer (paint_context);
}
static void
clutter_pipeline_node_draw (ClutterPaintNode *node,
ClutterPaintContext *paint_context)
{
ClutterPipelineNode *pnode = CLUTTER_PIPELINE_NODE (node);
CoglFramebuffer *fb;
guint i;
if (pnode->pipeline == NULL)
return;
if (node->operations == NULL)
return;
fb = clutter_paint_context_get_framebuffer (paint_context);
for (i = 0; i < node->operations->len; i++)
{
const ClutterPaintOperation *op;
op = &g_array_index (node->operations, ClutterPaintOperation, i);
switch (op->opcode)
{
case PAINT_OP_INVALID:
break;
case PAINT_OP_TEX_RECT:
cogl_framebuffer_draw_textured_rectangle (fb,
pnode->pipeline,
op->op.texrect[0],
op->op.texrect[1],
op->op.texrect[2],
op->op.texrect[3],
op->op.texrect[4],
op->op.texrect[5],
op->op.texrect[6],
op->op.texrect[7]);
break;
case PAINT_OP_MULTITEX_RECT:
cogl_framebuffer_draw_multitextured_rectangle (fb,
pnode->pipeline,
op->op.texrect[0],
op->op.texrect[1],
op->op.texrect[2],
op->op.texrect[3],
(float*) op->multitex_coords->data,
op->multitex_coords->len);
break;
case PAINT_OP_PRIMITIVE:
cogl_framebuffer_draw_primitive (fb,
pnode->pipeline,
op->op.primitive);
break;
}
}
}
static void
clutter_pipeline_node_post_draw (ClutterPaintNode *node,
ClutterPaintContext *paint_context)
{
}
static JsonNode *
clutter_pipeline_node_serialize (ClutterPaintNode *node)
{
ClutterPipelineNode *pnode = CLUTTER_PIPELINE_NODE (node);
JsonBuilder *builder;
CoglColor color;
JsonNode *res;
if (pnode->pipeline == NULL)
return json_node_new (JSON_NODE_NULL);
builder = json_builder_new ();
json_builder_begin_object (builder);
cogl_pipeline_get_color (pnode->pipeline, &color);
json_builder_set_member_name (builder, "color");
json_builder_begin_array (builder);
json_builder_add_double_value (builder, cogl_color_get_red (&color));
json_builder_add_double_value (builder, cogl_color_get_green (&color));
json_builder_add_double_value (builder, cogl_color_get_blue (&color));
json_builder_add_double_value (builder, cogl_color_get_alpha (&color));
json_builder_end_array (builder);
#if 0
json_builder_set_member_name (builder, "layers");
json_builder_begin_array (builder);
cogl_pipeline_foreach_layer (pnode->pipeline,
clutter_pipeline_node_serialize_layer,
builder);
json_builder_end_array (builder);
#endif
json_builder_end_object (builder);
res = json_builder_get_root (builder);
g_object_unref (builder);
return res;
}
static void
clutter_pipeline_node_class_init (ClutterPipelineNodeClass *klass)
{
ClutterPaintNodeClass *node_class;
node_class = CLUTTER_PAINT_NODE_CLASS (klass);
node_class->pre_draw = clutter_pipeline_node_pre_draw;
node_class->draw = clutter_pipeline_node_draw;
node_class->post_draw = clutter_pipeline_node_post_draw;
node_class->finalize = clutter_pipeline_node_finalize;
node_class->serialize = clutter_pipeline_node_serialize;
}
static void
clutter_pipeline_node_init (ClutterPipelineNode *self)
{
}
/**
* clutter_pipeline_node_new:
* @pipeline: (allow-none): a Cogl pipeline state object, or %NULL
*
* Creates a new #ClutterPaintNode that will use the @pipeline to
* paint its contents.
*
* This function will acquire a reference on the passed @pipeline,
* so it is safe to call cogl_object_unref() when it returns.
*
* Return value: (transfer full): the newly created #ClutterPaintNode.
* Use clutter_paint_node_unref() when done.
*
* Since: 1.10
*/
ClutterPaintNode *
clutter_pipeline_node_new (CoglPipeline *pipeline)
{
ClutterPipelineNode *res;
g_return_val_if_fail (pipeline == NULL || cogl_is_pipeline (pipeline), NULL);
res = _clutter_paint_node_create (CLUTTER_TYPE_PIPELINE_NODE);
if (pipeline != NULL)
res->pipeline = cogl_object_ref (pipeline);
return (ClutterPaintNode *) res;
}
/*
* Color node
*/
struct _ClutterColorNode
{
ClutterPipelineNode parent_instance;
};
/**
* ClutterColorNodeClass:
*
* The `ClutterColorNodeClass` structure is an
* opaque type whose members cannot be directly accessed.
*
* Since: 1.10
*/
struct _ClutterColorNodeClass
{
ClutterPipelineNodeClass parent_class;
};
G_DEFINE_TYPE (ClutterColorNode, clutter_color_node, CLUTTER_TYPE_PIPELINE_NODE)
static void
clutter_color_node_class_init (ClutterColorNodeClass *klass)
{
}
static void
clutter_color_node_init (ClutterColorNode *cnode)
{
ClutterPipelineNode *pnode = CLUTTER_PIPELINE_NODE (cnode);
g_assert (default_color_pipeline != NULL);
pnode->pipeline = cogl_pipeline_copy (default_color_pipeline);
}
/**
* clutter_color_node_new:
* @color: (allow-none): the color to paint, or %NULL
*
* Creates a new #ClutterPaintNode that will paint a solid color
* fill using @color.
*
* Return value: (transfer full): the newly created #ClutterPaintNode. Use
* clutter_paint_node_unref() when done
*
* Since: 1.10
*/
ClutterPaintNode *
clutter_color_node_new (const ClutterColor *color)
{
ClutterPipelineNode *cnode;
cnode = _clutter_paint_node_create (CLUTTER_TYPE_COLOR_NODE);
if (color != NULL)
{
CoglColor cogl_color;
cogl_color_init_from_4ub (&cogl_color,
color->red,
color->green,
color->blue,
color->alpha);
cogl_color_premultiply (&cogl_color);
cogl_pipeline_set_color (cnode->pipeline, &cogl_color);
}
return (ClutterPaintNode *) cnode;
}
/*
* Texture node
*/
struct _ClutterTextureNode
{
ClutterPipelineNode parent_instance;
};
/**
* ClutterTextureNodeClass:
*
* The `ClutterTextureNodeClass` structure is an
* opaque type whose members cannot be directly accessed.
*
* Since: 1.10
*/
struct _ClutterTextureNodeClass
{
ClutterPipelineNodeClass parent_class;
};
G_DEFINE_TYPE (ClutterTextureNode, clutter_texture_node, CLUTTER_TYPE_PIPELINE_NODE)
static void
clutter_texture_node_class_init (ClutterTextureNodeClass *klass)
{
}
static void
clutter_texture_node_init (ClutterTextureNode *self)
{
ClutterPipelineNode *pnode = CLUTTER_PIPELINE_NODE (self);
g_assert (default_texture_pipeline != NULL);
pnode->pipeline = cogl_pipeline_copy (default_texture_pipeline);
}
static CoglPipelineFilter
clutter_scaling_filter_to_cogl_pipeline_filter (ClutterScalingFilter filter)
{
switch (filter)
{
case CLUTTER_SCALING_FILTER_NEAREST:
return COGL_PIPELINE_FILTER_NEAREST;
case CLUTTER_SCALING_FILTER_LINEAR:
return COGL_PIPELINE_FILTER_LINEAR;
case CLUTTER_SCALING_FILTER_TRILINEAR:
return COGL_PIPELINE_FILTER_LINEAR_MIPMAP_LINEAR;
}
return COGL_PIPELINE_FILTER_LINEAR;
}
/**
* clutter_texture_node_new:
* @texture: a #CoglTexture
* @color: (allow-none): a #ClutterColor used for blending, or %NULL
* @min_filter: the minification filter for the texture
* @mag_filter: the magnification filter for the texture
*
* Creates a new #ClutterPaintNode that will paint the passed @texture.
*
* This function will take a reference on @texture, so it is safe to
* call cogl_object_unref() on @texture when it returns.
*
* The @color must not be pre-multiplied with its #ClutterColor.alpha
* channel value; if @color is %NULL, a fully opaque white color will
* be used for blending.
*
* Return value: (transfer full): the newly created #ClutterPaintNode.
* Use clutter_paint_node_unref() when done
*
* Since: 1.10
*/
ClutterPaintNode *
clutter_texture_node_new (CoglTexture *texture,
const ClutterColor *color,
ClutterScalingFilter min_filter,
ClutterScalingFilter mag_filter)
{
ClutterPipelineNode *tnode;
CoglColor cogl_color;
CoglPipelineFilter min_f, mag_f;
g_return_val_if_fail (cogl_is_texture (texture), NULL);
tnode = _clutter_paint_node_create (CLUTTER_TYPE_TEXTURE_NODE);
cogl_pipeline_set_layer_texture (tnode->pipeline, 0, texture);
min_f = clutter_scaling_filter_to_cogl_pipeline_filter (min_filter);
mag_f = clutter_scaling_filter_to_cogl_pipeline_filter (mag_filter);
cogl_pipeline_set_layer_filters (tnode->pipeline, 0, min_f, mag_f);
if (color != NULL)
{
cogl_color_init_from_4ub (&cogl_color,
color->red,
color->green,
color->blue,
color->alpha);
cogl_color_premultiply (&cogl_color);
}
else
cogl_color_init_from_4ub (&cogl_color, 255, 255, 255, 255);
cogl_pipeline_set_color (tnode->pipeline, &cogl_color);
return (ClutterPaintNode *) tnode;
}
/*
* Text node
*/
struct _ClutterTextNode
{
ClutterPaintNode parent_instance;
PangoLayout *layout;
CoglColor color;
};
/**
* ClutterTextNodeClass:
*
* The `ClutterTextNodeClass` structure is an opaque
* type whose contents cannot be directly accessed.
*
* Since: 1.10
*/
struct _ClutterTextNodeClass
{
ClutterPaintNodeClass parent_class;
};
G_DEFINE_TYPE (ClutterTextNode, clutter_text_node, CLUTTER_TYPE_PAINT_NODE)
static void
clutter_text_node_finalize (ClutterPaintNode *node)
{
ClutterTextNode *tnode = CLUTTER_TEXT_NODE (node);
if (tnode->layout != NULL)
g_object_unref (tnode->layout);
CLUTTER_PAINT_NODE_CLASS (clutter_text_node_parent_class)->finalize (node);
}
static gboolean
clutter_text_node_pre_draw (ClutterPaintNode *node,
ClutterPaintContext *paint_context)
{
ClutterTextNode *tnode = CLUTTER_TEXT_NODE (node);
return tnode->layout != NULL;
}
static void
clutter_text_node_draw (ClutterPaintNode *node,
ClutterPaintContext *paint_context)
{
ClutterTextNode *tnode = CLUTTER_TEXT_NODE (node);
PangoRectangle extents;
CoglFramebuffer *fb;
guint i;
if (node->operations == NULL)
return;
fb = get_target_framebuffer (node, paint_context);
pango_layout_get_pixel_extents (tnode->layout, NULL, &extents);
for (i = 0; i < node->operations->len; i++)
{
const ClutterPaintOperation *op;
float op_width, op_height;
gboolean clipped = FALSE;
op = &g_array_index (node->operations, ClutterPaintOperation, i);
switch (op->opcode)
{
case PAINT_OP_TEX_RECT:
op_width = op->op.texrect[2] - op->op.texrect[0];
op_height = op->op.texrect[3] - op->op.texrect[1];
/* if the primitive size was smaller than the layout,
* we clip the layout when drawin, to avoid spilling
* it out
*/
if (extents.width > op_width ||
extents.height > op_height)
{
cogl_framebuffer_push_rectangle_clip (fb,
op->op.texrect[0],
op->op.texrect[1],
op->op.texrect[2],
op->op.texrect[3]);
clipped = TRUE;
}
cogl_pango_show_layout (fb,
tnode->layout,
op->op.texrect[0],
op->op.texrect[1],
&tnode->color);
if (clipped)
cogl_framebuffer_pop_clip (fb);
break;
case PAINT_OP_MULTITEX_RECT:
case PAINT_OP_PRIMITIVE:
case PAINT_OP_INVALID:
break;
}
}
}
static JsonNode *
clutter_text_node_serialize (ClutterPaintNode *node)
{
ClutterTextNode *tnode = CLUTTER_TEXT_NODE (node);
JsonBuilder *builder;
JsonNode *res;
builder = json_builder_new ();
json_builder_begin_object (builder);
json_builder_set_member_name (builder, "layout");
if (pango_layout_get_character_count (tnode->layout) > 12)
{
const char *text = pango_layout_get_text (tnode->layout);
char *str;
str = g_strndup (text, 12);
json_builder_add_string_value (builder, str);
g_free (str);
}
else
json_builder_add_string_value (builder, pango_layout_get_text (tnode->layout));
json_builder_set_member_name (builder, "color");
json_builder_begin_array (builder);
json_builder_add_double_value (builder, cogl_color_get_red (&tnode->color));
json_builder_add_double_value (builder, cogl_color_get_green (&tnode->color));
json_builder_add_double_value (builder, cogl_color_get_blue (&tnode->color));
json_builder_add_double_value (builder, cogl_color_get_alpha (&tnode->color));
json_builder_end_array (builder);
json_builder_end_object (builder);
res = json_builder_get_root (builder);
g_object_unref (builder);
return res;
}
static void
clutter_text_node_class_init (ClutterTextNodeClass *klass)
{
ClutterPaintNodeClass *node_class = CLUTTER_PAINT_NODE_CLASS (klass);
node_class->pre_draw = clutter_text_node_pre_draw;
node_class->draw = clutter_text_node_draw;
node_class->finalize = clutter_text_node_finalize;
node_class->serialize = clutter_text_node_serialize;
}
static void
clutter_text_node_init (ClutterTextNode *self)
{
cogl_color_init_from_4f (&self->color, 0.0, 0.0, 0.0, 1.0);
}
/**
* clutter_text_node_new:
* @layout: (allow-none): a #PangoLayout, or %NULL
* @color: (allow-none): the color used to paint the layout,
* or %NULL
*
* Creates a new #ClutterPaintNode that will paint a #PangoLayout
* with the given color.
*
* This function takes a reference on the passed @layout, so it
* is safe to call g_object_unref() after it returns.
*
* Return value: (transfer full): the newly created #ClutterPaintNode.
* Use clutter_paint_node_unref() when done
*
* Since: 1.10
*/
ClutterPaintNode *
clutter_text_node_new (PangoLayout *layout,
const ClutterColor *color)
{
ClutterTextNode *res;
g_return_val_if_fail (layout == NULL || PANGO_IS_LAYOUT (layout), NULL);
res = _clutter_paint_node_create (CLUTTER_TYPE_TEXT_NODE);
if (layout != NULL)
res->layout = g_object_ref (layout);
if (color != NULL)
{
cogl_color_init_from_4ub (&res->color,
color->red,
color->green,
color->blue,
color->alpha);
}
return (ClutterPaintNode *) res;
}
/*
* Clip node
*/
struct _ClutterClipNode
{
ClutterPaintNode parent_instance;
};
/**
* ClutterClipNodeClass:
*
* The `ClutterClipNodeClass` structure is an opaque
* type whose members cannot be directly accessed.
*
* Since: 1.10
*/
struct _ClutterClipNodeClass
{
ClutterPaintNodeClass parent_class;
};
G_DEFINE_TYPE (ClutterClipNode, clutter_clip_node, CLUTTER_TYPE_PAINT_NODE)
static gboolean
clutter_clip_node_pre_draw (ClutterPaintNode *node,
ClutterPaintContext *paint_context)
{
gboolean retval = FALSE;
CoglFramebuffer *fb;
guint i;
if (node->operations == NULL)
return FALSE;
fb = get_target_framebuffer (node, paint_context);
for (i = 0; i < node->operations->len; i++)
{
const ClutterPaintOperation *op;
op = &g_array_index (node->operations, ClutterPaintOperation, i);
switch (op->opcode)
{
case PAINT_OP_TEX_RECT:
cogl_framebuffer_push_rectangle_clip (fb,
op->op.texrect[0],
op->op.texrect[1],
op->op.texrect[2],
op->op.texrect[3]);
retval = TRUE;
break;
case PAINT_OP_MULTITEX_RECT:
case PAINT_OP_PRIMITIVE:
case PAINT_OP_INVALID:
break;
}
}
return retval;
}
static void
clutter_clip_node_post_draw (ClutterPaintNode *node,
ClutterPaintContext *paint_context)
{
CoglFramebuffer *fb;
guint i;
if (node->operations == NULL)
return;
fb = get_target_framebuffer (node, paint_context);
for (i = 0; i < node->operations->len; i++)
{
const ClutterPaintOperation *op;
op = &g_array_index (node->operations, ClutterPaintOperation, i);
switch (op->opcode)
{
case PAINT_OP_TEX_RECT:
cogl_framebuffer_pop_clip (fb);
break;
case PAINT_OP_MULTITEX_RECT:
case PAINT_OP_PRIMITIVE:
case PAINT_OP_INVALID:
break;
}
}
}
static void
clutter_clip_node_class_init (ClutterClipNodeClass *klass)
{
ClutterPaintNodeClass *node_class;
node_class = CLUTTER_PAINT_NODE_CLASS (klass);
node_class->pre_draw = clutter_clip_node_pre_draw;
node_class->post_draw = clutter_clip_node_post_draw;
}
static void
clutter_clip_node_init (ClutterClipNode *self)
{
}
/**
* clutter_clip_node_new:
*
* Creates a new #ClutterPaintNode that will clip its child
* nodes to the 2D regions added to it.
*
* Return value: (transfer full): the newly created #ClutterPaintNode.
* Use clutter_paint_node_unref() when done.
*
* Since: 1.10
*/
ClutterPaintNode *
clutter_clip_node_new (void)
{
return _clutter_paint_node_create (CLUTTER_TYPE_CLIP_NODE);
}
/*
* ClutterActorNode
*/
struct _ClutterActorNode
{
ClutterPaintNode parent_instance;
ClutterActor *actor;
int opacity_override;
int saved_opacity_override;
};
struct _ClutterActorNodeClass
{
ClutterPaintNodeClass parent_class;
};
G_DEFINE_TYPE (ClutterActorNode, clutter_actor_node, CLUTTER_TYPE_PAINT_NODE)
static gboolean
clutter_actor_node_pre_draw (ClutterPaintNode *node,
ClutterPaintContext *paint_context)
{
ClutterActorNode *actor_node = CLUTTER_ACTOR_NODE (node);
if (actor_node->opacity_override != -1)
{
actor_node->saved_opacity_override =
clutter_actor_get_opacity_override (actor_node->actor);
clutter_actor_set_opacity_override (actor_node->actor,
actor_node->opacity_override);
}
CLUTTER_SET_PRIVATE_FLAGS (actor_node->actor, CLUTTER_IN_PAINT);
return TRUE;
}
static void
clutter_actor_node_draw (ClutterPaintNode *node,
ClutterPaintContext *paint_context)
{
ClutterActorNode *actor_node = CLUTTER_ACTOR_NODE (node);
clutter_actor_continue_paint (actor_node->actor, paint_context);
}
static void
clutter_actor_node_post_draw (ClutterPaintNode *node,
ClutterPaintContext *paint_context)
{
ClutterActorNode *actor_node = CLUTTER_ACTOR_NODE (node);
CLUTTER_UNSET_PRIVATE_FLAGS (actor_node->actor, CLUTTER_IN_PAINT);
if (actor_node->opacity_override != -1)
{
clutter_actor_set_opacity_override (actor_node->actor,
actor_node->saved_opacity_override);
}
}
static JsonNode *
clutter_actor_node_serialize (ClutterPaintNode *node)
{
ClutterActorNode *actor_node = CLUTTER_ACTOR_NODE (node);
g_autoptr (JsonBuilder) builder = NULL;
const char *debug_name;
debug_name = _clutter_actor_get_debug_name (actor_node->actor);
builder = json_builder_new ();
json_builder_begin_object (builder);
json_builder_set_member_name (builder, "actor");
json_builder_add_string_value (builder, debug_name);
json_builder_end_object (builder);
return json_builder_get_root (builder);
}
static void
clutter_actor_node_class_init (ClutterActorNodeClass *klass)
{
ClutterPaintNodeClass *node_class;
node_class = CLUTTER_PAINT_NODE_CLASS (klass);
node_class->pre_draw = clutter_actor_node_pre_draw;
node_class->draw = clutter_actor_node_draw;
node_class->post_draw = clutter_actor_node_post_draw;
node_class->serialize = clutter_actor_node_serialize;
}
static void
clutter_actor_node_init (ClutterActorNode *self)
{
}
/*
* clutter_actor_node_new:
* @actor: the actor to paint
* @opacity: opacity to draw the actor with, or -1 to use the actor's opacity
*
* Creates a new #ClutterActorNode.
*
* The actor is painted together with any effects
* applied to it. Children of this node will draw
* over the actor contents.
*
* Return value: (transfer full): the newly created #ClutterActorNode.
* Use clutter_paint_node_unref() when done.
*/
ClutterPaintNode *
clutter_actor_node_new (ClutterActor *actor,
int opacity)
{
ClutterActorNode *res;
g_assert (actor != NULL);
res = _clutter_paint_node_create (CLUTTER_TYPE_ACTOR_NODE);
res->actor = actor;
res->opacity_override = CLAMP (opacity, -1, 255);
return (ClutterPaintNode *) res;
}
/*
* ClutterEffectNode
*/
struct _ClutterEffectNode
{
ClutterPaintNode parent_instance;
ClutterEffect *effect;
};
struct _ClutterEffectNodeClass
{
ClutterPaintNodeClass parent_class;
};
G_DEFINE_TYPE (ClutterEffectNode, clutter_effect_node, CLUTTER_TYPE_PAINT_NODE)
static JsonNode *
clutter_effect_node_serialize (ClutterPaintNode *node)
{
ClutterEffectNode *effect_node = CLUTTER_EFFECT_NODE (node);
ClutterActorMeta *effect_meta = CLUTTER_ACTOR_META (effect_node->effect);
g_autoptr (JsonBuilder) builder = NULL;
g_autoptr (GString) string = NULL;
const char *meta_name;
meta_name = clutter_actor_meta_get_name (effect_meta);
string = g_string_new (NULL);
g_string_append (string, G_OBJECT_TYPE_NAME (effect_node->effect));
g_string_append (string, " (");
if (meta_name)
g_string_append_printf (string, "\"%s\"", meta_name);
else
g_string_append (string, "unnamed");
g_string_append (string, ")");
builder = json_builder_new ();
json_builder_begin_object (builder);
json_builder_set_member_name (builder, "effect");
json_builder_add_string_value (builder, string->str);
json_builder_end_object (builder);
return json_builder_get_root (builder);
}
static void
clutter_effect_node_class_init (ClutterEffectNodeClass *klass)
{
ClutterPaintNodeClass *node_class;
node_class = CLUTTER_PAINT_NODE_CLASS (klass);
node_class->serialize = clutter_effect_node_serialize;
}
static void
clutter_effect_node_init (ClutterEffectNode *self)
{
}
/**
* clutter_effect_node_new:
* @effect: the actor to paint
*
* Creates a new #ClutterEffectNode.
*
* Return value: (transfer full): the newly created #ClutterEffectNode.
* Use clutter_paint_node_unref() when done.
*/
ClutterPaintNode *
clutter_effect_node_new (ClutterEffect *effect)
{
ClutterEffectNode *res;
g_assert (CLUTTER_IS_EFFECT (effect));
res = _clutter_paint_node_create (CLUTTER_TYPE_EFFECT_NODE);
res->effect = effect;
return (ClutterPaintNode *) res;
}
/*
* ClutterLayerNode
*/
struct _ClutterLayerNode
{
ClutterPaintNode parent_instance;
cairo_rectangle_t viewport;
graphene_matrix_t projection;
float fbo_width;
float fbo_height;
CoglPipeline *pipeline;
CoglFramebuffer *offscreen;
guint8 opacity;
gboolean needs_fbo_setup : 1;
};
struct _ClutterLayerNodeClass
{
ClutterPaintNodeClass parent_class;
};
G_DEFINE_TYPE (ClutterLayerNode, clutter_layer_node, CLUTTER_TYPE_PAINT_NODE)
static gboolean
clutter_layer_node_pre_draw (ClutterPaintNode *node,
ClutterPaintContext *paint_context)
{
ClutterLayerNode *lnode = (ClutterLayerNode *) node;
CoglFramebuffer *framebuffer;
graphene_matrix_t matrix;
/* if we were unable to create an offscreen buffer for this node, then
* we simply ignore it
*/
if (lnode->offscreen == NULL)
return FALSE;
if (lnode->needs_fbo_setup)
{
/* copy the same modelview from the current framebuffer to the one we
* are going to use
*/
framebuffer = clutter_paint_context_get_framebuffer (paint_context);
cogl_framebuffer_get_modelview_matrix (framebuffer, &matrix);
cogl_framebuffer_set_modelview_matrix (lnode->offscreen, &matrix);
cogl_framebuffer_set_viewport (lnode->offscreen,
lnode->viewport.x,
lnode->viewport.y,
lnode->viewport.width,
lnode->viewport.height);
cogl_framebuffer_set_projection_matrix (lnode->offscreen,
&lnode->projection);
}
clutter_paint_context_push_framebuffer (paint_context, lnode->offscreen);
/* clear out the target framebuffer */
cogl_framebuffer_clear4f (lnode->offscreen,
COGL_BUFFER_BIT_COLOR | COGL_BUFFER_BIT_DEPTH,
0.f, 0.f, 0.f, 0.f);
cogl_framebuffer_push_matrix (lnode->offscreen);
/* every draw operation after this point will happen an offscreen
* framebuffer
*/
return TRUE;
}
static void
clutter_layer_node_post_draw (ClutterPaintNode *node,
ClutterPaintContext *paint_context)
{
ClutterLayerNode *lnode = CLUTTER_LAYER_NODE (node);
CoglFramebuffer *fb;
guint i;
/* switch to the previous framebuffer */
cogl_framebuffer_pop_matrix (lnode->offscreen);
clutter_paint_context_pop_framebuffer (paint_context);
if (!node->operations)
return;
fb = clutter_paint_context_get_framebuffer (paint_context);
for (i = 0; i < node->operations->len; i++)
{
const ClutterPaintOperation *op;
op = &g_array_index (node->operations, ClutterPaintOperation, i);
switch (op->opcode)
{
case PAINT_OP_INVALID:
break;
case PAINT_OP_TEX_RECT:
/* now we need to paint the texture */
cogl_framebuffer_draw_textured_rectangle (fb,
lnode->pipeline,
op->op.texrect[0],
op->op.texrect[1],
op->op.texrect[2],
op->op.texrect[3],
op->op.texrect[4],
op->op.texrect[5],
op->op.texrect[6],
op->op.texrect[7]);
break;
case PAINT_OP_MULTITEX_RECT:
cogl_framebuffer_draw_multitextured_rectangle (fb,
lnode->pipeline,
op->op.texrect[0],
op->op.texrect[1],
op->op.texrect[2],
op->op.texrect[3],
(float*) op->multitex_coords->data,
op->multitex_coords->len);
break;
case PAINT_OP_PRIMITIVE:
cogl_framebuffer_draw_primitive (fb,
lnode->pipeline,
op->op.primitive);
break;
}
}
}
static void
clutter_layer_node_finalize (ClutterPaintNode *node)
{
ClutterLayerNode *lnode = CLUTTER_LAYER_NODE (node);
if (lnode->pipeline != NULL)
cogl_object_unref (lnode->pipeline);
g_clear_object (&lnode->offscreen);
CLUTTER_PAINT_NODE_CLASS (clutter_layer_node_parent_class)->finalize (node);
}
static JsonNode *
clutter_layer_node_serialize (ClutterPaintNode *node)
{
ClutterLayerNode *layer_node = CLUTTER_LAYER_NODE (node);
g_autoptr (JsonBuilder) builder = NULL;
g_autofree char *framebuffer_ptr = NULL;
builder = json_builder_new ();
framebuffer_ptr = g_strdup_printf ("%p", layer_node->offscreen);
json_builder_begin_object (builder);
json_builder_set_member_name (builder, "framebuffer");
json_builder_add_string_value (builder, framebuffer_ptr);
json_builder_end_object (builder);
return json_builder_get_root (builder);
}
static void
clutter_layer_node_class_init (ClutterLayerNodeClass *klass)
{
ClutterPaintNodeClass *node_class;
node_class = CLUTTER_PAINT_NODE_CLASS (klass);
node_class->pre_draw = clutter_layer_node_pre_draw;
node_class->post_draw = clutter_layer_node_post_draw;
node_class->finalize = clutter_layer_node_finalize;
node_class->serialize = clutter_layer_node_serialize;
}
static void
clutter_layer_node_init (ClutterLayerNode *self)
{
graphene_matrix_init_identity (&self->projection);
}
/*
* clutter_layer_node_new:
* @projection: the projection matrix to use to set up the layer
* @viewport: (type cairo.Rectangle): the viewport to use to set up the layer
* @width: the width of the layer
* @height: the height of the layer
* @opacity: the opacity to be used when drawing the layer
*
* Creates a new #ClutterLayerNode.
*
* All children of this node will be painted inside a separate
* framebuffer; the framebuffer will then be painted using the
* given @opacity.
*
* Return value: (transfer full): the newly created #ClutterLayerNode.
* Use clutter_paint_node_unref() when done.
*
* Since: 1.10
*/
ClutterPaintNode *
clutter_layer_node_new (const graphene_matrix_t *projection,
const cairo_rectangle_t *viewport,
float width,
float height,
guint8 opacity)
{
ClutterLayerNode *res;
CoglContext *context;
CoglTexture2D *tex_2d;
CoglTexture *texture;
CoglColor color;
CoglOffscreen *offscreen;
g_autoptr (GError) error = NULL;
res = _clutter_paint_node_create (CLUTTER_TYPE_LAYER_NODE);
res->needs_fbo_setup = TRUE;
res->projection = *projection;
res->viewport = *viewport;
res->fbo_width = width;
res->fbo_height = height;
res->opacity = opacity;
/* the texture backing the FBO */
context = clutter_backend_get_cogl_context (clutter_get_default_backend ());
tex_2d = cogl_texture_2d_new_with_size (context,
MAX (res->fbo_width, 1),
MAX (res->fbo_height, 1));
texture = COGL_TEXTURE (tex_2d);
cogl_texture_set_premultiplied (texture, TRUE);
offscreen = cogl_offscreen_new_with_texture (texture);
if (!cogl_framebuffer_allocate (COGL_FRAMEBUFFER (offscreen), &error))
{
g_warning ("Unable to create an allocate paint node offscreen: %s",
error->message);
g_object_unref (offscreen);
goto out;
}
res->offscreen = COGL_FRAMEBUFFER (offscreen);
cogl_color_init_from_4ub (&color, opacity, opacity, opacity, opacity);
/* the pipeline used to paint the texture; we use nearest
* interpolation filters because the texture is always
* going to be painted at a 1:1 texel:pixel ratio
*/
res->pipeline = cogl_pipeline_copy (default_texture_pipeline);
cogl_pipeline_set_layer_filters (res->pipeline, 0,
COGL_PIPELINE_FILTER_NEAREST,
COGL_PIPELINE_FILTER_NEAREST);
cogl_pipeline_set_layer_texture (res->pipeline, 0, texture);
cogl_pipeline_set_color (res->pipeline, &color);
out:
cogl_object_unref (texture);
return (ClutterPaintNode *) res;
}
/**
* clutter_layer_node_new_to_framebuffer:
* @framebuffer: a #CoglFramebuffer
* @pipeline: a #CoglPipeline
*
* Creates a new #ClutterLayerNode that will redirect drawing at
* @framebuffer. It will then use @pipeline to paint the stored
* operations.
*
* When using this constructor, the caller is reponsible for setting
* up @framebuffer, including its modelview and projection matrices,
* and the viewport, and the @pipeline as well.
*
* Return value: (transfer full): the newly created #ClutterLayerNode.
* Use clutter_paint_node_unref() when done.
*/
ClutterPaintNode *
clutter_layer_node_new_to_framebuffer (CoglFramebuffer *framebuffer,
CoglPipeline *pipeline)
{
ClutterLayerNode *res;
g_return_val_if_fail (COGL_IS_FRAMEBUFFER (framebuffer), NULL);
g_return_val_if_fail (cogl_is_pipeline (pipeline), NULL);
res = _clutter_paint_node_create (CLUTTER_TYPE_LAYER_NODE);
res->needs_fbo_setup = FALSE;
res->fbo_width = cogl_framebuffer_get_width (framebuffer);
res->fbo_height = cogl_framebuffer_get_height (framebuffer);
res->offscreen = g_object_ref (framebuffer);
res->pipeline = cogl_pipeline_copy (pipeline);
return (ClutterPaintNode *) res;
}
/*
* ClutterBlitNode
*/
struct _ClutterBlitNode
{
ClutterPaintNode parent_instance;
CoglFramebuffer *src;
};
G_DEFINE_TYPE (ClutterBlitNode, clutter_blit_node, CLUTTER_TYPE_PAINT_NODE)
static gboolean
clutter_blit_node_pre_draw (ClutterPaintNode *node,
ClutterPaintContext *paint_context)
{
return TRUE;
}
static void
clutter_blit_node_draw (ClutterPaintNode *node,
ClutterPaintContext *paint_context)
{
ClutterBlitNode *blit_node = CLUTTER_BLIT_NODE (node);
g_autoptr (GError) error = NULL;
CoglFramebuffer *framebuffer;
unsigned int i;
if (node->operations == NULL)
return;
framebuffer = get_target_framebuffer (node, paint_context);
for (i = 0; i < node->operations->len; i++)
{
const ClutterPaintOperation *op;
float op_width, op_height;
op = &g_array_index (node->operations, ClutterPaintOperation, i);
switch (op->opcode)
{
case PAINT_OP_INVALID:
break;
case PAINT_OP_TEX_RECT:
op_width = op->op.texrect[6] - op->op.texrect[4];
op_height = op->op.texrect[7] - op->op.texrect[5];
cogl_blit_framebuffer (blit_node->src,
framebuffer,
op->op.texrect[0],
op->op.texrect[1],
op->op.texrect[4],
op->op.texrect[5],
op_width,
op_height,
&error);
if (error)
{
g_warning ("Error blitting framebuffers: %s", error->message);
return;
}
break;
case PAINT_OP_MULTITEX_RECT:
case PAINT_OP_PRIMITIVE:
break;
}
}
}
static void
clutter_blit_node_finalize (ClutterPaintNode *node)
{
ClutterBlitNode *blit_node = CLUTTER_BLIT_NODE (node);
g_clear_object (&blit_node->src);
CLUTTER_PAINT_NODE_CLASS (clutter_blit_node_parent_class)->finalize (node);
}
static JsonNode *
clutter_blit_node_serialize (ClutterPaintNode *node)
{
ClutterBlitNode *blit_node = CLUTTER_BLIT_NODE (node);
g_autoptr (JsonBuilder) builder = NULL;
g_autofree char *src_ptr = NULL;
src_ptr = g_strdup_printf ("%p", blit_node->src);
builder = json_builder_new ();
json_builder_begin_object (builder);
json_builder_set_member_name (builder, "source");
json_builder_add_string_value (builder, src_ptr);
json_builder_end_object (builder);
return json_builder_get_root (builder);
}
static void
clutter_blit_node_class_init (ClutterBlitNodeClass *klass)
{
ClutterPaintNodeClass *node_class;
node_class = CLUTTER_PAINT_NODE_CLASS (klass);
node_class->pre_draw = clutter_blit_node_pre_draw;
node_class->draw = clutter_blit_node_draw;
node_class->finalize = clutter_blit_node_finalize;
node_class->serialize = clutter_blit_node_serialize;
}
static void
clutter_blit_node_init (ClutterBlitNode *self)
{
}
/**
* clutter_blit_node_new:
* @src: the source #CoglFramebuffer
*
* Creates a new #ClutterBlitNode that blits @src into the current
* draw framebuffer.
*
* You must only add rectangles using clutter_blit_node_add_blit_rectangle().
*
* Return value: (transfer full): the newly created #ClutterBlitNode.
* Use clutter_paint_node_unref() when done.
*/
ClutterPaintNode *
clutter_blit_node_new (CoglFramebuffer *src)
{
ClutterBlitNode *res;
g_return_val_if_fail (COGL_IS_FRAMEBUFFER (src), NULL);
res = _clutter_paint_node_create (CLUTTER_TYPE_BLIT_NODE);
res->src = g_object_ref (src);
return (ClutterPaintNode *) res;
}
/**
* clutter_blit_node_add_blit_rectangle:
* @blit_node: a #ClutterBlitNode
* @src_x: Source x position
* @src_y: Source y position
* @dst_x: Destination x position
* @dst_y: Destination y position
* @width: Width of region to copy
* @height: Height of region to copy
*
* Adds a new blit rectangle to the stack of rectangles. All the
* constraints of cogl_blit_framebuffer() apply here.
*/
void
clutter_blit_node_add_blit_rectangle (ClutterBlitNode *blit_node,
int src_x,
int src_y,
int dst_x,
int dst_y,
int width,
int height)
{
g_return_if_fail (CLUTTER_IS_BLIT_NODE (blit_node));
clutter_paint_node_add_texture_rectangle (CLUTTER_PAINT_NODE (blit_node),
&(ClutterActorBox) {
.x1 = src_x,
.y1 = src_y,
.x2 = src_x + width,
.y2 = src_y + height,
},
dst_x,
dst_y,
dst_x + width,
dst_y + height);
}
/*
* ClutterBlurNode
*/
struct _ClutterBlurNode
{
ClutterLayerNode parent_instance;
ClutterBlur *blur;
unsigned int sigma;
};
G_DEFINE_TYPE (ClutterBlurNode, clutter_blur_node, CLUTTER_TYPE_LAYER_NODE)
static void
clutter_blur_node_post_draw (ClutterPaintNode *node,
ClutterPaintContext *paint_context)
{
ClutterPaintNodeClass *parent_class =
CLUTTER_PAINT_NODE_CLASS (clutter_blur_node_parent_class);
ClutterBlurNode *blur_node = CLUTTER_BLUR_NODE (node);
clutter_blur_apply (blur_node->blur);
parent_class->post_draw (node, paint_context);
}
static void
clutter_blur_node_finalize (ClutterPaintNode *node)
{
ClutterBlurNode *blur_node = CLUTTER_BLUR_NODE (node);
g_clear_pointer (&blur_node->blur, clutter_blur_free);
CLUTTER_PAINT_NODE_CLASS (clutter_blur_node_parent_class)->finalize (node);
}
static JsonNode *
clutter_blur_node_serialize (ClutterPaintNode *node)
{
ClutterBlurNode *blur_node = CLUTTER_BLUR_NODE (node);
g_autoptr (JsonBuilder) builder = NULL;
g_autofree char *src_ptr = NULL;
src_ptr = g_strdup_printf ("%d", blur_node->sigma);
builder = json_builder_new ();
json_builder_begin_object (builder);
json_builder_set_member_name (builder, "sigma");
json_builder_add_string_value (builder, src_ptr);
json_builder_end_object (builder);
return json_builder_get_root (builder);
}
static void
clutter_blur_node_class_init (ClutterBlurNodeClass *klass)
{
ClutterPaintNodeClass *node_class;
node_class = CLUTTER_PAINT_NODE_CLASS (klass);
node_class->post_draw = clutter_blur_node_post_draw;
node_class->finalize = clutter_blur_node_finalize;
node_class->serialize = clutter_blur_node_serialize;
}
static void
clutter_blur_node_init (ClutterBlurNode *blur_node)
{
}
/**
* clutter_blur_node_new:
* @width width of the blur layer
* @height: height of the blur layer
* @sigma: sigma value of the blur
*
* Creates a new #ClutterBlurNode.
*
* Children of this node will be painted inside a separate framebuffer,
* which will be blurred and painted on the current draw framebuffer.
*
* Return value: (transfer full): the newly created #ClutterBlurNode.
* Use clutter_paint_node_unref() when done.
*/
ClutterPaintNode *
clutter_blur_node_new (unsigned int width,
unsigned int height,
unsigned int sigma)
{
g_autoptr (CoglOffscreen) offscreen = NULL;
g_autoptr (GError) error = NULL;
graphene_matrix_t projection;
ClutterLayerNode *layer_node;
ClutterBlurNode *blur_node;
CoglTexture2D *tex_2d;
CoglContext *context;
CoglTexture *texture;
ClutterBlur *blur;
blur_node = _clutter_paint_node_create (CLUTTER_TYPE_BLUR_NODE);
blur_node->sigma = sigma;
context = clutter_backend_get_cogl_context (clutter_get_default_backend ());
tex_2d = cogl_texture_2d_new_with_size (context, width, height);
texture = COGL_TEXTURE (tex_2d);
cogl_texture_set_premultiplied (texture, TRUE);
offscreen = cogl_offscreen_new_with_texture (texture);
cogl_object_unref (tex_2d);
if (!cogl_framebuffer_allocate (COGL_FRAMEBUFFER (offscreen), &error))
{
g_warning ("Unable to allocate paint node offscreen: %s",
error->message);
goto out;
}
blur = clutter_blur_new (texture, sigma);
blur_node->blur = blur;
if (!blur)
{
g_warning ("Failed to create blur pipeline");
goto out;
}
layer_node = CLUTTER_LAYER_NODE (blur_node);
layer_node->offscreen = COGL_FRAMEBUFFER (g_steal_pointer (&offscreen));
layer_node->pipeline = cogl_pipeline_copy (default_texture_pipeline);
cogl_pipeline_set_layer_filters (layer_node->pipeline, 0,
COGL_PIPELINE_FILTER_LINEAR,
COGL_PIPELINE_FILTER_LINEAR);
cogl_pipeline_set_layer_texture (layer_node->pipeline,
0,
clutter_blur_get_texture (blur));
graphene_matrix_init_translate (&projection,
&GRAPHENE_POINT3D_INIT (-(width / 2.f),
-(height / 2.f),
0.f));
graphene_matrix_scale (&projection, 2.f / width, -2.f / height, 1.f);
cogl_framebuffer_set_projection_matrix (layer_node->offscreen, &projection);
out:
return (ClutterPaintNode *) blur_node;
}