summaryrefslogtreecommitdiff
path: root/contrib/SDL-3.2.8/src/video/kmsdrm
diff options
context:
space:
mode:
author3gg <3gg@shellblade.net>2025-12-27 12:03:39 -0800
committer3gg <3gg@shellblade.net>2025-12-27 12:03:39 -0800
commit5a079a2d114f96d4847d1ee305d5b7c16eeec50e (patch)
tree8926ab44f168acf787d8e19608857b3af0f82758 /contrib/SDL-3.2.8/src/video/kmsdrm
Initial commit
Diffstat (limited to 'contrib/SDL-3.2.8/src/video/kmsdrm')
-rw-r--r--contrib/SDL-3.2.8/src/video/kmsdrm/SDL_kmsdrmdyn.c161
-rw-r--r--contrib/SDL-3.2.8/src/video/kmsdrm/SDL_kmsdrmdyn.h54
-rw-r--r--contrib/SDL-3.2.8/src/video/kmsdrm/SDL_kmsdrmevents.c44
-rw-r--r--contrib/SDL-3.2.8/src/video/kmsdrm/SDL_kmsdrmevents.h29
-rw-r--r--contrib/SDL-3.2.8/src/video/kmsdrm/SDL_kmsdrmmouse.c410
-rw-r--r--contrib/SDL-3.2.8/src/video/kmsdrm/SDL_kmsdrmmouse.h53
-rw-r--r--contrib/SDL-3.2.8/src/video/kmsdrm/SDL_kmsdrmopengles.c203
-rw-r--r--contrib/SDL-3.2.8/src/video/kmsdrm/SDL_kmsdrmopengles.h42
-rw-r--r--contrib/SDL-3.2.8/src/video/kmsdrm/SDL_kmsdrmsym.h145
-rw-r--r--contrib/SDL-3.2.8/src/video/kmsdrm/SDL_kmsdrmvideo.c1820
-rw-r--r--contrib/SDL-3.2.8/src/video/kmsdrm/SDL_kmsdrmvideo.h176
-rw-r--r--contrib/SDL-3.2.8/src/video/kmsdrm/SDL_kmsdrmvulkan.c520
-rw-r--r--contrib/SDL-3.2.8/src/video/kmsdrm/SDL_kmsdrmvulkan.h51
13 files changed, 3708 insertions, 0 deletions
diff --git a/contrib/SDL-3.2.8/src/video/kmsdrm/SDL_kmsdrmdyn.c b/contrib/SDL-3.2.8/src/video/kmsdrm/SDL_kmsdrmdyn.c
new file mode 100644
index 0000000..a532cea
--- /dev/null
+++ b/contrib/SDL-3.2.8/src/video/kmsdrm/SDL_kmsdrmdyn.c
@@ -0,0 +1,161 @@
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#define DEBUG_DYNAMIC_KMSDRM 0
27
28#include "SDL_kmsdrmdyn.h"
29
30#ifdef SDL_VIDEO_DRIVER_KMSDRM_DYNAMIC
31
32typedef struct
33{
34 void *lib;
35 const char *libname;
36} kmsdrmdynlib;
37
38#ifndef SDL_VIDEO_DRIVER_KMSDRM_DYNAMIC_GBM
39#define SDL_VIDEO_DRIVER_KMSDRM_DYNAMIC_GBM NULL
40#endif
41
42static kmsdrmdynlib kmsdrmlibs[] = {
43 { NULL, SDL_VIDEO_DRIVER_KMSDRM_DYNAMIC_GBM },
44 { NULL, SDL_VIDEO_DRIVER_KMSDRM_DYNAMIC }
45};
46
47static void *KMSDRM_GetSym(const char *fnname, int *pHasModule, bool required)
48{
49 int i;
50 void *fn = NULL;
51 for (i = 0; i < SDL_arraysize(kmsdrmlibs); i++) {
52 if (kmsdrmlibs[i].lib) {
53 fn = SDL_LoadFunction(kmsdrmlibs[i].lib, fnname);
54 if (fn) {
55 break;
56 }
57 }
58 }
59
60#if DEBUG_DYNAMIC_KMSDRM
61 if (fn)
62 SDL_Log("KMSDRM: Found '%s' in %s (%p)", fnname, kmsdrmlibs[i].libname, fn);
63 else
64 SDL_Log("KMSDRM: Symbol '%s' NOT FOUND!", fnname);
65#endif
66
67 if (!fn && required) {
68 *pHasModule = 0; // kill this module.
69 }
70
71 return fn;
72}
73
74#endif // SDL_VIDEO_DRIVER_KMSDRM_DYNAMIC
75
76// Define all the function pointers and wrappers...
77#define SDL_KMSDRM_MODULE(modname) int SDL_KMSDRM_HAVE_##modname = 0;
78#define SDL_KMSDRM_SYM(rc, fn, params) SDL_DYNKMSDRMFN_##fn KMSDRM_##fn = NULL;
79#define SDL_KMSDRM_SYM_CONST(type, name) SDL_DYNKMSDRMCONST_##name KMSDRM_##name = NULL;
80#define SDL_KMSDRM_SYM_OPT(rc, fn, params) SDL_DYNKMSDRMFN_##fn KMSDRM_##fn = NULL;
81#include "SDL_kmsdrmsym.h"
82
83static int kmsdrm_load_refcount = 0;
84
85void SDL_KMSDRM_UnloadSymbols(void)
86{
87 // Don't actually unload if more than one module is using the libs...
88 if (kmsdrm_load_refcount > 0) {
89 if (--kmsdrm_load_refcount == 0) {
90#ifdef SDL_VIDEO_DRIVER_KMSDRM_DYNAMIC
91 int i;
92#endif
93
94 // set all the function pointers to NULL.
95#define SDL_KMSDRM_MODULE(modname) SDL_KMSDRM_HAVE_##modname = 0;
96#define SDL_KMSDRM_SYM(rc, fn, params) KMSDRM_##fn = NULL;
97#define SDL_KMSDRM_SYM_CONST(type, name) KMSDRM_##name = NULL;
98#define SDL_KMSDRM_SYM_OPT(rc, fn, params) KMSDRM_##fn = NULL;
99#include "SDL_kmsdrmsym.h"
100
101#ifdef SDL_VIDEO_DRIVER_KMSDRM_DYNAMIC
102 for (i = 0; i < SDL_arraysize(kmsdrmlibs); i++) {
103 if (kmsdrmlibs[i].lib) {
104 SDL_UnloadObject(kmsdrmlibs[i].lib);
105 kmsdrmlibs[i].lib = NULL;
106 }
107 }
108#endif
109 }
110 }
111}
112
113// returns non-zero if all needed symbols were loaded.
114bool SDL_KMSDRM_LoadSymbols(void)
115{
116 bool result = true; // always succeed if not using Dynamic KMSDRM stuff.
117
118 // deal with multiple modules needing these symbols...
119 if (kmsdrm_load_refcount++ == 0) {
120#ifdef SDL_VIDEO_DRIVER_KMSDRM_DYNAMIC
121 int i;
122 int *thismod = NULL;
123 for (i = 0; i < SDL_arraysize(kmsdrmlibs); i++) {
124 if (kmsdrmlibs[i].libname) {
125 kmsdrmlibs[i].lib = SDL_LoadObject(kmsdrmlibs[i].libname);
126 }
127 }
128
129#define SDL_KMSDRM_MODULE(modname) SDL_KMSDRM_HAVE_##modname = 1; // default yes
130#include "SDL_kmsdrmsym.h"
131
132#define SDL_KMSDRM_MODULE(modname) thismod = &SDL_KMSDRM_HAVE_##modname;
133#define SDL_KMSDRM_SYM(rc, fn, params) KMSDRM_##fn = (SDL_DYNKMSDRMFN_##fn)KMSDRM_GetSym(#fn, thismod, true);
134#define SDL_KMSDRM_SYM_CONST(type, name) KMSDRM_##name = *(SDL_DYNKMSDRMCONST_##name *)KMSDRM_GetSym(#name, thismod, true);
135#define SDL_KMSDRM_SYM_OPT(rc, fn, params) KMSDRM_##fn = (SDL_DYNKMSDRMFN_##fn)KMSDRM_GetSym(#fn, thismod, false);
136#include "SDL_kmsdrmsym.h"
137
138 if ((SDL_KMSDRM_HAVE_LIBDRM) && (SDL_KMSDRM_HAVE_GBM)) {
139 // all required symbols loaded.
140 SDL_ClearError();
141 } else {
142 // in case something got loaded...
143 SDL_KMSDRM_UnloadSymbols();
144 result = false;
145 }
146
147#else // no dynamic KMSDRM
148
149#define SDL_KMSDRM_MODULE(modname) SDL_KMSDRM_HAVE_##modname = 1; // default yes
150#define SDL_KMSDRM_SYM(rc, fn, params) KMSDRM_##fn = fn;
151#define SDL_KMSDRM_SYM_CONST(type, name) KMSDRM_##name = name;
152#define SDL_KMSDRM_SYM_OPT(rc, fn, params) KMSDRM_##fn = fn;
153#include "SDL_kmsdrmsym.h"
154
155#endif
156 }
157
158 return result;
159}
160
161#endif // SDL_VIDEO_DRIVER_KMSDRM
diff --git a/contrib/SDL-3.2.8/src/video/kmsdrm/SDL_kmsdrmdyn.h b/contrib/SDL-3.2.8/src/video/kmsdrm/SDL_kmsdrmdyn.h
new file mode 100644
index 0000000..55c5199
--- /dev/null
+++ b/contrib/SDL-3.2.8/src/video/kmsdrm/SDL_kmsdrmdyn.h
@@ -0,0 +1,54 @@
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#ifndef SDL_kmsdrmdyn_h_
23#define SDL_kmsdrmdyn_h_
24
25#include "SDL_internal.h"
26
27#include <xf86drm.h>
28#include <xf86drmMode.h>
29#include <gbm.h>
30
31#ifdef __cplusplus
32extern "C" {
33#endif
34
35extern bool SDL_KMSDRM_LoadSymbols(void);
36extern void SDL_KMSDRM_UnloadSymbols(void);
37
38// Declare all the function pointers and wrappers...
39#define SDL_KMSDRM_SYM(rc, fn, params) \
40 typedef rc(*SDL_DYNKMSDRMFN_##fn) params; \
41 extern SDL_DYNKMSDRMFN_##fn KMSDRM_##fn;
42#define SDL_KMSDRM_SYM_CONST(type, name) \
43 typedef type SDL_DYNKMSDRMCONST_##name; \
44 extern SDL_DYNKMSDRMCONST_##name KMSDRM_##name;
45#define SDL_KMSDRM_SYM_OPT(rc, fn, params) \
46 typedef rc(*SDL_DYNKMSDRMFN_##fn) params; \
47 extern SDL_DYNKMSDRMFN_##fn KMSDRM_##fn;
48#include "SDL_kmsdrmsym.h"
49
50#ifdef __cplusplus
51}
52#endif
53
54#endif // SDL_kmsdrmdyn_h_
diff --git a/contrib/SDL-3.2.8/src/video/kmsdrm/SDL_kmsdrmevents.c b/contrib/SDL-3.2.8/src/video/kmsdrm/SDL_kmsdrmevents.c
new file mode 100644
index 0000000..78fb9f4
--- /dev/null
+++ b/contrib/SDL-3.2.8/src/video/kmsdrm/SDL_kmsdrmevents.c
@@ -0,0 +1,44 @@
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 "SDL_kmsdrmvideo.h"
27#include "SDL_kmsdrmevents.h"
28
29#ifdef SDL_INPUT_LINUXEV
30#include "../../core/linux/SDL_evdev.h"
31#elif defined SDL_INPUT_WSCONS
32#include "../../core/openbsd/SDL_wscons.h"
33#endif
34
35void KMSDRM_PumpEvents(SDL_VideoDevice *_this)
36{
37#ifdef SDL_INPUT_LINUXEV
38 SDL_EVDEV_Poll();
39#elif defined SDL_INPUT_WSCONS
40 SDL_WSCONS_PumpEvents();
41#endif
42}
43
44#endif // SDL_VIDEO_DRIVER_KMSDRM
diff --git a/contrib/SDL-3.2.8/src/video/kmsdrm/SDL_kmsdrmevents.h b/contrib/SDL-3.2.8/src/video/kmsdrm/SDL_kmsdrmevents.h
new file mode 100644
index 0000000..35473f8
--- /dev/null
+++ b/contrib/SDL-3.2.8/src/video/kmsdrm/SDL_kmsdrmevents.h
@@ -0,0 +1,29 @@
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#ifndef SDL_kmsdrmevents_h_
25#define SDL_kmsdrmevents_h_
26
27extern void KMSDRM_PumpEvents(SDL_VideoDevice *_this);
28
29#endif // SDL_kmsdrmevents_h_
diff --git a/contrib/SDL-3.2.8/src/video/kmsdrm/SDL_kmsdrmmouse.c b/contrib/SDL-3.2.8/src/video/kmsdrm/SDL_kmsdrmmouse.c
new file mode 100644
index 0000000..bb50ae3
--- /dev/null
+++ b/contrib/SDL-3.2.8/src/video/kmsdrm/SDL_kmsdrmmouse.c
@@ -0,0 +1,410 @@
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 "SDL_kmsdrmvideo.h"
27#include "SDL_kmsdrmmouse.h"
28#include "SDL_kmsdrmdyn.h"
29
30#include "../../events/SDL_mouse_c.h"
31#include "../../events/default_cursor.h"
32
33#include "../SDL_pixels_c.h"
34
35static SDL_Cursor *KMSDRM_CreateDefaultCursor(void);
36static SDL_Cursor *KMSDRM_CreateCursor(SDL_Surface *surface, int hot_x, int hot_y);
37static bool KMSDRM_ShowCursor(SDL_Cursor *cursor);
38static bool KMSDRM_MoveCursor(SDL_Cursor *cursor);
39static void KMSDRM_FreeCursor(SDL_Cursor *cursor);
40
41/**************************************************************************************/
42// BEFORE CODING ANYTHING MOUSE/CURSOR RELATED, REMEMBER THIS.
43// How does SDL manage cursors internally? First, mouse =! cursor. The mouse can have
44// many cursors in mouse->cursors.
45// -SDL tells us to create a cursor with KMSDRM_CreateCursor(). It can create many
46// cursosr with this, not only one.
47// -SDL stores those cursors in a cursors array, in mouse->cursors.
48// -Whenever it wants (or the programmer wants) takes a cursor from that array
49// and shows it on screen with KMSDRM_ShowCursor().
50// KMSDRM_ShowCursor() simply shows or hides the cursor it receives: it does NOT
51// mind if it's mouse->cur_cursor, etc.
52// -If KMSDRM_ShowCursor() returns successfully, that cursor becomes
53// mouse->cur_cursor and mouse->cursor_shown is 1.
54/**************************************************************************************/
55
56static SDL_Cursor *KMSDRM_CreateDefaultCursor(void)
57{
58 return SDL_CreateCursor(default_cdata, default_cmask, DEFAULT_CWIDTH, DEFAULT_CHEIGHT, DEFAULT_CHOTX, DEFAULT_CHOTY);
59}
60
61/* Given a display's internal, destroy the cursor BO for it.
62 To be called from KMSDRM_DestroyWindow(), as that's where we
63 destroy the internal for the window's display. */
64void KMSDRM_DestroyCursorBO(SDL_VideoDevice *_this, SDL_VideoDisplay *display)
65{
66 SDL_DisplayData *dispdata = display->internal;
67
68 // Destroy the curso GBM BO.
69 if (dispdata->cursor_bo) {
70 KMSDRM_gbm_bo_destroy(dispdata->cursor_bo);
71 dispdata->cursor_bo = NULL;
72 dispdata->cursor_bo_drm_fd = -1;
73 }
74}
75
76/* Given a display's internal, create the cursor BO for it.
77 To be called from KMSDRM_CreateWindow(), as that's where we
78 build a window and assign a display to it. */
79bool KMSDRM_CreateCursorBO(SDL_VideoDisplay *display)
80{
81
82 SDL_VideoDevice *dev = SDL_GetVideoDevice();
83 SDL_VideoData *viddata = dev->internal;
84 SDL_DisplayData *dispdata = display->internal;
85
86 if (!KMSDRM_gbm_device_is_format_supported(viddata->gbm_dev,
87 GBM_FORMAT_ARGB8888,
88 GBM_BO_USE_CURSOR | GBM_BO_USE_WRITE)) {
89 return SDL_SetError("Unsupported pixel format for cursor");
90 }
91
92 if (KMSDRM_drmGetCap(viddata->drm_fd,
93 DRM_CAP_CURSOR_WIDTH, &dispdata->cursor_w) ||
94 KMSDRM_drmGetCap(viddata->drm_fd, DRM_CAP_CURSOR_HEIGHT,
95 &dispdata->cursor_h)) {
96 return SDL_SetError("Could not get the recommended GBM cursor size");
97 }
98
99 if (dispdata->cursor_w == 0 || dispdata->cursor_h == 0) {
100 return SDL_SetError("Could not get an usable GBM cursor size");
101 }
102
103 dispdata->cursor_bo = KMSDRM_gbm_bo_create(viddata->gbm_dev,
104 dispdata->cursor_w, dispdata->cursor_h,
105 GBM_FORMAT_ARGB8888, GBM_BO_USE_CURSOR | GBM_BO_USE_WRITE | GBM_BO_USE_LINEAR);
106
107 if (!dispdata->cursor_bo) {
108 return SDL_SetError("Could not create GBM cursor BO");
109 }
110
111 dispdata->cursor_bo_drm_fd = viddata->drm_fd;
112 return true;
113}
114
115// Remove a cursor buffer from a display's DRM cursor BO.
116static bool KMSDRM_RemoveCursorFromBO(SDL_VideoDisplay *display)
117{
118 bool result = true;
119
120 SDL_DisplayData *dispdata = display->internal;
121 SDL_VideoDevice *video_device = SDL_GetVideoDevice();
122 SDL_VideoData *viddata = video_device->internal;
123
124 const int rc = KMSDRM_drmModeSetCursor(viddata->drm_fd, dispdata->crtc->crtc_id, 0, 0, 0);
125 if (rc < 0) {
126 result = SDL_SetError("drmModeSetCursor() failed: %s", strerror(-rc));
127 }
128 return result;
129}
130
131// Dump a cursor buffer to a display's DRM cursor BO.
132static bool KMSDRM_DumpCursorToBO(SDL_VideoDisplay *display, SDL_Cursor *cursor)
133{
134 SDL_DisplayData *dispdata = display->internal;
135 SDL_CursorData *curdata = cursor->internal;
136 SDL_VideoDevice *video_device = SDL_GetVideoDevice();
137 SDL_VideoData *viddata = video_device->internal;
138
139 uint32_t bo_handle;
140 size_t bo_stride;
141 size_t bufsize;
142 uint8_t *ready_buffer = NULL;
143 uint8_t *src_row;
144
145 int i, rc;
146 bool result = true;
147
148 if (!curdata || !dispdata->cursor_bo) {
149 return SDL_SetError("Cursor or display not initialized properly.");
150 }
151
152 /* Prepare a buffer we can dump to our GBM BO (different
153 size, alpha premultiplication...) */
154 bo_stride = KMSDRM_gbm_bo_get_stride(dispdata->cursor_bo);
155 bufsize = bo_stride * dispdata->cursor_h;
156
157 ready_buffer = (uint8_t *)SDL_calloc(1, bufsize);
158
159 if (!ready_buffer) {
160 result = false;
161 goto cleanup;
162 }
163
164 // Copy from the cursor buffer to a buffer that we can dump to the GBM BO.
165 for (i = 0; i < curdata->h; i++) {
166 src_row = &((uint8_t *)curdata->buffer)[i * curdata->w * 4];
167 SDL_memcpy(ready_buffer + (i * bo_stride), src_row, (size_t)4 * curdata->w);
168 }
169
170 // Dump the cursor buffer to our GBM BO.
171 if (KMSDRM_gbm_bo_write(dispdata->cursor_bo, ready_buffer, bufsize)) {
172 result = SDL_SetError("Could not write to GBM cursor BO");
173 goto cleanup;
174 }
175
176 // Put the GBM BO buffer on screen using the DRM interface.
177 bo_handle = KMSDRM_gbm_bo_get_handle(dispdata->cursor_bo).u32;
178 if (curdata->hot_x == 0 && curdata->hot_y == 0) {
179 rc = KMSDRM_drmModeSetCursor(viddata->drm_fd, dispdata->crtc->crtc_id,
180 bo_handle, dispdata->cursor_w, dispdata->cursor_h);
181 } else {
182 rc = KMSDRM_drmModeSetCursor2(viddata->drm_fd, dispdata->crtc->crtc_id,
183 bo_handle, dispdata->cursor_w, dispdata->cursor_h, curdata->hot_x, curdata->hot_y);
184 }
185 if (rc < 0) {
186 result = SDL_SetError("Failed to set DRM cursor: %s", strerror(-rc));
187 goto cleanup;
188 }
189
190cleanup:
191
192 if (ready_buffer) {
193 SDL_free(ready_buffer);
194 }
195 return result;
196}
197
198// This is only for freeing the SDL_cursor.
199static void KMSDRM_FreeCursor(SDL_Cursor *cursor)
200{
201 SDL_CursorData *curdata;
202
203 // Even if the cursor is not ours, free it.
204 if (cursor) {
205 curdata = cursor->internal;
206 // Free cursor buffer
207 if (curdata->buffer) {
208 SDL_free(curdata->buffer);
209 curdata->buffer = NULL;
210 }
211 // Free cursor itself
212 if (cursor->internal) {
213 SDL_free(cursor->internal);
214 }
215 SDL_free(cursor);
216 }
217}
218
219/* This simply gets the cursor soft-buffer ready.
220 We don't copy it to a GBO BO until ShowCursor() because the cusor GBM BO (living
221 in dispata) is destroyed and recreated when we recreate windows, etc. */
222static SDL_Cursor *KMSDRM_CreateCursor(SDL_Surface *surface, int hot_x, int hot_y)
223{
224 SDL_CursorData *curdata;
225 SDL_Cursor *cursor, *result;
226
227 curdata = NULL;
228 result = NULL;
229
230 cursor = (SDL_Cursor *)SDL_calloc(1, sizeof(*cursor));
231 if (!cursor) {
232 goto cleanup;
233 }
234 curdata = (SDL_CursorData *)SDL_calloc(1, sizeof(*curdata));
235 if (!curdata) {
236 goto cleanup;
237 }
238
239 // hox_x and hot_y are the coordinates of the "tip of the cursor" from it's base.
240 curdata->hot_x = hot_x;
241 curdata->hot_y = hot_y;
242 curdata->w = surface->w;
243 curdata->h = surface->h;
244 curdata->buffer = NULL;
245
246 /* Configure the cursor buffer info.
247 This buffer has the original size of the cursor surface we are given. */
248 curdata->buffer_pitch = surface->w;
249 curdata->buffer_size = (size_t)surface->w * surface->h * 4;
250 curdata->buffer = (uint32_t *)SDL_malloc(curdata->buffer_size);
251
252 if (!curdata->buffer) {
253 goto cleanup;
254 }
255
256 /* All code below assumes ARGB8888 format for the cursor surface,
257 like other backends do. Also, the GBM BO pixels have to be
258 alpha-premultiplied, but the SDL surface we receive has
259 straight-alpha pixels, so we always have to convert. */
260 SDL_PremultiplyAlpha(surface->w, surface->h,
261 surface->format, surface->pixels, surface->pitch,
262 SDL_PIXELFORMAT_ARGB8888, curdata->buffer, surface->w * 4, true);
263
264 cursor->internal = curdata;
265
266 result = cursor;
267
268cleanup:
269 if (!result) {
270 if (curdata) {
271 if (curdata->buffer) {
272 SDL_free(curdata->buffer);
273 }
274 SDL_free(curdata);
275 }
276 if (cursor) {
277 SDL_free(cursor);
278 }
279 }
280
281 return result;
282}
283
284// Show the specified cursor, or hide if cursor is NULL or has no focus.
285static bool KMSDRM_ShowCursor(SDL_Cursor *cursor)
286{
287 SDL_VideoDisplay *display;
288 SDL_Window *window;
289 SDL_Mouse *mouse = SDL_GetMouse();
290
291 int i;
292 bool result = true;
293
294 // Get the mouse focused window, if any.
295 window = mouse->focus;
296
297 if (!window || !cursor) {
298 /* If no window is focused by mouse or cursor is NULL,
299 since we have no window (no mouse->focus) and hence
300 we have no display, we simply hide mouse on all displays.
301 This happens on video quit, where we get here after
302 the mouse focus has been unset, yet SDL wants to
303 restore the system default cursor (makes no sense here). */
304 SDL_DisplayID *displays = SDL_GetDisplays(NULL);
305 if (displays) {
306 // Iterate on the displays, hiding the cursor.
307 for (i = 0; i < displays[i]; i++) {
308 display = SDL_GetVideoDisplay(displays[i]);
309 result = KMSDRM_RemoveCursorFromBO(display);
310 }
311 SDL_free(displays);
312 }
313 } else {
314 display = SDL_GetVideoDisplayForWindow(window);
315 if (display) {
316 if (cursor) {
317 /* Dump the cursor to the display DRM cursor BO so it becomes visible
318 on that display. */
319 result = KMSDRM_DumpCursorToBO(display, cursor);
320 } else {
321 // Hide the cursor on that display.
322 result = KMSDRM_RemoveCursorFromBO(display);
323 }
324 }
325 }
326
327 return result;
328}
329
330static bool KMSDRM_WarpMouseGlobal(float x, float y)
331{
332 SDL_Mouse *mouse = SDL_GetMouse();
333
334 if (mouse && mouse->cur_cursor && mouse->focus) {
335 SDL_Window *window = mouse->focus;
336 SDL_DisplayData *dispdata = SDL_GetDisplayDriverDataForWindow(window);
337
338 // Update internal mouse position.
339 SDL_SendMouseMotion(0, mouse->focus, SDL_GLOBAL_MOUSE_ID, false, x, y);
340
341 // And now update the cursor graphic position on screen.
342 if (dispdata->cursor_bo) {
343 const int rc = KMSDRM_drmModeMoveCursor(dispdata->cursor_bo_drm_fd, dispdata->crtc->crtc_id, (int)x, (int)y);
344 if (rc < 0) {
345 return SDL_SetError("drmModeMoveCursor() failed: %s", strerror(-rc));
346 }
347 return true;
348 } else {
349 return SDL_SetError("Cursor not initialized properly.");
350 }
351 } else {
352 return SDL_SetError("No mouse or current cursor.");
353 }
354}
355
356static bool KMSDRM_WarpMouse(SDL_Window *window, float x, float y)
357{
358 // Only one global/fullscreen window is supported
359 return KMSDRM_WarpMouseGlobal(x, y);
360}
361
362void KMSDRM_InitMouse(SDL_VideoDevice *_this, SDL_VideoDisplay *display)
363{
364 SDL_Mouse *mouse = SDL_GetMouse();
365 SDL_DisplayData *dispdata = display->internal;
366
367 mouse->CreateCursor = KMSDRM_CreateCursor;
368 mouse->ShowCursor = KMSDRM_ShowCursor;
369 mouse->MoveCursor = KMSDRM_MoveCursor;
370 mouse->FreeCursor = KMSDRM_FreeCursor;
371 mouse->WarpMouse = KMSDRM_WarpMouse;
372 mouse->WarpMouseGlobal = KMSDRM_WarpMouseGlobal;
373
374 /* Only create the default cursor for this display if we haven't done so before,
375 we don't want several cursors to be created for the same display. */
376 if (!dispdata->default_cursor_init) {
377 SDL_SetDefaultCursor(KMSDRM_CreateDefaultCursor());
378 dispdata->default_cursor_init = true;
379 }
380}
381
382void KMSDRM_QuitMouse(SDL_VideoDevice *_this)
383{
384 // TODO: ?
385}
386
387// This is called when a mouse motion event occurs
388static bool KMSDRM_MoveCursor(SDL_Cursor *cursor)
389{
390 SDL_Mouse *mouse = SDL_GetMouse();
391
392 /* We must NOT call SDL_SendMouseMotion() here or we will enter recursivity!
393 That's why we move the cursor graphic ONLY. */
394 if (mouse && mouse->cur_cursor && mouse->focus) {
395 SDL_Window *window = mouse->focus;
396 SDL_DisplayData *dispdata = SDL_GetDisplayDriverDataForWindow(window);
397
398 if (!dispdata->cursor_bo) {
399 return SDL_SetError("Cursor not initialized properly.");
400 }
401
402 const int rc = KMSDRM_drmModeMoveCursor(dispdata->cursor_bo_drm_fd, dispdata->crtc->crtc_id, (int)mouse->x, (int)mouse->y);
403 if (rc < 0) {
404 return SDL_SetError("drmModeMoveCursor() failed: %s", strerror(-rc));
405 }
406 }
407 return true;
408}
409
410#endif // SDL_VIDEO_DRIVER_KMSDRM
diff --git a/contrib/SDL-3.2.8/src/video/kmsdrm/SDL_kmsdrmmouse.h b/contrib/SDL-3.2.8/src/video/kmsdrm/SDL_kmsdrmmouse.h
new file mode 100644
index 0000000..53e33f4
--- /dev/null
+++ b/contrib/SDL-3.2.8/src/video/kmsdrm/SDL_kmsdrmmouse.h
@@ -0,0 +1,53 @@
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#ifndef SDL_KMSDRM_mouse_h_
25#define SDL_KMSDRM_mouse_h_
26
27#include <gbm.h>
28
29#define MAX_CURSOR_W 512
30#define MAX_CURSOR_H 512
31
32struct SDL_CursorData
33{
34 int hot_x, hot_y;
35 int w, h;
36
37 /* The buffer where we store the mouse bitmap ready to be used.
38 We get it ready and filled in CreateCursor(), and copy it
39 to a GBM BO in ShowCursor().*/
40 uint32_t *buffer;
41 size_t buffer_size;
42 size_t buffer_pitch;
43
44};
45
46extern void KMSDRM_InitMouse(SDL_VideoDevice *_this, SDL_VideoDisplay *display);
47extern void KMSDRM_QuitMouse(SDL_VideoDevice *_this);
48
49extern bool KMSDRM_CreateCursorBO(SDL_VideoDisplay *display);
50extern void KMSDRM_DestroyCursorBO(SDL_VideoDevice *_this, SDL_VideoDisplay *display);
51extern void KMSDRM_InitCursor(void);
52
53#endif // SDL_KMSDRM_mouse_h_
diff --git a/contrib/SDL-3.2.8/src/video/kmsdrm/SDL_kmsdrmopengles.c b/contrib/SDL-3.2.8/src/video/kmsdrm/SDL_kmsdrmopengles.c
new file mode 100644
index 0000000..cc93cde
--- /dev/null
+++ b/contrib/SDL-3.2.8/src/video/kmsdrm/SDL_kmsdrmopengles.c
@@ -0,0 +1,203 @@
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 "SDL_kmsdrmvideo.h"
27#include "SDL_kmsdrmopengles.h"
28#include "SDL_kmsdrmdyn.h"
29#include <errno.h>
30
31#ifndef EGL_PLATFORM_GBM_MESA
32#define EGL_PLATFORM_GBM_MESA 0x31D7
33#endif
34
35// EGL implementation of SDL OpenGL support
36
37void KMSDRM_GLES_DefaultProfileConfig(SDL_VideoDevice *_this, int *mask, int *major, int *minor)
38{
39 /* if SDL was _also_ built with the Raspberry Pi driver (so we're
40 definitely a Pi device) or with the ROCKCHIP video driver
41 (it's a ROCKCHIP device), default to GLES2. */
42#if defined(SDL_VIDEO_DRIVER_RPI) || defined(SDL_VIDEO_DRIVER_ROCKCHIP)
43 *mask = SDL_GL_CONTEXT_PROFILE_ES;
44 *major = 2;
45 *minor = 0;
46#endif
47}
48
49bool KMSDRM_GLES_LoadLibrary(SDL_VideoDevice *_this, const char *path)
50{
51 /* Just pretend you do this here, but don't do it until KMSDRM_CreateWindow(),
52 where we do the same library load we would normally do here.
53 because this gets called by SDL_CreateWindow() before KMSDR_CreateWindow(),
54 so gbm dev isn't yet created when this is called, AND we can't alter the
55 call order in SDL_CreateWindow(). */
56#if 0
57 NativeDisplayType display = (NativeDisplayType)_this->internal->gbm_dev;
58 return SDL_EGL_LoadLibrary(_this, path, display, EGL_PLATFORM_GBM_MESA);
59#endif
60 return true;
61}
62
63void KMSDRM_GLES_UnloadLibrary(SDL_VideoDevice *_this)
64{
65 /* As with KMSDRM_GLES_LoadLibrary(), we define our own "dummy" unloading function
66 so we manually unload the library whenever we want. */
67}
68
69SDL_EGL_CreateContext_impl(KMSDRM)
70
71bool KMSDRM_GLES_SetSwapInterval(SDL_VideoDevice *_this, int interval)
72{
73 if (!_this->egl_data) {
74 return SDL_SetError("EGL not initialized");
75 }
76
77 if (interval == 0 || interval == 1) {
78 _this->egl_data->egl_swapinterval = interval;
79 } else {
80 return SDL_SetError("Only swap intervals of 0 or 1 are supported");
81 }
82
83 return true;
84}
85
86bool KMSDRM_GLES_SwapWindow(SDL_VideoDevice *_this, SDL_Window *window)
87{
88 SDL_WindowData *windata = window->internal;
89 SDL_DisplayData *dispdata = SDL_GetDisplayDriverDataForWindow(window);
90 SDL_VideoData *viddata = _this->internal;
91 KMSDRM_FBInfo *fb_info;
92 int ret = 0;
93
94 /* Always wait for the previous issued flip before issuing a new one,
95 even if you do async flips. */
96 uint32_t flip_flags = DRM_MODE_PAGE_FLIP_EVENT;
97
98 // Skip the swap if we've switched away to another VT
99 if (windata->egl_surface == EGL_NO_SURFACE) {
100 // Wait a bit, throttling to ~100 FPS
101 SDL_Delay(10);
102 return true;
103 }
104
105 // Recreate the GBM / EGL surfaces if the display mode has changed
106 if (windata->egl_surface_dirty) {
107 KMSDRM_CreateSurfaces(_this, window);
108 }
109
110 /* Wait for confirmation that the next front buffer has been flipped, at which
111 point the previous front buffer can be released */
112 if (!KMSDRM_WaitPageflip(_this, windata)) {
113 return SDL_SetError("Wait for previous pageflip failed");
114 }
115
116 // Release the previous front buffer
117 if (windata->bo) {
118 KMSDRM_gbm_surface_release_buffer(windata->gs, windata->bo);
119 windata->bo = NULL;
120 }
121
122 windata->bo = windata->next_bo;
123
124 /* Mark a buffer to become the next front buffer.
125 This won't happen until pagelip completes. */
126 if (!(_this->egl_data->eglSwapBuffers(_this->egl_data->egl_display,
127 windata->egl_surface))) {
128 return SDL_SetError("eglSwapBuffers failed");
129 }
130
131 /* From the GBM surface, get the next BO to become the next front buffer,
132 and lock it so it can't be allocated as a back buffer (to prevent EGL
133 from drawing into it!) */
134 windata->next_bo = KMSDRM_gbm_surface_lock_front_buffer(windata->gs);
135 if (!windata->next_bo) {
136 return SDL_SetError("Could not lock front buffer on GBM surface");
137 }
138
139 // Get an actual usable fb for the next front buffer.
140 fb_info = KMSDRM_FBFromBO(_this, windata->next_bo);
141 if (!fb_info) {
142 return SDL_SetError("Could not get a framebuffer");
143 }
144
145 if (!windata->bo) {
146 /* On the first swap, immediately present the new front buffer. Before
147 drmModePageFlip can be used the CRTC has to be configured to use
148 the current connector and mode with drmModeSetCrtc */
149 ret = KMSDRM_drmModeSetCrtc(viddata->drm_fd,
150 dispdata->crtc->crtc_id, fb_info->fb_id, 0, 0,
151 &dispdata->connector->connector_id, 1, &dispdata->mode);
152
153 if (ret) {
154 return SDL_SetError("Could not set videomode on CRTC.");
155 }
156 } else {
157 /* On subsequent swaps, queue the new front buffer to be flipped during
158 the next vertical blank
159
160 Remember: drmModePageFlip() never blocks, it just issues the flip,
161 which will be done during the next vblank, or immediately if
162 we pass the DRM_MODE_PAGE_FLIP_ASYNC flag.
163 Since calling drmModePageFlip() will return EBUSY if we call it
164 without having completed the last issued flip, we must pass the
165 DRM_MODE_PAGE_FLIP_ASYNC if we don't block on EGL (egl_swapinterval = 0).
166 That makes it flip immediately, without waiting for the next vblank
167 to do so, so even if we don't block on EGL, the flip will have completed
168 when we get here again. */
169 if (_this->egl_data->egl_swapinterval == 0 && viddata->async_pageflip_support) {
170 flip_flags |= DRM_MODE_PAGE_FLIP_ASYNC;
171 }
172
173 ret = KMSDRM_drmModePageFlip(viddata->drm_fd, dispdata->crtc->crtc_id,
174 fb_info->fb_id, flip_flags, &windata->waiting_for_flip);
175
176 if (ret == 0) {
177 windata->waiting_for_flip = true;
178 } else {
179 SDL_LogError(SDL_LOG_CATEGORY_VIDEO, "Could not queue pageflip: %d", ret);
180 }
181
182 /* Wait immediately for vsync (as if we only had two buffers).
183 Even if we are already doing a WaitPageflip at the beginning of this
184 function, this is NOT redundant because here we wait immediately
185 after submitting the image to the screen, reducing lag, and if
186 we have waited here, there won't be a pending pageflip so the
187 WaitPageflip at the beginning of this function will be a no-op.
188 Just leave it here and don't worry.
189 Run your SDL program with "SDL_VIDEO_DOUBLE_BUFFER=1 <program_name>"
190 to enable this. */
191 if (windata->double_buffer) {
192 if (!KMSDRM_WaitPageflip(_this, windata)) {
193 return SDL_SetError("Immediate wait for previous pageflip failed");
194 }
195 }
196 }
197
198 return true;
199}
200
201SDL_EGL_MakeCurrent_impl(KMSDRM)
202
203#endif // SDL_VIDEO_DRIVER_KMSDRM
diff --git a/contrib/SDL-3.2.8/src/video/kmsdrm/SDL_kmsdrmopengles.h b/contrib/SDL-3.2.8/src/video/kmsdrm/SDL_kmsdrmopengles.h
new file mode 100644
index 0000000..6a4fa56
--- /dev/null
+++ b/contrib/SDL-3.2.8/src/video/kmsdrm/SDL_kmsdrmopengles.h
@@ -0,0 +1,42 @@
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#include "SDL_internal.h"
22
23#ifndef SDL_kmsdrmopengles_h_
24#define SDL_kmsdrmopengles_h_
25
26#include "../SDL_egl_c.h"
27
28// OpenGLES functions
29#define KMSDRM_GLES_GetAttribute SDL_EGL_GetAttribute
30#define KMSDRM_GLES_GetProcAddress SDL_EGL_GetProcAddressInternal
31#define KMSDRM_GLES_DestroyContext SDL_EGL_DestroyContext
32#define KMSDRM_GLES_GetSwapInterval SDL_EGL_GetSwapInterval
33
34extern void KMSDRM_GLES_DefaultProfileConfig(SDL_VideoDevice *_this, int *mask, int *major, int *minor);
35extern bool KMSDRM_GLES_SetSwapInterval(SDL_VideoDevice *_this, int interval);
36extern bool KMSDRM_GLES_LoadLibrary(SDL_VideoDevice *_this, const char *path);
37extern void KMSDRM_GLES_UnloadLibrary(SDL_VideoDevice *_this);
38extern SDL_GLContext KMSDRM_GLES_CreateContext(SDL_VideoDevice *_this, SDL_Window *window);
39extern bool KMSDRM_GLES_SwapWindow(SDL_VideoDevice *_this, SDL_Window *window);
40extern bool KMSDRM_GLES_MakeCurrent(SDL_VideoDevice *_this, SDL_Window *window, SDL_GLContext context);
41
42#endif // SDL_kmsdrmopengles_h_
diff --git a/contrib/SDL-3.2.8/src/video/kmsdrm/SDL_kmsdrmsym.h b/contrib/SDL-3.2.8/src/video/kmsdrm/SDL_kmsdrmsym.h
new file mode 100644
index 0000000..801942d
--- /dev/null
+++ b/contrib/SDL-3.2.8/src/video/kmsdrm/SDL_kmsdrmsym.h
@@ -0,0 +1,145 @@
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/* *INDENT-OFF* */ // clang-format off
23
24#ifndef SDL_KMSDRM_MODULE
25#define SDL_KMSDRM_MODULE(modname)
26#endif
27
28#ifndef SDL_KMSDRM_SYM
29#define SDL_KMSDRM_SYM(rc,fn,params)
30#endif
31
32#ifndef SDL_KMSDRM_SYM_CONST
33#define SDL_KMSDRM_SYM_CONST(type, name)
34#endif
35
36#ifndef SDL_KMSDRM_SYM_OPT
37#define SDL_KMSDRM_SYM_OPT(rc,fn,params)
38#endif
39
40
41SDL_KMSDRM_MODULE(LIBDRM)
42SDL_KMSDRM_SYM(void,drmModeFreeResources,(drmModeResPtr ptr))
43SDL_KMSDRM_SYM(void,drmModeFreeFB,(drmModeFBPtr ptr))
44SDL_KMSDRM_SYM(void,drmModeFreeCrtc,(drmModeCrtcPtr ptr))
45SDL_KMSDRM_SYM(void,drmModeFreeConnector,(drmModeConnectorPtr ptr))
46SDL_KMSDRM_SYM(void,drmModeFreeEncoder,(drmModeEncoderPtr ptr))
47SDL_KMSDRM_SYM(int,drmGetCap,(int fd, uint64_t capability, uint64_t *value))
48SDL_KMSDRM_SYM(int,drmSetMaster,(int fd))
49SDL_KMSDRM_SYM(int,drmDropMaster,(int fd))
50SDL_KMSDRM_SYM(int,drmAuthMagic,(int fd, drm_magic_t magic))
51SDL_KMSDRM_SYM(drmModeResPtr,drmModeGetResources,(int fd))
52SDL_KMSDRM_SYM(int,drmModeAddFB,(int fd, uint32_t width, uint32_t height, uint8_t depth,
53 uint8_t bpp, uint32_t pitch, uint32_t bo_handle,
54 uint32_t *buf_id))
55
56SDL_KMSDRM_SYM_OPT(int,drmModeAddFB2,(int fd, uint32_t width, uint32_t height,
57 uint32_t pixel_format, const uint32_t bo_handles[4],
58 const uint32_t pitches[4], const uint32_t offsets[4],
59 uint32_t *buf_id, uint32_t flags))
60
61SDL_KMSDRM_SYM_OPT(int,drmModeAddFB2WithModifiers,(int fd, uint32_t width,
62 uint32_t height, uint32_t pixel_format, const uint32_t bo_handles[4],
63 const uint32_t pitches[4], const uint32_t offsets[4],
64 const uint64_t modifier[4], uint32_t *buf_id, uint32_t flags))
65
66SDL_KMSDRM_SYM_OPT(const char *,drmModeGetConnectorTypeName,(uint32_t connector_type))
67
68SDL_KMSDRM_SYM(int,drmModeRmFB,(int fd, uint32_t bufferId))
69SDL_KMSDRM_SYM(drmModeFBPtr,drmModeGetFB,(int fd, uint32_t buf))
70SDL_KMSDRM_SYM(drmModeCrtcPtr,drmModeGetCrtc,(int fd, uint32_t crtcId))
71SDL_KMSDRM_SYM(int,drmModeSetCrtc,(int fd, uint32_t crtcId, uint32_t bufferId,
72 uint32_t x, uint32_t y, uint32_t *connectors, int count,
73 drmModeModeInfoPtr mode))
74SDL_KMSDRM_SYM(int,drmModeSetCursor,(int fd, uint32_t crtcId, uint32_t bo_handle,
75 uint32_t width, uint32_t height))
76SDL_KMSDRM_SYM(int,drmModeSetCursor2,(int fd, uint32_t crtcId, uint32_t bo_handle,
77 uint32_t width, uint32_t height,
78 int32_t hot_x, int32_t hot_y))
79SDL_KMSDRM_SYM(int,drmModeMoveCursor,(int fd, uint32_t crtcId, int x, int y))
80SDL_KMSDRM_SYM(drmModeEncoderPtr,drmModeGetEncoder,(int fd, uint32_t encoder_id))
81SDL_KMSDRM_SYM(drmModeConnectorPtr,drmModeGetConnector,(int fd, uint32_t connector_id))
82SDL_KMSDRM_SYM(int,drmHandleEvent,(int fd,drmEventContextPtr evctx))
83SDL_KMSDRM_SYM(int,drmModePageFlip,(int fd, uint32_t crtc_id, uint32_t fb_id,
84 uint32_t flags, void *user_data))
85
86// Planes stuff.
87SDL_KMSDRM_SYM(int,drmSetClientCap,(int fd, uint64_t capability, uint64_t value))
88SDL_KMSDRM_SYM(drmModePlaneResPtr,drmModeGetPlaneResources,(int fd))
89SDL_KMSDRM_SYM(drmModePlanePtr,drmModeGetPlane,(int fd, uint32_t plane_id))
90SDL_KMSDRM_SYM(drmModeObjectPropertiesPtr,drmModeObjectGetProperties,(int fd,uint32_t object_id,uint32_t object_type))
91SDL_KMSDRM_SYM(int,drmModeObjectSetProperty,(int fd, uint32_t object_id,
92 uint32_t object_type, uint32_t property_id,
93 uint64_t value))
94SDL_KMSDRM_SYM(drmModePropertyPtr,drmModeGetProperty,(int fd, uint32_t propertyId))
95
96SDL_KMSDRM_SYM(void,drmModeFreeProperty,(drmModePropertyPtr ptr))
97SDL_KMSDRM_SYM(void,drmModeFreeObjectProperties,(drmModeObjectPropertiesPtr ptr))
98SDL_KMSDRM_SYM(void,drmModeFreePlane,(drmModePlanePtr ptr))
99SDL_KMSDRM_SYM(void,drmModeFreePlaneResources,(drmModePlaneResPtr ptr))
100SDL_KMSDRM_SYM(int,drmModeSetPlane,(int fd, uint32_t plane_id, uint32_t crtc_id,
101 uint32_t fb_id, uint32_t flags,
102 int32_t crtc_x, int32_t crtc_y,
103 uint32_t crtc_w, uint32_t crtc_h,
104 uint32_t src_x, uint32_t src_y,
105 uint32_t src_w, uint32_t src_h))
106// Planes stuff ends.
107
108SDL_KMSDRM_MODULE(GBM)
109SDL_KMSDRM_SYM(int,gbm_device_is_format_supported,(struct gbm_device *gbm,
110 uint32_t format, uint32_t usage))
111SDL_KMSDRM_SYM(void,gbm_device_destroy,(struct gbm_device *gbm))
112SDL_KMSDRM_SYM(struct gbm_device *,gbm_create_device,(int fd))
113SDL_KMSDRM_SYM(unsigned int,gbm_bo_get_width,(struct gbm_bo *bo))
114SDL_KMSDRM_SYM(unsigned int,gbm_bo_get_height,(struct gbm_bo *bo))
115SDL_KMSDRM_SYM(uint32_t,gbm_bo_get_stride,(struct gbm_bo *bo))
116SDL_KMSDRM_SYM(uint32_t,gbm_bo_get_format,(struct gbm_bo *bo))
117SDL_KMSDRM_SYM(union gbm_bo_handle,gbm_bo_get_handle,(struct gbm_bo *bo))
118SDL_KMSDRM_SYM(int,gbm_bo_write,(struct gbm_bo *bo, const void *buf, size_t count))
119SDL_KMSDRM_SYM(struct gbm_device *,gbm_bo_get_device,(struct gbm_bo *bo))
120SDL_KMSDRM_SYM(void,gbm_bo_set_user_data,(struct gbm_bo *bo, void *data,
121 void (*destroy_user_data)(struct gbm_bo *, void *)))
122SDL_KMSDRM_SYM(void *,gbm_bo_get_user_data,(struct gbm_bo *bo))
123SDL_KMSDRM_SYM(void,gbm_bo_destroy,(struct gbm_bo *bo))
124SDL_KMSDRM_SYM(struct gbm_bo *,gbm_bo_create,(struct gbm_device *gbm,
125 uint32_t width, uint32_t height,
126 uint32_t format, uint32_t usage))
127SDL_KMSDRM_SYM(struct gbm_surface *,gbm_surface_create,(struct gbm_device *gbm,
128 uint32_t width, uint32_t height,
129 uint32_t format, uint32_t flags))
130SDL_KMSDRM_SYM(void,gbm_surface_destroy,(struct gbm_surface *surf))
131SDL_KMSDRM_SYM(struct gbm_bo *,gbm_surface_lock_front_buffer,(struct gbm_surface *surf))
132SDL_KMSDRM_SYM(void,gbm_surface_release_buffer,(struct gbm_surface *surf, struct gbm_bo *bo))
133
134SDL_KMSDRM_SYM_OPT(uint64_t,gbm_bo_get_modifier,(struct gbm_bo *bo))
135SDL_KMSDRM_SYM_OPT(int,gbm_bo_get_plane_count,(struct gbm_bo *bo))
136SDL_KMSDRM_SYM_OPT(uint32_t,gbm_bo_get_offset,(struct gbm_bo *bo, int plane))
137SDL_KMSDRM_SYM_OPT(uint32_t,gbm_bo_get_stride_for_plane,(struct gbm_bo *bo, int plane))
138SDL_KMSDRM_SYM_OPT(union gbm_bo_handle,gbm_bo_get_handle_for_plane,(struct gbm_bo *bo, int plane))
139
140#undef SDL_KMSDRM_MODULE
141#undef SDL_KMSDRM_SYM
142#undef SDL_KMSDRM_SYM_CONST
143#undef SDL_KMSDRM_SYM_OPT
144
145/* *INDENT-ON* */ // clang-format on
diff --git a/contrib/SDL-3.2.8/src/video/kmsdrm/SDL_kmsdrmvideo.c b/contrib/SDL-3.2.8/src/video/kmsdrm/SDL_kmsdrmvideo.c
new file mode 100644
index 0000000..be1db82
--- /dev/null
+++ b/contrib/SDL-3.2.8/src/video/kmsdrm/SDL_kmsdrmvideo.c
@@ -0,0 +1,1820 @@
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
diff --git a/contrib/SDL-3.2.8/src/video/kmsdrm/SDL_kmsdrmvideo.h b/contrib/SDL-3.2.8/src/video/kmsdrm/SDL_kmsdrmvideo.h
new file mode 100644
index 0000000..5bd2e9c
--- /dev/null
+++ b/contrib/SDL-3.2.8/src/video/kmsdrm/SDL_kmsdrmvideo.h
@@ -0,0 +1,176 @@
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#ifndef SDL_kmsdrmvideo_h
25#define SDL_kmsdrmvideo_h
26
27#include "../SDL_sysvideo.h"
28
29#include <fcntl.h>
30#include <unistd.h>
31#include <xf86drm.h>
32#include <xf86drmMode.h>
33#include <gbm.h>
34#include <EGL/egl.h>
35
36#ifndef DRM_FORMAT_MOD_INVALID
37#define DRM_FORMAT_MOD_INVALID 0x00ffffffffffffffULL
38#endif
39
40#ifndef DRM_MODE_FB_MODIFIERS
41#define DRM_MODE_FB_MODIFIERS 2
42#endif
43
44#ifndef DRM_MODE_PAGE_FLIP_ASYNC
45#define DRM_MODE_PAGE_FLIP_ASYNC 2
46#endif
47
48#ifndef DRM_MODE_OBJECT_CONNECTOR
49#define DRM_MODE_OBJECT_CONNECTOR 0xc0c0c0c0
50#endif
51
52#ifndef DRM_MODE_OBJECT_CRTC
53#define DRM_MODE_OBJECT_CRTC 0xcccccccc
54#endif
55
56#ifndef DRM_CAP_ASYNC_PAGE_FLIP
57#define DRM_CAP_ASYNC_PAGE_FLIP 7
58#endif
59
60#ifndef DRM_CAP_CURSOR_WIDTH
61#define DRM_CAP_CURSOR_WIDTH 8
62#endif
63
64#ifndef DRM_CAP_CURSOR_HEIGHT
65#define DRM_CAP_CURSOR_HEIGHT 9
66#endif
67
68#ifndef GBM_FORMAT_ARGB8888
69#define GBM_FORMAT_ARGB8888 ((uint32_t)('A') | ((uint32_t)('R') << 8) | ((uint32_t)('2') << 16) | ((uint32_t)('4') << 24))
70#define GBM_BO_USE_CURSOR (1 << 1)
71#define GBM_BO_USE_WRITE (1 << 3)
72#define GBM_BO_USE_LINEAR (1 << 4)
73#endif
74
75struct SDL_VideoData
76{
77 int devindex; // device index that was passed on creation
78 int drm_fd; // DRM file desc
79 char devpath[32]; // DRM dev path.
80
81 struct gbm_device *gbm_dev;
82
83 bool video_init; // Has VideoInit succeeded?
84 bool vulkan_mode; // Are we in Vulkan mode? One VK window is enough to be.
85 bool async_pageflip_support; // Does the hardware support async. pageflips?
86
87 SDL_Window **windows;
88 int max_windows;
89 int num_windows;
90
91 /* Even if we have several displays, we only have to
92 open 1 FD and create 1 gbm device. */
93 bool gbm_init;
94
95};
96
97struct SDL_DisplayModeData
98{
99 int mode_index;
100};
101
102struct SDL_DisplayData
103{
104 drmModeConnector *connector;
105 drmModeCrtc *crtc;
106 drmModeModeInfo mode;
107 drmModeModeInfo original_mode;
108 drmModeModeInfo fullscreen_mode;
109
110 drmModeCrtc *saved_crtc; // CRTC to restore on quit
111 bool saved_vrr;
112
113 /* DRM & GBM cursor stuff lives here, not in an SDL_Cursor's internal struct,
114 because setting/unsetting up these is done on window creation/destruction,
115 where we may not have an SDL_Cursor at all (so no SDL_Cursor internal).
116 There's only one cursor GBM BO because we only support one cursor. */
117 struct gbm_bo *cursor_bo;
118 int cursor_bo_drm_fd;
119 uint64_t cursor_w, cursor_h;
120
121 bool default_cursor_init;
122};
123
124struct SDL_WindowData
125{
126 SDL_VideoData *viddata;
127 /* SDL internals expect EGL surface to be here, and in KMSDRM the GBM surface is
128 what supports the EGL surface on the driver side, so all these surfaces and buffers
129 are expected to be here, in the struct pointed by SDL_Window internal pointer:
130 this one. So don't try to move these to dispdata! */
131 struct gbm_surface *gs;
132 struct gbm_bo *bo;
133 struct gbm_bo *next_bo;
134
135 bool waiting_for_flip;
136 bool double_buffer;
137
138 EGLSurface egl_surface;
139 bool egl_surface_dirty;
140};
141
142typedef struct KMSDRM_FBInfo
143{
144 int drm_fd; // DRM file desc
145 uint32_t fb_id; // DRM framebuffer ID
146} KMSDRM_FBInfo;
147
148// Helper functions
149extern bool KMSDRM_CreateSurfaces(SDL_VideoDevice *_this, SDL_Window *window);
150extern KMSDRM_FBInfo *KMSDRM_FBFromBO(SDL_VideoDevice *_this, struct gbm_bo *bo);
151extern KMSDRM_FBInfo *KMSDRM_FBFromBO2(SDL_VideoDevice *_this, struct gbm_bo *bo, int w, int h);
152extern bool KMSDRM_WaitPageflip(SDL_VideoDevice *_this, SDL_WindowData *windata);
153
154/****************************************************************************/
155// SDL_VideoDevice functions declaration
156/****************************************************************************/
157
158// Display and window functions
159extern bool KMSDRM_VideoInit(SDL_VideoDevice *_this);
160extern void KMSDRM_VideoQuit(SDL_VideoDevice *_this);
161extern bool KMSDRM_GetDisplayModes(SDL_VideoDevice *_this, SDL_VideoDisplay *display);
162extern bool KMSDRM_SetDisplayMode(SDL_VideoDevice *_this, SDL_VideoDisplay *display, SDL_DisplayMode *mode);
163extern bool KMSDRM_CreateWindow(SDL_VideoDevice *_this, SDL_Window *window, SDL_PropertiesID create_props);
164extern void KMSDRM_SetWindowTitle(SDL_VideoDevice *_this, SDL_Window *window);
165extern bool KMSDRM_SetWindowPosition(SDL_VideoDevice *_this, SDL_Window *window);
166extern void KMSDRM_SetWindowSize(SDL_VideoDevice *_this, SDL_Window *window);
167extern SDL_FullscreenResult KMSDRM_SetWindowFullscreen(SDL_VideoDevice *_this, SDL_Window *window, SDL_VideoDisplay *_display, SDL_FullscreenOp fullscreen);
168extern void KMSDRM_ShowWindow(SDL_VideoDevice *_this, SDL_Window *window);
169extern void KMSDRM_HideWindow(SDL_VideoDevice *_this, SDL_Window *window);
170extern void KMSDRM_RaiseWindow(SDL_VideoDevice *_this, SDL_Window *window);
171extern void KMSDRM_MaximizeWindow(SDL_VideoDevice *_this, SDL_Window *window);
172extern void KMSDRM_MinimizeWindow(SDL_VideoDevice *_this, SDL_Window *window);
173extern void KMSDRM_RestoreWindow(SDL_VideoDevice *_this, SDL_Window *window);
174extern void KMSDRM_DestroyWindow(SDL_VideoDevice *_this, SDL_Window *window);
175
176#endif // SDL_kmsdrmvideo_h
diff --git a/contrib/SDL-3.2.8/src/video/kmsdrm/SDL_kmsdrmvulkan.c b/contrib/SDL-3.2.8/src/video/kmsdrm/SDL_kmsdrmvulkan.c
new file mode 100644
index 0000000..d58277b
--- /dev/null
+++ b/contrib/SDL-3.2.8/src/video/kmsdrm/SDL_kmsdrmvulkan.c
@@ -0,0 +1,520 @@
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/*
23 * @author Manuel Alfayate Corchere <redwindwanderer@gmail.com>.
24 * Based on Jacob Lifshay's SDL_x11vulkan.c.
25 */
26
27#include "SDL_internal.h"
28
29#if defined(SDL_VIDEO_VULKAN) && defined(SDL_VIDEO_DRIVER_KMSDRM)
30
31#include "../SDL_vulkan_internal.h"
32
33#include "SDL_kmsdrmvideo.h"
34#include "SDL_kmsdrmdyn.h"
35#include "SDL_kmsdrmvulkan.h"
36
37#include <sys/ioctl.h>
38
39#ifdef SDL_PLATFORM_OPENBSD
40#define DEFAULT_VULKAN "libvulkan.so"
41#else
42#define DEFAULT_VULKAN "libvulkan.so.1"
43#endif
44
45bool KMSDRM_Vulkan_LoadLibrary(SDL_VideoDevice *_this, const char *path)
46{
47 VkExtensionProperties *extensions = NULL;
48 Uint32 i, extensionCount = 0;
49 bool hasSurfaceExtension = false;
50 bool hasDisplayExtension = false;
51 PFN_vkGetInstanceProcAddr vkGetInstanceProcAddr = NULL;
52
53 if (_this->vulkan_config.loader_handle) {
54 return SDL_SetError("Vulkan already loaded");
55 }
56
57 // Load the Vulkan library
58 if (!path) {
59 path = SDL_GetHint(SDL_HINT_VULKAN_LIBRARY);
60 }
61 if (!path) {
62 path = DEFAULT_VULKAN;
63 }
64
65 _this->vulkan_config.loader_handle = SDL_LoadObject(path);
66
67 if (!_this->vulkan_config.loader_handle) {
68 return false;
69 }
70
71 SDL_strlcpy(_this->vulkan_config.loader_path, path,
72 SDL_arraysize(_this->vulkan_config.loader_path));
73
74 vkGetInstanceProcAddr = (PFN_vkGetInstanceProcAddr)SDL_LoadFunction(
75 _this->vulkan_config.loader_handle, "vkGetInstanceProcAddr");
76
77 if (!vkGetInstanceProcAddr) {
78 goto fail;
79 }
80
81 _this->vulkan_config.vkGetInstanceProcAddr = (void *)vkGetInstanceProcAddr;
82 _this->vulkan_config.vkEnumerateInstanceExtensionProperties =
83 (void *)((PFN_vkGetInstanceProcAddr)_this->vulkan_config.vkGetInstanceProcAddr)(
84 VK_NULL_HANDLE, "vkEnumerateInstanceExtensionProperties");
85
86 if (!_this->vulkan_config.vkEnumerateInstanceExtensionProperties) {
87 goto fail;
88 }
89
90 extensions = SDL_Vulkan_CreateInstanceExtensionsList(
91 (PFN_vkEnumerateInstanceExtensionProperties)
92 _this->vulkan_config.vkEnumerateInstanceExtensionProperties,
93 &extensionCount);
94
95 if (!extensions) {
96 goto fail;
97 }
98
99 for (i = 0; i < extensionCount; i++) {
100 if (SDL_strcmp(VK_KHR_SURFACE_EXTENSION_NAME, extensions[i].extensionName) == 0) {
101 hasSurfaceExtension = true;
102 } else if (SDL_strcmp(VK_KHR_DISPLAY_EXTENSION_NAME, extensions[i].extensionName) == 0) {
103 hasDisplayExtension = true;
104 }
105 }
106
107 SDL_free(extensions);
108
109 if (!hasSurfaceExtension) {
110 SDL_SetError("Installed Vulkan doesn't implement the " VK_KHR_SURFACE_EXTENSION_NAME " extension");
111 goto fail;
112 } else if (!hasDisplayExtension) {
113 SDL_SetError("Installed Vulkan doesn't implement the " VK_KHR_DISPLAY_EXTENSION_NAME "extension");
114 goto fail;
115 }
116
117 return true;
118
119fail:
120 SDL_UnloadObject(_this->vulkan_config.loader_handle);
121 _this->vulkan_config.loader_handle = NULL;
122 return false;
123}
124
125void KMSDRM_Vulkan_UnloadLibrary(SDL_VideoDevice *_this)
126{
127 if (_this->vulkan_config.loader_handle) {
128 SDL_UnloadObject(_this->vulkan_config.loader_handle);
129 _this->vulkan_config.loader_handle = NULL;
130 }
131}
132
133/*********************************************************************/
134// Here we can put whatever Vulkan extensions we want to be enabled
135// at instance creation, which is done in the programs, not in SDL.
136// So: programs call SDL_Vulkan_GetInstanceExtensions() and here
137// we put the extensions specific to this backend so the programs
138// get a list with the extension we want, so they can include that
139// list in the ppEnabledExtensionNames and EnabledExtensionCount
140// members of the VkInstanceCreateInfo struct passed to
141// vkCreateInstance().
142/*********************************************************************/
143char const* const* KMSDRM_Vulkan_GetInstanceExtensions(SDL_VideoDevice *_this,
144 Uint32 *count)
145{
146 static const char *const extensionsForKMSDRM[] = {
147 VK_KHR_SURFACE_EXTENSION_NAME, VK_KHR_DISPLAY_EXTENSION_NAME
148 };
149 if (count) {
150 *count = SDL_arraysize(extensionsForKMSDRM);
151 }
152 return extensionsForKMSDRM;
153}
154
155/***********************************************************************/
156// First thing to know is that we don't call vkCreateInstance() here.
157// Instead, programs using SDL and Vulkan create their Vulkan instance
158// and we get it here, ready to use.
159// Extensions specific for this platform are activated in
160// KMSDRM_Vulkan_GetInstanceExtensions(), like we do with
161// VK_KHR_DISPLAY_EXTENSION_NAME, which is what we need for x-less VK.
162/***********************************************************************/
163bool KMSDRM_Vulkan_CreateSurface(SDL_VideoDevice *_this,
164 SDL_Window *window,
165 VkInstance instance,
166 const struct VkAllocationCallbacks *allocator,
167 VkSurfaceKHR *surface)
168{
169 VkPhysicalDevice gpu = NULL;
170 uint32_t gpu_count;
171 uint32_t display_count;
172 uint32_t mode_count;
173 uint32_t plane_count;
174 uint32_t plane = UINT32_MAX;
175
176 VkPhysicalDevice *physical_devices = NULL;
177 VkPhysicalDeviceProperties *device_props = NULL;
178 VkDisplayPropertiesKHR *display_props = NULL;
179 VkDisplayModePropertiesKHR *mode_props = NULL;
180 VkDisplayPlanePropertiesKHR *plane_props = NULL;
181 VkDisplayPlaneCapabilitiesKHR plane_caps;
182
183 VkDisplayModeCreateInfoKHR display_mode_create_info;
184 VkDisplaySurfaceCreateInfoKHR display_plane_surface_create_info;
185
186 VkExtent2D image_size;
187 VkDisplayKHR display;
188 VkDisplayModeKHR display_mode = (VkDisplayModeKHR)0;
189 VkDisplayModePropertiesKHR display_mode_props = { 0 };
190 VkDisplayModeParametersKHR new_mode_parameters = { { 0, 0 }, 0 };
191 // Prefer a plane that supports per-pixel alpha.
192 VkDisplayPlaneAlphaFlagBitsKHR alpha_mode = VK_DISPLAY_PLANE_ALPHA_OPAQUE_BIT_KHR;
193
194 VkResult result;
195 bool ret = false;
196 bool valid_gpu = false;
197 bool mode_found = false;
198 bool plane_supports_display = false;
199
200 // Get the display index from the display being used by the window.
201 int display_index = SDL_GetDisplayIndex(SDL_GetDisplayForWindow(window));
202 int i, j;
203
204 // Get the function pointers for the functions we will use.
205 PFN_vkGetInstanceProcAddr vkGetInstanceProcAddr =
206 (PFN_vkGetInstanceProcAddr)_this->vulkan_config.vkGetInstanceProcAddr;
207
208 PFN_vkCreateDisplayPlaneSurfaceKHR vkCreateDisplayPlaneSurfaceKHR =
209 (PFN_vkCreateDisplayPlaneSurfaceKHR)vkGetInstanceProcAddr(
210 instance, "vkCreateDisplayPlaneSurfaceKHR");
211
212 PFN_vkEnumeratePhysicalDevices vkEnumeratePhysicalDevices =
213 (PFN_vkEnumeratePhysicalDevices)vkGetInstanceProcAddr(
214 instance, "vkEnumeratePhysicalDevices");
215
216 PFN_vkGetPhysicalDeviceProperties vkGetPhysicalDeviceProperties =
217 (PFN_vkGetPhysicalDeviceProperties)vkGetInstanceProcAddr(
218 instance, "vkGetPhysicalDeviceProperties");
219
220 PFN_vkGetPhysicalDeviceDisplayPropertiesKHR vkGetPhysicalDeviceDisplayPropertiesKHR =
221 (PFN_vkGetPhysicalDeviceDisplayPropertiesKHR)vkGetInstanceProcAddr(
222 instance, "vkGetPhysicalDeviceDisplayPropertiesKHR");
223
224 PFN_vkGetDisplayModePropertiesKHR vkGetDisplayModePropertiesKHR =
225 (PFN_vkGetDisplayModePropertiesKHR)vkGetInstanceProcAddr(
226 instance, "vkGetDisplayModePropertiesKHR");
227
228 PFN_vkGetPhysicalDeviceDisplayPlanePropertiesKHR vkGetPhysicalDeviceDisplayPlanePropertiesKHR =
229 (PFN_vkGetPhysicalDeviceDisplayPlanePropertiesKHR)vkGetInstanceProcAddr(
230 instance, "vkGetPhysicalDeviceDisplayPlanePropertiesKHR");
231
232 PFN_vkGetDisplayPlaneSupportedDisplaysKHR vkGetDisplayPlaneSupportedDisplaysKHR =
233 (PFN_vkGetDisplayPlaneSupportedDisplaysKHR)vkGetInstanceProcAddr(
234 instance, "vkGetDisplayPlaneSupportedDisplaysKHR");
235
236 PFN_vkGetDisplayPlaneCapabilitiesKHR vkGetDisplayPlaneCapabilitiesKHR =
237 (PFN_vkGetDisplayPlaneCapabilitiesKHR)vkGetInstanceProcAddr(
238 instance, "vkGetDisplayPlaneCapabilitiesKHR");
239
240 PFN_vkCreateDisplayModeKHR vkCreateDisplayModeKHR =
241 (PFN_vkCreateDisplayModeKHR)vkGetInstanceProcAddr(
242 instance, "vkCreateDisplayModeKHR");
243
244 if (!_this->vulkan_config.loader_handle) {
245 SDL_SetError("Vulkan is not loaded");
246 goto clean;
247 }
248
249 /*************************************/
250 // Block for vulkan surface creation
251 /*************************************/
252
253 /****************************************************************/
254 // If we got vkCreateDisplayPlaneSurfaceKHR() pointer, it means
255 // that the VK_KHR_Display extension is active on the instance.
256 // That's the central extension we need for x-less VK!
257 /****************************************************************/
258 if (!vkCreateDisplayPlaneSurfaceKHR) {
259 SDL_SetError(VK_KHR_DISPLAY_EXTENSION_NAME
260 " extension is not enabled in the Vulkan instance.");
261 goto clean;
262 }
263
264 /* A GPU (or physical_device, in vkcube terms) is a physical GPU.
265 A machine with more than one video output doesn't need to have more than one GPU,
266 like the Pi4 which has 1 GPU and 2 video outputs.
267 Just in case, we test that the GPU we choose is Vulkan-capable.
268 If there are new reports about VK init failures, hardcode
269 gpu = physical_devices[0], instead of probing, and go with that.
270 */
271
272 // Get the physical device count.
273 vkEnumeratePhysicalDevices(instance, &gpu_count, NULL);
274
275 if (gpu_count == 0) {
276 SDL_SetError("Vulkan can't find physical devices (gpus).");
277 goto clean;
278 }
279
280 // Get the physical devices.
281 physical_devices = SDL_malloc(sizeof(VkPhysicalDevice) * gpu_count);
282 device_props = SDL_malloc(sizeof(VkPhysicalDeviceProperties));
283 vkEnumeratePhysicalDevices(instance, &gpu_count, physical_devices);
284
285 // Iterate on the physical devices.
286 for (i = 0; i < gpu_count; i++) {
287
288 // Get the physical device properties.
289 vkGetPhysicalDeviceProperties(
290 physical_devices[i],
291 device_props);
292
293 // Is this device a real GPU that supports API version 1 at least?
294 if (device_props->apiVersion >= 1 &&
295 (device_props->deviceType == 1 || device_props->deviceType == 2)) {
296 gpu = physical_devices[i];
297 valid_gpu = true;
298 break;
299 }
300 }
301
302 if (!valid_gpu) {
303 SDL_SetError("Vulkan can't find a valid physical device (gpu).");
304 goto clean;
305 }
306
307 /* A display is a video output. 1 GPU can have N displays.
308 Vulkan only counts the connected displays.
309 Get the display count of the GPU. */
310 vkGetPhysicalDeviceDisplayPropertiesKHR(gpu, &display_count, NULL);
311 if (display_count == 0) {
312 SDL_SetError("Vulkan can't find any displays.");
313 goto clean;
314 }
315
316 // Get the props of the displays of the physical device.
317 display_props = (VkDisplayPropertiesKHR *)SDL_malloc(display_count * sizeof(*display_props));
318 vkGetPhysicalDeviceDisplayPropertiesKHR(gpu,
319 &display_count,
320 display_props);
321
322 // Get the chosen display based on the display index.
323 display = display_props[display_index].display;
324
325 // Get the list of the display videomodes.
326 vkGetDisplayModePropertiesKHR(gpu,
327 display,
328 &mode_count, NULL);
329
330 if (mode_count == 0) {
331 SDL_SetError("Vulkan can't find any video modes for display %i (%s)", 0,
332 display_props[display_index].displayName);
333 goto clean;
334 }
335
336 mode_props = (VkDisplayModePropertiesKHR *)SDL_malloc(mode_count * sizeof(*mode_props));
337 vkGetDisplayModePropertiesKHR(gpu,
338 display,
339 &mode_count, mode_props);
340
341 /* Get a video mode equal to the window size among the predefined ones,
342 if possible.
343 REMEMBER: We have to get a small enough videomode for the window size,
344 because videomode determines how big the scanout region is and we can't
345 scanout a region bigger than the window (we would be reading past the
346 buffer, and Vulkan would give us a confusing VK_ERROR_SURFACE_LOST_KHR). */
347 for (i = 0; i < mode_count; i++) {
348 if (mode_props[i].parameters.visibleRegion.width == window->w &&
349 mode_props[i].parameters.visibleRegion.height == window->h) {
350 display_mode_props = mode_props[i];
351 mode_found = true;
352 break;
353 }
354 }
355
356 if (mode_found &&
357 display_mode_props.parameters.visibleRegion.width > 0 &&
358 display_mode_props.parameters.visibleRegion.height > 0) {
359 // Found a suitable mode among the predefined ones. Use that.
360 display_mode = display_mode_props.displayMode;
361 } else {
362
363 /* Couldn't find a suitable mode among the predefined ones, so try to create our own.
364 This won't work for some video chips atm (like Pi's VideoCore) so these are limited
365 to supported resolutions. Don't try to use "closest" resolutions either, because
366 those are often bigger than the window size, thus causing out-of-bunds scanout. */
367 new_mode_parameters.visibleRegion.width = window->w;
368 new_mode_parameters.visibleRegion.height = window->h;
369 /* SDL (and DRM, if we look at drmModeModeInfo vrefresh) uses plain integer Hz for
370 display mode refresh rate, but Vulkan expects higher precision. */
371 new_mode_parameters.refreshRate = (uint32_t)(window->current_fullscreen_mode.refresh_rate * 1000);
372
373 SDL_zero(display_mode_create_info);
374 display_mode_create_info.sType = VK_STRUCTURE_TYPE_DISPLAY_MODE_CREATE_INFO_KHR;
375 display_mode_create_info.parameters = new_mode_parameters;
376 result = vkCreateDisplayModeKHR(gpu,
377 display,
378 &display_mode_create_info,
379 NULL, &display_mode);
380 if (result != VK_SUCCESS) {
381 SDL_SetError("Vulkan couldn't find a predefined mode for that window size and couldn't create a suitable mode.");
382 goto clean;
383 }
384 }
385
386 // Just in case we get here without a display_mode.
387 if (!display_mode) {
388 SDL_SetError("Vulkan couldn't get a display mode.");
389 goto clean;
390 }
391
392 // Get the list of the physical device planes.
393 vkGetPhysicalDeviceDisplayPlanePropertiesKHR(gpu, &plane_count, NULL);
394 if (plane_count == 0) {
395 SDL_SetError("Vulkan can't find any planes.");
396 goto clean;
397 }
398 plane_props = SDL_malloc(sizeof(VkDisplayPlanePropertiesKHR) * plane_count);
399 vkGetPhysicalDeviceDisplayPlanePropertiesKHR(gpu, &plane_count, plane_props);
400
401 /* Iterate on the list of planes of the physical device
402 to find a plane that matches these criteria:
403 -It must be compatible with the chosen display + mode.
404 -It isn't currently bound to another display.
405 -It supports per-pixel alpha, if possible. */
406 for (i = 0; i < plane_count; i++) {
407
408 uint32_t supported_displays_count = 0;
409 VkDisplayKHR *supported_displays;
410
411 // See if the plane is compatible with the current display.
412 vkGetDisplayPlaneSupportedDisplaysKHR(gpu, i, &supported_displays_count, NULL);
413 if (supported_displays_count == 0) {
414 // This plane doesn't support any displays. Continue to the next plane.
415 continue;
416 }
417
418 // Get the list of displays supported by this plane.
419 supported_displays = (VkDisplayKHR *)SDL_malloc(sizeof(VkDisplayKHR) * supported_displays_count);
420 vkGetDisplayPlaneSupportedDisplaysKHR(gpu, i,
421 &supported_displays_count, supported_displays);
422
423 /* The plane must be bound to the chosen display, or not in use.
424 If none of these is true, iterate to another plane. */
425 if (!((plane_props[i].currentDisplay == display) || (plane_props[i].currentDisplay == VK_NULL_HANDLE))) {
426 continue;
427 }
428
429 /* Iterate the list of displays supported by this plane
430 in order to find out if the chosen display is among them. */
431 plane_supports_display = false;
432 for (j = 0; j < supported_displays_count; j++) {
433 if (supported_displays[j] == display) {
434 plane_supports_display = true;
435 break;
436 }
437 }
438
439 // Free the list of displays supported by this plane.
440 if (supported_displays) {
441 SDL_free(supported_displays);
442 }
443
444 // If the display is not supported by this plane, iterate to the next plane.
445 if (!plane_supports_display) {
446 continue;
447 }
448
449 // Want a plane that supports the alpha mode we have chosen.
450 vkGetDisplayPlaneCapabilitiesKHR(gpu, display_mode, i, &plane_caps);
451 if (plane_caps.supportedAlpha == alpha_mode) {
452 // Yep, this plane is alright.
453 plane = i;
454 break;
455 }
456 }
457
458 // If we couldn't find an appropriate plane, error out.
459 if (plane == UINT32_MAX) {
460 SDL_SetError("Vulkan couldn't find an appropriate plane.");
461 goto clean;
462 }
463
464 /********************************************/
465 // Let's finally create the Vulkan surface!
466 /********************************************/
467
468 image_size.width = window->w;
469 image_size.height = window->h;
470
471 SDL_zero(display_plane_surface_create_info);
472 display_plane_surface_create_info.sType = VK_STRUCTURE_TYPE_DISPLAY_SURFACE_CREATE_INFO_KHR;
473 display_plane_surface_create_info.displayMode = display_mode;
474 display_plane_surface_create_info.planeIndex = plane;
475 display_plane_surface_create_info.imageExtent = image_size;
476 display_plane_surface_create_info.transform = VK_SURFACE_TRANSFORM_IDENTITY_BIT_KHR;
477 display_plane_surface_create_info.alphaMode = alpha_mode;
478 result = vkCreateDisplayPlaneSurfaceKHR(instance,
479 &display_plane_surface_create_info,
480 allocator,
481 surface);
482 if (result != VK_SUCCESS) {
483 SDL_SetError("vkCreateDisplayPlaneSurfaceKHR failed: %s",
484 SDL_Vulkan_GetResultString(result));
485 goto clean;
486 }
487
488 ret = true; // success!
489
490clean:
491 if (physical_devices) {
492 SDL_free(physical_devices);
493 }
494 if (display_props) {
495 SDL_free(display_props);
496 }
497 if (device_props) {
498 SDL_free(device_props);
499 }
500 if (plane_props) {
501 SDL_free(plane_props);
502 }
503 if (mode_props) {
504 SDL_free(mode_props);
505 }
506
507 return ret;
508}
509
510void KMSDRM_Vulkan_DestroySurface(SDL_VideoDevice *_this,
511 VkInstance instance,
512 VkSurfaceKHR surface,
513 const struct VkAllocationCallbacks *allocator)
514{
515 if (_this->vulkan_config.loader_handle) {
516 SDL_Vulkan_DestroySurface_Internal(_this->vulkan_config.vkGetInstanceProcAddr, instance, surface, allocator);
517 }
518}
519
520#endif
diff --git a/contrib/SDL-3.2.8/src/video/kmsdrm/SDL_kmsdrmvulkan.h b/contrib/SDL-3.2.8/src/video/kmsdrm/SDL_kmsdrmvulkan.h
new file mode 100644
index 0000000..aabfd92
--- /dev/null
+++ b/contrib/SDL-3.2.8/src/video/kmsdrm/SDL_kmsdrmvulkan.h
@@ -0,0 +1,51 @@
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/*
23 * @author Manuel Alfayate Corchere <redwindwanderer@gmail.com>.
24 * Based on Jacob Lifshay's SDL_x11vulkan.c.
25 */
26
27#include "SDL_internal.h"
28
29#ifndef SDL_kmsdrm_vulkan_h_
30#define SDL_kmsdrm_vulkan_h_
31
32#include <SDL3/SDL_vulkan.h>
33
34#if defined(SDL_VIDEO_VULKAN) && defined(SDL_VIDEO_DRIVER_KMSDRM)
35
36extern bool KMSDRM_Vulkan_LoadLibrary(SDL_VideoDevice *_this, const char *path);
37extern void KMSDRM_Vulkan_UnloadLibrary(SDL_VideoDevice *_this);
38extern char const* const* KMSDRM_Vulkan_GetInstanceExtensions(SDL_VideoDevice *_this, Uint32 *count);
39extern bool KMSDRM_Vulkan_CreateSurface(SDL_VideoDevice *_this,
40 SDL_Window *window,
41 VkInstance instance,
42 const struct VkAllocationCallbacks *allocator,
43 VkSurfaceKHR *surface);
44extern void KMSDRM_Vulkan_DestroySurface(SDL_VideoDevice *_this,
45 VkInstance instance,
46 VkSurfaceKHR surface,
47 const struct VkAllocationCallbacks *allocator);
48
49#endif
50
51#endif // SDL_kmsdrm_vulkan_h_