Virtuous blogs jbarnes' braindump

10/31/11

English (US)   Writing standalone programs with EGL and KMS (updated)  -  Categories: Announcements [A]  -  @ 04:05:46 pm

Dear lazyweb,

Both on dri-devel and at the most recent Kernel Summit, the idea of a KMS based console program came up yet again. So in the interest of facilitating the lazyweb to write one, I thought I’d provide a review of what it takes to write a simple KMS program that ties in with GL, just in case anyone wants to port the VTE widget and give me my VTs on a cube. :) The ideal situation would be one where a distro could set CONFIG_VT=n and have userspace provide all console services, including support for the various types of input and output that exist in the wild today. The only thing that ought to be left to the kernel is spitting out panic information somehow. That means having a very simple text drawing layer remain, and a way for the kernel to flip to the latest kernel console buffer (today these functions are provided by fbcon and the DRM KMS layer, but if a userspace console service existed, we could likely clean them up quite a bit, and remove the need for the full VT and fb layers to be compiled in).

Initialization

One of the first things any DRM based program must do is open the DRI device:

int fd;
fd = open("/dev/dri/card0", O_RDWR);

You’ll need to save that fd for use with any later DRM call, including the ones we’ll need to get display resources. The path to the device may vary by distribution or configuration (e.g. with a multi-card setup), so take care to report sensible error strings if things go wrong.

The next step is to initialize the GBM library. GBM (for Generic Buffer Management) is a simple library to abstract buffers that will be used with Mesa. We’ll use the handle it creates to initialize EGL and to create render target buffers.

struct gbm_device *gbm;
gbm = gbm_create_device(fd);

Then we get EGL into shape:

EGLDisplay dpy;
dpy = eglGetDisplay(gbm);

We’ll need to hang on to both the GBM device handle and the EGL display handle as well, since most of the calls we make will need them. What follows is more EGL init.

EGLint major, minor;
const char *ver, *extensions;
eglInitialize(dpy, &major, &minor);
ver = eglQueryString(dpy, EGL_VERSION);
extensions = eglQueryString(dpy, EGL_EXTENSIONS);

Now that we have the extensions string, we need to check and make sure the EGL_KHR_surfaceless_opengl extension is available, since it’s what we’ll use to avoid the need for a full windowing system, which would normally provide us with pixmaps and windows to turn into EGL surfaces.

if (!strstr(extensions, "EGL_KHR_surfaceless_opengl")) {
    fprintf(stderr, "no surfaceless support, cannot initialize\n");
    exit(-1);
}

And now we’re finally ready to see what outputs are available for us to use. There are several good references for KMS output handling: the DDX drivers for radeon, nouveau, and intel, and testdisplay.c in the intel-gpu-tools package. (The below is taken from eglkms.c in the mesa-demos package.)

    drmModeRes *resources;
    drmModeConnector *connector;
    drmModeEncoder *encoder;
    int i;
 
    /* Find the first available connector with modes */
 
    resources = drmModeGetResources(fd);
    if (!resources) {
        fprintf(stderr, "drmModeGetResources failed\n");
        return;
    }
 
    for (i = 0; i < resources->count_connectors; i++) {
        connector = drmModeGetConnector(fd, resources->connectors[i]);
        if (connector == NULL)
            continue;
 
        if (connector->connection == DRM_MODE_CONNECTED &&
            connector->count_modes > 0)
            break;
 
        drmModeFreeConnector(connector);
    }
 
    if (i == resources->count_connectors) {
        fprintf(stderr, "No currently active connector found.\n");
        return;
    }
 
    for (i = 0; i < resources->count_encoders; i++) {
        encoder = drmModeGetEncoder(fd, resources->encoders[i]);
 
        if (encoder == NULL)
  	 continue;
 
       if (encoder->encoder_id == connector->encoder_id)
 	 break;
 
       drmModeFreeEncoder(encoder);
    }

At this point we have connector, encoder, and mode structures we’ll use to put something on the display later.

Drawing & displaying

We’ve initialized EGL, but we need to bind an API and create a context. In this case, we’ll use OpenGL.

    EGLContext ctx;

    eglBindAPI(EGL_OPENGL_API);
    ctx = eglCreateContext(dpy, NULL, EGL_NO_CONTEXT, NULL);

    eglMakeCurrent(dpy, EGL_NO_SURFACE, EGL_NO_SURFACE, ctx);

