[cogl/wip/neil/snippets: 8/27] cogl-pipeline: Add two hook points for adding shader snippets



commit ce1799f9b96c67574af1c9b1de68d92000fd3b36
Author: Neil Roberts <neil linux intel com>
Date:   Thu Nov 17 16:52:21 2011 +0000

    cogl-pipeline: Add two hook points for adding shader snippets
    
    This adds two new public experimental functions for attaching
    CoglSnippets to two hook points on a CoglPipeline:
    
    void cogl_pipeline_add_vertex_hook (CoglPipeline *, CoglSnippet *)
    void cogl_pipeline_add_fragment_hook (CoglPipeline *, CoglSnippet *)
    
    The hooks are intended to be around the entire vertex or fragment
    processing. That means the pre string in the snippet will be inserted
    at the very top of the main function and the post function will be
    inserted at the very end. The declarations get inserted in the global
    scope.
    
    The snippets are stored in two separate linked lists with a structure
    containing an enum representing the hook point and a pointer to the
    snippet. The lists are meant to be for hooks that affect the vertex
    shader and fragment shader respectively. Although there are currently
    only two hooks and the names match these two lists, the intention is
    *not* that each new hook will be in a separate list. The separation of
    the lists is just to make it easier to determine which shader needs to
    be regenerated when a new snippet is added.
    
    When a pipeline becomes the authority for either the vertex or
    fragment snipper state, it simply copies the entire list from the
    previous authority (although of course the shader snippet objects are
    referenced instead of copied so it doesn't duplicate the source
    strings).
    
    Each string is inserted into its own block in the shader. This means
    that each string has its own scope so it doesn't need to worry about
    name collisions with variables in other snippets. However it does mean
    that the pre and post strings can't share variables. It could be
    possible to wrap both parts in one block and then wrap the actual
    inner hook code in another block, however this would mean that any
    further snippets within the outer snippet would be able to see those
    variables. Perhaps something to consider would be to put each snippet
    into its own function which calls another function between the pre and
    post strings to do further processing.
    
    The pipeline cache for generated programs was previously shared with
    the fragment shader cache because the state that affects vertex
    shaders was a subset of the state that affects fragment shaders. This
    is no longer the case because there is a separate state mask for
    vertex snippets so the program cache now has its own hash table.

 cogl/cogl-pipeline-cache.c         |   84 +++++++++++++----
 cogl/cogl-pipeline-fragend-arbfp.c |    5 +
 cogl/cogl-pipeline-fragend-fixed.c |    5 +
 cogl/cogl-pipeline-fragend-glsl.c  |   61 ++++++++++++
 cogl/cogl-pipeline-private.h       |   52 ++++++++++-
 cogl/cogl-pipeline-state-private.h |   22 +++++
 cogl/cogl-pipeline-state.c         |  179 ++++++++++++++++++++++++++++++++++++
 cogl/cogl-pipeline-state.h         |   58 ++++++++++++
 cogl/cogl-pipeline-vertend-fixed.c |    5 +
 cogl/cogl-pipeline-vertend-glsl.c  |   62 ++++++++++++-
 cogl/cogl-pipeline.c               |   85 +++++++++++++++++-
 cogl/cogl-pipeline.h               |    1 +
 12 files changed, 593 insertions(+), 26 deletions(-)
---
diff --git a/cogl/cogl-pipeline-cache.c b/cogl/cogl-pipeline-cache.c
index b5d99da..5df18ce 100644
--- a/cogl/cogl-pipeline-cache.c
+++ b/cogl/cogl-pipeline-cache.c
@@ -37,6 +37,7 @@ struct _CoglPipelineCache
 {
   GHashTable *fragment_hash;
   GHashTable *vertex_hash;
+  GHashTable *combined_hash;
 };
 
 static unsigned int
@@ -101,6 +102,46 @@ pipeline_vertex_equal (const void *a, const void *b)
                                0);
 }
 
+static unsigned int
+pipeline_combined_hash (const void *data)
+{
+  unsigned int combined_state;
+  unsigned int layer_combined_state;
+
+  _COGL_GET_CONTEXT (ctx, 0);
+
+  combined_state =
+    _cogl_pipeline_get_state_for_fragment_codegen (ctx) |
+    COGL_PIPELINE_STATE_AFFECTS_VERTEX_CODEGEN;
+  layer_combined_state =
+    _cogl_pipeline_get_layer_state_for_fragment_codegen (ctx) |
+    COGL_PIPELINE_LAYER_STATE_AFFECTS_VERTEX_CODEGEN;
+
+  return _cogl_pipeline_hash ((CoglPipeline *)data,
+                              combined_state, layer_combined_state,
+                              0);
+}
+
+static gboolean
+pipeline_combined_equal (const void *a, const void *b)
+{
+  unsigned int combined_state;
+  unsigned int layer_combined_state;
+
+  _COGL_GET_CONTEXT (ctx, 0);
+
+  combined_state =
+    _cogl_pipeline_get_state_for_fragment_codegen (ctx) |
+    COGL_PIPELINE_STATE_AFFECTS_VERTEX_CODEGEN;
+  layer_combined_state =
+    _cogl_pipeline_get_layer_state_for_fragment_codegen (ctx) |
+    COGL_PIPELINE_LAYER_STATE_AFFECTS_VERTEX_CODEGEN;
+
+  return _cogl_pipeline_equal ((CoglPipeline *)a, (CoglPipeline *)b,
+                               combined_state, layer_combined_state,
+                               0);
+}
+
 CoglPipelineCache *
 cogl_pipeline_cache_new (void)
 {
@@ -114,6 +155,10 @@ cogl_pipeline_cache_new (void)
                                               pipeline_vertex_equal,
                                               cogl_object_unref,
                                               cogl_object_unref);
