Skip to content

Instantly share code, notes, and snippets.

@Miouyouyou
Last active October 6, 2024 02:38
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;
}
@habemus-papadum
Copy link

Thank you so much for all your code and posts. I was curious about the lowest level / simplest approach to opengl on an arm dev board, and have found your code samples extremely educational! I was wondering if you know the answers to the following:

  • What would be the minimal steps to hand build a minimal kernel (i.e. not using a distro like armian) that would be able to run this code snippet as /init on an Asus tinkerboard? I know there must be many ways to interpret what this means and also go about implementing it, but I am trying to just get a sense at a high level. I imagine it would be something like:

    • build a kernel and use your rockmyy drivers -- though I don't quite know the precise steps; I wonder what the key kernel config parameters to use, and how to build the driver directly into the kernel. You make reference to Rockchip DRM drivers and Mali Wayland/DRM userspace drivers -- I don't really understand precisely 'user space drivers' are and if they are relevant in my scenario where Wayland is not being used.
    • build some libc for this setup -- I'm understand this part
    • build libdrm, gbm, EGL, GLESv2 -- where exactly are these found, how should the builds be configured, and I believe some of them are part of the larger MESA package -- so how to take just pieces that are needed. Most of that doesn't seem to hard, but, I can' t figure out if building these items requires configuration flags specific to Mali -- I would like to thing that the EGL gbm device abstraction is completely device independent and does not have special case code for various chipsets e.g. Mali, but I'm not sure.
  • Further regarding drm/gbm -- I can understand how drm can be used for simple software rendering similar to what was done with the legacy linux frame buffer, but I am unclear as to how hardware accelerated graphics works in this context. There must be some very elaborate set of ioctl's, etc, to support all the needs of opengl -- do you know where to browse the source code for the details of how this works?

Sorry for so many questions; Keep up the great work!

nehal

@habemus-papadum
Copy link

Hi -- I realized that a lot of my questions can be answered by looking more carefully at RockMyy. I'm still not 100% clear on some things, but I will open an issue in the repo. thanks, nehal

@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