From 1a460d73be72b037c3d6c779db6fde224ce84dd2 Mon Sep 17 00:00:00 2001
From: Samuliak <samuliak77@gmail.com>
Date: Tue, 11 Mar 2025 08:35:35 +0100
Subject: [PATCH] metal: implement texture swizzling

---
 include/renderer_mtl/mtl_texture.hpp  |   1 +
 include/renderer_mtl/pica_to_mtl.hpp  |   2 +-
 src/core/renderer_mtl/mtl_texture.cpp | 216 ++------------------------
 src/core/renderer_mtl/pica_to_mtl.cpp |  38 ++++-
 4 files changed, 43 insertions(+), 214 deletions(-)

diff --git a/include/renderer_mtl/mtl_texture.hpp b/include/renderer_mtl/mtl_texture.hpp
index 0e78b13c..667fcb33 100644
--- a/include/renderer_mtl/mtl_texture.hpp
+++ b/include/renderer_mtl/mtl_texture.hpp
@@ -29,6 +29,7 @@ namespace Metal {
 		Interval<u32> range;
 
 		PICA::PixelFormatInfo formatInfo;
+		MTL::Texture* base = nullptr;
 		MTL::Texture* texture = nullptr;
 		MTL::SamplerState* sampler = nullptr;
 
diff --git a/include/renderer_mtl/pica_to_mtl.hpp b/include/renderer_mtl/pica_to_mtl.hpp
index a0874a65..5cb5947c 100644
--- a/include/renderer_mtl/pica_to_mtl.hpp
+++ b/include/renderer_mtl/pica_to_mtl.hpp
@@ -12,7 +12,7 @@ namespace PICA {
 		size_t bytesPerTexel;
 		void (*decoder)(OpenGL::uvec2, u32, u32, std::span<const u8>, std::vector<u8>&);
 		bool needsSwizzle{false};
-		// TODO: swizzle
+		MTL::TextureSwizzleChannels swizzle{.red = MTL::TextureSwizzleRed, .green = MTL::TextureSwizzleGreen, .blue = MTL::TextureSwizzleBlue, .alpha = MTL::TextureSwizzleAlpha};
 	};
 
 	extern PixelFormatInfo pixelFormatInfos[14];
diff --git a/src/core/renderer_mtl/mtl_texture.cpp b/src/core/renderer_mtl/mtl_texture.cpp
index 3b4e065d..9850cd54 100644
--- a/src/core/renderer_mtl/mtl_texture.cpp
+++ b/src/core/renderer_mtl/mtl_texture.cpp
@@ -21,10 +21,15 @@ namespace Metal {
 		descriptor->setStorageMode(MTL::StorageModeShared);  // TODO: use private + staging buffers?
 		texture = device->newTexture(descriptor);
 		texture->setLabel(toNSString(
-			"Texture " + std::string(PICA::textureFormatToString(format)) + " " + std::to_string(size.u()) + "x" + std::to_string(size.v())
+			"Base texture " + std::string(PICA::textureFormatToString(format)) + " " + std::to_string(size.u()) + "x" + std::to_string(size.v())
 		));
 		descriptor->release();
 
+		if (formatInfo.needsSwizzle) {
+		    base = texture;
+		    texture = base->newTextureView(formatInfo.pixelFormat, MTL::TextureType2D, NS::Range(0, 1), NS::Range(0, 1), formatInfo.swizzle);
+		}
+
 		setNewConfig(config);
 	}
 
@@ -58,6 +63,9 @@ namespace Metal {
 		if (texture) {
 			texture->release();
 		}
+		if (base) {
+		    base->release();
+		}
 		if (sampler) {
 			sampler->release();
 		}
@@ -99,212 +107,6 @@ namespace Metal {
 		}
 	}
 
-	/*
-	u8 Texture::decodeTexelU8(u32 u, u32 v, PICA::TextureFmt fmt, std::span<const u8> data) {
-		switch (fmt) {
-			case PICA::TextureFmt::A4: {
-				const u32 offset = getSwizzledOffset_4bpp(u, v, size.u());
-
-				// For odd U coordinates, grab the top 4 bits, and the low 4 bits for even coordinates
-				u8 alpha = data[offset] >> ((u % 2) ? 4 : 0);
-				alpha = Colour::convert4To8Bit(getBits<0, 4>(alpha));
-
-				// A8
-				return alpha;
-			}
-
-			case PICA::TextureFmt::A8: {
-				u32 offset = getSwizzledOffset(u, v, size.u(), 1);
-				const u8 alpha = data[offset];
-
-				// A8
-				return alpha;
-			}
-
-			default: Helpers::panic("[Texture::DecodeTexel] Unimplemented format = %d", static_cast<int>(fmt));
-		}
-	}
-
-	u16 Texture::decodeTexelU16(u32 u, u32 v, PICA::TextureFmt fmt, std::span<const u8> data) {
-		switch (fmt) {
-			case PICA::TextureFmt::RG8: {
-				u32 offset = getSwizzledOffset(u, v, size.u(), 2);
-				constexpr u8 b = 0;
-				const u8 g = data[offset];
-				const u8 r = data[offset + 1];
-
-				// RG8
-				return (g << 8) | r;
-			}
-
-			case PICA::TextureFmt::RGBA4: {
-				u32 offset = getSwizzledOffset(u, v, size.u(), 2);
-				u16 texel = u16(data[offset]) | (u16(data[offset + 1]) << 8);
-
-				u8 alpha = getBits<0, 4, u8>(texel);
-				u8 b = getBits<4, 4, u8>(texel);
-				u8 g = getBits<8, 4, u8>(texel);
-				u8 r = getBits<12, 4, u8>(texel);
-
-				// ABGR4
-				return (r << 12) | (g << 8) | (b << 4) | alpha;
-			}
-
-			case PICA::TextureFmt::RGBA5551: {
-				const u32 offset = getSwizzledOffset(u, v, size.u(), 2);
-				const u16 texel = u16(data[offset]) | (u16(data[offset + 1]) << 8);
-
-				u8 alpha = getBit<0>(texel) ? 0xff : 0;
-				u8 b = getBits<1, 5, u8>(texel);
-				u8 g = getBits<6, 5, u8>(texel);
-				u8 r = getBits<11, 5, u8>(texel);
-
-				// BGR5A1
-				return (alpha << 15) | (r << 10) | (g << 5) | b;
-			}
-
-			case PICA::TextureFmt::RGB565: {
-				const u32 offset = getSwizzledOffset(u, v, size.u(), 2);
-				const u16 texel = u16(data[offset]) | (u16(data[offset + 1]) << 8);
-
-				const u8 b = getBits<0, 5, u8>(texel);
-				const u8 g = getBits<5, 6, u8>(texel);
-				const u8 r = getBits<11, 5, u8>(texel);
-
-				// B5G6R5
-				return (r << 11) | (g << 5) | b;
-			}
-
-			case PICA::TextureFmt::IA4: {
-				const u32 offset = getSwizzledOffset(u, v, size.u(), 1);
-				const u8 texel = data[offset];
-				const u8 alpha = texel & 0xf;
-				const u8 intensity = texel >> 4;
-
-				// ABGR4
-				return (intensity << 12) | (intensity << 8) | (intensity << 4) | alpha;
-			}
-
-			case PICA::TextureFmt::I4: {
-				u32 offset = getSwizzledOffset_4bpp(u, v, size.u());
-
-				// For odd U coordinates, grab the top 4 bits, and the low 4 bits for even coordinates
-				u8 intensity = data[offset] >> ((u % 2) ? 4 : 0);
-				intensity = getBits<0, 4>(intensity);
-
-				// ABGR4
-				return (intensity << 12) | (intensity << 8) | (intensity << 4) | 0xff;
-			}
-
-			default: Helpers::panic("[Texture::DecodeTexel] Unimplemented format = %d", static_cast<int>(fmt));
-		}
-	}
-
-	u32 Texture::decodeTexelU32(u32 u, u32 v, PICA::TextureFmt fmt, std::span<const u8> data) {
-		switch (fmt) {
-			case PICA::TextureFmt::RGB8: {
-				const u32 offset = getSwizzledOffset(u, v, size.u(), 3);
-				const u8 b = data[offset];
-				const u8 g = data[offset + 1];
-				const u8 r = data[offset + 2];
-
-				// RGBA8
-				return (0xff << 24) | (b << 16) | (g << 8) | r;
-			}
-
-			case PICA::TextureFmt::RGBA8: {
-				const u32 offset = getSwizzledOffset(u, v, size.u(), 4);
-				const u8 alpha = data[offset];
-				const u8 b = data[offset + 1];
-				const u8 g = data[offset + 2];
-				const u8 r = data[offset + 3];
-
-				// RGBA8
-				return (alpha << 24) | (b << 16) | (g << 8) | r;
-			}
-
-			case PICA::TextureFmt::ETC1: return getTexelETC(false, u, v, size.u(), data);
-			case PICA::TextureFmt::ETC1A4: return getTexelETC(true, u, v, size.u(), data);
-
-			case PICA::TextureFmt::RGBA4: {
-				u32 offset = getSwizzledOffset(u, v, size.u(), 2);
-				u16 texel = u16(data[offset]) | (u16(data[offset + 1]) << 8);
-
-				u8 alpha = Colour::convert4To8Bit(getBits<0, 4, u8>(texel));
-				u8 b = Colour::convert4To8Bit(getBits<4, 4, u8>(texel));
-				u8 g = Colour::convert4To8Bit(getBits<8, 4, u8>(texel));
-				u8 r = Colour::convert4To8Bit(getBits<12, 4, u8>(texel));
-
-				return (alpha << 24) | (b << 16) | (g << 8) | r;
-			}
-
-			case PICA::TextureFmt::I4: {
-				u32 offset = getSwizzledOffset_4bpp(u, v, size.u());
-
-				// For odd U coordinates, grab the top 4 bits, and the low 4 bits for even coordinates
-				u8 intensity = data[offset] >> ((u % 2) ? 4 : 0);
-				intensity = Colour::convert4To8Bit(getBits<0, 4>(intensity));
-
-				// Intensity formats just copy the intensity value to every colour channel
-				return (0xff << 24) | (intensity << 16) | (intensity << 8) | intensity;
-			}
-
-			case PICA::TextureFmt::IA4: {
-				const u32 offset = getSwizzledOffset(u, v, size.u(), 1);
-				const u8 texel = data[offset];
-				const u8 alpha = Colour::convert4To8Bit(texel & 0xf);
-				const u8 intensity = Colour::convert4To8Bit(texel >> 4);
-
-				// Intensity formats just copy the intensity value to every colour channel
-				return (alpha << 24) | (intensity << 16) | (intensity << 8) | intensity;
-			}
-
-			case PICA::TextureFmt::A4: {
-				const u32 offset = getSwizzledOffset_4bpp(u, v, size.u());
-
-				// For odd U coordinates, grab the top 4 bits, and the low 4 bits for even coordinates
-				u8 alpha = data[offset] >> ((u % 2) ? 4 : 0);
-				alpha = Colour::convert4To8Bit(getBits<0, 4>(alpha));
-
-				// A8 sets RGB to 0
-				return (alpha << 24) | (0 << 16) | (0 << 8) | 0;
-			}
-
-			case PICA::TextureFmt::I8: {
-				u32 offset = getSwizzledOffset(u, v, size.u(), 1);
-				const u8 intensity = data[offset];
-
-				// RGBA8
-				return (0xff << 24) | (intensity << 16) | (intensity << 8) | intensity;
-			}
-
-			case PICA::TextureFmt::IA8: {
-				u32 offset = getSwizzledOffset(u, v, size.u(), 2);
-
-				// Same as I8 except each pixel gets its own alpha value too
-				const u8 alpha = data[offset];
-				const u8 intensity = data[offset + 1];
-
-				// RGBA8
-				return (alpha << 24) | (intensity << 16) | (intensity << 8) | intensity;
-			}
-
-			case PICA::TextureFmt::RGB565: {
-				const u32 offset = getSwizzledOffset(u, v, size.u(), 2);
-				const u16 texel = u16(data[offset]) | (u16(data[offset + 1]) << 8);
-
-				const u8 b = Colour::convert5To8Bit(getBits<0, 5, u8>(texel));
-				const u8 g = Colour::convert6To8Bit(getBits<5, 6, u8>(texel));
-				const u8 r = Colour::convert5To8Bit(getBits<11, 5, u8>(texel));
-
-				return (0xff << 24) | (b << 16) | (g << 8) | r;
-			}
-
-			default: Helpers::panic("[Texture::DecodeTexel] Unimplemented format = %d", static_cast<int>(fmt));
-		}
-	}
-	*/
-
 	void Texture::decodeTexture(std::span<const u8> data) {
 		std::vector<u8> decoded;
 		decoded.reserve(u64(size.u()) * u64(size.v()) * formatInfo.bytesPerTexel);
diff --git a/src/core/renderer_mtl/pica_to_mtl.cpp b/src/core/renderer_mtl/pica_to_mtl.cpp
index 10b3eda8..1b421765 100644
--- a/src/core/renderer_mtl/pica_to_mtl.cpp
+++ b/src/core/renderer_mtl/pica_to_mtl.cpp
@@ -5,19 +5,39 @@
 using namespace Helpers;
 
 namespace PICA {
-
     PixelFormatInfo pixelFormatInfos[14] = {
 		{MTL::PixelFormatRGBA8Unorm, 4, decodeTexelABGR8ToRGBA8},    // RGBA8
 		{MTL::PixelFormatRGBA8Unorm, 4, decodeTexelBGR8ToRGBA8},     // RGB8
 		{MTL::PixelFormatBGR5A1Unorm, 2, decodeTexelA1BGR5ToBGR5A1}, // RGBA5551
 		{MTL::PixelFormatB5G6R5Unorm, 2, decodeTexelB5G6R5ToB5G6R5}, // RGB565
 		{MTL::PixelFormatABGR4Unorm, 2, decodeTexelABGR4ToABGR4},    // RGBA4
-		{MTL::PixelFormatRG8Unorm, 2, decodeTexelAI8ToRG8},          // IA8
+		{MTL::PixelFormatRG8Unorm, 2, decodeTexelAI8ToRG8, true,
+		    {
+				.red = MTL::TextureSwizzleRed,
+				.green = MTL::TextureSwizzleRed,
+				.blue = MTL::TextureSwizzleRed,
+				.alpha = MTL::TextureSwizzleGreen,
+			}
+		},                                                           // IA8
 		{MTL::PixelFormatRG8Unorm, 2, decodeTexelGR8ToRG8},          // RG8
-		{MTL::PixelFormatR8Unorm, 1, decodeTexelI8ToR8},             // I8
+		{MTL::PixelFormatR8Unorm, 1, decodeTexelI8ToR8, true,
+		    {
+				.red = MTL::TextureSwizzleRed,
+				.green = MTL::TextureSwizzleRed,
+				.blue = MTL::TextureSwizzleRed,
+				.alpha = MTL::TextureSwizzleOne
+			}
+		},                                                           // I8
 		{MTL::PixelFormatA8Unorm, 1, decodeTexelA8ToA8},             // A8
 		{MTL::PixelFormatABGR4Unorm, 2, decodeTexelAI4ToABGR4},      // IA4
-		{MTL::PixelFormatR8Unorm, 1, decodeTexelI4ToR8},             // I4
+		{MTL::PixelFormatR8Unorm, 1, decodeTexelI4ToR8, true,
+		    {
+				.red = MTL::TextureSwizzleRed,
+				.green = MTL::TextureSwizzleRed,
+				.blue = MTL::TextureSwizzleRed,
+				.alpha = MTL::TextureSwizzleOne
+			}
+		},                                                           // I4
 		{MTL::PixelFormatA8Unorm, 1, decodeTexelA4ToA8},             // A4
 		{MTL::PixelFormatRGBA8Unorm, 4, decodeTexelETC1ToRGBA8},     // ETC1
 		{MTL::PixelFormatRGBA8Unorm, 4, decodeTexelETC1A4ToRGBA8},   // ETC1A4
@@ -28,8 +48,14 @@ namespace PICA {
 			pixelFormatInfos[2] = {MTL::PixelFormatRGBA8Unorm, 4, decodeTexelA1BGR5ToRGBA8};
 			pixelFormatInfos[3] = {MTL::PixelFormatRGBA8Unorm, 4, decodeTexelB5G6R5ToRGBA8};
 			pixelFormatInfos[4] = {MTL::PixelFormatRGBA8Unorm, 4, decodeTexelABGR4ToRGBA8};
-			pixelFormatInfos[9] = {MTL::PixelFormatRG8Unorm, 2, decodeTexelAI4ToRG8};
+			pixelFormatInfos[9] = {MTL::PixelFormatRG8Unorm, 2, decodeTexelAI4ToRG8, true,
+			    {
+					.red = MTL::TextureSwizzleRed,
+					.green = MTL::TextureSwizzleRed,
+					.blue = MTL::TextureSwizzleRed,
+					.alpha = MTL::TextureSwizzleGreen,
+				}
+			};
 		}
 	}
-
 }