From f47730b92c309092d0a5a95e2d49d7ad53230a97 Mon Sep 17 00:00:00 2001
From: Dave Parks <davep@lindenlab.com>
Date: Mon, 14 Feb 2022 18:07:24 +0000
Subject: [PATCH] SL-16418 Media texture update stall fix.  Make media texture
 updates use LLImageGL thread to update, fix AMD sync issue on ImageGL thread
 and install debug callbacks on LLImageGL thread when debug gl enabled.

---
 indra/llimage/llimage.cpp        |   6 +
 indra/llimage/llimage.h          |   6 +
 indra/llmath/llvolume.cpp        |   2 +-
 indra/llrender/llgl.cpp          |  11 -
 indra/llrender/llgltexture.cpp   |  12 +-
 indra/llrender/llgltexture.h     |  14 +-
 indra/llrender/llimagegl.cpp     | 139 +++++++----
 indra/llrender/llimagegl.h       |  14 +-
 indra/llrender/llrender.cpp      |  20 ++
 indra/newview/llviewermedia.cpp  | 393 ++++++++++++++++++-------------
 indra/newview/llviewermedia.h    |  13 +-
 indra/newview/llviewerwindow.cpp |   6 +-
 12 files changed, 398 insertions(+), 238 deletions(-)

diff --git a/indra/llimage/llimage.cpp b/indra/llimage/llimage.cpp
index 15b07e53189..5fa19ce9c6f 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 354926ee581..7a588cfb037 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 e9ccde5fae7..d91a9731539 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 922fcb867b5..50a5972d28f 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 b6a02f1c0a0..c7de8bc3381 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 028457c5101..216e7f640de 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 1e9b9f642e0..694c4a7b06d 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 d6f4b13a517..6c6e327e42b 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 92d8e6193f0..a03a27cf949 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 63f57e81cc1..bd60b9f1e28 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 71cec5125d9..806692929a3 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 b9a5e90df0b..80850265bc9 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;
-- 
GitLab