[Windows Mobile Software Development] Chainfire:
Textures, ATC, CTES, QTC, CFC (tech background)

The imageon 3D chip in our devices support texture compression, and Manila (Mode9) uses this. The format used is a special format created by AMD/ATI for low size and lower power use on mobile devices, called ATC (ATI Texture Compression).

There are three ATC formats:

- ATC_RGB: 4 bits per pixel (4 bits RGB)

- ATC_RGBA_EXPLICIT_ALPHA: 8 bits per pixel (4 bits RGB + 4 bits Alpha)

- ATC_RGBA_INTERPOLATED_ALPHA: 8 bits per pixel (not sure on the format)

Almost all images used in Manila are of the 'ATC_RBGA_EXPLICIT_ALPHA' variant, and this article will focus on these. ATC_RGB is also used for a small number of images, though I have not further investigated its format.

The image data for these formats are stored in one of the following file formats:

- ATC: The file format generally used by AMD/ATI

- CTES: ATC related, some weirdness, see below. Seems to be forward compatible with ATC, but not backwards.

- QTC: Qualcomm adapted version of ATC, used by Manila/Mode9

The formats are very similar, though we will focus only on ATC/QTC, that's all we need.

Image data (ATC_RGBA_EXPLICIT_ALPHA) - Updated January 12, 2009

A lot of the original information comes from D-MAN666's posts here.

I will skip over the headers (32 bytes), they are listed below for ATC and QTC specifically. This is about the actual image.

The image is divided into blocks of 4x4 pixels. An 8x8 image would be stored like this: (A, B, C and D are 'pixel blocks')

AAAABBBB

AAAABBBB

AAAABBBB

AAAABBBB

CCCCDDDD

CCCCDDDD

CCCCDDDD

CCCCDDDD

A 4x4 pixel block is 16 pixels and 16 bytes. So in effect, you can see it as 8 bits per pixel. An image is always stored using these 4x4 pixel blocks. A 5x5 images would thus use 4 blocks!

bytes 0-7 are alpha values for each pixel, 4 bits per pixel (4 bits * 16 = 64 bits = 8 bytes) - this is not present for the ATC_RGB format. Scale these to the 0..255 range by multiplying each alpha value by 17.

bytes 8-11 are color keys, there are two keys of 16 bits (2 bytes). The keys are stored like this:

word 1: XRRRRRGG GGGBBBBB (1-bit method, 5-bit R, G, B)

word 2: RRRRRGGG GGGBBBBB (5-bit R, 6-bit G, 5-bit B)

Where X is the decoding method to use, there are two.

bytes 12-15 are mixdown values, 2 bits per pixel (2 bits * 16 = 32 bits = 4 bytes). The per-pixel mixdown value, combined with the color keys mentioned above determine the actual color that is outputted. You must scale the scolor keys to the 0..255 range and apply a formula to them.

if HasAlpha then begin // skip for ATC_RGB

sIn.Read(dw, 4); // read dword
for i := 0 to 7 do begin
alpha[i] := (dw and $F) * 17;
dw := dw shr 4;
end;

sIn.Read(dw, 4); // read dword
for i := 8 to 15 do begin
alpha[i] := (dw and $F) * 17;
dw := dw shr 4;
end;

// alpha[0..15] now contain the scaled 4x4 pixel block alpha values
end;

sIn.Read(w, 2); // read a word, key1

Method := (w shr 15);
Keys[iR, 0] := ((w and $7C00) shr 10) * (255/31);
Keys[iG, 0] := ((w and $03E0) shr 5) * (255/31);
Keys[iB, 0] := (w and $001F) * (255/31);

sIn.Read(w, 2); // read a word, key2

Keys[iR, 1] := ((w and $F800) shr 11) * (255/31);
Keys[iG, 1] := ((w and $07E0) shr 5) * (255/63);
Keys[iB, 1] := (w and $001F) * (255/31);

sIn.Read(mixdown, 4); // read a dword, mixdown values

for i := 0 to 15 do begin
pixels[i] := (mixdown and $3);
mixdown := mixdown shr 2;
end;

// pixels[0..15] now contain the still-encoded 4x4 pixel block mixdown values

When thinking about the color keys and mixdown values, think of the keys as a color-range start and end value. The mixdown values decide which value to pick inside the range. (for each R,G,B)

For example, let's take a key1 of 10 and a key2 of 40 for Green. Then the decoded Green values would be:

mixdown 00 01 02 03
value 10 20 30 40

This is only true, however, when the 'method' bit (X) is 0. Full decoding of both methods:

for i := 0 to 15 do begin
a := alpha[i];

