diff --git a/indra/newview/app_settings/settings.xml b/indra/newview/app_settings/settings.xml
index 4ce5358ade15ea97ee7017e1b68efda309f78549..7ed24236b16639748bc37379155ae90035db22af 100644
--- a/indra/newview/app_settings/settings.xml
+++ b/indra/newview/app_settings/settings.xml
@@ -4161,6 +4161,17 @@
         <key>Value</key>
             <integer>1</integer>
         </map>
+    <key>LastGPUClass</key>
+    <map>
+      <key>Comment</key>
+      <string>[DO NOT MODIFY] previous GPU class for tracking hardware changes</string>
+      <key>Persist</key>
+      <integer>1</integer>
+      <key>Type</key>
+      <string>S32</string>
+      <key>Value</key>
+      <integer>-1</integer>
+    </map>
     <key>LastFeatureVersion</key>
     <map>
       <key>Comment</key>
diff --git a/indra/newview/llfeaturemanager.cpp b/indra/newview/llfeaturemanager.cpp
index 50b08f782a04a100227363acfc0df14dc6ae54e5..4fdb0101620b4415607e7b6524853b55de2bf00a 100644
--- a/indra/newview/llfeaturemanager.cpp
+++ b/indra/newview/llfeaturemanager.cpp
@@ -45,10 +45,13 @@
 #include "llsecondlifeurls.h"
 
 #include "llappviewer.h"
+#include "llhttpclient.h"
+#include "llnotificationsutil.h"
 #include "llviewercontrol.h"
 #include "llworld.h"
 #include "lldrawpoolterrain.h"
 #include "llviewertexturelist.h"
+#include "llversioninfo.h"
 #include "llwindow.h"
 #include "llui.h"
 #include "llcontrol.h"
@@ -62,15 +65,20 @@
 
 #if LL_DARWIN
 const char FEATURE_TABLE_FILENAME[] = "featuretable_mac.txt";
+const char FEATURE_TABLE_VER_FILENAME[] = "featuretable_mac.%s.txt";
 #elif LL_LINUX
 const char FEATURE_TABLE_FILENAME[] = "featuretable_linux.txt";
+const char FEATURE_TABLE_VER_FILENAME[] = "featuretable_linux.%s.txt";
 #elif LL_SOLARIS
 const char FEATURE_TABLE_FILENAME[] = "featuretable_solaris.txt";
+const char FEATURE_TABLE_VER_FILENAME[] = "featuretable_solaris.%s.txt";
 #else
 const char FEATURE_TABLE_FILENAME[] = "featuretable.txt";
+const char FEATURE_TABLE_VER_FILENAME[] = "featuretable.%s.txt";
 #endif
 
 const char GPU_TABLE_FILENAME[] = "gpu_table.txt";
+const char GPU_TABLE_VER_FILENAME[] = "gpu_table.%s.txt";
 
 LLFeatureInfo::LLFeatureInfo(const std::string& name, const BOOL available, const F32 level)
 	: mValid(TRUE), mName(name), mAvailable(available), mRecommendedLevel(level)
@@ -215,22 +223,44 @@ BOOL LLFeatureManager::loadFeatureTables()
 	mSkippedFeatures.insert("RenderVBOEnable");
 	mSkippedFeatures.insert("RenderFogRatio");
 
-	std::string data_path = gDirUtilp->getAppRODataDir();
+	// first table is install with app
+	std::string app_path = gDirUtilp->getAppRODataDir();
+	app_path += gDirUtilp->getDirDelimiter();
+	app_path += FEATURE_TABLE_FILENAME;
 
-	data_path += gDirUtilp->getDirDelimiter();
+	// second table is downloaded with HTTP
+	std::string http_filename = llformat(FEATURE_TABLE_VER_FILENAME, LLVersionInfo::getVersion().c_str());
+	std::string http_path = gDirUtilp->getExpandedFilename(LL_PATH_USER_SETTINGS, http_filename);
 
