From d0d1b832d4983f35ab29947eb6fda54a8aa48f8a Mon Sep 17 00:00:00 2001
From: Dave Parks <davep@lindenlab.com>
Date: Wed, 22 Jun 2022 13:25:50 -0500
Subject: [PATCH] SL-17600 Proper irradiance probes.

---
 indra/llrender/llshadermgr.cpp                |   1 +
 indra/llrender/llshadermgr.h                  |   1 +
 .../class1/interface/irradianceGenF.glsl      |  99 +++++++++++++++
 .../class1/interface/irradianceGenV.glsl      |  38 ++++++
 .../class1/interface/radianceGenF.glsl        |   5 +-
 .../class3/deferred/reflectionProbeF.glsl     | 120 +++++++++++++++---
 indra/newview/llreflectionmapmanager.cpp      |  68 +++++++++-
 indra/newview/llreflectionmapmanager.h        |   6 +-
 indra/newview/llviewershadermgr.cpp           |  12 ++
 indra/newview/llviewershadermgr.h             |   1 +
 indra/newview/pipeline.cpp                    |  14 +-
 11 files changed, 334 insertions(+), 31 deletions(-)
 create mode 100644 indra/newview/app_settings/shaders/class1/interface/irradianceGenF.glsl
 create mode 100644 indra/newview/app_settings/shaders/class1/interface/irradianceGenV.glsl

diff --git a/indra/llrender/llshadermgr.cpp b/indra/llrender/llshadermgr.cpp
index e2e1ff9714e..10939db5e49 100644
--- a/indra/llrender/llshadermgr.cpp
+++ b/indra/llrender/llshadermgr.cpp
@@ -1171,6 +1171,7 @@ void LLShaderMgr::initAttribsAndUniforms()
     mReservedUniforms.push_back("bumpMap2");
 	mReservedUniforms.push_back("environmentMap");
     mReservedUniforms.push_back("reflectionProbes");
+    mReservedUniforms.push_back("irradianceProbes");
 	mReservedUniforms.push_back("cloud_noise_texture");
     mReservedUniforms.push_back("cloud_noise_texture_next");
 	mReservedUniforms.push_back("fullbright");
diff --git a/indra/llrender/llshadermgr.h b/indra/llrender/llshadermgr.h
index d3bb2b9db41..5b19dd53d14 100644
--- a/indra/llrender/llshadermgr.h
+++ b/indra/llrender/llshadermgr.h
@@ -81,6 +81,7 @@ class LLShaderMgr
         BUMP_MAP2,                          //  "bumpMap2"
         ENVIRONMENT_MAP,                    //  "environmentMap"
         REFLECTION_PROBES,                     //  "reflectionProbes"
+        IRRADIANCE_PROBES,                     //  "irradianceProbes"
         CLOUD_NOISE_MAP,                    //  "cloud_noise_texture"
         CLOUD_NOISE_MAP_NEXT,               //  "cloud_noise_texture_next"
         FULLBRIGHT,                         //  "fullbright"
