diff --git a/indra/llmath/llcamera.h b/indra/llmath/llcamera.h
index c4d04f5d02c339d30014a85c5e0adf4f7322adf8..e1d3536f66201e75ef9334d1400b3f98376ad9b9 100644
--- a/indra/llmath/llcamera.h
+++ b/indra/llmath/llcamera.h
@@ -65,7 +65,6 @@ class LLCamera
 : 	public LLCoordFrame
 {
 public:
-	
 	LLCamera(const LLCamera& rhs)
 	{
 		*this = rhs;
diff --git a/indra/llrender/llvertexbuffer.cpp b/indra/llrender/llvertexbuffer.cpp
index de27636c3311c296e084eda3a6d40d9e77d191f0..4f5f30d7c2fa11113093705f64eee835e37f0f82 100644
--- a/indra/llrender/llvertexbuffer.cpp
+++ b/indra/llrender/llvertexbuffer.cpp
@@ -1319,6 +1319,10 @@ bool LLVertexBuffer::getNormalStrider(LLStrider<LLVector3>& strider, U32 index,
 {
 	return VertexBufferStrider<LLVector3,TYPE_NORMAL>::get(*this, strider, index, count);
 }
+bool LLVertexBuffer::getNormalStrider(LLStrider<LLVector4a>& strider, U32 index, S32 count)
+{
+    return VertexBufferStrider<LLVector4a, TYPE_NORMAL>::get(*this, strider, index, count);
+}
 bool LLVertexBuffer::getTangentStrider(LLStrider<LLVector3>& strider, U32 index, S32 count)
 {
 	return VertexBufferStrider<LLVector3,TYPE_TANGENT>::get(*this, strider, index, count);
diff --git a/indra/llrender/llvertexbuffer.h b/indra/llrender/llvertexbuffer.h
index 197fdbb1899f3b8f14b64b5168262b60a89d71a1..cc59e322ed83de6bdf5a167c87837232d36f0f2c 100644
--- a/indra/llrender/llvertexbuffer.h
+++ b/indra/llrender/llvertexbuffer.h
@@ -180,6 +180,7 @@ class LLVertexBuffer final : public LLRefCount
 	bool getTexCoord1Strider(LLStrider<LLVector2>& strider, U32 index=0, S32 count = -1);
 	bool getTexCoord2Strider(LLStrider<LLVector2>& strider, U32 index=0, S32 count = -1);
 	bool getNormalStrider(LLStrider<LLVector3>& strider, U32 index=0, S32 count = -1);
+	bool getNormalStrider(LLStrider<LLVector4a>& strider, U32 index = 0, S32 count = -1);
 	bool getTangentStrider(LLStrider<LLVector3>& strider, U32 index=0, S32 count = -1);
 	bool getTangentStrider(LLStrider<LLVector4a>& strider, U32 index=0, S32 count = -1);
 	bool getColorStrider(LLStrider<LLColor4U>& strider, U32 index=0, S32 count = -1);
diff --git a/indra/newview/CMakeLists.txt b/indra/newview/CMakeLists.txt
index 7a70d0b6e69aebc9afecea2b45812f220e3f59ba..8268fb3d97799c20237d8850f9de80dcf02d2bc2 100644
--- a/indra/newview/CMakeLists.txt
+++ b/indra/newview/CMakeLists.txt
@@ -310,6 +310,7 @@ set(viewer_SOURCE_FILES
     llgiveinventory.cpp
     llglsandbox.cpp
     llgltfmateriallist.cpp
+    llgltfmaterialpreviewmgr.cpp
     llgroupactions.cpp
     llgroupiconctrl.cpp
     llgrouplist.cpp
@@ -963,6 +964,7 @@ set(viewer_HEADER_FILES
     llgesturemgr.h
     llgiveinventory.h
     llgltfmateriallist.h
+    llgltfmaterialpreviewmgr.h
     llgroupactions.h
     llgroupiconctrl.h
     llgrouplist.h
diff --git a/indra/newview/app_settings/shaders/class1/deferred/postDeferredNoDoFF.glsl b/indra/newview/app_settings/shaders/class1/deferred/postDeferredNoDoFF.glsl
index bace9b8c90c48e1224f0e29d23bc5f9609ac5efc..cd4ae8110af6aa332eff9383bae77ade503dd71f 100644
--- a/indra/newview/app_settings/shaders/class1/deferred/postDeferredNoDoFF.glsl
+++ b/indra/newview/app_settings/shaders/class1/deferred/postDeferredNoDoFF.glsl
@@ -37,7 +37,11 @@ void main()
 {
 	vec4 diff = texture(diffuseRect, vary_fragcoord.xy);
 	
+#if 1
 	frag_color = diff;
+#else
+	frag_color = vec4(1,0,1,1);
+#endif
 
     gl_FragDepth = texture(depthMap, vary_fragcoord.xy).r;
 }
diff --git a/indra/newview/lldynamictexture.cpp b/indra/newview/lldynamictexture.cpp
index 1a817e4fd969be2b42f7ba1fb025c4820acaccbe..a66c3876fc7dc75d2b3c062bdd6022e6f9a95397 100644
--- a/indra/newview/lldynamictexture.cpp
+++ b/indra/newview/lldynamictexture.cpp
@@ -189,13 +189,18 @@ BOOL LLViewerDynamicTexture::updateAllInstances()
 		return TRUE;
 	}
 
-	bool use_fbo = gPipeline.mBake.isComplete() && !gGLManager.mIsAMD;
+    LLRenderTarget& bake_target = gPipeline.mAuxillaryRT.deferredScreen;
 
-	if (use_fbo)
-	{
-		gPipeline.mBake.bindTarget();
-        gPipeline.mBake.clear();
-	}
+	if (!bake_target.isComplete())
+    {
+        llassert(false);
+		return FALSE;
+    }
+    llassert(bake_target.getWidth() >= LLPipeline::MAX_BAKE_WIDTH);
+    llassert(bake_target.getHeight() >= LLPipeline::MAX_BAKE_WIDTH);
+
+    bake_target.bindTarget();
+    bake_target.clear();
 
 	LLGLSLShader::unbind();
 	LLVertexBuffer::unbind();
@@ -210,11 +215,14 @@ BOOL LLViewerDynamicTexture::updateAllInstances()
 			LLViewerDynamicTexture *dynamicTexture = *iter;
 			if (dynamicTexture->needsRender())
 			{				
+                llassert(dynamicTexture->getFullWidth() <= LLPipeline::MAX_BAKE_WIDTH);
+                llassert(dynamicTexture->getFullHeight() <= LLPipeline::MAX_BAKE_WIDTH);
+
 				glClear(GL_DEPTH_BUFFER_BIT);
 				gDepthDirty = TRUE;
 								
 				gGL.color4f(1,1,1,1);
-                dynamicTexture->setBoundTarget(use_fbo ? &gPipeline.mBake : nullptr);
+                dynamicTexture->setBoundTarget(&bake_target);
 				dynamicTexture->preRender();	// Must be called outside of startRender()
 				result = FALSE;
 				if (dynamicTexture->render())
@@ -231,10 +239,7 @@ BOOL LLViewerDynamicTexture::updateAllInstances()
 		}
 	}
 
-	if (use_fbo)
-	{
-		gPipeline.mBake.flush();
-	}
+	bake_target.flush();
 
     gGL.flush();
 
diff --git a/indra/newview/llfetchedgltfmaterial.cpp b/indra/newview/llfetchedgltfmaterial.cpp
index 46b9dffae9431224e5af6c81ca1ada538ef25832..90ec08391d686ce1a63cc6b42208a643c695223d 100644
--- a/indra/newview/llfetchedgltfmaterial.cpp
+++ b/indra/newview/llfetchedgltfmaterial.cpp
@@ -254,55 +254,3 @@ void LLFetchedGLTFMaterial::materialComplete()
     materialCompleteCallbacks.clear();
     materialCompleteCallbacks.shrink_to_fit();
 }
-
-LLPointer<LLViewerFetchedTexture> LLFetchedGLTFMaterial::getUITexture()
-{
-    if (mFetching)
-    {
-        return nullptr;
-    }
-
-    auto fetch_texture_for_ui = [](LLPointer<LLViewerFetchedTexture>& img, const LLUUID& id)
-    {
-        if (id.notNull())
-        {
-            if (LLAvatarAppearanceDefines::LLAvatarAppearanceDictionary::isBakedImageId(id))
-            {
-                LLViewerObject* obj = LLSelectMgr::getInstance()->getSelection()->getFirstObject();
-                if (obj)
-                {
-                    LLViewerTexture* viewerTexture = obj->getBakedTextureForMagicId(id);
-                    img = viewerTexture ? dynamic_cast<LLViewerFetchedTexture*>(viewerTexture) : NULL;
-                }
-
-            }
-            else
-            {
-                img = LLViewerTextureManager::getFetchedTexture(id, FTT_DEFAULT, TRUE, LLGLTexture::BOOST_NONE, LLViewerTexture::LOD_TEXTURE);
-            }
-        }
-        if (img)
-        {
-            img->setBoostLevel(LLGLTexture::BOOST_PREVIEW);
-            img->forceToSaveRawImage(0);
-        }
-    };
-
-    fetch_texture_for_ui(mBaseColorTexture, mTextureId[LLGLTFMaterial::GLTF_TEXTURE_INFO_BASE_COLOR]);
-    fetch_texture_for_ui(mNormalTexture, mTextureId[LLGLTFMaterial::GLTF_TEXTURE_INFO_NORMAL]);
-    fetch_texture_for_ui(mMetallicRoughnessTexture, mTextureId[LLGLTFMaterial::GLTF_TEXTURE_INFO_METALLIC_ROUGHNESS]);
-    fetch_texture_for_ui(mEmissiveTexture, mTextureId[LLGLTFMaterial::GLTF_TEXTURE_INFO_EMISSIVE]);
-
-    if ((mBaseColorTexture && (mBaseColorTexture->getRawImageLevel() != 0)) ||
-        (mNormalTexture && (mNormalTexture->getRawImageLevel() != 0)) ||
-        (mMetallicRoughnessTexture && (mMetallicRoughnessTexture->getRawImageLevel() != 0)) ||
-        (mEmissiveTexture && (mEmissiveTexture->getRawImageLevel() != 0)))
-    {
-        return nullptr;
-    }
-
-    // *HACK: Use one of the PBR texture components as the preview texture for now
-    mPreviewTexture = mBaseColorTexture;
-
-    return mPreviewTexture;
-}
diff --git a/indra/newview/llfetchedgltfmaterial.h b/indra/newview/llfetchedgltfmaterial.h
index a9e539633d1ebc0f657fbe02f296f77417a89825..2559aa46cc37a1e1b0f9575c547c9f708cee9c04 100644
--- a/indra/newview/llfetchedgltfmaterial.h
+++ b/indra/newview/llfetchedgltfmaterial.h
@@ -50,8 +50,6 @@ class LLFetchedGLTFMaterial: public LLGLTFMaterial
 
     bool isFetching() const { return mFetching; }
 
-    LLPointer<LLViewerFetchedTexture> getUITexture();
-
     void addTextureEntry(LLTextureEntry* te) override;
     void removeTextureEntry(LLTextureEntry* te) override;
     virtual bool replaceLocalTexture(const LLUUID& tracking_id, const LLUUID& old_id, const LLUUID& new_id) override;
@@ -65,9 +63,6 @@ class LLFetchedGLTFMaterial: public LLGLTFMaterial
 
     std::set<LLTextureEntry*> mTextureEntires;
 
-    // Texture used for previewing the material in the UI
-    LLPointer<LLViewerFetchedTexture> mPreviewTexture;
-
 protected:
     // Lifetime management
     
diff --git a/indra/newview/llgltfmaterialpreviewmgr.cpp b/indra/newview/llgltfmaterialpreviewmgr.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..9ef1bbf8847894a07e96c7eb7d90fde5f4dbecd1
--- /dev/null
+++ b/indra/newview/llgltfmaterialpreviewmgr.cpp
@@ -0,0 +1,501 @@
+/**
+ * @file llgltfmaterialpreviewmgr.cpp
+ *
+ * $LicenseInfo:firstyear=2023&license=viewerlgpl$
+ * Second Life Viewer Source Code
+ * Copyright (C) 2023, Linden Research, Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation;
+ * version 2.1 of the License only.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
+ *
+ * Linden Research, Inc., 945 Battery Street, San Francisco, CA  94111  USA
+ * $/LicenseInfo$
+ */
+
+#include "llviewerprecompiledheaders.h"
+
+#include "llgltfmaterialpreviewmgr.h"
+
+#include <vector>
+
+#include "llavatarappearancedefines.h"
+#include "llenvironment.h"
+#include "llselectmgr.h"
+#include "llviewercamera.h"
+#include "llviewerobject.h"
+#include "llviewershadermgr.h"
+#include "llviewertexturelist.h"
+#include "llviewerwindow.h"
+#include "llvolumemgr.h"
+#include "pipeline.h"
+
+LLGLTFMaterialPreviewMgr gGLTFMaterialPreviewMgr;
+
+namespace
+{
+    constexpr S32 FULLY_LOADED = 0;
+    constexpr S32 NOT_LOADED = 99;
+};
+
+LLGLTFPreviewTexture::MaterialLoadLevels::MaterialLoadLevels()
+{
+    for (U32 i = 0; i < LLGLTFMaterial::GLTF_TEXTURE_INFO_COUNT; ++i)
+    {
+        levels[i] = NOT_LOADED;
+    }
+}
+
+S32& LLGLTFPreviewTexture::MaterialLoadLevels::operator[](size_t i)
+{
+    llassert(i >= 0 && i < LLGLTFMaterial::GLTF_TEXTURE_INFO_COUNT);
+    return levels[i];
+}
+
+const S32& LLGLTFPreviewTexture::MaterialLoadLevels::operator[](size_t i) const
+{
+    llassert(i >= 0 && i < LLGLTFMaterial::GLTF_TEXTURE_INFO_COUNT);
+    return levels[i];
+}
+
+bool LLGLTFPreviewTexture::MaterialLoadLevels::operator<(const MaterialLoadLevels& other) const
+{
+    bool less = false;
+    for (U32 i = 0; i < LLGLTFMaterial::GLTF_TEXTURE_INFO_COUNT; ++i)
+    {
+        if (((*this)[i] > other[i])) { return false; }
+        less = less || ((*this)[i] < other[i]);
+    }
+    return less;
+}
+
+bool LLGLTFPreviewTexture::MaterialLoadLevels::operator>(const MaterialLoadLevels& other) const
+{
+    bool great = false;
+    for (U32 i = 0; i < LLGLTFMaterial::GLTF_TEXTURE_INFO_COUNT; ++i)
+    {
+        if (((*this)[i] < other[i])) { return false; }
+        great = great || ((*this)[i] > other[i]);
+    }
+    return great;
+}
+
+namespace
+{
+    void fetch_texture_for_ui(LLPointer<LLViewerFetchedTexture>& img, const LLUUID& id)
+    {
+        if (id.notNull())
+        {
+            if (LLAvatarAppearanceDefines::LLAvatarAppearanceDictionary::isBakedImageId(id))
+            {
+                LLViewerObject* obj = LLSelectMgr::getInstance()->getSelection()->getFirstObject();
+                if (obj)
+                {
+                    LLViewerTexture* viewerTexture = obj->getBakedTextureForMagicId(id);
+                    img = viewerTexture ? dynamic_cast<LLViewerFetchedTexture*>(viewerTexture) : NULL;
+                }
+            }
+            else
+            {
+                img = LLViewerTextureManager::getFetchedTexture(id, FTT_DEFAULT, TRUE, LLGLTexture::BOOST_NONE, LLViewerTexture::LOD_TEXTURE);
+            }
+        }
+        if (img)
+        {
+            img->setBoostLevel(LLGLTexture::BOOST_PREVIEW);
+            img->forceToSaveRawImage(0);
+        }
+    };
+
+    // *NOTE: Does not use the same conventions as texture discard level. Lower is better.
+    S32 get_texture_load_level(const LLPointer<LLViewerFetchedTexture>& texture)
+    {
+        if (!texture) { return FULLY_LOADED; }
+        const S32 raw_level = texture->getDiscardLevel();
+        if (raw_level < 0) { return NOT_LOADED; }
+        return raw_level;
+    }
+
+    LLGLTFPreviewTexture::MaterialLoadLevels get_material_load_levels(LLFetchedGLTFMaterial& material)
+    {
+        using MaterialTextures = LLPointer<LLViewerFetchedTexture>*[LLGLTFMaterial::GLTF_TEXTURE_INFO_COUNT];
+
+        MaterialTextures textures;
+
+        textures[LLGLTFMaterial::GLTF_TEXTURE_INFO_BASE_COLOR] = &material.mBaseColorTexture;
+        textures[LLGLTFMaterial::GLTF_TEXTURE_INFO_NORMAL] = &material.mNormalTexture;
+        textures[LLGLTFMaterial::GLTF_TEXTURE_INFO_METALLIC_ROUGHNESS] = &material.mMetallicRoughnessTexture;
+        textures[LLGLTFMaterial::GLTF_TEXTURE_INFO_EMISSIVE] = &material.mEmissiveTexture;
+
+        LLGLTFPreviewTexture::MaterialLoadLevels levels;
+
+        for (U32 i = 0; i < LLGLTFMaterial::GLTF_TEXTURE_INFO_COUNT; ++i)
+        {
+            fetch_texture_for_ui(*textures[i], material.mTextureId[i]);
+            levels[i] = get_texture_load_level(*textures[i]);
+        }
+
+        return levels;
+    }
+
+    // Is the material loaded enough to start rendering a preview?
+    bool is_material_loaded_enough_for_ui(LLFetchedGLTFMaterial& material)
+    {
+        if (material.isFetching())
+        {
+            return false;
+        }
+
+        LLGLTFPreviewTexture::MaterialLoadLevels levels = get_material_load_levels(material);
+
+        for (U32 i = 0; i < LLGLTFMaterial::GLTF_TEXTURE_INFO_COUNT; ++i)
+        {
+            if (levels[i] == NOT_LOADED)
+            {
+                return false;
+            }
+        }
+
+        return true;
+    }
+
+};  // namespace
+
+LLGLTFPreviewTexture::LLGLTFPreviewTexture(LLPointer<LLFetchedGLTFMaterial> material, S32 width)
+    : LLViewerDynamicTexture(width, width, 4, EOrder::ORDER_MIDDLE, FALSE)
+    , mGLTFMaterial(material)
+{
+}
+
+// static
+LLPointer<LLGLTFPreviewTexture> LLGLTFPreviewTexture::create(LLPointer<LLFetchedGLTFMaterial> material)
+{
+    return new LLGLTFPreviewTexture(material, LLPipeline::MAX_BAKE_WIDTH);
+}
+
+void LLGLTFPreviewTexture::preRender(BOOL clear_depth)
+{
+    LL_PROFILE_ZONE_SCOPED_CATEGORY_UI;
+
+    MaterialLoadLevels current_load = get_material_load_levels(*mGLTFMaterial.get());
+    if (current_load < mBestLoad)
+    {
+        mShouldRender = true;
+        mBestLoad = current_load;
+    }
+
+    if (!mShouldRender) { return; }
+
+    LLViewerDynamicTexture::preRender(clear_depth);
+}
+
+
+namespace {
+
+struct GLTFPreviewModel
+{
+    GLTFPreviewModel(LLPointer<LLDrawInfo>& info, const LLMatrix4& mat)
+    : mDrawInfo(info)
+    , mModelMatrix(mat)
+    {
+        mDrawInfo->mModelMatrix = &mModelMatrix;
+    }
+    LLPointer<LLDrawInfo> mDrawInfo;
+    LLMatrix4 mModelMatrix; // Referenced by mDrawInfo
+};
+
+// Like LLVolumeGeometryManager::registerFace but without batching or too-many-indices/vertices checking.
+std::vector<GLTFPreviewModel> create_preview_sphere(LLPointer<LLFetchedGLTFMaterial>& material, const LLMatrix4& model_matrix)
+{
+    LL_PROFILE_ZONE_SCOPED_CATEGORY_UI;
+
+    const LLColor4U vertex_color(material->mBaseColor);
+
+    LLPrimitive prim;
+    prim.init_primitive(LL_PCODE_VOLUME);
+    LLVolumeParams params;
+    params.setType(LL_PCODE_PROFILE_CIRCLE_HALF, LL_PCODE_PATH_CIRCLE);
+    params.setBeginAndEndS(0.f, 1.f);
+    params.setBeginAndEndT(0.f, 1.f);
+    params.setRatio(1, 1);
+    params.setShear(0, 0);
+    constexpr auto MAX_LOD = LLVolumeLODGroup::NUM_LODS - 1;
+    prim.setVolume(params, MAX_LOD);
+
+    LLVolume* volume = prim.getVolume();
+    llassert(volume);
+    for (LLVolumeFace& face : volume->getVolumeFaces())
+    {
+        face.createTangents();
+    }
+
+    std::vector<GLTFPreviewModel> preview_sphere;
+    preview_sphere.reserve(volume->getNumFaces());
+
+    LLPointer<LLVertexBuffer> buf = new LLVertexBuffer(
+        LLVertexBuffer::MAP_VERTEX |
+        LLVertexBuffer::MAP_NORMAL |
+        LLVertexBuffer::MAP_TEXCOORD0 |
+        LLVertexBuffer::MAP_COLOR |
+        LLVertexBuffer::MAP_TANGENT
+    );
+    U32 nv = 0;
+    U32 ni = 0;
+    for (LLVolumeFace& face : volume->getVolumeFaces())
+    {
+        nv += face.mNumVertices;
+        ni += face.mNumIndices;
+    }
+    buf->allocateBuffer(nv, ni);
+
+    // UV hacks
+    // Higher factor helps to see more details on the preview sphere
+    const LLVector2 uv_factor(2.0f, 2.0f);
+    // Offset places center of material in center of view
+    const LLVector2 uv_offset(-0.5f, -0.5f);
+
+    LLStrider<U16> indices;
+    LLStrider<LLVector4a> positions;
+    LLStrider<LLVector4a> normals;
+    LLStrider<LLVector2> texcoords;
+    LLStrider<LLColor4U> colors;
+    LLStrider<LLVector4a> tangents;
+	buf->getIndexStrider(indices);
+	buf->getVertexStrider(positions);
+	buf->getNormalStrider(normals);
+	buf->getTexCoord0Strider(texcoords);
+	buf->getColorStrider(colors);
+	buf->getTangentStrider(tangents);
+    U32 index_offset = 0;
+    U32 vertex_offset = 0;
+    for (const LLVolumeFace& face : volume->getVolumeFaces())
+    {
+        for (S32 i = 0; i < face.mNumIndices; ++i)
+        {
+            *indices++ = face.mIndices[i] + vertex_offset;
+        }
+        for (S32 v = 0; v < face.mNumVertices; ++v)
+        {
+            *positions++ = face.mPositions[v];
+            *normals++ = face.mNormals[v];
+            LLVector2 uv(face.mTexCoords[v]);
+            uv.scaleVec(uv_factor);
+            uv += uv_offset;
+            *texcoords++ = uv;
+            *colors++ = vertex_color;
+            *tangents++ = face.mTangents[v];
+        }
+
+        constexpr LLViewerTexture* no_media = nullptr;
+        LLPointer<LLDrawInfo> info = new LLDrawInfo(U16(vertex_offset), U16(vertex_offset + face.mNumVertices - 1), face.mNumIndices, index_offset, no_media, buf.get());
+        info->mGLTFMaterial = material;
+        preview_sphere.emplace_back(info, model_matrix);
+        index_offset += face.mNumIndices;
+        vertex_offset += face.mNumVertices;
+    }
+
+    buf->unmapBuffer();
+
+    return preview_sphere;
+}
+
+// Final, direct modifications to shader constants, just before render
+void fixup_shader_constants(LLGLSLShader& shader)
+{
+    // Sunlight intensity of 0 no matter what
+    shader.uniform1i(LLShaderMgr::SUN_UP_FACTOR, 1);
+    shader.uniform3fv(LLShaderMgr::SUNLIGHT_COLOR, 1, LLColor3::white.mV);
+    shader.uniform1f(LLShaderMgr::DENSITY_MULTIPLIER, 0.0f);
+
+    // Ignore sun shadow (if enabled)
+    for (U32 i = 0; i < 6; i++)
+    {
+        const S32 channel = shader.getTextureChannel(LLShaderMgr::DEFERRED_SHADOW0+i);
+        if (channel != -1)
+        {
+            gGL.getTexUnit(channel)->bind(LLViewerFetchedTexture::sWhiteImagep, TRUE);
+        }
+    }
+}
+
+// Set a variable to a value temporarily, and restor the variable's old value
+// when this object leaves scope.
+template<typename T>
+struct SetTemporarily
+{
+    T* mRef;
+    T mOldVal;
+    SetTemporarily(T* var, T temp_val)
+    {
+        mRef = var;
+        mOldVal = *mRef;
+        *mRef = temp_val;
+    }
+    ~SetTemporarily()
+    {
+        *mRef = mOldVal;
+    }
+};
+
+}; // namespace
+
+BOOL LLGLTFPreviewTexture::render()
+{
+    LL_PROFILE_ZONE_SCOPED_CATEGORY_UI;
+
+    if (!mShouldRender) { return FALSE; }
+    
+    LLGLDepthTest(GL_FALSE);
+    LLGLDisable stencil(GL_STENCIL_TEST);
+    LLGLDisable scissor(GL_SCISSOR_TEST);
+    SetTemporarily<bool> no_glow(&LLPipeline::sRenderGlow, false);
+    SetTemporarily<bool> no_ssr(&LLPipeline::RenderScreenSpaceReflections, false);
+    SetTemporarily<U32> no_fxaa(&LLPipeline::RenderFSAASamples, U32(0));
+    SetTemporarily<LLPipeline::RenderTargetPack*> use_auxiliary_render_target(&gPipeline.mRT, &gPipeline.mAuxillaryRT);
+
+    LLVector3 light_dir3(1.0f, 1.0f, 1.0f);
+    light_dir3.normalize();
+    const LLVector4 light_dir = LLVector4(light_dir3, 0);
+    SetTemporarily<S32> sun_light_only(&LLPipeline::RenderLocalLightCount, 0);
+
+    gPipeline.mReflectionMapManager.forceDefaultProbeAndUpdateUniforms();
+
+    LLViewerCamera camera;
+
+    // Calculate the object distance at which the object of a given radius will
+    // span the partial width of the screen given by fill_ratio.
+    // Assume the primitive has a scale of 1 (this is the default).
+    constexpr F32 fill_ratio = 0.8f;
+    constexpr F32 object_radius = 0.5f;
+    const F32 object_distance = (object_radius / fill_ratio) * tan(camera.getDefaultFOV());
+    // Negative coordinate shows the textures on the sphere right-side up, when
+    // combined with the UV hacks in create_preview_sphere
+    const LLVector3 object_position(0.0, -object_distance, 0.0);
+    LLMatrix4 object_transform;
+    object_transform.translate(object_position);
+
+    // Set up camera and viewport
+    const LLVector3 origin(0.0, 0.0, 0.0);
+    camera.lookAt(origin, object_position);
+    camera.setAspect(mFullHeight / mFullWidth);
+    const LLRect texture_rect(0, mFullHeight, mFullWidth, 0);
+    camera.setPerspective(NOT_FOR_SELECTION, texture_rect.mLeft, texture_rect.mBottom, texture_rect.getWidth(), texture_rect.getHeight(), FALSE, camera.getNear(), MAX_FAR_CLIP*2.f);
+
+    // Generate sphere object on-the-fly. Discard afterwards. (Vertex buffer is
+    // discarded, but the sphere should be cached in LLVolumeMgr.)
+    std::vector<GLTFPreviewModel> preview_sphere = create_preview_sphere(mGLTFMaterial, object_transform);
+
+    glClearColor(0, 0, 0, 0);
+
+    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
+
+    gPipeline.setupHWLights();
+    glh::matrix4f mat = copy_matrix(gGLModelView);
+    glh::vec4f transformed_light_dir(light_dir.mV);
+    mat.mult_matrix_vec(transformed_light_dir);
+    SetTemporarily<LLVector4> force_sun_direction_high_graphics(&gPipeline.mTransformedSunDir, LLVector4(transformed_light_dir.v));
+    // Override lights to ensure the sun is always shining from a certain direction (low graphics)
+    // See also force_sun_direction_high_graphics and fixup_shader_constants
+    {
+        LLLightState* light = gGL.getLight(0);
+        light->setPosition(light_dir);
+        constexpr bool sun_up = true;
+        light->setSunPrimary(sun_up);
+    }
+
+    LLRenderTarget& screen = gPipeline.mAuxillaryRT.screen;
+
+#if 0
+    if (mGLTFMaterial->mAlphaMode == LLGLTFMaterial::ALPHA_MODE_OPAQUE || mGLTFMaterial->mAlphaMode == LLGLTFMaterial::ALPHA_MODE_MASK)
+    {
+        // *TODO: Opaque/alpha mask rendering (gDeferredPBROpaqueProgram)
+    }
+    else
+#endif
+    {
+        // Alpha blend rendering
+
+        screen.bindTarget();
+        glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
+
+        LLGLSLShader& shader = gDeferredPBRAlphaProgram;
+
+        gPipeline.bindDeferredShader(shader);
+        fixup_shader_constants(shader);
+
+        for (GLTFPreviewModel& part : preview_sphere)
+        {
+            LLRenderPass::pushGLTFBatch(*part.mDrawInfo);
+        }
+
+        gPipeline.unbindDeferredShader(shader);
+
+        screen.flush();
+    }
+
+    gPipeline.copyScreenSpaceReflections(&screen, &gPipeline.mSceneMap);
+    gPipeline.generateLuminance(&screen, &gPipeline.mLuminanceMap);
+    gPipeline.generateExposure(&gPipeline.mLuminanceMap, &gPipeline.mExposureMap);
+    gPipeline.gammaCorrect(&screen, &gPipeline.mPostMap);
+    LLVertexBuffer::unbind();
+    gPipeline.generateGlow(&gPipeline.mPostMap);
+    gPipeline.combineGlow(&gPipeline.mPostMap, &screen);
+	gPipeline.renderDoF(&screen, &gPipeline.mPostMap);
+	gPipeline.applyFXAA(&gPipeline.mPostMap, &screen);
+
+	gDeferredPostNoDoFProgram.bind();
+
+	// From LLPipeline::renderFinalize: "Whatever is last in the above post processing chain should _always_ be rendered directly here.  If not, expect problems."
+	gDeferredPostNoDoFProgram.bindTexture(LLShaderMgr::DEFERRED_DIFFUSE, &screen);
+	gDeferredPostNoDoFProgram.bindTexture(LLShaderMgr::DEFERRED_DEPTH, mBoundTarget, true);
+
+	{
+		LLGLDepthTest depth_test(GL_TRUE, GL_TRUE, GL_ALWAYS);
+		gPipeline.mScreenTriangleVB->setBuffer();
+		gPipeline.mScreenTriangleVB->drawArrays(LLRender::TRIANGLES, 0, 3);
+	}
+
+	gDeferredPostNoDoFProgram.unbind();
+
+    // Clean up
+    gPipeline.setupHWLights();
+    gPipeline.mReflectionMapManager.forceDefaultProbeAndUpdateUniforms(false);
+
+    return TRUE;
+}
+
+void LLGLTFPreviewTexture::postRender(BOOL success)
+{
+    LL_PROFILE_ZONE_SCOPED_CATEGORY_UI;
+
+    if (!mShouldRender) { return; }
+    mShouldRender = false;
+
+    LLViewerDynamicTexture::postRender(success);
+}
+
+// static
+LLPointer<LLViewerTexture> LLGLTFMaterialPreviewMgr::getPreview(LLPointer<LLFetchedGLTFMaterial> &material)
+{
+    if (!material)
+    {
+        return nullptr;
+    }
+
+    if (!is_material_loaded_enough_for_ui(*material))
+    {
+        return nullptr;
+    }
+
+    return LLGLTFPreviewTexture::create(material);
+}
diff --git a/indra/newview/llgltfmaterialpreviewmgr.h b/indra/newview/llgltfmaterialpreviewmgr.h
new file mode 100644
index 0000000000000000000000000000000000000000..cc40a6f2e2a1afdc5cab1811fe6772e2aa3a1ac3
--- /dev/null
+++ b/indra/newview/llgltfmaterialpreviewmgr.h
@@ -0,0 +1,82 @@
+/**
+ * @file llgltfmaterialpreviewmgr.h
+ *
+ * $LicenseInfo:firstyear=2023&license=viewerlgpl$
+ * Second Life Viewer Source Code
+ * Copyright (C) 2023, Linden Research, Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation;
+ * version 2.1 of the License only.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
+ *
+ * Linden Research, Inc., 945 Battery Street, San Francisco, CA  94111  USA
+ * $/LicenseInfo$
+ */
+
+#pragma once
+
+#include "lldrawpool.h"
+#include "lldynamictexture.h"
+#include "llfetchedgltfmaterial.h"
+#include "llsingleton.h"
+#include "lltexture.h"
+
+class LLGLTFPreviewTexture : public LLViewerDynamicTexture
+{
+protected:
+    LLGLTFPreviewTexture(LLPointer<LLFetchedGLTFMaterial> material, S32 width);
+
+public:
+    // Width scales with size of material's textures
+    static LLPointer<LLGLTFPreviewTexture> create(LLPointer<LLFetchedGLTFMaterial> material);
+
+    BOOL needsRender() override { return mNeedsRender; }
+    void preRender(BOOL clear_depth = TRUE) override;
+    BOOL render() override;
+    void postRender(BOOL success) override;
+
+    struct MaterialLoadLevels
+    {
+        S32 levels[LLGLTFMaterial::GLTF_TEXTURE_INFO_COUNT];
+
+        MaterialLoadLevels();
+
+        S32& operator[](size_t i);
+
+        const S32& operator[](size_t i) const;
+
+        // Less is better
+        // Returns false if lhs is not strictly less or equal for all levels
+        bool operator<(const MaterialLoadLevels& other) const;
+
+        // Less is better
+        // Returns false if lhs is not strictly greater or equal for all levels
+        bool operator>(const MaterialLoadLevels& other) const;
+    };
+
+private:
+    LLPointer<LLFetchedGLTFMaterial> mGLTFMaterial;
+    bool mNeedsRender = true;
+    bool mShouldRender = true;
+    MaterialLoadLevels mBestLoad;
+};
+
+class LLGLTFMaterialPreviewMgr
+{
+  public:
+    // Returns null if the material is not loaded yet.
+    // *NOTE: User should cache the texture if the same material is being previewed
+    LLPointer<LLViewerTexture> getPreview(LLPointer<LLFetchedGLTFMaterial> &material);
+};
+
+extern LLGLTFMaterialPreviewMgr gGLTFMaterialPreviewMgr;
diff --git a/indra/newview/llreflectionmapmanager.cpp b/indra/newview/llreflectionmapmanager.cpp
index 69674417c10bc33f1a27774232d53a05605bf8b2..84f6dd7a4f51bdb8a58aa3f435f0b0305870ca86 100644
--- a/indra/newview/llreflectionmapmanager.cpp
+++ b/indra/newview/llreflectionmapmanager.cpp
@@ -27,6 +27,9 @@
 #include "llviewerprecompiledheaders.h"
 
 #include "llreflectionmapmanager.h"
+
+#include <bitset>
+
 #include "llviewercamera.h"
 #include "llspatialpartition.h"
 #include "llviewerregion.h"
@@ -1383,3 +1386,32 @@ void LLReflectionMapManager::doOcclusion()
         }
     }
 }
