Commit fb357ae8ac for aom

commit fb357ae8aca6ae49811719e0ccbde37fd77e031e
Author: Ihor Buhaiov <freedbrt@gmail.com>
Date:   Wed Oct 21 16:14:19 2026 +0200

    rtc: Reset the IntraBC flag in setup_planes

    setup_planes() builds the superblock prediction for the variance
    partitioning with the mode info of the superblock. Nonrd doesn't clear
    the mode info between frames. e161f898a0 reset the fields this
    prediction reads, but not use_intrabc, so after a key frame coded with
    IntraBC the flag can still be set. The prediction then takes the
    IntraBC path and copies from the frame being coded instead of LAST.
    Those pixels are either not coded yet or being coded by another
    thread, so the partition depends on thread timing and on the thread
    count.

    Clear use_intrabc with the other fields.

    The new AVxEncoderThreadRTIntraBCTest codes screendata.y4m with
    IntraBC, 2x2 tiles and row mt at speeds 7 to 11, and checks that 1, 2
    and 4 threads give the same output. It fails at every speed without
    the fix and passes with it, in realtime only and default builds, on
    arm and x86.

    On a 2558x1372 screen recording at 4 threads with an active map,
    slower threads gave a stream that differed by 1 byte in two frames.
    With the fix all 6 runs give the same stream. Encodes without IntraBC
    are bit exact.

    Change-Id: Iccd0c25edc6696a5ec57fa74740db64778c89349

diff --git a/av1/encoder/var_based_part.c b/av1/encoder/var_based_part.c
index cce5da955e..24b367ff8f 100644
--- a/av1/encoder/var_based_part.c
+++ b/av1/encoder/var_based_part.c
@@ -1468,6 +1468,7 @@ static void setup_planes(AV1_COMP *cpi, MACROBLOCK *x, unsigned int *y_sad,
   mi->bsize = cm->seq_params->sb_size;
   mi->mv[0].as_int = 0;
   mi->interp_filters = av1_broadcast_interp_filter(BILINEAR);
+  mi->use_intrabc = 0;

   if (use_last_ref) {
     av1_setup_pre_planes(
diff --git a/test/ethread_test.cc b/test/ethread_test.cc
index b366422e2a..215f740c9a 100644
--- a/test/ethread_test.cc
+++ b/test/ethread_test.cc
@@ -466,6 +466,72 @@ AV1_INSTANTIATE_TEST_SUITE(AVxEncoderThreadRTTest,
                            ::testing::Values(0, 2), ::testing::Values(0, 2),
                            ::testing::Values(0, 1));

+// Codes a key frame with IntraBC and then screen content in motion, with two
+// tile rows so that several threads code the inter frames at once. The output
+// must not depend on the number of threads.
+class AVxEncoderThreadRTIntraBCTest
+    : public ::libaom_test::CodecTestWithParam<int>,
+      public ::libaom_test::EncoderTest {
+ protected:
+  static const int kFrames = 10;
+
+  AVxEncoderThreadRTIntraBCTest()
+      : EncoderTest(GET_PARAM(0)), cpu_used_(GET_PARAM(1)) {}
+  ~AVxEncoderThreadRTIntraBCTest() override = default;
+
+  void SetUp() override {
+    InitializeConfig(::libaom_test::kRealTime);
+    cfg_.g_lag_in_frames = 0;
+    cfg_.rc_end_usage = AOM_CBR;
+    cfg_.rc_target_bitrate = 1000;
+    cfg_.kf_max_dist = 90000;
+  }
+
+  void PreEncodeFrameHook(::libaom_test::VideoSource *video,
+                          ::libaom_test::Encoder *encoder) override {
+    if (video->frame() != 0) return;
+    encoder->Control(AOME_SET_CPUUSED, cpu_used_);
+    encoder->Control(AV1E_SET_TUNE_CONTENT, AOM_CONTENT_SCREEN);
+    encoder->Control(AV1E_SET_ENABLE_INTRABC, 1);
+    encoder->Control(AV1E_SET_AQ_MODE, 0);
+    encoder->Control(AV1E_SET_ROW_MT, 1);
+    encoder->Control(AV1E_SET_TILE_COLUMNS, 1);
+    encoder->Control(AV1E_SET_TILE_ROWS, 1);
+    // Costs updated per superblock differ between one thread and row mt.
+    encoder->Control(AV1E_SET_COEFF_COST_UPD_FREQ, 3);
+    encoder->Control(AV1E_SET_MODE_COST_UPD_FREQ, 3);
+    encoder->Control(AV1E_SET_MV_COST_UPD_FREQ, 3);
+  }
+
+  void FramePktHook(const aom_codec_cx_pkt_t *pkt) override {
+    ::libaom_test::MD5 md5;
+    md5.Add(static_cast<const uint8_t *>(pkt->data.frame.buf),
+            pkt->data.frame.sz);
+    md5_.push_back(md5.Get());
+  }
+
+  std::vector<std::string> Encode(int threads) {
+    cfg_.g_threads = threads;
+    md5_.clear();
+    ::libaom_test::Y4mVideoSource video("screendata.y4m", 0, kFrames);
+    EXPECT_NO_FATAL_FAILURE(RunLoop(&video));
+    return md5_;
+  }
+
+  int cpu_used_;
+  std::vector<std::string> md5_;
+};
+
+TEST_P(AVxEncoderThreadRTIntraBCTest, SameOutput) {
+  const std::vector<std::string> ref = Encode(1);
+  ASSERT_EQ(ref.size(), static_cast<size_t>(kFrames));
+  EXPECT_EQ(ref, Encode(2));
+  EXPECT_EQ(ref, Encode(4));
+}
+
+AV1_INSTANTIATE_TEST_SUITE(AVxEncoderThreadRTIntraBCTest,
+                           ::testing::Range(7, 12));
+
 #if !CONFIG_REALTIME_ONLY

 // The AVxEncoderThreadTestLarge takes up ~14% of total run-time of the