Blog

Blog

Cogl Shader Snippets

Posted at 22:37 on 07 Dec 2011

Cogl has been getting some love lately to move towards the 2.0 API that will eventually replace the existing API. Now that Cogl has split out from Clutter and it can be used as a standalone library we are getting close to the goal of Cogl being a convenient replacement for OpenGL. However one thing that until recently has been severely lacking for Cogl to be a credible modern graphics library is good support for shaders. Cogl has had a sort of shader support for a long time but it has hardly been touched since the days when Cogl was just a very thin wrapper over the GL API. Cogl has evolved a lot since then so the previous shader support no longer fits with Cogl's design.

We've finally got around to improving this situation so there are now a few changes in git master of Cogl to make this better. In 2.0 land, instead of having to create a CoglProgram to replace the entire of either the vertex or fragment pipeline you can now create CoglSnippets. These can be inserted at specific hook points on a CoglPipeline to either wrap around or completely replace a part of the pipeline. For example, imagine we have a function in GLSL that can take an input colour and convert it to black-and-white. If we wanted to use this in Cogl we would previously have had to implement the entire fragment pipeline. Usually this would mean having to create a sampler uniform, update that uniform and writing the code to get a texel from the sampler. If we wanted to use this shader in another situation without texturing, for example when drawing a solid colour rectangle, we would have to write another shader that includes the function without the texturing.

With the snippets API we can now create an object that just contains the black-and-white conversion in a single line, like this:

snippet = cogl_snippet_new (COGL_SNIPPET_HOOK_FRAGMENT,
                            NULL,
                            "cogl_color_out.rgb = "
                            "vec3 (length (cogl_color_out.rgb) / 1.732);");

Then we can attach that snippet to any CoglPipeline and Cogl will fill in the rest of the code needed to make it work. It can be attached to multiple pipelines, for example one with texturing and one without.

The second feature that has landed recently is to be able to store uniform values on a CoglPipeline. The idea behind the design of Cogl is that you will store as much of the GL state as possible in a CoglPipeline object and try to switch between these cached states rather than redescribing the state to the graphics library every time something is drawn. That way Cogl can minimise the state changes by recognising the minimal set of differences between two pipelines. The previous approach for uniforms - which was the same as OpenGL where the uniform values are attached to the program object - doesn't match well to this design. If you wanted to use a program multiple times with different values then you would have to reset the values on the program for every primitive.

In Cogl master you can instead encapsulate these uniform values on the pipeline and Cogl can efficiently recognise which uniforms are different between two pipelines sharing the same program and avoid flushing uniforms that haven't changed. For example, imagine this snippet which just replaces the red component of the fragment colour based on the value of a uniform:

snippet = cogl_snippet_new (COGL_SNIPPET_HOOK_FRAGMENT,
                            "uniform float red_value;",
                            "cogl_color_out.r = red_value;");
pipeline = cogl_pipeline_new ();
cogl_pipeline_add_snippet (pipeline, snippet);
cogl_object_unref (snippet);

Now if we want to frequently paint using this snippet with two different values, we can create two pipelines like this:

pipeline1 = cogl_pipeline_copy (pipeline);
location = cogl_pipeline_get_uniform_location (pipeline, "red_value");
cogl_pipeline_set_uniform_1f (pipeline1, location, 0.5f);

pipeline2 = cogl_pipeline_copy (pipeline);
location = cogl_pipeline_get_uniform_location (pipeline, "red_value");
cogl_pipeline_set_uniform_1f (pipeline2, location, 0.8f);

Now we can draw with these two pipelines easily:

cogl_push_source (pipeline1);
cogl_rectangle (0, 0, 10, 10);
cogl_pop_source ();

cogl_push_source (pipeline2);
cogl_rectangle (10, 10, 20, 20);
cogl_pop_source ();

Cogl will quickly know that the two pipelines are using the same program because they are copied from the same parent so it will only flush the state for the new uniform value when switching between the two.

The third recent addition to Cogl related to shaders is support for custom attributes. The API has already been in place for this for a while from both CoglPrimitive and the now deprecated CoglVertexBuffer. However it was never actually connected together properly so no-one could use it. The custom attributes can be set by just naming them when a CoglAttribute is created. Here is a short example:

/* Create a pipeline with a snippet using a custom attribute. This
   will just replace the red component of the colour using a float */
snippet = cogl_snippet_new (COGL_SNIPPET_HOOK_VERTEX,
                            "attribute float redness;",
                            "cogl_color_out.r = redness;");
pipeline = cogl_pipeline_new ();
cogl_pipeline_add_snippet (pipeline, snippet);
cogl_object_unref (snippet);

/* Create a primitive with two attributes. One will be our custom
   attribute and the other will be the normal position attribute */
static const float verts[] = { 0, 0, 0.0,
                               100, 0, 0.5,
                               50, 100, 1.0 };
CoglAttributeBuffer *buffer =
 cogl_attribute_buffer_new (sizeof (verts), verts);
CoglAttribute *attributes[2];
attributes[0] = cogl_attribute_new (buffer,
                                    "cogl_position_in",
                                    /* Stride */
                                    sizeof (float) * 3,
                                    /* Offset */
                                    0,
                                    /* n_components */
                                    2,
                                    COGL_ATTRIBUTE_TYPE_FLOAT);
attributes[1] = cogl_attribute_new (buffer,
                                    "redness",
                                    /* Stride */
                                    sizeof (float) * 3,
                                    /* Offset */
                                    sizeof (float) * 2,
                                    /* n_components */
                                    1,
                                    COGL_ATTRIBUTE_TYPE_FLOAT);
primitive =
  cogl_primitive_new_with_attributes (COGL_VERTICES_MODE_TRIANGLES,
                                      3, /* n_vertices */
                                      attributes,
                                      2 /* n_attributes */);
cogl_object_unref (attributes[0]);
cogl_object_unref (attributes[1]);
cogl_object_unref (buffer);

/* Now we can draw with this primitive and pipeline */
cogl_push_source (pipeline);
cogl_primitive_draw (primitive);
cogl_pop_source ();

I've uploaded the current documentation to a temporary location if you want further details of the API. It's all still experimental so we'd very much welcome any testing and feedback. The Cogl developers can be contacted on the new mailing list or at #clutter on GIMPnet.