Texture Native (Секция RW)

Материал из GTAModding.ru
Версия от 00:32, 11 апреля 2014; DK22 (обсуждение | вклад)

(разн.) ← Предыдущая | Текущая версия (разн.) | Следующая → (разн.)
Перейти к: навигация, поиск
Секция RenderWare
Texture Native
0x0015

Texture Native - секция-контейнер, используемая в TXD-файлах внутри секции Texture Dictionary. Как правило, одна или несколько таких секций, следуют после секции Struct внутри контейнера Texture Dictionary.

Содержание

PC / XBOX

  • Texture_Native
  • Struct - заголовок и данные изображения
  • Extension - пусто

Общая структура

Структура начинается с т.н. заголовка размером в 92 байт. Заголовок содержит общую информацию о текстуре и растре.

struct NativeTexturePC_Header
{
    struct {
        unsigned int platformId;
        unsigned int filterMode : 8;
        unsigned int uAddressing : 4;
        unsigned int vAddressing : 4;
        char name[32];
        char maskName[32];
    } TextureFormat;
    struct {
        unsigned int rasterFormat;
        union {
            D3DFORMAT d3dFormat; // SA
            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;
            };
        };
    } RasterFormat;
};

После заголовка следует информация о палитре (если изображение использует палитру). Дальше - информация о каждой плоскости (допустим, если изображение использует мип-маппинг или является кубической текстурой) - размер и данные о текселах. Информация о плоскости начинается с 4 байт - размере данных о текселах.

struct TextureFormat
{
    unsigned int platformId;
    unsigned int filterMode : 8;
    unsigned int uAddressing : 4;
    unsigned int vAddressing : 4;
    char name[32];
    char maskName[32];
};

struct RasterFormat
{
    unsigned int rasterFormat;
    D3DFORMAT d3dFormat;
    unsigned short width;
    unsigned short height;
    unsigned char depth;
    unsigned char numLevels;
    unsigned char rasterType;
    unsigned char alpha : 1;
    unsigned char cubeTexture : 1;
    unsigned char autoMipMaps : 1;
    unsigned char compressed : 1; 
};

VALIDATE_SIZE(TextureFormat, 72);
VALIDATE_SIZE(RasterFormat, 16);

enum RasterFormat8
{
    FORMATDEFAULT = 0x00,
    FORMAT1555 = 0x01,
    FORMAT565 = 0x02,
    FORMAT4444 = 0x03,
    FORMATLUM8 = 0x04,
    FORMAT8888 = 0x05,
    FORMAT888 = 0x06,
    FORMAT16 = 0x07,
    FORMAT24 = 0x08,
    FORMAT32 = 0x09,
    FORMAT555 = 0x0a,
    FORMATAUTOMIPMAP = 0x10,
    FORMATPAL8 = 0x20,
    FORMATPAL4 = 0x40,
    FORMATMIPMAP = 0x80,
    FORMATFORCEENUMSIZEINT = RWFORCEENUMSIZEINT
};

