blog · git · desktop · images · contact


First attempt at “drawing something” in Linux

2020-05-30

One intriguing argument for Wayland is that “cutting out the middleman” thing. “It has all moved to the kernel, what is X11 doing anymore?” So, what’s “all”? How come I don’t see any display drivers in wlroots? How do you draw something on Linux in the first place?

The middleman

Two points are being made here. One refers to compositing. If you use a compositor (I don’t, never have), then X11 really is a middleman. From this point of view, X11 is just a waste of time and one should get rid of it.

That’s not what I’m interested in. I don’t use compositors. But there’s more to the “middleman” aspect.

The old framebuffer device

Let’s start with something very simple. Quit your X11 or Wayland session and go back to the Linux VT. Then run this code:

#include <fcntl.h>
#include <linux/fb.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/ioctl.h>
#include <sys/mman.h>
#include <sys/stat.h>
#include <sys/types.h>

int
main()
{
    int fd;
    struct fb_var_screeninfo screeninfo;
    uint32_t *data;
    uint32_t x, y, width, height, xor;

    fd = open("/dev/fb0", O_RDWR);
    if (fd == -1)
    {
        perror("open /dev/fb0");
        exit(EXIT_FAILURE);
    }

    ioctl(fd, FBIOGET_VSCREENINFO, &screeninfo);
    if (screeninfo.bits_per_pixel != 32)
    {
        fprintf(stderr, "Expected 32 bits per pixel\n");
        exit(EXIT_FAILURE);
    }

    width = screeninfo.xres;
    height = screeninfo.yres;

    data = (uint32_t *)mmap(0, width * height * 4, PROT_READ | PROT_WRITE,
                            MAP_SHARED, fd, 0);
    if (data == MAP_FAILED)
    {
        perror("mmap");
        exit(EXIT_FAILURE);
    }

    for (y = 0; y < height; y++)
    {
        for (x = 0; x < width; x++)
        {
            xor = (x ^ y) % 256;
            data[y * width + x] = (xor << 16) | (xor << 8) | xor;
        }
    }

    exit(EXIT_SUCCESS);
}

We’re opening /dev/fb0 here, which today is present on virtually all Linux computers. We then ask it for its size and mmap() the file descriptor. Finally, we draw a simple xor pattern.

The Linux VT itself will use that same framebuffer, so our output will clash with it. As soon as the program ends, the shell will reappear and a prompt will be drawn somewhere – overwriting our texture.

But it’s really easy. It actually can’t get any simpler than this.

Note, however, that I can’t point you to any documentation. The program above is based on code that I’ve written years ago. I must have used Goo^W DuckDuckGo back then, because neither apropos FBIOGET_VSCREENINFO nor apropos fb0 nor man fbdev nor anything similar yields a result. If there is a manpage, I can’t find it.

Also note that no drivers are involved. The kernel provides a generic framebuffer device.

On to DRM

“DRM” is one of the worst names ever, because it clashes with “Digital Rights/Restrictions Management”, so have fun searching for it on The Internet. What I’m talking about is the Direct Rendering Manager. That aside, this is what Wayland compositors typically use under the hood.

My actual goal for this blog post was to port the program above to using DRM. I failed to do that on my own, because … I couldn’t find any documentation. I am not sure if this is my fault or if it simply does not exist.

When you install libdrm, you get a manpage man 7 drm. It gives a brief introduction. It mentions a few other manpages, such as drmOpen. But they don’t exist. Turns out, they don’t exist upstream, either.

There is also the FDO wiki page on DRM. It tends to curse a bit. Doesn’t matter, since it’s not a documentation on libdrm, which is the library meant to be used by user space applications, but more the underlying kernel interface – in other words, that which libdrm is supposed to provide an abstraction for.

Long story short, if there is documentation, I can’t find it.

You can, however, find example programs using your favorite search engine. I really have a problem with that, because I prefer the actual official documentation of a library. I will have to continue looking for it.

A good, short example is this one:

https://gist.github.com/uobikiemukot/c2be4d7515e977fd9e85

It’s trivial to adapt to draw the xor pattern. As it’s hosted on GitHub and might vanish at any moment, I copied it (the modified version) to my server:

linux-xor-drm.tar.gz

It’s much longer than our first program, but it’s interesting to see how using these newer APIs work and that it’s, after all, not that much more code. Note that it exits cleanly and doesn’t leave artifacts behind. If you wanted to, you could even send different output to different monitors.

Another good example might be this one:

https://waynewolf.github.io/2012/09/05/libdrm-samples/

Note again, that DRM is a generic interface. Not specific to a certain hardware.

The middleman, revisited

If I remember correctly (and I might be wrong here, because I haven’t configured X.Org for a decade – it just works these days), then X.Org used to be in control of the display. It used to provide drivers for graphics cards. It used to manage user input.

Pretty much all of this has moved into the kernel. DRM is just one piece of the puzzle.

Add to that the fact that many modern X11 clients don’t use X11’s rendering API anymore. They don’t tell X11: “Draw a line!” Instead they render locally and just give X11 a pixmap and tell it: “Display it, but please don’t touch it!” They do that for performance reasons and because X11’s rendering API is really outdated.

Those two aspects combined make X11 and X.Org a middleman. And then there’s that compositing aspect mentioned earlier.

What you’re left with is this: Clients want to render into a buffer and then have that buffer displayed. Plus input event handling. That’s pretty much exactly what Wayland is, if I understood correctly.

Conclusion

Docs on libdrm would be great. I might keep searching for them. If a poor soul out there reads this and knows where to find them, I’d greatly appreciate an e-mail.

DRM is, of course, tailored to do accelerated 3D graphics and multiple outputs, so it’s not as simple as just opening a device and writing to it.

It would also be interesting to see how the actual modesetting works. And libinput?

Comments?