Support for BC1, BC2, BC3, BC4 and BC5

Added a block compression (BCn) decoder and logic for all
formats for n in the [1, 5] range.

The decompression follows the same logic as the ETC2
decompression in vk::Image.

Tests: dEQP-VK.*bc*
Bug: b/146052572
Change-Id: I64fac0a7af52e1be209c1cfd5373744918c7df14
Reviewed-on: https://swiftshader-review.googlesource.com/c/SwiftShader/+/39369
Presubmit-Ready: Alexis Hétu <sugoi@google.com>
Kokoro-Presubmit: kokoro <noreply+kokoro@google.com>
Tested-by: Alexis Hétu <sugoi@google.com>
Reviewed-by: Nicolas Capens <nicolascapens@google.com>
diff --git a/src/Device/BC_Decoder.cpp b/src/Device/BC_Decoder.cpp
new file mode 100644
index 0000000..f14d871
--- /dev/null
+++ b/src/Device/BC_Decoder.cpp
@@ -0,0 +1,305 @@
+// Copyright 2019 The SwiftShader Authors. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//    http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "BC_Decoder.hpp"
+
+namespace
+{
+	static constexpr int BlockWidth = 4;
+	static constexpr int BlockHeight = 4;
+
+	struct BC_color
+	{
+		void decode(unsigned char* dst, int x, int y, int dstW, int dstH, int dstPitch, int dstBpp, bool hasAlphaChannel, bool hasSeparateAlpha) const
+		{
+			Color c[4];
+			c[0].extract565(c0);
+			c[1].extract565(c1);
+			if(hasSeparateAlpha || (c0 > c1))
+			{
+				c[2] = ((c[0] * 2) + c[1]) / 3;
+				c[3] = ((c[1] * 2) + c[0]) / 3;
+			}
+			else
+			{
+				c[2] = (c[0] + c[1]) >> 1;
+				if(hasAlphaChannel)
+				{
+					c[3].clearAlpha();
+				}
+			}
+
+			for(int j = 0; j < BlockHeight && (y + j) < dstH; j++)
+			{
+				int dstOffset = j * dstPitch;
+				int idxOffset = j * BlockHeight;
+				for(int i = 0; i < BlockWidth && (x + i) < dstW; i++, idxOffset++, dstOffset += dstBpp)
+				{
+					*reinterpret_cast<unsigned int*>(dst + dstOffset) = c[getIdx(idxOffset)].pack8888();
+				}
+			}
+		}
+
+	private:
+		struct Color
+		{
+			Color()
+			{
+				c[0] = c[1] = c[2] = 0;
+				c[3] = 0xFF000000;
+			}
+
+			void extract565(const unsigned int c565)
+			{
+				c[0] = ((c565 & 0x0000001F) << 3) | ((c565 & 0x0000001C) >> 2);
+				c[1] = ((c565 & 0x000007E0) >> 3) | ((c565 & 0x00000600) >> 9);
+				c[2] = ((c565 & 0x0000F800) >> 8) | ((c565 & 0x0000E000) >> 13);
+			}
+
+			unsigned int pack8888() const
+			{
+				return ((c[2] & 0xFF) << 16) | ((c[1] & 0xFF) << 8) | (c[0] & 0xFF) | c[3];
+			}
+
+			void clearAlpha()
+			{
+				c[3] = 0;
+			}
+
+			Color operator*(int factor) const
+			{
+				Color res;
+				for(int i = 0; i < 4; ++i)
+				{
+					res.c[i] = c[i] * factor;
+				}
+				return res;
+			}
+
+			Color operator/(int factor) const
+			{
+				Color res;
+				for(int i = 0; i < 4; ++i)
+				{
+					res.c[i] = c[i] / factor;
+				}
+				return res;
+			}
+
+			Color operator>>(int shift) const
+			{
+				Color res;
+				for(int i = 0; i < 4; ++i)
+				{
+					res.c[i] = c[i] >> shift;
+				}
+				return res;
+			}
+
+			Color operator+(Color const& obj) const
+			{
+				Color res;
+				for(int i = 0; i < 4; ++i)
+				{
+					res.c[i] = c[i] + obj.c[i];
+				}
+				return res;
+			}
+
+		private:
+			int c[4];
+		};
+
+		unsigned int getIdx(int i) const
+		{
+			int offset = i << 1; // 2 bytes per index
+			return (idx & (0x3 << offset)) >> offset;
+		}
+
+		unsigned short c0;
+		unsigned short c1;
+		unsigned int idx;
+	};
+
+	struct BC_channel
+	{
+		void decode(unsigned char* dst, int x, int y, int dstW, int dstH, int dstPitch, int dstBpp, int channel, bool isSigned) const
+		{
+			int c[8] = { 0 };
+
+			if(isSigned)
+			{
+				c[0] = static_cast<signed char>(data & 0xFF);
+				c[1] = static_cast<signed char>((data & 0xFF00) >> 8);
+			}
+			else
+			{
+				c[0] = static_cast<unsigned char>(data & 0xFF);
+				c[1] = static_cast<unsigned char>((data & 0xFF00) >> 8);
+			}
+
+			if(c[0] > c[1])
+			{
+				for(int i = 2; i < 8; ++i)
+				{
+					c[i] = ((8 - i) * c[0] + (i - 1) * c[1]) / 7;
+				}
+			}
+			else
+			{
+				for(int i = 2; i < 6; ++i)
+				{
+					c[i] = ((6 - i) * c[0] + (i - 1) * c[1]) / 5;
+				}
+				c[6] = isSigned ? -128 : 0;
+				c[7] = isSigned ? 127 : 255;
+			}
+
+			for(int j = 0; j < BlockHeight && (y + j) < dstH; j++)
+			{
+				for(int i = 0; i < BlockWidth && (x + i) < dstW; i++)
+				{
+					dst[channel + (i * dstBpp) + (j * dstPitch)] = static_cast<unsigned char>(c[getIdx((j * BlockHeight) + i)]);
+				}
+			}
+		}
+
+	private:
+		unsigned char getIdx(int i) const
+		{
+			int offset = i * 3 + 16;
+			return static_cast<unsigned char>((data & (0x7ull << offset)) >> offset);
+		}
+
+		unsigned long long data;
+	};
+
+	struct BC_alpha
+	{
+		void decode(unsigned char* dst, int x, int y, int dstW, int dstH, int dstPitch, int dstBpp) const
+		{
+			dst += 3; // Write only to alpha (channel 3)
+			for(int j = 0; j < BlockHeight && (y + j) < dstH; j++, dst += dstPitch)
+			{
+				unsigned char* dstRow = dst;
+				for(int i = 0; i < BlockWidth && (x + i) < dstW; i++, dstRow += dstBpp)
+				{
+					*dstRow = getAlpha(j * BlockHeight + i);
+				}
+			}
+		}
+
+	private:
+		unsigned char getAlpha(int i) const
+		{
+			int offset = i << 2;
+			int alpha = (data & (0xFull << offset)) >> offset;
+			return static_cast<unsigned char>(alpha | (alpha << 4));
+		}
+
+		unsigned long long data;
+	};
+} // end namespace
+
+// Decodes 1 to 4 channel images to 8 bit output
+bool BC_Decoder::Decode(const unsigned char* src, unsigned char* dst, int w, int h, int dstW, int dstH, int dstPitch, int dstBpp, int n, bool isNoAlphaU)
+{
+	static_assert(sizeof(BC_color) == 8, "BC_color must be 8 bytes");
+	static_assert(sizeof(BC_channel) == 8, "BC_channel must be 8 bytes");
+	static_assert(sizeof(BC_alpha) == 8, "BC_alpha must be 8 bytes");
+
+	const int dx = BlockWidth * dstBpp;
+	const int dy = BlockHeight * dstPitch;
+	const bool isAlpha = (n == 1) && !isNoAlphaU;
+	const bool isSigned = ((n == 4) || (n == 5) || (n == 6)) && !isNoAlphaU;
+
+	switch(n)
+	{
+	case 1: // BC1
+		{
+			const BC_color* color = reinterpret_cast<const BC_color*>(src);
+			for(int y = 0; y < h; y += BlockHeight, dst += dy)
+			{
+				unsigned char* dstRow = dst;
+				for(int x = 0; x < w; x += BlockWidth, ++color, dstRow += dx)
+				{
+					color->decode(dstRow, x, y, dstW, dstH, dstPitch, dstBpp, isAlpha, false);
+				}
+			}
+		}
+		break;
+	case 2: // BC2
+		{
+			const BC_alpha* alpha = reinterpret_cast<const BC_alpha*>(src);
+			const BC_color* color = reinterpret_cast<const BC_color*>(src + 8);
+			for(int y = 0; y < h; y += BlockHeight, dst += dy)
+			{
+				unsigned char* dstRow = dst;
+				for(int x = 0; x < w; x += BlockWidth, alpha += 2, color += 2, dstRow += dx)
+				{
+					color->decode(dstRow, x, y, dstW, dstH, dstPitch, dstBpp, isAlpha, true);
+					alpha->decode(dstRow, x, y, dstW, dstH, dstPitch, dstBpp);
+				}
+			}
+		}
+		break;
+	case 3: // BC3
+		{
+			const BC_channel* alpha = reinterpret_cast<const BC_channel*>(src);
+			const BC_color* color = reinterpret_cast<const BC_color*>(src + 8);
+			for(int y = 0; y < h; y += BlockHeight, dst += dy)
+			{
+				unsigned char* dstRow = dst;
+				for(int x = 0; x < w; x += BlockWidth, alpha += 2, color += 2, dstRow += dx)
+				{
+					color->decode(dstRow, x, y, dstW, dstH, dstPitch, dstBpp, isAlpha, true);
+					alpha->decode(dstRow, x, y, dstW, dstH, dstPitch, dstBpp, 3, isSigned);
+				}
+			}
+		}
+		break;
+	case 4: // BC4
+		{
+			const BC_channel* red = reinterpret_cast<const BC_channel*>(src);
+			for(int y = 0; y < h; y += BlockHeight, dst += dy)
+			{
+				unsigned char* dstRow = dst;
+				for(int x = 0; x < w; x += BlockWidth, ++red, dstRow += dx)
+				{
+					red->decode(dstRow, x, y, dstW, dstH, dstPitch, dstBpp, 0, isSigned);
+				}
+			}
+		}
+		break;
+	case 5: // BC5
+		{
+			const BC_channel* red = reinterpret_cast<const BC_channel*>(src);
+			const BC_channel* green = reinterpret_cast<const BC_channel*>(src + 8);
+			for(int y = 0; y < h; y += BlockHeight, dst += dy)
+			{
+				unsigned char* dstRow = dst;
+				for(int x = 0; x < w; x += BlockWidth, red += 2, green += 2, dstRow += dx)
+				{
+					red->decode(dstRow, x, y, dstW, dstH, dstPitch, dstBpp, 0, isSigned);
+					green->decode(dstRow, x, y, dstW, dstH, dstPitch, dstBpp, 1, isSigned);
+				}
+			}
+		}
+		break;
+	default:
+		return false;
+	}
+
+	return true;
+}
diff --git a/src/Device/BC_Decoder.hpp b/src/Device/BC_Decoder.hpp
new file mode 100644
index 0000000..28547bb
--- /dev/null
+++ b/src/Device/BC_Decoder.hpp
@@ -0,0 +1,33 @@
+// Copyright 2019 The SwiftShader Authors. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//    http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+class BC_Decoder
+{
+public:
+
+	/// BCn_Decoder::Decode - Decodes 1 to 4 channel images to 8 bit output
+	/// @param src            Pointer to BCn encoded image
+	/// @param dst            Pointer to decoded output image
+	/// @param w              src image width
+	/// @param h              src image height
+	/// @param dstW           dst image width
+	/// @param dstH           dst image height
+	/// @param dstPitch       dst image pitch (bytes per row)
+	/// @param dstBpp         dst image bytes per pixel
+	/// @param n              n in BCn format
+	/// @param isNoAlphaU     BC1: true if RGB, BC2/BC3: unused, BC4/BC5: true if unsigned
+	/// @return               true if the decoding was performed
+
+	static bool Decode(const unsigned char* src, unsigned char* dst, int w, int h, int dstW, int dstH, int dstPitch, int dstBpp, int n, bool isNoAlphaU);
+};
diff --git a/src/Device/BUILD.gn b/src/Device/BUILD.gn
index c412c71..e46869b 100644
--- a/src/Device/BUILD.gn
+++ b/src/Device/BUILD.gn
@@ -16,6 +16,7 @@
 
 swiftshader_source_set("Device_headers") {
   sources = [
+    "BC_Decoder.hpp",
     "Blitter.hpp",
     "Clipper.hpp",
     "Color.hpp",
@@ -37,6 +38,7 @@
 
 swiftshader_source_set("Device") {
   sources = [
+    "BC_Decoder.cpp",
     "Blitter.cpp",
     "Clipper.cpp",
     "Color.cpp",
diff --git a/src/Vulkan/VkFormat.cpp b/src/Vulkan/VkFormat.cpp
index f454d94..9d97c49 100644
--- a/src/Vulkan/VkFormat.cpp
+++ b/src/Vulkan/VkFormat.cpp
@@ -305,6 +305,10 @@
 	case VK_FORMAT_ETC2_R8G8B8_SRGB_BLOCK:
 	case VK_FORMAT_ETC2_R8G8B8A1_SRGB_BLOCK:
 	case VK_FORMAT_ETC2_R8G8B8A8_SRGB_BLOCK:
+	case VK_FORMAT_BC1_RGB_SRGB_BLOCK:
+	case VK_FORMAT_BC1_RGBA_SRGB_BLOCK:
+	case VK_FORMAT_BC2_SRGB_BLOCK:
+	case VK_FORMAT_BC3_SRGB_BLOCK:
 		return true;
 	default:
 		return false;
@@ -555,6 +559,24 @@
 		return VK_FORMAT_R16G16_UNORM;
 	case VK_FORMAT_EAC_R11G11_SNORM_BLOCK:
 		return VK_FORMAT_R16G16_SNORM;
+	case VK_FORMAT_BC1_RGB_UNORM_BLOCK:
+	case VK_FORMAT_BC1_RGBA_UNORM_BLOCK:
+	case VK_FORMAT_BC2_UNORM_BLOCK:
+	case VK_FORMAT_BC3_UNORM_BLOCK:
+		return VK_FORMAT_B8G8R8A8_UNORM;
+	case VK_FORMAT_BC1_RGB_SRGB_BLOCK:
+	case VK_FORMAT_BC1_RGBA_SRGB_BLOCK:
+	case VK_FORMAT_BC2_SRGB_BLOCK:
+	case VK_FORMAT_BC3_SRGB_BLOCK:
+		return VK_FORMAT_B8G8R8A8_SRGB;
+	case VK_FORMAT_BC4_UNORM_BLOCK:
+		return VK_FORMAT_R8_UNORM;
+	case VK_FORMAT_BC4_SNORM_BLOCK:
+		return VK_FORMAT_R8_SNORM;
+	case VK_FORMAT_BC5_UNORM_BLOCK:
+		return VK_FORMAT_R8G8_UNORM;
+	case VK_FORMAT_BC5_SNORM_BLOCK:
+		return VK_FORMAT_R8G8_SNORM;
 	default:
 		UNIMPLEMENTED("format: %d", int(format));
 		return VK_FORMAT_UNDEFINED;
@@ -1112,6 +1134,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_BC4_UNORM_BLOCK:
+	case VK_FORMAT_BC4_SNORM_BLOCK:
 	case VK_FORMAT_EAC_R11_UNORM_BLOCK:
 	case VK_FORMAT_EAC_R11_SNORM_BLOCK:
 		return 1;
@@ -1136,6 +1160,8 @@
 	case VK_FORMAT_R64G64_UINT:
 	case VK_FORMAT_R64G64_SINT:
 	case VK_FORMAT_R64G64_SFLOAT:
+	case VK_FORMAT_BC5_UNORM_BLOCK:
+	case VK_FORMAT_BC5_SNORM_BLOCK:
 	case VK_FORMAT_EAC_R11G11_UNORM_BLOCK:
 	case VK_FORMAT_EAC_R11G11_SNORM_BLOCK:
 		return 2;
@@ -1172,6 +1198,8 @@
 	case VK_FORMAT_E5B9G9R9_UFLOAT_PACK32:
 	case VK_FORMAT_G8_B8_R8_3PLANE_420_UNORM:
 	case VK_FORMAT_G8_B8R8_2PLANE_420_UNORM:
+	case VK_FORMAT_BC1_RGB_UNORM_BLOCK:
+	case VK_FORMAT_BC1_RGB_SRGB_BLOCK:
 	case VK_FORMAT_ETC2_R8G8B8_UNORM_BLOCK:
 	case VK_FORMAT_ETC2_R8G8B8_SRGB_BLOCK:
 		return 3;
@@ -1226,6 +1254,12 @@
 	case VK_FORMAT_R64G64B64A64_UINT:
 	case VK_FORMAT_R64G64B64A64_SINT:
 	case VK_FORMAT_R64G64B64A64_SFLOAT:
+	case VK_FORMAT_BC1_RGBA_UNORM_BLOCK:
+	case VK_FORMAT_BC1_RGBA_SRGB_BLOCK:
+	case VK_FORMAT_BC2_UNORM_BLOCK:
+	case VK_FORMAT_BC2_SRGB_BLOCK:
+	case VK_FORMAT_BC3_UNORM_BLOCK:
+	case VK_FORMAT_BC3_SRGB_BLOCK:
 	case VK_FORMAT_ETC2_R8G8B8A1_UNORM_BLOCK:
 	case VK_FORMAT_ETC2_R8G8B8A1_SRGB_BLOCK:
 	case VK_FORMAT_ETC2_R8G8B8A8_UNORM_BLOCK:
@@ -1313,11 +1347,24 @@
 	case VK_FORMAT_D24_UNORM_S8_UINT:
 	case VK_FORMAT_D32_SFLOAT:
 	case VK_FORMAT_D32_SFLOAT_S8_UINT:
+	case VK_FORMAT_BC1_RGB_UNORM_BLOCK:
+	case VK_FORMAT_BC1_RGB_SRGB_BLOCK:
+	case VK_FORMAT_BC1_RGBA_UNORM_BLOCK:
+	case VK_FORMAT_BC1_RGBA_SRGB_BLOCK:
+	case VK_FORMAT_BC2_UNORM_BLOCK:
+	case VK_FORMAT_BC2_SRGB_BLOCK:
+	case VK_FORMAT_BC3_UNORM_BLOCK:
+	case VK_FORMAT_BC3_SRGB_BLOCK:
+	case VK_FORMAT_BC4_UNORM_BLOCK:
+	case VK_FORMAT_BC5_UNORM_BLOCK:
 	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_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:
 		return true;
 	case VK_FORMAT_R8G8B8A8_SNORM:
 	case VK_FORMAT_R8G8B8A8_SSCALED:
@@ -1342,11 +1389,10 @@
 	case VK_FORMAT_R32G32B32A32_SFLOAT:
 	case VK_FORMAT_R64G64B64A64_SINT:
 	case VK_FORMAT_R64G64B64A64_SFLOAT:
+	case VK_FORMAT_BC4_SNORM_BLOCK:
+	case VK_FORMAT_BC5_SNORM_BLOCK:
 	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:
 	// YCbCr formats treated as signed because VK_SAMPLER_YCBCR_MODEL_CONVERSION_YCBCR_IDENTITY
 	// expects chroma components to be in range [-0.5, 0.5]
 	case VK_FORMAT_G8_B8_R8_3PLANE_420_UNORM:
@@ -1636,6 +1682,12 @@
 
 	switch(format)
 	{
+	case VK_FORMAT_BC1_RGB_UNORM_BLOCK:
+	case VK_FORMAT_BC1_RGB_SRGB_BLOCK:
+	case VK_FORMAT_BC1_RGBA_UNORM_BLOCK:
+	case VK_FORMAT_BC1_RGBA_SRGB_BLOCK:
+	case VK_FORMAT_BC4_UNORM_BLOCK:
+	case VK_FORMAT_BC4_SNORM_BLOCK:
 	case VK_FORMAT_EAC_R11_UNORM_BLOCK:
 	case VK_FORMAT_EAC_R11_SNORM_BLOCK:
 	case VK_FORMAT_ETC2_R8G8B8_UNORM_BLOCK:
@@ -1643,6 +1695,12 @@
 	case VK_FORMAT_ETC2_R8G8B8A1_UNORM_BLOCK:
 	case VK_FORMAT_ETC2_R8G8B8A1_SRGB_BLOCK:
 		return 8 * ((width + 3) / 4);    // 64 bit per 4x4 block, computed per 4 rows
+	case VK_FORMAT_BC2_UNORM_BLOCK:
+	case VK_FORMAT_BC2_SRGB_BLOCK:
+	case VK_FORMAT_BC3_UNORM_BLOCK:
+	case VK_FORMAT_BC3_SRGB_BLOCK:
+	case VK_FORMAT_BC5_UNORM_BLOCK:
+	case VK_FORMAT_BC5_SNORM_BLOCK:
 	case VK_FORMAT_EAC_R11G11_UNORM_BLOCK:
 	case VK_FORMAT_EAC_R11G11_SNORM_BLOCK:
 	case VK_FORMAT_ETC2_R8G8B8A8_UNORM_BLOCK:
@@ -1701,6 +1759,18 @@
 
 	switch(format)
 	{
+	case VK_FORMAT_BC1_RGB_UNORM_BLOCK:
+	case VK_FORMAT_BC1_RGB_SRGB_BLOCK:
+	case VK_FORMAT_BC1_RGBA_UNORM_BLOCK:
+	case VK_FORMAT_BC1_RGBA_SRGB_BLOCK:
+	case VK_FORMAT_BC2_UNORM_BLOCK:
+	case VK_FORMAT_BC2_SRGB_BLOCK:
+	case VK_FORMAT_BC3_UNORM_BLOCK:
+	case VK_FORMAT_BC3_SRGB_BLOCK:
+	case VK_FORMAT_BC4_UNORM_BLOCK:
+	case VK_FORMAT_BC4_SNORM_BLOCK:
+	case VK_FORMAT_BC5_UNORM_BLOCK:
+	case VK_FORMAT_BC5_SNORM_BLOCK:
 	case VK_FORMAT_EAC_R11_UNORM_BLOCK:
 	case VK_FORMAT_EAC_R11_SNORM_BLOCK:
 	case VK_FORMAT_EAC_R11G11_UNORM_BLOCK:
diff --git a/src/Vulkan/VkImage.cpp b/src/Vulkan/VkImage.cpp
index e5d38e1..1606386 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/BC_Decoder.hpp"
 #include "Device/ETC_Decoder.hpp"
 #include <cstring>
 
@@ -53,6 +54,60 @@
 	}
 }
 
+int GetBCn(const vk::Format& format)
+{
+	switch(format)
+	{
+	case VK_FORMAT_BC1_RGB_UNORM_BLOCK:
+	case VK_FORMAT_BC1_RGBA_UNORM_BLOCK:
+	case VK_FORMAT_BC1_RGB_SRGB_BLOCK:
+	case VK_FORMAT_BC1_RGBA_SRGB_BLOCK:
+		return 1;
+	case VK_FORMAT_BC2_UNORM_BLOCK:
+	case VK_FORMAT_BC2_SRGB_BLOCK:
+		return 2;
+	case VK_FORMAT_BC3_UNORM_BLOCK:
+	case VK_FORMAT_BC3_SRGB_BLOCK:
+		return 3;
+	case VK_FORMAT_BC4_UNORM_BLOCK:
+	case VK_FORMAT_BC4_SNORM_BLOCK:
+		return 4;
+	case VK_FORMAT_BC5_UNORM_BLOCK:
+	case VK_FORMAT_BC5_SNORM_BLOCK:
+		return 5;
+	default:
+		UNIMPLEMENTED("format: %d", int(format));
+		return 0;
+	}
+}
+
+// Returns true for BC1 if we have an RGB format, false for RGBA
+// Returns true for BC4 and BC5 if we have an unsigned format, false for signed
+// Ignored by BC2 and BC3
+bool GetNoAlphaOrUnsigned(const vk::Format& format)
+{
+	switch(format)
+	{
+	case VK_FORMAT_BC1_RGB_UNORM_BLOCK:
+	case VK_FORMAT_BC1_RGB_SRGB_BLOCK:
+	case VK_FORMAT_BC4_UNORM_BLOCK:
+	case VK_FORMAT_BC5_UNORM_BLOCK:
+		return true;
+	case VK_FORMAT_BC1_RGBA_UNORM_BLOCK:
+	case VK_FORMAT_BC1_RGBA_SRGB_BLOCK:
+	case VK_FORMAT_BC2_UNORM_BLOCK:
+	case VK_FORMAT_BC2_SRGB_BLOCK:
+	case VK_FORMAT_BC3_UNORM_BLOCK:
+	case VK_FORMAT_BC3_SRGB_BLOCK:
+	case VK_FORMAT_BC4_SNORM_BLOCK:
+	case VK_FORMAT_BC5_SNORM_BLOCK:
+		return false;
+	default:
+		UNIMPLEMENTED("format: %d", int(format));
+		return false;
+	}
+}
+
 }  // anonymous namespace
 
 namespace vk {
@@ -917,6 +972,20 @@
 		case VK_FORMAT_ETC2_R8G8B8A8_SRGB_BLOCK:
 			decodeETC2(subresourceRange);
 			break;
+		case VK_FORMAT_BC1_RGB_UNORM_BLOCK:
+		case VK_FORMAT_BC1_RGB_SRGB_BLOCK:
+		case VK_FORMAT_BC1_RGBA_UNORM_BLOCK:
+		case VK_FORMAT_BC1_RGBA_SRGB_BLOCK:
+		case VK_FORMAT_BC2_UNORM_BLOCK:
+		case VK_FORMAT_BC2_SRGB_BLOCK:
+		case VK_FORMAT_BC3_UNORM_BLOCK:
+		case VK_FORMAT_BC3_SRGB_BLOCK:
+		case VK_FORMAT_BC4_UNORM_BLOCK:
+		case VK_FORMAT_BC4_SNORM_BLOCK:
+		case VK_FORMAT_BC5_UNORM_BLOCK:
+		case VK_FORMAT_BC5_SNORM_BLOCK:
+			decodeBC(subresourceRange);
+			break;
 		default:
 			break;
 		}
@@ -992,4 +1061,37 @@
 	}
 }
 
-}  // namespace vk
+void Image::decodeBC(const VkImageSubresourceRange& subresourceRange) const
+{
+	ASSERT(decompressedImage);
+
+	int n = GetBCn(format);
+	int noAlphaU = GetNoAlphaOrUnsigned(format);
+
+	uint32_t lastLayer = getLastLayerIndex(subresourceRange);
+	uint32_t lastMipLevel = getLastMipLevel(subresourceRange);
+
+	int bytes = decompressedImage->format.bytes();
+
+	VkImageSubresourceLayers subresourceLayers = { subresourceRange.aspectMask, subresourceRange.baseMipLevel, subresourceRange.baseArrayLayer, 1 };
+	for(; subresourceLayers.baseArrayLayer <= lastLayer; subresourceLayers.baseArrayLayer++)
+	{
+		for(; subresourceLayers.mipLevel <= lastMipLevel; subresourceLayers.mipLevel++)
+		{
+			VkExtent3D mipLevelExtent = getMipLevelExtent(static_cast<VkImageAspectFlagBits>(subresourceLayers.aspectMask), subresourceLayers.mipLevel);
+
+			int pitchB = decompressedImage->rowPitchBytes(VK_IMAGE_ASPECT_COLOR_BIT, subresourceLayers.mipLevel);
+
+			for(int32_t depth = 0; depth < static_cast<int32_t>(mipLevelExtent.depth); depth++)
+			{
+				uint8_t* source = static_cast<uint8_t*>(getTexelPointer({ 0, 0, depth }, subresourceLayers));
+				uint8_t* dest = static_cast<uint8_t*>(decompressedImage->getTexelPointer({ 0, 0, depth }, subresourceLayers));
+
+				BC_Decoder::Decode(source, dest, mipLevelExtent.width, mipLevelExtent.height,
+				                   mipLevelExtent.width, mipLevelExtent.height, pitchB, bytes, n, noAlphaU);
+			}
+		}
+	}
+}
+
+} // namespace vk
diff --git a/src/Vulkan/VkImage.hpp b/src/Vulkan/VkImage.hpp
index bc67d7e..358834f 100644
--- a/src/Vulkan/VkImage.hpp
+++ b/src/Vulkan/VkImage.hpp
@@ -108,6 +108,7 @@
 	void clear(void* pixelData, VkFormat pixelFormat, const vk::Format& viewFormat, const VkImageSubresourceRange& subresourceRange, const VkRect2D& renderArea);
 	int borderSize() const;
 	void decodeETC2(const VkImageSubresourceRange& subresourceRange) const;