+  cache->combined_hash = g_hash_table_new_full (pipeline_combined_hash,
+                                                pipeline_combined_equal,
+                                                cogl_object_unref,
+                                                cogl_object_unref);
 
   return cache;
 }
@@ -123,6 +168,7 @@ cogl_pipeline_cache_free (CoglPipelineCache *cache)
 {
   g_hash_table_destroy (cache->fragment_hash);
   g_hash_table_destroy (cache->vertex_hash);
+  g_hash_table_destroy (cache->combined_hash);
   g_free (cache);
 }
 
@@ -210,27 +256,27 @@ CoglPipeline *
 _cogl_pipeline_cache_get_combined_template (CoglPipelineCache *cache,
                                             CoglPipeline *key_pipeline)
 {
-  unsigned int pipeline_state_for_fragment_codegen;
-  unsigned int pipeline_layer_state_for_fragment_codegen;
-
-  _COGL_GET_CONTEXT (ctx, NULL);
+  CoglPipeline *template =
+    g_hash_table_lookup (cache->combined_hash, key_pipeline);
 
-  pipeline_state_for_fragment_codegen =
-    _cogl_pipeline_get_state_for_fragment_codegen (ctx);
-  pipeline_layer_state_for_fragment_codegen =
-    _cogl_pipeline_get_layer_state_for_fragment_codegen (ctx);
+  if (template == NULL)
+    {
+      template = cogl_pipeline_copy (key_pipeline);
 
-  /* Currently the vertex shader state is a subset of the fragment
-     shader state so we can avoid a third hash table here by just
-     using the fragment shader table. This assert should catch it if
-     that ever changes */
+      g_hash_table_insert (cache->combined_hash,
+                           template,
+                           cogl_object_ref (template));
 
-  g_assert ((pipeline_state_for_fragment_codegen |
-             COGL_PIPELINE_STATE_AFFECTS_VERTEX_CODEGEN) ==
-            pipeline_state_for_fragment_codegen);
-  g_assert ((pipeline_layer_state_for_fragment_codegen |
-             COGL_PIPELINE_LAYER_STATE_AFFECTS_VERTEX_CODEGEN) ==
-            pipeline_layer_state_for_fragment_codegen);
+      if (G_UNLIKELY (g_hash_table_size (cache->combined_hash) > 50))
+        {
+          static gboolean seen = FALSE;
+          if (!seen)
+            g_warning ("Over 50 separate programs have been "
+                       "generated which is very unusual, so something "
+                       "is probably wrong!\n");
+          seen = TRUE;
+        }
+    }
 
-  return _cogl_pipeline_cache_get_fragment_template (cache, key_pipeline);
+  return template;
 }
diff --git a/cogl/cogl-pipeline-fragend-arbfp.c b/cogl/cogl-pipeline-fragend-arbfp.c
index 10bd1bd..b08899b 100644
--- a/cogl/cogl-pipeline-fragend-arbfp.c
+++ b/cogl/cogl-pipeline-fragend-arbfp.c
@@ -32,6 +32,7 @@
 #include "cogl-debug.h"
 #include "cogl-context-private.h"
 #include "cogl-pipeline-private.h"
+#include "cogl-pipeline-state-private.h"
 #include "cogl-pipeline-layer-private.h"
 
 #ifdef COGL_PIPELINE_FRAGEND_ARBFP