+
+void LLReflectionMapManager::forceDefaultProbeAndUpdateUniforms(bool force)
+{
+    static std::bitset<LL_MAX_REFLECTION_PROBE_COUNT> mProbeWasOccluded;
+
+    if (force)
+    {
+        for (size_t i = 0; i < mProbes.size(); ++i)
+        {
+            auto& probe = mProbes[i];
+            mProbeWasOccluded[i] = probe->mOccluded;
+            if (probe != nullptr && probe != mDefaultProbe)
+            {
+                probe->mOccluded = true;
+            }
+        }
+
+        updateUniforms();
+    }
+    else
+    {
+        for (size_t i = 0; i < mProbes.size(); ++i)
+        {
+            auto& probe = mProbes[i];
+            llassert(probe->mOccluded == (probe != mDefaultProbe));
+            probe->mOccluded = mProbeWasOccluded[i];
+        }
+    }
+}
diff --git a/indra/newview/llreflectionmapmanager.h b/indra/newview/llreflectionmapmanager.h
index b77a33da898b2759598c5452fd31990215b5d4b2..dd641452aef3da2447737f48a402e3b6a5fbac96 100644
--- a/indra/newview/llreflectionmapmanager.h
+++ b/indra/newview/llreflectionmapmanager.h
@@ -106,6 +106,11 @@ class alignas(16) LLReflectionMapManager
     // perform occlusion culling on all active reflection probes
     void doOcclusion();
 
