diff --git a/Demo_Gallery/Afterglow_Plot.h b/Demo_Gallery/Afterglow_Plot.h index 2ee1d7a..4d35617 100644 --- a/Demo_Gallery/Afterglow_Plot.h +++ b/Demo_Gallery/Afterglow_Plot.h @@ -1,7 +1,7 @@ #pragma once #include "../Renderive/base/Tool.h" static QWidget* create_afterglow() { - class Afterglow_Plot : public renderive::Plot { + class Afterglow_Plot : public renderive::Latency_Eager_Plot { public: std::shared_ptr x_axis{}; std::shared_ptr y_axis{}; @@ -9,7 +9,7 @@ static QWidget* create_afterglow() { std::shared_ptr msr{}; QMenu* menu{}; void init() override { - Plot::init(); + Latency_Eager_Plot::init(); object_name = "Afterglow_Plot"; auto data = create_renderable_node(get_root_renderable(), "Data_Renderable"); auto axis = create_renderable_node(get_root_renderable(), "Axis_Renderable"); diff --git a/Demo_Gallery/Audio_Plot.h b/Demo_Gallery/Audio_Plot.h index 014ded0..548400f 100644 --- a/Demo_Gallery/Audio_Plot.h +++ b/Demo_Gallery/Audio_Plot.h @@ -1,7 +1,7 @@ #pragma once #include "../Renderive/base/Tool.h" static QWidget* create_audio_plot() { - class Audio_Plot : public renderive::Plot { + class Audio_Plot : public renderive::Latency_Eager_Plot { public: std::shared_ptr x_axis{}; std::shared_ptr y_axis{}; @@ -9,7 +9,7 @@ static QWidget* create_audio_plot() { std::shared_ptr msr{}; QMenu* menu{}; void init() override { - Plot::init(); + Latency_Eager_Plot::init(); object_name = "Audio_Plot"; auto data = create_renderable_node(get_root_renderable(), "Data_Renderable"); auto axis = create_renderable_node(get_root_renderable(), "Axis_Renderable"); diff --git a/Demo_Gallery/Planisphere_Plot.h b/Demo_Gallery/Planisphere_Plot.h index b512ab3..5749778 100644 --- a/Demo_Gallery/Planisphere_Plot.h +++ b/Demo_Gallery/Planisphere_Plot.h @@ -2,7 +2,7 @@ #include "../Renderive/base/Tool.h" static QWidget* create_planisphere_plot() { class Time_Axis; - class Planisphere_Plot : public renderive::Plot { + class Planisphere_Plot : public renderive::Latency_Eager_Plot { public: std::shared_ptr x_axis{}; std::shared_ptr y_axis{}; @@ -10,7 +10,7 @@ static QWidget* create_planisphere_plot() { std::shared_ptr msr{}; QMenu* menu{}; void init() override { - Plot::init(); + Latency_Eager_Plot::init(); object_name = "PlanispherePlot"; auto data = create_renderable_node(get_root_renderable(), "Data_Renderable"); auto axis = create_renderable_node(get_root_renderable(), "Axis_Renderable"); diff --git a/Demo_Gallery/Spectrum_Plot.h b/Demo_Gallery/Spectrum_Plot.h index 4e4b235..8072182 100644 --- a/Demo_Gallery/Spectrum_Plot.h +++ b/Demo_Gallery/Spectrum_Plot.h @@ -1,7 +1,7 @@ #pragma once #include "../Renderive/base/Tool.h" static QWidget* create_spectrun_plot() { - class Spectrum_Plot : public renderive::Plot { + class Spectrum_Plot : public renderive::Latency_Eager_Plot { public: std::shared_ptr x_axis{}; std::shared_ptr y_axis{}; @@ -9,7 +9,7 @@ static QWidget* create_spectrun_plot() { std::shared_ptr sp{}; QMenu* menu{}; void init() override { - Plot::init(); + Latency_Eager_Plot::init(); object_name = "Spectrum_Plot"; auto data = create_renderable_node(get_root_renderable(), "Data_Renderable"); auto axis = create_renderable_node(get_root_renderable(), "Axis_Renderable"); diff --git a/Demo_Gallery/Sweep_Frequent_Plot.h b/Demo_Gallery/Sweep_Frequent_Plot.h index bbd9b69..1df769f 100644 --- a/Demo_Gallery/Sweep_Frequent_Plot.h +++ b/Demo_Gallery/Sweep_Frequent_Plot.h @@ -1,7 +1,7 @@ #pragma once #include "../Renderive/base/Tool.h" static QWidget* create_sweep_frequent_plot() { - class Sweep_Frequent_Plot : public renderive::Plot { + class Sweep_Frequent_Plot : public renderive::Latency_Eager_Plot { public: std::shared_ptr x_axis{}; std::shared_ptr y_axis{}; @@ -9,7 +9,7 @@ static QWidget* create_sweep_frequent_plot() { std::shared_ptr sf{}; QMenu* menu{}; void init() override { - Plot::init(); + Latency_Eager_Plot::init(); object_name = "Axis_Test_Plot"; auto data = create_renderable_node(get_root_renderable(), "Data_Renderable"); auto axis = create_renderable_node(get_root_renderable(), "Axis_Renderable"); diff --git a/Demo_Gallery/Waterfall_Plot.h b/Demo_Gallery/Waterfall_Plot.h index a834e49..e9013c2 100644 --- a/Demo_Gallery/Waterfall_Plot.h +++ b/Demo_Gallery/Waterfall_Plot.h @@ -1,7 +1,7 @@ -#pragma once +#pragma once #include "../Renderive/base/Tool.h" static QWidget* create_water_fall_plot() { - class Waterfall_Plot : public renderive::Plot { + class Waterfall_Plot : public renderive::Latency_Eager_Plot { public: std::shared_ptr x_axis{}; std::shared_ptr y_axis{}; @@ -9,7 +9,7 @@ static QWidget* create_water_fall_plot() { std::shared_ptr wf{}; QMenu* menu{}; void init() override { - Plot::init(); + Latency_Eager_Plot::init(); object_name = "Water_Fall_Plot"; auto data = create_renderable_node(get_root_renderable(), "Data_Renderable"); auto axis = create_renderable_node(get_root_renderable(), "Axis_Renderable"); diff --git a/Demo_Gallery/base/Axis_Test.h b/Demo_Gallery/base/Axis_Test.h index 27076d6..20ce1a9 100644 --- a/Demo_Gallery/base/Axis_Test.h +++ b/Demo_Gallery/base/Axis_Test.h @@ -1,12 +1,12 @@ #pragma once #include "../../Renderive/base/Tool.h" static QWidget* create_axis_test_plot() { - class Axis_Test_Plot : public renderive::Plot { + class Axis_Test_Plot : public renderive::Latency_Eager_Plot { public: std::shared_ptr axis{}; QMenu* menu{}; void init() override { - Plot::init(); + Latency_Eager_Plot::init(); object_name = "Axis_Test_Plot"; auto axis_node = create_renderable_node(get_root_renderable(), "Axis_Renderable"); axis = renderive::Axis::Builder(axis_node, Qt::Horizontal) diff --git a/Demo_Gallery/base/Frequency_Axis.h b/Demo_Gallery/base/Frequency_Axis.h index 75e544f..3e81bee 100644 --- a/Demo_Gallery/base/Frequency_Axis.h +++ b/Demo_Gallery/base/Frequency_Axis.h @@ -1,17 +1,18 @@ #pragma once #include "../../Renderive/base/Tool.h" static QWidget* create_frequent_axis_test_plot() { - class Axis_Test_Plot : public YSG::Plot { + class Axis_Test_Plot : public renderive::Latency_Eager_Plot { public: - std::shared_ptr axis{}; + std::shared_ptr axis{}; QMenu* menu{}; void init() override { - Plot::init(); + Latency_Eager_Plot::init(); object_name = "Axis_Test_Plot"; - auto axis_node = create_renderable_node(root_renderable(), "Axis_Renderable"); - axis = YSG::Frequency_Axis::Builder(axis_node, Qt::Horizontal) + auto axis_node = create_renderable_node(get_root_renderable(), "Axis_Renderable"); + axis = renderive::Frequency_Axis::Builder(axis_node, Qt::Horizontal) .set_tick_length(-10) .set_sub_tick_length(-5) + .set_coord_range({-12000, 12000}) .build(); } protected: diff --git a/Demo_Gallery/base/Frequency_Axis_Test.h b/Demo_Gallery/base/Frequency_Axis_Test.h index 0439314..3e81bee 100644 --- a/Demo_Gallery/base/Frequency_Axis_Test.h +++ b/Demo_Gallery/base/Frequency_Axis_Test.h @@ -1,12 +1,12 @@ #pragma once #include "../../Renderive/base/Tool.h" static QWidget* create_frequent_axis_test_plot() { - class Axis_Test_Plot : public renderive::Plot { + class Axis_Test_Plot : public renderive::Latency_Eager_Plot { public: std::shared_ptr axis{}; QMenu* menu{}; void init() override { - Plot::init(); + Latency_Eager_Plot::init(); object_name = "Axis_Test_Plot"; auto axis_node = create_renderable_node(get_root_renderable(), "Axis_Renderable"); axis = renderive::Frequency_Axis::Builder(axis_node, Qt::Horizontal) diff --git a/Demo_Gallery/base/Multi_Select_Test.h b/Demo_Gallery/base/Multi_Select_Test.h index b4ec0c4..895e4a4 100644 --- a/Demo_Gallery/base/Multi_Select_Test.h +++ b/Demo_Gallery/base/Multi_Select_Test.h @@ -1,14 +1,14 @@ #pragma once #include "../../Renderive/base/Tool.h" static QWidget* create_Multi_Select_Plot() { - class Multi_Select_Plot : public renderive::Plot { + class Multi_Select_Plot : public renderive::Latency_Eager_Plot { public: std::shared_ptr x_axis{}; std::shared_ptr y_axis{}; std::shared_ptr multi_select_rect{}; QMenu* menu{}; void init() override { - Plot::init(); + Latency_Eager_Plot::init(); object_name = "PlanispherePlot"; auto axis = create_renderable_node(get_root_renderable(), "Axis_Renderable"); auto overlay = create_renderable_node(get_root_renderable(), "Overlay_Renderable"); diff --git a/Demo_Gallery/base/Time_Axis_Test.h b/Demo_Gallery/base/Time_Axis_Test.h index e2edcc2..58090d8 100644 --- a/Demo_Gallery/base/Time_Axis_Test.h +++ b/Demo_Gallery/base/Time_Axis_Test.h @@ -1,12 +1,12 @@ #pragma once #include "../../Renderive/base/Tool.h" static QWidget* create_time_axis_test_plot() { - class Axis_Test_Plot : public renderive::Plot { + class Axis_Test_Plot : public renderive::Latency_Eager_Plot { public: std::shared_ptr axis{}; QMenu* menu{}; void init() override { - Plot::init(); + Latency_Eager_Plot::init(); object_name = "TimeAxisTestPlot"; auto axis_node = create_renderable_node(get_root_renderable(), "Axis_Renderable"); axis = renderive::Time_Axis::Builder(axis_node, Qt::Horizontal) diff --git a/Renderive/Axis/Abs_Axis_p.h b/Renderive/Axis/Abs_Axis_p.h index a0f73b5..010f43b 100644 --- a/Renderive/Axis/Abs_Axis_p.h +++ b/Renderive/Axis/Abs_Axis_p.h @@ -14,7 +14,6 @@ struct Abs_Axis_Render_State : Render_State, Abs_Axis_Prop { }; struct Abs_Axis_Input_Data : Input_Data {}; struct Abs_Axis_Private : Typed_Render_Data { - friend class Abs_Axis; int tick_count = 2; std::atomic pixel_start{}; double get_mantissa(double input, double* magnitude = nullptr); @@ -30,15 +29,15 @@ struct Abs_Axis_Private : Typed_Render_Dataq(); double tick_step = q->get_tick_step(range); std::vector ticks = q->create_tick_vector(tick_step, range); - int sub_tick_count = q->get_sub_tick_count(tick_step); - std::vector sub_ticks = q->create_sub_tick_vector(sub_tick_count, ticks, range); - std::vector labels = q->create_label_vector(ticks, SRC::Render); while (!ticks.empty() && !range.contain(ticks.back())) { ticks.pop_back(); } + int sub_tick_count = q->get_sub_tick_count(tick_step); + std::vector sub_ticks = q->create_sub_tick_vector(sub_tick_count, ticks, range); while (!sub_ticks.empty() && !range.contain(sub_ticks.back())) { sub_ticks.pop_back(); } + std::vector labels = q->create_label_vector(ticks, SRC::Render); QFontMetrics fm(painter->font()); int h = fm.height(); painter->setPen(s->color); diff --git a/Renderive/Axis/Axis_p.h b/Renderive/Axis/Axis_p.h index d7005a5..f8d3474 100644 --- a/Renderive/Axis/Axis_p.h +++ b/Renderive/Axis/Axis_p.h @@ -6,7 +6,6 @@ namespace renderive { struct Axis_Render_State : Abs_Axis_Render_State {}; struct Axis_Input_Data : Abs_Axis_Input_Data {}; struct Axis_Private : Typed_Render_Data_Base { - friend class Axis; bool use_wheel = false; bool use_drag = false; bool select_test(const QPointF& pos) override { diff --git a/Renderive/Axis/Frequency_Axis_p.h b/Renderive/Axis/Frequency_Axis_p.h index 3a50e1f..351c98d 100644 --- a/Renderive/Axis/Frequency_Axis_p.h +++ b/Renderive/Axis/Frequency_Axis_p.h @@ -6,7 +6,6 @@ namespace renderive { struct Frequency_Axis_Input_Data : Axis_Input_Data {}; struct Frequency_Axis_Render_State : Axis_Render_State {}; struct Frequency_Axis_Private : Typed_Render_Data_Base { - friend class Frequency_Axis; enum X_Unit_Type { Unknown = -1, Hz = 0, diff --git a/Renderive/Axis/Time_Axis.cpp b/Renderive/Axis/Time_Axis.cpp index 475d2f4..8309f43 100644 --- a/Renderive/Axis/Time_Axis.cpp +++ b/Renderive/Axis/Time_Axis.cpp @@ -21,7 +21,10 @@ int Time_Axis::time_point_size(SRC src) { return d()->state_value(&Time_Axis_Render_State::time_point_size, src); } void Time_Axis::set_time_point_size(int value) { - d()->set_state_value(&Time_Axis_Render_State::time_point_size, qMax(1, value)); + value = qMax(1, value); + if (auto stream = timeline_stream()) + stream->set_time_point_size(value); + d()->set_state_value(&Time_Axis_Render_State::time_point_size, value); } int Time_Axis::tick_pixel_space(SRC src) { return d()->state_value(&Time_Axis_Render_State::tick_pixel_space, src); @@ -41,22 +44,42 @@ QFont Time_Axis::font(SRC src) { void Time_Axis::set_font(QFont value) { d()->set_state_value(&Time_Axis_Render_State::font, std::move(value)); } +std::shared_ptr Time_Axis::timeline_stream(SRC src) { + return d()->state_value(&Time_Axis_Render_State::timeline_stream, src); +} +void Time_Axis::set_timeline_stream(std::shared_ptr value) { + if (value) { + value->set_time_point_size(time_point_size()); + value->set_tick_space(d()->state_data(SRC::Edit)->tick_space); + value->set_start_coord_to_end_coord(start_coord_to_end_coord()); + } + d()->set_state_value(&Time_Axis_Render_State::timeline_stream, std::move(value)); +} +bool Time_Axis::start_coord_to_end_coord(SRC src) { + return d()->state_value(&Time_Axis_Render_State::start_coord_to_end_coord, src); +} +void Time_Axis::set_start_coord_to_end_coord(bool value) { + if (auto stream = timeline_stream()) + stream->set_start_coord_to_end_coord(value); + d()->set_state_value(&Time_Axis_Render_State::start_coord_to_end_coord, value); +} int Time_Axis::give_data(QTime time) { if (!ok()) return -1; - Time_Axis_Private* pd = d(); - std::pair ret = pd->time_ticker.get_new_data_tick_info(); - Render_Input_Lease input(pd); - input->push_time(time); - if (ret.second) { - input->push_tick({time, ret.first}); - } - return ret.first; + auto stream = timeline_stream(); + if (!stream) + return -1; + int tick = stream->push_time(time); + mark_render_dirty(); + return tick; } QTime Time_Axis::tick_to_time(int tick, SRC src) { - auto render_state = d()->state_data(src); - int offset = tick - (int)render_state->coord_start; - return d()->time_by_offset(offset); + if (src != SRC::Render) { + auto stream = timeline_stream(src); + if (stream) + return stream->capture().time_by_tick(tick); + } + return d()->time_by_tick(tick); } Time_Axis::Builder::Builder(std::shared_ptr parent, Qt::Orientation orientation) { ASSERT(parent && parent->plot, "Time_Axis build error! parent invalid"); @@ -72,8 +95,16 @@ std::shared_ptr Time_Axis::Builder::build() { PROP_R(int, tick_pixel_space) PROP_R(QString, time_format) PROP_R(QFont, font) + PROP_R(std::shared_ptr, timeline_stream) + PROP_R(bool, start_coord_to_end_coord) sc->time_point_size = qMax(1, sc->time_point_size); sc->tick_pixel_space = qMax(1, sc->tick_pixel_space); + if (!sc->timeline_stream) + sc->timeline_stream = renderive::make_shared(); + sc->tick_space = sc->get_tick_space_hint(); + sc->timeline_stream->set_time_point_size(sc->time_point_size); + sc->timeline_stream->set_tick_space(sc->tick_space); + sc->timeline_stream->set_start_coord_to_end_coord(sc->start_coord_to_end_coord); return ret; } } // namespace renderive diff --git a/Renderive/Axis/Time_Axis.h b/Renderive/Axis/Time_Axis.h index feb8a65..5dd7060 100644 --- a/Renderive/Axis/Time_Axis.h +++ b/Renderive/Axis/Time_Axis.h @@ -3,7 +3,7 @@ #include namespace renderive { struct Time_Axis_Private; -struct Time_Ticker; +class Timeline_Stream; /// 时间轴,输入 tick 后按格式显示时间标签。 class LIB_DECL Time_Axis : public Abs_Axis { public: @@ -17,6 +17,8 @@ public: PROP(int, tick_pixel_space) PROP(QString, time_format) PROP(QFont, font) + PROP(std::shared_ptr, timeline_stream) + PROP(bool, start_coord_to_end_coord) /// 推入一个新的时间点并返回对应 tick。 int give_data(QTime time); /// 将 tick 转换成时间。 @@ -28,6 +30,8 @@ struct Time_Axis_Prop { int tick_pixel_space = 8; QString time_format = "mm:ss.zzz"; QFont font; + std::shared_ptr timeline_stream; + bool start_coord_to_end_coord = false; }; struct LIB_DECL Time_Axis::Builder : Abs_Axis::Builder_T, public @@ -36,6 +40,8 @@ Time_Axis_Prop { PROP_B(int, tick_pixel_space) PROP_B(QString, time_format) PROP_B(QFont, font) + PROP_B(std::shared_ptr, timeline_stream) + PROP_B(bool, start_coord_to_end_coord) /// 创建时间轴构造器。 Builder(std::shared_ptr parent, Qt::Orientation orientation); /// 构建并返回时间轴对象。 diff --git a/Renderive/Axis/Time_Axis_p.h b/Renderive/Axis/Time_Axis_p.h index 9bf8ef9..422fd64 100644 --- a/Renderive/Axis/Time_Axis_p.h +++ b/Renderive/Axis/Time_Axis_p.h @@ -1,122 +1,15 @@ #pragma once #include -#include -#include -#include +#include #include #include -#include "../architecture/Bounded_Input_Buffer.h" +#include "../architecture/Plot_Render_Context.h" +#include "../architecture/Timeline_Stream.h" #include "../base/Memory.h" #include "Abs_Axis_p.h" -#include "Core/Base/RingBuffer.hpp" #include "Time_Axis.h" namespace renderive { -struct Time_Ticker { - bool start_coord_to_end_coord = true; - // 最小值的范围 lower在这里面循环变化 - int lower = 0, time_point_size{}; - int lower_min{}, lower_max{}; // 闭区间 - int tick_start = 0, tick_space = 4; - [[nodiscard]] int upper() const { - return lower - 1 + time_point_size; - } - void set_tick_space(int tick_space) { - if (this->tick_space == tick_space) - return; - this->tick_space = tick_space; - tick_start = 0; - } - void set_time_point_size(int value) { - if (time_point_size == value) - return; - time_point_size = value; - lower_min = std::numeric_limits::min() / 2; - lower_max = std::numeric_limits::max() / 2 - time_point_size + 1; - tick_start = 0; - } - std::pair get_new_data_tick_info() { - { - // 获取 坐标轴的起点 - if (start_coord_to_end_coord) { - if (lower != lower_min) - lower--; - else { - lower = lower_max; - } - } - else { - if (lower != lower_max) - lower++; - else { - lower = lower_min; - } - } - } - tick_start = ++tick_start % tick_space; - int end_index = lower + time_point_size - 1; - auto start_index = lower; - if (tick_start == 1) { - return {start_coord_to_end_coord ? start_index : end_index, true}; - } - return {start_coord_to_end_coord ? start_index : end_index, false}; - } -}; -struct Time_Tick { - QTime time; - int tick{}; -}; -struct Time_Axis_Input_Data : Abs_Axis_Input_Data { - static constexpr std::size_t Max_Pending_Time_Count = 256; - static constexpr std::size_t Max_Pending_Tick_Count = 256; - Bounded_Input_Buffer data; - Bounded_Input_Buffer all_time; - void push_time(const QTime& time) { - all_time.push(&time, sizeof(time), Max_Pending_Time_Count); - } - void push_tick(const Time_Tick& tick) { - data.push(&tick, sizeof(tick), Max_Pending_Tick_Count); - } - template - void read_all_time(Handler handler) { - all_time.read_all([&handler](const void* data, std::size_t size) { - (void)size; - QTime time; - std::memcpy(&time, data, sizeof(time)); - handler(time); - }); - } - template - void read_all_tick(Handler handler) { - data.read_all([&handler](const void* data, std::size_t size) { - (void)size; - Time_Tick tick; - std::memcpy(&tick, data, sizeof(tick)); - handler(tick); - }); - } - void clear() override { - data.clear(); - all_time.clear(); - } - std::uint64_t get_pending_count() const override { - return data.get_pending_count() + all_time.get_pending_count(); - } - std::uint64_t get_dropped_count() const override { - return data.get_dropped_count() + all_time.get_dropped_count(); - } - std::uint64_t get_capacity_dropped_count() const override { - return data.get_capacity_dropped_count() + all_time.get_capacity_dropped_count(); - } - std::uint64_t rebuild_dropped_count() const override { - return data.get_rebuild_dropped_count() + all_time.get_rebuild_dropped_count(); - } - std::uint64_t consumer_dropped_count() const override { - return data.get_consumer_dropped_count() + all_time.get_consumer_dropped_count(); - } - std::uint64_t get_pushed_count() const override { - return data.get_pushed_count() + all_time.get_pushed_count(); - } -}; +struct Time_Axis_Input_Data : Abs_Axis_Input_Data {}; struct Time_Axis_Render_State : Abs_Axis_Render_State, Time_Axis_Prop { int tick_space = 4; int get_tick_space_hint(SRC src = SRC::Edit) { @@ -127,53 +20,26 @@ struct Time_Axis_Render_State : Abs_Axis_Render_State, Time_Axis_Prop { } }; struct Time_Axis_Private : Typed_Render_Data_Base { - friend class Time_Axis; - Time_Ticker time_ticker{}; + Timeline_Stream_Snapshot timeline_snapshot; + std::pmr::vector time_snapshot{memory_resource(Memory_Domain::Axis)}; + std::pmr::vector data{memory_resource(Memory_Domain::Axis)}; + int time_data_count{}; int tick_pixel_space{}; QFont font; QString time_format; - Psc::StreamRingBuffer_ST buffer{memory_resource(Memory_Domain::Axis)}; - std::pmr::vector time_snapshot{memory_resource(Memory_Domain::Axis)}; - int time_data_count{}; - std::pmr::list data{memory_resource(Memory_Domain::Axis)}; - void resize_time_buffer(int count) { - buffer.init(sizeof(QTime) * count); - time_snapshot.resize(count); - time_data_count = 0; + std::vector ticks; + QTime time_by_tick(int tick) const { + return timeline_snapshot.time_by_tick(tick); } - void push_time(const QTime& time) { - if (time_data_count == time_ticker.time_point_size) { - QTime ignored; - std::size_t len = sizeof(QTime); - buffer.read(&ignored, len); - --time_data_count; - } - buffer.write(&time, sizeof(QTime)); - ++time_data_count; - } - void snapshot_time_buffer() { - buffer.peek_best_effort(time_snapshot.data(), sizeof(QTime) * time_data_count); - } - QTime time_by_offset(int offset) const { - int index = time_ticker.start_coord_to_end_coord ? time_data_count - 1 - offset : offset - (time_ticker.time_point_size - time_data_count); - if (index < 0 || index >= time_data_count) - return {}; - return time_snapshot[index]; - } - void prepare_data(const Plot_Render_Snapshot&) override { + void prepare_data(const Plot_Render_Snapshot& snapshot) override { sync_state_pipeline(); - auto d = render_input_data(); auto s = render_state(); - Triple_Buffer_Lease input_lease = input_control.wait_mark_use_role(Input_Edit); + auto stream = s->timeline_stream; + if (!stream) + return; Triple_Buffer_Lease state_lease = state_control.wait_mark_use_role(State_Edit); auto sc = reinterpret_cast(state_by_index(state_lease.index)); bool refresh_space = false; - if (time_ticker.time_point_size != s->time_point_size) { - time_ticker.set_time_point_size(s->time_point_size); - resize_time_buffer(s->time_point_size); - data.clear(); - refresh_space = true; - } if (tick_pixel_space != s->tick_pixel_space) { tick_pixel_space = s->tick_pixel_space; refresh_space = true; @@ -184,56 +50,41 @@ struct Time_Axis_Private : Typed_Render_Data_Basetime_format) { time_format = s->time_format; - if (s->orientation == Qt::Horizontal) { + if (s->orientation == Qt::Horizontal) refresh_space = true; - } } - if (refresh_space) { + if (refresh_space || s->tick_space <= 0) { s->tick_space = s->get_tick_space_hint(); sc->tick_space = s->tick_space; - if (time_ticker.tick_space != s->tick_space) { - time_ticker.set_tick_space(s->tick_space); - data.resize(0); - } } - s->coord_start = time_ticker.lower; - s->coord_length = time_ticker.time_point_size; - sc->coord_start = time_ticker.lower; - sc->coord_length = time_ticker.time_point_size; - int lower = time_ticker.lower; - int upper = time_ticker.upper(); - if (lower > upper) - std::swap(lower, upper); - d->read_all_time([this](const QTime& time) { - push_time(time); - }); - snapshot_time_buffer(); - d->read_all_tick([this](const Time_Tick& tick) { - data.push_front(tick); - }); - clear_render_input(); - while (!data.empty()) { - int tick = data.back().tick; - if (tick < lower || tick > upper) { - data.pop_back(); - } - else { - break; - } - } - int i = 0; + stream->set_time_point_size(s->time_point_size); + stream->set_tick_space(s->tick_space); + stream->set_start_coord_to_end_coord(s->start_coord_to_end_coord); + auto captured = snapshot.timeline_snapshot(stream); + if (captured) + timeline_snapshot = *captured; + s->coord_start = timeline_snapshot.lower; + s->coord_length = timeline_snapshot.time_point_size; + sc->coord_start = timeline_snapshot.lower; + sc->coord_length = timeline_snapshot.time_point_size; + time_snapshot.clear(); + time_snapshot.resize(static_cast(timeline_snapshot.time_point_size)); + std::copy(timeline_snapshot.times.begin(), timeline_snapshot.times.end(), time_snapshot.begin()); + time_data_count = static_cast(timeline_snapshot.times.size()); + data.assign(timeline_snapshot.ticks.begin(), timeline_snapshot.ticks.end()); ticks.clear(); - labels.clear(); - for (Time_Tick& t : data) { - ticks.push_back(t.tick); - labels.push_back(t.time.toString(s->time_format)); - ++i; + for (const Timeline_Tick& tick : data) { + ticks.push_back(tick.tick); } state_control.unmark_use(state_lease); - input_control.unmark_use(input_lease); } std::vector create_label_vector(const std::vector& ticks, SRC src) override { - return labels; + auto s = reinterpret_cast(state(src)); + std::vector result; + result.reserve(ticks.size()); + for (double tick : ticks) + result.push_back(time_by_tick((int)tick).toString(s->time_format)); + return result; } std::vector create_tick_vector(double tick_step, const Range& range) override { return ticks; @@ -241,11 +92,6 @@ struct Time_Axis_Private : Typed_Render_Data_Base create_sub_tick_vector(int sub_tick_count, const std::vector& ticks, const Range& range) override { return {}; } - // 最小值的范围 lower在这里面循环变化 - int lower = 0, lower_min = 0, lower_max{}; // 闭区间 - int tick_start = 0; - std::vector labels; - std::vector ticks; void draw(QPainter* painter, const Plot_Render_Snapshot& snapshot) override { Abs_Axis_Private::draw(painter, snapshot); } @@ -254,4 +100,4 @@ struct Time_Axis_Private : Typed_Render_Data_Basetick_to_time((int)tick, src).toString(render_state->time_format); } }; -} // namespace renderive +} diff --git a/Renderive/Axis/export.h b/Renderive/Axis/export.h index 41617e8..eec460a 100644 --- a/Renderive/Axis/export.h +++ b/Renderive/Axis/export.h @@ -1,3 +1,4 @@ #pragma once #include "Frequency_Axis.h" +#include "../architecture/Timeline_Stream.h" #include "Time_Axis.h" diff --git a/Renderive/architecture/Bounded_Input_Buffer.h b/Renderive/architecture/Bounded_Input_Buffer.h index 0025f35..a175a71 100644 --- a/Renderive/architecture/Bounded_Input_Buffer.h +++ b/Renderive/architecture/Bounded_Input_Buffer.h @@ -92,8 +92,8 @@ private: std::atomic rebuild_dropped_count{}; std::atomic consumer_dropped_count{}; std::atomic pushed_count{}; - Psc::RingBuffer_ST buffer{memory_resource(Memory_Domain::InputBuffer)}; - std::pmr::vector read_buffer{memory_resource(Memory_Domain::InputBuffer)}; + Psc::RingBuffer_ST buffer{memory_resource(Memory_Domain::Input_Buffer)}; + std::pmr::vector read_buffer{memory_resource(Memory_Domain::Input_Buffer)}; }; class Bounded_Vector_Double_Input_Buffer { public: @@ -149,7 +149,7 @@ private: static constexpr std::size_t Storage_Count = 64; std::pmr::vector& row_at(std::size_t index) { if (!rows[index]) - rows[index].emplace(memory_resource(Memory_Domain::InputBuffer)); + rows[index].emplace(memory_resource(Memory_Domain::Input_Buffer)); return *rows[index]; } void prepare_push(std::size_t max_packet_count) { diff --git a/Renderive/architecture/Frame_Scheduler.cpp b/Renderive/architecture/Frame_Scheduler.cpp index 3d470e3..c690b76 100644 --- a/Renderive/architecture/Frame_Scheduler.cpp +++ b/Renderive/architecture/Frame_Scheduler.cpp @@ -50,7 +50,7 @@ void Frame_Feedback_Controller::on_input_changed(std::uint64_t now_ns, std::uint ++state_value.latest_input_version; update_rates(); } -void Frame_Feedback_Controller::on_render_started(Frame_Metadata& frame, std::uint64_t now_ns) { +void Frame_Feedback_Controller::on_render_started(Frame_Lifecycle_Record& 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; @@ -62,7 +62,7 @@ void Frame_Feedback_Controller::on_render_started(Frame_Metadata& frame, std::ui update_rates(); write_state(frame); } -void Frame_Feedback_Controller::on_render_completed(Frame_Metadata& frame, bool ready_occupied, bool consumer_busy, bool input_pending) { +void Frame_Feedback_Controller::on_render_completed(Frame_Lifecycle_Record& 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; @@ -78,7 +78,7 @@ void Frame_Feedback_Controller::on_render_completed(Frame_Metadata& frame, bool update_control(ready_occupied, consumer_busy, input_pending); write_state(frame); } -void Frame_Feedback_Controller::on_frame_consumed(Frame_Metadata& frame, bool ready_occupied) { +void Frame_Feedback_Controller::on_frame_consumed(Frame_Lifecycle_Record& 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)); @@ -99,7 +99,7 @@ void Frame_Feedback_Controller::on_frame_consumed(Frame_Metadata& frame, bool re 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) { +void Frame_Feedback_Controller::on_frame_superseded(Frame_Lifecycle_Record& 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); @@ -123,10 +123,10 @@ bool Frame_Feedback_Controller::rate_limit_ready(std::uint64_t now_ns) const { std::uint64_t Frame_Feedback_Controller::next_render_time_ns() const { return next_render_time; } -const Frame_Feedback_State& Frame_Feedback_Controller::state() const { +const Frame_Feedback_Snapshot& Frame_Feedback_Controller::state() const { return state_value; } -void Frame_Feedback_Controller::write_state(Frame_Metadata& frame) const { +void Frame_Feedback_Controller::write_state(Frame_Lifecycle_Record& frame) const { frame.feedback = state_value; } void Frame_Feedback_Controller::update_control(bool ready_occupied, bool consumer_busy, bool input_pending) { @@ -246,36 +246,4 @@ double Frame_Feedback_Controller::rate_from_interval(double interval_ms) { 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 index 60173cb..d4fa4bd 100644 --- a/Renderive/architecture/Frame_Scheduler.h +++ b/Renderive/architecture/Frame_Scheduler.h @@ -1,6 +1,7 @@ #pragma once #include #include +#include #include #include "Render_Config.h" namespace renderive { @@ -12,7 +13,8 @@ struct Smoothed_Interval { [[nodiscard]] double safe_ms(double jitter_factor = 2.0) const; [[nodiscard]] bool valid() const; }; -struct Frame_Feedback_State { +// 用于负反馈条件的快照 +struct Frame_Feedback_Snapshot { Bottleneck_State bottleneck_state = Bottleneck_State::Unknown; double manual_min_render_interval_ms{}; double adaptive_min_render_interval_ms{}; @@ -58,13 +60,40 @@ struct Frame_Feedback_State { std::uint64_t superseded_frame_count{}; std::uint64_t repeated_frame_count{}; }; +// 用于每一个 renderable 上报自己的信息 struct Renderable_Cache_Frame_Stat { QString object_name; + QString parent_object_name; + int tree_depth{}; std::uint64_t hit_count{}; std::uint64_t stale_hit_count{}; std::uint64_t miss_count{}; + std::uint64_t rebuild_count{}; + std::uint64_t stale_rebuild_count{}; + std::uint64_t miss_rebuild_count{}; + std::uint64_t rebuild_time_ns{}; + std::uint64_t compose_time_ns{}; + std::uint64_t last_rebuild_time_ns{}; + std::uint64_t last_compose_time_ns{}; + std::uint64_t last_image_bytes{}; + std::uint64_t last_image_area{}; + std::uint64_t last_edit_version{}; + std::uint64_t last_ready_version{}; }; -struct Frame_Metadata { +struct Renderable_Frame_Stat { + QString object_name; + QString parent_object_name; + int tree_depth{}; + bool cache_enabled{}; + std::uint64_t render_count{}; + std::uint64_t self_draw_count{}; + std::uint64_t total_render_time_ns{}; + std::uint64_t self_draw_time_ns{}; + std::uint64_t last_total_render_time_ns{}; + std::uint64_t last_self_draw_time_ns{}; +}; +// 帧生命周期的记录结构 +struct Frame_Lifecycle_Record { std::uint64_t frame_id{}; std::uint64_t input_version{}; std::uint64_t snapshot_version{}; @@ -98,59 +127,179 @@ struct Frame_Metadata { 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 renderable_cache_rebuild_count{}; + std::uint64_t renderable_cache_stale_rebuild_count{}; + std::uint64_t renderable_cache_miss_rebuild_count{}; + std::uint64_t renderable_cache_rebuild_time_ns{}; + std::uint64_t renderable_cache_compose_time_ns{}; + std::uint64_t renderable_cache_image_bytes{}; + std::uint64_t renderable_cache_image_area{}; 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_stats; std::vector renderable_cache_stats; - Frame_Feedback_State feedback; - void record_renderable_cache_hit(const QString& object_name) { + void record_renderable_render(const QString& object_name, const QString& parent_object_name, int tree_depth, Renderable_Cache_Mode cache_mode, std::uint64_t render_time_ns) { + Renderable_Frame_Stat& stat = renderable_stat(object_name, parent_object_name, tree_depth); + stat.cache_enabled = cache_mode == Renderable_Cache_Mode::Local_Pixel; + ++stat.render_count; + stat.total_render_time_ns += render_time_ns; + stat.last_total_render_time_ns = render_time_ns; + } + void record_renderable_self_draw(const QString& object_name, const QString& parent_object_name, int tree_depth, Renderable_Cache_Mode cache_mode, std::uint64_t draw_time_ns) { + Renderable_Frame_Stat& stat = renderable_stat(object_name, parent_object_name, tree_depth); + stat.cache_enabled = cache_mode == Renderable_Cache_Mode::Local_Pixel; + ++stat.self_draw_count; + stat.self_draw_time_ns += draw_time_ns; + stat.last_self_draw_time_ns = draw_time_ns; + } + Frame_Feedback_Snapshot feedback; + void record_renderable_cache_hit(const QString& object_name, const QString& parent_object_name, int tree_depth) { ++renderable_cache_hit_count; - ++cache_stat(object_name).hit_count; + ++cache_stat(object_name, parent_object_name, tree_depth).hit_count; } - void record_renderable_cache_stale_hit(const QString& object_name) { + void record_renderable_cache_stale_hit(const QString& object_name, const QString& parent_object_name, int tree_depth) { ++renderable_cache_stale_hit_count; - ++cache_stat(object_name).stale_hit_count; + ++cache_stat(object_name, parent_object_name, tree_depth).stale_hit_count; } - void record_renderable_cache_miss(const QString& object_name) { + void record_renderable_cache_miss(const QString& object_name, const QString& parent_object_name, int tree_depth) { ++renderable_cache_miss_count; - ++cache_stat(object_name).miss_count; + ++cache_stat(object_name, parent_object_name, tree_depth).miss_count; } - Renderable_Cache_Frame_Stat& cache_stat(const QString& object_name) { - for (auto& stat : renderable_cache_stats) { - if (stat.object_name == object_name) + void record_renderable_cache_rebuild(const QString& object_name, const QString& parent_object_name, int tree_depth, std::uint64_t rebuild_time_ns, std::uint64_t image_bytes, std::uint64_t image_area, std::uint64_t edit_version, std::uint64_t ready_version, bool stale, bool miss) { + ++renderable_cache_rebuild_count; + record_renderable_cache_rebuild_detail(cache_stat(object_name, parent_object_name, tree_depth), rebuild_time_ns, image_bytes, image_area, edit_version, ready_version, stale, miss); + } + void record_renderable_cache_compose(const QString& object_name, const QString& parent_object_name, int tree_depth, std::uint64_t compose_time_ns, std::uint64_t image_bytes, std::uint64_t image_area, std::uint64_t edit_version, std::uint64_t ready_version) { + renderable_cache_compose_time_ns += compose_time_ns; + Renderable_Cache_Frame_Stat& stat = cache_stat(object_name, parent_object_name, tree_depth); + stat.compose_time_ns += compose_time_ns; + stat.last_compose_time_ns = compose_time_ns; + stat.last_image_bytes = image_bytes; + stat.last_image_area = image_area; + stat.last_edit_version = edit_version; + stat.last_ready_version = ready_version; + } + void merge_renderable_cache_stats(const Frame_Lifecycle_Record& other) { + renderable_cache_hit_count += other.renderable_cache_hit_count; + renderable_cache_stale_hit_count += other.renderable_cache_stale_hit_count; + renderable_cache_miss_count += other.renderable_cache_miss_count; + renderable_cache_rebuild_count += other.renderable_cache_rebuild_count; + renderable_cache_stale_rebuild_count += other.renderable_cache_stale_rebuild_count; + renderable_cache_miss_rebuild_count += other.renderable_cache_miss_rebuild_count; + renderable_cache_rebuild_time_ns += other.renderable_cache_rebuild_time_ns; + renderable_cache_compose_time_ns += other.renderable_cache_compose_time_ns; + renderable_cache_image_bytes += other.renderable_cache_image_bytes; + renderable_cache_image_area += other.renderable_cache_image_area; + for (const auto& other_stat : other.renderable_cache_stats) + merge_cache_stat(cache_stat(other_stat.object_name, other_stat.parent_object_name, other_stat.tree_depth), other_stat); + } + void merge_renderable_stats(const Frame_Lifecycle_Record& other) { + for (const auto& other_stat : other.renderable_stats) + merge_renderable_stat(renderable_stat(other_stat.object_name, other_stat.parent_object_name, other_stat.tree_depth), other_stat); + } + Renderable_Frame_Stat& renderable_stat(const QString& object_name, const QString& parent_object_name, int tree_depth) { + for (auto& stat : renderable_stats) { + if (stat.object_name == object_name && stat.parent_object_name == parent_object_name && stat.tree_depth == tree_depth) return stat; } - renderable_cache_stats.push_back({object_name}); + Renderable_Frame_Stat stat; + stat.object_name = object_name; + stat.parent_object_name = parent_object_name; + stat.tree_depth = tree_depth; + renderable_stats.push_back(std::move(stat)); + return renderable_stats.back(); + } + Renderable_Cache_Frame_Stat& cache_stat(const QString& object_name, const QString& parent_object_name, int tree_depth) { + for (auto& stat : renderable_cache_stats) { + if (stat.object_name == object_name && stat.parent_object_name == parent_object_name && stat.tree_depth == tree_depth) + return stat; + } + Renderable_Cache_Frame_Stat stat; + stat.object_name = object_name; + stat.parent_object_name = parent_object_name; + stat.tree_depth = tree_depth; + renderable_cache_stats.push_back(std::move(stat)); return renderable_cache_stats.back(); } +private: + static void merge_renderable_stat(Renderable_Frame_Stat& target, const Renderable_Frame_Stat& source) { + target.parent_object_name = source.parent_object_name; + target.tree_depth = source.tree_depth; + target.cache_enabled = source.cache_enabled; + target.render_count += source.render_count; + target.self_draw_count += source.self_draw_count; + target.total_render_time_ns += source.total_render_time_ns; + target.self_draw_time_ns += source.self_draw_time_ns; + target.last_total_render_time_ns = source.last_total_render_time_ns ? source.last_total_render_time_ns : target.last_total_render_time_ns; + target.last_self_draw_time_ns = source.last_self_draw_time_ns ? source.last_self_draw_time_ns : target.last_self_draw_time_ns; + } + void record_renderable_cache_rebuild_detail(Renderable_Cache_Frame_Stat& stat, std::uint64_t rebuild_time_ns, std::uint64_t image_bytes, std::uint64_t image_area, std::uint64_t edit_version, std::uint64_t ready_version, bool stale, bool miss) { + renderable_cache_rebuild_time_ns += rebuild_time_ns; + renderable_cache_image_bytes += image_bytes; + renderable_cache_image_area += image_area; + if (stale) { + ++renderable_cache_stale_rebuild_count; + ++stat.stale_rebuild_count; + } + if (miss) { + ++renderable_cache_miss_rebuild_count; + ++stat.miss_rebuild_count; + } + ++stat.rebuild_count; + stat.rebuild_time_ns += rebuild_time_ns; + stat.last_rebuild_time_ns = rebuild_time_ns; + stat.last_image_bytes = image_bytes; + stat.last_image_area = image_area; + stat.last_edit_version = edit_version; + stat.last_ready_version = ready_version; + } + static void merge_cache_stat(Renderable_Cache_Frame_Stat& target, const Renderable_Cache_Frame_Stat& source) { + target.parent_object_name = source.parent_object_name; + target.tree_depth = source.tree_depth; + target.hit_count += source.hit_count; + target.stale_hit_count += source.stale_hit_count; + target.miss_count += source.miss_count; + target.rebuild_count += source.rebuild_count; + target.stale_rebuild_count += source.stale_rebuild_count; + target.miss_rebuild_count += source.miss_rebuild_count; + target.rebuild_time_ns += source.rebuild_time_ns; + target.compose_time_ns += source.compose_time_ns; + target.last_rebuild_time_ns = source.last_rebuild_time_ns ? source.last_rebuild_time_ns : target.last_rebuild_time_ns; + target.last_compose_time_ns = source.last_compose_time_ns ? source.last_compose_time_ns : target.last_compose_time_ns; + target.last_image_bytes = source.last_image_bytes ? source.last_image_bytes : target.last_image_bytes; + target.last_image_area = source.last_image_area ? source.last_image_area : target.last_image_area; + target.last_edit_version = source.last_edit_version ? source.last_edit_version : target.last_edit_version; + target.last_ready_version = source.last_ready_version ? source.last_ready_version : target.last_ready_version; + } }; 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_render_started(Frame_Lifecycle_Record& frame, std::uint64_t now_ns); + void on_render_completed(Frame_Lifecycle_Record& frame, bool ready_occupied, bool consumer_busy, bool input_pending); + void on_frame_consumed(Frame_Lifecycle_Record& frame, bool ready_occupied); + void on_frame_superseded(Frame_Lifecycle_Record& 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; + [[nodiscard]] const Frame_Feedback_Snapshot& state() const; + void write_state(Frame_Lifecycle_Record& 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; + [[nodiscard]] 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; + Frame_Feedback_Snapshot state_value; Smoothed_Interval input_interval; Smoothed_Interval render_interval; Smoothed_Interval render_queue_wait; @@ -167,6 +316,4 @@ private: 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 deleted file mode 100644 index 60a1714..0000000 --- a/Renderive/architecture/Plot.cpp +++ /dev/null @@ -1,688 +0,0 @@ -#include -#include -#include -#include "../Axis/Time_Axis.h" -#include "../base/Frame_Memory.h" -#include "../base/Memory.h" -#include "../base/Task.h" -#include "../base/global.h" -#include "../plottable/Performance_Shower_p.h" -#include "Plot_p.h" -#include "Update_Completion.h" -namespace renderive { -namespace { -std::uint64_t steady_now_ns() { - return static_cast(std::chrono::duration_cast(std::chrono::steady_clock::now().time_since_epoch()).count()); -} -int renderable_prepare_priority(const std::shared_ptr& able) { - return dynamic_cast(able.get()) ? 0 : 1; -} -std::shared_ptr performance_shower_owner(Plot_Private* data) { - if (!data || !data->q) - return {}; - auto context = data->q->get_render_context(); - if (!context) - return {}; - std::lock_guard lock(context->mutex); - return context->performance_shower_value; -} -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) - bytes += able->d_ptr->estimated_frame_memory(frame_snapshot); - } - return bytes; -} -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(); -} -Abs_Plot::Abs_Plot() { - d = new Plot_Private(this); - render_context = renderive::make_shared(); - { - std::lock_guard lock(render_context->mutex); - render_context->background_color = d->background; - render_context->plot = this; - } - setAttribute(Qt::WA_OpaquePaintEvent, true); - setMouseTracking(true); - setFocusPolicy(Qt::NoFocus); - Global::instance()->render_scheduler().register_plot(this); -} -Abs_Plot::~Abs_Plot() { - { - std::lock_guard lock(render_context->mutex); - d->destroying.store(true, std::memory_order_release); - render_context->destroying.store(true, std::memory_order_release); - render_context->plot = nullptr; - } - d->render_enabled.store(false, std::memory_order_release); - Global::instance()->render_scheduler().remove_plot(this); - { - std::unique_lock lock(d->render_task_mutex); - d->render_task_done.wait(lock, [this]() { - return d->active_render_tasks.load(std::memory_order_acquire) == 0; - }); - } - 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) - root_renderable->detach_from_plot_tree(); - root_renderable.reset(); - } - { - std::lock_guard lock(render_context->mutex); - render_context->performance_shower_value.reset(); - } - 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; -} -std::shared_ptr Abs_Plot::get_root_renderable() const { - return root_renderable; -} -std::shared_ptr Abs_Plot::create_renderable_node(const std::shared_ptr& parent, const QString& object_name) { - if (!parent || parent->plot != this) { - ASSERT(parent && parent->plot == this, "Abs_Plot create_renderable_node error! parent invalid"); - return {}; - } - auto able = renderive::make_shared(); - able->object_name = object_name; - able->init(parent); - return able; -} -Abs_Plot::Renderable_Snapshot Abs_Plot::renderable_snapshot() const { - std::lock_guard lock(renderable_mutex); - Renderable_Snapshot snapshot; - append_renderable_snapshot(snapshot, root_renderable); - return snapshot; -} -void Abs_Plot::append_renderable_snapshot(Renderable_Snapshot& snapshot, const std::shared_ptr& able) const { - if (!able) - return; - snapshot.push_back(able); - std::vector> children = able->children_snapshot(); - for (const auto& child : children) - append_renderable_snapshot(snapshot, child); -} -std::shared_ptr Abs_Plot::render_snapshot() const { - auto snapshot = renderive::make_shared(); - snapshot->context = render_context; - if (!render_context) - return snapshot; - std::lock_guard lock(render_context->mutex); - snapshot->size = render_context->render_size; - snapshot->background_color = render_context->background_color; - snapshot->refresh_times_per_second = render_context->refresh_times_per_second; - return snapshot; -} -bool Abs_Plot::is_rendering() { - return d->render_enabled.load(std::memory_order_acquire); -} -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); - 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; - Plot_Private* private_data = d; - private_data->active_render_tasks.fetch_add(1, std::memory_order_acq_rel); - 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) - 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)); - 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(); - complete_update_states(presented_update_states, Update_Stage::Presented); - 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); - }); - } - else if (consumed.update_was_pending) { - report_consumer_underrun(); - } -} -void Abs_Plot::render() { - 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); - 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(std::max(0.0, fps), std::memory_order_release); - { - std::lock_guard lock(render_context->mutex); - if (fps > 0.0) - render_context->refresh_times_per_second = std::max(1, qRound(fps)); - } - 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); - }); - frame.snapshot_capture_end_time = steady_now_ns(); - frame_snapshot->frame_metadata = &frame; - std::pmr::vector> frame_update_states(memory_resource(Memory_Domain::UpdateCompletion)); - 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; - } - 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); - } - std::size_t frame_memory_hint = estimate_frame_memory(snapshot, *frame_snapshot); - struct Render_Color_Task_Context { - Plot_Private* private_data{}; - Renderable::Color_Buffer_Lease color_lease; - std::shared_ptr root; - 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{}; - }; - auto task_context = renderive::make_shared(); - task_context->private_data = d; - task_context->color_lease = std::move(color_lease); - task_context->root = root; - 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 = 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); - Global::instance()->render_scheduler().post_cpu([task_context]() { - Plot_Private* private_data = task_context->private_data; - 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.reset(); - notify_render_task_done(private_data); - return; - } - Frame_Arena frame_arena(task_context->frame_memory_hint); - 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; - Global::instance()->render_scheduler().post([plot, private_data]() { - if (!private_data->destroying.load(std::memory_order_acquire)) - plot->finish_render(); - notify_render_task_done(private_data); - }); - }); - return true; -} -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; - if (d->rate_limit_timer_target_ns && d->rate_limit_timer_target_ns <= target_ns) - return; - 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->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); - }); -} -void Abs_Plot::finish_render() { - if (d->destroying.load(std::memory_order_acquire)) - return; - Renderable::Render_Pipeline& pipeline = d->pipeline; - Renderable::Color_Buffer* render_color = pipeline.color_by_role(Color_Render); - 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; - publish_feedback_state(); - if (publish_result.request_update) - request_update(publish_result.region); - try_schedule_render(); -} -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::report_consumer_underrun() { - if (d->destroying.load(std::memory_order_acquire)) - 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]() { - 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::collect_returned_frame(Frame_Metadata& frame) { - auto shower = performance_shower_owner(d); - if (shower && shower->enabled()) - shower->collect_frame(frame); -} -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) { - QMetaObject::invokeMethod(this, [this, region]() { - if (d->destroying.load(std::memory_order_acquire)) - return; - if (region.isEmpty()) - update(); - else - 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; - } - 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 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; - private_data->active_render_tasks.fetch_add(1, std::memory_order_acq_rel); - 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; - } - notify_render_task_done(private_data); - }); - if (first_resize) { - auto snapshot = renderable_snapshot(); - for (const auto& able : snapshot) - able->first_resize_event(resize_event); - } - { - auto snapshot = renderable_snapshot(); - for (const auto& able : snapshot) - able->resizeEvent(resize_event); - } - if (first_resize) - first_resize = false; - render_context->first_resize.store(false, std::memory_order_release); - mark_render_state_dirty(); - break; - } - case QEvent::Show: - start_render(); - break; - case QEvent::Hide: - pause_render(); - break; - case QEvent::Leave: { - auto snapshot = renderable_snapshot(); - for (const auto& able : snapshot) - able->set_hover_state(QPoint(), false); - break; - } - case QEvent::Wheel: { - auto wheel_event = reinterpret_cast(event); - auto snapshot = renderable_snapshot(); - for (const auto& able : snapshot) - able->wheelEvent(wheel_event); - break; - } - case QEvent::MouseMove: { - auto mouse_event = reinterpret_cast(event); - auto snapshot = renderable_snapshot(); - auto root = get_root_renderable(); - auto hits = root ? root->hit_renderables(mouse_event->pos()) : Renderable_Snapshot{}; - for (const auto& able : snapshot) { - if (able) - able->set_hover_state(mouse_event->pos(), false); - } - for (const auto& able : hits) { - if (!able) - continue; - able->set_hover_state(mouse_event->pos(), true); - able->mouseMoveEvent(mouse_event); - } - break; - } - case QEvent::MouseButtonPress: { - auto mouse_event = reinterpret_cast(event); - auto root = get_root_renderable(); - auto hits = root ? root->hit_renderables(mouse_event->pos()) : Renderable_Snapshot{}; - for (const auto& able : hits) - able->mousePressEvent(mouse_event); - break; - } - case QEvent::MouseButtonRelease: { - auto mouse_event = reinterpret_cast(event); - auto root = get_root_renderable(); - auto hits = root ? root->hit_renderables(mouse_event->pos()) : Renderable_Snapshot{}; - for (const auto& able : hits) - able->mouseReleaseEvent(mouse_event); - break; - } - case QEvent::KeyPress: { - auto key_event = reinterpret_cast(event); - auto snapshot = renderable_snapshot(); - for (const auto& able : snapshot) - able->keyPressEvent(key_event); - break; - } - case QEvent::KeyRelease: { - auto key_event = reinterpret_cast(event); - auto snapshot = renderable_snapshot(); - for (const auto& able : snapshot) - able->keyReleaseEvent(key_event); - break; - } - default: - break; - } - auto snapshot = renderable_snapshot(); - for (const auto& able : snapshot) - able->plot_event(event); - return ret; -} -Plot::Plot() = default; -} // namespace renderive diff --git a/Renderive/architecture/Plot.h b/Renderive/architecture/Plot.h deleted file mode 100644 index df797f8..0000000 --- a/Renderive/architecture/Plot.h +++ /dev/null @@ -1,72 +0,0 @@ -#pragma once -#include -#include -#include -#include "Plot_Render_Context.h" -#include "Renderable.h" -namespace renderive { -void LIB_DECL start_render_scheduler(); -class Render_Scheduler; -struct Plot_Private; -class Performance_Shower; -class LIB_DECL Abs_Plot : public QWidget { - Q_OBJECT -public: - bool get_use_performance_shower(); - void set_use_performance_shower(bool use); - 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; - bool first_prepare_data = true; - void set_background_color(const QColor& color) const; - Plot_Render_Schedule_Mode get_render_schedule_mode() const; - void set_render_schedule_mode(Plot_Render_Schedule_Mode mode); - double get_max_render_fps() const; - void set_max_render_fps(double fps); - Frame_Feedback_State get_frame_feedback_state() const; - void mark_render_state_dirty(); - void remove_renderable(const std::shared_ptr& able); - QString object_name; - virtual void init_renderable_tree(); - bool is_rendering(); - void start_render(int max_render_fps); - void start_render() const; - void pause_render() const; - Abs_Plot(); - ~Abs_Plot() override; - Plot_Private* d{}; -private: - std::shared_ptr render_context; - std::shared_ptr root_renderable; - mutable std::mutex renderable_mutex; - using Renderable_Snapshot = std::vector>; - bool event(QEvent* event) final; - 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 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; -}; -class LIB_DECL Plot : public Abs_Plot { -public: - Plot(); - ~Plot() override = default; -}; -} // namespace renderive diff --git a/Renderive/architecture/Plot_Render_Context.cpp b/Renderive/architecture/Plot_Render_Context.cpp new file mode 100644 index 0000000..890c47a --- /dev/null +++ b/Renderive/architecture/Plot_Render_Context.cpp @@ -0,0 +1,17 @@ +#include "Plot_Render_Context.h" +#include "Timeline_Stream.h" +namespace renderive { +std::shared_ptr Plot_Render_Snapshot::timeline_snapshot(const std::shared_ptr& stream) const { + if (!stream) + return {}; + if (!timeline_cache) + timeline_cache = std::make_shared(); + for (auto& item : timeline_cache->items) { + if (item.stream.lock() == stream) + return item.snapshot; + } + auto snapshot = std::make_shared(stream->capture()); + timeline_cache->items.push_back({stream, snapshot}); + return snapshot; +} +} diff --git a/Renderive/architecture/Plot_Render_Context.h b/Renderive/architecture/Plot_Render_Context.h index f09bc29..0cfc3fd 100644 --- a/Renderive/architecture/Plot_Render_Context.h +++ b/Renderive/architecture/Plot_Render_Context.h @@ -3,30 +3,41 @@ #include #include #include -#include +#include #include "Frame_Scheduler.h" namespace renderive { class Abs_Plot; class Performance_Shower; +class Timeline_Stream; +struct Timeline_Stream_Snapshot; +struct Timeline_Stream_Cache_Item { + std::weak_ptr stream; + std::shared_ptr snapshot; +}; +struct Timeline_Stream_Frame_Cache { + std::vector items; +}; struct Plot_Render_Context { std::atomic_bool destroying{false}; std::atomic_bool first_resize{true}; std::atomic_bool first_prepare_data{true}; - mutable std::mutex mutex; - QSize render_size; - QColor background_color = Qt::black; - int refresh_times_per_second = 30; + std::atomic_int render_width{0}; + std::atomic_int render_height{0}; + std::atomic background_rgba{0xff000000u}; + std::atomic_int refresh_times_per_second{30}; std::shared_ptr performance_shower_value; - Abs_Plot* plot{}; + std::atomic plot{}; }; struct Plot_Render_Snapshot { std::shared_ptr context; QSize size; QColor background_color = Qt::black; int refresh_times_per_second = 30; - Frame_Metadata* frame_metadata{}; + Frame_Lifecycle_Record* frame_metadata{}; + mutable std::shared_ptr timeline_cache = std::make_shared(); bool destroying() const { return !context || context->destroying.load(std::memory_order_acquire); } + std::shared_ptr timeline_snapshot(const std::shared_ptr& stream) const; }; } // namespace renderive diff --git a/Renderive/architecture/Plot_p.h b/Renderive/architecture/Plot_p.h deleted file mode 100644 index 97ef66f..0000000 --- a/Renderive/architecture/Plot_p.h +++ /dev/null @@ -1,38 +0,0 @@ -#pragma once -#include -#include -#include -#include -#include -#include "Plot.h" -#include "asio.hpp" -namespace renderive { -struct Plot_Private { - explicit Plot_Private(Abs_Plot* plot) : q(plot) {} - Abs_Plot* q{}; - 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::Feedback}; - std::atomic max_render_fps{0.0}; - std::atomic_bool render_request_posted{false}; - 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 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; - 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 7e46b40..9b4ebf5 100644 --- a/Renderive/architecture/Render_Config.h +++ b/Renderive/architecture/Render_Config.h @@ -1,26 +1,15 @@ #pragma once #include namespace renderive { -enum class Plot_Render_Schedule_Mode : std::uint8_t { - Feedback, - Explicit_Only -}; +// 系统受限状态 enum class Bottleneck_State : std::uint8_t { - Unknown, - Input_Limited, - Producer_Limited, - Consumer_Limited, - Manual_Limited, + 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, diff --git a/Renderive/architecture/Render_Data.cpp b/Renderive/architecture/Render_Data.cpp index fd7df70..b2bab65 100644 --- a/Renderive/architecture/Render_Data.cpp +++ b/Renderive/architecture/Render_Data.cpp @@ -1,4 +1,4 @@ -#include "Renderable_Core.h" +#include "Renderable.h" namespace renderive { QDebug operator<<(QDebug debug, const Range& range) { QDebugStateSaver saver(debug); diff --git a/Renderive/architecture/Render_Data.h b/Renderive/architecture/Render_Data.h index b749e5e..0b25384 100644 --- a/Renderive/architecture/Render_Data.h +++ b/Renderive/architecture/Render_Data.h @@ -28,22 +28,22 @@ struct Renderable; struct Input_Data { virtual ~Input_Data() = default; virtual void clear() {} - virtual std::uint64_t get_pending_count() const { + [[nodiscard]] virtual std::uint64_t get_pending_count() const { return 0; } - virtual std::uint64_t get_dropped_count() const { + [[nodiscard]] virtual std::uint64_t get_dropped_count() const { return 0; } - virtual std::uint64_t get_capacity_dropped_count() const { + [[nodiscard]] virtual std::uint64_t get_capacity_dropped_count() const { return 0; } - virtual std::uint64_t rebuild_dropped_count() const { + [[nodiscard]] virtual std::uint64_t rebuild_dropped_count() const { return 0; } - virtual std::uint64_t consumer_dropped_count() const { + [[nodiscard]] virtual std::uint64_t consumer_dropped_count() const { return 0; } - virtual std::uint64_t get_pushed_count() const { + [[nodiscard]] virtual std::uint64_t get_pushed_count() const { return 0; } }; @@ -127,7 +127,7 @@ struct Render_Data { Triple_Buffer_Stats input_buffer_stats; std::array input_buffers{}; std::array input_buffer_owners{}; - std::pmr::vector> committed_update_states{memory_resource(Memory_Domain::UpdateCompletion)}; + std::pmr::vector> committed_update_states{memory_resource(Memory_Domain::Update_Completion)}; std::uint64_t input_version = 0; Render_State* state(std::uint8_t role) const { return state_by_index(state_control.read_view()[role]); diff --git a/Renderive/architecture/Render_Lease.h b/Renderive/architecture/Render_Lease.h index 761b1eb..a3497eb 100644 --- a/Renderive/architecture/Render_Lease.h +++ b/Renderive/architecture/Render_Lease.h @@ -2,7 +2,7 @@ #include #include #include -#include "Renderable_Core.h" +#include "Renderable.h" namespace renderive { template concept Render_State_Type = std::derived_from && std::is_copy_assignable_v; diff --git a/Renderive/architecture/Render_Pipeline.cpp b/Renderive/architecture/Render_Pipeline.cpp index 4194d93..b1da885 100644 --- a/Renderive/architecture/Render_Pipeline.cpp +++ b/Renderive/architecture/Render_Pipeline.cpp @@ -1,7 +1,7 @@ -#include "Renderable_Core.h" +#include "Renderable.h" #include "Update_Completion.h" namespace renderive { -Renderable::Color_Buffer::Color_Buffer() : presented_update_states(memory_resource(Memory_Domain::UpdateCompletion)) {} +Renderable::Frame::Frame() : presented_update_states(memory_resource(Memory_Domain::Update_Completion)) {} Renderable::Render_Pipeline::Render_Pipeline() { color_control.set_stats(&color_buffer_stats); color_control.reset(); @@ -12,25 +12,25 @@ void Renderable::Render_Pipeline::mark_edit_state() { bool Renderable::Render_Pipeline::has_new_state() const { return Renderable::version_newer(edit_state_version.load(std::memory_order_acquire), render_state_version); } -Renderable::Color_Buffer* Renderable::Render_Pipeline::color_by_index(std::uint8_t index) { +Renderable::Frame* 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 { +const Renderable::Frame* 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) { +Renderable::Frame* Renderable::Render_Pipeline::color_by_role(std::uint8_t role) { return color_by_index(color_control.read_view()[role]); } -const Renderable::Color_Buffer* Renderable::Render_Pipeline::color_by_role(std::uint8_t role) const { +const Renderable::Frame* 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::try_render_color() { +Renderable::Frame_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() { +Renderable::Frame_Lease Renderable::Render_Pipeline::try_front_color() { auto record = color_control.try_mark_use_role(Color_Front); if (record.result != Triple_Buffer_Result::Success) return {}; @@ -38,9 +38,9 @@ Renderable::Color_Buffer_Lease Renderable::Render_Pipeline::try_front_color() { } 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); + Frame* render = color_by_role(Color_Render); + Frame* old_ready = color_by_role(Color_Ready); + const Frame* 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); @@ -50,8 +50,8 @@ Renderable::Render_Pipeline::Frame_Publish_Result Renderable::Render_Pipeline::p 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]); + const Frame* render_buffer = color_by_index(view[Color_Render]); + const Frame* 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)); }); if (replace_ready && swap.result == Triple_Buffer_Result::Success) @@ -62,8 +62,8 @@ Renderable::Render_Pipeline::Frame_Publish_Result Renderable::Render_Pipeline::p } 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); + Frame* ready = color_by_role(Color_Ready); + const Frame* 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))) @@ -88,23 +88,23 @@ Renderable::Render_Pipeline::Frame_Consume_Result Renderable::Render_Pipeline::c 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) { - const Color_Buffer* ready = color_by_index(view[Color_Ready]); - const Color_Buffer* front = color_by_index(view[Color_Front]); + const Frame* ready = color_by_index(view[Color_Ready]); + const Frame* 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)); }; 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; + result.lease.frame->state.store(Frame_State::Consuming, std::memory_order_release); + result.update_request_id = result.lease.frame->metadata.update_request_id; + result.update_request_time = result.lease.frame->metadata.update_request_time; } 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); + const Frame* ready = color_by_role(Color_Ready); + const Frame* 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 { diff --git a/Renderive/architecture/Render_Utils.cpp b/Renderive/architecture/Render_Utils.cpp index 6797b1f..62a8bf5 100644 --- a/Renderive/architecture/Render_Utils.cpp +++ b/Renderive/architecture/Render_Utils.cpp @@ -1,6 +1,6 @@ #include #include -#include "Renderable_Core.h" +#include "Renderable.h" #include "psc_global_include/Fail_Fast.h" #include "psc_global_include/export.h" namespace renderive { diff --git a/Renderive/architecture/Renderable.cpp b/Renderive/architecture/Renderable.cpp index df49f2e..3cc3920 100644 --- a/Renderive/architecture/Renderable.cpp +++ b/Renderive/architecture/Renderable.cpp @@ -1,29 +1,34 @@ #include +#include #include #include "../base/Memory.h" -#include "../base/global.h" -#include "Plot.h" -#include "Renderable_Core.h" +#include "../plot/Plot.h" +#include "Renderable.h" namespace renderive { -Renderable::Color_Buffer_Lease::Color_Buffer_Lease(Color_Buffer* buffer, Triple_Role_Buffer_Control* control, Triple_Buffer_Lease lease) : buffer(buffer), control(control), lease(lease) {} -Renderable::Color_Buffer_Lease::Color_Buffer_Lease(Color_Buffer_Lease&& other) noexcept : buffer(std::exchange(other.buffer, nullptr)), control(std::exchange(other.control, nullptr)), lease(std::exchange(other.lease, Triple_Buffer_Lease{})) {} -Renderable::Color_Buffer_Lease& Renderable::Color_Buffer_Lease::operator=(Color_Buffer_Lease&& other) noexcept { +namespace { +std::uint64_t steady_now_ns() { + return static_cast(std::chrono::duration_cast(std::chrono::steady_clock::now().time_since_epoch()).count()); +} +} +Renderable::Frame_Lease::Frame_Lease(Frame* buffer, Triple_Role_Buffer_Control* control, Triple_Buffer_Lease lease) : frame(buffer), control(control), lease(lease) {} +Renderable::Frame_Lease::Frame_Lease(Frame_Lease&& other) noexcept : frame(std::exchange(other.frame, nullptr)), control(std::exchange(other.control, nullptr)), lease(std::exchange(other.lease, Triple_Buffer_Lease{})) {} +Renderable::Frame_Lease& Renderable::Frame_Lease::operator=(Frame_Lease&& other) noexcept { if (this == &other) return *this; reset(); - buffer = std::exchange(other.buffer, nullptr); + frame = std::exchange(other.frame, nullptr); control = std::exchange(other.control, nullptr); lease = std::exchange(other.lease, Triple_Buffer_Lease{}); return *this; } -Renderable::Color_Buffer_Lease::~Color_Buffer_Lease() { +Renderable::Frame_Lease::~Frame_Lease() { reset(); } -void Renderable::Color_Buffer_Lease::reset() { +void Renderable::Frame_Lease::reset() { if (!control) return; control->unmark_use(lease); - buffer = nullptr; + frame = nullptr; control = nullptr; lease = {}; } @@ -35,14 +40,65 @@ void Renderable::set_cache_mode(Renderable_Cache_Mode mode) { cache_dirty.store(true, std::memory_order_release); mark_render_dirty(); } +Renderable::Cache_Status Renderable::cache_status(const Plot_Render_Snapshot& snapshot) { + Cache_Status status; + QSize size = snapshot.size; + status.bounds = d_ptr ? d_ptr->pixel_bounds(size).intersected(QRect(QPoint(0, 0), size)) : QRect(QPoint(0, 0), size); + if (status.bounds.isEmpty()) + return status; + status.valid = true; + status.edit_version = cache_edit_version.load(std::memory_order_acquire); + status.ready_version = cache_ready_version.load(std::memory_order_acquire); + status.image_bytes = cache_image.isNull() ? 0 : static_cast(cache_image.sizeInBytes()); + status.image_area = static_cast(cache_bounds.width()) * static_cast(cache_bounds.height()); + bool shape_ready = cache_image.size() == status.bounds.size() && cache_image.format() == QImage::Format_ARGB32 && cache_bounds == status.bounds; + status.exact_hit = !cache_dirty.load(std::memory_order_acquire) && status.ready_version == status.edit_version && shape_ready; + status.stale_hit = !status.exact_hit && !cache_image.isNull() && cache_bounds == status.bounds; + status.miss = !status.exact_hit && !status.stale_hit; + return status; +} +void Renderable::rebuild_cache_image(const QRect& bounds, const Plot_Render_Snapshot& snapshot, std::uint64_t version) { + render_cache_image(bounds, snapshot, version); +} +QString Renderable::cache_parent_object_name() const { + std::shared_ptr parent = parent_renderable.lock(); + while (parent) { + if (parent->get_cache_mode() == Renderable_Cache_Mode::Local_Pixel) + return parent->object_name; + parent = parent->parent_renderable.lock(); + } + return {}; +} +int Renderable::cache_tree_depth() const { + int depth = 0; + std::shared_ptr parent = parent_renderable.lock(); + while (parent) { + if (parent->get_cache_mode() == Renderable_Cache_Mode::Local_Pixel) + ++depth; + parent = parent->parent_renderable.lock(); + } + return depth; +} std::uint64_t Renderable::cache_image_bytes() const { - std::lock_guard lock(cache_mutex); return cache_image.isNull() ? 0 : static_cast(cache_image.sizeInBytes()); } std::uint64_t Renderable::cache_image_area() const { - std::lock_guard lock(cache_mutex); return static_cast(cache_bounds.width()) * static_cast(cache_bounds.height()); } +QString Renderable::render_parent_object_name() const { + if (auto parent = parent_renderable.lock()) + return parent->object_name; + return {}; +} +int Renderable::render_tree_depth() const { + int depth = 0; + std::shared_ptr parent = parent_renderable.lock(); + while (parent) { + ++depth; + parent = parent->parent_renderable.lock(); + } + return depth; +} void Renderable::init_root(Abs_Plot* plot) { if (!plot) { ASSERT(plot, "Renderable root init error! plot == nullptr"); @@ -143,10 +199,14 @@ void Renderable::append_hit_renderables(std::vector> void Renderable::render(QPainter* painter, const Plot_Render_Snapshot& snapshot) { if (get_retiring() || !visible) return; - if (get_cache_mode() == Renderable_Cache_Mode::Local_Pixel) + Renderable_Cache_Mode mode = get_cache_mode(); + std::uint64_t begin_time = steady_now_ns(); + if (mode == Renderable_Cache_Mode::Local_Pixel) render_cached(painter, snapshot); else render_direct(painter, snapshot); + if (snapshot.frame_metadata) + snapshot.frame_metadata->record_renderable_render(object_name, render_parent_object_name(), render_tree_depth(), mode, steady_now_ns() - begin_time); } void Renderable::render_direct(QPainter* painter, const Plot_Render_Snapshot& snapshot) { render_self(painter, snapshot); @@ -165,34 +225,35 @@ void Renderable::render_cached(QPainter* painter, const Plot_Render_Snapshot& sn if (bounds.isEmpty()) return; std::uint64_t edit_version = cache_edit_version.load(std::memory_order_acquire); - { - 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; - } + QString parent_object_name = cache_parent_object_name(); + int tree_depth = cache_tree_depth(); + 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, parent_object_name, tree_depth); + std::uint64_t begin_time = steady_now_ns(); + painter->drawImage(bounds.topLeft(), cache_image.image()); + if (snapshot.frame_metadata) + snapshot.frame_metadata->record_renderable_cache_compose(object_name, parent_object_name, tree_depth, steady_now_ns() - begin_time, static_cast(cache_image.sizeInBytes()), static_cast(bounds.width()) * static_cast(bounds.height()), edit_version, cache_ready_version.load(std::memory_order_acquire)); + return; } if (snapshot.frame_metadata) - snapshot.frame_metadata->record_renderable_cache_miss(object_name); + snapshot.frame_metadata->record_renderable_cache_miss(object_name, parent_object_name, tree_depth); + std::uint64_t rebuild_begin_time = steady_now_ns(); render_cache_image(bounds, snapshot, edit_version); - std::lock_guard lock(cache_mutex); - if (!cache_image.isNull()) + if (snapshot.frame_metadata) + snapshot.frame_metadata->record_renderable_cache_rebuild(object_name, parent_object_name, tree_depth, steady_now_ns() - rebuild_begin_time, cache_image.isNull() ? 0 : static_cast(cache_image.sizeInBytes()), static_cast(bounds.width()) * static_cast(bounds.height()), edit_version, cache_ready_version.load(std::memory_order_acquire), false, true); + if (!cache_image.isNull()) { + std::uint64_t begin_time = steady_now_ns(); painter->drawImage(bounds.topLeft(), cache_image.image()); + if (snapshot.frame_metadata) + snapshot.frame_metadata->record_renderable_cache_compose(object_name, parent_object_name, tree_depth, steady_now_ns() - begin_time, static_cast(cache_image.sizeInBytes()), static_cast(bounds.width()) * static_cast(bounds.height()), edit_version, cache_ready_version.load(std::memory_order_acquire)); + } } void Renderable::render_cache_image(const QRect& bounds, const Plot_Render_Snapshot& snapshot, std::uint64_t version) { if (get_retiring()) return; - Pmr_QImage_Buffer cache_image(Memory_Domain::RenderableCacheImage); + Pmr_QImage_Buffer cache_image(Memory_Domain::Renderable_Cache_Image); cache_image.resize(bounds.size(), QImage::Format_ARGB32); cache_image.fill(Qt::transparent); { @@ -201,42 +262,11 @@ void Renderable::render_cache_image(const QRect& bounds, const Plot_Render_Snaps render_direct(&cache_painter, snapshot); cache_painter.end(); } - { - std::lock_guard lock(cache_mutex); - cache_bounds = bounds; - this->cache_image = std::move(cache_image); - cache_ready_version.store(version, std::memory_order_release); - bool dirty = cache_edit_version.load(std::memory_order_acquire) != version; - cache_dirty.store(dirty, std::memory_order_release); - } -} -void Renderable::schedule_cache_render(const QRect& bounds, const Plot_Render_Snapshot& snapshot, std::uint64_t version) { - if (cache_render_queued.exchange(true, std::memory_order_acq_rel)) - return; - struct Cache_Render_Task_Context { - std::shared_ptr self; - std::shared_ptr snapshot_owner; - QRect bounds; - std::uint64_t version{}; - }; - 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]() { - auto& self = task_context->self; - auto& snapshot_owner = task_context->snapshot_owner; - if (self->get_retiring() || !snapshot_owner || snapshot_owner->destroying()) { - self->cache_render_queued.store(false, std::memory_order_release); - return; - } - self->render_cache_image(task_context->bounds, *snapshot_owner, task_context->version); - self->cache_render_queued.store(false, std::memory_order_release); - if (!self->get_retiring()) - self->notify_parent_render_dirty(); - }); + cache_bounds = bounds; + this->cache_image = std::move(cache_image); + cache_ready_version.store(version, std::memory_order_release); + bool dirty = cache_edit_version.load(std::memory_order_acquire) != version; + cache_dirty.store(dirty, std::memory_order_release); } void Renderable::notify_parent_render_dirty() { if (get_retiring()) @@ -248,9 +278,9 @@ void Renderable::notify_parent_render_dirty() { parent->mark_render_dirty(); return; } - std::lock_guard lock(context->mutex); - if (!context->destroying.load(std::memory_order_acquire) && context->plot) - context->plot->mark_render_state_dirty(); + Abs_Plot* target_plot = context->plot.load(std::memory_order_acquire); + if (!context->destroying.load(std::memory_order_acquire) && target_plot) + target_plot->mark_render_state_dirty(); } void Renderable::render_self(QPainter* painter, const Plot_Render_Snapshot& snapshot) { if (!d_ptr) @@ -258,8 +288,13 @@ void Renderable::render_self(QPainter* painter, const Plot_Render_Snapshot& snap auto lease = d_ptr->mark_state_role(State_Render); if (!lease) return; + Renderable_Cache_Mode mode = get_cache_mode(); + std::uint64_t begin_time = steady_now_ns(); draw(painter, snapshot); + std::uint64_t draw_time = steady_now_ns() - begin_time; d_ptr->unmark_state(lease); + if (snapshot.frame_metadata) + snapshot.frame_metadata->record_renderable_self_draw(object_name, render_parent_object_name(), render_tree_depth(), mode, draw_time); } void Renderable::draw(QPainter* painter, const Plot_Render_Snapshot& snapshot) { if (get_retiring()) @@ -351,9 +386,9 @@ void Renderable::mark_render_dirty() { parent->mark_render_dirty(); return; } - std::lock_guard lock(context->mutex); - if (!context->destroying.load(std::memory_order_acquire) && context->plot) - context->plot->mark_render_state_dirty(); + Abs_Plot* target_plot = context->plot.load(std::memory_order_acquire); + if (!context->destroying.load(std::memory_order_acquire) && target_plot) + target_plot->mark_render_state_dirty(); } bool Renderable::ok() const { auto context = plot_context.lock(); @@ -376,12 +411,8 @@ void Renderable::detach_from_plot_tree() { child->detach_from_plot_tree(); } plot = nullptr; - cache_render_queued.store(false, std::memory_order_release); - { - std::lock_guard lock(cache_mutex); - cache_image.clear(); - cache_bounds = {}; - } + cache_image.clear(); + cache_bounds = {}; } Renderable::~Renderable() { detach_from_plot_tree(); diff --git a/Renderive/architecture/Renderable.h b/Renderive/architecture/Renderable.h index 811f7ee..ec959f6 100644 --- a/Renderive/architecture/Renderable.h +++ b/Renderive/architecture/Renderable.h @@ -1,2 +1,197 @@ #pragma once -#include "Render_Lease.h" +#include +#include +#include +#include +#include +#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 Renderable : std::enable_shared_from_this { + /// 后台渲染任务状态。 + enum class Job_State { + /// 没有任务运行或排队。 + Idle, + /// 已经进入调度队列。 + Queued, + /// CPU 渲染任务正在执行。 + Running + }; + /// Plot 级颜色三缓冲中的一个物理颜色缓冲。 + struct Frame { + Frame(); + std::atomic version{0}; + Pmr_QImage_Buffer image{Memory_Domain::Plot_Frame}; + QRegion dirty_region; + std::pmr::vector> presented_update_states; + std::atomic state{Frame_State::Free}; + std::uint64_t generation{}; + Frame_Lifecycle_Record metadata; + }; + /// 颜色缓冲占用租约,析构时自动释放底层 busy 标记。 + struct Frame_Lease : Move_Only { + Frame_Lease() = default; + Frame_Lease(Frame* buffer, Triple_Role_Buffer_Control* control, Triple_Buffer_Lease lease); + Frame_Lease(Frame_Lease&& other) noexcept; + Frame_Lease& operator=(Frame_Lease&& other) noexcept; + ~Frame_Lease(); + /// 提前释放当前租约。 + void reset(); + Frame* frame{}; + Triple_Role_Buffer_Control* control{}; + Triple_Buffer_Lease lease{}; + /// 判断当前租约是否持有有效颜色缓冲。 + explicit operator bool() const { + return frame && lease; + } + }; + /// 单个 Plot 的颜色三缓冲管线。 + struct Render_Pipeline { + struct Frame_Publish_Result { + QRegion region; + Frame* superseded_ready{}; + std::uint64_t update_request_id{}; + std::uint64_t update_request_time{}; + bool request_update{}; + }; + struct Frame_Consume_Result { + Frame_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; + std::atomic edit_state_version{1}; + 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; + Frame* color_by_index(std::uint8_t index); + const Frame* color_by_index(std::uint8_t index) const; + Frame* color_by_role(std::uint8_t role); + const Frame* color_by_role(std::uint8_t role) const; + Frame_Lease try_render_color(); + Frame_Lease try_front_color(); + 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; + }; + struct Cache_Status { + QRect bounds; + std::uint64_t edit_version{}; + std::uint64_t ready_version{}; + std::uint64_t image_bytes{}; + std::uint64_t image_area{}; + bool valid{}; + bool exact_hit{}; + bool stale_hit{}; + bool miss{}; + }; + /// 创建派生对象的私有渲染数据。 + virtual Render_Object_Owner create_render_data() { + return make_render_object(); + } + /// 创建派生对象的 State 缓冲实体。 + virtual Render_Object_Owner create_render_state() { + return make_render_object(); + } + /// 创建派生对象的 Input 缓冲实体。 + virtual Render_Object_Owner create_input_data() { + return make_render_object(); + } + /// 把对象挂接到 parent renderable。 + virtual void init(const std::shared_ptr& parent); + /// 初始化 Plot 根节点。 + virtual void init_root(Abs_Plot* plot); + void add_child(const std::shared_ptr& child); + void remove_child(const std::shared_ptr& child); + std::vector> children_snapshot() const; + std::vector> hit_renderables(const QPointF& pos); + void render(QPainter* painter, const Plot_Render_Snapshot& snapshot); + /// 绘制已准备好的 Render_Data。 + virtual void draw(QPainter* painter, const Plot_Render_Snapshot& snapshot); + /// 在 CPU render 阶段准备渲染数据。 + virtual void prepare_data(const Plot_Render_Snapshot& snapshot); + /// 判断指定 Plot 坐标是否命中当前对象。 + virtual bool select_test(const QPointF& pos); + /// 转发 Qt 事件给私有渲染数据。 + virtual void plot_event(QEvent* event); + virtual void mousePressEvent(QMouseEvent* event); + virtual void mouseMoveEvent(QMouseEvent* event); + virtual void mouseReleaseEvent(QMouseEvent* event); + virtual void set_hover_state(const QPoint& pos, bool active); + virtual void keyPressEvent(QKeyEvent* event); + virtual void keyReleaseEvent(QKeyEvent* event); + virtual void first_resize_event(QResizeEvent* event); + virtual void resizeEvent(QResizeEvent* event); + virtual void wheelEvent(QWheelEvent* event); + /// 按三缓冲版本号环绕语义判断 a 是否比 b 新。 + static bool version_newer(std::uint64_t a, std::uint64_t b); + /// 当前 Renderable 输出缓存策略。 + Renderable_Cache_Mode get_cache_mode() const; + void set_cache_mode(Renderable_Cache_Mode mode); + Cache_Status cache_status(const Plot_Render_Snapshot& snapshot); + void rebuild_cache_image(const QRect& bounds, const Plot_Render_Snapshot& snapshot, std::uint64_t version); + QString cache_parent_object_name() const; + int cache_tree_depth() const; + std::uint64_t cache_image_bytes() const; + std::uint64_t cache_image_area() const; + QString render_parent_object_name() const; + int render_tree_depth() const; + /// 标记 State 缓冲有新版本。 + void mark_state_dirty(); + /// 请求父 Plot 调度一次渲染。 + void mark_render_dirty(); + QString object_name; + Render_Object_Owner d_owner; + Render_Data* d_ptr{}; + Abs_Plot* plot{}; + std::weak_ptr parent_renderable; + [[nodiscard]] bool ok() const; + [[nodiscard]] bool get_retiring() const; + bool prepared = false; + virtual ~Renderable(); + virtual bool select(QPointF pos) { + return false; + } + bool visible = true; + std::vector> children; + mutable std::mutex children_mutex; + std::weak_ptr plot_context; + std::atomic cache_mode{Renderable_Cache_Mode::Direct}; + std::atomic_bool cache_dirty{true}; + std::atomic cache_edit_version{1}; + std::atomic cache_ready_version{0}; + Pmr_QImage_Buffer cache_image{Memory_Domain::Renderable_Cache_Image}; + QRect cache_bounds; + std::atomic_bool retiring{true}; + std::atomic_bool initialized{false}; + void init_common(Abs_Plot* plot); + void render_direct(QPainter* painter, const Plot_Render_Snapshot& snapshot); + void render_cached(QPainter* painter, const Plot_Render_Snapshot& snapshot); + void render_self(QPainter* painter, const Plot_Render_Snapshot& snapshot); + void render_children(QPainter* painter, const Plot_Render_Snapshot& snapshot); + void render_cache_image(const QRect& bounds, const Plot_Render_Snapshot& snapshot, std::uint64_t version); + void notify_parent_render_dirty(); + void append_hit_renderables(std::vector>& hits, const QPointF& pos); + void detach_from_plot_tree(); +}; +} // namespace renderive diff --git a/Renderive/architecture/Renderable_Core.h b/Renderive/architecture/Renderable_Core.h deleted file mode 100644 index 97ea830..0000000 --- a/Renderive/architecture/Renderable_Core.h +++ /dev/null @@ -1,184 +0,0 @@ -#pragma once -#include -#include -#include -#include -#include -#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 Renderable : std::enable_shared_from_this { - /// 后台渲染任务状态。 - enum class Job_State { - /// 没有任务运行或排队。 - Idle, - /// 已经进入调度队列。 - Queued, - /// CPU 渲染任务正在执行。 - Running - }; - /// Plot 级颜色三缓冲中的一个物理颜色缓冲。 - struct Color_Buffer { - Color_Buffer(); - std::atomic version{0}; - Pmr_QImage_Buffer image{Memory_Domain::PlotColorBuffer}; - QRegion dirty_region; - std::pmr::vector> presented_update_states; - std::atomic state{Frame_State::Free}; - std::uint64_t generation{}; - Frame_Metadata metadata; - }; - /// 颜色缓冲占用租约,析构时自动释放底层 busy 标记。 - 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(Color_Buffer_Lease&& other) noexcept; - Color_Buffer_Lease& operator=(Color_Buffer_Lease&& other) noexcept; - ~Color_Buffer_Lease(); - /// 提前释放当前租约。 - void reset(); - Color_Buffer* buffer{}; - Triple_Role_Buffer_Control* control{}; - Triple_Buffer_Lease lease{}; - /// 判断当前租约是否持有有效颜色缓冲。 - explicit operator bool() const { - return buffer && lease; - } - }; - /// 单个 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; - std::atomic edit_state_version{1}; - 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); - const Color_Buffer* color_by_role(std::uint8_t role) const; - Color_Buffer_Lease try_render_color(); - Color_Buffer_Lease try_front_color(); - 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() { - return make_render_object(); - } - /// 创建派生对象的 State 缓冲实体。 - virtual Render_Object_Owner create_render_state() { - return make_render_object(); - } - /// 创建派生对象的 Input 缓冲实体。 - virtual Render_Object_Owner create_input_data() { - return make_render_object(); - } - /// 把对象挂接到 parent renderable。 - virtual void init(const std::shared_ptr& parent); - /// 初始化 Plot 根节点。 - virtual void init_root(Abs_Plot* plot); - void add_child(const std::shared_ptr& child); - void remove_child(const std::shared_ptr& child); - std::vector> children_snapshot() const; - std::vector> hit_renderables(const QPointF& pos); - void render(QPainter* painter, const Plot_Render_Snapshot& snapshot); - /// 绘制已准备好的 Render_Data。 - virtual void draw(QPainter* painter, const Plot_Render_Snapshot& snapshot); - /// 在 CPU render 阶段准备渲染数据。 - virtual void prepare_data(const Plot_Render_Snapshot& snapshot); - /// 判断指定 Plot 坐标是否命中当前对象。 - virtual bool select_test(const QPointF& pos); - /// 转发 Qt 事件给私有渲染数据。 - virtual void plot_event(QEvent* event); - virtual void mousePressEvent(QMouseEvent* event); - virtual void mouseMoveEvent(QMouseEvent* event); - virtual void mouseReleaseEvent(QMouseEvent* event); - virtual void set_hover_state(const QPoint& pos, bool active); - virtual void keyPressEvent(QKeyEvent* event); - virtual void keyReleaseEvent(QKeyEvent* event); - virtual void first_resize_event(QResizeEvent* event); - virtual void resizeEvent(QResizeEvent* event); - virtual void wheelEvent(QWheelEvent* event); - /// 按三缓冲版本号环绕语义判断 a 是否比 b 新。 - static bool version_newer(std::uint64_t a, std::uint64_t b); - /// 当前 Renderable 输出缓存策略。 - Renderable_Cache_Mode get_cache_mode() const; - void set_cache_mode(Renderable_Cache_Mode mode); - std::uint64_t cache_image_bytes() const; - std::uint64_t cache_image_area() const; - /// 标记 State 缓冲有新版本。 - void mark_state_dirty(); - /// 请求父 Plot 调度一次渲染。 - void mark_render_dirty(); - QString object_name; - Render_Object_Owner d_owner; - Render_Data* d_ptr{}; - Abs_Plot* plot{}; - std::weak_ptr parent_renderable; - [[nodiscard]] bool ok() const; - [[nodiscard]] bool get_retiring() const; - bool prepared = false; - virtual ~Renderable(); - virtual bool select(QPointF pos) { - return false; - } - bool visible = true; - std::vector> children; - mutable std::mutex children_mutex; - std::weak_ptr plot_context; - std::atomic cache_mode{Renderable_Cache_Mode::Direct}; - std::atomic_bool cache_dirty{true}; - std::atomic_bool cache_render_queued{false}; - std::atomic cache_edit_version{1}; - std::atomic cache_ready_version{0}; - mutable std::mutex cache_mutex; - Pmr_QImage_Buffer cache_image{Memory_Domain::RenderableCacheImage}; - QRect cache_bounds; - std::atomic_bool retiring{true}; - std::atomic_bool initialized{false}; - friend class Abs_Plot; - void init_common(Abs_Plot* plot); - void render_direct(QPainter* painter, const Plot_Render_Snapshot& snapshot); - void render_cached(QPainter* painter, const Plot_Render_Snapshot& snapshot); - void render_self(QPainter* painter, const Plot_Render_Snapshot& snapshot); - void render_children(QPainter* painter, const Plot_Render_Snapshot& snapshot); - void render_cache_image(const QRect& bounds, const Plot_Render_Snapshot& snapshot, std::uint64_t version); - void schedule_cache_render(const QRect& bounds, const Plot_Render_Snapshot& snapshot, std::uint64_t version); - void notify_parent_render_dirty(); - void append_hit_renderables(std::vector>& hits, const QPointF& pos); - void detach_from_plot_tree(); -}; -} // namespace renderive diff --git a/Renderive/architecture/Timeline_Stream.cpp b/Renderive/architecture/Timeline_Stream.cpp new file mode 100644 index 0000000..d55a20e --- /dev/null +++ b/Renderive/architecture/Timeline_Stream.cpp @@ -0,0 +1,116 @@ +#include "Timeline_Stream.h" +#include +#include +namespace renderive { +int Timeline_Stream_Snapshot::upper() const { + return lower + time_point_size - 1; +} +QTime Timeline_Stream_Snapshot::time_by_tick(int tick) const { + int offset = tick - lower; + int count = static_cast(times.size()); + int index = start_coord_to_end_coord ? count - 1 - offset : offset - (time_point_size - count); + if (index < 0 || index >= count) + return {}; + return times[static_cast(index)]; +} +Timeline_Stream::Timeline_Stream() { + start_coord_to_end_coord_value = false; + reset_bounds(); +} +int Timeline_Stream::push_time(QTime time) { + std::lock_guard lock(mutex); + if (start_coord_to_end_coord_value) { + if (lower_value != lower_min) + --lower_value; + else + lower_value = lower_max; + } + else { + if (lower_value != lower_max) + ++lower_value; + else + lower_value = lower_min; + } + tick_start = ++tick_start % tick_space_value; + int tick = start_coord_to_end_coord_value ? lower_value : upper_locked(); + time_buffer.push_back(time); + while (time_buffer.size() > static_cast(time_point_size_value)) + time_buffer.pop_front(); + if (tick_start == 1) + tick_buffer.push_front({time, tick}); + prune_ticks(); + ++version_value; + return tick; +} +void Timeline_Stream::set_time_point_size(int value) { + value = std::max(1, value); + std::lock_guard lock(mutex); + if (time_point_size_value == value) + return; + time_point_size_value = value; + reset_bounds(); + time_buffer.clear(); + tick_buffer.clear(); + tick_start = 0; + ++version_value; +} +void Timeline_Stream::set_tick_space(int value) { + value = std::max(1, value); + std::lock_guard lock(mutex); + if (tick_space_value == value) + return; + tick_space_value = value; + tick_buffer.clear(); + tick_start = 0; + ++version_value; +} +void Timeline_Stream::set_start_coord_to_end_coord(bool value) { + std::lock_guard lock(mutex); + if (start_coord_to_end_coord_value == value) + return; + start_coord_to_end_coord_value = value; + time_buffer.clear(); + tick_buffer.clear(); + tick_start = 0; + ++version_value; +} +Timeline_Stream_Snapshot Timeline_Stream::capture() const { + std::lock_guard lock(mutex); + Timeline_Stream_Snapshot snapshot; + snapshot.version = version_value; + snapshot.start_coord_to_end_coord = start_coord_to_end_coord_value; + snapshot.lower = lower_value; + snapshot.time_point_size = time_point_size_value; + snapshot.tick_space = tick_space_value; + snapshot.times.assign(time_buffer.begin(), time_buffer.end()); + snapshot.ticks.assign(tick_buffer.begin(), tick_buffer.end()); + return snapshot; +} +int Timeline_Stream::time_point_size() const { + std::lock_guard lock(mutex); + return time_point_size_value; +} +int Timeline_Stream::lower() const { + std::lock_guard lock(mutex); + return lower_value; +} +int Timeline_Stream::upper() const { + std::lock_guard lock(mutex); + return upper_locked(); +} +int Timeline_Stream::upper_locked() const { + return lower_value + time_point_size_value - 1; +} +void Timeline_Stream::reset_bounds() { + lower_min = std::numeric_limits::min() / 2; + lower_max = std::numeric_limits::max() / 2 - time_point_size_value + 1; + lower_value = std::clamp(lower_value, lower_min, lower_max); +} +void Timeline_Stream::prune_ticks() { + int upper_value = upper_locked(); + tick_buffer.erase(std::remove_if(tick_buffer.begin(), tick_buffer.end(), [this, upper_value](const Timeline_Tick& tick) { + return tick.tick < lower_value || tick.tick > upper_value; + }), + tick_buffer.end()); +} +} diff --git a/Renderive/architecture/Timeline_Stream.h b/Renderive/architecture/Timeline_Stream.h new file mode 100644 index 0000000..94c0686 --- /dev/null +++ b/Renderive/architecture/Timeline_Stream.h @@ -0,0 +1,51 @@ +#pragma once +#include +#include +#include +#include +#include +#include "global.h" +namespace renderive { +struct Timeline_Tick { + QTime time; + int tick{}; +}; +struct LIB_DECL Timeline_Stream_Snapshot { + std::uint64_t version{}; + bool start_coord_to_end_coord = false; + int lower{}; + int time_point_size = 1; + int tick_space = 4; + std::vector times; + std::vector ticks; + [[nodiscard]] int upper() const; + [[nodiscard]] QTime time_by_tick(int tick) const; +}; +class LIB_DECL Timeline_Stream { +public: + Timeline_Stream(); + int push_time(QTime time); + void set_time_point_size(int value); + void set_tick_space(int value); + void set_start_coord_to_end_coord(bool value); + [[nodiscard]] Timeline_Stream_Snapshot capture() const; + [[nodiscard]] int time_point_size() const; + [[nodiscard]] int lower() const; + [[nodiscard]] int upper() const; +private: + [[nodiscard]] int upper_locked() const; + void reset_bounds(); + void prune_ticks(); + mutable std::mutex mutex; + std::uint64_t version_value = 1; + bool start_coord_to_end_coord_value = false; + int lower_value{}; + int time_point_size_value = 100; + int lower_min{}; + int lower_max{}; + int tick_start{}; + int tick_space_value = 4; + std::deque time_buffer; + std::deque tick_buffer; +}; +} diff --git a/Renderive/architecture/Update_Completion.cpp b/Renderive/architecture/Update_Completion.cpp index 99873cc..c8aae1d 100644 --- a/Renderive/architecture/Update_Completion.cpp +++ b/Renderive/architecture/Update_Completion.cpp @@ -8,7 +8,7 @@ int stage_index(Update_Stage stage) noexcept { } } Update_State::Stage_Record::Stage_Record(std::pmr::memory_resource* resource) : waiters(resource) {} -Update_State::Update_State(Update_Id id) : update_id(id), input_released(memory_resource(Memory_Domain::UpdateCompletion)), committed(memory_resource(Memory_Domain::UpdateCompletion)), rendered(memory_resource(Memory_Domain::UpdateCompletion)), presented(memory_resource(Memory_Domain::UpdateCompletion)) {} +Update_State::Update_State(Update_Id id) : update_id(id), input_released(memory_resource(Memory_Domain::Update_Completion)), committed(memory_resource(Memory_Domain::Update_Completion)), rendered(memory_resource(Memory_Domain::Update_Completion)), presented(memory_resource(Memory_Domain::Update_Completion)) {} Update_Id Update_State::id() const noexcept { return update_id; } @@ -66,7 +66,7 @@ void Update_State::drain_completed_waiters(Stage_Record& record, std::pmr::vecto record.waiters.clear(); } void Update_State::complete_until(Update_Stage stage, std::error_code error, Update_Outcome outcome) { - std::pmr::vector waiters(memory_resource(Memory_Domain::UpdateCompletion)); + std::pmr::vector waiters(memory_resource(Memory_Domain::Update_Completion)); { std::lock_guard lock(mutex); int last = std::min(stage_index(stage), stage_index(Update_Stage::Presented)); @@ -88,7 +88,7 @@ void Update_State::complete_all(std::error_code error, Update_Outcome outcome) { complete_until(Update_Stage::Presented, error, outcome); } std::shared_ptr make_update_state() { - std::pmr::polymorphic_allocator allocator(memory_resource(Memory_Domain::UpdateCompletion)); + std::pmr::polymorphic_allocator allocator(memory_resource(Memory_Domain::Update_Completion)); return std::allocate_shared(allocator, next_update_id.fetch_add(1, std::memory_order_relaxed)); } void complete_update_states(std::pmr::vector>& states, Update_Stage stage, std::error_code error, Update_Outcome outcome) { diff --git a/Renderive/architecture/Update_Completion.h b/Renderive/architecture/Update_Completion.h index bfcf997..e9f3737 100644 --- a/Renderive/architecture/Update_Completion.h +++ b/Renderive/architecture/Update_Completion.h @@ -33,7 +33,7 @@ public: template explicit Update_Waiter(Handler&& handler) { using Model = Waiter_Model>; - resource = memory_resource(Memory_Domain::UpdateCompletion); + resource = memory_resource(Memory_Domain::Update_Completion); std::pmr::polymorphic_allocator allocator(resource); object = allocator.allocate(1); try { diff --git a/Renderive/architecture/export.h b/Renderive/architecture/export.h index 0f291de..95c5bfd 100644 --- a/Renderive/architecture/export.h +++ b/Renderive/architecture/export.h @@ -1,9 +1,8 @@ #pragma once -#include "Plot.h" #include "Render_Config.h" #include "Render_Data.h" #include "Render_Lease.h" #include "Renderable.h" -#include "Renderable_Core.h" #include "Triple_Buffer.h" #include "global.h" +#include "../plot/Plot.h" diff --git a/Renderive/base/Frame_Memory.cpp b/Renderive/base/Frame_Memory.cpp index 0c24691..6b583b4 100644 --- a/Renderive/base/Frame_Memory.cpp +++ b/Renderive/base/Frame_Memory.cpp @@ -42,7 +42,7 @@ struct Thread_Frame_Scratch { } std::pmr::memory_resource* overflow_upstream() { if (!overflow_pool) - overflow_pool = std::make_unique(memory_resource(Memory_Domain::FrameArena)); + overflow_pool = std::make_unique(memory_resource(Memory_Domain::Frame_Arena)); return overflow_pool.get(); } void begin_frame(std::size_t initial_buffer_size) { diff --git a/Renderive/base/Memory.cpp b/Renderive/base/Memory.cpp index 790a729..d9364d0 100644 --- a/Renderive/base/Memory.cpp +++ b/Renderive/base/Memory.cpp @@ -101,8 +101,8 @@ Memory_System& memory_system() { bool domain_uses_small_pool(Memory_Domain domain) { switch (domain) { case Memory_Domain::Renderable: - case Memory_Domain::SchedulerTask: - case Memory_Domain::UpdateCompletion: + case Memory_Domain::Scheduler_Task: + case Memory_Domain::Update_Completion: case Memory_Domain::Other: return true; default: diff --git a/Renderive/base/Memory.h b/Renderive/base/Memory.h index 8f7361e..fe1fa64 100644 --- a/Renderive/base/Memory.h +++ b/Renderive/base/Memory.h @@ -25,22 +25,22 @@ struct Memory_Stats { }; enum class Memory_Domain : std::uint8_t { Renderable, - InputBuffer, + Input_Buffer, Waterfall, Spectrum, - FrameArena, - RingBuffer, + Frame_Arena, + Ring_Buffer, Image, Audio, Afterglow, Planisphere, Curve, - PointSet, + Point_Set, Axis, - SchedulerTask, - RenderableCacheImage, - PlotColorBuffer, - UpdateCompletion, + Scheduler_Task, + Renderable_Cache_Image, + Plot_Frame, + Update_Completion, Other, Count }; diff --git a/Renderive/base/Task.h b/Renderive/base/Task.h index 57718e0..3aaa528 100644 --- a/Renderive/base/Task.h +++ b/Renderive/base/Task.h @@ -42,7 +42,7 @@ public: #endif } else { - resource = memory_resource(Memory_Domain::SchedulerTask); + resource = memory_resource(Memory_Domain::Scheduler_Task); std::pmr::polymorphic_allocator allocator(resource); object = allocator.allocate(1); try { diff --git a/Renderive/base/Tool.h b/Renderive/base/Tool.h index 92ce561..8ad4034 100644 --- a/Renderive/base/Tool.h +++ b/Renderive/base/Tool.h @@ -17,6 +17,8 @@ #include "../architecture/export.h" #include "../../Widget/base/export.h" #include "../plottable/export.h" +#include "Renderive/plot/Latency_Eager_Plot.h" +#include "Renderive/plot/Plot.h" namespace renderive { static QColor get_color(int lowest, int highest) { int r = QRandomGenerator::global()->bounded(lowest, highest); @@ -50,7 +52,7 @@ inline QString get_value_from_string(const QString& value) { } template static QWidget* create_edit_widget_list(const QString& button_text, - const std::function &)>& click, const std::vector& init_values) { + const std::function&)>& click, const std::vector& init_values) { auto container = new QWidget; auto layout = new QHBoxLayout(container); layout->setAlignment(Qt::AlignLeft); @@ -92,16 +94,16 @@ static QWidget* create_edit_widget_list(const QString& button_text, } template static QWidget* create_edit_widget(const QString& button_text, - const std::function& click, const T& init_values) { + const std::function& click, const T& init_values) { return create_edit_widget_list(button_text, [click](const std::vector& data) { click(data.front()); }, std::vector{init_values}); } static QWidget* create_range(Axis* axis, const QString& text = "更改轴范围") { return create_edit_widget_list(text, [axis](const std::vector& values) { - axis->set_coord_range(Range{values[0], values[1]}); - }, - {axis->coord_range().origin, axis->coord_range().target}); + axis->set_coord_range(Range{values[0], values[1]}); + }, + {axis->coord_range().origin, axis->coord_range().target}); } static QMenu* find_parent_menu(QWidget* widget) { // 从当前窗口开始,逐步检查父窗口 @@ -225,26 +227,6 @@ static std::vector bw(const std::vector& widgets, const QStr } return {ret}; } -static QWidget* create_plot_render_schedule_combo(renderive::Abs_Plot* plot) { - auto container = new QWidget; - auto layout = new QHBoxLayout(container); - layout->setAlignment(Qt::AlignLeft); - layout->setContentsMargins(0, 0, 0, 0); - auto label = new QLabel("渲染策略"); - auto combo = new QComboBox; - label->setStyleSheet(renderive::get_button_style_sheet()); - combo->setStyleSheet(renderive::get_button_style_sheet()); - 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) { - plot->set_render_schedule_mode(static_cast(combo->itemData(index).toInt())); - }); - layout->addWidget(label); - layout->addWidget(combo); - return container; -} template static QWidget* create_line_interpolation_combo(T* renderable) { auto container = new QWidget; @@ -303,11 +285,12 @@ 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) { - plot->set_max_render_fps(data[0]); - }, {plot->get_max_render_fps()}), }; + if (auto latency_plot = dynamic_cast(plot)) { + ws.push_back(renderive::create_edit_widget_list("最大渲染FPS", [latency_plot](std::vector data) { + latency_plot->set_max_render_fps(data[0]); + }, {latency_plot->get_max_render_fps()})); + } for (QWidget* cur : concat_widgets(bw(ws, "plot基础"), widgets)) { l->addWidget(cur); } @@ -321,13 +304,13 @@ static std::vector cw(renderive::Abs_Axis* it) { it->set_orientation(it->orientation() == Qt::Horizontal ? Qt::Vertical : Qt::Horizontal); }), renderive::create_edit_widget_list("主刻度值", [it](const std::vector& values) { - it->set_tick_length(values[0]); - }, - {it->tick_length()}), + it->set_tick_length(values[0]); + }, + {it->tick_length()}), renderive::create_edit_widget_list("子刻度值", [it](const std::vector& values) { - it->set_sub_tick_length(values[0]); - }, - {it->sub_tick_length()}), + it->set_sub_tick_length(values[0]); + }, + {it->sub_tick_length()}), renderive::create_select_color("更改轴颜色", [it](const QColor& color) { it->set_color(color); }, it->color()), @@ -365,13 +348,13 @@ static std::vector cw(renderive::Time_Axis* it, int num_pre_second = - return {}; auto t = std::vector{ renderive::create_edit_widget_list("时间格式", [it](std::vector data) { - it->set_time_format(data[0]); - }, - {it->time_format()}), + it->set_time_format(data[0]); + }, + {it->time_format()}), renderive::create_edit_widget_list("单位间隔pixel", [it](std::vector data) { - it->set_tick_pixel_space(data[0]); - }, - {it->tick_pixel_space()}), + it->set_tick_pixel_space(data[0]); + }, + {it->tick_pixel_space()}), }; auto timer = new QTimer(it->plot); if (num_pre_second > 0) @@ -380,14 +363,14 @@ static std::vector cw(renderive::Time_Axis* it, int num_pre_second = - it->give_data(QTime::currentTime()); }); t.push_back(renderive::create_edit_widget_list("推数据Timer(次/s)", [timer](std::vector data) { - if (data[0] < 0) { - timer->stop(); - } - else { - timer->start((int)(1000.0 / data[0])); - } - }, - {timer->interval() > 0 ? 1000.0 / timer->interval() : -1})); + if (data[0] < 0) { + timer->stop(); + } + else { + timer->start((int)(1000.0 / data[0])); + } + }, + {timer->interval() > 0 ? 1000.0 / timer->interval() : -1})); std::vector axis_widgets = cw(dynamic_cast(it)); t.insert(t.end(), axis_widgets.begin(), axis_widgets.end()); return t; @@ -442,8 +425,7 @@ static std::vector cw(renderive::Waterfall* it, int num_pre_second = - timer->start(static_cast(1000.0 / static_cast(num_pre_second))); QObject::connect(timer, &QTimer::timeout, [it]() { std::vector d = renderive::get_data(it->power_range(), it->frequency_point_size()); - int tick = it->time_axis()->give_data(QTime::currentTime()); - it->give_data(tick, d); + it->give_data(QTime::currentTime(), d); }); auto t = std::vector{ renderive::create_toggle_button("关闭可见区裁剪", "开启可见区裁剪", [it]() { @@ -470,19 +452,18 @@ static std::vector cw(renderive::Audio_Frequency* it, int num_pre_seco if (num_pre_second > 0) timer->start(static_cast(1000.0 / static_cast(num_pre_second))); QObject::connect(timer, &QTimer::timeout, [it]() { - int tick = it->time_axis()->give_data(QTime::currentTime()); - it->give_data(tick, renderive::get_data({0, 20})); + it->give_data(QTime::currentTime(), renderive::get_data({0, 20})); }); auto t = std::vector{ renderive::create_edit_widget_list("推数据Timer(次/s)", [timer](std::vector data) { - if (data[0] < 0) { - timer->stop(); - } - else { - timer->start((int)(1000.0 / data[0])); - } - }, - {timer->interval() > 0 ? 1000.0 / timer->interval() : -1}), + if (data[0] < 0) { + timer->stop(); + } + else { + timer->start((int)(1000.0 / data[0])); + } + }, + {timer->interval() > 0 ? 1000.0 / timer->interval() : -1}), }; return t; } @@ -501,18 +482,18 @@ static std::vector cw(renderive::Planisphere* it, int num_pre_second = timer->start(static_cast(1000.0 / static_cast(num_pre_second))); auto t = std::vector{ renderive::create_edit_widget_list("推数据Timer(次/s)", [timer](std::vector data) { - if (data[0] < 0) { - timer->stop(); - } - else { - timer->start((int)(1000.0 / data[0])); - } - }, - {timer->interval() > 0 ? 1000.0 / timer->interval() : -1}), + if (data[0] < 0) { + timer->stop(); + } + else { + timer->start((int)(1000.0 / data[0])); + } + }, + {timer->interval() > 0 ? 1000.0 / timer->interval() : -1}), renderive::create_edit_widget_list("设置持续时间(ms)", [it](std::vector data) { - it->set_continue_millisecond(data[0]); - }, - {it->continue_millisecond()}), + it->set_continue_millisecond(data[0]); + }, + {it->continue_millisecond()}), renderive::create_select_color("更改IQ点颜色", [it](const QColor& color) { it->set_point_color(color); }, it->point_color()), @@ -567,22 +548,22 @@ static std::vector cw(renderive::Afterglow* it, int num_pre_second = - timer->start(static_cast(1000.0 / static_cast(num_pre_second))); auto t = std::vector{ renderive::create_edit_widget_list("推数据Timer(次/s)", [timer](std::vector data) { - if (data[0] < 0) { - timer->stop(); - } - else { - timer->start((int)(1000.0 / data[0])); - } - }, - {timer->interval() > 0 ? 1000.0 / timer->interval() : -1}), + if (data[0] < 0) { + timer->stop(); + } + else { + timer->start((int)(1000.0 / data[0])); + } + }, + {timer->interval() > 0 ? 1000.0 / timer->interval() : -1}), renderive::create_edit_widget_list("频率范围", [it](std::vector data) { - it->set_frequency_range({data[0], data[1]}); - }, - {it->frequency_range().origin, it->frequency_range().target}), + it->set_frequency_range({data[0], data[1]}); + }, + {it->frequency_range().origin, it->frequency_range().target}), renderive::create_edit_widget_list("功率概率范围", [it](std::vector data) { - it->set_power_range({data[0], data[1]}); - }, - {it->power_range().origin, it->power_range().target}), + it->set_power_range({data[0], data[1]}); + }, + {it->power_range().origin, it->power_range().target}), renderive::create_button("更改基础线", []() { base_data = create_base_data(); }), diff --git a/Renderive/base/global.cpp b/Renderive/base/global.cpp index 02fe2a1..e4e6cf1 100644 --- a/Renderive/base/global.cpp +++ b/Renderive/base/global.cpp @@ -1,7 +1,7 @@ #include "global.h" #include #include -#include "../architecture/Plot_p.h" +#include "../plot/Plot_p.h" #include "Memory.h" #include "asio.hpp" namespace renderive { @@ -138,33 +138,27 @@ void Render_Scheduler::register_plot_on_scheduler(Abs_Plot* plot) { } 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; + Abs_Plot_Private* data = plot->d; data->render_enabled.store(false, std::memory_order_release); - if (data->rate_limit_timer) { - data->rate_limit_timer->cancel(); - data->rate_limit_timer_target_ns = 0; - } + data->on_render_paused(); } void Render_Scheduler::shutdown_on_scheduler() { for (Abs_Plot* plot : d->plots) { - Plot_Private* data = plot->d; + Abs_Plot_Private* data = plot->d; data->destroying.store(true, std::memory_order_release); data->render_enabled.store(false, std::memory_order_release); - if (data->rate_limit_timer) { - data->rate_limit_timer->cancel(); - data->rate_limit_timer_target_ns = 0; - } + data->on_render_paused(); } d->plots.clear(); } void Render_Scheduler::initialize_plot_on_scheduler(Abs_Plot* plot) { - Plot_Private* data = plot->d; - 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()) + Abs_Plot_Private* data = plot->d; + data->render_context->render_width.store(data->pending_render_width.load(std::memory_order_acquire), std::memory_order_release); + data->render_context->render_height.store(data->pending_render_height.load(std::memory_order_acquire), std::memory_order_release); + if (plot->isVisible()) { data->render_enabled.store(true, std::memory_order_release); - plot->on_scheduler_event(Scheduler_Event::Control_Updated, 0); + data->on_render_started(); + } } Render_Scheduler& Global::render_scheduler() { return render_scheduler_value; diff --git a/Renderive/plot/Explicit_Plot.cpp b/Renderive/plot/Explicit_Plot.cpp new file mode 100644 index 0000000..8f05a50 --- /dev/null +++ b/Renderive/plot/Explicit_Plot.cpp @@ -0,0 +1,37 @@ +#include "Explicit_Plot_p.h" +#include +#include "Plot_p.h" +namespace renderive { +namespace { +std::uint64_t steady_now_ns() { + return static_cast(std::chrono::duration_cast(std::chrono::steady_clock::now().time_since_epoch()).count()); +} +} +Explicit_Plot::Explicit_Plot() : Abs_Plot(new Explicit_Plot_Private(this)) {} +void Explicit_Plot::replot() { + auto data = static_cast(d); + data->post_manual_request(); +} +void Explicit_Plot_Private::try_schedule_render() { + if (!render_enabled.load(std::memory_order_acquire) || destroying.load(std::memory_order_acquire)) + return; + if (!manual_request_pending.load(std::memory_order_acquire)) + return; + if (pipeline.job_state != Renderable::Job_State::Idle) + return; + manual_request_pending.store(false, std::memory_order_release); + std::uint64_t request_time = last_manual_request_ns.load(std::memory_order_acquire); + if (!submit_render_once(request_time ? request_time : steady_now_ns())) + manual_request_pending.store(true, std::memory_order_release); +} +void Explicit_Plot_Private::post_manual_request() { + last_manual_request_ns.store(steady_now_ns(), std::memory_order_release); + manual_request_pending.store(true, std::memory_order_release); + post_scheduler_task([this]() { + on_manual_request(); + }, true); +} +void Explicit_Plot_Private::on_manual_request() { + try_schedule_render(); +} +} // namespace renderive diff --git a/Renderive/plot/Explicit_Plot.h b/Renderive/plot/Explicit_Plot.h new file mode 100644 index 0000000..5e51ed6 --- /dev/null +++ b/Renderive/plot/Explicit_Plot.h @@ -0,0 +1,10 @@ +#pragma once +#include "Plot.h" +namespace renderive { +class LIB_DECL Explicit_Plot : public Abs_Plot { +public: + Explicit_Plot(); + ~Explicit_Plot() override = default; + void replot(); +}; +} // namespace renderive diff --git a/Renderive/plot/Explicit_Plot_p.h b/Renderive/plot/Explicit_Plot_p.h new file mode 100644 index 0000000..69c1289 --- /dev/null +++ b/Renderive/plot/Explicit_Plot_p.h @@ -0,0 +1,13 @@ +#pragma once +#include "Explicit_Plot.h" +#include "Plot_p.h" +namespace renderive { +struct Explicit_Plot_Private : Abs_Plot_Private { + explicit Explicit_Plot_Private(Abs_Plot* plot) : Abs_Plot_Private(plot) {} + std::atomic_bool manual_request_pending{false}; + std::atomic last_manual_request_ns{0}; + void try_schedule_render() override; + void post_manual_request(); + void on_manual_request(); +}; +} diff --git a/Renderive/plot/Latency_Eager_Plot.cpp b/Renderive/plot/Latency_Eager_Plot.cpp new file mode 100644 index 0000000..ab26148 --- /dev/null +++ b/Renderive/plot/Latency_Eager_Plot.cpp @@ -0,0 +1,125 @@ +#include "Latency_Eager_Plot_p.h" +#include +#include "../base/global.h" +namespace renderive { +namespace { +std::uint64_t steady_now_ns() { + return static_cast(std::chrono::duration_cast(std::chrono::steady_clock::now().time_since_epoch()).count()); +} +} +Latency_Eager_Plot::Latency_Eager_Plot() : Abs_Plot(new Latency_Eager_Plot_Private(this)) {} +double Latency_Eager_Plot::get_max_render_fps() const { + auto data = static_cast(d); + return data->max_render_fps.load(std::memory_order_acquire); +} +void Latency_Eager_Plot::set_max_render_fps(double fps) { + auto data = static_cast(d); + data->max_render_fps.store(fps, std::memory_order_release); + data->post_control_updated(); +} +Frame_Feedback_Snapshot Latency_Eager_Plot::get_frame_feedback_state() const { + auto data = static_cast(d); + auto state = std::atomic_load_explicit(&data->feedback_state, std::memory_order_acquire); + return state ? *state : Frame_Feedback_Snapshot{}; +} +void Latency_Eager_Plot::start_render(int max_render_fps) { + set_max_render_fps(max_render_fps); + Abs_Plot::start_render(); +} +void Latency_Eager_Plot_Private::on_render_started() { + if (!rate_limit_timer) + rate_limit_timer = std::make_unique(Global::instance()->render_scheduler().io_context()); + post_control_updated(); +} +void Latency_Eager_Plot_Private::on_render_paused() { + if (rate_limit_timer) + rate_limit_timer->cancel(); + rate_limit_timer_target_ns = 0; +} +void Latency_Eager_Plot_Private::on_render_state_dirty(std::uint64_t event_time_ns) { + post_input_changed(event_time_ns); +} +void Latency_Eager_Plot_Private::on_render_submitted(Frame_Lifecycle_Record& frame, std::uint64_t submit_time) { + feedback_controller.set_max_render_fps(max_render_fps.load(std::memory_order_acquire)); + feedback_controller.on_render_started(frame, submit_time); + publish_feedback_state(); +} +void Latency_Eager_Plot_Private::on_ready_frame_superseded(Frame_Lifecycle_Record& frame, bool consumer_busy, bool input_pending) { + feedback_controller.on_frame_superseded(frame, consumer_busy, input_pending); + publish_feedback_state(); +} +void Latency_Eager_Plot_Private::on_render_finished(Frame_Lifecycle_Record& frame, bool ready_occupied, bool consumer_busy, bool input_pending) { + feedback_controller.on_render_completed(frame, ready_occupied, consumer_busy, input_pending); + publish_feedback_state(); +} +void Latency_Eager_Plot_Private::on_frame_returned(Frame_Lifecycle_Record& frame, bool ready_occupied) { + feedback_controller.on_frame_consumed(frame, ready_occupied); + publish_feedback_state(); +} +void Latency_Eager_Plot_Private::on_consumer_waited() { + feedback_controller.on_consumer_underrun(steady_now_ns(), pipeline.has_new_state()); + publish_feedback_state(); +} +void Latency_Eager_Plot_Private::try_schedule_render() { + if (!render_enabled.load(std::memory_order_acquire) || destroying.load(std::memory_order_acquire)) + return; + if (pipeline.job_state != Renderable::Job_State::Idle || !pipeline.has_new_state()) + return; + std::uint64_t now_ns = steady_now_ns(); + std::uint64_t target_ns = 0; + feedback_controller.set_max_render_fps(max_render_fps.load(std::memory_order_acquire)); + if (!feedback_controller.rate_limit_ready(now_ns)) + target_ns = feedback_controller.next_render_time_ns(); + publish_feedback_state(); + if (target_ns) { + schedule_rate_limit(target_ns); + return; + } + submit_render_once(now_ns); +} +void Latency_Eager_Plot_Private::post_input_changed(std::uint64_t event_time_ns) { + post_scheduler_task([this, event_time_ns]() { + on_input_changed(event_time_ns); + }, true); +} +void Latency_Eager_Plot_Private::post_control_updated() { + post_scheduler_task([this]() { + on_scheduler_wakeup(); + }, true); +} +void Latency_Eager_Plot_Private::on_input_changed(std::uint64_t event_time_ns) { + feedback_controller.set_max_render_fps(max_render_fps.load(std::memory_order_acquire)); + feedback_controller.on_input_changed(event_time_ns, pipeline.edit_state_version.load(std::memory_order_acquire)); + publish_feedback_state(); + try_schedule_render(); +} +void Latency_Eager_Plot_Private::on_scheduler_wakeup() { + feedback_controller.set_max_render_fps(max_render_fps.load(std::memory_order_acquire)); + publish_feedback_state(); + try_schedule_render(); +} +void Latency_Eager_Plot_Private::on_rate_limit_deadline(std::uint64_t target_ns) { + if (rate_limit_timer_target_ns != target_ns) + return; + rate_limit_timer_target_ns = 0; + try_schedule_render(); +} +void Latency_Eager_Plot_Private::schedule_rate_limit(std::uint64_t target_ns) { + if (rate_limit_timer_target_ns == target_ns) + return; + if (!rate_limit_timer) + rate_limit_timer = std::make_unique(Global::instance()->render_scheduler().io_context()); + rate_limit_timer_target_ns = target_ns; + rate_limit_timer->cancel(); + rate_limit_timer->expires_at(std::chrono::steady_clock::time_point(std::chrono::nanoseconds(target_ns))); + add_render_task(); + rate_limit_timer->async_wait([this, target_ns](const asio::error_code& error) { + if (!error && !destroying.load(std::memory_order_acquire)) + on_rate_limit_deadline(target_ns); + finish_render_task(); + }); +} +void Latency_Eager_Plot_Private::publish_feedback_state() { + std::atomic_store_explicit(&feedback_state, std::make_shared(feedback_controller.state()), std::memory_order_release); +} +} // namespace renderive diff --git a/Renderive/plot/Latency_Eager_Plot.h b/Renderive/plot/Latency_Eager_Plot.h new file mode 100644 index 0000000..c83cfb7 --- /dev/null +++ b/Renderive/plot/Latency_Eager_Plot.h @@ -0,0 +1,13 @@ +#pragma once +#include "Plot.h" +namespace renderive { +class LIB_DECL Latency_Eager_Plot : public Abs_Plot { +public: + Latency_Eager_Plot(); + ~Latency_Eager_Plot() override = default; + [[nodiscard]] double get_max_render_fps() const; + void set_max_render_fps(double fps); + [[nodiscard]] Frame_Feedback_Snapshot get_frame_feedback_state() const; + void start_render(int max_render_fps); +}; +} // namespace renderive diff --git a/Renderive/plot/Latency_Eager_Plot_p.h b/Renderive/plot/Latency_Eager_Plot_p.h new file mode 100644 index 0000000..e24b13f --- /dev/null +++ b/Renderive/plot/Latency_Eager_Plot_p.h @@ -0,0 +1,29 @@ +#pragma once +#include "Latency_Eager_Plot.h" +#include "Plot_p.h" +namespace renderive { +struct Latency_Eager_Plot_Private : Abs_Plot_Private { + explicit Latency_Eager_Plot_Private(Abs_Plot* plot) : Abs_Plot_Private(plot) {} + std::unique_ptr rate_limit_timer; + std::atomic max_render_fps{0.0}; + std::uint64_t rate_limit_timer_target_ns{}; + Frame_Feedback_Controller feedback_controller; + std::shared_ptr feedback_state; + void on_render_started() override; + void on_render_paused() override; + void on_render_state_dirty(std::uint64_t event_time_ns) override; + void on_render_submitted(Frame_Lifecycle_Record& frame, std::uint64_t submit_time) override; + void on_ready_frame_superseded(Frame_Lifecycle_Record& frame, bool consumer_busy, bool input_pending) override; + void on_render_finished(Frame_Lifecycle_Record& frame, bool ready_occupied, bool consumer_busy, bool input_pending) override; + void on_frame_returned(Frame_Lifecycle_Record& frame, bool ready_occupied) override; + void on_consumer_waited() override; + void try_schedule_render() override; + void post_input_changed(std::uint64_t event_time_ns); + void post_control_updated(); + void on_input_changed(std::uint64_t event_time_ns); + void on_scheduler_wakeup(); + void on_rate_limit_deadline(std::uint64_t target_ns); + void schedule_rate_limit(std::uint64_t target_ns); + void publish_feedback_state(); +}; +} diff --git a/Renderive/plot/Plot.cpp b/Renderive/plot/Plot.cpp new file mode 100644 index 0000000..dcedc8a --- /dev/null +++ b/Renderive/plot/Plot.cpp @@ -0,0 +1,598 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "../Axis/Time_Axis.h" +#include "../base/Frame_Memory.h" +#include "../base/Memory.h" +#include "../base/Task.h" +#include "../base/global.h" +#include "../plottable/Performance_Shower_p.h" +#include "Plot_p.h" +#include "../architecture/Update_Completion.h" +namespace renderive { +namespace { +std::uint64_t steady_now_ns() { + return static_cast(std::chrono::duration_cast(std::chrono::steady_clock::now().time_since_epoch()).count()); +} +int renderable_prepare_priority(const std::shared_ptr& able) { + return dynamic_cast(able.get()) ? 0 : 1; +} +std::shared_ptr performance_shower_owner(Abs_Plot_Private* data) { + if (!data || !data->render_context) + return {}; + return std::atomic_load_explicit(&data->render_context->performance_shower_value, std::memory_order_acquire); +} +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) + bytes += able->d_ptr->estimated_frame_memory(frame_snapshot); + } + return bytes; +} +template +struct Cpu_Task_State { + Cpu_Task_State(Handler&& handler, asio::any_io_executor executor) : handler(std::move(handler)), executor(std::move(executor)) {} + Handler handler; + asio::any_io_executor executor; +}; +asio::awaitable await_cpu_task(Task task) { + auto executor = co_await asio::this_coro::executor; + auto token = asio::use_awaitable; + auto error = renderive::make_shared(); + co_await asio::async_initiate( + [executor, error](auto handler, Task task) mutable { + using Handler = std::decay_t; + auto state = renderive::make_shared>(std::move(handler), executor); + Global::instance()->render_scheduler().post_cpu([state, error, task = std::move(task)]() mutable { + try { + task(); + } + catch (...) { + *error = std::current_exception(); + } + asio::post(state->executor, [state]() mutable { + std::move(state->handler)(); + }); + }); + }, token, std::move(task)); + if (*error) + std::rethrow_exception(*error); +} +} +void start_render_scheduler() { + Global::instance()->start_render_scheduler(); +} +void Abs_Plot_Private::add_render_task() { + active_render_tasks.fetch_add(1, std::memory_order_acq_rel); +} +void Abs_Plot_Private::finish_render_task() { + if (active_render_tasks.fetch_sub(1, std::memory_order_acq_rel) == 1) + render_task_done.notify_all(); +} +int Abs_Plot_Private_Access::active_render_tasks(const Abs_Plot& plot) { + return plot.d->active_render_tasks.load(std::memory_order_acquire); +} +Abs_Plot::Abs_Plot(Abs_Plot_Private* private_data) : d(private_data) { + d->render_context = renderive::make_shared(); + d->render_context->background_rgba.store(d->background.rgba(), std::memory_order_release); + d->render_context->plot.store(this, std::memory_order_release); + setAttribute(Qt::WA_OpaquePaintEvent, true); + setMouseTracking(true); + setFocusPolicy(Qt::NoFocus); + Global::instance()->render_scheduler().register_plot(this); +} +Abs_Plot::~Abs_Plot() { + d->destroying.store(true, std::memory_order_release); + d->render_context->destroying.store(true, std::memory_order_release); + d->render_context->plot.store(nullptr, std::memory_order_release); + d->render_enabled.store(false, std::memory_order_release); + Global::instance()->render_scheduler().remove_plot(this); + { + std::unique_lock lock(d->render_task_mutex); + d->render_task_done.wait(lock, [this]() { + return d->active_render_tasks.load(std::memory_order_acquire) == 0; + }); + } + for (auto& color : d->pipeline.color_buffers) { + cancel_update_states(color.presented_update_states); + color.state.store(Frame_State::Free, std::memory_order_release); + } + if (d->root_renderable) + d->root_renderable->detach_from_plot_tree(); + d->root_renderable.reset(); + std::atomic_store_explicit(&d->render_context->performance_shower_value, std::shared_ptr{}, std::memory_order_release); + d->render_context.reset(); + delete d; +} +void Abs_Plot::init() { + init_renderable_tree(); +} +void Abs_Plot::init_renderable_tree() { + d->root_renderable = renderive::make_shared(); + d->root_renderable->object_name = "Root_Renderable"; + d->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(d->root_renderable); + std::atomic_store_explicit(&d->render_context->performance_shower_value, created_shower, std::memory_order_release); + } + 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 == d->root_renderable) + return; + std::shared_ptr parent = able->parent_renderable.lock(); + if (parent) + parent->remove_child(able); + auto shower = std::atomic_load_explicit(&d->render_context->performance_shower_value, std::memory_order_acquire); + if (shower.get() == able.get()) + std::atomic_store_explicit(&d->render_context->performance_shower_value, std::shared_ptr{}, std::memory_order_release); + mark_render_state_dirty(); +} +std::shared_ptr Abs_Plot::get_render_context() const { + return d->render_context; +} +std::shared_ptr Abs_Plot::get_root_renderable() const { + return d->root_renderable; +} +std::shared_ptr Abs_Plot::create_renderable_node(const std::shared_ptr& parent, const QString& object_name) const { + if (!parent || parent->plot != this) { + ASSERT(parent && parent->plot == this, "Abs_Plot create_renderable_node error! parent invalid"); + return {}; + } + auto able = renderive::make_shared(); + able->object_name = object_name; + able->init(parent); + return able; +} +bool Abs_Plot::is_rendering() { + return d->render_enabled.load(std::memory_order_acquire); +} +void Abs_Plot::start_render() const { + if (d->destroying.load(std::memory_order_acquire)) + return; + d->render_enabled.store(true, std::memory_order_release); + d->on_render_started(); +} +void Abs_Plot::pause_render() const { + d->render_enabled.store(false, std::memory_order_release); + if (d->destroying.load(std::memory_order_acquire)) + return; + d->on_render_paused(); +} +QColor Abs_Plot::background_color() { + return QColor::fromRgba(d->render_context->background_rgba.load(std::memory_order_acquire)); +} +void Abs_Plot::set_background_color(const QColor& color) const { + d->background = color; + d->render_context->background_rgba.store(color.rgba(), std::memory_order_release); + const_cast(this)->mark_render_state_dirty(); +} +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); + d->on_render_state_dirty(now_ns); +} +void Abs_Plot::paintEvent(QPaintEvent* event) { + d->paint_event(event); +} +bool Abs_Plot::event(QEvent* event) { + return d->event(event, QWidget::event(event)); +} +Abs_Plot_Private::Renderable_Snapshot Abs_Plot_Private::renderable_snapshot() const { + Renderable_Snapshot snapshot; + append_renderable_snapshot(snapshot, root_renderable); + return snapshot; +} +void Abs_Plot_Private::append_renderable_snapshot(Renderable_Snapshot& snapshot, const std::shared_ptr& able) const { + if (!able) + return; + snapshot.push_back(able); + std::vector> children = able->children_snapshot(); + for (const auto& child : children) + append_renderable_snapshot(snapshot, child); +} +std::shared_ptr Abs_Plot_Private::render_snapshot() const { + auto snapshot = renderive::make_shared(); + snapshot->context = render_context; + if (!render_context) + return snapshot; + snapshot->size = QSize(render_context->render_width.load(std::memory_order_acquire), render_context->render_height.load(std::memory_order_acquire)); + snapshot->background_color = QColor::fromRgba(render_context->background_rgba.load(std::memory_order_acquire)); + snapshot->refresh_times_per_second = render_context->refresh_times_per_second.load(std::memory_order_acquire); + return snapshot; +} +void Abs_Plot_Private::paint_event(QPaintEvent* event) { + Q_UNUSED(event) + std::uint64_t paint_begin_time = steady_now_ns(); + auto consumed = pipeline.consume_ready_color(); + std::pmr::vector> presented_update_states(memory_resource(Memory_Domain::Update_Completion)); + QPainter painter(q); + if (consumed.lease && !consumed.lease.frame->image.isNull()) { + if (consumed.new_frame) + consumed.lease.frame->metadata.paint_begin_time = paint_begin_time; + painter.drawImage(QPoint(0, 0), consumed.lease.frame->image.image()); + if (consumed.new_frame) + presented_update_states = std::move(consumed.lease.frame->presented_update_states); + } + else { + painter.fillRect(q->rect(), background); + } + painter.end(); + complete_update_states(presented_update_states, Update_Stage::Presented); + if (consumed.new_frame && consumed.lease) { + consumed.lease.frame->metadata.paint_end_time = steady_now_ns(); + consumed.lease.frame->metadata.consumer_release_time = steady_now_ns(); + Frame_Lifecycle_Record frame = consumed.lease.frame->metadata; + consumed.lease.frame->state.store(Frame_State::Free, std::memory_order_release); + consumed.lease.reset(); + add_render_task(); + Global::instance()->render_scheduler().post([this, frame = std::move(frame)]() mutable { + if (!destroying.load(std::memory_order_acquire)) + return_consumed_frame(std::move(frame)); + finish_render_task(); + }); + } + else if (consumed.update_was_pending) { + report_consumer_underrun(); + } +} +void Abs_Plot_Private::post_scheduler_task(Task task, bool coalesced) { + if (destroying.load(std::memory_order_acquire)) + return; + Render_Scheduler& scheduler = Global::instance()->render_scheduler(); + if (scheduler.is_scheduler_thread()) { + task(); + return; + } + if (coalesced && render_request_posted.exchange(true, std::memory_order_acq_rel)) + return; + add_render_task(); + scheduler.post([this, task = std::move(task), coalesced]() mutable { + if (coalesced) + render_request_posted.store(false, std::memory_order_release); + if (!destroying.load(std::memory_order_acquire)) + task(); + finish_render_task(); + }); +} +void Abs_Plot_Private::on_render_started() {} +void Abs_Plot_Private::on_render_paused() {} +void Abs_Plot_Private::on_render_state_dirty(std::uint64_t) {} +void Abs_Plot_Private::on_render_submitted(Frame_Lifecycle_Record&, std::uint64_t) {} +void Abs_Plot_Private::on_ready_frame_superseded(Frame_Lifecycle_Record&, bool, bool) {} +void Abs_Plot_Private::on_render_finished(Frame_Lifecycle_Record&, bool, bool, bool) {} +void Abs_Plot_Private::on_frame_returned(Frame_Lifecycle_Record&, bool) {} +void Abs_Plot_Private::on_consumer_waited() {} +bool Abs_Plot_Private::submit_render_once(std::uint64_t render_request_time) { + auto frame_snapshot = render_snapshot(); + if (frame_snapshot->destroying() || frame_snapshot->size.width() <= 0 || frame_snapshot->size.height() <= 0) + return false; + if (!root_renderable) + return false; + auto color_lease = pipeline.try_render_color(); + if (!color_lease) + return false; + Renderable::Frame* color = color_lease.frame; + 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(); + on_ready_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_Lifecycle_Record& frame = color->metadata; + std::uint64_t submit_time = steady_now_ns(); + frame.frame_id = ++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 = render_request_time; + frame.core_data_time = latest_input_time_ns.load(std::memory_order_acquire); + frame.source_data_time_begin = 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 = 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; + on_render_submitted(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); + }); + frame.snapshot_capture_end_time = steady_now_ns(); + frame_snapshot->frame_metadata = &frame; + std::pmr::vector> frame_update_states(memory_resource(Memory_Domain::Update_Completion)); + 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; + } + frame.prepare_end_time = steady_now_ns(); + if (q->first_prepare_data) { + q->first_prepare_data = false; + render_context->first_prepare_data.store(false, std::memory_order_release); + } + std::size_t frame_memory_hint = estimate_frame_memory(snapshot, *frame_snapshot); + struct Render_Color_Task_Context { + Abs_Plot_Private* private_data{}; + Renderable::Frame_Lease color_lease; + std::shared_ptr root; + std::shared_ptr snapshot; + std::shared_ptr frame_snapshot; + std::pmr::vector> update_states{memory_resource(Memory_Domain::Update_Completion)}; + std::size_t frame_memory_hint{}; + }; + auto task_context = renderive::make_shared(); + task_context->private_data = this; + task_context->color_lease = std::move(color_lease); + task_context->root = root_renderable; + 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; + frame.render_queue_enter_time = steady_now_ns(); + pipeline.job_state = Renderable::Job_State::Running; + add_render_task(); + asio::co_spawn(Global::instance()->render_scheduler().io_context(), [task_context]() -> asio::awaitable { + Abs_Plot_Private* private_data = task_context->private_data; + Renderable::Frame* color = task_context->color_lease.frame; + auto cancel_task = [&]() { + color->metadata.frame_outcome = Frame_Outcome::Cancelled; + color->state.store(Frame_State::Free, std::memory_order_release); + private_data->pipeline.job_state = Renderable::Job_State::Idle; + cancel_update_states(task_context->update_states); + task_context->color_lease.reset(); + }; + try { + if (private_data->destroying.load(std::memory_order_acquire)) { + cancel_task(); + private_data->finish_render_task(); + co_return; + } + co_await await_cpu_task([task_context]() { + Frame_Arena frame_arena(task_context->frame_memory_hint); + Abs_Plot_Private::render_color(task_context->color_lease.frame, 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)) { + cancel_task(); + private_data->finish_render_task(); + co_return; + } + private_data->finish_render(); + } + catch (...) { + cancel_task(); + } + private_data->finish_render_task(); + }, asio::detached); + return true; +} +void Abs_Plot_Private::finish_render() { + if (destroying.load(std::memory_order_acquire)) + return; + Renderable::Frame* render_color = pipeline.color_by_role(Color_Render); + 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(); + on_render_finished(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; + on_ready_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 (publish_result.request_update) + request_update(publish_result.region); + try_schedule_render(); +} +void Abs_Plot_Private::return_consumed_frame(Frame_Lifecycle_Record frame) { + bool ready_occupied = pipeline.has_ready_frame(); + on_frame_returned(frame, ready_occupied); + collect_returned_frame(frame); + auto update_result = pipeline.request_ready_update(steady_now_ns()); + if (update_result.request_update) + request_update(update_result.region); + try_schedule_render(); +} +void Abs_Plot_Private::report_consumer_underrun() { + if (destroying.load(std::memory_order_acquire)) + return; + add_render_task(); + Global::instance()->render_scheduler().post([this]() { + if (!destroying.load(std::memory_order_acquire)) + on_consumer_waited(); + finish_render_task(); + }); +} +void Abs_Plot_Private::collect_returned_frame(Frame_Lifecycle_Record& frame) { + auto shower = performance_shower_owner(this); + if (shower && shower->enabled()) + shower->collect_frame(frame); +} +void Abs_Plot_Private::request_update(const QRegion& region) { + QMetaObject::invokeMethod(q, [this, region]() { + if (destroying.load(std::memory_order_acquire)) + return; + if (region.isEmpty()) + q->update(); + else + q->update(region); + }, Qt::QueuedConnection); +} +void Abs_Plot_Private::render_color(Renderable::Frame* color, const std::shared_ptr& root, const Renderable_Snapshot& snapshot, const Plot_Render_Snapshot& render_snapshot_data, std::pmr::vector>&& update_states) { + Frame_Lifecycle_Record& 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); +} +bool Abs_Plot_Private::event(QEvent* event, bool base_result) { + switch (event->type()) { + case QEvent::Resize: { + auto resize_event = static_cast(event); + QSize new_render_size = resize_event->size(); + pending_render_width.store(new_render_size.width(), std::memory_order_release); + pending_render_height.store(new_render_size.height(), std::memory_order_release); + render_context->render_width.store(new_render_size.width(), std::memory_order_release); + render_context->render_height.store(new_render_size.height(), std::memory_order_release); + if (q->first_resize) { + auto snapshot = renderable_snapshot(); + for (const auto& able : snapshot) + able->first_resize_event(resize_event); + } + { + auto snapshot = renderable_snapshot(); + for (const auto& able : snapshot) + able->resizeEvent(resize_event); + } + if (q->first_resize) + q->first_resize = false; + render_context->first_resize.store(false, std::memory_order_release); + q->mark_render_state_dirty(); + break; + } + case QEvent::Show: + q->start_render(); + break; + case QEvent::Hide: + q->pause_render(); + break; + case QEvent::Leave: { + auto snapshot = renderable_snapshot(); + for (const auto& able : snapshot) + able->set_hover_state(QPoint(), false); + break; + } + case QEvent::Wheel: { + auto wheel_event = static_cast(event); + auto snapshot = renderable_snapshot(); + for (const auto& able : snapshot) + able->wheelEvent(wheel_event); + break; + } + case QEvent::MouseMove: { + auto mouse_event = static_cast(event); + auto snapshot = renderable_snapshot(); + auto hits = root_renderable ? root_renderable->hit_renderables(mouse_event->pos()) : Renderable_Snapshot{}; + for (const auto& able : snapshot) { + if (able) + able->set_hover_state(mouse_event->pos(), false); + } + for (const auto& able : hits) { + if (!able) + continue; + able->set_hover_state(mouse_event->pos(), true); + able->mouseMoveEvent(mouse_event); + } + break; + } + case QEvent::MouseButtonPress: { + auto mouse_event = static_cast(event); + auto hits = root_renderable ? root_renderable->hit_renderables(mouse_event->pos()) : Renderable_Snapshot{}; + for (const auto& able : hits) + able->mousePressEvent(mouse_event); + break; + } + case QEvent::MouseButtonRelease: { + auto mouse_event = static_cast(event); + auto hits = root_renderable ? root_renderable->hit_renderables(mouse_event->pos()) : Renderable_Snapshot{}; + for (const auto& able : hits) + able->mouseReleaseEvent(mouse_event); + break; + } + case QEvent::KeyPress: { + auto key_event = static_cast(event); + auto snapshot = renderable_snapshot(); + for (const auto& able : snapshot) + able->keyPressEvent(key_event); + break; + } + case QEvent::KeyRelease: { + auto key_event = static_cast(event); + auto snapshot = renderable_snapshot(); + for (const auto& able : snapshot) + able->keyReleaseEvent(key_event); + break; + } + default: + break; + } + auto snapshot = renderable_snapshot(); + for (const auto& able : snapshot) + able->plot_event(event); + return base_result; +} +} // namespace renderive diff --git a/Renderive/plot/Plot.h b/Renderive/plot/Plot.h new file mode 100644 index 0000000..cc9a76a --- /dev/null +++ b/Renderive/plot/Plot.h @@ -0,0 +1,40 @@ +#pragma once +#include +#include "../architecture/Plot_Render_Context.h" +#include "../architecture/Renderable.h" +namespace renderive { +void LIB_DECL start_render_scheduler(); +class Render_Scheduler; +struct Abs_Plot_Private; +struct Abs_Plot_Private_Access; +class Performance_Shower; +class Latency_Eager_Plot; +class Explicit_Plot; +class LIB_DECL Abs_Plot : public QWidget { + Q_OBJECT +public: + bool get_use_performance_shower(); + void set_use_performance_shower(bool use); + virtual void init(); + QColor background_color(); + [[nodiscard]] std::shared_ptr get_render_context() const; + [[nodiscard]] std::shared_ptr get_root_renderable() const; + [[nodiscard]] std::shared_ptr create_renderable_node(const std::shared_ptr& parent, const QString& object_name = {}) const; + bool first_resize = true; + bool first_prepare_data = true; + QString object_name; + void set_background_color(const QColor& color) const; + void mark_render_state_dirty(); + void remove_renderable(const std::shared_ptr& able); + virtual void init_renderable_tree(); + bool is_rendering(); + void start_render() const; + void pause_render() const; + ~Abs_Plot() override; + explicit Abs_Plot(Abs_Plot_Private* private_data); + Abs_Plot_Private* d{}; +protected: + bool event(QEvent* event) final; + void paintEvent(QPaintEvent* event) final; +}; +} // namespace renderive diff --git a/Renderive/plot/Plot_p.h b/Renderive/plot/Plot_p.h new file mode 100644 index 0000000..1f984f1 --- /dev/null +++ b/Renderive/plot/Plot_p.h @@ -0,0 +1,63 @@ +#pragma once +#include +#include +#include +#include +#include +#include +#include +#include +#include "../base/Task.h" +#include "Plot.h" +#include "asio.hpp" +namespace renderive { +struct Abs_Plot_Private { + using Renderable_Snapshot = std::vector>; + explicit Abs_Plot_Private(Abs_Plot* plot) : q(plot) {} + virtual ~Abs_Plot_Private() = default; + Abs_Plot* q{}; + std::shared_ptr render_context; + std::shared_ptr root_renderable; + std::atomic_bool render_enabled{false}; + std::atomic_bool destroying{false}; + std::atomic_bool render_request_posted{false}; + 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 latest_input_time_ns{0}; + std::atomic pending_input_begin_time_ns{0}; + std::atomic pending_input_count{0}; + Renderable::Render_Pipeline pipeline; + QColor background = Qt::black; + std::uint64_t next_frame_id{}; + void add_render_task(); + void finish_render_task(); + bool event(QEvent* event, bool base_result); + void paint_event(QPaintEvent* event); + 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 post_scheduler_task(Task task, bool coalesced); + bool submit_render_once(std::uint64_t render_request_time); + void finish_render(); + void return_consumed_frame(Frame_Lifecycle_Record frame); + void report_consumer_underrun(); + void collect_returned_frame(Frame_Lifecycle_Record& frame); + void request_update(const QRegion& region); + virtual void on_render_started(); + virtual void on_render_paused(); + virtual void on_render_state_dirty(std::uint64_t event_time_ns); + virtual void on_render_submitted(Frame_Lifecycle_Record& frame, std::uint64_t submit_time); + virtual void on_ready_frame_superseded(Frame_Lifecycle_Record& frame, bool consumer_busy, bool input_pending); + virtual void on_render_finished(Frame_Lifecycle_Record& frame, bool ready_occupied, bool consumer_busy, bool input_pending); + virtual void on_frame_returned(Frame_Lifecycle_Record& frame, bool ready_occupied); + virtual void on_consumer_waited(); + virtual void try_schedule_render() = 0; + static void render_color(Renderable::Frame* color, const std::shared_ptr& root, const Renderable_Snapshot& snapshot, const Plot_Render_Snapshot& render_snapshot_data, std::pmr::vector>&& update_states); +}; +struct Abs_Plot_Private_Access { + static int active_render_tasks(const Abs_Plot& plot); +}; +} // namespace renderive diff --git a/Renderive/plot/export.h b/Renderive/plot/export.h new file mode 100644 index 0000000..6ca435a --- /dev/null +++ b/Renderive/plot/export.h @@ -0,0 +1,3 @@ +#pragma once +#include "Explicit_Plot.h" +#include "../plot/Latency_Eager_Plot.h" diff --git a/Renderive/plottable/Afterglow.cpp b/Renderive/plottable/Afterglow.cpp index 05c5895..4d34a6f 100644 --- a/Renderive/plottable/Afterglow.cpp +++ b/Renderive/plottable/Afterglow.cpp @@ -46,7 +46,7 @@ void Afterglow::set_power_range(Range value) { if (value.size() == 0.0) return; d()->set_state_value(&Afterglow_Render_State::power_range, std::move(value)); - auto bar = color_bar_item.lock(); + auto bar = d()->color_bar_item.lock(); if (bar) bar->set_range(power_range()); } @@ -84,13 +84,13 @@ Color_Map Afterglow::color_map(SRC src) { return d()->state_value(&Afterglow_Render_State::color_map, src); } void Afterglow::set_color_map(Color_Map value) { - auto bar = color_bar_item.lock(); + auto bar = d()->color_bar_item.lock(); if (bar) bar->set_color_map(value); d()->set_state_value(&Afterglow_Render_State::color_map, std::move(value)); } std::shared_ptr Afterglow::color_bar() const { - return color_bar_item.lock(); + return const_cast(this)->d()->color_bar_item.lock(); } void Afterglow::set_frequency_point_size(int t) { Afterglow_Private* pd = d(); diff --git a/Renderive/plottable/Afterglow.h b/Renderive/plottable/Afterglow.h index 779a285..06ef596 100644 --- a/Renderive/plottable/Afterglow.h +++ b/Renderive/plottable/Afterglow.h @@ -12,30 +12,40 @@ namespace renderive { class Frequency_Axis; class Afterglow_Plot; struct Afterglow_Private; -/// 残影频谱图元,按强度衰减显示历史能量。 +/// @brief 残影频谱图元,按强度衰减显示历史能量。 class LIB_DECL Afterglow : public Renderable { public: - /// 返回私有渲染数据。 - Afterglow_Private* d(); - Render_Object_Owner create_render_data() override; - Render_Object_Owner create_render_state() override; - Render_Object_Owner create_input_data() override; + /// @brief 创建残影频谱图元。 Afterglow(); + /// @brief 绑定频率轴。 PROP(std::shared_ptr, frequency_axis) + /// @brief 绑定功率轴。 PROP(std::shared_ptr, power_axis) + /// @brief 频率数据范围。 PROP(Range, frequency_range) + /// @brief 功率数据范围。 PROP(Range, power_range) + /// @brief 每帧频率采样点数。 PROP(int, frequency_point_size) + /// @brief 功率方向采样点数。 PROP(int, power_point_size) + /// @brief 残影累计缓冲大小。 PROP(int, buffer_size) + /// @brief 是否在功率方向做线性补点。 PROP(bool, interpolate) + /// @brief 历史残影衰减率。 PROP(double, attenuation_rate) + /// @brief 初始强度累计阈值。 PROP(int, init_strong_value) + /// @brief 颜色映射表。 PROP(Color_Map, color_map) + /// @brief 返回构造时启用的颜色条。 std::shared_ptr color_bar() const; - /// 写入一帧功率数据。 + /// @brief 写入一帧功率数据。 void give_data(std::span power_range_data); + /// @brief 移动写入一帧功率数据。 void give_data(std::pmr::vector&& power_range_data); + /// @brief 用 writer 填充并写入一帧功率数据。 template void write_data(std::size_t count, Writer&& writer) { std::pmr::vector values(memory_resource(Memory_Domain::Afterglow)); @@ -46,7 +56,11 @@ public: template struct Builder_T; struct Builder; - std::weak_ptr color_bar_item; +private: + Afterglow_Private* d(); + Render_Object_Owner create_render_data() override; + Render_Object_Owner create_render_state() override; + Render_Object_Owner create_input_data() override; }; struct Afterglow_Prop { std::weak_ptr frequency_axis{}; @@ -56,9 +70,9 @@ struct Afterglow_Prop { int frequency_point_size{}; int power_point_size{}; int buffer_size{}; - bool interpolate = true; // 线性插值 - double attenuation_rate = 0.2; // 衰减率 衰减率为 1.0代表 不使用衰减 - int init_strong_value = 100; // 初始强度 + bool interpolate = true; + double attenuation_rate = 0.2; + int init_strong_value = 100; Color_Map color_map{}; bool color_bar_enabled = false; }; @@ -82,15 +96,11 @@ struct Afterglow::Builder_T { void set(Afterglow* t); std::shared_ptr parent{}; }; -struct LIB_DECL Afterglow::Builder : Afterglow::Builder_T, Afterglow_Prop{ - /// 构建并返回残影对象。 +/// @brief 残影频谱构造器。 +struct LIB_DECL Afterglow::Builder : Afterglow::Builder_T, Afterglow_Prop { + /// @brief 构建并返回残影对象。 std::shared_ptr build(); - /// 创建残影构造器。 - Builder(std::shared_ptr parent, std::shared_ptr frequency_axis, std::shared_ptr power_axis); - - - - - - }; + /// @brief 创建残影构造器。 + Builder(std::shared_ptr parent, std::shared_ptr frequency_axis, std::shared_ptr power_axis); +}; } // namespace renderive diff --git a/Renderive/plottable/Afterglow_p.h b/Renderive/plottable/Afterglow_p.h index 13d58c3..8709ac5 100644 --- a/Renderive/plottable/Afterglow_p.h +++ b/Renderive/plottable/Afterglow_p.h @@ -6,10 +6,10 @@ #include "../Axis/Frequency_Axis.h" #include "../Axis/Frequency_Axis_p.h" #include "../architecture/Bounded_Input_Buffer.h" -#include "../architecture/Plot_p.h" +#include "../plot/Plot_p.h" #include "../base/Memory.h" #include "../base/global.h" -#include "../primitive/Raster_Image.h" +#include "../primitive/Raster_Image_p.h" #include "Afterglow.h" namespace renderive { struct Afterglow_Render_State : Render_State, Afterglow_Prop {}; @@ -39,8 +39,8 @@ struct Afterglow_Input_Data : Input_Data { } }; struct Afterglow_Private : Typed_Render_Data { - friend class Afterglow; Raster_Image_Buffer image; + std::weak_ptr color_bar_item; bool cache_image_dirty = false; int cur_index = 0; int cache_frequency_point_size{}; @@ -165,7 +165,7 @@ void Afterglow::Builder_T::set(Afterglow* ret) { sc->init_strong_value = qMax(1, sc->init_strong_value); if (static_cast(this)->color_bar_enabled) { auto bar = Color_Bar::Builder(owner).set_color_map(sc->color_map).set_range(sc->power_range).set_name("Afterglow").build(); - ret->color_bar_item = bar; + pd->color_bar_item = bar; } } } // namespace renderive diff --git a/Renderive/plottable/Audio_Frequency.cpp b/Renderive/plottable/Audio_Frequency.cpp index d5b261f..e43d534 100644 --- a/Renderive/plottable/Audio_Frequency.cpp +++ b/Renderive/plottable/Audio_Frequency.cpp @@ -22,14 +22,17 @@ int Audio_Frequency::time_point_size(SRC src) { return d()->state_value(&Audio_Frequency_Render_State::time_point_size, src); } void Audio_Frequency::set_time_point_size(int value) { - d()->set_state_value(&Audio_Frequency_Render_State::time_point_size, qMax(1, value)); + value = qMax(1, value); + if (auto axis = time_axis()) + axis->set_time_point_size(value); + d()->set_state_value(&Audio_Frequency_Render_State::time_point_size, value); } std::shared_ptr Audio_Frequency::time_axis(SRC src) { return d()->state_value(&Audio_Frequency_Render_State::time_axis, src).lock(); } void Audio_Frequency::set_time_axis(std::shared_ptr value) { d()->set_state_value(&Audio_Frequency_Render_State::time_axis, std::weak_ptr(value)); - if (auto curve = curve_item.lock()) + if (auto curve = d()->curve_item.lock()) curve->set_x_axis(value); } std::shared_ptr Audio_Frequency::value_axis(SRC src) { @@ -37,7 +40,7 @@ std::shared_ptr Audio_Frequency::value_axis(SRC src) { } void Audio_Frequency::set_value_axis(std::shared_ptr value) { d()->set_state_value(&Audio_Frequency_Render_State::value_axis, std::weak_ptr(value)); - if (auto curve = curve_item.lock()) + if (auto curve = d()->curve_item.lock()) curve->set_y_axis(value); } Range Audio_Frequency::key_range(SRC src) { @@ -52,7 +55,7 @@ QColor Audio_Frequency::color(SRC src) { return d()->state_value(&Audio_Frequency_Render_State::color, src); } void Audio_Frequency::set_color(QColor value) { - if (auto curve = curve_item.lock()) { + if (auto curve = d()->curve_item.lock()) { QPen pen(value); pen.setWidth(0); curve->set_pen(pen); @@ -74,6 +77,9 @@ std::shared_ptr Audio_Frequency::Builder::build() { ret->init(parent); Audio_Frequency_Private* pd = ret->d(); Audio_Frequency_Render_State* sc = reinterpret_cast(pd->state(State_Edit)); + auto time_axis = this->time_axis.lock(); + if (!time_point_size_set && time_axis) + time_point_size = time_axis->time_point_size(); PROP_R(int, time_point_size) PROP_R(std::weak_ptr, time_axis) PROP_R(std::weak_ptr, value_axis) @@ -82,22 +88,35 @@ std::shared_ptr Audio_Frequency::Builder::build() { sc->time_point_size = qMax(1, sc->time_point_size); if (sc->key_range.length() == 0.0) sc->key_range = {0.0, 1.0}; - auto time_axis = this->time_axis.lock(); auto value_axis = this->value_axis.lock(); + if (time_axis) + time_axis->set_time_point_size(sc->time_point_size); if (time_axis && value_axis) { QPen pen(sc->color); pen.setWidth(0); auto curve = Curve::Builder(ret, time_axis, value_axis).set_pen(pen).build(); curve->object_name = "Audio_Frequency_Curve"; - ret->curve_item = curve; + pd->curve_item = curve; } return ret; } void Audio_Frequency::give_data(int tick, double data) { - Q_UNUSED(tick) if (!ok()) return; Render_Input_Lease input(d()); - input->data_list.push(data, Audio_Frequency_Input_Data::Max_Pending_Value_Count); + input->push(tick, data); +} +void Audio_Frequency::give_data(QTime time, double data) { + if (!ok()) + return; + auto axis = time_axis(); + if (!axis) + return; + auto stream = axis->timeline_stream(); + if (!stream) + return; + int tick = stream->push_time(time); + Render_Input_Lease input(d()); + input->push(tick, data); } } // namespace renderive diff --git a/Renderive/plottable/Audio_Frequency.h b/Renderive/plottable/Audio_Frequency.h index c4ea770..914eae2 100644 --- a/Renderive/plottable/Audio_Frequency.h +++ b/Renderive/plottable/Audio_Frequency.h @@ -4,25 +4,31 @@ namespace renderive { class Time_Axis; struct Audio_Frequency_Private; -/// 音频频率随时间变化的折线图元。 +/// @brief 音频频率随时间变化的折线图元。 class LIB_DECL Audio_Frequency : public Renderable { public: - /// 返回私有渲染数据。 + /// @brief 创建音频频率图元。 + Audio_Frequency(); + /// @brief 写入指定 tick 对应的音频频率值。 + void give_data(int tick, double data); + /// @brief 写入指定时间对应的音频频率值。 + void give_data(QTime time, double data); + /// @brief 时间窗口内保留的点数。 + PROP(int, time_point_size) + /// @brief 绑定时间轴。 + PROP(std::shared_ptr, time_axis) + /// @brief 绑定数值轴。 + PROP(std::shared_ptr, value_axis) + /// @brief 曲线数据值范围。 + PROP(Range, key_range) + /// @brief 曲线颜色。 + PROP(QColor, color) + struct Builder; +private: Audio_Frequency_Private* d(); Render_Object_Owner create_render_data() override; Render_Object_Owner create_render_state() override; Render_Object_Owner create_input_data() override; - Audio_Frequency(); - /// 写入一个 tick 和对应数值。 - void give_data(int tick, double data); - PROP(int, time_point_size) - PROP(std::shared_ptr, time_axis) - PROP(std::shared_ptr, value_axis) - PROP(Range, key_range) - PROP(QColor, color) - struct Builder; - friend struct Audio_Frequency_Private; - std::weak_ptr curve_item; }; struct Audio_Frequency_Prop { int time_point_size = 100; @@ -31,18 +37,21 @@ struct Audio_Frequency_Prop { Range key_range = {0, 20}; QColor color = Qt::yellow; }; -struct LIB_DECL Audio_Frequency::Builder : Audio_Frequency_Prop{ - PROP_B(int, time_point_size) +struct LIB_DECL Audio_Frequency::Builder : Audio_Frequency_Prop { PROP_B(std::shared_ptr, time_axis) PROP_B(std::shared_ptr, value_axis) PROP_B(Range, key_range) PROP_B(QColor, color) + Builder& set_time_point_size(int value) { + time_point_size = value; + time_point_size_set = true; + return *this; + } /// 创建音频频率构造器。 Builder(std::shared_ptr parent, std::shared_ptr time_axis, std::shared_ptr value_axis); /// 构建并返回音频频率对象。 std::shared_ptr build(); std::shared_ptr parent{}; - - + bool time_point_size_set{}; }; } // namespace renderive diff --git a/Renderive/plottable/Audio_Frequency_p.h b/Renderive/plottable/Audio_Frequency_p.h index 7d2d7f9..22683e6 100644 --- a/Renderive/plottable/Audio_Frequency_p.h +++ b/Renderive/plottable/Audio_Frequency_p.h @@ -1,89 +1,123 @@ #pragma once +#include #include #include #include "../Axis/Axis_p.h" #include "../Axis/Time_Axis_p.h" #include "../architecture/Bounded_Input_Buffer.h" -#include "../architecture/Plot_p.h" +#include "../plot/Plot_p.h" #include "../architecture/Renderable.h" #include "../base/Memory.h" #include "../primitive/Curve.h" +#include "Core/Base/RingBuffer.hpp" #include "Audio_Frequency.h" namespace renderive { +struct Audio_Frequency_Data { + int tick{}; + double power{}; +}; struct Audio_Frequency_Input_Data : Input_Data { - static constexpr std::size_t Max_Pending_Value_Count = 16; - Bounded_Double_Input_Buffer data_list; + static constexpr std::size_t Max_Pending_Value_Count = 256; + Bounded_Input_Buffer data_list; + void push(int tick, double power) { + Audio_Frequency_Data data{tick, power}; + data_list.push(&data, sizeof(data), Max_Pending_Value_Count); + } + template + void read_all(Handler handler) { + data_list.read_all([&handler](const void* data, std::size_t size) { + (void)size; + Audio_Frequency_Data value; + std::memcpy(&value, data, sizeof(value)); + handler(value); + }); + } void clear() override { data_list.clear(); } std::uint64_t get_pending_count() const override { - return data_list.pending_count(); + return data_list.get_pending_count(); } std::uint64_t get_dropped_count() const override { - return data_list.dropped_count(); + return data_list.get_dropped_count(); } std::uint64_t get_capacity_dropped_count() const override { - return data_list.capacity_dropped_count(); + return data_list.get_capacity_dropped_count(); } std::uint64_t rebuild_dropped_count() const override { - return data_list.rebuild_dropped_count(); + return data_list.get_rebuild_dropped_count(); } std::uint64_t consumer_dropped_count() const override { - return data_list.consumer_dropped_count(); + return data_list.get_consumer_dropped_count(); } std::uint64_t get_pushed_count() const override { - return data_list.pushed_count(); + return data_list.get_pushed_count(); } }; struct Audio_Frequency_Render_State : Render_State, Audio_Frequency_Prop {}; struct Audio_Frequency_Private : Typed_Render_Data { - friend class Audio_Frequency; Psc::StreamRingBuffer_ST buffer{memory_resource(Memory_Domain::Audio)}; + std::pmr::vector data_snapshot{memory_resource(Memory_Domain::Audio)}; std::pmr::vector power_snapshot{memory_resource(Memory_Domain::Audio)}; + std::weak_ptr curve_item; int data_count{}; int point_count{}; void resize_power_buffer(int count) { point_count = count; data_count = 0; + data_snapshot.resize(count); power_snapshot.resize(count); - buffer.init(sizeof(double) * count); + buffer.init(sizeof(Audio_Frequency_Data) * count); } - void push_power(double power) { + void push_power(const Audio_Frequency_Data& data) { if (data_count == point_count) { - double ignored; - std::size_t len = sizeof(double); + Audio_Frequency_Data ignored; + std::size_t len = sizeof(Audio_Frequency_Data); buffer.read(&ignored, len); --data_count; } - buffer.write(&power, sizeof(double)); + buffer.write(&data, sizeof(data)); ++data_count; } void snapshot_power_buffer() { - buffer.peek_best_effort(power_snapshot.data(), sizeof(double) * data_count); + buffer.peek_best_effort(data_snapshot.data(), sizeof(Audio_Frequency_Data) * data_count); + for (int i = 0; i < data_count; ++i) + power_snapshot[static_cast(i)] = data_snapshot[static_cast(i)].power; } - void prepare_data(const Plot_Render_Snapshot&) override { + void prepare_data(const Plot_Render_Snapshot& snapshot) override { sync_state_pipeline(); Audio_Frequency_Render_State* s = render_state(); Audio_Frequency_Input_Data* d = render_input_data(); - if (point_count != s->time_point_size) - resize_power_buffer(s->time_point_size); - d->data_list.read_all([this](double power) { - push_power(power); - }); - snapshot_power_buffer(); auto time_axis = s->time_axis.lock(); auto value_axis = s->value_axis.lock(); - if (time_axis && value_axis) { - int start = (int)time_axis->start_coord(); - std::pmr::vector points = Curve_Utils::create_points(data_count, [this, time_axis, start](int i) { - double* power = power_snapshot.data() + data_count - 1 - i; - int index = time_axis->d()->time_ticker.start_coord_to_end_coord ? start + i : start + time_axis->d()->time_ticker.time_point_size - i; - return QPointF(index, *power); - }); - if (auto curve = q()->curve_item.lock()) - curve->give_points(points, false); + if (!time_axis || !value_axis) + return; + auto timeline = snapshot.timeline_snapshot(time_axis->timeline_stream(SRC::Render)); + if (!timeline) + return; + if (point_count != timeline->time_point_size) + resize_power_buffer(timeline->time_point_size); + d->read_all([this](const Audio_Frequency_Data& data) { + push_power(data); + }); + snapshot_power_buffer(); + int lower = std::min(timeline->lower, timeline->upper()); + int upper = std::max(timeline->lower, timeline->upper()); + std::pmr::vector points(memory_resource(Memory_Domain::Curve)); + points.reserve(static_cast(data_count)); + for (int i = 0; i < data_count; ++i) { + const Audio_Frequency_Data& item = data_snapshot[static_cast(i)]; + if (item.tick >= lower && item.tick <= upper) + points.emplace_back(item.tick, item.power); } + if (auto curve = curve_item.lock()) + curve->give_points(points, false); clear_render_input(); } }; -} // namespace renderive +struct Audio_Frequency_Private_Access { + static Audio_Frequency_Private* data(Audio_Frequency* audio) { + return reinterpret_cast(audio->d_ptr); + } +}; +} diff --git a/Renderive/plottable/Hover_Info.h b/Renderive/plottable/Hover_Info.h index 40e2452..08f8da8 100644 --- a/Renderive/plottable/Hover_Info.h +++ b/Renderive/plottable/Hover_Info.h @@ -4,7 +4,9 @@ namespace renderive { struct Hover_Info_Render_State; struct Hover_Info_Renderable_Interface; +/// @brief 悬浮信息渲染状态。 struct LIB_DECL Hover_Info_Render_State { + /// @brief 销毁悬浮信息状态。 virtual ~Hover_Info_Render_State() = default; Hover_Info_Render_State() { hover_info_brush = QBrush(Qt::white); @@ -17,53 +19,70 @@ struct LIB_DECL Hover_Info_Render_State { bool hover_info_active = false; QPoint hover_info_pos; Hover_Info_Renderable_Interface* renderable{}; + /// @brief 绘制悬浮信息。 void draw_hover(QPainter* painter, Abs_Axis* h, Abs_Axis* v); + /// @brief 判断当前对象是否允许显示悬浮信息。 bool hover_ok(Renderable* able) const; }; +/// @brief 可提供悬浮信息的图元接口。 struct LIB_DECL Hover_Info_Renderable_Interface { + /// @brief 根据坐标轴和鼠标位置生成悬浮文本。 virtual std::vector create_hover_string(Abs_Axis* h, Abs_Axis* v, QPoint pos); + /// @brief 销毁悬浮信息接口。 virtual ~Hover_Info_Renderable_Interface() = default; + /// @brief 绘制悬浮信息。 virtual void draw_hover(QPainter* painter, Abs_Axis* h, Abs_Axis* v) { return hover_state(SRC::Render)->draw_hover(painter, h, v); } + /// @brief 判断指定图元是否允许显示悬浮信息。 virtual bool hover_ok(Renderable* able) { return hover_state(SRC::Render)->hover_ok(able); } + /// @brief 判断鼠标位置是否命中悬浮信息区域。 virtual bool hover_test(QPoint pos) { return true; } + /// @brief 返回是否启用悬浮信息。 bool use_hover_info(SRC src = SRC::Edit) { return hover_state(src)->use_hover_info; } + /// @brief 设置是否启用悬浮信息。 void set_use_hover_info(bool use) { Hover_Info_Render_State* sc = begin_hover_edit(); sc->use_hover_info = use; finish_hover_edit(); } + /// @brief 设置悬浮信息字体。 void set_hover_info_font(const QFont& font) { Hover_Info_Render_State* sc = begin_hover_edit(); sc->hover_info_font = font; finish_hover_edit(); } + /// @brief 返回悬浮信息字体。 QFont hover_info_font(SRC src = SRC::Edit) { return hover_state(src)->hover_info_font; } + /// @brief 返回悬浮信息背景刷。 QBrush hover_info_background_brush(SRC src = SRC::Edit) { return hover_state(src)->hover_info_brush; } + /// @brief 设置悬浮信息背景刷。 void set_hover_info_background_brush(const QBrush& brush) { Hover_Info_Render_State* sc = begin_hover_edit(); sc->hover_info_brush = brush.color().isValid() ? brush : Qt::NoBrush; finish_hover_edit(); } + /// @brief 返回悬浮信息文本画笔。 QPen hover_info_pen(SRC src = SRC::Edit) { return hover_state(src)->hover_info_pen; } + /// @brief 设置悬浮信息文本画笔。 void set_hover_info_pen(const QPen& pen) { Hover_Info_Render_State* sc = begin_hover_edit(); sc->hover_info_pen = pen.color().isValid() ? pen : Qt::NoPen; finish_hover_edit(); } + /// @brief 返回悬浮信息内容边距。 void get_hover_info_contents_margins(int& left, int& top, int& right, int& bottom, SRC src) { auto s = hover_state(src); left = s->hover_info_left; @@ -71,6 +90,7 @@ struct LIB_DECL Hover_Info_Renderable_Interface { right = s->hover_info_right; bottom = s->hover_info_bottom; } + /// @brief 设置悬浮信息内容边距。 void set_hover_info_contents_margins(int left, int top, int right, int bottom) { Hover_Info_Render_State* sc = begin_hover_edit(); sc->hover_info_left = left; @@ -79,10 +99,14 @@ struct LIB_DECL Hover_Info_Renderable_Interface { sc->hover_info_bottom = bottom; finish_hover_edit(); } + /// @brief 返回指定角色的悬浮信息状态。 virtual Hover_Info_Render_State* hover_state(SRC src) = 0; + /// @brief 开始编辑悬浮信息状态。 virtual Hover_Info_Render_State* begin_hover_edit() = 0; + /// @brief 结束编辑悬浮信息状态。 virtual void finish_hover_edit() = 0; }; +/// @brief 独立保存悬浮信息状态的可渲染接口。 struct LIB_DECL Hover_Info_Independent_Renderable : Hover_Info_Renderable_Interface { private: Hover_Info_Render_State s, sc; @@ -97,6 +121,7 @@ private: } void finish_hover_edit() override {} public: + /// @brief 同步独立状态并绘制悬浮信息。 void draw_hover(QPainter* painter, Abs_Axis* h, Abs_Axis* v) override { s.renderable = this; sc.renderable = this; @@ -104,6 +129,7 @@ public: Hover_Info_Renderable_Interface::draw_hover(painter, h, v); } }; +/// @brief 使用派生图元三缓冲状态保存悬浮信息的 mixin。 template struct LIB_DECL Hover_Info_Renderable : Hover_Info_Renderable_Interface { private: diff --git a/Renderive/plottable/Multi_Select_Rect.h b/Renderive/plottable/Multi_Select_Rect.h index 2562f71..b02e348 100644 --- a/Renderive/plottable/Multi_Select_Rect.h +++ b/Renderive/plottable/Multi_Select_Rect.h @@ -2,50 +2,51 @@ #include "global.h" namespace renderive { struct Multi_Select_Rect_Private; -/// 多区域框选图元。 +/// @brief 多区域框选图元。 class LIB_DECL Multi_Select_Rect : public Renderable { public: - /// 返回私有渲染数据。 + /// @brief 创建多区域框选图元。 + Multi_Select_Rect(); + /// @brief 设置水平坐标轴。 + void set_horizontal_axis(std::shared_ptr axis); + /// @brief 设置垂直坐标轴。 + void set_vertical_axis(std::shared_ptr axis); + /// @brief 返回标注字体。 + QFont font(SRC = SRC::Edit); + /// @brief 返回标注画笔。 + QPen font_pen(SRC = SRC::Edit); + /// @brief 返回矩形填充刷。 + QBrush rect_brush(SRC = SRC::Edit); + /// @brief 返回矩形边框画笔。 + QPen border_pen(SRC = SRC::Edit); + /// @brief 设置标注字体。 + void set_font(const QFont& font); + /// @brief 设置标注画笔。 + void set_font_pen(const QPen& pen); + /// @brief 设置矩形填充刷。 + void set_rect_brush(const QBrush& brush); + /// @brief 设置矩形边框画笔。 + void set_border_pen(const QPen& pen); + struct Builder; +private: Multi_Select_Rect_Private* d(); Render_Object_Owner create_render_data() override; Render_Object_Owner create_render_state() override; Render_Object_Owner create_input_data() override; - Multi_Select_Rect(); -public: - /// 设置水平坐标轴。 - void set_horizontal_axis(std::shared_ptr axis); - /// 设置垂直坐标轴。 - void set_vertical_axis(std::shared_ptr axis); - /// 返回标注字体。 - QFont font(SRC = SRC::Edit); - /// 返回标注画笔。 - QPen font_pen(SRC = SRC::Edit); - /// 返回矩形填充刷。 - QBrush rect_brush(SRC = SRC::Edit); - /// 返回矩形边框画笔。 - QPen border_pen(SRC = SRC::Edit); - /// 设置标注字体。 - void set_font(const QFont& font); - /// 设置标注画笔。 - void set_font_pen(const QPen& pen); - /// 设置矩形填充刷。 - void set_rect_brush(const QBrush& brush); - /// 设置矩形边框画笔。 - void set_border_pen(const QPen& pen); - struct Builder; }; +/// @brief 多区域框选构造器。 struct LIB_DECL Multi_Select_Rect::Builder { SETTER(QFont, font, QFont()) SETTER(QPen, font_pen, QPen(Qt::white)) SETTER(QBrush, rect_brush, QColor(0, 0, 255, 50)) SETTER(QPen, border_pen, []() { - QPen pen(Qt::white); - pen.setStyle(Qt::DashLine); - return pen; - }()) - /// 创建多区域框选构造器。 + QPen pen(Qt::white); + pen.setStyle(Qt::DashLine); + return pen; + }()) + /// @brief 创建多区域框选构造器。 Builder(std::shared_ptr parent, std::shared_ptr key_axis, std::shared_ptr value_axis); - /// 构建并返回多区域框选对象。 + /// @brief 构建并返回多区域框选对象。 std::shared_ptr build(); std::weak_ptr key_axis; std::weak_ptr value_axis{}; diff --git a/Renderive/plottable/Multi_Select_Rect_p.h b/Renderive/plottable/Multi_Select_Rect_p.h index 2b23f75..0d050be 100644 --- a/Renderive/plottable/Multi_Select_Rect_p.h +++ b/Renderive/plottable/Multi_Select_Rect_p.h @@ -16,7 +16,7 @@ struct Multi_Select_Rect_Render_State : renderive::Render_State { }; struct Multi_Select_Rect_Input_Data : renderive::Input_Data {}; struct Multi_Select_Rect_Private : renderive::Typed_Render_Data { - friend class Multi_Select_Rect; + bool active = false; bool select_test(const QPointF& pos) override { return true; diff --git a/Renderive/plottable/Performance_Shower.cpp b/Renderive/plottable/Performance_Shower.cpp index 71d83b5..98ae53c 100644 --- a/Renderive/plottable/Performance_Shower.cpp +++ b/Renderive/plottable/Performance_Shower.cpp @@ -25,7 +25,15 @@ std::string csv_escape(const std::string& value) { escaped.push_back('"'); return escaped; } -std::string renderable_cache_stats_text(const Frame_Metadata& frame) { +std::uint64_t cache_subtree_compose_time(const std::vector& stats, const Renderable_Cache_Frame_Stat& node) { + std::uint64_t compose_time_ns = node.compose_time_ns; + for (const auto& stat : stats) { + if (stat.parent_object_name == node.object_name && stat.tree_depth == node.tree_depth + 1) + compose_time_ns += cache_subtree_compose_time(stats, stat); + } + return compose_time_ns; +} +std::string renderable_cache_stats_text(const Frame_Lifecycle_Record& frame) { std::ostringstream stream; bool first = true; for (const auto& stat : frame.renderable_cache_stats) { @@ -33,104 +41,148 @@ std::string renderable_cache_stats_text(const Frame_Metadata& frame) { stream << '|'; first = false; QByteArray name = stat.object_name.toUtf8(); + QByteArray parent = stat.parent_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; + << ":parent=" << std::string(parent.constData(), static_cast(parent.size())) + << ";depth=" << stat.tree_depth + << ";hit=" << stat.hit_count + << ";stale=" << stat.stale_hit_count + << ";miss=" << stat.miss_count + << ";rebuild=" << stat.rebuild_count + << ";stale_rebuild=" << stat.stale_rebuild_count + << ";miss_rebuild=" << stat.miss_rebuild_count + << ";rebuild_ns=" << stat.rebuild_time_ns + << ";compose_ns=" << stat.compose_time_ns + << ";subtree_compose_ns=" << cache_subtree_compose_time(frame.renderable_cache_stats, stat) + << ";last_rebuild_ns=" << stat.last_rebuild_time_ns + << ";last_compose_ns=" << stat.last_compose_time_ns + << ";image_bytes=" << stat.last_image_bytes + << ";image_area=" << stat.last_image_area + << ";version=" << stat.last_edit_version << '/' << stat.last_ready_version; + } + return stream.str(); +} +std::string renderable_stats_text(const Frame_Lifecycle_Record& frame) { + std::ostringstream stream; + bool first = true; + for (const auto& stat : frame.renderable_stats) { + if (!first) + stream << '|'; + first = false; + QByteArray name = stat.object_name.toUtf8(); + QByteArray parent = stat.parent_object_name.toUtf8(); + stream << std::string(name.constData(), static_cast(name.size())) + << ":parent=" << std::string(parent.constData(), static_cast(parent.size())) + << ";depth=" << stat.tree_depth + << ";cache=" << (stat.cache_enabled ? 1 : 0) + << ";render=" << stat.render_count + << ";self_draw=" << stat.self_draw_count + << ";render_ns=" << stat.total_render_time_ns + << ";self_draw_ns=" << stat.self_draw_time_ns + << ";last_render_ns=" << stat.last_total_render_time_ns + << ";last_self_draw_ns=" << stat.last_self_draw_time_ns; } return stream.str(); } class Performance_Frame_Log { public: - void write(const Frame_Metadata& frame) { + void write(const Frame_Lifecycle_Record& frame) { if (!enabled()) return; - const Frame_Feedback_State& feedback = frame.feedback; + const Frame_Feedback_Snapshot& feedback = frame.feedback; std::ostringstream stream; stream << std::setprecision(9) - << 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)); + << frame.frame_id << ',' + << frame.input_version << ',' + << frame.snapshot_version << ',' + << frame.frame_slot_generation << ',' + << Psc::to_string(frame.frame_outcome) << ',' + << Psc::to_string(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 << ',' + << frame.renderable_cache_rebuild_count << ',' + << frame.renderable_cache_stale_rebuild_count << ',' + << frame.renderable_cache_miss_rebuild_count << ',' + << frame.renderable_cache_rebuild_time_ns << ',' + << frame.renderable_cache_compose_time_ns << ',' + << frame.renderable_cache_image_bytes << ',' + << frame.renderable_cache_image_area << ',' + << 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_stats_text(frame)) << ',' + << csv_escape(renderable_cache_stats_text(frame)); csv_log(path(), header(), stream.str()); } private: @@ -156,7 +208,7 @@ private: return value; } 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"; + 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_rebuild_count,cache_stale_rebuild_count,cache_miss_rebuild_count,cache_rebuild_time_ns,cache_compose_time_ns,cache_image_bytes,cache_image_area,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_stats,renderable_cache_stats"; } }; Performance_Frame_Log& frame_log() { @@ -181,15 +233,15 @@ Render_Object_Owner Performance_Shower::create_input_data() { return Performance_Shower_Binding::create_input_data(); } void Performance_Shower::setEnabled(bool enabled) { - enabled_value.store(enabled, std::memory_order_release); + d()->enabled_value.store(enabled, std::memory_order_release); 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); + return const_cast(this)->d()->enabled_value.load(std::memory_order_acquire); } -void Performance_Shower::collect_frame(const Frame_Metadata& frame) { +void Performance_Shower::collect_frame(const Frame_Lifecycle_Record& frame) { if (!enabled()) return; { @@ -198,7 +250,7 @@ void Performance_Shower::collect_frame(const Frame_Metadata& frame) { } write_frame_performance_log(frame); } -void write_frame_performance_log(const Frame_Metadata& frame) { +void write_frame_performance_log(const Frame_Lifecycle_Record& frame) { frame_log().write(frame); } } // namespace renderive diff --git a/Renderive/plottable/Performance_Shower_p.h b/Renderive/plottable/Performance_Shower_p.h index 40ceeee..efc0a79 100644 --- a/Renderive/plottable/Performance_Shower_p.h +++ b/Renderive/plottable/Performance_Shower_p.h @@ -8,8 +8,9 @@ #include "../architecture/Frame_Scheduler.h" #include "../architecture/Renderable.h" #include "Core/Statistics/Statistics.h" +#include "Renderive/architecture/Render_Lease.h" namespace renderive { -void write_frame_performance_log(const Frame_Metadata& frame); +void write_frame_performance_log(const Frame_Lifecycle_Record& frame); struct Performance_Shower_Render_State : Render_State { Performance_Shower_Render_State() { font.setStyleHint(QFont::Monospace); @@ -28,12 +29,12 @@ struct Performance_Shower_Render_State : Render_State { }; struct Performance_Shower_Input_Data : Input_Data { static constexpr std::size_t Capacity = 64; - std::array frames{}; + 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) { + void push(const Frame_Lifecycle_Record& frame) { if (count < Capacity) { frames[(first + count) % Capacity] = frame; ++count; @@ -67,6 +68,46 @@ struct Performance_Shower_Input_Data : Input_Data { return capacity_dropped; } }; +struct Renderable_Cache_Aggregate_Stat { + QString object_name; + QString parent_object_name; + int tree_depth{}; + std::uint64_t hit_count{}; + std::uint64_t stale_hit_count{}; + std::uint64_t miss_count{}; + std::uint64_t rebuild_count{}; + std::uint64_t stale_rebuild_count{}; + std::uint64_t miss_rebuild_count{}; + std::uint64_t rebuild_time_ns{}; + std::uint64_t compose_time_ns{}; + std::uint64_t subtree_compose_time_ns{}; + std::uint64_t last_rebuild_time_ns{}; + std::uint64_t last_compose_time_ns{}; + std::uint64_t last_subtree_compose_time_ns{}; + std::uint64_t last_image_bytes{}; + std::uint64_t last_image_area{}; + std::uint64_t last_edit_version{}; + std::uint64_t last_ready_version{}; + Psc::Value_Statistics rebuild_ms; + Psc::Value_Statistics compose_ms; + Psc::Value_Statistics subtree_compose_ms; +}; +struct Renderable_Aggregate_Stat { + QString object_name; + QString parent_object_name; + int tree_depth{}; + bool cache_enabled{}; + std::uint64_t render_count{}; + std::uint64_t self_draw_count{}; + std::uint64_t total_render_time_ns{}; + std::uint64_t self_draw_time_ns{}; + std::uint64_t last_total_render_time_ns{}; + std::uint64_t last_self_draw_time_ns{}; + Psc::Value_Statistics total_render_ms; + Psc::Value_Statistics self_draw_ms; + Psc::Value_Statistics total_render_ratio; + Psc::Value_Statistics self_draw_ratio; +}; struct Frame_Performance_Aggregate { Psc::Value_Statistics request_wait_ms; Psc::Value_Statistics render_queue_wait_ms; @@ -78,8 +119,13 @@ struct Frame_Performance_Aggregate { Psc::Value_Statistics paint_ms; Psc::Value_Statistics frame_age_ms; Psc::Value_Statistics frame_total_ms; - Frame_Metadata latest; + Psc::Value_Statistics cache_rebuild_ms; + Psc::Value_Statistics cache_compose_ms; + Psc::Value_Statistics cache_rebuild_ratio; + Psc::Value_Statistics cache_compose_ratio; + Frame_Lifecycle_Record latest; std::uint64_t observed_frame_count{}; + std::uint64_t render_time_ns{}; std::uint64_t produced_outcome_count{}; std::uint64_t consumed_outcome_count{}; std::uint64_t superseded_outcome_count{}; @@ -88,16 +134,24 @@ struct Frame_Performance_Aggregate { std::uint64_t cache_hit_count{}; std::uint64_t cache_stale_hit_count{}; std::uint64_t cache_miss_count{}; + std::uint64_t cache_rebuild_count{}; + std::uint64_t cache_stale_rebuild_count{}; + std::uint64_t cache_miss_rebuild_count{}; + std::uint64_t cache_rebuild_time_ns{}; + std::uint64_t cache_compose_time_ns{}; std::uint64_t input_capacity_dropped{}; bool has_frame{}; - std::vector renderable_cache_stats; + std::vector renderable_stats; + std::vector renderable_cache_stats; void clear() { *this = {}; } - void update(const Frame_Metadata& frame) { + void update(const Frame_Lifecycle_Record& frame) { latest = frame; has_frame = true; ++observed_frame_count; + std::uint64_t frame_render_time_ns = duration_ns(frame.render_begin_time, frame.render_end_time); + render_time_ns += frame_render_time_ns; 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); @@ -111,12 +165,59 @@ struct Frame_Performance_Aggregate { 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; + cache_rebuild_count += frame.renderable_cache_rebuild_count; + cache_stale_rebuild_count += frame.renderable_cache_stale_rebuild_count; + cache_miss_rebuild_count += frame.renderable_cache_miss_rebuild_count; + cache_rebuild_time_ns += frame.renderable_cache_rebuild_time_ns; + cache_compose_time_ns += frame.renderable_cache_compose_time_ns; + if (frame.renderable_cache_rebuild_time_ns) + cache_rebuild_ms.update(ns_to_ms(frame.renderable_cache_rebuild_time_ns)); + if (frame.renderable_cache_compose_time_ns) + cache_compose_ms.update(ns_to_ms(frame.renderable_cache_compose_time_ns)); + if (frame_render_time_ns) { + cache_rebuild_ratio.update(static_cast(frame.renderable_cache_rebuild_time_ns) * 100.0 / static_cast(frame_render_time_ns)); + cache_compose_ratio.update(static_cast(frame.renderable_cache_compose_time_ns) * 100.0 / static_cast(frame_render_time_ns)); + } + for (const auto& stat : frame.renderable_stats) { + Renderable_Aggregate_Stat& aggregate_stat = renderable_stat(stat.object_name, stat.parent_object_name, stat.tree_depth); + aggregate_stat.cache_enabled = stat.cache_enabled; + aggregate_stat.render_count += stat.render_count; + aggregate_stat.self_draw_count += stat.self_draw_count; + aggregate_stat.total_render_time_ns += stat.total_render_time_ns; + aggregate_stat.self_draw_time_ns += stat.self_draw_time_ns; + aggregate_stat.last_total_render_time_ns = stat.last_total_render_time_ns ? stat.last_total_render_time_ns : aggregate_stat.last_total_render_time_ns; + aggregate_stat.last_self_draw_time_ns = stat.last_self_draw_time_ns ? stat.last_self_draw_time_ns : aggregate_stat.last_self_draw_time_ns; + if (stat.total_render_time_ns) + aggregate_stat.total_render_ms.update(ns_to_ms(stat.total_render_time_ns)); + if (stat.self_draw_time_ns) + aggregate_stat.self_draw_ms.update(ns_to_ms(stat.self_draw_time_ns)); + if (frame_render_time_ns) { + aggregate_stat.total_render_ratio.update(static_cast(stat.total_render_time_ns) * 100.0 / static_cast(frame_render_time_ns)); + aggregate_stat.self_draw_ratio.update(static_cast(stat.self_draw_time_ns) * 100.0 / static_cast(frame_render_time_ns)); + } + } for (const auto& stat : frame.renderable_cache_stats) { - Renderable_Cache_Frame_Stat& aggregate_stat = cache_stat(stat.object_name); + Renderable_Cache_Aggregate_Stat& aggregate_stat = cache_stat(stat.object_name, stat.parent_object_name, stat.tree_depth); aggregate_stat.hit_count += stat.hit_count; aggregate_stat.stale_hit_count += stat.stale_hit_count; aggregate_stat.miss_count += stat.miss_count; + aggregate_stat.rebuild_count += stat.rebuild_count; + aggregate_stat.stale_rebuild_count += stat.stale_rebuild_count; + aggregate_stat.miss_rebuild_count += stat.miss_rebuild_count; + aggregate_stat.rebuild_time_ns += stat.rebuild_time_ns; + aggregate_stat.compose_time_ns += stat.compose_time_ns; + aggregate_stat.last_rebuild_time_ns = stat.last_rebuild_time_ns ? stat.last_rebuild_time_ns : aggregate_stat.last_rebuild_time_ns; + aggregate_stat.last_compose_time_ns = stat.last_compose_time_ns ? stat.last_compose_time_ns : aggregate_stat.last_compose_time_ns; + aggregate_stat.last_image_bytes = stat.last_image_bytes ? stat.last_image_bytes : aggregate_stat.last_image_bytes; + aggregate_stat.last_image_area = stat.last_image_area ? stat.last_image_area : aggregate_stat.last_image_area; + aggregate_stat.last_edit_version = stat.last_edit_version ? stat.last_edit_version : aggregate_stat.last_edit_version; + aggregate_stat.last_ready_version = stat.last_ready_version ? stat.last_ready_version : aggregate_stat.last_ready_version; + if (stat.rebuild_time_ns) + aggregate_stat.rebuild_ms.update(ns_to_ms(stat.rebuild_time_ns)); + if (stat.compose_time_ns) + aggregate_stat.compose_ms.update(ns_to_ms(stat.compose_time_ns)); } + update_cache_subtree_compose(frame.renderable_cache_stats); switch (frame.frame_outcome) { case Frame_Outcome::Produced: ++produced_outcome_count; @@ -136,37 +237,82 @@ struct Frame_Performance_Aggregate { } } private: - Renderable_Cache_Frame_Stat& cache_stat(const QString& object_name) { - for (auto& stat : renderable_cache_stats) { - if (stat.object_name == object_name) + Renderable_Aggregate_Stat& renderable_stat(const QString& object_name, const QString& parent_object_name, int tree_depth) { + for (auto& stat : renderable_stats) { + if (stat.object_name == object_name && stat.parent_object_name == parent_object_name && stat.tree_depth == tree_depth) return stat; } - renderable_cache_stats.push_back({object_name}); + Renderable_Aggregate_Stat stat; + stat.object_name = object_name; + stat.parent_object_name = parent_object_name; + stat.tree_depth = tree_depth; + renderable_stats.push_back(std::move(stat)); + return renderable_stats.back(); + } + void update_cache_subtree_compose(const std::vector& frame_stats) { + for (const auto& stat : frame_stats) { + std::uint64_t subtree_compose_time_ns = cache_subtree_compose_time(frame_stats, stat); + if (!subtree_compose_time_ns) + continue; + Renderable_Cache_Aggregate_Stat& aggregate_stat = cache_stat(stat.object_name, stat.parent_object_name, stat.tree_depth); + aggregate_stat.subtree_compose_time_ns += subtree_compose_time_ns; + aggregate_stat.last_subtree_compose_time_ns = subtree_compose_time_ns; + aggregate_stat.subtree_compose_ms.update(ns_to_ms(subtree_compose_time_ns)); + } + } + static std::uint64_t cache_subtree_compose_time(const std::vector& frame_stats, const Renderable_Cache_Frame_Stat& node) { + std::uint64_t compose_time_ns = node.compose_time_ns; + for (const auto& stat : frame_stats) { + if (stat.parent_object_name == node.object_name && stat.tree_depth == node.tree_depth + 1) + compose_time_ns += cache_subtree_compose_time(frame_stats, stat); + } + return compose_time_ns; + } + Renderable_Cache_Aggregate_Stat& cache_stat(const QString& object_name, const QString& parent_object_name, int tree_depth) { + for (auto& stat : renderable_cache_stats) { + if (stat.object_name == object_name && stat.parent_object_name == parent_object_name) + return stat; + } + Renderable_Cache_Aggregate_Stat stat; + stat.object_name = object_name; + stat.parent_object_name = parent_object_name; + stat.tree_depth = tree_depth; + renderable_cache_stats.push_back(std::move(stat)); return renderable_cache_stats.back(); } + static double ns_to_ms(std::uint64_t ns) { + return static_cast(ns) / 1000000.0; + } + static std::uint64_t duration_ns(std::uint64_t begin_ns, std::uint64_t end_ns) { + return end_ns > begin_ns ? end_ns - begin_ns : 0; + } 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; +/// @brief 后台渲染性能信息显示图元。 class Performance_Shower : public Renderable { public: + /// @brief 创建性能显示图元。 Performance_Shower(); + /// @brief 设置性能显示是否启用。 + void setEnabled(bool enabled); + /// @brief 返回性能显示是否启用。 + bool enabled() const; + /// @brief 收集一帧生命周期记录。 + void collect_frame(const Frame_Lifecycle_Record& frame); +private: Performance_Shower_Private* d(); Render_Object_Owner create_render_data() override; Render_Object_Owner create_render_state() override; Render_Object_Owner create_input_data() override; - void setEnabled(bool enabled); - bool enabled() const; - void collect_frame(const Frame_Metadata& frame); -private: - std::atomic_bool enabled_value{false}; }; struct Performance_Shower_Private : Typed_Render_Data { - friend class Performance_Shower; Frame_Performance_Aggregate aggregate; std::atomic_bool reset_requested{false}; + std::atomic_bool enabled_value{false}; std::vector lines; std::vector> stable_value_widths; QRect panel_rect; @@ -182,7 +328,7 @@ struct Performance_Shower_Private : Typed_Render_Datareset_pending) aggregate.clear(); aggregate.input_capacity_dropped += input->capacity_dropped; - input->read_all([this](const Frame_Metadata& frame) { + input->read_all([this](const Frame_Lifecycle_Record& frame) { aggregate.update(frame); }); clear_render_input(); @@ -298,18 +444,26 @@ private: } 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)); + .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 percent_stat_line(const QString& key, const QString& name, const Psc::Value_Statistics& statistics) { + return QString("%1 当前:%2% 平滑:%3% 波动:%4% P95:%5%") + .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 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)); + .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; @@ -349,14 +503,14 @@ private: } return "未知"; } - QString manual_limit_text(const Frame_Feedback_State& feedback) { + QString manual_limit_text(const Frame_Feedback_Snapshot& 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)); + .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) { + static double cache_total(const Renderable_Cache_Aggregate_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) { @@ -365,57 +519,210 @@ private: return stable_text(key, "0.00") + "%"; return number_text(key, static_cast(hit + stale_hit) * 100.0 / static_cast(total)) + "%"; } + QString ns_text(const QString& key, std::uint64_t ns) { + return number_text(key, static_cast(ns) / 1000000.0); + } + QString cache_key(const Renderable_Cache_Aggregate_Stat& stat) const { + return stat.parent_object_name + "/" + stat.object_name; + } + QString renderable_key(const Renderable_Aggregate_Stat& stat) const { + return stat.parent_object_name + "/" + stat.object_name + "/" + QString::number(stat.tree_depth); + } + QString cache_indent(int depth) const { + return QString(std::max(0, depth) * 2, QLatin1Char(' ')); + } + QString renderable_indent(int depth) const { + return QString(std::max(0, depth) * 2, QLatin1Char(' ')); + } + static QString cache_mode_text(bool cache_enabled) { + return cache_enabled ? "本地缓存" : "直接绘制"; + } + static std::uint64_t frame_render_time_ns(const Frame_Lifecycle_Record& frame) { + return frame.render_end_time > frame.render_begin_time ? frame.render_end_time - frame.render_begin_time : 0; + } + QString ns_ratio_text(const QString& key, std::uint64_t numerator, std::uint64_t denominator) { + if (!denominator) + return stable_text(key, "0.00") + "%"; + return number_text(key, static_cast(numerator) * 100.0 / static_cast(denominator)) + "%"; + } + bool should_show_cache(const Renderable_Cache_Aggregate_Stat& stat) const { + return cache_total(stat) > 0.0 || stat.rebuild_count || stat.rebuild_ms.times || stat.compose_ms.times || stat.subtree_compose_ms.times; + } + bool should_show_renderable(const Renderable_Aggregate_Stat& stat) const { + return stat.render_count || stat.self_draw_count || stat.total_render_ms.times || stat.self_draw_ms.times; + } + QString cache_duration_text(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)); + } + void append_cache_node_lines(const Renderable_Cache_Aggregate_Stat& stat) { + QString key = cache_key(stat); + QString indent = cache_indent(stat.tree_depth); + QString name = stat.object_name.isEmpty() ? "未命名元素" : stat.object_name; + lines.push_back(QString("缓存树 %1%2 命中:%3 过期可用:%4 未命中:%5 重建:%6 可用率:%7") + .arg(indent) + .arg(name) + .arg(count_text(key + "_cache_hit", stat.hit_count)) + .arg(count_text(key + "_cache_stale_hit", stat.stale_hit_count)) + .arg(count_text(key + "_cache_miss", stat.miss_count)) + .arg(count_text(key + "_cache_rebuild", stat.rebuild_count)) + .arg(cache_ratio_text(key + "_cache_ratio", stat.hit_count, stat.stale_hit_count, stat.miss_count))); + lines.push_back(QString("缓存图像 %1%2 面积:%3 字节:%4 版本:%5/%6") + .arg(indent) + .arg(name) + .arg(count_text(key + "_cache_image_area", stat.last_image_area)) + .arg(count_text(key + "_cache_image_bytes", stat.last_image_bytes)) + .arg(count_text(key + "_cache_edit_version", stat.last_edit_version)) + .arg(count_text(key + "_cache_ready_version", stat.last_ready_version))); + lines.push_back(QString("缓存重建 %1%2 %3") + .arg(indent) + .arg(name) + .arg(cache_duration_text(key + "_rebuild", "重建", stat.rebuild_ms))); + lines.push_back(QString("缓存合成 %1%2 %3") + .arg(indent) + .arg(name) + .arg(cache_duration_text(key + "_compose", "自身合成", stat.compose_ms))); + lines.push_back(QString("缓存子树 %1%2 %3") + .arg(indent) + .arg(name) + .arg(cache_duration_text(key + "_subtree_compose", "子树合成合计", stat.subtree_compose_ms))); + } + void append_renderable_node_lines(const Renderable_Aggregate_Stat& stat) { + QString key = renderable_key(stat); + QString indent = renderable_indent(stat.tree_depth); + QString name = "【" + (stat.object_name.isEmpty() ? "未命名元素" : stat.object_name) + "】"; + std::uint64_t latest_render_time = frame_render_time_ns(aggregate.latest); + QString name_indent = indent + QString(name.size(), QLatin1Char(' ')); + lines.push_back(QString("%1 %2 缓存:%3 渲染:%4 自身draw:%5 当前占:%6 累计占:%7") + .arg(indent) + .arg(name) + .arg(cache_mode_text(stat.cache_enabled)) + .arg(count_text(key + "_render_count", stat.render_count)) + .arg(count_text(key + "_self_draw_count", stat.self_draw_count)) + .arg(ns_ratio_text(key + "_current_total_ratio", stat.last_total_render_time_ns, latest_render_time)) + .arg(ns_ratio_text(key + "_aggregate_total_ratio", stat.total_render_time_ns, aggregate.render_time_ns))); + lines.push_back(QString("%1 总含子 %3") + .arg(name_indent) + .arg(duration_line(key + "_total_render", "耗时", stat.total_render_ms))); + lines.push_back(QString("%1 总占比 %3") + .arg(name_indent) + .arg(percent_stat_line(key + "_total_ratio", "占比", stat.total_render_ratio))); + lines.push_back(QString("%1 自身绘制 %3") + .arg(name_indent) + .arg(duration_line(key + "_self_draw", "耗时", stat.self_draw_ms))); + lines.push_back(QString("%1 自身占比 %3") + .arg(name_indent) + .arg(percent_stat_line(key + "_self_ratio", "占比", stat.self_draw_ratio))); + } + void append_renderable_tree_lines(const QString& parent_object_name, int tree_depth, std::vector& emitted) { + std::vector children; + for (const auto& stat : aggregate.renderable_stats) { + if (stat.parent_object_name == parent_object_name && stat.tree_depth == tree_depth && should_show_renderable(stat)) + children.push_back(&stat); + } + std::stable_sort(children.begin(), children.end(), [](const auto* a, const auto* b) { + return a->object_name < b->object_name; + }); + for (const auto* stat : children) { + if (std::find(emitted.begin(), emitted.end(), stat) != emitted.end()) + continue; + emitted.push_back(stat); + append_renderable_node_lines(*stat); + append_renderable_tree_lines(stat->object_name, stat->tree_depth + 1, emitted); + } + } + void append_renderable_tree_lines() { + std::vector emitted; + append_renderable_tree_lines({}, 0, emitted); + for (const auto& stat : aggregate.renderable_stats) { + if (!should_show_renderable(stat) || std::find(emitted.begin(), emitted.end(), &stat) != emitted.end()) + continue; + emitted.push_back(&stat); + append_renderable_node_lines(stat); + } + } + void append_cache_tree_lines(const QString& parent_object_name, int tree_depth, std::vector& emitted) { + std::vector children; + for (const auto& stat : aggregate.renderable_cache_stats) { + if (stat.parent_object_name == parent_object_name && stat.tree_depth == tree_depth && should_show_cache(stat)) + children.push_back(&stat); + } + std::stable_sort(children.begin(), children.end(), [](const auto* a, const auto* b) { + return a->object_name < b->object_name; + }); + for (const auto* stat : children) { + if (std::find(emitted.begin(), emitted.end(), stat) != emitted.end()) + continue; + emitted.push_back(stat); + append_cache_node_lines(*stat); + append_cache_tree_lines(stat->object_name, stat->tree_depth + 1, emitted); + } + } + void append_cache_tree_lines() { + std::vector emitted; + append_cache_tree_lines({}, 0, emitted); + for (const auto& stat : aggregate.renderable_cache_stats) { + if (!should_show_cache(stat) || std::find(emitted.begin(), emitted.end(), &stat) != emitted.end()) + continue; + emitted.push_back(&stat); + append_cache_node_lines(stat); + } + } void rebuild_lines() { if (!aggregate.has_frame) { lines = {"等待帧统计"}; return; } - const Frame_Metadata& frame = aggregate.latest; - const Frame_Feedback_State& feedback = frame.feedback; + const Frame_Lifecycle_Record& frame = aggregate.latest; + const Frame_Feedback_Snapshot& feedback = frame.feedback; std::uint64_t aggregate_cache_total = aggregate.cache_hit_count + aggregate.cache_stale_hit_count + aggregate.cache_miss_count; + std::uint64_t latest_render_time = frame_render_time_ns(frame); 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))); + .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("生产策略:低延迟优先"); + .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(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))); + .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))); + .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))); + .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))); + .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))); + .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))); + .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)); @@ -427,40 +734,46 @@ private: 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))); - } + .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))); + lines.push_back(QString("缓存重建 次数:%1 过期重建:%2 未命中重建:%3 重建耗时:%4ms 合成耗时:%5ms") + .arg(count_text("cache_rebuild_count", aggregate.cache_rebuild_count)) + .arg(count_text("cache_stale_rebuild_count", aggregate.cache_stale_rebuild_count)) + .arg(count_text("cache_miss_rebuild_count", aggregate.cache_miss_rebuild_count)) + .arg(ns_text("cache_rebuild_time", aggregate.cache_rebuild_time_ns)) + .arg(ns_text("cache_compose_time", aggregate.cache_compose_time_ns))); + lines.push_back(duration_line("cache_rebuild_slide", "缓存总耗时 重建", aggregate.cache_rebuild_ms)); + lines.push_back(duration_line("cache_compose_slide", "缓存总耗时 多Image合成", aggregate.cache_compose_ms)); + lines.push_back(percent_stat_line("cache_rebuild_ratio_slide", "缓存重建占后台生产", aggregate.cache_rebuild_ratio)); + lines.push_back(percent_stat_line("cache_compose_ratio_slide", "缓存合成占后台生产", aggregate.cache_compose_ratio)); + lines.push_back(QString("缓存当前占比 重建:%1 合成:%2 累计重建:%3 累计合成:%4") + .arg(ns_ratio_text("cache_rebuild_current_ratio", frame.renderable_cache_rebuild_time_ns, latest_render_time)) + .arg(ns_ratio_text("cache_compose_current_ratio", frame.renderable_cache_compose_time_ns, latest_render_time)) + .arg(ns_ratio_text("cache_rebuild_aggregate_ratio", aggregate.cache_rebuild_time_ns, aggregate.render_time_ns)) + .arg(ns_ratio_text("cache_compose_aggregate_ratio", aggregate.cache_compose_time_ns, aggregate.render_time_ns))); + append_renderable_tree_lines(); + append_cache_tree_lines(); 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))); + .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))); + .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))); + .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))); } void update_content_width(const QFontMetrics& metrics, Performance_Shower_Render_State* state) { int max_width = 0; diff --git a/Renderive/plottable/Planisphere.cpp b/Renderive/plottable/Planisphere.cpp index f2c848b..deade24 100644 --- a/Renderive/plottable/Planisphere.cpp +++ b/Renderive/plottable/Planisphere.cpp @@ -24,24 +24,26 @@ std::shared_ptr Planisphere::i_axis(SRC src) { return d()->state_value(&Planisphere_Render_State::i_axis, src).lock(); } void Planisphere::set_i_axis(std::shared_ptr value) { - d()->set_state_value(&Planisphere_Render_State::i_axis, std::weak_ptr(value)); - if (auto points = point_set.lock()) + Planisphere_Private* pd = d(); + pd->set_state_value(&Planisphere_Render_State::i_axis, std::weak_ptr(value)); + if (auto points = pd->point_set.lock()) points->set_x_axis(value); - if (auto anchors = anchor_set.lock()) + if (auto anchors = pd->anchor_set.lock()) anchors->set_x_axis(value); - if (auto grid = this->grid.lock()) + if (auto grid = pd->grid.lock()) grid->set_x_axis(value); } std::shared_ptr Planisphere::q_axis(SRC src) { return d()->state_value(&Planisphere_Render_State::q_axis, src).lock(); } void Planisphere::set_q_axis(std::shared_ptr value) { - d()->set_state_value(&Planisphere_Render_State::q_axis, std::weak_ptr(value)); - if (auto points = point_set.lock()) + Planisphere_Private* pd = d(); + pd->set_state_value(&Planisphere_Render_State::q_axis, std::weak_ptr(value)); + if (auto points = pd->point_set.lock()) points->set_y_axis(value); - if (auto anchors = anchor_set.lock()) + if (auto anchors = pd->anchor_set.lock()) anchors->set_y_axis(value); - if (auto grid = this->grid.lock()) + if (auto grid = pd->grid.lock()) grid->set_y_axis(value); } Range Planisphere::i_range(SRC src) { @@ -50,7 +52,7 @@ Range Planisphere::i_range(SRC src) { void Planisphere::set_i_range(Range value) { if (value.length() == 0.0) return; - if (auto grid = this->grid.lock()) + if (auto grid = d()->grid.lock()) grid->set_x_range(value); d()->set_state_value(&Planisphere_Render_State::i_range, std::move(value)); } @@ -60,7 +62,7 @@ Range Planisphere::q_range(SRC src) { void Planisphere::set_q_range(Range value) { if (value.length() == 0.0) return; - if (auto grid = this->grid.lock()) + if (auto grid = d()->grid.lock()) grid->set_y_range(value); d()->set_state_value(&Planisphere_Render_State::q_range, std::move(value)); } @@ -68,7 +70,7 @@ QColor Planisphere::point_color(SRC src) { return d()->state_value(&Planisphere_Render_State::point_color, src); } void Planisphere::set_point_color(QColor value) { - if (auto points = point_set.lock()) + if (auto points = d()->point_set.lock()) points->set_brush(QBrush(value)); d()->set_state_value(&Planisphere_Render_State::point_color, std::move(value)); } @@ -76,7 +78,7 @@ QColor Planisphere::anchor_color(SRC src) { return d()->state_value(&Planisphere_Render_State::anchor_color, src); } void Planisphere::set_anchor_color(QColor value) { - if (auto anchors = anchor_set.lock()) + if (auto anchors = d()->anchor_set.lock()) anchors->set_pen(QPen(value, 2)); d()->set_state_value(&Planisphere_Render_State::anchor_color, std::move(value)); } @@ -135,9 +137,9 @@ std::shared_ptr Planisphere::Builder::build() { grid->object_name = "PlanisphereGrid"; points->object_name = "PlanispherePoints"; anchors->object_name = "PlanisphereAnchors"; - ret->grid = grid; - ret->point_set = points; - ret->anchor_set = anchors; + pd->grid = grid; + pd->point_set = points; + pd->anchor_set = anchors; } return ret; } diff --git a/Renderive/plottable/Planisphere.h b/Renderive/plottable/Planisphere.h index 9d742c1..4f794e0 100644 --- a/Renderive/plottable/Planisphere.h +++ b/Renderive/plottable/Planisphere.h @@ -2,38 +2,44 @@ #include "global.h" #include "../primitive/Point_Set.h" namespace renderive { -/// 星座图调制类型。 +/// @brief 星座图调制类型。 enum class Planisphere_Type { - /// 8PSK 星座图。 + /// @brief 8PSK 星座图。 Psk8 = 8 }; struct Planisphere_Private; class Grid; -/// I/Q 星座图元。 +/// @brief I/Q 星座图元。 class LIB_DECL Planisphere : public Renderable { public: - /// 返回私有渲染数据。 + /// @brief 创建 I/Q 星座图元。 + Planisphere(); + /// @brief 写入一个 I/Q 点。 + void give_data(QPointF pos); + /// @brief 绑定 I 轴。 + PROP(std::shared_ptr, i_axis) + /// @brief 绑定 Q 轴。 + PROP(std::shared_ptr, q_axis) + /// @brief I 轴星座范围。 + PROP(Range, i_range) + /// @brief Q 轴星座范围。 + PROP(Range, q_range) + /// @brief 输入点颜色。 + PROP(QColor, point_color) + /// @brief 锚点颜色。 + PROP(QColor, anchor_color) + /// @brief 输入点保留时间。 + PROP(int, continue_millisecond) + /// @brief 星座调制类型。 + PROP(Planisphere_Type, type) + /// @brief 设置坐标轴到星座图中心范围。 + void set_to_axis_center(); + struct Builder; +private: Planisphere_Private* d(); Render_Object_Owner create_render_data() override; Render_Object_Owner create_render_state() override; Render_Object_Owner create_input_data() override; - Planisphere(); - /// 写入一个 I/Q 点。 - void give_data(QPointF pos); - PROP(std::shared_ptr, i_axis) - PROP(std::shared_ptr, q_axis) - PROP(Range, i_range) - PROP(Range, q_range) - PROP(QColor, point_color) - PROP(QColor, anchor_color) - PROP(int, continue_millisecond) - PROP(Planisphere_Type, type) - /// 设置坐标轴到星座图中心范围。 - void set_to_axis_center(); - struct Builder; - std::weak_ptr grid; - std::weak_ptr point_set; - std::weak_ptr anchor_set; }; struct Planisphere_Prop { std::weak_ptr i_axis{}; @@ -45,7 +51,8 @@ struct Planisphere_Prop { int continue_millisecond = 1000; Planisphere_Type type = Planisphere_Type::Psk8; }; -struct LIB_DECL Planisphere::Builder : Planisphere_Prop{ +/// @brief 星座图构造器。 +struct LIB_DECL Planisphere::Builder : Planisphere_Prop { PROP_B(std::shared_ptr, parent) PROP_B(std::shared_ptr, i_axis) PROP_B(std::shared_ptr, q_axis) @@ -60,6 +67,5 @@ struct LIB_DECL Planisphere::Builder : Planisphere_Prop{ /// 构建并返回星座图对象。 std::shared_ptr build(); std::shared_ptr parent{}; - }; } // namespace renderive diff --git a/Renderive/plottable/Planisphere_p.h b/Renderive/plottable/Planisphere_p.h index 38bf4e7..448b9e0 100644 --- a/Renderive/plottable/Planisphere_p.h +++ b/Renderive/plottable/Planisphere_p.h @@ -5,7 +5,7 @@ #include #include "../Axis/Axis.h" #include "../architecture/Bounded_Input_Buffer.h" -#include "../architecture/Plot_p.h" +#include "../plot/Plot_p.h" #include "../base/Frame_Memory.h" #include "../base/Memory.h" #include "Planisphere.h" @@ -59,7 +59,9 @@ struct Planisphere_Input_Data : Input_Data { }; struct Planisphere_Render_State : Render_State, Planisphere_Prop {}; struct Planisphere_Private : Typed_Render_Data { - friend class Planisphere; + std::weak_ptr grid; + std::weak_ptr point_set; + std::weak_ptr anchor_set; std::pmr::list data_list{memory_resource(Memory_Domain::Planisphere)}; std::pmr::vector anchor_points(const Planisphere_Render_State* s) const { std::pmr::vector points(frame_memory_resource()); @@ -112,9 +114,9 @@ struct Planisphere_Private : Typed_Render_Datapoint_set.lock()) + if (auto point_set = this->point_set.lock()) point_set->give_colored_points(points, colors, false); - if (auto anchor_set = q()->anchor_set.lock()) + if (auto anchor_set = this->anchor_set.lock()) anchor_set->give_points(anchor_points(s), false); } void draw(QPainter* painter, const Plot_Render_Snapshot&) override { @@ -126,4 +128,9 @@ struct Planisphere_Private : Typed_Render_Data(planisphere->d_ptr); + } +}; } // namespace renderive diff --git a/Renderive/plottable/Spectrum.cpp b/Renderive/plottable/Spectrum.cpp index b5efcde..f4cc346 100644 --- a/Renderive/plottable/Spectrum.cpp +++ b/Renderive/plottable/Spectrum.cpp @@ -28,9 +28,9 @@ std::shared_ptr Spectrum::frequency_axis(SRC src) { } void Spectrum::set_frequency_axis(std::shared_ptr value) { d()->set_state_value(&Spectrum_Render_State::frequency_axis, std::weak_ptr(value)); - if (auto item = marker_item.lock()) + if (auto item = d()->marker_item.lock()) item->set_x_axis(value); - if (auto item = label_item.lock()) + if (auto item = d()->label_item.lock()) item->set_x_axis(value); } std::shared_ptr Spectrum::power_axis(SRC src) { @@ -38,9 +38,9 @@ std::shared_ptr Spectrum::power_axis(SRC src) { } void Spectrum::set_power_axis(std::shared_ptr value) { d()->set_state_value(&Spectrum_Render_State::power_axis, std::weak_ptr(value)); - if (auto item = marker_item.lock()) + if (auto item = d()->marker_item.lock()) item->set_y_axis(value); - if (auto item = label_item.lock()) + if (auto item = d()->label_item.lock()) item->set_y_axis(value); } int Spectrum::frequency_point_size(SRC src) { @@ -232,8 +232,8 @@ std::shared_ptr Spectrum::Builder::build() { auto label_item = Label::Builder(ret, frequency_axis, power_axis).build(); marker_item->object_name = "SpectrumMarker"; label_item->object_name = "SpectrumLabel"; - ret->marker_item = marker_item; - ret->label_item = label_item; + pd->marker_item = marker_item; + pd->label_item = label_item; } return ret; } @@ -350,9 +350,9 @@ void Spectrum_Private::update_marker_items(const Spectrum_Render_State* s) { std::pmr::vector marker_items(memory_resource(Memory_Domain::Other)); std::pmr::vector label_items(memory_resource(Memory_Domain::Other)); if (!frequency_axis || !power_axis) { - if (auto marker_item = q()->marker_item.lock()) + if (auto marker_item = this->marker_item.lock()) marker_item->give_items(std::move(marker_items), false); - if (auto label_item = q()->label_item.lock()) + if (auto label_item = this->label_item.lock()) label_item->give_items(std::move(label_items), false); return; } @@ -389,9 +389,9 @@ void Spectrum_Private::update_marker_items(const Spectrum_Render_State* s) { if (ok) append_marker(custom_marker.frequency, value, custom_marker.draw_line, i == s->cur_select_index); } - if (auto marker_item = q()->marker_item.lock()) + if (auto marker_item = this->marker_item.lock()) marker_item->give_items(std::move(marker_items), false); - if (auto label_item = q()->label_item.lock()) + if (auto label_item = this->label_item.lock()) label_item->give_items(std::move(label_items), false); } void Spectrum_Private::draw_spectrum(QPainter* painter, Spectrum_Render_State* s) { diff --git a/Renderive/plottable/Spectrum.h b/Renderive/plottable/Spectrum.h index 0249c56..b174718 100644 --- a/Renderive/plottable/Spectrum.h +++ b/Renderive/plottable/Spectrum.h @@ -16,44 +16,68 @@ enum class Spectrum_Sample_Mode { Source, Pixel_Peak_Bucket }; -/// 单条频谱线图元,支持可见范围裁剪和多种线插值策略。 +/// @brief 单条频谱线图元,支持可见范围裁剪和多种线插值策略。 class LIB_DECL Spectrum : public Renderable, public Hover_Info_Renderable { public: - /// 返回私有渲染数据。 - Spectrum_Private* d(); - Render_Object_Owner create_render_data() override; - Render_Object_Owner create_render_state() override; - Render_Object_Owner create_input_data() override; + /// @brief 创建频谱线图元。 Spectrum(); + /// @brief 绑定频率轴。 PROP(std::shared_ptr, frequency_axis) + /// @brief 绑定功率轴。 PROP(std::shared_ptr, power_axis); + /// @brief 频率采样点数。 PROP(int, frequency_point_size); + /// @brief 频率数据范围。 PROP(Range, frequency_range); + /// @brief 中心扫频频率。 PROP(double, middle_sweep_frequency); + /// @brief 扫频高亮频率范围。 PROP(Range, sweep_frequency_range); + /// @brief 是否绘制最大保持线。 PROP(bool, use_max_line); + /// @brief 是否绘制最小保持线。 PROP(bool, use_min_line); + /// @brief 是否显示最大值标记。 PROP(bool, use_max_marker); + /// @brief 是否显示最小值标记。 PROP(bool, use_min_marker); + /// @brief 是否显示扫频高亮区域。 PROP(bool, use_sweep_frequency_rect); + /// @brief 是否只生成当前可见频率范围。 PROP(bool, visible_range_only); + /// @brief 折线插值模式。 PROP(Line_Interpolation_Mode, interpolation_mode); + /// @brief 采样降采样模式。 PROP(Spectrum_Sample_Mode, sample_mode); + /// @brief 最大保持线填充刷。 PROP(QBrush, max_brush); + /// @brief 当前频谱线填充刷。 PROP(QBrush, cur_brush); + /// @brief 最小保持线填充刷。 PROP(QBrush, min_brush); + /// @brief 最大保持线画笔。 PROP(QPen, max_pen); + /// @brief 当前频谱线画笔。 PROP(QPen, cur_pen); + /// @brief 最小保持线画笔。 PROP(QPen, min_pen); + /// @brief 选中标记画笔。 PROP(QPen, select_marker_pen); + /// @brief 普通标记画笔。 PROP(QPen, marker_pen); + /// @brief 中心频率线画笔。 PROP(QPen, middle_frequency_pen); + /// @brief 扫频区域填充刷。 PROP(QBrush, sweep_rect_brush); - /// 写入一帧频谱线数据。 + /// @brief 写入一帧频谱线数据。 void give_data(std::span line_data); + /// @brief 移动写入一帧频谱线数据。 void give_data(std::pmr::vector&& line_data); + /// @brief 提交一帧频谱线数据并返回完成票据。 Update_Ticket submit_data(std::span line_data, const Submit_Options& options = {}); + /// @brief 移动提交一帧频谱线数据并返回完成票据。 Update_Ticket submit_data(std::pmr::vector&& line_data, const Submit_Options& options = {}); + /// @brief 用 writer 填充并写入一帧频谱线数据。 template void write_data(std::size_t count, Writer&& writer) { std::pmr::vector values(memory_resource(Memory_Domain::Spectrum)); @@ -92,8 +116,11 @@ public: /// 设置当前选中标记频率。 void set_current_marker_frequency(double frequency); struct Builder; - std::weak_ptr marker_item; - std::weak_ptr