@@ -178,6 +179,10 @@ _cogl_pipeline_fragend_arbfp_start (CoglPipeline *pipeline,
   if (_cogl_pipeline_get_fog_enabled (pipeline))
     return FALSE;
 
+  /* Fragment snippets are only supported in the GLSL fragend */
+  if (_cogl_pipeline_has_fragment_snippets (pipeline))
+    return FALSE;
+
   user_program = cogl_pipeline_get_user_program (pipeline);
   if (user_program != COGL_INVALID_HANDLE)
     {
diff --git a/cogl/cogl-pipeline-fragend-fixed.c b/cogl/cogl-pipeline-fragend-fixed.c
index e6adfca..64b84b8 100644
--- a/cogl/cogl-pipeline-fragend-fixed.c
+++ b/cogl/cogl-pipeline-fragend-fixed.c
@@ -31,6 +31,7 @@
 
 #include "cogl-context-private.h"
 #include "cogl-pipeline-private.h"
+#include "cogl-pipeline-state-private.h"
 #include "cogl-pipeline-opengl-private.h"
 
 #ifdef COGL_PIPELINE_FRAGEND_FIXED
@@ -101,6 +102,10 @@ _cogl_pipeline_fragend_fixed_start (CoglPipeline *pipeline,
   if (ctx->driver == COGL_DRIVER_GLES2)
     return FALSE;
 
+  /* Fragment snippets are only supported in the GLSL fragend */
+  if (_cogl_pipeline_has_fragment_snippets (pipeline))
+    return FALSE;
+
   /* If there is a user program with a fragment shader then the
      appropriate backend for that language should handle it. We can
      still use the fixed fragment backend if the program only contains
diff --git a/cogl/cogl-pipeline-fragend-glsl.c b/cogl/cogl-pipeline-fragend-glsl.c
index 488070b..d445190 100644
--- a/cogl/cogl-pipeline-fragend-glsl.c
+++ b/cogl/cogl-pipeline-fragend-glsl.c
@@ -181,6 +181,16 @@ _cogl_pipeline_fragend_glsl_get_shader (CoglPipeline *pipeline)
     return 0;
 }
 
+static CoglPipelineSnippetList *
+get_fragment_snippets (CoglPipeline *pipeline)
+{
+  pipeline =
+    _cogl_pipeline_get_authority (pipeline,
+                                  COGL_PIPELINE_STATE_FRAGMENT_SNIPPETS);
+
+  return &pipeline->big_state->fragment_snippets;
+}
+
 static gboolean
 _cogl_pipeline_fragend_glsl_start (CoglPipeline *pipeline,
                                    int n_layers,
@@ -190,6 +200,7 @@ _cogl_pipeline_fragend_glsl_start (CoglPipeline *pipeline,
   CoglPipelineShaderState *shader_state;
   CoglPipeline *authority;
   CoglPipeline *template_pipeline = NULL;
+  CoglPipelineSnippet *snippet;
   CoglProgram *user_program;
   int i;
 
@@ -319,6 +330,37 @@ _cogl_pipeline_fragend_glsl_start (CoglPipeline *pipeline,
                    "main ()\n"
                    "{\n");
 
+  COGL_LIST_FOREACH (snippet, get_fragment_snippets (pipeline), list_node)
+    {
+      const char *declarations =
+        cogl_snippet_get_declarations (snippet->snippet);
+
+      /* Add all of the declarations for fragment snippets */
+      if (declarations)
+        {
+          g_string_append (shader_state->header, declarations);
+          g_string_append_c (shader_state->header, '\n');
+        }
+
+      /* Add all of the pre-hooks for fragment processing */
+      if (snippet->hook == COGL_PIPELINE_SNIPPET_HOOK_FRAGMENT)
+        {
+          const char *pre =
+            cogl_snippet_get_pre (snippet->snippet);
+
+          if (pre)
+            {
+              g_string_append (shader_state->source, "  {\n");
+              g_string_append (shader_state->source, pre);
+              g_string_append (shader_state->source, "  }\n");
+            }
+        }
+    }
+
+  /* Enclose the generated fragment processing in a block so that any
+     variables declared in it won't be in the scope of the snippets */
+  g_string_append (shader_state->source, "  {\n");
+
   for (i = 0; i < n_layers; i++)
     {
       shader_state->unit_state[i].sampled = FALSE;
@@ -885,6 +927,7 @@ _cogl_pipeline_fragend_glsl_end (CoglPipeline *pipeline,
       GLint lengths[2];
       GLint compile_status;
       GLuint shader;
+      CoglPipelineSnippet *snippet;
 
       COGL_STATIC_COUNTER (fragend_glsl_compile_counter,
                            "glsl fragment compile counter",
@@ -922,6 +965,24 @@ _cogl_pipeline_fragend_glsl_end (CoglPipeline *pipeline,
         add_alpha_test_snippet (pipeline, shader_state);
 #endif
 
+      /* Close the block surrounding the generated fragment processing */
+      g_string_append (shader_state->source, "  }\n");
+
+      /* Add all of the post-hooks for fragment processing */
+      COGL_LIST_FOREACH (snippet, get_fragment_snippets (pipeline), list_node)
+        if (snippet->hook == COGL_PIPELINE_SNIPPET_HOOK_FRAGMENT)
+          {
+            const char *post =
+              cogl_snippet_get_post (snippet->snippet);
+
+            if (post)
+              {
+                g_string_append (shader_state->source, "  {\n");
+                g_string_append (shader_state->source, post);
+                g_string_append (shader_state->source, "  }\n");
+              }
+          }
+
       g_string_append (shader_state->source, "}\n");
 
       GE_RET( shader, ctx, glCreateShader (GL_FRAGMENT_SHADER) );
diff --git a/cogl/cogl-pipeline-private.h b/cogl/cogl-pipeline-private.h
index a6100a8..e518ecf 100644
--- a/cogl/cogl-pipeline-private.h
+++ b/cogl/cogl-pipeline-private.h
@@ -168,6 +168,8 @@ typedef enum
   COGL_PIPELINE_STATE_LOGIC_OPS_INDEX,
   COGL_PIPELINE_STATE_CULL_FACE_INDEX,
   COGL_PIPELINE_STATE_UNIFORMS_INDEX,
+  COGL_PIPELINE_STATE_VERTEX_SNIPPETS_INDEX,
+  COGL_PIPELINE_STATE_FRAGMENT_SNIPPETS_INDEX,
 
   /* non-sparse */
   COGL_PIPELINE_STATE_REAL_BLEND_ENABLE_INDEX,
@@ -217,6 +219,10 @@ typedef enum _CoglPipelineState
     1L<<COGL_PIPELINE_STATE_CULL_FACE_INDEX,
   COGL_PIPELINE_STATE_UNIFORMS =
     1L<<COGL_PIPELINE_STATE_UNIFORMS_INDEX,
+  COGL_PIPELINE_STATE_VERTEX_SNIPPETS =
+    1L<<COGL_PIPELINE_STATE_VERTEX_SNIPPETS_INDEX,
+  COGL_PIPELINE_STATE_FRAGMENT_SNIPPETS =
+    1L<<COGL_PIPELINE_STATE_FRAGMENT_SNIPPETS_INDEX,
 
   COGL_PIPELINE_STATE_REAL_BLEND_ENABLE =
     1L<<COGL_PIPELINE_STATE_REAL_BLEND_ENABLE_INDEX,
@@ -240,7 +246,9 @@ typedef enum _CoglPipelineState
    COGL_PIPELINE_STATE_LAYERS | \
    COGL_PIPELINE_STATE_LIGHTING | \
    COGL_PIPELINE_STATE_BLEND | \
-   COGL_PIPELINE_STATE_USER_SHADER)
+   COGL_PIPELINE_STATE_USER_SHADER | \
+   COGL_PIPELINE_STATE_VERTEX_SNIPPETS | \
+   COGL_PIPELINE_STATE_FRAGMENT_SNIPPETS)
 
 #define COGL_PIPELINE_STATE_NEEDS_BIG_STATE \
   (COGL_PIPELINE_STATE_LIGHTING | \
@@ -253,7 +261,9 @@ typedef enum _CoglPipelineState
    COGL_PIPELINE_STATE_POINT_SIZE | \
    COGL_PIPELINE_STATE_LOGIC_OPS | \
    COGL_PIPELINE_STATE_CULL_FACE | \
-   COGL_PIPELINE_STATE_UNIFORMS)
+   COGL_PIPELINE_STATE_UNIFORMS | \
+   COGL_PIPELINE_STATE_VERTEX_SNIPPETS | \
+   COGL_PIPELINE_STATE_FRAGMENT_SNIPPETS)
 
 #define COGL_PIPELINE_STATE_MULTI_PROPERTY \
   (COGL_PIPELINE_STATE_LAYERS | \
@@ -263,11 +273,14 @@ typedef enum _CoglPipelineState
    COGL_PIPELINE_STATE_FOG | \
    COGL_PIPELINE_STATE_LOGIC_OPS | \
    COGL_PIPELINE_STATE_CULL_FACE | \
-   COGL_PIPELINE_STATE_UNIFORMS)
+   COGL_PIPELINE_STATE_UNIFORMS | \
+   COGL_PIPELINE_STATE_VERTEX_SNIPPETS | \
+   COGL_PIPELINE_STATE_FRAGMENT_SNIPPETS)
 
 #define COGL_PIPELINE_STATE_AFFECTS_VERTEX_CODEGEN \
   (COGL_PIPELINE_STATE_LAYERS | \
-   COGL_PIPELINE_STATE_USER_SHADER)
+   COGL_PIPELINE_STATE_USER_SHADER | \
+   COGL_PIPELINE_STATE_VERTEX_SNIPPETS)
 
 typedef enum
 {
@@ -353,6 +366,35 @@ typedef struct
   CoglBitmask changed_mask;
 } CoglPipelineUniformsState;
 
+/* Enumeration of all the hook points that a snippet can be attached
+   to within a pipeline. Note that although there are currently only
+   two points that directly correspond to the two state flags, the
+   idea isn't that each new enum here will mean a state flag. The
+   state flags are just intended to mark the split between hooks that
+   affect the fragment shader and hooks that affect the vertex
+   shader. For example, if we add a hook to wrap around the processing
+   for a particular layer then that hook would be part of the fragment
+   snippets state. */
+typedef enum
+{
+  COGL_PIPELINE_SNIPPET_HOOK_VERTEX,
+  COGL_PIPELINE_SNIPPET_HOOK_FRAGMENT
+} CoglPipelineSnippetHook;
+
+typedef struct _CoglPipelineSnippet CoglPipelineSnippet;
+
+COGL_LIST_HEAD (CoglPipelineSnippetList, CoglPipelineSnippet);
+
+struct _CoglPipelineSnippet
+{
+  COGL_LIST_ENTRY (CoglPipelineSnippet) list_node;
+
+  /* Hook where this snippet is attached */
+  CoglPipelineSnippetHook hook;
+
+  CoglSnippet *snippet;
+};
+
 typedef struct
 {
   CoglPipelineLightingState lighting_state;
@@ -365,6 +407,8 @@ typedef struct
   CoglPipelineLogicOpsState logic_ops_state;
   CoglPipelineCullFaceState cull_face_state;
   CoglPipelineUniformsState uniforms_state;
+  CoglPipelineSnippetList vertex_snippets;
+  CoglPipelineSnippetList fragment_snippets;
 } CoglPipelineBigState;
 
 typedef enum
diff --git a/cogl/cogl-pipeline-state-private.h b/cogl/cogl-pipeline-state-private.h
index ef7665a..9a38e11 100644
--- a/cogl/cogl-pipeline-state-private.h
+++ b/cogl/cogl-pipeline-state-private.h
@@ -31,6 +31,12 @@
 CoglPipeline *
 _cogl_pipeline_get_user_program (CoglPipeline *pipeline);
 
+gboolean
+_cogl_pipeline_has_vertex_snippets (CoglPipeline *pipeline);
+
+gboolean
+_cogl_pipeline_has_fragment_snippets (CoglPipeline *pipeline);
+
 void
 _cogl_pipeline_set_fog_state (CoglPipeline *pipeline,
                               const CoglPipelineFogState *fog_state);
@@ -83,6 +89,14 @@ gboolean
 _cogl_pipeline_uniforms_state_equal (CoglPipeline *authority0,
                                      CoglPipeline *authority1);
 
+gboolean
+_cogl_pipeline_vertex_snippets_state_equal (CoglPipeline *authority0,
+                                            CoglPipeline *authority1);
+
+gboolean
+_cogl_pipeline_fragment_snippets_state_equal (CoglPipeline *authority0,
+                                              CoglPipeline *authority1);
+
 void
 _cogl_pipeline_hash_color_state (CoglPipeline *authority,
                                  CoglPipelineHashState *state);
@@ -140,6 +154,14 @@ _cogl_pipeline_hash_uniforms_state (CoglPipeline *authority,
                                     CoglPipelineHashState *state);
 
 void
+_cogl_pipeline_hash_vertex_snippets_state (CoglPipeline *authority,
+                                           CoglPipelineHashState *state);
+
+void
+_cogl_pipeline_hash_fragment_snippets_state (CoglPipeline *authority,
+                                             CoglPipelineHashState *state);
+
+void
 _cogl_pipeline_compare_uniform_differences (unsigned long *differences,
                                             CoglPipeline *pipeline0,
                                             CoglPipeline *pipeline1);
diff --git a/cogl/cogl-pipeline-state.c b/cogl/cogl-pipeline-state.c
index 88bf153..56cdb16 100644
--- a/cogl/cogl-pipeline-state.c
+++ b/cogl/cogl-pipeline-state.c
@@ -35,6 +35,7 @@
 #include "cogl-util.h"
 #include "cogl-depth-state-private.h"
 #include "cogl-pipeline-state-private.h"
+#include "cogl-snippet-private.h"
 
 #include "string.h"
 
@@ -339,6 +340,41 @@ _cogl_pipeline_uniforms_state_equal (CoglPipeline *authority0,
   return TRUE;
 }
 
+static gboolean
+_cogl_pipeline_snippet_list_equal (CoglPipelineSnippetList *list0,
+                                   CoglPipelineSnippetList *list1)
+{
+  CoglPipelineSnippet *l0, *l1;
+
+  for (l0 = COGL_LIST_FIRST (list0), l1 = COGL_LIST_FIRST (list1);
+       l0 && l1;
+       l0 = COGL_LIST_NEXT (l0, list_node), l1 = COGL_LIST_NEXT (l1, list_node))
+    if (l0->hook != l1->hook || l0->snippet != l1->snippet)
+      return FALSE;
+
+  return l0 == NULL && l1 == NULL;
+}
+
+gboolean
+_cogl_pipeline_vertex_snippets_state_equal (CoglPipeline *authority0,
+                                            CoglPipeline *authority1)
+{
+  return _cogl_pipeline_snippet_list_equal (&authority0->big_state->
+                                            vertex_snippets,
+                                            &authority1->big_state->
+                                            vertex_snippets);
+}
+
+gboolean
+_cogl_pipeline_fragment_snippets_state_equal (CoglPipeline *authority0,
+                                              CoglPipeline *authority1)
+{
+  return _cogl_pipeline_snippet_list_equal (&authority0->big_state->
+                                            fragment_snippets,
+                                            &authority1->big_state->
+                                            fragment_snippets);
+}
+
 void
 cogl_pipeline_get_color (CoglPipeline *pipeline,
                          CoglColor    *color)
@@ -1548,6 +1584,114 @@ cogl_pipeline_set_uniform_matrix (CoglPipeline *pipeline,
                                 value);
 }
 
