Commit ae410fe8b7 for aom
commit ae410fe8b7bd45f3cf61dc8f112dc783e2a08089
Author: Marco Paniconi <marpan@google.com>
Date: Thu Oct 1 00:08:11 2026 +0000
Fix stale search_site_config in IntraBC motion search
Use av1_get_search_site_config() in rd_pick_intrabc_mode_sb()
and av1_search_intrabc_nonrd() instead of reading
search_site_cfg[SS_CFG_LOOKAHEAD] directly, and also
check y_stride_src in init_motion_estimation().
Fix is suggested by Yuan Tong.
Unittests written by Yuan Tong.
Bug: 568044446
Change-Id: Id25d82bed48b4fa4bdcc2175af4e6448e2ab05fc
diff --git a/av1/encoder/encoder.c b/av1/encoder/encoder.c
index 6ee258a762..359c5294ba 100644
--- a/av1/encoder/encoder.c
+++ b/av1/encoder/encoder.c
@@ -2520,7 +2520,9 @@ static void init_motion_estimation(AV1_COMP *cpi) {
!mv_search_params->search_site_cfg[SS_CFG_SRC][DIAMOND].stride ||
!mv_search_params->search_site_cfg[SS_CFG_LOOKAHEAD][DIAMOND].stride ||
(y_stride !=
- mv_search_params->search_site_cfg[SS_CFG_SRC][DIAMOND].stride);
+ mv_search_params->search_site_cfg[SS_CFG_SRC][DIAMOND].stride) ||
+ (y_stride_src !=
+ mv_search_params->search_site_cfg[SS_CFG_LOOKAHEAD][DIAMOND].stride);
if (!should_update) {
return;
diff --git a/av1/encoder/nonrd_pickmode.c b/av1/encoder/nonrd_pickmode.c
index f43c8df473..6c86499228 100644
--- a/av1/encoder/nonrd_pickmode.c
+++ b/av1/encoder/nonrd_pickmode.c
@@ -1633,12 +1633,12 @@ static void av1_search_intrabc_nonrd(AV1_COMP *cpi, MACROBLOCK *x,
FULLPEL_MOTION_SEARCH_PARAMS fullms_params;
const SEARCH_METHODS search_method =
av1_get_default_mv_search_method(x, &cpi->sf.mv_sf, bsize);
- const search_site_config *lookahead_search_sites =
- cpi->mv_search_params.search_site_cfg[SS_CFG_LOOKAHEAD];
+ const search_site_config *src_search_sites =
+ av1_get_search_site_config(cpi, x, search_method);
const FULLPEL_MV start_mv = get_fullmv_from_mv(&dv_ref.as_mv);
av1_make_default_fullpel_ms_params(&fullms_params, cpi, x, bsize,
- &dv_ref.as_mv, start_mv,
- lookahead_search_sites, search_method,
+ &dv_ref.as_mv, start_mv, src_search_sites,
+ search_method,
/*fine_search_interval=*/0);
av1_set_ms_to_intra_mode(&fullms_params, x->dv_costs);
fullms_params.mv_limits.col_min = (xd->tile.mi_col_start - mi_col) * MI_SIZE;
diff --git a/av1/encoder/rdopt.c b/av1/encoder/rdopt.c
index 77259ba3bd..89806021ed 100644
--- a/av1/encoder/rdopt.c
+++ b/av1/encoder/rdopt.c
@@ -3515,12 +3515,12 @@ static int64_t rd_pick_intrabc_mode_sb(const AV1_COMP *cpi, MACROBLOCK *x,
FULLPEL_MOTION_SEARCH_PARAMS fullms_params;
const SEARCH_METHODS search_method =
av1_get_default_mv_search_method(x, &cpi->sf.mv_sf, bsize);
- const search_site_config *lookahead_search_sites =
- cpi->mv_search_params.search_site_cfg[SS_CFG_LOOKAHEAD];
+ const search_site_config *src_search_sites =
+ av1_get_search_site_config(cpi, x, search_method);
const FULLPEL_MV start_mv = get_fullmv_from_mv(&dv_ref.as_mv);
av1_make_default_fullpel_ms_params(&fullms_params, cpi, x, bsize,
- &dv_ref.as_mv, start_mv,
- lookahead_search_sites, search_method,
+ &dv_ref.as_mv, start_mv, src_search_sites,
+ search_method,
/*fine_search_interval=*/0);
const IntraBCMVCosts *const dv_costs = x->dv_costs;
av1_set_ms_to_intra_mode(&fullms_params, dv_costs);
diff --git a/test/encode_api_test.cc b/test/encode_api_test.cc
index 2f470c25fd..7ac5498719 100644
--- a/test/encode_api_test.cc
+++ b/test/encode_api_test.cc
@@ -1508,6 +1508,115 @@ INSTANTIATE_TEST_SUITE_P(All, EncodeAPIParameterized,
/*speed=*/testing::Values(6, 7, 10),
/*aq_mode=*/testing::Values(0, 1, 2, 3)));
+// Triggers the IntraBC motion search by using screen content tools to encode a
+// key frame.
+// Note: The speed must be low enough to run the IntraBC pixel search.
+void IntraBCFrameSizeIncreaseTest(unsigned int usage, int speed) {
+ aom_codec_iface_t *iface = aom_codec_av1_cx();
+ aom_codec_enc_cfg_t cfg;
+ ASSERT_EQ(aom_codec_enc_config_default(iface, &cfg, usage), AOM_CODEC_OK);
+ cfg.g_w = 64;
+ cfg.g_h = 16;
+ cfg.g_forced_max_frame_width = 64;
+ cfg.g_forced_max_frame_height = 64;
+ cfg.g_lag_in_frames = 0;
+ cfg.g_threads = 1;
+
+ aom_codec_ctx_t enc;
+ ASSERT_EQ(aom_codec_enc_init(&enc, iface, &cfg, 0), AOM_CODEC_OK);
+ ASSERT_EQ(aom_codec_control(&enc, AOME_SET_CPUUSED, speed), AOM_CODEC_OK);
+ ASSERT_EQ(aom_codec_control(&enc, AV1E_SET_TUNE_CONTENT, AOM_CONTENT_SCREEN),
+ AOM_CODEC_OK);
+
+ aom_image_t *img_small = aom_img_alloc(nullptr, AOM_IMG_FMT_I420, 64, 16, 1);
+ ASSERT_NE(img_small, nullptr);
+ FillImageRandom(img_small);
+ aom_image_t *img_large = aom_img_alloc(nullptr, AOM_IMG_FMT_I420, 64, 64, 1);
+ ASSERT_NE(img_large, nullptr);
+ FillImageRandom(img_large);
+
+ EncodeOne(&enc, img_small, 0);
+
+ // Only the height grows, so the reconstructed frame keeps its stride while
+ // the source buffer gets a new one.
+ cfg.g_h = 64;
+ ASSERT_EQ(aom_codec_enc_config_set(&enc, &cfg), AOM_CODEC_OK);
+ ASSERT_EQ(aom_codec_encode(&enc, img_large, 1, 1, AOM_EFLAG_FORCE_KF),
+ AOM_CODEC_OK);
+ bool got_key_frame = false;
+ aom_codec_iter_t iter = nullptr;
+ while (const aom_codec_cx_pkt_t *pkt = aom_codec_get_cx_data(&enc, &iter)) {
+ if (pkt->kind == AOM_CODEC_CX_FRAME_PKT &&
+ (pkt->data.frame.flags & AOM_FRAME_IS_KEY)) {
+ got_key_frame = true;
+ }
+ }
+ EXPECT_TRUE(got_key_frame);
+
+ aom_img_free(img_small);
+ aom_img_free(img_large);
+ ASSERT_EQ(aom_codec_destroy(&enc), AOM_CODEC_OK);
+}
+
+#if !CONFIG_REALTIME_ONLY
+TEST(EncodeAPI, IntraBCFrameSizeIncreaseGoodQuality) {
+ IntraBCFrameSizeIncreaseTest(AOM_USAGE_GOOD_QUALITY, 2);
+}
+
+TEST(EncodeAPI, IntraBCFrameSizeIncreaseAllIntra) {
+ IntraBCFrameSizeIncreaseTest(AOM_USAGE_ALL_INTRA, 5);
+}
+#endif // !CONFIG_REALTIME_ONLY
+
+TEST(EncodeAPI, IntraBCFrameSizeIncreaseRealtime) {
+ IntraBCFrameSizeIncreaseTest(AOM_USAGE_REALTIME, 7);
+}
+
+// Encodes a narrow key frame after a wide one with screen content tools in
+// realtime mode. The source buffer of the narrow frame has a smaller stride
+// than the buffer of the wide frame, so the IntraBC motion search must not use
+// search sites built for the wider stride. The assertion in
+// av1_full_pixel_search() checks that the strides match.
+TEST(EncodeAPI, IntraBCFrameSizeDecreaseRealtime) {
+ aom_codec_iface_t *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 = 1024;
+ cfg.g_h = 512;
+ cfg.g_lag_in_frames = 0;
+ cfg.g_threads = 1;
+
+ aom_codec_ctx_t enc;
+ ASSERT_EQ(aom_codec_enc_init(&enc, iface, &cfg, 0), AOM_CODEC_OK);
+ ASSERT_EQ(aom_codec_control(&enc, AOME_SET_CPUUSED, 7), AOM_CODEC_OK);
+ ASSERT_EQ(aom_codec_control(&enc, AV1E_SET_TUNE_CONTENT, AOM_CONTENT_SCREEN),
+ AOM_CODEC_OK);
+
+ aom_image_t *img_wide =
+ aom_img_alloc(nullptr, AOM_IMG_FMT_I420, 1024, 512, 1);
+ ASSERT_NE(img_wide, nullptr);
+ FillImageRandom(img_wide);
+ aom_image_t *img_narrow =
+ aom_img_alloc(nullptr, AOM_IMG_FMT_I420, 32, 512, 1);
+ ASSERT_NE(img_narrow, nullptr);
+ FillImageRandom(img_narrow);
+
+ EncodeOne(&enc, img_wide, 0);
+
+ cfg.g_w = 32;
+ ASSERT_EQ(aom_codec_enc_config_set(&enc, &cfg), AOM_CODEC_OK);
+ ASSERT_EQ(aom_codec_encode(&enc, img_narrow, 1, 1, AOM_EFLAG_FORCE_KF),
+ AOM_CODEC_OK);
+ aom_codec_iter_t iter = nullptr;
+ while (aom_codec_get_cx_data(&enc, &iter) != nullptr) {
+ }
+
+ aom_img_free(img_wide);
+ aom_img_free(img_narrow);
+ ASSERT_EQ(aom_codec_destroy(&enc), AOM_CODEC_OK);
+}
+
#if !CONFIG_REALTIME_ONLY
TEST(EncodeAPI, AllIntraMode) {
aom_codec_iface_t *iface = aom_codec_av1_cx();