Commit 870508a1a3 for aom

commit 870508a1a31858b773bc53caf6b1fc9fa458ef8c
Author: Remya Prakasan <remya.prakasan@ittiam.com>
Date:   Tue Oct 13 00:02:07 2026 +0530

    Skip NEWMV based on modelRD after subpel search

    This CL introduces an intermediate level for the speed
    feature 'skip_newmv_in_drl' to enable pruning of NEWMV
    modes based on modelRD cost computed after subpel motion
    search. This level is enabled for speed >= 2 for non-boosted
    frames and tuned to make the pruning less aggressive for
    refmv indices < 2 for resolutions < 720p in speed 2.

    Encoder performance results averaged over all resolutions are
    as follows:
       Instruction Count          BD-Rate Loss(%)
    cpu   Reduction(%)   avg.psnr  ssim    vmaf   vmaf_neg  uvq_1p5
     2        1.937       0.0133   0.0123   0.047   -0.0086  0.1586
     3        0.828       0.0157   0.0121   0.0077   0.0194  0.1394
     4        0.569       0.0137   0.0113  -0.0257  -0.0146  0.287
     5        0.173       0.0018   0.0162  -0.0048   0.0051  0.0033
     6        0.123       0.0185   0.0119  -0.0034   0.0122  0.1728

    STATS_CHANGED for speed >= 2

    Change-Id: I47ab34080ae805d1b37121c0c9aac603e45e093d

diff --git a/av1/encoder/motion_search_facade.c b/av1/encoder/motion_search_facade.c
index f09c9112d9..dab1f13957 100644
--- a/av1/encoder/motion_search_facade.c
+++ b/av1/encoder/motion_search_facade.c
@@ -15,6 +15,7 @@
 #include "av1/encoder/encoder.h"
 #include "av1/encoder/interp_search.h"
 #include "av1/encoder/mcomp.h"
+#include "av1/encoder/model_rd.h"
 #include "av1/encoder/motion_search_facade.h"
 #include "av1/encoder/partition_strategy.h"
 #include "av1/encoder/reconinter_enc.h"
@@ -117,6 +118,15 @@ static inline void get_mv_candidate_from_tpl(const AV1_COMP *const cpi,
   }
 }

+static inline int64_t compute_modeled_rd(const AV1_COMP *const cpi,
+                                         const MACROBLOCK *x, BLOCK_SIZE bsize,
+                                         unsigned int sse, int rate_cost) {
+  int res_rate = 0;
+  int64_t dist = 0;
+  model_rd_from_sse(cpi, x, bsize, AOM_PLANE_Y, sse, 0, &res_rate, &dist);
+  return RDCOST(x->rdmult, res_rate + rate_cost, dist);
+}
+
 void av1_single_motion_search(const AV1_COMP *const cpi, MACROBLOCK *x,
                               BLOCK_SIZE bsize, int ref_idx, int *rate_mv,
                               int search_range, inter_mode_info *mode_info,
@@ -342,9 +352,9 @@ void av1_single_motion_search(const AV1_COMP *const cpi, MACROBLOCK *x,
   }

   // Terminate search with the current ref_idx based on fullpel mv, rate cost,
-  // and other know cost.
-  if (cpi->sf.inter_sf.skip_newmv_in_drl >= 2 &&
-      mbmi->motion_mode == SIMPLE_TRANSLATION &&
+  // and other known cost.
+  int skip_newmv_in_drl = cpi->sf.inter_sf.skip_newmv_in_drl;
+  if (skip_newmv_in_drl >= 4 && mbmi->motion_mode == SIMPLE_TRANSLATION &&
       best_mv->as_int != INVALID_MV) {
     int_mv this_mv;
     this_mv.as_mv = get_mv_from_fullmv(&best_mv->as_fullmv);
@@ -380,9 +390,8 @@ void av1_single_motion_search(const AV1_COMP *const cpi, MACROBLOCK *x,
       // drl_cost.
       const int psme = mode_info[prev_ref_idx].full_mv_bestsme;
       if (psme == INT_MAX) continue;
-      const int thr =
-          cpi->sf.inter_sf.skip_newmv_in_drl == 3 ? (psme + (psme >> 2)) : psme;
-      if (cpi->sf.inter_sf.skip_newmv_in_drl >= 3 &&
+      const int thr = skip_newmv_in_drl == 5 ? (psme + (psme >> 2)) : psme;
+      if (skip_newmv_in_drl >= 5 &&
           mode_info[ref_mv_idx].full_mv_bestsme > thr &&
           mode_info[prev_ref_idx].drl_cost < mode_info[ref_mv_idx].drl_cost) {
         best_mv->as_int = INVALID_MV;
@@ -496,9 +505,9 @@ void av1_single_motion_search(const AV1_COMP *const cpi, MACROBLOCK *x,
       default: assert(0 && "Invalid motion mode!\n");
     }

-    // Terminate search with the current ref_idx based on subpel mv and rate
-    // cost.
-    if (cpi->sf.inter_sf.skip_newmv_in_drl >= 1 && args != NULL &&
+    // Terminate search with the current ref_idx based on subpel mv, pred_sse,
+    // and rate cost.
+    if (skip_newmv_in_drl && args != NULL &&
         mbmi->motion_mode == SIMPLE_TRANSLATION &&
         best_mv->as_int != INVALID_MV) {
       const int ref_mv_idx = mbmi->ref_mv_idx;
@@ -507,29 +516,60 @@ void av1_single_motion_search(const AV1_COMP *const cpi, MACROBLOCK *x,
                           mv_costs->mv_cost_stack, MV_COST_WEIGHT);
       mv_rate_calculated = 1;

+      const unsigned int this_sse = x->pred_sse[ref];
+      mode_info[ref_mv_idx].pred_sse = this_sse;
+      mode_info[ref_mv_idx].model_rd = INT64_MAX;
+      const int this_rate_cost = best_mv_rate + mode_info[ref_mv_idx].drl_cost;
+      int64_t this_model_rd = INT64_MAX;
+
       for (int prev_ref_idx = 0; prev_ref_idx < ref_mv_idx; ++prev_ref_idx) {
         if (!args->single_newmv_valid[prev_ref_idx][ref]) continue;
-        // Check if the motion vectors are the same.
+
+        const int prev_rate_cost = args->single_newmv_rate[prev_ref_idx][ref] +
+                                   mode_info[prev_ref_idx].drl_cost;
+
+        // Case 1: If identical MVs, prune if worse/equal rate.
         if (best_mv->as_int == args->single_newmv[prev_ref_idx][ref].as_int) {
           // Skip this evaluation if the previous one is skipped.
           if (mode_info[prev_ref_idx].skip) {
             mode_info[ref_mv_idx].skip = 1;
             break;
           }
-          // Compare the rate cost that we current know.
-          const int prev_rate_cost =
-              args->single_newmv_rate[prev_ref_idx][ref] +
-              mode_info[prev_ref_idx].drl_cost;
-          const int this_rate_cost =
-              best_mv_rate + mode_info[ref_mv_idx].drl_cost;
-
           if (prev_rate_cost <= this_rate_cost) {
-            // If the current rate_cost is worse than the previous rate_cost,
-            // then we terminate the search for this ref_mv_idx.
             mode_info[ref_mv_idx].skip = 1;
             break;
           }
         }
+
+        // Case 2: Prune based on modeled rd.
+        if (skip_newmv_in_drl > 1) {
+          const unsigned int prev_sse = mode_info[prev_ref_idx].pred_sse;
+          if (prev_sse != UINT_MAX && prev_rate_cost < this_rate_cost) {
+            if (this_model_rd == INT64_MAX) {
+              this_model_rd =
+                  compute_modeled_rd(cpi, x, bsize, this_sse, this_rate_cost);
+              mode_info[ref_mv_idx].model_rd = this_model_rd;
+            }
+
+            int64_t prev_model_rd = mode_info[prev_ref_idx].model_rd;
+            if (prev_model_rd == INT64_MAX) {
+              prev_model_rd =
+                  compute_modeled_rd(cpi, x, bsize, prev_sse, prev_rate_cost);
+              mode_info[prev_ref_idx].model_rd = prev_model_rd;
+            }
+
+            int64_t rd_thresh = prev_model_rd;
+            // Prune less aggressively for lower refmv indices.
+            if (skip_newmv_in_drl == 2 && ref_mv_idx < 2) {
+              rd_thresh += prev_model_rd >> 3;
+            }
+
+            if (this_model_rd > rd_thresh) {
+              mode_info[ref_mv_idx].skip = 1;
+              break;
+            }
+          }
+        }
       }
     }
   }
diff --git a/av1/encoder/motion_search_facade.h b/av1/encoder/motion_search_facade.h
index e5b4dd2dc0..9c3118295d 100644
--- a/av1/encoder/motion_search_facade.h
+++ b/av1/encoder/motion_search_facade.h
@@ -23,10 +23,12 @@ extern "C" {
 // TODO(any): rename this struct to something else. There is already another
 // struct called inter_modes_info, which makes this terribly confusing.
 typedef struct {
+  int64_t model_rd;
   int drl_cost;
   int_mv full_search_mv;
   int full_mv_rate;
   int full_mv_bestsme;
+  unsigned int pred_sse;
   int skip;
 } inter_mode_info;

diff --git a/av1/encoder/rdopt.c b/av1/encoder/rdopt.c
index 5e0afbc8ef..0414900f8b 100644
--- a/av1/encoder/rdopt.c
+++ b/av1/encoder/rdopt.c
@@ -3280,6 +3280,8 @@ static int64_t handle_inter_mode(

     mode_info[ref_mv_idx].full_search_mv.as_int = INVALID_MV;
     mode_info[ref_mv_idx].full_mv_bestsme = INT_MAX;
+    mode_info[ref_mv_idx].pred_sse = UINT_MAX;
+    mode_info[ref_mv_idx].model_rd = INT64_MAX;
     const int drl_cost = get_drl_cost(
         mbmi, mbmi_ext, mode_costs->drl_mode_cost0, ref_frame_type);
     mode_info[ref_mv_idx].drl_cost = drl_cost;
diff --git a/av1/encoder/speed_features.c b/av1/encoder/speed_features.c
index 09e9108e6a..4f2752d666 100644
--- a/av1/encoder/speed_features.c
+++ b/av1/encoder/speed_features.c
@@ -814,6 +814,7 @@ static void set_good_speed_feature_framesize_dependent(
   }

   if (speed >= 2) {
+    sf->inter_sf.skip_newmv_in_drl = boosted ? 1 : !is_720p_or_larger ? 2 : 3;
     sf->part_sf.ml_4_partition_search_level_index = 2;
     if (is_720p_or_larger) {
       sf->part_sf.use_square_partition_only_threshold = BLOCK_64X64;
@@ -887,7 +888,7 @@ static void set_good_speed_feature_framesize_dependent(

   if (speed >= 3) {
     sf->inter_sf.enable_fast_wedge_mask_search = 1;
-    sf->inter_sf.skip_newmv_in_drl = 2;
+    sf->inter_sf.skip_newmv_in_drl = 4;
     sf->inter_sf.skip_ext_comp_nearmv_mode = 1;
     sf->inter_sf.limit_inter_mode_cands = is_lf_frame ? 3 : 0;
     sf->inter_sf.disable_interinter_wedge_newmv_search = boosted ? 0 : 1;
@@ -974,7 +975,7 @@ static void set_good_speed_feature_framesize_dependent(
     sf->inter_sf.disable_interinter_wedge_var_thresh = UINT_MAX;
     sf->inter_sf.prune_obmc_prob_thresh = INT_MAX;
     sf->inter_sf.limit_txfm_eval_per_mode = boosted ? 0 : 2;
-    if (is_480p_or_lesser) sf->inter_sf.skip_newmv_in_drl = 3;
+    if (is_480p_or_lesser) sf->inter_sf.skip_newmv_in_drl = 5;

     if (speed >= 5) {
       if (is_720p_or_larger) {
@@ -1010,7 +1011,7 @@ static void set_good_speed_feature_framesize_dependent(
     }
     if (is_720p_or_larger) sf->hl_sf.recode_tolerance = 40;

-    sf->inter_sf.skip_newmv_in_drl = 4;
+    sf->inter_sf.skip_newmv_in_drl = 6;
     sf->inter_sf.prune_comp_ref_frames = 2;
     sf->mv_sf.skip_fullpel_search_using_startmv_refmv = boosted ? 0 : 1;

@@ -2060,7 +2061,7 @@ static void set_rt_speed_features_framesize_independent(AV1_COMP *cpi,
   sf->inter_sf.prune_inter_modes_if_skippable = 1;
   sf->inter_sf.prune_nearmv_using_neighbors = PRUNE_NEARMV_LEVEL3;
   sf->inter_sf.reduce_inter_modes = boosted ? 1 : 3;
-  sf->inter_sf.skip_newmv_in_drl = 4;
+  sf->inter_sf.skip_newmv_in_drl = 6;

   sf->interp_sf.use_fast_interpolation_filter_search = 1;
   sf->interp_sf.use_interp_filter = 1;
diff --git a/av1/encoder/speed_features.h b/av1/encoder/speed_features.h
index c095780a0c..b1b1aa4921 100644
--- a/av1/encoder/speed_features.h
+++ b/av1/encoder/speed_features.h
@@ -1053,18 +1053,20 @@ typedef struct INTER_MODE_SPEED_FEATURES {
   //   current frame in both past and future direction.
   int prune_comp_ref_frames;

-  // Skip the current ref_mv in NEW_MV mode based on mv, rate cost, etc.
-  // This speed feature equaling 0 means no skipping.
-  // If the speed feature equals 1 or 2, skip the current ref_mv in NEW_MV mode
-  // if we have already encountered ref_mv in the drl such that:
-  //  1. The other drl has the same mv during the SIMPLE_TRANSLATION search
-  //     process as the current mv.
-  //  2. The rate needed to encode the current mv is larger than that for the
-  //     other ref_mv.
-  // The speed feature equaling 1 means using subpel mv in the comparison.
-  // The speed feature equaling 2 means using fullpel mv in the comparison.
-  // If the speed feature >= 3, skip the current ref_mv in NEW_MV mode based on
-  // known full_mv bestsme and drl cost.
+  // Skip the evaluation of the current ref_mv in NEW_MV mode based on motion
+  // vectors, rate cost, modeled RD cost, and search error:
+  // 0: Disabled.
+  // 1: After subpel search, skip if subpel MV is identical to an earlier DRL
+  //    candidate and rate cost is worse/equal.
+  // 2, 3: In addition to 1, prune based on modeled RD cost when rate is worse
+  // (level 2: relaxed threshold for ref_mv_idx < 2 and resolution < 720p, level
+  // 3: strict threshold). 4: In addition to post-subpel pruning, skip before
+  // subpel search if the
+  //    full-pel search MV is identical to an earlier DRL candidate and full-pel
+  //    rate cost is worse/equal.
+  // 5, 6: In addition to 4, skip before subpel search if full-pel search error
+  //    and drl_cost are worse (level 5: relaxed threshold with 25% margin,
+  //    level 6: strict threshold).
   int skip_newmv_in_drl;

   // This speed feature checks duplicate ref MVs among NEARESTMV, NEARMV,