#include "../../SDL_internal.h"
#if SDL_VIDEO_DRIVER_KMSDRM
#include "../SDL_sysvideo.h"
#include "SDL_syswm.h"
#include "SDL_log.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"
#elif defined SDL_INPUT_WSCONS
#include "../../core/openbsd/SDL_wscons.h"
#endif
#include "SDL_kmsdrmvideo.h"
#include "SDL_kmsdrmevents.h"
#include "SDL_kmsdrmopengles.h"
#include "SDL_kmsdrmmouse.h"
#include "SDL_kmsdrmdyn.h"
#include "SDL_kmsdrmvulkan.h"
#include <sys/stat.h>
#include <sys/param.h>
#include <sys/utsname.h>
#include <dirent.h>
#include <poll.h>
#include <errno.h>
#ifdef __OpenBSD__
static SDL_bool moderndri = SDL_FALSE;
#else
static SDL_bool moderndri = SDL_TRUE;
#endif
static char kmsdrm_dri_path[16];
static int kmsdrm_dri_pathsize = 0;
static char kmsdrm_dri_devname[8];
static int kmsdrm_dri_devnamesize = 0;
static char kmsdrm_dri_cardpath[32];
#ifndef EGL_PLATFORM_GBM_MESA
#define EGL_PLATFORM_GBM_MESA 0x31D7
#endif
static int
get_driindex(void)
{
int available = -ENOENT;
char device[sizeof(kmsdrm_dri_cardpath)];
int drm_fd;
int i;
int devindex = -1;
DIR *folder;
const char *hint;
hint = SDL_GetHint(SDL_HINT_KMSDRM_DEVICE_INDEX);
if (hint && *hint) {
char *endptr = NULL;
const int idx = (int) SDL_strtol(hint, &endptr, 10);
if ((*endptr == '\0') && (idx >= 0)) {
return idx;
}
}
SDL_strlcpy(device, kmsdrm_dri_path, sizeof(device));
folder = opendir(device);
if (!folder) {
SDL_SetError("Failed to open directory '%s'", device);
return -ENOENT;
}
SDL_strlcpy(device + kmsdrm_dri_pathsize, kmsdrm_dri_devname,
sizeof(device) - kmsdrm_dri_devnamesize);
for (struct dirent *res; (res = readdir(folder));) {
if (SDL_memcmp(res->d_name, kmsdrm_dri_devname,
kmsdrm_dri_devnamesize) == 0) {
SDL_strlcpy(device + kmsdrm_dri_pathsize + kmsdrm_dri_devnamesize,
res->d_name + kmsdrm_dri_devnamesize,
sizeof(device) - kmsdrm_dri_pathsize -
kmsdrm_dri_devnamesize);
drm_fd = open(device, O_RDWR | O_CLOEXEC);
if (drm_fd >= 0) {
devindex = SDL_atoi(device + kmsdrm_dri_pathsize +
kmsdrm_dri_devnamesize);
if (SDL_KMSDRM_LoadSymbols()) {
drmModeRes *resources = KMSDRM_drmModeGetResources(drm_fd);
if (resources) {
SDL_LogDebug(SDL_LOG_CATEGORY_VIDEO,
"%s%d connector, encoder and CRTC counts are: %d %d %d",
kmsdrm_dri_cardpath, devindex,
resources->count_connectors,
resources->count_encoders,
resources->count_crtcs);
if (resources->count_connectors > 0 &&
resources->count_encoders > 0 &&
resources->count_crtcs > 0) {
available = -ENOENT;
for (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) {
if (SDL_GetHintBoolean(
SDL_HINT_KMSDRM_REQUIRE_DRM_MASTER,
SDL_TRUE)) {
KMSDRM_drmSetMaster(drm_fd);
if (KMSDRM_drmAuthMagic(drm_fd, 0) ==
-EACCES) {
continue;
}
}
available = devindex;
break;
}
KMSDRM_drmModeFreeConnector(conn);
}
}
KMSDRM_drmModeFreeResources(resources);
}
SDL_KMSDRM_UnloadSymbols();
}
close(drm_fd);
}
SDL_LogDebug(SDL_LOG_CATEGORY_VIDEO,
"Failed to open KMSDRM device %s, errno: %d\n", device,
errno);
}
}
closedir(folder);
return available;
}
static int
KMSDRM_Available(void)
{
#ifdef __OpenBSD__
struct utsname nameofsystem;
double releaseversion;
#endif
int ret = -ENOENT;
#ifdef __OpenBSD__
if (!(uname(&nameofsystem) < 0)) {
releaseversion = SDL_atof(nameofsystem.release);
if (releaseversion >= 6.9) {
moderndri = SDL_TRUE;
}
}
#endif
if (moderndri) {
SDL_strlcpy(kmsdrm_dri_path, "/dev/dri/", sizeof(kmsdrm_dri_path));
SDL_strlcpy(kmsdrm_dri_devname, "card", sizeof(kmsdrm_dri_devname));
} else {
SDL_strlcpy(kmsdrm_dri_path, "/dev/", sizeof(kmsdrm_dri_path));
SDL_strlcpy(kmsdrm_dri_devname, "drm", sizeof(kmsdrm_dri_devname));
}
kmsdrm_dri_pathsize = SDL_strlen(kmsdrm_dri_path);
kmsdrm_dri_devnamesize = SDL_strlen(kmsdrm_dri_devname);
SDL_snprintf(kmsdrm_dri_cardpath, sizeof(kmsdrm_dri_cardpath), "%s%s",
kmsdrm_dri_path, kmsdrm_dri_devname);
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(void)
{
SDL_VideoDevice *device;
SDL_VideoData *viddata;
int devindex;
if (!KMSDRM_Available()) {
return NULL;
}
devindex = get_driindex();
if (devindex < 0) {
SDL_SetError("devindex (%d) must not be negative.", 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;
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->SetWindowFullscreen = KMSDRM_SetWindowFullscreen;
device->GetWindowGammaRamp = KMSDRM_GetWindowGammaRamp;
device->SetWindowGammaRamp = KMSDRM_SetWindowGammaRamp;
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->DestroyWindow = KMSDRM_DestroyWindow;
device->GetWindowWMInfo = KMSDRM_GetWindowWMInfo;
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;
device->GL_DefaultProfileConfig = KMSDRM_GLES_DefaultProfileConfig;
#if SDL_VIDEO_VULKAN
device->Vulkan_LoadLibrary = KMSDRM_Vulkan_LoadLibrary;
device->Vulkan_UnloadLibrary = KMSDRM_Vulkan_UnloadLibrary;
device->Vulkan_GetInstanceExtensions = KMSDRM_Vulkan_GetInstanceExtensions;
device->Vulkan_CreateSurface = KMSDRM_Vulkan_CreateSurface;
device->Vulkan_GetDrawableSize = KMSDRM_Vulkan_GetDrawableSize;
#endif
device->PumpEvents = KMSDRM_PumpEvents;
device->free = KMSDRM_DeleteDevice;
return device;
cleanup:
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;
KMSDRM_FBInfo *fb_info = (KMSDRM_FBInfo *)KMSDRM_gbm_bo_get_user_data(bo);
if (fb_info) {
return fb_info;
}
fb_info = (KMSDRM_FBInfo *)SDL_calloc(1, sizeof(KMSDRM_FBInfo));
if (!fb_info) {
SDL_OutOfMemory();
return NULL;
}
fb_info->drm_fd = viddata->drm_fd;
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);
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)
{
*((SDL_bool *) data) = SDL_FALSE;
}
SDL_bool
KMSDRM_WaitPageflip(_THIS, SDL_WindowData *windata) {
SDL_VideoData *viddata = ((SDL_VideoData *)_this->driverdata);
drmEventContext ev = {0};
struct pollfd pfd = {0};
int ret;
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;
ret = poll(&pfd, 1, -1);
if (ret < 0) {
if (errno == EINTR) {
continue;
} else {
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) {
KMSDRM_drmHandleEvent(viddata->drm_fd, &ev);
}
}
return SDL_TRUE;
}
static drmModeModeInfo*
KMSDRM_GetClosestDisplayMode(SDL_VideoDisplay * display,
uint32_t width, uint32_t height, uint32_t refresh_rate){
SDL_DisplayData *dispdata = (SDL_DisplayData *) display->driverdata;
drmModeConnector *connector = dispdata->connector;
SDL_DisplayMode target, closest;
drmModeModeInfo *drm_mode;
target.w = width;
target.h = height;
target.format = 0;
target.refresh_rate = refresh_rate;
target.driverdata = 0;
if (!SDL_GetClosestDisplayMode(SDL_atoi(display->name), &target, &closest)) {
return NULL;
} else {
SDL_DisplayModeData *modedata = (SDL_DisplayModeData *)closest.driverdata;
drm_mode = &connector->modes[modedata->mode_index];
return drm_mode;
}
}
static void
KMSDRM_DeinitDisplays (_THIS) {
SDL_DisplayData *dispdata;
int num_displays, i;
num_displays = SDL_GetNumVideoDisplays();
for (i = 0; i < num_displays; i++) {
dispdata = (SDL_DisplayData *)SDL_GetDisplayDriverData(i);
if (dispdata && dispdata->connector) {
KMSDRM_drmModeFreeConnector(dispdata->connector);
dispdata->connector = NULL;
}
if (dispdata && dispdata->crtc) {
KMSDRM_drmModeFreeCrtc(dispdata->crtc);
dispdata->crtc = NULL;
}
}
}
static uint32_t
KMSDRM_CrtcGetPropId(uint32_t drm_fd,
drmModeObjectPropertiesPtr props,
char const* name)
{
uint32_t i, prop_id = 0;
for (i = 0; !prop_id && i < props->count_props; ++i) {
drmModePropertyPtr drm_prop =
KMSDRM_drmModeGetProperty(drm_fd, props->props[i]);
if (!drm_prop)
continue;
if (strcmp(drm_prop->name, name) == 0)
prop_id = drm_prop->prop_id;
KMSDRM_drmModeFreeProperty(drm_prop);
}
return prop_id;
}
static SDL_bool KMSDRM_VrrPropId(uint32_t drm_fd, uint32_t crtc_id, uint32_t *vrr_prop_id) {
drmModeObjectPropertiesPtr drm_props;
drm_props = KMSDRM_drmModeObjectGetProperties(drm_fd,
crtc_id,
DRM_MODE_OBJECT_CRTC);
if (!drm_props)
return SDL_FALSE;
*vrr_prop_id = KMSDRM_CrtcGetPropId(drm_fd,
drm_props,
"VRR_ENABLED");
KMSDRM_drmModeFreeObjectProperties(drm_props);
return SDL_TRUE;
}
static SDL_bool
KMSDRM_ConnectorCheckVrrCapable(uint32_t drm_fd,
uint32_t output_id,
char const* name)
{
uint32_t i;
SDL_bool found = SDL_FALSE;
uint64_t prop_value = 0;
drmModeObjectPropertiesPtr props = KMSDRM_drmModeObjectGetProperties(drm_fd,
output_id,
DRM_MODE_OBJECT_CONNECTOR);
if(!props)
return SDL_FALSE;
for (i = 0; !found && i < props->count_props; ++i) {
drmModePropertyPtr drm_prop = KMSDRM_drmModeGetProperty(drm_fd, props->props[i]);
if (!drm_prop)
continue;
if (strcasecmp(drm_prop->name, name) == 0) {
prop_value = props->prop_values[i];
found = SDL_TRUE;
}
KMSDRM_drmModeFreeProperty(drm_prop);
}
if(found)
return prop_value ? SDL_TRUE: SDL_FALSE;
return SDL_FALSE;
}
void
KMSDRM_CrtcSetVrr(uint32_t drm_fd, uint32_t crtc_id, SDL_bool enabled)
{
uint32_t vrr_prop_id;
if (!KMSDRM_VrrPropId(drm_fd, crtc_id, &vrr_prop_id))
return;
KMSDRM_drmModeObjectSetProperty(drm_fd,
crtc_id,
DRM_MODE_OBJECT_CRTC,
vrr_prop_id,
enabled);
}
static SDL_bool
KMSDRM_CrtcGetVrr(uint32_t drm_fd, uint32_t crtc_id)
{
uint32_t object_prop_id, vrr_prop_id;
drmModeObjectPropertiesPtr props;
SDL_bool object_prop_value;
int i;
if (!KMSDRM_VrrPropId(drm_fd, crtc_id, &vrr_prop_id))
return SDL_FALSE;
props = KMSDRM_drmModeObjectGetProperties(drm_fd,
crtc_id,
DRM_MODE_OBJECT_CRTC);
if(!props)
return SDL_FALSE;
for (i = 0; i < props->count_props; ++i) {
drmModePropertyPtr drm_prop = KMSDRM_drmModeGetProperty(drm_fd, props->props[i]);
if (!drm_prop)
continue;
object_prop_id = drm_prop->prop_id;
object_prop_value = props->prop_values[i] ? SDL_TRUE : SDL_FALSE;
KMSDRM_drmModeFreeProperty(drm_prop);
if (object_prop_id == vrr_prop_id) {
return object_prop_value;
}
}
return SDL_FALSE;
}
static void
KMSDRM_AddDisplay (_THIS, drmModeConnector *connector, drmModeRes *resources)
{
SDL_VideoData *viddata = ((SDL_VideoData *)_this->driverdata);
SDL_DisplayData *dispdata = NULL;
SDL_VideoDisplay display = {0};
SDL_DisplayModeData *modedata = NULL;
drmModeEncoder *encoder = NULL;
drmModeCrtc *crtc = NULL;
int mode_index;
int i, j;
int ret = 0;
dispdata = (SDL_DisplayData *) SDL_calloc(1, sizeof(SDL_DisplayData));
if (!dispdata) {
ret = SDL_OutOfMemory();
goto cleanup;
}
dispdata->cursor_bo = NULL;
dispdata->cursor_bo_drm_fd = -1;
dispdata->default_cursor_init = SDL_FALSE;
for (i = 0; i < resources->count_encoders; i++) {
encoder = KMSDRM_drmModeGetEncoder(viddata->drm_fd, resources->encoders[i]);
if (!encoder) {
continue;
}
if (encoder->encoder_id == connector->encoder_id) {
break;
}
KMSDRM_drmModeFreeEncoder(encoder);
encoder = NULL;
}
if (!encoder) {
for (i = 0; i < resources->count_encoders; i++) {
encoder = KMSDRM_drmModeGetEncoder(viddata->drm_fd,
resources->encoders[i]);
if (!encoder) {
continue;
}
for (j = 0; j < connector->count_encoders; j++) {
if (connector->encoders[j] == encoder->encoder_id) {
break;
}
}
if (j != connector->count_encoders) {
break;
}
KMSDRM_drmModeFreeEncoder(encoder);
encoder = NULL;
}
}
if (!encoder) {
ret = SDL_SetError("No connected encoder found for connector.");
goto cleanup;
}
crtc = KMSDRM_drmModeGetCrtc(viddata->drm_fd, encoder->crtc_id);
if (!crtc) {
for (i = 0; i < resources->count_crtcs; i++) {
if (encoder->possible_crtcs & (1 << i)) {
encoder->crtc_id = resources->crtcs[i];
crtc = KMSDRM_drmModeGetCrtc(viddata->drm_fd, encoder->crtc_id);
break;
}
}
}
if (!crtc) {
ret = SDL_SetError("No CRTC found for connector.");
goto cleanup;
}
mode_index = -1;
for (i = 0; i < connector->count_modes; i++) {
drmModeModeInfo *mode = &connector->modes[i];
if (!SDL_memcmp(mode, &crtc->mode, sizeof(crtc->mode))) {
mode_index = i;
break;
}
}
if (mode_index == -1) {
int current_area, largest_area = 0;
for (i = 0; i < connector->count_modes; i++) {
drmModeModeInfo *mode = &connector->modes[i];
if (mode->type & DRM_MODE_TYPE_PREFERRED) {
mode_index = i;
break;
}
current_area = mode->hdisplay * mode->vdisplay;
if (current_area > largest_area) {
mode_index = i;
largest_area = current_area;
}
}
if (mode_index != -1) {
crtc->mode = connector->modes[mode_index];
}
}
if (mode_index == -1) {
ret = SDL_SetError("Failed to find index of mode attached to the CRTC.");
goto cleanup;
}
dispdata->mode = crtc->mode;
dispdata->original_mode = crtc->mode;
dispdata->fullscreen_mode = crtc->mode;
if (dispdata->mode.hdisplay == 0 || dispdata->mode.vdisplay == 0 ) {
ret = SDL_SetError("Couldn't get a valid connector videomode.");
goto cleanup;
}
dispdata->connector = connector;
dispdata->crtc = crtc;
dispdata->saved_vrr = KMSDRM_CrtcGetVrr(viddata->drm_fd, crtc->crtc_id);
if(KMSDRM_ConnectorCheckVrrCapable(viddata->drm_fd, connector->connector_id, "VRR_CAPABLE")) {
SDL_LogDebug(SDL_LOG_CATEGORY_VIDEO, "Enabling VRR");
KMSDRM_CrtcSetVrr(viddata->drm_fd, crtc->crtc_id, SDL_TRUE);
}
modedata = SDL_calloc(1, sizeof(SDL_DisplayModeData));
if (!modedata) {
ret = SDL_OutOfMemory();
goto cleanup;
}
modedata->mode_index = mode_index;
display.driverdata = dispdata;
display.desktop_mode.w = dispdata->mode.hdisplay;
display.desktop_mode.h = dispdata->mode.vdisplay;
display.desktop_mode.refresh_rate = dispdata->mode.vrefresh;
display.desktop_mode.format = SDL_PIXELFORMAT_ARGB8888;
display.desktop_mode.driverdata = modedata;
display.current_mode = display.desktop_mode;
SDL_AddVideoDisplay(&display, SDL_FALSE);
cleanup:
if (encoder)
KMSDRM_drmModeFreeEncoder(encoder);
if (ret) {
if (dispdata) {
if (dispdata->connector) {
KMSDRM_drmModeFreeConnector(dispdata->connector);
dispdata->connector = NULL;
}
if (dispdata->crtc) {
KMSDRM_drmModeFreeCrtc(dispdata->crtc);
dispdata->crtc = NULL;
}
SDL_free(dispdata);
}
}
}
static int
KMSDRM_InitDisplays (_THIS) {
SDL_VideoData *viddata = ((SDL_VideoData *)_this->driverdata);
drmModeRes *resources = NULL;
uint64_t async_pageflip = 0;
int ret = 0;
int i;
SDL_snprintf(viddata->devpath, sizeof(viddata->devpath), "%s%d",
kmsdrm_dri_cardpath, viddata->devindex);
SDL_LogDebug(SDL_LOG_CATEGORY_VIDEO, "Opening device %s", viddata->devpath);
viddata->drm_fd = open(viddata->devpath, O_RDWR | O_CLOEXEC);
if (viddata->drm_fd < 0) {
ret = SDL_SetError("Could not open %s", viddata->devpath);
goto cleanup;
}
SDL_LogDebug(SDL_LOG_CATEGORY_VIDEO, "Opened DRM FD (%d)", viddata->drm_fd);
resources = KMSDRM_drmModeGetResources(viddata->drm_fd);
if (!resources) {
ret = SDL_SetError("drmModeGetResources(%d) failed", viddata->drm_fd);
goto cleanup;
}
for (i = 0; i < resources->count_connectors; i++) {
drmModeConnector *connector = KMSDRM_drmModeGetConnector(viddata->drm_fd,
resources->connectors[i]);
if (!connector) {
continue;
}
if (connector->connection == DRM_MODE_CONNECTED && connector->count_modes) {
KMSDRM_AddDisplay(_this, connector, resources);
}
else {
KMSDRM_drmModeFreeConnector(connector);
}
}
if (!SDL_GetNumVideoDisplays()) {
ret = SDL_SetError("No connected displays found.");
goto cleanup;
}
ret = KMSDRM_drmGetCap(viddata->drm_fd, DRM_CAP_ASYNC_PAGE_FLIP, &async_pageflip);
if (ret) {
SDL_LogError(SDL_LOG_CATEGORY_VIDEO, "Could not determine async page flip capability.");
}
viddata->async_pageflip_support = async_pageflip ? SDL_TRUE : SDL_FALSE;
close (viddata->drm_fd);
viddata->drm_fd = -1;
cleanup:
if (resources)
KMSDRM_drmModeFreeResources(resources);
if (ret) {
if (viddata->drm_fd >= 0) {
close(viddata->drm_fd);
viddata->drm_fd = -1;
}
}
return ret;
}
static int
KMSDRM_GBMInit (_THIS, SDL_DisplayData *dispdata)
{
SDL_VideoData *viddata = (SDL_VideoData *)_this->driverdata;
int ret = 0;
viddata->drm_fd = open(viddata->devpath, O_RDWR | O_CLOEXEC);
KMSDRM_drmSetMaster(viddata->drm_fd);
viddata->gbm_dev = KMSDRM_gbm_create_device(viddata->drm_fd);
if (!viddata->gbm_dev) {
ret = SDL_SetError("Couldn't create gbm device.");
}
viddata->gbm_init = SDL_TRUE;
return ret;
}
static void
KMSDRM_GBMDeinit (_THIS, SDL_DisplayData *dispdata)
{
SDL_VideoData *viddata = ((SDL_VideoData *)_this->driverdata);
if (viddata->gbm_dev) {
KMSDRM_gbm_device_destroy(viddata->gbm_dev);
viddata->gbm_dev = NULL;
}
if (viddata->drm_fd >= 0) {
close(viddata->drm_fd);
viddata->drm_fd = -1;
}
viddata->gbm_init = SDL_FALSE;
}
static void
KMSDRM_DestroySurfaces(_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;
int ret;
ret = KMSDRM_drmModeSetCrtc(viddata->drm_fd, dispdata->crtc->crtc_id,
dispdata->crtc->buffer_id, 0, 0, &dispdata->connector->connector_id, 1,
&dispdata->original_mode);
if (ret && (dispdata->crtc->mode_valid == 0)) {
ret = KMSDRM_drmModeSetCrtc(viddata->drm_fd, dispdata->crtc->crtc_id,
dispdata->crtc->buffer_id, 0, 0, &dispdata->connector->connector_id, 1,
&dispdata->original_mode);
}
if(ret) {
SDL_LogError(SDL_LOG_CATEGORY_VIDEO, "Could not restore CRTC");
}
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;
}
if (windata->bo) {
KMSDRM_gbm_surface_release_buffer(windata->gs, windata->bo);
windata->bo = NULL;
}
if (windata->next_bo) {
KMSDRM_gbm_surface_release_buffer(windata->gs, windata->next_bo);
windata->next_bo = NULL;
}
if (windata->gs) {
KMSDRM_gbm_surface_destroy(windata->gs);
windata->gs = NULL;
}
}
static void
KMSDRM_GetModeToSet(SDL_Window *window, drmModeModeInfo *out_mode) {
SDL_VideoDisplay *display = SDL_GetDisplayForWindow(window);
SDL_DisplayData *dispdata = (SDL_DisplayData *)display->driverdata;
if ((window->flags & SDL_WINDOW_FULLSCREEN) == SDL_WINDOW_FULLSCREEN) {
*out_mode = dispdata->fullscreen_mode;
} else {
drmModeModeInfo *mode;
mode = KMSDRM_GetClosestDisplayMode(display,
window->windowed.w, window->windowed.h, 0);
if (mode) {
*out_mode = *mode;
} else {
*out_mode = dispdata->original_mode;
}
}
}
static void
KMSDRM_DirtySurfaces(SDL_Window *window) {
SDL_WindowData *windata = (SDL_WindowData *)window->driverdata;
drmModeModeInfo mode;
windata->egl_surface_dirty = SDL_TRUE;
KMSDRM_GetModeToSet(window, &mode);
SDL_SendWindowEvent(window, SDL_WINDOWEVENT_RESIZED, mode.hdisplay, mode.vdisplay);
}
int
KMSDRM_CreateSurfaces(_THIS, SDL_Window * window)
{
SDL_VideoData *viddata = ((SDL_VideoData *)_this->driverdata);
SDL_WindowData *windata = (SDL_WindowData *)window->driverdata;
SDL_VideoDisplay *display = SDL_GetDisplayForWindow(window);
SDL_DisplayData *dispdata = (SDL_DisplayData *)display->driverdata;
uint32_t surface_fmt = GBM_FORMAT_ARGB8888;
uint32_t surface_flags = GBM_BO_USE_SCANOUT | GBM_BO_USE_RENDERING;
EGLContext egl_context;
int ret = 0;
if (windata->gs) {
KMSDRM_DestroySurfaces(_this, window);
}
if (!KMSDRM_gbm_device_is_format_supported(viddata->gbm_dev,
surface_fmt, surface_flags)) {
SDL_LogWarn(SDL_LOG_CATEGORY_VIDEO,
"GBM surface format not supported. Trying anyway.");
}
KMSDRM_GetModeToSet(window, &dispdata->mode);
display->current_mode.w = dispdata->mode.hdisplay;
display->current_mode.h = dispdata->mode.vdisplay;
display->current_mode.refresh_rate = dispdata->mode.vrefresh;
display->current_mode.format = SDL_PIXELFORMAT_ARGB8888;
windata->gs = KMSDRM_gbm_surface_create(viddata->gbm_dev,
dispdata->mode.hdisplay, dispdata->mode.vdisplay,
surface_fmt, surface_flags);
if (!windata->gs) {
return SDL_SetError("Could not create GBM surface");
}
SDL_EGL_SetRequiredVisualId(_this, surface_fmt);
windata->egl_surface = SDL_EGL_CreateSurface(_this, (NativeWindowType)windata->gs);
if (windata->egl_surface == EGL_NO_SURFACE) {
ret = SDL_SetError("Could not create EGL window surface");
goto cleanup;
}
egl_context = (EGLContext)SDL_GL_GetCurrentContext();
ret = SDL_EGL_MakeCurrent(_this, windata->egl_surface, egl_context);
SDL_SendWindowEvent(window, SDL_WINDOWEVENT_RESIZED,
dispdata->mode.hdisplay, dispdata->mode.vdisplay);
windata->egl_surface_dirty = SDL_FALSE;
cleanup:
if (ret) {
if (windata->gs) {
KMSDRM_gbm_surface_destroy(windata->gs);
windata->gs = NULL;
}
}
return ret;
}
int
KMSDRM_VideoInit(_THIS)
{
int ret = 0;
SDL_VideoData *viddata = ((SDL_VideoData *)_this->driverdata);
SDL_LogDebug(SDL_LOG_CATEGORY_VIDEO, "KMSDRM_VideoInit()");
viddata->video_init = SDL_FALSE;
viddata->gbm_init = SDL_FALSE;
if (KMSDRM_InitDisplays(_this)) {
ret = SDL_SetError("error getting KMSDRM displays information");
}
#ifdef SDL_INPUT_LINUXEV
SDL_EVDEV_Init();
#elif defined(SDL_INPUT_WSCONS)
SDL_WSCONS_Init();
#endif
viddata->video_init = SDL_TRUE;
return ret;
}
void
KMSDRM_VideoQuit(_THIS)
{
SDL_VideoData *viddata = ((SDL_VideoData *)_this->driverdata);
KMSDRM_DeinitDisplays(_this);
#ifdef SDL_INPUT_LINUXEV
SDL_EVDEV_Quit();
#elif defined(SDL_INPUT_WSCONS)
SDL_WSCONS_Quit();
#endif
SDL_free(viddata->windows);
viddata->windows = NULL;
viddata->max_windows = 0;
viddata->num_windows = 0;
viddata->video_init = SDL_FALSE;
}
void
KMSDRM_GetDisplayModes(_THIS, SDL_VideoDisplay * display)
{
SDL_DisplayData *dispdata = display->driverdata;
drmModeConnector *conn = dispdata->connector;
SDL_DisplayMode mode;
int i;
for (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->connector;
int i;
if (viddata->vulkan_mode) {
return 0;
}
if (!modedata) {
return SDL_SetError("Mode doesn't have an associated index");
}
dispdata->fullscreen_mode = conn->modes[modedata->mode_index];
for (i = 0; i < viddata->num_windows; i++) {
KMSDRM_DirtySurfaces(viddata->windows[i]);
}
return 0;
}
void
KMSDRM_DestroyWindow(_THIS, SDL_Window *window)
{
SDL_WindowData *windata = (SDL_WindowData *) window->driverdata;
SDL_DisplayData *dispdata = (SDL_DisplayData *) SDL_GetDisplayForWindow(window)->driverdata;
SDL_VideoData *viddata;
SDL_bool is_vulkan = window->flags & SDL_WINDOW_VULKAN;
unsigned int i, j;
if (!windata) {
return;
}
KMSDRM_CrtcSetVrr(windata->viddata->drm_fd, dispdata->crtc->crtc_id, dispdata->saved_vrr);
viddata = windata->viddata;
if ( !is_vulkan && viddata->gbm_init) {
KMSDRM_DestroyCursorBO(_this, SDL_GetDisplayForWindow(window));
KMSDRM_DestroySurfaces(_this, window);
if (viddata->num_windows <= 1) {
if (_this->egl_data) {
SDL_EGL_UnloadLibrary(_this);
_this->gl_config.driver_loaded = 0;
}
KMSDRM_GBMDeinit(_this, dispdata);
}
} else {
if (viddata->vulkan_mode) {
viddata->vulkan_mode = SDL_FALSE;
}
}
for (i = 0; i < viddata->num_windows; i++) {
if (viddata->windows[i] == window) {
viddata->num_windows--;
for (j = i; j < viddata->num_windows; j++) {
viddata->windows[j] = viddata->windows[j + 1];
}
break;
}
}
SDL_free(window->driverdata);
window->driverdata = NULL;
}
int
KMSDRM_CreateWindow(_THIS, SDL_Window * window)
{
SDL_WindowData *windata = NULL;
SDL_VideoData *viddata = (SDL_VideoData *)_this->driverdata;
SDL_VideoDisplay *display = SDL_GetDisplayForWindow(window);
SDL_DisplayData *dispdata = display->driverdata;
SDL_bool is_vulkan = window->flags & SDL_WINDOW_VULKAN;
SDL_bool vulkan_mode = viddata->vulkan_mode;
NativeDisplayType egl_display;
drmModeModeInfo *mode;
int ret = 0;
windata = (SDL_WindowData *)SDL_calloc(1, sizeof(SDL_WindowData));
if (!windata) {
return(SDL_OutOfMemory());
}
windata->viddata = viddata;
window->driverdata = windata;
if (!is_vulkan && !vulkan_mode) {
window->flags |= SDL_WINDOW_OPENGL;
if (!(viddata->gbm_init)) {
if ((ret = KMSDRM_GBMInit(_this, dispdata))) {
return (SDL_SetError("Can't init GBM on window creation."));
}
}
if (!_this->egl_data) {
egl_display = (NativeDisplayType)((SDL_VideoData *)_this->driverdata)->gbm_dev;
if (SDL_EGL_LoadLibrary(_this, NULL, egl_display, EGL_PLATFORM_GBM_MESA) < 0) {
_this->gl_config.profile_mask = SDL_GL_CONTEXT_PROFILE_ES;
_this->gl_config.major_version = 2;
_this->gl_config.minor_version = 0;
if (SDL_EGL_LoadLibrary(_this, NULL, egl_display, EGL_PLATFORM_GBM_MESA) < 0) {
return (SDL_SetError("Can't load EGL/GL library on window creation."));
}
}
_this->gl_config.driver_loaded = 1;
}
KMSDRM_CreateCursorBO(display);
KMSDRM_InitMouse(_this, display);
mode = KMSDRM_GetClosestDisplayMode(display,
window->windowed.w, window->windowed.h, 0 );
if (mode) {
dispdata->fullscreen_mode = *mode;
} else {
dispdata->fullscreen_mode = dispdata->original_mode;
}
if ((ret = KMSDRM_CreateSurfaces(_this, window))) {
return (SDL_SetError("Can't window GBM/EGL surfaces on window creation."));
}
}
if (viddata->num_windows >= viddata->max_windows) {
unsigned 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) {
return (SDL_OutOfMemory());
}
}
viddata->windows[viddata->num_windows++] = window;
viddata->vulkan_mode = is_vulkan;
SDL_SetMouseFocus(window);
SDL_SetKeyboardFocus(window);
SDL_SendWindowEvent(window, SDL_WINDOWEVENT_MOVED, 0, 0);
return ret;
}
int
KMSDRM_GetWindowGammaRamp(_THIS, SDL_Window * window, Uint16 * ramp)
{
SDL_WindowData *windata = (SDL_WindowData*)window->driverdata;
SDL_VideoData *viddata = (SDL_VideoData*)windata->viddata;
SDL_VideoDisplay *disp = SDL_GetDisplayForWindow(window);
SDL_DisplayData* dispdata = (SDL_DisplayData*)disp->driverdata;
if (KMSDRM_drmModeCrtcGetGamma(viddata->drm_fd, dispdata->crtc->crtc_id, 256, &ramp[0*256], &ramp[1*256], &ramp[2*256]) == -1)
{
return SDL_SetError("Failed to get gamma ramp");
}
return 0;
}
int
KMSDRM_SetWindowGammaRamp(_THIS, SDL_Window * window, const Uint16 * ramp)
{
SDL_WindowData *windata = (SDL_WindowData*)window->driverdata;
SDL_VideoData *viddata = (SDL_VideoData*)windata->viddata;
SDL_VideoDisplay *disp = SDL_GetDisplayForWindow(window);
SDL_DisplayData* dispdata = (SDL_DisplayData*)disp->driverdata;
Uint16* tempRamp = SDL_calloc(3 * sizeof(Uint16), 256);
if (tempRamp == NULL)
{
return SDL_OutOfMemory();
}
SDL_memcpy(tempRamp, ramp, 3 * sizeof(Uint16) * 256);
if (KMSDRM_drmModeCrtcSetGamma(viddata->drm_fd, dispdata->crtc->crtc_id, 256, &tempRamp[0*256], &tempRamp[1*256], &tempRamp[2*256]) == -1)
{
SDL_free(tempRamp);
return SDL_SetError("Failed to set gamma ramp");
}
SDL_free(tempRamp);
return 0;
}
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)
{
SDL_VideoData *viddata = ((SDL_VideoData *)_this->driverdata);
if (!viddata->vulkan_mode) {
KMSDRM_DirtySurfaces(window);
}
}
void
KMSDRM_SetWindowFullscreen(_THIS, SDL_Window * window, SDL_VideoDisplay * display, SDL_bool fullscreen)
{
SDL_VideoData *viddata = ((SDL_VideoData *)_this->driverdata);
if (!viddata->vulkan_mode) {
KMSDRM_DirtySurfaces(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)
{
}
SDL_bool
KMSDRM_GetWindowWMInfo(_THIS, SDL_Window * window, struct SDL_SysWMinfo *info)
{
SDL_VideoData *viddata = ((SDL_VideoData *)_this->driverdata);
const Uint32 version = SDL_VERSIONNUM((Uint32)info->version.major,
(Uint32)info->version.minor,
(Uint32)info->version.patch);
if (version < SDL_VERSIONNUM(2, 0, 15)) {
SDL_SetError("Version must be 2.0.15 or newer");
return SDL_FALSE;
}
info->subsystem = SDL_SYSWM_KMSDRM;
info->info.kmsdrm.dev_index = viddata->devindex;
info->info.kmsdrm.drm_fd = viddata->drm_fd;
info->info.kmsdrm.gbm_dev = viddata->gbm_dev;
return SDL_TRUE;
}
#endif