File: basistoktx.cpp

package info (click to toggle)
meshoptimizer 0.15%2Bdfsg-3
  • links: PTS, VCS
  • area: main
  • in suites: bullseye
  • size: 1,148 kB
  • sloc: cpp: 11,845; ansic: 6,343; javascript: 305; makefile: 109; python: 24
file content (397 lines) | stat: -rw-r--r-- 10,730 bytes parent folder | download
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
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
// This file is part of gltfpack; see gltfpack.h for version/license details
#include <stdexcept>
#include <string>
#include <vector>

#include <assert.h>
#include <stdint.h>
#include <stdio.h>
#include <string.h>

#include "../extern/basisu_format.h"
#include "../extern/khr_df.h"

// KTX Specification: 2. File Structure
struct Ktx2Header
{
	uint8_t identifier[12];
	uint32_t vkFormat;
	uint32_t typeSize;
	uint32_t pixelWidth;
	uint32_t pixelHeight;
	uint32_t pixelDepth;
	uint32_t layerCount;
	uint32_t faceCount;
	uint32_t levelCount;
	uint32_t supercompressionScheme;

	uint32_t dfdByteOffset;
	uint32_t dfdByteLength;
	uint32_t kvdByteOffset;
	uint32_t kvdByteLength;
	uint64_t sgdByteOffset;
	uint64_t sgdByteLength;
};

struct Ktx2LevelIndex
{
	uint64_t byteOffset;
	uint64_t byteLength;
	uint64_t uncompressedByteLength;
};

enum
{
	Ktx2SupercompressionSchemeNone = 0,
	Ktx2SupercompressionSchemeBasis = 1,
};

// KTX Specification: 3.1. identifier
static const uint8_t Ktx2FileIdentifier[12] = {
    0xAB, 0x4B, 0x54, 0x58, 0x20, 0x32, 0x30, 0xBB, 0x0D, 0x0A, 0x1A, 0x0A, //
};

// KTX Specification: 3.12.2. Basis Universal Global Data
struct Ktx2BasisGlobalHeader
{
	uint16_t endpointCount;
	uint16_t selectorCount;
	uint32_t endpointsByteLength;
	uint32_t selectorsByteLength;
	uint32_t tablesByteLength;
	uint32_t extendedByteLength;
};

struct Ktx2BasisImageDesc
{
	uint32_t imageFlags;
	uint32_t rgbSliceByteOffset;
	uint32_t rgbSliceByteLength;
	uint32_t alphaSliceByteOffset;
	uint32_t alphaSliceByteLength;
};

template <typename T>
static void read(const std::string& data, size_t offset, T& result)
{
	if (offset + sizeof(T) > data.size())
		throw std::out_of_range("read");

	memcpy(&result, &data[offset], sizeof(T));
}

template <typename T>
static void write(std::string& data, const T& value)
{
	data.append(reinterpret_cast<const char*>(&value), sizeof(value));
}

template <typename T>
static void write(std::string& data, size_t offset, const T& value)
{
	if (offset + sizeof(T) > data.size())
		throw std::out_of_range("write");

	memcpy(&data[offset], &value, sizeof(T));
}

static void createDfd(std::vector<uint32_t>& result, bool alpha, bool srgb, bool uastc)
{
	int sample_count = (!uastc && alpha) ? 2 : 1;
	int descriptor_size = KHR_DF_WORD_SAMPLESTART + sample_count * KHR_DF_WORD_SAMPLEWORDS;

	result.clear();
	result.resize(1 + descriptor_size);

	result[0] = (1 + descriptor_size) * sizeof(uint32_t);

	uint32_t* dfd = &result[1];

	KHR_DFDSETVAL(dfd, VENDORID, KHR_DF_VENDORID_KHRONOS);
	KHR_DFDSETVAL(dfd, DESCRIPTORTYPE, KHR_DF_KHR_DESCRIPTORTYPE_BASICFORMAT);
	KHR_DFDSETVAL(dfd, VERSIONNUMBER, KHR_DF_VERSIONNUMBER_1_3);
	KHR_DFDSETVAL(dfd, DESCRIPTORBLOCKSIZE, descriptor_size * sizeof(uint32_t));
	KHR_DFDSETVAL(dfd, MODEL, uastc ? KHR_DF_MODEL_UASTC : KHR_DF_MODEL_ETC1S);
	KHR_DFDSETVAL(dfd, PRIMARIES, KHR_DF_PRIMARIES_BT709);
	KHR_DFDSETVAL(dfd, TRANSFER, srgb ? KHR_DF_TRANSFER_SRGB : KHR_DF_TRANSFER_LINEAR);
	KHR_DFDSETVAL(dfd, FLAGS, KHR_DF_FLAG_ALPHA_STRAIGHT);

	if (uastc)
	{
		// UASTC is ASTC 4x4 - but texelBlockDimension encodes size-1
		KHR_DFDSETVAL(dfd, TEXELBLOCKDIMENSION0, 3);
		KHR_DFDSETVAL(dfd, TEXELBLOCKDIMENSION1, 3);

		// Every block is 16 bytes = 128 bits (encoded as 127)
		KHR_DFDSETVAL(dfd, BYTESPLANE0, 16);
		KHR_DFDSETSVAL(dfd, 0, BITLENGTH, 127);

		// The single sample stores either RGB or RGBA data
		assert(sample_count == 1);
		KHR_DFDSETSVAL(dfd, 0, CHANNELID, alpha ? KHR_DF_CHANNEL_UASTC_RGBA : KHR_DF_CHANNEL_UASTC_RGB);
	}
	else
	{
		// Every decoded block is 8 bytes = 64 bits (encoded as 63)
		KHR_DFDSETSVAL(dfd, 0, BITLENGTH, 63);

		// RGB is stored in sample 0, alpha is stored in sample 1
		KHR_DFDSETSVAL(dfd, 0, CHANNELID, KHR_DF_CHANNEL_ETC1S_RGB);

		if (alpha)
			KHR_DFDSETSVAL(dfd, 1, CHANNELID, KHR_DF_CHANNEL_ETC1S_AAA);
	}

	for (int i = 0; i < sample_count; ++i)
	{
		KHR_DFDSETSVAL(dfd, i, SAMPLELOWER, 0);
		KHR_DFDSETSVAL(dfd, i, SAMPLEUPPER, ~0u);
	}
}

