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