ETC2 Image decompression and sampling

The ETC2 decoder already existed in SwiftShader, so this cl hooks it
up in vk::Image. In order to be transparent to the user, any time a
compressed image is created, another image is simultaneously created,
which will eventually contain the decompressed image.

Bug b/119620767

Tests: dEQP-VK.pipeline.sampler.view_type.2d.format.etc2_r8g8b8a1_unorm_block.*
Tests: dEQP-VK.pipeline.sampler.view_type.2d.format.etc2_r8g8b8a8_unorm_block.*
Tests: dEQP-VK.pipeline.sampler.view_type.2d.format.eac_r11g11_unorm_block.*

Change-Id: I8bfdccccd9cad30e5b707ba82aac2b581ec2a90e
Reviewed-on: https://swiftshader-review.googlesource.com/c/SwiftShader/+/29428
Presubmit-Ready: Alexis Hétu <sugoi@google.com>
Kokoro-Presubmit: kokoro <noreply+kokoro@google.com>
Reviewed-by: Nicolas Capens <nicolascapens@google.com>
Tested-by: Alexis Hétu <sugoi@google.com>
diff --git a/src/Vulkan/VkDescriptorSetLayout.cpp b/src/Vulkan/VkDescriptorSetLayout.cpp
index 25c1a9a..e0566a1 100644
--- a/src/Vulkan/VkDescriptorSetLayout.cpp
+++ b/src/Vulkan/VkDescriptorSetLayout.cpp
@@ -341,24 +341,24 @@
 						VkOffset3D offset = {-1, -1, 0};
 
 						// TODO(b/129523279): Implement as 6 consecutive layers instead of separate pointers.
-						mipmap.buffer[face] = imageView->getOffsetPointer(offset, aspect, level, face);
+						mipmap.buffer[face] = imageView->getOffsetPointer(offset, aspect, level, face, ImageView::SAMPLING);
 					}
 				}
 				else
 				{
 					VkOffset3D offset = {0, 0, 0};
-					mipmap.buffer[0] = imageView->getOffsetPointer(offset, aspect, level, 0);
+					mipmap.buffer[0] = imageView->getOffsetPointer(offset, aspect, level, 0, ImageView::SAMPLING);
 				}
 
 				VkExtent3D extent = imageView->getMipLevelExtent(level);
-				Format format = imageView->getFormat();
+				Format format = imageView->getFormat(ImageView::SAMPLING);
 				int layers = imageView->getSubresourceRange().layerCount;
 				// TODO(b/129523279): Untangle depth vs layers throughout the sampler
 				int width = extent.width;
 				int height = extent.height;
 				int depth = layers > 1 ? layers : extent.depth;
-				int pitchP = imageView->rowPitchBytes(aspect, level) / format.bytes();
-				int sliceP = (layers > 1 ? imageView->layerPitchBytes(aspect) : imageView->slicePitchBytes(aspect, level)) / format.bytes();
+				int pitchP = imageView->rowPitchBytes(aspect, level, ImageView::SAMPLING) / format.bytes();
+				int sliceP = (layers > 1 ? imageView->layerPitchBytes(aspect, ImageView::SAMPLING) : imageView->slicePitchBytes(aspect, level, ImageView::SAMPLING)) / format.bytes();
 
 				if(mipmapLevel == 0)
 				{
diff --git a/src/Vulkan/VkFormat.cpp b/src/Vulkan/VkFormat.cpp
index 8e40265..d1debf4 100644
--- a/src/Vulkan/VkFormat.cpp
+++ b/src/Vulkan/VkFormat.cpp
@@ -136,6 +136,9 @@
 	case VK_FORMAT_R8G8B8A8_SRGB:
 	case VK_FORMAT_B8G8R8A8_SRGB:
 	case VK_FORMAT_A8B8G8R8_SRGB_PACK32:
+	case VK_FORMAT_ETC2_R8G8B8_SRGB_BLOCK:
+	case VK_FORMAT_ETC2_R8G8B8A1_SRGB_BLOCK:
+	case VK_FORMAT_ETC2_R8G8B8A8_SRGB_BLOCK:
 		return true;
 	default:
 		return false;
@@ -350,6 +353,35 @@
 	}
 }
 
