diff --git a/include/dolphin/gx/GXEnum.h b/include/dolphin/gx/GXEnum.h index d081f45..d428d90 100644 --- a/include/dolphin/gx/GXEnum.h +++ b/include/dolphin/gx/GXEnum.h @@ -169,7 +169,9 @@ typedef enum { #ifdef TARGET_PC GX_TF_R8_PC = 0x1 | _GX_TF_PC, + GX_TF_RG8_PC = 0x3 | _GX_TF_PC, GX_TF_RGBA8_PC = 0x6 | _GX_TF_PC, + GX_TF_BC1_PC = 0xE | _GX_TF_PC, #endif } GXTexFmt; diff --git a/lib/gfx/texture.cpp b/lib/gfx/texture.cpp index 79507f1..e788b89 100644 --- a/lib/gfx/texture.cpp +++ b/lib/gfx/texture.cpp @@ -31,6 +31,29 @@ wgpu::Extent3D physical_size(wgpu::Extent3D size, TextureFormatInfo info) { const uint32_t height = ((size.height + info.blockHeight - 1) / info.blockHeight) * info.blockHeight; return {.width = width, .height = height, .depthOrArrayLayers = size.depthOrArrayLayers}; } + +bool setup_swizzle(wgpu::TextureComponentSwizzleDescriptor& swizzle, u32 format) noexcept { + if (!webgpu::g_textureComponentSwizzleSupported) { + return false; + } + + switch (format) { + case GX_TF_R8_PC: + swizzle.swizzle.r = wgpu::ComponentSwizzle::R; + swizzle.swizzle.g = wgpu::ComponentSwizzle::R; + swizzle.swizzle.b = wgpu::ComponentSwizzle::R; + swizzle.swizzle.a = wgpu::ComponentSwizzle::R; + return true; + case GX_TF_RG8_PC: + swizzle.swizzle.r = wgpu::ComponentSwizzle::R; + swizzle.swizzle.g = wgpu::ComponentSwizzle::R; + swizzle.swizzle.b = wgpu::ComponentSwizzle::R; + swizzle.swizzle.a = wgpu::ComponentSwizzle::G; + return true; + default: + return false; + } +} } // namespace TextureFormatInfo format_info(wgpu::TextureFormat format) noexcept { @@ -158,6 +181,10 @@ TextureHandle new_dynamic_texture_2d(uint32_t width, uint32_t height, uint32_t m .dimension = wgpu::TextureViewDimension::e2D, .mipLevelCount = mips, }; + wgpu::TextureComponentSwizzleDescriptor swizzle; + if (setup_swizzle(swizzle, gxFormat)) { + textureViewDescriptor.nextInChain = &swizzle; + } auto textureView = texture.CreateView(&textureViewDescriptor); return std::make_shared(std::move(texture), std::move(textureView), wgpu::TextureView{}, size, wgpuFormat, mips, gxFormat); diff --git a/lib/gfx/texture_convert.cpp b/lib/gfx/texture_convert.cpp index 6386fab..6137065 100644 --- a/lib/gfx/texture_convert.cpp +++ b/lib/gfx/texture_convert.cpp @@ -5,6 +5,7 @@ #include #include +#include #include namespace aurora::gfx { @@ -192,11 +193,11 @@ static ByteBuffer DecodeTiled(uint32_t width, uint32_t height, uint32_t mips, Ar } template -static ByteBuffer DecodeLinear(uint32_t width, ArrayRef data) { - ByteBuffer buf{width * sizeof(typename T::Target)}; +static ByteBuffer DecodeLinear(uint32_t texelCount, ArrayRef data) { + ByteBuffer buf{texelCount * sizeof(typename T::Target)}; auto* target = reinterpret_cast(buf.data()); const auto* in = reinterpret_cast(data.data()); - for (uint32_t x = 0; x < width; ++x) { + for (uint32_t x = 0; x < texelCount; ++x) { T::decode_texel(target, in, x); } return buf; @@ -236,6 +237,25 @@ struct TextureDecoderI8 { } }; +struct TextureDecoderRG8 { + struct Source { + uint8_t intensity; + uint8_t alpha; + }; + using Target = RGBA8; + + static constexpr uint32_t Frac = 1; + static constexpr uint32_t BlockWidth = 1; + static constexpr uint32_t BlockHeight = 1; + + static void decode_texel(Target* target, const Source* in, const uint32_t x) { + target[x].r = in[x].intensity; + target[x].g = in[x].intensity; + target[x].b = in[x].intensity; + target[x].a = in[x].alpha; + } +}; + struct TextureDecoderIA4 { using Source = uint8_t; using Target = RGBA8; @@ -462,15 +482,106 @@ static ByteBuffer BuildRGBA8FromCMPR(uint32_t width, uint32_t height, uint32_t m return buf; } +static ByteBuffer BuildRGBA8FromBC1(uint32_t width, uint32_t height, uint32_t mips, ArrayRef data) { + const size_t texelCount = ComputeMippedTexelCount(width, height, mips); + ByteBuffer buf{sizeof(RGBA8) * texelCount}; + + uint32_t h = height; + uint32_t w = width; + uint8_t* dst = buf.data(); + const uint8_t* src = data.data(); + for (uint32_t mip = 0; mip < mips; ++mip) { + for (uint32_t yy = 0; yy < h; yy += 4) { + for (uint32_t xx = 0; xx < w; xx += 4) { + const uint16_t color1 = *reinterpret_cast(src); + const uint16_t color2 = *reinterpret_cast(src + 2); + const uint32_t indices = *reinterpret_cast(src + 4); + src += 8; + + std::array colorTable{}; + + colorTable[0] = ExpandTo8<5>(static_cast((color1 >> 11) & 0x1F)); + colorTable[1] = ExpandTo8<6>(static_cast((color1 >> 5) & 0x3F)); + colorTable[2] = ExpandTo8<5>(static_cast(color1 & 0x1F)); + colorTable[3] = 0xFF; + + colorTable[4] = ExpandTo8<5>(static_cast((color2 >> 11) & 0x1F)); + colorTable[5] = ExpandTo8<6>(static_cast((color2 >> 5) & 0x3F)); + colorTable[6] = ExpandTo8<5>(static_cast(color2 & 0x1F)); + colorTable[7] = 0xFF; + if (color1 > color2) { + colorTable[8] = S3TCBlend(colorTable[4], colorTable[0]); + colorTable[9] = S3TCBlend(colorTable[5], colorTable[1]); + colorTable[10] = S3TCBlend(colorTable[6], colorTable[2]); + colorTable[11] = 0xFF; + + colorTable[12] = S3TCBlend(colorTable[0], colorTable[4]); + colorTable[13] = S3TCBlend(colorTable[1], colorTable[5]); + colorTable[14] = S3TCBlend(colorTable[2], colorTable[6]); + colorTable[15] = 0xFF; + } else { + colorTable[8] = HalfBlend(colorTable[0], colorTable[4]); + colorTable[9] = HalfBlend(colorTable[1], colorTable[5]); + colorTable[10] = HalfBlend(colorTable[2], colorTable[6]); + colorTable[11] = 0xFF; + + colorTable[12] = 0; + colorTable[13] = 0; + colorTable[14] = 0; + colorTable[15] = 0; + } + + for (uint32_t y = 0; y < 4; ++y) { + for (uint32_t x = 0; x < 4; ++x) { + if (xx + x >= w || yy + y >= h) { + continue; + } + const uint32_t index = (indices >> (2 * (y * 4 + x))) & 3; + uint8_t* dstOffs = dst + ((yy + y) * w + (xx + x)) * 4; + const uint8_t* colorTableOffs = &colorTable[static_cast(index) * 4]; + memcpy(dstOffs, colorTableOffs, 4); + } + } + } + } + dst += w * h * 4; + if (w > 1) { + w /= 2; + } + if (h > 1) { + h /= 2; + } + } + + return buf; +} + ConvertedTexture convert_texture(u32 format, uint32_t width, uint32_t height, uint32_t mips, ArrayRef data) { ByteBuffer converted; switch (format) { DEFAULT_FATAL("convert_texture: unknown texture format {}", format); case GX_TF_R8_PC: - converted = DecodeLinear(width * height, data); - break; + if (!uses_direct_texture_upload(format)) { + converted = + DecodeLinear(static_cast(ComputeMippedTexelCount(width, height, mips)), data); + break; + } + return {.format = to_wgpu(format), .width = width, .height = height, .mips = mips}; + case GX_TF_RG8_PC: + if (!uses_direct_texture_upload(format)) { + converted = + DecodeLinear(static_cast(ComputeMippedTexelCount(width, height, mips)), data); + break; + } + return {.format = to_wgpu(format), .width = width, .height = height, .mips = mips}; case GX_TF_RGBA8_PC: - return {}; // No conversion + return {.format = to_wgpu(format), .width = width, .height = height, .mips = mips}; + case GX_TF_BC1_PC: + if (uses_direct_texture_upload(format)) { + return {.format = to_wgpu(format), .width = width, .height = height, .mips = mips}; + } + converted = BuildRGBA8FromBC1(width, height, mips, data); + break; case GX_TF_I4: converted = DecodeTiled(width, height, mips, data); break; @@ -506,7 +617,7 @@ ConvertedTexture convert_texture(u32 format, uint32_t width, uint32_t height, ui } const auto wgpuFormat = to_wgpu(format); bool hasArbitraryMips = false; - if (wgpuFormat == wgpu::TextureFormat::RGBA8Unorm && mips > 1) { + if (!is_pc_texture_format(format) && wgpuFormat == wgpu::TextureFormat::RGBA8Unorm && mips > 1) { hasArbitraryMips = arb_mip_check(width, height, mips, converted); } return { diff --git a/lib/gfx/texture_convert.hpp b/lib/gfx/texture_convert.hpp index aff4da8..b363b4a 100644 --- a/lib/gfx/texture_convert.hpp +++ b/lib/gfx/texture_convert.hpp @@ -5,12 +5,36 @@ #include "../webgpu/gpu.hpp" namespace aurora::gfx { -static constexpr wgpu::TextureFormat to_wgpu(u32 gxFormat) { +inline bool is_pc_texture_format(u32 gxFormat) noexcept { + return (gxFormat & _GX_TF_PC) != 0; +} + +inline bool uses_direct_texture_upload(u32 gxFormat) noexcept { switch (gxFormat) { + case GX_TF_R8_PC: + case GX_TF_RG8_PC: + return webgpu::g_textureComponentSwizzleSupported; + case GX_TF_RGBA8_PC: + return true; + case GX_TF_BC1_PC: + return webgpu::g_bcTexturesSupported; + default: + return false; + } +} + +inline wgpu::TextureFormat to_wgpu(u32 gxFormat) noexcept { + switch (gxFormat) { + case GX_TF_R8_PC: + return uses_direct_texture_upload(gxFormat) ? wgpu::TextureFormat::R8Unorm : wgpu::TextureFormat::RGBA8Unorm; + case GX_TF_RG8_PC: + return uses_direct_texture_upload(gxFormat) ? wgpu::TextureFormat::RG8Unorm : wgpu::TextureFormat::RGBA8Unorm; case GX_TF_C4: case GX_TF_C8: case GX_TF_C14X2: return wgpu::TextureFormat::R16Sint; + case GX_TF_BC1_PC: + return uses_direct_texture_upload(gxFormat) ? wgpu::TextureFormat::BC1RGBAUnorm : wgpu::TextureFormat::RGBA8Unorm; default: return wgpu::TextureFormat::RGBA8Unorm; } @@ -25,6 +49,9 @@ struct ConvertedTexture { bool hasArbitraryMips = false; }; +// Returns converted bytes when Aurora must transform the source layout before upload. +// Empty data means callers should upload the original bytes directly using to_wgpu(format). +// hasArbitraryMips is only meaningful for decoded RGBA8 GC formats; PC formats skip that check. ConvertedTexture convert_texture(u32 format, uint32_t width, uint32_t height, uint32_t mips, ArrayRef data); ConvertedTexture convert_texture_palette(u32 textureFormat, uint32_t width, uint32_t height, uint32_t mips, ArrayRef textureData, GXTlutFmt tlutFormat, uint16_t tlutEntries, diff --git a/lib/gfx/texture_replacement.cpp b/lib/gfx/texture_replacement.cpp index 3ec80bd..ae151aa 100644 --- a/lib/gfx/texture_replacement.cpp +++ b/lib/gfx/texture_replacement.cpp @@ -197,8 +197,12 @@ uint32_t texture_base_level_size(const GXTexObj_& obj) noexcept { switch (obj.format()) { case GX_TF_R8_PC: return obj.width() * obj.height(); + case GX_TF_RG8_PC: + return obj.width() * obj.height() * 2; case GX_TF_RGBA8_PC: return obj.width() * obj.height() * 4; + case GX_TF_BC1_PC: + return ((obj.width() + 3) / 4) * ((obj.height() + 3) / 4) * 8; default: return GXGetTexBufferSize(obj.width(), obj.height(), obj.format(), false, 0); } diff --git a/lib/gx/gx_fmt.hpp b/lib/gx/gx_fmt.hpp index ca1fb33..ac60a07 100644 --- a/lib/gx/gx_fmt.hpp +++ b/lib/gx/gx_fmt.hpp @@ -931,8 +931,12 @@ inline std::string format_as(const GXTexFmt& fmt) { return "GX_CTF_Z16L"; case GX_TF_R8_PC: return "GX_TF_R8_PC"; + case GX_TF_RG8_PC: + return "GX_TF_RG8_PC"; case GX_TF_RGBA8_PC: return "GX_TF_RGBA8_PC"; + case GX_TF_BC1_PC: + return "GX_TF_BC1_PC"; default: return fmt::format("GXTexFmt({})", underlying(fmt)); } diff --git a/lib/webgpu/gpu.cpp b/lib/webgpu/gpu.cpp index d6c6404..9b75f06 100644 --- a/lib/webgpu/gpu.cpp +++ b/lib/webgpu/gpu.cpp @@ -56,6 +56,7 @@ wgpu::Instance g_instance; static wgpu::AdapterInfo g_adapterInfo; static wgpu::SurfaceCapabilities g_surfaceCapabilities; bool g_bcTexturesSupported; +bool g_textureComponentSwizzleSupported; namespace { @@ -802,12 +803,19 @@ bool initialize(AuroraBackend auroraBackend, bool allowCpu) { requiredLimits.maxDynamicStorageBuffersPerPipelineLayout, requiredLimits.maxStorageBuffersPerShaderStage, requiredLimits.minUniformBufferOffsetAlignment, requiredLimits.minStorageBufferOffsetAlignment); std::vector requiredFeatures; + g_bcTexturesSupported = false; + g_textureComponentSwizzleSupported = false; wgpu::SupportedFeatures supportedFeatures; g_adapter.GetFeatures(&supportedFeatures); for (size_t i = 0; i < supportedFeatures.featureCount; ++i) { const auto feature = supportedFeatures.features[i]; - if (feature == wgpu::FeatureName::TextureCompressionBC) { - g_bcTexturesSupported = true; + if (feature == wgpu::FeatureName::TextureCompressionBC || + feature == wgpu::FeatureName::TextureComponentSwizzle) { + if (feature == wgpu::FeatureName::TextureCompressionBC) { + g_bcTexturesSupported = true; + } else if (feature == wgpu::FeatureName::TextureComponentSwizzle) { + g_textureComponentSwizzleSupported = true; + } requiredFeatures.push_back(feature); } } diff --git a/lib/webgpu/gpu.hpp b/lib/webgpu/gpu.hpp index ae16f65..3176f32 100644 --- a/lib/webgpu/gpu.hpp +++ b/lib/webgpu/gpu.hpp @@ -51,6 +51,7 @@ extern wgpu::RenderPipeline g_CopyPipeline; extern wgpu::BindGroup g_CopyBindGroup; extern wgpu::Instance g_instance; extern bool g_bcTexturesSupported; +extern bool g_textureComponentSwizzleSupported; bool initialize(AuroraBackend backend, bool allowCpu); void shutdown(); diff --git a/tests/gx_fifo_test.cpp b/tests/gx_fifo_test.cpp index d661649..c437780 100644 --- a/tests/gx_fifo_test.cpp +++ b/tests/gx_fifo_test.cpp @@ -1304,6 +1304,24 @@ TEST_F(GXFifoTest, LoadTexObjPcFormat_PreservesFullFormatMetadata) { EXPECT_EQ(slot.raw_format(), static_cast(GX_TF_RGBA8)); } +TEST_F(GXFifoTest, TexImage0BpWrite_ClearsExtendedTextureMetadata) { + auto& slot = gxState().loadedTextures[GX_TEXMAP0]; + slot.mWidth = 1024; + slot.mHeight = 1024; + slot.mFormat = GX_TF_BC1_PC; + + const u32 image0 = (0x88u << 24) | (7u << 0) | (15u << 10) | (static_cast(GX_TF_RGBA8) << 20); + aurora::gx::fifo::write_u8(0x61); + aurora::gx::fifo::write_u32(image0); + auto bytes = capture_fifo(); + + decode_fifo(bytes); + + EXPECT_EQ(slot.width(), 8u); + EXPECT_EQ(slot.height(), 16u); + EXPECT_EQ(slot.format(), GX_TF_RGBA8); +} + TEST_F(GXFifoTest, TexObjRawDimensions_WrapAtTenBitBoundary) { auto& slot = gxState().loadedTextures[GX_TEXMAP0]; slot.image0 = (0x3FFu << 0) | (0x3FFu << 10);