diff --git a/indra/llprimitive/llgltfloader.cpp b/indra/llprimitive/llgltfloader.cpp
index fd304f7bc964268166b546e765a141b2f2b79c90..7394f99794e4ab1d2134f94985a68a6e23e865bb 100644
--- a/indra/llprimitive/llgltfloader.cpp
+++ b/indra/llprimitive/llgltfloader.cpp
@@ -106,10 +106,12 @@ bool LLGLTFLoader::OpenFile(const std::string &filename)
     tinygltf::TinyGLTF loader;
     std::string        error_msg;
     std::string        warn_msg;
+    std::string filename_lc(filename);
+    LLStringUtil::toLower(filename_lc);
 
     // Load a tinygltf model fom a file. Assumes that the input filename has already been
     // been sanitized to one of (.gltf , .glb) extensions, so does a simple find to distinguish.
-    if (std::string::npos == filename.rfind(".gltf"))
+    if (std::string::npos == filename_lc.rfind(".gltf"))
     {  // file is binary
         mGltfLoaded = loader.LoadBinaryFromFile(&mGltfModel, &error_msg, &warn_msg, filename);
     }
diff --git a/indra/llrender/llimagegl.cpp b/indra/llrender/llimagegl.cpp
index 50d4532fa7f73df88870deb5b3a62e32cfd004e1..b5c36ea35e385d86fca8d88e608ae283350ee2c9 100644
--- a/indra/llrender/llimagegl.cpp
+++ b/indra/llrender/llimagegl.cpp
@@ -1409,52 +1409,59 @@ void LLImageGL::setManualImage(U32 target, S32 miplevel, S32 intformat, S32 widt
 
         free_cur_tex_image();
 #if LL_DARWIN
-        {
-            LL_PROFILE_ZONE_NAMED("glTexImage2D alloc");
-            glTexImage2D(target, miplevel, intformat, width, height, 0, pixformat, pixtype, use_scratch ? scratch : pixels);
-        }
+        const bool use_sub_image = false;
 #else
-        // break up calls to a manageable size for the GL command buffer
+        // glTexSubImage2D doesn't work with compressed textures on select tested Nvidia GPUs on Windows 10 -Cosmic,2023-03-08
+        const bool use_sub_image = !allow_compression;
+#endif
+        if (!use_sub_image)
         {
             LL_PROFILE_ZONE_NAMED("glTexImage2D alloc");
-            glTexImage2D(target, miplevel, intformat, width, height, 0, pixformat, pixtype, nullptr);
+            glTexImage2D(target, miplevel, intformat, width, height, 0, pixformat, pixtype, use_scratch ? scratch : pixels);
         }
-
-        U8* src = (U8*)(use_scratch ? scratch : pixels);
-        if (src)
+        else
         {
-            LL_PROFILE_ZONE_NAMED("glTexImage2D copy");
-            U32 components = dataFormatComponents(pixformat);
-            U32 type_width = 0;
-
-            switch (pixtype)
+            // break up calls to a manageable size for the GL command buffer
             {
-            case GL_UNSIGNED_BYTE:
-            case GL_BYTE:
-            case GL_UNSIGNED_INT_8_8_8_8_REV:
-                type_width = 1;
-                break;
-            case GL_UNSIGNED_SHORT:
-            case GL_SHORT:
-                type_width = 2;
-                break;
-            case GL_UNSIGNED_INT:
-            case GL_INT:
-            case GL_FLOAT:
-                type_width = 4;
-                break;
-            default:
-                LL_ERRS() << "Unknown type: " << pixtype << LL_ENDL;
+                LL_PROFILE_ZONE_NAMED("glTexImage2D alloc");
+                glTexImage2D(target, miplevel, intformat, width, height, 0, pixformat, pixtype, nullptr);
             }
 
-            U32 line_width = width * components * type_width;
-            for (U32 y = 0; y < height; ++y)
+            U8* src = (U8*)(use_scratch ? scratch : pixels);
+            if (src)
             {
-                glTexSubImage2D(target, miplevel, 0, y, width, 1, pixformat, pixtype, src);
-                src += line_width;
+                LL_PROFILE_ZONE_NAMED("glTexImage2D copy");
+                U32 components = dataFormatComponents(pixformat);
+                U32 type_width = 0;
+
+                switch (pixtype)
+                {
+                case GL_UNSIGNED_BYTE:
+                case GL_BYTE:
+                case GL_UNSIGNED_INT_8_8_8_8_REV:
+                    type_width = 1;
+                    break;
+                case GL_UNSIGNED_SHORT:
+                case GL_SHORT:
+                    type_width = 2;
+                    break;
+                case GL_UNSIGNED_INT:
+                case GL_INT:
+                case GL_FLOAT:
+                    type_width = 4;
+                    break;
+                default:
+                    LL_ERRS() << "Unknown type: " << pixtype << LL_ENDL;
+                }
+
+                U32 line_width = width * components * type_width;
+                for (U32 y = 0; y < height; ++y)
+                {
+                    glTexSubImage2D(target, miplevel, 0, y, width, 1, pixformat, pixtype, src);
+                    src += line_width;
+                }
             }
         }
-#endif
         alloc_tex_image(width, height, pixformat);
     }
     stop_glerror();
