1 /*
2  Copyright (c) 2013 yvt
3  Portion of the code is based on Serverbrowser.cpp.
4 
5  This file is part of OpenSpades.
6 
7  OpenSpades is free software: you can redistribute it and/or modify
8  it under the terms of the GNU General Public License as published by
9  the Free Software Foundation, either version 3 of the License, or
10  (at your option) any later version.
11 
12  OpenSpades is distributed in the hope that it will be useful,
13  but WITHOUT ANY WARRANTY; without even the implied warranty of
14  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15  GNU General Public License for more details.
16 
17  You should have received a copy of the GNU General Public License
18  along with OpenSpades.  If not, see <http://www.gnu.org/licenses/>.
19 
20  */
21 
22 #include <algorithm>
23 #include <cctype>
24 #include <utility>
25 #include <regex>
26 
27 #include <Imports/OpenGL.h> //for gpu info
28 #include <Imports/SDL.h>
29 #include <json/json.h>
30 
31 #include "StartupScreenHelper.h"
32 
33 #include "StartupScreen.h"
34 #include <Audio/ALDevice.h>
35 #include <Audio/YsrDevice.h>
36 #include <Core/FileManager.h>
37 #include <Core/Settings.h>
38 #include <OpenSpades.h>
39 #include <Core/ShellApi.h>
40 #include <Gui/Main.h>
41 #include <Gui/Icon.h>
42 #include <Gui/PackageUpdateManager.h>
43 
44 SPADES_SETTING(r_bloom);
45 SPADES_SETTING(r_lens);
46 SPADES_SETTING(r_cameraBlur);
47 SPADES_SETTING(r_softParticles);
48 SPADES_SETTING(r_mapSoftShadow);
49 SPADES_SETTING(r_modelShadows);
50 SPADES_SETTING(r_radiosity);
51 SPADES_SETTING(r_dlights);
52 SPADES_SETTING(r_water);
53 SPADES_SETTING(r_multisamples);
54 SPADES_SETTING(r_fxaa);
55 SPADES_SETTING(r_videoWidth);
56 SPADES_SETTING(r_videoHeight);
57 SPADES_SETTING(r_fullscreen);
58 SPADES_SETTING(r_fogShadow);
59 SPADES_SETTING(r_lensFlare);
60 SPADES_SETTING(r_lensFlareDynamic);
61 SPADES_SETTING(r_blitFramebuffer);
62 SPADES_SETTING(r_srgb);
63 SPADES_SETTING(r_shadowMapSize);
64 SPADES_SETTING(s_maxPolyphonics);
65 SPADES_SETTING(s_eax);
66 SPADES_SETTING(r_maxAnisotropy);
67 SPADES_SETTING(r_colorCorrection);
68 SPADES_SETTING(r_physicalLighting);
69 SPADES_SETTING(r_occlusionQuery);
70 SPADES_SETTING(r_depthOfField);
71 SPADES_SETTING(r_vsync);
72 SPADES_SETTING(r_renderer);
73 SPADES_SETTING(r_swUndersampling);
74 SPADES_SETTING(r_hdr);
75 
76 namespace spades {
77 	namespace gui {
78 
StartupScreenHelper()79 		StartupScreenHelper::StartupScreenHelper()
80 		    : scr(nullptr),
81 		      shaderHighCapable(false),
82 		      postFilterHighCapable(false),
83 		      particleHighCapable(false) {
84 			SPADES_MARK_FUNCTION();
85 		}
86 
~StartupScreenHelper()87 		StartupScreenHelper::~StartupScreenHelper() { SPADES_MARK_FUNCTION(); }
88 
StartupScreenDestroyed()89 		void StartupScreenHelper::StartupScreenDestroyed() {
90 			SPADES_MARK_FUNCTION();
91 			scr = nullptr;
92 		}
93 
ExamineSystem()94 		void StartupScreenHelper::ExamineSystem() {
95 			SPADES_MARK_FUNCTION();
96 
97 			// clear capability report
98 			// (this function can be called multiple times via StartupScreenHelper::FixConfig)
99 			reportLines.clear();
100 			report.clear();
101 
102 			// check installed locales
103 			SPLog("Checking installed locales");
104 
105 			auto localeDirectories = FileManager::EnumFiles("Locales");
106 			locales.clear();
107 			for (const std::string &localeInfoName : localeDirectories) {
108 				static std::regex localeInfoRegex("[-a-zA-Z0-9_]+\\.json");
109 				if (!std::regex_match(localeInfoName, localeInfoRegex)) {
110 					continue;
111 				}
112 				std::string locale = localeInfoName.substr(0, localeInfoName.size() - 5);
113 				std::string localeInfoPath = "Locales/" + localeInfoName;
114 
115 				try {
116 					std::string buffer = FileManager::ReadAllBytes(localeInfoPath.c_str());
117 					LocaleInfo info;
118 					Json::Reader reader;
119 					Json::Value root;
120 					if (!reader.parse(buffer.c_str(), root, false)) {
121 						SPRaise("Failed to parse LocaleInfo.json: %s",
122 						        reader.getFormatedErrorMessages().c_str());
123 					}
124 
125 					info.name = locale;
126 					info.descriptionEnglish = root["descriptionEnglish"].asString();
127 					info.descriptionNative = root["description"].asString();
128 
129 					locales.push_back(std::move(info));
130 
131 					SPLog("Locale '%s' found.", locale.c_str());
132 				} catch (const std::exception &ex) {
133 					SPLog("Error while reading the locale info for '%s': %s", locale.c_str(),
134 					      ex.what());
135 				}
136 			}
137 
138 			// check audio device availability
139 			// Note: this only checks whether these libraries can be loaded.
140 
141 			SPLog("Checking YSR availability");
142 			if (!audio::YsrDevice::TryLoadYsr()) {
143 				incapableConfigs.insert(
144 				  std::make_pair("s_audioDriver", [](std::string value) -> std::string {
145 					  if (EqualsIgnoringCase(value, "ysr")) {
146 						  return "YSR library couldn't be loaded.";
147 					  } else {
148 						  return std::string();
149 					  }
150 				  }));
151 			}
152 
153 			SPLog("Checking OpenAL availability");
154 			if (!audio::ALDevice::TryLoad()) {
155 				incapableConfigs.insert(
156 				  std::make_pair("s_audioDriver", [](std::string value) -> std::string {
157 					  if (EqualsIgnoringCase(value, "openal")) {
158 						  return "OpenAL library couldn't be loaded.";
159 					  } else {
160 						  return std::string();
161 					  }
162 				  }));
163 			}
164 
165 			// check GL capabilities
166 
167 			SPLog("Performing ecapability query");
168 
169 			int idDisplay = 0;
170 
171 			int numDisplayMode = SDL_GetNumDisplayModes(idDisplay);
172 			SDL_DisplayMode mode;
173 			modes.clear();
174 			if (numDisplayMode > 0) {
175 				std::set<std::pair<int, int>> foundModes;
176 				for (int i = 0; i < numDisplayMode; i++) {
177 					SDL_GetDisplayMode(idDisplay, i, &mode);
178 					if (mode.w < 800 || mode.h < 600)
179 						continue;
180 					if (foundModes.find(std::make_pair(mode.w, mode.h)) != foundModes.end())
181 						continue;
182 
183 					foundModes.insert(std::make_pair(mode.w, mode.h));
184 					modes.push_back(spades::IntVector3::Make(mode.w, mode.h, 0));
185 					SPLog("Video Mode Found: %dx%d", mode.w, mode.h);
186 				}
187 			} else {
188 				SPLog("Failed to get video mode list. Presetting default list");
189 				modes.push_back(spades::IntVector3::Make(800, 600, 0));
190 				modes.push_back(spades::IntVector3::Make(1024, 768, 0));
191 				modes.push_back(spades::IntVector3::Make(1280, 720, 0));
192 				modes.push_back(spades::IntVector3::Make(1920, 1080, 0));
193 			}
194 
195 			bool capable = true;
196 			SDL_Window *window = SDL_CreateWindow("OpenSpades: Please wait...", 1, 1, 1, 1,
197 			                                      SDL_WINDOW_OPENGL | SDL_WINDOW_BORDERLESS);
198 			if (window == nullptr) {
199 				SPLog("Failed to create SDL window: %s", SDL_GetError());
200 			}
201 			SDL_GLContext context = window ? SDL_GL_CreateContext(window) : nullptr;
202 			if (window != nullptr && context == nullptr) {
203 				SPLog("Failed to create OpenGL context: %s", SDL_GetError());
204 			}
205 
206 #ifdef __APPLE__
207 #elif __unix
208 			SDL_Surface *icon = nullptr;
209 			SDL_RWops *icon_rw = nullptr;
210 			icon_rw = SDL_RWFromConstMem(g_appIconData, GetAppIconDataSize());
211 			if (icon_rw != nullptr) {
212 				icon = IMG_LoadPNG_RW(icon_rw);
213 				SDL_FreeRW(icon_rw);
214 			}
215 			if (icon == nullptr) {
216 				std::string msg = SDL_GetError();
217 				SPLog("Failed to load icon: %s", msg.c_str());
218 			} else {
219 				SDL_SetWindowIcon(window, icon);
220 				SDL_FreeSurface(icon);
221 			}
222 #endif
223 
224 			if (!context) {
225 				// OpenGL initialization failed!
226 
227 				std::string err = SDL_GetError();
228 				SPLog("SDL_SetVideoMode failed: %s", err.c_str());
229 
230 				AddReport("OpenGL-capable graphics accelerator is unavailable.",
231 				          MakeVector4(1.f, 0.5f, 0.5f, 1.f));
232 
233 				AddReport();
234 
235 				AddReport("OpenGL/SDL couldn't be initialized.");
236 				AddReport("Falling back to the software renderer.");
237 
238 				AddReport();
239 				AddReport("Message from SDL:", MakeVector4(1.f, 1.f, 1.f, 0.7f));
240 				AddReport(err, MakeVector4(1.f, 1.f, 1.f, 0.7f));
241 
242 				capable = false;
243 				shaderHighCapable = false;
244 				postFilterHighCapable = false;
245 				particleHighCapable = false;
246 			} else {
247 
248 				SDL_GL_MakeCurrent(window, context);
249 
250 				shaderHighCapable = true;
251 				postFilterHighCapable = true;
252 				particleHighCapable = true;
253 
254 				const char *str;
255 				GLint maxTextureSize;
256 				GLint max3DTextureSize;
257 				GLint maxCombinedTextureUnits;
258 				GLint maxVertexTextureUnits;
259 				GLint maxVaryingComponents;
260 				SPLog("--- OpenGL Renderer Info ---");
261 
262 				AddReport("OpenGL-capable graphics accelerator is available.");
263 
264 				if ((str = (const char *)glGetString(GL_VENDOR)) != NULL) {
265 					SPLog("Vendor: %s", str);
266 					AddReport(std::string("Vendor: ") + str, MakeVector4(1.f, 1.f, 1.f, 0.7f));
267 				}
268 				if ((str = (const char *)glGetString(GL_RENDERER)) != NULL) {
269 					SPLog("Name: %s", str);
270 					AddReport(std::string("Name: ") + str, MakeVector4(1.f, 1.f, 1.f, 0.7f));
271 				}
272 				if ((str = (const char *)glGetString(GL_VERSION)) != NULL) {
273 					AddReport(std::string("Version: ") + str, MakeVector4(1.f, 1.f, 1.f, 0.7f));
274 					SPLog("Version: %s", str);
275 				}
276 				if ((str = (const char *)glGetString(GL_SHADING_LANGUAGE_VERSION)) != NULL) {
277 					AddReport(std::string("GLSL Version: ") + str,
278 					          MakeVector4(1.f, 1.f, 1.f, 0.7f));
279 					SPLog("Shading Language Version: %s", str);
280 				}
281 
282 				AddReport();
283 
284 				maxTextureSize = 0;
285 				glGetIntegerv(GL_MAX_TEXTURE_SIZE, &maxTextureSize);
286 				if (maxTextureSize > 0) {
287 					SPLog("Max Texture Size: %d", (int)maxTextureSize);
288 				}
289 				max3DTextureSize = 0;
290 				glGetIntegerv(GL_MAX_3D_TEXTURE_SIZE, &max3DTextureSize);
291 				if (max3DTextureSize > 0) {
292 					SPLog("Max 3D Texture Size: %d", (int)max3DTextureSize);
293 				}
294 
295 				maxCombinedTextureUnits = 0;
296 				glGetIntegerv(GL_MAX_COMBINED_TEXTURE_IMAGE_UNITS, &maxCombinedTextureUnits);
297 				if (maxCombinedTextureUnits > 0) {
298 					SPLog("Max Combined Texture Image Units: %d", (int)maxCombinedTextureUnits);
299 				}
300 
301 				maxVertexTextureUnits = 0;
302 				glGetIntegerv(GL_MAX_VERTEX_TEXTURE_IMAGE_UNITS, &maxVertexTextureUnits);
303 				if (maxVertexTextureUnits > 0) {
304 					SPLog("Max Vertex Texture Image Units: %d", (int)maxVertexTextureUnits);
305 				}
306 
307 				maxVaryingComponents = 0;
308 				glGetIntegerv(GL_MAX_VARYING_COMPONENTS, &maxVaryingComponents);
309 				if (maxVaryingComponents > 0) {
310 					SPLog("Max Varying Components: %d", (int)maxVaryingComponents);
311 				}
312 
313 				str = (const char *)glGetString(GL_EXTENSIONS);
314 				std::string extensions;
315 				if (str)
316 					extensions = str;
317 				const char *const requiredExtensions[] = {"GL_ARB_multitexture",
318 				                                          "GL_ARB_shader_objects",
319 				                                          "GL_ARB_shading_language_100",
320 				                                          "GL_ARB_texture_non_power_of_two",
321 				                                          "GL_ARB_vertex_buffer_object",
322 				                                          "GL_EXT_framebuffer_object",
323 				                                          NULL};
324 
325 				SPLog("--- Extensions ---");
326 				std::vector<std::string> strs = spades::Split(str, " ");
327 				for (size_t i = 0; i < strs.size(); i++) {
328 					SPLog("%s", strs[i].c_str());
329 				}
330 				SPLog("------------------");
331 
332 				for (size_t i = 0; requiredExtensions[i]; i++) {
333 					const char *ex = requiredExtensions[i];
334 					if (extensions.find(ex) == std::string::npos) {
335 						// extension not found
336 						AddReport(std::string(ex) + " is NOT SUPPORTED!",
337 						          MakeVector4(1.f, 0.5f, 0.5f, 1.f));
338 						capable = false;
339 					}
340 				}
341 
342 				// non-requred extensions
343 				if (extensions.find("GL_ARB_framebuffer_sRGB") == std::string::npos) {
344 					if (r_srgb) {
345 						r_srgb = 0;
346 						SPLog("Disabling r_srgb: no GL_ARB_framebuffer_sRGB");
347 					}
348 
349 					incapableConfigs.insert(
350 					  std::make_pair("r_srgb", [](std::string value) -> std::string {
351 						  if (std::stoi(value) != 0) {
352 							  return "SRGB framebuffer is disabled because your video card doesn't "
353 							         "support GL_ARB_framebuffer_sRGB.";
354 						  } else {
355 							  return std::string();
356 						  }
357 					  }));
358 
359 					AddReport("GL_ARB_framebuffer_sRGB is NOT SUPPORTED",
360 					          MakeVector4(1.f, 1.f, 0.5f, 1.f));
361 					AddReport("  r_srgb is disabled.", MakeVector4(1.f, 1.f, 1.f, 0.7f));
362 				}
363 				if (extensions.find("GL_EXT_framebuffer_blit") == std::string::npos) {
364 					if (r_blitFramebuffer) {
365 						r_blitFramebuffer = 0;
366 						SPLog("Disabling r_blitFramebuffer: no GL_EXT_framebuffer_blit");
367 					}
368 					if (r_multisamples) {
369 						r_multisamples = 0;
370 						SPLog("Disabling r_multisamples: no GL_EXT_framebuffer_blit");
371 					}
372 					incapableConfigs.insert(
373 					  std::make_pair("r_blitFramebuffer", [](std::string value) -> std::string {
374 						  if (std::stoi(value) != 0) {
375 							  return "r_blitFramebuffer is disabled because your video card "
376 							         "doesn't support GL_EXT_framebuffer_blit.";
377 						  } else {
378 							  return std::string();
379 						  }
380 					  }));
381 
382 					AddReport("GL_EXT_framebuffer_blit is NOT SUPPORTED",
383 					          MakeVector4(1.f, 1.f, 0.5f, 1.f));
384 					AddReport("  r_blitFramebuffer is disabled.", MakeVector4(1.f, 1.f, 1.f, 0.7f));
385 				}
386 				if (extensions.find("GL_EXT_texture_filter_anisotropic") == std::string::npos) {
387 					if ((float)r_maxAnisotropy > 1.1f) {
388 						r_maxAnisotropy = 1;
389 						SPLog("Setting r_maxAnisotropy to 1: no GL_EXT_texture_filter_anisotropic");
390 					}
391 
392 					incapableConfigs.insert(
393 					  std::make_pair("r_maxAnisotropy", [](std::string value) -> std::string {
394 						  if (std::stof(value) > 1.001f) {
395 							  return "Anisotropic texture filtering is disabled because your video "
396 							         "card doesn't support GL_EXT_texture_filter_anisotropic.";
397 						  } else {
398 							  return std::string();
399 						  }
400 					  }));
401 					AddReport("GL_EXT_texture_filter_anisotropic is NOT SUPPORTED",
402 					          MakeVector4(1.f, 1.f, 0.5f, 1.f));
403 					AddReport("  r_maxAnisotropy is disabled.", MakeVector4(1.f, 1.f, 1.f, 0.7f));
404 				}
405 
406 				if (extensions.find("GL_ARB_occlusion_query") == std::string::npos) {
407 					if (r_occlusionQuery) {
408 						r_occlusionQuery = 0;
409 						SPLog("Disabling r_occlusionQuery: no GL_ARB_occlusion_query");
410 					}
411 					incapableConfigs.insert(
412 					  std::make_pair("r_occlusionQuery", [](std::string value) -> std::string {
413 						  if (std::stoi(value)) {
414 							  return "Occlusion query is disabled because your video card doesn't "
415 							         "support GL_ARB_occlusion_query.";
416 						  } else {
417 							  return std::string();
418 						  }
419 					  }));
420 					AddReport("GL_ARB_occlusion_query is NOT SUPPORTED",
421 					          MakeVector4(1.f, 1.f, 0.5f, 1.f));
422 					AddReport("  r_occlusionQuery is disabled.", MakeVector4(1.f, 1.f, 1.f, 0.7f));
423 				}
424 
425 				if (extensions.find("GL_NV_conditional_render") == std::string::npos) {
426 					if (r_occlusionQuery) {
427 						r_occlusionQuery = 0;
428 						SPLog("Disabling r_occlusionQuery: no GL_NV_conditional_render");
429 					}
430 					incapableConfigs.insert(
431 					  std::make_pair("r_occlusionQuery", [](std::string value) -> std::string {
432 						  if (std::stoi(value)) {
433 							  return "Occlusion query is disabled because your video card doesn't "
434 							         "support GL_NV_conditional_render.";
435 						  } else {
436 							  return std::string();
437 						  }
438 					  }));
439 					AddReport("GL_NV_conditional_render is NOT SUPPORTED",
440 					          MakeVector4(1.f, 1.f, 0.5f, 1.f));
441 					AddReport("  r_occlusionQuery is disabled.", MakeVector4(1.f, 1.f, 1.f, 0.7f));
442 				}
443 
444 				if (extensions.find("GL_ARB_color_buffer_float") == std::string::npos) {
445 					if (r_hdr) {
446 						r_hdr = 0;
447 						SPLog("Disabling r_hdr: no GL_ARB_color_buffer_float");
448 					}
449 					incapableConfigs.insert(
450 					  std::make_pair("r_hdr", [](std::string value) -> std::string {
451 						  if (std::stoi(value)) {
452 							  return "HDR Rendering is disabled because your video card doesn't "
453 							         "support GL_ARB_color_buffer_float.";
454 						  } else {
455 							  return std::string();
456 						  }
457 					  }));
458 					AddReport("GL_ARB_color_buffer_float is NOT SUPPORTED",
459 					          MakeVector4(1.f, 1.f, 0.5f, 1.f));
460 					AddReport("  r_hdr is disabled.", MakeVector4(1.f, 1.f, 1.f, 0.7f));
461 				}
462 
463 				if (extensions.find("GL_EXT_texture_array") == std::string::npos) {
464 					if ((int)r_water >= 2) {
465 						r_water = 1;
466 						SPLog("Disabling Water 2: no GL_EXT_texture_array");
467 					}
468 					shaderHighCapable = false;
469 					incapableConfigs.insert(
470 					  std::make_pair("r_water", [](std::string value) -> std::string {
471 						  if (std::stoi(value) >= 2) {
472 							  return "Water 2 is disabled because your video card doesn't "
473 							         "support GL_EXT_texture_array.";
474 						  } else {
475 							  return std::string();
476 						  }
477 					  }));
478 					AddReport("GL_EXT_texture_array is NOT SUPPORTED",
479 					          MakeVector4(1.f, 1.f, 0.5f, 1.f));
480 					AddReport("  Water 2 is disabled.", MakeVector4(1.f, 1.f, 1.f, 0.7f));
481 				}
482 
483 				AddReport("Max Texture Size: " + std::to_string(maxTextureSize),
484 				          MakeVector4(1.f, 1.f, 1.f, 0.7f));
485 				if (maxTextureSize < 1024) {
486 					capable = false;
487 					AddReport("  TOO SMALL (1024 required)", MakeVector4(1.f, 0.5f, 0.5f, 1.f));
488 				}
489 				if ((int)r_shadowMapSize > maxTextureSize) {
490 					SPLog("Changed r_shadowMapSize from %d to %d: too small GL_MAX_TEXTURE_SIZE",
491 					      (int)r_shadowMapSize, maxTextureSize);
492 
493 					r_shadowMapSize = maxTextureSize;
494 				}
495 
496 				AddReport("Max 3D Texture Size: " + std::to_string(max3DTextureSize),
497 				          MakeVector4(1.f, 1.f, 1.f, 0.7f));
498 				if (max3DTextureSize < 512) {
499 					AddReport("  Global Illumination is disabled (512 required)",
500 					          MakeVector4(1.f, 1.f, 0.5f, 1.f));
501 					incapableConfigs.insert(
502 					  std::make_pair("r_radiosity", [](std::string value) -> std::string {
503 						  if (std::stoi(value)) {
504 							  return "Global illumination is disabled because your video card "
505 							         "doesn't support a 3D texture of at least 512x512x64.";
506 						  } else {
507 							  return std::string();
508 						  }
509 					  }));
510 
511 					if (r_radiosity) {
512 						r_radiosity = 0;
513 						SPLog("Disabling r_radiosity: too small GL_MAX_3D_TEXTURE_SIZE");
514 					}
515 				}
516 
517 				AddReport("Max Combined Texture Image Units: " +
518 				            std::to_string(maxCombinedTextureUnits),
519 				          MakeVector4(1.f, 1.f, 1.f, 0.7f));
520 				if (maxCombinedTextureUnits < 12) {
521 					AddReport("  Global Illumination is disabled (12 required)",
522 					          MakeVector4(1.f, 1.f, 0.5f, 1.f));
523 					incapableConfigs.insert(
524 					  std::make_pair("r_radiosity", [](std::string value) -> std::string {
525 						  if (std::stoi(value)) {
526 							  return "Global illumination is disabled because your video card "
527 							         "supports too few combined texture image units.";
528 						  } else {
529 							  return std::string();
530 						  }
531 					  }));
532 
533 					if (r_radiosity) {
534 						r_radiosity = 0;
535 						SPLog(
536 						  "Disabling r_radiosity: too small GL_MAX_COMBINED_TEXTURE_IMAGE_UNITS");
537 					}
538 				}
539 				if (maxCombinedTextureUnits < 15) {
540 					AddReport("  Water 2 is disabled (15 required)",
541 					          MakeVector4(1.f, 1.f, 0.5f, 1.f));
542 					shaderHighCapable = false;
543 
544 					incapableConfigs.insert(
545 					  std::make_pair("r_water", [](std::string value) -> std::string {
546 						  if (std::stoi(value) >= 2) {
547 							  return "Water 2 is disabled because your video card supports too few "
548 							         "combined texture image units.";
549 						  } else {
550 							  return std::string();
551 						  }
552 					  }));
553 
554 					if ((int)r_water >= 2) {
555 						r_water = 1;
556 						SPLog("Disabling Water 2: too small GL_MAX_COMBINED_TEXTURE_IMAGE_UNITS");
557 					}
558 				}
559 
560 				AddReport("Max Vertex Texture Image Units: " +
561 				            std::to_string(maxVertexTextureUnits),
562 				          MakeVector4(1.f, 1.f, 1.f, 0.7f));
563 				if (maxVertexTextureUnits < 3) {
564 					AddReport("  Water 2 is disabled (3 required)",
565 					          MakeVector4(1.f, 1.f, 0.5f, 1.f));
566 					shaderHighCapable = false;
567 
568 					incapableConfigs.insert(
569 					  std::make_pair("r_water", [](std::string value) -> std::string {
570 						  if (std::stoi(value) >= 2) {
571 							  return "Water 2 is disabled because your video card supports too few "
572 							         "vertex texture image units.";
573 						  } else {
574 							  return std::string();
575 						  }
576 					  }));
577 
578 					if ((int)r_water >= 2) {
579 						r_water = 1;
580 						SPLog("Disabling Water 2: too small GL_MAX_VERTEX_TEXTURE_IMAGE_UNITS");
581 					}
582 				}
583 
584 				AddReport("Max Varying Components: " + std::to_string(maxVaryingComponents),
585 				          MakeVector4(1.f, 1.f, 1.f, 0.7f));
586 				if (maxVaryingComponents < 37) {
587 					AddReport("  Shaded Particle is disabled (37 required)",
588 					          MakeVector4(1.f, 1.f, 0.5f, 1.f));
589 					particleHighCapable = false;
590 
591 					incapableConfigs.insert(
592 					  std::make_pair("r_softParticles", [](std::string value) -> std::string {
593 						  if (std::stoi(value) >= 2) {
594 							  return "Shaded particle is disabled because your video card supports "
595 							         "too few varying fragment shader input components.";
596 						  } else {
597 							  return std::string();
598 						  }
599 					  }));
600 
601 					if ((int)r_softParticles >= 2) {
602 						r_softParticles = 1;
603 						SPLog(
604 						  "Disabling shaded particle: too small GL_MAX_VERTEX_TEXTURE_IMAGE_UNITS");
605 					}
606 				}
607 
608 				AddReport();
609 
610 				if (capable) {
611 					AddReport("Your video card supports all "
612 					          "required OpenGL extensions/features.",
613 					          MakeVector4(0.5f, 1.f, 0.5f, 1.f));
614 				} else {
615 					AddReport("Your video card/driver doesn't support "
616 					          "at least one of required OpenGL extensions/features."
617 					          " Falling back to the software renderer.",
618 					          MakeVector4(1.f, 0.5f, 0.5f, 1.f));
619 				}
620 
621 				SDL_GL_DeleteContext(context);
622 				SDL_DestroyWindow(window);
623 
624 				SPLog("SDL Capability Query Window finalized");
625 			}
626 
627 			SPLog("OpenGL driver is OpenSpades capable: %s", capable ? "YES" : "NO");
628 
629 			openGLCapable = capable;
630 			if (!openGLCapable) {
631 				if (spades::EqualsIgnoringCase(r_renderer, "gl")) {
632 					SPLog("Switched to software renderer");
633 					r_renderer = "sw";
634 				}
635 
636 				incapableConfigs.insert(
637 				  std::make_pair("r_renderer", [](std::string value) -> std::string {
638 					  if (spades::EqualsIgnoringCase(value, "gl")) {
639 						  return "OpenGL renderer is disabled because "
640 						         "your video card/driver doesn't support "
641 						         "at least one of required OpenGL extensions/features.";
642 					  } else {
643 						  return std::string();
644 					  }
645 				  }));
646 			}
647 		}
648 
FixConfigs()649 		void StartupScreenHelper::FixConfigs() { ExamineSystem(); }
650 
GetOperatingSystemType()651 		std::string StartupScreenHelper::GetOperatingSystemType() {
652 #if defined(OS_PLATFORM_LINUX)
653 			return "Linux";
654 #elif defined(TARGET_OS_MAC)
655 			return "Mac";
656 #elif defined(OS_PLATFORM_WINDOWS)
657 			return "Windows";
658 #elif defined(__FreeBSD__)
659 			return "FreeBSD";
660 #elif defined(__OpenBSD__)
661 			return "OpenBSD";
662 #else
663 			return std::string{};
664 #endif
665 		}
666 
GetPackageUpdateManager()667 		PackageUpdateManager& StartupScreenHelper::GetPackageUpdateManager() {
668 			return PackageUpdateManager::GetInstance();
669 		}
670 
OpenUpdateInfoURL()671 		bool StartupScreenHelper::OpenUpdateInfoURL() {
672 			std::string url = GetPackageUpdateManager().GetLatestVersionInfoPageURL();
673 			if (url.find("http:") != 0 && url.find("https:") != 0) {
674 				return false;
675 			}
676 			return OpenURLInBrowser(url);
677 		}
678 
BrowseUserDirectory()679 		bool StartupScreenHelper::BrowseUserDirectory() {
680 			std::string path = g_userResourceDirectory;
681 
682 			if (path.empty()) {
683 				SPLog("Cannot open the user resource directory: g_userResourceDirectory is empty.");
684 				return false;
685 			}
686 
687 			return ShowDirectoryInShell(path);
688 		}
689 
Start()690 		void StartupScreenHelper::Start() {
691 			if (scr == nullptr) {
692 				return;
693 			}
694 			scr->Start();
695 		}
696 
GetNumVideoModes()697 		int StartupScreenHelper::GetNumVideoModes() { return static_cast<int>(modes.size()); }
698 
GetVideoModeWidth(int index)699 		int StartupScreenHelper::GetVideoModeWidth(int index) {
700 			if (index < 0 || index >= GetNumVideoModes())
701 				SPInvalidArgument("index");
702 			return modes[index].x;
703 		}
GetVideoModeHeight(int index)704 		int StartupScreenHelper::GetVideoModeHeight(int index) {
705 			if (index < 0 || index >= GetNumVideoModes())
706 				SPInvalidArgument("index");
707 			return modes[index].y;
708 		}
709 
GetNumReportLines()710 		int StartupScreenHelper::GetNumReportLines() {
711 			return static_cast<int>(reportLines.size());
712 		}
713 
GetReportLineText(int index)714 		std::string StartupScreenHelper::GetReportLineText(int index) {
715 			if (index < 0 || index >= GetNumReportLines())
716 				SPInvalidArgument("index");
717 			return reportLines[index].text;
718 		}
GetReportLineColor(int index)719 		Vector4 StartupScreenHelper::GetReportLineColor(int index) {
720 			if (index < 0 || index >= GetNumReportLines())
721 				SPInvalidArgument("index");
722 			return reportLines[index].color;
723 		}
724 
AddReport(const std::string & text,Vector4 color)725 		void StartupScreenHelper::AddReport(const std::string &text, Vector4 color) {
726 			ReportLine l = {text, color};
727 			reportLines.push_back(l);
728 			report += text;
729 			report += '\n';
730 		}
731 
GetNumLocales()732 		int StartupScreenHelper::GetNumLocales() { return static_cast<int>(locales.size()); }
GetLocale(int index)733 		std::string StartupScreenHelper::GetLocale(int index) {
734 			if (index < 0 || index >= GetNumLocales())
735 				SPInvalidArgument("index");
736 			return locales[index].name;
737 		}
GetLocaleDescriptionNative(int index)738 		std::string StartupScreenHelper::GetLocaleDescriptionNative(int index) {
739 			if (index < 0 || index >= GetNumLocales())
740 				SPInvalidArgument("index");
741 			return locales[index].descriptionNative;
742 		}
GetLocaleDescriptionEnglish(int index)743 		std::string StartupScreenHelper::GetLocaleDescriptionEnglish(int index) {
744 			if (index < 0 || index >= GetNumLocales())
745 				SPInvalidArgument("index");
746 			return locales[index].descriptionEnglish;
747 		}
748 
CheckConfigCapability(const std::string & cfg,const std::string & value)749 		std::string StartupScreenHelper::CheckConfigCapability(const std::string &cfg,
750 		                                                       const std::string &value) {
751 			auto range = incapableConfigs.equal_range(cfg);
752 			std::string ret;
753 			bool hasMulti = false;
754 			for (auto it = range.first; it != range.second; it++) {
755 				auto &f = it->second;
756 				auto err = f(value);
757 				if (err.size() > 0) {
758 					if (ret.size() == 0) {
759 						ret = err;
760 					} else {
761 						if (!hasMulti) {
762 							ret = "- " + ret;
763 							hasMulti = true;
764 						}
765 						ret += "\n- " + err;
766 					}
767 				}
768 			}
769 			return ret;
770 		}
771 	}
772 }
773