Skip to content

Commit 86d9afc

Browse files
authored
Merge pull request #4988 from rob-bits/master
[rcore][drm] Replace DRM swap buffer implementation with asynchronous page-flipping and triple framebuffer caching
2 parents 715e174 + 8388160 commit 86d9afc

File tree

2 files changed

+240
-3
lines changed

2 files changed

+240
-3
lines changed

src/config.h

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -299,4 +299,9 @@
299299
//------------------------------------------------------------------------------------
300300
#define MAX_TRACELOG_MSG_LENGTH 256 // Max length of one trace-log message
301301

302+
//DRM configuration
303+
#if defined(PLATFORM_DRM)
304+
//#define SUPPORT_DRM_CACHE 1 //enable triple buffered DRM caching
305+
#endif //PLATFORM_DRM
306+
302307
#endif // CONFIG_H

src/platforms/rcore_drm.c

Lines changed: 235 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,24 @@
7171
#include "EGL/egl.h" // Native platform windowing system interface
7272
#include "EGL/eglext.h" // EGL extensions
7373

74+
#if defined(SUPPORT_DRM_CACHE)
75+
#include <poll.h> // for drmHandleEvent poll
76+
#include <errno.h> //for EBUSY, EAGAIN
77+
78+
#define MAX_CACHED_BOS 3
79+
80+
typedef struct {
81+
struct gbm_bo *bo;
82+
uint32_t fbId; // DRM framebuffer ID
83+
} FramebufferCache;
84+
85+
static FramebufferCache fbCache[MAX_CACHED_BOS] = {0};
86+
static volatile int fbCacheCount = 0;
87+
static volatile bool pendingFlip = false;
88+
static bool crtcSet = false;
89+
90+
#endif //SUPPORT_DRM_CACHE
91+
7492
#ifndef EGL_OPENGL_ES3_BIT
7593
#define EGL_OPENGL_ES3_BIT 0x40
7694
#endif
@@ -551,6 +569,211 @@ void DisableCursor(void)
551569
CORE.Input.Mouse.cursorHidden = true;
552570
}
553571

