diff --git a/indra/llrender/llimagegl.cpp b/indra/llrender/llimagegl.cpp
index 5b0690bc79a13774c006e96b27de2965cf6e8241..3e3a3095e3a52c9567e1c18246696aa20fb671f3 100644
--- a/indra/llrender/llimagegl.cpp
+++ b/indra/llrender/llimagegl.cpp
@@ -42,6 +42,8 @@
 #include "llwindow.h"
 #include "llframetimer.h"
 
+extern LL_COMMON_API bool on_main_thread();
+
 #if !LL_IMAGEGL_THREAD_CHECK
 #define checkActiveThread()
 #endif
@@ -132,7 +134,8 @@ bool LLImageGL::sCompressTextures = false;
 std::set<LLImageGL*> LLImageGL::sImageList;
 
 
-bool LLImageGLThread::sEnabled = false;
+bool LLImageGLThread::sEnabledTextures = false;
+bool LLImageGLThread::sEnabledMedia = false;
 
 //****************************************************************************************************
 //The below for texture auditing use only
@@ -242,14 +245,16 @@ BOOL is_little_endian()
 }
 
 //static 
-void LLImageGL::initClass(LLWindow* window, S32 num_catagories, BOOL skip_analyze_alpha /* = false */, bool multi_threaded /* = false */)
+void LLImageGL::initClass(LLWindow* window, S32 num_catagories, BOOL skip_analyze_alpha /* = false */, bool thread_texture_loads /* = false */, bool thread_media_updates /* = false */)
 {
     LL_PROFILE_ZONE_SCOPED_CATEGORY_TEXTURE;
 	sSkipAnalyzeAlpha = skip_analyze_alpha;
 
-    if (multi_threaded)
+    if (thread_texture_loads || thread_media_updates)
     {
         LLImageGLThread::createInstance(window);
+        LLImageGLThread::sEnabledTextures = thread_texture_loads;
+        LLImageGLThread::sEnabledMedia = thread_media_updates;
     }
 }
 
@@ -260,6 +265,7 @@ void LLImageGL::cleanupClass()
     LLImageGLThread::deleteSingleton();
 }
 
+
 //static
 S32 LLImageGL::dataFormatBits(S32 dataformat)
 {
@@ -1125,9 +1131,20 @@ U32 type_width_from_pixtype(U32 pixtype)
     return type_width;
 }
 
+bool should_stagger_image_set(bool compressed)
+{
+#if LL_DARWIN
+    return false;
+#else
+    // glTexSubImage2D doesn't work with compressed textures on select tested Nvidia GPUs on Windows 10 -Cosmic,2023-03-08
+    // Setting media textures off-thread seems faster when not using sub_image_lines (Nvidia/Windows 10) -Cosmic,2023-03-31
+    return !compressed && on_main_thread();
+#endif
+}
+
 // Equivalent to calling glSetSubImage2D(target, miplevel, x_offset, y_offset, width, height, pixformat, pixtype, src), assuming the total width of the image is data_width
 // However, instead there are multiple calls to glSetSubImage2D on smaller slices of the image
