1 //
2 // SuperTuxKart - a fun racing game with go-kart
3 // Copyright (C) 2014-2015 Joerg Henrichs
4 //
5 // This program is free software; you can redistribute it and/or
6 // modify it under the terms of the GNU General Public License
7 // as published by the Free Software Foundation; either version 3
8 // of the License, or (at your option) any later version.
9 //
10 // This program 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 this program; if not, write to the Free Software
17 // Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
18
19 #include "graphics/graphics_restrictions.hpp"
20
21 #include "io/file_manager.hpp"
22 #include "io/xml_node.hpp"
23 #include "utils/log.hpp"
24 #include "utils/no_copy.hpp"
25 #include "utils/string_utils.hpp"
26 #include "utils/types.hpp"
27
28 #ifdef ANDROID
29 #include "SDL_system.h"
30 #endif
31
32 #include <algorithm>
33 #include <array>
34
35 namespace GraphicsRestrictions
36 {
37
38 class Rule;
39 namespace Private
40 {
41 /** Stores for each grpahics restriction if it's enabled or not. */
42 std::vector<bool> m_all_graphics_restriction;
43
44 /** The list of names used in the XML file for the graphics
45 * restriction types. They must be in the same order as the types. */
46
47 std::array<std::string, 32> m_names_of_restrictions =
48 {
49 {
50 "UniformBufferObject",
51 "GeometryShader",
52 "DrawIndirect",
53 "TextureView",
54 "TextureStorage",
55 "ImageLoadStore",
56 "BaseInstance",
57 "ComputeShader",
58 "ArraysOfArrays",
59 "ShaderStorageBufferObject",
60 "MultiDrawIndirect",
61 "ShaderAtomicCounters",
62 "BufferStorage",
63 "BindlessTexture",
64 "TextureCompressionS3TC",
65 "AMDVertexShaderLayer",
66 "ExplicitAttribLocation",
67 "TextureFilterAnisotropic",
68 "TextureFormatBGRA8888",
69 "ColorBufferFloat",
70 "DriverRecentEnough",
71 "HighDefinitionTextures",
72 "HighDefinitionTextures256",
73 "AdvancedPipeline",
74 "Correct10bitNormalization",
75 "GI",
76 "ForceLegacyDevice",
77 "VertexIdWorking",
78 "HardwareSkinning",
79 "NpotTextures",
80 "TextureBufferObject",
81 "SystemScreenKeyboard"
82 }
83 };
84 } // namespace Private
85 using namespace Private;
86
87 /** Returns the graphics restrictions type for a string, or
88 * GR_COUNT if the name is not found. */
getTypeForName(const std::string & name)89 GraphicsRestrictionsType getTypeForName(const std::string &name)
90 {
91 for (unsigned int i = 0; i < m_names_of_restrictions.size(); i++)
92 {
93 if (name == m_names_of_restrictions[i])
94 return (GraphicsRestrictionsType)i;
95 }
96 return GR_COUNT;
97 } // getTypeForName
98
99 // ============================================================================
100 /** A small utility class to manage and compare version tuples.
101 */
102 class Version
103 {
104 private:
105 // ----------------------------------------------------------------------------
106 /** Searches for the first number in the string, then converts the rest of the
107 * string into a tuple. E.g. "Build 12.34.56" --> [12,34,56].
108 */
convertVersionString(const std::string & version)109 void convertVersionString(const std::string &version)
110 {
111 std::string s = version;
112 std::string::iterator p = s.begin();
113 while( (p !=s.end()) && ((*p<'0') || (*p>'9')) )
114 p++;
115 s.erase(s.begin(), p);
116 m_version = StringUtils::splitToUInt(s, '.');
117 } // convertVersionString
118
119 public:
120 /** The array containing the version number. */
121 std::vector<uint32_t> m_version;
122 // ------------------------------------------------------------------------
123 /** Dummy default constructor. */
Version()124 Version() {}
125 // ------------------------------------------------------------------------
126 /** Simple constructor which takes a string with "." separated numbers.
127 */
Version(const std::string & version)128 Version(const std::string &version)
129 {
130 convertVersionString(version);
131 } // Version(std::string)
132
133 // ------------------------------------------------------------------------
134 /** Create a version instance from the driver and car name. For example for
135 * an Intel HD3000 card the string is "3.1.0 - Build 9.17.10.3517" it would
136 * create [9,17,10,3517] - i.e. it takes the vendor info into account.
137 * \param driver_version The GL_VERSION string (i.e. opengl and version
138 * number).
139 * \param card_name The GL_RENDERER string (i.e. graphics card).
140 */
Version(const std::string & driver_version,const std::string & card_name)141 Version(const std::string &driver_version, const std::string &card_name)
142 {
143 m_version.clear();
144
145 #ifdef ANDROID
146 // Android version should be enough to disable certain features on this
147 // platform
148 int version = SDL_GetAndroidSDKVersion();
149
150 if (version > 0)
151 {
152 m_version.push_back(version);
153 return;
154 }
155 #endif
156
157 // Mesa needs to be tested first, otherwise (if testing for card name
158 // further down) it would be detected as a non-mesa driver.
159 if (driver_version.find("Mesa") != std::string::npos)
160 {
161 std::string driver;
162 // Try to force the driver name to be in a standard way by removing
163 // optional strings
164 driver = StringUtils::replace(driver_version, "-devel", "");
165 driver = StringUtils::replace(driver, "(Core Profile) ", "");
166 std::vector<std::string> l = StringUtils::split(driver, ' ');
167 if (l.size() > 2)
168 {
169 // driver can be: "1.4 (3.0 Mesa 10.1.0)" -->
170 // we use value next to "Mesa" word.
171 for (unsigned int i = 0; i < l.size(); i++)
172 {
173 if (l[i] == "Mesa" && i < l.size() - 1)
174 {
175 convertVersionString(l[i+1]);
176 return;
177 }
178 }
179 }
180 }
181
182 // Intel card: driver version = "3.1.0 - Build 9.17.10.3517"
183 // ---------------------------------------------------------
184 if (StringUtils::startsWith(card_name, "Intel"))
185 {
186 std::vector<std::string> s = StringUtils::split(driver_version, '-');
187 if (s.size() == 2)
188 {
189 convertVersionString(s[1]);
190 return;
191 }
192 }
193
194 // Nvidia: driver_version = "4.3.0 NVIDIA 340.58"
195 // ----------------------------------------------
196 if (driver_version.find("NVIDIA") != std::string::npos)
197 {
198 std::vector<std::string> s = StringUtils::split(driver_version, ' ');
199 if (s.size() == 3)
200 {
201 convertVersionString(s[2]);
202 return;
203 }
204 else if (s.size() == 5)
205 {
206 convertVersionString(s[4]);
207 return;
208 }
209
210 }
211
212 // ATI: some drivers use e.g.: "4.1 ATI-1.24.38"
213 if (driver_version.find("ATI-") != std::string::npos)
214 {
215 std::string driver;
216 // Try to force the driver name to be in a standard way by removing
217 // optional strings
218 driver = StringUtils::replace(driver_version, "ATI-", "");
219 std::vector<std::string> s = StringUtils::split(driver, ' ');
220 convertVersionString(s[1]);
221 return;
222 }
223
224 // AMD: driver_version = "4.3.13283 Core Profile/Debug Context 14.501.1003.0"
225 // ----------------------------------------------
226 if (card_name.find("AMD") != std::string::npos
227 || card_name.find("Radeon") != std::string::npos)
228 {
229 std::vector<std::string> s = StringUtils::split(driver_version, ' ');
230 if (s.size() == 5 || s.size() == 6)
231 {
232 convertVersionString(s[4]);
233 return;
234 }
235 }
236
237 // ATI: other drivers use "4.0.10188 Core Profile Context"
238 if (card_name.find("ATI") != std::string::npos)
239 {
240 std::vector<std::string> s = StringUtils::split(driver_version, ' ');
241 convertVersionString(s[0]);
242 return;
243 }
244
245 Log::warn("Graphics", "Can not find version for '%s' '%s' - ignored.",
246 driver_version.c_str(), card_name.c_str());
247
248 } // Version
249
250 // ------------------------------------------------------------------------
251 /** Compares two version numbers. Equal returns true if the elements are
252 * identical.
253 */
operator ==(const Version & other) const254 bool operator== (const Version &other) const
255 {
256 if (m_version.size() != other.m_version.size()) return false;
257 for(unsigned int i=0; i<m_version.size(); i++)
258 if(other.m_version[i]!=m_version[i]) return false;
259 return true;
260 } // operator==
261 // ------------------------------------------------------------------------
262 /** Compares two version numbers. Equal returns true if the elements are
263 * identical.
264 */
operator !=(const Version & other) const265 bool operator!= (const Version &other) const
266 {
267 return !this->operator==(other);
268 } // operator!=
269
270 }; // class Version
271
272 // ------------------------------------------------------------------------
273 /** Compares two version numbers. If left < right. */
operator <(const Version & left,const Version & right)274 bool operator< (const Version &left, const Version &right)
275 {
276 unsigned int min_n = (unsigned int)std::min(left.m_version.size(), right.m_version.size());
277 for (unsigned int i = 0; i<min_n; i++)
278 {
279 if (left.m_version[i] > right.m_version[i]) return false;
280 if (left.m_version[i] < right.m_version[i]) return true;
281 }
282 return (left.m_version.size() < right.m_version.size());
283 } // operator<
284 // ------------------------------------------------------------------------
285 /** Compares two version numbers. If left > right. */
operator >(const Version & left,const Version & right)286 bool operator> (const Version &left, const Version &right)
287 {
288 unsigned int min_n = (unsigned int)std::min(left.m_version.size(), right.m_version.size());
289 for (unsigned int i = 0; i<min_n; i++)
290 {
291 if (left.m_version[i] > right.m_version[i]) return true;
292 if (left.m_version[i] < right.m_version[i]) return false;
293 }
294 return (left.m_version.size() > right.m_version.size());
295 } // operator>
296 // ------------------------------------------------------------------------
297 /** Compares two version numbers. If left <= right. */
operator <=(const Version & left,const Version & right)298 bool operator<= (const Version &left, const Version &right)
299 {
300 return !(left > right);
301 } // operator<=
302
303 // ------------------------------------------------------------------------
304 /** Compares two version numbers. If *this >= other. */
operator >=(const Version & left,const Version & right)305 bool operator>= (const Version &left, const Version &right)
306 {
307 return !(left < right);
308 } // operator>=
309
310 // ============================================================================
311 class Rule : public NoCopy
312 {
313 private:
314 /** Operators to test for a card. */
315 enum {CARD_IGNORE, CARD_IS, CARD_CONTAINS} m_card_test;
316
317 /** Name of the card for which this rule applies. */
318 std::string m_card_name;
319
320 /** Operators to test version numbers with. */
321 enum VersionTest
322 {
323 VERSION_IGNORE,
324 VERSION_EQUAL,
325 VERSION_LESS,
326 VERSION_LESS_EQUAL,
327 VERSION_MORE,
328 VERSION_MORE_EQUAL
329 };
330
331 std::vector<VersionTest> m_version_tests;
332
333 /** Driver version for which this rule applies. */
334 std::vector<Version> m_driver_versions;
335
336 /** For which OS this rule applies. */
337 std::string m_os;
338
339 /** For which vendor this rule applies. */
340 std::string m_vendor;
341
342 /** Which options to disable. */
343 std::vector<std::string> m_disable_options;
344
345 public:
Rule(const XMLNode * rule)346 Rule(const XMLNode *rule)
347 {
348 m_card_test = CARD_IGNORE;
349
350 if (rule->get("is", &m_card_name))
351 m_card_test = CARD_IS;
352 else if (rule->get("contains", &m_card_name))
353 m_card_test = CARD_CONTAINS;
354
355 rule->get("os", &m_os);
356 rule->get("vendor", &m_vendor);
357
358 std::string s;
359
360 if (rule->get("version", &s) && s.size() > 1)
361 addVersion(s);
362
363 if (rule->get("version2", &s) && s.size() > 1)
364 addVersion(s);
365
366 if (rule->get("disable", &s))
367 m_disable_options = StringUtils::split(s, ' ');
368 } // Rule
369
370 // ------------------------------------------------------------------------
addVersion(std::string version)371 void addVersion(std::string version)
372 {
373 if (version.substr(0, 2) == "<=")
374 {
375 m_version_tests.push_back(VERSION_LESS_EQUAL);
376 version = version.substr(2, version.size());
377 }
378 else if (version[0] == '<')
379 {
380 m_version_tests.push_back(VERSION_LESS);
381 version.erase(version.begin());
382 }
383 else if (version.substr(0, 2) == ">=")
384 {
385 m_version_tests.push_back(VERSION_MORE_EQUAL);
386 version = version.substr(2, version.size());
387 }
388 else if (version[0] == '>')
389 {
390 m_version_tests.push_back(VERSION_MORE);
391 version.erase(version.begin());
392 }
393 else if (version[0] == '=')
394 {
395 m_version_tests.push_back(VERSION_EQUAL);
396 version.erase(version.begin());
397 }
398 else
399 {
400 m_version_tests.push_back(VERSION_IGNORE);
401 Log::warn("Graphics", "Invalid version '%s' found - ignored.",
402 version.c_str());
403 }
404
405 if (m_version_tests.back() != VERSION_IGNORE)
406 {
407 m_driver_versions.push_back(Version(version));
408 }
409 }
410
411 // ------------------------------------------------------------------------
applies(const std::string & card,const Version & version,const std::string & vendor) const412 bool applies(const std::string &card, const Version &version,
413 const std::string &vendor) const
414 {
415 // Test for OS
416 // -----------
417 if(m_os.size()>0)
418 {
419 #if defined(__linux__) && !defined(ANDROID)
420 if(m_os!="linux") return false;
421 #elif defined(IOS_STK)
422 if(m_os!="ios") return false;
423 #elif defined(WIN32)
424 if(m_os!="windows") return false;
425 #elif defined(__APPLE__)
426 if(m_os!="osx") return false;
427 #elif defined(BSD)
428 if(m_os!="bsd") return false;
429 #elif defined(ANDROID)
430 if(m_os!="android") return false;
431 #else
432 return false;
433 #endif
434 } // m_os.size()>0
435
436 // Test for vendor
437 // ---------------
438 if (m_vendor.size() > 0)
439 {
440 if (m_vendor != vendor)
441 return false;
442 }
443
444 // Test for card
445 // -------------
446 switch(m_card_test)
447 {
448 case CARD_IGNORE: break; // always true
449 case CARD_IS:
450 if(card!=m_card_name) return false;
451 break;
452 case CARD_CONTAINS:
453 if(card.find(m_card_name)==std::string::npos)
454 return false;
455 break;
456 } // switch m_card_test
457
458 // Test for driver version
459 // -----------------------
460 assert(m_version_tests.size() == m_driver_versions.size());
461
462 for (unsigned int i = 0; i < m_version_tests.size(); i++)
463 {
464 switch (m_version_tests[i])
465 {
466 case VERSION_IGNORE:
467 // always true
468 break;
469 case VERSION_EQUAL:
470 if (version != m_driver_versions[i])
471 return false;
472 break;
473 case VERSION_LESS_EQUAL:
474 if (version > m_driver_versions[i])
475 return false;
476 break;
477 case VERSION_LESS:
478 if (version >= m_driver_versions[i])
479 return false;
480 break;
481 case VERSION_MORE_EQUAL:
482 if (version < m_driver_versions[i])
483 return false;
484 break;
485 case VERSION_MORE:
486 if (version <= m_driver_versions[i])
487 return false;
488 } // switch m_version_tests
489 }
490
491 return true;
492 } // applies
493
494 // ------------------------------------------------------------------------
495 /** Returns a list of options to disable. */
getRestrictions() const496 const std::vector<std::string>& getRestrictions() const
497 {
498 return m_disable_options;
499 } // getRestrictions
500 // ------------------------------------------------------------------------
501 }; // class Rule
502
503 // ============================================================================
504 /** Very rudimentary and brute unit testing. Better than nothing :P
505 */
unitTesting()506 void unitTesting()
507 {
508 assert(Version("1") == Version("1"));
509 assert(Version("1") != Version("2"));
510 assert(Version("1") <= Version("2"));
511 assert(Version("1") < Version("2"));
512 assert(Version("1.2.3") < Version("2"));
513 assert(Version("1.2.3") < Version("1.3"));
514 assert(Version("1.2.3") < Version("1.2.4"));
515 assert(Version("1.2.3") < Version("1.2.3.1"));
516 assert(Version("1.2.3") <= Version("2"));
517 assert(Version("1.2.3") <= Version("1.3"));
518 assert(Version("1.2.3") <= Version("1.2.4"));
519 assert(Version("1.2.3") <= Version("1.2.3.1"));
520 assert(Version("1.2.3") <= Version("1.2.3"));
521 assert(Version("1.2.3") == Version("1.2.3"));
522 assert(Version("10.3") < Version("10.3.2"));
523 assert(Version("10.3") <= Version("10.3.2"));
524 assert(!(Version("10.3.2") < Version("10.3")));
525 assert(!(Version("10.3.2") <= Version("10.3")));
526 assert(Version("1.2.4") > Version("1.2.3"));
527 assert(Version("1.2.3.4") > Version("1.2.3"));
528 assert(Version("1.2.3") >= Version("1.2.3"));
529 assert(Version("1.2.4") >= Version("1.2.3"));
530 assert(Version("1.2.3.4") >= Version("1.2.3"));
531 assert(Version("3.3 NVIDIA-10.0.19 310.90.10.05b1",
532 "NVIDIA GeForce GTX 680MX OpenGL Engine")
533 == Version("310.90.10.5") );
534
535 assert(Version("4.1 NVIDIA-10.0.43 310.41.05f01",
536 "NVIDIA GeForce GTX 780M OpenGL Engine")
537 == Version("310.41.05"));
538
539 assert(Version("3.1 (Core Profile) Mesa 10.3.0",
540 "Mesa DRI Mobile Intel\u00ae GM45 Express Chipset")
541 == Version("10.3.0") );
542 assert(Version("3.3 (Core Profile) Mesa 10.5.0-devel",
543 "Gallium 0.4 on NVC1")
544 == Version("10.5.0") );
545 assert(Version("3.3 (Core Profile) Mesa 10.5.0-devel",
546 "Mesa DRI Intel(R) Sandybridge Mobile")
547 == Version("10.5.0") );
548 assert(Version("2.1 Mesa 10.5.0-devel (git-82e919d)",
549 "Gallium 0.4 on i915 (chipse)")
550 == Version("10.5.0") );
551 assert(Version("1.4 (3.0 Mesa 10.1.0)",
552 "Mesa DRI Intel(R) Ivybridge Mobile")
553 == Version("10.1.0"));
554 assert(Version("4.3.13283 Core Profile Context 14.501.1003.0",
555 "AMD Radeon R9 200 Series")
556 == Version("14.501.1003.0"));
557 assert(Version("4.0.10188 Core Profile Context",
558 "ATI Radeon HD 5400 Series")
559 == Version("4.0.10188"));
560 assert(Version("4.1 ATI-1.24.38", "AMD Radeon HD 6970M OpenGL Engine")
561 == Version("1.24.38"));
562
563 } // unitTesting
564
565 // ----------------------------------------------------------------------------
566 /** Reads in the graphical restriction file.
567 * \param driver_version The GL_VERSION string (i.e. opengl and version
568 * number).
569 * \param card_name The GL_RENDERER string (i.e. graphics card).
570 * \param vendor The GL_VENDOR string
571 */
init(const std::string & driver_version,const std::string & card_name,const std::string & vendor)572 void init(const std::string &driver_version,
573 const std::string &card_name,
574 const std::string &vendor)
575 {
576 for (unsigned int i = 0; i < GR_COUNT; i++)
577 m_all_graphics_restriction.push_back(false);
578
579 std::string filename =
580 file_manager->getUserConfigFile("graphical_restrictions.xml");
581 if (!file_manager->fileExists(filename))
582 filename = file_manager->getAsset("graphical_restrictions.xml");
583 const XMLNode *rules = file_manager->createXMLTree(filename);
584 if (!rules)
585 {
586 Log::warn("Graphics", "Could not find graphical_restrictions.xm");
587 return;
588 }
589 if (rules->getName() != "graphical-restrictions")
590 {
591 delete rules;
592 Log::warn("Graphics", "'%s' did not contain graphical-restrictions tag",
593 filename.c_str());
594 return;
595 }
596
597 Version version(driver_version, card_name);
598 for (unsigned int i = 0; i<rules->getNumNodes(); i++)
599 {
600 const XMLNode *xml_rule = rules->getNode(i);
601 if (xml_rule->getName() != "card")
602 {
603 Log::warn("Graphics", "Incorrect node '%s' found in '%s' - ignored.",
604 xml_rule->getName().c_str(), filename.c_str());
605 continue;
606 }
607 Rule rule(xml_rule);
608 if (rule.applies(card_name, version, vendor))
609 {
610 std::vector<std::string> restrictions = rule.getRestrictions();
611 std::vector<std::string>::iterator p;
612 for (p = restrictions.begin(); p != restrictions.end(); p++)
613 {
614 GraphicsRestrictionsType t = getTypeForName(*p);
615 if (t != GR_COUNT)
616 m_all_graphics_restriction[t] = true;
617 } // for p in rules
618 } // if m_all_rules[i].applies()
619 }
620 delete rules;
621 } // init
622
623 // ----------------------------------------------------------------------------
624 /** Returns if the specified graphics restriction is defined.
625 * \param type The graphical restriction to tes.t
626 */
isDisabled(GraphicsRestrictionsType type)627 bool isDisabled(GraphicsRestrictionsType type)
628 {
629 return m_all_graphics_restriction[type];
630 } // isDisabled
631
632 } // namespace HardwareStats
633