[librptexture] Add EAC R11 and RG11 decoding.

These are reduced to 8-bit for ARGB32.

ImageDecoder_ETC1.cpp: Renamed decodeBlock_ETC2_alpha() to
T_decodeBlock_EAC() and templatized the byteOffset parameter to allow
it to be used to decode alpha (regular), or R or G (EAC).
This commit is contained in:
David Korth 2021-11-27 10:37:46 -05:00
parent 1f6d742d59
commit 3da780cf57
9 changed files with 267 additions and 14 deletions

View File

@ -21,6 +21,12 @@
the LDR decoder is rather slow.)
* The GBS parser now partially supports the older GBR format.
* New compressed texture formats:
* EAC R11 and RG11, which uses ETC2's alpha channel compression for
1-channel and 2-channel textures. Note that the channels are truncated
from 11-bit to 8-bit for display purposes, and signed int versions
might not be displayed correctly.
* Bug fixes:
* EXE: Improve runtime DLL detection in some cases.
* SNES: Fix detection of games that declare usage of the S-RTC chip

View File

@ -169,6 +169,7 @@ button.
* GameCube: Tiled RGB5A3 and CI8 with RGB5A3 palette
* S3TC: DXT1, DXT2, DXT3, DXT4, DXT5, BC4, and BC5 codecs.
* GameCube 2x2-tiled DXT1 is supported in GVR texture files.
* ETCn: ETC1, ETC2 (RGB, RGBA, RGB_A1), EAC
* BC7: Supported in multiple texture file formats.
* The implementation is somewhat slow. (Contributions welcome.)
* PVRTC: Supported in multiple texture file formats.

View File

@ -32,6 +32,19 @@ union argb32_t {
};
ASSERT_STRUCT(argb32_t, 4);
// Byte offsets for certain functions.
#if SYS_BYTEORDER == SYS_LIL_ENDIAN
# define ARGB32_BYTE_OFFSET_B 0
# define ARGB32_BYTE_OFFSET_G 1
# define ARGB32_BYTE_OFFSET_R 2
# define ARGB32_BYTE_OFFSET_A 3
#else /* SYS_BYTEORDER == SYS_BIG_ENDIAN */
# define ARGB32_BYTE_OFFSET_A 0
# define ARGB32_BYTE_OFFSET_R 1
# define ARGB32_BYTE_OFFSET_G 2
# define ARGB32_BYTE_OFFSET_B 3
#endif
}
#endif /* __ROMPROPERTIES_LIBRPTEXTURE_ARGB32_T_HPP__ */

View File

@ -2,7 +2,7 @@
* ROM Properties Page shell extension. (librptexture) *
* ImageDecoder.cpp: Image decoding functions. *
* *
* Copyright (c) 2016-2020 by David Korth. *
* Copyright (c) 2016-2021 by David Korth. *
* SPDX-License-Identifier: GPL-2.0-or-later *
***************************************************************************/
@ -772,7 +772,7 @@ static inline int calcDreamcastSmallVQPaletteEntries_WithMipmaps(int width)
}
}
/* ETC1 */
/* ETCn */
/**
* Convert an ETC1 image to rp_image.
@ -822,6 +822,30 @@ ATTR_ACCESS_SIZE(read_only, 3, 4)
rp_image *fromETC2_RGB_A1(int width, int height,
const uint8_t *RESTRICT img_buf, int img_siz);
/**
* Convert an EAC R11 image to rp_image.
* @param width Image width.
* @param height Image height.
* @param img_buf EAC R11 image buffer.
* @param img_siz Size of image data. [must be >= (w*h)/2]
* @return rp_image, or nullptr on error.
*/
ATTR_ACCESS_SIZE(read_only, 3, 4)
rp_image *fromEAC_R11(int width, int height,
const uint8_t *RESTRICT img_buf, int img_siz);
/**
* Convert an EAC RG11 image to rp_image.
* @param width Image width.
* @param height Image height.
* @param img_buf EAC R11 image buffer.
* @param img_siz Size of image data. [must be >= (w*h)]
* @return rp_image, or nullptr on error.
*/
ATTR_ACCESS_SIZE(read_only, 3, 4)
rp_image *fromEAC_RG11(int width, int height,
const uint8_t *RESTRICT img_buf, int img_siz);
#ifdef ENABLE_PVRTC
/* PVRTC */

View File