+static void
+_cogl_pipeline_snippet_list_add (CoglPipelineSnippetList *list,
+                                 CoglPipelineSnippetHook hook,
+                                 CoglSnippet *snippet)
+{
+  CoglPipelineSnippet *pipeline_snippet = g_slice_new (CoglPipelineSnippet);
+
+  pipeline_snippet->hook = hook;
+  pipeline_snippet->snippet = cogl_object_ref (snippet);
+
+  _cogl_snippet_make_immutable (pipeline_snippet->snippet);
+
+  if (COGL_LIST_EMPTY (list))
+    COGL_LIST_INSERT_HEAD (list, pipeline_snippet, list_node);
+  else
+    {
+      CoglPipelineSnippet *tail;
+
+      for (tail = COGL_LIST_FIRST (list);
+           COGL_LIST_NEXT (tail, list_node);
+           tail = COGL_LIST_NEXT (tail, list_node));
+
+      COGL_LIST_INSERT_AFTER (tail, pipeline_snippet, list_node);
+    }
+}
+
+static void
+_cogl_pipeline_add_vertex_snippet (CoglPipeline *pipeline,
+                                   CoglPipelineSnippetHook hook,
+                                   CoglSnippet *snippet)
+{
+  CoglPipelineState state = COGL_PIPELINE_STATE_VERTEX_SNIPPETS;
+
+  g_return_if_fail (cogl_is_pipeline (pipeline));
+  g_return_if_fail (cogl_is_snippet (snippet));
+
+  /* - Flush journal primitives referencing the current state.
+   * - Make sure the pipeline has no dependants so it may be modified.
+   * - If the pipeline isn't currently an authority for the state being
+   *   changed, then initialize that state from the current authority.
+   */
+  _cogl_pipeline_pre_change_notify (pipeline, state, NULL, FALSE);
+
+  _cogl_pipeline_snippet_list_add (&pipeline->big_state->vertex_snippets,
+                                   hook,
+                                   snippet);
+}
+
+void
+cogl_pipeline_add_vertex_hook (CoglPipeline *pipeline,
+                               CoglSnippet *snippet)
+{
+  _cogl_pipeline_add_vertex_snippet (pipeline,
+                                     COGL_PIPELINE_SNIPPET_HOOK_VERTEX,
+                                     snippet);
+}
+
+static void
+_cogl_pipeline_add_fragment_snippet (CoglPipeline *pipeline,
+                                     CoglPipelineSnippetHook hook,
+                                     CoglSnippet *snippet)
+{
+  CoglPipelineState state = COGL_PIPELINE_STATE_FRAGMENT_SNIPPETS;
+
+  g_return_if_fail (cogl_is_pipeline (pipeline));
+  g_return_if_fail (cogl_is_snippet (snippet));
+
+  /* - Flush journal primitives referencing the current state.
+   * - Make sure the pipeline has no dependants so it may be modified.
+   * - If the pipeline isn't currently an authority for the state being
+   *   changed, then initialize that state from the current authority.
+   */
+  _cogl_pipeline_pre_change_notify (pipeline, state, NULL, FALSE);
+
+  _cogl_pipeline_snippet_list_add (&pipeline->big_state->fragment_snippets,
+                                   hook,
+                                   snippet);
+}
+
+void
+cogl_pipeline_add_fragment_hook (CoglPipeline *pipeline,
+                                 CoglSnippet *snippet)
+{
+  _cogl_pipeline_add_fragment_snippet (pipeline,
+                                       COGL_PIPELINE_SNIPPET_HOOK_FRAGMENT,
+                                       snippet);
+}
+
+gboolean
+_cogl_pipeline_has_vertex_snippets (CoglPipeline *pipeline)
+{
+  CoglPipeline *authority =
+    _cogl_pipeline_get_authority (pipeline,
+                                  COGL_PIPELINE_STATE_VERTEX_SNIPPETS);
+
+  return !COGL_LIST_EMPTY (&authority->big_state->vertex_snippets);
+}
+
+gboolean
+_cogl_pipeline_has_fragment_snippets (CoglPipeline *pipeline)
+{
+  CoglPipeline *authority =
+    _cogl_pipeline_get_authority (pipeline,
+                                  COGL_PIPELINE_STATE_FRAGMENT_SNIPPETS);
+
+  return !COGL_LIST_EMPTY (&authority->big_state->fragment_snippets);
+}
+
 void
 _cogl_pipeline_hash_color_state (CoglPipeline *authority,
                                  CoglPipelineHashState *state)