bool rwD3D9NativeTextureRead(RwStream *stream, RwTexture **textureOut)
{
    TextureFormat textureInfo; RasterFormat rasterInfo;
    unsigned int lengthOut, versionOut, numLevels; unsigned char savedFormat;
    RwRaster *raster; RwTexture *texture;

    // Ошибка чтения или некорректная версия
    if (!RwStreamFindChunk(stream, rwID_STRUCT, &lengthOut, &versionOut) || versionOut < 0x34000 || versionOut > 0x36003 || 
        RwStreamRead(stream, &textureInfo, 72) != 72 || textureInfo.platformId != rwID_PCD3D9 || 
        RwStreamRead(stream, &rasterInfo, 16) != 16)
        return false;

    // Рассматриваем 3 варианта - компрессия, кубическая текстура, компрессия+кубическая текстура
    if(rasterInfo.compressed)
    {
        if(rwD3D9CheckValidTextureFormat(rasterInfo.d3dFormat))
        {
            raster = RwRasterCreate(rasterInfo.width, rasterInfo.height, rasterInfo.depth, rasterInfo.rasterFormat | 
                rasterInfo.rasterType | rwRASTERDONTALLOCATE);
            if(!raster)
                return false;
        }
        else return false;
        numLevels = (raster->cFormat & FORMATMIPMAP) ? rasterInfo.numLevels : 1;
        if(rasterInfo.cubeTexture)
        {
            if(!(RwD3D9DeviceCaps->CubeTextureFilterCaps & (D3DPTFILTERCAPS_MIPFPOINT|D3DPTFILTERCAPS_MIPFLINEAR)) && numLevels > 1)
            {
                RwRasterDestroy(raster);
                return false;
            }
            raster->d3dRaster.isCubeMap = 1;
            if((rasterInfo.rasterFormat & rwRASTERFORMATMIPMAP) && (rasterInfo.rasterFormat & rwRASTERFORMATAUTOMIPMAP) && 
                rwD3D9CheckAutoMipmapGenCubeTextureFormat(rasterInfo.d3dFormat))
                raster->d3dRaster.autoMipMapping = 1;
            if(RwD3DDevice->CreateCubeTexture(raster->width, numLevels, raster->d3dRaster.autoMipMapping 
                ? D3DUSAGE_AUTOGENMIPMAP : 0, rasterInfo.d3dFormat, D3DPOOL_MANAGED, &raster->d3dRaster.cubeTexture, NULL) != D3D_OK)
            {
                RwRasterDestroy(raster);
                return false;
            }
            raster->d3dRaster.isCubeMap = 1; // ??
        }
        else
        {
            if((rasterInfo.rasterFormat & rwRASTERFORMATMIPMAP) && (rasterInfo.rasterFormat & rwRASTERFORMATAUTOMIPMAP) && 
                rwD3D9CheckAutoMipmapGenCubeTextureFormat(rasterInfo.d3dFormat))
                raster->d3dRaster.autoMipMapping = 1;
            if(gtaD3D9TexCreate(raster->width, raster->height, numLevels, rasterInfo.d3dFormat, &raster->d3dRaster.texture) != D3D_OK)
            {
                RwRasterDestroy(raster);
                return false;
            }
        }
        raster->d3dRaster.hasCompression = 1;
        raster->d3dRaster.format = rasterInfo.d3dFormat;
    }
    else
    {
        if(!rasterInfo.cubeTexture)
        {
            if((rasterInfo.rasterFormat & rwRASTERFORMATMIPMAP) && (rasterInfo.rasterFormat & rwRASTERFORMATAUTOMIPMAP))
            {
                raster = RwD3D9RasterCreate(rasterInfo.width, rasterInfo.height, rasterInfo.d3dFormat, rasterInfo.rasterType | 
                    (rasterInfo.rasterFormat & (rwRASTERFORMATMIPMAP|rwRASTERFORMATAUTOMIPMAP)));
                if(!raster)
                    return false;
            }
            else
            {
                raster = RwRasterCreate(rasterInfo.width, rasterInfo.height, rasterInfo.depth,
                   rasterInfo.rasterFormat | rasterInfo.rasterType);
                if(!raster)
                    return false;
                raster->d3dRaster.hasCompression = 0; // Зачем??
            }
        }
        else
        {
            raster = RwRasterCreate(rasterInfo.width, rasterInfo.height, rasterInfo.depth, rasterInfo.rasterFormat | 
                rasterInfo.rasterType | rwRASTERDONTALLOCATE);
            if(!raster)
                return false;

            numLevels = (raster->cFormat & FORMATMIPMAP) ? rasterInfo.numLevels : 1;
            if(!(RwD3D9DeviceCaps->CubeTextureFilterCaps & (D3DPTFILTERCAPS_MIPFPOINT|D3DPTFILTERCAPS_MIPFLINEAR)) && numLevels > 1)
            {
                RwRasterDestroy(raster);
                return false;
            }
            raster->d3dRaster.isCubeMap = 1;
            if((rasterInfo.rasterFormat & rwRASTERFORMATMIPMAP) && (rasterInfo.rasterFormat & rwRASTERFORMATAUTOMIPMAP) && 
                rwD3D9CheckAutoMipmapGenCubeTextureFormat(rasterInfo.d3dFormat))
                raster->d3dRaster.autoMipMapping = 1;
            if(RwD3DDevice->CreateCubeTexture(raster->width, numLevels, raster->d3dRaster.autoMipMapping 
                ? D3DUSAGE_AUTOGENMIPMAP : 0, rasterInfo.d3dFormat, D3DPOOL_MANAGED, &raster->d3dRaster.cubeTexture, NULL) != D3D_OK)
            {
                RwRasterDestroy(raster);
                return false;
            }
            raster->d3dRaster.isCubeMap = 1;
        }
    }
    raster->cFlags ^= rwRASTERDONTALLOCATE;
    raster->d3dRaster.hasAlpha = rasterInfo.alpha;

    // Если у нас что-то не совпало - ну его...
    if(rasterInfo.autoMipMaps && !raster->d3dRaster.autoMipMapping || rasterInfo.rasterFormat != ((raster->cFormat) << 8) || 
        rasterInfo.d3dFormat != raster->d3dRaster.format || rasterInfo.width != raster->width || rasterInfo.height != raster->height)
    {
        RwRasterDestroy(raster);
        return false;
    }

    // Читаем палитру, если надо
    if(rasterInfo.rasterFormat & rwRASTERFORMATPAL4)
    {
        if( RwStreamRead(stream, RwRasterLockPalette(raster, rwRASTERLOCKWRITE), 128) != 128)
        {
            RwRasterUnlockPalette(raster);
            RwRasterDestroy(raster);
            return false;
        }
    }
    else if(rasterInfo.rasterFormat & rwRASTERFORMATPAL8)
    {
        if(RwStreamRead(stream, RwRasterLockPalette(raster, rwRASTERLOCKWRITE), 1024) != 1024)
        {
            RwRasterUnlockPalette(raster);
            RwRasterDestroy(raster);
            return false;
        }
    }
    RwRasterUnlockPalette(raster);
    
    // Читаем сорфейсы
    savedFormat = raster->cFormat; // Зачем-то выключаем автомипмаппинг на время чтения
    raster->cFormat ^= FORMATAUTOMIPMAP;
    for(int i = 0; i < (raster->d3dRaster.cubeTexture ? 6 : 1); i++)
    {
        raster->d3dRaster.selectedCubeFace = 0;
        for(int j = 0; j < (raster->d3dRaster.autoMipMapping ? 1 : rasterInfo.numLevels); j++)
        {
            if(RwStreamRead(stream, &lengthOut, 4) == 4)
            {
                if(RwStreamRead(stream, RwRasterLock(raster, j, rwRASTERLOCKWRITE), lengthOut) == lengthOut)
                {
                    RwRasterUnlock(raster);
                    continue;
                }
            }
            RwRasterUnlock(raster);
            RwRasterDestroy(raster);
            return false;
        }
    }
    raster->cFormat = savedFormat;

    texture = RwTextureCreate(raster);
    if(!texture)
    {
        RwRasterDestroy(raster);
        return false;
    }
    texture->filter = textureInfo.filterMode;
    texture->uAddressing = textureInfo.uAddressing;
    texture->vAddressing = textureInfo.vAddressing;
    RwTextureSetName(texture, textureInfo.name);
    RwTextureSetMaskName(texture, textureInfo.maskName);
    *textureOut = texture;
    return true;
}

