mirror of
https://github.com/BinomialLLC/basis_universal.git
synced 2026-09-11 21:18:20 +00:00
This update impacts the UASTC HDR 4x4, UASTC HDR 6x6i, and XUASTC LDR formats. New KTX2 files in these specific formats using v2.1 can't be loaded using v2.0, however we've kept backwards compatibility with all of our previously written KTX2 files. (v2.1 can transcode all previously written files.) This is part of our effort to be compatible with Khronos's specification. bc6hf encoder's smooth block path updated to handle inputs with texels close to MAX_HALF_FLOAT. Adding command line options to basisu tool so it can write either v2.0 or v1.6 compatible UASTC HDR 6x6i files. The default is v1.6. Updating WebGL KTX2 testbed so on ASTC devices it automatically switches between sRGB or linear internal formats for the created texture.
5421 lines
188 KiB
C++
5421 lines
188 KiB
C++
// 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
|
|
{
|
|
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 (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 (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
|
|
{
|
|
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)
|
|
{
|
|
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_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_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 KTX2 key values: %u\n", m_params.m_ktx2_key_values.size());
|
|
for (uint32_t i = 0; i < m_params.m_ktx2_key_values.size(); i++)
|
|
{
|
|
debug_printf("Key: \"%s\"\n", m_params.m_ktx2_key_values[i].m_key.data());
|
|
debug_printf("Value size: %u\n", m_params.m_ktx2_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);
|
|
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_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("XUASTC LDR channel weights: ");
|
|
for (uint32_t i = 0; i < 4; i++)
|
|
fmt_debug_printf("{} ", m_params.m_xuastc_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);
|
|
|
|
#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("\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()
|
|
{
|
|
// Unfortunately due to the legacy of this code and backwards API compatibility this is more complex than I would like.
|
|
m_fmt_mode = basist::basis_tex_format::cETC1S;
|
|
m_fmt_mode_block_width = 4;
|
|
m_fmt_mode_block_height = 4;
|
|
|
|
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;
|
|
}
|
|
|
|
default:
|
|
assert(0);
|
|
break;
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
basis_compressor::error_code basis_compressor::process()
|
|
{
|
|
debug_printf("basis_compressor::process\n");
|
|
|
|
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 (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: {}, sRGB8 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_xuastc_ldr_channel_weights[0], m_params.m_xuastc_ldr_channel_weights[1], m_params.m_xuastc_ldr_channel_weights[2], m_params.m_xuastc_ldr_channel_weights[3]);
|
|
}
|
|
else
|
|
{
|
|
fmt_printf("Mode: ASTC LDR {}x{}, Effort Level (0-10): {}, Disable Subsets: {}, Disable RGB Dual Plane: {}, sRGB8 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_xuastc_ldr_channel_weights[0], m_params.m_xuastc_ldr_channel_weights[1], m_params.m_xuastc_ldr_channel_weights[2], m_params.m_xuastc_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
|
|
{
|
|
// ETC1S
|
|
if (m_params.m_status_output)
|
|
printf("Mode: ETC1S Quality (1-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 cECFailedFontendExtract;
|
|
|
|
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;
|
|
|
|
return cECSuccess;
|
|
}
|
|
|
|
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++)
|
|
{
|
|
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++)
|
|
{
|
|
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);
|
|
|
|
#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; // experimental, not recommended, very slow
|
|
cfg.m_block_blurring_p2 = m_params.m_xuastc_ldr_blurring; // experimental, not recommended, very slow
|
|
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_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 >= 0)
|
|
{
|
|
// 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
|
|
{
|
|
// No DCT quality level specified, but they wanted DCT - display warning
|
|
if (cfg.m_use_dct)
|
|
{
|
|
printf("Warning: m_use_dct enabled, but m_quality_level was -1 (not set). Not using DCT. 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_xuastc_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_output = m_params.m_debug;
|
|
cfg.m_debug_images = m_params.m_debug_images;
|
|
|
|
for (uint32_t slice_index = 0; slice_index < m_slice_descs.size(); 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_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++)
|
|
{
|
|
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, BASISU_MAX_SUPPORTED_TEXTURE_DIMENSION);
|
|
int new_height = basisu::minimum<int>(m_params.m_resample_height, 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)), 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)), 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)), 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)), 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 > BASISU_MAX_SUPPORTED_TEXTURE_DIMENSION) || (height > 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)
|
|
{
|
|
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 mode either.
|
|
if (basis_tex_format_is_xuastc_ldr(m_fmt_mode) || basis_tex_format_is_astc_ldr(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++)
|
|
{
|
|
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");
|
|
|
|
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))
|
|
{
|
|
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;
|
|
|
|
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
|
|
{
|
|
// ETC1S
|
|
tex_format = texture_format::cETC1;
|
|
blk_format = basist::block_format::cETC1;
|
|
}
|
|
|
|
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)
|
|
{
|
|
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
|
|
{
|
|
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
|
|
{
|
|
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))
|
|
{
|
|
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();
|
|
|
|
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];
|
|
|
|
if (m_params.m_print_stats)
|
|
{
|
|
printf("Slice: %u\n", slice_index);
|
|
}
|
|
|
|
image_metrics im;
|
|
|
|
if (m_params.m_print_stats)
|
|
{
|
|
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_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];
|
|
|
|
image_metrics em;
|
|
|
|
// ---- .basis stats
|
|
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_decoded_output_textures_unpacked_bc7[slice_index].get_width())
|
|
{
|
|
// ---- BC7 stats
|
|
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_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,0x0,0x0,0x0,
|
|
0x0,0x0,0x0,0x0,
|
|
0x0,0x0,0x3F,0x0,
|
|
0x0,0x0,0x0,0x0,
|
|
0x0,0x0,0x0,0x0,
|
|
0xFF,0xFF,0xFF,0xFF,
|
|
0x40,0x0,0x3F,0xF,
|
|
0x0,0x0,0x0,0x0,
|
|
0x0,0x0,0x0,0x0,
|
|
0xFF,0xFF,0xFF,0xFF
|
|
};
|
|
|
|
// 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)
|
|
};
|
|
|
|
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);
|
|
|
|
// 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)
|
|
{
|
|
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;
|
|
}
|
|
}
|
|
}
|
|
// 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) || basist::basis_tex_format_is_xuastc_ldr(m_fmt_mode) || basis_tex_format_is_astc_ldr(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;
|
|
|
|
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
|
|
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;
|
|
}
|
|
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);
|
|
|
|
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))
|
|
{
|
|
// The global data for UASTC HDR 6x6 INTERMEDIATE and XUASTC LDR 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
|
|
{
|
|
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.)
|
|
profile = m_uastc_backend_output.m_slice_image_data[slice_index][0] | (0x01 << 8); // TODO high byte is the XUASTC LDR codec variant index, currently hardcoded to 1 until we have an internal query/introspection API for this
|
|
}
|
|
}
|
|
|
|
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 : basist::KTX2_SS_XUASTC_LDR;
|
|
}
|
|
|
|
// Key values
|
|
basist::ktx2_transcoder::key_value_vec key_values(m_params.m_ktx2_key_values);
|
|
|
|
basist::ktx2_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::ktx2_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::ktx2_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->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->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 controls the error metrics, KTX2/.basis transfer function fields, and mipmap filtering
|
|
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;
|
|
|
|
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))
|
|
{
|
|
// TODO: change this so 100=no DCT to simplify the non-unified API, right now they must set 0=no DCT
|
|
if ((uastc_rdo_or_dct_quality >= (float)BASISU_XUASTC_QUALITY_MIN) && (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;
|
|
}
|
|
}
|
|
}
|
|
|
|
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);
|
|
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;
|
|
|
|
// 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 > BASISU_MAX_SUPPORTED_TEXTURE_DIMENSION) || (height > 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 > BASISU_MAX_SUPPORTED_TEXTURE_DIMENSION) || (height > 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
|
|
|
|
|
|
|