aboutsummaryrefslogtreecommitdiff
path: root/docs/specs/texture.md
blob: c25ec5647d21ba580a10ee4e88c1f38f07aec8aa (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
# Texture (`Texm`)

`Texm` — основной формат текстур движка.

Связанные страницы:

- [Material (`MAT0`)](material.md)
- [Wear table (`WEAR`)](wear.md)
- [Render pipeline](render.md)

## 1. Контейнер

- Тип ресурса: `0x6D786554` (`Texm`).
- Используется в `Textures.lib`, `LightMap.lib` и других `NRes` архивах.

## 2. Заголовок

```c
struct TexmHeader32 {
    uint32_t magic;    // 'Texm'
    uint32_t width;
    uint32_t height;
    uint32_t mipCount;
    uint32_t flags4;
    uint32_t flags5;
    uint32_t unk6;
    uint32_t format;
};
```

## 3. Поддерживаемые форматы

Базовые форматы:

- `0` (8-bit indexed + palette)
- `565`
- `4444`
- `888`
- `8888`

Дополнительные ветки загрузки поддерживают также `556` и `88`.

## 4. Layout payload

1. `TexmHeader32` (32 байта)
2. palette `1024` байта, если `format == 0`
3. mip-chain пикселей
4. optional `Page` chunk

Расчёт ядра:

```c
bytesPerPixel =
    (format == 0) ? 1 :
    (format == 565 || format == 556 || format == 4444 || format == 88) ? 2 :
    4;

pixelCount = sum(max(1, width>>i) * max(1, height>>i), i=0..mipCount-1);
sizeCore = 32 + (format==0 ? 1024 : 0) + bytesPerPixel * pixelCount;
```

## 4.1. Декодирование в RGBA8 (runtime/инструменты)

Для CPU-пути (preview, валидация, оффлайн-конвертация) используется декодирование:

- `0` (`Indexed8`): `index -> palette[index]` (`RGBA` из палитры 256×4).
- `565`: `R5 G6 B5`, `A=255`.
- `556`: `R5 G5 B6`, `A=255`.
- `4444`: `A4 R4 G4 B4` (с расширением 4-битных каналов в 8-битные).
- `88`: `L8 A8` (`R=G=B=L`).
- `888`: `R8 G8 B8` + padding/служебный байт, `A=255`.
- `8888`: `A8 R8 G8 B8`.

Это декодирование соответствует текущему test/demo pipeline проекта.

## 5. `Page` chunk

```c
struct PageChunk {
    uint32_t magic;      // 'Page'
    uint32_t rectCount;
    Rect16   rects[rectCount];
};

struct Rect16 {
    int16_t x;
    int16_t w;
    int16_t y;
    int16_t h;
};
```

`Page` задаёт atlas-прямоугольники для выборки под-областей текстуры.

## 6. Mip-skip политика

Загрузчик может пропускать первые mip-уровни в зависимости от:

- `flags5`,
- размеров текстуры,
- количества mip.

После `mipSkip`:

- уменьшаются `width/height/mipCount`;
- сдвигается начало пиксельных данных;
- `Page`-координаты пересчитываются в соответствии с новым базовым уровнем.

## 7. Палитры

Для части текстур движок связывает палитру по суффиксу имени.

Практический формат:

- буква `A..Z` + вариант `""` или `0..9`
- всего `26 * 11 = 286` возможных слотов палитр.

Невалидные суффиксы нужно считать ошибкой входных данных в инструментах.

## 8. Кэширование

Движок ведёт отдельные кэши:

- общий texture cache;
- lightmap cache.

Для обычных текстур используется отложенный сбор неиспользуемых слотов (по времени нулевого refcount).

## 9. Правила writer/editor

1. Не нормализовать `flags4/flags5/unk6`.
2. Сохранять payload без лишних хвостовых байт.
3. Если есть `Page`, его размер должен быть ровно `8 + rectCount * 8`.
4. Проверять `width > 0`, `height > 0`, `mipCount > 0`.

## 10. Статус валидации

- Инварианты `Texm` реализованы в `tools/msh_doc_validator.py`.
- В текущем окружении нет полного игрового набора текстур в `testdata`, поэтому массовая перепроверка не запускалась.