431 lines
12 KiB
C++
431 lines
12 KiB
C++
|
// basisu_comp.h
|
||
|
// Copyright (C) 2019 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.
|
||
|
#pragma once
|
||
|
#include "basisu_frontend.h"
|
||
|
#include "basisu_backend.h"
|
||
|
#include "basisu_basis_file.h"
|
||
|
#include "transcoder/basisu_global_selector_palette.h"
|
||
|
#include "transcoder/basisu_transcoder.h"
|
||
|
|
||
|
namespace basisu
|
||
|
{
|
||
|
const uint32_t BASISU_MAX_SUPPORTED_TEXTURE_DIMENSION = 16384;
|
||
|
|
||
|
// Allow block's color distance to increase by 1.5 while searching for an alternative nearby endpoint.
|
||
|
const float BASISU_DEFAULT_ENDPOINT_RDO_THRESH = 1.5f;
|
||
|
|
||
|
// Allow block's color distance to increase by 1.25 while searching the selector history buffer for a close enough match.
|
||
|
const float BASISU_DEFAULT_SELECTOR_RDO_THRESH = 1.25f;
|
||
|
|
||
|
const int BASISU_DEFAULT_QUALITY = 128;
|
||
|
const float BASISU_DEFAULT_HYBRID_SEL_CB_QUALITY_THRESH = 2.0f;
|
||
|
|
||
|
const uint32_t BASISU_MAX_IMAGE_DIMENSION = 16384;
|
||
|
const uint32_t BASISU_QUALITY_MIN = 1;
|
||
|
const uint32_t BASISU_QUALITY_MAX = 255;
|
||
|
const uint32_t BASISU_MAX_ENDPOINT_CLUSTERS = basisu_frontend::cMaxEndpointClusters;
|
||
|
const uint32_t BASISU_MAX_SELECTOR_CLUSTERS = basisu_frontend::cMaxSelectorClusters;
|
||
|
|
||
|
const uint32_t BASISU_MAX_SLICES = 0xFFFFFF;
|
||
|
|
||
|
struct image_stats
|
||
|
{
|
||
|
image_stats()
|
||
|
{
|
||
|
clear();
|
||
|
}
|
||
|
|
||
|
void clear()
|
||
|
{
|
||
|
m_filename.clear();
|
||
|
m_width = 0;
|
||
|
m_height = 0;
|
||
|
|
||
|
m_basis_etc1s_rgb_avg_psnr = 0.0f;
|
||
|
m_basis_etc1s_luma_709_psnr = 0.0f;
|
||
|
m_basis_etc1s_luma_601_psnr = 0.0f;
|
||
|
m_basis_etc1s_luma_709_ssim = 0.0f;
|
||
|
|
||
|
m_basis_bc1_rgb_avg_psnr = 0.0f;
|
||
|
m_basis_bc1_luma_709_psnr = 0.0f;
|
||
|
m_basis_bc1_luma_601_psnr = 0.0f;
|
||
|
m_basis_bc1_luma_709_ssim = 0.0f;
|
||
|
|
||
|
m_best_rgb_avg_psnr = 0.0f;
|
||
|
m_best_luma_709_psnr = 0.0f;
|
||
|
m_best_luma_601_psnr = 0.0f;
|
||
|
m_best_luma_709_ssim = 0.0f;
|
||
|
}
|
||
|
|
||
|
std::string m_filename;
|
||
|
uint32_t m_width;
|
||
|
uint32_t m_height;
|
||
|
|
||
|
// .basis compressed
|
||
|
float m_basis_etc1s_rgb_avg_psnr;
|
||
|
float m_basis_etc1s_luma_709_psnr;
|
||
|
float m_basis_etc1s_luma_601_psnr;
|
||
|
float m_basis_etc1s_luma_709_ssim;
|
||
|
|
||
|
float m_basis_bc1_rgb_avg_psnr;
|
||
|
float m_basis_bc1_luma_709_psnr;
|
||
|
float m_basis_bc1_luma_601_psnr;
|
||
|
float m_basis_bc1_luma_709_ssim;
|
||
|
|
||
|
// Normal (highest quality) compressed ETC1S
|
||
|
float m_best_rgb_avg_psnr;
|
||
|
float m_best_luma_709_psnr;
|
||
|
float m_best_luma_601_psnr;
|
||
|
float m_best_luma_709_ssim;
|
||
|
};
|
||
|
|
||
|
template<bool def>
|
||
|
struct bool_param
|
||
|
{
|
||
|
bool_param() :
|
||
|
m_value(def),
|
||
|
m_changed(false)
|
||
|
{
|
||
|
}
|
||
|
|
||
|
void clear()
|
||
|
{
|
||
|
m_value = def;
|
||
|
m_changed = false;
|
||
|
}
|
||
|
|
||
|
operator bool() const
|
||
|
{
|
||
|
return m_value;
|
||
|
}
|
||
|
|
||
|
bool operator= (bool v)
|
||
|
{
|
||
|
m_value = v;
|
||
|
m_changed = true;
|
||
|
return m_value;
|
||
|
}
|
||
|
|
||
|
bool was_changed() const { return m_changed; }
|
||
|
void set_changed(bool flag) { m_changed = flag; }
|
||
|
|
||
|
bool m_value;
|
||
|
bool m_changed;
|
||
|
};
|
||
|
|
||
|
template<typename T>
|
||
|
struct param
|
||
|
{
|
||
|
param(T def, T min_v, T max_v) :
|
||
|
m_value(def),
|
||
|
m_def(def),
|
||
|
m_min(min_v),
|
||
|
m_max(max_v),
|
||
|
m_changed(false)
|
||
|
{
|
||
|
}
|
||
|
|
||
|
void clear()
|
||
|
{
|
||
|
m_value = m_def;
|
||
|
m_changed = false;
|
||
|
}
|
||
|
|
||
|
operator T() const
|
||
|
{
|
||
|
return m_value;
|
||
|
}
|
||
|
|
||
|
T operator= (T v)
|
||
|
{
|
||
|
m_value = clamp<T>(v, m_min, m_max);
|
||
|
m_changed = true;
|
||
|
return m_value;
|
||
|
}
|
||
|
|
||
|
T operator *= (T v)
|
||
|
{
|
||
|
m_value *= v;
|
||
|
m_changed = true;
|
||
|
return m_value;
|
||
|
}
|
||
|
|
||
|
bool was_changed() const { return m_changed; }
|
||
|
void set_changed(bool flag) { m_changed = flag; }
|
||
|
|
||
|
T m_value;
|
||
|
T m_def;
|
||
|
T m_min;
|
||
|
T m_max;
|
||
|
bool m_changed;
|
||
|
};
|
||
|
|
||
|
struct basis_compressor_params
|
||
|
{
|
||
|
basis_compressor_params() :
|
||
|
m_hybrid_sel_cb_quality_thresh(BASISU_DEFAULT_HYBRID_SEL_CB_QUALITY_THRESH, 0.0f, 1e+10f),
|
||
|
m_global_pal_bits(8, 0, ETC1_GLOBAL_SELECTOR_CODEBOOK_MAX_PAL_BITS),
|
||
|
m_global_mod_bits(8, 0, basist::etc1_global_palette_entry_modifier::cTotalBits),
|
||
|
m_endpoint_rdo_thresh(BASISU_DEFAULT_ENDPOINT_RDO_THRESH, 0.0f, 1e+10f),
|
||
|
m_selector_rdo_thresh(BASISU_DEFAULT_SELECTOR_RDO_THRESH, 0.0f, 1e+10f),
|
||
|
m_pSel_codebook(NULL),
|
||
|
m_max_endpoint_clusters(512),
|
||
|
m_max_selector_clusters(512),
|
||
|
m_quality_level(-1),
|
||
|
m_mip_scale(1.0f, .000125f, 4.0f),
|
||
|
m_mip_smallest_dimension(1, 1, 16384),
|
||
|
m_compression_level((int)BASISU_DEFAULT_COMPRESSION_LEVEL, 0, (int)BASISU_MAX_COMPRESSION_LEVEL),
|
||
|
m_pJob_pool(nullptr)
|
||
|
{
|
||
|
clear();
|
||
|
}
|
||
|
|
||
|
void clear()
|
||
|
{
|
||
|
m_pSel_codebook = NULL;
|
||
|
|
||
|
m_source_filenames.clear();
|
||
|
m_source_alpha_filenames.clear();
|
||
|
|
||
|
m_source_images.clear();
|
||
|
|
||
|
m_out_filename.clear();
|
||
|
|
||
|
m_y_flip.clear();
|
||
|
m_debug.clear();
|
||
|
m_debug_images.clear();
|
||
|
m_global_sel_pal.clear();
|
||
|
m_no_auto_global_sel_pal.clear();
|
||
|
m_no_hybrid_sel_cb.clear();
|
||
|
m_perceptual.clear();
|
||
|
m_no_selector_rdo.clear();
|
||
|
m_selector_rdo_thresh.clear();
|
||
|
m_read_source_images.clear();
|
||
|
m_write_output_basis_files.clear();
|
||
|
m_compression_level.clear();
|
||
|
m_compute_stats.clear();
|
||
|
m_check_for_alpha.clear();
|
||
|
m_force_alpha.clear();
|
||
|
m_multithreading.clear();
|
||
|
m_seperate_rg_to_color_alpha.clear();
|
||
|
m_hybrid_sel_cb_quality_thresh.clear();
|
||
|
m_global_pal_bits.clear();
|
||
|
m_global_mod_bits.clear();
|
||
|
m_disable_hierarchical_endpoint_codebooks.clear();
|
||
|
|
||
|
m_no_endpoint_rdo.clear();
|
||
|
m_endpoint_rdo_thresh.clear();
|
||
|
|
||
|
m_mip_gen.clear();
|
||
|
m_mip_scale.clear();
|
||
|
m_mip_filter = "kaiser";
|
||
|
m_mip_scale = 1.0f;
|
||
|
m_mip_srgb.clear();
|
||
|
m_mip_premultiplied.clear();
|
||
|
m_mip_renormalize.clear();
|
||
|
m_mip_wrapping.clear();
|
||
|
m_mip_smallest_dimension.clear();
|
||
|
|
||
|
m_max_endpoint_clusters = 0;
|
||
|
m_max_selector_clusters = 0;
|
||
|
m_quality_level = -1;
|
||
|
|
||
|
m_tex_type = basist::cBASISTexType2D;
|
||
|
m_userdata0 = 0;
|
||
|
m_userdata1 = 0;
|
||
|
m_us_per_frame = 0;
|
||
|
|
||
|
m_pJob_pool = nullptr;
|
||
|
}
|
||
|
|
||
|
// Pointer to the global selector codebook, or nullptr to not use a global selector codebook
|
||
|
const basist::etc1_global_selector_codebook *m_pSel_codebook;
|
||
|
|
||
|
// If m_read_source_images is true, m_source_filenames (and optionally m_source_alpha_filenames) contains the filenames of PNG images to read.
|
||
|
// Otherwise, the compressor processes the images in m_source_images.
|
||
|
std::vector<std::string> m_source_filenames;
|
||
|
std::vector<std::string> m_source_alpha_filenames;
|
||
|
|
||
|
std::vector<image> m_source_images;
|
||
|
// TODO: Allow caller to supply their own mipmaps
|
||
|
|
||
|
// Filename of the output basis file
|
||
|
std::string m_out_filename;
|
||
|
|
||
|
// The params are done this way so we can detect when the user has explictly changed them.
|
||
|
|
||
|
// Flip images across Y axis
|
||
|
bool_param<false> m_y_flip;
|
||
|
|
||
|
// Output debug information during compression
|
||
|
bool_param<false> m_debug;
|
||
|
|
||
|
// m_debug_images is pretty slow
|
||
|
bool_param<false> m_debug_images;
|
||
|
|
||
|
// Compression level, from 0 to BASISU_MAX_COMPRESSION_LEVEL (higher is slower)
|
||
|
param<int> m_compression_level;
|
||
|
|
||
|
bool_param<false> m_global_sel_pal;
|
||
|
bool_param<false> m_no_auto_global_sel_pal;
|
||
|
|
||
|
// Frontend/backend codec parameters
|
||
|
bool_param<false> m_no_hybrid_sel_cb;
|
||
|
|
||
|
// Use perceptual sRGB colorspace metrics (for normal maps, etc.)
|
||
|
bool_param<true> m_perceptual;
|
||
|
|
||
|
// Disable selector RDO, for faster compression but larger files
|
||
|
bool_param<false> m_no_selector_rdo;
|
||
|
param<float> m_selector_rdo_thresh;
|
||
|
|
||
|
bool_param<false> m_no_endpoint_rdo;
|
||
|
param<float> m_endpoint_rdo_thresh;
|
||
|
|
||
|
// Read source images from m_source_filenames/m_source_alpha_filenames
|
||
|
bool_param<false> m_read_source_images;
|
||
|
|
||
|
// Write the output basis file to disk using m_out_filename
|
||
|
bool_param<false> m_write_output_basis_files;
|
||
|
|
||
|
// Compute and display image metrics
|
||
|
bool_param<false> m_compute_stats;
|
||
|
|
||
|
// Check to see if any input image has an alpha channel, if so then the output basis file will have alpha channels
|
||
|
bool_param<true> m_check_for_alpha;
|
||
|
|
||
|
// Always put alpha slices in the output basis file, even when the input doesn't have alpha
|
||
|
bool_param<false> m_force_alpha;
|
||
|
bool_param<true> m_multithreading;
|
||
|
|
||
|
// Split the R channel to RGB and the G channel to alpha, then write a basis file with alpha channels
|
||
|
bool_param<false> m_seperate_rg_to_color_alpha;
|
||
|
|
||
|
bool_param<false> m_disable_hierarchical_endpoint_codebooks;
|
||
|
|
||
|
// Global/hybrid selector codebook parameters
|
||
|
param<float> m_hybrid_sel_cb_quality_thresh;
|
||
|
param<int> m_global_pal_bits;
|
||
|
param<int> m_global_mod_bits;
|
||
|
|
||
|
// mipmap generation parameters
|
||
|
bool_param<false> m_mip_gen;
|
||
|
param<float> m_mip_scale;
|
||
|
std::string m_mip_filter;
|
||
|
bool_param<false> m_mip_srgb;
|
||
|
bool_param<true> m_mip_premultiplied; // not currently supported
|
||
|
bool_param<false> m_mip_renormalize;
|
||
|
bool_param<true> m_mip_wrapping;
|
||
|
param<int> m_mip_smallest_dimension;
|
||
|
|
||
|
// Codebook size (quality) control.
|
||
|
// If m_quality_level != -1, it controls the quality level. It ranges from [0,255].
|
||
|
// Otherwise m_max_endpoint_clusters/m_max_selector_clusters controls the codebook sizes directly.
|
||
|
uint32_t m_max_endpoint_clusters;
|
||
|
uint32_t m_max_selector_clusters;
|
||
|
int m_quality_level;
|
||
|
|
||
|
// m_tex_type, m_userdata0, m_userdata1, m_framerate - These fields go directly into the Basis file header.
|
||
|
basist::basis_texture_type m_tex_type;
|
||
|
uint32_t m_userdata0;
|
||
|
uint32_t m_userdata1;
|
||
|
uint32_t m_us_per_frame;
|
||
|
|
||
|
job_pool *m_pJob_pool;
|
||
|
};
|
||
|
|
||
|
class basis_compressor
|
||
|
{
|
||
|
BASISU_NO_EQUALS_OR_COPY_CONSTRUCT(basis_compressor);
|
||
|
|
||
|
public:
|
||
|
basis_compressor();
|
||
|
|
||
|
bool init(const basis_compressor_params ¶ms);
|
||
|
|
||
|
enum error_code
|
||
|
{
|
||
|
cECSuccess = 0,
|
||
|
cECFailedReadingSourceImages,
|
||
|
cECFailedValidating,
|
||
|
cECFailedFrontEnd,
|
||
|
cECFailedFontendExtract,
|
||
|
cECFailedBackend,
|
||
|
cECFailedCreateBasisFile,
|
||
|
cECFailedWritingOutput
|
||
|
};
|
||
|
|
||
|
error_code process();
|
||
|
|
||
|
const uint8_vec &get_output_basis_file() const { return m_output_basis_file; }
|
||
|
const etc_block_vec &get_output_blocks() const { return m_output_blocks; }
|
||
|
|
||
|
const std::vector<image_stats> &get_stats() const { return m_stats; }
|
||
|
|
||
|
uint32_t get_basis_file_size() const { return m_basis_file_size; }
|
||
|
double get_basis_bits_per_texel() const { return m_basis_bits_per_texel; }
|
||
|
|
||
|
bool get_any_source_image_has_alpha() const { return m_any_source_image_has_alpha; }
|
||
|
|
||
|
private:
|
||
|
basis_compressor_params m_params;
|
||
|
|
||
|
std::vector<image> m_slice_images;
|
||
|
|
||
|
std::vector<image_stats> m_stats;
|
||
|
|
||
|
uint32_t m_basis_file_size;
|
||
|
double m_basis_bits_per_texel;
|
||
|
|
||
|
basisu_backend_slice_desc_vec m_slice_descs;
|
||
|
|
||
|
uint32_t m_total_blocks;
|
||
|
bool m_auto_global_sel_pal;
|
||
|
|
||
|
basisu_frontend m_frontend;
|
||
|
pixel_block_vec m_source_blocks;
|
||
|
|
||
|
std::vector<gpu_image> m_frontend_output_textures;
|
||
|
|
||
|
std::vector<gpu_image> m_best_etc1s_images;
|
||
|
std::vector<image> m_best_etc1s_images_unpacked;
|
||
|
|
||
|
basisu_backend m_backend;
|
||
|
|
||
|
basisu_file m_basis_file;
|
||
|
|
||
|
std::vector<gpu_image> m_decoded_output_textures;
|
||
|
std::vector<image> m_decoded_output_textures_unpacked;
|
||
|
std::vector<gpu_image> m_decoded_output_textures_bc1;
|
||
|
std::vector<image> m_decoded_output_textures_unpacked_bc1;
|
||
|
|
||
|
uint8_vec m_output_basis_file;
|
||
|
etc_block_vec m_output_blocks;
|
||
|
|
||
|
bool m_any_source_image_has_alpha;
|
||
|
|
||
|
bool read_source_images();
|
||
|
bool process_frontend();
|
||
|
bool extract_frontend_texture_data();
|
||
|
bool process_backend();
|
||
|
bool create_basis_file_and_transcode();
|
||
|
bool write_output_files_and_compute_stats();
|
||
|
bool generate_mipmaps(const image &img, std::vector<image> &mips, bool has_alpha);
|
||
|
bool validate_texture_type_constraints();
|
||
|
};
|
||
|
|
||
|
} // namespace basisu
|
||
|
|