关于通过HDMI从GPU内存快速绘制OpenGL ES渲染内容(60 fps、FullHD)的Linux子系统选型及DRM适用性的技术问询
Which Linux Subsystem to Use?
Great question—if you’re targeting low-latency, FullHD@60fps OpenGL ES rendering over HDMI, you don’t need a heavy window system like X11 or Wayland. The optimal stack combines three lightweight, purpose-built components:
- DRM (Direct Rendering Manager): The core subsystem for controlling display hardware (mode setting, page flipping, buffer scanning to HDMI)
- libgbm (Generic Buffer Management): Bridges GPU memory (where OpenGL ES renders) and DRM (which displays the content) by allocating shared buffers
- libEGL (with DRM/GBM backend): Creates an OpenGL ES context that can render directly into GBM-managed buffers
This stack is the standard for embedded Linux and high-performance display scenarios, delivering exactly the performance you need.
Is DRM Alone Sufficient?
Short answer: No, but it’s the foundation. DRM handles low-level hardware interaction, but it has no understanding of OpenGL ES rendering. You need GBM to manage GPU-accessible buffers that both OpenGL ES and DRM can use, plus EGL to tie the OpenGL ES context to those buffers. Together, DRM + GBM + EGL form a minimal, overhead-free pipeline that avoids window system bloat.
General Architecture Overview
Here’s the step-by-step workflow to achieve your goal:
- DRM Initialization
- Open the DRM device node (typically
/dev/dri/card0) - Enumerate display resources (connectors, CRTCs, planes)
- Set the desired FullHD@60fps mode on the HDMI connector
- Open the DRM device node (typically
- GBM Setup
- Create a GBM device from the DRM file descriptor
- Allocate 2+ GBM buffers (double/triple buffering to eliminate tearing)
- EGL Context Creation
- Initialize EGL using the GBM device as the display
- Select an EGL configuration compatible with OpenGL ES 2.0+ and your display format
- Create EGL surfaces tied to each GBM buffer
- Create and activate the OpenGL ES context
- OpenGL ES Rendering Loop
- Bind the current EGL surface
- Perform rendering (clear screen, draw primitives, etc.)
- Trigger a buffer swap to queue the rendered frame for display
- DRM Page Flipping
- Sync with the display’s vertical blank (VSYNC) to ensure 60fps without tearing
- Wait for the page flip completion event before rendering the next frame
Example Source Code (Minimal Implementation)
Below is a stripped-down C example demonstrating the core flow (error handling is simplified for brevity):
#include <stdio.h> #include <stdlib.h> #include <fcntl.h> #include <unistd.h> #include <drm/drm.h> #include <drm/drm_mode.h> #include <gbm.h> #include <EGL/egl.h> #include <EGL/eglext.h> #include <GLES2/gl2.h> // Helper: Find connected HDMI connector static drmModeConnector* find_hdmi_connector(int drm_fd) { drmModeRes* res = drmModeGetResources(drm_fd); if (!res) return NULL; for (int i = 0; i < res->count_connectors; i++) { drmModeConnector* conn = drmModeGetConnector(drm_fd, res->connectors[i]); if (conn && conn->connector_type == DRM_MODE_CONNECTOR_HDMIA && conn->connection == DRM_MODE_CONNECTED) { drmModeFreeResources(res); return conn; } if (conn) drmModeFreeConnector(conn); } drmModeFreeResources(res); return NULL; } int main() { // Step 1: Initialize DRM int drm_fd = open("/dev/dri/card0", O_RDWR); if (drm_fd < 0) { perror("Failed to open DRM device"); return 1; } drmModeConnector* conn = find_hdmi_connector(drm_fd); if (!conn) { fprintf(stderr, "No connected HDMI connector found\n"); return 1; } drmModeModeInfo* mode = &conn->modes[0]; // Assume first mode is FullHD@60 // Set display mode (simplified) if (drmModeSetCrtc(drm_fd, conn->crtc_id, conn->encoder_id, 0, 0, &conn->connector_id, 1, mode) < 0) { perror("Failed to set CRTC mode"); drmModeFreeConnector(conn); close(drm_fd); return 1; } // Step 2: Initialize GBM struct gbm_device* gbm = gbm_create_device(drm_fd); if (!gbm) { perror("Failed to create GBM device"); return 1; } struct gbm_surface* gbm_surf = gbm_surface_create( gbm, mode->hdisplay, mode->vdisplay, GBM_FORMAT_XRGB8888, GBM_BO_USE_RENDERING | GBM_BO_USE_SCANOUT ); if (!gbm_surf) { perror("Failed to create GBM surface"); return 1; } // Step 3: Initialize EGL EGLDisplay egl_dpy = eglGetDisplay((EGLNativeDisplayType)gbm); if (!egl_dpy) { fprintf(stderr, "Failed to get EGL display\n"); return 1; } EGLint major, minor; if (!eglInitialize(egl_dpy, &major, &minor)) { fprintf(stderr, "EGL initialization failed\n"); return 1; } // Choose EGL config EGLConfig config; EGLint num_configs; EGLint config_attribs[] = { EGL_SURFACE_TYPE, EGL_WINDOW_BIT, EGL_RENDERABLE_TYPE, EGL_OPENGL_ES2_BIT, EGL_RED_SIZE, 8, EGL_GREEN_SIZE, 8, EGL_BLUE_SIZE, 8, EGL_NONE }; if (!eglChooseConfig(egl_dpy, config_attribs, &config, 1, &num_configs)) { fprintf(stderr, "Failed to choose EGL config\n"); return 1; } // Create EGL surface and context EGLSurface egl_surf = eglCreateWindowSurface(egl_dpy, config, (EGLNativeWindowType)gbm_surf, NULL); EGLContext egl_ctx = eglCreateContext(egl_dpy, config, EGL_NO_CONTEXT, NULL); if (!egl_surf || !egl_ctx) { fprintf(stderr, "Failed to create EGL surface/context\n"); return 1; } eglMakeCurrent(egl_dpy, egl_surf, egl_surf, egl_ctx); // Step 4: OpenGL ES Rendering Loop while (1) { // Clear screen to blue glClearColor(0.0f, 0.0f, 1.0f, 1.0f); glClear(GL_COLOR_BUFFER_BIT); // Swap buffers (triggers DRM page flip via EGL_GBM extension) eglSwapBuffers(egl_dpy, egl_surf); // Wait for VSYNC (use DRM events for precise sync in production) usleep(16667); // ~60fps interval } // Cleanup (omitted for brevity in example) eglDestroyContext(egl_dpy, egl_ctx); eglDestroySurface(egl_dpy, egl_surf); eglTerminate(egl_dpy); gbm_surface_destroy(gbm_surf); gbm_device_destroy(gbm); drmModeFreeConnector(conn); close(drm_fd); return 0; }
Compilation Note:
Link against required libraries with:
gcc -o gles_drm_render gles_drm_render.c -ldrm -lgbm -lEGL -lGLES2
内容的提问来源于stack exchange,提问作者Andrii Omelchenko




