Implement sRGB texture sampling.

Previously sRGB data was converted to linear space on upload. This
caused a loss of precision. This change performs the conversion after
texel lookup. Note that we had a code path for performing the
conversion after filtering, but that leads to failures in dEQP and
unacceptable darkening between texels.

Also, glTexSubImage calls can update sRGB textures using a format/type
combination with no indication of the color space, which caused an
unintentional conversion on upload. Likewise we were missing support
for an A2B10G10R10UI implementation format.

Change-Id: Ib10845f628fb2d1849e88d7a9350868cdec32fa2
Reviewed-on: https://swiftshader-review.googlesource.com/15068
Reviewed-by: Alexis Hétu <sugoi@google.com>
Reviewed-by: Nicolas Capens <nicolascapens@google.com>
Tested-by: Nicolas Capens <nicolascapens@google.com>
diff --git a/src/OpenGL/common/Image.cpp b/src/OpenGL/common/Image.cpp
index 9fab82e..3ed148e 100644
--- a/src/OpenGL/common/Image.cpp
+++ b/src/OpenGL/common/Image.cpp
@@ -52,8 +52,6 @@
 		RGB10A2UI,
 		R11G11B10F,
 		RGB9E5,
-		SRGB,
-		SRGBA,
 		D16,
 		D24,
 		D32,
@@ -298,36 +296,6 @@
 	}
 
 	template<>