+VkFormat Format::getDecompressedFormat() const
+{
+	// Note: our ETC2 decoder decompresses the 64 bit RGB compressed texel data to B8G8R8
+	switch(format)
+	{
+	case VK_FORMAT_ETC2_R8G8B8_UNORM_BLOCK:
+		return VK_FORMAT_B8G8R8_UNORM;
+	case VK_FORMAT_ETC2_R8G8B8_SRGB_BLOCK:
+		return VK_FORMAT_B8G8R8_SRGB;
+	case VK_FORMAT_ETC2_R8G8B8A1_UNORM_BLOCK:
+	case VK_FORMAT_ETC2_R8G8B8A8_UNORM_BLOCK:
+		return VK_FORMAT_B8G8R8A8_UNORM;
+	case VK_FORMAT_ETC2_R8G8B8A1_SRGB_BLOCK:
+	case VK_FORMAT_ETC2_R8G8B8A8_SRGB_BLOCK:
+		return VK_FORMAT_B8G8R8A8_SRGB;
+	case VK_FORMAT_EAC_R11_UNORM_BLOCK:
+		return VK_FORMAT_R16_UNORM;
+	case VK_FORMAT_EAC_R11_SNORM_BLOCK:
+		return VK_FORMAT_R16_SNORM;
+	case VK_FORMAT_EAC_R11G11_UNORM_BLOCK:
+		return VK_FORMAT_R16G16_UNORM;
+	case VK_FORMAT_EAC_R11G11_SNORM_BLOCK:
+		return VK_FORMAT_R16G16_SNORM;
+	default:
+		UNIMPLEMENTED("format: %d", int(format));
+		return VK_FORMAT_UNDEFINED;
+	}
+}
+
 VkFormat Format::compatibleFormat() const
 {
 	// According to the Vulkan 1.1 Spec, 37.1.6. Format Compatibility Classes:
@@ -901,6 +933,8 @@
 	case VK_FORMAT_D16_UNORM_S8_UINT:
 	case VK_FORMAT_D24_UNORM_S8_UINT:
 	case VK_FORMAT_D32_SFLOAT_S8_UINT:
+	case VK_FORMAT_EAC_R11_UNORM_BLOCK:
+	case VK_FORMAT_EAC_R11_SNORM_BLOCK:
 		return 1;
 	case VK_FORMAT_R4G4_UNORM_PACK8:
 	case VK_FORMAT_R8G8_UNORM:
@@ -923,6 +957,8 @@
 	case VK_FORMAT_R64G64_UINT:
 	case VK_FORMAT_R64G64_SINT:
 	case VK_FORMAT_R64G64_SFLOAT:
+	case VK_FORMAT_EAC_R11G11_UNORM_BLOCK:
+	case VK_FORMAT_EAC_R11G11_SNORM_BLOCK:
 		return 2;
 	case VK_FORMAT_R5G6B5_UNORM_PACK16:
 	case VK_FORMAT_B5G6R5_UNORM_PACK16:
@@ -956,6 +992,8 @@
 	case VK_FORMAT_B10G11R11_UFLOAT_PACK32:
 	case VK_FORMAT_E5B9G9R9_UFLOAT_PACK32:
 	case VK_FORMAT_G8_B8R8_2PLANE_420_UNORM:
+	case VK_FORMAT_ETC2_R8G8B8_UNORM_BLOCK:
+	case VK_FORMAT_ETC2_R8G8B8_SRGB_BLOCK:
 		return 3;
 	case VK_FORMAT_R4G4B4A4_UNORM_PACK16:
 	case VK_FORMAT_B4G4R4A4_UNORM_PACK16:
@@ -1008,6 +1046,10 @@
 	case VK_FORMAT_R64G64B64A64_UINT:
 	case VK_FORMAT_R64G64B64A64_SINT:
 	case VK_FORMAT_R64G64B64A64_SFLOAT:
+	case VK_FORMAT_ETC2_R8G8B8A1_UNORM_BLOCK:
+	case VK_FORMAT_ETC2_R8G8B8A1_SRGB_BLOCK:
+	case VK_FORMAT_ETC2_R8G8B8A8_UNORM_BLOCK:
+	case VK_FORMAT_ETC2_R8G8B8A8_SRGB_BLOCK:
 		return 4;
 	default:
 		UNIMPLEMENTED("Format: %d", int(format));
@@ -1092,6 +1134,11 @@
 	case VK_FORMAT_D32_SFLOAT:
 	case VK_FORMAT_D32_SFLOAT_S8_UINT:
 	case VK_FORMAT_G8_B8R8_2PLANE_420_UNORM:
+	case VK_FORMAT_EAC_R11_UNORM_BLOCK:
+	case VK_FORMAT_EAC_R11G11_UNORM_BLOCK:
+	case VK_FORMAT_ETC2_R8G8B8_UNORM_BLOCK:
+	case VK_FORMAT_ETC2_R8G8B8A1_UNORM_BLOCK:
+	case VK_FORMAT_ETC2_R8G8B8A8_UNORM_BLOCK:
 		return true;
 	case VK_FORMAT_R8G8B8A8_SNORM:
 	case VK_FORMAT_R8G8B8A8_SSCALED:
@@ -1116,6 +1163,11 @@
 	case VK_FORMAT_R32G32B32A32_SFLOAT:
 	case VK_FORMAT_R64G64B64A64_SINT:
 	case VK_FORMAT_R64G64B64A64_SFLOAT:
+	case VK_FORMAT_EAC_R11_SNORM_BLOCK:
+	case VK_FORMAT_EAC_R11G11_SNORM_BLOCK:
+	case VK_FORMAT_ETC2_R8G8B8_SRGB_BLOCK:
+	case VK_FORMAT_ETC2_R8G8B8A1_SRGB_BLOCK:
+	case VK_FORMAT_ETC2_R8G8B8A8_SRGB_BLOCK:
 		return false;
 	case VK_FORMAT_R8_SNORM:
 	case VK_FORMAT_R8_USCALED:
@@ -1685,14 +1737,20 @@
 	case VK_FORMAT_R32G32B32A32_SINT:
 	case VK_FORMAT_R32G32B32A32_UINT:
 	case VK_FORMAT_R8G8_UNORM:
+	case VK_FORMAT_B8G8R8_UNORM:

 	case VK_FORMAT_B8G8R8A8_UNORM:
 	case VK_FORMAT_R8G8B8A8_UNORM:
+	case VK_FORMAT_B8G8R8_SRGB:

 	case VK_FORMAT_R8G8B8A8_SRGB:
+	case VK_FORMAT_B8G8R8A8_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_R16_UNORM:

+	case VK_FORMAT_R16_SNORM:

+	case VK_FORMAT_R16G16_UNORM:

+	case VK_FORMAT_R16G16_SNORM:
 	case VK_FORMAT_R16G16B16A16_UNORM:
 	case VK_FORMAT_R16_SINT:
 	case VK_FORMAT_R16_UINT:
@@ -1722,9 +1780,12 @@
 	switch(format)
 	{
 	case VK_FORMAT_R8G8_UNORM:
+	case VK_FORMAT_B8G8R8_UNORM:

 	case VK_FORMAT_B8G8R8A8_UNORM:
 	case VK_FORMAT_R8G8B8A8_UNORM:
+	case VK_FORMAT_B8G8R8_SRGB:

 	case VK_FORMAT_R8G8B8A8_SRGB:
+	case VK_FORMAT_B8G8R8A8_SRGB:
 	case VK_FORMAT_R8_UNORM:
 	case VK_FORMAT_R8_SNORM:
 	case VK_FORMAT_R8G8_SNORM:
@@ -1742,7 +1803,10 @@
 	case VK_FORMAT_R32_SFLOAT:
 	case VK_FORMAT_R32G32_SFLOAT:
 	case VK_FORMAT_R32G32B32A32_SFLOAT:
-	case VK_FORMAT_R16G16_UNORM:
+	case VK_FORMAT_R16_UNORM:

+	case VK_FORMAT_R16_SNORM:

+	case VK_FORMAT_R16G16_UNORM:

+	case VK_FORMAT_R16G16_SNORM:
 	case VK_FORMAT_R16G16B16A16_UNORM:
 	case VK_FORMAT_R32_SINT:
 	case VK_FORMAT_R32_UINT:
@@ -1796,9 +1860,12 @@
 	case VK_FORMAT_R32G32B32A32_SINT:
 	case VK_FORMAT_R32G32B32A32_UINT:
 	case VK_FORMAT_R8G8_UNORM:
+	case VK_FORMAT_B8G8R8_UNORM:

 	case VK_FORMAT_B8G8R8A8_UNORM:
 	case VK_FORMAT_R8G8B8A8_UNORM:
+	case VK_FORMAT_B8G8R8_SRGB:

 	case VK_FORMAT_R8G8B8A8_SRGB:
+	case VK_FORMAT_B8G8R8A8_SRGB:
 	case VK_FORMAT_R32_SFLOAT:
 	case VK_FORMAT_R32G32_SFLOAT:
 	case VK_FORMAT_R32G32B32A32_SFLOAT:
@@ -1809,7 +1876,10 @@
 	case VK_FORMAT_E5B9G9R9_UFLOAT_PACK32:
 	case VK_FORMAT_B10G11R11_UFLOAT_PACK32:
 		return false;
+	case VK_FORMAT_R16_UNORM:

+	case VK_FORMAT_R16_SNORM:

 	case VK_FORMAT_R16G16_UNORM:
+	case VK_FORMAT_R16G16_SNORM:
 	case VK_FORMAT_R16G16B16A16_UNORM:
 	case VK_FORMAT_R16_SINT:
 	case VK_FORMAT_R16_UINT:
@@ -1846,10 +1916,16 @@
 	case VK_FORMAT_R8G8B8A8_SINT:
 	case VK_FORMAT_R8G8B8A8_UINT:
 	case VK_FORMAT_R8G8_UNORM:
+	case VK_FORMAT_B8G8R8_UNORM:
 	case VK_FORMAT_B8G8R8A8_UNORM:
 	case VK_FORMAT_R8G8B8A8_UNORM:
+	case VK_FORMAT_B8G8R8_SRGB:
 	case VK_FORMAT_R8G8B8A8_SRGB:
+	case VK_FORMAT_B8G8R8A8_SRGB:

+	case VK_FORMAT_R16_UNORM:

+	case VK_FORMAT_R16_SNORM:

 	case VK_FORMAT_R16G16_UNORM:
+	case VK_FORMAT_R16G16_SNORM:
 	case VK_FORMAT_R16G16B16A16_UNORM:
 	case VK_FORMAT_R16_SINT:
 	case VK_FORMAT_R16_UINT:
@@ -1909,14 +1985,20 @@
 	case VK_FORMAT_R32G32B32A32_SINT:
 	case VK_FORMAT_R32G32B32A32_UINT:
 	case VK_FORMAT_R8G8_UNORM:
+	case VK_FORMAT_B8G8R8_UNORM:
 	case VK_FORMAT_B8G8R8A8_UNORM:
 	case VK_FORMAT_R8G8B8A8_UNORM:
+	case VK_FORMAT_B8G8R8_SRGB:
 	case VK_FORMAT_R8G8B8A8_SRGB:
+	case VK_FORMAT_B8G8R8A8_SRGB:
 	case VK_FORMAT_R32_SFLOAT:
 	case VK_FORMAT_R32G32_SFLOAT:
 	case VK_FORMAT_R32G32B32A32_SFLOAT:
 	case VK_FORMAT_R8_UNORM:
+	case VK_FORMAT_R16_UNORM:
+	case VK_FORMAT_R16_SNORM:
 	case VK_FORMAT_R16G16_UNORM:
+	case VK_FORMAT_R16G16_SNORM:
 	case VK_FORMAT_R16G16B16A16_UNORM:
 	case VK_FORMAT_R16_SINT:
 	case VK_FORMAT_R16_UINT:
@@ -1949,6 +2031,8 @@
 	case VK_FORMAT_R8_UNORM:
 	case VK_FORMAT_R8_SINT:
 	case VK_FORMAT_R8_UINT:
+	case VK_FORMAT_R16_UNORM:

+	case VK_FORMAT_R16_SNORM:

 	case VK_FORMAT_R16_SINT:
 	case VK_FORMAT_R16_UINT:
 	case VK_FORMAT_R16_SFLOAT:
@@ -1962,6 +2046,7 @@
 	case VK_FORMAT_R8G8_UINT:
 	case VK_FORMAT_R16G16_SINT:
 	case VK_FORMAT_R16G16_UINT:
+	case VK_FORMAT_R16G16_SNORM:
 	case VK_FORMAT_R16G16_UNORM:
 	case VK_FORMAT_R16G16_SFLOAT:
 	case VK_FORMAT_R32G32_SINT:
@@ -1971,12 +2056,15 @@
 	case VK_FORMAT_A1R5G5B5_UNORM_PACK16:
 	case VK_FORMAT_B4G4R4A4_UNORM_PACK16:
 	case VK_FORMAT_R5G6B5_UNORM_PACK16:
+	case VK_FORMAT_B8G8R8_UNORM:

+	case VK_FORMAT_B8G8R8_SRGB:

 	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_B8G8R8A8_SRGB:

 	case VK_FORMAT_R16G16B16A16_UNORM:
 	case VK_FORMAT_R16G16B16A16_SINT:
 	case VK_FORMAT_R16G16B16A16_UINT:
diff --git a/src/Vulkan/VkFormat.h b/src/Vulkan/VkFormat.h
index 24a3724..26b19e4 100644
--- a/src/Vulkan/VkFormat.h
+++ b/src/Vulkan/VkFormat.h
@@ -47,6 +47,7 @@
 
 	bool isCompatible(const Format& other) const;
 	bool isCompressed() const;
+	VkFormat getDecompressedFormat() const;
 	int blockWidth() const;
 	int blockHeight() const;
 	int bytesPerBlock() const;
diff --git a/src/Vulkan/VkImage.cpp b/src/Vulkan/VkImage.cpp
index a2784e6..166050c 100644
--- a/src/Vulkan/VkImage.cpp
+++ b/src/Vulkan/VkImage.cpp
@@ -17,6 +17,7 @@
 #include "VkDevice.hpp"
 #include "VkImage.hpp"
 #include "Device/Blitter.hpp"
+#include "Device/ETC_Decoder.hpp"
 #include <cstring>
 
 namespace
@@ -36,6 +37,33 @@
 		if (!aspects) aspects |= VK_IMAGE_ASPECT_COLOR_BIT;
 		return aspects;
 	}
+
+	ETC_Decoder::InputType GetInputType(const vk::Format& format)
+	{
+		switch(format)
+		{
+		case VK_FORMAT_EAC_R11_UNORM_BLOCK:
+			return ETC_Decoder::ETC_R_UNSIGNED;
+		case VK_FORMAT_EAC_R11_SNORM_BLOCK:
+			return ETC_Decoder::ETC_R_SIGNED;
+		case VK_FORMAT_EAC_R11G11_UNORM_BLOCK:
+			return ETC_Decoder::ETC_RG_UNSIGNED;
+		case VK_FORMAT_EAC_R11G11_SNORM_BLOCK:
+			return ETC_Decoder::ETC_RG_SIGNED;
+		case VK_FORMAT_ETC2_R8G8B8_UNORM_BLOCK:
+		case VK_FORMAT_ETC2_R8G8B8_SRGB_BLOCK:
+			return ETC_Decoder::ETC_RGB;
+		case VK_FORMAT_ETC2_R8G8B8A1_UNORM_BLOCK:
+		case VK_FORMAT_ETC2_R8G8B8A1_SRGB_BLOCK:
+			return ETC_Decoder::ETC_RGB_PUNCHTHROUGH_ALPHA;
+		case VK_FORMAT_ETC2_R8G8B8A8_UNORM_BLOCK:
+		case VK_FORMAT_ETC2_R8G8B8A8_SRGB_BLOCK:
+			return ETC_Decoder::ETC_RGBA;
+		default:
+			UNIMPLEMENTED("format: %d", int(format));
+			return ETC_Decoder::ETC_RGBA;
+		}
+	}
 }
 
 namespace vk
