[PowerVR] PVRTDecompress.cpp: Initial support for PVRTC-II decoding.

Main changes:
- Color A and Color B now share an opaque flag.
- Color B: The low bit of alpha is always 1, not 0.
- Tiles are stored in linear order, not Morton order.

To make it easier to support both formats with minimal changes, the
original PVRTC code has been changed to use templated functions.
Simple non-templated wrapper functions are provided for external use.

PowerVR3, KhronosKTX: Handle PVRTC-II. Note that there's no FourCC
defined for DDS, so we're not updating DirectDrawSurface.

TODO:
- The Hard transition flag isn't supported. PVRTexToolCLI didn't use the
  hard transition flag when creating the test images.

[libromdata/tests] ImageDecoderTest: Added some PVRTC-II test images.
Based on tctest's example.png. Note that the PVRTC-II test images are
the full 512x512, whereas the PVRTC-I test image was 256x256.
This commit is contained in:
David Korth 2019-12-15 02:43:22 -05:00
parent 9ed6765b8a
commit 42bf06e82c
11 changed files with 211 additions and 43 deletions

View File

@ -42,12 +42,14 @@ struct PVRTCWordIndices
int P[2], Q[2], R[2], S[2];
};
template<bool PVRTCII>
static Pixel32 getColorA(uint32_t colorData)
{
Pixel32 color;
// Opaque Color Mode - RGB 554
if ((colorData & 0x8000) != 0)
const uint32_t opaque_flag = (PVRTCII ? 0x80000000 : 0x8000);
if ((colorData & opaque_flag) != 0)
{
color.red = static_cast<uint8_t>((colorData & 0x7c00) >> 10); // 5->5 bits
color.green = static_cast<uint8_t>((colorData & 0x3e0) >> 5); // 5->5 bits
@ -66,6 +68,7 @@ static Pixel32 getColorA(uint32_t colorData)
return color;
}
template<bool PVRTCII>
static Pixel32 getColorB(uint32_t colorData)
{
Pixel32 color;
@ -85,6 +88,10 @@ static Pixel32 getColorB(uint32_t colorData)
color.green = static_cast<uint8_t>(((colorData & 0xf00000) >> 19) | ((colorData & 0xf00000) >> 23)); // 4->5 bits
color.blue = static_cast<uint8_t>(((colorData & 0xf0000) >> 15) | ((colorData & 0xf0000) >> 19)); // 4->5 bits
color.alpha = static_cast<uint8_t>((colorData & 0x70000000) >> 27); // 3->4 bits - note 0 at right
if (PVRTCII) {
// PVRTC-II sets the low alpha bit of Color B to 1, not 0.
color.alpha |= 1;
}
}
return color;
@ -353,6 +360,7 @@ static int32_t getModulationValues(int32_t modulationValues[16][8], int32_t modu
return 0;
}
template<bool PVRTCII>
static void pvrtcGetDecompressedPixels(const PVRTCWord& P, const PVRTCWord& Q, const PVRTCWord& R, const PVRTCWord& S, Pixel32* pColorData, uint8_t bpp)
{
// 4bpp only needs 8*8 values, but 2bpp needs 16*8, so rather than wasting processor time we just statically allocate 16*8.
@ -374,8 +382,10 @@ static void pvrtcGetDecompressedPixels(const PVRTCWord& P, const PVRTCWord& Q, c
unpackModulations(S, wordWidth, wordHeight, modulationValues, modulationModes, bpp);
// Bilinear upscale image data from 2x2 -> 4x4
interpolateColors(getColorA(P.colorData), getColorA(Q.colorData), getColorA(R.colorData), getColorA(S.colorData), upscaledColorA, bpp);
interpolateColors(getColorB(P.colorData), getColorB(Q.colorData), getColorB(R.colorData), getColorB(S.colorData), upscaledColorB, bpp);
interpolateColors(getColorA<PVRTCII>(P.colorData), getColorA<PVRTCII>(Q.colorData),
getColorA<PVRTCII>(R.colorData), getColorA<PVRTCII>(S.colorData), upscaledColorA, bpp);
interpolateColors(getColorB<PVRTCII>(P.colorData), getColorB<PVRTCII>(Q.colorData),
getColorB<PVRTCII>(R.colorData), getColorB<PVRTCII>(S.colorData), upscaledColorB, bpp);
for (uint32_t y = 0; y < wordHeight; y++)
{
@ -430,46 +440,52 @@ static bool isPowerOf2(uint32_t input)
return ((input | minus1) == (input ^ minus1));
}
template<bool PVRTCII>
static uint32_t TwiddleUV(uint32_t XSize, uint32_t YSize, uint32_t XPos, uint32_t YPos)
{
// Initially assume X is the larger size.
uint32_t MinDimension = XSize;
uint32_t MaxValue = YPos;
uint32_t Twiddled = 0;
uint32_t SrcBitPos = 1;
uint32_t DstBitPos = 1;
int ShiftCount = 0;
// Check the sizes are valid.
assert(YPos < YSize);
assert(XPos < XSize);
assert(isPowerOf2(YSize));
assert(isPowerOf2(XSize));
// If Y is the larger dimension - switch the min/max values.
if (YSize < XSize)
{
MinDimension = YSize;
MaxValue = XPos;
if (PVRTCII) {
// PVRTC-II uses linear order, not Morton order.
return (YPos * XSize) + XPos;
} else {
// Initially assume X is the larger size.
uint32_t MinDimension = XSize;
uint32_t MaxValue = YPos;
uint32_t Twiddled = 0;
uint32_t SrcBitPos = 1;
uint32_t DstBitPos = 1;
int ShiftCount = 0;
// If Y is the larger dimension - switch the min/max values.
if (YSize < XSize)
{
MinDimension = YSize;
MaxValue = XPos;
}
// Step through all the bits in the "minimum" dimension
while (SrcBitPos < MinDimension)
{
if (YPos & SrcBitPos) { Twiddled |= DstBitPos; }
if (XPos & SrcBitPos) { Twiddled |= (DstBitPos << 1); }
SrcBitPos <<= 1;
DstBitPos <<= 2;
ShiftCount += 1;
}
// Prepend any unused bits
MaxValue >>= ShiftCount;
Twiddled |= (MaxValue << (2 * ShiftCount));
return Twiddled;
}
// Step through all the bits in the "minimum" dimension
while (SrcBitPos < MinDimension)
{
if (YPos & SrcBitPos) { Twiddled |= DstBitPos; }
if (XPos & SrcBitPos) { Twiddled |= (DstBitPos << 1); }
SrcBitPos <<= 1;
DstBitPos <<= 2;
ShiftCount += 1;
}
// Prepend any unused bits
MaxValue >>= ShiftCount;
Twiddled |= (MaxValue << (2 * ShiftCount));
return Twiddled;
}
static void mapDecompressedData(Pixel32* pOutput, uint32_t width, const Pixel32* pWord, const PVRTCWordIndices& words, uint8_t bpp)
@ -492,6 +508,7 @@ static void mapDecompressedData(Pixel32* pOutput, uint32_t width, const Pixel32*
}
}
}
template<bool PVRTCII>
static uint32_t pvrtcDecompress(uint8_t* pCompressedData, Pixel32* pDecompressedData, uint32_t width, uint32_t height, uint8_t bpp)
{
uint32_t wordWidth = 4;
@ -526,10 +543,10 @@ static uint32_t pvrtcDecompress(uint8_t* pCompressedData, Pixel32* pDecompressed
// Work out the offsets into the twiddle structs, multiply by two as there are two members per word.
uint32_t WordOffsets[4] = {
TwiddleUV(i32NumXWords, i32NumYWords, indices.P[0], indices.P[1]) * 2,
TwiddleUV(i32NumXWords, i32NumYWords, indices.Q[0], indices.Q[1]) * 2,
TwiddleUV(i32NumXWords, i32NumYWords, indices.R[0], indices.R[1]) * 2,
TwiddleUV(i32NumXWords, i32NumYWords, indices.S[0], indices.S[1]) * 2,
TwiddleUV<PVRTCII>(i32NumXWords, i32NumYWords, indices.P[0], indices.P[1]) * 2,
TwiddleUV<PVRTCII>(i32NumXWords, i32NumYWords, indices.Q[0], indices.Q[1]) * 2,
TwiddleUV<PVRTCII>(i32NumXWords, i32NumYWords, indices.R[0], indices.R[1]) * 2,
TwiddleUV<PVRTCII>(i32NumXWords, i32NumYWords, indices.S[0], indices.S[1]) * 2,
};
// Access individual elements to fill out PVRTCWord
@ -544,7 +561,7 @@ static uint32_t pvrtcDecompress(uint8_t* pCompressedData, Pixel32* pDecompressed
S.modulationData = static_cast<uint32_t>(pWordMembers[WordOffsets[3]]);
// assemble 4 words into struct to get decompressed pixels from
pvrtcGetDecompressedPixels(P, Q, R, S, pPixels.data(), bpp);
pvrtcGetDecompressedPixels<PVRTCII>(P, Q, R, S, pPixels.data(), bpp);
mapDecompressedData(pOutData, width, pPixels.data(), indices, bpp);
} // for each word
@ -554,7 +571,8 @@ static uint32_t pvrtcDecompress(uint8_t* pCompressedData, Pixel32* pDecompressed
return width * height / static_cast<uint32_t>((wordWidth / 2));
}
uint32_t PVRTDecompressPVRTC(const void* pCompressedData, uint32_t Do2bitMode, uint32_t XDim, uint32_t YDim, uint8_t* pResultImage)
template<bool PVRTCII>
static uint32_t PVRTDecompressPVRTC_int(const void* pCompressedData, uint32_t Do2bitMode, uint32_t XDim, uint32_t YDim, uint8_t* pResultImage)
{
// Cast the output buffer to a Pixel32 pointer.
Pixel32* pDecompressedData = (Pixel32*)pResultImage;
@ -567,7 +585,8 @@ uint32_t PVRTDecompressPVRTC(const void* pCompressedData, uint32_t Do2bitMode, u
if (XTrueDim != XDim || YTrueDim != YDim) { pDecompressedData = new Pixel32[XTrueDim * YTrueDim]; }
// Decompress the surface.
uint32_t retval = pvrtcDecompress((uint8_t*)pCompressedData, pDecompressedData, XTrueDim, YTrueDim, uint8_t(Do2bitMode == 1 ? 2 : 4));
uint32_t retval = pvrtcDecompress<PVRTCII>((uint8_t*)pCompressedData,
pDecompressedData, XTrueDim, YTrueDim, uint8_t(Do2bitMode == 1 ? 2 : 4));
// If the dimensions were too small, then copy the new buffer back into the output buffer.
if (XTrueDim != XDim || YTrueDim != YDim)
@ -583,5 +602,15 @@ uint32_t PVRTDecompressPVRTC(const void* pCompressedData, uint32_t Do2bitMode, u
}
return retval;
}
uint32_t PVRTDecompressPVRTC(const void* pCompressedData, uint32_t Do2bitMode, uint32_t XDim, uint32_t YDim, uint8_t* pResultImage)
{
return PVRTDecompressPVRTC_int<false>(pCompressedData, Do2bitMode, XDim, YDim, pResultImage);
}
uint32_t PVRTDecompressPVRTCII(const void* pCompressedData, uint32_t Do2bitMode, uint32_t XDim, uint32_t YDim, uint8_t* pResultImage)
{
return PVRTDecompressPVRTC_int<true>(pCompressedData, Do2bitMode, XDim, YDim, pResultImage);
}
} // namespace pvr
//!\endcond

View File

@ -20,4 +20,13 @@ namespace pvr {
/// <returns>Return the amount of data that was decompressed.</returns>
uint32_t PVRTDecompressPVRTC(const void* compressedData, uint32_t do2bitMode, uint32_t xDim, uint32_t yDim, uint8_t* outResultImage);
/// <summary>Decompresses PVRTC-II to RGBA 8888.</summary>
/// <param name="compressedData">The PVRTC-II texture data to decompress</param>
/// <param name="do2bitMode">Signifies whether the data is PVRTC2 or PVRTC4</param>
/// <param name="xDim">X dimension of the texture</param>
/// <param name="yDim">Y dimension of the texture</param>
/// <param name="outResultImage">The decompressed texture data</param>
/// <returns>Return the amount of data that was decompressed.</returns>
uint32_t PVRTDecompressPVRTCII(const void* compressedData, uint32_t do2bitMode, uint32_t xDim, uint32_t yDim, uint8_t* outResultImage);
} // namespace pvr

View File

@ -1048,7 +1048,13 @@ INSTANTIATE_TEST_CASE_P(TCtest_PVRTC, ImageDecoderTest,
::testing::Values(
ImageDecoderTest_mode(
"tctest/example-pvrtc1.pvr.gz",
"tctest/example-pvrtc1.pvr.png"))
"tctest/example-pvrtc1.pvr.png"),
ImageDecoderTest_mode(
"tctest/example-pvrtc2-2bpp.pvr.gz",
"tctest/example-pvrtc2-2bpp.pvr.png"),
ImageDecoderTest_mode(
"tctest/example-pvrtc2-4bpp.pvr.gz",
"tctest/example-pvrtc2-4bpp.pvr.png"))
, ImageDecoderTest::test_case_suffix_generator);

Binary file not shown.

After

Width:  |  Height:  |  Size: 596 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 669 KiB

View File

@ -678,7 +678,7 @@ enum PVRTC_Mode_e {
PVRTC_2BPP = (1 << 0),
PVRTC_BPP_MASK = (1 << 0),
// Alpha channel
// Alpha channel (PVRTC-I only)
PVRTC_ALPHA_NONE = (0 << 1),
PVRTC_ALPHA_YES = (1 << 1),
PVRTC_ALPHA_MASK = (1 << 1),
@ -696,6 +696,19 @@ enum PVRTC_Mode_e {
rp_image *fromPVRTC(int width, int height,
const uint8_t *RESTRICT img_buf, int img_siz,
uint8_t mode);
/**
* Convert a PVRTC-II 2bpp or 4bpp image to rp_image.
* @param width Image width.
* @param height Image height.
* @param img_buf PVRTC image buffer.
* @param img_siz Size of image data. [must be >= (w*h)/4]
* @param mode Mode bitfield. (See PVRTC_Mode_e.)
* @return rp_image, or nullptr on error.
*/
rp_image *fromPVRTCII(int width, int height,
const uint8_t *RESTRICT img_buf, int img_siz,
uint8_t mode);
#endif /* ENABLE_PVRTC */
/* BC7 */

View File

@ -100,4 +100,85 @@ rp_image *fromPVRTC(int width, int height,
return img;
}
/**
* Convert a PVRTC 2bpp or 4bpp image to rp_image.
* @param width Image width.
* @param height Image height.
* @param img_buf PVRTC image buffer.
* @param img_siz Size of image data. [must be >= (w*h)/4]
* @param mode Mode bitfield. (See PVRTC_Mode_e.)
* @return rp_image, or nullptr on error.
*/
rp_image *fromPVRTCII(int width, int height,
const uint8_t *RESTRICT img_buf, int img_siz,
uint8_t mode)
{
// Verify parameters.
assert(img_buf != nullptr);
assert(width > 0);
assert(height > 0);
// Expected size to be read by the PowerVR Native SDK.
const uint32_t expected_size_in = ((width * height) /
(((mode & PVRTC_BPP_MASK) == PVRTC_2BPP) ? 4 : 2));
assert(img_siz >= static_cast<int>(expected_size_in));
if (!img_buf || width <= 0 || height <= 0 ||
img_siz < static_cast<int>(expected_size_in))
{
return nullptr;
}
// FIXME: PVRTC-II supports non-power-of-two textures,
// including sizes that aren't a multiple of the tile size,
// in which case, we'll need to adjust it. For now, we'll
// keep the assertion in here.
// PVRTC-II 2bpp uses 8x4 tiles.
// PVRTC-II 4bpp uses 4x4 tiles.
if ((mode & PVRTC_BPP_MASK) == PVRTC_2BPP) {
// PVRTC 2bpp
assert(width % 8 == 0);
assert(height % 4 == 0);
if (width % 8 != 0 || height % 4 != 0)
return nullptr;
} else {
// PVRTC 4bpp
assert(width % 4 == 0);
assert(height % 4 == 0);
if (width % 4 != 0 || height % 4 != 0)
return nullptr;
}
// Create an rp_image.
rp_image *img = new rp_image(width, height, rp_image::FORMAT_ARGB32);
if (!img->isValid()) {
// Could not allocate the image.
delete img;
return nullptr;
}
// Use the PowerVR Native SDK to decompress the texture.
// Return value is the size of the *input* data that was decompressed.
// TODO: Row padding?
uint32_t size = pvr::PVRTDecompressPVRTCII(img_buf, ((mode & PVRTC_BPP_MASK) == PVRTC_2BPP), width, height,
static_cast<uint8_t*>(img->bits()));
assert(size == expected_size_in);
if (size != expected_size_in) {
// Read error...
delete img;
return nullptr;
}
// TODO: If !has_alpha, make sure the alpha channel is all 0xFF.
// Set the sBIT metadata.
// NOTE: Assuming PVRTC-II always has alpha for now.
static const rp_image::sBIT_t sBIT = {8,8,8,0,8};
img->set_sBIT(&sBIT);
// Image has been converted.
return img;
}
} }

View File

@ -472,6 +472,22 @@ const rp_image *KhronosKTXPrivate::loadImage(void)
buf.get(), expected_size,
ImageDecoder::PVRTC_4BPP | ImageDecoder::PVRTC_ALPHA_YES);
break;
case GL_COMPRESSED_RGBA_PVRTC_2BPPV2_IMG:
// PVRTC-II, 2bpp.
// NOTE: Assuming this has alpha.
img = ImageDecoder::fromPVRTCII(ktxHeader.pixelWidth, height,
buf.get(), expected_size,
ImageDecoder::PVRTC_2BPP | ImageDecoder::PVRTC_ALPHA_YES);
break;
case GL_COMPRESSED_RGBA_PVRTC_4BPPV2_IMG:
// PVRTC-II, 4bpp.
// NOTE: Assuming this has alpha.
img = ImageDecoder::fromPVRTCII(ktxHeader.pixelWidth, height,
buf.get(), expected_size,
ImageDecoder::PVRTC_4BPP | ImageDecoder::PVRTC_ALPHA_YES);
break;
#endif /* ENABLE_PVRTC */
default:

View File

@ -487,6 +487,20 @@ const rp_image *PowerVR3Private::loadImage(int mip)
img = ImageDecoder::fromPVRTC(width, height, buf.get(), expected_size,
ImageDecoder::PVRTC_4BPP | ImageDecoder::PVRTC_ALPHA_YES);
break;
case PVR3_PXF_PVRTCII_2bpp:
// PVRTC-II, 2bpp.
// NOTE: Assuming this has alpha.
img = ImageDecoder::fromPVRTCII(width, height, buf.get(), expected_size,
ImageDecoder::PVRTC_2BPP | ImageDecoder::PVRTC_ALPHA_YES);
break;
case PVR3_PXF_PVRTCII_4bpp:
// PVRTC-II, 4bpp.
// NOTE: Assuming this has alpha.
img = ImageDecoder::fromPVRTCII(width, height, buf.get(), expected_size,
ImageDecoder::PVRTC_4BPP | ImageDecoder::PVRTC_ALPHA_YES);
break;
#endif /* ENABLE_PVRTC */
case PVR3_PXF_ETC1: