/*
* Cogl
*
* An object oriented GL/GLES Abstraction/Utility Layer
*
* Copyright (C) 2008,2009,2010 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
* .
*
*
*
* Authors:
* Robert Bragg
*/
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#include "cogl.h"
#include "cogl-debug.h"
#include "cogl-internal.h"
#include "cogl-context.h"
#include "cogl-object.h"
#include "cogl-material-private.h"
#include "cogl-material-opengl-private.h"
#include "cogl-texture-private.h"
#include "cogl-blend-string.h"
#include "cogl-journal-private.h"
#include "cogl-color-private.h"
#include "cogl-profile.h"
#include
#include
#include
#define COGL_MATERIAL_LAYER(X) ((CoglMaterialLayer *)(X))
typedef gboolean (*CoglMaterialStateComparitor) (CoglMaterial *authority0,
CoglMaterial *authority1);
static CoglMaterialLayer *_cogl_material_layer_copy (CoglMaterialLayer *layer);
static void _cogl_material_free (CoglMaterial *tex);
static void _cogl_material_layer_free (CoglMaterialLayer *layer);
static void _cogl_material_add_layer_difference (CoglMaterial *material,
CoglMaterialLayer *layer,
gboolean inc_n_layers);
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];
#ifdef COGL_MATERIAL_BACKEND_GLSL
#include "cogl-material-glsl-private.h"
#endif
#ifdef COGL_MATERIAL_BACKEND_ARBFP
#include "cogl-material-arbfp-private.h"
#endif
#ifdef COGL_MATERIAL_BACKEND_FIXED
#include "cogl-material-fixed-private.h"
#endif
COGL_OBJECT_DEFINE (Material, material);
COGL_OBJECT_DEFINE_DEPRECATED_REF_COUNTING (material);
/* This type was made deprecated before the cogl_is_material_layer
function was ever exposed in the public headers so there's no need
to make the cogl_is_material_layer function public. We use INTERNAL
so that the cogl_is_* function won't get defined */
COGL_OBJECT_INTERNAL_DEFINE (MaterialLayer, material_layer);
GQuark
_cogl_material_error_quark (void)
{
return g_quark_from_static_string ("cogl-material-error-quark");
}
static void
_cogl_material_node_init (CoglMaterialNode *node)
{
node->parent = NULL;
node->has_children = FALSE;
}
static void
_cogl_material_node_set_parent_real (CoglMaterialNode *node,
CoglMaterialNode *parent,
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.
*
* 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)
unparent (node);
if (G_UNLIKELY (parent->has_children))
parent->children = g_list_prepend (parent->children, node);
else
{
parent->has_children = TRUE;
parent->first_child = node;
parent->children = NULL;
}
node->parent = parent;
node->has_parent_reference = take_strong_reference;
/* 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
_cogl_material_node_unparent_real (CoglMaterialNode *node)
{
CoglMaterialNode *parent = node->parent;
if (parent == NULL)
return;
g_return_if_fail (parent->has_children);
if (parent->first_child == node)
{
if (parent->children)
{
parent->first_child = parent->children->data;
parent->children =
g_list_delete_link (parent->children, parent->children);
}
else
parent->has_children = FALSE;
}
else
parent->children = g_list_remove (parent->children, node);
if (node->has_parent_reference)
cogl_object_unref (parent);
node->parent = NULL;
}
void
_cogl_material_node_foreach_child (CoglMaterialNode *node,
CoglMaterialNodeChildCallback callback,
void *user_data)
{
if (node->has_children)
{
callback (node->first_child, user_data);
g_list_foreach (node->children, (GFunc)callback, user_data);
}
}
/*
* This initializes the first material owned by the Cogl context. All
* subsequently instantiated materials created via the cogl_material_new()
* API will initially be a copy of this material.
*
* The default material is the topmost ancester for all materials.
*/
void
_cogl_material_init_default_material (void)
{
/* Create new - blank - material */
CoglMaterial *material = g_slice_new0 (CoglMaterial);
CoglMaterialBigState *big_state = g_slice_new0 (CoglMaterialBigState);
CoglMaterialLightingState *lighting_state = &big_state->lighting_state;
CoglMaterialAlphaFuncState *alpha_state = &big_state->alpha_state;
CoglMaterialBlendState *blend_state = &big_state->blend_state;
CoglMaterialDepthState *depth_state = &big_state->depth_state;
_COGL_GET_CONTEXT (ctx, NO_RETVAL);
/* Take this opportunity to setup the fragment processing backends... */
#ifdef COGL_MATERIAL_BACKEND_GLSL
_cogl_material_backends[COGL_MATERIAL_BACKEND_GLSL] =
&_cogl_material_glsl_backend;
#endif
#ifdef COGL_MATERIAL_BACKEND_ARBFP
_cogl_material_backends[COGL_MATERIAL_BACKEND_ARBFP] =
&_cogl_material_arbfp_backend;
#endif
#ifdef COGL_MATERIAL_BACKEND_FIXED
_cogl_material_backends[COGL_MATERIAL_BACKEND_FIXED] =
&_cogl_material_fixed_backend;
#endif
_cogl_material_node_init (COGL_MATERIAL_NODE (material));
material->is_weak = FALSE;
material->journal_ref_count = 0;
material->backend = COGL_MATERIAL_BACKEND_UNDEFINED;
material->differences = COGL_MATERIAL_STATE_ALL_SPARSE;
material->real_blend_enable = FALSE;
material->blend_enable = COGL_MATERIAL_BLEND_ENABLE_AUTOMATIC;
material->layer_differences = NULL;
material->n_layers = 0;
material->big_state = big_state;
material->has_big_state = TRUE;
material->static_breadcrumb = "default material";
material->has_static_breadcrumb = TRUE;
material->age = 0;
/* Use the same defaults as the GL spec... */
cogl_color_init_from_4ub (&material->color, 0xff, 0xff, 0xff, 0xff);
/* Use the same defaults as the GL spec... */
lighting_state->ambient[0] = 0.2;
lighting_state->ambient[1] = 0.2;
lighting_state->ambient[2] = 0.2;
lighting_state->ambient[3] = 1.0;
lighting_state->diffuse[0] = 0.8;
lighting_state->diffuse[1] = 0.8;
lighting_state->diffuse[2] = 0.8;
lighting_state->diffuse[3] = 1.0;
lighting_state->specular[0] = 0;
lighting_state->specular[1] = 0;
lighting_state->specular[2] = 0;
lighting_state->specular[3] = 1.0;
lighting_state->emission[0] = 0;
lighting_state->emission[1] = 0;
lighting_state->emission[2] = 0;
lighting_state->emission[3] = 1.0;
lighting_state->shininess = 0.0f;
/* Use the same defaults as the GL spec... */
alpha_state->alpha_func = COGL_MATERIAL_ALPHA_FUNC_ALWAYS;
alpha_state->alpha_func_reference = 0.0;
/* Not the same as the GL default, but seems saner... */
#ifndef HAVE_COGL_GLES
blend_state->blend_equation_rgb = GL_FUNC_ADD;
blend_state->blend_equation_alpha = GL_FUNC_ADD;
blend_state->blend_src_factor_alpha = GL_ONE;
blend_state->blend_dst_factor_alpha = GL_ONE_MINUS_SRC_ALPHA;
cogl_color_init_from_4ub (&blend_state->blend_constant,
0x00, 0x00, 0x00, 0x00);
#endif
blend_state->blend_src_factor_rgb = GL_ONE;
blend_state->blend_dst_factor_rgb = GL_ONE_MINUS_SRC_ALPHA;
big_state->user_program = COGL_INVALID_HANDLE;
/* The same as the GL defaults */
depth_state->depth_test_enabled = FALSE;
depth_state->depth_test_function = COGL_DEPTH_TEST_FUNCTION_LESS;
depth_state->depth_writing_enabled = TRUE;
depth_state->depth_range_near = 0;
depth_state->depth_range_far = 1;
big_state->point_size = 1.0f;
ctx->default_material = _cogl_material_object_new (material);
}
static void
_cogl_material_unparent (CoglMaterialNode *material)
{
/* Chain up */
_cogl_material_node_unparent_real (material);
}
static gboolean
recursively_free_layer_caches_cb (CoglMaterialNode *node,
void *user_data)
{
recursively_free_layer_caches (COGL_MATERIAL (node));
return TRUE;
}
/* This recursively frees the layers_cache of a material and all of
* its descendants.
*
* For instance if we change a materials ->layer_differences list
* then that material and all of its descendants may now have
* incorrect layer caches. */
static void
recursively_free_layer_caches (CoglMaterial *material)
{
/* Note: we maintain the invariable that if a material already has a
* dirty layers_cache then so do all of its descendants. */
if (material->layers_cache_dirty)
return;
if (G_UNLIKELY (material->layers_cache != material->short_layers_cache))
g_slice_free1 (sizeof (CoglMaterialLayer *) * material->n_layers,
material->layers_cache);
material->layers_cache_dirty = TRUE;
_cogl_material_node_foreach_child (COGL_MATERIAL_NODE (material),
recursively_free_layer_caches_cb,
NULL);
}
static void
_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,
take_strong_reference);
/* Since we just changed the ancestry of the material its cache of
* layers could now be invalid so free it... */
if (material->differences & COGL_MATERIAL_STATE_LAYERS)
recursively_free_layer_caches (material);
/* If the fragment processing backend is also caching state along
* with the material that depends on the material's ancestry then it
* may be notified here...
*/
if (material->backend != COGL_MATERIAL_BACKEND_UNDEFINED &&
_cogl_material_backends[material->backend]->material_set_parent_notify)
{
const CoglMaterialBackend *backend =
_cogl_material_backends[material->backend];
backend->material_set_parent_notify (material);
}
}
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. */
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 = is_weak;
material->journal_ref_count = 0;
material->differences = 0;
material->has_big_state = FALSE;
/* NB: real_blend_enable isn't a sparse property, it's valid for
* every material node so we have fast access to it. */
material->real_blend_enable = src->real_blend_enable;
/* XXX:
* consider generalizing the idea of "cached" properties. These
* would still have an authority like other sparse properties but
* you wouldn't have to walk up the ancestry to find the authority
* because the value would be cached directly in each material.
*/
material->layers_cache_dirty = TRUE;
material->deprecated_get_layers_list_dirty = TRUE;
material->backend = src->backend;
material->backend_priv_set_mask = 0;
material->has_static_breadcrumb = FALSE;
material->age = 0;
_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);
}
CoglMaterial *
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;
copy = _cogl_material_copy (material, TRUE);
copy_material = COGL_MATERIAL (copy);
copy_material->destroy_callback = callback;
copy_material->destroy_data = user_data;
return copy;
}
CoglMaterial *
cogl_material_new (void)
{
CoglMaterial *new;
_COGL_GET_CONTEXT (ctx, NULL);
new = cogl_material_copy (ctx->default_material);
_cogl_material_set_static_breadcrumb (new, "new");
return new;
}
static void
_cogl_material_backend_free_priv (CoglMaterial *material)
{
if (material->backend != COGL_MATERIAL_BACKEND_UNDEFINED &&
_cogl_material_backends[material->backend]->free_priv)
{
const CoglMaterialBackend *backend =
_cogl_material_backends[material->backend];
backend->free_priv (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));
if (material->differences & COGL_MATERIAL_STATE_USER_SHADER &&
material->big_state->user_program)
cogl_handle_unref (material->big_state->user_program);
if (material->differences & COGL_MATERIAL_STATE_NEEDS_BIG_STATE)
g_slice_free (CoglMaterialBigState, material->big_state);
if (material->differences & COGL_MATERIAL_STATE_LAYERS)
{
g_list_foreach (material->layer_differences,
(GFunc)cogl_object_unref, NULL);
g_list_free (material->layer_differences);
}
g_slice_free (CoglMaterial, material);
}
gboolean
_cogl_material_get_real_blend_enabled (CoglMaterial *material)
{
g_return_val_if_fail (cogl_is_material (material), FALSE);
return material->real_blend_enable;
}
inline CoglMaterial *
_cogl_material_get_parent (CoglMaterial *material)
{
CoglMaterialNode *parent_node = COGL_MATERIAL_NODE (material)->parent;
return COGL_MATERIAL (parent_node);
}
CoglMaterial *
_cogl_material_get_authority (CoglMaterial *material,
unsigned long difference)
{
CoglMaterial *authority = material;
while (!(authority->differences & difference))
authority = _cogl_material_get_parent (authority);
return authority;
}
/* XXX: Think twice before making this non static since it is used
* heavily and we expect the compiler to inline it...
*/
static CoglMaterialLayer *
_cogl_material_layer_get_parent (CoglMaterialLayer *layer)
{
CoglMaterialNode *parent_node = COGL_MATERIAL_NODE (layer)->parent;
return COGL_MATERIAL_LAYER (parent_node);
}
CoglMaterialLayer *
_cogl_material_layer_get_authority (CoglMaterialLayer *layer,
unsigned long difference)
{
CoglMaterialLayer *authority = layer;
while (!(authority->differences & difference))
authority = _cogl_material_layer_get_parent (authority);
return authority;
}
int
_cogl_material_layer_get_unit_index (CoglMaterialLayer *layer)
{
CoglMaterialLayer *authority =
_cogl_material_layer_get_authority (layer, COGL_MATERIAL_LAYER_STATE_UNIT);
return authority->unit_index;
}
static void
_cogl_material_update_layers_cache (CoglMaterial *material)
{
/* Note: we assume this material is a _LAYERS authority */
int n_layers;
CoglMaterial *current;
int layers_found;
if (G_LIKELY (!material->layers_cache_dirty) ||
material->n_layers == 0)
return;
material->layers_cache_dirty = FALSE;
n_layers = material->n_layers;
if (G_LIKELY (n_layers < G_N_ELEMENTS (material->short_layers_cache)))
{
material->layers_cache = material->short_layers_cache;
memset (material->layers_cache, 0,
sizeof (CoglMaterialLayer *) *
G_N_ELEMENTS (material->short_layers_cache));
}
else
{
material->layers_cache =
g_slice_alloc0 (sizeof (CoglMaterialLayer *) * n_layers);
}
/* Notes:
*
* Each material doesn't have to contain a complete list of the layers
* it depends on, some of them are indirectly referenced through the
* material's ancestors.
*
* material->layer_differences only contains a list of layers that
* have changed in relation to its parent.
*
* material->layer_differences is not maintained sorted, but it
* won't contain multiple layers corresponding to a particular
* ->unit_index.
*
* Some of the ancestor materials may reference layers with
* ->unit_index values >= n_layers so we ignore them.
*
* As we ascend through the ancestors we are searching for any
* CoglMaterialLayers corresponding to the texture ->unit_index
* values in the range [0,n_layers-1]. As soon as a pointer is found
* we ignore layers of further ancestors with the same ->unit_index
* values.
*/
layers_found = 0;
for (current = material;
_cogl_material_get_parent (current);
current = _cogl_material_get_parent (current))
{
GList *l;
if (!(current->differences & COGL_MATERIAL_STATE_LAYERS))
continue;
for (l = current->layer_differences; l; l = l->next)
{
CoglMaterialLayer *layer = l->data;
int unit_index = _cogl_material_layer_get_unit_index (layer);
if (unit_index < n_layers && !material->layers_cache[unit_index])
{
material->layers_cache[unit_index] = layer;
layers_found++;
if (layers_found == n_layers)
return;
}
}
}
g_warn_if_reached ();
}
/* XXX: Be carefull when using this API that the callback given doesn't result
* in the layer cache being invalidated during the iteration! */
void
_cogl_material_foreach_layer_internal (CoglMaterial *material,
CoglMaterialInternalLayerCallback callback,
void *user_data)
{
CoglMaterial *authority =
_cogl_material_get_authority (material, COGL_MATERIAL_STATE_LAYERS);
int n_layers;
int i;
gboolean cont;
n_layers = authority->n_layers;
if (n_layers == 0)
return;
_cogl_material_update_layers_cache (authority);
for (i = 0, cont = TRUE; i < n_layers && cont == TRUE; i++)
{
g_return_if_fail (authority->layers_cache_dirty == FALSE);
cont = callback (authority->layers_cache[i], user_data);
}
}
typedef struct
{
int i;
int *indices;
} AppendLayerIndexState;
static gboolean
append_layer_index_cb (CoglMaterialLayer *layer,
void *user_data)
{
AppendLayerIndexState *state = user_data;
state->indices[state->i++] = layer->index;
return TRUE;
}
void
cogl_material_foreach_layer (CoglMaterial *material,
CoglMaterialLayerCallback callback,
void *user_data)
{
CoglMaterial *authority =
_cogl_material_get_authority (material, COGL_MATERIAL_STATE_LAYERS);
AppendLayerIndexState state;
gboolean cont;
int i;
/* XXX: We don't know what the user is going to want to do to the layers
* but any modification of layers can result in the layer graph changing
* which could confuse _cogl_material_foreach_layer_internal(). We first
* get a list of layer indices which will remain valid so long as the
* user doesn't remove layers. */
state.i = 0;
state.indices = g_alloca (authority->n_layers * sizeof (int));
_cogl_material_foreach_layer_internal (material,
append_layer_index_cb,
&state);
for (i = 0, cont = TRUE; i < authority->n_layers && cont; i++)
cont = callback (material, state.indices[i], user_data);
}
static gboolean
layer_has_alpha_cb (CoglMaterialLayer *layer, void *data)
{
CoglMaterialLayer *combine_authority =
_cogl_material_layer_get_authority (layer,
COGL_MATERIAL_LAYER_STATE_COMBINE);
CoglMaterialLayerBigState *big_state = combine_authority->big_state;
CoglMaterialLayer *tex_authority;
gboolean *has_alpha = data;
/* has_alpha maintains the alpha status for the GL_PREVIOUS layer */
/* For anything but the default texture combine we currently just
* assume it may result in an alpha value < 1
*
* FIXME: we could do better than this. */
if (big_state->texture_combine_alpha_func != GL_MODULATE ||
big_state->texture_combine_alpha_src[0] != GL_PREVIOUS ||
big_state->texture_combine_alpha_op[0] != GL_SRC_ALPHA ||
big_state->texture_combine_alpha_src[0] != GL_TEXTURE ||
big_state->texture_combine_alpha_op[0] != GL_SRC_ALPHA)
{
*has_alpha = TRUE;
/* return FALSE to stop iterating layers... */
return FALSE;
}
/* NB: A layer may have a combine mode set on it but not yet
* have an associated texture which would mean we'd fallback
* to the default texture which doesn't have an alpha component
*/
tex_authority =
_cogl_material_layer_get_authority (layer,
COGL_MATERIAL_LAYER_STATE_TEXTURE);
if (tex_authority->texture &&
cogl_texture_get_format (tex_authority->texture) & COGL_A_BIT)
{
*has_alpha = TRUE;
/* return FALSE to stop iterating layers... */
return FALSE;
}
*has_alpha = FALSE;
/* return FALSE to continue iterating layers... */
return TRUE;
}
static CoglMaterial *
_cogl_material_get_user_program (CoglMaterial *material)
{
CoglMaterial *authority;
g_return_val_if_fail (cogl_is_material (material), NULL);
authority =
_cogl_material_get_authority (material, COGL_MATERIAL_STATE_USER_SHADER);
return authority->big_state->user_program;
}
static gboolean
_cogl_material_needs_blending_enabled (CoglMaterial *material,
unsigned long changes,
const CoglColor *override_color)
{
CoglMaterial *enable_authority;
CoglMaterial *blend_authority;
CoglMaterialBlendState *blend_state;
CoglMaterialBlendEnable enabled;
unsigned long other_state;
if (G_UNLIKELY (cogl_debug_flags & COGL_DEBUG_DISABLE_BLENDING))
return FALSE;
enable_authority =
_cogl_material_get_authority (material, COGL_MATERIAL_STATE_BLEND_ENABLE);
enabled = enable_authority->blend_enable;
if (enabled != COGL_MATERIAL_BLEND_ENABLE_AUTOMATIC)
return enabled == COGL_MATERIAL_BLEND_ENABLE_ENABLED ? TRUE : FALSE;
blend_authority =
_cogl_material_get_authority (material, COGL_MATERIAL_STATE_BLEND);
blend_state = &blend_authority->big_state->blend_state;
/* We are trying to identify awkward cases that are equivalent to
* blending being disable, where the output is simply GL_SRC_COLOR.
*
* Note: we assume that all OpenGL drivers will identify the simple
* case of ADD (ONE, ZERO) as equivalent to blending being disabled.
*
* We should update this when we add support for more blend
* functions...
*/
#ifndef HAVE_COGL_GLES
/* GLES 1 can't change the function or have separate alpha factors */
if (blend_state->blend_equation_rgb != GL_FUNC_ADD ||
blend_state->blend_equation_alpha != GL_FUNC_ADD)
return TRUE;
if (blend_state->blend_src_factor_alpha != GL_ONE ||
blend_state->blend_dst_factor_alpha != GL_ONE_MINUS_SRC_ALPHA)
return TRUE;
#endif
if (blend_state->blend_src_factor_rgb != GL_ONE ||
blend_state->blend_dst_factor_rgb != GL_ONE_MINUS_SRC_ALPHA)
return TRUE;
/* Given the above constraints, it's now a case of finding any
* SRC_ALPHA that != 1 */
/* In the case of a layer state change we need to check everything
* else first since they contribute to the has_alpha status of the
* GL_PREVIOUS layer. */
if (changes & COGL_MATERIAL_STATE_LAYERS)
changes = COGL_MATERIAL_STATE_AFFECTS_BLENDING;
/* XXX: we don't currently handle specific changes in an optimal way*/
changes = COGL_MATERIAL_STATE_AFFECTS_BLENDING;
if ((override_color && cogl_color_get_alpha_byte (override_color) != 0xff))
return TRUE;
if (changes & COGL_MATERIAL_STATE_COLOR)
{
CoglColor tmp;
cogl_material_get_color (material, &tmp);
if (cogl_color_get_alpha_byte (&tmp) != 0xff)
return TRUE;
}
/* We can't make any assumptions about the alpha channel if the user
* is using an unknown fragment shader.
*
* TODO: check that it isn't just a vertex shader!
*/
if (changes & COGL_MATERIAL_STATE_USER_SHADER)
{
if (_cogl_material_get_user_program (material) != COGL_INVALID_HANDLE)
return TRUE;
}
/* XXX: we should only need to look at these if lighting is enabled
*/
if (changes & COGL_MATERIAL_STATE_LIGHTING)
{
CoglColor tmp;
cogl_material_get_ambient (material, &tmp);
if (cogl_color_get_alpha_byte (&tmp) != 0xff)
return TRUE;
cogl_material_get_diffuse (material, &tmp);
if (cogl_color_get_alpha_byte (&tmp) != 0xff)
return TRUE;
cogl_material_get_specular (material, &tmp);
if (cogl_color_get_alpha_byte (&tmp) != 0xff)
return TRUE;
cogl_material_get_emission (material, &tmp);
if (cogl_color_get_alpha_byte (&tmp) != 0xff)
return TRUE;
}
if (changes & COGL_MATERIAL_STATE_LAYERS)
{
/* has_alpha tracks the alpha status of the GL_PREVIOUS layer.
* To start with that's defined by the material color which
* must be fully opaque if we got this far. */
gboolean has_alpha = FALSE;
_cogl_material_foreach_layer_internal (material,
layer_has_alpha_cb,
&has_alpha);
if (has_alpha)
return TRUE;
}
/* So far we have only checked the property that has been changed so
* we now need to check all the other properties too. */
other_state = COGL_MATERIAL_STATE_AFFECTS_BLENDING & ~changes;
if (other_state &&
_cogl_material_needs_blending_enabled (material,
other_state,
NULL))
return TRUE;
return FALSE;
}
void
_cogl_material_set_backend (CoglMaterial *material, int backend)
{
_cogl_material_backend_free_priv (material);
material->backend = backend;
}
static void
_cogl_material_copy_differences (CoglMaterial *dest,
CoglMaterial *src,
unsigned long differences)
{
CoglMaterialBigState *big_state;
if (differences & COGL_MATERIAL_STATE_COLOR)
dest->color = src->color;
if (differences & COGL_MATERIAL_STATE_BLEND_ENABLE)
dest->blend_enable = src->blend_enable;
if (differences & COGL_MATERIAL_STATE_LAYERS)
{
GList *l;
if (dest->differences & COGL_MATERIAL_STATE_LAYERS &&
dest->layer_differences)
{
g_list_foreach (dest->layer_differences,
(GFunc)cogl_object_unref,
NULL);
g_list_free (dest->layer_differences);
}
for (l = src->layer_differences; l; l = l->next)
{
/* NB: a layer can't have more than one ->owner so we can't
* simply take a references on each of the original
* layer_differences, we have to derive new layers from the
* originals instead. */
CoglMaterialLayer *copy = _cogl_material_layer_copy (l->data);
_cogl_material_add_layer_difference (dest, copy, FALSE);
cogl_object_unref (copy);
}
/* Note: we initialize n_layers after adding the layer differences
* since the act of adding the layers will initialize n_layers to 0
* because dest isn't initially a STATE_LAYERS authority. */
dest->n_layers = src->n_layers;
}
if (differences & COGL_MATERIAL_STATE_NEEDS_BIG_STATE)
{
if (!dest->has_big_state)
{
dest->big_state = g_slice_new (CoglMaterialBigState);
dest->has_big_state = TRUE;
}
big_state = dest->big_state;
}
else
goto check_for_blending_change;
if (differences & COGL_MATERIAL_STATE_LIGHTING)
{
memcpy (&big_state->lighting_state,
&src->big_state->lighting_state,
sizeof (CoglMaterialLightingState));
}
if (differences & COGL_MATERIAL_STATE_ALPHA_FUNC)
{
memcpy (&big_state->alpha_state,
&src->big_state->alpha_state,
sizeof (CoglMaterialAlphaFuncState));
}
if (differences & COGL_MATERIAL_STATE_BLEND)
{
memcpy (&big_state->blend_state,
&src->big_state->blend_state,
sizeof (CoglMaterialBlendState));
}
if (differences & COGL_MATERIAL_STATE_USER_SHADER)
{
if (src->big_state->user_program)
big_state->user_program =
cogl_handle_ref (src->big_state->user_program);
else
big_state->user_program = COGL_INVALID_HANDLE;
}
if (differences & COGL_MATERIAL_STATE_DEPTH)
{
memcpy (&big_state->depth_state,
&src->big_state->depth_state,
sizeof (CoglMaterialDepthState));
}
if (differences & COGL_MATERIAL_STATE_FOG)
{
memcpy (&big_state->fog_state,
&src->big_state->fog_state,
sizeof (CoglMaterialFogState));
}
if (differences & COGL_MATERIAL_STATE_POINT_SIZE)
big_state->point_size = src->big_state->point_size;
/* XXX: we shouldn't bother doing this in most cases since
* _copy_differences is typically used to initialize material state
* by copying it from the current authority, so it's not actually
* *changing* anything.
*/
check_for_blending_change:
if (differences & COGL_MATERIAL_STATE_AFFECTS_BLENDING)
handle_automatic_blend_enable (dest, differences);
dest->differences |= differences;
}
static void
_cogl_material_initialize_sparse_state (CoglMaterial *dest,
CoglMaterial *src,
CoglMaterialState state)
{
if (dest == src)
return;
g_return_if_fail (state & COGL_MATERIAL_STATE_ALL_SPARSE);
if (state != COGL_MATERIAL_STATE_LAYERS)
_cogl_material_copy_differences (dest, src, state);
else
{
dest->n_layers = src->n_layers;
dest->layer_differences = NULL;
}
}
static gboolean
check_if_strong_cb (CoglMaterialNode *node, void *user_data)
{
CoglMaterial *material = COGL_MATERIAL (node);
gboolean *has_strong_child = user_data;
if (!_cogl_material_is_weak (material))
{
*has_strong_child = TRUE;
return FALSE;
}
return TRUE;
}
static gboolean
has_strong_children (CoglMaterial *material)
{
gboolean has_strong_child = FALSE;
_cogl_material_node_foreach_child (COGL_MATERIAL_NODE (material),
check_if_strong_cb,
&has_strong_child);
return has_strong_child;
}
static gboolean
_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;
_cogl_material_set_parent (material, parent, TRUE);
return TRUE;
}
static void
_cogl_material_pre_change_notify (CoglMaterial *material,
CoglMaterialState change,
const CoglColor *new_color,
gboolean from_layer_change)
{
CoglMaterial *authority;
_COGL_GET_CONTEXT (ctx, NO_RETVAL);
/* If primitives have been logged in the journal referencing the
* current state of this material we need to flush the journal
* before we can modify it... */
if (material->journal_ref_count)
{
gboolean skip_journal_flush = FALSE;
/* XXX: We don't usually need to flush the journal just due to
* color changes since material colors are logged in the
* journal's vertex buffer. The exception is when the change in
* color enables or disables the need for blending. */
if (change == COGL_MATERIAL_STATE_COLOR)
{
gboolean will_need_blending =
_cogl_material_needs_blending_enabled (material,
change,
new_color);
gboolean blend_enable = material->real_blend_enable ? TRUE : FALSE;
if (will_need_blending == blend_enable)
skip_journal_flush = TRUE;
}
if (!skip_journal_flush)
_cogl_journal_flush ();
}
/* The fixed function backend has no private state and can't
* do anything special to handle small material changes so we may as
* well try to find a better backend whenever the material changes.
*
* The programmable backends may be able to cache a lot of the code
* they generate and only need to update a small section of that
* code in response to a material change therefore we don't want to
* try searching for another backend when the material changes.
*/
if (material->backend == COGL_MATERIAL_BACKEND_FIXED)
_cogl_material_set_backend (material, COGL_MATERIAL_BACKEND_UNDEFINED);
if (material->backend != COGL_MATERIAL_BACKEND_UNDEFINED &&
_cogl_material_backends[material->backend]->material_pre_change_notify)
{
const CoglMaterialBackend *backend =
_cogl_material_backends[material->backend];
/* To simplify things for the backends we are careful about how
* we report STATE_LAYERS changes.
*
* All STATE_LAYERS changes with the exception of ->n_layers
* will also result in layer_pre_change_notifications. For
* backends that perform code generation for fragment processing
* they typically need to understand the details of how layers
* get changed to determine if they need to repeat codegen. It
* doesn't help them to report a material STATE_LAYERS change
* for all layer changes since it's so broad, they really need
* to wait for the layer change to be notified. What does help
* though is to report a STATE_LAYERS change for a change in
* ->n_layers because they typically do need to repeat codegen
* in that case.
*
* This just ensures backends only get a single material or
* layer pre-change notification for any particular change.
*/
if (!from_layer_change)
backend->material_pre_change_notify (material, change, new_color);
}
/* 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.
*/
/* 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;
COGL_STATIC_COUNTER (material_copy_on_write_counter,
"material copy on write counter",
"Increments each time a material "
"must be copied to allow modification",
0 /* no application private data */);
COGL_COUNTER_INC (_cogl_uprof_context, material_copy_on_write_counter);
new_authority =
cogl_material_copy (_cogl_material_get_parent (material));
_cogl_material_set_static_breadcrumb (new_authority,
"pre_change_notify:copy-on-write");
/* We could explicitly walk the descendants, OR together the set
* of differences that we determine this material is the
* authority on and only copy those differences copied across.
*
* Or, if we don't explicitly walk the descendants we at least
* know that material->differences represents the largest set of
* differences that this material could possibly be an authority
* on.
*
* We do the later just because it's simplest, but we might need
* to come back to this later...
*/
_cogl_material_copy_differences (new_authority, material,
material->differences);
/* Reparent the dependants of material to be children of
* new_authority instead... */
_cogl_material_node_foreach_child (COGL_MATERIAL_NODE (material),
reparent_children_cb,
new_authority);
/* The children will keep the new authority alive so drop the
* reference we got when copying... */
cogl_object_unref (new_authority);
}
/* At this point we know we have a material with no strong
* dependants (though we may have some weak children) so we are now
* free to modify the material. */
material->age++;
/* If changing a sparse property and if the material isn't already an
* authority for the state group being modified then we need to
* initialize the corresponding state. */
if (change & COGL_MATERIAL_STATE_ALL_SPARSE &&
!(material->differences & change))
{
authority = _cogl_material_get_authority (material, change);
_cogl_material_initialize_sparse_state (material, authority, change);
}
/* Each material has a sorted cache of the layers it depends on
* which will need updating via _cogl_material_update_layers_cache
* if a material's layers are changed. */
if (change == COGL_MATERIAL_STATE_LAYERS)
recursively_free_layer_caches (material);
/* If the material being changed is the same as the last material we
* flushed then we keep a track of the changes so we can try to
* minimize redundant OpenGL calls if the same material is flushed
* again.
*/
if (ctx->current_material == material)
ctx->current_material_changes_since_flush |= change;
}
static void
_cogl_material_add_layer_difference (CoglMaterial *material,
CoglMaterialLayer *layer,
gboolean inc_n_layers)
{
g_return_if_fail (layer->owner == NULL);
layer->owner = material;
cogl_object_ref (layer);
/* - 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_STATE_LAYERS,
NULL,
FALSE);
material->differences |= COGL_MATERIAL_STATE_LAYERS;
material->layer_differences =
g_list_prepend (material->layer_differences, layer);
if (inc_n_layers)
material->n_layers++;
}
/* NB: If you are calling this it's your responsibility to have
* already called:
* _cogl_material_pre_change_notify (m, _CHANGE_LAYERS, NULL);
*/
static void
_cogl_material_remove_layer_difference (CoglMaterial *material,
CoglMaterialLayer *layer,
gboolean dec_n_layers)
{
g_return_if_fail (layer->owner == material);
/* - 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_STATE_LAYERS,
NULL,
FALSE);
layer->owner = NULL;
cogl_object_unref (layer);
material->differences |= COGL_MATERIAL_STATE_LAYERS;
material->layer_differences =
g_list_remove (material->layer_differences, layer);
if (dec_n_layers)
material->n_layers--;
}
static void
_cogl_material_try_reverting_layers_authority (CoglMaterial *authority,
CoglMaterial *old_authority)
{
if (authority->layer_differences == NULL &&
_cogl_material_get_parent (authority))
{
/* If the previous _STATE_LAYERS authority has the same
* ->n_layers then we can revert to that being the authority
* again. */
if (!old_authority)
{
old_authority =
_cogl_material_get_authority (_cogl_material_get_parent (authority),
COGL_MATERIAL_STATE_LAYERS);
}
if (old_authority->n_layers == authority->n_layers)
authority->differences &= ~COGL_MATERIAL_STATE_LAYERS;
}
}
static void
handle_automatic_blend_enable (CoglMaterial *material,
CoglMaterialState change)
{
gboolean blend_enable =
_cogl_material_needs_blending_enabled (material, change, NULL);
if (blend_enable != material->real_blend_enable)
{
/* - 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_STATE_REAL_BLEND_ENABLE,
NULL,
FALSE);
material->real_blend_enable = blend_enable;
}
}
typedef struct
{
int keep_n;
int current_pos;
gboolean needs_pruning;
int first_index_to_prune;
} CoglMaterialPruneLayersInfo;
static gboolean
update_prune_layers_info_cb (CoglMaterialLayer *layer, void *user_data)
{
CoglMaterialPruneLayersInfo *state = user_data;
if (state->current_pos == state->keep_n)
{
state->needs_pruning = TRUE;
state->first_index_to_prune = layer->index;
return FALSE;
}
state->current_pos++;
return TRUE;
}
void
_cogl_material_prune_to_n_layers (CoglMaterial *material, int n)
{
CoglMaterialPruneLayersInfo state;
gboolean notified_change = TRUE;
GList *l;
GList *next;
state.keep_n = n;
state.current_pos = 0;
state.needs_pruning = FALSE;
_cogl_material_foreach_layer_internal (material,
update_prune_layers_info_cb,
&state);
material->n_layers = n;
if (!state.needs_pruning)
return;
if (!(material->differences & COGL_MATERIAL_STATE_LAYERS))
return;
/* It's possible that this material owns some of the layers being
* discarded, so we'll need to unlink them... */
for (l = material->layer_differences; l; l = next)
{
CoglMaterialLayer *layer = l->data;
next = l->next; /* we're modifying the list we're iterating */
if (layer->index > state.first_index_to_prune)
{
if (!notified_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_STATE_LAYERS,
NULL,
FALSE);
notified_change = TRUE;
}
material->layer_differences =
g_list_delete_link (material->layer_differences, l);
}
}
}
static void
_cogl_material_backend_layer_change_notify (CoglMaterial *owner,
CoglMaterialLayer *layer,
CoglMaterialLayerState change)
{
int i;
/* NB: layers may be used by multiple materials which may be using
* different backends, therefore we determine which backends to
* notify based on the private state pointers for each backend...
*/
for (i = 0; i < COGL_MATERIAL_N_BACKENDS; i++)
{
if (layer->backend_priv[i] &&
_cogl_material_backends[i]->layer_pre_change_notify)
{
const CoglMaterialBackend *backend = _cogl_material_backends[i];
backend->layer_pre_change_notify (owner, layer, change);
}
}
}
unsigned int
_cogl_get_n_args_for_combine_func (GLint func)
{
switch (func)
{
case GL_REPLACE:
return 1;
case GL_MODULATE:
case GL_ADD:
case GL_ADD_SIGNED:
case GL_SUBTRACT:
case GL_DOT3_RGB:
case GL_DOT3_RGBA:
return 2;
case GL_INTERPOLATE:
return 3;
}
return 0;
}
static void
_cogl_material_layer_initialize_state (CoglMaterialLayer *dest,
CoglMaterialLayer *src,
unsigned long differences)
{
CoglMaterialLayerBigState *big_state;
dest->differences |= differences;
if (differences & COGL_MATERIAL_LAYER_STATE_UNIT)
dest->unit_index = src->unit_index;
if (differences & COGL_MATERIAL_LAYER_STATE_TEXTURE)
dest->texture = src->texture;
if (differences & COGL_MATERIAL_LAYER_STATE_FILTERS)
{
dest->min_filter = src->min_filter;
dest->mag_filter = src->mag_filter;
}
if (differences & COGL_MATERIAL_LAYER_STATE_WRAP_MODES)
{
dest->wrap_mode_s = src->wrap_mode_s;
dest->wrap_mode_t = src->wrap_mode_t;
dest->wrap_mode_p = src->wrap_mode_p;
}
if (differences & COGL_MATERIAL_LAYER_STATE_NEEDS_BIG_STATE)
{
if (!dest->has_big_state)
{
dest->big_state = g_slice_new (CoglMaterialLayerBigState);
dest->has_big_state = TRUE;
}
big_state = dest->big_state;
}
else
return;
if (differences & COGL_MATERIAL_LAYER_STATE_COMBINE)
{
int n_args;
int i;
GLint func = src->big_state->texture_combine_rgb_func;
big_state->texture_combine_rgb_func = func;
n_args = _cogl_get_n_args_for_combine_func (func);
for (i = 0; i < n_args; i++)
{
big_state->texture_combine_rgb_src[i] =
src->big_state->texture_combine_rgb_src[i];
big_state->texture_combine_rgb_op[i] =
src->big_state->texture_combine_rgb_op[i];
}
func = src->big_state->texture_combine_alpha_func;
big_state->texture_combine_alpha_func = func;
n_args = _cogl_get_n_args_for_combine_func (func);
for (i = 0; i < n_args; i++)
{
big_state->texture_combine_alpha_src[i] =
src->big_state->texture_combine_alpha_src[i];
big_state->texture_combine_alpha_op[i] =
src->big_state->texture_combine_alpha_op[i];
}
}
if (differences & COGL_MATERIAL_LAYER_STATE_COMBINE_CONSTANT)
memcpy (dest->big_state->texture_combine_constant,
src->big_state->texture_combine_constant,
sizeof (float) * 4);
if (differences & COGL_MATERIAL_LAYER_STATE_USER_MATRIX)
dest->big_state->matrix = src->big_state->matrix;
if (differences & COGL_MATERIAL_LAYER_STATE_POINT_SPRITE_COORDS)
dest->big_state->point_sprite_coords = src->big_state->point_sprite_coords;
}
/* NB: This function will allocate a new derived layer if you are
* trying to change the state of a layer with dependants so you must
* always check the return value.
*
* If a new layer is returned it will be owned by required_owner.
*
* required_owner can only by NULL for new, currently unowned layers
* with no dependants.
*/
static CoglMaterialLayer *
_cogl_material_layer_pre_change_notify (CoglMaterial *required_owner,
CoglMaterialLayer *layer,
CoglMaterialLayerState change)
{
CoglTextureUnit *unit;
CoglMaterialLayer *authority;
/* Identify the case where the layer is new with no owner or
* dependants and so we don't need to do anything. */
if (COGL_MATERIAL_NODE (layer)->has_children == FALSE &&
layer->owner == NULL)
goto init_layer_state;
/* We only allow a NULL required_owner for new layers */
g_return_val_if_fail (required_owner != NULL, layer);
/* Chain up:
* A modification of a layer is indirectly also a modification of
* its owner so first make sure to flush the journal of any
* references to the current owner state and if necessary perform
* a copy-on-write for the required_owner if it has dependants.
*/
_cogl_material_pre_change_notify (required_owner,
COGL_MATERIAL_STATE_LAYERS,
NULL,
TRUE);
/* Unlike materials; layers are simply considered immutable once
* they have dependants - either direct children, or another
* material as an owner.
*/
if (COGL_MATERIAL_NODE (layer)->has_children ||
layer->owner != required_owner)
{
CoglMaterialLayer *new = _cogl_material_layer_copy (layer);
if (layer->owner == required_owner)
_cogl_material_remove_layer_difference (required_owner, layer, FALSE);
_cogl_material_add_layer_difference (required_owner, new, FALSE);
cogl_object_unref (new);
layer = new;
goto init_layer_state;
}
/* Note: At this point we know there is only one material dependant on
* this layer (required_owner), and there are no other layers
* dependant on this layer so it's ok to modify it. */
_cogl_material_backend_layer_change_notify (required_owner, layer, change);
/* If the layer being changed is the same as the last layer we
* flushed to the corresponding texture unit then we keep a track of
* the changes so we can try to minimize redundant OpenGL calls if
* the same layer is flushed again.
*/
unit = _cogl_get_texture_unit (_cogl_material_layer_get_unit_index (layer));
if (unit->layer == layer)
unit->layer_changes_since_flush |= change;
init_layer_state:
if (required_owner)
required_owner->age++;
/* If the material isn't already an authority for the state group
* being modified then we need to initialize the corresponding
* state. */
authority = _cogl_material_layer_get_authority (layer, change);
_cogl_material_layer_initialize_state (layer, authority, change);
return layer;
}
static void
_cogl_material_layer_unparent (CoglMaterialNode *layer)
{
/* Chain up */
_cogl_material_node_unparent_real (layer);
}
static void
_cogl_material_layer_set_parent (CoglMaterialLayer *layer,
CoglMaterialLayer *parent)
{
/* Chain up */
_cogl_material_node_set_parent_real (COGL_MATERIAL_NODE (layer),
COGL_MATERIAL_NODE (parent),
_cogl_material_layer_unparent,
TRUE);
}
/* XXX: This is duplicated logic; the same as for
* _cogl_material_prune_redundant_ancestry it would be nice to find a
* way to consolidate these functions! */
static void
_cogl_material_layer_prune_redundant_ancestry (CoglMaterialLayer *layer)
{
CoglMaterialLayer *new_parent = _cogl_material_layer_get_parent (layer);
/* walk up past ancestors that are now redundant and potentially
* reparent the layer. */
while (_cogl_material_layer_get_parent (new_parent) &&
(new_parent->differences | layer->differences) ==
layer->differences)
new_parent = _cogl_material_layer_get_parent (new_parent);
_cogl_material_layer_set_parent (layer, new_parent);
}
/*
* XXX: consider special casing layer->unit_index so it's not a sparse
* property so instead we can assume it's valid for all layer
* instances.
* - We would need to initialize ->unit_index in
* _cogl_material_layer_copy ().
*
* XXX: If you use this API you should consider that the given layer
* might not be writeable and so a new derived layer will be allocated
* and modified instead. The layer modified will be returned so you
* can identify when this happens.
*/
static CoglMaterialLayer *
_cogl_material_set_layer_unit (CoglMaterial *required_owner,
CoglMaterialLayer *layer,
int unit_index)
{
CoglMaterialLayerState change = COGL_MATERIAL_LAYER_STATE_UNIT;
CoglMaterialLayer *authority =
_cogl_material_layer_get_authority (layer, change);
CoglMaterialLayer *new;
if (authority->unit_index == unit_index)
return layer;
new =
_cogl_material_layer_pre_change_notify (required_owner,
layer,
change);
if (new != layer)
layer = new;
else
{
/* If the layer we found is currently the authority on the state
* we are changing see if we can revert to one of our ancestors
* being the authority. */
if (layer == authority &&
_cogl_material_layer_get_parent (authority) != NULL)
{
CoglMaterialLayer *parent =
_cogl_material_layer_get_parent (authority);
CoglMaterialLayer *old_authority =
_cogl_material_layer_get_authority (parent, change);
if (old_authority->unit_index == unit_index)
{
layer->differences &= ~change;
return layer;
}
}
}
layer->unit_index = unit_index;
/* 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... */
if (layer != authority)
{
layer->differences |= change;
_cogl_material_layer_prune_redundant_ancestry (layer);
}
return layer;
}
typedef struct
{
/* The layer we are trying to find */
int layer_index;
/* The layer we find or untouched if not found */
CoglMaterialLayer *layer;
/* If the layer can't be found then a new layer should be
* inserted after this texture unit index... */
int insert_after;
/* When adding a layer we need the list of layers to shift up
* to a new texture unit. When removing we need the list of
* layers to shift down.
*
* Note: the list isn't sorted */
CoglMaterialLayer **layers_to_shift;
int n_layers_to_shift;
/* When adding a layer we don't need a complete list of
* layers_to_shift if we find a layer already corresponding to the
* layer_index. */
gboolean ignore_shift_layers_if_found;
} CoglMaterialLayerInfo;
/* Returns TRUE once we know there is nothing more to update */
static gboolean
update_layer_info (CoglMaterialLayer *layer,
CoglMaterialLayerInfo *layer_info)
{
if (layer->index == layer_info->layer_index)
{
layer_info->layer = layer;
if (layer_info->ignore_shift_layers_if_found)
return TRUE;
}
else if (layer->index < layer_info->layer_index)
{
int unit_index = _cogl_material_layer_get_unit_index (layer);
layer_info->insert_after = unit_index;
}
else
layer_info->layers_to_shift[layer_info->n_layers_to_shift++] =
layer;
return FALSE;
}
/* Returns FALSE to break out of a _foreach_layer () iteration */
static gboolean
update_layer_info_cb (CoglMaterialLayer *layer,
void *user_data)
{
CoglMaterialLayerInfo *layer_info = user_data;
if (update_layer_info (layer, layer_info))
return FALSE; /* break */
else
return TRUE; /* continue */
}
static void
_cogl_material_get_layer_info (CoglMaterial *material,
CoglMaterialLayerInfo *layer_info)
{
/* Note: we are assuming this material is a _STATE_LAYERS authority */
int n_layers = material->n_layers;
int i;
/* FIXME: _cogl_material_foreach_layer_internal now calls
* _cogl_material_update_layers_cache anyway so this codepath is
* pointless! */
if (layer_info->ignore_shift_layers_if_found &&
material->layers_cache_dirty)
{
/* The expectation is that callers of
* _cogl_material_get_layer_info are likely to be modifying the
* list of layers associated with a material so in this case
* where we don't have a cache of the layers and we don't
* necessarily have to iterate all the layers of the material we
* use a foreach_layer callback instead of updating the cache
* and iterating that as below. */
_cogl_material_foreach_layer_internal (material,
update_layer_info_cb,
layer_info);
return;
}
_cogl_material_update_layers_cache (material);
for (i = 0; i < n_layers; i++)
{
CoglMaterialLayer *layer = material->layers_cache[i];
if (update_layer_info (layer, layer_info))
return;
}
}
static CoglMaterialLayer *
_cogl_material_get_layer (CoglMaterial *material,
int layer_index)
{
CoglMaterial *authority =
_cogl_material_get_authority (material, COGL_MATERIAL_STATE_LAYERS);
CoglMaterialLayerInfo layer_info;
CoglMaterialLayer *layer;
int unit_index;
int i;
_COGL_GET_CONTEXT (ctx, NULL);
/* The layer index of the layer we want info about */
layer_info.layer_index = layer_index;
/* If a layer already exists with the given index this will be
* updated. */
layer_info.layer = NULL;
/* If a layer isn't found for the given index we'll need to know
* where to insert a new layer. */
layer_info.insert_after = -1;
/* If a layer can't be found then we'll need to insert a new layer
* and bump up the texture unit for all layers with an index
* > layer_index. */
layer_info.layers_to_shift =
g_alloca (sizeof (CoglMaterialLayer *) * authority->n_layers);
layer_info.n_layers_to_shift = 0;
/* If an exact match is found though we don't need a complete
* list of layers with indices > layer_index... */
layer_info.ignore_shift_layers_if_found = TRUE;
_cogl_material_get_layer_info (authority, &layer_info);
if (layer_info.layer)
return layer_info.layer;
unit_index = layer_info.insert_after + 1;
if (unit_index == 0)
layer = _cogl_material_layer_copy (ctx->default_layer_0);
else
{
CoglMaterialLayer *new;
layer = _cogl_material_layer_copy (ctx->default_layer_n);
new = _cogl_material_set_layer_unit (NULL, layer, unit_index);
/* Since we passed a newly allocated layer we wouldn't expect
* _set_layer_unit() to have to allocate *another* layer. */
g_assert (new == layer);
}
layer->index = layer_index;
for (i = 0; i < layer_info.n_layers_to_shift; i++)
{
CoglMaterialLayer *shift_layer = layer_info.layers_to_shift[i];
unit_index = _cogl_material_layer_get_unit_index (shift_layer);
_cogl_material_set_layer_unit (material, shift_layer, unit_index + 1);
/* NB: shift_layer may not be writeable so _set_layer_unit()
* will allocate a derived layer internally which will become
* owned by material. Check the return value if we need to do
* anything else with this layer. */
}
_cogl_material_add_layer_difference (material, layer, TRUE);
cogl_object_unref (layer);
return layer;
}
CoglHandle
_cogl_material_layer_get_texture (CoglMaterialLayer *layer)
{
CoglMaterialLayer *authority =
_cogl_material_layer_get_authority (layer,
COGL_MATERIAL_LAYER_STATE_TEXTURE);
return authority->texture;
}
CoglHandle
_cogl_material_get_layer_texture (CoglMaterial *material,
int layer_index)
{
CoglMaterialLayer *layer =
_cogl_material_get_layer (material, layer_index);
return _cogl_material_layer_get_texture (layer);
}
static void
_cogl_material_prune_empty_layer_difference (CoglMaterial *layers_authority,
CoglMaterialLayer *layer)
{
/* Find the GList link that references the empty layer */
GList *link = g_list_find (layers_authority->layer_differences, layer);
/* No material directly owns the root node layer so this is safe... */
CoglMaterialLayer *layer_parent = _cogl_material_layer_get_parent (layer);
CoglMaterialLayerInfo layer_info;
CoglMaterial *old_layers_authority;
g_return_if_fail (link != NULL);
/* If the layer's parent doesn't have an owner then we can simply
* take ownership ourselves and drop our reference on the empty
* layer.
*/
if (layer_parent->index == layer->index && layer_parent->owner == NULL)
{
cogl_object_ref (layer_parent);
link->data = _cogl_material_layer_get_parent (layer);
cogl_object_unref (layer);
recursively_free_layer_caches (layers_authority);
return;
}
/* Now we want to find the layer that would become the authority for
* layer->index if we were to remove layer from
* layers_authority->layer_differences
*/
/* The layer index of the layer we want info about */
layer_info.layer_index = layer->index;
/* If a layer already exists with the given index this will be
* updated. */
layer_info.layer = NULL;
/* If a layer can't be found then we'll need to insert a new layer
* and bump up the texture unit for all layers with an index
* > layer_index. */
layer_info.layers_to_shift =
g_alloca (sizeof (CoglMaterialLayer *) * layers_authority->n_layers);
layer_info.n_layers_to_shift = 0;
/* If an exact match is found though we don't need a complete
* list of layers with indices > layer_index... */
layer_info.ignore_shift_layers_if_found = TRUE;
/* We know the default/root material isn't a LAYERS authority so it's
* safe to use the result of _cogl_material_get_parent (layers_authority)
* without checking it.
*/
old_layers_authority =
_cogl_material_get_authority (_cogl_material_get_parent (layers_authority),
COGL_MATERIAL_STATE_LAYERS);
_cogl_material_get_layer_info (old_layers_authority, &layer_info);
/* If layer is the defining layer for the corresponding ->index then
* we can't get rid of it. */
if (!layer_info.layer)
return;
/* If the layer that would become the authority for layer->index is
* _cogl_material_layer_get_parent (layer) then we can simply remove the
* layer difference. */
if (layer_info.layer == _cogl_material_layer_get_parent (layer))
{
_cogl_material_remove_layer_difference (layers_authority, layer, FALSE);
_cogl_material_try_reverting_layers_authority (layers_authority,
old_layers_authority);
}
}
static void
_cogl_material_set_layer_texture (CoglMaterial *material,
int layer_index,
CoglHandle texture,
gboolean overriden,
GLuint slice_gl_texture,
GLenum slice_gl_target)
{
CoglMaterialLayerState change = COGL_MATERIAL_LAYER_STATE_TEXTURE;
CoglMaterialLayer *layer;
CoglMaterialLayer *authority;
CoglMaterialLayer *new;
/* Note: this will ensure that the layer exists, creating one if it
* doesn't already.
*
* Note: If the layer already existed it's possibly owned by another
* material. If the layer is created then it will be owned by
* material. */
layer = _cogl_material_get_layer (material, layer_index);
/* Now find the ancestor of the layer that is the authority for the
* state we want to change */
authority = _cogl_material_layer_get_authority (layer, change);
if (authority->texture_overridden == overriden &&
authority->texture == texture &&
(authority->texture_overridden == FALSE ||
(authority->slice_gl_texture == slice_gl_texture &&
authority->slice_gl_target == slice_gl_target)))
return;
new = _cogl_material_layer_pre_change_notify (material, layer, change);
if (new != layer)
layer = new;
else
{
/* If the original layer we found is currently the authority on
* the state we are changing see if we can revert to one of our
* ancestors being the authority. */
if (layer == authority &&
_cogl_material_layer_get_parent (authority) != NULL)
{
CoglMaterialLayer *parent =
_cogl_material_layer_get_parent (authority);
CoglMaterialLayer *old_authority =
_cogl_material_layer_get_authority (parent, change);
if (old_authority->texture_overridden == overriden &&
old_authority->texture == texture &&
(old_authority->texture_overridden == FALSE ||
(old_authority->slice_gl_texture == slice_gl_texture &&
old_authority->slice_gl_target == slice_gl_target)))
{
layer->differences &= ~change;
if (layer->texture != COGL_INVALID_HANDLE)
cogl_handle_unref (layer->texture);
g_assert (layer->owner == material);
if (layer->differences == 0)
_cogl_material_prune_empty_layer_difference (material,
layer);
goto changed;
}
}
}
if (texture != COGL_INVALID_HANDLE)
cogl_handle_ref (texture);
if (layer == authority &&
layer->texture != COGL_INVALID_HANDLE)
cogl_handle_unref (layer->texture);
layer->texture = texture;
layer->texture_overridden = overriden;
layer->slice_gl_texture = slice_gl_texture;
layer->slice_gl_target = slice_gl_target;
/* 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... */
if (layer != authority)
{
layer->differences |= change;
_cogl_material_layer_prune_redundant_ancestry (layer);
}
changed:
handle_automatic_blend_enable (material, COGL_MATERIAL_STATE_LAYERS);
}
static void
_cogl_material_set_layer_gl_texture_slice (CoglMaterial *material,
int layer_index,
CoglHandle texture,
GLuint slice_gl_texture,
GLenum slice_gl_target)
{
g_return_if_fail (cogl_is_material (material));
/* GL texture overrides can only be set in association with a parent
* CoglTexture */
g_return_if_fail (cogl_is_texture (texture));
_cogl_material_set_layer_texture (material,
layer_index,
texture,
TRUE, /* slice override */
slice_gl_texture,
slice_gl_target);
}
/* XXX: deprecate and replace with cogl_material_set_layer_texture?
*
* Originally I was planning on allowing users to set shaders somehow
* on layers (thus the ambiguous name), but now I wonder if we will do
* that with a more explicit "snippets" API and materials will have
* hooks defined to receive these snippets.
*/
void
cogl_material_set_layer (CoglMaterial *material,
int layer_index,
CoglHandle texture)
{
g_return_if_fail (cogl_is_material (material));
g_return_if_fail (texture == COGL_INVALID_HANDLE ||
cogl_is_texture (texture));
_cogl_material_set_layer_texture (material,
layer_index,
texture,
FALSE, /* slice override */
0, /* slice_gl_texture */
0); /* slice_gl_target */
}
typedef struct
{
int i;
CoglMaterial *material;
unsigned long fallback_layers;
} CoglMaterialFallbackState;
static gboolean
fallback_layer_cb (CoglMaterialLayer *layer, void *user_data)
{
CoglMaterialFallbackState *state = user_data;
CoglMaterial *material = state->material;
CoglHandle texture = _cogl_material_layer_get_texture (layer);
GLenum gl_target;
COGL_STATIC_COUNTER (layer_fallback_counter,
"layer fallback counter",
"Increments each time a layer's texture is "
"forced to a fallback texture",
0 /* no application private data */);
_COGL_GET_CONTEXT (ctx, FALSE);
if (!(state->fallback_layers & 1<i))
return TRUE;
COGL_COUNTER_INC (_cogl_uprof_context, layer_fallback_counter);
if (G_LIKELY (texture != COGL_INVALID_HANDLE))
cogl_texture_get_gl_texture (texture, NULL, &gl_target);
else
gl_target = GL_TEXTURE_2D;
if (gl_target == GL_TEXTURE_2D)
texture = ctx->default_gl_texture_2d_tex;
#ifdef HAVE_COGL_GL
else if (gl_target == GL_TEXTURE_RECTANGLE_ARB)
texture = ctx->default_gl_texture_rect_tex;
#endif
else
{
g_warning ("We don't have a fallback texture we can use to fill "
"in for an invalid material layer, since it was "
"using an unsupported texture target ");
/* might get away with this... */
texture = ctx->default_gl_texture_2d_tex;
}
cogl_material_set_layer (material, layer->index, texture);
state->i++;
return TRUE;
}
void
_cogl_material_set_layer_wrap_modes (CoglMaterial *material,
CoglMaterialLayer *layer,
CoglMaterialLayer *authority,
CoglMaterialWrapModeInternal wrap_mode_s,
CoglMaterialWrapModeInternal wrap_mode_t,
CoglMaterialWrapModeInternal wrap_mode_p)
{
CoglMaterialLayer *new;
CoglMaterialLayerState change = COGL_MATERIAL_LAYER_STATE_WRAP_MODES;
if (authority->wrap_mode_s == wrap_mode_s &&
authority->wrap_mode_t == wrap_mode_t &&
authority->wrap_mode_p == wrap_mode_p)
return;
new = _cogl_material_layer_pre_change_notify (material, layer, change);
if (new != layer)
layer = new;
else
{
/* If the original layer we found is currently the authority on
* the state we are changing see if we can revert to one of our
* ancestors being the authority. */
if (layer == authority &&
_cogl_material_layer_get_parent (authority) != NULL)
{
CoglMaterialLayer *parent =
_cogl_material_layer_get_parent (authority);
CoglMaterialLayer *old_authority =
_cogl_material_layer_get_authority (parent, change);
if (old_authority->wrap_mode_s == wrap_mode_s &&
old_authority->wrap_mode_t == wrap_mode_t &&
old_authority->wrap_mode_p == wrap_mode_p)
{
layer->differences &= ~change;
g_assert (layer->owner == material);
if (layer->differences == 0)
_cogl_material_prune_empty_layer_difference (material,
layer);
return;
}
}
}
layer->wrap_mode_s = wrap_mode_s;
layer->wrap_mode_t = wrap_mode_t;
layer->wrap_mode_p = wrap_mode_p;
/* 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... */
if (layer != authority)
{
layer->differences |= change;
_cogl_material_layer_prune_redundant_ancestry (layer);
}
}
static CoglMaterialWrapModeInternal
public_to_internal_wrap_mode (CoglMaterialWrapMode mode)
{
return (CoglMaterialWrapModeInternal)mode;
}
static CoglMaterialWrapMode
internal_to_public_wrap_mode (CoglMaterialWrapModeInternal internal_mode)
{
g_return_val_if_fail (internal_mode !=
COGL_MATERIAL_WRAP_MODE_INTERNAL_CLAMP_TO_BORDER,
COGL_MATERIAL_WRAP_MODE_AUTOMATIC);
return (CoglMaterialWrapMode)internal_mode;
}
void
cogl_material_set_layer_wrap_mode_s (CoglMaterial *material,
int layer_index,
CoglMaterialWrapMode mode)
{
CoglMaterialLayerState change = COGL_MATERIAL_LAYER_STATE_WRAP_MODES;
CoglMaterialLayer *layer;
CoglMaterialLayer *authority;
CoglMaterialWrapModeInternal internal_mode =
public_to_internal_wrap_mode (mode);
g_return_if_fail (cogl_is_material (material));
/* Note: this will ensure that the layer exists, creating one if it
* doesn't already.
*
* Note: If the layer already existed it's possibly owned by another
* material. If the layer is created then it will be owned by
* material. */
layer = _cogl_material_get_layer (material, layer_index);
/* Now find the ancestor of the layer that is the authority for the
* state we want to change */
authority = _cogl_material_layer_get_authority (layer, change);
_cogl_material_set_layer_wrap_modes (material, layer, authority,
internal_mode,
authority->wrap_mode_t,
authority->wrap_mode_p);
}
void
cogl_material_set_layer_wrap_mode_t (CoglMaterial *material,
int layer_index,
CoglMaterialWrapMode mode)
{
CoglMaterialLayerState change = COGL_MATERIAL_LAYER_STATE_WRAP_MODES;
CoglMaterialLayer *layer;
CoglMaterialLayer *authority;
CoglMaterialWrapModeInternal internal_mode =
public_to_internal_wrap_mode (mode);
g_return_if_fail (cogl_is_material (material));
/* Note: this will ensure that the layer exists, creating one if it
* doesn't already.
*
* Note: If the layer already existed it's possibly owned by another
* material. If the layer is created then it will be owned by
* material. */
layer = _cogl_material_get_layer (material, layer_index);
/* Now find the ancestor of the layer that is the authority for the
* state we want to change */
authority = _cogl_material_layer_get_authority (layer, change);
_cogl_material_set_layer_wrap_modes (material, layer, authority,
authority->wrap_mode_s,
internal_mode,
authority->wrap_mode_p);
}
/* The rationale for naming the third texture coordinate 'p' instead
of OpenGL's usual 'r' is that 'r' conflicts with the usual naming
of the 'red' component when treating a vector as a color. Under
GLSL this is awkward because the texture swizzling for a vector
uses a single letter for each component and the names for colors,
textures and positions are synonymous. GLSL works around this by
naming the components of the texture s, t, p and q. Cogl already
effectively already exposes this naming because it exposes GLSL so
it makes sense to use that naming consistently. Another alternative
could be u, v and w. This is what Blender and Direct3D use. However
the w component conflicts with the w component of a position
vertex. */
void
cogl_material_set_layer_wrap_mode_p (CoglMaterial *material,
int layer_index,
CoglMaterialWrapMode mode)
{
CoglMaterialLayerState change = COGL_MATERIAL_LAYER_STATE_WRAP_MODES;
CoglMaterialLayer *layer;
CoglMaterialLayer *authority;
CoglMaterialWrapModeInternal internal_mode =
public_to_internal_wrap_mode (mode);
g_return_if_fail (cogl_is_material (material));
/* Note: this will ensure that the layer exists, creating one if it
* doesn't already.
*
* Note: If the layer already existed it's possibly owned by another
* material. If the layer is created then it will be owned by
* material. */
layer = _cogl_material_get_layer (material, layer_index);
/* Now find the ancestor of the layer that is the authority for the
* state we want to change */
authority = _cogl_material_layer_get_authority (layer, change);
_cogl_material_set_layer_wrap_modes (material, layer, authority,
authority->wrap_mode_s,
authority->wrap_mode_t,
internal_mode);
}
void
cogl_material_set_layer_wrap_mode (CoglMaterial *material,
int layer_index,
CoglMaterialWrapMode mode)
{
CoglMaterialLayerState change = COGL_MATERIAL_LAYER_STATE_WRAP_MODES;
CoglMaterialLayer *layer;
CoglMaterialLayer *authority;
CoglMaterialWrapModeInternal internal_mode =
public_to_internal_wrap_mode (mode);
g_return_if_fail (cogl_is_material (material));
/* Note: this will ensure that the layer exists, creating one if it
* doesn't already.
*
* Note: If the layer already existed it's possibly owned by another
* material. If the layer is created then it will be owned by
* material. */
layer = _cogl_material_get_layer (material, layer_index);
/* Now find the ancestor of the layer that is the authority for the
* state we want to change */
authority = _cogl_material_layer_get_authority (layer, change);
_cogl_material_set_layer_wrap_modes (material, layer, authority,
internal_mode,
internal_mode,
internal_mode);
/* XXX: I wonder if we should really be duplicating the mode into
* the 'r' wrap mode too? */
}
/* FIXME: deprecate this API */
CoglMaterialWrapMode
cogl_material_layer_get_wrap_mode_s (CoglMaterialLayer *layer)
{
CoglMaterialLayerState change = COGL_MATERIAL_LAYER_STATE_WRAP_MODES;
CoglMaterialLayer *authority;
g_return_val_if_fail (_cogl_is_material_layer (layer), FALSE);
/* Now find the ancestor of the layer that is the authority for the
* state we want to change */
authority = _cogl_material_layer_get_authority (layer, change);
return internal_to_public_wrap_mode (authority->wrap_mode_s);
}
CoglMaterialWrapMode
cogl_material_get_layer_wrap_mode_s (CoglMaterial *material, int layer_index)
{
CoglMaterialLayer *layer;
g_return_val_if_fail (cogl_is_material (material), FALSE);
/* Note: this will ensure that the layer exists, creating one if it
* doesn't already.
*
* Note: If the layer already existed it's possibly owned by another
* material. If the layer is created then it will be owned by
* material. */
layer = _cogl_material_get_layer (material, layer_index);
/* FIXME: we shouldn't ever construct a layer in a getter function */
return cogl_material_layer_get_wrap_mode_s (layer);
}
/* FIXME: deprecate this API */
CoglMaterialWrapMode
cogl_material_layer_get_wrap_mode_t (CoglMaterialLayer *layer)
{
CoglMaterialLayerState change = COGL_MATERIAL_LAYER_STATE_WRAP_MODES;
CoglMaterialLayer *authority;
g_return_val_if_fail (_cogl_is_material_layer (layer), FALSE);
/* Now find the ancestor of the layer that is the authority for the
* state we want to change */
authority = _cogl_material_layer_get_authority (layer, change);
return internal_to_public_wrap_mode (authority->wrap_mode_t);
}
CoglMaterialWrapMode
cogl_material_get_layer_wrap_mode_t (CoglMaterial *material, int layer_index)
{
CoglMaterialLayer *layer;
g_return_val_if_fail (cogl_is_material (material), FALSE);
/* Note: this will ensure that the layer exists, creating one if it
* doesn't already.
*
* Note: If the layer already existed it's possibly owned by another
* material. If the layer is created then it will be owned by
* material. */
layer = _cogl_material_get_layer (material, layer_index);
/* FIXME: we shouldn't ever construct a layer in a getter function */
return cogl_material_layer_get_wrap_mode_t (layer);
}
CoglMaterialWrapMode
cogl_material_layer_get_wrap_mode_p (CoglMaterialLayer *layer)
{
CoglMaterialLayerState change = COGL_MATERIAL_LAYER_STATE_WRAP_MODES;
CoglMaterialLayer *authority =
_cogl_material_layer_get_authority (layer, change);
return internal_to_public_wrap_mode (authority->wrap_mode_p);
}
CoglMaterialWrapMode
cogl_material_get_layer_wrap_mode_p (CoglMaterial *material, int layer_index)
{
CoglMaterialLayer *layer;
g_return_val_if_fail (cogl_is_material (material), FALSE);
/* Note: this will ensure that the layer exists, creating one if it
* doesn't already.
*
* Note: If the layer already existed it's possibly owned by another
* material. If the layer is created then it will be owned by
* material. */
layer = _cogl_material_get_layer (material, layer_index);
return cogl_material_layer_get_wrap_mode_p (layer);
}
void
_cogl_material_layer_get_wrap_modes (CoglMaterialLayer *layer,
CoglMaterialWrapModeInternal *wrap_mode_s,
CoglMaterialWrapModeInternal *wrap_mode_t,
CoglMaterialWrapModeInternal *wrap_mode_p)
{
CoglMaterialLayer *authority =
_cogl_material_layer_get_authority (layer,
COGL_MATERIAL_LAYER_STATE_WRAP_MODES);
*wrap_mode_s = authority->wrap_mode_s;
*wrap_mode_t = authority->wrap_mode_t;
*wrap_mode_p = authority->wrap_mode_p;
}
gboolean
cogl_material_set_layer_point_sprite_coords_enabled (CoglMaterial *material,
int layer_index,
gboolean enable,
GError **error)
{
CoglMaterialLayerState change =
COGL_MATERIAL_LAYER_STATE_POINT_SPRITE_COORDS;
CoglMaterialLayer *layer;
CoglMaterialLayer *new;
CoglMaterialLayer *authority;
g_return_val_if_fail (cogl_is_material (material), FALSE);
/* Don't allow point sprite coordinates to be enabled if the driver
doesn't support it */
if (enable && !cogl_features_available (COGL_FEATURE_POINT_SPRITE))
{
if (error)
{
g_set_error (error, COGL_ERROR, COGL_ERROR_UNSUPPORTED,
"Point sprite texture coordinates are enabled "
"for a layer but the GL driver does not support it.");
}
else
{
static gboolean warning_seen = FALSE;
if (!warning_seen)
g_warning ("Point sprite texture coordinates are enabled "
"for a layer but the GL driver does not support it.");
warning_seen = TRUE;
}
return FALSE;
}
/* Note: this will ensure that the layer exists, creating one if it
* doesn't already.
*
* Note: If the layer already existed it's possibly owned by another
* material. If the layer is created then it will be owned by
* material. */
layer = _cogl_material_get_layer (material, layer_index);
/* Now find the ancestor of the layer that is the authority for the
* state we want to change */
authority = _cogl_material_layer_get_authority (layer, change);
if (authority->big_state->point_sprite_coords == enable)
return TRUE;
new = _cogl_material_layer_pre_change_notify (material, layer, change);
if (new != layer)
layer = new;
else
{
/* If the original layer we found is currently the authority on
* the state we are changing see if we can revert to one of our
* ancestors being the authority. */
if (layer == authority &&
_cogl_material_layer_get_parent (authority) != NULL)
{
CoglMaterialLayer *parent =
_cogl_material_layer_get_parent (authority);
CoglMaterialLayer *old_authority =
_cogl_material_layer_get_authority (parent, change);
if (old_authority->big_state->point_sprite_coords == enable)
{
layer->differences &= ~change;
g_assert (layer->owner == material);
if (layer->differences == 0)
_cogl_material_prune_empty_layer_difference (material,
layer);
return TRUE;
}
}
}
layer->big_state->point_sprite_coords = enable;
/* 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... */
if (layer != authority)
{
layer->differences |= change;
_cogl_material_layer_prune_redundant_ancestry (layer);
}
return TRUE;
}
gboolean
cogl_material_get_layer_point_sprite_coords_enabled (CoglMaterial *material,
int layer_index)
{
CoglMaterialLayerState change =
COGL_MATERIAL_LAYER_STATE_POINT_SPRITE_COORDS;
CoglMaterialLayer *layer;
CoglMaterialLayer *authority;
g_return_val_if_fail (cogl_is_material (material), FALSE);
/* Note: this will ensure that the layer exists, creating one if it
* doesn't already.
*
* Note: If the layer already existed it's possibly owned by another
* material. If the layer is created then it will be owned by
* material. */
layer = _cogl_material_get_layer (material, layer_index);
/* FIXME: we shouldn't ever construct a layer in a getter function */
authority = _cogl_material_layer_get_authority (layer, change);
return authority->big_state->point_sprite_coords;
}
typedef struct
{
CoglMaterial *material;
CoglMaterialWrapModeOverrides *wrap_mode_overrides;
int i;
} CoglMaterialWrapModeOverridesState;
static gboolean
apply_wrap_mode_overrides_cb (CoglMaterialLayer *layer,
void *user_data)
{
CoglMaterialWrapModeOverridesState *state = user_data;
CoglMaterialLayer *authority =
_cogl_material_layer_get_authority (layer,
COGL_MATERIAL_LAYER_STATE_WRAP_MODES);
CoglMaterialWrapModeInternal wrap_mode_s;
CoglMaterialWrapModeInternal wrap_mode_t;
CoglMaterialWrapModeInternal wrap_mode_p;
g_return_val_if_fail (state->i < 32, FALSE);
wrap_mode_s = state->wrap_mode_overrides->values[state->i].s;
if (wrap_mode_s == COGL_MATERIAL_WRAP_MODE_OVERRIDE_NONE)
wrap_mode_s = (CoglMaterialWrapModeInternal)authority->wrap_mode_s;
wrap_mode_t = state->wrap_mode_overrides->values[state->i].t;
if (wrap_mode_t == COGL_MATERIAL_WRAP_MODE_OVERRIDE_NONE)
wrap_mode_t = (CoglMaterialWrapModeInternal)authority->wrap_mode_t;
wrap_mode_p = state->wrap_mode_overrides->values[state->i].p;
if (wrap_mode_p == COGL_MATERIAL_WRAP_MODE_OVERRIDE_NONE)
wrap_mode_p = (CoglMaterialWrapModeInternal)authority->wrap_mode_p;
_cogl_material_set_layer_wrap_modes (state->material,
layer,
authority,
wrap_mode_s,
wrap_mode_t,
wrap_mode_p);
state->i++;
return TRUE;
}
typedef struct
{
CoglMaterial *material;
GLuint gl_texture;
} CoglMaterialOverrideLayerState;
static gboolean
override_layer_texture_cb (CoglMaterialLayer *layer, void *user_data)
{
CoglMaterialOverrideLayerState *state = user_data;
CoglHandle texture;
GLenum gl_target;
texture = _cogl_material_layer_get_texture (layer);
if (texture != COGL_INVALID_HANDLE)
cogl_texture_get_gl_texture (texture, NULL, &gl_target);
else
gl_target = GL_TEXTURE_2D;
_cogl_material_set_layer_gl_texture_slice (state->material,
layer->index,
texture,
state->gl_texture,
gl_target);
return TRUE;
}
void
_cogl_material_apply_overrides (CoglMaterial *material,
CoglMaterialFlushOptions *options)
{
COGL_STATIC_COUNTER (apply_overrides_counter,
"material overrides counter",
"Increments each time we have to apply "
"override options to a material",
0 /* no application private data */);
COGL_COUNTER_INC (_cogl_uprof_context, apply_overrides_counter);
if (options->flags & COGL_MATERIAL_FLUSH_DISABLE_MASK)
{
int i;
/* NB: we can assume that once we see one bit to disable
* a layer, all subsequent layers are also disabled. */
for (i = 0; i < 32 && options->disable_layers & (1<flags & COGL_MATERIAL_FLUSH_FALLBACK_MASK)
{
CoglMaterialFallbackState state;
state.i = 0;
state.material = material;
state.fallback_layers = options->fallback_layers;
_cogl_material_foreach_layer_internal (material,
fallback_layer_cb,
&state);
}
if (options->flags & COGL_MATERIAL_FLUSH_LAYER0_OVERRIDE)
{
CoglMaterialOverrideLayerState state;
_cogl_material_prune_to_n_layers (material, 1);
/* NB: we are overriding the first layer, but we don't know
* the user's given layer_index, which is why we use
* _cogl_material_foreach_layer_internal() here even though we know
* there's only one layer. */
state.material = material;
state.gl_texture = options->layer0_override_texture;
_cogl_material_foreach_layer_internal (material,
override_layer_texture_cb,
&state);
}
if (options->flags & COGL_MATERIAL_FLUSH_WRAP_MODE_OVERRIDES)
{
CoglMaterialWrapModeOverridesState state;
state.material = material;
state.wrap_mode_overrides = &options->wrap_mode_overrides;
state.i = 0;
_cogl_material_foreach_layer_internal (material,
apply_wrap_mode_overrides_cb,
&state);
}
}
static gboolean
_cogl_material_layer_texture_equal (CoglMaterialLayer *authority0,
CoglMaterialLayer *authority1)
{
GLuint gl_handle0, gl_handle1;
GLenum gl_target0, gl_target1;
if (authority0->texture_overridden)
{
gl_handle0 = authority0->slice_gl_texture;
gl_target0 = authority0->slice_gl_target;
}
else
cogl_texture_get_gl_texture (authority0->texture, &gl_handle0, &gl_target0);
if (authority1->texture_overridden)
{
gl_handle1 = authority1->slice_gl_texture;
gl_target1 = authority1->slice_gl_target;
}
else
cogl_texture_get_gl_texture (authority1->texture, &gl_handle1, &gl_target1);
return gl_handle0 == gl_handle1 && gl_target0 == gl_target1;
}
/* Determine the mask of differences between two layers.
*
* XXX: If layers and materials could both be cast to a common Tree
* type of some kind then we could have a unified
* compare_differences() function.
*/
unsigned long
_cogl_material_layer_compare_differences (CoglMaterialLayer *layer0,
CoglMaterialLayer *layer1)
{
CoglMaterialLayer *node0;
CoglMaterialLayer *node1;
int len0;
int len1;
int len0_index;
int len1_index;
int count;
int i;
CoglMaterialLayer *common_ancestor = NULL;
unsigned long layers_difference = 0;
_COGL_GET_CONTEXT (ctx, 0);
/* Algorithm:
*
* 1) Walk the ancestors of each layer to the root node, adding a
* pointer to each ancester node to two GArrays:
* ctx->material0_nodes, and ctx->material1_nodes.
*
* 2) Compare the arrays to find the nodes where they stop to
* differ.
*
* 3) For each array now iterate from index 0 to the first node of
* difference ORing that nodes ->difference mask into the final
* material_differences mask.
*/
g_array_set_size (ctx->material0_nodes, 0);
g_array_set_size (ctx->material1_nodes, 0);
for (node0 = layer0; node0; node0 = _cogl_material_layer_get_parent (node0))
g_array_append_vals (ctx->material0_nodes, &node0, 1);
for (node1 = layer1; node1; node1 = _cogl_material_layer_get_parent (node1))
g_array_append_vals (ctx->material1_nodes, &node1, 1);
len0 = ctx->material0_nodes->len;
len1 = ctx->material1_nodes->len;
/* There's no point looking at the last entries since we know both
* layers must have the same default layer as their root node. */
len0_index = len0 - 2;
len1_index = len1 - 2;
count = MIN (len0, len1) - 1;
for (i = 0; i < count; i++)
{
node0 = g_array_index (ctx->material0_nodes,
CoglMaterialLayer *, len0_index--);
node1 = g_array_index (ctx->material1_nodes,
CoglMaterialLayer *, len1_index--);
if (node0 != node1)
{
common_ancestor = _cogl_material_layer_get_parent (node0);
break;
}
}
/* If we didn't already find the first the common_ancestor ancestor
* that's because one material is a direct descendant of the other
* and in this case the first common ancestor is the last node we
* looked at. */
if (!common_ancestor)
common_ancestor = node0;
count = len0 - 1;
for (i = 0; i < count; i++)
{
node0 = g_array_index (ctx->material0_nodes, CoglMaterialLayer *, i);
if (node0 == common_ancestor)
break;
layers_difference |= node0->differences;
}
count = len1 - 1;
for (i = 0; i < count; i++)
{
node1 = g_array_index (ctx->material1_nodes, CoglMaterialLayer *, i);
if (node1 == common_ancestor)
break;
layers_difference |= node1->differences;
}
return layers_difference;
}
static gboolean
_cogl_material_layer_combine_state_equal (CoglMaterialLayer *authority0,
CoglMaterialLayer *authority1)
{
CoglMaterialLayerBigState *big_state0 = authority0->big_state;
CoglMaterialLayerBigState *big_state1 = authority1->big_state;
int n_args;
int i;
if (big_state0->texture_combine_rgb_func !=
big_state1->texture_combine_rgb_func)
return FALSE;
if (big_state0->texture_combine_alpha_func !=
big_state1->texture_combine_alpha_func)
return FALSE;
n_args =
_cogl_get_n_args_for_combine_func (big_state0->texture_combine_rgb_func);
for (i = 0; i < n_args; i++)
{
if ((big_state0->texture_combine_rgb_src[i] !=
big_state1->texture_combine_rgb_src[i]) ||
(big_state0->texture_combine_rgb_op[i] !=
big_state1->texture_combine_rgb_op[i]))
return FALSE;
}
n_args =
_cogl_get_n_args_for_combine_func (big_state0->texture_combine_alpha_func);
for (i = 0; i < n_args; i++)
{
if ((big_state0->texture_combine_alpha_src[i] !=
big_state1->texture_combine_alpha_src[i]) ||
(big_state0->texture_combine_alpha_op[i] !=
big_state1->texture_combine_alpha_op[i]))
return FALSE;
}
return TRUE;
}
static gboolean
_cogl_material_layer_combine_constant_equal (CoglMaterialLayer *authority0,
CoglMaterialLayer *authority1)
{
return memcmp (authority0->big_state->texture_combine_constant,
authority1->big_state->texture_combine_constant,
sizeof (float) * 4) == 0 ? TRUE : FALSE;
}
static gboolean
_cogl_material_layer_filters_equal (CoglMaterialLayer *authority0,
CoglMaterialLayer *authority1)
{
if (authority0->mag_filter != authority1->mag_filter)
return FALSE;
if (authority0->min_filter != authority1->min_filter)
return FALSE;
return TRUE;
}
static gboolean
_cogl_material_layer_wrap_modes_equal (CoglMaterialLayer *authority0,
CoglMaterialLayer *authority1)
{
if (authority0->wrap_mode_s != authority1->wrap_mode_s ||
authority0->wrap_mode_t != authority1->wrap_mode_t ||
authority0->wrap_mode_p != authority1->wrap_mode_p)
return FALSE;
return TRUE;
}
static gboolean
_cogl_material_layer_user_matrix_equal (CoglMaterialLayer *authority0,
CoglMaterialLayer *authority1)
{
CoglMaterialLayerBigState *big_state0 = authority0->big_state;
CoglMaterialLayerBigState *big_state1 = authority1->big_state;
if (!cogl_matrix_equal (&big_state0->matrix, &big_state1->matrix))
return FALSE;
return TRUE;
}
static gboolean
_cogl_material_layer_point_sprite_coords_equal (CoglMaterialLayer *authority0,
CoglMaterialLayer *authority1)
{
CoglMaterialLayerBigState *big_state0 = authority0->big_state;
CoglMaterialLayerBigState *big_state1 = authority1->big_state;
return big_state0->point_sprite_coords == big_state1->point_sprite_coords;
}
typedef gboolean
(*CoglMaterialLayerStateComparitor) (CoglMaterialLayer *authority0,
CoglMaterialLayer *authority1);
static gboolean
layer_state_equal (CoglMaterialLayerState state,
CoglMaterialLayer *layer0,
CoglMaterialLayer *layer1,
CoglMaterialLayerStateComparitor comparitor)
{
CoglMaterialLayer *authority0 =
_cogl_material_layer_get_authority (layer0, state);
CoglMaterialLayer *authority1 =
_cogl_material_layer_get_authority (layer1, state);
return comparitor (authority0, authority1);
}
static gboolean
_cogl_material_layer_equal (CoglMaterialLayer *layer0,
CoglMaterialLayer *layer1)
{
unsigned long layers_difference;
if (layer0 == layer1)
return TRUE;
layers_difference =
_cogl_material_layer_compare_differences (layer0, layer1);
if (layers_difference & COGL_MATERIAL_LAYER_STATE_TEXTURE &&
!layer_state_equal (COGL_MATERIAL_LAYER_STATE_TEXTURE,
layer0, layer1,
_cogl_material_layer_texture_equal))
return FALSE;
if (layers_difference & COGL_MATERIAL_LAYER_STATE_COMBINE &&
!layer_state_equal (COGL_MATERIAL_LAYER_STATE_COMBINE,
layer0, layer1,
_cogl_material_layer_combine_state_equal))
return FALSE;
if (layers_difference & COGL_MATERIAL_LAYER_STATE_COMBINE_CONSTANT &&
!layer_state_equal (COGL_MATERIAL_LAYER_STATE_COMBINE_CONSTANT,
layer0, layer1,
_cogl_material_layer_combine_constant_equal))
return FALSE;
if (layers_difference & COGL_MATERIAL_LAYER_STATE_FILTERS &&
!layer_state_equal (COGL_MATERIAL_LAYER_STATE_FILTERS,
layer0, layer1,
_cogl_material_layer_filters_equal))
return FALSE;
if (layers_difference & COGL_MATERIAL_LAYER_STATE_WRAP_MODES &&
!layer_state_equal (COGL_MATERIAL_LAYER_STATE_WRAP_MODES,
layer0, layer1,
_cogl_material_layer_wrap_modes_equal))
return FALSE;
if (layers_difference & COGL_MATERIAL_LAYER_STATE_USER_MATRIX &&
!layer_state_equal (COGL_MATERIAL_LAYER_STATE_USER_MATRIX,
layer0, layer1,
_cogl_material_layer_user_matrix_equal))
return FALSE;
if (layers_difference & COGL_MATERIAL_LAYER_STATE_POINT_SPRITE_COORDS &&
!layer_state_equal (COGL_MATERIAL_LAYER_STATE_POINT_SPRITE_COORDS,
layer0, layer1,
_cogl_material_layer_point_sprite_coords_equal))
return FALSE;
return TRUE;
}
static gboolean
_cogl_material_color_equal (CoglMaterial *authority0,
CoglMaterial *authority1)
{
return cogl_color_equal (&authority0->color, &authority1->color);
}
static gboolean
_cogl_material_lighting_state_equal (CoglMaterial *authority0,
CoglMaterial *authority1)
{
CoglMaterialLightingState *state0 = &authority0->big_state->lighting_state;
CoglMaterialLightingState *state1 = &authority1->big_state->lighting_state;
if (memcmp (state0->ambient, state1->ambient, sizeof (float) * 4) != 0)
return FALSE;
if (memcmp (state0->diffuse, state1->diffuse, sizeof (float) * 4) != 0)
return FALSE;
if (memcmp (state0->specular, state1->specular, sizeof (float) * 4) != 0)
return FALSE;
if (memcmp (state0->emission, state1->emission, sizeof (float) * 4) != 0)
return FALSE;
if (state0->shininess != state1->shininess)
return FALSE;
return TRUE;
}
static gboolean
_cogl_material_alpha_state_equal (CoglMaterial *authority0,
CoglMaterial *authority1)
{
CoglMaterialAlphaFuncState *alpha_state0 =
&authority0->big_state->alpha_state;
CoglMaterialAlphaFuncState *alpha_state1 =
&authority1->big_state->alpha_state;
if (alpha_state0->alpha_func != alpha_state1->alpha_func ||
alpha_state0->alpha_func_reference != alpha_state1->alpha_func_reference)
return FALSE;
else
return TRUE;
}
static gboolean
_cogl_material_blend_state_equal (CoglMaterial *authority0,
CoglMaterial *authority1)
{
CoglMaterialBlendState *blend_state0 = &authority0->big_state->blend_state;
CoglMaterialBlendState *blend_state1 = &authority1->big_state->blend_state;
#ifndef HAVE_COGL_GLES
if (blend_state0->blend_equation_rgb != blend_state1->blend_equation_rgb)
return FALSE;
if (blend_state0->blend_equation_alpha !=
blend_state1->blend_equation_alpha)
return FALSE;
if (blend_state0->blend_src_factor_alpha !=
blend_state1->blend_src_factor_alpha)
return FALSE;
if (blend_state0->blend_dst_factor_alpha !=
blend_state1->blend_dst_factor_alpha)
return FALSE;
#endif
if (blend_state0->blend_src_factor_rgb !=
blend_state1->blend_src_factor_rgb)
return FALSE;
if (blend_state0->blend_dst_factor_rgb !=
blend_state1->blend_dst_factor_rgb)
return FALSE;
#ifndef HAVE_COGL_GLES
if (!cogl_color_equal (&blend_state0->blend_constant,
&blend_state1->blend_constant))
return FALSE;
#endif
return TRUE;
}
static gboolean
_cogl_material_depth_state_equal (CoglMaterial *authority0,
CoglMaterial *authority1)
{
if (authority0->big_state->depth_state.depth_test_enabled == FALSE &&
authority1->big_state->depth_state.depth_test_enabled == FALSE)
return TRUE;
else
return memcmp (&authority0->big_state->depth_state,
&authority1->big_state->depth_state,
sizeof (CoglMaterialDepthState)) == 0;
}
static gboolean
_cogl_material_fog_state_equal (CoglMaterial *authority0,
CoglMaterial *authority1)
{
CoglMaterialFogState *fog_state0 = &authority0->big_state->fog_state;
CoglMaterialFogState *fog_state1 = &authority1->big_state->fog_state;
if (fog_state0->enabled == fog_state1->enabled &&
cogl_color_equal (&fog_state0->color, &fog_state1->color) &&
fog_state0->mode == fog_state1->mode &&
fog_state0->density == fog_state1->density &&
fog_state0->z_near == fog_state1->z_near &&
fog_state0->z_far == fog_state1->z_far)
return TRUE;
else
return FALSE;
}
static gboolean
_cogl_material_point_size_equal (CoglMaterial *authority0,
CoglMaterial *authority1)
{
return authority0->big_state->point_size == authority1->big_state->point_size;
}
static gboolean
_cogl_material_user_shader_equal (CoglMaterial *authority0,
CoglMaterial *authority1)
{
return (authority0->big_state->user_program ==
authority1->big_state->user_program);
}
static gboolean
_cogl_material_layers_equal (CoglMaterial *authority0,
CoglMaterial *authority1)
{
int i;
if (authority0->n_layers != authority1->n_layers)
return FALSE;
_cogl_material_update_layers_cache (authority0);
_cogl_material_update_layers_cache (authority1);
for (i = 0; i < authority0->n_layers; i++)
{
if (!_cogl_material_layer_equal (authority0->layers_cache[i],
authority1->layers_cache[i]))
return FALSE;
}
return TRUE;
}
/* Determine the mask of differences between two materials */
unsigned long
_cogl_material_compare_differences (CoglMaterial *material0,
CoglMaterial *material1)
{
CoglMaterial *node0;
CoglMaterial *node1;
int len0;
int len1;
int len0_index;
int len1_index;
int count;
int i;
CoglMaterial *common_ancestor = NULL;
unsigned long materials_difference = 0;
_COGL_GET_CONTEXT (ctx, 0);
/* Algorithm:
*
* 1) Walk the ancestors of each layer to the root node, adding a
* pointer to each ancester node to two GArrays:
* ctx->material0_nodes, and ctx->material1_nodes.
*
* 2) Compare the arrays to find the nodes where they stop to
* differ.
*
* 3) For each array now iterate from index 0 to the first node of
* difference ORing that nodes ->difference mask into the final
* material_differences mask.
*/
g_array_set_size (ctx->material0_nodes, 0);
g_array_set_size (ctx->material1_nodes, 0);
for (node0 = material0; node0; node0 = _cogl_material_get_parent (node0))
g_array_append_vals (ctx->material0_nodes, &node0, 1);
for (node1 = material1; node1; node1 = _cogl_material_get_parent (node1))
g_array_append_vals (ctx->material1_nodes, &node1, 1);
len0 = ctx->material0_nodes->len;
len1 = ctx->material1_nodes->len;
/* There's no point looking at the last entries since we know both
* layers must have the same default layer as their root node. */
len0_index = len0 - 2;
len1_index = len1 - 2;
count = MIN (len0, len1) - 1;
for (i = 0; i < count; i++)
{
node0 = g_array_index (ctx->material0_nodes,
CoglMaterial *, len0_index--);
node1 = g_array_index (ctx->material1_nodes,
CoglMaterial *, len1_index--);
if (node0 != node1)
{
common_ancestor = _cogl_material_get_parent (node0);
break;
}
}
/* If we didn't already find the first the common_ancestor ancestor
* that's because one material is a direct descendant of the other
* and in this case the first common ancestor is the last node we
* looked at. */
if (!common_ancestor)
common_ancestor = node0;
count = len0 - 1;
for (i = 0; i < count; i++)
{
node0 = g_array_index (ctx->material0_nodes, CoglMaterial *, i);
if (node0 == common_ancestor)
break;
materials_difference |= node0->differences;
}
count = len1 - 1;
for (i = 0; i < count; i++)
{
node1 = g_array_index (ctx->material1_nodes, CoglMaterial *, i);
if (node1 == common_ancestor)
break;
materials_difference |= node1->differences;
}
return materials_difference;
}
static gboolean
simple_property_equal (CoglMaterial *material0,
CoglMaterial *material1,
unsigned long materials_difference,
CoglMaterialState state,
CoglMaterialStateComparitor comparitor)
{
if (materials_difference & state)
{
if (!comparitor (_cogl_material_get_authority (material0, state),
_cogl_material_get_authority (material1, state)))
return FALSE;
}
return TRUE;
}
/* Comparison of two arbitrary materials is done by:
* 1) walking up the parents of each material until a common
* ancestor is found, and at each step ORing together the
* difference masks.
*
* 2) using the final difference mask to determine which state
* groups to compare.
*
* This is used by the Cogl journal to compare materials so that it
* can split up geometry that needs different OpenGL state.
*
* It is acceptable to have false negatives - although they will result
* in redundant OpenGL calls that try and update the state.
*
* When comparing texture layers, _cogl_material_equal will actually
* compare the underlying GL texture handle that the Cogl texture uses
* so that atlas textures and sub textures will be considered equal if
* they point to the same texture. This is useful for comparing
* materials in the journal but it means that _cogl_material_equal
* doesn't strictly compare whether the materials are the same. If we
* needed those semantics we could perhaps add another function or
* some flags to control the behaviour.
*
* False positives aren't allowed.
*/
gboolean
_cogl_material_equal (CoglMaterial *material0,
CoglMaterial *material1,
gboolean skip_gl_color)
{
unsigned long materials_difference;
gboolean ret;
COGL_STATIC_TIMER (material_equal_timer,
"Mainloop", /* parent */
"_cogl_material_equal",
"The time spent comparing cogl materials",
0 /* no application private data */);
COGL_TIMER_START (_cogl_uprof_context, material_equal_timer);
if (material0 == material1)
{
ret = TRUE;
goto done;
}
ret = FALSE;
/* First check non-sparse properties */
if (material0->real_blend_enable != material1->real_blend_enable)
goto done;
/* Then check sparse properties */
materials_difference =
_cogl_material_compare_differences (material0, material1);
if (materials_difference & COGL_MATERIAL_STATE_COLOR &&
!skip_gl_color)
{
CoglMaterialState state = COGL_MATERIAL_STATE_COLOR;
CoglMaterial *authority0 =
_cogl_material_get_authority (material0, state);
CoglMaterial *authority1 =
_cogl_material_get_authority (material1, state);
if (!cogl_color_equal (&authority0->color, &authority1->color))
goto done;
}
if (!simple_property_equal (material0, material1,
materials_difference,
COGL_MATERIAL_STATE_LIGHTING,
_cogl_material_lighting_state_equal))
goto done;
if (!simple_property_equal (material0, material1,
materials_difference,
COGL_MATERIAL_STATE_ALPHA_FUNC,
_cogl_material_alpha_state_equal))
goto done;
/* We don't need to compare the detailed blending state if we know
* blending is disabled for both materials. */
if (material0->real_blend_enable &&
materials_difference & COGL_MATERIAL_STATE_BLEND)
{
CoglMaterialState state = COGL_MATERIAL_STATE_BLEND;
CoglMaterial *authority0 =
_cogl_material_get_authority (material0, state);
CoglMaterial *authority1 =
_cogl_material_get_authority (material1, state);
if (!_cogl_material_blend_state_equal (authority0, authority1))
goto done;
}
/* XXX: we don't need to compare the BLEND_ENABLE state because it's
* already reflected in ->real_blend_enable */
#if 0
if (!simple_property_equal (material0, material1,
materials_difference,
COGL_MATERIAL_STATE_BLEND,
_cogl_material_blend_enable_equal))
return FALSE;
#endif
if (!simple_property_equal (material0, material1,
materials_difference,
COGL_MATERIAL_STATE_DEPTH,
_cogl_material_depth_state_equal))
goto done;
if (!simple_property_equal (material0, material1,
materials_difference,
COGL_MATERIAL_STATE_FOG,
_cogl_material_fog_state_equal))
goto done;
if (!simple_property_equal (material0, material1,
materials_difference,
COGL_MATERIAL_STATE_POINT_SIZE,
_cogl_material_point_size_equal))
goto done;
if (!simple_property_equal (material0, material1,
materials_difference,
COGL_MATERIAL_STATE_USER_SHADER,
_cogl_material_user_shader_equal))
goto done;
if (!simple_property_equal (material0, material1,
materials_difference,
COGL_MATERIAL_STATE_LAYERS,
_cogl_material_layers_equal))
goto done;
ret = TRUE;
done:
COGL_TIMER_STOP (_cogl_uprof_context, material_equal_timer);
return ret;
}
void
cogl_material_get_color (CoglMaterial *material,
CoglColor *color)
{
CoglMaterial *authority;
g_return_if_fail (cogl_is_material (material));
authority =
_cogl_material_get_authority (material, COGL_MATERIAL_STATE_COLOR);
*color = authority->color;
}
/* This is used heavily by the cogl journal when logging quads */
void
_cogl_material_get_colorubv (CoglMaterial *material,
guint8 *color)
{
CoglMaterial *authority =
_cogl_material_get_authority (material, COGL_MATERIAL_STATE_COLOR);
_cogl_color_get_rgba_4ubv (&authority->color, color);
}
static void
_cogl_material_prune_redundant_ancestry (CoglMaterial *material)
{
CoglMaterial *new_parent = _cogl_material_get_parent (material);
/* Before considering pruning redundant ancestry we check if this
* material is an authority for layer state and if so only consider
* reparenting if it *owns* all the layers it depends on. NB: A
* material can be be a STATE_LAYERS authority but it may still
* defer to its ancestors to define the state for some of its
* layers.
*
* For example a material that derives from a parent with 5 layers
* can become a STATE_LAYERS authority by simply changing it's
* ->n_layers count to 4 and in that case it can still defer to its
* ancestors to define the state of those 4 layers.
*
* If a material depends on any ancestors for layer state then we
* immediatly bail out.
*/
if (material->differences & COGL_MATERIAL_STATE_LAYERS)
{
if (material->n_layers != g_list_length (material->layer_differences))
return;
}
/* walk up past ancestors that are now redundant and potentially
* reparent the material. */
while (_cogl_material_get_parent (new_parent) &&
(new_parent->differences | material->differences) ==
material->differences)
new_parent = _cogl_material_get_parent (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
_cogl_material_update_authority (CoglMaterial *material,
CoglMaterial *authority,
CoglMaterialState state,
CoglMaterialStateComparitor comparitor)
{
/* 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);
}
}
void
cogl_material_set_color (CoglMaterial *material,
const CoglColor *color)
{
CoglMaterialState state = COGL_MATERIAL_STATE_COLOR;
CoglMaterial *authority;
g_return_if_fail (cogl_is_material (material));
authority = _cogl_material_get_authority (material, state);
if (cogl_color_equal (color, &authority->color))
return;
/* - 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, state, color, FALSE);
material->color = *color;
_cogl_material_update_authority (material, authority, state,
_cogl_material_color_equal);
handle_automatic_blend_enable (material, state);
}
void
cogl_material_set_color4ub (CoglMaterial *material,
guint8 red,
guint8 green,
guint8 blue,
guint8 alpha)
{
CoglColor color;
cogl_color_init_from_4ub (&color, red, green, blue, alpha);
cogl_material_set_color (material, &color);
}
void
cogl_material_set_color4f (CoglMaterial *material,
float red,
float green,
float blue,
float alpha)
{
CoglColor color;
cogl_color_init_from_4f (&color, red, green, blue, alpha);
cogl_material_set_color (material, &color);
}
CoglMaterialBlendEnable
_cogl_material_get_blend_enabled (CoglMaterial *material)
{
CoglMaterial *authority;
g_return_val_if_fail (cogl_is_material (material), FALSE);
authority =
_cogl_material_get_authority (material, COGL_MATERIAL_STATE_BLEND_ENABLE);
return authority->blend_enable;
}
static gboolean
_cogl_material_blend_enable_equal (CoglMaterial *authority0,
CoglMaterial *authority1)
{
return authority0->blend_enable == authority1->blend_enable ? TRUE : FALSE;
}
void
_cogl_material_set_blend_enabled (CoglMaterial *material,
CoglMaterialBlendEnable enable)
{
CoglMaterialState state = COGL_MATERIAL_STATE_BLEND_ENABLE;
CoglMaterial *authority;
g_return_if_fail (cogl_is_material (material));
g_return_if_fail (enable > 1 &&
"don't pass TRUE or FALSE to _set_blend_enabled!");
authority = _cogl_material_get_authority (material, state);
if (authority->blend_enable == enable)
return;
/* - 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, state, NULL, FALSE);
material->blend_enable = enable;
_cogl_material_update_authority (material, authority, state,
_cogl_material_blend_enable_equal);
handle_automatic_blend_enable (material, state);
}
void
cogl_material_get_ambient (CoglMaterial *material,
CoglColor *ambient)
{
CoglMaterial *authority;
g_return_if_fail (cogl_is_material (material));
authority =
_cogl_material_get_authority (material, COGL_MATERIAL_STATE_LIGHTING);
cogl_color_init_from_4fv (ambient,
authority->big_state->lighting_state.ambient);
}
void
cogl_material_set_ambient (CoglMaterial *material,
const CoglColor *ambient)
{
CoglMaterialState state = COGL_MATERIAL_STATE_LIGHTING;
CoglMaterial *authority;
CoglMaterialLightingState *lighting_state;
g_return_if_fail (cogl_is_material (material));
authority = _cogl_material_get_authority (material, state);
lighting_state = &authority->big_state->lighting_state;
if (cogl_color_equal (ambient, &lighting_state->ambient))
return;
/* - 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, state, NULL, FALSE);
lighting_state = &material->big_state->lighting_state;
lighting_state->ambient[0] = cogl_color_get_red_float (ambient);
lighting_state->ambient[1] = cogl_color_get_green_float (ambient);
lighting_state->ambient[2] = cogl_color_get_blue_float (ambient);
lighting_state->ambient[3] = cogl_color_get_alpha_float (ambient);
_cogl_material_update_authority (material, authority, state,
_cogl_material_lighting_state_equal);
handle_automatic_blend_enable (material, state);
}
void
cogl_material_get_diffuse (CoglMaterial *material,
CoglColor *diffuse)
{
CoglMaterial *authority;
g_return_if_fail (cogl_is_material (material));
authority =
_cogl_material_get_authority (material, COGL_MATERIAL_STATE_LIGHTING);
cogl_color_init_from_4fv (diffuse,
authority->big_state->lighting_state.diffuse);
}
void
cogl_material_set_diffuse (CoglMaterial *material,
const CoglColor *diffuse)
{
CoglMaterialState state = COGL_MATERIAL_STATE_LIGHTING;
CoglMaterial *authority;
CoglMaterialLightingState *lighting_state;
g_return_if_fail (cogl_is_material (material));
authority = _cogl_material_get_authority (material, state);
lighting_state = &authority->big_state->lighting_state;
if (cogl_color_equal (diffuse, &lighting_state->diffuse))
return;
/* - 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, state, NULL, FALSE);
lighting_state = &material->big_state->lighting_state;
lighting_state->diffuse[0] = cogl_color_get_red_float (diffuse);
lighting_state->diffuse[1] = cogl_color_get_green_float (diffuse);
lighting_state->diffuse[2] = cogl_color_get_blue_float (diffuse);
lighting_state->diffuse[3] = cogl_color_get_alpha_float (diffuse);
_cogl_material_update_authority (material, authority, state,
_cogl_material_lighting_state_equal);
handle_automatic_blend_enable (material, state);
}
void
cogl_material_set_ambient_and_diffuse (CoglMaterial *material,
const CoglColor *color)
{
cogl_material_set_ambient (material, color);
cogl_material_set_diffuse (material, color);
}
void
cogl_material_get_specular (CoglMaterial *material,
CoglColor *specular)
{
CoglMaterial *authority;
g_return_if_fail (cogl_is_material (material));
authority =
_cogl_material_get_authority (material, COGL_MATERIAL_STATE_LIGHTING);
cogl_color_init_from_4fv (specular,
authority->big_state->lighting_state.specular);
}
void
cogl_material_set_specular (CoglMaterial *material, const CoglColor *specular)
{
CoglMaterial *authority;
CoglMaterialState state = COGL_MATERIAL_STATE_LIGHTING;
CoglMaterialLightingState *lighting_state;
g_return_if_fail (cogl_is_material (material));
authority = _cogl_material_get_authority (material, state);
lighting_state = &authority->big_state->lighting_state;
if (cogl_color_equal (specular, &lighting_state->specular))
return;
/* - 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, state, NULL, FALSE);
lighting_state = &material->big_state->lighting_state;
lighting_state->specular[0] = cogl_color_get_red_float (specular);
lighting_state->specular[1] = cogl_color_get_green_float (specular);
lighting_state->specular[2] = cogl_color_get_blue_float (specular);
lighting_state->specular[3] = cogl_color_get_alpha_float (specular);
_cogl_material_update_authority (material, authority, state,
_cogl_material_lighting_state_equal);
handle_automatic_blend_enable (material, state);
}
float
cogl_material_get_shininess (CoglMaterial *material)
{
CoglMaterial *authority;
g_return_val_if_fail (cogl_is_material (material), 0);
authority =
_cogl_material_get_authority (material, COGL_MATERIAL_STATE_LIGHTING);
return authority->big_state->lighting_state.shininess;
}
void
cogl_material_set_shininess (CoglMaterial *material,
float shininess)
{
CoglMaterial *authority;
CoglMaterialState state = COGL_MATERIAL_STATE_LIGHTING;
CoglMaterialLightingState *lighting_state;
g_return_if_fail (cogl_is_material (material));
if (shininess < 0.0)
{
g_warning ("Out of range shininess %f supplied for material\n",
shininess);
return;
}
authority = _cogl_material_get_authority (material, state);
lighting_state = &authority->big_state->lighting_state;
if (lighting_state->shininess == shininess)
return;
/* - 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, state, NULL, FALSE);
lighting_state = &material->big_state->lighting_state;
lighting_state->shininess = shininess;
_cogl_material_update_authority (material, authority, state,
_cogl_material_lighting_state_equal);
}
void
cogl_material_get_emission (CoglMaterial *material,
CoglColor *emission)
{
CoglMaterial *authority;
g_return_if_fail (cogl_is_material (material));
authority =
_cogl_material_get_authority (material, COGL_MATERIAL_STATE_LIGHTING);
cogl_color_init_from_4fv (emission,
authority->big_state->lighting_state.emission);
}
void
cogl_material_set_emission (CoglMaterial *material, const CoglColor *emission)
{
CoglMaterial *authority;
CoglMaterialState state = COGL_MATERIAL_STATE_LIGHTING;
CoglMaterialLightingState *lighting_state;
g_return_if_fail (cogl_is_material (material));
authority = _cogl_material_get_authority (material, state);
lighting_state = &authority->big_state->lighting_state;
if (cogl_color_equal (emission, &lighting_state->emission))
return;
/* - 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, state, NULL, FALSE);
lighting_state = &material->big_state->lighting_state;
lighting_state->emission[0] = cogl_color_get_red_float (emission);
lighting_state->emission[1] = cogl_color_get_green_float (emission);
lighting_state->emission[2] = cogl_color_get_blue_float (emission);
lighting_state->emission[3] = cogl_color_get_alpha_float (emission);
_cogl_material_update_authority (material, authority, state,
_cogl_material_lighting_state_equal);
handle_automatic_blend_enable (material, state);
}
void
cogl_material_set_alpha_test_function (CoglMaterial *material,
CoglMaterialAlphaFunc alpha_func,
float alpha_reference)
{
CoglMaterialState state = COGL_MATERIAL_STATE_ALPHA_FUNC;
CoglMaterial *authority;
CoglMaterialAlphaFuncState *alpha_state;
g_return_if_fail (cogl_is_material (material));
authority = _cogl_material_get_authority (material, state);
alpha_state = &authority->big_state->alpha_state;
if (alpha_state->alpha_func == alpha_func &&
alpha_state->alpha_func_reference == alpha_reference)
return;
/* - 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, state, NULL, FALSE);
alpha_state = &material->big_state->alpha_state;
alpha_state->alpha_func = alpha_func;
alpha_state->alpha_func_reference = alpha_reference;
_cogl_material_update_authority (material, authority, state,
_cogl_material_alpha_state_equal);
}
GLenum
arg_to_gl_blend_factor (CoglBlendStringArgument *arg)
{
if (arg->source.is_zero)
return GL_ZERO;
if (arg->factor.is_one)
return GL_ONE;
else if (arg->factor.is_src_alpha_saturate)
return GL_SRC_ALPHA_SATURATE;
else if (arg->factor.source.info->type ==
COGL_BLEND_STRING_COLOR_SOURCE_SRC_COLOR)
{
if (arg->factor.source.mask != COGL_BLEND_STRING_CHANNEL_MASK_ALPHA)
{
if (arg->factor.source.one_minus)
return GL_ONE_MINUS_SRC_COLOR;
else
return GL_SRC_COLOR;
}
else
{
if (arg->factor.source.one_minus)
return GL_ONE_MINUS_SRC_ALPHA;
else
return GL_SRC_ALPHA;
}
}
else if (arg->factor.source.info->type ==
COGL_BLEND_STRING_COLOR_SOURCE_DST_COLOR)
{
if (arg->factor.source.mask != COGL_BLEND_STRING_CHANNEL_MASK_ALPHA)
{
if (arg->factor.source.one_minus)
return GL_ONE_MINUS_DST_COLOR;
else
return GL_DST_COLOR;
}
else
{
if (arg->factor.source.one_minus)
return GL_ONE_MINUS_DST_ALPHA;
else
return GL_DST_ALPHA;
}
}
#ifndef HAVE_COGL_GLES
else if (arg->factor.source.info->type ==
COGL_BLEND_STRING_COLOR_SOURCE_CONSTANT)
{
if (arg->factor.source.mask != COGL_BLEND_STRING_CHANNEL_MASK_ALPHA)
{
if (arg->factor.source.one_minus)
return GL_ONE_MINUS_CONSTANT_COLOR;
else
return GL_CONSTANT_COLOR;
}
else
{
if (arg->factor.source.one_minus)
return GL_ONE_MINUS_CONSTANT_ALPHA;
else
return GL_CONSTANT_ALPHA;
}
}
#endif
g_warning ("Unable to determine valid blend factor from blend string\n");
return GL_ONE;
}
void
setup_blend_state (CoglBlendStringStatement *statement,
GLenum *blend_equation,
GLint *blend_src_factor,
GLint *blend_dst_factor)
{
#ifndef HAVE_COGL_GLES
switch (statement->function->type)
{
case COGL_BLEND_STRING_FUNCTION_ADD:
*blend_equation = GL_FUNC_ADD;
break;
/* TODO - add more */
default:
g_warning ("Unsupported blend function given");
*blend_equation = GL_FUNC_ADD;
}
#endif
*blend_src_factor = arg_to_gl_blend_factor (&statement->args[0]);
*blend_dst_factor = arg_to_gl_blend_factor (&statement->args[1]);
}
gboolean
cogl_material_set_blend (CoglMaterial *material,
const char *blend_description,
GError **error)
{
CoglMaterialState state = COGL_MATERIAL_STATE_BLEND;
CoglMaterial *authority;
CoglBlendStringStatement statements[2];
CoglBlendStringStatement *rgb;
CoglBlendStringStatement *a;
GError *internal_error = NULL;
int count;
CoglMaterialBlendState *blend_state;
g_return_val_if_fail (cogl_is_material (material), FALSE);
count =
_cogl_blend_string_compile (blend_description,
COGL_BLEND_STRING_CONTEXT_BLENDING,
statements,
&internal_error);
if (!count)
{
if (error)
g_propagate_error (error, internal_error);
else
{
g_warning ("Cannot compile blend description: %s\n",
internal_error->message);
g_error_free (internal_error);
}
return FALSE;
}
if (count == 1)
rgb = a = statements;
else
{
rgb = &statements[0];
a = &statements[1];
}
authority =
_cogl_material_get_authority (material, state);
/* - 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, state, NULL, FALSE);
blend_state = &material->big_state->blend_state;
#ifndef HAVE_COGL_GLES
setup_blend_state (rgb,
&blend_state->blend_equation_rgb,
&blend_state->blend_src_factor_rgb,
&blend_state->blend_dst_factor_rgb);
setup_blend_state (a,
&blend_state->blend_equation_alpha,
&blend_state->blend_src_factor_alpha,
&blend_state->blend_dst_factor_alpha);
#else
setup_blend_state (rgb,
NULL,
&blend_state->blend_src_factor_rgb,
&blend_state->blend_dst_factor_rgb);
#endif
/* 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 (_cogl_material_blend_state_equal (authority, old_authority))
material->differences &= ~state;
}
/* 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... */
if (material != authority)
{
material->differences |= state;
_cogl_material_prune_redundant_ancestry (material);
}
handle_automatic_blend_enable (material, state);
return TRUE;
}
void
cogl_material_set_blend_constant (CoglMaterial *material,
const CoglColor *constant_color)
{
#ifndef HAVE_COGL_GLES
CoglMaterialState state = COGL_MATERIAL_STATE_BLEND;
CoglMaterial *authority;
CoglMaterialBlendState *blend_state;
g_return_if_fail (cogl_is_material (material));
authority = _cogl_material_get_authority (material, state);
blend_state = &authority->big_state->blend_state;
if (cogl_color_equal (constant_color, &blend_state->blend_constant))
return;
/* - 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, state, NULL, FALSE);
blend_state = &material->big_state->blend_state;
blend_state->blend_constant = *constant_color;
_cogl_material_update_authority (material, authority, state,
_cogl_material_blend_state_equal);
handle_automatic_blend_enable (material, state);
#endif
}
CoglHandle
cogl_material_get_user_program (CoglMaterial *material)
{
CoglMaterial *authority;
g_return_val_if_fail (cogl_is_material (material), COGL_INVALID_HANDLE);
authority =
_cogl_material_get_authority (material, COGL_MATERIAL_STATE_USER_SHADER);
return authority->big_state->user_program;
}
/* XXX: for now we don't mind if the program has vertex shaders
* attached but if we ever make a similar API public we should only
* allow attaching of programs containing fragment shaders. Eventually
* we will have a CoglPipeline abstraction to also cover vertex
* processing.
*/
void
cogl_material_set_user_program (CoglMaterial *material,
CoglHandle program)
{
CoglMaterialState state = COGL_MATERIAL_STATE_USER_SHADER;
CoglMaterial *authority;
g_return_if_fail (cogl_is_material (material));
authority = _cogl_material_get_authority (material, state);
if (authority->big_state->user_program == program)
return;
/* - 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, state, NULL, FALSE);
if (program != COGL_INVALID_HANDLE)
_cogl_material_set_backend (material, COGL_MATERIAL_BACKEND_DEFAULT);
/* 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 (old_authority->big_state->user_program == program)
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);
}
if (program != COGL_INVALID_HANDLE)
cogl_handle_ref (program);
if (authority == material &&
material->big_state->user_program != COGL_INVALID_HANDLE)
cogl_handle_unref (material->big_state->user_program);
material->big_state->user_program = program;
handle_automatic_blend_enable (material, state);
}
void
cogl_material_set_depth_test_enabled (CoglMaterial *material,
gboolean enable)
{
CoglMaterialState state = COGL_MATERIAL_STATE_DEPTH;
CoglMaterial *authority;
CoglMaterialDepthState *depth_state;
g_return_if_fail (cogl_is_material (material));
authority = _cogl_material_get_authority (material, state);
depth_state = &authority->big_state->depth_state;
if (depth_state->depth_test_enabled == enable)
return;
/* - 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, state, NULL, FALSE);
material->big_state->depth_state.depth_test_enabled = enable;
_cogl_material_update_authority (material, authority, state,
_cogl_material_depth_state_equal);
}
gboolean
cogl_material_get_depth_test_enabled (CoglMaterial *material)
{
CoglMaterial *authority;
g_return_val_if_fail (cogl_is_material (material), FALSE);
authority =
_cogl_material_get_authority (material, COGL_MATERIAL_STATE_DEPTH);
return authority->big_state->depth_state.depth_test_enabled;
}
void
cogl_material_set_depth_writing_enabled (CoglMaterial *material,
gboolean enable)
{
CoglMaterialState state = COGL_MATERIAL_STATE_DEPTH;
CoglMaterial *authority;
CoglMaterialDepthState *depth_state;
g_return_if_fail (cogl_is_material (material));
authority = _cogl_material_get_authority (material, state);
depth_state = &authority->big_state->depth_state;
if (depth_state->depth_writing_enabled == enable)
return;
/* - 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, state, NULL, FALSE);
material->big_state->depth_state.depth_writing_enabled = enable;
_cogl_material_update_authority (material, authority, state,
_cogl_material_depth_state_equal);
}
gboolean
cogl_material_get_depth_writing_enabled (CoglMaterial *material)
{
CoglMaterial *authority;
g_return_val_if_fail (cogl_is_material (material), TRUE);
authority =
_cogl_material_get_authority (material, COGL_MATERIAL_STATE_DEPTH);
return authority->big_state->depth_state.depth_writing_enabled;
}
void
cogl_material_set_depth_test_function (CoglMaterial *material,
CoglDepthTestFunction function)
{
CoglMaterialState state = COGL_MATERIAL_STATE_DEPTH;
CoglMaterial *authority;
CoglMaterialDepthState *depth_state;
g_return_if_fail (cogl_is_material (material));
authority = _cogl_material_get_authority (material, state);
depth_state = &authority->big_state->depth_state;
if (depth_state->depth_test_function == function)
return;
/* - 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, state, NULL, FALSE);
material->big_state->depth_state.depth_test_function = function;
_cogl_material_update_authority (material, authority, state,
_cogl_material_depth_state_equal);
}
CoglDepthTestFunction
cogl_material_get_depth_test_function (CoglMaterial *material)
{
CoglMaterial *authority;
g_return_val_if_fail (cogl_is_material (material),
COGL_DEPTH_TEST_FUNCTION_LESS);
authority =
_cogl_material_get_authority (material, COGL_MATERIAL_STATE_DEPTH);
return authority->big_state->depth_state.depth_test_function;
}
gboolean
cogl_material_set_depth_range (CoglMaterial *material,
float near_val,
float far_val,
GError **error)
{
#ifndef COGL_HAS_GLES
CoglMaterialState state = COGL_MATERIAL_STATE_DEPTH;
CoglMaterial *authority;
CoglMaterialDepthState *depth_state;
g_return_val_if_fail (cogl_is_material (material), FALSE);
authority = _cogl_material_get_authority (material, state);
depth_state = &authority->big_state->depth_state;
if (depth_state->depth_range_near == near_val &&
depth_state->depth_range_far == far_val)
return TRUE;
/* - 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, state, NULL, FALSE);
material->big_state->depth_state.depth_range_near = near_val;
material->big_state->depth_state.depth_range_far = far_val;
_cogl_material_update_authority (material, authority, state,
_cogl_material_depth_state_equal);
return TRUE;
#else
g_set_error (error,
COGL_ERROR,
COGL_ERROR_UNSUPPORTED,
"glDepthRange not available on GLES 1");
return FALSE;
#endif
}
void
cogl_material_get_depth_range (CoglMaterial *material,
float *near_val,
float *far_val)
{
CoglMaterial *authority;
g_return_if_fail (cogl_is_material (material));
authority =
_cogl_material_get_authority (material, COGL_MATERIAL_STATE_DEPTH);
*near_val = authority->big_state->depth_state.depth_range_near;
*far_val = authority->big_state->depth_state.depth_range_far;
}
static void
_cogl_material_set_fog_state (CoglMaterial *material,
const CoglMaterialFogState *fog_state)
{
CoglMaterialState state = COGL_MATERIAL_STATE_FOG;
CoglMaterial *authority;
CoglMaterialFogState *current_fog_state;
g_return_if_fail (cogl_is_material (material));
authority = _cogl_material_get_authority (material, state);
current_fog_state = &authority->big_state->fog_state;
if (current_fog_state->enabled == fog_state->enabled &&
cogl_color_equal (¤t_fog_state->color, &fog_state->color) &&
current_fog_state->mode == fog_state->mode &&
current_fog_state->density == fog_state->density &&
current_fog_state->z_near == fog_state->z_near &&
current_fog_state->z_far == fog_state->z_far)
return;
/* - 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, state, NULL, FALSE);
material->big_state->fog_state = *fog_state;
_cogl_material_update_authority (material, authority, state,
_cogl_material_fog_state_equal);
}
unsigned long
_cogl_material_get_age (CoglMaterial *material)
{
g_return_val_if_fail (cogl_is_material (material), 0);
return material->age;
}
static CoglMaterialLayer *
_cogl_material_layer_copy (CoglMaterialLayer *src)
{
CoglMaterialLayer *layer = g_slice_new (CoglMaterialLayer);
int i;
_cogl_material_node_init (COGL_MATERIAL_NODE (layer));
layer->owner = NULL;
layer->index = src->index;
layer->differences = 0;
layer->has_big_state = FALSE;
for (i = 0; i < COGL_MATERIAL_N_BACKENDS; i++)
layer->backend_priv[i] = NULL;
_cogl_material_layer_set_parent (layer, src);
return _cogl_material_layer_object_new (layer);
}
static void
_cogl_material_layer_free (CoglMaterialLayer *layer)
{
int i;
_cogl_material_layer_unparent (COGL_MATERIAL_NODE (layer));
/* NB: layers may be used by multiple materials which may be using
* different backends, therefore we determine which backends to
* notify based on the private state pointers for each backend...
*/
for (i = 0; i < COGL_MATERIAL_N_BACKENDS; i++)
{
if (layer->backend_priv[i] &&
_cogl_material_backends[i]->free_layer_priv)
{
const CoglMaterialBackend *backend = _cogl_material_backends[i];
backend->free_layer_priv (layer);
}
}
if (layer->differences & COGL_MATERIAL_LAYER_STATE_TEXTURE &&
layer->texture != COGL_INVALID_HANDLE)
cogl_handle_unref (layer->texture);
if (layer->differences & COGL_MATERIAL_LAYER_STATE_NEEDS_BIG_STATE)
g_slice_free (CoglMaterialLayerBigState, layer->big_state);
g_slice_free (CoglMaterialLayer, layer);
}
/* If a layer has descendants we can't modify it freely
*
* If the layer is owned and the owner has descendants we can't
* modify it freely.
*
* In both cases when we can't freely modify a layer we can either:
* - create a new layer; splice it in to replace the layer so it can
* be directly modified.
* XXX: disadvantage is that we have to invalidate the layers_cache
* for the owner and its descendants.
* - create a new derived layer and modify that.
*/
/* XXX: how is the caller expected to deal with ref-counting?
*
* If the layer can't be freely modified and we return a new layer
* then that will effectively make the caller own a new reference
* which doesn't happen if we simply modify the given layer.
*
* We could make it consistent by taking a reference on the layer if
* we don't create a new one. At least this way the caller could
* deal with it consistently, though the semantics are a bit
* strange.
*
* Alternatively we could leave it to the caller to check
* ...?
*/
void
_cogl_material_init_default_layers (void)
{
CoglMaterialLayer *layer = g_slice_new0 (CoglMaterialLayer);
CoglMaterialLayerBigState *big_state =
g_slice_new0 (CoglMaterialLayerBigState);
CoglMaterialLayer *new;
int i;
_COGL_GET_CONTEXT (ctx, NO_RETVAL);
_cogl_material_node_init (COGL_MATERIAL_NODE (layer));
layer->index = 0;
for (i = 0; i < COGL_MATERIAL_N_BACKENDS; i++)
layer->backend_priv[i] = NULL;
layer->differences = COGL_MATERIAL_LAYER_STATE_ALL_SPARSE;
layer->unit_index = 0;
layer->texture = COGL_INVALID_HANDLE;
layer->texture_overridden = FALSE;
layer->mag_filter = COGL_MATERIAL_FILTER_LINEAR;
layer->min_filter = COGL_MATERIAL_FILTER_LINEAR;
layer->wrap_mode_s = COGL_MATERIAL_WRAP_MODE_AUTOMATIC;
layer->wrap_mode_t = COGL_MATERIAL_WRAP_MODE_AUTOMATIC;
layer->wrap_mode_p = COGL_MATERIAL_WRAP_MODE_AUTOMATIC;
layer->big_state = big_state;
layer->has_big_state = TRUE;
/* Choose the same default combine mode as OpenGL:
* RGBA = MODULATE(PREVIOUS[RGBA],TEXTURE[RGBA]) */
big_state->texture_combine_rgb_func = GL_MODULATE;
big_state->texture_combine_rgb_src[0] = GL_PREVIOUS;
big_state->texture_combine_rgb_src[1] = GL_TEXTURE;
big_state->texture_combine_rgb_op[0] = GL_SRC_COLOR;
big_state->texture_combine_rgb_op[1] = GL_SRC_COLOR;
big_state->texture_combine_alpha_func = GL_MODULATE;
big_state->texture_combine_alpha_src[0] = GL_PREVIOUS;
big_state->texture_combine_alpha_src[1] = GL_TEXTURE;
big_state->texture_combine_alpha_op[0] = GL_SRC_ALPHA;
big_state->texture_combine_alpha_op[1] = GL_SRC_ALPHA;
big_state->point_sprite_coords = FALSE;
cogl_matrix_init_identity (&big_state->matrix);
ctx->default_layer_0 = _cogl_material_layer_object_new (layer);
/* TODO: we should make default_layer_n comprise of two
* descendants of default_layer_0:
* - the first descendant should change the texture combine
* to what we expect is most commonly used for multitexturing
* - the second should revert the above change.
*
* why? the documentation for how a new layer is initialized
* doesn't say that layers > 0 have different defaults so unless
* we change the documentation we can't use different defaults,
* but if the user does what we expect and changes the
* texture combine then we can revert the authority to the
* first descendant which means we can maximize the number
* of layers with a common ancestor.
*
* The main problem will be that we'll need to disable the
* optimizations for flattening the ancestry when we make
* the second descendant which reverts the state.
*/
ctx->default_layer_n = _cogl_material_layer_copy (layer);
new = _cogl_material_set_layer_unit (NULL, ctx->default_layer_n, 1);
g_assert (new == ctx->default_layer_n);
/* Since we passed a newly allocated layer we don't expect that
* _set_layer_unit() will have to allocate *another* layer. */
/* Finally we create a dummy dependant for ->default_layer_n which
* effectively ensures that ->default_layer_n and ->default_layer_0
* remain immutable.
*/
ctx->dummy_layer_dependant =
_cogl_material_layer_copy (ctx->default_layer_n);
}
static void
setup_texture_combine_state (CoglBlendStringStatement *statement,
GLint *texture_combine_func,
GLint *texture_combine_src,
GLint *texture_combine_op)
{
int i;
switch (statement->function->type)
{
case COGL_BLEND_STRING_FUNCTION_REPLACE:
*texture_combine_func = GL_REPLACE;
break;
case COGL_BLEND_STRING_FUNCTION_MODULATE:
*texture_combine_func = GL_MODULATE;
break;
case COGL_BLEND_STRING_FUNCTION_ADD:
*texture_combine_func = GL_ADD;
break;
case COGL_BLEND_STRING_FUNCTION_ADD_SIGNED:
*texture_combine_func = GL_ADD_SIGNED;
break;
case COGL_BLEND_STRING_FUNCTION_INTERPOLATE:
*texture_combine_func = GL_INTERPOLATE;
break;
case COGL_BLEND_STRING_FUNCTION_SUBTRACT:
*texture_combine_func = GL_SUBTRACT;
break;
case COGL_BLEND_STRING_FUNCTION_DOT3_RGB:
*texture_combine_func = GL_DOT3_RGB;
break;
case COGL_BLEND_STRING_FUNCTION_DOT3_RGBA:
*texture_combine_func = GL_DOT3_RGBA;
break;
}
for (i = 0; i < statement->function->argc; i++)
{
CoglBlendStringArgument *arg = &statement->args[i];
switch (arg->source.info->type)
{
case COGL_BLEND_STRING_COLOR_SOURCE_CONSTANT:
texture_combine_src[i] = GL_CONSTANT;
break;
case COGL_BLEND_STRING_COLOR_SOURCE_TEXTURE:
texture_combine_src[i] = GL_TEXTURE;
break;
case COGL_BLEND_STRING_COLOR_SOURCE_TEXTURE_N:
texture_combine_src[i] =
GL_TEXTURE0 + arg->source.texture;
break;
case COGL_BLEND_STRING_COLOR_SOURCE_PRIMARY:
texture_combine_src[i] = GL_PRIMARY_COLOR;
break;
case COGL_BLEND_STRING_COLOR_SOURCE_PREVIOUS:
texture_combine_src[i] = GL_PREVIOUS;
break;
default:
g_warning ("Unexpected texture combine source");
texture_combine_src[i] = GL_TEXTURE;
}
if (arg->source.mask == COGL_BLEND_STRING_CHANNEL_MASK_RGB)
{
if (statement->args[i].source.one_minus)
texture_combine_op[i] = GL_ONE_MINUS_SRC_COLOR;
else
texture_combine_op[i] = GL_SRC_COLOR;
}
else
{
if (statement->args[i].source.one_minus)
texture_combine_op[i] = GL_ONE_MINUS_SRC_ALPHA;
else
texture_combine_op[i] = GL_SRC_ALPHA;
}
}
}
gboolean
cogl_material_set_layer_combine (CoglMaterial *material,
int layer_index,
const char *combine_description,
GError **error)
{
CoglMaterialLayerState state = COGL_MATERIAL_LAYER_STATE_COMBINE;
CoglMaterialLayer *authority;
CoglMaterialLayer *layer;
CoglBlendStringStatement statements[2];
CoglBlendStringStatement split[2];
CoglBlendStringStatement *rgb;
CoglBlendStringStatement *a;
GError *internal_error = NULL;
int count;
g_return_val_if_fail (cogl_is_material (material), FALSE);
/* Note: this will ensure that the layer exists, creating one if it
* doesn't already.
*
* Note: If the layer already existed it's possibly owned by another
* material. If the layer is created then it will be owned by
* material. */
layer = _cogl_material_get_layer (material, layer_index);
/* Now find the ancestor of the layer that is the authority for the
* state we want to change */
authority = _cogl_material_layer_get_authority (layer, state);
count =
_cogl_blend_string_compile (combine_description,
COGL_BLEND_STRING_CONTEXT_TEXTURE_COMBINE,
statements,
&internal_error);
if (!count)
{
if (error)
g_propagate_error (error, internal_error);
else
{
g_warning ("Cannot compile combine description: %s\n",
internal_error->message);
g_error_free (internal_error);
}
return FALSE;
}
if (statements[0].mask == COGL_BLEND_STRING_CHANNEL_MASK_RGBA)
{
_cogl_blend_string_split_rgba_statement (statements,
&split[0], &split[1]);
rgb = &split[0];
a = &split[1];
}
else
{
rgb = &statements[0];
a = &statements[1];
}
/* FIXME: compare the new state with the current state! */
/* possibly flush primitives referencing the current state... */
layer = _cogl_material_layer_pre_change_notify (material, layer, state);
setup_texture_combine_state (rgb,
&layer->big_state->texture_combine_rgb_func,
layer->big_state->texture_combine_rgb_src,
layer->big_state->texture_combine_rgb_op);
setup_texture_combine_state (a,
&layer->big_state->texture_combine_alpha_func,
layer->big_state->texture_combine_alpha_src,
layer->big_state->texture_combine_alpha_op);
/* If the original layer we found is currently the authority on
* the state we are changing see if we can revert to one of our
* ancestors being the authority. */
if (layer == authority &&
_cogl_material_layer_get_parent (authority) != NULL)
{
CoglMaterialLayer *parent = _cogl_material_layer_get_parent (authority);
CoglMaterialLayer *old_authority =
_cogl_material_layer_get_authority (parent, state);
if (_cogl_material_layer_combine_state_equal (authority,
old_authority))
{
layer->differences &= ~state;
g_assert (layer->owner == material);
if (layer->differences == 0)
_cogl_material_prune_empty_layer_difference (material,
layer);
goto changed;
}
}
/* 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... */
if (layer != authority)
{
layer->differences |= state;
_cogl_material_layer_prune_redundant_ancestry (layer);
}
changed:
handle_automatic_blend_enable (material, COGL_MATERIAL_STATE_LAYERS);
return TRUE;
}
void
cogl_material_set_layer_combine_constant (CoglMaterial *material,
int layer_index,
const CoglColor *constant_color)
{
CoglMaterialLayerState state = COGL_MATERIAL_LAYER_STATE_COMBINE_CONSTANT;
CoglMaterialLayer *layer;
CoglMaterialLayer *authority;
CoglMaterialLayer *new;
g_return_if_fail (cogl_is_material (material));
/* Note: this will ensure that the layer exists, creating one if it
* doesn't already.
*
* Note: If the layer already existed it's possibly owned by another
* material. If the layer is created then it will be owned by
* material. */
layer = _cogl_material_get_layer (material, layer_index);
/* Now find the ancestor of the layer that is the authority for the
* state we want to change */
authority = _cogl_material_layer_get_authority (layer, state);
if (memcmp (authority->big_state->texture_combine_constant,
constant_color, sizeof (float) * 4) == 0)
return;
new = _cogl_material_layer_pre_change_notify (material, layer, state);
if (new != layer)
layer = new;
else
{
/* If the original layer we found is currently the authority on
* the state we are changing see if we can revert to one of our
* ancestors being the authority. */
if (layer == authority &&
_cogl_material_layer_get_parent (authority) != NULL)
{
CoglMaterialLayer *parent =
_cogl_material_layer_get_parent (authority);
CoglMaterialLayer *old_authority =
_cogl_material_layer_get_authority (parent, state);
CoglMaterialLayerBigState *old_big_state = old_authority->big_state;
if (memcmp (old_big_state->texture_combine_constant,
constant_color, sizeof (float) * 4) == 0)
{
layer->differences &= ~state;
g_assert (layer->owner == material);
if (layer->differences == 0)
_cogl_material_prune_empty_layer_difference (material,
layer);
goto changed;
}
}
}
layer->big_state->texture_combine_constant[0] =
cogl_color_get_red_float (constant_color);
layer->big_state->texture_combine_constant[1] =
cogl_color_get_green_float (constant_color);
layer->big_state->texture_combine_constant[2] =
cogl_color_get_blue_float (constant_color);
layer->big_state->texture_combine_constant[3] =
cogl_color_get_alpha_float (constant_color);
/* 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... */
if (layer != authority)
{
layer->differences |= state;
_cogl_material_layer_prune_redundant_ancestry (layer);
}
changed:
handle_automatic_blend_enable (material, COGL_MATERIAL_STATE_LAYERS);
}
void
_cogl_material_get_layer_combine_constant (CoglMaterial *material,
int layer_index,
float *constant)
{
CoglMaterialLayerState change =
COGL_MATERIAL_LAYER_STATE_COMBINE_CONSTANT;
CoglMaterialLayer *layer;
CoglMaterialLayer *authority;
g_return_if_fail (cogl_is_material (material));
/* Note: this will ensure that the layer exists, creating one if it
* doesn't already.
*
* Note: If the layer already existed it's possibly owned by another
* material. If the layer is created then it will be owned by
* material. */
layer = _cogl_material_get_layer (material, layer_index);
/* FIXME: we shouldn't ever construct a layer in a getter function */
authority = _cogl_material_layer_get_authority (layer, change);
memcpy (constant, authority->big_state->texture_combine_constant,
sizeof (float) * 4);
}
void
cogl_material_set_layer_matrix (CoglMaterial *material,
int layer_index,
const CoglMatrix *matrix)
{
CoglMaterialLayerState state = COGL_MATERIAL_LAYER_STATE_USER_MATRIX;
CoglMaterialLayer *layer;
CoglMaterialLayer *authority;
CoglMaterialLayer *new;
g_return_if_fail (cogl_is_material (material));
/* Note: this will ensure that the layer exists, creating one if it
* doesn't already.
*
* Note: If the layer already existed it's possibly owned by another
* material. If the layer is created then it will be owned by
* material. */
layer = _cogl_material_get_layer (material, layer_index);
/* Now find the ancestor of the layer that is the authority for the
* state we want to change */
authority = _cogl_material_layer_get_authority (layer, state);
if (cogl_matrix_equal (matrix, &authority->big_state->matrix))
return;
new = _cogl_material_layer_pre_change_notify (material, layer, state);
if (new != layer)
layer = new;
else
{
/* If the original layer we found is currently the authority on
* the state we are changing see if we can revert to one of our
* ancestors being the authority. */
if (layer == authority &&
_cogl_material_layer_get_parent (authority) != NULL)
{
CoglMaterialLayer *parent =
_cogl_material_layer_get_parent (authority);
CoglMaterialLayer *old_authority =
_cogl_material_layer_get_authority (parent, state);
if (cogl_matrix_equal (matrix, &old_authority->big_state->matrix))
{
layer->differences &= ~state;
g_assert (layer->owner == material);
if (layer->differences == 0)
_cogl_material_prune_empty_layer_difference (material,
layer);
return;
}
}
}
layer->big_state->matrix = *matrix;
/* 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... */
if (layer != authority)
{
layer->differences |= state;
_cogl_material_layer_prune_redundant_ancestry (layer);
}
}
void
cogl_material_remove_layer (CoglMaterial *material, int layer_index)
{
CoglMaterial *authority;
CoglMaterialLayerInfo layer_info;
int i;
g_return_if_fail (cogl_is_material (material));
authority =
_cogl_material_get_authority (material, COGL_MATERIAL_STATE_LAYERS);
/* The layer index of the layer we want info about */
layer_info.layer_index = layer_index;
/* This will be updated with a reference to the layer being removed
* if it can be found. */
layer_info.layer = NULL;
/* This will be filled in with a list of layers that need to be
* dropped down to a lower texture unit to fill the gap of the
* removed layer. */
layer_info.layers_to_shift =
g_alloca (sizeof (CoglMaterialLayer *) * authority->n_layers);
layer_info.n_layers_to_shift = 0;
/* Unlike when we query layer info when adding a layer we must
* always have a complete layers_to_shift list... */
layer_info.ignore_shift_layers_if_found = FALSE;
_cogl_material_get_layer_info (authority, &layer_info);
if (layer_info.layer == NULL)
return;
for (i = 0; i < layer_info.n_layers_to_shift; i++)
{
CoglMaterialLayer *shift_layer = layer_info.layers_to_shift[i];
int unit_index = _cogl_material_layer_get_unit_index (shift_layer);
_cogl_material_set_layer_unit (material, shift_layer, unit_index - 1);
/* NB: shift_layer may not be writeable so _set_layer_unit()
* will allocate a derived layer internally which will become
* owned by material. Check the return value if we need to do
* anything else with this layer. */
}
_cogl_material_remove_layer_difference (material, layer_info.layer, TRUE);
_cogl_material_try_reverting_layers_authority (material, NULL);
handle_automatic_blend_enable (material, COGL_MATERIAL_STATE_LAYERS);
}
static gboolean
prepend_layer_to_list_cb (CoglMaterialLayer *layer,
void *user_data)
{
GList **layers = user_data;
*layers = g_list_prepend (*layers, layer);
return TRUE;
}
/* TODO: deprecate this API and replace it with
* cogl_material_foreach_layer
* TODO: update the docs to note that if the user modifies any layers
* then the list may become invalid.
*/
const GList *
cogl_material_get_layers (CoglMaterial *material)
{
g_return_val_if_fail (cogl_is_material (material), NULL);
if (!material->deprecated_get_layers_list_dirty)
g_list_free (material->deprecated_get_layers_list);
material->deprecated_get_layers_list = NULL;
_cogl_material_foreach_layer_internal (material,
prepend_layer_to_list_cb,
&material->deprecated_get_layers_list);
material->deprecated_get_layers_list =
g_list_reverse (material->deprecated_get_layers_list);
material->deprecated_get_layers_list_dirty = 0;
return material->deprecated_get_layers_list;
}
int
cogl_material_get_n_layers (CoglMaterial *material)
{
CoglMaterial *authority;
g_return_val_if_fail (cogl_is_material (material), 0);
authority =
_cogl_material_get_authority (material, COGL_MATERIAL_STATE_LAYERS);
return authority->n_layers;
}
/* FIXME: deprecate and replace with
* cogl_material_get_layer_type() instead. */
CoglMaterialLayerType
cogl_material_layer_get_type (CoglMaterialLayer *layer)
{
return COGL_MATERIAL_LAYER_TYPE_TEXTURE;
}
/* FIXME: deprecate and replace with
* cogl_material_get_layer_texture() instead. */
CoglHandle
cogl_material_layer_get_texture (CoglMaterialLayer *layer)
{
g_return_val_if_fail (_cogl_is_material_layer (layer),
COGL_INVALID_HANDLE);
return _cogl_material_layer_get_texture (layer);
}
gboolean
_cogl_material_layer_has_user_matrix (CoglMaterialLayer *layer)
{
CoglMaterialLayer *authority;
g_return_val_if_fail (_cogl_is_material_layer (layer), FALSE);
authority =
_cogl_material_layer_get_authority (layer,
COGL_MATERIAL_LAYER_STATE_USER_MATRIX);
/* If the authority is the default material then no, otherwise yes */
return _cogl_material_layer_get_parent (authority) ? TRUE : FALSE;
}
void
_cogl_material_layer_get_filters (CoglMaterialLayer *layer,
CoglMaterialFilter *min_filter,
CoglMaterialFilter *mag_filter)
{
CoglMaterialLayer *authority =
_cogl_material_layer_get_authority (layer,
COGL_MATERIAL_LAYER_STATE_FILTERS);
*min_filter = authority->min_filter;
*mag_filter = authority->mag_filter;
}
void
_cogl_material_get_layer_filters (CoglMaterial *material,
int layer_index,
CoglMaterialFilter *min_filter,
CoglMaterialFilter *mag_filter)
{
CoglMaterialLayer *layer;
CoglMaterialLayer *authority;
g_return_if_fail (cogl_is_material (material));
layer = _cogl_material_get_layer (material, layer_index);
authority =
_cogl_material_layer_get_authority (layer,
COGL_MATERIAL_LAYER_STATE_FILTERS);
*min_filter = authority->min_filter;
*mag_filter = authority->mag_filter;
}
CoglMaterialFilter
_cogl_material_get_layer_min_filter (CoglMaterial *material,
int layer_index)
{
CoglMaterialFilter min_filter;
CoglMaterialFilter mag_filter;
_cogl_material_get_layer_filters (material, layer_index,
&min_filter, &mag_filter);
return min_filter;
}
CoglMaterialFilter
_cogl_material_get_layer_mag_filter (CoglMaterial *material,
int layer_index)
{
CoglMaterialFilter min_filter;
CoglMaterialFilter mag_filter;
_cogl_material_get_layer_filters (material, layer_index,
&min_filter, &mag_filter);
return mag_filter;
}
void
_cogl_material_layer_pre_paint (CoglMaterialLayer *layer)
{
CoglMaterialLayer *texture_authority;
texture_authority =
_cogl_material_layer_get_authority (layer,
COGL_MATERIAL_LAYER_STATE_TEXTURE);
if (texture_authority->texture != COGL_INVALID_HANDLE)
{
CoglTexturePrePaintFlags flags = 0;
CoglMaterialFilter min_filter;
CoglMaterialFilter mag_filter;
_cogl_material_layer_get_filters (layer, &min_filter, &mag_filter);
if (min_filter == COGL_MATERIAL_FILTER_NEAREST_MIPMAP_NEAREST
|| min_filter == COGL_MATERIAL_FILTER_LINEAR_MIPMAP_NEAREST
|| min_filter == COGL_MATERIAL_FILTER_NEAREST_MIPMAP_LINEAR
|| min_filter == COGL_MATERIAL_FILTER_LINEAR_MIPMAP_LINEAR)
flags |= COGL_TEXTURE_NEEDS_MIPMAP;
_cogl_texture_pre_paint (texture_authority->texture, flags);
}
}
void
_cogl_material_pre_paint_for_layer (CoglMaterial *material,
int layer_id)
{
CoglMaterialLayer *layer = _cogl_material_get_layer (material, layer_id);
_cogl_material_layer_pre_paint (layer);
}
CoglMaterialFilter
cogl_material_layer_get_min_filter (CoglMaterialLayer *layer)
{
CoglMaterialLayer *authority;
g_return_val_if_fail (_cogl_is_material_layer (layer), 0);
authority =
_cogl_material_layer_get_authority (layer,
COGL_MATERIAL_LAYER_STATE_FILTERS);
return authority->min_filter;
}
CoglMaterialFilter
cogl_material_layer_get_mag_filter (CoglMaterialLayer *layer)
{
CoglMaterialLayer *authority;
g_return_val_if_fail (_cogl_is_material_layer (layer), 0);
authority =
_cogl_material_layer_get_authority (layer,
COGL_MATERIAL_LAYER_STATE_FILTERS);
return authority->mag_filter;
}
void
cogl_material_set_layer_filters (CoglMaterial *material,
int layer_index,
CoglMaterialFilter min_filter,
CoglMaterialFilter mag_filter)
{
CoglMaterialLayerState state = COGL_MATERIAL_LAYER_STATE_FILTERS;
CoglMaterialLayer *layer;
CoglMaterialLayer *authority;
CoglMaterialLayer *new;
g_return_if_fail (cogl_is_material (material));
/* Note: this will ensure that the layer exists, creating one if it
* doesn't already.
*
* Note: If the layer already existed it's possibly owned by another
* material. If the layer is created then it will be owned by
* material. */
layer = _cogl_material_get_layer (material, layer_index);
/* Now find the ancestor of the layer that is the authority for the
* state we want to change */
authority = _cogl_material_layer_get_authority (layer, state);
if (authority->min_filter == min_filter &&
authority->mag_filter == mag_filter)
return;
new = _cogl_material_layer_pre_change_notify (material, layer, state);
if (new != layer)
layer = new;
else
{
/* If the original layer we found is currently the authority on
* the state we are changing see if we can revert to one of our
* ancestors being the authority. */
if (layer == authority &&
_cogl_material_layer_get_parent (authority) != NULL)
{
CoglMaterialLayer *parent =
_cogl_material_layer_get_parent (authority);
CoglMaterialLayer *old_authority =
_cogl_material_layer_get_authority (parent, state);
if (old_authority->min_filter == min_filter &&
old_authority->mag_filter == mag_filter)
{
layer->differences &= ~state;
g_assert (layer->owner == material);
if (layer->differences == 0)
_cogl_material_prune_empty_layer_difference (material,
layer);
return;
}
}
}
layer->min_filter = min_filter;
layer->mag_filter = mag_filter;
/* 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... */
if (layer != authority)
{
layer->differences |= state;
_cogl_material_layer_prune_redundant_ancestry (layer);
}
}
float
cogl_material_get_point_size (CoglHandle handle)
{
CoglMaterial *material = COGL_MATERIAL (handle);
CoglMaterial *authority;
g_return_val_if_fail (cogl_is_material (handle), FALSE);
authority =
_cogl_material_get_authority (material, COGL_MATERIAL_STATE_POINT_SIZE);
return authority->big_state->point_size;
}
void
cogl_material_set_point_size (CoglHandle handle,
float point_size)
{
CoglMaterial *material = COGL_MATERIAL (handle);
CoglMaterialState state = COGL_MATERIAL_STATE_POINT_SIZE;
CoglMaterial *authority;
g_return_if_fail (cogl_is_material (handle));
authority = _cogl_material_get_authority (material, state);
if (authority->big_state->point_size == point_size)
return;
/* - 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, state, NULL, FALSE);
material->big_state->point_size = point_size;
_cogl_material_update_authority (material, authority, state,
_cogl_material_point_size_equal);
}
/* While a material is referenced by the Cogl journal we can not allow
* modifications, so this gives us a mechanism to track journal
* references separately */
CoglMaterial *
_cogl_material_journal_ref (CoglMaterial *material)
{
material->journal_ref_count++;
return cogl_object_ref (material);
}
void
_cogl_material_journal_unref (CoglMaterial *material)
{
material->journal_ref_count--;
cogl_object_unref (material);
}
void
_cogl_material_apply_legacy_state (CoglMaterial *material)
{
_COGL_GET_CONTEXT (ctx, NO_RETVAL);
/* It was a mistake that we ever copied the OpenGL style API for
* associating these things directly with the context when we
* originally wrote Cogl. Until the corresponding deprecated APIs
* can be removed though we now shoehorn the state changes through
* the cogl_material API instead.
*/
/* A program explicitly set on the material has higher precedence than
* one associated with the context using cogl_program_use() */
if (ctx->current_program &&
cogl_material_get_user_program (material) == COGL_INVALID_HANDLE)
cogl_material_set_user_program (material, ctx->current_program);
if (ctx->legacy_depth_test_enabled)
cogl_material_set_depth_test_enabled (material, TRUE);
if (ctx->legacy_fog_state.enabled)
_cogl_material_set_fog_state (material, &ctx->legacy_fog_state);
}
void
_cogl_material_set_static_breadcrumb (CoglMaterial *material,
const char *breadcrumb)
{
material->has_static_breadcrumb = TRUE;
material->static_breadcrumb = breadcrumb;
}
typedef struct
{
int parent_id;
int *node_id_ptr;
GString *graph;
int indent;
} PrintDebugState;
static gboolean
dump_layer_cb (CoglMaterialNode *node, void *user_data)
{
CoglMaterialLayer *layer = COGL_MATERIAL_LAYER (node);
PrintDebugState *state = user_data;
int layer_id = *state->node_id_ptr;
PrintDebugState state_out;
GString *changes_label;
gboolean changes = FALSE;
if (state->parent_id >= 0)
g_string_append_printf (state->graph, "%*slayer%p -> layer%p;\n",
state->indent, "",
layer->_parent.parent,
layer);
g_string_append_printf (state->graph,
"%*slayer%p [label=\"layer=0x%p\\n"
"ref count=%d\" "
"color=\"blue\"];\n",
state->indent, "",
layer,
layer,
COGL_OBJECT (layer)->ref_count);
changes_label = g_string_new ("");
g_string_append_printf (changes_label,
"%*slayer%p -> layer_state%d [weight=100];\n"
"%*slayer_state%d [shape=box label=\"",
state->indent, "",
layer,
layer_id,
state->indent, "",
layer_id);
if (layer->differences & COGL_MATERIAL_LAYER_STATE_UNIT)
{
changes = TRUE;
g_string_append_printf (changes_label,
"\\lunit=%u\\n",
layer->unit_index);
}
if (layer->differences & COGL_MATERIAL_LAYER_STATE_TEXTURE)
{
changes = TRUE;
g_string_append_printf (changes_label,
"\\ltexture=%p\\n",
layer->texture);
}
if (changes)
{
g_string_append_printf (changes_label, "\"];\n");
g_string_append (state->graph, changes_label->str);
g_string_free (changes_label, TRUE);
}
state_out.parent_id = layer_id;
state_out.node_id_ptr = state->node_id_ptr;
(*state_out.node_id_ptr)++;
state_out.graph = state->graph;
state_out.indent = state->indent + 2;
_cogl_material_node_foreach_child (COGL_MATERIAL_NODE (layer),
dump_layer_cb,
&state_out);
return TRUE;
}
static gboolean
dump_layer_ref_cb (CoglMaterialLayer *layer, void *data)
{
PrintDebugState *state = data;
int material_id = *state->node_id_ptr;
g_string_append_printf (state->graph,
"%*smaterial_state%d -> layer%p;\n",
state->indent, "",
material_id,
layer);
return TRUE;
}
static gboolean
dump_material_cb (CoglMaterialNode *node, void *user_data)
{
CoglMaterial *material = COGL_MATERIAL (node);
PrintDebugState *state = user_data;
int material_id = *state->node_id_ptr;
PrintDebugState state_out;
GString *changes_label;
gboolean changes = FALSE;
gboolean layers = FALSE;
if (state->parent_id >= 0)
g_string_append_printf (state->graph, "%*smaterial%d -> material%d;\n",
state->indent, "",
state->parent_id,
material_id);
g_string_append_printf (state->graph,
"%*smaterial%d [label=\"material=0x%p\\n"
"ref count=%d\\n"
"breadcrumb=\\\"%s\\\"\" color=\"red\"];\n",
state->indent, "",
material_id,
material,
COGL_OBJECT (material)->ref_count,
material->has_static_breadcrumb ?
material->static_breadcrumb : "NULL");
changes_label = g_string_new ("");
g_string_append_printf (changes_label,
"%*smaterial%d -> material_state%d [weight=100];\n"
"%*smaterial_state%d [shape=box label=\"",
state->indent, "",
material_id,
material_id,
state->indent, "",
material_id);
if (material->differences & COGL_MATERIAL_STATE_COLOR)
{
changes = TRUE;
g_string_append_printf (changes_label,
"\\lcolor=0x%02X%02X%02X%02X\\n",
cogl_color_get_red_byte (&material->color),
cogl_color_get_green_byte (&material->color),
cogl_color_get_blue_byte (&material->color),
cogl_color_get_alpha_byte (&material->color));
}
if (material->differences & COGL_MATERIAL_STATE_BLEND)
{
const char *blend_enable_name;
changes = TRUE;
switch (material->blend_enable)
{
case COGL_MATERIAL_BLEND_ENABLE_AUTOMATIC:
blend_enable_name = "AUTO";
break;
case COGL_MATERIAL_BLEND_ENABLE_ENABLED:
blend_enable_name = "ENABLED";
break;
case COGL_MATERIAL_BLEND_ENABLE_DISABLED:
blend_enable_name = "DISABLED";
break;
default:
blend_enable_name = "UNKNOWN";
}
g_string_append_printf (changes_label,
"\\lblend=%s\\n",
blend_enable_name);
}
if (material->differences & COGL_MATERIAL_STATE_LAYERS)
{
changes = TRUE;
layers = TRUE;
g_string_append_printf (changes_label, "\\ln_layers=%d\\n",
material->n_layers);
}
if (changes)
{
g_string_append_printf (changes_label, "\"];\n");
g_string_append (state->graph, changes_label->str);
g_string_free (changes_label, TRUE);
}
if (layers)
{
g_list_foreach (material->layer_differences,
(GFunc)dump_layer_ref_cb,
state);
}
state_out.parent_id = material_id;
state_out.node_id_ptr = state->node_id_ptr;
(*state_out.node_id_ptr)++;
state_out.graph = state->graph;
state_out.indent = state->indent + 2;
_cogl_material_node_foreach_child (COGL_MATERIAL_NODE (material),
dump_material_cb,
&state_out);
return TRUE;
}
void
_cogl_debug_dump_materials_dot_file (const char *filename)
{
GString *graph;
PrintDebugState layer_state;
PrintDebugState material_state;
int layer_id = 0;
int material_id = 0;
_COGL_GET_CONTEXT (ctx, NO_RETVAL);
if (!ctx->default_material)
return;
graph = g_string_new ("");
g_string_append_printf (graph, "digraph {\n");
layer_state.graph = graph;
layer_state.parent_id = -1;
layer_state.node_id_ptr = &layer_id;
layer_state.indent = 0;
dump_layer_cb (ctx->default_layer_0, &layer_state);
material_state.graph = graph;
material_state.parent_id = -1;
material_state.node_id_ptr = &material_id;
material_state.indent = 0;
dump_material_cb (ctx->default_material, &material_state);
g_string_append_printf (graph, "}\n");
if (filename)
g_file_set_contents (filename, graph->str, -1, NULL);
else
g_print ("%s", graph->str);
g_string_free (graph, TRUE);
}