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