// File: crn_comp.cpp // See Copyright Notice and license at the end of inc/crnlib.h #include "crn_core.h" #include "crn_console.h" #include "crn_comp.h" #include "crn_checksum.h" #define CRNLIB_CREATE_DEBUG_IMAGES 0 #define CRNLIB_ENABLE_DEBUG_MESSAGES 0 namespace crnlib { crn_comp::crn_comp() : m_pParams(NULL) { } crn_comp::~crn_comp() { } bool crn_comp::pack_color_endpoints(crnlib::vector& packed_data, const crnlib::vector& remapping) { crnlib::vector remapped_endpoints(m_color_endpoints.size()); for (uint i = 0; i < m_color_endpoints.size(); i++) remapped_endpoints[remapping[i]] = m_color_endpoints[i]; const uint component_limits[6] = {31, 63, 31, 31, 63, 31}; symbol_histogram hist[2]; hist[0].resize(32); hist[1].resize(64); crnlib::vector residual_syms; residual_syms.reserve(m_color_endpoints.size() * 2 * 3); color_quad_u8 prev[2]; prev[0].clear(); prev[1].clear(); int total_residuals = 0; for (uint endpoint_index = 0; endpoint_index < m_color_endpoints.size(); endpoint_index++) { const uint endpoint = remapped_endpoints[endpoint_index]; color_quad_u8 cur[2]; cur[0] = dxt1_block::unpack_color((uint16)(endpoint & 0xFFFF), false); cur[1] = dxt1_block::unpack_color((uint16)((endpoint >> 16) & 0xFFFF), false); for (uint j = 0; j < 2; j++) { for (uint k = 0; k < 3; k++) { int delta = cur[j][k] - prev[j][k]; total_residuals += delta * delta; int sym = delta & component_limits[j * 3 + k]; int table = (k == 1) ? 1 : 0; hist[table].inc_freq(sym); residual_syms.push_back(sym); } } prev[0] = cur[0]; prev[1] = cur[1]; } static_huffman_data_model residual_dm[2]; symbol_codec codec; codec.start_encoding(1024 * 1024); // Transmit residuals for (uint i = 0; i < 2; i++) { if (!residual_dm[i].init(true, hist[i], 15)) return false; if (!codec.encode_transmit_static_huffman_data_model(residual_dm[i], false)) return false; } for (uint i = 0; i < residual_syms.size(); i++) { const uint sym = residual_syms[i]; const uint table = ((i % 3) == 1) ? 1 : 0; codec.encode(sym, residual_dm[table]); } codec.stop_encoding(false); packed_data.swap(codec.get_encoding_buf()); return true; } bool crn_comp::pack_color_endpoints_etc(crnlib::vector& packed_data, const crnlib::vector& remapping) { crnlib::vector remapped_endpoints(m_color_endpoints.size()); for (uint i = 0; i < m_color_endpoints.size(); i++) remapped_endpoints[remapping[i]] = (m_color_endpoints[i] & 0x07000000) | (m_color_endpoints[i] >> 3 & 0x001F1F1F); symbol_histogram hist(32); for (uint32 prev_endpoint = 0, p = 0; p < remapped_endpoints.size(); p++) { for (uint32 _e = prev_endpoint, e = prev_endpoint = remapped_endpoints[p], c = 0; c < 4; c++, _e >>= 8, e >>= 8) hist.inc_freq((e - _e) & 0x1F); } static_huffman_data_model dm; dm.init(true, hist, 15); symbol_codec codec; codec.start_encoding(1024 * 1024); codec.encode_transmit_static_huffman_data_model(dm, false); for (uint32 prev_endpoint = 0, p = 0; p < remapped_endpoints.size(); p++) { for (uint32 _e = prev_endpoint, e = prev_endpoint = remapped_endpoints[p], c = 0; c < 4; c++, _e >>= 8, e >>= 8) codec.encode((e - _e) & 0x1F, dm); } codec.stop_encoding(false); packed_data.swap(codec.get_encoding_buf()); return true; } bool crn_comp::pack_alpha_endpoints(crnlib::vector& packed_data, const crnlib::vector& remapping) { crnlib::vector remapped_endpoints(m_alpha_endpoints.size()); for (uint i = 0; i < m_alpha_endpoints.size(); i++) remapped_endpoints[remapping[i]] = m_alpha_endpoints[i]; symbol_histogram hist; hist.resize(256); crnlib::vector residual_syms; residual_syms.reserve(m_alpha_endpoints.size() * 2 * 3); uint prev[2]; utils::zero_object(prev); int total_residuals = 0; for (uint endpoint_index = 0; endpoint_index < m_alpha_endpoints.size(); endpoint_index++) { const uint endpoint = remapped_endpoints[endpoint_index]; uint cur[2]; cur[0] = dxt5_block::unpack_endpoint(endpoint, 0); cur[1] = dxt5_block::unpack_endpoint(endpoint, 1); for (uint j = 0; j < 2; j++) { int delta = cur[j] - prev[j]; total_residuals += delta * delta; int sym = delta & 255; hist.inc_freq(sym); residual_syms.push_back(sym); } prev[0] = cur[0]; prev[1] = cur[1]; } static_huffman_data_model residual_dm; symbol_codec codec; codec.start_encoding(1024 * 1024); // Transmit residuals if (!residual_dm.init(true, hist, 15)) return false; if (!codec.encode_transmit_static_huffman_data_model(residual_dm, false)) return false; for (uint i = 0; i < residual_syms.size(); i++) { const uint sym = residual_syms[i]; codec.encode(sym, residual_dm); } codec.stop_encoding(false); packed_data.swap(codec.get_encoding_buf()); return true; } bool crn_comp::pack_color_selectors(crnlib::vector& packed_data, const crnlib::vector& remapping) { crnlib::vector remapped_selectors(m_color_selectors.size()); for (uint i = 0; i < m_color_selectors.size(); i++) remapped_selectors[remapping[i]] = m_color_selectors[i]; symbol_histogram hist(16); for (uint32 c, selector, prev_selector = 0, i = 0; i < remapped_selectors.size(); i++) { for (selector = prev_selector ^ remapped_selectors[i], prev_selector ^= selector, c = 8; c; c--, selector >>= 4) hist.inc_freq(selector & 0xF); } static_huffman_data_model dm; dm.init(true, hist, 15); symbol_codec codec; codec.start_encoding(1024 * 1024); codec.encode_transmit_static_huffman_data_model(dm, false); for (uint32 c, selector, prev_selector = 0, i = 0; i < remapped_selectors.size(); i++) { for (selector = prev_selector ^ remapped_selectors[i], prev_selector ^= selector, c = 8; c; c--, selector >>= 4) codec.encode(selector & 0xF, dm); } codec.stop_encoding(false); packed_data.swap(codec.get_encoding_buf()); return true; } bool crn_comp::pack_alpha_selectors(crnlib::vector& packed_data, const crnlib::vector& remapping) { crnlib::vector remapped_selectors(m_alpha_selectors.size()); for (uint i = 0; i < m_alpha_selectors.size(); i++) remapped_selectors[remapping[i]] = m_alpha_selectors[i]; symbol_histogram hist(64); for (uint64 c, selector, prev_selector = 0, i = 0; i < remapped_selectors.size(); i++) { for (selector = prev_selector ^ remapped_selectors[i], prev_selector ^= selector, c = 8; c; c--, selector >>= 6) hist.inc_freq(selector & 0x3F); } static_huffman_data_model dm; dm.init(true, hist, 15); symbol_codec codec; codec.start_encoding(1024 * 1024); codec.encode_transmit_static_huffman_data_model(dm, false); for (uint64 c, selector, prev_selector = 0, i = 0; i < remapped_selectors.size(); i++) { for (selector = prev_selector ^ remapped_selectors[i], prev_selector ^= selector, c = 8; c; c--, selector >>= 6) codec.encode(selector & 0x3F, dm); } codec.stop_encoding(false); packed_data.swap(codec.get_encoding_buf()); return true; } bool crn_comp::pack_blocks( uint group, bool clear_histograms, symbol_codec* pCodec, const crnlib::vector* pColor_endpoint_remap, const crnlib::vector* pColor_selector_remap, const crnlib::vector* pAlpha_endpoint_remap, const crnlib::vector* pAlpha_selector_remap ) { if (!pCodec) { m_reference_hist.resize(256); if (clear_histograms) m_reference_hist.set_all(0); if (pColor_endpoint_remap) { m_endpoint_index_hist[0].resize(pColor_endpoint_remap->size()); if (clear_histograms) m_endpoint_index_hist[0].set_all(0); } if (pColor_selector_remap) { m_selector_index_hist[0].resize(pColor_selector_remap->size()); if (clear_histograms) m_selector_index_hist[0].set_all(0); } if (pAlpha_endpoint_remap) { m_endpoint_index_hist[1].resize(pAlpha_endpoint_remap->size()); if (clear_histograms) m_endpoint_index_hist[1].set_all(0); } if (pAlpha_selector_remap) { m_selector_index_hist[1].resize(pAlpha_selector_remap->size()); if (clear_histograms) m_selector_index_hist[1].set_all(0); } } uint endpoint_index[cNumComps] = {}; const crnlib::vector* endpoint_remap[cNumComps] = {}; const crnlib::vector* selector_remap[cNumComps] = {}; for (uint c = 0; c < cNumComps; c++) { if (m_has_comp[c]) { endpoint_remap[c] = c ? pAlpha_endpoint_remap : pColor_endpoint_remap; selector_remap[c] = c ? pAlpha_selector_remap : pColor_selector_remap; } } uint block_width = m_levels[group].block_width; for (uint by = 0, b = m_levels[group].first_block, bEnd = b + m_levels[group].num_blocks; b < bEnd; by++) { for (uint bx = 0; bx < block_width; bx++, b++) { const bool secondary_etc_subblock = m_has_subblocks && bx & 1; if (!(by & 1) && !(bx & 1)) { uint8 reference_group = m_endpoint_indices[b].reference | m_endpoint_indices[b + block_width].reference << 2 | m_endpoint_indices[b + 1].reference << 4 | m_endpoint_indices[b + block_width + 1].reference << 6; if (pCodec) pCodec->encode(reference_group, m_reference_dm); else m_reference_hist.inc_freq(reference_group); } for (uint c = 0, cEnd = secondary_etc_subblock ? cAlpha0 : cNumComps; c < cEnd; c++) { if (endpoint_remap[c]) { uint index = (*endpoint_remap[c])[m_endpoint_indices[b].component[c]]; if (secondary_etc_subblock ? m_endpoint_indices[b].reference : !m_endpoint_indices[b].reference) { int sym = index - endpoint_index[c]; if (sym < 0) sym += endpoint_remap[c]->size(); if (!pCodec) m_endpoint_index_hist[c ? 1 : 0].inc_freq(sym); else pCodec->encode(sym, m_endpoint_index_dm[c ? 1 : 0]); } endpoint_index[c] = index; } } for (uint c = 0, cEnd = secondary_etc_subblock ? 0 : cNumComps; c < cEnd; c++) { if (selector_remap[c]) { uint index = (*selector_remap[c])[m_selector_indices[b].component[c]]; if (!pCodec) m_selector_index_hist[c ? 1 : 0].inc_freq(index); else pCodec->encode(index, m_selector_index_dm[c ? 1 : 0]); } } } } return true; } bool crn_comp::alias_images() { for (uint face_index = 0; face_index < m_pParams->m_faces; face_index++) { for (uint level_index = 0; level_index < m_pParams->m_levels; level_index++) { const uint width = math::maximum(1U, m_pParams->m_width >> level_index); const uint height = math::maximum(1U, m_pParams->m_height >> level_index); if (!m_pParams->m_pImages[face_index][level_index]) return false; m_images[face_index][level_index].alias((color_quad_u8*)m_pParams->m_pImages[face_index][level_index], width, height); } } image_utils::conversion_type conv_type = image_utils::get_image_conversion_type_from_crn_format((crn_format)m_pParams->m_format); if (conv_type != image_utils::cConversion_Invalid) { for (uint face_index = 0; face_index < m_pParams->m_faces; face_index++) { for (uint level_index = 0; level_index < m_pParams->m_levels; level_index++) { image_u8 cooked_image(m_images[face_index][level_index]); image_utils::convert_image(cooked_image, conv_type); m_images[face_index][level_index].swap(cooked_image); } } } m_levels.resize(m_pParams->m_levels); m_total_blocks = 0; for (uint level = 0; level < m_pParams->m_levels; level++) { uint blockHeight = ((math::maximum(1U, m_pParams->m_height >> level) + 7) & ~7) >> 2; m_levels[level].block_width = ((math::maximum(1U, m_pParams->m_width >> level) + 7) & ~7) >> (m_has_subblocks ? 1 : 2); m_levels[level].first_block = m_total_blocks; m_levels[level].num_blocks = m_pParams->m_faces * m_levels[level].block_width * blockHeight; m_total_blocks += m_levels[level].num_blocks; } return true; } void crn_comp::clear() { m_pParams = NULL; for (uint f = 0; f < cCRNMaxFaces; f++) for (uint l = 0; l < cCRNMaxLevels; l++) m_images[f][l].clear(); utils::zero_object(m_has_comp); m_has_etc_color_blocks = false; m_has_subblocks = false; m_levels.clear(); m_total_blocks = 0; m_color_endpoints.clear(); m_alpha_endpoints.clear(); m_color_selectors.clear(); m_alpha_selectors.clear(); m_endpoint_indices.clear(); m_selector_indices.clear(); utils::zero_object(m_crn_header); m_comp_data.clear(); m_hvq.clear(); m_reference_hist.clear(); m_reference_dm.clear(); for (uint i = 0; i < 2; i++) { m_endpoint_remaping[i].clear(); m_endpoint_index_hist[i].clear(); m_endpoint_index_dm[i].clear(); m_selector_remaping[i].clear(); m_selector_index_hist[i].clear(); m_selector_index_dm[i].clear(); } for (uint i = 0; i < cCRNMaxLevels; i++) m_packed_blocks[i].clear(); m_packed_data_models.clear(); m_packed_color_endpoints.clear(); m_packed_color_selectors.clear(); m_packed_alpha_endpoints.clear(); m_packed_alpha_selectors.clear(); } bool crn_comp::quantize_images() { dxt_hc::params params; params.m_adaptive_tile_alpha_psnr_derating = m_pParams->m_crn_adaptive_tile_alpha_psnr_derating; params.m_adaptive_tile_color_psnr_derating = m_pParams->m_crn_adaptive_tile_color_psnr_derating; if (m_pParams->m_flags & cCRNCompFlagManualPaletteSizes) { params.m_color_endpoint_codebook_size = math::clamp(m_pParams->m_crn_color_endpoint_palette_size, cCRNMinPaletteSize, cCRNMaxPaletteSize); params.m_color_selector_codebook_size = math::clamp(m_pParams->m_crn_color_selector_palette_size, cCRNMinPaletteSize, cCRNMaxPaletteSize); params.m_alpha_endpoint_codebook_size = math::clamp(m_pParams->m_crn_alpha_endpoint_palette_size, cCRNMinPaletteSize, cCRNMaxPaletteSize); params.m_alpha_selector_codebook_size = math::clamp(m_pParams->m_crn_alpha_selector_palette_size, cCRNMinPaletteSize, cCRNMaxPaletteSize); } else { uint max_codebook_entries = ((m_pParams->m_width + 3) / 4) * ((m_pParams->m_height + 3) / 4); max_codebook_entries = math::clamp(max_codebook_entries, cCRNMinPaletteSize, cCRNMaxPaletteSize); float quality = math::clamp((float)m_pParams->m_quality_level / cCRNMaxQualityLevel, 0.0f, 1.0f); float color_quality_power_mul = 1.0f; float alpha_quality_power_mul = 1.0f; if (m_has_etc_color_blocks) { color_quality_power_mul = m_has_subblocks ? 1.31f : 0.7f; params.m_adaptive_tile_color_psnr_derating = m_has_subblocks ? 5.0f : 2.0f; } if (m_pParams->m_format == cCRNFmtDXT5_CCxY) { color_quality_power_mul = 3.5f; alpha_quality_power_mul = .35f; params.m_adaptive_tile_color_psnr_derating = 5.0f; } else if (m_pParams->m_format == cCRNFmtDXT5) { color_quality_power_mul = .75f; } else if (m_pParams->m_format == cCRNFmtETC2A) { alpha_quality_power_mul = .9f; } float color_endpoint_quality = powf(quality, 1.8f * color_quality_power_mul); float color_selector_quality = powf(quality, 1.65f * color_quality_power_mul); params.m_color_endpoint_codebook_size = math::clamp(math::float_to_uint(.5f + math::lerp(math::maximum(64, cCRNMinPaletteSize), (float)max_codebook_entries, color_endpoint_quality)), cCRNMinPaletteSize, cCRNMaxPaletteSize); params.m_color_selector_codebook_size = math::clamp(math::float_to_uint(.5f + math::lerp(math::maximum(96, cCRNMinPaletteSize), (float)max_codebook_entries, color_selector_quality)), cCRNMinPaletteSize, cCRNMaxPaletteSize); float alpha_endpoint_quality = powf(quality, 2.1f * alpha_quality_power_mul); float alpha_selector_quality = powf(quality, 1.65f * alpha_quality_power_mul); params.m_alpha_endpoint_codebook_size = math::clamp(math::float_to_uint(.5f + math::lerp(math::maximum(24, cCRNMinPaletteSize), (float)max_codebook_entries, alpha_endpoint_quality)), cCRNMinPaletteSize, cCRNMaxPaletteSize); params.m_alpha_selector_codebook_size = math::clamp(math::float_to_uint(.5f + math::lerp(math::maximum(48, cCRNMinPaletteSize), (float)max_codebook_entries, alpha_selector_quality)), cCRNMinPaletteSize, cCRNMaxPaletteSize); } if (m_pParams->m_flags & cCRNCompFlagDebugging) { console::debug("Color endpoints: %u", params.m_color_endpoint_codebook_size); console::debug("Color selectors: %u", params.m_color_selector_codebook_size); console::debug("Alpha endpoints: %u", params.m_alpha_endpoint_codebook_size); console::debug("Alpha selectors: %u", params.m_alpha_selector_codebook_size); } params.m_hierarchical = (m_pParams->m_flags & cCRNCompFlagHierarchical) != 0; params.m_perceptual = (m_pParams->m_flags & cCRNCompFlagPerceptual) != 0; params.m_pProgress_func = m_pParams->m_pProgress_func; params.m_pProgress_func_data = m_pParams->m_pProgress_func_data; switch (m_pParams->m_format) { case cCRNFmtDXT1: { params.m_format = cDXT1; m_has_comp[cColor] = true; break; } case cCRNFmtDXT3: { m_has_comp[cAlpha0] = true; return false; } case cCRNFmtDXT5: { params.m_format = cDXT5; params.m_alpha_component_indices[0] = m_pParams->m_alpha_component; m_has_comp[cColor] = true; m_has_comp[cAlpha0] = true; break; } case cCRNFmtDXT5_CCxY: { params.m_format = cDXT5; params.m_alpha_component_indices[0] = 3; m_has_comp[cColor] = true; m_has_comp[cAlpha0] = true; params.m_perceptual = false; //params.m_adaptive_tile_color_alpha_weighting_ratio = 1.0f; params.m_adaptive_tile_color_alpha_weighting_ratio = 1.5f; break; } case cCRNFmtDXT5_xGBR: case cCRNFmtDXT5_AGBR: case cCRNFmtDXT5_xGxR: { params.m_format = cDXT5; params.m_alpha_component_indices[0] = 3; m_has_comp[cColor] = true; m_has_comp[cAlpha0] = true; params.m_perceptual = false; break; } case cCRNFmtDXN_XY: { params.m_format = cDXN_XY; params.m_alpha_component_indices[0] = 0; params.m_alpha_component_indices[1] = 1; m_has_comp[cAlpha0] = true; m_has_comp[cAlpha1] = true; params.m_perceptual = false; break; } case cCRNFmtDXN_YX: { params.m_format = cDXN_YX; params.m_alpha_component_indices[0] = 1; params.m_alpha_component_indices[1] = 0; m_has_comp[cAlpha0] = true; m_has_comp[cAlpha1] = true; params.m_perceptual = false; break; } case cCRNFmtDXT5A: { params.m_format = cDXT5A; params.m_alpha_component_indices[0] = m_pParams->m_alpha_component; m_has_comp[cAlpha0] = true; params.m_perceptual = false; break; } case cCRNFmtETC1: { params.m_format = cETC1; m_has_comp[cColor] = true; break; } case cCRNFmtETC2: { params.m_format = cETC2; m_has_comp[cColor] = true; break; } case cCRNFmtETC2A: { params.m_format = cETC2A; params.m_alpha_component_indices[0] = m_pParams->m_alpha_component; m_has_comp[cColor] = true; m_has_comp[cAlpha0] = true; break; } case cCRNFmtETC1S: { params.m_format = cETC1S; m_has_comp[cColor] = true; break; } case cCRNFmtETC2AS: { params.m_format = cETC2AS; params.m_alpha_component_indices[0] = m_pParams->m_alpha_component; m_has_comp[cColor] = true; m_has_comp[cAlpha0] = true; break; } default: { return false; } } params.m_debugging = (m_pParams->m_flags & cCRNCompFlagDebugging) != 0; params.m_pTask_pool = &m_task_pool; params.m_num_levels = m_pParams->m_levels; for (uint i = 0; i < m_pParams->m_levels; i++) { params.m_levels[i].m_first_block = m_levels[i].first_block; params.m_levels[i].m_num_blocks = m_levels[i].num_blocks; params.m_levels[i].m_block_width = m_levels[i].block_width; params.m_levels[i].m_weight = math::minimum(12.0f, powf(1.3f, (float)i)); } params.m_num_faces = m_pParams->m_faces; params.m_num_blocks = m_total_blocks; color_quad_u8 (*blocks)[16] = (color_quad_u8(*)[16])crnlib_malloc(params.m_num_blocks * 16 * sizeof(color_quad_u8)); for (uint b = 0, level = 0; level < m_pParams->m_levels; level++) { for (uint face = 0; face < m_pParams->m_faces; face++) { image_u8& image = m_images[face][level]; uint width = image.get_width(); uint height = image.get_height(); uint blockWidth = ((width + 7) & ~7) >> 2; uint blockHeight = ((height + 7) & ~7) >> 2; for (uint by = 0; by < blockHeight; by++) { for (uint y0 = by << 2, bx = 0; bx < blockWidth; bx++, b++) { for (uint t = 0, x0 = bx << 2, dy = 0; dy < 4; dy++) { for (uint y = math::minimum(y0 + dy, height - 1), dx = 0; dx < 4; dx++, t++) blocks[b][t] = image(math::minimum(x0 + dx, width - 1), y); } } } } } bool result = m_hvq.compress(blocks, m_endpoint_indices, m_selector_indices, m_color_endpoints, m_alpha_endpoints, m_color_selectors, m_alpha_selectors, params); crnlib_free(blocks); return result; } struct optimize_color_params { struct unpacked_endpoint { color_quad_u8 low, high; }; const unpacked_endpoint* unpacked_endpoints; const uint* hist; uint16 n; uint16 selected; float weight; struct result { crnlib::vector endpoint_remapping; crnlib::vector packed_endpoints; uint total_bits; } *pResult; }; static void sort_color_endpoints(crnlib::vector& remapping, const optimize_color_params::unpacked_endpoint* unpacked_endpoints, uint16 n) { remapping.resize(n); crnlib::vector endpoints(n); crnlib::vector indices(n); for (uint16 i = 0; i < n; i++) { endpoints[i] = unpacked_endpoints[i]; indices[i] = i; } optimize_color_params::unpacked_endpoint selected_endpoint = {color_quad_u8(0), color_quad_u8(0)}; for (uint16 left = n; left;) { uint16 selected_index = 0; uint min_error = cUINT32_MAX; for (uint16 i = 0; i < left; i++) { optimize_color_params::unpacked_endpoint& endpoint = endpoints[i]; uint error = color::elucidian_distance(endpoint.low, selected_endpoint.low, false) + color::elucidian_distance(endpoint.high, selected_endpoint.high, false); if (error < min_error) { min_error = error; selected_index = i; } } selected_endpoint = endpoints[selected_index]; remapping[indices[selected_index]] = n - left; left--; endpoints[selected_index] = endpoints[left]; indices[selected_index] = indices[left]; } } static void remap_color_endpoints(uint16* remapping, const optimize_color_params::unpacked_endpoint* unpacked_endpoints, const uint* hist, uint16 n, uint16 selected, float weight) { struct Node { uint index, frequency, front_similarity, back_similarity; optimize_color_params::unpacked_endpoint e; Node() { utils::zero_object(*this); } }; crnlib::vector remaining(n); for (uint16 i = 0; i < n; i++) { remaining[i].index = i; remaining[i].e = unpacked_endpoints[i]; } crnlib::vector chosen(n << 1); uint remaining_count = n, chosen_front = n, chosen_back = chosen_front; chosen[chosen_front] = selected; optimize_color_params::unpacked_endpoint front_e = remaining[selected].e, back_e = front_e; bool front_updated = true, back_updated = true; remaining[selected] = remaining[--remaining_count]; const uint* frequency = hist + selected * n; for (uint similarity_base = (uint)(4000 * (1.0f + weight)), frequency_normalizer = 0; remaining_count;) { uint64 best_value = 0; uint best_index = 0; for (uint i = 0; i < remaining_count; i++) { Node& node = remaining[i]; node.frequency += frequency[node.index]; if (front_updated) node.front_similarity = similarity_base - math::minimum(4000, color::elucidian_distance(node.e.low, front_e.low, false) + color::elucidian_distance(node.e.high, front_e.high, false)); if (back_updated) node.back_similarity = similarity_base - math::minimum(4000, color::elucidian_distance(node.e.low, back_e.low, false) + color::elucidian_distance(node.e.high, back_e.high, false)); uint64 value = math::maximum(node.front_similarity, node.back_similarity) * (node.frequency + frequency_normalizer) + 1; if (value > best_value || (value == best_value && node.index < selected)) { best_value = value; best_index = i; selected = node.index; } } frequency = hist + selected * n; uint frequency_front = 0, frequency_back = 0; for (int front = chosen_front, back = chosen_back, scale = back - front; scale > 0; front++, back--, scale -= 2) { frequency_front += scale * frequency[chosen[front]]; frequency_back += scale * frequency[chosen[back]]; } front_updated = back_updated = false; Node& best_node = remaining[best_index]; frequency_normalizer = best_node.frequency << 3; if ((uint64)best_node.front_similarity * frequency_front > (uint64)best_node.back_similarity * frequency_back) { chosen[--chosen_front] = selected; front_e = best_node.e; front_updated = true; } else { chosen[++chosen_back] = selected; back_e = best_node.e; back_updated = true; } best_node = remaining[--remaining_count]; } for (uint16 i = chosen_front; i <= chosen_back; i++) remapping[chosen[i]] = i - chosen_front; } void crn_comp::optimize_color_endpoints_task(uint64 data, void* pData_ptr) { optimize_color_params* pParams = reinterpret_cast(pData_ptr); crnlib::vector& remapping = pParams->pResult->endpoint_remapping; uint16 n = pParams->n; remapping.resize(n); if (data) { remap_color_endpoints(remapping.get_ptr(), pParams->unpacked_endpoints, pParams->hist, n, pParams->selected, pParams->weight); } else { sort_color_endpoints(remapping, pParams->unpacked_endpoints, n); optimize_color_selectors(); } m_has_etc_color_blocks ? pack_color_endpoints_etc(pParams->pResult->packed_endpoints, remapping) : pack_color_endpoints(pParams->pResult->packed_endpoints, remapping); uint total_bits = pParams->pResult->packed_endpoints.size() << 3; crnlib::vector hist(n); for (uint level = 0; level < m_levels.size(); level++) { for (uint endpoint_index = 0, b = m_levels[level].first_block, bEnd = b + m_levels[level].num_blocks; b < bEnd; b++) { uint index = remapping[m_endpoint_indices[b].component[cColor]]; if (m_has_subblocks && b & 1 ? m_endpoint_indices[b].reference : !m_endpoint_indices[b].reference) { int sym = index - endpoint_index; hist[sym < 0 ? sym + n : sym]++; } endpoint_index = index; } } static_huffman_data_model dm; dm.init(true, n, hist.get_ptr(), 16); const uint8* code_sizes = dm.get_code_sizes(); for (uint16 s = 0; s < n; s++) total_bits += hist[s] * code_sizes[s]; symbol_codec codec; codec.start_encoding(64 * 1024); codec.encode_enable_simulation(true); codec.encode_transmit_static_huffman_data_model(dm, false); codec.stop_encoding(false); total_bits += codec.encode_get_total_bits_written(); pParams->pResult->total_bits = total_bits; crnlib_delete(pParams); } void crn_comp::optimize_color_selectors() { crnlib::vector& remapping = m_selector_remaping[cColor]; uint16 n = m_color_selectors.size(); remapping.resize(n); uint8 d[] = {0, 5, 14, 10}; uint8 D4[0x100]; for (uint16 i = 0; i < 0x100; i++) D4[i] = d[(i ^ i >> 4) & 3] + d[(i >> 2 ^ i >> 6) & 3]; uint8 D8[0x10000]; for (uint32 i = 0; i < 0x10000; i++) D8[i] = D4[(i >> 8 & 0xF0) | (i >> 4 & 0xF)] + D4[(i >> 4 & 0xF0) | (i & 0xF)]; crnlib::vector selectors(n); crnlib::vector indices(n); for (uint16 i = 0; i < n; i++) { selectors[i] = m_color_selectors[i]; indices[i] = i; } uint32 selected_selector = 0; for (uint16 left = n; left;) { uint16 selected_index = 0; uint min_error = cUINT32_MAX; for (uint16 i = 0; i < left; i++) { uint32 selector = selectors[i]; uint8 d0 = D8[(selector >> 16 & 0xFF00) | (selected_selector >> 24 & 0xFF)]; uint8 d1 = D8[(selector >> 8 & 0xFF00) | (selected_selector >> 16 & 0xFF)]; uint8 d2 = D8[(selector & 0xFF00) | (selected_selector >> 8 & 0xFF)]; uint8 d3 = D8[(selector << 8 & 0xFF00) | (selected_selector & 0xFF)]; uint error = d0 + d1 + d2 + d3; if (error < min_error) { min_error = error; selected_index = i; } } selected_selector = selectors[selected_index]; remapping[indices[selected_index]] = n - left; left--; selectors[selected_index] = selectors[left]; indices[selected_index] = indices[left]; } pack_color_selectors(m_packed_color_selectors, remapping); } void crn_comp::optimize_color() { uint16 n = m_color_endpoints.size(); crnlib::vector hist(n * n); crnlib::vector sum(n); for (uint i, i_prev = 0, b = 0; b < m_endpoint_indices.size(); b++, i_prev = i) { i = m_endpoint_indices[b].color; if ((m_has_subblocks && b & 1 ? m_endpoint_indices[b].reference : !m_endpoint_indices[b].reference) && i != i_prev) { hist[i * n + i_prev]++; hist[i_prev * n + i]++; sum[i]++; sum[i_prev]++; } } uint16 selected = 0; uint best_sum = 0; for (uint16 i = 0; i < n; i++) { if (best_sum < sum[i]) { best_sum = sum[i]; selected = i; } } crnlib::vector unpacked_endpoints(n); for (uint16 i = 0; i < n; i++) { unpacked_endpoints[i].low.m_u32 = m_has_etc_color_blocks ? m_color_endpoints[i] & 0xFFFFFF : dxt1_block::unpack_color(m_color_endpoints[i] & 0xFFFF, true).m_u32; unpacked_endpoints[i].high.m_u32 = m_has_etc_color_blocks ? m_color_endpoints[i] >> 24 : dxt1_block::unpack_color(m_color_endpoints[i] >> 16, true).m_u32; } optimize_color_params::result remapping_trial[4]; float weights[4] = {0, 0, 1.0f / 6.0f, 0.5f}; for (uint i = 0; i < 4; i++) { optimize_color_params* pParams = crnlib_new(); pParams->unpacked_endpoints = unpacked_endpoints.get_ptr(); pParams->hist = hist.get_ptr(); pParams->n = n; pParams->selected = selected; pParams->weight = weights[i]; pParams->pResult = remapping_trial + i; m_task_pool.queue_object_task(this, &crn_comp::optimize_color_endpoints_task, i, pParams); } m_task_pool.join(); for (uint best_bits = cUINT32_MAX, i = 0; i < 4; i++) { if (remapping_trial[i].total_bits < best_bits) { m_packed_color_endpoints.swap(remapping_trial[i].packed_endpoints); m_endpoint_remaping[cColor].swap(remapping_trial[i].endpoint_remapping); best_bits = remapping_trial[i].total_bits; } } } struct optimize_alpha_params { struct unpacked_endpoint { uint8 low, high; }; const unpacked_endpoint* unpacked_endpoints; const uint* hist; uint16 n; uint16 selected; float weight; struct result { crnlib::vector endpoint_remapping; crnlib::vector packed_endpoints; uint total_bits; } *pResult; }; static void sort_alpha_endpoints(crnlib::vector& remapping, const optimize_alpha_params::unpacked_endpoint* unpacked_endpoints, uint16 n) { remapping.resize(n); crnlib::vector endpoints(n); crnlib::vector indices(n); for (uint16 i = 0; i < n; i++) { endpoints[i] = unpacked_endpoints[i]; indices[i] = i; } optimize_alpha_params::unpacked_endpoint selected_endpoint = {0, 0}; for (uint16 left = n; left;) { uint16 selected_index = 0; uint min_error = cUINT32_MAX; for (uint16 i = 0; i < left; i++) { optimize_alpha_params::unpacked_endpoint& endpoint = endpoints[i]; uint error = math::square(endpoint.low - selected_endpoint.low) + math::square(endpoint.high - selected_endpoint.high); if (error < min_error) { min_error = error; selected_index = i; } } selected_endpoint = endpoints[selected_index]; remapping[indices[selected_index]] = n - left; left--; endpoints[selected_index] = endpoints[left]; indices[selected_index] = indices[left]; } } static void remap_alpha_endpoints(uint16* remapping, const optimize_alpha_params::unpacked_endpoint* unpacked_endpoints, const uint* hist, uint16 n, uint16 selected, float weight) { const uint* frequency = hist + selected * n; crnlib::vector chosen, remaining; crnlib::vector total_frequency(n); chosen.push_back(selected); for (uint16 i = 0; i < n; i++) { if (i != selected) { remaining.push_back(i); total_frequency[i] = frequency[i]; } } for (uint similarity_base = (uint)(1000 * (1.0f + weight)), total_frequency_normalizer = 0; remaining.size();) { const optimize_alpha_params::unpacked_endpoint& e_front = unpacked_endpoints[chosen.front()]; const optimize_alpha_params::unpacked_endpoint& e_back = unpacked_endpoints[chosen.back()]; uint16 selected_index = 0; uint64 best_value = 0, selected_similarity_front = 0, selected_similarity_back = 0; for (uint16 i = 0; i < remaining.size(); i++) { uint remaining_index = remaining[i]; const optimize_alpha_params::unpacked_endpoint& e_remaining = unpacked_endpoints[remaining_index]; uint error_front = math::square(e_remaining.low - e_front.low) + math::square(e_remaining.high - e_front.high); uint error_back = math::square(e_remaining.low - e_back.low) + math::square(e_remaining.high - e_back.high); uint64 similarity_front = similarity_base - math::minimum(error_front, 1000); uint64 similarity_back = similarity_base - math::minimum(error_back, 1000); uint64 value = math::maximum(similarity_front, similarity_back) * (total_frequency[remaining_index] + total_frequency_normalizer) + 1; if (value > best_value) { best_value = value; selected_index = i; selected_similarity_front = similarity_front; selected_similarity_back = similarity_back; } } selected = remaining[selected_index]; frequency = hist + selected * n; total_frequency_normalizer = total_frequency[selected]; uint frequency_front = 0, frequency_back = 0; for (int front = 0, back = chosen.size() - 1, scale = back; scale > 0; front++, back--, scale -= 2) { frequency_front += scale * frequency[chosen[front]]; frequency_back += scale * frequency[chosen[back]]; } if (selected_similarity_front * frequency_front > selected_similarity_back * frequency_back) { chosen.push_front(selected); } else { chosen.push_back(selected); } remaining.erase(remaining.begin() + selected_index); for (uint16 i = 0; i < remaining.size(); i++) total_frequency[remaining[i]] += frequency[remaining[i]]; } for (uint16 i = 0; i < n; i++) remapping[chosen[i]] = i; } void crn_comp::optimize_alpha_endpoints_task(uint64 data, void* pData_ptr) { optimize_alpha_params* pParams = reinterpret_cast(pData_ptr); crnlib::vector& remapping = pParams->pResult->endpoint_remapping; uint16 n = pParams->n; remapping.resize(n); if (data) { remap_alpha_endpoints(remapping.get_ptr(), pParams->unpacked_endpoints, pParams->hist, n, pParams->selected, pParams->weight); } else { sort_alpha_endpoints(remapping, pParams->unpacked_endpoints, n); optimize_alpha_selectors(); } pack_alpha_endpoints(pParams->pResult->packed_endpoints, remapping); uint total_bits = pParams->pResult->packed_endpoints.size() << 3; crnlib::vector hist(n); bool hasAlpha0 = m_has_comp[cAlpha0], hasAlpha1 = m_has_comp[cAlpha1]; for (uint level = 0; level < m_levels.size(); level++) { for (uint alpha0_index = 0, alpha1_index = 0, b = m_levels[level].first_block, bEnd = b + m_levels[level].num_blocks; b < bEnd; b++) { if (hasAlpha0) { uint index = remapping[m_endpoint_indices[b].component[cAlpha0]]; if (!m_endpoint_indices[b].reference) { int sym = index - alpha0_index; hist[sym < 0 ? sym + n : sym]++; } alpha0_index = index; } if (hasAlpha1) { uint index = remapping[m_endpoint_indices[b].component[cAlpha1]]; if (!m_endpoint_indices[b].reference) { int sym = index - alpha1_index; hist[sym < 0 ? sym + n : sym]++; } alpha1_index = index; } } } static_huffman_data_model dm; dm.init(true, n, hist.get_ptr(), 16); const uint8* code_sizes = dm.get_code_sizes(); for (uint16 s = 0; s < n; s++) total_bits += hist[s] * code_sizes[s]; symbol_codec codec; codec.start_encoding(64 * 1024); codec.encode_enable_simulation(true); codec.encode_transmit_static_huffman_data_model(dm, false); codec.stop_encoding(false); total_bits += codec.encode_get_total_bits_written(); pParams->pResult->total_bits = total_bits; crnlib_delete(pParams); } void crn_comp::optimize_alpha_selectors() { crnlib::vector& remapping = m_selector_remaping[cAlpha0]; uint16 n = m_alpha_selectors.size(); remapping.resize(n); uint8 d[] = {0, 2, 3, 3, 5, 5, 4, 4}; uint8 D6[0x1000]; for (uint16 i = 0; i < 0x1000; i++) D6[i] = d[(i ^ i >> 6) & 7] + d[(i >> 3 ^ i >> 9) & 7]; crnlib::vector selectors(n); crnlib::vector indices(n); for (uint16 i = 0; i < n; i++) { selectors[i] = m_alpha_selectors[i]; indices[i] = i; } uint64 selected_selector = 0; for (uint16 left = n; left;) { uint16 selected_index = 0; uint min_error = cUINT32_MAX; for (uint16 i = 0; i < left; i++) { uint error = 0; for (uint64 selector = selectors[i] << 6, delta_selector = selected_selector, j = 0; j < 8; j++, selector >>= 6, delta_selector >>= 6) error += D6[(selector & 0xFC0) | (delta_selector & 0x3F)]; if (error < min_error) { min_error = error; selected_index = i; } } selected_selector = selectors[selected_index]; remapping[indices[selected_index]] = n - left; left--; selectors[selected_index] = selectors[left]; indices[selected_index] = indices[left]; } pack_alpha_selectors(m_packed_alpha_selectors, remapping); } void crn_comp::optimize_alpha() { uint16 n = m_alpha_endpoints.size(); crnlib::vector hist(n * n); crnlib::vector sum(n); bool hasAlpha0 = m_has_comp[cAlpha0], hasAlpha1 = m_has_comp[cAlpha1]; for (uint i0, i1, i0_prev = 0, i1_prev = 0, b = 0; b < m_endpoint_indices.size(); b++, i0_prev = i0, i1_prev = i1) { i0 = m_endpoint_indices[b].alpha0; i1 = m_endpoint_indices[b].alpha1; if (!m_endpoint_indices[b].reference) { if (hasAlpha0 && i0 != i0_prev) { hist[i0 * n + i0_prev]++; hist[i0_prev * n + i0]++; sum[i0]++; sum[i0_prev]++; } if (hasAlpha1 && i1 != i1_prev) { hist[i1 * n + i1_prev]++; hist[i1_prev * n + i1]++; sum[i1]++; sum[i1_prev]++; } } } uint16 selected = 0; uint best_sum = 0; for (uint16 i = 0; i < n; i++) { if (best_sum < sum[i]) { best_sum = sum[i]; selected = i; } } crnlib::vector unpacked_endpoints(n); for (uint16 i = 0; i < n; i++) { unpacked_endpoints[i].low = dxt5_block::unpack_endpoint(m_alpha_endpoints[i], 0); unpacked_endpoints[i].high = dxt5_block::unpack_endpoint(m_alpha_endpoints[i], 1); } optimize_alpha_params::result remapping_trial[4]; float weights[4] = {0, 0, 1.0f / 6.0f, 0.5f}; for (uint i = 0; i < 4; i++) { optimize_alpha_params* pParams = crnlib_new(); pParams->unpacked_endpoints = unpacked_endpoints.get_ptr(); pParams->hist = hist.get_ptr(); pParams->n = n; pParams->selected = selected; pParams->weight = weights[i]; pParams->pResult = remapping_trial + i; m_task_pool.queue_object_task(this, &crn_comp::optimize_alpha_endpoints_task, i, pParams); } m_task_pool.join(); for (uint best_bits = cUINT32_MAX, i = 0; i < 4; i++) { if (remapping_trial[i].total_bits < best_bits) { m_packed_alpha_endpoints.swap(remapping_trial[i].packed_endpoints); m_endpoint_remaping[cAlpha0].swap(remapping_trial[i].endpoint_remapping); best_bits = remapping_trial[i].total_bits; } } } bool crn_comp::pack_data_models() { symbol_codec codec; codec.start_encoding(1024 * 1024); if (!codec.encode_transmit_static_huffman_data_model(m_reference_dm, false)) return false; for (uint i = 0; i < 2; i++) { if (m_endpoint_index_dm[i].get_total_syms()) { if (!codec.encode_transmit_static_huffman_data_model(m_endpoint_index_dm[i], false)) return false; } if (m_selector_index_dm[i].get_total_syms()) { if (!codec.encode_transmit_static_huffman_data_model(m_selector_index_dm[i], false)) return false; } } codec.stop_encoding(false); m_packed_data_models.swap(codec.get_encoding_buf()); return true; } void crn_comp::append_vec(crnlib::vector& a, const void* p, uint size) { if (size) { uint ofs = a.size(); a.resize(ofs + size); memcpy(&a[ofs], p, size); } } void crn_comp::append_vec(crnlib::vector& a, const crnlib::vector& b) { if (!b.empty()) { uint ofs = a.size(); a.resize(ofs + b.size()); memcpy(&a[ofs], &b[0], b.size()); } } bool crn_comp::create_comp_data() { utils::zero_object(m_crn_header); m_crn_header.m_width = static_cast(m_pParams->m_width); m_crn_header.m_height = static_cast(m_pParams->m_height); m_crn_header.m_levels = static_cast(m_pParams->m_levels); m_crn_header.m_faces = static_cast(m_pParams->m_faces); m_crn_header.m_format = static_cast(m_pParams->m_format); m_crn_header.m_userdata0 = m_pParams->m_userdata0; m_crn_header.m_userdata1 = m_pParams->m_userdata1; m_comp_data.clear(); m_comp_data.reserve(2 * 1024 * 1024); append_vec(m_comp_data, &m_crn_header, sizeof(m_crn_header)); // tack on the rest of the variable size m_level_ofs array m_comp_data.resize(m_comp_data.size() + sizeof(m_crn_header.m_level_ofs[0]) * (m_pParams->m_levels - 1)); if (m_packed_color_endpoints.size()) { m_crn_header.m_color_endpoints.m_num = static_cast(m_color_endpoints.size()); m_crn_header.m_color_endpoints.m_size = m_packed_color_endpoints.size(); m_crn_header.m_color_endpoints.m_ofs = m_comp_data.size(); append_vec(m_comp_data, m_packed_color_endpoints); } if (m_packed_color_selectors.size()) { m_crn_header.m_color_selectors.m_num = static_cast(m_color_selectors.size()); m_crn_header.m_color_selectors.m_size = m_packed_color_selectors.size(); m_crn_header.m_color_selectors.m_ofs = m_comp_data.size(); append_vec(m_comp_data, m_packed_color_selectors); } if (m_packed_alpha_endpoints.size()) { m_crn_header.m_alpha_endpoints.m_num = static_cast(m_alpha_endpoints.size()); m_crn_header.m_alpha_endpoints.m_size = m_packed_alpha_endpoints.size(); m_crn_header.m_alpha_endpoints.m_ofs = m_comp_data.size(); append_vec(m_comp_data, m_packed_alpha_endpoints); } if (m_packed_alpha_selectors.size()) { m_crn_header.m_alpha_selectors.m_num = static_cast(m_alpha_selectors.size()); m_crn_header.m_alpha_selectors.m_size = m_packed_alpha_selectors.size(); m_crn_header.m_alpha_selectors.m_ofs = m_comp_data.size(); append_vec(m_comp_data, m_packed_alpha_selectors); } m_crn_header.m_tables_ofs = m_comp_data.size(); m_crn_header.m_tables_size = m_packed_data_models.size(); append_vec(m_comp_data, m_packed_data_models); uint level_ofs[cCRNMaxLevels]; for (uint i = 0; i < m_levels.size(); i++) { level_ofs[i] = m_comp_data.size(); append_vec(m_comp_data, m_packed_blocks[i]); } crnd::crn_header& dst_header = *(crnd::crn_header*)&m_comp_data[0]; // don't change the m_comp_data vector - or dst_header will be invalidated! memcpy(&dst_header, &m_crn_header, sizeof(dst_header)); for (uint i = 0; i < m_levels.size(); i++) dst_header.m_level_ofs[i] = level_ofs[i]; const uint actual_header_size = sizeof(crnd::crn_header) + sizeof(dst_header.m_level_ofs[0]) * (m_levels.size() - 1); dst_header.m_sig = crnd::crn_header::cCRNSigValue; dst_header.m_data_size = m_comp_data.size(); dst_header.m_data_crc16 = crc16(&m_comp_data[actual_header_size], m_comp_data.size() - actual_header_size); dst_header.m_header_size = actual_header_size; dst_header.m_header_crc16 = crc16(&dst_header.m_data_size, actual_header_size - (uint)((uint8*)&dst_header.m_data_size - (uint8*)&dst_header)); return true; } bool crn_comp::update_progress(uint phase_index, uint subphase_index, uint subphase_total) { if (!m_pParams->m_pProgress_func) return true; #if CRNLIB_ENABLE_DEBUG_MESSAGES if (m_pParams->m_flags & cCRNCompFlagDebugging) return true; #endif return (*m_pParams->m_pProgress_func)(phase_index, cTotalCompressionPhases, subphase_index, subphase_total, m_pParams->m_pProgress_func_data) != 0; } bool crn_comp::compress_internal() { if (!alias_images()) return false; if (!quantize_images()) return false; m_reference_hist.clear(); for (uint i = 0; i < 2; i++) { m_endpoint_remaping[i].clear(); m_endpoint_index_hist[i].clear(); m_endpoint_index_dm[i].clear(); m_selector_remaping[i].clear(); m_selector_index_hist[i].clear(); m_selector_index_dm[i].clear(); } if (m_has_comp[cColor]) optimize_color(); if (m_has_comp[cAlpha0]) optimize_alpha(); for (uint pass = 0; pass < 2; pass++) { for (uint level = 0; level < m_levels.size(); level++) { symbol_codec codec; codec.start_encoding(2 * 1024 * 1024); if (!pack_blocks( level, !pass && !level, pass ? &codec : NULL, m_has_comp[cColor] ? &m_endpoint_remaping[cColor] : NULL, m_has_comp[cColor] ? &m_selector_remaping[cColor] : NULL, m_has_comp[cAlpha0] ? &m_endpoint_remaping[cAlpha0] : NULL, m_has_comp[cAlpha0] ? &m_selector_remaping[cAlpha0] : NULL)) { return false; } codec.stop_encoding(false); if (pass) m_packed_blocks[level].swap(codec.get_encoding_buf()); } if (!pass) { m_reference_dm.init(true, m_reference_hist, 16); for (uint i = 0; i < 2; i++) { if (m_endpoint_index_hist[i].size()) m_endpoint_index_dm[i].init(true, m_endpoint_index_hist[i], 16); if (m_selector_index_hist[i].size()) m_selector_index_dm[i].init(true, m_selector_index_hist[i], 16); } } } if (!pack_data_models()) return false; if (!create_comp_data()) return false; if (!update_progress(24, 1, 1)) return false; if (m_pParams->m_flags & cCRNCompFlagDebugging) { crnlib_print_mem_stats(); } return true; } bool crn_comp::compress_pass(const crn_comp_params& params, float* pEffective_bitrate) { clear(); if (pEffective_bitrate) *pEffective_bitrate = 0.0f; m_pParams = ¶ms; m_has_etc_color_blocks = params.m_format == cCRNFmtETC1 || params.m_format == cCRNFmtETC2 || params.m_format == cCRNFmtETC2A || params.m_format == cCRNFmtETC1S || params.m_format == cCRNFmtETC2AS; m_has_subblocks = params.m_format == cCRNFmtETC1 || params.m_format == cCRNFmtETC2 || params.m_format == cCRNFmtETC2A; if ((math::minimum(m_pParams->m_width, m_pParams->m_height) < 1) || (math::maximum(m_pParams->m_width, m_pParams->m_height) > cCRNMaxLevelResolution)) return false; if (!m_task_pool.init(params.m_num_helper_threads)) return false; bool status = compress_internal(); m_task_pool.deinit(); if ((status) && (pEffective_bitrate)) { uint total_pixels = 0; for (uint f = 0; f < m_pParams->m_faces; f++) for (uint l = 0; l < m_pParams->m_levels; l++) total_pixels += m_images[f][l].get_total_pixels(); *pEffective_bitrate = (m_comp_data.size() * 8.0f) / total_pixels; } return status; } void crn_comp::compress_deinit() { } } // namespace crnlib