Add RG8 and BC1 PC texture formats

This commit is contained in:
Luke Street
2026-05-30 09:57:21 -06:00
parent c214a67adf
commit 558ba0a987
9 changed files with 212 additions and 10 deletions
+27
View File
@@ -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<TextureRef>(std::move(texture), std::move(textureView), wgpu::TextureView{}, size, wgpuFormat,
mips, gxFormat);
+118 -7
View File
@@ -5,6 +5,7 @@
#include <algorithm>
#include <array>
#include <cstring>
#include <cmath>
namespace aurora::gfx {
@@ -192,11 +193,11 @@ static ByteBuffer DecodeTiled(uint32_t width, uint32_t height, uint32_t mips, Ar
}
template <TextureDecoder T>
static ByteBuffer DecodeLinear(uint32_t width, ArrayRef<uint8_t> data) {
ByteBuffer buf{width * sizeof(typename T::Target)};
static ByteBuffer DecodeLinear(uint32_t texelCount, ArrayRef<uint8_t> data) {
ByteBuffer buf{texelCount * sizeof(typename T::Target)};
auto* target = reinterpret_cast<typename T::Target*>(buf.data());
const auto* in = reinterpret_cast<const typename T::Source*>(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<uint8_t> 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<const uint16_t*>(src);
const uint16_t color2 = *reinterpret_cast<const uint16_t*>(src + 2);
const uint32_t indices = *reinterpret_cast<const uint32_t*>(src + 4);
src += 8;
std::array<uint8_t, 16> colorTable{};
colorTable[0] = ExpandTo8<5>(static_cast<uint8_t>((color1 >> 11) & 0x1F));
colorTable[1] = ExpandTo8<6>(static_cast<uint8_t>((color1 >> 5) & 0x3F));
colorTable[2] = ExpandTo8<5>(static_cast<uint8_t>(color1 & 0x1F));
colorTable[3] = 0xFF;
colorTable[4] = ExpandTo8<5>(static_cast<uint8_t>((color2 >> 11) & 0x1F));
colorTable[5] = ExpandTo8<6>(static_cast<uint8_t>((color2 >> 5) & 0x3F));
colorTable[6] = ExpandTo8<5>(static_cast<uint8_t>(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<size_t>(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<uint8_t> data) {
ByteBuffer converted;
switch (format) {
DEFAULT_FATAL("convert_texture: unknown texture format {}", format);
case GX_TF_R8_PC:
converted = DecodeLinear<TextureDecoderI8>(width * height, data);
break;
if (!uses_direct_texture_upload(format)) {
converted =
DecodeLinear<TextureDecoderI8>(static_cast<uint32_t>(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<TextureDecoderRG8>(static_cast<uint32_t>(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<TextureDecoderI4>(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 {
+28 -1
View File
@@ -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<uint8_t> data);
ConvertedTexture convert_texture_palette(u32 textureFormat, uint32_t width, uint32_t height, uint32_t mips,
ArrayRef<uint8_t> textureData, GXTlutFmt tlutFormat, uint16_t tlutEntries,
+4
View File
@@ -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);
}
+4
View File
@@ -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));
}
+10 -2
View File
@@ -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<wgpu::FeatureName> 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);
}
}
+1
View File
@@ -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();