diff --git a/indra/llrender/llimagegl.h b/indra/llrender/llimagegl.h
index bbd024abd9d131dfb18fa52931e6fbbe5e48086d..a03233323bf4358249d8734659c0717fd2d31c03 100644
--- a/indra/llrender/llimagegl.h
+++ b/indra/llrender/llimagegl.h
@@ -118,6 +118,9 @@ class LLImageGL : public LLRefCount
 	BOOL createGLTexture(S32 discard_level, const U8* data, BOOL data_hasmips = FALSE, S32 usename = 0, bool defer_copy = false, LLGLuint* tex_name = nullptr);
 	void setImage(const LLImageRaw* imageraw);
 	BOOL setImage(const U8* data_in, BOOL data_hasmips = FALSE, S32 usename = 0);
+    // *NOTE: force_fast_update should only be used if the texture is not
+    // compressed (i.e. RenderCompressTextures is 0). Partial image updates
+    // (glTexSubImage2D) do not work on compressed textures.
 	BOOL setSubImage(const LLImageRaw* imageraw, S32 x_pos, S32 y_pos, S32 width, S32 height, BOOL force_fast_update = FALSE, LLGLuint use_name = 0);
 	BOOL setSubImage(const U8* datap, S32 data_width, S32 data_height, S32 x_pos, S32 y_pos, S32 width, S32 height, BOOL force_fast_update = FALSE, LLGLuint use_name = 0);
 	BOOL setSubImageFromFrameBuffer(S32 fb_x, S32 fb_y, S32 x_pos, S32 y_pos, S32 width, S32 height);
diff --git a/indra/newview/app_settings/shaders/class1/deferred/materialV.glsl b/indra/newview/app_settings/shaders/class1/deferred/materialV.glsl
index a1cab87092a1b1508a3f961eda2933ed1b893d82..d41e0b202b89eae46aa0e71da9c37c5c8dd4b963 100644
--- a/indra/newview/app_settings/shaders/class1/deferred/materialV.glsl
+++ b/indra/newview/app_settings/shaders/class1/deferred/materialV.glsl
@@ -59,9 +59,9 @@ ATTRIBUTE vec2 texcoord0;
 ATTRIBUTE vec4 tangent;
 ATTRIBUTE vec2 texcoord1;
 
-VARYING vec3 vary_mat0;
-VARYING vec3 vary_mat1;
-VARYING vec3 vary_mat2;
+out vec3 vary_tangent;
+flat out float vary_sign;
+out vec3 vary_normal;
 
 VARYING vec2 vary_texcoord1;
 #else
@@ -111,24 +111,21 @@ void main()
 	vec3 n = normalize((mat*vec4(normal.xyz+position.xyz,1.0)).xyz-pos.xyz);
 #ifdef HAS_NORMAL_MAP
 	vec3 t = normalize((mat*vec4(tangent.xyz+position.xyz,1.0)).xyz-pos.xyz);
-	vec3 b = cross(n, t)*tangent.w;
-	
-	vary_mat0 = vec3(t.x, b.x, n.x);
-	vary_mat1 = vec3(t.y, b.y, n.y);
-	vary_mat2 = vec3(t.z, b.z, n.z);
+
+    vary_tangent = t;
+    vary_sign = tangent.w;
+    vary_normal = n;
 #else //HAS_NORMAL_MAP
-vary_normal  = n;
+	vary_normal  = n;
 #endif //HAS_NORMAL_MAP
 #else //HAS_SKIN
 	vec3 n = normalize(normal_matrix * normal);
 #ifdef HAS_NORMAL_MAP
 	vec3 t = normalize(normal_matrix * tangent.xyz);
-	vec3 b = cross(n,t)*tangent.w;
-	//vec3 t = cross(b,n) * binormal.w;
-	
-	vary_mat0 = vec3(t.x, b.x, n.x);
-	vary_mat1 = vec3(t.y, b.y, n.y);
-	vary_mat2 = vec3(t.z, b.z, n.z);
+
+    vary_tangent = t;
+    vary_sign = tangent.w;
+    vary_normal = n;
 #else //HAS_NORMAL_MAP
 	vary_normal = n;
 #endif //HAS_NORMAL_MAP
