diff --git a/bin/crunch_x64.exe b/bin/crunch_x64.exe index 8b7053d..e95ac9c 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 438af4c..e341a8c 100644 --- a/crnlib/crn_comp.cpp +++ b/crnlib/crn_comp.cpp @@ -272,7 +272,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; + 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; @@ -336,7 +336,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_has_etc_color_blocks ? 1 : 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; @@ -354,6 +354,7 @@ void crn_comp::clear() { utils::zero_object(m_has_comp); m_has_etc_color_blocks = false; + m_has_subblocks = false; m_levels.clear(); @@ -413,8 +414,8 @@ bool crn_comp::quantize_images() { 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; + 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; @@ -530,6 +531,18 @@ bool crn_comp::quantize_images() { 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; } @@ -695,7 +708,7 @@ void crn_comp::optimize_color_endpoints_task(uint64 data, void* pData_ptr) { 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_etc_color_blocks && b & 1 ? m_endpoint_indices[b].reference : !m_endpoint_indices[b].reference) { + 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]++; } @@ -773,7 +786,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_has_etc_color_blocks && b & 1 ? m_endpoint_indices[b].reference : !m_endpoint_indices[b].reference) && i != i_prev) { + 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]++; @@ -1279,7 +1292,8 @@ 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; + 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; diff --git a/crnlib/crn_comp.h b/crnlib/crn_comp.h index 14d03de..2c20133 100644 --- a/crnlib/crn_comp.h +++ b/crnlib/crn_comp.h @@ -46,6 +46,7 @@ class crn_comp : public itexture_comp { bool m_has_comp[cNumComps]; bool m_has_etc_color_blocks; + bool m_has_subblocks; struct level_details { uint first_block; diff --git a/crnlib/crn_dxt.cpp b/crnlib/crn_dxt.cpp index 272e5d1..5fa56c0 100644 --- a/crnlib/crn_dxt.cpp +++ b/crnlib/crn_dxt.cpp @@ -41,6 +41,10 @@ const char* get_dxt_format_string(dxt_format fmt) { return "ETC2"; case cETC2A: return "ETC2A"; + case cETC1S: + return "ETC1S"; + case cETC2AS: + return "ETC2AS"; default: break; } @@ -74,12 +78,14 @@ uint get_dxt_format_bits_per_pixel(dxt_format fmt) { case cDXT5A: case cETC1: case cETC2: + case cETC1S: return 4; case cDXT3: case cDXT5: case cDXN_XY: case cDXN_YX: case cETC2A: + case cETC2AS: return 8; default: break; @@ -95,6 +101,7 @@ bool get_dxt_format_has_alpha(dxt_format fmt) { case cDXT5: case cDXT5A: case cETC2A: + case cETC2AS: return true; default: break; diff --git a/crnlib/crn_dxt.h b/crnlib/crn_dxt.h index b5740f8..df5f9a7 100644 --- a/crnlib/crn_dxt.h +++ b/crnlib/crn_dxt.h @@ -45,6 +45,8 @@ enum dxt_format { cETC1, cETC2, cETC2A, + cETC1S, + cETC2AS, }; const float cDXT1MaxLinearValue = 3.0f; diff --git a/crnlib/crn_dxt_hc.cpp b/crnlib/crn_dxt_hc.cpp index aea1f6a..a099be8 100644 --- a/crnlib/crn_dxt_hc.cpp +++ b/crnlib/crn_dxt_hc.cpp @@ -27,6 +27,7 @@ dxt_hc::dxt_hc() : m_num_blocks(0), m_has_color_blocks(false), m_has_etc_color_blocks(false), + m_has_subblocks(false), m_num_alpha_blocks(0), m_main_thread_id(crn_get_current_thread_id()), m_canceled(false), @@ -78,9 +79,10 @@ bool dxt_hc::compress( const params& p ) { clear(); - m_has_etc_color_blocks = p.m_format == cETC1 || p.m_format == cETC2 || p.m_format == cETC2A; + m_has_etc_color_blocks = p.m_format == cETC1 || p.m_format == cETC2 || p.m_format == cETC2A || p.m_format == cETC1S || p.m_format == cETC2AS; + m_has_subblocks = 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; + m_num_alpha_blocks = p.m_format == cDXT5 || p.m_format == cDXT5A || p.m_format == cETC2A || p.m_format == cETC2AS ? 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; @@ -118,7 +120,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_has_etc_color_blocks ? &dxt_hc::determine_tiles_task_etc : &dxt_hc::determine_tiles_task, i); + m_pTask_pool->queue_object_task(this, m_has_subblocks ? &dxt_hc::determine_tiles_task_etc : &dxt_hc::determine_tiles_task, i); m_pTask_pool->join(); m_num_tiles = 0; @@ -212,7 +214,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_has_etc_color_blocks && top_match && bx; + bool diag_match = m_has_subblocks && 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]; @@ -222,7 +224,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_has_etc_color_blocks && b & 1 ? m_endpoint_indices[b].reference : left_match ? 1 : top_match ? 2 : diag_match ? 3 : 0; + endpoint_indices[b].reference = m_has_subblocks && b & 1 ? m_endpoint_indices[b].reference : left_match ? 1 : top_match ? 2 : diag_match ? 3 : 0; } } } @@ -290,6 +292,15 @@ void dxt_hc::determine_tiles_task(uint64 data, void*) { uint tile_error[3][9]; uint total_error[3][8]; + etc1_optimizer optimizer; + etc1_optimizer::params params; + params.m_use_color4 = false; + params.m_constrain_against_base_color5 = false; + etc1_optimizer::results results; + results.m_pSelectors = selectors; + int scan[] = {-1, 0, 1}; + int refine[] = {-3, -2, 2, 3}; + for (uint level = 0; level < m_params.m_num_levels; level++) { float weight = m_params.m_levels[level].m_weight; uint width = m_params.m_levels[level].m_block_width; @@ -318,7 +329,20 @@ void dxt_hc::determine_tiles_task(uint64 data, void*) { for (uint t = 0; t < 9; t++) { color_quad_u8* pixels = tilePixels + offsets[t]; uint size = 16 << (t >> 2); - if (m_has_color_blocks) { + if (m_has_etc_color_blocks) { + params.m_pSrc_pixels = pixels; + params.m_num_src_pixels = results.m_n = size; + optimizer.init(params, results); + params.m_pScan_deltas = scan; + params.m_scan_delta_size = sizeof(scan) / sizeof(*scan); + optimizer.compute(); + if (results.m_error > 375 * params.m_num_src_pixels) { + params.m_pScan_deltas = refine; + params.m_scan_delta_size = sizeof(refine) / sizeof(*refine); + optimizer.compute(); + } + tile_error[cColor][t] = results.m_error; + } else if (m_has_color_blocks) { uint low16, high16; dxt_fast::compress_color_block(size, pixels, low16, high16, selectors); color_quad_u8 block_colors[4]; @@ -605,15 +629,17 @@ void dxt_hc::determine_color_endpoint_codebook_task_etc(uint64 data, void*) { float endpoint_weight = powf(math::minimum((cluster.color_values[3].get_luma() - cluster.color_values[0].get_luma()) / 100.0f, 1.0f), 2.7f); crnlib::vector& blocks = cluster.blocks[cColor]; + uint blockSize = m_has_subblocks ? 8 : 16; for (uint i = 0; i < blocks.size(); i++) { uint b = blocks[i]; + color_quad_u8* pixels = m_has_subblocks ? ((color_quad_u8(*)[8])m_blocks)[b] : m_blocks[b]; uint weight = (uint)(math::clamp(0x8000 * endpoint_weight * m_block_weights[b] * (m_block_encodings[b] ? 0.972f : 1.0f), 1, 0xFFFF)); uint32 selector = 0; - for (uint p = 0; p < 8; p++) { + for (uint p = 0; p < blockSize; p++) { uint error_best = cUINT32_MAX; uint8 s_best = 0; for (uint8 s = 0; s < 4; s++) { - uint error = color::color_distance(m_params.m_perceptual, ((color_quad_u8(*)[8])m_blocks)[b][p], cluster.color_values[s], false); + uint error = color::color_distance(m_params.m_perceptual, pixels[p], cluster.color_values[s], false); if (error < error_best) { s_best = s; error_best = error; @@ -621,7 +647,7 @@ void dxt_hc::determine_color_endpoint_codebook_task_etc(uint64 data, void*) { } selector = selector << 2 | s_best; } - m_block_selectors[cColor][b] = (uint64)selector << ((b & 1) ? 32 : 48) | weight; + m_block_selectors[cColor][b] = (uint64)selector << (!m_has_subblocks || (b & 1) ? 32 : 48) | weight; } } } @@ -731,7 +757,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_has_etc_color_blocks && m_endpoint_indices[b].reference && cluster_index == m_endpoint_indices[b - 1].component[cColor]) { + if (m_has_subblocks && 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++) @@ -808,7 +834,7 @@ void dxt_hc::determine_alpha_endpoint_codebook_task(uint64 data, void*) { uint8 s_best = 0; for (uint8 t = 0; t < 8; t++) { 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]; + int delta = m_blocks[m_has_subblocks ? b >> 1 : b][p][component_index] - alpha_values[s]; uint error = delta >= 0 ? delta : -delta; if (error < error_best) { s_best = s; @@ -946,7 +972,7 @@ 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; - if (!(m_has_etc_color_blocks && b & 1)) + if (!(m_has_subblocks && b & 1)) m_alpha_clusters[cluster_index].blocks[cAlpha0 + a].push_back(b); } } @@ -968,12 +994,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_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 n = m_has_subblocks ? 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_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) : + E2[p][s] = m_has_subblocks ? 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++) { @@ -999,7 +1025,7 @@ 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_has_etc_color_blocks ? b << 1 : b].color = best_index; + m_selector_indices[m_has_subblocks ? b << 1 : b].color = best_index; } } @@ -1012,9 +1038,9 @@ struct SelectorNode { void dxt_hc::create_color_selector_codebook() { uint num_tasks = m_pTask_pool->get_num_threads() + 1; - crnlib::vector selectors(m_has_etc_color_blocks ? m_num_blocks >> 1 : m_num_blocks); - for (uint i = 0, b = 0, step = m_has_etc_color_blocks ? 2 : 1; b < m_num_blocks; b += step) - selectors[i++] = m_block_selectors[cColor][b] + (m_has_etc_color_blocks ? m_block_selectors[cColor][b + 1] : 0); + crnlib::vector selectors(m_has_subblocks ? m_num_blocks >> 1 : m_num_blocks); + for (uint i = 0, b = 0, step = m_has_subblocks ? 2 : 1; b < m_num_blocks; b += step) + selectors[i++] = m_block_selectors[cColor][b] + (m_has_subblocks ? m_block_selectors[cColor][b + 1] : 0); crnlib::vector nodes; SelectorNode node(0, selectors.get_ptr()); @@ -1115,10 +1141,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 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 n = m_has_subblocks ? 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[m_has_etc_color_blocks ? b << 1 : b].component[c]]; + alpha_cluster& cluster = m_alpha_clusters[m_endpoint_indices[m_has_subblocks ? 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++) { @@ -1161,16 +1187,16 @@ 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[m_has_etc_color_blocks ? b << 1 : b].component[c] = best_index; + m_selector_indices[m_has_subblocks ? b << 1 : b].component[c] = best_index; } } } void dxt_hc::create_alpha_selector_codebook() { uint num_tasks = m_pTask_pool->get_num_threads() + 1; - crnlib::vector selectors(m_num_alpha_blocks * (m_has_etc_color_blocks ? m_num_blocks >> 1 : m_num_blocks)); + crnlib::vector selectors(m_num_alpha_blocks * (m_has_subblocks ? m_num_blocks >> 1 : m_num_blocks)); for (uint i = 0, c = cAlpha0; c < cAlpha0 + m_num_alpha_blocks; c++) { - for (uint b = 0, step = m_has_etc_color_blocks ? 2 : 1; b < m_num_blocks; b += step) + for (uint b = 0, step = m_has_subblocks ? 2 : 1; b < m_num_blocks; b += step) selectors[i++] = m_block_selectors[c][b]; } diff --git a/crnlib/crn_dxt_hc.h b/crnlib/crn_dxt_hc.h index 1a0ed28..bb79505 100644 --- a/crnlib/crn_dxt_hc.h +++ b/crnlib/crn_dxt_hc.h @@ -149,6 +149,7 @@ class dxt_hc { uint m_num_alpha_blocks; bool m_has_color_blocks; bool m_has_etc_color_blocks; + bool m_has_subblocks; enum { cColor = 0, diff --git a/crnlib/crn_dxt_image.cpp b/crnlib/crn_dxt_image.cpp index 8abe3aa..bbb7aee 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) || (fmt == cETC2)) + if ((fmt == cDXT1) || (fmt == cDXT1A) || (fmt == cDXT5A) || (fmt == cETC1) || (fmt == cETC2) || (fmt == cETC1S)) m_num_elements_per_block = 1; m_total_blocks = m_blocks_x * m_blocks_y; @@ -151,7 +151,8 @@ bool dxt_image::init_internal(dxt_format fmt, uint width, uint height) { m_element_component_index[1] = 0; break; } - case cETC1: { + case cETC1: + case cETC1S: { m_element_type[0] = cColorETC1; m_element_component_index[0] = -1; break; @@ -161,7 +162,8 @@ bool dxt_image::init_internal(dxt_format fmt, uint width, uint height) { m_element_component_index[0] = -1; break; } - case cETC2A: { + case cETC2A: + case cETC2AS: { m_element_type[0] = cAlphaETC2; m_element_type[1] = cColorETC2; m_element_component_index[0] = 3; @@ -491,6 +493,7 @@ bool dxt_image::has_alpha() const { case cDXT5: case cDXT5A: case cETC2A: + case cETC2AS: return true; default: break; @@ -1498,7 +1501,7 @@ void dxt_image::flip_row(uint y) { } bool dxt_image::can_flip(uint axis_index) { - if (m_format == cETC1 || m_format == cETC2 || m_format == cETC2A) { + if (m_format == cETC1 || m_format == cETC2 || m_format == cETC2A || m_format == cETC1S || m_format == cETC2AS) { // Can't reliably flip ETCn textures (because of asymmetry in the 555/333 differential coding of subblock colors). return false; } @@ -1518,7 +1521,7 @@ bool dxt_image::can_flip(uint axis_index) { } bool dxt_image::flip_x() { - if (m_format == cETC1 || m_format == cETC2 || m_format == cETC2A) { + if (m_format == cETC1 || m_format == cETC2 || m_format == cETC2A || m_format == cETC1S || m_format == cETC2AS) { // Can't reliably flip ETCn textures (because of asymmetry in the 555/333 differential coding of subblock colors). return false; } @@ -1562,7 +1565,7 @@ bool dxt_image::flip_x() { } bool dxt_image::flip_y() { - if (m_format == cETC1 || m_format == cETC2 || m_format == cETC2A) { + if (m_format == cETC1 || m_format == cETC2 || m_format == cETC2A || m_format == cETC1S || m_format == cETC2AS) { // 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 b773e7c..fa815b7 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) || (m_format == cETC2) || (m_format == cETC2A); } + 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) || (m_format == cETC1S) || (m_format == cETC2AS); } // Will be pretty slow if the image is DXT1, as this method scans for alpha blocks/selectors. bool has_alpha() const; diff --git a/crnlib/crn_mipmapped_texture.cpp b/crnlib/crn_mipmapped_texture.cpp index d7e0770..7214daf 100644 --- a/crnlib/crn_mipmapped_texture.cpp +++ b/crnlib/crn_mipmapped_texture.cpp @@ -603,6 +603,16 @@ bool mipmapped_texture::read_dds_internal(data_stream_serializer& serializer) { dxt_fmt = cETC2A; break; } + case PIXEL_FMT_ETC1S: { + m_format = PIXEL_FMT_ETC1S; + dxt_fmt = cETC1S; + break; + } + case PIXEL_FMT_ETC2AS: { + m_format = PIXEL_FMT_ETC2AS; + dxt_fmt = cETC2AS; + break; + } default: { dynamic_string err_msg(cVarArg, "Unsupported DDS FOURCC format: 0x%08X", desc.ddpfPixelFormat.dwFourCC); set_last_error(err_msg.get_ptr()); @@ -944,6 +954,16 @@ bool mipmapped_texture::write_dds(data_stream_serializer& serializer) const { desc.ddpfPixelFormat.dwRGBBitCount = 0; break; } + case PIXEL_FMT_ETC1S: { + desc.ddpfPixelFormat.dwFourCC = (uint32)PIXEL_FMT_ETC1S; + desc.ddpfPixelFormat.dwRGBBitCount = 0; + break; + } + case PIXEL_FMT_ETC2AS: { + desc.ddpfPixelFormat.dwFourCC = (uint32)PIXEL_FMT_ETC2AS; + desc.ddpfPixelFormat.dwRGBBitCount = 0; + break; + } case PIXEL_FMT_DXN: { desc.ddpfPixelFormat.dwFourCC = (uint32)PIXEL_FMT_3DC; desc.ddpfPixelFormat.dwRGBBitCount = PIXEL_FMT_DXN; @@ -1575,7 +1595,8 @@ bool mipmapped_texture::write_ktx(data_stream_serializer& serializer) const { ogl_internal_fmt = KTX_COMPRESSED_LUMINANCE_LATC1_EXT; break; } - case PIXEL_FMT_ETC1: { + case PIXEL_FMT_ETC1: + case PIXEL_FMT_ETC1S: { ogl_internal_fmt = KTX_ETC1_RGB8_OES; break; } @@ -1583,7 +1604,8 @@ bool mipmapped_texture::write_ktx(data_stream_serializer& serializer) const { ogl_internal_fmt = KTX_COMPRESSED_RGB8_ETC2; break; } - case PIXEL_FMT_ETC2A: { + case PIXEL_FMT_ETC2A: + case PIXEL_FMT_ETC2AS: { ogl_internal_fmt = KTX_COMPRESSED_RGBA8_ETC2_EAC; break; } @@ -1870,7 +1892,7 @@ 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) || (fmt == PIXEL_FMT_ETC2) || (fmt == PIXEL_FMT_ETC2A)) { + if ((!pixel_format_helpers::is_dxt(fmt)) || (fmt == PIXEL_FMT_DXT3) || (fmt == PIXEL_FMT_ETC1) || (fmt == PIXEL_FMT_ETC2) || (fmt == PIXEL_FMT_ETC2A) || (fmt == PIXEL_FMT_ETC1S) || (fmt == PIXEL_FMT_ETC2AS)) { // QDXT doesn't support DXT3 or ETCn yet. return convert(fmt, cook, p); } @@ -2323,7 +2345,9 @@ bool mipmapped_texture::qdxt_pack_init(qdxt_state& state, mipmapped_texture& dst } case PIXEL_FMT_ETC1: case PIXEL_FMT_ETC2: - case PIXEL_FMT_ETC2A: { + case PIXEL_FMT_ETC2A: + case PIXEL_FMT_ETC1S: + case PIXEL_FMT_ETC2AS: { console::warning("mipmapped_texture::qdxt_pack_init: This method does not support ETCn"); return false; } diff --git a/crnlib/crn_pixel_format.cpp b/crnlib/crn_pixel_format.cpp index ae414c0..094ec5f 100644 --- a/crnlib/crn_pixel_format.cpp +++ b/crnlib/crn_pixel_format.cpp @@ -24,6 +24,8 @@ const pixel_format g_all_pixel_formats[] = PIXEL_FMT_ETC1, PIXEL_FMT_ETC2, PIXEL_FMT_ETC2A, + PIXEL_FMT_ETC1S, + PIXEL_FMT_ETC2AS, PIXEL_FMT_R8G8B8, PIXEL_FMT_L8, PIXEL_FMT_A8, @@ -75,6 +77,10 @@ const char* get_pixel_format_string(pixel_format fmt) { return "ETC2"; case PIXEL_FMT_ETC2A: return "ETC2A"; + case PIXEL_FMT_ETC1S: + return "ETC1S"; + case PIXEL_FMT_ETC2AS: + return "ETC2AS"; case PIXEL_FMT_R8G8B8: return "R8G8B8"; case PIXEL_FMT_A8R8G8B8: @@ -120,6 +126,10 @@ const char* get_crn_format_string(crn_format fmt) { return "ETC2"; case cCRNFmtETC2A: return "ETC2A"; + case cCRNFmtETC1S: + return "ETC1S"; + case cCRNFmtETC2AS: + return "ETC2AS"; default: break; } @@ -134,7 +144,8 @@ component_flags get_component_flags(pixel_format fmt) { switch (fmt) { case PIXEL_FMT_DXT1: case PIXEL_FMT_ETC1: - case PIXEL_FMT_ETC2: { + case PIXEL_FMT_ETC2: + case PIXEL_FMT_ETC1S: { flags = cCompFlagRValid | cCompFlagGValid | cCompFlagBValid; break; } @@ -149,7 +160,8 @@ component_flags get_component_flags(pixel_format fmt) { } case PIXEL_FMT_DXT4: case PIXEL_FMT_DXT5: - case PIXEL_FMT_ETC2A: { + case PIXEL_FMT_ETC2A: + case PIXEL_FMT_ETC2AS: { flags = cCompFlagRValid | cCompFlagGValid | cCompFlagBValid | cCompFlagAValid; break; } @@ -261,6 +273,12 @@ crn_format convert_pixel_format_to_best_crn_format(pixel_format crn_fmt) { case PIXEL_FMT_ETC2A: fmt = cCRNFmtETC2A; break; + case PIXEL_FMT_ETC1S: + fmt = cCRNFmtETC1S; + break; + case PIXEL_FMT_ETC2AS: + fmt = cCRNFmtETC2AS; + break; default: { CRNLIB_ASSERT(false); break; @@ -297,6 +315,10 @@ pixel_format convert_crn_format_to_pixel_format(crn_format fmt) { return PIXEL_FMT_ETC2; case cCRNFmtETC2A: return PIXEL_FMT_ETC2A; + case cCRNFmtETC1S: + return PIXEL_FMT_ETC1S; + case cCRNFmtETC2AS: + return PIXEL_FMT_ETC2AS; default: { CRNLIB_ASSERT(false); break; diff --git a/crnlib/crn_pixel_format.h b/crnlib/crn_pixel_format.h index 536cbe6..ea05e01 100644 --- a/crnlib/crn_pixel_format.h +++ b/crnlib/crn_pixel_format.h @@ -44,6 +44,7 @@ inline bool has_alpha(pixel_format fmt) { case PIXEL_FMT_A8L8: case PIXEL_FMT_DXT5_AGBR: case PIXEL_FMT_ETC2A: + case PIXEL_FMT_ETC2AS: return true; default: break; @@ -94,6 +95,8 @@ inline int is_dxt(pixel_format fmt) { case PIXEL_FMT_ETC1: case PIXEL_FMT_ETC2: case PIXEL_FMT_ETC2A: + case PIXEL_FMT_ETC1S: + case PIXEL_FMT_ETC2AS: return true; default: break; @@ -150,6 +153,10 @@ inline dxt_format get_dxt_format(pixel_format fmt) { return cETC2; case PIXEL_FMT_ETC2A: return cETC2A; + case PIXEL_FMT_ETC1S: + return cETC1S; + case PIXEL_FMT_ETC2AS: + return cETC2AS; default: break; } @@ -178,6 +185,10 @@ inline pixel_format from_dxt_format(dxt_format dxt_fmt) { return PIXEL_FMT_ETC2; case cETC2A: return PIXEL_FMT_ETC2A; + case cETC1S: + return PIXEL_FMT_ETC1S; + case cETC2AS: + return PIXEL_FMT_ETC2AS; default: break; } @@ -229,6 +240,10 @@ inline uint get_bpp(pixel_format fmt) { return 4; case PIXEL_FMT_ETC2A: return 8; + case PIXEL_FMT_ETC1S: + return 4; + case PIXEL_FMT_ETC2AS: + return 8; case PIXEL_FMT_DXT2: return 8; case PIXEL_FMT_DXT3: @@ -282,6 +297,10 @@ inline uint get_dxt_bytes_per_block(pixel_format fmt) { return 8; case PIXEL_FMT_ETC2A: return 16; + case PIXEL_FMT_ETC1S: + return 8; + case PIXEL_FMT_ETC2AS: + return 16; case PIXEL_FMT_DXT2: return 16; case PIXEL_FMT_DXT3: diff --git a/crunch/crunch.cpp b/crunch/crunch.cpp index 684d4c4..c1b20d1 100644 --- a/crunch/crunch.cpp +++ b/crunch/crunch.cpp @@ -482,7 +482,7 @@ class crunch { 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 || in_header.m_format == cCRNFmtETC2 || in_header.m_format == cCRNFmtETC2A)) + (in_header.m_format == cCRNFmtETC1 || in_header.m_format == cCRNFmtETC2 || in_header.m_format == cCRNFmtETC2A || in_header.m_format == cCRNFmtETC1S || in_header.m_format == cCRNFmtETC2AS)) 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 1f43e0c..5775745 100644 --- a/inc/crn_decomp.h +++ b/inc/crn_decomp.h @@ -2116,6 +2116,10 @@ uint32 crnd_crn_format_to_fourcc(crn_format fmt) { return CRND_FOURCC('E', 'T', 'C', '2'); case cCRNFmtETC2A: return CRND_FOURCC('E', 'T', '2', 'A'); + case cCRNFmtETC1S: + return CRND_FOURCC('E', 'T', '1', 'S'); + case cCRNFmtETC2AS: + return CRND_FOURCC('E', '2', 'A', 'S'); default: break; } @@ -2142,6 +2146,7 @@ uint32 crnd_get_crn_format_bits_per_texel(crn_format fmt) { case cCRNFmtDXT5A: case cCRNFmtETC1: case cCRNFmtETC2: + case cCRNFmtETC1S: return 4; case cCRNFmtDXT3: case cCRNFmtDXT5: @@ -2152,6 +2157,7 @@ uint32 crnd_get_crn_format_bits_per_texel(crn_format fmt) { case cCRNFmtDXT5_xGBR: case cCRNFmtDXT5_AGBR: case cCRNFmtETC2A: + case cCRNFmtETC2AS: return 8; default: break; @@ -2259,7 +2265,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 || pHeader->m_format == cCRNFmtETC2 ? 8 : 16; + pInfo->m_bytes_per_block = pHeader->m_format == cCRNFmtDXT1 || pHeader->m_format == cCRNFmtDXT5A || pHeader->m_format == cCRNFmtETC1 || pHeader->m_format == cCRNFmtETC2 || pHeader->m_format == cCRNFmtETC1S ? 8 : 16; pInfo->m_userdata0 = pHeader->m_userdata0; pInfo->m_userdata1 = pHeader->m_userdata1; @@ -2993,7 +2999,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 || m_pHeader->m_format == cCRNFmtETC2 ? 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 || m_pHeader->m_format == cCRNFmtETC1S ? 8 : 16; uint32 minimal_row_pitch = block_size * blocks_x; if (!row_pitch_in_bytes) @@ -3009,6 +3015,7 @@ class crn_unpacker { bool status = false; switch (m_pHeader->m_format) { case cCRNFmtDXT1: + case cCRNFmtETC1S: status = unpack_dxt1((uint8**)pDst, row_pitch_in_bytes, blocks_x, blocks_y); break; case cCRNFmtDXT5: @@ -3016,6 +3023,7 @@ class crn_unpacker { case cCRNFmtDXT5_xGBR: case cCRNFmtDXT5_AGBR: case cCRNFmtDXT5_xGxR: + case cCRNFmtETC2AS: status = unpack_dxt5((uint8**)pDst, row_pitch_in_bytes, blocks_x, blocks_y); break; case cCRNFmtDXT5A: @@ -3115,7 +3123,7 @@ class crn_unpacker { if (m_pHeader->m_alpha_endpoints.m_num) { if (!decode_alpha_endpoints()) return false; - if (!(m_pHeader->m_format == cCRNFmtETC2A ? decode_alpha_selectors_etc() : decode_alpha_selectors())) + if (!(m_pHeader->m_format == cCRNFmtETC2AS ? decode_alpha_selectors_etcs() : m_pHeader->m_format == cCRNFmtETC2A ? decode_alpha_selectors_etc() : decode_alpha_selectors())) return false; } @@ -3124,7 +3132,8 @@ 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; + const bool has_etc_color_blocks = m_pHeader->m_format == cCRNFmtETC1 || m_pHeader->m_format == cCRNFmtETC2 || m_pHeader->m_format == cCRNFmtETC2A || m_pHeader->m_format == cCRNFmtETC1S || m_pHeader->m_format == cCRNFmtETC2AS; + const bool has_subblocks = 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; @@ -3146,7 +3155,8 @@ class crn_unpacker { if (has_etc_color_blocks) { for (b = 0; b < 32; b += 8) a += m_codec.decode(dm[0]) << b; - *pDst++ = a &= 0x1F1F1F1F; + a &= 0x1F1F1F1F; + *pDst++ = has_subblocks ? a : (a & 0x07000000) << 5 | (a & 0x07000000) << 2 | 0x02000000 | (a & 0x001F1F1F) << 3; } else { a = (a + m_codec.decode(dm[0])) & 31; b = (b + m_codec.decode(dm[1])) & 63; @@ -3164,21 +3174,24 @@ 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; + const bool has_etc_color_blocks = m_pHeader->m_format == cCRNFmtETC1 || m_pHeader->m_format == cCRNFmtETC2 || m_pHeader->m_format == cCRNFmtETC2A || m_pHeader->m_format == cCRNFmtETC1S || m_pHeader->m_format == cCRNFmtETC2AS; + const bool has_subblocks = 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 << (has_etc_color_blocks ? 1 : 0)); + m_color_selectors.resize(m_pHeader->m_color_selectors.m_num << (has_subblocks ? 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 (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); - m_color_selectors[i << 1] |= ((s0 >> 1 & 1) | (s0 & 1) << 16) << (t & 15); + if (has_subblocks) { + uint32 s0 = selector >> (w << 3 | h << 1); + m_color_selectors[i << 1] |= ((s0 >> 1 & 1) | (s0 & 1) << 16) << (t & 15); + } uint32 s1 = selector >> (h << 3 | w << 1); - m_color_selectors[i << 1 | 1] |= ((s1 >> 1 & 1) | (s1 & 1) << 16) << (t & 15); + m_color_selectors[has_subblocks ? i << 1 | 1 : i] |= ((s1 >> 1 & 1) | (s1 & 1) << 16) << (t & 15); } } } else { @@ -3268,7 +3281,32 @@ class crn_unpacker { m_codec.stop_decoding(); return true; } - + + bool decode_alpha_selectors_etcs() { + 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 * 3); + uint8 s_linear[8] = {}; + uint8* data = (uint8*)m_alpha_selectors.begin(); + for (uint i = 0; i < (m_alpha_selectors.size() << 1); i += 6) { + for (uint s_group = 0, 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) + 9 * ((p & 3) - (p >> 2)); + uint8 byte_offset = d >> 3; + uint8 bit_offset = d & 7; + data[i + byte_offset] |= s << (8 - bit_offset); + if (bit_offset < 3) + data[i + byte_offset - 1] |= 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; diff --git a/inc/crnlib.h b/inc/crnlib.h index fb93b63..df5dbd9 100644 --- a/inc/crnlib.h +++ b/inc/crnlib.h @@ -73,6 +73,8 @@ enum crn_format { cCRNFmtETC1, cCRNFmtETC2, cCRNFmtETC2A, + cCRNFmtETC1S, + cCRNFmtETC2AS, cCRNFmtTotal, diff --git a/inc/dds_defs.h b/inc/dds_defs.h index 1216ff7..0f872a8 100644 --- a/inc/dds_defs.h +++ b/inc/dds_defs.h @@ -30,6 +30,8 @@ enum pixel_format { 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_ETC1S = CRNLIB_PIXEL_FMT_FOURCC('E', 'T', '1', 'S'), + PIXEL_FMT_ETC2AS = CRNLIB_PIXEL_FMT_FOURCC('E', '2', 'A', 'S'), PIXEL_FMT_R8G8B8 = CRNLIB_PIXEL_FMT_FOURCC('R', 'G', 'B', 'x'), PIXEL_FMT_L8 = CRNLIB_PIXEL_FMT_FOURCC('L', 'x', 'x', 'x'),