1 /*
2 This file is part of Warzone 2100.
3 Copyright (C) 2011-2019 Warzone 2100 Project
4
5 Warzone 2100 is free software; you can redistribute it and/or modify
6 it under the terms of the GNU General Public License as published by
7 the Free Software Foundation; either version 2 of the License, or
8 (at your option) any later version.
9
10 Warzone 2100 is distributed in the hope that it will be useful,
11 but WITHOUT ANY WARRANTY; without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 GNU General Public License for more details.
14
15 You should have received a copy of the GNU General Public License
16 along with Warzone 2100; if not, write to the Free Software
17 Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
18 */
19
20 #include "lib/framework/frame.h"
21 #include "gfx_api_gl_sdl.h"
22 #include <SDL_opengl.h>
23 #include <SDL_timer.h>
24 #include <SDL_hints.h>
25 #include <thread>
26 #include <chrono>
27
28 bool vsyncIsEnabled = true;
29
sdl_OpenGL_Impl(SDL_Window * _window,bool _useOpenGLES,bool _useOpenGLESLibrary)30 sdl_OpenGL_Impl::sdl_OpenGL_Impl(SDL_Window* _window, bool _useOpenGLES, bool _useOpenGLESLibrary)
31 {
32 ASSERT(_window != nullptr, "Invalid SDL_Window*");
33 window = _window;
34 useOpenglES = _useOpenGLES;
35 contextRequest = getInitialContextRequest(useOpenglES);
36 useOpenGLESLibrary = _useOpenGLESLibrary;
37 }
38
getGLGetProcAddress()39 GLADloadproc sdl_OpenGL_Impl::getGLGetProcAddress()
40 {
41 return SDL_GL_GetProcAddress;
42 }
43
setOpenGLESDriver(bool useOpenGLESLibrary)44 void sdl_OpenGL_Impl::setOpenGLESDriver(bool useOpenGLESLibrary)
45 {
46 if (useOpenGLESLibrary)
47 {
48 #if defined(SDL_HINT_OPENGL_ES_DRIVER)
49 SDL_SetHint(SDL_HINT_OPENGL_ES_DRIVER, "1");
50 #else
51 debug(LOG_WARNING, "SDL_HINT_OPENGL_ES_DRIVER is not available - may not use the OpenGL ES library");
52 #endif
53 }
54 else
55 {
56 #if defined(SDL_HINT_OPENGL_ES_DRIVER)
57 SDL_SetHint(SDL_HINT_OPENGL_ES_DRIVER, "0");
58 #endif
59 }
60 }
61
configureNextOpenGLContextRequest()62 bool sdl_OpenGL_Impl::configureNextOpenGLContextRequest()
63 {
64 contextRequest = GLContextRequests(contextRequest + 1);
65 return configureOpenGLContextRequest(contextRequest, useOpenGLESLibrary);
66 }
67
getInitialContextRequest(bool useOpenglES)68 sdl_OpenGL_Impl::GLContextRequests sdl_OpenGL_Impl::getInitialContextRequest(bool useOpenglES /*= false*/)
69 {
70 if (useOpenglES)
71 {
72 return OpenGLES30;
73 }
74 else
75 {
76 return OpenGLCore_HighestAvailable;
77 }
78 }
79
configureOpenGLContextRequest(GLContextRequests request,bool useOpenGLESLibrary)80 bool sdl_OpenGL_Impl::configureOpenGLContextRequest(GLContextRequests request, bool useOpenGLESLibrary)
81 {
82 switch (request)
83 {
84 case OpenGLCore_HighestAvailable:
85 // Request an OpenGL 3.0+ Core Profile context
86 // - On macOS, must request at least OpenGL 3.2 Core Profile (with FORWARD_COMPATIBLE_FLAG)
87 // to get the highest version OpenGL Core Profile context that's supported
88 // - Mesa allegedly requires a request for OpenGL 3.1+ Core Profile to get the highest version
89 // OpenGL Core Profile context that's supported, so use that as the default otherwise
90 // (Note: There is fallback handling inside sdl_OpenGL_Impl::createGLContext())
91
92 // SDL_GL_CONTEXT_FORWARD_COMPATIBLE_FLAG is *required* to obtain an OpenGL >= 3 Core Context on macOS
93 SDL_GL_SetAttribute(SDL_GL_CONTEXT_FLAGS, SDL_GL_CONTEXT_FORWARD_COMPATIBLE_FLAG);
94 SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_CORE);
95 SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 3);
96 # if defined(WZ_OS_MAC)
97 SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 2);
98 # else
99 SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 1);
100 # endif
101 return true;
102 case OpenGL21Compat:
103 SDL_GL_SetAttribute(SDL_GL_CONTEXT_FLAGS, 0);
104 SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_COMPATIBILITY);
105 SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 2);
106 SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 1);
107 return true;
108 # if !defined(WZ_OS_MAC)
109 case OpenGLES30:
110 setOpenGLESDriver(useOpenGLESLibrary);
111 SDL_GL_SetAttribute(SDL_GL_CONTEXT_FLAGS, 0);
112 SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_ES);
113 SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 3);
114 SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 0);
115 return true;
116 case OpenGLES20:
117 setOpenGLESDriver(useOpenGLESLibrary);
118 SDL_GL_SetAttribute(SDL_GL_CONTEXT_FLAGS, 0);
119 SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_ES);
120 SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 2);
121 SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 0);
122 return true;
123 # else
124 case OpenGLES30:
125 return false;
126 case OpenGLES20:
127 return false;
128 # endif
129 case MAX_CONTEXT_REQUESTS:
130 return false;
131 }
132 return false;
133 }
134
to_string(const GLContextRequests & request) const135 std::string sdl_OpenGL_Impl::to_string(const GLContextRequests& request) const\
136 {
137 switch (contextRequest)
138 {
139 case OpenGLCore_HighestAvailable:
140 return "OpenGL Core";
141 case OpenGL21Compat:
142 return "OpenGL 2.1 Compatibility";
143 case OpenGLES30:
144 return "OpenGL ES 3.0";
145 case OpenGLES20:
146 return "OpenGL ES 2.0";
147 case MAX_CONTEXT_REQUESTS:
148 return "";
149 }
150 return "";
151 }
152
isOpenGLES() const153 bool sdl_OpenGL_Impl::isOpenGLES() const
154 {
155 return contextRequest >= OpenGLES30;
156 }
157
createGLContext()158 bool sdl_OpenGL_Impl::createGLContext()
159 {
160 SDL_GLContext WZglcontext = SDL_GL_CreateContext(window);
161 std::string glContextErrors;
162 while (!WZglcontext)
163 {
164 glContextErrors += "Failed to create an " + to_string(contextRequest) + " context! [" + std::string(SDL_GetError()) + "]\n";
165 if (!configureNextOpenGLContextRequest())
166 {
167 // No more context requests to try
168 debug_multiline(LOG_ERROR, glContextErrors);
169 return false;
170 }
171 WZglcontext = SDL_GL_CreateContext(window);
172 }
173 if (!glContextErrors.empty())
174 {
175 // Although context creation eventually succeeded, log the attempts that failed
176 debug_multiline(LOG_3D, glContextErrors);
177 }
178 debug(LOG_3D, "Requested %s context", to_string(contextRequest).c_str());
179
180 int value = 0;
181 if (SDL_GL_GetAttribute(SDL_GL_DOUBLEBUFFER, &value) == 0)
182 {
183 if (value == 0)
184 {
185 debug(LOG_FATAL, "OpenGL initialization did not give double buffering! (%d)", value);
186 debug(LOG_FATAL, "Double buffering is required for this game!");
187 return false;
188 }
189 }
190 else
191 {
192 // SDL_GL_GetAttribute failed for SDL_GL_DOUBLEBUFFER
193 // For desktop OpenGL, treat this as a fatal error
194 code_part log_type = LOG_FATAL;
195 if (isOpenGLES())
196 {
197 // For OpenGL ES (EGL?), log this + let execution continue
198 //
199 // If SDL is compiled with Desktop OpenGL support, it may not properly
200 // query double buffering status for OpenGL ES contexts (as of: SDL 2.0.10)
201 log_type = LOG_3D;
202 }
203 debug(log_type, "SDL_GL_GetAttribute failed to get value for SDL_GL_DOUBLEBUFFER (%s)", SDL_GetError());
204 debug(log_type, "Double buffering is required for this game - if it isn't actually enabled, things will fail!");
205 if (log_type == LOG_FATAL)
206 {
207 return false;
208 }
209 }
210
211 int r, g, b, a;
212 SDL_GL_GetAttribute(SDL_GL_RED_SIZE, &r);
213 SDL_GL_GetAttribute(SDL_GL_GREEN_SIZE, &g);
214 SDL_GL_GetAttribute(SDL_GL_BLUE_SIZE, &b);
215 SDL_GL_GetAttribute(SDL_GL_ALPHA_SIZE, &a);
216 debug(LOG_3D, "Current values for: SDL_GL_RED_SIZE (%d), SDL_GL_GREEN_SIZE (%d), SDL_GL_BLUE_SIZE (%d), SDL_GL_ALPHA_SIZE (%d)", r, g, b, a);
217
218 if (SDL_GL_GetAttribute(SDL_GL_DEPTH_SIZE, &value) != 0)
219 {
220 debug(LOG_3D, "Failed to get value for SDL_GL_DEPTH_SIZE (%s)", SDL_GetError());
221 }
222 debug(LOG_3D, "Current value for SDL_GL_DEPTH_SIZE: (%d)", value);
223
224 if (SDL_GL_GetAttribute(SDL_GL_STENCIL_SIZE, &value) != 0)
225 {
226 debug(LOG_3D, "Failed to get value for SDL_GL_STENCIL_SIZE (%s)", SDL_GetError());
227 }
228 debug(LOG_3D, "Current value for SDL_GL_STENCIL_SIZE: (%d)", value);
229
230 int windowWidth, windowHeight = 0;
231 SDL_GetWindowSize(window, &windowWidth, &windowHeight);
232 debug(LOG_WZ, "Logical Window Size: %d x %d", windowWidth, windowHeight);
233
234 return true;
235 }
236
swapWindow()237 void sdl_OpenGL_Impl::swapWindow()
238 {
239 #if defined(WZ_OS_MAC)
240 // Workaround for OpenGL on macOS (see below)
241 const uint32_t swapStartTime = SDL_GetTicks();
242 #endif
243
244 SDL_GL_SwapWindow(window);
245
246 #if defined(WZ_OS_MAC)
247 // Workaround for OpenGL on macOS
248 // - If the OpenGL window is minimized (or occluded), SwapWindow may not wait for the vertical blanking interval
249 // - To workaround this, detect when we seem to be spinning without any wait, and sleep for a bit
250 static uint32_t numFramesNoVsync = 0;
251 static uint32_t lastSwapEndTime = 0;
252 const bool isMinimized = static_cast<bool>(SDL_GetWindowFlags(window) & SDL_WINDOW_MINIMIZED);
253 const uint32_t minFrameInterval = 1000 / ((isMinimized) ? 60 : 120);
254 const uint32_t minSwapEndTick = swapStartTime + 2;
255 uint32_t swapEndTime = SDL_GetTicks();
256 const uint32_t frameTime = swapEndTime - lastSwapEndTime;
257 if ((vsyncIsEnabled || isMinimized) && !SDL_TICKS_PASSED(swapEndTime, minSwapEndTick) && (frameTime < minFrameInterval))
258 {
259 const uint32_t leewayFramesBeforeThrottling = (isMinimized) ? 2 : 4;
260 if (leewayFramesBeforeThrottling < numFramesNoVsync)
261 {
262 std::this_thread::sleep_for(std::chrono::milliseconds(minFrameInterval - frameTime));
263 swapEndTime = SDL_GetTicks();
264 }
265 else
266 {
267 ++numFramesNoVsync;
268 }
269 }
270 else if (0 < numFramesNoVsync)
271 {
272 --numFramesNoVsync;
273 }
274 lastSwapEndTime = swapEndTime;
275 #endif
276 }
277
getDrawableSize(int * w,int * h)278 void sdl_OpenGL_Impl::getDrawableSize(int* w, int* h)
279 {
280 SDL_GL_GetDrawableSize(window, w, h);
281 }
282
to_sdl_swap_interval(gfx_api::context::swap_interval_mode mode,int & output_value)283 bool to_sdl_swap_interval(gfx_api::context::swap_interval_mode mode, int &output_value)
284 {
285 switch (mode)
286 {
287 case gfx_api::context::swap_interval_mode::immediate:
288 output_value = 0;
289 return true;
290 case gfx_api::context::swap_interval_mode::vsync:
291 output_value = 1;
292 return true;
293 case gfx_api::context::swap_interval_mode::adaptive_vsync:
294 output_value = -1;
295 return true;
296 default:
297 // unsupported swap_interval_mode
298 return false;
299 }
300 return false;
301 }
302
from_sdl_swap_interval(int swapInterval)303 gfx_api::context::swap_interval_mode from_sdl_swap_interval(int swapInterval)
304 {
305 switch (swapInterval)
306 {
307 case 0:
308 return gfx_api::context::swap_interval_mode::immediate;
309 case 1:
310 return gfx_api::context::swap_interval_mode::vsync;
311 case -1:
312 return gfx_api::context::swap_interval_mode::adaptive_vsync;
313 }
314 return gfx_api::context::swap_interval_mode::vsync;
315 }
316
setSwapInterval(gfx_api::context::swap_interval_mode mode)317 bool sdl_OpenGL_Impl::setSwapInterval(gfx_api::context::swap_interval_mode mode)
318 {
319 int interval = 1;
320 if (!to_sdl_swap_interval(mode, interval))
321 {
322 debug(LOG_3D, "Unsupported swap_interval_mode for SDL OpenGL backend: %d", to_int(mode));
323 return false;
324 }
325 if (SDL_GL_SetSwapInterval(interval) != 0)
326 {
327 debug(LOG_WARNING, "SDL_GL_SetSwapInterval(%d) failed with error (%s)", interval, SDL_GetError());
328 return false;
329 }
330 vsyncIsEnabled = interval != 0;
331 return true;
332 }
333
getSwapInterval() const334 gfx_api::context::swap_interval_mode sdl_OpenGL_Impl::getSwapInterval() const
335 {
336 int interval = SDL_GL_GetSwapInterval();
337 return from_sdl_swap_interval(interval);
338 }
339
340