From 93260cfeff2382dd1ffeecaef208d37bf21c2a01 Mon Sep 17 00:00:00 2001
From: Dave Parks <davep@lindenlab.com>
Date: Wed, 4 May 2022 16:07:50 +0000
Subject: [PATCH] SL-17283 LLReflectionMapManager prototype.  Remove snapshot
 code related overhead from reflection map renders.  Add parallax correction
 and support for multiple reflection maps.

---
 indra/llrender/llcubemap.cpp                  |  26 +++
 indra/llrender/llcubemap.h                    |  12 +-
 indra/llrender/llglslshader.cpp               |  33 ++-
 indra/llrender/llglslshader.h                 |   2 +-
 indra/llrender/llimagegl.h                    |   2 +-
 indra/llrender/llrendertarget.cpp             |   4 +
 indra/newview/CMakeLists.txt                  |   6 +-
 .../shaders/class2/deferred/softenLightF.glsl | 190 +++++++++++++++---
 indra/newview/llappviewer.cpp                 |  29 +--
 indra/newview/llenvironmentmap.cpp            | 116 -----------
 indra/newview/llreflectionmap.cpp             |  64 ++++++
 .../{llenvironmentmap.h => llreflectionmap.h} |  17 +-
 indra/newview/llreflectionmapmanager.cpp      | 106 ++++++++++
 indra/newview/llreflectionmapmanager.h        |  51 +++++
 indra/newview/llviewerwindow.cpp              | 147 ++++++++++++++
 indra/newview/llviewerwindow.h                |  12 ++
 indra/newview/pipeline.cpp                    |  87 ++++----
 indra/newview/pipeline.h                      |   7 +-
 18 files changed, 695 insertions(+), 216 deletions(-)
 delete mode 100644 indra/newview/llenvironmentmap.cpp
 create mode 100644 indra/newview/llreflectionmap.cpp
 rename indra/newview/{llenvironmentmap.h => llreflectionmap.h} (87%)
 create mode 100644 indra/newview/llreflectionmapmanager.cpp
 create mode 100644 indra/newview/llreflectionmapmanager.h

diff --git a/indra/llrender/llcubemap.cpp b/indra/llrender/llcubemap.cpp
index cc61158aa68..17a487b860d 100644
--- a/indra/llrender/llcubemap.cpp
+++ b/indra/llrender/llcubemap.cpp
@@ -166,6 +166,19 @@ void LLCubeMap::init(const std::vector<LLPointer<LLImageRaw> >& rawimages)
 	}
 }
 