diff --git a/indra/newview/app_settings/shaders/class1/interface/irradianceGenF.glsl b/indra/newview/app_settings/shaders/class1/interface/irradianceGenF.glsl
new file mode 100644
index 00000000000..2028509775f
--- /dev/null
+++ b/indra/newview/app_settings/shaders/class1/interface/irradianceGenF.glsl
@@ -0,0 +1,99 @@
+/** 
+ * @file irradianceGenF.glsl
+ *
+ * $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$
+ */
+ 
+
+/*[EXTRA_CODE_HERE]*/
+
+
+#ifdef DEFINE_GL_FRAGCOLOR
+out vec4 frag_color;
+#else
+#define frag_color gl_FragColor
+#endif
+
+uniform samplerCubeArray   reflectionProbes;
+uniform int sourceIdx;
+
+VARYING vec3 vary_dir;
+
+// =============================================================================================================
+// Parts of this file are (c) 2018 Sascha Willems
+// SNIPPED FROM https://github.com/SaschaWillems/Vulkan-glTF-PBR/blob/master/data/shaders/irradiancecube.frag
+/*
+MIT License
+
+Copyright (c) 2018 Sascha Willems
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
+*/
+// =============================================================================================================
+
+
+
+#define PI 3.1415926535897932384626433832795
+
+float deltaPhi = PI/16.0;
+float deltaTheta = deltaPhi*0.25;
+
+void main()
+{
+	vec3 N = normalize(vary_dir);
+	vec3 up = vec3(0.0, 1.0, 0.0);
+	vec3 right = normalize(cross(up, N));
+	up = cross(N, right);
+
+	const float TWO_PI = PI * 2.0;
+	const float HALF_PI = PI * 0.5;
+
+	vec3 color = vec3(0.0);
+	uint sampleCount = 0u;
+	for (float phi = 0.0; phi < TWO_PI; phi += deltaPhi) {
+		for (float theta = 0.0; theta < HALF_PI; theta += deltaTheta) {
+			vec3 tempVec = cos(phi) * right + sin(phi) * up;
+			vec3 sampleVector = cos(theta) * N + sin(theta) * tempVec;
+			color += textureLod(reflectionProbes, vec4(sampleVector, sourceIdx), 3).rgb * cos(theta) * sin(theta);
+			sampleCount++;
+		}
+	}
+	frag_color = vec4(PI * color / float(sampleCount), 1.0);
+}
+// =============================================================================================================
+
diff --git a/indra/newview/app_settings/shaders/class1/interface/irradianceGenV.glsl b/indra/newview/app_settings/shaders/class1/interface/irradianceGenV.glsl
new file mode 100644
index 00000000000..5190abf17cf
--- /dev/null
+++ b/indra/newview/app_settings/shaders/class1/interface/irradianceGenV.glsl
@@ -0,0 +1,38 @@
+/** 
+ * @file irradianceGenV.glsl
+ *
+ * $LicenseInfo:firstyear=2022&license=viewerlgpl$
+ * Second Life Viewer Source Code
+ * Copyright (C) 2011, 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$
+ */
+
+uniform mat4 modelview_matrix;
+
+ATTRIBUTE vec3 position;
+
+VARYING vec3 vary_dir;
+
+void main()
+{
+	gl_Position = vec4(position, 1.0);
+
+	vary_dir = vec3(modelview_matrix * vec4(position, 1.0)).xyz;
+}
+
diff --git a/indra/newview/app_settings/shaders/class1/interface/radianceGenF.glsl b/indra/newview/app_settings/shaders/class1/interface/radianceGenF.glsl
index 27008b8a1cf..3fc227eae73 100644
--- a/indra/newview/app_settings/shaders/class1/interface/radianceGenF.glsl
+++ b/indra/newview/app_settings/shaders/class1/interface/radianceGenF.glsl
@@ -26,8 +26,6 @@
 
 /*[EXTRA_CODE_HERE]*/
 
-#define REFMAP_COUNT 256
-
 #ifdef DEFINE_GL_FRAGCOLOR
 out vec4 frag_color;
 #else
@@ -35,6 +33,7 @@ out vec4 frag_color;
 #endif
 
 uniform samplerCubeArray   reflectionProbes;
+uniform int sourceIdx;
 
 VARYING vec3 vary_dir;
 
@@ -150,7 +149,7 @@ vec3 prefilterEnvMap(vec3 R, float roughness)
 			float omegaP = 4.0 * PI / (6.0 * envMapDim * envMapDim);
 			// Biased (+1.0) mip level for better result
 			float mipLevel = roughness == 0.0 ? 0.0 : max(0.5 * log2(omegaS / omegaP) + 1.0, 0.0f);
-			color += textureLod(reflectionProbes, vec4(L,REFMAP_COUNT), mipLevel).rgb * dotNL;
+			color += textureLod(reflectionProbes, vec4(L,sourceIdx), mipLevel).rgb * dotNL;
 			totalWeight += dotNL;
 
 		}
diff --git a/indra/newview/app_settings/shaders/class3/deferred/reflectionProbeF.glsl b/indra/newview/app_settings/shaders/class3/deferred/reflectionProbeF.glsl
index 40378b49ea6..83348077d72 100644
--- a/indra/newview/app_settings/shaders/class3/deferred/reflectionProbeF.glsl
+++ b/indra/newview/app_settings/shaders/class3/deferred/reflectionProbeF.glsl
@@ -31,6 +31,7 @@
 #define REF_SAMPLE_COUNT 64 //maximum number of samples to consider
 
 uniform samplerCubeArray   reflectionProbes;
