Fixed bug 4966 - KMSDRM: Add dynamic modeset support

Anthony Pesch

* Remove triple buffering support. As far as I can tell, this goes against the libdrm API; the EGL implementations themselves control the buffering. Removing it isn't absolutely necessary as it seemingly works on the Pi at least, but I noticed this while doing my work and explained my reasoning in the commit.

* Replace the crtc_ready logic which allocates an extra bo to perform the initial CRTC configuration (which is required before calling drmModePageFlip) with a call to drmModeSetCrtc after the front and back buffers are allocated, avoiding this allocation.

* Standardized the SDL_*Data variable names and null checks to improve readability. Given that there were duplicate fields in each SDL_*Data structure, having generic names such as "data" at times was very confusing.

* Removed unused fields from the SDL_*Data structures and moves all display related fields out of SDL_VideoData and into SDL_DisplayData. Not required since the code only supports a single display right now, but this was helpful in reading and understanding the code initially.

* Implement KMSDRM_GetDisplayModes / KMSDRM_SetDisplayMode to provide dynamic modeset support.

These changes have been tested on a Raspberry Pi 4 and a Dell XPS laptop with an HD 520.

As an update, I went back over the triple buffer changes and left them in. I didn't entirely get the code originally, I had just seen it calling KMSDRM_gbm_surface_lock_front_buffer twice for a single swap and had removed it because I was paranoid of bugs stemming from it while working on the modeset changes.

I've made a few small changes to the logic that had thrown me off originally and rebased the changes:
* The condition wrapping the call to release buffer was incorrect.
* The first call to KMSDRM_gbm_surface_lock_front_buffer has been removed. I don't understand why it existed.
* Added additional comments describing what was going on in the code (as it does fix the buffer release pattern of the original code before it).
Sam Lantinga 2020-02-09 11:44:22 -08:00
parent 64c58b9f19
commit 3e935aecb9
4 changed files with 533 additions and 422 deletions

View File

