| // basisu_comp.cpp | |
| // Copyright (C) 2019-2026 Binomial LLC. All Rights Reserved. | |
| // | |
| // Licensed under the Apache License, Version 2.0 (the "License"); | |
| // you may not use this file except in compliance with the License. | |
| // You may obtain a copy of the License at | |
| // | |
| // http://www.apache.org/licenses/LICENSE-2.0 | |
| // | |
| // Unless required by applicable law or agreed to in writing, software | |
| // distributed under the License is distributed on an "AS IS" BASIS, | |
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
| // See the License for the specific language governing permissions and | |
| // limitations under the License. | |
| #include "basisu_comp.h" | |
| #include "basisu_enc.h" | |
| #include <unordered_set> | |
| #include <atomic> | |
| #include <map> | |
| //#define UASTC_HDR_DEBUG_SAVE_CATEGORIZED_BLOCKS | |
| // basisu_transcoder.cpp is where basisu_miniz lives now, we just need the declarations here. | |
| #define MINIZ_NO_ZLIB_COMPATIBLE_NAMES | |
| #include "basisu_miniz.h" | |
| #include "basisu_opencl.h" | |
| #include "basisu_astc_ldr_encode.h" | |
| #include "../transcoder/basisu_astc_hdr_core.h" | |
| #if !BASISD_SUPPORT_KTX2 | |
| #error BASISD_SUPPORT_KTX2 must be enabled (set to 1). | |
| #endif | |
| #if BASISD_SUPPORT_KTX2_ZSTD | |
| #include "../zstd/zstd.h" | |
| #endif | |
| // Set to 1 to disable the mipPadding alignment workaround (which only seems to be needed when no key-values are written at all) | |
| #define BASISU_DISABLE_KTX2_ALIGNMENT_WORKAROUND (0) | |
| // Set to 1 to disable writing all KTX2 key values, triggering an early validator bug. | |
| #define BASISU_DISABLE_KTX2_KEY_VALUES (0) | |
| using namespace buminiz; | |
| #define BASISU_USE_STB_IMAGE_RESIZE_FOR_MIPMAP_GEN 0 | |
| #define DEBUG_CROP_TEXTURE_TO_64x64 (0) | |
| #define DEBUG_RESIZE_TEXTURE (0) | |
| namespace basisu | |
| { | |
| static float uastc_ldr_4x4_lambda_from_quality(float q) | |
| { | |
| q = clamp<float>(q, 0.0f, 1.0f); | |
| if (q >= 1.0f) | |
| return 0.0f; | |
| const float lambda_max = 20.0f; | |
| return lambda_max * pow(1.0f - q, 1.3f); | |
| } | |
| static float uastc_hdr_6x6_lambda_from_quality(float q) | |
| { | |
| // Ideally we would know if it's an upconverted LDR/SDR input, or HDR, then that controls the maximum useful lambda. | |
| q = clamp<float>(q, 0.0f, 1.0f); | |
| if (q >= 1.0f) | |
| return 0.0f; | |
| const float lambda_max = 50000.0f; | |
| return lambda_max * pow(1.0f - q, 1.5f); | |
| } | |
| bool basis_compressor_params::set_format_mode_and_effort(basist::basis_tex_format mode, int effort, bool set_defaults) | |
| { | |
| fmt_debug_printf("set_format_mode_and_effort: mode: {}, effort: {}, set_defaults: {}\n", basist::basis_get_tex_format_name(mode), effort, set_defaults); | |
| set_format_mode(mode); | |
| if (effort > 0) | |
| effort = clamp<int>(effort, 0, 10); | |
| const float feffort = (effort >= 0) ? clamp<float>((float)effort / 10.0f, 0.0f, 1.0f) : 0.0f; | |
| if (mode == basist::basis_tex_format::cETC1S) | |
| { | |
| if (effort >= 0) | |
| m_etc1s_compression_level = (int)std::round(lerp<float>(0, (float)BASISU_MAX_ETC1S_COMPRESSION_LEVEL, feffort)); | |
| else if (set_defaults) | |
| m_etc1s_compression_level = BASISU_DEFAULT_ETC1S_COMPRESSION_LEVEL; | |
| fmt_debug_printf("Low-level ETC1S compression (effort) level (0-6): {}\n", m_etc1s_compression_level); | |
| } | |
| else if (mode == basist::basis_tex_format::cUASTC_LDR_4x4) | |
| { | |
| if (effort >= 0) | |
| m_pack_uastc_ldr_4x4_flags = (int)std::round(lerp<float>((float)cPackUASTCLevelFastest, (float)cPackUASTCLevelVerySlow, feffort)); | |
| else if (set_defaults) | |
| m_pack_uastc_ldr_4x4_flags = cPackUASTCLevelDefault; | |
| fmt_debug_printf("Low-level UASTC LDR 4x4 pack (effort) level (0-4): {}\n", m_pack_uastc_ldr_4x4_flags); | |
| } | |
| else if (mode == basist::basis_tex_format::cUASTC_HDR_4x4) | |
| { | |
| // Set UASTC HDR 4x4 effort level (there is no quality to set - it doesn't support RDO yet). | |
| if (effort >= 0) | |
| m_uastc_hdr_4x4_options.set_quality_level((int)std::round(lerp<float>((float)uastc_hdr_4x4_codec_options::cMinLevel, (float)uastc_hdr_4x4_codec_options::cMaxLevel, feffort))); | |
| else if (set_defaults) | |
| m_uastc_hdr_4x4_options.set_quality_level(uastc_hdr_4x4_codec_options::cDefaultLevel); | |
| fmt_debug_printf("Low-level UASTC HDR 4x4 quality (actually effort) level (0-4): {}\n", m_uastc_hdr_4x4_options.m_level); | |
| } | |
| else if ((mode == basist::basis_tex_format::cASTC_HDR_6x6) || (mode == basist::basis_tex_format::cUASTC_HDR_6x6_INTERMEDIATE)) | |
| { | |
| // Set ASTC HDR 6x6/UASTC HDR 6x6 effort level | |
| if (effort >= 0) | |
| m_astc_hdr_6x6_options.set_user_level(effort); | |
| else if (set_defaults) | |
| m_astc_hdr_6x6_options.set_user_level(astc_6x6_hdr::ASTC_HDR_6X6_DEF_USER_COMP_LEVEL); | |
| fmt_debug_printf("Low-level UASTC HDR 6x6 master comp (effort) level (0-4): {}, highest comp (effort) level (0-4): {}, num reuse XY deltas: {}, extra patterns flag: {}, brute force partition matching: {}\n", | |
| m_astc_hdr_6x6_options.m_master_comp_level, | |
| m_astc_hdr_6x6_options.m_highest_comp_level, | |
| m_astc_hdr_6x6_options.m_num_reuse_xy_deltas, | |
| m_astc_hdr_6x6_options.m_extra_patterns_flag, | |
| m_astc_hdr_6x6_options.m_brute_force_partition_matching); | |
| } | |
| else if ((mode >= basist::basis_tex_format::cXUASTC_LDR_4x4) && (mode <= basist::basis_tex_format::cASTC_LDR_12x12)) | |
| { | |
| if (effort >= 0) | |
| m_xuastc_ldr_effort_level = effort; | |
| else if (set_defaults) | |
| m_xuastc_ldr_effort_level = astc_ldr::EFFORT_LEVEL_DEF; | |
| fmt_debug_printf("Low-level XUASTC LDR effort level (0-10): {}\n", m_xuastc_ldr_effort_level); | |
| } | |
| else if (mode == basist::basis_tex_format::cXUBC7) | |
| { | |
| if (effort >= 0) | |
| m_xubc7_effort_level = clamp<int>(effort, 0, 10); | |
| else if (set_defaults) | |
| m_xubc7_effort_level = xbc7::DEFAULT_EFFORT_LEVEL; | |
| fmt_debug_printf("Low-level XUBC7 effort level (0-10): {}\n", m_xubc7_effort_level); | |
| } | |
| else | |
| { | |
| assert(0); | |
| return false; | |
| } | |
| return true; | |
| } | |
| bool basis_compressor_params::set_format_mode_and_quality_effort(basist::basis_tex_format mode, int quality, int effort, bool set_defaults) | |
| { | |
| fmt_debug_printf("set_format_mode_and_quality_effort: mode: {}, quality: {}, effort: {}, set_defaults: {}\n", basist::basis_get_tex_format_name(mode), quality, effort, set_defaults); | |
| if (!set_format_mode_and_effort(mode, effort, set_defaults)) | |
| return false; | |
| if (quality > 0) | |
| quality = clamp<int>(quality, 0, 100); | |
| const float fquality = (quality >= 0) ? clamp<float>((float)quality / 100.0f, 0.0f, 1.0f) : 0.0f; | |
| if (mode == basist::basis_tex_format::cETC1S) | |
| { | |
| // ETC1S: Map quality and effort to ETC1S quality and effort levels | |
| if (quality >= 0) | |
| m_quality_level = (int)std::round(lerp<float>(0, 255.0f, fquality)); | |
| else if (set_defaults) | |
| m_quality_level = -1; | |
| fmt_debug_printf("Low-level ETC1S quality level (0-255): {}\n", m_quality_level); | |
| } | |
| else if (mode == basist::basis_tex_format::cUASTC_LDR_4x4) | |
| { | |
| // UASTC LDR 4x4: Map quality to RDO lambda scalar, effort to UASTC LDR 4x4 packing level | |
| if ((quality >= 0) && (quality < 100)) | |
| { | |
| // Enable RDO postprocessing | |
| m_rdo_uastc_ldr_4x4 = true; | |
| // Attempt to derive a reasonable lambda from quality | |
| m_rdo_uastc_ldr_4x4_quality_scalar = uastc_ldr_4x4_lambda_from_quality(fquality); | |
| } | |
| else if (quality == 100) | |
| { | |
| // Disable RDO postprocessing | |
| m_rdo_uastc_ldr_4x4 = false; | |
| m_rdo_uastc_ldr_4x4_quality_scalar = 1.0f; | |
| } | |
| else if (set_defaults) | |
| { | |
| m_rdo_uastc_ldr_4x4 = false; | |
| m_rdo_uastc_ldr_4x4_quality_scalar = 1.0f; // the default is 1.0, but the RDO flag isn't enabled | |
| } | |
| fmt_debug_printf("Low-level UASTC LDR 4x4 RDO flag: {}, lambda setting (0=no extra distortion, higher=more distortion): {}\n", m_rdo_uastc_ldr_4x4, m_rdo_uastc_ldr_4x4_quality_scalar); | |
| } | |
| else if (mode == basist::basis_tex_format::cUASTC_HDR_4x4) | |
| { | |
| // UASTC HDR 4x4: Nothing to do for quality, it doesn't support RDO | |
| if ((quality != -1) && (quality < 100)) | |
| { | |
| fmt_printf("WARNING: UASTC HDR 4x4 codec doesn't have a 'quality' parameter (it doesn't currently support RDO)\n"); | |
| } | |
| } | |
| else if ((mode == basist::basis_tex_format::cASTC_HDR_6x6) || (mode == basist::basis_tex_format::cUASTC_HDR_6x6_INTERMEDIATE)) | |
| { | |
| // Set lambda (rate-distortion tradeoff) | |
| if (quality >= 0) | |
| m_astc_hdr_6x6_options.m_lambda = uastc_hdr_6x6_lambda_from_quality(fquality); | |
| else if (set_defaults) | |
| m_astc_hdr_6x6_options.m_lambda = 0.0f; | |
| fmt_debug_printf("Low-level UASTC HDR 6x6 lambda setting (0=no extra distortion, higher=more distortion): {}\n", m_astc_hdr_6x6_options.m_lambda); | |
| } | |
| else if ((mode >= basist::basis_tex_format::cASTC_LDR_4x4) && (mode <= basist::basis_tex_format::cASTC_LDR_12x12)) | |
| { | |
| // ASTC LDR 4x4-12x12: Nothing to do for quality, it doesn't support RDO | |
| if ((quality != -1) && (quality < 100)) | |
| { | |
| fmt_printf("WARNING: ASTC LDR 4x4-12x12 codec doesn't have a 'quality' parameter (it doesn't currently support RDO)\n"); | |
| } | |
| } | |
| else if ((mode >= basist::basis_tex_format::cXUASTC_LDR_4x4) && (mode <= basist::basis_tex_format::cXUASTC_LDR_12x12)) | |
| { | |
| // XUASTC LDR 4x4-12x12 | |
| if ((quality >= 0) && (quality < 100)) | |
| { | |
| // Enable DCT + lossy supercompression | |
| m_quality_level = quality; | |
| m_xuastc_ldr_use_dct = true; | |
| m_xuastc_ldr_use_lossy_supercompression = true; | |
| } | |
| else if (quality == 100) | |
| { | |
| // Disable DCT + lossy supercompression | |
| m_quality_level = -1; | |
| m_xuastc_ldr_use_dct = false; | |
| m_xuastc_ldr_use_lossy_supercompression = false; | |
| } | |
| else if (set_defaults) | |
| { | |
| m_quality_level = -1; | |
| m_xuastc_ldr_use_dct = false; | |
| m_xuastc_ldr_use_lossy_supercompression = false; | |
| } | |
| fmt_debug_printf("Low-level XUASTC quality level (0-100): {}, Use DCT: {}, Use lossy supercompression: {}\n", m_quality_level, m_xuastc_ldr_use_dct, m_xuastc_ldr_use_lossy_supercompression); | |
| } | |
| else if (mode == basist::basis_tex_format::cXUBC7) | |
| { | |
| if ((quality >= 0) && (quality <= 100)) | |
| { | |
| m_quality_level = clamp<int>(quality, 1, 100); | |
| } | |
| else if (set_defaults) | |
| { | |
| m_quality_level = -1; | |
| } | |
| fmt_debug_printf("Low-level XUBC7 quality level (0-100): {}\n", m_quality_level); | |
| } | |
| else | |
| { | |
| assert(0); | |
| return false; | |
| } | |
| return true; | |
| } | |
| basis_compressor::basis_compressor() : | |
| m_pOpenCL_context(nullptr), | |
| m_fmt_mode(basist::basis_tex_format::cETC1S), | |
| m_fmt_mode_block_width(4), | |
| m_fmt_mode_block_height(4), | |
| m_total_slice_orig_texels(0), | |
| m_basis_file_size(0), | |
| m_basis_bits_per_texel(0.0f), | |
| m_ktx2_file_size(0), | |
| m_ktx2_bits_per_texel(0.0f), | |
| m_total_blocks(0), | |
| m_hdr_image_scale(1.0f), | |
| m_ldr_to_hdr_upconversion_nit_multiplier(1.0f), | |
| m_upconverted_any_ldr_images(false), | |
| m_any_source_image_has_alpha(false), | |
| m_opencl_failed(false), | |
| m_has_been_processed(false) | |
| { | |
| debug_printf("basis_compressor::basis_compressor\n"); | |
| assert(g_library_initialized); | |
| } | |
| basis_compressor::~basis_compressor() | |
| { | |
| if (m_pOpenCL_context) | |
| { | |
| opencl_destroy_context(m_pOpenCL_context); | |
| m_pOpenCL_context = nullptr; | |
| } | |
| } | |
| void basis_compressor::check_for_hdr_inputs() | |
| { | |
| if ((!m_params.m_source_filenames.size()) && (!m_params.m_source_images.size())) | |
| { | |
| if (m_params.m_source_images_hdr.size()) | |
| { | |
| // Assume they want UASTC HDR if they've specified any HDR source images. | |
| m_params.m_hdr = true; | |
| } | |
| } | |
| if (!m_params.m_hdr) | |
| { | |
| // See if any files are .EXR or .HDR, if so switch the compressor to UASTC HDR mode. | |
| for (uint32_t i = 0; i < m_params.m_source_filenames.size(); i++) | |
| { | |
| std::string filename; | |
| string_get_filename(m_params.m_source_filenames[i].c_str(), filename); | |
| std::string ext(string_get_extension(filename)); | |
| string_tolower(ext); | |
| if ((ext == "exr") || (ext == "hdr")) | |
| { | |
| m_params.m_hdr = true; | |
| break; | |
| } | |
| } | |
| } | |
| if (m_params.m_hdr) | |
| { | |
| if (m_params.m_source_alpha_filenames.size()) | |
| { | |
| debug_printf("Warning: Alpha channel image filenames are not yet supported in UASTC HDR/ASTC HDR modes.\n"); | |
| m_params.m_source_alpha_filenames.clear(); | |
| } | |
| } | |
| if (m_params.m_hdr) | |
| m_params.m_uastc = true; | |
| } | |
| bool basis_compressor::sanity_check_input_params() | |
| { | |
| // Check for no source filenames specified. | |
| if ((m_params.m_read_source_images) && (!m_params.m_source_filenames.size())) | |
| { | |
| assert(0); | |
| return false; | |
| } | |
| // See if they've specified any source filenames, but didn't tell us to read them. | |
| if ((!m_params.m_read_source_images) && (m_params.m_source_filenames.size())) | |
| { | |
| assert(0); | |
| return false; | |
| } | |
| // Sanity check the input image parameters. | |
| if (m_params.m_read_source_images) | |
| { | |
| // Caller can't specify their own images if they want us to read source images from files. | |
| if (m_params.m_source_images.size() || m_params.m_source_images_hdr.size()) | |
| { | |
| assert(0); | |
| return false; | |
| } | |
| if (m_params.m_source_mipmap_images.size() || m_params.m_source_mipmap_images_hdr.size()) | |
| { | |
| assert(0); | |
| return false; | |
| } | |
| } | |
| else | |
| { | |
| // They didn't tell us to read any source files, so check for no LDR/HDR source images. | |
| if (!m_params.m_source_images.size() && !m_params.m_source_images_hdr.size()) | |
| { | |
| assert(0); | |
| return false; | |
| } | |
| // Now we know we've been supplied LDR and/or HDR source images, check for LDR vs. HDR conflicts. | |
| if (m_params.m_source_images.size()) | |
| { | |
| // They've supplied LDR images, so make sure they also haven't specified HDR input images. | |
| if (m_params.m_source_images_hdr.size() || m_params.m_source_mipmap_images_hdr.size()) | |
| { | |
| assert(0); | |
| return false; | |
| } | |
| } | |
| else | |
| { | |
| // No LDR images, so make sure they haven't specified any LDR mipmaps. | |
| if (m_params.m_source_mipmap_images.size()) | |
| { | |
| assert(0); | |
| return false; | |
| } | |
| // No LDR images, so ensure they've supplied some HDR images to process. | |
| if (!m_params.m_source_images_hdr.size()) | |
| { | |
| assert(0); | |
| return false; | |
| } | |
| } | |
| } | |
| return true; | |
| } | |
| bool basis_compressor::init(const basis_compressor_params ¶ms) | |
| { | |
| debug_printf("basis_compressor::init\n"); | |
| if (!g_library_initialized) | |
| { | |
| error_printf("basis_compressor::init: basisu_encoder_init() MUST be called before using any encoder functionality!\n"); | |
| return false; | |
| } | |
| if (!params.m_pJob_pool) | |
| { | |
| error_printf("basis_compressor::init: A non-null job_pool pointer must be specified\n"); | |
| return false; | |
| } | |
| m_params = params; | |
| if ((m_params.m_compute_stats) && (!m_params.m_validate_output_data)) | |
| m_params.m_validate_output_data = true; | |
| m_hdr_image_scale = 1.0f; | |
| m_ldr_to_hdr_upconversion_nit_multiplier = 1.0f; | |
| m_upconverted_any_ldr_images = false; | |
| m_has_been_processed = false; | |
| m_total_slice_orig_texels = 0; | |
| m_basis_file_size = 0; | |
| m_basis_bits_per_texel = 0.0f; | |
| m_ktx2_file_size = 0; | |
| m_ktx2_bits_per_texel = 0.0f; | |
| check_for_hdr_inputs(); | |
| if (m_params.m_hdr) | |
| { | |
| if ((m_params.m_debug) && (m_params.m_ktx2_and_basis_srgb_transfer_function) && (m_params.m_ktx2_and_basis_srgb_transfer_function.was_changed())) | |
| { | |
| debug_printf("Warning: m_ktx2_and_basis_srgb_transfer_function being forced to false in HDR mode (we always write linear KTX2/.basis files in HDR mode)\n"); | |
| } | |
| // Always slam m_ktx2_and_basis_srgb_transfer_function on HDR inputs. We always write linear to KTX2 and .basis for HDR outputs. | |
| m_params.m_ktx2_and_basis_srgb_transfer_function = false; | |
| } | |
| if (m_params.m_debug) | |
| { | |
| debug_printf("\nbasis_compressor::init:\n"); | |
| #define PRINT_BOOL_VALUE(v) fmt_debug_printf("{}: {} {}\n", BASISU_STRINGIZE2(v), static_cast<bool>(m_params.v), m_params.v.was_changed()); | |
| #define PRINT_INT_VALUE(v) fmt_debug_printf("{}: {} {}\n", BASISU_STRINGIZE2(v), static_cast<int>(m_params.v), m_params.v.was_changed()); | |
| #define PRINT_UINT_VALUE(v) fmt_debug_printf("{}: {} {}\n", BASISU_STRINGIZE2(v), static_cast<uint32_t>(m_params.v), m_params.v.was_changed()); | |
| #define PRINT_FLOAT_VALUE(v) fmt_debug_printf("{}: {} {}\n", BASISU_STRINGIZE2(v), static_cast<float>(m_params.v), m_params.v.was_changed()); | |
| fmt_debug_printf("Source LDR images: {}, HDR images: {}, filenames: {}, alpha filenames: {}, LDR mipmap images: {}, HDR mipmap images: {}\n", | |
| (uint64_t)m_params.m_source_images.size(), (uint64_t)m_params.m_source_images_hdr.size(), | |
| (uint64_t)m_params.m_source_filenames.size(), (uint64_t)m_params.m_source_alpha_filenames.size(), | |
| (uint64_t)m_params.m_source_mipmap_images.size(), (uint64_t)m_params.m_source_mipmap_images_hdr.size()); | |
| if (m_params.m_source_mipmap_images.size()) | |
| { | |
| debug_printf("m_source_mipmap_images array sizes:\n"); | |
| for (uint32_t i = 0; i < m_params.m_source_mipmap_images.size(); i++) | |
| debug_printf("%u ", m_params.m_source_mipmap_images[i].size()); | |
| debug_printf("\n"); | |
| } | |
| if (m_params.m_source_mipmap_images_hdr.size()) | |
| { | |
| debug_printf("m_source_mipmap_images_hdr array sizes:\n"); | |
| for (uint32_t i = 0; i < m_params.m_source_mipmap_images_hdr.size(); i++) | |
| debug_printf("%u ", m_params.m_source_mipmap_images_hdr[i].size()); | |
| debug_printf("\n"); | |
| } | |
| PRINT_BOOL_VALUE(m_hdr); | |
| switch (m_params.m_hdr_mode) | |
| { | |
| case hdr_modes::cUASTC_HDR_4X4: | |
| { | |
| fmt_debug_printf("m_hdr_mode: cUASTC_HDR_4X4\n"); | |
| break; | |
| } | |
| case hdr_modes::cASTC_HDR_6X6: | |
| { | |
| fmt_debug_printf("m_hdr_mode: cASTC_HDR_6X6\n"); | |
| break; | |
| } | |
| case hdr_modes::cUASTC_HDR_6X6_INTERMEDIATE: | |
| { | |
| fmt_debug_printf("m_hdr_mode: cUASTC_HDR_6X6_INTERMEDIATE\n"); | |
| break; | |
| } | |
| default: | |
| assert(false); | |
| return false; | |
| } | |
| PRINT_BOOL_VALUE(m_uastc); | |
| PRINT_INT_VALUE(m_xuastc_or_astc_ldr_basis_tex_format); | |
| PRINT_BOOL_VALUE(m_use_opencl); | |
| PRINT_BOOL_VALUE(m_y_flip); | |
| PRINT_BOOL_VALUE(m_debug); | |
| PRINT_BOOL_VALUE(m_validate_etc1s); | |
| PRINT_BOOL_VALUE(m_debug_images); | |
| PRINT_INT_VALUE(m_etc1s_compression_level); | |
| PRINT_BOOL_VALUE(m_perceptual); | |
| PRINT_BOOL_VALUE(m_no_endpoint_rdo); | |
| PRINT_BOOL_VALUE(m_no_selector_rdo); | |
| PRINT_BOOL_VALUE(m_read_source_images); | |
| PRINT_BOOL_VALUE(m_write_output_basis_or_ktx2_files); | |
| PRINT_BOOL_VALUE(m_compute_stats); | |
| PRINT_BOOL_VALUE(m_psnr_hvs_m_stats); | |
| PRINT_BOOL_VALUE(m_check_for_alpha); | |
| PRINT_BOOL_VALUE(m_force_alpha); | |
| debug_printf("swizzle: %d,%d,%d,%d\n", | |
| m_params.m_swizzle[0], | |
| m_params.m_swizzle[1], | |
| m_params.m_swizzle[2], | |
| m_params.m_swizzle[3]); | |
| PRINT_BOOL_VALUE(m_renormalize); | |
| PRINT_BOOL_VALUE(m_multithreading); | |
| PRINT_BOOL_VALUE(m_disable_hierarchical_endpoint_codebooks); | |
| PRINT_FLOAT_VALUE(m_endpoint_rdo_thresh); | |
| PRINT_FLOAT_VALUE(m_selector_rdo_thresh); | |
| PRINT_BOOL_VALUE(m_mip_gen); | |
| PRINT_BOOL_VALUE(m_mip_renormalize); | |
| PRINT_BOOL_VALUE(m_mip_wrapping); | |
| PRINT_BOOL_VALUE(m_mip_fast); | |
| PRINT_BOOL_VALUE(m_mip_srgb); | |
| PRINT_FLOAT_VALUE(m_mip_premultiplied); | |
| PRINT_FLOAT_VALUE(m_mip_scale); | |
| PRINT_INT_VALUE(m_mip_smallest_dimension); | |
| debug_printf("m_mip_filter: %s\n", m_params.m_mip_filter.c_str()); | |
| debug_printf("m_max_endpoint_clusters: %u\n", m_params.m_etc1s_max_endpoint_clusters); | |
| debug_printf("m_max_selector_clusters: %u\n", m_params.m_etc1s_max_selector_clusters); | |
| debug_printf("m_quality_level: %i\n", m_params.m_quality_level); | |
| debug_printf("UASTC HDR 4x4 quality level: %u\n", m_params.m_uastc_hdr_4x4_options.m_level); | |
| debug_printf("m_tex_type: %u\n", m_params.m_tex_type); | |
| debug_printf("m_userdata0: 0x%X, m_userdata1: 0x%X\n", m_params.m_userdata0, m_params.m_userdata1); | |
| 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); | |
| debug_printf("m_pack_uastc_ldr_4x4_flags: 0x%X\n", m_params.m_pack_uastc_ldr_4x4_flags); | |
| PRINT_BOOL_VALUE(m_rdo_uastc_ldr_4x4); | |
| PRINT_FLOAT_VALUE(m_rdo_uastc_ldr_4x4_quality_scalar); | |
| PRINT_INT_VALUE(m_rdo_uastc_ldr_4x4_dict_size); | |
| PRINT_FLOAT_VALUE(m_rdo_uastc_ldr_4x4_max_allowed_rms_increase_ratio); | |
| PRINT_FLOAT_VALUE(m_rdo_uastc_ldr_4x4_skip_block_rms_thresh); | |
| PRINT_FLOAT_VALUE(m_rdo_uastc_ldr_4x4_max_smooth_block_error_scale); | |
| PRINT_FLOAT_VALUE(m_rdo_uastc_ldr_4x4_smooth_block_max_std_dev); | |
| PRINT_BOOL_VALUE(m_rdo_uastc_ldr_4x4_favor_simpler_modes_in_rdo_mode) | |
| PRINT_BOOL_VALUE(m_rdo_uastc_ldr_4x4_multithreading); | |
| PRINT_INT_VALUE(m_resample_width); | |
| PRINT_INT_VALUE(m_resample_height); | |
| PRINT_FLOAT_VALUE(m_resample_factor); | |
| debug_printf("Has global codebooks: %u\n", m_params.m_pGlobal_codebooks ? 1 : 0); | |
| if (m_params.m_pGlobal_codebooks) | |
| { | |
| 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()); | |
| } | |
| PRINT_BOOL_VALUE(m_create_ktx2_file); | |
| debug_printf("KTX2 UASTC supercompression: %u\n", m_params.m_ktx2_uastc_supercompression); | |
| debug_printf("KTX2 Zstd supercompression level: %i\n", (int)m_params.m_ktx2_zstd_supercompression_level); | |
| debug_printf("KTX2/basis sRGB transfer function: %u\n", (int)m_params.m_ktx2_and_basis_srgb_transfer_function); | |
| debug_printf("Total key values: %u\n", m_params.m_key_values.size()); | |
| for (uint32_t i = 0; i < m_params.m_key_values.size(); i++) | |
| { | |
| debug_printf(" Key: \"%s\"\n", m_params.m_key_values[i].m_key.data()); | |
| debug_printf(" Value size: %u\n", m_params.m_key_values[i].m_value.size()); | |
| } | |
| PRINT_BOOL_VALUE(m_validate_output_data); | |
| PRINT_UINT_VALUE(m_transcode_flags); | |
| PRINT_BOOL_VALUE(m_ldr_hdr_upconversion_srgb_to_linear); | |
| PRINT_FLOAT_VALUE(m_ldr_hdr_upconversion_nit_multiplier); | |
| PRINT_FLOAT_VALUE(m_ldr_hdr_upconversion_black_bias); | |
| debug_printf("Allow UASTC HDR 4x4 uber mode: %u\n", m_params.m_uastc_hdr_4x4_options.m_allow_uber_mode); | |
| debug_printf("UASTC HDR 4x4 ultra quant: %u\n", m_params.m_uastc_hdr_4x4_options.m_ultra_quant); | |
| PRINT_BOOL_VALUE(m_hdr_favor_astc); | |
| PRINT_INT_VALUE(m_xuastc_ldr_effort_level); | |
| PRINT_BOOL_VALUE(m_xuastc_ldr_blurring); | |
| PRINT_INT_VALUE(m_xuastc_ldr_astc_comp_selection); | |
| PRINT_INT_VALUE(m_xuastc_ldr_sharpen_mode); | |
| PRINT_FLOAT_VALUE(m_xuastc_ldr_sharpen_amount); | |
| PRINT_INT_VALUE(m_xuastc_ldr_deblocking_mode); | |
| PRINT_UINT_VALUE(m_xuastc_ldr_num_deblocking_passes); | |
| PRINT_BOOL_VALUE(m_xuastc_ldr_heavy_subset_usage); | |
| PRINT_BOOL_VALUE(m_xuastc_ldr_use_dct); | |
| PRINT_BOOL_VALUE(m_xuastc_ldr_use_lossy_supercompression); | |
| PRINT_BOOL_VALUE(m_xuastc_ldr_force_disable_subsets); | |
| PRINT_BOOL_VALUE(m_xuastc_ldr_force_disable_rgb_dual_plane); | |
| PRINT_INT_VALUE(m_xuastc_ldr_syntax); | |
| debug_printf("LDR channel weights: "); | |
| for (uint32_t i = 0; i < 4; i++) | |
| fmt_debug_printf("{} ", m_params.m_ldr_channel_weights[i]); | |
| debug_printf("\n"); | |
| PRINT_FLOAT_VALUE(m_ls_min_psnr); | |
| PRINT_FLOAT_VALUE(m_ls_thresh_psnr); | |
| PRINT_FLOAT_VALUE(m_ls_thresh_edge_psnr); | |
| PRINT_FLOAT_VALUE(m_ls_min_alpha_psnr); | |
| PRINT_FLOAT_VALUE(m_ls_thresh_alpha_psnr); | |
| PRINT_FLOAT_VALUE(m_ls_thresh_edge_alpha_psnr); | |
| PRINT_INT_VALUE(m_xuastc_ldr_debug_block_x); | |
| PRINT_INT_VALUE(m_xuastc_ldr_debug_block_y); | |
| PRINT_INT_VALUE(m_xubc7_effort_level); | |
| PRINT_INT_VALUE(m_xubc7_rdo_level); | |
| PRINT_INT_VALUE(m_xubc7_num_stripes); | |
| PRINT_INT_VALUE(m_xubc7_encoder); | |
| PRINT_INT_VALUE(m_xubc7_bc7e_scalar_level); | |
| PRINT_BOOL_VALUE(m_status_output); | |
| PRINT_BOOL_VALUE(m_print_stats); | |
| #undef PRINT_BOOL_VALUE | |
| #undef PRINT_INT_VALUE | |
| #undef PRINT_UINT_VALUE | |
| #undef PRINT_FLOAT_VALUE | |
| fmt_printf("m_format_mode: {}\n", (uint32_t)m_params.get_format_mode()); | |
| fmt_printf("job pool total threads: {}\n", m_params.m_pJob_pool ? (uint32_t)m_params.m_pJob_pool->get_total_threads() : 0); | |
| fmt_printf("\n"); | |
| } | |
| if (!sanity_check_input_params()) | |
| return false; | |
| if ((m_params.m_use_opencl) && opencl_is_available() && !m_pOpenCL_context && !m_opencl_failed) | |
| { | |
| m_pOpenCL_context = opencl_create_context(); | |
| if (!m_pOpenCL_context) | |
| m_opencl_failed = true; | |
| } | |
| return true; | |
| } | |
| bool basis_compressor::pick_format_mode() | |
| { | |
| m_fmt_mode_block_width = 4; | |
| m_fmt_mode_block_height = 4; | |
| if ((uint32_t)m_params.m_format_mode == (uint32_t)basist::basis_tex_format::cXUBC7) | |
| { | |
| // XUBC7 - must be set using the new format API. | |
| m_fmt_mode = basist::basis_tex_format::cXUBC7; | |
| m_params.m_xuastc_or_astc_ldr_basis_tex_format = -1; | |
| m_params.m_hdr = false; | |
| m_params.m_uastc = true; | |
| m_params.m_hdr_mode = hdr_modes::cUASTC_HDR_4X4; // doesn't matter | |
| return true; | |
| } | |
| // Unfortunately due to the legacy of this code and backwards API compatibility this is more complex than I would like. | |
| // First assume ETC1S unless we're being overridden. | |
| m_fmt_mode = basist::basis_tex_format::cETC1S; | |
| if (m_params.m_hdr) | |
| { | |
| assert(m_params.m_uastc); | |
| assert(m_params.m_xuastc_or_astc_ldr_basis_tex_format == -1); | |
| switch (m_params.m_hdr_mode) | |
| { | |
| case hdr_modes::cUASTC_HDR_4X4: | |
| m_fmt_mode = basist::basis_tex_format::cUASTC_HDR_4x4; | |
| break; | |
| case hdr_modes::cASTC_HDR_6X6: | |
| m_fmt_mode = basist::basis_tex_format::cASTC_HDR_6x6; | |
| m_fmt_mode_block_width = 6; | |
| m_fmt_mode_block_height = 6; | |
| break; | |
| case hdr_modes::cUASTC_HDR_6X6_INTERMEDIATE: | |
| m_fmt_mode = basist::basis_tex_format::cUASTC_HDR_6x6_INTERMEDIATE; | |
| m_fmt_mode_block_width = 6; | |
| m_fmt_mode_block_height = 6; | |
| break; | |
| default: | |
| assert(0); | |
| break; | |
| } | |
| } | |
| else if (m_params.m_uastc) | |
| { | |
| if (m_params.m_xuastc_or_astc_ldr_basis_tex_format == -1) | |
| { | |
| // UASTC LDR 4x4 | |
| m_fmt_mode = basist::basis_tex_format::cUASTC_LDR_4x4; | |
| } | |
| else | |
| { | |
| // XUASTC LDR 4x4-12x12 or ASTC LDR 4x4-12x12 | |
| m_fmt_mode = static_cast<basist::basis_tex_format>(static_cast<int>(m_params.m_xuastc_or_astc_ldr_basis_tex_format)); | |
| if (!basis_tex_format_is_xuastc_ldr(m_fmt_mode) && !basis_tex_format_is_astc_ldr(m_fmt_mode)) | |
| { | |
| assert(0); | |
| error_printf("basis_compressor::pick_format_mode: m_xuastc_or_astc_ldr_basis_tex_format is invalid\n"); | |
| return false; | |
| } | |
| basist::get_basis_tex_format_block_size(m_fmt_mode, m_fmt_mode_block_width, m_fmt_mode_block_height); | |
| } | |
| } | |
| else | |
| { | |
| // ETC1S | |
| assert(m_params.m_xuastc_or_astc_ldr_basis_tex_format == -1); | |
| } | |
| if (m_params.m_debug) | |
| { | |
| switch (m_fmt_mode) | |
| { | |
| case basist::basis_tex_format::cETC1S: | |
| fmt_debug_printf("Format Mode: cETC1S\n"); | |
| break; | |
| case basist::basis_tex_format::cUASTC_LDR_4x4: | |
| fmt_debug_printf("Format Mode: cUASTC_LDR_4x4\n"); | |
| break; | |
| case basist::basis_tex_format::cUASTC_HDR_4x4: | |
| fmt_debug_printf("Format Mode: cUASTC_HDR_4x4\n"); | |
| break; | |
| case basist::basis_tex_format::cASTC_HDR_6x6: | |
| fmt_debug_printf("Format Mode: cASTC_HDR_6x6\n"); | |
| break; | |
| case basist::basis_tex_format::cUASTC_HDR_6x6_INTERMEDIATE: | |
| fmt_debug_printf("Format Mode: cUASTC_HDR_6x6_INTERMEDIATE\n"); | |
| break; | |
| case basist::basis_tex_format::cXUASTC_LDR_4x4: | |
| case basist::basis_tex_format::cXUASTC_LDR_5x4: | |
| case basist::basis_tex_format::cXUASTC_LDR_5x5: | |
| case basist::basis_tex_format::cXUASTC_LDR_6x5: | |
| case basist::basis_tex_format::cXUASTC_LDR_6x6: | |
| case basist::basis_tex_format::cXUASTC_LDR_8x5: | |
| case basist::basis_tex_format::cXUASTC_LDR_8x6: | |
| case basist::basis_tex_format::cXUASTC_LDR_10x5: | |
| case basist::basis_tex_format::cXUASTC_LDR_10x6: | |
| case basist::basis_tex_format::cXUASTC_LDR_8x8: | |
| case basist::basis_tex_format::cXUASTC_LDR_10x8: | |
| case basist::basis_tex_format::cXUASTC_LDR_10x10: | |
| case basist::basis_tex_format::cXUASTC_LDR_12x10: | |
| case basist::basis_tex_format::cXUASTC_LDR_12x12: | |
| { | |
| fmt_debug_printf("Format Mode: cXUASTC_LDR_{}x{}\n", m_fmt_mode_block_width, m_fmt_mode_block_height); | |
| break; | |
| } | |
| case basist::basis_tex_format::cASTC_LDR_4x4: | |
| case basist::basis_tex_format::cASTC_LDR_5x4: | |
| case basist::basis_tex_format::cASTC_LDR_5x5: | |
| case basist::basis_tex_format::cASTC_LDR_6x5: | |
| case basist::basis_tex_format::cASTC_LDR_6x6: | |
| case basist::basis_tex_format::cASTC_LDR_8x5: | |
| case basist::basis_tex_format::cASTC_LDR_8x6: | |
| case basist::basis_tex_format::cASTC_LDR_10x5: | |
| case basist::basis_tex_format::cASTC_LDR_10x6: | |
| case basist::basis_tex_format::cASTC_LDR_8x8: | |
| case basist::basis_tex_format::cASTC_LDR_10x8: | |
| case basist::basis_tex_format::cASTC_LDR_10x10: | |
| case basist::basis_tex_format::cASTC_LDR_12x10: | |
| case basist::basis_tex_format::cASTC_LDR_12x12: | |
| { | |
| fmt_debug_printf("Format Mode: cASTC_LDR_{}x{}\n", m_fmt_mode_block_width, m_fmt_mode_block_height); | |
| break; | |
| } | |
| case basist::basis_tex_format::cXUBC7: | |
| { | |
| fmt_debug_printf("Format Mode: cXUBC7\n"); | |
| break; | |
| } | |
| default: | |
| assert(0); | |
| break; | |
| } | |
| } | |
| return true; | |
| } | |
| basis_compressor::error_code basis_compressor::process() | |
| { | |
| debug_printf("basis_compressor::process\n"); | |
| if (m_has_been_processed) | |
| { | |
| error_printf("basis_compressor::process: process()/process_source_images() may only be called once per init().\n"); | |
| return cECFailedInitializing; | |
| } | |
| m_has_been_processed = true; | |
| if (!read_dds_source_images()) | |
| return cECFailedReadingSourceImages; | |
| // Note: After here m_params.m_hdr, m_params.m_uastc and m_fmt_mode, m_fmt_mode_block_width/height cannot be changed. | |
| if (!pick_format_mode()) | |
| return cECFailedInvalidParameters; | |
| if (!read_source_images()) | |
| return cECFailedReadingSourceImages; | |
| if (!validate_texture_type_constraints()) | |
| return cECFailedValidating; | |
| if (m_params.m_create_ktx2_file) | |
| { | |
| if (!validate_ktx2_constraints()) | |
| { | |
| 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"); | |
| return cECFailedValidating; | |
| } | |
| } | |
| // Some modes/codecs require extracting source blocks up front. | |
| if (!extract_source_blocks()) | |
| return cECFailedFrontEnd; | |
| if (m_params.m_hdr) | |
| { | |
| if (m_params.m_hdr_mode == hdr_modes::cUASTC_HDR_4X4) | |
| { | |
| // UASTC 4x4 HDR | |
| if (m_params.m_status_output) | |
| printf("Mode: UASTC 4x4 HDR Effort Level (0-4): %u\n", m_params.m_uastc_hdr_4x4_options.m_level); | |
| error_code ec = encode_slices_to_uastc_4x4_hdr(); | |
| if (ec != cECSuccess) | |
| return ec; | |
| } | |
| else | |
| { | |
| // ASTC 6x6 HDR/UASTC HDR 6x6i | |
| assert((m_params.m_hdr_mode == hdr_modes::cASTC_HDR_6X6) || (m_params.m_hdr_mode == hdr_modes::cUASTC_HDR_6X6_INTERMEDIATE)); | |
| if (m_params.m_status_output) | |
| { | |
| fmt_printf("Mode: ASTC 6x6 HDR {}, Base Effort Level (0-4): {}, Highest Effort Level (0-4): {}, Lambda: {}, REC 2020: {}\n", | |
| (m_params.m_hdr_mode == hdr_modes::cUASTC_HDR_6X6_INTERMEDIATE) ? "Intermediate" : "", | |
| m_params.m_astc_hdr_6x6_options.m_master_comp_level, m_params.m_astc_hdr_6x6_options.m_highest_comp_level, | |
| m_params.m_astc_hdr_6x6_options.m_lambda, m_params.m_astc_hdr_6x6_options.m_rec2020_bt2100_color_gamut); | |
| if (m_params.m_hdr_mode == hdr_modes::cUASTC_HDR_6X6_INTERMEDIATE) | |
| { | |
| fmt_printf("Writing v{} compatible UASTC HDR 6x6i bitstream\n", m_params.m_astc_hdr_6x6_options.m_write_basisu_1_6_compatible_files ? "1.60" : "2.00+"); | |
| } | |
| } | |
| error_code ec = encode_slices_to_astc_6x6_hdr(); | |
| if (ec != cECSuccess) | |
| return ec; | |
| } | |
| } | |
| else if (m_params.m_uastc) | |
| { | |
| error_code ec = cECFailedEncodeUASTC; | |
| if (m_fmt_mode == basist::basis_tex_format::cXUBC7) | |
| { | |
| // XUBC7 | |
| if (m_params.m_status_output) | |
| { | |
| fmt_printf("Mode: XUBC7, Effort Level (0-10): {}, Quality level (1-100): {}, RDO level (0-100): {}\n", | |
| m_params.m_xubc7_effort_level, m_params.m_quality_level, m_params.m_xubc7_rdo_level); | |
| } | |
| ec = encode_slices_to_xubc7(); | |
| } | |
| else if (basis_tex_format_is_xuastc_ldr(m_fmt_mode) || basis_tex_format_is_astc_ldr(m_fmt_mode)) | |
| { | |
| // XUASTC LDR 4x4-12x12 or ASTC LDR 4x4-12x12 | |
| if (m_params.m_status_output) | |
| { | |
| uint32_t block_width = 0, block_height = 0; | |
| basist::get_basis_tex_format_block_size(m_fmt_mode, block_width, block_height); | |
| if (basis_tex_format_is_xuastc_ldr(m_fmt_mode)) | |
| { | |
| fmt_printf("Mode: XUASTC LDR {}x{}, Effort Level (0-10): {}, Disable Subsets: {}, Disable RGB Dual Plane: {}\nWeight grid DCT: {}, DCT quality level (1-100): {}, Lossy supercompression: {}\nsRGB8 ASTC decode profile: {}, Syntax: {}, Channel weights: {} {} {} {}\n", | |
| block_width, block_height, (int)m_params.m_xuastc_ldr_effort_level, (bool)m_params.m_xuastc_ldr_force_disable_subsets, (bool)m_params.m_xuastc_ldr_force_disable_rgb_dual_plane, | |
| (bool)m_params.m_xuastc_ldr_use_dct, | |
| (bool)m_params.m_xuastc_ldr_use_dct ? m_params.m_quality_level : 0, | |
| (bool)m_params.m_xuastc_ldr_use_lossy_supercompression, | |
| (bool)m_params.m_ktx2_and_basis_srgb_transfer_function, | |
| (int)m_params.m_xuastc_ldr_syntax, | |
| m_params.m_ldr_channel_weights[0], m_params.m_ldr_channel_weights[1], m_params.m_ldr_channel_weights[2], m_params.m_ldr_channel_weights[3]); | |
| } | |
| else | |
| { | |
| fmt_printf("Mode: ASTC LDR {}x{}, Effort Level (0-10): {}, Disable Subsets: {}, Disable RGB Dual Plane: {}\nsRGB8 ASTC decode profile: {}, Syntax: {}, Channel weights: {} {} {} {}\n", | |
| block_width, block_height, | |
| (int)m_params.m_xuastc_ldr_effort_level, (bool)m_params.m_xuastc_ldr_force_disable_subsets, (bool)m_params.m_xuastc_ldr_force_disable_rgb_dual_plane, | |
| (bool)m_params.m_ktx2_and_basis_srgb_transfer_function, | |
| (int)m_params.m_xuastc_ldr_syntax, | |
| m_params.m_ldr_channel_weights[0], m_params.m_ldr_channel_weights[1], m_params.m_ldr_channel_weights[2], m_params.m_ldr_channel_weights[3]); | |
| } | |
| } | |
| ec = encode_slices_to_xuastc_or_astc_ldr(); | |
| } | |
| else | |
| { | |
| // UASTC LDR 4x4 | |
| if (m_params.m_status_output) | |
| { | |
| if (m_params.m_rdo_uastc_ldr_4x4) | |
| fmt_printf("Mode: UASTC LDR 4x4 Effort Level (0-4): {}, using RDO lambda: {}\n", m_params.m_pack_uastc_ldr_4x4_flags & cPackUASTCLevelMask, m_params.m_rdo_uastc_ldr_4x4_quality_scalar); | |
| else | |
| printf("Mode: UASTC LDR 4x4 Effort Level (0-4): %u\n", m_params.m_pack_uastc_ldr_4x4_flags & cPackUASTCLevelMask); | |
| } | |
| ec = encode_slices_to_uastc_4x4_ldr(); | |
| } | |
| if (ec != cECSuccess) | |
| return ec; | |
| } | |
| else | |
| { | |
| assert(m_fmt_mode == basist::basis_tex_format::cETC1S); | |
| // ETC1S | |
| if (m_params.m_status_output) | |
| printf("Mode: ETC1S Quality (0-255): %i, Comp Level (Effort, 0-6): %i\n", m_params.m_quality_level, (int)m_params.m_etc1s_compression_level); | |
| if (!process_frontend()) | |
| return cECFailedFrontEnd; | |
| if (!extract_frontend_texture_data()) | |
| return cECFailedFrontendExtract; | |
| if (!process_backend()) | |
| return cECFailedBackend; | |
| } | |
| if (!create_basis_file_and_transcode()) | |
| return cECFailedCreateBasisFile; | |
| if (m_params.m_create_ktx2_file) | |
| { | |
| if (!create_ktx2_file()) | |
| return cECFailedCreateKTX2File; | |
| } | |
| if (!write_output_files_and_compute_stats()) | |
| return cECFailedWritingOutput; | |
| if (m_params.m_status_output) | |
| fmt_printf("basis_compressor::process: Success\n"); | |
| return cECSuccess; | |
| } | |
| // Special-purpose alternative to process(): runs ONLY the source image loading/preparation prefix (identical to the | |
| // first stage of process()), then STOPS before block extraction, encoding, or file output. After success the caller | |
| // inspects the prepared slices/metadata via the get_slice_*()/get_params()/etc. accessors. See the header for usage. | |
| basis_compressor::error_code basis_compressor::process_source_images() | |
| { | |
| debug_printf("basis_compressor::process_source_images\n"); | |
| if (m_has_been_processed) | |
| { | |
| error_printf("basis_compressor::process_source_images: process()/process_source_images() may only be called once per init().\n"); | |
| return cECFailedInitializing; | |
| } | |
| m_has_been_processed = true; | |
| if (!read_dds_source_images()) | |
| return cECFailedReadingSourceImages; | |
| // Note: After here m_params.m_hdr, m_params.m_uastc and m_fmt_mode, m_fmt_mode_block_width/height cannot be changed. | |
| if (!pick_format_mode()) | |
| return cECFailedInvalidParameters; | |
| if (!read_source_images()) | |
| return cECFailedReadingSourceImages; | |
| if (!validate_texture_type_constraints()) | |
| return cECFailedValidating; | |
| if (m_params.m_create_ktx2_file) | |
| { | |
| if (!validate_ktx2_constraints()) | |
| { | |
| 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"); | |
| return cECFailedValidating; | |
| } | |
| } | |
| return cECSuccess; | |
| } | |
| // This is both ASTC HDR 6x6 and UASTC HDR 6x6i. | |
| basis_compressor::error_code basis_compressor::encode_slices_to_astc_6x6_hdr() | |
| { | |
| debug_printf("basis_compressor::encode_slices_to_astc_6x6_hdr\n"); | |
| interval_timer tm; | |
| tm.start(); | |
| m_uastc_slice_textures.resize(m_slice_descs.size()); | |
| for (uint32_t slice_index = 0; slice_index < m_slice_descs.size(); slice_index++) | |
| 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); | |
| if (m_params.m_hdr_mode == hdr_modes::cASTC_HDR_6X6) | |
| m_uastc_backend_output.m_tex_format = basist::basis_tex_format::cASTC_HDR_6x6; | |
| else if (m_params.m_hdr_mode == hdr_modes::cUASTC_HDR_6X6_INTERMEDIATE) | |
| m_uastc_backend_output.m_tex_format = basist::basis_tex_format::cUASTC_HDR_6x6_INTERMEDIATE; | |
| else | |
| { | |
| assert(0); | |
| return cECFailedEncodeUASTC; | |
| } | |
| m_uastc_backend_output.m_etc1s = false; | |
| m_uastc_backend_output.m_srgb = false; | |
| m_uastc_backend_output.m_slice_desc = m_slice_descs; | |
| m_uastc_backend_output.m_slice_image_data.resize(m_slice_descs.size()); | |
| m_uastc_backend_output.m_slice_image_crcs.resize(m_slice_descs.size()); | |
| astc_6x6_hdr::astc_hdr_6x6_global_config global_cfg(m_params.m_astc_hdr_6x6_options); | |
| global_cfg.m_image_stats = m_params.m_compute_stats; | |
| global_cfg.m_debug_images = m_params.m_debug_images; | |
| global_cfg.m_output_images = m_params.m_debug_images; | |
| global_cfg.m_debug_output = m_params.m_debug; | |
| global_cfg.m_status_output = m_params.m_status_output || m_params.m_debug; | |
| for (uint32_t slice_index = 0; slice_index < m_slice_descs.size(); slice_index++) | |
| { | |
| if (m_params.m_status_output) | |
| fmt_printf("Encoding slice {}\n", slice_index); | |
| gpu_image& dst_tex = m_uastc_slice_textures[slice_index]; | |
| uint8_vec &dst_buf = m_uastc_backend_output.m_slice_image_data[slice_index]; | |
| basisu_backend_slice_desc& slice_desc = m_slice_descs[slice_index]; | |
| (void)slice_desc; | |
| const imagef& source_image = m_slice_images_hdr[slice_index]; | |
| assert(source_image.get_width() && source_image.get_height()); | |
| uint8_vec intermediate_tex_data, astc_tex_data; | |
| global_cfg.m_debug_image_prefix = m_params.m_astc_hdr_6x6_options.m_debug_image_prefix; | |
| global_cfg.m_debug_image_prefix += fmt_string("slice_{}_", slice_index); | |
| global_cfg.m_output_image_prefix = m_params.m_astc_hdr_6x6_options.m_output_image_prefix; | |
| global_cfg.m_output_image_prefix += fmt_string("slice_{}_", slice_index); | |
| if (m_params.m_debug) | |
| fmt_debug_printf("----------------------------------------------------------------------------\n"); | |
| astc_6x6_hdr::result_metrics metrics; | |
| bool status = astc_6x6_hdr::compress_photo(source_image, global_cfg, m_params.m_pJob_pool, intermediate_tex_data, astc_tex_data, metrics); | |
| if (!status) | |
| return cECFailedEncodeUASTC; | |
| if (m_params.m_debug) | |
| fmt_debug_printf("----------------------------------------------------------------------------\n"); | |
| // Currently it always gives us both intermediate and RDO | |
| assert(intermediate_tex_data.size()); | |
| assert(astc_tex_data.size()); | |
| assert((astc_tex_data.size() & 15) == 0); | |
| assert(dst_tex.get_size_in_bytes() == astc_tex_data.size_in_bytes()); | |
| memcpy(dst_tex.get_ptr(), astc_tex_data.data(), astc_tex_data.size_in_bytes()); | |
| if (m_params.m_hdr_mode == hdr_modes::cASTC_HDR_6X6) | |
| { | |
| dst_buf.resize(dst_tex.get_size_in_bytes()); | |
| memcpy(&dst_buf[0], dst_tex.get_ptr(), dst_tex.get_size_in_bytes()); | |
| } | |
| else | |
| { | |
| assert(m_params.m_hdr_mode == hdr_modes::cUASTC_HDR_6X6_INTERMEDIATE); | |
| dst_buf.resize(intermediate_tex_data.size_in_bytes()); | |
| memcpy(&dst_buf[0], intermediate_tex_data.get_ptr(), intermediate_tex_data.size_in_bytes()); | |
| } | |
| m_uastc_backend_output.m_slice_image_crcs[slice_index] = basist::crc16(dst_buf.get_ptr(), dst_buf.size_in_bytes(), 0); | |
| } | |
| return cECSuccess; | |
| } | |
| basis_compressor::error_code basis_compressor::encode_slices_to_uastc_4x4_hdr() | |
| { | |
| debug_printf("basis_compressor::encode_slices_to_uastc_4x4_hdr\n"); | |
| interval_timer tm; | |
| tm.start(); | |
| m_uastc_slice_textures.resize(m_slice_descs.size()); | |
| for (uint32_t slice_index = 0; slice_index < m_slice_descs.size(); slice_index++) | |
| 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); | |
| m_uastc_backend_output.m_tex_format = basist::basis_tex_format::cUASTC_HDR_4x4; | |
| m_uastc_backend_output.m_etc1s = false; | |
| m_uastc_backend_output.m_srgb = false; | |
| m_uastc_backend_output.m_slice_desc = m_slice_descs; | |
| m_uastc_backend_output.m_slice_image_data.resize(m_slice_descs.size()); | |
| m_uastc_backend_output.m_slice_image_crcs.resize(m_slice_descs.size()); | |
| if (!m_params.m_perceptual) | |
| { | |
| m_params.m_uastc_hdr_4x4_options.m_r_err_scale = 1.0f; | |
| m_params.m_uastc_hdr_4x4_options.m_g_err_scale = 1.0f; | |
| } | |
| const float DEFAULT_BC6H_ERROR_WEIGHT = .65f;// .85f; | |
| const float LOWEST_BC6H_ERROR_WEIGHT = .1f; | |
| 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; | |
| std::atomic<bool> any_failures; | |
| any_failures.store(false); | |
| astc_hdr_4x4_block_stats enc_stats; | |
| struct uastc_blk_desc | |
| { | |
| uint32_t m_solid_flag; | |
| uint32_t m_num_partitions; | |
| uint32_t m_cem_index; | |
| uint32_t m_weight_ise_range; | |
| uint32_t m_endpoint_ise_range; | |
| bool operator< (const uastc_blk_desc& desc) const | |
| { | |
| if (this == &desc) | |
| return false; | |
| #define COMP(XX) if (XX < desc.XX) return true; else if (XX != desc.XX) return false; | |
| COMP(m_solid_flag) | |
| COMP(m_num_partitions) | |
| COMP(m_cem_index) | |
| COMP(m_weight_ise_range) | |
| COMP(m_endpoint_ise_range) | |
| #undef COMP | |
| return false; | |
| } | |
| bool operator== (const uastc_blk_desc& desc) const | |
| { | |
| if (this == &desc) | |
| return true; | |
| if ((*this < desc) || (desc < *this)) | |
| return false; | |
| return true; | |
| } | |
| bool operator!= (const uastc_blk_desc& desc) const | |
| { | |
| return !(*this == desc); | |
| } | |
| }; | |
| struct uastc_blk_desc_stats | |
| { | |
| uastc_blk_desc_stats() : m_count(0) { } | |
| uint32_t m_count; | |
| #ifdef UASTC_HDR_DEBUG_SAVE_CATEGORIZED_BLOCKS | |
| basisu::vector<basist::astc_blk> m_blks; | |
| #endif | |
| }; | |
| std::map<uastc_blk_desc, uastc_blk_desc_stats> unique_block_descs; | |
| std::mutex unique_block_desc_mutex; | |
| std::mutex status_output_mutex; | |
| uint32_t total_blocks_processed = 0; | |
| float last_percentage_printed = 0; | |
| for (uint32_t slice_index = 0; slice_index < m_slice_descs.size(); slice_index++) | |
| { | |
| if (m_params.m_status_output) | |
| fmt_printf("Encoding slice {}\n", slice_index); | |
| gpu_image& tex = m_uastc_slice_textures[slice_index]; | |
| basisu_backend_slice_desc& slice_desc = m_slice_descs[slice_index]; | |
| (void)slice_desc; | |
| const uint32_t num_blocks_x = tex.get_blocks_x(); | |
| const uint32_t num_blocks_y = tex.get_blocks_y(); | |
| const uint32_t total_blocks = tex.get_total_blocks(); | |
| const imagef& source_image = m_slice_images_hdr[slice_index]; | |
| const uint32_t N = 256; | |
| for (uint32_t block_index_iter = 0; block_index_iter < total_blocks; block_index_iter += N) | |
| { | |
| const uint32_t first_index = block_index_iter; | |
| const uint32_t last_index = minimum<uint32_t>(total_blocks, block_index_iter + N); | |
| m_params.m_pJob_pool->add_job([this, first_index, last_index, num_blocks_x, num_blocks_y, total_blocks, &source_image, | |
| &tex, &any_failures, &enc_stats, &unique_block_descs, &unique_block_desc_mutex, | |
| &status_output_mutex, &total_blocks_processed, &last_percentage_printed] | |
| { | |
| BASISU_NOTE_UNUSED(num_blocks_y); | |
| basisu::vector<astc_hdr_4x4_pack_results> all_results; | |
| all_results.reserve(256); | |
| for (uint32_t block_index = first_index; block_index < last_index; block_index++) | |
| { | |
| const uint32_t block_x = block_index % num_blocks_x; | |
| const uint32_t block_y = block_index / num_blocks_x; | |
| //if ((block_x == 176) && (block_y == 128)) | |
| // printf("!"); | |
| vec4F block_pixels[16]; | |
| source_image.extract_block_clamped(&block_pixels[0], block_x * 4, block_y * 4, 4, 4); | |
| basist::astc_blk& dest_block = *(basist::astc_blk*)tex.get_block_ptr(block_x, block_y); | |
| float rgb_pixels[16 * 3]; | |
| basist::half_float rgb_pixels_half[16 * 3]; | |
| for (uint32_t i = 0; i < 16; i++) | |
| { | |
| rgb_pixels[i * 3 + 0] = block_pixels[i][0]; | |
| rgb_pixels_half[i * 3 + 0] = float_to_half_non_neg_no_nan_inf(block_pixels[i][0]); | |
| rgb_pixels[i * 3 + 1] = block_pixels[i][1]; | |
| rgb_pixels_half[i * 3 + 1] = float_to_half_non_neg_no_nan_inf(block_pixels[i][1]); | |
| rgb_pixels[i * 3 + 2] = block_pixels[i][2]; | |
| rgb_pixels_half[i * 3 + 2] = float_to_half_non_neg_no_nan_inf(block_pixels[i][2]); | |
| } | |
| bool status = astc_hdr_4x4_enc_block(&rgb_pixels[0], rgb_pixels_half, m_params.m_uastc_hdr_4x4_options, all_results); | |
| if (!status) | |
| { | |
| any_failures.store(true); | |
| continue; | |
| } | |
| double best_err = 1e+30f; | |
| int best_result_index = -1; | |
| const double bc6h_err_weight = m_params.m_uastc_hdr_4x4_options.m_bc6h_err_weight; | |
| const double astc_err_weight = (1.0f - bc6h_err_weight); | |
| for (uint32_t i = 0; i < all_results.size(); i++) | |
| { | |
| basist::half_float unpacked_bc6h_block[4 * 4 * 3]; | |
| unpack_bc6h(&all_results[i].m_bc6h_block, unpacked_bc6h_block, false); | |
| all_results[i].m_bc6h_block_error = compute_block_error(16, rgb_pixels_half, unpacked_bc6h_block, m_params.m_uastc_hdr_4x4_options); | |
| double overall_err = (all_results[i].m_bc6h_block_error * bc6h_err_weight) + (all_results[i].m_best_block_error * astc_err_weight); | |
| if ((!i) || (overall_err < best_err)) | |
| { | |
| best_err = overall_err; | |
| best_result_index = i; | |
| } | |
| } | |
| const astc_hdr_4x4_pack_results& best_results = all_results[best_result_index]; | |
| astc_hdr_4x4_pack_results_to_block(dest_block, best_results); | |
| // Verify that this block is valid UASTC HDR and we can successfully transcode it to BC6H. | |
| // (Well, except in fastest mode.) | |
| if (m_params.m_uastc_hdr_4x4_options.m_level > 0) | |
| { | |
| basist::bc6h_block transcoded_bc6h_blk; | |
| bool transcode_results = astc_hdr_transcode_to_bc6h(dest_block, transcoded_bc6h_blk); | |
| assert(transcode_results); | |
| if ((!transcode_results) && (!any_failures)) | |
| { | |
| error_printf("basis_compressor::encode_slices_to_uastc_4x4_hdr: UASTC HDR block transcode check failed!\n"); | |
| any_failures.store(true); | |
| continue; | |
| } | |
| } | |
| if (m_params.m_debug) | |
| { | |
| // enc_stats has its own mutex | |
| enc_stats.update(best_results); | |
| uastc_blk_desc blk_desc; | |
| clear_obj(blk_desc); | |
| blk_desc.m_solid_flag = best_results.m_is_solid; | |
| if (!blk_desc.m_solid_flag) | |
| { | |
| blk_desc.m_num_partitions = best_results.m_best_blk.m_num_partitions; | |
| blk_desc.m_cem_index = best_results.m_best_blk.m_color_endpoint_modes[0]; | |
| blk_desc.m_weight_ise_range = best_results.m_best_blk.m_weight_ise_range; | |
| blk_desc.m_endpoint_ise_range = best_results.m_best_blk.m_endpoint_ise_range; | |
| } | |
| { | |
| std::lock_guard<std::mutex> lck(unique_block_desc_mutex); | |
| auto res = unique_block_descs.insert(std::make_pair(blk_desc, uastc_blk_desc_stats())); | |
| (res.first)->second.m_count++; | |
| #ifdef UASTC_HDR_DEBUG_SAVE_CATEGORIZED_BLOCKS | |
| (res.first)->second.m_blks.push_back(dest_block); | |
| #endif | |
| } | |
| } | |
| } // block_index | |
| if (m_params.m_status_output) | |
| { | |
| float percent_done = 0; | |
| bool print_flag = false; | |
| { | |
| std::lock_guard<std::mutex> lck(status_output_mutex); | |
| total_blocks_processed += (last_index - first_index) + 1; | |
| percent_done = ((float)total_blocks_processed * 100.0f) / (float)total_blocks; | |
| if ((percent_done >= 100.0f) || (percent_done >= (last_percentage_printed + 5.0f))) | |
| { | |
| last_percentage_printed = percent_done; | |
| print_flag = true; | |
| } | |
| } | |
| // minor print race here, doesn't matter | |
| if (print_flag) | |
| debug_printf("basis_compressor::encode_slices_to_uastc_4x4_hdr: %3.1f%% done\n", percent_done); | |
| } | |
| }); | |
| } // block_index_iter | |
| m_params.m_pJob_pool->wait_for_all(); | |
| if (any_failures) | |
| return cECFailedEncodeUASTC; | |
| m_uastc_backend_output.m_slice_image_data[slice_index].resize(tex.get_size_in_bytes()); | |
| memcpy(&m_uastc_backend_output.m_slice_image_data[slice_index][0], tex.get_ptr(), tex.get_size_in_bytes()); | |
| m_uastc_backend_output.m_slice_image_crcs[slice_index] = basist::crc16(tex.get_ptr(), tex.get_size_in_bytes(), 0); | |
| } // slice_index | |
| debug_printf("basis_compressor::encode_slices_to_uastc_4x4_hdr: Total time: %3.3f secs\n", tm.get_elapsed_secs()); | |
| if (m_params.m_debug) | |
| { | |
| debug_printf("\n----- Total unique UASTC block descs: %u\n", (uint32_t)unique_block_descs.size()); | |
| uint32_t c = 0; | |
| for (auto it = unique_block_descs.begin(); it != unique_block_descs.end(); ++it) | |
| { | |
| debug_printf("%u. Total uses: %u %3.2f%%, solid color: %u\n", c, it->second.m_count, | |
| ((float)it->second.m_count * 100.0f) / enc_stats.m_total_blocks, it->first.m_solid_flag); | |
| if (!it->first.m_solid_flag) | |
| { | |
| debug_printf(" Num partitions: %u\n", it->first.m_num_partitions); | |
| debug_printf(" CEM index: %u\n", it->first.m_cem_index); | |
| 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)); | |
| 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)); | |
| } | |
| #ifdef UASTC_HDR_DEBUG_SAVE_CATEGORIZED_BLOCKS | |
| debug_printf(" -- UASTC HDR block bytes:\n"); | |
| for (uint32_t j = 0; j < minimum<uint32_t>(4, it->second.m_blks.size()); j++) | |
| { | |
| basist::astc_blk& blk = it->second.m_blks[j]; | |
| debug_printf(" - UASTC HDR: { "); | |
| for (uint32_t k = 0; k < 16; k++) | |
| debug_printf("%u%s", ((const uint8_t*)&blk)[k], (k != 15) ? ", " : ""); | |
| debug_printf(" }\n"); | |
| basist::bc6h_block bc6h_blk; | |
| bool res = astc_hdr_transcode_to_bc6h(blk, bc6h_blk); | |
| assert(res); | |
| if (!res) | |
| { | |
| error_printf("astc_hdr_transcode_to_bc6h() failed!\n"); | |
| return cECFailedEncodeUASTC; | |
| } | |
| debug_printf(" - BC6H: { "); | |
| for (uint32_t k = 0; k < 16; k++) | |
| debug_printf("%u%s", ((const uint8_t*)&bc6h_blk)[k], (k != 15) ? ", " : ""); | |
| debug_printf(" }\n"); | |
| } | |
| #endif | |
| c++; | |
| } | |
| printf("\n"); | |
| enc_stats.print(); | |
| } | |
| return cECSuccess; | |
| } | |
| // XUASTC 4x4-12x12 or ASTC 4x4-12x12 | |
| basis_compressor::error_code basis_compressor::encode_slices_to_xuastc_or_astc_ldr() | |
| { | |
| if (m_params.m_debug) | |
| debug_printf("basis_compressor::encode_slices_to_xuastc_or_astc_ldr\n"); | |
| m_uastc_slice_textures.resize(m_slice_descs.size()); | |
| const texture_format tex_fmt = basist::basis_get_texture_format_from_xuastc_or_astc_ldr_basis_tex_format(m_fmt_mode); | |
| const basist::transcoder_texture_format transcoder_tex_fmt = basist::basis_get_transcoder_texture_format_from_xuastc_or_astc_ldr_basis_tex_format(m_fmt_mode); | |
| uint32_t block_width = 0, block_height = 0; | |
| block_width = basist::basis_get_block_width(transcoder_tex_fmt); | |
| block_height = basist::basis_get_block_height(transcoder_tex_fmt); | |
| const bool very_large_blocks_flag = (block_width * block_height) >= basist::BASISU_DEBLOCKING_BLOCK_SIZE_THRESHOLD; | |
| #if defined(_DEBUG) || defined(DEBUG) | |
| // sanity checking | |
| { | |
| uint32_t alt_block_width = 0, alt_block_height = 0; | |
| get_basis_tex_format_block_size(m_fmt_mode, alt_block_width, alt_block_height); | |
| assert((block_width == alt_block_width) && (block_height == alt_block_height)); | |
| } | |
| #endif | |
| for (uint32_t slice_index = 0; slice_index < m_slice_descs.size(); slice_index++) | |
| m_uastc_slice_textures[slice_index].init(tex_fmt, m_slice_descs[slice_index].m_orig_width, m_slice_descs[slice_index].m_orig_height); | |
| m_uastc_backend_output.m_tex_format = m_fmt_mode; | |
| m_uastc_backend_output.m_etc1s = false; | |
| m_uastc_backend_output.m_srgb = m_params.m_ktx2_and_basis_srgb_transfer_function; | |
| m_uastc_backend_output.m_slice_desc = m_slice_descs; | |
| m_uastc_backend_output.m_slice_image_data.resize(m_slice_descs.size()); | |
| m_uastc_backend_output.m_slice_image_crcs.resize(m_slice_descs.size()); | |
| astc_ldr::astc_ldr_encode_config cfg; | |
| cfg.m_astc_block_width = block_width; | |
| cfg.m_astc_block_height = block_height; | |
| cfg.m_block_blurring_p1 = m_params.m_xuastc_ldr_blurring; | |
| cfg.m_block_blurring_p2 = m_params.m_xuastc_ldr_blurring; | |
| cfg.m_effort_level = clamp<int>(m_params.m_xuastc_ldr_effort_level, astc_ldr::EFFORT_LEVEL_MIN, astc_ldr::EFFORT_LEVEL_MAX); | |
| cfg.m_force_disable_subsets = m_params.m_xuastc_ldr_force_disable_subsets; | |
| cfg.m_force_disable_rgb_dual_plane = m_params.m_xuastc_ldr_force_disable_rgb_dual_plane; | |
| cfg.m_astc_decode_mode_srgb = m_params.m_ktx2_and_basis_srgb_transfer_function; | |
| cfg.m_use_astcenc = false; | |
| cfg.m_use_astcf = false; | |
| cfg.m_merge_basisu_into_output = false; | |
| switch (m_params.m_xuastc_ldr_astc_comp_selection) | |
| { | |
| case (int)xuastc_ldr_astc_comp_selection::cAuto: | |
| { | |
| switch (cfg.m_effort_level) | |
| { | |
| case 0: | |
| { | |
| if ((block_width == 4) && (block_height == 4)) | |
| { | |
| // special case: switches to bc7f->ASTC LDR 4x4 transcoding | |
| } | |
| else | |
| { | |
| cfg.m_use_astcf = true; | |
| } | |
| break; | |
| } | |
| case 1: | |
| case 2: | |
| { | |
| cfg.m_use_astcf = true; | |
| break; | |
| } | |
| case 3: | |
| { | |
| if (((block_width * block_height) > 64) || (m_any_source_image_has_alpha)) | |
| cfg.m_merge_basisu_into_output = true; | |
| cfg.m_use_astcf = true; | |
| break; | |
| } | |
| case 4: | |
| { | |
| cfg.m_merge_basisu_into_output = true; | |
| cfg.m_use_astcf = true; | |
| break; | |
| } | |
| default: | |
| { | |
| cfg.m_use_astcf = true; | |
| cfg.m_merge_basisu_into_output = true; | |
| break; | |
| } | |
| } | |
| break; | |
| } | |
| case (int)xuastc_ldr_astc_comp_selection::cBasisU: | |
| { | |
| break; | |
| } | |
| case (int)xuastc_ldr_astc_comp_selection::cASTCENC: | |
| { | |
| cfg.m_use_astcenc = true; | |
| break; | |
| } | |
| case (int)xuastc_ldr_astc_comp_selection::cASTCF: | |
| { | |
| cfg.m_use_astcf = true; | |
| break; | |
| } | |
| case (int)xuastc_ldr_astc_comp_selection::cBasisU_and_ASTCENC: | |
| { | |
| cfg.m_use_astcenc = true; | |
| cfg.m_merge_basisu_into_output = true; | |
| break; | |
| } | |
| case (int)xuastc_ldr_astc_comp_selection::cBasisU_and_ASTCF: | |
| { | |
| cfg.m_use_astcf = true; | |
| cfg.m_merge_basisu_into_output = true; | |
| break; | |
| } | |
| case (int)xuastc_ldr_astc_comp_selection::cUseAll: | |
| { | |
| cfg.m_use_astcenc = true; | |
| cfg.m_use_astcf = true; | |
| cfg.m_merge_basisu_into_output = true; | |
| break; | |
| } | |
| default: | |
| { | |
| fmt_error_printf("Invalid m_xuastc_ldr_astc_comp_selection\n"); | |
| return cECFailedInvalidParameters; | |
| } | |
| } | |
| cfg.m_sharpen_flag = false; | |
| if (m_params.m_xuastc_ldr_sharpen_mode == (int)xuastc_ldr_sharpen_mode::cAllBlockSizes) | |
| cfg.m_sharpen_flag = true; | |
| else if (m_params.m_xuastc_ldr_sharpen_mode == (int)xuastc_ldr_sharpen_mode::cOnlyLargestBlocks) | |
| { | |
| // only 10x8 or larger | |
| cfg.m_sharpen_flag = very_large_blocks_flag; | |
| } | |
| cfg.m_sharpen_amount = m_params.m_xuastc_ldr_sharpen_amount; | |
| cfg.m_compressed_syntax = (basist::astc_ldr_t::xuastc_ldr_syntax)(int)m_params.m_xuastc_ldr_syntax; | |
| if (cfg.m_compressed_syntax >= basist::astc_ldr_t::xuastc_ldr_syntax::cTotal) | |
| { | |
| error_printf("basis_compressor::encode_slices_to_xuastc_or_astc_ldr: Invalid XUASTC LDR syntax\n"); | |
| return cECFailedInvalidParameters; | |
| } | |
| if (basist::basis_tex_format_is_xuastc_ldr(m_fmt_mode)) | |
| { | |
| if ((m_params.m_quality_level >= astc_ldr::DCT_QUALITY_MIN) && (m_params.m_quality_level < astc_ldr::DCT_QUALITY_MAX)) | |
| { | |
| // Enable weight grid DCT | |
| cfg.m_dct_quality = static_cast<float>(clamp<int>(m_params.m_quality_level, astc_ldr::DCT_QUALITY_MIN, astc_ldr::DCT_QUALITY_MAX)); | |
| cfg.m_use_dct = m_params.m_xuastc_ldr_use_dct; | |
| } | |
| else | |
| { | |
| // <= 0 or >= 100 DCT quality level specified, but they wanted DCT - display warning | |
| if (m_params.m_xuastc_ldr_use_dct) | |
| { | |
| printf("Warning: m_xuastc_ldr_use_dct enabled, but quality level must range from [1,100).\n"); | |
| } | |
| } | |
| } | |
| cfg.m_lossy_supercompression = m_params.m_xuastc_ldr_use_lossy_supercompression; | |
| for (uint32_t i = 0; i < 4; i++) | |
| cfg.m_comp_weights[i] = m_params.m_ldr_channel_weights[i]; | |
| cfg.m_replacement_min_psnr = m_params.m_ls_min_psnr; | |
| cfg.m_psnr_trial_diff_thresh = m_params.m_ls_thresh_psnr; | |
| cfg.m_psnr_trial_diff_thresh_edge = m_params.m_ls_thresh_edge_psnr; | |
| cfg.m_replacement_min_psnr_alpha = m_params.m_ls_min_alpha_psnr; | |
| cfg.m_psnr_trial_diff_thresh_alpha = m_params.m_ls_thresh_alpha_psnr; | |
| cfg.m_psnr_trial_diff_thresh_edge_alpha = m_params.m_ls_thresh_edge_alpha_psnr; | |
| cfg.m_debug_block_x = m_params.m_xuastc_ldr_debug_block_x; | |
| cfg.m_debug_block_y = m_params.m_xuastc_ldr_debug_block_y; | |
| cfg.m_debug_output = m_params.m_debug; | |
| cfg.m_debug_images = m_params.m_debug_images; | |
| cfg.m_debug_output_image_metrics = m_params.m_compute_stats && m_params.m_print_stats; | |
| // By default: No SCD, no deblocking post filtering | |
| cfg.m_scd_enabled = false; | |
| cfg.m_scd_will_postfilter = false; | |
| cfg.m_num_scd_passes = 0; | |
| // SCD passes will factor in a chroma mismatch penality into the composite scores if m_params.m_perceptual is true (the default). | |
| cfg.m_scd_preserve_chroma = m_params.m_perceptual; | |
| // apply deblocking mode | |
| if (((very_large_blocks_flag) && (m_params.m_xuastc_ldr_deblocking_mode == (int)xuastc_ldr_deblocking_mode::cUseSCDAndFilteringOnlyLargestBlocks)) || | |
| (m_params.m_xuastc_ldr_deblocking_mode == (int)xuastc_ldr_deblocking_mode::cUseSCDAndFilteringAllBlockSizes)) | |
| { | |
| cfg.m_scd_enabled = true; | |
| cfg.m_scd_will_postfilter = true; | |
| } | |
| else if (m_params.m_xuastc_ldr_deblocking_mode == (int)xuastc_ldr_deblocking_mode::cUseSCDNoFiltering) | |
| { | |
| cfg.m_scd_enabled = true; | |
| } | |
| else if ( (m_params.m_xuastc_ldr_deblocking_mode == (int)xuastc_ldr_deblocking_mode::cNoSCDButEnableFilteringOnAllBlocks) || | |
| ((very_large_blocks_flag) && (m_params.m_xuastc_ldr_deblocking_mode == (int)xuastc_ldr_deblocking_mode::cNoSCDButEnableFilteringOnLargestBlocks)) ) | |
| { | |
| cfg.m_scd_will_postfilter = true; | |
| } | |
| if (cfg.m_scd_will_postfilter) | |
| { | |
| // Write the key-value field with the deblocking filter ID (there's only one defined currently) | |
| const uint32_t DEBLOCK_FILTER_ID = 1; | |
| basist::add_key_value(m_params.m_key_values, BASISU_DEBLOCK_FILTER_ID_NAME, fmt_string("{}", DEBLOCK_FILTER_ID)); | |
| } | |
| if (m_params.m_xuastc_ldr_num_deblocking_passes >= 256) | |
| { | |
| switch (cfg.m_effort_level) | |
| { | |
| case 0: | |
| case 1: | |
| cfg.m_num_scd_passes = 0; | |
| break; | |
| case 2: | |
| cfg.m_num_scd_passes = 2; | |
| break; | |
| case 3: | |
| cfg.m_num_scd_passes = 8; | |
| break; | |
| case 4: | |
| cfg.m_num_scd_passes = 10; | |
| break; | |
| case 5: | |
| cfg.m_num_scd_passes = 12; | |
| break; | |
| case 6: | |
| cfg.m_num_scd_passes = 14; | |
| break; | |
| case 7: | |
| cfg.m_num_scd_passes = 16; | |
| break; | |
| case 8: | |
| case 9: | |
| cfg.m_num_scd_passes = 20; | |
| break; | |
| case 10: | |
| default: | |
| cfg.m_num_scd_passes = 32; | |
| break; | |
| } | |
| } | |
| else if (cfg.m_scd_enabled) | |
| { | |
| cfg.m_num_scd_passes = m_params.m_xuastc_ldr_num_deblocking_passes; | |
| } | |
| if (!cfg.m_num_scd_passes) | |
| { | |
| cfg.m_scd_enabled = false; | |
| } | |
| cfg.m_try_simplified_latent_configs = m_params.m_xuastc_ldr_heavy_subset_usage; | |
| if (m_params.m_status_output) | |
| { | |
| fmt_printf("Prefiltering/blurring: {}, Sharpening: {} (Amount: {}), SCD/Deblocking Passes: {} (Factor in CPU/GPU Post-Filtering: {}), Heavy subset usage: {}, Compressor: {}\n", | |
| cfg.m_block_blurring_p1 || cfg.m_block_blurring_p2, | |
| cfg.m_sharpen_flag, cfg.m_sharpen_amount, cfg.m_scd_enabled, cfg.m_scd_will_postfilter, | |
| cfg.m_try_simplified_latent_configs, | |
| m_params.m_xuastc_ldr_astc_comp_selection); | |
| } | |
| for (uint32_t slice_index = 0; slice_index < m_slice_descs.size(); slice_index++) | |
| { | |
| if (m_params.m_status_output) | |
| fmt_printf("Encoding slice {}\n", slice_index); | |
| gpu_image& dst_tex = m_uastc_slice_textures[slice_index]; | |
| basisu_backend_slice_desc& slice_desc = m_slice_descs[slice_index]; | |
| (void)slice_desc; | |
| const image& slice_source_image = m_slice_images[slice_index]; | |
| const image* pSource_image = &slice_source_image; | |
| image temp_image; | |
| if ((slice_source_image.get_width() != slice_desc.m_orig_width) || (slice_source_image.get_height() != slice_desc.m_orig_height)) | |
| { | |
| // Copy to actual/original dimensions so PSNR statistics are calculated correctly. (There's no need to pad the image to multiples of the block dimensions.) | |
| temp_image = slice_source_image; | |
| temp_image.crop(slice_desc.m_orig_width, slice_desc.m_orig_height); | |
| pSource_image = &temp_image; | |
| } | |
| cfg.m_debug_file_prefix = fmt_string("slice_{}_", slice_index); | |
| if (m_params.m_debug) | |
| fmt_debug_printf("----------------------------------------------------------------------------\n"); | |
| uint8_vec intermediate_tex_data; | |
| vector2D<astc_helpers::log_astc_block> coded_log_blocks; | |
| bool comp_status = astc_ldr::compress_image(*pSource_image, intermediate_tex_data, coded_log_blocks, cfg, *m_params.m_pJob_pool); | |
| if (!comp_status) | |
| return cECFailedEncodeUASTC; | |
| if (m_params.m_debug) | |
| fmt_debug_printf("----------------------------------------------------------------------------\n"); | |
| const uint32_t num_blocks_x = dst_tex.get_blocks_x(); | |
| const uint32_t num_blocks_y = dst_tex.get_blocks_y(); | |
| assert(coded_log_blocks.get_width() == num_blocks_x); | |
| assert(coded_log_blocks.get_height() == num_blocks_y); | |
| for (uint32_t by = 0; by < num_blocks_y; by++) | |
| { | |
| for (uint32_t bx = 0; bx < num_blocks_x; bx++) | |
| { | |
| const astc_helpers::log_astc_block& log_blk = coded_log_blocks(bx, by); | |
| bool pack_status = astc_helpers::pack_astc_block(*static_cast<astc_helpers::astc_block *>(dst_tex.get_block_ptr(bx, by)), log_blk); | |
| if (!pack_status) | |
| { | |
| error_printf("basis_compressor::encode_slices_to_xuastc_or_astc_ldr: pack_astc_block() failed!\n"); | |
| return cECFailedEncodeUASTC; | |
| } | |
| } // bx | |
| } // by | |
| uint8_vec& dst_buf = m_uastc_backend_output.m_slice_image_data[slice_index]; | |
| if (basis_tex_format_is_astc_ldr(m_fmt_mode)) | |
| { | |
| // Plain ASTC LDR 4x4-12x12 | |
| dst_buf.resize(dst_tex.get_size_in_bytes()); | |
| memcpy(&dst_buf[0], dst_tex.get_ptr(), dst_tex.get_size_in_bytes()); | |
| } | |
| else | |
| { | |
| // Supercompressed XUASTC LDR 4x4-12x12 | |
| assert(intermediate_tex_data.size_in_bytes()); | |
| dst_buf.resize(intermediate_tex_data.size_in_bytes()); | |
| memcpy(&dst_buf[0], intermediate_tex_data.get_ptr(), intermediate_tex_data.size_in_bytes()); | |
| } | |
| m_uastc_backend_output.m_slice_image_crcs[slice_index] = basist::crc16(dst_buf.get_ptr(), dst_buf.size_in_bytes(), 0); | |
| } // slice_index | |
| return cECSuccess; | |
| } | |
| basis_compressor::error_code basis_compressor::encode_slices_to_xubc7() | |
| { | |
| if (m_params.m_debug) | |
| debug_printf("basis_compressor::encode_slices_to_xubc7\n"); | |
| m_uastc_slice_textures.resize(m_slice_descs.size()); | |
| const texture_format tex_fmt = texture_format::cBC7; | |
| //const basist::transcoder_texture_format transcoder_tex_fmt = basist::transcoder_texture_format::cTFBC7_RGBA; | |
| //BASISU_NOTE_UNUSED(transcoder_tex_fmt); | |
| //const uint32_t block_width = 4, block_height = 4; | |
| for (uint32_t slice_index = 0; slice_index < m_slice_descs.size(); slice_index++) | |
| m_uastc_slice_textures[slice_index].init(tex_fmt, m_slice_descs[slice_index].m_orig_width, m_slice_descs[slice_index].m_orig_height); | |
| m_uastc_backend_output.m_tex_format = m_fmt_mode; | |
| m_uastc_backend_output.m_etc1s = false; | |
| m_uastc_backend_output.m_srgb = m_params.m_ktx2_and_basis_srgb_transfer_function; | |
| m_uastc_backend_output.m_slice_desc = m_slice_descs; | |
| m_uastc_backend_output.m_slice_image_data.resize(m_slice_descs.size()); | |
| m_uastc_backend_output.m_slice_image_crcs.resize(m_slice_descs.size()); | |
| xbc7::pack_options options; | |
| options.m_dct_q = (m_params.m_quality_level < 0) ? 100 : clamp<int>(m_params.m_quality_level, 1, 100); | |
| options.set_rdo_level(m_params.m_xubc7_rdo_level); | |
| options.m_weights[0] = m_params.m_ldr_channel_weights[0]; | |
| options.m_weights[1] = m_params.m_ldr_channel_weights[1]; | |
| options.m_weights[2] = m_params.m_ldr_channel_weights[2]; | |
| options.m_weights[3] = m_params.m_ldr_channel_weights[3]; | |
| if ((options.m_dct_q >= 90) && (m_params.m_xubc7_effort_level >= 3)) | |
| { | |
| options.m_optimize_weights_after_bc7f = true; | |
| } | |
| if (m_params.m_xubc7_effort_level <= 1) | |
| { | |
| options.m_bc7_pack_flags = basist::bc7f::cPackBC7FlagDefault; | |
| } | |
| else if (m_params.m_xubc7_effort_level == 10) | |
| { | |
| options.m_bc7_pack_flags = basist::bc7f::cPackBC7FlagDefaultNonAnalytical; | |
| } | |
| options.m_effort_level = clamp<uint32_t>(m_params.m_xubc7_effort_level, 0, 10); | |
| options.m_bc7_encoder = (xbc7::bc7_encoder_type)(int)m_params.m_xubc7_encoder; | |
| options.m_bc7e_scalar_level = clamp<uint32_t>(m_params.m_xubc7_bc7e_scalar_level, xbc7::BC7E_SCALAR_MIN_LEVEL, xbc7::BC7E_SCALAR_MAX_LEVEL); | |
| options.m_perceptual = m_params.m_perceptual; | |
| options.m_debug_output = m_params.m_debug; | |
| options.m_print_stats = m_params.m_compute_stats && m_params.m_print_stats; | |
| options.m_debug_images = m_params.m_debug_images; | |
| options.m_pJob_pool = m_params.m_pJob_pool; | |
| const uint32_t desired_num_stripes = m_params.m_xubc7_num_stripes; // [1,16]; set_num_stripes_for_image() re-clamps per image | |
| //options.m_debug_file_prefix = fmt_string("slice_{}_", slice_index); | |
| if (m_params.m_status_output) | |
| { | |
| fmt_printf("XUBC7 encoder: {} (bc7e_scalar level: {}), effort: {}, RDO level [0,100]: {}, Desired stripes [1,16]: {}, Channel weights: {}, {}, {}, {}\n", | |
| (options.m_bc7_encoder == xbc7::bc7_encoder_type::cBC7E_Scalar) ? "bc7e_scalar" : "bc7f", options.m_bc7e_scalar_level, | |
| m_params.m_xubc7_effort_level, m_params.m_xubc7_rdo_level, desired_num_stripes, | |
| m_params.m_ldr_channel_weights[0], m_params.m_ldr_channel_weights[1], m_params.m_ldr_channel_weights[2], m_params.m_ldr_channel_weights[3]); | |
| } | |
| for (uint32_t slice_index = 0; slice_index < m_slice_descs.size(); slice_index++) | |
| { | |
| if (m_params.m_status_output) | |
| fmt_printf("Encoding slice {}\n", slice_index); | |
| gpu_image& dst_tex = m_uastc_slice_textures[slice_index]; | |
| basisu_backend_slice_desc& slice_desc = m_slice_descs[slice_index]; | |
| (void)slice_desc; | |
| const image& slice_source_image = m_slice_images[slice_index]; | |
| const image* pSource_image = &slice_source_image; | |
| image temp_image; | |
| if ((slice_source_image.get_width() != slice_desc.m_orig_width) || (slice_source_image.get_height() != slice_desc.m_orig_height)) | |
| { | |
| // Copy to actual/original dimensions so PSNR statistics are calculated correctly. (There's no need to pad the image to multiples of the block dimensions.) | |
| temp_image = slice_source_image; | |
| temp_image.crop(slice_desc.m_orig_width, slice_desc.m_orig_height); | |
| pSource_image = &temp_image; | |
| } | |
| options.m_debug_file_prefix = fmt_string("slice_{}_", slice_index); | |
| options.set_num_stripes_for_image(*pSource_image, desired_num_stripes); | |
| if (m_params.m_debug) | |
| fmt_debug_printf("----------------------------------------------------------------------------\n"); | |
| uint8_vec intermediate_tex_data; | |
| vector2D<basist::bc7u::log_bc7_block> coded_log_blocks; | |
| bool comp_status = xbc7::pack_image(*pSource_image, options, intermediate_tex_data, coded_log_blocks); | |
| if (!comp_status) | |
| return cECFailedEncodeUASTC; | |
| if (m_params.m_debug) | |
| fmt_debug_printf("----------------------------------------------------------------------------\n"); | |
| const uint32_t num_blocks_x = dst_tex.get_blocks_x(); | |
| const uint32_t num_blocks_y = dst_tex.get_blocks_y(); | |
| assert(coded_log_blocks.get_width() == num_blocks_x); | |
| assert(coded_log_blocks.get_height() == num_blocks_y); | |
| for (uint32_t by = 0; by < num_blocks_y; by++) | |
| { | |
| for (uint32_t bx = 0; bx < num_blocks_x; bx++) | |
| { | |
| const basist::bc7u::log_bc7_block& log_blk = coded_log_blocks(bx, by); | |
| bool pack_status = basist::bc7u::pack_bc7(log_blk, static_cast<basist::bc7u::phys_bc7_block*>(dst_tex.get_block_ptr(bx, by))); | |
| if (!pack_status) | |
| { | |
| error_printf("basis_compressor::encode_slices_to_xubc7: basist::bc7u::pack_bc7() failed!\n"); | |
| return cECFailedEncodeUASTC; | |
| } | |
| } // bx | |
| } // by | |
| uint8_vec& dst_buf = m_uastc_backend_output.m_slice_image_data[slice_index]; | |
| assert(intermediate_tex_data.size_in_bytes()); | |
| dst_buf.resize(intermediate_tex_data.size_in_bytes()); | |
| memcpy(&dst_buf[0], intermediate_tex_data.get_ptr(), intermediate_tex_data.size_in_bytes()); | |
| m_uastc_backend_output.m_slice_image_crcs[slice_index] = basist::crc16(dst_buf.get_ptr(), dst_buf.size_in_bytes(), 0); | |
| } // slice_index | |
| return cECSuccess; | |
| } | |
| basis_compressor::error_code basis_compressor::encode_slices_to_uastc_4x4_ldr() | |
| { | |
| debug_printf("basis_compressor::encode_slices_to_uastc_4x4_ldr\n"); | |
| m_uastc_slice_textures.resize(m_slice_descs.size()); | |
| for (uint32_t slice_index = 0; slice_index < m_slice_descs.size(); slice_index++) | |
| 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); | |
| m_uastc_backend_output.m_tex_format = basist::basis_tex_format::cUASTC_LDR_4x4; | |
| m_uastc_backend_output.m_etc1s = false; | |
| m_uastc_backend_output.m_slice_desc = m_slice_descs; | |
| m_uastc_backend_output.m_slice_image_data.resize(m_slice_descs.size()); | |
| m_uastc_backend_output.m_slice_image_crcs.resize(m_slice_descs.size()); | |
| for (uint32_t slice_index = 0; slice_index < m_slice_descs.size(); slice_index++) | |
| { | |
| if (m_params.m_status_output) | |
| fmt_printf("Encoding slice {}\n", slice_index); | |
| gpu_image& tex = m_uastc_slice_textures[slice_index]; | |
| basisu_backend_slice_desc& slice_desc = m_slice_descs[slice_index]; | |
| (void)slice_desc; | |
| const uint32_t num_blocks_x = tex.get_blocks_x(); | |
| const uint32_t num_blocks_y = tex.get_blocks_y(); | |
| const uint32_t total_blocks = tex.get_total_blocks(); | |
| const image& source_image = m_slice_images[slice_index]; | |
| std::mutex status_output_mutex; | |
| uint32_t total_blocks_processed = 0; | |
| float last_percentage_printed = 0; | |
| const uint32_t N = 256; | |
| for (uint32_t block_index_iter = 0; block_index_iter < total_blocks; block_index_iter += N) | |
| { | |
| const uint32_t first_index = block_index_iter; | |
| const uint32_t last_index = minimum<uint32_t>(total_blocks, block_index_iter + N); | |
| m_params.m_pJob_pool->add_job([this, first_index, last_index, num_blocks_x, num_blocks_y, total_blocks, &source_image, &tex, | |
| &status_output_mutex, &total_blocks_processed, &last_percentage_printed] | |
| { | |
| BASISU_NOTE_UNUSED(num_blocks_y); | |
| uint32_t uastc_flags = m_params.m_pack_uastc_ldr_4x4_flags; | |
| if ((m_params.m_rdo_uastc_ldr_4x4) && (m_params.m_rdo_uastc_ldr_4x4_favor_simpler_modes_in_rdo_mode)) | |
| uastc_flags |= cPackUASTCFavorSimplerModes; | |
| for (uint32_t block_index = first_index; block_index < last_index; block_index++) | |
| { | |
| const uint32_t block_x = block_index % num_blocks_x; | |
| const uint32_t block_y = block_index / num_blocks_x; | |
| color_rgba block_pixels[4][4]; | |
| source_image.extract_block_clamped((color_rgba*)block_pixels, block_x * 4, block_y * 4, 4, 4); | |
| basist::uastc_block& dest_block = *(basist::uastc_block*)tex.get_block_ptr(block_x, block_y); | |
| encode_uastc(&block_pixels[0][0].r, dest_block, uastc_flags); | |
| } // block_index | |
| if (m_params.m_status_output) | |
| { | |
| float percent_done = 0; | |
| bool print_flag = false; | |
| { | |
| std::lock_guard<std::mutex> lck(status_output_mutex); | |
| total_blocks_processed += (last_index - first_index) + 1; | |
| percent_done = ((float)total_blocks_processed * 100.0f) / (float)total_blocks; | |
| if ((percent_done >= 100.0f) || (percent_done >= (last_percentage_printed + 5.0f))) | |
| { | |
| last_percentage_printed = percent_done; | |
| print_flag = true; | |
| } | |
| } | |
| // minor print race here, doesn't matter | |
| if (print_flag) | |
| debug_printf("basis_compressor::encode_slices_to_uastc_4x4_ldr: %3.1f%% done\n", percent_done); | |
| } | |
| }); | |
| } // block_index_iter | |
| m_params.m_pJob_pool->wait_for_all(); | |
| if (m_params.m_rdo_uastc_ldr_4x4) | |
| { | |
| uastc_rdo_params rdo_params; | |
| rdo_params.m_lambda = m_params.m_rdo_uastc_ldr_4x4_quality_scalar; | |
| rdo_params.m_max_allowed_rms_increase_ratio = m_params.m_rdo_uastc_ldr_4x4_max_allowed_rms_increase_ratio; | |
| rdo_params.m_skip_block_rms_thresh = m_params.m_rdo_uastc_ldr_4x4_skip_block_rms_thresh; | |
| rdo_params.m_lz_dict_size = m_params.m_rdo_uastc_ldr_4x4_dict_size; | |
| rdo_params.m_smooth_block_max_error_scale = m_params.m_rdo_uastc_ldr_4x4_max_smooth_block_error_scale; | |
| rdo_params.m_max_smooth_block_std_dev = m_params.m_rdo_uastc_ldr_4x4_smooth_block_max_std_dev; | |
| bool status = uastc_rdo(tex.get_total_blocks(), (basist::uastc_block*)tex.get_ptr(), | |
| (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, | |
| (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); | |
| if (!status) | |
| { | |
| return cECFailedUASTCRDOPostProcess; | |
| } | |
| } | |
| m_uastc_backend_output.m_slice_image_data[slice_index].resize(tex.get_size_in_bytes()); | |
| memcpy(&m_uastc_backend_output.m_slice_image_data[slice_index][0], tex.get_ptr(), tex.get_size_in_bytes()); | |
| m_uastc_backend_output.m_slice_image_crcs[slice_index] = basist::crc16(tex.get_ptr(), tex.get_size_in_bytes(), 0); | |
| } // slice_index | |
| return cECSuccess; | |
| } | |
| bool basis_compressor::generate_mipmaps(const imagef& img, basisu::vector<imagef>& mips, bool has_alpha) | |
| { | |
| debug_printf("basis_compressor::generate_mipmaps\n"); | |
| interval_timer tm; | |
| tm.start(); | |
| uint32_t total_levels = 1; | |
| uint32_t w = img.get_width(), h = img.get_height(); | |
| while (maximum<uint32_t>(w, h) > (uint32_t)m_params.m_mip_smallest_dimension) | |
| { | |
| w = maximum(w >> 1U, 1U); | |
| h = maximum(h >> 1U, 1U); | |
| total_levels++; | |
| } | |
| for (uint32_t level = 1; level < total_levels; level++) | |
| { | |
| const uint32_t level_width = maximum<uint32_t>(1, img.get_width() >> level); | |
| const uint32_t level_height = maximum<uint32_t>(1, img.get_height() >> level); | |
| imagef& level_img = *enlarge_vector(mips, 1); | |
| level_img.resize(level_width, level_height); | |
| const imagef* pSource_image = &img; | |
| if (m_params.m_mip_fast) | |
| { | |
| if (level > 1) | |
| pSource_image = &mips[level - 1]; | |
| } | |
| bool status = image_resample(*pSource_image, level_img, | |
| //m_params.m_mip_filter.c_str(), | |
| "box", // TODO: negative lobes in the filter are causing negative colors, try Mitchell | |
| m_params.m_mip_scale, m_params.m_mip_wrapping, 0, has_alpha ? 4 : 3); | |
| if (!status) | |
| { | |
| error_printf("basis_compressor::generate_mipmaps: image_resample() failed!\n"); | |
| return false; | |
| } | |
| clean_hdr_image(level_img); | |
| } | |
| if (m_params.m_debug) | |
| debug_printf("Total mipmap generation time: %3.3f secs\n", tm.get_elapsed_secs()); | |
| return true; | |
| } | |
| bool basis_compressor::generate_mipmaps(const image &img, basisu::vector<image> &mips, bool has_alpha) | |
| { | |
| debug_printf("basis_compressor::generate_mipmaps\n"); | |
| interval_timer tm; | |
| tm.start(); | |
| uint32_t total_levels = 1; | |
| uint32_t w = img.get_width(), h = img.get_height(); | |
| while (maximum<uint32_t>(w, h) > (uint32_t)m_params.m_mip_smallest_dimension) | |
| { | |
| w = maximum(w >> 1U, 1U); | |
| h = maximum(h >> 1U, 1U); | |
| total_levels++; | |
| } | |
| #if BASISU_USE_STB_IMAGE_RESIZE_FOR_MIPMAP_GEN | |
| // Requires stb_image_resize | |
| stbir_filter filter = STBIR_FILTER_DEFAULT; | |
| if (m_params.m_mip_filter == "box") | |
| filter = STBIR_FILTER_BOX; | |
| else if (m_params.m_mip_filter == "triangle") | |
| filter = STBIR_FILTER_TRIANGLE; | |
| else if (m_params.m_mip_filter == "cubic") | |
| filter = STBIR_FILTER_CUBICBSPLINE; | |
| else if (m_params.m_mip_filter == "catmull") | |
| filter = STBIR_FILTER_CATMULLROM; | |
| else if (m_params.m_mip_filter == "mitchell") | |
| filter = STBIR_FILTER_MITCHELL; | |
| for (uint32_t level = 1; level < total_levels; level++) | |
| { | |
| const uint32_t level_width = maximum<uint32_t>(1, img.get_width() >> level); | |
| const uint32_t level_height = maximum<uint32_t>(1, img.get_height() >> level); | |
| image &level_img = *enlarge_vector(mips, 1); | |
| level_img.resize(level_width, level_height); | |
| int result = stbir_resize_uint8_generic( | |
| (const uint8_t *)img.get_ptr(), img.get_width(), img.get_height(), img.get_pitch() * sizeof(color_rgba), | |
| (uint8_t *)level_img.get_ptr(), level_img.get_width(), level_img.get_height(), level_img.get_pitch() * sizeof(color_rgba), | |
| has_alpha ? 4 : 3, has_alpha ? 3 : STBIR_ALPHA_CHANNEL_NONE, m_params.m_mip_premultiplied ? STBIR_FLAG_ALPHA_PREMULTIPLIED : 0, | |
| m_params.m_mip_wrapping ? STBIR_EDGE_WRAP : STBIR_EDGE_CLAMP, filter, m_params.m_mip_srgb ? STBIR_COLORSPACE_SRGB : STBIR_COLORSPACE_LINEAR, | |
| nullptr); | |
| if (result == 0) | |
| { | |
| error_printf("basis_compressor::generate_mipmaps: stbir_resize_uint8_generic() failed!\n"); | |
| return false; | |
| } | |
| if (m_params.m_mip_renormalize) | |
| level_img.renormalize_normal_map(); | |
| } | |
| #else | |
| for (uint32_t level = 1; level < total_levels; level++) | |
| { | |
| const uint32_t level_width = maximum<uint32_t>(1, img.get_width() >> level); | |
| const uint32_t level_height = maximum<uint32_t>(1, img.get_height() >> level); | |
| image& level_img = *enlarge_vector(mips, 1); | |
| level_img.resize(level_width, level_height); | |
| const image* pSource_image = &img; | |
| if (m_params.m_mip_fast) | |
| { | |
| if (level > 1) | |
| pSource_image = &mips[level - 1]; | |
| } | |
| 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); | |
| if (!status) | |
| { | |
| error_printf("basis_compressor::generate_mipmaps: image_resample() failed!\n"); | |
| return false; | |
| } | |
| if (m_params.m_mip_renormalize) | |
| level_img.renormalize_normal_map(); | |
| } | |
| #endif | |
| if (m_params.m_debug) | |
| debug_printf("Total mipmap generation time: %3.3f secs\n", tm.get_elapsed_secs()); | |
| return true; | |
| } | |
| void basis_compressor::clean_hdr_image(imagef& src_img) | |
| { | |
| const uint32_t width = src_img.get_width(); | |
| const uint32_t height = src_img.get_height(); | |
| // Find max used value | |
| float max_used_val = 0.0f; | |
| for (uint32_t y = 0; y < height; y++) | |
| { | |
| for (uint32_t x = 0; x < width; x++) | |
| { | |
| vec4F& c = src_img(x, y); | |
| for (uint32_t i = 0; i < 3; i++) | |
| max_used_val = maximum(max_used_val, c[i]); | |
| } | |
| } | |
| double hdr_image_scale = 1.0f; | |
| // If the max value can't be encoded safely to ASTC HDR, we'll have to rescale the source image. | |
| if (max_used_val > basist::ASTC_HDR_MAX_VAL) | |
| { | |
| hdr_image_scale = max_used_val / basist::ASTC_HDR_MAX_VAL; | |
| const double inv_hdr_image_scale = basist::ASTC_HDR_MAX_VAL / max_used_val; | |
| for (uint32_t y = 0; y < src_img.get_height(); y++) | |
| { | |
| for (uint32_t x = 0; x < src_img.get_width(); x++) | |
| { | |
| vec4F& c = src_img(x, y); | |
| for (uint32_t i = 0; i < 3; i++) | |
| c[i] = (float)minimum<double>(c[i] * inv_hdr_image_scale, basist::ASTC_HDR_MAX_VAL); | |
| } | |
| } | |
| 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", | |
| max_used_val, basist::ASTC_HDR_MAX_VAL, inv_hdr_image_scale); | |
| printf("The decoded/sampled ASTC HDR texture will have to be scaled up by %f. See the \"HDRScale\" KTX2 key value field.\n", hdr_image_scale); | |
| } | |
| // Remember the scale factor so it can be written to the output file. | |
| m_hdr_image_scale = (float)hdr_image_scale; | |
| // Final check of the input pixels for anything bad that could cause downstream encoding problems. | |
| if (!src_img.clean_astc_hdr_pixels(basist::ASTC_HDR_MAX_VAL)) | |
| printf("Warning: clean_astc_hdr_pixels() had to modify the input image to encode to ASTC HDR - see previous warning(s).\n"); | |
| float lowest_nonzero_val = 1e+30f; | |
| float lowest_val = 1e+30f; | |
| float highest_val = -1e+30f; | |
| for (uint32_t y = 0; y < src_img.get_height(); y++) | |
| { | |
| for (uint32_t x = 0; x < src_img.get_width(); x++) | |
| { | |
| const vec4F& c = src_img(x, y); | |
| for (uint32_t i = 0; i < 3; i++) | |
| { | |
| lowest_val = basisu::minimum(lowest_val, c[i]); | |
| if (c[i] != 0.0f) | |
| lowest_nonzero_val = basisu::minimum(lowest_nonzero_val, c[i]); | |
| highest_val = basisu::maximum(highest_val, c[i]); | |
| } | |
| } | |
| } | |
| 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); | |
| } | |
| bool basis_compressor::read_dds_source_images() | |
| { | |
| debug_printf("basis_compressor::read_dds_source_images\n"); | |
| // Nothing to do if the caller doesn't want us reading source images. | |
| if ((!m_params.m_read_source_images) || (!m_params.m_source_filenames.size())) | |
| return true; | |
| // Just bail of the caller has specified their own source images. | |
| if (m_params.m_source_images.size() || m_params.m_source_images_hdr.size()) | |
| return true; | |
| if (m_params.m_source_mipmap_images.size() || m_params.m_source_mipmap_images_hdr.size()) | |
| return true; | |
| // See if any input filenames are .DDS | |
| bool any_dds = false, all_dds = true; | |
| for (uint32_t i = 0; i < m_params.m_source_filenames.size(); i++) | |
| { | |
| std::string ext(string_get_extension(m_params.m_source_filenames[i])); | |
| if (strcasecmp(ext.c_str(), "dds") == 0) | |
| any_dds = true; | |
| else | |
| all_dds = false; | |
| } | |
| // Bail if no .DDS files specified. | |
| if (!any_dds) | |
| return true; | |
| // If any input is .DDS they all must be .DDS, for simplicity. | |
| if (!all_dds) | |
| { | |
| error_printf("If any filename is DDS, all filenames must be DDS.\n"); | |
| return false; | |
| } | |
| // Can't jam in alpha channel images if any .DDS files specified. | |
| if (m_params.m_source_alpha_filenames.size()) | |
| { | |
| error_printf("Source alpha filenames are not supported in DDS mode.\n"); | |
| return false; | |
| } | |
| bool any_mipmaps = false; | |
| // Read each .DDS texture file | |
| for (uint32_t i = 0; i < m_params.m_source_filenames.size(); i++) | |
| { | |
| basisu::vector<image> ldr_mips; | |
| basisu::vector<imagef> hdr_mips; | |
| bool status = read_uncompressed_dds_file(m_params.m_source_filenames[i].c_str(), ldr_mips, hdr_mips); | |
| if (!status) | |
| return false; | |
| assert(ldr_mips.size() || hdr_mips.size()); | |
| if (m_params.m_status_output) | |
| { | |
| printf("Read DDS file \"%s\", %s, %ux%u, %zu mipmap levels\n", | |
| m_params.m_source_filenames[i].c_str(), | |
| ldr_mips.size() ? "LDR" : "HDR", | |
| ldr_mips.size() ? ldr_mips[0].get_width() : hdr_mips[0].get_width(), | |
| ldr_mips.size() ? ldr_mips[0].get_height() : hdr_mips[0].get_height(), | |
| ldr_mips.size() ? ldr_mips.size() : hdr_mips.size()); | |
| } | |
| if (ldr_mips.size()) | |
| { | |
| if (m_params.m_source_images_hdr.size()) | |
| { | |
| error_printf("All DDS files must be of the same type (all LDR, or all HDR)\n"); | |
| return false; | |
| } | |
| m_params.m_source_images.push_back(ldr_mips[0]); | |
| m_params.m_source_mipmap_images.resize(m_params.m_source_mipmap_images.size() + 1); | |
| if (ldr_mips.size() > 1) | |
| { | |
| ldr_mips.erase_index(0U); | |
| m_params.m_source_mipmap_images.back().swap(ldr_mips); | |
| any_mipmaps = true; | |
| } | |
| } | |
| else | |
| { | |
| if (m_params.m_source_images.size()) | |
| { | |
| error_printf("All DDS files must be of the same type (all LDR, or all HDR)\n"); | |
| return false; | |
| } | |
| m_params.m_source_images_hdr.push_back(hdr_mips[0]); | |
| m_params.m_source_mipmap_images_hdr.resize(m_params.m_source_mipmap_images_hdr.size() + 1); | |
| if (hdr_mips.size() > 1) | |
| { | |
| hdr_mips.erase_index(0U); | |
| m_params.m_source_mipmap_images_hdr.back().swap(hdr_mips); | |
| any_mipmaps = true; | |
| } | |
| m_params.m_hdr = true; | |
| m_params.m_uastc = true; | |
| } | |
| } | |
| m_params.m_read_source_images = false; | |
| m_params.m_source_filenames.clear(); | |
| m_params.m_source_alpha_filenames.clear(); | |
| if (!any_mipmaps) | |
| { | |
| m_params.m_source_mipmap_images.clear(); | |
| m_params.m_source_mipmap_images_hdr.clear(); | |
| } | |
| if ((m_params.m_hdr) && (!m_params.m_source_images_hdr.size())) | |
| { | |
| error_printf("HDR mode enabled, but only LDR .DDS files were loaded. HDR mode requires half or float (HDR) .DDS inputs.\n"); | |
| return false; | |
| } | |
| return true; | |
| } | |
| bool basis_compressor::read_source_images() | |
| { | |
| debug_printf("basis_compressor::read_source_images\n"); | |
| const uint32_t total_source_files = m_params.m_read_source_images ? (uint32_t)m_params.m_source_filenames.size() : | |
| (m_params.m_hdr ? (uint32_t)m_params.m_source_images_hdr.size() : (uint32_t)m_params.m_source_images.size()); | |
| if (!total_source_files) | |
| { | |
| debug_printf("basis_compressor::read_source_images: No source images to process\n"); | |
| return false; | |
| } | |
| m_stats.resize(0); | |
| m_slice_descs.resize(0); | |
| m_slice_images.resize(0); | |
| m_slice_images_hdr.resize(0); | |
| m_total_blocks = 0; | |
| uint32_t total_macroblocks = 0; | |
| m_any_source_image_has_alpha = false; | |
| basisu::vector<image> source_images; | |
| basisu::vector<imagef> source_images_hdr; | |
| basisu::vector<std::string> source_filenames; | |
| // TODO: Note HDR images don't support alpha here, currently. | |
| // First load all source images, and determine if any have an alpha channel. | |
| for (uint32_t source_file_index = 0; source_file_index < total_source_files; source_file_index++) | |
| { | |
| const char* pSource_filename = ""; | |
| image file_image; | |
| imagef file_image_hdr; | |
| if (m_params.m_read_source_images) | |
| { | |
| pSource_filename = m_params.m_source_filenames[source_file_index].c_str(); | |
| // Load the source image | |
| if (m_params.m_hdr) | |
| { | |
| float upconversion_nit_multiplier = m_params.m_ldr_hdr_upconversion_nit_multiplier; | |
| if (upconversion_nit_multiplier == 0.0f) | |
| { | |
| // 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). | |
| upconversion_nit_multiplier = LDR_TO_HDR_NITS; | |
| } | |
| m_ldr_to_hdr_upconversion_nit_multiplier = upconversion_nit_multiplier; | |
| if (!is_image_filename_hdr(pSource_filename)) | |
| m_upconverted_any_ldr_images = true; | |
| 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)) | |
| { | |
| error_printf("Failed reading source image: %s\n", pSource_filename); | |
| return false; | |
| } | |
| // TODO: For now, just slam alpha to 1.0f. None of our HDR encoders support alpha yet. | |
| for (uint32_t y = 0; y < file_image_hdr.get_height(); y++) | |
| for (uint32_t x = 0; x < file_image_hdr.get_width(); x++) | |
| file_image_hdr(x, y)[3] = 1.0f; | |
| } | |
| else | |
| { | |
| if (!load_image(pSource_filename, file_image)) | |
| { | |
| error_printf("Failed reading source image: %s\n", pSource_filename); | |
| return false; | |
| } | |
| } | |
| const uint32_t width = m_params.m_hdr ? file_image_hdr.get_width() : file_image.get_width(); | |
| const uint32_t height = m_params.m_hdr ? file_image_hdr.get_height() : file_image.get_height(); | |
| if (m_params.m_status_output) | |
| { | |
| printf("Read source image \"%s\", %ux%u\n", pSource_filename, width, height); | |
| } | |
| if (m_params.m_hdr) | |
| { | |
| clean_hdr_image(file_image_hdr); | |
| } | |
| else | |
| { | |
| // Optionally load another image and put a grayscale version of it into the alpha channel. | |
| if ((source_file_index < m_params.m_source_alpha_filenames.size()) && (m_params.m_source_alpha_filenames[source_file_index].size())) | |
| { | |
| const char* pSource_alpha_image = m_params.m_source_alpha_filenames[source_file_index].c_str(); | |
| image alpha_data; | |
| if (!load_image(pSource_alpha_image, alpha_data)) | |
| { | |
| error_printf("Failed reading source image: %s\n", pSource_alpha_image); | |
| return false; | |
| } | |
| if (m_params.m_status_output) | |
| printf("Read source alpha image \"%s\", %ux%u\n", pSource_alpha_image, alpha_data.get_width(), alpha_data.get_height()); | |
| alpha_data.crop(width, height); | |
| for (uint32_t y = 0; y < height; y++) | |
| for (uint32_t x = 0; x < width; x++) | |
| file_image(x, y).a = (uint8_t)alpha_data(x, y).get_709_luma(); | |
| } | |
| } | |
| } | |
| else | |
| { | |
| if (m_params.m_hdr) | |
| { | |
| file_image_hdr = m_params.m_source_images_hdr[source_file_index]; | |
| clean_hdr_image(file_image_hdr); | |
| } | |
| else | |
| { | |
| file_image = m_params.m_source_images[source_file_index]; | |
| } | |
| } | |
| if (!m_params.m_hdr) | |
| { | |
| if (m_params.m_renormalize) | |
| file_image.renormalize_normal_map(); | |
| } | |
| bool alpha_swizzled = false; | |
| if (m_params.m_swizzle[0] != 0 || | |
| m_params.m_swizzle[1] != 1 || | |
| m_params.m_swizzle[2] != 2 || | |
| m_params.m_swizzle[3] != 3) | |
| { | |
| if (!m_params.m_hdr) | |
| { | |
| // Used for XY normal maps in RG - puts X in color, Y in alpha | |
| for (uint32_t y = 0; y < file_image.get_height(); y++) | |
| { | |
| for (uint32_t x = 0; x < file_image.get_width(); x++) | |
| { | |
| const color_rgba& c = file_image(x, y); | |
| 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]]); | |
| } | |
| } | |
| alpha_swizzled = (m_params.m_swizzle[3] != 3); | |
| } | |
| else | |
| { | |
| // Used for XY normal maps in RG - puts X in color, Y in alpha | |
| for (uint32_t y = 0; y < file_image_hdr.get_height(); y++) | |
| { | |
| for (uint32_t x = 0; x < file_image_hdr.get_width(); x++) | |
| { | |
| const vec4F& c = file_image_hdr(x, y); | |
| // For now, alpha is always 1.0f in UASTC HDR. | |
| 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]]); | |
| } | |
| } | |
| } | |
| } | |
| bool has_alpha = false; | |
| if (!m_params.m_hdr) | |
| { | |
| if (m_params.m_force_alpha || alpha_swizzled) | |
| has_alpha = true; | |
| else if (!m_params.m_check_for_alpha) | |
| file_image.set_alpha(255); | |
| else if (file_image.has_alpha()) | |
| has_alpha = true; | |
| if (has_alpha) | |
| m_any_source_image_has_alpha = true; | |
| } | |
| { | |
| const uint32_t width = m_params.m_hdr ? file_image_hdr.get_width() : file_image.get_width(); | |
| const uint32_t height = m_params.m_hdr ? file_image_hdr.get_height() : file_image.get_height(); | |
| debug_printf("Source image index %u filename %s %ux%u has alpha: %u\n", source_file_index, pSource_filename, width, height, has_alpha); | |
| } | |
| if (m_params.m_y_flip) | |
| { | |
| if (m_params.m_hdr) | |
| file_image_hdr.flip_y(); | |
| else | |
| file_image.flip_y(); | |
| } | |
| #if DEBUG_CROP_TEXTURE_TO_64x64 | |
| if (m_params.m_hdr) | |
| file_image_hdr.resize(64, 64); | |
| else | |
| file_image.resize(64, 64); | |
| #endif | |
| if ((m_params.m_resample_width > 0) && (m_params.m_resample_height > 0)) | |
| { | |
| int new_width = basisu::minimum<int>(m_params.m_resample_width, basist::BASISU_MAX_SUPPORTED_TEXTURE_DIMENSION); | |
| int new_height = basisu::minimum<int>(m_params.m_resample_height, basist::BASISU_MAX_SUPPORTED_TEXTURE_DIMENSION); | |
| debug_printf("Resampling to %ix%i\n", new_width, new_height); | |
| // TODO: A box filter - kaiser looks too sharp on video. Let the caller control this. | |
| if (m_params.m_hdr) | |
| { | |
| imagef temp_img(new_width, new_height); | |
| image_resample(file_image_hdr, temp_img, "box"); // "kaiser"); | |
| clean_hdr_image(temp_img); | |
| temp_img.swap(file_image_hdr); | |
| } | |
| else | |
| { | |
| image temp_img(new_width, new_height); | |
| image_resample(file_image, temp_img, m_params.m_perceptual, "box"); // "kaiser"); | |
| temp_img.swap(file_image); | |
| } | |
| } | |
| else if (m_params.m_resample_factor > 0.0f) | |
| { | |
| // TODO: A box filter - kaiser looks too sharp on video. Let the caller control this. | |
| if (m_params.m_hdr) | |
| { | |
| int new_width = basisu::minimum<int>(basisu::maximum(1, (int)ceilf(file_image_hdr.get_width() * m_params.m_resample_factor)), basist::BASISU_MAX_SUPPORTED_TEXTURE_DIMENSION); | |
| int new_height = basisu::minimum<int>(basisu::maximum(1, (int)ceilf(file_image_hdr.get_height() * m_params.m_resample_factor)), basist::BASISU_MAX_SUPPORTED_TEXTURE_DIMENSION); | |
| debug_printf("Resampling to %ix%i\n", new_width, new_height); | |
| imagef temp_img(new_width, new_height); | |
| image_resample(file_image_hdr, temp_img, "box"); // "kaiser"); | |
| clean_hdr_image(temp_img); | |
| temp_img.swap(file_image_hdr); | |
| } | |
| else | |
| { | |
| int new_width = basisu::minimum<int>(basisu::maximum(1, (int)ceilf(file_image.get_width() * m_params.m_resample_factor)), basist::BASISU_MAX_SUPPORTED_TEXTURE_DIMENSION); | |
| int new_height = basisu::minimum<int>(basisu::maximum(1, (int)ceilf(file_image.get_height() * m_params.m_resample_factor)), basist::BASISU_MAX_SUPPORTED_TEXTURE_DIMENSION); | |
| debug_printf("Resampling to %ix%i\n", new_width, new_height); | |
| image temp_img(new_width, new_height); | |
| image_resample(file_image, temp_img, m_params.m_perceptual, "box"); // "kaiser"); | |
| temp_img.swap(file_image); | |
| } | |
| } | |
| const uint32_t width = m_params.m_hdr ? file_image_hdr.get_width() : file_image.get_width(); | |
| const uint32_t height = m_params.m_hdr ? file_image_hdr.get_height() : file_image.get_height(); | |
| if ((!width) || (!height)) | |
| { | |
| error_printf("basis_compressor::read_source_images: Source image has a zero width and/or height!\n"); | |
| return false; | |
| } | |
| if ((width > basist::BASISU_MAX_SUPPORTED_TEXTURE_DIMENSION) || (height > basist::BASISU_MAX_SUPPORTED_TEXTURE_DIMENSION)) | |
| { | |
| error_printf("basis_compressor::read_source_images: Source image \"%s\" is too large!\n", pSource_filename); | |
| return false; | |
| } | |
| if (!m_params.m_hdr) | |
| source_images.enlarge(1)->swap(file_image); | |
| else | |
| source_images_hdr.enlarge(1)->swap(file_image_hdr); | |
| source_filenames.push_back(pSource_filename); | |
| } | |
| // Check if the caller has generated their own mipmaps. | |
| if (m_params.m_hdr) | |
| { | |
| if (m_params.m_source_mipmap_images_hdr.size()) | |
| { | |
| // Make sure they've passed us enough mipmap chains. | |
| 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())) | |
| { | |
| 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"); | |
| return false; | |
| } | |
| } | |
| } | |
| else | |
| { | |
| if (m_params.m_source_mipmap_images.size()) | |
| { | |
| // Make sure they've passed us enough mipmap chains. | |
| if ((m_params.m_source_images.size() != m_params.m_source_mipmap_images.size()) || (total_source_files != m_params.m_source_images.size())) | |
| { | |
| error_printf("basis_compressor::read_source_images(): m_params.m_source_mipmap_images.size() must equal m_params.m_source_images.size()!\n"); | |
| return false; | |
| } | |
| // Check if any of the user-supplied mipmap levels has alpha. | |
| if (!m_any_source_image_has_alpha) | |
| { | |
| for (uint32_t source_file_index = 0; source_file_index < total_source_files; source_file_index++) | |
| { | |
| for (uint32_t mip_index = 0; mip_index < m_params.m_source_mipmap_images[source_file_index].size(); mip_index++) | |
| { | |
| const image& mip_img = m_params.m_source_mipmap_images[source_file_index][mip_index]; | |
| // Be sure to take into account any swizzling which will be applied. | |
| if (mip_img.has_alpha(m_params.m_swizzle[3])) | |
| { | |
| m_any_source_image_has_alpha = true; | |
| break; | |
| } | |
| } | |
| if (m_any_source_image_has_alpha) | |
| break; | |
| } | |
| } | |
| } | |
| } | |
| debug_printf("Any source image has alpha: %u\n", m_any_source_image_has_alpha); | |
| // Now, for each source image, create the slices corresponding to that image. | |
| for (uint32_t source_file_index = 0; source_file_index < total_source_files; source_file_index++) | |
| { | |
| const std::string &source_filename = source_filenames[source_file_index]; | |
| basisu::vector<image> slices; | |
| basisu::vector<imagef> slices_hdr; | |
| slices.reserve(32); | |
| slices_hdr.reserve(32); | |
| // The first (largest) mipmap level. | |
| image *pFile_image = source_images.size() ? &source_images[source_file_index] : nullptr; | |
| imagef *pFile_image_hdr = source_images_hdr.size() ? &source_images_hdr[source_file_index] : nullptr; | |
| // Reserve a slot for mip0. | |
| if (m_params.m_hdr) | |
| slices_hdr.resize(1); | |
| else | |
| slices.resize(1); | |
| if ((!m_params.m_hdr) && (m_params.m_source_mipmap_images.size())) | |
| { | |
| // User-provided mipmaps for each layer or image in the texture array. | |
| for (uint32_t mip_index = 0; mip_index < m_params.m_source_mipmap_images[source_file_index].size(); mip_index++) | |
| { | |
| image& mip_img = m_params.m_source_mipmap_images[source_file_index][mip_index]; | |
| if ((m_params.m_swizzle[0] != 0) || | |
| (m_params.m_swizzle[1] != 1) || | |
| (m_params.m_swizzle[2] != 2) || | |
| (m_params.m_swizzle[3] != 3)) | |
| { | |
| // Used for XY normal maps in RG - puts X in color, Y in alpha | |
| for (uint32_t y = 0; y < mip_img.get_height(); y++) | |
| { | |
| for (uint32_t x = 0; x < mip_img.get_width(); x++) | |
| { | |
| const color_rgba& c = mip_img(x, y); | |
| 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]]); | |
| } | |
| } | |
| } | |
| slices.push_back(mip_img); | |
| } | |
| } | |
| else if ((m_params.m_hdr) && (m_params.m_source_mipmap_images_hdr.size())) | |
| { | |
| // User-provided mipmaps for each layer or image in the texture array. | |
| for (uint32_t mip_index = 0; mip_index < m_params.m_source_mipmap_images_hdr[source_file_index].size(); mip_index++) | |
| { | |
| imagef& mip_img = m_params.m_source_mipmap_images_hdr[source_file_index][mip_index]; | |
| if ((m_params.m_swizzle[0] != 0) || | |
| (m_params.m_swizzle[1] != 1) || | |
| (m_params.m_swizzle[2] != 2) || | |
| (m_params.m_swizzle[3] != 3)) | |
| { | |
| // Used for XY normal maps in RG - puts X in color, Y in alpha | |
| for (uint32_t y = 0; y < mip_img.get_height(); y++) | |
| { | |
| for (uint32_t x = 0; x < mip_img.get_width(); x++) | |
| { | |
| const vec4F& c = mip_img(x, y); | |
| // For now, HDR alpha is always 1.0f. | |
| 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]]); | |
| } | |
| } | |
| } | |
| clean_hdr_image(mip_img); | |
| slices_hdr.push_back(mip_img); | |
| } | |
| } | |
| else if (m_params.m_mip_gen) | |
| { | |
| // Automatically generate mipmaps. | |
| if (m_params.m_hdr) | |
| { | |
| if (!generate_mipmaps(*pFile_image_hdr, slices_hdr, m_any_source_image_has_alpha)) | |
| return false; | |
| } | |
| else | |
| { | |
| if (!generate_mipmaps(*pFile_image, slices, m_any_source_image_has_alpha)) | |
| return false; | |
| } | |
| } | |
| // Swap in the largest mipmap level here to avoid copying it, because generate_mips() will change the array. | |
| // NOTE: file_image is now blank. | |
| if (m_params.m_hdr) | |
| slices_hdr[0].swap(*pFile_image_hdr); | |
| else | |
| slices[0].swap(*pFile_image); | |
| uint_vec mip_indices(m_params.m_hdr ? slices_hdr.size() : slices.size()); | |
| for (uint32_t i = 0; i < (m_params.m_hdr ? slices_hdr.size() : slices.size()); i++) | |
| mip_indices[i] = i; | |
| if ((!m_params.m_hdr) && (m_any_source_image_has_alpha) && (!m_params.m_uastc)) | |
| { | |
| // For ETC1S, if source has alpha, then even mips will have RGB, and odd mips will have alpha in RGB. | |
| basisu::vector<image> alpha_slices; | |
| uint_vec new_mip_indices; | |
| alpha_slices.reserve(slices.size() * 2); | |
| for (uint32_t i = 0; i < slices.size(); i++) | |
| { | |
| image lvl_rgb(slices[i]); | |
| image lvl_a(lvl_rgb); | |
| for (uint32_t y = 0; y < lvl_a.get_height(); y++) | |
| { | |
| for (uint32_t x = 0; x < lvl_a.get_width(); x++) | |
| { | |
| uint8_t a = lvl_a(x, y).a; | |
| lvl_a(x, y).set_noclamp_rgba(a, a, a, 255); | |
| } | |
| } | |
| lvl_rgb.set_alpha(255); | |
| alpha_slices.push_back(lvl_rgb); | |
| new_mip_indices.push_back(i); | |
| alpha_slices.push_back(lvl_a); | |
| new_mip_indices.push_back(i); | |
| } | |
| slices.swap(alpha_slices); | |
| mip_indices.swap(new_mip_indices); | |
| } | |
| if (m_params.m_hdr) | |
| { | |
| assert(slices_hdr.size() == mip_indices.size()); | |
| } | |
| else | |
| { | |
| assert(slices.size() == mip_indices.size()); | |
| } | |
| for (uint32_t slice_index = 0; slice_index < (m_params.m_hdr ? slices_hdr.size() : slices.size()); slice_index++) | |
| { | |
| image *pSlice_image = m_params.m_hdr ? nullptr : &slices[slice_index]; | |
| imagef *pSlice_image_hdr = m_params.m_hdr ? &slices_hdr[slice_index] : nullptr; | |
| const uint32_t orig_width = m_params.m_hdr ? pSlice_image_hdr->get_width() : pSlice_image->get_width(); | |
| const uint32_t orig_height = m_params.m_hdr ? pSlice_image_hdr->get_height() : pSlice_image->get_height(); | |
| bool is_alpha_slice = false; | |
| if ((!m_params.m_hdr) && (m_any_source_image_has_alpha)) | |
| { | |
| if (m_params.m_uastc) | |
| { | |
| is_alpha_slice = pSlice_image->has_alpha(); | |
| } | |
| else | |
| { | |
| is_alpha_slice = (slice_index & 1) != 0; | |
| } | |
| } | |
| // Enlarge the source image to block boundaries, duplicating edge pixels if necessary to avoid introducing extra colors into blocks. | |
| if (m_params.m_hdr) | |
| { | |
| // Don't pad in 6x6 mode, the lower level compressor handles it. | |
| if (m_params.m_hdr_mode == hdr_modes::cUASTC_HDR_4X4) | |
| { | |
| 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()); | |
| } | |
| } | |
| else | |
| { | |
| 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()); | |
| } | |
| if (m_params.m_debug_images) | |
| { | |
| if (m_params.m_hdr) | |
| write_exr(string_format("basis_debug_source_image_%u_slice_%u.exr", source_file_index, slice_index).c_str(), *pSlice_image_hdr, 3, 0); | |
| else | |
| save_png(string_format("basis_debug_source_image_%u_slice_%u.png", source_file_index, slice_index).c_str(), *pSlice_image); | |
| } | |
| const size_t dest_image_index = (m_params.m_hdr ? m_slice_images_hdr.size() : m_slice_images.size()); | |
| enlarge_vector(m_stats, 1); | |
| if (m_params.m_hdr) | |
| enlarge_vector(m_slice_images_hdr, 1); | |
| else | |
| enlarge_vector(m_slice_images, 1); | |
| enlarge_vector(m_slice_descs, 1); | |
| m_stats[dest_image_index].m_filename = source_filename.c_str(); | |
| m_stats[dest_image_index].m_width = orig_width; | |
| m_stats[dest_image_index].m_height = orig_height; | |
| debug_printf("****** Slice %u: mip %u, alpha_slice: %u, filename: \"%s\", original: %ux%u actual: %ux%u\n", | |
| m_slice_descs.size() - 1, mip_indices[slice_index], is_alpha_slice, source_filename.c_str(), | |
| orig_width, orig_height, | |
| m_params.m_hdr ? pSlice_image_hdr->get_width() : pSlice_image->get_width(), | |
| m_params.m_hdr ? pSlice_image_hdr->get_height() : pSlice_image->get_height()); | |
| basisu_backend_slice_desc& slice_desc = m_slice_descs[dest_image_index]; | |
| slice_desc.m_first_block_index = m_total_blocks; | |
| slice_desc.m_orig_width = orig_width; | |
| slice_desc.m_orig_height = orig_height; | |
| if (m_params.m_hdr) | |
| { | |
| slice_desc.m_width = pSlice_image_hdr->get_width(); | |
| slice_desc.m_height = pSlice_image_hdr->get_height(); | |
| slice_desc.m_num_blocks_x = pSlice_image_hdr->get_block_width(get_block_width()); | |
| slice_desc.m_num_blocks_y = pSlice_image_hdr->get_block_height(get_block_height()); | |
| } | |
| else | |
| { | |
| slice_desc.m_width = pSlice_image->get_width(); | |
| slice_desc.m_height = pSlice_image->get_height(); | |
| slice_desc.m_num_blocks_x = pSlice_image->get_block_width(get_block_width()); | |
| slice_desc.m_num_blocks_y = pSlice_image->get_block_height(get_block_height()); | |
| } | |
| slice_desc.m_num_macroblocks_x = (slice_desc.m_num_blocks_x + 1) >> 1; | |
| slice_desc.m_num_macroblocks_y = (slice_desc.m_num_blocks_y + 1) >> 1; | |
| slice_desc.m_source_file_index = source_file_index; | |
| slice_desc.m_mip_index = mip_indices[slice_index]; | |
| slice_desc.m_alpha = is_alpha_slice; | |
| slice_desc.m_iframe = false; | |
| if (m_params.m_tex_type == basist::cBASISTexTypeVideoFrames) | |
| { | |
| if (m_params.m_uastc) | |
| { | |
| // If it's not ETC1S, all slices are currently i-frames. | |
| slice_desc.m_iframe = true; | |
| } | |
| else | |
| { | |
| // ETC1S: only the first frame is currently an iframe. (TODO: We can easily fix this so ETC1S has periodic i-frames.) | |
| slice_desc.m_iframe = (source_file_index == 0); | |
| } | |
| } | |
| m_total_blocks += slice_desc.m_num_blocks_x * slice_desc.m_num_blocks_y; | |
| total_macroblocks += slice_desc.m_num_macroblocks_x * slice_desc.m_num_macroblocks_y; | |
| // Finally, swap in the slice's image to avoid copying it. | |
| // NOTE: slice_image is now blank. | |
| if (m_params.m_hdr) | |
| m_slice_images_hdr[dest_image_index].swap(*pSlice_image_hdr); | |
| else | |
| m_slice_images[dest_image_index].swap(*pSlice_image); | |
| } // slice_index | |
| } // source_file_index | |
| debug_printf("Total blocks: %u, Total macroblocks: %u\n", m_total_blocks, total_macroblocks); | |
| // Make sure we don't have too many slices | |
| if (m_slice_descs.size() > BASISU_MAX_SLICES) | |
| { | |
| error_printf("Too many slices!\n"); | |
| return false; | |
| } | |
| // Basic sanity check on the slices | |
| for (uint32_t i = 1; i < m_slice_descs.size(); i++) | |
| { | |
| const basisu_backend_slice_desc &prev_slice_desc = m_slice_descs[i - 1]; | |
| const basisu_backend_slice_desc &slice_desc = m_slice_descs[i]; | |
| // Make sure images are in order | |
| int image_delta = (int)slice_desc.m_source_file_index - (int)prev_slice_desc.m_source_file_index; | |
| if (image_delta > 1) | |
| return false; | |
| // Make sure mipmap levels are in order | |
| if (!image_delta) | |
| { | |
| int level_delta = (int)slice_desc.m_mip_index - (int)prev_slice_desc.m_mip_index; | |
| if (level_delta > 1) | |
| return false; | |
| } | |
| } | |
| if (m_params.m_status_output) | |
| { | |
| printf("Total slices: %u\n", (uint32_t)m_slice_descs.size()); | |
| } | |
| for (uint32_t i = 0; i < m_slice_descs.size(); i++) | |
| { | |
| const basisu_backend_slice_desc &slice_desc = m_slice_descs[i]; | |
| if (m_params.m_status_output) | |
| { | |
| 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", | |
| i, slice_desc.m_alpha, slice_desc.m_orig_width, slice_desc.m_orig_height, | |
| slice_desc.m_width, slice_desc.m_height, | |
| slice_desc.m_first_block_index, slice_desc.m_source_file_index, slice_desc.m_mip_index, slice_desc.m_iframe); | |
| } | |
| if (m_any_source_image_has_alpha) | |
| { | |
| // HDR doesn't support alpha yet | |
| if (m_params.m_hdr) | |
| return false; | |
| if (!m_params.m_uastc) | |
| { | |
| // For ETC1S, alpha slices must be at odd slice indices. | |
| if (slice_desc.m_alpha) | |
| { | |
| if ((i & 1) == 0) | |
| return false; | |
| const basisu_backend_slice_desc& prev_slice_desc = m_slice_descs[i - 1]; | |
| // Make sure previous slice has this image's color data | |
| if (prev_slice_desc.m_source_file_index != slice_desc.m_source_file_index) | |
| return false; | |
| if (prev_slice_desc.m_alpha) | |
| return false; | |
| if (prev_slice_desc.m_mip_index != slice_desc.m_mip_index) | |
| return false; | |
| if (prev_slice_desc.m_num_blocks_x != slice_desc.m_num_blocks_x) | |
| return false; | |
| if (prev_slice_desc.m_num_blocks_y != slice_desc.m_num_blocks_y) | |
| return false; | |
| } | |
| else if (i & 1) | |
| return false; | |
| } | |
| } | |
| else if (slice_desc.m_alpha) | |
| { | |
| return false; | |
| } | |
| if ((slice_desc.m_orig_width > slice_desc.m_width) || (slice_desc.m_orig_height > slice_desc.m_height)) | |
| return false; | |
| if ((slice_desc.m_source_file_index == 0) && (m_params.m_tex_type == basist::cBASISTexTypeVideoFrames)) | |
| { | |
| if (!slice_desc.m_iframe) | |
| return false; | |
| } | |
| } | |
| return true; | |
| } | |
| // Do some basic validation for 2D arrays, cubemaps, video, and volumes. | |
| bool basis_compressor::validate_texture_type_constraints() | |
| { | |
| debug_printf("basis_compressor::validate_texture_type_constraints\n"); | |
| // In 2D mode anything goes (each image may have a different resolution and # of mipmap levels). | |
| if (m_params.m_tex_type == basist::cBASISTexType2D) | |
| return true; | |
| uint32_t total_basis_images = 0; | |
| for (uint32_t slice_index = 0; slice_index < (m_params.m_hdr ? m_slice_images_hdr.size() : m_slice_images.size()); slice_index++) | |
| { | |
| const basisu_backend_slice_desc &slice_desc = m_slice_descs[slice_index]; | |
| total_basis_images = maximum<uint32_t>(total_basis_images, slice_desc.m_source_file_index + 1); | |
| } | |
| if (m_params.m_tex_type == basist::cBASISTexTypeCubemapArray) | |
| { | |
| // For cubemaps, validate that the total # of Basis images is a multiple of 6. | |
| if ((total_basis_images % 6) != 0) | |
| { | |
| error_printf("basis_compressor::validate_texture_type_constraints: For cubemaps the total number of input images is not a multiple of 6!\n"); | |
| return false; | |
| } | |
| } | |
| // Now validate that all the mip0's have the same dimensions, and that each image has the same # of mipmap levels. | |
| uint_vec image_mipmap_levels(total_basis_images); | |
| int width = -1, height = -1; | |
| for (uint32_t slice_index = 0; slice_index < (m_params.m_hdr ? m_slice_images_hdr.size() : m_slice_images.size()); slice_index++) | |
| { | |
| const basisu_backend_slice_desc &slice_desc = m_slice_descs[slice_index]; | |
| 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); | |
| if (slice_desc.m_mip_index != 0) | |
| continue; | |
| if (width < 0) | |
| { | |
| width = slice_desc.m_orig_width; | |
| height = slice_desc.m_orig_height; | |
| } | |
| else if ((width != (int)slice_desc.m_orig_width) || (height != (int)slice_desc.m_orig_height)) | |
| { | |
| error_printf("basis_compressor::validate_texture_type_constraints: The source image resolutions are not all equal!\n"); | |
| return false; | |
| } | |
| } | |
| for (size_t i = 1; i < image_mipmap_levels.size(); i++) | |
| { | |
| if (image_mipmap_levels[0] != image_mipmap_levels[i]) | |
| { | |
| error_printf("basis_compressor::validate_texture_type_constraints: Each image must have the same number of mipmap levels!\n"); | |
| return false; | |
| } | |
| } | |
| return true; | |
| } | |
| bool basis_compressor::extract_source_blocks() | |
| { | |
| debug_printf("basis_compressor::extract_source_blocks\n"); | |
| // No need to extract blocks in 6x6 mode, but the 4x4 compressors want 4x4 blocks. | |
| if ((m_fmt_mode == basist::basis_tex_format::cASTC_HDR_6x6) || (m_fmt_mode == basist::basis_tex_format::cUASTC_HDR_6x6_INTERMEDIATE)) | |
| return true; | |
| // No need to extract blocks in XUASTC/ASTC LDR/XUBC7 modes either. | |
| if (basis_tex_format_is_xuastc_ldr(m_fmt_mode) || basis_tex_format_is_astc_ldr(m_fmt_mode) || basis_tex_format_is_xubc7(m_fmt_mode)) | |
| return true; | |
| if (m_params.m_hdr) | |
| m_source_blocks_hdr.resize(m_total_blocks); | |
| else | |
| m_source_blocks.resize(m_total_blocks); | |
| for (uint32_t slice_index = 0; slice_index < (m_params.m_hdr ? m_slice_images_hdr.size() : m_slice_images.size()); slice_index++) | |
| { | |
| const basisu_backend_slice_desc& slice_desc = m_slice_descs[slice_index]; | |
| const uint32_t num_blocks_x = slice_desc.m_num_blocks_x; | |
| const uint32_t num_blocks_y = slice_desc.m_num_blocks_y; | |
| const image *pSource_image = m_params.m_hdr ? nullptr : &m_slice_images[slice_index]; | |
| const imagef *pSource_image_hdr = m_params.m_hdr ? &m_slice_images_hdr[slice_index] : nullptr; | |
| for (uint32_t block_y = 0; block_y < num_blocks_y; block_y++) | |
| { | |
| for (uint32_t block_x = 0; block_x < num_blocks_x; block_x++) | |
| { | |
| if (m_params.m_hdr) | |
| { | |
| vec4F* pBlock = m_source_blocks_hdr[slice_desc.m_first_block_index + block_x + block_y * num_blocks_x].get_ptr(); | |
| pSource_image_hdr->extract_block_clamped(pBlock, block_x * 4, block_y * 4, 4, 4); | |
| // Additional (technically optional) early sanity checking of the block texels. | |
| for (uint32_t i = 0; i < 16; i++) | |
| { | |
| for (uint32_t c = 0; c < 3; c++) | |
| { | |
| float v = pBlock[i][c]; | |
| if (std::isnan(v) || std::isinf(v) || (v < 0.0f) || (v > basist::MAX_HALF_FLOAT)) | |
| { | |
| error_printf("basis_compressor::extract_source_blocks: invalid float component\n"); | |
| return false; | |
| } | |
| } | |
| } | |
| } | |
| else | |
| { | |
| 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); | |
| } | |
| } | |
| } | |
| } | |
| return true; | |
| } | |
| bool basis_compressor::process_frontend() | |
| { | |
| debug_printf("basis_compressor::process_frontend\n"); | |
| #if 0 | |
| // TODO | |
| basis_etc1_pack_params pack_params; | |
| pack_params.m_quality = cETCQualityMedium; | |
| pack_params.m_perceptual = m_params.m_perceptual; | |
| pack_params.m_use_color4 = false; | |
| pack_etc1_block_context pack_context; | |
| std::unordered_set<uint64_t> endpoint_hash; | |
| std::unordered_set<uint32_t> selector_hash; | |
| for (uint32_t i = 0; i < m_source_blocks.size(); i++) | |
| { | |
| etc_block blk; | |
| pack_etc1_block(blk, m_source_blocks[i].get_ptr(), pack_params, pack_context); | |
| const color_rgba c0(blk.get_block_color(0, false)); | |
| endpoint_hash.insert((c0.r | (c0.g << 5) | (c0.b << 10)) | (blk.get_inten_table(0) << 16)); | |
| const color_rgba c1(blk.get_block_color(1, false)); | |
| endpoint_hash.insert((c1.r | (c1.g << 5) | (c1.b << 10)) | (blk.get_inten_table(1) << 16)); | |
| selector_hash.insert(blk.get_raw_selector_bits()); | |
| } | |
| const uint32_t total_unique_endpoints = (uint32_t)endpoint_hash.size(); | |
| const uint32_t total_unique_selectors = (uint32_t)selector_hash.size(); | |
| if (m_params.m_debug) | |
| { | |
| debug_printf("Unique endpoints: %u, unique selectors: %u\n", total_unique_endpoints, total_unique_selectors); | |
| } | |
| #endif | |
| const double total_texels = m_total_blocks * 16.0f; | |
| int endpoint_clusters = m_params.m_etc1s_max_endpoint_clusters; | |
| int selector_clusters = m_params.m_etc1s_max_selector_clusters; | |
| if (endpoint_clusters > basisu_frontend::cMaxEndpointClusters) | |
| { | |
| error_printf("Too many endpoint clusters! (%u but max is %u)\n", endpoint_clusters, basisu_frontend::cMaxEndpointClusters); | |
| return false; | |
| } | |
| if (selector_clusters > basisu_frontend::cMaxSelectorClusters) | |
| { | |
| error_printf("Too many selector clusters! (%u but max is %u)\n", selector_clusters, basisu_frontend::cMaxSelectorClusters); | |
| return false; | |
| } | |
| if (m_params.m_quality_level != -1) | |
| { | |
| const float quality = saturate(m_params.m_quality_level / 255.0f); | |
| const float bits_per_endpoint_cluster = 14.0f; | |
| const float max_desired_endpoint_cluster_bits_per_texel = 1.0f; // .15f | |
| int max_endpoints = static_cast<int>((max_desired_endpoint_cluster_bits_per_texel * total_texels) / bits_per_endpoint_cluster); | |
| const float mid = 128.0f / 255.0f; | |
| float color_endpoint_quality = quality; | |
| const float endpoint_split_point = 0.5f; | |
| // In v1.2 and in previous versions, the endpoint codebook size at quality 128 was 3072. This wasn't quite large enough. | |
| const int ENDPOINT_CODEBOOK_MID_QUALITY_CODEBOOK_SIZE = 4800; | |
| const int MAX_ENDPOINT_CODEBOOK_SIZE = 8192; | |
| if (color_endpoint_quality <= mid) | |
| { | |
| color_endpoint_quality = lerp(0.0f, endpoint_split_point, powf(color_endpoint_quality / mid, .65f)); | |
| max_endpoints = clamp<int>(max_endpoints, 256, ENDPOINT_CODEBOOK_MID_QUALITY_CODEBOOK_SIZE); | |
| max_endpoints = minimum<uint32_t>(max_endpoints, m_total_blocks); | |
| if (max_endpoints < 64) | |
| max_endpoints = 64; | |
| endpoint_clusters = clamp<uint32_t>((uint32_t)(.5f + lerp<float>(32, static_cast<float>(max_endpoints), color_endpoint_quality)), 32, basisu_frontend::cMaxEndpointClusters); | |
| } | |
| else | |
| { | |
| color_endpoint_quality = powf((color_endpoint_quality - mid) / (1.0f - mid), 1.6f); | |
| max_endpoints = clamp<int>(max_endpoints, 256, MAX_ENDPOINT_CODEBOOK_SIZE); | |
| max_endpoints = minimum<uint32_t>(max_endpoints, m_total_blocks); | |
| if (max_endpoints < ENDPOINT_CODEBOOK_MID_QUALITY_CODEBOOK_SIZE) | |
| max_endpoints = ENDPOINT_CODEBOOK_MID_QUALITY_CODEBOOK_SIZE; | |
| 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); | |
| } | |
| float bits_per_selector_cluster = 14.0f; | |
| const float max_desired_selector_cluster_bits_per_texel = 1.0f; // .15f | |
| int max_selectors = static_cast<int>((max_desired_selector_cluster_bits_per_texel * total_texels) / bits_per_selector_cluster); | |
| max_selectors = clamp<int>(max_selectors, 256, basisu_frontend::cMaxSelectorClusters); | |
| max_selectors = minimum<uint32_t>(max_selectors, m_total_blocks); | |
| float color_selector_quality = quality; | |
| //color_selector_quality = powf(color_selector_quality, 1.65f); | |
| color_selector_quality = powf(color_selector_quality, 2.62f); | |
| if (max_selectors < 96) | |
| max_selectors = 96; | |
| selector_clusters = clamp<uint32_t>((uint32_t)(.5f + lerp<float>(96, static_cast<float>(max_selectors), color_selector_quality)), 8, basisu_frontend::cMaxSelectorClusters); | |
| debug_printf("Max endpoints: %u, max selectors: %u\n", endpoint_clusters, selector_clusters); | |
| if (m_params.m_quality_level >= 223) | |
| { | |
| if (!m_params.m_selector_rdo_thresh.was_changed()) | |
| { | |
| if (!m_params.m_endpoint_rdo_thresh.was_changed()) | |
| m_params.m_endpoint_rdo_thresh *= .25f; | |
| if (!m_params.m_selector_rdo_thresh.was_changed()) | |
| m_params.m_selector_rdo_thresh *= .25f; | |
| } | |
| } | |
| else if (m_params.m_quality_level >= 192) | |
| { | |
| if (!m_params.m_endpoint_rdo_thresh.was_changed()) | |
| m_params.m_endpoint_rdo_thresh *= .5f; | |
| if (!m_params.m_selector_rdo_thresh.was_changed()) | |
| m_params.m_selector_rdo_thresh *= .5f; | |
| } | |
| else if (m_params.m_quality_level >= 160) | |
| { | |
| if (!m_params.m_endpoint_rdo_thresh.was_changed()) | |
| m_params.m_endpoint_rdo_thresh *= .75f; | |
| if (!m_params.m_selector_rdo_thresh.was_changed()) | |
| m_params.m_selector_rdo_thresh *= .75f; | |
| } | |
| else if (m_params.m_quality_level >= 129) | |
| { | |
| float l = (quality - 129 / 255.0f) / ((160 - 129) / 255.0f); | |
| if (!m_params.m_endpoint_rdo_thresh.was_changed()) | |
| m_params.m_endpoint_rdo_thresh *= lerp<float>(1.0f, .75f, l); | |
| if (!m_params.m_selector_rdo_thresh.was_changed()) | |
| m_params.m_selector_rdo_thresh *= lerp<float>(1.0f, .75f, l); | |
| } | |
| } | |
| basisu_frontend::params p; | |
| p.m_num_source_blocks = m_total_blocks; | |
| p.m_pSource_blocks = &m_source_blocks[0]; | |
| p.m_max_endpoint_clusters = endpoint_clusters; | |
| p.m_max_selector_clusters = selector_clusters; | |
| p.m_perceptual = m_params.m_perceptual; | |
| p.m_debug_stats = m_params.m_debug; | |
| p.m_debug_images = m_params.m_debug_images; | |
| p.m_compression_level = m_params.m_etc1s_compression_level; | |
| p.m_tex_type = m_params.m_tex_type; | |
| p.m_multithreaded = m_params.m_multithreading; | |
| p.m_disable_hierarchical_endpoint_codebooks = m_params.m_disable_hierarchical_endpoint_codebooks; | |
| p.m_validate = m_params.m_validate_etc1s; | |
| p.m_pJob_pool = m_params.m_pJob_pool; | |
| p.m_pGlobal_codebooks = m_params.m_pGlobal_codebooks; | |
| // Don't keep trying to use OpenCL if it ever fails. | |
| p.m_pOpenCL_context = !m_opencl_failed ? m_pOpenCL_context : nullptr; | |
| if (!m_frontend.init(p)) | |
| { | |
| error_printf("basisu_frontend::init() failed!\n"); | |
| return false; | |
| } | |
| m_frontend.compress(); | |
| if (m_frontend.get_opencl_failed()) | |
| m_opencl_failed = true; | |
| if (m_params.m_debug_images) | |
| { | |
| for (uint32_t i = 0; i < m_slice_descs.size(); i++) | |
| { | |
| char filename[1024]; | |
| #ifdef _WIN32 | |
| sprintf_s(filename, sizeof(filename), "rdo_frontend_output_output_blocks_%u.png", i); | |
| #else | |
| snprintf(filename, sizeof(filename), "rdo_frontend_output_output_blocks_%u.png", i); | |
| #endif | |
| 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); | |
| #ifdef _WIN32 | |
| sprintf_s(filename, sizeof(filename), "rdo_frontend_output_api_%u.png", i); | |
| #else | |
| snprintf(filename, sizeof(filename), "rdo_frontend_output_api_%u.png", i); | |
| #endif | |
| 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); | |
| } | |
| } | |
| return true; | |
| } | |
| bool basis_compressor::extract_frontend_texture_data() | |
| { | |
| if (!m_params.m_compute_stats) | |
| return true; | |
| debug_printf("basis_compressor::extract_frontend_texture_data\n"); | |
| m_frontend_output_textures.resize(m_slice_descs.size()); | |
| m_best_etc1s_images.resize(m_slice_descs.size()); | |
| m_best_etc1s_images_unpacked.resize(m_slice_descs.size()); | |
| for (uint32_t i = 0; i < m_slice_descs.size(); i++) | |
| { | |
| if (m_params.m_status_output) | |
| fmt_printf("Processing slice {}\n", i); | |
| const basisu_backend_slice_desc &slice_desc = m_slice_descs[i]; | |
| const uint32_t num_blocks_x = slice_desc.m_num_blocks_x; | |
| const uint32_t num_blocks_y = slice_desc.m_num_blocks_y; | |
| const uint32_t width = num_blocks_x * 4; | |
| const uint32_t height = num_blocks_y * 4; | |
| m_frontend_output_textures[i].init(texture_format::cETC1, width, height); | |
| for (uint32_t block_y = 0; block_y < num_blocks_y; block_y++) | |
| for (uint32_t block_x = 0; block_x < num_blocks_x; block_x++) | |
| 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)); | |
| #if 0 | |
| if (m_params.m_debug_images) | |
| { | |
| char filename[1024]; | |
| sprintf_s(filename, sizeof(filename), "rdo_etc_frontend_%u_", i); | |
| write_etc1_vis_images(m_frontend_output_textures[i], filename); | |
| } | |
| #endif | |
| m_best_etc1s_images[i].init(texture_format::cETC1, width, height); | |
| for (uint32_t block_y = 0; block_y < num_blocks_y; block_y++) | |
| for (uint32_t block_x = 0; block_x < num_blocks_x; block_x++) | |
| 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)); | |
| m_best_etc1s_images[i].unpack(m_best_etc1s_images_unpacked[i], false); | |
| } | |
| return true; | |
| } | |
| bool basis_compressor::process_backend() | |
| { | |
| debug_printf("basis_compressor::process_backend\n"); | |
| basisu_backend_params backend_params; | |
| backend_params.m_debug = m_params.m_debug; | |
| backend_params.m_debug_images = m_params.m_debug_images; | |
| backend_params.m_etc1s = true; | |
| backend_params.m_compression_level = m_params.m_etc1s_compression_level; | |
| if (!m_params.m_no_endpoint_rdo) | |
| backend_params.m_endpoint_rdo_quality_thresh = m_params.m_endpoint_rdo_thresh; | |
| if (!m_params.m_no_selector_rdo) | |
| backend_params.m_selector_rdo_quality_thresh = m_params.m_selector_rdo_thresh; | |
| backend_params.m_used_global_codebooks = m_frontend.get_params().m_pGlobal_codebooks != nullptr; | |
| backend_params.m_validate = m_params.m_validate_output_data; | |
| m_backend.init(&m_frontend, backend_params, m_slice_descs); | |
| uint32_t total_packed_bytes = m_backend.encode(); | |
| if (!total_packed_bytes) | |
| { | |
| error_printf("basis_compressor::encode() failed!\n"); | |
| return false; | |
| } | |
| debug_printf("Total packed bytes (estimated): %u\n", total_packed_bytes); | |
| return true; | |
| } | |
| bool basis_compressor::create_basis_file_and_transcode() | |
| { | |
| debug_printf("basis_compressor::create_basis_file_and_transcode\n"); | |
| basist::key_value_vec key_values(m_params.m_key_values); | |
| if (find_key_value(key_values, BASISU_LIB_VERSION_KEY_NAME) != nullptr) | |
| { | |
| error_printf("basis_compressor::create_basis_file_and_transcode: Key already exists\n"); | |
| return false; | |
| } | |
| basist::add_key_value(key_values, BASISU_LIB_VERSION_KEY_NAME, fmt_string("{}", BASISU_LIB_VERSION_STRING)); | |
| if (m_params.m_hdr) | |
| { | |
| if (m_upconverted_any_ldr_images) | |
| { | |
| if (find_key_value(key_values, BASISU_LDR_UPCONVERSION_SCALE_KEY_NAME) == nullptr) | |
| basist::add_key_value(key_values, BASISU_LDR_UPCONVERSION_SCALE_KEY_NAME, fmt_string("{g}", m_ldr_to_hdr_upconversion_nit_multiplier)); | |
| if (m_params.m_ldr_hdr_upconversion_srgb_to_linear) | |
| { | |
| if (find_key_value(key_values, BASISU_LDR_UPCONVERSION_SRGB_TO_LIN_KEY_NAME) == nullptr) | |
| basist::add_key_value(key_values, BASISU_LDR_UPCONVERSION_SRGB_TO_LIN_KEY_NAME, "1"); | |
| } | |
| } | |
| if (find_key_value(key_values, BASISU_HDR_MAP_RANGE_KEY_NAME) == nullptr) | |
| { | |
| // add HDR map range key value | |
| basist::basisu_map_range val; | |
| val.m_scale = *reinterpret_cast<const uint32_t*>(&m_hdr_image_scale); | |
| val.m_offset = 0; | |
| auto* pNew_key = key_values.enlarge(1); | |
| const char* pKey_name = BASISU_HDR_MAP_RANGE_KEY_NAME; | |
| size_t key_name_len = strlen(pKey_name) + 1; | |
| pNew_key->m_key.resize(key_name_len); | |
| memcpy(pNew_key->m_key.data(), pKey_name, key_name_len); | |
| pNew_key->m_value.resize(sizeof(val)); | |
| memcpy(pNew_key->m_value.data(), &val, sizeof(val)); | |
| } | |
| } | |
| const basisu_backend_output& encoded_output = m_params.m_uastc ? m_uastc_backend_output : m_backend.get_output(); | |
| 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, key_values)) | |
| { | |
| error_printf("basis_compressor::create_basis_file_and_transcode: basisu_backend:init() failed!\n"); | |
| return false; | |
| } | |
| const uint8_vec& comp_data = m_basis_file.get_compressed_data(); | |
| m_output_basis_file = comp_data; | |
| uint32_t total_orig_pixels = 0; | |
| for (uint32_t i = 0; i < m_slice_descs.size(); i++) | |
| { | |
| const basisu_backend_slice_desc& slice_desc = m_slice_descs[i]; | |
| total_orig_pixels += slice_desc.m_orig_width * slice_desc.m_orig_height; | |
| } | |
| m_total_slice_orig_texels = total_orig_pixels; | |
| m_basis_file_size = comp_data.size(); | |
| m_basis_bits_per_texel = total_orig_pixels ? (comp_data.size() * 8.0f) / total_orig_pixels : 0; | |
| fmt_debug_printf("Total .basis output file size: {}, {3.3} bits/texel\n", m_basis_file_size, m_basis_bits_per_texel); | |
| // HDR 6x6 TODO | |
| const bool is_hdr_6x6 = m_params.m_hdr && (m_params.m_hdr_mode != hdr_modes::cUASTC_HDR_4X4); | |
| if (m_params.m_validate_output_data) | |
| { | |
| interval_timer tm; | |
| tm.start(); | |
| basist::basisu_transcoder_init(); | |
| debug_printf("basist::basisu_transcoder_init: Took %f ms\n", tm.get_elapsed_ms()); | |
| // Verify the compressed data by transcoding it to ASTC (or ETC1)/BC7 and validating the CRC's. | |
| basist::basisu_transcoder decoder; | |
| if (!decoder.validate_file_checksums(&comp_data[0], (uint32_t)comp_data.size(), true)) | |
| { | |
| error_printf("decoder.validate_file_checksums() failed!\n"); | |
| return false; | |
| } | |
| m_decoded_output_textures.resize(m_slice_descs.size()); | |
| if (m_params.m_hdr) | |
| { | |
| m_decoded_output_textures_bc6h_hdr_unpacked.resize(m_slice_descs.size()); | |
| m_decoded_output_textures_astc_hdr.resize(m_slice_descs.size()); | |
| m_decoded_output_textures_astc_hdr_unpacked.resize(m_slice_descs.size()); | |
| } | |
| else | |
| { | |
| m_decoded_output_textures_unpacked.resize(m_slice_descs.size()); | |
| m_decoded_output_textures_bc7.resize(m_slice_descs.size()); | |
| m_decoded_output_textures_unpacked_bc7.resize(m_slice_descs.size()); | |
| } | |
| tm.start(); | |
| if (m_params.m_pGlobal_codebooks) | |
| { | |
| decoder.set_global_codebooks(m_params.m_pGlobal_codebooks); | |
| } | |
| if (!decoder.start_transcoding(&comp_data[0], (uint32_t)comp_data.size())) | |
| { | |
| error_printf("decoder.start_transcoding() failed!\n"); | |
| return false; | |
| } | |
| double start_transcoding_time = tm.get_elapsed_secs(); | |
| debug_printf("basisu_compressor::start_transcoding() took %3.3fms\n", start_transcoding_time * 1000.0f); | |
| double total_time_etc1s_or_astc = 0; | |
| // Select formats to transcode to | |
| basisu::texture_format tex_format; | |
| basist::block_format blk_format; | |
| // Chose first transcode format | |
| if (m_params.m_hdr) | |
| { | |
| // HDR | |
| tex_format = texture_format::cBC6HUnsigned; | |
| blk_format = basist::block_format::cBC6H; | |
| } | |
| else if (m_fmt_mode == basist::basis_tex_format::cUASTC_LDR_4x4) | |
| { | |
| // UASTC LDR 4x4 | |
| tex_format = texture_format::cUASTC4x4; | |
| blk_format = basist::block_format::cUASTC_4x4; | |
| } | |
| else if (basis_tex_format_is_xuastc_ldr(m_fmt_mode) || basis_tex_format_is_astc_ldr(m_fmt_mode)) | |
| { | |
| // XUASTC LDR 4x4-12x12 or ASTC LDR 4x4-12x12 | |
| basist::transcoder_texture_format transcoder_fmt = basist::basis_get_transcoder_texture_format_from_xuastc_or_astc_ldr_basis_tex_format(m_fmt_mode); | |
| tex_format = basist::basis_get_texture_format_from_xuastc_or_astc_ldr_basis_tex_format(m_fmt_mode); | |
| blk_format = basist::xuastc_get_block_format(transcoder_fmt); | |
| } | |
| else if (basis_tex_format_is_xubc7(m_fmt_mode)) | |
| { | |
| // Choose ASTC - we'll also transcode to BC7 too as the alternate. | |
| tex_format = texture_format::cASTC_LDR_4x4; | |
| blk_format = basist::block_format::cASTC_LDR_4x4; | |
| } | |
| else | |
| { | |
| // ETC1S | |
| tex_format = texture_format::cETC1; | |
| blk_format = basist::block_format::cETC1; | |
| } | |
| if (m_params.m_print_stats) | |
| fmt_printf("Transcoding to texture format: {}\n", get_texture_format_name(tex_format)); | |
| for (uint32_t slice_iter = 0; slice_iter < m_slice_descs.size(); slice_iter++) | |
| { | |
| gpu_image decoded_texture; | |
| decoded_texture.init( | |
| tex_format, | |
| m_slice_descs[slice_iter].m_orig_width, m_slice_descs[slice_iter].m_orig_height); | |
| const uint32_t dst_block_size_x = basisu::get_block_width(tex_format); | |
| const uint32_t dst_block_size_y = basisu::get_block_height(tex_format); | |
| const uint32_t num_dst_blocks_x = (m_slice_descs[slice_iter].m_orig_width + dst_block_size_x - 1) / dst_block_size_x; | |
| const uint32_t num_dst_blocks_y = (m_slice_descs[slice_iter].m_orig_height + dst_block_size_y - 1) / dst_block_size_y; | |
| const uint32_t total_dst_blocks = num_dst_blocks_x * num_dst_blocks_y; | |
| const uint32_t bytes_per_block = decoded_texture.get_bytes_per_block(); | |
| tm.start(); | |
| if (!decoder.transcode_slice(&comp_data[0], (uint32_t)comp_data.size(), slice_iter, | |
| reinterpret_cast<etc_block*>(decoded_texture.get_ptr()), total_dst_blocks, blk_format, bytes_per_block, m_params.m_transcode_flags)) | |
| { | |
| error_printf("Transcoding failed on slice %u!\n", slice_iter); | |
| return false; | |
| } | |
| total_time_etc1s_or_astc += tm.get_elapsed_secs(); | |
| if (encoded_output.m_tex_format == basist::basis_tex_format::cETC1S) | |
| { | |
| uint32_t image_crc16 = basist::crc16(decoded_texture.get_ptr(), decoded_texture.get_size_in_bytes(), 0); | |
| if (image_crc16 != encoded_output.m_slice_image_crcs[slice_iter]) | |
| { | |
| error_printf("Decoded image data CRC check failed on slice %u!\n", slice_iter); | |
| return false; | |
| } | |
| debug_printf("Decoded image data CRC check succeeded on slice %i\n", slice_iter); | |
| } | |
| m_decoded_output_textures[slice_iter] = decoded_texture; | |
| } | |
| double total_alt_transcode_time = 0; | |
| tm.start(); | |
| if (m_params.m_hdr) | |
| { | |
| if (is_hdr_6x6) | |
| { | |
| // Transcode to ASTC HDR 6x6 | |
| assert(basist::basis_is_format_supported(basist::transcoder_texture_format::cTFASTC_HDR_6x6_RGBA, basist::basis_tex_format::cASTC_HDR_6x6)); | |
| assert(basist::basis_is_format_supported(basist::transcoder_texture_format::cTFASTC_HDR_6x6_RGBA, basist::basis_tex_format::cUASTC_HDR_6x6_INTERMEDIATE)); | |
| for (uint32_t i = 0; i < m_slice_descs.size(); i++) | |
| { | |
| gpu_image decoded_texture; | |
| decoded_texture.init(texture_format::cASTC_HDR_6x6, m_slice_descs[i].m_orig_width, m_slice_descs[i].m_orig_height); | |
| if (!decoder.transcode_slice(&comp_data[0], (uint32_t)comp_data.size(), i, | |
| 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, m_params.m_transcode_flags)) | |
| { | |
| error_printf("Transcoding failed to ASTC HDR on slice %u!\n", i); | |
| return false; | |
| } | |
| m_decoded_output_textures_astc_hdr[i] = decoded_texture; | |
| } | |
| } | |
| else | |
| { | |
| // Transcode to ASTC HDR 4x4 | |
| assert(basist::basis_is_format_supported(basist::transcoder_texture_format::cTFASTC_HDR_4x4_RGBA, basist::basis_tex_format::cUASTC_HDR_4x4)); | |
| for (uint32_t i = 0; i < m_slice_descs.size(); i++) | |
| { | |
| gpu_image decoded_texture; | |
| decoded_texture.init(texture_format::cASTC_HDR_4x4, m_slice_descs[i].m_orig_width, m_slice_descs[i].m_orig_height); | |
| if (!decoder.transcode_slice(&comp_data[0], (uint32_t)comp_data.size(), i, | |
| 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, m_params.m_transcode_flags)) | |
| { | |
| error_printf("Transcoding failed to ASTC HDR on slice %u!\n", i); | |
| return false; | |
| } | |
| m_decoded_output_textures_astc_hdr[i] = decoded_texture; | |
| } | |
| } | |
| } | |
| else | |
| { | |
| // Transcode to BC7 | |
| if (basist::basis_is_format_supported(basist::transcoder_texture_format::cTFBC7_RGBA, basist::basis_tex_format::cUASTC_LDR_4x4) && | |
| basist::basis_is_format_supported(basist::transcoder_texture_format::cTFBC7_RGBA, basist::basis_tex_format::cETC1S) && | |
| basist::basis_is_format_supported(basist::transcoder_texture_format::cTFBC7_RGBA, basist::basis_tex_format::cXUBC7)) | |
| { | |
| for (uint32_t i = 0; i < m_slice_descs.size(); i++) | |
| { | |
| gpu_image decoded_texture; | |
| decoded_texture.init(texture_format::cBC7, m_slice_descs[i].m_orig_width, m_slice_descs[i].m_orig_height); | |
| const uint32_t num_bc7_blocks_x = decoded_texture.get_blocks_x(); | |
| const uint32_t num_bc7_blocks_y = decoded_texture.get_blocks_y(); | |
| if (!decoder.transcode_slice(&comp_data[0], (uint32_t)comp_data.size(), i, | |
| decoded_texture.get_ptr(), num_bc7_blocks_x * num_bc7_blocks_y, basist::block_format::cBC7, 16, m_params.m_transcode_flags)) | |
| { | |
| error_printf("Transcoding failed to BC7 on slice %u!\n", i); | |
| return false; | |
| } | |
| m_decoded_output_textures_bc7[i] = decoded_texture; | |
| } | |
| } | |
| } | |
| total_alt_transcode_time = tm.get_elapsed_secs(); | |
| // Unpack transcoded GPU textures to raw images | |
| for (uint32_t i = 0; i < m_slice_descs.size(); i++) | |
| { | |
| if (m_params.m_hdr) | |
| { | |
| bool status = m_decoded_output_textures[i].unpack_hdr(m_decoded_output_textures_bc6h_hdr_unpacked[i]); | |
| if (!status) | |
| { | |
| error_printf("Unpacking failed on slice %u!\n", i); | |
| return false; | |
| } | |
| status = m_decoded_output_textures_astc_hdr[i].unpack_hdr(m_decoded_output_textures_astc_hdr_unpacked[i]); | |
| if (!status) | |
| { | |
| error_printf("Unpacking failed on slice %u!\n", i); | |
| return false; | |
| } | |
| } | |
| else | |
| { | |
| bool status = m_decoded_output_textures[i].unpack(m_decoded_output_textures_unpacked[i], m_params.m_ktx2_and_basis_srgb_transfer_function); | |
| if (!status) | |
| { | |
| error_printf("Unpacking failed on slice %u!\n", i); | |
| return false; | |
| } | |
| if (m_decoded_output_textures_bc7[i].get_pixel_width()) | |
| { | |
| status = m_decoded_output_textures_bc7[i].unpack(m_decoded_output_textures_unpacked_bc7[i], m_params.m_ktx2_and_basis_srgb_transfer_function); | |
| if (!status) | |
| { | |
| error_printf("Unpacking failed on slice %u!\n", i); | |
| return false; | |
| } | |
| } | |
| } | |
| } | |
| debug_printf("Transcoded to %s in %3.3fms, %f texels/sec\n", | |
| m_params.m_hdr ? "BC6H" : (m_params.m_uastc ? "ASTC" : "ETC1"), | |
| total_time_etc1s_or_astc * 1000.0f, total_orig_pixels / total_time_etc1s_or_astc); | |
| if (total_alt_transcode_time != 0) | |
| debug_printf("Alternate transcode in %3.3fms, %f texels/sec\n", total_alt_transcode_time * 1000.0f, total_orig_pixels / total_alt_transcode_time); | |
| if (!is_hdr_6x6) | |
| { | |
| // Sanity check decoded output texture sizes | |
| for (uint32_t slice_index = 0; slice_index < m_slice_descs.size(); slice_index++) | |
| { | |
| const basisu_backend_slice_desc& slice_desc = m_slice_descs[slice_index]; | |
| const uint32_t total_blocks = slice_desc.m_num_blocks_x * slice_desc.m_num_blocks_y; | |
| BASISU_NOTE_UNUSED(total_blocks); | |
| assert(m_decoded_output_textures[slice_index].get_total_blocks() == total_blocks); | |
| } | |
| } | |
| } // if (m_params.m_validate_output_data) | |
| return true; | |
| } | |
| bool basis_compressor::write_hdr_debug_images(const char* pBasename, const imagef& orig_hdr_img, uint32_t width, uint32_t height) | |
| { | |
| // Copy image to account for 4x4 block expansion | |
| imagef hdr_img(orig_hdr_img); | |
| hdr_img.resize(width, height); | |
| image srgb_img(width, height); | |
| 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; | |
| for (uint32_t y = 0; y < height; y++) | |
| { | |
| for (uint32_t x = 0; x < width; x++) | |
| { | |
| vec4F p(hdr_img(x, y)); | |
| p[0] = clamp(p[0] * inv_upconversion_scale, 0.0f, 1.0f); | |
| p[1] = clamp(p[1] * inv_upconversion_scale, 0.0f, 1.0f); | |
| p[2] = clamp(p[2] * inv_upconversion_scale, 0.0f, 1.0f); | |
| int rc = (int)std::round(linear_to_srgb(p[0]) * 255.0f); | |
| int gc = (int)std::round(linear_to_srgb(p[1]) * 255.0f); | |
| int bc = (int)std::round(linear_to_srgb(p[2]) * 255.0f); | |
| srgb_img.set_clipped(x, y, color_rgba(rc, gc, bc, 255)); | |
| } | |
| } | |
| { | |
| const std::string filename(string_format("%s_linear_clamped_to_srgb.png", pBasename)); | |
| save_png(filename.c_str(), srgb_img); | |
| printf("Wrote .PNG file %s\n", filename.c_str()); | |
| } | |
| { | |
| const std::string filename(string_format("%s_compressive_tonemapped.png", pBasename)); | |
| image compressive_tonemapped_img; | |
| bool status = tonemap_image_compressive(compressive_tonemapped_img, hdr_img); | |
| if (!status) | |
| { | |
| error_printf("basis_compressor::write_hdr_debug_images: tonemap_image_compressive() failed (invalid half-float input)\n"); | |
| } | |
| else | |
| { | |
| save_png(filename.c_str(), compressive_tonemapped_img); | |
| printf("Wrote .PNG file %s\n", filename.c_str()); | |
| } | |
| } | |
| image tonemapped_img; | |
| for (int e = -5; e <= 5; e++) | |
| { | |
| const float scale = powf(2.0f, (float)e); | |
| tonemap_image_reinhard(tonemapped_img, hdr_img, scale); | |
| std::string filename(string_format("%s_reinhard_tonemapped_scale_%f.png", pBasename, scale)); | |
| save_png(filename.c_str(), tonemapped_img, cImageSaveIgnoreAlpha); | |
| printf("Wrote .PNG file %s\n", filename.c_str()); | |
| } | |
| return true; | |
| } | |
| bool basis_compressor::write_output_files_and_compute_stats() | |
| { | |
| debug_printf("basis_compressor::write_output_files_and_compute_stats\n"); | |
| const uint8_vec& comp_data = m_params.m_create_ktx2_file ? m_output_ktx2_file : m_basis_file.get_compressed_data(); | |
| if (m_params.m_write_output_basis_or_ktx2_files) | |
| { | |
| const std::string& output_filename = m_params.m_out_filename; | |
| if (!write_vec_to_file(output_filename.c_str(), comp_data)) | |
| { | |
| error_printf("Failed writing output data to file \"%s\"\n", output_filename.c_str()); | |
| return false; | |
| } | |
| if (m_params.m_status_output) | |
| { | |
| printf("Wrote compressed output file \"%s\"\n", output_filename.c_str()); | |
| } | |
| } | |
| size_t comp_size = 0; | |
| if ((m_params.m_compute_stats) && (m_params.m_uastc) && (comp_data.size())) | |
| { | |
| void* pComp_data = tdefl_compress_mem_to_heap(&comp_data[0], comp_data.size(), &comp_size, TDEFL_MAX_PROBES_MASK);// TDEFL_DEFAULT_MAX_PROBES); | |
| size_t decomp_size = 0; | |
| void* pDecomp_data = tinfl_decompress_mem_to_heap(pComp_data, comp_size, &decomp_size, 0); | |
| if ((decomp_size != comp_data.size()) || (memcmp(pDecomp_data, &comp_data[0], decomp_size) != 0)) | |
| { | |
| printf("basis_compressor::create_basis_file_and_transcode:: miniz compression or decompression failed!\n"); | |
| return false; | |
| } | |
| mz_free(pComp_data); | |
| mz_free(pDecomp_data); | |
| uint32_t total_texels = 0; | |
| for (uint32_t i = 0; i < m_slice_descs.size(); i++) | |
| total_texels += (m_slice_descs[i].m_orig_width * m_slice_descs[i].m_orig_height); | |
| m_basis_bits_per_texel = ((float)comp_size * 8.0f) / total_texels; | |
| fmt_debug_printf("Output file size: {}, {3.2} bits/texel, LZ compressed file size: {}, {3.2} bits/texel\n", | |
| (uint64_t)comp_data.size(), ((float)comp_data.size() * 8.0f) / total_texels, | |
| (uint64_t)comp_size, m_basis_bits_per_texel); | |
| } | |
| m_stats.resize(m_slice_descs.size()); | |
| if (m_params.m_validate_output_data) | |
| { | |
| if (m_params.m_hdr) | |
| { | |
| if (m_params.m_print_stats) | |
| { | |
| printf("ASTC/BC6H half float space error metrics (a piecewise linear approximation of log2 error):\n"); | |
| } | |
| for (uint32_t slice_index = 0; slice_index < m_slice_descs.size(); slice_index++) | |
| { | |
| const basisu_backend_slice_desc& slice_desc = m_slice_descs[slice_index]; | |
| if (m_params.m_compute_stats) | |
| { | |
| image_stats& s = m_stats[slice_index]; | |
| // The m_basis_* stats are computed against the transcoded ASTC HDR texture (same format printed below). | |
| s.m_tex_format = m_decoded_output_textures_astc_hdr[slice_index].get_format(); | |
| if (m_params.m_print_stats) | |
| { | |
| printf("Slice: %u\n", slice_index); | |
| } | |
| image_metrics im; | |
| if (m_params.m_print_stats) | |
| { | |
| fmt_printf("Quality stats vs. transcoded {} (and {} alternate) textures:\n", get_texture_format_name(m_decoded_output_textures_astc_hdr[slice_index].get_format()), get_texture_format_name(m_decoded_output_textures[slice_index].get_format())); | |
| printf("\nASTC channels:\n"); | |
| for (uint32_t i = 0; i < 3; i++) | |
| { | |
| im.calc_half(m_slice_images_hdr[slice_index], m_decoded_output_textures_astc_hdr_unpacked[slice_index], i, 1, true); | |
| printf("%c: ", "RGB"[i]); | |
| im.print_hp(); | |
| } | |
| printf("BC6H channels:\n"); | |
| for (uint32_t i = 0; i < 3; i++) | |
| { | |
| im.calc_half(m_slice_images_hdr[slice_index], m_decoded_output_textures_bc6h_hdr_unpacked[slice_index], i, 1, true); | |
| printf("%c: ", "RGB"[i]); | |
| im.print_hp(); | |
| } | |
| } | |
| im.calc_half(m_slice_images_hdr[slice_index], m_decoded_output_textures_astc_hdr_unpacked[slice_index], 0, 3, true); | |
| s.m_basis_rgb_avg_psnr = (float)im.m_psnr; | |
| if (m_params.m_print_stats) | |
| { | |
| printf("\nASTC RGB: "); | |
| im.print_hp(); | |
| #if 0 | |
| // Validation | |
| im.calc_half2(m_slice_images_hdr[slice_index], m_decoded_output_textures_astc_hdr_unpacked[slice_index], 0, 3, true); | |
| printf("\nASTC RGB (Alt): "); | |
| im.print_hp(); | |
| #endif | |
| } | |
| im.calc_half(m_slice_images_hdr[slice_index], m_decoded_output_textures_bc6h_hdr_unpacked[slice_index], 0, 3, true); | |
| s.m_basis_rgb_avg_bc6h_psnr = (float)im.m_psnr; | |
| if (m_params.m_print_stats) | |
| { | |
| printf("BC6H RGB: "); | |
| im.print_hp(); | |
| //printf("\n"); | |
| } | |
| im.calc(m_slice_images_hdr[slice_index], m_decoded_output_textures_astc_hdr_unpacked[slice_index], 0, 3, true, true); | |
| s.m_basis_rgb_avg_astc_hdr_log2_psnr = (float)im.m_psnr; | |
| if (m_params.m_print_stats) | |
| { | |
| printf("\nASTC Log2 RGB: "); | |
| im.print_hp(); | |
| } | |
| im.calc(m_slice_images_hdr[slice_index], m_decoded_output_textures_bc6h_hdr_unpacked[slice_index], 0, 3, true, true); | |
| s.m_basis_rgb_avg_bc6h_log2_psnr = (float)im.m_psnr; | |
| if (m_params.m_print_stats) | |
| { | |
| printf("BC6H Log2 RGB: "); | |
| im.print_hp(); | |
| printf("\n"); | |
| } | |
| } | |
| if (m_params.m_debug_images) | |
| { | |
| std::string out_basename; | |
| if (m_params.m_out_filename.size()) | |
| string_get_filename(m_params.m_out_filename.c_str(), out_basename); | |
| else if (m_params.m_source_filenames.size()) | |
| string_get_filename(m_params.m_source_filenames[slice_desc.m_source_file_index].c_str(), out_basename); | |
| string_remove_extension(out_basename); | |
| out_basename = "basis_debug_" + out_basename + string_format("_slice_%u", slice_index); | |
| // Write BC6H .DDS file. | |
| { | |
| gpu_image bc6h_tex(m_decoded_output_textures[slice_index]); | |
| bc6h_tex.override_dimensions(slice_desc.m_orig_width, slice_desc.m_orig_height); | |
| std::string filename(out_basename + "_bc6h.dds"); | |
| write_compressed_texture_file(filename.c_str(), bc6h_tex, false); | |
| printf("Wrote .DDS file %s\n", filename.c_str()); | |
| } | |
| // Write ASTC .KTX/.astc files. ("astcenc -dh input.astc output.exr" to decode the astc file.) | |
| { | |
| gpu_image astc_tex(m_decoded_output_textures_astc_hdr[slice_index]); | |
| astc_tex.override_dimensions(slice_desc.m_orig_width, slice_desc.m_orig_height); | |
| std::string filename1(out_basename + "_astc.astc"); | |
| uint32_t block_width = 4, block_height = 4; | |
| if ((m_params.m_hdr_mode == hdr_modes::cASTC_HDR_6X6) || (m_params.m_hdr_mode == hdr_modes::cUASTC_HDR_6X6_INTERMEDIATE)) | |
| { | |
| block_width = 6; | |
| block_height = 6; | |
| } | |
| write_astc_file(filename1.c_str(), astc_tex.get_ptr(), block_width, block_height, slice_desc.m_orig_width, slice_desc.m_orig_height); | |
| printf("Wrote .ASTC file %s\n", filename1.c_str()); | |
| std::string filename2(out_basename + "_astc.ktx"); | |
| write_compressed_texture_file(filename2.c_str(), astc_tex, false); | |
| printf("Wrote .KTX file %s\n", filename2.c_str()); | |
| } | |
| // Write unpacked ASTC image to .EXR | |
| { | |
| imagef astc_img(m_decoded_output_textures_astc_hdr_unpacked[slice_index]); | |
| astc_img.resize(slice_desc.m_orig_width, slice_desc.m_orig_height); | |
| std::string filename(out_basename + "_unpacked_astc.exr"); | |
| write_exr(filename.c_str(), astc_img, 3, 0); | |
| printf("Wrote .EXR file %s\n", filename.c_str()); | |
| } | |
| // Write unpacked BC6H image to .EXR | |
| { | |
| imagef bc6h_img(m_decoded_output_textures_bc6h_hdr_unpacked[slice_index]); | |
| bc6h_img.resize(slice_desc.m_orig_width, slice_desc.m_orig_height); | |
| std::string filename(out_basename + "_unpacked_bc6h.exr"); | |
| write_exr(filename.c_str(), bc6h_img, 3, 0); | |
| printf("Wrote .EXR file %s\n", filename.c_str()); | |
| } | |
| // Write tonemapped/srgb images | |
| 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); | |
| 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); | |
| 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); | |
| } | |
| } | |
| } | |
| else | |
| { | |
| for (uint32_t slice_index = 0; slice_index < m_slice_descs.size(); slice_index++) | |
| { | |
| const basisu_backend_slice_desc& slice_desc = m_slice_descs[slice_index]; | |
| if (m_params.m_compute_stats) | |
| { | |
| if (m_params.m_print_stats) | |
| printf("Slice: %u\n", slice_index); | |
| image_stats& s = m_stats[slice_index]; | |
| // The m_basis_* stats are computed against the transcoded LDR texture (same format printed below). | |
| s.m_tex_format = m_decoded_output_textures[slice_index].get_format(); | |
| image_metrics em; | |
| if (m_params.m_print_stats) | |
| fmt_printf("Quality stats vs. transcoded {} texture:\n", get_texture_format_name(m_decoded_output_textures[slice_index].get_format())); | |
| em.calc(m_slice_images[slice_index], m_decoded_output_textures_unpacked[slice_index], 0, 3); | |
| if (m_params.m_print_stats) | |
| em.print("RGB Avg: "); | |
| s.m_basis_rgb_avg_psnr = (float)em.m_psnr; | |
| em.calc(m_slice_images[slice_index], m_decoded_output_textures_unpacked[slice_index], 0, 4); | |
| if (m_params.m_print_stats) | |
| em.print("RGBA Avg: "); | |
| s.m_basis_rgba_avg_psnr = (float)em.m_psnr; | |
| em.calc(m_slice_images[slice_index], m_decoded_output_textures_unpacked[slice_index], 0, 1); | |
| if (m_params.m_print_stats) | |
| em.print("R Avg: "); | |
| em.calc(m_slice_images[slice_index], m_decoded_output_textures_unpacked[slice_index], 1, 1); | |
| if (m_params.m_print_stats) | |
| em.print("G Avg: "); | |
| em.calc(m_slice_images[slice_index], m_decoded_output_textures_unpacked[slice_index], 2, 1); | |
| if (m_params.m_print_stats) | |
| em.print("B Avg: "); | |
| //if (m_params.m_uastc) | |
| { | |
| em.calc(m_slice_images[slice_index], m_decoded_output_textures_unpacked[slice_index], 3, 1); | |
| if (m_params.m_print_stats) | |
| em.print("A Avg: "); | |
| s.m_basis_a_avg_psnr = (float)em.m_psnr; | |
| } | |
| em.calc(m_slice_images[slice_index], m_decoded_output_textures_unpacked[slice_index], 0, 0); | |
| if (m_params.m_print_stats) | |
| em.print("709 Luma: "); | |
| s.m_basis_luma_709_psnr = static_cast<float>(em.m_psnr); | |
| s.m_basis_luma_709_ssim = static_cast<float>(em.m_ssim); | |
| em.calc(m_slice_images[slice_index], m_decoded_output_textures_unpacked[slice_index], 0, 0, true, true); | |
| if (m_params.m_print_stats) | |
| em.print("601 Luma: "); | |
| s.m_basis_luma_601_psnr = static_cast<float>(em.m_psnr); | |
| if (m_slice_descs.size() == 1) | |
| { | |
| const uint32_t output_size = comp_size ? (uint32_t)comp_size : (uint32_t)comp_data.size(); | |
| if (m_params.m_print_stats) | |
| { | |
| debug_printf("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))); | |
| debug_printf("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))); | |
| } | |
| } | |
| if (m_params.m_psnr_hvs_m_stats) | |
| { | |
| bool hvs_status = psnr_hvs_compute_metrics(m_slice_images[slice_index], m_decoded_output_textures_unpacked[slice_index], s.m_hvs_metrics); | |
| if (!hvs_status) | |
| error_printf("psnr_hvs_compute_metrics() failed!\n"); | |
| if (m_params.m_print_stats) | |
| { | |
| fmt_printf("PSNR-HVS and PSNR-HVS-M metrics:\n"); | |
| psnr_hvs_print_metrics(s.m_hvs_metrics); | |
| } | |
| } | |
| if (m_decoded_output_textures_unpacked_bc7[slice_index].get_width()) | |
| { | |
| // ---- BC7 stats | |
| if (m_params.m_print_stats) | |
| fmt_printf("Quality stats vs. transcoded {} texture:\n", get_texture_format_name(m_decoded_output_textures_bc7[slice_index].get_format())); | |
| em.calc(m_slice_images[slice_index], m_decoded_output_textures_unpacked_bc7[slice_index], 0, 3); | |
| if (m_params.m_print_stats) | |
| em.print("BC7 RGB Avg: "); | |
| s.m_bc7_rgb_avg_psnr = (float)em.m_psnr; | |
| em.calc(m_slice_images[slice_index], m_decoded_output_textures_unpacked_bc7[slice_index], 0, 4); | |
| if (m_params.m_print_stats) | |
| em.print("BC7 RGBA Avg: "); | |
| s.m_bc7_rgba_avg_psnr = (float)em.m_psnr; | |
| em.calc(m_slice_images[slice_index], m_decoded_output_textures_unpacked_bc7[slice_index], 0, 1); | |
| if (m_params.m_print_stats) | |
| em.print("BC7 R Avg: "); | |
| em.calc(m_slice_images[slice_index], m_decoded_output_textures_unpacked_bc7[slice_index], 1, 1); | |
| if (m_params.m_print_stats) | |
| em.print("BC7 G Avg: "); | |
| em.calc(m_slice_images[slice_index], m_decoded_output_textures_unpacked_bc7[slice_index], 2, 1); | |
| if (m_params.m_print_stats) | |
| em.print("BC7 B Avg: "); | |
| //if (m_params.m_uastc) | |
| { | |
| em.calc(m_slice_images[slice_index], m_decoded_output_textures_unpacked_bc7[slice_index], 3, 1); | |
| if (m_params.m_print_stats) | |
| em.print("BC7 A Avg: "); | |
| s.m_bc7_a_avg_psnr = (float)em.m_psnr; | |
| } | |
| em.calc(m_slice_images[slice_index], m_decoded_output_textures_unpacked_bc7[slice_index], 0, 0); | |
| if (m_params.m_print_stats) | |
| em.print("BC7 709 Luma: "); | |
| s.m_bc7_luma_709_psnr = static_cast<float>(em.m_psnr); | |
| s.m_bc7_luma_709_ssim = static_cast<float>(em.m_ssim); | |
| em.calc(m_slice_images[slice_index], m_decoded_output_textures_unpacked_bc7[slice_index], 0, 0, true, true); | |
| if (m_params.m_print_stats) | |
| em.print("BC7 601 Luma: "); | |
| s.m_bc7_luma_601_psnr = static_cast<float>(em.m_psnr); | |
| if (m_params.m_psnr_hvs_m_stats) | |
| { | |
| bool hvs_status2 = psnr_hvs_compute_metrics(m_slice_images[slice_index], m_decoded_output_textures_unpacked_bc7[slice_index], s.m_hvs_metrics_bc7); | |
| if (!hvs_status2) | |
| error_printf("psnr_hvs_compute_metrics() failed!\n"); | |
| if (m_params.m_print_stats) | |
| { | |
| fmt_printf("PSNR-HVS and PSNR-HVS-M metrics (BC7):\n"); | |
| psnr_hvs_print_metrics(s.m_hvs_metrics_bc7); | |
| } | |
| } | |
| } | |
| if (!m_params.m_uastc) | |
| { | |
| // ---- Nearly best possible ETC1S stats | |
| em.calc(m_slice_images[slice_index], m_best_etc1s_images_unpacked[slice_index], 0, 3); | |
| //if (m_params.m_print_stats) | |
| // em.print("Unquantized ETC1S RGB Avg: "); | |
| s.m_best_etc1s_rgb_avg_psnr = static_cast<float>(em.m_psnr); | |
| em.calc(m_slice_images[slice_index], m_best_etc1s_images_unpacked[slice_index], 0, 0); | |
| //if (m_params.m_print_stats) | |
| // em.print("Unquantized ETC1S 709 Luma: "); | |
| s.m_best_etc1s_luma_709_psnr = static_cast<float>(em.m_psnr); | |
| s.m_best_etc1s_luma_709_ssim = static_cast<float>(em.m_ssim); | |
| em.calc(m_slice_images[slice_index], m_best_etc1s_images_unpacked[slice_index], 0, 0, true, true); | |
| //if (m_params.m_print_stats) | |
| // em.print("Unquantized ETC1S 601 Luma: "); | |
| s.m_best_etc1s_luma_601_psnr = static_cast<float>(em.m_psnr); | |
| } | |
| } | |
| std::string out_basename; | |
| if (m_params.m_out_filename.size()) | |
| string_get_filename(m_params.m_out_filename.c_str(), out_basename); | |
| else if (m_params.m_source_filenames.size()) | |
| string_get_filename(m_params.m_source_filenames[slice_desc.m_source_file_index].c_str(), out_basename); | |
| string_remove_extension(out_basename); | |
| out_basename = "basis_debug_" + out_basename + string_format("_slice_%u", slice_index); | |
| if ((!m_params.m_uastc) && (m_frontend.get_params().m_debug_images)) | |
| { | |
| // Write "best" ETC1S debug images | |
| if (!m_params.m_uastc) | |
| { | |
| gpu_image best_etc1s_gpu_image(m_best_etc1s_images[slice_index]); | |
| best_etc1s_gpu_image.override_dimensions(slice_desc.m_orig_width, slice_desc.m_orig_height); | |
| write_compressed_texture_file((out_basename + "_best_etc1s.ktx").c_str(), best_etc1s_gpu_image, m_params.m_ktx2_and_basis_srgb_transfer_function); | |
| image best_etc1s_unpacked; | |
| best_etc1s_gpu_image.unpack(best_etc1s_unpacked, m_params.m_ktx2_and_basis_srgb_transfer_function); | |
| save_png(out_basename + "_best_etc1s.png", best_etc1s_unpacked); | |
| } | |
| } | |
| if (m_params.m_debug_images) | |
| { | |
| // Write decoded ETC1S/ASTC debug images | |
| { | |
| gpu_image decoded_etc1s_or_astc(m_decoded_output_textures[slice_index]); | |
| decoded_etc1s_or_astc.override_dimensions(slice_desc.m_orig_width, slice_desc.m_orig_height); | |
| write_compressed_texture_file((out_basename + "_transcoded_etc1s_or_astc.ktx").c_str(), decoded_etc1s_or_astc, m_params.m_ktx2_and_basis_srgb_transfer_function); | |
| image temp(m_decoded_output_textures_unpacked[slice_index]); | |
| temp.crop(slice_desc.m_orig_width, slice_desc.m_orig_height); | |
| save_png(out_basename + "_transcoded_etc1s_or_astc.png", temp); | |
| } | |
| // Write decoded BC7 debug images | |
| if (m_decoded_output_textures_bc7[slice_index].get_pixel_width()) | |
| { | |
| gpu_image decoded_bc7(m_decoded_output_textures_bc7[slice_index]); | |
| decoded_bc7.override_dimensions(slice_desc.m_orig_width, slice_desc.m_orig_height); | |
| write_compressed_texture_file((out_basename + "_transcoded_bc7.ktx").c_str(), decoded_bc7, m_params.m_ktx2_and_basis_srgb_transfer_function); | |
| image temp(m_decoded_output_textures_unpacked_bc7[slice_index]); | |
| temp.crop(slice_desc.m_orig_width, slice_desc.m_orig_height); | |
| save_png(out_basename + "_transcoded_bc7.png", temp); | |
| } | |
| } | |
| if ((m_params.m_debug) && (m_decoded_output_textures_bc7[slice_index].get_pixel_width())) | |
| { | |
| const gpu_image& decoded_bc7 = m_decoded_output_textures_bc7[slice_index]; | |
| create_bc7_debug_images(slice_desc.m_orig_width, slice_desc.m_orig_height, decoded_bc7.get_ptr(), m_params.m_debug_images ? out_basename.c_str() : nullptr); | |
| } | |
| } | |
| } // if (m_params.m_hdr) | |
| } // if (m_params.m_validate_output_data) | |
| return true; | |
| } | |
| // Make sure all the mip 0's have the same dimensions and number of mipmap levels, or we can't encode the KTX2 file. | |
| bool basis_compressor::validate_ktx2_constraints() | |
| { | |
| uint32_t base_width = 0, base_height = 0; | |
| uint32_t total_layers = 0; | |
| for (uint32_t i = 0; i < m_slice_descs.size(); i++) | |
| { | |
| if (m_slice_descs[i].m_mip_index == 0) | |
| { | |
| if (!base_width) | |
| { | |
| base_width = m_slice_descs[i].m_orig_width; | |
| base_height = m_slice_descs[i].m_orig_height; | |
| } | |
| else | |
| { | |
| if ((m_slice_descs[i].m_orig_width != base_width) || (m_slice_descs[i].m_orig_height != base_height)) | |
| { | |
| return false; | |
| } | |
| } | |
| total_layers = maximum<uint32_t>(total_layers, m_slice_descs[i].m_source_file_index + 1); | |
| } | |
| } | |
| basisu::vector<uint32_t> total_mips(total_layers); | |
| for (uint32_t i = 0; i < m_slice_descs.size(); i++) | |
| 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); | |
| for (uint32_t i = 1; i < total_layers; i++) | |
| { | |
| if (total_mips[0] != total_mips[i]) | |
| { | |
| return false; | |
| } | |
| } | |
| return true; | |
| } | |
| // KTX2 DFD base definitions | |
| // colorModel=KTX2_KDF_DF_MODEL_ETC1S (0xA3) | |
| // LDR ETC1S texture data in a custom format, with global codebooks | |
| static uint8_t g_ktx2_etc1s_nonalpha_dfd[44] = | |
| { | |
| 0x2C,0x0,0x0,0x0, // 0 totalSize | |
| 0x0,0x0,0x0,0x0, // 1 descriptorType/vendorId | |
| 0x2,0x0,0x28,0x0, // 2 descriptorBlockSize/versionNumber | |
| 0xA3,0x1,0x2,0x0, // 3 flags, transferFunction, colorPrimaries, colorModel (KTX2_KDF_DF_MODEL_UASTC_HDR_6X6_INTERMEDIATE) | |
| 0x3,0x3,0x0,0x0, // 4 texelBlockDimension0-texelBlockDimension3 | |
| 0x8,0x0,0x0,0x0, // 5 bytesPlane0-bytesPlane3 | |
| 0x0,0x0,0x0,0x0, // 6 bytesPlane4-bytesPlane7 | |
| 0x0,0x0,0x3F,0x0, // 7 bitOffset/bitLength/channelType and Qualifer flags (KHR_DF_SAMPLE_DATATYPE_FLOAT etc.) | |
| 0x0,0x0,0x0,0x0, // 8 samplePosition0-samplePosition3 | |
| 0x0,0x0,0x0,0x0, // 9 sampleLower (0) | |
| 0xFF,0xFF,0xFF,0xFF // 10 sampleHigher (0xFF) | |
| }; | |
| 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, | |
| 0x8,0x8,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 | |
| }; | |
| // colorModel=KTX2_KDF_DF_MODEL_UASTC_LDR_4X4 (0xA6) | |
| // LDR UASTC 4x4 texture data in a custom block format | |
| 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 | |
| }; | |
| 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 | |
| }; | |
| // colorModel=KTX2_KDF_DF_MODEL_UASTC_HDR_4X4 (0xA7) | |
| // Standard ASTC HDR 4x4 texture data but constrained for easy transcoding to BC6H, either highest quality or RDO optimized. | |
| static uint8_t g_ktx2_uastc_hdr_4x4_nonalpha_dfd[44] = | |
| { | |
| 0x2C,0x0,0x0,0x0, // 0 totalSize | |
| 0x0,0x0,0x0,0x0, // 1 descriptorType/vendorId | |
| 0x2,0x0,0x28,0x0, // 2 descriptorBlockSize/versionNumber | |
| 0xA7,0x1,0x1,0x0, // 3 flags, transferFunction, colorPrimaries, colorModel (KTX2_KDF_DF_MODEL_UASTC_HDR_4X4) | |
| 0x3,0x3,0x0,0x0, // 4 texelBlockDimension0-texelBlockDimension3 | |
| 0x10,0x0,0x0,0x0, // 5 bytesPlane0-bytesPlane3 | |
| 0x0,0x0,0x0,0x0, // 6 bytesPlane4-bytesPlane7 | |
| 0x0,0x0,0x7F,0x80, // 7 bitOffset/bitLength/channelType and Qualifer flags (KHR_DF_SAMPLE_DATATYPE_FLOAT etc.) | |
| 0x0,0x0,0x0,0x0, // 8 samplePosition0-samplePosition3 | |
| 0x0,0x0,0x0,0x0, // 9 sampleLower (0.0) | |
| 0x00, 0x00, 0x80, 0x3F // 10 sampleHigher (1.0) | |
| }; | |
| // colorModel=KTX2_KDF_DF_MODEL_ASTC (0xA2) | |
| // Standard ASTC HDR 6x6 texture data, either highest quality or RDO optimized. | |
| static uint8_t g_ktx2_astc_hdr_6x6_nonalpha_dfd[44] = | |
| { | |
| 0x2C,0x0,0x0,0x0, // 0 totalSize | |
| 0x0,0x0,0x0,0x0, // 1 descriptorType/vendorId | |
| 0x2,0x0,0x28,0x0, // 2 descriptorBlockSize/versionNumber | |
| 0xA2,0x1,0x1,0x0, // 3 flags, transferFunction, colorPrimaries, colorModel (0xA2/162, standard ASTC, KTX2_KDF_DF_MODEL_ASTC) | |
| 0x5,0x5,0x0,0x0, // 4 texelBlockDimension0-texelBlockDimension3 | |
| 0x10,0x0,0x0,0x0, // 5 bytesPlane0-bytesPlane3 | |
| 0x0,0x0,0x0,0x0, // 6 bytesPlane4-bytesPlane7 | |
| 0x0,0x0,0x7F,0x80 | 0x40, // 7 bitOffset/bitLength/channelType and Qualifer flags (KHR_DF_SAMPLE_DATATYPE_FLOAT etc.) | |
| 0x0,0x0,0x0,0x0, // 8 samplePosition0-samplePosition3 | |
| 0x0, 0x0, 0x80, 0xBF, // 9 sampleLower (-1.0), to match KTX-Software expected value | |
| 0x00, 0x00, 0x80, 0x3F // 10 sampleHigher (1.0) | |
| }; | |
| // colorModel=KTX2_KDF_DF_MODEL_UASTC_HDR_6X6_INTERMEDIATE (0xA8) | |
| // Our custom intermediate format that when decoded directly outputs ASTC HDR 6x6 | |
| static uint8_t g_ktx2_uastc_hdr_6x6_intermediate_nonalpha_dfd[44] = | |
| { | |
| 0x2C,0x0,0x0,0x0, // 0 totalSize | |
| 0x0,0x0,0x0,0x0, // 1 descriptorType/vendorId | |
| 0x2,0x0,0x28,0x0, // 2 descriptorBlockSize/versionNumber | |
| 0xA8,0x1,0x1,0x0, // 3 flags, transferFunction, colorPrimaries, colorModel (KTX2_KDF_DF_MODEL_UASTC_HDR_6X6_INTERMEDIATE) | |
| 0x5,0x5,0x0,0x0, // 4 texelBlockDimension0-texelBlockDimension3 | |
| 0x10,0x0,0x0,0x0, // 5 bytesPlane0-bytesPlane3 | |
| 0x0,0x0,0x0,0x0, // 6 bytesPlane4-bytesPlane7 | |
| 0x0,0x0,0x7F,0x80, // 7 bitOffset/bitLength/channelType and Qualifer flags (KHR_DF_SAMPLE_DATATYPE_FLOAT etc.) | |
| 0x0,0x0,0x0,0x0, // 8 samplePosition0-samplePosition3 | |
| 0x0,0x0,0x0,0x0, // 9 sampleLower (0.0) | |
| 0x00, 0x00, 0x80, 0x3F // 10 sampleHigher (1.0) | |
| }; | |
| // colorModel=KTX2_KDF_DF_MODEL_XUASTC_LDR_INTERMEDIATE (0xA9) | |
| // Custom supercompressed intermediate format, decodes directly to standard ASTC LDR 4x4-12x12. | |
| static uint8_t g_ktx2_xuastc_ldr_intermediate_dfd[44] = | |
| { | |
| 0x2C,0x0,0x0,0x0, // 0 totalSize | |
| 0x0,0x0,0x0,0x0, // 1 descriptorType/vendorId | |
| 0x2,0x0,0x28,0x0, // 2 descriptorBlockSize/versionNumber | |
| (uint8_t)basist::KTX2_KDF_DF_MODEL_XUASTC_LDR_INTERMEDIATE,0x1,0x1,0x0, // 3 flags, transferFunction, colorPrimaries, colorModel (KTX2_KDF_DF_MODEL_UASTC_HDR_6X6_INTERMEDIATE) | |
| 0x3,0x3,0x0,0x0, // 4 texelBlockDimension0-texelBlockDimension3 | |
| 0x10,0x0,0x0,0x0, // 5 bytesPlane0-bytesPlane3 | |
| 0x0,0x0,0x0,0x0, // 6 bytesPlane4-bytesPlane7 | |
| 0x0,0x0,0x7F,0x00, // 7 bitOffset/bitLength/channelType and Qualifer flags (KHR_DF_SAMPLE_DATATYPE_FLOAT etc.) | |
| 0x0,0x0,0x0,0x0, // 8 samplePosition0-samplePosition3 | |
| 0x0,0x0,0x0,0x0, // 9 sampleLower (0) | |
| 0xFF,0xFF,0xFF,0xFF // 10 sampleHigher (0xFF) | |
| }; | |
| // ASTC LDR 4x4 | |
| static uint8_t g_ktx2_astc_ldr_dfd[44] = | |
| { | |
| 0x2C,0x0,0x0,0x0, // 0 totalSize | |
| 0x0,0x0,0x0,0x0, // 1 descriptorType/vendorId | |
| 0x2,0x0,0x28,0x0, // 2 descriptorBlockSize/versionNumber | |
| (uint8_t)basist::KTX2_KDF_DF_MODEL_ASTC,0x1,0x1,0x0, // 3 flags, transferFunction, colorPrimaries, colorModel | |
| 0x3,0x3,0x0,0x0, // 4 texelBlockDimension0-texelBlockDimension3 | |
| 0x10,0x0,0x0,0x0, // 5 bytesPlane0-bytesPlane3 | |
| 0x0,0x0,0x0,0x0, // 6 bytesPlane4-bytesPlane7 | |
| 0x0,0x0,0x7F,0x00, // 7 bitOffset/bitLength/channelType and Qualifer flags (KHR_DF_SAMPLE_DATATYPE_FLOAT etc.), channelID=KHR_DF_CHANNEL_ASTC_DATA | |
| 0x0,0x0,0x0,0x0, // 8 samplePosition0-samplePosition3 | |
| 0x0,0x0,0x0,0x0, // 9 sampleLower (0.0) | |
| 0xFF,0xFF,0xFF,0xFF // 10 sampleHigher (0xFF) | |
| }; | |
| // colorModel=KTX2_KDF_DF_MODEL_XUBC7 (0xAA) | |
| // Custom supercompressed intermediate format, decodes directly to standard ASTC LDR 4x4-12x12. | |
| static uint8_t g_ktx2_xubc7_dfd[44] = | |
| { | |
| 0x2C,0x0,0x0,0x0, // 0 totalSize | |
| 0x0,0x0,0x0,0x0, // 1 descriptorType/vendorId | |
| 0x2,0x0,0x28,0x0, // 2 descriptorBlockSize/versionNumber | |
| (uint8_t)basist::KTX2_KDF_DF_MODEL_XUBC7,0x1,0x1,0x0, // 3 flags, transferFunction, colorPrimaries, colorModel | |
| 0x3,0x3,0x0,0x0, // 4 texelBlockDimension0-texelBlockDimension3 | |
| 0x10,0x0,0x0,0x0, // 5 bytesPlane0-bytesPlane3 | |
| 0x0,0x0,0x0,0x0, // 6 bytesPlane4-bytesPlane7 | |
| 0x0,0x0,0x7F,0x00, // 7 bitOffset/bitLength/channelType and Qualifer flags (KHR_DF_SAMPLE_DATATYPE_FLOAT etc.) | |
| 0x0,0x0,0x0,0x0, // 8 samplePosition0-samplePosition3 | |
| 0x0,0x0,0x0,0x0, // 9 sampleLower (0) | |
| 0xFF,0xFF,0xFF,0xFF // 10 sampleHigher (0xFF) | |
| }; | |
| bool basis_compressor::get_dfd(uint8_vec &dfd, const basist::ktx2_header &header) | |
| { | |
| BASISU_NOTE_UNUSED(header); | |
| const uint8_t* pDFD = nullptr; | |
| uint32_t dfd_len = 0; | |
| const bool is_xuastc_ldr = basis_tex_format_is_xuastc_ldr(m_fmt_mode); | |
| const bool is_astc_ldr = basis_tex_format_is_astc_ldr(m_fmt_mode); | |
| const bool is_xubc7 = basis_tex_format_is_xubc7(m_fmt_mode); | |
| // TODO: This was writen before m_fmt_mode existed, refactor to use that exclusively instead. | |
| if (is_xuastc_ldr) | |
| { | |
| // XUASTC LDR 4x4-12x12 | |
| pDFD = g_ktx2_xuastc_ldr_intermediate_dfd; | |
| dfd_len = sizeof(g_ktx2_xuastc_ldr_intermediate_dfd); | |
| } | |
| else if (is_astc_ldr) | |
| { | |
| // ASTC LDR 4x4-12x12 | |
| pDFD = g_ktx2_astc_ldr_dfd; | |
| dfd_len = sizeof(g_ktx2_astc_ldr_dfd); | |
| } | |
| else if (m_params.m_uastc) | |
| { | |
| if (m_params.m_hdr) | |
| { | |
| assert(!is_xubc7); | |
| switch (m_params.m_hdr_mode) | |
| { | |
| case hdr_modes::cUASTC_HDR_4X4: | |
| { | |
| assert(m_fmt_mode == basist::basis_tex_format::cUASTC_HDR_4x4); | |
| pDFD = g_ktx2_uastc_hdr_4x4_nonalpha_dfd; | |
| dfd_len = sizeof(g_ktx2_uastc_hdr_4x4_nonalpha_dfd); | |
| break; | |
| } | |
| case hdr_modes::cASTC_HDR_6X6: | |
| { | |
| assert(m_fmt_mode == basist::basis_tex_format::cASTC_HDR_6x6); | |
| pDFD = g_ktx2_astc_hdr_6x6_nonalpha_dfd; | |
| dfd_len = sizeof(g_ktx2_astc_hdr_6x6_nonalpha_dfd); | |
| break; | |
| } | |
| case hdr_modes::cUASTC_HDR_6X6_INTERMEDIATE: | |
| { | |
| assert(m_fmt_mode == basist::basis_tex_format::cUASTC_HDR_6x6_INTERMEDIATE); | |
| pDFD = g_ktx2_uastc_hdr_6x6_intermediate_nonalpha_dfd; | |
| dfd_len = sizeof(g_ktx2_uastc_hdr_6x6_intermediate_nonalpha_dfd); | |
| break; | |
| } | |
| default: | |
| { | |
| assert(0); | |
| return false; | |
| } | |
| } | |
| } | |
| else if (is_xubc7) | |
| { | |
| // XUBC7 | |
| pDFD = g_ktx2_xubc7_dfd; | |
| dfd_len = sizeof(g_ktx2_xubc7_dfd); | |
| } | |
| // Fallback: Must be LDR UASTC 4x4 | |
| else if (m_any_source_image_has_alpha) | |
| { | |
| assert(m_fmt_mode == basist::basis_tex_format::cUASTC_LDR_4x4); | |
| pDFD = g_ktx2_uastc_ldr_4x4_alpha_dfd; | |
| dfd_len = sizeof(g_ktx2_uastc_ldr_4x4_alpha_dfd); | |
| } | |
| else | |
| { | |
| assert(m_fmt_mode == basist::basis_tex_format::cUASTC_LDR_4x4); | |
| pDFD = g_ktx2_uastc_ldr_4x4_nonalpha_dfd; | |
| dfd_len = sizeof(g_ktx2_uastc_ldr_4x4_nonalpha_dfd); | |
| } | |
| } | |
| else | |
| { | |
| // Must be ETC1S. | |
| assert(!m_params.m_hdr); | |
| assert(m_fmt_mode == basist::basis_tex_format::cETC1S); | |
| if (m_any_source_image_has_alpha) | |
| { | |
| pDFD = g_ktx2_etc1s_alpha_dfd; | |
| dfd_len = sizeof(g_ktx2_etc1s_alpha_dfd); | |
| } | |
| else | |
| { | |
| pDFD = g_ktx2_etc1s_nonalpha_dfd; | |
| dfd_len = sizeof(g_ktx2_etc1s_nonalpha_dfd); | |
| } | |
| } | |
| assert(dfd_len >= 44); | |
| dfd.resize(dfd_len); | |
| memcpy(dfd.data(), pDFD, dfd_len); | |
| // Now modify the DFD DWORD's directly | |
| uint32_t dfd_bits = basisu::read_le_dword(dfd.data() + 3 * sizeof(uint32_t)); | |
| // Color primaries - TODO: Move this option outside of the m_astc_hdr_6x6_options struct. | |
| //if ((m_params.m_hdr) && (m_params.m_astc_hdr_6x6_options.m_rec2020_bt2100_color_gamut)) | |
| if (m_params.m_astc_hdr_6x6_options.m_rec2020_bt2100_color_gamut) | |
| { | |
| dfd_bits &= ~(0xFF << 8); | |
| dfd_bits |= (basist::KTX2_DF_PRIMARIES_BT2020 << 8); | |
| } | |
| // Write the transfer function (linear vs. sRGB) - crucial so any decoders/transcoders know which ASTC decoding profile was used during encoding. | |
| dfd_bits &= ~(0xFF << 16); | |
| if (m_params.m_hdr) | |
| { | |
| if (m_params.m_ktx2_and_basis_srgb_transfer_function) | |
| { | |
| debug_printf("WARNING: In HDR mode but m_ktx2_and_basis_srgb_transfer_function was set to true, which is being ignored while writing the KTX2 DFD transfer function field\n"); | |
| } | |
| // TODO: In HDR mode, always write linear, as a sRGB transfer function doesn't make sense for HDR. | |
| dfd_bits |= (basist::KTX2_KHR_DF_TRANSFER_LINEAR << 16); | |
| } | |
| else | |
| { | |
| // set the KTX2 DFD transfer function | |
| if (m_params.m_ktx2_and_basis_srgb_transfer_function) | |
| dfd_bits |= (basist::KTX2_KHR_DF_TRANSFER_SRGB << 16); | |
| else | |
| dfd_bits |= (basist::KTX2_KHR_DF_TRANSFER_LINEAR << 16); | |
| } | |
| basisu::write_le_dword(dfd.data() + 3 * sizeof(uint32_t), dfd_bits); | |
| // If supercompressed, manipulate the plane bits to match the khronos ktx2 tool's output | |
| // 2/13/2026: for ETC1S, UASTC HDR 6x6i, UASTC LDR 4x4, and possibly other formats this differs now. Looks like we need to write valid plane sizes, Zstd supercompression or not. | |
| #if 0 | |
| if (header.m_supercompression_scheme != basist::KTX2_SS_NONE) | |
| { | |
| uint32_t plane_bits = basisu::read_le_dword(dfd.data() + 5 * sizeof(uint32_t)); | |
| plane_bits &= ~0xFF; | |
| basisu::write_le_dword(dfd.data() + 5 * sizeof(uint32_t), plane_bits); | |
| } | |
| #endif | |
| // Fix up the DFD channel(s) | |
| uint32_t dfd_chan0 = basisu::read_le_dword(dfd.data() + 7 * sizeof(uint32_t)); | |
| if (m_params.m_uastc) | |
| { | |
| dfd_chan0 &= ~(0xF << 24); | |
| // TODO: Allow the caller to override this. Derive from swizzle? | |
| // Only do this for UASTC LDR 4x4 or XUASTC LDR 4x4-12x12 - and now also ASTC LDR 4x4-12x12, which isn't quite standard, but we need some way of determining if the ASTC data has alpha by examining the KTX2 DFD. | |
| if ((m_any_source_image_has_alpha) && | |
| ((m_fmt_mode == basist::basis_tex_format::cUASTC_LDR_4x4) || basis_tex_format_is_xuastc_ldr(m_fmt_mode) || basis_tex_format_is_astc_ldr(m_fmt_mode) || basis_tex_format_is_xubc7(m_fmt_mode))) | |
| { | |
| dfd_chan0 |= (basist::KTX2_DF_CHANNEL_UASTC_RGBA << 24); | |
| } | |
| else | |
| { | |
| // basist::KTX2_DF_CHANNEL_UASTC_RGB==0 | |
| dfd_chan0 |= (basist::KTX2_DF_CHANNEL_UASTC_RGB << 24); | |
| } | |
| } | |
| basisu::write_le_dword(dfd.data() + 7 * sizeof(uint32_t), dfd_chan0); | |
| if ((is_xuastc_ldr) || (is_astc_ldr)) | |
| { | |
| // Write XUASTC/ASTC LDR block dimensions | |
| uint32_t texelBlockDimensions = basisu::read_le_dword(dfd.data() + 4 * sizeof(uint32_t)); | |
| texelBlockDimensions &= ~0xFFFF; | |
| texelBlockDimensions |= ((m_fmt_mode_block_width - 1) | ((m_fmt_mode_block_height - 1) << 8)); | |
| basisu::write_le_dword(dfd.data() + 4 * sizeof(uint32_t), texelBlockDimensions); | |
| } | |
| return true; | |
| } | |
| bool basis_compressor::create_ktx2_file() | |
| { | |
| //bool needs_global_data = false; | |
| bool can_use_zstd = false; | |
| bool is_xuastc_ldr = false; | |
| bool is_astc_ldr = false; | |
| bool is_hdr_6x6i = false; | |
| bool is_xubc7 = false; | |
| switch (m_fmt_mode) | |
| { | |
| case basist::basis_tex_format::cETC1S: | |
| { | |
| //needs_global_data = true; | |
| break; | |
| } | |
| case basist::basis_tex_format::cUASTC_LDR_4x4: | |
| { | |
| can_use_zstd = true; | |
| break; | |
| } | |
| case basist::basis_tex_format::cUASTC_HDR_4x4: | |
| { | |
| can_use_zstd = true; | |
| break; | |
| } | |
| case basist::basis_tex_format::cASTC_HDR_6x6: | |
| { | |
| can_use_zstd = true; | |
| break; | |
| } | |
| case basist::basis_tex_format::cUASTC_HDR_6x6_INTERMEDIATE: | |
| { | |
| //needs_global_data = true; | |
| is_hdr_6x6i = true; | |
| break; | |
| } | |
| case basist::basis_tex_format::cXUASTC_LDR_4x4: | |
| case basist::basis_tex_format::cXUASTC_LDR_5x4: | |
| case basist::basis_tex_format::cXUASTC_LDR_5x5: | |
| case basist::basis_tex_format::cXUASTC_LDR_6x5: | |
| case basist::basis_tex_format::cXUASTC_LDR_6x6: | |
| case basist::basis_tex_format::cXUASTC_LDR_8x5: | |
| case basist::basis_tex_format::cXUASTC_LDR_8x6: | |
| case basist::basis_tex_format::cXUASTC_LDR_10x5: | |
| case basist::basis_tex_format::cXUASTC_LDR_10x6: | |
| case basist::basis_tex_format::cXUASTC_LDR_8x8: | |
| case basist::basis_tex_format::cXUASTC_LDR_10x8: | |
| case basist::basis_tex_format::cXUASTC_LDR_10x10: | |
| case basist::basis_tex_format::cXUASTC_LDR_12x10: | |
| case basist::basis_tex_format::cXUASTC_LDR_12x12: | |
| { | |
| // has built-in compression, no need for Zstd, global supercompressed data has seek tables | |
| is_xuastc_ldr = true; | |
| break; | |
| } | |
| case basist::basis_tex_format::cASTC_LDR_4x4: | |
| case basist::basis_tex_format::cASTC_LDR_5x4: | |
| case basist::basis_tex_format::cASTC_LDR_5x5: | |
| case basist::basis_tex_format::cASTC_LDR_6x5: | |
| case basist::basis_tex_format::cASTC_LDR_6x6: | |
| case basist::basis_tex_format::cASTC_LDR_8x5: | |
| case basist::basis_tex_format::cASTC_LDR_8x6: | |
| case basist::basis_tex_format::cASTC_LDR_10x5: | |
| case basist::basis_tex_format::cASTC_LDR_10x6: | |
| case basist::basis_tex_format::cASTC_LDR_8x8: | |
| case basist::basis_tex_format::cASTC_LDR_10x8: | |
| case basist::basis_tex_format::cASTC_LDR_10x10: | |
| case basist::basis_tex_format::cASTC_LDR_12x10: | |
| case basist::basis_tex_format::cASTC_LDR_12x12: | |
| { | |
| // plain ASTC LDR 4x4-12x12 - can use Zstd | |
| is_astc_ldr = true; | |
| can_use_zstd = true; | |
| break; | |
| } | |
| case basist::basis_tex_format::cXUBC7: | |
| { | |
| // has built-in compression, global supercompressed data has seek tables | |
| is_xubc7 = true; | |
| break; | |
| } | |
| default: | |
| assert(0); | |
| //fmt_debug_printf("HERE 1\n"); | |
| return false; | |
| } | |
| if (can_use_zstd) | |
| { | |
| if ((m_params.m_ktx2_uastc_supercompression != basist::KTX2_SS_NONE) && (m_params.m_ktx2_uastc_supercompression != basist::KTX2_SS_ZSTANDARD)) | |
| { | |
| return false; | |
| } | |
| } | |
| const basisu_backend_output& backend_output = m_backend.get_output(); | |
| // Determine the width/height, number of array layers, mipmap levels, and the number of faces (1 for 2D, 6 for cubemap). | |
| // This does not support 1D or 3D. | |
| uint32_t base_width = 0, base_height = 0, total_layers = 0, total_levels = 0, total_faces = 1; | |
| for (uint32_t i = 0; i < m_slice_descs.size(); i++) | |
| { | |
| if ((m_slice_descs[i].m_mip_index == 0) && (!base_width)) | |
| { | |
| base_width = m_slice_descs[i].m_orig_width; | |
| base_height = m_slice_descs[i].m_orig_height; | |
| } | |
| total_layers = maximum<uint32_t>(total_layers, m_slice_descs[i].m_source_file_index + 1); | |
| if (!m_slice_descs[i].m_source_file_index) | |
| total_levels = maximum<uint32_t>(total_levels, m_slice_descs[i].m_mip_index + 1); | |
| } | |
| if (m_params.m_tex_type == basist::cBASISTexTypeCubemapArray) | |
| { | |
| assert((total_layers % 6) == 0); | |
| total_layers /= 6; | |
| assert(total_layers >= 1); | |
| total_faces = 6; | |
| } | |
| basist::ktx2_header header; | |
| memset((void *)&header, 0, sizeof(header)); | |
| memcpy(header.m_identifier, basist::g_ktx2_file_identifier, sizeof(basist::g_ktx2_file_identifier)); | |
| header.m_pixel_width = base_width; | |
| header.m_pixel_height = base_height; | |
| header.m_face_count = total_faces; | |
| if (m_params.m_hdr) | |
| { | |
| if (m_params.m_hdr_mode == hdr_modes::cUASTC_HDR_4X4) | |
| header.m_vk_format = basist::KTX2_FORMAT_ASTC_4x4_SFLOAT_BLOCK; | |
| else if (m_params.m_hdr_mode == hdr_modes::cASTC_HDR_6X6) | |
| header.m_vk_format = basist::KTX2_FORMAT_ASTC_6x6_SFLOAT_BLOCK; | |
| else | |
| { | |
| assert(m_params.m_hdr_mode == hdr_modes::cUASTC_HDR_6X6_INTERMEDIATE); | |
| header.m_vk_format = basist::KTX2_VK_FORMAT_UNDEFINED; | |
| } | |
| } | |
| else | |
| { | |
| // Either ETC1S, UASTC LDR 4x4, or XUASTC/ASTC LDR 4x4-12x12. | |
| assert((m_fmt_mode == basist::basis_tex_format::cETC1S) || (m_fmt_mode == basist::basis_tex_format::cUASTC_LDR_4x4) || is_xuastc_ldr || is_astc_ldr || is_xubc7); | |
| if (is_astc_ldr) | |
| { | |
| // Get the correct Vulkan format (UNORM or sRGB). | |
| uint32_t fmt = 0; | |
| assert((basist::KTX2_FORMAT_ASTC_4x4_UNORM_BLOCK + 1) == basist::KTX2_FORMAT_ASTC_4x4_SRGB_BLOCK); | |
| switch (m_fmt_mode) | |
| { | |
| case basist::basis_tex_format::cASTC_LDR_4x4: fmt = basist::KTX2_FORMAT_ASTC_4x4_UNORM_BLOCK; break; | |
| case basist::basis_tex_format::cASTC_LDR_5x4: fmt = basist::KTX2_FORMAT_ASTC_5x4_UNORM_BLOCK; break; | |
| case basist::basis_tex_format::cASTC_LDR_5x5: fmt = basist::KTX2_FORMAT_ASTC_5x5_UNORM_BLOCK; break; | |
| case basist::basis_tex_format::cASTC_LDR_6x5: fmt = basist::KTX2_FORMAT_ASTC_6x5_UNORM_BLOCK; break; | |
| case basist::basis_tex_format::cASTC_LDR_6x6: fmt = basist::KTX2_FORMAT_ASTC_6x6_UNORM_BLOCK; break; | |
| case basist::basis_tex_format::cASTC_LDR_8x5: fmt = basist::KTX2_FORMAT_ASTC_8x5_UNORM_BLOCK; break; | |
| case basist::basis_tex_format::cASTC_LDR_8x6: fmt = basist::KTX2_FORMAT_ASTC_8x6_UNORM_BLOCK; break; | |
| case basist::basis_tex_format::cASTC_LDR_10x5: fmt = basist::KTX2_FORMAT_ASTC_10x5_UNORM_BLOCK; break; | |
| case basist::basis_tex_format::cASTC_LDR_10x6: fmt = basist::KTX2_FORMAT_ASTC_10x6_UNORM_BLOCK; break; | |
| case basist::basis_tex_format::cASTC_LDR_8x8: fmt = basist::KTX2_FORMAT_ASTC_8x8_UNORM_BLOCK; break; | |
| case basist::basis_tex_format::cASTC_LDR_10x8: fmt = basist::KTX2_FORMAT_ASTC_10x8_UNORM_BLOCK; break; | |
| case basist::basis_tex_format::cASTC_LDR_10x10: fmt = basist::KTX2_FORMAT_ASTC_10x10_UNORM_BLOCK; break; | |
| case basist::basis_tex_format::cASTC_LDR_12x10: fmt = basist::KTX2_FORMAT_ASTC_12x10_UNORM_BLOCK; break; | |
| case basist::basis_tex_format::cASTC_LDR_12x12: fmt = basist::KTX2_FORMAT_ASTC_12x12_UNORM_BLOCK; break; | |
| default: | |
| assert(0); | |
| return false; | |
| } | |
| assert(fmt); | |
| header.m_vk_format = fmt + (m_params.m_ktx2_and_basis_srgb_transfer_function ? 1 : 0); | |
| } | |
| else | |
| { | |
| // A supercompressed format, i.e. not a standard format. | |
| header.m_vk_format = basist::KTX2_VK_FORMAT_UNDEFINED; | |
| } | |
| } | |
| header.m_type_size = 1; | |
| header.m_level_count = total_levels; | |
| header.m_layer_count = (total_layers > 1) ? total_layers : 0; | |
| if (can_use_zstd) | |
| { | |
| switch (m_params.m_ktx2_uastc_supercompression) | |
| { | |
| case basist::KTX2_SS_NONE: | |
| { | |
| header.m_supercompression_scheme = basist::KTX2_SS_NONE; | |
| break; | |
| } | |
| case basist::KTX2_SS_ZSTANDARD: | |
| { | |
| #if BASISD_SUPPORT_KTX2_ZSTD | |
| header.m_supercompression_scheme = basist::KTX2_SS_ZSTANDARD; | |
| #else | |
| header.m_supercompression_scheme = basist::KTX2_SS_NONE; | |
| #endif | |
| break; | |
| } | |
| default: | |
| assert(0); | |
| //fmt_debug_printf("HERE 3\n"); | |
| return false; | |
| } | |
| } | |
| basisu::vector<uint8_vec> level_data_bytes(total_levels); | |
| basisu::vector<uint8_vec> compressed_level_data_bytes(total_levels); | |
| size_t_vec slice_level_offsets(m_slice_descs.size()); | |
| // This will append the texture data in the correct order (for each level: layer, then face). | |
| for (uint32_t slice_index = 0; slice_index < m_slice_descs.size(); slice_index++) | |
| { | |
| const basisu_backend_slice_desc& slice_desc = m_slice_descs[slice_index]; | |
| slice_level_offsets[slice_index] = level_data_bytes[slice_desc.m_mip_index].size(); | |
| if (m_fmt_mode == basist::basis_tex_format::cETC1S) | |
| { | |
| append_vector(level_data_bytes[slice_desc.m_mip_index], backend_output.m_slice_image_data[slice_index]); | |
| } | |
| else | |
| { | |
| append_vector(level_data_bytes[slice_desc.m_mip_index], m_uastc_backend_output.m_slice_image_data[slice_index]); | |
| } | |
| } | |
| // Zstd Supercompression | |
| if ((can_use_zstd) && (header.m_supercompression_scheme == basist::KTX2_SS_ZSTANDARD)) | |
| { | |
| #if BASISD_SUPPORT_KTX2_ZSTD | |
| for (uint32_t level_index = 0; level_index < total_levels; level_index++) | |
| { | |
| compressed_level_data_bytes[level_index].resize(ZSTD_compressBound(level_data_bytes[level_index].size())); | |
| size_t result = ZSTD_compress(compressed_level_data_bytes[level_index].data(), compressed_level_data_bytes[level_index].size(), | |
| level_data_bytes[level_index].data(), level_data_bytes[level_index].size(), | |
| m_params.m_ktx2_zstd_supercompression_level); | |
| if (ZSTD_isError(result)) | |
| { | |
| //fmt_debug_printf("HERE 5\n"); | |
| return false; | |
| } | |
| compressed_level_data_bytes[level_index].resize(result); | |
| } | |
| #else | |
| // Can't get here | |
| assert(0); | |
| //fmt_debug_printf("HERE 6\n"); | |
| return false; | |
| #endif | |
| } | |
| else | |
| { | |
| // No supercompression | |
| compressed_level_data_bytes = level_data_bytes; | |
| } | |
| uint8_vec ktx2_global_data; | |
| // Create global supercompressed data | |
| if (m_fmt_mode == basist::basis_tex_format::cETC1S) | |
| { | |
| basist::ktx2_etc1s_global_data_header etc1s_global_data_header; | |
| clear_obj(etc1s_global_data_header); | |
| etc1s_global_data_header.m_endpoint_count = backend_output.m_num_endpoints; | |
| etc1s_global_data_header.m_selector_count = backend_output.m_num_selectors; | |
| etc1s_global_data_header.m_endpoints_byte_length = backend_output.m_endpoint_palette.size(); | |
| etc1s_global_data_header.m_selectors_byte_length = backend_output.m_selector_palette.size(); | |
| etc1s_global_data_header.m_tables_byte_length = backend_output.m_slice_image_tables.size(); | |
| basisu::vector<basist::ktx2_etc1s_image_desc> etc1s_image_descs(total_levels * total_layers * total_faces); | |
| memset((void *)etc1s_image_descs.data(), 0, etc1s_image_descs.size_in_bytes()); | |
| for (uint32_t slice_index = 0; slice_index < m_slice_descs.size(); slice_index++) | |
| { | |
| const basisu_backend_slice_desc& slice_desc = m_slice_descs[slice_index]; | |
| const uint32_t level_index = slice_desc.m_mip_index; | |
| uint32_t layer_index = slice_desc.m_source_file_index; | |
| uint32_t face_index = 0; | |
| if (m_params.m_tex_type == basist::cBASISTexTypeCubemapArray) | |
| { | |
| face_index = layer_index % 6; | |
| layer_index /= 6; | |
| } | |
| const uint32_t etc1s_image_index = level_index * (total_layers * total_faces) + layer_index * total_faces + face_index; | |
| if (slice_desc.m_alpha) | |
| { | |
| etc1s_image_descs[etc1s_image_index].m_alpha_slice_byte_length = backend_output.m_slice_image_data[slice_index].size(); | |
| etc1s_image_descs[etc1s_image_index].m_alpha_slice_byte_offset = slice_level_offsets[slice_index]; | |
| } | |
| else | |
| { | |
| if (m_params.m_tex_type == basist::cBASISTexTypeVideoFrames) | |
| etc1s_image_descs[etc1s_image_index].m_image_flags = !slice_desc.m_iframe ? basist::KTX2_IMAGE_IS_P_FRAME : 0; | |
| etc1s_image_descs[etc1s_image_index].m_rgb_slice_byte_length = backend_output.m_slice_image_data[slice_index].size(); | |
| etc1s_image_descs[etc1s_image_index].m_rgb_slice_byte_offset = slice_level_offsets[slice_index]; | |
| } | |
| } // slice_index | |
| append_vector(ktx2_global_data, (const uint8_t*)&etc1s_global_data_header, sizeof(etc1s_global_data_header)); | |
| append_vector(ktx2_global_data, (const uint8_t*)etc1s_image_descs.data(), etc1s_image_descs.size_in_bytes()); | |
| append_vector(ktx2_global_data, backend_output.m_endpoint_palette); | |
| append_vector(ktx2_global_data, backend_output.m_selector_palette); | |
| append_vector(ktx2_global_data, backend_output.m_slice_image_tables); | |
| header.m_supercompression_scheme = basist::KTX2_SS_BASISLZ; | |
| } | |
| else if ((is_hdr_6x6i) || (is_xuastc_ldr) || (is_xubc7)) | |
| { | |
| // The global data for UASTC HDR 6x6 INTERMEDIATE/XUASTC LDR/XUBC7 is an array of ktx2_slice_offset_len_desc_std's, which the transcoder needs to locate the variable length compressed slice data. | |
| // Note: The original v2.0 release used ktx2_slice_offset_len_desc_orig's | |
| basisu::vector<basist::ktx2_slice_offset_len_desc_std> slice_offset_len_descs(total_levels * total_layers * total_faces); | |
| memset((void *)slice_offset_len_descs.data(), 0, slice_offset_len_descs.size_in_bytes()); | |
| for (uint32_t slice_index = 0; slice_index < m_slice_descs.size(); slice_index++) | |
| { | |
| const basisu_backend_slice_desc& slice_desc = m_slice_descs[slice_index]; | |
| const uint32_t level_index = slice_desc.m_mip_index; | |
| uint32_t layer_index = slice_desc.m_source_file_index; | |
| uint32_t face_index = 0; | |
| if (m_params.m_tex_type == basist::cBASISTexTypeCubemapArray) | |
| { | |
| face_index = layer_index % 6; | |
| layer_index /= 6; | |
| } | |
| const uint32_t output_image_index = level_index * (total_layers * total_faces) + layer_index * total_faces + face_index; | |
| slice_offset_len_descs[output_image_index].m_slice_byte_length = m_uastc_backend_output.m_slice_image_data[slice_index].size(); | |
| slice_offset_len_descs[output_image_index].m_slice_byte_offset = slice_level_offsets[slice_index]; | |
| uint32_t profile = 0; | |
| if (is_hdr_6x6i) | |
| { | |
| assert(m_uastc_backend_output.m_slice_image_data[slice_index].size() >= 2); | |
| if (m_uastc_backend_output.m_slice_image_data[slice_index].size() >= 2) | |
| { | |
| // First LE16 is the marker/profile version | |
| profile = m_uastc_backend_output.m_slice_image_data[slice_index][0] | (m_uastc_backend_output.m_slice_image_data[slice_index][1] << 8); | |
| } | |
| } | |
| else if (is_xubc7) | |
| { | |
| assert(m_uastc_backend_output.m_slice_image_data[slice_index].size() >= 1); | |
| if (m_uastc_backend_output.m_slice_image_data[slice_index].size() >= 1) | |
| { | |
| // First 8-bits is the marker/profile version | |
| // TODO high byte is the XUBC7 codec variant index, currently hardcoded to 1 until we have an internal query/introspection API for this | |
| profile = m_uastc_backend_output.m_slice_image_data[slice_index][0] | (0x01 << 8); | |
| } | |
| } | |
| else | |
| { | |
| assert(is_xuastc_ldr ); | |
| assert(m_uastc_backend_output.m_slice_image_data[slice_index].size() >= 1); | |
| if (m_uastc_backend_output.m_slice_image_data[slice_index].size() >= 1) | |
| { | |
| // First byte is always the profile index (Zstd, hybrid, arithmetic etc.) | |
| // TODO high byte is the XUASTC LDR codec variant index, currently hardcoded to 1 until we have an internal query/introspection API for this | |
| profile = m_uastc_backend_output.m_slice_image_data[slice_index][0] | (0x01 << 8); | |
| } | |
| } | |
| slice_offset_len_descs[output_image_index].m_profile = profile; | |
| } // slice_index | |
| append_vector(ktx2_global_data, (const uint8_t*)slice_offset_len_descs.data(), slice_offset_len_descs.size_in_bytes()); | |
| // Note v2.0 would always write BASISLZ for the supercompression scheme. KTX-Software changes this, and we need to be compatible. | |
| //header.m_supercompression_scheme = basist::KTX2_SS_BASISLZ; | |
| header.m_supercompression_scheme = is_hdr_6x6i ? basist::KTX2_SS_UASTC_HDR_6x6I : (is_xubc7 ? basist::KTX2_SS_XUBC7 : basist::KTX2_SS_XUASTC_LDR); | |
| } | |
| // Key values | |
| basist::key_value_vec key_values(m_params.m_key_values); | |
| basist::add_key_value(key_values, "KTXwriter", fmt_string("Basis Universal {}", BASISU_LIB_VERSION_STRING)); | |
| if (m_params.m_hdr) | |
| { | |
| if (m_upconverted_any_ldr_images) | |
| { | |
| basist::add_key_value(key_values, "LDRUpconversionMultiplier", fmt_string("{}", m_ldr_to_hdr_upconversion_nit_multiplier)); | |
| if (m_params.m_ldr_hdr_upconversion_srgb_to_linear) | |
| basist::add_key_value(key_values, "LDRUpconversionSRGBToLinear", "1"); | |
| } | |
| // Always write the scale to simplify testing. | |
| //if (m_hdr_image_scale != 1.0f) | |
| { | |
| // add "KTXmapRange" key value | |
| struct ktx_map_range | |
| { | |
| packed_uint<4> m_scale; | |
| packed_uint<4> m_offset; | |
| }; | |
| ktx_map_range val; | |
| val.m_scale = *reinterpret_cast<const uint32_t *>(&m_hdr_image_scale); | |
| val.m_offset = 0; | |
| auto* pNew_key = key_values.enlarge(1); | |
| const char* pKey_name = "KTXmapRange"; | |
| size_t key_name_len = strlen(pKey_name) + 1; | |
| pNew_key->m_key.resize(key_name_len); | |
| memcpy(pNew_key->m_key.data(), pKey_name, key_name_len); | |
| pNew_key->m_value.resize(sizeof(val)); | |
| memcpy(pNew_key->m_value.data(), &val, sizeof(val)); | |
| } | |
| } | |
| key_values.sort(); | |
| #if BASISU_DISABLE_KTX2_KEY_VALUES | |
| // HACK HACK - Clear the key values array, which causes no key values to be written (triggering the ktx2check validator bug). | |
| key_values.clear(); | |
| #endif | |
| uint8_vec key_value_data; | |
| // DFD (Data Format Descriptor) | |
| uint8_vec dfd; | |
| if (!get_dfd(dfd, header)) | |
| { | |
| return false; | |
| } | |
| const uint32_t kvd_file_offset = sizeof(header) + sizeof(basist::ktx2_level_index) * total_levels + (uint32_t)dfd.size(); | |
| for (uint32_t pass = 0; pass < 2; pass++) | |
| { | |
| for (uint32_t i = 0; i < key_values.size(); i++) | |
| { | |
| if (key_values[i].m_key.size() < 2) | |
| { | |
| return false; | |
| } | |
| if (key_values[i].m_key.back() != 0) | |
| { | |
| return false; | |
| } | |
| const uint64_t total_len = (uint64_t)key_values[i].m_key.size() + (uint64_t)key_values[i].m_value.size(); | |
| if (total_len >= UINT32_MAX) | |
| { | |
| return false; | |
| } | |
| packed_uint<4> le_len((uint32_t)total_len); | |
| append_vector(key_value_data, (const uint8_t*)&le_len, sizeof(le_len)); | |
| append_vector(key_value_data, key_values[i].m_key); | |
| append_vector(key_value_data, key_values[i].m_value); | |
| const uint32_t ofs = key_value_data.size() & 3; | |
| const uint32_t padding = (4 - ofs) & 3; | |
| for (uint32_t p = 0; p < padding; p++) | |
| key_value_data.push_back(0); | |
| } | |
| if (header.m_supercompression_scheme != basist::KTX2_SS_NONE) | |
| break; | |
| #if BASISU_DISABLE_KTX2_ALIGNMENT_WORKAROUND | |
| break; | |
| #endif | |
| // Hack to ensure the KVD block ends on a 16 byte boundary, because we have no other official way of aligning the data. | |
| uint32_t kvd_end_file_offset = kvd_file_offset + (uint32_t)key_value_data.size(); | |
| uint32_t bytes_needed_to_pad = (16 - (kvd_end_file_offset & 15)) & 15; | |
| if (!bytes_needed_to_pad) | |
| { | |
| // We're good. No need to add a dummy key. | |
| break; | |
| } | |
| assert(!pass); | |
| if (pass) | |
| { | |
| return false; | |
| } | |
| if (bytes_needed_to_pad < 6) | |
| bytes_needed_to_pad += 16; | |
| // Just add the padding. It's likely not necessary anymore, but can't really hurt other than a tiny increase in file size. | |
| //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); | |
| // We're not good - need to add a dummy key large enough to force file alignment so the mip level array gets aligned. | |
| // 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(). | |
| key_values.enlarge(1); | |
| for (uint32_t i = 0; i < (bytes_needed_to_pad - 4 - 1 - 1); i++) | |
| key_values.back().m_key.push_back(127); | |
| key_values.back().m_key.push_back(0); | |
| key_values.back().m_value.push_back(0); | |
| key_values.sort(); | |
| key_value_data.resize(0); | |
| // Try again | |
| } | |
| basisu::vector<basist::ktx2_level_index> level_index_array(total_levels); | |
| memset((void *)level_index_array.data(), 0, level_index_array.size_in_bytes()); | |
| m_output_ktx2_file.clear(); | |
| m_output_ktx2_file.reserve(m_output_basis_file.size()); | |
| // Dummy header | |
| m_output_ktx2_file.resize(sizeof(header)); | |
| // Level index array | |
| append_vector(m_output_ktx2_file, (const uint8_t*)level_index_array.data(), level_index_array.size_in_bytes()); | |
| // Write DFD | |
| const uint8_t* pDFD = dfd.data(); | |
| uint32_t dfd_len = (uint32_t)dfd.size(); | |
| header.m_dfd_byte_offset = m_output_ktx2_file.size(); | |
| header.m_dfd_byte_length = dfd_len; | |
| append_vector(m_output_ktx2_file, pDFD, dfd_len); | |
| // Write Key value data | |
| if (key_value_data.size()) | |
| { | |
| assert(kvd_file_offset == m_output_ktx2_file.size()); | |
| header.m_kvd_byte_offset = m_output_ktx2_file.size(); | |
| header.m_kvd_byte_length = key_value_data.size(); | |
| append_vector(m_output_ktx2_file, key_value_data); | |
| } | |
| // Write Global Supercompressed Data | |
| if (ktx2_global_data.size()) | |
| { | |
| uint32_t ofs = m_output_ktx2_file.size() & 7; | |
| uint32_t padding = (8 - ofs) & 7; | |
| for (uint32_t i = 0; i < padding; i++) | |
| m_output_ktx2_file.push_back(0); | |
| header.m_sgd_byte_length = ktx2_global_data.size(); | |
| header.m_sgd_byte_offset = m_output_ktx2_file.size(); | |
| append_vector(m_output_ktx2_file, ktx2_global_data); | |
| } | |
| // Write mipPadding | |
| if (header.m_supercompression_scheme == basist::KTX2_SS_NONE) | |
| { | |
| uint32_t ofs = m_output_ktx2_file.size() & 15; | |
| uint32_t padding = (16 - ofs) & 15; | |
| // Make sure we're always aligned here (due to an old validator bug, which has been fixed). | |
| if (padding) | |
| { | |
| 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); | |
| } | |
| for (uint32_t i = 0; i < padding; i++) | |
| m_output_ktx2_file.push_back(0); | |
| } | |
| // Level data - write the smallest mipmap first. | |
| for (int level = total_levels - 1; level >= 0; level--) | |
| { | |
| level_index_array[level].m_byte_length = compressed_level_data_bytes[level].size(); | |
| if (can_use_zstd) | |
| { | |
| level_index_array[level].m_uncompressed_byte_length = level_data_bytes[level].size(); | |
| } | |
| level_index_array[level].m_byte_offset = m_output_ktx2_file.size(); | |
| append_vector(m_output_ktx2_file, compressed_level_data_bytes[level]); | |
| } | |
| // Write final header | |
| memcpy(m_output_ktx2_file.data(), &header, sizeof(header)); | |
| // Write final level index array | |
| memcpy(m_output_ktx2_file.data() + sizeof(header), level_index_array.data(), level_index_array.size_in_bytes()); | |
| uint32_t total_orig_pixels = 0; | |
| for (uint32_t i = 0; i < m_slice_descs.size(); i++) | |
| { | |
| const basisu_backend_slice_desc& slice_desc = m_slice_descs[i]; | |
| total_orig_pixels += slice_desc.m_orig_width * slice_desc.m_orig_height; | |
| } | |
| m_ktx2_file_size = m_output_ktx2_file.size(); | |
| m_ktx2_bits_per_texel = total_orig_pixels ? (m_ktx2_file_size * 8.0f) / total_orig_pixels : 0; | |
| fmt_debug_printf("Total .ktx2 output file size: {}, {3.3} bits/texel\n", m_ktx2_file_size, m_ktx2_bits_per_texel); | |
| return true; | |
| } | |
| bool basis_parallel_compress( | |
| uint32_t total_threads, | |
| const basisu::vector<basis_compressor_params>& params_vec, | |
| basisu::vector< parallel_results >& results_vec) | |
| { | |
| assert(g_library_initialized); | |
| if (!g_library_initialized) | |
| { | |
| error_printf("basis_parallel_compress: basisu_encoder_init() MUST be called before using any encoder functionality!\n"); | |
| return false; | |
| } | |
| assert(total_threads >= 1); | |
| total_threads = basisu::maximum<uint32_t>(total_threads, 1); | |
| job_pool jpool(total_threads); | |
| results_vec.resize(0); | |
| results_vec.resize(params_vec.size()); | |
| std::atomic<bool> result; | |
| result.store(true); | |
| std::atomic<bool> opencl_failed; | |
| opencl_failed.store(false); | |
| for (uint32_t pindex = 0; pindex < params_vec.size(); pindex++) | |
| { | |
| jpool.add_job([pindex, ¶ms_vec, &results_vec, &result, &opencl_failed] { | |
| basis_compressor_params params = params_vec[pindex]; | |
| parallel_results& results = results_vec[pindex]; | |
| interval_timer tm; | |
| tm.start(); | |
| basis_compressor c; | |
| // Dummy job pool | |
| job_pool task_jpool(1); | |
| params.m_pJob_pool = &task_jpool; | |
| // TODO: Remove this flag entirely | |
| params.m_multithreading = true; | |
| // Stop using OpenCL if a failure ever occurs. | |
| if (opencl_failed) | |
| params.m_use_opencl = false; | |
| bool status = c.init(params); | |
| if (c.get_opencl_failed()) | |
| opencl_failed.store(true); | |
| if (status) | |
| { | |
| basis_compressor::error_code ec = c.process(); | |
| if (c.get_opencl_failed()) | |
| opencl_failed.store(true); | |
| results.m_error_code = ec; | |
| if (ec == basis_compressor::cECSuccess) | |
| { | |
| results.m_basis_file = c.get_output_basis_file(); | |
| results.m_ktx2_file = c.get_output_ktx2_file(); | |
| results.m_stats = c.get_stats(); | |
| results.m_basis_bits_per_texel = c.get_basis_bits_per_texel(); | |
| results.m_any_source_image_has_alpha = c.get_any_source_image_has_alpha(); | |
| } | |
| else | |
| { | |
| result = false; | |
| } | |
| } | |
| else | |
| { | |
| results.m_error_code = basis_compressor::cECFailedInitializing; | |
| result = false; | |
| } | |
| results.m_total_time = tm.get_elapsed_secs(); | |
| } ); | |
| } // pindex | |
| jpool.wait_for_all(); | |
| if (opencl_failed) | |
| error_printf("An OpenCL error occured sometime during compression. The compressor fell back to CPU processing after the failure.\n"); | |
| return result; | |
| } | |
| void* basis_compress_internal( | |
| basist::basis_tex_format mode, | |
| const basisu::vector<image>* pSource_images, | |
| const basisu::vector<imagef>* pSource_images_hdr, | |
| uint32_t flags_and_quality, float uastc_rdo_or_dct_quality, | |
| size_t* pSize, | |
| image_stats* pStats, | |
| int quality_level, int effort_level) | |
| { | |
| assert((pSource_images != nullptr) || (pSource_images_hdr != nullptr)); | |
| assert(!((pSource_images != nullptr) && (pSource_images_hdr != nullptr))); | |
| if ((quality_level != -1) && (uastc_rdo_or_dct_quality != 0.0f)) | |
| { | |
| fmt_debug_printf("basis_compress_internal: quality_level is not -1, but uastc_rdo_or_dct_quality isn't 0!\n"); | |
| // Can't use both old and new-style quality control methods | |
| uastc_rdo_or_dct_quality = 0.0f; | |
| } | |
| if (!pSize) | |
| { | |
| error_printf("basis_compress: Need pSize parameter!\n"); | |
| assert(0); | |
| return nullptr; | |
| } | |
| // Can't provide both LDR and HDR images | |
| if ( ((pSource_images) && (pSource_images->size() != 0)) && | |
| ((pSource_images_hdr) && (pSource_images_hdr->size() != 0)) | |
| ) | |
| { | |
| error_printf("basis_compress: Can't provide both LDR and HDR source images!\n"); | |
| assert(0); | |
| return nullptr; | |
| } | |
| // Check input parameters | |
| if (pSource_images) | |
| { | |
| if (!pSource_images->size()) | |
| { | |
| error_printf("basis_compress: No source LDR images\n"); | |
| assert(0); | |
| return nullptr; | |
| } | |
| } | |
| else | |
| { | |
| if ((!pSource_images_hdr) || (!pSource_images_hdr->size())) | |
| { | |
| error_printf("basis_compress: No source HDR images\n"); | |
| assert(0); | |
| return nullptr; | |
| } | |
| } | |
| *pSize = 0; | |
| // Initialize a job pool | |
| uint32_t num_threads = 1; | |
| if (flags_and_quality & cFlagThreaded) | |
| num_threads = basisu::maximum<uint32_t>(1, get_num_hardware_threads()); | |
| job_pool jp(num_threads); | |
| // Initialize the compressor parameter struct | |
| basis_compressor_params comp_params; | |
| // Set the codec (basist::basis_tex_format) we'll be using. | |
| comp_params.set_format_mode(mode); | |
| comp_params.m_pJob_pool = &jp; | |
| comp_params.m_y_flip = (flags_and_quality & cFlagYFlip) != 0; | |
| // Set debug related parameters | |
| comp_params.m_debug = (flags_and_quality & cFlagDebug) != 0; | |
| comp_params.m_debug_images = (flags_and_quality & cFlagDebugImages) != 0; | |
| // Set texture type: 2D, 2D array, cubemap array etc. | |
| comp_params.m_tex_type = (basist::basis_texture_type)((flags_and_quality >> cFlagTextureTypeShift) & cFlagTextureTypeMask); | |
| if (comp_params.m_tex_type != basist::basis_texture_type::cBASISTexType2D) | |
| { | |
| // 2D array, cubemap array, or texture video. Assume any extra images the user has supplied are actually cubemap faces, or array layers, or texture video frames. | |
| // We assume the dimensions are correct here and let the compressor validate them. | |
| // TODO: This simplified API doesn't allow the user to also specify the mipmap levels here. | |
| if (pSource_images) | |
| { | |
| for (uint32_t i = 0; i < pSource_images->size(); i++) | |
| comp_params.m_source_images.push_back((*pSource_images)[i]); | |
| } | |
| else | |
| { | |
| for (uint32_t i = 0; i < pSource_images_hdr->size(); i++) | |
| comp_params.m_source_images_hdr.push_back((*pSource_images_hdr)[i]); | |
| } | |
| } | |
| else | |
| { | |
| // Plain 2D mode. Assume any extra images the user has supplied are precomputed mipmap levels of the correct dimensions. | |
| // Copy the largest mipmap level and mipmaps. We assume the dimensions are correct here and let the compressor validate them. | |
| if (pSource_images) | |
| { | |
| comp_params.m_source_images.resize(1); | |
| comp_params.m_source_images[0] = (*pSource_images)[0]; | |
| // Copy the smaller mipmap levels, if any | |
| if (pSource_images->size() > 1) | |
| { | |
| comp_params.m_source_mipmap_images.resize(1); | |
| comp_params.m_source_mipmap_images[0].resize(pSource_images->size() - 1); | |
| for (uint32_t i = 1; i < pSource_images->size(); i++) | |
| comp_params.m_source_mipmap_images[0][i - 1] = (*pSource_images)[i]; | |
| } | |
| } | |
| else | |
| { | |
| comp_params.m_source_images_hdr.resize(1); | |
| comp_params.m_source_images_hdr[0] = (*pSource_images_hdr)[0]; | |
| // Copy the smaller mipmap levels, if any | |
| if (pSource_images_hdr->size() > 1) | |
| { | |
| comp_params.m_source_mipmap_images_hdr.resize(1); | |
| comp_params.m_source_mipmap_images_hdr[0].resize(pSource_images_hdr->size() - 1); | |
| for (uint32_t i = 1; i < pSource_images_hdr->size(); i++) | |
| comp_params.m_source_mipmap_images_hdr[0][i - 1] = (*pSource_images_hdr)[i]; | |
| } | |
| } | |
| } | |
| comp_params.m_multithreading = (flags_and_quality & cFlagThreaded) != 0; | |
| comp_params.m_use_opencl = (flags_and_quality & cFlagUseOpenCL) != 0; | |
| comp_params.m_write_output_basis_or_ktx2_files = false; | |
| // sRGB handling - set parameters consistently. "sRGB" here also applies perceptual metrics, or the opposite of linear RGB(A) metrics. | |
| // sRGB here controls the error metrics, KTX2/.basis transfer function fields, mipmap filtering, and ASTC/XUASTC channel weights. | |
| const bool srgb_flag = (flags_and_quality & cFlagSRGB) != 0; | |
| // Use sRGB colorspace metrics, channel weights | |
| comp_params.m_perceptual = srgb_flag; | |
| // This will be written to the KTX2 DFD, .basis file header, also controls the ASTC profile decoding mode for ASTC LDR 4x4 - 12x12 and XUASTC LDR 4x4 - 12x12. | |
| comp_params.m_ktx2_and_basis_srgb_transfer_function = srgb_flag; | |
| // Correct for sRGB transfer function during mipmapping | |
| comp_params.m_mip_srgb = srgb_flag; | |
| // Set linear or Rec 709 weights (we assume Rec 709 weights if content is sRGB) - ASTC/XUASTC/XUBC7 | |
| comp_params.set_ldr_srgb_channel_weights(srgb_flag); | |
| comp_params.m_mip_gen = (flags_and_quality & (cFlagGenMipsWrap | cFlagGenMipsClamp)) != 0; | |
| comp_params.m_mip_wrapping = (flags_and_quality & cFlagGenMipsWrap) != 0; | |
| if (mode == basist::basis_tex_format::cUASTC_LDR_4x4) | |
| { | |
| // Set pack level from flags | |
| comp_params.m_pack_uastc_ldr_4x4_flags = flags_and_quality & cPackUASTCLevelMask; | |
| // Now optionally enable UASTC LDR 4x4 RDO. | |
| // We used to look at the (flags_and_quality & cFlagUASTCRDO) != 0; flag to determine if we'll be using RDO here. | |
| // The flag isn't necessary, we'll now just examine uastc_rdo_or_dct_quality and decide to enable it. | |
| if (uastc_rdo_or_dct_quality > 0.0f) | |
| { | |
| comp_params.m_rdo_uastc_ldr_4x4 = true; | |
| comp_params.m_rdo_uastc_ldr_4x4_quality_scalar = uastc_rdo_or_dct_quality; | |
| } | |
| } | |
| else if (mode == basist::basis_tex_format::cETC1S) | |
| { | |
| // Set ETC1S quality level (codebook sizes) from flags. | |
| comp_params.m_quality_level = basisu::maximum<uint32_t>(1, flags_and_quality & 255); | |
| } | |
| else if (basist::basis_tex_format_is_xuastc_ldr(mode) || basist::basis_tex_format_is_astc_ldr(mode)) | |
| { | |
| // Set ASTC LDR/UASTC LDR 4x4-12x12 effort level | |
| comp_params.m_xuastc_ldr_effort_level = flags_and_quality & 255; | |
| // Optionally enable weight grid DCT for XUASTC. | |
| // Valid XUASTC LDR weight grid DCT quality levels are 1-100. | |
| if (basist::basis_tex_format_is_xuastc_ldr(mode) && (uastc_rdo_or_dct_quality != 0.0f)) | |
| { | |
| // [1,99]=DCT, 100=no DCT | |
| if ((uastc_rdo_or_dct_quality >= (float)BASISU_XUASTC_QUALITY_MIN) && (uastc_rdo_or_dct_quality <= (float)BASISU_XUASTC_QUALITY_MAX)) | |
| { | |
| if (uastc_rdo_or_dct_quality < (float)BASISU_XUASTC_QUALITY_MAX) | |
| { | |
| // Enable weight grid DCT usage, set quality level. | |
| comp_params.m_xuastc_ldr_use_dct = true; | |
| comp_params.m_quality_level = (int)uastc_rdo_or_dct_quality; | |
| // Also enable bounded lossy distortion mode in the normally lossless supercompressor for extra savings. | |
| comp_params.m_xuastc_ldr_use_lossy_supercompression = true; | |
| } | |
| } | |
| else | |
| { | |
| // Invalid quality level | |
| assert(0); | |
| return nullptr; | |
| } | |
| } | |
| if (basist::basis_tex_format_is_xuastc_ldr(mode)) | |
| { | |
| // Set XUASTC LDR syntax | |
| comp_params.m_xuastc_ldr_syntax = (flags_and_quality >> cFlagXUASTCLDRSyntaxShift) & cFlagXUASTCLDRSyntaxMask; | |
| if (comp_params.m_xuastc_ldr_syntax >= (int)basist::astc_ldr_t::xuastc_ldr_syntax::cTotal) | |
| { | |
| error_printf("basis_compress: basis_compressor::init() failed - invalid XUASTC LDR syntax\n"); | |
| return nullptr; | |
| } | |
| } | |
| } | |
| else if (basist::basis_tex_format_is_xubc7(mode)) | |
| { | |
| // XUBC7: configure effort and quality level | |
| comp_params.m_xubc7_effort_level = clamp<int>(flags_and_quality & 255, 0, 10); | |
| if (uastc_rdo_or_dct_quality != 0.0f) | |
| { | |
| int quality = clamp<int>((int)std::round(uastc_rdo_or_dct_quality), 1, 100); | |
| comp_params.m_quality_level = quality; | |
| } | |
| // BC7 base encoder selection (packed 3-bit field in the high flag bits): | |
| // 0 = bc7f (the default fast packer); 1-7 = bc7e_scalar at level 0-6. | |
| const uint32_t xubc7_base_enc = (flags_and_quality >> cFlagXUBC7BaseEncoderShift) & cFlagXUBC7BaseEncoderMask; | |
| if (xubc7_base_enc == 0) | |
| { | |
| comp_params.m_xubc7_encoder = (int)xbc7::bc7_encoder_type::cBC7F; | |
| } | |
| else | |
| { | |
| comp_params.m_xubc7_encoder = (int)xbc7::bc7_encoder_type::cBC7E_Scalar; | |
| comp_params.m_xubc7_bc7e_scalar_level = clamp<int>((int)xubc7_base_enc - 1, (int)xbc7::BC7E_SCALAR_MIN_LEVEL, (int)xbc7::BC7E_SCALAR_MAX_LEVEL); | |
| } | |
| } | |
| comp_params.m_create_ktx2_file = (flags_and_quality & cFlagKTX2) != 0; | |
| if (comp_params.m_create_ktx2_file) | |
| { | |
| // Set KTX2 specific parameters. | |
| if ((flags_and_quality & cFlagKTX2UASTCSuperCompression) && (comp_params.m_uastc)) | |
| comp_params.m_ktx2_uastc_supercompression = basist::KTX2_SS_ZSTANDARD; | |
| } | |
| comp_params.m_compute_stats = (pStats != nullptr); | |
| if (comp_params.m_compute_stats) | |
| comp_params.m_psnr_hvs_m_stats = true; // TODO: should put this behind a flag, it's not very fast | |
| comp_params.m_print_stats = (flags_and_quality & cFlagPrintStats) != 0; | |
| comp_params.m_status_output = (flags_and_quality & cFlagPrintStatus) != 0; | |
| if (mode == basist::basis_tex_format::cUASTC_HDR_4x4) | |
| { | |
| // Set UASTC HDR 4x4 effort level | |
| comp_params.m_uastc_hdr_4x4_options.set_quality_level(flags_and_quality & cPackUASTCLevelMask); | |
| } | |
| else if ((mode == basist::basis_tex_format::cASTC_HDR_6x6) || (mode == basist::basis_tex_format::cUASTC_HDR_6x6_INTERMEDIATE)) | |
| { | |
| // Set ASTC HDR 6x6/UASTC HDR 6x6 effort level | |
| comp_params.m_astc_hdr_6x6_options.set_user_level(flags_and_quality & cPackUASTCLevelMask); | |
| // Set lambda (rate-distortion tradeoff) | |
| comp_params.m_astc_hdr_6x6_options.m_lambda = uastc_rdo_or_dct_quality; | |
| } | |
| // TODO: REC2020 isn't specific to HDR 6x6 anymore, it's always used for KTX2 files. | |
| // This will be written to the KTX2 DFD. | |
| comp_params.m_astc_hdr_6x6_options.m_rec2020_bt2100_color_gamut = (flags_and_quality & cFlagREC2020) != 0; | |
| comp_params.m_validate_output_data = (flags_and_quality & cFlagValidateOutput) != 0; | |
| // The defaults are to use deblocking/sharpening on larger block sizes, unless the user disables them. | |
| // More finer grain control could be useful but would need more flags/config. | |
| if (flags_and_quality & cFlagDisableDeblocking) | |
| comp_params.m_xuastc_ldr_deblocking_mode = (int)xuastc_ldr_deblocking_mode::cDisabled; | |
| else if (flags_and_quality & cFlagForceDeblocking) | |
| comp_params.m_xuastc_ldr_deblocking_mode = (int)xuastc_ldr_deblocking_mode::cUseSCDAndFilteringAllBlockSizes; | |
| //if (flags_and_quality & cFlagDisableSharpening) | |
| // comp_params.m_xuastc_ldr_sharpen_mode = (int)xuastc_ldr_sharpen_mode::cDisabled; | |
| // Now set the unified quality/effort level, if they've specified it. | |
| // This will override some of the lower-level options set above, or leave them alone if -1. | |
| if ((quality_level != -1) || (effort_level != -1)) | |
| { | |
| comp_params.set_format_mode_and_quality_effort(mode, quality_level, effort_level, false); | |
| } | |
| // Create the compressor, initialize it, and process the input | |
| basis_compressor comp; | |
| if (!comp.init(comp_params)) | |
| { | |
| error_printf("basis_compress: basis_compressor::init() failed!\n"); | |
| return nullptr; | |
| } | |
| basis_compressor::error_code ec = comp.process(); | |
| if (ec != basis_compressor::cECSuccess) | |
| { | |
| error_printf("basis_compress: basis_compressor::process() failed with error code %u\n", (uint32_t)ec); | |
| return nullptr; | |
| } | |
| if ((pStats) && (comp.get_opencl_failed())) | |
| { | |
| pStats->m_opencl_failed = true; | |
| } | |
| // Get the output file data and return it to the caller | |
| void* pFile_data = nullptr; | |
| const uint8_vec* pFile_data_vec = comp_params.m_create_ktx2_file ? &comp.get_output_ktx2_file() : &comp.get_output_basis_file(); | |
| pFile_data = malloc(pFile_data_vec->size()); | |
| if (!pFile_data) | |
| { | |
| error_printf("basis_compress: Out of memory\n"); | |
| return nullptr; | |
| } | |
| memcpy(pFile_data, pFile_data_vec->get_ptr(), pFile_data_vec->size()); | |
| *pSize = pFile_data_vec->size(); | |
| if ((pStats) && (comp.get_stats().size())) | |
| { | |
| *pStats = comp.get_stats()[0]; | |
| } | |
| return pFile_data; | |
| } | |
| void* basis_compress( | |
| basist::basis_tex_format mode, | |
| const basisu::vector<image>& source_images, | |
| uint32_t flags_and_quality, float uastc_rdo_or_dct_quality, | |
| size_t* pSize, | |
| image_stats* pStats) | |
| { | |
| return basis_compress_internal(mode, &source_images, nullptr, flags_and_quality, uastc_rdo_or_dct_quality, pSize, pStats, -1, -1); | |
| } | |
| void* basis_compress2( | |
| basist::basis_tex_format mode, | |
| const basisu::vector<image>& source_images, | |
| uint32_t flags_and_quality, int quality_level, int effort_level, | |
| size_t* pSize, | |
| image_stats* pStats) | |
| { | |
| return basis_compress_internal(mode, &source_images, nullptr, flags_and_quality, 0.0f, pSize, pStats, quality_level, effort_level); | |
| } | |
| void* basis_compress( | |
| basist::basis_tex_format mode, | |
| const basisu::vector<imagef>& source_images_hdr, | |
| uint32_t flags_and_quality, float uastc_rdo_or_dct_quality, | |
| size_t* pSize, | |
| image_stats* pStats) | |
| { | |
| return basis_compress_internal(mode, nullptr, &source_images_hdr, flags_and_quality, uastc_rdo_or_dct_quality, pSize, pStats, -1, -1); | |
| } | |
| void* basis_compress2( | |
| basist::basis_tex_format mode, | |
| const basisu::vector<imagef>& source_images_hdr, | |
| uint32_t flags_and_quality, int quality_level, int effort_level, | |
| size_t* pSize, | |
| image_stats* pStats) | |
| { | |
| return basis_compress_internal(mode, nullptr, &source_images_hdr, flags_and_quality, 0.0f, pSize, pStats, quality_level, effort_level); | |
| } | |
| void* basis_compress( | |
| basist::basis_tex_format mode, | |
| const uint8_t* pImageRGBA, uint32_t width, uint32_t height, uint32_t pitch_in_pixels, | |
| uint32_t flags_and_quality, float uastc_rdo_or_dct_quality, | |
| size_t* pSize, | |
| image_stats* pStats) | |
| { | |
| if (!pitch_in_pixels) | |
| pitch_in_pixels = width; | |
| if ((!pImageRGBA) || (!width) || (!height) || (pitch_in_pixels < width) || (!pSize)) | |
| { | |
| error_printf("basis_compress: Invalid parameter\n"); | |
| assert(0); | |
| return nullptr; | |
| } | |
| *pSize = 0; | |
| if ((width > basist::BASISU_MAX_SUPPORTED_TEXTURE_DIMENSION) || (height > basist::BASISU_MAX_SUPPORTED_TEXTURE_DIMENSION)) | |
| { | |
| error_printf("basis_compress: Image too large\n"); | |
| return nullptr; | |
| } | |
| // Copy the source image | |
| basisu::vector<image> source_image(1); | |
| source_image[0].crop(width, height, width, g_black_color, false); | |
| for (uint32_t y = 0; y < height; y++) | |
| memcpy(source_image[0].get_ptr() + y * width, (const color_rgba*)pImageRGBA + y * pitch_in_pixels, width * sizeof(color_rgba)); | |
| return basis_compress(mode, source_image, flags_and_quality, uastc_rdo_or_dct_quality, pSize, pStats); | |
| } | |
| void* basis_compress2( | |
| basist::basis_tex_format mode, | |
| const uint8_t* pImageRGBA, uint32_t width, uint32_t height, uint32_t pitch_in_pixels, | |
| uint32_t flags_and_quality, int quality_level, int effort_level, | |
| size_t* pSize, | |
| image_stats* pStats) | |
| { | |
| if (!pitch_in_pixels) | |
| pitch_in_pixels = width; | |
| if ((!pImageRGBA) || (!width) || (!height) || (pitch_in_pixels < width) || (!pSize)) | |
| { | |
| error_printf("basis_compress: Invalid parameter\n"); | |
| assert(0); | |
| return nullptr; | |
| } | |
| *pSize = 0; | |
| if ((width > basist::BASISU_MAX_SUPPORTED_TEXTURE_DIMENSION) || (height > basist::BASISU_MAX_SUPPORTED_TEXTURE_DIMENSION)) | |
| { | |
| error_printf("basis_compress: Image too large\n"); | |
| return nullptr; | |
| } | |
| // Copy the source image | |
| basisu::vector<image> source_image(1); | |
| source_image[0].crop(width, height, width, g_black_color, false); | |
| for (uint32_t y = 0; y < height; y++) | |
| memcpy(source_image[0].get_ptr() + y * width, (const color_rgba*)pImageRGBA + y * pitch_in_pixels, width * sizeof(color_rgba)); | |
| return basis_compress2(mode, source_image, flags_and_quality, quality_level, effort_level, pSize, pStats); | |
| } | |
| void basis_free_data(void* p) | |
| { | |
| free(p); | |
| } | |
| bool basis_benchmark_etc1s_opencl(bool* pOpenCL_failed) | |
| { | |
| if (pOpenCL_failed) | |
| *pOpenCL_failed = false; | |
| if (!opencl_is_available()) | |
| { | |
| error_printf("basis_benchmark_etc1s_opencl: OpenCL support must be enabled first!\n"); | |
| return false; | |
| } | |
| const uint32_t W = 1024, H = 1024; | |
| basisu::vector<image> images; | |
| image& img = images.enlarge(1)->resize(W, H); | |
| const uint32_t NUM_RAND_LETTERS = 6000;// 40000; | |
| rand r; | |
| r.seed(200); | |
| for (uint32_t i = 0; i < NUM_RAND_LETTERS; i++) | |
| { | |
| uint32_t x = r.irand(0, W - 1), y = r.irand(0, H - 1); | |
| uint32_t sx = r.irand(1, 4), sy = r.irand(1, 4); | |
| color_rgba c(r.byte(), r.byte(), r.byte(), 255); | |
| img.debug_text(x, y, sx, sy, c, nullptr, false, "%c", static_cast<char>(r.irand(32, 127))); | |
| } | |
| //save_png("test.png", img); | |
| image_stats stats; | |
| uint32_t flags_and_quality = cFlagSRGB | cFlagThreaded | 255; | |
| size_t comp_size = 0; | |
| double best_cpu_time = 1e+9f, best_gpu_time = 1e+9f; | |
| const uint32_t TIMES_TO_ENCODE = 2; | |
| interval_timer tm; | |
| for (uint32_t i = 0; i < TIMES_TO_ENCODE; i++) | |
| { | |
| tm.start(); | |
| void* pComp_data = basis_compress( | |
| basist::basis_tex_format::cETC1S, | |
| images, | |
| flags_and_quality, 1.0f, | |
| &comp_size, | |
| &stats); | |
| double cpu_time = tm.get_elapsed_secs(); | |
| if (!pComp_data) | |
| { | |
| error_printf("basis_benchmark_etc1s_opencl: basis_compress() failed (CPU)!\n"); | |
| return false; | |
| } | |
| best_cpu_time = minimum(best_cpu_time, cpu_time); | |
| basis_free_data(pComp_data); | |
| } | |
| printf("Best CPU time: %3.3f\n", best_cpu_time); | |
| for (uint32_t i = 0; i < TIMES_TO_ENCODE; i++) | |
| { | |
| tm.start(); | |
| void* pComp_data = basis_compress( | |
| basist::basis_tex_format::cETC1S, | |
| images, | |
| flags_and_quality | cFlagUseOpenCL, 1.0f, | |
| &comp_size, | |
| &stats); | |
| if (stats.m_opencl_failed) | |
| { | |
| error_printf("basis_benchmark_etc1s_opencl: OpenCL failed!\n"); | |
| basis_free_data(pComp_data); | |
| if (pOpenCL_failed) | |
| *pOpenCL_failed = true; | |
| return false; | |
| } | |
| double gpu_time = tm.get_elapsed_secs(); | |
| if (!pComp_data) | |
| { | |
| error_printf("basis_benchmark_etc1s_opencl: basis_compress() failed (GPU)!\n"); | |
| return false; | |
| } | |
| best_gpu_time = minimum(best_gpu_time, gpu_time); | |
| basis_free_data(pComp_data); | |
| } | |
| printf("Best GPU time: %3.3f\n", best_gpu_time); | |
| return best_gpu_time < best_cpu_time; | |
| } | |
| } // namespace basisu | |