+uniform samplerCubeArray   irradianceProbes;
 
 layout (std140, binding = 1) uniform ReflectionProbes
 {
@@ -308,7 +309,7 @@ vec3 boxIntersect(vec3 origin, vec3 dir, int i)
 
 
 
-// Tap a sphere based reflection probe
+// Tap a reflection probe
 // pos - position of pixel
 // dir - pixel normal
 // lod - which mip to bias towards (lower is higher res, sharper reflections)
@@ -334,10 +335,36 @@ vec3 tapRefMap(vec3 pos, vec3 dir, float lod, vec3 c, float r2, int i)
     v -= c;
     v = env_mat * v;
     {
-        //float min_lod = textureQueryLod(reflectionProbes,v).y; // lower is higher res
-        //return textureLod(reflectionProbes, vec4(v.xyz, refIndex[i].x), max(min_lod, lod)).rgb;
         return textureLod(reflectionProbes, vec4(v.xyz, refIndex[i].x), lod).rgb;
-        //return texture(reflectionProbes, vec4(v.xyz, refIndex[i].x)).rgb;
+    }
+}
+
+// Tap an irradiance map
+// pos - position of pixel
+// dir - pixel normal
+// c - center of probe
+// r2 - radius of probe squared
+// i - index of probe 
+// vi - point at which reflection vector struck the influence volume, in clip space
+vec3 tapIrradianceMap(vec3 pos, vec3 dir, vec3 c, float r2, int i)
+{
+    //lod = max(lod, 1);
+    // parallax adjustment
+
+    vec3 v;
+    if (refIndex[i].w < 0)
+    {
+        v = boxIntersect(pos, dir, i);
+    }
+    else
+    {
+        v = sphereIntersect(pos, dir, c, r2);
+    }
+
+    v -= c;
+    v = env_mat * v;
+    {
+        return texture(irradianceProbes, vec4(v.xyz, refIndex[i].x)).rgb * refParams[i].x;
     }
 }
 
@@ -371,7 +398,7 @@ vec3 sampleProbes(vec3 pos, vec3 dir, float lod, float minweight)
             float atten = 1.0-max(d2-r2, 0.0)/(rr-r2);
             w *= atten;
             //w *= p; // boost weight based on priority
-            col += refcol*w*max(minweight, refParams[i].x);
+            col += refcol*w;
             
             wsum += w;
         }
@@ -394,7 +421,7 @@ vec3 sampleProbes(vec3 pos, vec3 dir, float lod, float minweight)
                 
                 float w = 1.0/d2;
                 w *= w;
-                col += refcol*w*max(minweight, refParams[i].x);
+                col += refcol*w;
                 wsum += w;
             }
         }
@@ -408,24 +435,75 @@ vec3 sampleProbes(vec3 pos, vec3 dir, float lod, float minweight)
     return col;
 }
 
-vec3 sampleProbeAmbient(vec3 pos, vec3 dir, float lod)
+vec3 sampleProbeAmbient(vec3 pos, vec3 dir)
 {
-    vec3 col = sampleProbes(pos, dir, lod, 0.f);
+    // modified copy/paste of sampleProbes follows, will likely diverge from sampleProbes further
+    // as irradiance map mixing is tuned independently of radiance map mixing
+    float wsum = 0.0;
+    vec3 col = vec3(0,0,0);
+    float vd2 = dot(pos,pos); // view distance squared
 
-    //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;
+    float minweight = 1.0;
 
-    return col;
+    for (int idx = 0; idx < probeInfluences; ++idx)
+    {
+        int i = probeIndex[idx];
+        if (abs(refIndex[i].w) < max_priority)
+        {
+            continue;
+        }
+        float r = refSphere[i].w; // radius of sphere volume
+        float p = float(abs(refIndex[i].w)); // priority
+        
+        float rr = r*r; // radius squred
+        float r1 = r * 0.1; // 75% of radius (outer sphere to start interpolating down)
+        vec3 delta = pos.xyz-refSphere[i].xyz;
+        float d2 = dot(delta,delta);
+        float r2 = r1*r1; 
+        
+        {
+            vec3 refcol = tapIrradianceMap(pos, dir, refSphere[i].xyz, rr, i);
+            
+            float w = 1.0/d2;
+
+            float atten = 1.0-max(d2-r2, 0.0)/(rr-r2);
+            w *= atten;
+            //w *= p; // boost weight based on priority
+            col += refcol*w;
+            
+            wsum += w;
+        }
+    }
 
+    if (probeInfluences <= 1)
+    { //edge-of-scene probe or no probe influence, mix in with embiggened version of probes closest to camera 
+        for (int idx = 0; idx < 8; ++idx)
+        {
+            if (refIndex[idx].w < 0)
+            { // don't fallback to box probes, they are *very* specific
+                continue;
+            }
+            int i = idx;
+            vec3 delta = pos.xyz-refSphere[i].xyz;
+            float d2 = dot(delta,delta);
+            
+            {
+                vec3 refcol = tapIrradianceMap(pos, dir, refSphere[i].xyz, d2, i);
+                
+                float w = 1.0/d2;
+                w *= w;
+                col += refcol*w;
+                wsum += w;
+            }
+        }
+    }
+
+    if (wsum > 0.0)
+    {
+        col *= 1.0/wsum;
+    }
+    
+    return col;
 }
 
 // brighten a color so that at least one component is 1
