diff --git a/indra/llrender/llimagegl.cpp b/indra/llrender/llimagegl.cpp
index 38482edd225fd697900baccce9ea31f1d5c7c8ea..a69fcd8ca81115b00f0206a143ffc87532fa8806 100644
--- a/indra/llrender/llimagegl.cpp
+++ b/indra/llrender/llimagegl.cpp
@@ -1354,6 +1354,153 @@ void LLImageGL::setManualImage(U32 target, S32 miplevel, S32 intformat, S32 widt
 	stop_glerror();
 }
 
+void LLImageGL::setManualImage3D(U32 target, S32 miplevel, S32 intformat, S32 width, S32 height, S32 depth, U32 pixformat, U32 pixtype, const void* pixels, bool allow_compression)
+{
+	LL_RECORD_BLOCK_TIME(FTM_SET_MANUAL_IMAGE);
+	std::vector<U32> scratch;
+	if (LLRender::sGLCoreProfile)
+	{
+#ifdef GL_ARB_texture_swizzle
+		if (gGLManager.mHasTextureSwizzle)
+		{
+			if (pixformat == GL_ALPHA)
+			{ //GL_ALPHA is deprecated, convert to RGBA
+				const GLint mask[] = { GL_ZERO, GL_ZERO, GL_ZERO, GL_RED };
+				glTexParameteriv(GL_TEXTURE_3D, GL_TEXTURE_SWIZZLE_RGBA, mask);
+				pixformat = GL_RED;
+				intformat = GL_R8;
+			}
+
+			if (pixformat == GL_LUMINANCE)
+			{ //GL_LUMINANCE is deprecated, convert to GL_RGBA
+				const GLint mask[] = { GL_RED, GL_RED, GL_RED, GL_ONE };
+				glTexParameteriv(GL_TEXTURE_3D, GL_TEXTURE_SWIZZLE_RGBA, mask);
+				pixformat = GL_RED;
+				intformat = GL_R8;
+			}
+
+			if (pixformat == GL_LUMINANCE_ALPHA)
+			{ //GL_LUMINANCE_ALPHA is deprecated, convert to RGBA
+				const GLint mask[] = { GL_RED, GL_RED, GL_RED, GL_GREEN };
+				glTexParameteriv(GL_TEXTURE_3D, GL_TEXTURE_SWIZZLE_RGBA, mask);
+				pixformat = GL_RG;
+				intformat = GL_RG8;
+			}
+		}
+		else
+#endif
+		{
+			if (pixformat == GL_ALPHA && pixtype == GL_UNSIGNED_BYTE)
+			{ //GL_ALPHA is deprecated, convert to RGBA
+				scratch.resize(width * height);
+
+				U32 pixel_count = (U32)(width * height);
+				for (U32 i = 0; i < pixel_count; i++)
+				{
+					U8* pix = (U8*)&scratch[i];
+					pix[0] = pix[1] = pix[2] = 0;
+					pix[3] = ((U8*)pixels)[i];
+				}
+
+				pixels = &scratch[0];
+
+				pixformat = GL_RGBA;
+				intformat = GL_RGBA8;
+			}
+
+			if (pixformat == GL_LUMINANCE_ALPHA && pixtype == GL_UNSIGNED_BYTE)
+			{ //GL_LUMINANCE_ALPHA is deprecated, convert to RGBA
+				scratch.resize(width * height);
+
+				U32 pixel_count = (U32)(width * height);
+				for (U32 i = 0; i < pixel_count; i++)
+				{
+					U8 lum = ((U8*)pixels)[i * 2 + 0];
+					U8 alpha = ((U8*)pixels)[i * 2 + 1];
+
+					U8* pix = (U8*)&scratch[i];
+					pix[0] = pix[1] = pix[2] = lum;
+					pix[3] = alpha;
+				}
+
+				pixels = &scratch[0];
+
+				pixformat = GL_RGBA;
+				intformat = GL_RGBA8;
+			}
+
+			if (pixformat == GL_LUMINANCE && pixtype == GL_UNSIGNED_BYTE)
+			{ //GL_LUMINANCE_ALPHA is deprecated, convert to RGB
+				scratch.resize(width * height);
+
+				U32 pixel_count = (U32)(width * height);
+				for (U32 i = 0; i < pixel_count; i++)
+				{
+					U8 lum = ((U8*)pixels)[i];
+
+					U8* pix = (U8*)&scratch[i];
+					pix[0] = pix[1] = pix[2] = lum;
+					pix[3] = 255;
+				}
+
+				pixels = &scratch[0];
+
+				pixformat = GL_RGBA;
+				intformat = GL_RGB8;
+			}
+		}
+	}
+	if (LLImageGL::sCompressTextures && allow_compression)
+	{
+		switch (intformat)
+		{
+		case GL_RED:
+		case GL_R8:
+			intformat = GL_COMPRESSED_RED;
+			break;
+		case GL_RG:
+		case GL_RG8:
+			intformat = GL_COMPRESSED_RG;
+			break;
+		case GL_RGB:
+		case GL_RGB8:
+			intformat = GL_COMPRESSED_RGB;
+			break;
+		case GL_SRGB:
+		case GL_SRGB8:
+			intformat = GL_COMPRESSED_SRGB;
+			break;
+		case GL_RGBA:
+		case GL_RGBA8:
+			intformat = GL_COMPRESSED_RGBA;
+			break;
+		case GL_SRGB_ALPHA:
+		case GL_SRGB8_ALPHA8:
+			intformat = GL_COMPRESSED_SRGB_ALPHA;
+			break;
+		case GL_LUMINANCE:
+		case GL_LUMINANCE8:
+			intformat = GL_COMPRESSED_LUMINANCE;
+			break;
+		case GL_LUMINANCE_ALPHA:
+		case GL_LUMINANCE8_ALPHA8:
+			intformat = GL_COMPRESSED_LUMINANCE_ALPHA;
+			break;
+		case GL_ALPHA:
+		case GL_ALPHA8:
+			intformat = GL_COMPRESSED_ALPHA;
+			break;
+		default:
+			LL_WARNS() << "Could not compress format: " << std::hex << intformat << std::dec << LL_ENDL;
+			break;
+		}
+	}
+
+	stop_glerror();
+	glTexImage3D(target, miplevel, intformat, width, height, depth, 0, pixformat, pixtype, pixels);
+	stop_glerror();
+}
+
 //create an empty GL texture: just create a texture name
 //the texture is assiciate with some image by calling glTexImage outside LLImageGL
 static LLTrace::BlockTimerStatHandle FTM_CREATE_GL_TEXTURE1("createGLTexture()");
