Don't accumulate errors in capture frame pacing
This commit is contained in:
@@ -120,29 +120,30 @@ namespace platf::dxgi {
|
|||||||
|
|
||||||
capture_e
|
capture_e
|
||||||
display_base_t::capture(const push_captured_image_cb_t &push_captured_image_cb, const pull_free_image_cb_t &pull_free_image_cb, bool *cursor) {
|
display_base_t::capture(const push_captured_image_cb_t &push_captured_image_cb, const pull_free_image_cb_t &pull_free_image_cb, bool *cursor) {
|
||||||
auto calculate_client_frame_interval = [&]() -> std::chrono::nanoseconds {
|
auto adjust_client_frame_rate = [&]() -> DXGI_RATIONAL {
|
||||||
double display_frame_rate = (double) display_refresh_rate.Numerator / display_refresh_rate.Denominator;
|
|
||||||
|
|
||||||
double client_frame_rate_adjusted;
|
|
||||||
if (client_frame_rate >= display_frame_rate) {
|
|
||||||
client_frame_rate_adjusted = display_frame_rate * std::round(client_frame_rate / display_frame_rate);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
client_frame_rate_adjusted = display_frame_rate / std::round(display_frame_rate / client_frame_rate);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Adjust capture frame interval when display refresh rate is not integral but very close to requested fps.
|
// Adjust capture frame interval when display refresh rate is not integral but very close to requested fps.
|
||||||
// Can only decrease requested fps, otherwise client may start accumulating frames and suffer increased latency.
|
if (display_refresh_rate.Denominator > 1) {
|
||||||
if (client_frame_rate > client_frame_rate_adjusted && client_frame_rate_adjusted / client_frame_rate > 0.99) {
|
DXGI_RATIONAL candidate = display_refresh_rate;
|
||||||
BOOST_LOG(info) << "Adjusted capture rate to " << client_frame_rate_adjusted << "fps to better match display";
|
if (client_frame_rate % display_refresh_rate_rounded == 0) {
|
||||||
return std::chrono::nanoseconds(std::llround(std::nano::den / client_frame_rate_adjusted));
|
candidate.Numerator *= client_frame_rate / display_refresh_rate_rounded;
|
||||||
|
}
|
||||||
|
else if (display_refresh_rate_rounded % client_frame_rate == 0) {
|
||||||
|
candidate.Denominator *= display_refresh_rate_rounded / client_frame_rate;
|
||||||
|
}
|
||||||
|
double candidate_rate = (double) candidate.Numerator / candidate.Denominator;
|
||||||
|
// Can only decrease requested fps, otherwise client may start accumulating frames and suffer increased latency.
|
||||||
|
if (client_frame_rate > candidate_rate && candidate_rate / client_frame_rate > 0.99) {
|
||||||
|
BOOST_LOG(info) << "Adjusted capture rate to " << candidate_rate << "fps to better match display";
|
||||||
|
return candidate;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return std::chrono::nanoseconds(1s) / client_frame_rate;
|
return { (uint32_t) client_frame_rate, 1 };
|
||||||
};
|
};
|
||||||
|
|
||||||
const auto client_frame_interval = calculate_client_frame_interval();
|
DXGI_RATIONAL client_frame_rate_adjusted = adjust_client_frame_rate();
|
||||||
std::optional<std::chrono::steady_clock::time_point> next_frame_time;
|
std::optional<std::chrono::steady_clock::time_point> frame_pacing_group_start;
|
||||||
|
uint32_t frame_pacing_group_frames = 0;
|
||||||
|
|
||||||
// Keep the display awake during capture. If the display goes to sleep during
|
// Keep the display awake during capture. If the display goes to sleep during
|
||||||
// capture, best case is that capture stops until it powers back on. However,
|
// capture, best case is that capture stops until it powers back on. However,
|
||||||
@@ -167,12 +168,18 @@ namespace platf::dxgi {
|
|||||||
std::shared_ptr<img_t> img_out;
|
std::shared_ptr<img_t> img_out;
|
||||||
|
|
||||||
// Try to continue frame pacing group, snapshot() is called with zero timeout after waiting for client frame interval
|
// Try to continue frame pacing group, snapshot() is called with zero timeout after waiting for client frame interval
|
||||||
if (next_frame_time) {
|
if (frame_pacing_group_start) {
|
||||||
const auto sleep_period = *next_frame_time - std::chrono::steady_clock::now();
|
const uint32_t seconds = (uint64_t) frame_pacing_group_frames * client_frame_rate_adjusted.Denominator / client_frame_rate_adjusted.Numerator;
|
||||||
|
const uint32_t remainder = (uint64_t) frame_pacing_group_frames * client_frame_rate_adjusted.Denominator % client_frame_rate_adjusted.Numerator;
|
||||||
|
const auto sleep_target = *frame_pacing_group_start +
|
||||||
|
std::chrono::nanoseconds(1s) * seconds +
|
||||||
|
std::chrono::nanoseconds(1s) * remainder / client_frame_rate_adjusted.Numerator;
|
||||||
|
const auto sleep_period = sleep_target - std::chrono::steady_clock::now();
|
||||||
|
|
||||||
if (sleep_period <= 0ns) {
|
if (sleep_period <= 0ns) {
|
||||||
// We missed next frame time, invalidating current frame pacing group
|
// We missed next frame time, invalidating current frame pacing group
|
||||||
next_frame_time = std::nullopt;
|
frame_pacing_group_start = std::nullopt;
|
||||||
|
frame_pacing_group_frames = 0;
|
||||||
status = capture_e::timeout;
|
status = capture_e::timeout;
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
@@ -184,34 +191,35 @@ namespace platf::dxgi {
|
|||||||
auto f = stat_trackers::one_digit_after_decimal();
|
auto f = stat_trackers::one_digit_after_decimal();
|
||||||
BOOST_LOG(debug) << "Sleep overshoot (min/max/avg): " << f % min_overshoot << "ms/" << f % max_overshoot << "ms/" << f % avg_overshoot << "ms";
|
BOOST_LOG(debug) << "Sleep overshoot (min/max/avg): " << f % min_overshoot << "ms/" << f % max_overshoot << "ms/" << f % avg_overshoot << "ms";
|
||||||
};
|
};
|
||||||
std::chrono::nanoseconds overshoot_ns = std::chrono::steady_clock::now() - *next_frame_time;
|
std::chrono::nanoseconds overshoot_ns = std::chrono::steady_clock::now() - sleep_target;
|
||||||
sleep_overshoot_tracker.collect_and_callback_on_interval(overshoot_ns.count() / 1000000., print_info, 20s);
|
sleep_overshoot_tracker.collect_and_callback_on_interval(overshoot_ns.count() / 1000000., print_info, 20s);
|
||||||
}
|
}
|
||||||
|
|
||||||
status = snapshot(pull_free_image_cb, img_out, 0ms, *cursor);
|
status = snapshot(pull_free_image_cb, img_out, 0ms, *cursor);
|
||||||
|
|
||||||
if (status == capture_e::ok && img_out) {
|
if (status == capture_e::ok && img_out) {
|
||||||
*next_frame_time += client_frame_interval;
|
frame_pacing_group_frames += 1;
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
next_frame_time = std::nullopt;
|
frame_pacing_group_start = std::nullopt;
|
||||||
|
frame_pacing_group_frames = 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Start new frame pacing group if necessary, snapshot() is called with non-zero timeout
|
// Start new frame pacing group if necessary, snapshot() is called with non-zero timeout
|
||||||
if (status == capture_e::timeout || (status == capture_e::ok && !next_frame_time)) {
|
if (status == capture_e::timeout || (status == capture_e::ok && !frame_pacing_group_start)) {
|
||||||
status = snapshot(pull_free_image_cb, img_out, 1000ms, *cursor);
|
status = snapshot(pull_free_image_cb, img_out, 1000ms, *cursor);
|
||||||
|
|
||||||
if (status == capture_e::ok && img_out) {
|
if (status == capture_e::ok && img_out) {
|
||||||
next_frame_time = img_out->frame_timestamp;
|
frame_pacing_group_start = img_out->frame_timestamp;
|
||||||
|
|
||||||
if (!next_frame_time) {
|
if (!frame_pacing_group_start) {
|
||||||
BOOST_LOG(warning) << "snapshot() provided image without timestamp";
|
BOOST_LOG(warning) << "snapshot() provided image without timestamp";
|
||||||
next_frame_time = std::chrono::steady_clock::now();
|
frame_pacing_group_start = std::chrono::steady_clock::now();
|
||||||
}
|
}
|
||||||
|
|
||||||
*next_frame_time += client_frame_interval;
|
frame_pacing_group_frames = 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user