Commit 02fa5d6fbd for aom
commit 02fa5d6fbddfe52d3429cefefc8ec2571f50738b
Author: Ihor Buhaiov <freedbrt@gmail.com>
Date: Fri Oct 16 01:33:16 2026 +0200
rtc: Set costs on frames that skip CDF updates
With nonrd pick mode the coefficient, mode and mv costs are updated
at the start of each superblock or superblock row, but not in frames
that don't update their CDFs. The frame level update is skipped too,
unless the update level is tile. So every thread codes such a frame
with the costs the main thread had at the end of an earlier frame.
With row mt the superblock the main thread coded last depends on
thread timing, so the output changes from run to run.
The default build hits this in real time mode, since its default
cost update frequency is per superblock. The realtime only build
defaults to off and is only hit when the cost update controls are
set.
Set these costs at the frame level when the frame doesn't update its
CDFs and the update level isn't off. The CDFs of such a frame stay at
the frame context, so these are the costs the per row update would
compute.
Checked on main at speed 11 with 4 threads, row mt, 2 tile columns
and the default build. On two screen captures of 150 frames, 3 runs
each, there were 2 or 3 different streams before and 1 after, in
both 4:2:0 and 4:4:4. niklas_640_480_30 with 300 frames gave 6
streams in 6 runs before and 1 after. The realtime only build output
is unchanged, also for 10 bit 4:2:0 and 4:4:4. The new
AVxEncoderThreadRTCostUpdTest fails on main in both builds.
At 1 thread the default build output changes a little on the two
screen captures:
4:2:0: 0.21% to 0.39% smaller, PSNR 0.07 dB lower to 0.03 dB
higher, encode time 1.6% faster to 0.5% slower
4:4:4: up to 1.11% smaller, PSNR up to 0.10 dB lower, encode
time unchanged to 1.2% slower
The time changes are within run to run noise.
Change-Id: I446b973cb941ed5e70e2c9fd93062dde67e6c154
diff --git a/av1/encoder/rd.c b/av1/encoder/rd.c
index 9fafdafae6..9abd00ccdd 100644
--- a/av1/encoder/rd.c
+++ b/av1/encoder/rd.c
@@ -754,7 +754,10 @@ static inline int is_frame_level_cost_upd_freq_set(
frame_is_intra_only(cm) ||
(use_nonrd_pick_mode ? frames_since_key < 2
: (cm->current_frame.frame_number & 0x07) == 1);
- return ((!use_nonrd_pick_mode && cost_upd_level != INTERNAL_COST_UPD_OFF) ||
+ // Nonrd frames without CDF updates get no per superblock cost update.
+ const int update_per_frame =
+ !use_nonrd_pick_mode || cm->features.disable_cdf_update;
+ return ((update_per_frame && cost_upd_level != INTERNAL_COST_UPD_OFF) ||
cost_upd_level == INTERNAL_COST_UPD_TILE || fill_costs);
}
diff --git a/test/ethread_test.cc b/test/ethread_test.cc
index b0940bade9..c432fdd440 100644
--- a/test/ethread_test.cc
+++ b/test/ethread_test.cc
@@ -221,7 +221,7 @@ class AVxEncoderThreadTest
: EncoderTest(GET_PARAM(0)), encoder_initialized_(false),
encoding_mode_(GET_PARAM(1)), set_cpu_used_(GET_PARAM(2)),
tile_cols_(GET_PARAM(3)), tile_rows_(GET_PARAM(4)),
- row_mt_(GET_PARAM(5)) {
+ row_mt_(GET_PARAM(5)), frame_limit_(26) {
init_flags_ = AOM_CODEC_USE_PSNR;
aom_codec_dec_cfg_t cfg = aom_codec_dec_cfg_t();
cfg.w = 1280;
@@ -324,8 +324,9 @@ class AVxEncoderThreadTest
}
void DoTest() {
- ::libaom_test::YUVVideoSource video(
- "niklas_640_480_30.yuv", AOM_IMG_FMT_I420, 640, 480, 30, 1, 15, 26);
+ ::libaom_test::YUVVideoSource video("niklas_640_480_30.yuv",
+ AOM_IMG_FMT_I420, 640, 480, 30, 1, 15,
+ frame_limit_);
cfg_.rc_target_bitrate = 1000;
if (row_mt_ == 0) {
@@ -445,6 +446,7 @@ class AVxEncoderThreadTest
int tile_cols_;
int tile_rows_;
int row_mt_;
+ int frame_limit_;
::libaom_test::Decoder *decoder_;
std::vector<size_t> size_enc_;
std::vector<std::string> md5_enc_;
@@ -567,6 +569,35 @@ AV1_INSTANTIATE_TEST_SUITE(AVxEncoderThreadRTSegTest,
::testing::Values(7, 9), ::testing::Values(1),
::testing::Values(1), ::testing::Values(0, 1));
+// Per superblock cost updates must give the same output for any thread count.
+class AVxEncoderThreadRTCostUpdTest : public AVxEncoderThreadTest {
+ protected:
+ AVxEncoderThreadRTCostUpdTest() { frame_limit_ = 60; }
+
+ void PreEncodeFrameHook(::libaom_test::VideoSource *video,
+ ::libaom_test::Encoder *encoder) override {
+ const bool first_frame = !encoder_initialized_;
+ AVxEncoderThreadTest::PreEncodeFrameHook(video, encoder);
+ if (first_frame) {
+ encoder->Control(AV1E_SET_COEFF_COST_UPD_FREQ, 0);
+ encoder->Control(AV1E_SET_MODE_COST_UPD_FREQ, 0);
+ encoder->Control(AV1E_SET_MV_COST_UPD_FREQ, 0);
+ encoder->Control(AV1E_SET_CDF_UPDATE_MODE, 2);
+ }
+ }
+};
+
+TEST_P(AVxEncoderThreadRTCostUpdTest, EncoderResultTest) {
+ cfg_.large_scale_tile = 0;
+ decoder_->Control(AV1_SET_TILE_MODE, 0);
+ DoTest();
+}
+
+AV1_INSTANTIATE_TEST_SUITE(AVxEncoderThreadRTCostUpdTest,
+ ::testing::Values(::libaom_test::kRealTime),
+ ::testing::Values(7, 10), ::testing::Values(0, 1),
+ ::testing::Values(0, 1), ::testing::Values(1));
+
#if !CONFIG_REALTIME_ONLY
// The AVxEncoderThreadTestLarge takes up ~14% of total run-time of the