diff --git a/indra/llrender/llimagegl.h b/indra/llrender/llimagegl.h
index c452a17a8c5df905e32789b9d01a7e35e50bef19..27d7d30b048401803f223f42391627840e73364f 100644
--- a/indra/llrender/llimagegl.h
+++ b/indra/llrender/llimagegl.h
@@ -102,6 +102,7 @@ class LLImageGL : public LLRefCount, public LLTrace::MemTrackable<LLImageGL>
 	void setAllowCompression(bool allow) { mAllowCompression = allow; }
 
 	static void setManualImage(U32 target, S32 miplevel, S32 intformat, S32 width, S32 height, U32 pixformat, U32 pixtype, const void *pixels, bool allow_compression = true);
+	static void setManualImage3D(U32 target, S32 miplevel, S32 intformat, S32 width, S32 height, S32 depth, U32 pixformat, U32 pixtype, const void* pixels, bool allow_compression = true);
 
 	BOOL createGLTexture() ;
 	BOOL createGLTexture(S32 discard_level, const LLImageRaw* imageraw, S32 usename = 0, BOOL to_create = TRUE,
diff --git a/indra/llrender/llrender.cpp b/indra/llrender/llrender.cpp
index 5d62ef03be632051d3423aef7939d2ea66c7ddec..0c041f6071e20fcc5cc2e3b21b583e8352a430aa 100644
--- a/indra/llrender/llrender.cpp
+++ b/indra/llrender/llrender.cpp
@@ -472,9 +472,9 @@ void LLTexUnit::setTextureAddressMode(eTextureAddressMode mode)
 
 	glTexParameteri (sGLTextureType[mCurrTexType], GL_TEXTURE_WRAP_S, sGLAddressMode[mode]);
 	glTexParameteri (sGLTextureType[mCurrTexType], GL_TEXTURE_WRAP_T, sGLAddressMode[mode]);
-	if (mCurrTexType == TT_CUBE_MAP)
+	if (mCurrTexType == TT_CUBE_MAP || mCurrTexType == TT_TEXTURE_3D)
 	{
-		glTexParameteri (GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_R, sGLAddressMode[mode]);
+		glTexParameteri (sGLTextureType[mCurrTexType], GL_TEXTURE_WRAP_R, sGLAddressMode[mode]);
 	}
 }
 
@@ -2397,6 +2397,9 @@ void LLRender::debugTexUnits(void)
 				case LLTexUnit::TT_CUBE_MAP:
 					LL_CONT << "Cube Map";
 					break;
+				case LLTexUnit::TT_TEXTURE_3D:
+					LL_CONT << "Texture 3D";
+					break;
 				default:
 					LL_CONT << "ARGH!!! NONE!";
 					break;
diff --git a/indra/newview/alrenderutils.cpp b/indra/newview/alrenderutils.cpp
index 5cab2e9ec082189706f3fc2175daa529cd5552f4..857b13599cf8572ea505aedbdb38fba855063109 100644
--- a/indra/newview/alrenderutils.cpp
+++ b/indra/newview/alrenderutils.cpp
@@ -246,14 +246,14 @@ bool ALRenderUtil::setupColorGrade()
 						mCGLutSize = LLVector4(1.f / image_width, 1.f / image_height, (F32)image_width, (F32)image_height);
 
 						LLImageGL::generateTextures(1, &mCGLut);
