REVIEWED: DRM cache buffers support #4988

This commit is contained in:
Ray
2025-07-26 12:49:49 +02:00
parent 5c680739bd
commit c9f9219fa6

View File

@@ -48,11 +48,11 @@
* *
**********************************************************************************************/ **********************************************************************************************/
#include <fcntl.h> // POSIX file control definitions - open(), creat(), fcntl() #include <fcntl.h> // POSIX file control definitions - open(), creat(), fcntl()
#include <unistd.h> // POSIX standard function definitions - read(), close(), STDIN_FILENO #include <unistd.h> // POSIX standard function definitions - read(), close(), STDIN_FILENO
#include <termios.h> // POSIX terminal control definitions - tcgetattr(), tcsetattr() #include <termios.h> // POSIX terminal control definitions - tcgetattr(), tcsetattr()
#include <pthread.h> // POSIX threads management (inputs reading) #include <pthread.h> // POSIX threads management (inputs reading)
#include <dirent.h> // POSIX directory browsing #include <dirent.h> // POSIX directory browsing
#include <sys/ioctl.h> // Required for: ioctl() - UNIX System call for device-specific input/output operations #include <sys/ioctl.h> // Required for: ioctl() - UNIX System call for device-specific input/output operations
#include <linux/kd.h> // Linux: KDSKBMODE, K_MEDIUMRAM constants definition #include <linux/kd.h> // Linux: KDSKBMODE, K_MEDIUMRAM constants definition
@@ -71,23 +71,13 @@
#include "EGL/egl.h" // Native platform windowing system interface #include "EGL/egl.h" // Native platform windowing system interface
#include "EGL/eglext.h" // EGL extensions #include "EGL/eglext.h" // EGL extensions
// NOTE: DRM cache enables triple buffered DRM caching
#if defined(SUPPORT_DRM_CACHE) #if defined(SUPPORT_DRM_CACHE)
#include <poll.h> // for drmHandleEvent poll #include <poll.h> // Required for: drmHandleEvent() poll
#include <errno.h> //for EBUSY, EAGAIN #include <errno.h> // Required for: EBUSY, EAGAIN
#define MAX_CACHED_BOS 3 #define MAX_DRM_CACHED_BUFFERS 3
#endif // SUPPORT_DRM_CACHE
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
#ifndef EGL_OPENGL_ES3_BIT #ifndef EGL_OPENGL_ES3_BIT
#define EGL_OPENGL_ES3_BIT 0x40 #define EGL_OPENGL_ES3_BIT 0x40
@@ -96,18 +86,16 @@ static bool crtcSet = false;
//---------------------------------------------------------------------------------- //----------------------------------------------------------------------------------
// Defines and Macros // Defines and Macros
//---------------------------------------------------------------------------------- //----------------------------------------------------------------------------------
#define USE_LAST_TOUCH_DEVICE // When multiple touchscreens are connected, only use the one with the highest event<N> number #define USE_LAST_TOUCH_DEVICE // When multiple touchscreens are connected, only use the one with the highest event<N> 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 // Actually biggest key is KEY_CNT but we only really map the keys up to KEY_ALS_TOGGLE
// KEY_ALS_TOGGLE
#define KEYMAP_SIZE KEY_ALS_TOGGLE #define KEYMAP_SIZE KEY_ALS_TOGGLE
//---------------------------------------------------------------------------------- //----------------------------------------------------------------------------------
// Types and Structures Definition // Types and Structures Definition
//---------------------------------------------------------------------------------- //----------------------------------------------------------------------------------
typedef struct { typedef struct {
// Display data // Display data
int fd; // File descriptor for /dev/dri/... int fd; // File descriptor for /dev/dri/...
@@ -147,6 +135,18 @@ typedef struct {
int gamepadCount; // The number of gamepads registered int gamepadCount; // The number of gamepads registered
} PlatformData; } 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 // Global Variables Definition
//---------------------------------------------------------------------------------- //----------------------------------------------------------------------------------
@@ -570,18 +570,23 @@ void DisableCursor(void)
} }
#if defined(SUPPORT_DRM_CACHE) #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; uint32_t fbId = (uintptr_t)data;
// Remove from cache // Remove from cache
for (int i = 0; i < fbCacheCount; i++) { for (int i = 0; i < fbCacheCount; i++)
if (fbCache[i].bo == bo) { {
TRACELOG(LOG_INFO, "DRM: fb removed %u", (uintptr_t)fbId); 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 drmModeRmFB(platform.fd, fbCache[i].fbId); // Release DRM FB
// Shift remaining entries // Shift remaining entries
for (int j = i; j < fbCacheCount - 1; j++) { for (int j = i; j < fbCacheCount - 1; j++) fbCache[j] = fbCache[j + 1];
fbCache[j] = fbCache[j + 1];
}
fbCacheCount--; fbCacheCount--;
break; break;
} }
@@ -589,56 +594,53 @@ static void DestroyFrameBufferCallback(struct gbm_bo *bo, void *data) {
} }
// Create or retrieve cached DRM FB for BO // 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 // Try to find existing cache entry
for (int i = 0; i < fbCacheCount; i++) { for (int i = 0; i < fbCacheCount; i++)
if (fbCache[i].bo == bo) { {
return fbCache[i].fbId; if (fbCache[i].bo == bo) return fbCache[i].fbId;
}
} }
// Create new entry if cache not full // Create new entry if cache not full
if (fbCacheCount >= MAX_CACHED_BOS) { if (fbCacheCount >= MAX_DRM_CACHED_BUFFERS) return 0; // FB cache full
//FB cache full!
return 0;
}
uint32_t handle = gbm_bo_get_handle(bo).u32; uint32_t handle = gbm_bo_get_handle(bo).u32;
uint32_t stride = gbm_bo_get_stride(bo); uint32_t stride = gbm_bo_get_stride(bo);
uint32_t width = gbm_bo_get_width(bo); uint32_t width = gbm_bo_get_width(bo);
uint32_t height = gbm_bo_get_height(bo); uint32_t height = gbm_bo_get_height(bo);
uint32_t fbId; uint32_t fbId = 0;
if (drmModeAddFB(platform.fd, width, height, 24, 32, stride, handle, &fbId)) { if (drmModeAddFB(platform.fd, width, height, 24, 32, stride, handle, &fbId)) return 0;
//rmModeAddFB failed
return 0;
}
// Store in cache // Store in cache
fbCache[fbCacheCount] = (FramebufferCache){ .bo = bo, .fbId = fbId }; fbCache[fbCacheCount] = (FramebufferCache){ .bo = bo, .fbId = fbId };
fbCacheCount++; fbCacheCount++;
// Set destroy callback to auto-cleanup // 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; return fbId;
} }
// Renders a blank frame to allocate initial buffers // 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); glClearColor(0, 0, 0, 1);
glClear(GL_COLOR_BUFFER_BIT); glClear(GL_COLOR_BUFFER_BIT);
eglSwapBuffers(platform.device, platform.surface); eglSwapBuffers(platform.device, platform.surface);
glFinish(); // Ensure the buffer is processed
// Ensure the buffer is processed
glFinish();
} }
// Initialize with first buffer only // Initialize with first buffer only
int InitSwapScreenBuffer() { int InitSwapScreenBuffer()
if (!platform.gbmSurface || platform.fd < 0) { {
TRACELOG(LOG_ERROR, "DRM not initialized"); if (!platform.gbmSurface || (platform.fd < 0))
{
TRACELOG(LOG_ERROR, "DISPLAY: DRM: Swap buffers can not be initialized");
return -1; return -1;
} }
@@ -647,23 +649,24 @@ int InitSwapScreenBuffer() {
// Get first buffer // Get first buffer
struct gbm_bo *bo = gbm_surface_lock_front_buffer(platform.gbmSurface); struct gbm_bo *bo = gbm_surface_lock_front_buffer(platform.gbmSurface);
if (!bo) { if (!bo)
TRACELOG(LOG_ERROR, "Failed to lock initial buffer"); {
TRACELOG(LOG_ERROR, "DISPLAY: DRM: Failed to lock initial swap buffer");
return -1; return -1;
} }
// Create FB for first buffer // Create FB for first buffer
uint32_t fbId = GetOrCreateFbForBo(bo); uint32_t fbId = GetOrCreateFbForBo(bo);
if (!fbId) { if (!fbId)
{
gbm_surface_release_buffer(platform.gbmSurface, bo); gbm_surface_release_buffer(platform.gbmSurface, bo);
return -1; return -1;
} }
// Initial CRTC setup // Initial CRTC setup
if (drmModeSetCrtc(platform.fd, platform.crtc->crtc_id, fbId, if (drmModeSetCrtc(platform.fd, platform.crtc->crtc_id, fbId, 0, 0, &platform.connector->connector_id, 1, &platform.connector->modes[platform.modeIndex]))
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));
TRACELOG(LOG_ERROR, "Initial CRTC setup failed: %s", strerror(errno));
gbm_surface_release_buffer(platform.gbmSurface, bo); gbm_surface_release_buffer(platform.gbmSurface, bo);
return -1; return -1;
} }
@@ -671,32 +674,38 @@ int InitSwapScreenBuffer() {
// Keep first buffer locked until flipped // Keep first buffer locked until flipped
platform.prevBO = bo; platform.prevBO = bo;
crtcSet = true; crtcSet = true;
return 0; return 0;
} }
// Static page flip handler // Static page flip handler
// this will be called once the drmModePageFlip() finished from the drmHandleEvent(platform.fd, &evctx); context // NOTE: Called once the drmModePageFlip() finished from the drmHandleEvent() context
static void PageFlipHandler(int fd, unsigned int frame, static void PageFlipHandler(int fd, unsigned int frame, unsigned int sec, unsigned int usec, void *data)
unsigned int sec, unsigned int usec, {
void *data) { // Unused inputs
(void)fd; (void)frame; (void)sec; (void)usec; // Unused (void)fd;
(void)frame;
(void)sec;
(void)usec;
pendingFlip = false; pendingFlip = false;
struct gbm_bo *bo_to_release = (struct gbm_bo *)data; 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.
// 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 // Prevents the GPU from writing to a buffer being scanned out
if (bo_to_release) { if (boToRelease) gbm_surface_release_buffer(platform.gbmSurface, boToRelease);
gbm_surface_release_buffer(platform.gbmSurface, bo_to_release);
}
} }
// Swap implementation with proper caching // Swap implementation with proper caching
void SwapScreenBuffer() { void SwapScreenBuffer()
static int loopCnt = 0; {
loopCnt++;
static int errCnt[5] = {0};
if (!crtcSet || !platform.gbmSurface) return; 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); eglSwapBuffers(platform.device, platform.surface);
// Process pending events non-blocking // Process pending events non-blocking
@@ -704,76 +713,68 @@ void SwapScreenBuffer() {
.version = DRM_EVENT_CONTEXT_VERSION, .version = DRM_EVENT_CONTEXT_VERSION,
.page_flip_handler = PageFlipHandler .page_flip_handler = PageFlipHandler
}; };
struct pollfd pfd = { .fd = platform.fd, .events = POLLIN }; struct pollfd pfd = { .fd = platform.fd, .events = POLLIN };
//polling for event for 0ms
while (poll(&pfd, 1, 0) > 0) { // Polling for event for 0ms
drmHandleEvent(platform.fd, &evctx); while (poll(&pfd, 1, 0) > 0) drmHandleEvent(platform.fd, &evctx);
}
// Skip if previous flip pending // Skip if previous flip pending
if (pendingFlip) { if (pendingFlip)
//Skip frame: flip pending {
errCnt[0]++; errCnt[0]++; // Skip frame: flip pending
return; return;
} }
// Get new front buffer // Get new front buffer
struct gbm_bo *next_bo = gbm_surface_lock_front_buffer(platform.gbmSurface); struct gbm_bo *nextBO = gbm_surface_lock_front_buffer(platform.gbmSurface);
if (!next_bo) { if (!nextBO) // Failed to lock front buffer
//Failed to lock front buffer {
errCnt[1]++; errCnt[1]++;
return; return;
} }
// Get FB ID (creates new one if needed) // Get FB ID (creates new one if needed)
uint32_t fbId = GetOrCreateFbForBo(next_bo); uint32_t fbId = GetOrCreateFbForBo(nextBO);
if (!fbId) { if (!fbId)
gbm_surface_release_buffer(platform.gbmSurface, next_bo); {
gbm_surface_release_buffer(platform.gbmSurface, nextBO);
errCnt[2]++; errCnt[2]++;
return; return;
} }
// Attempt page flip // Attempt page flip
/* rmModePageFlip() schedules a buffer-flip for the next vblank and then // NOTE: rmModePageFlip() schedules a buffer-flip for the next vblank and then notifies us about it.
* notifies us about it. It takes a CRTC-id, fb-id and an arbitrary // It takes a CRTC-id, fb-id and an arbitrary data-pointer and then schedules the page-flip.
* data-pointer and then schedules the page-flip. This is fully asynchronous and // This is fully asynchronous and when the page-flip happens, the DRM-fd will become readable and we can call drmHandleEvent().
* When the page-flip happens, the DRM-fd will become readable and we can call // 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().
* drmHandleEvent(). This will read all vblank/page-flip events and call our // We simply call modeset_draw_dev() then so the next frame is rendered... returns immediately.
* modeset_page_flip_event() callback with the data-pointer that we passed to if (drmModePageFlip(platform.fd, platform.crtc->crtc_id, fbId, DRM_MODE_PAGE_FLIP_EVENT, platform.prevBO))
* drmModePageFlip(). We simply call modeset_draw_dev() then so the next frame {
* is rendered.. if (errno == EBUSY) errCnt[3]++; // Display busy - skip flip
* returns immediately. else errCnt[4]++; // Page flip failed
*/
if (drmModePageFlip(platform.fd, platform.crtc->crtc_id, fbId, gbm_surface_release_buffer(platform.gbmSurface, nextBO);
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; return;
} }
// Success: update state // Success: update state
pendingFlip = true; pendingFlip = true;
platform.prevBO = nextBO;
platform.prevBO = next_bo;
//successful usage, do benchmarking /*
//in every 10 sec, at 60FPS 60*10 -> 600 // Some benchmarking code
if(loopCnt >= 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 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++) { for (int i = 0; i < 5; i++) errCnt[i] = 0;
errCnt[i] = 0;
}
loopCnt = 0; loopCnt = 0;
} }
*/
} }
#else //SUPPORT_DRM_CACHE is not defined
#else // !SUPPORT_DRM_CACHE
// Swap back buffer with front buffer (screen drawing) // Swap back buffer with front buffer (screen drawing)
void SwapScreenBuffer(void) void SwapScreenBuffer(void)
{ {
@@ -803,7 +804,8 @@ void SwapScreenBuffer(void)
platform.prevBO = bo; platform.prevBO = bo;
} }
#endif //SUPPORT_DRM_CACHE #endif // SUPPORT_DRM_CACHE
//---------------------------------------------------------------------------------- //----------------------------------------------------------------------------------
// Module Functions Definition: Misc // Module Functions Definition: Misc
//---------------------------------------------------------------------------------- //----------------------------------------------------------------------------------
@@ -1307,17 +1309,20 @@ int InitPlatform(void)
//---------------------------------------------------------------------------- //----------------------------------------------------------------------------
#if defined(SUPPORT_DRM_CACHE) #if defined(SUPPORT_DRM_CACHE)
if(InitSwapScreenBuffer() == 0) { if (InitSwapScreenBuffer() == 0)
#endif//SUPPORT_DRM_CACHE {
TRACELOG(LOG_INFO, "PLATFORM: DRM: Initialized successfully"); TRACELOG(LOG_INFO, "PLATFORM: DRM: Initialized successfully");
return 0; return 0;
#if defined(SUPPORT_DRM_CACHE) }
} else { else
{
TRACELOG(LOG_INFO, "PLATFORM: DRM: Initialized failed"); TRACELOG(LOG_INFO, "PLATFORM: DRM: Initialized failed");
return -1; return -1;
} }
#endif //SUPPORT_DRM_CACHE #else // !SUPPORT_DRM_CACHE
TRACELOG(LOG_INFO, "PLATFORM: DRM: Initialized successfully");
return 0;
#endif
} }
// Close platform // Close platform