Skip to content

Commit 11bbda5

Browse files
authored
fix(vrr): respect input activity boost cadence (#726)
1 parent d3ffb5d commit 11bbda5

3 files changed

Lines changed: 191 additions & 22 deletions

File tree

src/video.cpp

Lines changed: 68 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,52 @@ namespace video {
9898
}
9999
} // namespace
100100

101+
std::chrono::duration<double, std::milli>
102+
minimum_frame_time_for_vrr(int stream_fps, int minimum_fps_target) {
103+
if (minimum_fps_target > 0) {
104+
return std::chrono::duration<double, std::milli> { 1000.0 / minimum_fps_target };
105+
}
106+
107+
return std::chrono::duration<double, std::milli> { 2000.0 / std::max(stream_fps, 1) };
108+
}
109+
110+
input_activity_boost_policy_t
111+
make_input_activity_boost_policy(const input_activity_boost_config_t &config) {
112+
input_activity_boost_policy_t policy {};
113+
policy.configured =
114+
config.variable_refresh_rate &&
115+
config.enabled &&
116+
config.boost_fps > 0 &&
117+
config.window_ms > 0;
118+
119+
if (!policy.configured) {
120+
return policy;
121+
}
122+
123+
policy.fps = std::min(config.boost_fps, std::max(config.stream_fps, 1));
124+
policy.frame_time = std::chrono::duration<double, std::milli> { 1000.0 / policy.fps };
125+
policy.useful = config.minimum_fps_target == 0 || policy.fps > config.minimum_fps_target;
126+
127+
return policy;
128+
}
129+
130+
std::chrono::duration<double, std::milli>
131+
effective_minimum_frame_time(
132+
const std::chrono::duration<double, std::milli> &base_minimum_frame_time,
133+
const input_activity_boost_policy_t &input_activity_boost_policy,
134+
bool input_boost_active,
135+
int minimum_fps_target) {
136+
if (!input_boost_active || !input_activity_boost_policy.useful) {
137+
return base_minimum_frame_time;
138+
}
139+
140+
if (minimum_fps_target > 0) {
141+
return std::min(base_minimum_frame_time, input_activity_boost_policy.frame_time);
142+
}
143+
144+
return input_activity_boost_policy.frame_time;
145+
}
146+
101147
void
102148
free_ctx(AVCodecContext *ctx) {
103149
avcodec_free_context(&ctx);
@@ -2687,34 +2733,32 @@ namespace video {
26872733

26882734
// Set the base minimum frame time based on client-requested target framerate or minimum_fps_target.
26892735
// This can be temporarily reduced later if VRR input activity boost is active.
2690-
std::chrono::duration<double, std::milli> base_minimum_frame_time;
2736+
const auto base_minimum_frame_time = minimum_frame_time_for_vrr(config.framerate, config::video.minimum_fps_target);
26912737
if (config::video.minimum_fps_target > 0) {
2692-
// Use minimum_fps_target if specified
2693-
base_minimum_frame_time = std::chrono::duration<double, std::milli> { 1000.0 / config::video.minimum_fps_target };
26942738
BOOST_LOG(info) << "Minimum frame time set to "sv << base_minimum_frame_time.count() << "ms, based on minimum_fps_target "sv << config::video.minimum_fps_target << " fps."sv;
26952739
}
26962740
else {
2697-
// Default behavior: about half the stream FPS
2698-
base_minimum_frame_time = std::chrono::duration<double, std::milli> { 2000.0 / config.framerate };
26992741
BOOST_LOG(info) << "Minimum frame time set to "sv << base_minimum_frame_time.count() << "ms, based on client-requested target framerate "sv << config.framerate << "."sv;
27002742
}
27012743

2702-
const bool input_activity_boost_enabled =
2703-
config::video.variable_refresh_rate &&
2704-
config::video.input_activity_boost &&
2705-
config::video.input_activity_boost_fps > 0 &&
2706-
config::video.input_activity_boost_window_ms > 0;
2707-
2708-
const auto input_activity_boost_frame_time = input_activity_boost_enabled ?
2709-
std::chrono::duration<double, std::milli> { 1000.0 / config::video.input_activity_boost_fps } :
2710-
base_minimum_frame_time;
2744+
const auto input_activity_boost_policy = make_input_activity_boost_policy({
2745+
config::video.variable_refresh_rate,
2746+
config::video.input_activity_boost,
2747+
config.framerate,
2748+
config::video.minimum_fps_target,
2749+
config::video.input_activity_boost_fps,
2750+
config::video.input_activity_boost_window_ms,
2751+
});
27112752
const auto input_activity_boost_window = std::chrono::milliseconds { config::video.input_activity_boost_window_ms };
27122753

2713-
if (input_activity_boost_enabled) {
2754+
if (input_activity_boost_policy.useful) {
27142755
BOOST_LOG(info) << "Input activity boost enabled: floor="sv
2715-
<< config::video.input_activity_boost_fps
2756+
<< input_activity_boost_policy.fps
27162757
<< " fps, window="sv << config::video.input_activity_boost_window_ms << " ms"sv;
27172758
}
2759+
else if (input_activity_boost_policy.configured) {
2760+
BOOST_LOG(info) << "Input activity boost configured but not enabled because it would not raise the current VRR minimum cadence."sv;
2761+
}
27182762

27192763
auto shutdown_event = mail->event<bool>(mail::shutdown);
27202764
auto packets = mail::man->queue<packet_t>(mail::video_packets);
@@ -2806,18 +2850,20 @@ namespace video {
28062850
}
28072851

28082852
consume_input_activity();
2809-
auto input_boost_active = input_activity_boost_enabled && std::chrono::steady_clock::now() < input_boost_until;
2810-
auto effective_minimum_frame_time = input_boost_active ?
2811-
std::min(base_minimum_frame_time, input_activity_boost_frame_time) :
2812-
base_minimum_frame_time;
2853+
auto input_boost_active = input_activity_boost_policy.useful && std::chrono::steady_clock::now() < input_boost_until;
2854+
auto effective_frame_time = effective_minimum_frame_time(
2855+
base_minimum_frame_time,
2856+
input_activity_boost_policy,
2857+
input_boost_active,
2858+
config::video.minimum_fps_target);
28132859

28142860
std::optional<std::chrono::steady_clock::time_point> frame_timestamp;
28152861
bool has_new_frame = false;
28162862

28172863
// Encode at a minimum FPS to avoid image quality issues with static content
28182864
// When variable_refresh_rate is enabled, only encode when we have a new frame
28192865
if (!requested_idr_frame || images->peek()) {
2820-
if (auto img = pop_image_interruptible(effective_minimum_frame_time, input_activity_boost_enabled && !input_boost_active)) {
2866+
if (auto img = pop_image_interruptible(effective_frame_time, input_activity_boost_policy.useful && !input_boost_active)) {
28212867
frame_timestamp = img->frame_timestamp;
28222868
if (session->convert(*img)) {
28232869
BOOST_LOG(error) << "Could not convert image"sv;
@@ -2832,7 +2878,7 @@ namespace video {
28322878
}
28332879

28342880
consume_input_activity();
2835-
input_boost_active = input_activity_boost_enabled && std::chrono::steady_clock::now() < input_boost_until;
2881+
input_boost_active = input_activity_boost_policy.useful && std::chrono::steady_clock::now() < input_boost_until;
28362882

28372883
// While streaming check to see if the mouse is present and enable Mouse Keys to force the cursor to appear.
28382884
// Run this BEFORE the VRR early-continue so a KVM switch on a static screen still recovers the cursor

src/video.h

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
#include "thread_safe.h"
1010
#include "video_colorspace.h"
1111

12+
#include <chrono>
1213
#include <string>
1314

1415
extern "C" {
@@ -102,6 +103,35 @@ namespace video {
102103
}
103104
};
104105

106+
struct input_activity_boost_policy_t {
107+
bool configured {};
108+
bool useful {};
109+
int fps {};
110+
std::chrono::duration<double, std::milli> frame_time {};
111+
};
112+
113+
struct input_activity_boost_config_t {
114+
bool variable_refresh_rate {};
115+
bool enabled {};
116+
int stream_fps {};
117+
int minimum_fps_target {};
118+
int boost_fps {};
119+
int window_ms {};
120+
};
121+
122+
std::chrono::duration<double, std::milli>
123+
minimum_frame_time_for_vrr(int stream_fps, int minimum_fps_target);
124+
125+
input_activity_boost_policy_t
126+
make_input_activity_boost_policy(const input_activity_boost_config_t &config);
127+
128+
std::chrono::duration<double, std::milli>
129+
effective_minimum_frame_time(
130+
const std::chrono::duration<double, std::milli> &base_minimum_frame_time,
131+
const input_activity_boost_policy_t &input_activity_boost_policy,
132+
bool input_boost_active,
133+
int minimum_fps_target);
134+
105135
platf::mem_type_e
106136
map_base_dev_type(AVHWDeviceType type);
107137
platf::pix_fmt_e

tests/unit/test_video.cpp

Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,99 @@
66

77
#include "../tests_common.h"
88

9+
namespace {
10+
11+
double
12+
millis(std::chrono::duration<double, std::milli> duration) {
13+
return duration.count();
14+
}
15+
16+
} // namespace
17+
18+
TEST(VideoInputActivityBoostPolicy, UsesConfiguredBoostCadenceWhenMinimumIsAuto) {
19+
const auto base_frame_time = video::minimum_frame_time_for_vrr(240, 0);
20+
const auto policy = video::make_input_activity_boost_policy({
21+
true,
22+
true,
23+
240,
24+
0,
25+
60,
26+
150,
27+
});
28+
29+
ASSERT_TRUE(policy.configured);
30+
ASSERT_TRUE(policy.useful);
31+
EXPECT_EQ(policy.fps, 60);
32+
EXPECT_NEAR(millis(base_frame_time), 1000.0 / 120.0, 0.001);
33+
EXPECT_NEAR(millis(policy.frame_time), 1000.0 / 60.0, 0.001);
34+
35+
const auto effective_frame_time = video::effective_minimum_frame_time(base_frame_time, policy, true, 0);
36+
EXPECT_NEAR(millis(effective_frame_time), 1000.0 / 60.0, 0.001);
37+
}
38+
39+
TEST(VideoInputActivityBoostPolicy, KeepsAutoBoostAtConfiguredCadenceOn144FpsStreams) {
40+
const auto base_frame_time = video::minimum_frame_time_for_vrr(144, 0);
41+
const auto policy = video::make_input_activity_boost_policy({
42+
true,
43+
true,
44+
144,
45+
0,
46+
60,
47+
150,
48+
});
49+
50+
ASSERT_TRUE(policy.useful);
51+
EXPECT_NEAR(millis(base_frame_time), 1000.0 / 72.0, 0.001);
52+
EXPECT_NEAR(millis(video::effective_minimum_frame_time(base_frame_time, policy, true, 0)), 1000.0 / 60.0, 0.001);
53+
}
54+
55+
TEST(VideoInputActivityBoostPolicy, DisablesBoostWhenExplicitMinimumIsAlreadyAsFast) {
56+
const auto policy = video::make_input_activity_boost_policy({
57+
true,
58+
true,
59+
144,
60+
60,
61+
60,
62+
150,
63+
});
64+
65+
EXPECT_TRUE(policy.configured);
66+
EXPECT_FALSE(policy.useful);
67+
68+
const auto base_frame_time = video::minimum_frame_time_for_vrr(144, 60);
69+
EXPECT_NEAR(millis(video::effective_minimum_frame_time(base_frame_time, policy, true, 60)), 1000.0 / 60.0, 0.001);
70+
}
71+
72+
TEST(VideoInputActivityBoostPolicy, UsesBoostWhenExplicitMinimumIsSlower) {
73+
const auto base_frame_time = video::minimum_frame_time_for_vrr(144, 30);
74+
const auto policy = video::make_input_activity_boost_policy({
75+
true,
76+
true,
77+
144,
78+
30,
79+
60,
80+
150,
81+
});
82+
83+
ASSERT_TRUE(policy.useful);
84+
EXPECT_NEAR(millis(video::effective_minimum_frame_time(base_frame_time, policy, true, 30)), 1000.0 / 60.0, 0.001);
85+
}
86+
87+
TEST(VideoInputActivityBoostPolicy, CapsBoostAtStreamFps) {
88+
const auto policy = video::make_input_activity_boost_policy({
89+
true,
90+
true,
91+
120,
92+
0,
93+
240,
94+
150,
95+
});
96+
97+
ASSERT_TRUE(policy.useful);
98+
EXPECT_EQ(policy.fps, 120);
99+
EXPECT_NEAR(millis(policy.frame_time), 1000.0 / 120.0, 0.001);
100+
}
101+
9102
struct EncoderTest: PlatformTestSuite, testing::WithParamInterface<video::encoder_t *> {
10103
void
11104
SetUp() override {

0 commit comments

Comments
 (0)