Skip to content

Instantly share code, notes, and snippets.

@Miouyouyou
Last active May 31, 2024 03:27
Show Gist options
  • Save Miouyouyou/89e9fe56a2c59bce7d4a18a858f389ef to your computer and use it in GitHub Desktop.
Save Miouyouyou/89e9fe56a2c59bce7d4a18a858f389ef to your computer and use it in GitHub Desktop.
An example, inspired by Rob Clark "kmscube.c" that uses Linux Direct Rendering Manager ( DRM ) and EGL to create an OpenGL ES 2 context. This is a standalone example, that just clears the screen with a blueish color. Usable with Rockchip DRM drivers and Mali Wayland/DRM userspace drivers.
// gcc -o drmgl Linux_DRM_OpenGLES.c `pkg-config --cflags --libs libdrm` -lgbm -lEGL -lGLESv2
/*
* Copyright (c) 2012 Arvin Schnell <arvin.schnell@gmail.com>
* Copyright (c) 2012 Rob Clark <rob@ti.com>
* Copyright (c) 2017 Miouyouyou <Myy> <myy@miouyouyou.fr>
*
* Permission is hereby granted, free of charge, to any person obtaining a
* copy of this software and associated documentation files (the "Software"),
* to deal in the Software without restriction, including without limitation
* the rights to use, copy, modify, merge, publish, distribute, sub license,
* and/or sell copies of the Software, and to permit persons to whom the
* Software is furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice (including the
* next paragraph) shall be included in all copies or substantial portions
* of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL
* THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
* DEALINGS IN THE SOFTWARE.
*/
/* Based on a egl cube test app originally written by Arvin Schnell */
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <string.h>
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <xf86drm.h>
#include <xf86drmMode.h>
#include <gbm.h>
#define GL_GLEXT_PROTOTYPES 1
#include <GLES2/gl2.h>
#include <GLES2/gl2ext.h>
#include <EGL/egl.h>
#include <EGL/eglext.h>
#include <assert.h>
#define ARRAY_SIZE(arr) (sizeof(arr) / sizeof((arr)[0]))
static struct {
EGLDisplay display;
EGLConfig config;
EGLContext context;
EGLSurface surface;
GLuint program;
GLint modelviewmatrix, modelviewprojectionmatrix, normalmatrix;
GLuint vbo;
GLuint positionsoffset, colorsoffset, normalsoffset;
} gl;
static struct {
struct gbm_device *dev;
struct gbm_surface *surface;
} gbm;
static struct {
int fd;
drmModeModeInfo *mode;
uint32_t crtc_id;
uint32_t connector_id;
} drm;
struct drm_fb {
struct gbm_bo *bo;
uint32_t fb_id;
};
static uint32_t find_crtc_for_encoder(const drmModeRes *resources,
const drmModeEncoder *encoder) {
int i;
for (i = 0; i < resources->count_crtcs; i++) {
/* possible_crtcs is a bitmask as described here:
* https://dvdhrm.wordpress.com/2012/09/13/linux-drm-mode-setting-api
*/
const uint32_t crtc_mask = 1 << i;
const uint32_t crtc_id = resources->crtcs[i];
if (encoder->possible_crtcs & crtc_mask) {
return crtc_id;
}
}
/* no match found */
return -1;
}
static uint32_t find_crtc_for_connector(const drmModeRes *resources,
const drmModeConnector *connector) {
int i;
for (i = 0; i < connector->count_encoders; i++) {
const uint32_t encoder_id = connector->encoders[i];
drmModeEncoder *encoder = drmModeGetEncoder(drm.fd, encoder_id);
if (encoder) {
const uint32_t crtc_id = find_crtc_for_encoder(resources, encoder);
drmModeFreeEncoder(encoder);
if (crtc_id != 0) {
return crtc_id;
}
}
}
/* no match found */
return -1;
}
static int init_drm(void)
{
drmModeRes *resources;
drmModeConnector *connector = NULL;
drmModeEncoder *encoder = NULL;
int i, area;
drm.fd = open("/dev/dri/card0", O_RDWR);
if (drm.fd < 0) {
printf("could not open drm device\n");
return -1;
}
resources = drmModeGetResources(drm.fd);
if (!resources) {
printf("drmModeGetResources failed: %s\n", strerror(errno));
return -1;
}
/* find a connected connector: */
for (i = 0; i < resources->count_connectors; i++) {
connector = drmModeGetConnector(drm.fd, resources->connectors[i]);
if (connector->connection == DRM_MODE_CONNECTED) {
/* it's connected, let's use this! */
break;
}
drmModeFreeConnector(connector);
connector = NULL;
}
if (!connector) {
/* we could be fancy and listen for hotplug events and wait for
* a connector..
*/
printf("no connected connector!\n");
return -1;
}
/* find prefered mode or the highest resolution mode: */
for (i = 0, area = 0; i < connector->count_modes; i++) {
drmModeModeInfo *current_mode = &connector->modes[i];
if (current_mode->type & DRM_MODE_TYPE_PREFERRED) {
drm.mode = current_mode;
}
int current_area = current_mode->hdisplay * current_mode->vdisplay;
if (current_area > area) {
drm.mode = current_mode;
area = current_area;
}
}
if (!drm.mode) {
printf("could not find mode!\n");
return -1;
}
/* find encoder: */
for (i = 0; i < resources->count_encoders; i++) {
encoder = drmModeGetEncoder(drm.fd, resources->encoders[i]);
if (encoder->encoder_id == connector->encoder_id)
break;
drmModeFreeEncoder(encoder);
encoder = NULL;
}
if (encoder) {
drm.crtc_id = encoder->crtc_id;
} else {
uint32_t crtc_id = find_crtc_for_connector(resources, connector);
if (crtc_id == 0) {
printf("no crtc found!\n");
return -1;
}
drm.crtc_id = crtc_id;
}
drm.connector_id = connector->connector_id;
return 0;
}
static int init_gbm(void)
{
gbm.dev = gbm_create_device(drm.fd);
gbm.surface = gbm_surface_create(gbm.dev,
drm.mode->hdisplay, drm.mode->vdisplay,
GBM_FORMAT_XRGB8888,
GBM_BO_USE_SCANOUT | GBM_BO_USE_RENDERING);
if (!gbm.surface) {
printf("failed to create gbm surface\n");
return -1;
}
return 0;
}
static int init_gl(void)
{
EGLint major, minor, n;
GLuint vertex_shader, fragment_shader;
GLint ret;
static const EGLint context_attribs[] = {
EGL_CONTEXT_CLIENT_VERSION, 2,
EGL_NONE
};
static const EGLint config_attribs[] = {
EGL_SURFACE_TYPE, EGL_WINDOW_BIT,
EGL_RED_SIZE, 1,
EGL_GREEN_SIZE, 1,
EGL_BLUE_SIZE, 1,
EGL_ALPHA_SIZE, 0,
EGL_RENDERABLE_TYPE, EGL_OPENGL_ES2_BIT,
EGL_NONE
};
PFNEGLGETPLATFORMDISPLAYEXTPROC get_platform_display = NULL;
get_platform_display =
(void *) eglGetProcAddress("eglGetPlatformDisplayEXT");
assert(get_platform_display != NULL);
gl.display = get_platform_display(EGL_PLATFORM_GBM_KHR, gbm.dev, NULL);
if (!eglInitialize(gl.display, &major, &minor)) {
printf("failed to initialize\n");
return -1;
}
printf("Using display %p with EGL version %d.%d\n",
gl.display, major, minor);
printf("EGL Version \"%s\"\n", eglQueryString(gl.display, EGL_VERSION));
printf("EGL Vendor \"%s\"\n", eglQueryString(gl.display, EGL_VENDOR));
printf("EGL Extensions \"%s\"\n", eglQueryString(gl.display, EGL_EXTENSIONS));
if (!eglBindAPI(EGL_OPENGL_ES_API)) {
printf("failed to bind api EGL_OPENGL_ES_API\n");
return -1;
}
if (!eglChooseConfig(gl.display, config_attribs, &gl.config, 1, &n) || n != 1) {
printf("failed to choose config: %d\n", n);
return -1;
}
gl.context = eglCreateContext(gl.display, gl.config,
EGL_NO_CONTEXT, context_attribs);
if (gl.context == NULL) {
printf("failed to create context\n");
return -1;
}
gl.surface = eglCreateWindowSurface(gl.display, gl.config, gbm.surface, NULL);
if (gl.surface == EGL_NO_SURFACE) {
printf("failed to create egl surface\n");
return -1;
}
/* connect the context to the surface */
eglMakeCurrent(gl.display, gl.surface, gl.surface, gl.context);
printf("GL Extensions: \"%s\"\n", glGetString(GL_EXTENSIONS));
return 0;
}
/* Draw code here */
static void draw(uint32_t i)
{
glClear(GL_COLOR_BUFFER_BIT);
glClearColor(0.2f, 0.3f, 0.5f, 1.0f);
}
static void
drm_fb_destroy_callback(struct gbm_bo *bo, void *data)
{
struct drm_fb *fb = data;
struct gbm_device *gbm = gbm_bo_get_device(bo);
if (fb->fb_id)
drmModeRmFB(drm.fd, fb->fb_id);
free(fb);
}
static struct drm_fb * drm_fb_get_from_bo(struct gbm_bo *bo)
{
struct drm_fb *fb = gbm_bo_get_user_data(bo);
uint32_t width, height, stride, handle;
int ret;
if (fb)
return fb;
fb = calloc(1, sizeof *fb);
fb->bo = bo;
width = gbm_bo_get_width(bo);
height = gbm_bo_get_height(bo);
stride = gbm_bo_get_stride(bo);
handle = gbm_bo_get_handle(bo).u32;
ret = drmModeAddFB(drm.fd, width, height, 24, 32, stride, handle, &fb->fb_id);
if (ret) {
printf("failed to create fb: %s\n", strerror(errno));
free(fb);
return NULL;
}
gbm_bo_set_user_data(bo, fb, drm_fb_destroy_callback);
return fb;
}
static void page_flip_handler(int fd, unsigned int frame,
unsigned int sec, unsigned int usec, void *data)
{
int *waiting_for_flip = data;
*waiting_for_flip = 0;
}
int main(int argc, char *argv[])
{
fd_set fds;
drmEventContext evctx = {
.version = DRM_EVENT_CONTEXT_VERSION,
.page_flip_handler = page_flip_handler,
};
struct gbm_bo *bo;
struct drm_fb *fb;
uint32_t i = 0;
int ret;
ret = init_drm();
if (ret) {
printf("failed to initialize DRM\n");
return ret;
}
FD_ZERO(&fds);
FD_SET(0, &fds);
FD_SET(drm.fd, &fds);
ret = init_gbm();
if (ret) {
printf("failed to initialize GBM\n");
return ret;
}
ret = init_gl();
if (ret) {
printf("failed to initialize EGL\n");
return ret;
}
/* clear the color buffer */
glClearColor(0.5, 0.5, 0.5, 1.0);
glClear(GL_COLOR_BUFFER_BIT);
eglSwapBuffers(gl.display, gl.surface);
bo = gbm_surface_lock_front_buffer(gbm.surface);
fb = drm_fb_get_from_bo(bo);
/* set mode: */
ret = drmModeSetCrtc(drm.fd, drm.crtc_id, fb->fb_id, 0, 0,
&drm.connector_id, 1, drm.mode);
if (ret) {
printf("failed to set mode: %s\n", strerror(errno));
return ret;
}
while (1) {
struct gbm_bo *next_bo;
int waiting_for_flip = 1;
draw(i++);
eglSwapBuffers(gl.display, gl.surface);
next_bo = gbm_surface_lock_front_buffer(gbm.surface);
fb = drm_fb_get_from_bo(next_bo);
/*
* Here you could also update drm plane layers if you want
* hw composition
*/
ret = drmModePageFlip(drm.fd, drm.crtc_id, fb->fb_id,
DRM_MODE_PAGE_FLIP_EVENT, &waiting_for_flip);
if (ret) {
printf("failed to queue page flip: %s\n", strerror(errno));
return -1;
}
while (waiting_for_flip) {
ret = select(drm.fd + 1, &fds, NULL, NULL, NULL);
if (ret < 0) {
printf("select err: %s\n", strerror(errno));
return ret;
} else if (ret == 0) {
printf("select timeout!\n");
return -1;
} else if (FD_ISSET(0, &fds)) {
printf("user interrupted!\n");
break;
}
drmHandleEvent(drm.fd, &evctx);
}
/* release last buffer to render on again: */
gbm_surface_release_buffer(gbm.surface, bo);
bo = next_bo;
}
return ret;
}
@Miouyouyou
Copy link
Author

