arbfp: Adds an ARBfp program cache
authorRobert Bragg <robert@linux.intel.com>
Fri, 3 Dec 2010 12:01:18 +0000 (12:01 +0000)
committerRobert Bragg <robert@linux.intel.com>
Tue, 7 Dec 2010 16:00:32 +0000 (16:00 +0000)
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.

clutter/cogl/cogl/cogl-context.c
clutter/cogl/cogl/cogl-context.h
clutter/cogl/cogl/cogl-debug-options.h
clutter/cogl/cogl/cogl-debug.c
clutter/cogl/cogl/cogl-debug.h
clutter/cogl/cogl/cogl-pipeline-arbfp-private.h
clutter/cogl/cogl/cogl-pipeline-arbfp.c

index a366b8b..3044ec0 100644 (file)
@@ -40,6 +40,7 @@
 #include <string.h>
 
 #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);
 }
 
index 5511ed8..14d70e9 100644 (file)
@@ -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;
index c93c301..972c69c 100644 (file)
@@ -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")
 
index 5cfa833..5c2b5c2 100644 (file)
@@ -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);
index 4c49d73..1a06bf7 100644 (file)
@@ -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
index cbd2b39..d7be948 100644 (file)
 
 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 */
 
index 1e558c9..7daf60f 100644 (file)
 #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;