rtc: Copy inactive superblocks with whole rows In the nonrd path a skip segment superblock is coded as one GLOBALMV block with a zero motion vector, so its prediction is a plain copy of the reference. encode_superblock() built it block by block and plane by plane through av1_enc_build_inter_predictor(). On screen recordings with a mostly inactive map, that copy was the top encoder function in profiles and took 10 to 30 percent of the single thread encode time. Copying tall, narrow blocks is slow on memory: a 1440p I420 frame takes 0.62 ms in 64x64 blocks and 0.25 ms in whole rows. When a superblock row starts, copy each run of skip segment superblocks from its reference with whole rows. encode_superblock() then skips the prediction build for these blocks. A block that is not a zero motion vector copy of the same reference still builds its prediction over the copy. The copy is limited to 8 bit buffers and to unscaled references with identity global motion. Frames without a skip segment, like cyclic refresh frames, skip the row pass. The output does not change. Two 2560x1440 screen recordings, speed 11, screen content, an active map from frame differences, Apple M3 Pro: encoder cycles per frame drop by 6 to 18 percent with 1 thread and by 4 to 12 percent with 4 threads and row mt. Change-Id: I9f7070aed11927a0d9aae4ab851bafaaadcc3563
diff --git a/av1/encoder/block.h b/av1/encoder/block.h index bf70610..a932715 100644 --- a/av1/encoder/block.h +++ b/av1/encoder/block.h
@@ -1382,6 +1382,9 @@ //! Flag to indicate if a fixed partition should be used, only if the // speed feature rt_sf->use_fast_fixed_part is enabled. int sb_force_fixed_part; + //! Reference already copied into the current superblock when its row + // started, or NONE_FRAME. Only set for skip segment superblocks. + MV_REFERENCE_FRAME sb_copied_ref; //! SSE of the current predictor. unsigned int pred_sse[REF_FRAMES]; //! Prediction for ML based partition.
diff --git a/av1/encoder/encodeframe.c b/av1/encoder/encodeframe.c index da81788..3a53e683 100644 --- a/av1/encoder/encodeframe.c +++ b/av1/encoder/encodeframe.c
@@ -17,6 +17,7 @@ #include "config/aom_config.h" #include "config/aom_dsp_rtcd.h" +#include "config/aom_scale_rtcd.h" #include "config/av1_rtcd.h" #include "aom_dsp/aom_dsp_common.h" @@ -1204,6 +1205,107 @@ av1_source_content_sb(cpi, x, tile_data, mi_row, mi_col); } +static uint8_t get_sb_segment_id(const AV1_COMP *cpi, int mi_row, int mi_col) { + const AV1_COMMON *const cm = &cpi->common; + const struct segmentation *const seg = &cm->seg; + const uint8_t *const map = + seg->update_map ? cpi->enc_seg.map : cm->last_frame_seg_map; + if (!map) return 0; + return get_segment_id(&cm->mi_params, map, cm->seq_params->sb_size, mi_row, + mi_col); +} + +// Returns true when skip segment superblocks of this frame may be copied from +// their reference before they are coded. +static bool can_copy_skip_sbs(const AV1_COMP *cpi) { + const AV1_COMMON *const cm = &cpi->common; + if (!cm->seg.enabled || frame_is_intra_only(cm) || + !cpi->sf.rt_sf.use_nonrd_pick_mode) + return false; + + // Segmentation without a skip segment, like cyclic refresh, has nothing to + // copy, so this saves the segment lookups of the whole frame. + for (int i = 0; i < MAX_SEGMENTS; ++i) { + if (segfeature_active(&cm->seg, i, SEG_LVL_SKIP)) return true; + } + return false; +} + +// Returns the reference that a superblock in this segment predicts from with +// a plain copy, or NONE_FRAME when it needs the normal prediction build. +static MV_REFERENCE_FRAME get_skip_sb_copy_ref(const AV1_COMMON *cm, + uint8_t segment_id) { + const struct segmentation *const seg = &cm->seg; + if (!segfeature_active(seg, segment_id, SEG_LVL_SKIP)) return NONE_FRAME; + + // Same reference as av1_nonrd_pick_inter_mode_sb_seg_skip(). + const MV_REFERENCE_FRAME ref = + segfeature_active(seg, segment_id, SEG_LVL_REF_FRAME) + ? (MV_REFERENCE_FRAME)get_segdata(seg, segment_id, SEG_LVL_REF_FRAME) + : LAST_FRAME; + if (ref <= INTRA_FRAME || cm->global_motion[ref].wmtype != IDENTITY) + return NONE_FRAME; + + const YV12_BUFFER_CONFIG *const ref_buf = get_ref_frame_yv12_buf(cm, ref); + if (!ref_buf || (ref_buf->flags & YV12_FLAG_HIGHBITDEPTH) || + av1_is_scaled(get_ref_scale_factors_const(cm, ref))) + return NONE_FRAME; + return ref; +} + +// Copies the reference into the superblocks from mi_col_start to mi_col_end +// of one superblock row, inside the frame area. +static void copy_sb_span(const AV1_COMMON *cm, MV_REFERENCE_FRAME ref, + int mi_row, int mi_col_start, int mi_col_end) { + const YV12_BUFFER_CONFIG *const src = get_ref_frame_yv12_buf(cm, ref); + YV12_BUFFER_CONFIG *const dst = &cm->cur_frame->buf; + const int x0 = mi_col_start * MI_SIZE; + const int x1 = AOMMIN(mi_col_end, cm->mi_params.mi_cols) * MI_SIZE; + const int y0 = mi_row * MI_SIZE; + const int y1 = + AOMMIN(mi_row + cm->seq_params->mib_size, cm->mi_params.mi_rows) * + MI_SIZE; + aom_yv12_partial_coloc_copy_y(src, dst, x0, x1, y0, y1); + if (av1_num_planes(cm) == 1) return; + + const int ss_x = cm->seq_params->subsampling_x; + const int ss_y = cm->seq_params->subsampling_y; + aom_yv12_partial_coloc_copy_u(src, dst, x0 >> ss_x, x1 >> ss_x, y0 >> ss_y, + y1 >> ss_y); + aom_yv12_partial_coloc_copy_v(src, dst, x0 >> ss_x, x1 >> ss_x, y0 >> ss_y, + y1 >> ss_y); +} + +// Fills the skip segment superblocks of a row from their reference before the +// row is coded. Wide row copies are much faster than copying every 64x64 block +// plane by plane, so encode_superblock() then skips their prediction. +static void copy_skip_sbs_of_row(const AV1_COMP *cpi, const TileInfo *tile_info, + int mi_row) { + if (!can_copy_skip_sbs(cpi)) return; + + const AV1_COMMON *const cm = &cpi->common; + const int mib_size = cm->seq_params->mib_size; + int span_start = -1; + MV_REFERENCE_FRAME span_ref = NONE_FRAME; + + for (int mi_col = tile_info->mi_col_start;; mi_col += mib_size) { + const int in_tile = mi_col < tile_info->mi_col_end; + const MV_REFERENCE_FRAME ref = + in_tile + ? get_skip_sb_copy_ref(cm, get_sb_segment_id(cpi, mi_row, mi_col)) + : NONE_FRAME; + if (span_start >= 0 && ref != span_ref) { + copy_sb_span(cm, span_ref, mi_row, span_start, mi_col); + span_start = -1; + } + if (span_start < 0 && ref != NONE_FRAME) { + span_start = mi_col; + span_ref = ref; + } + if (!in_tile) break; + } +} + /*!\brief Encode a superblock row by breaking it into superblocks * * \ingroup partition_search @@ -1247,6 +1349,7 @@ } reset_thresh_freq_fact(x); + copy_skip_sbs_of_row(cpi, tile_info, mi_row); // Code each SB in the row for (int mi_col = tile_info->mi_col_start, sb_col_in_tile = 0; @@ -1318,13 +1421,12 @@ // Get segment id and skip flag const struct segmentation *const seg = &cm->seg; int seg_skip = 0; + x->sb_copied_ref = NONE_FRAME; if (seg->enabled) { - const uint8_t *const map = - seg->update_map ? cpi->enc_seg.map : cm->last_frame_seg_map; - const uint8_t segment_id = - map ? get_segment_id(&cm->mi_params, map, sb_size, mi_row, mi_col) - : 0; + const uint8_t segment_id = get_sb_segment_id(cpi, mi_row, mi_col); seg_skip = segfeature_active(seg, segment_id, SEG_LVL_SKIP); + if (seg_skip && !frame_is_intra_only(cm) && use_nonrd_mode) + x->sb_copied_ref = get_skip_sb_copy_ref(cm, segment_id); } produce_gradients_for_sb(cpi, x, sb_size, mi_row, mi_col);
diff --git a/av1/encoder/partition_search.c b/av1/encoder/partition_search.c index 373116c..132871c 100644 --- a/av1/encoder/partition_search.c +++ b/av1/encoder/partition_search.c
@@ -391,6 +391,17 @@ } } +// Returns true when the pixels of this block already match its prediction, +// because its whole skip superblock was copied from the reference up front. +static bool is_prediction_copied(const MACROBLOCK *x, const MB_MODE_INFO *mbmi, + int seg_skip) { + if (!seg_skip || x->sb_copied_ref == NONE_FRAME) return false; + + return !has_second_ref(mbmi) && !is_interintra_pred(mbmi) && + mbmi->ref_frame[0] == x->sb_copied_ref && + mbmi->motion_mode == SIMPLE_TRANSLATION && mbmi->mv[0].as_int == 0; +} + static void encode_superblock(const AV1_COMP *const cpi, TileDataEnc *tile_data, ThreadData *td, TokenExtra **t, RUN_TYPE dry_run, BLOCK_SIZE bsize, int *rate) { @@ -473,8 +484,10 @@ !seg_skip && cm->seq_params->bit_depth == AOM_BITS_8) ? 1 : 0; - av1_enc_build_inter_predictor(cm, xd, mi_row, mi_col, NULL, bsize, - start_plane, av1_num_planes(cm) - 1); + if (!is_prediction_copied(x, mbmi, seg_skip)) { + av1_enc_build_inter_predictor(cm, xd, mi_row, mi_col, NULL, bsize, + start_plane, av1_num_planes(cm) - 1); + } if (mbmi->motion_mode == OBMC_CAUSAL) { assert(cpi->oxcf.motion_mode_cfg.enable_obmc); av1_build_obmc_inter_predictors_sb(cm, xd);