| // basisu_frontend.h | |
| // Copyright (C) 2019-2026 Binomial LLC. All Rights Reserved. | |
| // | |
| // Licensed under the Apache License, Version 2.0 (the "License"); | |
| // you may not use this file except in compliance with the License. | |
| // You may obtain a copy of the License at | |
| // | |
| // http://www.apache.org/licenses/LICENSE-2.0 | |
| // | |
| // Unless required by applicable law or agreed to in writing, software | |
| // distributed under the License is distributed on an "AS IS" BASIS, | |
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
| // See the License for the specific language governing permissions and | |
| // limitations under the License. | |
| #pragma once | |
| #include "basisu_enc.h" | |
| #include "basisu_etc.h" | |
| #include "basisu_gpu_texture.h" | |
| #include "../transcoder/basisu_file_headers.h" | |
| #include "../transcoder/basisu_transcoder.h" | |
| namespace basisu | |
| { | |
| struct opencl_context; | |
| typedef opencl_context* opencl_context_ptr; | |
| struct vec2U | |
| { | |
| uint32_t m_comps[2]; | |
| vec2U() { } | |
| vec2U(uint32_t a, uint32_t b) { set(a, b); } | |
| void set(uint32_t a, uint32_t b) { m_comps[0] = a; m_comps[1] = b; } | |
| uint32_t operator[] (uint32_t i) const { assert(i < 2); return m_comps[i]; } | |
| uint32_t &operator[] (uint32_t i) { assert(i < 2); return m_comps[i]; } | |
| }; | |
| // rg [11/25/25] - The command line tool defaults to ETC1S level 1, but the API 2. Changing this breaks backwards compatibility for anyone using the API and our test suite. | |
| const uint32_t BASISU_DEFAULT_ETC1S_COMPRESSION_LEVEL = 2; | |
| const uint32_t BASISU_MAX_ETC1S_COMPRESSION_LEVEL = 6; | |
| class basisu_frontend | |
| { | |
| BASISU_NO_EQUALS_OR_COPY_CONSTRUCT(basisu_frontend); | |
| public: | |
| basisu_frontend() : | |
| m_total_blocks(0), | |
| m_total_pixels(0), | |
| m_endpoint_refinement(false), | |
| m_use_hierarchical_endpoint_codebooks(false), | |
| m_use_hierarchical_selector_codebooks(false), | |
| m_num_endpoint_codebook_iterations(0), | |
| m_num_selector_codebook_iterations(0), | |
| m_opencl_failed(false) | |
| { | |
| } | |
| enum | |
| { | |
| cMaxEndpointClusters = 16128, | |
| cMaxSelectorClusters = 16128, | |
| }; | |
| struct params | |
| { | |
| params() : | |
| m_num_source_blocks(0), | |
| m_pSource_blocks(NULL), | |
| m_max_endpoint_clusters(256), | |
| m_max_selector_clusters(256), | |
| m_compression_level(BASISU_DEFAULT_ETC1S_COMPRESSION_LEVEL), | |
| m_perceptual(true), | |
| m_debug_stats(false), | |
| m_debug_images(false), | |
| m_dump_endpoint_clusterization(true), | |
| m_validate(false), | |
| m_multithreaded(false), | |
| m_disable_hierarchical_endpoint_codebooks(false), | |
| m_tex_type(basist::cBASISTexType2D), | |
| m_pOpenCL_context(nullptr), | |
| m_pJob_pool(nullptr) | |
| { | |
| } | |
| uint32_t m_num_source_blocks; | |
| pixel_block *m_pSource_blocks; | |
| uint32_t m_max_endpoint_clusters; | |
| uint32_t m_max_selector_clusters; | |
| uint32_t m_compression_level; | |
| bool m_perceptual; | |
| bool m_debug_stats; | |
| bool m_debug_images; | |
| bool m_dump_endpoint_clusterization; | |
| bool m_validate; | |
| bool m_multithreaded; | |
| bool m_disable_hierarchical_endpoint_codebooks; | |
| basist::basis_texture_type m_tex_type; | |
| const basist::basisu_lowlevel_etc1s_transcoder *m_pGlobal_codebooks; | |
| opencl_context_ptr m_pOpenCL_context; | |
| job_pool *m_pJob_pool; | |
| }; | |
| bool init(const params &p); | |
| bool compress(); | |
| const params &get_params() const { return m_params; } | |
| const pixel_block &get_source_pixel_block(uint32_t i) const { return m_source_blocks[i]; } | |
| // RDO output blocks | |
| uint32_t get_total_output_blocks() const { return static_cast<uint32_t>(m_encoded_blocks.size()); } | |
| const etc_block &get_output_block(uint32_t block_index) const { return m_encoded_blocks[block_index]; } | |
| const etc_block_vec &get_output_blocks() const { return m_encoded_blocks; } | |
| // "Best" ETC1S blocks | |
| const etc_block &get_etc1s_block(uint32_t block_index) const { return m_etc1_blocks_etc1s[block_index]; } | |
| // Per-block flags | |
| bool get_diff_flag(uint32_t block_index) const { return m_encoded_blocks[block_index].get_diff_bit(); } | |
| // Endpoint clusters | |
| uint32_t get_total_endpoint_clusters() const { return static_cast<uint32_t>(m_endpoint_clusters.size()); } | |
| uint32_t get_subblock_endpoint_cluster_index(uint32_t block_index, uint32_t subblock_index) const { return m_block_endpoint_clusters_indices[block_index][subblock_index]; } | |
| const color_rgba &get_endpoint_cluster_unscaled_color(uint32_t cluster_index, bool individual_mode) const { return m_endpoint_cluster_etc_params[cluster_index].m_color_unscaled[individual_mode]; } | |
| uint32_t get_endpoint_cluster_inten_table(uint32_t cluster_index, bool individual_mode) const { return m_endpoint_cluster_etc_params[cluster_index].m_inten_table[individual_mode]; } | |
| bool get_endpoint_cluster_color_is_used(uint32_t cluster_index, bool individual_mode) const { return m_endpoint_cluster_etc_params[cluster_index].m_color_used[individual_mode]; } | |
| // Selector clusters | |
| uint32_t get_total_selector_clusters() const { return static_cast<uint32_t>(m_selector_cluster_block_indices.size()); } | |
| uint32_t get_block_selector_cluster_index(uint32_t block_index) const { return m_block_selector_cluster_index[block_index]; } | |
| const etc_block &get_selector_cluster_selector_bits(uint32_t cluster_index) const { return m_optimized_cluster_selectors[cluster_index]; } | |
| // Returns block indices using each selector cluster | |
| const uint_vec &get_selector_cluster_block_indices(uint32_t selector_cluster_index) const { return m_selector_cluster_block_indices[selector_cluster_index]; } | |
| void dump_debug_image(const char *pFilename, uint32_t first_block, uint32_t num_blocks_x, uint32_t num_blocks_y, bool output_blocks); | |
| void reoptimize_remapped_endpoints(const uint_vec &new_block_endpoints, int_vec &old_to_new_endpoint_cluster_indices, bool optimize_final_codebook, uint_vec *pBlock_selector_indices = nullptr); | |
| bool get_opencl_failed() const { return m_opencl_failed; } | |
| private: | |
| params m_params; | |
| uint32_t m_total_blocks; | |
| uint32_t m_total_pixels; | |
| bool m_endpoint_refinement; | |
| bool m_use_hierarchical_endpoint_codebooks; | |
| bool m_use_hierarchical_selector_codebooks; | |
| uint32_t m_num_endpoint_codebook_iterations; | |
| uint32_t m_num_selector_codebook_iterations; | |
| // Source pixels for each blocks | |
| pixel_block_vec m_source_blocks; | |
| // The quantized ETC1S texture. | |
| etc_block_vec m_encoded_blocks; | |
| // Quantized blocks after endpoint quant, but before selector quant | |
| etc_block_vec m_orig_encoded_blocks; | |
| // Full quality ETC1S texture | |
| etc_block_vec m_etc1_blocks_etc1s; | |
| typedef vec<6, float> vec6F; | |
| // Endpoint clusterizer | |
| typedef tree_vector_quant<vec6F> vec6F_quantizer; | |
| vec6F_quantizer m_endpoint_clusterizer; | |
| // For each endpoint cluster: An array of which subblock indices (block_index*2+subblock) are located in that cluster. | |
| basisu::vector<uint_vec> m_endpoint_clusters; | |
| // Array of subblock indices for each parent endpoint cluster | |
| // Note: Initially, each endpoint cluster will only live in a single parent cluster, in a shallow tree. | |
| // As the endpoint clusters are manipulated this constraint gets broken. | |
| basisu::vector<uint_vec> m_endpoint_parent_clusters; | |
| // Each block's parent endpoint cluster index | |
| uint8_vec m_block_parent_endpoint_cluster; | |
| // Array of endpoint cluster indices for each parent endpoint cluster | |
| basisu::vector<uint_vec> m_endpoint_clusters_within_each_parent_cluster; | |
| struct endpoint_cluster_etc_params | |
| { | |
| endpoint_cluster_etc_params() | |
| { | |
| clear(); | |
| } | |
| void clear() | |
| { | |
| clear_obj(m_color_unscaled); | |
| clear_obj(m_inten_table); | |
| clear_obj(m_color_error); | |
| m_subblocks.clear(); | |
| clear_obj(m_color_used); | |
| m_valid = false; | |
| } | |
| // TODO: basisu doesn't use individual mode. | |
| color_rgba m_color_unscaled[2]; // [use_individual_mode] | |
| uint32_t m_inten_table[2]; | |
| uint64_t m_color_error[2]; | |
| uint_vec m_subblocks; | |
| bool m_color_used[2]; | |
| bool m_valid; | |
| bool operator== (const endpoint_cluster_etc_params &other) const | |
| { | |
| for (uint32_t i = 0; i < 2; i++) | |
| { | |
| if (m_color_unscaled[i] != other.m_color_unscaled[i]) | |
| return false; | |
| } | |
| if (m_inten_table[0] != other.m_inten_table[0]) | |
| return false; | |
| if (m_inten_table[1] != other.m_inten_table[1]) | |
| return false; | |
| return true; | |
| } | |
| bool operator< (const endpoint_cluster_etc_params &other) const | |
| { | |
| for (uint32_t i = 0; i < 2; i++) | |
| { | |
| if (m_color_unscaled[i] < other.m_color_unscaled[i]) | |
| return true; | |
| else if (m_color_unscaled[i] != other.m_color_unscaled[i]) | |
| return false; | |
| } | |
| if (m_inten_table[0] < other.m_inten_table[0]) | |
| return true; | |
| else if (m_inten_table[0] == other.m_inten_table[0]) | |
| { | |
| if (m_inten_table[1] < other.m_inten_table[1]) | |
| return true; | |
| } | |
| return false; | |
| } | |
| }; | |
| typedef basisu::vector<endpoint_cluster_etc_params> cluster_subblock_etc_params_vec; | |
| // Each endpoint cluster's ETC1S parameters | |
| cluster_subblock_etc_params_vec m_endpoint_cluster_etc_params; | |
| // The endpoint cluster index used by each ETC1 subblock. | |
| basisu::vector<vec2U> m_block_endpoint_clusters_indices; | |
| // The block(s) within each selector cluster | |
| // Note: If you add anything here that uses selector cluster indicies, be sure to update optimize_selector_codebook()! | |
| basisu::vector<uint_vec> m_selector_cluster_block_indices; | |
| // The selector bits for each selector cluster. | |
| basisu::vector<etc_block> m_optimized_cluster_selectors; | |
| // The block(s) within each parent selector cluster. | |
| basisu::vector<uint_vec> m_selector_parent_cluster_block_indices; | |
| // Each block's parent selector cluster | |
| uint8_vec m_block_parent_selector_cluster; | |
| // Array of selector cluster indices for each parent selector cluster | |
| basisu::vector<uint_vec> m_selector_clusters_within_each_parent_cluster; | |
| // Each block's selector cluster index | |
| basisu::vector<uint32_t> m_block_selector_cluster_index; | |
| struct subblock_endpoint_quant_err | |
| { | |
| uint64_t m_total_err; | |
| uint32_t m_cluster_index; | |
| uint32_t m_cluster_subblock_index; | |
| uint32_t m_block_index; | |
| uint32_t m_subblock_index; | |
| bool operator< (const subblock_endpoint_quant_err &rhs) const | |
| { | |
| if (m_total_err < rhs.m_total_err) | |
| return true; | |
| else if (m_total_err == rhs.m_total_err) | |
| { | |
| if (m_block_index < rhs.m_block_index) | |
| return true; | |
| else if (m_block_index == rhs.m_block_index) | |
| return m_subblock_index < rhs.m_subblock_index; | |
| } | |
| return false; | |
| } | |
| }; | |
| // The sorted subblock endpoint quant error for each endpoint cluster | |
| basisu::vector<subblock_endpoint_quant_err> m_subblock_endpoint_quant_err_vec; | |
| std::mutex m_lock; | |
| bool m_opencl_failed; | |
| //----------------------------------------------------------------------------- | |
| void init_etc1_images(); | |
| bool init_global_codebooks(); | |
| void init_endpoint_training_vectors(); | |
| void dump_endpoint_clusterization_visualization(const char *pFilename, bool vis_endpoint_colors); | |
| void generate_endpoint_clusters(); | |
| void compute_endpoint_subblock_error_vec(); | |
| void introduce_new_endpoint_clusters(); | |
| void generate_endpoint_codebook(uint32_t step); | |
| uint32_t refine_endpoint_clusterization(); | |
| void eliminate_redundant_or_empty_endpoint_clusters(); | |
| void generate_block_endpoint_clusters(); | |
| void compute_endpoint_clusters_within_each_parent_cluster(); | |
| void compute_selector_clusters_within_each_parent_cluster(); | |
| void create_initial_packed_texture(); | |
| void generate_selector_clusters(); | |
| void create_optimized_selector_codebook(uint32_t iter); | |
| void find_optimal_selector_clusters_for_each_block(); | |
| uint32_t refine_block_endpoints_given_selectors(); | |
| void finalize(); | |
| bool validate_endpoint_cluster_hierarchy(bool ensure_clusters_have_same_parents) const; | |
| bool validate_output() const; | |
| void introduce_special_selector_clusters(); | |
| void optimize_selector_codebook(); | |
| bool check_etc1s_constraints() const; | |
| }; | |
| } // namespace basisu |