+	void decodeBC(const VkImageSubresourceRange& subresourceRange) const;
 
 	const Device *const      device = nullptr;
 	DeviceMemory*            deviceMemory = nullptr;
diff --git a/src/Vulkan/VkPhysicalDevice.cpp b/src/Vulkan/VkPhysicalDevice.cpp
index 92d6b9a..b60e56e 100644
--- a/src/Vulkan/VkPhysicalDevice.cpp
+++ b/src/Vulkan/VkPhysicalDevice.cpp
@@ -475,6 +475,18 @@
 	case VK_FORMAT_R16G16B16A16_SFLOAT:
 	case VK_FORMAT_B10G11R11_UFLOAT_PACK32:
 	case VK_FORMAT_E5B9G9R9_UFLOAT_PACK32:
+	case VK_FORMAT_BC1_RGB_UNORM_BLOCK:
+	case VK_FORMAT_BC1_RGB_SRGB_BLOCK:
+	case VK_FORMAT_BC1_RGBA_UNORM_BLOCK:
+	case VK_FORMAT_BC1_RGBA_SRGB_BLOCK:
+	case VK_FORMAT_BC2_UNORM_BLOCK:
+	case VK_FORMAT_BC2_SRGB_BLOCK:
+	case VK_FORMAT_BC3_UNORM_BLOCK:
+	case VK_FORMAT_BC3_SRGB_BLOCK:
+	case VK_FORMAT_BC4_UNORM_BLOCK:
+	case VK_FORMAT_BC4_SNORM_BLOCK:
+	case VK_FORMAT_BC5_UNORM_BLOCK:
+	case VK_FORMAT_BC5_SNORM_BLOCK:
 	case VK_FORMAT_ETC2_R8G8B8_UNORM_BLOCK:
 	case VK_FORMAT_ETC2_R8G8B8_SRGB_BLOCK:
 	case VK_FORMAT_ETC2_R8G8B8A1_UNORM_BLOCK: