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
204