+void LLCubeMap::initReflectionMap(U32 resolution, U32 components)
+{
+    U32 texname = 0;
+
+    LLImageGL::generateTextures(1, &texname);
+
+    mImages[0] = new LLImageGL(resolution, resolution, components, TRUE);
+    mImages[0]->setTexName(texname);
+    mImages[0]->setTarget(mTargets[0], LLTexUnit::TT_CUBE_MAP);
+    gGL.getTexUnit(0)->bindManual(LLTexUnit::TT_CUBE_MAP, texname);
+    mImages[0]->setAddressMode(LLTexUnit::TAM_CLAMP);
+}
+
 void LLCubeMap::initEnvironmentMap(const std::vector<LLPointer<LLImageRaw> >& rawimages)
 {
     llassert(rawimages.size() == 6);
@@ -203,6 +216,19 @@ void LLCubeMap::initEnvironmentMap(const std::vector<LLPointer<LLImageRaw> >& ra
     disable();
 }
 
+void LLCubeMap::generateMipMaps()
+{
+    mImages[0]->setUseMipMaps(TRUE);
+    mImages[0]->setHasMipMaps(TRUE);
+    enableTexture(0);
+    bind();
+    mImages[0]->setFilteringOption(LLTexUnit::TFO_ANISOTROPIC);
+    glEnable(GL_TEXTURE_CUBE_MAP_SEAMLESS);
+    glGenerateMipmap(GL_TEXTURE_CUBE_MAP);
+    gGL.getTexUnit(0)->disable();
+    disable();
+}
+
 GLuint LLCubeMap::getGLName()
 {
 	return mImages[0]->getTexName();
diff --git a/indra/llrender/llcubemap.h b/indra/llrender/llcubemap.h
index 9ce4a94bcae..b9e081cea3b 100644
--- a/indra/llrender/llcubemap.h
+++ b/indra/llrender/llcubemap.h
@@ -41,11 +41,15 @@ class LLCubeMap : public LLRefCount
 	LLCubeMap(bool init_as_srgb);
 	void init(const std::vector<LLPointer<LLImageRaw> >& rawimages);
 
+    // initialize as an undefined cubemap at the given resolution
+    //  used for render-to-cubemap operations
+    //  avoids usage of LLImageRaw
+    void initReflectionMap(U32 resolution, U32 components = 3);
+
     // init from environment map images
     // Similar to init, but takes ownership of rawimages and makes this cubemap
     // respect the resolution of rawimages
     // Raw images must point to array of six square images that are all the same resolution
-    // For example usage, see LLEnvironmentMap
     void initEnvironmentMap(const std::vector<LLPointer<LLImageRaw> >& rawimages);
 	void initGL();
 	void initRawData(const std::vector<LLPointer<LLImageRaw> >& rawimages);
@@ -62,6 +66,12 @@ class LLCubeMap : public LLRefCount
 	void setMatrix(S32 stage);
 	void restoreMatrix();
 
+    U32 getResolution() { return mImages[0].notNull() ? mImages[0]->getWidth(0) : 0; }
+    
+    // generate mip maps for this Cube Map using GL
+    // NOTE: Cube Map MUST already be resident in VRAM
+    void generateMipMaps();
+
 	GLuint getGLName();
 
 	void destroyGL();
diff --git a/indra/llrender/llglslshader.cpp b/indra/llrender/llglslshader.cpp
index 3001375c607..a52dcd5aa18 100644
--- a/indra/llrender/llglslshader.cpp
+++ b/indra/llrender/llglslshader.cpp
@@ -738,7 +738,7 @@ void LLGLSLShader::mapUniform(GLint index, const vector<LLStaticHashedString> *
             {
                 //found it
                 mUniform[i] = location;
-                mTexture[i] = mapUniformTextureChannel(location, type);
+                mTexture[i] = mapUniformTextureChannel(location, type, size);
                 return;
             }
         }
@@ -752,7 +752,7 @@ void LLGLSLShader::mapUniform(GLint index, const vector<LLStaticHashedString> *
                 {
                     //found it
                     mUniform[i+LLShaderMgr::instance()->mReservedUniforms.size()] = location;
-                    mTexture[i+LLShaderMgr::instance()->mReservedUniforms.size()] = mapUniformTextureChannel(location, type);
+                    mTexture[i+LLShaderMgr::instance()->mReservedUniforms.size()] = mapUniformTextureChannel(location, type, size);
                     return;
                 }
             }
@@ -775,16 +775,37 @@ void LLGLSLShader::removePermutation(std::string name)
     mDefines[name].erase();
 }
 
-GLint LLGLSLShader::mapUniformTextureChannel(GLint location, GLenum type)
+GLint LLGLSLShader::mapUniformTextureChannel(GLint location, GLenum type, GLint size)
 {
     LL_PROFILE_ZONE_SCOPED_CATEGORY_SHADER;
 
     if ((type >= GL_SAMPLER_1D_ARB && type <= GL_SAMPLER_2D_RECT_SHADOW_ARB) ||
         type == GL_SAMPLER_2D_MULTISAMPLE)
     {   //this here is a texture
-        glUniform1iARB(location, mActiveTextureChannels);
-        LL_DEBUGS("ShaderUniform") << "Assigned to texture channel " << mActiveTextureChannels << LL_ENDL;
-        return mActiveTextureChannels++;
+        GLint ret = mActiveTextureChannels;
+        if (size == 1)
+        {
+            glUniform1iARB(location, mActiveTextureChannels);
+            LL_DEBUGS("ShaderUniform") << "Assigned to texture channel " << mActiveTextureChannels << LL_ENDL;
+            mActiveTextureChannels++;
+        }
+        else
+        {
+            //is array of textures, make sequential after this texture
+            GLint channel[32]; // <=== only support up to 32 texture channels
+            llassert(size <= 32);
+            size = llmin(size, 32);
+            for (int i = 0; i < size; ++i)
+            {
+                channel[i] = mActiveTextureChannels++;
+            }
+            glUniform1ivARB(location, size, channel);
+            LL_DEBUGS("ShaderUniform") << "Assigned to texture channel " << 
+                (mActiveTextureChannels-size) << " through " << (mActiveTextureChannels-1) << LL_ENDL;
+        }
+
+        llassert(mActiveTextureChannels <= 32); // too many textures (probably)
+        return ret;
     }
     return -1;
 }
diff --git a/indra/llrender/llglslshader.h b/indra/llrender/llglslshader.h
index 85e83dbcb99..7d6b341d3d1 100644
--- a/indra/llrender/llglslshader.h
+++ b/indra/llrender/llglslshader.h
@@ -207,7 +207,7 @@ class LLGLSLShader
 	GLint getUniformLocation(U32 index);
 
 	GLint getAttribLocation(U32 attrib);
-	GLint mapUniformTextureChannel(GLint location, GLenum type);
+	GLint mapUniformTextureChannel(GLint location, GLenum type, GLint size);
 	
     void clearPermutations();
 	void addPermutation(std::string name, std::string value);
diff --git a/indra/llrender/llimagegl.h b/indra/llrender/llimagegl.h
index 4d5b60d6bc7..b419c9fab56 100644
--- a/indra/llrender/llimagegl.h
+++ b/indra/llrender/llimagegl.h
@@ -161,7 +161,7 @@ class LLImageGL : public LLRefCount
 
 	BOOL getUseMipMaps() const { return mUseMipMaps; }
 	void setUseMipMaps(BOOL usemips) { mUseMipMaps = usemips; }	
-
+    void setHasMipMaps(BOOL hasmips) { mHasMipMaps = hasmips; }
 	void updatePickMask(S32 width, S32 height, const U8* data_in);
 	BOOL getMask(const LLVector2 &tc);
 
diff --git a/indra/llrender/llrendertarget.cpp b/indra/llrender/llrendertarget.cpp
index 04080105131..0a4cc1bcf55 100644
--- a/indra/llrender/llrendertarget.cpp
+++ b/indra/llrender/llrendertarget.cpp
@@ -119,6 +119,7 @@ void LLRenderTarget::resize(U32 resx, U32 resy)
 
 bool LLRenderTarget::allocate(U32 resx, U32 resy, U32 color_fmt, bool depth, bool stencil, LLTexUnit::eTextureType usage, bool use_fbo, S32 samples)
 {
+    LL_PROFILE_ZONE_SCOPED_CATEGORY_DISPLAY;
 	resx = llmin(resx, (U32) gGLManager.mGLMaxTextureSize);
 	resy = llmin(resy, (U32) gGLManager.mGLMaxTextureSize);
 
@@ -219,6 +220,7 @@ void LLRenderTarget::releaseColorAttachment()
 
 bool LLRenderTarget::addColorAttachment(U32 color_fmt)
 {
+    LL_PROFILE_ZONE_SCOPED_CATEGORY_DISPLAY;
 	if (color_fmt == 0)
 	{
 		return true;
@@ -315,6 +317,7 @@ bool LLRenderTarget::addColorAttachment(U32 color_fmt)
 
 bool LLRenderTarget::allocateDepth()
 {
+    LL_PROFILE_ZONE_SCOPED_CATEGORY_DISPLAY;
 	if (mStencil)
 	{
 		//use render buffers where stencil buffers are in play
@@ -395,6 +398,7 @@ void LLRenderTarget::shareDepthBuffer(LLRenderTarget& target)
 
 void LLRenderTarget::release()
 {
+    LL_PROFILE_ZONE_SCOPED_CATEGORY_DISPLAY;
 	if (mDepth)
 	{
 		if (mStencil)
diff --git a/indra/newview/CMakeLists.txt b/indra/newview/CMakeLists.txt
index 5e50f585953..2bd231bcdc2 100644
--- a/indra/newview/CMakeLists.txt
+++ b/indra/newview/CMakeLists.txt
@@ -196,7 +196,6 @@ set(viewer_SOURCE_FILES
     lldynamictexture.cpp
     llemote.cpp
     llenvironment.cpp
-    llenvironmentmap.cpp
     llestateinfomodel.cpp
     lleventnotifier.cpp
     lleventpoll.cpp
@@ -546,6 +545,8 @@ set(viewer_SOURCE_FILES
     llproductinforequest.cpp
     llprogressview.cpp
     llrecentpeople.cpp
+    llreflectionmap.cpp
+    llreflectionmapmanager.cpp
     llregioninfomodel.cpp
     llregionposition.cpp
     llremoteparcelrequest.cpp
@@ -833,7 +834,6 @@ set(viewer_HEADER_FILES
     lldynamictexture.h
     llemote.h
     llenvironment.h
-    llenvironmentmap.h
     llestateinfomodel.h
     lleventnotifier.h
     lleventpoll.h
@@ -1171,6 +1171,8 @@ set(viewer_HEADER_FILES
     llproductinforequest.h
     llprogressview.h
     llrecentpeople.h
+    llreflectionmap.h
+    llreflectionmapmanager.h
     llregioninfomodel.h
     llregionposition.h
     llremoteparcelrequest.h
diff --git a/indra/newview/app_settings/shaders/class2/deferred/softenLightF.glsl b/indra/newview/app_settings/shaders/class2/deferred/softenLightF.glsl
index 6958841d05b..5bb64e18a75 100644
--- a/indra/newview/app_settings/shaders/class2/deferred/softenLightF.glsl
+++ b/indra/newview/app_settings/shaders/class2/deferred/softenLightF.glsl
@@ -28,6 +28,8 @@
 
 /*[EXTRA_CODE_HERE]*/
 
+#define REFMAP_COUNT 8
+
 #ifdef DEFINE_GL_FRAGCOLOR
 out vec4 frag_color;
 #else
@@ -40,9 +42,13 @@ uniform sampler2DRect normalMap;
 uniform sampler2DRect lightMap;
 uniform sampler2DRect depthMap;
 uniform samplerCube   environmentMap;
-uniform samplerCube   reflectionMap;
+uniform samplerCube   reflectionMap[REFMAP_COUNT];
 uniform sampler2D     lightFunc;
 
+uniform int refmapCount;
+
+uniform vec3 refOrigin[REFMAP_COUNT];
+
 uniform float blur_size;
 uniform float blur_fidelity;
 
@@ -74,8 +80,136 @@ vec3 srgb_to_linear(vec3 c);
 vec4 applyWaterFogView(vec3 pos, vec4 color);
 #endif
 
+
+
+// from https://www.scratchapixel.com/lessons/3d-basic-rendering/minimal-ray-tracer-rendering-simple-shapes/ray-sphere-intersection
+
+// original reference implementation:
+/*
+bool intersect(const Ray &ray) const 
+{ 
+        float t0, t1; // solutions for t if the ray intersects 
+#if 0 
+        // geometric solution
+        Vec3f L = center - orig; 
+        float tca = L.dotProduct(dir); 
+        // if (tca < 0) return false;
+        float d2 = L.dotProduct(L) - tca * tca; 
+        if (d2 > radius2) return false; 
+        float thc = sqrt(radius2 - d2); 
+        t0 = tca - thc; 
+        t1 = tca + thc; 
+#else 
+        // analytic solution
+        Vec3f L = orig - center; 
+        float a = dir.dotProduct(dir); 
+        float b = 2 * dir.dotProduct(L); 
+        float c = L.dotProduct(L) - radius2; 
+        if (!solveQuadratic(a, b, c, t0, t1)) return false; 
+#endif 
+        if (t0 > t1) std::swap(t0, t1); 
+ 
+        if (t0 < 0) { 
+            t0 = t1; // if t0 is negative, let's use t1 instead 
+            if (t0 < 0) return false; // both t0 and t1 are negative 
+        } 
+ 
+        t = t0; 
+ 
+        return true; 
+} */
+
+// adapted -- assume that origin is inside sphere, return distance from origin to edge of sphere
+float sphereIntersect(vec3 origin, vec3 dir, vec4 sph )
+{ 
+        float t0, t1; // solutions for t if the ray intersects 
+
+        vec3 center = sph.xyz;
+        float radius2 = sph.w * sph.w;
+
+        vec3 L = center - origin; 
+        float tca = dot(L,dir);
+
+        float d2 = dot(L,L) - tca * tca; 
+
+        float thc = sqrt(radius2 - d2); 
+        t0 = tca - thc; 
+        t1 = tca + thc; 
+ 
+        return t1; 
+} 
+
+vec3 sampleRefMap(vec3 pos, vec3 dir, float lod)
+{
+    float wsum = 0.0;
+
+    vec3 col = vec3(0,0,0);
+
+    for (int i = 0; i < refmapCount; ++i)
+    //int i = 0;
+    {
+        float r = 16.0;
+        vec3 delta = pos.xyz-refOrigin[i].xyz;
+        if (length(delta) < r)
+        {
+            float w = 1.0/max(dot(delta, delta), r);
+            w *= w;
+            w *= w;
+
+            // parallax adjustment
+            float d = sphereIntersect(pos, dir, vec4(refOrigin[i].xyz, r));
+
+            {
+                vec3 v = pos + dir * d;
+                v -= refOrigin[i].xyz;
+                v = env_mat * v;
+
+                float min_lod = textureQueryLod(reflectionMap[i],v).y; // lower is higher res
+                col += textureLod(reflectionMap[i], v, max(min_lod, lod)).rgb*w;
+                wsum += w;
+            }
+        }
+    }
+
+    if (wsum > 0.0)
+    {
+        col *= 1.0/wsum;
+    }
+    else
+    {
+        // this pixel not covered by a probe, fallback to "full scene" environment map
+        vec3 v = env_mat * dir;
+        float min_lod = textureQueryLod(environmentMap, v).y; // lower is higher res
+        col = textureLod(environmentMap, v, max(min_lod, lod)).rgb;
+    }
+    
+    return col;
+}
+
+vec3 sampleAmbient(vec3 pos, vec3 dir, float lod)
+{
+    vec3 col = sampleRefMap(pos, dir, lod);
+
+    //desaturate
+    vec3 hcol = col *0.5;
+    
+    col *= 2.0;
+    col = vec3(
+        col.r + hcol.g + hcol.b,
+        col.g + hcol.r + hcol.b,
+        col.b + hcol.r + hcol.g
+    );
+    
+    col *= 0.333333;
+
+    return col*0.6; // fudge darker
+
+}
+
 void main()
 {
+    float reflection_lods = 11; // TODO -- base this on resolution of reflection map instead of hard coding
+
     vec2  tc           = vary_fragcoord.xy;
     float depth        = texture2DRect(depthMap, tc.xy).r;
     vec4  pos          = getPositionWithDepth(tc, depth);
@@ -106,25 +240,29 @@ void main()
     vec3 atten;
     calcAtmosphericVars(pos.xyz, light_dir, ambocc, sunlit, amblit, additive, atten, true);
 
+    //vec3 amb_vec = env_mat * norm.xyz;
+
+    vec3 ambenv = sampleAmbient(pos.xyz, norm.xyz, reflection_lods-1);
+    amblit = mix(ambenv, amblit, amblit);
     color.rgb = amblit;
+    
 
-    float ambient = min(abs(dot(norm.xyz, sun_dir.xyz)), 1.0);
-    ambient *= 0.5;
-    ambient *= ambient;
-    ambient = (1.0 - ambient);
-    color.rgb *= ambient;
+    //float ambient = min(abs(dot(norm.xyz, sun_dir.xyz)), 1.0);
+    //ambient *= 0.5;
+    //ambient *= ambient;
+    //ambient = (1.0 - ambient);
+    //color.rgb *= ambient;
 
     vec3 sun_contrib = min(da, scol) * sunlit;
     color.rgb += sun_contrib;
     color.rgb *= diffuse.rgb;
 
-    vec3 refnormpersp = normalize(reflect(pos.xyz, norm.xyz));
+    vec3 refnormpersp = reflect(pos.xyz, norm.xyz);
 
     vec3 env_vec         = env_mat * refnormpersp;
-
     if (spec.a > 0.0)  // specular reflection
     {
-        float sa        = dot(refnormpersp, light_dir.xyz);
+        float sa        = dot(normalize(refnormpersp), light_dir.xyz);
         vec3  dumbshiny = sunlit * scol * (texture2D(lightFunc, vec2(sa, spec.a)).r);
 
         // add the two types of shiny together
@@ -133,28 +271,30 @@ void main()
         color.rgb += spec_contrib;
 
         // add reflection map - EXPERIMENTAL WORK IN PROGRESS
-        float reflection_lods = 11; // TODO -- base this on resolution of reflection map instead of hard coding
-        float min_lod = textureQueryLod(reflectionMap,env_vec).y; // lower is higher res
-
-        //vec3 reflected_color = texture(reflectionMap, env_vec, (1.0-spec.a)*reflection_lod).rgb;
-        vec3 reflected_color = textureLod(reflectionMap, env_vec, max(min_lod, (1.0-spec.a)*reflection_lods)).rgb;
-        //vec3 reflected_color = texture(reflectionMap, env_vec).rgb;
-        //vec3 reflected_color = normalize(env_vec)*0.5+0.5;
-        reflected_color *= spec.rgb;
+        
+        float lod = (1.0-spec.a)*reflection_lods;
+        vec3 reflected_color = sampleRefMap(pos.xyz, normalize(refnormpersp), lod);
+        reflected_color *= 0.5; // fudge darker, not sure where there's a multiply by two and it's late
+        float fresnel = 1.0+dot(normalize(pos.xyz), norm.xyz);
+        fresnel += spec.a;
+        fresnel *= fresnel;
+        //fresnel *= spec.a;
+        reflected_color *= spec.rgb*min(fresnel, 1.0);
+        //reflected_color = srgb_to_linear(reflected_color);
         vec3 mixer = clamp(color.rgb + vec3(1,1,1) - spec.rgb, vec3(0,0,0), vec3(1,1,1));
-
-        color.rgb = mix(reflected_color*sqrt(spec.a*0.8), color, mixer);
-                         
-        //color.rgb = mix(reflected_color * spec.rgb * sqrt(spec.a*0.8), color.rgb, color.rgb);
-        //color.rgb += reflected_color * spec.rgb; // * sqrt(spec.a*0.8), color.rgb, color.rgb);
+        color.rgb = mix(reflected_color, color, mixer);
     }
 
     color.rgb = mix(color.rgb, diffuse.rgb, diffuse.a);
 
     if (envIntensity > 0.0)
     {  // add environmentmap
-        vec3 reflected_color = textureCube(environmentMap, env_vec).rgb;
-        color                = mix(color.rgb, reflected_color, envIntensity);
+        vec3 reflected_color = sampleRefMap(pos.xyz, normalize(refnormpersp), 0.0);
+        float fresnel = 1.0+dot(normalize(pos.xyz), norm.xyz);
+        fresnel *= fresnel;
+        fresnel = fresnel * 0.95 + 0.05;
+        reflected_color *= fresnel;
+        color = mix(color.rgb, reflected_color, envIntensity);
     }
 
     if (norm.w < 0.5)
@@ -172,6 +312,4 @@ void main()
     // convert to linear as fullscreen lights need to sum in linear colorspace
     // and will be gamma (re)corrected downstream...
     frag_color.rgb = srgb_to_linear(color.rgb);
-    //frag_color.r = 1.0;
-    frag_color.a   = bloom;
 }
diff --git a/indra/newview/llappviewer.cpp b/indra/newview/llappviewer.cpp
index a63203f3cba..97485797604 100644
--- a/indra/newview/llappviewer.cpp
+++ b/indra/newview/llappviewer.cpp
@@ -1512,22 +1512,23 @@ bool LLAppViewer::doFrame()
 
 			// Render scene.
 			// *TODO: Should we run display() even during gHeadlessClient?  DK 2011-02-18
-			if (!LLApp::isExiting() && !gHeadlessClient && gViewerWindow)
-			{
-				LL_PROFILE_ZONE_NAMED_CATEGORY_APP( "df Display" )
-				pingMainloopTimeout("Main:Display");
-				gGLActive = TRUE;
+            if (!LLApp::isExiting() && !gHeadlessClient && gViewerWindow)
+            {
+                LL_PROFILE_ZONE_NAMED_CATEGORY_APP("df Display");
+                pingMainloopTimeout("Main:Display");
+                gGLActive = TRUE;
 
-				display();
+                display();
 
-				{
-					LL_PROFILE_ZONE_NAMED_CATEGORY_APP( "df Snapshot" )
-				pingMainloopTimeout("Main:Snapshot");
-				LLFloaterSnapshot::update(); // take snapshots
-					LLFloaterOutfitSnapshot::update();
-				gGLActive = FALSE;
-			}
-		}
+                {
+                    LL_PROFILE_ZONE_NAMED_CATEGORY_APP("df Snapshot");
+                    pingMainloopTimeout("Main:Snapshot");
+                    gPipeline.mReflectionMapManager.update();
+                    LLFloaterSnapshot::update(); // take snapshots
+                    LLFloaterOutfitSnapshot::update();
+                    gGLActive = FALSE;
+                }
+            }
 		}
 
 		{
diff --git a/indra/newview/llenvironmentmap.cpp b/indra/newview/llenvironmentmap.cpp
deleted file mode 100644
index ee185d8ce77..00000000000
--- a/indra/newview/llenvironmentmap.cpp
+++ /dev/null
@@ -1,116 +0,0 @@
-/**
- * @file llenvironmentmap.cpp
- * @brief LLEnvironmentMap class implementation
- *
- * $LicenseInfo:firstyear=2022&license=viewerlgpl$
- * Second Life Viewer Source Code
- * Copyright (C) 2022, 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 "llenvironmentmap.h"
-#include "pipeline.h"
-#include "llviewerwindow.h"
-
-LLEnvironmentMap::LLEnvironmentMap()
-{
-    mOrigin.setVec(0, 0, 0);
-}
-
-void LLEnvironmentMap::update(const LLVector3& origin, U32 resolution)
-{
-    LL_PROFILE_ZONE_SCOPED_CATEGORY_DISPLAY;
-
-    mOrigin = origin;
-
-    // allocate images
-    std::vector<LLPointer<LLImageRaw> > rawimages;
-    rawimages.reserve(6);
-
-    for (int i = 0; i < 6; ++i)
-    {
-        rawimages.push_back(new LLImageRaw(resolution, resolution, 3));
-    }
-
-    // ============== modified copy/paste of LLFloater360Capture::capture360Images() follows ==============
-
-    // these are the 6 directions we will point the camera, see LLCubeMap::mTargets
-    LLVector3 look_dirs[6] = {
-        LLVector3(-1, 0, 0),
-        LLVector3(1, 0, 0),
-        LLVector3(0, -1, 0),
-        LLVector3(0, 1, 0),
-        LLVector3(0, 0, -1),
-        LLVector3(0, 0, 1)
-    };
-
-    LLVector3 look_upvecs[6] = { 
-        LLVector3(0, -1, 0), 
-        LLVector3(0, -1, 0), 
-        LLVector3(0, 0, -1), 
-        LLVector3(0, 0, 1), 
-        LLVector3(0, -1, 0), 
-        LLVector3(0, -1, 0) 
-    };
-
-    // save current view/camera settings so we can restore them afterwards
-    S32 old_occlusion = LLPipeline::sUseOcclusion;
-
-    // set new parameters specific to the 360 requirements
-    LLPipeline::sUseOcclusion = 0;
-    LLViewerCamera* camera = LLViewerCamera::getInstance();
-    LLVector3 old_origin = camera->getOrigin();
-    F32 old_fov = camera->getView();
-    F32 old_aspect = camera->getAspect();
-    F32 old_yaw = camera->getYaw();
-
-    // camera constants for the square, cube map capture image
-    camera->setAspect(1.0); // must set aspect ratio first to avoid undesirable clamping of vertical FoV
-    camera->setView(F_PI_BY_TWO);
-    camera->yaw(0.0);
-    camera->setOrigin(mOrigin);
-
-    // for each of the 6 directions we shoot...
-    for (int i = 0; i < 6; i++)
-    {
-        // set up camera to look in each direction
-        camera->lookDir(look_dirs[i], look_upvecs[i]);
-
-        // call the (very) simplified snapshot code that simply deals
-        // with a single image, no sub-images etc. but is very fast
-        gViewerWindow->simpleSnapshot(rawimages[i],
-            resolution, resolution, 1);
-    }
-
-    // restore original view/camera/avatar settings settings
-    camera->setAspect(old_aspect);
-    camera->setView(old_fov);
-    camera->yaw(old_yaw);
-    camera->setOrigin(old_origin);
-
-    LLPipeline::sUseOcclusion = old_occlusion;
-
-    // ====================================================
-    
-    mCubeMap = new LLCubeMap(false);
-    mCubeMap->initEnvironmentMap(rawimages);
-}
-
diff --git a/indra/newview/llreflectionmap.cpp b/indra/newview/llreflectionmap.cpp
new file mode 100644
index 00000000000..b75dba877ee
--- /dev/null
+++ b/indra/newview/llreflectionmap.cpp
@@ -0,0 +1,64 @@
+/**
+ * @file llreflectionmap.cpp
+ * @brief LLReflectionMap class implementation
+ *
+ * $LicenseInfo:firstyear=2022&license=viewerlgpl$
+ * Second Life Viewer Source Code
+ * Copyright (C) 2022, 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 "llreflectionmap.h"
+#include "pipeline.h"
+#include "llviewerwindow.h"
+
+LLReflectionMap::LLReflectionMap()
+{
+}
+
+void LLReflectionMap::update(const LLVector3& origin, U32 resolution)
+{
+    LL_PROFILE_ZONE_SCOPED_CATEGORY_DISPLAY;
+    llassert(LLPipeline::sRenderDeferred);
+
+    // make sure resolution is < gPipeline.mDeferredScreen.getWidth()
+
+    while (resolution > gPipeline.mDeferredScreen.getWidth() ||
+        resolution > gPipeline.mDeferredScreen.getHeight())
+    {
+        resolution /= 2;
+    }
+
+    if (resolution == 0)
+    {
+        return;
+    }
+
+    mOrigin.load3(origin.mV);
+
+    mCubeMap = new LLCubeMap(false);
+    mCubeMap->initReflectionMap(resolution);
+
+    gViewerWindow->cubeSnapshot(origin, mCubeMap);
+
+    mCubeMap->generateMipMaps();
+}
+
diff --git a/indra/newview/llenvironmentmap.h b/indra/newview/llreflectionmap.h
similarity index 87%
rename from indra/newview/llenvironmentmap.h
rename to indra/newview/llreflectionmap.h
index 7d951eb678c..7d39e7e562d 100644
--- a/indra/newview/llenvironmentmap.h
+++ b/indra/newview/llreflectionmap.h
@@ -1,6 +1,6 @@
 /**
- * @file llenvironmentmap.h
- * @brief LLEnvironmentMap class declaration
+ * @file llreflectionmap.h
+ * @brief LLReflectionMap class declaration
  *
  * $LicenseInfo:firstyear=2022&license=viewerlgpl$
  * Second Life Viewer Source Code
@@ -28,21 +28,24 @@
 
 #include "llcubemap.h"
 
-class LLEnvironmentMap
+class LLReflectionMap : public LLRefCount
 {
 public:
     // allocate an environment map of the given resolution 
-    LLEnvironmentMap();
+    LLReflectionMap();
 
     // update this environment map
     // origin - position in agent space to generate environment map from in agent space
     // resolution - size of cube map to generate
     void update(const LLVector3& origin, U32 resolution);
     
+    // point at which environment map was generated from (in agent space)
+    LLVector4a mOrigin;
+
+    // distance from viewer camera
+    F32 mDistance;
+
     // cube map used to sample this environment map
     LLPointer<LLCubeMap> mCubeMap;
-
-    // point at which environment map was generated from (in agent space)
-    LLVector3 mOrigin;
 };
 
diff --git a/indra/newview/llreflectionmapmanager.cpp b/indra/newview/llreflectionmapmanager.cpp
new file mode 100644
index 00000000000..95b72e1d3b0
--- /dev/null
+++ b/indra/newview/llreflectionmapmanager.cpp
@@ -0,0 +1,106 @@
+/**
+ * @file llreflectionmapmanager.cpp
+ * @brief LLReflectionMapManager class implementation
+ *
+ * $LicenseInfo:firstyear=2022&license=viewerlgpl$
+ * Second Life Viewer Source Code
+ * Copyright (C) 2022, 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 "llreflectionmapmanager.h"
+#include "llviewercamera.h"
+
+LLReflectionMapManager::LLReflectionMapManager()
+{
+
+}
+
+struct CompareReflectionMapDistance
+{
+    
+};
+
+
+struct CompareProbeDistance
+{
+    bool operator()(const LLReflectionMap& lhs, const LLReflectionMap& rhs)
+    {
+        return lhs.mDistance < rhs.mDistance;
+    }
+};
+
+// helper class to seed octree with probes
+void LLReflectionMapManager::update()
+{
+    LL_PROFILE_ZONE_SCOPED_CATEGORY_DISPLAY;
+
+    // naively drop probes every 16m as we move the camera around for now
+    // later, use LLSpatialPartition to manage probes
+    const F32 PROBE_SPACING = 16.f;
+    const U32 MAX_PROBES = 8;
+
+    LLVector4a camera_pos;
+    camera_pos.load3(LLViewerCamera::instance().getOrigin().mV);
+
+    for (auto& probe : mProbes)
+    {
+        LLVector4a d;
+        d.setSub(camera_pos, probe.mOrigin);
+        probe.mDistance = d.getLength3().getF32();
+    }
+
+    if (mProbes.empty() || mProbes[0].mDistance > PROBE_SPACING)
+    {
+        addProbe(LLViewerCamera::instance().getOrigin());
+    }
+
+    // update distance to camera for all probes
+    std::sort(mProbes.begin(), mProbes.end(), CompareProbeDistance());
+
+    if (mProbes.size() > MAX_PROBES)
+    {
+        mProbes.resize(MAX_PROBES);
+    }
+}
+
+void LLReflectionMapManager::addProbe(const LLVector3& pos)
+{
+    LLReflectionMap probe;
+    probe.update(pos, 1024);
+    mProbes.push_back(probe);
+}
+
+void LLReflectionMapManager::getReflectionMaps(std::vector<LLReflectionMap*>& maps)
+{
+    // just null out for now
+    U32 i = 0;
+    for (i = 0; i < maps.size() && i < mProbes.size(); ++i)
+    {
+        maps[i] = &(mProbes[i]);
+    }
+
+    for (++i; i < maps.size(); ++i)
+    {
+        maps[i] = nullptr;
+    }
+}
+
diff --git a/indra/newview/llreflectionmapmanager.h b/indra/newview/llreflectionmapmanager.h
new file mode 100644
index 00000000000..40e925d9168
--- /dev/null
+++ b/indra/newview/llreflectionmapmanager.h
@@ -0,0 +1,51 @@
+/**
+ * @file llreflectionmapmanager.h
+ * @brief LLReflectionMapManager class declaration
+ *
+ * $LicenseInfo:firstyear=2022&license=viewerlgpl$
+ * Second Life Viewer Source Code
+ * Copyright (C) 2022, 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 "llreflectionmap.h"
+
+class LLReflectionMapManager
+{
+public:
+    // allocate an environment map of the given resolution 
+    LLReflectionMapManager();
+    
+    // maintain reflection probes
+    void update();
+
+    // drop a reflection probe at the specified position in agent space
+    void addProbe(const LLVector3& pos);
+
+    // Populate "maps" with the N most relevant Reflection Maps where N is no more than maps.size()
+    // If less than maps.size() ReflectionMaps are available, will assign trailing elements to nullptr.
+    //  maps -- presized array of Reflection Map pointers
+    void getReflectionMaps(std::vector<LLReflectionMap*>& maps);
+
+    // list of active reflection maps
+    std::vector<LLReflectionMap> mProbes;
+};
+
diff --git a/indra/newview/llviewerwindow.cpp b/indra/newview/llviewerwindow.cpp
index 42e21f71304..f562a9458b2 100644
--- a/indra/newview/llviewerwindow.cpp
+++ b/indra/newview/llviewerwindow.cpp
@@ -5170,6 +5170,7 @@ BOOL LLViewerWindow::rawSnapshot(LLImageRaw *raw, S32 image_width, S32 image_hei
 
 BOOL LLViewerWindow::simpleSnapshot(LLImageRaw* raw, S32 image_width, S32 image_height, const int num_render_passes)
 {
+    LL_PROFILE_ZONE_SCOPED_CATEGORY_APP;
     gDisplaySwapBuffers = FALSE;
 
     glClear(GL_DEPTH_BUFFER_BIT | GL_COLOR_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);
@@ -5266,6 +5267,152 @@ BOOL LLViewerWindow::simpleSnapshot(LLImageRaw* raw, S32 image_width, S32 image_
     return true;
 }
 
+BOOL LLViewerWindow::cubeSnapshot(const LLVector3& origin, LLCubeMap* cubemap)
+{
+    // NOTE: implementation derived from LLFloater360Capture::capture360Images() and simpleSnapshot
+    LL_PROFILE_ZONE_SCOPED_CATEGORY_APP;
+    llassert(LLPipeline::sRenderDeferred);
+    
+    U32 res = cubemap->getResolution();
+
+    llassert(res <= gPipeline.mDeferredScreen.getWidth());
+    llassert(res <= gPipeline.mDeferredScreen.getHeight());
+
+    
+
+    // save current view/camera settings so we can restore them afterwards
+    S32 old_occlusion = LLPipeline::sUseOcclusion;
+
+    // set new parameters specific to the 360 requirements
+    LLPipeline::sUseOcclusion = 0;
+    LLViewerCamera* camera = LLViewerCamera::getInstance();
+    LLVector3 old_origin = camera->getOrigin();
+    F32 old_fov = camera->getView();
+    F32 old_aspect = camera->getAspect();
+    F32 old_yaw = camera->getYaw();
+
+    // camera constants for the square, cube map capture image
+    camera->setAspect(1.0); // must set aspect ratio first to avoid undesirable clamping of vertical FoV
+    camera->setView(F_PI_BY_TWO);
+    camera->yaw(0.0);
+    camera->setOrigin(origin);
+
+    gDisplaySwapBuffers = FALSE;
+
+    glClear(GL_DEPTH_BUFFER_BIT | GL_COLOR_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);
+    
+    BOOL prev_draw_ui = gPipeline.hasRenderDebugFeatureMask(LLPipeline::RENDER_DEBUG_FEATURE_UI) ? TRUE : FALSE;
+    if (prev_draw_ui != false)
+    {
+        LLPipeline::toggleRenderDebugFeature(LLPipeline::RENDER_DEBUG_FEATURE_UI);
+    }
+
+    LLPipeline::sShowHUDAttachments = FALSE;
+    LLRect window_rect = getWorldViewRectRaw();
+
+    LLRenderTarget scratch_space;  // TODO: hold onto "scratch space" render target and allocate oncer per session (allocate takes > 1ms)
+    U32 color_fmt = GL_RGBA;
+    const bool use_depth_buffer = true;
+    const bool use_stencil_buffer = true;
+    if (scratch_space.allocate(res, res, color_fmt, use_depth_buffer, use_stencil_buffer))
+    {
+        mWorldViewRectRaw.set(0, res, res, 0);
+        scratch_space.bindTarget();
+    }
+    else
+    {
+        return FALSE;
+    }
+
+    // "target" parameter of glCopyTexImage2D for each side of cubemap
+    U32 targets[6] = {
+        GL_TEXTURE_CUBE_MAP_NEGATIVE_X_ARB,
+        GL_TEXTURE_CUBE_MAP_POSITIVE_X_ARB,
+        GL_TEXTURE_CUBE_MAP_NEGATIVE_Y_ARB,
+        GL_TEXTURE_CUBE_MAP_POSITIVE_Y_ARB,
+        GL_TEXTURE_CUBE_MAP_NEGATIVE_Z_ARB,
+        GL_TEXTURE_CUBE_MAP_POSITIVE_Z_ARB
+    };
+
+    // these are the 6 directions we will point the camera, see LLCubeMap::mTargets
+    LLVector3 look_dirs[6] = {
+        LLVector3(-1, 0, 0),
+        LLVector3(1, 0, 0),
+        LLVector3(0, -1, 0),
+        LLVector3(0, 1, 0),
+        LLVector3(0, 0, -1),
+        LLVector3(0, 0, 1)
+    };
+
+    LLVector3 look_upvecs[6] = {
+        LLVector3(0, -1, 0),
+        LLVector3(0, -1, 0),
+        LLVector3(0, 0, -1),
+        LLVector3(0, 0, 1),
+        LLVector3(0, -1, 0),
+        LLVector3(0, -1, 0)
+    };
+
+    // for each of six sides of cubemap
+    for (int i = 0; i < 6; ++i)
+    {
+        // set up camera to look in each direction
+        camera->lookDir(look_dirs[i], look_upvecs[i]);
+
+        // turning this flag off here prohibits the screen swap
+        // to present the new page to the viewer - this stops
+        // the black flash in between captures when the number
+        // of render passes is more than 1. We need to also
+        // set it here because code in LLViewerDisplay resets
+        // it to TRUE each time.
+        gDisplaySwapBuffers = FALSE;
+
+        // actually render the scene
+        const U32 subfield = 0;
+        const bool do_rebuild = true;
+        const F32 zoom = 1.0;
+        const bool for_snapshot = TRUE;
+        display(do_rebuild, zoom, subfield, for_snapshot);
+
+        // copy results to cube map face
+        cubemap->enable(0);
+        cubemap->bind();
+        glCopyTexImage2D(targets[i], 0, GL_RGB, 0, 0, res, res, 0);
+        gGL.getTexUnit(0)->disable();
+        cubemap->disable();
+    }
+
+    gDisplaySwapBuffers = FALSE;
+    gDepthDirty = TRUE;
+
+    if (!gPipeline.hasRenderDebugFeatureMask(LLPipeline::RENDER_DEBUG_FEATURE_UI))
+    {
+        if (prev_draw_ui != false)
+        {
+            LLPipeline::toggleRenderDebugFeature(LLPipeline::RENDER_DEBUG_FEATURE_UI);
+        }
+    }
+
+    LLPipeline::sShowHUDAttachments = TRUE;
+
+    gPipeline.resetDrawOrders();
+    mWorldViewRectRaw = window_rect;
+    scratch_space.flush();
+    scratch_space.release();
+    
+    // restore original view/camera/avatar settings settings
+    camera->setAspect(old_aspect);
+    camera->setView(old_fov);
+    camera->yaw(old_yaw);
+    camera->setOrigin(old_origin);
+
+    LLPipeline::sUseOcclusion = old_occlusion;
+
+    // ====================================================
+
+    return true;
+}
+
 void LLViewerWindow::destroyWindow()
 {
 	if (mWindow)
diff --git a/indra/newview/llviewerwindow.h b/indra/newview/llviewerwindow.h
index 979a5605086..be4e9a17a53 100644
--- a/indra/newview/llviewerwindow.h
+++ b/indra/newview/llviewerwindow.h
@@ -68,6 +68,7 @@ class LLWindowListener;
 class LLViewerWindowListener;
 class LLVOPartGroup;
 class LLPopupView;
+class LLCubeMap;
 
 #define PICK_HALF_WIDTH 5
 #define PICK_DIAMETER (2 * PICK_HALF_WIDTH + 1)
@@ -360,6 +361,17 @@ class LLViewerWindow : public LLWindowCallbacks
 
     BOOL			simpleSnapshot(LLImageRaw *raw, S32 image_width, S32 image_height, const int num_render_passes);
 
+    
+    
+    // take a cubemap snapshot
+    // origin - vantage point to take the snapshot from
+    // cubemap - cubemap to store the results
+    BOOL cubeSnapshot(const LLVector3& origin, LLCubeMap* cubemap);
+
+    
+    // special implementation of simpleSnapshot for reflection maps
+    BOOL			reflectionSnapshot(LLImageRaw* raw, S32 image_width, S32 image_height, const int num_render_passes);
+
     BOOL			thumbnailSnapshot(LLImageRaw *raw, S32 preview_width, S32 preview_height, BOOL show_ui, BOOL show_hud, BOOL do_rebuild, LLSnapshotModel::ESnapshotLayerType type);
 	BOOL			isSnapshotLocSet() const;
 	void			resetSnapshotLoc() const;
diff --git a/indra/newview/pipeline.cpp b/indra/newview/pipeline.cpp
index ac7276e1e08..e93ff95ed4b 100644
--- a/indra/newview/pipeline.cpp
+++ b/indra/newview/pipeline.cpp
@@ -767,42 +767,16 @@ void LLPipeline::allocatePhysicsBuffer()
 
 bool LLPipeline::allocateScreenBuffer(U32 resX, U32 resY)
 {
-	refreshCachedSettings();
-	
-	bool save_settings = sRenderDeferred;
-	if (save_settings)
-	{
-		// Set this flag in case we crash while resizing window or allocating space for deferred rendering targets
-		gSavedSettings.setBOOL("RenderInitError", TRUE);
-		gSavedSettings.saveToFile( gSavedSettings.getString("ClientSettingsFile"), TRUE );
-	}
-
+    LL_PROFILE_ZONE_SCOPED_CATEGORY_DISPLAY;
 	eFBOStatus ret = doAllocateScreenBuffer(resX, resY);
 
-	if (save_settings)
-	{
-		// don't disable shaders on next session
-		gSavedSettings.setBOOL("RenderInitError", FALSE);
-		gSavedSettings.saveToFile( gSavedSettings.getString("ClientSettingsFile"), TRUE );
-	}
-	
-	if (ret == FBO_FAILURE)
-	{ //FAILSAFE: screen buffer allocation failed, disable deferred rendering if it's enabled
-		//NOTE: if the session closes successfully after this call, deferred rendering will be 
-		// disabled on future sessions
-		if (LLPipeline::sRenderDeferred)
-		{
-			gSavedSettings.setBOOL("RenderDeferred", FALSE);
-			LLPipeline::refreshCachedSettings();
-		}
-	}
-
 	return ret == FBO_SUCCESS_FULLRES;
 }
 
 
 LLPipeline::eFBOStatus LLPipeline::doAllocateScreenBuffer(U32 resX, U32 resY)
 {
+    LL_PROFILE_ZONE_SCOPED_CATEGORY_DISPLAY;
 	// try to allocate screen buffers at requested resolution and samples
 	// - on failure, shrink number of samples and try again
 	// - if not multisampled, shrink resolution and try again (favor X resolution over Y)
@@ -856,8 +830,7 @@ LLPipeline::eFBOStatus LLPipeline::doAllocateScreenBuffer(U32 resX, U32 resY)
 
 bool LLPipeline::allocateScreenBuffer(U32 resX, U32 resY, U32 samples)
 {
-	refreshCachedSettings();
-
+    LL_PROFILE_ZONE_SCOPED_CATEGORY_DISPLAY;
 	// remember these dimensions
 	mScreenWidth = resX;
 	mScreenHeight = resY;
@@ -961,8 +934,7 @@ inline U32 BlurHappySize(U32 x, F32 scale) { return U32( x * scale + 16.0f) & ~0
 
 bool LLPipeline::allocateShadowBuffer(U32 resX, U32 resY)
 {
-	refreshCachedSettings();
-	
+    LL_PROFILE_ZONE_SCOPED_CATEGORY_DISPLAY;
 	if (LLPipeline::sRenderDeferred)
 	{
 		S32 shadow_detail = RenderShadowDetail;
@@ -1053,6 +1025,7 @@ void LLPipeline::updateRenderDeferred()
 // static
 void LLPipeline::refreshCachedSettings()
 {
+    LL_PROFILE_ZONE_SCOPED_CATEGORY_DISPLAY;
 	LLPipeline::sAutoMaskAlphaDeferred = gSavedSettings.getBOOL("RenderAutoMaskAlphaDeferred");
 	LLPipeline::sAutoMaskAlphaNonDeferred = gSavedSettings.getBOOL("RenderAutoMaskAlphaNonDeferred");
 	LLPipeline::sUseFarClip = gSavedSettings.getBOOL("RenderUseFarClip");
@@ -8204,12 +8177,41 @@ void LLPipeline::bindDeferredShader(LLGLSLShader& shader, LLRenderTarget* light_
     channel = shader.enableTexture(LLShaderMgr::REFLECTION_MAP, LLTexUnit::TT_CUBE_MAP);
     if (channel > -1)
     {
-        LLCubeMap* cube_map = mEnvironmentMap.mCubeMap;
-        if (cube_map)
+        mReflectionMaps.resize(8); //TODO -- declare the number of reflection maps the shader knows about somewhere sane
+        mReflectionMapManager.getReflectionMaps(mReflectionMaps);
+        LLVector3 origin[8]; //origin of refmaps in clip space
+
+        // load modelview matrix into matrix 4a
+        LLMatrix4a modelview;
+        modelview.loadu(gGLModelView);
+        LLVector4a oa; // scratch space for transformed origin
+
+        S32 count = 0;
+        for (auto* refmap : mReflectionMaps)
+        {
+            if (refmap)
+            {
+                LLCubeMap* cubemap = refmap->mCubeMap;
+                if (cubemap)
+                {
+                    cubemap->enable(channel + count);
+                    cubemap->bind();
+
+                    modelview.affineTransform(refmap->mOrigin, oa);
+                    origin[count].set(oa.getF32ptr());
+
+                    count++;
+                }
+            }
+        }
+
+        if (count > 0)
         {
+            LLStaticHashedString refmapCount("refmapCount");
+            LLStaticHashedString refOrigin("refOrigin");
+            shader.uniform1i(refmapCount, count);
+            shader.uniform3fv(refOrigin, count, (F32*)origin);
             setup_env_mat = true;
-            cube_map->enable(channel);
-            cube_map->bind();
         }
     }
 
@@ -9125,10 +9127,14 @@ void LLPipeline::unbindDeferredShader(LLGLSLShader &shader)
     channel = shader.disableTexture(LLShaderMgr::REFLECTION_MAP, LLTexUnit::TT_CUBE_MAP);
     if (channel > -1)
     {
-        LLCubeMap* cube_map = mEnvironmentMap.mCubeMap;
-        if (cube_map)
+        for (int i = 0; i < mReflectionMaps.size(); ++i)
+        {
+            gGL.getTexUnit(channel + i)->disable();
+        }
+
+        if (channel == 0)
         {
-            cube_map->disable();
+            gGL.getTexUnit(channel)->enable(LLTexUnit::TT_TEXTURE);
         }
     }
 
@@ -11498,6 +11504,7 @@ void LLPipeline::restoreHiddenObject( const LLUUID& id )
 
 void LLPipeline::overrideEnvironmentMap()
 {
-    mEnvironmentMap.update(LLViewerCamera::instance().getOrigin(), 1024);
+    mReflectionMapManager.mProbes.clear();
+    mReflectionMapManager.addProbe(LLViewerCamera::instance().getOrigin());
 }
 
diff --git a/indra/newview/pipeline.h b/indra/newview/pipeline.h
index 150d3c7d58f..29c93c199b8 100644
--- a/indra/newview/pipeline.h
+++ b/indra/newview/pipeline.h
@@ -38,7 +38,7 @@
 #include "llgl.h"
 #include "lldrawable.h"
 #include "llrendertarget.h"
-#include "llenvironmentmap.h"
+#include "llreflectionmapmanager.h"
 
 #include <stack>
 
@@ -427,7 +427,7 @@ class LLPipeline
 	void hideObject( const LLUUID& id );
 	void restoreHiddenObject( const LLUUID& id );
 
-    LLEnvironmentMap mEnvironmentMap;
+    LLReflectionMapManager mReflectionMapManager;
     void overrideEnvironmentMap();
 
 private:
@@ -661,6 +661,9 @@ class LLPipeline
 	//utility buffer for rendering cubes, 8 vertices are corners of a cube [-1, 1]
 	LLPointer<LLVertexBuffer> mCubeVB;
 
+    //list of currently bound reflection maps
+    std::vector<LLReflectionMap*> mReflectionMaps;
+
 	//sun shadow map
 	LLRenderTarget			mShadow[6];
 	LLRenderTarget			mShadowOcclusion[6];
-- 
GitLab