@@ -53,15 +81,26 @@
 	tiling(pCreateInfo->pCreateInfo->tiling),
 	usage(pCreateInfo->pCreateInfo->usage)
 {
+	if(format.isCompressed())
+	{
+		VkImageCreateInfo imageCreateInfo = *(pCreateInfo->pCreateInfo);
+		imageCreateInfo.format = format.getDecompressedFormat();
+		Image::CreateInfo createInfo = { &imageCreateInfo, pCreateInfo->device };
+		decompressedImage = new (mem) Image(&createInfo, nullptr);
+	}
 }
 
 void Image::destroy(const VkAllocationCallbacks* pAllocator)
 {
+	if(decompressedImage)
+	{
+		vk::deallocate(decompressedImage, pAllocator);
+	}
 }
 
 size_t Image::ComputeRequiredAllocationSize(const Image::CreateInfo* pCreateInfo)
 {
-	return 0;
+	return Format(pCreateInfo->pCreateInfo->format).isCompressed() ? sizeof(Image) : 0;
 }
 
 const VkMemoryRequirements Image::getMemoryRequirements() const
@@ -69,7 +108,8 @@
 	VkMemoryRequirements memoryRequirements;
 	memoryRequirements.alignment = vk::REQUIRED_MEMORY_ALIGNMENT;
 	memoryRequirements.memoryTypeBits = vk::MEMORY_TYPE_GENERIC_BIT;
