cog /käg/ Noun: A wheel or bar with a series of projections on its edge that transfers motion by engaging with projections on another wheel or bar

 

Here is an introductory example of using the Cogl 2.0 api.

After the example there are detailed explanations of each section.

#include <cogl/cogl.h>
#include <glib.h>
#include <stdio.h>

typedef struct _Data
{
    CoglContext *ctx;
    CoglFramebuffer *fb;
    CoglPrimitive *triangle;
    CoglPipeline *pipeline;

    unsigned int redraw_idle;
    CoglBool frame_sync_ready;
    CoglBool is_dirty;
} Data;

static CoglBool
paint_cb (void *user_data)
{
    Data *data = user_data;

    data->redraw_idle = 0;
    data->is_dirty = FALSE;
    data->frame_sync_ready = FALSE;

    cogl_framebuffer_clear4f (data->fb, COGL_BUFFER_BIT_COLOR, 0, 0, 0, 1);
    cogl_primitive_draw (data->triangle, data->fb, data->pipeline);
    cogl_onscreen_swap_buffers (data->fb);

    return FALSE; /* un-register idle callback */
}

static void
maybe_queue_redraw (Data *data)
{
    if (data->is_dirty && data->frame_sync_ready && data->redraw_idle == 0) {
        /* We'll draw on idle instead of drawing immediately so that
         * if Cogl reports multiple dirty rectangles we won't
         * redundantly draw multiple frames */
        data->redraw_idle = g_idle_add (paint_cb, data);
    }
}

static void
frame_event_cb (CoglOnscreen *onscreen,
                CoglFrameEvent event,
                CoglFrameInfo *info,
                void *user_data)
{
    Data *data = user_data;

    if (event == COGL_FRAME_EVENT_SYNC) {
        data->frame_sync_ready = TRUE;
        maybe_queue_redraw (data);
    }
}

static void
dirty_cb (CoglOnscreen *onscreen,
          const CoglOnscreenDirtyInfo *info,
          void *user_data)
{
    Data *data = user_data;

    data->is_dirty = TRUE;
    maybe_queue_redraw (data);
}

int
main (int argc, char **argv)
{
    Data data;
    CoglError *error = NULL;
    CoglVertexP2C4 triangle_vertices[] = {
        {0, 0.7, 0xff, 0x00, 0x00, 0x80},
        {-0.7, -0.7, 0x00, 0xff, 0x00, 0xff},
        {0.7, -0.7, 0x00, 0x00, 0xff, 0xff}
    };
    GSource *cogl_source;
    GMainLoop *loop;

    data.redraw_idle = 0;
    data.is_dirty = FALSE;
    data.frame_sync_ready = TRUE;

    data.ctx = cogl_context_new (NULL, &error);
    if (!data.ctx) {
        fprintf (stderr, "Failed to create context: %s\n", error->message);
        return 1;
    }

    data.fb = cogl_onscreen_new (data.ctx, 640, 480);
    cogl_onscreen_show (data.fb);

    data.triangle = cogl_primitive_new_p2c4 (data.ctx,
                                             COGL_VERTICES_MODE_TRIANGLES,
                                             3, triangle_vertices);
    data.pipeline = cogl_pipeline_new (data.ctx);

    cogl_source = cogl_glib_source_new (data.ctx, G_PRIORITY_DEFAULT);

    g_source_attach (cogl_source, NULL);

    cogl_onscreen_add_frame_callback (data.fb,
                                      frame_event_cb,
                                      &data,
                                      NULL /* destroy callback */);

    cogl_onscreen_add_dirty_callback (data.fb,
                                      dirty_cb,
                                      &data,
                                      NULL /* destroy callback */);

    g_idle_add (paint_cb, &data);

    loop = g_main_loop_new (NULL, TRUE);
    g_main_loop_run (loop);

    return 0;
}

Building the example

If you save the above code to a file named hello.c then you can compile the example as follows:

gcc -o hello hello.c `pkg-config --cflags --libs cogl2 glib-2.0`

Step by Step...

Including the right headers

First include the experimental Cogl 2.0 API via <cogl/cogl.h>

Note: The Cogl 2.0 api is still a work in progress so be aware that there is a chance that this example is slightly out of date.

