Bladeren bron

Refactor: Replace DRM swap buffer implementation with asynchronous page-flipping and framebuffer caching

The original implementation created/destroyed framebuffers (FBs) per-frame, leading to kernel overhead and screen tearing. This commit replaces it with a different approach using:
- Asynchronous `drmModePageFlip()` with vblank sync
- Framebuffer caching to reduce repeated FB creation/removal operations
- Proper resource management through BO callbacks and buffer release synchronization
- Added error handling for busy displays, cache overflows, and flip failures
- Event-driven cleanup via page_flip_handler to prevent GPU/scanout conflicts

Co-authored-by: rob-bits
pull/4988/head
katonar 1 week geleden
bovenliggende
commit
060bd787b1
1 gewijzigde bestanden met toevoegingen van 215 en 20 verwijderingen
  1. +215
    -20
      src/platforms/rcore_drm.c

+ 215
- 20
src/platforms/rcore_drm.c Bestand weergeven

@ -71,6 +71,21 @@
#include "EGL/egl.h" // Native platform windowing system interface
#include "EGL/eglext.h" // EGL extensions
#include <poll.h> // for drmHandleEvent poll
#include <errno.h> //for EBUSY, EAGAIN
#define MAX_CACHED_BOS 3
typedef struct {
struct gbm_bo *bo;
uint32_t fb_id; // DRM framebuffer ID
} FramebufferCache;
static FramebufferCache fbCache[MAX_CACHED_BOS];
static volatile int fbCacheCount = 0;
static volatile bool pendingFlip = false;
static bool crtcSet = false;
#ifndef EGL_OPENGL_ES3_BIT
#define EGL_OPENGL_ES3_BIT 0x40
#endif
@ -217,6 +232,7 @@ static const short linuxToRaylibMap[KEYMAP_SIZE] = {
//----------------------------------------------------------------------------------
// Module Internal Functions Declaration
//----------------------------------------------------------------------------------
int InitSwapScreenBuffer(void);
int InitPlatform(void); // Initialize platform (graphics, inputs and more)
void ClosePlatform(void); // Close platform
@ -551,34 +567,207 @@ void DisableCursor(void)
CORE.Input.Mouse.cursorHidden = true;
}
// Swap back buffer with front buffer (screen drawing)
void SwapScreenBuffer(void)
{
static void drm_fb_destroy_callback(struct gbm_bo *bo, void *data) {
uint32_t fb_id = (uintptr_t)data;
// Remove from cache
for (int i = 0; i < fbCacheCount; i++) {
if (fbCache[i].bo == bo) {
TRACELOG(LOG_INFO, "DRM: fb removed %u", (uintptr_t)fb_id);
drmModeRmFB(platform.fd, fbCache[i].fb_id); // Release DRM FB
// Shift remaining entries
for (int j = i; j < fbCacheCount - 1; j++) {
fbCache[j] = fbCache[j + 1];
}
fbCacheCount--;
break;
}
}
}
// Create or retrieve cached DRM FB for BO
static uint32_t get_or_create_fb_for_bo(struct gbm_bo *bo) {
// Try to find existing cache entry
for (int i = 0; i < fbCacheCount; i++) {
if (fbCache[i].bo == bo) {
return fbCache[i].fb_id;
}
}
// Create new entry if cache not full
if (fbCacheCount >= MAX_CACHED_BOS) {
//FB cache full!
return 0;
}
uint32_t handle = gbm_bo_get_handle(bo).u32;
uint32_t stride = gbm_bo_get_stride(bo);
uint32_t width = gbm_bo_get_width(bo);
uint32_t height = gbm_bo_get_height(bo);
uint32_t fb_id;
if (drmModeAddFB(platform.fd, width, height, 24, 32, stride, handle, &fb_id)) {
//rmModeAddFB failed
return 0;
}
// Store in cache
fbCache[fbCacheCount] = (FramebufferCache){ .bo = bo, .fb_id = fb_id };
fbCacheCount++;
// Set destroy callback to auto-cleanup
gbm_bo_set_user_data(bo, (void*)(uintptr_t)fb_id, drm_fb_destroy_callback);
TRACELOG(LOG_INFO, "DRM: added new bo %u" , (uintptr_t)fb_id);
return fb_id;
}
// Renders a blank frame to allocate initial buffers
void RenderBlankFrame() {
glClearColor(0, 0, 0, 1);
glClear(GL_COLOR_BUFFER_BIT);
eglSwapBuffers(platform.device, platform.surface);
// Ensure the buffer is processed
glFinish();
}
// Initialize with first buffer only
int InitSwapScreenBuffer() {
if (!platform.gbmSurface || platform.fd < 0) {
TRACELOG(LOG_ERROR, "DRM not initialized");
return -1;
}
if (!platform.gbmSurface || (-1 == platform.fd) || !platform.connector || !platform.crtc) TRACELOG(LOG_ERROR, "DISPLAY: DRM initialization failed to swap");
// Render a blank frame to allocate buffers
RenderBlankFrame();
// Get first buffer
struct gbm_bo *bo = gbm_surface_lock_front_buffer(platform.gbmSurface);
if (!bo) TRACELOG(LOG_ERROR, "DISPLAY: Failed GBM to lock front buffer");
if (!bo) {
TRACELOG(LOG_ERROR, "Failed to lock initial buffer");
return -1;
}
uint32_t fb = 0;
int result = drmModeAddFB(platform.fd, platform.connector->modes[platform.modeIndex].hdisplay, platform.connector->modes[platform.modeIndex].vdisplay, 24, 32, gbm_bo_get_stride(bo), gbm_bo_get_handle(bo).u32, &fb);
if (result != 0) TRACELOG(LOG_ERROR, "DISPLAY: drmModeAddFB() failed with result: %d", result);
// Create FB for first buffer
uint32_t fb_id = get_or_create_fb_for_bo(bo);
if (!fb_id) {
gbm_surface_release_buffer(platform.gbmSurface, bo);
return -1;
}
result = drmModeSetCrtc(platform.fd, platform.crtc->crtc_id, fb, 0, 0, &platform.connector->connector_id, 1, &platform.connector->modes[platform.modeIndex]);
if (result != 0) TRACELOG(LOG_ERROR, "DISPLAY: drmModeSetCrtc() failed with result: %d", result);
// Initial CRTC setup
if (drmModeSetCrtc(platform.fd, platform.crtc->crtc_id, fb_id,
0, 0, &platform.connector->connector_id, 1,
&platform.connector->modes[platform.modeIndex])) {
TRACELOG(LOG_ERROR, "Initial CRTC setup failed: %s", strerror(errno));
gbm_surface_release_buffer(platform.gbmSurface, bo);
return -1;
}
if (platform.prevFB)
{
result = drmModeRmFB(platform.fd, platform.prevFB);
if (result != 0) TRACELOG(LOG_ERROR, "DISPLAY: drmModeRmFB() failed with result: %d", result);
// Keep first buffer locked until flipped
platform.prevBO = bo;
crtcSet = true;
return 0;
}
// Static page flip handler
// this will be called once the drmModePageFlip() finished from the drmHandleEvent(platform.fd, &evctx); context
static void page_flip_handler(int fd, unsigned int frame,
unsigned int sec, unsigned int usec,
void *data) {
(void)fd; (void)frame; (void)sec; (void)usec; // Unused
pendingFlip = false;
struct gbm_bo *bo_to_release = (struct gbm_bo *)data;
//Buffers are released after the flip completes (via page_flip_handler), ensuring they're no longer in use.
// Prevents the GPU from writing to a buffer being scanned out
if (bo_to_release) {
gbm_surface_release_buffer(platform.gbmSurface, bo_to_release);
}
}
platform.prevFB = fb;
// Swap implementation with proper caching
void SwapScreenBuffer() {
static int loopCnt = 0;
loopCnt++;
static int errCnt[5] = {0};
if (!crtcSet || !platform.gbmSurface) return;
if (platform.prevBO) gbm_surface_release_buffer(platform.gbmSurface, platform.prevBO);
//call this only, if pendingFlip is not set
eglSwapBuffers(platform.device, platform.surface);
platform.prevBO = bo;
// Process pending events non-blocking
drmEventContext evctx = {
.version = DRM_EVENT_CONTEXT_VERSION,
.page_flip_handler = page_flip_handler
};
struct pollfd pfd = { .fd = platform.fd, .events = POLLIN };
//polling for event for 0ms
while (poll(&pfd, 1, 0) > 0) {
drmHandleEvent(platform.fd, &evctx);
}
// Skip if previous flip pending
if (pendingFlip) {
//Skip frame: flip pending
errCnt[0]++;
return;
}
// Get new front buffer
struct gbm_bo *next_bo = gbm_surface_lock_front_buffer(platform.gbmSurface);
if (!next_bo) {
//Failed to lock front buffer
errCnt[1]++;
return;
}
// Get FB ID (creates new one if needed)
uint32_t fb_id = get_or_create_fb_for_bo(next_bo);
if (!fb_id) {
gbm_surface_release_buffer(platform.gbmSurface, next_bo);
errCnt[2]++;
return;
}
// Attempt page flip
/* rmModePageFlip() schedules a buffer-flip for the next vblank and then
* notifies us about it. It takes a CRTC-id, fb-id and an arbitrary
* data-pointer and then schedules the page-flip. This is fully asynchronous and
* When the page-flip happens, the DRM-fd will become readable and we can call
* drmHandleEvent(). This will read all vblank/page-flip events and call our
* modeset_page_flip_event() callback with the data-pointer that we passed to
* drmModePageFlip(). We simply call modeset_draw_dev() then so the next frame
* is rendered..
* returns immediately.
*/
if (drmModePageFlip(platform.fd, platform.crtc->crtc_id, fb_id,
DRM_MODE_PAGE_FLIP_EVENT, platform.prevBO)) {
if (errno == EBUSY) {
//Display busy - skip flip
errCnt[3]++;
} else {
//Page flip failed
errCnt[4]++;
}
gbm_surface_release_buffer(platform.gbmSurface, next_bo);
return;
}
// Success: update state
pendingFlip = true;
platform.prevBO = next_bo;
//successful usage, do benchmarking
//in every 10 sec, at 60FPS 60*10 -> 600
if(loopCnt >= 600) {
TRACELOG(LOG_INFO, "DRM err counters: %d, %d, %d, %d, %d, %d",errCnt[0],errCnt[1],errCnt[2],errCnt[3],errCnt[4], loopCnt);
//reinit the errors
for(int i=0;i<5;i++) {
errCnt[i] = 0;
}
loopCnt = 0;
}
}
//----------------------------------------------------------------------------------
@ -910,7 +1099,8 @@ int InitPlatform(void)
EGL_BLUE_SIZE, 8, // BLUE color bit depth (alternative: 5)
EGL_ALPHA_SIZE, 8, // ALPHA bit depth (required for transparent framebuffer)
//EGL_TRANSPARENT_TYPE, EGL_NONE, // Request transparent framebuffer (EGL_TRANSPARENT_RGB does not work on RPI)
EGL_DEPTH_SIZE, 24, // Depth buffer size (Required to use Depth testing!)
//ToDo: verify this. In 5.5 it is 16, in master it was 24
EGL_DEPTH_SIZE, 16, // Depth buffer size (Required to use Depth testing!)
//EGL_STENCIL_SIZE, 8, // Stencil buffer size
EGL_SAMPLE_BUFFERS, sampleBuffer, // Activate MSAA
EGL_SAMPLES, samples, // 4x Antialiasing if activated (Free on MALI GPUs)
@ -1083,9 +1273,14 @@ int InitPlatform(void)
CORE.Storage.basePath = GetWorkingDirectory();
//----------------------------------------------------------------------------
TRACELOG(LOG_INFO, "PLATFORM: DRM: Initialized successfully");
if(InitSwapScreenBuffer() == 0) {
TRACELOG(LOG_INFO, "PLATFORM: DRM: Initialized successfully");
return 0;
} else {
TRACELOG(LOG_INFO, "PLATFORM: DRM: Initialized failed");
return -1;
}
return 0;
}
// Close platform

Laden…
Annuleren
Opslaan