diff --git a/av2/encoder/motion_search_facade.c b/av2/encoder/motion_search_facade.c index 087631bcfb..905a06f261 100644 --- a/av2/encoder/motion_search_facade.c +++ b/av2/encoder/motion_search_facade.c @@ -382,7 +382,8 @@ void av2_single_motion_search(const AV2_COMP *const cpi, MACROBLOCK *x, switch (mbmi->motion_mode) { case SIMPLE_TRANSLATION: if (cpi->sf.mv_sf.subpel_search_type) { - const int try_second = second_best_mv.as_int != INVALID_MV && + const int try_second = !cpi->sf.mv_sf.skip_second_best_subpel && + second_best_mv.as_int != INVALID_MV && second_best_mv.as_int != best_mv->as_int; const int best_mv_var = mv_search_params->find_fractional_mv_step( xd, cm, &ms_params, subpel_start_mv, &best_mv->as_mv, &dis, diff --git a/av2/encoder/rdopt.c b/av2/encoder/rdopt.c index 955a4dbc74..e0289f3209 100644 --- a/av2/encoder/rdopt.c +++ b/av2/encoder/rdopt.c @@ -1428,6 +1428,72 @@ static int64_t handle_newmv(const AV2_COMP *const cpi, MACROBLOCK *const x, mode_info, &start_mv, &best_mv); } else { + // Predictive NEWMV reuse across the DRL. Two triggers select a match: + // Tier 1 (predict_repeated_newmv): match when reference MVs are + // within one full pel. + // Tier 2 (newmv_drl_search_limit): once ref_mv_idx reaches the limit, + // reuse the nearest searched result regardless of distance. + const int t1_active = cpi->sf.mv_sf.predict_repeated_newmv != 0; + const int drl_limit = cpi->sf.mv_sf.newmv_drl_search_limit; + const int t2_active = + drl_limit > 0 && mbmi->ref_mv_idx[ref_idx] >= drl_limit; + if ((t1_active || t2_active) && !mbmi->use_amvd && + mbmi->ref_mv_idx[ref_idx] > 0) { + const MV ref_mv = av2_get_ref_mv(x, ref_idx).as_mv; + int near_diff = INT_MAX; + int near_idx = -1; + for (int idx = 0; idx < mbmi->ref_mv_idx[ref_idx]; ++idx) { + if (!args->single_newmv_valid[pb_mv_precision][idx][refs[0]]) + continue; + const MV prev_ref_mv = + av2_get_ref_mv_from_stack(ref_idx, mbmi->ref_frame, idx, + x->mbmi_ext, mbmi) + .as_mv; + const int ref_mv_diff = AVMMAX(abs(ref_mv.row - prev_ref_mv.row), + abs(ref_mv.col - prev_ref_mv.col)); + if (ref_mv_diff < near_diff) { + near_diff = ref_mv_diff; + near_idx = idx; + } + } + int best_match = -1; + // Reuse the nearest previously-searched ref-mv when Tier 1 (within + // one full pel -- (1 << 3) in 1/8-pel units) or Tier 2 (no distance + // cap) fires. + if (near_idx >= 0 && + ((t1_active && near_diff <= (1 << 3)) || t2_active)) { + best_match = near_idx; + } + if (best_match >= 0) { + const int_mv reuse_mv = + args->single_newmv[pb_mv_precision][best_match][refs[0]]; + SubpelMvLimits mv_limits; + av2_set_subpel_mv_search_range(&mv_limits, &x->mv_limits, &ref_mv, + pb_mv_precision); + const int is_adaptive_mvd = enable_adaptive_mvd_resolution(cm, mbmi); + // The reused MV was searched against a different reference MV, so + // check that its MVD relative to the *current* reference MV is + // representable at pb_mv_precision (otherwise the decoder would + // reconstruct a different MV -- bitstream mismatch). + MV reuse_mvd; + get_mvd_from_ref_mv(reuse_mv.as_mv, ref_mv, is_adaptive_mvd, + pb_mv_precision, &reuse_mvd); + if (av2_is_subpelmv_in_range(&mv_limits, reuse_mv.as_mv) && + is_this_mv_precision_compliant(reuse_mvd, pb_mv_precision)) { + *rate_mv = + av2_mv_bit_cost(&reuse_mv.as_mv, &ref_mv, pb_mv_precision, + &x->mv_costs, MV_COST_WEIGHT, is_adaptive_mvd); + const int cur_idx = get_ref_mv_idx(mbmi, 0); + args->single_newmv[pb_mv_precision][cur_idx][refs[0]] = reuse_mv; + args->single_newmv_rate[pb_mv_precision][cur_idx][refs[0]] = + *rate_mv; + args->single_newmv_valid[pb_mv_precision][cur_idx][refs[0]] = 1; + cur_mv[0].as_int = reuse_mv.as_int; + return 0; + } + } + } + int search_range = INT_MAX; if (cpi->sf.mv_sf.reduce_search_range && mbmi->ref_mv_idx[0] > 0) { const MV ref_mv = av2_get_ref_mv(x, ref_idx).as_mv; diff --git a/av2/encoder/speed_features.c b/av2/encoder/speed_features.c index 8f48156c7a..0bc28123cc 100644 --- a/av2/encoder/speed_features.c +++ b/av2/encoder/speed_features.c @@ -344,6 +344,17 @@ static void set_good_speed_features_framesize_independent( sf->tx_sf.adaptive_tx_partition_type_search_idx = 4; sf->inter_sf.prune_comp_search_by_single_result = boosted ? 2 : 1; + + // Skip the second-best full-pel candidate's subpel refinement in the + // single-ref NEWMV search. + sf->mv_sf.skip_second_best_subpel = 1; + + // Predictive single-ref NEWMV reuse across the DRL. + sf->mv_sf.predict_repeated_newmv = 1; + + // Cap the DRL depth for a fresh single-ref NEWMV search; reuse the + // nearest searched result beyond the cap. + sf->mv_sf.newmv_drl_search_limit = 2; } if (speed >= 2) { @@ -655,6 +666,9 @@ static AVM_INLINE void init_mv_sf(MV_SPEED_FEATURES *mv_sf) { mv_sf->exhaustive_searches_thresh = 0; mv_sf->prune_mesh_search = 0; mv_sf->reduce_search_range = 0; + mv_sf->skip_second_best_subpel = 0; + mv_sf->predict_repeated_newmv = 0; + mv_sf->newmv_drl_search_limit = 0; mv_sf->search_method = NSTEP; mv_sf->simple_motion_subpel_force_stop = EIGHTH_PEL; mv_sf->subpel_force_stop = EIGHTH_PEL; diff --git a/av2/encoder/speed_features.h b/av2/encoder/speed_features.h index 240939eb8d..1bcbbaf9d2 100644 --- a/av2/encoder/speed_features.h +++ b/av2/encoder/speed_features.h @@ -477,6 +477,21 @@ typedef struct MV_SPEED_FEATURES { // Reduce single motion search range based on MV result of prior ref_mv_idx. int reduce_search_range; + // When set, skip the second-best full-pel candidate's subpel refinement in + // the single-ref NEWMV search. + int skip_second_best_subpel; + + // Predictively reuse single-ref NEWMV results across the DRL. + // 0: off + // 1: reuse when reference MVs are within one full pel. + int predict_repeated_newmv; + + // Cap the DRL depth that runs a fresh single-ref NEWMV motion search; + // beyond the cap, reuse the nearest searched result. + // 0: off (no cap). + // K: search ref_mv_idx 0..K-1, reuse for ref_mv_idx >= K. + int newmv_drl_search_limit; + // Prune mesh search. int prune_mesh_search;