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...
Create pristine texture-maps, which are resolution-independent and combine those with your custom mip-mapping.
Get access to gradient-rendering functions not provided by OpenGL.
Get crisp unicode-based font-rendering for "free" (needs pango in addition to cairo).
Easily integrate features like PS-, PDF-, SVG-support to your OpenGL-application (may need additional libraries).
Have animated vector-graphics as head-up-displays or overlays in your OpenGL-program.
Get your good looking graphics-application ported to other systems, without having to worry about API-availability.
Just be ultra trendy and the coolest kid on the block ;)
General approach
Which steps should one usually take in order to get cairo-drawn vector-graphics into an OpenGL-rendered scene...
Create your cairo-surface/context (maybe even glitz-based) you want to use as a source for later texture-map generation.
Render into the cairo-context with normal cairo-calls.
Copy the cairo-surface into an OpenGL-texture with
glTexImage2D()
.Use this texture to map your OpenGL-primitives with.
Rendering to the cairo-surface and copying that to the OpenGL-texture afterwards, can also be done in your rendering-loop, if you want to animate the cairo-graphics themselves from frame to frame.
For the time being disregard the idea of drawing into the same surface or texture with cairo- and OpenGL-calls. This is possible, but not in a very straight forward fashion. Also threadding-issues are not covered here at the moment. Everything happens in one thread right now.
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...
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...
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)...
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
OpenGL, the main resource for OpenGL on the web
MacSlow's site with several example programs using cairo, OpenGL, glitz and librsvg
librsvg, a library for adding SVG-support to your application
gtk+, the native UI-toolkit of Gnome, used by many Linux-programs, cross-platform
pigment, library for fast, visually rich graphical user interfaces (currently geared towards Elisa)
clutter, another library for fast, visually rich graphical user interfaces
GtkGlExt, a very good OpenGL-widget/binding for gtk+
SDL, a handy and ultra-thin "convenience" library for platform-abstraction
Amanith, the "competition", good, but not as free and versatile