1 /*
2 Simple DirectMedia Layer
3 Copyright (C) 1997-2020 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 #if SDL_VIDEO_DRIVER_KMSDRM && SDL_VIDEO_OPENGL_EGL
25
26 #include "SDL_kmsdrmvideo.h"
27 #include "SDL_kmsdrmopengles.h"
28 #include "SDL_kmsdrmdyn.h"
29
30 #ifndef EGL_PLATFORM_GBM_MESA
31 #define EGL_PLATFORM_GBM_MESA 0x31D7
32 #endif
33
34 /* EGL implementation of SDL OpenGL support */
35
36 int
KMSDRM_GLES_LoadLibrary(_THIS,const char * path)37 KMSDRM_GLES_LoadLibrary(_THIS, const char *path) {
38 NativeDisplayType display = (NativeDisplayType)((SDL_VideoData *)_this->driverdata)->gbm;
39 return SDL_EGL_LoadLibrary(_this, path, display, EGL_PLATFORM_GBM_MESA);
40 }
41
SDL_EGL_CreateContext_impl(KMSDRM)42 SDL_EGL_CreateContext_impl(KMSDRM)
43
44 int KMSDRM_GLES_SetSwapInterval(_THIS, int interval) {
45 if (!_this->egl_data) {
46 return SDL_SetError("EGL not initialized");
47 }
48
49 if (interval == 0 || interval == 1) {
50 _this->egl_data->egl_swapinterval = interval;
51 } else {
52 return SDL_SetError("Only swap intervals of 0 or 1 are supported");
53 }
54
55 return 0;
56 }
57
58 int
KMSDRM_GLES_SwapWindow(_THIS,SDL_Window * window)59 KMSDRM_GLES_SwapWindow(_THIS, SDL_Window * window) {
60 SDL_WindowData *windata = ((SDL_WindowData *) window->driverdata);
61 SDL_DisplayData *dispdata = (SDL_DisplayData *) SDL_GetDisplayForWindow(window)->driverdata;
62 SDL_VideoData *viddata = ((SDL_VideoData *)_this->driverdata);
63 KMSDRM_FBInfo *fb_info;
64 SDL_bool crtc_setup_pending = SDL_FALSE;
65
66 /* ALWAYS wait for each pageflip to complete before issuing another, vsync or not,
67 or drmModePageFlip() will start returning EBUSY if there are pending pageflips.
68
69 To disable vsync in games, it would be needed to issue async pageflips,
70 and then wait for each pageflip to complete. Since async pageflips complete ASAP
71 instead of VBLANK, thats how non-vsync screen updates should wok.
72
73 BUT Async pageflips do not work right now because calling drmModePageFlip() with the
74 DRM_MODE_PAGE_FLIP_ASYNC flag returns error on every driver I have tried.
75
76 So, for now, only do vsynced updates: _this->egl_data->egl_swapinterval is
77 ignored for now, it makes no sense to use it until async pageflips work on drm drivers. */
78
79 /* Recreate the GBM / EGL surfaces if the display mode has changed */
80 if (windata->egl_surface_dirty) {
81 KMSDRM_CreateSurfaces(_this, window);
82 /* Do this later, when a fb_id is obtained. */
83 crtc_setup_pending = SDL_TRUE;
84 }
85
86 if (windata->double_buffer) {
87 /* Use a double buffering scheme, independently of the number of buffers that the GBM surface has,
88 (number of buffers on the GBM surface depends on the implementation).
89 Double buffering (instead of triple) is achieved by waiting for synchronous pageflip to complete
90 inmediately after the pageflip is issued. That way, in the end of this function only two buffers
91 are needed: a buffer that is available to be picked again by EGL as a backbuffer to draw on it,
92 and the new front buffer that has just been set up.
93
94 Since programmer has no control over the number of buffers of the GBM surface, wait for pageflip
95 is done inmediately after issuing pageflip, and so a double-buffer scheme is achieved. */
96
97 /* Ask EGL to mark the current back buffer to become the next front buffer.
98 That will happen when a pageflip is issued, and the next vsync arrives (sync flip)
99 or ASAP (async flip). */
100 if (!(_this->egl_data->eglSwapBuffers(_this->egl_data->egl_display, windata->egl_surface))) {
101 SDL_LogError(SDL_LOG_CATEGORY_VIDEO, "eglSwapBuffers failed.");
102 return 0;
103 }
104
105 /* Get a handler to the buffer that is marked to become the next front buffer, and lock it
106 so it can not be chosen by EGL as a back buffer. */
107 windata->next_bo = KMSDRM_gbm_surface_lock_front_buffer(windata->gs);
108 if (!windata->next_bo) {
109 SDL_LogError(SDL_LOG_CATEGORY_VIDEO, "Could not lock GBM surface front buffer");
110 return 0;
111 /* } else {
112 SDL_LogDebug(SDL_LOG_CATEGORY_VIDEO, "Locked GBM surface %p", (void *)windata->next_bo); */
113 }
114
115 /* Issue synchronous pageflip: drmModePageFlip() NEVER blocks, synchronous here means that it
116 will be done on next VBLANK, not ASAP. And return to program loop inmediately. */
117
118 fb_info = KMSDRM_FBFromBO(_this, windata->next_bo);
119 if (!fb_info) {
120 return 0;
121 }
122
123 /* When needed, this is done once we have the needed fb_id, not before. */
124 if (crtc_setup_pending) {
125 if (KMSDRM_drmModeSetCrtc(viddata->drm_fd, dispdata->crtc_id, fb_info->fb_id, 0,
126 0, &dispdata->conn->connector_id, 1, &dispdata->mode)) {
127 SDL_LogError(SDL_LOG_CATEGORY_VIDEO, "Could not configure CRTC on video mode setting.");
128 }
129 crtc_setup_pending = SDL_FALSE;
130 }
131
132 if (!KMSDRM_drmModePageFlip(viddata->drm_fd, dispdata->crtc_id, fb_info->fb_id,
133 DRM_MODE_PAGE_FLIP_EVENT, &windata->waiting_for_flip)) {
134 windata->waiting_for_flip = SDL_TRUE;
135 } else {
136 SDL_LogError(SDL_LOG_CATEGORY_VIDEO, "Could not issue pageflip");
137 }
138
139 /* Since issued pageflips are always synchronous (ASYNC dont currently work), these pageflips
140 will happen at next vsync, so in practice waiting for vsync is being done here. */
141 if (!KMSDRM_WaitPageFlip(_this, windata, -1)) {
142 SDL_LogError(SDL_LOG_CATEGORY_VIDEO, "Error waiting for pageflip event");
143 return 0;
144 }
145
146 /* Return the previous front buffer to the available buffer pool of the GBM surface,
147 so it can be chosen again by EGL as the back buffer for drawing into it. */
148 if (windata->curr_bo) {
149 KMSDRM_gbm_surface_release_buffer(windata->gs, windata->curr_bo);
150 /* SDL_LogDebug(SDL_LOG_CATEGORY_VIDEO, "Released GBM surface buffer %p", (void *)windata->curr_bo); */
151 windata->curr_bo = NULL;
152 }
153
154 /* Take note of the current front buffer, so it can be freed next time this function is called. */
155 windata->curr_bo = windata->next_bo;
156 } else {
157 /* Triple buffering requires waiting for last pageflip upon entering instead of waiting at the end,
158 and issuing the next pageflip at the end, thus allowing the program loop to run
159 while the issued pageflip arrives (at next VBLANK, since ONLY synchronous pageflips are possible).
160 In a game context, this means that the player can be doing inputs before seeing the last
161 completed frame, causing "input lag" that is known to plage other APIs and backends.
162 Triple buffering requires the use of three different buffers at the end of this function:
163 1- the front buffer which is on screen,
164 2- the back buffer wich is ready to be flipped (a pageflip has been issued on it, which has yet to complete)
165 3- a third buffer that can be used by EGL to draw while the previously issued pageflip arrives
166 (should not put back the previous front buffer into the free buffers pool of the
167 GBM surface until that happens).
168 If the implementation only has two buffers for the GBM surface, this would behave like a double buffer.
169 */
170
171 /* Wait for previously issued pageflip to complete. */
172 if (!KMSDRM_WaitPageFlip(_this, windata, -1)) {
173 SDL_LogError(SDL_LOG_CATEGORY_VIDEO, "Error waiting for pageflip event");
174 return 0;
175 }
176
177 /* Free the previous front buffer so EGL can pick it again as back buffer.*/
178 if (windata->curr_bo) {
179 KMSDRM_gbm_surface_release_buffer(windata->gs, windata->curr_bo);
180 /* SDL_LogDebug(SDL_LOG_CATEGORY_VIDEO, "Released GBM surface buffer %p", (void *)windata->curr_bo); */
181 windata->curr_bo = NULL;
182 }
183
184 /* Ask EGL to mark the current back buffer to become the next front buffer.
185 That will happen when a pageflip is issued, and the next vsync arrives (sync flip)
186 or ASAP (async flip). */
187 if (!(_this->egl_data->eglSwapBuffers(_this->egl_data->egl_display, windata->egl_surface))) {
188 SDL_LogError(SDL_LOG_CATEGORY_VIDEO, "eglSwapBuffers failed.");
189 return 0;
190 }
191
192 /* Take note of the current front buffer, so it can be freed next time this function is called. */
193 windata->curr_bo = windata->next_bo;
194
195 /* Get a handler to the buffer that is marked to become the next front buffer, and lock it
196 so it can not be chosen by EGL as a back buffer. */
197 windata->next_bo = KMSDRM_gbm_surface_lock_front_buffer(windata->gs);
198 if (!windata->next_bo) {
199 SDL_LogError(SDL_LOG_CATEGORY_VIDEO, "Could not lock GBM surface front buffer");
200 return 0;
201 /* } else {
202 SDL_LogDebug(SDL_LOG_CATEGORY_VIDEO, "Locked GBM surface %p", (void *)windata->next_bo); */
203 }
204
205 /* Issue synchronous pageflip: drmModePageFlip() NEVER blocks, synchronous here means that it
206 will be done on next VBLANK, not ASAP. And return to program loop inmediately. */
207 fb_info = KMSDRM_FBFromBO(_this, windata->next_bo);
208 if (!fb_info) {
209 return 0;
210 }
211
212 /* When needed, this is done once we have the needed fb_id, not before. */
213 if (crtc_setup_pending) {
214 if (KMSDRM_drmModeSetCrtc(viddata->drm_fd, dispdata->crtc_id, fb_info->fb_id, 0,
215 0, &dispdata->conn->connector_id, 1, &dispdata->mode)) {
216 SDL_LogError(SDL_LOG_CATEGORY_VIDEO, "Could not configure CRTC on video mode setting.");
217 }
218 crtc_setup_pending = SDL_FALSE;
219 }
220
221
222 if (!KMSDRM_drmModePageFlip(viddata->drm_fd, dispdata->crtc_id, fb_info->fb_id,
223 DRM_MODE_PAGE_FLIP_EVENT, &windata->waiting_for_flip)) {
224 windata->waiting_for_flip = SDL_TRUE;
225 } else {
226 SDL_LogError(SDL_LOG_CATEGORY_VIDEO, "Could not issue pageflip");
227 }
228 }
229
230 return 0;
231 }
232
233 SDL_EGL_MakeCurrent_impl(KMSDRM)
234
235 #endif /* SDL_VIDEO_DRIVER_KMSDRM && SDL_VIDEO_OPENGL_EGL */
236
237 /* vi: set ts=4 sw=4 expandtab: */
238