diff --git a/doc/testplans/RenderMaxTextureResolution.md b/doc/testplans/RenderMaxTextureResolution.md
new file mode 100644
index 0000000000000000000000000000000000000000..2b117050c757c04333fd499afff3613354c26485
--- /dev/null
+++ b/doc/testplans/RenderMaxTextureResolution.md
@@ -0,0 +1,16 @@
+The Setting RenderMaxTextureResolution controls the maximum resolution of non-boosted textures as displayed by the viewer.
+
+Valid values are 512-2048 (clamped in C++).
+
+![image](https://github.com/secondlife/viewer/assets/23218274/d0f889fc-8135-41d2-9d83-871ad4eebed5)
+
+![image](https://github.com/secondlife/viewer/assets/23218274/19950828-7eb1-4bb2-85d7-f35c63b34294)
+
+![image](https://github.com/secondlife/viewer/assets/23218274/249afc83-4de6-488d-a05e-4877d08573b1)
+
+Test Asset available on beta grid:
+Object: 'Damaged Helmet', AssetID 0623e759-11b5-746c-a75e-7ba1caa6eb0e
+
+
+
+
diff --git a/indra/newview/app_settings/settings.xml b/indra/newview/app_settings/settings.xml
index aaab7f1d457f6a0322ff49bd94a4ee2f4555765f..f248fac2ee2b7099a26854b8be70a4548eed0724 100644
--- a/indra/newview/app_settings/settings.xml
+++ b/indra/newview/app_settings/settings.xml
@@ -9405,6 +9405,17 @@
     <string>U32</string>
     <key>Value</key>
     <integer>16</integer>
+  </map>
+  <key>RenderMaxTextureResolution</key>
+  <map>
+    <key>Comment</key>
+    <string>Maximum texture resolution to download for non-boosted textures.</string>
+    <key>Persist</key>
+    <integer>1</integer>
+    <key>Type</key>
+    <string>U32</string>
+    <key>Value</key>
+    <integer>2048</integer>
   </map>
     <key>RenderDebugTextureBind</key>
     <map>
diff --git a/indra/newview/llheroprobemanager.cpp b/indra/newview/llheroprobemanager.cpp
index 10b743ceefb3bd3ec3ea8b962118d4282c235a9e..13c12b5e557d95c711293dda9bd1391f674f7d61 100644
--- a/indra/newview/llheroprobemanager.cpp
+++ b/indra/newview/llheroprobemanager.cpp
@@ -581,7 +581,7 @@ bool LLHeroProbeManager::registerViewerObject(LLVOVolume* drawablep)
 
 void LLHeroProbeManager::unregisterViewerObject(LLVOVolume* drawablep)
 {
-    std::vector<LLVOVolume*>::iterator found_itr = std::find(mHeroVOList.begin(), mHeroVOList.end(), drawablep);
+    std::vector<LLPointer<LLVOVolume>>::iterator found_itr = std::find(mHeroVOList.begin(), mHeroVOList.end(), drawablep);
     if (found_itr != mHeroVOList.end())
     {
         mHeroVOList.erase(found_itr);
diff --git a/indra/newview/llheroprobemanager.h b/indra/newview/llheroprobemanager.h
index 04027cd57e838ccb538e7b951e817ba75cc15a5e..17e75a18d498e4dbc3ff7257825c71e050d036fb 100644
--- a/indra/newview/llheroprobemanager.h
+++ b/indra/newview/llheroprobemanager.h
@@ -146,8 +146,8 @@ class alignas(16) LLHeroProbeManager
     
     U32 mCurrentProbeUpdateFrame = 0;
     
-    std::vector<LLVOVolume*>                       mHeroVOList;
-    LLVOVolume*                                 mNearestHero;
+    std::vector<LLPointer<LLVOVolume>>                       mHeroVOList;
+    LLPointer<LLVOVolume>                                 mNearestHero;
 
 };
 
diff --git a/indra/newview/llviewertexture.cpp b/indra/newview/llviewertexture.cpp
index 8436159e14e488596658b98c303f4d588cd62a66..9759b9a31bac30673f77c33e7b4bb37b1eed4509 100644
--- a/indra/newview/llviewertexture.cpp
+++ b/indra/newview/llviewertexture.cpp
@@ -3079,7 +3079,15 @@ void LLViewerLODTexture::processTextureStats()
 	updateVirtualSize();
 	
 	static LLCachedControl<bool> textures_fullres(gSavedSettings,"TextureLoadFullRes", false);
-	
+
+    { // restrict texture resolution to download based on RenderMaxTextureResolution
+        static LLCachedControl<U32> max_texture_resolution(gSavedSettings, "RenderMaxTextureResolution", 2048);
+        // sanity clamp debug setting to avoid settings hack shenanigans
+        F32 tex_res = (F32)llclamp((S32)max_texture_resolution, 512, 2048);
+        tex_res *= tex_res;
+        mMaxVirtualSize = llmin(mMaxVirtualSize, tex_res);
+    }
+
 	if (textures_fullres)
 	{
 		mDesiredDiscardLevel = 0;