Difference between revisions of "Raster (RW Section)"
Line 1: | Line 1: | ||
− | {{RW Section|Texture Native| | + | {{RW Section |
− | + | | NAME = Texture Native | |
+ | | VENDORNAME = Criterion Games | ||
+ | | MODULENAME = Core | ||
+ | | MODULEID = 000000 | ||
+ | | IDENTIFIER = 15 | ||
+ | | PARENTS = [[Texture Dictionary (RW Section)|Texture Dictionary]] | ||
+ | | CHILDREN = [[Struct (RW Section)|Struct]] | ||
+ | | EXTENSIONS = [[Sky Mipmap Val (RW Section)|Sky MipMap Val]] | ||
+ | }} | ||
'''Texture Native''' is a container section used in [[texture archive|TXD files]] as child of a [[Texture Dictionary (RW Section)|Texture Dictionary]] section. It is normally accompanied by a [[Struct (RW Section)#Texture_Native|Struct]] section. | '''Texture Native''' is a container section used in [[texture archive|TXD files]] as child of a [[Texture Dictionary (RW Section)|Texture Dictionary]] section. It is normally accompanied by a [[Struct (RW Section)#Texture_Native|Struct]] section. | ||
− | ==See also== | + | == Binary Structure == |
− | * [[Texture | + | |
+ | The '''Texture Native's''' [[Struct (RW Section)|Struct] sections' contents differs between PC, XBOX and PS2 versions of the GTA III game trilogy. | ||
+ | |||
+ | ===Direct3D Header=== | ||
+ | |||
+ | There is a ''86 byte'' header with general image information. | ||
+ | |||
+ | The '''name''' and '''maskName''' fields are strings that are expected to be zero terminated. This allows a maximum string length of 31 bytes (last byte taken by '\0'). | ||
+ | |||
+ | <source lang="cpp">struct NativeTexturePC_Header | ||
+ | { | ||
+ | struct { | ||
+ | unsigned int platformId; | ||
+ | unsigned int filterMode : 8; | ||
+ | unsigned int uAddressing : 4; | ||
+ | unsigned int vAddressing : 4; | ||
+ | unsigned int pad : 16; // should be zeroed out for sanity | ||
+ | char name[32]; | ||
+ | char maskName[32]; | ||
+ | } TextureFormat; | ||
+ | |||
+ | struct { | ||
+ | unsigned int rasterFormat; | ||
+ | union { | ||
+ | D3DFORMAT d3dFormat; // SA, see D3DFORMAT on MSDN | ||
+ | unsigned int hasAlpha // GTA3 & VC | ||
+ | }; | ||
+ | unsigned short width; | ||
+ | unsigned short height; | ||
+ | unsigned char depth; | ||
+ | unsigned char numLevels; | ||
+ | unsigned char rasterType; | ||
+ | union { | ||
+ | unsigned char compression; // GTA3 & VC | ||
+ | struct { // SA | ||
+ | unsigned char alpha : 1; | ||
+ | unsigned char cubeTexture : 1; | ||
+ | unsigned char autoMipMaps : 1; | ||
+ | unsigned char compressed : 1; // if true, raster may not be defined in original RW (compressed?) | ||
+ | unsigned char pad : 4; // recommended to be zero'ed out | ||
+ | }; | ||
+ | }; | ||
+ | } RasterFormat; | ||
+ | };</source> | ||
+ | |||
+ | ===PlayStation 2 Header=== | ||
+ | |||
+ | The PlayStation 2 architecture uses header data of dynamic size. While the '''name''' and '''maskName''' fields are 32 bytes in length each on the Direct3D architecture, here they can be any size. It is not recommended to overshoot 32 bytes though, as there will be cross-platform compatibility issues. Do not forget to append '\0' for zero termination! | ||
+ | |||
+ | It is important to align all data to 4 bytes, since the PlayStation 2 implementation may fail to read your data properly otherwise. | ||
+ | |||
+ | <source lang="cpp">struct NativeTexturePS2_Header | ||
+ | { | ||
+ | struct { | ||
+ | unsigned int platformId; | ||
+ | unsigned int filterMode : 8; | ||
+ | unsigned int uAddressing : 4; | ||
+ | unsigned int vAddressing : 4; | ||
+ | unsigned int pad : 16; // should be zero'ed out for sanity | ||
+ | } TextureFormat; | ||
+ | |||
+ | // CHUNK_STRING for name (aligned to 4 bytes) | ||
+ | // CHUNK_STRING for alpha-mask name (aligned to 4 bytes) | ||
+ | |||
+ | //size: 64 bytes | ||
+ | // Wrapped in a CHUNK_STRUCT | ||
+ | struct RasterFormat | ||
+ | { | ||
+ | unsigned int width, height; | ||
+ | unsigned int depth; | ||
+ | unsigned int rasterFormat; | ||
+ | |||
+ | // See https://www.dropbox.com/s/4wfvq86qk3h6v0n/GS_Users_Manual.pdf | ||
+ | unsigned long long tex0; // PS2 TEX0 GS register | ||
+ | unsigned long long tex1; // PS2 TEX1 GS register | ||
+ | unsigned long long miptbp1; // PS2 MIPTBP1 GS register | ||
+ | unsigned long long miptbp2; // PS2 MIPTBP2 GS register | ||
+ | |||
+ | unsigned int texelDataSectionSize; // stream size of the mipmap data | ||
+ | unsigned int paletteDataSectionSize; // stream size of the palette data (zero if no palette) | ||
+ | unsigned int gpuDataAlignedSize; // memory span of the texture mipmap data on the GS (aligned to pages/2048) | ||
+ | |||
+ | unsigned int skyMipmapVal; // always 4032 | ||
+ | }; | ||
+ | };</source> | ||
+ | |||
+ | ===Description=== | ||
+ | |||
+ | ;Platform ID: This is '''8''' for GTA3 and VC on the PC, '''9''' for GTA SA on the PC, '''PS2\0''' FourCC on PS2 (any game version), and '''5''' for GTA3/VC/SA on the XBOX. | ||
+ | ;Filter Mode: | ||
+ | FILTER_NONE 0x00 | ||
+ | FILTER_NEAREST 0x01 | ||
+ | FILTER_LINEAR 0x02 | ||
+ | FILTER_MIP_NEAREST 0x03 | ||
+ | FILTER_MIP_LINEAR 0x04 | ||
+ | FILTER_LINEAR_MIP_NEAREST 0x05 | ||
+ | FILTER_LINEAR_MIP_LINEAR 0x06 | ||
+ | |||
+ | ;Addressing Mode: | ||
+ | WRAP_NONE 0x00 | ||
+ | WRAP_WRAP 0x01 | ||
+ | WRAP_MIRROR 0x02 | ||
+ | WRAP_CLAMP 0x03 | ||
+ | |||
+ | ;Raster Format: | ||
+ | FORMAT_DEFAULT 0x0000 | ||
+ | FORMAT_1555 0x0100 (1 bit alpha, RGB 5 bits each; also used for DXT1 with alpha) | ||
+ | FORMAT_565 0x0200 (5 bits red, 6 bits green, 5 bits blue; also used for DXT1 without alpha) | ||
+ | FORMAT_4444 0x0300 (RGBA 4 bits each; also used for DXT3) | ||
+ | FORMAT_LUM8 0x0400 (gray scale, D3DFMT_L8) | ||
+ | FORMAT_8888 0x0500 (RGBA 8 bits each) | ||
+ | FORMAT_888 0x0600 (RGB 8 bits each, D3DFMT_X8R8G8B8) | ||
+ | FORMAT_555 0x0A00 (RGB 5 bits each - rare, use 565 instead, D3DFMT_X1R5G5B5) | ||
+ | |||
+ | FORMAT_EXT_AUTO_MIPMAP 0x1000 (RW generates mipmaps, see special section below) | ||
+ | FORMAT_EXT_PAL8 0x2000 (2^8 = 256 palette colors) | ||
+ | FORMAT_EXT_PAL4 0x4000 (2^4 = 16 palette colors) | ||
+ | FORMAT_EXT_MIPMAP 0x8000 (mipmaps included) | ||
+ | |||
+ | '''For verification of Direct3D 9 mapping, see disassembly offset 0x0085C670 in GTA:SA 1.0.''' | ||
+ | |||
+ | ;Depth: 4, 8, 16 or 32; 4 and 8 usually come with palette | ||
+ | ;Compression: for more information, refer to [[Wikipedia:S3 Texture Compression|S3 Texture Compression]]. | ||
+ | |||
+ | ===Raster Data=== | ||
+ | |||
+ | The header is followed by an optional palette and the raster data (pixels), according to the Raster Format flags. | ||
+ | |||
+ | ====Raster using Palette==== | ||
+ | |||
+ | If Raster Format is <code>FORMAT_EXT_PAL8 | FORMAT_8888</code> or <code>FORMAT_EXT_PAL8 | FORMAT_888</code>, then a ''color palette'' of (up to) 256 RGBA colors is included. This is used in GTA3 quite often. The raster's one byte for each pixel is an index into the palette. | ||
+ | |||
+ | Here is some pseudo-code that shows the layout of the raster data if the texture uses a palette. | ||
+ | <source lang="C"> | ||
+ | // paletteSize can have many values: | ||
+ | // - 16 for FORMAT_EXT_PAL4 on PS2 (rwver: 3.0.0.0 - (below) 3.6.0.3, III and VC) | ||
+ | // - 24 for FORMAT_EXT_PAL4 on PS2 (rwver: 3.6.0.3, SA) | ||
+ | // - 32 for FORMAT_EXT_PAL4 on XBOX (PC?) | ||
+ | // - 256 for FORMAT_EXT_PAL8 on any configuration | ||
+ | |||
+ | colorType_t colors[ paletteSize ]; // palette color entries, colorType_t is defined by depth and raster format | ||
+ | |||
+ | repeated for each mipmap: | ||
+ | { | ||
+ | // mipWidth is the texture width, halfed for each level | ||
+ | // mipHeight is the texture height, halfed for each level | ||
+ | |||
+ | unsigned int rasterSize; // size of raster data in bytes, see Texture Data Layout section | ||
+ | indexType_t paletteIndice[ mipWidth * mipHeight ]; // palette indice into the colors array. | ||
+ | } | ||
+ | </source> | ||
+ | |||
+ | '''NOTE:''' palette colors use '''RGBA''' color order! | ||
+ | |||
+ | Depending on the depth, the palette index can be '''4 or 8 bits wide'''. The PlayStation 2 architecture expects a 4 bits wide palette index for <code>FORMAT_EXT_PAL4</code> while the XBOX/PC expect a 8 bits wide palette index. | ||
+ | |||
+ | ====Raster without palette==== | ||
+ | |||
+ | If no palette is included, the header is followed by raster size and data. If ''[[Wikipedia:mipmap|mipmap]]s'' are included (<code>FORMAT_EXT_MIPMAP</code>) then this is repeated for each mipmap. With increasing mipmap levels the raster size is decreased by the factor 4 (side lengths halved). Small initial textures can result in mipmaps of the size 0. For ''uncompressed'' images: | ||
+ | |||
+ | <source lang="c"> | ||
+ | repeated for each mipmap: | ||
+ | { | ||
+ | // mipWidth is the texture width, halfed for each level | ||
+ | // mipHeight is the texture height, halfed for each level | ||
+ | |||
+ | unsigned int rasterSize; // size of raster data in bytes, see Texture Data Layout section | ||
+ | colorType_t colors[ mipWidth * mipHeight ]; | ||
+ | } | ||
+ | </source> | ||
+ | |||
+ | '''WARNING:''' FORMAT_888(8) uses '''BGRA''' color order! | ||
+ | |||
+ | If DXT compression is used, blocks of 4x4 pixels occupy only 8 or 16 bytes (depending on the type). | ||
+ | |||
+ | =====Special Direct3D 9 Rasters===== | ||
+ | |||
+ | By setting the <code>isNotRWCompatible</code> field to '''true''' in the ''Direct3D 9'' native texture, the GTA:SA engine will use the '''D3DFORMAT''' field instead of the RenderWare raster format definition. This allows you to load any Direct3D 9 texture into that engine at the expense of compatibility between different RenderWare implementations. | ||
+ | |||
+ | Originally this feature has been used to ship '''DXT compressed textures''' with the game. That is why this field is historically known as "compression flag". | ||
+ | |||
+ | ====Texture Data Layout==== | ||
+ | |||
+ | How the texel data is placed in the color buffer '''depends on the platform'''. The Direct3D 8/9 architecture places colors in byte-aligned scanlines that are arranged in an array. Very often this storage format looks exactly like a ''width x height'' array of colors '''but''' you must be careful here. It starts to make a difference for correctly generating mipmaps or NPOT textures. | ||
+ | |||
+ | In the special case of Direct3D, Criterion defined the row byte-alignment to '''4 bytes'''. This is a ''universally accepted standard alignment'', originating from the beginnings of the '''DirectDraw Surface''' format. Long story short, many things went wrong and people assumed too many things. In the end, Criterion created a texture loader that violated the Direct3D specification. | ||
+ | |||
+ | ====Known color types==== | ||
+ | |||
+ | Here is a list of known color types that can be used in the palette or the image arrays. Any of these can map to the '''colorType_t''' type depending on the parameters of the texture. | ||
+ | |||
+ | <source lang="cpp"> | ||
+ | // Known color types (in BGRA order): | ||
+ | //FORMAT_8888, depth 32 (D3DFMT_A8R8G8B8) | ||
+ | struct format8888_depth32 | ||
+ | { | ||
+ | unsigned char b, g, r, a; | ||
+ | }; | ||
+ | |||
+ | //FORMAT_565, depth 16 (D3DFMT_R5G6B5) | ||
+ | struct format565_depth16 | ||
+ | { | ||
+ | unsigned short blue : 5; | ||
+ | unsigned short green : 6; | ||
+ | unsigned short red : 5; | ||
+ | }; | ||
+ | |||
+ | //FORMAT_4444, depth 16 (D3DFMT_A4R4G4B4) | ||
+ | struct format4444_depth16 | ||
+ | { | ||
+ | unsigned short blue : 4; | ||
+ | unsigned short green : 4; | ||
+ | unsigned short red : 4; | ||
+ | unsigned short alpha : 4; | ||
+ | }; | ||
+ | |||
+ | //FORMAT_888, depth 32 (D3DFMT_X8R8G8B8) | ||
+ | struct format888_depth32 | ||
+ | { | ||
+ | unsigned char b, g, r; | ||
+ | unsigned char unused; | ||
+ | }; | ||
+ | |||
+ | //FORMAT_888, depth 24 (D3DFMT_R8G8B8) | ||
+ | struct format888_depth24 | ||
+ | { | ||
+ | unsigned char b, g, r; | ||
+ | }; | ||
+ | </source> | ||
+ | |||
+ | ====Color Ordering==== | ||
+ | |||
+ | The order of the RGBA colors '''depends on the platform'''. | ||
+ | * PlayStation 2 uses '''RGBA''' colors | ||
+ | * Direct3D 9 uses '''BGRA''' or '''RGBA''' depending on the '''D3DFORMAT''' field | ||
+ | |||
+ | For example, let's investigate how to convert color formats from PS2 to Direct3D. Since <code>FORMAT_8888</code> always is RGBA on the PlayStation 2, the color struct should look like this. | ||
+ | <source lang="cpp">struct format8888_texel_rgba | ||
+ | { | ||
+ | uint8 red; | ||
+ | uint8 green; | ||
+ | uint8 blue; | ||
+ | uint8 alpha; | ||
+ | };</source> | ||
+ | '''Now we have two choices.''' | ||
+ | * Use D3DFMT_A8B8G8R8 so the texel data is correctly ordered already, but with less compatibility with GTA community tools. | ||
+ | * Use D3DFMT_A8R8G8B8 and swap red with blue. | ||
+ | |||
+ | If we go for the latter, the new color struct should look like this. | ||
+ | <source lang="cpp">struct format8888_texel_bgra | ||
+ | { | ||
+ | uint8 blue; | ||
+ | uint8 green; | ||
+ | uint8 red; | ||
+ | uint8 alpha; | ||
+ | };</source> | ||
+ | |||
+ | The '''conversion between RGBA to BGRA''' can be performed using the following algorithm. | ||
+ | <source lang="cpp"> | ||
+ | void convertRGBA2BGRA(void *theTexels, uint32 texelCount) | ||
+ | { | ||
+ | for (uint32 n = 0; n < texelCount; n++) | ||
+ | { | ||
+ | uint8 r, g, b, a; | ||
+ | |||
+ | // Read the color from the texels. | ||
+ | { | ||
+ | format8888_texel_rgba *rgba8888 = (format8888_texel_rgba*)theTexels + n; | ||
+ | |||
+ | r = rgba8888->red; | ||
+ | g = rgba8888->green; | ||
+ | b = rgba8888->blue; | ||
+ | a = rgba8888->alpha; | ||
+ | } | ||
+ | |||
+ | // Write back the color in a different ordering. | ||
+ | { | ||
+ | format8888_texel_bgra *bgra8888 = (format8888_texel_bgra*)theTexels + n; | ||
+ | |||
+ | bgra8888->red = r; | ||
+ | bgra8888->green = g; | ||
+ | bgra8888->blue = b; | ||
+ | bgra8888->alpha = a; | ||
+ | } | ||
+ | } | ||
+ | } | ||
+ | </source> | ||
+ | |||
+ | ====Mipmapping==== | ||
+ | |||
+ | The only engine that supports rendering mipmaps is '''GTA:SA'''. Mipmaps are essential to improve the visual quality of your models when they are rendered from a far distance. Every modification should either come with custom mipmaps or have the automatic mipmap generation flag enabled. | ||
+ | |||
+ | If '''automatic mipmap generation''' is enabled (<code>FORMAT_EXT_AUTO_MIPMAP</code>), then the texture must not come with any additional mipmap layers. Otherwise the TXD archive will '''fail to load'''. | ||
+ | |||
+ | Shipping mipmaps with the TXD files reduces the CPU/GPU load of the game, since they do not have to be generated during runtime. | ||
+ | |||
+ | On the PlayStation 2 architecture, all textures that ship with custom mipmaps also have the <code>FORMAT_EXT_AUTO_MIPMAP</code> flag enabled. This could have been an oversight and points to the fact that the PS2 does not support generating mipmaps. | ||
+ | |||
+ | ====PlayStation 2==== | ||
+ | |||
+ | [[File:ps2texalloc.png|thumb|Example of texture allocation.]] | ||
+ | |||
+ | The image and palette data is both handled in a (generic) texture format. There are special raster flags reserved. | ||
+ | |||
+ | <source lang="cpp"> | ||
+ | #define RASTER_SWIZZLED 0x10000 // the images are "swizzled" | ||
+ | #define RASTER_HAS_TEXEL_HEADERS 0x20000 // the image data is preceeded by headers | ||
+ | </source> | ||
+ | |||
+ | If the rasterFlags contain the <code>RASTER_HAS_TEXEL_HEADERS</code> flag, then each image data and palette data are a chain of '''GIF packets''' that are directly uploaded to the GS. Each mipmap consists of one register list and the image data. The GIFtag struct that is the header of each packet could look like this. | ||
+ | |||
+ | <source lang="cpp">struct GIFtag | ||
+ | { | ||
+ | unsigned long long nloop : 15; | ||
+ | unsigned long long eop : 1; | ||
+ | unsigned long long pad1 : 30; | ||
+ | unsigned long long pre : 1; | ||
+ | unsigned long long prim : 11; | ||
+ | unsigned long long flg : 2; | ||
+ | unsigned long long nreg : 4; | ||
+ | |||
+ | unsigned long long regs; | ||
+ | |||
+ | uint32 getRegisterID(uint32 i) const | ||
+ | { | ||
+ | assert(i < 16); | ||
+ | |||
+ | unsigned long long shiftPos = i * 4; | ||
+ | |||
+ | return ( this->regs & ( 0xF << shiftPos ) ) >> shiftPos; | ||
+ | } | ||
+ | |||
+ | void setRegisterID(uint32 i, uint32 regContent) | ||
+ | { | ||
+ | assert(i < 16); | ||
+ | |||
+ | unsigned long long shiftPos = i * 4; | ||
+ | |||
+ | this->regs &= ~( 0xF << shiftPos ); | ||
+ | |||
+ | this->regs |= regContent >> shiftPos; | ||
+ | } | ||
+ | };</source> | ||
+ | |||
+ | By default, each texture that is made of GIF packets has the '''TRXPOS''', '''TRXREG''' and '''TRXDIR''' registers including the '''TEX0''', '''TEX1''', '''MIPTBP1''' and '''MIPTBP2''' registers from the native header. This practically makes the image and palette headers look like this. | ||
+ | |||
+ | <source lang="cpp"> | ||
+ | // Note: this is just an illustration, not an actual header. | ||
+ | struct commonTexelHeader | ||
+ | { | ||
+ | GIFtag regListTag; | ||
+ | /* | ||
+ | regListTag.flg = 0; | ||
+ | regListTag.eop = false; | ||
+ | regListTag.pre = false; | ||
+ | regListTag.prim = 0; | ||
+ | regListTag.nreg = 1; | ||
+ | regListTag.regs = 0; | ||
+ | regListTag.setRegisterID(0, 0xE); | ||
+ | regListTag.pad1 = 0; | ||
+ | regListTag.nloop = 3; | ||
+ | */ | ||
+ | |||
+ | unsigned long long trxpos; // ssax = 0, ssay = 0, dsax = ?, dsay = ?, dir = 0 | ||
+ | unsigned long long trxpos_id; // = 0x51 | ||
+ | |||
+ | unsigned long long trxreg; // width = ?, height = ? | ||
+ | unsigned long long trxreg_id; // = 0x52 | ||
+ | |||
+ | unsigned long long trxdir; // xdir = 0 | ||
+ | unsigned long long trxdir_id; // = 0x53 | ||
+ | |||
+ | GIFtag imgDataTag; | ||
+ | /* | ||
+ | imgDataTag.flg = 2; | ||
+ | imgDataTag.eop = false; | ||
+ | imgDataTag.pre = false; | ||
+ | imgDataTag.prim = 0; | ||
+ | imgDataTag.nreg = 0; | ||
+ | imgDataTag.regs = 0; | ||
+ | imgDataTag.pad1 = 0; | ||
+ | imgDataTag.nloop = (size of image data in bytes / 16); | ||
+ | */ | ||
+ | }; | ||
+ | </source> | ||
+ | For more details, see the [https://www.dropbox.com/s/onjaprt82y81sj7/EE_Users_Manual.pdf Emotion Engine User Manual] under the GIF Interface chapter. | ||
+ | |||
+ | The actual image data is stored afterward. It is packed in either PSMCT32, PSMCT16, PSMT8 or PSMT4 internal GS types according to the documentation and game version. So before you can get usable images in linear format out of PS2 textures, you have to "unswizzle" them. This is done by permuting the data. | ||
+ | |||
+ | Until San Andreas (rwver 3.6.0.3) all rasters come with predefined texture base pointers. That means that textures are statically mapped into GS memory. For San Andreas though the engine is sophisticated enough to dynamically map the textures during runtime (texture base pointers are zero). | ||
+ | |||
+ | {{N|SA|VC|3}} |
Revision as of 12:03, 11 September 2020
Texture Native | |
---|---|
RenderWare Stream Section | |
Vendor | Criterion Games |
Module | Core |
Module ID | 0x000000
|
Identifier | 0x15
|
Chunk ID | 0x00000015
|
Versions | All |
Hierarchy | |
Parents: Texture Dictionary | |
Children: Struct | |
Extensions: Sky MipMap Val | |
File Format |
Texture Native is a container section used in TXD files as child of a Texture Dictionary section. It is normally accompanied by a Struct section.
Contents
Binary Structure
The Texture Native's [[Struct (RW Section)|Struct] sections' contents differs between PC, XBOX and PS2 versions of the GTA III game trilogy.
Direct3D Header
There is a 86 byte header with general image information.
The name and maskName fields are strings that are expected to be zero terminated. This allows a maximum string length of 31 bytes (last byte taken by '\0').
struct NativeTexturePC_Header
{
struct {
unsigned int platformId;
unsigned int filterMode : 8;
unsigned int uAddressing : 4;
unsigned int vAddressing : 4;
unsigned int pad : 16; // should be zeroed out for sanity
char name[32];
char maskName[32];
} TextureFormat;
struct {
unsigned int rasterFormat;
union {
D3DFORMAT d3dFormat; // SA, see D3DFORMAT on MSDN
unsigned int hasAlpha // GTA3 & VC
};
unsigned short width;
unsigned short height;
unsigned char depth;
unsigned char numLevels;
unsigned char rasterType;
union {
unsigned char compression; // GTA3 & VC
struct { // SA
unsigned char alpha : 1;
unsigned char cubeTexture : 1;
unsigned char autoMipMaps : 1;
unsigned char compressed : 1; // if true, raster may not be defined in original RW (compressed?)
unsigned char pad : 4; // recommended to be zero'ed out
};
};
} RasterFormat;
};
PlayStation 2 Header
The PlayStation 2 architecture uses header data of dynamic size. While the name and maskName fields are 32 bytes in length each on the Direct3D architecture, here they can be any size. It is not recommended to overshoot 32 bytes though, as there will be cross-platform compatibility issues. Do not forget to append '\0' for zero termination!
It is important to align all data to 4 bytes, since the PlayStation 2 implementation may fail to read your data properly otherwise.
struct NativeTexturePS2_Header
{
struct {
unsigned int platformId;
unsigned int filterMode : 8;
unsigned int uAddressing : 4;
unsigned int vAddressing : 4;
unsigned int pad : 16; // should be zero'ed out for sanity
} TextureFormat;
// CHUNK_STRING for name (aligned to 4 bytes)
// CHUNK_STRING for alpha-mask name (aligned to 4 bytes)
//size: 64 bytes
// Wrapped in a CHUNK_STRUCT
struct RasterFormat
{
unsigned int width, height;
unsigned int depth;
unsigned int rasterFormat;
// See https://www.dropbox.com/s/4wfvq86qk3h6v0n/GS_Users_Manual.pdf
unsigned long long tex0; // PS2 TEX0 GS register
unsigned long long tex1; // PS2 TEX1 GS register
unsigned long long miptbp1; // PS2 MIPTBP1 GS register
unsigned long long miptbp2; // PS2 MIPTBP2 GS register
unsigned int texelDataSectionSize; // stream size of the mipmap data
unsigned int paletteDataSectionSize; // stream size of the palette data (zero if no palette)
unsigned int gpuDataAlignedSize; // memory span of the texture mipmap data on the GS (aligned to pages/2048)
unsigned int skyMipmapVal; // always 4032
};
};
Description
- Platform ID
- This is 8 for GTA3 and VC on the PC, 9 for GTA SA on the PC, PS2\0 FourCC on PS2 (any game version), and 5 for GTA3/VC/SA on the XBOX.
- Filter Mode
FILTER_NONE 0x00 FILTER_NEAREST 0x01 FILTER_LINEAR 0x02 FILTER_MIP_NEAREST 0x03 FILTER_MIP_LINEAR 0x04 FILTER_LINEAR_MIP_NEAREST 0x05 FILTER_LINEAR_MIP_LINEAR 0x06
- Addressing Mode
WRAP_NONE 0x00 WRAP_WRAP 0x01 WRAP_MIRROR 0x02 WRAP_CLAMP 0x03
- Raster Format
FORMAT_DEFAULT 0x0000 FORMAT_1555 0x0100 (1 bit alpha, RGB 5 bits each; also used for DXT1 with alpha) FORMAT_565 0x0200 (5 bits red, 6 bits green, 5 bits blue; also used for DXT1 without alpha) FORMAT_4444 0x0300 (RGBA 4 bits each; also used for DXT3) FORMAT_LUM8 0x0400 (gray scale, D3DFMT_L8) FORMAT_8888 0x0500 (RGBA 8 bits each) FORMAT_888 0x0600 (RGB 8 bits each, D3DFMT_X8R8G8B8) FORMAT_555 0x0A00 (RGB 5 bits each - rare, use 565 instead, D3DFMT_X1R5G5B5) FORMAT_EXT_AUTO_MIPMAP 0x1000 (RW generates mipmaps, see special section below) FORMAT_EXT_PAL8 0x2000 (2^8 = 256 palette colors) FORMAT_EXT_PAL4 0x4000 (2^4 = 16 palette colors) FORMAT_EXT_MIPMAP 0x8000 (mipmaps included)
For verification of Direct3D 9 mapping, see disassembly offset 0x0085C670 in GTA:SA 1.0.
- Depth
- 4, 8, 16 or 32; 4 and 8 usually come with palette
- Compression
- for more information, refer to S3 Texture Compression.
Raster Data
The header is followed by an optional palette and the raster data (pixels), according to the Raster Format flags.
Raster using Palette
If Raster Format is FORMAT_EXT_PAL8 | FORMAT_8888
or FORMAT_EXT_PAL8 | FORMAT_888
, then a color palette of (up to) 256 RGBA colors is included. This is used in GTA3 quite often. The raster's one byte for each pixel is an index into the palette.
Here is some pseudo-code that shows the layout of the raster data if the texture uses a palette.
// paletteSize can have many values:
// - 16 for FORMAT_EXT_PAL4 on PS2 (rwver: 3.0.0.0 - (below) 3.6.0.3, III and VC)
// - 24 for FORMAT_EXT_PAL4 on PS2 (rwver: 3.6.0.3, SA)
// - 32 for FORMAT_EXT_PAL4 on XBOX (PC?)
// - 256 for FORMAT_EXT_PAL8 on any configuration
colorType_t colors[ paletteSize ]; // palette color entries, colorType_t is defined by depth and raster format
repeated for each mipmap:
{
// mipWidth is the texture width, halfed for each level
// mipHeight is the texture height, halfed for each level
unsigned int rasterSize; // size of raster data in bytes, see Texture Data Layout section
indexType_t paletteIndice[ mipWidth * mipHeight ]; // palette indice into the colors array.
}
NOTE: palette colors use RGBA color order!
Depending on the depth, the palette index can be 4 or 8 bits wide. The PlayStation 2 architecture expects a 4 bits wide palette index for FORMAT_EXT_PAL4
while the XBOX/PC expect a 8 bits wide palette index.
Raster without palette
If no palette is included, the header is followed by raster size and data. If mipmaps are included (FORMAT_EXT_MIPMAP
) then this is repeated for each mipmap. With increasing mipmap levels the raster size is decreased by the factor 4 (side lengths halved). Small initial textures can result in mipmaps of the size 0. For uncompressed images:
repeated for each mipmap:
{
// mipWidth is the texture width, halfed for each level
// mipHeight is the texture height, halfed for each level
unsigned int rasterSize; // size of raster data in bytes, see Texture Data Layout section
colorType_t colors[ mipWidth * mipHeight ];
}
WARNING: FORMAT_888(8) uses BGRA color order!
If DXT compression is used, blocks of 4x4 pixels occupy only 8 or 16 bytes (depending on the type).
Special Direct3D 9 Rasters
By setting the isNotRWCompatible
field to true in the Direct3D 9 native texture, the GTA:SA engine will use the D3DFORMAT field instead of the RenderWare raster format definition. This allows you to load any Direct3D 9 texture into that engine at the expense of compatibility between different RenderWare implementations.
Originally this feature has been used to ship DXT compressed textures with the game. That is why this field is historically known as "compression flag".
Texture Data Layout
How the texel data is placed in the color buffer depends on the platform. The Direct3D 8/9 architecture places colors in byte-aligned scanlines that are arranged in an array. Very often this storage format looks exactly like a width x height array of colors but you must be careful here. It starts to make a difference for correctly generating mipmaps or NPOT textures.
In the special case of Direct3D, Criterion defined the row byte-alignment to 4 bytes. This is a universally accepted standard alignment, originating from the beginnings of the DirectDraw Surface format. Long story short, many things went wrong and people assumed too many things. In the end, Criterion created a texture loader that violated the Direct3D specification.
Known color types
Here is a list of known color types that can be used in the palette or the image arrays. Any of these can map to the colorType_t type depending on the parameters of the texture.
// Known color types (in BGRA order):
//FORMAT_8888, depth 32 (D3DFMT_A8R8G8B8)
struct format8888_depth32
{
unsigned char b, g, r, a;
};
//FORMAT_565, depth 16 (D3DFMT_R5G6B5)
struct format565_depth16
{
unsigned short blue : 5;
unsigned short green : 6;
unsigned short red : 5;
};
//FORMAT_4444, depth 16 (D3DFMT_A4R4G4B4)
struct format4444_depth16
{
unsigned short blue : 4;
unsigned short green : 4;
unsigned short red : 4;
unsigned short alpha : 4;
};
//FORMAT_888, depth 32 (D3DFMT_X8R8G8B8)
struct format888_depth32
{
unsigned char b, g, r;
unsigned char unused;
};
//FORMAT_888, depth 24 (D3DFMT_R8G8B8)
struct format888_depth24
{
unsigned char b, g, r;
};
Color Ordering
The order of the RGBA colors depends on the platform.
- PlayStation 2 uses RGBA colors
- Direct3D 9 uses BGRA or RGBA depending on the D3DFORMAT field
For example, let's investigate how to convert color formats from PS2 to Direct3D. Since FORMAT_8888
always is RGBA on the PlayStation 2, the color struct should look like this.
struct format8888_texel_rgba
{
uint8 red;
uint8 green;
uint8 blue;
uint8 alpha;
};
Now we have two choices.
- Use D3DFMT_A8B8G8R8 so the texel data is correctly ordered already, but with less compatibility with GTA community tools.
- Use D3DFMT_A8R8G8B8 and swap red with blue.
If we go for the latter, the new color struct should look like this.
struct format8888_texel_bgra
{
uint8 blue;
uint8 green;
uint8 red;
uint8 alpha;
};
The conversion between RGBA to BGRA can be performed using the following algorithm.
void convertRGBA2BGRA(void *theTexels, uint32 texelCount)
{
for (uint32 n = 0; n < texelCount; n++)
{
uint8 r, g, b, a;
// Read the color from the texels.
{
format8888_texel_rgba *rgba8888 = (format8888_texel_rgba*)theTexels + n;
r = rgba8888->red;
g = rgba8888->green;
b = rgba8888->blue;
a = rgba8888->alpha;
}
// Write back the color in a different ordering.
{
format8888_texel_bgra *bgra8888 = (format8888_texel_bgra*)theTexels + n;
bgra8888->red = r;
bgra8888->green = g;
bgra8888->blue = b;
bgra8888->alpha = a;
}
}
}
Mipmapping
The only engine that supports rendering mipmaps is GTA:SA. Mipmaps are essential to improve the visual quality of your models when they are rendered from a far distance. Every modification should either come with custom mipmaps or have the automatic mipmap generation flag enabled.
If automatic mipmap generation is enabled (FORMAT_EXT_AUTO_MIPMAP
), then the texture must not come with any additional mipmap layers. Otherwise the TXD archive will fail to load.
Shipping mipmaps with the TXD files reduces the CPU/GPU load of the game, since they do not have to be generated during runtime.
On the PlayStation 2 architecture, all textures that ship with custom mipmaps also have the FORMAT_EXT_AUTO_MIPMAP
flag enabled. This could have been an oversight and points to the fact that the PS2 does not support generating mipmaps.
PlayStation 2
The image and palette data is both handled in a (generic) texture format. There are special raster flags reserved.
#define RASTER_SWIZZLED 0x10000 // the images are "swizzled"
#define RASTER_HAS_TEXEL_HEADERS 0x20000 // the image data is preceeded by headers
If the rasterFlags contain the RASTER_HAS_TEXEL_HEADERS
flag, then each image data and palette data are a chain of GIF packets that are directly uploaded to the GS. Each mipmap consists of one register list and the image data. The GIFtag struct that is the header of each packet could look like this.
struct GIFtag
{
unsigned long long nloop : 15;
unsigned long long eop : 1;
unsigned long long pad1 : 30;
unsigned long long pre : 1;
unsigned long long prim : 11;
unsigned long long flg : 2;
unsigned long long nreg : 4;
unsigned long long regs;
uint32 getRegisterID(uint32 i) const
{
assert(i < 16);
unsigned long long shiftPos = i * 4;
return ( this->regs & ( 0xF << shiftPos ) ) >> shiftPos;
}
void setRegisterID(uint32 i, uint32 regContent)
{
assert(i < 16);
unsigned long long shiftPos = i * 4;
this->regs &= ~( 0xF << shiftPos );
this->regs |= regContent >> shiftPos;
}
};
By default, each texture that is made of GIF packets has the TRXPOS, TRXREG and TRXDIR registers including the TEX0, TEX1, MIPTBP1 and MIPTBP2 registers from the native header. This practically makes the image and palette headers look like this.
// Note: this is just an illustration, not an actual header.
struct commonTexelHeader
{
GIFtag regListTag;
/*
regListTag.flg = 0;
regListTag.eop = false;
regListTag.pre = false;
regListTag.prim = 0;
regListTag.nreg = 1;
regListTag.regs = 0;
regListTag.setRegisterID(0, 0xE);
regListTag.pad1 = 0;
regListTag.nloop = 3;
*/
unsigned long long trxpos; // ssax = 0, ssay = 0, dsax = ?, dsay = ?, dir = 0
unsigned long long trxpos_id; // = 0x51
unsigned long long trxreg; // width = ?, height = ?
unsigned long long trxreg_id; // = 0x52
unsigned long long trxdir; // xdir = 0
unsigned long long trxdir_id; // = 0x53
GIFtag imgDataTag;
/*
imgDataTag.flg = 2;
imgDataTag.eop = false;
imgDataTag.pre = false;
imgDataTag.prim = 0;
imgDataTag.nreg = 0;
imgDataTag.regs = 0;
imgDataTag.pad1 = 0;
imgDataTag.nloop = (size of image data in bytes / 16);
*/
};
For more details, see the Emotion Engine User Manual under the GIF Interface chapter.
The actual image data is stored afterward. It is packed in either PSMCT32, PSMCT16, PSMT8 or PSMT4 internal GS types according to the documentation and game version. So before you can get usable images in linear format out of PS2 textures, you have to "unswizzle" them. This is done by permuting the data.
Until San Andreas (rwver 3.6.0.3) all rasters come with predefined texture base pointers. That means that textures are statically mapped into GS memory. For San Andreas though the engine is sophisticated enough to dynamically map the textures during runtime (texture base pointers are zero).