if (method = 0) then begin
r := Round( Keys[iR, 0] + ((pixels[i] / 3) * (Keys[iR, 1] - Keys[iR, 0])) );
g := Round( Keys[iG, 0] + ((pixels[i] / 3) * (Keys[iG, 1] - Keys[iG, 0])) );
b := Round( Keys[iB, 0] + ((pixels[i] / 3) * (Keys[iB, 1] - Keys[iB, 0])) );
end else begin
case pixels[i] of
0: begin
r := 0;
g := 0;
b := 0;
end;
1: begin
r := Round( Keys[iR, 0] - ((1/4) * Keys[iR, 1]) );
g := Round( Keys[iG, 0] - ((1/4) * Keys[iG, 1]) );
b := Round( Keys[iB, 0] - ((1/4) * Keys[iB, 1]) );
end;
2: begin
r := Round( Keys[iR, 0] );
g := Round( Keys[iG, 0] );
b := Round( Keys[iB, 0] );
end;
3: begin
r := Round( Keys[iR, 1] );
g := Round( Keys[iG, 1] );
b := Round( Keys[iB, 1] );
end;
end;
end;
end;

Both methods have various way of formulating them. I thought the ways I have chosen here are clearest for how it works.

Update Jan 7, 2009

Image data (ATC_RGB)

The image data here is exactly the same as ATC_RGBA_EXPLICIT_ALPHA, except that the alpha bits aren't includes. So, each 16-pixel block becomes 8 bytes instead of 16, as bytes 0-7 from ATC_RGBA_EXPLICIT_ALPHA are not there. This means 4 bits per pixel.

- end of update -

ATC, CTES, QTC

This image data seems to be the same across all formats - and it should be, as this data is sent directly to the 3D chip. You would not want to have to process it first.

Let's first pick out CTES, as I have very little to say about it. It seems to be nearly the same as ATC and QTC, however, for some reason, "The Compressonator" will output CTES files we can use as ATC, but will not read our own Manila-based ATC's in CTES mode (only in ATC mode). What's up with that? I don't know. Perhaps one of you will figure it out.

QTC header

Magic: DWORD; // 0x31435451 : "QTC1"
Magic2: DWORD; // always 1 ?
Width: DWORD;
Height: DWORD;

Format: DWORD; // 0x14, 0x15
Dummy1: DWORD; // formerly known as Unknown1, may be 0 - Jan 7, 2009
PayloadSize: DWORD; // formerly known as Unknown2 - Jan 7, 2009
Dummy3: DWORD; // formerly known as Unknown3, may be 0 - Jan 7, 2009

The meaning of the unknowns has not been deciphered yet. Setting them to weird values does muck-up the decoding of the images, however, they do not seem to be actually sent to the 3D chip. Or perhaps I just have not found where and when!

For format, 0x14 is used for ATC_RGB_EXPLICIT_ALPHA. The small number of images that use 0x15, I suspect, are ATC_RGB. Either way, they do not decode using the ATC_RGB_EXPLICIT_ALPHA method and I know ATC_RGB is used some places, so it would make some sense to make this assumption.

Update Jan 7, 2009

Unknown2 has been replaced by PayloadSize, thanks to myself, D-MAN666 and eidolen.

The PayloadSize is the number of bytes after the header that contain content.

For images of type 0x14 (ATC_RGB_EXPLICIT_ALPHA) this is: Width * Height, where both Width and Height are multiples of 4, due to how the format itself works, in other words: (RoundUp(Width / 4) * 4) * (RoundUp(Height / 4) * 4).

For images of type 0x15 (ATC_RGB) this is half of type 0x14, because ATC_RGB uses 4 bits per pixel instead of 8. The multiples of 4 rule still stands, so the PayloadSize is: Round(((RoundUp(Width / 4) * 4) * (RoundUp(Height / 4) * 4)) / 2)

Note that all Manila image files (at least the ones I have) are padded to be a multiple of 512 bytes in size. This is probably a speed optimization for when these files are cooked into a ROM.

Dummy1 and Dummy3 (aptly renamed from Unknown1 and Unknown3) seem to be irrelevant. After we figured out how PayloadSize (Unknown2) was relevant, we tried blanking out Dummy1 and Dummy3 with 0's, and TF3D still works without a hitch. The original values do not seem to be related to the dimensions nor the payload size, and they are not sent to the graphics chip either.

- end of update -

Update November 6, 2009

Manila 2.5 uses 4 additional file formats:

0x01 - 8888 RGBA, 32bpp

0x02 - 888 RGB, 24bpp (I have not encountered an actual image in this format, so processing may be faulty by CFC and CFC GUI)

0x03 - 565 RGB, 16bpp

0x05 - 4444 RGBA, 16bpp

- end of update -

ATC header

Updated 08/Jan/2009

Magic: DWORD; // 0xCCC40002
Width: DWORD;
Height: DWORD;
Format: DWORD; // ATC_FORMAT, 0x01 for RGB, 0x02 for RGBA_EXPLICIT_ALPHA, 08/Jan/2009

Magic3: DWORD; // 0x20 ... mucks up colors... not clear?
Magic4: DWORD; // 0x01, 08/Jan/2009
Magic5: DWORD; // 0x01, 08/Jan/2009
FormatMagic: DWORD; // 0x8C92 for RGB, 0x8C93 for RGBA_EXPLICIT_ALPHA, 08/Jan/2009

- end of updated content -