From c9f9219fa62d117eedb146d216f11f63aade30a7 Mon Sep 17 00:00:00 2001 From: Ray Date: Sat, 26 Jul 2025 12:49:49 +0200 Subject: [PATCH] REVIEWED: DRM cache buffers support #4988 --- src/platforms/rcore_drm.c | 269 +++++++++++++++++++------------------- 1 file changed, 137 insertions(+), 132 deletions(-) diff --git a/src/platforms/rcore_drm.c b/src/platforms/rcore_drm.c index a5c36977e..a3051f485 100644 --- a/src/platforms/rcore_drm.c +++ b/src/platforms/rcore_drm.c @@ -48,11 +48,11 @@ * **********************************************************************************************/ -#include // POSIX file control definitions - open(), creat(), fcntl() -#include // POSIX standard function definitions - read(), close(), STDIN_FILENO -#include // POSIX terminal control definitions - tcgetattr(), tcsetattr() -#include // POSIX threads management (inputs reading) -#include // POSIX directory browsing +#include // POSIX file control definitions - open(), creat(), fcntl() +#include // POSIX standard function definitions - read(), close(), STDIN_FILENO +#include // POSIX terminal control definitions - tcgetattr(), tcsetattr() +#include // POSIX threads management (inputs reading) +#include // POSIX directory browsing #include // Required for: ioctl() - UNIX System call for device-specific input/output operations #include // Linux: KDSKBMODE, K_MEDIUMRAM constants definition @@ -71,23 +71,13 @@ #include "EGL/egl.h" // Native platform windowing system interface #include "EGL/eglext.h" // EGL extensions +// NOTE: DRM cache enables triple buffered DRM caching #if defined(SUPPORT_DRM_CACHE) -#include // for drmHandleEvent poll -#include //for EBUSY, EAGAIN + #include // Required for: drmHandleEvent() poll + #include // Required for: EBUSY, EAGAIN -#define MAX_CACHED_BOS 3 - -typedef struct { - struct gbm_bo *bo; - uint32_t fbId; // DRM framebuffer ID -} FramebufferCache; - -static FramebufferCache fbCache[MAX_CACHED_BOS] = {0}; -static volatile int fbCacheCount = 0; -static volatile bool pendingFlip = false; -static bool crtcSet = false; - -#endif //SUPPORT_DRM_CACHE + #define MAX_DRM_CACHED_BUFFERS 3 +#endif // SUPPORT_DRM_CACHE #ifndef EGL_OPENGL_ES3_BIT #define EGL_OPENGL_ES3_BIT 0x40 @@ -96,18 +86,16 @@ static bool crtcSet = false; //---------------------------------------------------------------------------------- // Defines and Macros //---------------------------------------------------------------------------------- -#define USE_LAST_TOUCH_DEVICE // When multiple touchscreens are connected, only use the one with the highest event number +#define USE_LAST_TOUCH_DEVICE // When multiple touchscreens are connected, only use the one with the highest event number -#define DEFAULT_EVDEV_PATH "/dev/input/" // Path to the linux input events +#define DEFAULT_EVDEV_PATH "/dev/input/" // Path to the linux input events -// So actually the biggest key is KEY_CNT but we only really map the keys up to -// KEY_ALS_TOGGLE +// Actually biggest key is KEY_CNT but we only really map the keys up to KEY_ALS_TOGGLE #define KEYMAP_SIZE KEY_ALS_TOGGLE //---------------------------------------------------------------------------------- // Types and Structures Definition //---------------------------------------------------------------------------------- - typedef struct { // Display data int fd; // File descriptor for /dev/dri/... @@ -147,6 +135,18 @@ typedef struct { int gamepadCount; // The number of gamepads registered } PlatformData; +#if defined(SUPPORT_DRM_CACHE) +typedef struct { + struct gbm_bo *bo; // Graphics buffer object + uint32_t fbId; // DRM framebuffer ID +} FramebufferCache; + +static FramebufferCache fbCache[MAX_DRM_CACHED_BUFFERS] = { 0 }; +static volatile int fbCacheCount = 0; +static volatile bool pendingFlip = false; +static bool crtcSet = false; +#endif // SUPPORT_DRM_CACHE + //---------------------------------------------------------------------------------- // Global Variables Definition //---------------------------------------------------------------------------------- @@ -570,18 +570,23 @@ void DisableCursor(void) } #if defined(SUPPORT_DRM_CACHE) -//callback to destroy cached framebuffer, set by gbm_bo_set_user_data() -static void DestroyFrameBufferCallback(struct gbm_bo *bo, void *data) { + +// Destroy cached framebuffer callback, set by gbm_bo_set_user_data() +static void DestroyFrameBufferCallback(struct gbm_bo *bo, void *data) +{ uint32_t fbId = (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)fbId); + for (int i = 0; i < fbCacheCount; i++) + { + if (fbCache[i].bo == bo) + { + TRACELOG(LOG_INFO, "DISPLAY: DRM: Framebuffer removed [%u]", (uintptr_t)fbId); drmModeRmFB(platform.fd, fbCache[i].fbId); // Release DRM FB + // Shift remaining entries - for (int j = i; j < fbCacheCount - 1; j++) { - fbCache[j] = fbCache[j + 1]; - } + for (int j = i; j < fbCacheCount - 1; j++) fbCache[j] = fbCache[j + 1]; + fbCacheCount--; break; } @@ -589,56 +594,53 @@ static void DestroyFrameBufferCallback(struct gbm_bo *bo, void *data) { } // Create or retrieve cached DRM FB for BO -static uint32_t GetOrCreateFbForBo(struct gbm_bo *bo) { +static uint32_t GetOrCreateFbForBo(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].fbId; - } + for (int i = 0; i < fbCacheCount; i++) + { + if (fbCache[i].bo == bo) return fbCache[i].fbId; } // Create new entry if cache not full - if (fbCacheCount >= MAX_CACHED_BOS) { - //FB cache full! - return 0; - } + if (fbCacheCount >= MAX_DRM_CACHED_BUFFERS) return 0; // FB cache full 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 fbId; - if (drmModeAddFB(platform.fd, width, height, 24, 32, stride, handle, &fbId)) { - //rmModeAddFB failed - return 0; - } + uint32_t fbId = 0; + if (drmModeAddFB(platform.fd, width, height, 24, 32, stride, handle, &fbId)) return 0; // Store in cache fbCache[fbCacheCount] = (FramebufferCache){ .bo = bo, .fbId = fbId }; fbCacheCount++; // Set destroy callback to auto-cleanup - gbm_bo_set_user_data(bo, (void*)(uintptr_t)fbId, DestroyFrameBufferCallback); + gbm_bo_set_user_data(bo, (void *)(uintptr_t)fbId, DestroyFrameBufferCallback); + + TRACELOG(LOG_INFO, "DISPLAY: DRM: Added new buffer object [%u]" , (uintptr_t)fbId); - TRACELOG(LOG_INFO, "DRM: added new bo %u" , (uintptr_t)fbId); return fbId; } // Renders a blank frame to allocate initial buffers -void RenderBlankFrame() { +// TODO: WARNING: Platform layers do not include OpenGL code! +void RenderBlankFrame() +{ glClearColor(0, 0, 0, 1); glClear(GL_COLOR_BUFFER_BIT); eglSwapBuffers(platform.device, platform.surface); - - // Ensure the buffer is processed - glFinish(); + glFinish(); // Ensure the buffer is processed } // Initialize with first buffer only -int InitSwapScreenBuffer() { - if (!platform.gbmSurface || platform.fd < 0) { - TRACELOG(LOG_ERROR, "DRM not initialized"); +int InitSwapScreenBuffer() +{ + if (!platform.gbmSurface || (platform.fd < 0)) + { + TRACELOG(LOG_ERROR, "DISPLAY: DRM: Swap buffers can not be initialized"); return -1; } @@ -647,23 +649,24 @@ int InitSwapScreenBuffer() { // Get first buffer struct gbm_bo *bo = gbm_surface_lock_front_buffer(platform.gbmSurface); - if (!bo) { - TRACELOG(LOG_ERROR, "Failed to lock initial buffer"); + if (!bo) + { + TRACELOG(LOG_ERROR, "DISPLAY: DRM: Failed to lock initial swap buffer"); return -1; } // Create FB for first buffer uint32_t fbId = GetOrCreateFbForBo(bo); - if (!fbId) { + if (!fbId) + { gbm_surface_release_buffer(platform.gbmSurface, bo); return -1; } // Initial CRTC setup - if (drmModeSetCrtc(platform.fd, platform.crtc->crtc_id, fbId, - 0, 0, &platform.connector->connector_id, 1, - &platform.connector->modes[platform.modeIndex])) { - TRACELOG(LOG_ERROR, "Initial CRTC setup failed: %s", strerror(errno)); + if (drmModeSetCrtc(platform.fd, platform.crtc->crtc_id, fbId, 0, 0, &platform.connector->connector_id, 1, &platform.connector->modes[platform.modeIndex])) + { + TRACELOG(LOG_ERROR, "DISPLAY: DRM: Failed to initialize CRTC setup. ERROR: %s", strerror(errno)); gbm_surface_release_buffer(platform.gbmSurface, bo); return -1; } @@ -671,32 +674,38 @@ int InitSwapScreenBuffer() { // 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 PageFlipHandler(int fd, unsigned int frame, - unsigned int sec, unsigned int usec, - void *data) { - (void)fd; (void)frame; (void)sec; (void)usec; // Unused +// Static page flip handler +// NOTE: Called once the drmModePageFlip() finished from the drmHandleEvent() context +static void PageFlipHandler(int fd, unsigned int frame, unsigned int sec, unsigned int usec, void *data) +{ + // Unused inputs + (void)fd; + (void)frame; + (void)sec; + (void)usec; + 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. + struct gbm_bo *boToRelease = (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); - } + if (boToRelease) gbm_surface_release_buffer(platform.gbmSurface, boToRelease); } // Swap implementation with proper caching -void SwapScreenBuffer() { - static int loopCnt = 0; - loopCnt++; - static int errCnt[5] = {0}; +void SwapScreenBuffer() +{ if (!crtcSet || !platform.gbmSurface) return; - //call this only, if pendingFlip is not set + static int loopCnt = 0; + static int errCnt[5] = {0}; + loopCnt++; + + // Call this only, if pendingFlip is not set eglSwapBuffers(platform.device, platform.surface); // Process pending events non-blocking @@ -704,76 +713,68 @@ void SwapScreenBuffer() { .version = DRM_EVENT_CONTEXT_VERSION, .page_flip_handler = PageFlipHandler }; - + struct pollfd pfd = { .fd = platform.fd, .events = POLLIN }; - //polling for event for 0ms - while (poll(&pfd, 1, 0) > 0) { - drmHandleEvent(platform.fd, &evctx); - } + + // 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]++; + if (pendingFlip) + { + errCnt[0]++; // Skip frame: flip pending 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 + struct gbm_bo *nextBO = gbm_surface_lock_front_buffer(platform.gbmSurface); + if (!nextBO) // Failed to lock front buffer + { errCnt[1]++; return; } // Get FB ID (creates new one if needed) - uint32_t fbId = GetOrCreateFbForBo(next_bo); - if (!fbId) { - gbm_surface_release_buffer(platform.gbmSurface, next_bo); + uint32_t fbId = GetOrCreateFbForBo(nextBO); + if (!fbId) + { + gbm_surface_release_buffer(platform.gbmSurface, nextBO); 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, fbId, - 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); + // NOTE: 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, fbId, DRM_MODE_PAGE_FLIP_EVENT, platform.prevBO)) + { + if (errno == EBUSY) errCnt[3]++; // Display busy - skip flip + else errCnt[4]++; // Page flip failed + + gbm_surface_release_buffer(platform.gbmSurface, nextBO); 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; - } + platform.prevBO = nextBO; + +/* + // Some benchmarking code + if (loopCnt >= 600) + { + TRACELOG(LOG_INFO, "DRM: Error counters: %d, %d, %d, %d, %d, %d", errCnt[0], errCnt[1], errCnt[2], errCnt[3], errCnt[4], loopCnt); + for (int i = 0; i < 5; i++) errCnt[i] = 0; loopCnt = 0; } +*/ } -#else //SUPPORT_DRM_CACHE is not defined + +#else // !SUPPORT_DRM_CACHE + // Swap back buffer with front buffer (screen drawing) void SwapScreenBuffer(void) { @@ -803,7 +804,8 @@ void SwapScreenBuffer(void) platform.prevBO = bo; } -#endif //SUPPORT_DRM_CACHE +#endif // SUPPORT_DRM_CACHE + //---------------------------------------------------------------------------------- // Module Functions Definition: Misc //---------------------------------------------------------------------------------- @@ -1307,17 +1309,20 @@ int InitPlatform(void) //---------------------------------------------------------------------------- #if defined(SUPPORT_DRM_CACHE) - if(InitSwapScreenBuffer() == 0) { -#endif//SUPPORT_DRM_CACHE + if (InitSwapScreenBuffer() == 0) + { TRACELOG(LOG_INFO, "PLATFORM: DRM: Initialized successfully"); return 0; -#if defined(SUPPORT_DRM_CACHE) - } else { + } + else + { TRACELOG(LOG_INFO, "PLATFORM: DRM: Initialized failed"); return -1; } -#endif //SUPPORT_DRM_CACHE - +#else // !SUPPORT_DRM_CACHE + TRACELOG(LOG_INFO, "PLATFORM: DRM: Initialized successfully"); + return 0; +#endif } // Close platform