1 /*
2   Simple DirectMedia Layer
3   Copyright (C) 1997-2021 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 #if SDL_VIDEO_DRIVER_WAYLAND && SDL_VIDEO_OPENGL_EGL
24 
25 #include "SDL_timer.h"
26 #include "../../core/unix/SDL_poll.h"
27 #include "../SDL_sysvideo.h"
28 #include "../../events/SDL_windowevents_c.h"
29 #include "SDL_waylandvideo.h"
30 #include "SDL_waylandopengles.h"
31 #include "SDL_waylandwindow.h"
32 #include "SDL_waylandevents_c.h"
33 
34 #include "xdg-shell-client-protocol.h"
35 
36 /* EGL implementation of SDL OpenGL ES support */
37 
38 int
Wayland_GLES_LoadLibrary(_THIS,const char * path)39 Wayland_GLES_LoadLibrary(_THIS, const char *path) {
40     int ret;
41     SDL_VideoData *data = (SDL_VideoData *) _this->driverdata;
42 
43     ret = SDL_EGL_LoadLibrary(_this, path, (NativeDisplayType) data->display, 0);
44 
45     Wayland_PumpEvents(_this);
46     WAYLAND_wl_display_flush(data->display);
47 
48     return ret;
49 }
50 
51 
52 SDL_GLContext
Wayland_GLES_CreateContext(_THIS,SDL_Window * window)53 Wayland_GLES_CreateContext(_THIS, SDL_Window * window)
54 {
55     SDL_GLContext context;
56     context = SDL_EGL_CreateContext(_this, ((SDL_WindowData *) window->driverdata)->egl_surface);
57     WAYLAND_wl_display_flush( ((SDL_VideoData*)_this->driverdata)->display );
58 
59     return context;
60 }
61 
62 /* Wayland wants to tell you when to provide new frames, and if you have a non-zero
63    swap interval, Mesa will block until a callback tells it to do so. On some
64    compositors, they might decide that a minimized window _never_ gets a callback,
65    which causes apps to hang during swapping forever. So we always set the official
66    eglSwapInterval to zero to avoid blocking inside EGL, and manage this ourselves.
67    If a swap blocks for too long waiting on a callback, we just go on, under the
68    assumption the frame will be wasted, but this is better than freezing the app.
69    I frown upon platforms that dictate this sort of control inversion (the callback
70    is intended for _rendering_, not stalling until vsync), but we can work around
71    this for now.  --ryan. */
72 /* Addendum: several recent APIs demand this sort of control inversion: Emscripten,
73    libretro, Wayland, probably others...it feels like we're eventually going to have
74    to give in with a future SDL API revision, since we can bend the other APIs to
75    this style, but this style is much harder to bend the other way.  :/ */
76 int
Wayland_GLES_SetSwapInterval(_THIS,int interval)77 Wayland_GLES_SetSwapInterval(_THIS, int interval)
78 {
79     if (!_this->egl_data) {
80         return SDL_SetError("EGL not initialized");
81     }
82 
83     /* technically, this is _all_ adaptive vsync (-1), because we can't
84        actually wait for the _next_ vsync if you set 1, but things that
85        request 1 probably won't care _that_ much. I hope. No matter what
86        you do, though, you never see tearing on Wayland. */
87     if (interval > 1) {
88         interval = 1;
89     } else if (interval < -1) {
90         interval = -1;
91     }
92 
93     /* !!! FIXME: technically, this should be per-context, right? */
94     _this->egl_data->egl_swapinterval = interval;
95     _this->egl_data->eglSwapInterval(_this->egl_data->egl_display, 0);
96     return 0;
97 }
98 
99 int
Wayland_GLES_GetSwapInterval(_THIS)100 Wayland_GLES_GetSwapInterval(_THIS)
101 {
102     if (!_this->egl_data) {
103         SDL_SetError("EGL not initialized");
104         return 0;
105     }
106 
107     return _this->egl_data->egl_swapinterval;
108 }
109 
110 int
Wayland_GLES_SwapWindow(_THIS,SDL_Window * window)111 Wayland_GLES_SwapWindow(_THIS, SDL_Window *window)
112 {
113     SDL_WindowData *data = (SDL_WindowData *) window->driverdata;
114     const int swap_interval = _this->egl_data->egl_swapinterval;
115 
116     /* For windows that we know are hidden, skip swaps entirely, if we don't do
117      * this compositors will intentionally stall us indefinitely and there's no
118      * way for an end user to show the window, unlike other situations (i.e.
119      * the window is minimized, behind another window, etc.).
120      *
121      * FIXME: Request EGL_WAYLAND_swap_buffers_with_timeout.
122      * -flibit
123      */
124     if (window->flags & SDL_WINDOW_HIDDEN) {
125         return 0;
126     }
127 
128     /* Control swap interval ourselves. See comments on Wayland_GLES_SetSwapInterval */
129     if (swap_interval != 0) {
130         SDL_VideoData *videodata = (SDL_VideoData *)_this->driverdata;
131         struct wl_display *display = videodata->display;
132         SDL_VideoDisplay *sdldisplay = SDL_GetDisplayForWindow(window);
133         /* ~10 frames (or 1 sec), so we'll progress even if throttled to zero. */
134         const Uint32 max_wait = SDL_GetTicks() + (sdldisplay->current_mode.refresh_rate ?
135                                                   (10000 / sdldisplay->current_mode.refresh_rate) : 1000);
136         while (SDL_AtomicGet(&data->swap_interval_ready) == 0) {
137             Uint32 now;
138 
139             WAYLAND_wl_display_flush(display);
140 
141             /* wl_display_prepare_read_queue() will return -1 if the event queue is not empty.
142              * If the event queue is empty, it will prepare us for our SDL_IOReady() call. */
143             if (WAYLAND_wl_display_prepare_read_queue(display, data->frame_event_queue) != 0) {
144                 /* We have some pending events. Check if the frame callback happened. */
145                 WAYLAND_wl_display_dispatch_queue_pending(display, data->frame_event_queue);
146                 continue;
147             }
148 
149             /* Beyond this point, we must either call wl_display_cancel_read() or wl_display_read_events() */
150 
151             now = SDL_GetTicks();
152             if (SDL_TICKS_PASSED(now, max_wait)) {
153                 /* Timeout expired. Cancel the read. */
154                 WAYLAND_wl_display_cancel_read(display);
155                 break;
156             }
157 
158             if (SDL_IOReady(WAYLAND_wl_display_get_fd(display), SDL_IOR_READ, max_wait - now) <= 0) {
159                 /* Error or timeout expired without any events for us. Cancel the read. */
160                 WAYLAND_wl_display_cancel_read(display);
161                 break;
162             }
163 
164             /* We have events. Read and dispatch them. */
165             WAYLAND_wl_display_read_events(display);
166             WAYLAND_wl_display_dispatch_queue_pending(display, data->frame_event_queue);
167         }
168         SDL_AtomicSet(&data->swap_interval_ready, 0);
169     }
170 
171     /* Feed the frame to Wayland. This will set it so the wl_surface_frame callback can fire again. */
172     if (!_this->egl_data->eglSwapBuffers(_this->egl_data->egl_display, data->egl_surface)) {
173         return SDL_EGL_SetError("unable to show color buffer in an OS-native window", "eglSwapBuffers");
174     }
175 
176     WAYLAND_wl_display_flush( data->waylandData->display );
177 
178     return 0;
179 }
180 
181 int
Wayland_GLES_MakeCurrent(_THIS,SDL_Window * window,SDL_GLContext context)182 Wayland_GLES_MakeCurrent(_THIS, SDL_Window * window, SDL_GLContext context)
183 {
184     int ret;
185 
186     if (window && context) {
187         ret = SDL_EGL_MakeCurrent(_this, ((SDL_WindowData *) window->driverdata)->egl_surface, context);
188     }
189     else {
190         ret = SDL_EGL_MakeCurrent(_this, NULL, NULL);
191     }
192 
193     WAYLAND_wl_display_flush( ((SDL_VideoData*)_this->driverdata)->display );
194 
195     _this->egl_data->eglSwapInterval(_this->egl_data->egl_display, 0);  /* see comments on Wayland_GLES_SetSwapInterval. */
196 
197     return ret;
198 }
199 
200 void
Wayland_GLES_GetDrawableSize(_THIS,SDL_Window * window,int * w,int * h)201 Wayland_GLES_GetDrawableSize(_THIS, SDL_Window * window, int * w, int * h)
202 {
203     SDL_WindowData *data;
204     if (window->driverdata) {
205         data = (SDL_WindowData *) window->driverdata;
206 
207         if (w) {
208             *w = window->w * data->scale_factor;
209         }
210 
211         if (h) {
212             *h = window->h * data->scale_factor;
213         }
214     }
215 }
216 
217 void
Wayland_GLES_DeleteContext(_THIS,SDL_GLContext context)218 Wayland_GLES_DeleteContext(_THIS, SDL_GLContext context)
219 {
220     SDL_EGL_DeleteContext(_this, context);
221     WAYLAND_wl_display_flush( ((SDL_VideoData*)_this->driverdata)->display );
222 }
223 
224 #endif /* SDL_VIDEO_DRIVER_WAYLAND && SDL_VIDEO_OPENGL_EGL */
225 
226 /* vi: set ts=4 sw=4 expandtab: */
227