-	void LoadImageRow<SRGB>(const unsigned char *source, unsigned char *dest, GLint xoffset, GLsizei width)
-	{
-		dest += xoffset * 4;
-
-		for(int x = 0; x < width; x++)
-		{
-			for(int rgb = 0; rgb < 3; ++rgb)
-			{
-				*dest++ = sw::sRGB8toLinear8(*source++);
-			}
-			*dest++ = 255;
-		}
-	}
-
-	template<>
-	void LoadImageRow<SRGBA>(const unsigned char *source, unsigned char *dest, GLint xoffset, GLsizei width)
-	{
-		dest += xoffset * 4;
-
-		for(int x = 0; x < width; x++)
-		{
-			for(int rgb = 0; rgb < 3; ++rgb)
-			{
-				*dest++ = sw::sRGB8toLinear8(*source++);
-			}
-			*dest++ = *source++;
-		}
-	}
-
-	template<>
 	void LoadImageRow<D16>(const unsigned char *source, unsigned char *dest, GLint xoffset, GLsizei width)
 	{
 		const unsigned short *sourceD16 = reinterpret_cast<const unsigned short*>(source);
@@ -559,6 +527,7 @@
 			{
 			case GL_INT:          return sw::FORMAT_A32B32G32R32I;
 			case GL_UNSIGNED_INT: return sw::FORMAT_A32B32G32R32UI;
+			case GL_UNSIGNED_INT_2_10_10_10_REV: return sw::FORMAT_A2B10G10R10UI;
 			default: UNREACHABLE(type);
 			}
 			break;
@@ -805,16 +774,18 @@
 			case GL_RGB8_SNORM:
 			case GL_RGB8:
 			case GL_RGB:
-			case GL_SRGB8:
 				return sw::FORMAT_X8B8G8R8;
+			case GL_SRGB8:
+				return sw::FORMAT_SRGB8_X8;
 			case GL_RGB8UI:
 			case GL_RGB_INTEGER:
 				return sw::FORMAT_X8B8G8R8UI;
 			case GL_RGBA8_SNORM:
 			case GL_RGBA8:
 			case GL_RGBA:
-			case GL_SRGB8_ALPHA8:
 				return sw::FORMAT_A8B8G8R8;
+			case GL_SRGB8_ALPHA8:
+				return sw::FORMAT_SRGB8_A8;
 			case GL_RGBA8UI:
 			case GL_RGBA_INTEGER:
 				return sw::FORMAT_A8B8G8R8UI;
@@ -934,7 +905,7 @@
 		case GL_UNSIGNED_INT_2_10_10_10_REV:
 			if(format == GL_RGB10_A2UI)
 			{
-				return sw::FORMAT_A16B16G16R16UI;
+				return sw::FORMAT_A2B10G10R10UI;
 			}
 			else
 			{
@@ -1338,8 +1309,8 @@
 
 	void Image::loadImageData(Context *context, GLint xoffset, GLint yoffset, GLint zoffset, GLsizei width, GLsizei height, GLsizei depth, GLenum format, GLenum type, const UnpackInfo& unpackInfo, const void *input)
 	{
-		sw::Format selectedInternalFormat = SelectInternalFormat(format, type);
-		if(selectedInternalFormat == sw::FORMAT_NULL)
+		sw::Format uploadFormat = SelectInternalFormat(format, type);
+		if(uploadFormat == sw::FORMAT_NULL)
 		{
 			return;
 		}
@@ -1349,7 +1320,10 @@
 		GLsizei inputHeight = (unpackInfo.imageHeight == 0) ? height : unpackInfo.imageHeight;
 		input = ((char*)input) + ComputePackingOffset(format, type, inputWidth, inputHeight, unpackInfo.alignment, unpackInfo.skipImages, unpackInfo.skipRows, unpackInfo.skipPixels);
 
-		if(selectedInternalFormat == internalFormat)
+		if(uploadFormat == internalFormat ||
+		   (uploadFormat == sw::FORMAT_A8B8G8R8 && internalFormat == sw::FORMAT_SRGB8_A8) ||
+		   (uploadFormat == sw::FORMAT_X8B8G8R8 && internalFormat == sw::FORMAT_SRGB8_X8) ||
+		   (uploadFormat == sw::FORMAT_A2B10G10R10 && internalFormat == sw::FORMAT_A2B10G10R10UI))
 		{
 			void *buffer = lock(0, 0, sw::LOCK_WRITEONLY);
 
@@ -1427,6 +1401,7 @@
 					case GL_RGB8_SNORM:
 					case GL_RGB:
 					case GL_RGB_INTEGER:
+					case GL_SRGB8:
 						LoadImageData<UByteRGB>(xoffset, yoffset, zoffset, width, height, depth, inputPitch, inputHeight, getPitch(), getSlice(), input, buffer);
 						break;
 					case GL_RGBA8:
@@ -1436,13 +1411,8 @@
 					case GL_RGBA_INTEGER:
 					case GL_BGRA_EXT:
 					case GL_BGRA8_EXT:
-						LoadImageData<Bytes_4>(xoffset, yoffset, zoffset, width, height, depth, inputPitch, inputHeight, getPitch(), getSlice(), input, buffer);
-						break;
-					case GL_SRGB8:
-						LoadImageData<SRGB>(xoffset, yoffset, zoffset, width, height, depth, inputPitch, inputHeight, getPitch(), getSlice(), input, buffer);
-						break;
 					case GL_SRGB8_ALPHA8:
-						LoadImageData<SRGBA>(xoffset, yoffset, zoffset, width, height, depth, inputPitch, inputHeight, getPitch(), getSlice(), input, buffer);
+						LoadImageData<Bytes_4>(xoffset, yoffset, zoffset, width, height, depth, inputPitch, inputHeight, getPitch(), getSlice(), input, buffer);
 						break;
 					default: UNREACHABLE(format);
 					}
@@ -1501,8 +1471,6 @@
 					switch(format)
 					{
 					case GL_RGB10_A2UI:
-						LoadImageData<RGB10A2UI>(xoffset, yoffset, zoffset, width, height, depth, inputPitch, inputHeight, getPitch(), getSlice(), input, buffer);
-						break;
 					case GL_RGB10_A2:
 					case GL_RGBA:
 					case GL_RGBA_INTEGER:
diff --git a/src/OpenGL/libGLESv2/libGLESv2.cpp b/src/OpenGL/libGLESv2/libGLESv2.cpp
index 93a59a6..b834e00 100644
--- a/src/OpenGL/libGLESv2/libGLESv2.cpp
+++ b/src/OpenGL/libGLESv2/libGLESv2.cpp
@@ -5062,7 +5062,7 @@
 
 		GLenum sizedInternalFormat = GetSizedInternalFormat(internalformat, type);
 
-		validationError = context->getPixels(&data, type, context->getRequiredBufferSize(width, height, 1, sizedInternalFormat, type));
+		validationError = context->getPixels(&data, type, context->getRequiredBufferSize(width, height, 1, format, type));
 		if(validationError != GL_NONE)
 		{
 			return error(validationError);
@@ -5423,8 +5423,6 @@
 
 	if(context)
 	{
-		GLenum sizedInternalFormat = GetSizedInternalFormat(format, type);
-
 		if(target == GL_TEXTURE_2D)
 		{
 			es2::Texture2D *texture = context->getTexture2D();
@@ -5435,13 +5433,13 @@
 				return error(validationError);
 			}
 
-			validationError = context->getPixels(&data, type, context->getRequiredBufferSize(width, height, 1, sizedInternalFormat, type));
+			validationError = context->getPixels(&data, type, context->getRequiredBufferSize(width, height, 1, format, type));
 			if(validationError != GL_NONE)
 			{
 				return error(validationError);
 			}
 
-			texture->subImage(context, level, xoffset, yoffset, width, height, sizedInternalFormat, type, context->getUnpackInfo(), data);
+			texture->subImage(context, level, xoffset, yoffset, width, height, format, type, context->getUnpackInfo(), data);
 		}
 		else if(es2::IsCubemapTextureTarget(target))
 		{
@@ -5453,13 +5451,13 @@
 				return error(validationError);
 			}
 
-			validationError = context->getPixels(&data, type, context->getRequiredBufferSize(width, height, 1, sizedInternalFormat, type));
+			validationError = context->getPixels(&data, type, context->getRequiredBufferSize(width, height, 1, format, type));
 			if(validationError != GL_NONE)
 			{
 				return error(validationError);
 			}
 
-			texture->subImage(context, target, level, xoffset, yoffset, width, height, sizedInternalFormat, type, context->getUnpackInfo(), data);
+			texture->subImage(context, target, level, xoffset, yoffset, width, height, format, type, context->getUnpackInfo(), data);
 		}
 		else UNREACHABLE(target);
 	}
@@ -6342,21 +6340,19 @@
 	{
 		es2::Texture3D *texture = context->getTexture3D();
 
-		GLenum sizedInternalFormat = GetSizedInternalFormat(format, type);
-
 		GLenum validationError = ValidateSubImageParams(false, false, target, level, xoffset, yoffset, zoffset, width, height, depth, format, type, texture, context->getClientVersion());
 		if(validationError != GL_NONE)
 		{
 			return error(validationError);
 		}
 
-		validationError = context->getPixels(&data, type, context->getRequiredBufferSize(width, height, depth, sizedInternalFormat, type));
+		validationError = context->getPixels(&data, type, context->getRequiredBufferSize(width, height, depth, format, type));
 		if(validationError != GL_NONE)
 		{
 			return error(validationError);
 		}
 
-		texture->subImage(context, level, xoffset, yoffset, zoffset, width, height, depth, sizedInternalFormat, type, context->getUnpackInfo(), data);
+		texture->subImage(context, level, xoffset, yoffset, zoffset, width, height, depth, format, type, context->getUnpackInfo(), data);
 	}
 }
 
diff --git a/src/OpenGL/libGLESv2/utilities.cpp b/src/OpenGL/libGLESv2/utilities.cpp
index e4f54ed..e068b6a 100644
--- a/src/OpenGL/libGLESv2/utilities.cpp
+++ b/src/OpenGL/libGLESv2/utilities.cpp
@@ -2193,7 +2193,6 @@
 		case GL_R16UI:                return sw::FORMAT_R16UI;
 		case GL_RG16UI:               return sw::FORMAT_G16R16UI;
 		case GL_RGB16UI:              return sw::FORMAT_X16B16G16R16UI;
-		case GL_RGB10_A2UI:
 		case GL_RGBA16UI:             return sw::FORMAT_A16B16G16R16UI;
 		case GL_R32I:                 return sw::FORMAT_R32I;
 		case GL_RG32I:                return sw::FORMAT_G32R32I;
@@ -2213,6 +2212,7 @@
 		case GL_RGB32F:               return sw::FORMAT_B32G32R32F;
 		case GL_RGBA32F:              return sw::FORMAT_A32B32G32R32F;
 		case GL_RGB10_A2:             return sw::FORMAT_A2B10G10R10;
+		case GL_RGB10_A2UI:           return sw::FORMAT_A2B10G10R10UI;
 		case GL_SRGB8:                return sw::FORMAT_SRGB8_X8;
 		case GL_SRGB8_ALPHA8:         return sw::FORMAT_SRGB8_A8;
 		default: UNREACHABLE(format); return sw::FORMAT_NULL;
diff --git a/src/Renderer/Blitter.cpp b/src/Renderer/Blitter.cpp
index f0de2f2..e41b937 100644
--- a/src/Renderer/Blitter.cpp
+++ b/src/Renderer/Blitter.cpp
@@ -393,6 +393,7 @@
 			c.z = Float(Int(*Pointer<UShort>(element) & UShort(0x001F)));
 			break;
 		case FORMAT_A2B10G10R10:
+		case FORMAT_A2B10G10R10UI:
 			c.x = Float(Int((*Pointer<UInt>(element) & UInt(0x000003FF))));
 			c.y = Float(Int((*Pointer<UInt>(element) & UInt(0x000FFC00)) >> 10));
 			c.z = Float(Int((*Pointer<UInt>(element) & UInt(0x3FF00000)) >> 20));
@@ -741,6 +742,7 @@
 			}
 			break;
 		case FORMAT_A2B10G10R10:
+		case FORMAT_A2B10G10R10UI:
 			if(writeRGBA)
 			{
 				*Pointer<UInt>(element) = UInt(RoundInt(Float(c.x)) |
@@ -1047,6 +1049,7 @@
 		case FORMAT_B32G32R32F:
 		case FORMAT_G32R32F:
 		case FORMAT_R32F:
+		case FORMAT_A2B10G10R10UI:
 			scale = vector(1.0f, 1.0f, 1.0f, 1.0f);
 			break;
 		case FORMAT_R5G6B5:
diff --git a/src/Renderer/Sampler.cpp b/src/Renderer/Sampler.cpp
index 5862996..85448b9 100644
--- a/src/Renderer/Sampler.cpp
+++ b/src/Renderer/Sampler.cpp
@@ -96,7 +96,7 @@
 			state.addressingModeV = getAddressingModeV();
 			state.addressingModeW = getAddressingModeW();
 			state.mipmapFilter = mipmapFilter();
-			state.sRGB = sRGB && Surface::isSRGBreadable(externalTextureFormat);
+			state.sRGB = (sRGB && Surface::isSRGBreadable(externalTextureFormat)) || Surface::isSRGBformat(internalTextureFormat);
 			state.swizzleR = swizzleR;
 			state.swizzleG = swizzleG;
 			state.swizzleB = swizzleB;
diff --git a/src/Renderer/Surface.cpp b/src/Renderer/Surface.cpp
index e732fb8..899e7e1 100644
--- a/src/Renderer/Surface.cpp
+++ b/src/Renderer/Surface.cpp
@@ -169,6 +169,7 @@
 			*(unsigned int*)element = (unorm<2>(color.a) << 30) | (unorm<10>(color.r) << 20) | (unorm<10>(color.g) << 10) | (unorm<10>(color.b) << 0);
 			break;
 		case FORMAT_A2B10G10R10:
+		case FORMAT_A2B10G10R10UI:
 			*(unsigned int*)element = (unorm<2>(color.a) << 30) | (unorm<10>(color.b) << 20) | (unorm<10>(color.g) << 10) | (unorm<10>(color.r) << 0);
 			break;
 		case FORMAT_G8R8I_SNORM:
@@ -701,6 +702,16 @@
 				r = (abgr & 0x000003FF) * (1.0f / 0x000003FF);
 			}
 			break;
+		case FORMAT_A2B10G10R10UI:
+			{
+				unsigned int abgr = *(unsigned int*)element;
+
+				a = static_cast<float>((abgr & 0xC0000000) >> 30);
+				b = static_cast<float>((abgr & 0x3FF00000) >> 20);
+				g = static_cast<float>((abgr & 0x000FFC00) >> 10);
+				r = static_cast<float>(abgr & 0x000003FF);
+			}
+			break;
 		case FORMAT_A16B16G16R16I:
 			{
 				short* abgr = (short*)element;
@@ -1544,6 +1555,7 @@
 		case FORMAT_A8B8G8R8I_SNORM:	return 4;
 		case FORMAT_A2R10G10B10:		return 4;
 		case FORMAT_A2B10G10R10:		return 4;
+		case FORMAT_A2B10G10R10UI:		return 4;
 		case FORMAT_G8R8I:				return 2;
 		case FORMAT_G8R8:				return 2;
 		case FORMAT_G16R16I:			return 4;
@@ -2824,6 +2836,7 @@
 		case FORMAT_G8R8I:
 		case FORMAT_G8R8:
 		case FORMAT_A2B10G10R10:
+		case FORMAT_A2B10G10R10UI:
 		case FORMAT_R8I_SNORM:
 		case FORMAT_G8R8I_SNORM:
 		case FORMAT_X8B8G8R8I_SNORM:
@@ -2908,6 +2921,7 @@
 		case FORMAT_SRGB8_A8:
 		case FORMAT_G8R8:
 		case FORMAT_A2B10G10R10:
+		case FORMAT_A2B10G10R10UI:
 		case FORMAT_R16UI:
 		case FORMAT_G16R16:
 		case FORMAT_G16R16UI:
@@ -3027,6 +3041,18 @@
 		}
 	}
 
+	bool Surface::isSRGBformat(Format format)
+	{
+		switch(format)
+		{
+		case FORMAT_SRGB8_X8:
+		case FORMAT_SRGB8_A8:
+			return true;
+		default:
+			return false;
+		}
+	}
+
 	bool Surface::isCompressed(Format format)
 	{
 		switch(format)
@@ -3166,6 +3192,7 @@
 		case FORMAT_X8B8G8R8UI:     return 3;
 		case FORMAT_A8B8G8R8UI:     return 4;
 		case FORMAT_A2B10G10R10:    return 4;
+		case FORMAT_A2B10G10R10UI:  return 4;
 		case FORMAT_G16R16I:        return 2;
 		case FORMAT_G16R16UI:       return 2;
 		case FORMAT_G16R16:         return 2;
@@ -3765,6 +3792,8 @@
 		case FORMAT_A2B10G10R10:
 		case FORMAT_A16B16G16R16:
 			return FORMAT_A16B16G16R16;
+		case FORMAT_A2B10G10R10UI:
+			return FORMAT_A16B16G16R16UI;
 		case FORMAT_X32B32G32R32I:
 			return FORMAT_X32B32G32R32I;
 		case FORMAT_A32B32G32R32I:
diff --git a/src/Renderer/Surface.hpp b/src/Renderer/Surface.hpp
index 7ea7bee..c32c14f 100644
--- a/src/Renderer/Surface.hpp
+++ b/src/Renderer/Surface.hpp
@@ -107,6 +107,7 @@
 		FORMAT_G32R32UI,
 		FORMAT_A2R10G10B10,
 		FORMAT_A2B10G10R10,
+		FORMAT_A2B10G10R10UI,
 		FORMAT_A16B16G16R16, // D3D format
 		FORMAT_X16B16G16R16I,
 		FORMAT_X16B16G16R16UI,
@@ -368,6 +369,7 @@
 		static bool isUnsignedComponent(Format format, int component);
 		static bool isSRGBreadable(Format format);
 		static bool isSRGBwritable(Format format);
+		static bool isSRGBformat(Format format);
 		static bool isCompressed(Format format);
 		static bool isSignedNonNormalizedInteger(Format format);
 		static bool isUnsignedNonNormalizedInteger(Format format);
diff --git a/src/Shader/Constants.cpp b/src/Shader/Constants.cpp
index e02ba03..06dda32 100644
--- a/src/Shader/Constants.cpp
+++ b/src/Shader/Constants.cpp
@@ -264,17 +264,17 @@
 
 		for(int i = 0; i < 256; i++)
 		{
-			sRGBtoLinear8_12[i] = (unsigned short)(sw::sRGBtoLinear((float)i / 0xFF) * 0x1000 + 0.5f);
+			sRGBtoLinear8_16[i] = (unsigned short)(sw::sRGBtoLinear((float)i / 0xFF) * 0xFFFF + 0.5f);
 		}
 
 		for(int i = 0; i < 64; i++)
 		{
-			sRGBtoLinear6_12[i] = (unsigned short)(sw::sRGBtoLinear((float)i / 0x3F) * 0x1000 + 0.5f);
+			sRGBtoLinear6_16[i] = (unsigned short)(sw::sRGBtoLinear((float)i / 0x3F) * 0xFFFF + 0.5f);
 		}
 
 		for(int i = 0; i < 32; i++)
 		{
-			sRGBtoLinear5_12[i] = (unsigned short)(sw::sRGBtoLinear((float)i / 0x1F) * 0x1000 + 0.5f);
+			sRGBtoLinear5_16[i] = (unsigned short)(sw::sRGBtoLinear((float)i / 0x1F) * 0xFFFF + 0.5f);
 		}
 
 		for(int i = 0; i < 0x1000; i++)
diff --git a/src/Shader/Constants.hpp b/src/Shader/Constants.hpp
index 5210643..6b70e04 100644
--- a/src/Shader/Constants.hpp
+++ b/src/Shader/Constants.hpp
@@ -22,7 +22,7 @@
 	struct Constants
 	{
 		Constants();
-	
+
 		unsigned int transposeBit0[16];
 		unsigned int transposeBit1[16];
 		unsigned int transposeBit2[16];
@@ -67,9 +67,9 @@
 		dword4 maskD01X[4];
 		word4 mask565Q[8];
 
-		unsigned short sRGBtoLinear8_12[256];
-		unsigned short sRGBtoLinear6_12[64];
-		unsigned short sRGBtoLinear5_12[32];
+		unsigned short sRGBtoLinear8_16[256];
+		unsigned short sRGBtoLinear6_16[64];
+		unsigned short sRGBtoLinear5_16[32];
 
 		unsigned short linearToSRGB12_16[4096];
 		unsigned short sRGBtoLinear12_16[4096];
diff --git a/src/Shader/SamplerCore.cpp b/src/Shader/SamplerCore.cpp
index a50c1e8..8aebbd8 100644
--- a/src/Shader/SamplerCore.cpp
+++ b/src/Shader/SamplerCore.cpp
@@ -133,46 +133,23 @@
 
 			if(fixed12 && !hasFloatTexture())
 			{
-				if(has16bitTextureFormat())
+				if(state.textureFormat == FORMAT_R5G6B5)
 				{
-					switch(state.textureFormat)
-					{
-					case FORMAT_R5G6B5:
-						if(state.sRGB)
-						{
-							sRGBtoLinear16_5_12(c.x);
-							sRGBtoLinear16_6_12(c.y);
-							sRGBtoLinear16_5_12(c.z);
-						}
-						else
-						{
-							c.x = MulHigh(As<UShort4>(c.x), UShort4(0x10000000 / 0xF800));
-							c.y = MulHigh(As<UShort4>(c.y), UShort4(0x10000000 / 0xFC00));
-							c.z = MulHigh(As<UShort4>(c.z), UShort4(0x10000000 / 0xF800));
-						}
-						break;
-					default:
-						ASSERT(false);
-					}
+					c.x = MulHigh(As<UShort4>(c.x), UShort4(0x10000000 / 0xF800));
+					c.y = MulHigh(As<UShort4>(c.y), UShort4(0x10000000 / 0xFC00));
+					c.z = MulHigh(As<UShort4>(c.z), UShort4(0x10000000 / 0xF800));
 				}
 				else
 				{
 					for(int component = 0; component < textureComponentCount(); component++)
 					{
-						if(state.sRGB && isRGBComponent(component))
+						if(hasUnsignedTextureComponent(component))
 						{
-							sRGBtoLinear16_8_12(c[component]);   // FIXME: Perform linearization at surface level for read-only textures
+							c[component] = As<UShort4>(c[component]) >> 4;
 						}
 						else
 						{
-							if(hasUnsignedTextureComponent(component))
-							{
-								c[component] = As<UShort4>(c[component]) >> 4;
-							}
-							else
-							{
-								c[component] = c[component] >> 3;
-							}
+							c[component] = c[component] >> 3;
 						}
 					}
 				}
@@ -316,8 +293,8 @@
 		}
 		else
 		{
-			// FIXME: YUV and sRGB are not supported by the floating point path
-			bool forceFloatFiltering = state.highPrecisionFiltering && !state.sRGB && !hasYuvFormat() && (state.textureFilter != FILTER_POINT);
+			// FIXME: YUV is not supported by the floating point path
+			bool forceFloatFiltering = state.highPrecisionFiltering && !hasYuvFormat() && (state.textureFilter != FILTER_POINT);
 			bool seamlessCube = (state.addressingModeU == ADDRESSING_SEAMLESS);
 			if(hasFloatTexture() || hasUnnormalizedIntegerTexture() || forceFloatFiltering || seamlessCube)   // FIXME: Mostly identical to integer sampling
 			{
@@ -380,52 +357,23 @@
 			{
 				Vector4s cs = sampleTexture(texture, u, v, w, q, bias, dsx, dsy, offset, function, false);
 
-				if(has16bitTextureFormat())
+				if(state.textureFormat ==  FORMAT_R5G6B5)
 				{
-					switch(state.textureFormat)
-					{
-					case FORMAT_R5G6B5:
-						if(state.sRGB)
-						{
-							sRGBtoLinear16_5_12(cs.x);
-							sRGBtoLinear16_6_12(cs.y);
-							sRGBtoLinear16_5_12(cs.z);
-
-							convertSigned12(c.x, cs.x);
-							convertSigned12(c.y, cs.y);
-							convertSigned12(c.z, cs.z);
-						}
-						else
-						{
-							c.x = Float4(As<UShort4>(cs.x)) * Float4(1.0f / 0xF800);
-							c.y = Float4(As<UShort4>(cs.y)) * Float4(1.0f / 0xFC00);
-							c.z = Float4(As<UShort4>(cs.z)) * Float4(1.0f / 0xF800);
-						}
-						break;
-					default:
-						ASSERT(false);
-					}
+					c.x = Float4(As<UShort4>(cs.x)) * Float4(1.0f / 0xF800);
+					c.y = Float4(As<UShort4>(cs.y)) * Float4(1.0f / 0xFC00);
+					c.z = Float4(As<UShort4>(cs.z)) * Float4(1.0f / 0xF800);
 				}
 				else
 				{
 					for(int component = 0; component < textureComponentCount(); component++)
 					{
-						// Normalized integer formats
-						if(state.sRGB && isRGBComponent(component))
+						if(hasUnsignedTextureComponent(component))
 						{
-							sRGBtoLinear16_8_12(cs[component]);   // FIXME: Perform linearization at surface level for read-only textures
-							convertSigned12(c[component], cs[component]);
+							convertUnsigned16(c[component], cs[component]);
 						}
 						else
 						{
-							if(hasUnsignedTextureComponent(component))
-							{
-								convertUnsigned16(c[component], cs[component]);
-							}
-							else
-							{
-								convertSigned15(c[component], cs[component]);
-							}
+							convertSigned15(c[component], cs[component]);
 						}
 					}
 				}
@@ -2036,6 +1984,26 @@
 		}
 		else ASSERT(false);
 
+		if(state.sRGB)
+		{
+			if(state.textureFormat == FORMAT_R5G6B5)
+			{
+				sRGBtoLinear16_5_16(c.x);
+				sRGBtoLinear16_6_16(c.y);
+				sRGBtoLinear16_5_16(c.z);
+			}
+			else
+			{
+				for(int i = 0; i < textureComponentCount(); i++)
+				{
+					if(isRGBComponent(i))
+					{
+						sRGBtoLinear16_8_16(c[i]);
+					}
+				}
+			}
+		}
+
 		return c;
 	}
 
@@ -2238,7 +2206,7 @@
 
 			bool isInteger = Surface::isNonNormalizedInteger(state.textureFormat);
 			int componentCount = textureComponentCount();
-			for(int n = 0; n < componentCount; ++n)
+			for(int n = 0; n < componentCount; n++)
 			{
 				if(hasUnsignedTextureComponent(n))
 				{
@@ -2555,11 +2523,11 @@
 		cf = Float4(As<UShort4>(cs)) * Float4(1.0f / 0xFFFF);
 	}
 
-	void SamplerCore::sRGBtoLinear16_8_12(Short4 &c)
+	void SamplerCore::sRGBtoLinear16_8_16(Short4 &c)
 	{
 		c = As<UShort4>(c) >> 8;
 
-		Pointer<Byte> LUT = Pointer<Byte>(constants + OFFSET(Constants,sRGBtoLinear8_12));
+		Pointer<Byte> LUT = Pointer<Byte>(constants + OFFSET(Constants,sRGBtoLinear8_16));
 
 		c = Insert(c, *Pointer<Short>(LUT + 2 * Int(Extract(c, 0))), 0);
 		c = Insert(c, *Pointer<Short>(LUT + 2 * Int(Extract(c, 1))), 1);
@@ -2567,11 +2535,11 @@
 		c = Insert(c, *Pointer<Short>(LUT + 2 * Int(Extract(c, 3))), 3);
 	}
 
-	void SamplerCore::sRGBtoLinear16_6_12(Short4 &c)
+	void SamplerCore::sRGBtoLinear16_6_16(Short4 &c)
 	{
 		c = As<UShort4>(c) >> 10;
 
-		Pointer<Byte> LUT = Pointer<Byte>(constants + OFFSET(Constants,sRGBtoLinear6_12));
+		Pointer<Byte> LUT = Pointer<Byte>(constants + OFFSET(Constants,sRGBtoLinear6_16));
 
 		c = Insert(c, *Pointer<Short>(LUT + 2 * Int(Extract(c, 0))), 0);
 		c = Insert(c, *Pointer<Short>(LUT + 2 * Int(Extract(c, 1))), 1);
@@ -2579,11 +2547,11 @@
 		c = Insert(c, *Pointer<Short>(LUT + 2 * Int(Extract(c, 3))), 3);
 	}
 
-	void SamplerCore::sRGBtoLinear16_5_12(Short4 &c)
+	void SamplerCore::sRGBtoLinear16_5_16(Short4 &c)
 	{
 		c = As<UShort4>(c) >> 11;
 
-		Pointer<Byte> LUT = Pointer<Byte>(constants + OFFSET(Constants,sRGBtoLinear5_12));
+		Pointer<Byte> LUT = Pointer<Byte>(constants + OFFSET(Constants,sRGBtoLinear5_16));
 
 		c = Insert(c, *Pointer<Short>(LUT + 2 * Int(Extract(c, 0))), 0);
 		c = Insert(c, *Pointer<Short>(LUT + 2 * Int(Extract(c, 1))), 1);
diff --git a/src/Shader/SamplerCore.hpp b/src/Shader/SamplerCore.hpp
index 6004bd7..684c1a7 100644
--- a/src/Shader/SamplerCore.hpp
+++ b/src/Shader/SamplerCore.hpp
@@ -92,9 +92,9 @@
 		void convertSigned12(Float4 &cf, Short4 &ci);
 		void convertSigned15(Float4 &cf, Short4 &ci);
 		void convertUnsigned16(Float4 &cf, Short4 &ci);
-		void sRGBtoLinear16_8_12(Short4 &c);
-		void sRGBtoLinear16_6_12(Short4 &c);
-		void sRGBtoLinear16_5_12(Short4 &c);
+		void sRGBtoLinear16_8_16(Short4 &c);
+		void sRGBtoLinear16_6_16(Short4 &c);
+		void sRGBtoLinear16_5_16(Short4 &c);
 
 		bool hasFloatTexture() const;
 		bool hasUnnormalizedIntegerTexture() const;