1/*
2 Simple DirectMedia Layer
3 Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org>
4
5 This software is provided 'as-is', without any express or implied
6 warranty. In no event will the authors be held liable for any damages
7 arising from the use of this software.
8
9 Permission is granted to anyone to use this software for any purpose,
10 including commercial applications, and to alter it and redistribute it
11 freely, subject to the following restrictions:
12
13 1. The origin of this software must not be misrepresented; you must not
14 claim that you wrote the original software. If you use this software
15 in a product, an acknowledgment in the product documentation would be
16 appreciated but is not required.
17 2. Altered source versions must be plainly marked as such, and must not be
18 misrepresented as being the original software.
19 3. This notice may not be removed or altered from any source distribution.
20*/
21
22#include "SDL_internal.h"
23
24#ifdef SDL_VIDEO_DRIVER_KMSDRM
25
26/* Include this first, as some system headers may pull in EGL headers that
27 * define EGL types as native types for other enabled platforms, which can
28 * result in type-mismatch warnings when building with LTO.
29 */
30#include "../SDL_egl_c.h"
31
32// SDL internals
33#include "../../events/SDL_events_c.h"
34#include "../../events/SDL_keyboard_c.h"
35#include "../../events/SDL_mouse_c.h"
36
37#ifdef SDL_INPUT_LINUXEV
38#include "../../core/linux/SDL_evdev.h"
39#elif defined SDL_INPUT_WSCONS
40#include "../../core/openbsd/SDL_wscons.h"
41#endif
42
43// KMS/DRM declarations
44#include "SDL_kmsdrmdyn.h"
45#include "SDL_kmsdrmevents.h"
46#include "SDL_kmsdrmmouse.h"
47#include "SDL_kmsdrmvideo.h"
48#include "SDL_kmsdrmopengles.h"
49#include "SDL_kmsdrmvulkan.h"
50#include <dirent.h>
51#include <errno.h>
52#include <poll.h>
53#include <sys/param.h>
54#include <sys/stat.h>
55#include <sys/utsname.h>
56
57#ifdef SDL_PLATFORM_OPENBSD
58static bool moderndri = false;
59#else
60static bool moderndri = true;
61#endif
62
63static char kmsdrm_dri_path[16];
64static int kmsdrm_dri_pathsize = 0;
65static char kmsdrm_dri_devname[8];
66static int kmsdrm_dri_devnamesize = 0;
67static char kmsdrm_dri_cardpath[32];
68
69#ifndef EGL_PLATFORM_GBM_MESA
70#define EGL_PLATFORM_GBM_MESA 0x31D7
71#endif
72
73static int get_driindex(void)
74{
75 int available = -ENOENT;
76 char device[sizeof(kmsdrm_dri_cardpath)];
77 int drm_fd;
78 int i;
79 int devindex = -1;
80 DIR *folder;
81 const char *hint;
82 struct dirent *res;
83
84 hint = SDL_GetHint(SDL_HINT_KMSDRM_DEVICE_INDEX);
85 if (hint && *hint) {
86 char *endptr = NULL;
87 const int idx = (int)SDL_strtol(hint, &endptr, 10);
88 if ((*endptr == '\0') && (idx >= 0)) { /* *endptr==0 means "whole string was a valid number" */
89 return idx; // we'll take the user's request here.
90 }
91 }
92
93 SDL_strlcpy(device, kmsdrm_dri_path, sizeof(device));
94 folder = opendir(device);
95 if (!folder) {
96 SDL_SetError("Failed to open directory '%s'", device);
97 return -ENOENT;
98 }
99
100 SDL_strlcpy(device + kmsdrm_dri_pathsize, kmsdrm_dri_devname,
101 sizeof(device) - kmsdrm_dri_pathsize);
102 while((res = readdir(folder)) != NULL && available < 0) {
103 if (SDL_memcmp(res->d_name, kmsdrm_dri_devname,
104 kmsdrm_dri_devnamesize) == 0) {
105 SDL_strlcpy(device + kmsdrm_dri_pathsize + kmsdrm_dri_devnamesize,
106 res->d_name + kmsdrm_dri_devnamesize,
107 sizeof(device) - kmsdrm_dri_pathsize -
108 kmsdrm_dri_devnamesize);
109
110 drm_fd = open(device, O_RDWR | O_CLOEXEC);
111 if (drm_fd >= 0) {
112 devindex = SDL_atoi(device + kmsdrm_dri_pathsize +
113 kmsdrm_dri_devnamesize);
114 if (SDL_KMSDRM_LoadSymbols()) {
115 drmModeRes *resources = KMSDRM_drmModeGetResources(drm_fd);
116 if (resources) {
117 SDL_LogDebug(SDL_LOG_CATEGORY_VIDEO,
118 "%s%d connector, encoder and CRTC counts are: %d %d %d",
119 kmsdrm_dri_cardpath, devindex,
120 resources->count_connectors,
121 resources->count_encoders,
122 resources->count_crtcs);
123
124 if (resources->count_connectors > 0 &&
125 resources->count_encoders > 0 &&
126 resources->count_crtcs > 0) {
127 available = -ENOENT;
128 for (i = 0; i < resources->count_connectors && available < 0; i++) {
129 drmModeConnector *conn =
130 KMSDRM_drmModeGetConnector(
131 drm_fd, resources->connectors[i]);
132
133 if (!conn) {
134 continue;
135 }
136
137 if (conn->connection == DRM_MODE_CONNECTED &&
138 conn->count_modes) {
139 bool access_denied = false;
140 if (SDL_GetHintBoolean(
141 SDL_HINT_KMSDRM_REQUIRE_DRM_MASTER,
142 true)) {
143 /* Skip this device if we can't obtain
144 * DRM master */
145 KMSDRM_drmSetMaster(drm_fd);
146 if (KMSDRM_drmAuthMagic(drm_fd, 0) == -EACCES) {
147 access_denied = true;
148 }
149 }
150
151 if (!access_denied) {
152 available = devindex;
153 }
154 }
155
156 KMSDRM_drmModeFreeConnector(conn);
157 }
158 }
159 KMSDRM_drmModeFreeResources(resources);
160 }
161 SDL_KMSDRM_UnloadSymbols();
162 }
163 close(drm_fd);
164 } else {
165 SDL_LogDebug(SDL_LOG_CATEGORY_VIDEO,
166 "Failed to open KMSDRM device %s, errno: %d", device, errno);
167 }
168 }
169 }
170
171 closedir(folder);
172
173 return available;
174}
175
176static void CalculateRefreshRate(drmModeModeInfo *mode, int *numerator, int *denominator)
177{
178 *numerator = mode->clock * 1000;
179 *denominator = mode->htotal * mode->vtotal;
180
181 if (mode->flags & DRM_MODE_FLAG_INTERLACE) {
182 *numerator *= 2;
183 }
184
185 if (mode->flags & DRM_MODE_FLAG_DBLSCAN) {
186 *denominator *= 2;
187 }
188
189 if (mode->vscan > 1) {
190 *denominator *= mode->vscan;
191 }
192}
193
194static bool KMSDRM_Available(void)
195{
196#ifdef SDL_PLATFORM_OPENBSD
197 struct utsname nameofsystem;
198 double releaseversion;
199#endif
200 int ret = -ENOENT;
201
202#ifdef SDL_PLATFORM_OPENBSD
203 if (!(uname(&nameofsystem) < 0)) {
204 releaseversion = SDL_atof(nameofsystem.release);
205 if (releaseversion >= 6.9) {
206 moderndri = true;
207 }
208 }
209#endif
210
211 if (moderndri) {
212 SDL_strlcpy(kmsdrm_dri_path, "/dev/dri/", sizeof(kmsdrm_dri_path));
213 SDL_strlcpy(kmsdrm_dri_devname, "card", sizeof(kmsdrm_dri_devname));
214 } else {
215 SDL_strlcpy(kmsdrm_dri_path, "/dev/", sizeof(kmsdrm_dri_path));
216 SDL_strlcpy(kmsdrm_dri_devname, "drm", sizeof(kmsdrm_dri_devname));
217 }
218
219 kmsdrm_dri_pathsize = SDL_strlen(kmsdrm_dri_path);
220 kmsdrm_dri_devnamesize = SDL_strlen(kmsdrm_dri_devname);
221 (void)SDL_snprintf(kmsdrm_dri_cardpath, sizeof(kmsdrm_dri_cardpath), "%s%s",
222 kmsdrm_dri_path, kmsdrm_dri_devname);
223
224 ret = get_driindex();
225 if (ret >= 0) {
226 return true;
227 }
228
229 return false;
230}
231
232static void KMSDRM_DeleteDevice(SDL_VideoDevice *device)
233{
234 if (device->internal) {
235 SDL_free(device->internal);
236 device->internal = NULL;
237 }
238
239 SDL_free(device);
240
241 SDL_KMSDRM_UnloadSymbols();
242}
243
244static SDL_VideoDevice *KMSDRM_CreateDevice(void)
245{
246 SDL_VideoDevice *device;
247 SDL_VideoData *viddata;
248 int devindex;
249
250 if (!KMSDRM_Available()) {
251 return NULL;
252 }
253
254 devindex = get_driindex();
255 if (devindex < 0) {
256 SDL_SetError("devindex (%d) must not be negative.", devindex);
257 return NULL;
258 }
259
260 if (!SDL_KMSDRM_LoadSymbols()) {
261 return NULL;
262 }
263
264 device = (SDL_VideoDevice *)SDL_calloc(1, sizeof(SDL_VideoDevice));
265 if (!device) {
266 return NULL;
267 }
268
269 viddata = (SDL_VideoData *)SDL_calloc(1, sizeof(SDL_VideoData));
270 if (!viddata) {
271 goto cleanup;
272 }
273 viddata->devindex = devindex;
274 viddata->drm_fd = -1;
275
276 device->internal = viddata;
277
278 // Setup all functions which we can handle
279 device->VideoInit = KMSDRM_VideoInit;
280 device->VideoQuit = KMSDRM_VideoQuit;
281 device->GetDisplayModes = KMSDRM_GetDisplayModes;
282 device->SetDisplayMode = KMSDRM_SetDisplayMode;
283 device->CreateSDLWindow = KMSDRM_CreateWindow;
284 device->SetWindowTitle = KMSDRM_SetWindowTitle;
285 device->SetWindowPosition = KMSDRM_SetWindowPosition;
286 device->SetWindowSize = KMSDRM_SetWindowSize;
287 device->SetWindowFullscreen = KMSDRM_SetWindowFullscreen;
288 device->ShowWindow = KMSDRM_ShowWindow;
289 device->HideWindow = KMSDRM_HideWindow;
290 device->RaiseWindow = KMSDRM_RaiseWindow;
291 device->MaximizeWindow = KMSDRM_MaximizeWindow;
292 device->MinimizeWindow = KMSDRM_MinimizeWindow;
293 device->RestoreWindow = KMSDRM_RestoreWindow;
294 device->DestroyWindow = KMSDRM_DestroyWindow;
295
296 device->GL_LoadLibrary = KMSDRM_GLES_LoadLibrary;
297 device->GL_GetProcAddress = KMSDRM_GLES_GetProcAddress;
298 device->GL_UnloadLibrary = KMSDRM_GLES_UnloadLibrary;
299 device->GL_CreateContext = KMSDRM_GLES_CreateContext;
300 device->GL_MakeCurrent = KMSDRM_GLES_MakeCurrent;
301 device->GL_SetSwapInterval = KMSDRM_GLES_SetSwapInterval;
302 device->GL_GetSwapInterval = KMSDRM_GLES_GetSwapInterval;
303 device->GL_SwapWindow = KMSDRM_GLES_SwapWindow;
304 device->GL_DestroyContext = KMSDRM_GLES_DestroyContext;
305 device->GL_DefaultProfileConfig = KMSDRM_GLES_DefaultProfileConfig;
306
307#ifdef SDL_VIDEO_VULKAN
308 device->Vulkan_LoadLibrary = KMSDRM_Vulkan_LoadLibrary;
309 device->Vulkan_UnloadLibrary = KMSDRM_Vulkan_UnloadLibrary;
310 device->Vulkan_GetInstanceExtensions = KMSDRM_Vulkan_GetInstanceExtensions;
311 device->Vulkan_CreateSurface = KMSDRM_Vulkan_CreateSurface;
312 device->Vulkan_DestroySurface = KMSDRM_Vulkan_DestroySurface;
313#endif
314
315 device->PumpEvents = KMSDRM_PumpEvents;
316 device->free = KMSDRM_DeleteDevice;
317
318 return device;
319
320cleanup:
321 if (device) {
322 SDL_free(device);
323 }
324
325 if (viddata) {
326 SDL_free(viddata);
327 }
328 return NULL;
329}
330
331VideoBootStrap KMSDRM_bootstrap = {
332 "kmsdrm",
333 "KMS/DRM Video Driver",
334 KMSDRM_CreateDevice,
335 NULL, // no ShowMessageBox implementation
336 false
337};
338
339static void KMSDRM_FBDestroyCallback(struct gbm_bo *bo, void *data)
340{
341 KMSDRM_FBInfo *fb_info = (KMSDRM_FBInfo *)data;
342
343 if (fb_info && fb_info->drm_fd >= 0 && fb_info->fb_id != 0) {
344 KMSDRM_drmModeRmFB(fb_info->drm_fd, fb_info->fb_id);
345 SDL_LogDebug(SDL_LOG_CATEGORY_VIDEO, "Delete DRM FB %u", fb_info->fb_id);
346 }
347
348 SDL_free(fb_info);
349}
350
351KMSDRM_FBInfo *KMSDRM_FBFromBO(SDL_VideoDevice *_this, struct gbm_bo *bo)
352{
353 SDL_VideoData *viddata = _this->internal;
354 unsigned w, h;
355 int rc = -1;
356 int num_planes = 0;
357 uint32_t format, strides[4] = { 0 }, handles[4] = { 0 }, offsets[4] = { 0 }, flags = 0;
358 uint64_t modifiers[4] = { 0 };
359
360 // Check for an existing framebuffer
361 KMSDRM_FBInfo *fb_info = (KMSDRM_FBInfo *)KMSDRM_gbm_bo_get_user_data(bo);
362
363 if (fb_info) {
364 return fb_info;
365 }
366
367 /* Create a structure that contains enough info to remove the framebuffer
368 when the backing buffer is destroyed */
369 fb_info = (KMSDRM_FBInfo *)SDL_calloc(1, sizeof(KMSDRM_FBInfo));
370
371 if (!fb_info) {
372 return NULL;
373 }
374
375 fb_info->drm_fd = viddata->drm_fd;
376
377 /* Create framebuffer object for the buffer using the modifiers requested by GBM.
378 Use of the modifiers is necessary on some platforms. */
379 w = KMSDRM_gbm_bo_get_width(bo);
380 h = KMSDRM_gbm_bo_get_height(bo);
381 format = KMSDRM_gbm_bo_get_format(bo);
382
383 if (KMSDRM_drmModeAddFB2WithModifiers &&
384 KMSDRM_gbm_bo_get_modifier &&
385 KMSDRM_gbm_bo_get_plane_count &&
386 KMSDRM_gbm_bo_get_offset &&
387 KMSDRM_gbm_bo_get_stride_for_plane &&
388 KMSDRM_gbm_bo_get_handle_for_plane) {
389
390 modifiers[0] = KMSDRM_gbm_bo_get_modifier(bo);
391 num_planes = KMSDRM_gbm_bo_get_plane_count(bo);
392 for (int i = 0; i < num_planes; i++) {
393 strides[i] = KMSDRM_gbm_bo_get_stride_for_plane(bo, i);
394 handles[i] = KMSDRM_gbm_bo_get_handle_for_plane(bo, i).u32;
395 offsets[i] = KMSDRM_gbm_bo_get_offset(bo, i);
396 modifiers[i] = modifiers[0];
397 }
398
399 if (modifiers[0] && modifiers[0] != DRM_FORMAT_MOD_INVALID) {
400 flags = DRM_MODE_FB_MODIFIERS;
401 }
402
403 rc = KMSDRM_drmModeAddFB2WithModifiers(viddata->drm_fd, w, h, format, handles, strides, offsets, modifiers, &fb_info->fb_id, flags);
404 }
405
406 if (rc < 0) {
407 strides[0] = KMSDRM_gbm_bo_get_stride(bo);
408 handles[0] = KMSDRM_gbm_bo_get_handle(bo).u32;
409 rc = KMSDRM_drmModeAddFB(viddata->drm_fd, w, h, 24, 32, strides[0], handles[0], &fb_info->fb_id);
410 }
411
412 if (rc < 0) {
413 SDL_free(fb_info);
414 return NULL;
415 }
416
417 SDL_LogDebug(SDL_LOG_CATEGORY_VIDEO, "New DRM FB (%u): %ux%u, from BO %p",
418 fb_info->fb_id, w, h, (void *)bo);
419
420 // Associate our DRM framebuffer with this buffer object
421 KMSDRM_gbm_bo_set_user_data(bo, fb_info, KMSDRM_FBDestroyCallback);
422
423 return fb_info;
424}
425
426static void KMSDRM_FlipHandler(int fd, unsigned int frame, unsigned int sec, unsigned int usec, void *data)
427{
428 *((bool *)data) = false;
429}
430
431bool KMSDRM_WaitPageflip(SDL_VideoDevice *_this, SDL_WindowData *windata)
432{
433
434 SDL_VideoData *viddata = _this->internal;
435 drmEventContext ev = { 0 };
436 struct pollfd pfd = { 0 };
437 int ret;
438
439 ev.version = DRM_EVENT_CONTEXT_VERSION;
440 ev.page_flip_handler = KMSDRM_FlipHandler;
441
442 pfd.fd = viddata->drm_fd;
443 pfd.events = POLLIN;
444
445 /* Stay on the while loop until we get the desired event.
446 We need the while the loop because we could be in a situation where:
447 -We get and event on the FD in time, thus not on exiting on return number 1.
448 -The event is not an error, thus not exiting on return number 2.
449 -The event is of POLLIN type, but even then, if the event is not a pageflip,
450 drmHandleEvent() won't unset wait_for_pageflip, so we have to iterate
451 and go polling again.
452
453 If it wasn't for the while loop, we could erroneously exit the function
454 without the pageflip event to arrive!
455
456 For example, vblank events hit the FD and they are POLLIN events too (POLLIN
457 means "there's data to read on the FD"), but they are not the pageflip event
458 we are waiting for, so the drmEventHandle() doesn't run the flip handler, and
459 since waiting_for_flip is set on the pageflip handle, it's not set and we stay
460 on the loop, until we get the event for the pageflip, which is fine.
461 */
462 while (windata->waiting_for_flip) {
463
464 pfd.revents = 0;
465
466 /* poll() waits for events arriving on the FD, and returns < 0 if timeout passes
467 with no events or a signal occurred before any requested event (-EINTR).
468 We wait forever (timeout = -1), but even if we DO get an event, we have yet
469 to see if it's of the required type, then if it's a pageflip, etc */
470 ret = poll(&pfd, 1, -1);
471
472 if (ret < 0) {
473 if (errno == EINTR) {
474 /* poll() returning < 0 and setting errno = EINTR means there was a signal before
475 any requested event, so we immediately poll again. */
476 continue;
477 } else {
478 // There was another error. Don't pull again or we could get into a busy loop.
479 SDL_LogError(SDL_LOG_CATEGORY_VIDEO, "DRM poll error");
480 return false;
481 }
482 }
483
484 if (pfd.revents & (POLLHUP | POLLERR)) {
485 // An event arrived on the FD in time, but it's an error.
486 SDL_LogError(SDL_LOG_CATEGORY_VIDEO, "DRM poll hup or error");
487 return false;
488 }
489
490 if (pfd.revents & POLLIN) {
491 /* There is data to read on the FD!
492 Is the event a pageflip? We know it is a pageflip if it matches the
493 event we are passing in &ev. If it does, drmHandleEvent() will unset
494 windata->waiting_for_flip and we will get out of the "while" loop.
495 If it's not, we keep iterating on the loop. */
496 KMSDRM_drmHandleEvent(viddata->drm_fd, &ev);
497 }
498
499 /* If we got to this point in the loop, we may iterate or exit the loop:
500 -A legit (non-error) event arrived, and it was a POLLING event, and it was consumed
501 by drmHandleEvent().
502 -If it was a PAGEFLIP event, waiting_for_flip will be unset by drmHandleEvent()
503 and we will exit the loop.
504 -If it wasn't a PAGEFLIP, drmHandleEvent() won't unset waiting_for_flip, so we
505 iterare back to polling.
506 -A legit (non-error) event arrived, but it's not a POLLIN event, so it hasn't to be
507 consumed by drmHandleEvent(), so waiting_for_flip isn't set and we iterate back
508 to polling. */
509 }
510
511 return true;
512}
513
514/* Given w, h and refresh rate, returns the closest DRM video mode
515 available on the DRM connector of the display.
516 We use the SDL mode list (which we filled in KMSDRM_GetDisplayModes)
517 because it's ordered, while the list on the connector is mostly random.*/
518static drmModeModeInfo *KMSDRM_GetClosestDisplayMode(SDL_VideoDisplay *display, int width, int height)
519{
520
521 SDL_DisplayData *dispdata = display->internal;
522 drmModeConnector *connector = dispdata->connector;
523
524 SDL_DisplayMode closest;
525 drmModeModeInfo *drm_mode;
526
527 if (SDL_GetClosestFullscreenDisplayMode(display->id, width, height, 0.0f, false, &closest)) {
528 const SDL_DisplayModeData *modedata = closest.internal;
529 drm_mode = &connector->modes[modedata->mode_index];
530 return drm_mode;
531 } else {
532 return NULL;
533 }
534}
535
536/*****************************************************************************/
537// SDL Video and Display initialization/handling functions
538/* _this is a SDL_VideoDevice * */
539/*****************************************************************************/
540
541static bool KMSDRM_DropMaster(SDL_VideoDevice *_this)
542{
543 SDL_VideoData *viddata = _this->internal;
544
545 /* Check if we have DRM master to begin with */
546 if (KMSDRM_drmAuthMagic(viddata->drm_fd, 0) == -EACCES) {
547 /* Nope, nothing to do then */
548 return true;
549 }
550
551 return KMSDRM_drmDropMaster(viddata->drm_fd) == 0;
552}
553
554// Deinitializes the internal of the SDL Displays in the SDL display list.
555static void KMSDRM_DeinitDisplays(SDL_VideoDevice *_this)
556{
557 SDL_VideoData *viddata = _this->internal;
558 SDL_DisplayID *displays;
559 SDL_DisplayData *dispdata;
560 int i;
561
562 displays = SDL_GetDisplays(NULL);
563 if (displays) {
564 // Iterate on the SDL Display list.
565 for (i = 0; displays[i]; ++i) {
566
567 // Get the internal for this display
568 dispdata = SDL_GetDisplayDriverData(displays[i]);
569
570 // Free connector
571 if (dispdata && dispdata->connector) {
572 KMSDRM_drmModeFreeConnector(dispdata->connector);
573 dispdata->connector = NULL;
574 }
575
576 // Free CRTC
577 if (dispdata && dispdata->crtc) {
578 KMSDRM_drmModeFreeCrtc(dispdata->crtc);
579 dispdata->crtc = NULL;
580 }
581 }
582 SDL_free(displays);
583 }
584
585 if (viddata->drm_fd >= 0) {
586 close(viddata->drm_fd);
587 viddata->drm_fd = -1;
588 }
589}
590
591static uint32_t KMSDRM_CrtcGetPropId(uint32_t drm_fd,
592 drmModeObjectPropertiesPtr props,
593 char const *name)
594{
595 uint32_t i, prop_id = 0;
596
597 for (i = 0; !prop_id && i < props->count_props; ++i) {
598 drmModePropertyPtr drm_prop =
599 KMSDRM_drmModeGetProperty(drm_fd, props->props[i]);
600
601 if (!drm_prop) {
602 continue;
603 }
604
605 if (SDL_strcmp(drm_prop->name, name) == 0) {
606 prop_id = drm_prop->prop_id;
607 }
608
609 KMSDRM_drmModeFreeProperty(drm_prop);
610 }
611
612 return prop_id;
613}
614
615static bool KMSDRM_VrrPropId(uint32_t drm_fd, uint32_t crtc_id, uint32_t *vrr_prop_id)
616{
617 drmModeObjectPropertiesPtr drm_props;
618
619 drm_props = KMSDRM_drmModeObjectGetProperties(drm_fd,
620 crtc_id,
621 DRM_MODE_OBJECT_CRTC);
622
623 if (!drm_props) {
624 return false;
625 }
626
627 *vrr_prop_id = KMSDRM_CrtcGetPropId(drm_fd,
628 drm_props,
629 "VRR_ENABLED");
630
631 KMSDRM_drmModeFreeObjectProperties(drm_props);
632
633 return true;
634}
635
636static bool KMSDRM_ConnectorCheckVrrCapable(uint32_t drm_fd,
637 uint32_t output_id,
638 char const *name)
639{
640 uint32_t i;
641 bool found = false;
642 uint64_t prop_value = 0;
643
644 drmModeObjectPropertiesPtr props = KMSDRM_drmModeObjectGetProperties(drm_fd,
645 output_id,
646 DRM_MODE_OBJECT_CONNECTOR);
647
648 if (!props) {
649 return false;
650 }
651
652 for (i = 0; !found && i < props->count_props; ++i) {
653 drmModePropertyPtr drm_prop = KMSDRM_drmModeGetProperty(drm_fd, props->props[i]);
654
655 if (!drm_prop) {
656 continue;
657 }
658
659 if (SDL_strcasecmp(drm_prop->name, name) == 0) {
660 prop_value = props->prop_values[i];
661 found = true;
662 }
663
664 KMSDRM_drmModeFreeProperty(drm_prop);
665 }
666 if (found) {
667 return prop_value ? true : false;
668 }
669
670 return false;
671}
672
673static void KMSDRM_CrtcSetVrr(uint32_t drm_fd, uint32_t crtc_id, bool enabled)
674{
675 uint32_t vrr_prop_id;
676 if (!KMSDRM_VrrPropId(drm_fd, crtc_id, &vrr_prop_id)) {
677 return;
678 }
679
680 KMSDRM_drmModeObjectSetProperty(drm_fd,
681 crtc_id,
682 DRM_MODE_OBJECT_CRTC,
683 vrr_prop_id,
684 enabled);
685}
686
687static bool KMSDRM_CrtcGetVrr(uint32_t drm_fd, uint32_t crtc_id)
688{
689 uint32_t object_prop_id, vrr_prop_id;
690 drmModeObjectPropertiesPtr props;
691 bool object_prop_value;
692 int i;
693
694 if (!KMSDRM_VrrPropId(drm_fd, crtc_id, &vrr_prop_id)) {
695 return false;
696 }
697
698 props = KMSDRM_drmModeObjectGetProperties(drm_fd,
699 crtc_id,
700 DRM_MODE_OBJECT_CRTC);
701
702 if (!props) {
703 return false;
704 }
705
706 for (i = 0; i < props->count_props; ++i) {
707 drmModePropertyPtr drm_prop = KMSDRM_drmModeGetProperty(drm_fd, props->props[i]);
708
709 if (!drm_prop) {
710 continue;
711 }
712
713 object_prop_id = drm_prop->prop_id;
714 object_prop_value = props->prop_values[i] ? true : false;
715
716 KMSDRM_drmModeFreeProperty(drm_prop);
717
718 if (object_prop_id == vrr_prop_id) {
719 return object_prop_value;
720 }
721 }
722 return false;
723}
724
725static bool KMSDRM_OrientationPropId(uint32_t drm_fd, uint32_t crtc_id, uint32_t *orientation_prop_id)
726{
727 drmModeObjectPropertiesPtr drm_props;
728
729 drm_props = KMSDRM_drmModeObjectGetProperties(drm_fd,
730 crtc_id,
731 DRM_MODE_OBJECT_CONNECTOR);
732
733 if (!drm_props) {
734 return false;
735 }
736
737 *orientation_prop_id = KMSDRM_CrtcGetPropId(drm_fd,
738 drm_props,
739 "panel orientation");
740
741 KMSDRM_drmModeFreeObjectProperties(drm_props);
742
743 return true;
744}
745
746static int KMSDRM_CrtcGetOrientation(uint32_t drm_fd, uint32_t crtc_id)
747{
748 uint32_t orientation_prop_id;
749 drmModeObjectPropertiesPtr props;
750 int i;
751 bool done = false;
752 int orientation = 0;
753
754 if (!KMSDRM_OrientationPropId(drm_fd, crtc_id, &orientation_prop_id)) {
755 return orientation;
756 }
757
758 props = KMSDRM_drmModeObjectGetProperties(drm_fd,
759 crtc_id,
760 DRM_MODE_OBJECT_CONNECTOR);
761
762 if (!props) {
763 return orientation;
764 }
765
766 for (i = 0; i < props->count_props && !done; ++i) {
767 drmModePropertyPtr drm_prop = KMSDRM_drmModeGetProperty(drm_fd, props->props[i]);
768
769 if (!drm_prop) {
770 continue;
771 }
772
773 if (drm_prop->prop_id == orientation_prop_id && (drm_prop->flags & DRM_MODE_PROP_ENUM)) {
774 if (drm_prop->count_enums) {
775 // "Normal" is the default of no rotation (0 degrees)
776 if (SDL_strcmp(drm_prop->enums[0].name, "Left Side Up") == 0) {
777 orientation = 90;
778 } else if (SDL_strcmp(drm_prop->enums[0].name, "Upside Down") == 0) {
779 orientation = 180;
780 } else if (SDL_strcmp(drm_prop->enums[0].name, "Right Side Up") == 0) {
781 orientation = 270;
782 }
783 }
784
785 done = true;
786 }
787
788 KMSDRM_drmModeFreeProperty(drm_prop);
789 }
790
791 KMSDRM_drmModeFreeObjectProperties(props);
792
793 return orientation;
794}
795
796/* Gets a DRM connector, builds an SDL_Display with it, and adds it to the
797 list of SDL Displays in _this->displays[] */
798static void KMSDRM_AddDisplay(SDL_VideoDevice *_this, drmModeConnector *connector, drmModeRes *resources)
799{
800 SDL_VideoData *viddata = _this->internal;
801 SDL_DisplayData *dispdata = NULL;
802 SDL_VideoDisplay display = { 0 };
803 SDL_DisplayModeData *modedata = NULL;
804 drmModeEncoder *encoder = NULL;
805 drmModeCrtc *crtc = NULL;
806 const char *connector_type = NULL;
807 SDL_DisplayID display_id;
808 SDL_PropertiesID display_properties;
809 char name_fmt[64];
810 int orientation;
811 int mode_index;
812 int i, j;
813 int ret = 0;
814
815 // Reserve memory for the new display's internal.
816 dispdata = (SDL_DisplayData *)SDL_calloc(1, sizeof(SDL_DisplayData));
817 if (!dispdata) {
818 ret = -1;
819 goto cleanup;
820 }
821
822 /* Initialize some of the members of the new display's internal
823 to sane values. */
824 dispdata->cursor_bo = NULL;
825 dispdata->cursor_bo_drm_fd = -1;
826
827 /* Since we create and show the default cursor on KMSDRM_InitMouse(),
828 and we call KMSDRM_InitMouse() when we create a window, we have to know
829 if the display used by the window already has a default cursor or not.
830 If we don't, new default cursors would stack up on mouse->cursors and SDL
831 would have to hide and delete them at quit, not to mention the memory leak... */
832 dispdata->default_cursor_init = false;
833
834 // Try to find the connector's current encoder
835 for (i = 0; i < resources->count_encoders; i++) {
836 encoder = KMSDRM_drmModeGetEncoder(viddata->drm_fd, resources->encoders[i]);
837
838 if (!encoder) {
839 continue;
840 }
841
842 if (encoder->encoder_id == connector->encoder_id) {
843 break;
844 }
845
846 KMSDRM_drmModeFreeEncoder(encoder);
847 encoder = NULL;
848 }
849
850 if (!encoder) {
851 // No encoder was connected, find the first supported one
852 for (i = 0; i < resources->count_encoders; i++) {
853 encoder = KMSDRM_drmModeGetEncoder(viddata->drm_fd,
854 resources->encoders[i]);
855
856 if (!encoder) {
857 continue;
858 }
859
860 for (j = 0; j < connector->count_encoders; j++) {
861 if (connector->encoders[j] == encoder->encoder_id) {
862 break;
863 }
864 }
865
866 if (j != connector->count_encoders) {
867 break;
868 }
869
870 KMSDRM_drmModeFreeEncoder(encoder);
871 encoder = NULL;
872 }
873 }
874
875 if (!encoder) {
876 ret = SDL_SetError("No connected encoder found for connector.");
877 goto cleanup;
878 }
879
880 // Try to find a CRTC connected to this encoder
881 crtc = KMSDRM_drmModeGetCrtc(viddata->drm_fd, encoder->crtc_id);
882
883 /* If no CRTC was connected to the encoder, find the first CRTC
884 that is supported by the encoder, and use that. */
885 if (!crtc) {
886 for (i = 0; i < resources->count_crtcs; i++) {
887 if (encoder->possible_crtcs & (1 << i)) {
888 encoder->crtc_id = resources->crtcs[i];
889 crtc = KMSDRM_drmModeGetCrtc(viddata->drm_fd, encoder->crtc_id);
890 break;
891 }
892 }
893 }
894
895 if (!crtc) {
896 ret = SDL_SetError("No CRTC found for connector.");
897 goto cleanup;
898 }
899
900 // Find the index of the mode attached to this CRTC
901 mode_index = -1;
902
903 for (i = 0; i < connector->count_modes; i++) {
904 drmModeModeInfo *mode = &connector->modes[i];
905
906 if (!SDL_memcmp(mode, &crtc->mode, sizeof(crtc->mode))) {
907 mode_index = i;
908 break;
909 }
910 }
911
912 if (mode_index == -1) {
913 int current_area, largest_area = 0;
914
915 // Find the preferred mode or the highest resolution mode
916 for (i = 0; i < connector->count_modes; i++) {
917 drmModeModeInfo *mode = &connector->modes[i];
918
919 if (mode->type & DRM_MODE_TYPE_PREFERRED) {
920 mode_index = i;
921 break;
922 }
923
924 current_area = mode->hdisplay * mode->vdisplay;
925 if (current_area > largest_area) {
926 mode_index = i;
927 largest_area = current_area;
928 }
929 }
930 if (mode_index != -1) {
931 crtc->mode = connector->modes[mode_index];
932 }
933 }
934
935 if (mode_index == -1) {
936 ret = SDL_SetError("Failed to find index of mode attached to the CRTC.");
937 goto cleanup;
938 }
939
940 /*********************************************/
941 // Create an SDL Display for this connector.
942 /*********************************************/
943
944 /*********************************************/
945 // Part 1: setup the SDL_Display internal.
946 /*********************************************/
947
948 /* Get the mode currently setup for this display,
949 which is the mode currently setup on the CRTC
950 we found for the active connector. */
951 dispdata->mode = crtc->mode;
952 dispdata->original_mode = crtc->mode;
953 dispdata->fullscreen_mode = crtc->mode;
954
955 if (dispdata->mode.hdisplay == 0 || dispdata->mode.vdisplay == 0) {
956 ret = SDL_SetError("Couldn't get a valid connector videomode.");
957 goto cleanup;
958 }
959
960 // Store the connector and crtc for this display.
961 dispdata->connector = connector;
962 dispdata->crtc = crtc;
963
964 // save previous vrr state
965 dispdata->saved_vrr = KMSDRM_CrtcGetVrr(viddata->drm_fd, crtc->crtc_id);
966 // try to enable vrr
967 if (KMSDRM_ConnectorCheckVrrCapable(viddata->drm_fd, connector->connector_id, "VRR_CAPABLE")) {
968 SDL_LogDebug(SDL_LOG_CATEGORY_VIDEO, "Enabling VRR");
969 KMSDRM_CrtcSetVrr(viddata->drm_fd, crtc->crtc_id, true);
970 }
971
972 // Set the name by the connector type, if possible
973 if (KMSDRM_drmModeGetConnectorTypeName) {
974 connector_type = KMSDRM_drmModeGetConnectorTypeName(connector->connector_type);
975 if (connector_type == NULL) {
976 connector_type = "Unknown";
977 }
978 SDL_snprintf(name_fmt, sizeof(name_fmt), "%s-%u", connector_type, connector->connector_type_id);
979 }
980
981 /*****************************************/
982 // Part 2: setup the SDL_Display itself.
983 /*****************************************/
984
985 /* Setup the display.
986 There's no problem with it being still incomplete. */
987 modedata = SDL_calloc(1, sizeof(SDL_DisplayModeData));
988
989 if (!modedata) {
990 ret = -1;
991 goto cleanup;
992 }
993
994 modedata->mode_index = mode_index;
995
996 display.internal = dispdata;
997 display.desktop_mode.w = dispdata->mode.hdisplay;
998 display.desktop_mode.h = dispdata->mode.vdisplay;
999 CalculateRefreshRate(&dispdata->mode, &display.desktop_mode.refresh_rate_numerator, &display.desktop_mode.refresh_rate_denominator);
1000 display.desktop_mode.format = SDL_PIXELFORMAT_ARGB8888;
1001 display.desktop_mode.internal = modedata;
1002 if (connector_type) {
1003 display.name = name_fmt;
1004 }
1005
1006 // Add the display to the list of SDL displays.
1007 display_id = SDL_AddVideoDisplay(&display, false);
1008 if (!display_id) {
1009 ret = -1;
1010 goto cleanup;
1011 }
1012
1013 orientation = KMSDRM_CrtcGetOrientation(viddata->drm_fd, crtc->crtc_id);
1014 display_properties = SDL_GetDisplayProperties(display_id);
1015 SDL_SetNumberProperty(display_properties, SDL_PROP_DISPLAY_KMSDRM_PANEL_ORIENTATION_NUMBER, orientation);
1016
1017cleanup:
1018 if (encoder) {
1019 KMSDRM_drmModeFreeEncoder(encoder);
1020 }
1021 if (ret) {
1022 // Error (complete) cleanup
1023 if (dispdata) {
1024 if (dispdata->connector) {
1025 KMSDRM_drmModeFreeConnector(dispdata->connector);
1026 dispdata->connector = NULL;
1027 }
1028 if (dispdata->crtc) {
1029 KMSDRM_drmModeFreeCrtc(dispdata->crtc);
1030 dispdata->crtc = NULL;
1031 }
1032 SDL_free(dispdata);
1033 }
1034 }
1035} // NOLINT(clang-analyzer-unix.Malloc): If no error `dispdata` is saved in the display
1036
1037static void KMSDRM_SortDisplays(SDL_VideoDevice *_this)
1038{
1039 const char *name_hint = SDL_GetHint(SDL_HINT_VIDEO_DISPLAY_PRIORITY);
1040
1041 if (name_hint) {
1042 char *saveptr;
1043 char *str = SDL_strdup(name_hint);
1044 SDL_VideoDisplay **sorted_list = SDL_malloc(sizeof(SDL_VideoDisplay *) * _this->num_displays);
1045
1046 if (str && sorted_list) {
1047 int sorted_index = 0;
1048
1049 // Sort the requested displays to the front of the list.
1050 const char *token = SDL_strtok_r(str, ",", &saveptr);
1051 while (token) {
1052 for (int i = 0; i < _this->num_displays; ++i) {
1053 SDL_VideoDisplay *d = _this->displays[i];
1054 if (d && SDL_strcmp(token, d->name) == 0) {
1055 sorted_list[sorted_index++] = d;
1056 _this->displays[i] = NULL;
1057 break;
1058 }
1059 }
1060
1061 token = SDL_strtok_r(NULL, ",", &saveptr);
1062 }
1063
1064 // Append the remaining displays to the end of the list.
1065 for (int i = 0; i < _this->num_displays; ++i) {
1066 if (_this->displays[i]) {
1067 sorted_list[sorted_index++] = _this->displays[i];
1068 }
1069 }
1070
1071 // Copy the sorted list back to the display list.
1072 SDL_memcpy(_this->displays, sorted_list, sizeof(SDL_VideoDisplay *) * _this->num_displays);
1073 }
1074
1075 SDL_free(str);
1076 SDL_free(sorted_list);
1077 }
1078}
1079
1080/* Initializes the list of SDL displays: we build a new display for each
1081 connecter connector we find.
1082 This is to be called early, in VideoInit(), because it gets us
1083 the videomode information, which SDL needs immediately after VideoInit(). */
1084static bool KMSDRM_InitDisplays(SDL_VideoDevice *_this)
1085{
1086
1087 SDL_VideoData *viddata = _this->internal;
1088 drmModeRes *resources = NULL;
1089 uint64_t async_pageflip = 0;
1090 int i;
1091 bool result = true;
1092
1093 // Open /dev/dri/cardNN (/dev/drmN if on OpenBSD version less than 6.9)
1094 (void)SDL_snprintf(viddata->devpath, sizeof(viddata->devpath), "%s%d",
1095 kmsdrm_dri_cardpath, viddata->devindex);
1096
1097 SDL_LogDebug(SDL_LOG_CATEGORY_VIDEO, "Opening device %s", viddata->devpath);
1098 viddata->drm_fd = open(viddata->devpath, O_RDWR | O_CLOEXEC);
1099
1100 if (viddata->drm_fd < 0) {
1101 result = SDL_SetError("Could not open %s", viddata->devpath);
1102 goto cleanup;
1103 }
1104
1105 SDL_LogDebug(SDL_LOG_CATEGORY_VIDEO, "Opened DRM FD (%d)", viddata->drm_fd);
1106
1107 // Get all of the available connectors / devices / crtcs
1108 resources = KMSDRM_drmModeGetResources(viddata->drm_fd);
1109 if (!resources) {
1110 result = SDL_SetError("drmModeGetResources(%d) failed", viddata->drm_fd);
1111 goto cleanup;
1112 }
1113
1114 /* Iterate on the available connectors. For every connected connector,
1115 we create an SDL_Display and add it to the list of SDL Displays. */
1116 for (i = 0; i < resources->count_connectors; i++) {
1117 drmModeConnector *connector = KMSDRM_drmModeGetConnector(viddata->drm_fd,
1118 resources->connectors[i]);
1119
1120 if (!connector) {
1121 continue;
1122 }
1123
1124 if (connector->connection == DRM_MODE_CONNECTED && connector->count_modes) {
1125 /* If it's a connected connector with available videomodes, try to add
1126 an SDL Display representing it. KMSDRM_AddDisplay() is purposely void,
1127 so if it fails (no encoder for connector, no valid video mode for
1128 connector etc...) we can keep looking for connected connectors. */
1129 KMSDRM_AddDisplay(_this, connector, resources);
1130 } else {
1131 // If it's not, free it now.
1132 KMSDRM_drmModeFreeConnector(connector);
1133 }
1134 }
1135
1136 // Have we added any SDL displays?
1137 if (SDL_GetPrimaryDisplay() == 0) {
1138 result = SDL_SetError("No connected displays found.");
1139 goto cleanup;
1140 }
1141
1142 // Sort the displays, if necessary
1143 KMSDRM_SortDisplays(_this);
1144
1145 // Determine if video hardware supports async pageflips.
1146 if (KMSDRM_drmGetCap(viddata->drm_fd, DRM_CAP_ASYNC_PAGE_FLIP, &async_pageflip) != 0) {
1147 SDL_LogError(SDL_LOG_CATEGORY_VIDEO, "Could not determine async page flip capability.");
1148 }
1149 viddata->async_pageflip_support = async_pageflip ? true : false;
1150
1151 /***********************************/
1152 // Block for Vulkan compatibility.
1153 /***********************************/
1154
1155 /* Vulkan requires DRM master on its own FD to work, so try to drop master
1156 on our FD. This will only work without root on kernels v5.8 and later.
1157 If it doesn't work, just close the FD and we'll reopen it later. */
1158 if (!KMSDRM_DropMaster(_this)) {
1159 close(viddata->drm_fd);
1160 viddata->drm_fd = -1;
1161 }
1162
1163cleanup:
1164 if (resources) {
1165 KMSDRM_drmModeFreeResources(resources);
1166 }
1167 if (!result) {
1168 if (viddata->drm_fd >= 0) {
1169 close(viddata->drm_fd);
1170 viddata->drm_fd = -1;
1171 }
1172 }
1173 return result;
1174}
1175
1176/* Init the Vulkan-INCOMPATIBLE stuff:
1177 Reopen FD, create gbm dev, create dumb buffer and setup display plane.
1178 This is to be called late, in WindowCreate(), and ONLY if this is not
1179 a Vulkan window.
1180 We are doing this so late to allow Vulkan to work if we build a VK window.
1181 These things are incompatible with Vulkan, which accesses the same resources
1182 internally so they must be free when trying to build a Vulkan surface.
1183*/
1184static bool KMSDRM_GBMInit(SDL_VideoDevice *_this, SDL_DisplayData *dispdata)
1185{
1186 SDL_VideoData *viddata = _this->internal;
1187 bool result = true;
1188
1189 // Reopen the FD if we weren't able to drop master on the original one
1190 if (viddata->drm_fd < 0) {
1191 viddata->drm_fd = open(viddata->devpath, O_RDWR | O_CLOEXEC);
1192 if (viddata->drm_fd < 0) {
1193 return SDL_SetError("Could not reopen %s", viddata->devpath);
1194 }
1195 }
1196
1197 // Set the FD as current DRM master.
1198 KMSDRM_drmSetMaster(viddata->drm_fd);
1199
1200 // Create the GBM device.
1201 viddata->gbm_dev = KMSDRM_gbm_create_device(viddata->drm_fd);
1202 if (!viddata->gbm_dev) {
1203 result = SDL_SetError("Couldn't create gbm device.");
1204 }
1205
1206 viddata->gbm_init = true;
1207
1208 return result;
1209}
1210
1211// Deinit the Vulkan-incompatible KMSDRM stuff.
1212static void KMSDRM_GBMDeinit(SDL_VideoDevice *_this, SDL_DisplayData *dispdata)
1213{
1214 SDL_VideoData *viddata = _this->internal;
1215
1216 /* Destroy GBM device. GBM surface is destroyed by DestroySurfaces(),
1217 already called when we get here. */
1218 if (viddata->gbm_dev) {
1219 KMSDRM_gbm_device_destroy(viddata->gbm_dev);
1220 viddata->gbm_dev = NULL;
1221 }
1222
1223 /* Finally drop DRM master if possible, otherwise close DRM FD.
1224 May be reopened on next non-vulkan window creation. */
1225 if (viddata->drm_fd >= 0 && !KMSDRM_DropMaster(_this)) {
1226 close(viddata->drm_fd);
1227 viddata->drm_fd = -1;
1228 }
1229
1230 viddata->gbm_init = false;
1231}
1232
1233static void KMSDRM_DestroySurfaces(SDL_VideoDevice *_this, SDL_Window *window)
1234{
1235 SDL_VideoData *viddata = _this->internal;
1236 SDL_WindowData *windata = window->internal;
1237 SDL_DisplayData *dispdata = SDL_GetDisplayDriverDataForWindow(window);
1238 int ret;
1239
1240 /**********************************************/
1241 // Wait for last issued pageflip to complete.
1242 /**********************************************/
1243 // KMSDRM_WaitPageflip(_this, windata);
1244
1245 /************************************************************************/
1246 // Restore the original CRTC configuration: configure the crtc with the
1247 // original video mode and make it point to the original TTY buffer.
1248 /************************************************************************/
1249
1250 ret = KMSDRM_drmModeSetCrtc(viddata->drm_fd, dispdata->crtc->crtc_id,
1251 dispdata->crtc->buffer_id, 0, 0, &dispdata->connector->connector_id, 1,
1252 &dispdata->original_mode);
1253
1254 // If we failed to set the original mode, try to set the connector preferred mode.
1255 if (ret && (dispdata->crtc->mode_valid == 0)) {
1256 ret = KMSDRM_drmModeSetCrtc(viddata->drm_fd, dispdata->crtc->crtc_id,
1257 dispdata->crtc->buffer_id, 0, 0, &dispdata->connector->connector_id, 1,
1258 &dispdata->original_mode);
1259 }
1260
1261 if (ret) {
1262 SDL_LogError(SDL_LOG_CATEGORY_VIDEO, "Could not restore CRTC");
1263 }
1264
1265 /***************************/
1266 // Destroy the EGL surface
1267 /***************************/
1268
1269 SDL_EGL_MakeCurrent(_this, EGL_NO_SURFACE, EGL_NO_CONTEXT);
1270
1271 if (windata->egl_surface != EGL_NO_SURFACE) {
1272 SDL_EGL_DestroySurface(_this, windata->egl_surface);
1273 windata->egl_surface = EGL_NO_SURFACE;
1274 }
1275
1276 /***************************/
1277 // Destroy the GBM buffers
1278 /***************************/
1279
1280 if (windata->bo) {
1281 KMSDRM_gbm_surface_release_buffer(windata->gs, windata->bo);
1282 windata->bo = NULL;
1283 }
1284
1285 if (windata->next_bo) {
1286 KMSDRM_gbm_surface_release_buffer(windata->gs, windata->next_bo);
1287 windata->next_bo = NULL;
1288 }
1289
1290 /***************************/
1291 // Destroy the GBM surface
1292 /***************************/
1293
1294 if (windata->gs) {
1295 KMSDRM_gbm_surface_destroy(windata->gs);
1296 windata->gs = NULL;
1297 }
1298}
1299
1300static void KMSDRM_GetModeToSet(SDL_Window *window, drmModeModeInfo *out_mode)
1301{
1302 SDL_VideoDisplay *display = SDL_GetVideoDisplayForWindow(window);
1303 SDL_DisplayData *dispdata = display->internal;
1304
1305 if (window->fullscreen_exclusive) {
1306 *out_mode = dispdata->fullscreen_mode;
1307 } else {
1308 drmModeModeInfo *mode = KMSDRM_GetClosestDisplayMode(display, window->windowed.w, window->windowed.h);
1309 if (mode) {
1310 *out_mode = *mode;
1311 } else {
1312 *out_mode = dispdata->original_mode;
1313 }
1314 }
1315}
1316
1317static void KMSDRM_DirtySurfaces(SDL_Window *window)
1318{
1319 SDL_WindowData *windata = window->internal;
1320 drmModeModeInfo mode;
1321
1322 /* Can't recreate EGL surfaces right now, need to wait until SwapWindow
1323 so the correct thread-local surface and context state are available */
1324 windata->egl_surface_dirty = true;
1325
1326 /* The app may be waiting for the resize event after calling SetWindowSize
1327 or SetWindowFullscreen, send a fake event for now since the actual
1328 recreation is deferred */
1329 KMSDRM_GetModeToSet(window, &mode);
1330 SDL_SendWindowEvent(window, SDL_EVENT_WINDOW_RESIZED, mode.hdisplay, mode.vdisplay);
1331}
1332
1333/* This determines the size of the fb, which comes from the GBM surface
1334 that we create here. */
1335bool KMSDRM_CreateSurfaces(SDL_VideoDevice *_this, SDL_Window *window)
1336{
1337 SDL_VideoData *viddata = _this->internal;
1338 SDL_WindowData *windata = window->internal;
1339 SDL_VideoDisplay *display = SDL_GetVideoDisplayForWindow(window);
1340 SDL_DisplayData *dispdata = display->internal;
1341
1342 uint32_t surface_fmt = GBM_FORMAT_ARGB8888;
1343 uint32_t surface_flags = GBM_BO_USE_SCANOUT | GBM_BO_USE_RENDERING;
1344
1345 EGLContext egl_context;
1346
1347 bool result = true;
1348
1349 // If the current window already has surfaces, destroy them before creating other.
1350 if (windata->gs) {
1351 KMSDRM_DestroySurfaces(_this, window);
1352 }
1353
1354 if (!KMSDRM_gbm_device_is_format_supported(viddata->gbm_dev,
1355 surface_fmt, surface_flags)) {
1356 SDL_LogWarn(SDL_LOG_CATEGORY_VIDEO,
1357 "GBM surface format not supported. Trying anyway.");
1358 }
1359
1360 /* The KMSDRM backend doesn't always set the mode the higher-level code in
1361 SDL_video.c expects. Hulk-smash the display's current_mode to keep the
1362 mode that's set in sync with what SDL_video.c thinks is set
1363
1364 FIXME: How do we do that now? Can we get a better idea at the higher level?
1365 */
1366 KMSDRM_GetModeToSet(window, &dispdata->mode);
1367
1368 windata->gs = KMSDRM_gbm_surface_create(viddata->gbm_dev,
1369 dispdata->mode.hdisplay, dispdata->mode.vdisplay,
1370 surface_fmt, surface_flags);
1371
1372 if (!windata->gs) {
1373 return SDL_SetError("Could not create GBM surface");
1374 }
1375
1376 /* We can't get the EGL context yet because SDL_CreateRenderer has not been called,
1377 but we need an EGL surface NOW, or GL won't be able to render into any surface
1378 and we won't see the first frame. */
1379 SDL_EGL_SetRequiredVisualId(_this, surface_fmt);
1380 windata->egl_surface = SDL_EGL_CreateSurface(_this, window, (NativeWindowType)windata->gs);
1381
1382 if (windata->egl_surface == EGL_NO_SURFACE) {
1383 result = SDL_SetError("Could not create EGL window surface");
1384 goto cleanup;
1385 }
1386
1387 /* Current context passing to EGL is now done here. If something fails,
1388 go back to delayed SDL_EGL_MakeCurrent() call in SwapWindow. */
1389 egl_context = (EGLContext)SDL_GL_GetCurrentContext();
1390 result = SDL_EGL_MakeCurrent(_this, windata->egl_surface, egl_context);
1391
1392 SDL_SendWindowEvent(window, SDL_EVENT_WINDOW_RESIZED,
1393 dispdata->mode.hdisplay, dispdata->mode.vdisplay);
1394
1395 windata->egl_surface_dirty = false;
1396
1397cleanup:
1398
1399 if (!result) {
1400 // Error (complete) cleanup.
1401 if (windata->gs) {
1402 KMSDRM_gbm_surface_destroy(windata->gs);
1403 windata->gs = NULL;
1404 }
1405 }
1406
1407 return result;
1408}
1409
1410#ifdef SDL_INPUT_LINUXEV
1411static void KMSDRM_ReleaseVT(void *userdata)
1412{
1413 SDL_VideoDevice *_this = (SDL_VideoDevice *)userdata;
1414 SDL_VideoData *viddata = _this->internal;
1415 int i;
1416
1417 for (i = 0; i < viddata->num_windows; i++) {
1418 SDL_Window *window = viddata->windows[i];
1419 if (!(window->flags & SDL_WINDOW_VULKAN)) {
1420 KMSDRM_DestroySurfaces(_this, window);
1421 }
1422 }
1423 KMSDRM_drmDropMaster(viddata->drm_fd);
1424}
1425
1426static void KMSDRM_AcquireVT(void *userdata)
1427{
1428 SDL_VideoDevice *_this = (SDL_VideoDevice *)userdata;
1429 SDL_VideoData *viddata = _this->internal;
1430 int i;
1431
1432 KMSDRM_drmSetMaster(viddata->drm_fd);
1433 for (i = 0; i < viddata->num_windows; i++) {
1434 SDL_Window *window = viddata->windows[i];
1435 if (!(window->flags & SDL_WINDOW_VULKAN)) {
1436 KMSDRM_CreateSurfaces(_this, window);
1437 }
1438 }
1439}
1440#endif // defined SDL_INPUT_LINUXEV
1441
1442bool KMSDRM_VideoInit(SDL_VideoDevice *_this)
1443{
1444 bool result = true;
1445
1446 SDL_VideoData *viddata = _this->internal;
1447 SDL_LogDebug(SDL_LOG_CATEGORY_VIDEO, "KMSDRM_VideoInit()");
1448
1449 viddata->video_init = false;
1450 viddata->gbm_init = false;
1451
1452 /* Get KMSDRM resources info and store what we need. Getting and storing
1453 this info isn't a problem for VK compatibility.
1454 For VK-incompatible initializations we have KMSDRM_GBMInit(), which is
1455 called on window creation, and only when we know it's not a VK window. */
1456 if (!KMSDRM_InitDisplays(_this)) {
1457 result = SDL_SetError("error getting KMSDRM displays information");
1458 }
1459
1460#ifdef SDL_INPUT_LINUXEV
1461 SDL_EVDEV_Init();
1462 SDL_EVDEV_SetVTSwitchCallbacks(KMSDRM_ReleaseVT, _this, KMSDRM_AcquireVT, _this);
1463#elif defined(SDL_INPUT_WSCONS)
1464 SDL_WSCONS_Init();
1465#endif
1466
1467 viddata->video_init = true;
1468
1469 return result;
1470}
1471
1472/* The internal pointers, like dispdata, viddata, windata, etc...
1473 are freed by SDL internals, so not our job. */
1474void KMSDRM_VideoQuit(SDL_VideoDevice *_this)
1475{
1476 SDL_VideoData *viddata = _this->internal;
1477
1478 KMSDRM_DeinitDisplays(_this);
1479
1480#ifdef SDL_INPUT_LINUXEV
1481 SDL_EVDEV_SetVTSwitchCallbacks(NULL, NULL, NULL, NULL);
1482 SDL_EVDEV_Quit();
1483#elif defined(SDL_INPUT_WSCONS)
1484 SDL_WSCONS_Quit();
1485#endif
1486
1487 // Clear out the window list
1488 SDL_free(viddata->windows);
1489 viddata->windows = NULL;
1490 viddata->max_windows = 0;
1491 viddata->num_windows = 0;
1492 viddata->video_init = false;
1493}
1494
1495// Read modes from the connector modes, and store them in display->display_modes.
1496bool KMSDRM_GetDisplayModes(SDL_VideoDevice *_this, SDL_VideoDisplay *display)
1497{
1498 SDL_DisplayData *dispdata = display->internal;
1499 drmModeConnector *conn = dispdata->connector;
1500 SDL_DisplayMode mode;
1501 int i;
1502
1503 for (i = 0; i < conn->count_modes; i++) {
1504 SDL_DisplayModeData *modedata = SDL_calloc(1, sizeof(SDL_DisplayModeData));
1505
1506 if (modedata) {
1507 modedata->mode_index = i;
1508 }
1509
1510 SDL_zero(mode);
1511 mode.w = conn->modes[i].hdisplay;
1512 mode.h = conn->modes[i].vdisplay;
1513 CalculateRefreshRate(&conn->modes[i], &mode.refresh_rate_numerator, &mode.refresh_rate_denominator);
1514 mode.format = SDL_PIXELFORMAT_ARGB8888;
1515 mode.internal = modedata;
1516
1517 if (!SDL_AddFullscreenDisplayMode(display, &mode)) {
1518 SDL_free(modedata);
1519 }
1520 }
1521 return true;
1522}
1523
1524bool KMSDRM_SetDisplayMode(SDL_VideoDevice *_this, SDL_VideoDisplay *display, SDL_DisplayMode *mode)
1525{
1526 /* Set the dispdata->mode to the new mode and leave actual modesetting
1527 pending to be done on SwapWindow() via drmModeSetCrtc() */
1528
1529 SDL_VideoData *viddata = _this->internal;
1530 SDL_DisplayData *dispdata = display->internal;
1531 SDL_DisplayModeData *modedata = mode->internal;
1532 drmModeConnector *conn = dispdata->connector;
1533 int i;
1534
1535 // Don't do anything if we are in Vulkan mode.
1536 if (viddata->vulkan_mode) {
1537 return true;
1538 }
1539
1540 if (!modedata) {
1541 return SDL_SetError("Mode doesn't have an associated index");
1542 }
1543
1544 /* Take note of the new mode to be set, and leave the CRTC modeset pending
1545 so it's done in SwapWindow. */
1546 dispdata->fullscreen_mode = conn->modes[modedata->mode_index];
1547
1548 for (i = 0; i < viddata->num_windows; i++) {
1549 KMSDRM_DirtySurfaces(viddata->windows[i]);
1550 }
1551
1552 return true;
1553}
1554
1555void KMSDRM_DestroyWindow(SDL_VideoDevice *_this, SDL_Window *window)
1556{
1557 SDL_WindowData *windata = window->internal;
1558 SDL_DisplayData *dispdata = SDL_GetDisplayDriverDataForWindow(window);
1559 SDL_VideoData *viddata;
1560 bool is_vulkan = window->flags & SDL_WINDOW_VULKAN; // Is this a VK window?
1561 unsigned int i, j;
1562
1563 if (!windata) {
1564 return;
1565 }
1566
1567 // restore vrr state
1568 KMSDRM_CrtcSetVrr(windata->viddata->drm_fd, dispdata->crtc->crtc_id, dispdata->saved_vrr);
1569
1570 viddata = windata->viddata;
1571
1572 if (!is_vulkan && viddata->gbm_init) {
1573
1574 // Destroy cursor GBM BO of the display of this window.
1575 KMSDRM_DestroyCursorBO(_this, SDL_GetVideoDisplayForWindow(window));
1576
1577 // Destroy GBM surface and buffers.
1578 KMSDRM_DestroySurfaces(_this, window);
1579
1580 /* Unload library and deinit GBM, but only if this is the last window.
1581 Note that this is the right comparison because num_windows could be 1
1582 if there is a complete window, or 0 if we got here from SDL_CreateWindow()
1583 because KMSDRM_CreateWindow() returned an error so the window wasn't
1584 added to the windows list. */
1585 if (viddata->num_windows <= 1) {
1586
1587 // Unload EGL/GL library and free egl_data.
1588 if (_this->egl_data) {
1589 SDL_EGL_UnloadLibrary(_this);
1590 _this->gl_config.driver_loaded = 0;
1591 }
1592
1593 // Free display plane, and destroy GBM device.
1594 KMSDRM_GBMDeinit(_this, dispdata);
1595 }
1596
1597 } else {
1598
1599 // If we were in Vulkan mode, get out of it.
1600 if (viddata->vulkan_mode) {
1601 viddata->vulkan_mode = false;
1602 }
1603 }
1604
1605 /********************************************/
1606 // Remove from the internal SDL window list
1607 /********************************************/
1608
1609 for (i = 0; i < viddata->num_windows; i++) {
1610 if (viddata->windows[i] == window) {
1611 viddata->num_windows--;
1612
1613 for (j = i; j < viddata->num_windows; j++) {
1614 viddata->windows[j] = viddata->windows[j + 1];
1615 }
1616
1617 break;
1618 }
1619 }
1620
1621 /*********************************************************************/
1622 // Free the window internal. Bye bye, surface and buffer pointers!
1623 /*********************************************************************/
1624 SDL_free(window->internal);
1625 window->internal = NULL;
1626}
1627
1628/**********************************************************************/
1629// We simply IGNORE if it's a fullscreen window, window->flags don't
1630// reflect it: if it's fullscreen, KMSDRM_SetWindwoFullscreen() will
1631// be called by SDL later, and we can manage it there.
1632/**********************************************************************/
1633bool KMSDRM_CreateWindow(SDL_VideoDevice *_this, SDL_Window *window, SDL_PropertiesID create_props)
1634{
1635 SDL_WindowData *windata = NULL;
1636 SDL_VideoData *viddata = _this->internal;
1637 SDL_VideoDisplay *display = SDL_GetVideoDisplayForWindow(window);
1638 SDL_DisplayData *dispdata = display->internal;
1639 bool is_vulkan = window->flags & SDL_WINDOW_VULKAN; // Is this a VK window?
1640 bool vulkan_mode = viddata->vulkan_mode; // Do we have any Vulkan windows?
1641 NativeDisplayType egl_display;
1642 drmModeModeInfo *mode;
1643 bool result = true;
1644
1645 // Allocate window internal data
1646 windata = (SDL_WindowData *)SDL_calloc(1, sizeof(SDL_WindowData));
1647 if (!windata) {
1648 return false;
1649 }
1650
1651 // Setup driver data for this window
1652 windata->viddata = viddata;
1653 window->internal = windata;
1654
1655 // Do we want a double buffering scheme to get low video lag?
1656 windata->double_buffer = false;
1657 if (SDL_GetHintBoolean(SDL_HINT_VIDEO_DOUBLE_BUFFER, false)) {
1658 windata->double_buffer = true;
1659 }
1660
1661 if (!is_vulkan && !vulkan_mode) { // NON-Vulkan block.
1662
1663 /* Maybe you didn't ask for an OPENGL window, but that's what you will get.
1664 See following comments on why. */
1665 window->flags |= SDL_WINDOW_OPENGL;
1666
1667 if (!(viddata->gbm_init)) {
1668
1669 /* After SDL_CreateWindow, most SDL programs will do SDL_CreateRenderer(),
1670 which will in turn call GL_CreateRenderer() or GLES2_CreateRenderer().
1671 In order for the GL_CreateRenderer() or GLES2_CreateRenderer() call to
1672 succeed without an unnecessary window re-creation, we must:
1673 -Mark the window as being OPENGL
1674 -Load the GL library (which can't be done until the GBM device has been
1675 created, so we have to do it here instead of doing it on VideoInit())
1676 and mark it as loaded by setting gl_config.driver_loaded to 1.
1677 So if you ever see KMSDRM_CreateWindow() to be called two times in tests,
1678 don't be shy to debug GL_CreateRenderer() or GLES2_CreateRenderer()
1679 to find out why!
1680 */
1681
1682 /* Reopen FD, create gbm dev, setup display plane, etc,.
1683 but only when we come here for the first time,
1684 and only if it's not a VK window. */
1685 if (!KMSDRM_GBMInit(_this, dispdata)) {
1686 return SDL_SetError("Can't init GBM on window creation.");
1687 }
1688 }
1689
1690 /* Manually load the GL library. KMSDRM_EGL_LoadLibrary() has already
1691 been called by SDL_CreateWindow() but we don't do anything there,
1692 our KMSDRM_EGL_LoadLibrary() is a dummy precisely to be able to load it here.
1693 If we let SDL_CreateWindow() load the lib, it would be loaded
1694 before we call KMSDRM_GBMInit(), causing all GLES programs to fail. */
1695 if (!_this->egl_data) {
1696 egl_display = (NativeDisplayType)_this->internal->gbm_dev;
1697 if (!SDL_EGL_LoadLibrary(_this, NULL, egl_display, EGL_PLATFORM_GBM_MESA)) {
1698 // Try again with OpenGL ES 2.0
1699 _this->gl_config.profile_mask = SDL_GL_CONTEXT_PROFILE_ES;
1700 _this->gl_config.major_version = 2;
1701 _this->gl_config.minor_version = 0;
1702 if (!SDL_EGL_LoadLibrary(_this, NULL, egl_display, EGL_PLATFORM_GBM_MESA)) {
1703 return SDL_SetError("Can't load EGL/GL library on window creation.");
1704 }
1705 }
1706
1707 _this->gl_config.driver_loaded = 1;
1708 }
1709
1710 /* Create the cursor BO for the display of this window,
1711 now that we know this is not a VK window. */
1712 KMSDRM_CreateCursorBO(display);
1713
1714 /* Create and set the default cursor for the display
1715 of this window, now that we know this is not a VK window. */
1716 KMSDRM_InitMouse(_this, display);
1717
1718 /* The FULLSCREEN flags are cut out from window->flags at this point,
1719 so we can't know if a window is fullscreen or not, hence all windows
1720 are considered "windowed" at this point of their life.
1721 If a window is fullscreen, SDL internals will call
1722 KMSDRM_SetWindowFullscreen() to reconfigure it if necessary. */
1723 mode = KMSDRM_GetClosestDisplayMode(display, window->windowed.w, window->windowed.h);
1724
1725 if (mode) {
1726 dispdata->fullscreen_mode = *mode;
1727 } else {
1728 dispdata->fullscreen_mode = dispdata->original_mode;
1729 }
1730
1731 /* Create the window surfaces with the size we have just chosen.
1732 Needs the window diverdata in place. */
1733 if (!KMSDRM_CreateSurfaces(_this, window)) {
1734 return SDL_SetError("Can't window GBM/EGL surfaces on window creation.");
1735 }
1736 } // NON-Vulkan block ends.
1737
1738 /* Add window to the internal list of tracked windows. Note, while it may
1739 seem odd to support multiple fullscreen windows, some apps create an
1740 extra window as a dummy surface when working with multiple contexts */
1741 if (viddata->num_windows >= viddata->max_windows) {
1742 unsigned int new_max_windows = viddata->max_windows + 1;
1743 SDL_Window **new_windows = (SDL_Window **)SDL_realloc(viddata->windows,
1744 new_max_windows * sizeof(SDL_Window *));
1745 if (!new_windows) {
1746 return false;
1747 }
1748 viddata->windows = new_windows;
1749 viddata->max_windows = new_max_windows;
1750
1751 }
1752
1753 viddata->windows[viddata->num_windows++] = window;
1754
1755 // If we have just created a Vulkan window, establish that we are in Vulkan mode now.
1756 viddata->vulkan_mode = is_vulkan;
1757
1758 SDL_PropertiesID props = SDL_GetWindowProperties(window);
1759 SDL_SetNumberProperty(props, SDL_PROP_WINDOW_KMSDRM_DEVICE_INDEX_NUMBER, viddata->devindex);
1760 SDL_SetNumberProperty(props, SDL_PROP_WINDOW_KMSDRM_DRM_FD_NUMBER, viddata->drm_fd);
1761 SDL_SetPointerProperty(props, SDL_PROP_WINDOW_KMSDRM_GBM_DEVICE_POINTER, viddata->gbm_dev);
1762
1763 /* Focus on the newly created window.
1764 SDL_SetMouseFocus() also takes care of calling KMSDRM_ShowCursor() if necessary. */
1765 SDL_SetMouseFocus(window);
1766 SDL_SetKeyboardFocus(window);
1767
1768 // Tell the app that the window has moved to top-left.
1769 SDL_Rect display_bounds;
1770 SDL_GetDisplayBounds(SDL_GetDisplayForWindow(window), &display_bounds);
1771 SDL_SendWindowEvent(window, SDL_EVENT_WINDOW_MOVED, display_bounds.x, display_bounds.y);
1772
1773 /* Allocated windata will be freed in KMSDRM_DestroyWindow,
1774 and KMSDRM_DestroyWindow() will be called by SDL_CreateWindow()
1775 if we return error on any of the previous returns of the function. */
1776 return result;
1777}
1778
1779void KMSDRM_SetWindowTitle(SDL_VideoDevice *_this, SDL_Window *window)
1780{
1781}
1782bool KMSDRM_SetWindowPosition(SDL_VideoDevice *_this, SDL_Window *window)
1783{
1784 return SDL_Unsupported();
1785}
1786void KMSDRM_SetWindowSize(SDL_VideoDevice *_this, SDL_Window *window)
1787{
1788 SDL_VideoData *viddata = _this->internal;
1789 if (!viddata->vulkan_mode) {
1790 KMSDRM_DirtySurfaces(window);
1791 }
1792}
1793SDL_FullscreenResult KMSDRM_SetWindowFullscreen(SDL_VideoDevice *_this, SDL_Window *window, SDL_VideoDisplay *display, SDL_FullscreenOp fullscreen)
1794{
1795 SDL_VideoData *viddata = _this->internal;
1796 if (!viddata->vulkan_mode) {
1797 KMSDRM_DirtySurfaces(window);
1798 }
1799 return SDL_FULLSCREEN_SUCCEEDED;
1800}
1801void KMSDRM_ShowWindow(SDL_VideoDevice *_this, SDL_Window *window)
1802{
1803}
1804void KMSDRM_HideWindow(SDL_VideoDevice *_this, SDL_Window *window)
1805{
1806}
1807void KMSDRM_RaiseWindow(SDL_VideoDevice *_this, SDL_Window *window)
1808{
1809}
1810void KMSDRM_MaximizeWindow(SDL_VideoDevice *_this, SDL_Window *window)
1811{
1812}
1813void KMSDRM_MinimizeWindow(SDL_VideoDevice *_this, SDL_Window *window)
1814{
1815}
1816void KMSDRM_RestoreWindow(SDL_VideoDevice *_this, SDL_Window *window)
1817{
1818}
1819
1820#endif // SDL_VIDEO_DRIVER_KMSDRM
1821