arbfp: Adds an ARBfp program cache

This adds a cache (A GHashTable) of ARBfp programs and before ever
starting to code-generate a new program we will always first try and
find an existing program in the cache. This uses _cogl_pipeline_hash and
_cogl_pipeline_equal to hash and compare the keys for the cache.

There is a new COGL_DEBUG=disable-program-caches option that can disable
the cache for debugging purposes.
This commit is contained in:
Robert Bragg 2010-12-03 12:01:18 +00:00
parent e87c3820ee
commit 464ed7e6b7
7 changed files with 118 additions and 23 deletions

View File

@ -40,6 +40,7 @@
#include <string.h> #include <string.h>
#ifdef HAVE_COGL_GL #ifdef HAVE_COGL_GL
#include "cogl-pipeline-arbfp-private.h"
#define glActiveTexture _context->drv.pf_glActiveTexture #define glActiveTexture _context->drv.pf_glActiveTexture
#endif #endif
@ -207,6 +208,9 @@ cogl_create_context (void)
_context->legacy_depth_test_enabled = FALSE; _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++) for (i = 0; i < COGL_BUFFER_BIND_TARGET_COUNT; i++)
_context->current_buffer[i] = NULL; _context->current_buffer[i] = NULL;
@ -356,6 +360,8 @@ _cogl_destroy_context (void)
g_slist_free (_context->texture_types); g_slist_free (_context->texture_types);
g_slist_free (_context->buffer_types); g_slist_free (_context->buffer_types);
g_hash_table_unref (_context->arbfp_cache);
g_free (_context); g_free (_context);
} }

View File

@ -91,6 +91,8 @@ typedef struct
int legacy_state_set; int legacy_state_set;
GHashTable *arbfp_cache;
/* Textures */ /* Textures */
CoglHandle default_gl_texture_2d_tex; CoglHandle default_gl_texture_2d_tex;
CoglHandle default_gl_texture_rect_tex; CoglHandle default_gl_texture_rect_tex;

View File

@ -163,4 +163,9 @@ OPT (OFFSCREEN,
"offscreen", "offscreen",
"Trace offscreen support", "Trace offscreen support",
"Debug offscreen support") "Debug offscreen support")
OPT (DISABLE_BLENDING,
"Root Cause",
"disable-program-caches",
"Disable program caches",
"Disable fallback caches for arbfp and glsl programs")

View File

@ -32,8 +32,9 @@
#ifdef COGL_ENABLE_DEBUG #ifdef COGL_ENABLE_DEBUG
/* XXX: If you add a debug option, please also scroll down to /* XXX: If you add a debug option, please also add an option
* cogl_arg_debug_cb() and add a "help" description of the option too. * 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 /* 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-blending", COGL_DEBUG_DISABLE_BLENDING},
{ "disable-npot-textures", COGL_DEBUG_DISABLE_NPOT_TEXTURES}, { "disable-npot-textures", COGL_DEBUG_DISABLE_NPOT_TEXTURES},
{ "wireframe", COGL_DEBUG_WIREFRAME}, { "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 = static const int n_cogl_behavioural_debug_keys =
G_N_ELEMENTS (cogl_behavioural_debug_keys); G_N_ELEMENTS (cogl_behavioural_debug_keys);

View File

@ -59,7 +59,8 @@ typedef enum {
COGL_DEBUG_BITMAP = 1 << 26, COGL_DEBUG_BITMAP = 1 << 26,
COGL_DEBUG_DISABLE_NPOT_TEXTURES = 1 << 27, COGL_DEBUG_DISABLE_NPOT_TEXTURES = 1 << 27,
COGL_DEBUG_WIREFRAME = 1 << 28, 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; } CoglDebugFlags;
#ifdef COGL_ENABLE_DEBUG #ifdef COGL_ENABLE_DEBUG

View File

@ -32,5 +32,11 @@
extern const CoglPipelineBackend _cogl_pipeline_arbfp_backend; 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 */ #endif /* __COGL_PIPELINE_ARBFP_PRIVATE_H */

View File

@ -72,6 +72,30 @@
#define GL_TEXTURE_3D 0x806F #define GL_TEXTURE_3D 0x806F
#endif #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 typedef struct _UnitState
{ {
int constant_id; /* The program.local[] index */ int constant_id; /* The program.local[] index */
@ -239,15 +263,27 @@ _cogl_pipeline_backend_arbfp_start (CoglPipeline *pipeline,
set_arbfp_priv (authority, authority_priv); set_arbfp_priv (authority, authority_priv);
} }
/* If we don't have an existing program associated with the /* If we haven't yet found an existing program then before we resort to
* arbfp-authority then start generating code for a new program... * 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) if (!authority_priv->arbfp_program_state)
{ {
ArbfpProgramState *arbfp_program_state = ArbfpProgramState *arbfp_program_state =
arbfp_program_state_new (n_layers); arbfp_program_state_new (n_layers);
authority_priv->arbfp_program_state = arbfp_program_state; 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; arbfp_program_state->user_program = user_program;
if (user_program == COGL_INVALID_HANDLE) if (user_program == COGL_INVALID_HANDLE)
{ {
@ -284,6 +320,34 @@ _cogl_pipeline_backend_arbfp_start (CoglPipeline *pipeline,
return TRUE; 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 * static const char *
gl_target_to_arbfp_string (GLenum gl_target) gl_target_to_arbfp_string (GLenum gl_target)
{ {
@ -842,6 +906,28 @@ _cogl_pipeline_backend_arbfp_end (CoglPipeline *pipeline,
} }
arbfp_program_state->source = NULL; 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) if (arbfp_program_state->user_program != COGL_INVALID_HANDLE)
@ -918,12 +1004,7 @@ _cogl_pipeline_backend_arbfp_pipeline_pre_change_notify (
CoglPipelineState change, CoglPipelineState change,
const CoglColor *new_color) const CoglColor *new_color)
{ {
static const unsigned long fragment_op_changes = if (!(change & COGL_PIPELINE_ARBFP_FRAGMENT_PROGRAM_STATE_MASK))
COGL_PIPELINE_STATE_LAYERS |
COGL_PIPELINE_STATE_USER_SHADER;
/* TODO: COGL_PIPELINE_STATE_FOG */
if (!(change & fragment_op_changes))
return; return;
dirty_arbfp_program_state (pipeline); dirty_arbfp_program_state (pipeline);
@ -943,19 +1024,11 @@ _cogl_pipeline_backend_arbfp_layer_pre_change_notify (
CoglPipelineLayer *layer, CoglPipelineLayer *layer,
CoglPipelineLayerState change) CoglPipelineLayerState change)
{ {
CoglPipelineBackendARBfpPrivate *priv; CoglPipelineBackendARBfpPrivate *priv = get_arbfp_priv (owner);
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);
if (!priv) if (!priv)
return; return;
if (!(change & not_fragment_op_changes)) if (change & COGL_PIPELINE_ARBFP_LAYER_FRAGMENT_PROGRAM_STATE_MASK)
{ {
dirty_arbfp_program_state (owner); dirty_arbfp_program_state (owner);
return; return;