-						gGL.getTexUnit(0)->bindManual(LLTexUnit::TT_TEXTURE, mCGLut);
-						LLImageGL::setManualImage(LLTexUnit::getInternalType(LLTexUnit::TT_TEXTURE), 0, int_format, image_width,
-							image_height, primary_format, GL_UNSIGNED_BYTE, raw_image->getData(), false);
+						gGL.getTexUnit(0)->bindManual(LLTexUnit::TT_TEXTURE_3D, mCGLut);
+						LLImageGL::setManualImage3D(LLTexUnit::getInternalType(LLTexUnit::TT_TEXTURE_3D), 0, int_format, image_height,
+							image_height, image_height, primary_format, GL_UNSIGNED_BYTE, raw_image->getData(), false);
 						stop_glerror();
 						gGL.getTexUnit(0)->setTextureFilteringOption(LLTexUnit::TFO_BILINEAR);
-						gGL.getTexUnit(0)->setTextureAddressMode(LLTexUnit::TAM_WRAP);
+						gGL.getTexUnit(0)->setTextureAddressMode(LLTexUnit::TAM_CLAMP);
 						gGL.getTexUnit(0)->setTextureColorSpace(LLTexUnit::TCS_LINEAR);
-						gGL.getTexUnit(0)->unbind(LLTexUnit::TT_TEXTURE);
+						gGL.getTexUnit(0)->unbind(LLTexUnit::TT_TEXTURE_3D);
 					}
 					else
 					{
@@ -331,12 +331,12 @@ void ALRenderUtil::renderTonemap(LLRenderTarget* src, LLRenderTarget* dst, LLRen
 
 	if (mCGLut != 0)
 	{
-		S32 channel = tone_shader->enableTexture(LLShaderMgr::COLORGRADE_LUT, LLTexUnit::TT_TEXTURE);
+		S32 channel = tone_shader->enableTexture(LLShaderMgr::COLORGRADE_LUT, LLTexUnit::TT_TEXTURE_3D);
 		if (channel > -1)
 		{
-			gGL.getTexUnit(channel)->bindManual(LLTexUnit::TT_TEXTURE, mCGLut);
+			gGL.getTexUnit(channel)->bindManual(LLTexUnit::TT_TEXTURE_3D, mCGLut);
 			gGL.getTexUnit(channel)->setTextureFilteringOption(LLTexUnit::TFO_BILINEAR);
-			gGL.getTexUnit(channel)->setTextureAddressMode(LLTexUnit::TAM_WRAP);
+			gGL.getTexUnit(channel)->setTextureAddressMode(LLTexUnit::TAM_CLAMP);
 			gGL.getTexUnit(channel)->setTextureColorSpace(LLTexUnit::TCS_LINEAR);
 		}
 
diff --git a/indra/newview/app_settings/shaders/class1/alchemy/toneMapF.glsl b/indra/newview/app_settings/shaders/class1/alchemy/toneMapF.glsl
index e64b4c9200177215da67f991571ac5fabaf2b83b..403fbf6ff1cfc7d904afe976c37668bafd90a604 100644
--- a/indra/newview/app_settings/shaders/class1/alchemy/toneMapF.glsl
+++ b/indra/newview/app_settings/shaders/class1/alchemy/toneMapF.glsl
@@ -42,7 +42,7 @@ vec3 srgb_to_linear(vec3 cl);
 vec3 linear_to_srgb(vec3 cl);
 
 #if COLOR_GRADE_LUT != 0
-uniform sampler2D colorgrade_lut;
+uniform sampler3D colorgrade_lut;
 uniform vec4 colorgrade_lut_size;
 #endif
 
@@ -223,25 +223,13 @@ void main()
     
     #if COLOR_GRADE_LUT != 0
     // Invert coord for compat with DX-style LUT
-    diff.y = 1.0 - diff.y;
+    diff.g = 1.0 - diff.g;
     
-    // Convert to texel coords
-    vec3 lutRange = diff.rgb * ( colorgrade_lut_size.w - 1);
-    
-    // Calculate coords in texel space
-    vec2 lutX = vec2(floor(lutRange.z)*colorgrade_lut_size.w+lutRange.x, lutRange.y);
-    vec2 lutY = vec2(ceil(lutRange.z)*colorgrade_lut_size.w+lutRange.x, lutRange.y);
-    
-    // texel to ndc
-    lutX = (lutX+0.5)*colorgrade_lut_size.xy;
-    lutY = (lutY+0.5)*colorgrade_lut_size.xy;
-    
-    // LUT interpolation
-    diff.rgb = mix(
-    linear_to_srgb(texture2D(colorgrade_lut, lutX).rgb),
-    linear_to_srgb(texture2D(colorgrade_lut, lutY).rgb),
-    fract(lutRange.z)
-    );
+    //see https://developer.nvidia.com/gpugems/GPUGems2/gpugems2_chapter24.html
+    vec3 scale = (vec3(colorgrade_lut_size.w) - 1.0) / vec3(colorgrade_lut_size.w);
+    vec3 offset = 1.0 / (2.0 * vec3(colorgrade_lut_size.w));
+
+    diff = vec4(linear_to_srgb(textureLod(colorgrade_lut, scale * diff.rbg + offset, 0).rgb), diff.a);
     #endif
 
     frag_color = diff;