@@ -451,7 +529,7 @@ void sampleReflectionProbes(inout vec3 ambenv, inout vec3 glossenv, inout vec3 l
 
     vec3 refnormpersp = reflect(pos.xyz, norm.xyz);
 
-    ambenv = sampleProbeAmbient(pos, norm, reflection_lods-2);
+    ambenv = sampleProbeAmbient(pos, norm);
 
     if (glossiness > 0.0)
     {
diff --git a/indra/newview/llreflectionmapmanager.cpp b/indra/newview/llreflectionmapmanager.cpp
index 025b8457c14..dfd0d1b9a9a 100644
--- a/indra/newview/llreflectionmapmanager.cpp
+++ b/indra/newview/llreflectionmapmanager.cpp
@@ -80,8 +80,11 @@ void LLReflectionMapManager::update()
     if (mTexture.isNull())
     {
         mTexture = new LLCubeMapArray();
-        // store LL_REFLECTION_PROBE_COUNT+1 cube maps, final cube map is used for render target and radiance map generation source)
+        // store LL_REFLECTION_PROBE_COUNT+2 cube maps, final two cube maps are used for render target and radiance map generation source)
         mTexture->allocate(LL_REFLECTION_PROBE_RESOLUTION, 3, LL_REFLECTION_PROBE_COUNT+2);
+
+        mIrradianceMaps = new LLCubeMapArray();
+        mIrradianceMaps->allocate(LL_IRRADIANCE_MAP_RESOLUTION, 3, LL_REFLECTION_PROBE_COUNT);
     }
 
     if (!mRenderTarget.isComplete())
@@ -396,6 +399,13 @@ void LLReflectionMapManager::updateProbeFace(LLReflectionMap* probe, U32 face)
     gPipeline.mRT = &gPipeline.mMainRT;
     mRenderTarget.flush();
 
+    S32 targetIdx = LL_REFLECTION_PROBE_COUNT;
+
+    if (probe != mUpdatingProbe)
+    { // this is the "realtime" probe that's updating every frame, use the secondary scratch space channel
+        targetIdx += 1;
+    }
+
     // downsample to placeholder map
     {
         LLGLDepthTest depth(GL_FALSE, GL_FALSE);
@@ -453,7 +463,7 @@ void LLReflectionMapManager::updateProbeFace(LLReflectionMap* probe, U32 face)
             {
                 mTexture->bind(0);
                 //glCopyTexSubImage3D(GL_TEXTURE_CUBE_MAP_ARRAY, mip, 0, 0, probe->mCubeIndex * 6 + face, 0, 0, res, res);
-                glCopyTexSubImage3D(GL_TEXTURE_CUBE_MAP_ARRAY, mip, 0, 0, LL_REFLECTION_PROBE_COUNT * 6 + face, 0, 0, res, res);
+                glCopyTexSubImage3D(GL_TEXTURE_CUBE_MAP_ARRAY, mip, 0, 0, targetIdx * 6 + face, 0, 0, res, res);
                 mTexture->unbind();
             }
             mMipChain[i].flush();
@@ -472,7 +482,9 @@ void LLReflectionMapManager::updateProbeFace(LLReflectionMap* probe, U32 face)
         gRadianceGenProgram.bind();
         S32 channel = gRadianceGenProgram.enableTexture(LLShaderMgr::REFLECTION_PROBES, LLTexUnit::TT_CUBE_MAP_ARRAY);
         mTexture->bind(channel);
-    
+        static LLStaticHashedString sSourceIdx("sourceIdx");
+        gRadianceGenProgram.uniform1i(sSourceIdx, targetIdx);
+
         for (int cf = 0; cf < 6; ++cf)
         { // for each cube face
             LLCoordFrame frame;
@@ -499,13 +511,59 @@ void LLReflectionMapManager::updateProbeFace(LLReflectionMap* probe, U32 face)
                 gGL.end();
                 gGL.flush();
 
-                
-                
                 S32 res = mMipChain[i].getWidth();
                 glCopyTexSubImage3D(GL_TEXTURE_CUBE_MAP_ARRAY, i, 0, 0, probe->mCubeIndex * 6 + cf, 0, 0, res, res);
                 mMipChain[i].flush();
             }
         }
+        gRadianceGenProgram.unbind();
+
+        //generate irradiance map
+        gIrradianceGenProgram.bind();
+        channel = gIrradianceGenProgram.enableTexture(LLShaderMgr::REFLECTION_PROBES, LLTexUnit::TT_CUBE_MAP_ARRAY);
+        mTexture->bind(channel);
+
+        gIrradianceGenProgram.uniform1i(sSourceIdx, targetIdx);
+
+        int start_mip = 0;
+        // find the mip target to start with based on irradiance map resolution
+        for (start_mip = 0; start_mip < mMipChain.size(); ++start_mip)
+        {
+            if (mMipChain[start_mip].getWidth() == LL_IRRADIANCE_MAP_RESOLUTION)
+            {
+                break;
+            }
+        }
+
+        for (int cf = 0; cf < 6; ++cf)
+        { // for each cube face
+            LLCoordFrame frame;
+            frame.lookAt(LLVector3(0, 0, 0), LLCubeMapArray::sClipToCubeLookVecs[cf], LLCubeMapArray::sClipToCubeUpVecs[cf]);
+
+            F32 mat[16];
+            frame.getOpenGLRotation(mat);
+            gGL.loadMatrix(mat);
+
+            for (int i = start_mip; i < mMipChain.size(); ++i)
+            {
+                mMipChain[i].bindTarget();
+
+                gGL.begin(gGL.QUADS);
+                gGL.vertex3f(-1, -1, -1);
+                gGL.vertex3f(1, -1, -1);
+                gGL.vertex3f(1, 1, -1);
+                gGL.vertex3f(-1, 1, -1);
+                gGL.end();
+                gGL.flush();
+
+                S32 res = mMipChain[i].getWidth();
+                mIrradianceMaps->bind(channel);
+                glCopyTexSubImage3D(GL_TEXTURE_CUBE_MAP_ARRAY, i-start_mip, 0, 0, probe->mCubeIndex * 6 + cf, 0, 0, res, res);
+                mTexture->bind(channel);
+                mMipChain[i].flush();
+            }
+        }
+        gIrradianceGenProgram.unbind();
     }
 }
 