@@ -1828,3 +1972,38 @@ _cogl_pipeline_compare_uniform_differences (unsigned long *differences,
         }
     }
 }
+
+static void
+_cogl_pipeline_snippet_list_hash (CoglPipelineSnippetList *list,
+                                  CoglPipelineHashState *state)
+{
+  CoglPipelineSnippet *l;
+
+  COGL_LIST_FOREACH (l, list, list_node)
+    {
+      state->hash =
+        _cogl_util_one_at_a_time_hash (state->hash,
+                                       &l->hook,
+                                       sizeof (CoglPipelineSnippetHook));
+      state->hash =
+        _cogl_util_one_at_a_time_hash (state->hash,
+                                       &l->snippet,
+                                       sizeof (CoglSnippet *));
+    }
+}
+
+void
+_cogl_pipeline_hash_vertex_snippets_state (CoglPipeline *authority,
+                                           CoglPipelineHashState *state)
+{
+  _cogl_pipeline_snippet_list_hash (&authority->big_state->vertex_snippets,
+                                    state);
+}
+
+void
+_cogl_pipeline_hash_fragment_snippets_state (CoglPipeline *authority,
+                                             CoglPipelineHashState *state)
+{
+  _cogl_pipeline_snippet_list_hash (&authority->big_state->fragment_snippets,
+                                    state);
+}
diff --git a/cogl/cogl-pipeline-state.h b/cogl/cogl-pipeline-state.h
index b69966c..d2c0ada 100644
--- a/cogl/cogl-pipeline-state.h
+++ b/cogl/cogl-pipeline-state.h
@@ -937,6 +937,64 @@ cogl_pipeline_set_uniform_matrix (CoglPipeline *pipeline,
                                   gboolean transpose,
                                   const float *value);
 
