Contents |
The intended way for materials to be used is that they are initialized once and they should basically be considered immutable once constructed and should be retained for as long as they may be used.
Developers should always aim to use cogl_material_copy to derive new materials from shared template materials instead of using cogl_material_new() and initializing all the properties from scratch.
Any components given an input material to prepare for drawing that need to derive additional materials should create their derived materials using weak copies and cache the derived materials with the input material so that the material doesn't have to be repeatedly derived if the same input is given again later.
A single CoglMaterial is a diff of state. The difference represented by a single CoglMaterial is applied to its parent. At the top of the graph you have the original "default" material that provides a default value for everything. To be able to flush the state of a specific material though you need to walk up the ancestry combining all the differences together.
A material is said to be an "authority" for a given state group if the value of that state can be got directly from that material without deferring to its ancestry.
A Cogl application has one tree of CoglMaterials; the root node is the "default" material created when Cogl is initialized.
Each material has a set of ->differences flags to identify what groups of state in the material are valid and override the value that was previously defined by one of its ancestors.
If a material has children then we have to be careful if the user tries to modify its properties so we don't change the state of its children.
This is an outline for how something like cogl_material_set_color will work:
First we have to find the "authority" ancestor for the given material's color state. We do this by walking up the material's ancestry until we hit an ancestor that has the corresponding difference bit set:
authority = material;
while (!(authority->differences & COGL_MATERIAL_DIFFERENCE_COLOR))
authority = authority->parent;
Then we check if the incoming value is the same as the current value set on the authority material and bail out if so:
if (cogl_color_equal (color, &authority->color)) return;
We notify that the material is actually going to change:
/* - Flush journal primitives referencing the current state. * - Make sure the material has no dependants so it may be modified. * - If the material isn't currently an authority for the state being * changed, then initialize that state from the current authority. */ _cogl_material_pre_change_notify (material, COGL_MATERIAL_CHANGE_COLOR, color);
We apply the change.
material->color = *color;
Finally we update the difference flags but we applying some optimizations to actually try and revert the authority to one of our ancestors if possible. This means that if you change the value for some property in material X which currently has an authority ancestor Y from A → B, then create a copy of the material Z and change the same property back from B → A again we will revert the authority for Z back to Y.
/* If we are the current authority see if we can revert to one of
* our ancestors being the authority */
if (material == authority &&
_cogl_material_get_parent (authority) != NULL)
{
CoglMaterial *parent = _cogl_material_get_parent (authority);
CoglMaterial *old_authority =
_cogl_material_get_authority (parent, state);
if (comparitor (authority, old_authority))
material->differences &= ~state;
}
else if (material != authority)
{
/* If we weren't previously the authority on this state then we
* need to extended our differences mask and so it's possible
* that some of our ancestry will now become redundant, so we
* aim to reparent ourselves if that's true... */
material->differences |= state;
_cogl_material_prune_redundant_ancestry (material);
}
A single CoglMaterialLayer is also a diff of state applied to a parent layer. All layers belong to one graph with the root node being the default_layer0 which Cogl creates during initialization. So to clarify; materials and layers are maintained in two separate sparse graphs. Materials and Layers can actually both be cast to a common CoglMaterialNode type so they share code for dealing with the parenting of children.
A layer can have zero or one "owner" material. A material owns a layer if it references it in its list of ->layer_differences. Note that although a specific node in the layer graph can only have one material owner, a layer may indirectly be referenced my multiple materials if it part of the ancestry for other nodes that have different owners.
Layers can be reached from materials via:
material->layer_differences;
But note: each CoglMaterial node doesn't necessarily contain a *full* list of the layers it uses; some of the layers may be indirectly referenced by any of the material's ancestors.
layer->owner refers to the material whos material->layer_differences references this layer, but there is no easy way to get a complete list of materials that indirectly depend on the state of a layer.
If a layer has children or it has an owner then it is considered immutable so any attempt to modify it will actually result in a copy-on-write and we modify the copy instead.
that it can invalidate any private state associated with the layer? (NB: currently the backend is defined by the material)
A weak material is one that doesn't take a reference on its parent and instead will simply be destroyed if its parent is freed or modified, notifying the user with a callback.
Since we promote weak materials to strong materials when you take a regular copy of a weak material then we have to be careful about taking and releasing temporary references on the weak material parents during cogl_material_copy.
In cogl_material_copy() when copying a material with ->is_weak == TRUE we have to walk up the ancestry until we find the next material with ->is_weak == FALSE taking a reference on each material on the way, including the next strong material.
So if we have a tree like:
A(2)
B(1)
W0(1) W1(1)
W2(1)
Where nodes beginning with W are weak and the value in brackets is the reference count.
And if we derive from weak material W2 we should get:
A(2)
B(2)
W0(2) W1(1)
W2(2)
C(1)
We also have to do the opposite when a material with ->is_weak == FALSE is freed and again walk up the ancestry until we find the next material with ->is_weak == FALSE this time unrefing each material on the way, including the next strong material.
So if for example the user drops there reference to B and we have:
A(2)
B(1)
W0(2) W1(1)
W2(2)
C(1)
Then they drop their reference to C we get:
A(2)
B(0)
W0(1) W1(1)
W2(1)
C(0)
And so everything below B will be freed, with the notification callbacks for W0,W1 and W2 being called.