Still a WIP - needs more testing (especially when video is disabled, which hasn't been tested on this branch yet):

- Adding conditional replenishment (CR) support to the system, so blocks which don't change their selectors or endpoints from the last frame don't need to be decoded. This can greatly increase the compression ratio on many video sequences.
- Updating help text.
- Adding support for slices to be marked as i-frames
- Updating transcoder to support CR
This commit is contained in:
richgel999
2019-05-18 17:54:52 -07:00
parent 2b08177cd8
commit 9529bdbff8
10 changed files with 448 additions and 157 deletions

View File

@@ -170,14 +170,13 @@ namespace basisu
{
{ -1, 0 },
{ 0, -1 },
{ -1, -1 }
{ -1, -1 } // or conditional replenishment in videos
};
const uint32_t NUM_ENDPOINT_PREDS = BASISU_ARRAY_SIZE(g_endpoint_preds);
const uint32_t NO_ENDPOINT_PRED_INDEX = 3;//NUM_ENDPOINT_PREDS;
void basisu_backend::reoptimize_and_sort_endpoints_codebook(uint32_t total_block_endpoints_remapped, uint_vec &all_endpoint_indices)
{
basisu_frontend &r = *m_pFront_end;
const bool is_video = r.get_params().m_tex_type == basist::cBASISTexTypeVideoFrames;
if (total_block_endpoints_remapped)
{
@@ -292,10 +291,106 @@ namespace basisu
for (uint32_t i = 0; i < m_selector_remap_table_new_to_old.size(); i++)
m_selector_remap_table_old_to_new[m_selector_remap_table_new_to_old[i]] = i;
}
int basisu_backend::find_video_frame(int slice_index, int delta)
{
for (uint32_t s = 0; s < m_slices.size(); s++)
{
if ((int)m_slices[s].m_source_file_index != (m_slices[slice_index].m_source_file_index + delta))
continue;
if (m_slices[s].m_mip_index != m_slices[slice_index].m_mip_index)
continue;
// Being super paranoid here.
if (m_slices[s].m_num_blocks_x != (m_slices[slice_index].m_num_blocks_x))
continue;
if (m_slices[s].m_num_blocks_y != (m_slices[slice_index].m_num_blocks_y))
continue;
if (m_slices[s].m_alpha != (m_slices[slice_index].m_alpha))
continue;
return s;
}
return -1;
}
void basisu_backend::check_for_valid_cr_blocks()
{
basisu_frontend& r = *m_pFront_end;
const bool is_video = r.get_params().m_tex_type == basist::cBASISTexTypeVideoFrames;
if (!is_video)
return;
uint32_t total_crs = 0;
uint32_t total_invalid_crs = 0;
for (uint32_t slice_index = 0; slice_index < m_slices.size(); slice_index++)
{
const bool is_iframe = m_slices[slice_index].m_iframe;
const uint32_t first_block_index = m_slices[slice_index].m_first_block_index;
const uint32_t width = m_slices[slice_index].m_width;
const uint32_t height = m_slices[slice_index].m_height;
const uint32_t num_blocks_x = m_slices[slice_index].m_num_blocks_x;
const uint32_t num_blocks_y = m_slices[slice_index].m_num_blocks_y;
const int prev_frame_slice_index = find_video_frame(slice_index, -1);
// If we don't have a previous frame, and we're not an i-frame, something is wrong.
if ((prev_frame_slice_index < 0) && (!is_iframe))
{
BASISU_BACKEND_VERIFY(0);
}
if ((is_iframe) || (prev_frame_slice_index < 0))
{
// Ensure no blocks use CR's
for (uint32_t block_y = 0; block_y < num_blocks_y; block_y++)
{
for (uint32_t block_x = 0; block_x < num_blocks_x; block_x++)
{
encoder_block& m = m_slice_encoder_blocks[slice_index](block_x, block_y);
BASISU_BACKEND_VERIFY(m.m_endpoint_predictor != basist::CR_ENDPOINT_PRED_INDEX);
}
}
}
else
{
// For blocks that use CR's, make sure the endpoints/selectors haven't really changed.
for (uint32_t block_y = 0; block_y < num_blocks_y; block_y++)
{
for (uint32_t block_x = 0; block_x < num_blocks_x; block_x++)
{
encoder_block& m = m_slice_encoder_blocks[slice_index](block_x, block_y);
if (m.m_endpoint_predictor == basist::CR_ENDPOINT_PRED_INDEX)
{
total_crs++;
encoder_block& prev_m = m_slice_encoder_blocks[prev_frame_slice_index](block_x, block_y);
if ((m.m_endpoint_index != prev_m.m_endpoint_index) || (m.m_selector_index != prev_m.m_selector_index))
{
total_invalid_crs++;
}
}
} // block_x
} // block_y
} // !slice_index
} // slice_index
debug_printf("Total CR's: %u, Total invalid CR's: %u\n", total_crs, total_invalid_crs);
BASISU_BACKEND_VERIFY(total_invalid_crs == 0);
}
void basisu_backend::create_encoder_blocks()
{
basisu_frontend &r = *m_pFront_end;
const bool is_video = r.get_params().m_tex_type == basist::cBASISTexTypeVideoFrames;
m_slice_encoder_blocks.resize(m_slices.size());
@@ -306,6 +401,9 @@ namespace basisu
for (uint32_t slice_index = 0; slice_index < m_slices.size(); slice_index++)
{
const int prev_frame_slice_index = is_video ? find_video_frame(slice_index, -1) : -1;
const bool is_iframe = m_slices[slice_index].m_iframe;
const uint32_t first_block_index = m_slices[slice_index].m_first_block_index;
const uint32_t width = m_slices[slice_index].m_width;
@@ -328,29 +426,50 @@ namespace basisu
m.m_selector_index = r.get_block_selector_cluster_index(block_index);
m.m_endpoint_predictor = NO_ENDPOINT_PRED_INDEX;
m.m_endpoint_predictor = basist::NO_ENDPOINT_PRED_INDEX;
const uint32_t block_endpoint = m.m_endpoint_index;
uint32_t best_endpoint_pred = UINT32_MAX;
for (uint32_t endpoint_pred = 0; endpoint_pred < NUM_ENDPOINT_PREDS; endpoint_pred++)
for (uint32_t endpoint_pred = 0; endpoint_pred < basist::NUM_ENDPOINT_PREDS; endpoint_pred++)
{
int pred_block_x = block_x + g_endpoint_preds[endpoint_pred].m_dx;
if ((pred_block_x < 0) || (pred_block_x >= (int)num_blocks_x))
continue;
int pred_block_y = block_y + g_endpoint_preds[endpoint_pred].m_dy;
if ((pred_block_y < 0) || (pred_block_y >= (int)num_blocks_y))
continue;
uint32_t pred_endpoint = m_slice_encoder_blocks[slice_index](pred_block_x, pred_block_y).m_endpoint_index;
if (pred_endpoint == block_endpoint)
if ((is_video) && (endpoint_pred == basist::CR_ENDPOINT_PRED_INDEX))
{
if (endpoint_pred < best_endpoint_pred)
if ((prev_frame_slice_index != -1) && (!is_iframe))
{
best_endpoint_pred = endpoint_pred;
const uint32_t cur_endpoint = m_slice_encoder_blocks[slice_index](block_x, block_y).m_endpoint_index;
const uint32_t cur_selector = m_slice_encoder_blocks[slice_index](block_x, block_y).m_selector_index;
const uint32_t prev_endpoint = m_slice_encoder_blocks[prev_frame_slice_index](block_x, block_y).m_endpoint_index;
const uint32_t prev_selector = m_slice_encoder_blocks[prev_frame_slice_index](block_x, block_y).m_selector_index;
if ((cur_endpoint == prev_endpoint) && (cur_selector == prev_selector))
{
best_endpoint_pred = basist::CR_ENDPOINT_PRED_INDEX;
m_slice_encoder_blocks[prev_frame_slice_index](block_x, block_y).m_is_cr_target = true;
}
}
}
else
{
int pred_block_x = block_x + g_endpoint_preds[endpoint_pred].m_dx;
if ((pred_block_x < 0) || (pred_block_x >= (int)num_blocks_x))
continue;
int pred_block_y = block_y + g_endpoint_preds[endpoint_pred].m_dy;
if ((pred_block_y < 0) || (pred_block_y >= (int)num_blocks_y))
continue;
uint32_t pred_endpoint = m_slice_encoder_blocks[slice_index](pred_block_x, pred_block_y).m_endpoint_index;
if (pred_endpoint == block_endpoint)
{
if (endpoint_pred < best_endpoint_pred)
{
best_endpoint_pred = endpoint_pred;
}
}
}
@@ -381,8 +500,11 @@ namespace basisu
best_endpoint_pred = UINT32_MAX;
for (uint32_t endpoint_pred = 0; endpoint_pred < NUM_ENDPOINT_PREDS; endpoint_pred++)
for (uint32_t endpoint_pred = 0; endpoint_pred < basist::NUM_ENDPOINT_PREDS; endpoint_pred++)
{
if ((is_video) && (endpoint_pred == basist::CR_ENDPOINT_PRED_INDEX))
continue;
int pred_block_x = block_x + g_endpoint_preds[endpoint_pred].m_dx;
if ((pred_block_x < 0) || (pred_block_x >= (int)num_blocks_x))
continue;
@@ -441,7 +563,7 @@ namespace basisu
total_endpoint_pred_missed++;
}
if (m.m_endpoint_predictor == NO_ENDPOINT_PRED_INDEX)
if (m.m_endpoint_predictor == basist::NO_ENDPOINT_PRED_INDEX)
{
all_endpoint_indices.push_back(m.m_endpoint_index);
}
@@ -456,10 +578,12 @@ namespace basisu
total_endpoint_pred_missed, total_endpoint_pred_missed * 100.0f / get_total_blocks(),
total_endpoint_pred_hits, total_endpoint_pred_hits * 100.0f / get_total_blocks(),
total_block_endpoints_remapped, total_block_endpoints_remapped * 100.0f / get_total_blocks());
reoptimize_and_sort_endpoints_codebook(total_block_endpoints_remapped, all_endpoint_indices);
sort_selector_codebook();
check_for_valid_cr_blocks();
}
void basisu_backend::compute_slice_crcs()
@@ -481,22 +605,22 @@ namespace basisu
{
const uint32_t block_index = first_block_index + block_x + block_y * num_blocks_x;
encoder_block &m = m_slice_encoder_blocks[slice_index](block_x, block_y);
encoder_block& m = m_slice_encoder_blocks[slice_index](block_x, block_y);
{
etc_block &output_block = *(etc_block *)gi.get_block_ptr(block_x, block_y);
etc_block& output_block = *(etc_block*)gi.get_block_ptr(block_x, block_y);
output_block.set_diff_bit(true);
output_block.set_flip_bit(true);
const uint32_t endpoint_index = m.m_endpoint_index;
output_block.set_block_color5_etc1s(m_endpoint_palette[endpoint_index].m_color5);
output_block.set_inten_tables_etc1s(m_endpoint_palette[endpoint_index].m_inten5);
const uint32_t selector_idx = m.m_selector_index;
const basist::etc1_selector_palette_entry &selectors = m_selector_palette[selector_idx];
const basist::etc1_selector_palette_entry& selectors = m_selector_palette[selector_idx];
for (uint32_t sy = 0; sy < 4; sy++)
for (uint32_t sx = 0; sx < 4; sx++)
output_block.set_selector(sx, sy, selectors(sx, sy));
@@ -520,7 +644,7 @@ namespace basisu
#endif
save_png(buf, gi_unpacked);
}
} // slice_index
}
@@ -528,6 +652,7 @@ namespace basisu
bool basisu_backend::encode_image()
{
basisu_frontend &r = *m_pFront_end;
const bool is_video = r.get_params().m_tex_type == basist::cBASISTexTypeVideoFrames;
uint32_t total_used_selector_history_buf = 0;
uint32_t total_selector_indices_remapped = 0;
@@ -555,6 +680,9 @@ namespace basisu
for (uint32_t slice_index = 0; slice_index < m_slices.size(); slice_index++)
{
const int prev_frame_slice_index = is_video ? find_video_frame(slice_index, -1) : -1;
const int next_frame_slice_index = is_video ? find_video_frame(slice_index, 1) : -1;
const uint32_t first_block_index = m_slices[slice_index].m_first_block_index;
const uint32_t width = m_slices[slice_index].m_width;
const uint32_t height = m_slices[slice_index].m_height;
@@ -584,10 +712,21 @@ namespace basisu
else if (m.m_endpoint_predictor == 1)
block_endpoints_are_referenced(block_x, block_y - 1) = true;
else if (m.m_endpoint_predictor == 2)
block_endpoints_are_referenced(block_x - 1, block_y - 1) = true;
}
}
{
if (!is_video)
block_endpoints_are_referenced(block_x - 1, block_y - 1) = true;
}
if (is_video)
{
if (m.m_is_cr_target)
block_endpoints_are_referenced(block_x, block_y) = true;
}
} // block_x
} // block_y
for (uint32_t block_y = 0; block_y < num_blocks_y; block_y++)
{
for (uint32_t block_x = 0; block_x < num_blocks_x; block_x++)
@@ -607,7 +746,7 @@ namespace basisu
const uint32_t bx = block_x + x;
const uint32_t by = block_y + y;
uint32_t pred = NO_ENDPOINT_PRED_INDEX;
uint32_t pred = basist::NO_ENDPOINT_PRED_INDEX;
if ((bx < num_blocks_x) && (by < num_blocks_y))
pred = m_slice_encoder_blocks[slice_index](bx, by).m_endpoint_predictor;
@@ -647,11 +786,11 @@ namespace basisu
prev_endpoint_pred_sym_bits = endpoint_pred_cur_sym_bits;
}
}
} // if (((block_x & 1) == 0) && ((block_y & 1) == 0))
int new_endpoint_index = m_endpoint_remap_table_old_to_new[m.m_endpoint_index];
if (m.m_endpoint_predictor == NO_ENDPOINT_PRED_INDEX)
if (m.m_endpoint_predictor == basist::NO_ENDPOINT_PRED_INDEX)
{
int endpoint_delta = new_endpoint_index - prev_endpoint_index;
@@ -719,17 +858,34 @@ namespace basisu
endpoint_delta += (int)r.get_total_endpoint_clusters();
delta_endpoint_histogram.inc(endpoint_delta);
}
} // if (m.m_endpoint_predictor == NO_ENDPOINT_PRED_INDEX)
block_endpoint_indices.push_back(m_endpoint_remap_table_new_to_old[new_endpoint_index]);
prev_endpoint_index = new_endpoint_index;
if ((!is_video) || (m.m_endpoint_predictor != basist::CR_ENDPOINT_PRED_INDEX))
{
int new_selector_index = m_selector_remap_table_old_to_new[m.m_selector_index];
int selector_history_buf_index = -1;
if (m.m_is_cr_target)
{
for (uint32_t j = 0; j < selector_history_buf.size(); j++)
{
const int trial_idx = selector_history_buf[j];
if (trial_idx == new_selector_index)
{
total_used_selector_history_buf++;
selector_history_buf_index = j;
selector_history_buf_histogram.inc(j);
break;
}
}
}
else
{
const pixel_block &src_pixels = r.get_source_pixel_block(block_index);
@@ -796,11 +952,9 @@ namespace basisu
selector_history_buf_histogram.inc(best_trial_history_buf_idx);
}
} // if (m_params.m_selector_rdo_quality_thresh > 0.0f)
m.m_selector_index = m_selector_remap_table_new_to_old[new_selector_index];
block_selector_indices.push_back(m.m_selector_index);
if ((selector_history_buf_rle_count) && (selector_history_buf_index != 0))
{
if (selector_history_buf_rle_count >= (int)basist::SELECTOR_HISTORY_BUF_RLE_COUNT_THRESH)
@@ -857,7 +1011,10 @@ namespace basisu
selector_history_buf.add(new_selector_index);
else if (selector_history_buf.size())
selector_history_buf.use(selector_history_buf_index);
}
} // if ((!is_video) || (m.m_endpoint_predictor != basist::CR_ENDPOINT_PRED_INDEX))
block_selector_indices.push_back(m.m_selector_index);
} // block_x
@@ -931,6 +1088,8 @@ namespace basisu
create_endpoint_palette();
}
check_for_valid_cr_blocks();
compute_slice_crcs();
double endpoint_pred_entropy = endpoint_pred_histogram.get_entropy() / endpoint_pred_histogram.get_total();
@@ -939,6 +1098,9 @@ namespace basisu
debug_printf("Histogram entropy: EndpointPred: %3.3f DeltaEndpoint: %3.3f DeltaSelector: %3.3f\n", endpoint_pred_entropy, delta_endpoint_entropy, selector_entropy);
if (!endpoint_pred_histogram.get_total())
endpoint_pred_histogram.inc(0);
huffman_encoding_table endpoint_pred_model;
if (!endpoint_pred_model.init(endpoint_pred_histogram, 16))
{
@@ -946,6 +1108,9 @@ namespace basisu
return false;
}
if (!delta_endpoint_histogram.get_total())
delta_endpoint_histogram.inc(0);
huffman_encoding_table delta_endpoint_model;
if (!delta_endpoint_model.init(delta_endpoint_histogram, 16))
{
@@ -953,6 +1118,9 @@ namespace basisu
return false;
}
if (!selector_histogram.get_total())
selector_histogram.inc(0);
huffman_encoding_table selector_model;
if (!selector_model.init(selector_histogram, 16))
{
@@ -1047,11 +1215,11 @@ namespace basisu
prev_endpoint_pred_sym = sym;
}
}
}
} // if (((block_x & 1) == 0) && ((block_y & 1) == 0))
const int new_endpoint_index = m_endpoint_remap_table_old_to_new[m.m_endpoint_index];
if (m.m_endpoint_predictor == NO_ENDPOINT_PRED_INDEX)
if (m.m_endpoint_predictor == basist::NO_ENDPOINT_PRED_INDEX)
{
int endpoint_delta = new_endpoint_index - prev_endpoint_index;
if (endpoint_delta < 0)
@@ -1062,32 +1230,35 @@ namespace basisu
prev_endpoint_index = new_endpoint_index;
if (!selector_rle_count)
if ((!is_video) || (m.m_endpoint_predictor != basist::CR_ENDPOINT_PRED_INDEX))
{
uint32_t selector_sym_index = selector_syms[slice_index][cur_selector_sym_ofs++];
if (selector_sym_index == SELECTOR_HISTORY_BUF_RLE_SYMBOL_INDEX)
selector_rle_count = selector_syms[slice_index][cur_selector_sym_ofs++];
total_selector_bits += coder.put_code(selector_sym_index, selector_model);
if (selector_sym_index == SELECTOR_HISTORY_BUF_RLE_SYMBOL_INDEX)
if (!selector_rle_count)
{
int run_sym = selector_rle_count - basist::SELECTOR_HISTORY_BUF_RLE_COUNT_THRESH;
if (run_sym >= ((int)basist::SELECTOR_HISTORY_BUF_RLE_COUNT_TOTAL - 1))
{
total_selector_bits += coder.put_code(basist::SELECTOR_HISTORY_BUF_RLE_COUNT_TOTAL - 1, selector_history_buf_rle_model);
uint32_t n = selector_rle_count - basist::SELECTOR_HISTORY_BUF_RLE_COUNT_THRESH;
total_selector_bits += coder.put_vlc(n, 7);
}
else
total_selector_bits += coder.put_code(run_sym, selector_history_buf_rle_model);
}
}
uint32_t selector_sym_index = selector_syms[slice_index][cur_selector_sym_ofs++];
if (selector_rle_count)
selector_rle_count--;
if (selector_sym_index == SELECTOR_HISTORY_BUF_RLE_SYMBOL_INDEX)
selector_rle_count = selector_syms[slice_index][cur_selector_sym_ofs++];
total_selector_bits += coder.put_code(selector_sym_index, selector_model);
if (selector_sym_index == SELECTOR_HISTORY_BUF_RLE_SYMBOL_INDEX)
{
int run_sym = selector_rle_count - basist::SELECTOR_HISTORY_BUF_RLE_COUNT_THRESH;
if (run_sym >= ((int)basist::SELECTOR_HISTORY_BUF_RLE_COUNT_TOTAL - 1))
{
total_selector_bits += coder.put_code(basist::SELECTOR_HISTORY_BUF_RLE_COUNT_TOTAL - 1, selector_history_buf_rle_model);
uint32_t n = selector_rle_count - basist::SELECTOR_HISTORY_BUF_RLE_COUNT_THRESH;
total_selector_bits += coder.put_vlc(n, 7);
}
else
total_selector_bits += coder.put_code(run_sym, selector_history_buf_rle_model);
}
}
if (selector_rle_count)
selector_rle_count--;
}
} // block_x
@@ -1471,9 +1642,11 @@ namespace basisu
uint32_t basisu_backend::encode()
{
const bool is_video = m_pFront_end->get_params().m_tex_type == basist::cBASISTexTypeVideoFrames;
m_output.m_slice_desc = m_slices;
m_output.m_etc1s = m_params.m_etc1s;
create_endpoint_palette();
create_selector_palette();

View File

@@ -36,6 +36,8 @@ namespace basisu
int m_selector_history_buf_index;
bool m_is_cr_target;
void clear()
{
m_endpoint_predictor = 0;
@@ -44,6 +46,8 @@ namespace basisu
m_selector_index = 0;
m_selector_history_buf_index = 0;
m_is_cr_target = false;
}
};
@@ -103,6 +107,16 @@ namespace basisu
struct basisu_backend_slice_desc
{
basisu_backend_slice_desc()
{
clear();
}
void clear()
{
clear_obj(*this);
}
uint32_t m_first_block_index;
uint32_t m_orig_width;
@@ -119,7 +133,9 @@ namespace basisu
uint32_t m_source_file_index; // also the basis image index
uint32_t m_mip_index;
bool m_alpha;
bool m_iframe;
};
typedef std::vector<basisu_backend_slice_desc> basisu_backend_slice_desc_vec;
@@ -306,6 +322,8 @@ namespace basisu
bool encode_image();
bool encode_endpoint_palette();
bool encode_selector_palette();
int find_video_frame(int slice_index, int delta);
void check_for_valid_cr_blocks();
};
} // namespace basisu

