#include "SDL_internal.h"
#ifdef SDL_VIDEO_DRIVER_KMSDRM
#include "../SDL_egl_c.h"
#include "../../events/SDL_events_c.h"
#include "../../events/SDL_keyboard_c.h"
#include "../../events/SDL_mouse_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_kmsdrmdyn.h"
#include "SDL_kmsdrmevents.h"
#include "SDL_kmsdrmmouse.h"
#include "SDL_kmsdrmvideo.h"
#include "SDL_kmsdrmopengles.h"
#include "SDL_kmsdrmvulkan.h"
#include <dirent.h>
#include <errno.h>
#include <poll.h>
#include <sys/param.h>
#include <sys/stat.h>
#include <sys/utsname.h>
#ifdef SDL_PLATFORM_OPENBSD
static bool moderndri = false;
#else
static bool moderndri = 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 DRM_FORMAT_MOD_VENDOR_NONE
#define DRM_FORMAT_MOD_VENDOR_NONE 0
#endif
#ifndef DRM_FORMAT_MOD_LINEAR
#define DRM_FORMAT_MOD_LINEAR fourcc_mod_code(NONE, 0)
#endif
#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;
struct dirent *res;
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_pathsize);
while((res = readdir(folder)) != NULL && available < 0) {
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 && available < 0; i++) {
drmModeConnector *conn =
KMSDRM_drmModeGetConnector(
drm_fd, resources->connectors[i]);
if (!conn) {
continue;
}
if (conn->connection == DRM_MODE_CONNECTED &&
conn->count_modes) {
bool access_denied = false;
if (SDL_GetHintBoolean(
SDL_HINT_KMSDRM_REQUIRE_DRM_MASTER,
true)) {
KMSDRM_drmSetMaster(drm_fd);
if (KMSDRM_drmAuthMagic(drm_fd, 0) == -EACCES) {
access_denied = true;
}
}
if (!access_denied) {
available = devindex;
}
}
KMSDRM_drmModeFreeConnector(conn);
}
}
KMSDRM_drmModeFreeResources(resources);
}
SDL_KMSDRM_UnloadSymbols();
}
close(drm_fd);
} else {
SDL_LogDebug(SDL_LOG_CATEGORY_VIDEO,
"Failed to open KMSDRM device %s, errno: %d", device, errno);
}
}
}
closedir(folder);
return available;
}
static void CalculateRefreshRate(drmModeModeInfo *mode, int *numerator, int *denominator)
{
*numerator = mode->clock * 1000;
*denominator = mode->htotal * mode->vtotal;
if (mode->flags & DRM_MODE_FLAG_INTERLACE) {
*numerator *= 2;
}
if (mode->flags & DRM_MODE_FLAG_DBLSCAN) {
*denominator *= 2;
}
if (mode->vscan > 1) {
*denominator *= mode->vscan;
}
}
int add_connector_property(drmModeAtomicReq *req, KMSDRM_connector *conn, const char *name, uint64_t value)
{
unsigned int i;
int prop_id = 0;
for (i = 0 ; i < conn->props->count_props ; i++) {
if (SDL_strcmp(conn->props_info[i]->name, name) == 0) {
prop_id = conn->props_info[i]->prop_id;
break;
}
}
if (prop_id < 0) {
SDL_SetError("no connector property: %s", name);
return -EINVAL;
}
return KMSDRM_drmModeAtomicAddProperty(req, conn->connector->connector_id, prop_id, value);
}
int add_crtc_property(drmModeAtomicReq *req, KMSDRM_crtc *c, const char *name, uint64_t value)
{
unsigned int i;
int prop_id = -1;
for (i = 0 ; i < c->props->count_props ; i++) {
if (SDL_strcmp(c->props_info[i]->name, name) == 0) {
prop_id = c->props_info[i]->prop_id;
break;
}
}
if (prop_id < 0) {
SDL_SetError("no crtc property: %s", name);
return -EINVAL;
}
return KMSDRM_drmModeAtomicAddProperty(req, c->crtc->crtc_id, prop_id, value);
}
int add_plane_property(drmModeAtomicReq *req, KMSDRM_plane *p, const char *name, uint64_t value)
{
unsigned int i;
int prop_id = -1;
for (i = 0 ; i < p->props->count_props ; i++) {
if (SDL_strcmp(p->props_info[i]->name, name) == 0) {
prop_id = p->props_info[i]->prop_id;
break;
}
}
if (prop_id < 0) {
SDL_SetError("no plane property: %s", name);
return -EINVAL;
}
return KMSDRM_drmModeAtomicAddProperty(req, p->plane->plane_id, prop_id, value);
}
#ifdef DEBUG_KMSDRM
static void print_plane_info(SDL_VideoDevice *_this, drmModePlanePtr plane)
{
SDL_VideoData *viddata = ((SDL_VideoData *)_this->internal);
drmModeObjectPropertiesPtr props = KMSDRM_drmModeObjectGetProperties(viddata->drm_fd,
plane->plane_id, DRM_MODE_OBJECT_PLANE);
uint32_t type = 0;
for (int i = 0; i < props->count_props; i++) {
drmModePropertyPtr p = KMSDRM_drmModeGetProperty(viddata->drm_fd, props->props[i]);
if ((SDL_strcmp(p->name, "type") == 0)) {
type = props->prop_values[i];
}
KMSDRM_drmModeFreeProperty(p);
}
char *plane_type = "unknown";
switch (type) {
case DRM_PLANE_TYPE_OVERLAY:
plane_type = "overlay";
break;
case DRM_PLANE_TYPE_PRIMARY:
plane_type = "primary";
break;
case DRM_PLANE_TYPE_CURSOR:
plane_type = "cursor";
break;
}
drmModeRes *resources = KMSDRM_drmModeGetResources(viddata->drm_fd);
if (resources) {
printf("--PLANE ID: %d\nPLANE TYPE: %s\nCRTC READING THIS PLANE: %d\nCRTCS SUPPORTED BY THIS PLANE: ", plane->plane_id, plane_type, plane->crtc_id);
for (int i = 0; i < resources->count_crtcs; i++) {
if (plane->possible_crtcs & (1 << i)) {
uint32_t crtc_id = resources->crtcs[i];
printf ("%d", crtc_id);
break;
}
}
printf ("\n\n");
}
}
static void get_planes_info(SDL_VideoDevice *_this, SDL_DisplayData *dispdata)
{
SDL_VideoData *viddata = ((SDL_VideoData *)_this->internal);
drmModePlaneResPtr plane_resources = KMSDRM_drmModeGetPlaneResources(viddata->drm_fd);
if (!plane_resources) {
printf("drmModeGetPlaneResources failed: %s\n", strerror(errno));
return;
}
printf("--Number of planes found: %d-- \n", plane_resources->count_planes);
printf("--Usable CRTC that we have chosen: %d-- \n", dispdata->crtc.crtc->crtc_id);
for (uint32_t i = 0; (i < plane_resources->count_planes); i++) {
const uint32_t plane_id = plane_resources->planes[i];
drmModePlanePtr plane = KMSDRM_drmModeGetPlane(viddata->drm_fd, plane_id);
if (!plane) {
printf("drmModeGetPlane(%u) failed: %s\n", plane_id, strerror(errno));
continue;
}
print_plane_info(_this, plane);
KMSDRM_drmModeFreePlane(plane);
}
KMSDRM_drmModeFreePlaneResources(plane_resources);
}
#endif
static int get_plane_id(SDL_VideoDevice *_this, unsigned int crtc_id, uint32_t plane_type)
{
drmModeRes *resources = NULL;
drmModePlaneResPtr plane_resources = NULL;
uint32_t i, j;
unsigned int crtc_index = 0;
int ret = -EINVAL;
int found = 0;
SDL_VideoData *viddata = ((SDL_VideoData *)_this->internal);
resources = KMSDRM_drmModeGetResources(viddata->drm_fd);
for (i = 0; i < resources->count_crtcs; i++) {
if (resources->crtcs[i] == crtc_id) {
crtc_index = i;
break;
}
}
plane_resources = KMSDRM_drmModeGetPlaneResources(viddata->drm_fd);
if (!plane_resources) {
return SDL_SetError("drmModeGetPlaneResources failed.");
}
for (i = 0; (i < plane_resources->count_planes) && !found; i++) {
uint32_t plane_id = plane_resources->planes[i];
drmModePlanePtr plane = KMSDRM_drmModeGetPlane(viddata->drm_fd, plane_id);
if (!plane) {
continue;
}
if (plane->possible_crtcs & (1 << crtc_index)) {
drmModeObjectPropertiesPtr props = KMSDRM_drmModeObjectGetProperties(
viddata->drm_fd, plane_id, DRM_MODE_OBJECT_PLANE);
ret = plane_id;
for (j = 0; j < props->count_props; j++) {
drmModePropertyPtr p = KMSDRM_drmModeGetProperty(viddata->drm_fd,
props->props[j]);
if ((SDL_strcmp(p->name, "type") == 0) && (props->prop_values[j] == plane_type)) {
found = 1;
}
KMSDRM_drmModeFreeProperty(p);
}
KMSDRM_drmModeFreeObjectProperties(props);
}
KMSDRM_drmModeFreePlane(plane);
}
KMSDRM_drmModeFreePlaneResources(plane_resources);
KMSDRM_drmModeFreeResources(resources);
return ret;
}
bool setup_plane(SDL_VideoDevice *_this, SDL_DisplayData *dispdata, KMSDRM_plane **_plane, uint32_t plane_type)
{
uint32_t plane_id;
SDL_VideoData *viddata = ((SDL_VideoData *)_this->internal);
bool ret = true;
*_plane = SDL_calloc(1, sizeof(**_plane));
if (!(*_plane)) {
ret = false;
goto cleanup;
}
plane_id = get_plane_id(_this, dispdata->crtc.crtc->crtc_id, plane_type);
if (!plane_id) {
ret = SDL_SetError("Invalid Plane ID");
goto cleanup;
}
(*_plane)->plane = KMSDRM_drmModeGetPlane(viddata->drm_fd, plane_id);
if ((*_plane)->plane) {
unsigned int i;
(*_plane)->props = KMSDRM_drmModeObjectGetProperties(viddata->drm_fd,
(*_plane)->plane->plane_id, DRM_MODE_OBJECT_PLANE);
(*_plane)->props_info = SDL_calloc((*_plane)->props->count_props, sizeof(*(*_plane)->props_info));
if ( !((*_plane)->props_info) ) {
ret = false;
goto cleanup;
}
for (i = 0; i < (*_plane)->props->count_props; i++) {
(*_plane)->props_info[i] = KMSDRM_drmModeGetProperty(viddata->drm_fd, (*_plane)->props->props[i]);
}
}
cleanup:
if (!ret) {
if (*_plane) {
SDL_free(*_plane);
*_plane = NULL;
}
}
return ret;
}
void free_plane(KMSDRM_plane **_plane)
{
if (*_plane) {
if ((*_plane)->plane) {
KMSDRM_drmModeFreePlane((*_plane)->plane);
(*_plane)->plane = NULL;
}
if ((*_plane)->props_info) {
SDL_free((*_plane)->props_info);
(*_plane)->props_info = NULL;
}
SDL_free(*_plane);
*_plane = NULL;
}
}
void
drm_atomic_set_plane_props(SDL_DisplayData *dispdata, struct KMSDRM_PlaneInfo *info)
{
if (!dispdata->atomic_req) {
dispdata->atomic_req = KMSDRM_drmModeAtomicAlloc();
}
add_plane_property(dispdata->atomic_req, info->plane, "FB_ID", info->fb_id);
add_plane_property(dispdata->atomic_req, info->plane, "CRTC_ID", info->crtc_id);
add_plane_property(dispdata->atomic_req, info->plane, "SRC_W", info->src_w << 16);
add_plane_property(dispdata->atomic_req, info->plane, "SRC_H", info->src_h << 16);
add_plane_property(dispdata->atomic_req, info->plane, "SRC_X", info->src_x);
add_plane_property(dispdata->atomic_req, info->plane, "SRC_Y", info->src_y);
add_plane_property(dispdata->atomic_req, info->plane, "CRTC_W", info->crtc_w);
add_plane_property(dispdata->atomic_req, info->plane, "CRTC_H", info->crtc_h);
add_plane_property(dispdata->atomic_req, info->plane, "CRTC_X", info->crtc_x);
add_plane_property(dispdata->atomic_req, info->plane, "CRTC_Y", info->crtc_y);
}
int drm_atomic_commit(SDL_VideoDevice *_this, SDL_DisplayData *dispdata, bool blocking, bool allow_modeset)
{
SDL_VideoData *viddata = ((SDL_VideoData *)_this->internal);
uint32_t atomic_flags = 0;
int ret;
if (!blocking) {
atomic_flags |= DRM_MODE_ATOMIC_NONBLOCK;
}
if (allow_modeset) {
atomic_flags |= DRM_MODE_ATOMIC_ALLOW_MODESET;
}
drm_atomic_waitpending(_this, dispdata);
ret = KMSDRM_drmModeAtomicCommit(viddata->drm_fd, dispdata->atomic_req,
atomic_flags, NULL);
if (ret) {
SDL_SetError("Atomic commit failed, returned %d.", ret);
#if 0#endif
goto out;
}
if (dispdata->kms_in_fence_fd != -1) {
close(dispdata->kms_in_fence_fd);
dispdata->kms_in_fence_fd = -1;
}
out:
KMSDRM_drmModeAtomicFree(dispdata->atomic_req);
dispdata->atomic_req = NULL;
return ret;
}
void
drm_atomic_waitpending(SDL_VideoDevice *_this, SDL_DisplayData *dispdata)
{
if (dispdata->kms_fence) {
EGLint status;
do {
status = _this->egl_data->eglClientWaitSyncKHR(_this->egl_data->egl_display,
dispdata->kms_fence, 0, EGL_FOREVER_KHR);
} while (status != EGL_CONDITION_SATISFIED_KHR);
_this->egl_data->eglDestroySyncKHR(_this->egl_data->egl_display, dispdata->kms_fence);
dispdata->kms_fence = NULL;
}
}
static bool KMSDRM_Available(void)
{
#ifdef SDL_PLATFORM_OPENBSD
struct utsname nameofsystem;
double releaseversion;
#endif
int ret = -ENOENT;
#ifdef SDL_PLATFORM_OPENBSD
if (!(uname(&nameofsystem) < 0)) {
releaseversion = SDL_atof(nameofsystem.release);
if (releaseversion >= 6.9) {
moderndri = 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);
(void)SDL_snprintf(kmsdrm_dri_cardpath, sizeof(kmsdrm_dri_cardpath), "%s%s",
kmsdrm_dri_path, kmsdrm_dri_devname);
ret = get_driindex();
if (ret >= 0) {
return true;
}
return false;
}
static void KMSDRM_DeleteDevice(SDL_VideoDevice *device)
{
if (device->internal) {
SDL_free(device->internal);
device->internal = 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) {
return NULL;
}
viddata = (SDL_VideoData *)SDL_calloc(1, sizeof(SDL_VideoData));
if (!viddata) {
goto cleanup;
}
viddata->devindex = devindex;
viddata->drm_fd = -1;
device->internal = viddata;
device->VideoInit = KMSDRM_VideoInit;
device->VideoQuit = KMSDRM_VideoQuit;
device->GetDisplayModes = KMSDRM_GetDisplayModes;
device->SetDisplayMode = KMSDRM_SetDisplayMode;
device->CreateSDLWindow = KMSDRM_CreateWindow;
device->SetWindowTitle = KMSDRM_SetWindowTitle;
device->SetWindowPosition = KMSDRM_SetWindowPosition;
device->SetWindowSize = KMSDRM_SetWindowSize;
device->SetWindowFullscreen = KMSDRM_SetWindowFullscreen;
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->SetWindowFocusable = KMSDRM_SetWindowFocusable;
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_DestroyContext = KMSDRM_GLES_DestroyContext;
device->GL_DefaultProfileConfig = KMSDRM_GLES_DefaultProfileConfig;
#ifdef 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_DestroySurface = KMSDRM_Vulkan_DestroySurface;
#endif
device->PumpEvents = KMSDRM_PumpEvents;
device->free = KMSDRM_DeleteDevice;
return device;
cleanup:
SDL_free(device);
SDL_free(viddata);
return NULL;
}
VideoBootStrap KMSDRM_bootstrap = {
"kmsdrm",
"KMS/DRM Video Driver",
KMSDRM_CreateDevice,
NULL, false
};
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(SDL_VideoDevice *_this, struct gbm_bo *bo)
{
SDL_VideoData *viddata = _this->internal;
unsigned w, h;
int rc = -1;
int num_planes = 0;
uint32_t format, strides[4] = { 0 }, handles[4] = { 0 }, offsets[4] = { 0 }, flags = 0;
uint64_t modifiers[4] = { 0 };
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) {
return NULL;
}
fb_info->drm_fd = viddata->drm_fd;
w = KMSDRM_gbm_bo_get_width(bo);
h = KMSDRM_gbm_bo_get_height(bo);
format = KMSDRM_gbm_bo_get_format(bo);
if (KMSDRM_drmModeAddFB2WithModifiers &&
KMSDRM_gbm_bo_get_modifier &&
KMSDRM_gbm_bo_get_plane_count &&
KMSDRM_gbm_bo_get_offset &&
KMSDRM_gbm_bo_get_stride_for_plane &&
KMSDRM_gbm_bo_get_handle_for_plane) {
modifiers[0] = KMSDRM_gbm_bo_get_modifier(bo);
num_planes = KMSDRM_gbm_bo_get_plane_count(bo);
for (int i = 0; i < num_planes; i++) {
strides[i] = KMSDRM_gbm_bo_get_stride_for_plane(bo, i);
handles[i] = KMSDRM_gbm_bo_get_handle_for_plane(bo, i).u32;
offsets[i] = KMSDRM_gbm_bo_get_offset(bo, i);
modifiers[i] = modifiers[0];
}
if (modifiers[0] && modifiers[0] != DRM_FORMAT_MOD_INVALID) {
flags = DRM_MODE_FB_MODIFIERS;
}
rc = KMSDRM_drmModeAddFB2WithModifiers(viddata->drm_fd, w, h, format, handles, strides, offsets, modifiers, &fb_info->fb_id, flags);
}
if (rc < 0) {
strides[0] = KMSDRM_gbm_bo_get_stride(bo);
handles[0] = KMSDRM_gbm_bo_get_handle(bo).u32;
rc = KMSDRM_drmModeAddFB(viddata->drm_fd, w, h, 24, 32, strides[0], handles[0], &fb_info->fb_id);
}
if (rc < 0) {
SDL_free(fb_info);
return NULL;
}
SDL_LogDebug(SDL_LOG_CATEGORY_VIDEO, "New DRM FB (%u): %ux%u, from BO %p",
fb_info->fb_id, w, h, (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)
{
*((bool *)data) = false;
}
bool KMSDRM_WaitPageflip(SDL_VideoDevice *_this, SDL_WindowData *windata)
{
SDL_VideoData *viddata = _this->internal;
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 false;
}
}
if (pfd.revents & (POLLHUP | POLLERR)) {
SDL_LogError(SDL_LOG_CATEGORY_VIDEO, "DRM poll hup or error");
return false;
}
if (pfd.revents & POLLIN) {
KMSDRM_drmHandleEvent(viddata->drm_fd, &ev);
}
}
return true;
}
static drmModeModeInfo *KMSDRM_GetClosestDisplayMode(SDL_VideoDisplay *display, int width, int height)
{
SDL_DisplayData *dispdata = display->internal;
drmModeConnector *conn = dispdata->connector.connector;
SDL_DisplayMode closest;
drmModeModeInfo *drm_mode;
if (SDL_GetClosestFullscreenDisplayMode(display->id, width, height, 0.0f, false, &closest)) {
const SDL_DisplayModeData *modedata = closest.internal;
drm_mode = &conn->modes[modedata->mode_index];
return drm_mode;
} else {
return NULL;
}
}
static bool KMSDRM_DropMaster(SDL_VideoDevice *_this)
{
SDL_VideoData *viddata = _this->internal;
if (viddata->is_atomic) { KMSDRM_drmSetClientCap(viddata->drm_fd, DRM_CLIENT_CAP_ATOMIC, 0);
KMSDRM_drmSetClientCap(viddata->drm_fd, DRM_CLIENT_CAP_UNIVERSAL_PLANES, 0);
}
if (KMSDRM_drmAuthMagic(viddata->drm_fd, 0) == -EACCES) {
return true;
}
return KMSDRM_drmDropMaster(viddata->drm_fd) == 0;
}
static void KMSDRM_DeinitDisplays(SDL_VideoDevice *_this)
{
SDL_VideoData *viddata = _this->internal;
SDL_DisplayID *displays;
SDL_DisplayData *dispdata;
int i;
displays = SDL_GetDisplays(NULL);
if (displays) {
for (i = 0; displays[i]; ++i) {
dispdata = SDL_GetDisplayDriverData(displays[i]);
if (dispdata && dispdata->connector.connector) {
KMSDRM_drmModeFreeConnector(dispdata->connector.connector);
dispdata->connector.connector = NULL;
}
if (dispdata && dispdata->crtc.crtc) {
KMSDRM_drmModeFreeCrtc(dispdata->crtc.crtc);
dispdata->crtc.crtc = NULL;
}
}
SDL_free(displays);
}
if (viddata->drm_fd >= 0) {
close(viddata->drm_fd);
viddata->drm_fd = -1;
}
}
static bool KMSDRM_ConnectorCheckVrrCapable(uint32_t drm_fd, uint32_t output_id)
{
bool found = false;
uint64_t prop_value = 0;
drmModeObjectPropertiesPtr props = KMSDRM_drmModeObjectGetProperties(drm_fd, output_id, DRM_MODE_OBJECT_CONNECTOR);
if (props) {
for (uint32_t i = 0; !found && i < props->count_props; ++i) {
drmModePropertyPtr prop = KMSDRM_drmModeGetProperty(drm_fd, props->props[i]);
if (prop) {
if (SDL_strcasecmp(prop->name, "VRR_CAPABLE") == 0) {
prop_value = props->prop_values[i];
found = true;
}
KMSDRM_drmModeFreeProperty(prop);
}
}
KMSDRM_drmModeFreeObjectProperties(props);
}
if (found) {
return prop_value ? true : false;
}
return false;
}
static bool KMSDRM_VrrPropId(uint32_t drm_fd, uint32_t crtc_id, uint32_t *vrr_prop_id)
{
bool found = false;
drmModeObjectPropertiesPtr props = KMSDRM_drmModeObjectGetProperties(drm_fd, crtc_id, DRM_MODE_OBJECT_CRTC);
if (props) {
for (uint32_t i = 0; !found && i < props->count_props; ++i) {
drmModePropertyPtr prop = KMSDRM_drmModeGetProperty(drm_fd, props->props[i]);
if (prop) {
if (SDL_strcmp(prop->name, "VRR_ENABLED") == 0) {
*vrr_prop_id = prop->prop_id;
found = true;
}
KMSDRM_drmModeFreeProperty(prop);
}
}
KMSDRM_drmModeFreeObjectProperties(props);
}
return found;
}
static void KMSDRM_CrtcSetVrr(uint32_t drm_fd, uint32_t crtc_id, 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 bool KMSDRM_CrtcGetVrr(uint32_t drm_fd, uint32_t crtc_id)
{
uint32_t vrr_prop_id = 0;
bool found = false;
uint64_t prop_value = 0;
if (!KMSDRM_VrrPropId(drm_fd, crtc_id, &vrr_prop_id)) {
return false;
}
drmModeObjectPropertiesPtr props = KMSDRM_drmModeObjectGetProperties(drm_fd, crtc_id, DRM_MODE_OBJECT_CRTC);
if (props) {
for (uint32_t i = 0; !found && i < props->count_props; ++i) {
drmModePropertyPtr prop = KMSDRM_drmModeGetProperty(drm_fd, props->props[i]);
if (prop) {
if (prop->prop_id == vrr_prop_id) {
prop_value = props->prop_values[i];
found = true;
}
KMSDRM_drmModeFreeProperty(prop);
}
}
KMSDRM_drmModeFreeObjectProperties(props);
}
if (found) {
return prop_value ? true : false;
}
return false;
}
static int KMSDRM_CrtcGetOrientation(uint32_t drm_fd, uint32_t crtc_id)
{
bool found = false;
int orientation = 0;
drmModeObjectPropertiesPtr props = KMSDRM_drmModeObjectGetProperties(drm_fd, crtc_id, DRM_MODE_OBJECT_CONNECTOR);
if (props) {
for (uint32_t i = 0; !found && i < props->count_props; ++i) {
drmModePropertyPtr prop = KMSDRM_drmModeGetProperty(drm_fd, props->props[i]);
if (prop) {
if (SDL_strcasecmp(prop->name, "panel orientation") == 0 && (prop->flags & DRM_MODE_PROP_ENUM)) {
if (prop->count_enums) {
if (SDL_strcmp(prop->enums[0].name, "Left Side Up") == 0) {
orientation = 90;
} else if (SDL_strcmp(prop->enums[0].name, "Upside Down") == 0) {
orientation = 180;
} else if (SDL_strcmp(prop->enums[0].name, "Right Side Up") == 0) {
orientation = 270;
}
}
found = true;
}
KMSDRM_drmModeFreeProperty(prop);
}
}
KMSDRM_drmModeFreeObjectProperties(props);
}
return orientation;
}
static void KMSDRM_AddDisplay(SDL_VideoDevice *_this, drmModeConnector *conn, drmModeRes *resources)
{
SDL_VideoData *viddata = _this->internal;
SDL_DisplayData *dispdata = NULL;
SDL_VideoDisplay display = { 0 };
SDL_DisplayModeData *modedata = NULL;
drmModeEncoder *encoder = NULL;
drmModeCrtc *crtc = NULL;
const char *connector_type = NULL;
SDL_DisplayID display_id;
SDL_PropertiesID display_properties;
char name_fmt[64];
int orientation;
int mode_index;
int i, j;
int ret = 0;
dispdata = (SDL_DisplayData *)SDL_calloc(1, sizeof(SDL_DisplayData));
if (!dispdata) {
ret = -1;
goto cleanup;
}
dispdata->cursor_bo = NULL;
dispdata->cursor_bo_drm_fd = -1;
dispdata->kms_in_fence_fd = -1;
dispdata->kms_out_fence_fd = -1;
dispdata->default_cursor_init = 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 == conn->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 < conn->count_encoders; j++) {
if (conn->encoders[j] == encoder->encoder_id) {
break;
}
}
if (j != conn->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 < conn->count_modes; i++) {
drmModeModeInfo *mode = &conn->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 < conn->count_modes; i++) {
drmModeModeInfo *mode = &conn->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 = conn->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 = conn;
dispdata->crtc.crtc = crtc;
dispdata->saved_vrr = KMSDRM_CrtcGetVrr(viddata->drm_fd, crtc->crtc_id);
if (KMSDRM_ConnectorCheckVrrCapable(viddata->drm_fd, conn->connector_id)) {
SDL_LogDebug(SDL_LOG_CATEGORY_VIDEO, "Enabling VRR");
KMSDRM_CrtcSetVrr(viddata->drm_fd, crtc->crtc_id, true);
}
if (KMSDRM_drmModeGetConnectorTypeName) {
connector_type = KMSDRM_drmModeGetConnectorTypeName(conn->connector_type);
if (connector_type == NULL) {
connector_type = "Unknown";
}
SDL_snprintf(name_fmt, sizeof(name_fmt), "%s-%u", connector_type, conn->connector_type_id);
}
dispdata->crtc.props = KMSDRM_drmModeObjectGetProperties(viddata->drm_fd, crtc->crtc_id, DRM_MODE_OBJECT_CRTC);
dispdata->crtc.props_info = SDL_calloc(dispdata->crtc.props->count_props, sizeof(*dispdata->crtc.props_info));
if (!dispdata->crtc.props_info) {
ret = false;
goto cleanup;
}
for (i = 0; i < dispdata->crtc.props->count_props; i++) {
dispdata->crtc.props_info[i] = KMSDRM_drmModeGetProperty(viddata->drm_fd, dispdata->crtc.props->props[i]);
}
dispdata->connector.props = KMSDRM_drmModeObjectGetProperties(viddata->drm_fd, conn->connector_id, DRM_MODE_OBJECT_CONNECTOR);
dispdata->connector.props_info = SDL_calloc(dispdata->connector.props->count_props, sizeof(*dispdata->connector.props_info));
if (!dispdata->connector.props_info) {
ret = false;
goto cleanup;
}
for (i = 0; i < dispdata->connector.props->count_props; i++) {
dispdata->connector.props_info[i] = KMSDRM_drmModeGetProperty(viddata->drm_fd,
dispdata->connector.props->props[i]);
}
modedata = SDL_calloc(1, sizeof(SDL_DisplayModeData));
if (!modedata) {
ret = -1;
goto cleanup;
}
modedata->mode_index = mode_index;
display.internal = dispdata;
display.desktop_mode.w = dispdata->mode.hdisplay;
display.desktop_mode.h = dispdata->mode.vdisplay;
CalculateRefreshRate(&dispdata->mode, &display.desktop_mode.refresh_rate_numerator, &display.desktop_mode.refresh_rate_denominator);
display.desktop_mode.format = SDL_PIXELFORMAT_ARGB8888;
display.desktop_mode.internal = modedata;
if (connector_type) {
display.name = name_fmt;
}
display_id = SDL_AddVideoDisplay(&display, false);
if (!display_id) {
ret = -1;
goto cleanup;
}
orientation = KMSDRM_CrtcGetOrientation(viddata->drm_fd, crtc->crtc_id);
display_properties = SDL_GetDisplayProperties(display_id);
SDL_SetNumberProperty(display_properties, SDL_PROP_DISPLAY_KMSDRM_PANEL_ORIENTATION_NUMBER, orientation);
#ifdef DEBUG_KMSDRM
get_planes_info(_this, dispdata);
#endif
cleanup:
if (encoder) {
KMSDRM_drmModeFreeEncoder(encoder);
}
if (ret) {
if (dispdata) {
if (dispdata->connector.connector) {
KMSDRM_drmModeFreeConnector(dispdata->connector.connector);
}
if (dispdata->crtc.crtc) {
KMSDRM_drmModeFreeCrtc(dispdata->crtc.crtc);
}
SDL_free(dispdata->connector.props_info);
SDL_free(dispdata->crtc.props_info);
SDL_free(dispdata->display_plane);
SDL_free(dispdata);
}
}
}
static void KMSDRM_SortDisplays(SDL_VideoDevice *_this)
{
const char *name_hint = SDL_GetHint(SDL_HINT_VIDEO_DISPLAY_PRIORITY);
if (name_hint) {
char *saveptr;
char *str = SDL_strdup(name_hint);
SDL_VideoDisplay **sorted_list = SDL_malloc(sizeof(SDL_VideoDisplay *) * _this->num_displays);
if (str && sorted_list) {
int sorted_index = 0;
const char *token = SDL_strtok_r(str, ",", &saveptr);
while (token) {
for (int i = 0; i < _this->num_displays; ++i) {
SDL_VideoDisplay *d = _this->displays[i];
if (d && SDL_strcmp(token, d->name) == 0) {
sorted_list[sorted_index++] = d;
_this->displays[i] = NULL;
break;
}
}
token = SDL_strtok_r(NULL, ",", &saveptr);
}
for (int i = 0; i < _this->num_displays; ++i) {
if (_this->displays[i]) {
sorted_list[sorted_index++] = _this->displays[i];
}
}
SDL_memcpy(_this->displays, sorted_list, sizeof(SDL_VideoDisplay *) * _this->num_displays);
}
SDL_free(str);
SDL_free(sorted_list);
}
}
static bool set_client_atomic_caps(int fd)
{
if (!SDL_GetHintBoolean(SDL_HINT_KMSDRM_ATOMIC, true)) {
return false; } else if (KMSDRM_drmSetClientCap(fd, DRM_CLIENT_CAP_ATOMIC, 1)) {
return false; } else if (KMSDRM_drmSetClientCap(fd, DRM_CLIENT_CAP_UNIVERSAL_PLANES, 1)) {
KMSDRM_drmSetClientCap(fd, DRM_CLIENT_CAP_ATOMIC, 0); return false; }
return true;
}
static bool KMSDRM_InitDisplays(SDL_VideoDevice *_this)
{
SDL_VideoData *viddata = _this->internal;
drmModeRes *resources = NULL;
uint64_t async_pageflip = 0;
int i;
bool result = true;
(void)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) {
result = SDL_SetError("Could not open %s", viddata->devpath);
goto cleanup;
}
SDL_LogDebug(SDL_LOG_CATEGORY_VIDEO, "Opened DRM FD (%d)", viddata->drm_fd);
viddata->is_atomic = set_client_atomic_caps(viddata->drm_fd);
SDL_LogDebug(SDL_LOG_CATEGORY_VIDEO, "DRM FD (%d) %s atomic", viddata->drm_fd, viddata->is_atomic ? "SUPPORTS" : "DOES NOT SUPPORT");
resources = KMSDRM_drmModeGetResources(viddata->drm_fd);
if (!resources) {
result = SDL_SetError("drmModeGetResources(%d) failed", viddata->drm_fd);
goto cleanup;
}
for (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) {
KMSDRM_AddDisplay(_this, conn, resources);
} else {
KMSDRM_drmModeFreeConnector(conn);
}
}
if (SDL_GetPrimaryDisplay() == 0) {
result = SDL_SetError("No connected displays found.");
goto cleanup;
}
KMSDRM_SortDisplays(_this);
if (KMSDRM_drmGetCap(viddata->drm_fd, DRM_CAP_ASYNC_PAGE_FLIP, &async_pageflip) != 0) {
SDL_LogError(SDL_LOG_CATEGORY_VIDEO, "Could not determine async page flip capability.");
}
viddata->async_pageflip_support = async_pageflip ? true : false;
if (!KMSDRM_DropMaster(_this)) {
close(viddata->drm_fd);
viddata->drm_fd = -1;
}
cleanup:
if (resources) {
KMSDRM_drmModeFreeResources(resources);
}
if (!result) {
if (viddata->drm_fd >= 0) {
close(viddata->drm_fd);
viddata->drm_fd = -1;
}
}
return result;
}
static bool KMSDRM_GBMInit(SDL_VideoDevice *_this, SDL_DisplayData *dispdata)
{
SDL_VideoData *viddata = _this->internal;
bool result = true;
if (viddata->drm_fd < 0) {
viddata->drm_fd = open(viddata->devpath, O_RDWR | O_CLOEXEC);
if (viddata->drm_fd < 0) {
return SDL_SetError("Could not reopen %s", viddata->devpath);
}
}
viddata->is_atomic = set_client_atomic_caps(viddata->drm_fd);
KMSDRM_drmSetMaster(viddata->drm_fd);
viddata->gbm_dev = KMSDRM_gbm_create_device(viddata->drm_fd);
if (!viddata->gbm_dev) {
result = SDL_SetError("Couldn't create gbm device.");
} else {
result = setup_plane(_this, dispdata, &dispdata->display_plane, DRM_PLANE_TYPE_PRIMARY);
if (!result) {
SDL_SetError("can't find suitable display plane.");
}
}
viddata->gbm_init = true;
return result;
}
static void KMSDRM_GBMDeinit(SDL_VideoDevice *_this, SDL_DisplayData *dispdata)
{
SDL_VideoData *viddata = _this->internal;
free_plane(&dispdata->display_plane);
free_plane(&dispdata->cursor_plane);
if (viddata->gbm_dev) {
KMSDRM_gbm_device_destroy(viddata->gbm_dev);
viddata->gbm_dev = NULL;
}
if (viddata->drm_fd >= 0 && !KMSDRM_DropMaster(_this)) {
close(viddata->drm_fd);
viddata->drm_fd = -1;
}
viddata->gbm_init = false;
}
static void KMSDRM_DestroySurfaces(SDL_VideoDevice *_this, SDL_Window *window)
{
SDL_VideoData *viddata = _this->internal;
SDL_WindowData *windata = window->internal;
SDL_DisplayData *dispdata = SDL_GetDisplayDriverDataForWindow(window);
int ret;
if (viddata->is_atomic) {
#if 0#endif
#if 1
KMSDRM_PlaneInfo plane_info;
SDL_zero(plane_info);
plane_info.plane = dispdata->display_plane;
plane_info.crtc_id = dispdata->crtc.crtc->crtc_id;
plane_info.fb_id = dispdata->crtc.crtc->buffer_id;
plane_info.src_w = dispdata->original_mode.hdisplay;
plane_info.src_h = dispdata->original_mode.vdisplay;
plane_info.crtc_w = dispdata->original_mode.hdisplay;
plane_info.crtc_h = dispdata->original_mode.vdisplay;
drm_atomic_set_plane_props(dispdata, &plane_info);
if (drm_atomic_commit(_this, dispdata, true, false)) {
SDL_SetError("Failed to issue atomic commit on surfaces destruction.");
}
}
#endif
ret = KMSDRM_drmModeSetCrtc(viddata->drm_fd, dispdata->crtc.crtc->crtc_id,
dispdata->crtc.crtc->buffer_id, 0, 0, &dispdata->connector.connector->connector_id, 1,
&dispdata->original_mode);
if (ret && (dispdata->crtc.crtc->mode_valid == 0)) {
ret = KMSDRM_drmModeSetCrtc(viddata->drm_fd, dispdata->crtc.crtc->crtc_id,
dispdata->crtc.crtc->buffer_id, 0, 0, &dispdata->connector.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_GetVideoDisplayForWindow(window);
SDL_DisplayData *dispdata = display->internal;
if (window->fullscreen_exclusive) {
*out_mode = dispdata->fullscreen_mode;
} else {
drmModeModeInfo *mode = KMSDRM_GetClosestDisplayMode(display, window->windowed.w, window->windowed.h);
if (mode) {
*out_mode = *mode;
} else {
*out_mode = dispdata->original_mode;
}
}
}
static void KMSDRM_DirtySurfaces(SDL_Window *window)
{
SDL_WindowData *windata = window->internal;
drmModeModeInfo mode;
windata->egl_surface_dirty = true;
KMSDRM_GetModeToSet(window, &mode);
SDL_SendWindowEvent(window, SDL_EVENT_WINDOW_RESIZED, mode.hdisplay, mode.vdisplay);
}
bool KMSDRM_CreateSurfaces(SDL_VideoDevice *_this, SDL_Window *window)
{
SDL_VideoData *viddata = _this->internal;
SDL_WindowData *windata = window->internal;
SDL_VideoDisplay *display = SDL_GetVideoDisplayForWindow(window);
SDL_DisplayData *dispdata = display->internal;
uint32_t surface_fmt = GBM_FORMAT_ARGB8888;
uint32_t surface_flags = GBM_BO_USE_SCANOUT | GBM_BO_USE_RENDERING;
EGLContext egl_context;
bool result = true;
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);
windata->gs = KMSDRM_gbm_surface_create(viddata->gbm_dev,
dispdata->mode.hdisplay, dispdata->mode.vdisplay,
surface_fmt, surface_flags);
if (!windata->gs && errno == ENOSYS) {
windata->gs = KMSDRM_gbm_surface_create(viddata->gbm_dev,
dispdata->mode.hdisplay, dispdata->mode.vdisplay,
surface_fmt, 0);
}
if (!windata->gs) {
return SDL_SetError("Could not create GBM surface: %s", strerror(errno));
}
SDL_EGL_SetRequiredVisualId(_this, surface_fmt);
windata->egl_surface = SDL_EGL_CreateSurface(_this, window, (NativeWindowType)windata->gs);
if (windata->egl_surface == EGL_NO_SURFACE) {
result = SDL_SetError("Could not create EGL window surface");
goto cleanup;
}
egl_context = (EGLContext)SDL_GL_GetCurrentContext();
result = SDL_EGL_MakeCurrent(_this, windata->egl_surface, egl_context);
SDL_SendWindowEvent(window, SDL_EVENT_WINDOW_RESIZED,
dispdata->mode.hdisplay, dispdata->mode.vdisplay);
windata->egl_surface_dirty = false;
cleanup:
if (!result) {
if (windata->gs) {
KMSDRM_gbm_surface_destroy(windata->gs);
windata->gs = NULL;
}
}
return result;
}
#ifdef SDL_INPUT_LINUXEV
static void KMSDRM_ReleaseVT(void *userdata)
{
SDL_VideoDevice *_this = (SDL_VideoDevice *)userdata;
SDL_VideoData *viddata = _this->internal;
int i;
for (i = 0; i < viddata->num_windows; i++) {
SDL_Window *window = viddata->windows[i];
if (!(window->flags & SDL_WINDOW_VULKAN)) {
KMSDRM_DestroySurfaces(_this, window);
}
}
KMSDRM_drmDropMaster(viddata->drm_fd);
}
static void KMSDRM_AcquireVT(void *userdata)
{
SDL_VideoDevice *_this = (SDL_VideoDevice *)userdata;
SDL_VideoData *viddata = _this->internal;
int i;
KMSDRM_drmSetMaster(viddata->drm_fd);
for (i = 0; i < viddata->num_windows; i++) {
SDL_Window *window = viddata->windows[i];
if (!(window->flags & SDL_WINDOW_VULKAN)) {
KMSDRM_CreateSurfaces(_this, window);
}
}
}
#endif
bool KMSDRM_VideoInit(SDL_VideoDevice *_this)
{
bool result = true;
SDL_VideoData *viddata = _this->internal;
SDL_LogDebug(SDL_LOG_CATEGORY_VIDEO, "KMSDRM_VideoInit()");
viddata->video_init = false;
viddata->gbm_init = false;
if (!KMSDRM_InitDisplays(_this)) {
result = SDL_SetError("error getting KMSDRM displays information");
}
#ifdef SDL_INPUT_LINUXEV
SDL_EVDEV_Init();
SDL_EVDEV_SetVTSwitchCallbacks(KMSDRM_ReleaseVT, _this, KMSDRM_AcquireVT, _this);
#elif defined(SDL_INPUT_WSCONS)
SDL_WSCONS_Init();
#endif
viddata->video_init = true;
return result;
}
void KMSDRM_VideoQuit(SDL_VideoDevice *_this)
{
SDL_VideoData *viddata = _this->internal;
KMSDRM_DeinitDisplays(_this);
#ifdef SDL_INPUT_LINUXEV
SDL_EVDEV_SetVTSwitchCallbacks(NULL, NULL, NULL, NULL);
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 = false;
}
bool KMSDRM_GetDisplayModes(SDL_VideoDevice *_this, SDL_VideoDisplay *display)
{
SDL_DisplayData *dispdata = display->internal;
drmModeConnector *conn = dispdata->connector.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;
}
SDL_zero(mode);
mode.w = conn->modes[i].hdisplay;
mode.h = conn->modes[i].vdisplay;
CalculateRefreshRate(&conn->modes[i], &mode.refresh_rate_numerator, &mode.refresh_rate_denominator);
mode.format = SDL_PIXELFORMAT_ARGB8888;
mode.internal = modedata;
if (!SDL_AddFullscreenDisplayMode(display, &mode)) {
SDL_free(modedata);
}
}
return true;
}
bool KMSDRM_SetDisplayMode(SDL_VideoDevice *_this, SDL_VideoDisplay *display, SDL_DisplayMode *mode)
{
SDL_VideoData *viddata = _this->internal;
SDL_DisplayData *dispdata = display->internal;
SDL_DisplayModeData *modedata = mode->internal;
drmModeConnector *conn = dispdata->connector.connector;
int i;
if (viddata->vulkan_mode) {
return true;
}
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 true;
}
void KMSDRM_DestroyWindow(SDL_VideoDevice *_this, SDL_Window *window)
{
SDL_WindowData *windata = window->internal;
SDL_DisplayData *dispdata = SDL_GetDisplayDriverDataForWindow(window);
SDL_VideoData *viddata;
bool is_vulkan = window->flags & SDL_WINDOW_VULKAN; unsigned int i, j;
if (!windata) {
return;
}
KMSDRM_CrtcSetVrr(windata->viddata->drm_fd, dispdata->crtc.crtc->crtc_id, dispdata->saved_vrr);
viddata = windata->viddata;
if (!is_vulkan && viddata->gbm_init) {
KMSDRM_DestroyCursorBO(_this, SDL_GetVideoDisplayForWindow(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 = 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->internal);
window->internal = NULL;
}
bool KMSDRM_CreateWindow(SDL_VideoDevice *_this, SDL_Window *window, SDL_PropertiesID create_props)
{
SDL_WindowData *windata = NULL;
SDL_VideoData *viddata = _this->internal;
SDL_VideoDisplay *display = SDL_GetVideoDisplayForWindow(window);
SDL_DisplayData *dispdata = display->internal;
bool is_vulkan = window->flags & SDL_WINDOW_VULKAN; bool vulkan_mode = viddata->vulkan_mode; NativeDisplayType egl_display;
drmModeModeInfo *mode;
bool result = true;
windata = (SDL_WindowData *)SDL_calloc(1, sizeof(SDL_WindowData));
if (!windata) {
return false;
}
windata->viddata = viddata;
window->internal = windata;
windata->double_buffer = false;
if (SDL_GetHintBoolean(SDL_HINT_VIDEO_DOUBLE_BUFFER, false)) {
windata->double_buffer = true;
}
if (!is_vulkan && !vulkan_mode) {
window->flags |= SDL_WINDOW_OPENGL;
if (!(viddata->gbm_init)) {
if (!KMSDRM_GBMInit(_this, dispdata)) {
return SDL_SetError("Can't init GBM on window creation.");
}
}
if (!_this->egl_data) {
egl_display = (NativeDisplayType)_this->internal->gbm_dev;
if (!SDL_EGL_LoadLibrary(_this, NULL, egl_display, EGL_PLATFORM_GBM_MESA)) {
_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)) {
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);
if (mode) {
dispdata->fullscreen_mode = *mode;
} else {
dispdata->fullscreen_mode = dispdata->original_mode;
}
if (!KMSDRM_CreateSurfaces(_this, window)) {
return false;
}
}
if (viddata->num_windows >= viddata->max_windows) {
unsigned int new_max_windows = viddata->max_windows + 1;
SDL_Window **new_windows = (SDL_Window **)SDL_realloc(viddata->windows,
new_max_windows * sizeof(SDL_Window *));
if (!new_windows) {
return false;
}
viddata->windows = new_windows;
viddata->max_windows = new_max_windows;
}
viddata->windows[viddata->num_windows++] = window;
viddata->vulkan_mode = is_vulkan;
SDL_PropertiesID props = SDL_GetWindowProperties(window);
SDL_SetNumberProperty(props, SDL_PROP_WINDOW_KMSDRM_DEVICE_INDEX_NUMBER, viddata->devindex);
SDL_SetNumberProperty(props, SDL_PROP_WINDOW_KMSDRM_DRM_FD_NUMBER, viddata->drm_fd);
SDL_SetPointerProperty(props, SDL_PROP_WINDOW_KMSDRM_GBM_DEVICE_POINTER, viddata->gbm_dev);
if ((window->flags & SDL_WINDOW_NOT_FOCUSABLE) == 0) {
SDL_SetMouseFocus(window);
SDL_SetKeyboardFocus(window);
}
SDL_Rect display_bounds;
SDL_GetDisplayBounds(SDL_GetDisplayForWindow(window), &display_bounds);
SDL_SendWindowEvent(window, SDL_EVENT_WINDOW_MOVED, display_bounds.x, display_bounds.y);
return result;
}
void KMSDRM_SetWindowTitle(SDL_VideoDevice *_this, SDL_Window *window)
{
}
bool KMSDRM_SetWindowPosition(SDL_VideoDevice *_this, SDL_Window *window)
{
return SDL_Unsupported();
}
void KMSDRM_SetWindowSize(SDL_VideoDevice *_this, SDL_Window *window)
{
SDL_VideoData *viddata = _this->internal;
if (!viddata->vulkan_mode) {
KMSDRM_DirtySurfaces(window);
}
}
SDL_FullscreenResult KMSDRM_SetWindowFullscreen(SDL_VideoDevice *_this, SDL_Window *window, SDL_VideoDisplay *display, SDL_FullscreenOp fullscreen)
{
SDL_VideoData *viddata = _this->internal;
if (!viddata->vulkan_mode) {
KMSDRM_DirtySurfaces(window);
}
return SDL_FULLSCREEN_SUCCEEDED;
}
void KMSDRM_ShowWindow(SDL_VideoDevice *_this, SDL_Window *window)
{
}
void KMSDRM_HideWindow(SDL_VideoDevice *_this, SDL_Window *window)
{
}
void KMSDRM_RaiseWindow(SDL_VideoDevice *_this, SDL_Window *window)
{
}
void KMSDRM_MaximizeWindow(SDL_VideoDevice *_this, SDL_Window *window)
{
}
void KMSDRM_MinimizeWindow(SDL_VideoDevice *_this, SDL_Window *window)
{
}
void KMSDRM_RestoreWindow(SDL_VideoDevice *_this, SDL_Window *window)
{
}
bool KMSDRM_SetWindowFocusable(SDL_VideoDevice *_this, SDL_Window *window, bool focusable)
{
return true; }
#endif