PlayStation 2

  • Texture Native
  • Struct - 4 байта - Платформа (PS2), 4байта - Флаги Фильтра
  • String - Название текстуры (размер кратен 4 байтам)
  • String - Название альфа-канала (размер кратен 4 байтам)
  • Struct
  • Struct - 4 байта ширина, 4 байта - высота, 4 байта - глубина цвета (bpp), далее - неизвестно
  • Struct - 80 байт - информация о изображении, растр, (80 байт - информация о палитре), палитра

Информация ниже находится на этапе эксперементирования и проверки, а, следовательно, может оказаться неточной.

Платформа и фильтр

Информация о платформе и фильтрах хранится в стандартной 8-байтовой структуре:

BYTE[4] - 4 байта - Строка "PS2" с завершающим нулём
DWORD   - 4 байта - Флажки фильтра и UV Wrapping

Флажки фильтра (RwTextureFilterMode) предствалены ниже.
Использовать можно только один из фильтров.

0x01  rwFILTERNEAREST            - Point sampled
0x02  rwFILTERLINEAR             - Bilinear
0x03  rwFILTERMIPNEAREST         - Point sampled per pixel mip map
0x04  rwFILTERMIPLINEAR          - Bilinear per pixel mipmap
0x05  rwFILTERLINEARMIPNEAREST   - MipMap interp point sampled
0x06  rwFILTERLINEARMIPLINEAR    - Trilinear