#include <cogl/cogl.h>

Since Cogl needs to be integrated with a mainloop this example uses the Glib mainloop because it's portable.

Note: Glib's mainloop is not a required dependency for Cogl, and if you want to integrate with an alternative mainloop library then Cogl makes that possible.

#include <glib.h>

We will construct various state in the main() function which we also need to share with the paint() callback so we declare a structure to hold that shared state...

typedef struct _Data
{
    CoglContext *ctx;
    CoglFramebuffer *fb;
    CoglPrimitive *triangle;
    CoglPipeline *pipeline;

    unsigned int redraw_idle;
    CoglBool frame_sync_ready;
    CoglBool is_dirty;
} Data;

Creating a context

A CoglContext is an application sandbox for interacting with a graphics renderer (typically a GPU) and an application should normally only create a single Context.

Although Cogl does offer a lot of control over the details of how a Context is initialized, in this example we are going to gloss over that capability by passing a NULL display pointer to cogl_context_new() so that Cogl can instead just choose sensible defaults. This is what most simple applications should do.

Strictly speaking passing the error argument to cogl_context_new() is optional but this is a good opportunity to show how Cogl exposes runtime exceptions to the user.

If a runtime recoverable error occurs (such as an IO error when trying to communicate with the platform specific graphics stack) then cogl_context_new() will return NULL and also pass back an exception pointer through the error argument.

If an exception is returned then it contains a domain value, an error code and a human readable error message that can be shown to a user.

In this example we are just aborting the program so really this error handling example is redundant because the default behaviour of Cogl is to automatically print the error message and abort if the error argument is NULL.

Note: for those interested in more details about controlling the context initialization they should look into the CoglRenderer, CoglDisplay and CoglOnscreenTemplate apis.

    data.ctx = cogl_context_new (NULL, &error);
    if (!data.ctx) {
        fprintf (stderr, "Failed to create context: %s\n", error->message);
        return 1;
    }

Creating an onscreen framebuffer

Once we have a context we are able to create an onscreen framebuffer with a requested size.

Note: the dimensions are only a request since on some platforms an onscreen framebuffer will be forced to be fullscreen.

    data.fb = cogl_onscreen_new (data.ctx, 640, 480);

When we are ready we can make the onscreen framebuffer visible to the user like so...

    cogl_onscreen_show (data.fb);

At this point it's worth mentioning that Cogl's type system is more "Interface Oriented" than it is strictly "Object Oriented".

All this means is that the programming style puts more emphasis on what interfaces an object implements than it does on defining a hierarchy of type inheritance.

One of the interfaces Cogl exposes is the "CoglFramebuffer" interface. This interface exposes any functionality that is common between onscreen and offscreen framebuffers that can be rendered too.

Creating a primitive

Next we are going to create a primitive, representing geometry that we can draw.

A primitive represents a collection of vertex attributes where a vertex attribute might for example be a position, color or texture coordinate. So if you imagine a model made of points in space (vertices) which we join up with lots of triangles then each one of those vertices can have a position, a color and texture coordinate.

Cogl offers lots of control over the details of how primitives are memory managed; over what attributes should be associated with a primitive and what data types should be used for each attribute but Cogl also provides some convenience functions for the most common choices made.

Cogl provides a number of convenient vertex structures representing the most common vertex layouts with commonly used attribute types. In this example we want each vertex of our triangle to have x and y position coordinates as well as 4 unsigned byte color components. Although at first sight the "P2C4" suffix may seem awkward, the convenience this offers is really worthwhile. The 'P2" refers to the 2 position coordinates and the C4 refers to the 4 color coordinates.

The data we declare here represents 3 vertices of a triangle and you can see each vertex has a position followed by an rgba color.

    CoglVertexP2C4 triangle_vertices[] = {
        {0, 0.7, 0xff, 0x00, 0x00, 0x80},
        {-0.7, -0.7, 0x00, 0xff, 0x00, 0xff},
        {0.7, -0.7, 0x00, 0x00, 0xff, 0xff}
    };

Once we have the data for a primitive we upload the data to the GPU and specify a topology for the data.