@ -49,19 +49,21 @@ static SDL_bool
KMSDRM_IsCursorSizeSupported (int w, int h, uint32_t bo_format) { KMSDRM_IsCursorSizeSupported (int w, int h, uint32_t bo_format) {
SDL_VideoDevice *dev = SDL_GetVideoDevice(); SDL_VideoDevice *dev = SDL_GetVideoDevice();
SDL_VideoData *vdata = ((SDL_VideoData *)dev->driverdata); SDL_VideoData *viddata = ((SDL_VideoData *)dev->driverdata);
SDL_DisplayData *dispdata = (SDL_DisplayData *)SDL_GetDisplayDriverData(0);
int ret; int ret;
uint32_t bo_handle; uint32_t bo_handle;
struct gbm_bo *bo = KMSDRM_gbm_bo_create(vdata->gbm, w, h, bo_format, struct gbm_bo *bo = KMSDRM_gbm_bo_create(viddata->gbm, w, h, bo_format,
GBM_BO_USE_CURSOR | GBM_BO_USE_WRITE); GBM_BO_USE_CURSOR | GBM_BO_USE_WRITE);
if (bo == NULL) { if (!bo) {
SDL_SetError("Could not create GBM cursor BO width size %dx%d for size testing", w, h); SDL_SetError("Could not create GBM cursor BO width size %dx%d for size testing", w, h);
goto cleanup; goto cleanup;
} }
bo_handle = KMSDRM_gbm_bo_get_handle(bo).u32; bo_handle = KMSDRM_gbm_bo_get_handle(bo).u32;
ret = KMSDRM_drmModeSetCursor(vdata->drm_fd, vdata->crtc_id, bo_handle, w, h); ret = KMSDRM_drmModeSetCursor(viddata->drm_fd, dispdata->crtc_id, bo_handle, w, h);
if (ret) { if (ret) {
goto cleanup; goto cleanup;
@ -72,7 +74,7 @@ KMSDRM_IsCursorSizeSupported (int w, int h, uint32_t bo_format) {
} }
cleanup: cleanup:
if (bo != NULL) { if (bo) {
KMSDRM_gbm_bo_destroy(bo); KMSDRM_gbm_bo_destroy(bo);
} }
return SDL_FALSE; return SDL_FALSE;
@ -83,7 +85,7 @@ static SDL_Cursor *
KMSDRM_CreateCursor(SDL_Surface * surface, int hot_x, int hot_y) KMSDRM_CreateCursor(SDL_Surface * surface, int hot_x, int hot_y)
{ {
SDL_VideoDevice *dev = SDL_GetVideoDevice(); SDL_VideoDevice *dev = SDL_GetVideoDevice();
SDL_VideoData *vdata = ((SDL_VideoData *)dev->driverdata); SDL_VideoData *viddata = ((SDL_VideoData *)dev->driverdata);
SDL_PixelFormat *pixlfmt = surface->format; SDL_PixelFormat *pixlfmt = surface->format;
KMSDRM_CursorData *curdata; KMSDRM_CursorData *curdata;
SDL_Cursor *cursor; SDL_Cursor *cursor;
@ -161,18 +163,18 @@ KMSDRM_CreateCursor(SDL_Surface * surface, int hot_x, int hot_y)
return NULL; return NULL;
} }
if (!KMSDRM_gbm_device_is_format_supported(vdata->gbm, bo_format, GBM_BO_USE_CURSOR | GBM_BO_USE_WRITE)) { if (!KMSDRM_gbm_device_is_format_supported(viddata->gbm, bo_format, GBM_BO_USE_CURSOR | GBM_BO_USE_WRITE)) {
SDL_SetError("Unsupported pixel format for cursor"); SDL_SetError("Unsupported pixel format for cursor");
return NULL; return NULL;
} }
cursor = (SDL_Cursor *) SDL_calloc(1, sizeof(*cursor)); cursor = (SDL_Cursor *) SDL_calloc(1, sizeof(*cursor));
if (cursor == NULL) { if (!cursor) {
SDL_OutOfMemory(); SDL_OutOfMemory();
return NULL; return NULL;
} }
curdata = (KMSDRM_CursorData *) SDL_calloc(1, sizeof(*curdata)); curdata = (KMSDRM_CursorData *) SDL_calloc(1, sizeof(*curdata));
if (curdata == NULL) { if (!curdata) {
SDL_OutOfMemory(); SDL_OutOfMemory();
SDL_free(cursor); SDL_free(cursor);
return NULL; return NULL;
@ -205,10 +207,10 @@ KMSDRM_CreateCursor(SDL_Surface * surface, int hot_x, int hot_y)
curdata->w = usable_cursor_w; curdata->w = usable_cursor_w;
curdata->h = usable_cursor_h; curdata->h = usable_cursor_h;
curdata->bo = KMSDRM_gbm_bo_create(vdata->gbm, usable_cursor_w, usable_cursor_h, bo_format, curdata->bo = KMSDRM_gbm_bo_create(viddata->gbm, usable_cursor_w, usable_cursor_h, bo_format,
GBM_BO_USE_CURSOR | GBM_BO_USE_WRITE); GBM_BO_USE_CURSOR | GBM_BO_USE_WRITE);
if (curdata->bo == NULL) { if (!curdata->bo) {
SDL_SetError("Could not create GBM cursor BO"); SDL_SetError("Could not create GBM cursor BO");
goto cleanup; goto cleanup;
} }
@ -219,7 +221,7 @@ KMSDRM_CreateCursor(SDL_Surface * surface, int hot_x, int hot_y)
if (surface->pitch != bo_stride) { if (surface->pitch != bo_stride) {
/* pitch doesn't match stride, must be copied to temp buffer */ /* pitch doesn't match stride, must be copied to temp buffer */
buffer = SDL_malloc(bufsize); buffer = SDL_malloc(bufsize);
if (buffer == NULL) { if (!buffer) {
SDL_OutOfMemory(); SDL_OutOfMemory();
goto cleanup; goto cleanup;
} }
@ -279,14 +281,14 @@ KMSDRM_CreateCursor(SDL_Surface * surface, int hot_x, int hot_y)
return cursor; return cursor;
cleanup: cleanup:
if (buffer != NULL) { if (buffer) {
SDL_free(buffer); SDL_free(buffer);
} }
if (cursor != NULL) { if (cursor) {
SDL_free(cursor); SDL_free(cursor);
} }
if (curdata != NULL) { if (curdata) {
if (curdata->bo != NULL) { if (curdata->bo) {
KMSDRM_gbm_bo_destroy(curdata->bo); KMSDRM_gbm_bo_destroy(curdata->bo);
} }
SDL_free(curdata); SDL_free(curdata);
@ -299,33 +301,33 @@ static int
KMSDRM_ShowCursor(SDL_Cursor * cursor) KMSDRM_ShowCursor(SDL_Cursor * cursor)
{ {
SDL_VideoDevice *dev = SDL_GetVideoDevice(); SDL_VideoDevice *dev = SDL_GetVideoDevice();
SDL_VideoData *vdata = ((SDL_VideoData *)dev->driverdata); SDL_VideoData *viddata = ((SDL_VideoData *)dev->driverdata);
SDL_Mouse *mouse; SDL_Mouse *mouse;
KMSDRM_CursorData *curdata; KMSDRM_CursorData *curdata;
SDL_VideoDisplay *display = NULL; SDL_VideoDisplay *display = NULL;
SDL_DisplayData *ddata = NULL; SDL_DisplayData *dispdata = NULL;
int ret; int ret;
uint32_t bo_handle; uint32_t bo_handle;
mouse = SDL_GetMouse(); mouse = SDL_GetMouse();
if (mouse == NULL) { if (!mouse) {
return SDL_SetError("No mouse."); return SDL_SetError("No mouse.");
} }
if (mouse->focus != NULL) { if (mouse->focus) {
display = SDL_GetDisplayForWindow(mouse->focus); display = SDL_GetDisplayForWindow(mouse->focus);
if (display != NULL) { if (display) {
ddata = (SDL_DisplayData*) display->driverdata; dispdata = (SDL_DisplayData*) display->driverdata;
} }
} }
if (cursor == NULL) { if (!cursor) {
/* Hide current cursor */ /* Hide current cursor */
if ( mouse->cur_cursor != NULL && mouse->cur_cursor->driverdata != NULL) { if (mouse->cur_cursor && mouse->cur_cursor->driverdata) {
curdata = (KMSDRM_CursorData *) mouse->cur_cursor->driverdata; curdata = (KMSDRM_CursorData *) mouse->cur_cursor->driverdata;
if (curdata->crtc_id != 0) { if (curdata->crtc_id != 0) {
ret = KMSDRM_drmModeSetCursor(vdata->drm_fd, curdata->crtc_id, 0, 0, 0); ret = KMSDRM_drmModeSetCursor(viddata->drm_fd, curdata->crtc_id, 0, 0, 0);
if (ret) { if (ret) {
SDL_SetError("Could not hide current cursor with drmModeSetCursor()."); SDL_SetError("Could not hide current cursor with drmModeSetCursor().");
return ret; return ret;
@ -337,8 +339,8 @@ KMSDRM_ShowCursor(SDL_Cursor * cursor)
} }
} }
/* otherwise if possible, hide global cursor */ /* otherwise if possible, hide global cursor */
if (ddata != NULL && ddata->crtc_id != 0) { if (dispdata && dispdata->crtc_id != 0) {
ret = KMSDRM_drmModeSetCursor(vdata->drm_fd, ddata->crtc_id, 0, 0, 0); ret = KMSDRM_drmModeSetCursor(viddata->drm_fd, dispdata->crtc_id, 0, 0, 0);
if (ret) { if (ret) {
SDL_SetError("Could not hide display's cursor with drmModeSetCursor()."); SDL_SetError("Could not hide display's cursor with drmModeSetCursor().");
return ret; return ret;
@ -349,33 +351,32 @@ KMSDRM_ShowCursor(SDL_Cursor * cursor)
return SDL_SetError("Couldn't find cursor to hide."); return SDL_SetError("Couldn't find cursor to hide.");
} }
/* If cursor != NULL, show new cursor on display */ /* If cursor != NULL, show new cursor on display */
if (display == NULL) { if (!display) {
return SDL_SetError("Could not get display for mouse."); return SDL_SetError("Could not get display for mouse.");
} }
if (ddata == NULL) { if (!dispdata) {
return SDL_SetError("Could not get display driverdata."); return SDL_SetError("Could not get display driverdata.");
} }
curdata = (KMSDRM_CursorData *) cursor->driverdata; curdata = (KMSDRM_CursorData *) cursor->driverdata;
if (curdata == NULL || curdata->bo == NULL) { if (!curdata || !curdata->bo) {
return SDL_SetError("Cursor not initialized properly."); return SDL_SetError("Cursor not initialized properly.");
} }
bo_handle = KMSDRM_gbm_bo_get_handle(curdata->bo).u32; bo_handle = KMSDRM_gbm_bo_get_handle(curdata->bo).u32;
if (curdata->hot_x == 0 && curdata->hot_y == 0) { if (curdata->hot_x == 0 && curdata->hot_y == 0) {
ret = KMSDRM_drmModeSetCursor(vdata->drm_fd, ddata->crtc_id, bo_handle, ret = KMSDRM_drmModeSetCursor(viddata->drm_fd, dispdata->crtc_id, bo_handle,
curdata->w, curdata->h); curdata->w, curdata->h);
} else { } else {
ret = KMSDRM_drmModeSetCursor2(vdata->drm_fd, ddata->crtc_id, bo_handle, ret = KMSDRM_drmModeSetCursor2(viddata->drm_fd, dispdata->crtc_id, bo_handle,
curdata->w, curdata->h, curdata->w, curdata->h, curdata->hot_x, curdata->hot_y);
curdata->hot_x, curdata->hot_y);
} }
if (ret) { if (ret) {
SDL_SetError("drmModeSetCursor failed."); SDL_SetError("drmModeSetCursor failed.");
return ret; return ret;
} }
curdata->crtc_id = ddata->crtc_id; curdata->crtc_id = dispdata->crtc_id;
return 0; return 0;
} }
@ -387,11 +388,11 @@ KMSDRM_FreeCursor(SDL_Cursor * cursor)
KMSDRM_CursorData *curdata; KMSDRM_CursorData *curdata;
int drm_fd; int drm_fd;
if (cursor != NULL) { if (cursor) {
curdata = (KMSDRM_CursorData *) cursor->driverdata; curdata = (KMSDRM_CursorData *) cursor->driverdata;
if (curdata != NULL) { if (curdata) {
if (curdata->bo != NULL) { if (curdata->bo) {
if (curdata->crtc_id != 0) { if (curdata->crtc_id != 0) {
drm_fd = KMSDRM_gbm_device_get_fd(KMSDRM_gbm_bo_get_device(curdata->bo)); drm_fd = KMSDRM_gbm_device_get_fd(KMSDRM_gbm_bo_get_device(curdata->bo));
/* Hide the cursor if previously shown on a CRTC */ /* Hide the cursor if previously shown on a CRTC */
@ -422,13 +423,13 @@ KMSDRM_WarpMouseGlobal(int x, int y)
KMSDRM_CursorData *curdata; KMSDRM_CursorData *curdata;
SDL_Mouse *mouse = SDL_GetMouse(); SDL_Mouse *mouse = SDL_GetMouse();
if (mouse != NULL && mouse->cur_cursor != NULL && mouse->cur_cursor->driverdata != NULL) { if (mouse && mouse->cur_cursor && mouse->cur_cursor->driverdata) {
/* Update internal mouse position. */ /* Update internal mouse position. */
SDL_SendMouseMotion(mouse->focus, mouse->mouseID, 0, x, y); SDL_SendMouseMotion(mouse->focus, mouse->mouseID, 0, x, y);
/* And now update the cursor graphic position on screen. */ /* And now update the cursor graphic position on screen. */
curdata = (KMSDRM_CursorData *) mouse->cur_cursor->driverdata; curdata = (KMSDRM_CursorData *) mouse->cur_cursor->driverdata;
if (curdata->bo != NULL) { if (curdata->bo) {
if (curdata->crtc_id != 0) { if (curdata->crtc_id != 0) {
int ret, drm_fd; int ret, drm_fd;
@ -485,7 +486,7 @@ KMSDRM_MoveCursor(SDL_Cursor * cursor)
/* We must NOT call SDL_SendMouseMotion() here or we will enter recursivity! /* We must NOT call SDL_SendMouseMotion() here or we will enter recursivity!
That's why we move the cursor graphic ONLY. */ That's why we move the cursor graphic ONLY. */
if (mouse != NULL && mouse->cur_cursor != NULL && mouse->cur_cursor->driverdata != NULL) { if (mouse && mouse->cur_cursor && mouse->cur_cursor->driverdata) {
curdata = (KMSDRM_CursorData *) mouse->cur_cursor->driverdata; curdata = (KMSDRM_CursorData *) mouse->cur_cursor->driverdata;
drm_fd = KMSDRM_gbm_device_get_fd(KMSDRM_gbm_bo_get_device(curdata->bo)); drm_fd = KMSDRM_gbm_device_get_fd(KMSDRM_gbm_bo_get_device(curdata->bo));
ret = KMSDRM_drmModeMoveCursor(drm_fd, curdata->crtc_id, mouse->x, mouse->y); ret = KMSDRM_drmModeMoveCursor(drm_fd, curdata->crtc_id, mouse->x, mouse->y);

View File

@ -42,40 +42,6 @@ KMSDRM_GLES_LoadLibrary(_THIS, const char *path) {
SDL_EGL_CreateContext_impl(KMSDRM) SDL_EGL_CreateContext_impl(KMSDRM)
SDL_bool
KMSDRM_GLES_SetupCrtc(_THIS, SDL_Window * window) {
SDL_WindowData *wdata = ((SDL_WindowData *) window->driverdata);
SDL_DisplayData *displaydata = (SDL_DisplayData *) SDL_GetDisplayForWindow(window)->driverdata;
SDL_VideoData *vdata = ((SDL_VideoData *)_this->driverdata);
KMSDRM_FBInfo *fb_info;
if (!(_this->egl_data->eglSwapBuffers(_this->egl_data->egl_display, wdata->egl_surface))) {
SDL_LogError(SDL_LOG_CATEGORY_VIDEO, "eglSwapBuffers failed on CRTC setup");
return SDL_FALSE;
}
wdata->crtc_bo = KMSDRM_gbm_surface_lock_front_buffer(wdata->gs);
if (wdata->crtc_bo == NULL) {
SDL_LogError(SDL_LOG_CATEGORY_VIDEO, "Could not lock GBM surface front buffer on CRTC setup");
return SDL_FALSE;
}
fb_info = KMSDRM_FBFromBO(_this, wdata->crtc_bo);
if (fb_info == NULL) {
return SDL_FALSE;
}
if(KMSDRM_drmModeSetCrtc(vdata->drm_fd, displaydata->crtc_id, fb_info->fb_id,
0, 0, &vdata->saved_conn_id, 1, &displaydata->cur_mode) != 0) {
SDL_LogWarn(SDL_LOG_CATEGORY_VIDEO, "Could not set up CRTC to a GBM buffer");
return SDL_FALSE;
}
wdata->crtc_ready = SDL_TRUE;
return SDL_TRUE;
}
int KMSDRM_GLES_SetSwapInterval(_THIS, int interval) { int KMSDRM_GLES_SetSwapInterval(_THIS, int interval) {
if (!_this->egl_data) { if (!_this->egl_data) {
return SDL_SetError("EGL not initialized"); return SDL_SetError("EGL not initialized");
@ -92,82 +58,86 @@ int KMSDRM_GLES_SetSwapInterval(_THIS, int interval) {
int int
KMSDRM_GLES_SwapWindow(_THIS, SDL_Window * window) { KMSDRM_GLES_SwapWindow(_THIS, SDL_Window * window) {
SDL_WindowData *wdata = ((SDL_WindowData *) window->driverdata); SDL_WindowData *windata = ((SDL_WindowData *) window->driverdata);
SDL_DisplayData *displaydata = (SDL_DisplayData *) SDL_GetDisplayForWindow(window)->driverdata; SDL_DisplayData *dispdata = (SDL_DisplayData *) SDL_GetDisplayForWindow(window)->driverdata;
SDL_VideoData *vdata = ((SDL_VideoData *)_this->driverdata); SDL_VideoData *viddata = ((SDL_VideoData *)_this->driverdata);
KMSDRM_FBInfo *fb_info; KMSDRM_FBInfo *fb_info;
int ret; int ret;
/* Do we still need to wait for a flip? */ /* Recreate the GBM / EGL surfaces if the display mode has changed */
if (windata->egl_surface_dirty) {
KMSDRM_CreateSurfaces(_this, window);
}
/* Wait for confirmation that the next front buffer has been flipped, at which
point the previous front buffer can be released */
int timeout = 0; int timeout = 0;
if (_this->egl_data->egl_swapinterval == 1) { if (_this->egl_data->egl_swapinterval == 1) {
timeout = -1; timeout = -1;
} }
if (!KMSDRM_WaitPageFlip(_this, wdata, timeout)) { if (!KMSDRM_WaitPageFlip(_this, windata, timeout)) {
return 0; return 0;
} }
/* Release previously displayed buffer (which is now the backbuffer) and lock a new one */ /* Release the previous front buffer */
if (wdata->next_bo != NULL) { if (windata->curr_bo) {
KMSDRM_gbm_surface_release_buffer(wdata->gs, wdata->current_bo); KMSDRM_gbm_surface_release_buffer(windata->gs, windata->curr_bo);
/* SDL_LogDebug(SDL_LOG_CATEGORY_VIDEO, "Released GBM surface %p", (void *)wdata->next_bo); */ /* SDL_LogDebug(SDL_LOG_CATEGORY_VIDEO, "Released GBM surface %p", (void *)windata->curr_bo); */
windata->curr_bo = NULL;
wdata->current_bo = wdata->next_bo;
wdata->next_bo = NULL;
} }
if (!(_this->egl_data->eglSwapBuffers(_this->egl_data->egl_display, wdata->egl_surface))) { windata->curr_bo = windata->next_bo;
/* Make the current back buffer the next front buffer */
if (!(_this->egl_data->eglSwapBuffers(_this->egl_data->egl_display, windata->egl_surface))) {
SDL_LogError(SDL_LOG_CATEGORY_VIDEO, "eglSwapBuffers failed."); SDL_LogError(SDL_LOG_CATEGORY_VIDEO, "eglSwapBuffers failed.");
return 0; return 0;
} }
if (wdata->current_bo == NULL) { /* Lock the next front buffer so it can't be allocated as a back buffer */
wdata->current_bo = KMSDRM_gbm_surface_lock_front_buffer(wdata->gs); windata->next_bo = KMSDRM_gbm_surface_lock_front_buffer(windata->gs);
if (wdata->current_bo == NULL) { if (!windata->next_bo) {
return 0;
}
}
wdata->next_bo = KMSDRM_gbm_surface_lock_front_buffer(wdata->gs);
if (wdata->next_bo == NULL) {
SDL_LogError(SDL_LOG_CATEGORY_VIDEO, "Could not lock GBM surface front buffer"); SDL_LogError(SDL_LOG_CATEGORY_VIDEO, "Could not lock GBM surface front buffer");
return 0; return 0;
/* } else { /* } else {
SDL_LogDebug(SDL_LOG_CATEGORY_VIDEO, "Locked GBM surface %p", (void *)wdata->next_bo); */ SDL_LogDebug(SDL_LOG_CATEGORY_VIDEO, "Locked GBM surface %p", (void *)windata->next_bo); */
} }
fb_info = KMSDRM_FBFromBO(_this, wdata->next_bo); fb_info = KMSDRM_FBFromBO(_this, windata->next_bo);
if (fb_info == NULL) { if (!fb_info) {
return 0; return 0;
} }
/* Have we already setup the CRTC to one of the GBM buffers? Do so if we have not, if (!windata->curr_bo) {
or FlipPage won't work in some cases. */ /* On the first swap, immediately present the new front buffer. Before
if (!wdata->crtc_ready) { drmModePageFlip can be used the CRTC has to be configured to use
if(!KMSDRM_GLES_SetupCrtc(_this, window)) { the current connector and mode with drmModeSetCrtc */
SDL_LogError(SDL_LOG_CATEGORY_VIDEO, "Could not set up CRTC for doing pageflips"); ret = KMSDRM_drmModeSetCrtc(viddata->drm_fd, dispdata->crtc_id, fb_info->fb_id, 0,
return 0; 0, &dispdata->conn->connector_id, 1, &dispdata->mode);
}
}
/* SDL_LogDebug(SDL_LOG_CATEGORY_VIDEO, "drmModePageFlip(%d, %u, %u, DRM_MODE_PAGE_FLIP_EVENT, &wdata->waiting_for_flip)", if (ret) {
vdata->drm_fd, displaydata->crtc_id, fb_info->fb_id); */ SDL_LogError(SDL_LOG_CATEGORY_VIDEO, "Could not configure CRTC");
ret = KMSDRM_drmModePageFlip(vdata->drm_fd, displaydata->crtc_id, fb_info->fb_id, }
DRM_MODE_PAGE_FLIP_EVENT, &wdata->waiting_for_flip); } else {
/* On subsequent swaps, queue the new front buffer to be flipped during
the next vertical blank */
ret = KMSDRM_drmModePageFlip(viddata->drm_fd, dispdata->crtc_id, fb_info->fb_id,
DRM_MODE_PAGE_FLIP_EVENT, &windata->waiting_for_flip);
/* SDL_LogDebug(SDL_LOG_CATEGORY_VIDEO, "drmModePageFlip(%d, %u, %u, DRM_MODE_PAGE_FLIP_EVENT, &windata->waiting_for_flip)",
viddata->drm_fd, displaydata->crtc_id, fb_info->fb_id); */
if (_this->egl_data->egl_swapinterval == 1) { if (_this->egl_data->egl_swapinterval == 1) {
/* Queue page flip at vsync */
if (ret == 0) { if (ret == 0) {
wdata->waiting_for_flip = SDL_TRUE; windata->waiting_for_flip = SDL_TRUE;
} else { } else {
SDL_LogError(SDL_LOG_CATEGORY_VIDEO, "Could not queue pageflip: %d", ret); SDL_LogError(SDL_LOG_CATEGORY_VIDEO, "Could not queue pageflip: %d", ret);
} }
}
/* Wait immediately for vsync (as if we only had two buffers), for low input-lag scenarios. /* Wait immediately for vsync (as if we only had two buffers), for low input-lag scenarios.
Run your SDL2 program with "SDL_KMSDRM_DOUBLE_BUFFER=1 <program_name>" to enable this. */ Run your SDL2 program with "SDL_KMSDRM_DOUBLE_BUFFER=1 <program_name>" to enable this. */
if (wdata->double_buffer) { if (_this->egl_data->egl_swapinterval == 1 && windata->double_buffer) {
KMSDRM_WaitPageFlip(_this, wdata, -1); KMSDRM_WaitPageFlip(_this, windata, -1);
} }
} }

View File

@ -28,6 +28,7 @@
#include "SDL_syswm.h" #include "SDL_syswm.h"
#include "SDL_log.h" #include "SDL_log.h"
#include "SDL_hints.h" #include "SDL_hints.h"
#include "../../events/SDL_events_c.h"
#include "../../events/SDL_mouse_c.h" #include "../../events/SDL_mouse_c.h"
#include "../../events/SDL_keyboard_c.h" #include "../../events/SDL_keyboard_c.h"
@ -44,6 +45,7 @@
#include <sys/stat.h> #include <sys/stat.h>
#include <dirent.h> #include <dirent.h>
#include <errno.h> #include <errno.h>
#include <poll.h>
#define KMSDRM_DRI_PATH "/dev/dri/" #define KMSDRM_DRI_PATH "/dev/dri/"
@ -60,7 +62,7 @@ check_modestting(int devindex)
if (drm_fd >= 0) { if (drm_fd >= 0) {
if (SDL_KMSDRM_LoadSymbols()) { if (SDL_KMSDRM_LoadSymbols()) {
drmModeRes *resources = KMSDRM_drmModeGetResources(drm_fd); drmModeRes *resources = KMSDRM_drmModeGetResources(drm_fd);
if (resources != NULL) { if (resources) {
SDL_LogDebug(SDL_LOG_CATEGORY_VIDEO, "%scard%d connector, encoder and CRTC counts are: %d %d %d", SDL_LogDebug(SDL_LOG_CATEGORY_VIDEO, "%scard%d connector, encoder and CRTC counts are: %d %d %d",
KMSDRM_DRI_PATH, devindex, KMSDRM_DRI_PATH, devindex,
resources->count_connectors, resources->count_encoders, resources->count_crtcs); resources->count_connectors, resources->count_encoders, resources->count_crtcs);
@ -140,22 +142,23 @@ KMSDRM_Available(void)
} }
static void static void
KMSDRM_Destroy(SDL_VideoDevice * device) KMSDRM_DeleteDevice(SDL_VideoDevice * device)
{ {
if (device->driverdata != NULL) { if (device->driverdata) {
SDL_free(device->driverdata); SDL_free(device->driverdata);
device->driverdata = NULL; device->driverdata = NULL;
} }
SDL_free(device); SDL_free(device);
SDL_KMSDRM_UnloadSymbols(); SDL_KMSDRM_UnloadSymbols();
} }
static SDL_VideoDevice * static SDL_VideoDevice *
KMSDRM_Create(int devindex) KMSDRM_CreateDevice(int devindex)
{ {
SDL_VideoDevice *device; SDL_VideoDevice *device;
SDL_VideoData *vdata; SDL_VideoData *viddata;
if (!devindex || (devindex > 99)) { if (!devindex || (devindex > 99)) {
devindex = get_driindex(); devindex = get_driindex();
@ -170,29 +173,21 @@ KMSDRM_Create(int devindex)
return NULL; return NULL;
} }
/* Initialize SDL_VideoDevice structure */
device = (SDL_VideoDevice *) SDL_calloc(1, sizeof(SDL_VideoDevice)); device = (SDL_VideoDevice *) SDL_calloc(1, sizeof(SDL_VideoDevice));
if (device == NULL) { if (!device) {
SDL_OutOfMemory(); SDL_OutOfMemory();
return NULL; return NULL;
} }
/* Initialize internal data */ viddata = (SDL_VideoData *) SDL_calloc(1, sizeof(SDL_VideoData));
vdata = (SDL_VideoData *) SDL_calloc(1, sizeof(SDL_VideoData)); if (!viddata) {
if (vdata == NULL) {
SDL_OutOfMemory(); SDL_OutOfMemory();
goto cleanup; goto cleanup;
} }
vdata->devindex = devindex; viddata->devindex = devindex;
vdata->drm_fd = -1; viddata->drm_fd = -1;
device->driverdata = vdata; device->driverdata = viddata;
/* Setup amount of available displays and current display */
device->num_displays = 0;
/* Set device free function */
device->free = KMSDRM_Destroy;
/* Setup all functions which we can handle */ /* Setup all functions which we can handle */
device->VideoInit = KMSDRM_VideoInit; device->VideoInit = KMSDRM_VideoInit;
@ -225,16 +220,16 @@ KMSDRM_Create(int devindex)
device->GL_SwapWindow = KMSDRM_GLES_SwapWindow; device->GL_SwapWindow = KMSDRM_GLES_SwapWindow;
device->GL_DeleteContext = KMSDRM_GLES_DeleteContext; device->GL_DeleteContext = KMSDRM_GLES_DeleteContext;
#endif #endif
device->PumpEvents = KMSDRM_PumpEvents; device->PumpEvents = KMSDRM_PumpEvents;
device->free = KMSDRM_DeleteDevice;
return device; return device;
cleanup: cleanup:
if (device != NULL) if (device)
SDL_free(device); SDL_free(device);
if (vdata != NULL) if (viddata)
SDL_free(vdata); SDL_free(viddata);
return NULL; return NULL;
} }
@ -242,7 +237,7 @@ VideoBootStrap KMSDRM_bootstrap = {
"KMSDRM", "KMSDRM",
"KMS/DRM Video Driver", "KMS/DRM Video Driver",
KMSDRM_Available, KMSDRM_Available,
KMSDRM_Create KMSDRM_CreateDevice
}; };
@ -262,178 +257,248 @@ KMSDRM_FBDestroyCallback(struct gbm_bo *bo, void *data)
KMSDRM_FBInfo * KMSDRM_FBInfo *
KMSDRM_FBFromBO(_THIS, struct gbm_bo *bo) KMSDRM_FBFromBO(_THIS, struct gbm_bo *bo)
{ {
uint32_t w, h, stride, handle; SDL_VideoData *viddata = ((SDL_VideoData *)_this->driverdata);
int ret;
SDL_VideoData *vdata = ((SDL_VideoData *)_this->driverdata);
KMSDRM_FBInfo *fb_info;
fb_info = (KMSDRM_FBInfo *)KMSDRM_gbm_bo_get_user_data(bo); /* Check for an existing framebuffer */
if (fb_info != NULL) { KMSDRM_FBInfo *fb_info = (KMSDRM_FBInfo *)KMSDRM_gbm_bo_get_user_data(bo);
/* Have a previously used framebuffer, return it */
if (fb_info) {
return fb_info; return fb_info;
} }
/* Here a new DRM FB must be created */ /* Create a structure that contains enough info to remove the framebuffer
when the backing buffer is destroyed */
fb_info = (KMSDRM_FBInfo *)SDL_calloc(1, sizeof(KMSDRM_FBInfo)); fb_info = (KMSDRM_FBInfo *)SDL_calloc(1, sizeof(KMSDRM_FBInfo));
if (fb_info == NULL) {
if (!fb_info) {
SDL_OutOfMemory(); SDL_OutOfMemory();
return NULL; return NULL;
} }
fb_info->drm_fd = vdata->drm_fd;
w = KMSDRM_gbm_bo_get_width(bo); fb_info->drm_fd = viddata->drm_fd;
h = KMSDRM_gbm_bo_get_height(bo);
stride = KMSDRM_gbm_bo_get_stride(bo);
handle = KMSDRM_gbm_bo_get_handle(bo).u32;
ret = KMSDRM_drmModeAddFB(vdata->drm_fd, w, h, 24, 32, stride, handle, &fb_info->fb_id); /* Create framebuffer object for the buffer */
if (ret < 0) { unsigned w = KMSDRM_gbm_bo_get_width(bo);
unsigned h = KMSDRM_gbm_bo_get_height(bo);
Uint32 stride = KMSDRM_gbm_bo_get_stride(bo);
Uint32 handle = KMSDRM_gbm_bo_get_handle(bo).u32;
int ret = KMSDRM_drmModeAddFB(viddata->drm_fd, w, h, 24, 32, stride, handle,
&fb_info->fb_id);
if (ret) {
SDL_free(fb_info); SDL_free(fb_info);
return NULL; return NULL;
} }
SDL_LogDebug(SDL_LOG_CATEGORY_VIDEO, "New DRM FB (%u): %ux%u, stride %u from BO %p", fb_info->fb_id, w, h, stride, (void *)bo);
SDL_LogDebug(SDL_LOG_CATEGORY_VIDEO, "New DRM FB (%u): %ux%u, stride %u from BO %p",
fb_info->fb_id, w, h, stride, (void *)bo);
/* Associate our DRM framebuffer with this buffer object */ /* Associate our DRM framebuffer with this buffer object */
KMSDRM_gbm_bo_set_user_data(bo, fb_info, KMSDRM_FBDestroyCallback); KMSDRM_gbm_bo_set_user_data(bo, fb_info, KMSDRM_FBDestroyCallback);
return fb_info; return fb_info;
} }
SDL_bool
KMSDRM_WaitPageFlip(_THIS, SDL_WindowData *wdata, int timeout) {
SDL_VideoData *vdata = ((SDL_VideoData *)_this->driverdata);
while (wdata->waiting_for_flip) {
vdata->drm_pollfd.revents = 0;
if (poll(&vdata->drm_pollfd, 1, timeout) < 0) {
SDL_LogError(SDL_LOG_CATEGORY_VIDEO, "DRM poll error");
return SDL_FALSE;
}
if (vdata->drm_pollfd.revents & (POLLHUP | POLLERR)) {
SDL_LogError(SDL_LOG_CATEGORY_VIDEO, "DRM poll hup or error");
return SDL_FALSE;
}
if (vdata->drm_pollfd.revents & POLLIN) {
/* Page flip? If so, drmHandleEvent will unset wdata->waiting_for_flip */
KMSDRM_drmHandleEvent(vdata->drm_fd, &vdata->drm_evctx);
} else {
/* Timed out and page flip didn't happen */
SDL_LogDebug(SDL_LOG_CATEGORY_VIDEO, "Dropping frame while waiting_for_flip");
return SDL_FALSE;
}
}
return SDL_TRUE;
}
static void static void
KMSDRM_FlipHandler(int fd, unsigned int frame, unsigned int sec, unsigned int usec, void *data) KMSDRM_FlipHandler(int fd, unsigned int frame, unsigned int sec, unsigned int usec, void *data)
{ {
*((SDL_bool *) data) = SDL_FALSE; *((SDL_bool *) data) = SDL_FALSE;
} }
SDL_bool
KMSDRM_WaitPageFlip(_THIS, SDL_WindowData *windata, int timeout) {
SDL_VideoData *viddata = ((SDL_VideoData *)_this->driverdata);
drmEventContext ev = {0};
ev.version = DRM_EVENT_CONTEXT_VERSION;
ev.page_flip_handler = KMSDRM_FlipHandler;
struct pollfd pfd = {0};
pfd.fd = viddata->drm_fd;
pfd.events = POLLIN;
while (windata->waiting_for_flip) {
pfd.revents = 0;
if (poll(&pfd, 1, timeout) < 0) {
SDL_LogError(SDL_LOG_CATEGORY_VIDEO, "DRM poll error");
return SDL_FALSE;
}
if (pfd.revents & (POLLHUP | POLLERR)) {
SDL_LogError(SDL_LOG_CATEGORY_VIDEO, "DRM poll hup or error");
return SDL_FALSE;
}
if (pfd.revents & POLLIN) {
/* Page flip? If so, drmHandleEvent will unset windata->waiting_for_flip */
KMSDRM_drmHandleEvent(viddata->drm_fd, &ev);
} else {
/* Timed out and page flip didn't happen */
SDL_LogDebug(SDL_LOG_CATEGORY_VIDEO, "Dropping frame while waiting_for_flip");
return SDL_FALSE;
}
}
return SDL_TRUE;
}
/*****************************************************************************/ /*****************************************************************************/
/* SDL Video and Display initialization/handling functions */ /* SDL Video and Display initialization/handling functions */
/* _this is a SDL_VideoDevice * */ /* _this is a SDL_VideoDevice * */
/*****************************************************************************/ /*****************************************************************************/
static int
KMSDRM_DestroySurfaces(_THIS, SDL_Window * window)
{
SDL_VideoData *viddata = ((SDL_VideoData *)_this->driverdata);
SDL_WindowData *windata = (SDL_WindowData *)window->driverdata;
KMSDRM_WaitPageFlip(_this, windata, -1);
if (windata->curr_bo) {
KMSDRM_gbm_surface_release_buffer(windata->gs, windata->curr_bo);
windata->curr_bo = NULL;
}
if (windata->next_bo) {
KMSDRM_gbm_surface_release_buffer(windata->gs, windata->next_bo);
windata->next_bo = NULL;
}
#if SDL_VIDEO_OPENGL_EGL
SDL_EGL_MakeCurrent(_this, EGL_NO_SURFACE, EGL_NO_CONTEXT);
if (windata->egl_surface != EGL_NO_SURFACE) {
SDL_EGL_DestroySurface(_this, windata->egl_surface);
windata->egl_surface = EGL_NO_SURFACE;
}
#endif
if (windata->gs) {
KMSDRM_gbm_surface_destroy(windata->gs);
windata->gs = NULL;
}
}
int
KMSDRM_CreateSurfaces(_THIS, SDL_Window * window)
{
SDL_VideoData *viddata = ((SDL_VideoData *)_this->driverdata);
SDL_WindowData *windata = (SDL_WindowData *)window->driverdata;
SDL_DisplayData *dispdata = (SDL_DisplayData *) SDL_GetDisplayForWindow(window)->driverdata;
Uint32 width = dispdata->mode.hdisplay;
Uint32 height = dispdata->mode.vdisplay;
Uint32 surface_fmt = GBM_FORMAT_XRGB8888;
Uint32 surface_flags = GBM_BO_USE_SCANOUT | GBM_BO_USE_RENDERING;
if (!KMSDRM_gbm_device_is_format_supported(viddata->gbm, surface_fmt, surface_flags)) {
SDL_LogWarn(SDL_LOG_CATEGORY_VIDEO, "GBM surface format not supported. Trying anyway.");
}
#if SDL_VIDEO_OPENGL_EGL
SDL_EGL_SetRequiredVisualId(_this, surface_fmt);
EGLContext egl_context = (EGLContext)SDL_GL_GetCurrentContext();
#endif
KMSDRM_DestroySurfaces(_this, window);
windata->gs = KMSDRM_gbm_surface_create(viddata->gbm, width, height, surface_fmt, surface_flags);
if (!windata->gs) {
return SDL_SetError("Could not create GBM surface");
}
#if SDL_VIDEO_OPENGL_EGL
windata->egl_surface = SDL_EGL_CreateSurface(_this, (NativeWindowType)windata->gs);
if (windata->egl_surface == EGL_NO_SURFACE) {
return SDL_SetError("Could not create EGL window surface");
}
SDL_EGL_MakeCurrent(_this, windata->egl_surface, egl_context);
windata->egl_surface_dirty = 0;
#endif
return 0;
}
int int
KMSDRM_VideoInit(_THIS) KMSDRM_VideoInit(_THIS)
{ {
int i, j;
SDL_bool found;
int ret = 0; int ret = 0;
char *devname; SDL_VideoData *viddata = ((SDL_VideoData *)_this->driverdata);
SDL_VideoData *vdata = ((SDL_VideoData *)_this->driverdata); SDL_DisplayData *dispdata = NULL;
drmModeRes *resources = NULL; drmModeRes *resources = NULL;
drmModeConnector *connector = NULL;
drmModeEncoder *encoder = NULL; drmModeEncoder *encoder = NULL;
SDL_DisplayMode current_mode;
SDL_VideoDisplay display;
/* Allocate display internal data */ dispdata = (SDL_DisplayData *) SDL_calloc(1, sizeof(SDL_DisplayData));
SDL_DisplayData *data = (SDL_DisplayData *) SDL_calloc(1, sizeof(SDL_DisplayData));
if (data == NULL) { if (!dispdata) {
return SDL_OutOfMemory(); return SDL_OutOfMemory();
} }
SDL_LogDebug(SDL_LOG_CATEGORY_VIDEO, "KMSDRM_VideoInit()"); SDL_LogDebug(SDL_LOG_CATEGORY_VIDEO, "KMSDRM_VideoInit()");
/* Open /dev/dri/cardNN */ /* Open /dev/dri/cardNN */
devname = (char *) SDL_calloc(1, 16); char devname[32];
if (devname == NULL) { SDL_snprintf(devname, sizeof(devname), "/dev/dri/card%d", viddata->devindex);
ret = SDL_OutOfMemory();
SDL_LogDebug(SDL_LOG_CATEGORY_VIDEO, "Opening device %s", devname);
viddata->drm_fd = open(devname, O_RDWR | O_CLOEXEC);
if (viddata->drm_fd < 0) {
ret = SDL_SetError("Could not open %s", devname);
goto cleanup; goto cleanup;
} }
SDL_LogDebug(SDL_LOG_CATEGORY_VIDEO, "Opening device /dev/dri/card%d", vdata->devindex);
SDL_snprintf(devname, 16, "/dev/dri/card%d", vdata->devindex);
vdata->drm_fd = open(devname, O_RDWR | O_CLOEXEC);
SDL_free(devname);
if (vdata->drm_fd < 0) { SDL_LogDebug(SDL_LOG_CATEGORY_VIDEO, "Opened DRM FD (%d)", viddata->drm_fd);
ret = SDL_SetError("Could not open /dev/dri/card%d.", vdata->devindex);
goto cleanup;
}
SDL_LogDebug(SDL_LOG_CATEGORY_VIDEO, "Opened DRM FD (%d)", vdata->drm_fd);
vdata->gbm = KMSDRM_gbm_create_device(vdata->drm_fd); viddata->gbm = KMSDRM_gbm_create_device(viddata->drm_fd);
if (vdata->gbm == NULL) { if (!viddata->gbm) {
ret = SDL_SetError("Couldn't create gbm device."); ret = SDL_SetError("Couldn't create gbm device.");
goto cleanup; goto cleanup;
} }
/* Find the first available connector with modes */ /* Get all of the available connectors / devices / crtcs */
resources = KMSDRM_drmModeGetResources(vdata->drm_fd); resources = KMSDRM_drmModeGetResources(viddata->drm_fd);
if (!resources) { if (!resources) {
ret = SDL_SetError("drmModeGetResources(%d) failed", vdata->drm_fd); ret = SDL_SetError("drmModeGetResources(%d) failed", viddata->drm_fd);
goto cleanup; goto cleanup;
} }
for (i = 0; i < resources->count_connectors; i++) { for (int i = 0; i < resources->count_connectors; i++) {
connector = KMSDRM_drmModeGetConnector(vdata->drm_fd, resources->connectors[i]); drmModeConnector *conn = KMSDRM_drmModeGetConnector(viddata->drm_fd, resources->connectors[i]);
if (connector == NULL)
continue;
if (connector->connection == DRM_MODE_CONNECTED && if (!conn) {
connector->count_modes > 0) { continue;
}
if (conn->connection == DRM_MODE_CONNECTED && conn->count_modes) {
SDL_LogDebug(SDL_LOG_CATEGORY_VIDEO, "Found connector %d with %d modes.", SDL_LogDebug(SDL_LOG_CATEGORY_VIDEO, "Found connector %d with %d modes.",
connector->connector_id, connector->count_modes); conn->connector_id, conn->count_modes);
vdata->saved_conn_id = connector->connector_id; dispdata->conn = conn;
break; break;
} }
KMSDRM_drmModeFreeConnector(connector); KMSDRM_drmModeFreeConnector(conn);
connector = NULL;
} }
if (i == resources->count_connectors) { if (!dispdata->conn) {
ret = SDL_SetError("No currently active connector found."); ret = SDL_SetError("No currently active connector found.");
goto cleanup; goto cleanup;
} }
found = SDL_FALSE; /* Try to find the connector's current encoder */
for (int i = 0; i < resources->count_encoders; i++) {
encoder = KMSDRM_drmModeGetEncoder(viddata->drm_fd, resources->encoders[i]);
for (i = 0; i < resources->count_encoders; i++) { if (!encoder) {
encoder = KMSDRM_drmModeGetEncoder(vdata->drm_fd, resources->encoders[i]);
if (encoder == NULL)
continue; continue;
if (encoder->encoder_id == connector->encoder_id) {
data->encoder_id = encoder->encoder_id;
found = SDL_TRUE;
} else {
for (j = 0; j < connector->count_encoders; j++) {
if (connector->encoders[j] == encoder->encoder_id) {
data->encoder_id = encoder->encoder_id;
found = SDL_TRUE;
break;
}
}
} }
if (found == SDL_TRUE) { if (encoder->encoder_id == dispdata->conn->encoder_id) {
SDL_LogDebug(SDL_LOG_CATEGORY_VIDEO, "Found encoder %d.", data->encoder_id); SDL_LogDebug(SDL_LOG_CATEGORY_VIDEO, "Found encoder %d.", encoder->encoder_id);
break; break;
} }
@ -441,71 +506,91 @@ KMSDRM_VideoInit(_THIS)
encoder = NULL; encoder = NULL;
} }
if (i == resources->count_encoders) { if (!encoder) {
/* No encoder was connected, find the first supported one */
for (int i = 0, j; i < resources->count_encoders; i++) {
encoder = KMSDRM_drmModeGetEncoder(viddata->drm_fd, resources->encoders[i]);
if (!encoder) {
continue;
}
for (j = 0; j < dispdata->conn->count_encoders; j++) {
if (dispdata->conn->encoders[j] == encoder->encoder_id) {
break;
}
}
if (j != dispdata->conn->count_encoders) {
break;
}
KMSDRM_drmModeFreeEncoder(encoder);
encoder = NULL;
}
}
if (!encoder) {
ret = SDL_SetError("No connected encoder found."); ret = SDL_SetError("No connected encoder found.");
goto cleanup; goto cleanup;
} }
vdata->saved_crtc = KMSDRM_drmModeGetCrtc(vdata->drm_fd, encoder->crtc_id); SDL_LogDebug(SDL_LOG_CATEGORY_VIDEO, "Found encoder %d.", encoder->encoder_id);
if (vdata->saved_crtc == NULL) { /* Try to find a CRTC connected to this encoder */
for (i = 0; i < resources->count_crtcs; i++) { dispdata->saved_crtc = KMSDRM_drmModeGetCrtc(viddata->drm_fd, encoder->crtc_id);
if (!dispdata->saved_crtc) {
/* No CRTC was connected, find the first CRTC that can be connected */
for (int i = 0; i < resources->count_crtcs; i++) {
if (encoder->possible_crtcs & (1 << i)) { if (encoder->possible_crtcs & (1 << i)) {
encoder->crtc_id = resources->crtcs[i]; encoder->crtc_id = resources->crtcs[i];
SDL_LogDebug(SDL_LOG_CATEGORY_VIDEO, "Set encoder's CRTC to %d.", encoder->crtc_id); dispdata->saved_crtc = KMSDRM_drmModeGetCrtc(viddata->drm_fd, encoder->crtc_id);
vdata->saved_crtc = KMSDRM_drmModeGetCrtc(vdata->drm_fd, encoder->crtc_id);
break; break;
} }
} }
} }
if (vdata->saved_crtc == NULL) { if (!dispdata->saved_crtc) {
ret = SDL_SetError("No CRTC found."); ret = SDL_SetError("No CRTC found.");
goto cleanup; goto cleanup;
} }
SDL_LogDebug(SDL_LOG_CATEGORY_VIDEO, "Saved crtc_id %u, fb_id %u, (%u,%u), %ux%u",
vdata->saved_crtc->crtc_id, vdata->saved_crtc->buffer_id, vdata->saved_crtc->x,
vdata->saved_crtc->y, vdata->saved_crtc->width, vdata->saved_crtc->height);
data->crtc_id = encoder->crtc_id;
data->cur_mode = vdata->saved_crtc->mode;
vdata->crtc_id = encoder->crtc_id;
// select default mode if this one is not valid SDL_LogDebug(SDL_LOG_CATEGORY_VIDEO, "Saved crtc_id %u, fb_id %u, (%u,%u), %ux%u",
if (vdata->saved_crtc->mode_valid == 0) { dispdata->saved_crtc->crtc_id, dispdata->saved_crtc->buffer_id, dispdata->saved_crtc->x,
dispdata->saved_crtc->y, dispdata->saved_crtc->width, dispdata->saved_crtc->height);
dispdata->crtc_id = encoder->crtc_id;
/* Figure out the default mode to be set. If the current CRTC's mode isn't
valid, select the first mode supported by the connector
FIXME find first mode that specifies DRM_MODE_TYPE_PREFERRED */
dispdata->mode = dispdata->saved_crtc->mode;
if (dispdata->saved_crtc->mode_valid == 0) {
SDL_LogDebug(SDL_LOG_CATEGORY_VIDEO, SDL_LogDebug(SDL_LOG_CATEGORY_VIDEO,
"Current mode is invalid, selecting connector's mode #0."); "Current mode is invalid, selecting connector's mode #0.");
data->cur_mode = connector->modes[0]; dispdata->mode = dispdata->conn->modes[0];
} }
SDL_zero(current_mode); /* Setup the single display that's available */
SDL_VideoDisplay display = {0};
current_mode.w = data->cur_mode.hdisplay; display.desktop_mode.w = dispdata->mode.hdisplay;
current_mode.h = data->cur_mode.vdisplay; display.desktop_mode.h = dispdata->mode.vdisplay;
current_mode.refresh_rate = data->cur_mode.vrefresh; display.desktop_mode.refresh_rate = dispdata->mode.vrefresh;
#if 1
/* FIXME ? display.desktop_mode.format = SDL_PIXELFORMAT_ARGB8888;
drmModeFB *fb = drmModeGetFB(vdata->drm_fd, vdata->saved_crtc->buffer_id); #else
current_mode.format = drmToSDLPixelFormat(fb->bpp, fb->depth); /* FIXME */
drmModeFB *fb = drmModeGetFB(viddata->drm_fd, dispdata->saved_crtc->buffer_id);
display.desktop_mode.format = drmToSDLPixelFormat(fb->bpp, fb->depth);
drmModeFreeFB(fb); drmModeFreeFB(fb);
*/ #endif
current_mode.format = SDL_PIXELFORMAT_ARGB8888; display.current_mode = display.desktop_mode;
display.driverdata = dispdata;
current_mode.driverdata = NULL;
SDL_zero(display);
display.desktop_mode = current_mode;
display.current_mode = current_mode;
display.driverdata = data;
/* SDL_VideoQuit will later SDL_free(display.driverdata) */
SDL_AddVideoDisplay(&display); SDL_AddVideoDisplay(&display);
/* Setup page flip handler */
vdata->drm_pollfd.fd = vdata->drm_fd;
vdata->drm_pollfd.events = POLLIN;
vdata->drm_evctx.version = DRM_EVENT_CONTEXT_VERSION;
vdata->drm_evctx.page_flip_handler = KMSDRM_FlipHandler;
#ifdef SDL_INPUT_LINUXEV #ifdef SDL_INPUT_LINUXEV
SDL_EVDEV_Init(); SDL_EVDEV_Init();
#endif #endif
@ -515,28 +600,30 @@ KMSDRM_VideoInit(_THIS)
return ret; return ret;
cleanup: cleanup:
if (encoder != NULL) if (encoder)
KMSDRM_drmModeFreeEncoder(encoder); KMSDRM_drmModeFreeEncoder(encoder);
if (connector != NULL) if (resources)
KMSDRM_drmModeFreeConnector(connector);
if (resources != NULL)
KMSDRM_drmModeFreeResources(resources); KMSDRM_drmModeFreeResources(resources);
if (ret != 0) { if (ret != 0) {
/* Error (complete) cleanup */ /* Error (complete) cleanup */
SDL_free(data); if (dispdata->conn) {
if(vdata->saved_crtc != NULL) { KMSDRM_drmModeFreeConnector(dispdata->conn);
KMSDRM_drmModeFreeCrtc(vdata->saved_crtc); dispdata->conn = NULL;
vdata->saved_crtc = NULL;
} }
if (vdata->gbm != NULL) { if (dispdata->saved_crtc) {
KMSDRM_gbm_device_destroy(vdata->gbm); KMSDRM_drmModeFreeCrtc(dispdata->saved_crtc);
vdata->gbm = NULL; dispdata->saved_crtc = NULL;
} }
if (vdata->drm_fd >= 0) { if (viddata->gbm) {
close(vdata->drm_fd); KMSDRM_gbm_device_destroy(viddata->gbm);
vdata->drm_fd = -1; viddata->gbm = NULL;
} }
if (viddata->drm_fd >= 0) {
close(viddata->drm_fd);
viddata->drm_fd = -1;
}
SDL_free(dispdata);
} }
return ret; return ret;
} }
@ -544,7 +631,8 @@ cleanup:
void void
KMSDRM_VideoQuit(_THIS) KMSDRM_VideoQuit(_THIS)
{ {
SDL_VideoData *vdata = ((SDL_VideoData *)_this->driverdata); SDL_VideoData *viddata = ((SDL_VideoData *)_this->driverdata);
SDL_DisplayData *dispdata = (SDL_DisplayData *)SDL_GetDisplayDriverData(0);
SDL_LogDebug(SDL_LOG_CATEGORY_VIDEO, "KMSDRM_VideoQuit()"); SDL_LogDebug(SDL_LOG_CATEGORY_VIDEO, "KMSDRM_VideoQuit()");
@ -552,27 +640,40 @@ KMSDRM_VideoQuit(_THIS)
SDL_GL_UnloadLibrary(); SDL_GL_UnloadLibrary();
} }
if(vdata->saved_crtc != NULL) { /* Clear out the window list */
if(vdata->drm_fd >= 0 && vdata->saved_conn_id > 0) { SDL_free(viddata->windows);
viddata->windows = NULL;
viddata->max_windows = 0;
viddata->num_windows = 0;
/* Restore saved CRTC settings */ /* Restore saved CRTC settings */
drmModeCrtc *crtc = vdata->saved_crtc; if (viddata->drm_fd >= 0 && dispdata->conn && dispdata->saved_crtc) {
if(KMSDRM_drmModeSetCrtc(vdata->drm_fd, crtc->crtc_id, crtc->buffer_id, drmModeConnector *conn = dispdata->conn;
crtc->x, crtc->y, &vdata->saved_conn_id, 1, drmModeCrtc *crtc = dispdata->saved_crtc;
&crtc->mode) != 0) {
int ret = KMSDRM_drmModeSetCrtc(viddata->drm_fd, crtc->crtc_id, crtc->buffer_id,
crtc->x, crtc->y, &conn->connector_id, 1, &crtc->mode);
if (ret != 0) {
SDL_LogWarn(SDL_LOG_CATEGORY_VIDEO, "Could not restore original CRTC mode"); SDL_LogWarn(SDL_LOG_CATEGORY_VIDEO, "Could not restore original CRTC mode");
} }
} }
KMSDRM_drmModeFreeCrtc(vdata->saved_crtc); if (dispdata->conn) {
vdata->saved_crtc = NULL; KMSDRM_drmModeFreeConnector(dispdata->conn);
dispdata->conn = NULL;
} }
if (vdata->gbm != NULL) { if (dispdata->saved_crtc) {
KMSDRM_gbm_device_destroy(vdata->gbm); KMSDRM_drmModeFreeCrtc(dispdata->saved_crtc);
vdata->gbm = NULL; dispdata->saved_crtc = NULL;
} }
if (vdata->drm_fd >= 0) { if (viddata->gbm) {
close(vdata->drm_fd); KMSDRM_gbm_device_destroy(viddata->gbm);
SDL_LogDebug(SDL_LOG_CATEGORY_VIDEO, "Closed DRM FD %d", vdata->drm_fd); viddata->gbm = NULL;
vdata->drm_fd = -1; }
if (viddata->drm_fd >= 0) {
close(viddata->drm_fd);
SDL_LogDebug(SDL_LOG_CATEGORY_VIDEO, "Closed DRM FD %d", viddata->drm_fd);
viddata->drm_fd = -1;
} }
#ifdef SDL_INPUT_LINUXEV #ifdef SDL_INPUT_LINUXEV
SDL_EVDEV_Quit(); SDL_EVDEV_Quit();
@ -582,48 +683,68 @@ KMSDRM_VideoQuit(_THIS)
void void
KMSDRM_GetDisplayModes(_THIS, SDL_VideoDisplay * display) KMSDRM_GetDisplayModes(_THIS, SDL_VideoDisplay * display)
{ {
/* Only one display mode available, the current one */ SDL_DisplayData *dispdata = display->driverdata;
SDL_AddDisplayMode(display, &display->current_mode); drmModeConnector *conn = dispdata->conn;
for (int i = 0; i < conn->count_modes; i++) {
SDL_DisplayModeData *modedata = SDL_calloc(1, sizeof(SDL_DisplayModeData));
if (modedata) {
modedata->mode_index = i;
}
SDL_DisplayMode mode;
mode.w = conn->modes[i].hdisplay;
mode.h = conn->modes[i].vdisplay;
mode.refresh_rate = conn->modes[i].vrefresh;
mode.format = SDL_PIXELFORMAT_ARGB8888;
mode.driverdata = modedata;
if (!SDL_AddDisplayMode(display, &mode)) {
SDL_free(modedata);
}
}
} }
int int
KMSDRM_SetDisplayMode(_THIS, SDL_VideoDisplay * display, SDL_DisplayMode * mode) KMSDRM_SetDisplayMode(_THIS, SDL_VideoDisplay * display, SDL_DisplayMode * mode)
{ {
SDL_VideoData *viddata = (SDL_VideoData *)_this->driverdata;
SDL_DisplayData *dispdata = (SDL_DisplayData *)display->driverdata;
SDL_DisplayModeData *modedata = (SDL_DisplayModeData *)mode->driverdata;
if (!modedata) {
return SDL_SetError("Mode doesn't have an associated index");
}
drmModeConnector *conn = dispdata->conn;
dispdata->mode = conn->modes[modedata->mode_index];
for (int i = 0; i < viddata->num_windows; i++) {
SDL_Window *window = viddata->windows[i];
SDL_WindowData *windata = (SDL_WindowData *)window->driverdata;
#if SDL_VIDEO_OPENGL_EGL
/* Can't recreate EGL surfaces right now, need to wait until SwapWindow
so the correct thread-local surface and context state are available */
windata->egl_surface_dirty = 1;
#else
if (KMSDRM_CreateSurfaces(_this, window)) {
return -1;
}
#endif
/* Tell app about the resize */
SDL_SendWindowEvent(window, SDL_WINDOWEVENT_RESIZED, mode->w, mode->h);
}
return 0; return 0;
} }
int int
KMSDRM_CreateWindow(_THIS, SDL_Window * window) KMSDRM_CreateWindow(_THIS, SDL_Window * window)
{ {
SDL_WindowData *wdata; SDL_VideoData *viddata = (SDL_VideoData *)_this->driverdata;
SDL_VideoDisplay *display;
SDL_VideoData *vdata = ((SDL_VideoData *)_this->driverdata);
Uint32 surface_fmt, surface_flags;
/* Allocate window internal data */
wdata = (SDL_WindowData *) SDL_calloc(1, sizeof(SDL_WindowData));
if (wdata == NULL) {
SDL_OutOfMemory();
goto error;
}
wdata->waiting_for_flip = SDL_FALSE;
display = SDL_GetDisplayForWindow(window);
/* Windows have one size for now */
window->w = display->desktop_mode.w;
window->h = display->desktop_mode.h;
/* Maybe you didn't ask for a fullscreen OpenGL window, but that's what you get */
window->flags |= (SDL_WINDOW_FULLSCREEN | SDL_WINDOW_OPENGL);
surface_fmt = GBM_FORMAT_XRGB8888;
surface_flags = GBM_BO_USE_SCANOUT | GBM_BO_USE_RENDERING;
if (!KMSDRM_gbm_device_is_format_supported(vdata->gbm, surface_fmt, surface_flags)) {
SDL_LogWarn(SDL_LOG_CATEGORY_VIDEO, "GBM surface format not supported. Trying anyway.");
}
wdata->gs = KMSDRM_gbm_surface_create(vdata->gbm, window->w, window->h, surface_fmt, surface_flags);
#if SDL_VIDEO_OPENGL_EGL #if SDL_VIDEO_OPENGL_EGL
if (!_this->egl_data) { if (!_this->egl_data) {
@ -631,82 +752,94 @@ KMSDRM_CreateWindow(_THIS, SDL_Window * window)
goto error; goto error;
} }
} }
SDL_EGL_SetRequiredVisualId(_this, surface_fmt); #endif
wdata->egl_surface = SDL_EGL_CreateSurface(_this, (NativeWindowType) wdata->gs);
if (wdata->egl_surface == EGL_NO_SURFACE) { /* Allocate window internal data */
SDL_SetError("Could not create EGL window surface"); SDL_WindowData *windata = (SDL_WindowData *)SDL_calloc(1, sizeof(SDL_WindowData));
if (!windata) {
SDL_OutOfMemory();
goto error; goto error;
} }
#endif /* SDL_VIDEO_OPENGL_EGL */
/* Windows have one size for now */
SDL_VideoDisplay *display = SDL_GetDisplayForWindow(window);
window->w = display->desktop_mode.w;
window->h = display->desktop_mode.h;
/* Maybe you didn't ask for a fullscreen OpenGL window, but that's what you get */
window->flags |= (SDL_WINDOW_FULLSCREEN | SDL_WINDOW_OPENGL);
/* In case we want low-latency, double-buffer video, we take note here */ /* In case we want low-latency, double-buffer video, we take note here */
wdata->double_buffer = SDL_FALSE; windata->double_buffer = SDL_FALSE;
if (SDL_GetHintBoolean(SDL_HINT_VIDEO_DOUBLE_BUFFER, SDL_FALSE)) { if (SDL_GetHintBoolean(SDL_HINT_VIDEO_DOUBLE_BUFFER, SDL_FALSE)) {
wdata->double_buffer = SDL_TRUE; windata->double_buffer = SDL_TRUE;
} }
/* Window is created, but we have yet to set up CRTC to one of the GBM buffers if we want
drmModePageFlip to work, and we can't do it until EGL is completely setup, because we
need to do eglSwapBuffers so we can get a valid GBM buffer object to call
drmModeSetCrtc on it. */
wdata->crtc_ready = SDL_FALSE;
/* Setup driver data for this window */ /* Setup driver data for this window */
window->driverdata = wdata; window->driverdata = windata;
/* One window, it always has focus */ if (KMSDRM_CreateSurfaces(_this, window)) {
SDL_SetMouseFocus(window); goto error;
SDL_SetKeyboardFocus(window); }
/* Add window to the internal list of tracked windows. Note, while it may
seem odd to support multiple fullscreen windows, some apps create an
extra window as a dummy surface when working with multiple contexts */
windata->viddata = viddata;
if (viddata->num_windows >= viddata->max_windows) {
int new_max_windows = viddata->max_windows + 1;
viddata->windows = (SDL_Window **)SDL_realloc(viddata->windows,
new_max_windows * sizeof(SDL_Window *));
viddata->max_windows = new_max_windows;
if (!viddata->windows) {
SDL_OutOfMemory();
goto error;
}
}
viddata->windows[viddata->num_windows++] = window;
/* Window has been successfully created */
return 0; return 0;
error: error:
if (wdata != NULL) { KMSDRM_DestroyWindow(_this, window);
#if SDL_VIDEO_OPENGL_EGL
if (wdata->egl_surface != EGL_NO_SURFACE)
SDL_EGL_DestroySurface(_this, wdata->egl_surface);
#endif /* SDL_VIDEO_OPENGL_EGL */
if (wdata->gs != NULL)
KMSDRM_gbm_surface_destroy(wdata->gs);
SDL_free(wdata);
}
return -1; return -1;
} }
void void
KMSDRM_DestroyWindow(_THIS, SDL_Window * window) KMSDRM_DestroyWindow(_THIS, SDL_Window * window)
{ {
SDL_WindowData *data = (SDL_WindowData *) window->driverdata; SDL_WindowData *windata = (SDL_WindowData *) window->driverdata;
if(data) {
/* Wait for any pending page flips and unlock buffer */ if (!windata) {
KMSDRM_WaitPageFlip(_this, data, -1); return;
if (data->crtc_bo != NULL) {
KMSDRM_gbm_surface_release_buffer(data->gs, data->crtc_bo);
data->crtc_bo = NULL;
} }
if (data->next_bo != NULL) {
KMSDRM_gbm_surface_release_buffer(data->gs, data->next_bo); /* Remove from the internal window list */
data->next_bo = NULL; SDL_VideoData *viddata = windata->viddata;
for (int i = 0; i < viddata->num_windows; i++) {
if (viddata->windows[i] == window) {
viddata->num_windows--;
for (int j = i; j < viddata->num_windows; j++) {
viddata->windows[j] = viddata->windows[j + 1];
} }
if (data->current_bo != NULL) {
KMSDRM_gbm_surface_release_buffer(data->gs, data->current_bo); break;
data->current_bo = NULL;
} }
#if SDL_VIDEO_OPENGL_EGL
SDL_EGL_MakeCurrent(_this, EGL_NO_SURFACE, EGL_NO_CONTEXT);
if (data->egl_surface != EGL_NO_SURFACE) {
SDL_EGL_DestroySurface(_this, data->egl_surface);
} }
#endif /* SDL_VIDEO_OPENGL_EGL */
if (data->gs != NULL) { KMSDRM_DestroySurfaces(_this, window);
KMSDRM_gbm_surface_destroy(data->gs);
data->gs = NULL;
}
SDL_free(data);
window->driverdata = NULL; window->driverdata = NULL;
}
SDL_free(windata);
} }
int int

View File

@ -28,7 +28,6 @@
#include <fcntl.h> #include <fcntl.h>
#include <unistd.h> #include <unistd.h>
#include <poll.h>
#include <xf86drm.h> #include <xf86drm.h>
#include <xf86drmMode.h> #include <xf86drmMode.h>
#include <gbm.h> #include <gbm.h>
@ -41,32 +40,39 @@ typedef struct SDL_VideoData
int devindex; /* device index that was passed on creation */ int devindex; /* device index that was passed on creation */
int drm_fd; /* DRM file desc */ int drm_fd; /* DRM file desc */
struct gbm_device *gbm; struct gbm_device *gbm;
drmEventContext drm_evctx; /* DRM event context */
struct pollfd drm_pollfd; /* pollfd containing DRM file desc */ SDL_Window **windows;
drmModeCrtc *saved_crtc; /* Saved CRTC to restore on quit */ int max_windows;
uint32_t saved_conn_id; /* Saved DRM connector ID */ int num_windows;
uint32_t crtc_id; /* CRTC in use */
} SDL_VideoData; } SDL_VideoData;
typedef struct SDL_DisplayModeData
{
int mode_index;
} SDL_DisplayModeData;
typedef struct SDL_DisplayData typedef struct SDL_DisplayData
{ {
uint32_t encoder_id;
uint32_t crtc_id; uint32_t crtc_id;
drmModeModeInfo cur_mode; drmModeConnector *conn;
drmModeModeInfo mode;
drmModeCrtc *saved_crtc; /* CRTC to restore on quit */
} SDL_DisplayData; } SDL_DisplayData;
typedef struct SDL_WindowData typedef struct SDL_WindowData
{ {
SDL_VideoData *viddata;
struct gbm_surface *gs; struct gbm_surface *gs;
struct gbm_bo *current_bo; struct gbm_bo *curr_bo;
struct gbm_bo *next_bo; struct gbm_bo *next_bo;
struct gbm_bo *crtc_bo; struct gbm_bo *crtc_bo;
SDL_bool waiting_for_flip; SDL_bool waiting_for_flip;
SDL_bool crtc_ready;
SDL_bool double_buffer; SDL_bool double_buffer;
#if SDL_VIDEO_OPENGL_EGL #if SDL_VIDEO_OPENGL_EGL
int egl_surface_dirty;
EGLSurface egl_surface; EGLSurface egl_surface;
#endif #endif
} SDL_WindowData; } SDL_WindowData;
@ -78,8 +84,9 @@ typedef struct KMSDRM_FBInfo
} KMSDRM_FBInfo; } KMSDRM_FBInfo;
/* Helper functions */ /* Helper functions */
int KMSDRM_CreateSurfaces(_THIS, SDL_Window * window);
KMSDRM_FBInfo *KMSDRM_FBFromBO(_THIS, struct gbm_bo *bo); KMSDRM_FBInfo *KMSDRM_FBFromBO(_THIS, struct gbm_bo *bo);
SDL_bool KMSDRM_WaitPageFlip(_THIS, SDL_WindowData *wdata, int timeout); SDL_bool KMSDRM_WaitPageFlip(_THIS, SDL_WindowData *windata, int timeout);
/****************************************************************************/ /****************************************************************************/
/* SDL_VideoDevice functions declaration */ /* SDL_VideoDevice functions declaration */