sw::Surface references removed from sampling code

vk::Format was expanded to include more format related checks and the
sampler object now uses them. Whether or not the Sampler code ends up
actually being used is unsure, but the code has been updated to use
vk::Image instead of sw::Surface. This should be the last sw::Surface
reference in Vulkan code.

Bug b/126883332

Change-Id: Ib1b4c3ce87d0fdad5ac7238b7e86211a499871a5
Reviewed-on: https://swiftshader-review.googlesource.com/c/SwiftShader/+/27490
Tested-by: Alexis Hétu <sugoi@google.com>
Presubmit-Ready: Alexis Hétu <sugoi@google.com>
Kokoro-Presubmit: kokoro <noreply+kokoro@google.com>
Reviewed-by: Nicolas Capens <nicolascapens@google.com>
diff --git a/src/Device/Blitter.hpp b/src/Device/Blitter.hpp
index 7c4ef7e..f842081 100644
--- a/src/Device/Blitter.hpp
+++ b/src/Device/Blitter.hpp
@@ -71,8 +71,8 @@
 				return memcmp(this, &state, sizeof(State)) == 0;
 			}
 
-			vk::Format sourceFormat = VK_FORMAT_UNDEFINED;
-			vk::Format destFormat = VK_FORMAT_UNDEFINED;
+			vk::Format sourceFormat;
+			vk::Format destFormat;
 			int destSamples = 0;
 		};
 
diff --git a/src/Device/Sampler.cpp b/src/Device/Sampler.cpp
index 841a15f..029f4c4 100644
--- a/src/Device/Sampler.cpp
+++ b/src/Device/Sampler.cpp
@@ -15,9 +15,9 @@
 #include "Sampler.hpp"
 
 #include "Context.hpp"
-#include "Surface.hpp"
 #include "Pipeline/PixelRoutine.hpp"
 #include "Vulkan/VkDebug.hpp"
+#include "Vulkan/VkImage.hpp"
 
 #include <cstring>
 
@@ -48,8 +48,7 @@
 			}
 		}
 
-		externalTextureFormat = VK_FORMAT_UNDEFINED;
-		internalTextureFormat = VK_FORMAT_UNDEFINED;
+		textureFormat = VK_FORMAT_UNDEFINED;
 		textureType = TEXTURE_NULL;
 
 		textureFilter = FILTER_LINEAR;
@@ -89,13 +88,13 @@
 		if(textureType != TEXTURE_NULL)
 		{
 			state.textureType = textureType;
-			state.textureFormat = internalTextureFormat;
+			state.textureFormat = textureFormat;
 			state.textureFilter = getTextureFilter();
 			state.addressingModeU = getAddressingModeU();
 			state.addressingModeV = getAddressingModeV();
 			state.addressingModeW = getAddressingModeW();
 			state.mipmapFilter = mipmapFilter();
-			state.sRGB = (sRGB && Surface::isSRGBreadable(externalTextureFormat)) || Surface::isSRGBformat(internalTextureFormat);
+			state.sRGB = (sRGB && textureFormat.isSRGBreadable()) || textureFormat.isSRGBformat();
 			state.swizzleR = swizzleR;
 			state.swizzleG = swizzleG;
 			state.swizzleB = swizzleB;
@@ -111,25 +110,33 @@
 		return state;
 	}
 