По всей видимости, флажки UV на этапе разработки могут занимать по байту каждый, но в конечной версии используется лишь 1 байт под обе координаты. Можно добавить, что лишь пара-тройка текстур имеет этот байт отличный от 0x11.

Ниже предсталены флажки UV. По идее, wrapping может быть не использован вообще - т.е. 0x00.

0x00  rwTEXTUREADDRESSNATEXTUREADDRESS - ??? (вроде как N/A)
0x01  rwTEXTUREADDRESSWRAP       - UV wraps (tiles)
0x02  rwTEXTUREADDRESSMIRROR     - Alternate UV is flipped
0x03  rwTEXTUREADDRESSCLAMP      - UV is clamped to 0-1
0x04  rwTEXTUREADDRESSBORDER     - Border color takes effect outside of 0-1

Основная структура информации

Здесь содержится вся или практически вся информация об изображении.

DWORD   - 4 байта - Ширина изображения в пикселях
DWORD   - 4 байта - Высота изображения в пикселях
DWORD   - 4 байта - Глубина цвета (bpp)
DWORD   - 2 байта - Флаги изображения
BYTE[7] - 7 байт  - неизвестно
BYTE    - 1 байт  - Тип палитры или что-то в этом роде (всегда равно 0x32, если палитры нет - всегда ноль и последующие 8 байт тоже нули)
WORD[4] - 8 байт  - Флаги палитры (?) (TODO)
...
DWORD - 4 байта - дубль секции SkyMipMapVal, на деле - оба значения всегда равны 0x0FC0 (TODO)

Флаги изображения

Это обычные флаги RenderWare Graphics, соеденённые в двойном слове (DWORD) при помощи побитового AND:

  • RwRasterFormat (Как правило, встречаются лишь RGBA, 8bit pal и 4bit pal, но приведу остальные, которые могут встретится):
Одновременно может использоваться только один флаг из каждой "группы" (группы флажков тут условно разделены пустой строкой).
0x0100  rwRASTERFORMAT1555        - 16 бит  - 1 бит альфа, по 5 бит на красный, зелёный и синий (5-5-5, только первый бит - альфа)
0x0200  rwRASTERFORMAT565         - 16 бит  - 5 бит - красный, 6 бит  - зелёный, 5 бит - синий (5-6-5)
0x0300  rwRASTERFORMAT4444        - 16 бит  - по 4 бита на красный, зелёный и синий
0x0400  rwRASTERFORMATLUM8        -  8 бит  - белый (красный = зелёный = синий)
0x0500  rwRASTERFORMAT8888        - 32 бита - 8 bits per component
0x0600  rwRASTERFORMAT888         - 24 бита - 8 bits per component
...
0x0A00  rwRASTERFORMAT555         - 16 бит  - по 5 бит на красный, зелёный и синий (обычный 5-5-5)

