diff --git a/bin/crunch_x64.exe b/bin/crunch_x64.exe index 8fbbac5..dda44f0 100644 Binary files a/bin/crunch_x64.exe and b/bin/crunch_x64.exe differ diff --git a/crnlib/crn_comp.cpp b/crnlib/crn_comp.cpp index db5ab3a..4c53023 100644 --- a/crnlib/crn_comp.cpp +++ b/crnlib/crn_comp.cpp @@ -278,6 +278,7 @@ bool crn_comp::pack_blocks( 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_etc_color_blocks && 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; @@ -286,10 +287,10 @@ bool crn_comp::pack_blocks( else m_reference_hist.inc_freq(reference_group); } - for (uint c = 0; c < cNumComps; c++) { + 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 (m_pParams->m_format == cCRNFmtETC1 && (b & 1) ? m_endpoint_indices[b].reference : !m_endpoint_indices[b].reference) { + 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(); @@ -301,8 +302,8 @@ bool crn_comp::pack_blocks( endpoint_index[c] = index; } } - for (uint c = 0; c < cNumComps; c++) { - if (selector_remap[c] && (m_pParams->m_format != cCRNFmtETC1 || !(bx & 1))) { + 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); @@ -341,7 +342,7 @@ bool crn_comp::alias_images() { 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_pParams->m_format == cCRNFmtETC1 ? 1 : 2); + m_levels[level].block_width = (math::maximum(1U, m_pParams->m_width >> level) + 7 & ~7) >> (m_has_etc_color_blocks ? 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; @@ -358,6 +359,7 @@ void crn_comp::clear() { m_images[f][l].clear(); utils::zero_object(m_has_comp); + m_has_etc_color_blocks = false; m_levels.clear(); @@ -416,15 +418,18 @@ bool crn_comp::quantize_images() { 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 = 1.31f; + params.m_adaptive_tile_color_psnr_derating = 5.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 == cCRNFmtETC1) { - color_quality_power_mul = 1.31f; - params.m_adaptive_tile_color_psnr_derating = 5.0f; + } else if (m_pParams->m_format == cCRNFmtETC2A) { + alpha_quality_power_mul = .9f; } float color_endpoint_quality = powf(quality, 1.8f * color_quality_power_mul); @@ -519,6 +524,18 @@ bool crn_comp::quantize_images() { 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; + } default: { return false; } @@ -668,14 +685,14 @@ void crn_comp::optimize_color_endpoints_task(uint64 data, void* pData_ptr) { optimize_color_selectors(); } - m_pParams->m_format == cCRNFmtETC1 ? pack_color_endpoints_etc(pParams->pResult->packed_endpoints, remapping) : pack_color_endpoints(pParams->pResult->packed_endpoints, remapping); + 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_pParams->m_format == cCRNFmtETC1 && (b & 1) ? m_endpoint_indices[b].reference : !m_endpoint_indices[b].reference) { + if (m_has_etc_color_blocks && b & 1 ? m_endpoint_indices[b].reference : !m_endpoint_indices[b].reference) { int sym = index - endpoint_index; hist[sym < 0 ? sym + n : sym]++; } @@ -753,7 +770,7 @@ void crn_comp::optimize_color() { 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_pParams->m_format == cCRNFmtETC1 && (b & 1) ? m_endpoint_indices[b].reference : !m_endpoint_indices[b].reference) && i != i_prev) { + if ((m_has_etc_color_blocks && 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]++; @@ -770,8 +787,8 @@ void crn_comp::optimize_color() { } crnlib::vector unpacked_endpoints(n); for (uint16 i = 0; i < n; i++) { - unpacked_endpoints[i].low.m_u32 = m_pParams->m_format == cCRNFmtETC1 ? m_color_endpoints[i] & 0xFFFFFF : dxt1_block::unpack_color(m_color_endpoints[i] & 0xFFFF, true).m_u32; - unpacked_endpoints[i].high.m_u32 = m_pParams->m_format == cCRNFmtETC1 ? m_color_endpoints[i] >> 24 : dxt1_block::unpack_color(m_color_endpoints[i] >> 16, true).m_u32; + 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]; @@ -1264,6 +1281,7 @@ bool crn_comp::compress_pass(const crn_comp_params& params, float* pEffective_bi *pEffective_bitrate = 0.0f; m_pParams = ¶ms; + m_has_etc_color_blocks = 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; diff --git a/crnlib/crn_comp.h b/crnlib/crn_comp.h index 38651a1..49ab4fe 100644 --- a/crnlib/crn_comp.h +++ b/crnlib/crn_comp.h @@ -45,6 +45,7 @@ class crn_comp : public itexture_comp { }; bool m_has_comp[cNumComps]; + bool m_has_etc_color_blocks; struct level_details { uint first_block; diff --git a/crnlib/crn_dxt.cpp b/crnlib/crn_dxt.cpp index 17e3bab..272e5d1 100644 --- a/crnlib/crn_dxt.cpp +++ b/crnlib/crn_dxt.cpp @@ -37,6 +37,10 @@ const char* get_dxt_format_string(dxt_format fmt) { return "DXN_YX"; case cETC1: return "ETC1"; + case cETC2: + return "ETC2"; + case cETC2A: + return "ETC2A"; default: break; } @@ -69,11 +73,13 @@ uint get_dxt_format_bits_per_pixel(dxt_format fmt) { case cDXT1A: case cDXT5A: case cETC1: + case cETC2: return 4; case cDXT3: case cDXT5: case cDXN_XY: case cDXN_YX: + case cETC2A: return 8; default: break; @@ -88,6 +94,7 @@ bool get_dxt_format_has_alpha(dxt_format fmt) { case cDXT3: case cDXT5: case cDXT5A: + case cETC2A: return true; default: break; diff --git a/crnlib/crn_dxt.h b/crnlib/crn_dxt.h index 92447ff..b5740f8 100644 --- a/crnlib/crn_dxt.h +++ b/crnlib/crn_dxt.h @@ -42,7 +42,9 @@ enum dxt_format { cDXN_XY, // inverted relative to standard ATI2, 360's DXN cDXN_YX, // standard ATI2, - cETC1 // Ericsson texture compression (color only, 4x4 blocks, 4bpp, 64-bits/block) + cETC1, + cETC2, + cETC2A, }; const float cDXT1MaxLinearValue = 3.0f; diff --git a/crnlib/crn_dxt_hc.cpp b/crnlib/crn_dxt_hc.cpp index 173ac3a..94b473f 100644 --- a/crnlib/crn_dxt_hc.cpp +++ b/crnlib/crn_dxt_hc.cpp @@ -26,6 +26,7 @@ static uint8 g_tile_map[8][2][2] = { dxt_hc::dxt_hc() : m_num_blocks(0), m_has_color_blocks(false), + m_has_etc_color_blocks(false), m_num_alpha_blocks(0), m_main_thread_id(crn_get_current_thread_id()), m_canceled(false), @@ -77,8 +78,9 @@ bool dxt_hc::compress( const params& p ) { clear(); - m_has_color_blocks = p.m_format == cDXT1 || p.m_format == cDXT5 || p.m_format == cETC1; - m_num_alpha_blocks = p.m_format == cDXT5 || p.m_format == cDXT5A ? 1 : p.m_format == cDXN_XY || p.m_format == cDXN_YX ? 2 : 0; + m_has_etc_color_blocks = p.m_format == cETC1 || p.m_format == cETC2 || p.m_format == cETC2A; + m_has_color_blocks = p.m_format == cDXT1 || p.m_format == cDXT5 || m_has_etc_color_blocks; + m_num_alpha_blocks = p.m_format == cDXT5 || p.m_format == cDXT5A || p.m_format == cETC2A ? 1 : p.m_format == cDXN_XY || p.m_format == cDXN_YX ? 2 : 0; if (!m_has_color_blocks && !m_num_alpha_blocks) return false; m_blocks = blocks; @@ -116,7 +118,7 @@ bool dxt_hc::compress( } for (uint i = 0; i <= m_pTask_pool->get_num_threads(); i++) - m_pTask_pool->queue_object_task(this, m_params.m_format == cETC1 ? &dxt_hc::determine_tiles_task_etc : &dxt_hc::determine_tiles_task, i); + m_pTask_pool->queue_object_task(this, m_has_etc_color_blocks ? &dxt_hc::determine_tiles_task_etc : &dxt_hc::determine_tiles_task, i); m_pTask_pool->join(); m_num_tiles = 0; @@ -142,7 +144,7 @@ bool dxt_hc::compress( hash_map color_endpoints_map; for (uint i = 0; i < m_color_clusters.size(); i++) { if (m_color_clusters[i].pixels.size()) { - uint32 endpoint = m_params.m_format == cETC1 ? m_color_clusters[i].first_endpoint : + uint32 endpoint = m_has_etc_color_blocks ? m_color_clusters[i].first_endpoint : dxt1_block::pack_endpoints(m_color_clusters[i].first_endpoint, m_color_clusters[i].second_endpoint); hash_map::insert_result insert_result = color_endpoints_map.insert(endpoint, color_endpoints.size()); if (insert_result.second) { @@ -210,7 +212,7 @@ bool dxt_hc::compress( for (uint bx = 0; bx < block_width; bx++, b++) { bool top_match = by != 0; bool left_match = top_match || bx; - bool diag_match = m_params.m_format == cETC1 && top_match && bx; + bool diag_match = m_has_etc_color_blocks && top_match && bx; for (uint c = m_has_color_blocks ? 0 : cAlpha0; c < cAlpha0 + m_num_alpha_blocks; c++) { uint16 endpoint_index = (c ? alpha_endpoints_remap : color_endpoints_remap)[m_endpoint_indices[b].component[c]]; left_match = left_match && endpoint_index == endpoint_indices[b - 1].component[c]; @@ -220,7 +222,7 @@ bool dxt_hc::compress( uint16 selector_index = (c ? alpha_selectors_remap : color_selectors_remap)[m_selector_indices[b].component[c]]; selector_indices[b].component[c] = selector_index; } - endpoint_indices[b].reference = m_params.m_format == cETC1 && (b & 1) ? m_endpoint_indices[b].reference : left_match ? 1 : top_match ? 2 : diag_match ? 3 : 0; + endpoint_indices[b].reference = m_has_etc_color_blocks && b & 1 ? m_endpoint_indices[b].reference : left_match ? 1 : top_match ? 2 : diag_match ? 3 : 0; } } } @@ -384,6 +386,7 @@ void dxt_hc::determine_tiles_task_etc(uint64 data, void* pData_ptr) { uint tile_error[5]; uint total_error[3]; tree_clusterizer color_palettizer; + tree_clusterizer alpha_palettizer; etc1_optimizer optimizer; etc1_optimizer::params params; @@ -435,6 +438,17 @@ void dxt_hc::determine_tiles_task_etc(uint64 data, void* pData_ptr) { } } + vec2F alpha_endpoints; + if (m_num_alpha_blocks) { + alpha_palettizer.clear(); + for (uint p = 0; p < 16; p++) + alpha_palettizer.add_training_vec(vec1F(m_uint8_to_float[tilePixels[p].a]), 1); + alpha_palettizer.generate_codebook(2); + float v[2] = {alpha_palettizer.get_codebook_entry(0)[0], alpha_palettizer.get_codebook_entry(alpha_palettizer.get_codebook_size() - 1)[0]}; + alpha_endpoints[0] = math::minimum(v[0], v[1]); + alpha_endpoints[1] = math::maximum(v[0], v[1]); + } + for (uint tile_index = 0, s = best_encoding + 1; s; s >>= 1, tile_index++) { tile_details& tile = m_tiles[b | tile_index]; uint t = tiles[best_encoding][tile_index]; @@ -454,6 +468,8 @@ void dxt_hc::determine_tiles_task_etc(uint64 data, void* pData_ptr) { for (uint c = 0; c < 3; c++, t++) tile.color_endpoint[t] = v[c]; } + if (m_num_alpha_blocks) + tile.alpha_endpoints[0] = alpha_endpoints; } for (uint bx = 0; bx < 2; bx++) { @@ -661,7 +677,7 @@ void dxt_hc::determine_color_endpoints() { uint cluster_index = m_tiles[m_tile_indices[b]].cluster_indices[cColor]; m_endpoint_indices[b].component[cColor] = cluster_index; m_color_clusters[cluster_index].blocks[cColor].push_back(b); - if (m_params.m_format == cETC1 && m_endpoint_indices[b].reference && cluster_index == m_endpoint_indices[b - 1].component[cColor]) { + if (m_has_etc_color_blocks && m_endpoint_indices[b].reference && cluster_index == m_endpoint_indices[b - 1].component[cColor]) { if (m_endpoint_indices[b].reference >> 1) { color_quad_u8 mirror[16]; for (uint p = 0; p < 16; p++) @@ -673,7 +689,7 @@ void dxt_hc::determine_color_endpoints() { } for (uint i = 0; i <= m_pTask_pool->get_num_threads(); i++) - m_pTask_pool->queue_object_task(this, m_params.m_format == cETC1 ? &dxt_hc::determine_color_endpoint_codebook_task_etc : &dxt_hc::determine_color_endpoint_codebook_task, i, NULL); + m_pTask_pool->queue_object_task(this, m_has_etc_color_blocks ? &dxt_hc::determine_color_endpoint_codebook_task_etc : &dxt_hc::determine_color_endpoint_codebook_task, i, NULL); m_pTask_pool->join(); } @@ -719,12 +735,26 @@ void dxt_hc::determine_alpha_endpoint_codebook_task(uint64 data, void* pData_ptr dxt5_block::get_block_values(block_values, cluster.first_endpoint, cluster.second_endpoint); for (uint i = 0; i < 8; i++) alpha_values[i] = cluster.alpha_values[i] = block_values[g_dxt5_from_linear[i]]; - - int delta = cluster.second_endpoint - cluster.first_endpoint; + int delta = cluster.first_endpoint - cluster.second_endpoint; uint encoding_weight[8]; for (uint endpoint_weight = math::clamp(delta * delta >> 3, 1, 2048), i = 0; i < 8; i++) encoding_weight[i] = (uint)(endpoint_weight * math::lerp(1.15f, 1.0f, i / 7.0f)); + if (m_has_etc_color_blocks) { + static const int stripped_modifier_table[2][8] = { + {-10, -7, -5, -2, 1, 4, 6, 9}, + {-10, -3, -2, -1, 0, 1, 2, 9} + }; + int base_codeword = (results.m_first_endpoint + results.m_second_endpoint + 1) >> 1; + int modifier_index = delta <= 6 ? 13 : 11; + int multiplier = delta <= 6 ? 1 : math::clamp((delta + 12) / 18, 1, 15); + const int* modifier = stripped_modifier_table[modifier_index == 11 ? 0 : 1]; + for (int i = 0; i < 8; i++) + alpha_values[i] = cluster.alpha_values[i] = math::clamp(base_codeword + modifier[i] * multiplier, 0, 255); + cluster.first_endpoint = base_codeword; + cluster.second_endpoint = multiplier << 4 | modifier_index; + } + for (uint a = 0; a < m_num_alpha_blocks; a++) { uint component_index = m_params.m_alpha_component_indices[a]; crnlib::vector& blocks = cluster.blocks[cAlpha0 + a]; @@ -736,8 +766,8 @@ void dxt_hc::determine_alpha_endpoint_codebook_task(uint64 data, void* pData_ptr uint error_best = cUINT32_MAX; uint8 s_best = 0; for (uint8 t = 0; t < 8; t++) { - uint8 s = results.m_reordered ? 7 - g_dxt5_to_linear[t] : g_dxt5_to_linear[t]; - int delta = m_blocks[b][p][component_index] - alpha_values[s]; + uint8 s = m_has_etc_color_blocks ? t : results.m_reordered ? 7 - g_dxt5_to_linear[t] : g_dxt5_to_linear[t]; + int delta = m_blocks[m_has_etc_color_blocks ? b >> 1 : b][p][component_index] - alpha_values[s]; uint error = delta >= 0 ? delta : -delta; if (error < error_best) { s_best = s; @@ -760,7 +790,7 @@ void dxt_hc::determine_alpha_endpoint_codebook_task(uint64 data, void* pData_ptr refinerParams.m_dxt1_selectors = false; refinerParams.m_error_to_beat = results.m_error; refinerParams.m_block_index = cluster_index; - cluster.refined_alpha = refiner.refine(refinerParams, refinerResults); + cluster.refined_alpha = !m_has_etc_color_blocks && refiner.refine(refinerParams, refinerResults); if (cluster.refined_alpha) { cluster.first_endpoint = refinerResults.m_low_color; cluster.second_endpoint = refinerResults.m_high_color; @@ -816,7 +846,8 @@ void dxt_hc::determine_alpha_endpoints() { for (uint a = 0; a < m_num_alpha_blocks; a++) { uint cluster_index = m_tiles[m_tile_indices[b]].cluster_indices[cAlpha0 + a]; m_endpoint_indices[b].component[cAlpha0 + a] = cluster_index; - m_alpha_clusters[cluster_index].blocks[cAlpha0 + a].push_back(b); + if (!(m_has_etc_color_blocks && b & 1)) + m_alpha_clusters[cluster_index].blocks[cAlpha0 + a].push_back(b); } } @@ -837,12 +868,12 @@ void dxt_hc::create_color_selector_codebook_task(uint64 data, void* pData_ptr) { uint E2[16][4]; uint E4[8][16]; uint E8[4][256]; - for (uint n = m_params.m_format == cETC1 ? m_num_blocks >> 1 : m_num_blocks, b = n * data / num_tasks, bEnd = n * (data + 1) / num_tasks; b < bEnd; b++) { + for (uint n = m_has_etc_color_blocks ? m_num_blocks >> 1 : m_num_blocks, b = n * data / num_tasks, bEnd = n * (data + 1) / num_tasks; b < bEnd; b++) { color_cluster& cluster = m_color_clusters[m_endpoint_indices[b].color]; color_quad_u8* endpoint_colors = cluster.color_values; for (uint p = 0; p < 16; p++) { for (uint s = 0; s < 4; s++) - E2[p][s] = m_params.m_format == cETC1 ? color::color_distance(m_params.m_perceptual, m_blocks[b][p], m_color_clusters[m_endpoint_indices[b << 1 | p >> 3].color].color_values[s], false) : + E2[p][s] = m_has_etc_color_blocks ? color::color_distance(m_params.m_perceptual, m_blocks[b][p], m_color_clusters[m_endpoint_indices[b << 1 | p >> 3].color].color_values[s], false) : color::color_distance(m_params.m_perceptual, m_blocks[b][p], endpoint_colors[s], false); } for (uint p = 0; p < 8; p++) { @@ -868,18 +899,18 @@ void dxt_hc::create_color_selector_codebook_task(uint64 data, void* pData_ptr) { total_errors[p][s] += E2[p][s]; } selector_details[best_index].used = true; - m_selector_indices[m_params.m_format == cETC1 ? b << 1 : b].color = best_index; + m_selector_indices[m_has_etc_color_blocks ? b << 1 : b].color = best_index; } } void dxt_hc::create_color_selector_codebook() { tree_clusterizer selector_vq; vec16F v; - for (uint n = m_params.m_format == cETC1 ? m_num_blocks >> 1 : m_num_blocks, b = 0; b < n; b++) { - uint64 selector = m_params.m_format == cETC1 ? m_block_selectors[cColor][b << 1] | m_block_selectors[cColor][b << 1 | 1] << 16 : m_block_selectors[cColor][b]; + for (uint n = m_has_etc_color_blocks ? m_num_blocks >> 1 : m_num_blocks, b = 0; b < n; b++) { + uint64 selector = m_has_etc_color_blocks ? m_block_selectors[cColor][b << 1] | m_block_selectors[cColor][b << 1 | 1] << 16 : m_block_selectors[cColor][b]; for (uint8 p = 0; p < 16; p++, selector >>= 2) v[p] = ((selector & 3) + 0.5f) * 0.25f; - selector_vq.add_training_vec(v, m_params.m_format == cETC1 ? (selector & 0xFFFF) + (selector >> 16) : selector); + selector_vq.add_training_vec(v, m_has_etc_color_blocks ? (selector & 0xFFFF) + (selector >> 16) : selector); } selector_vq.generate_codebook(m_params.m_color_selector_codebook_size); m_color_selectors.resize(selector_vq.get_codebook_size()); @@ -933,10 +964,10 @@ void dxt_hc::create_alpha_selector_codebook_task(uint64 data, void* pData_ptr) { uint num_tasks = m_pTask_pool->get_num_threads() + 1; uint E3[16][8]; uint E6[8][64]; - for (uint b = m_num_blocks * data / num_tasks, bEnd = m_num_blocks * (data + 1) / num_tasks; b < bEnd; b++) { + for (uint n = m_has_etc_color_blocks ? m_num_blocks >> 1 : m_num_blocks, b = n * data / num_tasks, bEnd = n * (data + 1) / num_tasks; b < bEnd; b++) { for (uint c = cAlpha0; c < cAlpha0 + m_num_alpha_blocks; c++) { const uint alpha_pixel_comp = m_params.m_alpha_component_indices[c - cAlpha0]; - alpha_cluster& cluster = m_alpha_clusters[m_endpoint_indices[b].component[c]]; + alpha_cluster& cluster = m_alpha_clusters[m_endpoint_indices[m_has_etc_color_blocks ? b << 1 : b].component[c]]; uint* block_values = cluster.alpha_values; for (uint p = 0; p < 16; p++) { for (uint s = 0; s < 8; s++) { @@ -979,7 +1010,7 @@ void dxt_hc::create_alpha_selector_codebook_task(uint64 data, void* pData_ptr) { total_errors[p][s] += E3[p][s]; } selector_details[best_index].used = true; - m_selector_indices[b].component[c] = best_index; + m_selector_indices[m_has_etc_color_blocks ? b << 1 : b].component[c] = best_index; } } } @@ -988,7 +1019,7 @@ void dxt_hc::create_alpha_selector_codebook() { tree_clusterizer selector_vq; vec16F v; for (uint c = cAlpha0; c < cAlpha0 + m_num_alpha_blocks; c++) { - for (uint b = 0; b < m_num_blocks; b++) { + for (uint b = 0; b < m_num_blocks; b += m_has_etc_color_blocks ? 2 : 1) { uint64 selector = m_block_selectors[c][b]; for (uint8 p = 0; p < 16; p++, selector >>= 3) v[p] = ((selector & 7) + 0.5f) * 0.125f; diff --git a/crnlib/crn_dxt_hc.h b/crnlib/crn_dxt_hc.h index 096d356..022bab4 100644 --- a/crnlib/crn_dxt_hc.h +++ b/crnlib/crn_dxt_hc.h @@ -32,6 +32,7 @@ class dxt_hc { uint16 component[3]; }; uint8 reference; + endpoint_indices_details() { utils::zero_object(*this); } }; struct selector_indices_details { @@ -43,6 +44,7 @@ class dxt_hc { }; uint16 component[3]; }; + selector_indices_details() { utils::zero_object(*this); } }; struct tile_details { @@ -146,6 +148,7 @@ class dxt_hc { uint m_num_alpha_blocks; bool m_has_color_blocks; + bool m_has_etc_color_blocks; enum { cColor = 0, diff --git a/crnlib/crn_dxt_image.cpp b/crnlib/crn_dxt_image.cpp index cf1ac0f..37e6833 100644 --- a/crnlib/crn_dxt_image.cpp +++ b/crnlib/crn_dxt_image.cpp @@ -100,7 +100,7 @@ bool dxt_image::init_internal(dxt_format fmt, uint width, uint height) { m_blocks_y = (m_height + 3) >> cDXTBlockShift; m_num_elements_per_block = 2; - if ((fmt == cDXT1) || (fmt == cDXT1A) || (fmt == cDXT5A) || (fmt == cETC1)) + if ((fmt == cDXT1) || (fmt == cDXT1A) || (fmt == cDXT5A) || (fmt == cETC1) || (fmt == cETC2)) m_num_elements_per_block = 1; m_total_blocks = m_blocks_x * m_blocks_y; @@ -156,6 +156,18 @@ bool dxt_image::init_internal(dxt_format fmt, uint width, uint height) { m_element_component_index[0] = -1; break; } + case cETC2: { + m_element_type[0] = cColorETC2; + m_element_component_index[0] = -1; + break; + } + case cETC2A: { + m_element_type[0] = cAlphaETC2; + m_element_type[1] = cColorETC2; + m_element_component_index[0] = 3; + m_element_component_index[1] = -1; + break; + } default: { CRNLIB_ASSERT(0); clear(); @@ -478,6 +490,7 @@ bool dxt_image::has_alpha() const { case cDXT3: case cDXT5: case cDXT5A: + case cETC2A: return true; default: break; @@ -896,6 +909,18 @@ bool dxt_image::get_block_pixels(uint block_x, uint block_y, color_quad_u8* pPix break; } + case cColorETC2: { + const etc1_block& block = *reinterpret_cast(&get_element(block_x, block_y, element_index)); + if (!rg_etc1::unpack_etc2_color(&block, (uint32*)pPixels, m_format != cETC2)) + success = false; + break; + } + case cAlphaETC2: { + const etc1_block& block = *reinterpret_cast(&get_element(block_x, block_y, element_index)); + if (!rg_etc1::unpack_etc2_alpha(&block, (uint32*)pPixels, m_element_component_index[element_index])) + success = false; + break; + } case cColorDXT1: { const dxt1_block* pDXT1_block = reinterpret_cast(pElement); @@ -990,6 +1015,28 @@ void dxt_image::set_block_pixels( pack_etc1_block(dst_block, pPixels, pack_params, context.m_etc1_optimizer); #endif + } else if (m_format == cETC2) { + etc1_block& dst_block = *reinterpret_cast(pElement); + rg_etc1::etc1_pack_params pack_params; + pack_params.m_dithering = p.m_dithering; + pack_params.m_quality = p.m_quality <= cCRNDXTQualityFast ? rg_etc1::cLowQuality : p.m_quality <= cCRNDXTQualityNormal ? rg_etc1::cMediumQuality : rg_etc1::cHighQuality; + rg_etc1::pack_etc1_block(&dst_block, (uint32*)pPixels, pack_params); + + } else if (m_format == cETC2A) { + rg_etc1::etc1_quality etc_quality = p.m_quality <= cCRNDXTQualityFast ? rg_etc1::cLowQuality : p.m_quality <= cCRNDXTQualityNormal ? rg_etc1::cMediumQuality : rg_etc1::cHighQuality; + for (uint element_index = 0; element_index < m_num_elements_per_block; element_index++, pElement++) { + if (m_element_type[element_index] == cAlphaETC2) { + rg_etc1::etc2a_pack_params pack_params; + pack_params.m_quality = etc_quality; + pack_params.comp_index = m_element_component_index[element_index]; + rg_etc1::pack_etc2_alpha(pElement, (uint32*)pPixels, pack_params); + } else { + rg_etc1::etc1_pack_params pack_params; + pack_params.m_dithering = p.m_dithering; + pack_params.m_quality = etc_quality; + rg_etc1::pack_etc1_block(pElement, (uint32*)pPixels, pack_params); + } + } } else #if CRNLIB_SUPPORT_SQUISH if ((p.m_compressor == cCRNDXTCompressorSquish) && ((m_format == cDXT1) || (m_format == cDXT1A) || (m_format == cDXT3) || (m_format == cDXT5) || (m_format == cDXT5A))) { @@ -1454,8 +1501,8 @@ void dxt_image::flip_row(uint y) { } bool dxt_image::can_flip(uint axis_index) { - if (m_format == cETC1) { - // Can't reliably flip ETC1 textures (because of asymmetry in the 555/333 differential coding of subblock colors). + if (m_format == cETC1 || m_format == cETC2 || m_format == cETC2A) { + // Can't reliably flip ETCn textures (because of asymmetry in the 555/333 differential coding of subblock colors). return false; } @@ -1474,8 +1521,8 @@ bool dxt_image::can_flip(uint axis_index) { } bool dxt_image::flip_x() { - if (m_format == cETC1) { - // Can't reliably flip ETC1 textures (because of asymmetry in the 555/333 differential coding of subblock colors). + if (m_format == cETC1 || m_format == cETC2 || m_format == cETC2A) { + // Can't reliably flip ETCn textures (because of asymmetry in the 555/333 differential coding of subblock colors). return false; } @@ -1518,8 +1565,8 @@ bool dxt_image::flip_x() { } bool dxt_image::flip_y() { - if (m_format == cETC1) { - // Can't reliably flip ETC1 textures (because of asymmetry in the 555/333 differential coding of subblock colors). + if (m_format == cETC1 || m_format == cETC2 || m_format == cETC2A) { + // Can't reliably flip ETCn textures (because of asymmetry in the 555/333 differential coding of subblock colors). return false; } diff --git a/crnlib/crn_dxt_image.h b/crnlib/crn_dxt_image.h index 65296c5..29c667b 100644 --- a/crnlib/crn_dxt_image.h +++ b/crnlib/crn_dxt_image.h @@ -36,7 +36,7 @@ class dxt_image { dxt_format get_format() const { return m_format; } - bool has_color() const { return (m_format == cDXT1) || (m_format == cDXT1A) || (m_format == cDXT3) || (m_format == cDXT5) || (m_format == cETC1); } + bool has_color() const { return (m_format == cDXT1) || (m_format == cDXT1A) || (m_format == cDXT3) || (m_format == cDXT5) || (m_format == cETC1) || (m_format == cETC2) || (m_format == cETC2A); } // Will be pretty slow if the image is DXT1, as this method scans for alpha blocks/selectors. bool has_alpha() const; @@ -50,6 +50,9 @@ class dxt_image { cAlphaDXT5, // DXT5 alpha block (only) cColorETC1, // ETC1 color block + cColorETC2, // ETC2 color block + + cAlphaETC2, // ETC2 alpha block (only) }; element_type get_element_type(uint element_index) const { diff --git a/crnlib/crn_ktx_texture.cpp b/crnlib/crn_ktx_texture.cpp index cf3cf48..e61c72d 100644 --- a/crnlib/crn_ktx_texture.cpp +++ b/crnlib/crn_ktx_texture.cpp @@ -69,11 +69,13 @@ uint get_ogl_type_size(uint32 ogl_type) { uint32 get_ogl_base_internal_fmt(uint32 ogl_fmt) { switch (ogl_fmt) { case KTX_ETC1_RGB8_OES: + case KTX_COMPRESSED_RGB8_ETC2: case KTX_RGB_S3TC: case KTX_RGB4_S3TC: case KTX_COMPRESSED_RGB_S3TC_DXT1_EXT: case KTX_COMPRESSED_SRGB_S3TC_DXT1_EXT: return KTX_RGB; + case KTX_COMPRESSED_RGBA8_ETC2_EAC: case KTX_COMPRESSED_RGBA_S3TC_DXT1_EXT: case KTX_COMPRESSED_SRGB_ALPHA_S3TC_DXT1_EXT: case KTX_RGBA_S3TC: @@ -146,6 +148,7 @@ bool get_ogl_fmt_desc(uint32 ogl_fmt, uint32 ogl_type, uint& block_dim, uint& by case KTX_COMPRESSED_LUMINANCE_LATC1_EXT: case KTX_COMPRESSED_SIGNED_LUMINANCE_LATC1_EXT: case KTX_ETC1_RGB8_OES: + case KTX_COMPRESSED_RGB8_ETC2: case KTX_RGB_S3TC: case KTX_RGB4_S3TC: case KTX_COMPRESSED_RGB_S3TC_DXT1_EXT: @@ -156,6 +159,7 @@ bool get_ogl_fmt_desc(uint32 ogl_fmt, uint32 ogl_type, uint& block_dim, uint& by bytes_per_block = 8; break; } + case KTX_COMPRESSED_RGBA8_ETC2_EAC: case KTX_COMPRESSED_LUMINANCE_ALPHA_LATC2_EXT: case KTX_COMPRESSED_SIGNED_LUMINANCE_ALPHA_LATC2_EXT: case KTX_COMPRESSED_RED_GREEN_RGTC2_EXT: diff --git a/crnlib/crn_ktx_texture.h b/crnlib/crn_ktx_texture.h index ec914b2..6569879 100644 --- a/crnlib/crn_ktx_texture.h +++ b/crnlib/crn_ktx_texture.h @@ -44,6 +44,8 @@ typedef crnlib::vector ktx_image_data_vec; // Compressed pixel data formats: ETC1, DXT1, DXT3, DXT5 enum { KTX_ETC1_RGB8_OES = 0x8D64, + KTX_COMPRESSED_RGB8_ETC2 = 0x9274, + KTX_COMPRESSED_RGBA8_ETC2_EAC = 0x9278, KTX_RGB_S3TC = 0x83A0, KTX_RGB4_S3TC = 0x83A1, KTX_COMPRESSED_RGB_S3TC_DXT1_EXT = 0x83F0, diff --git a/crnlib/crn_mipmapped_texture.cpp b/crnlib/crn_mipmapped_texture.cpp index ed220bd..2986441 100644 --- a/crnlib/crn_mipmapped_texture.cpp +++ b/crnlib/crn_mipmapped_texture.cpp @@ -596,6 +596,16 @@ bool mipmapped_texture::read_dds_internal(data_stream_serializer& serializer) { dxt_fmt = cETC1; break; } + case PIXEL_FMT_ETC2: { + m_format = PIXEL_FMT_ETC2; + dxt_fmt = cETC2; + break; + } + case PIXEL_FMT_ETC2A: { + m_format = PIXEL_FMT_ETC2A; + dxt_fmt = cETC2A; + break; + } default: { dynamic_string err_msg(cVarArg, "Unsupported DDS FOURCC format: 0x%08X", desc.ddpfPixelFormat.dwFourCC); set_last_error(err_msg.get_ptr()); @@ -927,6 +937,16 @@ bool mipmapped_texture::write_dds(data_stream_serializer& serializer) const { desc.ddpfPixelFormat.dwRGBBitCount = 0; break; } + case PIXEL_FMT_ETC2: { + desc.ddpfPixelFormat.dwFourCC = (uint32)PIXEL_FMT_ETC2; + desc.ddpfPixelFormat.dwRGBBitCount = 0; + break; + } + case PIXEL_FMT_ETC2A: { + desc.ddpfPixelFormat.dwFourCC = (uint32)PIXEL_FMT_ETC2A; + desc.ddpfPixelFormat.dwRGBBitCount = 0; + break; + } case PIXEL_FMT_DXN: { desc.ddpfPixelFormat.dwFourCC = (uint32)PIXEL_FMT_3DC; desc.ddpfPixelFormat.dwRGBBitCount = PIXEL_FMT_DXN; @@ -1198,6 +1218,12 @@ bool mipmapped_texture::read_ktx(data_stream_serializer& serializer) { case KTX_ETC1_RGB8_OES: dxt_fmt = cETC1; break; + case KTX_COMPRESSED_RGB8_ETC2: + dxt_fmt = cETC2; + break; + case KTX_COMPRESSED_RGBA8_ETC2_EAC: + dxt_fmt = cETC2A; + break; case KTX_RGB_S3TC: case KTX_RGB4_S3TC: case KTX_COMPRESSED_RGB_S3TC_DXT1_EXT: @@ -1556,6 +1582,14 @@ bool mipmapped_texture::write_ktx(data_stream_serializer& serializer) const { ogl_internal_fmt = KTX_ETC1_RGB8_OES; break; } + case PIXEL_FMT_ETC2: { + ogl_internal_fmt = KTX_COMPRESSED_RGB8_ETC2; + break; + } + case PIXEL_FMT_ETC2A: { + ogl_internal_fmt = KTX_COMPRESSED_RGBA8_ETC2_EAC; + break; + } default: { CRNLIB_ASSERT(0); return false; @@ -1839,8 +1873,8 @@ bool mipmapped_texture::convert(pixel_format fmt, const dxt_image::pack_params& } bool mipmapped_texture::convert(pixel_format fmt, bool cook, const dxt_image::pack_params& p, int qdxt_quality, bool hierarchical) { - if ((!pixel_format_helpers::is_dxt(fmt)) || (fmt == PIXEL_FMT_DXT3) || (fmt == PIXEL_FMT_ETC1)) { - // QDXT doesn't support DXT3 or ETC1 yet. + if ((!pixel_format_helpers::is_dxt(fmt)) || (fmt == PIXEL_FMT_DXT3) || (fmt == PIXEL_FMT_ETC1) || (fmt == PIXEL_FMT_ETC2) || (fmt == PIXEL_FMT_ETC2A)) { + // QDXT doesn't support DXT3 or ETCn yet. return convert(fmt, cook, p); } @@ -2290,8 +2324,10 @@ bool mipmapped_texture::qdxt_pack_init(qdxt_state& state, mipmapped_texture& dst state.m_qdxt5_params[0].m_comp_index = 3; break; } - case PIXEL_FMT_ETC1: { - console::warning("mipmapped_texture::qdxt_pack_init: This method does not support ETC1"); + case PIXEL_FMT_ETC1: + case PIXEL_FMT_ETC2: + case PIXEL_FMT_ETC2A: { + console::warning("mipmapped_texture::qdxt_pack_init: This method does not support ETCn"); return false; } default: { diff --git a/crnlib/crn_pixel_format.cpp b/crnlib/crn_pixel_format.cpp index 1a02ae3..ae414c0 100644 --- a/crnlib/crn_pixel_format.cpp +++ b/crnlib/crn_pixel_format.cpp @@ -22,6 +22,8 @@ const pixel_format g_all_pixel_formats[] = PIXEL_FMT_DXT5_AGBR, PIXEL_FMT_DXT1A, PIXEL_FMT_ETC1, + PIXEL_FMT_ETC2, + PIXEL_FMT_ETC2A, PIXEL_FMT_R8G8B8, PIXEL_FMT_L8, PIXEL_FMT_A8, @@ -69,6 +71,10 @@ const char* get_pixel_format_string(pixel_format fmt) { return "DXT5_AGBR"; case PIXEL_FMT_ETC1: return "ETC1"; + case PIXEL_FMT_ETC2: + return "ETC2"; + case PIXEL_FMT_ETC2A: + return "ETC2A"; case PIXEL_FMT_R8G8B8: return "R8G8B8"; case PIXEL_FMT_A8R8G8B8: @@ -110,6 +116,10 @@ const char* get_crn_format_string(crn_format fmt) { return "DXT5A"; case cCRNFmtETC1: return "ETC1"; + case cCRNFmtETC2: + return "ETC2"; + case cCRNFmtETC2A: + return "ETC2A"; default: break; } @@ -123,7 +133,8 @@ component_flags get_component_flags(pixel_format fmt) { uint flags = cCompFlagRValid | cCompFlagGValid | cCompFlagBValid | cCompFlagAValid | cCompFlagGrayscale; switch (fmt) { case PIXEL_FMT_DXT1: - case PIXEL_FMT_ETC1: { + case PIXEL_FMT_ETC1: + case PIXEL_FMT_ETC2: { flags = cCompFlagRValid | cCompFlagGValid | cCompFlagBValid; break; } @@ -137,7 +148,8 @@ component_flags get_component_flags(pixel_format fmt) { break; } case PIXEL_FMT_DXT4: - case PIXEL_FMT_DXT5: { + case PIXEL_FMT_DXT5: + case PIXEL_FMT_ETC2A: { flags = cCompFlagRValid | cCompFlagGValid | cCompFlagBValid | cCompFlagAValid; break; } @@ -243,6 +255,12 @@ crn_format convert_pixel_format_to_best_crn_format(pixel_format crn_fmt) { case PIXEL_FMT_ETC1: fmt = cCRNFmtETC1; break; + case PIXEL_FMT_ETC2: + fmt = cCRNFmtETC2; + break; + case PIXEL_FMT_ETC2A: + fmt = cCRNFmtETC2A; + break; default: { CRNLIB_ASSERT(false); break; @@ -275,6 +293,10 @@ pixel_format convert_crn_format_to_pixel_format(crn_format fmt) { return PIXEL_FMT_DXT5A; case cCRNFmtETC1: return PIXEL_FMT_ETC1; + case cCRNFmtETC2: + return PIXEL_FMT_ETC2; + case cCRNFmtETC2A: + return PIXEL_FMT_ETC2A; default: { CRNLIB_ASSERT(false); break; diff --git a/crnlib/crn_pixel_format.h b/crnlib/crn_pixel_format.h index dc79d99..536cbe6 100644 --- a/crnlib/crn_pixel_format.h +++ b/crnlib/crn_pixel_format.h @@ -43,6 +43,7 @@ inline bool has_alpha(pixel_format fmt) { case PIXEL_FMT_A8: case PIXEL_FMT_A8L8: case PIXEL_FMT_DXT5_AGBR: + case PIXEL_FMT_ETC2A: return true; default: break; @@ -91,6 +92,8 @@ inline int is_dxt(pixel_format fmt) { case PIXEL_FMT_DXT5_xGBR: case PIXEL_FMT_DXT5_AGBR: case PIXEL_FMT_ETC1: + case PIXEL_FMT_ETC2: + case PIXEL_FMT_ETC2A: return true; default: break; @@ -143,6 +146,10 @@ inline dxt_format get_dxt_format(pixel_format fmt) { return cDXT5; case PIXEL_FMT_ETC1: return cETC1; + case PIXEL_FMT_ETC2: + return cETC2; + case PIXEL_FMT_ETC2A: + return cETC2A; default: break; } @@ -167,6 +174,10 @@ inline pixel_format from_dxt_format(dxt_format dxt_fmt) { return PIXEL_FMT_DXT5A; case cETC1: return PIXEL_FMT_ETC1; + case cETC2: + return PIXEL_FMT_ETC2; + case cETC2A: + return PIXEL_FMT_ETC2A; default: break; } @@ -214,6 +225,10 @@ inline uint get_bpp(pixel_format fmt) { return 4; case PIXEL_FMT_ETC1: return 4; + case PIXEL_FMT_ETC2: + return 4; + case PIXEL_FMT_ETC2A: + return 8; case PIXEL_FMT_DXT2: return 8; case PIXEL_FMT_DXT3: @@ -263,6 +278,10 @@ inline uint get_dxt_bytes_per_block(pixel_format fmt) { return 8; case PIXEL_FMT_ETC1: return 8; + case PIXEL_FMT_ETC2: + return 8; + case PIXEL_FMT_ETC2A: + return 16; case PIXEL_FMT_DXT2: return 16; case PIXEL_FMT_DXT3: diff --git a/crnlib/crn_rg_etc1.cpp b/crnlib/crn_rg_etc1.cpp index 28787ed..77b9ef1 100644 --- a/crnlib/crn_rg_etc1.cpp +++ b/crnlib/crn_rg_etc1.cpp @@ -7,6 +7,7 @@ // v1.03 - 5/12/13 - Initial public release #include "crn_core.h" #include "crn_rg_etc1.h" +#include "crn_dxt5a.h" #include #include @@ -404,6 +405,27 @@ static const int g_etc1_inten_tables[cETC1IntenModifierValues][cETC1SelectorValu {-106, -33, 33, 106}, {-183, -47, 47, 183}}; +static const uint8 g_etc2_modifier_table[8] = { 3, 6, 11, 16, 23, 32, 41, 64 }; + +static const int g_etc2a_modifier_table[16][8] = { + { -3, -6, -9, -15, 2, 5, 8, 14}, + { -3, -7, -10, -13, 2, 6, 9, 12}, + { -2, -5, -8, -13, 1, 4, 7, 12}, + { -2, -4, -6, -13, 1, 3, 5, 12}, + { -3, -6, -8, -12, 2, 5, 7, 11}, + { -3, -7, -9, -11, 2, 6, 8, 10}, + { -4, -7, -8, -11, 3, 6, 7, 10}, + { -3, -5, -8, -11, 2, 4, 7, 10}, + { -2, -6, -8, -10, 1, 5, 7, 9}, + { -2, -5, -8, -10, 1, 4, 7, 9}, + { -2, -4, -8, -10, 1, 3, 7, 9}, + { -2, -5, -7, -10, 1, 4, 6, 9}, + { -3, -4, -7, -10, 2, 3, 6, 9}, + { -1, -2, -3, -10, 0, 1, 2, 9}, + { -4, -6, -8, -9, 3, 5, 7, 8}, + { -3, -5, -7, -9, 2, 4, 6, 8}, +}; + static const uint8 g_etc1_to_selector_index[cETC1SelectorValues] = {2, 3, 1, 0}; static const uint8 g_selector_index_to_etc1[cETC1SelectorValues] = {3, 2, 0, 1}; @@ -1428,6 +1450,104 @@ bool unpack_etc1_block(const void* pETC1_block, unsigned int* pDst_pixels_rgba, return success; } +bool unpack_etc2_color(const void* pBlock, unsigned int* pDst_pixels_rgba, bool preserve_alpha) { + if (unpack_etc1_block(pBlock, pDst_pixels_rgba, preserve_alpha)) + return true; + + color_quad_u8* pDst = reinterpret_cast(pDst_pixels_rgba); + const etc1_block& block = *static_cast(pBlock); + const uint8* B = block.m_bytes; + const bool rOverflow = (int8(B[0] << 5) >> 5) + (B[0] >> 3) & 0x20; + const bool gOverflow = (int8(B[1] << 5) >> 5) + (B[1] >> 3) & 0x20; + + if (rOverflow || gOverflow) { + color_quad_u8 block_colors[4]; + if (rOverflow) { + uint8 unpacked[3] = { + B[0] & 0x3 | B[0] >> 1 & 0xC | B[2] & 0xF0, + B[1] >> 4 | B[2] << 4, + B[1] & 0xF | B[3] & 0xF0, + }; + uint8 delta = g_etc2_modifier_table[B[3] & 1 | B[3] >> 1 & 6]; + for (uint c = 0; c < 3; c++) { + block_colors[2][c] = unpacked[c] << 4 | unpacked[c] & 0xF; + block_colors[1][c] = unpacked[c] >> 4 | unpacked[c] & 0xF0; + block_colors[0][c] = math::maximum(0, block_colors[1][c] - delta); + block_colors[3][c] = math::minimum(255, block_colors[1][c] + delta); + } + } else { + uint8 unpacked[3] = { + B[0] >> 3 & 0xF | B[2] << 1 & 0xF0, + B[1] >> 4 & 0x1 | B[0] << 1 & 0xE | B[3] >> 3 & 0x10 | B[2] << 5, + B[2] >> 7 | B[1] << 1 & 0x6 | B[1] & 0x8 | B[3] << 1 & 0xF0, + }; + uint8 modifier = B[3] & 4 | B[3] << 1 & 2 | 1; + for (int d = 0, c = 0; !d && c < 3; c++, modifier &= d < 0 ? 6 : 7) + d = (unpacked[c] & 0xF) - (unpacked[c] >> 4); + uint8 delta = g_etc2_modifier_table[modifier]; + for (uint c = 0; c < 3; c++) { + uint8 c0 = unpacked[c] << 4 | unpacked[c] & 0xF; + uint8 c1 = unpacked[c] >> 4 | unpacked[c] & 0xF0; + block_colors[0][c] = math::maximum(0, c1 - delta); + block_colors[1][c] = math::minimum(255, c1 + delta); + block_colors[2][c] = math::minimum(255, c0 + delta); + block_colors[3][c] = math::maximum(0, c0 - delta); + } + } + for (uint i = 0; i < 4; i++) { + for (uint j = 0; j < 4; j++, pDst++) { + pDst->set_rgb(block_colors[block.get_selector(j, i)]); + if (!preserve_alpha) + pDst->a = 255; + } + } + } else { + int16 base[3], dj[3], di[3], color[3]; + base[0] = B[0] << 1 & 0xFC | B[0] >> 5 & 3; + base[1] = B[0] << 7 & 0x80 | B[1] & 0x7E | B[0] & 1; + base[2] = B[1] << 7 & 0x80 | B[2] << 2 & 0x60 | B[2] << 3 & 0x18 | B[3] >> 5 & 4 | B[1] << 1 & 2 | B[2] >> 4 & 1; + di[0] = (B[5] << 5 & 0xE0 | B[6] >> 3 & 0x1C | B[5] >> 1 & 0x3) - base[0]; + di[1] = (B[6] << 3 & 0xF8 | B[7] >> 5 & 0x6 | B[6] >> 4 & 0x1) - base[1]; + di[2] = (B[7] << 2 & 0xFC | B[7] >> 4 & 0x3) - base[2]; + dj[0] = (B[3] << 1 & 0xF8 | B[3] << 2 & 0x4 | B[3] >> 5 & 0x3) - base[0]; + dj[1] = (B[4] & 0xFE | B[4] >> 7) - base[1]; + dj[2] = (B[4] << 7 & 0x80 | B[5] >> 1 & 0x7C | B[4] << 1 & 0x2 | B[5] >> 7) - base[2]; + for (uint c = 0; c < 3; c++) + base[c] = (base[c] << 2) + 2; + for (uint i = 0; i < 4; i++) { + for (uint c = 0; c < 3; base[c] += di[c], c++) + color[c] = base[c]; + for (uint j = 0; j < 4; j++, pDst++) { + for (uint c = 0; c < 3; color[c] += dj[c], c++) + pDst->c[c] = math::clamp(color[c], 0, 1020) >> 2; + if (!preserve_alpha) + pDst->a = 255; + } + } + } + return true; +} + +bool unpack_etc2_alpha(const void* pBlock, unsigned int* pDst_pixels_rgba, int comp_index) { + color_quad_u8* pDst = (color_quad_u8*)pDst_pixels_rgba; + const uint8* B = (const uint8*)pBlock; + const int* modifier = g_etc2a_modifier_table[B[1] & 0xF]; + uint8 values[8]; + for (int base_codeword = B[0], multiplier = B[1] >> 4, i = 0; i < 8; i++) + values[i] = math::clamp(base_codeword + modifier[i] * multiplier, 0, 255); + for (uint d0 = 3, i = 0; i < 4; i++, d0 += 3) { + for (uint d = d0, j = 0; j < 4; j++, pDst++, d += 12) { + int byte_offset = 2 + (d >> 3); + int bit_offset = d & 7; + int s = B[byte_offset] >> 8 - bit_offset & 7; + if (bit_offset < 3) + s |= B[byte_offset - 1] << bit_offset & 7; + pDst->c[comp_index] = values[s]; + } + } + return true; +} + struct etc1_solution_coordinates { inline etc1_solution_coordinates() : m_unscaled_color(0, 0, 0, 0), @@ -2275,13 +2395,6 @@ unsigned int pack_etc1_block(void* pETC1_block, const unsigned int* pSrc_pixels_ const color_quad_u8* pSrc_pixels = reinterpret_cast(pSrc_pixels_rgba); etc1_block& dst_block = *static_cast(pETC1_block); -#ifdef RG_ETC1_BUILD_DEBUG - // Ensure all alpha values are 0xFF. - for (uint i = 0; i < 16; i++) { - RG_ETC1_ASSERT(pSrc_pixels[i].a == 255); - } -#endif - color_quad_u8 src_pixel0(pSrc_pixels[0]); // Check for solid block. @@ -2520,6 +2633,60 @@ unsigned int pack_etc1_block(void* pETC1_block, const unsigned int* pSrc_pixels_ return static_cast(best_error); } +unsigned int pack_etc2_alpha(void* pBlock, const unsigned int* pSrc_pixels_rgba, etc2a_pack_params& pack_params) { + crnlib::color_quad_u8* pixels = (crnlib::color_quad_u8*)pSrc_pixels_rgba; + dxt5_endpoint_optimizer dxt5_optimizer; + + dxt5_endpoint_optimizer::results results; + uint8 selectors[16]; + results.m_pSelectors = selectors; + + dxt5_endpoint_optimizer::params params; + params.m_pPixels = pixels; + params.m_num_pixels = 16; + params.m_comp_index = pack_params.comp_index; + params.m_quality = pack_params.m_quality == cHighQuality ? cCRNDXTQualityUber : pack_params.m_quality == cMediumQuality ? cCRNDXTQualityNormal : cCRNDXTQualityFast; + params.m_use_both_block_types = false; + dxt5_optimizer.compute(params, results); + + uint base_codeword = (results.m_first_endpoint + results.m_second_endpoint + 1) >> 1; + uint best_error = cUINT32_MAX; + for (int modifier_index = 0; modifier_index < 16; modifier_index++) { + const int* modifier = g_etc2a_modifier_table[modifier_index]; + int multiplier = math::clamp((results.m_first_endpoint - results.m_second_endpoint + modifier[7] + (modifier[7] >> 1)) / (modifier[7] << 1), 1, 15); + uint8 data[8] = {base_codeword, multiplier << 4 | modifier_index}, values[8]; + for (int i = 0; i < 8; i++) + values[i] = math::clamp(base_codeword + modifier[i] * multiplier, 0, 255); + uint error = 0; + for (uint d0 = 3, t = 0, i = 0; i < 4; i++, d0 += 3) { + for (uint d = d0, j = 0; j < 4; j++, t++, d += 12) { + int a = pixels[t].a; + uint byte_offset = 2 + (d >> 3); + uint bit_offset = d & 7; + uint best_s = 0; + uint best_delta = cUINT32_MAX; + for (uint s = 0; s < 8; s++) { + uint delta = abs(a - values[s]); + if (delta < best_delta) { + best_s = s; + best_delta = delta; + } + } + error += best_delta * best_delta; + data[byte_offset] |= best_s << 8 - bit_offset; + if (bit_offset < 3) + data[byte_offset - 1] |= best_s >> bit_offset; + } + } + if (error < best_error) { + memcpy(pBlock, data, 8); + best_error = error; + } + } + + return best_error; +} + } // namespace rg_etc1 } // namespace crnlib diff --git a/crnlib/crn_rg_etc1.h b/crnlib/crn_rg_etc1.h index 52cfc88..9378ae2 100644 --- a/crnlib/crn_rg_etc1.h +++ b/crnlib/crn_rg_etc1.h @@ -10,6 +10,8 @@ namespace rg_etc1 { // This function is thread safe, and does not dynamically allocate any memory. // If preserve_alpha is true, the alpha channel of the destination pixels will not be overwritten. Otherwise, alpha will be set to 255. bool unpack_etc1_block(const void* pETC1_block, unsigned int* pDst_pixels_rgba, bool preserve_alpha = false); +bool unpack_etc2_color(const void* pBlock, unsigned int* pDst_pixels_rgba, bool preserve_alpha = false); +bool unpack_etc2_alpha(const void* pBlock, unsigned int* pDst_pixels_rgba, int comp_index = 3); // Quality setting = the higher the quality, the slower. // To pack large textures, it is highly recommended to call pack_etc1_block() in parallel, on different blocks, from multiple threads (particularly when using cHighQuality). @@ -33,6 +35,20 @@ struct etc1_pack_params { } }; +struct etc2a_pack_params { + etc1_quality m_quality; + int comp_index; + + inline etc2a_pack_params() { + clear(); + } + + void clear() { + m_quality = cHighQuality; + comp_index = 3; + } +}; + // Important: pack_etc1_block_init() must be called before calling pack_etc1_block(). void pack_etc1_block_init(); @@ -42,6 +58,7 @@ void pack_etc1_block_init(); // This function is thread safe, and does not dynamically allocate any memory. // pack_etc1_block() does not currently support "perceptual" colorspace metrics - it primarily optimizes for RGB RMSE. unsigned int pack_etc1_block(void* pETC1_block, const unsigned int* pSrc_pixels_rgba, etc1_pack_params& pack_params); +unsigned int pack_etc2_alpha(void* pBlock, const unsigned int* pSrc_pixels_rgba, etc2a_pack_params& pack_params); } // namespace rg_etc1 diff --git a/crnlib/crn_texture_conversion.cpp b/crnlib/crn_texture_conversion.cpp index 810ddf0..1ec13be 100644 --- a/crnlib/crn_texture_conversion.cpp +++ b/crnlib/crn_texture_conversion.cpp @@ -285,11 +285,11 @@ static pixel_format choose_pixel_format(convert_params& params, const crn_comp_p return pixel_format_helpers::has_alpha(src_fmt) ? PIXEL_FMT_A8R8G8B8 : PIXEL_FMT_R8G8B8; } else if (pixel_format_helpers::is_grayscale(src_fmt)) { if (pixel_format_helpers::has_alpha(src_fmt)) - return PIXEL_FMT_A8L8; + return PIXEL_FMT_ETC2A; else return PIXEL_FMT_ETC1; } else if (pixel_format_helpers::has_alpha(src_fmt)) - return PIXEL_FMT_A8R8G8B8; + return PIXEL_FMT_ETC2A; else return PIXEL_FMT_ETC1; } diff --git a/crunch/crunch.cpp b/crunch/crunch.cpp index b1e51d8..684d4c4 100644 --- a/crunch/crunch.cpp +++ b/crunch/crunch.cpp @@ -481,7 +481,8 @@ class crunch { out_file_type = texture_file_types::cFormatDDS; cfile_stream in_stream; crnd::crn_header in_header; - if (in_stream.open(in_filename.get_ptr()) && in_stream.read(&in_header, sizeof(in_header)) == sizeof(in_header) && in_header.m_format == cCRNFmtETC1) + if (in_stream.open(in_filename.get_ptr()) && in_stream.read(&in_header, sizeof(in_header)) == sizeof(in_header) && + (in_header.m_format == cCRNFmtETC1 || in_header.m_format == cCRNFmtETC2 || in_header.m_format == cCRNFmtETC2A)) out_file_type = texture_file_types::cFormatKTX; } else if (input_file_type == texture_file_types::cFormatKTX) { // Default to converting KTX files to PNG diff --git a/inc/crn_decomp.h b/inc/crn_decomp.h index 580ec27..9c3fdba 100644 --- a/inc/crn_decomp.h +++ b/inc/crn_decomp.h @@ -2127,6 +2127,10 @@ uint32 crnd_crn_format_to_fourcc(crn_format fmt) { return CRND_FOURCC('A', 'G', 'B', 'R'); case cCRNFmtETC1: return CRND_FOURCC('E', 'T', 'C', '1'); + case cCRNFmtETC2: + return CRND_FOURCC('E', 'T', 'C', '2'); + case cCRNFmtETC2A: + return CRND_FOURCC('E', 'T', '2', 'A'); default: break; } @@ -2152,6 +2156,7 @@ uint32 crnd_get_crn_format_bits_per_texel(crn_format fmt) { case cCRNFmtDXT1: case cCRNFmtDXT5A: case cCRNFmtETC1: + case cCRNFmtETC2: return 4; case cCRNFmtDXT3: case cCRNFmtDXT5: @@ -2161,6 +2166,7 @@ uint32 crnd_get_crn_format_bits_per_texel(crn_format fmt) { case cCRNFmtDXT5_xGxR: case cCRNFmtDXT5_xGBR: case cCRNFmtDXT5_AGBR: + case cCRNFmtETC2A: return 8; default: break; @@ -2272,7 +2278,7 @@ bool crnd_get_texture_info(const void* pData, uint32 data_size, crn_texture_info pInfo->m_levels = pHeader->m_levels; pInfo->m_faces = pHeader->m_faces; pInfo->m_format = static_cast((uint32)pHeader->m_format); - pInfo->m_bytes_per_block = (pHeader->m_format == cCRNFmtDXT1 || pHeader->m_format == cCRNFmtDXT5A || pHeader->m_format == cCRNFmtETC1) ? 8 : 16; + pInfo->m_bytes_per_block = pHeader->m_format == cCRNFmtDXT1 || pHeader->m_format == cCRNFmtDXT5A || pHeader->m_format == cCRNFmtETC1 || pHeader->m_format == cCRNFmtETC2 ? 8 : 16; pInfo->m_userdata0 = pHeader->m_userdata0; pInfo->m_userdata1 = pHeader->m_userdata1; @@ -3011,7 +3017,7 @@ class crn_unpacker { const uint32 height = math::maximum(m_pHeader->m_height >> level_index, 1U); const uint32 blocks_x = (width + 3U) >> 2U; const uint32 blocks_y = (height + 3U) >> 2U; - const uint32 block_size = (m_pHeader->m_format == cCRNFmtDXT1 || m_pHeader->m_format == cCRNFmtDXT5A || m_pHeader->m_format == cCRNFmtETC1) ? 8 : 16; + const uint32 block_size = m_pHeader->m_format == cCRNFmtDXT1 || m_pHeader->m_format == cCRNFmtDXT5A || m_pHeader->m_format == cCRNFmtETC1 || m_pHeader->m_format == cCRNFmtETC2 ? 8 : 16; uint32 minimal_row_pitch = block_size * blocks_x; if (!row_pitch_in_bytes) @@ -3046,6 +3052,12 @@ class crn_unpacker { case cCRNFmtETC1: status = unpack_etc1((uint8**)pDst, row_pitch_in_bytes, blocks_x, blocks_y); break; + case cCRNFmtETC2: + status = unpack_etc1((uint8**)pDst, row_pitch_in_bytes, blocks_x, blocks_y); + break; + case cCRNFmtETC2A: + status = unpack_etc2a((uint8**)pDst, row_pitch_in_bytes, blocks_x, blocks_y); + break; default: return false; } @@ -3128,7 +3140,7 @@ class crn_unpacker { if (m_pHeader->m_alpha_endpoints.m_num) { if (!decode_alpha_endpoints()) return false; - if (!decode_alpha_selectors()) + if (!(m_pHeader->m_format == cCRNFmtETC2A ? decode_alpha_selectors_etc() : decode_alpha_selectors())) return false; } @@ -3137,6 +3149,7 @@ class crn_unpacker { bool decode_color_endpoints() { const uint32 num_color_endpoints = m_pHeader->m_color_endpoints.m_num; + const bool has_etc_color_blocks = m_pHeader->m_format == cCRNFmtETC1 || m_pHeader->m_format == cCRNFmtETC2 || m_pHeader->m_format == cCRNFmtETC2A; if (!m_color_endpoints.resize(num_color_endpoints)) return false; @@ -3145,7 +3158,7 @@ class crn_unpacker { return false; static_huffman_data_model dm[2]; - for (uint32 i = 0; i < (m_pHeader->m_format == cCRNFmtETC1 ? 1 : 2); i++) + for (uint32 i = 0; i < (has_etc_color_blocks ? 1 : 2); i++) if (!m_codec.decode_receive_static_data_model(dm[i])) return false; @@ -3155,7 +3168,7 @@ class crn_unpacker { uint32* CRND_RESTRICT pDst = &m_color_endpoints[0]; for (uint32 i = 0; i < num_color_endpoints; i++) { - if (m_pHeader->m_format == cCRNFmtETC1) { + if (has_etc_color_blocks) { for (b = 0; b < 32; b += 8) a += m_codec.decode(dm[0]) << b; *pDst++ = a &= 0x1F1F1F1F; @@ -3176,14 +3189,15 @@ class crn_unpacker { } bool decode_color_selectors() { + const bool has_etc_color_blocks = m_pHeader->m_format == cCRNFmtETC1 || m_pHeader->m_format == cCRNFmtETC2 || m_pHeader->m_format == cCRNFmtETC2A; m_codec.start_decoding(m_pData + m_pHeader->m_color_selectors.m_ofs, m_pHeader->m_color_selectors.m_size); static_huffman_data_model dm; m_codec.decode_receive_static_data_model(dm); - m_color_selectors.resize(m_pHeader->m_color_selectors.m_num << (m_pHeader->m_format == cCRNFmtETC1 ? 1 : 0)); + m_color_selectors.resize(m_pHeader->m_color_selectors.m_num << (has_etc_color_blocks ? 1 : 0)); for (uint32 s = 0, i = 0; i < m_pHeader->m_color_selectors.m_num; i++) { for (uint32 j = 0; j < 32; j += 4) s ^= m_codec.decode(dm) << j; - if (m_pHeader->m_format == cCRNFmtETC1) { + if (has_etc_color_blocks) { for (uint32 selector = ~s & 0xAAAAAAAA | ~(s ^ s >> 1) & 0x55555555, t = 8, h = 0; h < 4; h++, t -= 15) { for (uint32 w = 0; w < 4; w++, t += 4) { uint32 s0 = selector >> (w << 3 | h << 1); @@ -3249,6 +3263,37 @@ class crn_unpacker { return true; } + bool decode_alpha_selectors_etc() { + m_codec.start_decoding(m_pData + m_pHeader->m_alpha_selectors.m_ofs, m_pHeader->m_alpha_selectors.m_size); + static_huffman_data_model dm; + m_codec.decode_receive_static_data_model(dm); + m_alpha_selectors.resize(m_pHeader->m_alpha_selectors.m_num * 6); + uint8 s_linear[8] = {}; + uint8* data = (uint8*)m_alpha_selectors.begin(); + for (uint i = 0; i < m_alpha_selectors.size(); i += 6, data += 12) { + for (uint s_group, p = 0; p < 16; p++) { + s_group = p & 1 ? s_group >> 3 : s_linear[p >> 1] ^= m_codec.decode(dm); + uint8 s = s_group & 7; + if (s <= 3) + s = 3 - s; + uint8 d = 3 * (p + 1); + uint8 byte_offset = d >> 3; + uint8 bit_offset = d & 7; + data[byte_offset] |= s << 8 - bit_offset; + if (bit_offset < 3) + data[byte_offset - 1] |= s >> bit_offset; + d += 9 * ((p & 3) - (p >> 2)); + byte_offset = d >> 3; + bit_offset = d & 7; + data[byte_offset + 6] |= s << 8 - bit_offset; + if (bit_offset < 3) + data[byte_offset + 5] |= s >> bit_offset; + } + } + m_codec.stop_decoding(); + return true; + } + static inline uint32 tiled_offset_2d_outer(uint32 y, uint32 AlignedWidth, uint32 LogBpp) { uint32 Macro = ((y >> 5) * (AlignedWidth >> 5)) << (LogBpp + 7); uint32 Micro = ((y & 6) << 2) << LogBpp; @@ -3570,6 +3615,86 @@ class crn_unpacker { return true; } + bool unpack_etc2a(uint8** pDst, uint32 output_pitch_in_bytes, uint32 output_width, uint32 output_height) { + const uint32 num_color_endpoints = m_color_endpoints.size(); + const uint32 num_alpha_endpoints = m_alpha_endpoints.size(); + const uint32 width = output_width + 1 & ~1; + const uint32 height = output_height + 1 & ~1; + const int32 delta_pitch_in_dwords = (output_pitch_in_bytes >> 2) - (width << 2); + + if (m_block_buffer.size() < width << 1) + m_block_buffer.resize(width << 1); + + uint32 color_endpoint_index = 0, diagonal_color_endpoint_index = 0, alpha0_endpoint_index = 0, diagonal_alpha0_endpoint_index = 0; + uint8 reference_group = 0; + + for (uint32 f = 0; f < m_pHeader->m_faces; f++) { + uint32* pData = (uint32*)pDst[f]; + for (uint32 y = 0; y < height; y++, pData += delta_pitch_in_dwords) { + bool visible = y < output_height; + for (uint32 x = 0; x < width; x++, pData += 4) { + visible = visible && x < output_width; + block_buffer_element &buffer = m_block_buffer[x << 1]; + uint8 endpoint_reference, block_endpoint[4], e0[4], e1[4]; + if (y & 1) { + endpoint_reference = buffer.endpoint_reference; + } else { + reference_group = m_codec.decode(m_reference_encoding_dm); + endpoint_reference = reference_group & 3 | reference_group >> 2 & 12; + buffer.endpoint_reference = reference_group >> 2 & 3 | reference_group >> 4 & 12; + } + if (!(endpoint_reference & 3)) { + color_endpoint_index += m_codec.decode(m_endpoint_delta_dm[0]); + if (color_endpoint_index >= num_color_endpoints) + color_endpoint_index -= num_color_endpoints; + alpha0_endpoint_index += m_codec.decode(m_endpoint_delta_dm[1]); + if (alpha0_endpoint_index >= num_alpha_endpoints) + alpha0_endpoint_index -= num_alpha_endpoints; + buffer.color_endpoint_index = color_endpoint_index; + buffer.alpha0_endpoint_index = alpha0_endpoint_index; + } else if ((endpoint_reference & 3) == 1) { + buffer.color_endpoint_index = color_endpoint_index; + buffer.alpha0_endpoint_index = alpha0_endpoint_index; + } else if ((endpoint_reference & 3) == 3) { + buffer.color_endpoint_index = color_endpoint_index = diagonal_color_endpoint_index; + buffer.alpha0_endpoint_index = alpha0_endpoint_index = diagonal_alpha0_endpoint_index; + } else { + color_endpoint_index = buffer.color_endpoint_index; + alpha0_endpoint_index = buffer.alpha0_endpoint_index; + } + endpoint_reference >>= 2; + *(uint32*)&e0 = m_color_endpoints[color_endpoint_index]; + uint32 color_selector_index = m_codec.decode(m_selector_delta_dm[0]); + uint32 alpha0_selector_index = m_codec.decode(m_selector_delta_dm[1]); + if (endpoint_reference) { + color_endpoint_index += m_codec.decode(m_endpoint_delta_dm[0]); + if (color_endpoint_index >= num_color_endpoints) + color_endpoint_index -= num_color_endpoints; + } + *(uint32*)&e1 = m_color_endpoints[color_endpoint_index]; + diagonal_color_endpoint_index = m_block_buffer[x << 1 | 1].color_endpoint_index; + diagonal_alpha0_endpoint_index = m_block_buffer[x << 1 | 1].alpha0_endpoint_index; + m_block_buffer[x << 1 | 1].color_endpoint_index = color_endpoint_index; + m_block_buffer[x << 1 | 1].alpha0_endpoint_index = alpha0_endpoint_index; + if (visible) { + uint32 flip = endpoint_reference >> 1 ^ 1, diff = 1; + for (uint c = 0; diff && c < 3; c++) + diff = e0[c] + 3 >= e1[c] && e1[c] + 4 >= e0[c] ? diff : 0; + for (uint c = 0; c < 3; c++) + block_endpoint[c] = diff ? e0[c] << 3 | e1[c] - e0[c] & 7 : e0[c] << 3 & 0xF0 | e1[c] >> 1; + block_endpoint[3] = e0[3] << 5 | e1[3] << 2 | diff << 1 | flip; + const uint16* pAlpha0_selectors = &m_alpha_selectors[alpha0_selector_index * 6 + (flip ? 3 : 0)]; + pData[0] = m_alpha_endpoints[alpha0_endpoint_index] | pAlpha0_selectors[0] << 16; + pData[1] = pAlpha0_selectors[1] | pAlpha0_selectors[2] << 16; + pData[2] = *(uint32*)&block_endpoint; + pData[3] = m_color_selectors[color_selector_index << 1 | flip]; + } + } + } + } + return true; + } + }; crnd_unpack_context crnd_unpack_begin(const void* pData, uint32 data_size) { diff --git a/inc/crnlib.h b/inc/crnlib.h index b810b65..fb93b63 100644 --- a/inc/crnlib.h +++ b/inc/crnlib.h @@ -71,6 +71,8 @@ enum crn_format { cCRNFmtDXT5A, cCRNFmtETC1, + cCRNFmtETC2, + cCRNFmtETC2A, cCRNFmtTotal, diff --git a/inc/dds_defs.h b/inc/dds_defs.h index 8e9b8e5..1216ff7 100644 --- a/inc/dds_defs.h +++ b/inc/dds_defs.h @@ -28,6 +28,8 @@ enum pixel_format { PIXEL_FMT_DXT1A = CRNLIB_PIXEL_FMT_FOURCC('D', 'X', '1', 'A'), PIXEL_FMT_ETC1 = CRNLIB_PIXEL_FMT_FOURCC('E', 'T', 'C', '1'), + PIXEL_FMT_ETC2 = CRNLIB_PIXEL_FMT_FOURCC('E', 'T', 'C', '2'), + PIXEL_FMT_ETC2A = CRNLIB_PIXEL_FMT_FOURCC('E', 'T', '2', 'A'), PIXEL_FMT_R8G8B8 = CRNLIB_PIXEL_FMT_FOURCC('R', 'G', 'B', 'x'), PIXEL_FMT_L8 = CRNLIB_PIXEL_FMT_FOURCC('L', 'x', 'x', 'x'),