/* * 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 . * * Author: * Emmanuele Bassi */ /** * 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 #include #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_TEX_RECTS: cogl_framebuffer_draw_textured_rectangles (fb, pnode->pipeline, (float *) op->coords->data, op->coords->len / 8); 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->coords->data, op->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_TEX_RECTS: 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_TEX_RECTS: 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_TEX_RECTS: 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_TEX_RECTS: cogl_framebuffer_draw_textured_rectangles (fb, lnode->pipeline, (float *) op->coords->data, op->coords->len / 8); 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->coords->data, op->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_TEX_RECTS: 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, float sigma) { g_autoptr (CoglOffscreen) offscreen = NULL; g_autoptr (GError) error = NULL; ClutterLayerNode *layer_node; ClutterBlurNode *blur_node; CoglTexture2D *tex_2d; CoglContext *context; CoglTexture *texture; ClutterBlur *blur; g_return_val_if_fail (sigma >= 0.0, NULL); 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)); cogl_framebuffer_orthographic (layer_node->offscreen, 0.0, 0.0, width, height, 0.0, 1.0); out: return (ClutterPaintNode *) blur_node; }