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 |
58 | static bool moderndri = false; |
59 | #else |
60 | static bool moderndri = true; |
61 | #endif |
62 | |
63 | static char kmsdrm_dri_path[16]; |
64 | static int kmsdrm_dri_pathsize = 0; |
65 | static char kmsdrm_dri_devname[8]; |
66 | static int kmsdrm_dri_devnamesize = 0; |
67 | static char kmsdrm_dri_cardpath[32]; |
68 | |
69 | #ifndef EGL_PLATFORM_GBM_MESA |
70 | #define EGL_PLATFORM_GBM_MESA 0x31D7 |
71 | #endif |
72 | |
73 | static 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 | |
176 | static 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 | |
194 | static 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 | |
232 | static 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 | |
244 | static 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 | |
320 | cleanup: |
321 | if (device) { |
322 | SDL_free(device); |
323 | } |
324 | |
325 | if (viddata) { |
326 | SDL_free(viddata); |
327 | } |
328 | return NULL; |
329 | } |
330 | |
331 | VideoBootStrap KMSDRM_bootstrap = { |
332 | "kmsdrm" , |
333 | "KMS/DRM Video Driver" , |
334 | KMSDRM_CreateDevice, |
335 | NULL, // no ShowMessageBox implementation |
336 | false |
337 | }; |
338 | |
339 | static 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 | |
351 | KMSDRM_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 | |
426 | static void KMSDRM_FlipHandler(int fd, unsigned int frame, unsigned int sec, unsigned int usec, void *data) |
427 | { |
428 | *((bool *)data) = false; |
429 | } |
430 | |
431 | bool 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.*/ |
518 | static 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 | |
541 | static 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. |
555 | static 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 | |
591 | static 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 | |
615 | static 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 | |
636 | static 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 | |
673 | static 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 | |
687 | static 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 | |
725 | static 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 | |
746 | static 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[] */ |
798 | static 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 | |
1017 | cleanup: |
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 | |
1037 | static 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(). */ |
1084 | static 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 | |
1163 | cleanup: |
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 | */ |
1184 | static 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. |
1212 | static 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 | |
1233 | static 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 | |
1300 | static 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 | |
1317 | static 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. */ |
1335 | bool 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 | |
1397 | cleanup: |
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 |
1411 | static 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 | |
1426 | static 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 | |
1442 | bool 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. */ |
1474 | void 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. |
1496 | bool 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 | |
1524 | bool 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 | |
1555 | void 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 | /**********************************************************************/ |
1633 | bool 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 | |
1779 | void KMSDRM_SetWindowTitle(SDL_VideoDevice *_this, SDL_Window *window) |
1780 | { |
1781 | } |
1782 | bool KMSDRM_SetWindowPosition(SDL_VideoDevice *_this, SDL_Window *window) |
1783 | { |
1784 | return SDL_Unsupported(); |
1785 | } |
1786 | void 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 | } |
1793 | SDL_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 | } |
1801 | void KMSDRM_ShowWindow(SDL_VideoDevice *_this, SDL_Window *window) |
1802 | { |
1803 | } |
1804 | void KMSDRM_HideWindow(SDL_VideoDevice *_this, SDL_Window *window) |
1805 | { |
1806 | } |
1807 | void KMSDRM_RaiseWindow(SDL_VideoDevice *_this, SDL_Window *window) |
1808 | { |
1809 | } |
1810 | void KMSDRM_MaximizeWindow(SDL_VideoDevice *_this, SDL_Window *window) |
1811 | { |
1812 | } |
1813 | void KMSDRM_MinimizeWindow(SDL_VideoDevice *_this, SDL_Window *window) |
1814 | { |
1815 | } |
1816 | void KMSDRM_RestoreWindow(SDL_VideoDevice *_this, SDL_Window *window) |
1817 | { |
1818 | } |
1819 | |
1820 | #endif // SDL_VIDEO_DRIVER_KMSDRM |
1821 | |