mirror of
https://github.com/brl/mutter.git
synced 2024-11-22 08:00:42 -05:00
1912f28063
We use markdown for the documentation.
1300 lines
33 KiB
C
1300 lines
33 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.
|
|
*/
|
|
|
|
#ifdef HAVE_CONFIG_H
|
|
#include "config.h"
|
|
#endif
|
|
|
|
#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-color.h"
|
|
#include "clutter-debug.h"
|
|
#include "clutter-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_TEXTURE_TYPE_2D);
|
|
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, private
|
|
*
|
|
* 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
|
|
|
|
typedef struct _ClutterRootNode ClutterRootNode;
|
|
typedef struct _ClutterPaintNodeClass ClutterRootNodeClass;
|
|
|
|
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)
|
|
{
|
|
ClutterRootNode *rnode = (ClutterRootNode *) node;
|
|
|
|
cogl_framebuffer_clear (rnode->framebuffer,
|
|
rnode->clear_flags,
|
|
&rnode->clear_color);
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
static void
|
|
clutter_root_node_post_draw (ClutterPaintNode *node)
|
|
{
|
|
}
|
|
|
|
static void
|
|
clutter_root_node_finalize (ClutterPaintNode *node)
|
|
{
|
|
ClutterRootNode *rnode = (ClutterRootNode *) node;
|
|
|
|
cogl_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;
|
|
|
|
res = _clutter_paint_node_create (_clutter_root_node_get_type ());
|
|
|
|
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);
|
|
|
|
if (G_LIKELY (framebuffer != NULL))
|
|
res->framebuffer = cogl_object_ref (framebuffer);
|
|
else
|
|
res->framebuffer = cogl_object_ref (cogl_get_draw_framebuffer ());
|
|
|
|
res->clear_flags = clear_flags;
|
|
|
|
return (ClutterPaintNode *) res;
|
|
}
|
|
|
|
/*
|
|
* Transform node
|
|
*
|
|
* A private PaintNode, that changes the modelview of its child
|
|
* nodes.
|
|
*/
|
|
|
|
#define clutter_transform_node_get_type _clutter_transform_node_get_type
|
|
|
|
typedef struct _ClutterTransformNode {
|
|
ClutterPaintNode parent_instance;
|
|
|
|
CoglMatrix modelview;
|
|
} ClutterTransformNode;
|
|
|
|
typedef struct _ClutterPaintNodeClass ClutterTransformNodeClass;
|
|
|
|
G_DEFINE_TYPE (ClutterTransformNode, clutter_transform_node, CLUTTER_TYPE_PAINT_NODE)
|
|
|
|
static gboolean
|
|
clutter_transform_node_pre_draw (ClutterPaintNode *node)
|
|
{
|
|
ClutterTransformNode *tnode = (ClutterTransformNode *) node;
|
|
CoglMatrix matrix;
|
|
|
|
cogl_push_matrix ();
|
|
|
|
cogl_get_modelview_matrix (&matrix);
|
|
cogl_matrix_multiply (&matrix, &matrix, &tnode->modelview);
|
|
cogl_set_modelview_matrix (&matrix);
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
static void
|
|
clutter_transform_node_post_draw (ClutterPaintNode *node)
|
|
{
|
|
cogl_pop_matrix ();
|
|
}
|
|
|
|
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)
|
|
{
|
|
cogl_matrix_init_identity (&self->modelview);
|
|
}
|
|
|
|
ClutterPaintNode *
|
|
_clutter_transform_node_new (const CoglMatrix *modelview)
|
|
{
|
|
ClutterTransformNode *res;
|
|
|
|
res = _clutter_paint_node_create (_clutter_transform_node_get_type ());
|
|
|
|
if (modelview != NULL)
|
|
res->modelview = *modelview;
|
|
|
|
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)
|
|
{
|
|
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_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;
|
|
}
|
|
|
|
static void
|
|
clutter_dummy_node_init (ClutterDummyNode *self)
|
|
{
|
|
}
|
|
|
|
ClutterPaintNode *
|
|
_clutter_dummy_node_new (ClutterActor *actor)
|
|
{
|
|
ClutterPaintNode *res;
|
|
ClutterDummyNode *dnode;
|
|
|
|
res = _clutter_paint_node_create (_clutter_dummy_node_get_type ());
|
|
|
|
dnode = (ClutterDummyNode *) res;
|
|
dnode->actor = actor;
|
|
dnode->framebuffer = _clutter_actor_get_active_framebuffer (actor);
|
|
|
|
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)
|
|
{
|
|
ClutterPipelineNode *pnode = CLUTTER_PIPELINE_NODE (node);
|
|
|
|
if (node->operations != NULL &&
|
|
pnode->pipeline != NULL)
|
|
{
|
|
cogl_push_source (pnode->pipeline);
|
|
return TRUE;
|
|
}
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
static void
|
|
clutter_pipeline_node_draw (ClutterPaintNode *node)
|
|
{
|
|
ClutterPipelineNode *pnode = CLUTTER_PIPELINE_NODE (node);
|
|
CoglFramebuffer *fb;
|
|
guint i;
|
|
|
|
if (pnode->pipeline == NULL)
|
|
return;
|
|
|
|
if (node->operations == NULL)
|
|
return;
|
|
|
|
fb = clutter_paint_node_get_framebuffer (node);
|
|
|
|
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_rectangle_with_texture_coords (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_PATH:
|
|
cogl_path_fill (op->op.path);
|
|
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)
|
|
{
|
|
cogl_pop_source ();
|
|
}
|
|
|
|
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)
|
|
{
|
|
ClutterTextNode *tnode = CLUTTER_TEXT_NODE (node);
|
|
|
|
return tnode->layout != NULL;
|
|
}
|
|
|
|
static void
|
|
clutter_text_node_draw (ClutterPaintNode *node)
|
|
{
|
|
ClutterTextNode *tnode = CLUTTER_TEXT_NODE (node);
|
|
PangoRectangle extents;
|
|
CoglFramebuffer *fb;
|
|
guint i;
|
|
|
|
if (node->operations == NULL)
|
|
return;
|
|
|
|
fb = clutter_paint_node_get_framebuffer (node);
|
|
|
|
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_render_layout (tnode->layout,
|
|
op->op.texrect[0],
|
|
op->op.texrect[1],
|
|
&tnode->color,
|
|
0);
|
|
|
|
if (clipped)
|
|
cogl_framebuffer_pop_clip (fb);
|
|
break;
|
|
|
|
case PAINT_OP_PATH:
|
|
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)
|
|
{
|
|
gboolean retval = FALSE;
|
|
CoglFramebuffer *fb;
|
|
guint i;
|
|
|
|
if (node->operations == NULL)
|
|
return FALSE;
|
|
|
|
fb = clutter_paint_node_get_framebuffer (node);
|
|
|
|
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_PATH:
|
|
cogl_framebuffer_push_path_clip (fb, op->op.path);
|
|
retval = TRUE;
|
|
break;
|
|
|
|
case PAINT_OP_PRIMITIVE:
|
|
case PAINT_OP_INVALID:
|
|
break;
|
|
}
|
|
}
|
|
|
|
return retval;
|
|
}
|
|
|
|
static void
|
|
clutter_clip_node_post_draw (ClutterPaintNode *node)
|
|
{
|
|
CoglFramebuffer *fb;
|
|
guint i;
|
|
|
|
if (node->operations == NULL)
|
|
return;
|
|
|
|
fb = clutter_paint_node_get_framebuffer (node);
|
|
|
|
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_PATH:
|
|
case PAINT_OP_TEX_RECT:
|
|
cogl_framebuffer_pop_clip (fb);
|
|
break;
|
|
|
|
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);
|
|
}
|
|
|
|
/*
|
|
* ClutterLayerNode (private)
|
|
*/
|
|
|
|
#define clutter_layer_node_get_type _clutter_layer_node_get_type
|
|
|
|
struct _ClutterLayerNode
|
|
{
|
|
ClutterPaintNode parent_instance;
|
|
|
|
cairo_rectangle_t viewport;
|
|
|
|
CoglMatrix projection;
|
|
|
|
float fbo_width;
|
|
float fbo_height;
|
|
|
|
CoglPipeline *state;
|
|
CoglFramebuffer *offscreen;
|
|
CoglTexture *texture;
|
|
|
|
guint8 opacity;
|
|
};
|
|
|
|
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)
|
|
{
|
|
ClutterLayerNode *lnode = (ClutterLayerNode *) node;
|
|
CoglMatrix 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 no geometry was submitted for this node then we simply ignore it */
|
|
if (node->operations == NULL)
|
|
return FALSE;
|
|
|
|
/* copy the same modelview from the current framebuffer to the one we
|
|
* are going to use
|
|
*/
|
|
cogl_get_modelview_matrix (&matrix);
|
|
|
|
cogl_push_framebuffer (lnode->offscreen);
|
|
|
|
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);
|
|
|
|
/* 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_push_matrix ();
|
|
|
|
/* every draw operation after this point will happen an offscreen
|
|
* framebuffer
|
|
*/
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
static void
|
|
clutter_layer_node_post_draw (ClutterPaintNode *node)
|
|
{
|
|
ClutterLayerNode *lnode = CLUTTER_LAYER_NODE (node);
|
|
CoglFramebuffer *fb;
|
|
guint i;
|
|
|
|
/* switch to the previous framebuffer */
|
|
cogl_pop_matrix ();
|
|
cogl_pop_framebuffer ();
|
|
|
|
fb = cogl_get_draw_framebuffer ();
|
|
|
|
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_push_source (lnode->state);
|
|
cogl_rectangle_with_texture_coords (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]);
|
|
cogl_pop_source ();
|
|
break;
|
|
|
|
case PAINT_OP_PATH:
|
|
cogl_push_source (lnode->state);
|
|
cogl_path_fill (op->op.path);
|
|
cogl_pop_source ();
|
|
break;
|
|
|
|
case PAINT_OP_PRIMITIVE:
|
|
cogl_framebuffer_draw_primitive (fb, lnode->state, op->op.primitive);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
static void
|
|
clutter_layer_node_finalize (ClutterPaintNode *node)
|
|
{
|
|
ClutterLayerNode *lnode = CLUTTER_LAYER_NODE (node);
|
|
|
|
if (lnode->state != NULL)
|
|
cogl_object_unref (lnode->state);
|
|
|
|
if (lnode->offscreen != NULL)
|
|
cogl_object_unref (lnode->offscreen);
|
|
|
|
CLUTTER_PAINT_NODE_CLASS (clutter_layer_node_parent_class)->finalize (node);
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
static void
|
|
clutter_layer_node_init (ClutterLayerNode *self)
|
|
{
|
|
cogl_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 CoglMatrix *projection,
|
|
const cairo_rectangle_t *viewport,
|
|
float width,
|
|
float height,
|
|
guint8 opacity)
|
|
{
|
|
ClutterLayerNode *res;
|
|
CoglColor color;
|
|
|
|
res = _clutter_paint_node_create (CLUTTER_TYPE_LAYER_NODE);
|
|
|
|
res->projection = *projection;
|
|
res->viewport = *viewport;
|
|
res->fbo_width = width;
|
|
res->fbo_height = height;
|
|
res->opacity = opacity;
|
|
|
|
/* the texture backing the FBO */
|
|
res->texture = cogl_texture_new_with_size (MAX (res->fbo_width, 1),
|
|
MAX (res->fbo_height, 1),
|
|
COGL_TEXTURE_NO_SLICING,
|
|
COGL_PIXEL_FORMAT_RGBA_8888_PRE);
|
|
|
|
res->offscreen = COGL_FRAMEBUFFER (cogl_offscreen_new_to_texture (res->texture));
|
|
if (res->offscreen == NULL)
|
|
{
|
|
g_critical ("%s: Unable to create an offscreen buffer", G_STRLOC);
|
|
|
|
cogl_object_unref (res->texture);
|
|
res->texture = NULL;
|
|
|
|
goto out;
|
|
}
|
|
|
|
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->state = cogl_pipeline_copy (default_texture_pipeline);
|
|
cogl_pipeline_set_layer_filters (res->state, 0,
|
|
COGL_PIPELINE_FILTER_NEAREST,
|
|
COGL_PIPELINE_FILTER_NEAREST);
|
|
cogl_pipeline_set_layer_texture (res->state, 0, res->texture);
|
|
cogl_pipeline_set_color (res->state, &color);
|
|
cogl_object_unref (res->texture);
|
|
|
|
out:
|
|
return (ClutterPaintNode *) res;
|
|
}
|