/* * Cogl * * An object oriented GL/GLES Abstraction/Utility Layer * * Copyright (C) 2008,2009,2010 Intel Corporation. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library. If not, see * . * * * * Authors: * Robert Bragg */ #ifdef HAVE_CONFIG_H #include "config.h" #endif #include "cogl.h" #include "cogl-debug.h" #include "cogl-internal.h" #include "cogl-context.h" #include "cogl-object.h" #include "cogl-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 #include #include 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; 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; } if (changes & COGL_PIPELINE_STATE_USER_SHADER) { /* 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 (_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) { /* XXX: This stuff is showing up in sysprof reports which is * silly because lighting isn't currently actually supported * by Cogl except for these token properties. When we actually * expose lighting support we can avoid these checks when * lighting is disabled. */ #if 0 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; #endif } 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; } else { /* In this case we have so far 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<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<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); }