Using cairo with OpenGL

These are my humble beginnings of writing up a tutorial on using cairo with OpenGL. The general view-point of this document is that of a Linux-developer. Please feel free to add your examples and experience from other use-cases (preferably with sourcecode) gained on other platforms. This document assumes some familiarity with the APIs of cairo and OpenGL, the typical development tool-chain under Linux and some graphics-programming in general. Furthermore I want to point out, that I am not an native english speaker and cannot guarantee this text to be error-free.

What is OpenGL and how does it relate to Cairo?

OpenGL is a low-level abstraction for feeding rendering primitives to the GPU. The base elements are triangles and shaders.

Cairo is a high-level canvas drawing model for laying out pages and other views. The base elements are resolution independent paths and patterns. Cairo can translate that canvas model into GPU primitives using OpenGL (among others), but the canvas model does not often translate efficiently to the GPU.

Cairo is a much higher abstraction which is both an advantage and disadvantage:

For the application programmer, a canvas api (e.g. cairo) is much simpler to use. You do not need to worry about GPU limitations and are device and display independent, using a layout language much closer to the graphic designers.

Conversely, the abstraction of the canvas api divorces the application programmer from considering the limitations of the GPU and how poor they are at rendering the canvas model. In order to gain the most performance, you need to construct your UI fully cognisant of the GPU and program within its capabilities. That is highly device and driver specific.

cairo - easy to use, portable to any device, scales to any device opengl - very hard to use (have to write your own toolkit), device dependent. May be faster.

To use OpenGL without Cairo, you essentially have to create your own UI toolkit, which if you desire the general qualities Cairo offers, ends up looking very much like Cairo, with similar advantages and disadvantages over raw OpenGL. Where it is easiest to use OpenGL (for example blending multiple layers), it is also easy for Cairo to efficiently use OpenGL.

Typical benefits

So why would you want to mix vector-graphics rendering, provided by cairo, with OpenGL in the first place? Well, there are a few obvious advantages that come to mind...

General approach

Which steps should one usually take in order to get cairo-drawn vector-graphics into an OpenGL-rendered scene...

Our version of "Hello, world!"

This example program tries to be as simple as possible, without being too boring. It is written in plain ANSI C and uses cairo, OpenGL and SDL. Thus it should "translate" to other platforms or languages without much effort. Download the sources with this command...

bzr branch lp:gl-cairo-simple

... or just head over to the project page here

If you have all needed libraries and header-files in place a simple make will compile the program. The result will look something like this...

gl-cairo-simple screenshot

The above image is actually a link to an ogg/theora video, which is about 734 KBytes large. You can see that the graphics adapt to the window-size (due to the screen-capture a bit jerky in response) and during the demonstration the line-thickness is changed with the scroll-wheel of the mouse. The frame-dumps shown after each run are triggered with the d-key.

To avoid blowing up this tutorial, I did not copy&paste the whole sourcecode into this document. The sourcecode is thoroughly commented to help you understand what goes on. Should you have questions, suggestions or patches, feel free to send them to me (macslow@bangang.de).

Beef it up a little

Here is an example-program, that shows off a bit more than the previous "Hello, world!"-variant. It creates three cairo-surfaces (and contexts) and a corresponding OpenGL-texture-object for each of them. In the animation-loop each cairo-surface is refreshed with different contents, that is then copied to an OpenGL-texture and finally mapped on one side of a cube. It addition to cairo and OpenGL it makes use of GTK+ and GtkGlExt for the typical windowing-system boilerplate-code. You can grab the up-to-date version of this with...

bzr branch lp:gl-cairo-cube

... or just head over to the project page here

Once successfully compiled it will look like this...

gl-cairo-cube screenshot

The above image is also a link to an ogg/theora video, which is about 2.9 MBytes in size, so you can see if it's worth trying out getting cairo and OpenGL do their stuff together. You can quit the program with the q- or esc-key, change the overall transparency with the mouse-wheel, rotate the object with LMB-drag and zoom it with RMB-drag. To move the object around on the desktop you'll have to setup your window-manager to trigger window-movement on Alt-LMB-drag. Again, if you have questions, suggestions or patches regarding this program, feel free to send them to me (macslow@bangang.de).

Something with a bit more glitz

This used to be an example using the glitz-backend of cairo. But since glitz has been deprecated and removed from cairo I changed this example to ignore glitz completely. The new cairo-gl backend, which is still experimental, sort of takes over at this point. But I've not been able to update the example (cairo-gimmicks) to use this new backend. Right now only the image-backend is used and should therefore run nearly on every system. To throttle the framerate you can pass -r and the program will try to match that frame-rate. The default is to try to render with 30 Hz. Be sure to read the supplied README. You can grab the up-to-date version of this with...

bzr branch lp:cairo-gimmicks

... or just head over to the project page here

Once successfully compiled it will look like this (depending wether you have a composited environment or not)...

twirling farsi screenshot

composited twirling farsi screenshot

The above images are links to ogg/theora videos, which are 1.6 MBytes and 1.3 MBytes in size. You can quit the program with the q- or esc-key, change the demos with the F1..F4 keys, or mouse-wheel or cursor keys, or PageUp/PageDown keys. Space toggles between pause and animation. If you have questions, suggestions or patches regarding this program, feel free to send them to me (macslow@bangang.de).

Use cairo for an anti-aliasing trick

Here a clever method is demonstrated to get edge-anti-aliasing for textured single quads or rectangles. The computational costs are negligible compared to usual multi- or super-sample based anti-aliasing. The demonstrated technique needs OpenGL-hardware, which is capable of texture-mapping, multi-texturing and executing fragment-shaders. The main idea is to leverage the texture-filtering functions available on OpenGL-cards to do some filtering for us on a cleverly modified texture-image trimmed with a second texture-image acting as a mask or stencil. The cairo-API offers the right means to easily create this trimming mask texture. Thanks to cairo it is also very simple to add nicely rounded corners to the texture image to further improve the smooth look of the final object on screen. The fragment-shader is used to do the actual masking out operation of the two texture-images.

The additional filters implemented as fragment-shaders do not play any role regarding this example. I just added them for fun and to see what one can do image-filter-wise without using FBOs (note: I am well aware that doing gaussian blurring without FBOs is anything but fast).

You can check it out the sources via bazaar, like this...

bzr branch lp:gl-cairo-aatrick

Once successfully compiled it will look like this when running...

The above image is a link to an ogg/theora video, which is 10.4 MBytes in size. You can quit the program with the q- or esc-key and space toggles between pause and animation. The scroll-wheel on the mouse controls a parameter feed to the image-filter shaders in realtime. If you have questions, suggestions or patches regarding this program, feel free to send them to me (macslow@bangang.de).

The simplest example

You can find another simplest example here (no glut, only one source file) (Juan Manuel Mouriz jmouriz@gmail.com).

Further reading