572+
#if defined(SUPPORT_DRM_CACHE)
573+
//callback to destroy cached framebuffer, set by gbm_bo_set_user_data()
574+
static void DestroyFrameBufferCallback(struct gbm_bo *bo, void *data) {
575+
uint32_t fbId = (uintptr_t)data;
576+
// Remove from cache
577+
for (int i = 0; i < fbCacheCount; i++) {
578+
if (fbCache[i].bo == bo) {
579+
TRACELOG(LOG_INFO, "DRM: fb removed %u", (uintptr_t)fbId);
580+
drmModeRmFB(platform.fd, fbCache[i].fbId); // Release DRM FB
581+
// Shift remaining entries
582+
for (int j = i; j < fbCacheCount - 1; j++) {
583+
fbCache[j] = fbCache[j + 1];
584+
}
585+
fbCacheCount--;
586+
break;
587+
}
588+
}
589+
}
590+
591+
// Create or retrieve cached DRM FB for BO
592+
static uint32_t GetOrCreateFbForBo(struct gbm_bo *bo) {
593+
// Try to find existing cache entry
594+
for (int i = 0; i < fbCacheCount; i++) {
595+
if (fbCache[i].bo == bo) {
596+
return fbCache[i].fbId;
597+
}
598+
}
599+
600+
// Create new entry if cache not full
601+
if (fbCacheCount >= MAX_CACHED_BOS) {
602+
//FB cache full!
603+
return 0;
604+
}
605+
606+
uint32_t handle = gbm_bo_get_handle(bo).u32;
607+
uint32_t stride = gbm_bo_get_stride(bo);
608+
uint32_t width = gbm_bo_get_width(bo);
609+
uint32_t height = gbm_bo_get_height(bo);
610+
611+
uint32_t fbId;
612+
if (drmModeAddFB(platform.fd, width, height, 24, 32, stride, handle, &fbId)) {
613+
//rmModeAddFB failed
614+
return 0;
615+
}
616+
617+
// Store in cache
618+
fbCache[fbCacheCount] = (FramebufferCache){ .bo = bo, .fbId = fbId };
619+
fbCacheCount++;
620+
621+
// Set destroy callback to auto-cleanup
622+
gbm_bo_set_user_data(bo, (void*)(uintptr_t)fbId, DestroyFrameBufferCallback);
623+
624+
TRACELOG(LOG_INFO, "DRM: added new bo %u" , (uintptr_t)fbId);
625+
return fbId;
626+
}
627+
628+
// Renders a blank frame to allocate initial buffers
629+
void RenderBlankFrame() {
630+
glClearColor(0, 0, 0, 1);
631+
glClear(GL_COLOR_BUFFER_BIT);
632+
eglSwapBuffers(platform.device, platform.surface);
633+
634+
// Ensure the buffer is processed
635+
glFinish();
636+
}
637+
638+
// Initialize with first buffer only
639+
int InitSwapScreenBuffer() {
640+
if (!platform.gbmSurface || platform.fd < 0) {
641+
TRACELOG(LOG_ERROR, "DRM not initialized");
642+
return -1;
643+
}
644+
645+
// Render a blank frame to allocate buffers
646+
RenderBlankFrame();
647+
648+
// Get first buffer
649+
struct gbm_bo *bo = gbm_surface_lock_front_buffer(platform.gbmSurface);
650+
if (!bo) {
651+
TRACELOG(LOG_ERROR, "Failed to lock initial buffer");
652+
return -1;
653+
}
654+
655+
// Create FB for first buffer
656+
uint32_t fbId = GetOrCreateFbForBo(bo);
657+
if (!fbId) {
658+
gbm_surface_release_buffer(platform.gbmSurface, bo);
659+
return -1;
660+
}
661+
662+
// Initial CRTC setup
663+
if (drmModeSetCrtc(platform.fd, platform.crtc->crtc_id, fbId,
664+
0, 0, &platform.connector->connector_id, 1,
665+
&platform.connector->modes[platform.modeIndex])) {
666+
TRACELOG(LOG_ERROR, "Initial CRTC setup failed: %s", strerror(errno));
667+
gbm_surface_release_buffer(platform.gbmSurface, bo);
668+
return -1;
669+
}
670+
671+
// Keep first buffer locked until flipped
672+
platform.prevBO = bo;
673+
crtcSet = true;
674+
return 0;
675+
}
676+
677+
// Static page flip handler
678+
// this will be called once the drmModePageFlip() finished from the drmHandleEvent(platform.fd, &evctx); context
679+
static void PageFlipHandler(int fd, unsigned int frame,
680+
unsigned int sec, unsigned int usec,
681+
void *data) {
682+
(void)fd; (void)frame; (void)sec; (void)usec; // Unused
683+
pendingFlip = false;
684+
struct gbm_bo *bo_to_release = (struct gbm_bo *)data;
685+
//Buffers are released after the flip completes (via page_flip_handler), ensuring they're no longer in use.
686+
// Prevents the GPU from writing to a buffer being scanned out
687+
if (bo_to_release) {
688+
gbm_surface_release_buffer(platform.gbmSurface, bo_to_release);
689+
}
690+
}
691+
692+
// Swap implementation with proper caching
693+
void SwapScreenBuffer() {
694+
static int loopCnt = 0;
695+
loopCnt++;
696+
static int errCnt[5] = {0};
697+
if (!crtcSet || !platform.gbmSurface) return;
698+
699+
//call this only, if pendingFlip is not set
700+
eglSwapBuffers(platform.device, platform.surface);
701+
702+
// Process pending events non-blocking
703+
drmEventContext evctx = {
704+
.version = DRM_EVENT_CONTEXT_VERSION,
705+
.page_flip_handler = PageFlipHandler
706+
};
707+
708+
struct pollfd pfd = { .fd = platform.fd, .events = POLLIN };
709+
//polling for event for 0ms
710+
while (poll(&pfd, 1, 0) > 0) {
711+
drmHandleEvent(platform.fd, &evctx);
712+
}
713+
714+
// Skip if previous flip pending
715+
if (pendingFlip) {
716+
//Skip frame: flip pending
717+
errCnt[0]++;
718+
return;
719+
}
720+
721+
// Get new front buffer
722+
struct gbm_bo *next_bo = gbm_surface_lock_front_buffer(platform.gbmSurface);
723+
if (!next_bo) {
724+
//Failed to lock front buffer
725+
errCnt[1]++;
726+
return;
727+
}
728+
729+
// Get FB ID (creates new one if needed)
730+
uint32_t fbId = GetOrCreateFbForBo(next_bo);
731+
if (!fbId) {
732+
gbm_surface_release_buffer(platform.gbmSurface, next_bo);
733+
errCnt[2]++;
734+
return;
735+
}
736+
737+
// Attempt page flip
738+
/* rmModePageFlip() schedules a buffer-flip for the next vblank and then
739+
* notifies us about it. It takes a CRTC-id, fb-id and an arbitrary
740+
* data-pointer and then schedules the page-flip. This is fully asynchronous and
741+
* When the page-flip happens, the DRM-fd will become readable and we can call
742+
* drmHandleEvent(). This will read all vblank/page-flip events and call our
743+
* modeset_page_flip_event() callback with the data-pointer that we passed to
744+
* drmModePageFlip(). We simply call modeset_draw_dev() then so the next frame
745+
* is rendered..
746+
* returns immediately.
747+
*/
748+
if (drmModePageFlip(platform.fd, platform.crtc->crtc_id, fbId,
749+
DRM_MODE_PAGE_FLIP_EVENT, platform.prevBO)) {
750+
if (errno == EBUSY) {
751+
//Display busy - skip flip
752+
errCnt[3]++;
753+
} else {
754+
//Page flip failed
755+
errCnt[4]++;
756+
}
757+
gbm_surface_release_buffer(platform.gbmSurface, next_bo);
758+
return;
759+
}
760+
761+
// Success: update state
762+
pendingFlip = true;
763+
764+
platform.prevBO = next_bo;
765+
//successful usage, do benchmarking
766+
//in every 10 sec, at 60FPS 60*10 -> 600
767+
if(loopCnt >= 600) {
768+
TRACELOG(LOG_INFO, "DRM err counters: %d, %d, %d, %d, %d, %d",errCnt[0],errCnt[1],errCnt[2],errCnt[3],errCnt[4], loopCnt);
769+
//reinit the errors
770+
for(int i=0;i<5;i++) {
771+
errCnt[i] = 0;
772+
}
773+
loopCnt = 0;
774+
}
775+
}
776+
#else //SUPPORT_DRM_CACHE is not defined
554777
// Swap back buffer with front buffer (screen drawing)
555778
void SwapScreenBuffer(void)
556779
{
@@ -580,7 +803,7 @@ void SwapScreenBuffer(void)
580803

581804
platform.prevBO = bo;
582805
}
583-
806+
#endif //SUPPORT_DRM_CACHE
584807
//----------------------------------------------------------------------------------
585808
// Module Functions Definition: Misc
586809
//----------------------------------------------------------------------------------
@@ -1083,9 +1306,18 @@ int InitPlatform(void)
10831306
CORE.Storage.basePath = GetWorkingDirectory();
10841307
//----------------------------------------------------------------------------
10851308

1086-
TRACELOG(LOG_INFO, "PLATFORM: DRM: Initialized successfully");
1309+
#if defined(SUPPORT_DRM_CACHE)
1310+
if(InitSwapScreenBuffer() == 0) {
1311+
#endif//SUPPORT_DRM_CACHE
1312+
TRACELOG(LOG_INFO, "PLATFORM: DRM: Initialized successfully");
1313+
return 0;
1314+
#if defined(SUPPORT_DRM_CACHE)
1315+
} else {
1316+
TRACELOG(LOG_INFO, "PLATFORM: DRM: Initialized failed");
1317+
return -1;
1318+
}
1319+
#endif //SUPPORT_DRM_CACHE
10871320

1088-
return 0;
10891321
}
10901322

10911323
// Close platform

0 commit comments

Comments
 (0)