From 0cd3f581d3502a5eb31a3bd216811a87c1652802 Mon Sep 17 00:00:00 2001 From: wheremyfoodat Date: Wed, 1 Mar 2023 01:42:46 +0200 Subject: [PATCH] [PICA] Add ETC1 and ETC1A4 support --- CMakeLists.txt | 2 +- include/renderer_gl/textures.hpp | 6 ++ src/core/renderer_gl/etc1.cpp | 119 ++++++++++++++++++++++++++++++ src/core/renderer_gl/textures.cpp | 4 +- 4 files changed, 129 insertions(+), 2 deletions(-) create mode 100644 src/core/renderer_gl/etc1.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index cbff4b67..e3009c1f 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -64,7 +64,7 @@ set(SERVICE_SOURCE_FILES src/core/services/service_manager.cpp src/core/services set(PICA_SOURCE_FILES src/core/PICA/gpu.cpp src/core/PICA/regs.cpp src/core/PICA/shader_unit.cpp src/core/PICA/shader_interpreter.cpp ) -set(RENDERER_GL_SOURCE_FILES src/core/renderer_gl/renderer_gl.cpp src/core/renderer_gl/textures.cpp) +set(RENDERER_GL_SOURCE_FILES src/core/renderer_gl/renderer_gl.cpp src/core/renderer_gl/textures.cpp src/core/renderer_gl/etc1.cpp) set(LOADER_SOURCE_FILES src/core/loader/elf.cpp src/core/loader/ncsd.cpp src/core/loader/ncch.cpp) set(FS_SOURCE_FILES src/core/fs/archive_self_ncch.cpp src/core/fs/archive_save_data.cpp src/core/fs/archive_sdmc.cpp diff --git a/include/renderer_gl/textures.hpp b/include/renderer_gl/textures.hpp index 955c7cc3..2deaf33f 100644 --- a/include/renderer_gl/textures.hpp +++ b/include/renderer_gl/textures.hpp @@ -65,4 +65,10 @@ struct Texture { static u32 mortonInterleave(u32 u, u32 v); // Get the byte offset of texel (u, v) in the texture static u32 getSwizzledOffset(u32 u, u32 v, u32 width, u32 bytesPerPixel); + + // Returns the texel at coordinates (u, v) of an ETC1(A4) texture + // TODO: Make hasAlpha a template parameter + u32 getTexelETC(bool hasAlpha, u32 u, u32 v, u32 width, const void* data); + u32 decodeETC(u32 alpha, u32 u, u32 v, u64 colourData); + }; \ No newline at end of file diff --git a/src/core/renderer_gl/etc1.cpp b/src/core/renderer_gl/etc1.cpp new file mode 100644 index 00000000..b983bb58 --- /dev/null +++ b/src/core/renderer_gl/etc1.cpp @@ -0,0 +1,119 @@ +#include +#include "colour.hpp" +#include "renderer_gl/renderer_gl.hpp" +#include "renderer_gl/textures.hpp" + +static constexpr u32 signExtend3To32(u32 val) { + return (u32)(s32(val) << 29 >> 29); +} + +u32 Texture::getTexelETC(bool hasAlpha, u32 u, u32 v, u32 width, const void* data) { + // Pixel offset of the 8x8 tile based on u, vand the width of the texture + u32 offs = ((u & ~7) * 8) + ((v & ~7) * width); + if (!hasAlpha) + offs >>= 1; + + // In-tile offsets for u/v + u &= 7; + v &= 7; + + // ETC1(A4) also subdivide the 8x8 tile to 4 4x4 tiles + // Each tile is 8 bytes for ETC1, but since ETC1A4 has 4 alpha bits per pixel, that becomes 16 bytes + const u32 subTileSize = hasAlpha ? 16 : 8; + const u32 subTileIndex = (u / 4) + 2 * (v / 4); // Which of the 4 subtiles is this texel in? + + // In-subtile offsets for u/v + u &= 3; + v &= 3; + offs += subTileSize * subTileIndex; + + u32 alpha; + const u8* tmp = static_cast(data) + offs; // Pointer to colour and alpha data as u8* + const u64* ptr = reinterpret_cast(tmp); // Cast to u64* + + if (hasAlpha) { + // First 64 bits of the 4x4 subtile are alpha data + const u64 alphaData = *ptr++; + alpha = Colour::convert4To8Bit((alphaData >> (4 * (u * 4 + v))) & 0xf); + } + else { + alpha = 0xff; // ETC1 without alpha uses ff for every pixel + } + + // Next 64 bits of the subtile are colour data + u64 colourData = *ptr; + return decodeETC(alpha, u, v, colourData); +} + +u32 Texture::decodeETC(u32 alpha, u32 u, u32 v, u64 colourData) { + static constexpr u32 modifiers[8][2] = { + { 2, 8 }, + { 5, 17 }, + { 9, 29 }, + { 13, 42 }, + { 18, 60 }, + { 24, 80 }, + { 33, 106 }, + { 47, 183 }, + }; + + // Parse colour data for 4x4 block + const u32 subindices = colourData & 0xffff; + const u32 negationFlags = (colourData >> 16) & 0xffff; + const bool flip = (colourData >> 32) & 1; + const bool diffMode = (colourData >> 33) & 1; + + // Note: index1 is indeed stored on the higher bits, with index2 in the lower bits + const u32 tableIndex1 = (colourData >> 37) & 7; + const u32 tableIndex2 = (colourData >> 34) & 7; + const u32 texelIndex = u * 4 + v; // Index of the texel in the block + + if (flip) + std::swap(u, v); + + s32 r, g, b; + if (diffMode) { + r = (colourData >> 59) & 0x1f; + g = (colourData >> 51) & 0x1f; + b = (colourData >> 43) & 0x1f; + + if (u >= 2) { + r += signExtend3To32((colourData >> 56) & 0x7); + g += signExtend3To32((colourData >> 48) & 0x7); + b += signExtend3To32((colourData >> 40) & 0x7); + } + + // Expand from 5 to 8 bits per channel + r = Colour::convert5To8Bit(r); + g = Colour::convert5To8Bit(g); + b = Colour::convert5To8Bit(b); + } else { + if (u < 2) { + r = (colourData >> 60) & 0xf; + g = (colourData >> 52) & 0xf; + b = (colourData >> 44) & 0xf; + } else { + r = (colourData >> 56) & 0xf; + g = (colourData >> 48) & 0xf; + b = (colourData >> 40) & 0xf; + } + + // Expand from 4 to 8 bits per channel + r = Colour::convert4To8Bit(r); + g = Colour::convert4To8Bit(g); + b = Colour::convert4To8Bit(b); + } + + const u32 index = (u < 2) ? tableIndex1 : tableIndex2; + s32 modifier = modifiers[index][(subindices >> texelIndex) & 1]; + + if (((negationFlags >> texelIndex) & 1) != 0) { + modifier = -modifier; + } + + r = std::clamp(r + modifier, 0, 255); + g = std::clamp(g + modifier, 0, 255); + b = std::clamp(b + modifier, 0, 255); + + return (alpha << 24) | (u32(b) << 16) | (u32(g) << 8) | u32(r); +} \ No newline at end of file diff --git a/src/core/renderer_gl/textures.cpp b/src/core/renderer_gl/textures.cpp index 8d5e4060..6e7ea3b1 100644 --- a/src/core/renderer_gl/textures.cpp +++ b/src/core/renderer_gl/textures.cpp @@ -85,7 +85,6 @@ u32 Texture::getSwizzledOffset(u32 u, u32 v, u32 width, u32 bytesPerPixel) { // data: texture data of the texture u32 Texture::decodeTexel(u32 u, u32 v, Texture::Formats fmt, const void* data) { switch (fmt) { - case Formats::ETC1A4: case Formats::RGBA4: { u32 offset = getSwizzledOffset(u, v, size.u(), 2); auto ptr = static_cast(data); @@ -167,6 +166,9 @@ u32 Texture::decodeTexel(u32 u, u32 v, Texture::Formats fmt, const void* data) { return (alpha << 24) | (intensity << 16) | (intensity << 8) | intensity; } + case Formats::ETC1: return getTexelETC(false, u, v, size.u(), data); + case Formats::ETC1A4: return getTexelETC(true, u, v, size.u(), data); + default: Helpers::panic("[Texture::DecodeTexel] Unimplemented format = %d", static_cast(fmt)); }