Now we have a fully functional KMS and EGL stack and it’s time for a little bit of glue magic. We need to create some buffers using GBM and bind them into EGL images that we can use as renderbuffers.

    EGLImageKHR image;
    struct gbm_bo *bo;
    GLuint fb, color_rb, depth_rb;
    uint32_t handle, stride;

    /* Create a full screen buffer for use as a render target */
    bo = gbm_bo_create(gbm, mode.hdisplay, mode.vdisplay, GBM_BO_FORMAT_XRGB888, GBM_BO_USE_SCANOUT | GBM_BO_USE_RENDERING);

    glGenFramebuffers(1, &fb);
    glBindFramebuffer(GL_FRAMEBUFFER_EXT, fb);

    handle = gbm_bo_get_handle(bo).u32;
    stride = gbm_bo_get_pitch(bo);

    image = eglCreateImageKHR(dpy, NULL, EGL_NATIVE_PIXMAP_KHR, bo, NULL);

    /* Set up render buffer... */
    glGenRenderbuffers(1, &color_rb);
    glBindRenderbuffer(GL_RENDERBUFFER_EXT, color_rb);
    glEGLImageTargetRenderbufferStorageOES(GL_RENDERBUFFER, image);
    glFramebufferRenderbufferEXT(GL_FRAMEBUFFER_EXT, GL_COLOR_ATTACHMENT0_EXT, GL_RENDERBUFFER_EXT, color_rb);

    /* and depth buffer */
    glGenRenderbuffers(1, &depth_rb);
    glBindRenderbuffer(GL_RENDERBUFFER_EXT, depth_rb);
    glRenderbufferStorage(GL_RENDERBUFFER_EXT, GL_DEPTH_COMPONENT, mode.hdisplay, mode.vdisplay);
    glFramebufferRenderbufferEXT(GL_FRAMEBUFFER_EXT, GL_DEPTH_ATTACHMENT_EXT, GL_RENDERBUFFER_EXT, depth_rb);

    if (glCheckFramebufferStatusEXT(GL_FRAMEBUFFER_EXT)) != GL_FRAMEBUFFER_COMPLETE) {
        fprintf(stderr, "something bad happened\n");
        exit(-1);
    }

At this point, you’re all ready to start rendering using standard GL calls. Note that since we don’t have a window system or a window bound, we won’t be using eglSwapBuffers() to display the final renderbuffer. But we still need to make sure the rendering is finished before we display it, so we can use glFinish() or some out of band synchronization mechanism (TBD) to ensure our rendering is complete before we display the new buffer.

Assuming we have something presentable, we can now try to display it:

    uint32_t crtc;

    /* Create a KMS framebuffer handle to set a mode with */
    drmModeAddFB(fd, mode.hdisplay, mode.vdisplay, 24, 32, stride, handle, &fb_id);

    drmModeSetCrtc(fd, encoder->crtc_id, fb_id, 0, 0, connector->connector_id, 1, mode);

In this example, we’re assuming a 24 bit depth buffer with 32 bits per pixel. That can of course vary, but what’s supported will depend on the chipset (24/32 is fairly common however). The drmModeSetCrtc call will also take care to simply flip to the fb in question if the mode timings are already programmed and are the same as the mode timings passed into the call. This allows for a simple page flipping scheme that simply creates two fbs using AddFB (pointing at different buffers) and flips between them with SetCrtc calls in a loop. Alternately, one can simply draw to the buffer that’s being displayed, if the visibility of partial rendering isn’t a problem.

DRM master privs and authentication

I forgot to mention that any program like this must drop DRM master privileges if another app, like X, wants to start up and use the DRM layer. The calls for this are drmSetMaster(int fd) and drmDropMaster(int fd). If no other DRM master is present, the SetMaster call should succeed. And before switching away due to a dbus call or whatever, any console program using the DRM layer should call drmDropMaster to allow the next client to get access to the DRM. Alternately, the console program could provide a display management API and make X a slave of that interface. That would also mean providing DRM authentication for other apps wanting to do direct rendering (typically master privs are required for display management, and simple auth privs are required to do command submission). Wayland is a good example of a display manager that provides this kind of functionality.

And that’s all there is to it, and should be enough to get someone started on kmscon… :) If you have questions or comments, feel free to bring them up at dri-devel@lists.freedesktop.org, in the comments section here, or via email to me.

8 commentsTrackback (0)

Comments:

