PageRenderTime 516ms CodeModel.GetById 141ms app.highlight 248ms RepoModel.GetById 114ms app.codeStats 1ms

/indra/newview/llfeaturemanager.cpp

https://bitbucket.org/lindenlab/viewer-beta/
C++ | 817 lines | 609 code | 124 blank | 84 comment | 106 complexity | f5c3734e33a0291defdf55fbb876e65c MD5 | raw file
  1/** 
  2 * @file llfeaturemanager.cpp
  3 * @brief LLFeatureManager class implementation
  4 *
  5 * $LicenseInfo:firstyear=2003&license=viewerlgpl$
  6 * Second Life Viewer Source Code
  7 * Copyright (C) 2010, Linden Research, Inc.
  8 * 
  9 * This library is free software; you can redistribute it and/or
 10 * modify it under the terms of the GNU Lesser General Public
 11 * License as published by the Free Software Foundation;
 12 * version 2.1 of the License only.
 13 * 
 14 * This library is distributed in the hope that it will be useful,
 15 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 16 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 17 * Lesser General Public License for more details.
 18 * 
 19 * You should have received a copy of the GNU Lesser General Public
 20 * License along with this library; if not, write to the Free Software
 21 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
 22 * 
 23 * Linden Research, Inc., 945 Battery Street, San Francisco, CA  94111  USA
 24 * $/LicenseInfo$
 25 */
 26
 27#include "llviewerprecompiledheaders.h"
 28
 29#include <iostream>
 30#include <fstream>
 31
 32#include <boost/regex.hpp>
 33
 34#include "llfeaturemanager.h"
 35#include "lldir.h"
 36
 37#include "llsys.h"
 38#include "llgl.h"
 39#include "llsecondlifeurls.h"
 40
 41#include "llappviewer.h"
 42#include "llhttpclient.h"
 43#include "llnotificationsutil.h"
 44#include "llviewercontrol.h"
 45#include "llworld.h"
 46#include "lldrawpoolterrain.h"
 47#include "llviewertexturelist.h"
 48#include "llversioninfo.h"
 49#include "llwindow.h"
 50#include "llui.h"
 51#include "llcontrol.h"
 52#include "llboost.h"
 53#include "llweb.h"
 54#include "llviewershadermgr.h"
 55
 56#if LL_WINDOWS
 57#include "lldxhardware.h"
 58#endif
 59
 60
 61#if LL_DARWIN
 62const char FEATURE_TABLE_FILENAME[] = "featuretable_mac.txt";
 63const char FEATURE_TABLE_VER_FILENAME[] = "featuretable_mac.%s.txt";
 64#elif LL_LINUX
 65const char FEATURE_TABLE_FILENAME[] = "featuretable_linux.txt";
 66const char FEATURE_TABLE_VER_FILENAME[] = "featuretable_linux.%s.txt";
 67#elif LL_SOLARIS
 68const char FEATURE_TABLE_FILENAME[] = "featuretable_solaris.txt";
 69const char FEATURE_TABLE_VER_FILENAME[] = "featuretable_solaris.%s.txt";
 70#else
 71const char FEATURE_TABLE_FILENAME[] = "featuretable%s.txt";
 72const char FEATURE_TABLE_VER_FILENAME[] = "featuretable%s.%s.txt";
 73#endif
 74
 75const char GPU_TABLE_FILENAME[] = "gpu_table.txt";
 76const char GPU_TABLE_VER_FILENAME[] = "gpu_table.%s.txt";
 77
 78LLFeatureInfo::LLFeatureInfo(const std::string& name, const BOOL available, const F32 level)
 79	: mValid(TRUE), mName(name), mAvailable(available), mRecommendedLevel(level)
 80{
 81}
 82
 83LLFeatureList::LLFeatureList(const std::string& name)
 84	: mName(name)
 85{
 86}
 87
 88LLFeatureList::~LLFeatureList()
 89{
 90}
 91
 92void LLFeatureList::addFeature(const std::string& name, const BOOL available, const F32 level)
 93{
 94	if (mFeatures.count(name))
 95	{
 96		LL_WARNS("RenderInit") << "LLFeatureList::Attempting to add preexisting feature " << name << LL_ENDL;
 97	}
 98
 99	LLFeatureInfo fi(name, available, level);
100	mFeatures[name] = fi;
101}
102
103BOOL LLFeatureList::isFeatureAvailable(const std::string& name)
104{
105	if (mFeatures.count(name))
106	{
107		return mFeatures[name].mAvailable;
108	}
109
110	LL_WARNS_ONCE("RenderInit") << "Feature " << name << " not on feature list!" << LL_ENDL;
111	
112	// changing this to TRUE so you have to explicitly disable 
113	// something for it to be disabled
114	return TRUE;
115}
116
117F32 LLFeatureList::getRecommendedValue(const std::string& name)
118{
119	if (mFeatures.count(name) && isFeatureAvailable(name))
120	{
121		return mFeatures[name].mRecommendedLevel;
122	}
123
124	LL_WARNS_ONCE("RenderInit") << "Feature " << name << " not on feature list or not available!" << LL_ENDL;
125	return 0;
126}
127
128BOOL LLFeatureList::maskList(LLFeatureList &mask)
129{
130	//llinfos << "Masking with " << mask.mName << llendl;
131	//
132	// Lookup the specified feature mask, and overlay it on top of the
133	// current feature mask.
134	//
135
136	LLFeatureInfo mask_fi;
137
138	feature_map_t::iterator feature_it;
139	for (feature_it = mask.mFeatures.begin(); feature_it != mask.mFeatures.end(); ++feature_it)
140	{
141		mask_fi = feature_it->second;
142		//
143		// Look for the corresponding feature
144		//
145		if (!mFeatures.count(mask_fi.mName))
146		{
147			LL_WARNS("RenderInit") << "Feature " << mask_fi.mName << " in mask not in top level!" << LL_ENDL;
148			continue;
149		}
150
151		LLFeatureInfo &cur_fi = mFeatures[mask_fi.mName];
152		if (mask_fi.mAvailable && !cur_fi.mAvailable)
153		{
154			LL_WARNS("RenderInit") << "Mask attempting to reenabling disabled feature, ignoring " << cur_fi.mName << LL_ENDL;
155			continue;
156		}
157		cur_fi.mAvailable = mask_fi.mAvailable;
158		cur_fi.mRecommendedLevel = llmin(cur_fi.mRecommendedLevel, mask_fi.mRecommendedLevel);
159		LL_DEBUGS("RenderInit") << "Feature mask " << mask.mName
160				<< " Feature " << mask_fi.mName
161				<< " Mask: " << mask_fi.mRecommendedLevel
162				<< " Now: " << cur_fi.mRecommendedLevel << LL_ENDL;
163	}
164
165	LL_DEBUGS("RenderInit") << "After applying mask " << mask.mName << std::endl;
166		// Will conditionally call dump only if the above message will be logged, thanks 
167		// to it being wrapped by the LL_DEBUGS and LL_ENDL macros.
168		dump();
169	LL_CONT << LL_ENDL;
170
171	return TRUE;
172}
173
174void LLFeatureList::dump()
175{
176	LL_DEBUGS("RenderInit") << "Feature list: " << mName << LL_ENDL;
177	LL_DEBUGS("RenderInit") << "--------------" << LL_ENDL;
178
179	LLFeatureInfo fi;
180	feature_map_t::iterator feature_it;
181	for (feature_it = mFeatures.begin(); feature_it != mFeatures.end(); ++feature_it)
182	{
183		fi = feature_it->second;
184		LL_DEBUGS("RenderInit") << fi.mName << "\t\t" << fi.mAvailable << ":" << fi.mRecommendedLevel << LL_ENDL;
185	}
186	LL_DEBUGS("RenderInit") << LL_ENDL;
187}
188
189LLFeatureList *LLFeatureManager::findMask(const std::string& name)
190{
191	if (mMaskList.count(name))
192	{
193		return mMaskList[name];
194	}
195
196	return NULL;
197}
198
199BOOL LLFeatureManager::maskFeatures(const std::string& name)
200{
201	LLFeatureList *maskp = findMask(name);
202	if (!maskp)
203	{
204 		LL_DEBUGS("RenderInit") << "Unknown feature mask " << name << LL_ENDL;
205		return FALSE;
206	}
207	LL_INFOS("RenderInit") << "Applying GPU Feature list: " << name << LL_ENDL;
208	return maskList(*maskp);
209}
210
211BOOL LLFeatureManager::loadFeatureTables()
212{
213	// *TODO - if I or anyone else adds something else to the skipped list
214	// make this data driven.  Put it in the feature table and parse it
215	// correctly
216	mSkippedFeatures.insert("RenderAnisotropic");
217	mSkippedFeatures.insert("RenderGamma");
218	mSkippedFeatures.insert("RenderVBOEnable");
219	mSkippedFeatures.insert("RenderFogRatio");
220
221	// first table is install with app
222	std::string app_path = gDirUtilp->getAppRODataDir();
223	app_path += gDirUtilp->getDirDelimiter();
224
225	std::string filename;
226	std::string http_filename; 
227#if LL_WINDOWS
228	std::string os_string = LLAppViewer::instance()->getOSInfo().getOSStringSimple();
229	if (os_string.find("Microsoft Windows XP") == 0)
230	{
231		filename = llformat(FEATURE_TABLE_FILENAME, "_xp");
232		http_filename = llformat(FEATURE_TABLE_VER_FILENAME, "_xp", LLVersionInfo::getVersion().c_str());
233	}
234	else
235	{
236		filename = llformat(FEATURE_TABLE_FILENAME, "");
237		http_filename = llformat(FEATURE_TABLE_VER_FILENAME, "", LLVersionInfo::getVersion().c_str());
238	}
239#else
240	filename = FEATURE_TABLE_FILENAME;
241	http_filename = llformat(FEATURE_TABLE_VER_FILENAME, LLVersionInfo::getVersion().c_str());
242#endif
243
244	app_path += filename;
245
246	
247	// second table is downloaded with HTTP
248	std::string http_path = gDirUtilp->getExpandedFilename(LL_PATH_USER_SETTINGS, http_filename);
249
250	// use HTTP table if it exists
251	std::string path;
252	if (gDirUtilp->fileExists(http_path))
253	{
254		path = http_path;
255	}
256	else
257	{
258		path = app_path;
259	}
260
261	
262	return parseFeatureTable(path);
263}
264
265
266BOOL LLFeatureManager::parseFeatureTable(std::string filename)
267{
268	llinfos << "Looking for feature table in " << filename << llendl;
269
270	llifstream file;
271	std::string name;
272	U32		version;
273	
274	file.open(filename); 	 /*Flawfinder: ignore*/
275
276	if (!file)
277	{
278		LL_WARNS("RenderInit") << "Unable to open feature table " << filename << LL_ENDL;
279		return FALSE;
280	}
281
282	// Check file version
283	file >> name;
284	file >> version;
285	if (name != "version")
286	{
287		LL_WARNS("RenderInit") << filename << " does not appear to be a valid feature table!" << LL_ENDL;
288		return FALSE;
289	}
290
291	mTableVersion = version;
292
293	LLFeatureList *flp = NULL;
294	while (file >> name)
295	{
296		char buffer[MAX_STRING];		 /*Flawfinder: ignore*/
297		
298		if (name.substr(0,2) == "//")
299		{
300			// This is a comment.
301			file.getline(buffer, MAX_STRING);
302			continue;
303		}
304
305		if (name == "list")
306		{
307			if (flp)
308			{
309				//flp->dump();
310			}
311			// It's a new mask, create it.
312			file >> name;
313			if (mMaskList.count(name))
314			{
315				LL_ERRS("RenderInit") << "Overriding mask " << name << ", this is invalid!" << LL_ENDL;
316			}
317
318			flp = new LLFeatureList(name);
319			mMaskList[name] = flp;
320		}
321		else
322		{
323			if (!flp)
324			{
325				LL_ERRS("RenderInit") << "Specified parameter before <list> keyword!" << LL_ENDL;
326				return FALSE;
327			}
328			S32 available;
329			F32 recommended;
330			file >> available >> recommended;
331			flp->addFeature(name, available, recommended);
332		}
333	}
334	file.close();
335
336	return TRUE;
337}
338
339void LLFeatureManager::loadGPUClass()
340{
341	// defaults
342	mGPUClass = GPU_CLASS_UNKNOWN;
343	mGPUString = gGLManager.getRawGLString();
344	mGPUSupported = FALSE;
345
346	// first table is in the app dir
347	std::string app_path = gDirUtilp->getAppRODataDir();
348	app_path += gDirUtilp->getDirDelimiter();
349	app_path += GPU_TABLE_FILENAME;
350	
351	// second table is downloaded with HTTP
352	std::string http_filename = llformat(GPU_TABLE_VER_FILENAME, LLVersionInfo::getVersion().c_str());
353	std::string http_path = gDirUtilp->getExpandedFilename(LL_PATH_USER_SETTINGS, http_filename);
354
355	// use HTTP table if it exists
356	std::string path;
357	if (gDirUtilp->fileExists(http_path))
358	{
359		path = http_path;
360	}
361	else
362	{
363		path = app_path;
364	}
365
366	parseGPUTable(path);
367}
368
369	
370void LLFeatureManager::parseGPUTable(std::string filename)
371{
372	llifstream file;
373		
374	file.open(filename);
375
376	if (!file)
377	{
378		LL_WARNS("RenderInit") << "Unable to open GPU table: " << filename << "!" << LL_ENDL;
379		return;
380	}
381
382	std::string rawRenderer = gGLManager.getRawGLString();
383	std::string renderer = rawRenderer;
384	for (std::string::iterator i = renderer.begin(); i != renderer.end(); ++i)
385	{
386		*i = tolower(*i);
387	}
388
389	bool gpuFound;
390	U32 lineNumber;
391	for (gpuFound = false, lineNumber = 0; !gpuFound && !file.eof(); lineNumber++)
392	{
393		char buffer[MAX_STRING];		 /*Flawfinder: ignore*/
394		buffer[0] = 0;
395
396		file.getline(buffer, MAX_STRING);
397		
398		if (strlen(buffer) >= 2 && 	 /*Flawfinder: ignore*/
399			buffer[0] == '/' && 
400			buffer[1] == '/')
401		{
402			// This is a comment.
403			continue;
404		}
405
406		if (strlen(buffer) == 0)	 /*Flawfinder: ignore*/
407		{
408			// This is a blank line
409			continue;
410		}
411
412		// setup the tokenizer
413		std::string buf(buffer);
414		std::string cls, label, expr, supported;
415		boost_tokenizer tokens(buf, boost::char_separator<char>("\t\n"));
416		boost_tokenizer::iterator token_iter = tokens.begin();
417
418		// grab the label, pseudo regular expression, and class
419		if(token_iter != tokens.end())
420		{
421			label = *token_iter++;
422		}
423		if(token_iter != tokens.end())
424		{
425			expr = *token_iter++;
426		}
427		if(token_iter != tokens.end())
428		{
429			cls = *token_iter++;
430		}
431		if(token_iter != tokens.end())
432		{
433			supported = *token_iter++;
434		}
435
436		if (label.empty() || expr.empty() || cls.empty() || supported.empty())
437		{
438			LL_WARNS("RenderInit") << "invald gpu_table.txt:" << lineNumber << ": '" << buffer << "'" << LL_ENDL;
439			continue;
440		}
441	
442		for (U32 i = 0; i < expr.length(); i++)	 /*Flawfinder: ignore*/
443		{
444			expr[i] = tolower(expr[i]);
445		}
446
447		// run the regular expression against the renderer
448		boost::regex re(expr.c_str());
449		if(boost::regex_search(renderer, re))
450		{
451			// if we found it, stop!
452			gpuFound = true;
453			mGPUString = label;
454			mGPUClass = (EGPUClass) strtol(cls.c_str(), NULL, 10);
455			mGPUSupported = (BOOL) strtol(supported.c_str(), NULL, 10);
456		}
457	}
458	file.close();
459
460	if ( gpuFound )
461	{
462		LL_INFOS("RenderInit") << "GPU '" << rawRenderer << "' recognized as '" << mGPUString << "'" << LL_ENDL;
463		if (!mGPUSupported)
464		{
465			LL_INFOS("RenderInit") << "GPU '" << mGPUString << "' is not supported." << LL_ENDL;
466		}
467	}
468	else
469	{
470		LL_WARNS("RenderInit") << "GPU '" << rawRenderer << "' not recognized" << LL_ENDL;
471	}
472}
473
474// responder saves table into file
475class LLHTTPFeatureTableResponder : public LLHTTPClient::Responder
476{
477public:
478
479	LLHTTPFeatureTableResponder(std::string filename) :
480		mFilename(filename)
481	{
482	}
483
484	
485	virtual void completedRaw(U32 status, const std::string& reason,
486							  const LLChannelDescriptors& channels,
487							  const LLIOPipe::buffer_ptr_t& buffer)
488	{
489		if (isGoodStatus(status))
490		{
491			// write to file
492
493			llinfos << "writing feature table to " << mFilename << llendl;
494			
495			S32 file_size = buffer->countAfter(channels.in(), NULL);
496			if (file_size > 0)
497			{
498				// read from buffer
499				U8* copy_buffer = new U8[file_size];
500				buffer->readAfter(channels.in(), NULL, copy_buffer, file_size);
501
502				// write to file
503				LLAPRFile out(mFilename, LL_APR_WB);
504				out.write(copy_buffer, file_size);
505				out.close();
506			}
507		}
508		
509	}
510	
511private:
512	std::string mFilename;
513};
514
515void fetch_feature_table(std::string table)
516{
517	const std::string base       = gSavedSettings.getString("FeatureManagerHTTPTable");
518
519#if LL_WINDOWS
520	std::string os_string = LLAppViewer::instance()->getOSInfo().getOSStringSimple();
521	std::string filename;
522	if (os_string.find("Microsoft Windows XP") == 0)
523	{
524		filename = llformat(table.c_str(), "_xp", LLVersionInfo::getVersion().c_str());
525	}
526	else
527	{
528		filename = llformat(table.c_str(), "", LLVersionInfo::getVersion().c_str());
529	}
530#else
531	const std::string filename   = llformat(table.c_str(), LLVersionInfo::getVersion().c_str());
532#endif
533
534	const std::string url        = base + "/" + filename;
535
536	const std::string path       = gDirUtilp->getExpandedFilename(LL_PATH_USER_SETTINGS, filename);
537
538	llinfos << "LLFeatureManager fetching " << url << " into " << path << llendl;
539	
540	LLHTTPClient::get(url, new LLHTTPFeatureTableResponder(path));
541}
542
543void fetch_gpu_table(std::string table)
544{
545	const std::string base       = gSavedSettings.getString("FeatureManagerHTTPTable");
546
547	const std::string filename   = llformat(table.c_str(), LLVersionInfo::getVersion().c_str());
548
549	const std::string url        = base + "/" + filename;
550
551	const std::string path       = gDirUtilp->getExpandedFilename(LL_PATH_USER_SETTINGS, filename);
552
553	llinfos << "LLFeatureManager fetching " << url << " into " << path << llendl;
554	
555	LLHTTPClient::get(url, new LLHTTPFeatureTableResponder(path));
556}
557
558// fetch table(s) from a website (S3)
559void LLFeatureManager::fetchHTTPTables()
560{
561	fetch_feature_table(FEATURE_TABLE_VER_FILENAME);
562	fetch_gpu_table(GPU_TABLE_VER_FILENAME);
563}
564
565
566void LLFeatureManager::cleanupFeatureTables()
567{
568	std::for_each(mMaskList.begin(), mMaskList.end(), DeletePairedPointer());
569	mMaskList.clear();
570}
571
572void LLFeatureManager::init()
573{
574	// load the tables
575	loadFeatureTables();
576
577	// get the gpu class
578	loadGPUClass();
579
580	// apply the base masks, so we know if anything is disabled
581	applyBaseMasks();
582}
583
584void LLFeatureManager::applyRecommendedSettings()
585{
586	// apply saved settings
587	// cap the level at 2 (high)
588	S32 level = llmax(GPU_CLASS_0, llmin(mGPUClass, GPU_CLASS_2));
589
590	llinfos << "Applying Recommended Features" << llendl;
591
592	setGraphicsLevel(level, false);
593	gSavedSettings.setU32("RenderQualityPerformance", level);
594
595	// now apply the tweaks to draw distance
596	// these are double negatives, because feature masks only work by
597	// downgrading values, so i needed to make a true value go to false
598	// for certain cards, thus the awkward name, "Disregard..."
599	if(!gSavedSettings.getBOOL("Disregard96DefaultDrawDistance"))
600	{
601		gSavedSettings.setF32("RenderFarClip", 96.0f);
602	}
603	else if(!gSavedSettings.getBOOL("Disregard128DefaultDrawDistance"))
604	{
605		gSavedSettings.setF32("RenderFarClip", 128.0f);
606	}
607}
608
609void LLFeatureManager::applyFeatures(bool skipFeatures)
610{
611	// see featuretable.txt / featuretable_linux.txt / featuretable_mac.txt
612
613#ifndef LL_RELEASE_FOR_DOWNLOAD
614	dump();
615#endif
616
617	// scroll through all of these and set their corresponding control value
618	for(feature_map_t::iterator mIt = mFeatures.begin(); 
619		mIt != mFeatures.end(); 
620		++mIt)
621	{
622		// skip features you want to skip
623		// do this for when you don't want to change certain settings
624		if(skipFeatures)
625		{
626			if(mSkippedFeatures.find(mIt->first) != mSkippedFeatures.end())
627			{
628				continue;
629			}
630		}
631
632		// get the control setting
633		LLControlVariable* ctrl = gSavedSettings.getControl(mIt->first);
634		if(ctrl == NULL)
635		{
636			llwarns << "AHHH! Control setting " << mIt->first << " does not exist!" << llendl;
637			continue;
638		}
639
640		// handle all the different types
641		if(ctrl->isType(TYPE_BOOLEAN))
642		{
643			gSavedSettings.setBOOL(mIt->first, (BOOL)getRecommendedValue(mIt->first));
644		}
645		else if (ctrl->isType(TYPE_S32))
646		{
647			gSavedSettings.setS32(mIt->first, (S32)getRecommendedValue(mIt->first));
648		}
649		else if (ctrl->isType(TYPE_U32))
650		{
651			gSavedSettings.setU32(mIt->first, (U32)getRecommendedValue(mIt->first));
652		}
653		else if (ctrl->isType(TYPE_F32))
654		{
655			gSavedSettings.setF32(mIt->first, (F32)getRecommendedValue(mIt->first));
656		}
657		else
658		{
659			llwarns << "AHHH! Control variable is not a numeric type!" << llendl;
660		}
661	}
662}
663
664void LLFeatureManager::setGraphicsLevel(S32 level, bool skipFeatures)
665{
666	LLViewerShaderMgr::sSkipReload = true;
667
668	applyBaseMasks();
669	
670	switch (level)
671	{
672		case 0:
673			maskFeatures("Low");			
674			break;
675		case 1:
676			maskFeatures("Mid");
677			break;
678		case 2:
679			maskFeatures("High");
680			break;
681		case 3:
682			maskFeatures("Ultra");
683			break;
684		default:
685			maskFeatures("Low");
686			break;
687	}
688
689	applyFeatures(skipFeatures);
690
691	LLViewerShaderMgr::sSkipReload = false;
692	LLViewerShaderMgr::instance()->setShaders();
693}
694
695void LLFeatureManager::applyBaseMasks()
696{
697	// reapply masks
698	mFeatures.clear();
699
700	LLFeatureList* maskp = findMask("all");
701	if(maskp == NULL)
702	{
703		LL_WARNS("RenderInit") << "AHH! No \"all\" in feature table!" << LL_ENDL;
704		return;
705	}
706
707	mFeatures = maskp->getFeatures();
708
709	// mask class
710	if (mGPUClass >= 0 && mGPUClass < 4)
711	{
712		const char* class_table[] =
713		{
714			"Class0",
715			"Class1",
716			"Class2",
717			"Class3"
718		};
719
720		LL_INFOS("RenderInit") << "Setting GPU Class to " << class_table[mGPUClass] << LL_ENDL;
721		maskFeatures(class_table[mGPUClass]);
722	}
723	else
724	{
725		LL_INFOS("RenderInit") << "Setting GPU Class to Unknown" << LL_ENDL;
726		maskFeatures("Unknown");
727	}
728
729	// now all those wacky ones
730	if (!gGLManager.mHasFragmentShader)
731	{
732		maskFeatures("NoPixelShaders");
733	}
734	if (!gGLManager.mHasVertexShader || !mGPUSupported)
735	{
736		maskFeatures("NoVertexShaders");
737	}
738	if (gGLManager.mIsNVIDIA)
739	{
740		maskFeatures("NVIDIA");
741	}
742	if (gGLManager.mIsGF2or4MX)
743	{
744		maskFeatures("GeForce2");
745	}
746	if (gGLManager.mIsATI)
747	{
748		maskFeatures("ATI");
749	}
750	if (gGLManager.mHasATIMemInfo && gGLManager.mVRAM < 256)
751	{
752		maskFeatures("ATIVramLT256");
753	}
754	if (gGLManager.mATIOldDriver)
755	{
756		maskFeatures("ATIOldDriver");
757	}
758	if (gGLManager.mIsGFFX)
759	{
760		maskFeatures("GeForceFX");
761	}
762	if (gGLManager.mIsIntel)
763	{
764		maskFeatures("Intel");
765	}
766	if (gGLManager.mGLVersion < 1.5f)
767	{
768		maskFeatures("OpenGLPre15");
769	}
770	if (gGLManager.mGLVersion < 3.f)
771	{
772		maskFeatures("OpenGLPre30");
773	}
774	if (gGLManager.mNumTextureImageUnits <= 8)
775	{
776		maskFeatures("TexUnit8orLess");
777	}
778	if (gGLManager.mHasMapBufferRange)
779	{
780		maskFeatures("MapBufferRange");
781	}
782
783	// now mask by gpu string
784	// Replaces ' ' with '_' in mGPUString to deal with inability for parser to handle spaces
785	std::string gpustr = mGPUString;
786	for (std::string::iterator iter = gpustr.begin(); iter != gpustr.end(); ++iter)
787	{
788		if (*iter == ' ')
789		{
790			*iter = '_';
791		}
792	}
793
794	//llinfos << "Masking features from gpu table match: " << gpustr << llendl;
795	maskFeatures(gpustr);
796
797	// now mask cpu type ones
798	if (gSysMemory.getPhysicalMemoryClamped() <= 256*1024*1024)
799	{
800		maskFeatures("RAM256MB");
801	}
802	
803#if LL_SOLARIS && defined(__sparc) 	//  even low MHz SPARCs are fast
804#error The 800 is hinky. Would something like a LL_MIN_MHZ make more sense here?
805	if (gSysCPU.getMHz() < 800)
806#else
807	if (gSysCPU.getMHz() < 1100)
808#endif
809	{
810		maskFeatures("CPUSlow");
811	}
812
813	if (isSafe())
814	{
815		maskFeatures("safe");
816	}
817}