diff options
Diffstat (limited to 'cogl/cogl-pipeline.c')
-rw-r--r-- | cogl/cogl-pipeline.c | 5725 |
1 files changed, 5725 insertions, 0 deletions
diff --git a/cogl/cogl-pipeline.c b/cogl/cogl-pipeline.c new file mode 100644 index 00000000..bde53f11 --- /dev/null +++ b/cogl/cogl-pipeline.c @@ -0,0 +1,5725 @@ +/* + * 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 + * <http://www.gnu.org/licenses/>. + * + * + * + * Authors: + * Robert Bragg <robert@linux.intel.com> + */ + +#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-pipeline-private.h" +#include "cogl-pipeline-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 <glib.h> +#include <glib/gprintf.h> +#include <string.h> + +typedef gboolean (*CoglPipelineStateComparitor) (CoglPipeline *authority0, + CoglPipeline *authority1); + +static CoglPipelineLayer *_cogl_pipeline_layer_copy (CoglPipelineLayer *layer); + +static void _cogl_pipeline_free (CoglPipeline *tex); +static void _cogl_pipeline_layer_free (CoglPipelineLayer *layer); +static void _cogl_pipeline_add_layer_difference (CoglPipeline *pipeline, + CoglPipelineLayer *layer, + gboolean inc_n_layers); +static void handle_automatic_blend_enable (CoglPipeline *pipeline, + CoglPipelineState changes); +static void recursively_free_layer_caches (CoglPipeline *pipeline); +static gboolean _cogl_pipeline_is_weak (CoglPipeline *pipeline); + +const CoglPipelineBackend *_cogl_pipeline_backends[COGL_PIPELINE_N_BACKENDS]; + +#ifdef COGL_PIPELINE_BACKEND_GLSL +#include "cogl-pipeline-glsl-private.h" +#endif +#ifdef COGL_PIPELINE_BACKEND_ARBFP +#include "cogl-pipeline-arbfp-private.h" +#endif +#ifdef COGL_PIPELINE_BACKEND_FIXED +#include "cogl-pipeline-fixed-private.h" +#endif + +COGL_OBJECT_DEFINE (Pipeline, pipeline); +/* This type was made deprecated before the cogl_is_pipeline_layer + function was ever exposed in the public headers so there's no need + to make the cogl_is_pipeline_layer function public. We use INTERNAL + so that the cogl_is_* function won't get defined */ +COGL_OBJECT_INTERNAL_DEFINE (PipelineLayer, pipeline_layer); + +GQuark +_cogl_pipeline_error_quark (void) +{ + return g_quark_from_static_string ("cogl-pipeline-error-quark"); +} + +static void +_cogl_pipeline_node_init (CoglPipelineNode *node) +{ + node->parent = NULL; + node->has_children = FALSE; +} + +static void +_cogl_pipeline_node_set_parent_real (CoglPipelineNode *node, + CoglPipelineNode *parent, + CoglPipelineNodeUnparentVFunc 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_pipeline_node_unparent_real (CoglPipelineNode *node) +{ + CoglPipelineNode *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_pipeline_node_foreach_child (CoglPipelineNode *node, + CoglPipelineNodeChildCallback 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 pipeline owned by the Cogl context. All + * subsequently instantiated pipelines created via the cogl_pipeline_new() + * API will initially be a copy of this pipeline. + * + * The default pipeline is the topmost ancester for all pipelines. + */ +void +_cogl_pipeline_init_default_pipeline (void) +{ + /* Create new - blank - pipeline */ + CoglPipeline *pipeline = g_slice_new0 (CoglPipeline); + CoglPipelineBigState *big_state = g_slice_new0 (CoglPipelineBigState); + CoglPipelineLightingState *lighting_state = &big_state->lighting_state; + CoglPipelineAlphaFuncState *alpha_state = &big_state->alpha_state; + CoglPipelineBlendState *blend_state = &big_state->blend_state; + CoglPipelineDepthState *depth_state = &big_state->depth_state; + + _COGL_GET_CONTEXT (ctx, NO_RETVAL); + + /* Take this opportunity to setup the fragment processing backends... */ +#ifdef COGL_PIPELINE_BACKEND_GLSL + _cogl_pipeline_backends[COGL_PIPELINE_BACKEND_GLSL] = + &_cogl_pipeline_glsl_backend; +#endif +#ifdef COGL_PIPELINE_BACKEND_ARBFP + _cogl_pipeline_backends[COGL_PIPELINE_BACKEND_ARBFP] = + &_cogl_pipeline_arbfp_backend; +#endif +#ifdef COGL_PIPELINE_BACKEND_FIXED + _cogl_pipeline_backends[COGL_PIPELINE_BACKEND_FIXED] = + &_cogl_pipeline_fixed_backend; +#endif + + _cogl_pipeline_node_init (COGL_PIPELINE_NODE (pipeline)); + + pipeline->is_weak = FALSE; + pipeline->journal_ref_count = 0; + pipeline->backend = COGL_PIPELINE_BACKEND_UNDEFINED; + pipeline->differences = COGL_PIPELINE_STATE_ALL_SPARSE; + + pipeline->real_blend_enable = FALSE; + + pipeline->blend_enable = COGL_PIPELINE_BLEND_ENABLE_AUTOMATIC; + pipeline->layer_differences = NULL; + pipeline->n_layers = 0; + + pipeline->big_state = big_state; + pipeline->has_big_state = TRUE; + + pipeline->static_breadcrumb = "default pipeline"; + pipeline->has_static_breadcrumb = TRUE; + + pipeline->age = 0; + + /* Use the same defaults as the GL spec... */ + cogl_color_init_from_4ub (&pipeline->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_PIPELINE_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_pipeline = _cogl_pipeline_object_new (pipeline); +} + +static void +_cogl_pipeline_unparent (CoglPipelineNode *pipeline) +{ + /* Chain up */ + _cogl_pipeline_node_unparent_real (pipeline); +} + +static gboolean +recursively_free_layer_caches_cb (CoglPipelineNode *node, + void *user_data) +{ + recursively_free_layer_caches (COGL_PIPELINE (node)); + return TRUE; +} + +/* This recursively frees the layers_cache of a pipeline and all of + * its descendants. + * + * For instance if we change a pipelines ->layer_differences list + * then that pipeline and all of its descendants may now have + * incorrect layer caches. */ +static void +recursively_free_layer_caches (CoglPipeline *pipeline) +{ + /* Note: we maintain the invariable that if a pipeline already has a + * dirty layers_cache then so do all of its descendants. */ + if (pipeline->layers_cache_dirty) + return; + + if (G_UNLIKELY (pipeline->layers_cache != pipeline->short_layers_cache)) + g_slice_free1 (sizeof (CoglPipelineLayer *) * pipeline->n_layers, + pipeline->layers_cache); + pipeline->layers_cache_dirty = TRUE; + + _cogl_pipeline_node_foreach_child (COGL_PIPELINE_NODE (pipeline), + recursively_free_layer_caches_cb, + NULL); +} + +static void +_cogl_pipeline_set_parent (CoglPipeline *pipeline, + CoglPipeline *parent, + gboolean take_strong_reference) +{ + /* Chain up */ + _cogl_pipeline_node_set_parent_real (COGL_PIPELINE_NODE (pipeline), + COGL_PIPELINE_NODE (parent), + _cogl_pipeline_unparent, + take_strong_reference); + + /* Since we just changed the ancestry of the pipeline its cache of + * layers could now be invalid so free it... */ + if (pipeline->differences & COGL_PIPELINE_STATE_LAYERS) + recursively_free_layer_caches (pipeline); + + /* If the fragment processing backend is also caching state along + * with the pipeline that depends on the pipeline's ancestry then it + * may be notified here... + */ + if (pipeline->backend != COGL_PIPELINE_BACKEND_UNDEFINED && + _cogl_pipeline_backends[pipeline->backend]->pipeline_set_parent_notify) + { + const CoglPipelineBackend *backend = + _cogl_pipeline_backends[pipeline->backend]; + backend->pipeline_set_parent_notify (pipeline); + } +} + +static void +_cogl_pipeline_promote_weak_ancestors (CoglPipeline *strong) +{ + CoglPipelineNode *n; + + g_return_if_fail (!strong->is_weak); + + for (n = COGL_PIPELINE_NODE (strong)->parent; n; n = n->parent) + { + CoglPipeline *pipeline = COGL_PIPELINE (n); + + cogl_object_ref (pipeline); + + if (!pipeline->is_weak) + return; + } +} + +static void +_cogl_pipeline_revert_weak_ancestors (CoglPipeline *strong) +{ + CoglPipeline *parent = _cogl_pipeline_get_parent (strong); + CoglPipelineNode *n; + + g_return_if_fail (!strong->is_weak); + + if (!parent || !parent->is_weak) + return; + + for (n = COGL_PIPELINE_NODE (strong)->parent; n; n = n->parent) + { + CoglPipeline *pipeline = COGL_PIPELINE (n); + + cogl_object_unref (pipeline); + + if (!pipeline->is_weak) + return; + } +} + +/* XXX: Always have an eye out for opportunities to lower the cost of + * cogl_pipeline_copy. */ +static CoglPipeline * +_cogl_pipeline_copy (CoglPipeline *src, gboolean is_weak) +{ + CoglPipeline *pipeline = g_slice_new (CoglPipeline); + + _cogl_pipeline_node_init (COGL_PIPELINE_NODE (pipeline)); + + pipeline->is_weak = is_weak; + + pipeline->journal_ref_count = 0; + + pipeline->differences = 0; + + pipeline->has_big_state = FALSE; + + /* NB: real_blend_enable isn't a sparse property, it's valid for + * every pipeline node so we have fast access to it. */ + pipeline->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 pipeline. + */ + + pipeline->layers_cache_dirty = TRUE; + pipeline->deprecated_get_layers_list_dirty = TRUE; + + pipeline->backend = src->backend; + pipeline->backend_priv_set_mask = 0; + + pipeline->has_static_breadcrumb = FALSE; + + pipeline->age = 0; + + _cogl_pipeline_set_parent (pipeline, src, !is_weak); + + /* The semantics for copying a weak pipeline are that we promote all + * weak ancestors to temporarily become strong pipelines until the + * copy is freed. */ + if (!is_weak) + _cogl_pipeline_promote_weak_ancestors (pipeline); + + return _cogl_pipeline_object_new (pipeline); +} + +CoglPipeline * +cogl_pipeline_copy (CoglPipeline *src) +{ + return _cogl_pipeline_copy (src, FALSE); +} + +CoglPipeline * +_cogl_pipeline_weak_copy (CoglPipeline *pipeline, + CoglPipelineDestroyCallback callback, + void *user_data) +{ + CoglPipeline *copy; + CoglPipeline *copy_pipeline; + + copy = _cogl_pipeline_copy (pipeline, TRUE); + copy_pipeline = COGL_PIPELINE (copy); + copy_pipeline->destroy_callback = callback; + copy_pipeline->destroy_data = user_data; + + return copy; +} + +CoglPipeline * +cogl_pipeline_new (void) +{ + CoglPipeline *new; + + _COGL_GET_CONTEXT (ctx, NULL); + + new = cogl_pipeline_copy (ctx->default_pipeline); + _cogl_pipeline_set_static_breadcrumb (new, "new"); + return new; +} + +static void +_cogl_pipeline_backend_free_priv (CoglPipeline *pipeline) +{ + if (pipeline->backend != COGL_PIPELINE_BACKEND_UNDEFINED && + _cogl_pipeline_backends[pipeline->backend]->free_priv) + { + const CoglPipelineBackend *backend = + _cogl_pipeline_backends[pipeline->backend]; + backend->free_priv (pipeline); + } +} + +static gboolean +destroy_weak_children_cb (CoglPipelineNode *node, + void *user_data) +{ + CoglPipeline *pipeline = COGL_PIPELINE (node); + + if (_cogl_pipeline_is_weak (pipeline)) + { + _cogl_pipeline_node_foreach_child (COGL_PIPELINE_NODE (pipeline), + destroy_weak_children_cb, + NULL); + + pipeline->destroy_callback (pipeline, pipeline->destroy_data); + _cogl_pipeline_unparent (COGL_PIPELINE_NODE (pipeline)); + } + + return TRUE; +} + +static void +_cogl_pipeline_free (CoglPipeline *pipeline) +{ + if (!pipeline->is_weak) + _cogl_pipeline_revert_weak_ancestors (pipeline); + + /* Weak pipelines don't take a reference on their parent */ + _cogl_pipeline_node_foreach_child (COGL_PIPELINE_NODE (pipeline), + destroy_weak_children_cb, + NULL); + + g_assert (!COGL_PIPELINE_NODE (pipeline)->has_children); + + _cogl_pipeline_backend_free_priv (pipeline); + + _cogl_pipeline_unparent (COGL_PIPELINE_NODE (pipeline)); + + if (pipeline->differences & COGL_PIPELINE_STATE_USER_SHADER && + pipeline->big_state->user_program) + cogl_handle_unref (pipeline->big_state->user_program); + + if (pipeline->differences & COGL_PIPELINE_STATE_NEEDS_BIG_STATE) + g_slice_free (CoglPipelineBigState, pipeline->big_state); + + if (pipeline->differences & COGL_PIPELINE_STATE_LAYERS) + { + g_list_foreach (pipeline->layer_differences, + (GFunc)cogl_object_unref, NULL); + g_list_free (pipeline->layer_differences); + } + + g_slice_free (CoglPipeline, pipeline); +} + +gboolean +_cogl_pipeline_get_real_blend_enabled (CoglPipeline *pipeline) +{ + g_return_val_if_fail (cogl_is_pipeline (pipeline), FALSE); + + return pipeline->real_blend_enable; +} + +inline CoglPipeline * +_cogl_pipeline_get_parent (CoglPipeline *pipeline) +{ + CoglPipelineNode *parent_node = COGL_PIPELINE_NODE (pipeline)->parent; + return COGL_PIPELINE (parent_node); +} + +CoglPipeline * +_cogl_pipeline_get_authority (CoglPipeline *pipeline, + unsigned long difference) +{ + CoglPipeline *authority = pipeline; + while (!(authority->differences & difference)) + authority = _cogl_pipeline_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 CoglPipelineLayer * +_cogl_pipeline_layer_get_parent (CoglPipelineLayer *layer) +{ + CoglPipelineNode *parent_node = COGL_PIPELINE_NODE (layer)->parent; + return COGL_PIPELINE_LAYER (parent_node); +} + +CoglPipelineLayer * +_cogl_pipeline_layer_get_authority (CoglPipelineLayer *layer, + unsigned long difference) +{ + CoglPipelineLayer *authority = layer; + while (!(authority->differences & difference)) + authority = _cogl_pipeline_layer_get_parent (authority); + return authority; +} + +int +_cogl_pipeline_layer_get_unit_index (CoglPipelineLayer *layer) +{ + CoglPipelineLayer *authority = + _cogl_pipeline_layer_get_authority (layer, COGL_PIPELINE_LAYER_STATE_UNIT); + return authority->unit_index; +} + +static void +_cogl_pipeline_update_layers_cache (CoglPipeline *pipeline) +{ + /* Note: we assume this pipeline is a _LAYERS authority */ + int n_layers; + CoglPipeline *current; + int layers_found; + + if (G_LIKELY (!pipeline->layers_cache_dirty) || + pipeline->n_layers == 0) + return; + + pipeline->layers_cache_dirty = FALSE; + + n_layers = pipeline->n_layers; + if (G_LIKELY (n_layers < G_N_ELEMENTS (pipeline->short_layers_cache))) + { + pipeline->layers_cache = pipeline->short_layers_cache; + memset (pipeline->layers_cache, 0, + sizeof (CoglPipelineLayer *) * + G_N_ELEMENTS (pipeline->short_layers_cache)); + } + else + { + pipeline->layers_cache = + g_slice_alloc0 (sizeof (CoglPipelineLayer *) * n_layers); + } + + /* Notes: + * + * Each pipeline doesn't have to contain a complete list of the layers + * it depends on, some of them are indirectly referenced through the + * pipeline's ancestors. + * + * pipeline->layer_differences only contains a list of layers that + * have changed in relation to its parent. + * + * pipeline->layer_differences is not maintained sorted, but it + * won't contain multiple layers corresponding to a particular + * ->unit_index. + * + * Some of the ancestor pipelines may reference layers with + * ->unit_index values >= n_layers so we ignore them. + * + * As we ascend through the ancestors we are searching for any + * CoglPipelineLayers 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 = pipeline; + _cogl_pipeline_get_parent (current); + current = _cogl_pipeline_get_parent (current)) + { + GList *l; + + if (!(current->differences & COGL_PIPELINE_STATE_LAYERS)) + continue; + + for (l = current->layer_differences; l; l = l->next) + { + CoglPipelineLayer *layer = l->data; + int unit_index = _cogl_pipeline_layer_get_unit_index (layer); + + if (unit_index < n_layers && !pipeline->layers_cache[unit_index]) + { + pipeline->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_pipeline_foreach_layer_internal (CoglPipeline *pipeline, + CoglPipelineInternalLayerCallback callback, + void *user_data) +{ + CoglPipeline *authority = + _cogl_pipeline_get_authority (pipeline, COGL_PIPELINE_STATE_LAYERS); + int n_layers; + int i; + gboolean cont; + + n_layers = authority->n_layers; + if (n_layers == 0) + return; + + _cogl_pipeline_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 (CoglPipelineLayer *layer, + void *user_data) +{ + AppendLayerIndexState *state = user_data; + state->indices[state->i++] = layer->index; + return TRUE; +} + +void +cogl_pipeline_foreach_layer (CoglPipeline *pipeline, + CoglPipelineLayerCallback callback, + void *user_data) +{ + CoglPipeline *authority = + _cogl_pipeline_get_authority (pipeline, COGL_PIPELINE_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_pipeline_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_pipeline_foreach_layer_internal (pipeline, + append_layer_index_cb, + &state); + + for (i = 0, cont = TRUE; i < authority->n_layers && cont; i++) + cont = callback (pipeline, state.indices[i], user_data); +} + +static gboolean +layer_has_alpha_cb (CoglPipelineLayer *layer, void *data) +{ + CoglPipelineLayer *combine_authority = + _cogl_pipeline_layer_get_authority (layer, + COGL_PIPELINE_LAYER_STATE_COMBINE); + CoglPipelineLayerBigState *big_state = combine_authority->big_state; + CoglPipelineLayer *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_pipeline_layer_get_authority (layer, + COGL_PIPELINE_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 CoglPipeline * +_cogl_pipeline_get_user_program (CoglPipeline *pipeline) +{ + CoglPipeline *authority; + + g_return_val_if_fail (cogl_is_pipeline (pipeline), NULL); + + authority = + _cogl_pipeline_get_authority (pipeline, COGL_PIPELINE_STATE_USER_SHADER); + + return authority->big_state->user_program; +} + +static gboolean +_cogl_pipeline_needs_blending_enabled (CoglPipeline *pipeline, + unsigned long changes, + const CoglColor *override_color) +{ + CoglPipeline *enable_authority; + CoglPipeline *blend_authority; + CoglPipelineBlendState *blend_state; + CoglPipelineBlendEnable enabled; + unsigned long other_state; + + if (G_UNLIKELY (cogl_debug_flags & COGL_DEBUG_DISABLE_BLENDING)) + return FALSE; + + enable_authority = + _cogl_pipeline_get_authority (pipeline, COGL_PIPELINE_STATE_BLEND_ENABLE); + + enabled = enable_authority->blend_enable; + if (enabled != COGL_PIPELINE_BLEND_ENABLE_AUTOMATIC) + return enabled == COGL_PIPELINE_BLEND_ENABLE_ENABLED ? TRUE : FALSE; + + blend_authority = + _cogl_pipeline_get_authority (pipeline, COGL_PIPELINE_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_PIPELINE_STATE_LAYERS) + changes = COGL_PIPELINE_STATE_AFFECTS_BLENDING; + + /* XXX: we don't currently handle specific changes in an optimal way*/ + changes = COGL_PIPELINE_STATE_AFFECTS_BLENDING; + + if ((override_color && cogl_color_get_alpha_byte (override_color) != 0xff)) + return TRUE; + + if (changes & COGL_PIPELINE_STATE_COLOR) + { + CoglColor tmp; + cogl_pipeline_get_color (pipeline, &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_PIPELINE_STATE_USER_SHADER) + { + if (_cogl_pipeline_get_user_program (pipeline) != COGL_INVALID_HANDLE) + return TRUE; + } + + /* XXX: we should only need to look at these if lighting is enabled + */ + if (changes & COGL_PIPELINE_STATE_LIGHTING) + { + CoglColor tmp; + + cogl_pipeline_get_ambient (pipeline, &tmp); + if (cogl_color_get_alpha_byte (&tmp) != 0xff) + return TRUE; + cogl_pipeline_get_diffuse (pipeline, &tmp); + if (cogl_color_get_alpha_byte (&tmp) != 0xff) + return TRUE; + cogl_pipeline_get_specular (pipeline, &tmp); + if (cogl_color_get_alpha_byte (&tmp) != 0xff) + return TRUE; + cogl_pipeline_get_emission (pipeline, &tmp); + if (cogl_color_get_alpha_byte (&tmp) != 0xff) + return TRUE; + } + + if (changes & COGL_PIPELINE_STATE_LAYERS) + { + /* has_alpha tracks the alpha status of the GL_PREVIOUS layer. + * To start with that's defined by the pipeline color which + * must be fully opaque if we got this far. */ + gboolean has_alpha = FALSE; + _cogl_pipeline_foreach_layer_internal (pipeline, + 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_PIPELINE_STATE_AFFECTS_BLENDING & ~changes; + if (other_state && + _cogl_pipeline_needs_blending_enabled (pipeline, + other_state, + NULL)) + return TRUE; + + return FALSE; +} + +void +_cogl_pipeline_set_backend (CoglPipeline *pipeline, int backend) +{ + _cogl_pipeline_backend_free_priv (pipeline); + pipeline->backend = backend; +} + +static void +_cogl_pipeline_copy_differences (CoglPipeline *dest, + CoglPipeline *src, + unsigned long differences) +{ + CoglPipelineBigState *big_state; + + if (differences & COGL_PIPELINE_STATE_COLOR) + dest->color = src->color; + + if (differences & COGL_PIPELINE_STATE_BLEND_ENABLE) + dest->blend_enable = src->blend_enable; + + if (differences & COGL_PIPELINE_STATE_LAYERS) + { + GList *l; + + if (dest->differences & COGL_PIPELINE_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. */ + CoglPipelineLayer *copy = _cogl_pipeline_layer_copy (l->data); + _cogl_pipeline_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_PIPELINE_STATE_NEEDS_BIG_STATE) + { + if (!dest->has_big_state) + { + dest->big_state = g_slice_new (CoglPipelineBigState); + dest->has_big_state = TRUE; + } + big_state = dest->big_state; + } + else + goto check_for_blending_change; + + if (differences & COGL_PIPELINE_STATE_LIGHTING) + { + memcpy (&big_state->lighting_state, + &src->big_state->lighting_state, + sizeof (CoglPipelineLightingState)); + } + + if (differences & COGL_PIPELINE_STATE_ALPHA_FUNC) + { + memcpy (&big_state->alpha_state, + &src->big_state->alpha_state, + sizeof (CoglPipelineAlphaFuncState)); + } + + if (differences & COGL_PIPELINE_STATE_BLEND) + { + memcpy (&big_state->blend_state, + &src->big_state->blend_state, + sizeof (CoglPipelineBlendState)); + } + + if (differences & COGL_PIPELINE_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_PIPELINE_STATE_DEPTH) + { + memcpy (&big_state->depth_state, + &src->big_state->depth_state, + sizeof (CoglPipelineDepthState)); + } + + if (differences & COGL_PIPELINE_STATE_FOG) + { + memcpy (&big_state->fog_state, + &src->big_state->fog_state, + sizeof (CoglPipelineFogState)); + } + + if (differences & COGL_PIPELINE_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 pipeline state + * by copying it from the current authority, so it's not actually + * *changing* anything. + */ +check_for_blending_change: + if (differences & COGL_PIPELINE_STATE_AFFECTS_BLENDING) + handle_automatic_blend_enable (dest, differences); + + dest->differences |= differences; +} + +static void +_cogl_pipeline_initialize_sparse_state (CoglPipeline *dest, + CoglPipeline *src, + CoglPipelineState state) +{ + if (dest == src) + return; + + g_return_if_fail (state & COGL_PIPELINE_STATE_ALL_SPARSE); + + if (state != COGL_PIPELINE_STATE_LAYERS) + _cogl_pipeline_copy_differences (dest, src, state); + else + { + dest->n_layers = src->n_layers; + dest->layer_differences = NULL; + } +} + +static gboolean +check_if_strong_cb (CoglPipelineNode *node, void *user_data) +{ + CoglPipeline *pipeline = COGL_PIPELINE (node); + gboolean *has_strong_child = user_data; + + if (!_cogl_pipeline_is_weak (pipeline)) + { + *has_strong_child = TRUE; + return FALSE; + } + + return TRUE; +} + +static gboolean +has_strong_children (CoglPipeline *pipeline) +{ + gboolean has_strong_child = FALSE; + _cogl_pipeline_node_foreach_child (COGL_PIPELINE_NODE (pipeline), + check_if_strong_cb, + &has_strong_child); + return has_strong_child; +} + +static gboolean +_cogl_pipeline_is_weak (CoglPipeline *pipeline) +{ + if (pipeline->is_weak && !has_strong_children (pipeline)) + return TRUE; + else + return FALSE; +} + +static gboolean +reparent_children_cb (CoglPipelineNode *node, + void *user_data) +{ + CoglPipeline *pipeline = COGL_PIPELINE (node); + CoglPipeline *parent = user_data; + + _cogl_pipeline_set_parent (pipeline, parent, TRUE); + + return TRUE; +} + +static void +_cogl_pipeline_pre_change_notify (CoglPipeline *pipeline, + CoglPipelineState change, + const CoglColor *new_color, + gboolean from_layer_change) +{ + CoglPipeline *authority; + + _COGL_GET_CONTEXT (ctx, NO_RETVAL); + + /* If primitives have been logged in the journal referencing the + * current state of this pipeline we need to flush the journal + * before we can modify it... */ + if (pipeline->journal_ref_count) + { + gboolean skip_journal_flush = FALSE; + + /* XXX: We don't usually need to flush the journal just due to + * color changes since pipeline 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_PIPELINE_STATE_COLOR) + { + gboolean will_need_blending = + _cogl_pipeline_needs_blending_enabled (pipeline, + change, + new_color); + gboolean blend_enable = pipeline->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 pipeline changes so we may as + * well try to find a better backend whenever the pipeline 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 pipeline change therefore we don't want to + * try searching for another backend when the pipeline changes. + */ + if (pipeline->backend == COGL_PIPELINE_BACKEND_FIXED) + _cogl_pipeline_set_backend (pipeline, COGL_PIPELINE_BACKEND_UNDEFINED); + + if (pipeline->backend != COGL_PIPELINE_BACKEND_UNDEFINED && + _cogl_pipeline_backends[pipeline->backend]->pipeline_pre_change_notify) + { + const CoglPipelineBackend *backend = + _cogl_pipeline_backends[pipeline->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 pipeline 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 pipeline or + * layer pre-change notification for any particular change. + */ + if (!from_layer_change) + backend->pipeline_pre_change_notify (pipeline, change, new_color); + } + + /* There may be an arbitrary tree of descendants of this pipeline; + * any of which may indirectly depend on this pipeline as the + * authority for some set of properties. (Meaning for example that + * one of its descendants derives its color or blending state from + * this pipeline.) + * + * We can't modify any property that this pipeline is the authority + * for unless we create another pipeline to take its place first and + * make sure descendants reference this new pipeline instead. + */ + + /* The simplest descendants to handle are weak pipelines; we simply + * destroy them if we are modifying a pipeline they depend on. This + * means weak pipelines never cause us to do a copy-on-write. */ + _cogl_pipeline_node_foreach_child (COGL_PIPELINE_NODE (pipeline), + 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_PIPELINE_NODE (pipeline)->has_children) + { + CoglPipeline *new_authority; + + COGL_STATIC_COUNTER (pipeline_copy_on_write_counter, + "pipeline copy on write counter", + "Increments each time a pipeline " + "must be copied to allow modification", + 0 /* no application private data */); + + COGL_COUNTER_INC (_cogl_uprof_context, pipeline_copy_on_write_counter); + + new_authority = + cogl_pipeline_copy (_cogl_pipeline_get_parent (pipeline)); + _cogl_pipeline_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 pipeline 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 pipeline->differences represents the largest set of + * differences that this pipeline 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_pipeline_copy_differences (new_authority, pipeline, + pipeline->differences); + + /* Reparent the dependants of pipeline to be children of + * new_authority instead... */ + _cogl_pipeline_node_foreach_child (COGL_PIPELINE_NODE (pipeline), + 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 pipeline with no strong + * dependants (though we may have some weak children) so we are now + * free to modify the pipeline. */ + + pipeline->age++; + + /* If changing a sparse property and if the pipeline isn't already an + * authority for the state group being modified then we need to + * initialize the corresponding state. */ + if (change & COGL_PIPELINE_STATE_ALL_SPARSE && + !(pipeline->differences & change)) + { + authority = _cogl_pipeline_get_authority (pipeline, change); + _cogl_pipeline_initialize_sparse_state (pipeline, authority, change); + } + + /* Each pipeline has a sorted cache of the layers it depends on + * which will need updating via _cogl_pipeline_update_layers_cache + * if a pipeline's layers are changed. */ + if (change == COGL_PIPELINE_STATE_LAYERS) + recursively_free_layer_caches (pipeline); + + /* If the pipeline being changed is the same as the last pipeline we + * flushed then we keep a track of the changes so we can try to + * minimize redundant OpenGL calls if the same pipeline is flushed + * again. + */ + if (ctx->current_pipeline == pipeline) + ctx->current_pipeline_changes_since_flush |= change; +} + + +static void +_cogl_pipeline_add_layer_difference (CoglPipeline *pipeline, + CoglPipelineLayer *layer, + gboolean inc_n_layers) +{ + g_return_if_fail (layer->owner == NULL); + + layer->owner = pipeline; + cogl_object_ref (layer); + + /* - Flush journal primitives referencing the current state. + * - Make sure the pipeline has no dependants so it may be modified. + * - If the pipeline isn't currently an authority for the state being + * changed, then initialize that state from the current authority. + */ + _cogl_pipeline_pre_change_notify (pipeline, + COGL_PIPELINE_STATE_LAYERS, + NULL, + FALSE); + + pipeline->differences |= COGL_PIPELINE_STATE_LAYERS; + + pipeline->layer_differences = + g_list_prepend (pipeline->layer_differences, layer); + + if (inc_n_layers) + pipeline->n_layers++; +} + +/* NB: If you are calling this it's your responsibility to have + * already called: + * _cogl_pipeline_pre_change_notify (m, _CHANGE_LAYERS, NULL); + */ +static void +_cogl_pipeline_remove_layer_difference (CoglPipeline *pipeline, + CoglPipelineLayer *layer, + gboolean dec_n_layers) +{ + g_return_if_fail (layer->owner == pipeline); + + /* - Flush journal primitives referencing the current state. + * - Make sure the pipeline has no dependants so it may be modified. + * - If the pipeline isn't currently an authority for the state being + * changed, then initialize that state from the current authority. + */ + _cogl_pipeline_pre_change_notify (pipeline, + COGL_PIPELINE_STATE_LAYERS, + NULL, + FALSE); + + layer->owner = NULL; + cogl_object_unref (layer); + + pipeline->differences |= COGL_PIPELINE_STATE_LAYERS; + + pipeline->layer_differences = + g_list_remove (pipeline->layer_differences, layer); + + if (dec_n_layers) + pipeline->n_layers--; +} + +static void +_cogl_pipeline_try_reverting_layers_authority (CoglPipeline *authority, + CoglPipeline *old_authority) +{ + if (authority->layer_differences == NULL && + _cogl_pipeline_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_pipeline_get_authority (_cogl_pipeline_get_parent (authority), + COGL_PIPELINE_STATE_LAYERS); + } + + if (old_authority->n_layers == authority->n_layers) + authority->differences &= ~COGL_PIPELINE_STATE_LAYERS; + } +} + + +static void +handle_automatic_blend_enable (CoglPipeline *pipeline, + CoglPipelineState change) +{ + gboolean blend_enable = + _cogl_pipeline_needs_blending_enabled (pipeline, change, NULL); + + if (blend_enable != pipeline->real_blend_enable) + { + /* - Flush journal primitives referencing the current state. + * - Make sure the pipeline has no dependants so it may be + * modified. + * - If the pipeline isn't currently an authority for the state + * being changed, then initialize that state from the current + * authority. + */ + _cogl_pipeline_pre_change_notify (pipeline, + COGL_PIPELINE_STATE_REAL_BLEND_ENABLE, + NULL, + FALSE); + pipeline->real_blend_enable = blend_enable; + } +} + +typedef struct +{ + int keep_n; + int current_pos; + gboolean needs_pruning; + int first_index_to_prune; +} CoglPipelinePruneLayersInfo; + +static gboolean +update_prune_layers_info_cb (CoglPipelineLayer *layer, void *user_data) +{ + CoglPipelinePruneLayersInfo *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_pipeline_prune_to_n_layers (CoglPipeline *pipeline, int n) +{ + CoglPipelinePruneLayersInfo state; + gboolean notified_change = TRUE; + GList *l; + GList *next; + + state.keep_n = n; + state.current_pos = 0; + state.needs_pruning = FALSE; + _cogl_pipeline_foreach_layer_internal (pipeline, + update_prune_layers_info_cb, + &state); + + pipeline->n_layers = n; + + if (!state.needs_pruning) + return; + + if (!(pipeline->differences & COGL_PIPELINE_STATE_LAYERS)) + return; + + /* It's possible that this pipeline owns some of the layers being + * discarded, so we'll need to unlink them... */ + for (l = pipeline->layer_differences; l; l = next) + { + CoglPipelineLayer *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 pipeline has no dependants so it may + * be modified. + * - If the pipeline isn't currently an authority for + * the state being changed, then initialize that state + * from the current authority. + */ + _cogl_pipeline_pre_change_notify (pipeline, + COGL_PIPELINE_STATE_LAYERS, + NULL, + FALSE); + notified_change = TRUE; + } + + pipeline->layer_differences = + g_list_delete_link (pipeline->layer_differences, l); + } + } +} + +static void +_cogl_pipeline_backend_layer_change_notify (CoglPipeline *owner, + CoglPipelineLayer *layer, + CoglPipelineLayerState change) +{ + int i; + + /* NB: layers may be used by multiple pipelines 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_PIPELINE_N_BACKENDS; i++) + { + if (layer->backend_priv[i] && + _cogl_pipeline_backends[i]->layer_pre_change_notify) + { + const CoglPipelineBackend *backend = _cogl_pipeline_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_pipeline_layer_initialize_state (CoglPipelineLayer *dest, + CoglPipelineLayer *src, + unsigned long differences) +{ + CoglPipelineLayerBigState *big_state; + + dest->differences |= differences; + + if (differences & COGL_PIPELINE_LAYER_STATE_UNIT) + dest->unit_index = src->unit_index; + + if (differences & COGL_PIPELINE_LAYER_STATE_TEXTURE) + dest->texture = src->texture; + + if (differences & COGL_PIPELINE_LAYER_STATE_FILTERS) + { + dest->min_filter = src->min_filter; + dest->mag_filter = src->mag_filter; + } + + if (differences & COGL_PIPELINE_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_PIPELINE_LAYER_STATE_NEEDS_BIG_STATE) + { + if (!dest->has_big_state) + { + dest->big_state = g_slice_new (CoglPipelineLayerBigState); + dest->has_big_state = TRUE; + } + big_state = dest->big_state; + } + else + return; + + if (differences & COGL_PIPELINE_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_PIPELINE_LAYER_STATE_COMBINE_CONSTANT) + memcpy (dest->big_state->texture_combine_constant, + src->big_state->texture_combine_constant, + sizeof (float) * 4); + + if (differences & COGL_PIPELINE_LAYER_STATE_USER_MATRIX) + dest->big_state->matrix = src->big_state->matrix; + + if (differences & COGL_PIPELINE_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 CoglPipelineLayer * +_cogl_pipeline_layer_pre_change_notify (CoglPipeline *required_owner, + CoglPipelineLayer *layer, + CoglPipelineLayerState change) +{ + CoglTextureUnit *unit; + CoglPipelineLayer *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_PIPELINE_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_pipeline_pre_change_notify (required_owner, + COGL_PIPELINE_STATE_LAYERS, + NULL, + TRUE); + + /* Unlike pipelines; layers are simply considered immutable once + * they have dependants - either direct children, or another + * pipeline as an owner. + */ + if (COGL_PIPELINE_NODE (layer)->has_children || + layer->owner != required_owner) + { + CoglPipelineLayer *new = _cogl_pipeline_layer_copy (layer); + if (layer->owner == required_owner) + _cogl_pipeline_remove_layer_difference (required_owner, layer, FALSE); + _cogl_pipeline_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 pipeline dependant on + * this layer (required_owner), and there are no other layers + * dependant on this layer so it's ok to modify it. */ + + _cogl_pipeline_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_pipeline_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 pipeline isn't already an authority for the state group + * being modified then we need to initialize the corresponding + * state. */ + authority = _cogl_pipeline_layer_get_authority (layer, change); + _cogl_pipeline_layer_initialize_state (layer, authority, change); + + return layer; +} + +static void +_cogl_pipeline_layer_unparent (CoglPipelineNode *layer) +{ + /* Chain up */ + _cogl_pipeline_node_unparent_real (layer); +} + +static void +_cogl_pipeline_layer_set_parent (CoglPipelineLayer *layer, + CoglPipelineLayer *parent) +{ + /* Chain up */ + _cogl_pipeline_node_set_parent_real (COGL_PIPELINE_NODE (layer), + COGL_PIPELINE_NODE (parent), + _cogl_pipeline_layer_unparent, + TRUE); +} + +/* XXX: This is duplicated logic; the same as for + * _cogl_pipeline_prune_redundant_ancestry it would be nice to find a + * way to consolidate these functions! */ +static void +_cogl_pipeline_layer_prune_redundant_ancestry (CoglPipelineLayer *layer) +{ + CoglPipelineLayer *new_parent = _cogl_pipeline_layer_get_parent (layer); + + /* walk up past ancestors that are now redundant and potentially + * reparent the layer. */ + while (_cogl_pipeline_layer_get_parent (new_parent) && + (new_parent->differences | layer->differences) == + layer->differences) + new_parent = _cogl_pipeline_layer_get_parent (new_parent); + + _cogl_pipeline_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_pipeline_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 CoglPipelineLayer * +_cogl_pipeline_set_layer_unit (CoglPipeline *required_owner, + CoglPipelineLayer *layer, + int unit_index) +{ + CoglPipelineLayerState change = COGL_PIPELINE_LAYER_STATE_UNIT; + CoglPipelineLayer *authority = + _cogl_pipeline_layer_get_authority (layer, change); + CoglPipelineLayer *new; + + if (authority->unit_index == unit_index) + return layer; + + new = + _cogl_pipeline_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_pipeline_layer_get_parent (authority) != NULL) + { + CoglPipelineLayer *parent = + _cogl_pipeline_layer_get_parent (authority); + CoglPipelineLayer *old_authority = + _cogl_pipeline_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_pipeline_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 */ + CoglPipelineLayer *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 */ + CoglPipelineLayer **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; + +} CoglPipelineLayerInfo; + +/* Returns TRUE once we know there is nothing more to update */ +static gboolean +update_layer_info (CoglPipelineLayer *layer, + CoglPipelineLayerInfo *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_pipeline_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 (CoglPipelineLayer *layer, + void *user_data) +{ + CoglPipelineLayerInfo *layer_info = user_data; + + if (update_layer_info (layer, layer_info)) + return FALSE; /* break */ + else + return TRUE; /* continue */ +} + +static void +_cogl_pipeline_get_layer_info (CoglPipeline *pipeline, + CoglPipelineLayerInfo *layer_info) +{ + /* Note: we are assuming this pipeline is a _STATE_LAYERS authority */ + int n_layers = pipeline->n_layers; + int i; + + /* FIXME: _cogl_pipeline_foreach_layer_internal now calls + * _cogl_pipeline_update_layers_cache anyway so this codepath is + * pointless! */ + if (layer_info->ignore_shift_layers_if_found && + pipeline->layers_cache_dirty) + { + /* The expectation is that callers of + * _cogl_pipeline_get_layer_info are likely to be modifying the + * list of layers associated with a pipeline 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 pipeline we + * use a foreach_layer callback instead of updating the cache + * and iterating that as below. */ + _cogl_pipeline_foreach_layer_internal (pipeline, + update_layer_info_cb, + layer_info); + return; + } + + _cogl_pipeline_update_layers_cache (pipeline); + for (i = 0; i < n_layers; i++) + { + CoglPipelineLayer *layer = pipeline->layers_cache[i]; + + if (update_layer_info (layer, layer_info)) + return; + } +} + +static CoglPipelineLayer * +_cogl_pipeline_get_layer (CoglPipeline *pipeline, + int layer_index) +{ + CoglPipeline *authority = + _cogl_pipeline_get_authority (pipeline, COGL_PIPELINE_STATE_LAYERS); + CoglPipelineLayerInfo layer_info; + CoglPipelineLayer *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 (CoglPipelineLayer *) * 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_pipeline_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_pipeline_layer_copy (ctx->default_layer_0); + else + { + CoglPipelineLayer *new; + layer = _cogl_pipeline_layer_copy (ctx->default_layer_n); + new = _cogl_pipeline_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++) + { + CoglPipelineLayer *shift_layer = layer_info.layers_to_shift[i]; + + unit_index = _cogl_pipeline_layer_get_unit_index (shift_layer); + _cogl_pipeline_set_layer_unit (pipeline, 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 pipeline. Check the return value if we need to do + * anything else with this layer. */ + } + + _cogl_pipeline_add_layer_difference (pipeline, layer, TRUE); + + cogl_object_unref (layer); + + return layer; +} + +CoglHandle +_cogl_pipeline_layer_get_texture_real (CoglPipelineLayer *layer) +{ + CoglPipelineLayer *authority = + _cogl_pipeline_layer_get_authority (layer, + COGL_PIPELINE_LAYER_STATE_TEXTURE); + + return authority->texture; +} + +CoglHandle +_cogl_pipeline_get_layer_texture (CoglPipeline *pipeline, + int layer_index) +{ + CoglPipelineLayer *layer = + _cogl_pipeline_get_layer (pipeline, layer_index); + return _cogl_pipeline_layer_get_texture (layer); +} + +static void +_cogl_pipeline_prune_empty_layer_difference (CoglPipeline *layers_authority, + CoglPipelineLayer *layer) +{ + /* Find the GList link that references the empty layer */ + GList *link = g_list_find (layers_authority->layer_differences, layer); + /* No pipeline directly owns the root node layer so this is safe... */ + CoglPipelineLayer *layer_parent = _cogl_pipeline_layer_get_parent (layer); + CoglPipelineLayerInfo layer_info; + CoglPipeline *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_pipeline_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 (CoglPipelineLayer *) * 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 pipeline isn't a LAYERS authority so it's + * safe to use the result of _cogl_pipeline_get_parent (layers_authority) + * without checking it. + */ + old_layers_authority = + _cogl_pipeline_get_authority (_cogl_pipeline_get_parent (layers_authority), + COGL_PIPELINE_STATE_LAYERS); + + _cogl_pipeline_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_pipeline_layer_get_parent (layer) then we can simply remove the + * layer difference. */ + if (layer_info.layer == _cogl_pipeline_layer_get_parent (layer)) + { + _cogl_pipeline_remove_layer_difference (layers_authority, layer, FALSE); + _cogl_pipeline_try_reverting_layers_authority (layers_authority, + old_layers_authority); + } +} + +static void +_cogl_pipeline_set_layer_texture (CoglPipeline *pipeline, + int layer_index, + CoglHandle texture, + gboolean overriden, + GLuint slice_gl_texture, + GLenum slice_gl_target) +{ + CoglPipelineLayerState change = COGL_PIPELINE_LAYER_STATE_TEXTURE; + CoglPipelineLayer *layer; + CoglPipelineLayer *authority; + CoglPipelineLayer *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 + * pipeline. If the layer is created then it will be owned by + * pipeline. */ + layer = _cogl_pipeline_get_layer (pipeline, layer_index); + + /* Now find the ancestor of the layer that is the authority for the + * state we want to change */ + authority = _cogl_pipeline_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_pipeline_layer_pre_change_notify (pipeline, 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_pipeline_layer_get_parent (authority) != NULL) + { + CoglPipelineLayer *parent = + _cogl_pipeline_layer_get_parent (authority); + CoglPipelineLayer *old_authority = + _cogl_pipeline_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 == pipeline); + if (layer->differences == 0) + _cogl_pipeline_prune_empty_layer_difference (pipeline, + 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_pipeline_layer_prune_redundant_ancestry (layer); + } + +changed: + + handle_automatic_blend_enable (pipeline, COGL_PIPELINE_STATE_LAYERS); +} + +static void +_cogl_pipeline_set_layer_gl_texture_slice (CoglPipeline *pipeline, + int layer_index, + CoglHandle texture, + GLuint slice_gl_texture, + GLenum slice_gl_target) +{ + g_return_if_fail (cogl_is_pipeline (pipeline)); + /* GL texture overrides can only be set in association with a parent + * CoglTexture */ + g_return_if_fail (cogl_is_texture (texture)); + + _cogl_pipeline_set_layer_texture (pipeline, + layer_index, + texture, + TRUE, /* slice override */ + slice_gl_texture, + slice_gl_target); +} + +void +cogl_pipeline_set_layer_texture (CoglPipeline *pipeline, + int layer_index, + CoglHandle texture) +{ + g_return_if_fail (cogl_is_pipeline (pipeline)); + g_return_if_fail (texture == COGL_INVALID_HANDLE || + cogl_is_texture (texture)); + + _cogl_pipeline_set_layer_texture (pipeline, + layer_index, + texture, + FALSE, /* slice override */ + 0, /* slice_gl_texture */ + 0); /* slice_gl_target */ +} + +typedef struct +{ + int i; + CoglPipeline *pipeline; + unsigned long fallback_layers; +} CoglPipelineFallbackState; + +static gboolean +fallback_layer_cb (CoglPipelineLayer *layer, void *user_data) +{ + CoglPipelineFallbackState *state = user_data; + CoglPipeline *pipeline = state->pipeline; + CoglHandle texture = _cogl_pipeline_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<<state->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 pipeline layer, since it was " + "using an unsupported texture target "); + /* might get away with this... */ + texture = ctx->default_gl_texture_2d_tex; + } + + cogl_pipeline_set_layer_texture (pipeline, layer->index, texture); + + state->i++; + + return TRUE; +} + +void +_cogl_pipeline_set_layer_wrap_modes (CoglPipeline *pipeline, + CoglPipelineLayer *layer, + CoglPipelineLayer *authority, + CoglPipelineWrapModeInternal wrap_mode_s, + CoglPipelineWrapModeInternal wrap_mode_t, + CoglPipelineWrapModeInternal wrap_mode_p) +{ + CoglPipelineLayer *new; + CoglPipelineLayerState change = COGL_PIPELINE_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_pipeline_layer_pre_change_notify (pipeline, 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_pipeline_layer_get_parent (authority) != NULL) + { + CoglPipelineLayer *parent = + _cogl_pipeline_layer_get_parent (authority); + CoglPipelineLayer *old_authority = + _cogl_pipeline_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 == pipeline); + if (layer->differences == 0) + _cogl_pipeline_prune_empty_layer_difference (pipeline, + 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_pipeline_layer_prune_redundant_ancestry (layer); + } +} + +static CoglPipelineWrapModeInternal +public_to_internal_wrap_mode (CoglPipelineWrapMode mode) +{ + return (CoglPipelineWrapModeInternal)mode; +} + +static CoglPipelineWrapMode +internal_to_public_wrap_mode (CoglPipelineWrapModeInternal internal_mode) +{ + g_return_val_if_fail (internal_mode != + COGL_PIPELINE_WRAP_MODE_INTERNAL_CLAMP_TO_BORDER, + COGL_PIPELINE_WRAP_MODE_AUTOMATIC); + return (CoglPipelineWrapMode)internal_mode; +} + +void +cogl_pipeline_set_layer_wrap_mode_s (CoglPipeline *pipeline, + int layer_index, + CoglPipelineWrapMode mode) +{ + CoglPipelineLayerState change = COGL_PIPELINE_LAYER_STATE_WRAP_MODES; + CoglPipelineLayer *layer; + CoglPipelineLayer *authority; + CoglPipelineWrapModeInternal internal_mode = + public_to_internal_wrap_mode (mode); + + g_return_if_fail (cogl_is_pipeline (pipeline)); + + /* 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 + * pipeline. If the layer is created then it will be owned by + * pipeline. */ + layer = _cogl_pipeline_get_layer (pipeline, layer_index); + + /* Now find the ancestor of the layer that is the authority for the + * state we want to change */ + authority = _cogl_pipeline_layer_get_authority (layer, change); + + _cogl_pipeline_set_layer_wrap_modes (pipeline, layer, authority, + internal_mode, + authority->wrap_mode_t, + authority->wrap_mode_p); +} + +void +cogl_pipeline_set_layer_wrap_mode_t (CoglPipeline *pipeline, + int layer_index, + CoglPipelineWrapMode mode) +{ + CoglPipelineLayerState change = COGL_PIPELINE_LAYER_STATE_WRAP_MODES; + CoglPipelineLayer *layer; + CoglPipelineLayer *authority; + CoglPipelineWrapModeInternal internal_mode = + public_to_internal_wrap_mode (mode); + + g_return_if_fail (cogl_is_pipeline (pipeline)); + + /* 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 + * pipeline. If the layer is created then it will be owned by + * pipeline. */ + layer = _cogl_pipeline_get_layer (pipeline, layer_index); + + /* Now find the ancestor of the layer that is the authority for the + * state we want to change */ + authority = _cogl_pipeline_layer_get_authority (layer, change); + + _cogl_pipeline_set_layer_wrap_modes (pipeline, 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_pipeline_set_layer_wrap_mode_p (CoglPipeline *pipeline, + int layer_index, + CoglPipelineWrapMode mode) +{ + CoglPipelineLayerState change = COGL_PIPELINE_LAYER_STATE_WRAP_MODES; + CoglPipelineLayer *layer; + CoglPipelineLayer *authority; + CoglPipelineWrapModeInternal internal_mode = + public_to_internal_wrap_mode (mode); + + g_return_if_fail (cogl_is_pipeline (pipeline)); + + /* 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 + * pipeline. If the layer is created then it will be owned by + * pipeline. */ + layer = _cogl_pipeline_get_layer (pipeline, layer_index); + + /* Now find the ancestor of the layer that is the authority for the + * state we want to change */ + authority = _cogl_pipeline_layer_get_authority (layer, change); + + _cogl_pipeline_set_layer_wrap_modes (pipeline, layer, authority, + authority->wrap_mode_s, + authority->wrap_mode_t, + internal_mode); +} + +void +cogl_pipeline_set_layer_wrap_mode (CoglPipeline *pipeline, + int layer_index, + CoglPipelineWrapMode mode) +{ + CoglPipelineLayerState change = COGL_PIPELINE_LAYER_STATE_WRAP_MODES; + CoglPipelineLayer *layer; + CoglPipelineLayer *authority; + CoglPipelineWrapModeInternal internal_mode = + public_to_internal_wrap_mode (mode); + + g_return_if_fail (cogl_is_pipeline (pipeline)); + + /* 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 + * pipeline. If the layer is created then it will be owned by + * pipeline. */ + layer = _cogl_pipeline_get_layer (pipeline, layer_index); + + /* Now find the ancestor of the layer that is the authority for the + * state we want to change */ + authority = _cogl_pipeline_layer_get_authority (layer, change); + + _cogl_pipeline_set_layer_wrap_modes (pipeline, 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 */ +CoglPipelineWrapMode +_cogl_pipeline_layer_get_wrap_mode_s (CoglPipelineLayer *layer) +{ + CoglPipelineLayerState change = COGL_PIPELINE_LAYER_STATE_WRAP_MODES; + CoglPipelineLayer *authority; + + g_return_val_if_fail (_cogl_is_pipeline_layer (layer), FALSE); + + /* Now find the ancestor of the layer that is the authority for the + * state we want to change */ + authority = _cogl_pipeline_layer_get_authority (layer, change); + + return internal_to_public_wrap_mode (authority->wrap_mode_s); +} + +CoglPipelineWrapMode +cogl_pipeline_get_layer_wrap_mode_s (CoglPipeline *pipeline, int layer_index) +{ + CoglPipelineLayer *layer; + + g_return_val_if_fail (cogl_is_pipeline (pipeline), 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 + * pipeline. If the layer is created then it will be owned by + * pipeline. */ + layer = _cogl_pipeline_get_layer (pipeline, layer_index); + /* FIXME: we shouldn't ever construct a layer in a getter function */ + + return _cogl_pipeline_layer_get_wrap_mode_s (layer); +} + +/* FIXME: deprecate this API */ +CoglPipelineWrapMode +_cogl_pipeline_layer_get_wrap_mode_t (CoglPipelineLayer *layer) +{ + CoglPipelineLayerState change = COGL_PIPELINE_LAYER_STATE_WRAP_MODES; + CoglPipelineLayer *authority; + + g_return_val_if_fail (_cogl_is_pipeline_layer (layer), FALSE); + + /* Now find the ancestor of the layer that is the authority for the + * state we want to change */ + authority = _cogl_pipeline_layer_get_authority (layer, change); + + return internal_to_public_wrap_mode (authority->wrap_mode_t); +} + +CoglPipelineWrapMode +cogl_pipeline_get_layer_wrap_mode_t (CoglPipeline *pipeline, int layer_index) +{ + CoglPipelineLayer *layer; + + g_return_val_if_fail (cogl_is_pipeline (pipeline), 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 + * pipeline. If the layer is created then it will be owned by + * pipeline. */ + layer = _cogl_pipeline_get_layer (pipeline, layer_index); + /* FIXME: we shouldn't ever construct a layer in a getter function */ + + return _cogl_pipeline_layer_get_wrap_mode_t (layer); +} + +CoglPipelineWrapMode +_cogl_pipeline_layer_get_wrap_mode_p (CoglPipelineLayer *layer) +{ + CoglPipelineLayerState change = COGL_PIPELINE_LAYER_STATE_WRAP_MODES; + CoglPipelineLayer *authority = + _cogl_pipeline_layer_get_authority (layer, change); + + return internal_to_public_wrap_mode (authority->wrap_mode_p); +} + +CoglPipelineWrapMode +cogl_pipeline_get_layer_wrap_mode_p (CoglPipeline *pipeline, int layer_index) +{ + CoglPipelineLayer *layer; + + g_return_val_if_fail (cogl_is_pipeline (pipeline), 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 + * pipeline. If the layer is created then it will be owned by + * pipeline. */ + layer = _cogl_pipeline_get_layer (pipeline, layer_index); + + return _cogl_pipeline_layer_get_wrap_mode_p (layer); +} + +void +_cogl_pipeline_layer_get_wrap_modes (CoglPipelineLayer *layer, + CoglPipelineWrapModeInternal *wrap_mode_s, + CoglPipelineWrapModeInternal *wrap_mode_t, + CoglPipelineWrapModeInternal *wrap_mode_p) +{ + CoglPipelineLayer *authority = + _cogl_pipeline_layer_get_authority (layer, + COGL_PIPELINE_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_pipeline_set_layer_point_sprite_coords_enabled (CoglPipeline *pipeline, + int layer_index, + gboolean enable, + GError **error) +{ + CoglPipelineLayerState change = + COGL_PIPELINE_LAYER_STATE_POINT_SPRITE_COORDS; + CoglPipelineLayer *layer; + CoglPipelineLayer *new; + CoglPipelineLayer *authority; + + g_return_val_if_fail (cogl_is_pipeline (pipeline), 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 + * pipeline. If the layer is created then it will be owned by + * pipeline. */ + layer = _cogl_pipeline_get_layer (pipeline, layer_index); + + /* Now find the ancestor of the layer that is the authority for the + * state we want to change */ + authority = _cogl_pipeline_layer_get_authority (layer, change); + + if (authority->big_state->point_sprite_coords == enable) + return TRUE; + + new = _cogl_pipeline_layer_pre_change_notify (pipeline, 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_pipeline_layer_get_parent (authority) != NULL) + { + CoglPipelineLayer *parent = + _cogl_pipeline_layer_get_parent (authority); + CoglPipelineLayer *old_authority = + _cogl_pipeline_layer_get_authority (parent, change); + + if (old_authority->big_state->point_sprite_coords == enable) + { + layer->differences &= ~change; + + g_assert (layer->owner == pipeline); + if (layer->differences == 0) + _cogl_pipeline_prune_empty_layer_difference (pipeline, + 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_pipeline_layer_prune_redundant_ancestry (layer); + } + + return TRUE; +} + +gboolean +cogl_pipeline_get_layer_point_sprite_coords_enabled (CoglPipeline *pipeline, + int layer_index) +{ + CoglPipelineLayerState change = + COGL_PIPELINE_LAYER_STATE_POINT_SPRITE_COORDS; + CoglPipelineLayer *layer; + CoglPipelineLayer *authority; + + g_return_val_if_fail (cogl_is_pipeline (pipeline), 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 + * pipeline. If the layer is created then it will be owned by + * pipeline. */ + layer = _cogl_pipeline_get_layer (pipeline, layer_index); + /* FIXME: we shouldn't ever construct a layer in a getter function */ + + authority = _cogl_pipeline_layer_get_authority (layer, change); + + return authority->big_state->point_sprite_coords; +} + +typedef struct +{ + CoglPipeline *pipeline; + CoglPipelineWrapModeOverrides *wrap_mode_overrides; + int i; +} CoglPipelineWrapModeOverridesState; + +static gboolean +apply_wrap_mode_overrides_cb (CoglPipelineLayer *layer, + void *user_data) +{ + CoglPipelineWrapModeOverridesState *state = user_data; + CoglPipelineLayer *authority = + _cogl_pipeline_layer_get_authority (layer, + COGL_PIPELINE_LAYER_STATE_WRAP_MODES); + CoglPipelineWrapModeInternal wrap_mode_s; + CoglPipelineWrapModeInternal wrap_mode_t; + CoglPipelineWrapModeInternal 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_PIPELINE_WRAP_MODE_OVERRIDE_NONE) + wrap_mode_s = (CoglPipelineWrapModeInternal)authority->wrap_mode_s; + wrap_mode_t = state->wrap_mode_overrides->values[state->i].t; + if (wrap_mode_t == COGL_PIPELINE_WRAP_MODE_OVERRIDE_NONE) + wrap_mode_t = (CoglPipelineWrapModeInternal)authority->wrap_mode_t; + wrap_mode_p = state->wrap_mode_overrides->values[state->i].p; + if (wrap_mode_p == COGL_PIPELINE_WRAP_MODE_OVERRIDE_NONE) + wrap_mode_p = (CoglPipelineWrapModeInternal)authority->wrap_mode_p; + + _cogl_pipeline_set_layer_wrap_modes (state->pipeline, + layer, + authority, + wrap_mode_s, + wrap_mode_t, + wrap_mode_p); + + state->i++; + + return TRUE; +} + +typedef struct +{ + CoglPipeline *pipeline; + GLuint gl_texture; +} CoglPipelineOverrideLayerState; + +static gboolean +override_layer_texture_cb (CoglPipelineLayer *layer, void *user_data) +{ + CoglPipelineOverrideLayerState *state = user_data; + CoglHandle texture; + GLenum gl_target; + + texture = _cogl_pipeline_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_pipeline_set_layer_gl_texture_slice (state->pipeline, + layer->index, + texture, + state->gl_texture, + gl_target); + return TRUE; +} + +void +_cogl_pipeline_apply_overrides (CoglPipeline *pipeline, + CoglPipelineFlushOptions *options) +{ + COGL_STATIC_COUNTER (apply_overrides_counter, + "pipeline overrides counter", + "Increments each time we have to apply " + "override options to a pipeline", + 0 /* no application private data */); + + COGL_COUNTER_INC (_cogl_uprof_context, apply_overrides_counter); + + if (options->flags & COGL_PIPELINE_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<<i); i++) + ; + + _cogl_pipeline_prune_to_n_layers (pipeline, i); + } + + if (options->flags & COGL_PIPELINE_FLUSH_FALLBACK_MASK) + { + CoglPipelineFallbackState state; + + state.i = 0; + state.pipeline = pipeline; + state.fallback_layers = options->fallback_layers; + + _cogl_pipeline_foreach_layer_internal (pipeline, + fallback_layer_cb, + &state); + } + + if (options->flags & COGL_PIPELINE_FLUSH_LAYER0_OVERRIDE) + { + CoglPipelineOverrideLayerState state; + + _cogl_pipeline_prune_to_n_layers (pipeline, 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_pipeline_foreach_layer_internal() here even though we know + * there's only one layer. */ + state.pipeline = pipeline; + state.gl_texture = options->layer0_override_texture; + _cogl_pipeline_foreach_layer_internal (pipeline, + override_layer_texture_cb, + &state); + } + + if (options->flags & COGL_PIPELINE_FLUSH_WRAP_MODE_OVERRIDES) + { + CoglPipelineWrapModeOverridesState state; + + state.pipeline = pipeline; + state.wrap_mode_overrides = &options->wrap_mode_overrides; + state.i = 0; + _cogl_pipeline_foreach_layer_internal (pipeline, + apply_wrap_mode_overrides_cb, + &state); + } +} + +static gboolean +_cogl_pipeline_layer_texture_equal (CoglPipelineLayer *authority0, + CoglPipelineLayer *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 pipelines could both be cast to a common Tree + * type of some kind then we could have a unified + * compare_differences() function. + */ +unsigned long +_cogl_pipeline_layer_compare_differences (CoglPipelineLayer *layer0, + CoglPipelineLayer *layer1) +{ + CoglPipelineLayer *node0; + CoglPipelineLayer *node1; + int len0; + int len1; + int len0_index; + int len1_index; + int count; + int i; + CoglPipelineLayer *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->pipeline0_nodes, and ctx->pipeline1_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 + * pipeline_differences mask. + */ + + g_array_set_size (ctx->pipeline0_nodes, 0); + g_array_set_size (ctx->pipeline1_nodes, 0); + for (node0 = layer0; node0; node0 = _cogl_pipeline_layer_get_parent (node0)) + g_array_append_vals (ctx->pipeline0_nodes, &node0, 1); + for (node1 = layer1; node1; node1 = _cogl_pipeline_layer_get_parent (node1)) + g_array_append_vals (ctx->pipeline1_nodes, &node1, 1); + + len0 = ctx->pipeline0_nodes->len; + len1 = ctx->pipeline1_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->pipeline0_nodes, + CoglPipelineLayer *, len0_index--); + node1 = g_array_index (ctx->pipeline1_nodes, + CoglPipelineLayer *, len1_index--); + if (node0 != node1) + { + common_ancestor = _cogl_pipeline_layer_get_parent (node0); + break; + } + } + + /* If we didn't already find the first the common_ancestor ancestor + * that's because one pipeline 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->pipeline0_nodes, CoglPipelineLayer *, i); + if (node0 == common_ancestor) + break; + layers_difference |= node0->differences; + } + + count = len1 - 1; + for (i = 0; i < count; i++) + { + node1 = g_array_index (ctx->pipeline1_nodes, CoglPipelineLayer *, i); + if (node1 == common_ancestor) + break; + layers_difference |= node1->differences; + } + + return layers_difference; +} + +static gboolean +_cogl_pipeline_layer_combine_state_equal (CoglPipelineLayer *authority0, + CoglPipelineLayer *authority1) +{ + CoglPipelineLayerBigState *big_state0 = authority0->big_state; + CoglPipelineLayerBigState *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_pipeline_layer_combine_constant_equal (CoglPipelineLayer *authority0, + CoglPipelineLayer *authority1) +{ + return memcmp (authority0->big_state->texture_combine_constant, + authority1->big_state->texture_combine_constant, + sizeof (float) * 4) == 0 ? TRUE : FALSE; +} + +static gboolean +_cogl_pipeline_layer_filters_equal (CoglPipelineLayer *authority0, + CoglPipelineLayer *authority1) +{ + if (authority0->mag_filter != authority1->mag_filter) + return FALSE; + if (authority0->min_filter != authority1->min_filter) + return FALSE; + + return TRUE; +} + +static gboolean +_cogl_pipeline_layer_wrap_modes_equal (CoglPipelineLayer *authority0, + CoglPipelineLayer *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_pipeline_layer_user_matrix_equal (CoglPipelineLayer *authority0, + CoglPipelineLayer *authority1) +{ + CoglPipelineLayerBigState *big_state0 = authority0->big_state; + CoglPipelineLayerBigState *big_state1 = authority1->big_state; + + if (!cogl_matrix_equal (&big_state0->matrix, &big_state1->matrix)) + return FALSE; + + return TRUE; +} + +static gboolean +_cogl_pipeline_layer_point_sprite_coords_equal (CoglPipelineLayer *authority0, + CoglPipelineLayer *authority1) +{ + CoglPipelineLayerBigState *big_state0 = authority0->big_state; + CoglPipelineLayerBigState *big_state1 = authority1->big_state; + + return big_state0->point_sprite_coords == big_state1->point_sprite_coords; +} + +typedef gboolean +(*CoglPipelineLayerStateComparitor) (CoglPipelineLayer *authority0, + CoglPipelineLayer *authority1); + +static gboolean +layer_state_equal (CoglPipelineLayerState state, + CoglPipelineLayer *layer0, + CoglPipelineLayer *layer1, + CoglPipelineLayerStateComparitor comparitor) +{ + CoglPipelineLayer *authority0 = + _cogl_pipeline_layer_get_authority (layer0, state); + CoglPipelineLayer *authority1 = + _cogl_pipeline_layer_get_authority (layer1, state); + + return comparitor (authority0, authority1); +} + +static gboolean +_cogl_pipeline_layer_equal (CoglPipelineLayer *layer0, + CoglPipelineLayer *layer1) +{ + unsigned long layers_difference; + + if (layer0 == layer1) + return TRUE; + + layers_difference = + _cogl_pipeline_layer_compare_differences (layer0, layer1); + + if (layers_difference & COGL_PIPELINE_LAYER_STATE_TEXTURE && + !layer_state_equal (COGL_PIPELINE_LAYER_STATE_TEXTURE, + layer0, layer1, + _cogl_pipeline_layer_texture_equal)) + return FALSE; + + if (layers_difference & COGL_PIPELINE_LAYER_STATE_COMBINE && + !layer_state_equal (COGL_PIPELINE_LAYER_STATE_COMBINE, + layer0, layer1, + _cogl_pipeline_layer_combine_state_equal)) + return FALSE; + + if (layers_difference & COGL_PIPELINE_LAYER_STATE_COMBINE_CONSTANT && + !layer_state_equal (COGL_PIPELINE_LAYER_STATE_COMBINE_CONSTANT, + layer0, layer1, + _cogl_pipeline_layer_combine_constant_equal)) + return FALSE; + + if (layers_difference & COGL_PIPELINE_LAYER_STATE_FILTERS && + !layer_state_equal (COGL_PIPELINE_LAYER_STATE_FILTERS, + layer0, layer1, + _cogl_pipeline_layer_filters_equal)) + return FALSE; + + if (layers_difference & COGL_PIPELINE_LAYER_STATE_WRAP_MODES && + !layer_state_equal (COGL_PIPELINE_LAYER_STATE_WRAP_MODES, + layer0, layer1, + _cogl_pipeline_layer_wrap_modes_equal)) + return FALSE; + + if (layers_difference & COGL_PIPELINE_LAYER_STATE_USER_MATRIX && + !layer_state_equal (COGL_PIPELINE_LAYER_STATE_USER_MATRIX, + layer0, layer1, + _cogl_pipeline_layer_user_matrix_equal)) + return FALSE; + + if (layers_difference & COGL_PIPELINE_LAYER_STATE_POINT_SPRITE_COORDS && + !layer_state_equal (COGL_PIPELINE_LAYER_STATE_POINT_SPRITE_COORDS, + layer0, layer1, + _cogl_pipeline_layer_point_sprite_coords_equal)) + return FALSE; + + return TRUE; +} + +static gboolean +_cogl_pipeline_color_equal (CoglPipeline *authority0, + CoglPipeline *authority1) +{ + return cogl_color_equal (&authority0->color, &authority1->color); +} + +static gboolean +_cogl_pipeline_lighting_state_equal (CoglPipeline *authority0, + CoglPipeline *authority1) +{ + CoglPipelineLightingState *state0 = &authority0->big_state->lighting_state; + CoglPipelineLightingState *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_pipeline_alpha_state_equal (CoglPipeline *authority0, + CoglPipeline *authority1) +{ + CoglPipelineAlphaFuncState *alpha_state0 = + &authority0->big_state->alpha_state; + CoglPipelineAlphaFuncState *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_pipeline_blend_state_equal (CoglPipeline *authority0, + CoglPipeline *authority1) +{ + CoglPipelineBlendState *blend_state0 = &authority0->big_state->blend_state; + CoglPipelineBlendState *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_pipeline_depth_state_equal (CoglPipeline *authority0, + CoglPipeline *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 (CoglPipelineDepthState)) == 0; +} + +static gboolean +_cogl_pipeline_fog_state_equal (CoglPipeline *authority0, + CoglPipeline *authority1) +{ + CoglPipelineFogState *fog_state0 = &authority0->big_state->fog_state; + CoglPipelineFogState *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_pipeline_point_size_equal (CoglPipeline *authority0, + CoglPipeline *authority1) +{ + return authority0->big_state->point_size == authority1->big_state->point_size; +} + +static gboolean +_cogl_pipeline_user_shader_equal (CoglPipeline *authority0, + CoglPipeline *authority1) +{ + return (authority0->big_state->user_program == + authority1->big_state->user_program); +} + +static gboolean +_cogl_pipeline_layers_equal (CoglPipeline *authority0, + CoglPipeline *authority1) +{ + int i; + + if (authority0->n_layers != authority1->n_layers) + return FALSE; + + _cogl_pipeline_update_layers_cache (authority0); + _cogl_pipeline_update_layers_cache (authority1); + + for (i = 0; i < authority0->n_layers; i++) + { + if (!_cogl_pipeline_layer_equal (authority0->layers_cache[i], + authority1->layers_cache[i])) + return FALSE; + } + return TRUE; +} + +/* Determine the mask of differences between two pipelines */ +unsigned long +_cogl_pipeline_compare_differences (CoglPipeline *pipeline0, + CoglPipeline *pipeline1) +{ + CoglPipeline *node0; + CoglPipeline *node1; + int len0; + int len1; + int len0_index; + int len1_index; + int count; + int i; + CoglPipeline *common_ancestor = NULL; + unsigned long pipelines_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->pipeline0_nodes, and ctx->pipeline1_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 + * pipeline_differences mask. + */ + + g_array_set_size (ctx->pipeline0_nodes, 0); + g_array_set_size (ctx->pipeline1_nodes, 0); + for (node0 = pipeline0; node0; node0 = _cogl_pipeline_get_parent (node0)) + g_array_append_vals (ctx->pipeline0_nodes, &node0, 1); + for (node1 = pipeline1; node1; node1 = _cogl_pipeline_get_parent (node1)) + g_array_append_vals (ctx->pipeline1_nodes, &node1, 1); + + len0 = ctx->pipeline0_nodes->len; + len1 = ctx->pipeline1_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->pipeline0_nodes, + CoglPipeline *, len0_index--); + node1 = g_array_index (ctx->pipeline1_nodes, + CoglPipeline *, len1_index--); + if (node0 != node1) + { + common_ancestor = _cogl_pipeline_get_parent (node0); + break; + } + } + + /* If we didn't already find the first the common_ancestor ancestor + * that's because one pipeline 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->pipeline0_nodes, CoglPipeline *, i); + if (node0 == common_ancestor) + break; + pipelines_difference |= node0->differences; + } + + count = len1 - 1; + for (i = 0; i < count; i++) + { + node1 = g_array_index (ctx->pipeline1_nodes, CoglPipeline *, i); + if (node1 == common_ancestor) + break; + pipelines_difference |= node1->differences; + } + + return pipelines_difference; + +} + +static gboolean +simple_property_equal (CoglPipeline *pipeline0, + CoglPipeline *pipeline1, + unsigned long pipelines_difference, + CoglPipelineState state, + CoglPipelineStateComparitor comparitor) +{ + if (pipelines_difference & state) + { + if (!comparitor (_cogl_pipeline_get_authority (pipeline0, state), + _cogl_pipeline_get_authority (pipeline1, state))) + return FALSE; + } + return TRUE; +} + +/* Comparison of two arbitrary pipelines is done by: + * 1) walking up the parents of each pipeline 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 pipelines 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_pipeline_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 + * pipelines in the journal but it means that _cogl_pipeline_equal + * doesn't strictly compare whether the pipelines 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_pipeline_equal (CoglPipeline *pipeline0, + CoglPipeline *pipeline1, + gboolean skip_gl_color) +{ + unsigned long pipelines_difference; + gboolean ret; + COGL_STATIC_TIMER (pipeline_equal_timer, + "Mainloop", /* parent */ + "_cogl_pipeline_equal", + "The time spent comparing cogl pipelines", + 0 /* no application private data */); + + COGL_TIMER_START (_cogl_uprof_context, pipeline_equal_timer); + + if (pipeline0 == pipeline1) + { + ret = TRUE; + goto done; + } + + ret = FALSE; + + /* First check non-sparse properties */ + + if (pipeline0->real_blend_enable != pipeline1->real_blend_enable) + goto done; + + /* Then check sparse properties */ + + pipelines_difference = + _cogl_pipeline_compare_differences (pipeline0, pipeline1); + + if (pipelines_difference & COGL_PIPELINE_STATE_COLOR && + !skip_gl_color) + { + CoglPipelineState state = COGL_PIPELINE_STATE_COLOR; + CoglPipeline *authority0 = + _cogl_pipeline_get_authority (pipeline0, state); + CoglPipeline *authority1 = + _cogl_pipeline_get_authority (pipeline1, state); + + if (!cogl_color_equal (&authority0->color, &authority1->color)) + goto done; + } + + if (!simple_property_equal (pipeline0, pipeline1, + pipelines_difference, + COGL_PIPELINE_STATE_LIGHTING, + _cogl_pipeline_lighting_state_equal)) + goto done; + + if (!simple_property_equal (pipeline0, pipeline1, + pipelines_difference, + COGL_PIPELINE_STATE_ALPHA_FUNC, + _cogl_pipeline_alpha_state_equal)) + goto done; + + /* We don't need to compare the detailed blending state if we know + * blending is disabled for both pipelines. */ + if (pipeline0->real_blend_enable && + pipelines_difference & COGL_PIPELINE_STATE_BLEND) + { + CoglPipelineState state = COGL_PIPELINE_STATE_BLEND; + CoglPipeline *authority0 = + _cogl_pipeline_get_authority (pipeline0, state); + CoglPipeline *authority1 = + _cogl_pipeline_get_authority (pipeline1, state); + + if (!_cogl_pipeline_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 (pipeline0, pipeline1, + pipelines_difference, + COGL_PIPELINE_STATE_BLEND, + _cogl_pipeline_blend_enable_equal)) + return FALSE; +#endif + + if (!simple_property_equal (pipeline0, pipeline1, + pipelines_difference, + COGL_PIPELINE_STATE_DEPTH, + _cogl_pipeline_depth_state_equal)) + goto done; + + if (!simple_property_equal (pipeline0, pipeline1, + pipelines_difference, + COGL_PIPELINE_STATE_FOG, + _cogl_pipeline_fog_state_equal)) + goto done; + + if (!simple_property_equal (pipeline0, pipeline1, + pipelines_difference, + COGL_PIPELINE_STATE_POINT_SIZE, + _cogl_pipeline_point_size_equal)) + goto done; + + if (!simple_property_equal (pipeline0, pipeline1, + pipelines_difference, + COGL_PIPELINE_STATE_USER_SHADER, + _cogl_pipeline_user_shader_equal)) + goto done; + + if (!simple_property_equal (pipeline0, pipeline1, + pipelines_difference, + COGL_PIPELINE_STATE_LAYERS, + _cogl_pipeline_layers_equal)) + goto done; + + ret = TRUE; +done: + COGL_TIMER_STOP (_cogl_uprof_context, pipeline_equal_timer); + return ret; +} + +void +cogl_pipeline_get_color (CoglPipeline *pipeline, + CoglColor *color) +{ + CoglPipeline *authority; + + g_return_if_fail (cogl_is_pipeline (pipeline)); + + authority = + _cogl_pipeline_get_authority (pipeline, COGL_PIPELINE_STATE_COLOR); + + *color = authority->color; +} + +/* This is used heavily by the cogl journal when logging quads */ +void +_cogl_pipeline_get_colorubv (CoglPipeline *pipeline, + guint8 *color) +{ + CoglPipeline *authority = + _cogl_pipeline_get_authority (pipeline, COGL_PIPELINE_STATE_COLOR); + + _cogl_color_get_rgba_4ubv (&authority->color, color); +} + +static void +_cogl_pipeline_prune_redundant_ancestry (CoglPipeline *pipeline) +{ + CoglPipeline *new_parent = _cogl_pipeline_get_parent (pipeline); + + /* Before considering pruning redundant ancestry we check if this + * pipeline is an authority for layer state and if so only consider + * reparenting if it *owns* all the layers it depends on. NB: A + * pipeline 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 pipeline 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 pipeline depends on any ancestors for layer state then we + * immediatly bail out. + */ + if (pipeline->differences & COGL_PIPELINE_STATE_LAYERS) + { + if (pipeline->n_layers != g_list_length (pipeline->layer_differences)) + return; + } + + /* walk up past ancestors that are now redundant and potentially + * reparent the pipeline. */ + while (_cogl_pipeline_get_parent (new_parent) && + (new_parent->differences | pipeline->differences) == + pipeline->differences) + new_parent = _cogl_pipeline_get_parent (new_parent); + + if (new_parent != _cogl_pipeline_get_parent (pipeline)) + { + gboolean is_weak = _cogl_pipeline_is_weak (pipeline); + _cogl_pipeline_set_parent (pipeline, new_parent, is_weak ? FALSE : TRUE); + } +} + +static void +_cogl_pipeline_update_authority (CoglPipeline *pipeline, + CoglPipeline *authority, + CoglPipelineState state, + CoglPipelineStateComparitor comparitor) +{ + /* If we are the current authority see if we can revert to one of + * our ancestors being the authority */ + if (pipeline == authority && + _cogl_pipeline_get_parent (authority) != NULL) + { + CoglPipeline *parent = _cogl_pipeline_get_parent (authority); + CoglPipeline *old_authority = + _cogl_pipeline_get_authority (parent, state); + + if (comparitor (authority, old_authority)) + pipeline->differences &= ~state; + } + else if (pipeline != 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... */ + pipeline->differences |= state; + _cogl_pipeline_prune_redundant_ancestry (pipeline); + } +} + +void +cogl_pipeline_set_color (CoglPipeline *pipeline, + const CoglColor *color) +{ + CoglPipelineState state = COGL_PIPELINE_STATE_COLOR; + CoglPipeline *authority; + + g_return_if_fail (cogl_is_pipeline (pipeline)); + + authority = _cogl_pipeline_get_authority (pipeline, state); + + if (cogl_color_equal (color, &authority->color)) + return; + + /* - Flush journal primitives referencing the current state. + * - Make sure the pipeline has no dependants so it may be modified. + * - If the pipeline isn't currently an authority for the state being + * changed, then initialize that state from the current authority. + */ + _cogl_pipeline_pre_change_notify (pipeline, state, color, FALSE); + + pipeline->color = *color; + + _cogl_pipeline_update_authority (pipeline, authority, state, + _cogl_pipeline_color_equal); + + handle_automatic_blend_enable (pipeline, state); +} + +void +cogl_pipeline_set_color4ub (CoglPipeline *pipeline, + guint8 red, + guint8 green, + guint8 blue, + guint8 alpha) +{ + CoglColor color; + cogl_color_init_from_4ub (&color, red, green, blue, alpha); + cogl_pipeline_set_color (pipeline, &color); +} + +void +cogl_pipeline_set_color4f (CoglPipeline *pipeline, + float red, + float green, + float blue, + float alpha) +{ + CoglColor color; + cogl_color_init_from_4f (&color, red, green, blue, alpha); + cogl_pipeline_set_color (pipeline, &color); +} + +CoglPipelineBlendEnable +_cogl_pipeline_get_blend_enabled (CoglPipeline *pipeline) +{ + CoglPipeline *authority; + + g_return_val_if_fail (cogl_is_pipeline (pipeline), FALSE); + + authority = + _cogl_pipeline_get_authority (pipeline, COGL_PIPELINE_STATE_BLEND_ENABLE); + return authority->blend_enable; +} + +static gboolean +_cogl_pipeline_blend_enable_equal (CoglPipeline *authority0, + CoglPipeline *authority1) +{ + return authority0->blend_enable == authority1->blend_enable ? TRUE : FALSE; +} + +void +_cogl_pipeline_set_blend_enabled (CoglPipeline *pipeline, + CoglPipelineBlendEnable enable) +{ + CoglPipelineState state = COGL_PIPELINE_STATE_BLEND_ENABLE; + CoglPipeline *authority; + + g_return_if_fail (cogl_is_pipeline (pipeline)); + g_return_if_fail (enable > 1 && + "don't pass TRUE or FALSE to _set_blend_enabled!"); + + authority = _cogl_pipeline_get_authority (pipeline, state); + + if (authority->blend_enable == enable) + return; + + /* - Flush journal primitives referencing the current state. + * - Make sure the pipeline has no dependants so it may be modified. + * - If the pipeline isn't currently an authority for the state being + * changed, then initialize that state from the current authority. + */ + _cogl_pipeline_pre_change_notify (pipeline, state, NULL, FALSE); + + pipeline->blend_enable = enable; + + _cogl_pipeline_update_authority (pipeline, authority, state, + _cogl_pipeline_blend_enable_equal); + + handle_automatic_blend_enable (pipeline, state); +} + +void +cogl_pipeline_get_ambient (CoglPipeline *pipeline, + CoglColor *ambient) +{ + CoglPipeline *authority; + + g_return_if_fail (cogl_is_pipeline (pipeline)); + + authority = + _cogl_pipeline_get_authority (pipeline, COGL_PIPELINE_STATE_LIGHTING); + + cogl_color_init_from_4fv (ambient, + authority->big_state->lighting_state.ambient); +} + +void +cogl_pipeline_set_ambient (CoglPipeline *pipeline, + const CoglColor *ambient) +{ + CoglPipelineState state = COGL_PIPELINE_STATE_LIGHTING; + CoglPipeline *authority; + CoglPipelineLightingState *lighting_state; + + g_return_if_fail (cogl_is_pipeline (pipeline)); + + authority = _cogl_pipeline_get_authority (pipeline, 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 pipeline has no dependants so it may be modified. + * - If the pipeline isn't currently an authority for the state being + * changed, then initialize that state from the current authority. + */ + _cogl_pipeline_pre_change_notify (pipeline, state, NULL, FALSE); + + lighting_state = &pipeline->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_pipeline_update_authority (pipeline, authority, state, + _cogl_pipeline_lighting_state_equal); + + handle_automatic_blend_enable (pipeline, state); +} + +void +cogl_pipeline_get_diffuse (CoglPipeline *pipeline, + CoglColor *diffuse) +{ + CoglPipeline *authority; + + g_return_if_fail (cogl_is_pipeline (pipeline)); + + authority = + _cogl_pipeline_get_authority (pipeline, COGL_PIPELINE_STATE_LIGHTING); + + cogl_color_init_from_4fv (diffuse, + authority->big_state->lighting_state.diffuse); +} + +void +cogl_pipeline_set_diffuse (CoglPipeline *pipeline, + const CoglColor *diffuse) +{ + CoglPipelineState state = COGL_PIPELINE_STATE_LIGHTING; + CoglPipeline *authority; + CoglPipelineLightingState *lighting_state; + + g_return_if_fail (cogl_is_pipeline (pipeline)); + + authority = _cogl_pipeline_get_authority (pipeline, 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 pipeline has no dependants so it may be modified. + * - If the pipeline isn't currently an authority for the state being + * changed, then initialize that state from the current authority. + */ + _cogl_pipeline_pre_change_notify (pipeline, state, NULL, FALSE); + + lighting_state = &pipeline->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_pipeline_update_authority (pipeline, authority, state, + _cogl_pipeline_lighting_state_equal); + + handle_automatic_blend_enable (pipeline, state); +} + +void +cogl_pipeline_set_ambient_and_diffuse (CoglPipeline *pipeline, + const CoglColor *color) +{ + cogl_pipeline_set_ambient (pipeline, color); + cogl_pipeline_set_diffuse (pipeline, color); +} + +void +cogl_pipeline_get_specular (CoglPipeline *pipeline, + CoglColor *specular) +{ + CoglPipeline *authority; + + g_return_if_fail (cogl_is_pipeline (pipeline)); + + authority = + _cogl_pipeline_get_authority (pipeline, COGL_PIPELINE_STATE_LIGHTING); + + cogl_color_init_from_4fv (specular, + authority->big_state->lighting_state.specular); +} + +void +cogl_pipeline_set_specular (CoglPipeline *pipeline, const CoglColor *specular) +{ + CoglPipeline *authority; + CoglPipelineState state = COGL_PIPELINE_STATE_LIGHTING; + CoglPipelineLightingState *lighting_state; + + g_return_if_fail (cogl_is_pipeline (pipeline)); + + authority = _cogl_pipeline_get_authority (pipeline, 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 pipeline has no dependants so it may be modified. + * - If the pipeline isn't currently an authority for the state being + * changed, then initialize that state from the current authority. + */ + _cogl_pipeline_pre_change_notify (pipeline, state, NULL, FALSE); + + lighting_state = &pipeline->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_pipeline_update_authority (pipeline, authority, state, + _cogl_pipeline_lighting_state_equal); + + handle_automatic_blend_enable (pipeline, state); +} + +float +cogl_pipeline_get_shininess (CoglPipeline *pipeline) +{ + CoglPipeline *authority; + + g_return_val_if_fail (cogl_is_pipeline (pipeline), 0); + + authority = + _cogl_pipeline_get_authority (pipeline, COGL_PIPELINE_STATE_LIGHTING); + + return authority->big_state->lighting_state.shininess; +} + +void +cogl_pipeline_set_shininess (CoglPipeline *pipeline, + float shininess) +{ + CoglPipeline *authority; + CoglPipelineState state = COGL_PIPELINE_STATE_LIGHTING; + CoglPipelineLightingState *lighting_state; + + g_return_if_fail (cogl_is_pipeline (pipeline)); + + if (shininess < 0.0) + { + g_warning ("Out of range shininess %f supplied for pipeline\n", + shininess); + return; + } + + authority = _cogl_pipeline_get_authority (pipeline, state); + + lighting_state = &authority->big_state->lighting_state; + + if (lighting_state->shininess == shininess) + return; + + /* - Flush journal primitives referencing the current state. + * - Make sure the pipeline has no dependants so it may be modified. + * - If the pipeline isn't currently an authority for the state being + * changed, then initialize that state from the current authority. + */ + _cogl_pipeline_pre_change_notify (pipeline, state, NULL, FALSE); + + lighting_state = &pipeline->big_state->lighting_state; + lighting_state->shininess = shininess; + + _cogl_pipeline_update_authority (pipeline, authority, state, + _cogl_pipeline_lighting_state_equal); +} + +void +cogl_pipeline_get_emission (CoglPipeline *pipeline, + CoglColor *emission) +{ + CoglPipeline *authority; + + g_return_if_fail (cogl_is_pipeline (pipeline)); + + authority = + _cogl_pipeline_get_authority (pipeline, COGL_PIPELINE_STATE_LIGHTING); + + cogl_color_init_from_4fv (emission, + authority->big_state->lighting_state.emission); +} + +void +cogl_pipeline_set_emission (CoglPipeline *pipeline, const CoglColor *emission) +{ + CoglPipeline *authority; + CoglPipelineState state = COGL_PIPELINE_STATE_LIGHTING; + CoglPipelineLightingState *lighting_state; + + g_return_if_fail (cogl_is_pipeline (pipeline)); + + authority = _cogl_pipeline_get_authority (pipeline, 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 pipeline has no dependants so it may be modified. + * - If the pipeline isn't currently an authority for the state being + * changed, then initialize that state from the current authority. + */ + _cogl_pipeline_pre_change_notify (pipeline, state, NULL, FALSE); + + lighting_state = &pipeline->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_pipeline_update_authority (pipeline, authority, state, + _cogl_pipeline_lighting_state_equal); + + handle_automatic_blend_enable (pipeline, state); +} + +void +cogl_pipeline_set_alpha_test_function (CoglPipeline *pipeline, + CoglPipelineAlphaFunc alpha_func, + float alpha_reference) +{ + CoglPipelineState state = COGL_PIPELINE_STATE_ALPHA_FUNC; + CoglPipeline *authority; + CoglPipelineAlphaFuncState *alpha_state; + + g_return_if_fail (cogl_is_pipeline (pipeline)); + + authority = _cogl_pipeline_get_authority (pipeline, 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 pipeline has no dependants so it may be modified. + * - If the pipeline isn't currently an authority for the state being + * changed, then initialize that state from the current authority. + */ + _cogl_pipeline_pre_change_notify (pipeline, state, NULL, FALSE); + + alpha_state = &pipeline->big_state->alpha_state; + alpha_state->alpha_func = alpha_func; + alpha_state->alpha_func_reference = alpha_reference; + + _cogl_pipeline_update_authority (pipeline, authority, state, + _cogl_pipeline_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_pipeline_set_blend (CoglPipeline *pipeline, + const char *blend_description, + GError **error) +{ + CoglPipelineState state = COGL_PIPELINE_STATE_BLEND; + CoglPipeline *authority; + CoglBlendStringStatement statements[2]; + CoglBlendStringStatement *rgb; + CoglBlendStringStatement *a; + GError *internal_error = NULL; + int count; + CoglPipelineBlendState *blend_state; + + g_return_val_if_fail (cogl_is_pipeline (pipeline), 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_pipeline_get_authority (pipeline, state); + + /* - Flush journal primitives referencing the current state. + * - Make sure the pipeline has no dependants so it may be modified. + * - If the pipeline isn't currently an authority for the state being + * changed, then initialize that state from the current authority. + */ + _cogl_pipeline_pre_change_notify (pipeline, state, NULL, FALSE); + + blend_state = &pipeline->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 (pipeline == authority && + _cogl_pipeline_get_parent (authority) != NULL) + { + CoglPipeline *parent = _cogl_pipeline_get_parent (authority); + CoglPipeline *old_authority = + _cogl_pipeline_get_authority (parent, state); + + if (_cogl_pipeline_blend_state_equal (authority, old_authority)) + pipeline->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 (pipeline != authority) + { + pipeline->differences |= state; + _cogl_pipeline_prune_redundant_ancestry (pipeline); + } + + handle_automatic_blend_enable (pipeline, state); + + return TRUE; +} + +void +cogl_pipeline_set_blend_constant (CoglPipeline *pipeline, + const CoglColor *constant_color) +{ +#ifndef HAVE_COGL_GLES + CoglPipelineState state = COGL_PIPELINE_STATE_BLEND; + CoglPipeline *authority; + CoglPipelineBlendState *blend_state; + + g_return_if_fail (cogl_is_pipeline (pipeline)); + + authority = _cogl_pipeline_get_authority (pipeline, 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 pipeline has no dependants so it may be modified. + * - If the pipeline isn't currently an authority for the state being + * changed, then initialize that state from the current authority. + */ + _cogl_pipeline_pre_change_notify (pipeline, state, NULL, FALSE); + + blend_state = &pipeline->big_state->blend_state; + blend_state->blend_constant = *constant_color; + + _cogl_pipeline_update_authority (pipeline, authority, state, + _cogl_pipeline_blend_state_equal); + + handle_automatic_blend_enable (pipeline, state); +#endif +} + +CoglHandle +cogl_pipeline_get_user_program (CoglPipeline *pipeline) +{ + CoglPipeline *authority; + + g_return_val_if_fail (cogl_is_pipeline (pipeline), COGL_INVALID_HANDLE); + + authority = + _cogl_pipeline_get_authority (pipeline, COGL_PIPELINE_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_pipeline_set_user_program (CoglPipeline *pipeline, + CoglHandle program) +{ + CoglPipelineState state = COGL_PIPELINE_STATE_USER_SHADER; + CoglPipeline *authority; + + g_return_if_fail (cogl_is_pipeline (pipeline)); + + authority = _cogl_pipeline_get_authority (pipeline, state); + + if (authority->big_state->user_program == program) + return; + + /* - Flush journal primitives referencing the current state. + * - Make sure the pipeline has no dependants so it may be modified. + * - If the pipeline isn't currently an authority for the state being + * changed, then initialize that state from the current authority. + */ + _cogl_pipeline_pre_change_notify (pipeline, state, NULL, FALSE); + + if (program != COGL_INVALID_HANDLE) + _cogl_pipeline_set_backend (pipeline, COGL_PIPELINE_BACKEND_DEFAULT); + + /* If we are the current authority see if we can revert to one of our + * ancestors being the authority */ + if (pipeline == authority && + _cogl_pipeline_get_parent (authority) != NULL) + { + CoglPipeline *parent = _cogl_pipeline_get_parent (authority); + CoglPipeline *old_authority = + _cogl_pipeline_get_authority (parent, state); + + if (old_authority->big_state->user_program == program) + pipeline->differences &= ~state; + } + else if (pipeline != 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... */ + pipeline->differences |= state; + _cogl_pipeline_prune_redundant_ancestry (pipeline); + } + + if (program != COGL_INVALID_HANDLE) + cogl_handle_ref (program); + if (authority == pipeline && + pipeline->big_state->user_program != COGL_INVALID_HANDLE) + cogl_handle_unref (pipeline->big_state->user_program); + pipeline->big_state->user_program = program; + + handle_automatic_blend_enable (pipeline, state); +} + +void +cogl_pipeline_set_depth_test_enabled (CoglPipeline *pipeline, + gboolean enable) +{ + CoglPipelineState state = COGL_PIPELINE_STATE_DEPTH; + CoglPipeline *authority; + CoglPipelineDepthState *depth_state; + + g_return_if_fail (cogl_is_pipeline (pipeline)); + + authority = _cogl_pipeline_get_authority (pipeline, 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 pipeline has no dependants so it may be modified. + * - If the pipeline isn't currently an authority for the state being + * changed, then initialize that state from the current authority. + */ + _cogl_pipeline_pre_change_notify (pipeline, state, NULL, FALSE); + + pipeline->big_state->depth_state.depth_test_enabled = enable; + + _cogl_pipeline_update_authority (pipeline, authority, state, + _cogl_pipeline_depth_state_equal); +} + +gboolean +cogl_pipeline_get_depth_test_enabled (CoglPipeline *pipeline) +{ + CoglPipeline *authority; + + g_return_val_if_fail (cogl_is_pipeline (pipeline), FALSE); + + authority = + _cogl_pipeline_get_authority (pipeline, COGL_PIPELINE_STATE_DEPTH); + + return authority->big_state->depth_state.depth_test_enabled; +} + +void +cogl_pipeline_set_depth_writing_enabled (CoglPipeline *pipeline, + gboolean enable) +{ + CoglPipelineState state = COGL_PIPELINE_STATE_DEPTH; + CoglPipeline *authority; + CoglPipelineDepthState *depth_state; + + g_return_if_fail (cogl_is_pipeline (pipeline)); + + authority = _cogl_pipeline_get_authority (pipeline, 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 pipeline has no dependants so it may be modified. + * - If the pipeline isn't currently an authority for the state being + * changed, then initialize that state from the current authority. + */ + _cogl_pipeline_pre_change_notify (pipeline, state, NULL, FALSE); + + pipeline->big_state->depth_state.depth_writing_enabled = enable; + + _cogl_pipeline_update_authority (pipeline, authority, state, + _cogl_pipeline_depth_state_equal); +} + +gboolean +cogl_pipeline_get_depth_writing_enabled (CoglPipeline *pipeline) +{ + CoglPipeline *authority; + + g_return_val_if_fail (cogl_is_pipeline (pipeline), TRUE); + + authority = + _cogl_pipeline_get_authority (pipeline, COGL_PIPELINE_STATE_DEPTH); + + return authority->big_state->depth_state.depth_writing_enabled; +} + +void +cogl_pipeline_set_depth_test_function (CoglPipeline *pipeline, + CoglDepthTestFunction function) +{ + CoglPipelineState state = COGL_PIPELINE_STATE_DEPTH; + CoglPipeline *authority; + CoglPipelineDepthState *depth_state; + + g_return_if_fail (cogl_is_pipeline (pipeline)); + + authority = _cogl_pipeline_get_authority (pipeline, 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 pipeline has no dependants so it may be modified. + * - If the pipeline isn't currently an authority for the state being + * changed, then initialize that state from the current authority. + */ + _cogl_pipeline_pre_change_notify (pipeline, state, NULL, FALSE); + + pipeline->big_state->depth_state.depth_test_function = function; + + _cogl_pipeline_update_authority (pipeline, authority, state, + _cogl_pipeline_depth_state_equal); +} + +CoglDepthTestFunction +cogl_pipeline_get_depth_test_function (CoglPipeline *pipeline) +{ + CoglPipeline *authority; + + g_return_val_if_fail (cogl_is_pipeline (pipeline), + COGL_DEPTH_TEST_FUNCTION_LESS); + + authority = + _cogl_pipeline_get_authority (pipeline, COGL_PIPELINE_STATE_DEPTH); + + return authority->big_state->depth_state.depth_test_function; +} + + +gboolean +cogl_pipeline_set_depth_range (CoglPipeline *pipeline, + float near_val, + float far_val, + GError **error) +{ +#ifndef COGL_HAS_GLES + CoglPipelineState state = COGL_PIPELINE_STATE_DEPTH; + CoglPipeline *authority; + CoglPipelineDepthState *depth_state; + + g_return_val_if_fail (cogl_is_pipeline (pipeline), FALSE); + + authority = _cogl_pipeline_get_authority (pipeline, 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 pipeline has no dependants so it may be modified. + * - If the pipeline isn't currently an authority for the state being + * changed, then initialize that state from the current authority. + */ + _cogl_pipeline_pre_change_notify (pipeline, state, NULL, FALSE); + + pipeline->big_state->depth_state.depth_range_near = near_val; + pipeline->big_state->depth_state.depth_range_far = far_val; + + _cogl_pipeline_update_authority (pipeline, authority, state, + _cogl_pipeline_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_pipeline_get_depth_range (CoglPipeline *pipeline, + float *near_val, + float *far_val) +{ + CoglPipeline *authority; + + g_return_if_fail (cogl_is_pipeline (pipeline)); + + authority = + _cogl_pipeline_get_authority (pipeline, COGL_PIPELINE_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_pipeline_set_fog_state (CoglPipeline *pipeline, + const CoglPipelineFogState *fog_state) +{ + CoglPipelineState state = COGL_PIPELINE_STATE_FOG; + CoglPipeline *authority; + CoglPipelineFogState *current_fog_state; + + g_return_if_fail (cogl_is_pipeline (pipeline)); + + authority = _cogl_pipeline_get_authority (pipeline, 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 pipeline has no dependants so it may be modified. + * - If the pipeline isn't currently an authority for the state being + * changed, then initialize that state from the current authority. + */ + _cogl_pipeline_pre_change_notify (pipeline, state, NULL, FALSE); + + pipeline->big_state->fog_state = *fog_state; + + _cogl_pipeline_update_authority (pipeline, authority, state, + _cogl_pipeline_fog_state_equal); +} + +unsigned long +_cogl_pipeline_get_age (CoglPipeline *pipeline) +{ + g_return_val_if_fail (cogl_is_pipeline (pipeline), 0); + + return pipeline->age; +} + +static CoglPipelineLayer * +_cogl_pipeline_layer_copy (CoglPipelineLayer *src) +{ + CoglPipelineLayer *layer = g_slice_new (CoglPipelineLayer); + int i; + + _cogl_pipeline_node_init (COGL_PIPELINE_NODE (layer)); + + layer->owner = NULL; + layer->index = src->index; + layer->differences = 0; + layer->has_big_state = FALSE; + + for (i = 0; i < COGL_PIPELINE_N_BACKENDS; i++) + layer->backend_priv[i] = NULL; + + _cogl_pipeline_layer_set_parent (layer, src); + + return _cogl_pipeline_layer_object_new (layer); +} + +static void +_cogl_pipeline_layer_free (CoglPipelineLayer *layer) +{ + int i; + + _cogl_pipeline_layer_unparent (COGL_PIPELINE_NODE (layer)); + + /* NB: layers may be used by multiple pipelines 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_PIPELINE_N_BACKENDS; i++) + { + if (layer->backend_priv[i] && + _cogl_pipeline_backends[i]->free_layer_priv) + { + const CoglPipelineBackend *backend = _cogl_pipeline_backends[i]; + backend->free_layer_priv (layer); + } + } + + if (layer->differences & COGL_PIPELINE_LAYER_STATE_TEXTURE && + layer->texture != COGL_INVALID_HANDLE) + cogl_handle_unref (layer->texture); + + if (layer->differences & COGL_PIPELINE_LAYER_STATE_NEEDS_BIG_STATE) + g_slice_free (CoglPipelineLayerBigState, layer->big_state); + + g_slice_free (CoglPipelineLayer, 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_pipeline_init_default_layers (void) +{ + CoglPipelineLayer *layer = g_slice_new0 (CoglPipelineLayer); + CoglPipelineLayerBigState *big_state = + g_slice_new0 (CoglPipelineLayerBigState); + CoglPipelineLayer *new; + int i; + + _COGL_GET_CONTEXT (ctx, NO_RETVAL); + + _cogl_pipeline_node_init (COGL_PIPELINE_NODE (layer)); + + layer->index = 0; + + for (i = 0; i < COGL_PIPELINE_N_BACKENDS; i++) + layer->backend_priv[i] = NULL; + + layer->differences = COGL_PIPELINE_LAYER_STATE_ALL_SPARSE; + + layer->unit_index = 0; + + layer->texture = COGL_INVALID_HANDLE; + layer->texture_overridden = FALSE; + + layer->mag_filter = COGL_PIPELINE_FILTER_LINEAR; + layer->min_filter = COGL_PIPELINE_FILTER_LINEAR; + + layer->wrap_mode_s = COGL_PIPELINE_WRAP_MODE_AUTOMATIC; + layer->wrap_mode_t = COGL_PIPELINE_WRAP_MODE_AUTOMATIC; + layer->wrap_mode_p = COGL_PIPELINE_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_pipeline_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_pipeline_layer_copy (layer); + new = _cogl_pipeline_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_pipeline_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_pipeline_set_layer_combine (CoglPipeline *pipeline, + int layer_index, + const char *combine_description, + GError **error) +{ + CoglPipelineLayerState state = COGL_PIPELINE_LAYER_STATE_COMBINE; + CoglPipelineLayer *authority; + CoglPipelineLayer *layer; + CoglBlendStringStatement statements[2]; + CoglBlendStringStatement split[2]; + CoglBlendStringStatement *rgb; + CoglBlendStringStatement *a; + GError *internal_error = NULL; + int count; + + g_return_val_if_fail (cogl_is_pipeline (pipeline), 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 + * pipeline. If the layer is created then it will be owned by + * pipeline. */ + layer = _cogl_pipeline_get_layer (pipeline, layer_index); + + /* Now find the ancestor of the layer that is the authority for the + * state we want to change */ + authority = _cogl_pipeline_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_pipeline_layer_pre_change_notify (pipeline, 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_pipeline_layer_get_parent (authority) != NULL) + { + CoglPipelineLayer *parent = _cogl_pipeline_layer_get_parent (authority); + CoglPipelineLayer *old_authority = + _cogl_pipeline_layer_get_authority (parent, state); + + if (_cogl_pipeline_layer_combine_state_equal (authority, + old_authority)) + { + layer->differences &= ~state; + + g_assert (layer->owner == pipeline); + if (layer->differences == 0) + _cogl_pipeline_prune_empty_layer_difference (pipeline, + 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_pipeline_layer_prune_redundant_ancestry (layer); + } + +changed: + + handle_automatic_blend_enable (pipeline, COGL_PIPELINE_STATE_LAYERS); + return TRUE; +} + +void +cogl_pipeline_set_layer_combine_constant (CoglPipeline *pipeline, + int layer_index, + const CoglColor *constant_color) +{ + CoglPipelineLayerState state = COGL_PIPELINE_LAYER_STATE_COMBINE_CONSTANT; + CoglPipelineLayer *layer; + CoglPipelineLayer *authority; + CoglPipelineLayer *new; + + g_return_if_fail (cogl_is_pipeline (pipeline)); + + /* 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 + * pipeline. If the layer is created then it will be owned by + * pipeline. */ + layer = _cogl_pipeline_get_layer (pipeline, layer_index); + + /* Now find the ancestor of the layer that is the authority for the + * state we want to change */ + authority = _cogl_pipeline_layer_get_authority (layer, state); + + if (memcmp (authority->big_state->texture_combine_constant, + constant_color, sizeof (float) * 4) == 0) + return; + + new = _cogl_pipeline_layer_pre_change_notify (pipeline, 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_pipeline_layer_get_parent (authority) != NULL) + { + CoglPipelineLayer *parent = + _cogl_pipeline_layer_get_parent (authority); + CoglPipelineLayer *old_authority = + _cogl_pipeline_layer_get_authority (parent, state); + CoglPipelineLayerBigState *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 == pipeline); + if (layer->differences == 0) + _cogl_pipeline_prune_empty_layer_difference (pipeline, + 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_pipeline_layer_prune_redundant_ancestry (layer); + } + +changed: + + handle_automatic_blend_enable (pipeline, COGL_PIPELINE_STATE_LAYERS); +} + +void +_cogl_pipeline_get_layer_combine_constant (CoglPipeline *pipeline, + int layer_index, + float *constant) +{ + CoglPipelineLayerState change = + COGL_PIPELINE_LAYER_STATE_COMBINE_CONSTANT; + CoglPipelineLayer *layer; + CoglPipelineLayer *authority; + + g_return_if_fail (cogl_is_pipeline (pipeline)); + + /* 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 + * pipeline. If the layer is created then it will be owned by + * pipeline. */ + layer = _cogl_pipeline_get_layer (pipeline, layer_index); + /* FIXME: we shouldn't ever construct a layer in a getter function */ + + authority = _cogl_pipeline_layer_get_authority (layer, change); + memcpy (constant, authority->big_state->texture_combine_constant, + sizeof (float) * 4); +} + +void +cogl_pipeline_set_layer_matrix (CoglPipeline *pipeline, + int layer_index, + const CoglMatrix *matrix) +{ + CoglPipelineLayerState state = COGL_PIPELINE_LAYER_STATE_USER_MATRIX; + CoglPipelineLayer *layer; + CoglPipelineLayer *authority; + CoglPipelineLayer *new; + + g_return_if_fail (cogl_is_pipeline (pipeline)); + + /* 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 + * pipeline. If the layer is created then it will be owned by + * pipeline. */ + layer = _cogl_pipeline_get_layer (pipeline, layer_index); + + /* Now find the ancestor of the layer that is the authority for the + * state we want to change */ + authority = _cogl_pipeline_layer_get_authority (layer, state); + + if (cogl_matrix_equal (matrix, &authority->big_state->matrix)) + return; + + new = _cogl_pipeline_layer_pre_change_notify (pipeline, 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_pipeline_layer_get_parent (authority) != NULL) + { + CoglPipelineLayer *parent = + _cogl_pipeline_layer_get_parent (authority); + CoglPipelineLayer *old_authority = + _cogl_pipeline_layer_get_authority (parent, state); + + if (cogl_matrix_equal (matrix, &old_authority->big_state->matrix)) + { + layer->differences &= ~state; + + g_assert (layer->owner == pipeline); + if (layer->differences == 0) + _cogl_pipeline_prune_empty_layer_difference (pipeline, + 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_pipeline_layer_prune_redundant_ancestry (layer); + } +} + +void +cogl_pipeline_remove_layer (CoglPipeline *pipeline, int layer_index) +{ + CoglPipeline *authority; + CoglPipelineLayerInfo layer_info; + int i; + + g_return_if_fail (cogl_is_pipeline (pipeline)); + + authority = + _cogl_pipeline_get_authority (pipeline, COGL_PIPELINE_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 (CoglPipelineLayer *) * 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_pipeline_get_layer_info (authority, &layer_info); + + if (layer_info.layer == NULL) + return; + + for (i = 0; i < layer_info.n_layers_to_shift; i++) + { + CoglPipelineLayer *shift_layer = layer_info.layers_to_shift[i]; + int unit_index = _cogl_pipeline_layer_get_unit_index (shift_layer); + _cogl_pipeline_set_layer_unit (pipeline, 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 pipeline. Check the return value if we need to do + * anything else with this layer. */ + } + + _cogl_pipeline_remove_layer_difference (pipeline, layer_info.layer, TRUE); + _cogl_pipeline_try_reverting_layers_authority (pipeline, NULL); + + handle_automatic_blend_enable (pipeline, COGL_PIPELINE_STATE_LAYERS); +} + +static gboolean +prepend_layer_to_list_cb (CoglPipelineLayer *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_pipeline_foreach_layer + * TODO: update the docs to note that if the user modifies any layers + * then the list may become invalid. + */ +const GList * +_cogl_pipeline_get_layers (CoglPipeline *pipeline) +{ + g_return_val_if_fail (cogl_is_pipeline (pipeline), NULL); + + if (!pipeline->deprecated_get_layers_list_dirty) + g_list_free (pipeline->deprecated_get_layers_list); + + pipeline->deprecated_get_layers_list = NULL; + + _cogl_pipeline_foreach_layer_internal (pipeline, + prepend_layer_to_list_cb, + &pipeline->deprecated_get_layers_list); + pipeline->deprecated_get_layers_list = + g_list_reverse (pipeline->deprecated_get_layers_list); + + pipeline->deprecated_get_layers_list_dirty = 0; + + return pipeline->deprecated_get_layers_list; +} + +int +cogl_pipeline_get_n_layers (CoglPipeline *pipeline) +{ + CoglPipeline *authority; + + g_return_val_if_fail (cogl_is_pipeline (pipeline), 0); + + authority = + _cogl_pipeline_get_authority (pipeline, COGL_PIPELINE_STATE_LAYERS); + + return authority->n_layers; +} + +/* FIXME: deprecate and replace with + * cogl_pipeline_get_layer_texture() instead. */ +CoglHandle +_cogl_pipeline_layer_get_texture (CoglPipelineLayer *layer) +{ + g_return_val_if_fail (_cogl_is_pipeline_layer (layer), + COGL_INVALID_HANDLE); + + return _cogl_pipeline_layer_get_texture_real (layer); +} + +gboolean +_cogl_pipeline_layer_has_user_matrix (CoglPipelineLayer *layer) +{ + CoglPipelineLayer *authority; + + g_return_val_if_fail (_cogl_is_pipeline_layer (layer), FALSE); + + authority = + _cogl_pipeline_layer_get_authority (layer, + COGL_PIPELINE_LAYER_STATE_USER_MATRIX); + + /* If the authority is the default pipeline then no, otherwise yes */ + return _cogl_pipeline_layer_get_parent (authority) ? TRUE : FALSE; +} + +void +_cogl_pipeline_layer_get_filters (CoglPipelineLayer *layer, + CoglPipelineFilter *min_filter, + CoglPipelineFilter *mag_filter) +{ + CoglPipelineLayer *authority = + _cogl_pipeline_layer_get_authority (layer, + COGL_PIPELINE_LAYER_STATE_FILTERS); + + *min_filter = authority->min_filter; + *mag_filter = authority->mag_filter; +} + +void +_cogl_pipeline_get_layer_filters (CoglPipeline *pipeline, + int layer_index, + CoglPipelineFilter *min_filter, + CoglPipelineFilter *mag_filter) +{ + CoglPipelineLayer *layer; + CoglPipelineLayer *authority; + + g_return_if_fail (cogl_is_pipeline (pipeline)); + + layer = _cogl_pipeline_get_layer (pipeline, layer_index); + + authority = + _cogl_pipeline_layer_get_authority (layer, + COGL_PIPELINE_LAYER_STATE_FILTERS); + + *min_filter = authority->min_filter; + *mag_filter = authority->mag_filter; +} + +CoglPipelineFilter +_cogl_pipeline_get_layer_min_filter (CoglPipeline *pipeline, + int layer_index) +{ + CoglPipelineFilter min_filter; + CoglPipelineFilter mag_filter; + + _cogl_pipeline_get_layer_filters (pipeline, layer_index, + &min_filter, &mag_filter); + return min_filter; +} + +CoglPipelineFilter +_cogl_pipeline_get_layer_mag_filter (CoglPipeline *pipeline, + int layer_index) +{ + CoglPipelineFilter min_filter; + CoglPipelineFilter mag_filter; + + _cogl_pipeline_get_layer_filters (pipeline, layer_index, + &min_filter, &mag_filter); + return mag_filter; +} + +void +_cogl_pipeline_layer_pre_paint (CoglPipelineLayer *layer) +{ + CoglPipelineLayer *texture_authority; + + texture_authority = + _cogl_pipeline_layer_get_authority (layer, + COGL_PIPELINE_LAYER_STATE_TEXTURE); + + if (texture_authority->texture != COGL_INVALID_HANDLE) + { + CoglTexturePrePaintFlags flags = 0; + CoglPipelineFilter min_filter; + CoglPipelineFilter mag_filter; + + _cogl_pipeline_layer_get_filters (layer, &min_filter, &mag_filter); + + if (min_filter == COGL_PIPELINE_FILTER_NEAREST_MIPMAP_NEAREST + || min_filter == COGL_PIPELINE_FILTER_LINEAR_MIPMAP_NEAREST + || min_filter == COGL_PIPELINE_FILTER_NEAREST_MIPMAP_LINEAR + || min_filter == COGL_PIPELINE_FILTER_LINEAR_MIPMAP_LINEAR) + flags |= COGL_TEXTURE_NEEDS_MIPMAP; + + _cogl_texture_pre_paint (texture_authority->texture, flags); + } +} + +void +_cogl_pipeline_pre_paint_for_layer (CoglPipeline *pipeline, + int layer_id) +{ + CoglPipelineLayer *layer = _cogl_pipeline_get_layer (pipeline, layer_id); + _cogl_pipeline_layer_pre_paint (layer); +} + +CoglPipelineFilter +_cogl_pipeline_layer_get_min_filter (CoglPipelineLayer *layer) +{ + CoglPipelineLayer *authority; + + g_return_val_if_fail (_cogl_is_pipeline_layer (layer), 0); + + authority = + _cogl_pipeline_layer_get_authority (layer, + COGL_PIPELINE_LAYER_STATE_FILTERS); + + return authority->min_filter; +} + +CoglPipelineFilter +_cogl_pipeline_layer_get_mag_filter (CoglPipelineLayer *layer) +{ + CoglPipelineLayer *authority; + + g_return_val_if_fail (_cogl_is_pipeline_layer (layer), 0); + + authority = + _cogl_pipeline_layer_get_authority (layer, + COGL_PIPELINE_LAYER_STATE_FILTERS); + + return authority->mag_filter; +} + +void +cogl_pipeline_set_layer_filters (CoglPipeline *pipeline, + int layer_index, + CoglPipelineFilter min_filter, + CoglPipelineFilter mag_filter) +{ + CoglPipelineLayerState state = COGL_PIPELINE_LAYER_STATE_FILTERS; + CoglPipelineLayer *layer; + CoglPipelineLayer *authority; + CoglPipelineLayer *new; + + g_return_if_fail (cogl_is_pipeline (pipeline)); + + /* 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 + * pipeline. If the layer is created then it will be owned by + * pipeline. */ + layer = _cogl_pipeline_get_layer (pipeline, layer_index); + + /* Now find the ancestor of the layer that is the authority for the + * state we want to change */ + authority = _cogl_pipeline_layer_get_authority (layer, state); + + if (authority->min_filter == min_filter && + authority->mag_filter == mag_filter) + return; + + new = _cogl_pipeline_layer_pre_change_notify (pipeline, 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_pipeline_layer_get_parent (authority) != NULL) + { + CoglPipelineLayer *parent = + _cogl_pipeline_layer_get_parent (authority); + CoglPipelineLayer *old_authority = + _cogl_pipeline_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 == pipeline); + if (layer->differences == 0) + _cogl_pipeline_prune_empty_layer_difference (pipeline, + 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_pipeline_layer_prune_redundant_ancestry (layer); + } +} + +float +cogl_pipeline_get_point_size (CoglHandle handle) +{ + CoglPipeline *pipeline = COGL_PIPELINE (handle); + CoglPipeline *authority; + + g_return_val_if_fail (cogl_is_pipeline (handle), FALSE); + + authority = + _cogl_pipeline_get_authority (pipeline, COGL_PIPELINE_STATE_POINT_SIZE); + + return authority->big_state->point_size; +} + +void +cogl_pipeline_set_point_size (CoglHandle handle, + float point_size) +{ + CoglPipeline *pipeline = COGL_PIPELINE (handle); + CoglPipelineState state = COGL_PIPELINE_STATE_POINT_SIZE; + CoglPipeline *authority; + + g_return_if_fail (cogl_is_pipeline (handle)); + + authority = _cogl_pipeline_get_authority (pipeline, state); + + if (authority->big_state->point_size == point_size) + return; + + /* - Flush journal primitives referencing the current state. + * - Make sure the pipeline has no dependants so it may be modified. + * - If the pipeline isn't currently an authority for the state being + * changed, then initialize that state from the current authority. + */ + _cogl_pipeline_pre_change_notify (pipeline, state, NULL, FALSE); + + pipeline->big_state->point_size = point_size; + + _cogl_pipeline_update_authority (pipeline, authority, state, + _cogl_pipeline_point_size_equal); +} + +/* While a pipeline is referenced by the Cogl journal we can not allow + * modifications, so this gives us a mechanism to track journal + * references separately */ +CoglPipeline * +_cogl_pipeline_journal_ref (CoglPipeline *pipeline) +{ + pipeline->journal_ref_count++; + return cogl_object_ref (pipeline); +} + +void +_cogl_pipeline_journal_unref (CoglPipeline *pipeline) +{ + pipeline->journal_ref_count--; + cogl_object_unref (pipeline); +} + +void +_cogl_pipeline_apply_legacy_state (CoglPipeline *pipeline) +{ + _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_pipeline API instead. + */ + + /* A program explicitly set on the pipeline has higher precedence than + * one associated with the context using cogl_program_use() */ + if (ctx->current_program && + cogl_pipeline_get_user_program (pipeline) == COGL_INVALID_HANDLE) + cogl_pipeline_set_user_program (pipeline, ctx->current_program); + + if (ctx->legacy_depth_test_enabled) + cogl_pipeline_set_depth_test_enabled (pipeline, TRUE); + + if (ctx->legacy_fog_state.enabled) + _cogl_pipeline_set_fog_state (pipeline, &ctx->legacy_fog_state); +} + +void +_cogl_pipeline_set_static_breadcrumb (CoglPipeline *pipeline, + const char *breadcrumb) +{ + pipeline->has_static_breadcrumb = TRUE; + pipeline->static_breadcrumb = breadcrumb; +} + +typedef struct +{ + int parent_id; + int *node_id_ptr; + GString *graph; + int indent; +} PrintDebugState; + +static gboolean +dump_layer_cb (CoglPipelineNode *node, void *user_data) +{ + CoglPipelineLayer *layer = COGL_PIPELINE_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_PIPELINE_LAYER_STATE_UNIT) + { + changes = TRUE; + g_string_append_printf (changes_label, + "\\lunit=%u\\n", + layer->unit_index); + } + + if (layer->differences & COGL_PIPELINE_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_pipeline_node_foreach_child (COGL_PIPELINE_NODE (layer), + dump_layer_cb, + &state_out); + + return TRUE; +} + +static gboolean +dump_layer_ref_cb (CoglPipelineLayer *layer, void *data) +{ + PrintDebugState *state = data; + int pipeline_id = *state->node_id_ptr; + + g_string_append_printf (state->graph, + "%*spipeline_state%d -> layer%p;\n", + state->indent, "", + pipeline_id, + layer); + + return TRUE; +} + +static gboolean +dump_pipeline_cb (CoglPipelineNode *node, void *user_data) +{ + CoglPipeline *pipeline = COGL_PIPELINE (node); + PrintDebugState *state = user_data; + int pipeline_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, "%*spipeline%d -> pipeline%d;\n", + state->indent, "", + state->parent_id, + pipeline_id); + + g_string_append_printf (state->graph, + "%*spipeline%d [label=\"pipeline=0x%p\\n" + "ref count=%d\\n" + "breadcrumb=\\\"%s\\\"\" color=\"red\"];\n", + state->indent, "", + pipeline_id, + pipeline, + COGL_OBJECT (pipeline)->ref_count, + pipeline->has_static_breadcrumb ? + pipeline->static_breadcrumb : "NULL"); + + changes_label = g_string_new (""); + g_string_append_printf (changes_label, + "%*spipeline%d -> pipeline_state%d [weight=100];\n" + "%*spipeline_state%d [shape=box label=\"", + state->indent, "", + pipeline_id, + pipeline_id, + state->indent, "", + pipeline_id); + + + if (pipeline->differences & COGL_PIPELINE_STATE_COLOR) + { + changes = TRUE; + g_string_append_printf (changes_label, + "\\lcolor=0x%02X%02X%02X%02X\\n", + cogl_color_get_red_byte (&pipeline->color), + cogl_color_get_green_byte (&pipeline->color), + cogl_color_get_blue_byte (&pipeline->color), + cogl_color_get_alpha_byte (&pipeline->color)); + } + + if (pipeline->differences & COGL_PIPELINE_STATE_BLEND) + { + const char *blend_enable_name; + + changes = TRUE; + + switch (pipeline->blend_enable) + { + case COGL_PIPELINE_BLEND_ENABLE_AUTOMATIC: + blend_enable_name = "AUTO"; + break; + case COGL_PIPELINE_BLEND_ENABLE_ENABLED: + blend_enable_name = "ENABLED"; + break; + case COGL_PIPELINE_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 (pipeline->differences & COGL_PIPELINE_STATE_LAYERS) + { + changes = TRUE; + layers = TRUE; + g_string_append_printf (changes_label, "\\ln_layers=%d\\n", + pipeline->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 (pipeline->layer_differences, + (GFunc)dump_layer_ref_cb, + state); + } + + state_out.parent_id = pipeline_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_pipeline_node_foreach_child (COGL_PIPELINE_NODE (pipeline), + dump_pipeline_cb, + &state_out); + + return TRUE; +} + +void +_cogl_debug_dump_pipelines_dot_file (const char *filename) +{ + GString *graph; + PrintDebugState layer_state; + PrintDebugState pipeline_state; + int layer_id = 0; + int pipeline_id = 0; + + _COGL_GET_CONTEXT (ctx, NO_RETVAL); + + if (!ctx->default_pipeline) + 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); + + pipeline_state.graph = graph; + pipeline_state.parent_id = -1; + pipeline_state.node_id_ptr = &pipeline_id; + pipeline_state.indent = 0; + dump_pipeline_cb (ctx->default_pipeline, &pipeline_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); +} + |