-	memoryRequirements.size = getStorageSize(GetAspects(format));
+	memoryRequirements.size = getStorageSize(GetAspects(format)) +
+	                          (decompressedImage ? decompressedImage->getStorageSize(GetAspects(decompressedImage->format)) : 0);
 	return memoryRequirements;
 }
 
@@ -77,6 +117,11 @@
 {
 	deviceMemory = Cast(pDeviceMemory);
 	memoryOffset = pMemoryOffset;
+	if(decompressedImage)
+	{
+		decompressedImage->deviceMemory = deviceMemory;
+		decompressedImage->memoryOffset = memoryOffset + getStorageSize(GetAspects(format));
+	}
 }
 
 void Image::getSubresourceLayout(const VkImageSubresource* pSubresource, VkSubresourceLayout* pLayout) const
@@ -345,6 +390,12 @@
 		srcMemory += srcLayerSize;
 		dstMemory += dstLayerSize;
 	}
+
+	if(bufferIsSource && decompressedImage)
+	{
+		prepareForSampling({ region.imageSubresource.aspectMask, region.imageSubresource.mipLevel, 1,
+		                     region.imageSubresource.baseArrayLayer, region.imageSubresource.layerCount });
+	}
 }
 
 void Image::copyTo(VkBuffer dstBuffer, const VkBufferImageCopy& region)
