Cairo is a 2-D graphics library with support for multiple output devices. EGL is one of the backends for Cairo graphics, and this means that it can draw to Wayland surfaces. This chapter looks at this backend binding.
Cairo is a 2-D graphics library originally developed for use in the X Window system, but abstracted away from that in early 2000's. With Cairo, most of the original X Window calls are redundant, and this is one of the drivers for replacing X with Wayland.
Cairo can be output to X, OS X Quartz, PNG files, PDF files, ..., and OpenGL. This last allows Cairo to be used to write to EGL surfaces, and this forms the basis of using Cairo with Wayland.
Cairo uses as backend a device
of type cairo_device_t
.
On a device it will create a surface
of type cairo_surface_t
to write to. Writing is done using a context
of type cairo_t
which holdsthe current state of the rendering device,
such as the location of objects to be drawn, fonts,
colours, etc.
The value of fields in the context can be changed by calls such as
cairo_move_to
and objects drawn to the surface
by calls such as
cairo_show_text
.
The surface may then be rendered to the device by calls
such as cairo_paint
or written
to a file by calls such as cairo_surface_write_to_png
.
The following program from the Cairo FAQ doesn't create a
device, but instead creates a surface directly as an image
surface, draws a string in blue to it and then writes the surface out
to a PNG file:
#include <cairo.h>
int
main (int argc, char *argv[])
{
cairo_surface_t *surface =
cairo_image_surface_create (CAIRO_FORMAT_ARGB32, 240, 80);
cairo_t *cr =
cairo_create (surface);
cairo_select_font_face (cr, "serif", CAIRO_FONT_SLANT_NORMAL, CAIRO_FONT_WEIGHT_BOLD);
cairo_set_font_size (cr, 32.0);
cairo_set_source_rgb (cr, 0.0, 0.0, 1.0);
cairo_move_to (cr, 10.0, 50.0);
cairo_show_text (cr, "Hello, world");
cairo_destroy (cr);
cairo_surface_write_to_png (surface, "hello.png");
cairo_surface_destroy (surface);
return 0;
}
The functions to deal with EGL are not documented in the online
Cairo documentation. Instead you have to look inside the
cairo-gl.h
header file. This defines the call
cairo_egl_device_create()
which takes an
EGLDisplay
and an EGLContext
as
parameters. To use this call in Wayland means that you to
have initialised these to Wayland as described in the EGL
chapter.
This can be used as in
init_cairo() {
cairo_device =
cairo_egl_device_create(egl_display, egl_context);
if (cairo_device_status(cairo_device) != CAIRO_STATUS_SUCCESS) {
fprintf(stderr, "failed to get cairo EGL device\n");
exit(1);
}
}
Once the Cairo device has been created, it can be used with an EGL surface to create a Cairo surface to render to:
cairo_surface = cairo_gl_surface_create_for_egl (cairo_device,
egl_surface,
width, height);
Once you have a Cairo surface, you draw to it using standard Cairo calls. These are not dealt with here - they would take a whole book. Instead, refer to the tutorials and documentation listed on the Cairo site.
In the EGL chapter, an EGL buffer was sent to the Wayland
compositor by EGLSwapBuffers
.
In Cairo, this wrapped in the call
cairo_gl_surface_swapbuffers(()
.
That's it! No other changes need to be made to
standard Cairo programs.
The following example cairo_window.c creates an EGL Wayland window, then a Cairo surface and draws "hello" in green on a yellow rectangle:
#include <stdio.h> #include <stdlib.h> #include <string.h> #include <wayland-client.h> #include <wayland-server.h> #include <wayland-client-protocol.h> #include <wayland-egl.h> #include <EGL/egl.h> #include <cairo/cairo-gl.h> struct wl_display *display = NULL; struct wl_compositor *compositor = NULL; struct wl_surface *surface; struct wl_egl_window *egl_window; struct wl_region *region; struct wl_shell *shell; struct wl_shell_surface *shell_surface; EGLDisplay egl_display; EGLConfig egl_conf; EGLSurface egl_surface; EGLContext egl_context; cairo_surface_t *cairo_surface; cairo_device_t *cairo_device; static void global_registry_handler(void *data, struct wl_registry *registry, uint32_t id, const char *interface, uint32_t version) { printf("Got a registry event for %s id %d\n", interface, id); if (strcmp(interface, "wl_compositor") == 0) { compositor = wl_registry_bind(registry, id, &wl_compositor_interface, 1); } else if (strcmp(interface, "wl_shell") == 0) { shell = wl_registry_bind(registry, id, &wl_shell_interface, 1); } } static void global_registry_remover(void *data, struct wl_registry *registry, uint32_t id) { printf("Got a registry losing event for %d\n", id); } static const struct wl_registry_listener registry_listener = { global_registry_handler, global_registry_remover }; static void init_cairo(); static void create_window() { egl_window = wl_egl_window_create(surface, 480, 360); if (egl_window == EGL_NO_SURFACE) { fprintf(stderr, "Can't create egl window\n"); exit(1); } else { fprintf(stderr, "Created egl window\n"); } egl_surface = eglCreateWindowSurface(egl_display, egl_conf, egl_window, NULL); cairo_surface = cairo_gl_surface_create_for_egl (cairo_device, egl_surface, 480, 360); if (cairo_surface == NULL) { fprintf(stderr, "Can't create Cairo surface\n"); exit(1); } else { fprintf(stderr, "Created Cairo surface\n"); } cairo_t *cr = cairo_create(cairo_surface); int err; if (err = cairo_status(cr) != CAIRO_STATUS_SUCCESS) { fprintf(stderr, "Cairo error on create %s\n", cairo_status_to_string(err)); } cairo_set_source_rgb(cr, 1.0, 1.0, 0.0); if (err = cairo_status(cr) != CAIRO_STATUS_SUCCESS) { fprintf(stderr, "Cairo error after set source %s\n", cairo_status_to_string(err)); } cairo_paint(cr); cairo_set_source_rgb(cr, 0.0, 1.0, 0.0); if (err = cairo_status(cr) != CAIRO_STATUS_SUCCESS) { fprintf(stderr, "Cairo error after set source %s\n", cairo_status_to_string(err)); exit(1); } // this is a standard font for Cairo cairo_select_font_face (cr, "cairo:serif", CAIRO_FONT_SLANT_NORMAL, CAIRO_FONT_WEIGHT_BOLD); cairo_set_font_size (cr, 20); cairo_move_to(cr, 10.0, 50.0); cairo_show_text (cr, "hello"); if (err = cairo_status(cr) != CAIRO_STATUS_SUCCESS) { fprintf(stderr, "Cairo error after paint %s\n", cairo_status_to_string(err)); } cairo_gl_surface_swapbuffers(cairo_surface); } static void init_cairo() { cairo_device = cairo_egl_device_create(egl_display, egl_context); if (cairo_device_status(cairo_device) != CAIRO_STATUS_SUCCESS) { fprintf(stderr, "failed to get cairo EGL device\n"); exit(1); } } static void init_egl() { EGLint major, minor, count, n, size; EGLConfig *configs; int i; EGLint config_attribs[] = { EGL_SURFACE_TYPE, EGL_WINDOW_BIT, EGL_RED_SIZE, 8, EGL_GREEN_SIZE, 8, EGL_BLUE_SIZE, 8, EGL_RENDERABLE_TYPE, EGL_OPENGL_ES2_BIT, EGL_NONE }; static const EGLint context_attribs[] = { EGL_CONTEXT_CLIENT_VERSION, 2, EGL_NONE }; egl_display = eglGetDisplay((EGLNativeDisplayType) display); if (egl_display == EGL_NO_DISPLAY) { fprintf(stderr, "Can't create egl display\n"); exit(1); } else { fprintf(stderr, "Created egl display\n"); } if (eglInitialize(egl_display, &major, &minor) != EGL_TRUE) { fprintf(stderr, "Can't initialise egl display\n"); exit(1); } fprintf(stderr, "EGL major: %d, minor %d\n", major, minor); if (!eglBindAPI(EGL_OPENGL_API)) { fprintf(stderr, "failed to bind EGL client API\n"); } eglGetConfigs(egl_display, NULL, 0, &count); printf("EGL has %d configs\n", count); configs = calloc(count, sizeof *configs); eglChooseConfig(egl_display, config_attribs, configs, count, &n); for (i = 0; i < n; i++) { eglGetConfigAttrib(egl_display, configs[i], EGL_BUFFER_SIZE, &size); printf("Buffer size for config %d is %d\n", i, size); eglGetConfigAttrib(egl_display, configs[i], EGL_RED_SIZE, &size); printf("Red size for config %d is %d\n", i, size); // just choose the first one egl_conf = configs[i]; break; } egl_context = eglCreateContext(egl_display, egl_conf, EGL_NO_CONTEXT, context_attribs); if (egl_context == NULL) { fprintf(stderr, "Can't create context\n"); } } static void get_server_references(void) { display = wl_display_connect(NULL); if (display == NULL) { fprintf(stderr, "Can't connect to display\n"); exit(1); } printf("connected to display\n"); struct wl_registry *registry = wl_display_get_registry(display); wl_registry_add_listener(registry, ®istry_listener, NULL); wl_display_dispatch(display); wl_display_roundtrip(display); if (compositor == NULL || shell == NULL) { fprintf(stderr, "Can't find compositor or shell\n"); exit(1); } else { fprintf(stderr, "Found compositor and shell\n"); } } int main(int argc, char **argv) { get_server_references(); surface = wl_compositor_create_surface(compositor); if (surface == NULL) { fprintf(stderr, "Can't create surface\n"); exit(1); } else { fprintf(stderr, "Created surface\n"); } shell_surface = wl_shell_get_shell_surface(shell, surface); wl_shell_surface_set_toplevel(shell_surface); init_egl(); init_cairo(); create_window(); while (wl_display_dispatch(display) != -1) { ; } wl_display_disconnect(display); printf("disconnected from display\n"); exit(0); }
These are defined in cairo-gl.h
:
cairo_public cairo_surface_t *
cairo_gl_surface_create (cairo_device_t *device,
cairo_content_t content,
int width, int height);
cairo_public cairo_surface_t *
cairo_gl_surface_create_for_texture (cairo_device_t *abstract_device,
cairo_content_t content,
unsigned int tex,
int width, int height);
cairo_public void
cairo_gl_surface_set_size (cairo_surface_t *surface, int width, int height);
cairo_public int
cairo_gl_surface_get_width (cairo_surface_t *abstract_surface);
cairo_public int
cairo_gl_surface_get_height (cairo_surface_t *abstract_surface);
cairo_public void
cairo_gl_surface_swapbuffers (cairo_surface_t *surface);
cairo_public void
cairo_gl_device_set_thread_aware (cairo_device_t *device,
cairo_bool_t thread_aware);
cairo_public cairo_device_t *
cairo_egl_device_create (EGLDisplay dpy, EGLContext egl);
cairo_public cairo_surface_t *
cairo_gl_surface_create_for_egl (cairo_device_t *device,
EGLSurface egl,
int width,
int height);
cairo_public EGLDisplay
cairo_egl_device_get_display (cairo_device_t *device);
cairo_public EGLSurface
cairo_egl_device_get_context (cairo_device_t *device);
Copyright © Jan Newmarch, jan@newmarch.name
If you like this book, please contribute using Flattr
or donate using PayPal