diff --git a/indra/newview/llreflectionmapmanager.h b/indra/newview/llreflectionmapmanager.h
index 551a461e639..5f0b11ec175 100644
--- a/indra/newview/llreflectionmapmanager.h
+++ b/indra/newview/llreflectionmapmanager.h
@@ -39,6 +39,7 @@ class LLViewerObject;
 
 // reflection probe resolution
 #define LL_REFLECTION_PROBE_RESOLUTION 256
+#define LL_IRRADIANCE_MAP_RESOLUTION 64
 
 // reflection probe mininum scale
 #define LL_REFLECTION_PROBE_MINIMUM_SCALE 1.f;
@@ -112,9 +113,12 @@ class alignas(16) LLReflectionMapManager
 
     std::vector<LLRenderTarget> mMipChain;
 
-    // storage for reflection probes
+    // storage for reflection probe radiance maps (plus two scratch space cubemaps)
     LLPointer<LLCubeMapArray> mTexture;
 
+    // storage for reflection probe irradiance maps
+    LLPointer<LLCubeMapArray> mIrradianceMaps;
+
     // array indicating if a particular cubemap is free
     bool mCubeFree[LL_REFLECTION_PROBE_COUNT];
 
diff --git a/indra/newview/llviewershadermgr.cpp b/indra/newview/llviewershadermgr.cpp
index d5ca915da51..c041d2470ca 100644
--- a/indra/newview/llviewershadermgr.cpp
+++ b/indra/newview/llviewershadermgr.cpp
@@ -86,6 +86,7 @@ LLGLSLShader	gGlowCombineProgram;
 LLGLSLShader	gSplatTextureRectProgram;
 LLGLSLShader	gReflectionMipProgram;
 LLGLSLShader	gRadianceGenProgram;
