Commit 483ff213fc for aom
commit 483ff213fc8d8e0785958578ac85e0eaeefc91a8
Author: Ihor Buhaiov <freedbrt@gmail.com>
Date: Sat Oct 17 16:42:25 2026 +0200
rtc: Reset low variance flags per superblock
x->part_search_info.variance_low is set only by
av1_choose_var_based_partitioning(), but nonrd mode search reads it
for every block through force_skip_low_temp_var to decide whether to
search GOLDEN, and NEWMV on 64x64 blocks. Superblocks with a fixed
partition, such as those with a skip segment or the fixed 32x32
partition for high motion screen content, don't run the variance
partitioning, so their blocks read the flags of an earlier
superblock. Each thread starts the frame with a copy of the main
thread's state, so with row mt the flags depend on thread timing.
Clear the flags at the start of every superblock.
Checked with row mt at 4 threads in a realtime only build, on arm and
x86. An ROI map that skips segment 0 in every superblock, 640x480
camera, speed 9: 2 different streams in 30 runs before, 1 after. A
720p camera clip at speed 11 without a map: up to 3 streams in 12
runs before, 1 after. A 1080p screen recording with fast window
motion and an active map: 2 streams in 16 runs before, 1 after.
At 1 thread screen content is bit exact. Camera clips can change a
little, because 1 thread read stale flags too. The new
ROIMapThreadTest checks that 1, 2 and 4 threads give the same output.
Change-Id: I81f749afededbedc1b9950b27f99c0cbfd23e8a5
diff --git a/av1/encoder/encodeframe.c b/av1/encoder/encodeframe.c
index f594e83056..a14b18a02e 100644
--- a/av1/encoder/encodeframe.c
+++ b/av1/encoder/encodeframe.c
@@ -1405,6 +1405,9 @@ static inline void encode_sb_row(AV1_COMP *cpi, ThreadData *td,
x->sb_me_partition = 0;
x->sb_me_mv.as_int = 0;
x->sb_force_fixed_part = 1;
+ // Superblocks coded without the variance partitioning must not use the
+ // low variance flags of an earlier superblock.
+ av1_zero(x->part_search_info.variance_low);
x->color_palette_thresh = 64;
x->force_color_check_block_level = 0;
x->nonrd_prune_ref_frame_search =
diff --git a/av1/encoder/var_based_part.c b/av1/encoder/var_based_part.c
index 3b16e659a6..cce5da955e 100644
--- a/av1/encoder/var_based_part.c
+++ b/av1/encoder/var_based_part.c
@@ -1701,8 +1701,6 @@ int av1_choose_var_based_partitioning(AV1_COMP *cpi, const TileInfo *const tile,
// Index for force_split: 0 for 64x64, 1-4 for 32x32 blocks,
// 5-20 for the 16x16 blocks.
force_split[0] = PART_EVAL_ALL;
- memset(x->part_search_info.variance_low, 0,
- sizeof(x->part_search_info.variance_low));
// Check if LAST frame is NULL, and if so, treat this frame
// as a key frame, for the purpose of the superblock partitioning.
diff --git a/test/roi_map_test.cc b/test/roi_map_test.cc
index 2dc5e1524e..cfca18f598 100644
--- a/test/roi_map_test.cc
+++ b/test/roi_map_test.cc
@@ -11,6 +11,7 @@
#include <climits>
#include <cstring>
+#include <string>
#include <vector>
#include "gtest/gtest.h"
@@ -21,6 +22,7 @@
#include "test/codec_factory.h"
#include "test/encode_test_driver.h"
#include "test/i420_video_source.h"
+#include "test/md5_helper.h"
#include "test/util.h"
namespace {
@@ -161,4 +163,84 @@ AV1_INSTANTIATE_TEST_SUITE(ROIMapTest,
::testing::Range(7, 12), ::testing::Values(0, 1),
::testing::Values(0, 1));
+// Codes the top left 32x32 of every 64x64 and skips the rest, so every
+// superblock has a skipped segment and is coded without the variance
+// partitioning. The output must not depend on the number of threads.
+class ROIMapThreadTest : public ::libaom_test::CodecTestWithParam<int>,
+ public ::libaom_test::EncoderTest {
+ protected:
+ static const int kWidth = 640;
+ static const int kHeight = 480;
+
+ ROIMapThreadTest() : EncoderTest(GET_PARAM(0)), cpu_used_(GET_PARAM(1)) {}
+ ~ROIMapThreadTest() 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 = 4000;
+ cfg_.kf_max_dist = 90000;
+ cfg_.rc_min_quantizer = 0;
+ cfg_.rc_max_quantizer = 52;
+ }
+
+ 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_AQ_MODE, 0);
+ encoder->Control(AV1E_SET_ROW_MT, 1);
+ encoder->Control(AV1E_SET_TILE_COLUMNS, 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);
+ aom_roi_map_t roi = aom_roi_map_t();
+ roi.rows = (kHeight + 3) / 4;
+ roi.cols = (kWidth + 3) / 4;
+ std::vector<uint8_t> map(roi.rows * roi.cols);
+ for (unsigned int i = 0; i < roi.rows; ++i) {
+ for (unsigned int j = 0; j < roi.cols; ++j) {
+ map[i * roi.cols + j] = (i & 15) < 8 && (j & 15) < 8;
+ }
+ }
+ roi.enabled = 1;
+ roi.roi_map = map.data();
+ memset(roi.ref_frame, -1, sizeof(roi.ref_frame));
+ roi.skip[0] = 1;
+ // Segment 1 needs a feature to stay a separate segment.
+ roi.delta_q[1] = 8;
+ encoder->Control(AOME_SET_ROI_MAP, &roi);
+ }
+
+ 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::I420VideoSource video("niklas_640_480_30.yuv", kWidth,
+ kHeight, 30, 1, 0, 30);
+ EXPECT_NO_FATAL_FAILURE(RunLoop(&video));
+ return md5_;
+ }
+
+ int cpu_used_;
+ std::vector<std::string> md5_;
+};
+
+TEST_P(ROIMapThreadTest, SameOutput) {
+ const std::vector<std::string> ref = Encode(1);
+ ASSERT_EQ(ref.size(), 30u);
+ EXPECT_EQ(ref, Encode(2));
+ EXPECT_EQ(ref, Encode(4));
+}
+
+AV1_INSTANTIATE_TEST_SUITE(ROIMapThreadTest, ::testing::Values(9));
+
} // namespace