Implementing Resea GUI (Part 1): Porting Cairo 2D graphics library

A sphere filled with a gradient using Cairo
A sphere filled with a gradient using Cairo

A GUI desktop environment is one of the major missing features in Resea. I intentionally skipped it since I think it's more important to improve under the hood, but I realized that it's a pretty interesting topic to work on and moreover it's attractive to newbies.

How a Resea GUI system would work?

Like other modern window managers like Wayland-based ones, I plan to implement Resea GUI as a compositing window manager.

In a compositing windowing system, applications draw contents into their own independent buffers. A single user process called window manager (or compositor) is responsible for handling the display framebuffer and user inputs (keyboard and mouse). It merges application buffers into a single framebuffer and delivers user inputs to the active window.

Why Cairo?

Since I'd implement a modern good-looking desktop environment, I decided not to implement our own 2D graphics library just because it takes too much time (help me if you're familiar with good-quality font rendering!). Instead, I added a port of Cairo, a well-known 2D graphics library as a former backend for Firefox.

By the way, I also checked Skia, the current backend for Firefox and Chrome, but it seemed to require extra work since we don't (yet) support compiling C++ source files.

POSIX API compatibility layer

Almost all libraries written in C depend on the POSIX APIs even if they call themselves portable. It's true on all modern operating systems like macOS and Linux, it's problematic for Resea as its standard library design is totally different. A good example is true and false: while they are defined in stdbool.h in the C standard library, in Resea we define them in types.h.

I'm happy with the Resea's way so isolated source files that depend on POSIX APIs by allowing overriding include directories: Resea apps reference the Resea standard library and POSIX apps reference the C standard library.

Here's a simple example in the virtio-gpu driver:

# servers/drivers/gpu/virtio_gpu/build.mk
$(build_dir)/cairo_demo.o: INCLUDES += $(LIBC_INCLUDES) $(CAIRO_INCLUDES)

cairo_demo.c internally uses Cairo APIs (that obviously depend on POSIX APIs) to draw a demo image pattern to demonstrate the GPU driver implementation. By overriding $(INCLUDES), the build system disables implicit include directories like libs/resea and libs/common.

Porting Newlib

The C standard library is a port of newlib. Its most attractive point is its portability: all what we need to do is to implement OS-dependent interfaces (i.e. *NIX system calls) listed here.

While I don't apply any patches to newlib, some libc functions are disabled by tweaking symbols using llvm-objcopy --redefine-syms. POSIX apps will use Resea's implementation of these functions for now.

Moreover, newlib is built in a Docker container because newlib's build system is a bit complicated (it produces multiple object files from a single C file by changing macros!). We need refactoring and improvements on our build system to make it easy to POSIX-dependent libraries.

Draw something using Cairo!

Here's a sample code which draws a rectangle on the screen using cairo:

#include <cairo.h>
#include <stdint.h>

void draw(uint32_t *framebuffer, uint32_t width, uint32_t height) {
    surface = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, width, height);
    cairo_t *cr = cairo_create(surface);

    // Draw a rectangle.
    cairo_set_source_rgb(cr, 1, 0, 0);
    cairo_rectangle(cr, 50, 50, 100, 100);
    cairo_fill(cr);

    cairo_surface_flush(surface);
    image = cairo_image_surface_get_data(surface);

    // Copy into the framebuffer.
    for (uint32_t y = 0; y < height; y++) {
        for (uint32_t x = 0; x < width; x++) {
            uint32_t pixel = image[y * width + x];
            framebuffer[y * width + x] = pixel;
        }
    }
}

Cairo Tutorial would be helpful to get started with Cairo.

Next Steps

By porting Cairo, we can now draw anti-aliased good looking 2D primitives. The first milestone of Resea GUI implementation is a GUI-based shell (servers/apps/shell).

In the next post, I'll try to support FreeType, a high-quality font rendering library used in major operating systems to draw terminal contents.