Miouyouyou commented Nov 3, 2017

Aaah, I only noticed your comments now. It seems there's zero notifications when people comments on my gists.

So, yeah, in order :
The minimum required... I guess that you'll need a :

  • libc
  • libc++ (not sure but that might be required)
  • Mali User-space binary drivers which provide libEGL, libGLESv2, libgbm and others symbols. Generally, all these libraries are symlinked to a libmali-midgard-xxx.so .
    You can obtain a libmali-midgard-xxx.so here : https://github.com/rockchip-linux/libmali/tree/rockchip/lib/arm-linux-gnueabihf
    The t76x-r14p0-wayland.so should do the trick. You'll then have to rebuild all the symlinks for libEGL.so{,.1} libGLESv2.so{,.2} libgbm.so{,.1} in order to point them to libmali-midgard-t76x-r14p0-wayland.so
  • libdrm

Concerning GBM, it's a 'global buffer management' library. It's only used with OpenGL to setup the "big array-like memory space" that will be used to draw the pixels that will be sent to the output (the screen in most cases). If you take a look at my "Nyanbada" repo, there's some test code that show how to allocate a buffer on the GPU, using DRM IOCTLS, and how to mmap this buffer, write in it and get something displayed on the screen.
The whole "OpenGL" work (meaning getting the GPU running full speed and send to each of its work unit a vertex shader or a fragment shader to compute) is done by libGLESv2 and, in this case, is serious proprietary stuff. Sure there's IOCTL and what not and some work is being done to reverse engineer the whole work but... it takes time...

@habemus-papadum
Copy link

No worries, and good to know that github doesn't notify about gists comments. I finally got around opening an issue with more details. Take care

@Thor-x86
Copy link

Thor-x86 commented Jun 4, 2021

It seems there's zero notifications when people comments on my gists

I just noticed there is a "subscribe" button at very top of this page. Consider to making sure it's enabled

@Miouyouyou
Copy link
Author

Thanks for the heads-up !

Turns out that I'm receiving the comments to the Gist now. This seems to have changed... 6 months ago I guess ?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment