#include "CTexture.hpp" #include "CSimplePool.hpp" #include "CToken.hpp" #include "Graphics/CGraphics.hpp" namespace urde { static logvisor::Module Log("urde::CTextureBoo"); /* GX uses this upsampling technique to extract full 8-bit range */ static inline uint8_t Convert3To8(uint8_t v) { /* Swizzle bits: 00000123 -> 12312312 */ return (v << 5) | (v << 2) | (v >> 1); } static inline uint8_t Convert4To8(uint8_t v) { /* Swizzle bits: 00001234 -> 12341234 */ return (v << 4) | v; } static inline uint8_t Convert5To8(uint8_t v) { /* Swizzle bits: 00012345 -> 12345123 */ return (v << 3) | (v >> 2); } static inline uint8_t Convert6To8(uint8_t v) { /* Swizzle bits: 00123456 -> 12345612 */ return (v << 2) | (v >> 4); } size_t CTexture::ComputeMippedTexelCount() { size_t w = x4_w; size_t h = x6_h; size_t ret = w * h; for (int i=x8_mips ; i>1 ; --i) { if (w > 1) w /= 2; if (h > 1) h /= 2; ret += w * h; } return ret; } size_t CTexture::ComputeMippedBlockCountDXT1() { size_t w = x4_w / 4; size_t h = x6_h / 4; size_t ret = w * h; for (int i=x8_mips ; i>1 ; --i) { if (w > 1) w /= 2; if (h > 1) h /= 2; ret += w * h; } return ret; } struct RGBA8 { u8 r; u8 g; u8 b; u8 a; }; void CTexture::BuildI4FromGCN(CInputStream& in) { size_t texelCount = ComputeMippedTexelCount(); std::unique_ptr buf(new RGBA8[texelCount]); int w = x4_w; int h = x6_h; RGBA8* targetMip = buf.get(); for (int mip=0 ; mip> ((x&1)?0:4) & 0xf); target[x].g = target[x].r; target[x].b = target[x].r; target[x].a = target[x].r; } } } } targetMip += w * h; if (w > 1) w /= 2; if (h > 1) h /= 2; } m_booToken = CGraphics::CommitResources([&](boo::IGraphicsDataFactory::Context& ctx) -> bool { m_booTex = ctx.newStaticTexture(x4_w, x6_h, x8_mips, boo::TextureFormat::RGBA8, buf.get(), texelCount * 4); return true; }); } void CTexture::BuildI8FromGCN(CInputStream& in) { size_t texelCount = ComputeMippedTexelCount(); std::unique_ptr buf(new RGBA8[texelCount]); int w = x4_w; int h = x6_h; RGBA8* targetMip = buf.get(); for (int mip=0 ; mip 1) w /= 2; if (h > 1) h /= 2; } m_booToken = CGraphics::CommitResources([&](boo::IGraphicsDataFactory::Context& ctx) -> bool { m_booTex = ctx.newStaticTexture(x4_w, x6_h, x8_mips, boo::TextureFormat::RGBA8, buf.get(), texelCount * 4); return true; }); } void CTexture::BuildIA4FromGCN(CInputStream& in) { size_t texelCount = ComputeMippedTexelCount(); std::unique_ptr buf(new RGBA8[texelCount]); int w = x4_w; int h = x6_h; RGBA8* targetMip = buf.get(); for (int mip=0 ; mip> 4 & 0xf); target[x].r = intensity; target[x].g = intensity; target[x].b = intensity; target[x].a = Convert4To8(source[x] & 0xf); } } } } targetMip += w * h; if (w > 1) w /= 2; if (h > 1) h /= 2; } m_booToken = CGraphics::CommitResources([&](boo::IGraphicsDataFactory::Context& ctx) -> bool { m_booTex = ctx.newStaticTexture(x4_w, x6_h, x8_mips, boo::TextureFormat::RGBA8, buf.get(), texelCount * 4); return true; }); } void CTexture::BuildIA8FromGCN(CInputStream& in) { size_t texelCount = ComputeMippedTexelCount(); std::unique_ptr buf(new RGBA8[texelCount]); int w = x4_w; int h = x6_h; RGBA8* targetMip = buf.get(); for (int mip=0 ; mip> 8; target[x].r = intensity; target[x].g = intensity; target[x].b = intensity; target[x].a = source[x] & 0xff; } } } } targetMip += w * h; if (w > 1) w /= 2; if (h > 1) h /= 2; } m_booToken = CGraphics::CommitResources([&](boo::IGraphicsDataFactory::Context& ctx) -> bool { m_booTex = ctx.newStaticTexture(x4_w, x6_h, x8_mips, boo::TextureFormat::RGBA8, buf.get(), texelCount * 4); return true; }); } static std::vector DecodePalette(int numEntries, CInputStream& in) { std::vector ret; ret.reserve(numEntries); enum class EPaletteType { IA8, RGB565, RGB5A3 }; EPaletteType format = EPaletteType(in.readUint32Big()); in.readUint32Big(); switch (format) { case EPaletteType::IA8: { for (int e=0 ; e> 11 & 0x1f), Convert6To8(texel >> 5 & 0x3f), Convert5To8(texel & 0x1f), 0xff}); } break; } case EPaletteType::RGB5A3: { for (int e=0 ; e> 10 & 0x1f), Convert5To8(texel >> 5 & 0x1f), Convert5To8(texel & 0x1f), 0xff}); } else { ret.push_back({Convert4To8(texel >> 8 & 0xf), Convert4To8(texel >> 4 & 0xf), Convert4To8(texel & 0xf), Convert3To8(texel >> 12 & 0x7)}); } } break; } } return ret; } void CTexture::BuildC4FromGCN(CInputStream& in) { size_t texelCount = ComputeMippedTexelCount(); std::unique_ptr buf(new RGBA8[texelCount]); std::vector palette = DecodePalette(16, in); int w = x4_w; int h = x6_h; RGBA8* targetMip = buf.get(); for (int mip=0 ; mip> ((x&1)?0:4) & 0xf]; } } } targetMip += w * h; if (w > 1) w /= 2; if (h > 1) h /= 2; } m_booToken = CGraphics::CommitResources([&](boo::IGraphicsDataFactory::Context& ctx) -> bool { m_booTex = ctx.newStaticTexture(x4_w, x6_h, x8_mips, boo::TextureFormat::RGBA8, buf.get(), texelCount * 4); return true; }); } void CTexture::BuildC8FromGCN(CInputStream& in) { size_t texelCount = ComputeMippedTexelCount(); std::unique_ptr buf(new RGBA8[texelCount]); std::vector palette = DecodePalette(256, in); int w = x4_w; int h = x6_h; RGBA8* targetMip = buf.get(); for (int mip=0 ; mip 1) w /= 2; if (h > 1) h /= 2; } m_booToken = CGraphics::CommitResources([&](boo::IGraphicsDataFactory::Context& ctx) -> bool { m_booTex = ctx.newStaticTexture(x4_w, x6_h, x8_mips, boo::TextureFormat::RGBA8, buf.get(), texelCount * 4); return true; }); } void CTexture::BuildC14X2FromGCN(CInputStream& in) { } void CTexture::BuildRGB565FromGCN(CInputStream& in) { size_t texelCount = ComputeMippedTexelCount(); std::unique_ptr buf(new RGBA8[texelCount]); int w = x4_w; int h = x6_h; RGBA8* targetMip = buf.get(); for (int mip=0 ; mip> 11 & 0x1f); target[x].g = Convert6To8(texel >> 5 & 0x3f); target[x].b = Convert5To8(texel & 0x1f); target[x].a = 0xff; } } } } targetMip += w * h; if (w > 1) w /= 2; if (h > 1) h /= 2; } m_booToken = CGraphics::CommitResources([&](boo::IGraphicsDataFactory::Context& ctx) -> bool { m_booTex = ctx.newStaticTexture(x4_w, x6_h, x8_mips, boo::TextureFormat::RGBA8, buf.get(), texelCount * 4); return true; }); } void CTexture::BuildRGB5A3FromGCN(CInputStream& in) { size_t texelCount = ComputeMippedTexelCount(); std::unique_ptr buf(new RGBA8[texelCount]); int w = x4_w; int h = x6_h; RGBA8* targetMip = buf.get(); for (int mip=0 ; mip> 10 & 0x1f); target[x].g = Convert5To8(texel >> 5 & 0x1f); target[x].b = Convert5To8(texel & 0x1f); target[x].a = 0xff; } else { target[x].r = Convert4To8(texel >> 8 & 0xf); target[x].g = Convert4To8(texel >> 4 & 0xf); target[x].b = Convert4To8(texel & 0xf); target[x].a = Convert3To8(texel >> 12 & 0x7); } } } } } targetMip += w * h; if (w > 1) w /= 2; if (h > 1) h /= 2; } m_booToken = CGraphics::CommitResources([&](boo::IGraphicsDataFactory::Context& ctx) -> bool { m_booTex = ctx.newStaticTexture(x4_w, x6_h, x8_mips, boo::TextureFormat::RGBA8, buf.get(), texelCount * 4); return true; }); } void CTexture::BuildRGBA8FromGCN(CInputStream& in) { size_t texelCount = ComputeMippedTexelCount(); std::unique_ptr buf(new RGBA8[texelCount]); int w = x4_w; int h = x6_h; RGBA8* targetMip = buf.get(); for (int mip=0 ; mip 1) w /= 2; if (h > 1) h /= 2; } m_booToken = CGraphics::CommitResources([&](boo::IGraphicsDataFactory::Context& ctx) -> bool { m_booTex = ctx.newStaticTexture(x4_w, x6_h, x8_mips, boo::TextureFormat::RGBA8, buf.get(), texelCount * 4); return true; }); } struct DXT1Block { uint16_t color1; uint16_t color2; uint8_t lines[4]; }; void CTexture::BuildDXT1FromGCN(CInputStream& in) { size_t blockCount = ComputeMippedBlockCountDXT1(); std::unique_ptr buf(new DXT1Block[blockCount]); int w = x4_w / 4; int h = x6_h / 4; DXT1Block* targetMip = buf.get(); for (int mip=0 ; mip> 2) & 0x3; ind[1] = (packed >> 4) & 0x3; ind[0] = (packed >> 6) & 0x3; target[x].lines[i] = ind[0] | (ind[1] << 2) | (ind[2] << 4) | (ind[3] << 6); } } } } } targetMip += w * h; if (w > 1) w /= 2; if (h > 1) h /= 2; } m_booToken = CGraphics::CommitResources([&](boo::IGraphicsDataFactory::Context& ctx) -> bool { m_booTex = ctx.newStaticTexture(x4_w, x6_h, x8_mips, boo::TextureFormat::DXT1, buf.get(), blockCount * 8); return true; }); } void CTexture::BuildRGBA8(const void* data, size_t length) { size_t texelCount = ComputeMippedTexelCount(); size_t expectedSize = texelCount * 4; if (expectedSize > length) Log.report(logvisor::Fatal, "insufficient TXTR length (%" PRISize "/%" PRISize ")", length, expectedSize); m_booToken = CGraphics::CommitResources([&](boo::IGraphicsDataFactory::Context& ctx) -> bool { m_booTex = ctx.newStaticTexture(x4_w, x6_h, x8_mips, boo::TextureFormat::RGBA8, data, expectedSize); return true; }); } void CTexture::BuildC8(const void* data, size_t length) { size_t texelCount = ComputeMippedTexelCount(); if (texelCount > length) Log.report(logvisor::Fatal, "insufficient TXTR length (%" PRISize "/%" PRISize ")", length, texelCount); m_booToken = CGraphics::CommitResources([&](boo::IGraphicsDataFactory::Context& ctx) -> bool { uint32_t nentries = hecl::SBig(*reinterpret_cast(data)); const u8* paletteTexels = reinterpret_cast(data) + 4; const u8* texels = reinterpret_cast(data) + 4 + nentries * 4; m_paletteTex = ctx.newStaticTexture(nentries, 1, 1, boo::TextureFormat::RGBA8, paletteTexels, nentries * 4); m_booTex = ctx.newStaticTexture(x4_w, x6_h, x8_mips, boo::TextureFormat::I8, texels, texelCount); return true; }); } CTexture::CTexture(std::unique_ptr&& in, u32 length) { std::unique_ptr owned = std::move(in); athena::io::MemoryReader r(owned.get(), length); x0_fmt = ETexelFormat(r.readUint32Big()); x4_w = r.readUint16Big(); x6_h = r.readUint16Big(); x8_mips = r.readUint32Big(); switch (x0_fmt) { case ETexelFormat::I4: BuildI4FromGCN(r); break; case ETexelFormat::I8: BuildI8FromGCN(r); break; case ETexelFormat::IA4: BuildIA4FromGCN(r); break; case ETexelFormat::IA8: BuildIA8FromGCN(r); break; case ETexelFormat::C4: BuildC4FromGCN(r); break; case ETexelFormat::C8: BuildC8FromGCN(r); break; case ETexelFormat::C14X2: BuildC14X2FromGCN(r); break; case ETexelFormat::RGB565: BuildRGB565FromGCN(r); break; case ETexelFormat::RGB5A3: BuildRGB5A3FromGCN(r); break; case ETexelFormat::RGBA8: BuildRGBA8FromGCN(r); break; case ETexelFormat::CMPR: BuildDXT1FromGCN(r); break; case ETexelFormat::RGBA8PC: BuildRGBA8(owned.get() + 12, length - 12); break; case ETexelFormat::C8PC: BuildC8(owned.get() + 12, length - 12); break; default: Log.report(logvisor::Fatal, "invalid texture type %d for boo", int(x0_fmt)); } } void CTexture::Load(int slot, EClampMode clamp) const { } CFactoryFnReturn FTextureFactory(const urde::SObjectTag& tag, std::unique_ptr&& in, u32 len, const urde::CVParamTransfer& vparms, CObjectReference* selfRef) { return TToken::GetIObjObjectFor(std::make_unique(std::move(in), len)); } }