// 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; | |
} |
This comment has been minimized.
This comment has been minimized.
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 |
This comment has been minimized.
This comment has been minimized.
Aaah, I only noticed your comments now. It seems there's zero notifications when people comments on my gists. So, yeah, in order :
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. |
This comment has been minimized.
This comment has been minimized.
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 |
This comment has been minimized.
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: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.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