material: Implements weak materials

Weak materials are ones that don't take a reference on their parent and
they are associated with a callback that notifies when the material is
destroyed, because its parent was freed or modified.

More details can be found at:
http://wiki.clutter-project.org/wiki/CoglDesign/CoglMaterial

For now the concept is internal only but the plan is to make this public
at some point once we have tested the design internally.
This commit is contained in:
Robert Bragg 2010-08-09 16:01:17 +01:00
parent 50c5ecfe87
commit 12031ca2c5
2 changed files with 297 additions and 36 deletions

View File

@ -388,6 +388,17 @@ typedef struct
CoglMaterialLayer *layer;
} CoglMaterialLayerCacheEntry;
/*
* CoglMaterialDestroyCallback
* @material: The #CoglMaterial that has been destroyed
* @user_data: The private data associated with the callback
*
* Notifies when a weak material has been destroyed because one
* of its ancestors has been freed or modified.
*/
typedef void (*CoglMaterialDestroyCallback)(CoglMaterial *material,
void *user_data);
struct _CoglMaterial
{
/* XXX: Please think twice about adding members that *have* be
@ -415,6 +426,14 @@ struct _CoglMaterial
* flushing the journal first */
unsigned long journal_ref_count;
/* When weak materials are destroyed the user is notified via this
* callback */
CoglMaterialDestroyCallback destroy_callback;
/* When notifying that a weak material has been destroyed this
* private data is passed to the above callback */
void *destroy_data;
/* A mask of which sparse state groups are different in this
* material in comparison to its parent. */
unsigned long differences;
@ -685,6 +704,129 @@ _cogl_use_program (CoglHandle program_handle, CoglMaterialProgramType type);
unsigned int
_cogl_get_n_args_for_combine_func (GLint func);
/*
* _cogl_material_weak_copy:
* @material: A #CoglMaterial object
* @callback: A callback to notify when your weak material is destroyed
* @user_data: Private data to pass to your given callback.
*
* Returns a weak copy of the given source @material. Unlike a normal
* copy no internal reference is taken on the source @material and you
* can expect that later modifications of the source material (or in
* fact any other material) can result in the weak material being
* destroyed.
*
* To understand this better its good to know a bit about the internal
* design of #CoglMaterial...
*
* Internally #CoglMaterial<!-- -->s are represented as a graph of
* property diff's, where each node is a diff of properties that gets
* applied on top of its parent. Copying a material creates an empty
* diff and a child->parent relationship between the empty diff and
* the source @material, parent.
*
* Because of this internal graph design a single #CoglMaterial may
* indirectly depend on a chain of ancestors to fully define all of
* its properties. Because a node depends on its ancestors it normally
* owns a reference to its parent to stop it from being freed. Also if
* you try to modify a material with children we internally use a
* copy-on-write mechanism to ensure that you don't indirectly change
* the properties those children.
*
* Weak materials avoid the use of copy-on-write to preserve the
* integrity of weak dependants and instead weak dependants are
* simply destroyed allowing the parent to be modified directly. Also
* because weak materials don't own a reference to their parent they
* won't stop the source @material from being freed when the user
* releases their reference on it.
*
* Because weak materials don't own a reference on their parent they
* are the recommended mechanism for creating derived materials that you
* want to cache as a private property of the original material
* because they won't result in a circular dependency.
*
* An example use case:
*
* Consider for example you are implementing a custom primitive that is
* not compatible with certain source materials. To handle this you
* implement a validation stage that given an arbitrary material as
* input will create a derived material that is suitable for drawing
* your primitive.
*
* Because you don't want to have to repeat this validation every time
* the same incompatible material is given as input you want to cache
* the result as a private property of the original material. If the
* derived material were created using cogl_material_copy that would
* create a circular dependency so the original material can never be
* freed.
*
* If you instead create a weak copy you won't stop the original material
* from being freed if it's no longer needed, and you will instead simply
* be notified that your weak material has been destroyed.
*
* This is the recommended coding pattern for validating an input
* material and caching a derived result:
* |[
* static CoglUserDataKey _cogl_my_cache_key;
*
* typedef struct {
* CoglMaterial *validated_source;
* } MyValidatedMaterialCache;
*
* static void
* destroy_cache_cb (CoglObject *object, void *user_data)
* {
* g_slice_free (MyValidatedMaterialCache, user_data);
* }
*
* static void
* invalidate_cache_cb (CoglMaterial *destroyed, void *user_data)
* {
* MyValidatedMaterialCache *cache = user_data;
* cogl_object_unref (cache->validated_source);
* cache->validated_source = NULL;
* }
*
* static CoglMaterial *
* get_validated_material (CoglMaterial *source)
* {
* MyValidatedMaterialCache *cache =
* cogl_object_get_user_data (COGL_OBJECT (source),
* &_cogl_my_cache_key);
* if (G_UNLIKELY (cache == NULL))
* {
* cache = g_slice_new (MyValidatedMaterialCache);
* cogl_object_set_user_data (COGL_OBJECT (source),
* &_cogl_my_cache_key,
* cache, destroy_cache_cb);
* cache->validated_source = source;
* }
*
* if (G_UNLIKELY (cache->validated_source == NULL))
* {
* cache->validated_source = source;
*
* /&nbsp;* Start validating source... *&nbsp;/
*
* /&nbsp;* If you find you need to change something... *&nbsp;/
* if (cache->validated_source == source)
* cache->validated_source =
* cogl_material_weak_copy (source,
* invalidate_cache_cb,
* cache);
*
* /&nbsp;* Modify cache->validated_source *&nbsp;/
* }
*
* return cache->validated_source;
* }
* ]|
*/
CoglMaterial *
_cogl_material_weak_copy (CoglMaterial *material,
CoglMaterialDestroyCallback callback,
void *user_data);
void
_cogl_material_set_backend (CoglMaterial *material, int backend);

View File

@ -62,6 +62,7 @@ static void _cogl_material_add_layer_difference (CoglMaterial *material,
static void handle_automatic_blend_enable (CoglMaterial *material,
CoglMaterialState changes);
static void recursively_free_layer_caches (CoglMaterial *material);
static gboolean _cogl_material_is_weak (CoglMaterial *material);
const CoglMaterialBackend *_cogl_material_backends[COGL_MATERIAL_N_BACKENDS];
@ -99,10 +100,18 @@ _cogl_material_node_init (CoglMaterialNode *node)
static void
_cogl_material_node_set_parent_real (CoglMaterialNode *node,
CoglMaterialNode *parent,
CoglMaterialNodeUnparentVFunc unparent)
CoglMaterialNodeUnparentVFunc unparent,
gboolean take_strong_reference)
{
/* NB: the old parent may indirectly be keeping the new parent alive so we
* have to ref the new parent before unrefing the old */
/* NB: the old parent may indirectly be keeping the new parent alive
* so we have to ref the new parent before unrefing the old.
*
* Note: we take a reference here regardless of
* take_strong_reference because weak children may need special
* handling when the parent disposes itself which relies on a
* consistent link to all weak nodes. Once the node is linked to its
* parent then we remove the reference at the end if
* take_strong_reference == FALSE. */
cogl_object_ref (parent);
if (node->parent)
@ -118,6 +127,13 @@ _cogl_material_node_set_parent_real (CoglMaterialNode *node,
}
node->parent = parent;
/* Now that there is a consistent parent->child link we can remove
* the parent reference if no reference was requested. If it turns
* out that the new parent was only being kept alive by the old
* parent then it will be disposed of here. */
if (!take_strong_reference)
cogl_object_unref (parent);
}
static void
@ -312,12 +328,15 @@ recursively_free_layer_caches (CoglMaterial *material)
}
static void
_cogl_material_set_parent (CoglMaterial *material, CoglMaterial *parent)
_cogl_material_set_parent (CoglMaterial *material,
CoglMaterial *parent,
gboolean take_strong_reference)
{
/* Chain up */
_cogl_material_node_set_parent_real (COGL_MATERIAL_NODE (material),
COGL_MATERIAL_NODE (parent),
_cogl_material_unparent);
_cogl_material_unparent,
take_strong_reference);
/* Since we just changed the ancestry of the material its cache of
* layers could now be invalid so free it... */
@ -337,16 +356,56 @@ _cogl_material_set_parent (CoglMaterial *material, CoglMaterial *parent)
}
}
static void
_cogl_material_promote_weak_ancestors (CoglMaterial *strong)
{
CoglMaterialNode *n;
g_return_if_fail (!strong->is_weak);
for (n = COGL_MATERIAL_NODE (strong)->parent; n; n = n->parent)
{
CoglMaterial *material = COGL_MATERIAL (n);
cogl_object_ref (material);
if (!material->is_weak)
return;
}
}
static void
_cogl_material_revert_weak_ancestors (CoglMaterial *strong)
{
CoglMaterial *parent = _cogl_material_get_parent (strong);
CoglMaterialNode *n;
g_return_if_fail (!strong->is_weak);
if (!parent || !parent->is_weak)
return;
for (n = COGL_MATERIAL_NODE (strong)->parent; n; n = n->parent)
{
CoglMaterial *material = COGL_MATERIAL (n);
cogl_object_unref (material);
if (!material->is_weak)
return;
}
}
/* XXX: Always have an eye out for opportunities to lower the cost of
* cogl_material_copy. */
CoglMaterial *
cogl_material_copy (CoglMaterial *src)
static CoglMaterial *
_cogl_material_copy (CoglMaterial *src, gboolean is_weak)
{
CoglMaterial *material = g_slice_new (CoglMaterial);
_cogl_material_node_init (COGL_MATERIAL_NODE (material));
material->is_weak = FALSE;
material->is_weak = is_weak;
material->journal_ref_count = 0;
@ -375,26 +434,35 @@ cogl_material_copy (CoglMaterial *src)
material->age = 0;
_cogl_material_set_parent (material, src);
_cogl_material_set_parent (material, src, !is_weak);
/* The semantics for copying a weak material are that we promote all
* weak ancestors to temporarily become strong materials until the
* copy is freed. */
if (!is_weak)
_cogl_material_promote_weak_ancestors (material);
return _cogl_material_object_new (material);
}
/* XXX: we should give this more thought before making anything like
* this API public! */
CoglMaterial *
_cogl_material_weak_copy (CoglMaterial *material)
cogl_material_copy (CoglMaterial *src)
{
return _cogl_material_copy (src, FALSE);
}
CoglMaterial *
_cogl_material_weak_copy (CoglMaterial *material,
CoglMaterialDestroyCallback callback,
void *user_data)
{
CoglMaterial *copy;
CoglMaterial *copy_material;
/* If we make a public API we might want want to allow weak copies
* of weak material? */
g_return_val_if_fail (!material->is_weak, NULL);
copy = cogl_material_copy (material);
copy = _cogl_material_copy (material, TRUE);
copy_material = COGL_MATERIAL (copy);
copy_material->is_weak = TRUE;
copy_material->destroy_callback = callback;
copy_material->destroy_data = user_data;
return copy;
}
@ -423,9 +491,38 @@ _cogl_material_backend_free_priv (CoglMaterial *material)
}
}
static gboolean
destroy_weak_children_cb (CoglMaterialNode *node,
void *user_data)
{
CoglMaterial *material = COGL_MATERIAL (node);
if (_cogl_material_is_weak (material))
{
_cogl_material_node_foreach_child (COGL_MATERIAL_NODE (material),
destroy_weak_children_cb,
NULL);
material->destroy_callback (material, material->destroy_data);
_cogl_material_unparent (COGL_MATERIAL_NODE (material));
}
return TRUE;
}
static void
_cogl_material_free (CoglMaterial *material)
{
if (!material->is_weak)
_cogl_material_revert_weak_ancestors (material);
/* Weak materials don't take a reference on their parent */
_cogl_material_node_foreach_child (COGL_MATERIAL_NODE (material),
destroy_weak_children_cb,
NULL);
g_assert (!COGL_MATERIAL_NODE (material)->has_children);
_cogl_material_backend_free_priv (material);
_cogl_material_unparent (COGL_MATERIAL_NODE (material));
@ -940,11 +1037,12 @@ check_if_strong_cb (CoglMaterialNode *node, void *user_data)
CoglMaterial *material = COGL_MATERIAL (node);
gboolean *has_strong_child = user_data;
if (!material->is_weak)
if (!_cogl_material_is_weak (material))
{
*has_strong_child = TRUE;
return FALSE;
}
return TRUE;
}
@ -959,16 +1057,22 @@ has_strong_children (CoglMaterial *material)
}
static gboolean
reparent_strong_children_cb (CoglMaterialNode *node,
void *user_data)
_cogl_material_is_weak (CoglMaterial *material)
{
if (material->is_weak && !has_strong_children (material))
return TRUE;
else
return FALSE;
}
static gboolean
reparent_children_cb (CoglMaterialNode *node,
void *user_data)
{
CoglMaterial *material = COGL_MATERIAL (node);
CoglMaterial *parent = user_data;
if (material->is_weak)
return TRUE;
_cogl_material_set_parent (material, parent);
_cogl_material_set_parent (material, parent, TRUE);
return TRUE;
}
@ -1029,18 +1133,28 @@ _cogl_material_pre_change_notify (CoglMaterial *material,
backend->material_pre_change_notify (material, change, new_color);
}
/*
* There is an arbitrary tree of descendants of this material; any of
* which may indirectly depend on this material as the authority for
* some set of properties. (Meaning for example that one of its
* descendants derives its color or blending state from this
* material.)
/* There may be an arbitrary tree of descendants of this material;
* any of which may indirectly depend on this material as the
* authority for some set of properties. (Meaning for example that
* one of its descendants derives its color or blending state from
* this material.)
*
* We can't modify any property that this material is the authority
* for unless we create another material to take its place first and
* make sure descendants reference this new material instead.
*/
if (has_strong_children (material))
/* The simplest descendants to handle are weak materials; we simply
* destroy them if we are modifying a material they depend on. This
* means weak materials never cause us to do a copy-on-write. */
_cogl_material_node_foreach_child (COGL_MATERIAL_NODE (material),
destroy_weak_children_cb,
NULL);
/* If there are still children remaining though we'll need to
* perform a copy-on-write and reparent the dependants as children
* of the copy. */
if (COGL_MATERIAL_NODE (material)->has_children)
{
CoglMaterial *new_authority;
@ -1072,10 +1186,10 @@ _cogl_material_pre_change_notify (CoglMaterial *material,
_cogl_material_copy_differences (new_authority, material,
material->differences);
/* Reparent the strong children of material to be children of
/* Reparent the dependants of material to be children of
* new_authority instead... */
_cogl_material_node_foreach_child (COGL_MATERIAL_NODE (material),
reparent_strong_children_cb,
reparent_children_cb,
new_authority);
/* The children will keep the new authority alive so drop the
@ -1513,7 +1627,8 @@ _cogl_material_layer_set_parent (CoglMaterialLayer *layer,
/* Chain up */
_cogl_material_node_set_parent_real (COGL_MATERIAL_NODE (layer),
COGL_MATERIAL_NODE (parent),
_cogl_material_layer_unparent);
_cogl_material_layer_unparent,
TRUE);
}
/* XXX: This is duplicated logic; the same as for
@ -3342,7 +3457,11 @@ _cogl_material_prune_redundant_ancestry (CoglMaterial *material)
material->differences)
new_parent = _cogl_material_get_parent (new_parent);
_cogl_material_set_parent (material, new_parent);
if (new_parent != _cogl_material_get_parent (material))
{
gboolean is_weak = _cogl_material_is_weak (material);
_cogl_material_set_parent (material, new_parent, is_weak ? FALSE : TRUE);
}
}
static void