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 e9ccde5fae7f7c81f64388dff6a2dfac01c7576a..d91a97315390d1541bc6323c2c430a5740dcbf6a 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 922fcb867b56c3377f960778ae85b6b3bed9b37e..50a5972d28f870b318a190ce74b66e7fd03648d5 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
diff --git a/indra/llrender/llgltexture.cpp b/indra/llrender/llgltexture.cpp
index b6a02f1c0a0ffa133097e7671f3d55000cfffc78..c7de8bc338140ebe85c7679a572c610473a60173 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)
 {
 	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) ;
 
 	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, bool use_new_name /* = false */)
 {
     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_new_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, bool use_new_name /* = false */)
 {
     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_new_name) ;
 }
 
 void LLGLTexture::setGLTextureCreated (bool initialized)
diff --git a/indra/llrender/llgltexture.h b/indra/llrender/llgltexture.h
index 028457c510174c45cbc61dc19800325ef305a86a..216e7f640dec16055c6c02d202d45edd3d980ecd 100644
--- a/indra/llrender/llgltexture.h
+++ b/indra/llrender/llgltexture.h
@@ -124,13 +124,21 @@ 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
+    BOOL       createGLTexture(S32 discard_level, const LLImageRaw* imageraw, S32 usename = 0, BOOL to_create = TRUE, S32 category = LLGLTexture::OTHER, bool defer_copy = false);
 
 	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, bool use_new_name = false);
+	BOOL       setSubImage(const U8* datap, S32 data_width, S32 data_height, S32 x_pos, S32 y_pos, S32 width, S32 height, bool use_new_name = false);
 	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..694c4a7b06def88d30557e5d05ce876b67756274 100644
--- a/indra/llrender/llimagegl.cpp
+++ b/indra/llrender/llimagegl.cpp
@@ -550,7 +550,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 +674,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 +709,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 +1075,14 @@ 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 */, bool use_new_name /* = false */)
 {
     LL_PROFILE_ZONE_SCOPED_CATEGORY_TEXTURE;
 	if (!width || !height)
 	{
 		return TRUE;
 	}
-	if (mTexName == 0)
+	if (0 == (use_new_name ? mNewTexName : mTexName))
 	{
 		// *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 +1098,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, use_new_name ? mNewTexName : mTexName);
 	}
 	else
 	{
@@ -1144,12 +1150,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, use_new_name ? mNewTexName : mTexName);
 		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 +1171,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 */, bool use_new_name /* = false */)
 {
     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_new_name);
 }
 
 // Copy sub image from frame buffer
@@ -1356,7 +1361,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)
 {
     LL_PROFILE_ZONE_SCOPED_CATEGORY_TEXTURE;
     checkActiveThread();
@@ -1386,6 +1391,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 +1474,24 @@ 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);
 }
 
-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)
+    // Call with void data, vmem is allocated but unitialized
 {
     LL_PROFILE_ZONE_SCOPED_CATEGORY_TEXTURE;
     checkActiveThread();
 
-    llassert(data_in);
+    if (defer_copy)
+    {
+        data_in = nullptr;
+    }
+    else
+    {
+        llassert(data_in);
+    }
+
     stop_glerror();
 
     if (discard_level < 0)
@@ -1486,7 +1501,8 @@ 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 (!defer_copy // <--- hacky way to force creation of mNewTexName from media texture update
+        && mTexName != 0 && discard_level == mCurrentDiscardLevel)
     {
         // This will only be true if the size has not changed
         return setImage(data_in, data_hasmips);
@@ -1534,36 +1550,12 @@ 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 (!on_main_thread())
     {
+        if (!defer_copy)
         {
-            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();
         }
-
-        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 
     {
@@ -1587,6 +1579,55 @@ BOOL LLImageGL::createGLTexture(S32 discard_level, const U8* data_in, BOOL data_
     return TRUE;
 }
 
+void LLImageGL::syncToMainThread()
+{
+    {
+        LL_PROFILE_ZONE_NAMED("cglt - sync");
+        if (gGLManager.mHasSync)
+        {
+            // post a sync to the main thread (will execute before tex name swap lambda below)
+            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");
+            if (mNewTexName != 0)
+            {
+                if (mTexName != 0)
+                {
+                    LLImageGL::deleteTextures(1, &mTexName);
+                }
+                mTexName = mNewTexName;
+                mNewTexName = 0;
+                unref();
+            }
+        });
+}
+
 BOOL LLImageGL::readBackRaw(S32 discard_level, LLImageRaw* imageraw, bool compressed_ok) const
 {
 	llassert_always(sAllowReadBackRaw) ;
@@ -1714,7 +1755,15 @@ void LLImageGL::destroyGLTexture()
 		mCurrentDiscardLevel = -1 ; //invalidate mCurrentDiscardLevel.
 		mTexName = 0;		
 		mGLTextureCreated = FALSE ;
-	}	
+	}
+
+    // clean up any in-flight name change
+    if (0 != mNewTexName)
+    {
+        // Memory is transient, not tracked by sGlobalTextuerMemory
+        LLImageGL::deleteTextures(1, &mNewTexName);
+        mNewTexName = 0;
+    }
 }
 
 //force to invalidate the gl texture, most likely a sculpty texture
diff --git a/indra/llrender/llimagegl.h b/indra/llrender/llimagegl.h
index d6f4b13a517fd59b08970d4d531a99d177e67e25..6c6e327e42b832db975f621c864e94f8ac49877a 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);
+	BOOL createGLTexture(S32 discard_level, const U8* data, BOOL data_hasmips = FALSE, S32 usename = 0, bool defer_copy = false);
 	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, bool use_new_name = 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 use_new_name = false);
 	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();
+
 	// 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();
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/newview/llviewermedia.cpp b/indra/newview/llviewermedia.cpp
index 63f57e81cc14f56afff8712e5d9a672b2b2e6aef..bd60b9f1e28bd51b5a85e72ac47465797de7d8f7 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"
@@ -635,6 +636,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");
@@ -654,6 +656,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.
@@ -1590,6 +1599,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.
 }
 
 //////////////////////////////////////////////////////////////////////////////////////////
@@ -1676,11 +1687,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();
 }
 
 //////////////////////////////////////////////////////////////////////////////////////////
@@ -2087,6 +2100,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
@@ -2799,199 +2813,256 @@ 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
+                {
+                    doMediaTexUpdate(media_tex, data, data_width, data_height, x_pos, y_pos, width, height, true);
+                },
+                [=]() // callback to main thread
+                {
+                    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
+    media_tex->createGLTexture(0, raw, 0, TRUE, LLGLTexture::OTHER, true);
+
+    // 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, sync);
+    media_tex->getGLTexture()->syncToMainThread();
+    
+    // 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/llviewerwindow.cpp b/indra/newview/llviewerwindow.cpp
index b9a5e90df0b393728b378afc3204b9cf08c8e697..80850265bc978a9b59edb08415625c9c50bf2d47 100644
--- a/indra/newview/llviewerwindow.cpp
+++ b/indra/newview/llviewerwindow.cpp
@@ -2023,12 +2023,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);
@@ -2416,7 +2416,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;