diff --git a/cogl/cogl-context.c b/cogl/cogl-context.c index a366b8bfd..3044ec099 100644 --- a/cogl/cogl-context.c +++ b/cogl/cogl-context.c @@ -40,6 +40,7 @@ #include #ifdef HAVE_COGL_GL +#include "cogl-pipeline-arbfp-private.h" #define glActiveTexture _context->drv.pf_glActiveTexture #endif @@ -207,6 +208,9 @@ cogl_create_context (void) _context->legacy_depth_test_enabled = FALSE; + _context->arbfp_cache = g_hash_table_new (_cogl_pipeline_arbfp_hash, + _cogl_pipeline_arbfp_equal); + for (i = 0; i < COGL_BUFFER_BIND_TARGET_COUNT; i++) _context->current_buffer[i] = NULL; @@ -356,6 +360,8 @@ _cogl_destroy_context (void) g_slist_free (_context->texture_types); g_slist_free (_context->buffer_types); + g_hash_table_unref (_context->arbfp_cache); + g_free (_context); } diff --git a/cogl/cogl-context.h b/cogl/cogl-context.h index 5511ed835..14d70e9fd 100644 --- a/cogl/cogl-context.h +++ b/cogl/cogl-context.h @@ -91,6 +91,8 @@ typedef struct int legacy_state_set; + GHashTable *arbfp_cache; + /* Textures */ CoglHandle default_gl_texture_2d_tex; CoglHandle default_gl_texture_rect_tex; diff --git a/cogl/cogl-debug-options.h b/cogl/cogl-debug-options.h index c93c301f3..972c69cfe 100644 --- a/cogl/cogl-debug-options.h +++ b/cogl/cogl-debug-options.h @@ -163,4 +163,9 @@ OPT (OFFSCREEN, "offscreen", "Trace offscreen support", "Debug offscreen support") +OPT (DISABLE_BLENDING, + "Root Cause", + "disable-program-caches", + "Disable program caches", + "Disable fallback caches for arbfp and glsl programs") diff --git a/cogl/cogl-debug.c b/cogl/cogl-debug.c index 5cfa8339e..5c2b5c2fe 100644 --- a/cogl/cogl-debug.c +++ b/cogl/cogl-debug.c @@ -32,8 +32,9 @@ #ifdef COGL_ENABLE_DEBUG -/* XXX: If you add a debug option, please also scroll down to - * cogl_arg_debug_cb() and add a "help" description of the option too. +/* XXX: If you add a debug option, please also add an option + * definition to cogl-debug-options.h. This will enable us - for + * example - to emit a "help" description for the option. */ /* NB: Only these options get enabled if COGL_DEBUG=all is @@ -73,7 +74,8 @@ static const GDebugKey cogl_behavioural_debug_keys[] = { { "disable-blending", COGL_DEBUG_DISABLE_BLENDING}, { "disable-npot-textures", COGL_DEBUG_DISABLE_NPOT_TEXTURES}, { "wireframe", COGL_DEBUG_WIREFRAME}, - { "disable-software-clip", COGL_DEBUG_DISABLE_SOFTWARE_CLIP} + { "disable-software-clip", COGL_DEBUG_DISABLE_SOFTWARE_CLIP}, + { "disable-program-caches", COGL_DEBUG_DISABLE_PROGRAM_CACHES} }; static const int n_cogl_behavioural_debug_keys = G_N_ELEMENTS (cogl_behavioural_debug_keys); diff --git a/cogl/cogl-debug.h b/cogl/cogl-debug.h index 4c49d7381..1a06bf734 100644 --- a/cogl/cogl-debug.h +++ b/cogl/cogl-debug.h @@ -59,7 +59,8 @@ typedef enum { COGL_DEBUG_BITMAP = 1 << 26, COGL_DEBUG_DISABLE_NPOT_TEXTURES = 1 << 27, COGL_DEBUG_WIREFRAME = 1 << 28, - COGL_DEBUG_DISABLE_SOFTWARE_CLIP = 1 << 29 + COGL_DEBUG_DISABLE_SOFTWARE_CLIP = 1 << 29, + COGL_DEBUG_DISABLE_PROGRAM_CACHES = 1 << 30 } CoglDebugFlags; #ifdef COGL_ENABLE_DEBUG diff --git a/cogl/cogl-pipeline-arbfp-private.h b/cogl/cogl-pipeline-arbfp-private.h index cbd2b39d0..d7be94868 100644 --- a/cogl/cogl-pipeline-arbfp-private.h +++ b/cogl/cogl-pipeline-arbfp-private.h @@ -32,5 +32,11 @@ extern const CoglPipelineBackend _cogl_pipeline_arbfp_backend; +unsigned int +_cogl_pipeline_arbfp_hash (const void *pipeline); + +gboolean +_cogl_pipeline_arbfp_equal (const void *pipeline0, const void *pipeline1); + #endif /* __COGL_PIPELINE_ARBFP_PRIVATE_H */ diff --git a/cogl/cogl-pipeline-arbfp.c b/cogl/cogl-pipeline-arbfp.c index 1e558c9d4..7daf60f0c 100644 --- a/cogl/cogl-pipeline-arbfp.c +++ b/cogl/cogl-pipeline-arbfp.c @@ -72,6 +72,30 @@ #define GL_TEXTURE_3D 0x806F #endif +/* When we add new pipeline or layer state groups we need to be careful to + * update backends to understand if that new state is associated with vertex, + * fragment or other processing. The idea here is to attribute which groups + * affect fragment processing and more specifically which contribute to arbfp + * code generation. + */ + +#define COGL_PIPELINE_ARBFP_FRAGMENT_STATE_MASK \ + (COGL_PIPELINE_STATE_LAYERS | \ + COGL_PIPELINE_STATE_USER_SHADER) + +#define COGL_PIPELINE_ARBFP_FRAGMENT_PROGRAM_STATE_MASK \ + COGL_PIPELINE_ARBFP_FRAGMENT_STATE_MASK + +#define COGL_PIPELINE_ARBFP_LAYER_FRAGMENT_STATE_MASK \ + COGL_PIPELINE_LAYER_STATE_ALL + +#define COGL_PIPELINE_ARBFP_LAYER_FRAGMENT_PROGRAM_STATE_MASK \ + (COGL_PIPELINE_ARBFP_LAYER_FRAGMENT_STATE_MASK & \ + ~(COGL_PIPELINE_LAYER_STATE_COMBINE_CONSTANT | \ + COGL_PIPELINE_LAYER_STATE_FILTERS | \ + COGL_PIPELINE_LAYER_STATE_WRAP_MODES | \ + COGL_PIPELINE_LAYER_STATE_USER_MATRIX)) + typedef struct _UnitState { int constant_id; /* The program.local[] index */ @@ -239,15 +263,27 @@ _cogl_pipeline_backend_arbfp_start (CoglPipeline *pipeline, set_arbfp_priv (authority, authority_priv); } - /* If we don't have an existing program associated with the - * arbfp-authority then start generating code for a new program... - */ + /* If we haven't yet found an existing program then before we resort to + * generating a new arbfp program we see if we can find a suitable + * program in the arbfp_cache. */ + if (!authority_priv->arbfp_program_state && + G_LIKELY (!(cogl_debug_flags & COGL_DEBUG_DISABLE_PROGRAM_CACHES))) + { + authority_priv->arbfp_program_state = + g_hash_table_lookup (ctx->arbfp_cache, authority); + if (authority_priv->arbfp_program_state) + arbfp_program_state_ref (authority_priv->arbfp_program_state); + } + if (!authority_priv->arbfp_program_state) { ArbfpProgramState *arbfp_program_state = arbfp_program_state_new (n_layers); authority_priv->arbfp_program_state = arbfp_program_state; + /* If we don't have an existing program associated with the + * arbfp-authority then start generating code for a new program... + */ arbfp_program_state->user_program = user_program; if (user_program == COGL_INVALID_HANDLE) { @@ -284,6 +320,34 @@ _cogl_pipeline_backend_arbfp_start (CoglPipeline *pipeline, return TRUE; } +unsigned int +_cogl_pipeline_arbfp_hash (const void *data) +{ + unsigned long fragment_state = + COGL_PIPELINE_ARBFP_FRAGMENT_PROGRAM_STATE_MASK; + unsigned long layer_fragment_state = + COGL_PIPELINE_ARBFP_LAYER_FRAGMENT_PROGRAM_STATE_MASK; + CoglPipelineEvalFlags flags = COGL_PIPELINE_EVAL_FLAG_IGNORE_TEXTURE_DATA; + + return _cogl_pipeline_hash ((CoglPipeline *)data, + fragment_state, layer_fragment_state, + flags); +} + +gboolean +_cogl_pipeline_arbfp_equal (const void *a, const void *b) +{ + unsigned long fragment_state = + COGL_PIPELINE_ARBFP_FRAGMENT_PROGRAM_STATE_MASK; + unsigned long layer_fragment_state = + COGL_PIPELINE_ARBFP_LAYER_FRAGMENT_PROGRAM_STATE_MASK; + CoglPipelineEvalFlags flags = COGL_PIPELINE_EVAL_FLAG_IGNORE_TEXTURE_DATA; + + return _cogl_pipeline_equal ((CoglPipeline *)a, (CoglPipeline *)b, + fragment_state, layer_fragment_state, + flags); +} + static const char * gl_target_to_arbfp_string (GLenum gl_target) { @@ -842,6 +906,28 @@ _cogl_pipeline_backend_arbfp_end (CoglPipeline *pipeline, } arbfp_program_state->source = NULL; + + if (G_LIKELY (!(cogl_debug_flags & COGL_DEBUG_DISABLE_PROGRAM_CACHES))) + { + /* XXX: I wish there was a way to insert into a GHashTable + * with a pre-calculated hash value since there is a cost to + * calculating the hash of a CoglPipeline and in this case + * we know we have already called _cogl_pipeline_hash during + * _cogl_pipeline_arbfp_backend_start so we could pass the + * value through to here to avoid hashing it again. + */ + g_hash_table_insert (ctx->arbfp_cache, pipeline, arbfp_program_state); + arbfp_program_state_ref (arbfp_program_state); + if (G_UNLIKELY (g_hash_table_size (ctx->arbfp_cache) > 50)) + { + static gboolean seen = FALSE; + if (!seen) + g_warning ("Over 50 separate ARBfp programs have been " + "generated which is very unusual, so something " + "is probably wrong!\n"); + seen = TRUE; + } + } } if (arbfp_program_state->user_program != COGL_INVALID_HANDLE) @@ -918,12 +1004,7 @@ _cogl_pipeline_backend_arbfp_pipeline_pre_change_notify ( CoglPipelineState change, const CoglColor *new_color) { - static const unsigned long fragment_op_changes = - COGL_PIPELINE_STATE_LAYERS | - COGL_PIPELINE_STATE_USER_SHADER; - /* TODO: COGL_PIPELINE_STATE_FOG */ - - if (!(change & fragment_op_changes)) + if (!(change & COGL_PIPELINE_ARBFP_FRAGMENT_PROGRAM_STATE_MASK)) return; dirty_arbfp_program_state (pipeline); @@ -943,19 +1024,11 @@ _cogl_pipeline_backend_arbfp_layer_pre_change_notify ( CoglPipelineLayer *layer, CoglPipelineLayerState change) { - CoglPipelineBackendARBfpPrivate *priv; - static const unsigned long not_fragment_op_changes = - COGL_PIPELINE_LAYER_STATE_COMBINE_CONSTANT | - COGL_PIPELINE_LAYER_STATE_TEXTURE | - COGL_PIPELINE_LAYER_STATE_FILTERS | - COGL_PIPELINE_LAYER_STATE_WRAP_MODES | - COGL_PIPELINE_LAYER_STATE_USER_MATRIX; - - priv = get_arbfp_priv (owner); + CoglPipelineBackendARBfpPrivate *priv = get_arbfp_priv (owner); if (!priv) return; - if (!(change & not_fragment_op_changes)) + if (change & COGL_PIPELINE_ARBFP_LAYER_FRAGMENT_PROGRAM_STATE_MASK) { dirty_arbfp_program_state (owner); return;