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