+/**
+ * cogl_pipeline_add_vertex_hook:
+ * @pipeline: A #CoglPipeline
+ * @snippet: The #CoglSnippet to add to the vertex processing hook
+ *
+ * Adds a shader snippet that will hook on to the vertex processing
+ * stage of @pipeline. This gives a chance for the application to
+ * modify the vertex attributes generated by the shader. Typically the
+ * snippet will modify cogl_color_out or cogl_position_out builtins.
+ *
+ * The âdeclarationsâ string in @snippet will be inserted in the
+ * global scope of the shader. Use this to declare any uniforms,
+ * attributes or functions that the snippet requires.
+ *
+ * The âpreâ string in @snippet will be inserted at the top of the
+ * main() function before any vertex processing is done.
+ *
+ * The âpostâ string in @snippet will be inserted after all of the
+ * standard vertex processing is done. This can be used to modify the
+ * outputs.
+ *
+ * Since: 1.10
+ * Stability: Unstable
+ */
+void
+cogl_pipeline_add_vertex_hook (CoglPipeline *pipeline,
+                               CoglSnippet *snippet);
+
+/**
+ * cogl_pipeline_add_fragment_hook:
+ * @pipeline: A #CoglPipeline
+ * @snippet: The #CoglSnippet to add to the fragment processing hook
+ *
+ * Adds a shader snippet that will hook on to the fragment processing
+ * stage of @pipeline. This gives a chance for the application to
+ * modify the fragment color generated by the shader. Typically the
+ * snippet will modify cogl_color_out.
+ *
+ * The âdeclarationsâ string in @snippet will be inserted in the
+ * global scope of the shader. Use this to declare any uniforms,
+ * attributes or functions that the snippet requires.
+ *
+ * The âpreâ string in @snippet will be inserted at the top of the
+ * main() function before any fragment processing is done.
+ *
+ * The âpostâ string in @snippet will be inserted after all of the
+ * standard fragment processing is done. At this point the generated
+ * value for the rest of the pipeline state will already be in
+ * cogl_color_out so the application can modify the result by altering
+ * this variable.
+ *
+ * Since: 1.10
+ * Stability: Unstable
+ */
+void
+cogl_pipeline_add_fragment_hook (CoglPipeline *pipeline,
+                                 CoglSnippet *snippet);
+
 #endif /* COGL_ENABLE_EXPERIMENTAL_API */
 
 G_END_DECLS
diff --git a/cogl/cogl-pipeline-vertend-fixed.c b/cogl/cogl-pipeline-vertend-fixed.c
index 5850677..3b19016 100644
--- a/cogl/cogl-pipeline-vertend-fixed.c
+++ b/cogl/cogl-pipeline-vertend-fixed.c
@@ -31,6 +31,7 @@
 
 #include "cogl-context-private.h"
 #include "cogl-pipeline-private.h"
+#include "cogl-pipeline-state-private.h"
 #include "cogl-pipeline-opengl-private.h"
 
 #ifdef COGL_PIPELINE_VERTEND_FIXED