GPUs can handle a number of standard topologies which we won't cover in full here, but the COGL_VERTICES_MODE_TRIANGLES topology implies that each sequential set of 3 vertices defines a single distinct triangle for the GPU.

    data.triangle = cogl_primitive_new_p2c4 (data.ctx,
                                             COGL_VERTICES_MODE_TRIANGLES,
                                             3, triangle_vertices);

Describing a pipeline

Once we have a primitive that represents the geometry we want to draw the next thing we need is a pipeline that defines how to draw our geometry.

GPUs process data in a long pipeline which might start with some vertex processing (take a model and translate, rotate and squash it), followed by pixel processing (for each triangle of a model apply a color gradient and combine with a texture) and end on blending (take a colored and textured triangle and blend it onto a framebuffer to become part of a bigger picture).

Cogl provides an api to control how that GPU processing pipeline works and encapsulate a complete pipeline description into a single object.

In this example we are actually just going to use a default pipeline so its enough to allocate a pipeline object and use that.

    data.pipeline = cogl_pipeline_new (data.ctx);

Mainloop Integration

Cogl requires that it is integrated with a system mainloop of some kind because the api is designed to allow certain work to be handled asynchronously without blocking an application but when that work completes we can notify the application via a callback that they registered.

The GLib api provides a portable mainloop that we are using in this example, but Cogl also provides alternative apis to allow integration with a different mainloop if required.

    cogl_source = cogl_glib_source_new (data.ctx, G_PRIORITY_DEFAULT);

    g_source_attach (cogl_source, NULL);

What does a mainloop provide?

A mainloop is a very common way of driving event based applications by providing a centralized event dispatch point that is able to manage event sources, such as socket file descriptors (that might provide events from another process) or device file descriptors (that can provide events from the system).

A mainloop is typically managed by adding event sources to the loop and once an application is initialized it will run the mainloop by calling into a function that won't return until the mainloop stops running. Once a mainloop is running then the application is driven by the events triggered by registered event sources which will typically result in calling application callback functions that were previously associated with the event sources.

Since graphics hardware runs asynchronously with respect to an application running on the CPU it's important that Cogl has an efficient way to deliver events about the state of the hardware to the application without it having to repeatedly, manually, probe the state of the hardware. This is why Cogl is designed to be integrated with a mainloop, so we can take advantage of platform specific event delivery mechanisms that enable an application to go to sleep while it is idle waiting for events without wasting power.

Registering for frame synchronization events

An important example of events in Cogl is the ability to register a callback for synchronizing the painting of an application.

This synchronization mechanism will make sure your applications doesn't waste resources rendering faster than can be displayed. When your application is running under a system compositor then Cogl will make sure rendering is synchronized with the painting of the compositor, otherwise Cogl will make sure rendering is synchronized to the refresh rate of your display.

This mechanism also helps to ensure that your application doesn't end up trying to render new frames when the GPU is not ready to accept more frame which would otherwise lead to the application being forcibly blocked until the GPU is ready. By helping your application avoid these forced blocks that means your application gets more CPU time overall for processing other mainloop events which can have a big impact on interactivity.

This code asks to be notified about "frame events" that let us track the life-cycle of every frame.

    cogl_onscreen_add_frame_callback (data.fb,
                                      frame_event_cb,
                                      &data,
                                      NULL /* destroy callback */);

This code asks to be notified whenever the contents of an onscreen framebuffer has been invalidated by some window system event that we aren't in control of.

    cogl_onscreen_add_dirty_callback (data.fb,
                                      dirty_cb,
                                      &data,
                                      NULL /* destroy callback */);

Running the mainloop

We are almost ready to start running the mainloop, but to bootstrap rendering we need to register an idle callback function with the mainloop that will be immediately dispatched once we start it running.

    g_idle_add (paint_cb, &data);

Now we are ready; we enter the mainloop as the last thing we do in the main() function. In this example we never exit the mainloop but a more complete application might exit the function in response to a user request to quit the application.

    loop = g_main_loop_new (NULL, TRUE);
    g_main_loop_run (loop);

Drawing our triangle

Once the mainloop starts running our paint_cb callback function will be called and is the place for us to render a new frame. The user_data argument is the Data pointer we passed to g_idle_add earlier so we can access the Cogl framebuffer and context we created.

