2021-05-07 15:00:41 +00:00
// basisu_comp.cpp
// Copyright (C) 2019-2021 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>
// 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"
2022-03-24 19:39:24 +00:00
# include "basisu_opencl.h"
2021-05-07 15:00:41 +00:00
# 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 the 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)
# define DEBUG_EXTRACT_SINGLE_BLOCK (0)
namespace basisu
{
basis_compressor : : basis_compressor ( ) :
2022-03-24 19:39:24 +00:00
m_pOpenCL_context ( nullptr ) ,
2021-05-07 15:00:41 +00:00
m_basis_file_size ( 0 ) ,
m_basis_bits_per_texel ( 0.0f ) ,
m_total_blocks ( 0 ) ,
2022-03-24 19:39:24 +00:00
m_any_source_image_has_alpha ( false ) ,
m_opencl_failed ( false )
2021-05-07 15:00:41 +00:00
{
debug_printf ( " basis_compressor::basis_compressor \n " ) ;
2022-03-24 19:39:24 +00:00
assert ( g_library_initialized ) ;
2021-05-07 15:00:41 +00:00
}
2022-03-24 19:39:24 +00:00
basis_compressor : : ~ basis_compressor ( )
{
if ( m_pOpenCL_context )
{
opencl_destroy_context ( m_pOpenCL_context ) ;
m_pOpenCL_context = nullptr ;
}
}
2021-05-07 15:00:41 +00:00
bool basis_compressor : : init ( const basis_compressor_params & params )
{
debug_printf ( " basis_compressor::init \n " ) ;
2022-03-24 19:39:24 +00:00
if ( ! g_library_initialized )
{
error_printf ( " basis_compressor::init: basisu_encoder_init() MUST be called before using any encoder functionality! \n " ) ;
return false ;
}
2021-05-07 15:00:41 +00:00
2022-03-24 19:39:24 +00:00
if ( ! params . m_pJob_pool )
{
error_printf ( " basis_compressor::init: A non-null job_pool pointer must be specified \n " ) ;
return false ;
}
2021-05-07 15:00:41 +00:00
m_params = params ;
2022-03-24 19:39:24 +00:00
2021-05-07 15:00:41 +00:00
if ( m_params . m_debug )
{
debug_printf ( " basis_compressor::init: \n " ) ;
# define PRINT_BOOL_VALUE(v) debug_printf("%s: %u %u\n", BASISU_STRINGIZE2(v), static_cast<int>(m_params.v), m_params.v.was_changed());
# define PRINT_INT_VALUE(v) debug_printf("%s: %i %u\n", BASISU_STRINGIZE2(v), static_cast<int>(m_params.v), m_params.v.was_changed());
# define PRINT_UINT_VALUE(v) debug_printf("%s: %u %u\n", BASISU_STRINGIZE2(v), static_cast<uint32_t>(m_params.v), m_params.v.was_changed());
# define PRINT_FLOAT_VALUE(v) debug_printf("%s: %f %u\n", BASISU_STRINGIZE2(v), static_cast<float>(m_params.v), m_params.v.was_changed());
2022-03-24 19:39:24 +00:00
2021-05-07 15:00:41 +00:00
debug_printf ( " Source images: %u, source filenames: %u, source alpha filenames: %i, Source mipmap images: %u \n " ,
m_params . m_source_images . size ( ) , m_params . m_source_filenames . size ( ) , m_params . m_source_alpha_filenames . size ( ) , m_params . m_source_mipmap_images . 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 " ) ;
}
PRINT_BOOL_VALUE ( m_uastc ) ;
2022-03-24 19:39:24 +00:00
PRINT_BOOL_VALUE ( m_use_opencl ) ;
2021-05-07 15:00:41 +00:00
PRINT_BOOL_VALUE ( m_y_flip ) ;
PRINT_BOOL_VALUE ( m_debug ) ;
2022-03-24 19:39:24 +00:00
PRINT_BOOL_VALUE ( m_validate_etc1s ) ;
2021-05-07 15:00:41 +00:00
PRINT_BOOL_VALUE ( m_debug_images ) ;
PRINT_INT_VALUE ( m_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_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 ) ;
2022-03-24 19:39:24 +00:00
2021-05-07 15:00:41 +00:00
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_max_endpoint_clusters ) ;
debug_printf ( " m_max_selector_clusters: %u \n " , m_params . m_max_selector_clusters ) ;
debug_printf ( " m_quality_level: %i \n " , m_params . m_quality_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_flags: 0x%X \n " , m_params . m_pack_uastc_flags ) ;
PRINT_BOOL_VALUE ( m_rdo_uastc ) ;
PRINT_FLOAT_VALUE ( m_rdo_uastc_quality_scalar ) ;
PRINT_INT_VALUE ( m_rdo_uastc_dict_size ) ;
PRINT_FLOAT_VALUE ( m_rdo_uastc_max_allowed_rms_increase_ratio ) ;
PRINT_FLOAT_VALUE ( m_rdo_uastc_skip_block_rms_thresh ) ;
PRINT_FLOAT_VALUE ( m_rdo_uastc_max_smooth_block_error_scale ) ;
PRINT_FLOAT_VALUE ( m_rdo_uastc_smooth_block_max_std_dev ) ;
PRINT_BOOL_VALUE ( m_rdo_uastc_favor_simpler_modes_in_rdo_mode )
PRINT_BOOL_VALUE ( m_rdo_uastc_multithreading ) ;
PRINT_INT_VALUE ( m_resample_width ) ;
PRINT_INT_VALUE ( m_resample_height ) ;
PRINT_FLOAT_VALUE ( m_resample_factor ) ;
2022-03-24 19:39:24 +00:00
2021-05-07 15:00:41 +00:00
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 sRGB transfer func: %u \n " , ( int ) m_params . m_ktx2_srgb_transfer_func ) ;
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 ( ) ) ;
}
2022-03-24 19:39:24 +00:00
PRINT_BOOL_VALUE ( m_validate_output_data ) ;
2021-05-07 15:00:41 +00:00
# undef PRINT_BOOL_VALUE
# undef PRINT_INT_VALUE
# undef PRINT_UINT_VALUE
# undef PRINT_FLOAT_VALUE
}
if ( ( m_params . m_read_source_images ) & & ( ! m_params . m_source_filenames . size ( ) ) )
{
assert ( 0 ) ;
return false ;
}
2022-03-24 19:39:24 +00:00
if ( ( m_params . m_compute_stats ) & & ( ! m_params . m_validate_output_data ) )
{
m_params . m_validate_output_data = true ;
debug_printf ( " Note: m_compute_stats is true, so forcing m_validate_output_data to true as well \n " ) ;
}
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 ;
}
2021-05-07 15:00:41 +00:00
return true ;
}
basis_compressor : : error_code basis_compressor : : process ( )
{
debug_printf ( " basis_compressor::process \n " ) ;
if ( ! read_source_images ( ) )
return cECFailedReadingSourceImages ;
if ( ! validate_texture_type_constraints ( ) )
return cECFailedValidating ;
if ( m_params . m_create_ktx2_file )
{
if ( ! validate_ktx2_constraints ( ) )
return cECFailedValidating ;
}
if ( ! extract_source_blocks ( ) )
return cECFailedFrontEnd ;
if ( m_params . m_uastc )
{
error_code ec = encode_slices_to_uastc ( ) ;
if ( ec ! = cECSuccess )
return ec ;
}
else
{
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_uastc ( )
{
debug_printf ( " basis_compressor::encode_slices_to_uastc \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 : : cUASTC4x4 ;
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 : : atomic < uint32_t > total_blocks_processed ;
total_blocks_processed = 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 ) ;
// FIXME: This sucks, but we're having a stack size related problem with std::function with emscripten.
# ifndef __EMSCRIPTEN__
m_params . m_pJob_pool - > add_job ( [ this , first_index , last_index , num_blocks_x , num_blocks_y , total_blocks , & source_image , & tex , & total_blocks_processed ]
{
# endif
BASISU_NOTE_UNUSED ( num_blocks_y ) ;
uint32_t uastc_flags = m_params . m_pack_uastc_flags ;
if ( ( m_params . m_rdo_uastc ) & & ( m_params . m_rdo_uastc_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 ) ;
total_blocks_processed + + ;
uint32_t val = total_blocks_processed ;
if ( ( val & 16383 ) = = 16383 )
{
debug_printf ( " basis_compressor::encode_slices_to_uastc: %3.1f%% done \n " , static_cast < float > ( val ) * 100.0f / total_blocks ) ;
}
}
# ifndef __EMSCRIPTEN__
} ) ;
# endif
} // block_index_iter
# ifndef __EMSCRIPTEN__
m_params . m_pJob_pool - > wait_for_all ( ) ;
# endif
if ( m_params . m_rdo_uastc )
{
uastc_rdo_params rdo_params ;
rdo_params . m_lambda = m_params . m_rdo_uastc_quality_scalar ;
rdo_params . m_max_allowed_rms_increase_ratio = m_params . m_rdo_uastc_max_allowed_rms_increase_ratio ;
rdo_params . m_skip_block_rms_thresh = m_params . m_rdo_uastc_skip_block_rms_thresh ;
rdo_params . m_lz_dict_size = m_params . m_rdo_uastc_dict_size ;
rdo_params . m_smooth_block_max_error_scale = m_params . m_rdo_uastc_max_smooth_block_error_scale ;
rdo_params . m_max_smooth_block_std_dev = m_params . m_rdo_uastc_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_flags , m_params . m_rdo_uastc_multithreading ? m_params . m_pJob_pool : nullptr ,
( m_params . m_rdo_uastc_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 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 )
2022-03-24 19:39:24 +00:00
debug_printf ( " Total mipmap generation time: %3.3f secs \n " , tm . get_elapsed_secs ( ) ) ;
2021-05-07 15:00:41 +00:00
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 ( ) : ( uint32_t ) m_params . m_source_images . size ( ) ;
if ( ! total_source_files )
return false ;
m_stats . resize ( 0 ) ;
m_slice_descs . resize ( 0 ) ;
m_slice_images . 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 < std : : string > source_filenames ;
// 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 ;
if ( m_params . m_read_source_images )
{
pSource_filename = m_params . m_source_filenames [ source_file_index ] . c_str ( ) ;
// Load the source image
if ( ! load_image ( pSource_filename , file_image ) )
{
error_printf ( " Failed reading source image: %s \n " , pSource_filename ) ;
return false ;
}
2021-11-19 17:40:08 +00:00
if ( m_params . m_status_output )
{
printf ( " Read source image \" %s \" , %ux%u \n " , pSource_filename , file_image . get_width ( ) , file_image . get_height ( ) ) ;
}
2021-05-07 15:00:41 +00:00
// 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 ;
}
printf ( " Read source alpha image \" %s \" , %ux%u \n " , pSource_alpha_image , alpha_data . get_width ( ) , alpha_data . get_height ( ) ) ;
alpha_data . crop ( file_image . get_width ( ) , file_image . get_height ( ) ) ;
for ( uint32_t y = 0 ; y < file_image . get_height ( ) ; y + + )
for ( uint32_t x = 0 ; x < file_image . get_width ( ) ; x + + )
file_image ( x , y ) . a = ( uint8_t ) alpha_data ( x , y ) . get_709_luma ( ) ;
}
}
else
{
file_image = m_params . m_source_images [ source_file_index ] ;
}
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 )
{
// 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 ;
}
bool has_alpha = false ;
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 ;
debug_printf ( " Source image index %u filename %s %ux%u has alpha: %u \n " , source_file_index , pSource_filename , file_image . get_width ( ) , file_image . get_height ( ) , has_alpha ) ;
if ( m_params . m_y_flip )
file_image . flip_y ( ) ;
# if DEBUG_EXTRACT_SINGLE_BLOCK
image block_image ( 4 , 4 ) ;
const uint32_t block_x = 0 ;
const uint32_t block_y = 0 ;
block_image . blit ( block_x * 4 , block_y * 4 , 4 , 4 , 0 , 0 , file_image , 0 ) ;
file_image = block_image ;
# endif
# if DEBUG_CROP_TEXTURE_TO_64x64
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.
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 )
{
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 ) ;
// TODO: A box filter - kaiser looks too sharp on video. Let the caller control this.
image temp_img ( new_width , new_height ) ;
image_resample ( file_image , temp_img , m_params . m_perceptual , " box " ) ; // "kaiser");
temp_img . swap ( file_image ) ;
}
if ( ( ! file_image . get_width ( ) ) | | ( ! file_image . get_height ( ) ) )
{
error_printf ( " basis_compressor::read_source_images: Source image has a zero width and/or height! \n " ) ;
return false ;
}
if ( ( file_image . get_width ( ) > BASISU_MAX_SUPPORTED_TEXTURE_DIMENSION ) | | ( file_image . get_height ( ) > BASISU_MAX_SUPPORTED_TEXTURE_DIMENSION ) )
{
2022-03-24 19:39:24 +00:00
error_printf ( " basis_compressor::read_source_images: Source image \" %s \" is too large! \n " , pSource_filename ) ;
2021-05-07 15:00:41 +00:00
return false ;
}
2022-03-24 19:39:24 +00:00
source_images . enlarge ( 1 ) - > swap ( file_image ) ;
2021-05-07 15:00:41 +00:00
source_filenames . push_back ( pSource_filename ) ;
}
// Check if the caller has generated their own mipmaps.
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.
// We're assuming the user has already preswizzled their mipmap source images.
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 ] ;
if ( mip_img . has_alpha ( ) )
{
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 ) ;
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 ] ;
// Now, for each source image, create the slices corresponding to that image.
basisu : : vector < image > slices ;
slices . reserve ( 32 ) ;
2022-03-24 19:39:24 +00:00
2021-05-07 15:00:41 +00:00
// The first (largest) mipmap level.
2022-03-24 19:39:24 +00:00
image & file_image = source_images [ source_file_index ] ;
// Reserve a slot for mip0.
slices . resize ( 1 ) ;
2021-05-07 15:00:41 +00:00
if ( 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_mip_gen )
{
// Automatically generate mipmaps.
if ( ! generate_mipmaps ( file_image , slices , m_any_source_image_has_alpha ) )
return false ;
}
2022-03-24 19:39:24 +00:00
// Swap in the largest mipmap level here to avoid copying it, because generate_mips() will change the array.
// NOTE: file_image is now blank.
slices [ 0 ] . swap ( file_image ) ;
2021-05-07 15:00:41 +00:00
uint_vec mip_indices ( slices . size ( ) ) ;
for ( uint32_t i = 0 ; i < slices . size ( ) ; i + + )
mip_indices [ i ] = i ;
if ( ( 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 ) ;
}
assert ( slices . size ( ) = = mip_indices . size ( ) ) ;
for ( uint32_t slice_index = 0 ; slice_index < slices . size ( ) ; slice_index + + )
{
image & slice_image = slices [ slice_index ] ;
const uint32_t orig_width = slice_image . get_width ( ) ;
const uint32_t orig_height = slice_image . get_height ( ) ;
bool is_alpha_slice = false ;
if ( m_any_source_image_has_alpha )
{
if ( m_params . m_uastc )
{
is_alpha_slice = slice_image . has_alpha ( ) ;
}
else
{
is_alpha_slice = ( slice_index & 1 ) ! = 0 ;
}
}
// Enlarge the source image to 4x4 block boundaries, duplicating edge pixels if necessary to avoid introducing extra colors into blocks.
slice_image . crop_dup_borders ( slice_image . get_block_width ( 4 ) * 4 , slice_image . get_block_height ( 4 ) * 4 ) ;
if ( m_params . m_debug_images )
{
save_png ( string_format ( " basis_debug_source_image_%u_slice_%u.png " , source_file_index , slice_index ) . c_str ( ) , slice_image ) ;
}
2022-03-24 19:39:24 +00:00
const uint32_t dest_image_index = m_slice_images . size ( ) ;
2021-05-07 15:00:41 +00:00
enlarge_vector ( m_stats , 1 ) ;
enlarge_vector ( m_slice_images , 1 ) ;
enlarge_vector ( m_slice_descs , 1 ) ;
2022-03-24 19:39:24 +00:00
2021-05-07 15:00:41 +00:00
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 ;
2022-03-24 19:39:24 +00:00
2021-05-07 15:00:41 +00:00
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 , slice_image . get_width ( ) , slice_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 ;
slice_desc . m_width = slice_image . get_width ( ) ;
slice_desc . m_height = slice_image . get_height ( ) ;
slice_desc . m_num_blocks_x = slice_image . get_block_width ( 4 ) ;
slice_desc . m_num_blocks_y = slice_image . get_block_height ( 4 ) ;
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 ;
2022-03-24 19:39:24 +00:00
// Finally, swap in the slice's image to avoid copying it.
// NOTE: slice_image is now blank.
m_slice_images [ dest_image_index ] . swap ( slice_image ) ;
2021-05-07 15:00:41 +00:00
} // 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 basis file 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 )
{
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_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_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 " ) ;
m_source_blocks . resize ( m_total_blocks ) ;
for ( uint32_t slice_index = 0 ; slice_index < 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 & source_image = m_slice_images [ slice_index ] ;
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 + + )
source_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_max_endpoint_clusters ;
int selector_clusters = m_params . m_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 ) ;
}
2022-03-24 19:39:24 +00:00
float bits_per_selector_cluster = 14.0f ;
2021-05-07 15:00:41 +00:00
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 ) ;
}
}
2022-03-24 19:39:24 +00:00
2021-05-07 15:00:41 +00:00
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_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 ;
2022-03-24 19:39:24 +00:00
p . m_validate = m_params . m_validate_etc1s ;
2021-05-07 15:00:41 +00:00
p . m_pJob_pool = m_params . m_pJob_pool ;
p . m_pGlobal_codebooks = m_params . m_pGlobal_codebooks ;
2022-03-24 19:39:24 +00:00
// Don't keep trying to use OpenCL if it ever fails.
p . m_pOpenCL_context = ! m_opencl_failed ? m_pOpenCL_context : nullptr ;
2021-05-07 15:00:41 +00:00
if ( ! m_frontend . init ( p ) )
{
error_printf ( " basisu_frontend::init() failed! \n " ) ;
return false ;
}
2022-03-24 19:39:24 +00:00
2021-05-07 15:00:41 +00:00
m_frontend . compress ( ) ;
2022-03-24 19:39:24 +00:00
if ( m_frontend . get_opencl_failed ( ) )
m_opencl_failed = true ;
2021-05-07 15:00:41 +00:00
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 ( )
{
2022-03-24 19:39:24 +00:00
if ( ! m_params . m_compute_stats )
return true ;
2021-05-07 15:00:41 +00:00
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 ] ) ;
}
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_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 ;
2022-03-24 19:39:24 +00:00
backend_params . m_validate = m_params . m_validate_output_data ;
2021-05-07 15:00:41 +00:00
2022-03-24 19:39:24 +00:00
m_backend . init ( & m_frontend , backend_params , m_slice_descs ) ;
2021-05-07 15:00:41 +00:00
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 ;
2022-03-24 19:39:24 +00:00
uint32_t total_orig_pixels = 0 , total_texels = 0 , total_orig_texels = 0 ;
for ( uint32_t i = 0 ; i < m_slice_descs . size ( ) ; i + + )
2021-05-07 15:00:41 +00:00
{
2022-03-24 19:39:24 +00:00
const basisu_backend_slice_desc & slice_desc = m_slice_descs [ i ] ;
total_orig_pixels + = slice_desc . m_orig_width * slice_desc . m_orig_height ;
total_texels + = slice_desc . m_width * slice_desc . m_height ;
2021-05-07 15:00:41 +00:00
}
2022-03-24 19:39:24 +00:00
m_basis_file_size = ( uint32_t ) comp_data . size ( ) ;
m_basis_bits_per_texel = total_orig_texels ? ( comp_data . size ( ) * 8.0f ) / total_orig_texels : 0 ;
2021-05-07 15:00:41 +00:00
2022-03-24 19:39:24 +00:00
debug_printf ( " Total .basis output file size: %u, %3.3f bits/texel \n " , comp_data . size ( ) , comp_data . size ( ) * 8.0f / total_orig_pixels ) ;
2021-05-07 15:00:41 +00:00
2022-03-24 19:39:24 +00:00
if ( m_params . m_validate_output_data )
2021-05-07 15:00:41 +00:00
{
2022-03-24 19:39:24 +00:00
interval_timer tm ;
tm . start ( ) ;
2021-05-07 15:00:41 +00:00
2022-03-24 19:39:24 +00:00
basist : : basisu_transcoder_init ( ) ;
2021-05-07 15:00:41 +00:00
2022-03-24 19:39:24 +00:00
debug_printf ( " basist::basisu_transcoder_init: Took %f ms \n " , tm . get_elapsed_ms ( ) ) ;
2021-05-07 15:00:41 +00:00
2022-03-24 19:39:24 +00:00
// 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 ;
}
2021-05-07 15:00:41 +00:00
2022-03-24 19:39:24 +00:00
m_decoded_output_textures . resize ( m_slice_descs . size ( ) ) ;
m_decoded_output_textures_unpacked . resize ( m_slice_descs . size ( ) ) ;
2021-05-07 15:00:41 +00:00
2022-03-24 19:39:24 +00:00
m_decoded_output_textures_bc7 . resize ( m_slice_descs . size ( ) ) ;
m_decoded_output_textures_unpacked_bc7 . resize ( m_slice_descs . size ( ) ) ;
2021-05-07 15:00:41 +00:00
2022-03-24 19:39:24 +00:00
tm . start ( ) ;
if ( m_params . m_pGlobal_codebooks )
2021-05-07 15:00:41 +00:00
{
2022-03-24 19:39:24 +00:00
decoder . set_global_codebooks ( m_params . m_pGlobal_codebooks ) ;
2021-05-07 15:00:41 +00:00
}
2022-03-24 19:39:24 +00:00
if ( ! decoder . start_transcoding ( & comp_data [ 0 ] , ( uint32_t ) comp_data . size ( ) ) )
2021-05-07 15:00:41 +00:00
{
2022-03-24 19:39:24 +00:00
error_printf ( " decoder.start_transcoding() failed! \n " ) ;
return false ;
2021-05-07 15:00:41 +00:00
}
2022-03-24 19:39:24 +00:00
double start_transcoding_time = tm . get_elapsed_secs ( ) ;
2021-05-07 15:00:41 +00:00
2022-03-24 19:39:24 +00:00
debug_printf ( " basisu_compressor::start_transcoding() took %3.3fms \n " , start_transcoding_time * 1000.0f ) ;
double total_time_etc1s_or_astc = 0 ;
2021-05-07 15:00:41 +00:00
for ( uint32_t i = 0 ; i < m_slice_descs . size ( ) ; i + + )
{
gpu_image decoded_texture ;
2022-03-24 19:39:24 +00:00
decoded_texture . init ( m_params . m_uastc ? texture_format : : cUASTC4x4 : texture_format : : cETC1 , m_slice_descs [ i ] . m_width , m_slice_descs [ i ] . m_height ) ;
2021-05-07 15:00:41 +00:00
tm . start ( ) ;
2022-03-24 19:39:24 +00:00
basist : : block_format format = m_params . m_uastc ? basist : : block_format : : cUASTC_4x4 : basist : : block_format : : cETC1 ;
uint32_t bytes_per_block = m_params . m_uastc ? 16 : 8 ;
2021-05-07 15:00:41 +00:00
if ( ! decoder . transcode_slice ( & comp_data [ 0 ] , ( uint32_t ) comp_data . size ( ) , i ,
2022-03-24 19:39:24 +00:00
reinterpret_cast < etc_block * > ( decoded_texture . get_ptr ( ) ) , m_slice_descs [ i ] . m_num_blocks_x * m_slice_descs [ i ] . m_num_blocks_y , format , bytes_per_block ) )
2021-05-07 15:00:41 +00:00
{
2022-03-24 19:39:24 +00:00
error_printf ( " Transcoding failed on slice %u! \n " , i ) ;
2021-05-07 15:00:41 +00:00
return false ;
}
2022-03-24 19:39:24 +00:00
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 [ i ] )
{
error_printf ( " Decoded image data CRC check failed on slice %u! \n " , i ) ;
return false ;
}
debug_printf ( " Decoded image data CRC check succeeded on slice %i \n " , i ) ;
}
2021-05-07 15:00:41 +00:00
2022-03-24 19:39:24 +00:00
m_decoded_output_textures [ i ] = decoded_texture ;
2021-05-07 15:00:41 +00:00
}
2022-03-24 19:39:24 +00:00
double total_time_bc7 = 0 ;
2021-05-07 15:00:41 +00:00
2022-03-24 19:39:24 +00:00
if ( basist : : basis_is_format_supported ( basist : : transcoder_texture_format : : cTFBC7_RGBA , basist : : basis_tex_format : : cUASTC4x4 ) & &
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_width , m_slice_descs [ i ] . m_height ) ;
2021-05-07 15:00:41 +00:00
2022-03-24 19:39:24 +00:00
tm . start ( ) ;
2021-05-07 15:00:41 +00:00
2022-03-24 19:39:24 +00:00
if ( ! decoder . transcode_slice ( & comp_data [ 0 ] , ( uint32_t ) comp_data . size ( ) , i ,
reinterpret_cast < etc_block * > ( decoded_texture . get_ptr ( ) ) , m_slice_descs [ i ] . m_num_blocks_x * m_slice_descs [ i ] . m_num_blocks_y , basist : : block_format : : cBC7 , 16 ) )
{
error_printf ( " Transcoding failed to BC7 on slice %u! \n " , i ) ;
return false ;
}
2021-05-07 15:00:41 +00:00
2022-03-24 19:39:24 +00:00
total_time_bc7 + = tm . get_elapsed_secs ( ) ;
2021-05-07 15:00:41 +00:00
2022-03-24 19:39:24 +00:00
m_decoded_output_textures_bc7 [ i ] = decoded_texture ;
}
}
2021-05-07 15:00:41 +00:00
2022-03-24 19:39:24 +00:00
for ( uint32_t i = 0 ; i < m_slice_descs . size ( ) ; i + + )
{
m_decoded_output_textures [ i ] . unpack ( m_decoded_output_textures_unpacked [ i ] ) ;
2021-05-07 15:00:41 +00:00
2022-03-24 19:39:24 +00:00
if ( m_decoded_output_textures_bc7 [ i ] . get_pixel_width ( ) )
m_decoded_output_textures_bc7 [ i ] . unpack ( m_decoded_output_textures_unpacked_bc7 [ i ] ) ;
}
2021-05-07 15:00:41 +00:00
2022-03-24 19:39:24 +00:00
debug_printf ( " Transcoded to %s in %3.3fms, %f texels/sec \n " , m_params . m_uastc ? " ASTC " : " ETC1 " , total_time_etc1s_or_astc * 1000.0f , total_orig_pixels / total_time_etc1s_or_astc ) ;
if ( total_time_bc7 ! = 0 )
debug_printf ( " Transcoded to BC7 in %3.3fms, %f texels/sec \n " , total_time_bc7 * 1000.0f , total_orig_pixels / total_time_bc7 ) ;
2021-05-07 15:00:41 +00:00
2022-03-24 19:39:24 +00:00
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)
2021-05-07 15:00:41 +00:00
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_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 ;
}
2021-11-19 17:40:08 +00:00
if ( m_params . m_status_output )
{
printf ( " Wrote output .basis/.ktx2 file \" %s \" \n " , output_filename . c_str ( ) ) ;
}
2021-05-07 15:00:41 +00:00
}
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_num_blocks_x * m_slice_descs [ i ] . m_num_blocks_y ) * 16 ;
m_basis_bits_per_texel = comp_size * 8.0f / total_texels ;
debug_printf ( " .basis file size: %u, LZ compressed file size: %u, %3.2f bits/texel \n " ,
( uint32_t ) comp_data . size ( ) ,
( uint32_t ) comp_size ,
m_basis_bits_per_texel ) ;
}
m_stats . resize ( m_slice_descs . size ( ) ) ;
2022-03-24 19:39:24 +00:00
if ( m_params . m_validate_output_data )
2021-05-07 15:00:41 +00:00
{
2022-03-24 19:39:24 +00:00
for ( uint32_t slice_index = 0 ; slice_index < m_slice_descs . size ( ) ; slice_index + + )
2021-05-07 15:00:41 +00:00
{
2022-03-24 19:39:24 +00:00
const basisu_backend_slice_desc & slice_desc = m_slice_descs [ slice_index ] ;
2021-05-07 15:00:41 +00:00
2022-03-24 19:39:24 +00:00
if ( m_params . m_compute_stats )
{
printf ( " Slice: %u \n " , slice_index ) ;
2021-05-07 15:00:41 +00:00
2022-03-24 19:39:24 +00:00
image_stats & s = m_stats [ slice_index ] ;
2021-05-07 15:00:41 +00:00
2022-03-24 19:39:24 +00:00
// TODO: We used to output SSIM (during heavy encoder development), but this slowed down compression too much. We'll be adding it back.
2021-05-07 15:00:41 +00:00
2022-03-24 19:39:24 +00:00
image_metrics em ;
2021-05-07 15:00:41 +00:00
2022-03-24 19:39:24 +00:00
// ---- .basis stats
em . calc ( m_slice_images [ slice_index ] , m_decoded_output_textures_unpacked [ slice_index ] , 0 , 3 ) ;
em . print ( " .basis RGB Avg: " ) ;
s . m_basis_rgb_avg_psnr = em . m_psnr ;
2021-05-07 15:00:41 +00:00
2022-03-24 19:39:24 +00:00
em . calc ( m_slice_images [ slice_index ] , m_decoded_output_textures_unpacked [ slice_index ] , 0 , 4 ) ;
em . print ( " .basis RGBA Avg: " ) ;
s . m_basis_rgba_avg_psnr = em . m_psnr ;
2021-05-07 15:00:41 +00:00
2022-03-24 19:39:24 +00:00
em . calc ( m_slice_images [ slice_index ] , m_decoded_output_textures_unpacked [ slice_index ] , 0 , 1 ) ;
em . print ( " .basis R Avg: " ) ;
2021-05-07 15:00:41 +00:00
2022-03-24 19:39:24 +00:00
em . calc ( m_slice_images [ slice_index ] , m_decoded_output_textures_unpacked [ slice_index ] , 1 , 1 ) ;
em . print ( " .basis G Avg: " ) ;
2021-05-07 15:00:41 +00:00
2022-03-24 19:39:24 +00:00
em . calc ( m_slice_images [ slice_index ] , m_decoded_output_textures_unpacked [ slice_index ] , 2 , 1 ) ;
em . print ( " .basis B Avg: " ) ;
2021-05-07 15:00:41 +00:00
2022-03-24 19:39:24 +00:00
if ( m_params . m_uastc )
{
em . calc ( m_slice_images [ slice_index ] , m_decoded_output_textures_unpacked [ slice_index ] , 3 , 1 ) ;
em . print ( " .basis A Avg: " ) ;
2021-05-07 15:00:41 +00:00
2022-03-24 19:39:24 +00:00
s . m_basis_a_avg_psnr = em . m_psnr ;
}
2021-05-07 15:00:41 +00:00
2022-03-24 19:39:24 +00:00
em . calc ( m_slice_images [ slice_index ] , m_decoded_output_textures_unpacked [ slice_index ] , 0 , 0 ) ;
em . print ( " .basis 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 ) ;
2021-05-07 15:00:41 +00:00
2022-03-24 19:39:24 +00:00
em . calc ( m_slice_images [ slice_index ] , m_decoded_output_textures_unpacked [ slice_index ] , 0 , 0 , true , true ) ;
em . print ( " .basis 601 Luma: " ) ;
s . m_basis_luma_601_psnr = static_cast < float > ( em . m_psnr ) ;
2021-05-07 15:00:41 +00:00
2022-03-24 19:39:24 +00:00
if ( m_slice_descs . size ( ) = = 1 )
{
const uint32_t output_size = comp_size ? ( uint32_t ) comp_size : ( uint32_t ) comp_data . size ( ) ;
debug_printf ( " .basis RGB PSNR per bit/texel*10000: %3.3f \n " , 10000.0f * s . m_basis_rgb_avg_psnr / ( ( output_size * 8.0f ) / ( slice_desc . m_orig_width * slice_desc . m_orig_height ) ) ) ;
debug_printf ( " .basis Luma 709 PSNR per bit/texel*10000: %3.3f \n " , 10000.0f * s . m_basis_luma_709_psnr / ( ( output_size * 8.0f ) / ( slice_desc . m_orig_width * slice_desc . m_orig_height ) ) ) ;
}
2021-05-07 15:00:41 +00:00
2022-03-24 19:39:24 +00:00
if ( m_decoded_output_textures_unpacked_bc7 [ slice_index ] . get_width ( ) )
2021-05-07 15:00:41 +00:00
{
2022-03-24 19:39:24 +00:00
// ---- BC7 stats
em . calc ( m_slice_images [ slice_index ] , m_decoded_output_textures_unpacked_bc7 [ slice_index ] , 0 , 3 ) ;
em . print ( " BC7 RGB Avg: " ) ;
s . m_bc7_rgb_avg_psnr = em . m_psnr ;
2021-05-07 15:00:41 +00:00
2022-03-24 19:39:24 +00:00
em . calc ( m_slice_images [ slice_index ] , m_decoded_output_textures_unpacked_bc7 [ slice_index ] , 0 , 4 ) ;
em . print ( " BC7 RGBA Avg: " ) ;
s . m_bc7_rgba_avg_psnr = em . m_psnr ;
2021-05-07 15:00:41 +00:00
2022-03-24 19:39:24 +00:00
em . calc ( m_slice_images [ slice_index ] , m_decoded_output_textures_unpacked_bc7 [ slice_index ] , 0 , 1 ) ;
em . print ( " BC7 R Avg: " ) ;
2021-05-07 15:00:41 +00:00
2022-03-24 19:39:24 +00:00
em . calc ( m_slice_images [ slice_index ] , m_decoded_output_textures_unpacked_bc7 [ slice_index ] , 1 , 1 ) ;
em . print ( " BC7 G Avg: " ) ;
2021-05-07 15:00:41 +00:00
2022-03-24 19:39:24 +00:00
em . calc ( m_slice_images [ slice_index ] , m_decoded_output_textures_unpacked_bc7 [ slice_index ] , 2 , 1 ) ;
em . print ( " BC7 B Avg: " ) ;
2021-05-07 15:00:41 +00:00
2022-03-24 19:39:24 +00:00
if ( m_params . m_uastc )
{
em . calc ( m_slice_images [ slice_index ] , m_decoded_output_textures_unpacked_bc7 [ slice_index ] , 3 , 1 ) ;
em . print ( " BC7 A Avg: " ) ;
2021-05-07 15:00:41 +00:00
2022-03-24 19:39:24 +00:00
s . m_bc7_a_avg_psnr = em . m_psnr ;
}
2021-05-07 15:00:41 +00:00
2022-03-24 19:39:24 +00:00
em . calc ( m_slice_images [ slice_index ] , m_decoded_output_textures_unpacked_bc7 [ slice_index ] , 0 , 0 ) ;
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 ) ;
2021-05-07 15:00:41 +00:00
2022-03-24 19:39:24 +00:00
em . calc ( m_slice_images [ slice_index ] , m_decoded_output_textures_unpacked_bc7 [ slice_index ] , 0 , 0 , true , true ) ;
em . print ( " BC7 601 Luma: " ) ;
s . m_bc7_luma_601_psnr = static_cast < float > ( em . m_psnr ) ;
}
2021-05-07 15:00:41 +00:00
2022-03-24 19:39:24 +00:00
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 ) ;
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 ) ;
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 ) ;
em . print ( " Unquantized ETC1S 601 Luma: " ) ;
s . m_best_etc1s_luma_601_psnr = static_cast < float > ( em . m_psnr ) ;
}
2021-05-07 15:00:41 +00:00
}
2022-03-24 19:39:24 +00:00
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 ) ;
2021-05-07 15:00:41 +00:00
2022-03-24 19:39:24 +00:00
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 ) )
2021-05-07 15:00:41 +00:00
{
2022-03-24 19:39:24 +00:00
// 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 ) ;
2021-05-07 15:00:41 +00:00
2022-03-24 19:39:24 +00:00
image best_etc1s_unpacked ;
best_etc1s_gpu_image . unpack ( best_etc1s_unpacked ) ;
save_png ( out_basename + " _best_etc1s.png " , best_etc1s_unpacked ) ;
}
2021-05-07 15:00:41 +00:00
}
2022-03-24 19:39:24 +00:00
if ( m_params . m_debug_images )
2021-05-07 15:00:41 +00:00
{
2022-03-24 19:39:24 +00:00
// 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 ) ;
2021-05-07 15:00:41 +00:00
2022-03-24 19:39:24 +00:00
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 ) ;
}
2021-05-07 15:00:41 +00:00
2022-03-24 19:39:24 +00:00
// 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 ) ;
2021-05-07 15:00:41 +00:00
2022-03-24 19:39:24 +00:00
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 ) ;
}
2021-05-07 15:00:41 +00:00
}
}
2022-03-24 19:39:24 +00:00
} // if (m_params.m_validate_output_data)
2021-05-07 15:00:41 +00:00
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 ;
}
static uint8_t g_ktx2_etc1s_nonalpha_dfd [ 44 ] = { 0x2C , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x2 , 0x0 , 0x28 , 0x0 , 0xA3 , 0x1 , 0x2 , 0x0 , 0x3 , 0x3 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x3F , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0xFF , 0xFF , 0xFF , 0xFF } ;
static uint8_t g_ktx2_etc1s_alpha_dfd [ 60 ] = { 0x3C , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x2 , 0x0 , 0x38 , 0x0 , 0xA3 , 0x1 , 0x2 , 0x0 , 0x3 , 0x3 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x3F , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0xFF , 0xFF , 0xFF , 0xFF , 0x40 , 0x0 , 0x3F , 0xF , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0xFF , 0xFF , 0xFF , 0xFF } ;
static uint8_t g_ktx2_uastc_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_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 } ;
void basis_compressor : : get_dfd ( uint8_vec & dfd , const basist : : ktx2_header & header )
{
const uint8_t * pDFD ;
uint32_t dfd_len ;
if ( m_params . m_uastc )
{
if ( m_any_source_image_has_alpha )
{
pDFD = g_ktx2_uastc_alpha_dfd ;
dfd_len = sizeof ( g_ktx2_uastc_alpha_dfd ) ;
}
else
{
pDFD = g_ktx2_uastc_nonalpha_dfd ;
dfd_len = sizeof ( g_ktx2_uastc_nonalpha_dfd ) ;
}
}
else
{
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 ) ;
uint32_t dfd_bits = basisu : : read_le_dword ( dfd . data ( ) + 3 * sizeof ( uint32_t ) ) ;
dfd_bits & = ~ ( 0xFF < < 16 ) ;
if ( m_params . m_ktx2_srgb_transfer_func )
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 ( 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 ) ;
}
// 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
if ( m_any_source_image_has_alpha )
dfd_chan0 | = ( basist : : KTX2_DF_CHANNEL_UASTC_RGBA < < 24 ) ;
else
dfd_chan0 | = ( basist : : KTX2_DF_CHANNEL_UASTC_RGB < < 24 ) ;
}
basisu : : write_le_dword ( dfd . data ( ) + 7 * sizeof ( uint32_t ) , dfd_chan0 ) ;
}
bool basis_compressor : : create_ktx2_file ( )
{
if ( m_params . m_uastc )
{
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 ( & 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 ;
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 ( m_params . m_uastc )
{
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 ) ; return false ;
}
}
basisu : : vector < uint8_vec > level_data_bytes ( total_levels ) ;
basisu : : vector < uint8_vec > compressed_level_data_bytes ( total_levels ) ;
uint_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_params . m_uastc )
append_vector ( level_data_bytes [ slice_desc . m_mip_index ] , m_uastc_backend_output . m_slice_image_data [ slice_index ] ) ;
else
append_vector ( level_data_bytes [ slice_desc . m_mip_index ] , backend_output . m_slice_image_data [ slice_index ] ) ;
}
// UASTC supercompression
if ( ( m_params . m_uastc ) & & ( 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 ) )
return false ;
compressed_level_data_bytes [ level_index ] . resize ( result ) ;
}
# else
// Can't get here
assert ( 0 ) ;
return false ;
# endif
}
else
{
// No supercompression
compressed_level_data_bytes = level_data_bytes ;
}
uint8_vec etc1s_global_data ;
// Create ETC1S global supercompressed data
if ( ! m_params . m_uastc )
{
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 ( 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 ( etc1s_global_data , ( const uint8_t * ) & etc1s_global_data_header , sizeof ( etc1s_global_data_header ) ) ;
append_vector ( etc1s_global_data , ( const uint8_t * ) etc1s_image_descs . data ( ) , etc1s_image_descs . size_in_bytes ( ) ) ;
append_vector ( etc1s_global_data , backend_output . m_endpoint_palette ) ;
append_vector ( etc1s_global_data , backend_output . m_selector_palette ) ;
append_vector ( etc1s_global_data , backend_output . m_slice_image_tables ) ;
header . m_supercompression_scheme = basist : : KTX2_SS_BASISLZ ;
}
// Key values
basist : : ktx2_transcoder : : key_value_vec key_values ( m_params . m_ktx2_key_values ) ;
key_values . enlarge ( 1 ) ;
const char * pKTXwriter = " KTXwriter " ;
key_values . back ( ) . m_key . resize ( strlen ( pKTXwriter ) + 1 ) ;
memcpy ( key_values . back ( ) . m_key . data ( ) , pKTXwriter , strlen ( pKTXwriter ) + 1 ) ;
char writer_id [ 128 ] ;
# ifdef _MSC_VER
sprintf_s ( writer_id , sizeof ( writer_id ) , " Basis Universal %s " , BASISU_LIB_VERSION_STRING ) ;
# else
snprintf ( writer_id , sizeof ( writer_id ) , " Basis Universal %s " , BASISU_LIB_VERSION_STRING ) ;
# endif
key_values . back ( ) . m_value . resize ( strlen ( writer_id ) + 1 ) ;
memcpy ( key_values . back ( ) . m_value . data ( ) , writer_id , strlen ( writer_id ) + 1 ) ;
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
uint8_vec dfd ;
get_dfd ( dfd , header ) ;
const uint32_t kvd_file_offset = sizeof ( header ) + sizeof ( basist : : ktx2_level_index ) * total_levels + 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 + 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 ;
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 ( 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 ( ) ) ;
// DFD
const uint8_t * pDFD = dfd . data ( ) ;
uint32_t dfd_len = 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 ) ;
// 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 ) ;
}
// Global Supercompressed Data
if ( etc1s_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 = etc1s_global_data . size ( ) ;
header . m_sgd_byte_offset = m_output_ktx2_file . size ( ) ;
append_vector ( m_output_ktx2_file , etc1s_global_data ) ;
}
// mipPadding
if ( header . m_supercompression_scheme = = basist : : KTX2_SS_NONE )
{
// We currently can't do this or the validator will incorrectly give an error.
uint32_t ofs = m_output_ktx2_file . size ( ) & 15 ;
uint32_t padding = ( 16 - ofs ) & 15 ;
// Make sure we're always aligned here (due to a validator bug).
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 ( m_params . m_uastc )
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 ( ) ) ;
debug_printf ( " Total .ktx2 output file size: %u \n " , m_output_ktx2_file . size ( ) ) ;
return true ;
}
2022-03-24 19:39:24 +00:00
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 = true ;
std : : atomic < bool > opencl_failed ;
opencl_failed = false ;
for ( uint32_t pindex = 0 ; pindex < params_vec . size ( ) ; pindex + + )
{
jpool . add_job ( [ pindex , & params_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 = true ;
if ( status )
{
basis_compressor : : error_code ec = c . process ( ) ;
if ( c . get_opencl_failed ( ) )
opencl_failed = 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 (
const basisu : : vector < image > & source_images ,
uint32_t flags_and_quality , float uastc_rdo_quality ,
size_t * pSize ,
image_stats * pStats )
{
// Check input parameters
if ( ( ! source_images . size ( ) ) | | ( ! pSize ) )
{
error_printf ( " basis_compress: Invalid parameter \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 , std : : thread : : hardware_concurrency ( ) ) ;
job_pool jp ( num_threads ) ;
// Initialize the compressor parameter struct
basis_compressor_params comp_params ;
comp_params . m_pJob_pool = & jp ;
comp_params . m_y_flip = ( flags_and_quality & cFlagYFlip ) ! = 0 ;
comp_params . m_debug = ( flags_and_quality & cFlagDebug ) ! = 0 ;
// Copy the largest mipmap level
comp_params . m_source_images . resize ( 1 ) ;
comp_params . m_source_images [ 0 ] = source_images [ 0 ] ;
// Copy the smaller mipmap levels, if any
if ( source_images . size ( ) > 1 )
{
comp_params . m_source_mipmap_images . resize ( 1 ) ;
comp_params . m_source_mipmap_images [ 0 ] . resize ( source_images . size ( ) - 1 ) ;
for ( uint32_t i = 1 ; i < source_images . size ( ) ; i + + )
comp_params . m_source_mipmap_images [ 0 ] [ i - 1 ] = source_images [ 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_files = false ;
comp_params . m_perceptual = ( flags_and_quality & cFlagSRGB ) ! = 0 ;
comp_params . m_mip_srgb = comp_params . m_perceptual ;
comp_params . m_mip_gen = ( flags_and_quality & ( cFlagGenMipsWrap | cFlagGenMipsClamp ) ) ! = 0 ;
comp_params . m_mip_wrapping = ( flags_and_quality & cFlagGenMipsWrap ) ! = 0 ;
comp_params . m_uastc = ( flags_and_quality & cFlagUASTC ) ! = 0 ;
if ( comp_params . m_uastc )
{
comp_params . m_pack_uastc_flags = flags_and_quality & cPackUASTCLevelMask ;
comp_params . m_rdo_uastc = ( flags_and_quality & cFlagUASTCRDO ) ! = 0 ;
comp_params . m_rdo_uastc_quality_scalar = uastc_rdo_quality ;
}
else
comp_params . m_quality_level = basisu : : maximum < uint32_t > ( 1 , flags_and_quality & 255 ) ;
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_ktx2_srgb_transfer_func = comp_params . m_perceptual ;
}
comp_params . m_compute_stats = ( pStats ! = nullptr ) ;
// 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 ;
}
// 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 (
const uint8_t * pImageRGBA , uint32_t width , uint32_t height , uint32_t pitch_in_pixels ,
uint32_t flags_and_quality , float uastc_rdo_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 ( source_image , flags_and_quality , uastc_rdo_quality , pSize , pStats ) ;
}
void basis_free_data ( void * p )
{
free ( p ) ;
}
2021-05-07 15:00:41 +00:00
} // namespace basisu