@@ -631,6 +682,7 @@
 {
 	if (aspectMask == (VK_IMAGE_ASPECT_DEPTH_BIT|VK_IMAGE_ASPECT_STENCIL_BIT))
 	{
+		ASSERT(!format.isCompressed());
 		return arrayLayers * (getLayerSize(VK_IMAGE_ASPECT_DEPTH_BIT) + getLayerSize(VK_IMAGE_ASPECT_STENCIL_BIT));
 	}
 	return arrayLayers * getLayerSize(static_cast<VkImageAspectFlagBits>(aspectMask));
@@ -758,4 +810,53 @@
 	}
 }
 
+void Image::prepareForSampling(const VkImageSubresourceRange& subresourceRange) const
+{
+	switch(format)
+	{
+	case VK_FORMAT_EAC_R11_UNORM_BLOCK:
+	case VK_FORMAT_EAC_R11_SNORM_BLOCK:
+	case VK_FORMAT_EAC_R11G11_UNORM_BLOCK:
+	case VK_FORMAT_EAC_R11G11_SNORM_BLOCK:
+	case VK_FORMAT_ETC2_R8G8B8_UNORM_BLOCK:
+	case VK_FORMAT_ETC2_R8G8B8_SRGB_BLOCK:
+	case VK_FORMAT_ETC2_R8G8B8A1_UNORM_BLOCK:
+	case VK_FORMAT_ETC2_R8G8B8A1_SRGB_BLOCK:
+	case VK_FORMAT_ETC2_R8G8B8A8_UNORM_BLOCK:
+	case VK_FORMAT_ETC2_R8G8B8A8_SRGB_BLOCK:
+		decodeETC2(subresourceRange);
+		break;
+	default:
+		break;
+	}
+}
+
+void Image::decodeETC2(const VkImageSubresourceRange& subresourceRange) const
+{
+	ASSERT(decompressedImage);
+
+	ETC_Decoder::InputType inputType = GetInputType(format);
+
+	uint32_t lastLayer = getLastLayerIndex(subresourceRange);
+	uint32_t lastMipLevel = getLastMipLevel(subresourceRange);
+
+	VkImageSubresourceLayers subresourceLayers = { subresourceRange.aspectMask, subresourceRange.baseMipLevel, subresourceRange.baseArrayLayer, 1 };
+	for(; subresourceLayers.baseArrayLayer <= lastLayer; subresourceLayers.baseArrayLayer++)
+	{
+		for(; subresourceLayers.mipLevel <= lastMipLevel; subresourceLayers.mipLevel++)
+		{
+			uint8_t* source = static_cast<uint8_t*>(getTexelPointer({ 0, 0, 0 }, subresourceLayers));
+			uint8_t* dest = static_cast<uint8_t*>(decompressedImage->getTexelPointer({ 0, 0, 0 }, subresourceLayers));
+
+			VkExtent3D mipLevelExtent = getMipLevelExtent(subresourceLayers.mipLevel);
+
+			int bytes = decompressedImage->format.bytes();
+			int pitchB = decompressedImage->rowPitchBytes(VK_IMAGE_ASPECT_COLOR_BIT, subresourceLayers.mipLevel);
+
+			ETC_Decoder::Decode(source, dest, mipLevelExtent.width, mipLevelExtent.height,
+			                    mipLevelExtent.width, mipLevelExtent.height, pitchB, bytes, inputType);
+		}
+	}
+}
+
 } // namespace vk