-	data_path += FEATURE_TABLE_FILENAME;
-	lldebugs << "Looking for feature table in " << data_path << llendl;
+	// use HTTP table if it exists
+	std::string path;
+	if (gDirUtilp->fileExists(http_path))
+	{
+		path = http_path;
+	}
+	else
+	{
+		path = app_path;
+	}
+
+	
+	return parseFeatureTable(path);
+}
+
+
+BOOL LLFeatureManager::parseFeatureTable(std::string filename)
+{
+	llinfos << "Looking for feature table in " << filename << llendl;
 
 	llifstream file;
 	std::string name;
 	U32		version;
 	
-	file.open(data_path); 	 /*Flawfinder: ignore*/
+	file.open(filename); 	 /*Flawfinder: ignore*/
 
 	if (!file)
 	{
-		LL_WARNS("RenderInit") << "Unable to open feature table!" << LL_ENDL;
+		LL_WARNS("RenderInit") << "Unable to open feature table " << filename << LL_ENDL;
 		return FALSE;
 	}
 
@@ -239,7 +269,7 @@ BOOL LLFeatureManager::loadFeatureTables()
 	file >> version;
 	if (name != "version")
 	{
-		LL_WARNS("RenderInit") << data_path << " does not appear to be a valid feature table!" << LL_ENDL;
+		LL_WARNS("RenderInit") << filename << " does not appear to be a valid feature table!" << LL_ENDL;
 		return FALSE;
 	}
 
@@ -302,24 +332,44 @@ BOOL LLFeatureManager::loadFeatureTables()
 
 void LLFeatureManager::loadGPUClass()
 {
-	std::string data_path = gDirUtilp->getAppRODataDir();
-
-	data_path += gDirUtilp->getDirDelimiter();
-
-	data_path += GPU_TABLE_FILENAME;
-
 	// defaults
 	mGPUClass = GPU_CLASS_UNKNOWN;
 	mGPUString = gGLManager.getRawGLString();
 	mGPUSupported = FALSE;
 
+	// first table is in the app dir
+	std::string app_path = gDirUtilp->getAppRODataDir();
+	app_path += gDirUtilp->getDirDelimiter();
+	app_path += GPU_TABLE_FILENAME;
+	
+	// second table is downloaded with HTTP
+	std::string http_filename = llformat(GPU_TABLE_VER_FILENAME, LLVersionInfo::getVersion().c_str());
+	std::string http_path = gDirUtilp->getExpandedFilename(LL_PATH_USER_SETTINGS, http_filename);
+
+	// use HTTP table if it exists
+	std::string path;
+	if (gDirUtilp->fileExists(http_path))
+	{
+		path = http_path;
+	}
+	else
+	{
+		path = app_path;
+	}
+
+	parseGPUTable(path);
+}
+
+	
+void LLFeatureManager::parseGPUTable(std::string filename)
+{
 	llifstream file;
 		
-	file.open(data_path); 		 /*Flawfinder: ignore*/
+	file.open(filename);
 
 	if (!file)
 	{
-		LL_WARNS("RenderInit") << "Unable to open GPU table: " << data_path << "!" << LL_ENDL;
+		LL_WARNS("RenderInit") << "Unable to open GPU table: " << filename << "!" << LL_ENDL;
 		return;
 	}
 
@@ -403,6 +453,70 @@ void LLFeatureManager::loadGPUClass()
 	LL_WARNS("RenderInit") << "Couldn't match GPU to a class: " << gGLManager.getRawGLString() << LL_ENDL;
 }
 
+// responder saves table into file
+class LLHTTPFeatureTableResponder : public LLHTTPClient::Responder
+{
+public:
+
+	LLHTTPFeatureTableResponder(std::string filename) :
+		mFilename(filename)
+	{
+	}
+
+	
+	virtual void completedRaw(U32 status, const std::string& reason,
+							  const LLChannelDescriptors& channels,
+							  const LLIOPipe::buffer_ptr_t& buffer)
+	{
+		if (isGoodStatus(status))
+		{
+			// write to file
+
+			llinfos << "writing feature table to " << mFilename << llendl;
+			
+			S32 file_size = buffer->countAfter(channels.in(), NULL);
+			if (file_size > 0)
+			{
+				// read from buffer
+				U8* copy_buffer = new U8[file_size];
+				buffer->readAfter(channels.in(), NULL, copy_buffer, file_size);
+
+				// write to file
+				LLAPRFile out(mFilename, LL_APR_WB);
+				out.write(copy_buffer, file_size);
+				out.close();
+			}
+		}
+		
+	}
+	
+private:
+	std::string mFilename;
+};
+
+void fetch_table(std::string table)
+{
+	const std::string base       = "http://viewer-settings.s3.amazonaws.com/";
+
+	const std::string filename   = llformat(table.c_str(), LLVersionInfo::getVersion().c_str());
+
+	const std::string url        = base + filename;
+
+	const std::string path       = gDirUtilp->getExpandedFilename(LL_PATH_USER_SETTINGS, filename);
+
+	llinfos << "LLFeatureManager fetching " << url << " into " << path << llendl;
+	
+	LLHTTPClient::get(url, new LLHTTPFeatureTableResponder(path));
+}
+
+// fetch table(s) from a website (S3)
+void LLFeatureManager::fetchHTTPTables()
+{
+	fetch_table(FEATURE_TABLE_VER_FILENAME);
+	fetch_table(GPU_TABLE_VER_FILENAME);
+}
+
+
 void LLFeatureManager::cleanupFeatureTables()
 {
 	std::for_each(mMaskList.begin(), mMaskList.end(), DeletePairedPointer());
diff --git a/indra/newview/llfeaturemanager.h b/indra/newview/llfeaturemanager.h
index dd218d428fa169be4bc7f6eb1472f95ccc2b88f7..c2ecede2c55caecafb8a0e7682ca5a0bf472399b 100644
--- a/indra/newview/llfeaturemanager.h
+++ b/indra/newview/llfeaturemanager.h
@@ -48,6 +48,7 @@ typedef enum EGPUClass
 	GPU_CLASS_3 = 3
 } EGPUClass; 
 
+
 class LLFeatureInfo
 {
 public:
@@ -144,8 +145,13 @@ class LLFeatureManager : public LLFeatureList, public LLSingleton<LLFeatureManag
 	// in the skip list if true
 	void applyFeatures(bool skipFeatures);
 
+	// load the dynamic GPU/feature table from a website
+	void fetchHTTPTables();
+	
 protected:
 	void loadGPUClass();
+	BOOL parseFeatureTable(std::string filename);
+	void parseGPUTable(std::string filename);
 	void initBaseMask();
 
 
diff --git a/indra/newview/llstartup.cpp b/indra/newview/llstartup.cpp
index 466c154f366e2343048cd79fc0797cc9bd514e94..d8b5618d8f85903155693b3c1ff4780808e08b13 100644
--- a/indra/newview/llstartup.cpp
+++ b/indra/newview/llstartup.cpp
@@ -385,13 +385,22 @@ bool idle_startup()
 		{
 			LLNotificationsUtil::add("DisplaySetToRecommended");
 		}
+		else if ((gSavedSettings.getS32("LastGPUClass") != LLFeatureManager::getInstance()->getGPUClass()) &&
+				 (gSavedSettings.getS32("LastGPUClass") != -1))
+		{
+			LLNotificationsUtil::add("DisplaySetToRecommended");
+		}
 		else if (!gViewerWindow->getInitAlert().empty())
 		{
 			LLNotificationsUtil::add(gViewerWindow->getInitAlert());
 		}
 			
 		gSavedSettings.setS32("LastFeatureVersion", LLFeatureManager::getInstance()->getVersion());
+		gSavedSettings.setS32("LastGPUClass", LLFeatureManager::getInstance()->getGPUClass());
 
+		// load dynamic GPU/feature tables from website (S3)
+		LLFeatureManager::getInstance()->fetchHTTPTables();
+		
 		std::string xml_file = LLUI::locateSkin("xui_version.xml");
 		LLXMLNodePtr root;
 		bool xml_ok = false;
diff --git a/indra/newview/llviewerwindow.cpp b/indra/newview/llviewerwindow.cpp
index da4ff1568bef03219c1e4c829cf90ca2e0f276f1..d91f232f0e9f0f1fae38d508b4484fcd468b8019 100644
--- a/indra/newview/llviewerwindow.cpp
+++ b/indra/newview/llviewerwindow.cpp
@@ -1432,6 +1432,7 @@ LLViewerWindow::LLViewerWindow(
 
 	if (LLFeatureManager::getInstance()->isSafe()
 		|| (gSavedSettings.getS32("LastFeatureVersion") != LLFeatureManager::getInstance()->getVersion())
+		|| (gSavedSettings.getS32("LastGPUClass") != LLFeatureManager::getInstance()->getGPUClass())
 		|| (gSavedSettings.getBOOL("ProbeHardwareOnStartup")))
 	{
 		LLFeatureManager::getInstance()->applyRecommendedSettings();