mirror of
https://github.com/brl/mutter.git
synced 2024-11-26 18:11:05 -05:00
a64742f3e4
The PaintNode hierarchy should have the ability to retrieve the current active framebuffer by itself, instead of asking Cogl using the global state API. In order to do this, we ask the root node of a PaintNode graph for the active framebuffer. In the current, 1.x-compatibility mode we have two potential root node types: ClutterRootNode, used by ClutterStage; and ClutterDummyNode, used a local root for each actor. The former takes a framebuffer as part of its construction; the latter takes the actor that acts as the local top-level during the actor's paint sequence, which means we can get the active framebuffer from the stage associated to the actor. By keeping track of the active framebuffer on the node themselves we can drop the usage of cogl_get_draw_framebuffer() in their implementation.
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 <structname>ClutterPipelineNodeClass</structname> 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 <structname>ClutterColorNodeClass</structname> 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 <structname>ClutterTextureNodeClass</structname> 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 <structname>ClutterTextNodeClass</structname> 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 <structname>ClutterClipNodeClass</structname> 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;
|
|
}
|