diff --git a/src/Vulkan/VkImage.hpp b/src/Vulkan/VkImage.hpp
index eff44b0..2090832 100644
--- a/src/Vulkan/VkImage.hpp
+++ b/src/Vulkan/VkImage.hpp
@@ -70,6 +70,9 @@
 	uint8_t*                 end() const;
 	VkDeviceSize             getLayerSize(VkImageAspectFlagBits aspect) const;
 
+	void                     prepareForSampling(const VkImageSubresourceRange& subresourceRange) const;
+	const Image*             getSampledImage() const { return decompressedImage ? decompressedImage : this; }
+
 	static Format            GetFormat(const vk::Format& format, VkImageAspectFlagBits aspect);
 
 private:
@@ -89,6 +92,7 @@
 	VkFormat getClearFormat() const;
 	void clear(void* pixelData, VkFormat pixelFormat, const vk::Format& viewFormat, const VkImageSubresourceRange& subresourceRange, const VkRect2D& renderArea);
 	int borderSize(VkImageAspectFlagBits aspect) const;
+	void decodeETC2(const VkImageSubresourceRange& subresourceRange) const;
 
 	const Device *const      device = nullptr;
 	DeviceMemory*            deviceMemory = nullptr;
@@ -102,6 +106,7 @@
 	VkSampleCountFlagBits    samples = VK_SAMPLE_COUNT_1_BIT;
 	VkImageTiling            tiling = VK_IMAGE_TILING_OPTIMAL;
 	VkImageUsageFlags        usage = (VkImageUsageFlags)0;