-void subImageLines(U32 target, S32 miplevel, S32 x_offset, S32 y_offset, S32 width, S32 height, U32 pixformat, U32 pixtype, const U8* src, S32 data_width)
+void sub_image_lines(U32 target, S32 miplevel, S32 x_offset, S32 y_offset, S32 width, S32 height, U32 pixformat, U32 pixtype, const U8* src, S32 data_width)
 {
     LL_PROFILE_ZONE_SCOPED_CATEGORY_TEXTURE;
 
@@ -1223,11 +1240,7 @@ BOOL LLImageGL::setSubImage(const U8* datap, S32 data_width, S32 data_height, S3
 		if (!res) LL_ERRS() << "LLImageGL::setSubImage(): bindTexture failed" << LL_ENDL;
 		stop_glerror();
 
-#if LL_DARWIN
-        const bool use_sub_image = false;
-#else
-        const bool use_sub_image = !isCompressed();
-#endif
+        const bool use_sub_image = should_stagger_image_set(isCompressed());
         if (!use_sub_image)
         {
             // *TODO: Why does this work here, in setSubImage, but not in
@@ -1238,7 +1251,7 @@ BOOL LLImageGL::setSubImage(const U8* datap, S32 data_width, S32 data_height, S3
         }
         else
         {
-            subImageLines(mTarget, 0, x_pos, y_pos, width, height, mFormatPrimary, mFormatType, sub_datap, data_width);
+            sub_image_lines(mTarget, 0, x_pos, y_pos, width, height, mFormatPrimary, mFormatType, sub_datap, data_width);
         }
 		gGL.getTexUnit(0)->disable();
 		stop_glerror();
@@ -1455,13 +1468,7 @@ void LLImageGL::setManualImage(U32 target, S32 miplevel, S32 intformat, S32 widt
         LL_PROFILE_ZONE_NUM(height);
 
         free_cur_tex_image();
-#if LL_DARWIN
-        const bool use_sub_image = false;
-#else
-        // glTexSubImage2D doesn't work with compressed textures on select tested Nvidia GPUs on Windows 10 -Cosmic,2023-03-08
-        // *TODO: Small chance that glCompressedTexImage2D/glCompressedTexSubImage2D may work better here
-        const bool use_sub_image = !compress;
-#endif
+        const bool use_sub_image = should_stagger_image_set(compress);
         if (!use_sub_image)
         {
             LL_PROFILE_ZONE_NAMED("glTexImage2D alloc + copy");
@@ -1479,7 +1486,7 @@ void LLImageGL::setManualImage(U32 target, S32 miplevel, S32 intformat, S32 widt
             if (src)
             {
                 LL_PROFILE_ZONE_NAMED("glTexImage2D copy");
-                subImageLines(target, miplevel, 0, 0, width, height, pixformat, pixtype, src, width);
+                sub_image_lines(target, miplevel, 0, 0, width, height, pixformat, pixtype, src, width);
             }
         }
         alloc_tex_image(width, height, pixformat);
@@ -2504,7 +2511,6 @@ LLImageGLThread::LLImageGLThread(LLWindow* window)
     , mWindow(window)
 {
     LL_PROFILE_ZONE_SCOPED_CATEGORY_TEXTURE;
-    sEnabled = true;
     mFinished = false;
 
     mContext = mWindow->createSharedContext();
diff --git a/indra/llrender/llimagegl.h b/indra/llrender/llimagegl.h
index 42f6efef779bb1c6a51b01824be5b8d082038845..87fb9e363e6f739d9c504d48cae409d632aa205c 100644
--- a/indra/llrender/llimagegl.h
+++ b/indra/llrender/llimagegl.h
@@ -287,7 +287,7 @@ class LLImageGL : public LLRefCount
 #endif
 
 public:
-	static void initClass(LLWindow* window, S32 num_catagories, BOOL skip_analyze_alpha = false, bool multi_threaded = false); 
+	static void initClass(LLWindow* window, S32 num_catagories, BOOL skip_analyze_alpha = false, bool thread_texture_loads = false, bool thread_media_updates = false);
 	static void cleanupClass() ;
 
 private:
@@ -329,8 +329,10 @@ class LLImageGL : public LLRefCount
 class LLImageGLThread : public LLSimpleton<LLImageGLThread>, LL::ThreadPool
 {
 public:
-    // follows gSavedSettings "RenderGLMultiThreaded"
-    static bool sEnabled;
+    // follows gSavedSettings "RenderGLMultiThreadedTextures"
+    static bool sEnabledTextures;
+    // follows gSavedSettings "RenderGLMultiThreadedMedia"
+    static bool sEnabledMedia;
     
     LLImageGLThread(LLWindow* window);
 
@@ -349,5 +351,4 @@ class LLImageGLThread : public LLSimpleton<LLImageGLThread>, LL::ThreadPool
     LLAtomicBool mFinished;
 };
 
-
 #endif // LL_LLIMAGEGL_H
diff --git a/indra/newview/app_settings/settings.xml b/indra/newview/app_settings/settings.xml
index 4490e3eec291e670bcfa48ade200b6e7d987659f..9ae33a85b42fc9c3062bfcca8bf46002f52e4b87 100644
--- a/indra/newview/app_settings/settings.xml
+++ b/indra/newview/app_settings/settings.xml
@@ -9909,10 +9909,21 @@
       <key>Value</key>
       <integer>0</integer>
     </map>
-    <key>RenderGLMultiThreaded</key>
+    <key>RenderGLMultiThreadedTextures</key>
     <map>
       <key>Comment</key>
-      <string>Allow OpenGL to use multiple render contexts (reduces frame stutters from loading textures, doesn't play nice with Intel drivers).</string>
+      <string>Allow OpenGL to use multiple render contexts for loading textures (may reduce frame stutters, doesn't play nice with Intel drivers).</string>
+      <key>Persist</key>
+      <integer>1</integer>
+      <key>Type</key>
+      <string>Boolean</string>
+      <key>Value</key>
+      <integer>0</integer>
+    </map>
+    <key>RenderGLMultiThreadedMedia</key>
+    <map>
+      <key>Comment</key>
+      <string>Allow OpenGL to use multiple render contexts for playing media (may reduce frame stutters, doesn't play nice with Intel drivers)</string>
       <key>Persist</key>
       <integer>1</integer>
       <key>Type</key>
diff --git a/indra/newview/featuretable.txt b/indra/newview/featuretable.txt
index 3d634c74842e28e4b6e15c22065da5caaabb8f0d..99007d52c285030e377d4015a0b6fe0a7bd0479e 100644
--- a/indra/newview/featuretable.txt
+++ b/indra/newview/featuretable.txt
@@ -1,4 +1,4 @@
-version 53
+version 54
 // The version number above should be incremented IF AND ONLY IF some
 // change has been made that is sufficiently important to justify
 // resetting the graphics preferences of all users to the recommended
@@ -70,7 +70,8 @@ RenderUseStreamVBO			1	1
 RenderFSAASamples			1	16
 RenderMaxTextureIndex		1	16
 RenderGLContextCoreProfile         1   1
-RenderGLMultiThreaded       1   0
+RenderGLMultiThreadedTextures      1   0
+RenderGLMultiThreadedMedia         1   1
 RenderReflectionProbeResolution 1 128
 RenderScreenSpaceReflections 1  1
 
@@ -311,9 +312,10 @@ list Intel
 RenderAnisotropic			1	0
 RenderFSAASamples			1	0
 RenderGLContextCoreProfile  1   0
+RenderGLMultiThreadedMedia  1   0
 
 list AMD
-RenderGLMultiThreaded       1   1
+RenderGLMultiThreadedTextures       1   1
 
 list GL3
 RenderFSAASamples           0   0
diff --git a/indra/newview/featuretable_mac.txt b/indra/newview/featuretable_mac.txt
index eed790ddac81af3b5a1fdd29a92d636edeba7fc2..24023901d9a4180e31549259c91b3487cc8f9263 100644
--- a/indra/newview/featuretable_mac.txt
+++ b/indra/newview/featuretable_mac.txt
@@ -1,4 +1,4 @@
-version 48
+version 49
 // The version number above should be incremented IF AND ONLY IF some
 // change has been made that is sufficiently important to justify
 // resetting the graphics preferences of all users to the recommended
@@ -67,7 +67,8 @@ RenderUseStreamVBO			1	1
 RenderFSAASamples			1	16
 RenderMaxTextureIndex		1	16
 RenderGLContextCoreProfile         1   1
-RenderGLMultiThreaded       1   0
+RenderGLMultiThreadedTextures      1   0
+RenderGLMultiThreadedMedia         1   0
 RenderReflectionsEnabled    1   1
 RenderReflectionProbeDetail	1	2
 RenderScreenSpaceReflections 1  1
diff --git a/indra/newview/lldrawpoolbump.cpp b/indra/newview/lldrawpoolbump.cpp
index 763943f9ccef268cb829304e2aecdd196d588358..45c74776e1928e8a9f209ee2706ab5bc57864f31 100644
--- a/indra/newview/lldrawpoolbump.cpp
+++ b/indra/newview/lldrawpoolbump.cpp
@@ -1042,7 +1042,7 @@ void LLBumpImageList::onSourceLoaded( BOOL success, LLViewerTexture *src_vi, LLI
 				bump->setExplicitFormat(GL_ALPHA8, GL_ALPHA);
 
 #if LL_BUMPLIST_MULTITHREADED
-                auto tex_queue = LLImageGLThread::sEnabled ? sTexUpdateQueue.lock() : nullptr;
+                auto tex_queue = LLImageGLThread::sEnabledTextures ? sTexUpdateQueue.lock() : nullptr;
 
                 if (tex_queue)
                 { //dispatch creation to background thread
@@ -1154,7 +1154,7 @@ void LLBumpImageList::onSourceLoaded( BOOL success, LLViewerTexture *src_vi, LLI
                 };
 
 #if LL_BUMPLIST_MULTITHREADED
-                auto main_queue = LLImageGLThread::sEnabled ? sMainQueue.lock() : nullptr;
+                auto main_queue = LLImageGLThread::sEnabledTextures ? sMainQueue.lock() : nullptr;
 
                 if (main_queue)
                 { //dispatch texture upload to background thread, issue GPU commands to generate normal map on main thread
diff --git a/indra/newview/llstartup.cpp b/indra/newview/llstartup.cpp
index 0dc47461231b7057bc9b9390f9fe86b0c5a8d754..6bcee31b72d5842da482ce3508dff85aba4d7032 100644
--- a/indra/newview/llstartup.cpp
+++ b/indra/newview/llstartup.cpp
@@ -312,7 +312,7 @@ void update_texture_fetch()
 	LLAppViewer::getTextureFetch()->update(1); // unpauses the texture fetch thread
 	gTextureList.updateImages(0.10f);
 
-    if (LLImageGLThread::sEnabled)
+    if (LLImageGLThread::sEnabledTextures)
     {
         std::shared_ptr<LL::WorkQueue> main_queue = LL::WorkQueue::getInstance("mainloop");
         main_queue->runFor(std::chrono::milliseconds(1));
diff --git a/indra/newview/llviewermedia.cpp b/indra/newview/llviewermedia.cpp
index aae3bd7f259717cea52d2216acbdd85f8c0788ee..c8e279c9913a3b4f10f777f1cbd620b36c865d9b 100644
--- a/indra/newview/llviewermedia.cpp
+++ b/indra/newview/llviewermedia.cpp
@@ -2878,7 +2878,7 @@ void LLViewerMediaImpl::update()
     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;
+        auto main_queue = LLImageGLThread::sEnabledMedia ? mMainQueue.lock() : nullptr;
         if (main_queue)
         {
             mTextureUpdatePending = true;
@@ -2967,11 +2967,12 @@ void LLViewerMediaImpl::doMediaTexUpdate(LLViewerMediaTexture* media_tex, U8* da
 
     // 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);
-        
+
     // *NOTE: Recreating the GL texture each media update may seem wasteful
     // (note the texture creation in preMediaTexUpdate), however, it apparently
     // prevents GL calls from blocking, due to poor bookkeeping of state of
-    // updated textures by the OpenGL implementation.
+    // updated textures by the OpenGL implementation. (Windows 10/Nvidia)
+    // -Cosmic,2023-04-04
     // 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);
diff --git a/indra/newview/llviewertexture.cpp b/indra/newview/llviewertexture.cpp
index ca8cdbaea9f72cc129f4c0de368960bd0c0349d0..29c8700105959e153e562fb40781bdb54cd0a0f5 100644
--- a/indra/newview/llviewertexture.cpp
+++ b/indra/newview/llviewertexture.cpp
@@ -1578,7 +1578,7 @@ void LLViewerFetchedTexture::scheduleCreateTexture()
             }
 #endif
             mNeedsCreateTexture = true;
-            auto mainq = LLImageGLThread::sEnabled ? mMainQueue.lock() : nullptr;
+            auto mainq = LLImageGLThread::sEnabledTextures ? mMainQueue.lock() : nullptr;
             if (mainq)
             {
                 ref();
diff --git a/indra/newview/llviewertexturelist.cpp b/indra/newview/llviewertexturelist.cpp
index 09a1cd51480e6d54e3951f5dd92ec80891d89286..1449844588dcff32119abe53359d12152de28a03 100644
--- a/indra/newview/llviewertexturelist.cpp
+++ b/indra/newview/llviewertexturelist.cpp
@@ -1230,7 +1230,7 @@ void LLViewerTextureList::decodeAllImages(F32 max_time)
 		LLViewerFetchedTexture* imagep = *iter++;
 		imagep->updateFetch();
 	}
-    std::shared_ptr<LL::WorkQueue> main_queue = LLImageGLThread::sEnabled ? LL::WorkQueue::getInstance("mainloop") : NULL;
+    std::shared_ptr<LL::WorkQueue> main_queue = LLImageGLThread::sEnabledTextures ? LL::WorkQueue::getInstance("mainloop") : NULL;
 	// Run threads
 	S32 fetch_pending = 0;
 	while (1)
@@ -1239,7 +1239,7 @@ void LLViewerTextureList::decodeAllImages(F32 max_time)
 		LLAppViewer::instance()->getImageDecodeThread()->update(1); // unpauses the image thread
 		fetch_pending = LLAppViewer::instance()->getTextureFetch()->update(1); // unpauses the texture fetch thread
 
-        if (LLImageGLThread::sEnabled)
+        if (LLImageGLThread::sEnabledTextures)
         {
             main_queue->runFor(std::chrono::milliseconds(1));
             fetch_pending += main_queue->size();
diff --git a/indra/newview/llviewerwindow.cpp b/indra/newview/llviewerwindow.cpp
index ad9e3da379acb824922f6bcd7d67e1ddadf58001..3fee3766a99ba200578a1c49d08191d323a20bbb 100644
--- a/indra/newview/llviewerwindow.cpp
+++ b/indra/newview/llviewerwindow.cpp
@@ -1975,8 +1975,8 @@ 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"));
+	// LLViewerWindow needs are requested, as well as before LLViewerMedia starts updating images.
+    LLImageGL::initClass(mWindow, LLViewerTexture::MAX_GL_IMAGE_CATEGORY, false, gSavedSettings.getBOOL("RenderGLMultiThreadedTextures"), gSavedSettings.getBOOL("RenderGLMultiThreadedMedia"));
 	gTextureList.init();
 	LLViewerTextureManager::init() ;
 	gBumpImageList.init();