diff --git a/indra/llcommon/llrefcount.cpp b/indra/llcommon/llrefcount.cpp
index 29a5ca6f245cbec59e6be9205607a415fc4e050b..5cbd3464115ea784952d572bab513d3c8045ea31 100644
--- a/indra/llcommon/llrefcount.cpp
+++ b/indra/llcommon/llrefcount.cpp
@@ -29,6 +29,9 @@
 
 #include "llerror.h"
 
+// maximum reference count before sounding memory leak alarm
+const S32 gMaxRefCount = 65536;
+
 LLRefCount::LLRefCount(const LLRefCount& other)
 :	mRef(0)
 {
@@ -47,7 +50,7 @@ LLRefCount::LLRefCount() :
 
 LLRefCount::~LLRefCount()
 { 
-	if (mRef != 0)
+	if (mRef != LL_REFCOUNT_FREE && mRef != 0)
 	{
 		LL_ERRS() << "deleting non-zero reference" << LL_ENDL;
 	}
diff --git a/indra/llcommon/llrefcount.h b/indra/llcommon/llrefcount.h
index 7e4af6ea66a58ca9972583ff29e3060c9ce43269..2080da15653d8e9974b3d2d09aa38755aa981151 100644
--- a/indra/llcommon/llrefcount.h
+++ b/indra/llcommon/llrefcount.h
@@ -37,6 +37,10 @@ class LLMutex;
 // see llthread.h for LLThreadSafeRefCount
 //----------------------------------------------------------------------------
 
+//nonsense but recognizable value for freed LLRefCount (aids in debugging)
+#define LL_REFCOUNT_FREE 1234567890
+extern const S32 gMaxRefCount;
+
 class LL_COMMON_API LLRefCount
 {
 protected:
@@ -47,17 +51,25 @@ class LL_COMMON_API LLRefCount
 public:
 	LLRefCount();
 
+    inline void validateRefCount() const
+    {
+        llassert(mRef > 0); // ref count below 0, likely corrupted
+        llassert(mRef < gMaxRefCount); // ref count excessive, likely memory leak
+    }
+
 	inline void ref() const
 	{ 
 		mRef++; 
+        validateRefCount();
 	} 
 
 	inline S32 unref() const
 	{
-		llassert(mRef >= 1);
+        validateRefCount();
 		if (0 == --mRef)
 		{
-			delete this; 
+            mRef = LL_REFCOUNT_FREE; // set to nonsense yet recognizable value to aid in debugging
+			delete this;
 			return 0;
 		}
 		return mRef;
diff --git a/indra/llimage/llimage.cpp b/indra/llimage/llimage.cpp
index 15b07e53189875f96c1b38796e15fce7e5e65fc7..5fa19ce9c6f4dc2b3123751dbebcecda321a9ccf 100644
--- a/indra/llimage/llimage.cpp
+++ b/indra/llimage/llimage.cpp
@@ -860,6 +860,12 @@ U8* LLImageRaw::reallocateData(S32 size)
 	return res;
 }
 
+void LLImageRaw::releaseData()
+{
+    LLImageBase::setSize(0, 0, 0);
+    LLImageBase::setDataAndSize(nullptr, 0);
+}
+
 // virtual
 void LLImageRaw::deleteData()
 {
diff --git a/indra/llimage/llimage.h b/indra/llimage/llimage.h
index 354926ee5816c17e6a2a649c9ac0311996e36016..7a588cfb0370347ae57e7285e31a0b64333c8af5 100644
--- a/indra/llimage/llimage.h
+++ b/indra/llimage/llimage.h
@@ -191,6 +191,12 @@ class LLImageRaw : public LLImageBase
 	/*virtual*/ void deleteData();
 	/*virtual*/ U8* allocateData(S32 size = -1);
 	/*virtual*/ U8* reallocateData(S32 size);
+
+    // use in conjunction with "no_copy" constructor to release data pointer before deleting
+    // so that deletion of this LLImageRaw will not free the memory at the "data" parameter 
+    // provided to "no_copy" constructor
+    void releaseData();
+
 	
 	bool resize(U16 width, U16 height, S8 components);
 
diff --git a/indra/llmath/llvolume.cpp b/indra/llmath/llvolume.cpp
index fd20024f7ec15f20bb5d90d8763839b49469184d..efa1a4076b1e7f4e38398957af19bb427e52f0b1 100644
--- a/indra/llmath/llvolume.cpp
+++ b/indra/llmath/llvolume.cpp
@@ -88,7 +88,7 @@ const F32 SKEW_MAX	=  0.95f;
 const F32 SCULPT_MIN_AREA = 0.002f;
 const S32 SCULPT_MIN_AREA_DETAIL = 1;
 
-BOOL gDebugGL = FALSE;
+BOOL gDebugGL = FALSE; // See settings.xml "RenderDebugGL"
 
 BOOL check_same_clock_dir( const LLVector3& pt1, const LLVector3& pt2, const LLVector3& pt3, const LLVector3& norm)
 {    
diff --git a/indra/llrender/llgl.cpp b/indra/llrender/llgl.cpp
index 12da961cef12314dafcac614869ca99c83143ef1..639d1fba32ab9794ca2fc95b86c16f843cd3ed3a 100644
--- a/indra/llrender/llgl.cpp
+++ b/indra/llrender/llgl.cpp
@@ -757,17 +757,6 @@ bool LLGLManager::initGL()
 
 	stop_glerror();
 
-#if LL_WINDOWS
-	if (mHasDebugOutput && gDebugGL)
-	{ //setup debug output callback
-		//glDebugMessageControlARB(GL_DONT_CARE, GL_DONT_CARE, GL_DEBUG_SEVERITY_LOW_ARB, 0, NULL, GL_TRUE);
-		glDebugMessageCallbackARB((GLDEBUGPROCARB) gl_debug_callback, NULL);
-		glEnable(GL_DEBUG_OUTPUT_SYNCHRONOUS_ARB);
-	}
-#endif
-
-	stop_glerror();
-
 	//HACK always disable texture multisample, use FXAA instead
 	mHasTextureMultisample = FALSE;
 #if LL_WINDOWS
@@ -1029,7 +1018,9 @@ void LLGLManager::initExtensions()
 	mHasSync = ExtensionExists("GL_ARB_sync", gGLHExts.mSysExts);
 	mHasMapBufferRange = ExtensionExists("GL_ARB_map_buffer_range", gGLHExts.mSysExts);
 	mHasFlushBufferRange = ExtensionExists("GL_APPLE_flush_buffer_range", gGLHExts.mSysExts);
-	mHasDepthClamp = ExtensionExists("GL_ARB_depth_clamp", gGLHExts.mSysExts) || ExtensionExists("GL_NV_depth_clamp", gGLHExts.mSysExts);
+    // NOTE: Using extensions breaks reflections when Shadows are set to projector.  See: SL-16727
+    //mHasDepthClamp = ExtensionExists("GL_ARB_depth_clamp", gGLHExts.mSysExts) || ExtensionExists("GL_NV_depth_clamp", gGLHExts.mSysExts);
+    mHasDepthClamp = FALSE;
 	// mask out FBO support when packed_depth_stencil isn't there 'cause we need it for LLRenderTarget -Brad
 #ifdef GL_ARB_framebuffer_object
 	mHasFramebufferObject = ExtensionExists("GL_ARB_framebuffer_object", gGLHExts.mSysExts);
@@ -2159,95 +2150,6 @@ LLGLUserClipPlane::~LLGLUserClipPlane()
 	disable();
 }
 
-LLGLNamePool::LLGLNamePool()
-{
-}
-
-LLGLNamePool::~LLGLNamePool()
-{
-}
-
-void LLGLNamePool::upkeep()
-{
-	std::sort(mNameList.begin(), mNameList.end(), CompareUsed());
-}
-
-void LLGLNamePool::cleanup()
-{
-	for (name_list_t::iterator iter = mNameList.begin(); iter != mNameList.end(); ++iter)
-	{
-		releaseName(iter->name);
-	}
-
-	mNameList.clear();
-}
-
-GLuint LLGLNamePool::allocate()
-{
-    LL_PROFILE_ZONE_SCOPED_CATEGORY_PIPELINE;
-#if LL_GL_NAME_POOLING
-	for (name_list_t::iterator iter = mNameList.begin(); iter != mNameList.end(); ++iter)
-	{
-		if (!iter->used)
-		{
-			iter->used = TRUE;
-			return iter->name;
-		}
-	}
-
-	NameEntry entry;
-	entry.name = allocateName();
-	entry.used = TRUE;
-	mNameList.push_back(entry);
-
-	return entry.name;
-#else
-	return allocateName();
-#endif
-}
-
-void LLGLNamePool::release(GLuint name)
-{
-#if LL_GL_NAME_POOLING
-	for (name_list_t::iterator iter = mNameList.begin(); iter != mNameList.end(); ++iter)
-	{
-		if (iter->name == name)
-		{
-			if (iter->used)
-			{
-				iter->used = FALSE;
-				return;
-			}
-			else
-			{
-				LL_ERRS() << "Attempted to release a pooled name that is not in use!" << LL_ENDL;
-			}
-		}
-	}
-	LL_ERRS() << "Attempted to release a non pooled name!" << LL_ENDL;
-#else
-	releaseName(name);
-#endif
-}
-
-//static
-void LLGLNamePool::upkeepPools()
-{
-	for (auto& pool : instance_snapshot())
-	{
-		pool.upkeep();
-	}
-}
-
-//static
-void LLGLNamePool::cleanupPools()
-{
-	for (auto& pool : instance_snapshot())
-	{
-		pool.cleanup();
-	}
-}
-
 LLGLDepthTest::LLGLDepthTest(GLboolean depth_enabled, GLboolean write_enabled, GLenum depth_func)
 : mPrevDepthEnabled(sDepthEnabled), mPrevDepthFunc(sDepthFunc), mPrevWriteEnabled(sWriteEnabled)
 {
@@ -2469,3 +2371,4 @@ extern "C"
 }
 #endif
 
+
diff --git a/indra/llrender/llgl.h b/indra/llrender/llgl.h
index ec97eb0faaf864834268a3b6298fe39b419028d4..52338364e61ef5b129d254df81d31daa780e7931 100644
--- a/indra/llrender/llgl.h
+++ b/indra/llrender/llgl.h
@@ -366,51 +366,6 @@ class LLGLSquashToFarClip
 	~LLGLSquashToFarClip();
 };
 
-/*
-	Generic pooling scheme for things which use GL names (used for occlusion queries and vertex buffer objects).
-	Prevents thrashing of GL name caches by avoiding calls to glGenFoo and glDeleteFoo.
-*/
-class LLGLNamePool : public LLInstanceTracker<LLGLNamePool>
-{
-public:
-	typedef LLInstanceTracker<LLGLNamePool> tracker_t;
-
-	struct NameEntry
-	{
-		GLuint name;
-		BOOL used;
-	};
-
-	struct CompareUsed
-	{
-		bool operator()(const NameEntry& lhs, const NameEntry& rhs)
-		{
-			return lhs.used < rhs.used;  //FALSE entries first
-		}
-	};
-
-	typedef std::vector<NameEntry> name_list_t;
-	name_list_t mNameList;
-
-	LLGLNamePool();
-	virtual ~LLGLNamePool();
-	
-	void upkeep();
-	void cleanup();
-	
-	GLuint allocate();
-	void release(GLuint name);
-	
-	static void upkeepPools();
-	static void cleanupPools();
-
-protected:
-	typedef std::vector<LLGLNamePool*> pool_list_t;
-	
-	virtual GLuint allocateName() = 0;
-	virtual void releaseName(GLuint name) = 0;
-};
-
 /*
 	Interface for objects that need periodic GL updates applied to them.
 	Used to synchronize GL updates with GL thread.
diff --git a/indra/llrender/llgltexture.cpp b/indra/llrender/llgltexture.cpp
index b6a02f1c0a0ffa133097e7671f3d55000cfffc78..e012eb9a62f4a4e758e6c2beefe4ae968135b1ab 100644
--- a/indra/llrender/llgltexture.cpp
+++ b/indra/llrender/llgltexture.cpp
@@ -164,11 +164,11 @@ BOOL LLGLTexture::createGLTexture()
 	return mGLTexturep->createGLTexture() ;
 }
 
-BOOL LLGLTexture::createGLTexture(S32 discard_level, const LLImageRaw* imageraw, S32 usename, BOOL to_create, S32 category)
+BOOL LLGLTexture::createGLTexture(S32 discard_level, const LLImageRaw* imageraw, S32 usename, BOOL to_create, S32 category, bool defer_copy, LLGLuint* tex_name)
 {
-	llassert(mGLTexturep.notNull()) ;	
+	llassert(mGLTexturep.notNull());
 
-	BOOL ret = mGLTexturep->createGLTexture(discard_level, imageraw, usename, to_create, category) ;
+	BOOL ret = mGLTexturep->createGLTexture(discard_level, imageraw, usename, to_create, category, defer_copy, tex_name) ;
 
 	if(ret)
 	{
@@ -260,20 +260,20 @@ LLTexUnit::eTextureType LLGLTexture::getTarget(void) const
 	return mGLTexturep->getTarget() ;
 }
 
-BOOL LLGLTexture::setSubImage(const LLImageRaw* imageraw, S32 x_pos, S32 y_pos, S32 width, S32 height)
+BOOL LLGLTexture::setSubImage(const LLImageRaw* imageraw, S32 x_pos, S32 y_pos, S32 width, S32 height, LLGLuint use_name)
 {
     LL_PROFILE_ZONE_SCOPED_CATEGORY_TEXTURE;
 	llassert(mGLTexturep.notNull()) ;
 
-	return mGLTexturep->setSubImage(imageraw, x_pos, y_pos, width, height) ;
+	return mGLTexturep->setSubImage(imageraw, x_pos, y_pos, width, height, 0, use_name) ;
 }
 
-BOOL LLGLTexture::setSubImage(const U8* datap, S32 data_width, S32 data_height, S32 x_pos, S32 y_pos, S32 width, S32 height)
+BOOL LLGLTexture::setSubImage(const U8* datap, S32 data_width, S32 data_height, S32 x_pos, S32 y_pos, S32 width, S32 height, LLGLuint use_name)
 {
     LL_PROFILE_ZONE_SCOPED_CATEGORY_TEXTURE;
 	llassert(mGLTexturep.notNull()) ;
 
-	return mGLTexturep->setSubImage(datap, data_width, data_height, x_pos, y_pos, width, height) ;
+	return mGLTexturep->setSubImage(datap, data_width, data_height, x_pos, y_pos, width, height, 0, use_name) ;
 }
 
 void LLGLTexture::setGLTextureCreated (bool initialized)
diff --git a/indra/llrender/llgltexture.h b/indra/llrender/llgltexture.h
index 028457c510174c45cbc61dc19800325ef305a86a..8cfe7b62de4f6e35bcb4db87f876a693fe5d7312 100644
--- a/indra/llrender/llgltexture.h
+++ b/indra/llrender/llgltexture.h
@@ -124,13 +124,22 @@ class LLGLTexture : public LLTexture
 	BOOL       hasGLTexture() const ;
 	LLGLuint   getTexName() const ;		
 	BOOL       createGLTexture() ;
-	BOOL       createGLTexture(S32 discard_level, const LLImageRaw* imageraw, S32 usename = 0, BOOL to_create = TRUE, S32 category = LLGLTexture::OTHER);
+	
+    // Create a GL Texture from an image raw
+    // discard_level - mip level, 0 for highest resultion mip
+    // imageraw - the image to copy from
+    // usename - explicit GL name override
+    // to_create - set to FALSE to force gl texture to not be created
+    // category - LLGLTexture category for this LLGLTexture
+    // defer_copy - set to true to allocate GL texture but NOT initialize with imageraw data
+    // tex_name - if not null, will be set to the GL name of the texture created
+    BOOL       createGLTexture(S32 discard_level, const LLImageRaw* imageraw, S32 usename = 0, BOOL to_create = TRUE, S32 category = LLGLTexture::OTHER, bool defer_copy = false, LLGLuint* tex_name = nullptr);
 
 	void       setFilteringOption(LLTexUnit::eTextureFilterOptions option);
 	void       setExplicitFormat(LLGLint internal_format, LLGLenum primary_format, LLGLenum type_format = 0, BOOL swap_bytes = FALSE);
 	void       setAddressMode(LLTexUnit::eTextureAddressMode mode);
-	BOOL       setSubImage(const LLImageRaw* imageraw, S32 x_pos, S32 y_pos, S32 width, S32 height);
-	BOOL       setSubImage(const U8* datap, S32 data_width, S32 data_height, S32 x_pos, S32 y_pos, S32 width, S32 height);
+	BOOL       setSubImage(const LLImageRaw* imageraw, S32 x_pos, S32 y_pos, S32 width, S32 height, LLGLuint use_name = 0);
+	BOOL       setSubImage(const U8* datap, S32 data_width, S32 data_height, S32 x_pos, S32 y_pos, S32 width, S32 height, LLGLuint use_name = 0);
 	void       setGLTextureCreated (bool initialized);
 	void       setCategory(S32 category) ;
     void       setTexName(LLGLuint); // for forcing w/ externally created textures only
diff --git a/indra/llrender/llimagegl.cpp b/indra/llrender/llimagegl.cpp
index 1e9b9f642e04b741f71f9e03ee3581dda5b8e8e0..f43671dee5c23cb53937442fd2c3eef3244ce0de 100644
--- a/indra/llrender/llimagegl.cpp
+++ b/indra/llrender/llimagegl.cpp
@@ -40,6 +40,7 @@
 #include "llglslshader.h"
 #include "llrender.h"
 #include "llwindow.h"
+#include "llframetimer.h"
 
 #if !LL_IMAGEGL_THREAD_CHECK
 #define checkActiveThread()
@@ -436,7 +437,7 @@ LLImageGL::LLImageGL(
 
 LLImageGL::~LLImageGL()
 {
-    if (!mExternalTexture)
+    if (!mExternalTexture && gGLManager.mInited)
     {
 	    LLImageGL::cleanup();
 	    sImageList.erase(this);
@@ -550,7 +551,7 @@ bool LLImageGL::setSize(S32 width, S32 height, S32 ncomponents, S32 discard_leve
 	if (width != mWidth || height != mHeight || ncomponents != mComponents)
 	{
 		// Check if dimensions are a power of two!
-		if (!checkSize(width,height))
+		if (!checkSize(width, height))
 		{
 			LL_WARNS() << llformat("Texture has non power of two dimension: %dx%d",width,height) << LL_ENDL;
 			return false;
@@ -674,7 +675,7 @@ void LLImageGL::setImage(const LLImageRaw* imageraw)
 	setImage(rawdata, FALSE);
 }
 
-BOOL LLImageGL::setImage(const U8* data_in, BOOL data_hasmips, S32 usename)
+BOOL LLImageGL::setImage(const U8* data_in, BOOL data_hasmips /* = FALSE */, S32 usename /* = 0 */)
 {
     LL_PROFILE_ZONE_SCOPED_CATEGORY_TEXTURE;
 	bool is_compressed = false;
@@ -709,8 +710,14 @@ BOOL LLImageGL::setImage(const U8* data_in, BOOL data_hasmips, S32 usename)
 	
     gGL.getTexUnit(0)->bind(this, false, false, usename);
 
-
-    if (mUseMipMaps)
+    if (data_in == nullptr)
+    {
+        S32 w = getWidth();
+        S32 h = getHeight();
+        LLImageGL::setManualImage(mTarget, 0, mFormatInternal, w, h,
+            mFormatPrimary, mFormatType, (GLvoid*)data_in, mAllowCompression);
+    }
+    else if (mUseMipMaps)
 	{
 		if (data_hasmips)
 		{
@@ -1069,14 +1076,15 @@ void LLImageGL::postAddToAtlas()
 	stop_glerror();	
 }
 
-BOOL LLImageGL::setSubImage(const U8* datap, S32 data_width, S32 data_height, S32 x_pos, S32 y_pos, S32 width, S32 height, BOOL force_fast_update)
+BOOL LLImageGL::setSubImage(const U8* datap, S32 data_width, S32 data_height, S32 x_pos, S32 y_pos, S32 width, S32 height, BOOL force_fast_update /* = FALSE */, LLGLuint use_name)
 {
     LL_PROFILE_ZONE_SCOPED_CATEGORY_TEXTURE;
 	if (!width || !height)
 	{
 		return TRUE;
 	}
-	if (mTexName == 0)
+    LLGLuint tex_name = use_name != 0 ? use_name : mTexName;
+	if (0 == tex_name)
 	{
 		// *TODO: Re-enable warning?  Ran into thread locking issues? DK 2011-02-18
 		//LL_WARNS() << "Setting subimage on image without GL texture" << LL_ENDL;
@@ -1092,7 +1100,7 @@ BOOL LLImageGL::setSubImage(const U8* datap, S32 data_width, S32 data_height, S3
 	// HACK: allow the caller to explicitly force the fast path (i.e. using glTexSubImage2D here instead of calling setImage) even when updating the full texture.
 	if (!force_fast_update && x_pos == 0 && y_pos == 0 && width == getWidth() && height == getHeight() && data_width == width && data_height == height)
 	{
-		setImage(datap, FALSE);
+		setImage(datap, FALSE, tex_name);
 	}
 	else
 	{
@@ -1144,12 +1152,11 @@ BOOL LLImageGL::setSubImage(const U8* datap, S32 data_width, S32 data_height, S3
 
 		datap += (y_pos * data_width + x_pos) * getComponents();
 		// Update the GL texture
-		BOOL res = gGL.getTexUnit(0)->bindManual(mBindTarget, mTexName);
+		BOOL res = gGL.getTexUnit(0)->bindManual(mBindTarget, tex_name);
 		if (!res) LL_ERRS() << "LLImageGL::setSubImage(): bindTexture failed" << LL_ENDL;
 		stop_glerror();
 
-		glTexSubImage2D(mTarget, 0, x_pos, y_pos, 
-						width, height, mFormatPrimary, mFormatType, datap);
+		glTexSubImage2D(mTarget, 0, x_pos, y_pos, width, height, mFormatPrimary, mFormatType, datap);
 		gGL.getTexUnit(0)->disable();
 		stop_glerror();
 
@@ -1166,10 +1173,10 @@ BOOL LLImageGL::setSubImage(const U8* datap, S32 data_width, S32 data_height, S3
 	return TRUE;
 }
 
-BOOL LLImageGL::setSubImage(const LLImageRaw* imageraw, S32 x_pos, S32 y_pos, S32 width, S32 height, BOOL force_fast_update)
+BOOL LLImageGL::setSubImage(const LLImageRaw* imageraw, S32 x_pos, S32 y_pos, S32 width, S32 height, BOOL force_fast_update /* = FALSE */, LLGLuint use_name)
 {
     LL_PROFILE_ZONE_SCOPED_CATEGORY_TEXTURE;
-	return setSubImage(imageraw->getData(), imageraw->getWidth(), imageraw->getHeight(), x_pos, y_pos, width, height, force_fast_update);
+	return setSubImage(imageraw->getData(), imageraw->getWidth(), imageraw->getHeight(), x_pos, y_pos, width, height, force_fast_update, use_name);
 }
 
 // Copy sub image from frame buffer
@@ -1356,7 +1363,7 @@ BOOL LLImageGL::createGLTexture()
 	return TRUE ;
 }
 
-BOOL LLImageGL::createGLTexture(S32 discard_level, const LLImageRaw* imageraw, S32 usename/*=0*/, BOOL to_create, S32 category)
+BOOL LLImageGL::createGLTexture(S32 discard_level, const LLImageRaw* imageraw, S32 usename/*=0*/, BOOL to_create, S32 category, bool defer_copy, LLGLuint* tex_name)
 {
     LL_PROFILE_ZONE_SCOPED_CATEGORY_TEXTURE;
     checkActiveThread();
@@ -1386,6 +1393,7 @@ BOOL LLImageGL::createGLTexture(S32 discard_level, const LLImageRaw* imageraw, S
 	// Actual image width/height = raw image width/height * 2^discard_level
 	S32 raw_w = imageraw->getWidth() ;
 	S32 raw_h = imageraw->getHeight() ;
+
 	S32 w = raw_w << discard_level;
 	S32 h = raw_h << discard_level;
 
@@ -1468,15 +1476,26 @@ BOOL LLImageGL::createGLTexture(S32 discard_level, const LLImageRaw* imageraw, S
 
 	setCategory(category);
  	const U8* rawdata = imageraw->getData();
-	return createGLTexture(discard_level, rawdata, FALSE, usename);
+	return createGLTexture(discard_level, rawdata, FALSE, usename, defer_copy, tex_name);
 }
 
-BOOL LLImageGL::createGLTexture(S32 discard_level, const U8* data_in, BOOL data_hasmips, S32 usename)
+BOOL LLImageGL::createGLTexture(S32 discard_level, const U8* data_in, BOOL data_hasmips, S32 usename, bool defer_copy, LLGLuint* tex_name)
+// Call with void data, vmem is allocated but unitialized
 {
     LL_PROFILE_ZONE_SCOPED_CATEGORY_TEXTURE;
     checkActiveThread();
 
-    llassert(data_in);
+    bool main_thread = on_main_thread();
+
+    if (defer_copy)
+    {
+        data_in = nullptr;
+    }
+    else
+    {
+        llassert(data_in);
+    }
+
     stop_glerror();
 
     if (discard_level < 0)
@@ -1486,28 +1505,41 @@ BOOL LLImageGL::createGLTexture(S32 discard_level, const U8* data_in, BOOL data_
     }
     discard_level = llclamp(discard_level, 0, (S32)mMaxDiscardLevel);
 
-    if (mTexName != 0 && discard_level == mCurrentDiscardLevel)
+    if (main_thread // <--- always force creation of new_texname when not on main thread ...
+        && !defer_copy // <--- ... or defer copy is set
+        && mTexName != 0 && discard_level == mCurrentDiscardLevel)
     {
+        LL_PROFILE_ZONE_NAMED("cglt - early setImage");
         // This will only be true if the size has not changed
+        if (tex_name != nullptr)
+        {
+            *tex_name = mTexName;
+        }
         return setImage(data_in, data_hasmips);
     }
 
     GLuint old_texname = mTexName;
-    
+    GLuint new_texname = 0;
     if (usename != 0)
     {
-        mNewTexName = usename;
+        llassert(main_thread);
+        new_texname = usename;
     }
     else
     {
-        LLImageGL::generateTextures(1, &mNewTexName);
+        LLImageGL::generateTextures(1, &new_texname);
         {
-            gGL.getTexUnit(0)->bind(this, false, false, mNewTexName);
+            gGL.getTexUnit(0)->bind(this, false, false, new_texname);
             glTexParameteri(LLTexUnit::getInternalType(mBindTarget), GL_TEXTURE_BASE_LEVEL, 0);
             glTexParameteri(LLTexUnit::getInternalType(mBindTarget), GL_TEXTURE_MAX_LEVEL, mMaxDiscardLevel - discard_level);
         }
     }
-    
+
+    if (tex_name != nullptr)
+    {
+        *tex_name = new_texname;
+    }
+
     if (mUseMipMaps)
     {
         mAutoGenMips = gGLManager.mHasMipMapGeneration;
@@ -1515,9 +1547,12 @@ BOOL LLImageGL::createGLTexture(S32 discard_level, const U8* data_in, BOOL data_
 
     mCurrentDiscardLevel = discard_level;
 
-    if (!setImage(data_in, data_hasmips, mNewTexName))
     {
-        return FALSE;
+        LL_PROFILE_ZONE_NAMED("cglt - late setImage");
+        if (!setImage(data_in, data_hasmips, new_texname))
+        {
+            return FALSE;
+        }
     }
 
     // Set texture options to our defaults.
@@ -1534,47 +1569,23 @@ BOOL LLImageGL::createGLTexture(S32 discard_level, const U8* data_in, BOOL data_
     }
 
     //if we're on the image loading thread, be sure to delete old_texname and update mTexName on the main thread
-    if (! on_main_thread())
+    if (!defer_copy)
     {
+        if (!main_thread)
         {
-            LL_PROFILE_ZONE_NAMED("cglt - sync");
-            if (gGLManager.mHasSync)
-            {
-                auto sync = glFenceSync(GL_SYNC_GPU_COMMANDS_COMPLETE, 0);
-                glClientWaitSync(sync, 0, 0);
-                glDeleteSync(sync);
-            }
-            else
-            {
-                glFinish();
-            }
+            syncToMainThread(new_texname);
         }
-
-        ref();
-        LL::WorkQueue::postMaybe(
-            mMainQueue,
-            [=]()
-            {
-                LL_PROFILE_ZONE_NAMED("cglt - delete callback");
-                if (old_texname != 0)
-                {
-                    LLImageGL::deleteTextures(1, &old_texname);
-                }
-                mTexName = mNewTexName;
-                mNewTexName = 0;
-                unref();
-            });
-    }
-    else 
-    {
-        //not on background thread, immediately set mTexName
-        if (old_texname != 0)
+        else
         {
-            LLImageGL::deleteTextures(1, &old_texname);
+            //not on background thread, immediately set mTexName
+            if (old_texname != 0 && old_texname != new_texname)
+            {
+                LLImageGL::deleteTextures(1, &old_texname);
+            }
+            mTexName = new_texname;
         }
-        mTexName = mNewTexName;
-        mNewTexName = 0;
     }
+
     
     mTextureMemory = (S32Bytes)getMipBytes(mCurrentDiscardLevel);
     sGlobalTextureMemory += mTextureMemory;
@@ -1587,6 +1598,110 @@ BOOL LLImageGL::createGLTexture(S32 discard_level, const U8* data_in, BOOL data_
     return TRUE;
 }
 
+void LLImageGLThread::updateClass()
+{
+    LL_PROFILE_ZONE_SCOPED;
+
+    // update available vram one per second
+    static LLFrameTimer sTimer;
+
+    if (sTimer.getElapsedSeconds() < 1.f)
+    {
+        return;
+    }
+    
+    sTimer.reset();
+
+    auto func = []()
+    {
+        if (gGLManager.mHasATIMemInfo)
+        {
+            S32 meminfo[4];
+            glGetIntegerv(GL_TEXTURE_FREE_MEMORY_ATI, meminfo);
+            LLImageGLThread::sFreeVRAMMegabytes = meminfo[0];
+
+        }
+        else if (gGLManager.mHasNVXMemInfo)
+        {
+            S32 free_memory;
+            glGetIntegerv(GL_GPU_MEMORY_INFO_CURRENT_AVAILABLE_VIDMEM_NVX, &free_memory);
+            LLImageGLThread::sFreeVRAMMegabytes = free_memory / 1024;
+        }
+    };
+
+    
+    // post update to background thread if available, otherwise execute immediately
+    auto queue = LL::WorkQueue::getInstance("LLImageGL");
+    if (sEnabled)
+    {
+        queue->post(func);
+    }
+    else
+    {
+        llassert(queue == nullptr);
+        func();
+    }
+}
+
+void LLImageGL::syncToMainThread(LLGLuint new_tex_name)
+{
+    LL_PROFILE_ZONE_SCOPED;
+    llassert(!on_main_thread());
+
+    {
+        LL_PROFILE_ZONE_NAMED("cglt - sync");
+        if (gGLManager.mHasSync)
+        {
+            // post a sync to the main thread (will execute before tex name swap lambda below)
+            // glFlush calls here are partly superstitious and partly backed by observation
+            // on AMD hardware
+            glFlush();
+            auto sync = glFenceSync(GL_SYNC_GPU_COMMANDS_COMPLETE, 0);
+            glFlush();
+            LL::WorkQueue::postMaybe(
+                mMainQueue,
+                [=]()
+                {
+                    LL_PROFILE_ZONE_NAMED("cglt - wait sync");
+                    {
+                        LL_PROFILE_ZONE_NAMED("glWaitSync");
+                        glWaitSync(sync, 0, GL_TIMEOUT_IGNORED);
+                    }
+                    {
+                        LL_PROFILE_ZONE_NAMED("glDeleteSync");
+                        glDeleteSync(sync);
+                    }
+                });
+        }
+        else
+        {
+            glFinish();
+        }
+    }
+
+    ref();
+    LL::WorkQueue::postMaybe(
+        mMainQueue,
+        [=]()
+        {
+            LL_PROFILE_ZONE_NAMED("cglt - delete callback");
+            syncTexName(new_tex_name);
+            unref();
+        });
+}
+
+void LLImageGL::syncTexName(LLGLuint texname)
+{
+    if (texname != 0)
+    {
+        if (mTexName != 0 && mTexName != texname)
+        {
+            LLImageGL::deleteTextures(1, &mTexName);
+        }
+        mTexName = texname;
+    }
+}
+
 BOOL LLImageGL::readBackRaw(S32 discard_level, LLImageRaw* imageraw, bool compressed_ok) const
 {
 	llassert_always(sAllowReadBackRaw) ;
@@ -1714,7 +1829,7 @@ void LLImageGL::destroyGLTexture()
 		mCurrentDiscardLevel = -1 ; //invalidate mCurrentDiscardLevel.
 		mTexName = 0;		
 		mGLTextureCreated = FALSE ;
-	}	
+	}
 }
 
 //force to invalidate the gl texture, most likely a sculpty texture
@@ -2257,6 +2372,8 @@ void LLImageGL::checkActiveThread()
 		glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_LEVEL,  nummips);
 */  
 
+std::atomic<S32> LLImageGLThread::sFreeVRAMMegabytes(4096); //if free vram is unknown, default to 4GB
+
 LLImageGLThread::LLImageGLThread(LLWindow* window)
     // We want exactly one thread, but a very large capacity: we never want
     // anyone, especially inner-loop render code, to have to block on post()
@@ -2283,3 +2400,9 @@ void LLImageGLThread::run()
     gGL.shutdown();
     mWindow->destroySharedContext(mContext);
 }
+
+S32 LLImageGLThread::getFreeVRAMMegabytes()
+{
+    return sFreeVRAMMegabytes;
+}
+
diff --git a/indra/llrender/llimagegl.h b/indra/llrender/llimagegl.h
index d6f4b13a517fd59b08970d4d531a99d177e67e25..4d5b60d6bc7c8db350aa7b9db4748a9306bbc00a 100644
--- a/indra/llrender/llimagegl.h
+++ b/indra/llrender/llimagegl.h
@@ -110,14 +110,18 @@ class LLImageGL : public LLRefCount
     
 	BOOL createGLTexture() ;
 	BOOL createGLTexture(S32 discard_level, const LLImageRaw* imageraw, S32 usename = 0, BOOL to_create = TRUE,
-		S32 category = sMaxCategories-1);
-	BOOL createGLTexture(S32 discard_level, const U8* data, BOOL data_hasmips = FALSE, S32 usename = 0);
+		S32 category = sMaxCategories-1, bool defer_copy = false, LLGLuint* tex_name = nullptr);
+	BOOL createGLTexture(S32 discard_level, const U8* data, BOOL data_hasmips = FALSE, S32 usename = 0, bool defer_copy = false, LLGLuint* tex_name = nullptr);
 	void setImage(const LLImageRaw* imageraw);
 	BOOL setImage(const U8* data_in, BOOL data_hasmips = FALSE, S32 usename = 0);
-	BOOL setSubImage(const LLImageRaw* imageraw, S32 x_pos, S32 y_pos, S32 width, S32 height, BOOL force_fast_update = FALSE);
-	BOOL setSubImage(const U8* datap, S32 data_width, S32 data_height, S32 x_pos, S32 y_pos, S32 width, S32 height, BOOL force_fast_update = FALSE);
+	BOOL setSubImage(const LLImageRaw* imageraw, S32 x_pos, S32 y_pos, S32 width, S32 height, BOOL force_fast_update = FALSE, LLGLuint use_name = 0);
+	BOOL setSubImage(const U8* datap, S32 data_width, S32 data_height, S32 x_pos, S32 y_pos, S32 width, S32 height, BOOL force_fast_update = FALSE, LLGLuint use_name = 0);
 	BOOL setSubImageFromFrameBuffer(S32 fb_x, S32 fb_y, S32 x_pos, S32 y_pos, S32 width, S32 height);
-	
+
+    // wait for gl commands to finish on current thread and push
+    // a lambda to main thread to swap mNewTexName and mTexName
+    void syncToMainThread(LLGLuint new_tex_name);
+
 	// Read back a raw image for this discard level, if it exists
 	BOOL readBackRaw(S32 discard_level, LLImageRaw* imageraw, bool compressed_ok) const;
 	void destroyGLTexture();
@@ -220,7 +224,7 @@ class LLImageGL : public LLRefCount
 
 	bool     mGLTextureCreated ;
 	LLGLuint mTexName;
-    LLGLuint mNewTexName = 0; // tex name set by background thread to be applied in main thread
+    //LLGLuint mNewTexName = 0; // tex name set by background thread to be applied in main thread
 	U16      mWidth;
 	U16      mHeight;
 	S8       mCurrentDiscardLevel;
@@ -300,6 +304,9 @@ class LLImageGL : public LLRefCount
 	
     void setTexName(GLuint texName) { mTexName = texName; }
 
+    //similar to setTexName, but will call deleteTextures on mTexName if mTexName is not 0 or texname
+    void syncTexName(LLGLuint texname);
+
 	//for debug use: show texture size distribution 
 	//----------------------------------------
 	static S32 sCurTexSizeBar ;
@@ -319,6 +326,12 @@ class LLImageGLThread : public LLSimpleton<LLImageGLThread>, LL::ThreadPool
 public:
     // follows gSavedSettings "RenderGLMultiThreaded"
     static bool sEnabled;
+    
+    // app should call this function periodically
+    static void updateClass();
+
+    // free video memory in megabytes
+    static std::atomic<S32> sFreeVRAMMegabytes;
 
     LLImageGLThread(LLWindow* window);
 
@@ -331,6 +344,8 @@ class LLImageGLThread : public LLSimpleton<LLImageGLThread>, LL::ThreadPool
 
     void run() override;
 
+    static S32 getFreeVRAMMegabytes();
+
 private:
     LLWindow* mWindow;
     void* mContext = nullptr;
diff --git a/indra/llrender/llrender.cpp b/indra/llrender/llrender.cpp
index 92d8e6193f04af023e4d08cb2b476db038353dc2..a03a27cf9490289f521d581795c04de82389d282 100644
--- a/indra/llrender/llrender.cpp
+++ b/indra/llrender/llrender.cpp
@@ -36,6 +36,17 @@
 #include "lltexture.h"
 #include "llshadermgr.h"
 
+#if LL_WINDOWS
+extern void APIENTRY gl_debug_callback(GLenum source,
+                                GLenum type,
+                                GLuint id,
+                                GLenum severity,
+                                GLsizei length,
+                                const GLchar* message,
+                                GLvoid* userParam)
+;
+#endif
+
 thread_local LLRender gGL;
 
 // Handy copies of last good GL matrices
@@ -860,6 +871,15 @@ LLRender::~LLRender()
 
 void LLRender::init()
 {
+#if LL_WINDOWS
+    if (gGLManager.mHasDebugOutput && gDebugGL)
+    { //setup debug output callback
+        //glDebugMessageControlARB(GL_DONT_CARE, GL_DONT_CARE, GL_DEBUG_SEVERITY_LOW_ARB, 0, NULL, GL_TRUE);
+        glDebugMessageCallbackARB((GLDEBUGPROCARB) gl_debug_callback, NULL);
+        glEnable(GL_DEBUG_OUTPUT_SYNCHRONOUS_ARB);
+    }
+#endif
+
     glPixelStorei(GL_PACK_ALIGNMENT, 1);
     glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
 
diff --git a/indra/llui/llcombobox.cpp b/indra/llui/llcombobox.cpp
index 576868665937c93bd941b263bea27d67d799fbef..9ca05a16f374a0f7fc15bbd4756ebe400377f212 100644
--- a/indra/llui/llcombobox.cpp
+++ b/indra/llui/llcombobox.cpp
@@ -78,7 +78,6 @@ LLComboBox::Params::Params()
 	combo_button("combo_button"),
 	combo_list("combo_list"),
 	combo_editor("combo_editor"),
-    mouse_down_callback("mouse_down_callback"),
 	drop_down_button("drop_down_button")
 {
 	addSynonym(items, "combo_item");
@@ -98,8 +97,7 @@ LLComboBox::LLComboBox(const LLComboBox::Params& p)
 	mTextChangedCallback(p.text_changed_callback()),
 	mListPosition(p.list_position),
 	mLastSelectedIndex(-1),
-	mLabel(p.label),
-    mMouseDownSignal(NULL)
+	mLabel(p.label)
 {
 	// Text label button
 
@@ -115,6 +113,10 @@ LLComboBox::LLComboBox(const LLComboBox::Params& p)
 	}
 
 	mArrowImage = button_params.image_unselected;
+    if (mArrowImage.notNull())
+    {
+        mImageLoadedConnection = mArrowImage->addLoadedCallback(boost::bind(&LLComboBox::imageLoaded, this));
+    }
 
 	mButton = LLUICtrlFactory::create<LLButton>(button_params);
 
@@ -155,11 +157,6 @@ LLComboBox::LLComboBox(const LLComboBox::Params& p)
 
 	createLineEditor(p);
 
-    if (p.mouse_down_callback.isProvided())
-    {
-        setMouseDownCallback(initCommitCallback(p.mouse_down_callback));
-    }
-
 	mTopLostSignalConnection = setTopLostCallback(boost::bind(&LLComboBox::hideList, this));
 }
 
@@ -190,7 +187,7 @@ LLComboBox::~LLComboBox()
 
 	// explicitly disconect this signal, since base class destructor might fire top lost
 	mTopLostSignalConnection.disconnect();
-    delete mMouseDownSignal;
+    mImageLoadedConnection.disconnect();
 }
 
 
@@ -717,9 +714,6 @@ void LLComboBox::hideList()
 
 void LLComboBox::onButtonMouseDown()
 {
-    if (mMouseDownSignal)
-        (*mMouseDownSignal)( this, 0 );
-
 	if (!mList->getVisible())
 	{
 		// this might change selection, so do it first
@@ -1085,6 +1079,30 @@ void LLComboBox::onSetHighlight() const
     }
 }
 
+void LLComboBox::imageLoaded()
+{
+    static LLUICachedControl<S32> drop_shadow_button("DropShadowButton", 0);
+
+    if (mAllowTextEntry)
+    {
+        LLRect rect = getLocalRect();
+        S32 arrow_width = mArrowImage ? mArrowImage->getWidth() : 0;
+        S32 shadow_size = drop_shadow_button;
+        mButton->setRect(LLRect(getRect().getWidth() - llmax(8, arrow_width) - 2 * shadow_size,
+            rect.mTop, rect.mRight, rect.mBottom));
+        if (mButton->getVisible())
+        {
+            // recalculate field size
+            if (mTextEntry)
+            {
+                LLRect text_entry_rect(0, getRect().getHeight(), getRect().getWidth(), 0);
+                text_entry_rect.mRight -= llmax(8, arrow_width) + 2 * drop_shadow_button;
+                mTextEntry->reshape(text_entry_rect.getWidth(), text_entry_rect.getHeight(), TRUE);
+            }
+        }
+    }
+}
+
 //============================================================================
 // LLCtrlListInterface functions
 
@@ -1194,11 +1212,6 @@ BOOL LLComboBox::selectItemRange( S32 first, S32 last )
 	return mList->selectItemRange(first, last);
 }
 
-boost::signals2::connection LLComboBox::setMouseDownCallback( const commit_signal_t::slot_type& cb ) 
-{ 
-    if (!mMouseDownSignal) mMouseDownSignal = new commit_signal_t();
-    return mMouseDownSignal->connect(cb); 
-}
 
 static LLDefaultChildRegistry::Register<LLIconsComboBox> register_icons_combo_box("icons_combo_box");
 
diff --git a/indra/llui/llcombobox.h b/indra/llui/llcombobox.h
index 49a55c98a37934b150bf620d7bec85157c2cd01e..cac8850a25e77b55b5dedca7bf9304b5b1836124 100644
--- a/indra/llui/llcombobox.h
+++ b/indra/llui/llcombobox.h
@@ -78,8 +78,6 @@ class LLComboBox
 											text_entry_callback,
 											text_changed_callback;
 
-        Optional<CommitCallbackParam>       mouse_down_callback;
-
 		Optional<EPreferredPosition, PreferredPositionValues>	list_position;
 		
 		// components
@@ -107,6 +105,8 @@ class LLComboBox
     virtual std::string _getSearchText() const;
     virtual void onSetHighlight() const;
 
+    void imageLoaded();
+
 public:
 	// LLView interface
 	virtual void	onFocusLost();
@@ -209,8 +209,6 @@ class LLComboBox
 	void			setTextEntryCallback( commit_callback_t cb ) { mTextEntryCallback = cb; }
 	void			setTextChangedCallback( commit_callback_t cb ) { mTextChangedCallback = cb; }
 
-    boost::signals2::connection setMouseDownCallback( const commit_signal_t::slot_type& cb );
-
 	/**
 	* Connects callback to signal called when Return key is pressed.
 	*/
@@ -248,7 +246,7 @@ class LLComboBox
 	commit_callback_t	mTextChangedCallback;
 	commit_callback_t	mSelectionCallback;
 	boost::signals2::connection mTopLostSignalConnection;
-    commit_signal_t*	mMouseDownSignal;
+    boost::signals2::connection mImageLoadedConnection;
 	commit_signal_t		mOnReturnSignal;
 	S32                 mLastSelectedIndex;
 };
diff --git a/indra/llwindow/llwindowwin32.cpp b/indra/llwindow/llwindowwin32.cpp
index 09e2896392306470639fbb5690e4eefc3d891723..f26e3a84d2e327233af061d4e28ea7a890880814 100644
--- a/indra/llwindow/llwindowwin32.cpp
+++ b/indra/llwindow/llwindowwin32.cpp
@@ -3566,6 +3566,7 @@ void LLWindowWin32::swapBuffers()
 {
     LL_PROFILE_ZONE_SCOPED_CATEGORY_WIN32;
     ASSERT_MAIN_THREAD();
+    glFlush(); //superstitious flush for maybe frame stall removal?
 	SwapBuffers(mhDC);
 
     LL_PROFILER_GPU_COLLECT
diff --git a/indra/newview/CMakeLists.txt b/indra/newview/CMakeLists.txt
index 73b234eb75a58912d3ed98a8438f0be83d4bd448..e7f0af84ef0e408b5f2f3966f27e8adecdb497f1 100644
--- a/indra/newview/CMakeLists.txt
+++ b/indra/newview/CMakeLists.txt
@@ -1697,6 +1697,8 @@ set(viewer_APPSETTINGS_FILES
     ${CMAKE_SOURCE_DIR}/../etc/message.xml
     ${CMAKE_SOURCE_DIR}/../scripts/messages/message_template.msg
     packages-info.txt
+    featuretable.txt
+    featuretable_mac.txt
     )
 
 source_group("App Settings" FILES ${viewer_APPSETTINGS_FILES})
@@ -1751,10 +1753,6 @@ endif (HAVOK OR HAVOK_TPV)
 # progress view disables/enables icons based on available packages
 set_source_files_properties(llprogressview.cpp PROPERTIES COMPILE_FLAGS "${LLSTARTUP_COMPILE_FLAGS}")
 
-if (GLODLIB)
-  set_source_files_properties(llfloatermodelpreview.cpp PROPERTIES COMPILE_FLAGS "-DLL_GLOD")
-endif (GLODLIB)
-
 list(APPEND viewer_SOURCE_FILES ${viewer_HEADER_FILES})
 
 set_source_files_properties(${viewer_HEADER_FILES}
diff --git a/indra/newview/app_settings/settings.xml b/indra/newview/app_settings/settings.xml
index bb838246281069e98344d918af6fcb17c1b1b974..bfc47d2c65225c2e8b613f9cca367a26a094f9f7 100644
--- a/indra/newview/app_settings/settings.xml
+++ b/indra/newview/app_settings/settings.xml
@@ -9886,7 +9886,7 @@
     <key>Value</key>
     <real>2.2</real>
   </map>
-    <key>RenderGLCoreProfile</key>
+    <key>RenderGLContextCoreProfile</key>
     <map>
       <key>Comment</key>
       <string>Don't use a compatibility profile OpenGL context.  Requires restart.  Basic shaders MUST be enabled.</string>
diff --git a/indra/newview/featuretable.txt b/indra/newview/featuretable.txt
index a5e0e395ded06e8fc36c1d04876347da9f85e27d..ff7ac848031790c39af2bdde1097b3f8b32dccd5 100644
--- a/indra/newview/featuretable.txt
+++ b/indra/newview/featuretable.txt
@@ -70,7 +70,7 @@ RenderShadowDetail			1	2
 RenderUseStreamVBO			1	1
 RenderFSAASamples			1	16
 RenderMaxTextureIndex		1	16
-RenderGLCoreProfile         1   1
+RenderGLContextCoreProfile         1   1
 RenderGLMultiThreaded       1   1
 
 
@@ -320,12 +320,12 @@ list Intel
 RenderAnisotropic			1	0
 RenderFSAASamples			1	0
 RenderGLMultiThreaded       1   0
-RenderGLCoreProfile         1   0
+RenderGLContextCoreProfile         1   0
 
 // AMD cards generally perform better when not using VBOs for streaming data
 // AMD cards also prefer an OpenGL Compatibility Profile Context
 list AMD
 RenderUseStreamVBO			1	0
-RenderGLCoreProfile         1   0
+RenderGLContextCoreProfile         1   0
 
 
diff --git a/indra/newview/featuretable_mac.txt b/indra/newview/featuretable_mac.txt
index 5140614bab887c2b6ba454879d0488ebbf3e8827..248eb68ee91cb4baebcd3c6e8500dfaa195e24ee 100644
--- a/indra/newview/featuretable_mac.txt
+++ b/indra/newview/featuretable_mac.txt
@@ -70,7 +70,7 @@ RenderShadowDetail			1	2
 RenderUseStreamVBO			1	1
 RenderFSAASamples			1	16
 RenderMaxTextureIndex		1	16
-RenderGLCoreProfile         1   0
+RenderGLContextCoreProfile         1   0
 RenderGLMultiThreaded       1   0
 
 //
diff --git a/indra/newview/llagentcamera.cpp b/indra/newview/llagentcamera.cpp
index ed6c3c307fcc2712f09be591415350ecdca21283..84a41113be3ee54bf1f73421f1a3fdc6c5a4702e 100644
--- a/indra/newview/llagentcamera.cpp
+++ b/indra/newview/llagentcamera.cpp
@@ -210,9 +210,6 @@ void LLAgentCamera::init()
 	
 	mCameraPreset = (ECameraPreset) gSavedSettings.getU32("CameraPresetType");
 
-	mCameraOffsetInitial = gSavedSettings.getControl("CameraOffsetRearView");
-	mFocusOffsetInitial = gSavedSettings.getControl("FocusOffsetRearView");
-
 	mCameraCollidePlane.clearVec();
 	mCurrentCameraDistance = getCameraOffsetInitial().magVec() * gSavedSettings.getF32("CameraOffsetScale");
 	mTargetCameraDistance = mCurrentCameraDistance;
@@ -1672,8 +1669,8 @@ LLVector3d LLAgentCamera::calcThirdPersonFocusOffset()
 		agent_rot *= ((LLViewerObject*)(gAgentAvatarp->getParent()))->getRenderRotation();
 	}
 
-	focus_offset = convert_from_llsd<LLVector3d>(mFocusOffsetInitial->get(), TYPE_VEC3D, "");
-	return focus_offset * agent_rot;
+    static LLCachedControl<LLVector3d> focus_offset_initial(gSavedSettings, "FocusOffsetRearView", LLVector3d());
+	return focus_offset_initial * agent_rot;
 }
 
 void LLAgentCamera::setupSitCamera()
@@ -1810,8 +1807,9 @@ LLVector3d LLAgentCamera::calcCameraPositionTargetGlobal(BOOL *hit_limit)
 		}
 		else
 		{
-			local_camera_offset = mCameraZoomFraction * getCameraOffsetInitial() * gSavedSettings.getF32("CameraOffsetScale");
-			
+            static LLCachedControl<F32> camera_offset_scale(gSavedSettings, "CameraOffsetScale");
+            local_camera_offset = mCameraZoomFraction * getCameraOffsetInitial() * camera_offset_scale;
+
 			// are we sitting down?
 			if (isAgentAvatarValid() && gAgentAvatarp->getParent())
 			{
@@ -2028,12 +2026,15 @@ bool LLAgentCamera::isJoystickCameraUsed()
 
 LLVector3 LLAgentCamera::getCameraOffsetInitial()
 {
-	return convert_from_llsd<LLVector3>(mCameraOffsetInitial->get(), TYPE_VEC3, "");
+    // getCameraOffsetInitial and getFocusOffsetInitial can be called on update from idle before init()
+    static LLCachedControl<LLVector3> camera_offset_initial (gSavedSettings, "CameraOffsetRearView", LLVector3());
+	return camera_offset_initial;
 }
 
 LLVector3d LLAgentCamera::getFocusOffsetInitial()
 {
-	return convert_from_llsd<LLVector3d>(mFocusOffsetInitial->get(), TYPE_VEC3D, "");
+    static LLCachedControl<LLVector3d> focus_offset_initial(gSavedSettings, "FocusOffsetRearView", LLVector3d());
+	return focus_offset_initial;
 }
 
 F32 LLAgentCamera::getCameraMaxZoomDistance()
diff --git a/indra/newview/llagentcamera.h b/indra/newview/llagentcamera.h
index ec1ed433d7cd9b26564b7c719041db24c7ffa3ee..89680f95dc28982ffadcb0c26fcf05b54e1886a1 100644
--- a/indra/newview/llagentcamera.h
+++ b/indra/newview/llagentcamera.h
@@ -131,12 +131,6 @@ class LLAgentCamera
 	/** Camera preset in Third Person Mode */
 	ECameraPreset mCameraPreset; 
 
-	/** Initial camera offset */
-	LLPointer<LLControlVariable> mCameraOffsetInitial;
-
-	/** Initial focus offset */
-	LLPointer<LLControlVariable> mFocusOffsetInitial;
-
 	LLQuaternion mInitSitRot;
 
 	//--------------------------------------------------------------------
diff --git a/indra/newview/llappviewer.cpp b/indra/newview/llappviewer.cpp
index 73d0eac0ac3832d85fb76efedff43685df888a2e..66c44ef6a622225acb00d23fa60d3117446ee9c3 100644
--- a/indra/newview/llappviewer.cpp
+++ b/indra/newview/llappviewer.cpp
@@ -532,7 +532,7 @@ static void settings_to_globals()
 
 	LLSurface::setTextureSize(gSavedSettings.getU32("RegionTextureSize"));
 
-	LLRender::sGLCoreProfile = gSavedSettings.getBOOL("RenderGLCoreProfile");
+	LLRender::sGLCoreProfile = gSavedSettings.getBOOL("RenderGLContextCoreProfile");
 	LLRender::sNsightDebugSupport = gSavedSettings.getBOOL("RenderNsightDebugSupport");
 	LLVertexBuffer::sUseVAO = gSavedSettings.getBOOL("RenderUseVAO");
 	LLImageGL::sGlobalUseAnisotropic	= gSavedSettings.getBOOL("RenderAnisotropic");
diff --git a/indra/newview/lldrawpool.h b/indra/newview/lldrawpool.h
index d4f30fc51a8ad4ceb9a0454be6600c8f45b8a85f..fd1b022e5bf3d7b513de8f35c1b5b85a149098e0 100644
--- a/indra/newview/lldrawpool.h
+++ b/indra/newview/lldrawpool.h
@@ -183,6 +183,7 @@ class LLRenderPass : public LLDrawPool
 		PASS_GLOW,
         PASS_GLOW_RIGGED,
 		PASS_ALPHA,
+        PASS_ALPHA_RIGGED,
 		PASS_ALPHA_MASK,
         PASS_ALPHA_MASK_RIGGED,
 		PASS_FULLBRIGHT_ALPHA_MASK,            // Diffuse texture used as alpha mask and fullbright
diff --git a/indra/newview/lldrawpoolalpha.cpp b/indra/newview/lldrawpoolalpha.cpp
index 9da20cc3752f165f61c4b64a754598bb061451b8..2bf8e9b9112d0798aed014436f31d2fc6e4691ff 100644
--- a/indra/newview/lldrawpoolalpha.cpp
+++ b/indra/newview/lldrawpoolalpha.cpp
@@ -129,23 +129,26 @@ void LLDrawPoolAlpha::renderPostDeferred(S32 pass)
     LL_PROFILE_ZONE_SCOPED_CATEGORY_DRAWPOOL;
     deferred_render = TRUE;
 
-    // first pass, regular forward alpha rendering
-    {
-        emissive_shader = (LLPipeline::sUnderWaterRender) ? &gObjectEmissiveWaterProgram : &gObjectEmissiveProgram;
-        prepare_alpha_shader(emissive_shader, true, false);
-
-        fullbright_shader = (LLPipeline::sImpostorRender) ? &gDeferredFullbrightProgram :
-            (LLPipeline::sUnderWaterRender) ? &gDeferredFullbrightWaterProgram : &gDeferredFullbrightProgram;
-        prepare_alpha_shader(fullbright_shader, true, false);
-
-        simple_shader = (LLPipeline::sImpostorRender) ? &gDeferredAlphaImpostorProgram :
-            (LLPipeline::sUnderWaterRender) ? &gDeferredAlphaWaterProgram : &gDeferredAlphaProgram;
-        prepare_alpha_shader(simple_shader, false, true); //prime simple shader (loads shadow relevant uniforms)
-        
-        forwardRender();
-    }
+    // prepare shaders
+    emissive_shader = (LLPipeline::sUnderWaterRender) ? &gObjectEmissiveWaterProgram : &gObjectEmissiveProgram;
+    prepare_alpha_shader(emissive_shader, true, false);
+
+    fullbright_shader = (LLPipeline::sImpostorRender) ? &gDeferredFullbrightProgram :
+        (LLPipeline::sUnderWaterRender) ? &gDeferredFullbrightWaterProgram : &gDeferredFullbrightProgram;
+    prepare_alpha_shader(fullbright_shader, true, false);
+
+    simple_shader = (LLPipeline::sImpostorRender) ? &gDeferredAlphaImpostorProgram :
+        (LLPipeline::sUnderWaterRender) ? &gDeferredAlphaWaterProgram : &gDeferredAlphaProgram;
+    prepare_alpha_shader(simple_shader, false, true); //prime simple shader (loads shadow relevant uniforms)
+
 
-    // second pass, render to depth for depth of field effects
+    // first pass, render rigged objects only and render to depth buffer
+    forwardRender(true);
+
+    // second pass, regular forward alpha rendering
+    forwardRender();
+
+    // final pass, render to depth for depth of field effects
     if (!LLPipeline::sImpostorRender && gSavedSettings.getBOOL("RenderDepthOfField"))
     { 
         //update depth buffer sampler
@@ -209,10 +212,14 @@ void LLDrawPoolAlpha::render(S32 pass)
     prepare_forward_shader(fullbright_shader, minimum_alpha);
     prepare_forward_shader(simple_shader, minimum_alpha);
 
+    //first pass -- rigged only and drawn to depth buffer
+    forwardRender(true);
+
+    //second pass -- non-rigged, no depth buffer writes
     forwardRender();
 }
 
-void LLDrawPoolAlpha::forwardRender()
+void LLDrawPoolAlpha::forwardRender(bool rigged)
 {
     gPipeline.enableLightsDynamic();
 
@@ -221,7 +228,8 @@ void LLDrawPoolAlpha::forwardRender()
     //enable writing to alpha for emissive effects
     gGL.setColorMask(true, true);
 
-    bool write_depth = LLDrawPoolWater::sSkipScreenCopy
+    bool write_depth = rigged
+        || LLDrawPoolWater::sSkipScreenCopy
         // we want depth written so that rendered alpha will
         // contribute to the alpha mask used for impostors
         || LLPipeline::sImpostorRenderAlphaDepthPass;
@@ -236,11 +244,17 @@ void LLDrawPoolAlpha::forwardRender()
 
     // If the face is more than 90% transparent, then don't update the Depth buffer for Dof
     // We don't want the nearly invisible objects to cause of DoF effects
-    renderAlpha(getVertexDataMask() | LLVertexBuffer::MAP_TEXTURE_INDEX | LLVertexBuffer::MAP_TANGENT | LLVertexBuffer::MAP_TEXCOORD1 | LLVertexBuffer::MAP_TEXCOORD2);
+    renderAlpha(getVertexDataMask() | LLVertexBuffer::MAP_TEXTURE_INDEX | LLVertexBuffer::MAP_TANGENT | LLVertexBuffer::MAP_TEXCOORD1 | LLVertexBuffer::MAP_TEXCOORD2, false, rigged);
 
     gGL.setColorMask(true, false);
 
-    renderDebugAlpha();
+    if (!rigged)
+    { //render "highlight alpha" on final non-rigged pass
+        // NOTE -- hacky call here protected by !rigged instead of alongside "forwardRender"
+        // so renderDebugAlpha is executed while gls_pipeline_alpha and depth GL state
+        // variables above are still in scope
+        renderDebugAlpha();
+    }
 }
 
 void LLDrawPoolAlpha::renderDebugAlpha()
@@ -291,54 +305,60 @@ void LLDrawPoolAlpha::renderDebugAlpha()
 
 void LLDrawPoolAlpha::renderAlphaHighlight(U32 mask)
 {
-    LLVOAvatar* lastAvatar = nullptr;
-    U64 lastMeshId = 0;
+    for (int pass = 0; pass < 2; ++pass)
+    { //two passes, one rigged and one not
+        LLVOAvatar* lastAvatar = nullptr;
+        U64 lastMeshId = 0;
 
-	for (LLCullResult::sg_iterator i = gPipeline.beginAlphaGroups(); i != gPipeline.endAlphaGroups(); ++i)
-	{
-		LLSpatialGroup* group = *i;
-		if (group->getSpatialPartition()->mRenderByGroup &&
-			!group->isDead())
-		{
-			LLSpatialGroup::drawmap_elem_t& draw_info = group->mDrawMap[LLRenderPass::PASS_ALPHA];	
+        LLCullResult::sg_iterator begin = pass == 0 ? gPipeline.beginAlphaGroups() : gPipeline.beginRiggedAlphaGroups();
+        LLCullResult::sg_iterator end = pass == 0 ? gPipeline.endAlphaGroups() : gPipeline.endRiggedAlphaGroups();
 
-			for (LLSpatialGroup::drawmap_elem_t::iterator k = draw_info.begin(); k != draw_info.end(); ++k)	
-			{
-				LLDrawInfo& params = **k;
-				
-				if (params.mParticle)
-				{
-					continue;
-				}
-
-                bool rigged = (params.mAvatar != nullptr);
-                gHighlightProgram.bind(rigged);
-                gGL.diffuseColor4f(1, 0, 0, 1);
+        for (LLCullResult::sg_iterator i = begin; i != end; ++i)
+        {
+            LLSpatialGroup* group = *i;
+            if (group->getSpatialPartition()->mRenderByGroup &&
+                !group->isDead())
+            {
+                LLSpatialGroup::drawmap_elem_t& draw_info = group->mDrawMap[LLRenderPass::PASS_ALPHA+pass]; // <-- hacky + pass to use PASS_ALPHA_RIGGED on second pass 
 
-                if (rigged)
+                for (LLSpatialGroup::drawmap_elem_t::iterator k = draw_info.begin(); k != draw_info.end(); ++k)
                 {
-                    if (lastAvatar != params.mAvatar ||
-                        lastMeshId != params.mSkinInfo->mHash)
+                    LLDrawInfo& params = **k;
+
+                    if (params.mParticle)
                     {
-                        if (!uploadMatrixPalette(params))
+                        continue;
+                    }
+
+                    bool rigged = (params.mAvatar != nullptr);
+                    gHighlightProgram.bind(rigged);
+                    gGL.diffuseColor4f(1, 0, 0, 1);
+
+                    if (rigged)
+                    {
+                        if (lastAvatar != params.mAvatar ||
+                            lastMeshId != params.mSkinInfo->mHash)
                         {
-                            continue;
+                            if (!uploadMatrixPalette(params))
+                            {
+                                continue;
+                            }
+                            lastAvatar = params.mAvatar;
+                            lastMeshId = params.mSkinInfo->mHash;
                         }
-                        lastAvatar = params.mAvatar;
-                        lastMeshId = params.mSkinInfo->mHash;
                     }
-                }
 
-				LLRenderPass::applyModelMatrix(params);
-				if (params.mGroup)
-				{
-					params.mGroup->rebuildMesh();
-				}
-				params.mVertexBuffer->setBufferFast(rigged ?  mask | LLVertexBuffer::MAP_WEIGHT4 : mask);
-				params.mVertexBuffer->drawRangeFast(params.mDrawMode, params.mStart, params.mEnd, params.mCount, params.mOffset);
-			}
-		}
-	}
+                    LLRenderPass::applyModelMatrix(params);
+                    if (params.mGroup)
+                    {
+                        params.mGroup->rebuildMesh();
+                    }
+                    params.mVertexBuffer->setBufferFast(rigged ? mask | LLVertexBuffer::MAP_WEIGHT4 : mask);
+                    params.mVertexBuffer->drawRangeFast(params.mDrawMode, params.mStart, params.mEnd, params.mCount, params.mOffset);
+                }
+            }
+        }
+    }
 
     // make sure static version of highlight shader is bound before returning
     gHighlightProgram.bind();
@@ -471,6 +491,8 @@ void LLDrawPoolAlpha::renderRiggedEmissives(U32 mask, std::vector<LLDrawInfo*>&
     LLVOAvatar* lastAvatar = nullptr;
     U64 lastMeshId = 0;
 
+    mask |= LLVertexBuffer::MAP_WEIGHT4;
+
     for (LLDrawInfo* draw : emissives)
     {
         bool tex_setup = TexSetup(draw, false);
@@ -488,7 +510,7 @@ void LLDrawPoolAlpha::renderRiggedEmissives(U32 mask, std::vector<LLDrawInfo*>&
     }
 }
 
-void LLDrawPoolAlpha::renderAlpha(U32 mask, bool depth_only)
+void LLDrawPoolAlpha::renderAlpha(U32 mask, bool depth_only, bool rigged)
 {
     LL_PROFILE_ZONE_SCOPED_CATEGORY_DRAWPOOL;
     BOOL initialized_lighting = FALSE;
@@ -498,7 +520,21 @@ void LLDrawPoolAlpha::renderAlpha(U32 mask, bool depth_only)
     U64 lastMeshId = 0;
     LLGLSLShader* lastAvatarShader = nullptr;
 
-    for (LLCullResult::sg_iterator i = gPipeline.beginAlphaGroups(); i != gPipeline.endAlphaGroups(); ++i)
+    LLCullResult::sg_iterator begin;
+    LLCullResult::sg_iterator end;
+
+    if (rigged)
+    {
+        begin = gPipeline.beginRiggedAlphaGroups();
+        end = gPipeline.endRiggedAlphaGroups();
+    }
+    else
+    {
+        begin = gPipeline.beginAlphaGroups();
+        end = gPipeline.endAlphaGroups();
+    }
+
+    for (LLCullResult::sg_iterator i = begin; i != end; ++i)
 	{
         LL_PROFILE_ZONE_NAMED_CATEGORY_DRAWPOOL("renderAlpha - group");
 		LLSpatialGroup* group = *i;
@@ -521,12 +557,18 @@ void LLDrawPoolAlpha::renderAlpha(U32 mask, bool depth_only)
 			bool disable_cull = is_particle_or_hud_particle;
 			LLGLDisable cull(disable_cull ? GL_CULL_FACE : 0);
 
-			LLSpatialGroup::drawmap_elem_t& draw_info = group->mDrawMap[LLRenderPass::PASS_ALPHA];
+			LLSpatialGroup::drawmap_elem_t& draw_info = rigged ? group->mDrawMap[LLRenderPass::PASS_ALPHA_RIGGED] : group->mDrawMap[LLRenderPass::PASS_ALPHA];
 
 			for (LLSpatialGroup::drawmap_elem_t::iterator k = draw_info.begin(); k != draw_info.end(); ++k)	
 			{
-                LL_PROFILE_ZONE_NAMED_CATEGORY_DRAWPOOL("ra - push batch")
 				LLDrawInfo& params = **k;
+                if ((bool)params.mAvatar != rigged)
+                {
+                    continue;
+                }
+
+                LL_PROFILE_ZONE_NAMED_CATEGORY_DRAWPOOL("ra - push batch")
+
                 U32 have_mask = params.mVertexBuffer->getTypeMask() & mask;
 				if (have_mask != mask)
 				{ //FIXME!
@@ -753,7 +795,7 @@ void LLDrawPoolAlpha::renderAlpha(U32 mask, bool depth_only)
 
 bool LLDrawPoolAlpha::uploadMatrixPalette(const LLDrawInfo& params)
 {
-    const LLVOAvatar::MatrixPaletteCache& mpc = params.mAvatar->updateSkinInfoMatrixPalette(params.mSkinInfo);
+    const LLVOAvatar::MatrixPaletteCache& mpc = params.mAvatar.get()->updateSkinInfoMatrixPalette(params.mSkinInfo);
     U32 count = mpc.mMatrixPalette.size();
 
     if (count == 0)
diff --git a/indra/newview/lldrawpoolalpha.h b/indra/newview/lldrawpoolalpha.h
index 1f6909e28221481c008bce33a664aa296446267a..fa8ef0f22763658f370e9241f14c60ff52fd7f1e 100644
--- a/indra/newview/lldrawpoolalpha.h
+++ b/indra/newview/lldrawpoolalpha.h
@@ -55,13 +55,13 @@ class LLDrawPoolAlpha: public LLRenderPass
 	/*virtual*/ S32	 getNumPasses() { return 1; }
 
 	virtual void render(S32 pass = 0);
-    void forwardRender();
+    void forwardRender(bool write_depth = false);
 	/*virtual*/ void prerender();
 
     void renderDebugAlpha();
 
 	void renderGroupAlpha(LLSpatialGroup* group, U32 type, U32 mask, BOOL texture = TRUE);
-	void renderAlpha(U32 mask, bool depth_only = false);
+	void renderAlpha(U32 mask, bool depth_only = false, bool rigged = false);
 	void renderAlphaHighlight(U32 mask);
     bool uploadMatrixPalette(const LLDrawInfo& params);
 
diff --git a/indra/newview/lldrawpoolbump.cpp b/indra/newview/lldrawpoolbump.cpp
index 471b0e2c4872155ac97aa7318c791a77f49abba4..1d5419b515a1c6e3974a8108c0308c6d295439ac 100644
--- a/indra/newview/lldrawpoolbump.cpp
+++ b/indra/newview/lldrawpoolbump.cpp
@@ -54,6 +54,8 @@
 
 // static
 LLStandardBumpmap gStandardBumpmapList[TEM_BUMPMAP_COUNT]; 
+LL::WorkQueue::weak_t LLBumpImageList::sMainQueue;
+LL::WorkQueue::weak_t LLBumpImageList::sTexUpdateQueue;
 
 // static
 U32 LLStandardBumpmap::sStandardBumpmapCount = 0;
@@ -74,6 +76,8 @@ static S32 cube_channel = -1;
 static S32 diffuse_channel = -1;
 static S32 bump_channel = -1;
 
+#define LL_BUMPLIST_MULTITHREADED 0
+
 // static 
 void LLStandardBumpmap::init()
 {
@@ -761,6 +765,8 @@ void LLBumpImageList::init()
 	LLStandardBumpmap::init();
 
 	LLStandardBumpmap::restoreGL();
+    sMainQueue = LL::WorkQueue::getInstance("mainloop");
+    sTexUpdateQueue = LL::WorkQueue::getInstance("LLImageGL"); // Share work queue with tex loader.
 }
 
 void LLBumpImageList::clear()
@@ -909,10 +915,7 @@ LLViewerTexture* LLBumpImageList::getBrightnessDarknessImage(LLViewerFetchedText
 	}
 	else
 	{
-		LLPointer<LLImageRaw> raw = new LLImageRaw(1,1,1);
-		raw->clear(0x77, 0x77, 0xFF, 0xFF);
-
-		(*entries_list)[src_image->getID()] = LLViewerTextureManager::getLocalTexture( raw.get(), TRUE);
+		(*entries_list)[src_image->getID()] = LLViewerTextureManager::getLocalTexture( TRUE );
 		bump = (*entries_list)[src_image->getID()]; // In case callback was called immediately and replaced the image
 	}
 
@@ -1043,16 +1046,13 @@ void LLBumpImageList::onSourceLoaded( BOOL success, LLViewerTexture *src_vi, LLI
 							iter->second->getWidth() != src->getWidth() ||
 							iter->second->getHeight() != src->getHeight()) // bump not cached yet or has changed resolution
 			{ //make sure an entry exists for this image
-				LLPointer<LLImageRaw> raw = new LLImageRaw(1,1,1);
-				raw->clear(0x77, 0x77, 0xFF, 0xFF);
-
-				entries_list[src_vi->getID()] = LLViewerTextureManager::getLocalTexture( raw.get(), TRUE);
+				entries_list[src_vi->getID()] = LLViewerTextureManager::getLocalTexture(TRUE);
 				iter = entries_list.find(src_vi->getID());
 			}
 		}
 
-		//if (iter->second->getWidth() != src->getWidth() ||
-		//	iter->second->getHeight() != src->getHeight()) // bump not cached yet or has changed resolution
+		if (iter->second->getWidth() != src->getWidth() ||
+			iter->second->getHeight() != src->getHeight()) // bump not cached yet or has changed resolution
 		{
 			LLPointer<LLImageRaw> dst_image = new LLImageRaw(src->getWidth(), src->getHeight(), 1);
 			U8* dst_data = dst_image->getData();
@@ -1166,108 +1166,178 @@ void LLBumpImageList::onSourceLoaded( BOOL success, LLViewerTexture *src_vi, LLI
 			}
 
 			//---------------------------------------------------
-			// immediately assign bump to a global smart pointer in case some local smart pointer
+			// immediately assign bump to a smart pointer in case some local smart pointer
 			// accidentally releases it.
-			LLPointer<LLViewerTexture> bump = LLViewerTextureManager::getLocalTexture( TRUE );
+            LLPointer<LLViewerTexture> bump = iter->second;
 
 			if (!LLPipeline::sRenderDeferred)
 			{
 				bump->setExplicitFormat(GL_ALPHA8, GL_ALPHA);
-				bump->createGLTexture(0, dst_image);
+
+#if LL_BUMPLIST_MULTITHREADED
+                auto tex_queue = LLImageGLThread::sEnabled ? sTexUpdateQueue.lock() : nullptr;
+
+                if (tex_queue)
+                { //dispatch creation to background thread
+                    LLImageRaw* dst_ptr = dst_image;
+                    LLViewerTexture* bump_ptr = bump;
+                    dst_ptr->ref();
+                    bump_ptr->ref();
+                    tex_queue->post(
+                        [=]()
+                        {
+                            LL_PROFILE_ZONE_NAMED("bil - create texture");
+                            bump_ptr->createGLTexture(0, dst_ptr);
+                            bump_ptr->unref();
+                            dst_ptr->unref();
+                        });
+
+                }
+                else
+#endif
+                {
+                    bump->createGLTexture(0, dst_image);
+                }
 			}
 			else 
 			{ //convert to normal map
 				
 				//disable compression on normal maps to prevent errors below
 				bump->getGLTexture()->setAllowCompression(false);
+                bump->getGLTexture()->setUseMipMaps(TRUE);
 
-				{
-					bump->setExplicitFormat(GL_RGBA8, GL_ALPHA);
-					bump->createGLTexture(0, dst_image);
-				}
+                auto* bump_ptr = bump.get();
+                auto* dst_ptr = dst_image.get();
 
-				{
-					gPipeline.mScreen.bindTarget();
-					
-					LLGLDepthTest depth(GL_FALSE);
-					LLGLDisable cull(GL_CULL_FACE);
-					LLGLDisable blend(GL_BLEND);
-					gGL.setColorMask(TRUE, TRUE);
-					gNormalMapGenProgram.bind();
+#if LL_BUMPLIST_MULTITHREADED
+                bump_ptr->ref();
+                dst_ptr->ref();
+#endif
 
-					static LLStaticHashedString sNormScale("norm_scale");
-					static LLStaticHashedString sStepX("stepX");
-					static LLStaticHashedString sStepY("stepY");
+                bump_ptr->setExplicitFormat(GL_RGBA8, GL_ALPHA);
 
-					gNormalMapGenProgram.uniform1f(sNormScale, gSavedSettings.getF32("RenderNormalMapScale"));
-					gNormalMapGenProgram.uniform1f(sStepX, 1.f/bump->getWidth());
-					gNormalMapGenProgram.uniform1f(sStepY, 1.f/bump->getHeight());
+                auto create_texture = [=]()
+                {
+#if LL_IMAGEGL_THREAD_CHECK
+                    bump_ptr->getGLTexture()->mActiveThread = LLThread::currentID();
+#endif
+                    LL_PROFILE_ZONE_NAMED("bil - create texture deferred");
+                    bump_ptr->createGLTexture(0, dst_ptr);
+                };
+
+                auto gen_normal_map = [=]()
+                {
+#if LL_IMAGEGL_THREAD_CHECK
+                    bump_ptr->getGLTexture()->mActiveThread = LLThread::currentID();
+#endif
+                    LL_PROFILE_ZONE_NAMED("bil - generate normal map");
+                    if (gNormalMapGenProgram.mProgramObject == 0)
+                    {
+#if LL_BUMPLIST_MULTITHREADED
+                        bump_ptr->unref();
+                        dst_ptr->unref();
+#endif
+                        return;
+                    }
+                    gPipeline.mScreen.bindTarget();
 
-					LLVector2 v((F32) bump->getWidth()/gPipeline.mScreen.getWidth(),
-								(F32) bump->getHeight()/gPipeline.mScreen.getHeight());
+                    LLGLDepthTest depth(GL_FALSE);
+                    LLGLDisable cull(GL_CULL_FACE);
+                    LLGLDisable blend(GL_BLEND);
+                    gGL.setColorMask(TRUE, TRUE);
+                    gNormalMapGenProgram.bind();
 
-					gGL.getTexUnit(0)->bind(bump);
-					
-					S32 width = bump->getWidth();
-					S32 height = bump->getHeight();
+                    static LLStaticHashedString sNormScale("norm_scale");
+                    static LLStaticHashedString sStepX("stepX");
+                    static LLStaticHashedString sStepY("stepY");
 
-					S32 screen_width = gPipeline.mScreen.getWidth();
-					S32 screen_height = gPipeline.mScreen.getHeight();
+                    gNormalMapGenProgram.uniform1f(sNormScale, gSavedSettings.getF32("RenderNormalMapScale"));
+                    gNormalMapGenProgram.uniform1f(sStepX, 1.f / bump_ptr->getWidth());
+                    gNormalMapGenProgram.uniform1f(sStepY, 1.f / bump_ptr->getHeight());
 
-					glViewport(0, 0, screen_width, screen_height);
+                    LLVector2 v((F32)bump_ptr->getWidth() / gPipeline.mScreen.getWidth(),
+                        (F32)bump_ptr->getHeight() / gPipeline.mScreen.getHeight());
 
-					for (S32 left = 0; left < width; left += screen_width)
-					{
-						S32 right = left + screen_width;
-						right = llmin(right, width);
-						
-						F32 left_tc = (F32) left/ width;
-						F32 right_tc = (F32) right/width;
+                    gGL.getTexUnit(0)->bind(bump_ptr);
 
-						for (S32 bottom = 0; bottom < height; bottom += screen_height)
-						{
-							S32 top = bottom+screen_height;
-							top = llmin(top, height);
+                    S32 width = bump_ptr->getWidth();
+                    S32 height = bump_ptr->getHeight();
 
-							F32 bottom_tc = (F32) bottom/height;
-							F32 top_tc = (F32)(bottom+screen_height)/height;
-							top_tc = llmin(top_tc, 1.f);
+                    S32 screen_width = gPipeline.mScreen.getWidth();
+                    S32 screen_height = gPipeline.mScreen.getHeight();
 
-							F32 screen_right = (F32) (right-left)/screen_width;
-							F32 screen_top = (F32) (top-bottom)/screen_height;
+                    glViewport(0, 0, screen_width, screen_height);
 
-							gGL.begin(LLRender::TRIANGLE_STRIP);
-							gGL.texCoord2f(left_tc, bottom_tc);
-							gGL.vertex2f(0, 0);
+                    for (S32 left = 0; left < width; left += screen_width)
+                    {
+                        S32 right = left + screen_width;
+                        right = llmin(right, width);
 
-							gGL.texCoord2f(left_tc, top_tc);
-							gGL.vertex2f(0, screen_top);
+                        F32 left_tc = (F32)left / width;
+                        F32 right_tc = (F32)right / width;
 
-							gGL.texCoord2f(right_tc, bottom_tc);
-							gGL.vertex2f(screen_right, 0);
+                        for (S32 bottom = 0; bottom < height; bottom += screen_height)
+                        {
+                            S32 top = bottom + screen_height;
+                            top = llmin(top, height);
 
-							gGL.texCoord2f(right_tc, top_tc);
-							gGL.vertex2f(screen_right, screen_top);
+                            F32 bottom_tc = (F32)bottom / height;
+                            F32 top_tc = (F32)(bottom + screen_height) / height;
+                            top_tc = llmin(top_tc, 1.f);
 
-							gGL.end();
+                            F32 screen_right = (F32)(right - left) / screen_width;
+                            F32 screen_top = (F32)(top - bottom) / screen_height;
 
-							gGL.flush();
+                            gGL.begin(LLRender::TRIANGLE_STRIP);
+                            gGL.texCoord2f(left_tc, bottom_tc);
+                            gGL.vertex2f(0, 0);
 
-							S32 w = right-left;
-							S32 h = top-bottom;
+                            gGL.texCoord2f(left_tc, top_tc);
+                            gGL.vertex2f(0, screen_top);
 
-							glCopyTexSubImage2D(GL_TEXTURE_2D, 0, left, bottom, 0, 0, w, h);
-						}
-					}
+                            gGL.texCoord2f(right_tc, bottom_tc);
+                            gGL.vertex2f(screen_right, 0);
 
-					glGenerateMipmap(GL_TEXTURE_2D);
+                            gGL.texCoord2f(right_tc, top_tc);
+                            gGL.vertex2f(screen_right, screen_top);
 
-					gPipeline.mScreen.flush();
+                            gGL.end();
 
-					gNormalMapGenProgram.unbind();
-										
-					//generateNormalMapFromAlpha(dst_image, nrm_image);
-				}
+                            gGL.flush();
+
+                            S32 w = right - left;
+                            S32 h = top - bottom;
+
+                            glCopyTexSubImage2D(GL_TEXTURE_2D, 0, left, bottom, 0, 0, w, h);
+                        }
+                    }
+
+                    glGenerateMipmap(GL_TEXTURE_2D);
+
+                    gPipeline.mScreen.flush();
+
+                    gNormalMapGenProgram.unbind();
+
+                    //generateNormalMapFromAlpha(dst_image, nrm_image);
+#if LL_BUMPLIST_MULTITHREADED
+                    bump_ptr->unref();
+                    dst_ptr->unref();
+#endif
+                };
+
+#if LL_BUMPLIST_MULTITHREADED
+                auto main_queue = sMainQueue.lock();
+
+                if (LLImageGLThread::sEnabled)
+                { //dispatch creation to background thread
+                    main_queue->postTo(sTexUpdateQueue, create_texture, gen_normal_map);
+                }
+                else
+#endif
+                {
+                    create_texture();
+                    gen_normal_map();
+                }
 			}
 		
 			iter->second = bump; // derefs (and deletes) old image
diff --git a/indra/newview/lldrawpoolbump.h b/indra/newview/lldrawpoolbump.h
index 38744a7d98c40f25ede335bf1249f040cd298bfe..6e218597387188f560d96d1e55f773b7f6b373dc 100644
--- a/indra/newview/lldrawpoolbump.h
+++ b/indra/newview/lldrawpoolbump.h
@@ -161,6 +161,8 @@ class LLBumpImageList
 	typedef std::unordered_map<LLUUID, LLPointer<LLViewerTexture> > bump_image_map_t;
 	bump_image_map_t mBrightnessEntries;
 	bump_image_map_t mDarknessEntries;
+    static LL::WorkQueue::weak_t sMainQueue;
+    static LL::WorkQueue::weak_t sTexUpdateQueue;
 };
 
 extern LLBumpImageList gBumpImageList;
diff --git a/indra/newview/lldrawpoolwater.cpp b/indra/newview/lldrawpoolwater.cpp
index 0f2bcf4708c4a0954c38a78f2d70e40d7d8c6f6c..6762b38c3957a64204062552a658dee52556581c 100644
--- a/indra/newview/lldrawpoolwater.cpp
+++ b/indra/newview/lldrawpoolwater.cpp
@@ -520,9 +520,10 @@ void LLDrawPoolWater::renderWater()
 
     LLColor4      specular(sun_up ? psky->getSunlightColor() : psky->getMoonlightColor());
     F32           phase_time = (F32) LLFrameTimer::getElapsedSeconds() * 0.5f;
-    bool          edge       = false;
     LLGLSLShader *shader     = nullptr;
-    do  // twice through, once with normal shader bound & once with edge shader bound
+
+    // two passes, first with standard water shader bound, second with edge water shader bound
+    for( int edge = 0 ; edge < 2; edge++ )
     {
         // select shader
         if (underwater && LLPipeline::sWaterReflections)
@@ -675,7 +676,7 @@ void LLDrawPoolWater::renderWater()
 
             gGL.getTexUnit(diffTex)->bind(face->getTexture());
 
-            if (edge == (bool) water->getIsEdgePatch())
+            if ((bool)edge == (bool) water->getIsEdgePatch())
             {
                 face->renderIndexed();
 
@@ -699,9 +700,7 @@ void LLDrawPoolWater::renderWater()
         shader->unbind();
         gGL.getTexUnit(bumpTex)->unbind(LLTexUnit::TT_TEXTURE);
         gGL.getTexUnit(bumpTex2)->unbind(LLTexUnit::TT_TEXTURE);
-
-        edge = !edge;
-    } while (!edge);
+    }
 
     gGL.getTexUnit(0)->activate();
     gGL.getTexUnit(0)->enable(LLTexUnit::TT_TEXTURE);
diff --git a/indra/newview/llfloatermodelpreview.cpp b/indra/newview/llfloatermodelpreview.cpp
index 6135c4f13eaec8b015b7784b7fe50bcd69953048..fe5120376c2420b6b4e59657161457174f440164 100644
--- a/indra/newview/llfloatermodelpreview.cpp
+++ b/indra/newview/llfloatermodelpreview.cpp
@@ -351,15 +351,6 @@ void LLFloaterModelPreview::initModelPreview()
 //static
 bool LLFloaterModelPreview::showModelPreview()
 {
-#ifdef LL_GLOD
-    if (LLRender::sGLCoreProfile)
-    {
-        // GLOD is incompatible with RenderGLCoreProfile, will crash on init
-        LLNotificationsUtil::add("MeshUploadProfilerError");
-        return false;
-    }
-#endif
-
     LLFloaterModelPreview* fmp = (LLFloaterModelPreview*)LLFloaterReg::getInstance("upload_model");
     if (fmp && !fmp->isModelLoading())
     {
diff --git a/indra/newview/llinventorybridge.cpp b/indra/newview/llinventorybridge.cpp
index 06c8f66c1e39ec931fedf3acc38c5c46a21eaadc..a0de3a2af11bfe02406a63c035c346e8be0f81ad 100644
--- a/indra/newview/llinventorybridge.cpp
+++ b/indra/newview/llinventorybridge.cpp
@@ -5777,12 +5777,13 @@ LLCallingCardBridge::LLCallingCardBridge(LLInventoryPanel* inventory,
 	LLItemBridge(inventory, root, uuid)
 {
     mObserver = new LLCallingCardObserver(this);
-    LLAvatarTracker::instance().addParticularFriendObserver(getItem()->getCreatorUUID(), mObserver);
+    mCreatorUUID = getItem()->getCreatorUUID();
+    LLAvatarTracker::instance().addParticularFriendObserver(mCreatorUUID, mObserver);
 }
 
 LLCallingCardBridge::~LLCallingCardBridge()
 {
-    LLAvatarTracker::instance().removeParticularFriendObserver(getItem()->getCreatorUUID(), mObserver);
+    LLAvatarTracker::instance().removeParticularFriendObserver(mCreatorUUID, mObserver);
 
     delete mObserver;
 }
diff --git a/indra/newview/llinventorybridge.h b/indra/newview/llinventorybridge.h
index c21bfbd02d6786fa8df11fabd3d8defd4cfefc9a..0b0ef273e17438df0d8147ad20a5a1c47d3087c4 100644
--- a/indra/newview/llinventorybridge.h
+++ b/indra/newview/llinventorybridge.h
@@ -457,6 +457,7 @@ class LLCallingCardBridge : public LLItemBridge
 	void checkSearchBySuffixChanges();
 protected:
 	LLCallingCardObserver* mObserver;
+    LLUUID mCreatorUUID;
 };
 
 class LLNotecardBridge : public LLItemBridge
diff --git a/indra/newview/llmodelpreview.cpp b/indra/newview/llmodelpreview.cpp
index c9bf0b0a1208bf994a60edcc623a2da2e9f59afa..d3eb2dd4d45aa8aca413e7ca680cc9f51159c7d5 100644
--- a/indra/newview/llmodelpreview.cpp
+++ b/indra/newview/llmodelpreview.cpp
@@ -1235,6 +1235,11 @@ F32 LLModelPreview::genMeshOptimizerPerModel(LLModel *base_model, LLModel *targe
         size_vertices += face.mNumVertices;
     }
 
+    if (size_indices < 3)
+    {
+        return -1;
+    }
+
     // Allocate buffers, note that we are using U32 buffer instead of U16
     U32* combined_indices = (U32*)ll_aligned_malloc_32(size_indices * sizeof(U32));
     U32* output_indices = (U32*)ll_aligned_malloc_32(size_indices * sizeof(U32));
@@ -1279,10 +1284,10 @@ F32 LLModelPreview::genMeshOptimizerPerModel(LLModel *base_model, LLModel *targe
 
     // Now that we have buffers, optimize
     S32 target_indices = 0;
-    F32 result_code = 0; // how far from original the model is, 1 == 100%
+    F32 result_error = 0; // how far from original the model is, 1 == 100%
     S32 new_indices = 0;
 
-    target_indices = llmax(3, llfloor(size_indices / indices_decimator)); // leave at least one triangle
+    target_indices = llclamp(llfloor(size_indices / indices_decimator), 3, (S32)size_indices); // leave at least one triangle
     new_indices = LLMeshOptimizer::simplifyU32(
         output_indices,
         combined_indices,
@@ -1293,17 +1298,27 @@ F32 LLModelPreview::genMeshOptimizerPerModel(LLModel *base_model, LLModel *targe
         target_indices,
         error_threshold,
         sloppy,
-        &result_code);
+        &result_error);
 
 
-    if (result_code < 0)
+    if (result_error < 0)
     {
-        LL_WARNS() << "Negative result code from meshoptimizer for model " << target_model->mLabel
+        LL_WARNS() << "Negative result error from meshoptimizer for model " << target_model->mLabel
             << " target Indices: " << target_indices
             << " new Indices: " << new_indices
             << " original count: " << size_indices << LL_ENDL;
     }
 
+    if (new_indices < 3)
+    {
+        // Model should have at least one visible triangle
+        ll_aligned_free<64>(combined_positions);
+        ll_aligned_free_32(output_indices);
+        ll_aligned_free_32(combined_indices);
+
+        return -1;
+    }
+
     // repack back into individual faces
 
     LLVector4a* buffer_positions = (LLVector4a*)ll_aligned_malloc<64>(sizeof(LLVector4a) * 2 * size_vertices + tc_bytes_size);
@@ -1456,16 +1471,20 @@ F32 LLModelPreview::genMeshOptimizerPerFace(LLModel *base_model, LLModel *target
 {
     const LLVolumeFace &face = base_model->getVolumeFace(face_idx);
     S32 size_indices = face.mNumIndices;
+    if (size_indices < 3)
+    {
+        return -1;
+    }
     // todo: do not allocate per each face, add one large buffer somewhere
     // faces have limited amount of indices
     S32 size = (size_indices * sizeof(U16) + 0xF) & ~0xF;
     U16* output = (U16*)ll_aligned_malloc_16(size);
 
     S32 target_indices = 0;
-    F32 result_code = 0; // how far from original the model is, 1 == 100%
+    F32 result_error = 0; // how far from original the model is, 1 == 100%
     S32 new_indices = 0;
 
-    target_indices = llmax(3, llfloor(size_indices / indices_decimator)); // leave at least one triangle
+    target_indices = llclamp(llfloor(size_indices / indices_decimator), 3, (S32)size_indices); // leave at least one triangle
     new_indices = LLMeshOptimizer::simplify(
         output,
         face.mIndices,
@@ -1476,12 +1495,12 @@ F32 LLModelPreview::genMeshOptimizerPerFace(LLModel *base_model, LLModel *target
         target_indices,
         error_threshold,
         sloppy,
-        &result_code);
+        &result_error);
 
 
-    if (result_code < 0)
+    if (result_error < 0)
     {
-        LL_WARNS() << "Negative result code from meshoptimizer for face " << face_idx
+        LL_WARNS() << "Negative result error from meshoptimizer for face " << face_idx
             << " of model " << target_model->mLabel
             << " target Indices: " << target_indices
             << " new Indices: " << new_indices
@@ -1693,21 +1712,21 @@ void LLModelPreview::genMeshOptimizerLODs(S32 which_lod, S32 meshopt_mode, U32 d
                 // Run meshoptimizer for each face
                 for (U32 face_idx = 0; face_idx < base->getNumVolumeFaces(); ++face_idx)
                 {
-                    genMeshOptimizerPerFace(base, target_model, face_idx, indices_decimator, lod_error_threshold, true);
+                    if (genMeshOptimizerPerFace(base, target_model, face_idx, indices_decimator, lod_error_threshold, true) < 0)
+                    {
+                        // Sloppy failed and returned an invalid model
+                        genMeshOptimizerPerFace(base, target_model, face_idx, indices_decimator, lod_error_threshold, false);
+                    }
                 }
-
-                LL_INFOS() << "Model " << target_model->getName()
-                    << " lod " << which_lod
-                    << " simplified using per face method." << LL_ENDL;
             }
 
             if (model_meshopt_mode == MESH_OPTIMIZER_AUTO)
             {
-                // Switches between 'combine' method and 'per model sloppy' based on combine's result.
+                // Switches between 'combine' method and 'sloppy' based on combine's result.
                 F32 allowed_ratio_drift = 2.f;
-                F32 res_ratio = genMeshOptimizerPerModel(base, target_model, indices_decimator, lod_error_threshold, false);
-
-                if (res_ratio < 0)
+                F32 precise_ratio = genMeshOptimizerPerModel(base, target_model, indices_decimator, lod_error_threshold, false);
+                
+                if (precise_ratio < 0)
                 {
                     // U16 vertices overflow, shouldn't happen, but just in case
                     for (U32 face_idx = 0; face_idx < base->getNumVolumeFaces(); ++face_idx)
@@ -1715,42 +1734,101 @@ void LLModelPreview::genMeshOptimizerLODs(S32 which_lod, S32 meshopt_mode, U32 d
                         genMeshOptimizerPerFace(base, target_model, face_idx, indices_decimator, lod_error_threshold, false);
                     }
                 }
-                else if (res_ratio * allowed_ratio_drift < indices_decimator)
+                else if (precise_ratio * allowed_ratio_drift < indices_decimator)
                 {
                     // Try sloppy variant if normal one failed to simplify model enough.
-                    res_ratio = genMeshOptimizerPerModel(base, target_model, indices_decimator, lod_error_threshold, true);
+                    // Sloppy variant can fail entirely and has issues with precision,
+                    // so code needs to do multiple attempts with different decimators.
+                    // Todo: this is a bit of a mess, needs to be refined and improved
+                    F32 last_working_decimator = 0.f;
+                    F32 last_working_ratio = F32_MAX;
+
+                    F32 sloppy_ratio = genMeshOptimizerPerModel(base, target_model, indices_decimator, lod_error_threshold, true);
+
+                    if (sloppy_ratio > 0)
+                    {
+                        // Would be better to do a copy of target_model here, but if
+                        // we need to use sloppy decimation, model should be cheap
+                        // and fast to generate and it won't affect end result
+                        last_working_decimator = indices_decimator;
+                        last_working_ratio = sloppy_ratio;
+                    }
 
                     // Sloppy has a tendecy to error into lower side, so a request for 100
                     // triangles turns into ~70, so check for significant difference from target decimation
                     F32 sloppy_ratio_drift = 1.4f;
                     if (lod_mode == LIMIT_TRIANGLES
-                        && (res_ratio > indices_decimator * sloppy_ratio_drift || res_ratio < 0))
+                        && (sloppy_ratio > indices_decimator * sloppy_ratio_drift || sloppy_ratio < 0))
                     {
                         // Apply a correction to compensate.
 
                         // (indices_decimator / res_ratio) by itself is likely to overshoot to a differend
                         // side due to overal lack of precision, and we don't need an ideal result, which
                         // likely does not exist, just a better one, so a partial correction is enough.
-                        F32 sloppy_decimator = indices_decimator * (indices_decimator / res_ratio + 1) / 2;
-                        res_ratio = genMeshOptimizerPerModel(base, target_model, sloppy_decimator, lod_error_threshold, true);
+                        F32 sloppy_decimator = indices_decimator * (indices_decimator / sloppy_ratio + 1) / 2;
+                        sloppy_ratio = genMeshOptimizerPerModel(base, target_model, sloppy_decimator, lod_error_threshold, true);
+                    }
+
+                    if (last_working_decimator > 0 && sloppy_ratio < last_working_ratio)
+                    {
+                        // Compensation didn't work, return back to previous decimator
+                        sloppy_ratio = genMeshOptimizerPerModel(base, target_model, indices_decimator, lod_error_threshold, true);
                     }
 
+                    if (sloppy_ratio < 0)
+                    {
+                        // Sloppy method didn't work, try with smaller decimation values
+                        S32 size_vertices = 0;
+
+                        for (U32 face_idx = 0; face_idx < base->getNumVolumeFaces(); ++face_idx)
+                        {
+                            const LLVolumeFace &face = base->getVolumeFace(face_idx);
+                            size_vertices += face.mNumVertices;
+                        }
+
+                        // Complex models aren't supposed to get here, they are supposed
+                        // to work on a first try of sloppy due to having more viggle room.
+                        // If they didn't, something is likely wrong, no point locking the
+                        // thread in a long calculation that will fail.
+                        const U32 too_many_vertices = 27000;
+                        if (size_vertices > too_many_vertices)
+                        {
+                            LL_WARNS() << "Sloppy optimization method failed for a complex model " << target_model->getName() << LL_ENDL;
+                        }
+                        else
+                        {
+                            // Find a decimator that does work
+                            F32 sloppy_decimation_step = sqrt((F32)decimation); // example: 27->15->9->5->3
+                            F32 sloppy_decimator = indices_decimator / sloppy_decimation_step;
+
+                            while (sloppy_ratio < 0
+                                && sloppy_decimator > precise_ratio
+                                && sloppy_decimator > 1)// precise_ratio isn't supposed to be below 1, but check just in case
+                            {
+                                sloppy_ratio = genMeshOptimizerPerModel(base, target_model, sloppy_decimator, lod_error_threshold, true);
+                                sloppy_decimator = sloppy_decimator / sloppy_decimation_step;
+                            }
+                        }
+                    }
 
-                    if (res_ratio < 0)
+                    if (sloppy_ratio < 0 || sloppy_ratio < precise_ratio)
                     {
-                        // Sloppy variant failed to generate triangles.
+                        // Sloppy variant failed to generate triangles or is worse.
                         // Can happen with models that are too simple as is.
-                        // Fallback to normal method or use lower decimator.
-                        genMeshOptimizerPerModel(base, target_model, indices_decimator, lod_error_threshold, false);
+                        // Fallback to normal method
+
+                        precise_ratio = genMeshOptimizerPerModel(base, target_model, indices_decimator, lod_error_threshold, false);
 
                         LL_INFOS() << "Model " << target_model->getName()
                             << " lod " << which_lod
+                            << " resulting ratio " << precise_ratio
                             << " simplified using per model method." << LL_ENDL;
                     }
                     else
                     {
                         LL_INFOS() << "Model " << target_model->getName()
                             << " lod " << which_lod
+                            << " resulting ratio " << sloppy_ratio
                             << " sloppily simplified using per model method." << LL_ENDL;
                     }
                 }
@@ -1758,6 +1836,7 @@ void LLModelPreview::genMeshOptimizerLODs(S32 which_lod, S32 meshopt_mode, U32 d
                 {
                     LL_INFOS() << "Model " << target_model->getName()
                         << " lod " << which_lod
+                        << " resulting ratio " << precise_ratio
                         << " simplified using per model method." << LL_ENDL;
                 }
             }
diff --git a/indra/newview/llnetmap.cpp b/indra/newview/llnetmap.cpp
index 937f36b6fc789b4d0546c4aa30e4a4c2519475a3..1240ce7c0ff5dce3bc622575c9832b73e82f5584 100644
--- a/indra/newview/llnetmap.cpp
+++ b/indra/newview/llnetmap.cpp
@@ -147,6 +147,10 @@ void LLNetMap::setScale( F32 scale )
 
 void LLNetMap::draw()
 {
+    if (!LLWorld::instanceExists())
+    {
+        return;
+    }
     LL_PROFILE_ZONE_SCOPED;
  	static LLFrameTimer map_timer;
 	static LLUIColor map_avatar_color = LLUIColorTable::instance().getColor("MapAvatarColor", LLColor4::white);
diff --git a/indra/newview/llspatialpartition.cpp b/indra/newview/llspatialpartition.cpp
index eaf6186daee4eacd11cca5da9587f82023f179e7..5c648c11e1e7e9ce8d48402478db15562098c1f2 100644
--- a/indra/newview/llspatialpartition.cpp
+++ b/indra/newview/llspatialpartition.cpp
@@ -4022,6 +4022,7 @@ LLCullResult::LLCullResult()
 {
 	mVisibleGroupsAllocated = 0;
 	mAlphaGroupsAllocated = 0;
+    mRiggedAlphaGroupsAllocated = 0;
 	mOcclusionGroupsAllocated = 0;
 	mDrawableGroupsAllocated = 0;
 	mVisibleListAllocated = 0;
@@ -4033,6 +4034,9 @@ LLCullResult::LLCullResult()
 	mAlphaGroups.clear();
 	mAlphaGroups.push_back(NULL);
 	mAlphaGroupsEnd = &mAlphaGroups[0];
+    mRiggedAlphaGroups.clear();
+    mRiggedAlphaGroups.push_back(NULL);
+    mRiggedAlphaGroupsEnd = &mRiggedAlphaGroups[0];
 	mOcclusionGroups.clear();
 	mOcclusionGroups.push_back(NULL);
 	mOcclusionGroupsEnd = &mOcclusionGroups[0];
@@ -4073,6 +4077,9 @@ void LLCullResult::clear()
 	mAlphaGroupsSize = 0;
 	mAlphaGroupsEnd = &mAlphaGroups[0];
 
+    mRiggedAlphaGroupsSize = 0;
+    mRiggedAlphaGroupsEnd = &mRiggedAlphaGroups[0];
+
 	mOcclusionGroupsSize = 0;
 	mOcclusionGroupsEnd = &mOcclusionGroups[0];
 
@@ -4117,6 +4124,16 @@ LLCullResult::sg_iterator LLCullResult::endAlphaGroups()
 	return mAlphaGroupsEnd;
 }
 
+LLCullResult::sg_iterator LLCullResult::beginRiggedAlphaGroups()
+{
+    return &mRiggedAlphaGroups[0];
+}
+
+LLCullResult::sg_iterator LLCullResult::endRiggedAlphaGroups()
+{
+    return mRiggedAlphaGroupsEnd;
+}
+
 LLCullResult::sg_iterator LLCullResult::beginOcclusionGroups()
 {
 	return &mOcclusionGroups[0];
@@ -4195,6 +4212,20 @@ void LLCullResult::pushAlphaGroup(LLSpatialGroup* group)
 	mAlphaGroupsEnd = &mAlphaGroups[mAlphaGroupsSize];
 }
 
+void LLCullResult::pushRiggedAlphaGroup(LLSpatialGroup* group)
+{
+    if (mRiggedAlphaGroupsSize < mRiggedAlphaGroupsAllocated)
+    {
+        mRiggedAlphaGroups[mRiggedAlphaGroupsSize] = group;
+    }
+    else
+    {
+        pushBack(mRiggedAlphaGroups, mRiggedAlphaGroupsAllocated, group);
+    }
+    ++mRiggedAlphaGroupsSize;
+    mRiggedAlphaGroupsEnd = &mRiggedAlphaGroups[mRiggedAlphaGroupsSize];
+}
+
 void LLCullResult::pushOcclusionGroup(LLSpatialGroup* group)
 {
 	if (mOcclusionGroupsSize < mOcclusionGroupsAllocated)
diff --git a/indra/newview/llspatialpartition.h b/indra/newview/llspatialpartition.h
index afe24d7d1fa9b8f332f320044461d44cc7166325..acfcd63686cde377bb93a6b622e33e23e86a05d6 100644
--- a/indra/newview/llspatialpartition.h
+++ b/indra/newview/llspatialpartition.h
@@ -40,6 +40,8 @@
 #include "llface.h"
 #include "llviewercamera.h"
 #include "llvector4a.h"
+#include "llvoavatar.h"
+
 #include <queue>
 #include <unordered_map>
 
@@ -125,7 +127,7 @@ class LLDrawInfo : public LLRefCount
 	F32  mAlphaMaskCutoff;
 	U8   mDiffuseAlphaMode;
 	bool mSelected;
-    LLVOAvatar* mAvatar = nullptr;
+    LLPointer<LLVOAvatar> mAvatar = nullptr;
     LLMeshSkinInfo* mSkinInfo = nullptr;
 
 
@@ -470,6 +472,9 @@ class LLCullResult
 	sg_iterator beginAlphaGroups();
 	sg_iterator endAlphaGroups();
 
+    sg_iterator beginRiggedAlphaGroups();
+    sg_iterator endRiggedAlphaGroups();
+
 	bool hasOcclusionGroups() { return mOcclusionGroupsSize > 0; }
 	sg_iterator beginOcclusionGroups();
 	sg_iterator endOcclusionGroups();
@@ -488,6 +493,7 @@ class LLCullResult
 
 	void pushVisibleGroup(LLSpatialGroup* group);
 	void pushAlphaGroup(LLSpatialGroup* group);
+    void pushRiggedAlphaGroup(LLSpatialGroup* group);
 	void pushOcclusionGroup(LLSpatialGroup* group);
 	void pushDrawableGroup(LLSpatialGroup* group);
 	void pushDrawable(LLDrawable* drawable);
@@ -496,6 +502,7 @@ class LLCullResult
 	
 	U32 getVisibleGroupsSize()		{ return mVisibleGroupsSize; }
 	U32	getAlphaGroupsSize()		{ return mAlphaGroupsSize; }
+    U32	getRiggedAlphaGroupsSize() { return mRiggedAlphaGroupsSize; }
 	U32	getDrawableGroupsSize()		{ return mDrawableGroupsSize; }
 	U32	getVisibleListSize()		{ return mVisibleListSize; }
 	U32	getVisibleBridgeSize()		{ return mVisibleBridgeSize; }
@@ -509,6 +516,7 @@ class LLCullResult
 
 	U32					mVisibleGroupsSize;
 	U32					mAlphaGroupsSize;
+    U32                 mRiggedAlphaGroupsSize;
 	U32					mOcclusionGroupsSize;
 	U32					mDrawableGroupsSize;
 	U32					mVisibleListSize;
@@ -516,6 +524,7 @@ class LLCullResult
 
 	U32					mVisibleGroupsAllocated;
 	U32					mAlphaGroupsAllocated;
+    U32                 mRiggedAlphaGroupsAllocated;
 	U32					mOcclusionGroupsAllocated;
 	U32					mDrawableGroupsAllocated;
 	U32					mVisibleListAllocated;
@@ -527,6 +536,8 @@ class LLCullResult
 	sg_iterator			mVisibleGroupsEnd;
 	sg_list_t			mAlphaGroups;
 	sg_iterator			mAlphaGroupsEnd;
+    sg_list_t           mRiggedAlphaGroups;
+    sg_iterator         mRiggedAlphaGroupsEnd;
 	sg_list_t			mOcclusionGroups;
 	sg_iterator			mOcclusionGroupsEnd;
 	sg_list_t			mDrawableGroups;
diff --git a/indra/newview/lltoolmorph.cpp b/indra/newview/lltoolmorph.cpp
index b2e8348ffd4665d5b73e580c25caaf1c122abe77..b5eb2880aee21b58c6077d6e4e7cb51e0aae41ba 100644
--- a/indra/newview/lltoolmorph.cpp
+++ b/indra/newview/lltoolmorph.cpp
@@ -239,17 +239,8 @@ BOOL LLVisualParamHint::render()
 
 	LLViewerCamera::getInstance()->setPerspective(FALSE, mOrigin.mX, mOrigin.mY, mFullWidth, mFullHeight, FALSE);
 
-	if (gAgentAvatarp->mDrawable.notNull() &&
-		gAgentAvatarp->mDrawable->getFace(0))
-	{
-		LLDrawPoolAvatar *avatarPoolp = (LLDrawPoolAvatar *)gAgentAvatarp->mDrawable->getFace(0)->getPool();
-		LLGLDepthTest gls_depth(GL_TRUE, GL_TRUE);
-        gGL.flush();
-		gGL.setSceneBlendType(LLRender::BT_REPLACE);
-		avatarPoolp->renderAvatars(gAgentAvatarp);  // renders only one avatar
-		gGL.setSceneBlendType(LLRender::BT_ALPHA);
-        gGL.flush();
-	}
+    gPipeline.previewAvatar(gAgentAvatarp);
+
 	gAgentAvatarp->setVisualParamWeight(mVisualParam->getID(), mLastParamWeight);
 	mWearablePtr->setVisualParamWeight(mVisualParam->getID(), mLastParamWeight);
 	LLViewerWearable* wearable = (LLViewerWearable*)mWearablePtr;
diff --git a/indra/newview/lltracker.cpp b/indra/newview/lltracker.cpp
index 728d0c9417218af854b2dee3e534fcb76d201f7f..ab4ad5817b95bc89530b74fe855244df48712fa0 100644
--- a/indra/newview/lltracker.cpp
+++ b/indra/newview/lltracker.cpp
@@ -109,7 +109,16 @@ void LLTracker::stopTracking(bool clear_ui)
 // static virtual
 void LLTracker::drawHUDArrow()
 {
-	if (!gSavedSettings.getBOOL("RenderTrackerBeacon")) return;
+    if (!LLWorld::instanceExists())
+    {
+        return;
+    }
+
+    static LLCachedControl<bool> render_beacon(gSavedSettings, "RenderTrackerBeacon", true);
+    if (!render_beacon)
+    {
+        return;
+    }
 
 	if (gViewerWindow->getProgressView()->getVisible()) return;
 
diff --git a/indra/newview/llviewercontrol.cpp b/indra/newview/llviewercontrol.cpp
index 4d86da5f784e5ea6349408061e58020cb239862f..40a7f0a9418d8ec7a04afbe89d3f998e517eb0db 100644
--- a/indra/newview/llviewercontrol.cpp
+++ b/indra/newview/llviewercontrol.cpp
@@ -266,9 +266,8 @@ static bool handleAnisotropicChanged(const LLSD& newvalue)
 
 static bool handleVSyncChanged(const LLSD& newvalue)
 {
-#if LL_WINDOWS
     gViewerWindow->getWindow()->toggleVSync(newvalue.asBoolean());
-#endif
+
     return true;
 }
 
diff --git a/indra/newview/llviewerdisplay.cpp b/indra/newview/llviewerdisplay.cpp
index a26331251ced5089113890803e43849760783606..bc8171b2fc56ab49917152307a7a6b7964aaa294 100644
--- a/indra/newview/llviewerdisplay.cpp
+++ b/indra/newview/llviewerdisplay.cpp
@@ -649,9 +649,6 @@ void display(BOOL rebuild, F32 zoom_factor, int subfield, BOOL for_snapshot)
 			gPipeline.toggleRenderType(LLPipeline::RENDER_TYPE_HUD_PARTICLES);
 		}
 
-		//upkeep gl name pools
-		LLGLNamePool::upkeepPools();
-		
 		stop_glerror();
 		display_update_camera();
 		stop_glerror();
@@ -791,6 +788,8 @@ void display(BOOL rebuild, F32 zoom_factor, int subfield, BOOL for_snapshot)
 				LLViewerTexture::updateClass();
 			}
 
+            LLImageGLThread::updateClass();
+
 			{
 				LL_RECORD_BLOCK_TIME(FTM_IMAGE_UPDATE_BUMP);
 				gBumpImageList.updateImages();  // must be called before gTextureList version so that it's textures are thrown out first.
diff --git a/indra/newview/llviewermedia.cpp b/indra/newview/llviewermedia.cpp
index 2bc0eeed923795f7fb45632c367cc37a4e7a38c8..27d8df28c34892abee0dea1cf9316baf13ef2516 100644
--- a/indra/newview/llviewermedia.cpp
+++ b/indra/newview/llviewermedia.cpp
@@ -39,6 +39,7 @@
 #include "llfilepicker.h"
 #include "llfloaterwebcontent.h"	// for handling window close requests and geometry change requests in media browser windows.
 #include "llfocusmgr.h"
+#include "llimagegl.h"
 #include "llkeyboard.h"
 #include "lllogininstance.h"
 #include "llmarketplacefunctions.h"
@@ -606,6 +607,7 @@ static bool proximity_comparitor(const LLViewerMediaImpl* i1, const LLViewerMedi
 static LLTrace::BlockTimerStatHandle FTM_MEDIA_UPDATE("Update Media");
 static LLTrace::BlockTimerStatHandle FTM_MEDIA_SPARE_IDLE("Spare Idle");
 static LLTrace::BlockTimerStatHandle FTM_MEDIA_UPDATE_INTEREST("Update/Interest");
+static LLTrace::BlockTimerStatHandle FTM_MEDIA_UPDATE_VOLUME("Update/Volume");
 static LLTrace::BlockTimerStatHandle FTM_MEDIA_SORT("Media Sort");
 static LLTrace::BlockTimerStatHandle FTM_MEDIA_SORT2("Media Sort 2");
 static LLTrace::BlockTimerStatHandle FTM_MEDIA_MISC("Misc");
@@ -625,6 +627,13 @@ void LLViewerMedia::updateMedia(void *dummy_arg)
 	// Enable/disable the plugin read thread
 	LLPluginProcessParent::setUseReadThread(gSavedSettings.getBOOL("PluginUseReadThread"));
 
+    // SL-16418 We can't call LLViewerMediaImpl->update() if we are in the state of shutting down.
+    if(LLApp::isExiting())
+    {
+        setAllMediaEnabled(false);
+        return;
+    }
+
 	// HACK: we always try to keep a spare running webkit plugin around to improve launch times.
 	// 2017-04-19 Removed CP - this doesn't appear to buy us much and consumes a lot of resources so
 	// removing it for now.
@@ -1561,6 +1570,8 @@ LLViewerMediaImpl::LLViewerMediaImpl(	  const LLUUID& texture_id,
 		media_tex->setMediaImpl();
 	}
 
+    mMainQueue = LL::WorkQueue::getInstance("mainloop");
+    mTexUpdateQueue = LL::WorkQueue::getInstance("LLImageGL"); // Share work queue with tex loader.
 }
 
 //////////////////////////////////////////////////////////////////////////////////////////
@@ -1647,11 +1658,13 @@ void LLViewerMediaImpl::destroyMediaSource()
 
 	cancelMimeTypeProbe();
 
+    mLock.lock();   // Delay tear-down while bg thread is updating
 	if(mMediaSource)
 	{
 		mMediaSource->setDeleteOK(true) ;
 		mMediaSource = NULL; // shared pointer
 	}
+    mLock.unlock();
 }
 
 //////////////////////////////////////////////////////////////////////////////////////////
@@ -2060,6 +2073,7 @@ void LLViewerMediaImpl::setMute(bool mute)
 //////////////////////////////////////////////////////////////////////////////////////////
 void LLViewerMediaImpl::updateVolume()
 {
+    LL_RECORD_BLOCK_TIME(FTM_MEDIA_UPDATE_VOLUME);
 	if(mMediaSource)
 	{
 		// always scale the volume by the global media volume
@@ -2772,199 +2786,271 @@ static LLTrace::BlockTimerStatHandle FTM_MEDIA_SET_SUBIMAGE("Set Subimage");
 void LLViewerMediaImpl::update()
 {
     LL_PROFILE_ZONE_SCOPED_CATEGORY_MEDIA; //LL_RECORD_BLOCK_TIME(FTM_MEDIA_DO_UPDATE);
-	if(mMediaSource == NULL)
-	{
-		if(mPriority == LLPluginClassMedia::PRIORITY_UNLOADED)
-		{
-			// This media source should not be loaded.
-		}
-		else if(mPriority <= LLPluginClassMedia::PRIORITY_SLIDESHOW)
-		{
-			// Don't load new instances that are at PRIORITY_SLIDESHOW or below.  They're just kept around to preserve state.
-		}
+    if(mMediaSource == NULL)
+    {
+        if(mPriority == LLPluginClassMedia::PRIORITY_UNLOADED)
+        {
+            // This media source should not be loaded.
+        }
+        else if(mPriority <= LLPluginClassMedia::PRIORITY_SLIDESHOW)
+        {
+            // Don't load new instances that are at PRIORITY_SLIDESHOW or below.  They're just kept around to preserve state.
+        }
         else if (!mMimeProbe.expired())
-		{
-			// this media source is doing a MIME type probe -- don't try loading it again.
-		}
-		else
-		{
-			// This media may need to be loaded.
-			if(sMediaCreateTimer.hasExpired())
-			{
-				LL_DEBUGS("PluginPriority") << this << ": creating media based on timer expiration" << LL_ENDL;
-				createMediaSource();
-				sMediaCreateTimer.setTimerExpirySec(LLVIEWERMEDIA_CREATE_DELAY);
-			}
-			else
-			{
-				LL_DEBUGS("PluginPriority") << this << ": NOT creating media (waiting on timer)" << LL_ENDL;
-			}
-		}
-	}
-	else
-	{
-		updateVolume();
+        {
+            // this media source is doing a MIME type probe -- don't try loading it again.
+        }
+        else
+        {
+            // This media may need to be loaded.
+            if(sMediaCreateTimer.hasExpired())
+            {
+                LL_DEBUGS("PluginPriority") << this << ": creating media based on timer expiration" << LL_ENDL;
+                createMediaSource();
+                sMediaCreateTimer.setTimerExpirySec(LLVIEWERMEDIA_CREATE_DELAY);
+            }
+            else
+            {
+                LL_DEBUGS("PluginPriority") << this << ": NOT creating media (waiting on timer)" << LL_ENDL;
+            }
+        }
+    }
+    else
+    {
+        updateVolume();
 
-		// TODO: this is updated every frame - is this bad?
-		// Removing this as part of the post viewer64 media update
-		// Removed as not implemented in CEF embedded browser
-		// See MAINT-8194 for a more fuller description
-		// updateJavascriptObject();
-	}
+        // TODO: this is updated every frame - is this bad?
+        // Removing this as part of the post viewer64 media update
+        // Removed as not implemented in CEF embedded browser
+        // See MAINT-8194 for a more fuller description
+        // updateJavascriptObject();
+    }
 
 
-	if(mMediaSource == NULL)
-	{
-		return;
-	}
+    if(mMediaSource == NULL)
+    {
+        return;
+    }
 
-	// Make sure a navigate doesn't happen during the idle -- it can cause mMediaSource to get destroyed, which can cause a crash.
-	setNavigateSuspended(true);
+    // Make sure a navigate doesn't happen during the idle -- it can cause mMediaSource to get destroyed, which can cause a crash.
+    setNavigateSuspended(true);
 
-	mMediaSource->idle();
+    mMediaSource->idle();
 
-	setNavigateSuspended(false);
+    setNavigateSuspended(false);
 
-	if(mMediaSource == NULL)
-	{
-		return;
-	}
+    if(mMediaSource == NULL)
+    {
+        return;
+    }
 
-	if(mMediaSource->isPluginExited())
-	{
-		resetPreviousMediaState();
-		destroyMediaSource();
-		return;
-	}
+    if(mMediaSource->isPluginExited())
+    {
+        resetPreviousMediaState();
+        destroyMediaSource();
+        return;
+    }
 
-	if(!mMediaSource->textureValid())
-	{
-		return;
-	}
+    if(!mMediaSource->textureValid())
+    {
+        return;
+    }
 
-	if(mSuspendUpdates || !mVisible)
-	{
-		return;
-	}
+    if(mSuspendUpdates || !mVisible)
+    {
+        return;
+    }
 
-	LLViewerMediaTexture* placeholder_image = updatePlaceholderImage();
+    
+    LLViewerMediaTexture* media_tex;
+    U8* data;
+    S32 data_width;
+    S32 data_height;
+    S32 x_pos;
+    S32 y_pos;
+    S32 width;
+    S32 height;
+
+    if (preMediaTexUpdate(media_tex, data, data_width, data_height, x_pos, y_pos, width, height))
+    {
+        // Push update to worker thread
+        auto main_queue = LLImageGLThread::sEnabled ? mMainQueue.lock() : nullptr;
+        if (main_queue)
+        {
+            mTextureUpdatePending = true;
+            ref();  // protect texture from deletion while active on bg queue
+            media_tex->ref();
+            main_queue->postTo(
+                mTexUpdateQueue, // Worker thread queue
+                [=]() // work done on update worker thread
+                {
+#if LL_IMAGEGL_THREAD_CHECK
+                    media_tex->getGLTexture()->mActiveThread = LLThread::currentID();
+#endif
+                    doMediaTexUpdate(media_tex, data, data_width, data_height, x_pos, y_pos, width, height, true);
+                },
+                [=]() // callback to main thread
+                {
+#if LL_IMAGEGL_THREAD_CHECK
+                    media_tex->getGLTexture()->mActiveThread = LLThread::currentID();
+#endif
+                    mTextureUpdatePending = false;
+                    media_tex->unref();
+                    unref();
+                });
+        }
+        else
+        {
+            doMediaTexUpdate(media_tex, data, data_width, data_height, x_pos, y_pos, width, height, false); // otherwise, update on main thread
+        }
+    }
+}
 
-	if(placeholder_image)
-	{
-		LLRect dirty_rect;
+bool LLViewerMediaImpl::preMediaTexUpdate(LLViewerMediaTexture*& media_tex, U8*& data, S32& data_width, S32& data_height, S32& x_pos, S32& y_pos, S32& width, S32& height)
+{
+    LL_PROFILE_ZONE_SCOPED_CATEGORY_MEDIA;
 
-		// Since we're updating this texture, we know it's playing.  Tell the texture to do its replacement magic so it gets rendered.
-		placeholder_image->setPlaying(TRUE);
+    bool retval = false;
 
-		if(mMediaSource->getDirty(&dirty_rect))
-		{
-			// Constrain the dirty rect to be inside the texture
-			S32 x_pos = llmax(dirty_rect.mLeft, 0);
-			S32 y_pos = llmax(dirty_rect.mBottom, 0);
-			S32 width = llmin(dirty_rect.mRight, placeholder_image->getWidth()) - x_pos;
-			S32 height = llmin(dirty_rect.mTop, placeholder_image->getHeight()) - y_pos;
+    if (!mTextureUpdatePending)
+    {
+        media_tex = updateMediaImage();
 
-			if(width > 0 && height > 0)
-			{
+        if (media_tex && mMediaSource)
+        {
+            LLRect dirty_rect;
+            S32 media_width = mMediaSource->getTextureWidth();
+            S32 media_height = mMediaSource->getTextureHeight();
+            //S32 media_depth = mMediaSource->getTextureDepth();
 
-				U8* data = NULL;
-				{
-					LL_PROFILE_ZONE_NAMED_CATEGORY_MEDIA("media get data"); //LL_RECORD_BLOCK_TIME(FTM_MEDIA_GET_DATA);
-					data = mMediaSource->getBitsData();
-				}
+            // Since we're updating this texture, we know it's playing.  Tell the texture to do its replacement magic so it gets rendered.
+            media_tex->setPlaying(TRUE);
 
-				if(data != NULL)
-				{
-					// Offset the pixels pointer to match x_pos and y_pos
-					data += ( x_pos * mMediaSource->getTextureDepth() * mMediaSource->getBitsWidth() );
-					data += ( y_pos * mMediaSource->getTextureDepth() );
+            if (mMediaSource->getDirty(&dirty_rect))
+            {
+                // Constrain the dirty rect to be inside the texture
+                x_pos = llmax(dirty_rect.mLeft, 0);
+                y_pos = llmax(dirty_rect.mBottom, 0);
+                width = llmin(dirty_rect.mRight, media_width) - x_pos;
+                height = llmin(dirty_rect.mTop, media_height) - y_pos;
 
-					{
-						LL_PROFILE_ZONE_NAMED_CATEGORY_MEDIA("media set subimage"); //LL_RECORD_BLOCK_TIME(FTM_MEDIA_SET_SUBIMAGE);
-									placeholder_image->setSubImage(
-									data,
-									mMediaSource->getBitsWidth(),
-									mMediaSource->getBitsHeight(),
-									x_pos,
-									y_pos,
-									width,
-									height);
-					}
-				}
+                if (width > 0 && height > 0)
+                {
+                    data = mMediaSource->getBitsData();
+                    data_width = mMediaSource->getWidth();
+                    data_height = mMediaSource->getHeight();
+
+                    if (data != NULL)
+                    {
+                        // data is ready to be copied to GL
+                        retval = true;
+                    }
+                }
 
-			}
+                mMediaSource->resetDirty();
+            }
+        }
+    }
 
-			mMediaSource->resetDirty();
-		}
-	}
+    return retval;
 }
 
+//////////////////////////////////////////////////////////////////////////////////////////
+void LLViewerMediaImpl::doMediaTexUpdate(LLViewerMediaTexture* media_tex, U8* data, S32 data_width, S32 data_height, S32 x_pos, S32 y_pos, S32 width, S32 height, bool sync)
+{
+    LL_PROFILE_ZONE_SCOPED_CATEGORY_MEDIA;
+    mLock.lock();   // don't allow media source tear-down during update
+
+    // wrap "data" in an LLImageRaw but do NOT make a copy
+    LLPointer<LLImageRaw> raw = new LLImageRaw(data, media_tex->getWidth(), media_tex->getHeight(), media_tex->getComponents(), true);
+        
+    // Allocate GL texture based on LLImageRaw but do NOT copy to GL
+    LLGLuint tex_name = 0;
+    media_tex->createGLTexture(0, raw, 0, TRUE, LLGLTexture::OTHER, true, &tex_name);
+
+    // copy just the subimage covered by the image raw to GL
+    media_tex->setSubImage(data, data_width, data_height, x_pos, y_pos, width, height, tex_name);
+    
+    if (sync)
+    {
+        media_tex->getGLTexture()->syncToMainThread(tex_name);
+    }
+    else
+    {
+        media_tex->getGLTexture()->syncTexName(tex_name);
+    }
+    
+    // release the data pointer before freeing raw so LLImageRaw destructor doesn't
+    // free memory at data pointer
+    raw->releaseData();
+
+    mLock.unlock();
+}
 
 //////////////////////////////////////////////////////////////////////////////////////////
 void LLViewerMediaImpl::updateImagesMediaStreams()
 {
 }
 
-
 //////////////////////////////////////////////////////////////////////////////////////////
-LLViewerMediaTexture* LLViewerMediaImpl::updatePlaceholderImage()
+LLViewerMediaTexture* LLViewerMediaImpl::updateMediaImage()
 {
-	if(mTextureId.isNull())
-	{
-		// The code that created this instance will read from the plugin's bits.
-		return NULL;
-	}
-
-	LLViewerMediaTexture* placeholder_image = LLViewerTextureManager::getMediaTexture( mTextureId );
-
-	if (mNeedsNewTexture
-		|| placeholder_image->getUseMipMaps()
-		|| (placeholder_image->getWidth() != mMediaSource->getTextureWidth())
-		|| (placeholder_image->getHeight() != mMediaSource->getTextureHeight())
-		|| (mTextureUsedWidth != mMediaSource->getWidth())
-		|| (mTextureUsedHeight != mMediaSource->getHeight())
-		)
-	{
-		LL_DEBUGS("Media") << "initializing media placeholder" << LL_ENDL;
-		LL_DEBUGS("Media") << "movie image id " << mTextureId << LL_ENDL;
-
-		int texture_width = mMediaSource->getTextureWidth();
-		int texture_height = mMediaSource->getTextureHeight();
-		int texture_depth = mMediaSource->getTextureDepth();
-
-		// MEDIAOPT: check to see if size actually changed before doing work
-		placeholder_image->destroyGLTexture();
-		// MEDIAOPT: apparently just calling setUseMipMaps(FALSE) doesn't work?
-		placeholder_image->reinit(FALSE);	// probably not needed
-
-		// MEDIAOPT: seems insane that we actually have to make an imageraw then
-		// immediately discard it
-		LLPointer<LLImageRaw> raw = new LLImageRaw(texture_width, texture_height, texture_depth);
-		// Clear the texture to the background color, ignoring alpha.
-		// convert background color channels from [0.0, 1.0] to [0, 255];
-		raw->clear(int(mBackgroundColor.mV[VX] * 255.0f), int(mBackgroundColor.mV[VY] * 255.0f), int(mBackgroundColor.mV[VZ] * 255.0f), 0xff);
-		int discard_level = 0;
-
-		// ask media source for correct GL image format constants
-		placeholder_image->setExplicitFormat(mMediaSource->getTextureFormatInternal(),
-											 mMediaSource->getTextureFormatPrimary(),
-											 mMediaSource->getTextureFormatType(),
-											 mMediaSource->getTextureFormatSwapBytes());
-
-		placeholder_image->createGLTexture(discard_level, raw);
-
-		// MEDIAOPT: set this dynamically on play/stop
-		// FIXME
-//		placeholder_image->mIsMediaTexture = true;
-		mNeedsNewTexture = false;
-
-		// If the amount of the texture being drawn by the media goes down in either width or height,
-		// recreate the texture to avoid leaving parts of the old image behind.
-		mTextureUsedWidth = mMediaSource->getWidth();
-		mTextureUsedHeight = mMediaSource->getHeight();
-	}
+    LL_PROFILE_ZONE_SCOPED_CATEGORY_MEDIA;
+    if (!mMediaSource)
+    {
+        return nullptr; // not ready for updating
+    }
 
-	return placeholder_image;
+    llassert(!mTextureId.isNull());
+    LLViewerMediaTexture* media_tex = LLViewerTextureManager::getMediaTexture( mTextureId );
+ 
+    if ( mNeedsNewTexture
+        || media_tex->getUseMipMaps()
+        || (media_tex->getWidth() != mMediaSource->getTextureWidth())
+        || (media_tex->getHeight() != mMediaSource->getTextureHeight())
+        || (mTextureUsedWidth != mMediaSource->getWidth())
+        || (mTextureUsedHeight != mMediaSource->getHeight())
+        )
+    {
+        LL_DEBUGS("Media") << "initializing media placeholder" << LL_ENDL;
+        LL_DEBUGS("Media") << "movie image id " << mTextureId << LL_ENDL;
+
+        int texture_width = mMediaSource->getTextureWidth();
+        int texture_height = mMediaSource->getTextureHeight();
+        int texture_depth = mMediaSource->getTextureDepth();
+
+        // MEDIAOPT: check to see if size actually changed before doing work
+        media_tex->destroyGLTexture();
+        // MEDIAOPT: apparently just calling setUseMipMaps(FALSE) doesn't work?
+        media_tex->reinit(FALSE);	// probably not needed
+
+        // MEDIAOPT: seems insane that we actually have to make an imageraw then
+        // immediately discard it
+        LLPointer<LLImageRaw> raw = new LLImageRaw(texture_width, texture_height, texture_depth);
+        // Clear the texture to the background color, ignoring alpha.
+        // convert background color channels from [0.0, 1.0] to [0, 255];
+        raw->clear(int(mBackgroundColor.mV[VX] * 255.0f), int(mBackgroundColor.mV[VY] * 255.0f), int(mBackgroundColor.mV[VZ] * 255.0f), 0xff);
+
+        // ask media source for correct GL image format constants
+        media_tex->setExplicitFormat(mMediaSource->getTextureFormatInternal(),
+            mMediaSource->getTextureFormatPrimary(),
+            mMediaSource->getTextureFormatType(),
+            mMediaSource->getTextureFormatSwapBytes());
+
+        int discard_level = 0;
+        media_tex->createGLTexture(discard_level, raw);
+
+        // MEDIAOPT: set this dynamically on play/stop
+        // FIXME
+//		media_tex->mIsMediaTexture = true;
+        mNeedsNewTexture = false;
+
+        // If the amount of the texture being drawn by the media goes down in either width or height,
+        // recreate the texture to avoid leaving parts of the old image behind.
+        mTextureUsedWidth = mMediaSource->getWidth();
+        mTextureUsedHeight = mMediaSource->getHeight();
+    }
+    return media_tex;
 }
 
 
diff --git a/indra/newview/llviewermedia.h b/indra/newview/llviewermedia.h
index 71cec5125d91aa4f86cf1757b7eb8920af9fa421..806692929a3ea55b471ff7f95981760bfe3b0c83 100644
--- a/indra/newview/llviewermedia.h
+++ b/indra/newview/llviewermedia.h
@@ -197,7 +197,7 @@ class LLViewerMediaImpl
 		U8 media_loop);
 
 	~LLViewerMediaImpl();
-	
+
 	// Override inherited version from LLViewerMediaEventEmitter 
 	virtual void emitEvent(LLPluginClassMedia* self, LLViewerMediaObserver::EMediaEvent event);
 
@@ -266,6 +266,8 @@ class LLViewerMediaImpl
 	void scaleTextureCoords(const LLVector2& texture_coords, S32 *x, S32 *y);
 
 	void update();
+    bool preMediaTexUpdate(LLViewerMediaTexture*& media_tex, U8*& data, S32& data_width, S32& data_height, S32& x_pos, S32& y_pos, S32& width, S32& height);
+    void doMediaTexUpdate(LLViewerMediaTexture* media_tex, U8* data, S32 data_width, S32 data_height, S32 x_pos, S32 y_pos, S32 width, S32 height, bool sync);
 	void updateImagesMediaStreams();
 	LLUUID getMediaTextureID() const;
 	
@@ -427,6 +429,7 @@ class LLViewerMediaImpl
 private:
 	// a single media url with some data and an impl.
 	boost::shared_ptr<LLPluginClassMedia> mMediaSource;
+    LLMutex mLock;
 	F64		mZoomFactor;
 	LLUUID mTextureId;
 	bool  mMovieImageHasMips;
@@ -446,6 +449,7 @@ class LLViewerMediaImpl
 	S32 mTextureUsedWidth;
 	S32 mTextureUsedHeight;
 	bool mSuspendUpdates;
+    bool mTextureUpdatePending = false;
 	bool mVisible;
 	ECursorType mLastSetCursor;
 	EMediaNavState mMediaNavState;
@@ -479,7 +483,7 @@ class LLViewerMediaImpl
 	LLNotificationPtr mNotification;
     bool mCleanBrowser;     // force the creation of a clean browsing target with full options enabled
     static std::vector<std::string> sMimeTypesFailed;
-
+    LLPointer<LLImageRaw> mRawImage; //backing buffer for texture updates
 private:
 	BOOL mIsUpdated ;
 	std::list< LLVOVolume* > mObjectList ;
@@ -489,7 +493,10 @@ class LLViewerMediaImpl
     bool mCanceling;
 
 private:
-	LLViewerMediaTexture *updatePlaceholderImage();
+	LLViewerMediaTexture *updateMediaImage();
+    LL::WorkQueue::weak_t mMainQueue;
+    LL::WorkQueue::weak_t mTexUpdateQueue;
+
 };
 
 #endif	// LLVIEWERMEDIA_H
diff --git a/indra/newview/llvieweroctree.cpp b/indra/newview/llvieweroctree.cpp
index 3cdef0ebff1ae075fb932680d85a2a81b45f7f58..5eda75753e5f3cfb3403fd1f70f0c156af30fa24 100644
--- a/indra/newview/llvieweroctree.cpp
+++ b/indra/newview/llvieweroctree.cpp
@@ -787,42 +787,39 @@ void LLViewerOctreeGroup::checkStates()
 //occulsion culling functions and classes
 //-------------------------------------------------------------------------------------------
 std::set<U32> LLOcclusionCullingGroup::sPendingQueries;
-class LLOcclusionQueryPool : public LLGLNamePool
-{
-public:
-	LLOcclusionQueryPool()
-	{
-	}
-
-protected:
 
-	virtual GLuint allocateName()
-	{
-		GLuint ret = 0;
+static std::queue<GLuint> sFreeQueries;
 
-		glGenQueriesARB(1, &ret);
-	
-		return ret;
-	}
+#define QUERY_POOL_SIZE 1024
 
-	virtual void releaseName(GLuint name)
-	{
-#if LL_TRACK_PENDING_OCCLUSION_QUERIES
-		LLOcclusionCullingGroup::sPendingQueries.erase(name);
-#endif
-		glDeleteQueriesARB(1, &name);
-	}
-};
-
-static LLOcclusionQueryPool sQueryPool;
 U32 LLOcclusionCullingGroup::getNewOcclusionQueryObjectName()
 {
-	return sQueryPool.allocate();
+    LL_PROFILE_ZONE_SCOPED;
+    
+    if (sFreeQueries.empty())
+    {
+        //seed 1024 query names into the free query pool
+        GLuint queries[1024];
+        glGenQueriesARB(1024, queries);
+        for (int i = 0; i < 1024; ++i)
+        {
+            sFreeQueries.push(queries[i]);
+        }
+    }
+
+    // pull from pool
+    GLuint ret = sFreeQueries.front();
+    sFreeQueries.pop();
+    return ret;
 }
 
 void LLOcclusionCullingGroup::releaseOcclusionQueryObjectName(GLuint name)
 {
-	sQueryPool.release(name);
+    if (name != 0)
+    {
+        LL_PROFILE_ZONE_SCOPED;
+        sFreeQueries.push(name);
+    }
 }
 
 //=====================================
@@ -1243,7 +1240,14 @@ void LLOcclusionCullingGroup::doOcclusion(LLCamera* camera, const LLVector4a* sh
 						//store which frame this query was issued on
 						mOcclusionIssued[LLViewerCamera::sCurCameraID] = gFrameCount;
 
-    					glBeginQueryARB(mode, mOcclusionQuery[LLViewerCamera::sCurCameraID]);					
+                        {
+                            LL_PROFILE_ZONE_NAMED("glBeginQuery");
+
+                            //get an occlusion query that hasn't been used in awhile
+                            releaseOcclusionQueryObjectName(mOcclusionQuery[LLViewerCamera::sCurCameraID]);
+                            mOcclusionQuery[LLViewerCamera::sCurCameraID] = getNewOcclusionQueryObjectName();
+                            glBeginQueryARB(mode, mOcclusionQuery[LLViewerCamera::sCurCameraID]);
+                        }
 					
 						LLGLSLShader* shader = LLGLSLShader::sCurBoundShaderPtr;
 						llassert(shader);
@@ -1282,7 +1286,10 @@ void LLOcclusionCullingGroup::doOcclusion(LLCamera* camera, const LLVector4a* sh
 							}
 						}
 	
-						glEndQueryARB(mode);
+                        {
+                            LL_PROFILE_ZONE_NAMED("glEndQuery");
+                            glEndQueryARB(mode);
+                        }
 					}
 				}
 
diff --git a/indra/newview/llviewerparcelmgr.cpp b/indra/newview/llviewerparcelmgr.cpp
index 56370df7513638ff54198a47c4f440de5d9b04f5..e69b0347f8f9863e2e5a8cefc3c06f9fd5edf545 100644
--- a/indra/newview/llviewerparcelmgr.cpp
+++ b/indra/newview/llviewerparcelmgr.cpp
@@ -878,7 +878,7 @@ LLParcel* LLViewerParcelMgr::getCollisionParcel() const
 
 void LLViewerParcelMgr::render()
 {
-	if (mSelected && mRenderSelection && gSavedSettings.getBOOL("RenderParcelSelection"))
+	if (mSelected && mRenderSelection && gSavedSettings.getBOOL("RenderParcelSelection") && !gDisconnected)
 	{
 		// Rendering is done in agent-coordinates, so need to supply
 		// an appropriate offset to the render code.
diff --git a/indra/newview/llviewertexture.cpp b/indra/newview/llviewertexture.cpp
index 5fed46f43727810ac1637006069d16e9fab05dc7..e3ac56d0d399d4d817adcb540a1a616bf7a6ab9b 100644
--- a/indra/newview/llviewertexture.cpp
+++ b/indra/newview/llviewertexture.cpp
@@ -515,9 +515,10 @@ void LLViewerTexture::getGPUMemoryForTextures(S32Megabytes &gpu, S32Megabytes &p
 {
     LL_PROFILE_ZONE_SCOPED_CATEGORY_TEXTURE;
     static LLFrameTimer timer;
+
     static S32Megabytes gpu_res = S32Megabytes(S32_MAX);
     static S32Megabytes physical_res = S32Megabytes(S32_MAX);
-
+    
     if (timer.getElapsedTimeF32() < GPU_MEMORY_CHECK_WAIT_TIME) //call this once per second.
     {
         gpu = gpu_res;
@@ -527,22 +528,11 @@ void LLViewerTexture::getGPUMemoryForTextures(S32Megabytes &gpu, S32Megabytes &p
     timer.reset();
 
     {
-        if (gGLManager.mHasATIMemInfo)
-        {
-            S32 meminfo[4];
-            glGetIntegerv(GL_TEXTURE_FREE_MEMORY_ATI, meminfo);
-            gpu_res = (S32Megabytes)meminfo[0];
-
-            //check main memory, only works for windows.
-            LLMemory::updateMemoryInfo();
-            physical_res = LLMemory::getAvailableMemKB();
-        }
-        else if (gGLManager.mHasNVXMemInfo)
-        {
-            S32 free_memory;
-            glGetIntegerv(GL_GPU_MEMORY_INFO_CURRENT_AVAILABLE_VIDMEM_NVX, &free_memory);
-            gpu_res = (S32Megabytes)(free_memory / 1024);
-        }
+        gpu_res = (S32Megabytes) LLImageGLThread::getFreeVRAMMegabytes();
+        
+        //check main memory, only works for windows.
+        LLMemory::updateMemoryInfo();
+        physical_res = LLMemory::getAvailableMemKB();
 
         gpu = gpu_res;
         physical = physical_res;
@@ -1629,7 +1619,6 @@ void LLViewerFetchedTexture::scheduleCreateTexture()
         mNeedsCreateTexture = TRUE;
         if (preCreateTexture())
         {
-            ref();
 #if LL_IMAGEGL_THREAD_CHECK
             //grab a copy of the raw image data to make sure it isn't modified pending texture creation
             U8* data = mRawImage->getData();
@@ -1645,6 +1634,7 @@ void LLViewerFetchedTexture::scheduleCreateTexture()
             auto mainq = LLImageGLThread::sEnabled ? mMainQueue.lock() : nullptr;
             if (mainq)
             {
+                ref();
                 mainq->postTo(
                     mImageQueue,
                     // work to be done on LLImageGL worker thread
@@ -1691,7 +1681,6 @@ void LLViewerFetchedTexture::scheduleCreateTexture()
             else
             {
                 gTextureList.mCreateTextureList.insert(this);
-                unref();
             }
         }
     }
diff --git a/indra/newview/llviewerwindow.cpp b/indra/newview/llviewerwindow.cpp
index 5487f0851ec4624bb06d608a36481d3c30c1acba..a54c29a404197dd1511b3b7a143cccaf4c1f341f 100644
--- a/indra/newview/llviewerwindow.cpp
+++ b/indra/newview/llviewerwindow.cpp
@@ -2024,12 +2024,12 @@ LLViewerWindow::LLViewerWindow(const Params& p)
 		
 	// Init the image list.  Must happen after GL is initialized and before the images that
 	// LLViewerWindow needs are requested.
-	LLImageGL::initClass(mWindow, LLViewerTexture::MAX_GL_IMAGE_CATEGORY, false, gSavedSettings.getBOOL("RenderGLMultiThreaded"));
+    LLImageGL::initClass(mWindow, LLViewerTexture::MAX_GL_IMAGE_CATEGORY, false, gSavedSettings.getBOOL("RenderGLMultiThreaded"));
 	gTextureList.init();
 	LLViewerTextureManager::init() ;
 	gBumpImageList.init();
 	
-	// Create container for all sub-views
+    // Create container for all sub-views
 	LLView::Params rvp;
 	rvp.name("root");
 	rvp.rect(mWindowRectScaled);
@@ -2417,7 +2417,7 @@ void LLViewerWindow::shutdownGL()
 
 	LLViewerTextureManager::cleanup() ;
 	SUBSYSTEM_CLEANUP(LLImageGL) ;
-
+    
 	LL_INFOS() << "All textures and llimagegl images are destroyed!" << LL_ENDL ;
 
 	LL_INFOS() << "Cleaning up select manager" << LL_ENDL;
diff --git a/indra/newview/llvoavatar.cpp b/indra/newview/llvoavatar.cpp
index 548dadddb47cfca2e02e9667dca666ddb92ea13e..6a5cd6eabcf2f1b784a7afbc4b1549c05726d0e4 100644
--- a/indra/newview/llvoavatar.cpp
+++ b/indra/newview/llvoavatar.cpp
@@ -127,6 +127,9 @@ const F32 MIN_HOVER_Z = -2.0;
 const F32 MIN_ATTACHMENT_COMPLEXITY = 0.f;
 const F32 DEFAULT_MAX_ATTACHMENT_COMPLEXITY = 1.0e6f;
 
+// Unlike with 'self' avatar, server doesn't inform viewer about
+// expected attachments so viewer has to wait to see if anything
+// else will arrive
 const F32 FIRST_APPEARANCE_CLOUD_MIN_DELAY = 3.f; // seconds
 const F32 FIRST_APPEARANCE_CLOUD_MAX_DELAY = 45.f;
 
@@ -616,6 +619,7 @@ F32 LLVOAvatar::sUnbakedTime = 0.f;
 F32 LLVOAvatar::sUnbakedUpdateTime = 0.f;
 F32 LLVOAvatar::sGreyTime = 0.f;
 F32 LLVOAvatar::sGreyUpdateTime = 0.f;
+LLPointer<LLViewerTexture> LLVOAvatar::sCloudTexture = NULL;
 
 //-----------------------------------------------------------------------------
 // Helper functions
@@ -746,7 +750,7 @@ LLVOAvatar::LLVOAvatar(const LLUUID& id,
 
 	mCurrentGesticulationLevel = 0;
 
-	mFirstSeenTimer.reset();
+    mFirstAppearanceMessageTimer.reset();
 	mRuthTimer.reset();
 	mRuthDebugTimer.reset();
 	mDebugExistenceTimer.reset();
@@ -1133,6 +1137,7 @@ void LLVOAvatar::initClass()
 
 	LLControlAvatar::sRegionChangedSlot = gAgent.addRegionChangedCallback(&LLControlAvatar::onRegionChanged);
 
+    sCloudTexture = LLViewerTextureManager::getFetchedTextureFromFile("cloud-particle.j2c");
 }
 
 
@@ -2826,7 +2831,7 @@ void LLVOAvatar::idleUpdateMisc(bool detailed_update)
                     //override rigged attachments' octree spatial extents with this avatar's bounding box
                     LLSpatialBridge* bridge = attached_object->mDrawable->getSpatialBridge();
                     bool rigged = false;
-                    if (bridge)
+                    if (bridge && !bridge->isDead())
                     {
                         //transform avatar bounding box into attachment's coordinate frame
                         LLVector4a extents[2];
@@ -2843,13 +2848,21 @@ void LLVOAvatar::idleUpdateMisc(bool detailed_update)
                     attached_object->mDrawable->makeActive();
                     attached_object->mDrawable->updateXform(TRUE);
                     
-                    if (!rigged)
+                    if (bridge && !bridge->isDead())
                     {
-                        if (bridge)
+                        if (!rigged)
                         {
                             gPipeline.updateMoveNormalAsync(bridge);
                         }
+                        else
+                        {
+                            //specialized impl of updateMoveNormalAsync just for rigged attachment SpatialBridge
+                            bridge->setState(LLDrawable::MOVE_UNDAMPED);
+                            bridge->updateMove();
+                            bridge->setState(LLDrawable::EARLY_MOVE);
+                        }
                     }
+
 					attached_object->updateText();	
 				}
 			}
@@ -3073,8 +3086,7 @@ void LLVOAvatar::idleUpdateLoadingEffect()
 			particle_parameters.mPartData.mStartColor        = LLColor4(1, 1, 1, 0.5f);
 			particle_parameters.mPartData.mEndColor          = LLColor4(1, 1, 1, 0.0f);
 			particle_parameters.mPartData.mStartScale.mV[VX] = 0.8f;
-			LLViewerTexture* cloud = LLViewerTextureManager::getFetchedTextureFromFile("cloud-particle.j2c");
-			particle_parameters.mPartImageID                 = cloud->getID();
+			particle_parameters.mPartImageID                 = sCloudTexture->getID();
 			particle_parameters.mMaxAge                      = 0.f;
 			particle_parameters.mPattern                     = LLPartSysData::LL_PART_SRC_PATTERN_ANGLE_CONE;
 			particle_parameters.mInnerAngle                  = F_PI;
@@ -6428,6 +6440,16 @@ void LLVOAvatar::updateAttachmentOverrides()
 #endif
 }
 
+void LLVOAvatar::notifyAttachmentMeshLoaded()
+{
+    if (!isFullyLoaded())
+    {
+        // We just received mesh or skin info
+        // Reset timer to wait for more potential meshes or changes
+        mFullyLoadedTimer.reset();
+    }
+}
+
 //-----------------------------------------------------------------------------
 // addAttachmentOverridesForObject
 //-----------------------------------------------------------------------------
@@ -8150,7 +8172,7 @@ BOOL LLVOAvatar::processFullyLoadedChange(bool loading)
                 // Note that textures can causes 60s delay on thier own
                 // so this delay might end up on top of textures' delay
                 mFirstUseDelaySeconds = llclamp(
-                    mFirstSeenTimer.getElapsedTimeF32(),
+                    mFirstAppearanceMessageTimer.getElapsedTimeF32(),
                     FIRST_APPEARANCE_CLOUD_MIN_DELAY,
                     FIRST_APPEARANCE_CLOUD_MAX_DELAY);
 
@@ -8920,6 +8942,9 @@ void LLVOAvatar::onFirstTEMessageReceived()
 
         mMeshTexturesDirty = TRUE;
 		gPipeline.markGLRebuild(this);
+
+        mFirstAppearanceMessageTimer.reset();
+        mFullyLoadedTimer.reset();
 	}
 }
 
diff --git a/indra/newview/llvoavatar.h b/indra/newview/llvoavatar.h
index 282d0f6cdd7452bf5927088bc5d5bf926c1304dd..acebbc10743469f42bb32371d34940b2a97a3979 100644
--- a/indra/newview/llvoavatar.h
+++ b/indra/newview/llvoavatar.h
@@ -206,7 +206,7 @@ class LLVOAvatar :
     inline LLJoint* getSkeletonJoint(S32 joint_num) { return mSkeleton[joint_num]; }
     inline size_t getSkeletonJointCount() const { return mSkeleton.size(); }
 
-
+    void 					notifyAttachmentMeshLoaded();
 	void 					addAttachmentOverridesForObject(LLViewerObject *vo, std::set<LLUUID>* meshes_seen = NULL, bool recursive = true);
 	void					removeAttachmentOverridesForObject(const LLUUID& mesh_id);
 	void					removeAttachmentOverridesForObject(LLViewerObject *vo);
@@ -337,7 +337,8 @@ class LLVOAvatar :
 	static F32		sLODFactor; // user-settable LOD factor
 	static F32		sPhysicsLODFactor; // user-settable physics LOD factor
 	static BOOL		sJointDebug; // output total number of joints being touched for each avatar
-	static BOOL		sDebugAvatarRotation;
+
+    static LLPointer<LLViewerTexture>  sCloudTexture;
 
 	//--------------------------------------------------------------------
 	// Region state
@@ -380,7 +381,7 @@ class LLVOAvatar :
 private:
 	BOOL			mFirstFullyVisible;
 	F32				mFirstUseDelaySeconds;
-	LLFrameTimer	mFirstSeenTimer;
+	LLFrameTimer	mFirstAppearanceMessageTimer;
 
 	BOOL			mFullyLoaded;
 	BOOL			mPreviousFullyLoaded;
diff --git a/indra/newview/llvovolume.cpp b/indra/newview/llvovolume.cpp
index 4d72dc8776fa2a88e092218d3a4afe30c3bf6c7e..1625dd527632e13bca1243ae3373ba688c97d85e 100644
--- a/indra/newview/llvovolume.cpp
+++ b/indra/newview/llvovolume.cpp
@@ -225,6 +225,7 @@ LLVOVolume::LLVOVolume(const LLUUID &id, const LLPCode pcode, LLViewerRegion *re
 	mNumFaces = 0;
 	mLODChanged = FALSE;
 	mSculptChanged = FALSE;
+    mColorChanged = FALSE;
 	mSpotLightPriority = 0.f;
 
 	mMediaImplList.resize(getNumTEs());
@@ -1170,13 +1171,17 @@ void LLVOVolume::notifyMeshLoaded()
 	mSculptChanged = TRUE;
 	gPipeline.markRebuild(mDrawable, LLDrawable::REBUILD_GEOMETRY, TRUE);
 
-    if (getAvatar() && !isAnimatedObject())
+    LLVOAvatar *av = getAvatar();
+    if (av && !isAnimatedObject())
     {
-        getAvatar()->addAttachmentOverridesForObject(this);
+        av->addAttachmentOverridesForObject(this);
+        av->notifyAttachmentMeshLoaded();
     }
-    if (getControlAvatar() && isAnimatedObject())
+    LLControlAvatar *cav = getControlAvatar();
+    if (cav && isAnimatedObject())
     {
-        getControlAvatar()->addAttachmentOverridesForObject(this);
+        cav->addAttachmentOverridesForObject(this);
+        cav->notifyAttachmentMeshLoaded();
     }
     updateVisualComplexity();
 }
@@ -2027,7 +2032,7 @@ BOOL LLVOVolume::updateGeometry(LLDrawable *drawable)
 			was_regen_faces = lodOrSculptChanged(drawable, compiled);
 			drawable->setState(LLDrawable::REBUILD_VOLUME);
 		}
-		else if (mSculptChanged || mLODChanged)
+		else if (mSculptChanged || mLODChanged || mColorChanged)
 		{
 			compiled = TRUE;
 			was_regen_faces = lodOrSculptChanged(drawable, compiled);
@@ -2039,7 +2044,7 @@ BOOL LLVOVolume::updateGeometry(LLDrawable *drawable)
 
 		genBBoxes(FALSE);
 	}
-	else if (mLODChanged || mSculptChanged)
+	else if (mLODChanged || mSculptChanged || mColorChanged)
 	{
 		dirtySpatialGroup(drawable->isState(LLDrawable::IN_REBUILD_Q1));
 		compiled = TRUE;
@@ -2071,6 +2076,7 @@ BOOL LLVOVolume::updateGeometry(LLDrawable *drawable)
 	mLODChanged = FALSE;
 	mSculptChanged = FALSE;
 	mFaceMappingChanged = FALSE;
+    mColorChanged = FALSE;
 	
 	return LLViewerObject::updateGeometry(drawable);
 }
@@ -2209,6 +2215,7 @@ S32 LLVOVolume::setTEColor(const U8 te, const LLColor4& color)
 		if (mDrawable.notNull() && retval)
 		{
 			// These should only happen on updates which are not the initial update.
+            mColorChanged = TRUE;
 			mDrawable->setState(LLDrawable::REBUILD_COLOR);
 			dirtyMesh();
 		}
@@ -5139,7 +5146,7 @@ void LLVolumeGeometryManager::registerFace(LLSpatialGroup* group, LLFace* facep,
 
     bool rigged = facep->isState(LLFace::RIGGED);
 
-    if (rigged && type != LLRenderPass::PASS_ALPHA)
+    if (rigged)
     {
         // hacky, should probably clean up -- if this face is rigged, put it in "type + 1"
         // See LLRenderPass PASS_foo enum
@@ -5903,23 +5910,25 @@ void LLVolumeGeometryManager::rebuildGeom(LLSpatialGroup* group)
 
 	U32 geometryBytes = 0;
 
+    // generate render batches for static geometry
     U32 extra_mask = LLVertexBuffer::MAP_TEXTURE_INDEX;
-	geometryBytes += genDrawInfo(group, simple_mask | extra_mask, sSimpleFaces[0], simple_count[0], FALSE, batch_textures);
-	geometryBytes += genDrawInfo(group, fullbright_mask | extra_mask, sFullbrightFaces[0], fullbright_count[0], FALSE, batch_textures);
-	geometryBytes += genDrawInfo(group, alpha_mask | extra_mask, sAlphaFaces[0], alpha_count[0], TRUE, batch_textures);
-	geometryBytes += genDrawInfo(group, bump_mask | extra_mask, sBumpFaces[0], bump_count[0], FALSE, FALSE);
-	geometryBytes += genDrawInfo(group, norm_mask | extra_mask, sNormFaces[0], norm_count[0], FALSE, FALSE);
-	geometryBytes += genDrawInfo(group, spec_mask | extra_mask, sSpecFaces[0], spec_count[0], FALSE, FALSE);
-	geometryBytes += genDrawInfo(group, normspec_mask | extra_mask, sNormSpecFaces[0], normspec_count[0], FALSE, FALSE);
-
-    extra_mask |= LLVertexBuffer::MAP_WEIGHT4;
-    geometryBytes += genDrawInfo(group, simple_mask | extra_mask, sSimpleFaces[1], simple_count[1], FALSE, batch_textures, TRUE);
-    geometryBytes += genDrawInfo(group, fullbright_mask | extra_mask, sFullbrightFaces[1], fullbright_count[1], FALSE, batch_textures, TRUE);
-    geometryBytes += genDrawInfo(group, alpha_mask | extra_mask, sAlphaFaces[1], alpha_count[1], TRUE, batch_textures, TRUE);
-    geometryBytes += genDrawInfo(group, bump_mask | extra_mask, sBumpFaces[1], bump_count[1], FALSE, TRUE);
-    geometryBytes += genDrawInfo(group, norm_mask | extra_mask, sNormFaces[1], norm_count[1], FALSE, TRUE);
-    geometryBytes += genDrawInfo(group, spec_mask | extra_mask, sSpecFaces[1], spec_count[1], FALSE, TRUE);
-    geometryBytes += genDrawInfo(group, normspec_mask | extra_mask, sNormSpecFaces[1], normspec_count[1], FALSE, TRUE);
+    BOOL alpha_sort = TRUE;
+    BOOL rigged = FALSE;
+    for (int i = 0; i < 2; ++i) //two sets, static and rigged)
+    {
+        geometryBytes += genDrawInfo(group, simple_mask | extra_mask, sSimpleFaces[i], simple_count[i], FALSE, batch_textures, rigged);
+        geometryBytes += genDrawInfo(group, fullbright_mask | extra_mask, sFullbrightFaces[i], fullbright_count[i], FALSE, batch_textures, rigged);
+        geometryBytes += genDrawInfo(group, alpha_mask | extra_mask, sAlphaFaces[i], alpha_count[i], alpha_sort, batch_textures, rigged);
+        geometryBytes += genDrawInfo(group, bump_mask | extra_mask, sBumpFaces[i], bump_count[i], FALSE, FALSE, rigged);
+        geometryBytes += genDrawInfo(group, norm_mask | extra_mask, sNormFaces[i], norm_count[i], FALSE, FALSE, rigged);
+        geometryBytes += genDrawInfo(group, spec_mask | extra_mask, sSpecFaces[i], spec_count[i], FALSE, FALSE, rigged);
+        geometryBytes += genDrawInfo(group, normspec_mask | extra_mask, sNormSpecFaces[i], normspec_count[i], FALSE, FALSE, rigged);
+
+        // for rigged set, add weights and disable alpha sorting (rigged items use depth buffer)
+        extra_mask |= LLVertexBuffer::MAP_WEIGHT4;
+        alpha_sort = FALSE;
+        rigged = TRUE;
+    }
 
 	group->mGeometryBytes = geometryBytes;
 
@@ -5972,8 +5981,9 @@ void LLVolumeGeometryManager::rebuildMesh(LLSpatialGroup* group)
 				if (drawablep && !drawablep->isDead() && drawablep->isState(LLDrawable::REBUILD_ALL) && !drawablep->isState(LLDrawable::RIGGED) )
 				{
 					LLVOVolume* vobj = drawablep->getVOVolume();
-                    if (!vobj) continue;
-
+					
+					if (!vobj) continue;
+					
 					if (debugLoggingEnabled("AnimatedObjectsLinkset"))
 					{
 						if (vobj->isAnimatedObject() && vobj->isRiggedMesh())
@@ -5993,7 +6003,7 @@ void LLVolumeGeometryManager::rebuildMesh(LLSpatialGroup* group)
 					}
 
 					LLVolume* volume = vobj->getVolume();
-                    if (!volume) continue;
+					if (!volume) continue;
 					for (S32 i = 0; i < drawablep->getNumFaces(); ++i)
 					{
 						LLFace* face = drawablep->getFace(i);
diff --git a/indra/newview/llvovolume.h b/indra/newview/llvovolume.h
index d158044f5dd389f5e8e7fa8603d5e6b429638d4b..4cb7a5481c6ff856fd55357d75da7419fca9d710 100644
--- a/indra/newview/llvovolume.h
+++ b/indra/newview/llvovolume.h
@@ -413,6 +413,7 @@ class LLVOVolume : public LLViewerObject
 	S32			mLOD;
 	BOOL		mLODChanged;
 	BOOL		mSculptChanged;
+    BOOL		mColorChanged;
 	F32			mSpotLightPriority;
 	LLMatrix4	mRelativeXform;
 	LLMatrix3	mRelativeXformInvTrans;
diff --git a/indra/newview/pipeline.cpp b/indra/newview/pipeline.cpp
index a6fa58f476767c6fed6cfcb5d3820b3aafae3a6a..a3c971153c81c0c49c487d25433bdb3e1a2aad16 100644
--- a/indra/newview/pipeline.cpp
+++ b/indra/newview/pipeline.cpp
@@ -3828,6 +3828,16 @@ void LLPipeline::postSort(LLCamera& camera)
 					sCull->pushAlphaGroup(group);
 				}
 			}
+
+            LLSpatialGroup::draw_map_t::iterator rigged_alpha = group->mDrawMap.find(LLRenderPass::PASS_ALPHA_RIGGED);
+
+            if (rigged_alpha != group->mDrawMap.end())
+            { //store rigged alpha groups for LLDrawPoolAlpha prepass (skip distance update, rigged attachments use depth buffer)
+                if (hasRenderType(LLDrawPool::POOL_ALPHA))
+                {
+                    sCull->pushRiggedAlphaGroup(group);
+                }
+            }
 		}
 	}
 	
@@ -7264,11 +7274,6 @@ void LLPipeline::doResetVertexBuffers(bool forced)
 
 	SUBSYSTEM_CLEANUP(LLVertexBuffer);
 	
-	//delete all name pool caches
-	LLGLNamePool::cleanupPools();
-
-	
-
 	if (LLVertexBuffer::sGLCount > 0)
 	{
 		LL_WARNS() << "VBO wipe failed -- " << LLVertexBuffer::sGLCount << " buffers remaining." << LL_ENDL;
@@ -9332,7 +9337,11 @@ void LLPipeline::generateWaterReflection(LLCamera& camera_in)
                 LLColor3 col = LLEnvironment::instance().getCurrentWater()->getWaterFogColor();
                 glClearColor(col.mV[0], col.mV[1], col.mV[2], 0.f);
 
-                LLViewerCamera::sCurCameraID = LLViewerCamera::CAMERA_WATER1;
+                // HACK FIX -- pretend underwater camera is the world camera to fix weird visibility artifacts
+                // during distortion render (doesn't break main render because the camera is the same perspective
+                // as world camera and occlusion culling is disabled for this pass)
+                //LLViewerCamera::sCurCameraID = LLViewerCamera::CAMERA_WATER1;
+                LLViewerCamera::sCurCameraID = LLViewerCamera::CAMERA_WORLD;
 
                 mWaterDis.bindTarget();
                 mWaterDis.getViewport(gGLViewport);
@@ -11125,6 +11134,167 @@ void LLPipeline::generateImpostor(LLVOAvatar* avatar)
 	LLGLState::checkTextureChannels();
 }
 
+static LLTrace::BlockTimerStatHandle FTM_PREVIEW_AVATAR("Preview Avatar");
+
+void LLPipeline::previewAvatar(LLVOAvatar* avatar)
+{
+    LL_RECORD_BLOCK_TIME(FTM_PREVIEW_AVATAR);
+
+    LLGLDepthTest gls_depth(GL_TRUE, GL_TRUE);
+    gGL.flush();
+    gGL.setSceneBlendType(LLRender::BT_REPLACE);
+
+    LLGLState::checkStates();
+    LLGLState::checkTextureChannels();
+
+    static LLCullResult result;
+    result.clear();
+    grabReferences(result);
+
+    if (!avatar || !avatar->mDrawable)
+    {
+        LL_WARNS_ONCE("AvatarRenderPipeline") << "Avatar is " << (avatar ? "not drawable" : "null") << LL_ENDL;
+        return;
+    }
+    LL_DEBUGS_ONCE("AvatarRenderPipeline") << "Avatar " << avatar->getID() << " is drawable" << LL_ENDL;
+
+    assertInitialized();
+
+    pushRenderTypeMask();
+
+    {
+        //hide world geometry
+        clearRenderTypeMask(
+            RENDER_TYPE_SKY,
+            RENDER_TYPE_WL_SKY,
+            RENDER_TYPE_GROUND,
+            RENDER_TYPE_TERRAIN,
+            RENDER_TYPE_GRASS,
+            RENDER_TYPE_CONTROL_AV, // Animesh
+            RENDER_TYPE_TREE,
+            RENDER_TYPE_VOIDWATER,
+            RENDER_TYPE_WATER,
+            RENDER_TYPE_PASS_GRASS,
+            RENDER_TYPE_HUD,
+            RENDER_TYPE_PARTICLES,
+            RENDER_TYPE_CLOUDS,
+            RENDER_TYPE_HUD_PARTICLES,
+            END_RENDER_TYPES
+        );
+    }
+
+    S32 occlusion = sUseOcclusion;
+    sUseOcclusion = 0;
+
+    sReflectionRender = !sRenderDeferred;
+
+    sShadowRender = true;
+    sImpostorRender = true; // Likely not needed for previews
+
+    LLViewerCamera* viewer_camera = LLViewerCamera::getInstance();
+
+    {
+        markVisible(avatar->mDrawable, *viewer_camera);
+
+        LLVOAvatar::attachment_map_t::iterator iter;
+        for (iter = avatar->mAttachmentPoints.begin();
+            iter != avatar->mAttachmentPoints.end();
+            ++iter)
+        {
+            LLViewerJointAttachment *attachment = iter->second;
+            for (LLViewerJointAttachment::attachedobjs_vec_t::iterator attachment_iter = attachment->mAttachedObjects.begin();
+                attachment_iter != attachment->mAttachedObjects.end();
+                ++attachment_iter)
+            {
+                if (LLViewerObject* attached_object = attachment_iter->get())
+                {
+                    markVisible(attached_object->mDrawable->getSpatialBridge(), *viewer_camera);
+                }
+            }
+        }
+    }
+
+    stateSort(*LLViewerCamera::getInstance(), result);
+
+    LLCamera camera = *viewer_camera;
+
+    F32 old_alpha = LLDrawPoolAvatar::sMinimumAlpha;
+
+    if (LLPipeline::sRenderDeferred)
+    {
+        renderGeomDeferred(camera);
+
+        renderGeomPostDeferred(camera);
+    }
+    else
+    {
+        renderGeom(camera);
+    }
+
+    LLDrawPoolAvatar::sMinimumAlpha = old_alpha;
+
+    { //create alpha mask based on depth buffer
+        if (LLPipeline::sRenderDeferred)
+        {
+            GLuint buff = GL_COLOR_ATTACHMENT0;
+            LL_PROFILER_GPU_ZONEC("gl.DrawBuffersARB", 0x8000FF);
+            glDrawBuffersARB(1, &buff);
+        }
+
+        LLGLDisable blend(GL_BLEND);
+
+        gGL.setColorMask(false, true);
+
+        gGL.getTexUnit(0)->unbind(LLTexUnit::TT_TEXTURE);
+
+        LLGLDepthTest depth(GL_TRUE, GL_FALSE, GL_GREATER);
+
+        gGL.flush();
+
+        gGL.pushMatrix();
+        gGL.loadIdentity();
+        gGL.matrixMode(LLRender::MM_PROJECTION);
+        gGL.pushMatrix();
+        gGL.loadIdentity();
+
+        static const F32 clip_plane = 0.99999f;
+
+        gDebugProgram.bind();
+
+        gGL.begin(LLRender::QUADS);
+        gGL.vertex3f(-1, -1, clip_plane);
+        gGL.vertex3f(1, -1, clip_plane);
+        gGL.vertex3f(1, 1, clip_plane);
+        gGL.vertex3f(-1, 1, clip_plane);
+        gGL.end();
+        gGL.flush();
+
+        gDebugProgram.unbind();
+
+        gGL.popMatrix();
+        gGL.matrixMode(LLRender::MM_MODELVIEW);
+        gGL.popMatrix();
+    }
+
+    sUseOcclusion = occlusion;
+    sReflectionRender = false;
+    sImpostorRender = false;
+    sShadowRender = false;
+    popRenderTypeMask();
+
+    gGL.matrixMode(LLRender::MM_PROJECTION);
+    gGL.popMatrix();
+    gGL.matrixMode(LLRender::MM_MODELVIEW);
+    gGL.popMatrix();
+
+    LLVertexBuffer::unbind();
+    LLGLState::checkStates();
+    LLGLState::checkTextureChannels();
+
+    gGL.setSceneBlendType(LLRender::BT_ALPHA);
+    gGL.flush();
+}
+
 bool LLPipeline::hasRenderBatches(const U32 type) const
 {
 	return sCull->getRenderMapSize(type) > 0;
@@ -11150,6 +11320,16 @@ LLCullResult::sg_iterator LLPipeline::endAlphaGroups()
 	return sCull->endAlphaGroups();
 }
 
+LLCullResult::sg_iterator LLPipeline::beginRiggedAlphaGroups()
+{
+    return sCull->beginRiggedAlphaGroups();
+}
+
+LLCullResult::sg_iterator LLPipeline::endRiggedAlphaGroups()
+{
+    return sCull->endRiggedAlphaGroups();
+}
+
 bool LLPipeline::hasRenderType(const U32 type) const
 {
     // STORM-365 : LLViewerJointAttachment::setAttachmentVisibility() is setting type to 0 to actually mean "do not render"
diff --git a/indra/newview/pipeline.h b/indra/newview/pipeline.h
index b86b68b09dcee47ae7c3a9f7b9f65b65b3b70c1c..4946bac7013c392e832422e26228a506b878de91 100644
--- a/indra/newview/pipeline.h
+++ b/indra/newview/pipeline.h
@@ -135,6 +135,7 @@ class LLPipeline
 	
 	void resetVertexBuffers(LLDrawable* drawable);
 	void generateImpostor(LLVOAvatar* avatar);
+    void previewAvatar(LLVOAvatar* avatar);
 	void bindScreenToTexture();
 	void renderFinalize();
 
@@ -338,6 +339,8 @@ class LLPipeline
 	LLCullResult::drawinfo_iterator endRenderMap(U32 type);
 	LLCullResult::sg_iterator beginAlphaGroups();
 	LLCullResult::sg_iterator endAlphaGroups();
+    LLCullResult::sg_iterator beginRiggedAlphaGroups();
+    LLCullResult::sg_iterator endRiggedAlphaGroups();
 	
 
 	void addTrianglesDrawn(S32 index_count, U32 render_type = LLRender::TRIANGLES);
diff --git a/indra/newview/skins/default/xui/en/notifications.xml b/indra/newview/skins/default/xui/en/notifications.xml
index 7be0e0ad8547548ef5ff35f23a5a6e8847e8f630..98f74d321be7d20be406780781187c47896bdcca 100644
--- a/indra/newview/skins/default/xui/en/notifications.xml
+++ b/indra/newview/skins/default/xui/en/notifications.xml
@@ -8821,13 +8821,6 @@ See SecondLife.log for details
     type="alert">
     Error while requesting mesh upload permissons.
   </notification>
-
-  <notification
-    name="MeshUploadProfilerError"
-    icon="alert.tga"
-    type="alert">
-Mesh uploader is incompatible with RenderGLCoreProfile, please turn RenderGLCoreProfile off in debug settings and restart the viewer.
-  </notification>
   
   <notification
     name="RegionCapabilityRequestError"