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