279 lines
11 KiB
C++
279 lines
11 KiB
C++
#include "Frame_Scheduler.h"
|
|
#include <algorithm>
|
|
#include <cmath>
|
|
#include <utility>
|
|
|
|
namespace renderive {
|
|
namespace {
|
|
constexpr double Ratio_Threshold = 1.03;
|
|
constexpr int Switch_Samples = 3;
|
|
}
|
|
|
|
void Smoothed_Interval::update(double sample_ms) {
|
|
if (sample_ms <= 0.0 || !std::isfinite(sample_ms))
|
|
return;
|
|
if (!sample_count) {
|
|
srtt_ms = sample_ms;
|
|
jitter_ms = sample_ms * 0.5;
|
|
sample_count = 1;
|
|
return;
|
|
}
|
|
double error = std::abs(sample_ms - srtt_ms);
|
|
jitter_ms += 0.25 * (error - jitter_ms);
|
|
srtt_ms += 0.125 * (sample_ms - srtt_ms);
|
|
++sample_count;
|
|
}
|
|
|
|
double Smoothed_Interval::safe_ms(double jitter_factor) const {
|
|
return sample_count ? srtt_ms + jitter_factor * jitter_ms : 0.0;
|
|
}
|
|
|
|
bool Smoothed_Interval::valid() const {
|
|
return sample_count > 0;
|
|
}
|
|
|
|
void Frame_Lifecycle_Record::record_renderable_render(const std::string& object_name, const std::string& parent_object_name, int tree_depth, Renderable_Cache_Mode cache_mode, std::uint64_t render_time_ns) {
|
|
Renderable_Frame_Stat& stat = renderable_stat(object_name, parent_object_name, tree_depth);
|
|
stat.cache_enabled = cache_mode == Renderable_Cache_Mode::Local_Pixel;
|
|
++stat.render_count;
|
|
stat.total_render_time_ns += render_time_ns;
|
|
stat.last_total_render_time_ns = render_time_ns;
|
|
}
|
|
|
|
void Frame_Lifecycle_Record::record_renderable_self_draw(const std::string& object_name, const std::string& parent_object_name, int tree_depth, Renderable_Cache_Mode cache_mode, std::uint64_t draw_time_ns) {
|
|
Renderable_Frame_Stat& stat = renderable_stat(object_name, parent_object_name, tree_depth);
|
|
stat.cache_enabled = cache_mode == Renderable_Cache_Mode::Local_Pixel;
|
|
++stat.self_draw_count;
|
|
stat.self_draw_time_ns += draw_time_ns;
|
|
stat.last_self_draw_time_ns = draw_time_ns;
|
|
}
|
|
|
|
void Frame_Lifecycle_Record::record_renderable_cache_hit(const std::string& object_name, const std::string& parent_object_name, int tree_depth) {
|
|
++renderable_cache_hit_count;
|
|
++cache_stat(object_name, parent_object_name, tree_depth).hit_count;
|
|
}
|
|
|
|
void Frame_Lifecycle_Record::record_renderable_cache_miss(const std::string& object_name, const std::string& parent_object_name, int tree_depth) {
|
|
++renderable_cache_miss_count;
|
|
++cache_stat(object_name, parent_object_name, tree_depth).miss_count;
|
|
}
|
|
|
|
void Frame_Lifecycle_Record::record_renderable_cache_rebuild(const std::string& object_name, const std::string& parent_object_name, int tree_depth, std::uint64_t rebuild_time_ns, std::uint64_t image_bytes, std::uint64_t image_area, std::uint64_t edit_version, std::uint64_t ready_version) {
|
|
++renderable_cache_rebuild_count;
|
|
Renderable_Cache_Frame_Stat& stat = cache_stat(object_name, parent_object_name, tree_depth);
|
|
renderable_cache_rebuild_time_ns += rebuild_time_ns;
|
|
renderable_cache_image_bytes += image_bytes;
|
|
renderable_cache_image_area += image_area;
|
|
++stat.rebuild_count;
|
|
stat.rebuild_time_ns += rebuild_time_ns;
|
|
stat.last_rebuild_time_ns = rebuild_time_ns;
|
|
stat.last_image_bytes = image_bytes;
|
|
stat.last_image_area = image_area;
|
|
stat.last_edit_version = edit_version;
|
|
stat.last_ready_version = ready_version;
|
|
}
|
|
|
|
void Frame_Lifecycle_Record::record_renderable_cache_compose(const std::string& object_name, const std::string& parent_object_name, int tree_depth, std::uint64_t compose_time_ns, std::uint64_t image_bytes, std::uint64_t image_area, std::uint64_t edit_version, std::uint64_t ready_version) {
|
|
renderable_cache_compose_time_ns += compose_time_ns;
|
|
Renderable_Cache_Frame_Stat& stat = cache_stat(object_name, parent_object_name, tree_depth);
|
|
stat.compose_time_ns += compose_time_ns;
|
|
stat.last_compose_time_ns = compose_time_ns;
|
|
stat.last_image_bytes = image_bytes;
|
|
stat.last_image_area = image_area;
|
|
stat.last_edit_version = edit_version;
|
|
stat.last_ready_version = ready_version;
|
|
}
|
|
|
|
Renderable_Frame_Stat& Frame_Lifecycle_Record::renderable_stat(const std::string& object_name, const std::string& parent_object_name, int tree_depth) {
|
|
for (auto& stat : renderable_stats) {
|
|
if (stat.object_name == object_name && stat.parent_object_name == parent_object_name && stat.tree_depth == tree_depth)
|
|
return stat;
|
|
}
|
|
Renderable_Frame_Stat stat;
|
|
stat.object_name = object_name;
|
|
stat.parent_object_name = parent_object_name;
|
|
stat.tree_depth = tree_depth;
|
|
renderable_stats.push_back(std::move(stat));
|
|
return renderable_stats.back();
|
|
}
|
|
|
|
Renderable_Cache_Frame_Stat& Frame_Lifecycle_Record::cache_stat(const std::string& object_name, const std::string& parent_object_name, int tree_depth) {
|
|
for (auto& stat : renderable_cache_stats) {
|
|
if (stat.object_name == object_name && stat.parent_object_name == parent_object_name && stat.tree_depth == tree_depth)
|
|
return stat;
|
|
}
|
|
Renderable_Cache_Frame_Stat stat;
|
|
stat.object_name = object_name;
|
|
stat.parent_object_name = parent_object_name;
|
|
stat.tree_depth = tree_depth;
|
|
renderable_cache_stats.push_back(std::move(stat));
|
|
return renderable_cache_stats.back();
|
|
}
|
|
|
|
void Frame_Feedback_Controller::reset() {
|
|
std::uint64_t user_period = state_value.user_period_ns;
|
|
*this = Frame_Feedback_Controller();
|
|
state_value.user_period_ns = user_period;
|
|
refresh_control_snapshot();
|
|
}
|
|
|
|
void Frame_Feedback_Controller::set_max_render_fps(double fps) {
|
|
state_value.user_period_ns = fps > 0.0 ? ms_to_ns(1000.0 / fps) : 0;
|
|
refresh_control_snapshot();
|
|
}
|
|
|
|
void Frame_Feedback_Controller::on_input_changed(std::uint64_t, std::uint64_t input_version) {
|
|
if (input_version)
|
|
latest_input_version = input_version;
|
|
else
|
|
++latest_input_version;
|
|
}
|
|
|
|
void Frame_Feedback_Controller::on_render_started(Frame_Lifecycle_Record& frame, std::uint64_t now_ns) {
|
|
last_render_begin_ns = now_ns;
|
|
frame.render_begin_ns = now_ns;
|
|
frame.wait_21_ns = now_ns > frame.render_role_enter_ns ? now_ns - frame.render_role_enter_ns : 0;
|
|
refresh_control_snapshot();
|
|
write_state(frame);
|
|
}
|
|
|
|
void Frame_Feedback_Controller::on_render_completed(Frame_Lifecycle_Record& frame) {
|
|
std::uint64_t render_duration_ns = frame.render_end_ns > frame.render_begin_ns ? frame.render_end_ns - frame.render_begin_ns : 0;
|
|
last_render_begin_ns = frame.render_begin_ns;
|
|
render_period.update(static_cast<double>(render_duration_ns + frame.wait_21_ns) / 1000000.0);
|
|
refresh_control_snapshot();
|
|
write_state(frame);
|
|
}
|
|
|
|
void Frame_Feedback_Controller::on_frame_presented(Frame_Lifecycle_Record& frame) {
|
|
std::uint64_t paint_duration_ns = frame.paint_end_ns > frame.paint_begin_ns ? frame.paint_end_ns - frame.paint_begin_ns : 0;
|
|
paint_period.update(static_cast<double>(paint_duration_ns + frame.wait_23_ns) / 1000000.0);
|
|
frame.outcome = Frame_Outcome::Presented;
|
|
refresh_control_snapshot();
|
|
write_state(frame);
|
|
}
|
|
|
|
void Frame_Feedback_Controller::on_frame_superseded(Frame_Lifecycle_Record& frame) {
|
|
frame.outcome = Frame_Outcome::Superseded;
|
|
refresh_control_snapshot();
|
|
write_state(frame);
|
|
}
|
|
|
|
void Frame_Feedback_Controller::on_frame_dropped(Frame_Lifecycle_Record& frame, Frame_Outcome outcome) {
|
|
frame.outcome = outcome;
|
|
refresh_control_snapshot();
|
|
write_state(frame);
|
|
}
|
|
|
|
bool Frame_Feedback_Controller::rate_limit_ready(std::uint64_t now_ns) const {
|
|
return now_ns >= next_render_time;
|
|
}
|
|
|
|
std::uint64_t Frame_Feedback_Controller::next_render_time_ns() const {
|
|
return next_render_time;
|
|
}
|
|
|
|
const Refresh_Control_Snapshot& Frame_Feedback_Controller::state() const {
|
|
return state_value;
|
|
}
|
|
|
|
void Frame_Feedback_Controller::write_state(Frame_Lifecycle_Record& frame) const {
|
|
frame.refresh = state_value;
|
|
}
|
|
|
|
void Frame_Feedback_Controller::refresh_control_snapshot() {
|
|
std::uint64_t render_period_ns = render_period.valid() ? ms_to_ns(render_period.safe_ms(0.5)) : 0;
|
|
std::uint64_t paint_period_ns = paint_period.valid() ? ms_to_ns(paint_period.safe_ms(0.5)) : 0;
|
|
std::uint64_t user_period_ns = state_value.user_period_ns;
|
|
Refresh_Limit_Source candidate = classify_limit_source(render_period_ns, paint_period_ns, user_period_ns);
|
|
std::uint64_t candidate_period = 0;
|
|
switch (candidate) {
|
|
case Refresh_Limit_Source::Render:
|
|
candidate_period = render_period_ns;
|
|
break;
|
|
case Refresh_Limit_Source::Paint:
|
|
candidate_period = paint_period_ns;
|
|
break;
|
|
case Refresh_Limit_Source::User:
|
|
candidate_period = user_period_ns;
|
|
break;
|
|
case Refresh_Limit_Source::Unknown:
|
|
break;
|
|
}
|
|
update_limit_source(candidate, candidate_period);
|
|
std::uint64_t target_period = std::max({render_period_ns, paint_period_ns, user_period_ns});
|
|
state_value.render_period_ns = render_period_ns;
|
|
state_value.paint_period_ns = paint_period_ns;
|
|
state_value.target_period_ns = target_period;
|
|
if (state_value.source == Refresh_Limit_Source::Unknown)
|
|
state_value.source = candidate;
|
|
if (state_value.source == Refresh_Limit_Source::User && last_render_begin_ns)
|
|
next_render_time = last_render_begin_ns + user_period_ns;
|
|
else
|
|
next_render_time = 0;
|
|
}
|
|
|
|
Refresh_Limit_Source Frame_Feedback_Controller::classify_limit_source(std::uint64_t render_period_ns, std::uint64_t paint_period_ns, std::uint64_t user_period_ns) const {
|
|
if (!render_period_ns && !paint_period_ns)
|
|
return user_period_ns ? Refresh_Limit_Source::User : Refresh_Limit_Source::Unknown;
|
|
std::uint64_t max_period = std::max({render_period_ns, paint_period_ns, user_period_ns});
|
|
if (max_period == user_period_ns && user_period_ns)
|
|
return Refresh_Limit_Source::User;
|
|
if (max_period == paint_period_ns && paint_period_ns)
|
|
return Refresh_Limit_Source::Paint;
|
|
if (max_period == render_period_ns && render_period_ns)
|
|
return Refresh_Limit_Source::Render;
|
|
return Refresh_Limit_Source::Unknown;
|
|
}
|
|
|
|
void Frame_Feedback_Controller::update_limit_source(Refresh_Limit_Source candidate, std::uint64_t candidate_period_ns) {
|
|
if (candidate == Refresh_Limit_Source::Unknown)
|
|
return;
|
|
if (state_value.source == Refresh_Limit_Source::Unknown) {
|
|
state_value.source = candidate;
|
|
candidate_source = candidate;
|
|
candidate_count = 0;
|
|
return;
|
|
}
|
|
std::uint64_t current_period = 0;
|
|
switch (state_value.source) {
|
|
case Refresh_Limit_Source::Render:
|
|
current_period = state_value.render_period_ns;
|
|
break;
|
|
case Refresh_Limit_Source::Paint:
|
|
current_period = state_value.paint_period_ns;
|
|
break;
|
|
case Refresh_Limit_Source::User:
|
|
current_period = state_value.user_period_ns;
|
|
break;
|
|
case Refresh_Limit_Source::Unknown:
|
|
break;
|
|
}
|
|
if (candidate == state_value.source || (current_period && static_cast<double>(candidate_period_ns) <= static_cast<double>(current_period) * Ratio_Threshold)) {
|
|
candidate_source = candidate;
|
|
candidate_count = 0;
|
|
return;
|
|
}
|
|
if (candidate != candidate_source) {
|
|
candidate_source = candidate;
|
|
candidate_count = 1;
|
|
return;
|
|
}
|
|
if (++candidate_count >= Switch_Samples) {
|
|
state_value.source = candidate;
|
|
candidate_count = 0;
|
|
}
|
|
}
|
|
|
|
double Frame_Feedback_Controller::duration_ms(std::uint64_t begin_ns, std::uint64_t end_ns) {
|
|
return end_ns > begin_ns ? static_cast<double>(end_ns - begin_ns) / 1000000.0 : 0.0;
|
|
}
|
|
|
|
std::uint64_t Frame_Feedback_Controller::ms_to_ns(double ms) {
|
|
if (ms <= 0.0 || !std::isfinite(ms))
|
|
return 0;
|
|
return static_cast<std::uint64_t>(ms * 1000000.0);
|
|
}
|
|
|
|
} // namespace renderive
|