Commit d50b000ac8 for aom

commit d50b000ac8e16c7bdb2dd5ea2ea98dd2f63c3907
Author: Bohan Li <bohanli@google.com>
Date:   Thu May 28 13:14:07 2026 -0700

    Store tpl recon of last ARF

    - Store the tpl recon of last ARF. Used for ext rc to find propagation to prev arf.
    - Fix src buffer condition of last ARF. It should use gf_group->display_idx consistently.
    - In tpl process, if a frame wants to refer to previous GOP's ref frames, they can only use the previous ARF frame.

    Primarily intended for ext RC. Shows slight gains for libaom RC in q mode speed 2.
    See results at: http://shortn/_y5vuPIozhv

    STATS_CHANGED

    Change-Id: Ic06670f5a4df217f84596c597f3811c44ef9c5a6

diff --git a/av1/encoder/encoder.c b/av1/encoder/encoder.c
index 07ef75b615..878075f820 100644
--- a/av1/encoder/encoder.c
+++ b/av1/encoder/encoder.c
@@ -1761,6 +1761,7 @@ void av1_remove_primary_compressor(AV1_PRIMARY *ppi) {
     aom_free(tpl_data->tpl_stats_pool[frame]);
     aom_free_frame_buffer(&tpl_data->tpl_rec_pool[frame]);
     aom_free_frame_buffer(&tpl_data->prev_gop_arf_src);
+    aom_free_frame_buffer(&tpl_data->prev_gop_arf_tpl_recon);
     tpl_data->prev_gop_arf_disp_order = -1;
     tpl_data->tpl_stats_pool[frame] = NULL;
   }
@@ -4573,16 +4574,18 @@ int av1_encode(AV1_COMP *const cpi, uint8_t *const dest, size_t dest_size,
       cpi->ppi->gf_group.layer_depth[cpi->gf_frame_index],
       current_frame->display_order_hint, cpi->ppi->gf_group.max_layer_depth);

+#if !CONFIG_REALTIME_ONLY
   const GF_GROUP *gf_group = &cpi->ppi->gf_group;
   // Check if this is the last frame in the gop. If so, make a copy of the
   // source for TPL.
   if (cpi->oxcf.algo_cfg.enable_tpl_model &&
+      av1_tpl_stats_ready(&cpi->ppi->tpl_data, cpi->gf_frame_index) &&
       gf_group->update_type[cpi->gf_frame_index] != OVERLAY_UPDATE &&
       gf_group->update_type[cpi->gf_frame_index] != INTNL_OVERLAY_UPDATE) {
     int is_last = 1;
     for (int i = 0; i < gf_group->size; ++i) {
       if (gf_group->display_idx[i] >
-          (int64_t)current_frame->display_order_hint) {
+          gf_group->display_idx[cpi->gf_frame_index]) {
         is_last = 0;
         break;
       }
@@ -4590,6 +4593,7 @@ int av1_encode(AV1_COMP *const cpi, uint8_t *const dest, size_t dest_size,
     if (is_last) {
       cpi->ppi->tpl_data.prev_gop_arf_disp_order = -1;
       const AV1EncoderConfig *const oxcf = &cpi->oxcf;
+      int available = 1;
       int ret = aom_realloc_frame_buffer(
           &cpi->ppi->tpl_data.prev_gop_arf_src, oxcf->frm_dim_cfg.width,
           oxcf->frm_dim_cfg.height, cm->seq_params->subsampling_x,
@@ -4600,19 +4604,53 @@ int av1_encode(AV1_COMP *const cpi, uint8_t *const dest, size_t dest_size,
         aom_internal_error(cm->error, AOM_CODEC_MEM_ERROR,
                            "Failed to allocate tpl prev_gop_arf_src buf.");

-      // Currently it is not supported if source/refernece is resized.
+      // Currently it is not supported if source/reference is resized.
       if (cpi->source->y_width == cpi->ppi->tpl_data.prev_gop_arf_src.y_width &&
           cpi->source->y_height ==
               cpi->ppi->tpl_data.prev_gop_arf_src.y_height) {
         // Copy the content from source to this buffer for next gop.
         aom_yv12_copy_frame(cpi->source, &cpi->ppi->tpl_data.prev_gop_arf_src,
                             av1_num_planes(cm));
+      } else {
+        available = 0;
+      }

+      ret = aom_realloc_frame_buffer(
+          &cpi->ppi->tpl_data.prev_gop_arf_tpl_recon, oxcf->frm_dim_cfg.width,
+          oxcf->frm_dim_cfg.height, cm->seq_params->subsampling_x,
+          cm->seq_params->subsampling_y, cm->seq_params->use_highbitdepth,
+          cpi->oxcf.border_in_pixels, cm->features.byte_alignment, NULL, NULL,
+          NULL, cpi->alloc_pyramid, 0);
+      if (ret)
+        aom_internal_error(
+            cm->error, AOM_CODEC_MEM_ERROR,
+            "Failed to allocate tpl prev_gop_arf_tpl_recon buf.");
+
+      YV12_BUFFER_CONFIG *prev_gop_arf_tpl_recon_buf =
+          cpi->ppi->tpl_data.tpl_frame
+              ? cpi->ppi->tpl_data.tpl_frame[cpi->gf_frame_index].rec_picture
+              : NULL;
+
+      // Currently it is not supported if source/reference is resized.
+      if (prev_gop_arf_tpl_recon_buf &&
+          prev_gop_arf_tpl_recon_buf->y_width ==
+              cpi->ppi->tpl_data.prev_gop_arf_tpl_recon.y_width &&
+          prev_gop_arf_tpl_recon_buf->y_height ==
+              cpi->ppi->tpl_data.prev_gop_arf_tpl_recon.y_height) {
+        // Copy the content from source to this buffer for next gop.
+        aom_yv12_copy_frame(prev_gop_arf_tpl_recon_buf,
+                            &cpi->ppi->tpl_data.prev_gop_arf_tpl_recon, 1);
+      } else {
+        available = 0;
+      }
+
+      if (available) {
         cpi->ppi->tpl_data.prev_gop_arf_disp_order =
             current_frame->display_order_hint;
       }
     }
   }
+#endif

   if (is_stat_generation_stage(cpi)) {
 #if !CONFIG_REALTIME_ONLY
diff --git a/av1/encoder/tpl_model.c b/av1/encoder/tpl_model.c
index 7f3bc97d7b..c1691f9151 100644
--- a/av1/encoder/tpl_model.c
+++ b/av1/encoder/tpl_model.c
@@ -851,7 +851,11 @@ static inline void mode_estimation(AV1_COMP *cpi, TplTxfmStats *tpl_txfm_stats,
     // Store inter cost for each ref frame. This is used to prune inter modes.
     tpl_stats->pred_error[rf_idx] = AOMMAX(1, inter_cost);

-    if (inter_cost < best_inter_cost) {
+    // If we have saved the previous arf frame. Only allow using it as reference
+    // for previous gop.
+    if (inter_cost < best_inter_cost &&
+        (tpl_data->prev_gop_arf_disp_order < 0 ||
+         tpl_data->src_ref_frame[rf_idx] != tpl_data->ref_frame[rf_idx])) {
       best_rf_idx = rf_idx;

       best_inter_cost = inter_cost;
@@ -981,7 +985,13 @@ static inline void mode_estimation(AV1_COMP *cpi, TplTxfmStats *tpl_txfm_stats,
     inter_cost =
         tpl_get_satd_cost(bd_info, src_diff, bw, src_mb_buffer, src_stride,
                           predictor, bw, coeff, bw, bh, tx_size);
-    if (inter_cost < best_inter_cost) {
+
+    // If we have saved the previous arf frame. Only allow using it as reference
+    // for previous gop.
+    if (inter_cost < best_inter_cost &&
+        (tpl_data->prev_gop_arf_disp_order < 0 ||
+         (tpl_data->src_ref_frame[rf_idx0] != tpl_data->ref_frame[rf_idx0] &&
+          tpl_data->src_ref_frame[rf_idx1] != tpl_data->ref_frame[rf_idx1]))) {
       best_cmp_rf_idx = cmp_rf_idx;
       best_inter_cost = inter_cost;
       best_mv[0] = tmp_mv[0];
@@ -1648,15 +1658,23 @@ static inline int init_gop_frames_for_tpl(
       tpl_data->tpl_frame[-i - 1].rec_picture = NULL;
       tpl_data->tpl_frame[-i - 1].frame_display_index = 0;
     } else {
+      int has_prev_arf = 0;
       if (cm->ref_frame_map[i]->display_order_hint ==
           tpl_data->prev_gop_arf_disp_order) {
         tpl_data->tpl_frame[-i - 1].gf_picture = &tpl_data->prev_gop_arf_src;
+        tpl_data->tpl_frame[-i - 1].rec_picture =
+            &tpl_data->prev_gop_arf_tpl_recon;
+        has_prev_arf = 1;
       } else {
         tpl_data->tpl_frame[-i - 1].gf_picture = &cm->ref_frame_map[i]->buf;
+        tpl_data->tpl_frame[-i - 1].rec_picture = &cm->ref_frame_map[i]->buf;
       }
-      tpl_data->tpl_frame[-i - 1].rec_picture = &cm->ref_frame_map[i]->buf;
       tpl_data->tpl_frame[-i - 1].frame_display_index =
           cm->ref_frame_map[i]->display_order_hint;
+
+      if (!has_prev_arf) {
+        tpl_data->prev_gop_arf_disp_order = -1;
+      }
     }

     ref_picture_map[i] = -i - 1;
diff --git a/av1/encoder/tpl_model.h b/av1/encoder/tpl_model.h
index a69bf46271..34bcfc67c7 100644
--- a/av1/encoder/tpl_model.h
+++ b/av1/encoder/tpl_model.h
@@ -243,6 +243,11 @@ typedef struct TplParams {
    */
   YV12_BUFFER_CONFIG prev_gop_arf_src;

+  /*!
+   * The buffer for the past gop's last frame's tpl recon.
+   */
+  YV12_BUFFER_CONFIG prev_gop_arf_tpl_recon;
+
   /*!
    * Display order of the past gop's last frame.
    */