std::string basisToKtx(const std::string& data, bool srgb, bool uastc)
{
	std::string ktx;

	basist::basis_file_header basis_header;
	read(data, 0, basis_header);

	assert(basis_header.m_sig == basist::basis_file_header::cBASISSigValue);

	assert(basis_header.m_total_slices > 0);
	assert(basis_header.m_total_images == 1);

	assert(basis_header.m_tex_format == uint32_t(uastc ? basist::cUASTC4x4 : basist::cETC1S));
	assert(!(basis_header.m_flags & basist::cBASISHeaderFlagETC1S) == uastc);
	assert(!(basis_header.m_flags & basist::cBASISHeaderFlagYFlipped));
	assert(basis_header.m_tex_type == basist::cBASISTexType2D);

	if (uastc)
	{
		assert(basis_header.m_endpoint_cb_file_size == 0);
		assert(basis_header.m_selector_cb_file_size == 0);
		assert(basis_header.m_tables_file_size == 0);
		assert(basis_header.m_extended_file_size == 0);
	}

	bool has_alpha = (basis_header.m_flags & basist::cBASISHeaderFlagHasAlphaSlices) != 0;
	bool alpha_slices = has_alpha && !uastc;
	bool basis = !uastc;

	std::vector<basist::basis_slice_desc> slices(basis_header.m_total_slices);

	for (size_t i = 0; i < basis_header.m_total_slices; ++i)
		read(data, basis_header.m_slice_desc_file_ofs + i * sizeof(basist::basis_slice_desc), slices[i]);

	assert(slices[0].m_level_index == 0);
	uint32_t width = slices[0].m_orig_width;
	uint32_t height = slices[0].m_orig_height;
	uint32_t levels = alpha_slices ? uint32_t(slices.size()) / 2 : uint32_t(slices.size());

	Ktx2Header ktx_header = {};
	memcpy(ktx_header.identifier, Ktx2FileIdentifier, sizeof(Ktx2FileIdentifier));
	ktx_header.typeSize = 1;
	ktx_header.pixelWidth = width;
	ktx_header.pixelHeight = height;
	ktx_header.layerCount = 0;
	ktx_header.faceCount = 1;
	ktx_header.levelCount = levels;
	ktx_header.supercompressionScheme = basis ? Ktx2SupercompressionSchemeBasis : Ktx2SupercompressionSchemeNone;

	size_t header_size = sizeof(Ktx2Header) + levels * sizeof(Ktx2LevelIndex);

	std::vector<uint32_t> dfd;
	createDfd(dfd, has_alpha, srgb, uastc);

	const char* kvp_data[][2] = {
	    {"KTXwriter", "gltfpack"},
	};

	std::string kvp;

	for (size_t i = 0; i < sizeof(kvp_data) / sizeof(kvp_data[0]); ++i)
	{
		const char* key = kvp_data[i][0];
		const char* value = kvp_data[i][1];

		write(kvp, uint32_t(strlen(key) + strlen(value) + 2));
		kvp += key;
		kvp += '\0';
		kvp += value;
		kvp += '\0';

		kvp.resize((kvp.size() + 3) & ~3);
	}

	size_t kvp_size = kvp.size();
	size_t dfd_size = dfd.size() * sizeof(uint32_t);

	ktx_header.dfdByteOffset = uint32_t(header_size);
	ktx_header.dfdByteLength = uint32_t(dfd_size);

	ktx_header.kvdByteOffset = uint32_t(header_size + dfd_size);
	ktx_header.kvdByteLength = uint32_t(kvp_size);

	if (basis)
	{
		size_t bgd_size =
		    sizeof(Ktx2BasisGlobalHeader) + sizeof(Ktx2BasisImageDesc) * levels +
		    basis_header.m_endpoint_cb_file_size + basis_header.m_selector_cb_file_size + basis_header.m_tables_file_size;

		ktx_header.sgdByteOffset = (header_size + dfd_size + kvp_size + 7) & ~7;
		ktx_header.sgdByteLength = bgd_size;
	}

	// KTX2 header
	write(ktx, ktx_header);

	size_t ktx_level_offset = ktx.size();

	for (size_t i = 0; i < levels; ++i)
	{
		Ktx2LevelIndex le = {}; // This will be patched later
		write(ktx, le);
	}

	// data format descriptor
	for (size_t i = 0; i < dfd.size(); ++i)
		write(ktx, dfd[i]);

	// key/value pair data
	ktx += kvp;
	ktx.resize((ktx.size() + 7) & ~7);

	// supercompression global data
	if (basis)
	{
		Ktx2BasisGlobalHeader sgd_header = {};
		sgd_header.endpointCount = uint16_t(basis_header.m_total_endpoints);
		sgd_header.selectorCount = uint16_t(basis_header.m_total_selectors);
		sgd_header.endpointsByteLength = basis_header.m_endpoint_cb_file_size;
		sgd_header.selectorsByteLength = basis_header.m_selector_cb_file_size;
		sgd_header.tablesByteLength = basis_header.m_tables_file_size;
		sgd_header.extendedByteLength = basis_header.m_extended_file_size;

		write(ktx, sgd_header);
	}

	size_t sgd_level_offset = ktx.size();

	if (basis)
	{
		for (size_t i = 0; i < levels; ++i)
		{
			Ktx2BasisImageDesc sgd_image = {}; // This will be patched later
			write(ktx, sgd_image);
		}

		ktx.append(data.substr(basis_header.m_endpoint_cb_file_ofs, basis_header.m_endpoint_cb_file_size));
		ktx.append(data.substr(basis_header.m_selector_cb_file_ofs, basis_header.m_selector_cb_file_size));
		ktx.append(data.substr(basis_header.m_tables_file_ofs, basis_header.m_tables_file_size));
		ktx.append(data.substr(basis_header.m_extended_file_ofs, basis_header.m_extended_file_size));
	}

	// align to UASTC block size (16b) before writing mips
	if (uastc)
	{
		ktx.resize((ktx.size() + 15) & ~15);
	}

	// mip levels
	for (size_t i = 0; i < levels; ++i)
	{
		size_t level_index = levels - i - 1;
		size_t slice_index = level_index * (alpha_slices + 1);

		const basist::basis_slice_desc& slice = slices[slice_index];
		const basist::basis_slice_desc* slice_alpha = alpha_slices ? &slices[slice_index + 1] : 0;

		assert(slice.m_image_index == 0);
		assert(slice.m_level_index == level_index);

		size_t file_offset = ktx.size();

		ktx.append(data.substr(slice.m_file_ofs, slice.m_file_size));

		if (slice_alpha)
			ktx.append(data.substr(slice_alpha->m_file_ofs, slice_alpha->m_file_size));

		Ktx2LevelIndex le = {};
		le.byteOffset = file_offset;
		le.byteLength = ktx.size() - file_offset;
		le.uncompressedByteLength = basis ? 0 : slice.m_file_size;

		write(ktx, ktx_level_offset + level_index * sizeof(Ktx2LevelIndex), le);

		if (basis)
		{
			Ktx2BasisImageDesc sgd_image = {};
			sgd_image.rgbSliceByteOffset = 0;
			sgd_image.rgbSliceByteLength = slice.m_file_size;

			if (slice_alpha)
			{
				sgd_image.alphaSliceByteOffset = slice.m_file_size;
				sgd_image.alphaSliceByteLength = slice_alpha->m_file_size;
			}

			write(ktx, sgd_level_offset + level_index * sizeof(Ktx2BasisImageDesc), sgd_image);
		}
	}

	return ktx;
}

#ifdef STANDALONE
bool readFile(const char* path, std::string& data)
{
	FILE* file = fopen(path, "rb");
	if (!file)
		return false;

	fseek(file, 0, SEEK_END);
	long length = ftell(file);
	fseek(file, 0, SEEK_SET);

	if (length <= 0)
	{
		fclose(file);
		return false;
	}

	data.resize(length);
	size_t result = fread(&data[0], 1, data.size(), file);
	fclose(file);

	return result == data.size();
}

bool writeFile(const char* path, const std::string& data)
{
	FILE* file = fopen(path, "wb");
	if (!file)
		return false;

	size_t result = fwrite(&data[0], 1, data.size(), file);
	fclose(file);

	return result == data.size();
}

int main(int argc, const char** argv)
{
	if (argc < 2)
		return 1;

	std::string data;
	if (!readFile(argv[1], data))
		return 1;

	std::string ktx = basisToKtx(data, true, true);

	if (!writeFile(argv[2], ktx))
		return 1;

	return 0;
}
#endif