1/**
2 * Copyright (c) 2006-2023 LOVE Development Team
3 *
4 * This software is provided 'as-is', without any express or implied
5 * warranty. In no event will the authors be held liable for any damages
6 * arising from the use of this software.
7 *
8 * Permission is granted to anyone to use this software for any purpose,
9 * including commercial applications, and to alter it and redistribute it
10 * freely, subject to the following restrictions:
11 *
12 * 1. The origin of this software must not be misrepresented; you must not
13 * claim that you wrote the original software. If you use this software
14 * in a product, an acknowledgment in the product documentation would be
15 * appreciated but is not required.
16 * 2. Altered source versions must be plainly marked as such, and must not be
17 * misrepresented as being the original software.
18 * 3. This notice may not be removed or altered from any source distribution.
19 **/
20
21// LOVE
22#include "KTXHandler.h"
23#include "common/int.h"
24#include "common/Exception.h"
25
26// C
27#include <string.h>
28
29// C++
30#include <algorithm>
31
32namespace love
33{
34namespace image
35{
36namespace magpie
37{
38
39namespace
40{
41
42#define KTX_IDENTIFIER_REF {0xAB, 0x4B, 0x54, 0x58, 0x20, 0x31, 0x31, 0xBB, 0x0D, 0x0A, 0x1A, 0x0A}
43#define KTX_ENDIAN_REF (0x04030201)
44#define KTX_ENDIAN_REF_REV (0x01020304)
45#define KTX_HEADER_SIZE (64)
46
47struct KTXHeader
48{
49 uint8 identifier[12];
50 uint32 endianness;
51 uint32 glType;
52 uint32 glTypeSize;
53 uint32 glFormat;
54 uint32 glInternalFormat;
55 uint32 glBaseInternalFormat;
56 uint32 pixelWidth;
57 uint32 pixelHeight;
58 uint32 pixelDepth;
59 uint32 numberOfArrayElements;
60 uint32 numberOfFaces;
61 uint32 numberOfMipmapLevels;
62 uint32 bytesOfKeyValueData;
63};
64
65static_assert(sizeof(KTXHeader) == KTX_HEADER_SIZE, "Real size of KTX header doesn't match struct size!");
66
67enum KTXGLInternalFormat
68{
69 KTX_GL_ETC1_RGB8_OES = 0x8D64,
70
71 // ETC2 and EAC.
72 KTX_GL_COMPRESSED_R11_EAC = 0x9270,
73 KTX_GL_COMPRESSED_SIGNED_R11_EAC = 0x9271,
74 KTX_GL_COMPRESSED_RG11_EAC = 0x9272,
75 KTX_GL_COMPRESSED_SIGNED_RG11_EAC = 0x9273,
76 KTX_GL_COMPRESSED_RGB8_ETC2 = 0x9274,
77 KTX_GL_COMPRESSED_SRGB8_ETC2 = 0x9275,
78 KTX_GL_COMPRESSED_RGB8_PUNCHTHROUGH_ALPHA1_ETC2 = 0x9276,
79 KTX_GL_COMPRESSED_SRGB8_PUNCHTHROUGH_ALPHA1_ETC2 = 0x9277,
80 KTX_GL_COMPRESSED_RGBA8_ETC2_EAC = 0x9278,
81 KTX_GL_COMPRESSED_SRGB8_ALPHA8_ETC2_EAC = 0x9279,
82
83 // PVRTC1.
84 KTX_GL_COMPRESSED_RGB_PVRTC_4BPPV1_IMG = 0x8C00,
85 KTX_GL_COMPRESSED_RGB_PVRTC_2BPPV1_IMG = 0x8C01,
86 KTX_GL_COMPRESSED_RGBA_PVRTC_4BPPV1_IMG = 0x8C02,
87 KTX_GL_COMPRESSED_RGBA_PVRTC_2BPPV1_IMG = 0x8C03,
88
89 // DXT1, DXT3, and DXT5.
90 KTX_GL_COMPRESSED_RGB_S3TC_DXT1_EXT = 0x83F0,
91 KTX_GL_COMPRESSED_RGBA_S3TC_DXT3_EXT = 0x83F2,
92 KTX_GL_COMPRESSED_RGBA_S3TC_DXT5_EXT = 0x83F3,
93 KTX_GL_COMPRESSED_SRGB_S3TC_DXT1_EXT = 0x8C4C,
94 KTX_GL_COMPRESSED_SRGB_ALPHA_S3TC_DXT3_EXT = 0x8C4E,
95 KTX_GL_COMPRESSED_SRGB_ALPHA_S3TC_DXT5_EXT = 0x8C4F,
96
97 // BC4 and BC5.
98 KTX_GL_COMPRESSED_RED_RGTC1 = 0x8DBB,
99 KTX_GL_COMPRESSED_SIGNED_RED_RGTC1 = 0x8DBC,
100 KTX_GL_COMPRESSED_RG_RGTC2 = 0x8DBD,
101 KTX_GL_COMPRESSED_SIGNED_RG_RGTC2 = 0x8DBE,
102
103 // BC6 and BC7.
104 KTX_GL_COMPRESSED_RGBA_BPTC_UNORM = 0x8E8C,
105 KTX_GL_COMPRESSED_SRGB_ALPHA_BPTC_UNORM = 0x8E8D,
106 KTX_GL_COMPRESSED_RGB_BPTC_SIGNED_FLOAT = 0x8E8E,
107 KTX_GL_COMPRESSED_RGB_BPTC_UNSIGNED_FLOAT = 0x8E8F,
108
109 // ASTC.
110 KTX_GL_COMPRESSED_RGBA_ASTC_4x4_KHR = 0x93B0,
111 KTX_GL_COMPRESSED_RGBA_ASTC_5x4_KHR = 0x93B1,
112 KTX_GL_COMPRESSED_RGBA_ASTC_5x5_KHR = 0x93B2,
113 KTX_GL_COMPRESSED_RGBA_ASTC_6x5_KHR = 0x93B3,
114 KTX_GL_COMPRESSED_RGBA_ASTC_6x6_KHR = 0x93B4,
115 KTX_GL_COMPRESSED_RGBA_ASTC_8x5_KHR = 0x93B5,
116 KTX_GL_COMPRESSED_RGBA_ASTC_8x6_KHR = 0x93B6,
117 KTX_GL_COMPRESSED_RGBA_ASTC_8x8_KHR = 0x93B7,
118 KTX_GL_COMPRESSED_RGBA_ASTC_10x5_KHR = 0x93B8,
119 KTX_GL_COMPRESSED_RGBA_ASTC_10x6_KHR = 0x93B9,
120 KTX_GL_COMPRESSED_RGBA_ASTC_10x8_KHR = 0x93BA,
121 KTX_GL_COMPRESSED_RGBA_ASTC_10x10_KHR = 0x93BB,
122 KTX_GL_COMPRESSED_RGBA_ASTC_12x10_KHR = 0x93BC,
123 KTX_GL_COMPRESSED_RGBA_ASTC_12x12_KHR = 0x93BD,
124 KTX_GL_COMPRESSED_SRGB8_ALPHA8_ASTC_4x4_KHR = 0x93D0,
125 KTX_GL_COMPRESSED_SRGB8_ALPHA8_ASTC_5x4_KHR = 0x93D1,
126 KTX_GL_COMPRESSED_SRGB8_ALPHA8_ASTC_5x5_KHR = 0x93D2,
127 KTX_GL_COMPRESSED_SRGB8_ALPHA8_ASTC_6x5_KHR = 0x93D3,
128 KTX_GL_COMPRESSED_SRGB8_ALPHA8_ASTC_6x6_KHR = 0x93D4,
129 KTX_GL_COMPRESSED_SRGB8_ALPHA8_ASTC_8x5_KHR = 0x93D5,
130 KTX_GL_COMPRESSED_SRGB8_ALPHA8_ASTC_8x6_KHR = 0x93D6,
131 KTX_GL_COMPRESSED_SRGB8_ALPHA8_ASTC_8x8_KHR = 0x93D7,
132 KTX_GL_COMPRESSED_SRGB8_ALPHA8_ASTC_10x5_KHR = 0x93D8,
133 KTX_GL_COMPRESSED_SRGB8_ALPHA8_ASTC_10x6_KHR = 0x93D9,
134 KTX_GL_COMPRESSED_SRGB8_ALPHA8_ASTC_10x8_KHR = 0x93DA,
135 KTX_GL_COMPRESSED_SRGB8_ALPHA8_ASTC_10x10_KHR = 0x93DB,
136 KTX_GL_COMPRESSED_SRGB8_ALPHA8_ASTC_12x10_KHR = 0x93DC,
137 KTX_GL_COMPRESSED_SRGB8_ALPHA8_ASTC_12x12_KHR = 0x93DD
138};
139
140PixelFormat convertFormat(uint32 glformat, bool &sRGB)
141{
142 sRGB = false;
143
144 // hnnngg ASTC...
145
146 switch (glformat)
147 {
148 case KTX_GL_ETC1_RGB8_OES:
149 return PIXELFORMAT_ETC1;
150
151 // EAC and ETC2.
152 case KTX_GL_COMPRESSED_R11_EAC:
153 return PIXELFORMAT_EAC_R;
154 case KTX_GL_COMPRESSED_SIGNED_R11_EAC:
155 return PIXELFORMAT_EAC_Rs;
156 case KTX_GL_COMPRESSED_RG11_EAC:
157 return PIXELFORMAT_EAC_RG;
158 case KTX_GL_COMPRESSED_SIGNED_RG11_EAC:
159 return PIXELFORMAT_EAC_RGs;
160 case KTX_GL_COMPRESSED_RGB8_ETC2:
161 return PIXELFORMAT_ETC2_RGB;
162 case KTX_GL_COMPRESSED_SRGB8_ETC2:
163 sRGB = true;
164 return PIXELFORMAT_ETC2_RGB;
165 case KTX_GL_COMPRESSED_RGB8_PUNCHTHROUGH_ALPHA1_ETC2:
166 return PIXELFORMAT_ETC2_RGBA1;
167 case KTX_GL_COMPRESSED_SRGB8_PUNCHTHROUGH_ALPHA1_ETC2:
168 sRGB = true;
169 return PIXELFORMAT_ETC2_RGBA1;
170 case KTX_GL_COMPRESSED_RGBA8_ETC2_EAC:
171 return PIXELFORMAT_ETC2_RGBA;
172 case KTX_GL_COMPRESSED_SRGB8_ALPHA8_ETC2_EAC:
173 sRGB = true;
174 return PIXELFORMAT_ETC2_RGBA;
175
176 // PVRTC.
177 case KTX_GL_COMPRESSED_RGB_PVRTC_4BPPV1_IMG:
178 return PIXELFORMAT_PVR1_RGB4;
179 case KTX_GL_COMPRESSED_RGB_PVRTC_2BPPV1_IMG:
180 return PIXELFORMAT_PVR1_RGB2;
181 case KTX_GL_COMPRESSED_RGBA_PVRTC_4BPPV1_IMG:
182 return PIXELFORMAT_PVR1_RGBA4;
183 case KTX_GL_COMPRESSED_RGBA_PVRTC_2BPPV1_IMG:
184 return PIXELFORMAT_PVR1_RGBA2;
185
186 // DXT.
187 case KTX_GL_COMPRESSED_SRGB_S3TC_DXT1_EXT:
188 sRGB = true;
189 case KTX_GL_COMPRESSED_RGB_S3TC_DXT1_EXT:
190 return PIXELFORMAT_DXT1;
191 case KTX_GL_COMPRESSED_SRGB_ALPHA_S3TC_DXT3_EXT:
192 sRGB = true;
193 case KTX_GL_COMPRESSED_RGBA_S3TC_DXT3_EXT:
194 return PIXELFORMAT_DXT3;
195 case KTX_GL_COMPRESSED_SRGB_ALPHA_S3TC_DXT5_EXT:
196 sRGB = true;
197 case KTX_GL_COMPRESSED_RGBA_S3TC_DXT5_EXT:
198 return PIXELFORMAT_DXT5;
199
200 // BC4 and BC5.
201 case KTX_GL_COMPRESSED_RED_RGTC1:
202 return PIXELFORMAT_BC4;
203 case KTX_GL_COMPRESSED_SIGNED_RED_RGTC1:
204 return PIXELFORMAT_BC4s;
205 case KTX_GL_COMPRESSED_RG_RGTC2:
206 return PIXELFORMAT_BC5;
207 case KTX_GL_COMPRESSED_SIGNED_RG_RGTC2:
208 return PIXELFORMAT_BC5s;
209
210 // BC6 and BC7.
211 case KTX_GL_COMPRESSED_SRGB_ALPHA_BPTC_UNORM:
212 sRGB = true;
213 case KTX_GL_COMPRESSED_RGBA_BPTC_UNORM:
214 return PIXELFORMAT_BC7;
215 case KTX_GL_COMPRESSED_RGB_BPTC_SIGNED_FLOAT:
216 return PIXELFORMAT_BC6Hs;
217 case KTX_GL_COMPRESSED_RGB_BPTC_UNSIGNED_FLOAT:
218 return PIXELFORMAT_BC6H;
219
220 // ASTC.
221 case KTX_GL_COMPRESSED_SRGB8_ALPHA8_ASTC_4x4_KHR:
222 sRGB = true;
223 case KTX_GL_COMPRESSED_RGBA_ASTC_4x4_KHR:
224 return PIXELFORMAT_ASTC_4x4;
225 case KTX_GL_COMPRESSED_SRGB8_ALPHA8_ASTC_5x4_KHR:
226 sRGB = true;
227 case KTX_GL_COMPRESSED_RGBA_ASTC_5x4_KHR:
228 return PIXELFORMAT_ASTC_5x4;
229 case KTX_GL_COMPRESSED_SRGB8_ALPHA8_ASTC_5x5_KHR:
230 sRGB = true;
231 case KTX_GL_COMPRESSED_RGBA_ASTC_5x5_KHR:
232 return PIXELFORMAT_ASTC_5x5;
233 case KTX_GL_COMPRESSED_SRGB8_ALPHA8_ASTC_6x5_KHR:
234 sRGB = true;
235 case KTX_GL_COMPRESSED_RGBA_ASTC_6x5_KHR:
236 return PIXELFORMAT_ASTC_6x5;
237 case KTX_GL_COMPRESSED_SRGB8_ALPHA8_ASTC_6x6_KHR:
238 sRGB = true;
239 case KTX_GL_COMPRESSED_RGBA_ASTC_6x6_KHR:
240 return PIXELFORMAT_ASTC_6x6;
241 case KTX_GL_COMPRESSED_SRGB8_ALPHA8_ASTC_8x5_KHR:
242 sRGB = true;
243 case KTX_GL_COMPRESSED_RGBA_ASTC_8x5_KHR:
244 return PIXELFORMAT_ASTC_8x5;
245 case KTX_GL_COMPRESSED_SRGB8_ALPHA8_ASTC_8x6_KHR:
246 sRGB = true;
247 case KTX_GL_COMPRESSED_RGBA_ASTC_8x6_KHR:
248 return PIXELFORMAT_ASTC_8x6;
249 case KTX_GL_COMPRESSED_SRGB8_ALPHA8_ASTC_8x8_KHR:
250 sRGB = true;
251 case KTX_GL_COMPRESSED_RGBA_ASTC_8x8_KHR:
252 return PIXELFORMAT_ASTC_8x8;
253 case KTX_GL_COMPRESSED_SRGB8_ALPHA8_ASTC_10x5_KHR:
254 sRGB = true;
255 case KTX_GL_COMPRESSED_RGBA_ASTC_10x5_KHR:
256 return PIXELFORMAT_ASTC_10x5;
257 case KTX_GL_COMPRESSED_SRGB8_ALPHA8_ASTC_10x6_KHR:
258 sRGB = true;
259 case KTX_GL_COMPRESSED_RGBA_ASTC_10x6_KHR:
260 return PIXELFORMAT_ASTC_10x6;
261 case KTX_GL_COMPRESSED_SRGB8_ALPHA8_ASTC_10x8_KHR:
262 sRGB = true;
263 case KTX_GL_COMPRESSED_RGBA_ASTC_10x8_KHR:
264 return PIXELFORMAT_ASTC_10x8;
265 case KTX_GL_COMPRESSED_SRGB8_ALPHA8_ASTC_10x10_KHR:
266 sRGB = true;
267 case KTX_GL_COMPRESSED_RGBA_ASTC_10x10_KHR:
268 return PIXELFORMAT_ASTC_10x10;
269 case KTX_GL_COMPRESSED_SRGB8_ALPHA8_ASTC_12x10_KHR:
270 sRGB = true;
271 case KTX_GL_COMPRESSED_RGBA_ASTC_12x10_KHR:
272 return PIXELFORMAT_ASTC_12x10;
273 case KTX_GL_COMPRESSED_SRGB8_ALPHA8_ASTC_12x12_KHR:
274 sRGB = true;
275 case KTX_GL_COMPRESSED_RGBA_ASTC_12x12_KHR:
276 return PIXELFORMAT_ASTC_12x12;
277 default:
278 return PIXELFORMAT_UNKNOWN;
279 }
280}
281
282} // Anonymous namespace.
283
284bool KTXHandler::canParseCompressed(Data *data)
285{
286 if (data->getSize() < sizeof(KTXHeader))
287 return false;
288
289 KTXHeader *header = (KTXHeader *) data->getData();
290 uint8 ktxidentifier[12] = KTX_IDENTIFIER_REF;
291
292 if (memcmp(header->identifier, ktxidentifier, 12) != 0)
293 return false;
294
295 if (header->endianness != KTX_ENDIAN_REF && header->endianness != KTX_ENDIAN_REF_REV)
296 return false;
297
298 return true;
299}
300
301StrongRef<CompressedMemory> KTXHandler::parseCompressed(Data *filedata, std::vector<StrongRef<CompressedSlice>> &images, PixelFormat &format, bool &sRGB)
302{
303 if (!canParseCompressed(filedata))
304 throw love::Exception("Could not decode compressed data (not a KTX file?)");
305
306 KTXHeader header = *(KTXHeader *) filedata->getData();
307
308 if (header.endianness == KTX_ENDIAN_REF_REV)
309 {
310 uint32 *headerArray = (uint32 *) &header.glType;
311 for (int i = 0; i < 12; i++)
312 headerArray[i] = swapuint32(headerArray[i]);
313 }
314
315 header.numberOfMipmapLevels = std::max(header.numberOfMipmapLevels, 1u);
316
317 bool isSRGB = false;
318 PixelFormat cformat = convertFormat(header.glInternalFormat, isSRGB);
319
320 if (cformat == PIXELFORMAT_UNKNOWN)
321 throw love::Exception("Unsupported image format in KTX file.");
322
323 if (header.numberOfArrayElements > 0)
324 throw love::Exception("Texture arrays in KTX files are not supported.");
325
326 if (header.pixelDepth > 1)
327 throw love::Exception("3D textures in KTX files are not supported.");
328
329 if (header.numberOfFaces > 1)
330 throw love::Exception("Cubemap textures in KTX files are not supported.");
331
332 size_t fileoffset = sizeof(KTXHeader) + header.bytesOfKeyValueData;
333 const uint8 *filebytes = (uint8 *) filedata->getData();
334 size_t totalsize = 0;
335
336 // Calculate the total size needed to hold the data in memory.
337 for (int i = 0; i < (int) header.numberOfMipmapLevels; i++)
338 {
339 if (fileoffset + sizeof(uint32) > filedata->getSize())
340 throw love::Exception("Could not parse KTX file: unexpected EOF.");
341
342 uint32 mipsize = *(uint32 *) (filebytes + fileoffset);
343
344 if (header.endianness == KTX_ENDIAN_REF_REV)
345 mipsize = swapuint32(mipsize);
346
347 fileoffset += sizeof(uint32);
348
349 // All mipsize fields are at a file offset that's a multiple of 4, so
350 // there might be some padding after the actual data in this mip level.
351 uint32 mipsizepadded = (mipsize + 3) & ~uint32(3);
352
353 totalsize += mipsizepadded;
354 fileoffset += mipsizepadded;
355 }
356
357 StrongRef<CompressedMemory> memory;
358 memory.set(new CompressedMemory(totalsize), Acquire::NORETAIN);
359
360 // Reset the file offset to the start of the file's image data.
361 fileoffset = sizeof(KTXHeader) + header.bytesOfKeyValueData;
362 size_t dataoffset = 0;
363
364 // Copy each mipmap level of the image from the file to our block of memory.
365 for (int i = 0; i < (int) header.numberOfMipmapLevels; i++)
366 {
367 uint32 mipsize = *(uint32 *) (filebytes + fileoffset);
368
369 if (header.endianness == KTX_ENDIAN_REF_REV)
370 mipsize = swapuint32(mipsize);
371
372 fileoffset += sizeof(uint32);
373
374 uint32 mipsizepadded = (mipsize + 3) & ~uint32(3);
375
376 int width = (int) std::max(header.pixelWidth >> i, 1u);
377 int height = (int) std::max(header.pixelHeight >> i, 1u);
378
379 memcpy(memory->data + dataoffset, filebytes + fileoffset, mipsize);
380
381 auto slice = new CompressedSlice(cformat, width, height, memory, dataoffset, mipsize);
382 images.push_back(slice);
383 slice->release();
384
385 fileoffset += mipsizepadded;
386 dataoffset += mipsizepadded;
387 }
388
389 format = cformat;
390 sRGB = isSRGB;
391
392 return memory;
393}
394
395} // magpie
396} // image
397} // love
398