static CoglBool
paint_cb (void *user_data)
{
    Data *data = user_data;

The first thing we do is reset some state that we use for knowing when to redraw (which is described in more detail below)

    data->redraw_idle = 0;
    data->is_dirty = FALSE;
    data->frame_sync_ready = FALSE;

The next thing we do clear the colour contents of the framebuffer we're drawing too.

Note: you can't assume that anything is preserved in your framebuffer between frames since a single CoglFramebuffer may internally be comprised of multiple buffers that are switched between so for each frame you render you may not be rendering to the same memory as the previous frame.

After clearing we submit our triangle primitive to the GPU along with the pipeline that should be used to process that primitive as it is rendered to the given framebuffer.

When we've finished drawing we explicitly ask for the new frame to be presented to the user by "swapping" the colour buffer from being a hidden back-buffer to being a visible front-buffer.

Remember, as we mentioned earlier, the GPU works asynchronously with respect to the application so when cogl_onscreen_swap_buffers() returns that doesn't necesarily mean the GPU has finished rendering the new frame or that the frame is literally visible yet. The frame-event-callback registered in the main() function can tell us about this if supported by the platform.

    cogl_framebuffer_clear4f (data->fb, COGL_BUFFER_BIT_COLOR, 0, 0, 0, 1);
    cogl_primitive_draw (data->triangle, data->fb, data->pipeline);
    cogl_onscreen_swap_buffers (data->fb);

Handling further redraws

Finally once we have finished rendering a frame we need to think about how we render our next frame.

Firstly the paint_cb function returns FALSE which notifies glib that the function shouldn't continue to be repeatedly called on idle.

    return FALSE; /* un-register idle callback */
}

From now on the rate of painting should be throttled by COGL_FRAME_EVENT_SYNC events that get delivered to the frame_event_cb function registered earlier. Since what this example draws doesn't change over time the only thing that causes us to need to re-draw after the first frame is if a dirty event is delievered to the registered dirty_cb function.

Now if we look at the implementation of dirty_cb we can see that whenever the window system notifies us that our framebuffer contents have been invalidated we keep track of this dirty state in data->is_dirty so we know we need to redraw asap. Next we try and queue a redraw by calling maybe_queue_redraw() which will make sure we have received a frame-sync event before actually preparing to draw a new frame. (If we didn't check for the frame-sync events then we could end up drawing too fast and waste system resources.)

static void
dirty_cb (CoglOnscreen *onscreen,
          const CoglOnscreenDirtyInfo *info,
          void *user_data)
{
    Data *data = user_data;

    data->is_dirty = TRUE;
    maybe_queue_redraw (data);
}

Now if we look at the implementation of frame_event_cb we identify whenever the current event is a COGL_FRAME_EVENT_SYNC and if so keep track of this state in data->frame_sync_ready. This event lets us know that once we need to redraw, then we are ready to do so. In case we already need to redraw something (due to a dirty event) we then call maybe_queue_redraw().

static void
frame_event_cb (CoglOnscreen *onscreen,
                CoglFrameEvent event,
                CoglFrameInfo *info,
                void *user_data)
{
    Data *data = user_data;

    if (event == COGL_FRAME_EVENT_SYNC) {
        data->frame_sync_ready = TRUE;
        maybe_queue_redraw (data);
    }
}

To prepare for drawing a new frame the maybe_queue_redraw() checks if we actually need to redraw anything (data->is_dirty) and checks if we are ready to redraw (data->frame_sync_ready) and if we haven't already done so then we queue paint_cb to be called by the mainloop the next time we are idle. We draw on idle instead of drawing immediately so that if Cogl reports multiple dirty events together then they can be coalesced into one redraw.

static void
maybe_queue_redraw (Data *data)
{
    if (data->is_dirty && data->frame_sync_ready && data->redraw_idle == 0) {
        /* We'll draw on idle instead of drawing immediately so that
         * if Cogl reports multiple dirty rectangles we won't
         * redundantly draw multiple frames */
        data->redraw_idle = g_idle_add (paint_cb, data);
    }
}

Done!

And there you are, a first example of using Cogl.

As you may imagine this example glosses over many interesting details, and so we would recommend also looking at the reference manual if you are interested in learning more, and feel free to ask questions on IRC and on the mailinglist.