Path: blob/master/thirdparty/astcenc/astcenc_entry.cpp
9902 views
// SPDX-License-Identifier: Apache-2.01// ----------------------------------------------------------------------------2// Copyright 2011-2025 Arm Limited3//4// Licensed under the Apache License, Version 2.0 (the "License"); you may not5// use this file except in compliance with the License. You may obtain a copy6// of the License at:7//8// http://www.apache.org/licenses/LICENSE-2.09//10// Unless required by applicable law or agreed to in writing, software11// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT12// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the13// License for the specific language governing permissions and limitations14// under the License.15// ----------------------------------------------------------------------------1617/**18* @brief Functions for the library entrypoint.19*/2021#include <array>22#include <cstring>23#include <new>2425#include "astcenc.h"26#include "astcenc_internal_entry.h"27#include "astcenc_diagnostic_trace.h"2829/**30* @brief Record of the quality tuning parameter values.31*32* See the @c astcenc_config structure for detailed parameter documentation.33*34* Note that the mse_overshoot entries are scaling factors relative to the base MSE to hit db_limit.35* A 20% overshoot is harder to hit for a higher base db_limit, so we may actually use lower ratios36* for the more through search presets because the underlying db_limit is so much higher.37*/38struct astcenc_preset_config39{40float quality;41unsigned int tune_partition_count_limit;42unsigned int tune_2partition_index_limit;43unsigned int tune_3partition_index_limit;44unsigned int tune_4partition_index_limit;45unsigned int tune_block_mode_limit;46unsigned int tune_refinement_limit;47unsigned int tune_candidate_limit;48unsigned int tune_2partitioning_candidate_limit;49unsigned int tune_3partitioning_candidate_limit;50unsigned int tune_4partitioning_candidate_limit;51float tune_db_limit_a_base;52float tune_db_limit_b_base;53float tune_mse_overshoot;54float tune_2partition_early_out_limit_factor;55float tune_3partition_early_out_limit_factor;56float tune_2plane_early_out_limit_correlation;57float tune_search_mode0_enable;58};5960/**61* @brief The static presets for high bandwidth encodings (x < 25 texels per block).62*/63static const std::array<astcenc_preset_config, 6> preset_configs_high {{64{65ASTCENC_PRE_FASTEST,662, 10, 6, 4, 43, 2, 2, 2, 2, 2, 85.2f, 63.2f, 3.5f, 1.0f, 1.0f, 0.85f, 0.0f67}, {68ASTCENC_PRE_FAST,693, 18, 10, 8, 55, 3, 3, 2, 2, 2, 85.2f, 63.2f, 3.5f, 1.0f, 1.0f, 0.90f, 0.0f70}, {71ASTCENC_PRE_MEDIUM,724, 34, 28, 16, 77, 3, 3, 2, 2, 2, 95.0f, 70.0f, 2.5f, 1.1f, 1.05f, 0.95f, 0.0f73}, {74ASTCENC_PRE_THOROUGH,754, 82, 60, 30, 94, 4, 4, 3, 2, 2, 105.0f, 77.0f, 10.0f, 1.35f, 1.15f, 0.97f, 0.0f76}, {77ASTCENC_PRE_VERYTHOROUGH,784, 256, 128, 64, 98, 4, 6, 8, 6, 4, 200.0f, 200.0f, 10.0f, 1.6f, 1.4f, 0.98f, 0.0f79}, {80ASTCENC_PRE_EXHAUSTIVE,814, 512, 512, 512, 100, 4, 8, 8, 8, 8, 200.0f, 200.0f, 10.0f, 2.0f, 2.0f, 0.99f, 0.0f82}83}};8485/**86* @brief The static presets for medium bandwidth encodings (25 <= x < 64 texels per block).87*/88static const std::array<astcenc_preset_config, 6> preset_configs_mid {{89{90ASTCENC_PRE_FASTEST,912, 10, 6, 4, 43, 2, 2, 2, 2, 2, 85.2f, 63.2f, 3.5f, 1.0f, 1.0f, 0.80f, 1.0f92}, {93ASTCENC_PRE_FAST,943, 18, 12, 10, 55, 3, 3, 2, 2, 2, 85.2f, 63.2f, 3.5f, 1.0f, 1.0f, 0.85f, 1.0f95}, {96ASTCENC_PRE_MEDIUM,973, 34, 28, 16, 77, 3, 3, 2, 2, 2, 95.0f, 70.0f, 3.0f, 1.1f, 1.05f, 0.90f, 1.0f98}, {99ASTCENC_PRE_THOROUGH,1004, 82, 60, 30, 94, 4, 4, 3, 2, 2, 105.0f, 77.0f, 10.0f, 1.4f, 1.2f, 0.95f, 0.0f101}, {102ASTCENC_PRE_VERYTHOROUGH,1034, 256, 128, 64, 98, 4, 6, 8, 6, 3, 200.0f, 200.0f, 10.0f, 1.6f, 1.4f, 0.98f, 0.0f104}, {105ASTCENC_PRE_EXHAUSTIVE,1064, 256, 256, 256, 100, 4, 8, 8, 8, 8, 200.0f, 200.0f, 10.0f, 2.0f, 2.0f, 0.99f, 0.0f107}108}};109110/**111* @brief The static presets for low bandwidth encodings (64 <= x texels per block).112*/113static const std::array<astcenc_preset_config, 6> preset_configs_low {{114{115ASTCENC_PRE_FASTEST,1162, 10, 6, 4, 40, 2, 2, 2, 2, 2, 85.0f, 63.0f, 3.5f, 1.0f, 1.0f, 0.80f, 1.0f117}, {118ASTCENC_PRE_FAST,1192, 18, 12, 10, 55, 3, 3, 2, 2, 2, 85.0f, 63.0f, 3.5f, 1.0f, 1.0f, 0.85f, 1.0f120}, {121ASTCENC_PRE_MEDIUM,1223, 34, 28, 16, 77, 3, 3, 2, 2, 2, 95.0f, 70.0f, 3.5f, 1.1f, 1.05f, 0.90f, 1.0f123}, {124ASTCENC_PRE_THOROUGH,1254, 82, 60, 30, 93, 4, 4, 3, 2, 2, 105.0f, 77.0f, 10.0f, 1.3f, 1.2f, 0.97f, 1.0f126}, {127ASTCENC_PRE_VERYTHOROUGH,1284, 256, 128, 64, 98, 4, 6, 8, 5, 2, 200.0f, 200.0f, 10.0f, 1.6f, 1.4f, 0.98f, 1.0f129}, {130ASTCENC_PRE_EXHAUSTIVE,1314, 256, 256, 256, 100, 4, 8, 8, 8, 8, 200.0f, 200.0f, 10.0f, 2.0f, 2.0f, 0.99f, 1.0f132}133}};134135/**136* @brief Validate CPU floating point meets assumptions made in the codec.137*138* The codec is written with the assumption that a float threaded through the @c if32 union will be139* stored and reloaded as a 32-bit IEEE-754 float with round-to-nearest rounding. This is always the140* case in an IEEE-754 compliant system, however not every system or compilation mode is actually141* IEEE-754 compliant. This normally fails if the code is compiled with fast math enabled.142*143* @return Return @c ASTCENC_SUCCESS if validated, otherwise an error on failure.144*/145static astcenc_error validate_cpu_float()146{147if32 p;148volatile float xprec_testval = 2.51f;149p.f = xprec_testval + 12582912.0f;150float q = p.f - 12582912.0f;151152if (q != 3.0f)153{154return ASTCENC_ERR_BAD_CPU_FLOAT;155}156157return ASTCENC_SUCCESS;158}159160/**161* @brief Validate config profile.162*163* @param profile The profile to check.164*165* @return Return @c ASTCENC_SUCCESS if validated, otherwise an error on failure.166*/167static astcenc_error validate_profile(168astcenc_profile profile169) {170// Values in this enum are from an external user, so not guaranteed to be171// bounded to the enum values172switch (static_cast<int>(profile))173{174case ASTCENC_PRF_LDR_SRGB:175case ASTCENC_PRF_LDR:176case ASTCENC_PRF_HDR_RGB_LDR_A:177case ASTCENC_PRF_HDR:178return ASTCENC_SUCCESS;179default:180return ASTCENC_ERR_BAD_PROFILE;181}182}183184/**185* @brief Validate block size.186*187* @param block_x The block x dimensions.188* @param block_y The block y dimensions.189* @param block_z The block z dimensions.190*191* @return Return @c ASTCENC_SUCCESS if validated, otherwise an error on failure.192*/193static astcenc_error validate_block_size(194unsigned int block_x,195unsigned int block_y,196unsigned int block_z197) {198// Test if this is a legal block size at all199bool is_legal = (((block_z <= 1) && is_legal_2d_block_size(block_x, block_y)) ||200((block_z >= 2) && is_legal_3d_block_size(block_x, block_y, block_z)));201if (!is_legal)202{203return ASTCENC_ERR_BAD_BLOCK_SIZE;204}205206// Test if this build has sufficient capacity for this block size207bool have_capacity = (block_x * block_y * block_z) <= BLOCK_MAX_TEXELS;208if (!have_capacity)209{210return ASTCENC_ERR_NOT_IMPLEMENTED;211}212213return ASTCENC_SUCCESS;214}215216/**217* @brief Validate flags.218*219* @param profile The profile to check.220* @param flags The flags to check.221*222* @return Return @c ASTCENC_SUCCESS if validated, otherwise an error on failure.223*/224static astcenc_error validate_flags(225astcenc_profile profile,226unsigned int flags227) {228// Flags field must not contain any unknown flag bits229unsigned int exMask = ~ASTCENC_ALL_FLAGS;230if (popcount(flags & exMask) != 0)231{232return ASTCENC_ERR_BAD_FLAGS;233}234235// Flags field must only contain at most a single map type236exMask = ASTCENC_FLG_MAP_NORMAL237| ASTCENC_FLG_MAP_RGBM;238if (popcount(flags & exMask) > 1)239{240return ASTCENC_ERR_BAD_FLAGS;241}242243// Decode_unorm8 must only be used with an LDR profile244bool is_unorm8 = flags & ASTCENC_FLG_USE_DECODE_UNORM8;245bool is_hdr = (profile == ASTCENC_PRF_HDR) || (profile == ASTCENC_PRF_HDR_RGB_LDR_A);246if (is_unorm8 && is_hdr)247{248return ASTCENC_ERR_BAD_DECODE_MODE;249}250251return ASTCENC_SUCCESS;252}253254#if !defined(ASTCENC_DECOMPRESS_ONLY)255256/**257* @brief Validate single channel compression swizzle.258*259* @param swizzle The swizzle to check.260*261* @return Return @c ASTCENC_SUCCESS if validated, otherwise an error on failure.262*/263static astcenc_error validate_compression_swz(264astcenc_swz swizzle265) {266// Not all enum values are handled; SWZ_Z is invalid for compression267switch (static_cast<int>(swizzle))268{269case ASTCENC_SWZ_R:270case ASTCENC_SWZ_G:271case ASTCENC_SWZ_B:272case ASTCENC_SWZ_A:273case ASTCENC_SWZ_0:274case ASTCENC_SWZ_1:275return ASTCENC_SUCCESS;276default:277return ASTCENC_ERR_BAD_SWIZZLE;278}279}280281/**282* @brief Validate overall compression swizzle.283*284* @param swizzle The swizzle to check.285*286* @return Return @c ASTCENC_SUCCESS if validated, otherwise an error on failure.287*/288static astcenc_error validate_compression_swizzle(289const astcenc_swizzle& swizzle290) {291if (validate_compression_swz(swizzle.r) ||292validate_compression_swz(swizzle.g) ||293validate_compression_swz(swizzle.b) ||294validate_compression_swz(swizzle.a))295{296return ASTCENC_ERR_BAD_SWIZZLE;297}298299return ASTCENC_SUCCESS;300}301#endif302303/**304* @brief Validate single channel decompression swizzle.305*306* @param swizzle The swizzle to check.307*308* @return Return @c ASTCENC_SUCCESS if validated, otherwise an error on failure.309*/310static astcenc_error validate_decompression_swz(311astcenc_swz swizzle312) {313// Values in this enum are from an external user, so not guaranteed to be314// bounded to the enum values315switch (static_cast<int>(swizzle))316{317case ASTCENC_SWZ_R:318case ASTCENC_SWZ_G:319case ASTCENC_SWZ_B:320case ASTCENC_SWZ_A:321case ASTCENC_SWZ_0:322case ASTCENC_SWZ_1:323case ASTCENC_SWZ_Z:324return ASTCENC_SUCCESS;325default:326return ASTCENC_ERR_BAD_SWIZZLE;327}328}329330/**331* @brief Validate overall decompression swizzle.332*333* @param swizzle The swizzle to check.334*335* @return Return @c ASTCENC_SUCCESS if validated, otherwise an error on failure.336*/337static astcenc_error validate_decompression_swizzle(338const astcenc_swizzle& swizzle339) {340if (validate_decompression_swz(swizzle.r) ||341validate_decompression_swz(swizzle.g) ||342validate_decompression_swz(swizzle.b) ||343validate_decompression_swz(swizzle.a))344{345return ASTCENC_ERR_BAD_SWIZZLE;346}347348return ASTCENC_SUCCESS;349}350351/**352* Validate that an incoming configuration is in-spec.353*354* This function can respond in two ways:355*356* * Numerical inputs that have valid ranges are clamped to those valid ranges. No error is thrown357* for out-of-range inputs in this case.358* * Numerical inputs and logic inputs are are logically invalid and which make no sense359* algorithmically will return an error.360*361* @param[in,out] config The input compressor configuration.362*363* @return Return @c ASTCENC_SUCCESS if validated, otherwise an error on failure.364*/365static astcenc_error validate_config(366astcenc_config &config367) {368astcenc_error status;369370status = validate_profile(config.profile);371if (status != ASTCENC_SUCCESS)372{373return status;374}375376status = validate_flags(config.profile, config.flags);377if (status != ASTCENC_SUCCESS)378{379return status;380}381382status = validate_block_size(config.block_x, config.block_y, config.block_z);383if (status != ASTCENC_SUCCESS)384{385return status;386}387388#if defined(ASTCENC_DECOMPRESS_ONLY)389// Decompress-only builds only support decompress-only contexts390if (!(config.flags & ASTCENC_FLG_DECOMPRESS_ONLY))391{392return ASTCENC_ERR_BAD_PARAM;393}394#endif395396config.rgbm_m_scale = astc::max(config.rgbm_m_scale, 1.0f);397398config.tune_partition_count_limit = astc::clamp(config.tune_partition_count_limit, 1u, 4u);399config.tune_2partition_index_limit = astc::clamp(config.tune_2partition_index_limit, 1u, BLOCK_MAX_PARTITIONINGS);400config.tune_3partition_index_limit = astc::clamp(config.tune_3partition_index_limit, 1u, BLOCK_MAX_PARTITIONINGS);401config.tune_4partition_index_limit = astc::clamp(config.tune_4partition_index_limit, 1u, BLOCK_MAX_PARTITIONINGS);402config.tune_block_mode_limit = astc::clamp(config.tune_block_mode_limit, 1u, 100u);403config.tune_refinement_limit = astc::max(config.tune_refinement_limit, 1u);404config.tune_candidate_limit = astc::clamp(config.tune_candidate_limit, 1u, TUNE_MAX_TRIAL_CANDIDATES);405config.tune_2partitioning_candidate_limit = astc::clamp(config.tune_2partitioning_candidate_limit, 1u, TUNE_MAX_PARTITIONING_CANDIDATES);406config.tune_3partitioning_candidate_limit = astc::clamp(config.tune_3partitioning_candidate_limit, 1u, TUNE_MAX_PARTITIONING_CANDIDATES);407config.tune_4partitioning_candidate_limit = astc::clamp(config.tune_4partitioning_candidate_limit, 1u, TUNE_MAX_PARTITIONING_CANDIDATES);408config.tune_db_limit = astc::max(config.tune_db_limit, 0.0f);409config.tune_mse_overshoot = astc::max(config.tune_mse_overshoot, 1.0f);410config.tune_2partition_early_out_limit_factor = astc::max(config.tune_2partition_early_out_limit_factor, 0.0f);411config.tune_3partition_early_out_limit_factor = astc::max(config.tune_3partition_early_out_limit_factor, 0.0f);412config.tune_2plane_early_out_limit_correlation = astc::max(config.tune_2plane_early_out_limit_correlation, 0.0f);413414// Specifying a zero weight color component is not allowed; force to small value415float max_weight = astc::max(astc::max(config.cw_r_weight, config.cw_g_weight),416astc::max(config.cw_b_weight, config.cw_a_weight));417if (max_weight > 0.0f)418{419max_weight /= 1000.0f;420config.cw_r_weight = astc::max(config.cw_r_weight, max_weight);421config.cw_g_weight = astc::max(config.cw_g_weight, max_weight);422config.cw_b_weight = astc::max(config.cw_b_weight, max_weight);423config.cw_a_weight = astc::max(config.cw_a_weight, max_weight);424}425// If all color components error weights are zero then return an error426else427{428return ASTCENC_ERR_BAD_PARAM;429}430431return ASTCENC_SUCCESS;432}433434/* See header for documentation. */435astcenc_error astcenc_config_init(436astcenc_profile profile,437unsigned int block_x,438unsigned int block_y,439unsigned int block_z,440float quality,441unsigned int flags,442astcenc_config* configp443) {444astcenc_error status;445446status = validate_cpu_float();447if (status != ASTCENC_SUCCESS)448{449return status;450}451452// Zero init all config fields; although most of will be over written453astcenc_config& config = *configp;454std::memset(&config, 0, sizeof(config));455456// Process the block size457block_z = astc::max(block_z, 1u); // For 2D blocks Z==0 is accepted, but convert to 1458status = validate_block_size(block_x, block_y, block_z);459if (status != ASTCENC_SUCCESS)460{461return status;462}463464config.block_x = block_x;465config.block_y = block_y;466config.block_z = block_z;467468float texels = static_cast<float>(block_x * block_y * block_z);469float ltexels = logf(texels) / logf(10.0f);470471// Process the performance quality level or preset; note that this must be done before we472// process any additional settings, such as color profile and flags, which may replace some of473// these settings with more use case tuned values474if (quality < ASTCENC_PRE_FASTEST ||475quality > ASTCENC_PRE_EXHAUSTIVE)476{477return ASTCENC_ERR_BAD_QUALITY;478}479480static const std::array<astcenc_preset_config, 6>* preset_configs;481int texels_int = block_x * block_y * block_z;482if (texels_int < 25)483{484preset_configs = &preset_configs_high;485}486else if (texels_int < 64)487{488preset_configs = &preset_configs_mid;489}490else491{492preset_configs = &preset_configs_low;493}494495// Determine which preset to use, or which pair to interpolate496size_t start;497size_t end;498for (end = 0; end < preset_configs->size(); end++)499{500if ((*preset_configs)[end].quality >= quality)501{502break;503}504}505506start = end == 0 ? 0 : end - 1;507508// Start and end node are the same - so just transfer the values.509if (start == end)510{511config.tune_partition_count_limit = (*preset_configs)[start].tune_partition_count_limit;512config.tune_2partition_index_limit = (*preset_configs)[start].tune_2partition_index_limit;513config.tune_3partition_index_limit = (*preset_configs)[start].tune_3partition_index_limit;514config.tune_4partition_index_limit = (*preset_configs)[start].tune_4partition_index_limit;515config.tune_block_mode_limit = (*preset_configs)[start].tune_block_mode_limit;516config.tune_refinement_limit = (*preset_configs)[start].tune_refinement_limit;517config.tune_candidate_limit = (*preset_configs)[start].tune_candidate_limit;518config.tune_2partitioning_candidate_limit = (*preset_configs)[start].tune_2partitioning_candidate_limit;519config.tune_3partitioning_candidate_limit = (*preset_configs)[start].tune_3partitioning_candidate_limit;520config.tune_4partitioning_candidate_limit = (*preset_configs)[start].tune_4partitioning_candidate_limit;521config.tune_db_limit = astc::max((*preset_configs)[start].tune_db_limit_a_base - 35 * ltexels,522(*preset_configs)[start].tune_db_limit_b_base - 19 * ltexels);523524config.tune_mse_overshoot = (*preset_configs)[start].tune_mse_overshoot;525526config.tune_2partition_early_out_limit_factor = (*preset_configs)[start].tune_2partition_early_out_limit_factor;527config.tune_3partition_early_out_limit_factor = (*preset_configs)[start].tune_3partition_early_out_limit_factor;528config.tune_2plane_early_out_limit_correlation = (*preset_configs)[start].tune_2plane_early_out_limit_correlation;529config.tune_search_mode0_enable = (*preset_configs)[start].tune_search_mode0_enable;530}531// Start and end node are not the same - so interpolate between them532else533{534auto& node_a = (*preset_configs)[start];535auto& node_b = (*preset_configs)[end];536537float wt_range = node_b.quality - node_a.quality;538assert(wt_range > 0);539540// Compute interpolation factors541float wt_node_a = (node_b.quality - quality) / wt_range;542float wt_node_b = (quality - node_a.quality) / wt_range;543544#define LERP(param) ((node_a.param * wt_node_a) + (node_b.param * wt_node_b))545#define LERPI(param) astc::flt2int_rtn(\546(static_cast<float>(node_a.param) * wt_node_a) + \547(static_cast<float>(node_b.param) * wt_node_b))548#define LERPUI(param) static_cast<unsigned int>(LERPI(param))549550config.tune_partition_count_limit = LERPI(tune_partition_count_limit);551config.tune_2partition_index_limit = LERPI(tune_2partition_index_limit);552config.tune_3partition_index_limit = LERPI(tune_3partition_index_limit);553config.tune_4partition_index_limit = LERPI(tune_4partition_index_limit);554config.tune_block_mode_limit = LERPI(tune_block_mode_limit);555config.tune_refinement_limit = LERPI(tune_refinement_limit);556config.tune_candidate_limit = LERPUI(tune_candidate_limit);557config.tune_2partitioning_candidate_limit = LERPUI(tune_2partitioning_candidate_limit);558config.tune_3partitioning_candidate_limit = LERPUI(tune_3partitioning_candidate_limit);559config.tune_4partitioning_candidate_limit = LERPUI(tune_4partitioning_candidate_limit);560config.tune_db_limit = astc::max(LERP(tune_db_limit_a_base) - 35 * ltexels,561LERP(tune_db_limit_b_base) - 19 * ltexels);562563config.tune_mse_overshoot = LERP(tune_mse_overshoot);564565config.tune_2partition_early_out_limit_factor = LERP(tune_2partition_early_out_limit_factor);566config.tune_3partition_early_out_limit_factor = LERP(tune_3partition_early_out_limit_factor);567config.tune_2plane_early_out_limit_correlation = LERP(tune_2plane_early_out_limit_correlation);568config.tune_search_mode0_enable = LERP(tune_search_mode0_enable);569#undef LERP570#undef LERPI571#undef LERPUI572}573574// Set heuristics to the defaults for each color profile575config.cw_r_weight = 1.0f;576config.cw_g_weight = 1.0f;577config.cw_b_weight = 1.0f;578config.cw_a_weight = 1.0f;579580config.a_scale_radius = 0;581582config.rgbm_m_scale = 0.0f;583584config.profile = profile;585586// Values in this enum are from an external user, so not guaranteed to be587// bounded to the enum values588switch (static_cast<int>(profile))589{590case ASTCENC_PRF_LDR:591case ASTCENC_PRF_LDR_SRGB:592break;593case ASTCENC_PRF_HDR_RGB_LDR_A:594case ASTCENC_PRF_HDR:595config.tune_db_limit = 999.0f;596config.tune_search_mode0_enable = 0.0f;597break;598default:599return ASTCENC_ERR_BAD_PROFILE;600}601602// Flags field must not contain any unknown flag bits603status = validate_flags(profile, flags);604if (status != ASTCENC_SUCCESS)605{606return status;607}608609if (flags & ASTCENC_FLG_MAP_NORMAL)610{611// Normal map encoding uses L+A blocks, so allow one more partitioning612// than normal. We need need fewer bits for endpoints, so more likely613// to be able to use more partitions than an RGB/RGBA block614config.tune_partition_count_limit = astc::min(config.tune_partition_count_limit + 1u, 4u);615616config.cw_g_weight = 0.0f;617config.cw_b_weight = 0.0f;618config.tune_2partition_early_out_limit_factor *= 1.5f;619config.tune_3partition_early_out_limit_factor *= 1.5f;620config.tune_2plane_early_out_limit_correlation = 0.99f;621622// Normals are prone to blocking artifacts on smooth curves623// so force compressor to try harder here ...624config.tune_db_limit *= 1.03f;625}626else if (flags & ASTCENC_FLG_MAP_RGBM)627{628config.rgbm_m_scale = 5.0f;629config.cw_a_weight = 2.0f * config.rgbm_m_scale;630}631else // (This is color data)632{633// This is a very basic perceptual metric for RGB color data, which weights error634// significance by the perceptual luminance contribution of each color channel. For635// luminance the usual weights to compute luminance from a linear RGB value are as636// follows:637//638// l = r * 0.3 + g * 0.59 + b * 0.11639//640// ... but we scale these up to keep a better balance between color and alpha. Note641// that if the content is using alpha we'd recommend using the -a option to weight642// the color contribution by the alpha transparency.643if (flags & ASTCENC_FLG_USE_PERCEPTUAL)644{645config.cw_r_weight = 0.30f * 2.25f;646config.cw_g_weight = 0.59f * 2.25f;647config.cw_b_weight = 0.11f * 2.25f;648}649}650config.flags = flags;651652return ASTCENC_SUCCESS;653}654655/* See header for documentation. */656astcenc_error astcenc_context_alloc(657const astcenc_config* configp,658unsigned int thread_count,659astcenc_context** context660) {661astcenc_error status;662const astcenc_config& config = *configp;663664status = validate_cpu_float();665if (status != ASTCENC_SUCCESS)666{667return status;668}669670if (thread_count == 0)671{672return ASTCENC_ERR_BAD_PARAM;673}674675#if defined(ASTCENC_DIAGNOSTICS)676// Force single threaded compressor use in diagnostic mode.677if (thread_count != 1)678{679return ASTCENC_ERR_BAD_PARAM;680}681#endif682683astcenc_context* ctxo = new astcenc_context;684astcenc_contexti* ctx = &ctxo->context;685ctx->thread_count = thread_count;686ctx->config = config;687ctx->working_buffers = nullptr;688689// These are allocated per-compress, as they depend on image size690ctx->input_alpha_averages = nullptr;691692// Copy the config first and validate the copy (we may modify it)693status = validate_config(ctx->config);694if (status != ASTCENC_SUCCESS)695{696delete ctxo;697return status;698}699700ctx->bsd = aligned_malloc<block_size_descriptor>(sizeof(block_size_descriptor), ASTCENC_VECALIGN);701if (!ctx->bsd)702{703delete ctxo;704return ASTCENC_ERR_OUT_OF_MEM;705}706707bool can_omit_modes = static_cast<bool>(config.flags & ASTCENC_FLG_SELF_DECOMPRESS_ONLY);708init_block_size_descriptor(config.block_x, config.block_y, config.block_z,709can_omit_modes,710config.tune_partition_count_limit,711static_cast<float>(config.tune_block_mode_limit) / 100.0f,712*ctx->bsd);713714#if !defined(ASTCENC_DECOMPRESS_ONLY)715// Do setup only needed by compression716if (!(ctx->config.flags & ASTCENC_FLG_DECOMPRESS_ONLY))717{718// Turn a dB limit into a per-texel error for faster use later719if ((ctx->config.profile == ASTCENC_PRF_LDR) || (ctx->config.profile == ASTCENC_PRF_LDR_SRGB))720{721ctx->config.tune_db_limit = astc::pow(0.1f, ctx->config.tune_db_limit * 0.1f) * 65535.0f * 65535.0f;722}723else724{725ctx->config.tune_db_limit = 0.0f;726}727728size_t worksize = sizeof(compression_working_buffers) * thread_count;729ctx->working_buffers = aligned_malloc<compression_working_buffers>(worksize, ASTCENC_VECALIGN);730static_assert((ASTCENC_VECALIGN == 0) || ((sizeof(compression_working_buffers) % ASTCENC_VECALIGN) == 0),731"compression_working_buffers size must be multiple of vector alignment");732if (!ctx->working_buffers)733{734aligned_free<block_size_descriptor>(ctx->bsd);735delete ctxo;736*context = nullptr;737return ASTCENC_ERR_OUT_OF_MEM;738}739}740#endif741742#if defined(ASTCENC_DIAGNOSTICS)743ctx->trace_log = new TraceLog(ctx->config.trace_file_path);744if (!ctx->trace_log->m_file)745{746return ASTCENC_ERR_DTRACE_FAILURE;747}748749trace_add_data("block_x", config.block_x);750trace_add_data("block_y", config.block_y);751trace_add_data("block_z", config.block_z);752#endif753754*context = ctxo;755756#if !defined(ASTCENC_DECOMPRESS_ONLY)757prepare_angular_tables();758#endif759760return ASTCENC_SUCCESS;761}762763/* See header dor documentation. */764void astcenc_context_free(765astcenc_context* ctxo766) {767if (ctxo)768{769astcenc_contexti* ctx = &ctxo->context;770aligned_free<compression_working_buffers>(ctx->working_buffers);771aligned_free<block_size_descriptor>(ctx->bsd);772#if defined(ASTCENC_DIAGNOSTICS)773delete ctx->trace_log;774#endif775delete ctxo;776}777}778779#if !defined(ASTCENC_DECOMPRESS_ONLY)780781/**782* @brief Compress an image, after any preflight has completed.783*784* @param[out] ctxo The compressor context.785* @param thread_index The thread index.786* @param image The intput image.787* @param swizzle The input swizzle.788* @param[out] buffer The output array for the compressed data.789*/790static void compress_image(791astcenc_context& ctxo,792unsigned int thread_index,793const astcenc_image& image,794const astcenc_swizzle& swizzle,795uint8_t* buffer796) {797astcenc_contexti& ctx = ctxo.context;798const block_size_descriptor& bsd = *ctx.bsd;799astcenc_profile decode_mode = ctx.config.profile;800801image_block blk;802803int block_x = bsd.xdim;804int block_y = bsd.ydim;805int block_z = bsd.zdim;806blk.texel_count = static_cast<uint8_t>(block_x * block_y * block_z);807808int dim_x = image.dim_x;809int dim_y = image.dim_y;810int dim_z = image.dim_z;811812int xblocks = (dim_x + block_x - 1) / block_x;813int yblocks = (dim_y + block_y - 1) / block_y;814int zblocks = (dim_z + block_z - 1) / block_z;815int block_count = zblocks * yblocks * xblocks;816817int row_blocks = xblocks;818int plane_blocks = xblocks * yblocks;819820blk.decode_unorm8 = ctxo.context.config.flags & ASTCENC_FLG_USE_DECODE_UNORM8;821822// Populate the block channel weights823blk.channel_weight = vfloat4(ctx.config.cw_r_weight,824ctx.config.cw_g_weight,825ctx.config.cw_b_weight,826ctx.config.cw_a_weight);827828// Use preallocated scratch buffer829auto& temp_buffers = ctx.working_buffers[thread_index];830831// Only the first thread actually runs the initializer832ctxo.manage_compress.init(block_count, ctx.config.progress_callback);833834// Determine if we can use an optimized load function835bool needs_swz = (swizzle.r != ASTCENC_SWZ_R) || (swizzle.g != ASTCENC_SWZ_G) ||836(swizzle.b != ASTCENC_SWZ_B) || (swizzle.a != ASTCENC_SWZ_A);837838bool needs_hdr = (decode_mode == ASTCENC_PRF_HDR) ||839(decode_mode == ASTCENC_PRF_HDR_RGB_LDR_A);840841bool use_fast_load = !needs_swz && !needs_hdr &&842block_z == 1 && image.data_type == ASTCENC_TYPE_U8;843844auto load_func = load_image_block;845if (use_fast_load)846{847load_func = load_image_block_fast_ldr;848}849850// All threads run this processing loop until there is no work remaining851while (true)852{853unsigned int count;854unsigned int base = ctxo.manage_compress.get_task_assignment(16, count);855if (!count)856{857break;858}859860for (unsigned int i = base; i < base + count; i++)861{862// Decode i into x, y, z block indices863int z = i / plane_blocks;864unsigned int rem = i - (z * plane_blocks);865int y = rem / row_blocks;866int x = rem - (y * row_blocks);867868// Test if we can apply some basic alpha-scale RDO869bool use_full_block = true;870if (ctx.config.a_scale_radius != 0 && block_z == 1)871{872int start_x = x * block_x;873int end_x = astc::min(dim_x, start_x + block_x);874875int start_y = y * block_y;876int end_y = astc::min(dim_y, start_y + block_y);877878// SATs accumulate error, so don't test exactly zero. Test for879// less than 1 alpha in the expanded block footprint that880// includes the alpha radius.881int x_footprint = block_x + 2 * (ctx.config.a_scale_radius - 1);882883int y_footprint = block_y + 2 * (ctx.config.a_scale_radius - 1);884885float footprint = static_cast<float>(x_footprint * y_footprint);886float threshold = 0.9f / (255.0f * footprint);887888// Do we have any alpha values?889use_full_block = false;890for (int ay = start_y; ay < end_y; ay++)891{892for (int ax = start_x; ax < end_x; ax++)893{894float a_avg = ctx.input_alpha_averages[ay * dim_x + ax];895if (a_avg > threshold)896{897use_full_block = true;898ax = end_x;899ay = end_y;900}901}902}903}904905// Fetch the full block for compression906if (use_full_block)907{908load_func(decode_mode, image, blk, bsd, x * block_x, y * block_y, z * block_z, swizzle);909910// Scale RGB error contribution by the maximum alpha in the block911// This encourages preserving alpha accuracy in regions with high912// transparency, and can buy up to 0.5 dB PSNR.913if (ctx.config.flags & ASTCENC_FLG_USE_ALPHA_WEIGHT)914{915float alpha_scale = blk.data_max.lane<3>() * (1.0f / 65535.0f);916blk.channel_weight = vfloat4(ctx.config.cw_r_weight * alpha_scale,917ctx.config.cw_g_weight * alpha_scale,918ctx.config.cw_b_weight * alpha_scale,919ctx.config.cw_a_weight);920}921}922// Apply alpha scale RDO - substitute constant color block923else924{925blk.origin_texel = vfloat4::zero();926blk.data_min = vfloat4::zero();927blk.data_mean = vfloat4::zero();928blk.data_max = vfloat4::zero();929blk.grayscale = true;930}931932int offset = ((z * yblocks + y) * xblocks + x) * 16;933uint8_t *bp = buffer + offset;934compress_block(ctx, blk, bp, temp_buffers);935}936937ctxo.manage_compress.complete_task_assignment(count);938}939}940941/**942* @brief Compute regional averages in an image.943*944* This function can be called by multiple threads, but only after a single945* thread calls the setup function @c init_compute_averages().946*947* Results are written back into @c img->input_alpha_averages.948*949* @param[out] ctx The context.950* @param ag The average and variance arguments created during setup.951*/952static void compute_averages(953astcenc_context& ctx,954const avg_args &ag955) {956pixel_region_args arg = ag.arg;957arg.work_memory = new vfloat4[ag.work_memory_size];958959int size_x = ag.img_size_x;960int size_y = ag.img_size_y;961int size_z = ag.img_size_z;962963int step_xy = ag.blk_size_xy;964int step_z = ag.blk_size_z;965966int y_tasks = (size_y + step_xy - 1) / step_xy;967968// All threads run this processing loop until there is no work remaining969while (true)970{971unsigned int count;972unsigned int base = ctx.manage_avg.get_task_assignment(16, count);973if (!count)974{975break;976}977978for (unsigned int i = base; i < base + count; i++)979{980int z = (i / (y_tasks)) * step_z;981int y = (i - (z * y_tasks)) * step_xy;982983arg.size_z = astc::min(step_z, size_z - z);984arg.offset_z = z;985986arg.size_y = astc::min(step_xy, size_y - y);987arg.offset_y = y;988989for (int x = 0; x < size_x; x += step_xy)990{991arg.size_x = astc::min(step_xy, size_x - x);992arg.offset_x = x;993compute_pixel_region_variance(ctx.context, arg);994}995}996997ctx.manage_avg.complete_task_assignment(count);998}9991000delete[] arg.work_memory;1001}10021003#endif10041005/* See header for documentation. */1006astcenc_error astcenc_compress_image(1007astcenc_context* ctxo,1008astcenc_image* imagep,1009const astcenc_swizzle* swizzle,1010uint8_t* data_out,1011size_t data_len,1012unsigned int thread_index1013) {1014#if defined(ASTCENC_DECOMPRESS_ONLY)1015(void)ctxo;1016(void)imagep;1017(void)swizzle;1018(void)data_out;1019(void)data_len;1020(void)thread_index;1021return ASTCENC_ERR_BAD_CONTEXT;1022#else1023astcenc_contexti* ctx = &ctxo->context;1024astcenc_error status;1025astcenc_image& image = *imagep;10261027if (ctx->config.flags & ASTCENC_FLG_DECOMPRESS_ONLY)1028{1029return ASTCENC_ERR_BAD_CONTEXT;1030}10311032status = validate_compression_swizzle(*swizzle);1033if (status != ASTCENC_SUCCESS)1034{1035return status;1036}10371038if (thread_index >= ctx->thread_count)1039{1040return ASTCENC_ERR_BAD_PARAM;1041}10421043unsigned int block_x = ctx->config.block_x;1044unsigned int block_y = ctx->config.block_y;1045unsigned int block_z = ctx->config.block_z;10461047unsigned int xblocks = (image.dim_x + block_x - 1) / block_x;1048unsigned int yblocks = (image.dim_y + block_y - 1) / block_y;1049unsigned int zblocks = (image.dim_z + block_z - 1) / block_z;10501051// Check we have enough output space (16 bytes per block)1052size_t size_needed = xblocks * yblocks * zblocks * 16;1053if (data_len < size_needed)1054{1055return ASTCENC_ERR_OUT_OF_MEM;1056}10571058// If context thread count is one then implicitly reset1059if (ctx->thread_count == 1)1060{1061astcenc_compress_reset(ctxo);1062}10631064if (ctx->config.a_scale_radius != 0)1065{1066// First thread to enter will do setup, other threads will subsequently1067// enter the critical section but simply skip over the initialization1068auto init_avg = [ctx, &image, swizzle]() {1069// Perform memory allocations for the destination buffers1070size_t texel_count = image.dim_x * image.dim_y * image.dim_z;1071ctx->input_alpha_averages = new float[texel_count];10721073return init_compute_averages(1074image, ctx->config.a_scale_radius, *swizzle,1075ctx->avg_preprocess_args);1076};10771078// Only the first thread actually runs the initializer1079ctxo->manage_avg.init(init_avg);10801081// All threads will enter this function and dynamically grab work1082compute_averages(*ctxo, ctx->avg_preprocess_args);1083}10841085// Wait for compute_averages to complete before compressing1086ctxo->manage_avg.wait();10871088compress_image(*ctxo, thread_index, image, *swizzle, data_out);10891090// Wait for compress to complete before freeing memory1091ctxo->manage_compress.wait();10921093auto term_compress = [ctx]() {1094delete[] ctx->input_alpha_averages;1095ctx->input_alpha_averages = nullptr;1096};10971098// Only the first thread to arrive actually runs the term1099ctxo->manage_compress.term(term_compress);11001101return ASTCENC_SUCCESS;1102#endif1103}11041105/* See header for documentation. */1106astcenc_error astcenc_compress_reset(1107astcenc_context* ctxo1108) {1109#if defined(ASTCENC_DECOMPRESS_ONLY)1110(void)ctxo;1111return ASTCENC_ERR_BAD_CONTEXT;1112#else1113astcenc_contexti* ctx = &ctxo->context;1114if (ctx->config.flags & ASTCENC_FLG_DECOMPRESS_ONLY)1115{1116return ASTCENC_ERR_BAD_CONTEXT;1117}11181119ctxo->manage_avg.reset();1120ctxo->manage_compress.reset();1121return ASTCENC_SUCCESS;1122#endif1123}11241125/* See header for documentation. */1126astcenc_error astcenc_compress_cancel(1127astcenc_context* ctxo1128) {1129#if defined(ASTCENC_DECOMPRESS_ONLY)1130(void)ctxo;1131return ASTCENC_ERR_BAD_CONTEXT;1132#else1133astcenc_contexti* ctx = &ctxo->context;1134if (ctx->config.flags & ASTCENC_FLG_DECOMPRESS_ONLY)1135{1136return ASTCENC_ERR_BAD_CONTEXT;1137}11381139// Cancel compression before cancelling avg. This avoids the race condition1140// where cancelling them in the other order could see a compression worker1141// starting to process even though some of the avg data is undefined.1142ctxo->manage_compress.cancel();1143ctxo->manage_avg.cancel();1144return ASTCENC_SUCCESS;1145#endif1146}11471148/* See header for documentation. */1149astcenc_error astcenc_decompress_image(1150astcenc_context* ctxo,1151const uint8_t* data,1152size_t data_len,1153astcenc_image* image_outp,1154const astcenc_swizzle* swizzle,1155unsigned int thread_index1156) {1157astcenc_error status;1158astcenc_image& image_out = *image_outp;1159astcenc_contexti* ctx = &ctxo->context;11601161// Today this doesn't matter (working set on stack) but might in future ...1162if (thread_index >= ctx->thread_count)1163{1164return ASTCENC_ERR_BAD_PARAM;1165}11661167status = validate_decompression_swizzle(*swizzle);1168if (status != ASTCENC_SUCCESS)1169{1170return status;1171}11721173unsigned int block_x = ctx->config.block_x;1174unsigned int block_y = ctx->config.block_y;1175unsigned int block_z = ctx->config.block_z;11761177unsigned int xblocks = (image_out.dim_x + block_x - 1) / block_x;1178unsigned int yblocks = (image_out.dim_y + block_y - 1) / block_y;1179unsigned int zblocks = (image_out.dim_z + block_z - 1) / block_z;1180unsigned int block_count = zblocks * yblocks * xblocks;11811182int row_blocks = xblocks;1183int plane_blocks = xblocks * yblocks;11841185// Check we have enough output space (16 bytes per block)1186size_t size_needed = xblocks * yblocks * zblocks * 16;1187if (data_len < size_needed)1188{1189return ASTCENC_ERR_OUT_OF_MEM;1190}11911192image_block blk {};1193blk.texel_count = static_cast<uint8_t>(block_x * block_y * block_z);11941195// Decode mode inferred from the output data type1196blk.decode_unorm8 = image_out.data_type == ASTCENC_TYPE_U8;11971198// If context thread count is one then implicitly reset1199if (ctx->thread_count == 1)1200{1201astcenc_decompress_reset(ctxo);1202}12031204// Only the first thread actually runs the initializer1205ctxo->manage_decompress.init(block_count, nullptr);12061207// All threads run this processing loop until there is no work remaining1208while (true)1209{1210unsigned int count;1211unsigned int base = ctxo->manage_decompress.get_task_assignment(128, count);1212if (!count)1213{1214break;1215}12161217for (unsigned int i = base; i < base + count; i++)1218{1219// Decode i into x, y, z block indices1220int z = i / plane_blocks;1221unsigned int rem = i - (z * plane_blocks);1222int y = rem / row_blocks;1223int x = rem - (y * row_blocks);12241225unsigned int offset = (((z * yblocks + y) * xblocks) + x) * 16;1226const uint8_t* bp = data + offset;12271228symbolic_compressed_block scb;12291230physical_to_symbolic(*ctx->bsd, bp, scb);12311232decompress_symbolic_block(ctx->config.profile, *ctx->bsd,1233x * block_x, y * block_y, z * block_z,1234scb, blk);12351236store_image_block(image_out, blk, *ctx->bsd,1237x * block_x, y * block_y, z * block_z, *swizzle);1238}12391240ctxo->manage_decompress.complete_task_assignment(count);1241}12421243return ASTCENC_SUCCESS;1244}12451246/* See header for documentation. */1247astcenc_error astcenc_decompress_reset(1248astcenc_context* ctxo1249) {1250ctxo->manage_decompress.reset();1251return ASTCENC_SUCCESS;1252}12531254/* See header for documentation. */1255astcenc_error astcenc_get_block_info(1256astcenc_context* ctxo,1257const uint8_t data[16],1258astcenc_block_info* info1259) {1260#if defined(ASTCENC_DECOMPRESS_ONLY)1261(void)ctxo;1262(void)data;1263(void)info;1264return ASTCENC_ERR_BAD_CONTEXT;1265#else1266astcenc_contexti* ctx = &ctxo->context;12671268// Decode the compressed data into a symbolic form1269symbolic_compressed_block scb;1270physical_to_symbolic(*ctx->bsd, data, scb);12711272// Fetch the appropriate partition and decimation tables1273block_size_descriptor& bsd = *ctx->bsd;12741275// Start from a clean slate1276memset(info, 0, sizeof(*info));12771278// Basic info we can always populate1279info->profile = ctx->config.profile;12801281info->block_x = ctx->config.block_x;1282info->block_y = ctx->config.block_y;1283info->block_z = ctx->config.block_z;1284info->texel_count = bsd.texel_count;12851286// Check for error blocks first1287info->is_error_block = scb.block_type == SYM_BTYPE_ERROR;1288if (info->is_error_block)1289{1290return ASTCENC_SUCCESS;1291}12921293// Check for constant color blocks second1294info->is_constant_block = scb.block_type == SYM_BTYPE_CONST_F16 ||1295scb.block_type == SYM_BTYPE_CONST_U16;1296if (info->is_constant_block)1297{1298return ASTCENC_SUCCESS;1299}13001301// Otherwise handle a full block ; known to be valid after conditions above have been checked1302int partition_count = scb.partition_count;1303const auto& pi = bsd.get_partition_info(partition_count, scb.partition_index);13041305const block_mode& bm = bsd.get_block_mode(scb.block_mode);1306const decimation_info& di = bsd.get_decimation_info(bm.decimation_mode);13071308info->weight_x = di.weight_x;1309info->weight_y = di.weight_y;1310info->weight_z = di.weight_z;13111312info->is_dual_plane_block = bm.is_dual_plane != 0;13131314info->partition_count = scb.partition_count;1315info->partition_index = scb.partition_index;1316info->dual_plane_component = scb.plane2_component;13171318info->color_level_count = get_quant_level(scb.get_color_quant_mode());1319info->weight_level_count = get_quant_level(bm.get_weight_quant_mode());13201321// Unpack color endpoints for each active partition1322for (unsigned int i = 0; i < scb.partition_count; i++)1323{1324bool rgb_hdr;1325bool a_hdr;1326vint4 endpnt[2];13271328unpack_color_endpoints(ctx->config.profile,1329scb.color_formats[i],1330scb.color_values[i],1331rgb_hdr, a_hdr,1332endpnt[0], endpnt[1]);13331334// Store the color endpoint mode info1335info->color_endpoint_modes[i] = scb.color_formats[i];1336info->is_hdr_block = info->is_hdr_block || rgb_hdr || a_hdr;13371338// Store the unpacked and decoded color endpoint1339vmask4 hdr_mask(rgb_hdr, rgb_hdr, rgb_hdr, a_hdr);1340for (int j = 0; j < 2; j++)1341{1342vint4 color_lns = lns_to_sf16(endpnt[j]);1343vint4 color_unorm = unorm16_to_sf16(endpnt[j]);1344vint4 datai = select(color_unorm, color_lns, hdr_mask);1345store(float16_to_float(datai), info->color_endpoints[i][j]);1346}1347}13481349// Unpack weights for each texel1350int weight_plane1[BLOCK_MAX_TEXELS];1351int weight_plane2[BLOCK_MAX_TEXELS];13521353unpack_weights(bsd, scb, di, bm.is_dual_plane, weight_plane1, weight_plane2);1354for (unsigned int i = 0; i < bsd.texel_count; i++)1355{1356info->weight_values_plane1[i] = static_cast<float>(weight_plane1[i]) * (1.0f / WEIGHTS_TEXEL_SUM);1357if (info->is_dual_plane_block)1358{1359info->weight_values_plane2[i] = static_cast<float>(weight_plane2[i]) * (1.0f / WEIGHTS_TEXEL_SUM);1360}1361}13621363// Unpack partition assignments for each texel1364for (unsigned int i = 0; i < bsd.texel_count; i++)1365{1366info->partition_assignment[i] = pi.partition_of_texel[i];1367}13681369return ASTCENC_SUCCESS;1370#endif1371}13721373/* See header for documentation. */1374const char* astcenc_get_error_string(1375astcenc_error status1376) {1377// Values in this enum are from an external user, so not guaranteed to be1378// bounded to the enum values1379switch (static_cast<int>(status))1380{1381case ASTCENC_SUCCESS:1382return "ASTCENC_SUCCESS";1383case ASTCENC_ERR_OUT_OF_MEM:1384return "ASTCENC_ERR_OUT_OF_MEM";1385case ASTCENC_ERR_BAD_CPU_FLOAT:1386return "ASTCENC_ERR_BAD_CPU_FLOAT";1387case ASTCENC_ERR_BAD_PARAM:1388return "ASTCENC_ERR_BAD_PARAM";1389case ASTCENC_ERR_BAD_BLOCK_SIZE:1390return "ASTCENC_ERR_BAD_BLOCK_SIZE";1391case ASTCENC_ERR_BAD_PROFILE:1392return "ASTCENC_ERR_BAD_PROFILE";1393case ASTCENC_ERR_BAD_QUALITY:1394return "ASTCENC_ERR_BAD_QUALITY";1395case ASTCENC_ERR_BAD_FLAGS:1396return "ASTCENC_ERR_BAD_FLAGS";1397case ASTCENC_ERR_BAD_SWIZZLE:1398return "ASTCENC_ERR_BAD_SWIZZLE";1399case ASTCENC_ERR_BAD_CONTEXT:1400return "ASTCENC_ERR_BAD_CONTEXT";1401case ASTCENC_ERR_NOT_IMPLEMENTED:1402return "ASTCENC_ERR_NOT_IMPLEMENTED";1403case ASTCENC_ERR_BAD_DECODE_MODE:1404return "ASTCENC_ERR_BAD_DECODE_MODE";1405#if defined(ASTCENC_DIAGNOSTICS)1406case ASTCENC_ERR_DTRACE_FAILURE:1407return "ASTCENC_ERR_DTRACE_FAILURE";1408#endif1409default:1410return nullptr;1411}1412}141314141415