Comment from: Alexander E. Patrakov [Visitor] Email
You told that the existence of a userspace KMS-based console program would help cleaning up the fbcon layer. However, I don't quite unserstand why. Indeed, there are some non-x86 targets (e.g. arm) that currently rely on the framebuffer console to display any console at all. What are you proposing to do with them? What will be done with too-new Intel display hardware that is not supported by the current in-kernel driver (except via text mode or {u,}vesafb)?
PermalinkPermalink 11/01/11 @ 03:06
Comment from: jbarnes [Member] Email
If a platform doesn't have any DRM drivers at all, then it would probably need to stick with the fb layer for console services and leave CONFIG_VT compiled in. But on platforms with DRM drivers, a DRM/KMS based console service in userspace could allow distros to turn off CONFIG_VT and most of the fb layer (with some refactoring) and allow for internationalized console support (both input and output). That's the main advantage.

And our current in-kernel driver supports hardware that isn't even out yet; I don't think we've been behind hardware release for at least basic mode setting support for over 5 years now...
PermalinkPermalink 11/01/11 @ 09:59
Comment from: Alexander E. Patrakov [Visitor] Email
OK, Intel was a bad example. Let's say "some other vendor" instead, or "qemu virtual graphics card", or just consider the case of someone in year 2015 installing a 2012's year distro on the 2015's hardware due to some stupid corporate policy. Is there any plan to handle such unsupported-beyond-vesa chipsets (other than to say "no") to users of distros with CONFIG_VT=n?
PermalinkPermalink 11/01/11 @ 11:26
Comment from: jbarnes [Member] Email
I think there will have to be a fallback, yes. But that's up to distros. It may be that they just won't support unsupported hardware. :) It depends on the type of product they're trying to produce and whether they certify specific platforms etc. Fortunately, drivers like vesafb and friends do exist, so it's even possible to build a user level console system on hardware w/o native drivers, even if the functionality is impaired (as it would be for any device w/o native drivers).
PermalinkPermalink 11/01/11 @ 11:38
Comment from: gogoliu [Visitor] Email
why not use MESA Screen Surface extension?
PermalinkPermalink 11/09/11 @ 04:45
Comment from: jbarnes [Member] Email
The screen surface extension doesn't get much attention these days, I'm not sure if it still works. IIRC it has some limitations about handling multiple outputs, so won't be suitable for a full fledged standalone console program.
PermalinkPermalink 11/09/11 @ 09:26
Comment from: David Herrmann [Visitor] Email
I put your code together with a simple glClear() glFinish() before the setFB() followed by a sleep(5). I get the cleared white framebuffer on the screen, however, its only a quarter of the screen. Only the left side of my screen is white, the rest stays black. However, if I draw a triangle from 1/1 1/0 0/0 it fits perfectly into the white area. That is, the whole framebuffer that I can draw into is shown on the screen but squeezed to the left side.

If I use twice the horizontal size in the gbm_bo object, then the framebuffer is shown on half the screen. However, I haven't succeeded in displaying it on the whole screen. The vertical size is always perfect, though.

Do you have an idea what could have gone wrong here? I also tried changing the pitch, but that didn't help either.
PermalinkPermalink 11/13/11 @ 07:32
Comment from: jbarnes [Member] Email
My first guess would be that the mode struct you're using isn't the one for the native mode of your panel. There's more sophisticated mode picking code in the various DDX drivers, or you can just walk the lists yourself looking for the preferred mode if your connector. But it could also be a pixel vs bpp confusion; 1/4 of the width of the display in bytes is pixels, if you're running at 24 bit depth with 32 bit pixels...
PermalinkPermalink 11/14/11 @ 08:38

Leave a comment:

Your email address will not be displayed on this site.
Your URL will be displayed.

Allowed XHTML tags: <p, ul, ol, li, dl, dt, dd, address, blockquote, ins, del, span, bdo, br, em, strong, dfn, code, samp, kdb, var, cite, abbr, acronym, q, sub, sup, tt, i, b, big, small>
(Line breaks become <br />)
(Set cookies for name, email and url)
(Allow users to contact you through a message form (your email will NOT be displayed.))
This is a captcha-picture. It is used to prevent mass-access by robots.

Please enter the characters from the image above. (case insensitive)

Trackback address for this post:

This is a captcha-picture. It is used to prevent mass-access by robots.

Please enter the characters from the image above. (case insensitive)

Trackbacks:

No Trackbacks for this post yet...

Pingbacks:

No Pingbacks for this post yet...

powered by b2evolution free blog software

Contact the admin - Credits: blog software | best hosting | blog ads