@ -737,14 +737,16 @@ rp_image *fromETC2_RGB(int width, int height,
}
/**
* Decode an ETC2 alpha block.
* Decode an EAC (ETC2) alpha block.
* @tparam byteOffset [in] Byte offset. (Usually ARGB32_BYTE_OFFSET_A for alpha.)
* @param tileBuf [out] Destination tile buffer.
* @param src [in] Source alpha block.
*/
static void decodeBlock_ETC2_alpha(array<uint32_t, 4*4> &tileBuf, const etc2_alpha *alpha)
template<unsigned int byteOffset>
static void T_decodeBlock_EAC(array<uint32_t, 4*4> &tileBuf, const etc2_alpha *alpha)
{
// argb32_t for alpha channel handling.
argb32_t *const pArgb = reinterpret_cast<argb32_t*>(tileBuf.data());
// uint8_t* for byte offset handling.
uint8_t *const pU8 = reinterpret_cast<uint8_t*>(tileBuf.data());
// Get the base codeword and multiplier.
// NOTE: mult == 0 is not allowed to be used by the encoder,
@ -760,11 +762,13 @@ static void decodeBlock_ETC2_alpha(array<uint32_t, 4*4> &tileBuf, const etc2_alp
// Pixel index.
uint64_t alpha48 = extract48(alpha);
// NOTE: alpha is stored *backwards*.
// NOTE: EAC codeword bits are stored *backwards*.
// TODO: Optimize to eliminate double-shifting.
// TODO: For R11/RG11 EAC, this should result in an 11-bit value, not 8-bit.
for (unsigned int i = 0; i < 16; i++, alpha48 <<= 3) {
// Calculate the alpha value for this pixel.
int A = base + (tbl[(alpha48 >> 45) & 0x07] * mult);
// NOTE: For EAC, this is an 11-bit value that must be truncated to 8-bit.
if (A > 255) {
A = 255;
} else if (A < 0) {
@ -772,7 +776,7 @@ static void decodeBlock_ETC2_alpha(array<uint32_t, 4*4> &tileBuf, const etc2_alp
}
// Set the new alpha value.
pArgb[etc1_mapping[i]].a = static_cast<uint8_t>(A);
pU8[(etc1_mapping[i] * sizeof(uint32_t)) + byteOffset] = static_cast<uint8_t>(A);
}
}
@ -828,7 +832,7 @@ rp_image *fromETC2_RGBA(int width, int height,
// Decode the ETC2 alpha block.
// TODO: Don't fill in the alpha channel in decodeBlock_ETC2_RGB()?
decodeBlock_ETC2_alpha(tileBuf, &etc2_src->alpha);
T_decodeBlock_EAC<ARGB32_BYTE_OFFSET_A>(tileBuf, &etc2_src->alpha);
// Blit the tile to the main image buffer.
ImageDecoderPrivate::BlitTile<uint32_t, 4, 4>(img, tileBuf, x, y);
@ -914,4 +918,148 @@ rp_image *fromETC2_RGB_A1(int width, int height,
return img;
}
/**
* Convert an EAC R11 image to rp_image.
* @param width Image width.
* @param height Image height.
* @param img_buf EAC R11 image buffer.
* @param img_siz Size of image data. [must be >= (w*h)/2]
* @return rp_image, or nullptr on error.
*/
rp_image *fromEAC_R11(int width, int height,
const uint8_t *RESTRICT img_buf, int img_siz)
{
// Verify parameters.
assert(img_buf != nullptr);
assert(width > 0);
assert(height > 0);
assert(img_siz >= ((width * height) / 2));
if (!img_buf || width <= 0 || height <= 0 ||
img_siz < ((width * height) / 2))
{
return nullptr;
}
// ETC2 uses 4x4 tiles, but some container formats allow
// the last tile to be cut off, so round up for the
// physical tile size.
const int physWidth = ALIGN_BYTES(4, width);
const int physHeight = ALIGN_BYTES(4, height);
// Create an rp_image.
rp_image *const img = new rp_image(physWidth, physHeight, rp_image::Format::ARGB32);
if (!img->isValid()) {
// Could not allocate the image.
img->unref();
return nullptr;
}
const etc2_alpha *eac_block = reinterpret_cast<const etc2_alpha*>(img_buf);
// Calculate the total number of tiles.
const unsigned int tilesX = static_cast<unsigned int>(physWidth / 4);
const unsigned int tilesY = static_cast<unsigned int>(physHeight / 4);
// Temporary tile buffer.
// NOTE: Must be initialized to 0xFF000000U, since
// T_decodeBlock_EAC<>() only modifies a single channel.
array<uint32_t, 4*4> tileBuf;
tileBuf.fill(0xFF000000U);
for (unsigned int y = 0; y < tilesY; y++) {
for (unsigned int x = 0; x < tilesX; x++, eac_block++) {
// Decode the EAC R11 block.
T_decodeBlock_EAC<ARGB32_BYTE_OFFSET_R>(tileBuf, eac_block);
// Blit the tile to the main image buffer.
ImageDecoderPrivate::BlitTile<uint32_t, 4, 4>(img, tileBuf, x, y);
} }
if (width < physWidth || height < physHeight) {
// Shrink the image.
img->shrink(width, height);
}
// Set the sBIT metadata.
// NOTE: Cannot set the G and B channels to 0, so setting them to 1.
static const rp_image::sBIT_t sBIT = {8,1,1,0,0};
img->set_sBIT(&sBIT);
// Image has been converted.
return img;
}
/**
* Convert an EAC RG11 image to rp_image.
* @param width Image width.
* @param height Image height.
* @param img_buf EAC R11 image buffer.
* @param img_siz Size of image data. [must be >= (w*h)]
* @return rp_image, or nullptr on error.
*/
rp_image *fromEAC_RG11(int width, int height,
const uint8_t *RESTRICT img_buf, int img_siz)
{
// Verify parameters.
assert(img_buf != nullptr);
assert(width > 0);
assert(height > 0);
assert(img_siz >= (width * height));
if (!img_buf || width <= 0 || height <= 0 ||
img_siz < (width * height))
{
return nullptr;
}
// ETC2 uses 4x4 tiles, but some container formats allow
// the last tile to be cut off, so round up for the
// physical tile size.
const int physWidth = ALIGN_BYTES(4, width);
const int physHeight = ALIGN_BYTES(4, height);
// Create an rp_image.
rp_image *const img = new rp_image(physWidth, physHeight, rp_image::Format::ARGB32);
if (!img->isValid()) {
// Could not allocate the image.
img->unref();
return nullptr;
}
const etc2_alpha *eac_block = reinterpret_cast<const etc2_alpha*>(img_buf);
// Calculate the total number of tiles.
const unsigned int tilesX = static_cast<unsigned int>(physWidth / 4);
const unsigned int tilesY = static_cast<unsigned int>(physHeight / 4);
// Temporary tile buffer.
// NOTE: Must be initialized to 0xFF000000U, since
// T_decodeBlock_EAC<>() only modifies a single channel.
array<uint32_t, 4*4> tileBuf;
tileBuf.fill(0xFF000000U);
for (unsigned int y = 0; y < tilesY; y++) {
for (unsigned int x = 0; x < tilesX; x++, eac_block += 2) {
// Decode the EAC R11 block.
T_decodeBlock_EAC<ARGB32_BYTE_OFFSET_R>(tileBuf, &eac_block[0]);
// Decode the EAC G11 block.
T_decodeBlock_EAC<ARGB32_BYTE_OFFSET_G>(tileBuf, &eac_block[1]);
// Blit the tile to the main image buffer.
ImageDecoderPrivate::BlitTile<uint32_t, 4, 4>(img, tileBuf, x, y);
} }
if (width < physWidth || height < physHeight) {
// Shrink the image.
img->shrink(width, height);
}
// Set the sBIT metadata.
// NOTE: Cannot set the B channel to 0, so setting it to 1 instead.
static const rp_image::sBIT_t sBIT = {8,8,1,0,0};
img->set_sBIT(&sBIT);
// Image has been converted.
return img;
}
} }

View File

@ -196,12 +196,12 @@ const ImageSizeCalc::OpCode GodotSTEXPrivate::op_tbl[] = {
OpCode::Divide2, // STEX_FORMAT_PVRTC1_4
OpCode::Divide2, // STEX_FORMAT_PVRTC1_4A
OpCode::Divide2, // STEX_FORMAT_ETC
OpCode::Divide2, // STEX_FORMAT_ETC2_R11 // TODO: Verify; Align4?
OpCode::Divide2, // STEX_FORMAT_ETC2_R11S // TODO: Verify; Align4?
OpCode::Divide2, // STEX_FORMAT_ETC2_R11
OpCode::Divide2, // STEX_FORMAT_ETC2_R11S
// 0x20
OpCode::Divide2, // STEX_FORMAT_ETC2_RG11 // TODO: Verify; Align4?
OpCode::Divide2, // STEX_FORMAT_ETC2_RG11S // TODO: Verify; Align4?
OpCode::None, // STEX_FORMAT_ETC2_RG11
OpCode::None, // STEX_FORMAT_ETC2_RG11S
OpCode::Align4Divide2, // STEX_FORMAT_ETC2_RGB8 // TODO: Verify?
OpCode::Align4, // STEX_FORMAT_ETC2_RGBA8 // TODO: Verify?
OpCode::Align4Divide2, // STEX_FORMAT_ETC2_RGB8A1 // TODO: Verify?
@ -667,6 +667,23 @@ const rp_image *GodotSTEXPrivate::loadImage(int mip)
}
break;
case STEX_FORMAT_ETC2_R11:
case STEX_FORMAT_ETC2_R11S:
// EAC-compressed R11 texture.
// TODO: Does the signed version get decoded differently?
img = ImageDecoder::fromEAC_R11(
mdata.width, mdata.height,
buf.get(), mdata.size);
break;
case STEX_FORMAT_ETC2_RG11:
case STEX_FORMAT_ETC2_RG11S:
// EAC-compressed RG11 texture.
// TODO: Does the signed version get decoded differently?
img = ImageDecoder::fromEAC_RG11(
mdata.width, mdata.height,
buf.get(), mdata.size);
break;
#ifdef ENABLE_ASTC
case STEX_FORMAT_SCU_ASTC_8x8:
// NOTE: Only valid for Godot 3.

View File

@ -536,6 +536,24 @@ const rp_image *KhronosKTXPrivate::loadImage(void)
buf.get(), expected_size);
break;
case GL_COMPRESSED_R11_EAC:
case GL_COMPRESSED_SIGNED_R11_EAC:
// EAC-compressed R11 texture.
// TODO: Does the signed version get decoded differently?
img = ImageDecoder::fromEAC_R11(
ktxHeader.pixelWidth, height,
buf.get(), expected_size);
break;
case GL_COMPRESSED_RG11_EAC:
case GL_COMPRESSED_SIGNED_RG11_EAC:
// EAC-compressed RG11 texture.
// TODO: Does the signed version get decoded differently?
img = ImageDecoder::fromEAC_RG11(
ktxHeader.pixelWidth, height,
buf.get(), expected_size);
break;
case GL_COMPRESSED_RED_RGTC1:
case GL_COMPRESSED_SIGNED_RED_RGTC1:
// RGTC, one component. (BC4)

