Skip to content
Tristan Grimmer edited this page Dec 31, 2023 · 7 revisions

Tacent View (experimentally) supports Imagination Technologies' PVR textures V1, V2, and V3.

I'm not particularly familiar with this format, but the specification is public. I've included a pdf below just in case the official link ever changes. These documents are for V3 of the specification, which I believe to be current.

Local: PVR V3 Specification Official: PVR V3 Specification

Here is the legacy V1/V2 specification.

Local: PVR Legacy Specification Official: PVR Legacy Specification

The PVR SDK is MIT licensed.

There are a few things I like about this format.

  • It's simple and well-documented.
  • It has been modernized (V3) to include the ASTC formats.
  • It specifies coordinate systems in a clean way.
  • It supports user-meta-data in a clean way.
  • It allows only specific sides of a cube map to be stored.
  • It (mostly) separates what the format for the data is from what the data in the format is.

This last one is important in my opinion because the current plethora of pixel-formats as specified by Vulcan and DirectX is, well, a bit of a mess. It all happened when it became clear that colour-space information as well as different data representations were needed. For example, look at the DGXI pixel formats for the BGR8 pixel format:

DXGIFMT_B8G8R8A8_TYPELESS
DXGIFMT_B8G8R8A8_UNORM_SRGB
DXGIFMT_B8G8R8X8_TYPELESS
DXGIFMT_B8G8R8X8_UNORM_SRGB

We now have 4x as many formats as needed and yet they're all 4 components of 8 bits each. In the Tacent library I also didn't like this explosion of pixel formats and made the pixel-format represent (only) the encoding format for the data. Satellite information is where the colour-profile (what the data is) and the representation (how the data is to be interpreted) should be stored. Essentially there are 4 concepts here:

  1. What the data represents (colour-space/colour-profile. e.g. sRBG, linear, etc).
  2. How the data is represented (e.g. SIGNED/UNSIGNED, FLOAT etc).
  3. How the data is encoded (pixel format).
  4. Hints. What you may want to do to the data once decoded. Things like normalizing would fall here.

These are independant and should be kept separate to avoid confusion and boiler-plate code that has to extract this information from the large number of traditional pixel formats. This is what the PVR-spec does. In particular PVR files have:

  • A field for the (pixel) format (BC7,ACTC4x4,etc)
  • A field for the colour-space (linear RGB or sRGB), and
  • A field for the data-type (Unsigned Integer Normalised, Signed Integer Normalised, Unsigned Integer, Signed Integer, Float, etc).

Of course the container format isn't perfect. The idea of a colour-profile rather than just a colour-space with two options (sRGB or Linear) would be a possible improvement -- not all pixel-data falls nicely into those two categories -- especially when more than 3 channels are encoded.

There are also a few issues in supporting PVRTC. Container file-format aside, the actual PVR pixel formats are a bit problematic (and it doesn't make much sense to support the PVR container format if it can't support the PVRTC pixel formats). Basically PVRTC comes in 2 flavours: the original PVRTC pixel format that supports 2 and 4 bpp, and the 'newer' (can't find exact release date) PVRTC2 (AKA PVRTCII) format that also supports 2 and 4 bpp. Further for both PVRTC and PVRTC2 there are 6 and 8 bpp HDR formats. The issues are:

  • There is no specification, implementation, or information for any of the PVR HDR formats (PVRTC 6bpp HDR, PVRTC 8bpp HDR, PVRTC2 6bpp HDR, PVRTC2 8bpp HDR). The closed-source PVRTexTool can generate and load them, and does come with a lib, but the lib is large compared to the overall size of Tacent View.
  • There is no software decode implementations (C or C++ or any high-level language) available for PVRTC2 (as of 2023.08.29). Things labeled PVRTC2 on the web are named for implementations of PVRTC(V1) 2BPP.
  • The software decoder in the SDK is only for PVRTC 2bpp and 4bpp. No HDR or PVRTC2. This means we'd have to either a) rely on GPU support which isn't great for a texture viewer, or b) implement it ourselves which is no small time commitment.
  • If we were to implement PVRTC2 ourselves the only 'official' specification I can find is pvrtc-and-texture-compression-user-guide and it is missing some vital information -- specifically how the modulation indexes are to be interpreted when the local-palette and hard flags are set.

In short, PVRTC2 is a bit more 'closed' compared to other available formats like ASTC. The current set of resources describing it in detail (as of 2023.9.2) are included here for future reference:

  • Local: PVRTC User Guide PDF External: PVRTC User Guide PDF
  • Local : Texture Compression Techniques PDF External: Texture Compression Techniques. This has some potential information regarding the 'hard' flag and palette lookup mode, but I think it misinterprets how the 4x4 blocks are overlaid on the image pixels. I believe the PVRTC decompress code that is available will go across the first row with the 4x4 block halfway through the top edge (index starts at -1). This means there is no (2,2) offset as described in the report. Basically all 4x4 texel-decodes straddle 4 PVRTC blocks (P, Q, R, and S). This also means 4 blocks is not good enough to decode a 5x5 areas, as the right and bottom texels in a 5x5 should be getting two of their A/B colours from the next right and lower blocks. In any case, this document may be of help for the alpha computation and the palette lookup for PVRTC2.

RGBM and RGBD

This was taken from iwasbeingirony. Needed a reference regarding these HDR formats that is easy to understand.

START OF COPY

RGBM is an 8bit RGBA format, where Alpha is sacrificed to store a shared multiplier. Decoding RGBM:

float3 DecodeRGBM(float4 rgbm)
{
    return rgbm.rgb * (rgbm.a * MaxRange);
}

This produces a range of 0 to MaxRange.

Assuming MaxRange is 65025, When M=1, the range is 0, 1, 2, 3 ... 255, When M=2, the range is 0, 2, 4, 6 ... 510, When M=3, the range is 0, 3, 6, 9 ... 765, When M=255, the range is 0,255,510,765 ... 65025.

Encoding RGBM:

float4 EncodeRGBM(float3 rgb)
{
    float maxRGB = max(rgb.x,max(rgb.g,rgb.b));
    float M =      maxRGB / MaxRange;
    M =            ceil(M * 255.0) / 255.0;
    return float4(rgb / (M * MaxRange), M);
}

RGBD follows the same rules as RGBM, however it stores a divider in Alpha, instead of a Multiplier. Decoding RGBD:

float3 DecodeRGBD(float4 rgbd)
{
    return rgbd.rgb * ((MaxRange / 255.0) / rgbd.a);
}

Encoding RGBD:

float4 EncodeRGBD(float3 rgb)
{
    float maxRGB = max(rgb.x,max(rgb.g,rgb.b));
    float D      = max(MaxRange / maxRGB, 1);
    D            = saturate(floor(D) / 255.0);
    return float4(rgb.rgb * (D * (255.0 / MaxRange)), D);
}

As can be seen, RGBD is a bit more complex in both the encode and the decode. The encode is trickier, as RGBD is a bit more sensitive to values outside of {0,MaxRange}. END OF COPY

Clone this wiki locally