+	Image*                   decompressedImage = nullptr;
 };
 
 static inline Image* Cast(VkImage object)
diff --git a/src/Vulkan/VkImageView.cpp b/src/Vulkan/VkImageView.cpp
index 23f79c8..9abefd6 100644
--- a/src/Vulkan/VkImageView.cpp
+++ b/src/Vulkan/VkImageView.cpp
@@ -171,7 +171,46 @@
 	image->copyTo(*(resolveAttachment->image), region);
 }
 
-void *ImageView::getOffsetPointer(const VkOffset3D& offset, VkImageAspectFlagBits aspect, uint32_t mipLevel, uint32_t layer) const
+const Image* ImageView::getImage(Usage usage) const
+{
+	switch(usage)
+	{
+	case RAW:
+		return image;
+	case SAMPLING:
+		return image->getSampledImage();
+	default:
+		UNIMPLEMENTED("usage %d", int(usage));
+		return nullptr;
+	}
+}
+
+Format ImageView::getFormat(Usage usage) const
+{
+	return ((usage == RAW) || (getImage(usage) == image)) ? format : getImage(usage)->getFormat();
+}
+
+int ImageView::rowPitchBytes(VkImageAspectFlagBits aspect, uint32_t mipLevel, Usage usage) const
+{
+	return getImage(usage)->rowPitchBytes(aspect, subresourceRange.baseMipLevel + mipLevel);
+}
+
+int ImageView::slicePitchBytes(VkImageAspectFlagBits aspect, uint32_t mipLevel, Usage usage) const
+{
+	return getImage(usage)->slicePitchBytes(aspect, subresourceRange.baseMipLevel + mipLevel);
+}
+
+int ImageView::layerPitchBytes(VkImageAspectFlagBits aspect, Usage usage) const
+{
+	return static_cast<int>(getImage(usage)->getLayerSize(aspect));
+}
+
+VkExtent3D ImageView::getMipLevelExtent(uint32_t mipLevel) const
+{
+	return image->getMipLevelExtent(subresourceRange.baseMipLevel + mipLevel);
+}
+
+void *ImageView::getOffsetPointer(const VkOffset3D& offset, VkImageAspectFlagBits aspect, uint32_t mipLevel, uint32_t layer, Usage usage) const
 {
 	ASSERT(mipLevel < subresourceRange.levelCount);
 
@@ -182,7 +221,7 @@
 		subresourceRange.baseArrayLayer + layer,
 		subresourceRange.layerCount
 	};
-	return image->getTexelPointer(offset, imageSubresourceLayers);
+	return getImage(usage)->getTexelPointer(offset, imageSubresourceLayers);
 }
 
 }
diff --git a/src/Vulkan/VkImageView.hpp b/src/Vulkan/VkImageView.hpp
index fad7bfb..27d909c 100644
--- a/src/Vulkan/VkImageView.hpp
+++ b/src/Vulkan/VkImageView.hpp
@@ -28,6 +28,11 @@
 class ImageView : public Object<ImageView, VkImageView>
 {
 public:
+	// Image usage:
+	// RAW: Use the base image as is
+	// SAMPLING: Image used for texture sampling
+	enum Usage { RAW, SAMPLING };
+
 	ImageView(const VkImageViewCreateInfo* pCreateInfo, void* mem);
 	~ImageView() = delete;
 	void destroy(const VkAllocationCallbacks* pAllocator);
@@ -39,17 +44,19 @@
 	void resolve(ImageView* resolveAttachment);
 
 	VkImageViewType getType() const { return viewType; }
-	Format getFormat() const { return format; }
+	Format getFormat(Usage usage = RAW) const;
 	int getSampleCount() const { return image->getSampleCountFlagBits(); }
-	int rowPitchBytes(VkImageAspectFlagBits aspect, uint32_t mipLevel) const { return image->rowPitchBytes(aspect, subresourceRange.baseMipLevel + mipLevel); }
-	int slicePitchBytes(VkImageAspectFlagBits aspect, uint32_t mipLevel) const { return image->slicePitchBytes(aspect, subresourceRange.baseMipLevel + mipLevel); }
-	int layerPitchBytes(VkImageAspectFlagBits aspect) const { return static_cast<int>(image->getLayerSize(aspect)); }
-	VkExtent3D getMipLevelExtent(uint32_t mipLevel) const { return image->getMipLevelExtent(subresourceRange.baseMipLevel + mipLevel); }
+	int rowPitchBytes(VkImageAspectFlagBits aspect, uint32_t mipLevel, Usage usage = RAW) const;
+	int slicePitchBytes(VkImageAspectFlagBits aspect, uint32_t mipLevel, Usage usage = RAW) const;
+	int layerPitchBytes(VkImageAspectFlagBits aspect, Usage usage = RAW) const;
+	VkExtent3D getMipLevelExtent(uint32_t mipLevel) const;
 
-	void *getOffsetPointer(const VkOffset3D& offset, VkImageAspectFlagBits aspect, uint32_t mipLevel, uint32_t layer) const;
+	void *getOffsetPointer(const VkOffset3D& offset, VkImageAspectFlagBits aspect, uint32_t mipLevel, uint32_t layer, Usage usage = RAW) const;
 	bool hasDepthAspect() const { return (subresourceRange.aspectMask & VK_IMAGE_ASPECT_DEPTH_BIT) != 0; }
 	bool hasStencilAspect() const { return (subresourceRange.aspectMask & VK_IMAGE_ASPECT_STENCIL_BIT) != 0; }
 
+	void prepareForSampling() const { image->prepareForSampling(subresourceRange); }
+
 	const VkComponentMapping &getComponentMapping() const { return components; }
 	const VkImageSubresourceRange &getSubresourceRange() const { return subresourceRange; }
 	size_t getImageSizeInBytes() const { return image->getMemoryRequirements().size; }
@@ -59,6 +66,7 @@
 	static std::atomic<uint32_t> nextID;
 
 	bool                          imageTypesMatch(VkImageType imageType) const;
+	const Image*                  getImage(Usage usage) const;
 
 	Image *const                  image = nullptr;
 	const VkImageViewType         viewType = VK_IMAGE_VIEW_TYPE_2D;