Clutter Wiki

Views
From ClutterProject
Jump to: navigation, search

Contents

Requirements

Sparse State
We want a design that allows sparse descriptions of state so it scales well as we make CoglMaterial responsible for more and more state. It needs to scale well in terms of memory usage and the cost of operations we need to apply to materials such as comparing, copying and flushing their state. We would rather have these things scale by the number of real changes a material represents not by how much overall state cogl-material.c becomes responsible for.
Cheap Copies
We need to be able to make fast copies of materials
  • cogl_material_copy should have the semantics of a deep copy of the source material state and of the currently associated layers. Something worth noting here though is that this doesn't extend to the image data of textures. (So a texture's image data is undefined if modified mid-scene.)
  • These semantics will be required for deferred rendering techniques such as via our planned renderlist API.
Weak Materials
We need a way to cheaply derive materials that don't take a strong reference on the source material so we can cache them as private properties of the source material without creating circular references.
  • There can be several stages of validation that happen after a user creates their materials and passes them on to other components to draw with. Multiple additional materials may need to be derived from the user's original material and to avoid repeatedly deriving these materials they should be cached as private properties of the original user material. Weak materials should be used for this because the won't stop the user's material being freed and they are automatically destroyed when the user's material is destroyed.
No more flush overrides
We want to get rid of the flush overrides mechanism we currently use to deal with fallbacks and to handle primitives using high level CoglTextures that need to be resolved into lowlevel textures before flushing the material state. Both of these issues are dealt with by the primitives rendering code and not by the user so these are basically one-shot modifications of the user's material made just before flushing. The current flush override options should instead be handled using weak materials and they should be cached with the user's original material.
Fast compare and flush
We need to be able to quickly determine the differences between arbitrary materials so we can determine the minimum amount of state changes that need to be made with OpenGL when flushing a material.
Handle legacy per-context state
Cogl currently associates some GPU state with the context, such as the depth enable, the fog and the current program. CoglMaterial is going to become responsible for these but we are still going to have to maintain the old API in a way that doesn't conflict with people using the replacement material APIs. So cogl_set_fog should somehow automatically modify all materials that get used while it's enabled and have higher precedence than any fog setup using a new cogl_material_set_fog API, but when cogl_disable_fog is called we should revert to the users cogl_material_set_fog state.

The usage model we are aiming for

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.

Design

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.

Changing a material property

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);
   }

Layers

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.

Q
Different materials may currently use different backends, so does that mean that layers may be associated with multiple backends at the same time?
 
Being able to mix and match backends seems quite a good feature, since all backends don't have to be feature complete so long as they can fallback to the fixed backend + it's likely that the ARBfp backend will be better supported and faster than the GLSL backend for some drivers but it's also possible that the glsl backend will gain more features than the ARBfp backend.
A
Since layers can be indirectly referenced by multiple materials each associated with different backends the layers have an array of backend private pointers.

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.

Q
When a layer is modified how can we notify the material backend so

that it can invalidate any private state associated with the layer? (NB: currently the backend is defined by the material)

A
Each layer has an array of backend private pointers (since a layer can indirectly be referenced by multiple materials each with different associated backends). Whenever a layer change is notified we can notify the backends depending on which of these points aren't NULL.
Q
How will layer change notifications propagate up to material change notifications?
A
A layer can either have zero or one owner material. Whenever a layer is modified it is done in the context of a material modification so _cogl_material_layer_pre_change_notify is passed a pointer to the owning material and will chain up to _cogl_material_pre_change_notify.
Q
How will the backends access the material corresponding to a layer passed to the add_layer vfunc, since previously there was previously a 1:1 relationship and the material was referenced by layer->material?
A
The material will be passed directly to the vfunc
Q
How will we handle reference counting of materials?
A
  • When a material is created the user owns one reference
  • When a material is derived from another as a result of a cogl_material_copy() then the derived material takes a reference on the parent it derives from (that it is dependent on)
Q
How will we handle reference counting of layers?
A
  • When a layer is derived from another then it takes a reference on its parent
  • When a material references a layer in its list of ->layer_differences it will own a reference on that layer.
Q
How will we handle legacy per context state, such as depth enable, fog and cogl_program_use?
A
It is the responsibility of the primitives code to call _cogl_material_apply_legacy_state() to potentially derive a replacement source material with any legacy state applied.

Weak materials

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.

Q
Is it allowed to create a weak-copy of a weak material?
A
yes, we can define consistent semantics here and since the typical use case for weak materials is for caching materials derived from a given input material and we may not always have tight control over that input material it would be awkward if we had to special case when that input is a weak material.
Q
What are the semantics of modifying a material that has weak children?
A
All weak materials below the point being modified should be destroyed, but considering that although some of the children may be marked as "weak" they may have been promoted to be strong if they have strong children.
Q
So how do we determine which materials to destroy?
A
A material is weak if material->is_weak == TRUE and all of its children are also weak.
Q
What are the semantics of making a weak-copy of a weak material?
A
It is possible to have a weak graph of materials derived from a strong material and if any material is modified or freed then all the weak materials below the point of modification will be destroyed.
Q
What are the semantics of making a normal copy of a weak material?
A
Any weak materials above the new copy become strong until the new copy is freed at which point they revert to being weak again.

Some fiddle ref counting details for weak materials:

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.

Personal tools