-	void Sampler::setTextureLevel(int face, int level, Surface *surface, TextureType type)
+	void Sampler::setTextureLevel(int face, int level, vk::Image *image, TextureType type)
 	{
-		if(surface)
+		if(image)
 		{
 			Mipmap &mipmap = texture.mipmap[level];
 
-			border = surface->getBorder();
-			mipmap.buffer[face] = surface->lockInternal(-border, -border, 0, LOCK_UNLOCKED, PRIVATE);
+			border = image->isCube() ? 1 : 0;
+			VkImageSubresourceLayers subresourceLayers =
+			{
+				VK_IMAGE_ASPECT_COLOR_BIT,
+				static_cast<uint32_t>(level),
+				static_cast<uint32_t>(face),
+				1
+			};
+			mipmap.buffer[face] = image->getTexelPointer({ -border, -border, 0 }, subresourceLayers);
 
 			if(face == 0)
 			{
-				externalTextureFormat = surface->getExternalFormat();
-				internalTextureFormat = surface->getInternalFormat();
+				VkImageAspectFlagBits aspect = VK_IMAGE_ASPECT_COLOR_BIT; // FIXME: get proper aspect
+				textureFormat = image->getFormat(aspect);
 
-				int width = surface->getWidth();
-				int height = surface->getHeight();
-				int depth = surface->getDepth();
-				int pitchP = surface->getInternalPitchP();
-				int sliceP = surface->getInternalSliceP();
+				VkExtent3D mipLevelExtent = image->getMipLevelExtent(level);
+				int width = mipLevelExtent.width;
+				int height = mipLevelExtent.height;
+				int depth = mipLevelExtent.depth;
+				int pitchP = image->rowPitchBytes(aspect, level);
+				int sliceP = image->slicePitchBytes(aspect, level);
 
 				if(level == 0)
 				{
@@ -154,7 +161,7 @@
 					texture.depthLOD[3] = depth * exp2LOD;
 				}
 
-				if(Surface::isFloatFormat(internalTextureFormat))
+				if(textureFormat.isFloatFormat())
 				{
 					mipmap.fWidth[0] = (float)width / 65536.0f;
 					mipmap.fWidth[1] = (float)width / 65536.0f;
@@ -221,7 +228,7 @@
 				mipmap.sliceP[2] = sliceP;
 				mipmap.sliceP[3] = sliceP;
 
-				if(internalTextureFormat == VK_FORMAT_G8_B8R8_2PLANE_420_UNORM)
+				if(textureFormat.hasYuvFormat())
 				{
 					unsigned int YStride = pitchP;
 					unsigned int YSize = YStride * height;
@@ -382,10 +389,10 @@
 
 	bool Sampler::hasUnsignedTexture() const
 	{
-		return Surface::isUnsignedComponent(internalTextureFormat, 0) &&
-		       Surface::isUnsignedComponent(internalTextureFormat, 1) &&
-		       Surface::isUnsignedComponent(internalTextureFormat, 2) &&
-		       Surface::isUnsignedComponent(internalTextureFormat, 3);
+		return textureFormat.isUnsignedComponent(0) &&
+		       textureFormat.isUnsignedComponent(1) &&
+		       textureFormat.isUnsignedComponent(2) &&
+		       textureFormat.isUnsignedComponent(3);
 	}
 
 	bool Sampler::hasCubeTexture() const
@@ -438,7 +445,7 @@
 
 		FilterType filter = textureFilter;
 
-		if(gather && Surface::componentCount(internalTextureFormat) == 1)
+		if(gather && textureFormat.componentCount() == 1)
 		{
 			filter = FILTER_GATHER;
 		}
diff --git a/src/Device/Sampler.hpp b/src/Device/Sampler.hpp
index 01e81f7..f86db85 100644
--- a/src/Device/Sampler.hpp
+++ b/src/Device/Sampler.hpp
@@ -15,9 +15,15 @@
 #ifndef sw_Sampler_hpp
 #define sw_Sampler_hpp
 
+#include "Device/Color.hpp"
 #include "Device/Config.hpp"
-#include "Device/Surface.hpp"
 #include "System/Types.hpp"
+#include "Vulkan/VkFormat.h"
+
+namespace vk
+{
+	class Image;
+}
 
 namespace sw
 {
@@ -147,7 +153,7 @@
 			State();
 
 			TextureType textureType;
-			VkFormat textureFormat;
+			vk::Format textureFormat;
 			FilterType textureFilter;
 			AddressingMode addressingModeU;
 			AddressingMode addressingModeV;
@@ -172,7 +178,7 @@
 
 		State samplerState() const;
 
-		void setTextureLevel(int face, int level, Surface *surface, TextureType type);
+		void setTextureLevel(int face, int level, vk::Image *image, TextureType type);
 
 		void setTextureFilter(FilterType textureFilter);
 		void setMipmapFilter(MipmapType mipmapFilter);
@@ -214,8 +220,7 @@
 		AddressingMode getAddressingModeW() const;
 		CompareFunc getCompareFunc() const;
 
-		VkFormat externalTextureFormat;
-		VkFormat internalTextureFormat;
+		vk::Format textureFormat;
 		TextureType textureType;
 
 		FilterType textureFilter;
diff --git a/src/Pipeline/SamplerCore.cpp b/src/Pipeline/SamplerCore.cpp
index c6bf10b..d799252 100644
--- a/src/Pipeline/SamplerCore.cpp
+++ b/src/Pipeline/SamplerCore.cpp
@@ -2057,7 +2057,7 @@
 
 			Vector4s cs = sampleTexel(index, buffer);
 
-			bool isInteger = Surface::isNonNormalizedInteger(state.textureFormat);
+			bool isInteger = state.textureFormat.isNonNormalizedInteger();
 			int componentCount = textureComponentCount();
 			for(int n = 0; n < componentCount; n++)
 			{
@@ -2426,22 +2426,22 @@
 
 	bool SamplerCore::hasFloatTexture() const
 	{
-		return Surface::isFloatFormat(state.textureFormat);
+		return state.textureFormat.isFloatFormat();
 	}
 
 	bool SamplerCore::hasUnnormalizedIntegerTexture() const
 	{
-		return Surface::isNonNormalizedInteger(state.textureFormat);
+		return state.textureFormat.isNonNormalizedInteger();
 	}
 
 	bool SamplerCore::hasUnsignedTextureComponent(int component) const
 	{
-		return Surface::isUnsignedComponent(state.textureFormat, component);
+		return state.textureFormat.isUnsignedComponent(component);
 	}
 
 	int SamplerCore::textureComponentCount() const
 	{
-		return Surface::componentCount(state.textureFormat);
+		return state.textureFormat.componentCount();
 	}
 
 	bool SamplerCore::hasThirdCoordinate() const
@@ -2451,275 +2451,31 @@
 
 	bool SamplerCore::has16bitTextureFormat() const
 	{
-		switch(state.textureFormat)
-		{
-		case VK_FORMAT_R5G6B5_UNORM_PACK16:
-			return true;
-		case VK_FORMAT_R8_SNORM:
-		case VK_FORMAT_R8G8_SNORM:
-		case VK_FORMAT_R8G8B8A8_SNORM:
-		case VK_FORMAT_R8_SINT:
-		case VK_FORMAT_R8_UINT:
-		case VK_FORMAT_R8G8_SINT:
-		case VK_FORMAT_R8G8_UINT:
-		case VK_FORMAT_R8G8B8A8_SINT:
-		case VK_FORMAT_R8G8B8A8_UINT:
-		case VK_FORMAT_R32_SINT:
-		case VK_FORMAT_R32_UINT:
-		case VK_FORMAT_R32G32_SINT:
-		case VK_FORMAT_R32G32_UINT:
-		case VK_FORMAT_R32G32B32A32_SINT:
-		case VK_FORMAT_R32G32B32A32_UINT:
-		case VK_FORMAT_R8G8_UNORM:
-		case VK_FORMAT_B8G8R8A8_UNORM:
-		case VK_FORMAT_R8G8B8A8_UNORM:
-		case VK_FORMAT_R8G8B8A8_SRGB:
-		case VK_FORMAT_R32_SFLOAT:
-		case VK_FORMAT_R32G32_SFLOAT:
-		case VK_FORMAT_R32G32B32A32_SFLOAT:
-		case VK_FORMAT_R8_UNORM:
-		case VK_FORMAT_R16G16_UNORM:
-		case VK_FORMAT_R16G16B16A16_UNORM:
-		case VK_FORMAT_R16_SINT:
-		case VK_FORMAT_R16_UINT:
-		case VK_FORMAT_R16G16_SINT:
-		case VK_FORMAT_R16G16_UINT:
-		case VK_FORMAT_R16G16B16A16_SINT:
-		case VK_FORMAT_R16G16B16A16_UINT:
-		case VK_FORMAT_G8_B8R8_2PLANE_420_UNORM:
-			return false;
-		default:
-			ASSERT(false);
-		}
-
-		return false;
+		return state.textureFormat.has16bitTextureFormat();
 	}
 
 	bool SamplerCore::has8bitTextureComponents() const
 	{
-		switch(state.textureFormat)
-		{
-		case VK_FORMAT_R8G8_UNORM:
-		case VK_FORMAT_B8G8R8A8_UNORM:
-		case VK_FORMAT_R8G8B8A8_UNORM:
-		case VK_FORMAT_R8G8B8A8_SRGB:
-		case VK_FORMAT_R8_UNORM:
-		case VK_FORMAT_R8_SNORM:
-		case VK_FORMAT_R8G8_SNORM:
-		case VK_FORMAT_R8G8B8A8_SNORM:
-		case VK_FORMAT_R8_SINT:
-		case VK_FORMAT_R8_UINT:
-		case VK_FORMAT_R8G8_SINT:
-		case VK_FORMAT_R8G8_UINT:
-		case VK_FORMAT_R8G8B8A8_SINT:
-		case VK_FORMAT_R8G8B8A8_UINT:
-			return true;
-		case VK_FORMAT_R5G6B5_UNORM_PACK16:
-		case VK_FORMAT_R32_SFLOAT:
-		case VK_FORMAT_R32G32_SFLOAT:
-		case VK_FORMAT_R32G32B32A32_SFLOAT:
-		case VK_FORMAT_R16G16_UNORM:
-		case VK_FORMAT_R16G16B16A16_UNORM:
-		case VK_FORMAT_R32_SINT:
-		case VK_FORMAT_R32_UINT:
-		case VK_FORMAT_R32G32_SINT:
-		case VK_FORMAT_R32G32_UINT:
-		case VK_FORMAT_R32G32B32A32_SINT:
-		case VK_FORMAT_R32G32B32A32_UINT:
-		case VK_FORMAT_R16_SINT:
-		case VK_FORMAT_R16_UINT:
-		case VK_FORMAT_R16G16_SINT:
-		case VK_FORMAT_R16G16_UINT:
-		case VK_FORMAT_R16G16B16A16_SINT:
-		case VK_FORMAT_R16G16B16A16_UINT:
-		case VK_FORMAT_G8_B8R8_2PLANE_420_UNORM:
-			return false;
-		default:
-			ASSERT(false);
-		}
-
-		return false;
+		return state.textureFormat.has8bitTextureComponents();
 	}
 
 	bool SamplerCore::has16bitTextureComponents() const
 	{
-		switch(state.textureFormat)
-		{
-		case VK_FORMAT_R5G6B5_UNORM_PACK16:
-		case VK_FORMAT_R8_SNORM:
-		case VK_FORMAT_R8G8_SNORM:
-		case VK_FORMAT_R8G8B8A8_SNORM:
-		case VK_FORMAT_R8_SINT:
-		case VK_FORMAT_R8_UINT:
-		case VK_FORMAT_R8G8_SINT:
-		case VK_FORMAT_R8G8_UINT:
-		case VK_FORMAT_R8G8B8A8_SINT:
-		case VK_FORMAT_R8G8B8A8_UINT:
-		case VK_FORMAT_R32_SINT:
-		case VK_FORMAT_R32_UINT:
-		case VK_FORMAT_R32G32_SINT:
-		case VK_FORMAT_R32G32_UINT:
-		case VK_FORMAT_R32G32B32A32_SINT:
-		case VK_FORMAT_R32G32B32A32_UINT:
-		case VK_FORMAT_R8G8_UNORM:
-		case VK_FORMAT_B8G8R8A8_UNORM:
-		case VK_FORMAT_R8G8B8A8_UNORM:
-		case VK_FORMAT_R8G8B8A8_SRGB:
-		case VK_FORMAT_R32_SFLOAT:
-		case VK_FORMAT_R32G32_SFLOAT:
-		case VK_FORMAT_R32G32B32A32_SFLOAT:
-		case VK_FORMAT_R8_UNORM:
-		case VK_FORMAT_G8_B8R8_2PLANE_420_UNORM:
-			return false;
-		case VK_FORMAT_R16G16_UNORM:
-		case VK_FORMAT_R16G16B16A16_UNORM:
-		case VK_FORMAT_R16_SINT:
-		case VK_FORMAT_R16_UINT:
-		case VK_FORMAT_R16G16_SINT:
-		case VK_FORMAT_R16G16_UINT:
-		case VK_FORMAT_R16G16B16A16_SINT:
-		case VK_FORMAT_R16G16B16A16_UINT:
-			return true;
-		default:
-			ASSERT(false);
-		}
-
-		return false;
+		return state.textureFormat.has16bitTextureComponents();
 	}
 
 	bool SamplerCore::has32bitIntegerTextureComponents() const
 	{
-		switch(state.textureFormat)
-		{
-		case VK_FORMAT_R5G6B5_UNORM_PACK16:
-		case VK_FORMAT_R8_SNORM:
-		case VK_FORMAT_R8G8_SNORM:
-		case VK_FORMAT_R8G8B8A8_SNORM:
-		case VK_FORMAT_R8_SINT:
-		case VK_FORMAT_R8_UINT:
-		case VK_FORMAT_R8G8_SINT:
-		case VK_FORMAT_R8G8_UINT:
-		case VK_FORMAT_R8G8B8A8_SINT:
-		case VK_FORMAT_R8G8B8A8_UINT:
-		case VK_FORMAT_R8G8_UNORM:
-		case VK_FORMAT_B8G8R8A8_UNORM:
-		case VK_FORMAT_R8G8B8A8_UNORM:
-		case VK_FORMAT_R8G8B8A8_SRGB:
-		case VK_FORMAT_R16G16_UNORM:
-		case VK_FORMAT_R16G16B16A16_UNORM:
-		case VK_FORMAT_R16_SINT:
-		case VK_FORMAT_R16_UINT:
-		case VK_FORMAT_R16G16_SINT:
-		case VK_FORMAT_R16G16_UINT:
-		case VK_FORMAT_R16G16B16A16_SINT:
-		case VK_FORMAT_R16G16B16A16_UINT:
-		case VK_FORMAT_R32_SFLOAT:
-		case VK_FORMAT_R32G32_SFLOAT:
-		case VK_FORMAT_R32G32B32A32_SFLOAT:
-		case VK_FORMAT_R8_UNORM:
-		case VK_FORMAT_G8_B8R8_2PLANE_420_UNORM:
-			return false;
-		case VK_FORMAT_R32_SINT:
-		case VK_FORMAT_R32_UINT:
-		case VK_FORMAT_R32G32_SINT:
-		case VK_FORMAT_R32G32_UINT:
-		case VK_FORMAT_R32G32B32A32_SINT:
-		case VK_FORMAT_R32G32B32A32_UINT:
-			return true;
-		default:
-			ASSERT(false);
-		}
-
-		return false;
+		return state.textureFormat.has32bitIntegerTextureComponents();
 	}
 
 	bool SamplerCore::hasYuvFormat() const
 	{
-		switch(state.textureFormat)
-		{
-		case VK_FORMAT_G8_B8R8_2PLANE_420_UNORM:
-			return true;
-		case VK_FORMAT_R5G6B5_UNORM_PACK16:
-		case VK_FORMAT_R8_SNORM:
-		case VK_FORMAT_R8G8_SNORM:
-		case VK_FORMAT_R8G8B8A8_SNORM:
-		case VK_FORMAT_R8_SINT:
-		case VK_FORMAT_R8_UINT:
-		case VK_FORMAT_R8G8_SINT:
-		case VK_FORMAT_R8G8_UINT:
-		case VK_FORMAT_R8G8B8A8_SINT:
-		case VK_FORMAT_R8G8B8A8_UINT:
-		case VK_FORMAT_R32_SINT:
-		case VK_FORMAT_R32_UINT:
-		case VK_FORMAT_R32G32_SINT:
-		case VK_FORMAT_R32G32_UINT:
-		case VK_FORMAT_R32G32B32A32_SINT:
-		case VK_FORMAT_R32G32B32A32_UINT:
-		case VK_FORMAT_R8G8_UNORM:
-		case VK_FORMAT_B8G8R8A8_UNORM:
-		case VK_FORMAT_R8G8B8A8_UNORM:
-		case VK_FORMAT_R8G8B8A8_SRGB:
-		case VK_FORMAT_R32_SFLOAT:
-		case VK_FORMAT_R32G32_SFLOAT:
-		case VK_FORMAT_R32G32B32A32_SFLOAT:
-		case VK_FORMAT_R8_UNORM:
-		case VK_FORMAT_R16G16_UNORM:
-		case VK_FORMAT_R16G16B16A16_UNORM:
-		case VK_FORMAT_R16_SINT:
-		case VK_FORMAT_R16_UINT:
-		case VK_FORMAT_R16G16_SINT:
-		case VK_FORMAT_R16G16_UINT:
-		case VK_FORMAT_R16G16B16A16_SINT:
-		case VK_FORMAT_R16G16B16A16_UINT:
-			return false;
-		default:
-			ASSERT(false);
-		}
-
-		return false;
+		return state.textureFormat.hasYuvFormat();
 	}
 
 	bool SamplerCore::isRGBComponent(int component) const
 	{
-		switch(state.textureFormat)
-		{
-		case VK_FORMAT_R5G6B5_UNORM_PACK16:      return component < 3;
-		case VK_FORMAT_R8_SNORM:                 return component < 1;
-		case VK_FORMAT_R8G8_SNORM:               return component < 2;
-		case VK_FORMAT_R8G8B8A8_SNORM:           return component < 3;
-		case VK_FORMAT_R8_SINT:                  return component < 1;
-		case VK_FORMAT_R8_UINT:                  return component < 1;
-		case VK_FORMAT_R8G8_SINT:                return component < 2;
-		case VK_FORMAT_R8G8_UINT:                return component < 2;
-		case VK_FORMAT_R8G8B8A8_SINT:            return component < 3;
-		case VK_FORMAT_R8G8B8A8_UINT:            return component < 3;
-		case VK_FORMAT_R32_SINT:                 return component < 1;
-		case VK_FORMAT_R32_UINT:                 return component < 1;
-		case VK_FORMAT_R32G32_SINT:              return component < 2;
-		case VK_FORMAT_R32G32_UINT:              return component < 2;
-		case VK_FORMAT_R32G32B32A32_SINT:        return component < 3;
-		case VK_FORMAT_R32G32B32A32_UINT:        return component < 3;
-		case VK_FORMAT_R8G8_UNORM:               return component < 2;
-		case VK_FORMAT_B8G8R8A8_UNORM:           return component < 3;
-		case VK_FORMAT_R8G8B8A8_UNORM:           return component < 3;
-		case VK_FORMAT_R8G8B8A8_SRGB:            return component < 3;
-		case VK_FORMAT_R32_SFLOAT:               return component < 1;
-		case VK_FORMAT_R32G32_SFLOAT:            return component < 2;
-		case VK_FORMAT_R32G32B32A32_SFLOAT:      return component < 3;
-		case VK_FORMAT_R8_UNORM:                 return component < 1;
-		case VK_FORMAT_R16G16_UNORM:             return component < 2;
-		case VK_FORMAT_R16G16B16A16_UNORM:       return component < 3;
-		case VK_FORMAT_R16_SINT:                 return component < 1;
-		case VK_FORMAT_R16_UINT:                 return component < 1;
-		case VK_FORMAT_R16G16_SINT:              return component < 2;
-		case VK_FORMAT_R16G16_UINT:              return component < 2;
-		case VK_FORMAT_R16G16B16A16_SINT:        return component < 3;
-		case VK_FORMAT_R16G16B16A16_UINT:        return component < 3;
-		case VK_FORMAT_G8_B8R8_2PLANE_420_UNORM: return component < 3;
-		default:
-			ASSERT(false);
-		}
-
-		return false;
+		return state.textureFormat.isRGBComponent(component);
 	}
 }
diff --git a/src/Vulkan/VkFormat.cpp b/src/Vulkan/VkFormat.cpp
index 1309db8..79e3d23 100644
--- a/src/Vulkan/VkFormat.cpp
+++ b/src/Vulkan/VkFormat.cpp
@@ -142,6 +142,21 @@
 	}
 }
 
+bool Format::isSRGBreadable() const
+{
+	// Keep in sync with Capabilities::isSRGBreadable
+	switch(format)
+	{
+	case VK_FORMAT_B8G8R8A8_UNORM:
+	case VK_FORMAT_R8G8B8A8_UNORM:
+	case VK_FORMAT_R8G8B8A8_SRGB:
+	case VK_FORMAT_R5G6B5_UNORM_PACK16:
+	case VK_FORMAT_A1R5G5B5_UNORM_PACK16:
+		return true;
+	default:
+		return false;
+	}
+}
 
 bool Format::isSRGBwritable() const
 {
@@ -302,6 +317,153 @@
 
 	return false;
 }
+
+int Format::componentCount() const
+{
+	switch(format)
+	{
+	case VK_FORMAT_R8_UNORM:
+	case VK_FORMAT_R8_SNORM:
+	case VK_FORMAT_R8_USCALED:
+	case VK_FORMAT_R8_SSCALED:
+	case VK_FORMAT_R8_UINT:
+	case VK_FORMAT_R8_SINT:
+	case VK_FORMAT_R8_SRGB:
+	case VK_FORMAT_R16_UNORM:
+	case VK_FORMAT_R16_SNORM:
+	case VK_FORMAT_R16_USCALED:
+	case VK_FORMAT_R16_SSCALED:
+	case VK_FORMAT_R16_UINT:
+	case VK_FORMAT_R16_SINT:
+	case VK_FORMAT_R16_SFLOAT:
+	case VK_FORMAT_R32_UINT:
+	case VK_FORMAT_R32_SINT:
+	case VK_FORMAT_R32_SFLOAT:
+	case VK_FORMAT_R64_UINT:
+	case VK_FORMAT_R64_SINT:
+	case VK_FORMAT_R64_SFLOAT:
+	case VK_FORMAT_D16_UNORM:
+	case VK_FORMAT_X8_D24_UNORM_PACK32:
+	case VK_FORMAT_D32_SFLOAT:
+	case VK_FORMAT_S8_UINT:
+	case VK_FORMAT_D16_UNORM_S8_UINT:
+	case VK_FORMAT_D24_UNORM_S8_UINT:
+	case VK_FORMAT_D32_SFLOAT_S8_UINT:
+		return 1;
+	case VK_FORMAT_R4G4_UNORM_PACK8:
+	case VK_FORMAT_R8G8_UNORM:
+	case VK_FORMAT_R8G8_SNORM:
+	case VK_FORMAT_R8G8_USCALED:
+	case VK_FORMAT_R8G8_SSCALED:
+	case VK_FORMAT_R8G8_UINT:
+	case VK_FORMAT_R8G8_SINT:
+	case VK_FORMAT_R8G8_SRGB:
+	case VK_FORMAT_R16G16_UNORM:
+	case VK_FORMAT_R16G16_SNORM:
+	case VK_FORMAT_R16G16_USCALED:
+	case VK_FORMAT_R16G16_SSCALED:
+	case VK_FORMAT_R16G16_UINT:
+	case VK_FORMAT_R16G16_SINT:
+	case VK_FORMAT_R16G16_SFLOAT:
+	case VK_FORMAT_R32G32_UINT:
+	case VK_FORMAT_R32G32_SINT:
+	case VK_FORMAT_R32G32_SFLOAT:
+	case VK_FORMAT_R64G64_UINT:
+	case VK_FORMAT_R64G64_SINT:
+	case VK_FORMAT_R64G64_SFLOAT:
+		return 2;
+	case VK_FORMAT_R5G6B5_UNORM_PACK16:
+	case VK_FORMAT_B5G6R5_UNORM_PACK16:
+	case VK_FORMAT_R8G8B8_UNORM:
+	case VK_FORMAT_R8G8B8_SNORM:
+	case VK_FORMAT_R8G8B8_USCALED:
+	case VK_FORMAT_R8G8B8_SSCALED:
+	case VK_FORMAT_R8G8B8_UINT:
+	case VK_FORMAT_R8G8B8_SINT:
+	case VK_FORMAT_R8G8B8_SRGB:
+	case VK_FORMAT_B8G8R8_UNORM:
+	case VK_FORMAT_B8G8R8_SNORM:
+	case VK_FORMAT_B8G8R8_USCALED:
+	case VK_FORMAT_B8G8R8_SSCALED:
+	case VK_FORMAT_B8G8R8_UINT:
+	case VK_FORMAT_B8G8R8_SINT:
+	case VK_FORMAT_B8G8R8_SRGB:
+	case VK_FORMAT_R16G16B16_UNORM:
+	case VK_FORMAT_R16G16B16_SNORM:
+	case VK_FORMAT_R16G16B16_USCALED:
+	case VK_FORMAT_R16G16B16_SSCALED:
+	case VK_FORMAT_R16G16B16_UINT:
+	case VK_FORMAT_R16G16B16_SINT:
+	case VK_FORMAT_R16G16B16_SFLOAT:
+	case VK_FORMAT_R32G32B32_UINT:
+	case VK_FORMAT_R32G32B32_SINT:
+	case VK_FORMAT_R32G32B32_SFLOAT:
+	case VK_FORMAT_R64G64B64_UINT:
+	case VK_FORMAT_R64G64B64_SINT:
+	case VK_FORMAT_R64G64B64_SFLOAT:
+	case VK_FORMAT_B10G11R11_UFLOAT_PACK32:
+	case VK_FORMAT_E5B9G9R9_UFLOAT_PACK32:
+	case VK_FORMAT_G8_B8R8_2PLANE_420_UNORM:
+		return 3;
+	case VK_FORMAT_R4G4B4A4_UNORM_PACK16:
+	case VK_FORMAT_B4G4R4A4_UNORM_PACK16:
+	case VK_FORMAT_R5G5B5A1_UNORM_PACK16:
+	case VK_FORMAT_B5G5R5A1_UNORM_PACK16:
+	case VK_FORMAT_A1R5G5B5_UNORM_PACK16:
+	case VK_FORMAT_R8G8B8A8_UNORM:
+	case VK_FORMAT_R8G8B8A8_SNORM:
+	case VK_FORMAT_R8G8B8A8_USCALED:
+	case VK_FORMAT_R8G8B8A8_SSCALED:
+	case VK_FORMAT_R8G8B8A8_UINT:
+	case VK_FORMAT_R8G8B8A8_SINT:
+	case VK_FORMAT_R8G8B8A8_SRGB:
+	case VK_FORMAT_B8G8R8A8_UNORM:
+	case VK_FORMAT_B8G8R8A8_SNORM:
+	case VK_FORMAT_B8G8R8A8_USCALED:
+	case VK_FORMAT_B8G8R8A8_SSCALED:
+	case VK_FORMAT_B8G8R8A8_UINT:
+	case VK_FORMAT_B8G8R8A8_SINT:
+	case VK_FORMAT_B8G8R8A8_SRGB:
+	case VK_FORMAT_A8B8G8R8_UNORM_PACK32:
+	case VK_FORMAT_A8B8G8R8_SNORM_PACK32:
+	case VK_FORMAT_A8B8G8R8_USCALED_PACK32:
+	case VK_FORMAT_A8B8G8R8_SSCALED_PACK32:
+	case VK_FORMAT_A8B8G8R8_UINT_PACK32:
+	case VK_FORMAT_A8B8G8R8_SINT_PACK32:
+	case VK_FORMAT_A8B8G8R8_SRGB_PACK32:
+	case VK_FORMAT_A2R10G10B10_UNORM_PACK32:
+	case VK_FORMAT_A2R10G10B10_SNORM_PACK32:
+	case VK_FORMAT_A2R10G10B10_USCALED_PACK32:
+	case VK_FORMAT_A2R10G10B10_SSCALED_PACK32:
+	case VK_FORMAT_A2R10G10B10_UINT_PACK32:
+	case VK_FORMAT_A2R10G10B10_SINT_PACK32:
+	case VK_FORMAT_A2B10G10R10_UNORM_PACK32:
+	case VK_FORMAT_A2B10G10R10_SNORM_PACK32:
+	case VK_FORMAT_A2B10G10R10_USCALED_PACK32:
+	case VK_FORMAT_A2B10G10R10_SSCALED_PACK32:
+	case VK_FORMAT_A2B10G10R10_UINT_PACK32:
+	case VK_FORMAT_A2B10G10R10_SINT_PACK32:
+	case VK_FORMAT_R16G16B16A16_UNORM:
+	case VK_FORMAT_R16G16B16A16_SNORM:
+	case VK_FORMAT_R16G16B16A16_USCALED:
+	case VK_FORMAT_R16G16B16A16_SSCALED:
+	case VK_FORMAT_R16G16B16A16_UINT:
+	case VK_FORMAT_R16G16B16A16_SINT:
+	case VK_FORMAT_R16G16B16A16_SFLOAT:
+	case VK_FORMAT_R32G32B32A32_UINT:
+	case VK_FORMAT_R32G32B32A32_SINT:
+	case VK_FORMAT_R32G32B32A32_SFLOAT:
+	case VK_FORMAT_R64G64B64A64_UINT:
+	case VK_FORMAT_R64G64B64A64_SINT:
+	case VK_FORMAT_R64G64B64A64_SFLOAT:
+		return 4;
+	default:
+		ASSERT(false);
+	}
+
+	return 1;
+}
+
 bool Format::isUnsignedComponent(int component) const
 {
 	switch(format)
@@ -947,4 +1109,281 @@
 	return true;
 }
 
+bool Format::has16bitTextureFormat() const
+{
+	switch(format)
+	{
+	case VK_FORMAT_R5G6B5_UNORM_PACK16:
+		return true;
+	case VK_FORMAT_R8_SNORM:
+	case VK_FORMAT_R8G8_SNORM:
+	case VK_FORMAT_R8G8B8A8_SNORM:
+	case VK_FORMAT_R8_SINT:
+	case VK_FORMAT_R8_UINT:
+	case VK_FORMAT_R8G8_SINT:
+	case VK_FORMAT_R8G8_UINT:
+	case VK_FORMAT_R8G8B8A8_SINT:
+	case VK_FORMAT_R8G8B8A8_UINT:
+	case VK_FORMAT_R32_SINT:
+	case VK_FORMAT_R32_UINT:
+	case VK_FORMAT_R32G32_SINT:
+	case VK_FORMAT_R32G32_UINT:
+	case VK_FORMAT_R32G32B32A32_SINT:
+	case VK_FORMAT_R32G32B32A32_UINT:
+	case VK_FORMAT_R8G8_UNORM:
+	case VK_FORMAT_B8G8R8A8_UNORM:
+	case VK_FORMAT_R8G8B8A8_UNORM:
+	case VK_FORMAT_R8G8B8A8_SRGB:
+	case VK_FORMAT_R32_SFLOAT:
+	case VK_FORMAT_R32G32_SFLOAT:
+	case VK_FORMAT_R32G32B32A32_SFLOAT:
+	case VK_FORMAT_R8_UNORM:
+	case VK_FORMAT_R16G16_UNORM:
+	case VK_FORMAT_R16G16B16A16_UNORM:
+	case VK_FORMAT_R16_SINT:
+	case VK_FORMAT_R16_UINT:
+	case VK_FORMAT_R16G16_SINT:
+	case VK_FORMAT_R16G16_UINT:
+	case VK_FORMAT_R16G16B16A16_SINT:
+	case VK_FORMAT_R16G16B16A16_UINT:
+	case VK_FORMAT_G8_B8R8_2PLANE_420_UNORM:
+		return false;
+	default:
+		ASSERT(false);
+	}
+
+	return false;
+}
+
+bool Format::has8bitTextureComponents() const
+{
+	switch(format)
+	{
+	case VK_FORMAT_R8G8_UNORM:
+	case VK_FORMAT_B8G8R8A8_UNORM:
+	case VK_FORMAT_R8G8B8A8_UNORM:
+	case VK_FORMAT_R8G8B8A8_SRGB:
+	case VK_FORMAT_R8_UNORM:
+	case VK_FORMAT_R8_SNORM:
+	case VK_FORMAT_R8G8_SNORM:
+	case VK_FORMAT_R8G8B8A8_SNORM:
+	case VK_FORMAT_R8_SINT:
+	case VK_FORMAT_R8_UINT:
+	case VK_FORMAT_R8G8_SINT:
+	case VK_FORMAT_R8G8_UINT:
+	case VK_FORMAT_R8G8B8A8_SINT:
+	case VK_FORMAT_R8G8B8A8_UINT:
+		return true;
+	case VK_FORMAT_R5G6B5_UNORM_PACK16:
+	case VK_FORMAT_R32_SFLOAT:
+	case VK_FORMAT_R32G32_SFLOAT:
+	case VK_FORMAT_R32G32B32A32_SFLOAT:
+	case VK_FORMAT_R16G16_UNORM:
+	case VK_FORMAT_R16G16B16A16_UNORM:
+	case VK_FORMAT_R32_SINT:
+	case VK_FORMAT_R32_UINT:
+	case VK_FORMAT_R32G32_SINT:
+	case VK_FORMAT_R32G32_UINT:
+	case VK_FORMAT_R32G32B32A32_SINT:
+	case VK_FORMAT_R32G32B32A32_UINT:
+	case VK_FORMAT_R16_SINT:
+	case VK_FORMAT_R16_UINT:
+	case VK_FORMAT_R16G16_SINT:
+	case VK_FORMAT_R16G16_UINT:
+	case VK_FORMAT_R16G16B16A16_SINT:
+	case VK_FORMAT_R16G16B16A16_UINT:
+	case VK_FORMAT_G8_B8R8_2PLANE_420_UNORM:
+		return false;
+	default:
+		ASSERT(false);
+	}
+
+	return false;
+}
+
+bool Format::has16bitTextureComponents() const
+{
+	switch(format)
+	{
+	case VK_FORMAT_R5G6B5_UNORM_PACK16:
+	case VK_FORMAT_R8_SNORM:
+	case VK_FORMAT_R8G8_SNORM:
+	case VK_FORMAT_R8G8B8A8_SNORM:
+	case VK_FORMAT_R8_SINT:
+	case VK_FORMAT_R8_UINT:
+	case VK_FORMAT_R8G8_SINT:
+	case VK_FORMAT_R8G8_UINT:
+	case VK_FORMAT_R8G8B8A8_SINT:
+	case VK_FORMAT_R8G8B8A8_UINT:
+	case VK_FORMAT_R32_SINT:
+	case VK_FORMAT_R32_UINT:
+	case VK_FORMAT_R32G32_SINT:
+	case VK_FORMAT_R32G32_UINT:
+	case VK_FORMAT_R32G32B32A32_SINT:
+	case VK_FORMAT_R32G32B32A32_UINT:
+	case VK_FORMAT_R8G8_UNORM:
+	case VK_FORMAT_B8G8R8A8_UNORM:
+	case VK_FORMAT_R8G8B8A8_UNORM:
+	case VK_FORMAT_R8G8B8A8_SRGB:
+	case VK_FORMAT_R32_SFLOAT:
+	case VK_FORMAT_R32G32_SFLOAT:
+	case VK_FORMAT_R32G32B32A32_SFLOAT:
+	case VK_FORMAT_R8_UNORM:
+	case VK_FORMAT_G8_B8R8_2PLANE_420_UNORM:
+		return false;
+	case VK_FORMAT_R16G16_UNORM:
+	case VK_FORMAT_R16G16B16A16_UNORM:
+	case VK_FORMAT_R16_SINT:
+	case VK_FORMAT_R16_UINT:
+	case VK_FORMAT_R16G16_SINT:
+	case VK_FORMAT_R16G16_UINT:
+	case VK_FORMAT_R16G16B16A16_SINT:
+	case VK_FORMAT_R16G16B16A16_UINT:
+		return true;
+	default:
+		ASSERT(false);
+	}
+
+	return false;
+}
+
+bool Format::has32bitIntegerTextureComponents() const
+{
+	switch(format)
+	{
+	case VK_FORMAT_R5G6B5_UNORM_PACK16:
+	case VK_FORMAT_R8_SNORM:
+	case VK_FORMAT_R8G8_SNORM:
+	case VK_FORMAT_R8G8B8A8_SNORM:
+	case VK_FORMAT_R8_SINT:
+	case VK_FORMAT_R8_UINT:
+	case VK_FORMAT_R8G8_SINT:
+	case VK_FORMAT_R8G8_UINT:
+	case VK_FORMAT_R8G8B8A8_SINT:
+	case VK_FORMAT_R8G8B8A8_UINT:
+	case VK_FORMAT_R8G8_UNORM:
+	case VK_FORMAT_B8G8R8A8_UNORM:
+	case VK_FORMAT_R8G8B8A8_UNORM:
+	case VK_FORMAT_R8G8B8A8_SRGB:
+	case VK_FORMAT_R16G16_UNORM:
+	case VK_FORMAT_R16G16B16A16_UNORM:
+	case VK_FORMAT_R16_SINT:
+	case VK_FORMAT_R16_UINT:
+	case VK_FORMAT_R16G16_SINT:
+	case VK_FORMAT_R16G16_UINT:
+	case VK_FORMAT_R16G16B16A16_SINT:
+	case VK_FORMAT_R16G16B16A16_UINT:
+	case VK_FORMAT_R32_SFLOAT:
+	case VK_FORMAT_R32G32_SFLOAT:
+	case VK_FORMAT_R32G32B32A32_SFLOAT:
+	case VK_FORMAT_R8_UNORM:
+	case VK_FORMAT_G8_B8R8_2PLANE_420_UNORM:
+		return false;
+	case VK_FORMAT_R32_SINT:
+	case VK_FORMAT_R32_UINT:
+	case VK_FORMAT_R32G32_SINT:
+	case VK_FORMAT_R32G32_UINT:
+	case VK_FORMAT_R32G32B32A32_SINT:
+	case VK_FORMAT_R32G32B32A32_UINT:
+		return true;
+	default:
+		ASSERT(false);
+	}
+
+	return false;
+}
+
+bool Format::hasYuvFormat() const
+{
+	switch(format)
+	{
+	case VK_FORMAT_G8_B8R8_2PLANE_420_UNORM:
+		return true;
+	case VK_FORMAT_R5G6B5_UNORM_PACK16:
+	case VK_FORMAT_R8_SNORM:
+	case VK_FORMAT_R8G8_SNORM:
+	case VK_FORMAT_R8G8B8A8_SNORM:
+	case VK_FORMAT_R8_SINT:
+	case VK_FORMAT_R8_UINT:
+	case VK_FORMAT_R8G8_SINT:
+	case VK_FORMAT_R8G8_UINT:
+	case VK_FORMAT_R8G8B8A8_SINT:
+	case VK_FORMAT_R8G8B8A8_UINT:
+	case VK_FORMAT_R32_SINT:
+	case VK_FORMAT_R32_UINT:
+	case VK_FORMAT_R32G32_SINT:
+	case VK_FORMAT_R32G32_UINT:
+	case VK_FORMAT_R32G32B32A32_SINT:
+	case VK_FORMAT_R32G32B32A32_UINT:
+	case VK_FORMAT_R8G8_UNORM:
+	case VK_FORMAT_B8G8R8A8_UNORM:
+	case VK_FORMAT_R8G8B8A8_UNORM:
+	case VK_FORMAT_R8G8B8A8_SRGB:
+	case VK_FORMAT_R32_SFLOAT:
+	case VK_FORMAT_R32G32_SFLOAT:
+	case VK_FORMAT_R32G32B32A32_SFLOAT:
+	case VK_FORMAT_R8_UNORM:
+	case VK_FORMAT_R16G16_UNORM:
+	case VK_FORMAT_R16G16B16A16_UNORM:
+	case VK_FORMAT_R16_SINT:
+	case VK_FORMAT_R16_UINT:
+	case VK_FORMAT_R16G16_SINT:
+	case VK_FORMAT_R16G16_UINT:
+	case VK_FORMAT_R16G16B16A16_SINT:
+	case VK_FORMAT_R16G16B16A16_UINT:
+		return false;
+	default:
+		ASSERT(false);
+	}
+
+	return false;
+}
+
+bool Format::isRGBComponent(int component) const
+{
+	switch(format)
+	{
+	case VK_FORMAT_R8_SNORM:
+	case VK_FORMAT_R8_UNORM:
+	case VK_FORMAT_R8_SINT:
+	case VK_FORMAT_R8_UINT:
+	case VK_FORMAT_R16_SINT:
+	case VK_FORMAT_R16_UINT:
+	case VK_FORMAT_R32_SINT:
+	case VK_FORMAT_R32_UINT:
+	case VK_FORMAT_R32_SFLOAT:
+		return component < 1;
+	case VK_FORMAT_R8G8_SNORM:
+	case VK_FORMAT_R8G8_UNORM:
+	case VK_FORMAT_R8G8_SINT:
+	case VK_FORMAT_R8G8_UINT:
+	case VK_FORMAT_R16G16_SINT:
+	case VK_FORMAT_R16G16_UINT:
+	case VK_FORMAT_R16G16_UNORM:
+	case VK_FORMAT_R32G32_SINT:
+	case VK_FORMAT_R32G32_UINT:
+	case VK_FORMAT_R32G32_SFLOAT:
+		return component < 2;
+	case VK_FORMAT_R5G6B5_UNORM_PACK16:
+	case VK_FORMAT_R8G8B8A8_SNORM:
+	case VK_FORMAT_R8G8B8A8_UNORM:
+	case VK_FORMAT_R8G8B8A8_SINT:
+	case VK_FORMAT_R8G8B8A8_UINT:
+	case VK_FORMAT_B8G8R8A8_UNORM:
+	case VK_FORMAT_R8G8B8A8_SRGB:
+	case VK_FORMAT_R16G16B16A16_UNORM:
+	case VK_FORMAT_R16G16B16A16_SINT:
+	case VK_FORMAT_R16G16B16A16_UINT:
+	case VK_FORMAT_R32G32B32A32_SINT:
+	case VK_FORMAT_R32G32B32A32_UINT:
+	case VK_FORMAT_R32G32B32A32_SFLOAT:
+	case VK_FORMAT_G8_B8R8_2PLANE_420_UNORM:
+		return component < 3;
+	default:
+		ASSERT(false);
+	}
+
+	return false;
+}
+
 } // namespace vk
\ No newline at end of file
diff --git a/src/Vulkan/VkFormat.h b/src/Vulkan/VkFormat.h
index 1cd16dd..595b48c4 100644
--- a/src/Vulkan/VkFormat.h
+++ b/src/Vulkan/VkFormat.h
@@ -28,6 +28,7 @@
 class Format
 {
 public:
+	Format() {}
 	Format(VkFormat format) : format(format) {}
 	inline operator VkFormat() const { return format; }
 
@@ -40,8 +41,11 @@
 	bool hasQuadLayout() const;
 
 	bool isSRGBformat() const;
+	bool isSRGBreadable() const;
 	bool isSRGBwritable() const;
 	bool isFloatFormat() const;
+
+	int componentCount() const;
 	bool isUnsignedComponent(int component) const;
 
 	int bytes() const;
@@ -49,8 +53,17 @@
 	int sliceB(int width, int height, int border, bool target) const;
 
 	bool getScale(sw::float4 &scale) const;
+
+	// Texture sampling utilities
+	bool has16bitTextureFormat() const;
+	bool has8bitTextureComponents() const;
+	bool has16bitTextureComponents() const;
+	bool has32bitIntegerTextureComponents() const;
+	bool hasYuvFormat() const;
+	bool isRGBComponent(int component) const;
+
 private:
-	VkFormat format;
+	VkFormat format = VK_FORMAT_UNDEFINED;
 };
 
 } // namespace vk
diff --git a/src/Vulkan/VkImage.hpp b/src/Vulkan/VkImage.hpp
index fa6f180..07f284b 100644
--- a/src/Vulkan/VkImage.hpp
+++ b/src/Vulkan/VkImage.hpp
@@ -84,7 +84,7 @@
 	VkDeviceSize             memoryOffset = 0;
 	VkImageCreateFlags       flags = 0;
 	VkImageType              imageType = VK_IMAGE_TYPE_2D;
-	Format                   format = VK_FORMAT_UNDEFINED;
+	Format                   format;
 	VkExtent3D               extent = {0, 0, 0};
 	uint32_t                 mipLevels = 0;
 	uint32_t                 arrayLayers = 0;
diff --git a/src/Vulkan/VkImageView.hpp b/src/Vulkan/VkImageView.hpp
index b94103e..1b58179 100644
--- a/src/Vulkan/VkImageView.hpp
+++ b/src/Vulkan/VkImageView.hpp
@@ -49,7 +49,7 @@
 
 	Image*                     image = nullptr;
 	VkImageViewType            viewType = VK_IMAGE_VIEW_TYPE_2D;
-	Format                     format = VK_FORMAT_UNDEFINED;
+	Format                     format;
 	VkComponentMapping         components = {};
 	VkImageSubresourceRange    subresourceRange = {};
 };
diff --git a/src/Vulkan/VkPipeline.cpp b/src/Vulkan/VkPipeline.cpp
index 1a28242..a0b6f2c 100644
--- a/src/Vulkan/VkPipeline.cpp
+++ b/src/Vulkan/VkPipeline.cpp
@@ -293,7 +293,7 @@
 		sw::Stream& input = context.input[desc.location];
 		input.count = getNumberOfChannels(desc.format);
 		input.type = getStreamType(desc.format);
-		input.normalized = !sw::Surface::isNonNormalizedInteger(desc.format);
+		input.normalized = !vk::Format(desc.format).isNonNormalizedInteger();
 		input.offset = desc.offset;
 		input.binding = desc.binding;
 		input.stride = bufferStrides[desc.binding];