blob: d0526cf2e5d390cb0b1229644c921764231454f4 [file] [log] [blame]
/*
Simple DirectMedia Layer
Copyright (C) 1997-2020 Sam Lantinga <slouken@libsdl.org>
This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages
arising from the use of this software.
Permission is granted to anyone to use this software for any purpose,
including commercial applications, and to alter it and redistribute it
freely, subject to the following restrictions:
1. The origin of this software must not be misrepresented; you must not
claim that you wrote the original software. If you use this software
in a product, an acknowledgment in the product documentation would be
appreciated but is not required.
2. Altered source versions must be plainly marked as such, and must not be
misrepresented as being the original software.
3. This notice may not be removed or altered from any source distribution.
*/
#include "../../SDL_internal.h"
#if SDL_VIDEO_DRIVER_KMSDRM
/* SDL internals */
#include "../SDL_sysvideo.h"
#include "SDL_syswm.h"
#include "SDL_hints.h"
#include "../../events/SDL_events_c.h"
#include "../../events/SDL_mouse_c.h"
#include "../../events/SDL_keyboard_c.h"
#ifdef SDL_INPUT_LINUXEV
#include "../../core/linux/SDL_evdev.h"
#endif
/* KMS/DRM declarations */
#include "SDL_kmsdrmvideo.h"
#include "SDL_kmsdrmevents.h"
#include "SDL_kmsdrmopengles.h"
#include "SDL_kmsdrmmouse.h"
#include "SDL_kmsdrmdyn.h"
#include <sys/stat.h>
#include <dirent.h>
#include <errno.h>
#include <poll.h>
#define KMSDRM_DRI_PATH "/dev/dri/"
static int
check_modesetting(int devindex)
{
SDL_bool available = SDL_FALSE;
char device[512];
int drm_fd;
SDL_snprintf(device, sizeof (device), "%scard%d", KMSDRM_DRI_PATH, devindex);
SDL_LogDebug(SDL_LOG_CATEGORY_VIDEO, "check_modesetting: probing \"%s\"", device);
drm_fd = open(device, O_RDWR | O_CLOEXEC);
if (drm_fd >= 0) {
if (SDL_KMSDRM_LoadSymbols()) {
drmModeRes *resources = KMSDRM_drmModeGetResources(drm_fd);
if (resources) {
SDL_LogDebug(SDL_LOG_CATEGORY_VIDEO, "%scard%d connector, encoder and CRTC counts are: %d %d %d",
KMSDRM_DRI_PATH, devindex,
resources->count_connectors, resources->count_encoders, resources->count_crtcs);
if (resources->count_connectors > 0 && resources->count_encoders > 0 && resources->count_crtcs > 0) {
for (int i = 0; i < resources->count_connectors; i++) {
drmModeConnector *conn = KMSDRM_drmModeGetConnector(drm_fd, resources->connectors[i]);
if (!conn) {
continue;
}
if (conn->connection == DRM_MODE_CONNECTED && conn->count_modes) {
available = SDL_TRUE;
break;
}
KMSDRM_drmModeFreeConnector(conn);
}
}
KMSDRM_drmModeFreeResources(resources);
}
SDL_KMSDRM_UnloadSymbols();
}
close(drm_fd);
}
return available;
}
static int get_dricount(void)
{
int devcount = 0;
struct dirent *res;
struct stat sb;
DIR *folder;
if (!(stat(KMSDRM_DRI_PATH, &sb) == 0
&& S_ISDIR(sb.st_mode))) {
printf("The path %s cannot be opened or is not available\n",
KMSDRM_DRI_PATH);
return 0;
}
if (access(KMSDRM_DRI_PATH, F_OK) == -1) {
printf("The path %s cannot be opened\n",
KMSDRM_DRI_PATH);
return 0;
}
folder = opendir(KMSDRM_DRI_PATH);
if (folder) {
while ((res = readdir(folder))) {
int len = SDL_strlen(res->d_name);
if (len > 4 && SDL_strncmp(res->d_name, "card", 4) == 0) {
devcount++;
}
}
closedir(folder);
}
return devcount;
}
static int
get_driindex(void)
{
const int devcount = get_dricount();
int i;
for (i = 0; i < devcount; i++) {
if (check_modesetting(i)) {
return i;
}
}
return -ENOENT;
}
static int
KMSDRM_Available(void)
{
int ret = -ENOENT;
ret = get_driindex();
if (ret >= 0)
return 1;
return ret;
}
static void
KMSDRM_DeleteDevice(SDL_VideoDevice * device)
{
if (device->driverdata) {
SDL_free(device->driverdata);
device->driverdata = NULL;
}
SDL_free(device);
SDL_KMSDRM_UnloadSymbols();
}
static SDL_VideoDevice *
KMSDRM_CreateDevice(int devindex)
{
SDL_VideoDevice *device;
SDL_VideoData *viddata;
if (!KMSDRM_Available()) {
return NULL;
}
if (!devindex || (devindex > 99)) {
devindex = get_driindex();
}
if (devindex < 0) {
SDL_SetError("devindex (%d) must be between 0 and 99.\n", devindex);
return NULL;
}
if (!SDL_KMSDRM_LoadSymbols()) {
return NULL;
}
device = (SDL_VideoDevice *) SDL_calloc(1, sizeof(SDL_VideoDevice));
if (!device) {
SDL_OutOfMemory();
return NULL;
}
viddata = (SDL_VideoData *) SDL_calloc(1, sizeof(SDL_VideoData));
if (!viddata) {
SDL_OutOfMemory();
goto cleanup;
}
viddata->devindex = devindex;
viddata->drm_fd = -1;
device->driverdata = viddata;
/* Setup all functions that can be handled from this backend. */
device->VideoInit = KMSDRM_VideoInit;
device->VideoQuit = KMSDRM_VideoQuit;
device->GetDisplayModes = KMSDRM_GetDisplayModes;
device->SetDisplayMode = KMSDRM_SetDisplayMode;
device->CreateSDLWindow = KMSDRM_CreateWindow;
device->CreateSDLWindowFrom = KMSDRM_CreateWindowFrom;
device->SetWindowTitle = KMSDRM_SetWindowTitle;
device->SetWindowIcon = KMSDRM_SetWindowIcon;
device->SetWindowPosition = KMSDRM_SetWindowPosition;
device->SetWindowSize = KMSDRM_SetWindowSize;
device->ShowWindow = KMSDRM_ShowWindow;
device->HideWindow = KMSDRM_HideWindow;
device->RaiseWindow = KMSDRM_RaiseWindow;
device->MaximizeWindow = KMSDRM_MaximizeWindow;
device->MinimizeWindow = KMSDRM_MinimizeWindow;
device->RestoreWindow = KMSDRM_RestoreWindow;
device->SetWindowGrab = KMSDRM_SetWindowGrab;
device->DestroyWindow = KMSDRM_DestroyWindow;
device->GetWindowWMInfo = KMSDRM_GetWindowWMInfo;
#if SDL_VIDEO_OPENGL_EGL
device->GL_LoadLibrary = KMSDRM_GLES_LoadLibrary;
device->GL_GetProcAddress = KMSDRM_GLES_GetProcAddress;
device->GL_UnloadLibrary = KMSDRM_GLES_UnloadLibrary;
device->GL_CreateContext = KMSDRM_GLES_CreateContext;
device->GL_MakeCurrent = KMSDRM_GLES_MakeCurrent;
device->GL_SetSwapInterval = KMSDRM_GLES_SetSwapInterval;
device->GL_GetSwapInterval = KMSDRM_GLES_GetSwapInterval;
device->GL_SwapWindow = KMSDRM_GLES_SwapWindow;
device->GL_DeleteContext = KMSDRM_GLES_DeleteContext;
#endif
device->PumpEvents = KMSDRM_PumpEvents;
device->free = KMSDRM_DeleteDevice;
return device;
cleanup:
if (device)
SDL_free(device);
if (viddata)
SDL_free(viddata);
return NULL;
}
VideoBootStrap KMSDRM_bootstrap = {
"KMSDRM",
"KMS/DRM Video Driver",
KMSDRM_CreateDevice
};
static void
KMSDRM_FBDestroyCallback(struct gbm_bo *bo, void *data)
{
KMSDRM_FBInfo *fb_info = (KMSDRM_FBInfo *)data;
if (fb_info && fb_info->drm_fd >= 0 && fb_info->fb_id != 0) {
KMSDRM_drmModeRmFB(fb_info->drm_fd, fb_info->fb_id);
SDL_LogDebug(SDL_LOG_CATEGORY_VIDEO, "Delete DRM FB %u", fb_info->fb_id);
}
SDL_free(fb_info);
}
KMSDRM_FBInfo *
KMSDRM_FBFromBO(_THIS, struct gbm_bo *bo)
{
SDL_VideoData *viddata = ((SDL_VideoData *)_this->driverdata);
unsigned w,h;
int ret;
Uint32 stride, handle;
/* Check for an existing framebuffer */
KMSDRM_FBInfo *fb_info = (KMSDRM_FBInfo *)KMSDRM_gbm_bo_get_user_data(bo);
if (fb_info) {
return fb_info;
}
/* 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));
if (!fb_info) {
SDL_OutOfMemory();
return NULL;
}
fb_info->drm_fd = viddata->drm_fd;
/* Create framebuffer object for the buffer */
w = KMSDRM_gbm_bo_get_width(bo);
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(viddata->drm_fd, w, h, 24, 32, stride, handle,
&fb_info->fb_id);
if (ret) {
SDL_free(fb_info);
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);
/* Associate our DRM framebuffer with this buffer object */
KMSDRM_gbm_bo_set_user_data(bo, fb_info, KMSDRM_FBDestroyCallback);
return fb_info;
}
static void
KMSDRM_FlipHandler(int fd, unsigned int frame, unsigned int sec, unsigned int usec, void *data)
{
/* If the data pointer received here is the same passed as the user_data in drmModePageFlip()
then this is the event handler for the pageflip that was issued on drmPageFlip(): got here
because of that precise page flip, the while loop gets broken here because of the right event.
This knowledge will allow handling different issued pageflips if sometime in the future
managing different CRTCs in SDL2 is needed, for example (synchronous pageflips happen on vblank
and vblank is a CRTC thing). */
*((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};
struct pollfd pfd = {0};
ev.version = DRM_EVENT_CONTEXT_VERSION;
ev.page_flip_handler = KMSDRM_FlipHandler;
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;
}
/* Is the fd readable? Thats enough to call drmHandleEvent() on it. */
if (pfd.revents & POLLIN) {
/* Page flip? ONLY if the event that made the fd readable (=POLLIN state)
is a page flip, will drmHandleEvent call page_flip_handler, which will break the loop.
The drmHandleEvent() and subsequent page_flip_handler calls are both synchronous (blocking),
nothing runs on a different thread, so no need to protect waiting_for_flip access with mutexes. */
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 */
/* _this is a SDL_VideoDevice * */
/*****************************************************************************/
static void
KMSDRM_DestroySurfaces(_THIS, SDL_Window * window)
{
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 SDL_VIDEO_OPENGL_EGL
EGLContext egl_context;
#endif
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);
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 = SDL_FALSE;
#endif
/* We can't call KMSDRM_SetCRTC() until we have a fb_id, in KMSDRM_GLES_SwapWindow().
So we take note here to do it there. */
windata->crtc_setup_pending = SDL_TRUE;
return 0;
}
int
KMSDRM_VideoInit(_THIS)
{
int ret = 0;
SDL_VideoData *viddata = ((SDL_VideoData *)_this->driverdata);
SDL_DisplayData *dispdata = NULL;
drmModeRes *resources = NULL;
drmModeEncoder *encoder = NULL;
char devname[32];
SDL_VideoDisplay display = {0};
dispdata = (SDL_DisplayData *) SDL_calloc(1, sizeof(SDL_DisplayData));
if (!dispdata) {
return SDL_OutOfMemory();
}
SDL_LogDebug(SDL_LOG_CATEGORY_VIDEO, "KMSDRM_VideoInit()");
/* Open /dev/dri/cardNN */
SDL_snprintf(devname, sizeof(devname), "/dev/dri/card%d", viddata->devindex);
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;
}
SDL_LogDebug(SDL_LOG_CATEGORY_VIDEO, "Opened DRM FD (%d)", viddata->drm_fd);
viddata->gbm = KMSDRM_gbm_create_device(viddata->drm_fd);
if (!viddata->gbm) {
ret = SDL_SetError("Couldn't create gbm device.");
goto cleanup;
}
/* Get all of the available connectors / devices / crtcs */
resources = KMSDRM_drmModeGetResources(viddata->drm_fd);
if (!resources) {
ret = SDL_SetError("drmModeGetResources(%d) failed", viddata->drm_fd);
goto cleanup;
}
for (int i = 0; i < resources->count_connectors; i++) {
drmModeConnector *conn = KMSDRM_drmModeGetConnector(viddata->drm_fd, resources->connectors[i]);
if (!conn) {
continue;
}
if (conn->connection == DRM_MODE_CONNECTED && conn->count_modes) {
SDL_LogDebug(SDL_LOG_CATEGORY_VIDEO, "Found connector %d with %d modes.",
conn->connector_id, conn->count_modes);
dispdata->conn = conn;
break;
}
KMSDRM_drmModeFreeConnector(conn);
}
if (!dispdata->conn) {
ret = SDL_SetError("No currently active connector found.");
goto cleanup;
}
/* 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]);
if (!encoder) {
continue;
}
if (encoder->encoder_id == dispdata->conn->encoder_id) {
SDL_LogDebug(SDL_LOG_CATEGORY_VIDEO, "Found encoder %d.", encoder->encoder_id);
break;
}
KMSDRM_drmModeFreeEncoder(encoder);
encoder = NULL;
}
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.");
goto cleanup;
}
SDL_LogDebug(SDL_LOG_CATEGORY_VIDEO, "Found encoder %d.", encoder->encoder_id);
/* Try to find a CRTC connected to this encoder */
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)) {
encoder->crtc_id = resources->crtcs[i];
dispdata->saved_crtc = KMSDRM_drmModeGetCrtc(viddata->drm_fd, encoder->crtc_id);
break;
}
}
}
if (!dispdata->saved_crtc) {
ret = SDL_SetError("No CRTC found.");
goto cleanup;
}
SDL_LogDebug(SDL_LOG_CATEGORY_VIDEO, "Saved crtc_id %u, fb_id %u, (%u,%u), %ux%u",
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,
"Current mode is invalid, selecting connector's mode #0.");
dispdata->mode = dispdata->conn->modes[0];
}
/* Setup the single display that's available */
display.desktop_mode.w = dispdata->mode.hdisplay;
display.desktop_mode.h = dispdata->mode.vdisplay;
display.desktop_mode.refresh_rate = dispdata->mode.vrefresh;
#if 1
display.desktop_mode.format = SDL_PIXELFORMAT_ARGB8888;
#else
/* FIXME */
drmModeFB *fb = drmModeGetFB(viddata->drm_fd, dispdata->saved_crtc->buffer_id);
display.desktop_mode.format = drmToSDLPixelFormat(fb->bpp, fb->depth);
drmModeFreeFB(fb);
#endif
/* DRM mode index for the desktop mode is needed to complete desktop mode init NOW,
so look for it in the DRM modes array. */
for (int i = 0; i < dispdata->conn->count_modes; i++) {
if (!SDL_memcmp(dispdata->conn->modes + i, &dispdata->saved_crtc->mode, sizeof(drmModeModeInfo))) {
SDL_DisplayModeData *modedata = SDL_calloc(1, sizeof(SDL_DisplayModeData));
if (modedata) {
modedata->mode_index = i;
display.desktop_mode.driverdata = modedata;
}
}
}
display.current_mode = display.desktop_mode;
display.driverdata = dispdata;
SDL_AddVideoDisplay(&display);
#ifdef SDL_INPUT_LINUXEV
SDL_EVDEV_Init();
#endif
KMSDRM_InitMouse(_this);
return ret;
cleanup:
if (encoder)
KMSDRM_drmModeFreeEncoder(encoder);
if (resources)
KMSDRM_drmModeFreeResources(resources);
if (ret != 0) {
/* Error (complete) cleanup */
if (dispdata->conn) {
KMSDRM_drmModeFreeConnector(dispdata->conn);
dispdata->conn = NULL;
}
if (dispdata->saved_crtc) {
KMSDRM_drmModeFreeCrtc(dispdata->saved_crtc);
dispdata->saved_crtc = NULL;
}
if (viddata->gbm) {
KMSDRM_gbm_device_destroy(viddata->gbm);
viddata->gbm = NULL;
}
if (viddata->drm_fd >= 0) {
close(viddata->drm_fd);
viddata->drm_fd = -1;
}
SDL_free(dispdata);
}
return ret;
}
void
KMSDRM_VideoQuit(_THIS)
{
SDL_VideoData *viddata = ((SDL_VideoData *)_this->driverdata);
SDL_DisplayData *dispdata = (SDL_DisplayData *)SDL_GetDisplayDriverData(0);
SDL_LogDebug(SDL_LOG_CATEGORY_VIDEO, "KMSDRM_VideoQuit()");
if (_this->gl_config.driver_loaded) {
SDL_GL_UnloadLibrary();
}
/* Clear out the window list */
SDL_free(viddata->windows);
viddata->windows = NULL;
viddata->max_windows = 0;
viddata->num_windows = 0;
/* Restore saved CRTC settings */
if (viddata->drm_fd >= 0 && dispdata && dispdata->conn && dispdata->saved_crtc) {
drmModeConnector *conn = dispdata->conn;
drmModeCrtc *crtc = dispdata->saved_crtc;
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");
}
}
if (dispdata && dispdata->conn) {
KMSDRM_drmModeFreeConnector(dispdata->conn);
dispdata->conn = NULL;
}
if (dispdata && dispdata->saved_crtc) {
KMSDRM_drmModeFreeCrtc(dispdata->saved_crtc);
dispdata->saved_crtc = NULL;
}
if (viddata->gbm) {
KMSDRM_gbm_device_destroy(viddata->gbm);
viddata->gbm = NULL;
}
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
SDL_EVDEV_Quit();
#endif
}
void
KMSDRM_GetDisplayModes(_THIS, SDL_VideoDisplay * display)
{
SDL_DisplayData *dispdata = display->driverdata;
drmModeConnector *conn = dispdata->conn;
SDL_DisplayMode mode;
for (int i = 0; i < conn->count_modes; i++) {
SDL_DisplayModeData *modedata = SDL_calloc(1, sizeof(SDL_DisplayModeData));
if (modedata) {
modedata->mode_index = i;
}
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
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;
drmModeConnector *conn = dispdata->conn;
if (!modedata) {
return SDL_SetError("Mode doesn't have an associated index");
}
dispdata->mode = conn->modes[modedata->mode_index];
for (int i = 0; i < viddata->num_windows; i++) {
SDL_Window *window = viddata->windows[i];
/* Re-create GBM and EGL surfaces everytime we change the display mode. */
if (KMSDRM_CreateSurfaces(_this, window)) {
return -1;
}
/* Tell app about the resize */
SDL_SendWindowEvent(window, SDL_WINDOWEVENT_RESIZED, mode->w, mode->h);
}
return 0;
}
int
KMSDRM_CreateWindow(_THIS, SDL_Window * window)
{
SDL_VideoData *viddata = (SDL_VideoData *)_this->driverdata;
SDL_WindowData *windata;
#if SDL_VIDEO_OPENGL_EGL
if (!_this->egl_data) {
if (SDL_GL_LoadLibrary(NULL) < 0) {
goto error;
}
}
#endif
/* Allocate window internal data */
windata = (SDL_WindowData *)SDL_calloc(1, sizeof(SDL_WindowData));
if (!windata) {
SDL_OutOfMemory();
goto error;
}
/* Init windata fields. */
windata->waiting_for_flip = SDL_FALSE;
windata->double_buffer = SDL_FALSE;
windata->crtc_setup_pending = SDL_FALSE;
windata->egl_surface_dirty = SDL_FALSE;
/* First remember that certain functions in SDL_Video.c will call *_SetDisplayMode when the
SDL_WINDOW_FULLSCREEN is set and SDL_WINDOW_FULLSCREEN_DESKTOP is not set.
So I am determining here that the behavior when creating an SDL_Window() in KMSDRM, is:
-Creating a normal non-fullscreen window won't change the display mode.
They won't cover the full screen area, either, because that breaks the image aspect ratio.
-Creating a SDL_WINDOW_FULLSCREEN window will change the display mode,
because SDL_WINDOW_FULLSCREEN flag is set.
-Creating a SDL_WINDOW_FULLSCREEN_DESKTOP window will not change the display mode,
because even if the SDL_WINDOW_FULLSCREEN flag is set, SDL_WINDOW_FULLSCREEN_DESKTOP prevents it.
If we ever decide that we want to have normal windows (non-SDL_WINDOW_FULLSCREEN) should cause a display
mode change, we could force the SDL_WINDOW_FULLSCREEN flag again on every window.
But remember that it will break games that check if a window is FULLSCREEN or not before setting
a fullscreen mode with SDL_SetWindowFullscreen(), like sm64ex (sm64 pc port).
If we ever decide that we want normal windows to cover the whole screen area, we can force window->w
and window->h to the original display mode dimensions.
Commented reference code for all this:
window->flags |= (SDL_WINDOW_FULLSCREEN | SDL_WINDOW_OPENGL);
SDL_VideoDisplay *display = SDL_GetDisplayForWindow(window);
window->w = display->desktop_mode.w;
window->h = display->desktop_mode.h; */
/* In case low-latency is wanted, double-buffered video will be used. We take note here */
if (SDL_GetHintBoolean(SDL_HINT_VIDEO_DOUBLE_BUFFER, SDL_FALSE)) {
windata->double_buffer = SDL_TRUE;
}
/* Setup driver data for this window */
windata->viddata = viddata;
window->driverdata = windata;
if (KMSDRM_CreateSurfaces(_this, window)) {
goto error;
}
/* 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 */
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;
/* Focus on the newly created window */
SDL_SetMouseFocus(window);
SDL_SetKeyboardFocus(window);
return 0;
error:
KMSDRM_DestroyWindow(_this, window);
return -1;
}
void
KMSDRM_DestroyWindow(_THIS, SDL_Window * window)
{
SDL_WindowData *windata = (SDL_WindowData *) window->driverdata;
SDL_VideoData *viddata;
if (!windata) {
return;
}
/* Remove from the internal window list */
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];
}
break;
}
}
KMSDRM_DestroySurfaces(_this, window);
window->driverdata = NULL;
SDL_free(windata);
}
int
KMSDRM_CreateWindowFrom(_THIS, SDL_Window * window, const void *data)
{
return -1;
}
void
KMSDRM_SetWindowTitle(_THIS, SDL_Window * window)
{
}
void
KMSDRM_SetWindowIcon(_THIS, SDL_Window * window, SDL_Surface * icon)
{
}
void
KMSDRM_SetWindowPosition(_THIS, SDL_Window * window)
{
}
void
KMSDRM_SetWindowSize(_THIS, SDL_Window * window)
{
}
void
KMSDRM_ShowWindow(_THIS, SDL_Window * window)
{
}
void
KMSDRM_HideWindow(_THIS, SDL_Window * window)
{
}
void
KMSDRM_RaiseWindow(_THIS, SDL_Window * window)
{
}
void
KMSDRM_MaximizeWindow(_THIS, SDL_Window * window)
{
}
void
KMSDRM_MinimizeWindow(_THIS, SDL_Window * window)
{
}
void
KMSDRM_RestoreWindow(_THIS, SDL_Window * window)
{
}
void
KMSDRM_SetWindowGrab(_THIS, SDL_Window * window, SDL_bool grabbed)
{
}
/*****************************************************************************/
/* SDL Window Manager function */
/*****************************************************************************/
SDL_bool
KMSDRM_GetWindowWMInfo(_THIS, SDL_Window * window, struct SDL_SysWMinfo *info)
{
if (info->version.major <= SDL_MAJOR_VERSION) {
return SDL_TRUE;
} else {
SDL_SetError("application not compiled with SDL %d.%d\n",
SDL_MAJOR_VERSION, SDL_MINOR_VERSION);
return SDL_FALSE;
}
/* Failed to get window manager information */
return SDL_FALSE;
}
#endif /* SDL_VIDEO_DRIVER_KMSDRM */
/* vi: set ts=4 sw=4 expandtab: */