diff --git a/Renderive/architecture/Frame_Scheduler.cpp b/Renderive/architecture/Frame_Scheduler.cpp new file mode 100644 index 0000000..3d470e3 --- /dev/null +++ b/Renderive/architecture/Frame_Scheduler.cpp @@ -0,0 +1,281 @@ +#include "Frame_Scheduler.h" +#include +#include +namespace renderive { +namespace { +constexpr double Ratio_Alpha = 0.125; +constexpr int Hysteresis_Samples = 4; +constexpr int Balanced_Hysteresis_Samples = 8; +double max_valid(double a, double b, double c) { + return std::max(a, std::max(b, c)); +} +} +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_Feedback_Controller::reset() { + double manual_interval = state_value.manual_min_render_interval_ms; + *this = Frame_Feedback_Controller(); + state_value.manual_min_render_interval_ms = manual_interval; + state_value.effective_min_render_interval_ms = manual_interval; +} +void Frame_Feedback_Controller::set_max_render_fps(double fps) { + state_value.manual_min_render_interval_ms = fps > 0.0 ? 1000.0 / fps : 0.0; + state_value.effective_min_render_interval_ms = std::max(state_value.manual_min_render_interval_ms, state_value.adaptive_min_render_interval_ms); +} +void Frame_Feedback_Controller::on_input_changed(std::uint64_t now_ns, std::uint64_t input_version) { + if (last_input_time) + input_interval.update(duration_ms(last_input_time, now_ns)); + last_input_time = now_ns; + if (input_version) + state_value.latest_input_version = input_version; + else + ++state_value.latest_input_version; + update_rates(); +} +void Frame_Feedback_Controller::on_render_started(Frame_Metadata& frame, std::uint64_t now_ns) { + if (last_render_begin_time) + render_interval.update(duration_ms(last_render_begin_time, now_ns)); + last_render_begin_time = now_ns; + state_value.rendering_input_version = frame.input_version; + frame.render_begin_time = now_ns; + double interval_ms = std::max(state_value.manual_min_render_interval_ms, state_value.adaptive_min_render_interval_ms); + state_value.effective_min_render_interval_ms = interval_ms; + next_render_time = now_ns + static_cast(interval_ms * 1000000.0); + update_rates(); + write_state(frame); +} +void Frame_Feedback_Controller::on_render_completed(Frame_Metadata& frame, bool ready_occupied, bool consumer_busy, bool input_pending) { + render_queue_wait.update(duration_ms(frame.render_queue_enter_time, frame.render_begin_time)); + render_duration.update(duration_ms(frame.render_begin_time, frame.render_end_time)); + ++state_value.produced_frame_count; + state_value.published_input_version = frame.input_version; + double busy_sample = 1.0; + if (render_interval.valid()) + busy_sample = std::clamp(render_duration.srtt_ms / std::max(render_interval.srtt_ms, render_duration.srtt_ms), 0.0, 1.0); + state_value.producer_busy_ratio = smooth_ratio(state_value.producer_busy_ratio, busy_sample); + state_value.ready_frame_occupancy_ratio = smooth_ratio(state_value.ready_frame_occupancy_ratio, ready_occupied ? 1.0 : 0.0); + state_value.consumer_busy_ratio = smooth_ratio(state_value.consumer_busy_ratio, consumer_busy ? 1.0 : 0.0); + state_value.input_pending_ratio = smooth_ratio(state_value.input_pending_ratio, input_pending ? 1.0 : 0.0); + frame.frame_outcome = Frame_Outcome::Produced; + update_control(ready_occupied, consumer_busy, input_pending); + write_state(frame); +} +void Frame_Feedback_Controller::on_frame_consumed(Frame_Metadata& frame, bool ready_occupied) { + ready_frame_wait.update(duration_ms(frame.publish_time, frame.paint_begin_time)); + update_queue_wait.update(duration_ms(frame.update_request_time, frame.paint_begin_time)); + paint_duration.update(duration_ms(frame.paint_begin_time, frame.paint_end_time)); + frame_age.update(duration_ms(frame.core_data_time, frame.paint_end_time)); + if (last_consume_time) + consume_interval.update(duration_ms(last_consume_time, frame.paint_begin_time)); + last_consume_time = frame.paint_begin_time; + ++state_value.consumed_frame_count; + state_value.consumed_input_version = frame.input_version; + state_value.superseded_ready_ratio = smooth_ratio(state_value.superseded_ready_ratio, 0.0); + state_value.consumer_underrun_ratio = smooth_ratio(state_value.consumer_underrun_ratio, 0.0); + state_value.ready_frame_occupancy_ratio = smooth_ratio(state_value.ready_frame_occupancy_ratio, ready_occupied ? 1.0 : 0.0); + double consumer_sample = 0.0; + if (consume_interval.valid()) + consumer_sample = std::clamp(paint_duration.srtt_ms / std::max(consume_interval.srtt_ms, paint_duration.srtt_ms), 0.0, 1.0); + state_value.consumer_busy_ratio = smooth_ratio(state_value.consumer_busy_ratio, consumer_sample); + frame.frame_outcome = Frame_Outcome::Consumed; + update_control(ready_occupied, false, state_value.latest_input_version > state_value.published_input_version); + write_state(frame); +} +void Frame_Feedback_Controller::on_frame_superseded(Frame_Metadata& frame, bool consumer_busy, bool input_pending) { + ready_frame_wait.update(duration_ms(frame.publish_time, frame.superseded_time)); + ++state_value.superseded_frame_count; + state_value.wasted_render_time_ms += duration_ms(frame.render_begin_time, frame.render_end_time); + state_value.superseded_ready_ratio = smooth_ratio(state_value.superseded_ready_ratio, 1.0); + state_value.ready_frame_occupancy_ratio = smooth_ratio(state_value.ready_frame_occupancy_ratio, 1.0); + state_value.consumer_busy_ratio = smooth_ratio(state_value.consumer_busy_ratio, consumer_busy ? 1.0 : 0.0); + state_value.input_pending_ratio = smooth_ratio(state_value.input_pending_ratio, input_pending ? 1.0 : 0.0); + frame.frame_outcome = Frame_Outcome::Superseded; + update_control(true, consumer_busy, input_pending); + write_state(frame); +} +void Frame_Feedback_Controller::on_consumer_underrun(std::uint64_t, bool input_pending) { + ++state_value.repeated_frame_count; + state_value.consumer_underrun_ratio = smooth_ratio(state_value.consumer_underrun_ratio, 1.0); + state_value.input_pending_ratio = smooth_ratio(state_value.input_pending_ratio, input_pending ? 1.0 : 0.0); + update_control(false, false, input_pending); +} +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 Frame_Feedback_State& Frame_Feedback_Controller::state() const { + return state_value; +} +void Frame_Feedback_Controller::write_state(Frame_Metadata& frame) const { + frame.feedback = state_value; +} +void Frame_Feedback_Controller::update_control(bool ready_occupied, bool consumer_busy, bool input_pending) { + update_rates(); + Bottleneck_State candidate = classify_bottleneck(ready_occupied, consumer_busy, input_pending); + update_bottleneck(candidate); + bool consumer_slower_than_render = paint_duration.valid() && render_duration.valid() && paint_duration.safe_ms(0.0) > render_duration.safe_ms(0.0) * 1.25; + bool backlog = ready_occupied || state_value.ready_frame_occupancy_ratio > 0.2 || state_value.superseded_ready_ratio > 0.03 || (consumer_busy && input_pending && consumer_slower_than_render); + bool ready_waiting = ready_frame_wait.valid() && paint_duration.valid() && ready_frame_wait.safe_ms(0.5) > paint_duration.safe_ms(0.5) * 0.5; + bool overproducing = state_value.superseded_ready_ratio > 0.03 || (backlog && ready_waiting) || (ready_occupied && consumer_busy); + update_effective_interval(overproducing); + state_value.display_efficiency = state_value.produced_frame_count ? static_cast(state_value.consumed_frame_count) / static_cast(state_value.produced_frame_count) : 0.0; + std::uint64_t display_events = state_value.consumed_frame_count + state_value.repeated_frame_count; + state_value.unique_consumed_ratio = display_events ? static_cast(state_value.consumed_frame_count) / static_cast(display_events) : 1.0; +} +Bottleneck_State Frame_Feedback_Controller::classify_bottleneck(bool ready_occupied, bool consumer_busy, bool input_pending) const { + double input_safe = input_interval.safe_ms(); + double render_safe = render_duration.safe_ms(); + double paint_safe = paint_duration.safe_ms(); + double manual = state_value.manual_min_render_interval_ms; + double capability_safe = max_valid(input_safe, render_safe, paint_safe); + if (manual > 0.0 && manual > capability_safe * 1.1) + return Bottleneck_State::Manual_Limited; + bool consumer_limited = state_value.superseded_ready_ratio > 0.08; + consumer_limited = consumer_limited || (input_pending && paint_duration.valid() && render_duration.valid() && paint_safe > render_safe * 1.25); + consumer_limited = consumer_limited || (consume_interval.valid() && render_duration.valid() && consume_interval.safe_ms() > render_safe * 1.25 && (ready_occupied || state_value.ready_frame_occupancy_ratio > 0.35)); + consumer_limited = consumer_limited || (update_queue_wait.valid() && render_duration.valid() && update_queue_wait.safe_ms() > render_safe && state_value.display_efficiency < 0.9); + consumer_limited = consumer_limited || (consumer_busy && ready_occupied); + if (consumer_limited) + return Bottleneck_State::Consumer_Limited; + bool producer_limited = input_pending && state_value.producer_busy_ratio > 0.82 && render_duration.valid() && (!paint_duration.valid() || render_safe >= paint_safe * 0.9); + if (producer_limited) + return Bottleneck_State::Producer_Limited; + bool input_limited = input_interval.valid() && render_duration.valid() && input_safe > std::max(render_safe, paint_safe) * 1.25 && state_value.producer_busy_ratio < 0.75; + if (input_limited) + return Bottleneck_State::Input_Limited; + if (render_duration.valid() && (paint_duration.valid() || state_value.produced_frame_count >= 8)) + return Bottleneck_State::Balanced; + return Bottleneck_State::Unknown; +} +void Frame_Feedback_Controller::update_bottleneck(Bottleneck_State candidate) { + if (candidate == Bottleneck_State::Unknown) + return; + if (candidate != candidate_state) { + candidate_state = candidate; + candidate_count = 1; + return; + } + ++candidate_count; + int required = candidate == Bottleneck_State::Balanced ? Balanced_Hysteresis_Samples : Hysteresis_Samples; + if (candidate_count >= required) + state_value.bottleneck_state = candidate; +} +void Frame_Feedback_Controller::update_rates() { + state_value.input_interval_srtt_ms = input_interval.srtt_ms; + state_value.input_interval_jitter_ms = input_interval.jitter_ms; + state_value.render_interval_srtt_ms = render_interval.srtt_ms; + state_value.render_interval_jitter_ms = render_interval.jitter_ms; + state_value.render_queue_wait_srtt_ms = render_queue_wait.srtt_ms; + state_value.render_queue_wait_jitter_ms = render_queue_wait.jitter_ms; + state_value.render_duration_srtt_ms = render_duration.srtt_ms; + state_value.render_duration_jitter_ms = render_duration.jitter_ms; + state_value.ready_frame_wait_srtt_ms = ready_frame_wait.srtt_ms; + state_value.ready_frame_wait_jitter_ms = ready_frame_wait.jitter_ms; + state_value.update_queue_wait_srtt_ms = update_queue_wait.srtt_ms; + state_value.update_queue_wait_jitter_ms = update_queue_wait.jitter_ms; + state_value.paint_duration_srtt_ms = paint_duration.srtt_ms; + state_value.paint_duration_jitter_ms = paint_duration.jitter_ms; + state_value.consume_interval_srtt_ms = consume_interval.srtt_ms; + state_value.consume_interval_jitter_ms = consume_interval.jitter_ms; + state_value.frame_age_srtt_ms = frame_age.srtt_ms; + state_value.frame_age_jitter_ms = frame_age.jitter_ms; + state_value.input_rate_fps = rate_from_interval(input_interval.srtt_ms); + state_value.production_rate_fps = rate_from_interval(render_interval.srtt_ms); + state_value.producer_capacity_fps = rate_from_interval(render_duration.srtt_ms); + state_value.display_rate_fps = rate_from_interval(consume_interval.srtt_ms); + state_value.consumer_capacity_fps = rate_from_interval(paint_duration.srtt_ms); + state_value.display_efficiency = state_value.produced_frame_count ? static_cast(state_value.consumed_frame_count) / static_cast(state_value.produced_frame_count) : 0.0; +} +void Frame_Feedback_Controller::update_effective_interval(bool overproducing) { + double& adaptive = state_value.adaptive_min_render_interval_ms; + if (state_value.bottleneck_state == Bottleneck_State::Consumer_Limited) { + double target = paint_duration.safe_ms(0.5); + if (consume_interval.valid() && target > 0.0 && (state_value.ready_frame_occupancy_ratio > 0.2 || state_value.superseded_ready_ratio > 0.03)) { + double cadence_target = consume_interval.safe_ms(0.0) * 0.95; + target = std::max(target, std::min(cadence_target, target * 1.5)); + } + if (target <= 0.0) + target = render_duration.safe_ms(0.5); + if (overproducing) { + adaptive = adaptive > 0.0 ? std::min(std::max(adaptive, target), target * 1.2) : target; + } + else if (adaptive > target) { + adaptive = std::max(target, adaptive * 0.96); + } + else { + adaptive = target; + } + } + else { + adaptive *= 0.98; + if (adaptive < 0.05) + adaptive = 0.0; + } + state_value.effective_min_render_interval_ms = std::max(state_value.manual_min_render_interval_ms, adaptive); + if (last_render_begin_time) { + std::uint64_t controlled_time = last_render_begin_time + static_cast(state_value.effective_min_render_interval_ms * 1000000.0); + next_render_time = std::max(next_render_time, controlled_time); + } +} +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; +} +double Frame_Feedback_Controller::rate_from_interval(double interval_ms) { + return interval_ms > 0.0 ? 1000.0 / interval_ms : 0.0; +} +double Frame_Feedback_Controller::smooth_ratio(double current, double sample) { + return current + Ratio_Alpha * (std::clamp(sample, 0.0, 1.0) - current); +} +const char* bottleneck_state_name(Bottleneck_State state) { + switch (state) { + case Bottleneck_State::Unknown: + return "Unknown"; + case Bottleneck_State::Input_Limited: + return "Input_Limited"; + case Bottleneck_State::Producer_Limited: + return "Producer_Limited"; + case Bottleneck_State::Consumer_Limited: + return "Consumer_Limited"; + case Bottleneck_State::Manual_Limited: + return "Manual_Limited"; + case Bottleneck_State::Balanced: + return "Balanced"; + } + return "Unknown"; +} +const char* frame_outcome_name(Frame_Outcome outcome) { + switch (outcome) { + case Frame_Outcome::Produced: + return "Produced"; + case Frame_Outcome::Consumed: + return "Consumed"; + case Frame_Outcome::Superseded: + return "Superseded"; + case Frame_Outcome::Cancelled: + return "Cancelled"; + case Frame_Outcome::Repeated: + return "Repeated"; + } + return "Produced"; +} +} // namespace renderive diff --git a/Renderive/architecture/Frame_Scheduler.h b/Renderive/architecture/Frame_Scheduler.h new file mode 100644 index 0000000..60173cb --- /dev/null +++ b/Renderive/architecture/Frame_Scheduler.h @@ -0,0 +1,172 @@ +#pragma once +#include +#include +#include +#include "Render_Config.h" +namespace renderive { +struct Smoothed_Interval { + double srtt_ms{}; + double jitter_ms{}; + std::uint64_t sample_count{}; + void update(double sample_ms); + [[nodiscard]] double safe_ms(double jitter_factor = 2.0) const; + [[nodiscard]] bool valid() const; +}; +struct Frame_Feedback_State { + Bottleneck_State bottleneck_state = Bottleneck_State::Unknown; + double manual_min_render_interval_ms{}; + double adaptive_min_render_interval_ms{}; + double effective_min_render_interval_ms{}; + double input_interval_srtt_ms{}; + double input_interval_jitter_ms{}; + double render_interval_srtt_ms{}; + double render_interval_jitter_ms{}; + double render_queue_wait_srtt_ms{}; + double render_queue_wait_jitter_ms{}; + double render_duration_srtt_ms{}; + double render_duration_jitter_ms{}; + double ready_frame_wait_srtt_ms{}; + double ready_frame_wait_jitter_ms{}; + double update_queue_wait_srtt_ms{}; + double update_queue_wait_jitter_ms{}; + double paint_duration_srtt_ms{}; + double paint_duration_jitter_ms{}; + double consume_interval_srtt_ms{}; + double consume_interval_jitter_ms{}; + double frame_age_srtt_ms{}; + double frame_age_jitter_ms{}; + double input_rate_fps{}; + double production_rate_fps{}; + double producer_capacity_fps{}; + double display_rate_fps{}; + double consumer_capacity_fps{}; + double producer_busy_ratio{}; + double ready_frame_occupancy_ratio{}; + double consumer_busy_ratio{}; + double input_pending_ratio{}; + double superseded_ready_ratio{}; + double consumer_underrun_ratio{}; + double unique_consumed_ratio{}; + double display_efficiency{}; + double wasted_render_time_ms{}; + std::uint64_t latest_input_version{}; + std::uint64_t rendering_input_version{}; + std::uint64_t published_input_version{}; + std::uint64_t consumed_input_version{}; + std::uint64_t produced_frame_count{}; + std::uint64_t consumed_frame_count{}; + std::uint64_t superseded_frame_count{}; + std::uint64_t repeated_frame_count{}; +}; +struct Renderable_Cache_Frame_Stat { + QString object_name; + std::uint64_t hit_count{}; + std::uint64_t stale_hit_count{}; + std::uint64_t miss_count{}; +}; +struct Frame_Metadata { + std::uint64_t frame_id{}; + std::uint64_t input_version{}; + std::uint64_t snapshot_version{}; + std::uint64_t frame_slot_generation{}; + std::uint64_t core_data_time{}; + std::uint64_t source_data_time_begin{}; + std::uint64_t source_data_time_end{}; + std::uint64_t snapshot_capture_begin_time{}; + std::uint64_t snapshot_capture_end_time{}; + std::uint64_t render_request_time{}; + std::uint64_t render_queue_enter_time{}; + std::uint64_t render_begin_time{}; + std::uint64_t prepare_begin_time{}; + std::uint64_t prepare_end_time{}; + std::uint64_t draw_begin_time{}; + std::uint64_t draw_end_time{}; + std::uint64_t render_end_time{}; + std::uint64_t publish_time{}; + std::uint64_t update_request_id{}; + std::uint64_t update_request_time{}; + std::uint64_t paint_begin_time{}; + std::uint64_t paint_end_time{}; + std::uint64_t consumer_release_time{}; + std::uint64_t superseded_time{}; + Frame_Outcome frame_outcome = Frame_Outcome::Produced; + std::uint64_t raw_input_count{}; + std::uint64_t visible_input_count{}; + std::uint64_t prepared_output_count{}; + std::uint64_t draw_point_count{}; + std::uint64_t draw_segment_count{}; + std::uint64_t renderable_cache_hit_count{}; + std::uint64_t renderable_cache_stale_hit_count{}; + std::uint64_t renderable_cache_miss_count{}; + std::uint64_t pixel_width{}; + std::uint64_t pixel_height{}; + std::uint64_t renderable_count{}; + std::uint64_t visible_renderable_count{}; + std::uint64_t direct_draw_count{}; + std::uint64_t local_cache_count{}; + std::vector renderable_cache_stats; + Frame_Feedback_State feedback; + void record_renderable_cache_hit(const QString& object_name) { + ++renderable_cache_hit_count; + ++cache_stat(object_name).hit_count; + } + void record_renderable_cache_stale_hit(const QString& object_name) { + ++renderable_cache_stale_hit_count; + ++cache_stat(object_name).stale_hit_count; + } + void record_renderable_cache_miss(const QString& object_name) { + ++renderable_cache_miss_count; + ++cache_stat(object_name).miss_count; + } + Renderable_Cache_Frame_Stat& cache_stat(const QString& object_name) { + for (auto& stat : renderable_cache_stats) { + if (stat.object_name == object_name) + return stat; + } + renderable_cache_stats.push_back({object_name}); + return renderable_cache_stats.back(); + } +}; +class Frame_Feedback_Controller { +public: + void reset(); + void set_max_render_fps(double fps); + void on_input_changed(std::uint64_t now_ns, std::uint64_t input_version = 0); + void on_render_started(Frame_Metadata& frame, std::uint64_t now_ns); + void on_render_completed(Frame_Metadata& frame, bool ready_occupied, bool consumer_busy, bool input_pending); + void on_frame_consumed(Frame_Metadata& frame, bool ready_occupied); + void on_frame_superseded(Frame_Metadata& frame, bool consumer_busy, bool input_pending); + void on_consumer_underrun(std::uint64_t now_ns, bool input_pending); + [[nodiscard]] bool rate_limit_ready(std::uint64_t now_ns) const; + [[nodiscard]] std::uint64_t next_render_time_ns() const; + [[nodiscard]] const Frame_Feedback_State& state() const; + void write_state(Frame_Metadata& frame) const; +private: + void update_control(bool ready_occupied, bool consumer_busy, bool input_pending); + Bottleneck_State classify_bottleneck(bool ready_occupied, bool consumer_busy, bool input_pending) const; + void update_bottleneck(Bottleneck_State candidate); + void update_rates(); + void update_effective_interval(bool overproducing); + static double duration_ms(std::uint64_t begin_ns, std::uint64_t end_ns); + static double rate_from_interval(double interval_ms); + static double smooth_ratio(double current, double sample); + Frame_Feedback_State state_value; + Smoothed_Interval input_interval; + Smoothed_Interval render_interval; + Smoothed_Interval render_queue_wait; + Smoothed_Interval render_duration; + Smoothed_Interval ready_frame_wait; + Smoothed_Interval update_queue_wait; + Smoothed_Interval paint_duration; + Smoothed_Interval consume_interval; + Smoothed_Interval frame_age; + Bottleneck_State candidate_state = Bottleneck_State::Unknown; + int candidate_count{}; + std::uint64_t last_input_time{}; + std::uint64_t last_render_begin_time{}; + std::uint64_t last_consume_time{}; + std::uint64_t next_render_time{}; +}; +const char* bottleneck_state_name(Bottleneck_State state); +const char* frame_outcome_name(Frame_Outcome outcome); +} // namespace renderive diff --git a/Renderive/architecture/Plot.cpp b/Renderive/architecture/Plot.cpp index 22a681b..60a1714 100644 --- a/Renderive/architecture/Plot.cpp +++ b/Renderive/architecture/Plot.cpp @@ -1,44 +1,23 @@ #include #include #include +#include "../Axis/Time_Axis.h" #include "../base/Frame_Memory.h" #include "../base/Memory.h" -#include "../base/Pmr_QImage_Buffer.h" #include "../base/Task.h" #include "../base/global.h" -#include "../Axis/Time_Axis.h" #include "../plottable/Performance_Shower_p.h" #include "Plot_p.h" #include "Update_Completion.h" namespace renderive { -static std::uint64_t steady_now_ns() { +namespace { +std::uint64_t steady_now_ns() { return static_cast(std::chrono::duration_cast(std::chrono::steady_clock::now().time_since_epoch()).count()); } -static double elapsed_ms(std::uint64_t start_ns, std::uint64_t end_ns) { - if (end_ns <= start_ns) - return 0.0; - return static_cast(end_ns - start_ns) / 1000000.0; -} -static QString job_state_text(Renderable::Job_State state) { - switch (state) { - case Renderable::Job_State::Idle: - return "Idle"; - case Renderable::Job_State::Queued: - return "Queued"; - case Renderable::Job_State::Running: - return "Running"; - } - return {}; -} -static QString renderable_name(Renderable* able) { - if (!able->object_name.isEmpty()) - return able->object_name; - return QString::number(reinterpret_cast(able), 16); -} -static int renderable_prepare_priority(const std::shared_ptr& able) { +int renderable_prepare_priority(const std::shared_ptr& able) { return dynamic_cast(able.get()) ? 0 : 1; } -static std::shared_ptr performance_shower_owner(Plot_Private* data) { +std::shared_ptr performance_shower_owner(Plot_Private* data) { if (!data || !data->q) return {}; auto context = data->q->get_render_context(); @@ -47,96 +26,7 @@ static std::shared_ptr performance_shower_owner(Plot_Private std::lock_guard lock(context->mutex); return context->performance_shower_value; } -static QString acquire_mode_text(Plot_Buffer_Acquire_Mode mode) { - return mode == Plot_Buffer_Acquire_Mode::Wait ? "Wait" : "Try"; -} -static QString plot_schedule_mode_text(Plot_Render_Schedule_Mode mode) { - switch (mode) { - case Plot_Render_Schedule_Mode::Timer_Check: - return "Timer_Check"; - case Plot_Render_Schedule_Mode::Chase_Latest: - return "Chase_Latest"; - case Plot_Render_Schedule_Mode::Max_Fps: - return "Max_Fps"; - case Plot_Render_Schedule_Mode::Manual: - return "Manual"; - } - return {}; -} -static QString renderable_schedule_mode_text(Renderable_Render_Schedule_Mode mode) { - switch (mode) { - case Renderable_Render_Schedule_Mode::Inherit: - return "Inherit"; - case Renderable_Render_Schedule_Mode::Chase_Latest: - return "Chase_Latest"; - case Renderable_Render_Schedule_Mode::Max_Fps: - return "Max_Fps"; - case Renderable_Render_Schedule_Mode::On_Dirty: - return "On_Dirty"; - case Renderable_Render_Schedule_Mode::Manual: - return "Manual"; - } - return {}; -} -static QString renderable_cache_mode_text(Renderable_Cache_Mode mode) { - switch (mode) { - case Renderable_Cache_Mode::Direct: - return "Direct"; - case Renderable_Cache_Mode::Local_Pixel: - return "Local_Pixel"; - } - return {}; -} -static void publish_triple_buffer_stats(Performance_Shower* shower, const QString& name, const Triple_Buffer_Stats& stats) { - if (!shower || !shower->enabled()) - return; - shower->set_gauge("Buffer", name + ".markSuccess", static_cast(stats.mark_success.load(std::memory_order_relaxed))); - shower->set_gauge("Buffer", name + ".markBusy", static_cast(stats.mark_busy.load(std::memory_order_relaxed))); - shower->set_gauge("Buffer", name + ".markStateChanged", static_cast(stats.mark_state_changed.load(std::memory_order_relaxed))); - shower->set_gauge("Buffer", name + ".swapSuccess", static_cast(stats.swap_success.load(std::memory_order_relaxed))); - shower->set_gauge("Buffer", name + ".swapBusy", static_cast(stats.swap_busy.load(std::memory_order_relaxed))); - shower->set_gauge("Buffer", name + ".swapStateChanged", static_cast(stats.swap_state_changed.load(std::memory_order_relaxed))); - shower->set_gauge("Buffer", name + ".swapNoNeed", static_cast(stats.swap_condition_failed.load(std::memory_order_relaxed))); - shower->set_gauge("Buffer", name + ".wait", static_cast(stats.wait_count.load(std::memory_order_relaxed))); -} -static void publish_renderable_buffer_stats(Performance_Shower* shower, Renderable* able) { - if (!shower || !shower->enabled()) - return; - if (able == shower) - return; - QString name = renderable_name(able); - shower->set_state("Renderable", name + ".buffer_acquire_mode", acquire_mode_text(able->get_buffer_acquire_mode())); - shower->set_state("Renderable", name + ".render_schedule_mode", renderable_schedule_mode_text(able->get_render_schedule_mode())); - shower->set_state("Renderable", name + ".cache_mode", renderable_cache_mode_text(able->get_cache_mode())); - shower->set_gauge("Renderable", name + ".render_schedule_max_fps", able->get_render_schedule_max_fps()); - std::uint64_t input_pending = 0; - std::uint64_t input_dropped = 0; - std::uint64_t input_capacity_dropped = 0; - std::uint64_t input_rebuild_dropped = 0; - std::uint64_t input_consumer_dropped = 0; - std::uint64_t input_pushed = 0; - for (const Input_Buffer_Slot& slot : able->d_ptr->input_buffers) { - if (!slot.data) - continue; - input_pending += slot.data->get_pending_count(); - input_dropped += slot.data->get_dropped_count(); - input_capacity_dropped += slot.data->get_capacity_dropped_count(); - input_rebuild_dropped += slot.data->rebuild_dropped_count(); - input_consumer_dropped += slot.data->consumer_dropped_count(); - input_pushed += slot.data->get_pushed_count(); - } - shower->set_gauge("Renderable", name + ".input_pending", static_cast(input_pending)); - shower->set_gauge("Renderable", name + ".input_dropped", static_cast(input_dropped)); - shower->set_gauge("Renderable", name + ".input_capacity_dropped", static_cast(input_capacity_dropped)); - shower->set_gauge("Renderable", name + ".input_rebuild_dropped", static_cast(input_rebuild_dropped)); - shower->set_gauge("Renderable", name + ".input_consumer_dropped", static_cast(input_consumer_dropped)); - shower->set_gauge("Renderable", name + ".input_pushed", static_cast(input_pushed)); - shower->set_gauge("Renderable", name + ".pixel_cache_area", static_cast(able->cache_image_area())); - shower->set_gauge("Renderable", name + ".pixel_cache_bytes", static_cast(able->cache_image_bytes())); - publish_triple_buffer_stats(shower, name + ".State", able->d_ptr->state_buffer_stats); - publish_triple_buffer_stats(shower, name + ".Input", able->d_ptr->input_buffer_stats); -} -static std::size_t estimate_frame_memory(const std::vector>& snapshot, const Plot_Render_Snapshot& frame_snapshot) { +std::size_t estimate_frame_memory(const std::vector>& snapshot, const Plot_Render_Snapshot& frame_snapshot) { std::size_t bytes = 64 * 1024; for (const auto& able : snapshot) { if (able && able->d_ptr) @@ -144,160 +34,11 @@ static std::size_t estimate_frame_memory(const std::vectorset_gauge("MemoryDomain", name + ".allocationCount", static_cast(stats.allocation_count)); - shower->set_gauge("MemoryDomain", name + ".deallocationCount", static_cast(stats.deallocation_count)); - shower->set_gauge("MemoryDomain", name + ".currentBytes", static_cast(stats.current_bytes)); - shower->set_gauge("MemoryDomain", name + ".peakBytes", static_cast(stats.peak_bytes)); -} -static void publish_pipeline_stats(Plot_Private* data) { - auto shower_owner = performance_shower_owner(data); - Performance_Shower* shower = shower_owner.get(); - if (!shower || !shower->enabled()) - return; - Memory_Stats memory = memory_stats(); - shower->set_gauge("Memory", "upstreamAllocationCount", static_cast(memory.upstream_allocation_count)); - shower->set_gauge("Memory", "upstreamDeallocationCount", static_cast(memory.upstream_deallocation_count)); - shower->set_gauge("Memory", "upstreamCurrentBytes", static_cast(memory.upstream_current_bytes)); - shower->set_gauge("Memory", "upstreamPeakBytes", static_cast(memory.upstream_peak_bytes)); - Frame_Memory_Stats frame_memory = frame_memory_stats(); - shower->set_gauge("FrameArena", "frameCount", static_cast(frame_memory.frame_count)); - shower->set_gauge("FrameArena", "overflowCount", static_cast(frame_memory.overflow_count)); - shower->set_gauge("FrameArena", "overflowBytes", static_cast(frame_memory.overflow_bytes)); - shower->set_gauge("FrameArena", "highWaterBytes", static_cast(frame_memory.high_water_bytes)); - shower->set_gauge("FrameArena", "scratchGrowCount", static_cast(frame_memory.scratch_grow_count)); - shower->set_gauge("FrameArena", "scratchBytes", static_cast(frame_memory.scratch_bytes)); - Task_Stats task_memory = task_stats(); - shower->set_gauge("Task", "sizeofTask", static_cast(task_memory.sizeof_task)); - shower->set_gauge("Task", "inlineSize", static_cast(task_memory.inline_size)); - shower->set_gauge("Task", "totalCount", static_cast(task_memory.total_count)); - shower->set_gauge("Task", "inlineCount", static_cast(task_memory.inline_count)); - shower->set_gauge("Task", "heapCount", static_cast(task_memory.heap_count)); - shower->set_gauge("Task", "heapBytes", static_cast(task_memory.heap_bytes)); - Pmr_QImage_Stats image_memory = pmr_qimage_stats(); - shower->set_gauge("PmrQImage", "resetCount", static_cast(image_memory.reset_count)); - shower->set_gauge("PmrQImage", "allocatedBytes", static_cast(image_memory.allocated_bytes)); - shower->set_gauge("PmrQImage", "detachCount", static_cast(image_memory.detach_count)); - shower->set_gauge("PmrQImage", "detachBytes", static_cast(image_memory.detach_bytes)); - publish_memory_domain_stats(shower, Memory_Domain::Renderable, "Renderable"); - publish_memory_domain_stats(shower, Memory_Domain::InputBuffer, "InputBuffer"); - publish_memory_domain_stats(shower, Memory_Domain::Waterfall, "Waterfall"); - publish_memory_domain_stats(shower, Memory_Domain::Spectrum, "Spectrum"); - publish_memory_domain_stats(shower, Memory_Domain::FrameArena, "FrameArena"); - publish_memory_domain_stats(shower, Memory_Domain::Image, "Image"); - publish_memory_domain_stats(shower, Memory_Domain::Audio, "Audio"); - publish_memory_domain_stats(shower, Memory_Domain::Afterglow, "Afterglow"); - publish_memory_domain_stats(shower, Memory_Domain::Planisphere, "Planisphere"); - publish_memory_domain_stats(shower, Memory_Domain::Curve, "Curve"); - publish_memory_domain_stats(shower, Memory_Domain::PointSet, "PointSet"); - publish_memory_domain_stats(shower, Memory_Domain::Axis, "Axis"); - publish_memory_domain_stats(shower, Memory_Domain::SchedulerTask, "SchedulerTask"); - publish_memory_domain_stats(shower, Memory_Domain::RenderableCacheImage, "RenderableCacheImage"); - publish_memory_domain_stats(shower, Memory_Domain::PlotColorBuffer, "PlotColorBuffer"); - publish_memory_domain_stats(shower, Memory_Domain::UpdateCompletion, "UpdateCompletion"); - publish_memory_domain_stats(shower, Memory_Domain::Other, "Other"); - Renderable::Render_Pipeline& pipeline = data->pipeline; - shower->set_state("Pipeline", "jobState", job_state_text(pipeline.job_state)); - shower->set_state("Pipeline", "render_schedule_mode", plot_schedule_mode_text(data->render_schedule_mode.load(std::memory_order_acquire))); - shower->set_state("Pipeline", "paint_buffer_acquire_mode", acquire_mode_text(data->paint_buffer_acquire_mode.load(std::memory_order_acquire))); - shower->set_state("Pipeline", "renderable_buffer_acquire_mode", acquire_mode_text(data->renderable_buffer_acquire_mode.load(std::memory_order_acquire))); - shower->set_gauge("Pipeline", "refresh_times_pre_second", static_cast(data->refresh_times_pre_second)); - shower->set_gauge("Pipeline", "max_render_fps", data->max_render_fps.load(std::memory_order_acquire)); - shower->set_gauge("Pipeline", "active_render_tasks", data->active_render_tasks.load(std::memory_order_acquire)); - shower->set_gauge("Pipeline", "renderEnabled", data->render_enabled.load(std::memory_order_acquire) ? 1.0 : 0.0); - shower->set_gauge("Pipeline", "render_submit_pending", data->render_submit_pending.load(std::memory_order_acquire) ? 1.0 : 0.0); - shower->set_gauge("Pipeline", "render_request_posted", data->render_request_posted.load(std::memory_order_acquire) ? 1.0 : 0.0); - shower->set_gauge("Pipeline", "paint_request_pending", pipeline.paint_request_pending.load(std::memory_order_acquire) ? 1.0 : 0.0); - shower->set_gauge("Pipeline", "edit_state_version", static_cast(pipeline.edit_state_version.load(std::memory_order_acquire))); - shower->set_gauge("Pipeline", "render_state_version", static_cast(pipeline.render_state_version)); - publish_triple_buffer_stats(shower, "color_buffer_stats", pipeline.color_buffer_stats); -} -static double frame_ratio_percent(double value, double total) { - return total > 0.0 ? value * 100.0 / total : 0.0; -} -static double positive_frame_cost(double value) { - return value > 0.0 ? value : 0.0; -} -struct Frame_Phase_Breakdown { - double data_ms{}; - double painter_ms{}; - double request_wait_ms{}; - double queue_ms{}; - double scheduler_wait_ms{}; - double present_wait_ms{}; - double lock_wait_ms{}; - double pipeline_ms{}; - double measured_ms{}; - double other_ms{}; - double phase_total_ms{}; - double necessary_ms{}; - double unnecessary_ms{}; -}; -static Frame_Phase_Breakdown frame_phase_breakdown(const Frame_Lifecycle_Sample& sample) { - Frame_Phase_Breakdown value; - double paint_overhead_ms = positive_frame_cost(sample.paint_event_ms - sample.paint_consume_ms - sample.paint_draw_image_ms); - double finish_overhead_ms = positive_frame_cost(sample.finish_render_ms - sample.publish_color_ms); - value.data_ms = sample.prepare_data_ms + sample.image_prepare_ms; - value.painter_ms = sample.painter_draw_ms + sample.painter_end_ms + sample.paint_draw_image_ms + paint_overhead_ms; - value.request_wait_ms = sample.request_to_submit_ms; - value.queue_ms = sample.cpu_queue_wait_ms; - value.scheduler_wait_ms = sample.finish_queue_wait_ms; - value.present_wait_ms = sample.update_to_paint_ms; - value.lock_wait_ms = sample.color_wait_ms + sample.paint_consume_ms + sample.publish_color_ms; - value.pipeline_ms = sample.submit_overhead_ms + finish_overhead_ms; - value.measured_ms = value.data_ms + value.painter_ms + value.request_wait_ms + value.queue_ms + value.scheduler_wait_ms + value.present_wait_ms + value.lock_wait_ms + value.pipeline_ms + sample.stats_log_ms; - value.phase_total_ms = sample.frame_total_ms > value.measured_ms ? sample.frame_total_ms : value.measured_ms; - value.other_ms = positive_frame_cost(value.phase_total_ms - value.measured_ms); - value.necessary_ms = value.data_ms + value.painter_ms + value.pipeline_ms + value.request_wait_ms + value.scheduler_wait_ms + value.present_wait_ms; - value.unnecessary_ms = positive_frame_cost(value.phase_total_ms - value.necessary_ms); - return value; -} -static void publish_frame_lifecycle_sample(Performance_Shower* shower, Frame_Lifecycle_Sample& sample) { - std::uint64_t stats_start_ns = steady_now_ns(); - Frame_Phase_Breakdown phase = frame_phase_breakdown(sample); - if (shower && shower->enabled()) { - shower->set_gauge("FrameLife", "totalMs", sample.frame_total_ms); - shower->set_gauge("FrameLife", "phaseTotalMs", phase.phase_total_ms); - shower->set_gauge("FrameLife", "necessaryMs", phase.necessary_ms); - shower->set_gauge("FrameLife", "unnecessaryMs", phase.unnecessary_ms); - shower->set_gauge("FrameLife", "dataMs", phase.data_ms); - shower->set_gauge("FrameLife", "painterMs", phase.painter_ms); - shower->set_gauge("FrameLife", "requestWaitMs", phase.request_wait_ms); - shower->set_gauge("FrameLife", "queueMs", phase.queue_ms); - shower->set_gauge("FrameLife", "schedulerWaitMs", phase.scheduler_wait_ms); - shower->set_gauge("FrameLife", "presentWaitMs", phase.present_wait_ms); - shower->set_gauge("FrameLife", "lockWaitMs", phase.lock_wait_ms); - shower->set_gauge("FrameLife", "pipelineMs", phase.pipeline_ms); - shower->set_gauge("FrameLife", "otherMs", phase.other_ms); - shower->set_gauge("FrameLife", "necessaryRatio", frame_ratio_percent(phase.necessary_ms, phase.phase_total_ms)); - shower->set_gauge("FrameLife", "unnecessaryRatio", frame_ratio_percent(phase.unnecessary_ms, phase.phase_total_ms)); - shower->set_gauge("FrameLife", "otherRatio", frame_ratio_percent(phase.other_ms, phase.phase_total_ms)); - shower->set_gauge("FrameLife", "dataRatio", frame_ratio_percent(phase.data_ms, phase.phase_total_ms)); - shower->set_gauge("FrameLife", "painterRatio", frame_ratio_percent(phase.painter_ms, phase.phase_total_ms)); - shower->set_gauge("FrameLife", "requestWaitRatio", frame_ratio_percent(phase.request_wait_ms, phase.phase_total_ms)); - shower->set_gauge("FrameLife", "queueRatio", frame_ratio_percent(phase.queue_ms, phase.phase_total_ms)); - shower->set_gauge("FrameLife", "schedulerWaitRatio", frame_ratio_percent(phase.scheduler_wait_ms, phase.phase_total_ms)); - shower->set_gauge("FrameLife", "presentWaitRatio", frame_ratio_percent(phase.present_wait_ms, phase.phase_total_ms)); - shower->set_gauge("FrameLife", "lockWaitRatio", frame_ratio_percent(phase.lock_wait_ms, phase.phase_total_ms)); - shower->set_gauge("FrameLife", "pipelineRatio", frame_ratio_percent(phase.pipeline_ms, phase.phase_total_ms)); - shower->set_gauge("FrameLife", "renderableCount", static_cast(sample.renderable_count)); - shower->set_gauge("FrameLife", "visibleRenderableCount", static_cast(sample.visible_renderable_count)); - } - sample.stats_log_ms += elapsed_ms(stats_start_ns, steady_now_ns()); - std::uint64_t log_start_ns = steady_now_ns(); - log_frame_lifecycle_sample(sample); - sample.stats_log_ms += elapsed_ms(log_start_ns, steady_now_ns()); - if (shower && shower->enabled()) { - shower->set_gauge("FrameLife", "statsLogMs", sample.stats_log_ms); - Frame_Phase_Breakdown logged_phase = frame_phase_breakdown(sample); - shower->set_gauge("FrameLife", "statsLogRatio", frame_ratio_percent(sample.stats_log_ms, logged_phase.phase_total_ms)); - } -} -static void notify_render_task_done(Plot_Private* data) { +void notify_render_task_done(Plot_Private* data) { if (data->active_render_tasks.fetch_sub(1, std::memory_order_acq_rel) == 1) data->render_task_done.notify_all(); } +} void start_render_scheduler() { Global::instance()->start_render_scheduler(); } @@ -307,7 +48,6 @@ Abs_Plot::Abs_Plot() { { std::lock_guard lock(render_context->mutex); render_context->background_color = d->background; - render_context->refresh_times_per_second = d->refresh_times_pre_second; render_context->plot = this; } setAttribute(Qt::WA_OpaquePaintEvent, true); @@ -315,45 +55,6 @@ Abs_Plot::Abs_Plot() { setFocusPolicy(Qt::NoFocus); Global::instance()->render_scheduler().register_plot(this); } -void Abs_Plot::init() { - init_renderable_tree(); -} -void Abs_Plot::init_renderable_tree() { - root_renderable = renderive::make_shared(); - root_renderable->object_name = "Root_Renderable"; - root_renderable->init_root(this); -} -bool Abs_Plot::get_use_performance_shower() { - auto shower = performance_shower_owner(d); - return shower && shower->enabled(); -} -void Abs_Plot::remove_renderable(const std::shared_ptr& able) { - if (!able || able->plot != this) - return; - if (able == root_renderable) - return; - std::shared_ptr parent = able->parent_renderable.lock(); - if (parent) - parent->remove_child(able); - { - std::lock_guard lock(render_context->mutex); - if (render_context->performance_shower_value.get() == able.get()) - render_context->performance_shower_value.reset(); - } - mark_render_state_dirty(); -} -void Abs_Plot::set_use_performance_shower(bool use) { - auto shower = performance_shower_owner(d); - if (!shower && use) { - auto created_shower = renderive::make_shared(); - created_shower->init(root_renderable); - std::lock_guard lock(render_context->mutex); - render_context->performance_shower_value = created_shower; - } - shower = performance_shower_owner(d); - if (shower) - shower->setEnabled(use); -} Abs_Plot::~Abs_Plot() { { std::lock_guard lock(render_context->mutex); @@ -369,8 +70,10 @@ Abs_Plot::~Abs_Plot() { return d->active_render_tasks.load(std::memory_order_acquire) == 0; }); } - for (auto& color : d->pipeline.color_buffers) + for (auto& color : d->pipeline.color_buffers) { cancel_update_states(color.presented_update_states); + color.state.store(Frame_State::Free, std::memory_order_release); + } { std::lock_guard lock(renderable_mutex); if (root_renderable) @@ -384,6 +87,43 @@ Abs_Plot::~Abs_Plot() { render_context.reset(); delete d; } +void Abs_Plot::init() { + init_renderable_tree(); +} +void Abs_Plot::init_renderable_tree() { + root_renderable = renderive::make_shared(); + root_renderable->object_name = "Root_Renderable"; + root_renderable->init_root(this); +} +bool Abs_Plot::get_use_performance_shower() { + auto shower = performance_shower_owner(d); + return shower && shower->enabled(); +} +void Abs_Plot::set_use_performance_shower(bool use) { + auto shower = performance_shower_owner(d); + if (!shower && use) { + auto created_shower = renderive::make_shared(); + created_shower->init(root_renderable); + std::lock_guard lock(render_context->mutex); + render_context->performance_shower_value = created_shower; + } + shower = performance_shower_owner(d); + if (shower) + shower->setEnabled(use); +} +void Abs_Plot::remove_renderable(const std::shared_ptr& able) { + if (!able || able->plot != this || able == root_renderable) + return; + std::shared_ptr parent = able->parent_renderable.lock(); + if (parent) + parent->remove_child(able); + { + std::lock_guard lock(render_context->mutex); + if (render_context->performance_shower_value.get() == able.get()) + render_context->performance_shower_value.reset(); + } + mark_render_state_dirty(); +} std::shared_ptr Abs_Plot::get_render_context() const { return render_context; } @@ -414,7 +154,7 @@ void Abs_Plot::append_renderable_snapshot(Renderable_Snapshot& snapshot, const s for (const auto& child : children) append_renderable_snapshot(snapshot, child); } -std::shared_ptr Abs_Plot::render_snapshot() const { +std::shared_ptr Abs_Plot::render_snapshot() const { auto snapshot = renderive::make_shared(); snapshot->context = render_context; if (!render_context) @@ -423,584 +163,355 @@ std::shared_ptr Abs_Plot::render_snapshot() const { snapshot->size = render_context->render_size; snapshot->background_color = render_context->background_color; snapshot->refresh_times_per_second = render_context->refresh_times_per_second; - snapshot->performance_shower = render_context->performance_shower_value; return snapshot; } bool Abs_Plot::is_rendering() { return d->render_enabled.load(std::memory_order_acquire); } -void Abs_Plot::start_render(int refresh_times_per_second) { - set_render_check_fps(refresh_times_per_second); +void Abs_Plot::start_render(int max_render_fps) { + set_max_render_fps(static_cast(max_render_fps)); start_render(); } void Abs_Plot::start_render() const { if (d->destroying.load(std::memory_order_acquire)) return; d->render_enabled.store(true, std::memory_order_release); - Abs_Plot * self = const_cast(this); - Plot_Private* private_data = d; - private_data->active_render_tasks.fetch_add(1, std::memory_order_acq_rel); - Global::instance()->render_scheduler().post([self, private_data]() { - Plot_Private* d = self->d; - if (!d->destroying.load(std::memory_order_acquire)) { - if (d->render_timer) { - d->render_timer->cancel(); - self->schedule_render_timer(); - } - self->on_render_request(Render_Request_Source::Dirty); - } - notify_render_task_done(private_data); - }); + const_cast(this)->post_scheduler_event(Scheduler_Event::Control_Updated); } void Abs_Plot::pause_render() const { d->render_enabled.store(false, std::memory_order_release); if (d->destroying.load(std::memory_order_acquire)) return; - Abs_Plot * self = const_cast(this); Plot_Private* private_data = d; private_data->active_render_tasks.fetch_add(1, std::memory_order_acq_rel); - Global::instance()->render_scheduler().post([self, private_data]() { - Plot_Private* d = self->d; - if (!d->destroying.load(std::memory_order_acquire)) { - if (d->render_timer) { - d->render_timer->cancel(); - } - if (d->submit_timer) { - d->submit_timer->cancel(); - d->submit_timer_target_ns = 0; - d->render_submit_pending.store(false, std::memory_order_release); - } + Global::instance()->render_scheduler().post([private_data]() { + if (private_data->rate_limit_timer) { + private_data->rate_limit_timer->cancel(); + private_data->rate_limit_timer_target_ns = 0; } notify_render_task_done(private_data); }); } void Abs_Plot::paintEvent(QPaintEvent* event) { Q_UNUSED(event) - auto shower_owner = performance_shower_owner(d); - Performance_Shower* shower = shower_owner.get(); - std::uint64_t paint_start_ns = steady_now_ns(); - Performance_Scope_Timer paint_data("Pipeline", "paintEvent", shower); - std::uint64_t paint_ns = paint_start_ns; - std::uint64_t update_request_ns = d->last_update_request_ns.exchange(0, std::memory_order_acq_rel); - if (update_request_ns && shower) - shower->record_duration("Pipeline", "updateToPaint", elapsed_ms(update_request_ns, paint_ns)); - QPainter painter(this); - auto mode = to_triple_buffer_mode(d->paint_buffer_acquire_mode.load(std::memory_order_acquire)); - bool color_lease_acquired = false; - bool has_frame_lifecycle_sample = false; - Frame_Lifecycle_Sample frame_lifecycle_sample; + std::uint64_t paint_begin_time = steady_now_ns(); + auto consumed = d->pipeline.consume_ready_color(); std::pmr::vector> presented_update_states(memory_resource(Memory_Domain::UpdateCompletion)); - { - std::uint64_t consume_start_ns = steady_now_ns(); - auto color_lease = d->pipeline.consume_ready_color(mode); - std::uint64_t consume_end_ns = steady_now_ns(); - if (shower) - shower->record_duration("Buffer", "Color.consumeReady", elapsed_ms(consume_start_ns, consume_end_ns)); - color_lease_acquired = static_cast(color_lease); - Renderable::Color_Buffer* color = color_lease.buffer; - if (color && !color->image.isNull()) { - std::uint64_t color_version = color->version.load(std::memory_order_acquire); - if (Renderable::version_newer(color_version, d->last_painted_color_version)) { - d->last_painted_color_version = color_version; - frame_lifecycle_sample = color->frame_lifecycle_sample; - frame_lifecycle_sample.paint_consume_ms = elapsed_ms(consume_start_ns, consume_end_ns); - has_frame_lifecycle_sample = true; - } - std::uint64_t draw_start_ns = steady_now_ns(); - painter.drawImage(QPoint(0, 0), color->image.image()); - if (has_frame_lifecycle_sample) - frame_lifecycle_sample.paint_draw_image_ms = elapsed_ms(draw_start_ns, steady_now_ns()); - presented_update_states = std::move(color->presented_update_states); - } - else { - painter.fillRect(rect(), d->background); - } + QPainter painter(this); + if (consumed.lease && !consumed.lease.buffer->image.isNull()) { + if (consumed.new_frame) + consumed.lease.buffer->metadata.paint_begin_time = paint_begin_time; + painter.drawImage(QPoint(0, 0), consumed.lease.buffer->image.image()); + if (consumed.new_frame) + presented_update_states = std::move(consumed.lease.buffer->presented_update_states); + } + else { + painter.fillRect(rect(), d->background); } painter.end(); - std::uint64_t paint_end_ns = steady_now_ns(); complete_update_states(presented_update_states, Update_Stage::Presented); - if (shower) { - std::uint64_t stats_start_ns = steady_now_ns(); - if (color_lease_acquired) - shower->increment_counter("Buffer", "Color.frontLeaseSuccess"); - else - shower->increment_counter("Buffer", "Color.frontLeaseMiss"); - shower->increment_counter("Pipeline", "paint"); - publish_pipeline_stats(d); - if (has_frame_lifecycle_sample) - frame_lifecycle_sample.stats_log_ms += elapsed_ms(stats_start_ns, steady_now_ns()); + if (consumed.new_frame && consumed.lease) { + consumed.lease.buffer->metadata.paint_end_time = steady_now_ns(); + consumed.lease.buffer->metadata.consumer_release_time = steady_now_ns(); + Frame_Metadata frame = consumed.lease.buffer->metadata; + consumed.lease.buffer->state.store(Frame_State::Free, std::memory_order_release); + consumed.lease.reset(); + Plot_Private* private_data = d; + private_data->active_render_tasks.fetch_add(1, std::memory_order_acq_rel); + Global::instance()->render_scheduler().post([this, private_data, frame = std::move(frame)]() mutable { + if (!private_data->destroying.load(std::memory_order_acquire)) + return_consumed_frame(std::move(frame)); + notify_render_task_done(private_data); + }); } - if (has_frame_lifecycle_sample) { - frame_lifecycle_sample.paint_ns = paint_ns; - frame_lifecycle_sample.update_to_paint_ms = frame_lifecycle_sample.update_request_ns ? elapsed_ms(frame_lifecycle_sample.update_request_ns, paint_ns) : 0.0; - frame_lifecycle_sample.paint_event_ms = elapsed_ms(paint_start_ns, paint_end_ns); - std::uint64_t frame_start_ns = frame_lifecycle_sample.request_ns ? frame_lifecycle_sample.request_ns : frame_lifecycle_sample.submit_ns; - frame_lifecycle_sample.frame_total_ms = frame_start_ns ? elapsed_ms(frame_start_ns, paint_end_ns) : frame_lifecycle_sample.paint_event_ms; - publish_frame_lifecycle_sample(shower, frame_lifecycle_sample); + else if (consumed.update_was_pending) { + report_consumer_underrun(); } } void Abs_Plot::render() { - request_render(Render_Request_Source::Manual); -} -Plot_Buffer_Acquire_Mode Abs_Plot::get_paint_buffer_acquire_mode() const { - return d->paint_buffer_acquire_mode.load(std::memory_order_acquire); -} -void Abs_Plot::set_paint_buffer_acquire_mode(Plot_Buffer_Acquire_Mode mode) { - d->paint_buffer_acquire_mode.store(mode, std::memory_order_release); -} -Plot_Buffer_Acquire_Mode Abs_Plot::get_renderable_buffer_acquire_mode() const { - return d->renderable_buffer_acquire_mode.load(std::memory_order_acquire); -} -void Abs_Plot::set_renderable_buffer_acquire_mode(Plot_Buffer_Acquire_Mode mode) { - d->renderable_buffer_acquire_mode.store(mode, std::memory_order_release); + d->manual_request_pending.store(true, std::memory_order_release); + std::uint64_t now_ns = steady_now_ns(); + d->last_manual_request_ns.store(now_ns, std::memory_order_release); + post_scheduler_event(Scheduler_Event::Manual_Request, now_ns); } Plot_Render_Schedule_Mode Abs_Plot::get_render_schedule_mode() const { return d->render_schedule_mode.load(std::memory_order_acquire); } void Abs_Plot::set_render_schedule_mode(Plot_Render_Schedule_Mode mode) { d->render_schedule_mode.store(mode, std::memory_order_release); - if (mode == Plot_Render_Schedule_Mode::Max_Fps && d->max_render_fps.load(std::memory_order_acquire) <= 0.0) - d->max_render_fps.store(get_render_check_fps(), std::memory_order_release); - request_render(Render_Request_Source::Dirty); -} -double Abs_Plot::get_render_check_fps() const { - return static_cast(d->refresh_times_pre_second); -} -void Abs_Plot::set_render_check_fps(double fps) { - d->refresh_times_pre_second = qMax(1, qRound(fps)); - std::lock_guard lock(render_context->mutex); - render_context->refresh_times_per_second = d->refresh_times_pre_second; + post_scheduler_event(Scheduler_Event::Control_Updated); } double Abs_Plot::get_max_render_fps() const { return d->max_render_fps.load(std::memory_order_acquire); } void Abs_Plot::set_max_render_fps(double fps) { - d->max_render_fps.store(fps, std::memory_order_release); -} -bool Abs_Plot::submit_render_once() { - Render_Scheduler& scheduler = Global::instance()->render_scheduler(); - if (!scheduler.is_scheduler_thread()) { - Plot_Private* private_data = d; - std::uint64_t post_ns = steady_now_ns(); - private_data->active_render_tasks.fetch_add(1, std::memory_order_acq_rel); - scheduler.post([this, private_data, post_ns]() { - auto shower = performance_shower_owner(private_data); - if (shower) - shower->record_duration("Pipeline", "schedulerQueueWait", elapsed_ms(post_ns, steady_now_ns())); - if (!private_data->destroying.load(std::memory_order_acquire)) - submit_render_once(); - notify_render_task_done(private_data); - }); - return false; - } - if (d->destroying.load(std::memory_order_acquire)) - return false; - std::uint64_t submit_ns = steady_now_ns(); - Render_Request_Source source = d->submit_source; - std::uint64_t request_ns = d->last_request_ns.exchange(0, std::memory_order_acq_rel); - Frame_Lifecycle_Sample frame_lifecycle_sample; - frame_lifecycle_sample.plot_id = reinterpret_cast(this); - frame_lifecycle_sample.request_ns = request_ns; - frame_lifecycle_sample.submit_ns = submit_ns; - frame_lifecycle_sample.request_to_submit_ms = request_ns ? elapsed_ms(request_ns, submit_ns) : 0.0; + d->max_render_fps.store(std::max(0.0, fps), std::memory_order_release); { - auto shower = performance_shower_owner(d); - if (request_ns && shower) - shower->record_duration("Pipeline", "request_to_submit", frame_lifecycle_sample.request_to_submit_ms); + std::lock_guard lock(render_context->mutex); + if (fps > 0.0) + render_context->refresh_times_per_second = std::max(1, qRound(fps)); } - if (first_resize) - return false; - if (!d->render_enabled.load(std::memory_order_acquire)) - return false; - auto frame_snapshot = render_snapshot(); - if (frame_snapshot->destroying()) - return false; - QSize render_size = frame_snapshot->size; - if (render_size.width() <= 0 || render_size.height() <= 0) - return false; + post_scheduler_event(Scheduler_Event::Control_Updated); +} +Frame_Feedback_State Abs_Plot::get_frame_feedback_state() const { + std::lock_guard lock(d->feedback_mutex); + return d->feedback_state; +} +void Abs_Plot::post_scheduler_event(Scheduler_Event event, std::uint64_t event_time_ns) { + if (d->destroying.load(std::memory_order_acquire)) + return; + if (!event_time_ns) + event_time_ns = steady_now_ns(); + Render_Scheduler& scheduler = Global::instance()->render_scheduler(); + if (scheduler.is_scheduler_thread()) { + on_scheduler_event(event, event_time_ns); + return; + } + bool coalesced = event == Scheduler_Event::Input_Changed; + if (coalesced && d->render_request_posted.exchange(true, std::memory_order_acq_rel)) + return; + Plot_Private* private_data = d; + private_data->active_render_tasks.fetch_add(1, std::memory_order_acq_rel); + scheduler.post([this, private_data, event, event_time_ns, coalesced]() { + if (coalesced) + private_data->render_request_posted.store(false, std::memory_order_release); + if (!private_data->destroying.load(std::memory_order_acquire)) + on_scheduler_event(event, event_time_ns); + notify_render_task_done(private_data); + }); +} +void Abs_Plot::on_scheduler_event(Scheduler_Event event, std::uint64_t event_time_ns) { + if (d->destroying.load(std::memory_order_acquire)) + return; + d->feedback_controller.set_max_render_fps(d->max_render_fps.load(std::memory_order_acquire)); + if (event == Scheduler_Event::Input_Changed) { + std::uint64_t latest_time = d->latest_input_time_ns.load(std::memory_order_acquire); + d->feedback_controller.on_input_changed(latest_time ? latest_time : event_time_ns, d->pipeline.edit_state_version.load(std::memory_order_acquire)); + } + if (event == Scheduler_Event::Rate_Limit_Deadline_Reached) + d->rate_limit_timer_target_ns = 0; + publish_feedback_state(); + try_schedule_render(); +} +void Abs_Plot::try_schedule_render() { + if (d->destroying.load(std::memory_order_acquire) || !d->render_enabled.load(std::memory_order_acquire) || first_resize) + return; Renderable::Render_Pipeline& pipeline = d->pipeline; if (pipeline.job_state != Renderable::Job_State::Idle) + return; + bool explicit_request = d->manual_request_pending.load(std::memory_order_acquire); + Plot_Render_Schedule_Mode mode = d->render_schedule_mode.load(std::memory_order_acquire); + if (mode == Plot_Render_Schedule_Mode::Explicit_Only && !explicit_request) + return; + if (mode == Plot_Render_Schedule_Mode::Feedback && !pipeline.has_new_state() && !explicit_request) + return; + std::uint64_t now_ns = steady_now_ns(); + if (!d->feedback_controller.rate_limit_ready(now_ns)) { + schedule_rate_limit(d->feedback_controller.next_render_time_ns()); + return; + } + if (submit_render_once(explicit_request)) { + if (explicit_request) + d->manual_request_pending.store(false, std::memory_order_release); + } + else if ((explicit_request || pipeline.has_new_state()) && pipeline.job_state == Renderable::Job_State::Idle) { + schedule_rate_limit(now_ns + 1000000); + } +} +bool Abs_Plot::submit_render_once(bool explicit_request) { + auto frame_snapshot = render_snapshot(); + if (frame_snapshot->destroying() || frame_snapshot->size.width() <= 0 || frame_snapshot->size.height() <= 0) return false; + auto root = get_root_renderable(); + if (!root) + return false; + Renderable::Render_Pipeline& pipeline = d->pipeline; + auto color_lease = pipeline.try_render_color(); + if (!color_lease) + return false; + Renderable::Color_Buffer* color = color_lease.buffer; + Frame_State old_state = color->state.load(std::memory_order_acquire); + bool input_pending = pipeline.has_new_state(); + if (old_state == Frame_State::Ready) { + color->metadata.superseded_time = steady_now_ns(); + d->feedback_controller.on_frame_superseded(color->metadata, pipeline.consumer_busy(), input_pending); + collect_returned_frame(color->metadata); + supersede_update_states(color->presented_update_states); + } + else if (old_state == Frame_State::Producing || old_state == Frame_State::Consuming) { + color_lease.reset(); + return false; + } + supersede_update_states(color->presented_update_states); + ++color->generation; + color->metadata = {}; + Frame_Metadata& frame = color->metadata; + std::uint64_t submit_time = steady_now_ns(); + frame.frame_id = ++d->next_frame_id; + frame.input_version = pipeline.edit_state_version.load(std::memory_order_acquire); + frame.snapshot_version = frame.input_version; + frame.frame_slot_generation = color->generation; + frame.render_request_time = explicit_request ? d->last_manual_request_ns.load(std::memory_order_acquire) : d->latest_input_time_ns.load(std::memory_order_acquire); + frame.core_data_time = d->latest_input_time_ns.load(std::memory_order_acquire); + frame.source_data_time_begin = d->pending_input_begin_time_ns.exchange(0, std::memory_order_acq_rel); + frame.source_data_time_end = frame.core_data_time; + frame.raw_input_count = d->pending_input_count.exchange(0, std::memory_order_acq_rel); + frame.visible_input_count = frame.raw_input_count; + frame.pixel_width = static_cast(frame_snapshot->size.width()); + frame.pixel_height = static_cast(frame_snapshot->size.height()); + frame.snapshot_capture_begin_time = submit_time; + color->state.store(Frame_State::Producing, std::memory_order_release); + pipeline.job_state = Renderable::Job_State::Queued; + pipeline.render_state_version = frame.input_version; + d->feedback_controller.on_render_started(frame, submit_time); auto snapshot = renderable_snapshot(); std::stable_sort(snapshot.begin(), snapshot.end(), [](const auto& a, const auto& b) { return renderable_prepare_priority(a) < renderable_prepare_priority(b); }); - auto root = get_root_renderable(); - if (!root) - return false; - if (!first_prepare_data && !pipeline.has_new_state()) - return false; - pipeline.job_state = Renderable::Job_State::Queued; - pipeline.render_state_version = pipeline.edit_state_version.load(std::memory_order_acquire); - frame_lifecycle_sample.version = pipeline.render_state_version; + frame.snapshot_capture_end_time = steady_now_ns(); + frame_snapshot->frame_metadata = &frame; std::pmr::vector> frame_update_states(memory_resource(Memory_Domain::UpdateCompletion)); - Performance_Shower* shower = frame_snapshot->performance_shower.get(); - if (shower) { - shower->increment_counter("Pipeline", "renderFrame"); - shower->set_info("interval", QString("渲染:%1, global:%2").arg(qRound(1000.0 / (double)frame_snapshot->refresh_times_per_second)).arg(Global::instance()->interval)); - publish_pipeline_stats(d); + frame.prepare_begin_time = steady_now_ns(); + for (const auto& cur : snapshot) { + if (!cur || cur->get_retiring() || !cur->visible || !cur->d_ptr) + continue; + cur->prepare_data(*frame_snapshot); + cur->d_ptr->drain_committed_update_states(frame_update_states); + ++frame.prepared_output_count; } - { - std::uint64_t prepare_start_ns = steady_now_ns(); - for (const auto& cur : snapshot) { - if (!cur || cur->get_retiring() || !cur->visible || !cur->d_ptr) - continue; - if (!renderable_ready_for_submit(cur, source, submit_ns)) - continue; - if (!cur->prepared) { - std::uint64_t renderable_prepare_start_ns = steady_now_ns(); - cur->prepare_data(*frame_snapshot); - double renderable_prepare_ms = elapsed_ms(renderable_prepare_start_ns, steady_now_ns()); - frame_lifecycle_sample.renderable_prepare_ms += renderable_prepare_ms; - if (shower) - shower->record_duration("Renderable", renderable_name(cur.get()) + ".prepare_data", renderable_prepare_ms); - cur->d_ptr->drain_committed_update_states(frame_update_states); - mark_renderable_submitted(cur, submit_ns); - publish_renderable_buffer_stats(shower, cur.get()); - cur->prepared = true; - } - } - if (first_prepare_data) { - first_prepare_data = false; - render_context->first_prepare_data.store(false, std::memory_order_release); - } - frame_lifecycle_sample.prepare_data_ms = elapsed_ms(prepare_start_ns, steady_now_ns()); - if (shower) - shower->record_duration("Pipeline", "prepare_data", frame_lifecycle_sample.prepare_data_ms); + frame.prepare_end_time = steady_now_ns(); + if (first_prepare_data) { + first_prepare_data = false; + render_context->first_prepare_data.store(false, std::memory_order_release); } - for (const auto& able : snapshot) { - if (able) - able->prepared = false; - } - Renderable::Color_Buffer_Lease color_lease; - { - std::uint64_t color_wait_start_ns = steady_now_ns(); - color_lease = pipeline.wait_render_color(); - frame_lifecycle_sample.color_wait_ms = elapsed_ms(color_wait_start_ns, steady_now_ns()); - if (shower) - shower->record_duration("Buffer", "Color.waitRenderLease", frame_lifecycle_sample.color_wait_ms); - } - Renderable::Color_Buffer* color = color_lease.buffer; - auto color_lease_owner = renderive::make_shared(std::move(color_lease)); - std::uint64_t version = pipeline.render_state_version; - pipeline.job_state = Renderable::Job_State::Running; - if (shower) - publish_pipeline_stats(d); - Plot_Private* private_data = d; - auto renderable_snapshot_owner = renderive::make_shared(std::move(snapshot)); - std::size_t frame_memory_hint = estimate_frame_memory(*renderable_snapshot_owner, *frame_snapshot); - Abs_Plot * plot = this; + std::size_t frame_memory_hint = estimate_frame_memory(snapshot, *frame_snapshot); struct Render_Color_Task_Context { Plot_Private* private_data{}; - std::shared_ptr color_lease_owner; - Renderable::Color_Buffer* color{}; - std::uint64_t version{}; - std::uint64_t cpu_post_ns{}; + Renderable::Color_Buffer_Lease color_lease; std::shared_ptr root; - std::shared_ptr renderable_snapshot_owner; - std::shared_ptr frame_snapshot; + std::shared_ptr snapshot; + std::shared_ptr frame_snapshot; std::pmr::vector> update_states{memory_resource(Memory_Domain::UpdateCompletion)}; std::size_t frame_memory_hint{}; Abs_Plot* plot{}; - Frame_Lifecycle_Sample frame_lifecycle_sample; }; auto task_context = renderive::make_shared(); - task_context->private_data = private_data; - task_context->color_lease_owner = color_lease_owner; - task_context->color = color; - task_context->version = version; + task_context->private_data = d; + task_context->color_lease = std::move(color_lease); task_context->root = root; - task_context->renderable_snapshot_owner = renderable_snapshot_owner; + task_context->snapshot = renderive::make_shared(std::move(snapshot)); task_context->frame_snapshot = frame_snapshot; task_context->update_states = std::move(frame_update_states); task_context->frame_memory_hint = frame_memory_hint; - task_context->plot = plot; - std::uint64_t cpu_post_ns = steady_now_ns(); - frame_lifecycle_sample.submit_overhead_ms = positive_frame_cost(elapsed_ms(submit_ns, cpu_post_ns) - frame_lifecycle_sample.prepare_data_ms - frame_lifecycle_sample.color_wait_ms); - task_context->cpu_post_ns = cpu_post_ns; - task_context->frame_lifecycle_sample = frame_lifecycle_sample; + task_context->plot = this; + frame.render_queue_enter_time = steady_now_ns(); + pipeline.job_state = Renderable::Job_State::Running; + Plot_Private* private_data = d; private_data->active_render_tasks.fetch_add(1, std::memory_order_acq_rel); - scheduler.post_cpu([task_context]() { + Global::instance()->render_scheduler().post_cpu([task_context]() { Plot_Private* private_data = task_context->private_data; - task_context->frame_lifecycle_sample.cpu_queue_wait_ms = elapsed_ms(task_context->cpu_post_ns, steady_now_ns()); - if (task_context->frame_snapshot->performance_shower) - task_context->frame_snapshot->performance_shower->record_duration("Pipeline", "cpuQueueWait", task_context->frame_lifecycle_sample.cpu_queue_wait_ms); + Renderable::Color_Buffer* color = task_context->color_lease.buffer; if (private_data->destroying.load(std::memory_order_acquire)) { + color->metadata.frame_outcome = Frame_Outcome::Cancelled; + color->state.store(Frame_State::Free, std::memory_order_release); cancel_update_states(task_context->update_states); - task_context->color_lease_owner->reset(); + task_context->color_lease.reset(); notify_render_task_done(private_data); return; } Frame_Arena frame_arena(task_context->frame_memory_hint); - Abs_Plot::render_color(task_context->color, task_context->version, task_context->root, *task_context->renderable_snapshot_owner, *task_context->frame_snapshot, std::move(task_context->update_states), task_context->frame_lifecycle_sample); - task_context->color_lease_owner->reset(); + Abs_Plot::render_color(color, task_context->root, *task_context->snapshot, *task_context->frame_snapshot, std::move(task_context->update_states)); + task_context->color_lease.reset(); if (private_data->destroying.load(std::memory_order_acquire)) { + color->metadata.frame_outcome = Frame_Outcome::Cancelled; + color->state.store(Frame_State::Free, std::memory_order_release); notify_render_task_done(private_data); return; } Abs_Plot* plot = task_context->plot; - std::uint64_t finish_post_ns = steady_now_ns(); - Global::instance()->render_scheduler().post([plot, private_data, finish_post_ns]() { + Global::instance()->render_scheduler().post([plot, private_data]() { if (!private_data->destroying.load(std::memory_order_acquire)) - plot->finish_render(elapsed_ms(finish_post_ns, steady_now_ns())); + plot->finish_render(); notify_render_task_done(private_data); }); }); return true; } -void Abs_Plot::on_render_request(Render_Request_Source source) { - if (d->destroying.load(std::memory_order_acquire)) +void Abs_Plot::schedule_rate_limit(std::uint64_t target_ns) { + if (!d->rate_limit_timer || !d->render_enabled.load(std::memory_order_acquire)) return; - Plot_Render_Schedule_Mode mode = d->render_schedule_mode.load(std::memory_order_acquire); - d->submit_source = source; - switch (mode) { - case Plot_Render_Schedule_Mode::Timer_Check: - if (source == Render_Request_Source::Timer) - submit_render_once(); - break; - case Plot_Render_Schedule_Mode::Chase_Latest: - submit_render_once(); - break; - case Plot_Render_Schedule_Mode::Max_Fps: - submit_render_with_fps_limit(); - break; - case Plot_Render_Schedule_Mode::Manual: - if (source == Render_Request_Source::Manual) - submit_render_once(); - break; - } - if (performance_shower_owner(d)) - publish_pipeline_stats(d); -} -bool Abs_Plot::submit_render_with_fps_limit() { - double fps = d->max_render_fps.load(std::memory_order_acquire); - if (fps <= 0.0) - return submit_render_once(); - std::uint64_t now = steady_now_ns(); - std::uint64_t interval_ns = static_cast(1000000000.0 / fps); - if (now < d->next_submit_ns) { - d->render_submit_pending.store(true, std::memory_order_release); - schedule_max_fps_submit(d->next_submit_ns); - return false; - } - bool submitted = submit_render_once(); - if (submitted) { - d->next_submit_ns = now + interval_ns; - d->render_submit_pending.store(false, std::memory_order_release); - } - else if (!d->pipeline.has_new_state()) { - d->render_submit_pending.store(false, std::memory_order_release); - } - return submitted; -} -void Abs_Plot::schedule_max_fps_submit(std::uint64_t target_ns) { - if (!d->submit_timer || !d->render_enabled.load(std::memory_order_acquire)) + if (d->rate_limit_timer_target_ns && d->rate_limit_timer_target_ns <= target_ns) return; - if (d->submit_timer_target_ns && d->submit_timer_target_ns <= target_ns) - return; - d->submit_timer_target_ns = target_ns; - std::uint64_t now = steady_now_ns(); - std::uint64_t delay_ns = target_ns > now ? target_ns - now : 0; - d->submit_timer->expires_after(std::chrono::nanoseconds(delay_ns)); + d->rate_limit_timer_target_ns = target_ns; + std::uint64_t now_ns = steady_now_ns(); + d->rate_limit_timer->expires_after(std::chrono::nanoseconds(target_ns > now_ns ? target_ns - now_ns : 0)); Plot_Private* private_data = d; private_data->active_render_tasks.fetch_add(1, std::memory_order_acq_rel); - d->submit_timer->async_wait([this, private_data, target_ns](const asio::error_code& error) { - if (!error && !private_data->destroying.load(std::memory_order_acquire) && private_data->submit_timer_target_ns == target_ns) { - private_data->submit_timer_target_ns = 0; - if (private_data->render_submit_pending.load(std::memory_order_acquire)) - on_render_request(private_data->submit_source); - } + d->rate_limit_timer->async_wait([this, private_data, target_ns](const asio::error_code& error) { + if (!error && !private_data->destroying.load(std::memory_order_acquire) && private_data->rate_limit_timer_target_ns == target_ns) + on_scheduler_event(Scheduler_Event::Rate_Limit_Deadline_Reached, target_ns); notify_render_task_done(private_data); }); } -bool Abs_Plot::renderable_ready_for_submit(const std::shared_ptr& able, Render_Request_Source source, std::uint64_t now_ns) { - if (!able || able->get_retiring()) - return false; - Renderable_Render_Schedule_Mode mode = able->get_render_schedule_mode(); - if (mode == Renderable_Render_Schedule_Mode::Inherit) { - switch (d->render_schedule_mode.load(std::memory_order_acquire)) { - case Plot_Render_Schedule_Mode::Timer_Check: - return source == Render_Request_Source::Timer; - case Plot_Render_Schedule_Mode::Chase_Latest: - return true; - case Plot_Render_Schedule_Mode::Max_Fps: - return true; - case Plot_Render_Schedule_Mode::Manual: - return source == Render_Request_Source::Manual; - } - } - switch (mode) { - case Renderable_Render_Schedule_Mode::Inherit: - return true; - case Renderable_Render_Schedule_Mode::Chase_Latest: - return true; - case Renderable_Render_Schedule_Mode::Max_Fps: { - double fps = able->get_render_schedule_max_fps(); - if (fps <= 0.0) - return true; - std::uint64_t next_ns = able->d_ptr->next_render_submit_ns.load(std::memory_order_acquire); - if (now_ns >= next_ns) - return true; - d->render_submit_pending.store(true, std::memory_order_release); - schedule_max_fps_submit(next_ns); - return false; - } - case Renderable_Render_Schedule_Mode::On_Dirty: - return source == Render_Request_Source::Dirty || source == Render_Request_Source::Finish || source == Render_Request_Source::Manual; - case Renderable_Render_Schedule_Mode::Manual: - return source == Render_Request_Source::Manual; - } - return true; -} -void Abs_Plot::mark_renderable_submitted(const std::shared_ptr& able, std::uint64_t now_ns) { - if (able->get_render_schedule_mode() != Renderable_Render_Schedule_Mode::Max_Fps) - return; - double fps = able->get_render_schedule_max_fps(); - if (fps <= 0.0) - return; - able->d_ptr->next_render_submit_ns.store(now_ns + static_cast(1000000000.0 / fps), std::memory_order_release); -} -void Abs_Plot::finish_render(double finish_queue_wait_ms) { +void Abs_Plot::finish_render() { if (d->destroying.load(std::memory_order_acquire)) return; - std::uint64_t finish_start_ns = steady_now_ns(); Renderable::Render_Pipeline& pipeline = d->pipeline; - QRegion update_region; - auto shower_owner = performance_shower_owner(d); - Performance_Shower* shower = shower_owner.get(); - Renderable::Color_Buffer* ready_color{}; Renderable::Color_Buffer* render_color = pipeline.color_by_role(Color_Render); - if (render_color) - render_color->frame_lifecycle_sample.finish_queue_wait_ms = finish_queue_wait_ms; - { - std::uint64_t publish_start_ns = steady_now_ns(); - update_region = pipeline.publish_render_color(); - double publish_ms = elapsed_ms(publish_start_ns, steady_now_ns()); - ready_color = pipeline.color_by_role(Color_Ready); - ready_color->frame_lifecycle_sample.publish_color_ms = publish_ms; - if (shower) - shower->record_duration("Pipeline", "publish_render_color", publish_ms); + if (!render_color || render_color->state.load(std::memory_order_acquire) != Frame_State::Producing) + return; + bool ready_occupied = pipeline.has_ready_frame(); + bool input_pending = pipeline.has_new_state(); + render_color->metadata.publish_time = steady_now_ns(); + d->feedback_controller.on_render_completed(render_color->metadata, ready_occupied, pipeline.consumer_busy(), input_pending); + auto publish_result = pipeline.publish_render_color(render_color->metadata.publish_time); + if (publish_result.superseded_ready) { + publish_result.superseded_ready->metadata.superseded_time = render_color->metadata.publish_time; + d->feedback_controller.on_frame_superseded(publish_result.superseded_ready->metadata, pipeline.consumer_busy(), input_pending); + collect_returned_frame(publish_result.superseded_ready->metadata); + supersede_update_states(publish_result.superseded_ready->presented_update_states); + publish_result.superseded_ready->state.store(Frame_State::Free, std::memory_order_release); } pipeline.job_state = Renderable::Job_State::Idle; - if (shower) - publish_pipeline_stats(d); - if (!update_region.isEmpty()) { - std::uint64_t update_request_ns = steady_now_ns(); - ready_color->frame_lifecycle_sample.update_request_ns = update_request_ns; - request_update(update_region, update_request_ns); - } - else { - ready_color->frame_lifecycle_sample.update_request_ns = steady_now_ns(); - } - ready_color->frame_lifecycle_sample.finish_render_ms = elapsed_ms(finish_start_ns, steady_now_ns()); - on_render_request(Render_Request_Source::Finish); + publish_feedback_state(); + if (publish_result.request_update) + request_update(publish_result.region); + try_schedule_render(); } -void Abs_Plot::render_color(Renderable::Color_Buffer* color, std::uint64_t version, const std::shared_ptr& root, const Renderable_Snapshot& snapshot, const Plot_Render_Snapshot& render_snapshot_data, std::pmr::vector>&& update_states, Frame_Lifecycle_Sample& frame_lifecycle_sample) { - std::uint64_t render_start_ns = steady_now_ns(); - Performance_Shower* shower = render_snapshot_data.performance_shower.get(); - QSize size = render_snapshot_data.size; - std::uint64_t image_prepare_start_ns = steady_now_ns(); - supersede_update_states(color->presented_update_states); - color->image.resize(size, QImage::Format_ARGB32); - color->image.fill(render_snapshot_data.background_color); - frame_lifecycle_sample.image_prepare_ms = elapsed_ms(image_prepare_start_ns, steady_now_ns()); - QPainter painter(&color->image.writable_image()); - int renderable_count = 0; - int visible_renderable_count = 0; - int direct_draw_count = 0; - int local_cache_count = 0; - for (const auto& cur : snapshot) { - ++renderable_count; - if (!cur || cur->get_retiring() || !cur->visible) - continue; - ++visible_renderable_count; - if (cur->get_cache_mode() == Renderable_Cache_Mode::Local_Pixel) - ++local_cache_count; - else - ++direct_draw_count; - } - std::uint64_t draw_start_ns = steady_now_ns(); - if (root) { - root->render(&painter, render_snapshot_data); - } - frame_lifecycle_sample.painter_draw_ms = elapsed_ms(draw_start_ns, steady_now_ns()); - std::uint64_t painter_end_start_ns = steady_now_ns(); - painter.end(); - frame_lifecycle_sample.painter_end_ms = elapsed_ms(painter_end_start_ns, steady_now_ns()); - frame_lifecycle_sample.render_color_ms = elapsed_ms(render_start_ns, steady_now_ns()); - frame_lifecycle_sample.renderable_count = renderable_count; - frame_lifecycle_sample.visible_renderable_count = visible_renderable_count; - frame_lifecycle_sample.direct_draw_count = direct_draw_count; - frame_lifecycle_sample.local_cache_count = local_cache_count; - color->version.store(version, std::memory_order_release); - color->dirty_region = QRegion(QRect(QPoint(0, 0), size)); - for (auto& state : update_states) { - if (state) - state->complete_until(Update_Stage::Rendered); - } - color->presented_update_states = std::move(update_states); - color->frame_lifecycle_sample = frame_lifecycle_sample; - if (shower) { - double area = static_cast(size.width()) * static_cast(size.height()); - shower->record_duration("Pipeline", "render_color", frame_lifecycle_sample.render_color_ms); - shower->record_duration("Pipeline", "draw", frame_lifecycle_sample.painter_draw_ms); - shower->record_duration("Pipeline", "image_prepare", frame_lifecycle_sample.image_prepare_ms); - shower->record_duration("Pipeline", "painter_end", frame_lifecycle_sample.painter_end_ms); - shower->set_gauge("Pipeline", "main_render_area", area); - shower->set_gauge("Pipeline", "main_render_full_ratio", area > 0.0 ? 1.0 : 0.0); - shower->set_gauge("Pipeline", "main_root_child_count", root ? static_cast(root->children_snapshot().size()) : 0.0); - shower->set_gauge("Pipeline", "main_renderable_count", static_cast(renderable_count)); - shower->set_gauge("Pipeline", "main_visible_renderable_count", static_cast(visible_renderable_count)); - shower->set_gauge("Pipeline", "main_direct_draw_count", static_cast(direct_draw_count)); - shower->set_gauge("Pipeline", "main_local_cache_count", static_cast(local_cache_count)); - shower->set_gauge("Pipeline", "main_state_lease_miss_count", 0.0); - } +void Abs_Plot::return_consumed_frame(Frame_Metadata frame) { + bool ready_occupied = d->pipeline.has_ready_frame(); + d->feedback_controller.on_frame_consumed(frame, ready_occupied); + collect_returned_frame(frame); + publish_feedback_state(); + auto update_result = d->pipeline.request_ready_update(steady_now_ns()); + if (update_result.request_update) + request_update(update_result.region); + try_schedule_render(); } -void Abs_Plot::schedule_render_timer() { - if (d->destroying.load(std::memory_order_acquire) || !d->render_timer || !d->render_enabled.load(std::memory_order_acquire)) +void Abs_Plot::report_consumer_underrun() { + if (d->destroying.load(std::memory_order_acquire)) return; - int interval = std::max(1, qRound(1000.0 / (double)d->refresh_times_pre_second)); - d->render_timer->expires_after(std::chrono::milliseconds(interval)); Plot_Private* private_data = d; private_data->active_render_tasks.fetch_add(1, std::memory_order_acq_rel); - d->render_timer->async_wait([this, private_data](const asio::error_code& error) { - if (!error && !d->destroying.load(std::memory_order_acquire)) { - auto shower = performance_shower_owner(private_data); - if (shower) - shower->increment_counter("Pipeline", "timerTick"); - on_render_request(Render_Request_Source::Timer); - schedule_render_timer(); + Global::instance()->render_scheduler().post([this, private_data]() { + if (!private_data->destroying.load(std::memory_order_acquire)) { + private_data->feedback_controller.on_consumer_underrun(steady_now_ns(), private_data->pipeline.has_new_state()); + publish_feedback_state(); + try_schedule_render(); } notify_render_task_done(private_data); }); } -void Abs_Plot::mark_render_state_dirty() { - if (d->destroying.load(std::memory_order_acquire)) - return; - d->pipeline.mark_edit_state(); - request_render(); +void Abs_Plot::collect_returned_frame(Frame_Metadata& frame) { + auto shower = performance_shower_owner(d); + if (shower && shower->enabled()) + shower->collect_frame(frame); } -void Abs_Plot::request_render(Render_Request_Source source) { - if (d->destroying.load(std::memory_order_acquire)) - return; - d->last_request_ns.store(steady_now_ns(), std::memory_order_release); - bool coalesced_request = source != Render_Request_Source::Manual; - if (coalesced_request && d->render_request_posted.exchange(true, std::memory_order_acq_rel)) - return; - Plot_Private* private_data = d; - private_data->active_render_tasks.fetch_add(1, std::memory_order_acq_rel); - Global::instance()->render_scheduler().post([this, private_data, source, coalesced_request]() { - if (coalesced_request) - private_data->render_request_posted.store(false, std::memory_order_release); - if (!d->destroying.load(std::memory_order_acquire)) - on_render_request(source); - notify_render_task_done(private_data); - }); +void Abs_Plot::publish_feedback_state() { + std::lock_guard lock(d->feedback_mutex); + d->feedback_state = d->feedback_controller.state(); } -void Abs_Plot::request_update(const QRegion& region, std::uint64_t request_ns) { - if (!request_ns) - request_ns = steady_now_ns(); - d->last_update_request_ns.store(request_ns, std::memory_order_release); +void Abs_Plot::request_update(const QRegion& region) { QMetaObject::invokeMethod(this, [this, region]() { if (d->destroying.load(std::memory_order_acquire)) return; @@ -1010,22 +521,67 @@ void Abs_Plot::request_update(const QRegion& region, std::uint64_t request_ns) { update(region); }, Qt::QueuedConnection); } +void Abs_Plot::render_color(Renderable::Color_Buffer* color, const std::shared_ptr& root, const Renderable_Snapshot& snapshot, const Plot_Render_Snapshot& render_snapshot_data, std::pmr::vector>&& update_states) { + Frame_Metadata& frame = color->metadata; + frame.render_begin_time = steady_now_ns(); + QSize size = render_snapshot_data.size; + supersede_update_states(color->presented_update_states); + color->image.resize(size, QImage::Format_ARGB32); + color->image.fill(render_snapshot_data.background_color); + QPainter painter(&color->image.writable_image()); + for (const auto& cur : snapshot) { + ++frame.renderable_count; + if (!cur || cur->get_retiring() || !cur->visible) + continue; + ++frame.visible_renderable_count; + if (cur->get_cache_mode() == Renderable_Cache_Mode::Local_Pixel) + ++frame.local_cache_count; + else + ++frame.direct_draw_count; + } + frame.draw_begin_time = steady_now_ns(); + if (root) + root->render(&painter, render_snapshot_data); + painter.end(); + frame.draw_end_time = steady_now_ns(); + frame.render_end_time = steady_now_ns(); + color->version.store(frame.frame_id, std::memory_order_release); + color->dirty_region = QRegion(QRect(QPoint(0, 0), size)); + for (auto& state : update_states) { + if (state) + state->complete_until(Update_Stage::Rendered); + } + color->presented_update_states = std::move(update_states); +} +void Abs_Plot::mark_render_state_dirty() { + if (d->destroying.load(std::memory_order_acquire)) + return; + std::uint64_t now_ns = steady_now_ns(); + d->pipeline.mark_edit_state(); + d->latest_input_time_ns.store(now_ns, std::memory_order_release); + d->pending_input_count.fetch_add(1, std::memory_order_relaxed); + std::uint64_t expected = 0; + d->pending_input_begin_time_ns.compare_exchange_strong(expected, now_ns, std::memory_order_acq_rel, std::memory_order_acquire); + post_scheduler_event(Scheduler_Event::Input_Changed, now_ns); +} QColor Abs_Plot::background_color() { std::lock_guard lock(render_context->mutex); return render_context->background_color; } void Abs_Plot::set_background_color(const QColor& color) const { d->background = color; - std::lock_guard lock(render_context->mutex); - render_context->background_color = color; + { + std::lock_guard lock(render_context->mutex); + render_context->background_color = color; + } const_cast(this)->mark_render_state_dirty(); } bool Abs_Plot::event(QEvent* event) { bool ret = QWidget::event(event); switch (event->type()) { case QEvent::Resize: { - auto e = reinterpret_cast(event); - QSize render_size = e->size(); + auto resize_event = reinterpret_cast(event); + QSize render_size = resize_event->size(); d->pending_render_width.store(render_size.width(), std::memory_order_release); d->pending_render_height.store(render_size.height(), std::memory_order_release); Plot_Private* private_data = d; @@ -1033,24 +589,20 @@ bool Abs_Plot::event(QEvent* event) { Global::instance()->render_scheduler().post([this, private_data, render_size]() { if (!private_data->destroying.load(std::memory_order_acquire)) { private_data->render_size = render_size; - { - std::lock_guard lock(render_context->mutex); - render_context->render_size = render_size; - } - private_data->pipeline.mark_edit_state(); - on_render_request(Render_Request_Source::Dirty); + std::lock_guard lock(render_context->mutex); + render_context->render_size = render_size; } notify_render_task_done(private_data); }); if (first_resize) { auto snapshot = renderable_snapshot(); for (const auto& able : snapshot) - able->first_resize_event(e); + able->first_resize_event(resize_event); } { auto snapshot = renderable_snapshot(); for (const auto& able : snapshot) - able->resizeEvent(e); + able->resizeEvent(resize_event); } if (first_resize) first_resize = false; @@ -1058,17 +610,12 @@ bool Abs_Plot::event(QEvent* event) { mark_render_state_dirty(); break; } - case QEvent::Show: { + case QEvent::Show: start_render(); break; - } - case QEvent::Hide: { + case QEvent::Hide: pause_render(); break; - } - case QEvent::Enter: { - break; - } case QEvent::Leave: { auto snapshot = renderable_snapshot(); for (const auto& able : snapshot) @@ -1076,57 +623,57 @@ bool Abs_Plot::event(QEvent* event) { break; } case QEvent::Wheel: { - auto e = reinterpret_cast(event); + auto wheel_event = reinterpret_cast(event); auto snapshot = renderable_snapshot(); for (const auto& able : snapshot) - able->wheelEvent(e); + able->wheelEvent(wheel_event); break; } case QEvent::MouseMove: { - auto e = reinterpret_cast(event); + auto mouse_event = reinterpret_cast(event); auto snapshot = renderable_snapshot(); auto root = get_root_renderable(); - auto hits = root ? root->hit_renderables(e->pos()) : Renderable_Snapshot{}; + auto hits = root ? root->hit_renderables(mouse_event->pos()) : Renderable_Snapshot{}; for (const auto& able : snapshot) { if (able) - able->set_hover_state(e->pos(), false); + able->set_hover_state(mouse_event->pos(), false); } for (const auto& able : hits) { if (!able) continue; - able->set_hover_state(e->pos(), true); - able->mouseMoveEvent(e); + able->set_hover_state(mouse_event->pos(), true); + able->mouseMoveEvent(mouse_event); } break; } case QEvent::MouseButtonPress: { - auto e = reinterpret_cast(event); + auto mouse_event = reinterpret_cast(event); auto root = get_root_renderable(); - auto hits = root ? root->hit_renderables(e->pos()) : Renderable_Snapshot{}; + auto hits = root ? root->hit_renderables(mouse_event->pos()) : Renderable_Snapshot{}; for (const auto& able : hits) - able->mousePressEvent(e); + able->mousePressEvent(mouse_event); break; } case QEvent::MouseButtonRelease: { - auto e = reinterpret_cast(event); + auto mouse_event = reinterpret_cast(event); auto root = get_root_renderable(); - auto hits = root ? root->hit_renderables(e->pos()) : Renderable_Snapshot{}; + auto hits = root ? root->hit_renderables(mouse_event->pos()) : Renderable_Snapshot{}; for (const auto& able : hits) - able->mouseReleaseEvent(e); + able->mouseReleaseEvent(mouse_event); break; } case QEvent::KeyPress: { - auto e = reinterpret_cast(event); + auto key_event = reinterpret_cast(event); auto snapshot = renderable_snapshot(); for (const auto& able : snapshot) - able->keyPressEvent(e); + able->keyPressEvent(key_event); break; } case QEvent::KeyRelease: { - auto e = reinterpret_cast(event); + auto key_event = reinterpret_cast(event); auto snapshot = renderable_snapshot(); for (const auto& able : snapshot) - able->keyReleaseEvent(e); + able->keyReleaseEvent(key_event); break; } default: diff --git a/Renderive/architecture/Plot.h b/Renderive/architecture/Plot.h index 79c5c10..df797f8 100644 --- a/Renderive/architecture/Plot.h +++ b/Renderive/architecture/Plot.h @@ -1,5 +1,4 @@ #pragma once -#include #include #include #include @@ -10,57 +9,32 @@ void LIB_DECL start_render_scheduler(); class Render_Scheduler; struct Plot_Private; class Performance_Shower; -/// Qt 图表窗口和全部 Renderable 的渲染调度入口。 class LIB_DECL Abs_Plot : public QWidget { Q_OBJECT public: - /// 返回性能面板是否启用。 bool get_use_performance_shower(); - /// 启用或关闭性能面板。 void set_use_performance_shower(bool use); - /// 初始化 Plot Renderable 根节点。 virtual void init(); - /// 同步请求一次渲染。 void render(); - /// 返回背景颜色。 QColor background_color(); std::shared_ptr get_render_context() const; std::shared_ptr get_root_renderable() const; std::shared_ptr create_renderable_node(const std::shared_ptr& parent, const QString& object_name = {}); - bool first_resize = true, first_prepare_data = true; - /// 设置背景颜色。 + bool first_resize = true; + bool first_prepare_data = true; void set_background_color(const QColor& color) const; - /// paintEvent 消费颜色缓冲时的获取模式。 - Plot_Buffer_Acquire_Mode get_paint_buffer_acquire_mode() const; - void set_paint_buffer_acquire_mode(Plot_Buffer_Acquire_Mode mode); - /// Renderable 侧 State/Input 缓冲默认获取模式。 - Plot_Buffer_Acquire_Mode get_renderable_buffer_acquire_mode() const; - void set_renderable_buffer_acquire_mode(Plot_Buffer_Acquire_Mode mode); - /// Plot 级渲染调度模式。 Plot_Render_Schedule_Mode get_render_schedule_mode() const; void set_render_schedule_mode(Plot_Render_Schedule_Mode mode); - /// 调度检查定时器频率。 - double get_render_check_fps() const; - void set_render_check_fps(double fps); - /// Plot 级最大提交帧率,0 表示不限速。 double get_max_render_fps() const; void set_max_render_fps(double fps); - /// 标记 Plot 状态有新版本。 + Frame_Feedback_State get_frame_feedback_state() const; void mark_render_state_dirty(); - /// 按请求来源交给调度策略尝试提交。 - void request_render(Render_Request_Source source = Render_Request_Source::Dirty); - /// 从 Renderable 子树移除对象。 void remove_renderable(const std::shared_ptr& able); QString object_name; - /// 创建 Renderable 根节点。 virtual void init_renderable_tree(); - /// 判断是否有 Plot 级 CPU 渲染任务运行。 bool is_rendering(); - /// 启动渲染检查定时器。 - void start_render(int refresh_times_per_second); - /// 启动默认渲染检查定时器。 + void start_render(int max_render_fps); void start_render() const; - /// 暂停自动渲染检查。 void pause_render() const; Abs_Plot(); ~Abs_Plot() override; @@ -74,17 +48,19 @@ private: void paintEvent(QPaintEvent* event) final; Renderable_Snapshot renderable_snapshot() const; void append_renderable_snapshot(Renderable_Snapshot& snapshot, const std::shared_ptr& able) const; - std::shared_ptr render_snapshot() const; - void schedule_render_timer(); - bool submit_render_once(); - void on_render_request(Render_Request_Source source); - bool submit_render_with_fps_limit(); - void schedule_max_fps_submit(std::uint64_t target_ns); - bool renderable_ready_for_submit(const std::shared_ptr& able, Render_Request_Source source, std::uint64_t now_ns); - void mark_renderable_submitted(const std::shared_ptr& able, std::uint64_t now_ns); - void finish_render(double finish_queue_wait_ms); - void request_update(const QRegion& region, std::uint64_t request_ns = 0); - static void render_color(Renderable::Color_Buffer* color, std::uint64_t version, const std::shared_ptr& root, const Renderable_Snapshot& snapshot, const Plot_Render_Snapshot& render_snapshot_data, std::pmr::vector>&& update_states, Frame_Lifecycle_Sample& frame_lifecycle_sample); + std::shared_ptr render_snapshot() const; + void post_scheduler_event(Scheduler_Event event, std::uint64_t event_time_ns = 0); + void on_scheduler_event(Scheduler_Event event, std::uint64_t event_time_ns); + void try_schedule_render(); + bool submit_render_once(bool explicit_request); + void schedule_rate_limit(std::uint64_t target_ns); + void finish_render(); + void return_consumed_frame(Frame_Metadata frame); + void report_consumer_underrun(); + void collect_returned_frame(Frame_Metadata& frame); + void publish_feedback_state(); + void request_update(const QRegion& region); + static void render_color(Renderable::Color_Buffer* color, const std::shared_ptr& root, const Renderable_Snapshot& snapshot, const Plot_Render_Snapshot& render_snapshot_data, std::pmr::vector>&& update_states); friend class Render_Scheduler; friend struct Renderable; }; diff --git a/Renderive/architecture/Plot_Render_Context.h b/Renderive/architecture/Plot_Render_Context.h index 1b00079..f09bc29 100644 --- a/Renderive/architecture/Plot_Render_Context.h +++ b/Renderive/architecture/Plot_Render_Context.h @@ -4,6 +4,7 @@ #include #include #include +#include "Frame_Scheduler.h" namespace renderive { class Abs_Plot; class Performance_Shower; @@ -23,7 +24,7 @@ struct Plot_Render_Snapshot { QSize size; QColor background_color = Qt::black; int refresh_times_per_second = 30; - std::shared_ptr performance_shower; + Frame_Metadata* frame_metadata{}; bool destroying() const { return !context || context->destroying.load(std::memory_order_acquire); } diff --git a/Renderive/architecture/Plot_p.h b/Renderive/architecture/Plot_p.h index 2761b5a..97ef66f 100644 --- a/Renderive/architecture/Plot_p.h +++ b/Renderive/architecture/Plot_p.h @@ -8,32 +8,31 @@ #include "asio.hpp" namespace renderive { struct Plot_Private { - explicit Plot_Private(Abs_Plot* plot) : q(plot) {}; + explicit Plot_Private(Abs_Plot* plot) : q(plot) {} Abs_Plot* q{}; - int refresh_times_pre_second = 30; - std::unique_ptr render_timer; - std::unique_ptr submit_timer; + std::unique_ptr rate_limit_timer; std::atomic_bool render_enabled{false}; std::atomic_bool destroying{false}; - std::atomic render_schedule_mode{Plot_Render_Schedule_Mode::Chase_Latest}; + std::atomic render_schedule_mode{Plot_Render_Schedule_Mode::Feedback}; std::atomic max_render_fps{0.0}; - std::atomic_bool render_submit_pending{false}; std::atomic_bool render_request_posted{false}; - std::uint64_t next_submit_ns = 0; - std::uint64_t submit_timer_target_ns = 0; - Render_Request_Source submit_source = Render_Request_Source::Dirty; + std::atomic_bool manual_request_pending{false}; + std::uint64_t rate_limit_timer_target_ns{}; std::atomic_int active_render_tasks{0}; std::condition_variable render_task_done; std::mutex render_task_mutex; std::atomic_int pending_render_width{0}; std::atomic_int pending_render_height{0}; - std::atomic last_request_ns{0}; - std::atomic last_update_request_ns{0}; - std::uint64_t last_painted_color_version{}; + std::atomic latest_input_time_ns{0}; + std::atomic pending_input_begin_time_ns{0}; + std::atomic pending_input_count{0}; + std::atomic last_manual_request_ns{0}; QSize render_size; Renderable::Render_Pipeline pipeline; - std::atomic paint_buffer_acquire_mode{Plot_Buffer_Acquire_Mode::Try}; - std::atomic renderable_buffer_acquire_mode{Plot_Buffer_Acquire_Mode::Wait}; QColor background = Qt::black; + Frame_Feedback_Controller feedback_controller; + std::mutex feedback_mutex; + Frame_Feedback_State feedback_state; + std::uint64_t next_frame_id{}; }; } // namespace renderive diff --git a/Renderive/architecture/Render_Config.h b/Renderive/architecture/Render_Config.h index 9e1fea8..7e46b40 100644 --- a/Renderive/architecture/Render_Config.h +++ b/Renderive/architecture/Render_Config.h @@ -1,89 +1,58 @@ #pragma once #include -#include "Triple_Buffer.h" namespace renderive { -/// 绘制或渲染线程获取三缓冲物理块时的策略。 -enum class Plot_Buffer_Acquire_Mode : std::uint8_t { - /// 缓冲被占用时立即放弃本次获取。 - Try, - /// 缓冲被占用时等待释放。 - Wait -}; -/// Plot 级渲染调度策略。 enum class Plot_Render_Schedule_Mode : std::uint8_t { - /// 只由定时器周期性检查是否需要提交。 - Timer_Check, - /// 有新状态时尽快追到最新帧。 - Chase_Latest, - /// 有新状态时按最大帧率闸门提交。 - Max_Fps, - /// 只接受显式手动请求。 - Manual + Feedback, + Explicit_Only }; -/// Renderable 级调度策略。 -enum class Renderable_Render_Schedule_Mode : std::uint8_t { - /// 使用父 Plot 的调度策略。 - Inherit, - /// 本对象有新状态时尽快追到最新帧。 - Chase_Latest, - /// 本对象按自己的最大帧率提交。 - Max_Fps, - /// 只在 dirty 后参与调度。 - On_Dirty, - /// 本对象只接受手动请求。 - Manual +enum class Bottleneck_State : std::uint8_t { + Unknown, + Input_Limited, + Producer_Limited, + Consumer_Limited, + Manual_Limited, + Balanced +}; +enum class Scheduler_Event : std::uint8_t { + Input_Changed, + Manual_Request, + Render_Completed, + Frame_Consumed, + Rate_Limit_Deadline_Reached, + Control_Updated +}; +enum class Frame_State : std::uint8_t { + Free, + Producing, + Ready, + Consuming +}; +enum class Frame_Outcome : std::uint8_t { + Produced, + Consumed, + Superseded, + Cancelled, + Repeated }; enum class Renderable_Cache_Mode : std::uint8_t { - Direct, - Local_Pixel + Direct, // 不启用缓存,直接绘制 + Local_Pixel // 启用缓存 }; -enum class Renderable_Child_Render_Order : std::uint8_t { - Self_Before_Children, - Children_Before_Self -}; -/// 渲染请求来源。 -enum class Render_Request_Source : std::uint8_t { - /// 调度定时器触发。 - Timer, - /// 数据或状态 dirty 触发。 - Dirty, - /// 上一帧完成后触发。 - Finish, - /// 外部显式请求触发。 - Manual -}; -/// 把 Plot 公开获取策略转换成底层三缓冲策略。 -inline Triple_Buffer_Mode to_triple_buffer_mode(Plot_Buffer_Acquire_Mode mode) { - return mode == Plot_Buffer_Acquire_Mode::Wait ? Triple_Buffer_Mode::Wait : Triple_Buffer_Mode::Try; -} -/// State 三缓冲角色。 enum State_Role : std::uint8_t { - /// GUI/edit 写入角色。 State_Edit, - /// 已发布等待 render 消费的角色。 State_Ready, - /// render 当前使用角色。 State_Render }; -/// Input 三缓冲角色。 enum Input_Role : std::uint8_t { - /// GUI/edit 写入输入角色。 Input_Edit, - /// 已发布等待 render 消费的输入角色。 Input_Ready, - /// render 当前消费输入角色。 Input_Render }; -/// Color 三缓冲角色。 enum Color_Role : std::uint8_t { - /// QWidget paintEvent 当前显示角色。 Color_Front, - /// render 已发布等待 paint 消费角色。 Color_Ready, - /// render 当前写入角色。 Color_Render }; -/// 按无符号版本号环绕语义判断 a 是否比 b 更新。 inline bool buffer_version_newer(std::uint64_t a, std::uint64_t b) { return static_cast(a - b) > 0; } diff --git a/Renderive/architecture/Render_Data.h b/Renderive/architecture/Render_Data.h index 3381b51..b749e5e 100644 --- a/Renderive/architecture/Render_Data.h +++ b/Renderive/architecture/Render_Data.h @@ -9,8 +9,10 @@ #include #include #include "../base/Memory.h" +#include "../base/Object_Semantics.h" #include "Render_Config.h" #include "Plot_Render_Context.h" +#include "Triple_Buffer.h" #include "Update_Completion.h" #include "global.h" class QEvent; @@ -51,7 +53,7 @@ struct Render_State { Render_Data* d_ptr{}; SRC src(); }; -struct Render_Object_Owner { +struct Render_Object_Owner : Move_Only { Render_Object_Owner() noexcept = default; Render_Object_Owner(void* pointer, std::pmr::memory_resource* resource, void (*destroy)(void*, std::pmr::memory_resource*)) noexcept : pointer(pointer), resource(resource), destroy(destroy) {} Render_Object_Owner(Render_Object_Owner&& other) noexcept : pointer(std::exchange(other.pointer, nullptr)), resource(std::exchange(other.resource, nullptr)), destroy(std::exchange(other.destroy, nullptr)) {} @@ -64,8 +66,6 @@ struct Render_Object_Owner { destroy = std::exchange(other.destroy, nullptr); return *this; } - Render_Object_Owner(const Render_Object_Owner&) = delete; - Render_Object_Owner& operator=(const Render_Object_Owner&) = delete; ~Render_Object_Owner() { reset(); } @@ -129,10 +129,6 @@ struct Render_Data { std::array input_buffer_owners{}; std::pmr::vector> committed_update_states{memory_resource(Memory_Domain::UpdateCompletion)}; std::uint64_t input_version = 0; - std::atomic buffer_acquire_mode{Plot_Buffer_Acquire_Mode::Wait}; - std::atomic render_schedule_mode{Renderable_Render_Schedule_Mode::Inherit}; - std::atomic render_schedule_max_fps{0.0}; - std::atomic next_render_submit_ns{0}; Render_State* state(std::uint8_t role) const { return state_by_index(state_control.read_view()[role]); } @@ -142,33 +138,8 @@ struct Render_Data { Render_State* state_by_index(std::uint8_t index) const { return state_buffers[index]; } - Plot_Buffer_Acquire_Mode get_buffer_acquire_mode() const { - return buffer_acquire_mode.load(std::memory_order_acquire); - } - void set_buffer_acquire_mode(Plot_Buffer_Acquire_Mode mode) { - buffer_acquire_mode.store(mode, std::memory_order_release); - } - Renderable_Render_Schedule_Mode get_render_schedule_mode() const { - return render_schedule_mode.load(std::memory_order_acquire); - } - void set_render_schedule_mode(Renderable_Render_Schedule_Mode mode) { - render_schedule_mode.store(mode, std::memory_order_release); - } - double get_render_schedule_max_fps() const { - return render_schedule_max_fps.load(std::memory_order_acquire); - } - void set_render_schedule_max_fps(double fps) { - render_schedule_max_fps.store(fps, std::memory_order_release); - } Triple_Buffer_Lease mark_state_role(std::uint8_t role) { - if (get_buffer_acquire_mode() == Plot_Buffer_Acquire_Mode::Wait) { - return state_control.wait_mark_use_role(role); - } - auto record = state_control.try_mark_use_role(role); - if (record.result != Triple_Buffer_Result::Success) { - return {}; - } - return record.lease; + return state_control.wait_mark_use_role(role); } void unmark_state(Triple_Buffer_Lease lease) { state_control.unmark_use(lease); diff --git a/Renderive/architecture/Render_Lease.h b/Renderive/architecture/Render_Lease.h index f404479..761b1eb 100644 --- a/Renderive/architecture/Render_Lease.h +++ b/Renderive/architecture/Render_Lease.h @@ -103,10 +103,8 @@ struct Renderable_Binding { } }; template -struct Render_State_Lease { +struct Render_State_Lease : Immovable { explicit Render_State_Lease(Data* data) : data(data), lease(data->state_control.wait_mark_use_role(State_Edit)), state(reinterpret_cast(data->state_by_index(lease.index))) {} - Render_State_Lease(const Render_State_Lease&) = delete; - Render_State_Lease& operator=(const Render_State_Lease&) = delete; ~Render_State_Lease() { data->unmark_state(lease); } @@ -121,10 +119,8 @@ struct Render_State_Lease { State* state{}; }; template -struct Render_Edit_Lease { +struct Render_Edit_Lease : Immovable { explicit Render_Edit_Lease(Data* data) : data(data), lease(data->state_control.wait_mark_use_role(State_Edit)), state(reinterpret_cast(data->state_by_index(lease.index))) {} - Render_Edit_Lease(const Render_Edit_Lease&) = delete; - Render_Edit_Lease& operator=(const Render_Edit_Lease&) = delete; ~Render_Edit_Lease() { ++data->state_by_index(lease.index)->version; if (data->q_ptr) @@ -142,10 +138,8 @@ struct Render_Edit_Lease { State* state{}; }; template -struct Render_Input_Lease { +struct Render_Input_Lease : Immovable { explicit Render_Input_Lease(Data* data, bool notify_render = true) : data(data), notify_render(notify_render), lease(data->input_control.wait_mark_use_role(Input_Edit)), input(reinterpret_cast(data->input_data_by_index(lease.index))) {} - Render_Input_Lease(const Render_Input_Lease&) = delete; - Render_Input_Lease& operator=(const Render_Input_Lease&) = delete; ~Render_Input_Lease() { data->mark_input_dirty(lease, notify_render); data->input_control.unmark_use(lease); diff --git a/Renderive/architecture/Render_Pipeline.cpp b/Renderive/architecture/Render_Pipeline.cpp index 4a91417..4194d93 100644 --- a/Renderive/architecture/Render_Pipeline.cpp +++ b/Renderive/architecture/Render_Pipeline.cpp @@ -15,54 +15,99 @@ bool Renderable::Render_Pipeline::has_new_state() const { Renderable::Color_Buffer* Renderable::Render_Pipeline::color_by_index(std::uint8_t index) { return &color_buffers[index]; } +const Renderable::Color_Buffer* Renderable::Render_Pipeline::color_by_index(std::uint8_t index) const { + return &color_buffers[index]; +} Renderable::Color_Buffer* Renderable::Render_Pipeline::color_by_role(std::uint8_t role) { return color_by_index(color_control.read_view()[role]); } -Renderable::Color_Buffer_Lease Renderable::Render_Pipeline::wait_render_color() { - auto lease = color_control.wait_mark_use_role(Color_Render); - return {color_by_index(lease.index), &color_control, lease}; +const Renderable::Color_Buffer* Renderable::Render_Pipeline::color_by_role(std::uint8_t role) const { + return color_by_index(color_control.read_view()[role]); } -Renderable::Color_Buffer_Lease Renderable::Render_Pipeline::wait_front_color() { - auto lease = color_control.wait_mark_use_role(Color_Front); - return {color_by_index(lease.index), &color_control, lease}; +Renderable::Color_Buffer_Lease Renderable::Render_Pipeline::try_render_color() { + auto record = color_control.try_mark_use_role(Color_Render); + if (record.result != Triple_Buffer_Result::Success) + return {}; + return {color_by_index(record.lease.index), &color_control, record.lease}; } Renderable::Color_Buffer_Lease Renderable::Render_Pipeline::try_front_color() { auto record = color_control.try_mark_use_role(Color_Front); - if (record.result != Triple_Buffer_Result::Success) { + if (record.result != Triple_Buffer_Result::Success) return {}; - } return {color_by_index(record.lease.index), &color_control, record.lease}; } -QRegion Renderable::Render_Pipeline::publish_render_color() { - color_control.wait_swap_role(Color_Ready, Color_Render, [this](const Triple_Buffer_View& view) { - auto render = color_by_index(view[Color_Render]); - auto ready = color_by_index(view[Color_Ready]); - return Renderable::version_newer(render->version.load(std::memory_order_acquire), ready->version.load(std::memory_order_acquire)); +Renderable::Render_Pipeline::Frame_Publish_Result Renderable::Render_Pipeline::publish_render_color(std::uint64_t now_ns) { + Frame_Publish_Result result; + Color_Buffer* render = color_by_role(Color_Render); + Color_Buffer* old_ready = color_by_role(Color_Ready); + const Color_Buffer* front = color_by_role(Color_Front); + bool replace_ready = old_ready->state.load(std::memory_order_acquire) == Frame_State::Ready && Renderable::version_newer(old_ready->version.load(std::memory_order_acquire), front->version.load(std::memory_order_acquire)); + if (paint_request_pending.load(std::memory_order_acquire)) { + render->metadata.update_request_id = pending_update_request_id.load(std::memory_order_acquire); + render->metadata.update_request_time = pending_update_request_time.load(std::memory_order_acquire); + } + render->metadata.publish_time = now_ns; + render->metadata.frame_outcome = Frame_Outcome::Produced; + render->state.store(Frame_State::Ready, std::memory_order_release); + auto swap = color_control.wait_swap_role(Color_Ready, Color_Render, [this](const Triple_Buffer_View& view) { + const Color_Buffer* render_buffer = color_by_index(view[Color_Render]); + const Color_Buffer* ready_buffer = color_by_index(view[Color_Ready]); + return Renderable::version_newer(render_buffer->version.load(std::memory_order_acquire), ready_buffer->version.load(std::memory_order_acquire)); }); - auto view = color_control.read_view(); - auto ready = color_by_index(view[Color_Ready]); - auto front = color_by_index(view[Color_Front]); - if (Renderable::version_newer(ready->version.load(std::memory_order_acquire), front->version.load(std::memory_order_acquire)) && !paint_request_pending.exchange(true, std::memory_order_acq_rel)) { - return ready->dirty_region; - } - return {}; + if (replace_ready && swap.result == Triple_Buffer_Result::Success) + result.superseded_ready = old_ready; + Frame_Publish_Result update_result = request_ready_update(now_ns); + update_result.superseded_ready = result.superseded_ready; + return update_result; } -Renderable::Color_Buffer_Lease Renderable::Render_Pipeline::consume_ready_color(Triple_Buffer_Mode mode) { +Renderable::Render_Pipeline::Frame_Publish_Result Renderable::Render_Pipeline::request_ready_update(std::uint64_t now_ns) { + Frame_Publish_Result result; + Color_Buffer* ready = color_by_role(Color_Ready); + const Color_Buffer* front = color_by_role(Color_Front); + if (ready->state.load(std::memory_order_acquire) != Frame_State::Ready) + return result; + if (!Renderable::version_newer(ready->version.load(std::memory_order_acquire), front->version.load(std::memory_order_acquire))) + return result; + if (paint_request_pending.load(std::memory_order_acquire)) { + ready->metadata.update_request_id = pending_update_request_id.load(std::memory_order_acquire); + ready->metadata.update_request_time = pending_update_request_time.load(std::memory_order_acquire); + return result; + } + result.update_request_id = ++next_update_request_id; + result.update_request_time = now_ns; + ready->metadata.update_request_id = result.update_request_id; + ready->metadata.update_request_time = result.update_request_time; + pending_update_request_id.store(result.update_request_id, std::memory_order_release); + pending_update_request_time.store(result.update_request_time, std::memory_order_release); + paint_request_pending.store(true, std::memory_order_release); + result.region = ready->dirty_region; + result.request_update = true; + return result; +} +Renderable::Render_Pipeline::Frame_Consume_Result Renderable::Render_Pipeline::consume_ready_color() { + Frame_Consume_Result result; + result.update_was_pending = paint_request_pending.exchange(false, std::memory_order_acq_rel); auto can_swap = [this](const Triple_Buffer_View& view) { - auto ready = color_by_index(view[Color_Ready]); - auto front = color_by_index(view[Color_Front]); - return Renderable::version_newer(ready->version.load(std::memory_order_acquire), front->version.load(std::memory_order_acquire)); + const Color_Buffer* ready = color_by_index(view[Color_Ready]); + const Color_Buffer* front = color_by_index(view[Color_Front]); + return ready->state.load(std::memory_order_acquire) == Frame_State::Ready && Renderable::version_newer(ready->version.load(std::memory_order_acquire), front->version.load(std::memory_order_acquire)); }; - if (mode == Triple_Buffer_Mode::Wait) { - color_control.wait_swap_role(Color_Front, Color_Ready, can_swap); + Triple_Buffer_Swap_Record swap = color_control.try_swap_role(Color_Front, Color_Ready, can_swap); + result.new_frame = swap.result == Triple_Buffer_Result::Success; + result.lease = try_front_color(); + if (result.new_frame && result.lease) { + result.lease.buffer->state.store(Frame_State::Consuming, std::memory_order_release); + result.update_request_id = result.lease.buffer->metadata.update_request_id; + result.update_request_time = result.lease.buffer->metadata.update_request_time; } - else { - color_control.try_swap_role(Color_Front, Color_Ready, can_swap); - } - paint_request_pending.store(false, std::memory_order_release); - if (mode == Triple_Buffer_Mode::Wait) { - return wait_front_color(); - } - return try_front_color(); + return result; +} +bool Renderable::Render_Pipeline::has_ready_frame() const { + const Color_Buffer* ready = color_by_role(Color_Ready); + const Color_Buffer* front = color_by_role(Color_Front); + return ready->state.load(std::memory_order_acquire) == Frame_State::Ready && Renderable::version_newer(ready->version.load(std::memory_order_acquire), front->version.load(std::memory_order_acquire)); +} +bool Renderable::Render_Pipeline::consumer_busy() const { + return color_by_role(Color_Front)->state.load(std::memory_order_acquire) == Frame_State::Consuming; } } // namespace renderive diff --git a/Renderive/architecture/Renderable.cpp b/Renderive/architecture/Renderable.cpp index 0905e13..df49f2e 100644 --- a/Renderive/architecture/Renderable.cpp +++ b/Renderive/architecture/Renderable.cpp @@ -27,24 +27,6 @@ void Renderable::Color_Buffer_Lease::reset() { control = nullptr; lease = {}; } -Plot_Buffer_Acquire_Mode Renderable::get_buffer_acquire_mode() const { - return d_ptr->get_buffer_acquire_mode(); -} -void Renderable::set_buffer_acquire_mode(Plot_Buffer_Acquire_Mode mode) { - d_ptr->set_buffer_acquire_mode(mode); -} -Renderable_Render_Schedule_Mode Renderable::get_render_schedule_mode() const { - return d_ptr->get_render_schedule_mode(); -} -void Renderable::set_render_schedule_mode(Renderable_Render_Schedule_Mode mode) { - d_ptr->set_render_schedule_mode(mode); -} -double Renderable::get_render_schedule_max_fps() const { - return d_ptr->get_render_schedule_max_fps(); -} -void Renderable::set_render_schedule_max_fps(double fps) { - d_ptr->set_render_schedule_max_fps(fps); -} Renderable_Cache_Mode Renderable::get_cache_mode() const { return cache_mode.load(std::memory_order_acquire); } @@ -53,13 +35,6 @@ void Renderable::set_cache_mode(Renderable_Cache_Mode mode) { cache_dirty.store(true, std::memory_order_release); mark_render_dirty(); } -Renderable_Child_Render_Order Renderable::get_child_render_order() const { - return child_render_order.load(std::memory_order_acquire); -} -void Renderable::set_child_render_order(Renderable_Child_Render_Order order) { - child_render_order.store(order, std::memory_order_release); - mark_render_dirty(); -} std::uint64_t Renderable::cache_image_bytes() const { std::lock_guard lock(cache_mutex); return cache_image.isNull() ? 0 : static_cast(cache_image.sizeInBytes()); @@ -101,7 +76,6 @@ void Renderable::init_common(Abs_Plot* plot) { d_owner = create_render_data(); d_ptr = d_owner.get(); } - d_ptr->set_buffer_acquire_mode(plot->get_renderable_buffer_acquire_mode()); d_ptr->clear_state_buffers(); d_ptr->clear_input_buffers(); d_ptr->state_buffer_owners[State_Edit] = create_render_state(); @@ -157,15 +131,13 @@ std::vector> Renderable::hit_renderables(const QPoin void Renderable::append_hit_renderables(std::vector>& hits, const QPointF& pos) { if (get_retiring() || !visible) return; - if (get_child_render_order() == Renderable_Child_Render_Order::Children_Before_Self && select_test(pos)) - hits.push_back(shared_from_this()); std::vector> children = children_snapshot(); - for (int i = children.size() - 1; i >= 0; --i) { + for (int i = static_cast(children.size()) - 1; i >= 0; --i) { const auto& child = children.at(i); if (child) child->append_hit_renderables(hits, pos); } - if (get_child_render_order() == Renderable_Child_Render_Order::Self_Before_Children && select_test(pos)) + if (select_test(pos)) hits.push_back(shared_from_this()); } void Renderable::render(QPainter* painter, const Plot_Render_Snapshot& snapshot) { @@ -177,14 +149,8 @@ void Renderable::render(QPainter* painter, const Plot_Render_Snapshot& snapshot) render_direct(painter, snapshot); } void Renderable::render_direct(QPainter* painter, const Plot_Render_Snapshot& snapshot) { - if (get_child_render_order() == Renderable_Child_Render_Order::Children_Before_Self) { - render_children(painter, snapshot); - render_self(painter, snapshot); - } - else { - render_self(painter, snapshot); - render_children(painter, snapshot); - } + render_self(painter, snapshot); + render_children(painter, snapshot); } void Renderable::render_children(QPainter* painter, const Plot_Render_Snapshot& snapshot) { std::vector> children = children_snapshot(); @@ -203,15 +169,21 @@ void Renderable::render_cached(QPainter* painter, const Plot_Render_Snapshot& sn std::lock_guard lock(cache_mutex); bool ready = !cache_dirty.load(std::memory_order_acquire) && cache_ready_version.load(std::memory_order_acquire) == edit_version && cache_image.size() == bounds.size() && cache_image.format() == QImage::Format_ARGB32 && cache_bounds == bounds; if (ready) { + if (snapshot.frame_metadata) + snapshot.frame_metadata->record_renderable_cache_hit(object_name); painter->drawImage(bounds.topLeft(), cache_image.image()); return; } if (!cache_image.isNull() && cache_bounds == bounds) { + if (snapshot.frame_metadata) + snapshot.frame_metadata->record_renderable_cache_stale_hit(object_name); schedule_cache_render(bounds, snapshot, edit_version); painter->drawImage(bounds.topLeft(), cache_image.image()); return; } } + if (snapshot.frame_metadata) + snapshot.frame_metadata->record_renderable_cache_miss(object_name); render_cache_image(bounds, snapshot, edit_version); std::lock_guard lock(cache_mutex); if (!cache_image.isNull()) @@ -250,6 +222,7 @@ void Renderable::schedule_cache_render(const QRect& bounds, const Plot_Render_Sn auto task_context = renderive::make_shared(); task_context->self = shared_from_this(); task_context->snapshot_owner = renderive::make_shared(snapshot); + task_context->snapshot_owner->frame_metadata = nullptr; task_context->bounds = bounds; task_context->version = version; Global::instance()->render_scheduler().post_cpu([task_context]() { diff --git a/Renderive/architecture/Renderable_Core.h b/Renderive/architecture/Renderable_Core.h index d3d8473..97ea830 100644 --- a/Renderive/architecture/Renderable_Core.h +++ b/Renderive/architecture/Renderable_Core.h @@ -7,40 +7,11 @@ #include #include #include "../base/Pmr_QImage_Buffer.h" +#include "../base/Object_Semantics.h" +#include "Frame_Scheduler.h" #include "Render_Data.h" namespace renderive { struct Update_State; -struct Frame_Lifecycle_Sample { - std::uintptr_t plot_id{}; - std::uint64_t version{}; - std::uint64_t request_ns{}; - std::uint64_t submit_ns{}; - std::uint64_t update_request_ns{}; - std::uint64_t paint_ns{}; - double request_to_submit_ms{}; - double prepare_data_ms{}; - double renderable_prepare_ms{}; - double submit_overhead_ms{}; - double color_wait_ms{}; - double cpu_queue_wait_ms{}; - double finish_queue_wait_ms{}; - double render_color_ms{}; - double image_prepare_ms{}; - double painter_draw_ms{}; - double painter_end_ms{}; - double publish_color_ms{}; - double finish_render_ms{}; - double update_to_paint_ms{}; - double paint_event_ms{}; - double paint_consume_ms{}; - double paint_draw_image_ms{}; - double stats_log_ms{}; - double frame_total_ms{}; - int renderable_count{}; - int visible_renderable_count{}; - int direct_draw_count{}; - int local_cache_count{}; -}; /// 可渲染对象的基础接口和每对象渲染管线。 struct Renderable : std::enable_shared_from_this { /// 后台渲染任务状态。 @@ -59,14 +30,14 @@ struct Renderable : std::enable_shared_from_this { Pmr_QImage_Buffer image{Memory_Domain::PlotColorBuffer}; QRegion dirty_region; std::pmr::vector> presented_update_states; - Frame_Lifecycle_Sample frame_lifecycle_sample; + std::atomic state{Frame_State::Free}; + std::uint64_t generation{}; + Frame_Metadata metadata; }; /// 颜色缓冲占用租约,析构时自动释放底层 busy 标记。 - struct Color_Buffer_Lease { + struct Color_Buffer_Lease : Move_Only { Color_Buffer_Lease() = default; Color_Buffer_Lease(Color_Buffer* buffer, Triple_Role_Buffer_Control* control, Triple_Buffer_Lease lease); - Color_Buffer_Lease(const Color_Buffer_Lease&) = delete; - Color_Buffer_Lease& operator=(const Color_Buffer_Lease&) = delete; Color_Buffer_Lease(Color_Buffer_Lease&& other) noexcept; Color_Buffer_Lease& operator=(Color_Buffer_Lease&& other) noexcept; ~Color_Buffer_Lease(); @@ -82,6 +53,20 @@ struct Renderable : std::enable_shared_from_this { }; /// 单个 Plot 的颜色三缓冲管线。 struct Render_Pipeline { + struct Frame_Publish_Result { + QRegion region; + Color_Buffer* superseded_ready{}; + std::uint64_t update_request_id{}; + std::uint64_t update_request_time{}; + bool request_update{}; + }; + struct Frame_Consume_Result { + Color_Buffer_Lease lease; + std::uint64_t update_request_id{}; + std::uint64_t update_request_time{}; + bool new_frame{}; + bool update_was_pending{}; + }; Triple_Role_Buffer_Control color_control; Triple_Buffer_Stats color_buffer_stats; std::array color_buffers; @@ -89,18 +74,25 @@ struct Renderable : std::enable_shared_from_this { std::uint64_t render_state_version = 0; Job_State job_state = Job_State::Idle; std::atomic_bool paint_request_pending{false}; + std::atomic pending_update_request_id{0}; + std::atomic pending_update_request_time{0}; + std::uint64_t next_update_request_id{}; Render_Pipeline(); /// 标记 GUI/edit 状态已经变化。 void mark_edit_state(); /// 判断是否存在 render 未消费的新状态。 bool has_new_state() const; Color_Buffer* color_by_index(std::uint8_t index); + const Color_Buffer* color_by_index(std::uint8_t index) const; Color_Buffer* color_by_role(std::uint8_t role); - Color_Buffer_Lease wait_render_color(); - Color_Buffer_Lease wait_front_color(); + const Color_Buffer* color_by_role(std::uint8_t role) const; + Color_Buffer_Lease try_render_color(); Color_Buffer_Lease try_front_color(); - QRegion publish_render_color(); - Color_Buffer_Lease consume_ready_color(Triple_Buffer_Mode mode); + Frame_Publish_Result publish_render_color(std::uint64_t now_ns); + Frame_Publish_Result request_ready_update(std::uint64_t now_ns); + Frame_Consume_Result consume_ready_color(); + bool has_ready_frame() const; + bool consumer_busy() const; }; /// 创建派生对象的私有渲染数据。 virtual Render_Object_Owner create_render_data() { @@ -142,20 +134,9 @@ struct Renderable : std::enable_shared_from_this { virtual void wheelEvent(QWheelEvent* event); /// 按三缓冲版本号环绕语义判断 a 是否比 b 新。 static bool version_newer(std::uint64_t a, std::uint64_t b); - /// 当前对象三缓冲获取模式。 - Plot_Buffer_Acquire_Mode get_buffer_acquire_mode() const; - void set_buffer_acquire_mode(Plot_Buffer_Acquire_Mode mode); - /// 当前对象渲染调度策略。 - Renderable_Render_Schedule_Mode get_render_schedule_mode() const; - void set_render_schedule_mode(Renderable_Render_Schedule_Mode mode); - /// 当前对象自身最大提交帧率,0 表示继承或不限速。 - double get_render_schedule_max_fps() const; - void set_render_schedule_max_fps(double fps); /// 当前 Renderable 输出缓存策略。 Renderable_Cache_Mode get_cache_mode() const; void set_cache_mode(Renderable_Cache_Mode mode); - Renderable_Child_Render_Order get_child_render_order() const; - void set_child_render_order(Renderable_Child_Render_Order order); std::uint64_t cache_image_bytes() const; std::uint64_t cache_image_area() const; /// 标记 State 缓冲有新版本。 @@ -179,7 +160,6 @@ struct Renderable : std::enable_shared_from_this { mutable std::mutex children_mutex; std::weak_ptr plot_context; std::atomic cache_mode{Renderable_Cache_Mode::Direct}; - std::atomic child_render_order{Renderable_Child_Render_Order::Self_Before_Children}; std::atomic_bool cache_dirty{true}; std::atomic_bool cache_render_queued{false}; std::atomic cache_edit_version{1}; diff --git a/Renderive/architecture/Triple_Buffer.h b/Renderive/architecture/Triple_Buffer.h index fec00e3..913bc10 100644 --- a/Renderive/architecture/Triple_Buffer.h +++ b/Renderive/architecture/Triple_Buffer.h @@ -15,13 +15,6 @@ enum class Triple_Buffer_Result : std::uint8_t { /// 交换条件拒绝,不代表错误。 Condition_Failed }; -/// 三缓冲获取模式。 -enum class Triple_Buffer_Mode : std::uint8_t { - /// 失败立即返回。 - Try, - /// 等待缓冲释放。 - Wait -}; /// 三个逻辑角色到三个物理缓冲的当前映射。 struct Triple_Buffer_View { std::array index{}; diff --git a/Renderive/architecture/Update_Completion.h b/Renderive/architecture/Update_Completion.h index bd83041..bfcf997 100644 --- a/Renderive/architecture/Update_Completion.h +++ b/Renderive/architecture/Update_Completion.h @@ -9,6 +9,7 @@ #include #include #include "../base/Memory.h" +#include "../base/Object_Semantics.h" #include "../base/Task.h" #include "asio.hpp" namespace renderive { @@ -26,7 +27,7 @@ enum class Update_Outcome : std::uint8_t { }; using Update_Id = std::uint64_t; struct Submit_Options {}; -class Update_Waiter { +class Update_Waiter : Move_Only { public: Update_Waiter() noexcept = default; template @@ -57,8 +58,6 @@ public: move_from(std::move(other)); return *this; } - Update_Waiter(const Update_Waiter&) = delete; - Update_Waiter& operator=(const Update_Waiter&) = delete; ~Update_Waiter() { reset(); } diff --git a/Renderive/base/Frame_Memory.h b/Renderive/base/Frame_Memory.h index 41fab7c..f830889 100644 --- a/Renderive/base/Frame_Memory.h +++ b/Renderive/base/Frame_Memory.h @@ -4,6 +4,7 @@ #include #include #include "../architecture/global.h" +#include "Object_Semantics.h" #ifndef RENDERIVE_FRAME_ARENA_GROW #define RENDERIVE_FRAME_ARENA_GROW 1 #endif @@ -17,12 +18,10 @@ struct Frame_Memory_Stats { std::uint64_t scratch_bytes{}; }; struct Thread_Frame_Scratch; -class LIB_DECL Frame_Arena { +class LIB_DECL Frame_Arena : Immovable { public: explicit Frame_Arena(std::size_t initial_buffer_size = 64 * 1024); ~Frame_Arena(); - Frame_Arena(const Frame_Arena&) = delete; - Frame_Arena& operator=(const Frame_Arena&) = delete; std::pmr::memory_resource* resource() noexcept; private: Thread_Frame_Scratch* scratch{}; diff --git a/Renderive/base/Object_Semantics.h b/Renderive/base/Object_Semantics.h new file mode 100644 index 0000000..31ca971 --- /dev/null +++ b/Renderive/base/Object_Semantics.h @@ -0,0 +1,39 @@ +#pragma once +namespace renderive { +class Copyable_Movable { +protected: + Copyable_Movable() = default; + Copyable_Movable(const Copyable_Movable&) = default; + Copyable_Movable& operator=(const Copyable_Movable&) = default; + Copyable_Movable(Copyable_Movable&&) noexcept = default; + Copyable_Movable& operator=(Copyable_Movable&&) noexcept = default; + ~Copyable_Movable() = default; +}; +class Move_Only { +protected: + Move_Only() = default; + Move_Only(const Move_Only&) = delete; + Move_Only& operator=(const Move_Only&) = delete; + Move_Only(Move_Only&&) noexcept = default; + Move_Only& operator=(Move_Only&&) noexcept = default; + ~Move_Only() = default; +}; +class Immovable { +protected: + Immovable() = default; + Immovable(const Immovable&) = delete; + Immovable& operator=(const Immovable&) = delete; + Immovable(Immovable&&) = delete; + Immovable& operator=(Immovable&&) = delete; + ~Immovable() = default; +}; +class Copy_Only { +protected: + Copy_Only() = default; + Copy_Only(const Copy_Only&) = default; + Copy_Only& operator=(const Copy_Only&) = default; + Copy_Only(Copy_Only&&) = delete; + Copy_Only& operator=(Copy_Only&&) = delete; + ~Copy_Only() = default; +}; +} diff --git a/Renderive/base/Task.h b/Renderive/base/Task.h index 958c28a..57718e0 100644 --- a/Renderive/base/Task.h +++ b/Renderive/base/Task.h @@ -7,6 +7,7 @@ #include #include #include "Memory.h" +#include "Object_Semantics.h" namespace renderive { struct Task_Stats { std::uint64_t sizeof_task{}; @@ -22,7 +23,7 @@ inline std::atomic inline_count{}; inline std::atomic heap_count{}; inline std::atomic heap_bytes{}; } -class Task { +class Task : Move_Only { public: static constexpr std::size_t Inline_Size = 64; static constexpr std::size_t Inline_Align = alignof(std::max_align_t); @@ -75,8 +76,6 @@ public: move_from(std::move(other)); return *this; } - Task(const Task&) = delete; - Task& operator=(const Task&) = delete; ~Task() { reset(); } diff --git a/Renderive/base/Tool.h b/Renderive/base/Tool.h index 9c3bc88..92ce561 100644 --- a/Renderive/base/Tool.h +++ b/Renderive/base/Tool.h @@ -234,10 +234,8 @@ static QWidget* create_plot_render_schedule_combo(renderive::Abs_Plot* plot) { auto combo = new QComboBox; label->setStyleSheet(renderive::get_button_style_sheet()); combo->setStyleSheet(renderive::get_button_style_sheet()); - combo->addItem("Timer_Check", static_cast(renderive::Plot_Render_Schedule_Mode::Timer_Check)); - combo->addItem("Chase_Latest", static_cast(renderive::Plot_Render_Schedule_Mode::Chase_Latest)); - combo->addItem("Max_Fps", static_cast(renderive::Plot_Render_Schedule_Mode::Max_Fps)); - combo->addItem("Manual", static_cast(renderive::Plot_Render_Schedule_Mode::Manual)); + combo->addItem("Feedback", static_cast(renderive::Plot_Render_Schedule_Mode::Feedback)); + combo->addItem("Explicit_Only", static_cast(renderive::Plot_Render_Schedule_Mode::Explicit_Only)); int index = combo->findData(static_cast(plot->get_render_schedule_mode())); combo->setCurrentIndex(index >= 0 ? index : 0); QObject::connect(combo, QOverload::of(&QComboBox::currentIndexChanged), [plot, combo](int index) { @@ -306,17 +304,9 @@ static QWidget* create_control(renderive::Abs_Plot* plot, const std::vectorset_background_color(color); }, plot->background_color()), create_plot_render_schedule_combo(plot), - renderive::create_edit_widget_list("渲染检查FPS", [plot](std::vector data) { - if (data[0] < 0) { - plot->pause_render(); - } - else { - plot->start_render(data[0]); - } - }, {30}), renderive::create_edit_widget_list("最大渲染FPS", [plot](std::vector data) { plot->set_max_render_fps(data[0]); - }, {plot->get_max_render_fps() > 0.0 ? plot->get_max_render_fps() : plot->get_render_check_fps()}), + }, {plot->get_max_render_fps()}), }; for (QWidget* cur : concat_widgets(bw(ws, "plot基础"), widgets)) { l->addWidget(cur); diff --git a/Renderive/base/global.cpp b/Renderive/base/global.cpp index a1b74dc..02fe2a1 100644 --- a/Renderive/base/global.cpp +++ b/Renderive/base/global.cpp @@ -140,12 +140,9 @@ void Render_Scheduler::remove_plot_on_scheduler(Abs_Plot* plot) { d->plots.erase(std::remove(d->plots.begin(), d->plots.end(), plot), d->plots.end()); Plot_Private* data = plot->d; data->render_enabled.store(false, std::memory_order_release); - if (data->render_timer) - data->render_timer->cancel(); - if (data->submit_timer) { - data->submit_timer->cancel(); - data->submit_timer_target_ns = 0; - data->render_submit_pending.store(false, std::memory_order_release); + if (data->rate_limit_timer) { + data->rate_limit_timer->cancel(); + data->rate_limit_timer_target_ns = 0; } } void Render_Scheduler::shutdown_on_scheduler() { @@ -153,28 +150,21 @@ void Render_Scheduler::shutdown_on_scheduler() { Plot_Private* data = plot->d; data->destroying.store(true, std::memory_order_release); data->render_enabled.store(false, std::memory_order_release); - if (data->render_timer) - data->render_timer->cancel(); - if (data->submit_timer) { - data->submit_timer->cancel(); - data->submit_timer_target_ns = 0; - data->render_submit_pending.store(false, std::memory_order_release); + if (data->rate_limit_timer) { + data->rate_limit_timer->cancel(); + data->rate_limit_timer_target_ns = 0; } } d->plots.clear(); } void Render_Scheduler::initialize_plot_on_scheduler(Abs_Plot* plot) { Plot_Private* data = plot->d; - if (!data->render_timer) - data->render_timer = std::make_unique(d->io_context); - if (!data->submit_timer) - data->submit_timer = std::make_unique(d->io_context); + if (!data->rate_limit_timer) + data->rate_limit_timer = std::make_unique(d->io_context); data->render_size = QSize(data->pending_render_width.load(std::memory_order_acquire), data->pending_render_height.load(std::memory_order_acquire)); if (plot->isVisible()) data->render_enabled.store(true, std::memory_order_release); - if (data->render_enabled.load(std::memory_order_acquire)) - plot->schedule_render_timer(); - plot->on_render_request(Render_Request_Source::Dirty); + plot->on_scheduler_event(Scheduler_Event::Control_Updated, 0); } Render_Scheduler& Global::render_scheduler() { return render_scheduler_value; diff --git a/Renderive/plottable/Afterglow_p.h b/Renderive/plottable/Afterglow_p.h index 3d382f0..13d58c3 100644 --- a/Renderive/plottable/Afterglow_p.h +++ b/Renderive/plottable/Afterglow_p.h @@ -11,7 +11,6 @@ #include "../base/global.h" #include "../primitive/Raster_Image.h" #include "Afterglow.h" -#include "Performance_Shower_p.h" namespace renderive { struct Afterglow_Render_State : Render_State, Afterglow_Prop {}; struct Afterglow_Input_Data : Input_Data { @@ -89,19 +88,15 @@ struct Afterglow_Private : Typed_Render_Datarestore(); } - void draw(QPainter* painter, const Plot_Render_Snapshot& snapshot) override { + void draw(QPainter* painter, const Plot_Render_Snapshot&) override { Afterglow_Render_State* s = render_state(); draw_image(painter, s); - Performance_Shower* shower = snapshot.performance_shower.get(); - if (shower) { - shower->set_performance_line("col_count, row_count", Performance_Line(QString("col_count:%1, row_count:%2").arg(s->frequency_point_size).arg(s->power_point_size))); - } } std::size_t estimated_frame_memory(const Plot_Render_Snapshot&) override { Afterglow_Render_State* s = render_state(); return static_cast(std::max(0, s->frequency_point_size)) * sizeof(int) + 8 * 1024; } - void prepare_data(const Plot_Render_Snapshot& snapshot) override { + void prepare_data(const Plot_Render_Snapshot&) override { sync_state_pipeline(); Afterglow_Render_State* s = render_state(); if (s->frequency_point_size <= 0 || s->power_point_size <= 0) @@ -129,7 +124,6 @@ struct Afterglow_Private : Typed_Render_Datafrequency_point_size, s->power_point_size, s->color_map); cache_image_dirty = false; } diff --git a/Renderive/plottable/Audio_Frequency_p.h b/Renderive/plottable/Audio_Frequency_p.h index 69466be..7d2d7f9 100644 --- a/Renderive/plottable/Audio_Frequency_p.h +++ b/Renderive/plottable/Audio_Frequency_p.h @@ -9,7 +9,6 @@ #include "../base/Memory.h" #include "../primitive/Curve.h" #include "Audio_Frequency.h" -#include "Performance_Shower_p.h" namespace renderive { struct Audio_Frequency_Input_Data : Input_Data { static constexpr std::size_t Max_Pending_Value_Count = 16; @@ -62,17 +61,12 @@ struct Audio_Frequency_Private : Typed_Render_Datatime_point_size) { + if (point_count != s->time_point_size) resize_power_buffer(s->time_point_size); - Performance_Shower* shower = snapshot.performance_shower.get(); - if (shower) { - shower->set_performance_line("TimePointSize", Performance_Line(QString("time_point_size:%1").arg(s->time_point_size))); - } - } d->data_list.read_all([this](double power) { push_power(power); }); @@ -91,16 +85,5 @@ struct Audio_Frequency_Private : Typed_Render_Datatime_axis.lock(); - if (!time_axis) - return; - Performance_Shower* shower = snapshot.performance_shower.get(); - if (shower) { - shower->set_performance_line("time_point_size", Performance_Line(QString("time_point_size:%1").arg(time_axis->time_point_size()))); - } - } }; } // namespace renderive diff --git a/Renderive/plottable/Performance_Shower.cpp b/Renderive/plottable/Performance_Shower.cpp index 5cae16d..71d83b5 100644 --- a/Renderive/plottable/Performance_Shower.cpp +++ b/Renderive/plottable/Performance_Shower.cpp @@ -1,4 +1,4 @@ -#include "../plottable/Performance_Shower_p.h" +#include "Performance_Shower_p.h" #include #include #include @@ -6,137 +6,162 @@ #include "Core/spdlog/export.h" namespace renderive { namespace { -static double non_negative(double value) { - return value > 0.0 ? value : 0.0; +double 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; } -static double ratio_percent(double value, double total) { - return total > 0.0 ? value * 100.0 / total : 0.0; +double cache_ratio(std::uint64_t hit, std::uint64_t stale_hit, std::uint64_t miss) { + std::uint64_t total = hit + stale_hit + miss; + return total ? static_cast(hit + stale_hit) / static_cast(total) : 0.0; } -struct Frame_Phase_Breakdown { - double data_ms{}; - double painter_ms{}; - double request_wait_ms{}; - double queue_ms{}; - double scheduler_wait_ms{}; - double present_wait_ms{}; - double lock_wait_ms{}; - double pipeline_ms{}; - double measured_ms{}; - double other_ms{}; - double phase_total_ms{}; - double necessary_ms{}; - double unnecessary_ms{}; -}; -static Frame_Phase_Breakdown frame_phase_breakdown(const Frame_Lifecycle_Sample& sample) { - Frame_Phase_Breakdown value; - double paint_overhead_ms = non_negative(sample.paint_event_ms - sample.paint_consume_ms - sample.paint_draw_image_ms); - double finish_overhead_ms = non_negative(sample.finish_render_ms - sample.publish_color_ms); - value.data_ms = sample.prepare_data_ms + sample.image_prepare_ms; - value.painter_ms = sample.painter_draw_ms + sample.painter_end_ms + sample.paint_draw_image_ms + paint_overhead_ms; - value.request_wait_ms = sample.request_to_submit_ms; - value.queue_ms = sample.cpu_queue_wait_ms; - value.scheduler_wait_ms = sample.finish_queue_wait_ms; - value.present_wait_ms = sample.update_to_paint_ms; - value.lock_wait_ms = sample.color_wait_ms + sample.paint_consume_ms + sample.publish_color_ms; - value.pipeline_ms = sample.submit_overhead_ms + finish_overhead_ms; - value.measured_ms = value.data_ms + value.painter_ms + value.request_wait_ms + value.queue_ms + value.scheduler_wait_ms + value.present_wait_ms + value.lock_wait_ms + value.pipeline_ms + sample.stats_log_ms; - value.phase_total_ms = sample.frame_total_ms > value.measured_ms ? sample.frame_total_ms : value.measured_ms; - value.other_ms = non_negative(value.phase_total_ms - value.measured_ms); - value.necessary_ms = value.data_ms + value.painter_ms + value.pipeline_ms + value.request_wait_ms + value.scheduler_wait_ms + value.present_wait_ms; - value.unnecessary_ms = non_negative(value.phase_total_ms - value.necessary_ms); - return value; +std::string csv_escape(const std::string& value) { + std::string escaped; + escaped.reserve(value.size() + 2); + escaped.push_back('"'); + for (char ch : value) { + if (ch == '"') + escaped.push_back('"'); + escaped.push_back(ch); + } + escaped.push_back('"'); + return escaped; +} +std::string renderable_cache_stats_text(const Frame_Metadata& frame) { + std::ostringstream stream; + bool first = true; + for (const auto& stat : frame.renderable_cache_stats) { + if (!first) + stream << '|'; + first = false; + QByteArray name = stat.object_name.toUtf8(); + stream << std::string(name.constData(), static_cast(name.size())) + << ":hit=" << stat.hit_count + << ";stale=" << stat.stale_hit_count + << ";miss=" << stat.miss_count; + } + return stream.str(); } class Performance_Frame_Log { public: - void write(const Frame_Lifecycle_Sample& sample) { + void write(const Frame_Metadata& frame) { if (!enabled()) return; - Frame_Phase_Breakdown phase = frame_phase_breakdown(sample); + const Frame_Feedback_State& feedback = frame.feedback; std::ostringstream stream; stream << std::setprecision(9) - << static_cast(sample.plot_id) << ',' - << static_cast(sample.version) << ',' - << sample.frame_total_ms << ',' - << phase.phase_total_ms << ',' - << phase.necessary_ms << ',' - << phase.unnecessary_ms << ',' - << phase.data_ms << ',' - << phase.painter_ms << ',' - << phase.request_wait_ms << ',' - << phase.queue_ms << ',' - << phase.scheduler_wait_ms << ',' - << phase.present_wait_ms << ',' - << phase.lock_wait_ms << ',' - << phase.pipeline_ms << ',' - << phase.other_ms << ',' - << ratio_percent(phase.necessary_ms, phase.phase_total_ms) << ',' - << ratio_percent(phase.unnecessary_ms, phase.phase_total_ms) << ',' - << ratio_percent(phase.other_ms, phase.phase_total_ms) << ',' - << ratio_percent(phase.data_ms, phase.phase_total_ms) << ',' - << ratio_percent(phase.painter_ms, phase.phase_total_ms) << ',' - << ratio_percent(phase.request_wait_ms, phase.phase_total_ms) << ',' - << ratio_percent(phase.queue_ms, phase.phase_total_ms) << ',' - << ratio_percent(phase.scheduler_wait_ms, phase.phase_total_ms) << ',' - << ratio_percent(phase.present_wait_ms, phase.phase_total_ms) << ',' - << ratio_percent(phase.lock_wait_ms, phase.phase_total_ms) << ',' - << ratio_percent(phase.pipeline_ms, phase.phase_total_ms) << ',' - << ratio_percent(sample.stats_log_ms, phase.phase_total_ms) << ',' - << sample.request_to_submit_ms << ',' - << sample.prepare_data_ms << ',' - << sample.renderable_prepare_ms << ',' - << sample.submit_overhead_ms << ',' - << sample.color_wait_ms << ',' - << sample.cpu_queue_wait_ms << ',' - << sample.finish_queue_wait_ms << ',' - << sample.render_color_ms << ',' - << sample.image_prepare_ms << ',' - << sample.painter_draw_ms << ',' - << sample.painter_end_ms << ',' - << sample.publish_color_ms << ',' - << sample.finish_render_ms << ',' - << sample.update_to_paint_ms << ',' - << sample.paint_event_ms << ',' - << sample.paint_consume_ms << ',' - << sample.paint_draw_image_ms << ',' - << sample.stats_log_ms << ',' - << sample.renderable_count << ',' - << sample.visible_renderable_count << ',' - << sample.direct_draw_count << ',' - << sample.local_cache_count; - csv_log(configured_path_utf8(), header(), stream.str()); + << frame.frame_id << ',' + << frame.input_version << ',' + << frame.snapshot_version << ',' + << frame.frame_slot_generation << ',' + << frame_outcome_name(frame.frame_outcome) << ',' + << bottleneck_state_name(feedback.bottleneck_state) << ',' + << feedback.input_rate_fps << ',' + << feedback.production_rate_fps << ',' + << feedback.producer_capacity_fps << ',' + << feedback.display_rate_fps << ',' + << feedback.consumer_capacity_fps << ',' + << feedback.manual_min_render_interval_ms << ',' + << feedback.adaptive_min_render_interval_ms << ',' + << feedback.effective_min_render_interval_ms << ',' + << feedback.input_interval_srtt_ms << ',' + << feedback.input_interval_jitter_ms << ',' + << feedback.render_interval_srtt_ms << ',' + << feedback.render_interval_jitter_ms << ',' + << feedback.render_queue_wait_srtt_ms << ',' + << feedback.render_queue_wait_jitter_ms << ',' + << feedback.render_duration_srtt_ms << ',' + << feedback.render_duration_jitter_ms << ',' + << feedback.ready_frame_wait_srtt_ms << ',' + << feedback.ready_frame_wait_jitter_ms << ',' + << feedback.update_queue_wait_srtt_ms << ',' + << feedback.update_queue_wait_jitter_ms << ',' + << feedback.paint_duration_srtt_ms << ',' + << feedback.paint_duration_jitter_ms << ',' + << feedback.consume_interval_srtt_ms << ',' + << feedback.consume_interval_jitter_ms << ',' + << feedback.frame_age_srtt_ms << ',' + << feedback.frame_age_jitter_ms << ',' + << feedback.producer_busy_ratio << ',' + << feedback.ready_frame_occupancy_ratio << ',' + << feedback.consumer_busy_ratio << ',' + << feedback.input_pending_ratio << ',' + << feedback.superseded_ready_ratio << ',' + << feedback.consumer_underrun_ratio << ',' + << feedback.unique_consumed_ratio << ',' + << feedback.display_efficiency << ',' + << feedback.wasted_render_time_ms << ',' + << feedback.latest_input_version << ',' + << feedback.rendering_input_version << ',' + << feedback.published_input_version << ',' + << feedback.consumed_input_version << ',' + << feedback.produced_frame_count << ',' + << feedback.consumed_frame_count << ',' + << feedback.superseded_frame_count << ',' + << feedback.repeated_frame_count << ',' + << duration_ms(frame.render_request_time, frame.render_queue_enter_time) << ',' + << duration_ms(frame.render_queue_enter_time, frame.render_begin_time) << ',' + << duration_ms(frame.prepare_begin_time, frame.prepare_end_time) << ',' + << duration_ms(frame.draw_begin_time, frame.draw_end_time) << ',' + << duration_ms(frame.render_begin_time, frame.render_end_time) << ',' + << duration_ms(frame.publish_time, frame.paint_begin_time ? frame.paint_begin_time : frame.superseded_time) << ',' + << duration_ms(frame.update_request_time, frame.paint_begin_time) << ',' + << duration_ms(frame.paint_begin_time, frame.paint_end_time) << ',' + << duration_ms(frame.core_data_time, frame.paint_end_time) << ',' + << duration_ms(frame.render_request_time, frame.consumer_release_time ? frame.consumer_release_time : frame.superseded_time) << ',' + << frame.raw_input_count << ',' + << frame.visible_input_count << ',' + << frame.prepared_output_count << ',' + << frame.draw_point_count << ',' + << frame.draw_segment_count << ',' + << frame.renderable_cache_hit_count << ',' + << frame.renderable_cache_stale_hit_count << ',' + << frame.renderable_cache_miss_count << ',' + << cache_ratio(frame.renderable_cache_hit_count, frame.renderable_cache_stale_hit_count, frame.renderable_cache_miss_count) << ',' + << frame.pixel_width << ',' + << frame.pixel_height << ',' + << frame.renderable_count << ',' + << frame.visible_renderable_count << ',' + << frame.direct_draw_count << ',' + << frame.local_cache_count << ',' + << frame.core_data_time << ',' + << frame.render_begin_time << ',' + << frame.render_end_time << ',' + << frame.publish_time << ',' + << frame.paint_begin_time << ',' + << frame.paint_end_time << ',' + << frame.consumer_release_time << ',' + << frame.superseded_time << ',' + << csv_escape(renderable_cache_stats_text(frame)); + csv_log(path(), header(), stream.str()); } private: - bool enabled() const { + static bool enabled() { static bool value = []() { bool ok = false; - int env_value = qEnvironmentVariableIntValue("RENDERIVE_PERFORMANCE_LOG", &ok); - return ok ? env_value != 0 : true; + int configured = qEnvironmentVariableIntValue("RENDERIVE_PERFORMANCE_LOG", &ok); + return ok && configured != 0; }(); return value; } - std::string configured_path_utf8() const { + static std::string path() { static std::string value = []() { - QString path = configured_path_qstring(); - QByteArray bytes = path.toUtf8(); + QString configured = qEnvironmentVariable("RENDERIVE_PERFORMANCE_LOG_PATH"); + if (configured.isEmpty()) { + QCoreApplication* application = QCoreApplication::instance(); + QString directory = application ? application->applicationDirPath() : QDir::currentPath(); + configured = QDir(directory).filePath("renderive_performance_log.csv"); + } + QByteArray bytes = configured.toUtf8(); return std::string(bytes.constData(), static_cast(bytes.size())); }(); return value; } - static QString configured_path_qstring() { - QString value = qEnvironmentVariable("RENDERIVE_PERFORMANCE_LOG_PATH"); - if (!value.isEmpty()) - return value; - QCoreApplication* app = QCoreApplication::instance(); - QString dir = app ? app->applicationDirPath() : QDir::currentPath(); - return QDir(dir).filePath("renderive_performance_log.csv"); - } - std::string header() const { - return "plot_id,version,frame_total_ms,phase_total_ms,necessary_ms,unnecessary_ms,data_ms,painter_ms,request_wait_ms,queue_ms,scheduler_wait_ms,present_wait_ms,lock_wait_ms,pipeline_ms,other_ms,necessary_ratio,unnecessary_ratio,other_ratio,data_ratio,painter_ratio,request_wait_ratio,queue_ratio,scheduler_wait_ratio,present_wait_ratio,lock_wait_ratio,pipeline_ratio,stats_log_ratio,request_to_submit_ms,prepare_data_ms,renderable_prepare_ms,submit_overhead_ms,color_wait_ms,cpu_queue_wait_ms,finish_queue_wait_ms,render_color_ms,image_prepare_ms,painter_draw_ms,painter_end_ms,publish_color_ms,finish_render_ms,update_to_paint_ms,paint_event_ms,paint_consume_ms,paint_draw_image_ms,stats_log_ms,renderable_count,visible_renderable_count,direct_draw_count,local_cache_count"; + static std::string header() { + return "frame_id,input_version,snapshot_version,slot_generation,outcome,bottleneck,input_fps,production_fps,producer_capacity_fps,display_fps,consumer_capacity_fps,manual_interval_ms,adaptive_interval_ms,effective_interval_ms,input_srtt_ms,input_jitter_ms,render_interval_srtt_ms,render_interval_jitter_ms,render_queue_srtt_ms,render_queue_jitter_ms,render_duration_srtt_ms,render_duration_jitter_ms,ready_wait_srtt_ms,ready_wait_jitter_ms,update_wait_srtt_ms,update_wait_jitter_ms,paint_srtt_ms,paint_jitter_ms,consume_interval_srtt_ms,consume_interval_jitter_ms,frame_age_srtt_ms,frame_age_jitter_ms,producer_busy_ratio,ready_occupancy_ratio,consumer_busy_ratio,input_pending_ratio,superseded_ratio,underrun_ratio,unique_consumed_ratio,display_efficiency,wasted_render_ms,latest_input_version,rendering_input_version,published_input_version,consumed_input_version,produced_frames,consumed_frames,superseded_frames,repeated_frames,request_wait_ms,render_queue_wait_ms,prepare_ms,draw_ms,render_ms,ready_wait_ms,update_wait_ms,paint_ms,frame_age_ms,frame_total_ms,raw_input_count,visible_input_count,prepared_output_count,draw_point_count,draw_segment_count,cache_hit_count,cache_stale_hit_count,cache_miss_count,cache_usable_ratio,pixel_width,pixel_height,renderable_count,visible_renderable_count,direct_draw_count,local_cache_count,core_data_time_ns,render_begin_time_ns,render_end_time_ns,publish_time_ns,paint_begin_time_ns,paint_end_time_ns,consumer_release_time_ns,superseded_time_ns,renderable_cache_stats"; } }; Performance_Frame_Log& frame_log() { - static Performance_Frame_Log log; - return log; + static Performance_Frame_Log value; + return value; } } using Performance_Shower_Binding = Renderable_Binding; @@ -157,108 +182,23 @@ Render_Object_Owner Performance_Shower::create_input_data() { } void Performance_Shower::setEnabled(bool enabled) { enabled_value.store(enabled, std::memory_order_release); - if (enabled) { - append_event({Performance_Event_Type::Clear}); - } + if (enabled) + d()->reset_requested.store(true, std::memory_order_release); mark_render_dirty(); } bool Performance_Shower::enabled() const { return enabled_value.load(std::memory_order_acquire); } -void Performance_Shower::refresh_counter() { - append_event({Performance_Event_Type::Clear}); -} -void Performance_Shower::counter_increase(const QString& counter_name) { - increment_counter("General", counter_name); -} -void Performance_Shower::increment_counter(const QString& scope, const QString& name) { - Performance_Event event; - event.type = Performance_Event_Type::Counter; - event.scope = scope; - event.key = name; - append_event(event); -} -void Performance_Shower::set_performance_line(const QString& key, const Performance_Line& line) { - Performance_Event event; - event.type = Performance_Event_Type::Line; - event.scope = "General"; - event.key = key; - event.line = line; - append_event(event); -} -void Performance_Shower::set_info(const QString& key, const QString& value) { - set_state("General", key, value); -} -void Performance_Shower::set_state(const QString& scope, const QString& name, const QString& value) { - Performance_Event event; - event.type = Performance_Event_Type::State; - event.scope = scope; - event.key = name; - event.value = value; - append_event(event); -} -void Performance_Shower::set_gauge(const QString& scope, const QString& name, double value) { - Performance_Event event; - event.type = Performance_Event_Type::Gauge; - event.scope = scope; - event.key = name; - event.number = value; - append_event(event); -} -void Performance_Shower::record_cost(const QString& key, double value) { - record_duration("General", key, value); -} -void Performance_Shower::record_duration(const QString& scope, const QString& name, double value) { - Performance_Event event; - event.type = Performance_Event_Type::Duration; - event.scope = scope; - event.key = name; - event.number = value; - append_event(event); -} -void log_frame_lifecycle_sample(const Frame_Lifecycle_Sample& sample) { - frame_log().write(sample); -} -void Performance_Shower::log_frame_sample(const Frame_Lifecycle_Sample& sample) { +void Performance_Shower::collect_frame(const Frame_Metadata& frame) { if (!enabled()) return; - log_frame_lifecycle_sample(sample); -} -void Performance_Shower::set_view_filter(const QString& name) { - set_view_filter(name, {}); -} -void Performance_Shower::set_view_filter(const QString& group, const QString& name) { - Performance_Event event; - event.type = Performance_Event_Type::Select_View; - event.scope = group; - event.key = name; - append_event(event); -} -void Performance_Shower::scroll_view(int lines) { - Performance_Event event; - event.type = Performance_Event_Type::Scroll_View; - event.number = lines; - append_event(event); -} -void Performance_Shower::append_event(const Performance_Event& event) { - if (!enabled() && event.type != Performance_Event_Type::Clear) - return; - auto pd = d(); - Render_Input_Lease input(pd, false); - input->push(event); - notify_observed_update(event.type == Performance_Event_Type::Clear || event.type == Performance_Event_Type::Select_View || event.type == Performance_Event_Type::Scroll_View); -} -void Performance_Shower::notify_observed_update(bool force) { - std::uint64_t now = static_cast(std::chrono::duration_cast(std::chrono::steady_clock::now().time_since_epoch()).count()); - std::uint64_t last = last_notify_ns.load(std::memory_order_acquire); - if (!force && now - last < 100000000) - return; - if (force) { - last_notify_ns.store(now, std::memory_order_release); - mark_render_dirty(); - return; + { + Render_Input_Lease input(d(), false); + input->push(frame); } - if (force || last_notify_ns.compare_exchange_strong(last, now, std::memory_order_acq_rel, std::memory_order_acquire)) - mark_render_dirty(); + write_frame_performance_log(frame); +} +void write_frame_performance_log(const Frame_Metadata& frame) { + frame_log().write(frame); } } // namespace renderive diff --git a/Renderive/plottable/Performance_Shower_p.h b/Renderive/plottable/Performance_Shower_p.h index 5a039a6..40ceeee 100644 --- a/Renderive/plottable/Performance_Shower_p.h +++ b/Renderive/plottable/Performance_Shower_p.h @@ -1,185 +1,152 @@ #pragma once -#include #include +#include #include -#include -#include +#include #include +#include +#include "../architecture/Frame_Scheduler.h" #include "../architecture/Renderable.h" -#include "../base/Memory.h" #include "Core/Statistics/Statistics.h" namespace renderive { -void log_frame_lifecycle_sample(const Frame_Lifecycle_Sample& sample); -inline QString performance_align_text(const QString& text, int& width) { - width = qMax(width, static_cast(text.size())); - return QString("%1").arg(text, width); -} -inline QString performance_number(double value, int& width, int precision = 2) { - return performance_align_text(QString::number(value, 'f', precision), width); -} -template -inline QString performance_count(T value, int& width) { - return performance_align_text(QString::number(static_cast(value)), width); -} -struct Performance_Line { - Performance_Line() = default; - explicit Performance_Line(const QString& data) { - this->data = data; - int arg_index = 1; - while (data.contains("%" + QString::number(arg_index))) { - arg_index++; - } - place_holder_num = arg_index - 1; - values.resize(place_holder_num); - value_widths.resize(place_holder_num); - } - QString data; - int place_holder_num{}; - std::vector values; - std::vector value_widths; - Psc::Value_Statistics statistics; - void update(double value) { - statistics.update(value); - if (values.size() > 0) - values[0] = statistics.instant; - if (values.size() > 1) - values[1] = statistics.smooth; - if (values.size() > 2) - values[2] = statistics.variation; - } - int& width_at(int index) { - if (value_widths.size() <= index) - value_widths.resize(index + 1); - return value_widths[index]; - } - QString to_string() { - QString ret = data; - int arg_index = 1; - for (const double& value : values) { - ret = ret.replace("%" + QString::number(arg_index), performance_number(value, width_at(arg_index - 1))); - arg_index++; - } - return ret; - } -}; -enum class Performance_Metric_Type : std::uint8_t { - Duration, - Counter, - Gauge, - State, - Line -}; -enum class Performance_Event_Type : std::uint8_t { - Clear, - Duration, - Counter, - Line, - Gauge, - State, - Select_View, - Scroll_View -}; -struct Performance_Event { - Performance_Event_Type type = Performance_Event_Type::Clear; - QString scope; - QString key; - QString value; - Performance_Line line; - double number{}; -}; -struct Performance_Event_Key { - QString scope; - QString key; - bool operator<(const Performance_Event_Key& other) const { - if (scope < other.scope) - return true; - if (other.scope < scope) - return false; - return key < other.key; - } -}; -struct Performance_Metric { - Performance_Metric_Type type = Performance_Metric_Type::State; - Psc::Value_Statistics value; - Psc::Speed_Statistics speed; - Performance_Line line; - QString text; - double gauge{}; - std::uint64_t count{}; - std::vector widths; - int& width_at(int index) { - if (widths.size() <= index) - widths.resize(index + 1); - return widths[index]; - } - [[nodiscard]] QString to_string(const QString& name) { - switch (type) { - case Performance_Metric_Type::Duration: - if (name.contains("%1")) { - QString ret = name; - ret = ret.replace("%1", performance_number(value.instant, width_at(0))); - ret = ret.replace("%2", performance_number(value.smooth, width_at(1))); - ret = ret.replace("%3", performance_number(value.variation, width_at(2))); - ret = ret.replace("%4", performance_number(value.p95, width_at(3))); - return ret; - } - return QString("%1 last:%2ms srtt:%3ms rttvar:%4ms min:%5ms max:%6ms p95:%7ms count:%8") - .arg(name, performance_number(value.instant, width_at(0)), performance_number(value.smooth, width_at(1)), performance_number(value.variation, width_at(2)), performance_number(value.min, width_at(3)), performance_number(value.max, width_at(4)), performance_number(value.p95, width_at(5)), performance_count(value.times, width_at(6))); - case Performance_Metric_Type::Counter: - return QString("%1 speed:%2/s avg:%3/s count:%4") - .arg(name, performance_number(speed.instant_speed, width_at(0)), performance_number(speed.average_speed, width_at(1)), performance_count(speed.times, width_at(2))); - case Performance_Metric_Type::Gauge: - return QString("%1: %2").arg(name, performance_number(gauge, width_at(0))); - case Performance_Metric_Type::State: - return QString("%1: %2").arg(name, text); - case Performance_Metric_Type::Line: - return line.to_string(); - } - return {}; - } -}; +void write_frame_performance_log(const Frame_Metadata& frame); struct Performance_Shower_Render_State : Render_State { + Performance_Shower_Render_State() { + font.setStyleHint(QFont::Monospace); + } QFont font; int top_margin = 4; - int left_margin = 4; - int right_margin = 28; - int bottom_margin = 8; - int column_gap = 12; - int group_sidebar_width = 92; - int target_sidebar_width = 170; + int left_margin = 6; + int right_margin = 12; + int bottom_margin = 6; + int scroll_bar_width = 8; + int scroll_bar_margin = 2; QColor background = Qt::white; QColor foreground = Qt::black; - QColor sidebar_background = QColor(235, 235, 235); - QColor selected_background = QColor(210, 225, 245); - QColor scrollbar_background = QColor(210, 210, 210); - QColor scrollbar_handle = QColor(120, 120, 120); + QColor scroll_track = QColor(220, 220, 220); + QColor scroll_thumb = QColor(80, 80, 80); }; struct Performance_Shower_Input_Data : Input_Data { - std::pmr::vector events{memory_resource(Memory_Domain::Other)}; - std::pmr::map pending_values{memory_resource(Memory_Domain::Other)}; - void push(const Performance_Event& event) { - if (event.type == Performance_Event_Type::Clear) { - events.clear(); - pending_values.clear(); - events.push_back(event); + static constexpr std::size_t Capacity = 64; + std::array frames{}; + std::size_t first{}; + std::size_t count{}; + std::uint64_t capacity_dropped{}; + bool reset_pending{}; + void push(const Frame_Metadata& frame) { + if (count < Capacity) { + frames[(first + count) % Capacity] = frame; + ++count; return; } - if (event.type == Performance_Event_Type::Gauge || event.type == Performance_Event_Type::State) { - pending_values[{event.scope, event.key}] = event; - return; - } - events.push_back(event); + frames[first] = frame; + first = (first + 1) % Capacity; + ++capacity_dropped; + } + void request_reset() { + first = 0; + count = 0; + capacity_dropped = 0; + reset_pending = true; } template - void read_all(Handler handler) { - for (const Performance_Event& event : events) - handler(event); - for (const auto& item : pending_values) - handler(item.second); + void read_all(Handler handler) const { + for (std::size_t i = 0; i < count; ++i) + handler(frames[(first + i) % Capacity]); } void clear() override { - events.clear(); - pending_values.clear(); + first = 0; + count = 0; + capacity_dropped = 0; + reset_pending = false; + } + std::uint64_t get_pending_count() const override { + return count; + } + std::uint64_t get_capacity_dropped_count() const override { + return capacity_dropped; + } +}; +struct Frame_Performance_Aggregate { + Psc::Value_Statistics request_wait_ms; + Psc::Value_Statistics render_queue_wait_ms; + Psc::Value_Statistics prepare_ms; + Psc::Value_Statistics draw_ms; + Psc::Value_Statistics render_ms; + Psc::Value_Statistics ready_wait_ms; + Psc::Value_Statistics update_wait_ms; + Psc::Value_Statistics paint_ms; + Psc::Value_Statistics frame_age_ms; + Psc::Value_Statistics frame_total_ms; + Frame_Metadata latest; + std::uint64_t observed_frame_count{}; + std::uint64_t produced_outcome_count{}; + std::uint64_t consumed_outcome_count{}; + std::uint64_t superseded_outcome_count{}; + std::uint64_t cancelled_outcome_count{}; + std::uint64_t repeated_outcome_count{}; + std::uint64_t cache_hit_count{}; + std::uint64_t cache_stale_hit_count{}; + std::uint64_t cache_miss_count{}; + std::uint64_t input_capacity_dropped{}; + bool has_frame{}; + std::vector renderable_cache_stats; + void clear() { + *this = {}; + } + void update(const Frame_Metadata& frame) { + latest = frame; + has_frame = true; + ++observed_frame_count; + update_duration(request_wait_ms, frame.render_request_time, frame.render_queue_enter_time); + update_duration(render_queue_wait_ms, frame.render_queue_enter_time, frame.render_begin_time); + update_duration(prepare_ms, frame.prepare_begin_time, frame.prepare_end_time); + update_duration(draw_ms, frame.draw_begin_time, frame.draw_end_time); + update_duration(render_ms, frame.render_begin_time, frame.render_end_time); + update_duration(ready_wait_ms, frame.publish_time, frame.paint_begin_time ? frame.paint_begin_time : frame.superseded_time); + update_duration(update_wait_ms, frame.update_request_time, frame.paint_begin_time); + update_duration(paint_ms, frame.paint_begin_time, frame.paint_end_time); + update_duration(frame_age_ms, frame.core_data_time, frame.paint_end_time); + update_duration(frame_total_ms, frame.render_request_time, frame.consumer_release_time ? frame.consumer_release_time : frame.superseded_time); + cache_hit_count += frame.renderable_cache_hit_count; + cache_stale_hit_count += frame.renderable_cache_stale_hit_count; + cache_miss_count += frame.renderable_cache_miss_count; + for (const auto& stat : frame.renderable_cache_stats) { + Renderable_Cache_Frame_Stat& aggregate_stat = cache_stat(stat.object_name); + aggregate_stat.hit_count += stat.hit_count; + aggregate_stat.stale_hit_count += stat.stale_hit_count; + aggregate_stat.miss_count += stat.miss_count; + } + switch (frame.frame_outcome) { + case Frame_Outcome::Produced: + ++produced_outcome_count; + break; + case Frame_Outcome::Consumed: + ++consumed_outcome_count; + break; + case Frame_Outcome::Superseded: + ++superseded_outcome_count; + break; + case Frame_Outcome::Cancelled: + ++cancelled_outcome_count; + break; + case Frame_Outcome::Repeated: + ++repeated_outcome_count; + break; + } + } +private: + Renderable_Cache_Frame_Stat& cache_stat(const QString& object_name) { + for (auto& stat : renderable_cache_stats) { + if (stat.object_name == object_name) + return stat; + } + renderable_cache_stats.push_back({object_name}); + return renderable_cache_stats.back(); + } + static void update_duration(Psc::Value_Statistics& statistics, std::uint64_t begin_ns, std::uint64_t end_ns) { + if (end_ns > begin_ns) + statistics.update(static_cast(end_ns - begin_ns) / 1000000.0); } }; struct Performance_Shower_Private; @@ -192,48 +159,31 @@ public: Render_Object_Owner create_input_data() override; void setEnabled(bool enabled); bool enabled() const; - void refresh_counter(); - void counter_increase(const QString& counter_name); - void increment_counter(const QString& scope, const QString& name); - void set_performance_line(const QString& key, const Performance_Line& line); - void set_info(const QString& key, const QString& value); - void set_state(const QString& scope, const QString& name, const QString& value); - void set_gauge(const QString& scope, const QString& name, double value); - void record_cost(const QString& key, double value); - void record_duration(const QString& scope, const QString& name, double value); - void log_frame_sample(const Frame_Lifecycle_Sample& sample); - void set_view_filter(const QString& name); - void set_view_filter(const QString& group, const QString& name); - void scroll_view(int lines); + void collect_frame(const Frame_Metadata& frame); private: - void append_event(const Performance_Event& event); - void notify_observed_update(bool force); std::atomic_bool enabled_value{false}; - std::atomic last_notify_ns{0}; }; struct Performance_Shower_Private : Typed_Render_Data { friend class Performance_Shower; - std::map> scopes; - std::vector all_info; - std::vector group_items; - std::vector target_items; - std::vector group_rects; - std::vector target_rects; + Frame_Performance_Aggregate aggregate; + std::atomic_bool reset_requested{false}; + std::vector lines; + std::vector> stable_value_widths; QRect panel_rect; - int group_sidebar_width{}; - int target_sidebar_width{}; + QRect scroll_bar_rect; + QRect scroll_thumb_rect; + std::atomic scroll_line{0}; + std::atomic max_scroll_line{0}; int content_width{}; - int scroll_line{}; - int max_scroll_line{}; - bool sidebar_width_dirty = true; - bool content_width_dirty = true; - QString selected_group = "Plot"; - QString selected_target = "Overview"; + int held_content_width{}; void prepare_data(const Plot_Render_Snapshot&) override { sync_state_pipeline(); - auto input = render_input_data(); - input->read_all([this](const Performance_Event& event) { - apply_event(event); + Performance_Shower_Input_Data* input = render_input_data(); + if (reset_requested.exchange(false, std::memory_order_acq_rel) || input->reset_pending) + aggregate.clear(); + aggregate.input_capacity_dropped += input->capacity_dropped; + input->read_all([this](const Frame_Metadata& frame) { + aggregate.update(frame); }); clear_render_input(); rebuild_lines(); @@ -241,348 +191,284 @@ struct Performance_Shower_Private : Typed_Render_Dataenabled() && panel_rect.contains(pos.toPoint()); } - void mousePressEvent(QMouseEvent* event) override { - for (int i = 0; i < group_rects.size(); ++i) { - if (group_rects[i].contains(event->pos())) { - q()->set_view_filter(group_items[i], {}); - return; - } - } - for (int i = 0; i < target_rects.size(); ++i) { - if (target_rects[i].contains(event->pos())) { - q()->set_view_filter(selected_group, target_items[i]); - return; - } - } - } - void wheelEvent(QWheelEvent* event) override { - if (!panel_rect.contains(event->pos())) + void draw(QPainter* painter, const Plot_Render_Snapshot& snapshot) override { + if (!q()->enabled() || lines.empty()) return; - int steps = event->angleDelta().y() > 0 ? -3 : 3; - q()->scroll_view(steps); - } - void draw(QPainter* painter, const Plot_Render_Snapshot&) override { - if (!q()->enabled() || group_items.empty()) - return; - auto s = render_state(); + Performance_Shower_Render_State* state = render_state(); painter->save(); - painter->setFont(s->font); - QFontMetrics fm(s->font); - if (sidebar_width_dirty) - update_sidebar_width(fm, s); - if (content_width_dirty) - update_content_width(fm, s); - int side_line_count = static_cast(std::max(group_items.size(), target_items.size())); - int line_count = std::max(static_cast(all_info.size()), side_line_count); - int max_height = painter->device()->height(); - int sidebar_width = group_sidebar_width + target_sidebar_width; - int content_x = sidebar_width + s->column_gap; - int width = sidebar_width + s->column_gap + content_width; - int full_height = fm.lineSpacing() * line_count + s->top_margin + s->bottom_margin; - int height = qMin(full_height, max_height); - int visible_lines = qMax(1, (height - s->top_margin - s->bottom_margin) / fm.lineSpacing()); - max_scroll_line = std::max(0, static_cast(all_info.size()) - visible_lines); - scroll_line = qBound(0, scroll_line, max_scroll_line); - QRect rect(0, 0, width, height); - QRect group_rect(0, 0, group_sidebar_width, height); - QRect target_rect(group_rect.right() + 1, 0, target_sidebar_width, height); - panel_rect = rect; - group_rects.clear(); - target_rects.clear(); - painter->fillRect(rect, s->background); - painter->fillRect(group_rect, s->sidebar_background); - painter->fillRect(target_rect, s->sidebar_background); - painter->setPen(s->foreground); - for (int i = 0; i < visible_lines && i + scroll_line < all_info.size(); ++i) { - painter->drawText(QPointF(content_x + s->left_margin, s->top_margin + i * fm.lineSpacing() + fm.lineSpacing()), all_info[i + scroll_line]); + painter->setFont(state->font); + QFontMetrics metrics(state->font); + update_content_width(metrics, state); + int line_height = metrics.lineSpacing(); + int panel_width = qMin(content_width, qMax(0, snapshot.size.width())); + int height = panel_height(metrics, state, snapshot.size.height()); + int visible_count = visible_line_count(metrics, state, height); + int max_scroll = qMax(0, static_cast(lines.size()) - visible_count); + int scroll = std::clamp(scroll_line.load(std::memory_order_acquire), 0, max_scroll); + max_scroll_line.store(max_scroll, std::memory_order_release); + scroll_line.store(scroll, std::memory_order_release); + panel_rect = QRect(0, 0, panel_width, height); + painter->fillRect(panel_rect, state->background); + painter->setPen(state->foreground); + QRect text_rect = panel_rect.adjusted(state->left_margin, state->top_margin, -state->right_margin - state->scroll_bar_width - state->scroll_bar_margin * 2, -state->bottom_margin); + painter->setClipRect(text_rect); + int last_line = qMin(static_cast(lines.size()), scroll + visible_count); + for (int i = scroll; i < last_line; ++i) + painter->drawText(QPointF(state->left_margin, state->top_margin + (i - scroll + 1) * line_height), lines[i]); + painter->setClipping(false); + update_scroll_bar_rects(state, max_scroll, scroll, visible_count); + if (!scroll_bar_rect.isEmpty()) { + painter->fillRect(scroll_bar_rect, state->scroll_track); + painter->fillRect(scroll_thumb_rect, state->scroll_thumb); } - for (int i = 0; i < group_items.size() && i < visible_lines; ++i) { - QRect item_rect(group_rect.left(), s->top_margin + i * fm.lineSpacing(), group_sidebar_width, fm.lineSpacing()); - group_rects.push_back(item_rect); - if (group_items[i] == selected_group) - painter->fillRect(item_rect, s->selected_background); - painter->drawText(QPointF(item_rect.left() + s->left_margin, item_rect.top() + fm.lineSpacing()), group_items[i]); - } - for (int i = 0; i < target_items.size() && i < visible_lines; ++i) { - QRect item_rect(target_rect.left(), s->top_margin + i * fm.lineSpacing(), target_sidebar_width, fm.lineSpacing()); - target_rects.push_back(item_rect); - if (target_items[i] == selected_target) - painter->fillRect(item_rect, s->selected_background); - painter->drawText(QPointF(item_rect.left() + s->left_margin, item_rect.top() + fm.lineSpacing()), target_items[i]); - } - if (max_scroll_line > 0) - draw_scrollbar(painter, s, QRect(width - 8, 0, 8, height), visible_lines); painter->restore(); } QRect pixel_bounds(const QSize& plot_size) override { - if (!q()->enabled() || group_items.empty()) + if (!q()->enabled() || lines.empty()) return {}; - auto s = render_state(); - QFontMetrics fm(s->font); - if (sidebar_width_dirty) - update_sidebar_width(fm, s); - if (content_width_dirty) - update_content_width(fm, s); - int side_line_count = static_cast(std::max(group_items.size(), target_items.size())); - int line_count = std::max(static_cast(all_info.size()), side_line_count); - int sidebar_width = group_sidebar_width + target_sidebar_width; - int width = sidebar_width + s->column_gap + content_width; - int full_height = fm.lineSpacing() * line_count + s->top_margin + s->bottom_margin; - int height = qMin(full_height, plot_size.height()); - return QRect(0, 0, width, height); + Performance_Shower_Render_State* state = render_state(); + QFontMetrics metrics(state->font); + update_content_width(metrics, state); + int height = panel_height(metrics, state, plot_size.height()); + return QRect(0, 0, qMin(content_width, plot_size.width()), height); } - void draw_scrollbar(QPainter* painter, Performance_Shower_Render_State* state, const QRect& rect, int visible_lines) { - painter->fillRect(rect, state->scrollbar_background); - double ratio = static_cast(visible_lines) / static_cast(all_info.size()); - int handle_height = qMax(16, qRound(rect.height() * ratio)); - int travel = qMax(1, rect.height() - handle_height); - int top = qRound(static_cast(travel) * static_cast(scroll_line) / static_cast(max_scroll_line)); - painter->fillRect(QRect(rect.left(), rect.top() + top, rect.width(), handle_height), state->scrollbar_handle); + void wheelEvent(QWheelEvent* event) override { + if (!q()->enabled() || !panel_rect.contains(event->pos())) + return; + int max_scroll = max_scroll_line.load(std::memory_order_acquire); + if (!max_scroll) + return; + int delta = event->angleDelta().y(); + if (!delta) + return; + int step = qMax(1, qAbs(delta) / 120) * 3; + int current = scroll_line.load(std::memory_order_acquire); + int next = std::clamp(current + (delta > 0 ? -step : step), 0, max_scroll); + if (next == current) + return; + scroll_line.store(next, std::memory_order_release); + event->accept(); + q()->mark_render_dirty(); } - void update_sidebar_width(const QFontMetrics& fm, Performance_Shower_Render_State* state) { - int group_width = 0; - int target_width = 0; - for (const QString& item : group_items) - group_width = qMax(group_width, fm.horizontalAdvance(item)); - for (const QString& item : target_items) - target_width = qMax(target_width, fm.horizontalAdvance(item)); - group_sidebar_width = qMax(state->group_sidebar_width, group_width + state->left_margin + state->right_margin); - target_sidebar_width = qMax(state->target_sidebar_width, target_width + state->left_margin + state->right_margin); - sidebar_width_dirty = false; +private: + static QString value(double number, int precision = 2) { + return QString::number(number, 'f', precision); } - void update_content_width(const QFontMetrics& fm, Performance_Shower_Render_State* state) { - int max_width = 0; - for (const QString& line : all_info) - max_width = qMax(max_width, fm.horizontalAdvance(line)); - content_width = max_width + state->left_margin + state->right_margin; - content_width_dirty = false; + int panel_height(const QFontMetrics& metrics, Performance_Shower_Render_State* state, int plot_height) const { + int content_height = metrics.lineSpacing() * static_cast(lines.size()) + state->top_margin + state->bottom_margin; + return qMin(content_height, qMax(0, plot_height)); } - void apply_event(const Performance_Event& event) { - switch (event.type) { - case Performance_Event_Type::Clear: - scopes.clear(); - all_info.clear(); - content_width = 0; - content_width_dirty = true; - break; - case Performance_Event_Type::Duration: { - Performance_Metric& metric = scopes[event.scope][event.key]; - metric.type = Performance_Metric_Type::Duration; - metric.value.update(event.number); - ++metric.count; - break; - } - case Performance_Event_Type::Counter: - scopes[event.scope][event.key].type = Performance_Metric_Type::Counter; - scopes[event.scope][event.key].speed.update(1); - ++scopes[event.scope][event.key].count; - break; - case Performance_Event_Type::Line: - scopes[event.scope][event.key].type = Performance_Metric_Type::Line; - scopes[event.scope][event.key].line = event.line; - break; - case Performance_Event_Type::Gauge: - scopes[event.scope][event.key].type = Performance_Metric_Type::Gauge; - scopes[event.scope][event.key].gauge = event.number; - ++scopes[event.scope][event.key].count; - break; - case Performance_Event_Type::State: - scopes[event.scope][event.key].type = Performance_Metric_Type::State; - scopes[event.scope][event.key].text = event.value; - break; - case Performance_Event_Type::Select_View: - selected_group = event.scope.isEmpty() ? "Plot" : event.scope; - selected_target = event.key; - scroll_line = 0; - content_width = 0; - content_width_dirty = true; - break; - case Performance_Event_Type::Scroll_View: - scroll_line += static_cast(event.number); - scroll_line = qBound(0, scroll_line, max_scroll_line); - break; - } + int visible_line_count(const QFontMetrics& metrics, Performance_Shower_Render_State* state, int height) const { + int text_height = qMax(0, height - state->top_margin - state->bottom_margin); + return qMax(1, text_height / qMax(1, metrics.lineSpacing())); } - static QString object_name_from_metric(const QString& key) { - int state_index = key.indexOf(".State."); - if (state_index > 0) - return key.left(state_index); - int input_index = key.indexOf(".Input."); - if (input_index > 0) - return key.left(input_index); - int pixel_index = key.indexOf(".Pixel."); - if (pixel_index > 0) - return key.left(pixel_index); - int prepare_index = key.indexOf(".prepare_data"); - if (prepare_index > 0) - return key.left(prepare_index); - int draw_index = key.indexOf(".draw"); - if (draw_index > 0) - return key.left(draw_index); - int lease_index = key.indexOf(".stateLeaseMiss"); - if (lease_index > 0) - return key.left(lease_index); - int mode_index = key.indexOf(".buffer_acquire_mode"); - if (mode_index > 0) - return key.left(mode_index); - int schedule_mode_index = key.indexOf(".render_schedule_mode"); - if (schedule_mode_index > 0) - return key.left(schedule_mode_index); - int schedule_max_fps_index = key.indexOf(".render_schedule_max_fps"); - if (schedule_max_fps_index > 0) - return key.left(schedule_max_fps_index); - return {}; + void update_scroll_bar_rects(Performance_Shower_Render_State* state, int max_scroll, int scroll, int visible_count) { + if (!max_scroll || panel_rect.isEmpty()) { + scroll_bar_rect = {}; + scroll_thumb_rect = {}; + return; + } + int track_height = qMax(0, panel_rect.height() - state->scroll_bar_margin * 2); + scroll_bar_rect = QRect(panel_rect.right() - state->scroll_bar_width - state->scroll_bar_margin + 1, state->scroll_bar_margin, state->scroll_bar_width, track_height); + int line_count = qMax(visible_count, static_cast(lines.size())); + int thumb_height = qMax(16, scroll_bar_rect.height() * visible_count / qMax(1, line_count)); + thumb_height = qMin(thumb_height, scroll_bar_rect.height()); + int travel = qMax(0, scroll_bar_rect.height() - thumb_height); + int thumb_y = scroll_bar_rect.y() + (max_scroll ? travel * scroll / max_scroll : 0); + scroll_thumb_rect = QRect(scroll_bar_rect.x(), thumb_y, scroll_bar_rect.width(), thumb_height); } - void rebuild_views() { - std::set object_items; - std::vector old_group_items = group_items; - std::vector old_target_items = target_items; - QString old_selected_target = selected_target; - auto renderable_scope = scopes.find("Renderable"); - if (renderable_scope != scopes.cend()) { - for (auto metric_it = renderable_scope->second.cbegin(); metric_it != renderable_scope->second.cend(); ++metric_it) { - QString object_name = object_name_from_metric(metric_it->first); - if (!object_name.isEmpty()) - object_items.insert(object_name); - } + int& stable_value_width(const QString& key) { + for (auto& field : stable_value_widths) { + if (field.first == key) + return field.second; } - auto buffer_scope = scopes.find("Buffer"); - if (buffer_scope != scopes.cend()) { - for (auto metric_it = buffer_scope->second.cbegin(); metric_it != buffer_scope->second.cend(); ++metric_it) { - if (!metric_it->first.startsWith("Color.")) { - QString object_name = object_name_from_metric(metric_it->first); - if (!object_name.isEmpty()) - object_items.insert(object_name); - } - } - } - group_items.clear(); - group_items.push_back("Plot"); - std::vector object_names(object_items.begin(), object_items.end()); - for (const QString& item : object_names) - group_items.push_back(item); - if (std::find(group_items.begin(), group_items.end(), selected_group) == group_items.end()) - selected_group = "Plot"; - if (selected_group == "Plot") { - target_items.clear(); - if (scopes.contains("Pipeline")) - target_items.push_back("Pipeline"); - if (scopes.contains("General")) - target_items.push_back("General"); - if (has_color_buffer_metrics()) - target_items.push_back("Color"); - } - else { - target_items.clear(); - if (has_renderable_metrics(selected_group)) - target_items.push_back("Renderable"); - if (has_object_buffer_metrics(selected_group, "State")) - target_items.push_back("State"); - if (has_object_buffer_metrics(selected_group, "Input")) - target_items.push_back("Input"); - if (has_object_buffer_metrics(selected_group, "Pixel")) - target_items.push_back("Pixel"); - } - if (target_items.empty()) - target_items.push_back("None"); - if (selected_target.isEmpty() || std::find(target_items.begin(), target_items.end(), selected_target) == target_items.end()) - selected_target = target_items.front(); - if (old_selected_target != selected_target) { - scroll_line = 0; - content_width = 0; - content_width_dirty = true; - } - if (old_group_items != group_items || old_target_items != target_items) - sidebar_width_dirty = true; + stable_value_widths.push_back({key, 0}); + return stable_value_widths.back().second; } - bool has_color_buffer_metrics() const { - auto buffer_scope = scopes.find("Buffer"); - if (buffer_scope == scopes.cend()) - return false; - for (auto metric_it = buffer_scope->second.cbegin(); metric_it != buffer_scope->second.cend(); ++metric_it) { - if (metric_it->first.startsWith("Color.")) - return true; - } - return false; + QString stable_text(const QString& key, QString text) { + int& width = stable_value_width(key); + width = qMax(width, text.size()); + return text.rightJustified(width, QLatin1Char(' ')); } - bool has_renderable_metrics(const QString& object_name) const { - auto renderable_scope = scopes.find("Renderable"); - if (renderable_scope == scopes.cend()) - return false; - for (auto metric_it = renderable_scope->second.cbegin(); metric_it != renderable_scope->second.cend(); ++metric_it) { - if (object_name_from_metric(metric_it->first) == object_name) - return true; - } - return false; + QString number_text(const QString& key, double number, int precision = 2) { + return stable_text(key, value(number, precision)); } - bool has_object_buffer_metrics(const QString& object_name, const QString& buffer_name) const { - auto buffer_scope = scopes.find("Buffer"); - if (buffer_scope == scopes.cend()) - return false; - QString prefix = object_name + "." + buffer_name + "."; - for (auto metric_it = buffer_scope->second.cbegin(); metric_it != buffer_scope->second.cend(); ++metric_it) { - if (metric_it->first.startsWith(prefix)) - return true; - } - return false; + QString count_text(const QString& key, std::uint64_t count) { + return stable_text(key, QString::number(count)); } - bool metric_visible(const QString& scope, const QString& key) const { - if (selected_group == "Plot") { - if (selected_target == "Pipeline") - return scope == "Pipeline"; - if (selected_target == "General") - return scope == "General"; - return selected_target == "Color" && scope == "Buffer" && key.startsWith("Color."); - } - if (selected_target == "Renderable") - return scope == "Renderable" && object_name_from_metric(key) == selected_group; - return scope == "Buffer" && key.startsWith(selected_group + "." + selected_target + "."); + QString duration_line(const QString& key, const QString& name, const Psc::Value_Statistics& statistics) { + return QString("%1 当前:%2ms 平滑:%3ms 波动:%4ms P95:%5ms") + .arg(name) + .arg(number_text(key + "_instant", statistics.instant)) + .arg(number_text(key + "_smooth", statistics.smooth)) + .arg(number_text(key + "_variation", statistics.variation)) + .arg(number_text(key + "_p95", statistics.p95)); } - QString display_metric_name(const QString& key) const { - if (selected_group == "Plot" && selected_target == "Color") - return key.mid(QString("Color.").size()); - if (selected_group != "Plot" && selected_target == "Renderable") - return key.mid(selected_group.size() + 1); - if (selected_group != "Plot") - return key.mid((selected_group + "." + selected_target + ".").size()); - return key; + QString interval_line(const QString& key, const QString& name, double srtt, double jitter, double fps) { + return QString("%1 间隔平滑:%2ms 波动:%3ms 实际:%4fps") + .arg(name) + .arg(number_text(key + "_srtt", srtt)) + .arg(number_text(key + "_jitter", jitter)) + .arg(number_text(key + "_fps", fps)); + } + static double percent(double value) { + return value * 100.0; + } + QString percent_text(const QString& key, double ratio) { + return number_text(key, percent(ratio)) + "%"; + } + static QString bottleneck_text(Bottleneck_State state) { + switch (state) { + case Bottleneck_State::Unknown: + return "未知"; + case Bottleneck_State::Input_Limited: + return "输入受限"; + case Bottleneck_State::Producer_Limited: + return "生产者受限"; + case Bottleneck_State::Consumer_Limited: + return "消费者受限"; + case Bottleneck_State::Manual_Limited: + return "人工帧率受限"; + case Bottleneck_State::Balanced: + return "均衡"; + } + return "未知"; + } + static QString outcome_text(Frame_Outcome outcome) { + switch (outcome) { + case Frame_Outcome::Produced: + return "已生产"; + case Frame_Outcome::Consumed: + return "已显示"; + case Frame_Outcome::Superseded: + return "被新帧替换"; + case Frame_Outcome::Cancelled: + return "已取消"; + case Frame_Outcome::Repeated: + return "重复旧帧"; + } + return "未知"; + } + QString manual_limit_text(const Frame_Feedback_State& feedback) { + if (feedback.manual_min_render_interval_ms <= 0.0) + return stable_text("manual_limit", "未设置"); + return QString("已设置 %1fps / %2ms") + .arg(number_text("manual_limit_fps", 1000.0 / feedback.manual_min_render_interval_ms)) + .arg(number_text("manual_limit_ms", feedback.manual_min_render_interval_ms)); + } + static double cache_total(const Renderable_Cache_Frame_Stat& stat) { + return static_cast(stat.hit_count + stat.stale_hit_count + stat.miss_count); + } + QString cache_ratio_text(const QString& key, std::uint64_t hit, std::uint64_t stale_hit, std::uint64_t miss) { + std::uint64_t total = hit + stale_hit + miss; + if (!total) + return stable_text(key, "0.00") + "%"; + return number_text(key, static_cast(hit + stale_hit) * 100.0 / static_cast(total)) + "%"; } void rebuild_lines() { - rebuild_views(); - std::vector lines; - for (auto scope_it = scopes.begin(); scope_it != scopes.end(); ++scope_it) { - for (auto metric_it = scope_it->second.begin(); metric_it != scope_it->second.end(); ++metric_it) { - if (metric_visible(scope_it->first, metric_it->first)) { - lines.push_back(metric_it->second.to_string(display_metric_name(metric_it->first))); - } - } - } - if (all_info != lines) { - all_info = lines; - content_width_dirty = true; - } - } -}; -class Performance_Scope_Timer { -public: - std::chrono::steady_clock::time_point start; - QString scope; - QString name; - Performance_Shower* performance_shower{}; - Performance_Scope_Timer(const QString& name, Performance_Shower* performance_shower_value) : Performance_Scope_Timer("General", name, performance_shower_value) {} - Performance_Scope_Timer(const QString& scope, const QString& name, Performance_Shower* performance_shower) { - if (!performance_shower || !performance_shower->enabled()) + if (!aggregate.has_frame) { + lines = {"等待帧统计"}; return; - start = std::chrono::steady_clock::now(); - this->scope = scope; - this->name = name; - this->performance_shower = performance_shower; + } + const Frame_Metadata& frame = aggregate.latest; + const Frame_Feedback_State& feedback = frame.feedback; + std::uint64_t aggregate_cache_total = aggregate.cache_hit_count + aggregate.cache_stale_hit_count + aggregate.cache_miss_count; + double exact_cache_ratio = aggregate_cache_total ? static_cast(aggregate.cache_hit_count) * 100.0 / static_cast(aggregate_cache_total) : 0.0; + double usable_cache_ratio = aggregate_cache_total ? static_cast(aggregate.cache_hit_count + aggregate.cache_stale_hit_count) * 100.0 / static_cast(aggregate_cache_total) : 0.0; + lines.clear(); + lines.push_back(QString("帧:%1 输入版本:%2 结果:%3 当前状态:%4") + .arg(count_text("frame_id", frame.frame_id)) + .arg(count_text("input_version", frame.input_version)) + .arg(outcome_text(frame.frame_outcome)) + .arg(bottleneck_text(feedback.bottleneck_state))); + lines.push_back(QString("人工限制:%1 负反馈限制:%2ms 实际最小间隔:%3ms") + .arg(manual_limit_text(feedback)) + .arg(number_text("adaptive_interval", feedback.adaptive_min_render_interval_ms)) + .arg(number_text("effective_interval", feedback.effective_min_render_interval_ms))); + lines.push_back("生产策略:低延迟优先"); + lines.push_back(QString("当前帧率 输入:%1fps 后台生产:%2fps 前端显示:%3fps") + .arg(number_text("input_rate_fps", feedback.input_rate_fps)) + .arg(number_text("production_rate_fps", feedback.production_rate_fps)) + .arg(number_text("display_rate_fps", feedback.display_rate_fps))); + lines.push_back(QString("最大处理能力 后台生产者:%1fps 前端消费者:%2fps") + .arg(number_text("producer_capacity_fps", feedback.producer_capacity_fps)) + .arg(number_text("consumer_capacity_fps", feedback.consumer_capacity_fps))); + lines.push_back(interval_line("input_interval", "前端输入给后台", feedback.input_interval_srtt_ms, feedback.input_interval_jitter_ms, feedback.input_rate_fps)); + lines.push_back(interval_line("render_interval", "后台生产输出", feedback.render_interval_srtt_ms, feedback.render_interval_jitter_ms, feedback.production_rate_fps)); + lines.push_back(interval_line("consume_interval", "前端消费显示", feedback.consume_interval_srtt_ms, feedback.consume_interval_jitter_ms, feedback.display_rate_fps)); + lines.push_back(QString("流水线占用 生产者忙:%1 Ready占用:%2 消费者忙:%3 输入等待:%4") + .arg(percent_text("producer_busy_ratio", feedback.producer_busy_ratio)) + .arg(percent_text("ready_frame_occupancy_ratio", feedback.ready_frame_occupancy_ratio)) + .arg(percent_text("consumer_busy_ratio", feedback.consumer_busy_ratio)) + .arg(percent_text("input_pending_ratio", feedback.input_pending_ratio))); + lines.push_back(QString("反馈质量 被替换:%1 前端空等:%2 唯一帧:%3 显示效率:%4") + .arg(percent_text("superseded_ready_ratio", feedback.superseded_ready_ratio)) + .arg(percent_text("consumer_underrun_ratio", feedback.consumer_underrun_ratio)) + .arg(percent_text("unique_consumed_ratio", feedback.unique_consumed_ratio)) + .arg(percent_text("display_efficiency", feedback.display_efficiency))); + lines.push_back(QString("帧计数 已生产:%1 已显示:%2 被替换:%3 重复:%4") + .arg(count_text("produced_frame_count", feedback.produced_frame_count)) + .arg(count_text("consumed_frame_count", feedback.consumed_frame_count)) + .arg(count_text("superseded_frame_count", feedback.superseded_frame_count)) + .arg(count_text("repeated_frame_count", feedback.repeated_frame_count))); + lines.push_back(QString("版本 最新输入:%1 渲染中:%2 已发布:%3 已显示:%4") + .arg(count_text("latest_input_version", feedback.latest_input_version)) + .arg(count_text("rendering_input_version", feedback.rendering_input_version)) + .arg(count_text("published_input_version", feedback.published_input_version)) + .arg(count_text("consumed_input_version", feedback.consumed_input_version))); + lines.push_back(duration_line("request_wait", "生命周期 数据请求到排队", aggregate.request_wait_ms)); + lines.push_back(duration_line("render_queue_wait", "生命周期 后台队列等待", aggregate.render_queue_wait_ms)); + lines.push_back(duration_line("prepare", "生命周期 准备渲染数据", aggregate.prepare_ms)); + lines.push_back(duration_line("draw", "生命周期 后台绘制", aggregate.draw_ms)); + lines.push_back(duration_line("render", "生命周期 后台生产总耗时", aggregate.render_ms)); + lines.push_back(duration_line("ready_wait", "生命周期 Ready到Paint", aggregate.ready_wait_ms)); + lines.push_back(duration_line("update_wait", "生命周期 Update排队", aggregate.update_wait_ms)); + lines.push_back(duration_line("paint", "生命周期 前端Paint", aggregate.paint_ms)); + lines.push_back(duration_line("frame_age", "生命周期 输入到显示年龄", aggregate.frame_age_ms)); + lines.push_back(duration_line("frame_total", "生命周期 端到端总耗时", aggregate.frame_total_ms)); + lines.push_back(QString("总缓存 命中:%1 过期可用:%2 未命中:%3 精确命中:%4% 可用命中:%5%") + .arg(count_text("cache_hit_count", aggregate.cache_hit_count)) + .arg(count_text("cache_stale_hit_count", aggregate.cache_stale_hit_count)) + .arg(count_text("cache_miss_count", aggregate.cache_miss_count)) + .arg(number_text("exact_cache_ratio", exact_cache_ratio)) + .arg(number_text("usable_cache_ratio", usable_cache_ratio))); + for (const auto& stat : aggregate.renderable_cache_stats) { + if (cache_total(stat) <= 0.0) + continue; + QString cache_key = stat.object_name; + lines.push_back(QString("元素缓存 %1 命中:%2 过期可用:%3 未命中:%4 可用率:%5") + .arg(stat.object_name.isEmpty() ? "未命名元素" : stat.object_name) + .arg(count_text(cache_key + "_cache_hit", stat.hit_count)) + .arg(count_text(cache_key + "_cache_stale_hit", stat.stale_hit_count)) + .arg(count_text(cache_key + "_cache_miss", stat.miss_count)) + .arg(cache_ratio_text(cache_key + "_cache_ratio", stat.hit_count, stat.stale_hit_count, stat.miss_count))); + } + lines.push_back(QString("工作量 原始输入:%1 可见输入:%2 准备输出:%3 点:%4 线段:%5") + .arg(count_text("raw_input_count", frame.raw_input_count)) + .arg(count_text("visible_input_count", frame.visible_input_count)) + .arg(count_text("prepared_output_count", frame.prepared_output_count)) + .arg(count_text("draw_point_count", frame.draw_point_count)) + .arg(count_text("draw_segment_count", frame.draw_segment_count))); + lines.push_back(QString("Renderable 总数:%1 可见:%2 直接绘制:%3 本地缓存:%4") + .arg(count_text("renderable_count", frame.renderable_count)) + .arg(count_text("visible_renderable_count", frame.visible_renderable_count)) + .arg(count_text("direct_draw_count", frame.direct_draw_count)) + .arg(count_text("local_cache_count", frame.local_cache_count))); + lines.push_back(QString("观测帧:%1 返回已显示:%2 返回被替换:%3 取消:%4 输入环丢弃:%5 浪费渲染:%6ms") + .arg(count_text("observed_frame_count", aggregate.observed_frame_count)) + .arg(count_text("consumed_outcome_count", aggregate.consumed_outcome_count)) + .arg(count_text("superseded_outcome_count", aggregate.superseded_outcome_count)) + .arg(count_text("cancelled_outcome_count", aggregate.cancelled_outcome_count)) + .arg(count_text("input_capacity_dropped", aggregate.input_capacity_dropped)) + .arg(number_text("wasted_render_time", feedback.wasted_render_time_ms))); } - ~Performance_Scope_Timer() { - if (!performance_shower || !performance_shower->enabled()) - return; - double use_time = std::chrono::duration(std::chrono::steady_clock::now() - start).count() * 1000; - performance_shower->record_duration(scope, name, use_time); + void update_content_width(const QFontMetrics& metrics, Performance_Shower_Render_State* state) { + int max_width = 0; + for (const QString& line : lines) + max_width = qMax(max_width, metrics.horizontalAdvance(line)); + int measured_width = max_width + state->left_margin + state->right_margin + state->scroll_bar_width + state->scroll_bar_margin * 2; + held_content_width = qMax(held_content_width, measured_width); + content_width = held_content_width; } }; } // namespace renderive diff --git a/Renderive/plottable/Spectrum.cpp b/Renderive/plottable/Spectrum.cpp index 2db1a38..b5efcde 100644 --- a/Renderive/plottable/Spectrum.cpp +++ b/Renderive/plottable/Spectrum.cpp @@ -225,7 +225,6 @@ std::shared_ptr Spectrum::Builder::build() { sc->frequency_range = {0.0, 1.0}; if (sc->sweep_frequency_range.length() == 0.0) sc->sweep_frequency_range = {0.0, 1.0}; - ret->set_child_render_order(Renderable_Child_Render_Order::Self_Before_Children); auto frequency_axis = this->frequency_axis.lock(); auto power_axis = this->power_axis.lock(); if (frequency_axis && power_axis) { @@ -400,12 +399,8 @@ void Spectrum_Private::draw_spectrum(QPainter* painter, Spectrum_Render_State* s s->draw_axis_rect(painter, s->sweep_frequency_range, s->middle_sweep_frequency); } } -void Spectrum_Private::draw(QPainter* painter, const Plot_Render_Snapshot& snapshot) { +void Spectrum_Private::draw(QPainter* painter, const Plot_Render_Snapshot&) { auto s = render_state(); - Performance_Shower* shower = snapshot.performance_shower.get(); - if (shower) { - shower->set_performance_line("frequency_point_size", Performance_Line(QString("frequency_point_size:%1").arg(s->frequency_point_size))); - } draw_power_curve(painter, s, cur_powers, s->cur_pen, s->cur_brush); draw_spectrum(painter, s); if (s->use_max_line) diff --git a/Renderive/plottable/Sweep_Frequency_p.h b/Renderive/plottable/Sweep_Frequency_p.h index 003bb4b..74bc4f6 100644 --- a/Renderive/plottable/Sweep_Frequency_p.h +++ b/Renderive/plottable/Sweep_Frequency_p.h @@ -5,7 +5,6 @@ #include "../base/Frame_Memory.h" #include "../primitive/Curve.h" #include "Interpolation_p.h" -#include "Performance_Shower_p.h" #include "Sweep_Frequency.h" namespace renderive { struct Sweep_Frequency_Render_State : renderive::Render_State, Sweep_Frequency_Prop {}; @@ -105,12 +104,9 @@ struct Sweep_Frequency_Private : renderive::Typed_Render_Data data(frame_memory_resource()); - { - Performance_Scope_Timer prepare_data("轴映射数据耗时:%1 SRTT:%2 RTTVAR:%3", snapshot.performance_shower.get()); - data = Curve_Utils::create_sampled_points(frequency_axis.get(), power_axis.get(), s->frequency_range, n, available_count, s->visible_range_only, s->interpolation_mode, [this](int i) { - return frequency_list.at(i).y(); - }); - } + data = Curve_Utils::create_sampled_points(frequency_axis.get(), power_axis.get(), s->frequency_range, n, available_count, s->visible_range_only, s->interpolation_mode, [this](int i) { + return frequency_list.at(i).y(); + }); Curve_Utils::draw_polyline(painter, data, s->pen); painter->setPen(s->cur_frequency_pen); double f = block_num > 1 ? offset * frequency_range.length() / (block_num - 1.0) : frequency_range.origin; diff --git a/Renderive/plottable/Waterfall_p.h b/Renderive/plottable/Waterfall_p.h index e90f58e..f033f75 100644 --- a/Renderive/plottable/Waterfall_p.h +++ b/Renderive/plottable/Waterfall_p.h @@ -5,11 +5,9 @@ #include #include #include -#include #include #include #include -#include #include #include #include "../Axis/Frequency_Axis_p.h" @@ -22,23 +20,11 @@ #include "../primitive/Raster_Image.h" #include "Core/Base/RingBuffer.hpp" #include "Interpolation_p.h" -#include "Performance_Shower_p.h" #include "Waterfall.h" namespace renderive { inline std::uint64_t waterfall_now_ns() { return static_cast(std::chrono::duration_cast(std::chrono::steady_clock::now().time_since_epoch()).count()); } -inline const char* waterfall_update_mode_text(Waterfall_Update_Mode mode) { - switch (mode) { - case Waterfall_Update_Mode::Batch: - return "Batch"; - case Waterfall_Update_Mode::Single_Line: - return "Single_Line"; - case Waterfall_Update_Mode::Timed_Latest: - return "Timed_Latest"; - } - return "Batch"; -} struct Waterfall_Data { int tick; std::pmr::vector frequency{memory_resource(Memory_Domain::Waterfall)}; @@ -239,26 +225,19 @@ struct Waterfall_Private : Typed_Render_Data(data->get_pending_count()); - int committed_rows = 0; - int invalid_rows = 0; int policy_dropped_rows = 0; if (s->update_mode == Waterfall_Update_Mode::Batch) { - data->read_all([this, data, s, col_count, &committed_rows, &invalid_rows](int tick, const auto& frequency_data) { - if (push_row(data, frequency_data, tick, col_count)) - ++committed_rows; - else - ++invalid_rows; + data->read_all([this, data, col_count](int tick, const auto& frequency_data) { + push_row(data, frequency_data, tick, col_count); }); } else { Waterfall_Data latest{}; bool has_latest = false; int valid_rows = 0; - data->read_all([this, data, col_count, &latest, &has_latest, &valid_rows, &invalid_rows](int tick, std::pmr::vector& frequency_data) { + data->read_all([data, col_count, &latest, &has_latest, &valid_rows](int tick, std::pmr::vector& frequency_data) { if (frequency_data.size() != col_count) { data->data_list.record_consumer_dropped(); - ++invalid_rows; return; } latest.tick = tick; @@ -275,7 +254,6 @@ struct Waterfall_Private : Typed_Render_Dataupdate_mode)) { - ++committed_rows; last_line_commit_ns = now_ns; } policy_dropped_rows = valid_rows - 1; @@ -287,19 +265,6 @@ struct Waterfall_Private : Typed_Render_Dataset_state("Waterfall", "update_mode", waterfall_update_mode_text(s->update_mode)); - shower->set_gauge("Waterfall", "update_interval_ms", static_cast(qMax(1, s->update_interval_ms))); - shower->set_gauge("Waterfall", "input_rows", static_cast(input_rows)); - shower->set_gauge("Waterfall", "committed_rows", static_cast(committed_rows)); - shower->set_gauge("Waterfall", "accepted_rows", static_cast(committed_rows)); - shower->set_gauge("Waterfall", "invalid_rows", static_cast(invalid_rows)); - shower->set_gauge("Waterfall", "policy_dropped_rows", static_cast(policy_dropped_rows)); - shower->set_gauge("Waterfall", "dropped_rows", static_cast(policy_dropped_rows + invalid_rows)); - shower->set_gauge("Waterfall", "ring_copy_bytes", static_cast(committed_rows) * static_cast(col_count * sizeof(double))); - shower->set_gauge("Waterfall", "pending_image_rows", static_cast(pending_image_rows.size())); - } update_image_item(snapshot); } bool write_image_row(int tick, const double* row_data, int first_col, int last_col, int start_tick, int row_count, double value_start_coord, double rate, const Color_Map& color_map, const QColor& background_color) { @@ -318,10 +283,6 @@ struct Waterfall_Private : Typed_Render_Datatime_axis.lock(); if (!frequency_axis || !time_axis) return; - Performance_Shower* shower = snapshot.performance_shower.get(); - if (shower) { - shower->set_performance_line("frequency_point_size", Performance_Line(QString("frequency_point_size:%1 time_point_size: %2").arg(s->frequency_point_size).arg(time_axis->time_point_size()))); - } Abs_Axis* h_axis = frequency_axis.get(); Time_Ticker& maker = time_axis->d()->time_ticker; Range h_range = s->frequency_range; @@ -337,12 +298,8 @@ struct Waterfall_Private : Typed_Render_Datafrequency_range.length() == 0.0 || s->power_range.size() == 0.0) return; bool image_size_changed = image.width() != col_count || image.height() != row_count; - if (image_size_changed) { + if (image_size_changed) image.resize(col_count, row_count); - if (shower) { - shower->set_performance_line("col_count, row_count", Performance_Line(QString("col_count:%1, row_count:%2").arg(col_count).arg(row_count))); - } - } double source_start = static_cast(col_count) * (h_range.origin - s->frequency_range.origin) / s->frequency_range.length(); double source_end = static_cast(col_count) * (h_range.target - s->frequency_range.origin) / s->frequency_range.length(); double source_min = std::min(source_start, source_end); @@ -352,35 +309,23 @@ struct Waterfall_Private : Typed_Render_Datapower_range.origin; double rate = static_cast(s->color_map.size()) / s->power_range.size(); int buffer_data_count = ring_buffer.snapshot(); - int visible_col_count = last_col - first_col + 1; int start_tick = maker.lower; std::uint64_t color_map_hash = s->color_map.hash(); bool full_rebuild = s->update_mode == Waterfall_Update_Mode::Batch || image_size_changed || !image_cache_match(s, start_tick, first_col, last_col, h_range, snapshot.background_color, color_map_hash); - int scan_rows = 0; - int draw_rows = 0; - std::uint64_t color_map_start_ns = waterfall_now_ns(); - { - Performance_Scope_Timer c("Waterfall set_time:%1 SRTT:%2 RTTVAR:%3", snapshot.performance_shower.get()); - if (full_rebuild) { - image.fill(snapshot.background_color); - scan_rows = buffer_data_count; - for (int row = 0; row < buffer_data_count; ++row) { - int buffer_row = buffer_data_count - row - 1; - auto list = ring_buffer.frequency_data(buffer_row, first_col, last_col); - int tick = ring_buffer.tick(buffer_row); - if (write_image_row(tick, list, first_col, last_col, start_tick, row_count, value_start_coord, rate, s->color_map, snapshot.background_color)) - ++draw_rows; - } - } - else { - scan_rows = static_cast(pending_image_rows.size()); - for (const Waterfall_Data& row : pending_image_rows) { - if (write_image_row(row.tick, row.frequency.data(), first_col, last_col, start_tick, row_count, value_start_coord, rate, s->color_map, snapshot.background_color)) - ++draw_rows; - } + if (full_rebuild) { + image.fill(snapshot.background_color); + for (int row = 0; row < buffer_data_count; ++row) { + int buffer_row = buffer_data_count - row - 1; + auto list = ring_buffer.frequency_data(buffer_row, first_col, last_col); + int tick = ring_buffer.tick(buffer_row); + write_image_row(tick, list, first_col, last_col, start_tick, row_count, value_start_coord, rate, s->color_map, snapshot.background_color); + } + } + else { + for (const Waterfall_Data& row : pending_image_rows) { + write_image_row(row.tick, row.frequency.data(), first_col, last_col, start_tick, row_count, value_start_coord, rate, s->color_map, snapshot.background_color); } } - std::uint64_t color_map_ns = waterfall_now_ns() - color_map_start_ns; pending_image_rows.clear(); image_valid = true; image_start_tick = start_tick; @@ -392,14 +337,6 @@ struct Waterfall_Private : Typed_Render_Dataupdate_mode; image_color_map_hash = color_map_hash; - if (shower) { - shower->set_gauge("Waterfall", "scan_rows", static_cast(scan_rows)); - shower->set_gauge("Waterfall", "draw_rows", static_cast(draw_rows)); - shower->set_gauge("Waterfall", "visible_cols", static_cast(visible_col_count)); - shower->set_gauge("Waterfall", "scan_pixels", static_cast(scan_rows) * static_cast(visible_col_count)); - shower->set_gauge("Waterfall", "full_rebuild", full_rebuild ? 1.0 : 0.0); - shower->set_gauge("Waterfall", "color_map_ns", static_cast(color_map_ns)); - } QRectF source_rect(source_start, 0.0, source_end - source_start, static_cast(row_count)); image_draw_frequency_range = h_range; image_draw_time_range = v_range; diff --git a/Renderive_实时渲染调度与三帧反馈设计.md b/Renderive_实时渲染调度与三帧反馈设计.md new file mode 100644 index 0000000..b33dca3 --- /dev/null +++ b/Renderive_实时渲染调度与三帧反馈设计.md @@ -0,0 +1,1523 @@ +# Renderive 实时渲染调度与三帧反馈设计 + +## 1. 文档目标 + +本文定义 Renderive 实时图表的生产、发布、消费、反馈和限流模型。 + +该设计面向以下场景: + +- 核心数据持续更新; +- 后台线程生成完整图像帧; +- Qt 主线程通过 `paintEvent()` 消费帧; +- 系统采用三个可循环复用的帧槽位; +- 默认追求低延迟和最新状态; +- 不要求每个输入版本都被渲染; +- 不要求每个已生成帧都被显示; +- 需要根据输入、生产和消费能力自动调整生产频率; +- 反馈路径不得引入热路径锁竞争(反馈数据直接记录在帧槽位自身的元信息中,并随帧槽位所有权无锁返回); + +默认语义为: + +```text +Data_Driven 数据驱动 +Latest_Wins 最新数据优先 +Triple_Frame 三帧机制 +Bounded_In_Flight 有界在途帧 +Max_Render_Rate 最大渲染频率限制 +Lock_Free_Frame_Feedback 无锁帧反馈 +``` + +## 2. 核心目标 + +系统需要满足以下目标: + +1. 核心数据变化后尽快生成新帧; +2. 数据输入、后台生产和前端消费可以并行; +3. 输入过快时合并中间版本; +4. 生产者过慢时不积压渲染任务; +5. 消费者过慢时不积压历史帧; +6. 用户可以设置最大生产速率; +7. 生产速率根据消费反馈自动调节; +8. 生产能力不足时由完成事件自时钟运行; +9. 所有阶段的耗时和排队信息随帧流转; +10. 帧返回生产侧时完成统计采集; +11. 热路径不创建字符串、动态事件对象或无界队列; +12. 控制器避免瞬时样本导致频繁振荡。 + +## 3. 基本原则 + +### 3.1 Latest_Wins(最新数据优先) + +系统只关心当前最新有效状态。 + +当后台正在渲染版本 100,而输入端连续产生: + +```text +101 +102 +103 +104 +``` + +下一帧直接选择版本 104。 + +未开始渲染的 101、102、103 被合并,不进入待执行渲染任务队列。 + +### 3.2 Bounded_In_Flight(有界在途帧) + +系统只允许三个帧槽位参与流水线: + +```text +Triple_Frame 三帧机制 +├── Frame_Slot_0 第一帧槽位 +├── Frame_Slot_1 第二帧槽位 +└── Frame_Slot_2 第三帧槽位 +``` + +三个帧槽位结构完全相同,通过 `Frame_State` 动态表示空闲、生产、就绪和消费状态,不绑定固定物理角色。 + +同一时刻最多存在: + +```text +一个 Producing 状态帧槽位 生产端正在写入 +一个 Ready 状态帧槽位 等待消费者取得 +一个 Consuming 状态帧槽位 消费者正在读取 +``` + +不建立大容量历史帧 FIFO。 + +### 3.3 Event_Driven(事件驱动) + +调度器不依赖持续轮询。 + +核心事件包括: + +```text +Input_Changed 输入数据已更新 +Render_Completed 渲染生产已完成 +Frame_Consumed 帧已消费 +Rate_Limit_Deadline_Reached 频率限制截止时间已到达 +Control_Updated 控制参数已更新 +``` + +### 3.4 State_Decides_Now,Statistics_Decide_Trend(状态决定当前,统计决定趋势) + +即时行为由当前状态决定: + +```text +是否有未渲染的新数据 +是否正在渲染 +是否存在 Ready 状态帧槽位 +消费者是否正在使用帧 +是否达到最大生产速率允许时间 +当前策略是否允许替代旧的 Ready 状态帧 +``` + +长期调节由平滑统计决定: + +```text +输入间隔 +生产耗时 +消费间隔 +GUI 排队时间 +Ready 状态帧等待时间 +帧年龄 +覆盖率 +断供率 +``` + +## 4. 命名约定 + +所有状态、事件、策略和字段使用下划线命名。 + +### 4.2 瓶颈状态 + +```text +Bottleneck_State 瓶颈状态 +├── Unknown 未知 +├── Input_Limited 输入受限 +├── Producer_Limited 生产端受限 +├── Consumer_Limited 消费端受限 +├── Manual_Limited 手动上限受限 +└── Balanced 均衡状态 +``` + +### 4.3 调度事件 + +```text +Scheduler_Event 调度事件 +├── Input_Changed 输入数据已更新 +├── Render_Completed 渲染生产已完成 +├── Frame_Consumed 帧已消费 +├── Rate_Limit_Deadline_Reached 频率限制截止时间已到达 +└── Control_Updated 控制参数已更新 +``` + +### 4.4 三帧槽位 + +三帧机制不是三个具有固定角色的缓冲区,而是三个结构完全相同、可循环复用的帧槽位: + +```text +Triple_Frame 三帧机制 +├── Frame_Slot_0 第一帧槽位 +├── Frame_Slot_1 第二帧槽位 +└── Frame_Slot_2 第三帧槽位 +``` + +每个帧槽位都是一份完整帧记录: + +```text +Frame_Slot 帧槽位 +├── Frame_Data 帧数据 +├── Frame_Metadata 帧元信息 +└── Frame_State 帧生命周期状态 +``` + +其中: + +- `Frame_Data`:当前帧实际使用的数据,例如渲染结果、绘制命令、顶点数据、索引数据、实例数据或场景快照; +- `Frame_Metadata`:该帧自身携带的版本、时间、耗时、工作量和反馈信息; +- `Frame_State`:该帧槽位当前所处的生命周期状态,并表示当前所有权阶段。 + +帧槽位状态为: + +```text +Frame_State 帧生命周期状态 +├── Free 空闲 +├── Producing 生产中 +├── Ready 已就绪 +└── Consuming 消费中 +``` + +状态流转为: + +```text +Free 空闲 + ↓ +Producing 生产中 + ↓ +Ready 已就绪 + ↓ +Consuming 消费中 + ↓ +Free 空闲 +``` + +`Returned_Frame` 可以描述消费者刚完成并归还的帧: + +```text +Returned_Frame 已归还帧 +``` + +`Returned_Frame` 只是所有权交接过程中的瞬时逻辑角色,不是第四个物理帧槽位,也不必成为持久的 `Frame_State`。 + +## 5. 系统角色 + +### 5.1 数据输入端 + +数据输入端负责接收所有会改变最终图像的状态: + +```text +核心数据 +坐标范围 +图表尺寸 +样式属性 +布局状态 +交互状态 +可见性状态 +其他渲染依赖 +``` + +每次有效变化更新: + +```text +latest_input_version +``` + +数据输入端只保存当前最新状态,不为每个版本创建独立渲染任务。 + +需要区分: + +```text +原始提交次数 +有效核心数据变化次数 +``` + +只有会改变最终图像的有效变化才参与输入速率统计。 + +### 5.2 生产端 + +生产端负责一张帧的完整生成过程: + +```text +收集一致快照 +→ 准备绘制数据 +→ 执行颜色缓冲绘制 +→ 填写生产阶段元数据 +→ 发布完整帧 +``` + +同一时间只允许一个完整帧生产任务运行。 + +生产完成后发出: + +```text +Render_Completed 渲染生产已完成 +``` + +生产端不直接决定是否继续生产下一帧。 + +### 5.3 消费端 + +消费端对应 Qt 主线程中的 `paintEvent()`。 + +消费端负责: + +```text +取得最新完整帧 +记录实际消费版本 +记录 paintEvent 开始时间 +执行图像显示 +记录 paintEvent 结束时间 +释放当前 `Consuming` 状态帧槽位 +``` + +消费端只上报事实,不直接控制后台生产线程。 + +`paintEvent()` 结束后不会无条件再次调用 `update()`。 + +### 5.4 Plot 调度器 + +Plot 调度器是唯一的全局决策者。 + +核心状态包括: + +```text +latest_input_version +rendering_input_version +published_input_version +consumed_input_version + +render_busy +ready_valid +consumer_busy +update_pending + +next_render_time +manual_min_render_interval +adaptive_min_render_interval + +production_policy +bottleneck_state +``` + +调度器负责决定: + +```text +立即开始生产 +等待最大速率期限 +等待消费者释放 +请求一次 QWidget::update() +保持等待并继续合并输入 +``` + +## 6. 三帧生命周期 + +三个帧槽位结构完全相同,不预先绑定生产端、就绪区或消费端。 + +每个帧槽位都按照以下状态循环: + +```text +Frame_State 帧生命周期状态 +├── Free 空闲,可由生产端取得 +├── Producing 生产中,由生产端独占写入 +├── Ready 已就绪,等待消费者取得 +└── Consuming 消费中,由消费者独占读取 +``` + +正常状态流转为: + +```text +Free 空闲 +→ Producing 生产中 +→ Ready 已就绪 +→ Consuming 消费中 +→ Free 空闲 +``` + +任意时刻,三个帧槽位可以分别处于不同状态,例如: + +```text +Frame_Slot_0 Consuming 正在显示 Frame_N +Frame_Slot_1 Ready 保存 Frame_N_Plus_1 +Frame_Slot_2 Producing 正在生成 Frame_N_Plus_2 +``` + +下一时刻,各槽位可以继续交换状态: + +```text +Frame_Slot_0 Free 已完成消费并归还 +Frame_Slot_1 Consuming 消费者正在显示 +Frame_Slot_2 Ready 新帧已经发布 +``` + +三帧机制不保存长历史帧序列。 + +处于 `Ready` 状态的帧槽位表示当前最新可消费帧,不表示 FIFO 中下一张必须显示的历史帧。 + +生产、就绪和消费是帧槽位的动态生命周期状态,不是三个物理帧槽位的固定身份。 + +## 7. Frame_Metadata(帧元信息) + +每个帧槽位携带固定大小的 `Frame_Metadata`。 + +```text +Frame_Metadata 帧元信息 +├── frame_id 帧唯一标识 +├── input_version 核心输入数据版本 +├── snapshot_version 完整状态快照版本 +├── frame_slot_generation 帧槽位复用代次 +├── core_data_time 核心数据时间 +├── source_data_time_begin 最早有效源数据时间 +├── source_data_time_end 最新有效源数据时间 +├── snapshot_capture_begin_time 快照采集开始时间 +├── snapshot_capture_end_time 快照采集结束时间 +├── render_request_time 调度器批准生产时间 +├── render_queue_enter_time 渲染任务进入后台队列时间 +├── render_begin_time 后台线程开始执行时间 +├── prepare_begin_time 绘制数据准备开始时间 +├── prepare_end_time 绘制数据准备结束时间 +├── draw_begin_time 最终绘制开始时间 +├── draw_end_time 最终绘制结束时间 +├── render_end_time 完整帧生产结束时间 +├── publish_time 帧发布为 Ready 状态的时间 +├── update_request_id GUI 更新请求标识 +├── update_request_time GUI 更新请求入队时间 +├── paint_begin_time paintEvent 开始时间 +├── paint_end_time paintEvent 结束时间 +├── consumer_release_time 消费者释放帧槽位时间 +├── superseded_time 帧被更新帧替代的时间 +├── frame_outcome 帧最终结果 +├── raw_input_count 原始输入数量 +├── visible_input_count 可见输入数量 +├── prepared_output_count 准备后的输出数量 +├── draw_point_count 绘制点数量 +├── draw_segment_count 绘制线段数量 +├── pixel_width 帧像素宽度 +├── pixel_height 帧像素高度 +└── renderable_count 可渲染对象数量 +``` + +`Frame_Metadata` 与帧数据一起经历: + +```text +生产 +→ 发布 +→ 消费 +→ 返回 +→ 聚合统计 +→ 清理 +→ 下一代复用 +``` + +热路径中不通过独立动态事件对象传递帧级统计。 + +反馈属于具体帧,并随帧槽位所有权一起返回生产侧。 + +## 8. 帧身份信息 + +每个帧携带: + +```text +frame_id 帧唯一标识 +input_version 核心输入数据版本 +snapshot_version 完整状态快照版本 +frame_slot_generation 帧槽位复用代次 +``` + +### frame_id(帧唯一标识) + +标识一次实际完成的帧生产。 + +### input_version(输入版本) + +标识该帧使用的核心输入数据版本。 + +### snapshot_version(快照版本) + +标识该帧使用的完整状态快照版本。 + +### frame_slot_generation(帧槽位复用代次) + +标识同一个物理帧槽位的复用代次,避免旧反馈与新帧混淆。 + +## 9. 数据时间信息 + +每个帧携带: + +```text +core_data_time +source_data_time_begin +source_data_time_end +snapshot_capture_begin_time +snapshot_capture_end_time +``` + +含义: + +```text +core_data_time +触发该帧生产的核心数据时间 + +source_data_time_begin +该帧使用的最早有效数据时间 + +source_data_time_end +该帧使用的最新有效数据时间 + +snapshot_capture_begin_time +开始收集一致状态快照的时间 + +snapshot_capture_end_time +完成一致状态快照收集的时间 +``` + +可派生: + +```text +snapshot_capture_duration +data_age_at_snapshot +``` + +## 10. 生产阶段时间信息 + +每个帧携带: + +```text +render_request_time +render_queue_enter_time +render_begin_time +prepare_begin_time +prepare_end_time +draw_begin_time +draw_end_time +render_end_time +publish_time +``` + +含义: + +```text +render_request_time +调度器批准生产该帧的时间 + +render_queue_enter_time +任务进入后台任务队列的时间 + +render_begin_time +后台工作线程实际开始执行的时间 + +prepare_begin_time +开始准备绘制数据的时间 + +prepare_end_time +完成绘制数据准备的时间 + +draw_begin_time +开始执行最终颜色缓冲绘制的时间 + +draw_end_time +完成最终颜色缓冲绘制的时间 + +render_end_time +完整帧生产任务结束的时间 + +publish_time +帧槽位从 `Producing` 状态发布为 `Ready` 状态的时间 +``` + +可派生: + +```text +render_queue_wait += render_begin_time - render_queue_enter_time + +snapshot_to_render_wait += render_begin_time - snapshot_capture_end_time + +prepare_duration += prepare_end_time - prepare_begin_time + +draw_duration += draw_end_time - draw_begin_time + +render_duration += render_end_time - render_begin_time + +production_lifecycle_duration += publish_time - render_request_time +``` + +## 11. 发布和 GUI 排队时间信息 + +每个帧携带: + +```text +update_request_id +update_request_time +paint_begin_time +``` + +含义: + +```text +update_request_id +标识实际对应当前 paintEvent 的待处理 update 请求 + +update_request_time +该 update 请求进入 GUI 事件队列的时间 + +paint_begin_time +消费者实际开始执行 paintEvent 的时间 +``` + +可派生: + +```text +publish_to_paint_wait += paint_begin_time - publish_time + +update_queue_wait += paint_begin_time - update_request_time + +ready_frame_wait += paint_begin_time - publish_time +``` + +当多个帧在同一个待处理 `update()` 期间发生替换时,最终被消费帧关联到实际触发本次 `paintEvent()` 的 `update_request_id` 和 +`update_request_time`。 + +## 12. 消费阶段时间信息 + +消费者填写: + +```text +paint_begin_time +paint_end_time +consumer_release_time +``` + +含义: + +```text +paint_begin_time +开始读取和绘制该帧的时间 + +paint_end_time +完成该帧显示的时间 + +consumer_release_time +消费者完成全部访问并释放当前 `Consuming` 状态帧槽位的时间 +``` + +可派生: + +```text +paint_duration += paint_end_time - paint_begin_time + +consumer_hold_duration += consumer_release_time - paint_begin_time + +data_to_paint_begin += paint_begin_time - core_data_time + +data_to_paint_end += paint_end_time - core_data_time + +frame_total_lifecycle += consumer_release_time - render_request_time +``` + +## 13. 帧结果信息 + +每个帧最终记录: + +```text +Frame_Outcome 帧结果 +├── Produced 已生产 +├── Consumed 已消费 +├── Superseded 已被更新帧替代 +├── Cancelled 已取消 +└── Repeated 重复显示上一帧 +``` + +### Produced(已生产) + +帧已经生产完成并发布。 + +### Consumed(已消费) + +帧被消费者实际取得并完成显示。 + +### Superseded(已被更新帧替代) + +帧已经生产完成,但在被消费者取得之前被更新帧替代。 + +### Cancelled(已取消) + +帧在完成前因 Plot 销毁、尺寸失效或明确终止条件被取消。 + +### Repeated(重复显示上一帧) + +消费者没有取得新帧,继续显示上一张已经消费的图像。 + +实时模式重点统计: + +```text +Consumed 已消费 +Superseded 已被更新帧替代 +Repeated 重复显示上一帧 +``` + +## 14. 帧工作量信息 + +每个帧携带: + +```text +raw_input_count +visible_input_count +prepared_output_count +draw_point_count +draw_segment_count +pixel_width +pixel_height +renderable_count +``` + +用于关联性能变化与实际工作量: + +```text +输入规模变化 +抽点结果变化 +可见范围变化 +图表尺寸变化 +Renderable 数量变化 +``` + +## 15. 无锁元数据写入规则 + +帧元信息采用阶段所有权写入。 + +### 15.1 Producing(生产阶段) + +处于 `Producing` 状态的帧槽位由生产端独占。 + +生产端填写: + +```text +帧身份信息 +数据时间信息 +快照时间信息 +后台排队信息 +prepare 时间信息 +draw 时间信息 +publish 时间信息 +工作量信息 +``` + +其他线程不修改这些字段。 + +### 15.2 Ready(发布阶段) + +生产端完成全部写入后,将帧槽位状态从: + +```text +Producing 生产中 +``` + +发布为: + +```text +Ready 已就绪 +``` + +状态发布建立完整内存可见性边界。 + +### 15.3 Consuming(消费阶段) + +消费者取得处于 `Ready` 状态的帧槽位后,将状态变为: + +```text +Consuming 消费中 +``` + +消费者独占填写: + +```text +update_request_id GUI 更新请求标识 +update_request_time GUI 更新请求入队时间 +paint_begin_time paintEvent 开始时间 +paint_end_time paintEvent 结束时间 +consumer_release_time 消费者释放时间 +Frame_Outcome::Consumed 已消费 +``` + +生产端在消费者释放该帧槽位前不再访问该帧。 + +### 15.4 Returned_Frame(返回阶段) + +消费者完成访问后,将帧槽位及完整反馈返回生产侧。 + +返回路径: + +```text +消费者填写消费信息 +→ 发布消费完成反馈 +→ 帧槽位所有权返回生产侧 +→ 调度侧读取完整 Frame_Metadata +→ 聚合统计 +→ 清理上一代元数据 +→ 递增 frame_slot_generation +→ 将帧槽位重新置为 Free +→ 下一代生产时切换为 Producing +``` + +不需要为每个时间字段单独使用原子变量。 + +无锁同步边界只放在: + +```text +Frame_State 发布 帧状态发布 +frame_slot_generation 发布 帧槽位复用代次发布 +帧版本发布 +``` + +## 16. Superseded(已被更新帧替代)帧反馈 + +`Latency_Eager`(低延迟优先)模式下,旧的 Ready 状态帧可能被更新帧替代。 + +被替代帧在复用前标记: + +```text +Frame_Outcome::Superseded 已被更新帧替代 +``` + +需要采集: + +```text +frame_id +input_version +publish_time +superseded_time +ready_frame_wait +render_duration +production_lifecycle_duration +``` + +可派生: + +```text +superseded_ready_count +superseded_ready_ratio +superseded_frame_age +wasted_render_time +``` + +其中: + +```text +wasted_render_time +``` + +表示已经完成渲染但最终没有显示的生产时间。 + +## 17. 重复显示反馈 + +当 `paintEvent()` 没有取得新帧,只能继续显示上一帧时,记录: + +```text +consumer_underrun_count +repeated_frame_count +repeat_time +last_consumed_frame_id +last_consumed_input_version +``` + +可派生: + +```text +consumer_underrun_ratio +repeated_frame_ratio +displayed_frame_age +``` + +重复绘制上一帧不创建新的完整 `Frame_Metadata`。 + +## 18. 调度事件 + +### 18.1 Input_Changed(输入数据已更新) + +处理路径: + +```text +更新 latest_input_version +更新有效输入间隔统计 +尝试启动生产 +``` + +### 18.2 Render_Completed(渲染生产已完成) + +处理路径: + +```text +完成生产阶段元数据 +将帧槽位发布为 `Ready` 状态 +请求一次 update() +更新生产能力统计 +执行负反馈计算 +尝试启动下一帧 +``` + +### 18.3 Frame_Consumed(帧已消费) + +处理路径: + +```text +完成消费阶段元数据 +释放当前 `Consuming` 状态帧槽位 +采集返回帧的 Frame_Metadata +更新消费能力统计 +执行负反馈计算 +检查是否存在更新的 Ready 状态帧 +尝试启动下一帧 +``` + +### 18.4 Rate_Limit_Deadline_Reached(频率限制截止时间已到达) + +处理路径: + +```text +检查最新数据版本 +检查生产状态 +检查三帧槽位状态 +检查生产策略 +满足条件则启动生产 +``` + +### 18.5 Control_Updated(控制参数已更新) + +低频执行: + +```text +更新平滑间隔 +更新抖动估计 +识别瓶颈 +应用滞回 +调整 adaptive_min_render_interval +``` + +## 19. 两个完成事件回路 + +### 19.1 生产完成自时钟 + +生产者受限时: + +```text +Frame_N 生产完成 +→ 发布 Frame_N +→ 检查是否存在更新数据 +→ 检查人工速率限制 +→ 检查三帧槽位状态 +→ 启动 Frame_N_Plus_1 +``` + +生产循环由: + +```text +Render_Completed 渲染生产已完成 +``` + +驱动。 + +### 19.2 消费完成反馈 + +消费者完成后: + +```text +Frame_N 消费完成 +→ 填写消费阶段元数据 +→ 释放当前 `Consuming` 状态帧槽位 +→ 返回 Frame_Metadata +→ 更新消费能力 +→ 调度器重新决策 +``` + +只有存在更新的 Ready 状态帧时才再次请求: + +```text +update() +``` + +## 20. 速率模型 + +调度器维护: + +```text +D:有效数据更新速率 +P:后台生产能力 +C:前端消费能力 +M:人工最大生产速率 +``` + +定义: + +```text +D 有效数据更新速率,由有效输入间隔估计 +P 后台生产能力,由完整帧生产耗时估计 +C 前端消费能力,由不同帧被实际消费的间隔估计 +M 人工最大生产速率,由用户配置 +``` + +长期有效显示速率近似为: + +```text +display_rate = min(D, P, C, M) +``` + +该公式描述稳定结果,不直接替代每次调度判断。 + +## 21. 能力测量有效条件 + +### 21.1 生产能力有效条件 + +生产耗时可解释为生产能力时,需要满足: + +```text +持续存在未渲染的新数据 +生产完成后立即具备下一次生产资格 +没有消费者背压 +没有人工最大速率等待 +``` + +### 21.2 消费能力有效条件 + +消费间隔可解释为消费者能力时,需要满足: + +```text +Ready 状态帧持续可用 +消费者一直有新帧可取 +生产端没有断供 +``` + +### 21.3 输入速率有效条件 + +输入速率只统计会改变最终图像的有效核心版本。 + +## 22. 平滑平均和抖动 + +不直接平滑 FPS。 + +分别平滑: + +```text +input_interval +render_queue_wait +render_duration +ready_frame_wait +update_queue_wait +paint_duration +consume_interval +data_to_paint_end +``` + +每个指标维护: + +```text +srtt +jitter +``` + +控制侧使用: + +```text +safe_interval = srtt + K × jitter +``` + +控制方向: + +```text +发现过度生产 +→ 快速增加生产间隔 + +发现消费者断供 +→ 缓慢缩短生产间隔 +``` + +## 23. 快反馈 + +每次调度事件立即检查: + +```text +latest_input_version > rendered_input_version +render_busy +ready_valid +consumer_busy +update_pending +current_time >= next_render_time +production_policy +``` + +直接决定: + +```text +立即生产 +等待生产完成 +等待消费者 +等待最大速率期限 +请求 update() +保持空闲 +``` + +## 24. 慢反馈 + +从返回的 `Frame_Metadata` 聚合: + +```text +input_interval_srtt +input_interval_jitter + +render_queue_wait_srtt +render_queue_wait_jitter + +render_duration_srtt +render_duration_jitter + +ready_frame_wait_srtt +ready_frame_wait_jitter + +update_queue_wait_srtt +update_queue_wait_jitter + +paint_duration_srtt +paint_duration_jitter + +consume_interval_srtt +consume_interval_jitter + +frame_age_srtt +frame_age_jitter +``` + +同时维护: + +```text +producer_busy_ratio +ready_frame_occupancy_ratio +consumer_busy_ratio + +superseded_ready_ratio +consumer_underrun_ratio +unique_consumed_ratio +display_efficiency +``` + +其中: + +```text +display_efficiency += consumed_frame_count / produced_frame_count +``` + +## 25. 瓶颈状态 + +### 25.1 Input_Limited(输入受限) + +特征: + +```text +有效输入间隔最长 +生产者经常空闲 +不存在 Ready 状态帧的时间较多 +消费者经常空闲 +``` + +行为: + +```text +有新核心数据立即生产 +没有新核心数据完全停止 +不重复生成相同版本 +``` + +### 25.2 Producer_Limited(生产端受限) + +特征: + +```text +render_duration 最大 +处于 `Producing` 状态的帧槽位长期繁忙 +不存在 Ready 状态帧的时间较多 +consumer_underrun_ratio 上升 +``` + +行为: + +```text +不增加额外自适应等待 +生产完成后立即检查最新数据 +存在更新则直接启动下一帧 +渲染期间的中间输入版本全部合并 +消费者没有新帧时继续显示上一帧 +``` + +人工最大速率仍然有效。 + +### 25.3 Consumer_Limited(消费端受限) + +特征: + +```text +ready_frame_wait 增加 +update_queue_wait 增加 +Ready 状态帧槽位长期占用 +superseded_ready_ratio 上升 +display_efficiency 下降 +``` + +#### Cost_Balanced(成本均衡) + +```text +已有 Ready 状态帧时停止继续生产 +输入端继续更新 latest_input_version +消费者释放帧槽位后再生产当前最新版本 +``` + +#### Latency_Eager(低延迟优先) + +```text +允许有限更新 Ready 状态帧 +允许新的 Ready 状态帧替代旧的 Ready 状态帧 +持续统计 superseded_ready_ratio +持续统计 wasted_render_time +覆盖率或浪费时间过高时增加生产间隔 +``` + +### 25.4 Manual_Limited(手动上限受限) + +特征: + +```text +manual_min_render_interval +大于输入、生产和消费安全周期 +``` + +行为: + +```text +只有达到 next_render_time 后才能启动生产 +限速期间的输入变化全部合并 +每次生产使用当时最新状态 +``` + +用户配置的是: + +```text +max_render_rate +``` + +不是必须达到的目标帧率。 + +### 25.5 Balanced(均衡状态) + +特征: + +```text +生产能力和消费能力差异处于抖动安全带内 +Ready 状态帧槽位占用稳定 +superseded_ready_ratio 较低 +consumer_underrun_ratio 较低 +``` + +行为: + +```text +保持当前生产间隔 +保持当前策略 +允许生产端有限领先一个 Ready 状态帧 +不根据单次样本切换瓶颈 +``` + +## 26. 滞回控制 + +新的瓶颈状态必须同时满足: + +```text +平滑差异超过抖动安全带 +持续多个控制周期 +帧槽位占用状态与候选瓶颈一致 +帧级反馈指标与候选瓶颈一致 +``` + +进入 `Consumer_Limited` 时需要同时观察: + +```text +ready_frame_wait 上升 +superseded_ready_ratio 上升 +update_queue_wait 上升 +display_efficiency 下降 +``` + +进入 `Producer_Limited` 时需要同时观察: + +```text +render_duration 较高 +producer_busy_ratio 接近满载 +ready_frame_occupancy_ratio 较低 +consumer_underrun_ratio 上升 +``` + +处于安全带内时保持原状态。 + +## 27. 负反馈调节 + +### 27.1 过度生产反馈 + +触发特征: + +```text +superseded_ready_ratio 持续升高 +wasted_render_time 持续升高 +ready_frame_wait 持续增加 +display_efficiency 持续下降 +frame_age 没有因为增加生产而改善 +``` + +调节动作: + +```text +快速增加 adaptive_min_render_interval +减少 Ready 状态帧替换频率 +必要时退化到 `Cost_Balanced`(成本均衡)行为 +``` + +### 27.2 消费断供反馈 + +触发特征: + +```text +consumer_underrun_ratio 持续升高 +不存在 Ready 状态帧的时间较多 +消费者经常重复显示上一帧 +存在持续未渲染的新输入 +生产端未满载 +``` + +调节动作: + +```text +缓慢缩短 adaptive_min_render_interval +直到人工最大速率或生产能力边界 +``` + +### 27.3 生产满载反馈 + +触发特征: + +```text +producer_busy_ratio 接近满载 +render_queue_wait 上升 +render_duration 达到稳定上限 +``` + +调节动作: + +```text +停止继续提速 +不堆积渲染任务 +生产完成后按最新数据自时钟运行 +``` + +## 28. 最大生产速率 + +最大生产速率属于生产端准入条件。 + +定义: + +```text +相邻两次完整帧生产开始之间允许的最小时间 +``` + +生产必须同时满足: + +```text +存在未渲染的新数据 +当前没有生产任务 +三帧槽位状态允许生产 +当前策略允许生产 +最大速率等待时间已经结束 +``` + +配置: + +```text +无限制 +30 FPS +60 FPS +120 FPS +其他用户指定值 +``` + +无限制表示不增加人工等待,不表示绕过数据、消费者和三帧槽位约束。 + +## 29. Production_Policy(生产策略) + +### 29.1 Cost_Balanced(成本均衡) + +目标: + +```text +减少无意义计算 +提高已生成帧的实际显示比例 +``` + +行为: + +```text +已有 Ready 状态帧时停止生产 +消费者消费后再生产最新状态 +长期生产速率接近有效消费速率 +``` + +### 29.2 Latency_Eager(低延迟优先) + +目标: + +```text +降低最新数据到显示完成的年龄 +``` + +行为: + +```text +消费者显示当前帧时允许生产下一帧 +允许新的 Ready 状态帧替代旧的 Ready 状态帧 +仍受最大生产速率约束 +连续覆盖过多时触发负反馈降速 +``` + +## 30. update () 调度 + +完整帧发布后: + +```text +ready_valid +且 update_pending 为 false +→ 请求一次 QWidget::update() +``` + +连续发布多个帧时,只保持一个待处理的 `update()`。 + +`paintEvent()` 开始时取得当前最新完整帧。 + +`paintEvent()` 完成后: + +```text +存在更新的 Ready 状态帧 +→ 再请求一次 update() + +不存在更新的 Ready 状态帧 +→ 不请求 update() +``` + +系统自身触发的窗口暴露、resize 或重复重绘不计为新帧消费。 + +## 31. 数据和帧丢弃规则 + +### 31.1 输入版本 + +未开始渲染的旧输入版本直接合并。 + +```text +正在渲染 100 +收到 101、102、103 +下一帧直接使用 103 +``` + +### 31.2 正在生产的帧 + +默认完成,不因新输入到达而中途取消。 + +### 31.3 已完成未消费帧 + +`Cost_Balanced`(成本均衡): + +```text +已有 Ready 状态帧时暂停继续生产 +``` + +`Latency_Eager`(低延迟优先): + +```text +允许新的 Ready 状态帧替代旧的 Ready 状态帧 +旧帧标记为 `Superseded`(已被更新帧替代) +``` + +### 31.4 正在消费的帧 + +处于 `Consuming` 状态的帧槽位在释放前不会被覆盖或修改。 + +## 32. 完整运行路径 + +```text +核心数据变化 +→ 更新 latest_input_version +→ 触发 Input_Changed +→ 调度器检查生产资格 +→ 收集一致快照 +→ 填写数据和快照时间 +→ 进入后台任务队列 +→ 记录 render_queue_wait +→ 执行 prepare +→ 执行 draw +→ 填写生产时间 +→ 将帧槽位发布为 `Ready` 状态 +→ 请求 update() +→ 记录 update_request_time +→ paintEvent 开始 +→ 取得最新 Ready 状态帧 +→ 填写 paint_begin_time +→ 执行绘制 +→ 填写 paint_end_time +→ 释放当前 `Consuming` 状态帧槽位 +→ 帧槽位所有权返回 +→ 调度侧读取完整 Frame_Metadata +→ 更新 srtt、jitter 和比例统计 +→ 更新 Bottleneck_State(瓶颈状态) +→ 更新 adaptive_min_render_interval +→ 重新判断是否生产下一帧 +``` + +## 33. 最终设计结论 + +默认实时图表采用: + +```text +Data_Driven 数据驱动 +Latest_Wins 最新数据优先 +Triple_Frame 三帧机制 +Max_Render_Rate 最大渲染频率限制 +Render_Completed_Self_Clock 渲染完成事件自时钟 +Frame_Consumed_Feedback 帧消费完成反馈 +Frame_Metadata_Lock_Free_Return 帧元信息无锁返回 +SRTT_And_Jitter 平滑时间与抖动估计 +Bottleneck_Detection 瓶颈识别 +Hysteresis_Control 滞回控制 +Fast_Backoff 快速退避 +Slow_Recovery 缓慢恢复 +``` + +最终控制原则: + +> 使用三帧槽位状态和版本状态决定当前动作,使用帧自身携带的完整生命周期信息形成无锁反馈,使用平滑平均和抖动识别长期瓶颈,使用已消费率、帧覆盖率、排队时间、渲染时间、 +> `paintEvent()` 时间和帧年龄控制生产频率。 diff --git a/main.cmake b/main.cmake index 9ed098e..26b7065 100644 --- a/main.cmake +++ b/main.cmake @@ -81,6 +81,24 @@ set_property(TARGET Renderive PROPERTY AUTOUIC ON) set_property(TARGET Renderive PROPERTY AUTORCC ON) target_link_libraries(Renderive PUBLIC ${Core}) ysgraphic_core_apply_sanitizer(Renderive) +if (YSGRAPHIC_CORE_BUILD_TESTS) + enable_testing() + add_executable(Renderive_Frame_Scheduler_Feedback EXCLUDE_FROM_ALL "${CMAKE_CURRENT_LIST_DIR}/tests/Frame_Scheduler_Feedback.cpp") + target_link_libraries(Renderive_Frame_Scheduler_Feedback PRIVATE Renderive) + ysgraphic_core_apply_sanitizer(Renderive_Frame_Scheduler_Feedback) + add_test(NAME Renderive.Frame_Scheduler_Feedback COMMAND Renderive_Frame_Scheduler_Feedback) + set_tests_properties(Renderive.Frame_Scheduler_Feedback PROPERTIES ENVIRONMENT "RENDERIVE_PERFORMANCE_LOG=1;RENDERIVE_PERFORMANCE_LOG_PATH=${CMAKE_BINARY_DIR}/renderive_feedback_test_log.csv") + add_executable(Renderive_Performance_Shower_Interaction EXCLUDE_FROM_ALL "${CMAKE_CURRENT_LIST_DIR}/tests/Performance_Shower_Interaction.cpp") + target_link_libraries(Renderive_Performance_Shower_Interaction PRIVATE Renderive Renderive_Widget) + ysgraphic_core_apply_sanitizer(Renderive_Performance_Shower_Interaction) + add_test(NAME Renderive.Performance_Shower_Interaction COMMAND Renderive_Performance_Shower_Interaction) + set_tests_properties(Renderive.Performance_Shower_Interaction PROPERTIES ENVIRONMENT "QT_QPA_PLATFORM=offscreen;RENDERIVE_PERFORMANCE_LOG=0" TIMEOUT 12) + add_executable(Renderive_Multithread_Input_Stress EXCLUDE_FROM_ALL "${CMAKE_CURRENT_LIST_DIR}/tests/Multithread_Input_Stress.cpp") + target_link_libraries(Renderive_Multithread_Input_Stress PRIVATE Renderive) + ysgraphic_core_apply_sanitizer(Renderive_Multithread_Input_Stress) + add_test(NAME Renderive.Multithread_Input_Stress COMMAND Renderive_Multithread_Input_Stress) + set_tests_properties(Renderive.Multithread_Input_Stress PROPERTIES ENVIRONMENT "QT_QPA_PLATFORM=offscreen;RENDERIVE_PERFORMANCE_LOG=0" TIMEOUT 15) +endif () #if (YSGRAPHIC_CORE_BUILD_TESTS) # enable_testing() # add_executable(Renderive_Core_Renderable_Lifecycle_Stress EXCLUDE_FROM_ALL "${CMAKE_CURRENT_LIST_DIR}/tests/Renderable_Lifecycle_Stress.cpp") diff --git a/tests/Frame_Scheduler_Feedback.cpp b/tests/Frame_Scheduler_Feedback.cpp new file mode 100644 index 0000000..ed99562 --- /dev/null +++ b/tests/Frame_Scheduler_Feedback.cpp @@ -0,0 +1,268 @@ +#include "../Renderive/plottable/Performance_Shower_p.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +namespace { +constexpr std::uint64_t Nanoseconds_Per_Millisecond = 1000000; +std::uint64_t to_nanoseconds(double milliseconds) { + return static_cast(std::llround(milliseconds * static_cast(Nanoseconds_Per_Millisecond))); +} +struct Scenario_Config { + const char* name{}; + std::uint64_t id{}; + double input_interval_ms{}; + double render_duration_ms{}; + double paint_duration_ms{}; + double max_render_fps{}; + renderive::Bottleneck_State expected_state = renderive::Bottleneck_State::Unknown; + double minimum_production_fps{}; + double maximum_production_fps{}; + double minimum_display_fps{}; + double maximum_display_fps{}; +}; +struct Scenario_Result { + renderive::Frame_Feedback_State state; + std::uint64_t returned_frame_count{}; +}; +class Scenario_Simulation { +public: + explicit Scenario_Simulation(Scenario_Config config) : config(config) { + controller.set_max_render_fps(config.max_render_fps); + } + Scenario_Result run(double duration_ms) { + end_time = to_nanoseconds(duration_ms); + next_input_time = to_nanoseconds(config.input_interval_ms); + while (now < end_time) { + start_consumer(); + start_render(); + std::uint64_t event_time = next_event_time(); + if (event_time > end_time) + event_time = end_time; + now = event_time; + if (now == next_input_time) + receive_input(); + if (now == render_complete_time) + complete_render(); + if (now == consume_complete_time) + complete_consume(); + } + if (ready_frame) { + ready_frame->superseded_time = now; + controller.on_frame_superseded(*ready_frame, consuming_frame.has_value(), false); + return_frame(*ready_frame); + ready_frame.reset(); + } + if (consuming_frame) { + consuming_frame->paint_end_time = now; + consuming_frame->consumer_release_time = now; + controller.on_frame_consumed(*consuming_frame, false); + return_frame(*consuming_frame); + consuming_frame.reset(); + } + return {controller.state(), returned_frames.size()}; + } +private: + bool input_pending() const { + return latest_input_version > published_input_version; + } + bool can_start_render() const { + if (rendering_frame || !input_pending()) + return false; + return true; + } + void start_render() { + if (!can_start_render() || !controller.rate_limit_ready(now)) + return; + renderive::Frame_Metadata frame; + frame.frame_id = config.id * 1000000 + ++next_frame_id; + frame.input_version = latest_input_version; + frame.snapshot_version = latest_input_version; + frame.frame_slot_generation = next_frame_id; + frame.core_data_time = latest_input_time; + frame.source_data_time_begin = latest_input_time; + frame.source_data_time_end = latest_input_time; + frame.render_request_time = latest_input_time; + frame.snapshot_capture_begin_time = now; + frame.snapshot_capture_end_time = now; + frame.render_queue_enter_time = now; + frame.prepare_begin_time = now; + frame.prepare_end_time = now; + frame.pixel_width = 1920; + frame.pixel_height = 1080; + controller.on_render_started(frame, now); + rendering_frame = frame; + render_complete_time = now + to_nanoseconds(config.render_duration_ms); + } + void complete_render() { + renderive::Frame_Metadata frame = *rendering_frame; + frame.draw_begin_time = frame.render_begin_time; + frame.draw_end_time = now; + frame.render_end_time = now; + frame.publish_time = now; + bool had_ready_frame = ready_frame.has_value(); + controller.on_render_completed(frame, had_ready_frame, consuming_frame.has_value(), latest_input_version > frame.input_version); + published_input_version = frame.input_version; + if (ready_frame) { + ready_frame->superseded_time = now; + controller.on_frame_superseded(*ready_frame, consuming_frame.has_value(), input_pending()); + return_frame(*ready_frame); + } + ready_frame = frame; + rendering_frame.reset(); + render_complete_time = Infinite_Time; + } + void start_consumer() { + if (consuming_frame || !ready_frame) + return; + consuming_frame = *ready_frame; + ready_frame.reset(); + consuming_frame->update_request_id = consuming_frame->frame_id; + consuming_frame->update_request_time = consuming_frame->publish_time; + consuming_frame->paint_begin_time = now; + consume_complete_time = now + to_nanoseconds(config.paint_duration_ms); + } + void complete_consume() { + consuming_frame->paint_end_time = now; + consuming_frame->consumer_release_time = now; + controller.on_frame_consumed(*consuming_frame, ready_frame.has_value()); + return_frame(*consuming_frame); + consuming_frame.reset(); + consume_complete_time = Infinite_Time; + } + void receive_input() { + ++latest_input_version; + latest_input_time = now; + controller.on_input_changed(now, latest_input_version); + next_input_time += to_nanoseconds(config.input_interval_ms); + } + std::uint64_t next_event_time() const { + std::uint64_t event_time = std::min({next_input_time, render_complete_time, consume_complete_time, end_time}); + if (can_start_render() && !controller.rate_limit_ready(now)) + event_time = std::min(event_time, controller.next_render_time_ns()); + return event_time; + } + void return_frame(renderive::Frame_Metadata& frame) { + renderive::write_frame_performance_log(frame); + returned_frames.push_back(frame); + } + static constexpr std::uint64_t Infinite_Time = std::numeric_limits::max(); + Scenario_Config config; + renderive::Frame_Feedback_Controller controller; + std::optional rendering_frame; + std::optional ready_frame; + std::optional consuming_frame; + std::vector returned_frames; + std::uint64_t now{}; + std::uint64_t end_time{}; + std::uint64_t next_input_time{}; + std::uint64_t render_complete_time = Infinite_Time; + std::uint64_t consume_complete_time = Infinite_Time; + std::uint64_t latest_input_time{}; + std::uint64_t latest_input_version{}; + std::uint64_t published_input_version{}; + std::uint64_t next_frame_id{}; +}; +bool in_range(double value, double minimum, double maximum) { + return value >= minimum && value <= maximum; +} +int validate_scenario(const Scenario_Config& config, const Scenario_Result& result, int index) { + const renderive::Frame_Feedback_State& state = result.state; + std::cout << config.name + << " state=" << renderive::bottleneck_state_name(state.bottleneck_state) + << " production_fps=" << state.production_rate_fps + << " display_fps=" << state.display_rate_fps + << " adaptive_interval_ms=" << state.adaptive_min_render_interval_ms + << " superseded_ratio=" << state.superseded_ready_ratio + << " display_efficiency=" << state.display_efficiency + << " returned=" << result.returned_frame_count + << '\n'; + if (state.bottleneck_state != config.expected_state) + return index * 10 + 1; + if (!in_range(state.production_rate_fps, config.minimum_production_fps, config.maximum_production_fps)) + return index * 10 + 2; + if (!in_range(state.display_rate_fps, config.minimum_display_fps, config.maximum_display_fps)) + return index * 10 + 3; + if (config.expected_state == renderive::Bottleneck_State::Consumer_Limited) { + if (std::abs(state.production_rate_fps - state.display_rate_fps) > 6.0) + return index * 10 + 4; + if (state.adaptive_min_render_interval_ms < config.paint_duration_ms * 0.8) + return index * 10 + 5; + } + if (config.expected_state == renderive::Bottleneck_State::Manual_Limited) { + double expected_interval = 1000.0 / config.max_render_fps; + if (std::abs(state.effective_min_render_interval_ms - expected_interval) > 0.2) + return index * 10 + 6; + } + return 0; +} +int validate_frame_input_ring() { + renderive::Performance_Shower_Input_Data input; + for (std::uint64_t frame_id = 1; frame_id <= 72; ++frame_id) { + renderive::Frame_Metadata frame; + frame.frame_id = frame_id; + input.push(frame); + } + if (input.get_pending_count() != renderive::Performance_Shower_Input_Data::Capacity) + return 1; + if (input.get_capacity_dropped_count() != 8) + return 2; + std::vector frame_ids; + input.read_all([&frame_ids](const renderive::Frame_Metadata& frame) { + frame_ids.push_back(frame.frame_id); + }); + if (frame_ids.front() != 9 || frame_ids.back() != 72) + return 3; + input.request_reset(); + if (input.get_pending_count() != 0 || !input.reset_pending) + return 4; + return 0; +} +int validate_renderable_cache_stats() { + renderive::Frame_Metadata frame; + frame.record_renderable_cache_hit("cache_a"); + frame.record_renderable_cache_stale_hit("cache_a"); + frame.record_renderable_cache_miss("cache_b"); + if (frame.renderable_cache_hit_count != 1 || frame.renderable_cache_stale_hit_count != 1 || frame.renderable_cache_miss_count != 1) + return 1; + renderive::Frame_Performance_Aggregate aggregate; + aggregate.update(frame); + if (aggregate.cache_hit_count != 1 || aggregate.cache_stale_hit_count != 1 || aggregate.cache_miss_count != 1) + return 2; + if (aggregate.renderable_cache_stats.size() != 2) + return 3; + return 0; +} +} +int main() { + QString log_path = qEnvironmentVariable("RENDERIVE_PERFORMANCE_LOG_PATH"); + if (!log_path.isEmpty()) + QFile::remove(log_path); + int ring_result = validate_frame_input_ring(); + if (ring_result) + return ring_result; + int cache_result = validate_renderable_cache_stats(); + if (cache_result) + return 100 + cache_result; + const std::array scenarios{{ + {"Input_Limited", 1, 50.0, 2.0, 2.0, 0.0, renderive::Bottleneck_State::Input_Limited, 18.0, 22.0, 18.0, 22.0}, + {"Producer_Limited", 2, 1.0, 20.0, 2.0, 0.0, renderive::Bottleneck_State::Producer_Limited, 45.0, 55.0, 45.0, 55.0}, + {"Consumer_Limited", 3, 1.0, 2.0, 25.0, 0.0, renderive::Bottleneck_State::Consumer_Limited, 34.0, 46.0, 36.0, 44.0}, + {"Manual_Limited", 4, 1.0, 2.0, 2.0, 30.0, renderive::Bottleneck_State::Manual_Limited, 28.0, 32.0, 28.0, 32.0} + }}; + for (int i = 0; i < scenarios.size(); ++i) { + Scenario_Simulation simulation(scenarios[i]); + Scenario_Result result = simulation.run(8000.0); + int validation_result = validate_scenario(scenarios[i], result, i + 1); + if (validation_result) + return validation_result; + } + std::this_thread::sleep_for(std::chrono::milliseconds(250)); + return 0; +} diff --git a/tests/Multithread_Input_Stress.cpp b/tests/Multithread_Input_Stress.cpp new file mode 100644 index 0000000..d09b44c --- /dev/null +++ b/tests/Multithread_Input_Stress.cpp @@ -0,0 +1,111 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "../Renderive/Axis/Axis.h" +#include "../Renderive/Axis/Frequency_Axis.h" +#include "../Renderive/Axis/Time_Axis.h" +#include "../Renderive/architecture/Plot.h" +#include "../Renderive/architecture/Plot_p.h" +#include "../Renderive/base/Memory.h" +#include "../Renderive/plottable/Audio_Frequency.h" +#include "../Renderive/plottable/Spectrum.h" +#include "../Renderive/plottable/Waterfall.h" +namespace { +std::pmr::vector make_data(int size, int seed, renderive::Memory_Domain domain) { + std::pmr::vector data(renderive::memory_resource(domain)); + data.resize(size); + for (int i = 0; i < size; ++i) + data[i] = -90.0 + 30.0 * std::sin(static_cast(i + seed) * 0.017); + return data; +} +void process_events(int milliseconds) { + QElapsedTimer timer; + timer.start(); + while (timer.elapsed() < milliseconds) { + QApplication::processEvents(QEventLoop::AllEvents, 10); + QThread::msleep(1); + } +} +bool wait_ready(const std::shared_ptr& waterfall, const std::shared_ptr& spectrum, const std::shared_ptr& audio, const std::shared_ptr& time_axis) { + QElapsedTimer timer; + timer.start(); + while (timer.elapsed() < 3000) { + QApplication::processEvents(QEventLoop::AllEvents, 10); + if (waterfall->ok() && spectrum->ok() && audio->ok() && time_axis->ok()) + return true; + QThread::msleep(1); + } + return false; +} +bool wait_idle(renderive::Plot& plot) { + QElapsedTimer timer; + timer.start(); + while (timer.elapsed() < 3000) { + QApplication::processEvents(QEventLoop::AllEvents, 10); + if (!plot.is_rendering() && !plot.d->active_render_tasks.load(std::memory_order_acquire)) + return true; + QThread::msleep(1); + } + return false; +} +int run_stress() { + renderive::Plot plot; + plot.init(); + plot.resize(640, 360); + plot.set_max_render_fps(1000); + auto data_node = plot.create_renderable_node(plot.get_root_renderable(), "Data_Renderable"); + auto axis_node = plot.create_renderable_node(plot.get_root_renderable(), "Axis_Renderable"); + auto frequency_axis = renderive::Frequency_Axis::Builder(axis_node, Qt::Horizontal).set_pixel_size(640).set_coord_range({0.0, 100.0}).build(); + auto time_axis = renderive::Time_Axis::Builder(axis_node, Qt::Vertical).set_pixel_size(360).set_time_point_size(128).build(); + auto power_axis = renderive::Axis::Builder(axis_node, Qt::Vertical).set_pixel_size(360).set_coord_range({0.0, -120.0}).build(); + auto waterfall = renderive::Waterfall::Builder(data_node, frequency_axis, time_axis).set_frequency_range({0.0, 100.0}).set_power_range({-120.0, 0.0}).set_frequency_point_size(256).build(); + auto spectrum = renderive::Spectrum::Builder(data_node, frequency_axis, power_axis).set_frequency_range({0.0, 100.0}).set_frequency_point_size(256).build(); + auto audio = renderive::Audio_Frequency::Builder(data_node, time_axis, power_axis).set_time_point_size(128).set_key_range({-120.0, 0.0}).build(); + plot.show(); + plot.start_render(1000); + if (!wait_ready(waterfall, spectrum, audio, time_axis)) + return 1; + std::atomic_bool running{true}; + std::atomic pushes{0}; + std::vector workers; + for (int thread_index = 0; thread_index < 4; ++thread_index) { + workers.emplace_back([&, thread_index]() { + for (int frame = 0; running.load(std::memory_order_acquire) && frame < 2500; ++frame) { + int seed = frame + thread_index * 10000; + QTime time = QTime::fromMSecsSinceStartOfDay(seed % 86400000); + int tick = time_axis->give_data(time); + waterfall->give_data(tick, make_data(256, seed, renderive::Memory_Domain::Waterfall)); + spectrum->give_data(make_data(256, seed, renderive::Memory_Domain::Spectrum)); + audio->give_data(tick, -90.0 + static_cast((seed % 120))); + pushes.fetch_add(1, std::memory_order_relaxed); + if ((frame & 15) == 15) + std::this_thread::yield(); + } + }); + } + QElapsedTimer timer; + timer.start(); + while (timer.elapsed() < 8000 && pushes.load(std::memory_order_relaxed) < 10000) + process_events(5); + running.store(false, std::memory_order_release); + for (auto& worker : workers) + worker.join(); + process_events(300); + plot.pause_render(); + if (!wait_idle(plot)) + return 2; + return pushes.load(std::memory_order_relaxed) > 1000 ? 0 : 3; +} +} +int main(int argc, char** argv) { + qputenv("QT_QPA_PLATFORM", "offscreen"); + QApplication app(argc, argv); + renderive::start_render_scheduler(); + return run_stress(); +} diff --git a/tests/Performance_Shower_Interaction.cpp b/tests/Performance_Shower_Interaction.cpp new file mode 100644 index 0000000..1b4df3b --- /dev/null +++ b/tests/Performance_Shower_Interaction.cpp @@ -0,0 +1,100 @@ +#include "../Renderive/base/Tool.h" +#include +#include +#include +#include +#include +#include +#include +namespace { +QPushButton* find_performance_button(QWidget* control) { + const auto buttons = control->findChildren(); + for (QPushButton* button : buttons) { + if (button->text().contains("性能测试")) + return button; + } + return nullptr; +} +void click_button(QPushButton* button) { + QPoint pos = button->rect().center(); + QMouseEvent press(QEvent::MouseButtonPress, pos, Qt::LeftButton, Qt::LeftButton, Qt::NoModifier); + QApplication::sendEvent(button, &press); + QMouseEvent release(QEvent::MouseButtonRelease, pos, Qt::LeftButton, Qt::NoButton, Qt::NoModifier); + QApplication::sendEvent(button, &release); +} +void double_click_button(QPushButton* button) { + QPoint pos = button->rect().center(); + QMouseEvent press(QEvent::MouseButtonPress, pos, Qt::LeftButton, Qt::LeftButton, Qt::NoModifier); + QApplication::sendEvent(button, &press); + QMouseEvent release(QEvent::MouseButtonRelease, pos, Qt::LeftButton, Qt::NoButton, Qt::NoModifier); + QApplication::sendEvent(button, &release); + QMouseEvent double_click(QEvent::MouseButtonDblClick, pos, Qt::LeftButton, Qt::LeftButton, Qt::NoModifier); + QApplication::sendEvent(button, &double_click); + QMouseEvent double_release(QEvent::MouseButtonRelease, pos, Qt::LeftButton, Qt::NoButton, Qt::NoModifier); + QApplication::sendEvent(button, &double_release); +} +void click_plot(QWidget* plot, const QPoint& pos, QEvent::Type type) { + QMouseEvent event(type, pos, Qt::LeftButton, Qt::LeftButton, Qt::NoModifier); + QApplication::sendEvent(plot, &event); +} +void wheel_plot(QWidget* plot, const QPoint& pos, int delta) { + QWheelEvent event(pos, plot->mapToGlobal(pos), QPoint(), QPoint(0, delta), Qt::NoButton, Qt::NoModifier, Qt::NoScrollPhase, false); + QApplication::sendEvent(plot, &event); +} +bool pump(QApplication& app, renderive::Plot& plot, int milliseconds) { + QElapsedTimer timer; + timer.start(); + while (timer.elapsed() < milliseconds) { + plot.mark_render_state_dirty(); + app.processEvents(QEventLoop::AllEvents, 5); + QThread::msleep(1); + } + return true; +} +} +int main(int argc, char** argv) { + qputenv("QT_QPA_PLATFORM", "offscreen"); + qputenv("RENDERIVE_PERFORMANCE_LOG", "0"); + QApplication app(argc, argv); + renderive::start_render_scheduler(); + renderive::Plot plot; + plot.resize(320, 240); + plot.init(); + plot.show(); + plot.start_render(240); + std::unique_ptr control(create_control(&plot)); + control->show(); + app.processEvents(); + QPushButton* performance_button = find_performance_button(control.get()); + if (!performance_button) + return 1; + QElapsedTimer total; + total.start(); + for (int i = 0; i < 40; ++i) { + click_button(performance_button); + pump(app, plot, 15); + if (total.elapsed() > 5000) + return 2; + } + double_click_button(performance_button); + pump(app, plot, 200); + plot.set_use_performance_shower(true); + pump(app, plot, 200); + click_plot(&plot, QPoint(8, 8), QEvent::MouseButtonPress); + click_plot(&plot, QPoint(8, 8), QEvent::MouseButtonRelease); + click_plot(&plot, QPoint(8, 8), QEvent::MouseButtonDblClick); + click_plot(&plot, QPoint(8, 8), QEvent::MouseButtonRelease); + for (int i = 0; i < 12; ++i) { + wheel_plot(&plot, QPoint(8, 8), -120); + pump(app, plot, 10); + } + for (int i = 0; i < 12; ++i) { + wheel_plot(&plot, QPoint(8, 8), 120); + pump(app, plot, 10); + } + pump(app, plot, 200); + if (!plot.get_use_performance_shower()) + return 3; + plot.pause_render(); + return 0; +}