Switch from the chunk encoding concept to the reference encoding concept
This change improves the compression ratio. Explanation: In the original version of Crunch all the blocks are grouped into chunks of 2x2 blocks. Each chunk can have one of 8 different types. The type of the chunk determines which blocks inside the chunk share the same endpoints (for example, all the blocks inside the chunk share the same endpoints, or blocks in the right column share the same endpoints, or all the blocks have different endpoints, etc.). Encoding of endpoints equality is usually cheaper than encoding of duplicate endpoint indices. The used 8 chunk types do not cover all the possibilities, but they can be efficiently encoded using 0.75 bits per block (uncompressed). The modified algorithm no longer uses the concept of chunks in the output file format and is based on an alternative approach. Endpoints for each block can be either copied from the left nearest block (reference to the left), copied from the upper nearest block (reference to the top), or decoded from the stream (reference to itself). Note that this is a superset of the original encoding, so all the images previously encoded with the original algorithm can be losslessly transcoded into the new format, but not vice versa. Even though the new endpoint equality encoding is more expensive (about 1.58 bits per block, uncompressed), it provides more flexibility for endpoint matching inside the former "chunks", and more importantly, it allows to inherit endpoints from outside the former "chunks" (which is not possible when using the original chunk encoding). The blocks are no longer grouped together and are encoded in the same order as they appear on the image. Note: This modification alters the output file format and makes it incompatible with the previous revisions. Testing: The modified algorithm has been tested on the Kodak test set using 64-bit build with default settings (running on Windows 10, i7-4790, 3.6GHz). All the decompressed test images are identical to the images being compressed and decompressed using original version of Crunch. [Compressing Kodak set without mipmaps] Original: 1582222 bytes / 28.903 sec Modified: 1548791 bytes / 28.818 sec Improvement: 2.11% (compression ratio) / 0.29% (compression time) [Compressing Kodak set with mipmaps] Original: 2065243 bytes / 36.978 sec Modified: 2017245 bytes / 36.846 sec Improvement: 2.32% (compression ratio) / 0.36% (compression time)
This commit is contained in:
Binary file not shown.
+103
-173
@@ -635,15 +635,19 @@ bool crn_comp::pack_selectors(
|
||||
}
|
||||
|
||||
bool crn_comp::pack_chunks(
|
||||
uint first_chunk, uint num_chunks,
|
||||
uint group,
|
||||
bool clear_histograms,
|
||||
symbol_codec* pCodec,
|
||||
const crnlib::vector<uint>* pColor_endpoint_remap,
|
||||
const crnlib::vector<uint>* pColor_selector_remap,
|
||||
const crnlib::vector<uint>* pAlpha_endpoint_remap,
|
||||
const crnlib::vector<uint>* pAlpha_selector_remap) {
|
||||
uint first_chunk = m_mip_groups[group].m_first_chunk;
|
||||
uint num_chunks = m_mip_groups[group].m_num_chunks;
|
||||
uint chunk_width = m_mip_groups[group].m_chunk_width;
|
||||
|
||||
if (!pCodec) {
|
||||
m_chunk_encoding_hist.resize(1 << (3 * cEncodingMapNumChunksPerCode));
|
||||
m_chunk_encoding_hist.resize(256);
|
||||
if (clear_histograms)
|
||||
m_chunk_encoding_hist.set_all(0);
|
||||
|
||||
@@ -676,137 +680,60 @@ bool crn_comp::pack_chunks(
|
||||
}
|
||||
}
|
||||
|
||||
uint endpoint_index[cNumComps][2][2] = {};
|
||||
uint selector_index[cNumComps][2][2] = {};
|
||||
|
||||
uint num_encodings_left = 0;
|
||||
|
||||
for (uint chunk_index = first_chunk; chunk_index < (first_chunk + num_chunks); chunk_index++) {
|
||||
if (!num_encodings_left) {
|
||||
uint index = 0;
|
||||
for (uint i = 0; i < cEncodingMapNumChunksPerCode; i++)
|
||||
if ((chunk_index + i) < (first_chunk + num_chunks))
|
||||
index |= (m_hvq.get_chunk_encoding(chunk_index + i).m_encoding_index << (i * 3));
|
||||
|
||||
if (pCodec)
|
||||
pCodec->encode(index, m_chunk_encoding_dm);
|
||||
else
|
||||
m_chunk_encoding_hist.inc_freq(index);
|
||||
|
||||
num_encodings_left = cEncodingMapNumChunksPerCode;
|
||||
uint selector_index[cNumComps] = {};
|
||||
uint endpoint_index[cNumComps] = {};
|
||||
const crnlib::vector<uint>* endpoint_remap[cNumComps] = {};
|
||||
const crnlib::vector<uint>* selector_remap[cNumComps] = {};
|
||||
for (uint c = 0; c < cNumComps; c++) {
|
||||
if (m_has_comp[c]) {
|
||||
endpoint_remap[c] = c ? pAlpha_endpoint_remap : pColor_endpoint_remap;
|
||||
selector_remap[c] = c ? pAlpha_selector_remap : pColor_selector_remap;
|
||||
}
|
||||
num_encodings_left--;
|
||||
}
|
||||
|
||||
const dxt_hc::chunk_encoding& encoding = m_hvq.get_chunk_encoding(chunk_index);
|
||||
const chunk_detail& details = m_chunk_details[chunk_index];
|
||||
|
||||
const uint comp_order[3] = {cAlpha0, cAlpha1, cColor};
|
||||
for (uint c = 0; c < 3; c++) {
|
||||
const uint comp_index = comp_order[c];
|
||||
if (!m_has_comp[comp_index])
|
||||
continue;
|
||||
|
||||
// endpoints
|
||||
if (comp_index == cColor) {
|
||||
if (pColor_endpoint_remap) {
|
||||
for (uint y = 0; y < 2; y++) {
|
||||
for (uint x = 0; x < 2; x++) {
|
||||
uint8 endpoint_reference = details.m_endpoint_references[comp_index][y][x];
|
||||
if (!endpoint_reference) {
|
||||
endpoint_index[comp_index][y][x] = (*pColor_endpoint_remap)[details.m_endpoint_indices[comp_index][y][x]];
|
||||
int endpoint_delta = endpoint_index[comp_index][y][x] - endpoint_index[comp_index][y][x ^ 1];
|
||||
|
||||
int sym = endpoint_delta;
|
||||
for (uint chunk_base = first_chunk; chunk_base < first_chunk + num_chunks; chunk_base += chunk_width) {
|
||||
for (uint by = 0; by < 2; by++) {
|
||||
for (uint cx = 0; cx < chunk_width; cx++) {
|
||||
const chunk_detail& details = m_chunk_details[chunk_base + cx];
|
||||
if (!by) {
|
||||
if (pCodec)
|
||||
pCodec->encode(details.m_reference_group, m_reference_encoding_dm);
|
||||
else
|
||||
m_chunk_encoding_hist.inc_freq(details.m_reference_group);
|
||||
}
|
||||
for (uint bx = 0; bx < 2; bx++) {
|
||||
for (uint c = 0; c < cNumComps; c++) {
|
||||
if (endpoint_remap[c]) {
|
||||
uint index = (*endpoint_remap[c])[details.m_endpoint_indices[by][bx][c]];
|
||||
if (!details.m_endpoint_references[by][bx]) {
|
||||
int sym = index - endpoint_index[c];
|
||||
if (sym < 0)
|
||||
sym += pColor_endpoint_remap->size();
|
||||
|
||||
CRNLIB_ASSERT(sym >= 0 && sym < (int)pColor_endpoint_remap->size());
|
||||
|
||||
sym += endpoint_remap[c]->size();
|
||||
if (!pCodec)
|
||||
m_endpoint_index_hist[0].inc_freq(sym);
|
||||
m_endpoint_index_hist[c ? 1 : 0].inc_freq(sym);
|
||||
else
|
||||
pCodec->encode(sym, m_endpoint_index_dm[0]);
|
||||
} else {
|
||||
endpoint_index[comp_index][y][x] = endpoint_reference == 1 ? endpoint_index[comp_index][y][x ^ 1] : endpoint_index[comp_index][y ^ 1][x];
|
||||
pCodec->encode(sym, m_endpoint_index_dm[c ? 1 : 0]);
|
||||
}
|
||||
endpoint_index[c] = index;
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (pAlpha_endpoint_remap) {
|
||||
for (uint y = 0; y < 2; y++) {
|
||||
for (uint x = 0; x < 2; x++) {
|
||||
uint8 endpoint_reference = details.m_endpoint_references[comp_index][y][x];
|
||||
if (!endpoint_reference) {
|
||||
endpoint_index[comp_index][y][x] = (*pAlpha_endpoint_remap)[details.m_endpoint_indices[comp_index][y][x]];
|
||||
int endpoint_delta = endpoint_index[comp_index][y][x] - endpoint_index[comp_index][y][x ^ 1];
|
||||
|
||||
int sym = endpoint_delta;
|
||||
if (sym < 0)
|
||||
sym += pAlpha_endpoint_remap->size();
|
||||
|
||||
CRNLIB_ASSERT(sym >= 0 && sym < (int)pAlpha_endpoint_remap->size());
|
||||
|
||||
if (!pCodec)
|
||||
m_endpoint_index_hist[1].inc_freq(sym);
|
||||
else
|
||||
pCodec->encode(sym, m_endpoint_index_dm[1]);
|
||||
} else {
|
||||
endpoint_index[comp_index][y][x] = endpoint_reference == 1 ? endpoint_index[comp_index][y][x ^ 1] : endpoint_index[comp_index][y ^ 1][x];
|
||||
}
|
||||
for (uint c = 0; c < cNumComps; c++) {
|
||||
if (selector_remap[c]) {
|
||||
uint index = (*selector_remap[c])[details.m_selector_indices[by][bx][c]];
|
||||
int sym = index - selector_index[c];
|
||||
if (sym < 0)
|
||||
sym += selector_remap[c]->size();
|
||||
if (!pCodec)
|
||||
m_selector_index_hist[c ? 1 : 0].inc_freq(sym);
|
||||
else
|
||||
pCodec->encode(sym, m_selector_index_dm[c ? 1 : 0]);
|
||||
selector_index[c] = index;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} // c
|
||||
|
||||
// selectors
|
||||
for (uint y = 0; y < 2; y++) {
|
||||
for (uint x = 0; x < 2; x++) {
|
||||
for (uint c = 0; c < 3; c++) {
|
||||
const uint comp_index = comp_order[c];
|
||||
if (!m_has_comp[comp_index])
|
||||
continue;
|
||||
|
||||
if (comp_index == cColor) {
|
||||
if (pColor_selector_remap) {
|
||||
selector_index[comp_index][y][x] = (*pColor_selector_remap)[details.m_selector_indices[comp_index][y][x]];
|
||||
int selector_delta = selector_index[comp_index][y][x] - selector_index[comp_index][y][x ^ 1];
|
||||
|
||||
int sym = selector_delta;
|
||||
if (sym < 0)
|
||||
sym += pColor_selector_remap->size();
|
||||
|
||||
CRNLIB_ASSERT(sym >= 0 && sym < (int)pColor_selector_remap->size());
|
||||
|
||||
if (!pCodec)
|
||||
m_selector_index_hist[cColor].inc_freq(sym);
|
||||
else
|
||||
pCodec->encode(sym, m_selector_index_dm[cColor]);
|
||||
}
|
||||
} else if (pAlpha_selector_remap) {
|
||||
selector_index[comp_index][y][x] = (*pAlpha_selector_remap)[details.m_selector_indices[comp_index][y][x]];
|
||||
int selector_delta = selector_index[comp_index][y][x] - selector_index[comp_index][y][x ^ 1];
|
||||
|
||||
int sym = selector_delta;
|
||||
if (sym < 0)
|
||||
sym += pAlpha_selector_remap->size();
|
||||
|
||||
CRNLIB_ASSERT(sym >= 0 && sym < (int)pAlpha_selector_remap->size());
|
||||
|
||||
if (!pCodec)
|
||||
m_selector_index_hist[1].inc_freq(sym);
|
||||
else
|
||||
pCodec->encode(sym, m_selector_index_dm[1]);
|
||||
}
|
||||
|
||||
} // c
|
||||
|
||||
} // x
|
||||
} // y
|
||||
|
||||
} // chunk_index
|
||||
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -817,14 +744,16 @@ bool crn_comp::pack_chunks_simulation(
|
||||
const crnlib::vector<uint>* pColor_selector_remap,
|
||||
const crnlib::vector<uint>* pAlpha_endpoint_remap,
|
||||
const crnlib::vector<uint>* pAlpha_selector_remap) {
|
||||
if (!pack_chunks(first_chunk, num_chunks, true, NULL, pColor_endpoint_remap, pColor_selector_remap, pAlpha_endpoint_remap, pAlpha_selector_remap))
|
||||
return false;
|
||||
for (uint group = 0; group < m_mip_groups.size(); group++) {
|
||||
if (!pack_chunks(group, !group, NULL, pColor_endpoint_remap, pColor_selector_remap, pAlpha_endpoint_remap, pAlpha_selector_remap))
|
||||
return false;
|
||||
}
|
||||
|
||||
symbol_codec codec;
|
||||
codec.start_encoding(2 * 1024 * 1024);
|
||||
codec.encode_enable_simulation(true);
|
||||
|
||||
m_chunk_encoding_dm.init(true, m_chunk_encoding_hist, 16);
|
||||
m_reference_encoding_dm.init(true, m_chunk_encoding_hist, 16);
|
||||
|
||||
for (uint i = 0; i < 2; i++) {
|
||||
if (m_endpoint_index_hist[i].size()) {
|
||||
@@ -840,8 +769,10 @@ bool crn_comp::pack_chunks_simulation(
|
||||
}
|
||||
}
|
||||
|
||||
if (!pack_chunks(first_chunk, num_chunks, false, &codec, pColor_endpoint_remap, pColor_selector_remap, pAlpha_endpoint_remap, pAlpha_selector_remap))
|
||||
return false;
|
||||
for (uint group = 0; group < m_mip_groups.size(); group++) {
|
||||
if (!pack_chunks(group, false, &codec, pColor_endpoint_remap, pColor_selector_remap, pAlpha_endpoint_remap, pAlpha_selector_remap))
|
||||
return false;
|
||||
}
|
||||
|
||||
codec.stop_encoding(false);
|
||||
|
||||
@@ -941,6 +872,7 @@ bool crn_comp::alias_images() {
|
||||
mip_group_chunk_index = 0;
|
||||
|
||||
m_mip_groups[mip_group].m_num_chunks += num_chunks;
|
||||
m_mip_groups[mip_group].m_chunk_width = chunk_width;
|
||||
|
||||
m_levels[level_index].m_width = width;
|
||||
m_levels[level_index].m_height = height;
|
||||
@@ -1031,7 +963,7 @@ void crn_comp::clear() {
|
||||
m_hvq.clear();
|
||||
|
||||
m_chunk_encoding_hist.clear();
|
||||
m_chunk_encoding_dm.clear();
|
||||
m_reference_encoding_dm.clear();
|
||||
for (uint i = 0; i < 2; i++) {
|
||||
m_endpoint_index_hist[i].clear();
|
||||
m_endpoint_index_dm[i].clear();
|
||||
@@ -1209,30 +1141,34 @@ void crn_comp::create_chunk_indices() {
|
||||
{ 0, 1, 2, 3 },
|
||||
};
|
||||
|
||||
uint8 endpoint_references[8][4] = {
|
||||
{ 0, 1, 2, 1 },
|
||||
{ 0, 1, 0, 1 },
|
||||
{ 0, 0, 2, 2 },
|
||||
{ 0, 1, 0, 0 },
|
||||
{ 0, 0, 0, 1 },
|
||||
{ 0, 0, 2, 0 },
|
||||
{ 0, 0, 0, 2 },
|
||||
{ 0, 0, 0, 0 },
|
||||
};
|
||||
|
||||
m_chunk_details.resize(m_total_chunks);
|
||||
|
||||
for (uint chunk_index = 0; chunk_index < m_total_chunks; chunk_index++) {
|
||||
const dxt_hc::chunk_encoding& chunk_encoding = m_hvq.get_chunk_encoding(chunk_index);
|
||||
|
||||
for (uint i = 0; i < cNumComps; i++) {
|
||||
if (!m_has_comp[i])
|
||||
continue;
|
||||
for (uint t = 0, y = 0; y < cChunkBlockHeight; y++) {
|
||||
for (uint x = 0; x < cChunkBlockWidth; x++, t++) {
|
||||
m_chunk_details[chunk_index].m_selector_indices[i][y][x] = chunk_encoding.m_selector_indices[i][y][x];
|
||||
m_chunk_details[chunk_index].m_endpoint_indices[i][y][x] = chunk_encoding.m_endpoint_indices[i][endpoint_index_map[chunk_encoding.m_encoding_index][t]];
|
||||
m_chunk_details[chunk_index].m_endpoint_references[i][y][x] = endpoint_references[chunk_encoding.m_encoding_index][t];
|
||||
for (uint group = 0; group < m_mip_groups.size(); group++) {
|
||||
uint chunk_width = m_mip_groups[group].m_chunk_width;
|
||||
for (uint i = 0; i < m_mip_groups[group].m_num_chunks;) {
|
||||
for (uint cx = 0; cx < chunk_width; cx++, i++) {
|
||||
uint chunk_index = m_mip_groups[group].m_first_chunk + i, left_chunk_index = 0, top_chunk_index = 0;
|
||||
const dxt_hc::chunk_encoding& chunk_encoding = m_hvq.get_chunk_encoding(chunk_index);
|
||||
for (uint t = 0, by = 0; by < 2; by++) {
|
||||
for (uint bx = 0; bx < 2; bx++, t++) {
|
||||
bool left_match = bx || cx;
|
||||
if (left_match)
|
||||
left_chunk_index = bx ? chunk_index : chunk_index - 1;
|
||||
bool top_match = by || i >= chunk_width;
|
||||
if (top_match)
|
||||
top_chunk_index = by ? chunk_index : chunk_index - chunk_width;
|
||||
for (uint c = 0; c < cNumComps; c++) {
|
||||
if (m_has_comp[c]) {
|
||||
m_chunk_details[chunk_index].m_endpoint_indices[by][bx][c] = chunk_encoding.m_endpoint_indices[c][endpoint_index_map[chunk_encoding.m_encoding_index][t]];
|
||||
left_match = left_match && m_chunk_details[chunk_index].m_endpoint_indices[by][bx][c] == m_chunk_details[left_chunk_index].m_endpoint_indices[by][bx ^ 1][c];
|
||||
top_match = top_match && m_chunk_details[chunk_index].m_endpoint_indices[by][bx][c] == m_chunk_details[top_chunk_index].m_endpoint_indices[by ^ 1][bx][c];
|
||||
m_chunk_details[chunk_index].m_selector_indices[by][bx][c] = chunk_encoding.m_selector_indices[c][by][bx];
|
||||
}
|
||||
}
|
||||
uint8 endpoint_reference = left_match ? 1 : top_match ? 2 : 0;
|
||||
m_chunk_details[chunk_index].m_endpoint_references[by][bx] = endpoint_reference;
|
||||
m_chunk_details[chunk_index].m_reference_group |= endpoint_reference << (bx << 2 | by << 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1297,13 +1233,10 @@ bool crn_comp::optimize_color_endpoint_codebook(crnlib::vector<uint>& remapping)
|
||||
const chunk_detail& details = m_chunk_details[chunk_index];
|
||||
for (uint y = 0; y < 2; y++) {
|
||||
for (uint x = 0; x < 2; x++) {
|
||||
uint8 endpoint_reference = details.m_endpoint_references[cColor][y][x];
|
||||
if (!endpoint_reference) {
|
||||
endpoint_index[y][x] = details.m_endpoint_indices[cColor][y][x];
|
||||
endpoint_index[y][x] = details.m_endpoint_indices[y][x][cColor];
|
||||
if (!details.m_endpoint_references[y][x]) {
|
||||
update_hist(xhist, endpoint_index[y][x], endpoint_index[y][x ^ 1], n);
|
||||
update_hist(xhist, endpoint_index[y][x ^ 1], endpoint_index[y][x], n);
|
||||
} else {
|
||||
endpoint_index[y][x] = endpoint_reference == 1 ? endpoint_index[y][x ^ 1] : endpoint_index[y ^ 1][x];
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1425,7 +1358,7 @@ bool crn_comp::optimize_color_selector_codebook(crnlib::vector<uint>& remapping)
|
||||
const chunk_detail& details = m_chunk_details[chunk_index];
|
||||
for (uint y = 0; y < 2; y++) {
|
||||
for (uint x = 0; x < 2; x++) {
|
||||
selector_index[y][x] = details.m_selector_indices[cColor][y][x];
|
||||
selector_index[y][x] = details.m_selector_indices[y][x][cColor];
|
||||
update_hist(xhist, selector_index[y][x], selector_index[y][x ^ 1], n);
|
||||
update_hist(xhist, selector_index[y][x ^ 1], selector_index[y][x], n);
|
||||
}
|
||||
@@ -1542,20 +1475,17 @@ bool crn_comp::optimize_alpha_endpoint_codebook(crnlib::vector<uint>& remapping)
|
||||
|
||||
uint n = m_hvq.get_alpha_endpoint_codebook_size();
|
||||
hist_type xhist(n * n);
|
||||
uint endpoint_index[cNumComps][2][2] = {};
|
||||
uint endpoint_index[2][2][cNumComps] = {};
|
||||
uint min_comp_index = m_has_comp[cAlpha0] ? cAlpha0 : cAlpha1, max_comp_index = m_has_comp[cAlpha1] ? cAlpha1 : cAlpha0;
|
||||
for (uint chunk_index = 0; chunk_index < m_chunks.size(); chunk_index++) {
|
||||
const chunk_detail& details = m_chunk_details[chunk_index];
|
||||
for (uint comp_index = min_comp_index; comp_index <= max_comp_index; comp_index++) {
|
||||
for (uint y = 0; y < 2; y++) {
|
||||
for (uint x = 0; x < 2; x++) {
|
||||
uint8 endpoint_reference = details.m_endpoint_references[comp_index][y][x];
|
||||
if (!endpoint_reference) {
|
||||
endpoint_index[comp_index][y][x] = details.m_endpoint_indices[comp_index][y][x];
|
||||
update_hist(xhist, endpoint_index[comp_index][y][x], endpoint_index[comp_index][y][x ^ 1], n);
|
||||
update_hist(xhist, endpoint_index[comp_index][y][x ^ 1], endpoint_index[comp_index][y][x], n);
|
||||
} else {
|
||||
endpoint_index[comp_index][y][x] = endpoint_reference == 1 ? endpoint_index[comp_index][y][x ^ 1] : endpoint_index[comp_index][y ^ 1][x];
|
||||
for (uint y = 0; y < 2; y++) {
|
||||
for (uint x = 0; x < 2; x++) {
|
||||
for (uint comp_index = min_comp_index; comp_index <= max_comp_index; comp_index++) {
|
||||
endpoint_index[y][x][comp_index] = details.m_endpoint_indices[y][x][comp_index];
|
||||
if (!details.m_endpoint_references[y][x]) {
|
||||
update_hist(xhist, endpoint_index[y][x][comp_index], endpoint_index[y][x ^ 1][comp_index], n);
|
||||
update_hist(xhist, endpoint_index[y][x ^ 1][comp_index], endpoint_index[y][x][comp_index], n);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1678,7 +1608,7 @@ bool crn_comp::optimize_alpha_selector_codebook(crnlib::vector<uint>& remapping)
|
||||
for (uint comp_index = min_comp_index; comp_index <= max_comp_index; comp_index++) {
|
||||
for (uint y = 0; y < 2; y++) {
|
||||
for (uint x = 0; x < 2; x++) {
|
||||
selector_index[comp_index][y][x] = details.m_selector_indices[comp_index][y][x];
|
||||
selector_index[comp_index][y][x] = details.m_selector_indices[y][x][comp_index];
|
||||
update_hist(xhist, selector_index[comp_index][y][x], selector_index[comp_index][y][x ^ 1], n);
|
||||
update_hist(xhist, selector_index[comp_index][y][x ^ 1], selector_index[comp_index][y][x], n);
|
||||
}
|
||||
@@ -1749,7 +1679,7 @@ bool crn_comp::pack_data_models() {
|
||||
symbol_codec codec;
|
||||
codec.start_encoding(1024 * 1024);
|
||||
|
||||
if (!codec.encode_transmit_static_huffman_data_model(m_chunk_encoding_dm, false))
|
||||
if (!codec.encode_transmit_static_huffman_data_model(m_reference_encoding_dm, false))
|
||||
return false;
|
||||
|
||||
for (uint i = 0; i < 2; i++) {
|
||||
@@ -1901,10 +1831,10 @@ bool crn_comp::compress_internal() {
|
||||
codec.start_encoding(2 * 1024 * 1024);
|
||||
|
||||
if (!pack_chunks(
|
||||
m_mip_groups[mip_group].m_first_chunk, m_mip_groups[mip_group].m_num_chunks,
|
||||
!pass && !mip_group, pass ? &codec : NULL,
|
||||
m_has_comp[cColor] ? &endpoint_remap[0] : NULL, m_has_comp[cColor] ? &selector_remap[0] : NULL,
|
||||
m_has_comp[cAlpha0] ? &endpoint_remap[1] : NULL, m_has_comp[cAlpha0] ? &selector_remap[1] : NULL)) {
|
||||
mip_group,
|
||||
!pass && !mip_group, pass ? &codec : NULL,
|
||||
m_has_comp[cColor] ? &endpoint_remap[0] : NULL, m_has_comp[cColor] ? &selector_remap[0] : NULL,
|
||||
m_has_comp[cAlpha0] ? &endpoint_remap[1] : NULL, m_has_comp[cAlpha0] ? &selector_remap[1] : NULL)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -1915,7 +1845,7 @@ bool crn_comp::compress_internal() {
|
||||
}
|
||||
|
||||
if (!pass) {
|
||||
m_chunk_encoding_dm.init(true, m_chunk_encoding_hist, 16);
|
||||
m_reference_encoding_dm.init(true, m_chunk_encoding_hist, 16);
|
||||
|
||||
for (uint i = 0; i < 2; i++) {
|
||||
if (m_endpoint_index_hist[i].size())
|
||||
|
||||
+7
-5
@@ -52,6 +52,7 @@ class crn_comp : public itexture_comp {
|
||||
|
||||
uint m_first_chunk;
|
||||
uint m_num_chunks;
|
||||
uint m_chunk_width;
|
||||
};
|
||||
crnlib::vector<mip_group> m_mip_groups;
|
||||
|
||||
@@ -66,9 +67,10 @@ class crn_comp : public itexture_comp {
|
||||
|
||||
struct chunk_detail {
|
||||
chunk_detail() { utils::zero_object(*this); }
|
||||
uint8 m_endpoint_references[cNumComps][2][2];
|
||||
uint16 m_endpoint_indices[cNumComps][2][2];
|
||||
uint16 m_selector_indices[cNumComps][2][2];
|
||||
uint16 m_endpoint_indices[2][2][cNumComps];
|
||||
uint16 m_selector_indices[2][2][cNumComps];
|
||||
uint8 m_endpoint_references[2][2];
|
||||
uint8 m_reference_group;
|
||||
};
|
||||
crnlib::vector<chunk_detail> m_chunk_details;
|
||||
|
||||
@@ -81,7 +83,7 @@ class crn_comp : public itexture_comp {
|
||||
dxt_hc m_hvq;
|
||||
|
||||
symbol_histogram m_chunk_encoding_hist;
|
||||
static_huffman_data_model m_chunk_encoding_dm;
|
||||
static_huffman_data_model m_reference_encoding_dm;
|
||||
|
||||
symbol_histogram m_endpoint_index_hist[2];
|
||||
static_huffman_data_model m_endpoint_index_dm[2]; // color, alpha
|
||||
@@ -126,7 +128,7 @@ class crn_comp : public itexture_comp {
|
||||
void create_chunk_indices();
|
||||
|
||||
bool pack_chunks(
|
||||
uint first_chunk, uint num_chunks,
|
||||
uint group,
|
||||
bool clear_histograms,
|
||||
symbol_codec* pCodec,
|
||||
const crnlib::vector<uint>* pColor_endpoint_remap,
|
||||
|
||||
+216
-438
@@ -1638,10 +1638,6 @@ class symbol_codec {
|
||||
|
||||
} // namespace crnd
|
||||
|
||||
#define CRND_HUFF_DECODE_BEGIN(x)
|
||||
#define CRND_HUFF_DECODE_END(x)
|
||||
#define CRND_HUFF_DECODE(codec, model, symbol) symbol = codec.decode(model);
|
||||
|
||||
namespace crnd {
|
||||
void crnd_assert(const char* pExp, const char* pFile, unsigned line) {
|
||||
char buf[512];
|
||||
@@ -2773,16 +2769,7 @@ uint32 symbol_codec::decode(const static_huffman_data_model& model) {
|
||||
}
|
||||
|
||||
uint64 symbol_codec::stop_decoding() {
|
||||
#if 0
|
||||
uint32 i = get_bits(4);
|
||||
uint32 k = get_bits(3);
|
||||
i, k;
|
||||
CRND_ASSERT((i == 15) && (k == 3));
|
||||
#endif
|
||||
|
||||
uint64 n = static_cast<uint64>(m_pDecode_buf_next - m_pDecode_buf);
|
||||
|
||||
return n;
|
||||
return static_cast<uint64>(m_pDecode_buf_next - m_pDecode_buf);
|
||||
}
|
||||
|
||||
} // namespace crnd
|
||||
@@ -3036,16 +3023,6 @@ uint32 dxt5_block::get_block_values(uint32* pDst, uint32 l, uint32 h) {
|
||||
// File: crnd_decode.cpp
|
||||
|
||||
namespace crnd {
|
||||
static uint8 g_crnd_endpoint_references[8][2][2] = {
|
||||
{{0, 1}, {2, 1}},
|
||||
{{0, 1}, {0, 1}},
|
||||
{{0, 0}, {2, 2}},
|
||||
{{0, 1}, {0, 0}},
|
||||
{{0, 0}, {0, 1}},
|
||||
{{0, 0}, {2, 0}},
|
||||
{{0, 0}, {0, 2}},
|
||||
{{0, 0}, {0, 0}},
|
||||
};
|
||||
|
||||
class crn_unpacker {
|
||||
public:
|
||||
@@ -3168,7 +3145,7 @@ class crn_unpacker {
|
||||
|
||||
symbol_codec m_codec;
|
||||
|
||||
static_huffman_data_model m_chunk_encoding_dm;
|
||||
static_huffman_data_model m_reference_encoding_dm;
|
||||
static_huffman_data_model m_endpoint_delta_dm[2];
|
||||
static_huffman_data_model m_selector_delta_dm[2];
|
||||
|
||||
@@ -3177,12 +3154,20 @@ class crn_unpacker {
|
||||
|
||||
crnd::vector<uint16> m_alpha_endpoints;
|
||||
crnd::vector<uint16> m_alpha_selectors;
|
||||
|
||||
struct block_buffer_element {
|
||||
uint16 endpoint_reference;
|
||||
uint16 color_endpoint_index;
|
||||
uint16 alpha0_endpoint_index;
|
||||
uint16 alpha1_endpoint_index;
|
||||
};
|
||||
crnd::vector<block_buffer_element> m_block_buffer;
|
||||
|
||||
bool init_tables() {
|
||||
if (!m_codec.start_decoding(m_pData + m_pHeader->m_tables_ofs, m_pHeader->m_tables_size))
|
||||
return false;
|
||||
|
||||
if (!m_codec.decode_receive_static_data_model(m_chunk_encoding_dm))
|
||||
if (!m_codec.decode_receive_static_data_model(m_reference_encoding_dm))
|
||||
return false;
|
||||
|
||||
if ((!m_pHeader->m_color_endpoints.m_num) && (!m_pHeader->m_alpha_endpoints.m_num))
|
||||
@@ -3244,29 +3229,16 @@ class crn_unpacker {
|
||||
|
||||
uint32* CRND_RESTRICT pDst = &m_color_endpoints[0];
|
||||
|
||||
CRND_HUFF_DECODE_BEGIN(m_codec);
|
||||
|
||||
for (uint32 i = 0; i < num_color_endpoints; i++) {
|
||||
uint32 da, db, dc, dd, de, df;
|
||||
CRND_HUFF_DECODE(m_codec, dm[0], da);
|
||||
a = (a + da) & 31;
|
||||
CRND_HUFF_DECODE(m_codec, dm[1], db);
|
||||
b = (b + db) & 63;
|
||||
CRND_HUFF_DECODE(m_codec, dm[0], dc);
|
||||
c = (c + dc) & 31;
|
||||
|
||||
CRND_HUFF_DECODE(m_codec, dm[0], dd);
|
||||
d = (d + dd) & 31;
|
||||
CRND_HUFF_DECODE(m_codec, dm[1], de);
|
||||
e = (e + de) & 63;
|
||||
CRND_HUFF_DECODE(m_codec, dm[0], df);
|
||||
f = (f + df) & 31;
|
||||
|
||||
a = (a + m_codec.decode(dm[0])) & 31;
|
||||
b = (b + m_codec.decode(dm[1])) & 63;
|
||||
c = (c + m_codec.decode(dm[0])) & 31;
|
||||
d = (d + m_codec.decode(dm[0])) & 31;
|
||||
e = (e + m_codec.decode(dm[1])) & 63;
|
||||
f = (f + m_codec.decode(dm[0])) & 31;
|
||||
*pDst++ = c | (b << 5U) | (a << 11U) | (f << 16U) | (e << 21U) | (d << 27U);
|
||||
}
|
||||
|
||||
CRND_HUFF_DECODE_END(m_codec);
|
||||
|
||||
m_codec.stop_decoding();
|
||||
|
||||
return true;
|
||||
@@ -3308,13 +3280,9 @@ class crn_unpacker {
|
||||
|
||||
const uint8* pFrom_linear = g_dxt1_from_linear;
|
||||
|
||||
CRND_HUFF_DECODE_BEGIN(m_codec);
|
||||
|
||||
for (uint32 i = 0; i < num_color_selectors; i++) {
|
||||
for (uint32 j = 0; j < 8; j++) {
|
||||
int32 sym;
|
||||
CRND_HUFF_DECODE(m_codec, dm, sym);
|
||||
|
||||
int32 sym = m_codec.decode(dm);
|
||||
cur[j * 2 + 0] = (delta0[sym] + cur[j * 2 + 0]) & 3;
|
||||
cur[j * 2 + 1] = (delta1[sym] + cur[j * 2 + 1]) & 3;
|
||||
}
|
||||
@@ -3326,8 +3294,6 @@ class crn_unpacker {
|
||||
(pFrom_linear[cur[12]] << 24) | (pFrom_linear[cur[13]] << 26) | (pFrom_linear[cur[14]] << 28) | (pFrom_linear[cur[15]] << 30);
|
||||
}
|
||||
|
||||
CRND_HUFF_DECODE_END(m_codec);
|
||||
|
||||
m_codec.stop_decoding();
|
||||
|
||||
return true;
|
||||
@@ -3349,22 +3315,12 @@ class crn_unpacker {
|
||||
uint16* CRND_RESTRICT pDst = &m_alpha_endpoints[0];
|
||||
uint32 a = 0, b = 0;
|
||||
|
||||
CRND_HUFF_DECODE_BEGIN(m_codec);
|
||||
|
||||
for (uint32 i = 0; i < num_alpha_endpoints; i++) {
|
||||
uint sa;
|
||||
CRND_HUFF_DECODE(m_codec, dm, sa);
|
||||
uint sb;
|
||||
CRND_HUFF_DECODE(m_codec, dm, sb);
|
||||
|
||||
a = (sa + a) & 255;
|
||||
b = (sb + b) & 255;
|
||||
|
||||
a = (a + m_codec.decode(dm)) & 255;
|
||||
b = (b + m_codec.decode(dm)) & 255;
|
||||
*pDst++ = (uint16)(a | (b << 8));
|
||||
}
|
||||
|
||||
CRND_HUFF_DECODE_END(m_codec);
|
||||
|
||||
m_codec.stop_decoding();
|
||||
|
||||
return true;
|
||||
@@ -3406,29 +3362,13 @@ class crn_unpacker {
|
||||
|
||||
const uint8* pFrom_linear = g_dxt5_from_linear;
|
||||
|
||||
CRND_HUFF_DECODE_BEGIN(m_codec);
|
||||
|
||||
for (uint32 i = 0; i < num_alpha_selectors; i++) {
|
||||
for (uint32 j = 0; j < 8; j++) {
|
||||
int32 sym;
|
||||
CRND_HUFF_DECODE(m_codec, dm, sym);
|
||||
|
||||
int32 sym = m_codec.decode(dm);
|
||||
cur[j * 2 + 0] = (delta0[sym] + cur[j * 2 + 0]) & 7;
|
||||
cur[j * 2 + 1] = (delta1[sym] + cur[j * 2 + 1]) & 7;
|
||||
//cur[j*2+0] = ((sym%15)-7 + cur[j*2+0]) & 7;
|
||||
//cur[j*2+1] = ((sym/15)-7 + cur[j*2+1]) & 7;
|
||||
}
|
||||
|
||||
#if 0
|
||||
dxt5_block blk;
|
||||
for (uint32 y = 0; y < 4; y++)
|
||||
for (uint32 x = 0; x < 4; x++)
|
||||
blk.set_selector(x, y, pFrom_linear[cur[x+y*4]]);
|
||||
|
||||
*pDst++ = blk.get_selectors_as_word(0);
|
||||
*pDst++ = blk.get_selectors_as_word(1);
|
||||
*pDst++ = blk.get_selectors_as_word(2);
|
||||
#else
|
||||
*pDst++ = (uint16)((pFrom_linear[cur[0]]) | (pFrom_linear[cur[1]] << 3) | (pFrom_linear[cur[2]] << 6) | (pFrom_linear[cur[3]] << 9) |
|
||||
(pFrom_linear[cur[4]] << 12) | (pFrom_linear[cur[5]] << 15));
|
||||
|
||||
@@ -3437,11 +3377,8 @@ class crn_unpacker {
|
||||
|
||||
*pDst++ = (uint16)((pFrom_linear[cur[10]] >> 2) | (pFrom_linear[cur[11]] << 1) | (pFrom_linear[cur[12]] << 4) |
|
||||
(pFrom_linear[cur[13]] << 7) | (pFrom_linear[cur[14]] << 10) | (pFrom_linear[cur[15]] << 13));
|
||||
#endif
|
||||
}
|
||||
|
||||
CRND_HUFF_DECODE_END(m_codec);
|
||||
|
||||
m_codec.stop_decoding();
|
||||
|
||||
return true;
|
||||
@@ -3474,408 +3411,249 @@ class crn_unpacker {
|
||||
}
|
||||
|
||||
bool unpack_dxt1(uint8** pDst, uint32 dst_size_in_bytes, uint32 row_pitch_in_bytes, uint32 blocks_x, uint32 blocks_y, uint32 chunks_x, uint32 chunks_y) {
|
||||
dst_size_in_bytes;
|
||||
|
||||
uint32 chunk_encoding_bits = 1;
|
||||
|
||||
const uint32 num_color_endpoints = m_color_endpoints.size();
|
||||
const uint32 num_color_selectors = m_color_selectors.size();
|
||||
const int32 delta_pitch_in_dwords = (row_pitch_in_bytes >> 2) - (chunks_x << 2);
|
||||
|
||||
uint32 color_endpoint_index[2][2] = {};
|
||||
uint32 color_selector_index[2][2] = {};
|
||||
if (m_block_buffer.size() < chunks_x << 1)
|
||||
m_block_buffer.resize(chunks_x << 1);
|
||||
|
||||
const uint32 num_faces = m_pHeader->m_faces;
|
||||
uint32 color_endpoint_index = 0;
|
||||
uint32 color_selector_index = 0;
|
||||
uint8 reference_group = 0;
|
||||
|
||||
const uint32 row_pitch_in_dwords = row_pitch_in_bytes >> 2U;
|
||||
|
||||
const int32 cBytesPerBlock = 8;
|
||||
|
||||
CRND_HUFF_DECODE_BEGIN(m_codec);
|
||||
|
||||
for (uint32 f = 0; f < num_faces; f++) {
|
||||
uint8* CRND_RESTRICT pRow = pDst[f];
|
||||
|
||||
for (uint32 y = 0; y < chunks_y; y++) {
|
||||
int32 block_delta = cBytesPerBlock * 2;
|
||||
uint8* CRND_RESTRICT pBlock = pRow;
|
||||
const bool skip_bottom_row = (y == (chunks_y - 1)) && (blocks_y & 1);
|
||||
|
||||
for (uint32 x = 0; x < chunks_x; x++) {
|
||||
|
||||
if (chunk_encoding_bits == 1) {
|
||||
CRND_HUFF_DECODE(m_codec, m_chunk_encoding_dm, chunk_encoding_bits);
|
||||
chunk_encoding_bits |= 512;
|
||||
for (uint32 f = 0; f < m_pHeader->m_faces; f++) {
|
||||
uint32* pData = (uint32*)pDst[f];
|
||||
for (uint32 y = 0; y < chunks_y << 1; y++, pData += delta_pitch_in_dwords) {
|
||||
bool visible = y < blocks_y;
|
||||
for (uint32 x = 0; x < chunks_x << 1; x++, pData += 2) {
|
||||
visible = visible && x < blocks_x;
|
||||
if (!(y & 1) && !(x & 1))
|
||||
reference_group = m_codec.decode(m_reference_encoding_dm);
|
||||
block_buffer_element &buffer = m_block_buffer[x];
|
||||
uint8 endpoint_reference;
|
||||
if (y & 1) {
|
||||
endpoint_reference = buffer.endpoint_reference;
|
||||
} else {
|
||||
endpoint_reference = reference_group & 3;
|
||||
reference_group >>= 2;
|
||||
buffer.endpoint_reference = reference_group & 3;
|
||||
reference_group >>= 2;
|
||||
}
|
||||
|
||||
const uint32 chunk_encoding_index = chunk_encoding_bits & 7;
|
||||
chunk_encoding_bits >>= 3;
|
||||
|
||||
for (uint32 by = 0; by < 2; by++) {
|
||||
for (uint32 bx = 0; bx < 2; bx++) {
|
||||
uint8 endpoint_reference = g_crnd_endpoint_references[chunk_encoding_index][by][bx];
|
||||
if (!endpoint_reference) {
|
||||
uint32 delta;
|
||||
CRND_HUFF_DECODE(m_codec, m_endpoint_delta_dm[0], delta);
|
||||
color_endpoint_index[by][bx] = color_endpoint_index[by][bx ^ 1] + delta;
|
||||
if (color_endpoint_index[by][bx] >= num_color_endpoints)
|
||||
color_endpoint_index[by][bx] -= num_color_endpoints;
|
||||
} else {
|
||||
color_endpoint_index[by][bx] = endpoint_reference == 1 ? color_endpoint_index[by][bx ^ 1] : color_endpoint_index[by ^ 1][bx];
|
||||
}
|
||||
}
|
||||
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;
|
||||
buffer.color_endpoint_index = color_endpoint_index;
|
||||
} else if (endpoint_reference == 1) {
|
||||
buffer.color_endpoint_index = color_endpoint_index;
|
||||
} else {
|
||||
color_endpoint_index = buffer.color_endpoint_index;
|
||||
}
|
||||
|
||||
const bool skip_right_col = (blocks_x & 1) && (x == ((int32)chunks_x - 1));
|
||||
|
||||
uint32* CRND_RESTRICT pD = (uint32*)pBlock;
|
||||
|
||||
for (uint32 by = 0; by < 2; by++) {
|
||||
pD = (uint32*)((uint8*)pBlock + row_pitch_in_bytes * by);
|
||||
for (uint32 bx = 0; bx < 2; bx++, pD += 2) {
|
||||
uint32 delta;
|
||||
CRND_HUFF_DECODE(m_codec, m_selector_delta_dm[0], delta);
|
||||
color_selector_index[by][bx] = color_selector_index[by][bx ^ 1] + delta;
|
||||
if (color_selector_index[by][bx] >= num_color_selectors)
|
||||
color_selector_index[by][bx] -= num_color_selectors;
|
||||
if (!((bx && skip_right_col) || (by && skip_bottom_row))) {
|
||||
pD[0] = m_color_endpoints[color_endpoint_index[by][bx]];
|
||||
pD[1] = m_color_selectors[color_selector_index[by][bx]];
|
||||
}
|
||||
}
|
||||
color_selector_index += m_codec.decode(m_selector_delta_dm[0]);
|
||||
if (color_selector_index >= num_color_selectors)
|
||||
color_selector_index -= num_color_selectors;
|
||||
if (visible) {
|
||||
pData[0] = m_color_endpoints[color_endpoint_index];
|
||||
pData[1] = m_color_selectors[color_selector_index];
|
||||
}
|
||||
|
||||
pBlock += block_delta;
|
||||
|
||||
} // x
|
||||
|
||||
pRow += row_pitch_in_bytes * 2;
|
||||
|
||||
} // y
|
||||
|
||||
} // f
|
||||
|
||||
CRND_HUFF_DECODE_END(m_codec);
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool unpack_dxt5(uint8** pDst, uint32 dst_size_in_bytes, uint32 row_pitch_in_bytes, uint32 blocks_x, uint32 blocks_y, uint32 chunks_x, uint32 chunks_y) {
|
||||
dst_size_in_bytes;
|
||||
|
||||
uint32 chunk_encoding_bits = 1;
|
||||
|
||||
const uint32 num_color_endpoints = m_color_endpoints.size();
|
||||
const uint32 num_color_selectors = m_color_selectors.size();
|
||||
const uint32 num_alpha_endpoints = m_alpha_endpoints.size();
|
||||
const uint32 num_alpha_selectors = m_pHeader->m_alpha_selectors.m_num;
|
||||
const int32 delta_pitch_in_dwords = (row_pitch_in_bytes >> 2) - (chunks_x << 3);
|
||||
|
||||
uint32 color_endpoint_index[2][2] = {};
|
||||
uint32 color_selector_index[2][2] = {};
|
||||
uint32 alpha_endpoint_index[2][2] = {};
|
||||
uint32 alpha_selector_index[2][2] = {};
|
||||
if (m_block_buffer.size() < chunks_x << 1)
|
||||
m_block_buffer.resize(chunks_x << 1);
|
||||
|
||||
const uint32 num_faces = m_pHeader->m_faces;
|
||||
uint32 color_endpoint_index = 0;
|
||||
uint32 color_selector_index = 0;
|
||||
uint32 alpha0_endpoint_index = 0;
|
||||
uint32 alpha0_selector_index = 0;
|
||||
uint8 reference_group = 0;
|
||||
|
||||
//const uint32 row_pitch_in_dwords = row_pitch_in_bytes >> 2U;
|
||||
|
||||
const int32 cBytesPerBlock = 16;
|
||||
|
||||
CRND_HUFF_DECODE_BEGIN(m_codec);
|
||||
|
||||
for (uint32 f = 0; f < num_faces; f++) {
|
||||
uint8* CRND_RESTRICT pRow = pDst[f];
|
||||
|
||||
for (uint32 y = 0; y < chunks_y; y++) {
|
||||
int32 block_delta = cBytesPerBlock * 2;
|
||||
uint8* CRND_RESTRICT pBlock = pRow;
|
||||
const bool skip_bottom_row = (y == (chunks_y - 1)) && (blocks_y & 1);
|
||||
|
||||
for (uint32 x = 0; x < chunks_x; x++) {
|
||||
uint32 color_endpoints[2][2];
|
||||
uint32 alpha_endpoints[2][2];
|
||||
|
||||
if (chunk_encoding_bits == 1) {
|
||||
CRND_HUFF_DECODE(m_codec, m_chunk_encoding_dm, chunk_encoding_bits);
|
||||
chunk_encoding_bits |= 512;
|
||||
for (uint32 f = 0; f < m_pHeader->m_faces; f++) {
|
||||
uint32* pData = (uint32*)pDst[f];
|
||||
for (uint32 y = 0; y < chunks_y << 1; y++, pData += delta_pitch_in_dwords) {
|
||||
bool visible = y < blocks_y;
|
||||
for (uint32 x = 0; x < chunks_x << 1; x++, pData += 4) {
|
||||
visible = visible && x < blocks_x;
|
||||
if (!(y & 1) && !(x & 1))
|
||||
reference_group = m_codec.decode(m_reference_encoding_dm);
|
||||
block_buffer_element &buffer = m_block_buffer[x];
|
||||
uint8 endpoint_reference;
|
||||
if (y & 1) {
|
||||
endpoint_reference = buffer.endpoint_reference;
|
||||
} else {
|
||||
endpoint_reference = reference_group & 3;
|
||||
reference_group >>= 2;
|
||||
buffer.endpoint_reference = reference_group & 3;
|
||||
reference_group >>= 2;
|
||||
}
|
||||
|
||||
const uint32 chunk_encoding_index = chunk_encoding_bits & 7;
|
||||
chunk_encoding_bits >>= 3;
|
||||
|
||||
const bool skip_right_col = (blocks_x & 1) && (x == ((int32)chunks_x - 1));
|
||||
|
||||
uint32* CRND_RESTRICT pD = (uint32*)pBlock;
|
||||
|
||||
for (uint32 by = 0; by < 2; by++) {
|
||||
for (uint32 bx = 0; bx < 2; bx++) {
|
||||
uint8 endpoint_reference = g_crnd_endpoint_references[chunk_encoding_index][by][bx];
|
||||
if (!endpoint_reference) {
|
||||
uint32 delta;
|
||||
CRND_HUFF_DECODE(m_codec, m_endpoint_delta_dm[1], delta);
|
||||
alpha_endpoint_index[by][bx] = alpha_endpoint_index[by][bx ^ 1] + delta;
|
||||
if (alpha_endpoint_index[by][bx] >= num_alpha_endpoints)
|
||||
alpha_endpoint_index[by][bx] -= num_alpha_endpoints;
|
||||
} else {
|
||||
alpha_endpoint_index[by][bx] = endpoint_reference == 1 ? alpha_endpoint_index[by][bx ^ 1] : alpha_endpoint_index[by ^ 1][bx];
|
||||
}
|
||||
}
|
||||
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;
|
||||
buffer.color_endpoint_index = color_endpoint_index;
|
||||
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.alpha0_endpoint_index = alpha0_endpoint_index;
|
||||
} else if (endpoint_reference == 1) {
|
||||
buffer.color_endpoint_index = color_endpoint_index;
|
||||
buffer.alpha0_endpoint_index = alpha0_endpoint_index;
|
||||
} else {
|
||||
color_endpoint_index = buffer.color_endpoint_index;
|
||||
alpha0_endpoint_index = buffer.alpha0_endpoint_index;
|
||||
}
|
||||
|
||||
for (uint32 by = 0; by < 2; by++) {
|
||||
for (uint32 bx = 0; bx < 2; bx++) {
|
||||
uint8 endpoint_reference = g_crnd_endpoint_references[chunk_encoding_index][by][bx];
|
||||
if (!endpoint_reference) {
|
||||
uint32 delta;
|
||||
CRND_HUFF_DECODE(m_codec, m_endpoint_delta_dm[0], delta);
|
||||
color_endpoint_index[by][bx] = color_endpoint_index[by][bx ^ 1] + delta;
|
||||
if (color_endpoint_index[by][bx] >= num_color_endpoints)
|
||||
color_endpoint_index[by][bx] -= num_color_endpoints;
|
||||
} else {
|
||||
color_endpoint_index[by][bx] = endpoint_reference == 1 ? color_endpoint_index[by][bx ^ 1] : color_endpoint_index[by ^ 1][bx];
|
||||
}
|
||||
}
|
||||
color_selector_index += m_codec.decode(m_selector_delta_dm[0]);
|
||||
if (color_selector_index >= num_color_selectors)
|
||||
color_selector_index -= num_color_selectors;
|
||||
alpha0_selector_index += m_codec.decode(m_selector_delta_dm[1]);
|
||||
if (alpha0_selector_index >= num_alpha_selectors)
|
||||
alpha0_selector_index -= num_alpha_selectors;
|
||||
if (visible) {
|
||||
const uint16* pAlpha0_selectors = &m_alpha_selectors[alpha0_selector_index * 3];
|
||||
pData[0] = m_alpha_endpoints[alpha0_endpoint_index] | (pAlpha0_selectors[0] << 16);
|
||||
pData[1] = pAlpha0_selectors[1] | (pAlpha0_selectors[2] << 16);
|
||||
pData[2] = m_color_endpoints[color_endpoint_index];
|
||||
pData[3] = m_color_selectors[color_selector_index];
|
||||
}
|
||||
|
||||
for (uint32 by = 0; by < 2; by++) {
|
||||
for (uint32 bx = 0; bx < 2; bx++, pD += 4) {
|
||||
uint32 delta0;
|
||||
CRND_HUFF_DECODE(m_codec, m_selector_delta_dm[1], delta0);
|
||||
alpha_selector_index[by][bx] = alpha_selector_index[by][bx ^ 1] + delta0;
|
||||
if (alpha_selector_index[by][bx] >= num_alpha_selectors)
|
||||
alpha_selector_index[by][bx] -= num_alpha_selectors;
|
||||
|
||||
uint32 delta1;
|
||||
CRND_HUFF_DECODE(m_codec, m_selector_delta_dm[0], delta1);
|
||||
color_selector_index[by][bx] = color_selector_index[by][bx ^ 1] + delta1;
|
||||
if (color_selector_index[by][bx] >= num_color_selectors)
|
||||
color_selector_index[by][bx] -= num_color_selectors;
|
||||
|
||||
if (!((bx && skip_right_col) || (by && skip_bottom_row))) {
|
||||
const uint16* pAlpha_selectors = &m_alpha_selectors[alpha_selector_index[by][bx] * 3];
|
||||
pD[0] = m_alpha_endpoints[alpha_endpoint_index[by][bx]] | (pAlpha_selectors[0] << 16);
|
||||
pD[1] = pAlpha_selectors[1] | (pAlpha_selectors[2] << 16);
|
||||
pD[2] = m_color_endpoints[color_endpoint_index[by][bx]];
|
||||
pD[3] = m_color_selectors[color_selector_index[by][bx]];
|
||||
}
|
||||
}
|
||||
|
||||
pD = (uint32*)((uint8*)pD - cBytesPerBlock * 2 + row_pitch_in_bytes);
|
||||
}
|
||||
|
||||
pBlock += block_delta;
|
||||
|
||||
} // x
|
||||
|
||||
pRow += row_pitch_in_bytes * 2;
|
||||
|
||||
} // y
|
||||
|
||||
} // f
|
||||
|
||||
CRND_HUFF_DECODE_END(m_codec);
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool unpack_dxn(uint8** pDst, uint32 dst_size_in_bytes, uint32 row_pitch_in_bytes, uint32 blocks_x, uint32 blocks_y, uint32 chunks_x, uint32 chunks_y) {
|
||||
dst_size_in_bytes;
|
||||
|
||||
uint32 chunk_encoding_bits = 1;
|
||||
|
||||
const uint32 num_alpha_endpoints = m_alpha_endpoints.size();
|
||||
const uint32 num_alpha_selectors = m_pHeader->m_alpha_selectors.m_num;
|
||||
const int32 delta_pitch_in_dwords = (row_pitch_in_bytes >> 2) - (chunks_x << 3);
|
||||
|
||||
uint32 alpha0_endpoint_index[2][2] = {};
|
||||
uint32 alpha0_selector_index[2][2] = {};
|
||||
uint32 alpha1_endpoint_index[2][2] = {};
|
||||
uint32 alpha1_selector_index[2][2] = {};
|
||||
if (m_block_buffer.size() < chunks_x << 1)
|
||||
m_block_buffer.resize(chunks_x << 1);
|
||||
|
||||
const uint32 num_faces = m_pHeader->m_faces;
|
||||
uint32 alpha0_endpoint_index = 0;
|
||||
uint32 alpha0_selector_index = 0;
|
||||
uint32 alpha1_endpoint_index = 0;
|
||||
uint32 alpha1_selector_index = 0;
|
||||
uint8 reference_group = 0;
|
||||
|
||||
//const uint32 row_pitch_in_dwords = row_pitch_in_bytes >> 2U;
|
||||
|
||||
const int32 cBytesPerBlock = 16;
|
||||
|
||||
CRND_HUFF_DECODE_BEGIN(m_codec);
|
||||
|
||||
for (uint32 f = 0; f < num_faces; f++) {
|
||||
uint8* CRND_RESTRICT pRow = pDst[f];
|
||||
|
||||
for (uint32 y = 0; y < chunks_y; y++) {
|
||||
int32 block_delta = cBytesPerBlock * 2;
|
||||
uint8* CRND_RESTRICT pBlock = pRow;
|
||||
const bool skip_bottom_row = (y == (chunks_y - 1)) && (blocks_y & 1);
|
||||
|
||||
for (uint32 x = 0; x < chunks_x; x++) {
|
||||
if (chunk_encoding_bits == 1) {
|
||||
CRND_HUFF_DECODE(m_codec, m_chunk_encoding_dm, chunk_encoding_bits);
|
||||
chunk_encoding_bits |= 512;
|
||||
for (uint32 f = 0; f < m_pHeader->m_faces; f++) {
|
||||
uint32* pData = (uint32*)pDst[f];
|
||||
for (uint32 y = 0; y < chunks_y << 1; y++, pData += delta_pitch_in_dwords) {
|
||||
bool visible = y < blocks_y;
|
||||
for (uint32 x = 0; x < chunks_x << 1; x++, pData += 4) {
|
||||
visible = visible && x < blocks_x;
|
||||
if (!(y & 1) && !(x & 1))
|
||||
reference_group = m_codec.decode(m_reference_encoding_dm);
|
||||
block_buffer_element &buffer = m_block_buffer[x];
|
||||
uint8 endpoint_reference;
|
||||
if (y & 1) {
|
||||
endpoint_reference = buffer.endpoint_reference;
|
||||
} else {
|
||||
endpoint_reference = reference_group & 3;
|
||||
reference_group >>= 2;
|
||||
buffer.endpoint_reference = reference_group & 3;
|
||||
reference_group >>= 2;
|
||||
}
|
||||
|
||||
const uint32 chunk_encoding_index = chunk_encoding_bits & 7;
|
||||
chunk_encoding_bits >>= 3;
|
||||
|
||||
const bool skip_right_col = (blocks_x & 1) && (x == ((int32)chunks_x - 1));
|
||||
|
||||
uint32* CRND_RESTRICT pD = (uint32*)pBlock;
|
||||
|
||||
for (uint32 by = 0; by < 2; by++) {
|
||||
for (uint32 bx = 0; bx < 2; bx++) {
|
||||
uint8 endpoint_reference = g_crnd_endpoint_references[chunk_encoding_index][by][bx];
|
||||
if (!endpoint_reference) {
|
||||
uint32 delta;
|
||||
CRND_HUFF_DECODE(m_codec, m_endpoint_delta_dm[1], delta);
|
||||
alpha0_endpoint_index[by][bx] = alpha0_endpoint_index[by][bx ^ 1] + delta;
|
||||
if (alpha0_endpoint_index[by][bx] >= num_alpha_endpoints)
|
||||
alpha0_endpoint_index[by][bx] -= num_alpha_endpoints;
|
||||
} else {
|
||||
alpha0_endpoint_index[by][bx] = endpoint_reference == 1 ? alpha0_endpoint_index[by][bx ^ 1] : alpha0_endpoint_index[by ^ 1][bx];
|
||||
}
|
||||
}
|
||||
if (!endpoint_reference) {
|
||||
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.alpha0_endpoint_index = alpha0_endpoint_index;
|
||||
alpha1_endpoint_index += m_codec.decode(m_endpoint_delta_dm[1]);
|
||||
if (alpha1_endpoint_index >= num_alpha_endpoints)
|
||||
alpha1_endpoint_index -= num_alpha_endpoints;
|
||||
buffer.alpha1_endpoint_index = alpha1_endpoint_index;
|
||||
} else if (endpoint_reference == 1) {
|
||||
buffer.alpha0_endpoint_index = alpha0_endpoint_index;
|
||||
buffer.alpha1_endpoint_index = alpha1_endpoint_index;
|
||||
} else {
|
||||
alpha0_endpoint_index = buffer.alpha0_endpoint_index;
|
||||
alpha1_endpoint_index = buffer.alpha1_endpoint_index;
|
||||
}
|
||||
|
||||
for (uint32 by = 0; by < 2; by++) {
|
||||
for (uint32 bx = 0; bx < 2; bx++) {
|
||||
uint8 endpoint_reference = g_crnd_endpoint_references[chunk_encoding_index][by][bx];
|
||||
if (!endpoint_reference) {
|
||||
uint32 delta;
|
||||
CRND_HUFF_DECODE(m_codec, m_endpoint_delta_dm[1], delta);
|
||||
alpha1_endpoint_index[by][bx] = alpha1_endpoint_index[by][bx ^ 1] + delta;
|
||||
if (alpha1_endpoint_index[by][bx] >= num_alpha_endpoints)
|
||||
alpha1_endpoint_index[by][bx] -= num_alpha_endpoints;
|
||||
} else {
|
||||
alpha1_endpoint_index[by][bx] = endpoint_reference == 1 ? alpha1_endpoint_index[by][bx ^ 1] : alpha1_endpoint_index[by ^ 1][bx];
|
||||
}
|
||||
}
|
||||
alpha0_selector_index += m_codec.decode(m_selector_delta_dm[1]);
|
||||
if (alpha0_selector_index >= num_alpha_selectors)
|
||||
alpha0_selector_index -= num_alpha_selectors;
|
||||
alpha1_selector_index += m_codec.decode(m_selector_delta_dm[1]);
|
||||
if (alpha1_selector_index >= num_alpha_selectors)
|
||||
alpha1_selector_index -= num_alpha_selectors;
|
||||
if (visible) {
|
||||
const uint16* pAlpha0_selectors = &m_alpha_selectors[alpha0_selector_index * 3];
|
||||
const uint16* pAlpha1_selectors = &m_alpha_selectors[alpha1_selector_index * 3];
|
||||
pData[0] = m_alpha_endpoints[alpha0_endpoint_index] | (pAlpha0_selectors[0] << 16);
|
||||
pData[1] = pAlpha0_selectors[1] | (pAlpha0_selectors[2] << 16);
|
||||
pData[2] = m_alpha_endpoints[alpha1_endpoint_index] | (pAlpha1_selectors[0] << 16);
|
||||
pData[3] = pAlpha1_selectors[1] | (pAlpha1_selectors[2] << 16);
|
||||
}
|
||||
|
||||
for (uint32 by = 0; by < 2; by++) {
|
||||
for (uint32 bx = 0; bx < 2; bx++, pD += 4) {
|
||||
uint32 delta0;
|
||||
CRND_HUFF_DECODE(m_codec, m_selector_delta_dm[1], delta0);
|
||||
alpha0_selector_index[by][bx] = alpha0_selector_index[by][bx ^ 1] + delta0;
|
||||
if (alpha0_selector_index[by][bx] >= num_alpha_selectors)
|
||||
alpha0_selector_index[by][bx] -= num_alpha_selectors;
|
||||
|
||||
uint32 delta1;
|
||||
CRND_HUFF_DECODE(m_codec, m_selector_delta_dm[1], delta1);
|
||||
alpha1_selector_index[by][bx] = alpha1_selector_index[by][bx ^ 1] + delta1;
|
||||
if (alpha1_selector_index[by][bx] >= num_alpha_selectors)
|
||||
alpha1_selector_index[by][bx] -= num_alpha_selectors;
|
||||
|
||||
if (!((bx && skip_right_col) || (by && skip_bottom_row))) {
|
||||
const uint16* pAlpha0_selectors = &m_alpha_selectors[alpha0_selector_index[by][bx] * 3];
|
||||
const uint16* pAlpha1_selectors = &m_alpha_selectors[alpha1_selector_index[by][bx] * 3];
|
||||
pD[0] = m_alpha_endpoints[alpha0_endpoint_index[by][bx]] | (pAlpha0_selectors[0] << 16);
|
||||
pD[1] = pAlpha0_selectors[1] | (pAlpha0_selectors[2] << 16);
|
||||
pD[2] = m_alpha_endpoints[alpha1_endpoint_index[by][bx]] | (pAlpha1_selectors[0] << 16);
|
||||
pD[3] = pAlpha1_selectors[1] | (pAlpha1_selectors[2] << 16);
|
||||
}
|
||||
}
|
||||
|
||||
pD = (uint32*)((uint8*)pD - cBytesPerBlock * 2 + row_pitch_in_bytes);
|
||||
}
|
||||
|
||||
pBlock += block_delta;
|
||||
|
||||
} // x
|
||||
|
||||
pRow += row_pitch_in_bytes * 2;
|
||||
|
||||
} // y
|
||||
|
||||
} // f
|
||||
|
||||
CRND_HUFF_DECODE_END(m_codec);
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool unpack_dxt5a(uint8** pDst, uint32 dst_size_in_bytes, uint32 row_pitch_in_bytes, uint32 blocks_x, uint32 blocks_y, uint32 chunks_x, uint32 chunks_y) {
|
||||
dst_size_in_bytes;
|
||||
|
||||
uint32 chunk_encoding_bits = 1;
|
||||
|
||||
const uint32 num_alpha_endpoints = m_alpha_endpoints.size();
|
||||
const uint32 num_alpha_selectors = m_pHeader->m_alpha_selectors.m_num;
|
||||
const int32 delta_pitch_in_dwords = (row_pitch_in_bytes >> 2) - (chunks_x << 2);
|
||||
|
||||
uint32 alpha0_endpoint_index[2][2] = {};
|
||||
uint32 alpha0_selector_index[2][2] = {};
|
||||
if (m_block_buffer.size() < chunks_x << 1)
|
||||
m_block_buffer.resize(chunks_x << 1);
|
||||
|
||||
const uint32 num_faces = m_pHeader->m_faces;
|
||||
uint32 alpha0_endpoint_index = 0;
|
||||
uint32 alpha0_selector_index = 0;
|
||||
uint8 reference_group = 0;
|
||||
|
||||
const int32 cBytesPerBlock = 8;
|
||||
|
||||
CRND_HUFF_DECODE_BEGIN(m_codec);
|
||||
|
||||
for (uint32 f = 0; f < num_faces; f++) {
|
||||
uint8* CRND_RESTRICT pRow = pDst[f];
|
||||
|
||||
for (uint32 y = 0; y < chunks_y; y++) {
|
||||
int32 block_delta = cBytesPerBlock * 2;
|
||||
uint8* CRND_RESTRICT pBlock = pRow;
|
||||
const bool skip_bottom_row = (y == (chunks_y - 1)) && (blocks_y & 1);
|
||||
|
||||
for (uint32 x = 0; x < chunks_x; x++) {
|
||||
if (chunk_encoding_bits == 1) {
|
||||
CRND_HUFF_DECODE(m_codec, m_chunk_encoding_dm, chunk_encoding_bits);
|
||||
chunk_encoding_bits |= 512;
|
||||
for (uint32 f = 0; f < m_pHeader->m_faces; f++) {
|
||||
uint32* pData = (uint32*)pDst[f];
|
||||
for (uint32 y = 0; y < chunks_y << 1; y++, pData += delta_pitch_in_dwords) {
|
||||
bool visible = y < blocks_y;
|
||||
for (uint32 x = 0; x < chunks_x << 1; x++, pData += 2) {
|
||||
visible = visible && x < blocks_x;
|
||||
if (!(y & 1) && !(x & 1))
|
||||
reference_group = m_codec.decode(m_reference_encoding_dm);
|
||||
block_buffer_element &buffer = m_block_buffer[x];
|
||||
uint8 endpoint_reference;
|
||||
if (y & 1) {
|
||||
endpoint_reference = buffer.endpoint_reference;
|
||||
} else {
|
||||
endpoint_reference = reference_group & 3;
|
||||
reference_group >>= 2;
|
||||
buffer.endpoint_reference = reference_group & 3;
|
||||
reference_group >>= 2;
|
||||
}
|
||||
|
||||
const uint32 chunk_encoding_index = chunk_encoding_bits & 7;
|
||||
chunk_encoding_bits >>= 3;
|
||||
|
||||
const bool skip_right_col = (blocks_x & 1) && (x == ((int32)chunks_x - 1));
|
||||
|
||||
uint32* CRND_RESTRICT pD = (uint32*)pBlock;
|
||||
|
||||
for (uint32 by = 0; by < 2; by++) {
|
||||
for (uint32 bx = 0; bx < 2; bx++) {
|
||||
uint8 endpoint_reference = g_crnd_endpoint_references[chunk_encoding_index][by][bx];
|
||||
if (!endpoint_reference) {
|
||||
uint32 delta;
|
||||
CRND_HUFF_DECODE(m_codec, m_endpoint_delta_dm[1], delta);
|
||||
alpha0_endpoint_index[by][bx] = alpha0_endpoint_index[by][bx ^ 1] + delta;
|
||||
if (alpha0_endpoint_index[by][bx] >= num_alpha_endpoints)
|
||||
alpha0_endpoint_index[by][bx] -= num_alpha_endpoints;
|
||||
} else {
|
||||
alpha0_endpoint_index[by][bx] = endpoint_reference == 1 ? alpha0_endpoint_index[by][bx ^ 1] : alpha0_endpoint_index[by ^ 1][bx];
|
||||
}
|
||||
}
|
||||
if (!endpoint_reference) {
|
||||
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.alpha0_endpoint_index = alpha0_endpoint_index;
|
||||
} else if (endpoint_reference == 1) {
|
||||
buffer.alpha0_endpoint_index = alpha0_endpoint_index;
|
||||
} else {
|
||||
alpha0_endpoint_index = buffer.alpha0_endpoint_index;
|
||||
}
|
||||
|
||||
for (uint32 by = 0; by < 2; by++) {
|
||||
for (uint32 bx = 0; bx < 2; bx++, pD += 2) {
|
||||
uint32 delta;
|
||||
CRND_HUFF_DECODE(m_codec, m_selector_delta_dm[1], delta);
|
||||
alpha0_selector_index[by][bx] = alpha0_selector_index[by][bx ^ 1] + delta;
|
||||
if (alpha0_selector_index[by][bx] >= num_alpha_selectors)
|
||||
alpha0_selector_index[by][bx] -= num_alpha_selectors;
|
||||
|
||||
if (!((bx && skip_right_col) || (by && skip_bottom_row))) {
|
||||
const uint16* pAlpha0_selectors = &m_alpha_selectors[alpha0_selector_index[by][bx] * 3];
|
||||
pD[0] = m_alpha_endpoints[alpha0_endpoint_index[by][bx]] | (pAlpha0_selectors[0] << 16);
|
||||
pD[1] = pAlpha0_selectors[1] | (pAlpha0_selectors[2] << 16);
|
||||
}
|
||||
}
|
||||
|
||||
pD = (uint32*)((uint8*)pD - cBytesPerBlock * 2 + row_pitch_in_bytes);
|
||||
alpha0_selector_index += m_codec.decode(m_selector_delta_dm[1]);
|
||||
if (alpha0_selector_index >= num_alpha_selectors)
|
||||
alpha0_selector_index -= num_alpha_selectors;
|
||||
if (visible) {
|
||||
const uint16* pAlpha0_selectors = &m_alpha_selectors[alpha0_selector_index * 3];
|
||||
pData[0] = m_alpha_endpoints[alpha0_endpoint_index] | (pAlpha0_selectors[0] << 16);
|
||||
pData[1] = pAlpha0_selectors[1] | (pAlpha0_selectors[2] << 16);
|
||||
}
|
||||
|
||||
pBlock += block_delta;
|
||||
|
||||
} // x
|
||||
|
||||
pRow += row_pitch_in_bytes * 2;
|
||||
|
||||
} // y
|
||||
|
||||
} // f
|
||||
|
||||
CRND_HUFF_DECODE_END(m_codec);
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
crnd_unpack_context crnd_unpack_begin(const void* pData, uint32 data_size) {
|
||||
|
||||
Reference in New Issue
Block a user