Skip to content

[rcore][drm] Replace DRM swap buffer implementation with asynchronous page-flipping and triple framebuffer caching #4988

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
235 changes: 215 additions & 20 deletions src/platforms/rcore_drm.c
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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

Expand Down Expand Up @@ -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;
}
}

//----------------------------------------------------------------------------------
Expand Down Expand Up @@ -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
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, that's correct, it was increased to address some z-fighting isssues. Afaik, most devies already support 24bit depth buffers.

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)
Expand Down Expand Up @@ -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
Expand Down