@@ -59,6 +60,10 @@ _cogl_pipeline_vertend_fixed_start (CoglPipeline *pipeline,
   if (ctx->driver == COGL_DRIVER_GLES2)
     return FALSE;
 
+  /* Vertex snippets are only supported in the GLSL fragend */
+  if (_cogl_pipeline_has_vertex_snippets (pipeline))
+    return FALSE;
+
   /* If there is a user program with a vertex shader then the
      appropriate backend for that language should handle it. We can
      still use the fixed vertex backend if the program only contains
diff --git a/cogl/cogl-pipeline-vertend-glsl.c b/cogl/cogl-pipeline-vertend-glsl.c
index dcd6ef5..a6cadee 100644
--- a/cogl/cogl-pipeline-vertend-glsl.c
+++ b/cogl/cogl-pipeline-vertend-glsl.c
@@ -128,6 +128,16 @@ _cogl_pipeline_vertend_glsl_get_shader (CoglPipeline *pipeline)
     return 0;
 }
 
+static CoglPipelineSnippetList *
+get_vertex_snippets (CoglPipeline *pipeline)
+{
+  pipeline =
+    _cogl_pipeline_get_authority (pipeline,
+                                  COGL_PIPELINE_STATE_VERTEX_SNIPPETS);
+
+  return &pipeline->big_state->vertex_snippets;
+}
+
 static gboolean
 _cogl_pipeline_vertend_glsl_start (CoglPipeline *pipeline,
                                    int n_layers,
@@ -136,6 +146,7 @@ _cogl_pipeline_vertend_glsl_start (CoglPipeline *pipeline,
 {
   CoglPipelineShaderState *shader_state;
   CoglPipeline *template_pipeline = NULL;
+  CoglPipelineSnippet *snippet;
   CoglProgram *user_program;
 
   _COGL_GET_CONTEXT (ctx, FALSE);
@@ -254,6 +265,37 @@ _cogl_pipeline_vertend_glsl_start (CoglPipeline *pipeline,
                    "main ()\n"
                    "{\n");
 
+  COGL_LIST_FOREACH (snippet, get_vertex_snippets (pipeline), list_node)
+    {
+      const char *declarations =
+        cogl_snippet_get_declarations (snippet->snippet);
+
+      /* Add all of the declarations for vertex snippets */
+      if (declarations)
+        {
+          g_string_append (shader_state->header, declarations);
+          g_string_append_c (shader_state->header, '\n');
+        }
+
+      /* Add all of the pre-hooks for vertex processing */
+      if (snippet->hook == COGL_PIPELINE_SNIPPET_HOOK_VERTEX)
+        {
+          const char *pre =
+            cogl_snippet_get_pre (snippet->snippet);
+
+          if (pre)
+            {
+              g_string_append (shader_state->source, "  {\n");
+              g_string_append (shader_state->source, pre);
+              g_string_append (shader_state->source, "  }\n");
+            }
+        }
+    }
+
+  /* Enclose the generated vertex processing in a block so that any
+     variables declared in it won't be in the scope of the snippets */
+  g_string_append (shader_state->source, "  {\n");
+
   if (ctx->driver == COGL_DRIVER_GLES2)
     /* There is no builtin uniform for the pointsize on GLES2 so we need
        to copy it from the custom uniform in the vertex shader */
@@ -350,6 +392,7 @@ _cogl_pipeline_vertend_glsl_end (CoglPipeline *pipeline,
       GLint lengths[2];
       GLint compile_status;
       GLuint shader;
+      CoglPipelineSnippet *snippet;
 
       COGL_STATIC_COUNTER (vertend_glsl_compile_counter,
                            "glsl vertex compile counter",
@@ -363,7 +406,24 @@ _cogl_pipeline_vertend_glsl_end (CoglPipeline *pipeline,
                        "cogl_modelview_projection_matrix * "
                        "cogl_position_in;\n"
                        "  cogl_color_out = cogl_color_in;\n"
-                       "}\n");
+                       "  }\n");
+
+      /* Add all of the post-hooks for vertex processing */
+      COGL_LIST_FOREACH (snippet, get_vertex_snippets (pipeline), list_node)
+        if (snippet->hook == COGL_PIPELINE_SNIPPET_HOOK_VERTEX)
+          {
+            const char *post =
+              cogl_snippet_get_post (snippet->snippet);
+
+            if (post)
+              {
+                g_string_append (shader_state->source, "  {\n");
+                g_string_append (shader_state->source, post);
+                g_string_append (shader_state->source, "  }\n");
+              }
+          }
+
+      g_string_append (shader_state->source, "}\n");
 
       GE_RET( shader, ctx, glCreateShader (GL_VERTEX_SHADER) );
 
diff --git a/cogl/cogl-pipeline.c b/cogl/cogl-pipeline.c
index 6b84b3f..e47a5de 100644
--- a/cogl/cogl-pipeline.c
+++ b/cogl/cogl-pipeline.c
@@ -445,6 +445,22 @@ destroy_weak_children_cb (CoglNode *node,
 }
 
 static void
+_cogl_pipeline_snippet_free (CoglPipelineSnippet *pipeline_snippet)
+{
+  cogl_object_unref (pipeline_snippet->snippet);
+  g_slice_free (CoglPipelineSnippet, pipeline_snippet);
+}
+
+static void
+_cogl_pipeline_snippet_list_free (CoglPipelineSnippetList *list)
+{
+  CoglPipelineSnippet *pipeline_snippet, *tmp;
+
+  COGL_LIST_FOREACH_SAFE (pipeline_snippet, list, list_node, tmp)
+    _cogl_pipeline_snippet_free (pipeline_snippet);
+}
+
+static void
 _cogl_pipeline_free (CoglPipeline *pipeline)
 {
   if (!pipeline->is_weak)
@@ -488,6 +504,12 @@ _cogl_pipeline_free (CoglPipeline *pipeline)
       g_list_free (pipeline->layer_differences);
     }
 
+  if (pipeline->differences & COGL_PIPELINE_STATE_VERTEX_SNIPPETS)
+    _cogl_pipeline_snippet_list_free (&pipeline->big_state->vertex_snippets);
+
+  if (pipeline->differences & COGL_PIPELINE_STATE_FRAGMENT_SNIPPETS)
+    _cogl_pipeline_snippet_list_free (&pipeline->big_state->fragment_snippets);
+
   g_list_free (pipeline->deprecated_get_layers_list);
 
   recursively_free_layer_caches (pipeline);
@@ -831,6 +853,30 @@ _cogl_pipeline_set_vertend (CoglPipeline *pipeline, int vertend)
 }
 
 static void
