@@ -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;
}
@@ -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;
@@ -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,15 +1595,17 @@ 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;
}
case PIXEL_FMT_ETC2: {
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;
}
@@ -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;
@@ -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:
@@ -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
@@ -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<crn_format>((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,13 +3015,15 @@ 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:
case cCRNFmtDXT5_CCxY:
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;
@@ -73,6 +73,8 @@ enum crn_format {
cCRNFmtETC1,
cCRNFmtETC2,
cCRNFmtETC2A,
cCRNFmtETC1S,
cCRNFmtETC2AS,

cCRNFmtTotal,

@@ -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'),