Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
godotengine
GitHub Repository: godotengine/godot
Path: blob/master/thirdparty/basis_universal/encoder/basisu_comp.cpp
9902 views
1
// basisu_comp.cpp
2
// Copyright (C) 2019-2024 Binomial LLC. All Rights Reserved.
3
//
4
// Licensed under the Apache License, Version 2.0 (the "License");
5
// you may not use this file except in compliance with the License.
6
// You may obtain a copy of the License at
7
//
8
// http://www.apache.org/licenses/LICENSE-2.0
9
//
10
// Unless required by applicable law or agreed to in writing, software
11
// distributed under the License is distributed on an "AS IS" BASIS,
12
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
// See the License for the specific language governing permissions and
14
// limitations under the License.
15
#include "basisu_comp.h"
16
#include "basisu_enc.h"
17
#include <unordered_set>
18
#include <atomic>
19
#include <map>
20
21
//#define UASTC_HDR_DEBUG_SAVE_CATEGORIZED_BLOCKS
22
23
// basisu_transcoder.cpp is where basisu_miniz lives now, we just need the declarations here.
24
#define MINIZ_NO_ZLIB_COMPATIBLE_NAMES
25
#include "basisu_miniz.h"
26
27
#include "basisu_opencl.h"
28
29
#include "../transcoder/basisu_astc_hdr_core.h"
30
31
#if !BASISD_SUPPORT_KTX2
32
#error BASISD_SUPPORT_KTX2 must be enabled (set to 1).
33
#endif
34
35
#if BASISD_SUPPORT_KTX2_ZSTD
36
#include <zstd.h>
37
#endif
38
39
// Set to 1 to disable the mipPadding alignment workaround (which only seems to be needed when no key-values are written at all)
40
#define BASISU_DISABLE_KTX2_ALIGNMENT_WORKAROUND (0)
41
42
// Set to 1 to disable writing all KTX2 key values, triggering an early validator bug.
43
#define BASISU_DISABLE_KTX2_KEY_VALUES (0)
44
45
using namespace buminiz;
46
47
#define BASISU_USE_STB_IMAGE_RESIZE_FOR_MIPMAP_GEN 0
48
#define DEBUG_CROP_TEXTURE_TO_64x64 (0)
49
#define DEBUG_RESIZE_TEXTURE (0)
50
51
namespace basisu
52
{
53
basis_compressor::basis_compressor() :
54
m_pOpenCL_context(nullptr),
55
m_fmt_mode(basist::basis_tex_format::cETC1S),
56
m_basis_file_size(0),
57
m_basis_bits_per_texel(0.0f),
58
m_total_blocks(0),
59
m_hdr_image_scale(1.0f),
60
m_ldr_to_hdr_upconversion_nit_multiplier(1.0f),
61
m_upconverted_any_ldr_images(false),
62
m_any_source_image_has_alpha(false),
63
m_opencl_failed(false)
64
{
65
debug_printf("basis_compressor::basis_compressor\n");
66
67
assert(g_library_initialized);
68
}
69
70
basis_compressor::~basis_compressor()
71
{
72
if (m_pOpenCL_context)
73
{
74
opencl_destroy_context(m_pOpenCL_context);
75
m_pOpenCL_context = nullptr;
76
}
77
}
78
79
void basis_compressor::check_for_hdr_inputs()
80
{
81
if ((!m_params.m_source_filenames.size()) && (!m_params.m_source_images.size()))
82
{
83
if (m_params.m_source_images_hdr.size())
84
{
85
// Assume they want UASTC HDR if they've specified any HDR source images.
86
m_params.m_hdr = true;
87
}
88
}
89
90
if (!m_params.m_hdr)
91
{
92
// See if any files are .EXR or .HDR, if so switch the compressor to UASTC HDR mode.
93
for (uint32_t i = 0; i < m_params.m_source_filenames.size(); i++)
94
{
95
std::string filename;
96
string_get_filename(m_params.m_source_filenames[i].c_str(), filename);
97
98
std::string ext(string_get_extension(filename));
99
string_tolower(ext);
100
101
if ((ext == "exr") || (ext == "hdr"))
102
{
103
m_params.m_hdr = true;
104
break;
105
}
106
}
107
}
108
109
if (m_params.m_hdr)
110
{
111
if (m_params.m_source_alpha_filenames.size())
112
{
113
debug_printf("Warning: Alpha channel image filenames are not yet supported in UASTC HDR/ASTC HDR modes.\n");
114
m_params.m_source_alpha_filenames.clear();
115
}
116
}
117
118
if (m_params.m_hdr)
119
m_params.m_uastc = true;
120
}
121
122
bool basis_compressor::sanity_check_input_params()
123
{
124
// Check for no source filenames specified.
125
if ((m_params.m_read_source_images) && (!m_params.m_source_filenames.size()))
126
{
127
assert(0);
128
return false;
129
}
130
131
// See if they've specified any source filenames, but didn't tell us to read them.
132
if ((!m_params.m_read_source_images) && (m_params.m_source_filenames.size()))
133
{
134
assert(0);
135
return false;
136
}
137
138
// Sanity check the input image parameters.
139
if (m_params.m_read_source_images)
140
{
141
// Caller can't specify their own images if they want us to read source images from files.
142
if (m_params.m_source_images.size() || m_params.m_source_images_hdr.size())
143
{
144
assert(0);
145
return false;
146
}
147
148
if (m_params.m_source_mipmap_images.size() || m_params.m_source_mipmap_images_hdr.size())
149
{
150
assert(0);
151
return false;
152
}
153
}
154
else
155
{
156
// They didn't tell us to read any source files, so check for no LDR/HDR source images.
157
if (!m_params.m_source_images.size() && !m_params.m_source_images_hdr.size())
158
{
159
assert(0);
160
return false;
161
}
162
163
// Now we know we've been supplied LDR and/or HDR source images, check for LDR vs. HDR conflicts.
164
165
if (m_params.m_source_images.size())
166
{
167
// They've supplied LDR images, so make sure they also haven't specified HDR input images.
168
if (m_params.m_source_images_hdr.size() || m_params.m_source_mipmap_images_hdr.size())
169
{
170
assert(0);
171
return false;
172
}
173
}
174
else
175
{
176
// No LDR images, so make sure they haven't specified any LDR mipmaps.
177
if (m_params.m_source_mipmap_images.size())
178
{
179
assert(0);
180
return false;
181
}
182
183
// No LDR images, so ensure they've supplied some HDR images to process.
184
if (!m_params.m_source_images_hdr.size())
185
{
186
assert(0);
187
return false;
188
}
189
}
190
}
191
192
return true;
193
}
194
195
bool basis_compressor::init(const basis_compressor_params &params)
196
{
197
debug_printf("basis_compressor::init\n");
198
199
if (!g_library_initialized)
200
{
201
error_printf("basis_compressor::init: basisu_encoder_init() MUST be called before using any encoder functionality!\n");
202
return false;
203
}
204
205
if (!params.m_pJob_pool)
206
{
207
error_printf("basis_compressor::init: A non-null job_pool pointer must be specified\n");
208
return false;
209
}
210
211
m_params = params;
212
213
if ((m_params.m_compute_stats) && (!m_params.m_validate_output_data))
214
m_params.m_validate_output_data = true;
215
216
m_hdr_image_scale = 1.0f;
217
m_ldr_to_hdr_upconversion_nit_multiplier = 1.0f;
218
m_upconverted_any_ldr_images = false;
219
220
check_for_hdr_inputs();
221
222
if (m_params.m_debug)
223
{
224
debug_printf("basis_compressor::init:\n");
225
226
#define PRINT_BOOL_VALUE(v) fmt_debug_printf("{}: {} {}\n", BASISU_STRINGIZE2(v), static_cast<bool>(m_params.v), m_params.v.was_changed());
227
#define PRINT_INT_VALUE(v) fmt_debug_printf("{}: {} {}\n", BASISU_STRINGIZE2(v), static_cast<int>(m_params.v), m_params.v.was_changed());
228
#define PRINT_UINT_VALUE(v) fmt_debug_printf("{}: {} {}\n", BASISU_STRINGIZE2(v), static_cast<uint32_t>(m_params.v), m_params.v.was_changed());
229
#define PRINT_FLOAT_VALUE(v) fmt_debug_printf("{}: {} {}\n", BASISU_STRINGIZE2(v), static_cast<float>(m_params.v), m_params.v.was_changed());
230
231
fmt_debug_printf("Source LDR images: {}, HDR images: {}, filenames: {}, alpha filenames: {}, LDR mipmap images: {}, HDR mipmap images: {}\n",
232
(uint64_t)m_params.m_source_images.size(), (uint64_t)m_params.m_source_images_hdr.size(),
233
(uint64_t)m_params.m_source_filenames.size(), (uint64_t)m_params.m_source_alpha_filenames.size(),
234
(uint64_t)m_params.m_source_mipmap_images.size(), (uint64_t)m_params.m_source_mipmap_images_hdr.size());
235
236
if (m_params.m_source_mipmap_images.size())
237
{
238
debug_printf("m_source_mipmap_images array sizes:\n");
239
for (uint32_t i = 0; i < m_params.m_source_mipmap_images.size(); i++)
240
debug_printf("%u ", m_params.m_source_mipmap_images[i].size());
241
debug_printf("\n");
242
}
243
244
if (m_params.m_source_mipmap_images_hdr.size())
245
{
246
debug_printf("m_source_mipmap_images_hdr array sizes:\n");
247
for (uint32_t i = 0; i < m_params.m_source_mipmap_images_hdr.size(); i++)
248
debug_printf("%u ", m_params.m_source_mipmap_images_hdr[i].size());
249
debug_printf("\n");
250
}
251
252
PRINT_BOOL_VALUE(m_hdr);
253
254
switch (m_params.m_hdr_mode)
255
{
256
case hdr_modes::cUASTC_HDR_4X4:
257
{
258
fmt_debug_printf("m_hdr_mode: cUASTC_HDR_4X4\n");
259
break;
260
}
261
case hdr_modes::cASTC_HDR_6X6:
262
{
263
fmt_debug_printf("m_hdr_mode: cASTC_HDR_6X6\n");
264
break;
265
}
266
case hdr_modes::cASTC_HDR_6X6_INTERMEDIATE:
267
{
268
fmt_debug_printf("m_hdr_mode: cASTC_HDR_6X6_INTERMEDIATE\n");
269
break;
270
}
271
default:
272
assert(false);
273
return false;
274
}
275
276
PRINT_BOOL_VALUE(m_uastc);
277
PRINT_BOOL_VALUE(m_use_opencl);
278
PRINT_BOOL_VALUE(m_y_flip);
279
PRINT_BOOL_VALUE(m_debug);
280
PRINT_BOOL_VALUE(m_validate_etc1s);
281
PRINT_BOOL_VALUE(m_debug_images);
282
PRINT_INT_VALUE(m_compression_level);
283
PRINT_BOOL_VALUE(m_perceptual);
284
PRINT_BOOL_VALUE(m_no_endpoint_rdo);
285
PRINT_BOOL_VALUE(m_no_selector_rdo);
286
PRINT_BOOL_VALUE(m_read_source_images);
287
PRINT_BOOL_VALUE(m_write_output_basis_or_ktx2_files);
288
PRINT_BOOL_VALUE(m_compute_stats);
289
PRINT_BOOL_VALUE(m_check_for_alpha);
290
PRINT_BOOL_VALUE(m_force_alpha);
291
debug_printf("swizzle: %d,%d,%d,%d\n",
292
m_params.m_swizzle[0],
293
m_params.m_swizzle[1],
294
m_params.m_swizzle[2],
295
m_params.m_swizzle[3]);
296
PRINT_BOOL_VALUE(m_renormalize);
297
PRINT_BOOL_VALUE(m_multithreading);
298
PRINT_BOOL_VALUE(m_disable_hierarchical_endpoint_codebooks);
299
300
PRINT_FLOAT_VALUE(m_endpoint_rdo_thresh);
301
PRINT_FLOAT_VALUE(m_selector_rdo_thresh);
302
303
PRINT_BOOL_VALUE(m_mip_gen);
304
PRINT_BOOL_VALUE(m_mip_renormalize);
305
PRINT_BOOL_VALUE(m_mip_wrapping);
306
PRINT_BOOL_VALUE(m_mip_fast);
307
PRINT_BOOL_VALUE(m_mip_srgb);
308
PRINT_FLOAT_VALUE(m_mip_premultiplied);
309
PRINT_FLOAT_VALUE(m_mip_scale);
310
PRINT_INT_VALUE(m_mip_smallest_dimension);
311
debug_printf("m_mip_filter: %s\n", m_params.m_mip_filter.c_str());
312
313
debug_printf("m_max_endpoint_clusters: %u\n", m_params.m_etc1s_max_endpoint_clusters);
314
debug_printf("m_max_selector_clusters: %u\n", m_params.m_etc1s_max_selector_clusters);
315
debug_printf("m_etc1s_quality_level: %i\n", m_params.m_etc1s_quality_level);
316
debug_printf("UASTC HDR 4x4 quality level: %u\n", m_params.m_uastc_hdr_4x4_options.m_level);
317
318
debug_printf("m_tex_type: %u\n", m_params.m_tex_type);
319
debug_printf("m_userdata0: 0x%X, m_userdata1: 0x%X\n", m_params.m_userdata0, m_params.m_userdata1);
320
debug_printf("m_us_per_frame: %i (%f fps)\n", m_params.m_us_per_frame, m_params.m_us_per_frame ? 1.0f / (m_params.m_us_per_frame / 1000000.0f) : 0);
321
debug_printf("m_pack_uastc_ldr_4x4_flags: 0x%X\n", m_params.m_pack_uastc_ldr_4x4_flags);
322
323
PRINT_BOOL_VALUE(m_rdo_uastc_ldr_4x4);
324
PRINT_FLOAT_VALUE(m_rdo_uastc_ldr_4x4_quality_scalar);
325
PRINT_INT_VALUE(m_rdo_uastc_ldr_4x4_dict_size);
326
PRINT_FLOAT_VALUE(m_rdo_uastc_ldr_4x4_max_allowed_rms_increase_ratio);
327
PRINT_FLOAT_VALUE(m_rdo_uastc_ldr_4x4_skip_block_rms_thresh);
328
PRINT_FLOAT_VALUE(m_rdo_uastc_ldr_4x4_max_smooth_block_error_scale);
329
PRINT_FLOAT_VALUE(m_rdo_uastc_ldr_4x4_smooth_block_max_std_dev);
330
PRINT_BOOL_VALUE(m_rdo_uastc_ldr_4x4_favor_simpler_modes_in_rdo_mode)
331
PRINT_BOOL_VALUE(m_rdo_uastc_ldr_4x4_multithreading);
332
333
PRINT_INT_VALUE(m_resample_width);
334
PRINT_INT_VALUE(m_resample_height);
335
PRINT_FLOAT_VALUE(m_resample_factor);
336
337
debug_printf("Has global codebooks: %u\n", m_params.m_pGlobal_codebooks ? 1 : 0);
338
if (m_params.m_pGlobal_codebooks)
339
{
340
debug_printf("Global codebook endpoints: %u selectors: %u\n", m_params.m_pGlobal_codebooks->get_endpoints().size(), m_params.m_pGlobal_codebooks->get_selectors().size());
341
}
342
343
PRINT_BOOL_VALUE(m_create_ktx2_file);
344
345
debug_printf("KTX2 UASTC supercompression: %u\n", m_params.m_ktx2_uastc_supercompression);
346
debug_printf("KTX2 Zstd supercompression level: %i\n", (int)m_params.m_ktx2_zstd_supercompression_level);
347
debug_printf("KTX2 sRGB transfer func: %u\n", (int)m_params.m_ktx2_srgb_transfer_func);
348
debug_printf("Total KTX2 key values: %u\n", m_params.m_ktx2_key_values.size());
349
for (uint32_t i = 0; i < m_params.m_ktx2_key_values.size(); i++)
350
{
351
debug_printf("Key: \"%s\"\n", m_params.m_ktx2_key_values[i].m_key.data());
352
debug_printf("Value size: %u\n", m_params.m_ktx2_key_values[i].m_value.size());
353
}
354
355
PRINT_BOOL_VALUE(m_validate_output_data);
356
PRINT_BOOL_VALUE(m_ldr_hdr_upconversion_srgb_to_linear);
357
PRINT_FLOAT_VALUE(m_ldr_hdr_upconversion_nit_multiplier);
358
debug_printf("Allow UASTC HDR 4x4 uber mode: %u\n", m_params.m_uastc_hdr_4x4_options.m_allow_uber_mode);
359
debug_printf("UASTC HDR 4x4 ultra quant: %u\n", m_params.m_uastc_hdr_4x4_options.m_ultra_quant);
360
PRINT_BOOL_VALUE(m_hdr_favor_astc);
361
362
#undef PRINT_BOOL_VALUE
363
#undef PRINT_INT_VALUE
364
#undef PRINT_UINT_VALUE
365
#undef PRINT_FLOAT_VALUE
366
}
367
368
if (!sanity_check_input_params())
369
return false;
370
371
if ((m_params.m_use_opencl) && opencl_is_available() && !m_pOpenCL_context && !m_opencl_failed)
372
{
373
m_pOpenCL_context = opencl_create_context();
374
if (!m_pOpenCL_context)
375
m_opencl_failed = true;
376
}
377
378
return true;
379
}
380
381
void basis_compressor::pick_format_mode()
382
{
383
// Unfortunately due to the legacy of this code and backwards compat this is more complex than I would like.
384
m_fmt_mode = basist::basis_tex_format::cETC1S;
385
386
if (m_params.m_hdr)
387
{
388
assert(m_params.m_uastc);
389
390
switch (m_params.m_hdr_mode)
391
{
392
case hdr_modes::cUASTC_HDR_4X4:
393
m_fmt_mode = basist::basis_tex_format::cUASTC_HDR_4x4;
394
break;
395
case hdr_modes::cASTC_HDR_6X6:
396
m_fmt_mode = basist::basis_tex_format::cASTC_HDR_6x6;
397
break;
398
case hdr_modes::cASTC_HDR_6X6_INTERMEDIATE:
399
m_fmt_mode = basist::basis_tex_format::cASTC_HDR_6x6_INTERMEDIATE;
400
break;
401
default:
402
assert(0);
403
break;
404
}
405
}
406
else if (m_params.m_uastc)
407
{
408
m_fmt_mode = basist::basis_tex_format::cUASTC4x4;
409
}
410
411
if (m_params.m_debug)
412
{
413
switch (m_fmt_mode)
414
{
415
case basist::basis_tex_format::cETC1S:
416
fmt_debug_printf("Format Mode: cETC1S\n");
417
break;
418
case basist::basis_tex_format::cUASTC4x4:
419
fmt_debug_printf("Format Mode: cUASTC4x4\n");
420
break;
421
case basist::basis_tex_format::cUASTC_HDR_4x4:
422
fmt_debug_printf("Format Mode: cUASTC_HDR_4x4\n");
423
break;
424
case basist::basis_tex_format::cASTC_HDR_6x6:
425
fmt_debug_printf("Format Mode: cASTC_HDR_6x6\n");
426
break;
427
case basist::basis_tex_format::cASTC_HDR_6x6_INTERMEDIATE:
428
fmt_debug_printf("Format Mode: cASTC_HDR_6x6_INTERMEDIATE\n");
429
break;
430
default:
431
assert(0);
432
break;
433
}
434
}
435
}
436
437
basis_compressor::error_code basis_compressor::process()
438
{
439
debug_printf("basis_compressor::process\n");
440
441
if (!read_dds_source_images())
442
return cECFailedReadingSourceImages;
443
444
// Note: After here m_params.m_hdr, m_params.m_uastc and m_fmt_mode cannot be changed.
445
pick_format_mode();
446
447
if (!read_source_images())
448
return cECFailedReadingSourceImages;
449
450
if (!validate_texture_type_constraints())
451
return cECFailedValidating;
452
453
if (m_params.m_create_ktx2_file)
454
{
455
if (!validate_ktx2_constraints())
456
{
457
error_printf("Inputs do not satisfy .KTX2 texture constraints: all source images must be the same resolution and have the same number of mipmap levels.\n");
458
return cECFailedValidating;
459
}
460
}
461
462
if (!extract_source_blocks())
463
return cECFailedFrontEnd;
464
465
if (m_params.m_hdr)
466
{
467
if (m_params.m_hdr_mode == hdr_modes::cUASTC_HDR_4X4)
468
{
469
// UASTC 4x4 HDR
470
if (m_params.m_status_output)
471
printf("Mode: UASTC 4x4 HDR Level %u\n", m_params.m_uastc_hdr_4x4_options.m_level);
472
473
error_code ec = encode_slices_to_uastc_4x4_hdr();
474
if (ec != cECSuccess)
475
return ec;
476
}
477
else
478
{
479
assert((m_params.m_hdr_mode == hdr_modes::cASTC_HDR_6X6) || (m_params.m_hdr_mode == hdr_modes::cASTC_HDR_6X6_INTERMEDIATE));
480
481
// ASTC 6x6 HDR
482
if (m_params.m_status_output)
483
{
484
fmt_printf("Mode: ASTC 6x6 HDR {}, Base Level: {}, Highest Level: {}, Lambda: {}, REC 2020: {}\n",
485
(m_params.m_hdr_mode == hdr_modes::cASTC_HDR_6X6_INTERMEDIATE) ? "Intermediate" : "",
486
m_params.m_astc_hdr_6x6_options.m_master_comp_level, m_params.m_astc_hdr_6x6_options.m_highest_comp_level,
487
m_params.m_astc_hdr_6x6_options.m_lambda, m_params.m_astc_hdr_6x6_options.m_rec2020_bt2100_color_gamut);
488
}
489
490
error_code ec = encode_slices_to_astc_6x6_hdr();
491
if (ec != cECSuccess)
492
return ec;
493
}
494
}
495
else if (m_params.m_uastc)
496
{
497
// UASTC 4x4 LDR
498
if (m_params.m_status_output)
499
printf("Mode: UASTC LDR 4x4 Level %u\n", m_params.m_pack_uastc_ldr_4x4_flags & cPackUASTCLevelMask);
500
501
error_code ec = encode_slices_to_uastc_4x4_ldr();
502
if (ec != cECSuccess)
503
return ec;
504
}
505
else
506
{
507
// ETC1S
508
if (m_params.m_status_output)
509
printf("Mode: ETC1S Quality %i, Level %i\n", m_params.m_etc1s_quality_level, (int)m_params.m_compression_level);
510
511
if (!process_frontend())
512
return cECFailedFrontEnd;
513
514
if (!extract_frontend_texture_data())
515
return cECFailedFontendExtract;
516
517
if (!process_backend())
518
return cECFailedBackend;
519
}
520
521
if (!create_basis_file_and_transcode())
522
return cECFailedCreateBasisFile;
523
524
if (m_params.m_create_ktx2_file)
525
{
526
if (!create_ktx2_file())
527
return cECFailedCreateKTX2File;
528
}
529
530
if (!write_output_files_and_compute_stats())
531
return cECFailedWritingOutput;
532
533
return cECSuccess;
534
}
535
536
basis_compressor::error_code basis_compressor::encode_slices_to_astc_6x6_hdr()
537
{
538
debug_printf("basis_compressor::encode_slices_to_astc_6x6_hdr\n");
539
540
interval_timer tm;
541
tm.start();
542
543
m_uastc_slice_textures.resize(m_slice_descs.size());
544
for (uint32_t slice_index = 0; slice_index < m_slice_descs.size(); slice_index++)
545
m_uastc_slice_textures[slice_index].init(texture_format::cASTC_HDR_6x6, m_slice_descs[slice_index].m_orig_width, m_slice_descs[slice_index].m_orig_height);
546
547
if (m_params.m_hdr_mode == hdr_modes::cASTC_HDR_6X6)
548
m_uastc_backend_output.m_tex_format = basist::basis_tex_format::cASTC_HDR_6x6;
549
else if (m_params.m_hdr_mode == hdr_modes::cASTC_HDR_6X6_INTERMEDIATE)
550
m_uastc_backend_output.m_tex_format = basist::basis_tex_format::cASTC_HDR_6x6_INTERMEDIATE;
551
else
552
{
553
assert(0);
554
return cECFailedEncodeUASTC;
555
}
556
557
m_uastc_backend_output.m_etc1s = false;
558
m_uastc_backend_output.m_srgb = false;
559
m_uastc_backend_output.m_slice_desc = m_slice_descs;
560
m_uastc_backend_output.m_slice_image_data.resize(m_slice_descs.size());
561
m_uastc_backend_output.m_slice_image_crcs.resize(m_slice_descs.size());
562
563
astc_6x6_hdr::astc_hdr_6x6_global_config global_cfg(m_params.m_astc_hdr_6x6_options);
564
565
global_cfg.m_image_stats = m_params.m_compute_stats;
566
global_cfg.m_debug_images = m_params.m_debug_images;
567
global_cfg.m_output_images = m_params.m_debug_images;
568
global_cfg.m_debug_output = m_params.m_debug;
569
global_cfg.m_status_output = m_params.m_status_output || m_params.m_debug;
570
571
for (uint32_t slice_index = 0; slice_index < m_slice_descs.size(); slice_index++)
572
{
573
gpu_image& dst_tex = m_uastc_slice_textures[slice_index];
574
uint8_vec &dst_buf = m_uastc_backend_output.m_slice_image_data[slice_index];
575
576
basisu_backend_slice_desc& slice_desc = m_slice_descs[slice_index];
577
(void)slice_desc;
578
579
const imagef& source_image = m_slice_images_hdr[slice_index];
580
assert(source_image.get_width() && source_image.get_height());
581
582
uint8_vec intermediate_tex_data, astc_tex_data;
583
584
global_cfg.m_debug_image_prefix = m_params.m_astc_hdr_6x6_options.m_debug_image_prefix;
585
global_cfg.m_debug_image_prefix += fmt_string("slice_{}_", slice_index);
586
587
global_cfg.m_output_image_prefix = m_params.m_astc_hdr_6x6_options.m_output_image_prefix;
588
global_cfg.m_output_image_prefix += fmt_string("slice_{}_", slice_index);
589
590
if (m_params.m_debug)
591
fmt_debug_printf("----------------------------------------------------------------------------\n");
592
593
astc_6x6_hdr::result_metrics metrics;
594
bool status = astc_6x6_hdr::compress_photo(source_image, global_cfg, m_params.m_pJob_pool, intermediate_tex_data, astc_tex_data, metrics);
595
if (!status)
596
return cECFailedEncodeUASTC;
597
598
if (m_params.m_debug)
599
fmt_debug_printf("----------------------------------------------------------------------------\n");
600
601
// Currently it always gives us both intermediate and RDO
602
assert(intermediate_tex_data.size());
603
assert(astc_tex_data.size());
604
assert((astc_tex_data.size() & 15) == 0);
605
assert(dst_tex.get_size_in_bytes() == astc_tex_data.size_in_bytes());
606
607
memcpy(dst_tex.get_ptr(), astc_tex_data.data(), astc_tex_data.size_in_bytes());
608
609
if (m_params.m_hdr_mode == hdr_modes::cASTC_HDR_6X6)
610
{
611
dst_buf.resize(dst_tex.get_size_in_bytes());
612
memcpy(&dst_buf[0], dst_tex.get_ptr(), dst_tex.get_size_in_bytes());
613
}
614
else
615
{
616
assert(m_params.m_hdr_mode == hdr_modes::cASTC_HDR_6X6_INTERMEDIATE);
617
618
dst_buf.resize(intermediate_tex_data.size_in_bytes());
619
memcpy(&dst_buf[0], intermediate_tex_data.get_ptr(), intermediate_tex_data.size_in_bytes());
620
}
621
622
m_uastc_backend_output.m_slice_image_crcs[slice_index] = basist::crc16(dst_buf.get_ptr(), dst_buf.size_in_bytes(), 0);
623
}
624
625
return cECSuccess;
626
}
627
628
basis_compressor::error_code basis_compressor::encode_slices_to_uastc_4x4_hdr()
629
{
630
debug_printf("basis_compressor::encode_slices_to_uastc_4x4_hdr\n");
631
632
interval_timer tm;
633
tm.start();
634
635
m_uastc_slice_textures.resize(m_slice_descs.size());
636
for (uint32_t slice_index = 0; slice_index < m_slice_descs.size(); slice_index++)
637
m_uastc_slice_textures[slice_index].init(texture_format::cUASTC_HDR_4x4, m_slice_descs[slice_index].m_orig_width, m_slice_descs[slice_index].m_orig_height);
638
639
m_uastc_backend_output.m_tex_format = basist::basis_tex_format::cUASTC_HDR_4x4;
640
m_uastc_backend_output.m_etc1s = false;
641
m_uastc_backend_output.m_srgb = false;
642
m_uastc_backend_output.m_slice_desc = m_slice_descs;
643
m_uastc_backend_output.m_slice_image_data.resize(m_slice_descs.size());
644
m_uastc_backend_output.m_slice_image_crcs.resize(m_slice_descs.size());
645
646
if (!m_params.m_perceptual)
647
{
648
m_params.m_uastc_hdr_4x4_options.m_r_err_scale = 1.0f;
649
m_params.m_uastc_hdr_4x4_options.m_g_err_scale = 1.0f;
650
}
651
652
const float DEFAULT_BC6H_ERROR_WEIGHT = .65f;// .85f;
653
const float LOWEST_BC6H_ERROR_WEIGHT = .1f;
654
m_params.m_uastc_hdr_4x4_options.m_bc6h_err_weight = m_params.m_hdr_favor_astc ? LOWEST_BC6H_ERROR_WEIGHT : DEFAULT_BC6H_ERROR_WEIGHT;
655
656
std::atomic<bool> any_failures;
657
any_failures.store(false);
658
659
astc_hdr_4x4_block_stats enc_stats;
660
661
struct uastc_blk_desc
662
{
663
uint32_t m_solid_flag;
664
uint32_t m_num_partitions;
665
uint32_t m_cem_index;
666
uint32_t m_weight_ise_range;
667
uint32_t m_endpoint_ise_range;
668
669
bool operator< (const uastc_blk_desc& desc) const
670
{
671
if (this == &desc)
672
return false;
673
674
#define COMP(XX) if (XX < desc.XX) return true; else if (XX != desc.XX) return false;
675
COMP(m_solid_flag)
676
COMP(m_num_partitions)
677
COMP(m_cem_index)
678
COMP(m_weight_ise_range)
679
COMP(m_endpoint_ise_range)
680
#undef COMP
681
682
return false;
683
}
684
685
bool operator== (const uastc_blk_desc& desc) const
686
{
687
if (this == &desc)
688
return true;
689
if ((*this < desc) || (desc < *this))
690
return false;
691
return true;
692
}
693
694
bool operator!= (const uastc_blk_desc& desc) const
695
{
696
return !(*this == desc);
697
}
698
};
699
700
struct uastc_blk_desc_stats
701
{
702
uastc_blk_desc_stats() : m_count(0) { }
703
uint32_t m_count;
704
#ifdef UASTC_HDR_DEBUG_SAVE_CATEGORIZED_BLOCKS
705
basisu::vector<basist::astc_blk> m_blks;
706
#endif
707
};
708
709
std::map<uastc_blk_desc, uastc_blk_desc_stats> unique_block_descs;
710
std::mutex unique_block_desc_mutex;
711
712
for (uint32_t slice_index = 0; slice_index < m_slice_descs.size(); slice_index++)
713
{
714
gpu_image& tex = m_uastc_slice_textures[slice_index];
715
basisu_backend_slice_desc& slice_desc = m_slice_descs[slice_index];
716
(void)slice_desc;
717
718
const uint32_t num_blocks_x = tex.get_blocks_x();
719
const uint32_t num_blocks_y = tex.get_blocks_y();
720
const uint32_t total_blocks = tex.get_total_blocks();
721
const imagef& source_image = m_slice_images_hdr[slice_index];
722
723
std::atomic<uint32_t> total_blocks_processed;
724
total_blocks_processed.store(0);
725
726
const uint32_t N = 256;
727
for (uint32_t block_index_iter = 0; block_index_iter < total_blocks; block_index_iter += N)
728
{
729
const uint32_t first_index = block_index_iter;
730
const uint32_t last_index = minimum<uint32_t>(total_blocks, block_index_iter + N);
731
732
m_params.m_pJob_pool->add_job([this, first_index, last_index, num_blocks_x, num_blocks_y, total_blocks, &source_image,
733
&tex, &total_blocks_processed, &any_failures, &enc_stats, &unique_block_descs, &unique_block_desc_mutex]
734
{
735
BASISU_NOTE_UNUSED(num_blocks_y);
736
737
basisu::vector<astc_hdr_4x4_pack_results> all_results;
738
all_results.reserve(256);
739
740
for (uint32_t block_index = first_index; block_index < last_index; block_index++)
741
{
742
const uint32_t block_x = block_index % num_blocks_x;
743
const uint32_t block_y = block_index / num_blocks_x;
744
745
//if ((block_x == 176) && (block_y == 128))
746
// printf("!");
747
748
vec4F block_pixels[16];
749
750
source_image.extract_block_clamped(&block_pixels[0], block_x * 4, block_y * 4, 4, 4);
751
752
basist::astc_blk& dest_block = *(basist::astc_blk*)tex.get_block_ptr(block_x, block_y);
753
754
float rgb_pixels[16 * 3];
755
basist::half_float rgb_pixels_half[16 * 3];
756
for (uint32_t i = 0; i < 16; i++)
757
{
758
rgb_pixels[i * 3 + 0] = block_pixels[i][0];
759
rgb_pixels_half[i * 3 + 0] = float_to_half_non_neg_no_nan_inf(block_pixels[i][0]);
760
761
rgb_pixels[i * 3 + 1] = block_pixels[i][1];
762
rgb_pixels_half[i * 3 + 1] = float_to_half_non_neg_no_nan_inf(block_pixels[i][1]);
763
764
rgb_pixels[i * 3 + 2] = block_pixels[i][2];
765
rgb_pixels_half[i * 3 + 2] = float_to_half_non_neg_no_nan_inf(block_pixels[i][2]);
766
}
767
768
bool status = astc_hdr_4x4_enc_block(&rgb_pixels[0], rgb_pixels_half, m_params.m_uastc_hdr_4x4_options, all_results);
769
if (!status)
770
{
771
any_failures.store(true);
772
continue;
773
}
774
775
double best_err = 1e+30f;
776
int best_result_index = -1;
777
778
const double bc6h_err_weight = m_params.m_uastc_hdr_4x4_options.m_bc6h_err_weight;
779
const double astc_err_weight = (1.0f - bc6h_err_weight);
780
781
for (uint32_t i = 0; i < all_results.size(); i++)
782
{
783
basist::half_float unpacked_bc6h_block[4 * 4 * 3];
784
unpack_bc6h(&all_results[i].m_bc6h_block, unpacked_bc6h_block, false);
785
786
all_results[i].m_bc6h_block_error = compute_block_error(16, rgb_pixels_half, unpacked_bc6h_block, m_params.m_uastc_hdr_4x4_options);
787
788
double overall_err = (all_results[i].m_bc6h_block_error * bc6h_err_weight) + (all_results[i].m_best_block_error * astc_err_weight);
789
790
if ((!i) || (overall_err < best_err))
791
{
792
best_err = overall_err;
793
best_result_index = i;
794
}
795
}
796
797
const astc_hdr_4x4_pack_results& best_results = all_results[best_result_index];
798
799
astc_hdr_4x4_pack_results_to_block(dest_block, best_results);
800
801
// Verify that this block is valid UASTC HDR and we can successfully transcode it to BC6H.
802
// (Well, except in fastest mode.)
803
if (m_params.m_uastc_hdr_4x4_options.m_level > 0)
804
{
805
basist::bc6h_block transcoded_bc6h_blk;
806
bool transcode_results = astc_hdr_transcode_to_bc6h(dest_block, transcoded_bc6h_blk);
807
assert(transcode_results);
808
if ((!transcode_results) && (!any_failures))
809
{
810
error_printf("basis_compressor::encode_slices_to_uastc_4x4_hdr: UASTC HDR block transcode check failed!\n");
811
812
any_failures.store(true);
813
continue;
814
}
815
}
816
817
if (m_params.m_debug)
818
{
819
// enc_stats has its own mutex
820
enc_stats.update(best_results);
821
822
uastc_blk_desc blk_desc;
823
clear_obj(blk_desc);
824
825
blk_desc.m_solid_flag = best_results.m_is_solid;
826
if (!blk_desc.m_solid_flag)
827
{
828
blk_desc.m_num_partitions = best_results.m_best_blk.m_num_partitions;
829
blk_desc.m_cem_index = best_results.m_best_blk.m_color_endpoint_modes[0];
830
blk_desc.m_weight_ise_range = best_results.m_best_blk.m_weight_ise_range;
831
blk_desc.m_endpoint_ise_range = best_results.m_best_blk.m_endpoint_ise_range;
832
}
833
834
{
835
std::lock_guard<std::mutex> lck(unique_block_desc_mutex);
836
837
auto res = unique_block_descs.insert(std::make_pair(blk_desc, uastc_blk_desc_stats()));
838
839
(res.first)->second.m_count++;
840
#ifdef UASTC_HDR_DEBUG_SAVE_CATEGORIZED_BLOCKS
841
(res.first)->second.m_blks.push_back(dest_block);
842
#endif
843
}
844
}
845
846
total_blocks_processed++;
847
848
uint32_t val = total_blocks_processed;
849
if (((val & 1023) == 1023) && m_params.m_status_output)
850
{
851
debug_printf("basis_compressor::encode_slices_to_uastc_4x4_hdr: %3.1f%% done\n", static_cast<float>(val) * 100.0f / total_blocks);
852
}
853
}
854
855
});
856
857
} // block_index_iter
858
859
m_params.m_pJob_pool->wait_for_all();
860
861
if (any_failures)
862
return cECFailedEncodeUASTC;
863
864
m_uastc_backend_output.m_slice_image_data[slice_index].resize(tex.get_size_in_bytes());
865
memcpy(&m_uastc_backend_output.m_slice_image_data[slice_index][0], tex.get_ptr(), tex.get_size_in_bytes());
866
867
m_uastc_backend_output.m_slice_image_crcs[slice_index] = basist::crc16(tex.get_ptr(), tex.get_size_in_bytes(), 0);
868
869
} // slice_index
870
871
debug_printf("basis_compressor::encode_slices_to_uastc_4x4_hdr: Total time: %3.3f secs\n", tm.get_elapsed_secs());
872
873
if (m_params.m_debug)
874
{
875
debug_printf("\n----- Total unique UASTC block descs: %u\n", (uint32_t)unique_block_descs.size());
876
877
uint32_t c = 0;
878
for (auto it = unique_block_descs.begin(); it != unique_block_descs.end(); ++it)
879
{
880
debug_printf("%u. Total uses: %u %3.2f%%, solid color: %u\n", c, it->second.m_count,
881
((float)it->second.m_count * 100.0f) / enc_stats.m_total_blocks, it->first.m_solid_flag);
882
883
if (!it->first.m_solid_flag)
884
{
885
debug_printf(" Num partitions: %u\n", it->first.m_num_partitions);
886
debug_printf(" CEM index: %u\n", it->first.m_cem_index);
887
debug_printf(" Weight ISE range: %u (%u levels)\n", it->first.m_weight_ise_range, astc_helpers::get_ise_levels(it->first.m_weight_ise_range));
888
debug_printf(" Endpoint ISE range: %u (%u levels)\n", it->first.m_endpoint_ise_range, astc_helpers::get_ise_levels(it->first.m_endpoint_ise_range));
889
}
890
891
#ifdef UASTC_HDR_DEBUG_SAVE_CATEGORIZED_BLOCKS
892
debug_printf(" -- UASTC HDR block bytes:\n");
893
for (uint32_t j = 0; j < minimum<uint32_t>(4, it->second.m_blks.size()); j++)
894
{
895
basist::astc_blk& blk = it->second.m_blks[j];
896
897
debug_printf(" - UASTC HDR: { ");
898
for (uint32_t k = 0; k < 16; k++)
899
debug_printf("%u%s", ((const uint8_t*)&blk)[k], (k != 15) ? ", " : "");
900
debug_printf(" }\n");
901
902
basist::bc6h_block bc6h_blk;
903
bool res = astc_hdr_transcode_to_bc6h(blk, bc6h_blk);
904
assert(res);
905
if (!res)
906
{
907
error_printf("astc_hdr_transcode_to_bc6h() failed!\n");
908
return cECFailedEncodeUASTC;
909
}
910
911
debug_printf(" - BC6H: { ");
912
for (uint32_t k = 0; k < 16; k++)
913
debug_printf("%u%s", ((const uint8_t*)&bc6h_blk)[k], (k != 15) ? ", " : "");
914
debug_printf(" }\n");
915
}
916
#endif
917
918
c++;
919
}
920
printf("\n");
921
922
enc_stats.print();
923
}
924
925
return cECSuccess;
926
}
927
928
basis_compressor::error_code basis_compressor::encode_slices_to_uastc_4x4_ldr()
929
{
930
debug_printf("basis_compressor::encode_slices_to_uastc_4x4_ldr\n");
931
932
m_uastc_slice_textures.resize(m_slice_descs.size());
933
for (uint32_t slice_index = 0; slice_index < m_slice_descs.size(); slice_index++)
934
m_uastc_slice_textures[slice_index].init(texture_format::cUASTC4x4, m_slice_descs[slice_index].m_orig_width, m_slice_descs[slice_index].m_orig_height);
935
936
m_uastc_backend_output.m_tex_format = basist::basis_tex_format::cUASTC4x4;
937
m_uastc_backend_output.m_etc1s = false;
938
m_uastc_backend_output.m_slice_desc = m_slice_descs;
939
m_uastc_backend_output.m_slice_image_data.resize(m_slice_descs.size());
940
m_uastc_backend_output.m_slice_image_crcs.resize(m_slice_descs.size());
941
942
for (uint32_t slice_index = 0; slice_index < m_slice_descs.size(); slice_index++)
943
{
944
gpu_image& tex = m_uastc_slice_textures[slice_index];
945
basisu_backend_slice_desc& slice_desc = m_slice_descs[slice_index];
946
(void)slice_desc;
947
948
const uint32_t num_blocks_x = tex.get_blocks_x();
949
const uint32_t num_blocks_y = tex.get_blocks_y();
950
const uint32_t total_blocks = tex.get_total_blocks();
951
const image& source_image = m_slice_images[slice_index];
952
953
std::atomic<uint32_t> total_blocks_processed;
954
total_blocks_processed.store(0);
955
956
const uint32_t N = 256;
957
for (uint32_t block_index_iter = 0; block_index_iter < total_blocks; block_index_iter += N)
958
{
959
const uint32_t first_index = block_index_iter;
960
const uint32_t last_index = minimum<uint32_t>(total_blocks, block_index_iter + N);
961
962
m_params.m_pJob_pool->add_job([this, first_index, last_index, num_blocks_x, num_blocks_y, total_blocks, &source_image, &tex, &total_blocks_processed]
963
{
964
BASISU_NOTE_UNUSED(num_blocks_y);
965
966
uint32_t uastc_flags = m_params.m_pack_uastc_ldr_4x4_flags;
967
if ((m_params.m_rdo_uastc_ldr_4x4) && (m_params.m_rdo_uastc_ldr_4x4_favor_simpler_modes_in_rdo_mode))
968
uastc_flags |= cPackUASTCFavorSimplerModes;
969
970
for (uint32_t block_index = first_index; block_index < last_index; block_index++)
971
{
972
const uint32_t block_x = block_index % num_blocks_x;
973
const uint32_t block_y = block_index / num_blocks_x;
974
975
color_rgba block_pixels[4][4];
976
977
source_image.extract_block_clamped((color_rgba*)block_pixels, block_x * 4, block_y * 4, 4, 4);
978
979
basist::uastc_block& dest_block = *(basist::uastc_block*)tex.get_block_ptr(block_x, block_y);
980
981
encode_uastc(&block_pixels[0][0].r, dest_block, uastc_flags);
982
983
total_blocks_processed++;
984
985
uint32_t val = total_blocks_processed;
986
if (((val & 16383) == 16383) && m_params.m_status_output)
987
{
988
debug_printf("basis_compressor::encode_slices_to_uastc_4x4_ldr: %3.1f%% done\n", static_cast<float>(val) * 100.0f / total_blocks);
989
}
990
991
}
992
993
});
994
995
} // block_index_iter
996
997
m_params.m_pJob_pool->wait_for_all();
998
999
if (m_params.m_rdo_uastc_ldr_4x4)
1000
{
1001
uastc_rdo_params rdo_params;
1002
rdo_params.m_lambda = m_params.m_rdo_uastc_ldr_4x4_quality_scalar;
1003
rdo_params.m_max_allowed_rms_increase_ratio = m_params.m_rdo_uastc_ldr_4x4_max_allowed_rms_increase_ratio;
1004
rdo_params.m_skip_block_rms_thresh = m_params.m_rdo_uastc_ldr_4x4_skip_block_rms_thresh;
1005
rdo_params.m_lz_dict_size = m_params.m_rdo_uastc_ldr_4x4_dict_size;
1006
rdo_params.m_smooth_block_max_error_scale = m_params.m_rdo_uastc_ldr_4x4_max_smooth_block_error_scale;
1007
rdo_params.m_max_smooth_block_std_dev = m_params.m_rdo_uastc_ldr_4x4_smooth_block_max_std_dev;
1008
1009
bool status = uastc_rdo(tex.get_total_blocks(), (basist::uastc_block*)tex.get_ptr(),
1010
(const color_rgba *)m_source_blocks[slice_desc.m_first_block_index].m_pixels, rdo_params, m_params.m_pack_uastc_ldr_4x4_flags, m_params.m_rdo_uastc_ldr_4x4_multithreading ? m_params.m_pJob_pool : nullptr,
1011
(m_params.m_rdo_uastc_ldr_4x4_multithreading && m_params.m_pJob_pool) ? basisu::minimum<uint32_t>(4, (uint32_t)m_params.m_pJob_pool->get_total_threads()) : 0);
1012
if (!status)
1013
{
1014
return cECFailedUASTCRDOPostProcess;
1015
}
1016
}
1017
1018
m_uastc_backend_output.m_slice_image_data[slice_index].resize(tex.get_size_in_bytes());
1019
memcpy(&m_uastc_backend_output.m_slice_image_data[slice_index][0], tex.get_ptr(), tex.get_size_in_bytes());
1020
1021
m_uastc_backend_output.m_slice_image_crcs[slice_index] = basist::crc16(tex.get_ptr(), tex.get_size_in_bytes(), 0);
1022
1023
} // slice_index
1024
1025
return cECSuccess;
1026
}
1027
1028
bool basis_compressor::generate_mipmaps(const imagef& img, basisu::vector<imagef>& mips, bool has_alpha)
1029
{
1030
debug_printf("basis_compressor::generate_mipmaps\n");
1031
1032
interval_timer tm;
1033
tm.start();
1034
1035
uint32_t total_levels = 1;
1036
uint32_t w = img.get_width(), h = img.get_height();
1037
while (maximum<uint32_t>(w, h) > (uint32_t)m_params.m_mip_smallest_dimension)
1038
{
1039
w = maximum(w >> 1U, 1U);
1040
h = maximum(h >> 1U, 1U);
1041
total_levels++;
1042
}
1043
1044
for (uint32_t level = 1; level < total_levels; level++)
1045
{
1046
const uint32_t level_width = maximum<uint32_t>(1, img.get_width() >> level);
1047
const uint32_t level_height = maximum<uint32_t>(1, img.get_height() >> level);
1048
1049
imagef& level_img = *enlarge_vector(mips, 1);
1050
level_img.resize(level_width, level_height);
1051
1052
const imagef* pSource_image = &img;
1053
1054
if (m_params.m_mip_fast)
1055
{
1056
if (level > 1)
1057
pSource_image = &mips[level - 1];
1058
}
1059
1060
bool status = image_resample(*pSource_image, level_img,
1061
//m_params.m_mip_filter.c_str(),
1062
"box", // TODO: negative lobes in the filter are causing negative colors, try Mitchell
1063
m_params.m_mip_scale, m_params.m_mip_wrapping, 0, has_alpha ? 4 : 3);
1064
if (!status)
1065
{
1066
error_printf("basis_compressor::generate_mipmaps: image_resample() failed!\n");
1067
return false;
1068
}
1069
1070
clean_hdr_image(level_img);
1071
}
1072
1073
if (m_params.m_debug)
1074
debug_printf("Total mipmap generation time: %3.3f secs\n", tm.get_elapsed_secs());
1075
1076
return true;
1077
}
1078
1079
bool basis_compressor::generate_mipmaps(const image &img, basisu::vector<image> &mips, bool has_alpha)
1080
{
1081
debug_printf("basis_compressor::generate_mipmaps\n");
1082
1083
interval_timer tm;
1084
tm.start();
1085
1086
uint32_t total_levels = 1;
1087
uint32_t w = img.get_width(), h = img.get_height();
1088
while (maximum<uint32_t>(w, h) > (uint32_t)m_params.m_mip_smallest_dimension)
1089
{
1090
w = maximum(w >> 1U, 1U);
1091
h = maximum(h >> 1U, 1U);
1092
total_levels++;
1093
}
1094
1095
#if BASISU_USE_STB_IMAGE_RESIZE_FOR_MIPMAP_GEN
1096
// Requires stb_image_resize
1097
stbir_filter filter = STBIR_FILTER_DEFAULT;
1098
if (m_params.m_mip_filter == "box")
1099
filter = STBIR_FILTER_BOX;
1100
else if (m_params.m_mip_filter == "triangle")
1101
filter = STBIR_FILTER_TRIANGLE;
1102
else if (m_params.m_mip_filter == "cubic")
1103
filter = STBIR_FILTER_CUBICBSPLINE;
1104
else if (m_params.m_mip_filter == "catmull")
1105
filter = STBIR_FILTER_CATMULLROM;
1106
else if (m_params.m_mip_filter == "mitchell")
1107
filter = STBIR_FILTER_MITCHELL;
1108
1109
for (uint32_t level = 1; level < total_levels; level++)
1110
{
1111
const uint32_t level_width = maximum<uint32_t>(1, img.get_width() >> level);
1112
const uint32_t level_height = maximum<uint32_t>(1, img.get_height() >> level);
1113
1114
image &level_img = *enlarge_vector(mips, 1);
1115
level_img.resize(level_width, level_height);
1116
1117
int result = stbir_resize_uint8_generic(
1118
(const uint8_t *)img.get_ptr(), img.get_width(), img.get_height(), img.get_pitch() * sizeof(color_rgba),
1119
(uint8_t *)level_img.get_ptr(), level_img.get_width(), level_img.get_height(), level_img.get_pitch() * sizeof(color_rgba),
1120
has_alpha ? 4 : 3, has_alpha ? 3 : STBIR_ALPHA_CHANNEL_NONE, m_params.m_mip_premultiplied ? STBIR_FLAG_ALPHA_PREMULTIPLIED : 0,
1121
m_params.m_mip_wrapping ? STBIR_EDGE_WRAP : STBIR_EDGE_CLAMP, filter, m_params.m_mip_srgb ? STBIR_COLORSPACE_SRGB : STBIR_COLORSPACE_LINEAR,
1122
nullptr);
1123
1124
if (result == 0)
1125
{
1126
error_printf("basis_compressor::generate_mipmaps: stbir_resize_uint8_generic() failed!\n");
1127
return false;
1128
}
1129
1130
if (m_params.m_mip_renormalize)
1131
level_img.renormalize_normal_map();
1132
}
1133
#else
1134
for (uint32_t level = 1; level < total_levels; level++)
1135
{
1136
const uint32_t level_width = maximum<uint32_t>(1, img.get_width() >> level);
1137
const uint32_t level_height = maximum<uint32_t>(1, img.get_height() >> level);
1138
1139
image& level_img = *enlarge_vector(mips, 1);
1140
level_img.resize(level_width, level_height);
1141
1142
const image* pSource_image = &img;
1143
1144
if (m_params.m_mip_fast)
1145
{
1146
if (level > 1)
1147
pSource_image = &mips[level - 1];
1148
}
1149
1150
bool status = image_resample(*pSource_image, level_img, m_params.m_mip_srgb, m_params.m_mip_filter.c_str(), m_params.m_mip_scale, m_params.m_mip_wrapping, 0, has_alpha ? 4 : 3);
1151
if (!status)
1152
{
1153
error_printf("basis_compressor::generate_mipmaps: image_resample() failed!\n");
1154
return false;
1155
}
1156
1157
if (m_params.m_mip_renormalize)
1158
level_img.renormalize_normal_map();
1159
}
1160
#endif
1161
1162
if (m_params.m_debug)
1163
debug_printf("Total mipmap generation time: %3.3f secs\n", tm.get_elapsed_secs());
1164
1165
return true;
1166
}
1167
1168
void basis_compressor::clean_hdr_image(imagef& src_img)
1169
{
1170
const uint32_t width = src_img.get_width();
1171
const uint32_t height = src_img.get_height();
1172
1173
float max_used_val = 0.0f;
1174
for (uint32_t y = 0; y < height; y++)
1175
{
1176
for (uint32_t x = 0; x < width; x++)
1177
{
1178
vec4F& c = src_img(x, y);
1179
for (uint32_t i = 0; i < 3; i++)
1180
max_used_val = maximum(max_used_val, c[i]);
1181
}
1182
}
1183
1184
double hdr_image_scale = 1.0f;
1185
if (max_used_val > basist::ASTC_HDR_MAX_VAL)
1186
{
1187
hdr_image_scale = max_used_val / basist::ASTC_HDR_MAX_VAL;
1188
1189
const double inv_hdr_image_scale = basist::ASTC_HDR_MAX_VAL / max_used_val;
1190
1191
for (uint32_t y = 0; y < src_img.get_height(); y++)
1192
{
1193
for (uint32_t x = 0; x < src_img.get_width(); x++)
1194
{
1195
vec4F& c = src_img(x, y);
1196
1197
for (uint32_t i = 0; i < 3; i++)
1198
c[i] = (float)minimum<double>(c[i] * inv_hdr_image_scale, basist::ASTC_HDR_MAX_VAL);
1199
}
1200
}
1201
1202
printf("Warning: The input HDR image's maximum used float value was %f, which is too high to encode as ASTC HDR. The image's components have been linearly scaled so the maximum used value is %f, by multiplying by %f.\n",
1203
max_used_val, basist::ASTC_HDR_MAX_VAL, inv_hdr_image_scale);
1204
1205
printf("The decoded ASTC HDR texture will have to be scaled up by %f.\n", hdr_image_scale);
1206
}
1207
1208
// TODO: Determine a constant scale factor, apply if > MAX_HALF_FLOAT
1209
if (!src_img.clean_astc_hdr_pixels(basist::ASTC_HDR_MAX_VAL))
1210
printf("Warning: clean_astc_hdr_pixels() had to modify the input image to encode to ASTC HDR - see previous warning(s).\n");
1211
1212
m_hdr_image_scale = (float)hdr_image_scale;
1213
1214
float lowest_nonzero_val = 1e+30f;
1215
float lowest_val = 1e+30f;
1216
float highest_val = -1e+30f;
1217
1218
for (uint32_t y = 0; y < src_img.get_height(); y++)
1219
{
1220
for (uint32_t x = 0; x < src_img.get_width(); x++)
1221
{
1222
const vec4F& c = src_img(x, y);
1223
1224
for (uint32_t i = 0; i < 3; i++)
1225
{
1226
lowest_val = basisu::minimum(lowest_val, c[i]);
1227
1228
if (c[i] != 0.0f)
1229
lowest_nonzero_val = basisu::minimum(lowest_nonzero_val, c[i]);
1230
1231
highest_val = basisu::maximum(highest_val, c[i]);
1232
}
1233
}
1234
}
1235
1236
debug_printf("Lowest image value: %e, lowest non-zero value: %e, highest value: %e, dynamic range: %e\n", lowest_val, lowest_nonzero_val, highest_val, highest_val / lowest_nonzero_val);
1237
}
1238
1239
bool basis_compressor::read_dds_source_images()
1240
{
1241
debug_printf("basis_compressor::read_dds_source_images\n");
1242
1243
// Nothing to do if the caller doesn't want us reading source images.
1244
if ((!m_params.m_read_source_images) || (!m_params.m_source_filenames.size()))
1245
return true;
1246
1247
// Just bail of the caller has specified their own source images.
1248
if (m_params.m_source_images.size() || m_params.m_source_images_hdr.size())
1249
return true;
1250
1251
if (m_params.m_source_mipmap_images.size() || m_params.m_source_mipmap_images_hdr.size())
1252
return true;
1253
1254
// See if any input filenames are .DDS
1255
bool any_dds = false, all_dds = true;
1256
for (uint32_t i = 0; i < m_params.m_source_filenames.size(); i++)
1257
{
1258
std::string ext(string_get_extension(m_params.m_source_filenames[i]));
1259
if (strcasecmp(ext.c_str(), "dds") == 0)
1260
any_dds = true;
1261
else
1262
all_dds = false;
1263
}
1264
1265
// Bail if no .DDS files specified.
1266
if (!any_dds)
1267
return true;
1268
1269
// If any input is .DDS they all must be .DDS, for simplicity.
1270
if (!all_dds)
1271
{
1272
error_printf("If any filename is DDS, all filenames must be DDS.\n");
1273
return false;
1274
}
1275
1276
// Can't jam in alpha channel images if any .DDS files specified.
1277
if (m_params.m_source_alpha_filenames.size())
1278
{
1279
error_printf("Source alpha filenames are not supported in DDS mode.\n");
1280
return false;
1281
}
1282
1283
bool any_mipmaps = false;
1284
1285
// Read each .DDS texture file
1286
for (uint32_t i = 0; i < m_params.m_source_filenames.size(); i++)
1287
{
1288
basisu::vector<image> ldr_mips;
1289
basisu::vector<imagef> hdr_mips;
1290
bool status = read_uncompressed_dds_file(m_params.m_source_filenames[i].c_str(), ldr_mips, hdr_mips);
1291
if (!status)
1292
return false;
1293
1294
assert(ldr_mips.size() || hdr_mips.size());
1295
1296
if (m_params.m_status_output)
1297
{
1298
printf("Read DDS file \"%s\", %s, %ux%u, %zu mipmap levels\n",
1299
m_params.m_source_filenames[i].c_str(),
1300
ldr_mips.size() ? "LDR" : "HDR",
1301
ldr_mips.size() ? ldr_mips[0].get_width() : hdr_mips[0].get_width(),
1302
ldr_mips.size() ? ldr_mips[0].get_height() : hdr_mips[0].get_height(),
1303
ldr_mips.size() ? ldr_mips.size() : hdr_mips.size());
1304
}
1305
1306
if (ldr_mips.size())
1307
{
1308
if (m_params.m_source_images_hdr.size())
1309
{
1310
error_printf("All DDS files must be of the same type (all LDR, or all HDR)\n");
1311
return false;
1312
}
1313
1314
m_params.m_source_images.push_back(ldr_mips[0]);
1315
m_params.m_source_mipmap_images.resize(m_params.m_source_mipmap_images.size() + 1);
1316
1317
if (ldr_mips.size() > 1)
1318
{
1319
ldr_mips.erase_index(0U);
1320
1321
m_params.m_source_mipmap_images.back().swap(ldr_mips);
1322
1323
any_mipmaps = true;
1324
}
1325
}
1326
else
1327
{
1328
if (m_params.m_source_images.size())
1329
{
1330
error_printf("All DDS files must be of the same type (all LDR, or all HDR)\n");
1331
return false;
1332
}
1333
1334
m_params.m_source_images_hdr.push_back(hdr_mips[0]);
1335
m_params.m_source_mipmap_images_hdr.resize(m_params.m_source_mipmap_images_hdr.size() + 1);
1336
1337
if (hdr_mips.size() > 1)
1338
{
1339
hdr_mips.erase_index(0U);
1340
1341
m_params.m_source_mipmap_images_hdr.back().swap(hdr_mips);
1342
1343
any_mipmaps = true;
1344
}
1345
1346
m_params.m_hdr = true;
1347
m_params.m_uastc = true;
1348
}
1349
}
1350
1351
m_params.m_read_source_images = false;
1352
m_params.m_source_filenames.clear();
1353
m_params.m_source_alpha_filenames.clear();
1354
1355
if (!any_mipmaps)
1356
{
1357
m_params.m_source_mipmap_images.clear();
1358
m_params.m_source_mipmap_images_hdr.clear();
1359
}
1360
1361
if ((m_params.m_hdr) && (!m_params.m_source_images_hdr.size()))
1362
{
1363
error_printf("HDR mode enabled, but only LDR .DDS files were loaded. HDR mode requires half or float (HDR) .DDS inputs.\n");
1364
return false;
1365
}
1366
1367
return true;
1368
}
1369
1370
bool basis_compressor::read_source_images()
1371
{
1372
debug_printf("basis_compressor::read_source_images\n");
1373
1374
const uint32_t total_source_files = m_params.m_read_source_images ? (uint32_t)m_params.m_source_filenames.size() :
1375
(m_params.m_hdr ? (uint32_t)m_params.m_source_images_hdr.size() : (uint32_t)m_params.m_source_images.size());
1376
1377
if (!total_source_files)
1378
{
1379
debug_printf("basis_compressor::read_source_images: No source images to process\n");
1380
1381
return false;
1382
}
1383
1384
m_stats.resize(0);
1385
m_slice_descs.resize(0);
1386
m_slice_images.resize(0);
1387
m_slice_images_hdr.resize(0);
1388
1389
m_total_blocks = 0;
1390
uint32_t total_macroblocks = 0;
1391
1392
m_any_source_image_has_alpha = false;
1393
1394
basisu::vector<image> source_images;
1395
basisu::vector<imagef> source_images_hdr;
1396
1397
basisu::vector<std::string> source_filenames;
1398
1399
// TODO: Note HDR images don't support alpha here, currently.
1400
1401
// First load all source images, and determine if any have an alpha channel.
1402
for (uint32_t source_file_index = 0; source_file_index < total_source_files; source_file_index++)
1403
{
1404
const char* pSource_filename = "";
1405
1406
image file_image;
1407
imagef file_image_hdr;
1408
1409
if (m_params.m_read_source_images)
1410
{
1411
pSource_filename = m_params.m_source_filenames[source_file_index].c_str();
1412
1413
// Load the source image
1414
if (m_params.m_hdr)
1415
{
1416
float upconversion_nit_multiplier = m_params.m_ldr_hdr_upconversion_nit_multiplier;
1417
if (upconversion_nit_multiplier == 0.0f)
1418
{
1419
// Note: We used to use a normalized nit multiplier of 1.0 for UASTC HDR 4x4. We're now writing upconverted output files in absolute luminance (100 nits).
1420
upconversion_nit_multiplier = LDR_TO_HDR_NITS;
1421
}
1422
1423
m_ldr_to_hdr_upconversion_nit_multiplier = upconversion_nit_multiplier;
1424
if (!is_image_filename_hdr(pSource_filename))
1425
m_upconverted_any_ldr_images = true;
1426
1427
if (!load_image_hdr(pSource_filename, file_image_hdr, m_params.m_ldr_hdr_upconversion_srgb_to_linear, upconversion_nit_multiplier, m_params.m_ldr_hdr_upconversion_black_bias))
1428
{
1429
error_printf("Failed reading source image: %s\n", pSource_filename);
1430
return false;
1431
}
1432
1433
// TODO: For now, just slam alpha to 1.0f. None of our HDR encoders support alpha yet.
1434
for (uint32_t y = 0; y < file_image_hdr.get_height(); y++)
1435
for (uint32_t x = 0; x < file_image_hdr.get_width(); x++)
1436
file_image_hdr(x, y)[3] = 1.0f;
1437
}
1438
else
1439
{
1440
if (!load_image(pSource_filename, file_image))
1441
{
1442
error_printf("Failed reading source image: %s\n", pSource_filename);
1443
return false;
1444
}
1445
}
1446
1447
const uint32_t width = m_params.m_hdr ? file_image_hdr.get_width() : file_image.get_width();
1448
const uint32_t height = m_params.m_hdr ? file_image_hdr.get_height() : file_image.get_height();
1449
1450
if (m_params.m_status_output)
1451
{
1452
printf("Read source image \"%s\", %ux%u\n", pSource_filename, width, height);
1453
}
1454
1455
if (m_params.m_hdr)
1456
{
1457
clean_hdr_image(file_image_hdr);
1458
}
1459
else
1460
{
1461
// Optionally load another image and put a grayscale version of it into the alpha channel.
1462
if ((source_file_index < m_params.m_source_alpha_filenames.size()) && (m_params.m_source_alpha_filenames[source_file_index].size()))
1463
{
1464
const char* pSource_alpha_image = m_params.m_source_alpha_filenames[source_file_index].c_str();
1465
1466
image alpha_data;
1467
1468
if (!load_image(pSource_alpha_image, alpha_data))
1469
{
1470
error_printf("Failed reading source image: %s\n", pSource_alpha_image);
1471
return false;
1472
}
1473
1474
if (m_params.m_status_output)
1475
printf("Read source alpha image \"%s\", %ux%u\n", pSource_alpha_image, alpha_data.get_width(), alpha_data.get_height());
1476
1477
alpha_data.crop(width, height);
1478
1479
for (uint32_t y = 0; y < height; y++)
1480
for (uint32_t x = 0; x < width; x++)
1481
file_image(x, y).a = (uint8_t)alpha_data(x, y).get_709_luma();
1482
}
1483
}
1484
}
1485
else
1486
{
1487
if (m_params.m_hdr)
1488
{
1489
file_image_hdr = m_params.m_source_images_hdr[source_file_index];
1490
clean_hdr_image(file_image_hdr);
1491
}
1492
else
1493
{
1494
file_image = m_params.m_source_images[source_file_index];
1495
}
1496
}
1497
1498
if (!m_params.m_hdr)
1499
{
1500
if (m_params.m_renormalize)
1501
file_image.renormalize_normal_map();
1502
}
1503
1504
bool alpha_swizzled = false;
1505
1506
if (m_params.m_swizzle[0] != 0 ||
1507
m_params.m_swizzle[1] != 1 ||
1508
m_params.m_swizzle[2] != 2 ||
1509
m_params.m_swizzle[3] != 3)
1510
{
1511
if (!m_params.m_hdr)
1512
{
1513
// Used for XY normal maps in RG - puts X in color, Y in alpha
1514
for (uint32_t y = 0; y < file_image.get_height(); y++)
1515
{
1516
for (uint32_t x = 0; x < file_image.get_width(); x++)
1517
{
1518
const color_rgba& c = file_image(x, y);
1519
file_image(x, y).set_noclamp_rgba(c[m_params.m_swizzle[0]], c[m_params.m_swizzle[1]], c[m_params.m_swizzle[2]], c[m_params.m_swizzle[3]]);
1520
}
1521
}
1522
1523
alpha_swizzled = (m_params.m_swizzle[3] != 3);
1524
}
1525
else
1526
{
1527
// Used for XY normal maps in RG - puts X in color, Y in alpha
1528
for (uint32_t y = 0; y < file_image_hdr.get_height(); y++)
1529
{
1530
for (uint32_t x = 0; x < file_image_hdr.get_width(); x++)
1531
{
1532
const vec4F& c = file_image_hdr(x, y);
1533
1534
// For now, alpha is always 1.0f in UASTC HDR.
1535
file_image_hdr(x, y).set(c[m_params.m_swizzle[0]], c[m_params.m_swizzle[1]], c[m_params.m_swizzle[2]], 1.0f); // c[m_params.m_swizzle[3]]);
1536
}
1537
}
1538
}
1539
}
1540
1541
bool has_alpha = false;
1542
1543
if (!m_params.m_hdr)
1544
{
1545
if (m_params.m_force_alpha || alpha_swizzled)
1546
has_alpha = true;
1547
else if (!m_params.m_check_for_alpha)
1548
file_image.set_alpha(255);
1549
else if (file_image.has_alpha())
1550
has_alpha = true;
1551
1552
if (has_alpha)
1553
m_any_source_image_has_alpha = true;
1554
}
1555
1556
{
1557
const uint32_t width = m_params.m_hdr ? file_image_hdr.get_width() : file_image.get_width();
1558
const uint32_t height = m_params.m_hdr ? file_image_hdr.get_height() : file_image.get_height();
1559
1560
debug_printf("Source image index %u filename %s %ux%u has alpha: %u\n", source_file_index, pSource_filename, width, height, has_alpha);
1561
}
1562
1563
if (m_params.m_y_flip)
1564
{
1565
if (m_params.m_hdr)
1566
file_image_hdr.flip_y();
1567
else
1568
file_image.flip_y();
1569
}
1570
1571
#if DEBUG_CROP_TEXTURE_TO_64x64
1572
if (m_params.m_hdr)
1573
file_image_hdr.resize(64, 64);
1574
else
1575
file_image.resize(64, 64);
1576
#endif
1577
1578
if ((m_params.m_resample_width > 0) && (m_params.m_resample_height > 0))
1579
{
1580
int new_width = basisu::minimum<int>(m_params.m_resample_width, BASISU_MAX_SUPPORTED_TEXTURE_DIMENSION);
1581
int new_height = basisu::minimum<int>(m_params.m_resample_height, BASISU_MAX_SUPPORTED_TEXTURE_DIMENSION);
1582
1583
debug_printf("Resampling to %ix%i\n", new_width, new_height);
1584
1585
// TODO: A box filter - kaiser looks too sharp on video. Let the caller control this.
1586
if (m_params.m_hdr)
1587
{
1588
imagef temp_img(new_width, new_height);
1589
image_resample(file_image_hdr, temp_img, "box"); // "kaiser");
1590
clean_hdr_image(temp_img);
1591
temp_img.swap(file_image_hdr);
1592
}
1593
else
1594
{
1595
image temp_img(new_width, new_height);
1596
image_resample(file_image, temp_img, m_params.m_perceptual, "box"); // "kaiser");
1597
temp_img.swap(file_image);
1598
}
1599
}
1600
else if (m_params.m_resample_factor > 0.0f)
1601
{
1602
// TODO: A box filter - kaiser looks too sharp on video. Let the caller control this.
1603
if (m_params.m_hdr)
1604
{
1605
int new_width = basisu::minimum<int>(basisu::maximum(1, (int)ceilf(file_image_hdr.get_width() * m_params.m_resample_factor)), BASISU_MAX_SUPPORTED_TEXTURE_DIMENSION);
1606
int new_height = basisu::minimum<int>(basisu::maximum(1, (int)ceilf(file_image_hdr.get_height() * m_params.m_resample_factor)), BASISU_MAX_SUPPORTED_TEXTURE_DIMENSION);
1607
1608
debug_printf("Resampling to %ix%i\n", new_width, new_height);
1609
1610
imagef temp_img(new_width, new_height);
1611
image_resample(file_image_hdr, temp_img, "box"); // "kaiser");
1612
clean_hdr_image(temp_img);
1613
temp_img.swap(file_image_hdr);
1614
}
1615
else
1616
{
1617
int new_width = basisu::minimum<int>(basisu::maximum(1, (int)ceilf(file_image.get_width() * m_params.m_resample_factor)), BASISU_MAX_SUPPORTED_TEXTURE_DIMENSION);
1618
int new_height = basisu::minimum<int>(basisu::maximum(1, (int)ceilf(file_image.get_height() * m_params.m_resample_factor)), BASISU_MAX_SUPPORTED_TEXTURE_DIMENSION);
1619
1620
debug_printf("Resampling to %ix%i\n", new_width, new_height);
1621
1622
image temp_img(new_width, new_height);
1623
image_resample(file_image, temp_img, m_params.m_perceptual, "box"); // "kaiser");
1624
temp_img.swap(file_image);
1625
}
1626
}
1627
1628
const uint32_t width = m_params.m_hdr ? file_image_hdr.get_width() : file_image.get_width();
1629
const uint32_t height = m_params.m_hdr ? file_image_hdr.get_height() : file_image.get_height();
1630
1631
if ((!width) || (!height))
1632
{
1633
error_printf("basis_compressor::read_source_images: Source image has a zero width and/or height!\n");
1634
return false;
1635
}
1636
1637
if ((width > BASISU_MAX_SUPPORTED_TEXTURE_DIMENSION) || (height > BASISU_MAX_SUPPORTED_TEXTURE_DIMENSION))
1638
{
1639
error_printf("basis_compressor::read_source_images: Source image \"%s\" is too large!\n", pSource_filename);
1640
return false;
1641
}
1642
1643
if (!m_params.m_hdr)
1644
source_images.enlarge(1)->swap(file_image);
1645
else
1646
source_images_hdr.enlarge(1)->swap(file_image_hdr);
1647
1648
source_filenames.push_back(pSource_filename);
1649
}
1650
1651
// Check if the caller has generated their own mipmaps.
1652
if (m_params.m_hdr)
1653
{
1654
if (m_params.m_source_mipmap_images_hdr.size())
1655
{
1656
// Make sure they've passed us enough mipmap chains.
1657
if ((m_params.m_source_images_hdr.size() != m_params.m_source_mipmap_images_hdr.size()) || (total_source_files != m_params.m_source_images_hdr.size()))
1658
{
1659
error_printf("basis_compressor::read_source_images(): m_params.m_source_mipmap_images_hdr.size() must equal m_params.m_source_images_hdr.size()!\n");
1660
return false;
1661
}
1662
}
1663
}
1664
else
1665
{
1666
if (m_params.m_source_mipmap_images.size())
1667
{
1668
// Make sure they've passed us enough mipmap chains.
1669
if ((m_params.m_source_images.size() != m_params.m_source_mipmap_images.size()) || (total_source_files != m_params.m_source_images.size()))
1670
{
1671
error_printf("basis_compressor::read_source_images(): m_params.m_source_mipmap_images.size() must equal m_params.m_source_images.size()!\n");
1672
return false;
1673
}
1674
1675
// Check if any of the user-supplied mipmap levels has alpha.
1676
if (!m_any_source_image_has_alpha)
1677
{
1678
for (uint32_t source_file_index = 0; source_file_index < total_source_files; source_file_index++)
1679
{
1680
for (uint32_t mip_index = 0; mip_index < m_params.m_source_mipmap_images[source_file_index].size(); mip_index++)
1681
{
1682
const image& mip_img = m_params.m_source_mipmap_images[source_file_index][mip_index];
1683
1684
// Be sure to take into account any swizzling which will be applied.
1685
if (mip_img.has_alpha(m_params.m_swizzle[3]))
1686
{
1687
m_any_source_image_has_alpha = true;
1688
break;
1689
}
1690
}
1691
1692
if (m_any_source_image_has_alpha)
1693
break;
1694
}
1695
}
1696
}
1697
}
1698
1699
debug_printf("Any source image has alpha: %u\n", m_any_source_image_has_alpha);
1700
1701
// Now, for each source image, create the slices corresponding to that image.
1702
for (uint32_t source_file_index = 0; source_file_index < total_source_files; source_file_index++)
1703
{
1704
const std::string &source_filename = source_filenames[source_file_index];
1705
1706
basisu::vector<image> slices;
1707
basisu::vector<imagef> slices_hdr;
1708
1709
slices.reserve(32);
1710
slices_hdr.reserve(32);
1711
1712
// The first (largest) mipmap level.
1713
image *pFile_image = source_images.size() ? &source_images[source_file_index] : nullptr;
1714
imagef *pFile_image_hdr = source_images_hdr.size() ? &source_images_hdr[source_file_index] : nullptr;
1715
1716
// Reserve a slot for mip0.
1717
if (m_params.m_hdr)
1718
slices_hdr.resize(1);
1719
else
1720
slices.resize(1);
1721
1722
if ((!m_params.m_hdr) && (m_params.m_source_mipmap_images.size()))
1723
{
1724
// User-provided mipmaps for each layer or image in the texture array.
1725
for (uint32_t mip_index = 0; mip_index < m_params.m_source_mipmap_images[source_file_index].size(); mip_index++)
1726
{
1727
image& mip_img = m_params.m_source_mipmap_images[source_file_index][mip_index];
1728
1729
if ((m_params.m_swizzle[0] != 0) ||
1730
(m_params.m_swizzle[1] != 1) ||
1731
(m_params.m_swizzle[2] != 2) ||
1732
(m_params.m_swizzle[3] != 3))
1733
{
1734
// Used for XY normal maps in RG - puts X in color, Y in alpha
1735
for (uint32_t y = 0; y < mip_img.get_height(); y++)
1736
{
1737
for (uint32_t x = 0; x < mip_img.get_width(); x++)
1738
{
1739
const color_rgba& c = mip_img(x, y);
1740
mip_img(x, y).set_noclamp_rgba(c[m_params.m_swizzle[0]], c[m_params.m_swizzle[1]], c[m_params.m_swizzle[2]], c[m_params.m_swizzle[3]]);
1741
}
1742
}
1743
}
1744
1745
slices.push_back(mip_img);
1746
}
1747
}
1748
else if ((m_params.m_hdr) && (m_params.m_source_mipmap_images_hdr.size()))
1749
{
1750
// User-provided mipmaps for each layer or image in the texture array.
1751
for (uint32_t mip_index = 0; mip_index < m_params.m_source_mipmap_images_hdr[source_file_index].size(); mip_index++)
1752
{
1753
imagef& mip_img = m_params.m_source_mipmap_images_hdr[source_file_index][mip_index];
1754
1755
if ((m_params.m_swizzle[0] != 0) ||
1756
(m_params.m_swizzle[1] != 1) ||
1757
(m_params.m_swizzle[2] != 2) ||
1758
(m_params.m_swizzle[3] != 3))
1759
{
1760
// Used for XY normal maps in RG - puts X in color, Y in alpha
1761
for (uint32_t y = 0; y < mip_img.get_height(); y++)
1762
{
1763
for (uint32_t x = 0; x < mip_img.get_width(); x++)
1764
{
1765
const vec4F& c = mip_img(x, y);
1766
1767
// For now, HDR alpha is always 1.0f.
1768
mip_img(x, y).set(c[m_params.m_swizzle[0]], c[m_params.m_swizzle[1]], c[m_params.m_swizzle[2]], 1.0f); // c[m_params.m_swizzle[3]]);
1769
}
1770
}
1771
}
1772
1773
clean_hdr_image(mip_img);
1774
1775
slices_hdr.push_back(mip_img);
1776
}
1777
}
1778
else if (m_params.m_mip_gen)
1779
{
1780
// Automatically generate mipmaps.
1781
if (m_params.m_hdr)
1782
{
1783
if (!generate_mipmaps(*pFile_image_hdr, slices_hdr, m_any_source_image_has_alpha))
1784
return false;
1785
}
1786
else
1787
{
1788
if (!generate_mipmaps(*pFile_image, slices, m_any_source_image_has_alpha))
1789
return false;
1790
}
1791
}
1792
1793
// Swap in the largest mipmap level here to avoid copying it, because generate_mips() will change the array.
1794
// NOTE: file_image is now blank.
1795
if (m_params.m_hdr)
1796
slices_hdr[0].swap(*pFile_image_hdr);
1797
else
1798
slices[0].swap(*pFile_image);
1799
1800
uint_vec mip_indices(m_params.m_hdr ? slices_hdr.size() : slices.size());
1801
for (uint32_t i = 0; i < (m_params.m_hdr ? slices_hdr.size() : slices.size()); i++)
1802
mip_indices[i] = i;
1803
1804
if ((!m_params.m_hdr) && (m_any_source_image_has_alpha) && (!m_params.m_uastc))
1805
{
1806
// For ETC1S, if source has alpha, then even mips will have RGB, and odd mips will have alpha in RGB.
1807
basisu::vector<image> alpha_slices;
1808
uint_vec new_mip_indices;
1809
1810
alpha_slices.reserve(slices.size() * 2);
1811
1812
for (uint32_t i = 0; i < slices.size(); i++)
1813
{
1814
image lvl_rgb(slices[i]);
1815
image lvl_a(lvl_rgb);
1816
1817
for (uint32_t y = 0; y < lvl_a.get_height(); y++)
1818
{
1819
for (uint32_t x = 0; x < lvl_a.get_width(); x++)
1820
{
1821
uint8_t a = lvl_a(x, y).a;
1822
lvl_a(x, y).set_noclamp_rgba(a, a, a, 255);
1823
}
1824
}
1825
1826
lvl_rgb.set_alpha(255);
1827
1828
alpha_slices.push_back(lvl_rgb);
1829
new_mip_indices.push_back(i);
1830
1831
alpha_slices.push_back(lvl_a);
1832
new_mip_indices.push_back(i);
1833
}
1834
1835
slices.swap(alpha_slices);
1836
mip_indices.swap(new_mip_indices);
1837
}
1838
1839
if (m_params.m_hdr)
1840
{
1841
assert(slices_hdr.size() == mip_indices.size());
1842
}
1843
else
1844
{
1845
assert(slices.size() == mip_indices.size());
1846
}
1847
1848
for (uint32_t slice_index = 0; slice_index < (m_params.m_hdr ? slices_hdr.size() : slices.size()); slice_index++)
1849
{
1850
image *pSlice_image = m_params.m_hdr ? nullptr : &slices[slice_index];
1851
imagef *pSlice_image_hdr = m_params.m_hdr ? &slices_hdr[slice_index] : nullptr;
1852
1853
const uint32_t orig_width = m_params.m_hdr ? pSlice_image_hdr->get_width() : pSlice_image->get_width();
1854
const uint32_t orig_height = m_params.m_hdr ? pSlice_image_hdr->get_height() : pSlice_image->get_height();
1855
1856
bool is_alpha_slice = false;
1857
if ((!m_params.m_hdr) && (m_any_source_image_has_alpha))
1858
{
1859
if (m_params.m_uastc)
1860
{
1861
is_alpha_slice = pSlice_image->has_alpha();
1862
}
1863
else
1864
{
1865
is_alpha_slice = (slice_index & 1) != 0;
1866
}
1867
}
1868
1869
// Enlarge the source image to block boundaries, duplicating edge pixels if necessary to avoid introducing extra colors into blocks.
1870
if (m_params.m_hdr)
1871
{
1872
// Don't pad in 6x6 mode, the lower level compressor handles it.
1873
if (m_params.m_hdr_mode == hdr_modes::cUASTC_HDR_4X4)
1874
{
1875
pSlice_image_hdr->crop_dup_borders(pSlice_image_hdr->get_block_width(get_block_width()) * get_block_width(), pSlice_image_hdr->get_block_height(get_block_height()) * get_block_height());
1876
}
1877
}
1878
else
1879
{
1880
pSlice_image->crop_dup_borders(pSlice_image->get_block_width(get_block_width()) * get_block_width(), pSlice_image->get_block_height(get_block_height()) * get_block_height());
1881
}
1882
1883
if (m_params.m_debug_images)
1884
{
1885
if (m_params.m_hdr)
1886
write_exr(string_format("basis_debug_source_image_%u_slice_%u.exr", source_file_index, slice_index).c_str(), *pSlice_image_hdr, 3, 0);
1887
else
1888
save_png(string_format("basis_debug_source_image_%u_slice_%u.png", source_file_index, slice_index).c_str(), *pSlice_image);
1889
}
1890
1891
const size_t dest_image_index = (m_params.m_hdr ? m_slice_images_hdr.size() : m_slice_images.size());
1892
1893
enlarge_vector(m_stats, 1);
1894
1895
if (m_params.m_hdr)
1896
enlarge_vector(m_slice_images_hdr, 1);
1897
else
1898
enlarge_vector(m_slice_images, 1);
1899
1900
enlarge_vector(m_slice_descs, 1);
1901
1902
m_stats[dest_image_index].m_filename = source_filename.c_str();
1903
m_stats[dest_image_index].m_width = orig_width;
1904
m_stats[dest_image_index].m_height = orig_height;
1905
1906
debug_printf("****** Slice %u: mip %u, alpha_slice: %u, filename: \"%s\", original: %ux%u actual: %ux%u\n",
1907
m_slice_descs.size() - 1, mip_indices[slice_index], is_alpha_slice, source_filename.c_str(),
1908
orig_width, orig_height,
1909
m_params.m_hdr ? pSlice_image_hdr->get_width() : pSlice_image->get_width(),
1910
m_params.m_hdr ? pSlice_image_hdr->get_height() : pSlice_image->get_height());
1911
1912
basisu_backend_slice_desc& slice_desc = m_slice_descs[dest_image_index];
1913
1914
slice_desc.m_first_block_index = m_total_blocks;
1915
1916
slice_desc.m_orig_width = orig_width;
1917
slice_desc.m_orig_height = orig_height;
1918
1919
if (m_params.m_hdr)
1920
{
1921
slice_desc.m_width = pSlice_image_hdr->get_width();
1922
slice_desc.m_height = pSlice_image_hdr->get_height();
1923
1924
slice_desc.m_num_blocks_x = pSlice_image_hdr->get_block_width(get_block_width());
1925
slice_desc.m_num_blocks_y = pSlice_image_hdr->get_block_height(get_block_height());
1926
}
1927
else
1928
{
1929
slice_desc.m_width = pSlice_image->get_width();
1930
slice_desc.m_height = pSlice_image->get_height();
1931
1932
slice_desc.m_num_blocks_x = pSlice_image->get_block_width(get_block_width());
1933
slice_desc.m_num_blocks_y = pSlice_image->get_block_height(get_block_height());
1934
}
1935
1936
slice_desc.m_num_macroblocks_x = (slice_desc.m_num_blocks_x + 1) >> 1;
1937
slice_desc.m_num_macroblocks_y = (slice_desc.m_num_blocks_y + 1) >> 1;
1938
1939
slice_desc.m_source_file_index = source_file_index;
1940
1941
slice_desc.m_mip_index = mip_indices[slice_index];
1942
1943
slice_desc.m_alpha = is_alpha_slice;
1944
slice_desc.m_iframe = false;
1945
if (m_params.m_tex_type == basist::cBASISTexTypeVideoFrames)
1946
{
1947
slice_desc.m_iframe = (source_file_index == 0);
1948
}
1949
1950
m_total_blocks += slice_desc.m_num_blocks_x * slice_desc.m_num_blocks_y;
1951
total_macroblocks += slice_desc.m_num_macroblocks_x * slice_desc.m_num_macroblocks_y;
1952
1953
// Finally, swap in the slice's image to avoid copying it.
1954
// NOTE: slice_image is now blank.
1955
if (m_params.m_hdr)
1956
m_slice_images_hdr[dest_image_index].swap(*pSlice_image_hdr);
1957
else
1958
m_slice_images[dest_image_index].swap(*pSlice_image);
1959
1960
} // slice_index
1961
1962
} // source_file_index
1963
1964
debug_printf("Total blocks: %u, Total macroblocks: %u\n", m_total_blocks, total_macroblocks);
1965
1966
// Make sure we don't have too many slices
1967
if (m_slice_descs.size() > BASISU_MAX_SLICES)
1968
{
1969
error_printf("Too many slices!\n");
1970
return false;
1971
}
1972
1973
// Basic sanity check on the slices
1974
for (uint32_t i = 1; i < m_slice_descs.size(); i++)
1975
{
1976
const basisu_backend_slice_desc &prev_slice_desc = m_slice_descs[i - 1];
1977
const basisu_backend_slice_desc &slice_desc = m_slice_descs[i];
1978
1979
// Make sure images are in order
1980
int image_delta = (int)slice_desc.m_source_file_index - (int)prev_slice_desc.m_source_file_index;
1981
if (image_delta > 1)
1982
return false;
1983
1984
// Make sure mipmap levels are in order
1985
if (!image_delta)
1986
{
1987
int level_delta = (int)slice_desc.m_mip_index - (int)prev_slice_desc.m_mip_index;
1988
if (level_delta > 1)
1989
return false;
1990
}
1991
}
1992
1993
if (m_params.m_status_output)
1994
{
1995
printf("Total slices: %u\n", (uint32_t)m_slice_descs.size());
1996
}
1997
1998
for (uint32_t i = 0; i < m_slice_descs.size(); i++)
1999
{
2000
const basisu_backend_slice_desc &slice_desc = m_slice_descs[i];
2001
2002
if (m_params.m_status_output)
2003
{
2004
printf("Slice: %u, alpha: %u, orig width/height: %ux%u, width/height: %ux%u, first_block: %u, image_index: %u, mip_level: %u, iframe: %u\n",
2005
i, slice_desc.m_alpha, slice_desc.m_orig_width, slice_desc.m_orig_height,
2006
slice_desc.m_width, slice_desc.m_height,
2007
slice_desc.m_first_block_index, slice_desc.m_source_file_index, slice_desc.m_mip_index, slice_desc.m_iframe);
2008
}
2009
2010
if (m_any_source_image_has_alpha)
2011
{
2012
// HDR doesn't support alpha yet
2013
if (m_params.m_hdr)
2014
return false;
2015
2016
if (!m_params.m_uastc)
2017
{
2018
// For ETC1S, alpha slices must be at odd slice indices.
2019
if (slice_desc.m_alpha)
2020
{
2021
if ((i & 1) == 0)
2022
return false;
2023
2024
const basisu_backend_slice_desc& prev_slice_desc = m_slice_descs[i - 1];
2025
2026
// Make sure previous slice has this image's color data
2027
if (prev_slice_desc.m_source_file_index != slice_desc.m_source_file_index)
2028
return false;
2029
if (prev_slice_desc.m_alpha)
2030
return false;
2031
if (prev_slice_desc.m_mip_index != slice_desc.m_mip_index)
2032
return false;
2033
if (prev_slice_desc.m_num_blocks_x != slice_desc.m_num_blocks_x)
2034
return false;
2035
if (prev_slice_desc.m_num_blocks_y != slice_desc.m_num_blocks_y)
2036
return false;
2037
}
2038
else if (i & 1)
2039
return false;
2040
}
2041
}
2042
else if (slice_desc.m_alpha)
2043
{
2044
return false;
2045
}
2046
2047
if ((slice_desc.m_orig_width > slice_desc.m_width) || (slice_desc.m_orig_height > slice_desc.m_height))
2048
return false;
2049
2050
if ((slice_desc.m_source_file_index == 0) && (m_params.m_tex_type == basist::cBASISTexTypeVideoFrames))
2051
{
2052
if (!slice_desc.m_iframe)
2053
return false;
2054
}
2055
}
2056
2057
return true;
2058
}
2059
2060
// Do some basic validation for 2D arrays, cubemaps, video, and volumes.
2061
bool basis_compressor::validate_texture_type_constraints()
2062
{
2063
debug_printf("basis_compressor::validate_texture_type_constraints\n");
2064
2065
// In 2D mode anything goes (each image may have a different resolution and # of mipmap levels).
2066
if (m_params.m_tex_type == basist::cBASISTexType2D)
2067
return true;
2068
2069
uint32_t total_basis_images = 0;
2070
2071
for (uint32_t slice_index = 0; slice_index < (m_params.m_hdr ? m_slice_images_hdr.size() : m_slice_images.size()); slice_index++)
2072
{
2073
const basisu_backend_slice_desc &slice_desc = m_slice_descs[slice_index];
2074
2075
total_basis_images = maximum<uint32_t>(total_basis_images, slice_desc.m_source_file_index + 1);
2076
}
2077
2078
if (m_params.m_tex_type == basist::cBASISTexTypeCubemapArray)
2079
{
2080
// For cubemaps, validate that the total # of Basis images is a multiple of 6.
2081
if ((total_basis_images % 6) != 0)
2082
{
2083
error_printf("basis_compressor::validate_texture_type_constraints: For cubemaps the total number of input images is not a multiple of 6!\n");
2084
return false;
2085
}
2086
}
2087
2088
// Now validate that all the mip0's have the same dimensions, and that each image has the same # of mipmap levels.
2089
uint_vec image_mipmap_levels(total_basis_images);
2090
2091
int width = -1, height = -1;
2092
for (uint32_t slice_index = 0; slice_index < (m_params.m_hdr ? m_slice_images_hdr.size() : m_slice_images.size()); slice_index++)
2093
{
2094
const basisu_backend_slice_desc &slice_desc = m_slice_descs[slice_index];
2095
2096
image_mipmap_levels[slice_desc.m_source_file_index] = maximum(image_mipmap_levels[slice_desc.m_source_file_index], slice_desc.m_mip_index + 1);
2097
2098
if (slice_desc.m_mip_index != 0)
2099
continue;
2100
2101
if (width < 0)
2102
{
2103
width = slice_desc.m_orig_width;
2104
height = slice_desc.m_orig_height;
2105
}
2106
else if ((width != (int)slice_desc.m_orig_width) || (height != (int)slice_desc.m_orig_height))
2107
{
2108
error_printf("basis_compressor::validate_texture_type_constraints: The source image resolutions are not all equal!\n");
2109
return false;
2110
}
2111
}
2112
2113
for (size_t i = 1; i < image_mipmap_levels.size(); i++)
2114
{
2115
if (image_mipmap_levels[0] != image_mipmap_levels[i])
2116
{
2117
error_printf("basis_compressor::validate_texture_type_constraints: Each image must have the same number of mipmap levels!\n");
2118
return false;
2119
}
2120
}
2121
2122
return true;
2123
}
2124
2125
bool basis_compressor::extract_source_blocks()
2126
{
2127
debug_printf("basis_compressor::extract_source_blocks\n");
2128
2129
// No need to extract blocks in 6x6 mode, but the 4x4 compressors want 4x4 blocks.
2130
if ((m_fmt_mode == basist::basis_tex_format::cASTC_HDR_6x6) || (m_fmt_mode == basist::basis_tex_format::cASTC_HDR_6x6_INTERMEDIATE))
2131
return true;
2132
2133
if (m_params.m_hdr)
2134
m_source_blocks_hdr.resize(m_total_blocks);
2135
else
2136
m_source_blocks.resize(m_total_blocks);
2137
2138
for (uint32_t slice_index = 0; slice_index < (m_params.m_hdr ? m_slice_images_hdr.size() : m_slice_images.size()); slice_index++)
2139
{
2140
const basisu_backend_slice_desc& slice_desc = m_slice_descs[slice_index];
2141
2142
const uint32_t num_blocks_x = slice_desc.m_num_blocks_x;
2143
const uint32_t num_blocks_y = slice_desc.m_num_blocks_y;
2144
2145
const image *pSource_image = m_params.m_hdr ? nullptr : &m_slice_images[slice_index];
2146
const imagef *pSource_image_hdr = m_params.m_hdr ? &m_slice_images_hdr[slice_index] : nullptr;
2147
2148
for (uint32_t block_y = 0; block_y < num_blocks_y; block_y++)
2149
{
2150
for (uint32_t block_x = 0; block_x < num_blocks_x; block_x++)
2151
{
2152
if (m_params.m_hdr)
2153
{
2154
vec4F* pBlock = m_source_blocks_hdr[slice_desc.m_first_block_index + block_x + block_y * num_blocks_x].get_ptr();
2155
2156
pSource_image_hdr->extract_block_clamped(pBlock, block_x * 4, block_y * 4, 4, 4);
2157
2158
// Additional (technically optional) early sanity checking of the block texels.
2159
for (uint32_t i = 0; i < 16; i++)
2160
{
2161
for (uint32_t c = 0; c < 3; c++)
2162
{
2163
float v = pBlock[i][c];
2164
2165
if (std::isnan(v) || std::isinf(v) || (v < 0.0f) || (v > basist::MAX_HALF_FLOAT))
2166
{
2167
error_printf("basis_compressor::extract_source_blocks: invalid float component\n");
2168
return false;
2169
}
2170
}
2171
}
2172
}
2173
else
2174
{
2175
pSource_image->extract_block_clamped(m_source_blocks[slice_desc.m_first_block_index + block_x + block_y * num_blocks_x].get_ptr(), block_x * 4, block_y * 4, 4, 4);
2176
}
2177
}
2178
}
2179
}
2180
2181
return true;
2182
}
2183
2184
bool basis_compressor::process_frontend()
2185
{
2186
debug_printf("basis_compressor::process_frontend\n");
2187
2188
#if 0
2189
// TODO
2190
basis_etc1_pack_params pack_params;
2191
pack_params.m_quality = cETCQualityMedium;
2192
pack_params.m_perceptual = m_params.m_perceptual;
2193
pack_params.m_use_color4 = false;
2194
2195
pack_etc1_block_context pack_context;
2196
2197
std::unordered_set<uint64_t> endpoint_hash;
2198
std::unordered_set<uint32_t> selector_hash;
2199
2200
for (uint32_t i = 0; i < m_source_blocks.size(); i++)
2201
{
2202
etc_block blk;
2203
pack_etc1_block(blk, m_source_blocks[i].get_ptr(), pack_params, pack_context);
2204
2205
const color_rgba c0(blk.get_block_color(0, false));
2206
endpoint_hash.insert((c0.r | (c0.g << 5) | (c0.b << 10)) | (blk.get_inten_table(0) << 16));
2207
2208
const color_rgba c1(blk.get_block_color(1, false));
2209
endpoint_hash.insert((c1.r | (c1.g << 5) | (c1.b << 10)) | (blk.get_inten_table(1) << 16));
2210
2211
selector_hash.insert(blk.get_raw_selector_bits());
2212
}
2213
2214
const uint32_t total_unique_endpoints = (uint32_t)endpoint_hash.size();
2215
const uint32_t total_unique_selectors = (uint32_t)selector_hash.size();
2216
2217
if (m_params.m_debug)
2218
{
2219
debug_printf("Unique endpoints: %u, unique selectors: %u\n", total_unique_endpoints, total_unique_selectors);
2220
}
2221
#endif
2222
2223
const double total_texels = m_total_blocks * 16.0f;
2224
2225
int endpoint_clusters = m_params.m_etc1s_max_endpoint_clusters;
2226
int selector_clusters = m_params.m_etc1s_max_selector_clusters;
2227
2228
if (endpoint_clusters > basisu_frontend::cMaxEndpointClusters)
2229
{
2230
error_printf("Too many endpoint clusters! (%u but max is %u)\n", endpoint_clusters, basisu_frontend::cMaxEndpointClusters);
2231
return false;
2232
}
2233
if (selector_clusters > basisu_frontend::cMaxSelectorClusters)
2234
{
2235
error_printf("Too many selector clusters! (%u but max is %u)\n", selector_clusters, basisu_frontend::cMaxSelectorClusters);
2236
return false;
2237
}
2238
2239
if (m_params.m_etc1s_quality_level != -1)
2240
{
2241
const float quality = saturate(m_params.m_etc1s_quality_level / 255.0f);
2242
2243
const float bits_per_endpoint_cluster = 14.0f;
2244
const float max_desired_endpoint_cluster_bits_per_texel = 1.0f; // .15f
2245
int max_endpoints = static_cast<int>((max_desired_endpoint_cluster_bits_per_texel * total_texels) / bits_per_endpoint_cluster);
2246
2247
const float mid = 128.0f / 255.0f;
2248
2249
float color_endpoint_quality = quality;
2250
2251
const float endpoint_split_point = 0.5f;
2252
2253
// In v1.2 and in previous versions, the endpoint codebook size at quality 128 was 3072. This wasn't quite large enough.
2254
const int ENDPOINT_CODEBOOK_MID_QUALITY_CODEBOOK_SIZE = 4800;
2255
const int MAX_ENDPOINT_CODEBOOK_SIZE = 8192;
2256
2257
if (color_endpoint_quality <= mid)
2258
{
2259
color_endpoint_quality = lerp(0.0f, endpoint_split_point, powf(color_endpoint_quality / mid, .65f));
2260
2261
max_endpoints = clamp<int>(max_endpoints, 256, ENDPOINT_CODEBOOK_MID_QUALITY_CODEBOOK_SIZE);
2262
max_endpoints = minimum<uint32_t>(max_endpoints, m_total_blocks);
2263
2264
if (max_endpoints < 64)
2265
max_endpoints = 64;
2266
endpoint_clusters = clamp<uint32_t>((uint32_t)(.5f + lerp<float>(32, static_cast<float>(max_endpoints), color_endpoint_quality)), 32, basisu_frontend::cMaxEndpointClusters);
2267
}
2268
else
2269
{
2270
color_endpoint_quality = powf((color_endpoint_quality - mid) / (1.0f - mid), 1.6f);
2271
2272
max_endpoints = clamp<int>(max_endpoints, 256, MAX_ENDPOINT_CODEBOOK_SIZE);
2273
max_endpoints = minimum<uint32_t>(max_endpoints, m_total_blocks);
2274
2275
if (max_endpoints < ENDPOINT_CODEBOOK_MID_QUALITY_CODEBOOK_SIZE)
2276
max_endpoints = ENDPOINT_CODEBOOK_MID_QUALITY_CODEBOOK_SIZE;
2277
endpoint_clusters = clamp<uint32_t>((uint32_t)(.5f + lerp<float>(ENDPOINT_CODEBOOK_MID_QUALITY_CODEBOOK_SIZE, static_cast<float>(max_endpoints), color_endpoint_quality)), 32, basisu_frontend::cMaxEndpointClusters);
2278
}
2279
2280
float bits_per_selector_cluster = 14.0f;
2281
2282
const float max_desired_selector_cluster_bits_per_texel = 1.0f; // .15f
2283
int max_selectors = static_cast<int>((max_desired_selector_cluster_bits_per_texel * total_texels) / bits_per_selector_cluster);
2284
max_selectors = clamp<int>(max_selectors, 256, basisu_frontend::cMaxSelectorClusters);
2285
max_selectors = minimum<uint32_t>(max_selectors, m_total_blocks);
2286
2287
float color_selector_quality = quality;
2288
//color_selector_quality = powf(color_selector_quality, 1.65f);
2289
color_selector_quality = powf(color_selector_quality, 2.62f);
2290
2291
if (max_selectors < 96)
2292
max_selectors = 96;
2293
selector_clusters = clamp<uint32_t>((uint32_t)(.5f + lerp<float>(96, static_cast<float>(max_selectors), color_selector_quality)), 8, basisu_frontend::cMaxSelectorClusters);
2294
2295
debug_printf("Max endpoints: %u, max selectors: %u\n", endpoint_clusters, selector_clusters);
2296
2297
if (m_params.m_etc1s_quality_level >= 223)
2298
{
2299
if (!m_params.m_selector_rdo_thresh.was_changed())
2300
{
2301
if (!m_params.m_endpoint_rdo_thresh.was_changed())
2302
m_params.m_endpoint_rdo_thresh *= .25f;
2303
2304
if (!m_params.m_selector_rdo_thresh.was_changed())
2305
m_params.m_selector_rdo_thresh *= .25f;
2306
}
2307
}
2308
else if (m_params.m_etc1s_quality_level >= 192)
2309
{
2310
if (!m_params.m_endpoint_rdo_thresh.was_changed())
2311
m_params.m_endpoint_rdo_thresh *= .5f;
2312
2313
if (!m_params.m_selector_rdo_thresh.was_changed())
2314
m_params.m_selector_rdo_thresh *= .5f;
2315
}
2316
else if (m_params.m_etc1s_quality_level >= 160)
2317
{
2318
if (!m_params.m_endpoint_rdo_thresh.was_changed())
2319
m_params.m_endpoint_rdo_thresh *= .75f;
2320
2321
if (!m_params.m_selector_rdo_thresh.was_changed())
2322
m_params.m_selector_rdo_thresh *= .75f;
2323
}
2324
else if (m_params.m_etc1s_quality_level >= 129)
2325
{
2326
float l = (quality - 129 / 255.0f) / ((160 - 129) / 255.0f);
2327
2328
if (!m_params.m_endpoint_rdo_thresh.was_changed())
2329
m_params.m_endpoint_rdo_thresh *= lerp<float>(1.0f, .75f, l);
2330
2331
if (!m_params.m_selector_rdo_thresh.was_changed())
2332
m_params.m_selector_rdo_thresh *= lerp<float>(1.0f, .75f, l);
2333
}
2334
}
2335
2336
basisu_frontend::params p;
2337
p.m_num_source_blocks = m_total_blocks;
2338
p.m_pSource_blocks = &m_source_blocks[0];
2339
p.m_max_endpoint_clusters = endpoint_clusters;
2340
p.m_max_selector_clusters = selector_clusters;
2341
p.m_perceptual = m_params.m_perceptual;
2342
p.m_debug_stats = m_params.m_debug;
2343
p.m_debug_images = m_params.m_debug_images;
2344
p.m_compression_level = m_params.m_compression_level;
2345
p.m_tex_type = m_params.m_tex_type;
2346
p.m_multithreaded = m_params.m_multithreading;
2347
p.m_disable_hierarchical_endpoint_codebooks = m_params.m_disable_hierarchical_endpoint_codebooks;
2348
p.m_validate = m_params.m_validate_etc1s;
2349
p.m_pJob_pool = m_params.m_pJob_pool;
2350
p.m_pGlobal_codebooks = m_params.m_pGlobal_codebooks;
2351
2352
// Don't keep trying to use OpenCL if it ever fails.
2353
p.m_pOpenCL_context = !m_opencl_failed ? m_pOpenCL_context : nullptr;
2354
2355
if (!m_frontend.init(p))
2356
{
2357
error_printf("basisu_frontend::init() failed!\n");
2358
return false;
2359
}
2360
2361
m_frontend.compress();
2362
2363
if (m_frontend.get_opencl_failed())
2364
m_opencl_failed = true;
2365
2366
if (m_params.m_debug_images)
2367
{
2368
for (uint32_t i = 0; i < m_slice_descs.size(); i++)
2369
{
2370
char filename[1024];
2371
#ifdef _WIN32
2372
sprintf_s(filename, sizeof(filename), "rdo_frontend_output_output_blocks_%u.png", i);
2373
#else
2374
snprintf(filename, sizeof(filename), "rdo_frontend_output_output_blocks_%u.png", i);
2375
#endif
2376
m_frontend.dump_debug_image(filename, m_slice_descs[i].m_first_block_index, m_slice_descs[i].m_num_blocks_x, m_slice_descs[i].m_num_blocks_y, true);
2377
2378
#ifdef _WIN32
2379
sprintf_s(filename, sizeof(filename), "rdo_frontend_output_api_%u.png", i);
2380
#else
2381
snprintf(filename, sizeof(filename), "rdo_frontend_output_api_%u.png", i);
2382
#endif
2383
m_frontend.dump_debug_image(filename, m_slice_descs[i].m_first_block_index, m_slice_descs[i].m_num_blocks_x, m_slice_descs[i].m_num_blocks_y, false);
2384
}
2385
}
2386
2387
return true;
2388
}
2389
2390
bool basis_compressor::extract_frontend_texture_data()
2391
{
2392
if (!m_params.m_compute_stats)
2393
return true;
2394
2395
debug_printf("basis_compressor::extract_frontend_texture_data\n");
2396
2397
m_frontend_output_textures.resize(m_slice_descs.size());
2398
m_best_etc1s_images.resize(m_slice_descs.size());
2399
m_best_etc1s_images_unpacked.resize(m_slice_descs.size());
2400
2401
for (uint32_t i = 0; i < m_slice_descs.size(); i++)
2402
{
2403
const basisu_backend_slice_desc &slice_desc = m_slice_descs[i];
2404
2405
const uint32_t num_blocks_x = slice_desc.m_num_blocks_x;
2406
const uint32_t num_blocks_y = slice_desc.m_num_blocks_y;
2407
2408
const uint32_t width = num_blocks_x * 4;
2409
const uint32_t height = num_blocks_y * 4;
2410
2411
m_frontend_output_textures[i].init(texture_format::cETC1, width, height);
2412
2413
for (uint32_t block_y = 0; block_y < num_blocks_y; block_y++)
2414
for (uint32_t block_x = 0; block_x < num_blocks_x; block_x++)
2415
memcpy(m_frontend_output_textures[i].get_block_ptr(block_x, block_y, 0), &m_frontend.get_output_block(slice_desc.m_first_block_index + block_x + block_y * num_blocks_x), sizeof(etc_block));
2416
2417
#if 0
2418
if (m_params.m_debug_images)
2419
{
2420
char filename[1024];
2421
sprintf_s(filename, sizeof(filename), "rdo_etc_frontend_%u_", i);
2422
write_etc1_vis_images(m_frontend_output_textures[i], filename);
2423
}
2424
#endif
2425
2426
m_best_etc1s_images[i].init(texture_format::cETC1, width, height);
2427
for (uint32_t block_y = 0; block_y < num_blocks_y; block_y++)
2428
for (uint32_t block_x = 0; block_x < num_blocks_x; block_x++)
2429
memcpy(m_best_etc1s_images[i].get_block_ptr(block_x, block_y, 0), &m_frontend.get_etc1s_block(slice_desc.m_first_block_index + block_x + block_y * num_blocks_x), sizeof(etc_block));
2430
2431
m_best_etc1s_images[i].unpack(m_best_etc1s_images_unpacked[i]);
2432
}
2433
2434
return true;
2435
}
2436
2437
bool basis_compressor::process_backend()
2438
{
2439
debug_printf("basis_compressor::process_backend\n");
2440
2441
basisu_backend_params backend_params;
2442
backend_params.m_debug = m_params.m_debug;
2443
backend_params.m_debug_images = m_params.m_debug_images;
2444
backend_params.m_etc1s = true;
2445
backend_params.m_compression_level = m_params.m_compression_level;
2446
2447
if (!m_params.m_no_endpoint_rdo)
2448
backend_params.m_endpoint_rdo_quality_thresh = m_params.m_endpoint_rdo_thresh;
2449
2450
if (!m_params.m_no_selector_rdo)
2451
backend_params.m_selector_rdo_quality_thresh = m_params.m_selector_rdo_thresh;
2452
2453
backend_params.m_used_global_codebooks = m_frontend.get_params().m_pGlobal_codebooks != nullptr;
2454
backend_params.m_validate = m_params.m_validate_output_data;
2455
2456
m_backend.init(&m_frontend, backend_params, m_slice_descs);
2457
uint32_t total_packed_bytes = m_backend.encode();
2458
2459
if (!total_packed_bytes)
2460
{
2461
error_printf("basis_compressor::encode() failed!\n");
2462
return false;
2463
}
2464
2465
debug_printf("Total packed bytes (estimated): %u\n", total_packed_bytes);
2466
2467
return true;
2468
}
2469
2470
bool basis_compressor::create_basis_file_and_transcode()
2471
{
2472
debug_printf("basis_compressor::create_basis_file_and_transcode\n");
2473
2474
const basisu_backend_output& encoded_output = m_params.m_uastc ? m_uastc_backend_output : m_backend.get_output();
2475
2476
if (!m_basis_file.init(encoded_output, m_params.m_tex_type, m_params.m_userdata0, m_params.m_userdata1, m_params.m_y_flip, m_params.m_us_per_frame))
2477
{
2478
error_printf("basis_compressor::create_basis_file_and_transcode: basisu_backend:init() failed!\n");
2479
return false;
2480
}
2481
2482
const uint8_vec& comp_data = m_basis_file.get_compressed_data();
2483
2484
m_output_basis_file = comp_data;
2485
2486
uint32_t total_orig_pixels = 0;
2487
2488
for (uint32_t i = 0; i < m_slice_descs.size(); i++)
2489
{
2490
const basisu_backend_slice_desc& slice_desc = m_slice_descs[i];
2491
2492
total_orig_pixels += slice_desc.m_orig_width * slice_desc.m_orig_height;
2493
}
2494
2495
m_basis_file_size = (uint32_t)comp_data.size();
2496
m_basis_bits_per_texel = total_orig_pixels ? (comp_data.size() * 8.0f) / total_orig_pixels : 0;
2497
2498
debug_printf("Total .basis output file size: %u, %3.3f bits/texel\n", comp_data.size(), comp_data.size() * 8.0f / total_orig_pixels);
2499
2500
// HDR 6x6 TODO
2501
// HACK HACK
2502
const bool is_hdr_6x6 = m_params.m_hdr && (m_params.m_hdr_mode != hdr_modes::cUASTC_HDR_4X4);
2503
2504
if (m_params.m_validate_output_data)
2505
{
2506
interval_timer tm;
2507
tm.start();
2508
2509
basist::basisu_transcoder_init();
2510
2511
debug_printf("basist::basisu_transcoder_init: Took %f ms\n", tm.get_elapsed_ms());
2512
2513
// Verify the compressed data by transcoding it to ASTC (or ETC1)/BC7 and validating the CRC's.
2514
basist::basisu_transcoder decoder;
2515
if (!decoder.validate_file_checksums(&comp_data[0], (uint32_t)comp_data.size(), true))
2516
{
2517
error_printf("decoder.validate_file_checksums() failed!\n");
2518
return false;
2519
}
2520
2521
m_decoded_output_textures.resize(m_slice_descs.size());
2522
2523
if (m_params.m_hdr)
2524
{
2525
m_decoded_output_textures_bc6h_hdr_unpacked.resize(m_slice_descs.size());
2526
2527
m_decoded_output_textures_astc_hdr.resize(m_slice_descs.size());
2528
m_decoded_output_textures_astc_hdr_unpacked.resize(m_slice_descs.size());
2529
}
2530
else
2531
{
2532
m_decoded_output_textures_unpacked.resize(m_slice_descs.size());
2533
2534
m_decoded_output_textures_bc7.resize(m_slice_descs.size());
2535
m_decoded_output_textures_unpacked_bc7.resize(m_slice_descs.size());
2536
}
2537
2538
tm.start();
2539
2540
if (m_params.m_pGlobal_codebooks)
2541
{
2542
decoder.set_global_codebooks(m_params.m_pGlobal_codebooks);
2543
}
2544
2545
if (!decoder.start_transcoding(&comp_data[0], (uint32_t)comp_data.size()))
2546
{
2547
error_printf("decoder.start_transcoding() failed!\n");
2548
return false;
2549
}
2550
2551
double start_transcoding_time = tm.get_elapsed_secs();
2552
2553
debug_printf("basisu_compressor::start_transcoding() took %3.3fms\n", start_transcoding_time * 1000.0f);
2554
2555
double total_time_etc1s_or_astc = 0;
2556
2557
for (uint32_t slice_iter = 0; slice_iter < m_slice_descs.size(); slice_iter++)
2558
{
2559
// Select either BC6H, UASTC LDR 4x4, or ETC1
2560
basisu::texture_format tex_format = m_params.m_hdr ? texture_format::cBC6HUnsigned : (m_params.m_uastc ? texture_format::cUASTC4x4 : texture_format::cETC1);
2561
basist::block_format blk_format = m_params.m_hdr ? basist::block_format::cBC6H : (m_params.m_uastc ? basist::block_format::cUASTC_4x4 : basist::block_format::cETC1);
2562
2563
gpu_image decoded_texture;
2564
decoded_texture.init(
2565
tex_format,
2566
m_slice_descs[slice_iter].m_width, m_slice_descs[slice_iter].m_height);
2567
2568
tm.start();
2569
2570
const uint32_t block_size_x = basisu::get_block_width(tex_format);
2571
const uint32_t block_size_y = basisu::get_block_height(tex_format);
2572
const uint32_t num_dst_blocks_x = (m_slice_descs[slice_iter].m_orig_width + block_size_x - 1) / block_size_x;
2573
const uint32_t num_dst_blocks_y = (m_slice_descs[slice_iter].m_orig_height + block_size_y - 1) / block_size_y;
2574
const uint32_t total_dst_blocks = num_dst_blocks_x * num_dst_blocks_y;
2575
2576
uint32_t bytes_per_block = m_params.m_uastc ? 16 : 8;
2577
2578
if (!decoder.transcode_slice(&comp_data[0], (uint32_t)comp_data.size(), slice_iter,
2579
reinterpret_cast<etc_block*>(decoded_texture.get_ptr()), total_dst_blocks, blk_format, bytes_per_block))
2580
{
2581
error_printf("Transcoding failed on slice %u!\n", slice_iter);
2582
return false;
2583
}
2584
2585
total_time_etc1s_or_astc += tm.get_elapsed_secs();
2586
2587
if (encoded_output.m_tex_format == basist::basis_tex_format::cETC1S)
2588
{
2589
uint32_t image_crc16 = basist::crc16(decoded_texture.get_ptr(), decoded_texture.get_size_in_bytes(), 0);
2590
if (image_crc16 != encoded_output.m_slice_image_crcs[slice_iter])
2591
{
2592
error_printf("Decoded image data CRC check failed on slice %u!\n", slice_iter);
2593
return false;
2594
}
2595
debug_printf("Decoded image data CRC check succeeded on slice %i\n", slice_iter);
2596
}
2597
2598
m_decoded_output_textures[slice_iter] = decoded_texture;
2599
}
2600
2601
double total_alt_transcode_time = 0;
2602
tm.start();
2603
2604
if (m_params.m_hdr)
2605
{
2606
if (is_hdr_6x6)
2607
{
2608
assert(basist::basis_is_format_supported(basist::transcoder_texture_format::cTFASTC_HDR_6x6_RGBA, basist::basis_tex_format::cASTC_HDR_6x6));
2609
assert(basist::basis_is_format_supported(basist::transcoder_texture_format::cTFASTC_HDR_6x6_RGBA, basist::basis_tex_format::cASTC_HDR_6x6_INTERMEDIATE));
2610
2611
for (uint32_t i = 0; i < m_slice_descs.size(); i++)
2612
{
2613
gpu_image decoded_texture;
2614
decoded_texture.init(texture_format::cASTC_HDR_6x6, m_slice_descs[i].m_width, m_slice_descs[i].m_height);
2615
2616
if (!decoder.transcode_slice(&comp_data[0], (uint32_t)comp_data.size(), i,
2617
reinterpret_cast<basist::astc_blk*>(decoded_texture.get_ptr()), m_slice_descs[i].m_num_blocks_x * m_slice_descs[i].m_num_blocks_y, basist::block_format::cASTC_HDR_6x6, 16))
2618
{
2619
error_printf("Transcoding failed to ASTC HDR on slice %u!\n", i);
2620
return false;
2621
}
2622
2623
m_decoded_output_textures_astc_hdr[i] = decoded_texture;
2624
}
2625
}
2626
else
2627
{
2628
assert(basist::basis_is_format_supported(basist::transcoder_texture_format::cTFASTC_HDR_4x4_RGBA, basist::basis_tex_format::cUASTC_HDR_4x4));
2629
2630
for (uint32_t i = 0; i < m_slice_descs.size(); i++)
2631
{
2632
gpu_image decoded_texture;
2633
decoded_texture.init(texture_format::cASTC_HDR_4x4, m_slice_descs[i].m_width, m_slice_descs[i].m_height);
2634
2635
if (!decoder.transcode_slice(&comp_data[0], (uint32_t)comp_data.size(), i,
2636
reinterpret_cast<basist::astc_blk*>(decoded_texture.get_ptr()), m_slice_descs[i].m_num_blocks_x * m_slice_descs[i].m_num_blocks_y, basist::block_format::cASTC_HDR_4x4, 16))
2637
{
2638
error_printf("Transcoding failed to ASTC HDR on slice %u!\n", i);
2639
return false;
2640
}
2641
2642
m_decoded_output_textures_astc_hdr[i] = decoded_texture;
2643
}
2644
}
2645
}
2646
else
2647
{
2648
if (basist::basis_is_format_supported(basist::transcoder_texture_format::cTFBC7_RGBA, basist::basis_tex_format::cUASTC4x4) &&
2649
basist::basis_is_format_supported(basist::transcoder_texture_format::cTFBC7_RGBA, basist::basis_tex_format::cETC1S))
2650
{
2651
for (uint32_t i = 0; i < m_slice_descs.size(); i++)
2652
{
2653
gpu_image decoded_texture;
2654
decoded_texture.init(texture_format::cBC7, m_slice_descs[i].m_width, m_slice_descs[i].m_height);
2655
2656
if (!decoder.transcode_slice(&comp_data[0], (uint32_t)comp_data.size(), i,
2657
reinterpret_cast<etc_block*>(decoded_texture.get_ptr()), m_slice_descs[i].m_num_blocks_x * m_slice_descs[i].m_num_blocks_y, basist::block_format::cBC7, 16))
2658
{
2659
error_printf("Transcoding failed to BC7 on slice %u!\n", i);
2660
return false;
2661
}
2662
2663
m_decoded_output_textures_bc7[i] = decoded_texture;
2664
}
2665
}
2666
}
2667
2668
total_alt_transcode_time = tm.get_elapsed_secs();
2669
2670
for (uint32_t i = 0; i < m_slice_descs.size(); i++)
2671
{
2672
if (m_params.m_hdr)
2673
{
2674
// BC6H
2675
bool status = m_decoded_output_textures[i].unpack_hdr(m_decoded_output_textures_bc6h_hdr_unpacked[i]);
2676
assert(status);
2677
BASISU_NOTE_UNUSED(status);
2678
2679
// ASTC HDR
2680
status = m_decoded_output_textures_astc_hdr[i].unpack_hdr(m_decoded_output_textures_astc_hdr_unpacked[i]);
2681
assert(status);
2682
}
2683
else
2684
{
2685
bool status = m_decoded_output_textures[i].unpack(m_decoded_output_textures_unpacked[i]);
2686
assert(status);
2687
BASISU_NOTE_UNUSED(status);
2688
2689
if (m_decoded_output_textures_bc7[i].get_pixel_width())
2690
{
2691
status = m_decoded_output_textures_bc7[i].unpack(m_decoded_output_textures_unpacked_bc7[i]);
2692
assert(status);
2693
}
2694
}
2695
}
2696
2697
debug_printf("Transcoded to %s in %3.3fms, %f texels/sec\n",
2698
m_params.m_hdr ? "BC6H" : (m_params.m_uastc ? "ASTC" : "ETC1"),
2699
total_time_etc1s_or_astc * 1000.0f, total_orig_pixels / total_time_etc1s_or_astc);
2700
2701
if (total_alt_transcode_time != 0)
2702
debug_printf("Alternate transcode in %3.3fms, %f texels/sec\n", total_alt_transcode_time * 1000.0f, total_orig_pixels / total_alt_transcode_time);
2703
2704
if (!is_hdr_6x6)
2705
{
2706
for (uint32_t slice_index = 0; slice_index < m_slice_descs.size(); slice_index++)
2707
{
2708
const basisu_backend_slice_desc& slice_desc = m_slice_descs[slice_index];
2709
2710
const uint32_t total_blocks = slice_desc.m_num_blocks_x * slice_desc.m_num_blocks_y;
2711
BASISU_NOTE_UNUSED(total_blocks);
2712
2713
assert(m_decoded_output_textures[slice_index].get_total_blocks() == total_blocks);
2714
}
2715
}
2716
2717
} // if (m_params.m_validate_output_data)
2718
2719
return true;
2720
}
2721
2722
bool basis_compressor::write_hdr_debug_images(const char* pBasename, const imagef& orig_hdr_img, uint32_t width, uint32_t height)
2723
{
2724
// Copy image to account for 4x4 block expansion
2725
imagef hdr_img(orig_hdr_img);
2726
hdr_img.resize(width, height);
2727
2728
image srgb_img(width, height);
2729
2730
const float inv_upconversion_scale = (m_ldr_to_hdr_upconversion_nit_multiplier > 0.0f) ? (1.0f / m_ldr_to_hdr_upconversion_nit_multiplier) : 1.0f;
2731
2732
for (uint32_t y = 0; y < height; y++)
2733
{
2734
for (uint32_t x = 0; x < width; x++)
2735
{
2736
vec4F p(hdr_img(x, y));
2737
2738
p[0] = clamp(p[0] * inv_upconversion_scale, 0.0f, 1.0f);
2739
p[1] = clamp(p[1] * inv_upconversion_scale, 0.0f, 1.0f);
2740
p[2] = clamp(p[2] * inv_upconversion_scale, 0.0f, 1.0f);
2741
2742
int rc = (int)std::round(linear_to_srgb(p[0]) * 255.0f);
2743
int gc = (int)std::round(linear_to_srgb(p[1]) * 255.0f);
2744
int bc = (int)std::round(linear_to_srgb(p[2]) * 255.0f);
2745
2746
srgb_img.set_clipped(x, y, color_rgba(rc, gc, bc, 255));
2747
}
2748
}
2749
2750
{
2751
const std::string filename(string_format("%s_linear_clamped_to_srgb.png", pBasename));
2752
save_png(filename.c_str(), srgb_img);
2753
printf("Wrote .PNG file %s\n", filename.c_str());
2754
}
2755
2756
{
2757
const std::string filename(string_format("%s_compressive_tonemapped.png", pBasename));
2758
image compressive_tonemapped_img;
2759
2760
bool status = tonemap_image_compressive(compressive_tonemapped_img, hdr_img);
2761
if (!status)
2762
{
2763
error_printf("basis_compressor::write_hdr_debug_images: tonemap_image_compressive() failed (invalid half-float input)\n");
2764
}
2765
else
2766
{
2767
save_png(filename.c_str(), compressive_tonemapped_img);
2768
printf("Wrote .PNG file %s\n", filename.c_str());
2769
}
2770
}
2771
2772
image tonemapped_img;
2773
2774
for (int e = -5; e <= 5; e++)
2775
{
2776
const float scale = powf(2.0f, (float)e);
2777
2778
tonemap_image_reinhard(tonemapped_img, hdr_img, scale);
2779
2780
std::string filename(string_format("%s_reinhard_tonemapped_scale_%f.png", pBasename, scale));
2781
save_png(filename.c_str(), tonemapped_img, cImageSaveIgnoreAlpha);
2782
printf("Wrote .PNG file %s\n", filename.c_str());
2783
}
2784
2785
return true;
2786
}
2787
2788
bool basis_compressor::write_output_files_and_compute_stats()
2789
{
2790
debug_printf("basis_compressor::write_output_files_and_compute_stats\n");
2791
2792
const uint8_vec& comp_data = m_params.m_create_ktx2_file ? m_output_ktx2_file : m_basis_file.get_compressed_data();
2793
if (m_params.m_write_output_basis_or_ktx2_files)
2794
{
2795
const std::string& output_filename = m_params.m_out_filename;
2796
2797
if (!write_vec_to_file(output_filename.c_str(), comp_data))
2798
{
2799
error_printf("Failed writing output data to file \"%s\"\n", output_filename.c_str());
2800
return false;
2801
}
2802
2803
if (m_params.m_status_output)
2804
{
2805
printf("Wrote output .basis/.ktx2 file \"%s\"\n", output_filename.c_str());
2806
}
2807
}
2808
2809
size_t comp_size = 0;
2810
if ((m_params.m_compute_stats) && (m_params.m_uastc) && (comp_data.size()))
2811
{
2812
void* pComp_data = tdefl_compress_mem_to_heap(&comp_data[0], comp_data.size(), &comp_size, TDEFL_MAX_PROBES_MASK);// TDEFL_DEFAULT_MAX_PROBES);
2813
size_t decomp_size = 0;
2814
void* pDecomp_data = tinfl_decompress_mem_to_heap(pComp_data, comp_size, &decomp_size, 0);
2815
if ((decomp_size != comp_data.size()) || (memcmp(pDecomp_data, &comp_data[0], decomp_size) != 0))
2816
{
2817
printf("basis_compressor::create_basis_file_and_transcode:: miniz compression or decompression failed!\n");
2818
return false;
2819
}
2820
2821
mz_free(pComp_data);
2822
mz_free(pDecomp_data);
2823
2824
uint32_t total_texels = 0;
2825
for (uint32_t i = 0; i < m_slice_descs.size(); i++)
2826
total_texels += (m_slice_descs[i].m_orig_width * m_slice_descs[i].m_orig_height);
2827
2828
m_basis_bits_per_texel = ((float)comp_size * 8.0f) / total_texels;
2829
2830
fmt_debug_printf("Output file size: {}, {3.2} bits/texel, LZ compressed file size: {}, {3.2} bits/texel\n",
2831
(uint64_t)comp_data.size(), ((float)comp_data.size() * 8.0f) / total_texels,
2832
(uint64_t)comp_size, m_basis_bits_per_texel);
2833
}
2834
2835
m_stats.resize(m_slice_descs.size());
2836
2837
if (m_params.m_validate_output_data)
2838
{
2839
if (m_params.m_hdr)
2840
{
2841
if (m_params.m_print_stats)
2842
{
2843
printf("ASTC/BC6H half float space error metrics (a piecewise linear approximation of log2 error):\n");
2844
}
2845
2846
for (uint32_t slice_index = 0; slice_index < m_slice_descs.size(); slice_index++)
2847
{
2848
const basisu_backend_slice_desc& slice_desc = m_slice_descs[slice_index];
2849
2850
if (m_params.m_compute_stats)
2851
{
2852
image_stats& s = m_stats[slice_index];
2853
2854
if (m_params.m_print_stats)
2855
{
2856
printf("Slice: %u\n", slice_index);
2857
}
2858
2859
image_metrics im;
2860
2861
if (m_params.m_print_stats)
2862
{
2863
printf("\nASTC channels:\n");
2864
for (uint32_t i = 0; i < 3; i++)
2865
{
2866
im.calc_half(m_slice_images_hdr[slice_index], m_decoded_output_textures_astc_hdr_unpacked[slice_index], i, 1, true);
2867
2868
printf("%c: ", "RGB"[i]);
2869
im.print_hp();
2870
}
2871
2872
printf("BC6H channels:\n");
2873
for (uint32_t i = 0; i < 3; i++)
2874
{
2875
im.calc_half(m_slice_images_hdr[slice_index], m_decoded_output_textures_bc6h_hdr_unpacked[slice_index], i, 1, true);
2876
2877
printf("%c: ", "RGB"[i]);
2878
im.print_hp();
2879
}
2880
}
2881
2882
im.calc_half(m_slice_images_hdr[slice_index], m_decoded_output_textures_astc_hdr_unpacked[slice_index], 0, 3, true);
2883
s.m_basis_rgb_avg_psnr = (float)im.m_psnr;
2884
2885
if (m_params.m_print_stats)
2886
{
2887
printf("\nASTC RGB: ");
2888
im.print_hp();
2889
#if 0
2890
// Validation
2891
im.calc_half2(m_slice_images_hdr[slice_index], m_decoded_output_textures_astc_hdr_unpacked[slice_index], 0, 3, true);
2892
printf("\nASTC RGB (Alt): ");
2893
im.print_hp();
2894
#endif
2895
}
2896
2897
im.calc_half(m_slice_images_hdr[slice_index], m_decoded_output_textures_bc6h_hdr_unpacked[slice_index], 0, 3, true);
2898
s.m_basis_rgb_avg_bc6h_psnr = (float)im.m_psnr;
2899
2900
if (m_params.m_print_stats)
2901
{
2902
printf("BC6H RGB: ");
2903
im.print_hp();
2904
//printf("\n");
2905
}
2906
2907
im.calc(m_slice_images_hdr[slice_index], m_decoded_output_textures_astc_hdr_unpacked[slice_index], 0, 3, true, true);
2908
s.m_basis_rgb_avg_log2_psnr = (float)im.m_psnr;
2909
2910
if (m_params.m_print_stats)
2911
{
2912
printf("\nASTC Log2 RGB: ");
2913
im.print_hp();
2914
}
2915
2916
im.calc(m_slice_images_hdr[slice_index], m_decoded_output_textures_bc6h_hdr_unpacked[slice_index], 0, 3, true, true);
2917
s.m_basis_rgb_avg_bc6h_log2_psnr = (float)im.m_psnr;
2918
2919
if (m_params.m_print_stats)
2920
{
2921
printf("BC6H Log2 RGB: ");
2922
im.print_hp();
2923
2924
printf("\n");
2925
}
2926
}
2927
2928
if (m_params.m_debug_images)
2929
{
2930
std::string out_basename;
2931
if (m_params.m_out_filename.size())
2932
string_get_filename(m_params.m_out_filename.c_str(), out_basename);
2933
else if (m_params.m_source_filenames.size())
2934
string_get_filename(m_params.m_source_filenames[slice_desc.m_source_file_index].c_str(), out_basename);
2935
2936
string_remove_extension(out_basename);
2937
out_basename = "basis_debug_" + out_basename + string_format("_slice_%u", slice_index);
2938
2939
// Write BC6H .DDS file.
2940
{
2941
gpu_image bc6h_tex(m_decoded_output_textures[slice_index]);
2942
bc6h_tex.override_dimensions(slice_desc.m_orig_width, slice_desc.m_orig_height);
2943
2944
std::string filename(out_basename + "_bc6h.dds");
2945
write_compressed_texture_file(filename.c_str(), bc6h_tex, true);
2946
printf("Wrote .DDS file %s\n", filename.c_str());
2947
}
2948
2949
// Write ASTC .KTX/.astc files. ("astcenc -dh input.astc output.exr" to decode the astc file.)
2950
{
2951
gpu_image astc_tex(m_decoded_output_textures_astc_hdr[slice_index]);
2952
astc_tex.override_dimensions(slice_desc.m_orig_width, slice_desc.m_orig_height);
2953
2954
std::string filename1(out_basename + "_astc.astc");
2955
2956
uint32_t block_width = 4, block_height = 4;
2957
if ((m_params.m_hdr_mode == hdr_modes::cASTC_HDR_6X6) || (m_params.m_hdr_mode == hdr_modes::cASTC_HDR_6X6_INTERMEDIATE))
2958
{
2959
block_width = 6;
2960
block_height = 6;
2961
}
2962
2963
write_astc_file(filename1.c_str(), astc_tex.get_ptr(), block_width, block_height, slice_desc.m_orig_width, slice_desc.m_orig_height);
2964
printf("Wrote .ASTC file %s\n", filename1.c_str());
2965
2966
std::string filename2(out_basename + "_astc.ktx");
2967
write_compressed_texture_file(filename2.c_str(), astc_tex, true);
2968
printf("Wrote .KTX file %s\n", filename2.c_str());
2969
}
2970
2971
// Write unpacked ASTC image to .EXR
2972
{
2973
imagef astc_img(m_decoded_output_textures_astc_hdr_unpacked[slice_index]);
2974
astc_img.resize(slice_desc.m_orig_width, slice_desc.m_orig_height);
2975
2976
std::string filename(out_basename + "_unpacked_astc.exr");
2977
write_exr(filename.c_str(), astc_img, 3, 0);
2978
printf("Wrote .EXR file %s\n", filename.c_str());
2979
}
2980
2981
// Write unpacked BC6H image to .EXR
2982
{
2983
imagef bc6h_img(m_decoded_output_textures_bc6h_hdr_unpacked[slice_index]);
2984
bc6h_img.resize(slice_desc.m_orig_width, slice_desc.m_orig_height);
2985
2986
std::string filename(out_basename + "_unpacked_bc6h.exr");
2987
write_exr(filename.c_str(), bc6h_img, 3, 0);
2988
printf("Wrote .EXR file %s\n", filename.c_str());
2989
}
2990
2991
// Write tonemapped/srgb images
2992
write_hdr_debug_images((out_basename + "_source").c_str(), m_slice_images_hdr[slice_index], slice_desc.m_orig_width, slice_desc.m_orig_height);
2993
write_hdr_debug_images((out_basename + "_unpacked_astc").c_str(), m_decoded_output_textures_astc_hdr_unpacked[slice_index], slice_desc.m_orig_width, slice_desc.m_orig_height);
2994
write_hdr_debug_images((out_basename + "_unpacked_bc6h").c_str(), m_decoded_output_textures_bc6h_hdr_unpacked[slice_index], slice_desc.m_orig_width, slice_desc.m_orig_height);
2995
}
2996
}
2997
}
2998
else
2999
{
3000
for (uint32_t slice_index = 0; slice_index < m_slice_descs.size(); slice_index++)
3001
{
3002
const basisu_backend_slice_desc& slice_desc = m_slice_descs[slice_index];
3003
3004
if (m_params.m_compute_stats)
3005
{
3006
if (m_params.m_print_stats)
3007
printf("Slice: %u\n", slice_index);
3008
3009
image_stats& s = m_stats[slice_index];
3010
3011
image_metrics em;
3012
3013
// ---- .basis stats
3014
em.calc(m_slice_images[slice_index], m_decoded_output_textures_unpacked[slice_index], 0, 3);
3015
if (m_params.m_print_stats)
3016
em.print(".basis RGB Avg: ");
3017
s.m_basis_rgb_avg_psnr = (float)em.m_psnr;
3018
3019
em.calc(m_slice_images[slice_index], m_decoded_output_textures_unpacked[slice_index], 0, 4);
3020
if (m_params.m_print_stats)
3021
em.print(".basis RGBA Avg: ");
3022
s.m_basis_rgba_avg_psnr = (float)em.m_psnr;
3023
3024
em.calc(m_slice_images[slice_index], m_decoded_output_textures_unpacked[slice_index], 0, 1);
3025
if (m_params.m_print_stats)
3026
em.print(".basis R Avg: ");
3027
3028
em.calc(m_slice_images[slice_index], m_decoded_output_textures_unpacked[slice_index], 1, 1);
3029
if (m_params.m_print_stats)
3030
em.print(".basis G Avg: ");
3031
3032
em.calc(m_slice_images[slice_index], m_decoded_output_textures_unpacked[slice_index], 2, 1);
3033
if (m_params.m_print_stats)
3034
em.print(".basis B Avg: ");
3035
3036
if (m_params.m_uastc)
3037
{
3038
em.calc(m_slice_images[slice_index], m_decoded_output_textures_unpacked[slice_index], 3, 1);
3039
if (m_params.m_print_stats)
3040
em.print(".basis A Avg: ");
3041
3042
s.m_basis_a_avg_psnr = (float)em.m_psnr;
3043
}
3044
3045
em.calc(m_slice_images[slice_index], m_decoded_output_textures_unpacked[slice_index], 0, 0);
3046
if (m_params.m_print_stats)
3047
em.print(".basis 709 Luma: ");
3048
s.m_basis_luma_709_psnr = static_cast<float>(em.m_psnr);
3049
s.m_basis_luma_709_ssim = static_cast<float>(em.m_ssim);
3050
3051
em.calc(m_slice_images[slice_index], m_decoded_output_textures_unpacked[slice_index], 0, 0, true, true);
3052
if (m_params.m_print_stats)
3053
em.print(".basis 601 Luma: ");
3054
s.m_basis_luma_601_psnr = static_cast<float>(em.m_psnr);
3055
3056
if (m_slice_descs.size() == 1)
3057
{
3058
const uint32_t output_size = comp_size ? (uint32_t)comp_size : (uint32_t)comp_data.size();
3059
if (m_params.m_print_stats)
3060
{
3061
debug_printf(".basis RGB PSNR per bit/texel*10000: %3.3f\n", 10000.0f * s.m_basis_rgb_avg_psnr / ((output_size * 8.0f) / (slice_desc.m_orig_width * slice_desc.m_orig_height)));
3062
debug_printf(".basis Luma 709 PSNR per bit/texel*10000: %3.3f\n", 10000.0f * s.m_basis_luma_709_psnr / ((output_size * 8.0f) / (slice_desc.m_orig_width * slice_desc.m_orig_height)));
3063
}
3064
}
3065
3066
if (m_decoded_output_textures_unpacked_bc7[slice_index].get_width())
3067
{
3068
// ---- BC7 stats
3069
em.calc(m_slice_images[slice_index], m_decoded_output_textures_unpacked_bc7[slice_index], 0, 3);
3070
//if (m_params.m_print_stats)
3071
// em.print("BC7 RGB Avg: ");
3072
s.m_bc7_rgb_avg_psnr = (float)em.m_psnr;
3073
3074
em.calc(m_slice_images[slice_index], m_decoded_output_textures_unpacked_bc7[slice_index], 0, 4);
3075
//if (m_params.m_print_stats)
3076
// em.print("BC7 RGBA Avg: ");
3077
s.m_bc7_rgba_avg_psnr = (float)em.m_psnr;
3078
3079
em.calc(m_slice_images[slice_index], m_decoded_output_textures_unpacked_bc7[slice_index], 0, 1);
3080
//if (m_params.m_print_stats)
3081
// em.print("BC7 R Avg: ");
3082
3083
em.calc(m_slice_images[slice_index], m_decoded_output_textures_unpacked_bc7[slice_index], 1, 1);
3084
//if (m_params.m_print_stats)
3085
// em.print("BC7 G Avg: ");
3086
3087
em.calc(m_slice_images[slice_index], m_decoded_output_textures_unpacked_bc7[slice_index], 2, 1);
3088
//if (m_params.m_print_stats)
3089
// em.print("BC7 B Avg: ");
3090
3091
if (m_params.m_uastc)
3092
{
3093
em.calc(m_slice_images[slice_index], m_decoded_output_textures_unpacked_bc7[slice_index], 3, 1);
3094
//if (m_params.m_print_stats)
3095
// em.print("BC7 A Avg: ");
3096
3097
s.m_bc7_a_avg_psnr = (float)em.m_psnr;
3098
}
3099
3100
em.calc(m_slice_images[slice_index], m_decoded_output_textures_unpacked_bc7[slice_index], 0, 0);
3101
//if (m_params.m_print_stats)
3102
// em.print("BC7 709 Luma: ");
3103
s.m_bc7_luma_709_psnr = static_cast<float>(em.m_psnr);
3104
s.m_bc7_luma_709_ssim = static_cast<float>(em.m_ssim);
3105
3106
em.calc(m_slice_images[slice_index], m_decoded_output_textures_unpacked_bc7[slice_index], 0, 0, true, true);
3107
//if (m_params.m_print_stats)
3108
// em.print("BC7 601 Luma: ");
3109
s.m_bc7_luma_601_psnr = static_cast<float>(em.m_psnr);
3110
}
3111
3112
if (!m_params.m_uastc)
3113
{
3114
// ---- Nearly best possible ETC1S stats
3115
em.calc(m_slice_images[slice_index], m_best_etc1s_images_unpacked[slice_index], 0, 3);
3116
//if (m_params.m_print_stats)
3117
// em.print("Unquantized ETC1S RGB Avg: ");
3118
s.m_best_etc1s_rgb_avg_psnr = static_cast<float>(em.m_psnr);
3119
3120
em.calc(m_slice_images[slice_index], m_best_etc1s_images_unpacked[slice_index], 0, 0);
3121
//if (m_params.m_print_stats)
3122
// em.print("Unquantized ETC1S 709 Luma: ");
3123
s.m_best_etc1s_luma_709_psnr = static_cast<float>(em.m_psnr);
3124
s.m_best_etc1s_luma_709_ssim = static_cast<float>(em.m_ssim);
3125
3126
em.calc(m_slice_images[slice_index], m_best_etc1s_images_unpacked[slice_index], 0, 0, true, true);
3127
//if (m_params.m_print_stats)
3128
// em.print("Unquantized ETC1S 601 Luma: ");
3129
s.m_best_etc1s_luma_601_psnr = static_cast<float>(em.m_psnr);
3130
}
3131
}
3132
3133
std::string out_basename;
3134
if (m_params.m_out_filename.size())
3135
string_get_filename(m_params.m_out_filename.c_str(), out_basename);
3136
else if (m_params.m_source_filenames.size())
3137
string_get_filename(m_params.m_source_filenames[slice_desc.m_source_file_index].c_str(), out_basename);
3138
3139
string_remove_extension(out_basename);
3140
out_basename = "basis_debug_" + out_basename + string_format("_slice_%u", slice_index);
3141
3142
if ((!m_params.m_uastc) && (m_frontend.get_params().m_debug_images))
3143
{
3144
// Write "best" ETC1S debug images
3145
if (!m_params.m_uastc)
3146
{
3147
gpu_image best_etc1s_gpu_image(m_best_etc1s_images[slice_index]);
3148
best_etc1s_gpu_image.override_dimensions(slice_desc.m_orig_width, slice_desc.m_orig_height);
3149
write_compressed_texture_file((out_basename + "_best_etc1s.ktx").c_str(), best_etc1s_gpu_image, true);
3150
3151
image best_etc1s_unpacked;
3152
best_etc1s_gpu_image.unpack(best_etc1s_unpacked);
3153
save_png(out_basename + "_best_etc1s.png", best_etc1s_unpacked);
3154
}
3155
}
3156
3157
if (m_params.m_debug_images)
3158
{
3159
// Write decoded ETC1S/ASTC debug images
3160
{
3161
gpu_image decoded_etc1s_or_astc(m_decoded_output_textures[slice_index]);
3162
decoded_etc1s_or_astc.override_dimensions(slice_desc.m_orig_width, slice_desc.m_orig_height);
3163
write_compressed_texture_file((out_basename + "_transcoded_etc1s_or_astc.ktx").c_str(), decoded_etc1s_or_astc, true);
3164
3165
image temp(m_decoded_output_textures_unpacked[slice_index]);
3166
temp.crop(slice_desc.m_orig_width, slice_desc.m_orig_height);
3167
save_png(out_basename + "_transcoded_etc1s_or_astc.png", temp);
3168
}
3169
3170
// Write decoded BC7 debug images
3171
if (m_decoded_output_textures_bc7[slice_index].get_pixel_width())
3172
{
3173
gpu_image decoded_bc7(m_decoded_output_textures_bc7[slice_index]);
3174
decoded_bc7.override_dimensions(slice_desc.m_orig_width, slice_desc.m_orig_height);
3175
write_compressed_texture_file((out_basename + "_transcoded_bc7.ktx").c_str(), decoded_bc7, true);
3176
3177
image temp(m_decoded_output_textures_unpacked_bc7[slice_index]);
3178
temp.crop(slice_desc.m_orig_width, slice_desc.m_orig_height);
3179
save_png(out_basename + "_transcoded_bc7.png", temp);
3180
}
3181
}
3182
}
3183
} // if (m_params.m_hdr)
3184
3185
} // if (m_params.m_validate_output_data)
3186
3187
return true;
3188
}
3189
3190
// Make sure all the mip 0's have the same dimensions and number of mipmap levels, or we can't encode the KTX2 file.
3191
bool basis_compressor::validate_ktx2_constraints()
3192
{
3193
uint32_t base_width = 0, base_height = 0;
3194
uint32_t total_layers = 0;
3195
for (uint32_t i = 0; i < m_slice_descs.size(); i++)
3196
{
3197
if (m_slice_descs[i].m_mip_index == 0)
3198
{
3199
if (!base_width)
3200
{
3201
base_width = m_slice_descs[i].m_orig_width;
3202
base_height = m_slice_descs[i].m_orig_height;
3203
}
3204
else
3205
{
3206
if ((m_slice_descs[i].m_orig_width != base_width) || (m_slice_descs[i].m_orig_height != base_height))
3207
{
3208
return false;
3209
}
3210
}
3211
3212
total_layers = maximum<uint32_t>(total_layers, m_slice_descs[i].m_source_file_index + 1);
3213
}
3214
}
3215
3216
basisu::vector<uint32_t> total_mips(total_layers);
3217
for (uint32_t i = 0; i < m_slice_descs.size(); i++)
3218
total_mips[m_slice_descs[i].m_source_file_index] = maximum<uint32_t>(total_mips[m_slice_descs[i].m_source_file_index], m_slice_descs[i].m_mip_index + 1);
3219
3220
for (uint32_t i = 1; i < total_layers; i++)
3221
{
3222
if (total_mips[0] != total_mips[i])
3223
{
3224
return false;
3225
}
3226
}
3227
3228
return true;
3229
}
3230
3231
// colorModel=KTX2_KDF_DF_MODEL_ETC1S (0xA3)
3232
// LDR ETC1S texture data in a custom format, with global codebooks
3233
static uint8_t g_ktx2_etc1s_nonalpha_dfd[44] = { 0x2C,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x2,0x0,0x28,0x0,0xA3,0x1,0x2,0x0,0x3,0x3,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x3F,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0xFF,0xFF,0xFF,0xFF };
3234
static uint8_t g_ktx2_etc1s_alpha_dfd[60] = { 0x3C,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x2,0x0,0x38,0x0,0xA3,0x1,0x2,0x0,0x3,0x3,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x3F,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0xFF,0xFF,0xFF,0xFF,0x40,0x0,0x3F,0xF,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0xFF,0xFF,0xFF,0xFF };
3235
3236
// colorModel=KTX2_KDF_DF_MODEL_UASTC_LDR_4X4 (0xA6)
3237
// LDR UASTC 4x4 texture data in a custom block format
3238
static uint8_t g_ktx2_uastc_ldr_4x4_nonalpha_dfd[44] = { 0x2C,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x2,0x0,0x28,0x0,0xA6,0x1,0x2,0x0,0x3,0x3,0x0,0x0,0x10,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x7F,0x4,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0xFF,0xFF,0xFF,0xFF };
3239
static uint8_t g_ktx2_uastc_ldr_4x4_alpha_dfd[44] = { 0x2C,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x2,0x0,0x28,0x0,0xA6,0x1,0x2,0x0,0x3,0x3,0x0,0x0,0x10,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x7F,0x3,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0xFF,0xFF,0xFF,0xFF };
3240
3241
// colorModel=KTX2_KDF_DF_MODEL_UASTC_HDR_4X4 (0xA7)
3242
// Standard ASTC HDR 4x4 texture data but constrained for easy transcoding to BC6H, either highest quality or RDO optimized.
3243
static uint8_t g_ktx2_uastc_hdr_4x4_nonalpha_dfd[44] =
3244
{
3245
0x2C,0x0,0x0,0x0, // 0 totalSize
3246
0x0,0x0,0x0,0x0, // 1 descriptorType/vendorId
3247
0x2,0x0,0x28,0x0, // 2 descriptorBlockSize/versionNumber
3248
0xA7,0x1,0x1,0x0, // 3 flags, transferFunction, colorPrimaries, colorModel (KTX2_KDF_DF_MODEL_UASTC_HDR_4X4)
3249
0x3,0x3,0x0,0x0, // 4 texelBlockDimension0-texelBlockDimension3
3250
0x10,0x0,0x0,0x0, // 5 bytesPlane0-bytesPlane3
3251
0x0,0x0,0x0,0x0, // 6 bytesPlane4-bytesPlane7
3252
0x0,0x0,0x7F,0x80, // 7 bitLength/bitOffset/channelType and Qualifer flags (KHR_DF_SAMPLE_DATATYPE_FLOAT etc.)
3253
0x0,0x0,0x0,0x0, // 8 samplePosition0-samplePosition3
3254
0x0,0x0,0x0,0x0, // 9 sampleLower (0.0)
3255
0x00, 0x00, 0x80, 0x3F // 10 sampleHigher (1.0)
3256
};
3257
3258
// colorModel=KTX2_KDF_DF_MODEL_ASTC (0xA2)
3259
// Standard ASTC HDR 6x6 texture data, either highest quality or RDO optimized.
3260
static uint8_t g_ktx2_astc_hdr_6x6_nonalpha_dfd[44] =
3261
{
3262
0x2C,0x0,0x0,0x0, // 0 totalSize
3263
0x0,0x0,0x0,0x0, // 1 descriptorType/vendorId
3264
0x2,0x0,0x28,0x0, // 2 descriptorBlockSize/versionNumber
3265
0xA2,0x1,0x1,0x0, // 3 flags, transferFunction, colorPrimaries, colorModel (0xA2/162, standard ASTC, KTX2_KDF_DF_MODEL_ASTC)
3266
0x5,0x5,0x0,0x0, // 4 texelBlockDimension0-texelBlockDimension3
3267
0x10,0x0,0x0,0x0, // 5 bytesPlane0-bytesPlane3
3268
0x0,0x0,0x0,0x0, // 6 bytesPlane4-bytesPlane7
3269
0x0,0x0,0x7F,0x80, // 7 bitLength/bitOffset/channelType and Qualifer flags (KHR_DF_SAMPLE_DATATYPE_FLOAT etc.)
3270
0x0,0x0,0x0,0x0, // 8 samplePosition0-samplePosition3
3271
0x0,0x0,0x0,0x0, // 9 sampleLower (0.0)
3272
0x00, 0x00, 0x80, 0x3F // 10 sampleHigher (1.0)
3273
};
3274
3275
// colorModel=KTX2_KDF_DF_MODEL_ASTC_HDR_6X6_INTERMEDIATE (0xA8)
3276
// Our custom intermediate format that when decoded directly outputs ASTC HDR 6x6
3277
static uint8_t g_ktx2_astc_hdr_6x6_intermediate_nonalpha_dfd[44] =
3278
{
3279
0x2C,0x0,0x0,0x0, // 0 totalSize
3280
0x0,0x0,0x0,0x0, // 1 descriptorType/vendorId
3281
0x2,0x0,0x28,0x0, // 2 descriptorBlockSize/versionNumber
3282
0xA8,0x1,0x1,0x0, // 3 flags, transferFunction, colorPrimaries, colorModel (KTX2_KDF_DF_MODEL_ASTC_HDR_6X6_INTERMEDIATE)
3283
0x5,0x5,0x0,0x0, // 4 texelBlockDimension0-texelBlockDimension3
3284
0x10,0x0,0x0,0x0, // 5 bytesPlane0-bytesPlane3
3285
0x0,0x0,0x0,0x0, // 6 bytesPlane4-bytesPlane7
3286
0x0,0x0,0x7F,0x80, // 7 bitLength/bitOffset/channelType and Qualifer flags (KHR_DF_SAMPLE_DATATYPE_FLOAT etc.)
3287
0x0,0x0,0x0,0x0, // 8 samplePosition0-samplePosition3
3288
0x0,0x0,0x0,0x0, // 9 sampleLower (0.0)
3289
0x00, 0x00, 0x80, 0x3F // 10 sampleHigher (1.0)
3290
};
3291
3292
bool basis_compressor::get_dfd(uint8_vec &dfd, const basist::ktx2_header &header)
3293
{
3294
const uint8_t* pDFD;
3295
uint32_t dfd_len;
3296
3297
if (m_params.m_uastc)
3298
{
3299
if (m_params.m_hdr)
3300
{
3301
switch (m_params.m_hdr_mode)
3302
{
3303
case hdr_modes::cUASTC_HDR_4X4:
3304
{
3305
pDFD = g_ktx2_uastc_hdr_4x4_nonalpha_dfd;
3306
dfd_len = sizeof(g_ktx2_uastc_hdr_4x4_nonalpha_dfd);
3307
break;
3308
}
3309
case hdr_modes::cASTC_HDR_6X6:
3310
{
3311
pDFD = g_ktx2_astc_hdr_6x6_nonalpha_dfd;
3312
dfd_len = sizeof(g_ktx2_astc_hdr_6x6_nonalpha_dfd);
3313
break;
3314
}
3315
case hdr_modes::cASTC_HDR_6X6_INTERMEDIATE:
3316
{
3317
pDFD = g_ktx2_astc_hdr_6x6_intermediate_nonalpha_dfd;
3318
dfd_len = sizeof(g_ktx2_astc_hdr_6x6_intermediate_nonalpha_dfd);
3319
break;
3320
}
3321
default:
3322
{
3323
assert(0);
3324
return false;
3325
}
3326
}
3327
}
3328
// Must be LDR UASTC 4x4
3329
else if (m_any_source_image_has_alpha)
3330
{
3331
pDFD = g_ktx2_uastc_ldr_4x4_alpha_dfd;
3332
dfd_len = sizeof(g_ktx2_uastc_ldr_4x4_alpha_dfd);
3333
}
3334
else
3335
{
3336
pDFD = g_ktx2_uastc_ldr_4x4_nonalpha_dfd;
3337
dfd_len = sizeof(g_ktx2_uastc_ldr_4x4_nonalpha_dfd);
3338
}
3339
}
3340
else
3341
{
3342
// Must be ETC1S.
3343
assert(!m_params.m_hdr);
3344
3345
if (m_any_source_image_has_alpha)
3346
{
3347
pDFD = g_ktx2_etc1s_alpha_dfd;
3348
dfd_len = sizeof(g_ktx2_etc1s_alpha_dfd);
3349
}
3350
else
3351
{
3352
pDFD = g_ktx2_etc1s_nonalpha_dfd;
3353
dfd_len = sizeof(g_ktx2_etc1s_nonalpha_dfd);
3354
}
3355
}
3356
3357
assert(dfd_len >= 44);
3358
3359
dfd.resize(dfd_len);
3360
memcpy(dfd.data(), pDFD, dfd_len);
3361
3362
uint32_t dfd_bits = basisu::read_le_dword(dfd.data() + 3 * sizeof(uint32_t));
3363
3364
// Color primaries
3365
if ((m_params.m_hdr) && (m_params.m_astc_hdr_6x6_options.m_rec2020_bt2100_color_gamut))
3366
{
3367
dfd_bits &= ~(0xFF << 8);
3368
dfd_bits |= (basist::KTX2_DF_PRIMARIES_BT2020 << 8);
3369
}
3370
3371
// Transfer function
3372
dfd_bits &= ~(0xFF << 16);
3373
3374
if (m_params.m_hdr)
3375
{
3376
// TODO: In HDR mode, always write linear for now.
3377
dfd_bits |= (basist::KTX2_KHR_DF_TRANSFER_LINEAR << 16);
3378
}
3379
else
3380
{
3381
if (m_params.m_ktx2_srgb_transfer_func)
3382
dfd_bits |= (basist::KTX2_KHR_DF_TRANSFER_SRGB << 16);
3383
else
3384
dfd_bits |= (basist::KTX2_KHR_DF_TRANSFER_LINEAR << 16);
3385
}
3386
3387
basisu::write_le_dword(dfd.data() + 3 * sizeof(uint32_t), dfd_bits);
3388
3389
if (header.m_supercompression_scheme != basist::KTX2_SS_NONE)
3390
{
3391
uint32_t plane_bits = basisu::read_le_dword(dfd.data() + 5 * sizeof(uint32_t));
3392
3393
plane_bits &= ~0xFF;
3394
3395
basisu::write_le_dword(dfd.data() + 5 * sizeof(uint32_t), plane_bits);
3396
}
3397
3398
// Fix up the DFD channel(s)
3399
uint32_t dfd_chan0 = basisu::read_le_dword(dfd.data() + 7 * sizeof(uint32_t));
3400
3401
if (m_params.m_uastc)
3402
{
3403
dfd_chan0 &= ~(0xF << 24);
3404
3405
// TODO: Allow the caller to override this
3406
if (m_any_source_image_has_alpha)
3407
dfd_chan0 |= (basist::KTX2_DF_CHANNEL_UASTC_RGBA << 24);
3408
else
3409
dfd_chan0 |= (basist::KTX2_DF_CHANNEL_UASTC_RGB << 24);
3410
}
3411
3412
basisu::write_le_dword(dfd.data() + 7 * sizeof(uint32_t), dfd_chan0);
3413
3414
return true;
3415
}
3416
3417
bool basis_compressor::create_ktx2_file()
3418
{
3419
//bool needs_global_data = false;
3420
bool can_use_zstd = false;
3421
3422
switch (m_fmt_mode)
3423
{
3424
case basist::basis_tex_format::cETC1S:
3425
{
3426
//needs_global_data = true;
3427
break;
3428
}
3429
case basist::basis_tex_format::cUASTC4x4:
3430
{
3431
can_use_zstd = true;
3432
break;
3433
}
3434
case basist::basis_tex_format::cUASTC_HDR_4x4:
3435
{
3436
can_use_zstd = true;
3437
break;
3438
}
3439
case basist::basis_tex_format::cASTC_HDR_6x6:
3440
{
3441
can_use_zstd = true;
3442
break;
3443
}
3444
case basist::basis_tex_format::cASTC_HDR_6x6_INTERMEDIATE:
3445
{
3446
//needs_global_data = true;
3447
break;
3448
}
3449
default:
3450
assert(0);
3451
fmt_debug_printf("HERE 1\n");
3452
return false;
3453
}
3454
3455
if (can_use_zstd)
3456
{
3457
if ((m_params.m_ktx2_uastc_supercompression != basist::KTX2_SS_NONE) && (m_params.m_ktx2_uastc_supercompression != basist::KTX2_SS_ZSTANDARD))
3458
{
3459
fmt_debug_printf("HERE 2\n");
3460
return false;
3461
}
3462
}
3463
3464
const basisu_backend_output& backend_output = m_backend.get_output();
3465
3466
// Determine the width/height, number of array layers, mipmap levels, and the number of faces (1 for 2D, 6 for cubemap).
3467
// This does not support 1D or 3D.
3468
uint32_t base_width = 0, base_height = 0, total_layers = 0, total_levels = 0, total_faces = 1;
3469
3470
for (uint32_t i = 0; i < m_slice_descs.size(); i++)
3471
{
3472
if ((m_slice_descs[i].m_mip_index == 0) && (!base_width))
3473
{
3474
base_width = m_slice_descs[i].m_orig_width;
3475
base_height = m_slice_descs[i].m_orig_height;
3476
}
3477
3478
total_layers = maximum<uint32_t>(total_layers, m_slice_descs[i].m_source_file_index + 1);
3479
3480
if (!m_slice_descs[i].m_source_file_index)
3481
total_levels = maximum<uint32_t>(total_levels, m_slice_descs[i].m_mip_index + 1);
3482
}
3483
3484
if (m_params.m_tex_type == basist::cBASISTexTypeCubemapArray)
3485
{
3486
assert((total_layers % 6) == 0);
3487
3488
total_layers /= 6;
3489
assert(total_layers >= 1);
3490
3491
total_faces = 6;
3492
}
3493
3494
basist::ktx2_header header;
3495
memset(&header, 0, sizeof(header));
3496
3497
memcpy(header.m_identifier, basist::g_ktx2_file_identifier, sizeof(basist::g_ktx2_file_identifier));
3498
header.m_pixel_width = base_width;
3499
header.m_pixel_height = base_height;
3500
header.m_face_count = total_faces;
3501
3502
if (m_params.m_hdr)
3503
{
3504
if (m_params.m_hdr_mode == hdr_modes::cUASTC_HDR_4X4)
3505
header.m_vk_format = basist::KTX2_FORMAT_ASTC_4x4_SFLOAT_BLOCK;
3506
else if (m_params.m_hdr_mode == hdr_modes::cASTC_HDR_6X6)
3507
header.m_vk_format = basist::KTX2_FORMAT_ASTC_6x6_SFLOAT_BLOCK;
3508
else
3509
{
3510
assert(m_params.m_hdr_mode == hdr_modes::cASTC_HDR_6X6_INTERMEDIATE);
3511
3512
header.m_vk_format = basist::KTX2_VK_FORMAT_UNDEFINED;
3513
}
3514
}
3515
else
3516
{
3517
// Either ETC1S or UASTC LDR 4x4.
3518
assert((m_fmt_mode == basist::basis_tex_format::cETC1S) || (m_fmt_mode == basist::basis_tex_format::cUASTC4x4));
3519
3520
header.m_vk_format = basist::KTX2_VK_FORMAT_UNDEFINED;
3521
}
3522
3523
header.m_type_size = 1;
3524
header.m_level_count = total_levels;
3525
header.m_layer_count = (total_layers > 1) ? total_layers : 0;
3526
3527
if (can_use_zstd)
3528
{
3529
switch (m_params.m_ktx2_uastc_supercompression)
3530
{
3531
case basist::KTX2_SS_NONE:
3532
{
3533
header.m_supercompression_scheme = basist::KTX2_SS_NONE;
3534
break;
3535
}
3536
case basist::KTX2_SS_ZSTANDARD:
3537
{
3538
#if BASISD_SUPPORT_KTX2_ZSTD
3539
header.m_supercompression_scheme = basist::KTX2_SS_ZSTANDARD;
3540
#else
3541
header.m_supercompression_scheme = basist::KTX2_SS_NONE;
3542
#endif
3543
break;
3544
}
3545
default:
3546
assert(0);
3547
fmt_debug_printf("HERE 3\n");
3548
return false;
3549
}
3550
}
3551
3552
basisu::vector<uint8_vec> level_data_bytes(total_levels);
3553
basisu::vector<uint8_vec> compressed_level_data_bytes(total_levels);
3554
size_t_vec slice_level_offsets(m_slice_descs.size());
3555
3556
// This will append the texture data in the correct order (for each level: layer, then face).
3557
for (uint32_t slice_index = 0; slice_index < m_slice_descs.size(); slice_index++)
3558
{
3559
const basisu_backend_slice_desc& slice_desc = m_slice_descs[slice_index];
3560
3561
slice_level_offsets[slice_index] = level_data_bytes[slice_desc.m_mip_index].size();
3562
3563
if (m_fmt_mode == basist::basis_tex_format::cETC1S)
3564
{
3565
append_vector(level_data_bytes[slice_desc.m_mip_index], backend_output.m_slice_image_data[slice_index]);
3566
}
3567
else
3568
{
3569
append_vector(level_data_bytes[slice_desc.m_mip_index], m_uastc_backend_output.m_slice_image_data[slice_index]);
3570
}
3571
}
3572
3573
// Zstd Supercompression
3574
if ((can_use_zstd) && (header.m_supercompression_scheme == basist::KTX2_SS_ZSTANDARD))
3575
{
3576
#if BASISD_SUPPORT_KTX2_ZSTD
3577
for (uint32_t level_index = 0; level_index < total_levels; level_index++)
3578
{
3579
compressed_level_data_bytes[level_index].resize(ZSTD_compressBound(level_data_bytes[level_index].size()));
3580
3581
size_t result = ZSTD_compress(compressed_level_data_bytes[level_index].data(), compressed_level_data_bytes[level_index].size(),
3582
level_data_bytes[level_index].data(), level_data_bytes[level_index].size(),
3583
m_params.m_ktx2_zstd_supercompression_level);
3584
3585
if (ZSTD_isError(result))
3586
{
3587
fmt_debug_printf("HERE 5\n");
3588
return false;
3589
}
3590
3591
compressed_level_data_bytes[level_index].resize(result);
3592
}
3593
#else
3594
// Can't get here
3595
assert(0);
3596
fmt_debug_printf("HERE 6\n");
3597
return false;
3598
#endif
3599
}
3600
else
3601
{
3602
// No supercompression
3603
compressed_level_data_bytes = level_data_bytes;
3604
}
3605
3606
uint8_vec ktx2_global_data;
3607
3608
// Create ETC1S global supercompressed data
3609
if (m_fmt_mode == basist::basis_tex_format::cETC1S)
3610
{
3611
basist::ktx2_etc1s_global_data_header etc1s_global_data_header;
3612
clear_obj(etc1s_global_data_header);
3613
3614
etc1s_global_data_header.m_endpoint_count = backend_output.m_num_endpoints;
3615
etc1s_global_data_header.m_selector_count = backend_output.m_num_selectors;
3616
etc1s_global_data_header.m_endpoints_byte_length = backend_output.m_endpoint_palette.size();
3617
etc1s_global_data_header.m_selectors_byte_length = backend_output.m_selector_palette.size();
3618
etc1s_global_data_header.m_tables_byte_length = backend_output.m_slice_image_tables.size();
3619
3620
basisu::vector<basist::ktx2_etc1s_image_desc> etc1s_image_descs(total_levels * total_layers * total_faces);
3621
memset(etc1s_image_descs.data(), 0, etc1s_image_descs.size_in_bytes());
3622
3623
for (uint32_t slice_index = 0; slice_index < m_slice_descs.size(); slice_index++)
3624
{
3625
const basisu_backend_slice_desc& slice_desc = m_slice_descs[slice_index];
3626
3627
const uint32_t level_index = slice_desc.m_mip_index;
3628
uint32_t layer_index = slice_desc.m_source_file_index;
3629
uint32_t face_index = 0;
3630
3631
if (m_params.m_tex_type == basist::cBASISTexTypeCubemapArray)
3632
{
3633
face_index = layer_index % 6;
3634
layer_index /= 6;
3635
}
3636
3637
const uint32_t etc1s_image_index = level_index * (total_layers * total_faces) + layer_index * total_faces + face_index;
3638
3639
if (slice_desc.m_alpha)
3640
{
3641
etc1s_image_descs[etc1s_image_index].m_alpha_slice_byte_length = backend_output.m_slice_image_data[slice_index].size();
3642
etc1s_image_descs[etc1s_image_index].m_alpha_slice_byte_offset = slice_level_offsets[slice_index];
3643
}
3644
else
3645
{
3646
if (m_params.m_tex_type == basist::cBASISTexTypeVideoFrames)
3647
etc1s_image_descs[etc1s_image_index].m_image_flags = !slice_desc.m_iframe ? basist::KTX2_IMAGE_IS_P_FRAME : 0;
3648
3649
etc1s_image_descs[etc1s_image_index].m_rgb_slice_byte_length = backend_output.m_slice_image_data[slice_index].size();
3650
etc1s_image_descs[etc1s_image_index].m_rgb_slice_byte_offset = slice_level_offsets[slice_index];
3651
}
3652
} // slice_index
3653
3654
append_vector(ktx2_global_data, (const uint8_t*)&etc1s_global_data_header, sizeof(etc1s_global_data_header));
3655
append_vector(ktx2_global_data, (const uint8_t*)etc1s_image_descs.data(), etc1s_image_descs.size_in_bytes());
3656
append_vector(ktx2_global_data, backend_output.m_endpoint_palette);
3657
append_vector(ktx2_global_data, backend_output.m_selector_palette);
3658
append_vector(ktx2_global_data, backend_output.m_slice_image_tables);
3659
3660
header.m_supercompression_scheme = basist::KTX2_SS_BASISLZ;
3661
}
3662
else if (m_fmt_mode == basist::basis_tex_format::cASTC_HDR_6x6_INTERMEDIATE)
3663
{
3664
basisu::vector<basist::ktx2_astc_hdr_6x6_intermediate_image_desc> image_descs(total_levels * total_layers * total_faces);
3665
memset(image_descs.data(), 0, image_descs.size_in_bytes());
3666
3667
for (uint32_t slice_index = 0; slice_index < m_slice_descs.size(); slice_index++)
3668
{
3669
const basisu_backend_slice_desc& slice_desc = m_slice_descs[slice_index];
3670
3671
const uint32_t level_index = slice_desc.m_mip_index;
3672
uint32_t layer_index = slice_desc.m_source_file_index;
3673
uint32_t face_index = 0;
3674
3675
if (m_params.m_tex_type == basist::cBASISTexTypeCubemapArray)
3676
{
3677
face_index = layer_index % 6;
3678
layer_index /= 6;
3679
}
3680
3681
const uint32_t output_image_index = level_index * (total_layers * total_faces) + layer_index * total_faces + face_index;
3682
3683
image_descs[output_image_index].m_rgb_slice_byte_length = m_uastc_backend_output.m_slice_image_data[slice_index].size();
3684
image_descs[output_image_index].m_rgb_slice_byte_offset = slice_level_offsets[slice_index];
3685
3686
} // slice_index
3687
3688
append_vector(ktx2_global_data, (const uint8_t*)image_descs.data(), image_descs.size_in_bytes());
3689
3690
header.m_supercompression_scheme = basist::KTX2_SS_BASISLZ;
3691
}
3692
3693
// Key values
3694
basist::ktx2_transcoder::key_value_vec key_values(m_params.m_ktx2_key_values);
3695
3696
basist::ktx2_add_key_value(key_values, "KTXwriter", fmt_string("Basis Universal {}", BASISU_LIB_VERSION_STRING));
3697
3698
if (m_params.m_hdr)
3699
{
3700
if (m_upconverted_any_ldr_images)
3701
basist::ktx2_add_key_value(key_values, "LDRUpconversionMultiplier", fmt_string("{}", m_ldr_to_hdr_upconversion_nit_multiplier));
3702
3703
if (m_params.m_ldr_hdr_upconversion_srgb_to_linear)
3704
basist::ktx2_add_key_value(key_values, "LDRUpconversionSRGBToLinear", "1");
3705
}
3706
3707
key_values.sort();
3708
3709
#if BASISU_DISABLE_KTX2_KEY_VALUES
3710
// HACK HACK - Clear the key values array, which causes no key values to be written (triggering the ktx2check validator bug).
3711
key_values.clear();
3712
#endif
3713
3714
uint8_vec key_value_data;
3715
3716
// DFD
3717
uint8_vec dfd;
3718
if (!get_dfd(dfd, header))
3719
{
3720
fmt_debug_printf("HERE 7\n");
3721
return false;
3722
}
3723
3724
const uint32_t kvd_file_offset = sizeof(header) + sizeof(basist::ktx2_level_index) * total_levels + (uint32_t)dfd.size();
3725
3726
for (uint32_t pass = 0; pass < 2; pass++)
3727
{
3728
for (uint32_t i = 0; i < key_values.size(); i++)
3729
{
3730
if (key_values[i].m_key.size() < 2)
3731
{
3732
fmt_debug_printf("HERE 8\n");
3733
return false;
3734
}
3735
3736
if (key_values[i].m_key.back() != 0)
3737
{
3738
fmt_debug_printf("HERE 9\n");
3739
return false;
3740
}
3741
3742
const uint64_t total_len = (uint64_t)key_values[i].m_key.size() + (uint64_t)key_values[i].m_value.size();
3743
if (total_len >= UINT32_MAX)
3744
{
3745
fmt_debug_printf("HERE 10\n");
3746
return false;
3747
}
3748
3749
packed_uint<4> le_len((uint32_t)total_len);
3750
append_vector(key_value_data, (const uint8_t*)&le_len, sizeof(le_len));
3751
3752
append_vector(key_value_data, key_values[i].m_key);
3753
append_vector(key_value_data, key_values[i].m_value);
3754
3755
const uint32_t ofs = key_value_data.size() & 3;
3756
const uint32_t padding = (4 - ofs) & 3;
3757
for (uint32_t p = 0; p < padding; p++)
3758
key_value_data.push_back(0);
3759
}
3760
3761
if (header.m_supercompression_scheme != basist::KTX2_SS_NONE)
3762
break;
3763
3764
#if BASISU_DISABLE_KTX2_ALIGNMENT_WORKAROUND
3765
break;
3766
#endif
3767
3768
// Hack to ensure the KVD block ends on a 16 byte boundary, because we have no other official way of aligning the data.
3769
uint32_t kvd_end_file_offset = kvd_file_offset + (uint32_t)key_value_data.size();
3770
uint32_t bytes_needed_to_pad = (16 - (kvd_end_file_offset & 15)) & 15;
3771
if (!bytes_needed_to_pad)
3772
{
3773
// We're good. No need to add a dummy key.
3774
break;
3775
}
3776
3777
assert(!pass);
3778
if (pass)
3779
{
3780
fmt_debug_printf("HERE 11\n");
3781
return false;
3782
}
3783
3784
if (bytes_needed_to_pad < 6)
3785
bytes_needed_to_pad += 16;
3786
3787
// Just add the padding. It's likely not necessary anymore, but can't really hurt.
3788
//printf("WARNING: Due to a KTX2 validator bug related to mipPadding, we must insert a dummy key into the KTX2 file of %u bytes\n", bytes_needed_to_pad);
3789
3790
// We're not good - need to add a dummy key large enough to force file alignment so the mip level array gets aligned.
3791
// We can't just add some bytes before the mip level array because ktx2check will see that as extra data in the file that shouldn't be there in ktxValidator::validateDataSize().
3792
key_values.enlarge(1);
3793
for (uint32_t i = 0; i < (bytes_needed_to_pad - 4 - 1 - 1); i++)
3794
key_values.back().m_key.push_back(127);
3795
3796
key_values.back().m_key.push_back(0);
3797
3798
key_values.back().m_value.push_back(0);
3799
3800
key_values.sort();
3801
3802
key_value_data.resize(0);
3803
3804
// Try again
3805
}
3806
3807
basisu::vector<basist::ktx2_level_index> level_index_array(total_levels);
3808
memset(level_index_array.data(), 0, level_index_array.size_in_bytes());
3809
3810
m_output_ktx2_file.clear();
3811
m_output_ktx2_file.reserve(m_output_basis_file.size());
3812
3813
// Dummy header
3814
m_output_ktx2_file.resize(sizeof(header));
3815
3816
// Level index array
3817
append_vector(m_output_ktx2_file, (const uint8_t*)level_index_array.data(), level_index_array.size_in_bytes());
3818
3819
// DFD
3820
const uint8_t* pDFD = dfd.data();
3821
uint32_t dfd_len = (uint32_t)dfd.size();
3822
3823
header.m_dfd_byte_offset = m_output_ktx2_file.size();
3824
header.m_dfd_byte_length = dfd_len;
3825
append_vector(m_output_ktx2_file, pDFD, dfd_len);
3826
3827
// Key value data
3828
if (key_value_data.size())
3829
{
3830
assert(kvd_file_offset == m_output_ktx2_file.size());
3831
3832
header.m_kvd_byte_offset = m_output_ktx2_file.size();
3833
header.m_kvd_byte_length = key_value_data.size();
3834
append_vector(m_output_ktx2_file, key_value_data);
3835
}
3836
3837
// Global Supercompressed Data
3838
if (ktx2_global_data.size())
3839
{
3840
uint32_t ofs = m_output_ktx2_file.size() & 7;
3841
uint32_t padding = (8 - ofs) & 7;
3842
for (uint32_t i = 0; i < padding; i++)
3843
m_output_ktx2_file.push_back(0);
3844
3845
header.m_sgd_byte_length = ktx2_global_data.size();
3846
header.m_sgd_byte_offset = m_output_ktx2_file.size();
3847
3848
append_vector(m_output_ktx2_file, ktx2_global_data);
3849
}
3850
3851
// mipPadding
3852
if (header.m_supercompression_scheme == basist::KTX2_SS_NONE)
3853
{
3854
// We currently can't do this or the validator will incorrectly give an error.
3855
uint32_t ofs = m_output_ktx2_file.size() & 15;
3856
uint32_t padding = (16 - ofs) & 15;
3857
3858
// Make sure we're always aligned here (due to a validator bug).
3859
if (padding)
3860
{
3861
printf("Warning: KTX2 mip level data is not 16-byte aligned. This may trigger a ktx2check validation bug. Writing %u bytes of mipPadding.\n", padding);
3862
}
3863
3864
for (uint32_t i = 0; i < padding; i++)
3865
m_output_ktx2_file.push_back(0);
3866
}
3867
3868
// Level data - write the smallest mipmap first.
3869
for (int level = total_levels - 1; level >= 0; level--)
3870
{
3871
level_index_array[level].m_byte_length = compressed_level_data_bytes[level].size();
3872
3873
//if (m_params.m_uastc)
3874
if (can_use_zstd)
3875
{
3876
level_index_array[level].m_uncompressed_byte_length = level_data_bytes[level].size();
3877
}
3878
3879
level_index_array[level].m_byte_offset = m_output_ktx2_file.size();
3880
append_vector(m_output_ktx2_file, compressed_level_data_bytes[level]);
3881
}
3882
3883
// Write final header
3884
memcpy(m_output_ktx2_file.data(), &header, sizeof(header));
3885
3886
// Write final level index array
3887
memcpy(m_output_ktx2_file.data() + sizeof(header), level_index_array.data(), level_index_array.size_in_bytes());
3888
3889
uint32_t total_orig_pixels = 0;
3890
3891
for (uint32_t i = 0; i < m_slice_descs.size(); i++)
3892
{
3893
const basisu_backend_slice_desc& slice_desc = m_slice_descs[i];
3894
total_orig_pixels += slice_desc.m_orig_width * slice_desc.m_orig_height;
3895
}
3896
3897
debug_printf("Total .ktx2 output file size: %u, %3.3f bits/texel\n", m_output_ktx2_file.size(), ((float)m_output_ktx2_file.size() * 8.0f) / total_orig_pixels);
3898
3899
return true;
3900
}
3901
3902
bool basis_parallel_compress(
3903
uint32_t total_threads,
3904
const basisu::vector<basis_compressor_params>& params_vec,
3905
basisu::vector< parallel_results >& results_vec)
3906
{
3907
assert(g_library_initialized);
3908
if (!g_library_initialized)
3909
{
3910
error_printf("basis_parallel_compress: basisu_encoder_init() MUST be called before using any encoder functionality!\n");
3911
return false;
3912
}
3913
3914
assert(total_threads >= 1);
3915
total_threads = basisu::maximum<uint32_t>(total_threads, 1);
3916
3917
job_pool jpool(total_threads);
3918
3919
results_vec.resize(0);
3920
results_vec.resize(params_vec.size());
3921
3922
std::atomic<bool> result;
3923
result.store(true);
3924
3925
std::atomic<bool> opencl_failed;
3926
opencl_failed.store(false);
3927
3928
for (uint32_t pindex = 0; pindex < params_vec.size(); pindex++)
3929
{
3930
jpool.add_job([pindex, &params_vec, &results_vec, &result, &opencl_failed] {
3931
3932
basis_compressor_params params = params_vec[pindex];
3933
parallel_results& results = results_vec[pindex];
3934
3935
interval_timer tm;
3936
tm.start();
3937
3938
basis_compressor c;
3939
3940
// Dummy job pool
3941
job_pool task_jpool(1);
3942
params.m_pJob_pool = &task_jpool;
3943
// TODO: Remove this flag entirely
3944
params.m_multithreading = true;
3945
3946
// Stop using OpenCL if a failure ever occurs.
3947
if (opencl_failed)
3948
params.m_use_opencl = false;
3949
3950
bool status = c.init(params);
3951
3952
if (c.get_opencl_failed())
3953
opencl_failed.store(true);
3954
3955
if (status)
3956
{
3957
basis_compressor::error_code ec = c.process();
3958
3959
if (c.get_opencl_failed())
3960
opencl_failed.store(true);
3961
3962
results.m_error_code = ec;
3963
3964
if (ec == basis_compressor::cECSuccess)
3965
{
3966
results.m_basis_file = c.get_output_basis_file();
3967
results.m_ktx2_file = c.get_output_ktx2_file();
3968
results.m_stats = c.get_stats();
3969
results.m_basis_bits_per_texel = c.get_basis_bits_per_texel();
3970
results.m_any_source_image_has_alpha = c.get_any_source_image_has_alpha();
3971
}
3972
else
3973
{
3974
result = false;
3975
}
3976
}
3977
else
3978
{
3979
results.m_error_code = basis_compressor::cECFailedInitializing;
3980
3981
result = false;
3982
}
3983
3984
results.m_total_time = tm.get_elapsed_secs();
3985
} );
3986
3987
} // pindex
3988
3989
jpool.wait_for_all();
3990
3991
if (opencl_failed)
3992
error_printf("An OpenCL error occured sometime during compression. The compressor fell back to CPU processing after the failure.\n");
3993
3994
return result;
3995
}
3996
3997
static void* basis_compress(
3998
basist::basis_tex_format mode,
3999
const basisu::vector<image> *pSource_images,
4000
const basisu::vector<imagef> *pSource_images_hdr,
4001
uint32_t flags_and_quality, float uastc_rdo_quality,
4002
size_t* pSize,
4003
image_stats* pStats)
4004
{
4005
assert((pSource_images != nullptr) || (pSource_images_hdr != nullptr));
4006
assert(!((pSource_images != nullptr) && (pSource_images_hdr != nullptr)));
4007
4008
// Check input parameters
4009
if (pSource_images)
4010
{
4011
if ((!pSource_images->size()) || (!pSize))
4012
{
4013
error_printf("basis_compress: Invalid parameter\n");
4014
assert(0);
4015
return nullptr;
4016
}
4017
}
4018
else
4019
{
4020
if ((!pSource_images_hdr->size()) || (!pSize))
4021
{
4022
error_printf("basis_compress: Invalid parameter\n");
4023
assert(0);
4024
return nullptr;
4025
}
4026
}
4027
4028
*pSize = 0;
4029
4030
// Initialize a job pool
4031
uint32_t num_threads = 1;
4032
if (flags_and_quality & cFlagThreaded)
4033
num_threads = basisu::maximum<uint32_t>(1, std::thread::hardware_concurrency());
4034
4035
job_pool jp(num_threads);
4036
4037
// Initialize the compressor parameter struct
4038
basis_compressor_params comp_params;
4039
comp_params.set_format_mode(mode);
4040
4041
comp_params.m_pJob_pool = &jp;
4042
4043
comp_params.m_y_flip = (flags_and_quality & cFlagYFlip) != 0;
4044
comp_params.m_debug = (flags_and_quality & cFlagDebug) != 0;
4045
comp_params.m_debug_images = (flags_and_quality & cFlagDebugImages) != 0;
4046
4047
// Copy the largest mipmap level
4048
if (pSource_images)
4049
{
4050
comp_params.m_source_images.resize(1);
4051
comp_params.m_source_images[0] = (*pSource_images)[0];
4052
4053
// Copy the smaller mipmap levels, if any
4054
if (pSource_images->size() > 1)
4055
{
4056
comp_params.m_source_mipmap_images.resize(1);
4057
comp_params.m_source_mipmap_images[0].resize(pSource_images->size() - 1);
4058
4059
for (uint32_t i = 1; i < pSource_images->size(); i++)
4060
comp_params.m_source_mipmap_images[0][i - 1] = (*pSource_images)[i];
4061
}
4062
}
4063
else
4064
{
4065
comp_params.m_source_images_hdr.resize(1);
4066
comp_params.m_source_images_hdr[0] = (*pSource_images_hdr)[0];
4067
4068
// Copy the smaller mipmap levels, if any
4069
if (pSource_images_hdr->size() > 1)
4070
{
4071
comp_params.m_source_mipmap_images_hdr.resize(1);
4072
comp_params.m_source_mipmap_images_hdr[0].resize(pSource_images_hdr->size() - 1);
4073
4074
for (uint32_t i = 1; i < pSource_images->size(); i++)
4075
comp_params.m_source_mipmap_images_hdr[0][i - 1] = (*pSource_images_hdr)[i];
4076
}
4077
}
4078
4079
comp_params.m_multithreading = (flags_and_quality & cFlagThreaded) != 0;
4080
comp_params.m_use_opencl = (flags_and_quality & cFlagUseOpenCL) != 0;
4081
4082
comp_params.m_write_output_basis_or_ktx2_files = false;
4083
4084
comp_params.m_perceptual = (flags_and_quality & cFlagSRGB) != 0;
4085
comp_params.m_mip_srgb = comp_params.m_perceptual;
4086
comp_params.m_mip_gen = (flags_and_quality & (cFlagGenMipsWrap | cFlagGenMipsClamp)) != 0;
4087
comp_params.m_mip_wrapping = (flags_and_quality & cFlagGenMipsWrap) != 0;
4088
4089
if (mode == basist::basis_tex_format::cUASTC4x4)
4090
{
4091
comp_params.m_pack_uastc_ldr_4x4_flags = flags_and_quality & cPackUASTCLevelMask;
4092
comp_params.m_rdo_uastc_ldr_4x4 = (flags_and_quality & cFlagUASTCRDO) != 0;
4093
comp_params.m_rdo_uastc_ldr_4x4_quality_scalar = uastc_rdo_quality;
4094
}
4095
else if (mode == basist::basis_tex_format::cETC1S)
4096
{
4097
comp_params.m_etc1s_quality_level = basisu::maximum<uint32_t>(1, flags_and_quality & 255);
4098
}
4099
4100
comp_params.m_create_ktx2_file = (flags_and_quality & cFlagKTX2) != 0;
4101
4102
if (comp_params.m_create_ktx2_file)
4103
{
4104
// Set KTX2 specific parameters.
4105
if ((flags_and_quality & cFlagKTX2UASTCSuperCompression) && (comp_params.m_uastc))
4106
comp_params.m_ktx2_uastc_supercompression = basist::KTX2_SS_ZSTANDARD;
4107
4108
comp_params.m_ktx2_srgb_transfer_func = comp_params.m_perceptual;
4109
}
4110
4111
comp_params.m_compute_stats = (pStats != nullptr);
4112
comp_params.m_print_stats = (flags_and_quality & cFlagPrintStats) != 0;
4113
comp_params.m_status_output = (flags_and_quality & cFlagPrintStatus) != 0;
4114
4115
if (mode == basist::basis_tex_format::cUASTC_HDR_4x4)
4116
{
4117
comp_params.m_uastc_hdr_4x4_options.set_quality_level(flags_and_quality & cPackUASTCLevelMask);
4118
}
4119
else if ((mode == basist::basis_tex_format::cASTC_HDR_6x6) || (mode == basist::basis_tex_format::cASTC_HDR_6x6_INTERMEDIATE))
4120
{
4121
comp_params.m_astc_hdr_6x6_options.set_user_level(flags_and_quality & cPackUASTCLevelMask);
4122
comp_params.m_astc_hdr_6x6_options.m_lambda = uastc_rdo_quality;
4123
comp_params.m_astc_hdr_6x6_options.m_rec2020_bt2100_color_gamut = (flags_and_quality & cFlagREC2020) != 0;
4124
}
4125
4126
// Create the compressor, initialize it, and process the input
4127
basis_compressor comp;
4128
if (!comp.init(comp_params))
4129
{
4130
error_printf("basis_compress: basis_compressor::init() failed!\n");
4131
return nullptr;
4132
}
4133
4134
basis_compressor::error_code ec = comp.process();
4135
4136
if (ec != basis_compressor::cECSuccess)
4137
{
4138
error_printf("basis_compress: basis_compressor::process() failed with error code %u\n", (uint32_t)ec);
4139
return nullptr;
4140
}
4141
4142
if ((pStats) && (comp.get_opencl_failed()))
4143
{
4144
pStats->m_opencl_failed = true;
4145
}
4146
4147
// Get the output file data and return it to the caller
4148
void* pFile_data = nullptr;
4149
const uint8_vec* pFile_data_vec = comp_params.m_create_ktx2_file ? &comp.get_output_ktx2_file() : &comp.get_output_basis_file();
4150
4151
pFile_data = malloc(pFile_data_vec->size());
4152
if (!pFile_data)
4153
{
4154
error_printf("basis_compress: Out of memory\n");
4155
return nullptr;
4156
}
4157
memcpy(pFile_data, pFile_data_vec->get_ptr(), pFile_data_vec->size());
4158
4159
*pSize = pFile_data_vec->size();
4160
4161
if ((pStats) && (comp.get_stats().size()))
4162
{
4163
*pStats = comp.get_stats()[0];
4164
}
4165
4166
return pFile_data;
4167
}
4168
4169
void* basis_compress(
4170
basist::basis_tex_format mode,
4171
const basisu::vector<image>& source_images,
4172
uint32_t flags_and_quality, float uastc_rdo_quality,
4173
size_t* pSize,
4174
image_stats* pStats)
4175
{
4176
return basis_compress(mode, &source_images, nullptr, flags_and_quality, uastc_rdo_quality, pSize, pStats);
4177
}
4178
4179
void* basis_compress(
4180
basist::basis_tex_format mode,
4181
const basisu::vector<imagef>& source_images_hdr,
4182
uint32_t flags_and_quality, float lambda,
4183
size_t* pSize,
4184
image_stats* pStats)
4185
{
4186
return basis_compress(mode, nullptr, &source_images_hdr, flags_and_quality, lambda, pSize, pStats);
4187
}
4188
4189
void* basis_compress(
4190
basist::basis_tex_format mode,
4191
const uint8_t* pImageRGBA, uint32_t width, uint32_t height, uint32_t pitch_in_pixels,
4192
uint32_t flags_and_quality, float uastc_rdo_quality,
4193
size_t* pSize,
4194
image_stats* pStats)
4195
{
4196
if (!pitch_in_pixels)
4197
pitch_in_pixels = width;
4198
4199
if ((!pImageRGBA) || (!width) || (!height) || (pitch_in_pixels < width) || (!pSize))
4200
{
4201
error_printf("basis_compress: Invalid parameter\n");
4202
assert(0);
4203
return nullptr;
4204
}
4205
4206
*pSize = 0;
4207
4208
if ((width > BASISU_MAX_SUPPORTED_TEXTURE_DIMENSION) || (height > BASISU_MAX_SUPPORTED_TEXTURE_DIMENSION))
4209
{
4210
error_printf("basis_compress: Image too large\n");
4211
return nullptr;
4212
}
4213
4214
// Copy the source image
4215
basisu::vector<image> source_image(1);
4216
source_image[0].crop(width, height, width, g_black_color, false);
4217
for (uint32_t y = 0; y < height; y++)
4218
memcpy(source_image[0].get_ptr() + y * width, (const color_rgba*)pImageRGBA + y * pitch_in_pixels, width * sizeof(color_rgba));
4219
4220
return basis_compress(mode, source_image, flags_and_quality, uastc_rdo_quality, pSize, pStats);
4221
}
4222
4223
void basis_free_data(void* p)
4224
{
4225
free(p);
4226
}
4227
4228
bool basis_benchmark_etc1s_opencl(bool* pOpenCL_failed)
4229
{
4230
if (pOpenCL_failed)
4231
*pOpenCL_failed = false;
4232
4233
if (!opencl_is_available())
4234
{
4235
error_printf("basis_benchmark_etc1s_opencl: OpenCL support must be enabled first!\n");
4236
return false;
4237
}
4238
4239
const uint32_t W = 1024, H = 1024;
4240
basisu::vector<image> images;
4241
image& img = images.enlarge(1)->resize(W, H);
4242
4243
const uint32_t NUM_RAND_LETTERS = 6000;// 40000;
4244
4245
rand r;
4246
r.seed(200);
4247
4248
for (uint32_t i = 0; i < NUM_RAND_LETTERS; i++)
4249
{
4250
uint32_t x = r.irand(0, W - 1), y = r.irand(0, H - 1);
4251
uint32_t sx = r.irand(1, 4), sy = r.irand(1, 4);
4252
color_rgba c(r.byte(), r.byte(), r.byte(), 255);
4253
4254
img.debug_text(x, y, sx, sy, c, nullptr, false, "%c", static_cast<char>(r.irand(32, 127)));
4255
}
4256
4257
//save_png("test.png", img);
4258
4259
image_stats stats;
4260
4261
uint32_t flags_and_quality = cFlagSRGB | cFlagThreaded | 255;
4262
size_t comp_size = 0;
4263
4264
double best_cpu_time = 1e+9f, best_gpu_time = 1e+9f;
4265
4266
const uint32_t TIMES_TO_ENCODE = 2;
4267
interval_timer tm;
4268
4269
for (uint32_t i = 0; i < TIMES_TO_ENCODE; i++)
4270
{
4271
tm.start();
4272
void* pComp_data = basis_compress(
4273
basist::basis_tex_format::cETC1S,
4274
images,
4275
flags_and_quality, 1.0f,
4276
&comp_size,
4277
&stats);
4278
double cpu_time = tm.get_elapsed_secs();
4279
if (!pComp_data)
4280
{
4281
error_printf("basis_benchmark_etc1s_opencl: basis_compress() failed (CPU)!\n");
4282
return false;
4283
}
4284
4285
best_cpu_time = minimum(best_cpu_time, cpu_time);
4286
4287
basis_free_data(pComp_data);
4288
}
4289
4290
printf("Best CPU time: %3.3f\n", best_cpu_time);
4291
4292
for (uint32_t i = 0; i < TIMES_TO_ENCODE; i++)
4293
{
4294
tm.start();
4295
void* pComp_data = basis_compress(
4296
basist::basis_tex_format::cETC1S,
4297
images,
4298
flags_and_quality | cFlagUseOpenCL, 1.0f,
4299
&comp_size,
4300
&stats);
4301
4302
if (stats.m_opencl_failed)
4303
{
4304
error_printf("basis_benchmark_etc1s_opencl: OpenCL failed!\n");
4305
4306
basis_free_data(pComp_data);
4307
4308
if (pOpenCL_failed)
4309
*pOpenCL_failed = true;
4310
4311
return false;
4312
}
4313
4314
double gpu_time = tm.get_elapsed_secs();
4315
if (!pComp_data)
4316
{
4317
error_printf("basis_benchmark_etc1s_opencl: basis_compress() failed (GPU)!\n");
4318
return false;
4319
}
4320
4321
best_gpu_time = minimum(best_gpu_time, gpu_time);
4322
4323
basis_free_data(pComp_data);
4324
}
4325
4326
printf("Best GPU time: %3.3f\n", best_gpu_time);
4327
4328
return best_gpu_time < best_cpu_time;
4329
}
4330
4331
} // namespace basisu
4332
4333
4334
4335
4336