diff --git a/basisu_backend.cpp b/basisu_backend.cpp index ab5d0dc..a3fbad4 100644 --- a/basisu_backend.cpp +++ b/basisu_backend.cpp @@ -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(); diff --git a/basisu_backend.h b/basisu_backend.h index 3aeec1a..73ce4eb 100644 --- a/basisu_backend.h +++ b/basisu_backend.h @@ -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_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 diff --git a/basisu_basis_file.cpp b/basisu_basis_file.cpp index e4a73a0..204a74d 100644 --- a/basisu_basis_file.cpp +++ b/basisu_basis_file.cpp @@ -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; diff --git a/basisu_comp.cpp b/basisu_comp.cpp index 7e7bf91..f22685d 100644 --- a/basisu_comp.cpp +++ b/basisu_comp.cpp @@ -17,7 +17,8 @@ #include #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)) { diff --git a/basisu_frontend.h b/basisu_frontend.h index 07fcac1..708e789 100644 --- a/basisu_frontend.h +++ b/basisu_frontend.h @@ -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); diff --git a/basisu_tool.cpp b/basisu_tool.cpp index 57b68ff..0bde1f1 100644 --- a/basisu_tool.cpp +++ b/basisu_tool.cpp @@ -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(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()); diff --git a/transcoder/basisu_file_headers.h b/transcoder/basisu_file_headers.h index 124b7f8..28756dc 100644 --- a/transcoder/basisu_file_headers.h +++ b/transcoder/basisu_file_headers.h @@ -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) diff --git a/transcoder/basisu_transcoder.cpp b/transcoder/basisu_transcoder.cpp index 7b159d3..b67de84 100644 --- a/transcoder/basisu_transcoder.cpp +++ b/transcoder/basisu_transcoder.cpp @@ -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* 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(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(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(static_cast(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 diff --git a/transcoder/basisu_transcoder.h b/transcoder/basisu_transcoder.h index 5927f0a..6dc90ef 100644 --- a/transcoder/basisu_transcoder.h +++ b/transcoder/basisu_transcoder.h @@ -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 m_block_endpoint_preds[2]; + + // Only used when decoding videos, for conditional replenishment. + enum { cMaxPrevFrameLevels = 16 }; + std::vector m_prev_frame_indices[2][cMaxPrevFrameLevels]; // [alpha_flag][level_index] }; struct basisu_slice_info diff --git a/transcoder/basisu_transcoder_internal.h b/transcoder/basisu_transcoder_internal.h index d4d210e..8a4674a 100644 --- a/transcoder/basisu_transcoder_internal.h +++ b/transcoder/basisu_transcoder_internal.h @@ -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;