+LLGLSLShader	gIrradianceGenProgram;
 LLGLSLShader	gGlowCombineFXAAProgram;
 LLGLSLShader	gTwoTextureAddProgram;
 LLGLSLShader	gTwoTextureCompareProgram;
@@ -764,6 +765,7 @@ void LLViewerShaderMgr::unloadShaders()
 	gSplatTextureRectProgram.unload();
     gReflectionMipProgram.unload();
     gRadianceGenProgram.unload();
+    gIrradianceGenProgram.unload();
 	gGlowCombineFXAAProgram.unload();
 	gTwoTextureAddProgram.unload();
 	gTwoTextureCompareProgram.unload();
@@ -3833,6 +3835,16 @@ BOOL LLViewerShaderMgr::loadShadersInterface()
         success = gRadianceGenProgram.createShader(NULL, NULL);
     }
 
+    if (success)
+    {
+        gIrradianceGenProgram.mName = "Irradiance Gen Shader";
+        gIrradianceGenProgram.mShaderFiles.clear();
+        gIrradianceGenProgram.mShaderFiles.push_back(make_pair("interface/irradianceGenV.glsl", GL_VERTEX_SHADER_ARB));
+        gIrradianceGenProgram.mShaderFiles.push_back(make_pair("interface/irradianceGenF.glsl", GL_FRAGMENT_SHADER_ARB));
+        gIrradianceGenProgram.mShaderLevel = mShaderLevel[SHADER_INTERFACE];
+        success = gIrradianceGenProgram.createShader(NULL, NULL);
+    }
+
 	if( !success )
 	{
 		mShaderLevel[SHADER_INTERFACE] = 0;
diff --git a/indra/newview/llviewershadermgr.h b/indra/newview/llviewershadermgr.h
index fa5b2121b9f..87d90b49a98 100644
--- a/indra/newview/llviewershadermgr.h
+++ b/indra/newview/llviewershadermgr.h
@@ -163,6 +163,7 @@ extern LLGLSLShader			gGlowCombineProgram;
 extern LLGLSLShader			gSplatTextureRectProgram;
 extern LLGLSLShader			gReflectionMipProgram;
 extern LLGLSLShader         gRadianceGenProgram;
+extern LLGLSLShader         gIrradianceGenProgram;
 extern LLGLSLShader			gGlowCombineFXAAProgram;
 extern LLGLSLShader			gDebugProgram;
 extern LLGLSLShader			gClipProgram;
diff --git a/indra/newview/pipeline.cpp b/indra/newview/pipeline.cpp
index 028a0db95cb..06c9d3c136e 100644
--- a/indra/newview/pipeline.cpp
+++ b/indra/newview/pipeline.cpp
@@ -9205,10 +9205,22 @@ void LLPipeline::unbindDeferredShader(LLGLSLShader &shader)
 void LLPipeline::bindReflectionProbes(LLGLSLShader& shader)
 {
     S32 channel = shader.enableTexture(LLShaderMgr::REFLECTION_PROBES, LLTexUnit::TT_CUBE_MAP_ARRAY);
+    bool bound = false;
     if (channel > -1 && mReflectionMapManager.mTexture.notNull())
     {
-        // see comments in class2/deferred/softenLightF.glsl for what these uniforms mean
         mReflectionMapManager.mTexture->bind(channel);
+        bound = true;
+    }
+
+    channel = shader.enableTexture(LLShaderMgr::IRRADIANCE_PROBES, LLTexUnit::TT_CUBE_MAP_ARRAY);
+    if (channel > -1 && mReflectionMapManager.mIrradianceMaps.notNull())
+    {
+        mReflectionMapManager.mIrradianceMaps->bind(channel);
+        bound = true;
+    }
+
+    if (bound)
+    {
         mReflectionMapManager.setUniforms();
 
         F32* m = gGLModelView;
-- 
GitLab