|
|
@ -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 |
|
|
@ -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 |
|
|
|