Commit f4f8fb8639 for aom

commit f4f8fb8639bacfd22266290d43362a367313ea29
Author: Erik Språng <sprang@google.com>
Date:   Fri Oct 9 10:28:17 2026 +0200

    rtc: Only enable nonrd compound ref for comp pair

    Previously, when compound prediction was enabled via
    AV1E_SET_SVC_REF_FRAME_COMP_PRED, sf->rt_sf.use_comp_ref_nonrd was set
    to 1 even on frames where LAST_FRAME or the corresponding second
    reference (GOLDEN_FRAME, LAST2_FRAME, ALTREF_FRAME) was not active. This
    caused single-reference frames to enter the REFERENCE_MODE_SELECT and
    SWITCHABLE interpolation filter paths.

    Require LAST_FRAME as well as the paired second reference for each
    sf->rt_sf.ref_frame_comp_nonrd[i] entry, and set
    sf->rt_sf.use_comp_ref_nonrd to the logical OR of the three active
    compound pair flags.

    Bug: webrtc:496266459
    TAG=agy
    CONV=e59647f0-f168-45b8-b962-aa515b0000cc
    Change-Id: I74d1262ff452d50347e0281d452719b207ec2a94

diff --git a/av1/encoder/speed_features.c b/av1/encoder/speed_features.c
index 03e9693daf..4c25f14f53 100644
--- a/av1/encoder/speed_features.c
+++ b/av1/encoder/speed_features.c
@@ -1738,18 +1738,18 @@ static void set_rt_speed_feature_framesize_dependent(const AV1_COMP *const cpi,
     sf->rt_sf.frame_level_mode_cost_update = false;

     // Compound mode enabling.
-    if (rtc_ref->ref_frame_comp[0] || rtc_ref->ref_frame_comp[1] ||
-        rtc_ref->ref_frame_comp[2]) {
-      sf->rt_sf.use_comp_ref_nonrd = 1;
-      sf->rt_sf.ref_frame_comp_nonrd[0] =
-          rtc_ref->ref_frame_comp[0] && rtc_ref->reference[GOLDEN_FRAME - 1];
-      sf->rt_sf.ref_frame_comp_nonrd[1] =
-          rtc_ref->ref_frame_comp[1] && rtc_ref->reference[LAST2_FRAME - 1];
-      sf->rt_sf.ref_frame_comp_nonrd[2] =
-          rtc_ref->ref_frame_comp[2] && rtc_ref->reference[ALTREF_FRAME - 1];
-    } else {
-      sf->rt_sf.use_comp_ref_nonrd = 0;
-    }
+    sf->rt_sf.ref_frame_comp_nonrd[0] = rtc_ref->ref_frame_comp[0] &&
+                                        rtc_ref->reference[LAST_FRAME - 1] &&
+                                        rtc_ref->reference[GOLDEN_FRAME - 1];
+    sf->rt_sf.ref_frame_comp_nonrd[1] = rtc_ref->ref_frame_comp[1] &&
+                                        rtc_ref->reference[LAST_FRAME - 1] &&
+                                        rtc_ref->reference[LAST2_FRAME - 1];
+    sf->rt_sf.ref_frame_comp_nonrd[2] = rtc_ref->ref_frame_comp[2] &&
+                                        rtc_ref->reference[LAST_FRAME - 1] &&
+                                        rtc_ref->reference[ALTREF_FRAME - 1];
+    sf->rt_sf.use_comp_ref_nonrd = sf->rt_sf.ref_frame_comp_nonrd[0] ||
+                                   sf->rt_sf.ref_frame_comp_nonrd[1] ||
+                                   sf->rt_sf.ref_frame_comp_nonrd[2];

     if (cpi->svc.number_spatial_layers > 1 ||
         cpi->svc.number_temporal_layers > 1)
diff --git a/test/encode_api_test.cc b/test/encode_api_test.cc
index c6540b2392..f3eca32b78 100644
--- a/test/encode_api_test.cc
+++ b/test/encode_api_test.cc
@@ -3641,4 +3641,104 @@ TEST(EncodeAPI, NonrdBlockYrdRateOverflow) {
   ASSERT_EQ(aom_codec_destroy(&enc), AOM_CODEC_OK);
 }

+// Regression test for webrtc:496266459: Enabling compound prediction via
+// AV1E_SET_SVC_REF_FRAME_COMP_PRED must not affect encoding of frames that do
+// not have any active compound reference pair (e.g., single-reference delta
+// frames referencing only LAST_FRAME).
+TEST(EncodeAPI, SvcCompPredDoesNotAffectSingleRefFrames) {
+  aom_codec_iface_t *const iface = aom_codec_av1_cx();
+  aom_codec_enc_cfg_t cfg;
+  ASSERT_EQ(aom_codec_enc_config_default(iface, &cfg, AOM_USAGE_REALTIME),
+            AOM_CODEC_OK);
+  cfg.g_w = 320;
+  cfg.g_h = 240;
+  cfg.g_lag_in_frames = 0;
+  cfg.rc_end_usage = AOM_Q;
+
+  aom_codec_ctx_t enc_no_comp;
+  aom_codec_ctx_t enc_with_comp;
+  ASSERT_EQ(aom_codec_enc_init(&enc_no_comp, iface, &cfg, 0), AOM_CODEC_OK);
+  ASSERT_EQ(aom_codec_enc_init(&enc_with_comp, iface, &cfg, 0), AOM_CODEC_OK);
+
+  for (aom_codec_ctx_t *enc : { &enc_no_comp, &enc_with_comp }) {
+    ASSERT_EQ(aom_codec_control(enc, AOME_SET_CPUUSED, 9), AOM_CODEC_OK);
+    ASSERT_EQ(aom_codec_control(enc, AOME_SET_CQ_LEVEL, 40), AOM_CODEC_OK);
+  }
+
+  // Enable compound prediction for all three compound reference pairs
+  // (GOLDEN_LAST, LAST2_LAST, ALTREF_LAST) on the second encoder.
+  aom_svc_ref_frame_comp_pred_t comp_pred = {};
+  comp_pred.use_comp_pred[0] = 1;
+  comp_pred.use_comp_pred[1] = 1;
+  comp_pred.use_comp_pred[2] = 1;
+  ASSERT_EQ(aom_codec_control(&enc_with_comp, AV1E_SET_SVC_REF_FRAME_COMP_PRED,
+                              &comp_pred),
+            AOM_CODEC_OK);
+
+  aom_image_t *img =
+      aom_img_alloc(nullptr, AOM_IMG_FMT_I420, cfg.g_w, cfg.g_h, 1);
+  ASSERT_NE(img, nullptr);
+
+  constexpr int kNumFrames = 10;
+  for (int frame = 0; frame < kNumFrames; ++frame) {
+    SCOPED_TRACE(frame);
+    // Fill moving textured pattern.
+    for (unsigned int r = 0; r < img->d_h; ++r) {
+      for (unsigned int c = 0; c < img->d_w; ++c) {
+        const int texture = (((r + frame) / 8 + (c + 2 * frame) / 8) & 1) * 32;
+        img->planes[AOM_PLANE_Y][r * img->stride[AOM_PLANE_Y] + c] =
+            static_cast<uint8_t>((r + 2 * c + 3 * frame + texture) & 0xff);
+      }
+    }
+    for (int plane = AOM_PLANE_U; plane <= AOM_PLANE_V; ++plane) {
+      const unsigned int h =
+          (img->d_h + img->y_chroma_shift) >> img->y_chroma_shift;
+      for (unsigned int r = 0; r < h; ++r) {
+        memset(img->planes[plane] + r * img->stride[plane], 128,
+               (img->d_w + img->x_chroma_shift) >> img->x_chroma_shift);
+      }
+    }
+
+    // Frame 0 refreshes slot 0; delta frames reference only LAST_FRAME (slot 0)
+    // with all other reference[i] = 0, and refresh slot 0.
+    aom_svc_ref_frame_config_t ref_cfg = {};
+    ref_cfg.ref_idx[0] = 0;  // LAST_FRAME -> slot 0.
+    ref_cfg.reference[0] = frame > 0;
+    ref_cfg.refresh[0] = 1;
+
+    for (aom_codec_ctx_t *enc : { &enc_no_comp, &enc_with_comp }) {
+      ASSERT_EQ(aom_codec_control(enc, AV1E_SET_SVC_REF_FRAME_CONFIG, &ref_cfg),
+                AOM_CODEC_OK);
+      ASSERT_EQ(aom_codec_encode(enc, img, frame, 1, 0), AOM_CODEC_OK);
+    }
+
+    aom_codec_iter_t iter_no_comp = nullptr;
+    aom_codec_iter_t iter_with_comp = nullptr;
+    const aom_codec_cx_pkt_t *pkt_no_comp =
+        aom_codec_get_cx_data(&enc_no_comp, &iter_no_comp);
+    const aom_codec_cx_pkt_t *pkt_with_comp =
+        aom_codec_get_cx_data(&enc_with_comp, &iter_with_comp);
+    ASSERT_NE(pkt_no_comp, nullptr);
+    ASSERT_NE(pkt_with_comp, nullptr);
+    ASSERT_EQ(pkt_no_comp->kind, AOM_CODEC_CX_FRAME_PKT);
+    ASSERT_EQ(pkt_with_comp->kind, AOM_CODEC_CX_FRAME_PKT);
+    // If use_comp_ref_nonrd is left enabled when no compound reference pair is
+    // active, single-reference frames switch reference mode
+    // (REFERENCE_MODE_SELECT) and interpolation filter (SWITCHABLE) signaling
+    // and mode search, altering both packet sizes and bitstream bytes. Verify
+    // that the output frames remain bit-exact (same size and identical bytes)
+    // as when compound prediction is disabled.
+    EXPECT_EQ(pkt_with_comp->data.frame.sz, pkt_no_comp->data.frame.sz);
+    if (pkt_with_comp->data.frame.sz == pkt_no_comp->data.frame.sz) {
+      EXPECT_EQ(memcmp(pkt_with_comp->data.frame.buf,
+                       pkt_no_comp->data.frame.buf, pkt_no_comp->data.frame.sz),
+                0);
+    }
+  }
+
+  aom_img_free(img);
+  ASSERT_EQ(aom_codec_destroy(&enc_with_comp), AOM_CODEC_OK);
+  ASSERT_EQ(aom_codec_destroy(&enc_no_comp), AOM_CODEC_OK);
+}
+
 }  // namespace