diff --git a/indra/newview/app_settings/shaders/class3/deferred/materialF.glsl b/indra/newview/app_settings/shaders/class3/deferred/materialF.glsl
index add1cb2a377b948c4c718977873db32462378ffd..6e41df34a4fd833c570763167d96c3c749558cec 100644
--- a/indra/newview/app_settings/shaders/class3/deferred/materialF.glsl
+++ b/indra/newview/app_settings/shaders/class3/deferred/materialF.glsl
@@ -210,9 +210,9 @@ uniform float minimum_alpha;
 #endif
 
 #ifdef HAS_NORMAL_MAP
-VARYING vec3 vary_mat0;
-VARYING vec3 vary_mat1;
-VARYING vec3 vary_mat2;
+in vec3 vary_normal;
+in vec3 vary_tangent;
+flat in float vary_sign;
 VARYING vec2 vary_texcoord1;
 #else
 VARYING vec3 vary_normal;
@@ -227,14 +227,17 @@ vec2 encode_normal(vec3 n);
 vec3 getNormal(inout float glossiness)
 {
 #ifdef HAS_NORMAL_MAP
-	vec4 norm = texture2D(bumpMap, vary_texcoord1.xy);
-    glossiness *= norm.a;
-
-	norm.xyz = norm.xyz * 2 - 1;
+	vec4 vNt = texture2D(bumpMap, vary_texcoord1.xy);
+    glossiness *= vNt.a;
+	vNt.xyz = vNt.xyz * 2 - 1;
+    float sign = vary_sign;
+    vec3 vN = vary_normal;
+    vec3 vT = vary_tangent.xyz;
+    
+    vec3 vB = sign * cross(vN, vT);
+    vec3 tnorm = normalize( vNt.x * vT + vNt.y * vB + vNt.z * vN );
 
-	return normalize(vec3(dot(norm.xyz,vary_mat0),
-			  dot(norm.xyz,vary_mat1),
-			  dot(norm.xyz,vary_mat2)));
+	return tnorm;
 #else
 	return normalize(vary_normal);
 #endif
@@ -316,6 +319,7 @@ void main()
     //forward rendering, output lit linear color
     diffcol.rgb = srgb_to_linear(diffcol.rgb);
     spec.rgb = srgb_to_linear(spec.rgb);
+    spec.a = glossiness; // pack glossiness into spec alpha for lighting functions
 
     vec3 pos = vary_position;
 
diff --git a/indra/newview/llmodelpreview.cpp b/indra/newview/llmodelpreview.cpp
index 56a48cb74da8a929dc3b9d9a5f07f25a021bc340..7ae48eef8c9ac37dfcca17527444050ee3eb85e5 100644
--- a/indra/newview/llmodelpreview.cpp
+++ b/indra/newview/llmodelpreview.cpp
@@ -750,7 +750,9 @@ void LLModelPreview::loadModel(std::string filename, S32 lod, bool force_disable
 
     // three possible file extensions, .dae .gltf .glb
     // check for .dae and if not then assume one of the .gl??
-    if (std::string::npos != filename.rfind(".dae"))
+    std::string filename_lc(filename);
+    LLStringUtil::toLower(filename_lc);
+    if (std::string::npos != filename_lc.rfind(".dae"))
     {
         mModelLoader = new LLDAELoader(
             filename,
diff --git a/indra/newview/llvovolume.cpp b/indra/newview/llvovolume.cpp
index a96701003b63fdb3d378398f54af8f3a3ff5b650..49939cbbf0c15f67e1f1b1639bce8bae6f0796c3 100644
--- a/indra/newview/llvovolume.cpp
+++ b/indra/newview/llvovolume.cpp
@@ -670,6 +670,21 @@ void LLVOVolume::animateTextures()
                 }
                 else
                 {
+                    if (!(result & LLViewerTextureAnim::ROTATE))
+                    {
+                        rot = 0.0f;
+                    }
+                    if (!(result & LLViewerTextureAnim::TRANSLATE))
+                    {
+                        off_s = 0.0f;
+                        off_t = 0.0f;
+                    }
+                    if (!(result & LLViewerTextureAnim::SCALE))
+                    {
+                        scale_s = 1.0f;
+                        scale_t = 1.0f;
+                    }
+
                     // For PBR materials, use Blinn-Phong rotation as hint for
                     // translation direction. In a Blinn-Phong material, the
                     // translation direction would be a byproduct the texture