|
|
@ -48,11 +48,11 @@ |
|
|
|
* |
|
|
|
**********************************************************************************************/ |
|
|
|
|
|
|
|
#include <fcntl.h> // POSIX file control definitions - open(), creat(), fcntl() |
|
|
|
#include <unistd.h> // POSIX standard function definitions - read(), close(), STDIN_FILENO |
|
|
|
#include <termios.h> // POSIX terminal control definitions - tcgetattr(), tcsetattr() |
|
|
|
#include <pthread.h> // POSIX threads management (inputs reading) |
|
|
|
#include <dirent.h> // POSIX directory browsing |
|
|
|
#include <fcntl.h> // POSIX file control definitions - open(), creat(), fcntl() |
|
|
|
#include <unistd.h> // POSIX standard function definitions - read(), close(), STDIN_FILENO |
|
|
|
#include <termios.h> // POSIX terminal control definitions - tcgetattr(), tcsetattr() |
|
|
|
#include <pthread.h> // POSIX threads management (inputs reading) |
|
|
|
#include <dirent.h> // POSIX directory browsing |
|
|
|
|
|
|
|
#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 |
|
|
@ -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 <poll.h> // for drmHandleEvent poll |
|
|
|
#include <errno.h> //for EBUSY, EAGAIN |
|
|
|
#include <poll.h> // Required for: drmHandleEvent() poll |
|
|
|
#include <errno.h> // 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<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 |
|
|
|
// 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() { |
|
|
|
void SwapScreenBuffer() |
|
|
|
{ |
|
|
|
if (!crtcSet || !platform.gbmSurface) return; |
|
|
|
|
|
|
|
static int loopCnt = 0; |
|
|
|
loopCnt++; |
|
|
|
static int errCnt[5] = {0}; |
|
|
|
k">if (!crtcSet || !platform.gbmSurface) return; |
|
|
|
n">loopCnt++; |
|
|
|
|
|
|
|
//call this only, if pendingFlip is not set |
|
|
|
// 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 |
|
|
|