+    // *HACK: "cull" all reflection probes except the default one. Only call
+    // this if you don't intend to call updateUniforms directly. Call again
+    // with false when done.
+    void forceDefaultProbeAndUpdateUniforms(bool force = true);
+
 private:
     friend class LLPipeline;
 
diff --git a/indra/newview/lltexturectrl.cpp b/indra/newview/lltexturectrl.cpp
index 233b864fbac5d8e3f9243c46a84b7de2d41c3a16..b9cb00b5616b5afb751d3cb41716208c1e49dede 100644
--- a/indra/newview/lltexturectrl.cpp
+++ b/indra/newview/lltexturectrl.cpp
@@ -72,6 +72,7 @@
 
 #include "llradiogroup.h"
 #include "llfloaterreg.h"
+#include "llgltfmaterialpreviewmgr.h"
 #include "lllocalbitmaps.h"
 #include "lllocalgltfmaterials.h"
 #include "llerror.h"
@@ -657,6 +658,7 @@ void LLFloaterTexturePicker::draw()
 	if( mOwner ) 
 	{
 		mTexturep = NULL;
+        LLPointer<LLFetchedGLTFMaterial> old_material = mGLTFMaterial;
         mGLTFMaterial = NULL;
         if (mImageAssetID.notNull())
         {
@@ -664,10 +666,23 @@ void LLFloaterTexturePicker::draw()
             {
                 mGLTFMaterial = (LLFetchedGLTFMaterial*) gGLTFMaterialList.getMaterial(mImageAssetID);
                 llassert(mGLTFMaterial == nullptr || dynamic_cast<LLFetchedGLTFMaterial*>(gGLTFMaterialList.getMaterial(mImageAssetID)) != nullptr);
+                if (mGLTFPreview.isNull() || mGLTFMaterial.isNull() || (old_material.notNull() && (*old_material.get() != *mGLTFMaterial.get())))
+                {
+                    // Only update the preview if needed, since gGLTFMaterialPreviewMgr does not cache the preview.
+                    if (mGLTFMaterial.isNull())
+                    {
+                        mGLTFPreview = nullptr;
+                    }
+                    else
+                    {
+                        mGLTFPreview = gGLTFMaterialPreviewMgr.getPreview(mGLTFMaterial);
+                    }
+                }
             }
             else
             {
                 LLPointer<LLViewerFetchedTexture> texture = NULL;
+                mGLTFPreview = nullptr;
 
                 if (LLAvatarAppearanceDefines::LLAvatarAppearanceDictionary::isBakedImageId(mImageAssetID))
                 {
@@ -677,7 +692,7 @@ void LLFloaterTexturePicker::draw()
                     if (obj)
                     {
                         LLViewerTexture* viewerTexture = obj->getBakedTextureForMagicId(mImageAssetID);
-                        texture = viewerTexture ? dynamic_cast<LLViewerFetchedTexture*>(viewerTexture) : NULL;
+						texture = viewerTexture ? dynamic_cast<LLViewerFetchedTexture*>(viewerTexture) : NULL;
                     }
                 }
 
@@ -718,27 +733,29 @@ void LLFloaterTexturePicker::draw()
 
 		// If the floater is focused, don't apply its alpha to the texture (STORM-677).
 		const F32 alpha = getTransparencyType() == TT_ACTIVE ? 1.0f : getCurrentTransparency();
-        LLViewerTexture* texture = nullptr;
+        LLViewerTexture* preview = nullptr;
         if (mGLTFMaterial)
         {
-            texture = mGLTFMaterial->getUITexture();
+            preview = mGLTFPreview.get();
         }
         else
         {
-            texture = mTexturep.get();
+            preview = mTexturep.get();
+            if (mTexturep)
+            {
+                // Pump the priority
+				mTexturep->addTextureStats( (F32)(interior.getWidth() * interior.getHeight()) );
+            }
         }
 
-		if( texture )
+		if( preview )
 		{
-			if( texture->getComponents() == 4 )
+			if( preview->getComponents() == 4 )
 			{
 				gl_rect_2d_checkerboard( interior, alpha );
 			}
 
-			gl_draw_scaled_image( interior.mLeft, interior.mBottom, interior.getWidth(), interior.getHeight(), texture, UI_VERTEX_COLOR % alpha );
-
-			// Pump the priority
-			texture->addTextureStats( (F32)(interior.getWidth() * interior.getHeight()) );
+			gl_draw_scaled_image( interior.mLeft, interior.mBottom, interior.getWidth(), interior.getHeight(), preview, UI_VERTEX_COLOR % alpha );
 		}
 		else if (!mFallbackImage.isNull())
 		{
@@ -2156,48 +2173,69 @@ void LLTextureCtrl::draw()
 {
 	mBorder->setKeyboardFocusHighlight(hasFocus());
 
+    LLPointer<LLViewerTexture> preview = NULL;
+
 	if (!mValid)
 	{
 		mTexturep = NULL;
+        mGLTFMaterial = NULL;
+        mGLTFPreview = NULL;
 	}
 	else if (!mImageAssetID.isNull())
 	{
-		LLPointer<LLViewerFetchedTexture> texture = NULL;
-
 		if (LLAvatarAppearanceDefines::LLAvatarAppearanceDictionary::isBakedImageId(mImageAssetID))
 		{
 			LLViewerObject* obj = LLSelectMgr::getInstance()->getSelection()->getFirstObject();
 			if (obj)
 			{
 				LLViewerTexture* viewerTexture = obj->getBakedTextureForMagicId(mImageAssetID);
-				texture = viewerTexture ? dynamic_cast<LLViewerFetchedTexture*>(viewerTexture) : NULL;
+				mTexturep = viewerTexture ? dynamic_cast<LLViewerFetchedTexture*>(viewerTexture) : NULL;
+				mGLTFMaterial = NULL;
+				mGLTFPreview = NULL;
+
+                preview = mTexturep;
 			}
 			
 		}
 
-		if (texture.isNull())
+		if (preview.isNull())
 		{
+            LLPointer<LLFetchedGLTFMaterial> old_material = mGLTFMaterial;
+            mGLTFMaterial = NULL;
+            mTexturep = NULL;
             if (mInventoryPickType == PICK_MATERIAL)
             {
-                LLPointer<LLFetchedGLTFMaterial> material = gGLTFMaterialList.getMaterial(mImageAssetID);
-                if (material)
+                mGLTFMaterial = gGLTFMaterialList.getMaterial(mImageAssetID);
+                if (mGLTFPreview.isNull() || mGLTFMaterial.isNull() || (old_material.notNull() && (*old_material.get() != *mGLTFMaterial.get())))
                 {
-                    texture = material->getUITexture();
+                    // Only update the preview if needed, since gGLTFMaterialPreviewMgr does not cache the preview.
+                    if (mGLTFMaterial.isNull())
+                    {
+                        mGLTFPreview = nullptr;
+                    }
+                    else
+                    {
+                        mGLTFPreview = gGLTFMaterialPreviewMgr.getPreview(mGLTFMaterial);
+                    }
                 }
+
+                preview = mGLTFPreview;
             }
             else
             {
-                texture = LLViewerTextureManager::getFetchedTexture(mImageAssetID, FTT_DEFAULT, TRUE, LLGLTexture::BOOST_NONE, LLViewerTexture::LOD_TEXTURE);
-                texture->setBoostLevel(LLGLTexture::BOOST_PREVIEW);
-                texture->forceToSaveRawImage(0);
+				mTexturep = LLViewerTextureManager::getFetchedTexture(mImageAssetID, FTT_DEFAULT, TRUE, LLGLTexture::BOOST_NONE, LLViewerTexture::LOD_TEXTURE);
+				mTexturep->setBoostLevel(LLGLTexture::BOOST_PREVIEW);
+				mTexturep->forceToSaveRawImage(0);
+
+				preview = mTexturep;
             }
 		}
-
-		mTexturep = texture;
 	}
 	else//mImageAssetID == LLUUID::null
 	{
 		mTexturep = NULL; 
+        mGLTFMaterial = NULL;
+        mGLTFPreview = NULL;
 	}
 	
 	// Border
@@ -2210,15 +2248,18 @@ void LLTextureCtrl::draw()
 
 	// If we're in a focused floater, don't apply the floater's alpha to the texture (STORM-677).
 	const F32 alpha = getTransparencyType() == TT_ACTIVE ? 1.0f : getCurrentTransparency();
-	if( mTexturep )
+	if( preview )
 	{
-		if( mTexturep->getComponents() == 4 )
+		if( preview->getComponents() == 4 )
 		{
 			gl_rect_2d_checkerboard( interior, alpha );
 		}
 		
-		gl_draw_scaled_image( interior.mLeft, interior.mBottom, interior.getWidth(), interior.getHeight(), mTexturep, UI_VERTEX_COLOR % alpha);
-		mTexturep->addTextureStats( (F32)(interior.getWidth() * interior.getHeight()) );
+		gl_draw_scaled_image( interior.mLeft, interior.mBottom, interior.getWidth(), interior.getHeight(), preview, UI_VERTEX_COLOR % alpha);
+        if (mTexturep)
+        {
+            mTexturep->addTextureStats( (F32)(interior.getWidth() * interior.getHeight()) );
+        }
 	}
 	else if (!mFallbackImage.isNull())
 	{
diff --git a/indra/newview/lltexturectrl.h b/indra/newview/lltexturectrl.h
index cb6ce636e0be37c70ccdd0e071bf2305c97b1b70..77919637ac68c40fb0c96c1b18c9ae98a40af18e 100644
--- a/indra/newview/lltexturectrl.h
+++ b/indra/newview/lltexturectrl.h
@@ -250,6 +250,8 @@ class LLTextureCtrl
 	commit_callback_t		 	mOnCloseCallback;
 	texture_selected_callback	mOnTextureSelectedCallback;
 	LLPointer<LLViewerFetchedTexture> mTexturep;
+	LLPointer<LLFetchedGLTFMaterial> mGLTFMaterial;
+	LLPointer<LLViewerTexture> mGLTFPreview;
 	LLUIColor				 	mBorderColor;
 	LLUUID					 	mImageItemID;
 	LLUUID					 	mImageAssetID;
@@ -382,6 +384,7 @@ class LLFloaterTexturePicker : public LLFloater
 
 	LLPointer<LLViewerTexture> mTexturep;
     LLPointer<LLFetchedGLTFMaterial> mGLTFMaterial;
+    LLPointer<LLViewerTexture> mGLTFPreview;
 	LLView*				mOwner;
 
 	LLUUID				mImageAssetID; // Currently selected texture
diff --git a/indra/newview/pipeline.cpp b/indra/newview/pipeline.cpp
index 3882472b49c1695bfe1620f2d58800d25aae615a..f34d5360e25a44653b6c97310e5e5ec6581428c7 100644
--- a/indra/newview/pipeline.cpp
+++ b/indra/newview/pipeline.cpp
@@ -163,6 +163,7 @@ F32 LLPipeline::CameraFocusTransitionTime;
 F32 LLPipeline::CameraFNumber;
 F32 LLPipeline::CameraFocalLength;
 F32 LLPipeline::CameraFieldOfView;
+S32 LLPipeline::RenderLocalLightCount;
 F32 LLPipeline::RenderShadowNoise;
 F32 LLPipeline::RenderShadowBlurSize;
 F32 LLPipeline::RenderSSAOScale;
@@ -200,6 +201,8 @@ S32 LLPipeline::RenderScreenSpaceReflectionGlossySamples;
 S32 LLPipeline::RenderBufferVisualization;
 LLTrace::EventStatHandle<S64> LLPipeline::sStatBatchSize("renderbatchsize");
 
+const U32 LLPipeline::MAX_BAKE_WIDTH = 512;
+
 const F32 BACKLIGHT_DAY_MAGNITUDE_OBJECT = 0.1f;
 const F32 BACKLIGHT_NIGHT_MAGNITUDE_OBJECT = 0.08f;
 const F32 ALPHA_BLEND_CUTOFF = 0.598f;
@@ -521,6 +524,7 @@ void LLPipeline::init()
 	connectRefreshCachedSettingsSafe("CameraFNumber");
 	connectRefreshCachedSettingsSafe("CameraFocalLength");
 	connectRefreshCachedSettingsSafe("CameraFieldOfView");
+	connectRefreshCachedSettingsSafe("RenderLocalLightCount");
 	connectRefreshCachedSettingsSafe("RenderShadowNoise");
 	connectRefreshCachedSettingsSafe("RenderShadowBlurSize");
 	connectRefreshCachedSettingsSafe("RenderSSAOScale");
@@ -1009,6 +1013,7 @@ void LLPipeline::refreshCachedSettings()
 	CameraFNumber = gSavedSettings.getF32("CameraFNumber");
 	CameraFocalLength = gSavedSettings.getF32("CameraFocalLength");
 	CameraFieldOfView = gSavedSettings.getF32("CameraFieldOfView");
+	RenderLocalLightCount = gSavedSettings.getS32("RenderLocalLightCount");
 	RenderShadowNoise = gSavedSettings.getF32("RenderShadowNoise");
 	RenderShadowBlurSize = gSavedSettings.getF32("RenderShadowBlurSize");
 	RenderSSAOScale = gSavedSettings.getF32("RenderSSAOScale");
@@ -1072,7 +1077,6 @@ void LLPipeline::releaseGLBuffers()
 	releaseLUTBuffers();
 
 	mWaterDis.release();
-    mBake.release();
 	
     mSceneMap.release();
 
@@ -1151,9 +1155,6 @@ void LLPipeline::createGLBuffers()
     stop_glerror();
 	assertInitialized();
 
-    // Use FBO for bake tex
-    mBake.allocate(512, 512, GL_RGBA, true); // SL-12781 Build > Upload > Model; 3D Preview
-
 	stop_glerror();
 
 	GLuint resX = gViewerWindow->getWorldViewWidthRaw();
@@ -5227,7 +5228,7 @@ void LLPipeline::calcNearbyLights(LLCamera& camera)
 		return;
 	}
 
-    static LLCachedControl<S32> local_light_count(gSavedSettings, "RenderLocalLightCount", 256);
+    const S32 local_light_count = LLPipeline::RenderLocalLightCount;
 
 	if (local_light_count >= 1)
 	{
@@ -5496,7 +5497,7 @@ void LLPipeline::setupHWLights()
 
 	mLightMovingMask = 0;
 	
-    static LLCachedControl<S32> local_light_count(gSavedSettings, "RenderLocalLightCount", 256);
+    const S32 local_light_count = LLPipeline::RenderLocalLightCount;
 
 	if (local_light_count >= 1)
 	{
@@ -7930,7 +7931,7 @@ void LLPipeline::renderDeferredLighting()
             unbindDeferredShader(gDeferredSoftenProgram);
         }
 
-        static LLCachedControl<S32> local_light_count(gSavedSettings, "RenderLocalLightCount", 256);
+        const S32 local_light_count = LLPipeline::RenderLocalLightCount;
 
         if (local_light_count > 0)
         {
diff --git a/indra/newview/pipeline.h b/indra/newview/pipeline.h
index 88a7eab813c4cb3d7ecda279898354139f8f1e9d..dd5040f76e701b82a55e9a0d8b302f24cf5afe2a 100644
--- a/indra/newview/pipeline.h
+++ b/indra/newview/pipeline.h
@@ -694,6 +694,7 @@ class LLPipeline
     RenderTargetPack mMainRT;
 
     // auxillary 512x512 render target pack
+    // used by reflection probes and dynamic texture bakes
     RenderTargetPack mAuxillaryRT;
 
     // currently used render target pack
@@ -754,7 +755,7 @@ class LLPipeline
 	//water distortion texture (refraction)
 	LLRenderTarget				mWaterDis;
 
-    LLRenderTarget				mBake;
+    static const U32 MAX_BAKE_WIDTH;
 
 	//texture for making the glow
 	LLRenderTarget				mGlow[3];
@@ -1012,6 +1013,7 @@ class LLPipeline
 	static F32 CameraFNumber;
 	static F32 CameraFocalLength;
 	static F32 CameraFieldOfView;
+	static S32 RenderLocalLightCount;
 	static F32 RenderShadowNoise;
 	static F32 RenderShadowBlurSize;
 	static F32 RenderSSAOScale;