6654053250
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>
1958 lines
53 KiB
C
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;
|
|
}
|