diff --git a/src/platforms/rcore_drm.c b/src/platforms/rcore_drm.c index 3ec3135e1..9aebe6cf0 100644 --- a/src/platforms/rcore_drm.c +++ b/src/platforms/rcore_drm.c @@ -71,6 +71,21 @@ #include "EGL/egl.h" // Native platform windowing system interface #include "EGL/eglext.h" // EGL extensions +#include // for drmHandleEvent poll +#include //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