#include "Frame_Scheduler.h" #include #include #include 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_Stats& 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_Stats& 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_Stats& 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_Stats 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(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(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(candidate_period_ns) <= static_cast(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(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(ms * 1000000.0); } } // namespace renderive