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

"); 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

"); 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.

"); 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

"); 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.