View File

@ -547,6 +547,22 @@ const rp_image *KhronosKTX2Private::loadImage(int mip)
buf.get(), expected_size);
break;
case VK_FORMAT_EAC_R11_UNORM_BLOCK:
case VK_FORMAT_EAC_R11_SNORM_BLOCK:
// EAC-compressed R11 texture.
// TODO: Does the signed version get decoded differently?
img = ImageDecoder::fromEAC_R11(
width, height,
buf.get(), expected_size);
case VK_FORMAT_EAC_R11G11_UNORM_BLOCK:
case VK_FORMAT_EAC_R11G11_SNORM_BLOCK:
// EAC-compressed RG11 texture.
// TODO: Does the signed version get decoded differently?
img = ImageDecoder::fromEAC_RG11(
width, height,
buf.get(), expected_size);
case VK_FORMAT_BC7_UNORM_BLOCK:
case VK_FORMAT_BC7_SRGB_BLOCK:
// BPTC-compressed RGBA texture. (BC7)

View File

@ -163,7 +163,7 @@ const struct PowerVR3Private::FmtLkup_t PowerVR3Private::fmtLkup_tbl_U8[] = {
// TODO: "Weird" formats.
{ 'abgr', 0x10101010, ImageDecoder::PixelFormat::A16B16G16R16, 64},
{ '\0bgr', 0x00101010, ImageDecoder::PixelFormat::B16G16R16, 48},
{ '\0rgb', 0x000B0B0A, ImageDecoder::PixelFormat::R11G11B10, 32},
{ '\0rgb', 0x000B0B0A, ImageDecoder::PixelFormat::R11G11B10, 32}, // NOTE: May be float.
#endif
{0, 0, ImageDecoder::PixelFormat::Unknown, 0}
@ -623,6 +623,16 @@ const rp_image *PowerVR3Private::loadImage(int mip)
img = ImageDecoder::fromETC2_RGBA(width, height, buf.get(), expected_size);
break;
case PVR3_PXF_EAC_R11:
// EAC-compressed R11 texture.
img = ImageDecoder::fromEAC_R11(width, height, buf.get(), expected_size);
break;
case PVR3_PXF_EAC_RG11:
// EAC-compressed RG11 texture.
img = ImageDecoder::fromEAC_RG11(width, height, buf.get(), expected_size);
break;
case PVR3_PXF_DXT1:
// DXT1-compressed texture.
img = ImageDecoder::fromDXT1(width, height, buf.get(), expected_size);