+_cogl_pipeline_snippet_list_copy (CoglPipelineSnippetList *dst,
+                                  const CoglPipelineSnippetList *src)
+{
+  CoglPipelineSnippet *tail = NULL;
+  const CoglPipelineSnippet *l;
+
+  COGL_LIST_INIT (dst);
+
+  COGL_LIST_FOREACH (l, src, list_node)
+    {
+      CoglPipelineSnippet *copy = g_slice_dup (CoglPipelineSnippet, l);
+
+      cogl_object_ref (copy->snippet);
+
+      if (tail)
+        COGL_LIST_INSERT_AFTER (tail, copy, list_node);
+      else
+        COGL_LIST_INSERT_HEAD (dst, copy, list_node);
+
+      tail = copy;
+    }
+}
+
+static void
 _cogl_pipeline_copy_differences (CoglPipeline *dest,
                                  CoglPipeline *src,
                                  unsigned long differences)
@@ -973,6 +1019,14 @@ _cogl_pipeline_copy_differences (CoglPipeline *dest,
       _cogl_bitmask_init (&big_state->uniforms_state.changed_mask);
     }
 
+  if (differences & COGL_PIPELINE_STATE_VERTEX_SNIPPETS)
+    _cogl_pipeline_snippet_list_copy (&big_state->vertex_snippets,
+                                      &src->big_state->vertex_snippets);
+
+  if (differences & COGL_PIPELINE_STATE_FRAGMENT_SNIPPETS)
+    _cogl_pipeline_snippet_list_copy (&big_state->fragment_snippets,
+                                      &src->big_state->fragment_snippets);
+
   /* XXX: we shouldn't bother doing this in most cases since
    * _copy_differences is typically used to initialize pipeline state
    * by copying it from the current authority, so it's not actually
@@ -1066,6 +1120,16 @@ _cogl_pipeline_init_multi_property_sparse_state (CoglPipeline *pipeline,
         uniforms_state->override_values = NULL;
         break;
       }
+    case COGL_PIPELINE_STATE_VERTEX_SNIPPETS:
+      _cogl_pipeline_snippet_list_copy (&pipeline->big_state->vertex_snippets,
+                                        &authority->big_state->vertex_snippets);
+      break;
+
+    case COGL_PIPELINE_STATE_FRAGMENT_SNIPPETS:
+      _cogl_pipeline_snippet_list_copy (&pipeline->big_state->fragment_snippets,
+                                        &authority->big_state->
+                                        fragment_snippets);
+      break;
     }
 }
 
@@ -2260,6 +2324,18 @@ _cogl_pipeline_equal (CoglPipeline *pipeline0,
                               _cogl_pipeline_uniforms_state_equal))
     goto done;
 
+  if (!simple_property_equal (authorities0, authorities1,
+                              pipelines_difference,
+                              COGL_PIPELINE_STATE_VERTEX_SNIPPETS_INDEX,
+                              _cogl_pipeline_vertex_snippets_state_equal))
+    goto done;
+
+  if (!simple_property_equal (authorities0, authorities1,
+                              pipelines_difference,
+                              COGL_PIPELINE_STATE_FRAGMENT_SNIPPETS_INDEX,
+                              _cogl_pipeline_fragment_snippets_state_equal))
+    goto done;
+
   if (pipelines_difference & COGL_PIPELINE_STATE_LAYERS)
     {
       CoglPipelineStateIndex state_index = COGL_PIPELINE_STATE_LAYERS_INDEX;
@@ -2669,9 +2745,13 @@ _cogl_pipeline_init_state_hash_functions (void)
     _cogl_pipeline_hash_logic_ops_state;
   state_hash_functions[COGL_PIPELINE_STATE_UNIFORMS_INDEX] =
     _cogl_pipeline_hash_uniforms_state;
+  state_hash_functions[COGL_PIPELINE_STATE_VERTEX_SNIPPETS_INDEX] =
+    _cogl_pipeline_hash_vertex_snippets_state;
+  state_hash_functions[COGL_PIPELINE_STATE_FRAGMENT_SNIPPETS_INDEX] =
+    _cogl_pipeline_hash_fragment_snippets_state;
 
   /* So we get a big error if we forget to update this code! */
-  g_assert (COGL_PIPELINE_STATE_SPARSE_COUNT == 14);
+  g_assert (COGL_PIPELINE_STATE_SPARSE_COUNT == 16);
 }
 
 unsigned int
@@ -2860,7 +2940,8 @@ CoglPipelineState
 _cogl_pipeline_get_state_for_fragment_codegen (CoglContext *context)
 {
   CoglPipelineState state = (COGL_PIPELINE_STATE_LAYERS |
-                             COGL_PIPELINE_STATE_USER_SHADER);
+                             COGL_PIPELINE_STATE_USER_SHADER |
+                             COGL_PIPELINE_STATE_FRAGMENT_SNIPPETS);
 
   if (context->driver == COGL_DRIVER_GLES2)
     state |= COGL_PIPELINE_STATE_ALPHA_FUNC;
diff --git a/cogl/cogl-pipeline.h b/cogl/cogl-pipeline.h
index 11b71a0..b66f18d 100644
--- a/cogl/cogl-pipeline.h
+++ b/cogl/cogl-pipeline.h
@@ -29,6 +29,7 @@
 #define __COGL_PIPELINE_H__
 
 #include <cogl/cogl-types.h>
+#include <cogl/cogl-snippet.h>
 
 G_BEGIN_DECLS
 



[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]