diff --git a/cogl/cogl-atlas-texture.c b/cogl/cogl-atlas-texture.c index b414b46f3..cf09a6490 100644 --- a/cogl/cogl-atlas-texture.c +++ b/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/cogl/cogl-context.c b/cogl/cogl-context.c index 830c69eda..a01d74007 100644 --- a/cogl/cogl-context.c +++ b/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/cogl/cogl-context.h b/cogl/cogl-context.h index 148bbaab4..530184f0b 100644 --- a/cogl/cogl-context.h +++ b/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/cogl/cogl-debug.c b/cogl/cogl-debug.c index df985fa4b..68ca4c38e 100644 --- a/cogl/cogl-debug.c +++ b/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/cogl/cogl-debug.h b/cogl/cogl-debug.h index 393373791..8341f68a2 100644 --- a/cogl/cogl-debug.h +++ b/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/cogl/cogl-internal.h b/cogl/cogl-internal.h index 7b1253a07..aa32f04c5 100644 --- a/cogl/cogl-internal.h +++ b/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/cogl/cogl-journal.c b/cogl/cogl-journal.c index 9bedf6b15..e850a2779 100644 --- a/cogl/cogl-journal.c +++ b/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/cogl/cogl-material-private.h b/cogl/cogl-material-private.h index 08ea01338..a1029fe84 100644 --- a/cogl/cogl-material-private.h +++ b/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/cogl/cogl-material.c b/cogl/cogl-material.c index 11cd52e75..f8f366fca 100644 --- a/cogl/cogl-material.c +++ b/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/cogl/cogl-material.h b/cogl/cogl-material.h index 425c7f77d..12c9b39a0 100644 --- a/cogl/cogl-material.h +++ b/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/cogl/cogl-path.c b/cogl/cogl-path.c index 46c6d59ef..e1c3b8f4d 100644 --- a/cogl/cogl-path.c +++ b/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/cogl/cogl-primitives.c b/cogl/cogl-primitives.c index 84a498596..b24ff3af4 100644 --- a/cogl/cogl-primitives.c +++ b/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/cogl/cogl-vertex-buffer.c b/cogl/cogl-vertex-buffer.c index 0c9b7569c..a4af74f89 100644 --- a/cogl/cogl-vertex-buffer.c +++ b/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/cogl/cogl.c b/cogl/cogl.c index 617ed9182..41ccad873 100644 --- a/cogl/cogl.c +++ b/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;