From 365605cf42fca1beb9b70acd58d23ee4bc4cd208 Mon Sep 17 00:00:00 2001 From: Robert Bragg Date: Thu, 8 Apr 2010 12:21:04 +0100 Subject: [PATCH] CoglMaterial: Implements sparse materials design This is a complete overhaul of the data structures used to manage CoglMaterial state. We have these requirements that were aiming to meet: (Note: the references to "renderlists" correspond to the effort to support scenegraph level shuffling of Clutter actor primitives so we can minimize GPU state changes) Sparse State: We wanted a design that allows sparse descriptions of state so it scales well as we make CoglMaterial responsible for more and more state. It needs to scale well in terms of memory usage and the cost of operations we need to apply to materials such as comparing, copying and flushing their state. I.e. we would rather have these things scale by the number of real changes a material represents not by how much overall state CoglMaterial becomes responsible for. Cheap Copies: As we add support for renderlists in Clutter we will need to be able to get an immutable handle for a given material's current state so that we can retain a record of a primitive with its associated material without worrying that changes to the original material will invalidate that record. No more flush override options: We want to get rid of the flush overrides mechanism we currently use to deal with texture fallbacks, wrap mode changes and to handle the use of highlevel CoglTextures that need to be resolved into lowlevel textures before flushing the material state. The flush options structure has been expanding in size and the structure is logged with every journal entry so it is not an approach that scales well at all. It also makes flushing material state that much more complex. Weak Materials: Again for renderlists we need a way to create materials derived from other materials but without the strict requirement that modifications to the original material wont affect the derived ("weak") material. The only requirement is that its possible to later check if the original material has been changed. A summary of the new design: A CoglMaterial now basically represents a diff against its parent. Each material has a single parent and a mask of state that it changes. Each group of state (such as the blending state) has an "authority" which is found by walking up from a given material through its ancestors checking the difference mask until a match for that group is found. There is only one root node to the graph of all materials, which is the default material first created when Cogl is being initialized. All the groups of state are divided into two types, such that infrequently changed state belongs in a separate "BigState" structure that is only allocated and attached to a material when necessary. CoglMaterialLayers are another sparse structure. Like CoglMaterials they represent a diff against their parent and all the layers are part of another graph with the "default_layer_0" layer being the root node that Cogl creates during initialization. Copying a material is now basically just a case of slice allocating a CoglMaterial, setting the parent to be the source being copied and zeroing the mask of changes. Flush overrides should now be handled by simply relying on the cheapness of copying a material and making changes to it. (This will be done in a follow on commit) Weak material support will be added in a follow on commit. --- clutter/cogl/cogl/cogl-atlas-texture.c | 10 + clutter/cogl/cogl/cogl-context.c | 23 +- clutter/cogl/cogl/cogl-context.h | 21 +- clutter/cogl/cogl/cogl-debug.c | 4 +- clutter/cogl/cogl/cogl-debug.h | 3 +- clutter/cogl/cogl/cogl-internal.h | 9 +- clutter/cogl/cogl/cogl-journal.c | 7 +- clutter/cogl/cogl/cogl-material-private.h | 610 +- clutter/cogl/cogl/cogl-material.c | 6186 +++++++++++++++------ clutter/cogl/cogl/cogl-material.h | 6 +- clutter/cogl/cogl/cogl-path.c | 7 +- clutter/cogl/cogl/cogl-primitives.c | 29 +- clutter/cogl/cogl/cogl-vertex-buffer.c | 21 +- clutter/cogl/cogl/cogl.c | 8 - 14 files changed, 5009 insertions(+), 1935 deletions(-) diff --git a/clutter/cogl/cogl/cogl-atlas-texture.c b/clutter/cogl/cogl/cogl-atlas-texture.c index b414b46f3..cf09a6490 100644 --- a/clutter/cogl/cogl/cogl-atlas-texture.c +++ b/clutter/cogl/cogl/cogl-atlas-texture.c @@ -374,6 +374,11 @@ _cogl_atlas_texture_migrate_out_of_atlas (CoglAtlasTexture *atlas_tex) */ _cogl_journal_flush (); + /* Notify cogl-material.c that the texture's underlying GL texture + * storage is changing so it knows it may need to bind a new texture + * if the CoglTexture is reused with the same texture unit. */ + _cogl_material_texture_storage_change_notify (atlas_tex); + cogl_handle_unref (atlas_tex->sub_texture); /* Create a new texture at the right size, not including the @@ -686,6 +691,11 @@ _cogl_atlas_texture_migrate (unsigned int n_textures, for (i = 0; i < n_textures; i++) { + /* Notify cogl-material.c that the texture's underlying GL texture + * storage is changing so it knows it may need to bind a new texture + * if the CoglTexture is reused with the same texture unit. */ + _cogl_material_texture_storage_change_notify (textures[i].texture); + /* Skip the texture that is being added because it doesn't contain any data yet */ if (textures[i].texture != skip_texture) diff --git a/clutter/cogl/cogl/cogl-context.c b/clutter/cogl/cogl/cogl-context.c index 830c69eda..a01d74007 100644 --- a/clutter/cogl/cogl/cogl-context.c +++ b/clutter/cogl/cogl/cogl-context.c @@ -67,9 +67,9 @@ cogl_create_context (void) _cogl_features_init (); _cogl_material_init_default_material (); + _cogl_material_init_default_layers (); _context->enable_flags = 0; - _context->color_alpha = 0; _context->fog_enabled = FALSE; _context->enable_backface_culling = FALSE; @@ -104,12 +104,14 @@ cogl_create_context (void) _context->logged_vertices = g_array_new (FALSE, FALSE, sizeof (GLfloat)); _context->current_material = NULL; - _context->current_material_flags = COGL_MATERIAL_FLAGS_INIT; - _context->current_material_fallback_layers = 0; - _context->current_material_disable_layers = 0; - _context->current_material_layer0_override = 0; + _context->current_material_changes_since_flush = 0; _context->current_material_skip_gl_color = FALSE; + _context->material0_nodes = + g_array_sized_new (FALSE, FALSE, sizeof (CoglHandle), 20); + _context->material1_nodes = + g_array_sized_new (FALSE, FALSE, sizeof (CoglHandle), 20); + _cogl_bitmask_init (&_context->texcoord_arrays_enabled); _cogl_bitmask_init (&_context->temp_bitmask); _cogl_bitmask_init (&_context->texcoord_arrays_to_disable); @@ -123,6 +125,8 @@ cogl_create_context (void) _context->current_use_program_type = COGL_MATERIAL_PROGRAM_TYPE_FIXED; _context->current_gl_program = 0; + _context->gl_blend_enable_cache = FALSE; + _context->framebuffer_stack = _cogl_create_framebuffer_stack (); window_buffer = _cogl_onscreen_new (); @@ -173,8 +177,6 @@ cogl_create_context (void) cogl_set_source (_context->simple_material); _cogl_material_flush_gl_state (_context->source_material, NULL); - enable_flags = - _cogl_material_get_cogl_enable_flags (_context->source_material); _cogl_enable (enable_flags); _cogl_flush_face_winding (); @@ -221,6 +223,13 @@ _cogl_destroy_context (void) if (_context->default_material) cogl_handle_unref (_context->default_material); + if (_context->dummy_layer_dependant) + cogl_handle_unref (_context->dummy_layer_dependant); + if (_context->default_layer_n) + cogl_handle_unref (_context->default_layer_n); + if (_context->default_layer_0) + cogl_handle_unref (_context->default_layer_0); + if (_context->atlas) _cogl_atlas_free (_context->atlas); if (_context->atlas_texture) diff --git a/clutter/cogl/cogl/cogl-context.h b/clutter/cogl/cogl/cogl-context.h index 148bbaab4..530184f0b 100644 --- a/clutter/cogl/cogl/cogl-context.h +++ b/clutter/cogl/cogl/cogl-context.h @@ -49,10 +49,12 @@ typedef struct gboolean features_cached; CoglHandle default_material; + CoglHandle default_layer_0; + CoglHandle default_layer_n; + CoglHandle dummy_layer_dependant; /* Enable cache */ unsigned long enable_flags; - guint8 color_alpha; gboolean fog_enabled; gboolean enable_backface_culling; @@ -91,11 +93,12 @@ typedef struct /* Some simple caching, to minimize state changes... */ CoglHandle current_material; - unsigned long current_material_flags; - gboolean current_material_fallback_layers; - gboolean current_material_disable_layers; - GLuint current_material_layer0_override; + unsigned long current_material_changes_since_flush; gboolean current_material_skip_gl_color; + + GArray *material0_nodes; + GArray *material1_nodes; + /* Bitmask of texture coordinates arrays that are enabled */ CoglBitmask texcoord_arrays_enabled; /* These are temporary bitmasks that are used when disabling @@ -104,6 +107,8 @@ typedef struct CoglBitmask texcoord_arrays_to_disable; CoglBitmask temp_bitmask; + gboolean gl_blend_enable_cache; + /* PBOs */ /* This can be used to check if a pbo is bound */ CoglBuffer *current_pbo; @@ -143,9 +148,11 @@ typedef struct GLint max_texture_image_units; GLint max_activateable_texture_units; - CoglHandle current_program; + /* Fragment processing programs */ + CoglHandle current_program; + CoglMaterialProgramType current_use_program_type; - GLuint current_gl_program; + GLuint current_gl_program; CoglContextDriver drv; } CoglContext; diff --git a/clutter/cogl/cogl/cogl-debug.c b/clutter/cogl/cogl/cogl-debug.c index df985fa4b..68ca4c38e 100644 --- a/clutter/cogl/cogl/cogl-debug.c +++ b/clutter/cogl/cogl/cogl-debug.c @@ -66,7 +66,8 @@ static const GDebugKey cogl_behavioural_debug_keys[] = { { "disable-atlas", COGL_DEBUG_DISABLE_ATLAS }, { "disable-texturing", COGL_DEBUG_DISABLE_TEXTURING}, { "disable-arbfp", COGL_DEBUG_DISABLE_ARBFP}, - { "disable-glsl", COGL_DEBUG_DISABLE_GLSL} + { "disable-glsl", COGL_DEBUG_DISABLE_GLSL}, + { "disable-blending", COGL_DEBUG_DISABLE_BLENDING} }; static const int n_cogl_behavioural_debug_keys = G_N_ELEMENTS (cogl_behavioural_debug_keys); @@ -123,6 +124,7 @@ _cogl_parse_debug_string (const char *value, OPT ("disable-texturing:", "disable texturing primitives"); OPT ("disable-arbfp:", "disable use of ARBfp"); OPT ("disable-glsl:", "disable use of GLSL"); + OPT ("disable-blending:", "disable use of blending"); OPT ("show-source:", "show generated ARBfp/GLSL"); OPT ("opengl:", "traces some select OpenGL calls"); OPT ("offscreen:", "debug offscreen support"); diff --git a/clutter/cogl/cogl/cogl-debug.h b/clutter/cogl/cogl/cogl-debug.h index 393373791..8341f68a2 100644 --- a/clutter/cogl/cogl/cogl-debug.h +++ b/clutter/cogl/cogl/cogl-debug.h @@ -50,7 +50,8 @@ typedef enum { COGL_DEBUG_DISABLE_TEXTURING = 1 << 19, COGL_DEBUG_DISABLE_ARBFP = 1 << 20, COGL_DEBUG_DISABLE_GLSL = 1 << 21, - COGL_DEBUG_SHOW_SOURCE = 1 << 22 + COGL_DEBUG_SHOW_SOURCE = 1 << 22, + COGL_DEBUG_DISABLE_BLENDING = 1 << 23 } CoglDebugFlags; #ifdef COGL_ENABLE_DEBUG diff --git a/clutter/cogl/cogl/cogl-internal.h b/clutter/cogl/cogl/cogl-internal.h index 7b1253a07..aa32f04c5 100644 --- a/clutter/cogl/cogl/cogl-internal.h +++ b/clutter/cogl/cogl/cogl-internal.h @@ -97,11 +97,10 @@ cogl_gl_error_to_string (GLenum error_code); #endif /* COGL_GL_DEBUG */ -#define COGL_ENABLE_BLEND (1<<1) -#define COGL_ENABLE_ALPHA_TEST (1<<2) -#define COGL_ENABLE_VERTEX_ARRAY (1<<3) -#define COGL_ENABLE_COLOR_ARRAY (1<<4) -#define COGL_ENABLE_BACKFACE_CULLING (1<<5) +#define COGL_ENABLE_ALPHA_TEST (1<<1) +#define COGL_ENABLE_VERTEX_ARRAY (1<<2) +#define COGL_ENABLE_COLOR_ARRAY (1<<3) +#define COGL_ENABLE_BACKFACE_CULLING (1<<4) void _cogl_features_init (void); diff --git a/clutter/cogl/cogl/cogl-journal.c b/clutter/cogl/cogl/cogl-journal.c index 9bedf6b15..e850a2779 100644 --- a/clutter/cogl/cogl/cogl-journal.c +++ b/clutter/cogl/cogl/cogl-journal.c @@ -318,10 +318,6 @@ _cogl_journal_flush_material_and_entries (CoglJournalEntry *batch_start, _cogl_material_flush_gl_state (batch_start->material, &batch_start->flush_options); - /* FIXME: This api is a bit yukky, ideally it will be removed if we - * re-work the _cogl_enable mechanism */ - enable_flags |= _cogl_material_get_cogl_enable_flags (batch_start->material); - if (ctx->enable_backface_culling) enable_flags |= COGL_ENABLE_BACKFACE_CULLING; @@ -356,7 +352,8 @@ compare_entry_materials (CoglJournalEntry *entry0, CoglJournalEntry *entry1) if (_cogl_material_equal (entry0->material, &entry0->flush_options, entry1->material, - &entry1->flush_options)) + &entry1->flush_options, + TRUE)) return TRUE; else return FALSE; diff --git a/clutter/cogl/cogl/cogl-material-private.h b/clutter/cogl/cogl/cogl-material-private.h index 08ea01338..a1029fe84 100644 --- a/clutter/cogl/cogl/cogl-material-private.h +++ b/clutter/cogl/cogl/cogl-material-private.h @@ -3,7 +3,7 @@ * * An object oriented GL/GLES Abstraction/Utility Layer * - * Copyright (C) 2008,2009 Intel Corporation. + * 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 @@ -16,7 +16,8 @@ * 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 . + * License along with this library. If not, see + * . * * * @@ -62,8 +63,9 @@ typedef struct _CoglTextureUnit /* Whether or not the corresponding gl_target has been glEnabled */ gboolean enabled; - /* The GL target currently glEnabled or 0 if .enabled == FALSE */ - GLenum enabled_gl_target; + /* The GL target currently glEnabled or the target last enabled + * if .enabled == FALSE */ + GLenum current_gl_target; /* The raw GL texture object name for which we called glBindTexture when * we flushed the last layer. (NB: The CoglTexture associated @@ -80,13 +82,13 @@ typedef struct _CoglTextureUnit /* We have many components in Cogl that need to temporarily bind arbitrary * textures e.g. to query texture object parameters and since we don't * want that to result in too much redundant reflushing of layer state - * when all that's needed is to re-bind the layers gl_texture we use this - * to track when the .layer_gl_texture state is invalid. + * when all that's needed is to re-bind the layer's gl_texture we use this + * to track when the unit->gl_texture state is out of sync with the GL + * texture object really bound too (GL_TEXTURE0+unit->index). * * XXX: as a further optimization cogl-material.c uses a convention - * of always leaving texture unit 1 active when not dealing with the - * flushing of layer state, so we can assume this is only ever TRUE - * for unit 1. + * of always using texture unit 1 for these transient bindings so we + * can assume this is only ever TRUE for unit 1. */ gboolean dirty_gl_texture; @@ -116,24 +118,16 @@ typedef struct _CoglTextureUnit * invalidated though these flags can be used to optimize the state * flush of the next layer */ - unsigned long layer_differences; + unsigned long layer_changes_since_flush; - /* The options that may have affected how the layer state updated - * this texture unit. */ - gboolean fallback; - gboolean layer0_overridden; - - /* When flushing a layers state, fallback options may mean that a - * different CoglTexture is used than layer->texture. - * - * Once a layers state has been flushed we have to keep track of - * changes to that layer so if we are asked to re-flush the same - * layer later we will know what work is required. This also means - * we need to keep track of changes to the CoglTexture of that layer - * so we need to explicitly keep a reference to the final texture - * chosen. - */ - CoglHandle texture; + /* Whenever a CoglTexture's internal GL texture storage changes + * cogl-material.c is notified with a call to + * _cogl_material_texture_storage_change_notify which inturn sets + * this to TRUE for each texture unit that it is currently bound + * too. When we later come to flush some material state then we will + * always check this to potentially force an update of the texture + * state even if the material hasn't changed. */ + gboolean texture_storage_changed; } CoglTextureUnit; @@ -148,63 +142,50 @@ _cogl_bind_gl_texture_transient (GLenum gl_target, GLuint gl_texture, gboolean is_foreign); -typedef enum _CoglMaterialEqualFlags +#if defined (HAVE_COGL_GL) +/* glsl, arbfp, fixed */ +#define COGL_MATERIAL_N_BACKENDS 3 +#elif defined (HAVE_COGL_GLES2) +/* glsl, fixed */ +#define COGL_MATERIAL_N_BACKENDS 2 +#else /* HAVE_COGL_GLES */ +/* fixed */ +#define COGL_MATERIAL_N_BACKENDS 1 +#endif + +typedef enum { - /* Return FALSE if any component of either material isn't set to its - * default value. (Note: if the materials have corresponding flush - * options indicating that e.g. the material color won't be flushed then - * this will not assert a default color value.) */ - COGL_MATERIAL_EQUAL_FLAGS_ASSERT_ALL_DEFAULTS = 1L<<0, + COGL_MATERIAL_LAYER_STATE_UNIT = 1L<<0, + COGL_MATERIAL_LAYER_STATE_TEXTURE = 1L<<1, + COGL_MATERIAL_LAYER_STATE_FILTERS = 1L<<2, + COGL_MATERIAL_LAYER_STATE_WRAP_MODES = 1L<<3, -} CoglMaterialEqualFlags; + COGL_MATERIAL_LAYER_STATE_COMBINE = 1L<<4, + COGL_MATERIAL_LAYER_STATE_COMBINE_CONSTANT = 1L<<5, + COGL_MATERIAL_LAYER_STATE_USER_MATRIX = 1L<<6, -typedef enum _CoglMaterialLayerDifferenceFlags + /* COGL_MATERIAL_LAYER_STATE_TEXTURE_INTERN = 1L<<7, */ + + COGL_MATERIAL_LAYER_STATE_ALL_SPARSE = + COGL_MATERIAL_LAYER_STATE_UNIT | + COGL_MATERIAL_LAYER_STATE_TEXTURE | + COGL_MATERIAL_LAYER_STATE_FILTERS | + COGL_MATERIAL_LAYER_STATE_WRAP_MODES | + COGL_MATERIAL_LAYER_STATE_COMBINE | + COGL_MATERIAL_LAYER_STATE_COMBINE_CONSTANT | + COGL_MATERIAL_LAYER_STATE_USER_MATRIX, + + COGL_MATERIAL_LAYER_STATE_NEEDS_BIG_STATE = + COGL_MATERIAL_LAYER_STATE_COMBINE | + COGL_MATERIAL_LAYER_STATE_COMBINE_CONSTANT | + COGL_MATERIAL_LAYER_STATE_USER_MATRIX + +} CoglMaterialLayerState; + +typedef struct { - COGL_MATERIAL_LAYER_DIFFERENCE_TEXTURE = 1L<<0, - COGL_MATERIAL_LAYER_DIFFERENCE_COMBINE = 1L<<1, - COGL_MATERIAL_LAYER_DIFFERENCE_COMBINE_CONSTANT = 1L<<2, - COGL_MATERIAL_LAYER_DIFFERENCE_USER_MATRIX = 1L<<3, - COGL_MATERIAL_LAYER_DIFFERENCE_FILTERS = 1L<<4 -} CoglMaterialLayerDifferenceFlags; - -typedef enum _CoglMaterialLayerChangeFlags -{ - COGL_MATERIAL_LAYER_CHANGE_TEXTURE = 1L<<0, - COGL_MATERIAL_LAYER_CHANGE_COMBINE = 1L<<1, - COGL_MATERIAL_LAYER_CHANGE_COMBINE_CONSTANT = 1L<<2, - COGL_MATERIAL_LAYER_CHANGE_USER_MATRIX = 1L<<3, - COGL_MATERIAL_LAYER_CHANGE_FILTERS = 1L<<4, - - COGL_MATERIAL_LAYER_CHANGE_TEXTURE_INTERN = 1L<<5, - COGL_MATERIAL_LAYER_CHANGE_UNIT = 1L<<6 -} CoglMaterialLayerChangeFlags; - -struct _CoglMaterialLayer -{ - CoglHandleObject _parent; - - /* Parent material */ - CoglMaterial *material; - - unsigned int index; /*!< lowest index is blended first then others on - top */ - - int unit_index; - - unsigned long differences; - - CoglHandle texture; /*!< The texture for this layer, or - COGL_INVALID_HANDLE for an empty layer */ - - CoglMaterialFilter mag_filter; - CoglMaterialFilter min_filter; - - CoglMaterialWrapMode wrap_mode_s; - CoglMaterialWrapMode wrap_mode_t; - CoglMaterialWrapMode wrap_mode_r; - - /* Determines how the color of individual texture fragments - * are calculated. */ + /* The texture combine state determines how the color of individual + * texture fragments are calculated. */ GLint texture_combine_rgb_func; GLint texture_combine_rgb_src[3]; GLint texture_combine_rgb_op[3]; @@ -213,104 +194,363 @@ struct _CoglMaterialLayer GLint texture_combine_alpha_src[3]; GLint texture_combine_alpha_op[3]; - GLfloat texture_combine_constant[4]; - - /* TODO: Support purely GLSL based material layers */ + float texture_combine_constant[4]; + /* The texture matrix dscribes how to transform texture coordinates */ CoglMatrix matrix; +} CoglMaterialLayerBigState; + +struct _CoglMaterialLayer +{ + /* XXX: Please think twice about adding members that *have* be + * initialized during a _cogl_material_layer_copy. We are aiming + * to have copies be as cheap as possible and copies may be + * done by the primitives APIs which means they may happen + * in performance critical code paths. + * + * XXX: If you are extending the state we track please consider if + * the state is expected to vary frequently across many materials or + * if the state can be shared among many derived materials instead. + * This will determine if the state should be added directly to this + * structure which will increase the memory overhead for *all* + * layers or if instead it can go under ->big_state. + */ + + /* the parent in terms of class hierarchy */ + CoglHandleObject _parent; + + /* Some layers have a material owner, which is to say that the layer + * is referenced in that materials->layer_differences list. A layer + * doesn't always have an owner and may simply be an ancestor for + * other layers that keeps track of some shared state. */ + CoglMaterial *owner; + + /* Layers are sparse structures defined as a diff against + * their parent... */ + CoglMaterialLayer *parent; + + /* As an optimization for creating leaf node layers (the most + * common) we don't require any list node allocations to link + * to a single descendant. */ + CoglMaterialLayer *first_child; + + /* Layers are sparse structures defined as a diff against + * their parent and may have multiple children which depend + * on them to define the values of properties which they don't + * change. */ + GList *children; + + /* The lowest index is blended first then others on top */ + int index; + /* Different material backends (GLSL/ARBfp/Fixed Function) may - * want to associate private data with a layer... */ - void *backend_priv; + * want to associate private data with a layer... + * + * NB: we have per backend pointers because a layer may be + * associated with multiple materials with different backends. + */ + void *backend_priv[COGL_MATERIAL_N_BACKENDS]; + + /* A mask of which state groups are different in this layer + * in comparison to its parent. */ + unsigned long differences; + + /* Common differences + * + * As a basic way to reduce memory usage we divide the layer + * state into two groups; the minimal state modified in 90% of + * all layers and the rest, so that the second group can + * be allocated dynamically when required. + */ + + /* Each layer is directly associated with a single texture unit */ + int unit_index; + + /* The texture for this layer, or COGL_INVALID_HANDLE for an empty + * layer */ + CoglHandle texture; + gboolean texture_overridden; + /* If ->texture_overridden == TRUE then the texture is instead + * defined by these... */ + GLuint slice_gl_texture; + GLenum slice_gl_target; + + CoglMaterialFilter mag_filter; + CoglMaterialFilter min_filter; + + CoglMaterialWrapMode wrap_mode_s; + CoglMaterialWrapMode wrap_mode_t; + CoglMaterialWrapMode wrap_mode_r; + + /* Infrequent differences aren't currently tracked in + * a separate, dynamically allocated structure as they are + * for materials... */ + CoglMaterialLayerBigState *big_state; + + /* bitfields */ + + /* Determines if layer->first_child and layer->children are + * initialized pointers. */ + unsigned int has_children:1; + + /* Determines if layer->big_state is valid */ + unsigned int has_big_state:1; + }; -typedef enum _CoglMaterialFlags +/* Used in material->differences masks and for notifying material + * state changes... */ +typedef enum _CoglMaterialState { - COGL_MATERIAL_FLAG_DEFAULT_COLOR = 1L<<1, - COGL_MATERIAL_FLAG_DEFAULT_GL_MATERIAL = 1L<<2, - COGL_MATERIAL_FLAG_DEFAULT_ALPHA_FUNC = 1L<<3, - COGL_MATERIAL_FLAG_ENABLE_BLEND = 1L<<4, - COGL_MATERIAL_FLAG_DEFAULT_BLEND = 1L<<5, - COGL_MATERIAL_FLAG_DEFAULT_USER_SHADER = 1L<<6, - COGL_MATERIAL_FLAG_DEFAULT_LAYERS = 1L<<7 -} CoglMaterialFlags; + COGL_MATERIAL_STATE_COLOR = 1L<<0, + COGL_MATERIAL_STATE_BLEND_ENABLE = 1L<<1, + COGL_MATERIAL_STATE_LAYERS = 1L<<2, -/* This defines the initialization state for - * ctx->current_material_flags which should result in the first - * material flush explicitly initializing everything - */ -#define COGL_MATERIAL_FLAGS_INIT \ - COGL_MATERIAL_FLAG_DEFAULT_USER_SHADER + COGL_MATERIAL_STATE_LIGHTING = 1L<<3, + COGL_MATERIAL_STATE_ALPHA_FUNC = 1L<<4, + COGL_MATERIAL_STATE_BLEND = 1L<<5, + COGL_MATERIAL_STATE_USER_SHADER = 1L<<6, -typedef enum _CoglMaterialChangeFlag + COGL_MATERIAL_STATE_REAL_BLEND_ENABLE = 1L<<7, + + COGL_MATERIAL_STATE_ALL_SPARSE = + COGL_MATERIAL_STATE_COLOR | + COGL_MATERIAL_STATE_BLEND_ENABLE | + COGL_MATERIAL_STATE_LAYERS | + COGL_MATERIAL_STATE_LIGHTING | + COGL_MATERIAL_STATE_ALPHA_FUNC | + COGL_MATERIAL_STATE_BLEND | + COGL_MATERIAL_STATE_USER_SHADER, + + COGL_MATERIAL_STATE_AFFECTS_BLENDING = + COGL_MATERIAL_STATE_COLOR | + COGL_MATERIAL_STATE_BLEND_ENABLE | + COGL_MATERIAL_STATE_LAYERS | + COGL_MATERIAL_STATE_LIGHTING | + COGL_MATERIAL_STATE_BLEND | + COGL_MATERIAL_STATE_USER_SHADER, + + COGL_MATERIAL_STATE_NEEDS_BIG_STATE = + COGL_MATERIAL_STATE_LIGHTING | + COGL_MATERIAL_STATE_ALPHA_FUNC | + COGL_MATERIAL_STATE_BLEND | + COGL_MATERIAL_STATE_USER_SHADER + +} CoglMaterialState; + +typedef enum { - COGL_MATERIAL_CHANGE_COLOR = 1L<<1, - COGL_MATERIAL_CHANGE_GL_MATERIAL = 1L<<2, - COGL_MATERIAL_CHANGE_ALPHA_FUNC = 1L<<3, - COGL_MATERIAL_CHANGE_ENABLE_BLEND = 1L<<4, - COGL_MATERIAL_CHANGE_BLEND = 1L<<5, - COGL_MATERIAL_CHANGE_USER_SHADER = 1L<<6, - COGL_MATERIAL_CHANGE_LAYERS = 1L<<7 -} CoglMaterialChangeFlag; + COGL_MATERIAL_LIGHTING_STATE_PROPERTY_AMBIENT = 1, + COGL_MATERIAL_LIGHTING_STATE_PROPERTY_DIFFUSE, + COGL_MATERIAL_LIGHTING_STATE_PROPERTY_SPECULAR, + COGL_MATERIAL_LIGHTING_STATE_PROPERTY_EMISSION, + COGL_MATERIAL_LIGHTING_STATE_PROPERTY_SHININESS +} CoglMaterialLightingStateProperty; -struct _CoglMaterial +typedef struct { - CoglHandleObject _parent; - unsigned long journal_ref_count; - - int backend; - - unsigned long flags; - - /* If no lighting is enabled; this is the basic material color */ - GLubyte unlit[4]; - /* Standard OpenGL lighting model attributes */ - GLfloat ambient[4]; - GLfloat diffuse[4]; - GLfloat specular[4]; - GLfloat emission[4]; - GLfloat shininess; + float ambient[4]; + float diffuse[4]; + float specular[4]; + float emission[4]; + float shininess; +} CoglMaterialLightingState; +typedef struct +{ /* Determines what fragments are discarded based on their alpha */ CoglMaterialAlphaFunc alpha_func; GLfloat alpha_func_reference; +} CoglMaterialAlphaFuncState; +typedef enum _CoglMaterialBlendEnable +{ + /* XXX: we want to detect users mistakenly using TRUE or FALSE + * so start the enum at 2. */ + COGL_MATERIAL_BLEND_ENABLE_ENABLED = 2, + COGL_MATERIAL_BLEND_ENABLE_DISABLED, + COGL_MATERIAL_BLEND_ENABLE_AUTOMATIC +} CoglMaterialBlendEnable; + +typedef struct +{ /* Determines how this material is blended with other primitives */ #ifndef HAVE_COGL_GLES - GLenum blend_equation_rgb; - GLenum blend_equation_alpha; - GLint blend_src_factor_alpha; - GLint blend_dst_factor_alpha; - GLfloat blend_constant[4]; + GLenum blend_equation_rgb; + GLenum blend_equation_alpha; + GLint blend_src_factor_alpha; + GLint blend_dst_factor_alpha; + CoglColor blend_constant; #endif - GLint blend_src_factor_rgb; - GLint blend_dst_factor_rgb; + GLint blend_src_factor_rgb; + GLint blend_dst_factor_rgb; +} CoglMaterialBlendState; +typedef struct +{ + CoglMaterialLightingState lighting_state; + CoglMaterialAlphaFuncState alpha_state; + CoglMaterialBlendState blend_state; CoglHandle user_program; +} CoglMaterialBigState; - GList *layers; - unsigned int n_layers; +typedef enum +{ + COGL_MATERIAL_FLAG_DIRTY_LAYERS_CACHE = 1L<<0, + COGL_MATERIAL_FLAG_DIRTY_GET_LAYERS_LIST = 1L<<1 +} CoglMaterialFlag; - void *backend_priv; +typedef struct +{ + CoglMaterial *owner; + CoglMaterialLayer *layer; +} CoglMaterialLayerCacheEntry; + +struct _CoglMaterial +{ + /* XXX: Please think twice about adding members that *have* be + * initialized during a cogl_material_copy. We are aiming to have + * copies be as cheap as possible and copies may be done by the + * primitives APIs which means they may happen in performance + * critical code paths. + * + * XXX: If you are extending the state we track please consider if + * the state is expected to vary frequently across many materials or + * if the state can be shared among many derived materials instead. + * This will determine if the state should be added directly to this + * structure which will increase the memory overhead for *all* + * materials or if instead it can go under ->big_state. + */ + + /* the parent in terms of class hierarchy */ + CoglHandleObject _parent; + + /* We need to track if a material is referenced in the journal + * because we can't allow modification to these materials without + * flushing the journal first */ + unsigned long journal_ref_count; + + /* Materials are sparse structures defined as a diff against + * their parent. */ + CoglMaterial *parent; + + /* As an optimization for creating leaf node materials (the most + * common) we don't require any list node allocations to link + * to a single descendant. + * + * Only valid if ->has_children bitfield is set */ + CoglMaterial *first_child; + + /* Materials are sparse structures defined as a diff against + * their parent and may have multiple children which depend + * on them to define the values of properties which they don't + * change. + * + * Only valid if ->has_children bitfield is set */ + GList *children; + + /* A mask of which sparse state groups are different in this + * material in comparison to its parent. */ + unsigned long differences; + + /* The fragment processing backend identified by the ->backend + * bitfield can associate private data with a material. */ + void *backend_priv; + + /* This is the primary color of the material. + * + * This is a sparse property, ref COGL_MATERIAL_STATE_COLOR */ + CoglColor color; + + /* A material may be made up with multiple layers used to combine + * textures together. + * + * This is sparse state, ref COGL_MATERIAL_STATE_LAYERS */ + GList *layer_differences; + unsigned int n_layers; + + /* As a basic way to reduce memory usage we divide the material + * state into two groups; the minimal state modified in 90% of + * all materials and the rest, so that the second group can + * be allocated dynamically when required... */ + CoglMaterialBigState *big_state; + + /* Cached state... */ + + /* A cached, complete list of the layers this material depends + * on sorted by layer->unit_index. */ + CoglMaterialLayer **layers_cache; + /* To avoid a separate ->layers_cache allocation for common + * materials with only a few layers... */ + CoglMaterialLayer *short_layers_cache[3]; + + /* The deprecated cogl_material_get_layers() API returns a + * const GList of layers, which we track here... */ + GList *deprecated_get_layers_list; + + /* XXX: consider adding an authorities cache to speed up sparse + * property value lookups: + * CoglMaterial *authorities_cache[COGL_MATERIAL_N_SPARSE_PROPERTIES]; + * and corresponding authorities_cache_dirty:1 bitfield + */ + + /* bitfields */ + + /* Determines if material->big_state is valid */ + unsigned int has_big_state:1; + + /* By default blending is enabled automatically depending on the + * unlit color, the lighting colors or the texture format. The user + * can override this to explicitly enable or disable blending. + * + * This is a sparse property */ + unsigned int blend_enable:3; + + /* There are many factors that can determine if we need to enable + * blending, this holds our final decision */ + unsigned int real_blend_enable:1; + + /* Determines if material->first_child and material->children are + * initialized pointers. */ + unsigned int has_children:1; + + unsigned int layers_cache_dirty:1; + unsigned int deprecated_get_layers_list_dirty:1; + + /* There are multiple fragment processing backends for CoglMaterial, + * glsl, arbfp and fixed. This identifies the backend being used for + * the material and any private state the backend has associated + * with the material. */ + unsigned int backend:3; + + /* Determines if ->backend_priv has been initialized */ + unsigned int backend_priv_set:1; }; typedef struct _CoglMaterialBackend { int (*get_max_texture_units) (void); - gboolean (*start) (CoglMaterial *material); - gboolean (*add_layer) (CoglMaterialLayer *layer); + gboolean (*start) (CoglMaterial *material, + int n_layers, + unsigned long materials_difference); + gboolean (*add_layer) (CoglMaterial *material, + CoglMaterialLayer *layer, + unsigned long layers_difference); gboolean (*passthrough) (CoglMaterial *material); - gboolean (*end) (CoglMaterial *material); + gboolean (*end) (CoglMaterial *material, + unsigned long materials_difference); - void (*material_change_notify) (CoglMaterial *material, - unsigned long changes, - GLubyte *new_color); - void (*layer_change_notify) (CoglMaterialLayer *layer, - unsigned long changes); + void (*material_pre_change_notify) (CoglMaterial *material, + CoglMaterialState change, + const CoglColor *new_color); + void (*layer_pre_change_notify) (CoglMaterialLayer *layer, + CoglMaterialLayerState change); void (*free_priv) (CoglMaterial *material); + void (*free_layer_priv) (CoglMaterialLayer *layer); } CoglMaterialBackend; typedef enum @@ -320,6 +560,12 @@ typedef enum COGL_MATERIAL_PROGRAM_TYPE_FIXED } CoglMaterialProgramType; +void +_cogl_material_init_default_material (void); + +void +_cogl_material_init_default_layers (void); + /* * SECTION:cogl-material-internals * @short_description: Functions for creating custom primitives that make use @@ -331,31 +577,8 @@ typedef enum * able to fill your geometry according to a given Cogl material. */ -/* - * _cogl_material_init_default_material: - * - * This initializes the first material owned by the Cogl context. All - * subsequently instantiated materials created via the cogl_material_new() - * API will initially be a copy of this material. - */ -void -_cogl_material_init_default_material (void); - -/* - * cogl_material_get_cogl_enable_flags: - * @material: A CoglMaterial object - * - * This determines what flags need to be passed to cogl_enable before this - * material can be used. Normally you shouldn't need to use this function - * directly since Cogl will do this internally, but if you are developing - * custom primitives directly with OpenGL you may want to use this. - * - * Note: This API is hopfully just a stop-gap solution. Ideally cogl_enable - * will be replaced. - */ -/* TODO: find a nicer solution! */ -unsigned long -_cogl_material_get_cogl_enable_flags (CoglHandle handle); +gboolean +_cogl_material_get_real_blend_enabled (CoglHandle handle); gboolean _cogl_material_layer_has_user_matrix (CoglHandle layer_handle); @@ -397,11 +620,35 @@ typedef enum _CoglMaterialFlushFlag COGL_MATERIAL_FLUSH_WRAP_MODE_OVERRIDES = 1L<<4 } CoglMaterialFlushFlag; -/* These constants are used to fill in wrap_mode_overrides */ -#define COGL_MATERIAL_WRAP_MODE_OVERRIDE_NONE 0 /* no override */ -#define COGL_MATERIAL_WRAP_MODE_OVERRIDE_REPEAT 1 -#define COGL_MATERIAL_WRAP_MODE_OVERRIDE_CLAMP_TO_EDGE 2 -#define COGL_MATERIAL_WRAP_MODE_OVERRIDE_CLAMP_TO_BORDER 3 +/* This isn't defined in the GLES headers */ +#ifndef GL_CLAMP_TO_BORDER +#define GL_CLAMP_TO_BORDER 0x812d +#endif + +/* GL_ALWAYS is just used here as a value that is known not to clash + * with any valid GL wrap modes. + * + * XXX: keep the values in sync with the CoglMaterialWrapMode enum + * so no conversion is actually needed. + */ +typedef enum _CoglMaterialWrapModeInternal +{ + COGL_MATERIAL_WRAP_MODE_INTERNAL_REPEAT = GL_REPEAT, + COGL_MATERIAL_WRAP_MODE_INTERNAL_CLAMP_TO_EDGE = GL_CLAMP_TO_EDGE, + COGL_MATERIAL_WRAP_MODE_INTERNAL_CLAMP_TO_BORDER = GL_CLAMP_TO_BORDER, + COGL_MATERIAL_WRAP_MODE_INTERNAL_AUTOMATIC = GL_ALWAYS +} CoglMaterialWrapModeInternal; + +typedef enum _CoglMaterialWrapModeOverride +{ + COGL_MATERIAL_WRAP_MODE_OVERRIDE_NONE = 0, + COGL_MATERIAL_WRAP_MODE_OVERRIDE_REPEAT = + COGL_MATERIAL_WRAP_MODE_INTERNAL_REPEAT, + COGL_MATERIAL_WRAP_MODE_OVERRIDE_CLAMP_TO_EDGE = + COGL_MATERIAL_WRAP_MODE_INTERNAL_CLAMP_TO_EDGE, + COGL_MATERIAL_WRAP_MODE_OVERRIDE_CLAMP_TO_BORDER = + COGL_MATERIAL_WRAP_MODE_INTERNAL_CLAMP_TO_BORDER, +} CoglMaterialWrapModeOverride; /* There can't be more than 32 layers because we need to fit a bitmask of the layers into a guint32 */ @@ -411,9 +658,9 @@ typedef struct _CoglMaterialWrapModeOverrides { struct { - unsigned long s : 2; - unsigned long t : 2; - unsigned long r : 2; + CoglMaterialWrapModeOverride s; + CoglMaterialWrapModeOverride t; + CoglMaterialWrapModeOverride r; } values[COGL_MATERIAL_MAX_LAYERS]; } CoglMaterialWrapModeOverrides; @@ -443,7 +690,8 @@ gboolean _cogl_material_equal (CoglHandle material0_handle, CoglMaterialFlushOptions *material0_flush_options, CoglHandle material1_handle, - CoglMaterialFlushOptions *material1_flush_options); + CoglMaterialFlushOptions *material1_flush_options, + gboolean skip_gl_color); CoglHandle _cogl_material_journal_ref (CoglHandle material_handle); @@ -468,11 +716,21 @@ _cogl_material_set_user_program (CoglHandle handle, void _cogl_delete_gl_texture (GLuint gl_texture); +void +_cogl_material_texture_storage_change_notify (CoglHandle texture); + void _cogl_material_apply_legacy_state (CoglHandle handle); void _cogl_gl_use_program_wrapper (GLuint program); +CoglMaterialBlendEnable +_cogl_material_get_blend_enabled (CoglHandle handle); + +void +_cogl_material_set_blend_enabled (CoglHandle handle, + CoglMaterialBlendEnable enable); + #endif /* __COGL_MATERIAL_PRIVATE_H */ diff --git a/clutter/cogl/cogl/cogl-material.c b/clutter/cogl/cogl/cogl-material.c index 11cd52e75..f8f366fca 100644 --- a/clutter/cogl/cogl/cogl-material.c +++ b/clutter/cogl/cogl/cogl-material.c @@ -16,7 +16,8 @@ * 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 . + * License along with this library. If not, see + * . * * * @@ -37,6 +38,8 @@ #include "cogl-texture-private.h" #include "cogl-blend-string.h" #include "cogl-journal-private.h" +#include "cogl-color-private.h" +#include "cogl-profile.h" #ifndef HAVE_COGL_GLES #include "cogl-program.h" #endif @@ -74,6 +77,9 @@ #define GL_CLAMP_TO_BORDER 0x812d #endif +#define COGL_MATERIAL(X) ((CoglMaterial *)(X)) +#define COGL_MATERIAL_LAYER(X) ((CoglMaterialLayer *)(X)) + typedef struct _CoglMaterialBackendARBfpPrivate { GString *source; @@ -82,10 +88,18 @@ typedef struct _CoglMaterialBackendARBfpPrivate int next_constant_id; } CoglMaterialBackendARBfpPrivate; -static CoglHandle _cogl_material_layer_copy (CoglHandle layer_handle); +typedef gboolean (*CoglMaterialStateComparitor) (CoglMaterial *authority0, + CoglMaterial *authority1); + +static CoglMaterialLayer *_cogl_material_layer_copy (CoglMaterialLayer *layer); static void _cogl_material_free (CoglMaterial *tex); static void _cogl_material_layer_free (CoglMaterialLayer *layer); +static void _cogl_material_add_layer_difference (CoglMaterial *material, + CoglMaterialLayer *layer, + gboolean inc_n_layers); +static void handle_automatic_blend_enable (CoglMaterial *material, + CoglMaterialState changes); #if defined (HAVE_COGL_GL) @@ -99,6 +113,7 @@ static const CoglMaterialBackend *backends[] = &_cogl_material_arbfp_backend, &_cogl_material_fixed_backend }; +/* NB: material->backend is currently a 3bit unsigned int bitfield */ #define COGL_MATERIAL_BACKEND_GLSL 0 #define COGL_MATERIAL_BACKEND_ARBFP 1 #define COGL_MATERIAL_BACKEND_FIXED 2 @@ -130,12 +145,185 @@ static const CoglMaterialBackend *backends[] = #endif #define COGL_MATERIAL_BACKEND_DEFAULT 0 -#define COGL_MATERIAL_BACKEND_UNDEFINED -1 +#define COGL_MATERIAL_BACKEND_UNDEFINED 3 COGL_HANDLE_DEFINE (Material, material); COGL_HANDLE_DEFINE (MaterialLayer, material_layer); -/* #define DISABLE_MATERIAL_CACHE 1 */ +static void +texture_unit_init (CoglTextureUnit *unit, int index_) +{ + unit->index = index_; + unit->enabled = FALSE; + unit->current_gl_target = 0; + unit->gl_texture = 0; + unit->is_foreign = FALSE; + unit->dirty_gl_texture = FALSE; + unit->matrix_stack = _cogl_matrix_stack_new (); + + unit->layer = NULL; + unit->layer_changes_since_flush = 0; + unit->texture_storage_changed = FALSE; +} + +static void +texture_unit_free (CoglTextureUnit *unit) +{ + if (unit->layer) + cogl_handle_unref (unit->layer); + _cogl_matrix_stack_destroy (unit->matrix_stack); +} + +CoglTextureUnit * +_cogl_get_texture_unit (int index_) +{ + _COGL_GET_CONTEXT (ctx, NULL); + + if (ctx->texture_units->len < (index_ + 1)) + { + int i; + int prev_len = ctx->texture_units->len; + ctx->texture_units = g_array_set_size (ctx->texture_units, index_ + 1); + for (i = prev_len; i <= index_; i++) + { + CoglTextureUnit *unit = + &g_array_index (ctx->texture_units, CoglTextureUnit, i); + + texture_unit_init (unit, i); + } + } + + return &g_array_index (ctx->texture_units, CoglTextureUnit, index_); +} + +void +_cogl_destroy_texture_units (void) +{ + int i; + + _COGL_GET_CONTEXT (ctx, NO_RETVAL); + + for (i = 0; i < ctx->texture_units->len; i++) + { + CoglTextureUnit *unit = + &g_array_index (ctx->texture_units, CoglTextureUnit, i); + texture_unit_free (unit); + } + g_array_free (ctx->texture_units, TRUE); +} + +static void +set_active_texture_unit (int unit_index) +{ + _COGL_GET_CONTEXT (ctx, NO_RETVAL); + + if (ctx->active_texture_unit != unit_index) + { + GE (glActiveTexture (GL_TEXTURE0 + unit_index)); + ctx->active_texture_unit = unit_index; + } +} + +/* Note: _cogl_bind_gl_texture_transient conceptually has slightly + * different semantics to OpenGL's glBindTexture because Cogl never + * cares about tracking multiple textures bound to different targets + * on the same texture unit. + * + * glBindTexture lets you bind multiple textures to a single texture + * unit if they are bound to different targets. So it does something + * like: + * unit->current_texture[target] = texture; + * + * Cogl only lets you associate one texture with the currently active + * texture unit, so the target is basically a redundant parameter + * that's implicitly set on that texture. + * + * Technically this is just a thin wrapper around glBindTexture so + * actually it does have the GL semantics but it seems worth + * mentioning the conceptual difference in case anyone wonders why we + * don't associate the gl_texture with a gl_target in the + * CoglTextureUnit. + */ +void +_cogl_bind_gl_texture_transient (GLenum gl_target, + GLuint gl_texture, + gboolean is_foreign) +{ + CoglTextureUnit *unit; + + _COGL_GET_CONTEXT (ctx, NO_RETVAL); + + /* We choose to always make texture unit 1 active for transient + * binds so that in the common case where multitexturing isn't used + * we can simply ignore the state of this texture unit. Notably we + * didn't use a large texture unit (.e.g. (GL_MAX_TEXTURE_UNITS - 1) + * in case the driver doesn't have a sparse data structure for + * texture units. + */ + set_active_texture_unit (1); + unit = _cogl_get_texture_unit (1); + + /* NB: If we have previously bound a foreign texture to this texture + * unit we don't know if that texture has since been deleted and we + * are seeing the texture name recycled */ + if (unit->gl_texture == gl_texture && + !unit->dirty_gl_texture && + !unit->is_foreign) + return; + + GE (glBindTexture (gl_target, gl_texture)); + + unit->dirty_gl_texture = TRUE; + unit->is_foreign = is_foreign; +} + +void +_cogl_delete_gl_texture (GLuint gl_texture) +{ + int i; + + _COGL_GET_CONTEXT (ctx, NO_RETVAL); + + for (i = 0; i < ctx->texture_units->len; i++) + { + CoglTextureUnit *unit = + &g_array_index (ctx->texture_units, CoglTextureUnit, i); + + if (unit->gl_texture == gl_texture) + { + unit->gl_texture = 0; + unit->dirty_gl_texture = FALSE; + } + } + + GE (glDeleteTextures (1, &gl_texture)); +} + +/* Whenever the underlying GL texture storage of a CoglTexture is + * changed (e.g. due to migration out of a texture atlas) then we are + * notified. This lets us ensure that we reflush that texture's state + * if it reused again with the same texture unit. + */ +void +_cogl_material_texture_storage_change_notify (CoglHandle texture) +{ + int i; + + _COGL_GET_CONTEXT (ctx, NO_RETVAL); + + for (i = 0; i < ctx->texture_units->len; i++) + { + CoglTextureUnit *unit = + &g_array_index (ctx->texture_units, CoglTextureUnit, i); + + if (unit->layer && + unit->layer->texture == texture) + unit->texture_storage_changed = TRUE; + + /* NB: the texture may be bound to multiple texture units so + * we continue to check the rest */ + } +} GQuark _cogl_material_error_quark (void) @@ -143,75 +331,130 @@ _cogl_material_error_quark (void) return g_quark_from_static_string ("cogl-material-error-quark"); } +/* + * This initializes the first material owned by the Cogl context. All + * subsequently instantiated materials created via the cogl_material_new() + * API will initially be a copy of this material. + * + * The default material is the topmost ancester for all materials. + */ void _cogl_material_init_default_material (void) { /* Create new - blank - material */ CoglMaterial *material = g_slice_new0 (CoglMaterial); - GLubyte *unlit = material->unlit; - GLfloat *ambient = material->ambient; - GLfloat *diffuse = material->diffuse; - GLfloat *specular = material->specular; - GLfloat *emission = material->emission; + CoglMaterialBigState *big_state = g_slice_new0 (CoglMaterialBigState); + CoglMaterialLightingState *lighting_state = &big_state->lighting_state; + CoglMaterialAlphaFuncState *alpha_state = &big_state->alpha_state; + CoglMaterialBlendState *blend_state = &big_state->blend_state; _COGL_GET_CONTEXT (ctx, NO_RETVAL); + material->journal_ref_count = 0; + material->parent = NULL; material->backend = COGL_MATERIAL_BACKEND_UNDEFINED; + material->differences = COGL_MATERIAL_STATE_ALL_SPARSE; + + material->real_blend_enable = FALSE; + + material->blend_enable = COGL_MATERIAL_BLEND_ENABLE_AUTOMATIC; + material->layer_differences = NULL; + material->n_layers = 0; + + material->big_state = big_state; + material->has_big_state = TRUE; /* Use the same defaults as the GL spec... */ - unlit[0] = 0xff; unlit[1] = 0xff; unlit[2] = 0xff; unlit[3] = 0xff; - material->flags |= COGL_MATERIAL_FLAG_DEFAULT_COLOR; + cogl_color_init_from_4ub (&material->color, 0xff, 0xff, 0xff, 0xff); /* Use the same defaults as the GL spec... */ - ambient[0] = 0.2; ambient[1] = 0.2; ambient[2] = 0.2; ambient[3] = 1.0; - diffuse[0] = 0.8; diffuse[1] = 0.8; diffuse[2] = 0.8; diffuse[3] = 1.0; - specular[0] = 0; specular[1] = 0; specular[2] = 0; specular[3] = 1.0; - emission[0] = 0; emission[1] = 0; emission[2] = 0; emission[3] = 1.0; - material->flags |= COGL_MATERIAL_FLAG_DEFAULT_GL_MATERIAL; + 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; /* Use the same defaults as the GL spec... */ - material->alpha_func = COGL_MATERIAL_ALPHA_FUNC_ALWAYS; - material->alpha_func_reference = 0.0; - material->flags |= COGL_MATERIAL_FLAG_DEFAULT_ALPHA_FUNC; + alpha_state->alpha_func = COGL_MATERIAL_ALPHA_FUNC_ALWAYS; + alpha_state->alpha_func_reference = 0.0; /* Not the same as the GL default, but seems saner... */ #ifndef HAVE_COGL_GLES - material->blend_equation_rgb = GL_FUNC_ADD; - material->blend_equation_alpha = GL_FUNC_ADD; - material->blend_src_factor_alpha = GL_ONE; - material->blend_dst_factor_alpha = GL_ONE_MINUS_SRC_ALPHA; - material->blend_constant[0] = 0; - material->blend_constant[1] = 0; - material->blend_constant[2] = 0; - material->blend_constant[3] = 0; + 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 - material->blend_src_factor_rgb = GL_ONE; - material->blend_dst_factor_rgb = GL_ONE_MINUS_SRC_ALPHA; - material->flags |= COGL_MATERIAL_FLAG_DEFAULT_BLEND; + blend_state->blend_src_factor_rgb = GL_ONE; + blend_state->blend_dst_factor_rgb = GL_ONE_MINUS_SRC_ALPHA; - material->user_program = COGL_INVALID_HANDLE; - material->flags |= COGL_MATERIAL_FLAG_DEFAULT_USER_SHADER; - - material->layers = NULL; - material->n_layers = 0; - material->flags |= COGL_MATERIAL_FLAG_DEFAULT_LAYERS; + big_state->user_program = COGL_INVALID_HANDLE; ctx->default_material = _cogl_material_handle_new (material); } +/* XXX: Always have an eye out for opportunities to lower the cost of + * cogl_material_copy. */ CoglHandle cogl_material_copy (CoglHandle handle) { + CoglMaterial *src = COGL_MATERIAL (handle); CoglMaterial *material = g_slice_new (CoglMaterial); - GList *l; - _COGL_GET_CONTEXT (ctx, COGL_INVALID_HANDLE); + cogl_handle_ref (handle); - memcpy (material, handle, sizeof (CoglMaterial)); + material->_parent = src->_parent; - material->layers = g_list_copy (material->layers); - for (l = material->layers; l; l = l->next) - l->data = _cogl_material_layer_copy (l->data); + material->journal_ref_count = 0; + + material->parent = cogl_handle_ref (src); + if (src->has_children) + src->children = g_list_prepend (src->children, material); + else + { + src->has_children = TRUE; + src->first_child = material; + src->children = NULL; + } + + material->has_children = FALSE; + + material->differences = 0; + + material->has_big_state = FALSE; + + /* NB: real_blend_enable isn't a sparse property, it's valid for + * every material node so we have fast access to it. */ + material->real_blend_enable = src->real_blend_enable; + + /* XXX: + * consider generalizing the idea of "cached" properties. These + * would still have an authority like other sparse properties but + * you wouldn't have to walk up the ancestry to find the authority + * because the value would be cached directly in each material. + */ + + material->layers_cache_dirty = TRUE; + material->deprecated_get_layers_list_dirty = TRUE; + + material->backend = src->backend; + material->backend_priv_set = FALSE; return _cogl_material_handle_new (material); } @@ -224,6 +467,33 @@ cogl_material_new (void) return cogl_material_copy (ctx->default_material); } +static void +_cogl_material_unparent (CoglMaterial *material) +{ + CoglMaterial *parent = material->parent; + + if (parent == NULL) + return; + + g_return_if_fail (parent->has_children); + + if (parent->first_child == material) + { + 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, material); + + cogl_handle_unref (parent); +} + static void _cogl_material_backend_free_priv (CoglMaterial *material) { @@ -235,97 +505,566 @@ _cogl_material_backend_free_priv (CoglMaterial *material) static void _cogl_material_free (CoglMaterial *material) { - _COGL_GET_CONTEXT (ctx, NO_RETVAL); - _cogl_material_backend_free_priv (material); - /* Invalidate the ->current_material reference to this material since - * it will no longer represent the current state. - * - * NB: we would also invalidate this if the material we being - * modified. - */ - ctx->current_material = COGL_INVALID_HANDLE; + _cogl_material_unparent (material); + + if (material->differences & COGL_MATERIAL_STATE_USER_SHADER && + material->big_state->user_program) + cogl_handle_unref (material->big_state->user_program); + + if (material->differences & COGL_MATERIAL_STATE_NEEDS_BIG_STATE) + g_slice_free (CoglMaterialBigState, material->big_state); + + if (material->differences & COGL_MATERIAL_STATE_LAYERS) + { + g_list_foreach (material->layer_differences, + (GFunc)cogl_handle_unref, NULL); + g_list_free (material->layer_differences); + } - g_list_foreach (material->layers, - (GFunc)cogl_handle_unref, NULL); - g_list_free (material->layers); g_slice_free (CoglMaterial, material); } -static gboolean -_cogl_material_needs_blending_enabled (CoglMaterial *material, - GLubyte *override_color) +gboolean +_cogl_material_get_real_blend_enabled (CoglHandle handle) { - GList *tmp; + CoglMaterial *material = COGL_MATERIAL (handle); - /* XXX: If we expose manual control over ENABLE_BLEND, we'll add - * a flag to know when it's user configured, so we don't trash it */ + g_return_val_if_fail (cogl_is_material (handle), FALSE); - /* XXX: Uncomment this to disable all blending */ -#if 0 - return; -#endif + return material->real_blend_enable; +} - if ((override_color && override_color[3] != 0xff) || - material->unlit[3] != 0xff || - material->ambient[3] != 1.0f || - material->diffuse[3] != 1.0f || - material->specular[3] != 1.0f || - material->emission[3] != 1.0f) - return TRUE; +static CoglMaterial * +_cogl_material_get_authority (CoglMaterial *material, + unsigned long difference) +{ + CoglMaterial *authority = material; + while (!(authority->differences & difference)) + authority = authority->parent; + return authority; +} - for (tmp = material->layers; tmp != NULL; tmp = tmp->next) +static CoglMaterialLayer * +_cogl_material_layer_get_authority (CoglMaterialLayer *layer, + unsigned long difference) +{ + CoglMaterialLayer *authority = layer; + while (!(authority->differences & difference)) + authority = authority->parent; + return authority; +} + +static int +_cogl_material_layer_get_unit_index (CoglMaterialLayer *layer) +{ + CoglMaterialLayer *authority = + _cogl_material_layer_get_authority (layer, COGL_MATERIAL_LAYER_STATE_UNIT); + return authority->unit_index; +} + +static void +_cogl_material_update_layers_cache (CoglMaterial *material) +{ + /* Note: we assume this material is a _LAYERS authority */ + int n_layers; + CoglMaterial *current; + int layers_found; + + if (G_LIKELY (!material->layers_cache_dirty) || + material->n_layers == 0) + return; + + material->layers_cache_dirty = FALSE; + + n_layers = material->n_layers; + if (G_LIKELY (n_layers < G_N_ELEMENTS (material->short_layers_cache))) { - CoglMaterialLayer *layer = tmp->data; + material->layers_cache = material->short_layers_cache; + memset (material->layers_cache, 0, + sizeof (CoglMaterialLayer *) * + G_N_ELEMENTS (material->short_layers_cache)); + } + else + { + material->layers_cache = + g_slice_alloc0 (sizeof (CoglMaterialLayer *) * n_layers); + } - /* NB: A layer may have a combine mode set on it but not yet have an - * associated texture. */ - if (!layer->texture) + /* Notes: + * + * Each material doesn't have to contain a complete list of the layers + * it depends on, some of them are indirectly referenced through the + * material's ancestors. + * + * material->layer_differences only contains a list of layers that + * have changed in relation to its parent. + * + * material->layer_differences is not maintained sorted, but it + * won't contain multiple layers corresponding to a particular + * ->unit_index. + * + * Some of the ancestor materials may reference layers with + * ->unit_index values >= n_layers so we ignore them. + * + * As we ascend through the ancestors we are searching for any + * CoglMaterialLayers corresponding to the texture ->unit_index + * values in the range [0,n_layers-1]. As soon as a pointer is found + * we ignore layers of further ancestors with the same ->unit_index + * values. + */ + + layers_found = 0; + for (current = material; current->parent; current = current->parent) + { + GList *l; + + if (!(current->differences & COGL_MATERIAL_STATE_LAYERS)) continue; - if (cogl_texture_get_format (layer->texture) & COGL_A_BIT) - return TRUE; + for (l = current->layer_differences; l; l = l->next) + { + CoglMaterialLayer *layer = l->data; + int unit_index = _cogl_material_layer_get_unit_index (layer); + + if (unit_index < n_layers && !material->layers_cache[unit_index]) + { + material->layers_cache[unit_index] = layer; + layers_found++; + if (layers_found == n_layers) + return; + } + } } + g_warn_if_reached (); +} + +/* This recursively frees the layers_cache of a material and all of + * its descendants. + * + * For instance if we change a materials ->layer_differences list + * then that material and all of its descendants may now have + * incorrect layer caches. */ +static void +recursively_free_layer_caches (CoglMaterial *material) +{ + GList *l; + + /* Note: we maintain the invariable that if a material already has a + * dirty layers_cache then so do all of its descendants. */ + if (material->layers_cache_dirty) + return; + + if (G_UNLIKELY (material->layers_cache != material->short_layers_cache)) + g_slice_free1 (sizeof (CoglMaterialLayer *) * material->n_layers, + material->layers_cache); + material->layers_cache_dirty = TRUE; + + if (material->has_children) + { + recursively_free_layer_caches (material->first_child); + for (l = material->children; l; l = l->next) + recursively_free_layer_caches (l->data); + } +} + +typedef gboolean (*CoglMaterialLayerCallback) (CoglMaterialLayer *layer, + void *user_data); + +/* TODO: add public cogl_material_foreach_layer but instead of passing + * a CoglMaterialLayer pointer to the callback we should pass a + * layer_index instead. */ + +static void +_cogl_material_foreach_layer (CoglHandle handle, + CoglMaterialLayerCallback callback, + void *user_data) +{ + CoglMaterial *material = COGL_MATERIAL (handle); + CoglMaterial *authority = + _cogl_material_get_authority (material, COGL_MATERIAL_STATE_LAYERS); + int n_layers; + int i; + gboolean cont; + + n_layers = authority->n_layers; + if (n_layers == 0) + return; + + _cogl_material_update_layers_cache (authority); + + for (i = 0, cont = TRUE; i < n_layers && cont == TRUE; i++) + cont = callback (authority->layers_cache[i], user_data); +} + +static gboolean +layer_has_alpha_cb (CoglMaterialLayer *layer, void *data) +{ + CoglMaterialLayer *combine_authority = + _cogl_material_layer_get_authority (layer, + COGL_MATERIAL_LAYER_STATE_COMBINE); + CoglMaterialLayerBigState *big_state = combine_authority->big_state; + CoglMaterialLayer *tex_authority; + gboolean *has_alpha = data; + + /* has_alpha maintains the alpha status for the GL_PREVIOUS layer */ + + /* For anything but the default texture combine we currently just + * assume it may result in an alpha value < 1 + * + * FIXME: we could do better than this. */ + if (big_state->texture_combine_alpha_func != GL_MODULATE || + big_state->texture_combine_alpha_src[0] != GL_PREVIOUS || + big_state->texture_combine_alpha_op[0] != GL_SRC_ALPHA || + big_state->texture_combine_alpha_src[0] != GL_TEXTURE || + big_state->texture_combine_alpha_op[0] != GL_SRC_ALPHA) + { + *has_alpha = TRUE; + /* return FALSE to stop iterating layers... */ + return FALSE; + } + + /* NB: A layer may have a combine mode set on it but not yet + * have an associated texture which would mean we'd fallback + * to the default texture which doesn't have an alpha component + */ + tex_authority = + _cogl_material_layer_get_authority (layer, + COGL_MATERIAL_LAYER_STATE_TEXTURE); + if (tex_authority->texture && + cogl_texture_get_format (tex_authority->texture) & COGL_A_BIT) + { + *has_alpha = TRUE; + /* return FALSE to stop iterating layers... */ + return FALSE; + } + + *has_alpha = FALSE; + /* return FALSE to continue iterating layers... */ + return TRUE; +} + +static CoglHandle +_cogl_material_get_user_program (CoglHandle handle) +{ + CoglMaterial *material = COGL_MATERIAL (handle); + CoglMaterial *authority; + + g_return_val_if_fail (cogl_is_material (handle), COGL_INVALID_HANDLE); + + authority = + _cogl_material_get_authority (material, COGL_MATERIAL_STATE_USER_SHADER); + + return authority->big_state->user_program; +} + +static gboolean +_cogl_material_needs_blending_enabled (CoglMaterial *material, + unsigned long changes, + const CoglColor *override_color) +{ + CoglMaterial *enable_authority; + CoglMaterial *blend_authority; + CoglMaterialBlendState *blend_state; + CoglMaterialBlendEnable enabled; + unsigned long other_state; + + if (G_UNLIKELY (cogl_debug_flags & COGL_DEBUG_DISABLE_BLENDING)) + return FALSE; + + enable_authority = + _cogl_material_get_authority (material, COGL_MATERIAL_STATE_BLEND_ENABLE); + + enabled = enable_authority->blend_enable; + if (enabled != COGL_MATERIAL_BLEND_ENABLE_AUTOMATIC) + return enabled == COGL_MATERIAL_BLEND_ENABLE_ENABLED ? TRUE : FALSE; + + blend_authority = + _cogl_material_get_authority (material, COGL_MATERIAL_STATE_BLEND); + + blend_state = &blend_authority->big_state->blend_state; + + /* We are trying to identify awkward cases that are equivalent to + * blending being disable, where the output is simply GL_SRC_COLOR. + * + * Note: we assume that all OpenGL drivers will identify the simple + * case of ADD (ONE, ZERO) as equivalent to blending being disabled. + * + * We should update this when we add support for more blend + * functions... + */ + +#ifndef HAVE_COGL_GLES + /* GLES 1 can't change the function or have separate alpha factors */ + if (blend_state->blend_equation_rgb != GL_FUNC_ADD || + blend_state->blend_equation_alpha != GL_FUNC_ADD) + return TRUE; + + if (blend_state->blend_src_factor_alpha != GL_ONE || + blend_state->blend_dst_factor_alpha != GL_ONE_MINUS_SRC_ALPHA) + return TRUE; +#endif + + if (blend_state->blend_src_factor_rgb != GL_ONE || + blend_state->blend_dst_factor_rgb != GL_ONE_MINUS_SRC_ALPHA) + return TRUE; + + /* Given the above constraints, it's now a case of finding any + * SRC_ALPHA that != 1 */ + + /* In the case of a layer state change we need to check everything + * else first since they contribute to the has_alpha status of the + * GL_PREVIOUS layer. */ + if (changes & COGL_MATERIAL_STATE_LAYERS) + changes = COGL_MATERIAL_STATE_AFFECTS_BLENDING; + + /* XXX: we don't currently handle specific changes in an optimal way*/ + changes = COGL_MATERIAL_STATE_AFFECTS_BLENDING; + + if ((override_color && cogl_color_get_alpha_byte (override_color) != 0xff)) + return TRUE; + + if (changes & COGL_MATERIAL_STATE_COLOR) + { + CoglColor tmp; + cogl_material_get_color (material, &tmp); + if (cogl_color_get_alpha_byte (&tmp) != 0xff) + return TRUE; + } + + /* We can't make any assumptions about the alpha channel if the user + * is using an unknown fragment shader. + * + * TODO: check that it isn't just a vertex shader! + */ + if (changes & COGL_MATERIAL_STATE_USER_SHADER) + { + if (_cogl_material_get_user_program (material) != COGL_INVALID_HANDLE) + return TRUE; + } + + /* XXX: we should only need to look at these if lighting is enabled + */ + if (changes & COGL_MATERIAL_STATE_LIGHTING) + { + CoglColor tmp; + + cogl_material_get_ambient (material, &tmp); + if (cogl_color_get_alpha_byte (&tmp) != 0xff) + return TRUE; + cogl_material_get_diffuse (material, &tmp); + if (cogl_color_get_alpha_byte (&tmp) != 0xff) + return TRUE; + cogl_material_get_specular (material, &tmp); + if (cogl_color_get_alpha_byte (&tmp) != 0xff) + return TRUE; + cogl_material_get_emission (material, &tmp); + if (cogl_color_get_alpha_byte (&tmp) != 0xff) + return TRUE; + } + + if (changes & COGL_MATERIAL_STATE_LAYERS) + { + /* has_alpha tracks the alpha status of the GL_PREVIOUS layer. + * To start with that's defined by the material color which + * must be fully opaque if we got this far. */ + gboolean has_alpha = FALSE; + _cogl_material_foreach_layer (material, + layer_has_alpha_cb, + &has_alpha); + if (has_alpha) + return TRUE; + } + + /* So far we have only checked the property that has been changed so + * we now need to check all the other properties too. */ + other_state = COGL_MATERIAL_STATE_AFFECTS_BLENDING & ~changes; + if (other_state && + _cogl_material_needs_blending_enabled (material, + other_state, + NULL)) + return TRUE; + return FALSE; } static void _cogl_material_set_backend (CoglMaterial *material, int backend) { - if (material->backend != COGL_MATERIAL_BACKEND_UNDEFINED && - backends[material->backend]->free_priv) - backends[material->backend]->free_priv (material); + _cogl_material_backend_free_priv (material); material->backend = backend; } -/* If primitives have been logged in the journal referencing the current - * state of this material we need to flush the journal before we can - * modify it... */ static void -_cogl_material_pre_change_notify (CoglMaterial *material, - unsigned long changes, - GLubyte *new_color) +_cogl_material_copy_differences (CoglMaterial *dest, + CoglMaterial *src, + unsigned long differences) { + CoglMaterialBigState *big_state; + + if (differences & COGL_MATERIAL_STATE_COLOR) + dest->color = src->color; + + if (differences & COGL_MATERIAL_STATE_BLEND_ENABLE) + dest->blend_enable = src->blend_enable; + + if (differences & COGL_MATERIAL_STATE_LAYERS) + { + GList *l; + + if (dest->differences & COGL_MATERIAL_STATE_LAYERS && + dest->layer_differences) + { + g_list_foreach (dest->layer_differences, + (GFunc)cogl_handle_unref, + NULL); + g_list_free (dest->layer_differences); + } + + dest->n_layers = src->n_layers; + dest->layer_differences = g_list_copy (src->layer_differences); + + for (l = src->layer_differences; l; l = l->next) + { + /* NB: a layer can't have more than one ->owner so we can't + * simply take a references on each of the original + * layer_differences, we have to derive new layers from the + * originals instead. */ + CoglMaterialLayer *copy = _cogl_material_layer_copy (l->data); + _cogl_material_add_layer_difference (dest, copy, FALSE); + cogl_handle_unref (copy); + } + } + + if (differences & COGL_MATERIAL_STATE_NEEDS_BIG_STATE) + { + if (!dest->has_big_state) + { + dest->big_state = g_slice_new (CoglMaterialBigState); + dest->has_big_state = TRUE; + } + big_state = dest->big_state; + } + else + goto check_for_blending_change; + + if (differences & COGL_MATERIAL_STATE_LIGHTING) + { + memcpy (&big_state->lighting_state, + &src->big_state->lighting_state, + sizeof (CoglMaterialLightingState)); + } + + if (differences & COGL_MATERIAL_STATE_ALPHA_FUNC) + { + memcpy (&big_state->alpha_state, + &src->big_state->alpha_state, + sizeof (CoglMaterialAlphaFuncState)); + } + + if (differences & COGL_MATERIAL_STATE_BLEND) + { + memcpy (&big_state->blend_state, + &src->big_state->blend_state, + sizeof (CoglMaterialBlendState)); + } + + if (differences & COGL_MATERIAL_STATE_USER_SHADER) + { + if (src->big_state->user_program) + big_state->user_program = + cogl_handle_ref (src->big_state->user_program); + else + big_state->user_program = COGL_INVALID_HANDLE; + } + + /* XXX: we shouldn't bother doing this in most cases since + * _copy_differences is typically used to initialize material state + * by copying it from the current authority, so it's not actually + * *changing* anything. + */ +check_for_blending_change: + if (differences & COGL_MATERIAL_STATE_AFFECTS_BLENDING) + handle_automatic_blend_enable (dest, differences); + + dest->differences |= differences; +} + +static void +_cogl_material_initialize_state (CoglMaterial *dest, + CoglMaterial *src, + CoglMaterialState state) +{ + if (dest == src) + return; + + if (state != COGL_MATERIAL_STATE_LAYERS) + _cogl_material_copy_differences (dest, src, state); + else + { + dest->n_layers = src->n_layers; + dest->layer_differences = NULL; + } +} + +typedef gboolean (*CoglMaterialChildCallback) (CoglMaterial *child, + void *user_data); + +static void +_cogl_material_foreach_child (CoglMaterial *material, + CoglMaterialChildCallback callback, + void *user_data) +{ + if (material->has_children) + { + callback (material->first_child, user_data); + g_list_foreach (material->children, (GFunc)callback, user_data); + } +} + +static gboolean +change_parent_cb (CoglMaterial *child, + void *user_data) +{ + child->parent = user_data; + return TRUE; +} + +static void +_cogl_material_pre_change_notify (CoglMaterial *material, + CoglMaterialState change, + const CoglColor *new_color) +{ + CoglMaterial *authority; + _COGL_GET_CONTEXT (ctx, NO_RETVAL); + /* If primitives have been logged in the journal referencing the + * current state of this material we need to flush the journal + * before we can modify it... */ if (material->journal_ref_count) { + gboolean skip_journal_flush = FALSE; + /* XXX: We don't usually need to flush the journal just due to * color changes since material colors are logged in the - * journals vertex buffer. The exception is when the change in + * journal's vertex buffer. The exception is when the change in * color enables or disables the need for blending. */ - if (changes == COGL_MATERIAL_CHANGE_COLOR) + if (change == COGL_MATERIAL_STATE_COLOR) { gboolean will_need_blending = - _cogl_material_needs_blending_enabled (material, new_color); - if (will_need_blending != - ((material->flags & COGL_MATERIAL_FLAG_ENABLE_BLEND) ? - TRUE : FALSE)) - _cogl_journal_flush (); + _cogl_material_needs_blending_enabled (material, + change, + new_color); + gboolean blend_enable = material->real_blend_enable ? TRUE : FALSE; + + if (will_need_blending == blend_enable) + skip_journal_flush = TRUE; } - else + + if (!skip_journal_flush) _cogl_journal_flush (); } @@ -342,87 +1081,2080 @@ _cogl_material_pre_change_notify (CoglMaterial *material, _cogl_material_set_backend (material, COGL_MATERIAL_BACKEND_UNDEFINED); if (material->backend != COGL_MATERIAL_BACKEND_UNDEFINED && - backends[material->backend]->material_change_notify) - backends[material->backend]->material_change_notify (material, - changes, - new_color); + backends[material->backend]->material_pre_change_notify) + backends[material->backend]->material_pre_change_notify (material, + change, + new_color); - /* Invalidate any ->current_material reference to this material since - * it will no longer represent the current state. + /* + * There is an arbitrary tree of descendants of this material; any of + * which may indirectly depend on this material as the authority for + * some set of properties. (Meaning for example that one of its + * descendants derives its color or blending state from this + * material.) * - * NB: we also invalidate this if the material is freed + * We can't modify any property that this material is the authority + * for unless we create another material to take its place first and + * make sure descendants reference this new material instead. + */ + if (material->has_children) + { + CoglMaterial *new_authority; + COGL_STATIC_COUNTER (material_copy_on_write_counter, + "material copy on write counter", + "Increments each time a material " + "must be copied to allow modification", + 0 /* no application private data */); + + COGL_COUNTER_INC (_cogl_uprof_context, material_copy_on_write_counter); + + new_authority = cogl_material_copy (material->parent); + + /* We could explicitly walk the descendants, OR together the set + * of differences that we determine this material is the + * authority on and only copy those differences copied across. + * + * Or, if we don't explicitly walk the descendants we at least + * know that material->differences represents the largest set of + * differences that this material could possibly be an authority + * on. + * + * We do the later just because it's simplest, but we might need + * to come back to this later... + */ + _cogl_material_copy_differences (new_authority, material, + material->differences); + + /* Reparent the children of material to be children of + * new_authority instead... */ + new_authority->has_children = TRUE; + new_authority->first_child = material->first_child; + new_authority->children = material->children; + material->has_children = FALSE; + _cogl_material_foreach_child (new_authority, + change_parent_cb, + new_authority); + + /* If the new_authority has any + * ->layer_differences then we dirty any layer_caches for all + * its descendants since _cogl_material_copy_differences will + * have created new layers which won't be correctly referenced + * in the caches. + */ + if (new_authority->differences & COGL_MATERIAL_STATE_LAYERS) + { + /* NB: we can't call recursively_free_layer_caches + * directly for the material since it will assume if + * new_authority->layers_cache == NULL then all the + * descendants already have invalidated caches. */ + _cogl_material_foreach_child (new_authority, + (CoglMaterialChildCallback) + recursively_free_layer_caches, + NULL); + } + } + + /* At this point we know we have a material with no dependants so we + * are now free to modify the material. */ + g_assert (!material->has_children); + + /* If the material isn't already an authority for the state group + * being modified then we need to initialize the corresponding + * state. */ + if (change & COGL_MATERIAL_STATE_ALL_SPARSE) + authority = _cogl_material_get_authority (material, change); + else + authority = material; + _cogl_material_initialize_state (material, authority, change); + + /* Each material has a sorted cache of the layers it depends on + * which will need updating via _cogl_material_update_layers_cache + * if a material's layers are changed. */ + if (change == COGL_MATERIAL_STATE_LAYERS) + recursively_free_layer_caches (material); + + /* If the material being changed is the same as the last material we + * flushed then we keep a track of the changes so we can try to + * minimize redundant OpenGL calls if the same material is flushed + * again. */ if (ctx->current_material == material) - ctx->current_material = COGL_INVALID_HANDLE; + ctx->current_material_changes_since_flush |= change; +} + + +static void +_cogl_material_add_layer_difference (CoglMaterial *material, + CoglMaterialLayer *layer, + gboolean inc_n_layers) +{ + g_return_if_fail (layer->owner == NULL); + + layer->owner = material; + cogl_handle_ref ((CoglHandle)layer); + + /* - Flush journal primitives referencing the current state. + * - Make sure the material has no dependants so it may be modified. + * - If the material isn't currently an authority for the state being + * changed, then initialize that state from the current authority. + */ + _cogl_material_pre_change_notify (material, + COGL_MATERIAL_STATE_LAYERS, + NULL); + + material->differences |= COGL_MATERIAL_STATE_LAYERS; + + material->layer_differences = + g_list_prepend (material->layer_differences, layer); + + if (inc_n_layers) + material->n_layers++; +} + +/* NB: If you are calling this it's your responsibility to have + * already called: + * _cogl_material_pre_change_notify (m, _CHANGE_LAYERS, NULL); + */ +static void +_cogl_material_remove_layer_difference (CoglMaterial *material, + CoglMaterialLayer *layer, + gboolean dec_n_layers) +{ + g_return_if_fail (layer->owner == material); + + /* - Flush journal primitives referencing the current state. + * - Make sure the material has no dependants so it may be modified. + * - If the material isn't currently an authority for the state being + * changed, then initialize that state from the current authority. + */ + _cogl_material_pre_change_notify (material, + COGL_MATERIAL_STATE_LAYERS, + NULL); + + layer->owner = NULL; + cogl_handle_unref ((CoglHandle)layer); + + material->differences |= COGL_MATERIAL_STATE_LAYERS; + + material->layer_differences = + g_list_remove (material->layer_differences, layer); + + if (dec_n_layers) + material->n_layers--; } static void -handle_automatic_blend_enable (CoglMaterial *material) +_cogl_material_try_reverting_layers_authority (CoglMaterial *authority, + CoglMaterial *old_authority) { - gboolean needs_blending_enabled = - _cogl_material_needs_blending_enabled (material, NULL); - - if (needs_blending_enabled != - !!(material->flags & COGL_MATERIAL_FLAG_ENABLE_BLEND)) + if (authority->layer_differences == NULL && authority->parent) { + /* If the previous _STATE_LAYERS authority has the same + * ->n_layers then we can revert to that being the authority + * again. */ + if (!old_authority) + { + old_authority = + _cogl_material_get_authority (authority->parent, + COGL_MATERIAL_STATE_LAYERS); + } + + if (old_authority->n_layers == authority->n_layers) + authority->differences &= ~COGL_MATERIAL_STATE_LAYERS; + } +} + + +static void +handle_automatic_blend_enable (CoglMaterial *material, + CoglMaterialState change) +{ + gboolean blend_enable = + _cogl_material_needs_blending_enabled (material, change, NULL); + + if (blend_enable != material->real_blend_enable) + { + /* - Flush journal primitives referencing the current state. + * - Make sure the material has no dependants so it may be + * modified. + * - If the material isn't currently an authority for the state + * being changed, then initialize that state from the current + * authority. + */ _cogl_material_pre_change_notify (material, - COGL_MATERIAL_CHANGE_ENABLE_BLEND, + COGL_MATERIAL_STATE_REAL_BLEND_ENABLE, NULL); - if (needs_blending_enabled) - material->flags |= COGL_MATERIAL_FLAG_ENABLE_BLEND; - else - material->flags &= ~COGL_MATERIAL_FLAG_ENABLE_BLEND; + material->real_blend_enable = blend_enable; + } +} + +typedef struct +{ + int keep_n; + int current_pos; + gboolean needs_pruning; + int first_index_to_prune; +} CoglMaterialPruneLayersInfo; + +static gboolean +update_prune_layers_info_cb (CoglMaterialLayer *layer, void *user_data) +{ + CoglMaterialPruneLayersInfo *state = user_data; + + if (state->current_pos == state->keep_n) + { + state->needs_pruning = TRUE; + state->first_index_to_prune = layer->index; + return FALSE; + } + state->current_pos++; + return TRUE; +} + +void +_cogl_material_prune_to_n_layers (CoglMaterial *material, int n) +{ + CoglMaterialPruneLayersInfo state; + gboolean notified_change = TRUE; + GList *l; + GList *next; + + state.keep_n = n; + state.current_pos = 0; + state.needs_pruning = FALSE; + _cogl_material_foreach_layer (material, + update_prune_layers_info_cb, + &state); + + material->n_layers = n; + + if (!state.needs_pruning) + return; + + if (!(material->differences & COGL_MATERIAL_STATE_LAYERS)) + return; + + /* It's possible that this material owns some of the layers being + * discarded, so we'll need to unlink them... */ + for (l = material->layer_differences; l; l = next) + { + CoglMaterialLayer *layer = l->data; + next = l->next; /* we're modifying the list we're iterating */ + + if (layer->index > state.first_index_to_prune) + { + if (!notified_change) + { + /* - Flush journal primitives referencing the current + * state. + * - Make sure the material has no dependants so it may + * be modified. + * - If the material isn't currently an authority for + * the state being changed, then initialize that state + * from the current authority. + */ + _cogl_material_pre_change_notify (material, + COGL_MATERIAL_STATE_LAYERS, + NULL); + notified_change = TRUE; + } + + material->layer_differences = + g_list_delete_link (material->layer_differences, l); + } } } static void _cogl_material_backend_layer_change_notify (CoglMaterialLayer *layer, - unsigned long changes) + CoglMaterialLayerState change) { - int backend = layer->material->backend; - if (backend == COGL_MATERIAL_BACKEND_UNDEFINED) - return; + int i; - if (backends[backend]->layer_change_notify) - backends[backend]->layer_change_notify (layer, changes); + /* NB: layers may be used by multiple materials which may be using + * different backends, therefore we determine which backends to + * notify based on the private state pointers for each backend... + */ + for (i = 0; i < COGL_MATERIAL_N_BACKENDS; i++) + { + if (layer->backend_priv[i] && backends[i]->layer_pre_change_notify) + backends[i]->layer_pre_change_notify (layer, change); + } +} + +static unsigned int +get_n_args_for_combine_func (GLint func) +{ + switch (func) + { + case GL_REPLACE: + return 1; + case GL_MODULATE: + case GL_ADD: + case GL_ADD_SIGNED: + case GL_SUBTRACT: + case GL_DOT3_RGB: + case GL_DOT3_RGBA: + return 2; + case GL_INTERPOLATE: + return 3; + } + return 0; } static void -_cogl_material_layer_pre_change_notify (CoglMaterialLayer *layer, - CoglMaterialLayerChangeFlags changes) +_cogl_material_layer_initialize_state (CoglMaterialLayer *dest, + CoglMaterialLayer *src, + unsigned long differences) { - CoglTextureUnit *unit = _cogl_get_texture_unit (layer->unit_index); + CoglMaterialLayerBigState *big_state; - /* Look at the texture unit corresponding to this layer, if it - * currently has a back reference to this layer then invalidate it - * so that next time we come to flush this layer we'll see that the - * texture unit no longer corresponds to this layer's state. + dest->differences |= differences; + + if (differences & COGL_MATERIAL_LAYER_STATE_UNIT) + dest->unit_index = src->unit_index; + + if (differences & COGL_MATERIAL_LAYER_STATE_TEXTURE) + dest->texture = src->texture; + + if (differences & COGL_MATERIAL_LAYER_STATE_FILTERS) + { + dest->min_filter = src->min_filter; + dest->mag_filter = src->mag_filter; + } + + if (differences & COGL_MATERIAL_LAYER_STATE_WRAP_MODES) + { + dest->wrap_mode_s = src->wrap_mode_s; + dest->wrap_mode_t = src->wrap_mode_t; + dest->wrap_mode_r = src->wrap_mode_r; + } + + if (differences & COGL_MATERIAL_LAYER_STATE_NEEDS_BIG_STATE) + { + if (!dest->has_big_state) + { + dest->big_state = g_slice_new (CoglMaterialLayerBigState); + dest->has_big_state = TRUE; + } + big_state = dest->big_state; + } + else + return; + + if (differences & COGL_MATERIAL_LAYER_STATE_COMBINE) + { + int n_args; + int i; + GLint func = src->big_state->texture_combine_rgb_func; + big_state->texture_combine_rgb_func = func; + n_args = 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 = get_n_args_for_combine_func (func); + for (i = 0; i < n_args; i++) + { + big_state->texture_combine_alpha_src[i] = + src->big_state->texture_combine_alpha_src[i]; + big_state->texture_combine_alpha_op[i] = + src->big_state->texture_combine_alpha_op[i]; + } + } + + if (differences & COGL_MATERIAL_LAYER_STATE_COMBINE_CONSTANT) + memcpy (dest->big_state->texture_combine_constant, + src->big_state->texture_combine_constant, + sizeof (float) * 4); + + if (differences & COGL_MATERIAL_LAYER_STATE_USER_MATRIX) + dest->big_state->matrix = src->big_state->matrix; +} + +/* NB: This function will allocate a new derived layer if you are + * trying to change the state of a layer with dependants so you must + * always check the return value. + * + * If a new layer is returned it will be owned by required_owner. + * + * required_owner can only by NULL for new, currently unowned layers + * with no dependants. + */ +static CoglMaterialLayer * +_cogl_material_layer_pre_change_notify (CoglMaterial *required_owner, + CoglMaterialLayer *layer, + CoglMaterialLayerState change) +{ + CoglTextureUnit *unit; + CoglMaterialLayer *authority; + + /* Identify the case where the layer is new with no owner or + * dependants and so we don't need to do anything. */ + if (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); + + /* Unlike materials; layers are simply considered immutable once + * they have dependants - either children or another material owner. */ + if (layer->has_children || layer->owner != required_owner) + { + CoglMaterialLayer *new = _cogl_material_layer_copy (layer); + _cogl_material_add_layer_difference (required_owner, new, FALSE); + cogl_handle_unref (new); + layer = new; + goto init_layer_state; + } + + /* Note: At this point we know there is only one material dependant on + * this layer (required_owner), and there are no other layers + * dependant on this layer so it's ok to modify it. */ + + if (required_owner->journal_ref_count) + _cogl_journal_flush (); + + _cogl_material_backend_layer_change_notify (layer, change); + + /* If the layer being changed is the same as the last layer we + * flushed to the corresponding texture unit then we keep a track of + * the changes so we can try to minimize redundant OpenGL calls if + * the same layer is flushed again. + */ + unit = _cogl_get_texture_unit (_cogl_material_layer_get_unit_index (layer)); if (unit->layer == layer) - unit->layer = NULL; + unit->layer_changes_since_flush |= change; - _cogl_material_backend_layer_change_notify (layer, changes); +init_layer_state: - _cogl_material_pre_change_notify (layer->material, - COGL_MATERIAL_CHANGE_LAYERS, - NULL); + /* If the material isn't already an authority for the state group + * being modified then we need to initialize the corresponding + * state. */ + authority = _cogl_material_layer_get_authority (layer, change); + _cogl_material_layer_initialize_state (layer, authority, change); + + return layer; +} + +/* XXX: This is duplicated logic; the same as for + * _cogl_material_prune_redundant_ancestry it would be nice to find a + * way to consolidate these functions! */ +static void +_cogl_material_layer_prune_redundant_ancestry (CoglMaterialLayer *layer) +{ + CoglMaterialLayer *new_parent = layer->parent; + + /* walk up past ancestors that are now redundant and potentially + * reparent the layer. */ + while (new_parent->parent && + (new_parent->differences | layer->differences) == + layer->differences) + new_parent = new_parent->parent; + + if (new_parent != layer->parent) + { + CoglMaterialLayer *old_parent = layer->parent; + layer->parent = cogl_handle_ref (new_parent); + /* Note: the old parent may indirectly be keeping + the new parent alive so we have to ref the new + parent before unrefing the old */ + cogl_handle_unref (old_parent); + } +} + +/* + * XXX: consider special casing layer->unit_index so it's not a sparse + * property so instead we can assume it's valid for all layer + * instances. + * - We would need to initialize ->unit_index in + * _cogl_material_layer_copy (). + * + * XXX: If you use this API you should consider that the given layer + * might not be writeable and so a new derived layer will be allocated + * and modified instead. The layer modified will be returned so you + * can identify when this happens. + */ +static CoglMaterialLayer * +_cogl_material_set_layer_unit (CoglMaterial *required_owner, + CoglMaterialLayer *layer, + int unit_index) +{ + CoglMaterialLayerState change = COGL_MATERIAL_LAYER_STATE_UNIT; + CoglMaterialLayer *authority = + _cogl_material_layer_get_authority (layer, change); + CoglMaterialLayer *new; + + if (authority->unit_index == unit_index) + return layer; + + new = + _cogl_material_layer_pre_change_notify (required_owner, + layer, + change); + if (new != layer) + layer = new; + else + { + /* If the layer we found is currently the authority on the state + * we are changing see if we can revert to one of our ancestors + * being the authority. */ + if (layer == authority && authority->parent != NULL) + { + CoglMaterialLayer *old_authority = + _cogl_material_layer_get_authority (authority->parent, change); + + if (old_authority->unit_index == unit_index) + { + layer->differences &= ~change; + return layer; + } + } + } + + layer->unit_index = unit_index; + + /* If we weren't previously the authority on this state then we need + * to extended our differences mask and so it's possible that some + * of our ancestry will now become redundant, so we aim to reparent + * ourselves if that's true... */ + if (layer != authority) + { + layer->differences |= change; + _cogl_material_layer_prune_redundant_ancestry (layer); + } + + return layer; +} + +typedef struct +{ + /* The layer we are trying to find */ + int layer_index; + + /* The layer we find or untouched if not found */ + CoglMaterialLayer *layer; + + /* If the layer can't be found then a new layer should be + * inserted after this texture unit index... */ + int insert_after; + + /* When adding a layer we need the list of layers to shift up + * to a new texture unit. When removing we need the list of + * layers to shift down. + * + * Note: the list isn't sorted */ + CoglMaterialLayer **layers_to_shift; + int n_layers_to_shift; + + /* When adding a layer we don't need a complete list of + * layers_to_shift if we find a layer already corresponding to the + * layer_index. */ + gboolean ignore_shift_layers_if_found; + +} CoglMaterialLayerInfo; + +/* Returns TRUE once we know there is nothing more to update */ +static gboolean +update_layer_info (CoglMaterialLayer *layer, + CoglMaterialLayerInfo *layer_info) +{ + if (layer->index == layer_info->layer_index) + { + layer_info->layer = layer; + if (layer_info->ignore_shift_layers_if_found) + return TRUE; + } + else if (layer->index < layer_info->layer_index) + { + int unit_index = _cogl_material_layer_get_unit_index (layer); + layer_info->insert_after = unit_index; + } + else + layer_info->layers_to_shift[layer_info->n_layers_to_shift++] = + layer; + + return FALSE; +} + +/* Returns FALSE to break out of a _foreach_layer () iteration */ +static gboolean +update_layer_info_cb (CoglMaterialLayer *layer, + void *user_data) +{ + CoglMaterialLayerInfo *layer_info = user_data; + + if (update_layer_info (layer, layer_info)) + return FALSE; /* break */ + else + return TRUE; /* continue */ +} + +static void +_cogl_material_get_layer_info (CoglMaterial *material, + CoglMaterialLayerInfo *layer_info) +{ + /* Note: we are assuming this material is a _STATE_LAYERS authority */ + int n_layers = material->n_layers; + int i; + + /* FIXME: _cogl_material_foreach_layer now calls + * _cogl_material_update_layers_cache anyway so this codepath is + * pointless! */ + if (layer_info->ignore_shift_layers_if_found && + material->layers_cache_dirty) + { + /* The expectation is that callers of + * _cogl_material_get_layer_info are likely to be modifying the + * list of layers associated with a material so in this case + * where we don't have a cache of the layers and we don't + * necessarily have to iterate all the layers of the material we + * use a foreach_layer callback instead of updating the cache + * and iterating that as below. */ + _cogl_material_foreach_layer (material, + update_layer_info_cb, + layer_info); + return; + } + + _cogl_material_update_layers_cache (material); + for (i = 0; i < n_layers; i++) + { + CoglMaterialLayer *layer = material->layers_cache[i]; + + if (update_layer_info (layer, layer_info)) + return; + } +} + +static CoglMaterialLayer * +_cogl_material_get_layer (CoglMaterial *material, + int layer_index) +{ + CoglMaterial *authority = + _cogl_material_get_authority (material, COGL_MATERIAL_STATE_LAYERS); + CoglMaterialLayerInfo layer_info; + CoglMaterialLayer *layer; + int unit_index; + int i; + + _COGL_GET_CONTEXT (ctx, NULL); + + /* The layer index of the layer we want info about */ + layer_info.layer_index = layer_index; + + /* If a layer already exists with the given index this will be + * updated. */ + layer_info.layer = NULL; + + /* If a layer isn't found for the given index we'll need to know + * where to insert a new layer. */ + layer_info.insert_after = -1; + + /* If a layer can't be found then we'll need to insert a new layer + * and bump up the texture unit for all layers with an index + * > layer_index. */ + layer_info.layers_to_shift = + g_alloca (sizeof (CoglMaterialLayer *) * authority->n_layers); + layer_info.n_layers_to_shift = 0; + + /* If an exact match is found though we don't need a complete + * list of layers with indices > layer_index... */ + layer_info.ignore_shift_layers_if_found = TRUE; + + _cogl_material_get_layer_info (authority, &layer_info); + + if (layer_info.layer) + return layer_info.layer; + + unit_index = layer_info.insert_after + 1; + if (unit_index == 0) + layer = _cogl_material_layer_copy (ctx->default_layer_0); + else + { + CoglMaterialLayer *new; + layer = _cogl_material_layer_copy (ctx->default_layer_n); + new = _cogl_material_set_layer_unit (NULL, layer, unit_index); + /* Since we passed a newly allocated layer we wouldn't expect + * _set_layer_unit() to have to allocate *another* layer. */ + g_assert (new == layer); + layer->index = layer_index; + } + + for (i = 0; i < layer_info.n_layers_to_shift; i++) + { + CoglMaterialLayer *shift_layer = layer_info.layers_to_shift[i]; + + unit_index = _cogl_material_layer_get_unit_index (shift_layer); + _cogl_material_set_layer_unit (material, shift_layer, unit_index + 1); + /* NB: shift_layer may not be writeable so _set_layer_unit() + * will allocate a derived layer internally which will become + * owned by material. Check the return value if we need to do + * anything else with this layer. */ + } + + _cogl_material_add_layer_difference (material, layer, TRUE); + + cogl_handle_unref (layer); + + return layer; +} + +static CoglHandle +_cogl_material_layer_get_texture (CoglMaterialLayer *layer) +{ + CoglMaterialLayer *authority = + _cogl_material_layer_get_authority (layer, + COGL_MATERIAL_LAYER_STATE_TEXTURE); + + return authority->texture; +} + +static void +_cogl_material_prune_empty_layer_difference (CoglMaterial *layers_authority, + CoglMaterialLayer *layer) +{ + /* Find the GList link that references the empty layer */ + GList *link = g_list_find (layers_authority->layer_differences, layer); + /* No material directly owns the root node layer so this is safe... */ + CoglMaterialLayer *layer_parent = layer->parent; + CoglMaterialLayerInfo layer_info; + CoglMaterial *old_layers_authority; + + g_return_if_fail (link != NULL); + + /* If the layer's parent doesn't have an owner then we can simply + * take ownership ourselves and drop our reference on the empty + * layer. + */ + if (layer_parent->index == layer->index && layer_parent->owner == NULL) + { + cogl_handle_ref (layer_parent); + cogl_handle_unref (layer); + link->data = layer->parent; + recursively_free_layer_caches (layers_authority); + return; + } + + /* Now we want to find the layer that would become the authority for + * layer->index if we were to remove layer from + * layers_authority->layer_differences + */ + + /* The layer index of the layer we want info about */ + layer_info.layer_index = layer->index; + + /* If a layer already exists with the given index this will be + * updated. */ + layer_info.layer = NULL; + + /* If a layer can't be found then we'll need to insert a new layer + * and bump up the texture unit for all layers with an index + * > layer_index. */ + layer_info.layers_to_shift = + g_alloca (sizeof (CoglMaterialLayer *) * layers_authority->n_layers); + layer_info.n_layers_to_shift = 0; + + /* If an exact match is found though we don't need a complete + * list of layers with indices > layer_index... */ + layer_info.ignore_shift_layers_if_found = TRUE; + + /* We know the default/root material isn't a LAYERS authority so + * it's safe to dereference layers_authority->parent. */ + old_layers_authority = + _cogl_material_get_authority (layers_authority->parent, + COGL_MATERIAL_STATE_LAYERS); + + _cogl_material_get_layer_info (old_layers_authority, &layer_info); + + /* If layer is the defining layer for the corresponding ->index then + * we can't get rid of it. */ + if (!layer_info.layer) + return; + + /* If the layer that would become the authority for layer->index + * is layer->parent then we can simply remove the layer difference. */ + if (layer_info.layer == layer->parent) + { + _cogl_material_remove_layer_difference (layers_authority, layer, FALSE); + _cogl_material_try_reverting_layers_authority (layers_authority, + old_layers_authority); + } +} + +static void +_cogl_material_set_layer_texture (CoglMaterial *material, + int layer_index, + CoglHandle texture, + gboolean overriden, + GLuint slice_gl_texture, + GLenum slice_gl_target) +{ + CoglMaterialLayerState change = COGL_MATERIAL_LAYER_STATE_TEXTURE; + CoglMaterialLayer *layer; + CoglMaterialLayer *authority; + CoglMaterialLayer *new; + + /* Note: this will ensure that the layer exists, creating one if it + * doesn't already. + * + * Note: If the layer already existed it's possibly owned by another + * material. If the layer is created then it will be owned by + * material. */ + layer = _cogl_material_get_layer (material, layer_index); + + /* Now find the ancestor of the layer that is the authority for the + * state we want to change */ + authority = _cogl_material_layer_get_authority (layer, change); + + if (authority->texture_overridden == overriden && + authority->texture == texture && + (authority->texture_overridden == FALSE || + (authority->slice_gl_texture == slice_gl_texture && + authority->slice_gl_target == slice_gl_target))) + return; + + new = _cogl_material_layer_pre_change_notify (material, layer, change); + if (new != layer) + layer = new; + else + { + /* If the original layer we found is currently the authority on + * the state we are changing see if we can revert to one of our + * ancestors being the authority. */ + if (layer == authority && authority->parent != NULL) + { + CoglMaterialLayer *old_authority = + _cogl_material_layer_get_authority (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; + + g_assert (layer->owner == material); + if (layer->differences == 0) + _cogl_material_prune_empty_layer_difference (material, + layer); + goto changed; + } + } + } + + if (texture != COGL_INVALID_HANDLE) + cogl_handle_ref (texture); + if (layer == authority && + layer->texture != COGL_INVALID_HANDLE) + cogl_handle_unref (layer->texture); + layer->texture = texture; + layer->texture_overridden = FALSE; + layer->slice_gl_texture = slice_gl_texture; + layer->slice_gl_target = slice_gl_target; + + /* If we weren't previously the authority on this state then we need + * to extended our differences mask and so it's possible that some + * of our ancestry will now become redundant, so we aim to reparent + * ourselves if that's true... */ + if (layer != authority) + { + layer->differences |= change; + _cogl_material_layer_prune_redundant_ancestry (layer); + } + +changed: + + handle_automatic_blend_enable (material, COGL_MATERIAL_STATE_LAYERS); +} + +static void +_cogl_material_set_layer_gl_texture_slice (CoglHandle handle, + int layer_index, + CoglHandle texture, + GLuint slice_gl_texture, + GLenum slice_gl_target) +{ + CoglMaterial *material = COGL_MATERIAL (handle); + + g_return_if_fail (cogl_is_material (handle)); + /* GL texture overrides can only be set in association with a parent + * CoglTexture */ + g_return_if_fail (cogl_is_texture (texture)); + + _cogl_material_set_layer_texture (material, + layer_index, + texture, + TRUE, /* slice override */ + slice_gl_texture, + slice_gl_target); +} + +/* XXX: deprecate and replace with cogl_material_set_layer_texture? + * + * Originally I was planning on allowing users to set shaders somehow + * on layers (thus the ambiguous name), but now I wonder if we will do + * that with a more explicit "snippets" API and materials will have + * hooks defined to receive these snippets. + */ +void +cogl_material_set_layer (CoglHandle handle, + int layer_index, + CoglHandle texture) +{ + CoglMaterial *material = COGL_MATERIAL (handle); + + g_return_if_fail (cogl_is_material (handle)); + g_return_if_fail (texture == COGL_INVALID_HANDLE || + cogl_is_texture (texture)); + + _cogl_material_set_layer_texture (material, + layer_index, + texture, + FALSE, /* slice override */ + 0, /* slice_gl_texture */ + 0); /* slice_gl_target */ +} + +typedef struct +{ + int i; + CoglMaterial *material; + unsigned long fallback_layers; +} CoglMaterialFallbackState; + +static gboolean +fallback_layer_cb (CoglMaterialLayer *layer, void *user_data) +{ + CoglMaterialFallbackState *state = user_data; + CoglMaterial *material = state->material; + CoglHandle texture = _cogl_material_layer_get_texture (layer); + GLenum gl_target; + COGL_STATIC_COUNTER (layer_fallback_counter, + "layer fallback counter", + "Increments each time a layer's texture is " + "forced to a fallback texture", + 0 /* no application private data */); + + _COGL_GET_CONTEXT (ctx, FALSE); + + if (!(state->fallback_layers & 1<i)) + return TRUE; + + COGL_COUNTER_INC (_cogl_uprof_context, layer_fallback_counter); + + if (G_LIKELY (texture != COGL_INVALID_HANDLE)) + cogl_texture_get_gl_texture (texture, NULL, &gl_target); + else + gl_target = GL_TEXTURE_2D; + + if (gl_target == GL_TEXTURE_2D) + texture = ctx->default_gl_texture_2d_tex; +#ifdef HAVE_COGL_GL + else if (gl_target == GL_TEXTURE_RECTANGLE_ARB) + texture = ctx->default_gl_texture_rect_tex; +#endif + else + { + g_warning ("We don't have a fallback texture we can use to fill " + "in for an invalid material layer, since it was " + "using an unsupported texture target "); + /* might get away with this... */ + texture = ctx->default_gl_texture_2d_tex; + } + + cogl_material_set_layer (material, layer->index, texture); + + state->i++; + + return TRUE; +} + +void +_cogl_material_set_layer_wrap_modes (CoglMaterial *material, + CoglMaterialLayer *layer, + CoglMaterialLayer *authority, + CoglMaterialWrapModeInternal wrap_mode_s, + CoglMaterialWrapModeInternal wrap_mode_t, + CoglMaterialWrapModeInternal wrap_mode_r) +{ + CoglMaterialLayer *new; + CoglMaterialLayerState change = COGL_MATERIAL_LAYER_STATE_WRAP_MODES; + + if (authority->wrap_mode_s == wrap_mode_s && + authority->wrap_mode_t == wrap_mode_t && + authority->wrap_mode_r == wrap_mode_r) + return; + + new = _cogl_material_layer_pre_change_notify (material, layer, change); + if (new != layer) + layer = new; + else + { + /* If the original layer we found is currently the authority on + * the state we are changing see if we can revert to one of our + * ancestors being the authority. */ + if (layer == authority && authority->parent != NULL) + { + CoglMaterialLayer *old_authority = + _cogl_material_layer_get_authority (authority->parent, change); + + if (old_authority->wrap_mode_s == wrap_mode_s && + old_authority->wrap_mode_t == wrap_mode_t && + old_authority->wrap_mode_r == wrap_mode_r) + { + layer->differences &= ~change; + + g_assert (layer->owner == material); + if (layer->differences == 0) + _cogl_material_prune_empty_layer_difference (material, + layer); + return; + } + } + } + + layer->wrap_mode_s = wrap_mode_s; + layer->wrap_mode_t = wrap_mode_t; + layer->wrap_mode_r = wrap_mode_r; + + /* If we weren't previously the authority on this state then we need + * to extended our differences mask and so it's possible that some + * of our ancestry will now become redundant, so we aim to reparent + * ourselves if that's true... */ + if (layer != authority) + { + layer->differences |= change; + _cogl_material_layer_prune_redundant_ancestry (layer); + } +} + +static CoglMaterialWrapModeInternal +public_to_internal_wrap_mode (CoglMaterialWrapMode mode) +{ + return (CoglMaterialWrapModeInternal)mode; +} + +static CoglMaterialWrapMode +internal_to_public_wrap_mode (CoglMaterialWrapModeInternal internal_mode) +{ + g_return_val_if_fail (internal_mode != + COGL_MATERIAL_WRAP_MODE_INTERNAL_CLAMP_TO_BORDER, + COGL_MATERIAL_WRAP_MODE_AUTOMATIC); + return (CoglMaterialWrapMode)internal_mode; +} + +void +cogl_material_set_layer_wrap_mode_s (CoglHandle handle, + int layer_index, + CoglMaterialWrapMode mode) +{ + CoglMaterial *material = COGL_MATERIAL (handle); + CoglMaterialLayerState change = COGL_MATERIAL_LAYER_STATE_WRAP_MODES; + CoglMaterialLayer *layer; + CoglMaterialLayer *authority; + CoglMaterialWrapModeInternal internal_mode = + public_to_internal_wrap_mode (mode); + + g_return_if_fail (cogl_is_material (handle)); + + /* Note: this will ensure that the layer exists, creating one if it + * doesn't already. + * + * Note: If the layer already existed it's possibly owned by another + * material. If the layer is created then it will be owned by + * material. */ + layer = _cogl_material_get_layer (material, layer_index); + + /* Now find the ancestor of the layer that is the authority for the + * state we want to change */ + authority = _cogl_material_layer_get_authority (layer, change); + + _cogl_material_set_layer_wrap_modes (material, layer, authority, + internal_mode, + authority->wrap_mode_t, + authority->wrap_mode_r); +} + +void +cogl_material_set_layer_wrap_mode_t (CoglHandle handle, + int layer_index, + CoglMaterialWrapMode mode) +{ + CoglMaterial *material = COGL_MATERIAL (handle); + CoglMaterialLayerState change = COGL_MATERIAL_LAYER_STATE_WRAP_MODES; + CoglMaterialLayer *layer; + CoglMaterialLayer *authority; + CoglMaterialWrapModeInternal internal_mode = + public_to_internal_wrap_mode (mode); + + g_return_if_fail (cogl_is_material (handle)); + + /* Note: this will ensure that the layer exists, creating one if it + * doesn't already. + * + * Note: If the layer already existed it's possibly owned by another + * material. If the layer is created then it will be owned by + * material. */ + layer = _cogl_material_get_layer (material, layer_index); + + /* Now find the ancestor of the layer that is the authority for the + * state we want to change */ + authority = _cogl_material_layer_get_authority (layer, change); + + _cogl_material_set_layer_wrap_modes (material, layer, authority, + authority->wrap_mode_s, + internal_mode, + authority->wrap_mode_r); +} + +/* TODO: this should be made public once we add support for 3D + textures in Cogl */ +void +_cogl_material_set_layer_wrap_mode_r (CoglHandle handle, + int layer_index, + CoglMaterialWrapMode mode) +{ + CoglMaterial *material = COGL_MATERIAL (handle); + CoglMaterialLayerState change = COGL_MATERIAL_LAYER_STATE_WRAP_MODES; + CoglMaterialLayer *layer; + CoglMaterialLayer *authority; + CoglMaterialWrapModeInternal internal_mode = + public_to_internal_wrap_mode (mode); + + g_return_if_fail (cogl_is_material (handle)); + + /* Note: this will ensure that the layer exists, creating one if it + * doesn't already. + * + * Note: If the layer already existed it's possibly owned by another + * material. If the layer is created then it will be owned by + * material. */ + layer = _cogl_material_get_layer (material, layer_index); + + /* Now find the ancestor of the layer that is the authority for the + * state we want to change */ + authority = _cogl_material_layer_get_authority (layer, change); + + _cogl_material_set_layer_wrap_modes (material, layer, authority, + authority->wrap_mode_s, + authority->wrap_mode_t, + internal_mode); +} + +void +cogl_material_set_layer_wrap_mode (CoglHandle handle, + int layer_index, + CoglMaterialWrapMode mode) +{ + CoglMaterial *material = COGL_MATERIAL (handle); + CoglMaterialLayerState change = COGL_MATERIAL_LAYER_STATE_WRAP_MODES; + CoglMaterialLayer *layer; + CoglMaterialLayer *authority; + CoglMaterialWrapModeInternal internal_mode = + public_to_internal_wrap_mode (mode); + + g_return_if_fail (cogl_is_material (handle)); + + /* Note: this will ensure that the layer exists, creating one if it + * doesn't already. + * + * Note: If the layer already existed it's possibly owned by another + * material. If the layer is created then it will be owned by + * material. */ + layer = _cogl_material_get_layer (material, layer_index); + + /* Now find the ancestor of the layer that is the authority for the + * state we want to change */ + authority = _cogl_material_layer_get_authority (layer, change); + + _cogl_material_set_layer_wrap_modes (material, layer, authority, + internal_mode, + internal_mode, + internal_mode); + /* XXX: I wonder if we should really be duplicating the mode into + * the 'r' wrap mode too? */ +} + +/* FIXME: deprecate this API */ +CoglMaterialWrapMode +cogl_material_layer_get_wrap_mode_s (CoglHandle handle) +{ + CoglMaterialLayer *layer = COGL_MATERIAL_LAYER (handle); + CoglMaterialLayerState change = COGL_MATERIAL_LAYER_STATE_WRAP_MODES; + CoglMaterialLayer *authority; + + g_return_val_if_fail (cogl_is_material_layer (handle), FALSE); + + /* Now find the ancestor of the layer that is the authority for the + * state we want to change */ + authority = _cogl_material_layer_get_authority (layer, change); + + return internal_to_public_wrap_mode (authority->wrap_mode_s); +} + +CoglMaterialWrapMode +cogl_material_get_layer_wrap_mode_s (CoglHandle handle, int layer_index) +{ + CoglMaterial *material = COGL_MATERIAL (handle); + CoglMaterialLayer *layer; + + g_return_val_if_fail (cogl_is_material (handle), FALSE); + + /* Note: this will ensure that the layer exists, creating one if it + * doesn't already. + * + * Note: If the layer already existed it's possibly owned by another + * material. If the layer is created then it will be owned by + * material. */ + layer = _cogl_material_get_layer (material, layer_index); + /* FIXME: we shouldn't ever construct a layer in a getter function */ + + return cogl_material_layer_get_wrap_mode_s ((CoglHandle)layer); +} + +/* FIXME: deprecate this API */ +CoglMaterialWrapMode +cogl_material_layer_get_wrap_mode_t (CoglHandle handle) +{ + CoglMaterialLayer *layer = COGL_MATERIAL_LAYER (handle); + CoglMaterialLayerState change = COGL_MATERIAL_LAYER_STATE_WRAP_MODES; + CoglMaterialLayer *authority; + + g_return_val_if_fail (cogl_is_material_layer (handle), FALSE); + + /* Now find the ancestor of the layer that is the authority for the + * state we want to change */ + authority = _cogl_material_layer_get_authority (layer, change); + + return internal_to_public_wrap_mode (authority->wrap_mode_t); +} + +CoglMaterialWrapMode +cogl_material_get_layer_wrap_mode_t (CoglHandle handle, int layer_index) +{ + CoglMaterial *material = COGL_MATERIAL (handle); + CoglMaterialLayer *layer; + + g_return_val_if_fail (cogl_is_material (handle), FALSE); + + /* Note: this will ensure that the layer exists, creating one if it + * doesn't already. + * + * Note: If the layer already existed it's possibly owned by another + * material. If the layer is created then it will be owned by + * material. */ + layer = _cogl_material_get_layer (material, layer_index); + /* FIXME: we shouldn't ever construct a layer in a getter function */ + + return cogl_material_layer_get_wrap_mode_t ((CoglHandle)layer); +} + +CoglMaterialWrapMode +_cogl_material_layer_get_wrap_mode_r (CoglHandle handle) +{ + CoglMaterialLayer *layer = COGL_MATERIAL_LAYER (handle); + CoglMaterialLayerState change = COGL_MATERIAL_LAYER_STATE_WRAP_MODES; + CoglMaterialLayer *authority = + _cogl_material_layer_get_authority (layer, change); + + return internal_to_public_wrap_mode (authority->wrap_mode_r); +} + +/* TODO: make this public when we expose 3D textures. */ +CoglMaterialWrapMode +_cogl_material_get_layer_wrap_mode_r (CoglHandle handle, int layer_index) +{ + CoglMaterial *material = COGL_MATERIAL (handle); + CoglMaterialLayer *layer; + + g_return_val_if_fail (cogl_is_material (handle), FALSE); + + /* Note: this will ensure that the layer exists, creating one if it + * doesn't already. + * + * Note: If the layer already existed it's possibly owned by another + * material. If the layer is created then it will be owned by + * material. */ + layer = _cogl_material_get_layer (material, layer_index); + + return _cogl_material_layer_get_wrap_mode_r (layer); +} + +static void +_cogl_material_layer_get_wrap_modes (CoglMaterialLayer *layer, + CoglMaterialWrapModeInternal *wrap_mode_s, + CoglMaterialWrapModeInternal *wrap_mode_t, + CoglMaterialWrapModeInternal *wrap_mode_r) +{ + CoglMaterialLayer *authority = + _cogl_material_layer_get_authority (layer, + COGL_MATERIAL_LAYER_STATE_WRAP_MODES); + + *wrap_mode_s = authority->wrap_mode_s; + *wrap_mode_t = authority->wrap_mode_t; + *wrap_mode_r = authority->wrap_mode_r; +} + +typedef struct +{ + CoglMaterial *material; + CoglMaterialWrapModeOverrides *wrap_mode_overrides; + int i; +} CoglMaterialWrapModeOverridesState; + +static gboolean +apply_wrap_mode_overrides_cb (CoglMaterialLayer *layer, + void *user_data) +{ + CoglMaterialWrapModeOverridesState *state = user_data; + CoglMaterialLayer *authority = + _cogl_material_layer_get_authority (layer, + COGL_MATERIAL_LAYER_STATE_WRAP_MODES); + CoglMaterialWrapModeInternal wrap_mode_s; + CoglMaterialWrapModeInternal wrap_mode_t; + CoglMaterialWrapModeInternal wrap_mode_r; + + g_return_val_if_fail (state->i < 32, FALSE); + + wrap_mode_s = state->wrap_mode_overrides->values[state->i].s; + if (wrap_mode_s == COGL_MATERIAL_WRAP_MODE_OVERRIDE_NONE) + wrap_mode_s = (CoglMaterialWrapModeInternal)authority->wrap_mode_s; + wrap_mode_t = state->wrap_mode_overrides->values[state->i].t; + if (wrap_mode_t == COGL_MATERIAL_WRAP_MODE_OVERRIDE_NONE) + wrap_mode_t = (CoglMaterialWrapModeInternal)authority->wrap_mode_t; + wrap_mode_r = state->wrap_mode_overrides->values[state->i].r; + if (wrap_mode_r == COGL_MATERIAL_WRAP_MODE_OVERRIDE_NONE) + wrap_mode_r = (CoglMaterialWrapModeInternal)authority->wrap_mode_r; + + _cogl_material_set_layer_wrap_modes (state->material, + layer, + authority, + wrap_mode_s, + wrap_mode_t, + wrap_mode_r); + + state->i++; + + return TRUE; +} + +typedef struct +{ + CoglMaterial *material; + GLuint gl_texture; +} CoglMaterialOverrideLayerState; + +static gboolean +override_layer_texture_cb (CoglMaterialLayer *layer, void *user_data) +{ + CoglMaterialOverrideLayerState *state = user_data; + CoglHandle texture; + GLenum gl_target; + + texture = _cogl_material_layer_get_texture (layer); + + if (texture != COGL_INVALID_HANDLE) + gl_target = cogl_texture_get_gl_texture (texture, NULL, &gl_target); + else + gl_target = GL_TEXTURE_2D; + + _cogl_material_set_layer_gl_texture_slice (state->material, + layer->index, + texture, + state->gl_texture, + gl_target); + return TRUE; +} + +static void +_cogl_material_apply_overrides (CoglMaterial *material, + CoglMaterialFlushOptions *options) +{ + COGL_STATIC_COUNTER (apply_overrides_counter, + "material overrides counter", + "Increments each time we have to apply " + "override options to a material", + 0 /* no application private data */); + + COGL_COUNTER_INC (_cogl_uprof_context, apply_overrides_counter); + + if (options->flags & COGL_MATERIAL_FLUSH_DISABLE_MASK) + { + int i; + + /* NB: we can assume that once we see one bit to disable + * a layer, all subsequent layers are also disabled. */ + for (i = 0; i < 32 && options->disable_layers & (1<flags & COGL_MATERIAL_FLUSH_FALLBACK_MASK) + { + CoglMaterialFallbackState state; + + state.i = 0; + state.material = material; + state.fallback_layers = options->fallback_layers; + + _cogl_material_foreach_layer (material, + fallback_layer_cb, + &state); + } + + if (options->flags & COGL_MATERIAL_FLUSH_LAYER0_OVERRIDE) + { + CoglMaterialOverrideLayerState state; + + _cogl_material_prune_to_n_layers (material, 1); + + /* NB: we are overriding the first layer, but we don't know + * the user's given layer_index, which is why we use + * _cogl_material_foreach_layer() here even though we know + * there's only one layer. */ + state.material = material; + state.gl_texture = options->layer0_override_texture; + _cogl_material_foreach_layer (material, + override_layer_texture_cb, + &state); + } + + if (options->flags & COGL_MATERIAL_FLUSH_WRAP_MODE_OVERRIDES) + { + CoglMaterialWrapModeOverridesState state; + + state.material = material; + state.wrap_mode_overrides = &options->wrap_mode_overrides; + state.i = 0; + _cogl_material_foreach_layer (material, + apply_wrap_mode_overrides_cb, + &state); + } +} + +static gboolean +_cogl_material_layer_texture_equal (CoglMaterialLayer *authority0, + CoglMaterialLayer *authority1) +{ + if (authority0->texture != authority1->texture) + return FALSE; + return TRUE; +} + +/* Determine the mask of differences between two layers. + * + * XXX: If layers and materials could both be cast to a common Tree + * type of some kind then we could have a unified + * compare_differences() function. + */ +static unsigned long +_cogl_material_layer_compare_differences (CoglMaterialLayer *layer0, + CoglMaterialLayer *layer1) +{ + CoglMaterialLayer *node0; + CoglMaterialLayer *node1; + int len0; + int len1; + int len0_index; + int len1_index; + int count; + int i; + CoglMaterialLayer *common_ancestor = NULL; + unsigned long layers_difference = 0; + + _COGL_GET_CONTEXT (ctx, 0); + + /* Algorithm: + * + * 1) Walk the ancestors of each layer to the root node, adding a + * pointer to each ancester node to two GArrays: + * ctx->material0_nodes, and ctx->material1_nodes. + * + * 2) Compare the arrays to find the nodes where they stop to + * differ. + * + * 3) For each array now iterate from index 0 to the first node of + * difference ORing that nodes ->difference mask into the final + * material_differences mask. + */ + + g_array_set_size (ctx->material0_nodes, 0); + g_array_set_size (ctx->material1_nodes, 0); + for (node0 = layer0; node0; node0 = node0->parent) + g_array_append_vals (ctx->material0_nodes, &node0, 1); + for (node1 = layer1; node1; node1 = node1->parent) + g_array_append_vals (ctx->material1_nodes, &node1, 1); + + len0 = ctx->material0_nodes->len; + len1 = ctx->material1_nodes->len; + /* There's no point looking at the last entries since we know both + * layers must have the same default layer as their root node. */ + len0_index = len0 - 2; + len1_index = len1 - 2; + count = MIN (len0, len1) - 1; + for (i = 0; i < count; i++) + { + node0 = g_array_index (ctx->material0_nodes, + CoglMaterialLayer *, len0_index--); + node1 = g_array_index (ctx->material1_nodes, + CoglMaterialLayer *, len1_index--); + if (node0 != node1) + { + common_ancestor = node0->parent; + break; + } + } + + /* If we didn't already find the first the common_ancestor ancestor + * that's because one material is a direct descendant of the other + * and in this case the first common ancestor is the last node we + * looked at. */ + if (!common_ancestor) + common_ancestor = node0; + + count = len0 - 1; + for (i = 0; i < count; i++) + { + node0 = g_array_index (ctx->material0_nodes, CoglMaterialLayer *, i); + if (node0 == common_ancestor) + break; + layers_difference |= node0->differences; + } + + count = len1 - 1; + for (i = 0; i < count; i++) + { + node1 = g_array_index (ctx->material1_nodes, CoglMaterialLayer *, i); + if (node1 == common_ancestor) + break; + layers_difference |= node1->differences; + } + + return layers_difference; +} + +static gboolean +_cogl_material_layer_combine_state_equal (CoglMaterialLayer *authority0, + CoglMaterialLayer *authority1) +{ + CoglMaterialLayerBigState *big_state0 = authority0->big_state; + CoglMaterialLayerBigState *big_state1 = authority1->big_state; + int n_args; + int i; + + if (big_state0->texture_combine_rgb_func != + big_state1->texture_combine_rgb_func) + return FALSE; + + if (big_state0->texture_combine_alpha_func != + big_state1->texture_combine_alpha_func) + return FALSE; + + n_args = + 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 = + get_n_args_for_combine_func (big_state0->texture_combine_alpha_func); + for (i = 0; i < n_args; i++) + { + if ((big_state0->texture_combine_alpha_src[i] != + big_state1->texture_combine_alpha_src[i]) || + (big_state0->texture_combine_alpha_op[i] != + big_state1->texture_combine_alpha_op[i])) + return FALSE; + } + + return TRUE; +} + +static gboolean +_cogl_material_layer_combine_constant_equal (CoglMaterialLayer *authority0, + CoglMaterialLayer *authority1) +{ + return memcmp (authority0->big_state->texture_combine_constant, + authority1->big_state->texture_combine_constant, + sizeof (float) * 4) == 0 ? TRUE : FALSE; +} + +static gboolean +_cogl_material_layer_filters_equal (CoglMaterialLayer *authority0, + CoglMaterialLayer *authority1) +{ + if (authority0->mag_filter != authority1->mag_filter) + return FALSE; + if (authority0->min_filter != authority1->min_filter) + return FALSE; + + return TRUE; +} + +static gboolean +_cogl_material_layer_wrap_modes_equal (CoglMaterialLayer *authority0, + CoglMaterialLayer *authority1) +{ + if (authority0->wrap_mode_s != authority1->wrap_mode_s || + authority0->wrap_mode_t != authority1->wrap_mode_t || + authority0->wrap_mode_r != authority1->wrap_mode_r) + return FALSE; + + return TRUE; +} + +static gboolean +_cogl_material_layer_user_matrix_equal (CoglMaterialLayer *authority0, + CoglMaterialLayer *authority1) +{ + CoglMaterialLayerBigState *big_state0 = authority0->big_state; + CoglMaterialLayerBigState *big_state1 = authority1->big_state; + + if (!cogl_matrix_equal (&big_state0->matrix, &big_state1->matrix)) + return FALSE; + + return TRUE; +} + +typedef gboolean +(*CoglMaterialLayerStateComparitor) (CoglMaterialLayer *authority0, + CoglMaterialLayer *authority1); + +static gboolean +layer_state_equal (CoglMaterialLayerState state, + CoglMaterialLayer *layer0, + CoglMaterialLayer *layer1, + CoglMaterialLayerStateComparitor comparitor) +{ + CoglMaterialLayer *authority0 = + _cogl_material_layer_get_authority (layer0, state); + CoglMaterialLayer *authority1 = + _cogl_material_layer_get_authority (layer1, state); + + return comparitor (authority0, authority1); +} + +static gboolean +_cogl_material_layer_equal (CoglMaterialLayer *layer0, + CoglMaterialLayer *layer1) +{ + unsigned long layers_difference; + + if (layer0 == layer1) + return TRUE; + + layers_difference = + _cogl_material_layer_compare_differences (layer0, layer1); + + if (layers_difference & COGL_MATERIAL_LAYER_STATE_TEXTURE && + !layer_state_equal (COGL_MATERIAL_LAYER_STATE_TEXTURE, + layer0, layer1, + _cogl_material_layer_texture_equal)) + return FALSE; + + if (layers_difference & COGL_MATERIAL_LAYER_STATE_COMBINE && + !layer_state_equal (COGL_MATERIAL_LAYER_STATE_COMBINE, + layer0, layer1, + _cogl_material_layer_combine_state_equal)) + return FALSE; + + if (layers_difference & COGL_MATERIAL_LAYER_STATE_COMBINE_CONSTANT && + !layer_state_equal (COGL_MATERIAL_LAYER_STATE_COMBINE_CONSTANT, + layer0, layer1, + _cogl_material_layer_combine_constant_equal)) + return FALSE; + + if (layers_difference & COGL_MATERIAL_LAYER_STATE_FILTERS && + !layer_state_equal (COGL_MATERIAL_LAYER_STATE_FILTERS, + layer0, layer1, + _cogl_material_layer_filters_equal)) + return FALSE; + + if (layers_difference & COGL_MATERIAL_LAYER_STATE_WRAP_MODES && + !layer_state_equal (COGL_MATERIAL_LAYER_STATE_WRAP_MODES, + layer0, layer1, + _cogl_material_layer_wrap_modes_equal)) + return FALSE; + + if (layers_difference & COGL_MATERIAL_LAYER_STATE_USER_MATRIX && + !layer_state_equal (COGL_MATERIAL_LAYER_STATE_USER_MATRIX, + layer0, layer1, + _cogl_material_layer_user_matrix_equal)) + return FALSE; + + return TRUE; +} + +static gboolean +_cogl_material_color_equal (CoglMaterial *authority0, + CoglMaterial *authority1) +{ + return cogl_color_equal (&authority0->color, &authority1->color); +} + +static gboolean +_cogl_material_lighting_state_equal (CoglMaterial *authority0, + CoglMaterial *authority1) +{ + CoglMaterialLightingState *state0 = &authority0->big_state->lighting_state; + CoglMaterialLightingState *state1 = &authority1->big_state->lighting_state; + + if (memcmp (state0->ambient, state1->ambient, sizeof (float) * 4) != 0) + return FALSE; + if (memcmp (state0->diffuse, state1->diffuse, sizeof (float) * 4) != 0) + return FALSE; + if (memcmp (state0->specular, state1->specular, sizeof (float) * 4) != 0) + return FALSE; + if (memcmp (state0->emission, state1->emission, sizeof (float) * 4) != 0) + return FALSE; + if (state0->shininess != state1->shininess) + return FALSE; + + return TRUE; +} + +static gboolean +_cogl_material_alpha_state_equal (CoglMaterial *authority0, + CoglMaterial *authority1) +{ + CoglMaterialAlphaFuncState *alpha_state0 = + &authority0->big_state->alpha_state; + CoglMaterialAlphaFuncState *alpha_state1 = + &authority1->big_state->alpha_state; + + if (alpha_state0->alpha_func != alpha_state1->alpha_func || + alpha_state0->alpha_func_reference != alpha_state1->alpha_func_reference) + return FALSE; + else + return TRUE; +} + +static gboolean +_cogl_material_blend_state_equal (CoglMaterial *authority0, + CoglMaterial *authority1) +{ + CoglMaterialBlendState *blend_state0 = &authority0->big_state->blend_state; + CoglMaterialBlendState *blend_state1 = &authority1->big_state->blend_state; + +#ifndef HAVE_COGL_GLES + if (blend_state0->blend_equation_rgb != blend_state1->blend_equation_rgb) + return FALSE; + if (blend_state0->blend_equation_alpha != + blend_state1->blend_equation_alpha) + return FALSE; + if (blend_state0->blend_src_factor_alpha != + blend_state1->blend_src_factor_alpha) + return FALSE; + if (blend_state0->blend_dst_factor_alpha != + blend_state1->blend_dst_factor_alpha) + return FALSE; +#endif + if (blend_state0->blend_src_factor_rgb != + blend_state1->blend_src_factor_rgb) + return FALSE; + if (blend_state0->blend_dst_factor_rgb != + blend_state1->blend_dst_factor_rgb) + return FALSE; +#ifndef HAVE_COGL_GLES + if (!cogl_color_equal (&blend_state0->blend_constant, + &blend_state1->blend_constant)) + return FALSE; +#endif + + return TRUE; +} + +static gboolean +_cogl_material_layers_equal (CoglMaterial *authority0, + CoglMaterial *authority1) +{ + int i; + + if (authority0->n_layers != authority1->n_layers) + return FALSE; + + _cogl_material_update_layers_cache (authority0); + _cogl_material_update_layers_cache (authority1); + + for (i = 0; i < authority0->n_layers; i++) + { + if (!_cogl_material_layer_equal (authority0->layers_cache[i], + authority1->layers_cache[i])) + return FALSE; + } + return TRUE; +} + +/* Determine the mask of differences between two materials */ +static unsigned long +_cogl_material_compare_differences (CoglMaterial *material0, + CoglMaterial *material1) +{ + CoglMaterial *node0; + CoglMaterial *node1; + int len0; + int len1; + int len0_index; + int len1_index; + int count; + int i; + CoglMaterial *common_ancestor = NULL; + unsigned long materials_difference = 0; + + _COGL_GET_CONTEXT (ctx, 0); + + /* Algorithm: + * + * 1) Walk the ancestors of each layer to the root node, adding a + * pointer to each ancester node to two GArrays: + * ctx->material0_nodes, and ctx->material1_nodes. + * + * 2) Compare the arrays to find the nodes where they stop to + * differ. + * + * 3) For each array now iterate from index 0 to the first node of + * difference ORing that nodes ->difference mask into the final + * material_differences mask. + */ + + g_array_set_size (ctx->material0_nodes, 0); + g_array_set_size (ctx->material1_nodes, 0); + for (node0 = material0; node0; node0 = node0->parent) + g_array_append_vals (ctx->material0_nodes, &node0, 1); + for (node1 = material1; node1; node1 = node1->parent) + g_array_append_vals (ctx->material1_nodes, &node1, 1); + + len0 = ctx->material0_nodes->len; + len1 = ctx->material1_nodes->len; + /* There's no point looking at the last entries since we know both + * layers must have the same default layer as their root node. */ + len0_index = len0 - 2; + len1_index = len1 - 2; + count = MIN (len0, len1) - 1; + for (i = 0; i < count; i++) + { + node0 = g_array_index (ctx->material0_nodes, + CoglMaterial *, len0_index--); + node1 = g_array_index (ctx->material1_nodes, + CoglMaterial *, len1_index--); + if (node0 != node1) + { + common_ancestor = node0->parent; + break; + } + } + + /* If we didn't already find the first the common_ancestor ancestor + * that's because one material is a direct descendant of the other + * and in this case the first common ancestor is the last node we + * looked at. */ + if (!common_ancestor) + common_ancestor = node0; + + count = len0 - 1; + for (i = 0; i < count; i++) + { + node0 = g_array_index (ctx->material0_nodes, CoglMaterial *, i); + if (node0 == common_ancestor) + break; + materials_difference |= node0->differences; + } + + count = len1 - 1; + for (i = 0; i < count; i++) + { + node1 = g_array_index (ctx->material1_nodes, CoglMaterial *, i); + if (node1 == common_ancestor) + break; + materials_difference |= node1->differences; + } + + return materials_difference; + +} + +/* Comparison of two arbitrary materials is done by: + * 1) walking up the parents of each material until a common + * ancestor is found, and at each step ORing together the + * difference masks. + * + * 2) using the final difference mask to determine which state + * groups to compare. + * + * This is used by the Cogl journal to compare materials so that it + * can split up geometry that needs different OpenGL state. + * + * It is acceptable to have false negatives - although they will result + * in redundant OpenGL calls that try and update the state. + * + * False positives aren't allowed. + */ +gboolean +_cogl_material_equal (CoglHandle material0_handle, + CoglMaterialFlushOptions *material0_flush_options, + CoglHandle material1_handle, + CoglMaterialFlushOptions *material1_flush_options, + gboolean skip_gl_color) +{ + CoglMaterial *material0; + CoglMaterial *material1; + gboolean material0_overridden = FALSE; + gboolean material1_overridden = FALSE; + unsigned long materials_difference; + gboolean equal = FALSE; + + if (material0_flush_options->flags) + { + /* XXX: we really need to get rid of overrides! */ + material0 = cogl_material_copy (material0_handle); + _cogl_material_apply_overrides (material0, material0_flush_options); + material0_overridden = TRUE; + } + else + material0 = COGL_MATERIAL (material0_handle); + + if (material1_flush_options->flags) + { + /* XXX: we really need to get rid of overrides! */ + material1 = cogl_material_copy (material1_handle); + _cogl_material_apply_overrides (material1, material1_flush_options); + material1_overridden = TRUE; + } + else + material1 = COGL_MATERIAL (material1_handle); + + if (material0 == material1) + goto done; + + /* First check non-sparse properties */ + + if (material0->real_blend_enable != material1->real_blend_enable) + goto done; + + /* Then check sparse properties */ + + materials_difference = + _cogl_material_compare_differences (material0, material1); + + if (materials_difference & COGL_MATERIAL_STATE_COLOR && + !skip_gl_color) + { + CoglMaterial *authority0 = + _cogl_material_get_authority (material0, + COGL_MATERIAL_STATE_COLOR); + CoglMaterial *authority1 = + _cogl_material_get_authority (material1, + COGL_MATERIAL_STATE_COLOR); + + if (!cogl_color_equal (&authority0->color, &authority1->color)) + goto done; + } + + if (materials_difference & COGL_MATERIAL_STATE_LIGHTING) + { + CoglMaterial *authority0 = + _cogl_material_get_authority (material0, + COGL_MATERIAL_STATE_LIGHTING); + CoglMaterial *authority1 = + _cogl_material_get_authority (material1, + COGL_MATERIAL_STATE_LIGHTING); + + if (!_cogl_material_lighting_state_equal (authority0, authority1)) + goto done; + } + + if (materials_difference & COGL_MATERIAL_STATE_ALPHA_FUNC) + { + CoglMaterial *authority0 = + _cogl_material_get_authority (material0, + COGL_MATERIAL_STATE_ALPHA_FUNC); + CoglMaterial *authority1 = + _cogl_material_get_authority (material1, + COGL_MATERIAL_STATE_ALPHA_FUNC); + + if (!_cogl_material_alpha_state_equal (authority0, authority1)) + goto done; + } + + if (materials_difference & COGL_MATERIAL_STATE_BLEND) + { + CoglMaterial *authority0 = + _cogl_material_get_authority (material0, + COGL_MATERIAL_STATE_BLEND); + CoglMaterial *authority1 = + _cogl_material_get_authority (material1, + COGL_MATERIAL_STATE_BLEND); + + if (!_cogl_material_blend_state_equal (authority0, authority1)) + goto done; + } + + if (materials_difference & COGL_MATERIAL_STATE_BLEND_ENABLE) + { + CoglMaterial *authority0 = + _cogl_material_get_authority (material0, + COGL_MATERIAL_STATE_BLEND_ENABLE); + CoglMaterial *authority1 = + _cogl_material_get_authority (material1, + COGL_MATERIAL_STATE_BLEND_ENABLE); + + if (authority0->blend_enable != authority1->blend_enable) + goto done; + } + + if (materials_difference & COGL_MATERIAL_STATE_LAYERS) + { + CoglMaterial *authority0 = + _cogl_material_get_authority (material0, + COGL_MATERIAL_STATE_LAYERS); + CoglMaterial *authority1 = + _cogl_material_get_authority (material1, + COGL_MATERIAL_STATE_LAYERS); + + if (!_cogl_material_layers_equal (authority0, authority1)) + goto done; + } + + equal = TRUE; + +done: + + if (material0_overridden) + cogl_handle_unref (material0); + if (material1_overridden) + cogl_handle_unref (material1); + + return equal; } void cogl_material_get_color (CoglHandle handle, CoglColor *color) { - CoglMaterial *material; + CoglMaterial *material = COGL_MATERIAL (handle); + CoglMaterial *authority; g_return_if_fail (cogl_is_material (handle)); - material = _cogl_material_pointer_from_handle (handle); + authority = + _cogl_material_get_authority (material, COGL_MATERIAL_STATE_COLOR); - cogl_color_set_from_4ub (color, - material->unlit[0], - material->unlit[1], - material->unlit[2], - material->unlit[3]); + *color = authority->color; } /* This is used heavily by the cogl journal when logging quads */ @@ -430,42 +3162,91 @@ void _cogl_material_get_colorubv (CoglHandle handle, guint8 *color) { - CoglMaterial *material = _cogl_material_pointer_from_handle (handle); - memcpy (color, material->unlit, 4); + CoglMaterial *material = COGL_MATERIAL (handle); + CoglMaterial *authority = + _cogl_material_get_authority (material, COGL_MATERIAL_STATE_COLOR); + + _cogl_color_get_rgba_4ubv (&authority->color, color); +} + +static void +_cogl_material_prune_redundant_ancestry (CoglMaterial *material) +{ + CoglMaterial *new_parent = material->parent; + + /* walk up past ancestors that are now redundant and potentially + * reparent the material. */ + while (new_parent->parent && + (new_parent->differences | material->differences) == + material->differences) + new_parent = new_parent->parent; + + if (new_parent != material->parent) + { + CoglMaterial *old_parent = material->parent; + material->parent = cogl_handle_ref (new_parent); + /* Note: the old parent may indirectly be keeping + the new parent alive so we have to ref the new + parent before unrefing the old */ + cogl_handle_unref (old_parent); + } +} + +static void +_cogl_material_update_authority (CoglMaterial *material, + CoglMaterial *authority, + CoglMaterialState state, + CoglMaterialStateComparitor comparitor) +{ + /* If we are the current authority see if we can revert to one of + * our ancestors being the authority */ + if (material == authority && authority->parent != NULL) + { + CoglMaterial *old_authority = + _cogl_material_get_authority (authority->parent, state); + + if (comparitor (authority, old_authority)) + material->differences &= ~state; + } + else if (material != authority) + { + /* If we weren't previously the authority on this state then we + * need to extended our differences mask and so it's possible + * that some of our ancestry will now become redundant, so we + * aim to reparent ourselves if that's true... */ + material->differences |= state; + _cogl_material_prune_redundant_ancestry (material); + } } void cogl_material_set_color (CoglHandle handle, - const CoglColor *unlit_color) + const CoglColor *color) { - CoglMaterial *material; - GLubyte unlit[4]; + CoglMaterial *material = COGL_MATERIAL (handle); + CoglMaterialState state = COGL_MATERIAL_STATE_COLOR; + CoglMaterial *authority; g_return_if_fail (cogl_is_material (handle)); - material = _cogl_material_pointer_from_handle (handle); + authority = _cogl_material_get_authority (material, state); - unlit[0] = cogl_color_get_red_byte (unlit_color); - unlit[1] = cogl_color_get_green_byte (unlit_color); - unlit[2] = cogl_color_get_blue_byte (unlit_color); - unlit[3] = cogl_color_get_alpha_byte (unlit_color); - if (memcmp (unlit, material->unlit, sizeof (unlit)) == 0) + if (cogl_color_equal (color, &authority->color)) return; - /* possibly flush primitives referencing the current state... */ - _cogl_material_pre_change_notify (material, COGL_MATERIAL_CHANGE_COLOR, - unlit); + /* - Flush journal primitives referencing the current state. + * - Make sure the material has no dependants so it may be modified. + * - If the material isn't currently an authority for the state being + * changed, then initialize that state from the current authority. + */ + _cogl_material_pre_change_notify (material, state, color); - memcpy (material->unlit, unlit, sizeof (unlit)); + material->color = *color; - material->flags &= ~COGL_MATERIAL_FLAG_DEFAULT_COLOR; - if (unlit[0] == 0xff && - unlit[1] == 0xff && - unlit[2] == 0xff && - unlit[3] == 0xff) - material->flags |= COGL_MATERIAL_FLAG_DEFAULT_COLOR; + _cogl_material_update_authority (material, authority, state, + _cogl_material_color_equal); - handle_automatic_blend_enable (material); + handle_automatic_blend_enable (material, state); } void @@ -492,92 +3273,161 @@ cogl_material_set_color4f (CoglHandle handle, cogl_material_set_color (handle, &color); } +CoglMaterialBlendEnable +_cogl_material_get_blend_enabled (CoglHandle handle) +{ + CoglMaterial *material = COGL_MATERIAL (handle);; + CoglMaterial *authority; + + g_return_val_if_fail (cogl_is_material (handle), FALSE); + + authority = + _cogl_material_get_authority (material, COGL_MATERIAL_STATE_BLEND_ENABLE); + return authority->blend_enable; +} + +static gboolean +_cogl_material_blend_enable_equal (CoglMaterial *authority0, + CoglMaterial *authority1) +{ + return authority0->blend_enable == authority1->blend_enable ? TRUE : FALSE; +} + +void +_cogl_material_set_blend_enabled (CoglHandle handle, + CoglMaterialBlendEnable enable) +{ + CoglMaterial *material = COGL_MATERIAL (handle); + CoglMaterialState state = COGL_MATERIAL_STATE_BLEND_ENABLE; + CoglMaterial *authority; + + g_return_if_fail (cogl_is_material (handle)); + g_return_if_fail (enable > 1 && + "don't pass TRUE or FALSE to _set_blend_enabled!"); + + authority = _cogl_material_get_authority (material, state); + + if (authority->blend_enable == enable) + return; + + /* - Flush journal primitives referencing the current state. + * - Make sure the material has no dependants so it may be modified. + * - If the material isn't currently an authority for the state being + * changed, then initialize that state from the current authority. + */ + _cogl_material_pre_change_notify (material, state, NULL); + + material->blend_enable = enable; + + _cogl_material_update_authority (material, authority, state, + _cogl_material_blend_enable_equal); + + handle_automatic_blend_enable (material, state); +} + void cogl_material_get_ambient (CoglHandle handle, CoglColor *ambient) { - CoglMaterial *material; + CoglMaterial *material = COGL_MATERIAL (handle); + CoglMaterial *authority; g_return_if_fail (cogl_is_material (handle)); - material = _cogl_material_pointer_from_handle (handle); + authority = + _cogl_material_get_authority (material, COGL_MATERIAL_STATE_LIGHTING); - cogl_color_set_from_4f (ambient, - material->ambient[0], - material->ambient[1], - material->ambient[2], - material->ambient[3]); + cogl_color_init_from_4fv (ambient, + authority->big_state->lighting_state.ambient); } void cogl_material_set_ambient (CoglHandle handle, - const CoglColor *ambient_color) + const CoglColor *ambient) { - CoglMaterial *material; - GLfloat *ambient; + CoglMaterial *material = COGL_MATERIAL (handle); + CoglMaterialState state = COGL_MATERIAL_STATE_LIGHTING; + CoglMaterial *authority; + CoglMaterialLightingState *lighting_state; g_return_if_fail (cogl_is_material (handle)); - material = _cogl_material_pointer_from_handle (handle); + authority = _cogl_material_get_authority (material, state); - /* possibly flush primitives referencing the current state... */ - _cogl_material_pre_change_notify (material, - COGL_MATERIAL_CHANGE_GL_MATERIAL, - NULL); + lighting_state = &authority->big_state->lighting_state; + if (cogl_color_equal (ambient, &lighting_state->ambient)) + return; - ambient = material->ambient; - ambient[0] = cogl_color_get_red_float (ambient_color); - ambient[1] = cogl_color_get_green_float (ambient_color); - ambient[2] = cogl_color_get_blue_float (ambient_color); - ambient[3] = cogl_color_get_alpha_float (ambient_color); + /* - Flush journal primitives referencing the current state. + * - Make sure the material has no dependants so it may be modified. + * - If the material isn't currently an authority for the state being + * changed, then initialize that state from the current authority. + */ + _cogl_material_pre_change_notify (material, state, NULL); - material->flags &= ~COGL_MATERIAL_FLAG_DEFAULT_GL_MATERIAL; + lighting_state = &material->big_state->lighting_state; + lighting_state->ambient[0] = cogl_color_get_red_float (ambient); + lighting_state->ambient[1] = cogl_color_get_green_float (ambient); + lighting_state->ambient[2] = cogl_color_get_blue_float (ambient); + lighting_state->ambient[3] = cogl_color_get_alpha_float (ambient); - handle_automatic_blend_enable (material); + _cogl_material_update_authority (material, authority, state, + _cogl_material_lighting_state_equal); + + handle_automatic_blend_enable (material, state); } void cogl_material_get_diffuse (CoglHandle handle, CoglColor *diffuse) { - CoglMaterial *material; + CoglMaterial *material = COGL_MATERIAL (handle); + CoglMaterial *authority; g_return_if_fail (cogl_is_material (handle)); - material = _cogl_material_pointer_from_handle (handle); + authority = + _cogl_material_get_authority (material, COGL_MATERIAL_STATE_LIGHTING); - cogl_color_set_from_4f (diffuse, - material->diffuse[0], - material->diffuse[1], - material->diffuse[2], - material->diffuse[3]); + cogl_color_init_from_4fv (diffuse, + authority->big_state->lighting_state.diffuse); } void cogl_material_set_diffuse (CoglHandle handle, - const CoglColor *diffuse_color) + const CoglColor *diffuse) { - CoglMaterial *material; - GLfloat *diffuse; + CoglMaterial *material = COGL_MATERIAL (handle); + CoglMaterialState state = COGL_MATERIAL_STATE_LIGHTING; + CoglMaterial *authority; + CoglMaterialLightingState *lighting_state; g_return_if_fail (cogl_is_material (handle)); - material = _cogl_material_pointer_from_handle (handle); + authority = _cogl_material_get_authority (material, state); - /* possibly flush primitives referencing the current state... */ - _cogl_material_pre_change_notify (material, - COGL_MATERIAL_CHANGE_GL_MATERIAL, - NULL); + lighting_state = &authority->big_state->lighting_state; + if (cogl_color_equal (diffuse, &lighting_state->diffuse)) + return; - diffuse = material->diffuse; - diffuse[0] = cogl_color_get_red_float (diffuse_color); - diffuse[1] = cogl_color_get_green_float (diffuse_color); - diffuse[2] = cogl_color_get_blue_float (diffuse_color); - diffuse[3] = cogl_color_get_alpha_float (diffuse_color); + /* - Flush journal primitives referencing the current state. + * - Make sure the material has no dependants so it may be modified. + * - If the material isn't currently an authority for the state being + * changed, then initialize that state from the current authority. + */ + _cogl_material_pre_change_notify (material, state, NULL); - material->flags &= ~COGL_MATERIAL_FLAG_DEFAULT_GL_MATERIAL; + lighting_state = &material->big_state->lighting_state; + lighting_state->diffuse[0] = cogl_color_get_red_float (diffuse); + lighting_state->diffuse[1] = cogl_color_get_green_float (diffuse); + lighting_state->diffuse[2] = cogl_color_get_blue_float (diffuse); + lighting_state->diffuse[3] = cogl_color_get_alpha_float (diffuse); - handle_automatic_blend_enable (material); + + _cogl_material_update_authority (material, authority, state, + _cogl_material_lighting_state_equal); + + handle_automatic_blend_enable (material, state); } void @@ -592,124 +3442,154 @@ void cogl_material_get_specular (CoglHandle handle, CoglColor *specular) { - CoglMaterial *material; + CoglMaterial *authority = COGL_MATERIAL (handle); g_return_if_fail (cogl_is_material (handle)); - material = _cogl_material_pointer_from_handle (handle); + while (!(authority->differences & COGL_MATERIAL_STATE_LIGHTING)) + authority = authority->parent; - cogl_color_set_from_4f (specular, - material->specular[0], - material->specular[1], - material->specular[2], - material->specular[3]); + cogl_color_init_from_4fv (specular, + authority->big_state->lighting_state.specular); } void -cogl_material_set_specular (CoglHandle handle, - const CoglColor *specular_color) +cogl_material_set_specular (CoglHandle handle, const CoglColor *specular) { - CoglMaterial *material; - GLfloat *specular; + CoglMaterial *material = COGL_MATERIAL (handle); + CoglMaterial *authority; + CoglMaterialState state = COGL_MATERIAL_STATE_LIGHTING; + CoglMaterialLightingState *lighting_state; g_return_if_fail (cogl_is_material (handle)); - material = _cogl_material_pointer_from_handle (handle); + authority = _cogl_material_get_authority (material, state); - /* possibly flush primitives referencing the current state... */ - _cogl_material_pre_change_notify (material, - COGL_MATERIAL_CHANGE_GL_MATERIAL, - NULL); + lighting_state = &authority->big_state->lighting_state; + if (cogl_color_equal (specular, &lighting_state->specular)) + return; - specular = material->specular; - specular[0] = cogl_color_get_red_float (specular_color); - specular[1] = cogl_color_get_green_float (specular_color); - specular[2] = cogl_color_get_blue_float (specular_color); - specular[3] = cogl_color_get_alpha_float (specular_color); + /* - Flush journal primitives referencing the current state. + * - Make sure the material has no dependants so it may be modified. + * - If the material isn't currently an authority for the state being + * changed, then initialize that state from the current authority. + */ + _cogl_material_pre_change_notify (material, state, NULL); - material->flags &= ~COGL_MATERIAL_FLAG_DEFAULT_GL_MATERIAL; + lighting_state = &material->big_state->lighting_state; + lighting_state->specular[0] = cogl_color_get_red_float (specular); + lighting_state->specular[1] = cogl_color_get_green_float (specular); + lighting_state->specular[2] = cogl_color_get_blue_float (specular); + lighting_state->specular[3] = cogl_color_get_alpha_float (specular); - handle_automatic_blend_enable (material); + _cogl_material_update_authority (material, authority, state, + _cogl_material_lighting_state_equal); + + handle_automatic_blend_enable (material, state); } float cogl_material_get_shininess (CoglHandle handle) { - CoglMaterial *material; + CoglMaterial *material = COGL_MATERIAL (handle); + CoglMaterial *authority; g_return_val_if_fail (cogl_is_material (handle), 0); - material = _cogl_material_pointer_from_handle (handle); + authority = + _cogl_material_get_authority (material, COGL_MATERIAL_STATE_LIGHTING); - return material->shininess; + return authority->big_state->lighting_state.shininess; } void cogl_material_set_shininess (CoglHandle handle, float shininess) { - CoglMaterial *material; + CoglMaterial *material = COGL_MATERIAL (handle); + CoglMaterial *authority; + CoglMaterialState state = COGL_MATERIAL_STATE_LIGHTING; + CoglMaterialLightingState *lighting_state; g_return_if_fail (cogl_is_material (handle)); if (shininess < 0.0 || shininess > 1.0) - g_warning ("Out of range shininess %f supplied for material\n", - shininess); + { + g_warning ("Out of range shininess %f supplied for material\n", + shininess); + return; + } - material = _cogl_material_pointer_from_handle (handle); + authority = _cogl_material_get_authority (material, state); - /* possibly flush primitives referencing the current state... */ - _cogl_material_pre_change_notify (material, - COGL_MATERIAL_CHANGE_GL_MATERIAL, - NULL); + lighting_state = &authority->big_state->lighting_state; - material->shininess = (GLfloat)shininess * 128.0; + if (lighting_state->shininess == shininess) + return; - material->flags &= ~COGL_MATERIAL_FLAG_DEFAULT_GL_MATERIAL; + /* - Flush journal primitives referencing the current state. + * - Make sure the material has no dependants so it may be modified. + * - If the material isn't currently an authority for the state being + * changed, then initialize that state from the current authority. + */ + _cogl_material_pre_change_notify (material, state, NULL); + + lighting_state = &material->big_state->lighting_state; + lighting_state->shininess = shininess; + + _cogl_material_update_authority (material, authority, state, + _cogl_material_lighting_state_equal); } void cogl_material_get_emission (CoglHandle handle, CoglColor *emission) { - CoglMaterial *material; + CoglMaterial *material = COGL_MATERIAL (handle); + CoglMaterial *authority; g_return_if_fail (cogl_is_material (handle)); - material = _cogl_material_pointer_from_handle (handle); + authority = + _cogl_material_get_authority (material, COGL_MATERIAL_STATE_LIGHTING); - cogl_color_set_from_4f (emission, - material->emission[0], - material->emission[1], - material->emission[2], - material->emission[3]); + cogl_color_init_from_4fv (emission, + authority->big_state->lighting_state.emission); } void -cogl_material_set_emission (CoglHandle handle, - const CoglColor *emission_color) +cogl_material_set_emission (CoglHandle handle, const CoglColor *emission) { - CoglMaterial *material; - GLfloat *emission; + CoglMaterial *material = COGL_MATERIAL (handle); + CoglMaterial *authority; + CoglMaterialState state = COGL_MATERIAL_STATE_LIGHTING; + CoglMaterialLightingState *lighting_state; g_return_if_fail (cogl_is_material (handle)); - material = _cogl_material_pointer_from_handle (handle); + authority = _cogl_material_get_authority (material, state); - /* possibly flush primitives referencing the current state... */ - _cogl_material_pre_change_notify (material, - COGL_MATERIAL_CHANGE_GL_MATERIAL, - NULL); + lighting_state = &authority->big_state->lighting_state; + if (cogl_color_equal (emission, &lighting_state->emission)) + return; - emission = material->emission; - emission[0] = cogl_color_get_red_float (emission_color); - emission[1] = cogl_color_get_green_float (emission_color); - emission[2] = cogl_color_get_blue_float (emission_color); - emission[3] = cogl_color_get_alpha_float (emission_color); + /* - Flush journal primitives referencing the current state. + * - Make sure the material has no dependants so it may be modified. + * - If the material isn't currently an authority for the state being + * changed, then initialize that state from the current authority. + */ + _cogl_material_pre_change_notify (material, state, NULL); - material->flags &= ~COGL_MATERIAL_FLAG_DEFAULT_GL_MATERIAL; + lighting_state = &material->big_state->lighting_state; + lighting_state->emission[0] = cogl_color_get_red_float (emission); + lighting_state->emission[1] = cogl_color_get_green_float (emission); + lighting_state->emission[2] = cogl_color_get_blue_float (emission); + lighting_state->emission[3] = cogl_color_get_alpha_float (emission); - handle_automatic_blend_enable (material); + _cogl_material_update_authority (material, authority, state, + _cogl_material_lighting_state_equal); + + handle_automatic_blend_enable (material, state); } void @@ -717,21 +3597,33 @@ cogl_material_set_alpha_test_function (CoglHandle handle, CoglMaterialAlphaFunc alpha_func, float alpha_reference) { - CoglMaterial *material; + CoglMaterial *material = COGL_MATERIAL (handle); + CoglMaterialState state = COGL_MATERIAL_STATE_ALPHA_FUNC; + CoglMaterial *authority; + CoglMaterialAlphaFuncState *alpha_state; g_return_if_fail (cogl_is_material (handle)); - material = _cogl_material_pointer_from_handle (handle); + authority = _cogl_material_get_authority (material, state); - /* possibly flush primitives referencing the current state... */ - _cogl_material_pre_change_notify (material, - COGL_MATERIAL_CHANGE_ALPHA_FUNC, - NULL); + alpha_state = &authority->big_state->alpha_state; + if (alpha_state->alpha_func == alpha_func && + alpha_state->alpha_func_reference == alpha_reference) + return; - material->alpha_func = alpha_func; - material->alpha_func_reference = (GLfloat)alpha_reference; + /* - Flush journal primitives referencing the current state. + * - Make sure the material has no dependants so it may be modified. + * - If the material isn't currently an authority for the state being + * changed, then initialize that state from the current authority. + */ + _cogl_material_pre_change_notify (material, state, NULL); - material->flags &= ~COGL_MATERIAL_FLAG_DEFAULT_ALPHA_FUNC; + alpha_state = &material->big_state->alpha_state; + alpha_state->alpha_func = alpha_func; + alpha_state->alpha_func_reference = alpha_reference; + + _cogl_material_update_authority (material, authority, state, + _cogl_material_alpha_state_equal); } GLenum @@ -832,17 +3724,18 @@ cogl_material_set_blend (CoglHandle handle, const char *blend_description, GError **error) { - CoglMaterial *material; + CoglMaterial *material = COGL_MATERIAL (handle); + CoglMaterialState state = COGL_MATERIAL_STATE_BLEND; + CoglMaterial *authority; CoglBlendStringStatement statements[2]; CoglBlendStringStatement *rgb; CoglBlendStringStatement *a; GError *internal_error = NULL; int count; + CoglMaterialBlendState *blend_state; g_return_val_if_fail (cogl_is_material (handle), FALSE); - material = _cogl_material_pointer_from_handle (handle); - count = _cogl_blend_string_compile (blend_description, COGL_BLEND_STRING_CONTEXT_BLENDING, @@ -869,28 +3762,55 @@ cogl_material_set_blend (CoglHandle handle, a = &statements[1]; } - /* possibly flush primitives referencing the current state... */ - _cogl_material_pre_change_notify (material, - COGL_MATERIAL_CHANGE_BLEND, - NULL); + authority = + _cogl_material_get_authority (material, state); + /* - Flush journal primitives referencing the current state. + * - Make sure the material has no dependants so it may be modified. + * - If the material isn't currently an authority for the state being + * changed, then initialize that state from the current authority. + */ + _cogl_material_pre_change_notify (material, state, NULL); + + blend_state = &material->big_state->blend_state; #ifndef HAVE_COGL_GLES setup_blend_state (rgb, - &material->blend_equation_rgb, - &material->blend_src_factor_rgb, - &material->blend_dst_factor_rgb); + &blend_state->blend_equation_rgb, + &blend_state->blend_src_factor_rgb, + &blend_state->blend_dst_factor_rgb); setup_blend_state (a, - &material->blend_equation_alpha, - &material->blend_src_factor_alpha, - &material->blend_dst_factor_alpha); + &blend_state->blend_equation_alpha, + &blend_state->blend_src_factor_alpha, + &blend_state->blend_dst_factor_alpha); #else setup_blend_state (rgb, NULL, - &material->blend_src_factor_rgb, - &material->blend_dst_factor_rgb); + &blend_state->blend_src_factor_rgb, + &blend_state->blend_dst_factor_rgb); #endif - material->flags &= ~COGL_MATERIAL_FLAG_DEFAULT_BLEND; + /* If we are the current authority see if we can revert to one of our + * ancestors being the authority */ + if (material == authority && authority->parent != NULL) + { + CoglMaterial *old_authority = + _cogl_material_get_authority (authority->parent, state); + + if (_cogl_material_blend_state_equal (authority, old_authority)) + material->differences &= ~state; + } + + /* If we weren't previously the authority on this state then we need + * to extended our differences mask and so it's possible that some + * of our ancestry will now become redundant, so we aim to reparent + * ourselves if that's true... */ + if (material != authority) + { + material->differences |= state; + _cogl_material_prune_redundant_ancestry (material); + } + + handle_automatic_blend_enable (material, state); return TRUE; } @@ -900,25 +3820,33 @@ cogl_material_set_blend_constant (CoglHandle handle, CoglColor *constant_color) { #ifndef HAVE_COGL_GLES - CoglMaterial *material; - GLfloat *constant; + CoglMaterial *material = COGL_MATERIAL (handle); + CoglMaterialState state = COGL_MATERIAL_STATE_BLEND; + CoglMaterial *authority; + CoglMaterialBlendState *blend_state; g_return_if_fail (cogl_is_material (handle)); - material = _cogl_material_pointer_from_handle (handle); + authority = _cogl_material_get_authority (material, state); - /* possibly flush primitives referencing the current state... */ - _cogl_material_pre_change_notify (material, - COGL_MATERIAL_CHANGE_BLEND, - NULL); + blend_state = &authority->big_state->blend_state; + if (cogl_color_equal (constant_color, &blend_state->blend_constant)) + return; - constant = material->blend_constant; - constant[0] = cogl_color_get_red_float (constant_color); - constant[1] = cogl_color_get_green_float (constant_color); - constant[2] = cogl_color_get_blue_float (constant_color); - constant[3] = cogl_color_get_alpha_float (constant_color); + /* - Flush journal primitives referencing the current state. + * - Make sure the material has no dependants so it may be modified. + * - If the material isn't currently an authority for the state being + * changed, then initialize that state from the current authority. + */ + _cogl_material_pre_change_notify (material, state, NULL); - material->flags &= ~COGL_MATERIAL_FLAG_DEFAULT_BLEND; + blend_state = &material->big_state->blend_state; + blend_state->blend_constant = *constant_color; + + _cogl_material_update_authority (material, authority, state, + _cogl_material_blend_state_equal); + + handle_automatic_blend_enable (material, state); #endif } @@ -932,284 +3860,251 @@ void _cogl_material_set_user_program (CoglHandle handle, CoglHandle program) { - CoglMaterial *material; + CoglMaterial *material = COGL_MATERIAL (handle); + CoglMaterialState state = COGL_MATERIAL_STATE_USER_SHADER; + CoglMaterial *authority; g_return_if_fail (cogl_is_material (handle)); - material = _cogl_material_pointer_from_handle (handle); + authority = _cogl_material_get_authority (material, state); - if (material->user_program == program) + if (authority->big_state->user_program == program) return; - /* possibly flush primitives referencing the current state... */ - _cogl_material_pre_change_notify (material, - COGL_MATERIAL_CHANGE_USER_SHADER, - NULL); + /* - Flush journal primitives referencing the current state. + * - Make sure the material has no dependants so it may be modified. + * - If the material isn't currently an authority for the state being + * changed, then initialize that state from the current authority. + */ + _cogl_material_pre_change_notify (material, state, NULL); - _cogl_material_set_backend (material, COGL_MATERIAL_BACKEND_DEFAULT); + if (program != COGL_INVALID_HANDLE) + _cogl_material_set_backend (material, COGL_MATERIAL_BACKEND_DEFAULT); + + /* If we are the current authority see if we can revert to one of our + * ancestors being the authority */ + if (material == authority && authority->parent != NULL) + { + CoglMaterial *old_authority = + _cogl_material_get_authority (authority->parent, state); + + if (old_authority->big_state->user_program == program) + material->differences &= ~state; + } + else if (material != authority) + { + /* If we weren't previously the authority on this state then we + * need to extended our differences mask and so it's possible + * that some of our ancestry will now become redundant, so we + * aim to reparent ourselves if that's true... */ + material->differences |= state; + _cogl_material_prune_redundant_ancestry (material); + } if (program != COGL_INVALID_HANDLE) cogl_handle_ref (program); - if (material->user_program != COGL_INVALID_HANDLE) - cogl_handle_unref (material->user_program); - material->user_program = program; + if (authority == material && + material->big_state->user_program != COGL_INVALID_HANDLE) + cogl_handle_unref (material->big_state->user_program); + material->big_state->user_program = program; - if (program == COGL_INVALID_HANDLE) - material->flags |= COGL_MATERIAL_FLAG_DEFAULT_USER_SHADER; - else - material->flags &= ~COGL_MATERIAL_FLAG_DEFAULT_USER_SHADER; + handle_automatic_blend_enable (material, state); } -static void -texture_unit_init (CoglTextureUnit *unit, int index_) -{ - unit->index = index_; - unit->enabled = FALSE; - unit->enabled_gl_target = 0; - unit->gl_texture = 0; - unit->is_foreign = FALSE; - unit->dirty_gl_texture = FALSE; - unit->matrix_stack = _cogl_matrix_stack_new (); - - unit->layer = NULL; - unit->layer_differences = COGL_MATERIAL_LAYER_DIFFERENCE_COMBINE; - unit->fallback = FALSE; - unit->layer0_overridden = FALSE; - unit->texture = COGL_INVALID_HANDLE; -} - -static void -texture_unit_free (CoglTextureUnit *unit) -{ - _cogl_matrix_stack_destroy (unit->matrix_stack); -} - -CoglTextureUnit * -_cogl_get_texture_unit (int index_) -{ - _COGL_GET_CONTEXT (ctx, NULL); - - if (ctx->texture_units->len < (index_ + 1)) - { - int i; - int prev_len = ctx->texture_units->len; - ctx->texture_units = g_array_set_size (ctx->texture_units, index_ + 1); - for (i = prev_len; i <= index_; i++) - { - CoglTextureUnit *unit = - &g_array_index (ctx->texture_units, CoglTextureUnit, i); - - texture_unit_init (unit, i); - } - } - - return &g_array_index (ctx->texture_units, CoglTextureUnit, index_); -} - -void -_cogl_destroy_texture_units (void) +static CoglMaterialLayer * +_cogl_material_layer_copy (CoglMaterialLayer *src) { + CoglMaterialLayer *layer = g_slice_new (CoglMaterialLayer); int i; - _COGL_GET_CONTEXT (ctx, NO_RETVAL); + cogl_handle_ref ((CoglHandle)src); - for (i = 0; i < ctx->texture_units->len; i++) + layer->_parent = src->_parent; + layer->owner = NULL; + layer->parent = src; + + if (src->has_children) + src->children = g_list_prepend (src->children, layer); + else { - CoglTextureUnit *unit = - &g_array_index (ctx->texture_units, CoglTextureUnit, i); - texture_unit_free (unit); + src->has_children = TRUE; + src->first_child = layer; + src->children = NULL; } - g_array_free (ctx->texture_units, TRUE); + + layer->has_children = FALSE; + layer->index = src->index; + layer->differences = 0; + layer->has_big_state = FALSE; + + for (i = 0; i < COGL_MATERIAL_N_BACKENDS; i++) + layer->backend_priv[i] = NULL; + + return _cogl_material_layer_handle_new (layer); } static void -set_active_texture_unit (int unit_index) +_cogl_material_layer_unparent (CoglMaterialLayer *layer) { - _COGL_GET_CONTEXT (ctx, NO_RETVAL); + CoglMaterialLayer *parent = layer->parent; - if (ctx->active_texture_unit != unit_index) - { - GE (glActiveTexture (GL_TEXTURE0 + unit_index)); - ctx->active_texture_unit = unit_index; - } -} - -/* Note: this conceptually has slightly different semantics to - * OpenGL's glBindTexture because Cogl never cares about tracking - * multiple textures bound to different targets on the same texture - * unit. - * - * glBindTexture lets you bind multiple textures to a single texture - * unit if they are bound to different targets. So it does something - * like: - * unit->current_texture[target] = texture; - * - * Cogl only lets you associate one texture with the currently active - * texture unit, so the target is basically a redundant parameter - * that's implicitly set on that texture. - * - * Technically this is just a thin wrapper around glBindTexture so - * actually it does have the GL semantics but it seems worth - * mentioning the conceptual difference in case anyone wonders why we - * don't associate the gl_texture with a gl_target in the - * CoglTextureUnit. - */ -void -_cogl_bind_gl_texture_transient (GLenum gl_target, - GLuint gl_texture, - gboolean is_foreign) -{ - CoglTextureUnit *unit; - - _COGL_GET_CONTEXT (ctx, NO_RETVAL); - - unit = _cogl_get_texture_unit (ctx->active_texture_unit); - - /* NB: If we have previously bound a foreign texture to this texture - * unit we don't know if that texture has since been deleted and we - * are seeing the texture name recycled */ - if (unit->gl_texture == gl_texture && - !unit->dirty_gl_texture && - !unit->is_foreign) + if (parent == NULL) return; - GE (glBindTexture (gl_target, gl_texture)); + g_return_if_fail (parent->has_children); - unit->dirty_gl_texture = TRUE; - unit->is_foreign = is_foreign; + if (parent->first_child == layer) + { + 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, layer); + + cogl_handle_unref (parent); } -void -_cogl_delete_gl_texture (GLuint gl_texture) +static void +_cogl_material_layer_free (CoglMaterialLayer *layer) { int i; + _cogl_material_layer_unparent (layer); + + /* NB: layers may be used by multiple materials which may be using + * different backends, therefore we determine which backends to + * notify based on the private state pointers for each backend... + */ + for (i = 0; i < COGL_MATERIAL_N_BACKENDS; i++) + { + if (layer->backend_priv[i] && backends[i]->free_layer_priv) + backends[i]->free_layer_priv (layer); + } + + if (layer->differences & COGL_MATERIAL_LAYER_STATE_TEXTURE) + cogl_handle_unref (layer->texture); + + if (layer->differences & COGL_MATERIAL_LAYER_STATE_NEEDS_BIG_STATE) + g_slice_free (CoglMaterialLayerBigState, layer->big_state); + + g_slice_free (CoglMaterialLayer, layer); +} + + /* If a layer has descendants we can't modify it freely + * + * If the layer is owned and the owner has descendants we can't + * modify it freely. + * + * In both cases when we can't freely modify a layer we can either: + * - create a new layer; splice it in to replace the layer so it can + * be directly modified. + * XXX: disadvantage is that we have to invalidate the layers_cache + * for the owner and its descendants. + * - create a new derived layer and modify that. + */ + + /* XXX: how is the caller expected to deal with ref-counting? + * + * If the layer can't be freely modified and we return a new layer + * then that will effectively make the caller own a new reference + * which doesn't happen if we simply modify the given layer. + * + * We could make it consistent by taking a reference on the layer if + * we don't create a new one. At least this way the caller could + * deal with it consistently, though the semantics are a bit + * strange. + * + * Alternatively we could leave it to the caller to check + * ...? + */ + +void +_cogl_material_init_default_layers (void) +{ + CoglMaterialLayer *layer = g_slice_new0 (CoglMaterialLayer); + CoglMaterialLayerBigState *big_state = + g_slice_new0 (CoglMaterialLayerBigState); + CoglMaterialLayer *new; + int i; + _COGL_GET_CONTEXT (ctx, NO_RETVAL); - for (i = 0; i < ctx->texture_units->len; i++) - { - CoglTextureUnit *unit = - &g_array_index (ctx->texture_units, CoglTextureUnit, i); + layer->has_children = FALSE; + layer->index = 0; - if (unit->gl_texture == gl_texture) - { - unit->gl_texture = 0; - unit->dirty_gl_texture = FALSE; - } - } -} + for (i = 0; i < COGL_MATERIAL_N_BACKENDS; i++) + layer->backend_priv[i] = NULL; -/* Asserts that a layer corresponding to the given index exists. If no - * match is found, then a new empty layer is added. - */ -static CoglMaterialLayer * -_cogl_material_get_layer (CoglMaterial *material, - int index_, - gboolean create_if_not_found) -{ - CoglMaterialLayer *layer; - GList *l; - CoglHandle layer_handle; - int i; + layer->differences = COGL_MATERIAL_LAYER_STATE_ALL_SPARSE; - for (l = material->layers, i = 0; l != NULL; l = l->next, i++) - { - layer = l->data; - if (layer->index == index_) - return layer; + layer->unit_index = 0; - /* The layers are always sorted, so at this point we know this layer - * doesn't exist */ - if (layer->index > index_) - break; - } - /* NB: if we now insert a new layer before l, that will maintain order. - */ + layer->texture = COGL_INVALID_HANDLE; + layer->texture_overridden = FALSE; - if (!create_if_not_found) - return NULL; - - /* possibly flush primitives referencing the current state... */ - _cogl_material_pre_change_notify (material, - COGL_MATERIAL_CHANGE_LAYERS, - NULL); - - layer = g_slice_new0 (CoglMaterialLayer); - - layer_handle = _cogl_material_layer_handle_new (layer); - layer->material = material; - layer->index = index_; - layer->differences = 0; layer->mag_filter = COGL_MATERIAL_FILTER_LINEAR; layer->min_filter = COGL_MATERIAL_FILTER_LINEAR; + layer->wrap_mode_s = COGL_MATERIAL_WRAP_MODE_AUTOMATIC; layer->wrap_mode_t = COGL_MATERIAL_WRAP_MODE_AUTOMATIC; layer->wrap_mode_r = COGL_MATERIAL_WRAP_MODE_AUTOMATIC; - layer->texture = COGL_INVALID_HANDLE; - layer->unit_index = i; + layer->big_state = big_state; + layer->has_big_state = TRUE; /* Choose the same default combine mode as OpenGL: - * MODULATE(PREVIOUS[RGBA],TEXTURE[RGBA]) */ - layer->texture_combine_rgb_func = GL_MODULATE; - layer->texture_combine_rgb_src[0] = GL_PREVIOUS; - layer->texture_combine_rgb_src[1] = GL_TEXTURE; - layer->texture_combine_rgb_op[0] = GL_SRC_COLOR; - layer->texture_combine_rgb_op[1] = GL_SRC_COLOR; - layer->texture_combine_alpha_func = GL_MODULATE; - layer->texture_combine_alpha_src[0] = GL_PREVIOUS; - layer->texture_combine_alpha_src[1] = GL_TEXTURE; - layer->texture_combine_alpha_op[0] = GL_SRC_ALPHA; - layer->texture_combine_alpha_op[1] = GL_SRC_ALPHA; + * 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; - cogl_matrix_init_identity (&layer->matrix); + cogl_matrix_init_identity (&big_state->matrix); - /* Note: see comment after for() loop above */ - material->layers = - g_list_insert_before (material->layers, l, layer_handle); + ctx->default_layer_0 = _cogl_material_layer_handle_new (layer); - material->flags &= ~COGL_MATERIAL_FLAG_DEFAULT_LAYERS; + /* TODO: we should make default_layer_n comprise of two + * descendants of default_layer_0: + * - the first descendant should change the texture combine + * to what we expect is most commonly used for multitexturing + * - the second should revert the above change. + * + * why? the documentation for how a new layer is initialized + * doesn't say that layers > 0 have different defaults so unless + * we change the documentation we can't use different defaults, + * but if the user does what we expect and changes the + * texture combine then we can revert the authority to the + * first descendant which means we can maximize the number + * of layers with a common ancestor. + * + * The main problem will be that we'll need to disable the + * optimizations for flattening the ancestry when we make + * the second descendant which reverts the state. + */ + ctx->default_layer_n = _cogl_material_layer_copy (layer); + new = _cogl_material_set_layer_unit (NULL, ctx->default_layer_n, 1); + g_assert (new == ctx->default_layer_n); + /* Since we passed a newly allocated layer we don't expect that + * _set_layer_unit() will have to allocate *another* layer. */ - material->n_layers++; - - return layer; -} - -void -cogl_material_set_layer (CoglHandle material_handle, - int layer_index, - CoglHandle texture_handle) -{ - CoglMaterial *material; - CoglMaterialLayer *layer; - - g_return_if_fail (cogl_is_material (material_handle)); - g_return_if_fail (texture_handle == COGL_INVALID_HANDLE - || cogl_is_texture (texture_handle)); - - material = _cogl_material_pointer_from_handle (material_handle); - - layer = _cogl_material_get_layer (material, layer_index, TRUE); - - if (texture_handle == layer->texture) - return; - - /* possibly flush primitives referencing the current state... */ - _cogl_material_pre_change_notify (material, - COGL_MATERIAL_CHANGE_LAYERS, - NULL); - - if (texture_handle) - cogl_handle_ref (texture_handle); - - if (layer->texture) - cogl_handle_unref (layer->texture); - - layer->texture = texture_handle; - - handle_automatic_blend_enable (material); - - layer->differences |= COGL_MATERIAL_LAYER_DIFFERENCE_TEXTURE; + /* Finally we create a dummy dependant for ->default_layer_n which + * effectively ensures that ->default_layer_n and ->default_layer_0 + * remain immutable. + */ + ctx->dummy_layer_dependant = + _cogl_material_layer_copy (ctx->default_layer_n); } static void @@ -1298,7 +4193,9 @@ cogl_material_set_layer_combine (CoglHandle handle, const char *combine_description, GError **error) { - CoglMaterial *material; + CoglMaterial *material = COGL_MATERIAL (handle); + CoglMaterialLayerState state = COGL_MATERIAL_LAYER_STATE_COMBINE; + CoglMaterialLayer *authority; CoglMaterialLayer *layer; CoglBlendStringStatement statements[2]; CoglBlendStringStatement split[2]; @@ -1309,8 +4206,17 @@ cogl_material_set_layer_combine (CoglHandle handle, g_return_val_if_fail (cogl_is_material (handle), FALSE); - material = _cogl_material_pointer_from_handle (handle); - layer = _cogl_material_get_layer (material, layer_index, TRUE); + /* Note: this will ensure that the layer exists, creating one if it + * doesn't already. + * + * Note: If the layer already existed it's possibly owned by another + * material. If the layer is created then it will be owned by + * material. */ + layer = _cogl_material_get_layer (material, layer_index); + + /* Now find the ancestor of the layer that is the authority for the + * state we want to change */ + authority = _cogl_material_layer_get_authority (layer, state); count = _cogl_blend_string_compile (combine_description, @@ -1343,23 +4249,55 @@ cogl_material_set_layer_combine (CoglHandle handle, a = &statements[1]; } + /* FIXME: compare the new state with the current state! */ + /* possibly flush primitives referencing the current state... */ - _cogl_material_layer_pre_change_notify ( - layer, - COGL_MATERIAL_LAYER_CHANGE_COMBINE); + layer = _cogl_material_layer_pre_change_notify (material, layer, state); setup_texture_combine_state (rgb, - &layer->texture_combine_rgb_func, - layer->texture_combine_rgb_src, - layer->texture_combine_rgb_op); + &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->texture_combine_alpha_func, - layer->texture_combine_alpha_src, - layer->texture_combine_alpha_op); + &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 && authority->parent != NULL) + { + CoglMaterialLayer *old_authority = + _cogl_material_layer_get_authority (authority->parent, state); - layer->differences |= COGL_MATERIAL_LAYER_DIFFERENCE_COMBINE; + if (_cogl_material_layer_combine_state_equal (authority, + old_authority)) + { + layer->differences &= ~state; + + g_assert (layer->owner == material); + if (layer->differences == 0) + _cogl_material_prune_empty_layer_difference (material, + layer); + goto changed; + } + } + + /* If we weren't previously the authority on this state then we need + * to extended our differences mask and so it's possible that some + * of our ancestry will now become redundant, so we aim to reparent + * ourselves if that's true... */ + if (layer != authority) + { + layer->differences |= state; + _cogl_material_layer_prune_redundant_ancestry (layer); + } + +changed: + + handle_automatic_blend_enable (material, COGL_MATERIAL_STATE_LAYERS); return TRUE; } @@ -1368,228 +4306,289 @@ cogl_material_set_layer_combine_constant (CoglHandle handle, int layer_index, CoglColor *constant_color) { - CoglMaterial *material; - CoglMaterialLayer *layer; - GLfloat *constant; + CoglMaterial *material = COGL_MATERIAL (handle); + CoglMaterialLayerState state = COGL_MATERIAL_LAYER_STATE_COMBINE_CONSTANT; + CoglMaterialLayer *layer; + CoglMaterialLayer *authority; + CoglMaterialLayer *new; g_return_if_fail (cogl_is_material (handle)); - material = _cogl_material_pointer_from_handle (handle); - layer = _cogl_material_get_layer (material, layer_index, TRUE); + /* Note: this will ensure that the layer exists, creating one if it + * doesn't already. + * + * Note: If the layer already existed it's possibly owned by another + * material. If the layer is created then it will be owned by + * material. */ + layer = _cogl_material_get_layer (material, layer_index); - /* possibly flush primitives referencing the current state... */ - _cogl_material_layer_pre_change_notify ( - layer, - COGL_MATERIAL_LAYER_CHANGE_COMBINE_CONSTANT); + /* Now find the ancestor of the layer that is the authority for the + * state we want to change */ + authority = _cogl_material_layer_get_authority (layer, state); - constant = layer->texture_combine_constant; - constant[0] = cogl_color_get_red_float (constant_color); - constant[1] = cogl_color_get_green_float (constant_color); - constant[2] = cogl_color_get_blue_float (constant_color); - constant[3] = cogl_color_get_alpha_float (constant_color); - - layer->differences |= COGL_MATERIAL_LAYER_DIFFERENCE_COMBINE_CONSTANT; -} - -void -cogl_material_set_layer_matrix (CoglHandle material_handle, - int layer_index, - CoglMatrix *matrix) -{ - CoglMaterial *material; - CoglMaterialLayer *layer; - - static gboolean initialized_identity_matrix = FALSE; - static CoglMatrix identity_matrix; - - g_return_if_fail (cogl_is_material (material_handle)); - - material = _cogl_material_pointer_from_handle (material_handle); - layer = _cogl_material_get_layer (material, layer_index, TRUE); - - if (cogl_matrix_equal (matrix, &layer->matrix)) + if (memcmp (authority->big_state->texture_combine_constant, + constant_color, sizeof (float) * 4) == 0) return; - /* possibly flush primitives referencing the current state... */ - _cogl_material_layer_pre_change_notify ( - layer, - COGL_MATERIAL_LAYER_CHANGE_USER_MATRIX); - layer->matrix = *matrix; - - if (G_UNLIKELY (!initialized_identity_matrix)) - cogl_matrix_init_identity (&identity_matrix); - - if (cogl_matrix_equal (matrix, &identity_matrix)) - layer->differences &= ~COGL_MATERIAL_LAYER_DIFFERENCE_USER_MATRIX; + new = _cogl_material_layer_pre_change_notify (material, layer, state); + if (new != layer) + layer = new; else - layer->differences |= COGL_MATERIAL_LAYER_DIFFERENCE_USER_MATRIX; -} - -static void -_cogl_material_layer_free (CoglMaterialLayer *layer) -{ - CoglTextureUnit *unit = _cogl_get_texture_unit (layer->unit_index); - - /* Since we're freeing the layer make sure the texture unit no - * longer keeps a back reference to it */ - if (unit->layer == layer) - unit->layer = NULL; - - if (layer->texture != COGL_INVALID_HANDLE) - cogl_handle_unref (layer->texture); - g_slice_free (CoglMaterialLayer, layer); -} - -void -cogl_material_remove_layer (CoglHandle material_handle, - int layer_index) -{ - CoglMaterial *material; - CoglMaterialLayer *layer; - GList *l; - GList *l2; - gboolean found = FALSE; - int i; - - g_return_if_fail (cogl_is_material (material_handle)); - - material = _cogl_material_pointer_from_handle (material_handle); - - for (l = material->layers, i = 0; l != NULL; l = l2, i++) { - /* were going to be modifying the list and continuing to iterate - * it so we get the pointer to the next link now... */ - l2 = l->next; - - layer = l->data; - if (layer->index == layer_index) - { - CoglHandle handle = (CoglHandle) layer; - - found = TRUE; - - /* possibly flush primitives referencing the current state... */ - _cogl_material_pre_change_notify (material, - COGL_MATERIAL_CHANGE_LAYERS, - NULL); - - cogl_handle_unref (handle); - material->layers = g_list_delete_link (material->layers, l); - material->n_layers--; - - /* We need to iterate through the rest of the layers - * updating the texture unit that they reference. */ - continue; - } - - /* All layers following a removed layer need to have their - * associated texture unit updated... */ - if (found) + /* 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 && authority->parent != NULL) { - _cogl_material_layer_pre_change_notify ( - layer, - COGL_MATERIAL_LAYER_CHANGE_UNIT); - layer->unit_index = i; + CoglMaterialLayer *old_authority = + _cogl_material_layer_get_authority (authority->parent, state); + CoglMaterialLayerBigState *old_big_state = old_authority->big_state; + + if (memcmp (old_big_state->texture_combine_constant, + constant_color, sizeof (float) * 4) == 0) + { + layer->differences &= ~state; + + g_assert (layer->owner == material); + if (layer->differences == 0) + _cogl_material_prune_empty_layer_difference (material, + layer); + goto changed; + } } } - handle_automatic_blend_enable (material); + layer->big_state->texture_combine_constant[0] = + cogl_color_get_red_float (constant_color); + layer->big_state->texture_combine_constant[1] = + cogl_color_get_green_float (constant_color); + layer->big_state->texture_combine_constant[2] = + cogl_color_get_blue_float (constant_color); + layer->big_state->texture_combine_constant[3] = + cogl_color_get_alpha_float (constant_color); + + /* If we weren't previously the authority on this state then we need + * to extended our differences mask and so it's possible that some + * of our ancestry will now become redundant, so we aim to reparent + * ourselves if that's true... */ + if (layer != authority) + { + layer->differences |= state; + _cogl_material_layer_prune_redundant_ancestry (layer); + } + +changed: + + handle_automatic_blend_enable (material, COGL_MATERIAL_STATE_LAYERS); } -/* XXX: This API is hopfully just a stop-gap solution. Ideally _cogl_enable - * will be replaced. */ -unsigned long -_cogl_material_get_cogl_enable_flags (CoglHandle material_handle) +void +cogl_material_set_layer_matrix (CoglHandle handle, + int layer_index, + CoglMatrix *matrix) { - CoglMaterial *material; - unsigned long enable_flags = 0; + CoglMaterial *material = COGL_MATERIAL (handle); + CoglMaterialLayerState state = COGL_MATERIAL_LAYER_STATE_USER_MATRIX; + CoglMaterialLayer *layer; + CoglMaterialLayer *authority; + CoglMaterialLayer *new; - _COGL_GET_CONTEXT (ctx, 0); + g_return_if_fail (cogl_is_material (handle)); - g_return_val_if_fail (cogl_is_material (material_handle), 0); + /* Note: this will ensure that the layer exists, creating one if it + * doesn't already. + * + * Note: If the layer already existed it's possibly owned by another + * material. If the layer is created then it will be owned by + * material. */ + layer = _cogl_material_get_layer (material, layer_index); - material = _cogl_material_pointer_from_handle (material_handle); + /* Now find the ancestor of the layer that is the authority for the + * state we want to change */ + authority = _cogl_material_layer_get_authority (layer, state); - /* Enable blending if the geometry has an associated alpha color, - * or the material wants blending enabled. */ - if (material->flags & COGL_MATERIAL_FLAG_ENABLE_BLEND) - enable_flags |= COGL_ENABLE_BLEND; + if (cogl_matrix_equal (matrix, &authority->big_state->matrix)) + return; - return enable_flags; + new = _cogl_material_layer_pre_change_notify (material, layer, state); + if (new != layer) + layer = new; + else + { + /* If the original layer we found is currently the authority on + * the state we are changing see if we can revert to one of our + * ancestors being the authority. */ + if (layer == authority && authority->parent != NULL) + { + CoglMaterialLayer *old_authority = + _cogl_material_layer_get_authority (authority->parent, state); + + if (cogl_matrix_equal (matrix, &old_authority->big_state->matrix)) + { + layer->differences &= ~state; + + g_assert (layer->owner == material); + if (layer->differences == 0) + _cogl_material_prune_empty_layer_difference (material, + layer); + return; + } + } + } + + layer->big_state->matrix = *matrix; + + /* If we weren't previously the authority on this state then we need + * to extended our differences mask and so it's possible that some + * of our ancestry will now become redundant, so we aim to reparent + * ourselves if that's true... */ + if (layer != authority) + { + layer->differences |= state; + _cogl_material_layer_prune_redundant_ancestry (layer); + } } -/* It's a bit out of the ordinary to return a const GList *, but it's - * probably sensible to try and avoid list manipulation for every - * primitive emitted in a scene, every frame. - * - * Alternatively; we could either add a _foreach function, or maybe - * a function that gets a passed a buffer (that may be stack allocated) - * by the caller. +void +cogl_material_remove_layer (CoglHandle handle, int layer_index) +{ + CoglMaterial *material = COGL_MATERIAL (handle); + CoglMaterial *authority; + CoglMaterialLayerInfo layer_info; + int i; + + g_return_if_fail (cogl_is_material (handle)); + + authority = + _cogl_material_get_authority (material, COGL_MATERIAL_STATE_LAYERS); + + /* The layer index of the layer we want info about */ + layer_info.layer_index = layer_index; + + /* This will be updated with a reference to the layer being removed + * if it can be found. */ + layer_info.layer = NULL; + + /* This will be filled in with a list of layers that need to be + * dropped down to a lower texture unit to fill the gap of the + * removed layer. */ + layer_info.layers_to_shift = + g_alloca (sizeof (CoglMaterialLayer *) * authority->n_layers); + layer_info.n_layers_to_shift = 0; + + /* Unlike when we query layer info when adding a layer we must + * always have a complete layers_to_shift list... */ + layer_info.ignore_shift_layers_if_found = FALSE; + + _cogl_material_get_layer_info (authority, &layer_info); + + if (layer_info.layer == NULL) + return; + + for (i = 0; i < layer_info.n_layers_to_shift; i++) + { + CoglMaterialLayer *shift_layer = layer_info.layers_to_shift[i]; + int unit_index = _cogl_material_layer_get_unit_index (shift_layer); + _cogl_material_set_layer_unit (material, shift_layer, unit_index - 1); + /* NB: shift_layer may not be writeable so _set_layer_unit() + * will allocate a derived layer internally which will become + * owned by material. Check the return value if we need to do + * anything else with this layer. */ + } + + _cogl_material_remove_layer_difference (material, layer_info.layer, TRUE); + _cogl_material_try_reverting_layers_authority (material, NULL); + + handle_automatic_blend_enable (material, COGL_MATERIAL_STATE_LAYERS); +} + +static gboolean +prepend_layer_to_list_cb (CoglMaterialLayer *layer, + void *user_data) +{ + GList **layers = user_data; + + *layers = g_list_prepend (*layers, layer); + return TRUE; +} + +/* TODO: deprecate this API and replace it with + * cogl_material_foreach_layer + * TODO: update the docs to note that if the user modifies any layers + * then the list may become invalid. */ const GList * -cogl_material_get_layers (CoglHandle material_handle) +cogl_material_get_layers (CoglHandle handle) { - CoglMaterial *material; + CoglMaterial *material = COGL_MATERIAL (handle); - g_return_val_if_fail (cogl_is_material (material_handle), NULL); + g_return_val_if_fail (cogl_is_material (handle), NULL); - material = _cogl_material_pointer_from_handle (material_handle); + if (!material->deprecated_get_layers_list_dirty) + g_list_free (material->deprecated_get_layers_list); - return material->layers; + material->deprecated_get_layers_list = NULL; + + _cogl_material_foreach_layer (material, + prepend_layer_to_list_cb, + &material->deprecated_get_layers_list); + material->deprecated_get_layers_list = + g_list_reverse (material->deprecated_get_layers_list); + + material->deprecated_get_layers_list_dirty = 0; + + return material->deprecated_get_layers_list; } int -cogl_material_get_n_layers (CoglHandle material_handle) +cogl_material_get_n_layers (CoglHandle handle) { - CoglMaterial *material; + CoglMaterial *material = COGL_MATERIAL (handle); + CoglMaterial *authority; - g_return_val_if_fail (cogl_is_material (material_handle), 0); + g_return_val_if_fail (cogl_is_material (handle), 0); - material = _cogl_material_pointer_from_handle (material_handle); + authority = + _cogl_material_get_authority (material, COGL_MATERIAL_STATE_LAYERS); - return material->n_layers; + return authority->n_layers; } +/* FIXME: deprecate and replace with + * cogl_material_get_layer_type() instead. */ CoglMaterialLayerType cogl_material_layer_get_type (CoglHandle layer_handle) { return COGL_MATERIAL_LAYER_TYPE_TEXTURE; } +/* FIXME: deprecate and replace with + * cogl_material_get_layer_texture() instead. */ CoglHandle -cogl_material_layer_get_texture (CoglHandle layer_handle) +cogl_material_layer_get_texture (CoglHandle handle) { - CoglMaterialLayer *layer; + CoglMaterialLayer *layer = COGL_MATERIAL_LAYER (handle); - g_return_val_if_fail (cogl_is_material_layer (layer_handle), + g_return_val_if_fail (cogl_is_material_layer (handle), COGL_INVALID_HANDLE); - layer = _cogl_material_layer_pointer_from_handle (layer_handle); - return layer->texture; + return _cogl_material_layer_get_texture (layer); } gboolean -_cogl_material_layer_has_user_matrix (CoglHandle layer_handle) +_cogl_material_layer_has_user_matrix (CoglHandle handle) { - CoglMaterialLayer *layer = - _cogl_material_layer_pointer_from_handle (layer_handle); - return layer->differences & COGL_MATERIAL_LAYER_CHANGE_USER_MATRIX ? - TRUE : FALSE; -} + CoglMaterialLayer *layer = COGL_MATERIAL_LAYER (handle); + CoglMaterialLayer *authority; -static CoglHandle -_cogl_material_layer_copy (CoglHandle layer_handle) -{ - CoglMaterialLayer *layer = - _cogl_material_layer_pointer_from_handle (layer_handle); - CoglMaterialLayer *layer_copy = g_slice_new (CoglMaterialLayer); + g_return_val_if_fail (cogl_is_material_layer (handle), FALSE); - memcpy (layer_copy, layer, sizeof (CoglMaterialLayer)); + authority = + _cogl_material_layer_get_authority (layer, + COGL_MATERIAL_LAYER_STATE_USER_MATRIX); - if (layer_copy->texture != COGL_INVALID_HANDLE) - cogl_handle_ref (layer_copy->texture); - - return _cogl_material_layer_handle_new (layer_copy); + /* If the authority is the default material then no, otherwise yes */ + return authority->parent ? TRUE : FALSE; } static gboolean @@ -1601,24 +4600,139 @@ is_mipmap_filter (CoglMaterialFilter filter) || filter == COGL_MATERIAL_FILTER_LINEAR_MIPMAP_LINEAR); } -#ifndef HAVE_COGL_GLES -static int -get_max_texture_image_units (void) +static void +_cogl_material_layer_get_filters (CoglMaterialLayer *layer, + CoglMaterialFilter *min_filter, + CoglMaterialFilter *mag_filter) { - _COGL_GET_CONTEXT (ctx, 0); + CoglMaterialLayer *authority = + _cogl_material_layer_get_authority (layer, + COGL_MATERIAL_LAYER_STATE_FILTERS); - /* This function is called quite often so we cache the value to - avoid too many GL calls */ - if (G_UNLIKELY (ctx->max_texture_image_units == -1)) + *min_filter = authority->min_filter; + *mag_filter = authority->mag_filter; +} + +void +_cogl_material_layer_ensure_mipmaps (CoglHandle handle) +{ + CoglMaterialLayer *layer = COGL_MATERIAL_LAYER (handle); + CoglMaterialLayer *texture_authority; + CoglMaterialFilter min_filter; + CoglMaterialFilter mag_filter; + + texture_authority = + _cogl_material_layer_get_authority (layer, + COGL_MATERIAL_LAYER_STATE_TEXTURE); + + _cogl_material_layer_get_filters (layer, &min_filter, &mag_filter); + + if (texture_authority->texture != COGL_INVALID_HANDLE && + (is_mipmap_filter (min_filter) || + is_mipmap_filter (mag_filter))) + _cogl_texture_ensure_mipmaps (texture_authority->texture); +} + +CoglMaterialFilter +cogl_material_layer_get_min_filter (CoglHandle handle) +{ + CoglMaterialLayer *layer = COGL_MATERIAL_LAYER (handle); + CoglMaterialLayer *authority; + + g_return_val_if_fail (cogl_is_material_layer (handle), 0); + + authority = + _cogl_material_layer_get_authority (layer, + COGL_MATERIAL_LAYER_STATE_FILTERS); + + return authority->min_filter; +} + +CoglMaterialFilter +cogl_material_layer_get_mag_filter (CoglHandle handle) +{ + CoglMaterialLayer *layer = COGL_MATERIAL_LAYER (handle); + CoglMaterialLayer *authority; + + g_return_val_if_fail (cogl_is_material_layer (handle), 0); + + authority = + _cogl_material_layer_get_authority (layer, + COGL_MATERIAL_LAYER_STATE_FILTERS); + + return authority->mag_filter; +} + +void +cogl_material_set_layer_filters (CoglHandle handle, + int layer_index, + CoglMaterialFilter min_filter, + CoglMaterialFilter mag_filter) +{ + CoglMaterial *material = COGL_MATERIAL (handle); + CoglMaterialLayerState change = COGL_MATERIAL_LAYER_STATE_FILTERS; + CoglMaterialLayer *layer; + CoglMaterialLayer *authority; + CoglMaterialLayer *new; + + g_return_if_fail (cogl_is_material (handle)); + + /* Note: this will ensure that the layer exists, creating one if it + * doesn't already. + * + * Note: If the layer already existed it's possibly owned by another + * material. If the layer is created then it will be owned by + * material. */ + layer = _cogl_material_get_layer (material, layer_index); + + /* Now find the ancestor of the layer that is the authority for the + * state we want to change */ + authority = _cogl_material_layer_get_authority (layer, change); + + if (authority->min_filter == min_filter && + authority->mag_filter == mag_filter) + return; + + new = _cogl_material_layer_pre_change_notify (material, layer, change); + if (new != layer) + layer = new; + else { - ctx->max_texture_image_units = 1; - GE (glGetIntegerv (GL_MAX_TEXTURE_IMAGE_UNITS, - &ctx->max_texture_image_units)); + /* 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 && authority->parent != NULL) + { + CoglMaterialLayer *old_authority = + _cogl_material_layer_get_authority (authority->parent, change); + + if (old_authority->min_filter == min_filter && + old_authority->mag_filter == mag_filter) + { + layer->differences &= ~change; + + g_assert (layer->owner == material); + if (layer->differences == 0) + _cogl_material_prune_empty_layer_difference (material, + layer); + return; + } + } } - return ctx->max_texture_image_units; + 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 |= change; + _cogl_material_layer_prune_redundant_ancestry (layer); + } } -#endif static void disable_texture_unit (int unit_index) @@ -1629,57 +4743,14 @@ disable_texture_unit (int unit_index) unit = &g_array_index (ctx->texture_units, CoglTextureUnit, unit_index); -#ifndef DISABLE_MATERIAL_CACHE if (unit->enabled) -#endif { set_active_texture_unit (unit_index); - GE (glDisable (unit->enabled_gl_target)); - unit->enabled_gl_target = 0; + GE (glDisable (unit->current_gl_target)); unit->enabled = FALSE; - - /* XXX: This implies that a lot of unneeded work will happen - * if a given layer somehow simply gets disabled and enabled - * without changing. Currently the public CoglMaterial API - * doesn't give a way to disable layers but one day we may - * want to avoid doing this if that changes... */ - unit->layer = NULL; } } -void -_cogl_material_layer_ensure_mipmaps (CoglHandle layer_handle) -{ - CoglMaterialLayer *layer; - - layer = _cogl_material_layer_pointer_from_handle (layer_handle); - - if (layer->texture && - (is_mipmap_filter (layer->min_filter) || - is_mipmap_filter (layer->mag_filter))) - _cogl_texture_ensure_mipmaps (layer->texture); -} - -static unsigned int -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; -} - void _cogl_gl_use_program_wrapper (GLuint program) { @@ -1799,6 +4870,26 @@ use_program (CoglHandle program_handle, CoglMaterialProgramType type) } } +#if defined (COGL_MATERIAL_BACKEND_GLSL) || \ + defined (COGL_MATERIAL_BACKEND_ARBFP) +static int +get_max_texture_image_units (void) +{ + _COGL_GET_CONTEXT (ctx, 0); + + /* This function is called quite often so we cache the value to + avoid too many GL calls */ + if (G_UNLIKELY (ctx->max_texture_image_units == -1)) + { + ctx->max_texture_image_units = 1; + GE (glGetIntegerv (GL_MAX_TEXTURE_IMAGE_UNITS, + &ctx->max_texture_image_units)); + } + + return ctx->max_texture_image_units; +} +#endif + #ifdef COGL_MATERIAL_BACKEND_GLSL static int @@ -1808,7 +4899,9 @@ _cogl_material_backend_glsl_get_max_texture_units (void) } static gboolean -_cogl_material_backend_glsl_start (CoglMaterial *material) +_cogl_material_backend_glsl_start (CoglMaterial *material, + int n_layers, + unsigned long materials_difference) { _COGL_GET_CONTEXT (ctx, FALSE); @@ -1818,10 +4911,13 @@ _cogl_material_backend_glsl_start (CoglMaterial *material) /* FIXME: This will likely conflict with the GLES 2 backends use of * glUseProgram. */ - if (!(ctx->current_material_flags & COGL_MATERIAL_FLAG_DEFAULT_USER_SHADER - && material->flags & COGL_MATERIAL_FLAG_DEFAULT_USER_SHADER)) + if (materials_difference & COGL_MATERIAL_STATE_USER_SHADER) { - CoglHandle program = material->user_program; + CoglMaterial *authority = + _cogl_material_get_authority (material, + COGL_MATERIAL_STATE_USER_SHADER); + CoglHandle program = authority->big_state->user_program; + if (program == COGL_INVALID_HANDLE) return FALSE; /* XXX: change me when we support code generation here */ @@ -1835,7 +4931,9 @@ _cogl_material_backend_glsl_start (CoglMaterial *material) } gboolean -_cogl_material_backend_glsl_add_layer (CoglMaterialLayer *layer) +_cogl_material_backend_glsl_add_layer (CoglMaterial *material, + CoglMaterialLayer *layer, + unsigned long layers_difference) { return TRUE; } @@ -1847,7 +4945,8 @@ _cogl_material_backend_glsl_passthrough (CoglMaterial *material) } gboolean -_cogl_material_backend_glsl_end (CoglMaterial *material) +_cogl_material_backend_glsl_end (CoglMaterial *material, + unsigned long materials_difference) { return TRUE; } @@ -1875,7 +4974,9 @@ _cogl_material_backend_arbfp_get_max_texture_units (void) } static gboolean -_cogl_material_backend_arbfp_start (CoglMaterial *material) +_cogl_material_backend_arbfp_start (CoglMaterial *material, + int n_layers, + unsigned long materials_difference) { CoglMaterialBackendARBfpPrivate *priv; @@ -1888,13 +4989,16 @@ _cogl_material_backend_arbfp_start (CoglMaterial *material) if (ctx->fog_enabled) return FALSE; - if (!material->backend_priv) - material->backend_priv = g_slice_new0 (CoglMaterialBackendARBfpPrivate); + if (!material->backend_priv_set) + { + material->backend_priv = g_slice_new0 (CoglMaterialBackendARBfpPrivate); + material->backend_priv_set = TRUE; + } priv = material->backend_priv; if (priv->gl_program == 0) { - /* Se reuse a single grow-only GString for ARBfp code-gen */ + /* We reuse a single grow-only GString for ARBfp code-gen */ g_string_set_size (ctx->arbfp_source_buffer, 0); priv->source = ctx->arbfp_source_buffer; g_string_append (priv->source, @@ -1905,7 +5009,7 @@ _cogl_material_backend_arbfp_start (CoglMaterial *material) "PARAM one = {1, 1, 1, 1};\n" "PARAM two = {2, 2, 2, 2};\n" "PARAM minus_one = {-1, -1, -1, -1};\n"); - priv->sampled = g_new0 (gboolean, material->n_layers); + priv->sampled = g_new0 (gboolean, n_layers); } return TRUE; @@ -1916,20 +5020,22 @@ _cogl_material_backend_arbfp_start (CoglMaterial *material) * with the same arguments... */ static gboolean -need_texture_combine_separate (CoglMaterialLayer *layer) +need_texture_combine_separate (CoglMaterialLayer *combine_authority) { + CoglMaterialLayerBigState *big_state = combine_authority->big_state; int n_args; int i; - if (layer->texture_combine_rgb_func != layer->texture_combine_alpha_func) + if (big_state->texture_combine_rgb_func != + big_state->texture_combine_alpha_func) return TRUE; - n_args = get_n_args_for_combine_func (layer->texture_combine_rgb_func); + n_args = get_n_args_for_combine_func (big_state->texture_combine_rgb_func); for (i = 0; i < n_args; i++) { - if (layer->texture_combine_rgb_src[i] != - layer->texture_combine_alpha_src[i]) + if (big_state->texture_combine_rgb_src[i] != + big_state->texture_combine_alpha_src[i]) return TRUE; /* @@ -1952,10 +5058,10 @@ need_texture_combine_separate (CoglMaterialLayer *layer) * "RGBA = REPLACE (1-CONSTANT)" or * "RGBA = REPLACE (1-CONSTANT[A])" */ - switch (layer->texture_combine_alpha_op[i]) + switch (big_state->texture_combine_alpha_op[i]) { case GL_SRC_ALPHA: - switch (layer->texture_combine_rgb_op[i]) + switch (big_state->texture_combine_rgb_op[i]) { case GL_SRC_COLOR: case GL_SRC_ALPHA: @@ -1965,7 +5071,7 @@ need_texture_combine_separate (CoglMaterialLayer *layer) } break; case GL_ONE_MINUS_SRC_ALPHA: - switch (layer->texture_combine_rgb_op[i]) + switch (big_state->texture_combine_rgb_op[i]) { case GL_ONE_MINUS_SRC_COLOR: case GL_ONE_MINUS_SRC_ALPHA: @@ -2079,36 +5185,45 @@ setup_arg (CoglMaterial *material, CoglMaterialBackendARBfpPrivate *priv = material->backend_priv; static const char *tmp_name[3] = { "tmp0", "tmp1", "tmp2" }; GLenum gl_target; + CoglHandle texture; switch (src) { case GL_TEXTURE: arg->type = COGL_MATERIAL_BACKEND_ARBFP_ARG_TYPE_TEXTURE; arg->name = "texel%d"; - arg->texture_unit = layer->unit_index; - cogl_texture_get_gl_texture (layer->texture, NULL, &gl_target); + arg->texture_unit = _cogl_material_layer_get_unit_index (layer); + texture = _cogl_material_layer_get_texture (layer); + cogl_texture_get_gl_texture (texture, NULL, &gl_target); setup_texture_source (priv, arg->texture_unit, gl_target); break; case GL_CONSTANT: - arg->type = COGL_MATERIAL_BACKEND_ARBFP_ARG_TYPE_CONSTANT; - arg->name = "constant%d"; - arg->constant_id = priv->next_constant_id++; - g_string_append_printf (priv->source, - "PARAM constant%d = " - " {%f, %f, %f, %f};\n", - arg->constant_id, - layer->texture_combine_constant[0], - layer->texture_combine_constant[1], - layer->texture_combine_constant[2], - layer->texture_combine_constant[3]); - break; + { + unsigned long state = COGL_MATERIAL_LAYER_STATE_COMBINE_CONSTANT; + CoglMaterialLayer *authority = + _cogl_material_layer_get_authority (layer, state); + CoglMaterialLayerBigState *big_state = authority->big_state; + + arg->type = COGL_MATERIAL_BACKEND_ARBFP_ARG_TYPE_CONSTANT; + arg->name = "constant%d"; + arg->constant_id = priv->next_constant_id++; + g_string_append_printf (priv->source, + "PARAM constant%d = " + " {%f, %f, %f, %f};\n", + arg->constant_id, + big_state->texture_combine_constant[0], + big_state->texture_combine_constant[1], + big_state->texture_combine_constant[2], + big_state->texture_combine_constant[3]); + break; + } case GL_PRIMARY_COLOR: arg->type = COGL_MATERIAL_BACKEND_ARBFP_ARG_TYPE_SIMPLE; arg->name = "fragment.color.primary"; break; case GL_PREVIOUS: arg->type = COGL_MATERIAL_BACKEND_ARBFP_ARG_TYPE_SIMPLE; - if (layer->unit_index == 0) + if (_cogl_material_layer_get_unit_index (layer) == 0) arg->name = "fragment.color.primary"; else arg->name = "output"; @@ -2117,7 +5232,8 @@ setup_arg (CoglMaterial *material, arg->type = COGL_MATERIAL_BACKEND_ARBFP_ARG_TYPE_TEXTURE; arg->name = "texture[%d]"; arg->texture_unit = src - GL_TEXTURE0; - cogl_texture_get_gl_texture (layer->texture, NULL, &gl_target); + texture = _cogl_material_layer_get_texture (layer); + cogl_texture_get_gl_texture (texture, NULL, &gl_target); setup_texture_source (priv, arg->texture_unit, gl_target); } @@ -2193,7 +5309,6 @@ backend_arbfp_args_equal (CoglMaterialBackendARBfpArg *arg0, static void append_function (CoglMaterial *material, - CoglMaterialLayer *layer, CoglBlendStringChannelMask mask, GLint function, CoglMaterialBackendARBfpArg *args, @@ -2353,7 +5468,6 @@ append_masked_combine (CoglMaterial *material, } append_function (material, - layer, mask, function, args, @@ -2361,10 +5475,15 @@ append_masked_combine (CoglMaterial *material, } static gboolean -_cogl_material_backend_arbfp_add_layer (CoglMaterialLayer *layer) +_cogl_material_backend_arbfp_add_layer (CoglMaterial *material, + CoglMaterialLayer *layer, + unsigned long layers_difference) { - CoglMaterial *material = layer->material; CoglMaterialBackendARBfpPrivate *priv = material->backend_priv; + CoglMaterialLayer *combine_authority = + _cogl_material_layer_get_authority (layer, + COGL_MATERIAL_LAYER_STATE_COMBINE); + CoglMaterialLayerBigState *big_state = combine_authority->big_state; /* Notes... * @@ -2398,16 +5517,16 @@ _cogl_material_backend_arbfp_add_layer (CoglMaterialLayer *layer) if (!priv->source) return TRUE; - if (!need_texture_combine_separate (layer)) + if (!need_texture_combine_separate (combine_authority)) { append_masked_combine (material, layer, COGL_BLEND_STRING_CHANNEL_MASK_RGBA, - layer->texture_combine_rgb_func, - layer->texture_combine_rgb_src, - layer->texture_combine_rgb_op); + big_state->texture_combine_rgb_func, + big_state->texture_combine_rgb_src, + big_state->texture_combine_rgb_op); } - else if (layer->texture_combine_rgb_func == GL_DOT3_RGBA) + else if (big_state->texture_combine_rgb_func == GL_DOT3_RGBA) { /* GL_DOT3_RGBA Is a bit weird as a GL_COMBINE_RGB function * since if you use it, it overrides your ALPHA function... @@ -2415,24 +5534,24 @@ _cogl_material_backend_arbfp_add_layer (CoglMaterialLayer *layer) append_masked_combine (material, layer, COGL_BLEND_STRING_CHANNEL_MASK_RGBA, - layer->texture_combine_rgb_func, - layer->texture_combine_rgb_src, - layer->texture_combine_rgb_op); + big_state->texture_combine_rgb_func, + big_state->texture_combine_rgb_src, + big_state->texture_combine_rgb_op); } else { append_masked_combine (material, layer, COGL_BLEND_STRING_CHANNEL_MASK_RGB, - layer->texture_combine_rgb_func, - layer->texture_combine_rgb_src, - layer->texture_combine_rgb_op); + big_state->texture_combine_rgb_func, + big_state->texture_combine_rgb_src, + big_state->texture_combine_rgb_op); append_masked_combine (material, layer, COGL_BLEND_STRING_CHANNEL_MASK_ALPHA, - layer->texture_combine_alpha_func, - layer->texture_combine_alpha_src, - layer->texture_combine_alpha_op); + big_state->texture_combine_alpha_func, + big_state->texture_combine_alpha_src, + big_state->texture_combine_alpha_op); } return TRUE; @@ -2451,7 +5570,8 @@ _cogl_material_backend_arbfp_passthrough (CoglMaterial *material) } static gboolean -_cogl_material_backend_arbfp_end (CoglMaterial *material) +_cogl_material_backend_arbfp_end (CoglMaterial *material, + unsigned long materials_difference) { CoglMaterialBackendARBfpPrivate *priv = material->backend_priv; @@ -2498,29 +5618,33 @@ _cogl_material_backend_arbfp_end (CoglMaterial *material) } static void -_cogl_material_backend_arbfp_material_change_notify (CoglMaterial *material, - unsigned long changes, - GLubyte *new_color) +_cogl_material_backend_arbfp_material_pre_change_notify ( + CoglMaterial *material, + CoglMaterialState change, + const CoglColor *new_color) { CoglMaterialBackendARBfpPrivate *priv = material->backend_priv; static const unsigned long fragment_op_changes = - COGL_MATERIAL_CHANGE_LAYERS; - /* TODO: COGL_MATERIAL_CHANGE_FOG */ + COGL_MATERIAL_STATE_LAYERS; + /* TODO: COGL_MATERIAL_STATE_FOG */ _COGL_GET_CONTEXT (ctx, NO_RETVAL); - if (priv && + if (material->backend_priv_set && priv->gl_program && - changes & fragment_op_changes) + change & fragment_op_changes) { + /* XXX: what if this is the currently bound program? we need to + * dirty our cache. */ GE (glDeletePrograms (1, &priv->gl_program)); priv->gl_program = 0; } } static void -_cogl_material_backend_arbfp_layer_change_notify (CoglMaterialLayer *layer, - unsigned long changes) +_cogl_material_backend_arbfp_layer_pre_change_notify ( + CoglMaterialLayer *layer, + CoglMaterialLayerState changes) { /* TODO: we could be saving snippets of texture combine code along * with each layer and then when a layer changes we would just free @@ -2531,16 +5655,17 @@ _cogl_material_backend_arbfp_layer_change_notify (CoglMaterialLayer *layer, static void _cogl_material_backend_arbfp_free_priv (CoglMaterial *material) { - CoglMaterialBackendARBfpPrivate *priv = material->backend_priv; - _COGL_GET_CONTEXT (ctx, NO_RETVAL); - if (priv) + if (material->backend_priv_set) { + CoglMaterialBackendARBfpPrivate *priv = material->backend_priv; + glDeletePrograms (1, &priv->gl_program); if (priv->sampled) g_free (priv->sampled); g_slice_free (CoglMaterialBackendARBfpPrivate, material->backend_priv); + material->backend_priv_set = FALSE; } } @@ -2551,9 +5676,10 @@ static const CoglMaterialBackend _cogl_material_arbfp_backend = _cogl_material_backend_arbfp_add_layer, _cogl_material_backend_arbfp_passthrough, _cogl_material_backend_arbfp_end, - _cogl_material_backend_arbfp_material_change_notify, - _cogl_material_backend_arbfp_layer_change_notify, - _cogl_material_backend_arbfp_free_priv + _cogl_material_backend_arbfp_material_pre_change_notify, + _cogl_material_backend_arbfp_layer_pre_change_notify, + _cogl_material_backend_arbfp_free_priv, + NULL }; #endif /* COGL_MATERIAL_BACKEND_ARBFP */ @@ -2576,16 +5702,22 @@ _cogl_material_backend_fixed_get_max_texture_units (void) } static gboolean -_cogl_material_backend_fixed_start (CoglMaterial *material) +_cogl_material_backend_fixed_start (CoglMaterial *material, + int n_layers, + unsigned long materials_difference) { use_program (COGL_INVALID_HANDLE, COGL_MATERIAL_PROGRAM_TYPE_FIXED); return TRUE; } static gboolean -_cogl_material_backend_fixed_add_layer (CoglMaterialLayer *layer) +_cogl_material_backend_fixed_add_layer (CoglMaterial *material, + CoglMaterialLayer *layer, + unsigned long layers_difference) { - CoglTextureUnit *unit = _cogl_get_texture_unit (layer->unit_index); + CoglTextureUnit *unit = + _cogl_get_texture_unit (_cogl_material_layer_get_unit_index (layer)); + int unit_index = unit->index; int n_rgb_func_args; int n_alpha_func_args; @@ -2593,26 +5725,28 @@ _cogl_material_backend_fixed_add_layer (CoglMaterialLayer *layer) /* XXX: Beware that since we are changing the active texture unit we * must make sure we don't call into other Cogl components that may - * temporarily bind texture objects to query/modify parameters until - * we restore texture unit 1 as the active unit. For more details - * about this see the end of _cogl_material_flush_gl_state + * temporarily bind texture objects to query/modify parameters since + * they will end up binding texture unit 1. See + * _cogl_bind_gl_texture_transient for more details. */ - set_active_texture_unit (unit->index); + set_active_texture_unit (unit_index); -#ifndef DISABLE_MATERIAL_CACHE - if (unit->layer_differences & COGL_MATERIAL_LAYER_DIFFERENCE_COMBINE || - layer->differences & COGL_MATERIAL_LAYER_DIFFERENCE_COMBINE) -#endif + if (layers_difference & COGL_MATERIAL_LAYER_STATE_COMBINE) { + CoglMaterialLayer *authority = + _cogl_material_layer_get_authority (layer, + COGL_MATERIAL_LAYER_STATE_COMBINE); + CoglMaterialLayerBigState *big_state = authority->big_state; + GE (glTexEnvi (GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_COMBINE)); /* Set the combiner functions... */ GE (glTexEnvi (GL_TEXTURE_ENV, GL_COMBINE_RGB, - layer->texture_combine_rgb_func)); + big_state->texture_combine_rgb_func)); GE (glTexEnvi (GL_TEXTURE_ENV, GL_COMBINE_ALPHA, - layer->texture_combine_alpha_func)); + big_state->texture_combine_alpha_func)); /* * Setup the function arguments... @@ -2620,68 +5754,69 @@ _cogl_material_backend_fixed_add_layer (CoglMaterialLayer *layer) /* For the RGB components... */ n_rgb_func_args = - get_n_args_for_combine_func (layer->texture_combine_rgb_func); + get_n_args_for_combine_func (big_state->texture_combine_rgb_func); GE (glTexEnvi (GL_TEXTURE_ENV, GL_SRC0_RGB, - layer->texture_combine_rgb_src[0])); + big_state->texture_combine_rgb_src[0])); GE (glTexEnvi (GL_TEXTURE_ENV, GL_OPERAND0_RGB, - layer->texture_combine_rgb_op[0])); + big_state->texture_combine_rgb_op[0])); if (n_rgb_func_args > 1) { GE (glTexEnvi (GL_TEXTURE_ENV, GL_SRC1_RGB, - layer->texture_combine_rgb_src[1])); + big_state->texture_combine_rgb_src[1])); GE (glTexEnvi (GL_TEXTURE_ENV, GL_OPERAND1_RGB, - layer->texture_combine_rgb_op[1])); + big_state->texture_combine_rgb_op[1])); } if (n_rgb_func_args > 2) { GE (glTexEnvi (GL_TEXTURE_ENV, GL_SRC2_RGB, - layer->texture_combine_rgb_src[2])); + big_state->texture_combine_rgb_src[2])); GE (glTexEnvi (GL_TEXTURE_ENV, GL_OPERAND2_RGB, - layer->texture_combine_rgb_op[2])); + big_state->texture_combine_rgb_op[2])); } /* For the Alpha component */ n_alpha_func_args = - get_n_args_for_combine_func (layer->texture_combine_alpha_func); + get_n_args_for_combine_func (big_state->texture_combine_alpha_func); GE (glTexEnvi (GL_TEXTURE_ENV, GL_SRC0_ALPHA, - layer->texture_combine_alpha_src[0])); + big_state->texture_combine_alpha_src[0])); GE (glTexEnvi (GL_TEXTURE_ENV, GL_OPERAND0_ALPHA, - layer->texture_combine_alpha_op[0])); + big_state->texture_combine_alpha_op[0])); if (n_alpha_func_args > 1) { GE (glTexEnvi (GL_TEXTURE_ENV, GL_SRC1_ALPHA, - layer->texture_combine_alpha_src[1])); + big_state->texture_combine_alpha_src[1])); GE (glTexEnvi (GL_TEXTURE_ENV, GL_OPERAND1_ALPHA, - layer->texture_combine_alpha_op[1])); + big_state->texture_combine_alpha_op[1])); } if (n_alpha_func_args > 2) { GE (glTexEnvi (GL_TEXTURE_ENV, GL_SRC2_ALPHA, - layer->texture_combine_alpha_src[2])); + big_state->texture_combine_alpha_src[2])); GE (glTexEnvi (GL_TEXTURE_ENV, GL_OPERAND2_ALPHA, - layer->texture_combine_alpha_op[2])); + big_state->texture_combine_alpha_op[2])); } + } + + if (layers_difference & COGL_MATERIAL_LAYER_STATE_COMBINE) + { + CoglMaterialLayer *authority = + _cogl_material_layer_get_authority (layer, + COGL_MATERIAL_LAYER_STATE_COMBINE); + CoglMaterialLayerBigState *big_state = authority->big_state; GE (glTexEnvfv (GL_TEXTURE_ENV, GL_TEXTURE_ENV_COLOR, - layer->texture_combine_constant)); + big_state->texture_combine_constant)); } return TRUE; } static gboolean -_cogl_material_backend_fixed_end (CoglMaterial *material) +_cogl_material_backend_fixed_end (CoglMaterial *material, + unsigned long materials_difference) { - /* There is a convention to always leave texture unit 1 active and - * since we modify the active unit in - * _cogl_material_backend_fixed_add_layer we need to restore it - * here... - * - * (See the end of _cogl_material_flush_gl_state for more - * details) */ - set_active_texture_unit (1); return TRUE; } @@ -2690,65 +5825,26 @@ static const CoglMaterialBackend _cogl_material_fixed_backend = _cogl_material_backend_fixed_get_max_texture_units, _cogl_material_backend_fixed_start, _cogl_material_backend_fixed_add_layer, - NULL, + NULL, /* passthrough */ _cogl_material_backend_fixed_end, NULL, /* material_change_notify */ NULL, /* layer_change_notify */ NULL /* free_priv */ }; -/* Here we resolve what low level GL texture we are *actually* going - * to use. This can either be a layer0 override texture, it can be a - * fallback texture or we can query the CoglTexture for the GL - * texture. - */ static void _cogl_material_layer_get_texture_info (CoglMaterialLayer *layer, - GLuint layer0_override_texture, - gboolean fallback, CoglHandle *texture, GLuint *gl_texture, GLuint *gl_target) { - gboolean layer0_overridden = layer0_override_texture ? TRUE : FALSE; - _COGL_GET_CONTEXT (ctx, NO_RETVAL); *texture = layer->texture; - if (G_LIKELY (*texture != COGL_INVALID_HANDLE)) - cogl_texture_get_gl_texture (*texture, gl_texture, gl_target); - else - { - fallback = TRUE; - *gl_target = GL_TEXTURE_2D; - } - - if (layer0_overridden && layer->unit_index == 0) - { - /* We assume that layer0 overrides are only used for sliced - * textures where the GL texture is actually a sub component - * of the layer->texture... */ - *texture = layer->texture; - *gl_texture = layer0_override_texture; - } - else if (fallback) - { - 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 default texture we can use to fill " - "in for an invalid material layer, since it was " - "using an unsupported texture target "); - /* might get away with this... */ - *texture = ctx->default_gl_texture_2d_tex; - } - cogl_texture_get_gl_texture (*texture, gl_texture, NULL); - } + if (G_UNLIKELY (*texture == COGL_INVALID_HANDLE)) + *texture = ctx->default_gl_texture_2d_tex; + cogl_texture_get_gl_texture (*texture, gl_texture, gl_target); + return; } #ifndef HAVE_COGL_GLES @@ -2766,47 +5862,51 @@ blend_factor_uses_constant (GLenum blend_factor) static void _cogl_material_flush_color_blend_alpha_state (CoglMaterial *material, - gboolean skip_gl_color) + unsigned long materials_difference, + gboolean skip_gl_color) { _COGL_GET_CONTEXT (ctx, NO_RETVAL); if (!skip_gl_color) { - if (!(ctx->current_material_flags & COGL_MATERIAL_FLAG_DEFAULT_COLOR) || - !(material->flags & COGL_MATERIAL_FLAG_DEFAULT_COLOR) || + if ((materials_difference & COGL_MATERIAL_STATE_COLOR) || /* Assume if we were previously told to skip the color, then * the current color needs updating... */ ctx->current_material_skip_gl_color) { - GE (glColor4ub (material->unlit[0], - material->unlit[1], - material->unlit[2], - material->unlit[3])); + CoglMaterial *authority = + _cogl_material_get_authority (material, COGL_MATERIAL_STATE_COLOR); + GE (glColor4ub (cogl_color_get_red_byte (&authority->color), + cogl_color_get_green_byte (&authority->color), + cogl_color_get_blue_byte (&authority->color), + cogl_color_get_alpha_byte (&authority->color))); } } - /* XXX: - * Currently we only don't update state when the flags indicate that the - * current material uses the defaults, and the new material also uses the - * defaults, but we could do deeper comparisons of state. - */ - - if (!(ctx->current_material_flags & - COGL_MATERIAL_FLAG_DEFAULT_GL_MATERIAL) || - !(material->flags & COGL_MATERIAL_FLAG_DEFAULT_GL_MATERIAL)) + if (materials_difference & COGL_MATERIAL_STATE_LIGHTING) { + CoglMaterial *authority = + _cogl_material_get_authority (material, COGL_MATERIAL_STATE_LIGHTING); + CoglMaterialLightingState *lighting_state = + &authority->big_state->lighting_state; + /* FIXME - we only need to set these if lighting is enabled... */ - GE (glMaterialfv (GL_FRONT_AND_BACK, GL_AMBIENT, material->ambient)); - GE (glMaterialfv (GL_FRONT_AND_BACK, GL_DIFFUSE, material->diffuse)); - GE (glMaterialfv (GL_FRONT_AND_BACK, GL_SPECULAR, material->specular)); - GE (glMaterialfv (GL_FRONT_AND_BACK, GL_EMISSION, material->emission)); - GE (glMaterialfv (GL_FRONT_AND_BACK, GL_SHININESS, - &material->shininess)); + GLfloat shininess = lighting_state->shininess * 128.0f; + + GE (glMaterialfv (GL_FRONT_AND_BACK, GL_AMBIENT, lighting_state->ambient)); + GE (glMaterialfv (GL_FRONT_AND_BACK, GL_DIFFUSE, lighting_state->diffuse)); + GE (glMaterialfv (GL_FRONT_AND_BACK, GL_SPECULAR, lighting_state->specular)); + GE (glMaterialfv (GL_FRONT_AND_BACK, GL_EMISSION, lighting_state->emission)); + GE (glMaterialfv (GL_FRONT_AND_BACK, GL_SHININESS, &shininess)); } - if (!(ctx->current_material_flags & COGL_MATERIAL_FLAG_DEFAULT_BLEND) || - !(material->flags & COGL_MATERIAL_FLAG_DEFAULT_BLEND)) + if (materials_difference & COGL_MATERIAL_STATE_BLEND) { + CoglMaterial *authority = + _cogl_material_get_authority (material, COGL_MATERIAL_STATE_BLEND); + CoglMaterialBlendState *blend_state = + &authority->big_state->blend_state; + #if defined (HAVE_COGL_GLES2) gboolean have_blend_equation_seperate = TRUE; gboolean have_blend_func_separate = TRUE; @@ -2820,41 +5920,64 @@ _cogl_material_flush_color_blend_alpha_state (CoglMaterial *material, #endif #ifndef HAVE_COGL_GLES /* GLES 1 only has glBlendFunc */ - if (have_blend_equation_seperate && - material->blend_equation_rgb != material->blend_equation_alpha) - GE (glBlendEquationSeparate (material->blend_equation_rgb, - material->blend_equation_alpha)); - else - GE (glBlendEquation (material->blend_equation_rgb)); + if (blend_factor_uses_constant (blend_state->blend_src_factor_rgb) || + blend_factor_uses_constant (blend_state->blend_src_factor_alpha) || + blend_factor_uses_constant (blend_state->blend_dst_factor_rgb) || + blend_factor_uses_constant (blend_state->blend_dst_factor_alpha)) + { + float red = + cogl_color_get_red_float (&blend_state->blend_constant); + float green = + cogl_color_get_green_float (&blend_state->blend_constant); + float blue = + cogl_color_get_blue_float (&blend_state->blend_constant); + float alpha = + cogl_color_get_alpha_float (&blend_state->blend_constant); - if (blend_factor_uses_constant (material->blend_src_factor_rgb) || - blend_factor_uses_constant (material->blend_src_factor_alpha) || - blend_factor_uses_constant (material->blend_dst_factor_rgb) || - blend_factor_uses_constant (material->blend_dst_factor_alpha)) - GE (glBlendColor (material->blend_constant[0], - material->blend_constant[1], - material->blend_constant[2], - material->blend_constant[3])); + + GE (glBlendColor (red, green, blue, alpha)); + } + + if (have_blend_equation_seperate && + blend_state->blend_equation_rgb != blend_state->blend_equation_alpha) + GE (glBlendEquationSeparate (blend_state->blend_equation_rgb, + blend_state->blend_equation_alpha)); + else + GE (glBlendEquation (blend_state->blend_equation_rgb)); if (have_blend_func_separate && - (material->blend_src_factor_rgb != material->blend_src_factor_alpha || - (material->blend_src_factor_rgb != - material->blend_src_factor_alpha))) - GE (glBlendFuncSeparate (material->blend_src_factor_rgb, - material->blend_dst_factor_rgb, - material->blend_src_factor_alpha, - material->blend_dst_factor_alpha)); + (blend_state->blend_src_factor_rgb != blend_state->blend_src_factor_alpha || + (blend_state->blend_src_factor_rgb != + blend_state->blend_src_factor_alpha))) + GE (glBlendFuncSeparate (blend_state->blend_src_factor_rgb, + blend_state->blend_dst_factor_rgb, + blend_state->blend_src_factor_alpha, + blend_state->blend_dst_factor_alpha)); else #endif - GE (glBlendFunc (material->blend_src_factor_rgb, - material->blend_dst_factor_rgb)); + GE (glBlendFunc (blend_state->blend_src_factor_rgb, + blend_state->blend_dst_factor_rgb)); } - if (!(ctx->current_material_flags & COGL_MATERIAL_FLAG_DEFAULT_ALPHA_FUNC) || - !(material->flags & COGL_MATERIAL_FLAG_DEFAULT_ALPHA_FUNC)) + if (materials_difference & COGL_MATERIAL_STATE_ALPHA_FUNC) { + CoglMaterial *authority = + _cogl_material_get_authority (material, COGL_MATERIAL_STATE_ALPHA_FUNC); + CoglMaterialAlphaFuncState *alpha_state = + &authority->big_state->alpha_state; + /* NB: Currently the Cogl defines are compatible with the GL ones: */ - GE (glAlphaFunc (material->alpha_func, material->alpha_func_reference)); + GE (glAlphaFunc (alpha_state->alpha_func, + alpha_state->alpha_func_reference)); + } + + if (material->real_blend_enable != ctx->gl_blend_enable_cache) + { + if (material->real_blend_enable) + GE (glEnable (GL_BLEND)); + else + GE (glDisable (GL_BLEND)); + ctx->gl_blend_enable_cache = material->real_blend_enable; } } @@ -2882,126 +6005,60 @@ get_max_activateable_texture_units (void) return ctx->max_activateable_texture_units; } -/* - * _cogl_material_flush_common_gl_state: - * @fallback_mask: is a bitmask of the material layers that need to be - * replaced with the default, fallback textures. The fallback textures are - * fully transparent textures so they hopefully wont contribute to the - * texture combining. - * - * The intention of fallbacks is to try and preserve - * the number of layers the user is expecting so that texture coordinates - * they gave will mostly still correspond to the textures they intended, and - * have a fighting chance of looking close to their originally intended - * result. - * - * @disable_mask: is a bitmask of the material layers that will simply have - * texturing disabled. It's only really intended for disabling all layers - * > X; i.e. we'd expect to see a contiguous run of 0 starting from the LSB - * and at some point the remaining bits flip to 1. It might work to disable - * arbitrary layers; though I'm not sure a.t.m how OpenGL would take to - * that. - * - * The intention of the disable_mask is for emitting geometry when the user - * hasn't supplied enough texture coordinates for all the layers and it's - * not possible to auto generate default texture coordinates for those - * layers. - * - * @layer0_override_texture: forcibly tells us to bind this GL texture name for - * layer 0 instead of plucking the gl_texture from the CoglTexture of layer - * 0. - * - * The intention of this is for any primitives that supports sliced textures. - * The code will can iterate each of the slices and re-flush the material - * forcing the GL texture of each slice in turn. - * - * @wrap_mode_overrides: overrides the wrap modes set on each - * layer. This is used to implement the automatic wrap mode. - * - * XXX: It might also help if we could specify a texture matrix for code - * dealing with slicing that would be multiplied with the users own matrix. - * - * Normaly texture coords in the range [0, 1] refer to the extents of the - * texture, but when your GL texture represents a slice of the real texture - * (from the users POV) then a texture matrix would be a neat way of - * transforming the mapping for each slice. - * - * Currently for textured rectangles we manually calculate the texture - * coords for each slice based on the users given coords, but this solution - * isn't ideal, and can't be used with CoglVertexBuffers. - */ -static void -_cogl_material_flush_common_gl_state (CoglMaterial *material, - gboolean skip_gl_color, - guint32 fallback_mask, - guint32 disable_mask, - GLuint layer0_override_texture, - const CoglMaterialWrapModeOverrides * - wrap_mode_overrides) +typedef struct { - GList *l; - int i; + int i; + unsigned long *layer_differences; +} CoglMaterialFlushLayerState; - _COGL_GET_CONTEXT (ctx, NO_RETVAL); +static gboolean +flush_layers_common_gl_state_cb (CoglMaterialLayer *layer, void *user_data) +{ + CoglMaterialFlushLayerState *flush_state = user_data; + int unit_index = flush_state->i; + CoglTextureUnit *unit = _cogl_get_texture_unit (unit_index); + unsigned long layers_difference = + flush_state->layer_differences[unit_index]; - _cogl_material_flush_color_blend_alpha_state (material, skip_gl_color); - - for (l = material->layers, i = 0; l != NULL; l = l->next, i++) + /* There may not be enough texture units so we can bail out if + * that's the case... + */ + if (G_UNLIKELY (unit_index >= get_max_activateable_texture_units ())) { - CoglMaterialLayer *layer = l->data; - CoglTextureUnit *unit; - gboolean fallback; - CoglHandle texture; - GLuint gl_texture; - GLenum gl_target; + static gboolean shown_warning = FALSE; - unit = _cogl_get_texture_unit (layer->unit_index); - - /* There may not be enough texture units so we can bail out if - * that's the case... - */ - if (G_UNLIKELY (unit->index >= get_max_activateable_texture_units ())) + if (!shown_warning) { - static gboolean shown_warning = FALSE; - - if (!shown_warning) - { - g_warning ("Your hardware does not have enough texture units" - "to handle this many texture layers"); - shown_warning = TRUE; - } - break; + g_warning ("Your hardware does not have enough texture units" + "to handle this many texture layers"); + shown_warning = TRUE; } + return FALSE; + } - /* Bail out as soon as we hit a bit set in the disable mask */ - if (G_UNLIKELY (disable_mask & (1<index))) - break; + if (layers_difference & COGL_MATERIAL_LAYER_STATE_TEXTURE) + { + CoglMaterialLayer *authority = + _cogl_material_layer_get_authority (layer, + COGL_MATERIAL_LAYER_STATE_TEXTURE); + CoglHandle texture; + GLuint gl_texture; + GLenum gl_target; - fallback = (fallback_mask & (1<index); - - _cogl_material_layer_get_texture_info (layer, - layer0_override_texture, - fallback, + _cogl_material_layer_get_texture_info (authority, &texture, &gl_texture, &gl_target); - /* NB: Due to fallbacks texture may not == layer->texture */ - unit->texture = texture; - unit->layer0_overridden = layer0_override_texture ? TRUE : FALSE; - unit->fallback = fallback; + set_active_texture_unit (unit_index); /* NB: There are several Cogl components and some code in * Clutter that will temporarily bind arbitrary GL textures to * query and modify texture object parameters. If you look at - * the end of _cogl_material_flush_gl_state() you can see we - * make sure that such code always binds to texture unit 1 by - * always leaving texture unit 1 active. This means we can't - * rely on the unit->gl_texture state if unit->index == 1. + * _cogl_bind_gl_texture_transient() you can see we make sure + * that such code always binds to texture unit 1 which means we + * can't rely on the unit->gl_texture state if unit->index == 1. + * * Because texture unit 1 is a bit special we actually defer any * necessary glBindTexture for it until the end of * _cogl_material_flush_gl_state(). @@ -3020,59 +6077,94 @@ _cogl_material_flush_common_gl_state (CoglMaterial *material, * associated with the texture unit then we can't assume that we * aren't seeing a recycled texture name so we have to bind. */ -#ifndef DISABLE_MATERIAL_CACHE if (unit->gl_texture != gl_texture || unit->is_foreign) { - if (unit->index != 1) + if (unit_index != 1) GE (glBindTexture (gl_target, gl_texture)); unit->gl_texture = gl_texture; } -#else - GE (glBindTexture (gl_target, gl_texture)); -#endif unit->is_foreign = _cogl_texture_is_foreign (texture); /* Disable the previous target if it was different and it's * still enabled */ - if (unit->enabled -#ifndef DISABLE_MATERIAL_CACHE - && unit->enabled_gl_target != gl_target -#endif - ) - GE (glDisable (unit->enabled_gl_target)); + if (unit->enabled && unit->current_gl_target != gl_target) + GE (glDisable (unit->current_gl_target)); - if (!G_UNLIKELY (cogl_debug_flags & COGL_DEBUG_DISABLE_TEXTURING) -#ifndef DISABLE_MATERIAL_CACHE - && !(unit->enabled && unit->enabled_gl_target == gl_target) -#endif - ) + if (!G_UNLIKELY (cogl_debug_flags & COGL_DEBUG_DISABLE_TEXTURING) && + (!unit->enabled || unit->current_gl_target != gl_target)) { GE (glEnable (gl_target)); unit->enabled = TRUE; - unit->enabled_gl_target = gl_target; + unit->current_gl_target = gl_target; } - if (unit->layer_differences & COGL_MATERIAL_LAYER_DIFFERENCE_USER_MATRIX || - layer->differences & COGL_MATERIAL_LAYER_DIFFERENCE_USER_MATRIX) + /* The texture_storage_changed boolean indicates if the + * CoglTexture's underlying GL texture storage has changed since + * it was flushed to the texture unit. We've just flushed the + * latest state so we can reset this. */ + unit->texture_storage_changed = FALSE; + } + else + { + /* Even though there may be no difference between the last flushed + * texture state and the current layers texture state it may be that the + * texture unit has been disabled for some time so we need to assert that + * it's enabled now. + */ + if (!G_UNLIKELY (cogl_debug_flags & COGL_DEBUG_DISABLE_TEXTURING) && + !unit->enabled) { - if (layer->differences & COGL_MATERIAL_LAYER_DIFFERENCE_USER_MATRIX) - _cogl_matrix_stack_set (unit->matrix_stack, &layer->matrix); - else - _cogl_matrix_stack_load_identity (unit->matrix_stack); - - _cogl_matrix_stack_flush_to_gl (unit->matrix_stack, - COGL_MATRIX_TEXTURE); + GE (glEnable (unit->current_gl_target)); + unit->enabled = TRUE; } } - /* Disable additional texture units that may have previously been in use.. */ - for (; i < ctx->texture_units->len; i++) - disable_texture_unit (i); + if (layers_difference & COGL_MATERIAL_LAYER_STATE_USER_MATRIX) + { + CoglMaterialLayerState state = COGL_MATERIAL_LAYER_STATE_USER_MATRIX; + CoglMaterialLayer *authority = + _cogl_material_layer_get_authority (layer, state); - /* There is a convention to always leave texture unit 1 active.. - * (See the end of _cogl_material_flush_gl_state for more - * details) */ - set_active_texture_unit (1); + _cogl_matrix_stack_set (unit->matrix_stack, + &authority->big_state->matrix); + + _cogl_matrix_stack_flush_to_gl (unit->matrix_stack, COGL_MATRIX_TEXTURE); + } + + cogl_handle_ref (layer); + if (unit->layer != COGL_INVALID_HANDLE) + cogl_handle_unref (unit->layer); + unit->layer = layer; + unit->layer_changes_since_flush = 0; + + flush_state->i++; + + return TRUE; +} + +static void +_cogl_material_flush_common_gl_state (CoglMaterial *material, + unsigned long materials_difference, + unsigned long *layer_differences, + gboolean skip_gl_color) +{ + CoglMaterialFlushLayerState state; + + _COGL_GET_CONTEXT (ctx, NO_RETVAL); + + _cogl_material_flush_color_blend_alpha_state (material, + materials_difference, + skip_gl_color); + + state.i = 0; + state.layer_differences = layer_differences; + _cogl_material_foreach_layer (material, + flush_layers_common_gl_state_cb, + &state); + + /* Disable additional texture units that may have previously been in use.. */ + for (; state.i < ctx->texture_units->len; state.i++) + disable_texture_unit (state.i); } /* Re-assert the layer's wrap modes on the given CoglTexture. @@ -3081,13 +6173,19 @@ _cogl_material_flush_common_gl_state (CoglMaterial *material, * since the actual texture being used may have been overridden. */ static void -_cogl_material_layer_forward_wrap_modes ( - CoglMaterialLayer *layer, - const CoglMaterialWrapModeOverrides *wrap_mode_overrides, - CoglHandle texture) +_cogl_material_layer_forward_wrap_modes (CoglMaterialLayer *layer, + CoglHandle texture) { - GLenum wrap_mode_s, wrap_mode_t, wrap_mode_r; - int unit_index = layer->unit_index; + CoglMaterialWrapModeInternal wrap_mode_s, wrap_mode_t, wrap_mode_r; + GLenum gl_wrap_mode_s, gl_wrap_mode_t, gl_wrap_mode_r; + + if (texture == COGL_INVALID_HANDLE) + return; + + _cogl_material_layer_get_wrap_modes (layer, + &wrap_mode_s, + &wrap_mode_t, + &wrap_mode_r); /* Update the wrap mode on the texture object. The texture backend should cache the value so that it will be a no-op if the object @@ -3100,49 +6198,25 @@ _cogl_material_layer_forward_wrap_modes ( will break if the application tries to use different modes in different layers using the same texture. */ - if (wrap_mode_overrides && wrap_mode_overrides->values[unit_index].s) - wrap_mode_s = (wrap_mode_overrides->values[unit_index].s == - COGL_MATERIAL_WRAP_MODE_OVERRIDE_REPEAT ? - GL_REPEAT : - wrap_mode_overrides->values[unit_index].s == - COGL_MATERIAL_WRAP_MODE_OVERRIDE_CLAMP_TO_EDGE ? - GL_CLAMP_TO_EDGE : - GL_CLAMP_TO_BORDER); - else if (layer->wrap_mode_s == COGL_MATERIAL_WRAP_MODE_AUTOMATIC) - wrap_mode_s = GL_CLAMP_TO_EDGE; + if (wrap_mode_s == COGL_MATERIAL_WRAP_MODE_INTERNAL_AUTOMATIC) + gl_wrap_mode_s = GL_CLAMP_TO_EDGE; else - wrap_mode_s = layer->wrap_mode_s; + gl_wrap_mode_s = wrap_mode_s; - if (wrap_mode_overrides && wrap_mode_overrides->values[unit_index].t) - wrap_mode_t = (wrap_mode_overrides->values[unit_index].t == - COGL_MATERIAL_WRAP_MODE_OVERRIDE_REPEAT ? - GL_REPEAT : - wrap_mode_overrides->values[unit_index].t == - COGL_MATERIAL_WRAP_MODE_OVERRIDE_CLAMP_TO_EDGE ? - GL_CLAMP_TO_EDGE : - GL_CLAMP_TO_BORDER); - else if (layer->wrap_mode_t == COGL_MATERIAL_WRAP_MODE_AUTOMATIC) - wrap_mode_t = GL_CLAMP_TO_EDGE; + if (wrap_mode_t == COGL_MATERIAL_WRAP_MODE_INTERNAL_AUTOMATIC) + gl_wrap_mode_t = GL_CLAMP_TO_EDGE; else - wrap_mode_t = layer->wrap_mode_t; + gl_wrap_mode_t = wrap_mode_t; - if (wrap_mode_overrides && wrap_mode_overrides->values[unit_index].r) - wrap_mode_r = (wrap_mode_overrides->values[unit_index].r == - COGL_MATERIAL_WRAP_MODE_OVERRIDE_REPEAT ? - GL_REPEAT : - wrap_mode_overrides->values[unit_index].r == - COGL_MATERIAL_WRAP_MODE_OVERRIDE_CLAMP_TO_EDGE ? - GL_CLAMP_TO_EDGE : - GL_CLAMP_TO_BORDER); - else if (layer->wrap_mode_r == COGL_MATERIAL_WRAP_MODE_AUTOMATIC) - wrap_mode_r = GL_CLAMP_TO_EDGE; + if (wrap_mode_r == COGL_MATERIAL_WRAP_MODE_INTERNAL_AUTOMATIC) + gl_wrap_mode_r = GL_CLAMP_TO_EDGE; else - wrap_mode_r = layer->wrap_mode_r; + gl_wrap_mode_r = wrap_mode_r; _cogl_texture_set_wrap_mode_parameters (texture, - wrap_mode_s, - wrap_mode_t, - wrap_mode_r); + gl_wrap_mode_s, + gl_wrap_mode_t, + gl_wrap_mode_r); } /* OpenGL associates the min/mag filters and repeat modes with the @@ -3154,8 +6228,7 @@ _cogl_material_layer_forward_wrap_modes ( * eventually look at using this extension when available. */ static void -foreach_texture_unit_update_filter_and_wrap_modes ( - const CoglMaterialWrapModeOverrides *wrap_mode_overrides) +foreach_texture_unit_update_filter_and_wrap_modes (void) { int i; @@ -3166,68 +6239,241 @@ foreach_texture_unit_update_filter_and_wrap_modes ( CoglTextureUnit *unit = &g_array_index (ctx->texture_units, CoglTextureUnit, i); - if (unit->enabled) - { - /* NB: we can't just look at unit->layer->texture because - * _cogl_material_flush_gl_state may have chosen to flush a - * different texture due to fallbacks. */ - _cogl_texture_set_filters (unit->texture, - unit->layer->min_filter, - unit->layer->mag_filter); + if (!unit->enabled) + break; - _cogl_material_layer_forward_wrap_modes (unit->layer, - wrap_mode_overrides, - unit->texture); + if (unit->layer) + { + CoglHandle texture = _cogl_material_layer_get_texture (unit->layer); + CoglMaterialFilter min; + CoglMaterialFilter mag; + + _cogl_material_layer_get_filters (unit->layer, &min, &mag); + _cogl_texture_set_filters (texture, min, mag); + + _cogl_material_layer_forward_wrap_modes (unit->layer, texture); } } } +typedef struct +{ + int i; + unsigned long *layer_differences; +} CoglMaterialCompareLayersState; + +static gboolean +compare_layer_differences_cb (CoglMaterialLayer *layer, void *user_data) +{ + CoglMaterialCompareLayersState *state = user_data; + CoglTextureUnit *unit = _cogl_get_texture_unit (state->i); + + if (unit->layer == layer) + state->layer_differences[state->i] = unit->layer_changes_since_flush; + else if (unit->layer) + { + state->layer_differences[state->i] = unit->layer_changes_since_flush; + state->layer_differences[state->i] |= + _cogl_material_layer_compare_differences (layer, unit->layer); + } + else + state->layer_differences[state->i] = COGL_MATERIAL_LAYER_STATE_ALL_SPARSE; + + /* XXX: There is always a possibility that a CoglTexture's + * underlying GL texture storage has been changed since it was last + * bound to a texture unit which is why we have a callback into + * _cogl_material_texture_storage_change_notify whenever a textures + * underlying GL texture storage changes which will set the + * unit->texture_intern_changed flag. If we see that's been set here + * then we force an update of the texture state... + */ + if (unit->texture_storage_changed) + state->layer_differences[state->i] |= COGL_MATERIAL_LAYER_STATE_TEXTURE; + + state->i++; + + return TRUE; +} + +typedef struct +{ + const CoglMaterialBackend *backend; + CoglMaterial *material; + unsigned long *layer_differences; + gboolean error_adding_layer; + gboolean added_layer; +} CoglMaterialBackendAddLayerState; + + +static gboolean +backend_add_layer_cb (CoglMaterialLayer *layer, + void *user_data) +{ + CoglMaterialBackendAddLayerState *state = user_data; + const CoglMaterialBackend *backend = state->backend; + CoglMaterial *material = state->material; + int unit_index = _cogl_material_layer_get_unit_index (layer); + CoglTextureUnit *unit = _cogl_get_texture_unit (unit_index); + + _COGL_GET_CONTEXT (ctx, FALSE); + + /* NB: We don't support the random disabling of texture + * units, so as soon as we hit a disabled unit we know all + * subsequent units are also disabled */ + if (!unit->enabled) + return FALSE; + + if (G_UNLIKELY (unit_index >= backend->get_max_texture_units ())) + { + int j; + for (j = unit_index; j < ctx->texture_units->len; j++) + disable_texture_unit (j); + /* TODO: although this isn't considered an error that + * warrants falling back to a different backend we + * should print a warning here. */ + return FALSE; + } + + /* Either generate per layer code snippets or setup the + * fixed function glTexEnv for each layer... */ + if (G_LIKELY (backend->add_layer (material, + layer, + state->layer_differences[unit_index]))) + state->added_layer = TRUE; + else + { + state->error_adding_layer = TRUE; + return FALSE; + } + + return TRUE; +} + +/* + * _cogl_material_flush_gl_state: + * + * Details of override options: + * ->fallback_mask: is a bitmask of the material layers that need to be + * replaced with the default, fallback textures. The fallback textures are + * fully transparent textures so they hopefully wont contribute to the + * texture combining. + * + * The intention of fallbacks is to try and preserve + * the number of layers the user is expecting so that texture coordinates + * they gave will mostly still correspond to the textures they intended, and + * have a fighting chance of looking close to their originally intended + * result. + * + * ->disable_mask: is a bitmask of the material layers that will simply have + * texturing disabled. It's only really intended for disabling all layers + * > X; i.e. we'd expect to see a contiguous run of 0 starting from the LSB + * and at some point the remaining bits flip to 1. It might work to disable + * arbitrary layers; though I'm not sure a.t.m how OpenGL would take to + * that. + * + * The intention of the disable_mask is for emitting geometry when the user + * hasn't supplied enough texture coordinates for all the layers and it's + * not possible to auto generate default texture coordinates for those + * layers. + * + * ->layer0_override_texture: forcibly tells us to bind this GL texture name for + * layer 0 instead of plucking the gl_texture from the CoglTexture of layer + * 0. + * + * The intention of this is for any primitives that supports sliced textures. + * The code will can iterate each of the slices and re-flush the material + * forcing the GL texture of each slice in turn. + * + * ->wrap_mode_overrides: overrides the wrap modes set on each + * layer. This is used to implement the automatic wrap mode. + * + * XXX: It might also help if we could specify a texture matrix for code + * dealing with slicing that would be multiplied with the users own matrix. + * + * Normaly texture coords in the range [0, 1] refer to the extents of the + * texture, but when your GL texture represents a slice of the real texture + * (from the users POV) then a texture matrix would be a neat way of + * transforming the mapping for each slice. + * + * Currently for textured rectangles we manually calculate the texture + * coords for each slice based on the users given coords, but this solution + * isn't ideal, and can't be used with CoglVertexBuffers. + */ void _cogl_material_flush_gl_state (CoglHandle handle, CoglMaterialFlushOptions *options) { - CoglMaterial *material; - guint32 fallback_layers = 0; - guint32 disable_layers = 0; - GLuint layer0_override_texture = 0; - gboolean skip_gl_color = FALSE; - const CoglMaterialWrapModeOverrides *wrap_mode_overrides = NULL; - int i; - CoglTextureUnit *unit1; - GList *tmp; + CoglMaterial *material; + gboolean material_overriden; + gboolean skip_gl_color = FALSE; + unsigned long materials_difference; + int n_layers; + unsigned long *layer_differences; + int i; + CoglTextureUnit *unit1; + + COGL_STATIC_TIMER (material_flush_timer, + "Mainloop", /* parent */ + "Material Flush", + "The time spent flushing material state", + 0 /* no application private data */); _COGL_GET_CONTEXT (ctx, NO_RETVAL); - material = _cogl_material_pointer_from_handle (handle); + COGL_TIMER_START (_cogl_uprof_context, material_flush_timer); - if (options) + if (G_UNLIKELY (options && + (options->flags & ~COGL_MATERIAL_FLUSH_SKIP_GL_COLOR))) { - if (options->flags & COGL_MATERIAL_FLUSH_FALLBACK_MASK) - fallback_layers = options->fallback_layers; - if (options->flags & COGL_MATERIAL_FLUSH_DISABLE_MASK) - disable_layers = options->disable_layers; - if (options->flags & COGL_MATERIAL_FLUSH_LAYER0_OVERRIDE) - layer0_override_texture = options->layer0_override_texture; - if (options->flags & COGL_MATERIAL_FLUSH_SKIP_GL_COLOR) - skip_gl_color = TRUE; - if (options->flags & COGL_MATERIAL_FLUSH_WRAP_MODE_OVERRIDES) - wrap_mode_overrides = &options->wrap_mode_overrides; + /* Create a oneshot material to handle the overrides. + * + * XXX: Note this is a stop-gap-solution: We are aiming to + * remove the overrides mechanism and move code like this out + * into the primitives code. + * + * Overrides were originally necessitated by the previously + * large cost of creating derived material, but they made things + * more complex and also introduced a limit of 32 layers. + * + * Although creating derived materials is now much cheaper it + * would be much better for primitives APIs to cache these + * derived materials as private data on the original material. + */ + material = cogl_material_copy (handle); + _cogl_material_apply_overrides (material, options); + material_overriden = TRUE; } + else + material = COGL_MATERIAL (handle); - /* If the material we are flushing and the override options are the - * same then try to bail out as quickly as possible. - * - * XXX: the more overrides we add the slower "quickly" will get; I - * think we need to move towards cheap copy-on-write materials so - * that exceptional fallbacks/overrides can be implemented simply by - * copying a material and modifying it before flushing. - */ - if (ctx->current_material == material && - ctx->current_material_fallback_layers == fallback_layers && - ctx->current_material_disable_layers == disable_layers && - ctx->current_material_layer0_override == layer0_override_texture && - ctx->current_material_skip_gl_color == skip_gl_color) - goto done; + if (options && options->flags & COGL_MATERIAL_FLUSH_SKIP_GL_COLOR) + skip_gl_color = TRUE; + + if (ctx->current_material == material) + materials_difference = ctx->current_material_changes_since_flush; + else if (ctx->current_material) + { + materials_difference = ctx->current_material_changes_since_flush; + materials_difference |= + _cogl_material_compare_differences (ctx->current_material, + material); + } + else + materials_difference = COGL_MATERIAL_STATE_ALL_SPARSE; + + /* Get a layer_differences mask for each layer to be flushed */ + n_layers = cogl_material_get_n_layers (material); + if (n_layers) + { + CoglMaterialCompareLayersState state; + layer_differences = g_alloca (sizeof (unsigned long *) * n_layers); + memset (layer_differences, 0, sizeof (layer_differences)); + state.i = 0; + state.layer_differences = layer_differences; + _cogl_material_foreach_layer (material, + compare_layer_differences_cb, + &state); + } /* First flush everything that's the same regardless of which * material backend is being used... @@ -3248,11 +6494,9 @@ _cogl_material_flush_gl_state (CoglHandle handle, * updated. */ _cogl_material_flush_common_gl_state (material, - skip_gl_color, - fallback_layers, - disable_layers, - layer0_override_texture, - wrap_mode_overrides); + materials_difference, + layer_differences, + skip_gl_color); /* Now flush the fragment processing state according to the current * fragment processing backend. @@ -3274,54 +6518,29 @@ _cogl_material_flush_gl_state (CoglHandle handle, i < G_N_ELEMENTS (backends); i++, _cogl_material_set_backend (material, i)) { - const GList *l; const CoglMaterialBackend *backend = backends[i]; - gboolean added_layer = FALSE; - gboolean error_adding_layer = FALSE; + CoglMaterialBackendAddLayerState state; /* E.g. For backends generating code they can setup their * scratch buffers here... */ - if (G_UNLIKELY (!backend->start (material))) + if (G_UNLIKELY (!backend->start (material, + n_layers, + materials_difference))) continue; - for (l = cogl_material_get_layers (material); l; l = l->next) - { - CoglMaterialLayer *layer = l->data; - CoglTextureUnit *unit = _cogl_get_texture_unit (layer->unit_index); + state.backend = backend; + state.material = material; + state.layer_differences = layer_differences; + state.error_adding_layer = FALSE; + state.added_layer = FALSE; + _cogl_material_foreach_layer (material, + backend_add_layer_cb, + &state); - /* NB: We don't support the random disabling of texture - * units, so as soon as we hit a disabled unit we know all - * subsequent units are also disabled */ - if (!unit->enabled) - break; - - if (G_UNLIKELY (layer->unit_index >= - backend->get_max_texture_units ())) - { - int j; - for (j = layer->unit_index; j < ctx->texture_units->len; j++) - disable_texture_unit (j); - /* TODO: although this isn't considered an error that - * warrants falling back to a different backend we - * should print a warning here. */ - break; - } - - /* Either generate per layer code snippets or setup the - * fixed function glTexEnv for each layer... */ - if (G_LIKELY (backend->add_layer (layer))) - added_layer = TRUE; - else - { - error_adding_layer = TRUE; - break; - } - } - - if (G_UNLIKELY (error_adding_layer)) + if (G_UNLIKELY (state.error_adding_layer)) continue; - if (!added_layer && + if (!state.added_layer && backend->passthrough && G_UNLIKELY (!backend->passthrough (material))) continue; @@ -3330,351 +6549,51 @@ _cogl_material_flush_gl_state (CoglHandle handle, * programs here, update any uniforms and tell OpenGL to use * that program. */ - if (G_UNLIKELY (!backend->end (material))) + if (G_UNLIKELY (!backend->end (material, materials_difference))) continue; break; } - for (tmp = material->layers; tmp != NULL; tmp = tmp->next) - { - CoglMaterialLayer *layer = tmp->data; - CoglTextureUnit *unit = _cogl_get_texture_unit (layer->unit_index); - - unit->layer = layer; - unit->layer_differences = layer->differences; - } - - /* NB: _cogl_material_pre_change_notify and _cogl_material_free will - * invalidate ctx->current_material (set it to COGL_INVALID_HANDLE) - * if the material is changed/freed. + /* FIXME: This reference is actually resulting in lots of + * copy-on-write reparenting because one-shot materials end up + * living for longer than necessary and so any later modification of + * the parent will cause a copy-on-write. + * + * XXX: The issue should largely go away when we switch to using + * weak materials for overrides. */ + cogl_handle_ref (handle); + if (ctx->current_material != COGL_INVALID_HANDLE) + cogl_handle_unref (ctx->current_material); ctx->current_material = handle; - ctx->current_material_flags = material->flags; - ctx->current_material_fallback_layers = fallback_layers; - ctx->current_material_disable_layers = disable_layers; - ctx->current_material_layer0_override = layer0_override_texture; + ctx->current_material_changes_since_flush = 0; ctx->current_material_skip_gl_color = skip_gl_color; -done: /* well, almost... */ - /* Handle the fact that OpenGL associates texture filter and wrap * modes with the texture objects not the texture units... */ - foreach_texture_unit_update_filter_and_wrap_modes (wrap_mode_overrides); + foreach_texture_unit_update_filter_and_wrap_modes (); /* If this material has more than one layer then we always need * to make sure we rebind the texture for unit 1. * * NB: various components of Cogl may temporarily bind arbitrary - * textures to the current texture unit so they can query and modify - * texture object parameters. cogl-material.c will always leave - * texture unit 1 active so we can ignore these temporary binds - * unless multitexturing is being used. + * textures to texture unit 1 so they can query and modify texture + * object parameters. cogl-material.c (See + * _cogl_bind_gl_texture_transient) */ unit1 = _cogl_get_texture_unit (1); if (unit1->enabled && unit1->dirty_gl_texture) { set_active_texture_unit (1); - GE (glBindTexture (unit1->enabled_gl_target, unit1->gl_texture)); + GE (glBindTexture (unit1->current_gl_target, unit1->gl_texture)); unit1->dirty_gl_texture = FALSE; } - /* Since there are several places where Cogl will temporarily bind a - * GL texture so that it can query or modify texture objects we want - * to make sure we know which texture unit state is being changed by - * such code. - * - * We choose to always end up with texture unit 1 active so that in - * the common case where multitexturing isn't used we can simply - * ignore the state of this texture unit. Notably we didn't use a - * large texture unit (.e.g. (GL_MAX_TEXTURE_UNITS - 1) in case the - * driver doesn't have a sparse data structure for texture units. - */ - set_active_texture_unit (1); -} + if (material_overriden) + cogl_handle_unref (material); -static gboolean -_cogl_material_texture_equal (CoglHandle texture0, CoglHandle texture1) -{ - GLenum gl_handle0, gl_handle1, gl_target0, gl_target1; - - /* If the texture handles are the same then the textures are - definitely equal */ - if (texture0 == texture1) - return TRUE; - - /* If neither texture is sliced then they could still be the same if - the are referring to the same GL texture */ - if (cogl_texture_is_sliced (texture0) || - cogl_texture_is_sliced (texture1)) - return FALSE; - - cogl_texture_get_gl_texture (texture0, &gl_handle0, &gl_target0); - cogl_texture_get_gl_texture (texture1, &gl_handle1, &gl_target1); - - return gl_handle0 == gl_handle1 && gl_target0 == gl_target1; -} - -static gboolean -_cogl_material_layer_equal (CoglMaterialLayer *material0_layer, - CoglHandle material0_layer_texture, - CoglMaterialLayer *material1_layer, - CoglHandle material1_layer_texture) -{ - if (!_cogl_material_texture_equal (material0_layer_texture, - material1_layer_texture)) - return FALSE; - - if ((material0_layer->differences & - COGL_MATERIAL_LAYER_DIFFERENCE_COMBINE) != - (material1_layer->differences & - COGL_MATERIAL_LAYER_DIFFERENCE_COMBINE)) - return FALSE; - -#if 0 /* TODO */ - if (!_deep_are_layer_combines_equal ()) - return FALSE; -#else - if (!(material0_layer->differences & COGL_MATERIAL_LAYER_DIFFERENCE_COMBINE)) - return FALSE; -#endif - - if (material0_layer->mag_filter != material1_layer->mag_filter) - return FALSE; - if (material0_layer->min_filter != material1_layer->min_filter) - return FALSE; - - if (material0_layer->wrap_mode_s != material1_layer->wrap_mode_s || - material0_layer->wrap_mode_t != material1_layer->wrap_mode_t || - material0_layer->wrap_mode_r != material1_layer->wrap_mode_r) - return FALSE; - - return TRUE; -} - -/* This is used by the Cogl journal to compare materials so that it - * can split up geometry that needs different OpenGL state. - * - * It is acceptable to have false negatives - although they will result - * in redundant OpenGL calls that try and update the state. - * - * False positives aren't allowed. - */ -gboolean -_cogl_material_equal (CoglHandle material0_handle, - CoglMaterialFlushOptions *material0_flush_options, - CoglHandle material1_handle, - CoglMaterialFlushOptions *material1_flush_options) -{ - CoglMaterial *material0; - CoglMaterial *material1; - CoglMaterialFlushFlag flush_flags0 = material0_flush_options->flags; - CoglMaterialFlushFlag flush_flags1 = material1_flush_options->flags; - guint32 fallback_layers0; - guint32 fallback_layers1; - guint32 disable_layers0; - guint32 disable_layers1; - GList *l0, *l1; - int i; - - /* Compare the flush options first; if they are equivalent then we - * can potentially return quickly if the material handles then match. */ - - - /* The skip color option is used when the color of the material is being - * submitted in a vertex array so cogl_material_flush_gl_state doesn't - * need to call glColor. - * - A skip gl color material following a non skip color material doesn't - * need a state change since putting a color in a vertex array (as done - * for skip color materials) would simply take precedence over one - * previously specified by glColor (as done for non skip color materials) - * - A non skip color material following a skip color material also doesn't - * need a state change for the same reason. - * - The problem is that a non skip color, followed by a skip color, followed - * by a non skip color does require a state change. Since we don't have - * enough contextual information here we currently return FALSE whenever - * the skip color option changes. */ - if ((flush_flags0 & COGL_MATERIAL_FLUSH_SKIP_GL_COLOR) != - (flush_flags1 & COGL_MATERIAL_FLUSH_SKIP_GL_COLOR)) - return FALSE; - - fallback_layers0 = flush_flags0 & COGL_MATERIAL_FLUSH_FALLBACK_MASK ? - material0_flush_options->fallback_layers : 0; - fallback_layers1 = flush_flags1 & COGL_MATERIAL_FLUSH_FALLBACK_MASK ? - material1_flush_options->fallback_layers : 0; - if (fallback_layers0 != fallback_layers1) - return FALSE; - - disable_layers0 = flush_flags0 & COGL_MATERIAL_FLUSH_DISABLE_MASK ? - material0_flush_options->disable_layers : 0; - disable_layers1 = flush_flags1 & COGL_MATERIAL_FLUSH_DISABLE_MASK ? - material1_flush_options->disable_layers : 0; - if (disable_layers0 != disable_layers1) - return FALSE; - - /* NB: Some unlikely false negatives are possible here. */ - if ((flush_flags0 & COGL_MATERIAL_FLUSH_LAYER0_OVERRIDE) != - (flush_flags1 & COGL_MATERIAL_FLUSH_LAYER0_OVERRIDE)) - return FALSE; - - if ((flush_flags0 & COGL_MATERIAL_FLUSH_LAYER0_OVERRIDE) && - material0_flush_options->layer0_override_texture != - material1_flush_options->layer0_override_texture) - return FALSE; - - /* If one has wrap mode overrides and the other doesn't then the - materials are different */ - if (((flush_flags0 ^ flush_flags1) & COGL_MATERIAL_FLUSH_WRAP_MODE_OVERRIDES)) - return FALSE; - /* If they both have overrides then we need to compare them */ - if ((flush_flags0 & COGL_MATERIAL_FLUSH_WRAP_MODE_OVERRIDES) && - memcmp (&material0_flush_options->wrap_mode_overrides, - &material1_flush_options->wrap_mode_overrides, - sizeof (CoglMaterialWrapModeOverrides))) - return FALSE; - - /* Since we know the flush options match at this point, if the material - * handles match then we know they are equivalent. */ - if (material0_handle == material1_handle) - return TRUE; - - /* Now we need to look in more detail... */ - - material0 = _cogl_material_pointer_from_handle (material0_handle); - material1 = _cogl_material_pointer_from_handle (material1_handle); - - if (!(material0_flush_options->flags & COGL_MATERIAL_FLUSH_SKIP_GL_COLOR) && - !memcmp (material0->unlit, material1->unlit, sizeof (material0->unlit))) - return FALSE; - - /* First we simply try and find a difference according to default flags - * for each material component to avoid deeper comparison. */ - - if ((material0->flags & COGL_MATERIAL_FLAG_DEFAULT_GL_MATERIAL) != - (material1->flags & COGL_MATERIAL_FLAG_DEFAULT_GL_MATERIAL)) - return FALSE; - - if ((material0->flags & COGL_MATERIAL_FLAG_DEFAULT_ALPHA_FUNC) != - (material1->flags & COGL_MATERIAL_FLAG_DEFAULT_ALPHA_FUNC)) - return FALSE; - - /* Potentially blending could be "enabled" but the blend mode - * could be equivalent to being disabled, but we accept those false - * negatives for now. */ - if ((material0->flags & COGL_MATERIAL_FLAG_ENABLE_BLEND) != - (material1->flags & COGL_MATERIAL_FLAG_ENABLE_BLEND)) - return FALSE; - - if ((material0->flags & COGL_MATERIAL_FLAG_ENABLE_BLEND) && - (material0->flags & COGL_MATERIAL_FLAG_DEFAULT_BLEND) != - (material1->flags & COGL_MATERIAL_FLAG_DEFAULT_BLEND)) - return FALSE; - - /* If we still haven't found a difference then do a deeper comparison.. - * - * Actually we don't currently do this; we simply assume anything - * non default is different and accept the false negatives for now. - */ - -#if 0 /* TODO */ - if (!_deep_are_gl_materials_equal ()) - return FALSE; -#else - /* Just assume that all non default materials are different */ - if (!(material0->flags & COGL_MATERIAL_FLAG_DEFAULT_GL_MATERIAL)) - return FALSE; -#endif - -#if 0 /* TODO */ - if (!_deep_are_alpha_funcs_equal ()) - return FALSE; -#else - /* Just assume that all non default alpha funcs are different */ - if (!(material0->flags & COGL_MATERIAL_FLAG_DEFAULT_ALPHA_FUNC)) - return FALSE; -#endif - - if (material0->flags & COGL_MATERIAL_FLAG_ENABLE_BLEND) - { -#if 0 /* TODO */ - if (!_deep_is_blend_equal ()) - return FALSE; -#else - if (!(material0->flags & COGL_MATERIAL_FLAG_DEFAULT_BLEND)) - return FALSE; -#endif - } - - - /* Finally compare each of the material layers ... */ - - l0 = material0->layers; - l1 = material1->layers; - i = 0; - - /* NB: At this point we know if COGL_MATERIAL_FLUSH_LAYER0_OVERRIDE is being - * used then both materials are overriding with the same texture. */ - if (flush_flags0 & COGL_MATERIAL_FLUSH_LAYER0_OVERRIDE && - l0 && l1) - { - /* We still need to check if the combine modes etc are equal, but we - * simply pass COGL_INVALID_HANDLE for both texture handles so they will - * be considered equal */ - if (!_cogl_material_layer_equal (l0->data, COGL_INVALID_HANDLE, - l1->data, COGL_INVALID_HANDLE)) - return FALSE; - - l0 = l0->next; - l1 = l1->next; - i++; - } - - while (l0 && l1) - { - CoglMaterialLayer *m0_layer; - CoglMaterialLayer *m1_layer; - - if ((l0 == NULL && l1 != NULL) || - (l1 == NULL && l0 != NULL)) - return FALSE; - - /* NB: At this point we know that the fallback and disable masks for - * both materials are equal. */ - if (disable_layers0 & (1<data; - m1_layer = l1->data; - - /* NB: The use of a fallback texture doesn't imply that the combine - * modes etc are the same. - */ - if ((disable_layers0 & (1<texture, - m1_layer, m1_layer->texture)) - return FALSE; - } - -next_layer: - l0 = l0->next; - l1 = l1->next; - i++; - } - - if ((l0 == NULL && l1 != NULL) || - (l1 == NULL && l0 != NULL)) - return FALSE; - - return TRUE; + COGL_TIMER_STOP (_cogl_uprof_context, material_flush_timer); } /* While a material is referenced by the Cogl journal we can not allow @@ -3683,8 +6602,8 @@ next_layer: CoglHandle _cogl_material_journal_ref (CoglHandle material_handle) { - CoglMaterial *material = - material = _cogl_material_pointer_from_handle (material_handle); + CoglMaterial *material = COGL_MATERIAL (material_handle); + material->journal_ref_count++; cogl_handle_ref (material_handle); return material_handle; @@ -3699,161 +6618,6 @@ _cogl_material_journal_unref (CoglHandle material_handle) cogl_handle_unref (material_handle); } -CoglMaterialFilter -cogl_material_layer_get_min_filter (CoglHandle layer_handle) -{ - CoglMaterialLayer *layer; - - g_return_val_if_fail (cogl_is_material_layer (layer_handle), 0); - - layer = _cogl_material_layer_pointer_from_handle (layer_handle); - - return layer->min_filter; -} - -CoglMaterialFilter -cogl_material_layer_get_mag_filter (CoglHandle layer_handle) -{ - CoglMaterialLayer *layer; - - g_return_val_if_fail (cogl_is_material_layer (layer_handle), 0); - - layer = _cogl_material_layer_pointer_from_handle (layer_handle); - - return layer->mag_filter; -} - -void -cogl_material_set_layer_filters (CoglHandle handle, - int layer_index, - CoglMaterialFilter min_filter, - CoglMaterialFilter mag_filter) -{ - CoglMaterial *material; - CoglMaterialLayer *layer; - - g_return_if_fail (cogl_is_material (handle)); - - material = _cogl_material_pointer_from_handle (handle); - layer = _cogl_material_get_layer (material, layer_index, TRUE); - - /* possibly flush primitives referencing the current state... */ - _cogl_material_layer_pre_change_notify ( - layer, - COGL_MATERIAL_LAYER_CHANGE_FILTERS); - - layer->min_filter = min_filter; - layer->mag_filter = mag_filter; - - /* Note we don't have a layer->difference flag for the min/mag - * filters since in GL terms this state is owned by the texture - * object so they are dealt with slightly differently. */ -} - -void -cogl_material_set_layer_wrap_mode_s (CoglHandle handle, - int layer_index, - CoglMaterialWrapMode mode) -{ - CoglMaterial *material; - CoglMaterialLayer *layer; - - g_return_if_fail (cogl_is_material (handle)); - - material = _cogl_material_pointer_from_handle (handle); - layer = _cogl_material_get_layer (material, layer_index, TRUE); - - if (layer->wrap_mode_s != mode) - { - /* possibly flush primitives referencing the current state... */ - _cogl_material_pre_change_notify (material, FALSE, NULL); - - layer->wrap_mode_s = mode; - } -} - -void -cogl_material_set_layer_wrap_mode_t (CoglHandle handle, - int layer_index, - CoglMaterialWrapMode mode) -{ - CoglMaterial *material; - CoglMaterialLayer *layer; - - g_return_if_fail (cogl_is_material (handle)); - - material = _cogl_material_pointer_from_handle (handle); - layer = _cogl_material_get_layer (material, layer_index, TRUE); - - if (layer->wrap_mode_t != mode) - { - /* possibly flush primitives referencing the current state... */ - _cogl_material_pre_change_notify (material, FALSE, NULL); - - layer->wrap_mode_t = mode; - } -} - -/* TODO: this should be made public once we add support for 3D - textures in Cogl */ -void -_cogl_material_set_layer_wrap_mode_r (CoglHandle handle, - int layer_index, - CoglMaterialWrapMode mode) -{ - CoglMaterial *material; - CoglMaterialLayer *layer; - - g_return_if_fail (cogl_is_material (handle)); - - material = _cogl_material_pointer_from_handle (handle); - layer = _cogl_material_get_layer (material, layer_index, TRUE); - - if (layer->wrap_mode_r != mode) - { - /* possibly flush primitives referencing the current state... */ - _cogl_material_pre_change_notify (material, FALSE, NULL); - - layer->wrap_mode_r = mode; - } -} - -void -cogl_material_set_layer_wrap_mode (CoglHandle material, - int layer_index, - CoglMaterialWrapMode mode) -{ - cogl_material_set_layer_wrap_mode_s (material, layer_index, mode); - cogl_material_set_layer_wrap_mode_t (material, layer_index, mode); - _cogl_material_set_layer_wrap_mode_r (material, layer_index, mode); -} - -CoglMaterialWrapMode -cogl_material_layer_get_wrap_mode_s (CoglHandle handle) -{ - g_return_val_if_fail (cogl_is_material_layer (handle), FALSE); - - return _cogl_material_layer_pointer_from_handle (handle)->wrap_mode_s; -} - -CoglMaterialWrapMode -cogl_material_layer_get_wrap_mode_t (CoglHandle handle) -{ - g_return_val_if_fail (cogl_is_material_layer (handle), FALSE); - - return _cogl_material_layer_pointer_from_handle (handle)->wrap_mode_t; -} - -/* TODO: this should be made public once we add support for 3D - textures in Cogl */ -CoglMaterialWrapMode -_cogl_material_layer_get_wrap_mode_r (CoglHandle handle) -{ - g_return_val_if_fail (cogl_is_material_layer (handle), FALSE); - - return _cogl_material_layer_pointer_from_handle (handle)->wrap_mode_r; -} - void _cogl_material_apply_legacy_state (CoglHandle handle) { diff --git a/clutter/cogl/cogl/cogl-material.h b/clutter/cogl/cogl/cogl-material.h index 425c7f77d..12c9b39a0 100644 --- a/clutter/cogl/cogl/cogl-material.h +++ b/clutter/cogl/cogl/cogl-material.h @@ -106,7 +106,11 @@ typedef enum { * Since: 1.4 */ /* GL_ALWAYS is just used here as a value that is known not to clash - with any valid GL wrap modes */ + * with any valid GL wrap modes + * + * XXX: keep the values in sync with the CoglMaterialWrapModeInternal + * enum so no conversion is actually needed. + */ typedef enum { COGL_MATERIAL_WRAP_MODE_REPEAT = GL_REPEAT, COGL_MATERIAL_WRAP_MODE_CLAMP_TO_EDGE = GL_CLAMP_TO_EDGE, diff --git a/clutter/cogl/cogl/cogl-path.c b/clutter/cogl/cogl/cogl-path.c index 46c6d59ef..e1c3b8f4d 100644 --- a/clutter/cogl/cogl/cogl-path.c +++ b/clutter/cogl/cogl/cogl-path.c @@ -145,7 +145,6 @@ _cogl_path_stroke_nodes (void) * always be done first when preparing to draw. */ _cogl_framebuffer_flush_state (_cogl_get_framebuffer (), 0); - enable_flags |= _cogl_material_get_cogl_enable_flags (ctx->source_material); _cogl_enable (enable_flags); options.flags = COGL_MATERIAL_FLUSH_DISABLE_MASK; @@ -234,8 +233,6 @@ _cogl_add_path_to_stencil_buffer (CoglPath *path, _cogl_material_flush_gl_state (ctx->source_material, NULL); - enable_flags |= - _cogl_material_get_cogl_enable_flags (ctx->source_material); _cogl_enable (enable_flags); GE( glEnable (GL_STENCIL_TEST) ); @@ -394,8 +391,8 @@ _cogl_path_fill_nodes_scanlines (CoglPathNode *path, _cogl_material_flush_gl_state (ctx->source_material, NULL); - _cogl_enable (COGL_ENABLE_VERTEX_ARRAY - | (ctx->color_alpha < 255 ? COGL_ENABLE_BLEND : 0)); + _cogl_enable (COGL_ENABLE_VERTEX_ARRAY); + /* clear scanline intersection lists */ for (i = 0; i < bounds_h; i++) diff --git a/clutter/cogl/cogl/cogl-primitives.c b/clutter/cogl/cogl/cogl-primitives.c index 84a498596..b24ff3af4 100644 --- a/clutter/cogl/cogl/cogl-primitives.c +++ b/clutter/cogl/cogl/cogl-primitives.c @@ -1073,6 +1073,8 @@ cogl_polygon (const CoglTextureVertex *vertices, GLfloat *v; CoglMaterialWrapModeOverrides wrap_mode_overrides; CoglMaterialWrapModeOverrides *wrap_mode_overrides_p = NULL; + CoglHandle original_source_material; + gboolean overrode_material = FALSE; _COGL_GET_CONTEXT (ctx, NO_RETVAL); @@ -1206,18 +1208,31 @@ cogl_polygon (const CoglTextureVertex *vertices, /* Prepare GL state */ enable_flags = COGL_ENABLE_VERTEX_ARRAY; - enable_flags |= _cogl_material_get_cogl_enable_flags (ctx->source_material); if (ctx->enable_backface_culling) enable_flags |= COGL_ENABLE_BACKFACE_CULLING; if (use_color) { - enable_flags |= COGL_ENABLE_COLOR_ARRAY | COGL_ENABLE_BLEND; + CoglHandle override; + enable_flags |= COGL_ENABLE_COLOR_ARRAY; GE( glColorPointer (4, GL_UNSIGNED_BYTE, stride_bytes, /* NB: [X,Y,Z,TX,TY...,R,G,B,A,...] */ v + 3 + 2 * n_layers) ); + + if (!_cogl_material_get_real_blend_enabled (ctx->source_material)) + { + CoglMaterialBlendEnable blend_enabled = + COGL_MATERIAL_BLEND_ENABLE_ENABLED; + original_source_material = ctx->source_material; + override = cogl_material_copy (original_source_material); + _cogl_material_set_blend_enabled (override, blend_enabled); + + /* XXX: cogl_push_source () */ + overrode_material = TRUE; + ctx->source_material = override; + } } _cogl_enable (enable_flags); @@ -1253,6 +1268,16 @@ cogl_polygon (const CoglTextureVertex *vertices, fallback_layers, wrap_mode_overrides_p); + /* XXX: cogl_pop_source () */ + if (overrode_material) + { + cogl_handle_unref (ctx->source_material); + ctx->source_material = original_source_material; + /* XXX: when we have weak materials then any override material + * should get associated with the original material so we don't + * create lots of one-shot materials! */ + } + /* Reset the size of the logged vertex array because rendering rectangles expects it to start at 0 */ g_array_set_size (ctx->logged_vertices, 0); diff --git a/clutter/cogl/cogl/cogl-vertex-buffer.c b/clutter/cogl/cogl/cogl-vertex-buffer.c index 0c9b7569c..a4af74f89 100644 --- a/clutter/cogl/cogl/cogl-vertex-buffer.c +++ b/clutter/cogl/cogl/cogl-vertex-buffer.c @@ -1525,6 +1525,8 @@ enable_state_for_drawing_buffer (CoglVertexBuffer *buffer) _COGL_GET_CONTEXT (ctx, COGL_INVALID_HANDLE); + source = ctx->source_material; + if (buffer->new_attributes) cogl_vertex_buffer_submit_real (buffer); @@ -1572,13 +1574,21 @@ enable_state_for_drawing_buffer (CoglVertexBuffer *buffer) switch (type) { case COGL_VERTEX_BUFFER_ATTRIB_FLAG_COLOR_ARRAY: - enable_flags |= COGL_ENABLE_COLOR_ARRAY | COGL_ENABLE_BLEND; + enable_flags |= COGL_ENABLE_COLOR_ARRAY; /* GE (glEnableClientState (GL_COLOR_ARRAY)); */ pointer = (const GLvoid *)(base + attribute->u.vbo_offset); GE (glColorPointer (attribute->n_components, gl_type, attribute->stride, pointer)); + + if (!_cogl_material_get_real_blend_enabled (ctx->source_material)) + { + CoglMaterialBlendEnable blend_enable = + COGL_MATERIAL_BLEND_ENABLE_ENABLED; + source = cogl_material_copy (ctx->source_material); + _cogl_material_set_blend_enabled (source, blend_enable); + } break; case COGL_VERTEX_BUFFER_ATTRIB_FLAG_NORMAL_ARRAY: /* FIXME: go through cogl cache to enable normal array */ @@ -1634,7 +1644,7 @@ enable_state_for_drawing_buffer (CoglVertexBuffer *buffer) } } - layers = cogl_material_get_layers (ctx->source_material); + layers = cogl_material_get_layers (source); for (tmp = (GList *)layers, i = 0; tmp != NULL; tmp = tmp->next, i++) @@ -1719,14 +1729,13 @@ enable_state_for_drawing_buffer (CoglVertexBuffer *buffer) if (G_UNLIKELY (ctx->legacy_state_set)) { - source = cogl_material_copy (ctx->source_material); + /* If we haven't already created a derived material... */ + if (source == ctx->source_material) + source = cogl_material_copy (ctx->source_material); _cogl_material_apply_legacy_state (source); } - else - source = ctx->source_material; _cogl_material_flush_gl_state (source, &options); - enable_flags |= _cogl_material_get_cogl_enable_flags (source); if (ctx->enable_backface_culling) enable_flags |= COGL_ENABLE_BACKFACE_CULLING; diff --git a/clutter/cogl/cogl/cogl.c b/clutter/cogl/cogl/cogl.c index 617ed9182..41ccad873 100644 --- a/clutter/cogl/cogl/cogl.c +++ b/clutter/cogl/cogl/cogl.c @@ -266,10 +266,6 @@ _cogl_enable (unsigned long flags) */ _COGL_GET_CONTEXT (ctx, NO_RETVAL); - toggle_flag (ctx, flags, - COGL_ENABLE_BLEND, - GL_BLEND); - toggle_flag (ctx, flags, COGL_ENABLE_BACKFACE_CULLING, GL_CULL_FACE); @@ -858,10 +854,6 @@ cogl_begin_gl (void) options.flags = 0; _cogl_material_flush_gl_state (ctx->source_material, &options); - /* FIXME: This api is a bit yukky, ideally it will be removed if we - * re-work the _cogl_enable mechanism */ - enable_flags |= _cogl_material_get_cogl_enable_flags (ctx->source_material); - if (ctx->enable_backface_culling) enable_flags |= COGL_ENABLE_BACKFACE_CULLING;