1 /*
2  * Copyright (C) 2006-2020 by the Widelands Development Team
3  *
4  * This program is free software; you can redistribute it and/or
5  * modify it under the terms of the GNU General Public License
6  * as published by the Free Software Foundation; either version 2
7  * of the License, or (at your option) any later version.
8  *
9  * This program is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12  * GNU General Public License for more details.
13  *
14  * You should have received a copy of the GNU General Public License
15  * along with this program; if not, write to the Free Software
16  * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
17  *
18  */
19 
20 #include "graphic/gl/initialize.h"
21 
22 #include <csignal>
23 
24 #include <SDL_messagebox.h>
25 #include <boost/algorithm/string.hpp>
26 #include <boost/regex.hpp>
27 
28 #include "base/i18n.h"
29 #include "base/macros.h"
30 #include "graphic/gl/utils.h"
31 #include "graphic/text/bidi.h"
32 
33 namespace Gl {
34 
initialize(const Trace & trace,SDL_Window * sdl_window,GLint * max_texture_size)35 SDL_GLContext initialize(
36 #ifdef USE_GLBINDING
37    const Trace& trace,
38 #else
39    const Trace&,
40 #endif
41    SDL_Window* sdl_window,
42    GLint* max_texture_size) {
43 	// Request an OpenGL 2 context with double buffering.
44 	SDL_GL_SetAttribute(SDL_GL_ACCELERATED_VISUAL, 1);
45 	SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 2);
46 	SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 1);
47 	SDL_GL_SetAttribute(SDL_GL_DEPTH_SIZE, 16);
48 	SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, 1);
49 
50 	SDL_GLContext gl_context = SDL_GL_CreateContext(sdl_window);
51 	SDL_GL_MakeCurrent(sdl_window, gl_context);
52 
53 #ifdef USE_GLBINDING
54 #ifndef GLBINDING3
55 	glbinding::Binding::initialize();
56 
57 	// The undocumented command line argument --debug_gl_trace will set
58 	// Trace::kYes. This will log every OpenGL call that is made, together with
59 	// arguments, return values and glError status. This requires that Widelands
60 	// is built using -DOPTION_USE_GLBINDING:BOOL=ON. It is a NoOp for GLEW.
61 	if (trace == Trace::kYes) {
62 		setCallbackMaskExcept(
63 		   glbinding::CallbackMask::After | glbinding::CallbackMask::ParametersAndReturnValue,
64 		   {"glGetError"});
65 		glbinding::setAfterCallback([](const glbinding::FunctionCall& call) {
66 			log("%s(", call.function->name());
67 			for (size_t i = 0; i < call.parameters.size(); ++i) {
68 				log("%s", call.parameters[i]->asString().c_str());
69 				if (i < call.parameters.size() - 1)
70 					log(", ");
71 			}
72 			log(")");
73 			if (call.returnValue) {
74 				log(" -> %s", call.returnValue->asString().c_str());
75 			}
76 			const auto error = glGetError();
77 			log(" [%s]\n", gl_error_to_string(error));
78 			// The next few lines will terminate Widelands if there was any OpenGL
79 			// error. This is useful for super aggressive debugging, but probably
80 			// not for regular builds. Comment it in if you need to understand
81 			// OpenGL problems.
82 			// if (error != GL_NO_ERROR) {
83 			// std::raise(SIGINT);
84 			// }
85 		});
86 	}
87 #else
88 	const glbinding::GetProcAddress get_proc_address = [](const char* name) {
89 		return reinterpret_cast<glbinding::ProcAddress>(SDL_GL_GetProcAddress(name));
90 	};
91 	glbinding::Binding::initialize(get_proc_address, true);
92 
93 	// The undocumented command line argument --debug_gl_trace will set
94 	// Trace::kYes. This will log every OpenGL call that is made, together with
95 	// arguments, return values and glError status. This requires that Widelands
96 	// is built using -DOPTION_USE_GLBINDING:BOOL=ON. It is a NoOp for GLEW.
97 	if (trace == Trace::kYes) {
98 		glbinding::setCallbackMaskExcept(
99 		   glbinding::CallbackMask::After | glbinding::CallbackMask::ParametersAndReturnValue,
100 		   {"glGetError"});
101 		glbinding::setAfterCallback([](const glbinding::FunctionCall& call) {
102 			log("%s(", call.function->name());
103 			for (size_t i = 0; i < call.parameters.size(); ++i) {
104 				FORMAT_WARNINGS_OFF
105 				log("%p", call.parameters[i].get());
106 				FORMAT_WARNINGS_ON
107 				if (i < call.parameters.size() - 1)
108 					log(", ");
109 			}
110 			log(")");
111 			if (call.returnValue) {
112 				FORMAT_WARNINGS_OFF
113 				log(" -> %p", call.returnValue.get());
114 				FORMAT_WARNINGS_ON
115 			}
116 			const auto error = glGetError();
117 			log(" [%s]\n", gl_error_to_string(error));
118 			// The next few lines will terminate Widelands if there was any OpenGL
119 			// error. This is useful for super aggressive debugging, but probably
120 			// not for regular builds. Comment it in if you need to understand
121 			// OpenGL problems.
122 			// if (error != GL_NO_ERROR) {
123 			// std::raise(SIGINT);
124 			// }
125 		});
126 	}
127 #endif
128 #else
129 	// See graphic/gl/system_headers.h for an explanation of the next line.
130 	glewExperimental = GL_TRUE;
131 	GLenum err = glewInit();
132 	// LeakSanitizer reports a memory leak which is triggered somewhere above this line, probably
133 	// coming from the gaphics drivers
134 
135 	if (err != GLEW_OK) {
136 		log("glewInit returns %i\nYour OpenGL installation must be __very__ broken. %s\n", err,
137 		    glewGetErrorString(err));
138 		throw wexception("glewInit returns %i: Broken OpenGL installation.", err);
139 	}
140 #endif
141 
142 	log(
143 	   "Graphics: OpenGL: Version \"%s\"\n", reinterpret_cast<const char*>(glGetString(GL_VERSION)));
144 
145 #define LOG_SDL_GL_ATTRIBUTE(x)                                                                    \
146 	{                                                                                               \
147 		int value;                                                                                   \
148 		SDL_GL_GetAttribute(x, &value);                                                              \
149 		log("Graphics: %s is %d\n", #x, value);                                                      \
150 	}
151 
152 	LOG_SDL_GL_ATTRIBUTE(SDL_GL_RED_SIZE)
153 	LOG_SDL_GL_ATTRIBUTE(SDL_GL_GREEN_SIZE)
154 	LOG_SDL_GL_ATTRIBUTE(SDL_GL_BLUE_SIZE)
155 	LOG_SDL_GL_ATTRIBUTE(SDL_GL_ALPHA_SIZE)
156 	LOG_SDL_GL_ATTRIBUTE(SDL_GL_BUFFER_SIZE)
157 	LOG_SDL_GL_ATTRIBUTE(SDL_GL_DOUBLEBUFFER)
158 	LOG_SDL_GL_ATTRIBUTE(SDL_GL_DEPTH_SIZE)
159 	LOG_SDL_GL_ATTRIBUTE(SDL_GL_STENCIL_SIZE)
160 	LOG_SDL_GL_ATTRIBUTE(SDL_GL_ACCUM_RED_SIZE)
161 	LOG_SDL_GL_ATTRIBUTE(SDL_GL_ACCUM_GREEN_SIZE)
162 	LOG_SDL_GL_ATTRIBUTE(SDL_GL_ACCUM_BLUE_SIZE)
163 	LOG_SDL_GL_ATTRIBUTE(SDL_GL_ACCUM_ALPHA_SIZE)
164 	LOG_SDL_GL_ATTRIBUTE(SDL_GL_STEREO)
165 	LOG_SDL_GL_ATTRIBUTE(SDL_GL_MULTISAMPLEBUFFERS)
166 	LOG_SDL_GL_ATTRIBUTE(SDL_GL_MULTISAMPLESAMPLES)
167 	LOG_SDL_GL_ATTRIBUTE(SDL_GL_ACCELERATED_VISUAL)
168 	LOG_SDL_GL_ATTRIBUTE(SDL_GL_CONTEXT_MAJOR_VERSION)
169 	LOG_SDL_GL_ATTRIBUTE(SDL_GL_CONTEXT_MINOR_VERSION)
170 	LOG_SDL_GL_ATTRIBUTE(SDL_GL_CONTEXT_FLAGS)
171 	LOG_SDL_GL_ATTRIBUTE(SDL_GL_CONTEXT_PROFILE_MASK)
172 	LOG_SDL_GL_ATTRIBUTE(SDL_GL_SHARE_WITH_CURRENT_CONTEXT)
173 	LOG_SDL_GL_ATTRIBUTE(SDL_GL_FRAMEBUFFER_SRGB_CAPABLE)
174 #undef LOG_SDL_GL_ATTRIBUTE
175 
176 	GLboolean glBool;
177 	glGetBooleanv(GL_DOUBLEBUFFER, &glBool);
178 	log("Graphics: OpenGL: Double buffering %s\n", (glBool == GL_TRUE) ? "enabled" : "disabled");
179 
180 	glGetIntegerv(GL_MAX_TEXTURE_SIZE, max_texture_size);
181 	log("Graphics: OpenGL: Max texture size: %u\n", *max_texture_size);
182 
183 	const char* const shading_language_version_string =
184 	   reinterpret_cast<const char*>(glGetString(GL_SHADING_LANGUAGE_VERSION));
185 	log("Graphics: OpenGL: ShadingLanguage: \"%s\"\n", shading_language_version_string);
186 
187 	// Show a basic SDL window with an error message, and log it too, then exit 1. Since font support
188 	// does not exist for all languages, we show both the original and a localized text.
189 	auto show_opengl_error_and_exit = [](
190 	   const std::string& message, const std::string& localized_message) {
191 		std::string display_message = "";
192 		if (message != localized_message) {
193 			display_message =
194 			   message + "\n\n" +
195 			   (i18n::has_rtl_character(localized_message.c_str()) ?
196 			       i18n::line2bidi(i18n::make_ligatures(localized_message.c_str()).c_str()) :
197 			       localized_message);
198 		} else {
199 			display_message = message;
200 		}
201 
202 		/** TRANSLATORS: Error message printed to console/command line/log file */
203 		log(_("ERROR: %s\n"), display_message.c_str());
204 		SDL_ShowSimpleMessageBox(SDL_MESSAGEBOX_ERROR, "OpenGL Error", display_message.c_str(), NULL);
205 		exit(1);
206 	};
207 
208 	// Exit because we couldn't detect the shading language version, so there must be a problem
209 	// communicating with the graphics adapter.
210 	auto handle_unreadable_opengl_shading_language = [show_opengl_error_and_exit]() {
211 		show_opengl_error_and_exit(
212 		   "Widelands won't work because we were unable to detect the shading language version.\n"
213 		   "There is an unknown problem with reading the information from the graphics driver.",
214 		   (boost::format("%s\n%s") %
215 		    /** TRANSLATORS: Basic error message when we can't handle the graphics driver. Font
216 		       support is limited here, so do not use advanced typography **/
217 		    _("Widelands won't work because we were unable to detect the shading language "
218 		      "version.") %
219 		    /** TRANSLATORS: Basic error message when we can't handle the graphics driver. Font
220 		       support is limited here, so do not use advanced typography **/
221 		    _("There is an unknown problem with reading the information from the graphics "
222 		      "driver."))
223 		      .str());
224 	};
225 
226 	// glGetString returned an error for the shading language
227 	if (glGetString(GL_SHADING_LANGUAGE_VERSION) == 0) {
228 		handle_unreadable_opengl_shading_language();
229 	}
230 
231 	std::vector<std::string> shading_language_version_vector;
232 	boost::split(
233 	   shading_language_version_vector, shading_language_version_string, boost::is_any_of("."));
234 	if (shading_language_version_vector.size() >= 2) {
235 		// The shading language version has been detected properly. Exit if the shading language
236 		// version is too old.
237 		const int major_shading_language_version =
238 		   atoi(shading_language_version_vector.front().c_str());
239 		const int minor_shading_language_version =
240 		   atoi(shading_language_version_vector.at(1).c_str());
241 		if (major_shading_language_version < 1 ||
242 		    (major_shading_language_version == 1 && minor_shading_language_version < 20)) {
243 			show_opengl_error_and_exit(
244 			   "Widelands won’t work because your graphics driver is too old.\n"
245 			   "The shading language needs to be version 1.20 or newer.",
246 			   (boost::format("%s\n%s") %
247 			    /** TRANSLATORS: Basic error message when we can't handle the graphics driver. Font
248 			       support is limited here, so do not use advanced typography **/
249 			    _("Widelands won’t work because your graphics driver is too old.") %
250 			    /** TRANSLATORS: Basic error message when we can't handle the graphics driver. Font
251 			       support is limited here, so do not use advanced typography **/
252 			    _("The shading language needs to be version 1.20 or newer."))
253 			      .str());
254 		}
255 	} else {
256 		// We don't have a minor version. Ensure that the string to compare is a valid integer before
257 		// conversion
258 		boost::regex re("\\d+");
259 		if (boost::regex_match(shading_language_version_string, re)) {
260 			const int major_shading_language_version = atoi(shading_language_version_string);
261 			if (major_shading_language_version < 2) {
262 				show_opengl_error_and_exit(
263 				   "Widelands won’t work because your graphics driver is too old.\n"
264 				   "The shading language needs to be version 1.20 or newer.",
265 				   (boost::format("%s\n%s") %
266 				    /** TRANSLATORS: Basic error message when we can't handle the graphics driver. Font
267 				       support is limited here, so do not use advanced typography **/
268 				    _("Widelands won’t work because your graphics driver is too old.") %
269 				    /** TRANSLATORS: Basic error message when we can't handle the graphics driver. Font
270 				       support is limited here, so do not use advanced typography **/
271 				    _("The shading language needs to be version 1.20 or newer."))
272 				      .str());
273 			}
274 		} else {
275 			// We don't know how to interpret the shading language info
276 			handle_unreadable_opengl_shading_language();
277 		}
278 	}
279 
280 	glDrawBuffer(GL_BACK);
281 
282 	glDisable(GL_DEPTH_TEST);
283 	glDepthFunc(GL_LEQUAL);
284 
285 	glEnable(GL_BLEND);
286 	glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
287 
288 	glClear(GL_COLOR_BUFFER_BIT);
289 
290 	return gl_context;
291 }
292 
293 }  // namespace Gl
294