0x1000  rwRASTERFORMATAUTOMIPMAP  - RenderWare генерирует мипмапы автоматически
 
0x2000  rwRASTERFORMATPAL8        - 8 бит с палитрой (в этих двух случаях тип палитры указан с помощью флагов выше)
0x4000  rwRASTERFORMATPAL4        - 4 бит с палитрой
 
0x8000  rwRASTERFORMATMIPMAP      - Флаг наличия мипмапов.
  • RwRasterPrivateFlag:
Здесь я в данный момент не уверен, но если значение равно единице - нужно как-то корректировать палитру (или наоборот - не корректировать). Пока что привожу с оригинальными комментами разработчиков.
0x01  rwRASTERGAMMACORRECTED
0x02  rwRASTERPIXELLOCKEDREAD     - pixels are locked for reading
0x04  rwRASTERPIXELLOCKEDWRITE    - pixels are locked for writing
0x08  rwRASTERPALETTELOCKEDREAD   - palette is locked for reading
0x10  rwRASTERPALETTELOCKEDWRITE  - palette is locked for writing
0x20  rwRASTERPIXELLOCKEDRAW      - the pixels are in platform specific
  • Также, почти все (на практике - все) текстуры имеют флаги 0x020000 и 0x04. Второй из них и означает "текстура" (есть ещё камера и ещё чё-то). Первый отвечает за фоормат структуры, содержащей растр:
0x000000 - Swizzling не используется
0x010000 - Swizzling используется
0x020000 - Растр и палитра (и мипмапы) предваряются информационными блоками. Swizzling может не использоваться.

Первые два флага встречаются только в GTA 3, всегов нескольких файлах. В третьем флажке swizzling может использоваться (а может и нет) в зависимости от других флагов и/или информационных блоков.

Растр и палитра

Само изображение хранится в растровом формате, характерном для Sony PlayStation 2, так сокращаемом называемом TIM2. Заголовки изображения и файла не используется. (Подробнее о самом формате можно почитать в отдельной статье).
Практически всегда изображение, имеющее палитру, находится под swizzling'ом. Исключение составляют, например, 4-битные текстуры GTA 3. Алгоритм swizzling'а одинаков и для 4 и для 8 бит (по крайней мерев RenderWare). Текстуры, имеющие глубину цвета более 8 (т.е. беспалитровые 16, 32 и, в теории, 24-битные текстуры) не используют swizzling.
Определить наличие swizzling'а на данный момент удаётся лишь сравнивая размеры в структуре информации и в блоке информации (см. ниже).
В RenderWare начиная с версии 3.4 или 3.5 используются мипмапы. Мипмапы могут быть сгенерированы и сохранены заранее, наподобие того, как это часто используется в PC-версии San Andreas. Каждый мипмап имеет заголовок наподобие оригинального растра и использует ту же палитру. После растра и мипмапов идёт палитра.

Совет: удобней считать палитру, отсчитав её размер от конца структуры. Размер палитры равен 4 x 2depth.
Если текстура 4-битная и под swizzling'ом, то палитра содержит ещё 32 дополнительных байта!

Блоки информация об изображении / палитре (заголовок палитры / изображения)

Если флаг условно-swizzling'а равен 0x020000, то изображение, палитра и мипмапы (если есть) предваряется инфо-блоками размером 80 байт. В любом из этих случаев, используется схожая структура. Здесь всегда содержится информация об размере последующих данных (размер выражен в 16-байтовых блоках).
Исходя из этих данных можно узнать наличие swizzling'а (но это не основной признак его наличия): если высота и ширина изображения указаны в да раза меньше тех, что в основной структуре информации, то текстура находится под swizzling'ом. Как следствие, в этом способе есть проблемы с текстурами, разрешение которых хотя бы одним из параметров меньше или равно 16px.

Смотри также

  1. TXD - информация о TXD-архивах (Texture Dictionary)
  2. Texture Native Struct на gtamodding.com (англ.)