View File

@@ -86,6 +86,9 @@ namespace basisu
if (slice_descs[i].m_alpha)
m_images_descs[i].m_flags = m_images_descs[i].m_flags | basist::cSliceDescFlagsIsAlphaData;
if (slice_descs[i].m_iframe)
m_images_descs[i].m_flags = m_images_descs[i].m_flags | basist::cSliceDescFlagsFrameIsIFrame;
m_images_descs[i].m_orig_width = slice_descs[i].m_orig_width;
m_images_descs[i].m_orig_height = slice_descs[i].m_orig_height;

View File

@@ -17,7 +17,8 @@
#include <unordered_set>
#define BASISU_USE_STB_IMAGE_RESIZE_FOR_MIPMAP_GEN 0
#define DEBUG_RESIZE_TEXTURE_TO_64x64 (0)
#define DEBUG_CROP_TEXTURE_TO_64x64 (0)
#define DEBUG_RESIZE_TEXTURE (0)
#define DEBUG_EXTRACT_SINGLE_BLOCK (0)
namespace basisu
@@ -301,8 +302,6 @@ namespace basisu
if (has_alpha)
m_any_source_image_has_alpha = true;
debug_printf("Source image index %u filename %s %ux%u has alpha: %u\n", source_file_index, pSource_filename, file_image.get_width(), file_image.get_height(), has_alpha);
if (m_params.m_y_flip)
file_image.flip_y();
@@ -315,10 +314,18 @@ namespace basisu
file_image = block_image;
#endif
#if DEBUG_RESIZE_TEXTURE_TO_64x64
#if DEBUG_CROP_TEXTURE_TO_64x64
file_image.resize(64, 64);
#endif
#if DEBUG_RESIZE_TEXTURE
image temp_img((file_image.get_width() + 3) / 4, (file_image.get_height() + 3) / 4);
image_resample(file_image, temp_img, m_params.m_perceptual, "kaiser");
temp_img.swap(file_image);
#endif
debug_printf("Source image index %u filename %s %ux%u has alpha: %u\n", source_file_index, pSource_filename, file_image.get_width(), file_image.get_height(), has_alpha);
if ((!file_image.get_width()) || (!file_image.get_height()))
{
error_printf("basis_compressor::read_source_images: Source image has a zero width and/or height!\n");
@@ -445,6 +452,15 @@ namespace basisu
slice_desc.m_mip_index = mip_indices[slice_index];
slice_desc.m_alpha = is_alpha_slice;
slice_desc.m_iframe = false;
if (m_params.m_tex_type == basist::cBASISTexTypeVideoFrames)
{
// TODO: For now, only the first frame is an i-frame in videos.
// We should allow the caller to specify the i-frame frequency, for fast seeking.
slice_desc.m_iframe = (source_file_index == 0);
}
m_total_blocks += slice_desc.m_num_blocks_x * slice_desc.m_num_blocks_y;
total_macroblocks += slice_desc.m_num_macroblocks_x * slice_desc.m_num_macroblocks_y;
@@ -488,8 +504,8 @@ namespace basisu
{
const basisu_backend_slice_desc &slice_desc = m_slice_descs[i];
printf("Slice: %u, alpha: %u, orig width/height: %ux%u, width/height: %ux%u, first_block: %u, image_index: %u, mip_level: %u\n",
i, slice_desc.m_alpha, slice_desc.m_orig_width, slice_desc.m_orig_height, slice_desc.m_width, slice_desc.m_height, slice_desc.m_first_block_index, slice_desc.m_source_file_index, slice_desc.m_mip_index);
printf("Slice: %u, alpha: %u, orig width/height: %ux%u, width/height: %ux%u, first_block: %u, image_index: %u, mip_level: %u, iframe: %u\n",
i, slice_desc.m_alpha, slice_desc.m_orig_width, slice_desc.m_orig_height, slice_desc.m_width, slice_desc.m_height, slice_desc.m_first_block_index, slice_desc.m_source_file_index, slice_desc.m_mip_index, slice_desc.m_iframe);
if (m_any_source_image_has_alpha)
{
@@ -523,6 +539,13 @@ namespace basisu
if ((slice_desc.m_orig_width > slice_desc.m_width) || (slice_desc.m_orig_height > slice_desc.m_height))
return false;
if ((slice_desc.m_source_file_index == 0) && (m_params.m_tex_type == basist::cBASISTexTypeVideoFrames))
{
// First image in the basis video file must be an i-frame.
if (!slice_desc.m_iframe)
return false;
}
}
return true;
@@ -780,6 +803,7 @@ namespace basisu
p.m_debug_stats = m_params.m_debug;
p.m_debug_images = m_params.m_debug_images;
p.m_faster = m_params.m_faster;
p.m_tex_type = m_params.m_tex_type;
if ((m_params.m_global_sel_pal) || (m_auto_global_sel_pal))
{

View File

@@ -17,6 +17,7 @@
#include "basisu_etc.h"
#include "basisu_gpu_texture.h"
#include "basisu_global_selector_palette_helpers.h"
#include "transcoder/basisu_file_headers.h"
namespace basisu
{
@@ -70,7 +71,8 @@ namespace basisu
m_num_global_sel_codebook_mod_bits(0),
m_use_hybrid_selector_codebooks(false),
m_hybrid_codebook_quality_thresh(0.0f),
m_validate(false)
m_validate(false),
m_tex_type(basist::cBASISTexType2D)
{
}
@@ -93,6 +95,8 @@ namespace basisu
uint32_t m_num_global_sel_codebook_mod_bits;
bool m_use_hybrid_selector_codebooks;
float m_hybrid_codebook_quality_thresh;
basist::basis_texture_type m_tex_type;
};
bool init(const params &p);

View File

@@ -30,7 +30,7 @@
using namespace basisu;
#define BASISU_TOOL_VERSION "1.05.00"
#define BASISU_TOOL_VERSION "1.06.00"
enum tool_mode
{
@@ -71,8 +71,9 @@ static void print_usage()
" -stats: Compute and display image quality metrics (slightly slower).\n"
" -slower: Enable optional stages in the compressor for slower but higher quality compression using better codebooks.\n"
" -tex_type <2d, 2darray, 3d, video, cubemap>: Set Basis file header's texture type field. Cubemap arrays require multiples of 6 images, in X+, X-, Y+, Y-, Z+, Z- order, each image must be the same resolutions.\n"
" 2d=arbitrary 2D images, 2darray=2D array, 3D=volume texture slices, video=video frames, cubemap=array of faces. For 2darray/3d/cubemaps/video, each source image's dimensions and # of mipmap levels must be the same.\n"
" -framerate X: Set framerate in header to X/frames sec\n"
" (2d=arbitrary 2D images, 2darray=2D array, 3D=volume texture slices, video=video frames, cubemap=array of faces. For 2darray/3d/cubemaps/video, each source image's dimensions and # of mipmap levels must be the same.)\n"
" For video, the .basis file will be written with the first frame being an I-Frame, and subsequent frames being P-Frames (using conditional replenishment). Playback must always occur in order from first to last image.\n"
" -framerate X: Set framerate in header to X/frames sec.\n"
" -individual: Process input images individually and output multiple .basis files (not as a texture array)\n"
" -fuzz_testing: Use with -validate: Disables CRC16 validation of file contents before transcoding\n"
"\n"
@@ -124,8 +125,12 @@ static void print_usage()
"basisu -q 255 -file x.png -mipmap -debug -stats : Compress sRGB x.png to x.basis at quality level 255 with compressor debug output/statistics\n"
"basisu -linear -max_endpoints 16128 -max_selectors 16128 -file x.png : Compress non-sRGB x.png to x.basis using the largest supported manually specified codebook sizes\n"
"basisu -linear -global_sel_pal -no_hybrid_sel_cb -file x.png : Compress a non-sRGB image, use virtual selector codebooks for improved compression (but slower encoding)\n"
"basisu -linear -global_sel_pal -file x.png: Compress a non-sRGB image, use hybrid selector codebooks for slightly improved compression (but slower encoding)\n"
"basisu -tex_type video -framerate 20 -multifile_printf \"x%02u.png\" -multifile_first 1 -multifile_count 20 : Compress a 20 sRGB source image video sequence (x01.png, x02.png, x03.png, etc.) to x01.basis\n"
"basisu -linear -global_sel_pal -file x.png : Compress a non-sRGB image, use hybrid selector codebooks for slightly improved compression (but slower encoding)\n"
"basisu -tex_type video -framerate 20 -slower -multifile_printf \"x%02u.png\" -multifile_first 1 -multifile_count 99 : Compress a 99 frame sRGB source image video sequence (x01.png, x02.png, x03.png, etc.) to x01.basis\n"
"\n"
"Note: For video use, it's recommended you use a very powerful machine with many cores. Use -slower for better codebook generation, specify very large codebooks using -max_endpoints and -max_selectors, and reduce\n"
"the default endpoint RDO threshold (-endpoint_rdo_thresh) to around 1.25. Videos may have mipmaps and alpha channels. Videos must always be played back by the transcoder in first to last image order.\n"
"Video files currently use I-Frames on the first image, and P-Frames using conditional replenishment on subsequent frames.\n"
);
}
@@ -876,20 +881,20 @@ static bool unpack_and_validate_mode(command_line_params &opts, bool validate_fl
}
// Now transcode the file to all supported texture formats and save mipmapped KTX files
for (uint32_t image_index = 0; image_index < fileinfo.m_total_images; image_index++)
for (int format_iter = 0; format_iter < basist::cTFTotalTextureFormats; format_iter++)
{
for (uint32_t level_index = 0; level_index < fileinfo.m_image_mipmap_levels[image_index]; level_index++)
for (uint32_t image_index = 0; image_index < fileinfo.m_total_images; image_index++)
{
basist::basisu_image_level_info level_info;
if (!dec.get_image_level_info(&basis_data[0], (uint32_t)basis_data.size(), level_info, image_index, level_index))
for (uint32_t level_index = 0; level_index < fileinfo.m_image_mipmap_levels[image_index]; level_index++)
{
error_printf("Failed retrieving image level information (%u %u)!\n", image_index, level_index);
return false;
}
basist::basisu_image_level_info level_info;
for (int format_iter = 0; format_iter < basist::cTFTotalTextureFormats; format_iter++)
{
if (!dec.get_image_level_info(&basis_data[0], (uint32_t)basis_data.size(), level_info, image_index, level_index))
{
error_printf("Failed retrieving image level information (%u %u)!\n", image_index, level_index);
return false;
}
const basist::transcoder_texture_format transcoder_tex_fmt = static_cast<basist::transcoder_texture_format>(format_iter);
if (transcoder_tex_fmt == basist::cTFPVRTC1_4_OPAQUE_ONLY)
@@ -968,7 +973,7 @@ static bool unpack_and_validate_mode(command_line_params &opts, bool validate_fl
if ((!opts.m_no_ktx) && (fileinfo.m_tex_type != basist::cBASISTexTypeCubemapArray))
{
std::string ktx_filename(base_filename + string_format("_transcoded_%s_%u.ktx", basist::basis_get_format_name(transcoder_tex_fmt), image_index));
std::string ktx_filename(base_filename + string_format("_transcoded_%s_%04u.ktx", basist::basis_get_format_name(transcoder_tex_fmt), image_index));
if (!write_compressed_texture_file(ktx_filename.c_str(), gi))
{
error_printf("Failed writing KTX file \"%s\"!\n", ktx_filename.c_str());
@@ -995,7 +1000,12 @@ static bool unpack_and_validate_mode(command_line_params &opts, bool validate_fl
}
//u.crop(level_info.m_orig_width, level_info.m_orig_height);
std::string rgb_filename(base_filename + string_format("_unpacked_rgb_%s_%u_%u.png", basist::basis_get_format_name(transcoder_tex_fmt), image_index, level_index));
std::string rgb_filename;
if (gi.size() > 1)
rgb_filename = base_filename + string_format("_unpacked_rgb_%s_%u_%04u.png", basist::basis_get_format_name(transcoder_tex_fmt), level_index, image_index);
else
rgb_filename = base_filename + string_format("_unpacked_rgb_%s_%04u.png", basist::basis_get_format_name(transcoder_tex_fmt), image_index);
if (!save_png(rgb_filename, u, cImageSaveIgnoreAlpha))
{
error_printf("Failed writing to PNG file \"%s\"\n", rgb_filename.c_str());
@@ -1005,7 +1015,12 @@ static bool unpack_and_validate_mode(command_line_params &opts, bool validate_fl
if (basis_transcoder_format_has_alpha(transcoder_tex_fmt))
{
std::string a_filename(base_filename + string_format("_unpacked_a_%s_%u_%u.png", basist::basis_get_format_name(transcoder_tex_fmt), image_index, level_index));
std::string a_filename;
if (gi.size() > 1)
a_filename = base_filename + string_format("_unpacked_a_%s_%u_%04u.png", basist::basis_get_format_name(transcoder_tex_fmt), level_index, image_index);
else
a_filename = base_filename + string_format("_unpacked_a_%s_%04u.png", basist::basis_get_format_name(transcoder_tex_fmt), image_index);
if (!save_png(a_filename, u, cImageSaveGrayscale, 3))
{
error_printf("Failed writing to PNG file \"%s\"\n", a_filename.c_str());

View File

@@ -20,7 +20,8 @@ namespace basist
// Slice desc header flags
enum basis_slice_desc_flags
{
cSliceDescFlagsIsAlphaData = 1,
cSliceDescFlagsIsAlphaData = 1, // Slice has the image's alpha data
cSliceDescFlagsFrameIsIFrame = 2 // Video only: Frame doesn't refer to previous frame (no usage of conditional replenishment pred symbols)
};
#pragma pack(push)

View File

@@ -3669,10 +3669,30 @@ namespace basist
}
bool basisu_lowlevel_transcoder::transcode_slice(void *pDst_blocks, uint32_t num_blocks_x, uint32_t num_blocks_y, const uint8_t *pImage_data, uint32_t image_data_size, block_format fmt,
uint32_t output_stride, bool pvrtc_wrap_addressing, bool bc1_allow_threecolor_blocks)
uint32_t output_stride, bool pvrtc_wrap_addressing, bool bc1_allow_threecolor_blocks, const basis_file_header &header, const basis_slice_desc &slice_desc)
{
const bool is_video = (header.m_tex_type == cBASISTexTypeVideoFrames);
const uint32_t total_blocks = num_blocks_x * num_blocks_y;
std::vector<uint32_t>* pPrevFrameIndices = nullptr;
if (is_video)
{
// TODO: Add check to make sure the caller hasn't tried skipping past p-frames
const bool alpha_flag = (slice_desc.m_flags & cSliceDescFlagsIsAlphaData) != 0;
const uint32_t level_index = slice_desc.m_level_index;
if (level_index >= cMaxPrevFrameLevels)
{
assert(0);
return false;
}
pPrevFrameIndices = &m_prev_frame_indices[alpha_flag][level_index];
if (pPrevFrameIndices->size() < total_blocks)
pPrevFrameIndices->resize(total_blocks);
}
basist::bitwise_decoder sym_codec;
if (!sym_codec.init(pImage_data, image_data_size))
@@ -3683,8 +3703,6 @@ namespace basist
approx_move_to_front selector_history_buf(m_selector_history_buf_size);
int prev_selector_index = 0;
const uint32_t SELECTOR_HISTORY_BUF_FIRST_SYMBOL_INDEX = (uint32_t)m_selectors.size();
const uint32_t SELECTOR_HISTORY_BUF_RLE_SYMBOL_INDEX = m_selector_history_buf_size + SELECTOR_HISTORY_BUF_FIRST_SYMBOL_INDEX;
uint32_t cur_selector_rle_count = 0;
@@ -3759,7 +3777,7 @@ namespace basist
}
// Decode endpoint index
uint32_t endpoint_index;
uint32_t endpoint_index, selector_index = 0;
const uint32_t pred = cur_pred_bits & 3;
cur_pred_bits >>= 2;
@@ -3792,16 +3810,29 @@ namespace basist
}
else if (pred == 2)
{
// Upper left
if ((!block_x) || (!block_y))
if (is_video)
{
BASISU_DEVEL_ERROR("basisu_lowlevel_transcoder::transcode_slice: invalid datastream (2)\n");
if (pPVRTC_work_mem)
free(pPVRTC_work_mem);
return false;
}
assert(pred == CR_ENDPOINT_PRED_INDEX);
endpoint_index = m_block_endpoint_preds[cur_block_endpoint_pred_array ^ 1][block_x - 1].m_endpoint_index;
// Conditional replenishment
endpoint_index = (*pPrevFrameIndices)[block_x + block_y * num_blocks_x];
selector_index = endpoint_index >> 16;
endpoint_index &= 0xFFFFU;
}
else
{
// Upper left
if ((!block_x) || (!block_y))
{
BASISU_DEVEL_ERROR("basisu_lowlevel_transcoder::transcode_slice: invalid datastream (2)\n");
if (pPVRTC_work_mem)
free(pPVRTC_work_mem);
return false;
}
endpoint_index = m_block_endpoint_preds[cur_block_endpoint_pred_array ^ 1][block_x - 1].m_endpoint_index;
}
}
else
{
@@ -3818,71 +3849,71 @@ namespace basist
prev_endpoint_index = endpoint_index;
// Decode selector index
uint32_t selector_index;
int selector_sym;
if (cur_selector_rle_count > 0)
if ((!is_video) || (pred != CR_ENDPOINT_PRED_INDEX))
{
cur_selector_rle_count--;
selector_sym = (int)m_selectors.size();
}
else
{
selector_sym = sym_codec.decode_huffman(m_selector_model);
if (selector_sym == static_cast<int>(SELECTOR_HISTORY_BUF_RLE_SYMBOL_INDEX))
int selector_sym;
if (cur_selector_rle_count > 0)
{
int run_sym = sym_codec.decode_huffman(m_selector_history_buf_rle_model);
cur_selector_rle_count--;
if (run_sym == (SELECTOR_HISTORY_BUF_RLE_COUNT_TOTAL - 1))
cur_selector_rle_count = sym_codec.decode_vlc(7) + SELECTOR_HISTORY_BUF_RLE_COUNT_THRESH;
else
cur_selector_rle_count = run_sym + SELECTOR_HISTORY_BUF_RLE_COUNT_THRESH;
selector_sym = (int)m_selectors.size();
}
else
{
selector_sym = sym_codec.decode_huffman(m_selector_model);
if (cur_selector_rle_count > total_blocks)
if (selector_sym == static_cast<int>(SELECTOR_HISTORY_BUF_RLE_SYMBOL_INDEX))
{
int run_sym = sym_codec.decode_huffman(m_selector_history_buf_rle_model);
if (run_sym == (SELECTOR_HISTORY_BUF_RLE_COUNT_TOTAL - 1))
cur_selector_rle_count = sym_codec.decode_vlc(7) + SELECTOR_HISTORY_BUF_RLE_COUNT_THRESH;
else
cur_selector_rle_count = run_sym + SELECTOR_HISTORY_BUF_RLE_COUNT_THRESH;
if (cur_selector_rle_count > total_blocks)
{
// The file is corrupted or we've got a bug.
BASISU_DEVEL_ERROR("basisu_lowlevel_transcoder::transcode_slice: invalid datastream (3)\n");
if (pPVRTC_work_mem)
free(pPVRTC_work_mem);
return false;
}
selector_sym = (int)m_selectors.size();
cur_selector_rle_count--;
}
}
if (selector_sym >= (int)m_selectors.size())
{
assert(m_selector_history_buf_size > 0);
int history_buf_index = selector_sym - (int)m_selectors.size();
if (history_buf_index >= (int)selector_history_buf.size())
{
// The file is corrupted or we've got a bug.
BASISU_DEVEL_ERROR("basisu_lowlevel_transcoder::transcode_slice: invalid datastream (3)\n");
BASISU_DEVEL_ERROR("basisu_lowlevel_transcoder::transcode_slice: invalid datastream (4)\n");
if (pPVRTC_work_mem)
free(pPVRTC_work_mem);
return false;
}
selector_sym = (int)m_selectors.size();
selector_index = selector_history_buf[history_buf_index];
cur_selector_rle_count--;
if (history_buf_index != 0)
selector_history_buf.use(history_buf_index);
}
}
if (selector_sym >= (int)m_selectors.size())
{
assert(m_selector_history_buf_size > 0);
int history_buf_index = selector_sym - (int)m_selectors.size();
if (history_buf_index >= (int)selector_history_buf.size())
else
{
// The file is corrupted or we've got a bug.
BASISU_DEVEL_ERROR("basisu_lowlevel_transcoder::transcode_slice: invalid datastream (4)\n");
if (pPVRTC_work_mem)
free(pPVRTC_work_mem);
return false;
selector_index = selector_sym;
if (m_selector_history_buf_size)
selector_history_buf.add(selector_index);
}
selector_index = selector_history_buf[history_buf_index];
if (history_buf_index != 0)
selector_history_buf.use(history_buf_index);
}
else
{
selector_index = selector_sym;
if (m_selector_history_buf_size)
selector_history_buf.add(selector_index);
}
prev_selector_index = selector_index;
if ((endpoint_index >= m_endpoints.size()) || (selector_index >= m_selectors.size()))
{
@@ -3893,6 +3924,19 @@ namespace basist
return false;
}
if (is_video)
(*pPrevFrameIndices)[block_x + block_y * num_blocks_x] = endpoint_index | (selector_index << 16);
#if 0
// Visualize CR's
if ((is_video) && (pred == 2))
{
decoder_etc_block* pDst_block = reinterpret_cast<decoder_etc_block*>(static_cast<uint8_t*>(pDst_blocks) + (block_x + block_y * num_blocks_x) * output_stride);
memset(pDst_block, 0xFF, 8);
continue;
}
#endif
const endpoint *pEndpoint0 = &m_endpoints[endpoint_index];
block.set_base5_color(decoder_etc_block::pack_color5(pEndpoint0->m_color5, false));
@@ -4005,7 +4049,7 @@ namespace basist
} // block_x
} // block-y
} // block_y
if (endpoint_pred_repeat_count != 0)
{
@@ -4604,7 +4648,7 @@ namespace basist
return m_lowlevel_decoder.transcode_slice(pOutput_blocks, slice_desc.m_num_blocks_x, slice_desc.m_num_blocks_y,
pDataU8 + slice_desc.m_file_ofs, slice_desc.m_file_size,
fmt, output_stride, (decode_flags & cDecodeFlagsPVRTCWrapAddressing) != 0, (decode_flags & cDecodeFlagsBC1ForbidThreeColorBlocks) == 0);
fmt, output_stride, (decode_flags & cDecodeFlagsPVRTCWrapAddressing) != 0, (decode_flags & cDecodeFlagsBC1ForbidThreeColorBlocks) == 0, *pHeader, slice_desc);
}
int basisu_transcoder::find_first_slice_index(const void *pData, uint32_t data_size, uint32_t image_index, uint32_t level_index) const

View File

@@ -73,7 +73,8 @@ namespace basist
bool decode_tables(const uint8_t *pTable_data, uint32_t table_data_size);
bool transcode_slice(void *pDst_blocks, uint32_t num_blocks_x, uint32_t num_blocks_y, const uint8_t *pImage_data, uint32_t image_data_size, block_format fmt, uint32_t output_stride, bool wrap_addressing, bool bc1_allow_threecolor_blocks);
bool transcode_slice(void *pDst_blocks, uint32_t num_blocks_x, uint32_t num_blocks_y, const uint8_t *pImage_data, uint32_t image_data_size, block_format fmt, uint32_t output_stride,
bool wrap_addressing, bool bc1_allow_threecolor_blocks, const basis_file_header &header, const basis_slice_desc& slice_desc);
private:
struct endpoint
@@ -101,6 +102,10 @@ namespace basist
};
std::vector<block_preds> m_block_endpoint_preds[2];
// Only used when decoding videos, for conditional replenishment.
enum { cMaxPrevFrameLevels = 16 };
std::vector<uint32_t> m_prev_frame_indices[2][cMaxPrevFrameLevels]; // [alpha_flag][level_index]
};
struct basisu_slice_info

View File

@@ -45,6 +45,10 @@ namespace basist
const uint32_t ENDPOINT_PRED_MIN_REPEAT_COUNT = 3;
const uint32_t ENDPOINT_PRED_COUNT_VLC_BITS = 4;
const uint32_t NUM_ENDPOINT_PREDS = 3;// BASISU_ARRAY_SIZE(g_endpoint_preds);
const uint32_t CR_ENDPOINT_PRED_INDEX = NUM_ENDPOINT_PREDS - 1;
const uint32_t NO_ENDPOINT_PRED_INDEX = 3;//NUM_ENDPOINT_PREDS;
const uint32_t MAX_SELECTOR_HISTORY_BUF_SIZE = 64;
const uint32_t SELECTOR_HISTORY_BUF_RLE_COUNT_THRESH = 3;
const uint32_t SELECTOR_HISTORY_BUF_RLE_COUNT_BITS = 6;