From 2df8959ed63f762ec78cd8694bf68b07a3058fad Mon Sep 17 00:00:00 2001 From: wyc <1104749580@qq.com> Date: Sun, 2 Aug 2026 14:20:27 +0800 Subject: [PATCH] =?UTF-8?q?=E7=AD=96=E7=95=A5=E6=8A=BD=E7=A6=BB?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Core/Axis/Abs_Axis.cpp | 9 +- Core/Axis/Abs_Axis_p.h | 48 +- Core/Axis/Axis_p.h | 4 +- Core/Axis/Frequency_Axis_p.h | 10 +- Core/Axis/Time_Axis.cpp | 4 +- Core/Axis/Time_Axis_p.h | 18 +- Core/architecture/Frame_Scheduler.cpp | 194 -------- Core/architecture/Frame_Scheduler.h | 50 +- Core/architecture/Plot_Render_Context.cpp | 19 - Core/architecture/Plot_Render_Context.h | 20 +- Core/architecture/Render_Config.h | 27 - Core/architecture/Renderable.cpp | 64 ++- Core/architecture/Renderable.h | 44 +- Core/base/global.cpp | 8 +- Core/flow/Flow_Diagnostics.h | 28 ++ Core/flow/Frame_Flow.h | 45 ++ Core/flow/Frame_Flow_Host.h | 26 + Core/flow/Present_Surface_Lease.cpp | 47 ++ Core/flow/Present_Surface_Lease.h | 51 ++ Core/flow/Render_Surface.h | 42 ++ Core/flow/Render_Ticket.h | 8 + Core/flow/explicit/Explicit_Frame_Flow.cpp | 275 ++++++++++ Core/flow/explicit/Explicit_Frame_Flow.h | 58 +++ .../Low_Latency_Feedback_Controller.cpp | 195 ++++++++ .../Low_Latency_Feedback_Controller.h | 53 ++ .../low_latency/Low_Latency_Frame_Flow.cpp | 268 ++++++++++ .../flow/low_latency/Low_Latency_Frame_Flow.h | 48 ++ .../Low_Latency_Frame_Pipeline.cpp} | 89 ++-- .../low_latency/Low_Latency_Frame_Pipeline.h} | 59 ++- Core/flow/low_latency/Low_Latency_Roles.h | 28 ++ .../low_latency}/Render_Data.cpp | 36 +- .../low_latency}/Render_Data.h | 49 +- .../low_latency}/Render_Lease.h | 11 +- .../low_latency}/Triple_Buffer.cpp | 0 .../low_latency}/Triple_Buffer.h | 24 +- Core/plot/Explicit_Refresh_Strategy.cpp | 25 - Core/plot/Explicit_Refresh_Strategy.h | 20 - Core/plot/Latency_Eager_Refresh_Strategy.cpp | 90 ---- Core/plot/Latency_Eager_Refresh_Strategy.h | 28 -- Core/plot/Plot_Core.cpp | 257 +++++----- Core/plot/Plot_Core.h | 20 +- Core/plot/Present_Frame_Lease.cpp | 21 - Core/plot/Present_Frame_Lease.h | 23 - Core/plot/Refresh_Strategy.h | 43 -- Core/plottable/Afterglow_p.h | 6 +- Core/plottable/Constellation_Diagram.cpp | 4 +- Core/plottable/Constellation_Diagram_p.h | 6 +- Core/plottable/Frequency_Trace.cpp | 2 +- Core/plottable/Frequency_Trace_p.h | 4 +- Core/plottable/Hover_Tooltip.h | 33 +- Core/plottable/Interpolation_p.h | 30 +- Core/plottable/Performance_Overlay.cpp | 10 +- Core/plottable/Performance_Overlay_p.h | 470 +++++++++--------- .../plottable/Selection_Rectangle_Overlay.cpp | 12 +- .../plottable/Selection_Rectangle_Overlay_p.h | 2 +- Core/plottable/Spectrum.cpp | 10 +- Core/plottable/Spectrum_p.h | 4 +- Core/plottable/Sweep_Spectrum.cpp | 2 +- Core/plottable/Sweep_Spectrum_p.h | 4 +- Core/plottable/Waterfall.cpp | 2 +- Core/plottable/Waterfall_p.h | 88 +++- Core/primitive/Band_Region.cpp | 4 +- Core/primitive/Color_Bar.cpp | 6 +- Core/primitive/Curve.cpp | 102 ++-- Core/primitive/Curve_Utils_p.h | 54 +- Core/primitive/Fill_Area.cpp | 6 +- Core/primitive/Grid.cpp | 4 +- Core/primitive/Label.cpp | 6 +- Core/primitive/Marker.cpp | 6 +- Core/primitive/Point_Set.cpp | 6 +- Core/primitive/Raster_Image.cpp | 22 +- Core/primitive/Raster_Image_p.h | 2 +- Core/render/Render_Frame_Snapshot.cpp | 27 + Core/render/Render_Frame_Snapshot.h | 34 ++ Core/render/Render_Output.cpp | 10 + Core/render/Render_Output.h | 17 + Core/render/Renderable_Frame_Node.h | 21 + Qt/plot/Explicit_Plot.cpp | 14 +- Qt/plot/Explicit_Plot.h | 3 +- Qt/plot/Explicit_Plot_p.h | 4 +- Qt/plot/Latency_Eager_Plot.cpp | 31 +- Qt/plot/Latency_Eager_Plot.h | 6 +- Qt/plot/Latency_Eager_Plot_p.h | 4 +- Qt/plot/Plot.cpp | 30 +- Qt/plot/Plot.h | 4 - Qt/plot/Plot_p.h | 6 +- test/Renderive_Core_Tests.cpp | 240 ++++++++- 87 files changed, 2454 insertions(+), 1390 deletions(-) create mode 100644 Core/flow/Flow_Diagnostics.h create mode 100644 Core/flow/Frame_Flow.h create mode 100644 Core/flow/Frame_Flow_Host.h create mode 100644 Core/flow/Present_Surface_Lease.cpp create mode 100644 Core/flow/Present_Surface_Lease.h create mode 100644 Core/flow/Render_Surface.h create mode 100644 Core/flow/Render_Ticket.h create mode 100644 Core/flow/explicit/Explicit_Frame_Flow.cpp create mode 100644 Core/flow/explicit/Explicit_Frame_Flow.h create mode 100644 Core/flow/low_latency/Low_Latency_Feedback_Controller.cpp create mode 100644 Core/flow/low_latency/Low_Latency_Feedback_Controller.h create mode 100644 Core/flow/low_latency/Low_Latency_Frame_Flow.cpp create mode 100644 Core/flow/low_latency/Low_Latency_Frame_Flow.h rename Core/{plot/Plot_Frame_Pipeline.cpp => flow/low_latency/Low_Latency_Frame_Pipeline.cpp} (63%) rename Core/{plot/Plot_Frame_Pipeline.h => flow/low_latency/Low_Latency_Frame_Pipeline.h} (55%) create mode 100644 Core/flow/low_latency/Low_Latency_Roles.h rename Core/{architecture => flow/low_latency}/Render_Data.cpp (58%) rename Core/{architecture => flow/low_latency}/Render_Data.h (86%) rename Core/{architecture => flow/low_latency}/Render_Lease.h (96%) rename Core/{architecture => flow/low_latency}/Triple_Buffer.cpp (100%) rename Core/{architecture => flow/low_latency}/Triple_Buffer.h (96%) delete mode 100644 Core/plot/Explicit_Refresh_Strategy.cpp delete mode 100644 Core/plot/Explicit_Refresh_Strategy.h delete mode 100644 Core/plot/Latency_Eager_Refresh_Strategy.cpp delete mode 100644 Core/plot/Latency_Eager_Refresh_Strategy.h delete mode 100644 Core/plot/Present_Frame_Lease.cpp delete mode 100644 Core/plot/Present_Frame_Lease.h delete mode 100644 Core/plot/Refresh_Strategy.h create mode 100644 Core/render/Render_Frame_Snapshot.cpp create mode 100644 Core/render/Render_Frame_Snapshot.h create mode 100644 Core/render/Render_Output.cpp create mode 100644 Core/render/Render_Output.h create mode 100644 Core/render/Renderable_Frame_Node.h diff --git a/Core/Axis/Abs_Axis.cpp b/Core/Axis/Abs_Axis.cpp index 7bc1e4b..d64bc64 100644 --- a/Core/Axis/Abs_Axis.cpp +++ b/Core/Axis/Abs_Axis.cpp @@ -43,11 +43,11 @@ std::vector Abs_Axis_Private::create_tick_vector(double tick_step, const } return result; } -std::vector Abs_Axis_Private::create_label_vector(const std::vector& ticks, State_Role State_Role) { +std::vector Abs_Axis_Private::create_label_vector(const std::vector& ticks, Abs_Axis_Render_State* state) { std::vector result; result.reserve(ticks.size()); for (double tick_coord : ticks) - result.push_back(get_tick_label(tick_coord, State_Role)); + result.push_back(get_tick_label(tick_coord, state)); return result; } std::vector Abs_Axis_Private::create_sub_tick_vector(int sub_tick_count, const std::vector& ticks, const Range& range) { @@ -164,9 +164,8 @@ double Abs_Axis_Private::pick_closest(double target, const std::vector& else return target - *(it - 1) < *it - target ? *(it - 1) : *it; } -std::string Abs_Axis_Private::get_tick_label(double tick, State_Role State_Role) { - auto render_state = reinterpret_cast(state(State_Role)); - return format_number(tick, Number_Format{.precision = render_state->label_precision}); +std::string Abs_Axis_Private::get_tick_label(double tick, Abs_Axis_Render_State* state) { + return format_number(tick, Number_Format{.precision = state->label_precision}); } double Abs_Axis::start_coord() { return d()->edit_axis_state()->coord_start; diff --git a/Core/Axis/Abs_Axis_p.h b/Core/Axis/Abs_Axis_p.h index 90d1702..25375b8 100644 --- a/Core/Axis/Abs_Axis_p.h +++ b/Core/Axis/Abs_Axis_p.h @@ -2,7 +2,7 @@ #include #include #include -#include "../architecture/Render_Lease.h" +#include "../flow/low_latency/Render_Lease.h" #include "../render/Canvas.h" #include "Abs_Axis.h" #include "blend2d/blend2d.h" @@ -19,7 +19,7 @@ struct Abs_Axis_Input_Data : Input_Data {}; struct Abs_Axis_Private : Typed_Render_Data { int tick_count = 2; [[nodiscard]] Abs_Axis_Render_State* edit_axis_state() { - return state_data(State_Role::State_Edit); + return edit_state(); } [[nodiscard]] Abs_Axis_Render_State* render_axis_state() { return render_state(); @@ -30,11 +30,11 @@ struct Abs_Axis_Private : Typed_Render_Data& candidates); - void prepare_data(const Plot_Render_Snapshot&) override { + void prepare_data(const Render_Frame_Snapshot&) override { sync_state_pipeline(); } - void draw(Canvas& canvas, const Plot_Render_Snapshot&) override { - Abs_Axis_Render_State* s = render_state(); + void draw(Canvas& canvas, const Render_Frame_Snapshot&) override { + Abs_Axis_Render_State* s = render_axis_state(); const int pixel_start = pixel_start_of(s); Range range = {s->coord_start, s->coord_start + s->coord_length}; double tick_step = get_tick_step(range); @@ -47,7 +47,7 @@ struct Abs_Axis_Private : Typed_Render_Data labels = create_label_vector(ticks, State_Render); + std::vector labels = render_label_vector(ticks); Text_Metrics tm = canvas.measure_text(!s->unit_text_font.valid() ? "0" : " "); double h = tm.line_height(); canvas.set_pen(Pen{.color = s->color}); @@ -56,7 +56,7 @@ struct Abs_Axis_Private : Typed_Render_Datay}, {x, (double)(s->y + s->tick_length)}); const std::string& label = labels[i]; PointF start{x, (double)(s->y + s->tick_length)}; @@ -67,7 +67,7 @@ struct Abs_Axis_Private : Typed_Render_Datay}, {x, (double)(s->y + s->sub_tick_length)}); } } @@ -76,7 +76,7 @@ struct Abs_Axis_Private : Typed_Render_Datax, y}, {(double)(s->x + s->tick_length), y}); PointF start{(double)(s->x + s->tick_length * 2), y + h / 2.0}; @@ -87,7 +87,7 @@ struct Abs_Axis_Private : Typed_Render_Datax, y}, {(double)(s->x + s->sub_tick_length), y}); } } @@ -116,12 +116,10 @@ struct Abs_Axis_Private : Typed_Render_Dataunit_text); } } - [[nodiscard]] virtual double pixel_to_coord(double pixel, State_Role State_Role) { - auto s = static_cast(state(State_Role)); + [[nodiscard]] virtual double pixel_to_coord(double pixel, Abs_Axis_Render_State* s) { return s->coord_start + s->coord_length * (pixel - (double)pixel_start_of(s)) / (double)s->pixel_length; } - [[nodiscard]] virtual double coord_to_pixel(double coord, State_Role State_Role) { - auto s = static_cast(state(State_Role)); + [[nodiscard]] virtual double coord_to_pixel(double coord, Abs_Axis_Render_State* s) { return pixel_start_of(s) + (double)s->pixel_length * (coord - s->coord_start) / s->coord_length; } virtual double get_tick_step(const Range& range) { @@ -131,19 +129,19 @@ struct Abs_Axis_Private : Typed_Render_Data create_tick_vector(double tick_step, const Range& range); virtual std::vector create_sub_tick_vector(int sub_tick_count, const std::vector& ticks, const Range& range); - virtual std::string get_tick_label(double tick, State_Role); - virtual std::vector create_label_vector(const std::vector& ticks, State_Role); + virtual std::string get_tick_label(double tick, Abs_Axis_Render_State* state); + virtual std::vector create_label_vector(const std::vector& ticks, Abs_Axis_Render_State* state); [[nodiscard]] double edit_pixel_to_coord(double pixel) { - return pixel_to_coord(pixel, State_Role::State_Edit); + return pixel_to_coord(pixel, edit_axis_state()); } [[nodiscard]] double render_pixel_to_coord(double pixel) { - return pixel_to_coord(pixel, State_Render); + return pixel_to_coord(pixel, render_axis_state()); } [[nodiscard]] double edit_coord_to_pixel(double coord) { - return coord_to_pixel(coord, State_Role::State_Edit); + return coord_to_pixel(coord, edit_axis_state()); } [[nodiscard]] double render_coord_to_pixel(double coord) { - return coord_to_pixel(coord, State_Render); + return coord_to_pixel(coord, render_axis_state()); } [[nodiscard]] Range edit_coord_range() { const auto* s = edit_axis_state(); @@ -157,16 +155,16 @@ struct Abs_Axis_Private : Typed_Render_Data(render_axis_state()->pixel_length)); } [[nodiscard]] std::string edit_tick_label(double tick) { - return get_tick_label(tick, State_Role::State_Edit); + return get_tick_label(tick, edit_axis_state()); } [[nodiscard]] std::string render_tick_label(double tick) { - return get_tick_label(tick, State_Render); + return get_tick_label(tick, render_axis_state()); } [[nodiscard]] std::vector edit_label_vector(const std::vector& ticks) { - return create_label_vector(ticks, State_Role::State_Edit); + return create_label_vector(ticks, edit_axis_state()); } [[nodiscard]] std::vector render_label_vector(const std::vector& ticks) { - return create_label_vector(ticks, State_Render); + return create_label_vector(ticks, render_axis_state()); } }; struct Axis_Frame_Snapshot { @@ -268,7 +266,7 @@ template void Abs_Axis::Builder_T::set(Abs_Axis* ret) { ret->init(parent); Abs_Axis_Private* pd = ret->d(); - Abs_Axis_Render_State* sc = reinterpret_cast(pd->state(State_Edit)); + Abs_Axis_Render_State* sc = reinterpret_cast(pd->edit_state()); sc->x = this->x; sc->y = this->y; sc->orientation = this->orientation; diff --git a/Core/Axis/Axis_p.h b/Core/Axis/Axis_p.h index aac561a..bfd1d5b 100644 --- a/Core/Axis/Axis_p.h +++ b/Core/Axis/Axis_p.h @@ -20,7 +20,7 @@ struct Axis_Private : Typed_Render_Data_Baseorientation == Orientation::Horizontal ? event.position.x : event.position.y; - double cursor_coord = pixel_to_coord(cursor_pixel, State_Edit); + double cursor_coord = edit_pixel_to_coord(cursor_pixel); double new_coord_length = edit->coord_length * factor; double new_coord_start = cursor_coord - new_coord_length * (cursor_coord - edit->coord_start) / edit->coord_length; edit->coord_start = new_coord_start; @@ -74,7 +74,7 @@ template void Axis::Builder_T::set(Axis* ret) { Abs_Axis::Builder_T::set(ret); Axis_Private* pd = ret->d(); - Axis_Render_State* sc = reinterpret_cast(pd->state(State_Edit)); + Axis_Render_State* sc = reinterpret_cast(pd->edit_state()); PROP_RT(int, label_precision) PROP_RT(double, coord_start) PROP_RT(double, coord_length) diff --git a/Core/Axis/Frequency_Axis_p.h b/Core/Axis/Frequency_Axis_p.h index 7824664..2012e53 100644 --- a/Core/Axis/Frequency_Axis_p.h +++ b/Core/Axis/Frequency_Axis_p.h @@ -9,14 +9,14 @@ 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 { - std::vector create_label_vector(const std::vector& ticks, State_Role src) override { + std::vector create_label_vector(const std::vector& ticks, Abs_Axis_Render_State* state) override { const int exponent = unit_exponent(ticks); if (!ticks.empty()) - render_state()->unit_text = "Frequency/" + unit_text(exponent); + state->unit_text = "Frequency/" + unit_text(exponent); std::vector result; result.reserve(ticks.size()); for (const double& tick_coord : ticks) - result.push_back(get_true_value(get_tick_label(tick_coord, src), exponent)); + result.push_back(get_true_value(get_tick_label(tick_coord, state), exponent, state)); return result; } static int unit_exponent(const std::vector& ticks) { @@ -44,8 +44,8 @@ struct Frequency_Axis_Private : Typed_Render_Data_Base(state); int precision = s->label_precision; auto dot_pos = value.find('.'); if (dot_pos == std::string::npos) diff --git a/Core/Axis/Time_Axis.cpp b/Core/Axis/Time_Axis.cpp index a358638..4c25e25 100644 --- a/Core/Axis/Time_Axis.cpp +++ b/Core/Axis/Time_Axis.cpp @@ -42,7 +42,7 @@ int Time_Axis::append_time(Time_Of_Day time) { Time_Of_Day Time_Axis::tick_to_time(int tick) { auto stream = timeline_stream(); if (stream) - return stream->capture(visible_time_point_count(), d()->state_data(State_Edit)->label_tick_interval_hint(), newest_at_axis_start()).time_by_tick(tick); + return stream->capture(visible_time_point_count(), d()->edit_state()->label_tick_interval_hint(), newest_at_axis_start()).time_by_tick(tick); return d()->time_by_tick(tick); } Time_Axis::Builder::Builder(std::shared_ptr parent, Orientation orientation) { @@ -54,7 +54,7 @@ std::shared_ptr Time_Axis::Builder::build() { auto ret = renderive::make_shared(); set(ret.get()); Time_Axis_Private* pd = ret->d(); - Time_Axis_Render_State* sc = reinterpret_cast(pd->state(State_Edit)); + Time_Axis_Render_State* sc = reinterpret_cast(pd->edit_state()); PROP_R(int, visible_time_point_count) PROP_R(int, tick_label_spacing_px) PROP_R(std::string, time_format) diff --git a/Core/Axis/Time_Axis_p.h b/Core/Axis/Time_Axis_p.h index a6191bb..7b23859 100644 --- a/Core/Axis/Time_Axis_p.h +++ b/Core/Axis/Time_Axis_p.h @@ -26,16 +26,16 @@ struct Time_Axis_Private : Typed_Render_Data_Basetime_by_tick(tick) : Time_Of_Day{}; } - std::shared_ptr capture_timeline(const Plot_Render_Snapshot& snapshot, const Time_Axis_Render_State* state) const { + std::shared_ptr capture_timeline(const Render_Frame_Snapshot& snapshot, const Time_Axis_Render_State* state) const { if (!state || !state->timeline_stream) return {}; return snapshot.timeline_snapshot(state->timeline_stream, state->visible_time_point_count, state->label_tick_interval_hint(), state->newest_at_axis_start); } - std::shared_ptr capture_timeline(const Plot_Render_Snapshot& snapshot) { + std::shared_ptr capture_timeline(const Render_Frame_Snapshot& snapshot) { sync_state_pipeline(); return capture_timeline(snapshot, render_state()); } - void prepare_data(const Plot_Render_Snapshot& snapshot) override { + void prepare_data(const Render_Frame_Snapshot& snapshot) override { sync_state_pipeline(); auto s = render_state(); auto captured = capture_timeline(snapshot, s); @@ -50,8 +50,8 @@ struct Time_Axis_Private : Typed_Render_Data_Base create_label_vector(const std::vector& ticks, State_Role src) override { - auto s = reinterpret_cast(state(src)); + std::vector create_label_vector(const std::vector& ticks, Abs_Axis_Render_State* state) override { + auto s = static_cast(state); std::vector result; result.reserve(ticks.size()); for (double tick : ticks) @@ -64,16 +64,16 @@ 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 {}; } - void draw(Canvas& canvas, const Plot_Render_Snapshot& snapshot) override { + void draw(Canvas& canvas, const Render_Frame_Snapshot& snapshot) override { Abs_Axis_Private::draw(canvas, snapshot); } - std::string get_tick_label(double tick, State_Role src) override { - auto render_state = reinterpret_cast(state(src)); + std::string get_tick_label(double tick, Abs_Axis_Render_State* state) override { + auto render_state = static_cast(state); return format_time(time_by_tick((int)tick), render_state->time_format); } }; struct Time_Axis_Render_Access final { - static std::shared_ptr capture_timeline(Time_Axis* axis, const Plot_Render_Snapshot& snapshot) { + static std::shared_ptr capture_timeline(Time_Axis* axis, const Render_Frame_Snapshot& snapshot) { return axis ? axis->d()->capture_timeline(snapshot) : std::shared_ptr{}; } }; diff --git a/Core/architecture/Frame_Scheduler.cpp b/Core/architecture/Frame_Scheduler.cpp index b459b12..6b49c9e 100644 --- a/Core/architecture/Frame_Scheduler.cpp +++ b/Core/architecture/Frame_Scheduler.cpp @@ -1,37 +1,8 @@ #include "Frame_Scheduler.h" #include -#include #include namespace renderive { -namespace { -constexpr double Ratio_Threshold = 1.03; -constexpr int Switch_Samples = 3; -} - -void Smoothed_Interval::update(double sample_ms) { - if (sample_ms <= 0.0 || !std::isfinite(sample_ms)) - return; - if (!sample_count) { - srtt_ms = sample_ms; - jitter_ms = sample_ms * 0.5; - sample_count = 1; - return; - } - double error = std::abs(sample_ms - srtt_ms); - jitter_ms += 0.25 * (error - jitter_ms); - srtt_ms += 0.125 * (sample_ms - srtt_ms); - ++sample_count; -} - -double Smoothed_Interval::safe_ms(double jitter_factor) const { - return sample_count ? srtt_ms + jitter_factor * jitter_ms : 0.0; -} - -bool Smoothed_Interval::valid() const { - return sample_count > 0; -} - void Frame_Lifecycle_Record::record_renderable_render(const std::string& object_name, const std::string& parent_object_name, int tree_depth, Renderable_Cache_Mode cache_mode, std::uint64_t render_time_ns) { Renderable_Frame_Stat& stat = renderable_stat(object_name, parent_object_name, tree_depth); stat.cache_enabled = cache_mode == Renderable_Cache_Mode::Local_Pixel; @@ -110,169 +81,4 @@ Renderable_Cache_Frame_Stats& Frame_Lifecycle_Record::cache_stat(const std::stri return renderable_cache_stats.back(); } -void Frame_Feedback_Controller::reset() { - std::uint64_t user_period = state_value.user_period_ns; - *this = Frame_Feedback_Controller(); - state_value.user_period_ns = user_period; - refresh_control_snapshot(); -} - -void Frame_Feedback_Controller::set_max_render_fps(double fps) { - state_value.user_period_ns = fps > 0.0 ? ms_to_ns(1000.0 / fps) : 0; - refresh_control_snapshot(); -} - -void Frame_Feedback_Controller::on_input_changed(std::uint64_t, std::uint64_t input_version) { - if (input_version) - latest_input_version = input_version; - else - ++latest_input_version; -} - -void Frame_Feedback_Controller::on_render_started(Frame_Lifecycle_Record& frame, std::uint64_t now_ns) { - last_render_begin_ns = now_ns; - frame.render_begin_ns = now_ns; - frame.wait_21_ns = now_ns > frame.render_role_enter_ns ? now_ns - frame.render_role_enter_ns : 0; - refresh_control_snapshot(); - write_state(frame); -} - -void Frame_Feedback_Controller::on_render_completed(Frame_Lifecycle_Record& frame) { - std::uint64_t render_duration_ns = frame.render_end_ns > frame.render_begin_ns ? frame.render_end_ns - frame.render_begin_ns : 0; - last_render_begin_ns = frame.render_begin_ns; - render_period.update(static_cast(render_duration_ns + frame.wait_21_ns) / 1000000.0); - refresh_control_snapshot(); - write_state(frame); -} - -void Frame_Feedback_Controller::on_frame_presented(Frame_Lifecycle_Record& frame) { - std::uint64_t paint_duration_ns = frame.paint_end_ns > frame.paint_begin_ns ? frame.paint_end_ns - frame.paint_begin_ns : 0; - paint_period.update(static_cast(paint_duration_ns + frame.wait_23_ns) / 1000000.0); - frame.outcome = Frame_Outcome::Presented; - refresh_control_snapshot(); - write_state(frame); -} - -void Frame_Feedback_Controller::on_frame_superseded(Frame_Lifecycle_Record& frame) { - frame.outcome = Frame_Outcome::Superseded; - refresh_control_snapshot(); - write_state(frame); -} - -void Frame_Feedback_Controller::on_frame_dropped(Frame_Lifecycle_Record& frame, Frame_Outcome outcome) { - frame.outcome = outcome; - refresh_control_snapshot(); - write_state(frame); -} - -bool Frame_Feedback_Controller::rate_limit_ready(std::uint64_t now_ns) const { - return now_ns >= next_render_time; -} - -std::uint64_t Frame_Feedback_Controller::next_render_time_ns() const { - return next_render_time; -} - -const Refresh_Control_Snapshot& Frame_Feedback_Controller::state() const { - return state_value; -} - -void Frame_Feedback_Controller::write_state(Frame_Lifecycle_Record& frame) const { - frame.refresh = state_value; -} - -void Frame_Feedback_Controller::refresh_control_snapshot() { - std::uint64_t render_period_ns = render_period.valid() ? ms_to_ns(render_period.safe_ms(0.5)) : 0; - std::uint64_t paint_period_ns = paint_period.valid() ? ms_to_ns(paint_period.safe_ms(0.5)) : 0; - std::uint64_t user_period_ns = state_value.user_period_ns; - Refresh_Limit_Source candidate = classify_limit_source(render_period_ns, paint_period_ns, user_period_ns); - std::uint64_t candidate_period = 0; - switch (candidate) { - case Refresh_Limit_Source::Render: - candidate_period = render_period_ns; - break; - case Refresh_Limit_Source::Paint: - candidate_period = paint_period_ns; - break; - case Refresh_Limit_Source::User: - candidate_period = user_period_ns; - break; - case Refresh_Limit_Source::Unknown: - break; - } - update_limit_source(candidate, candidate_period); - std::uint64_t target_period = std::max({render_period_ns, paint_period_ns, user_period_ns}); - state_value.render_period_ns = render_period_ns; - state_value.paint_period_ns = paint_period_ns; - state_value.target_period_ns = target_period; - if (state_value.source == Refresh_Limit_Source::Unknown) - state_value.source = candidate; - if (state_value.source == Refresh_Limit_Source::User && last_render_begin_ns) - next_render_time = last_render_begin_ns + user_period_ns; - else - next_render_time = 0; -} - -Refresh_Limit_Source Frame_Feedback_Controller::classify_limit_source(std::uint64_t render_period_ns, std::uint64_t paint_period_ns, std::uint64_t user_period_ns) const { - if (!render_period_ns && !paint_period_ns) - return user_period_ns ? Refresh_Limit_Source::User : Refresh_Limit_Source::Unknown; - std::uint64_t max_period = std::max({render_period_ns, paint_period_ns, user_period_ns}); - if (max_period == user_period_ns && user_period_ns) - return Refresh_Limit_Source::User; - if (max_period == paint_period_ns && paint_period_ns) - return Refresh_Limit_Source::Paint; - if (max_period == render_period_ns && render_period_ns) - return Refresh_Limit_Source::Render; - return Refresh_Limit_Source::Unknown; -} - -void Frame_Feedback_Controller::update_limit_source(Refresh_Limit_Source candidate, std::uint64_t candidate_period_ns) { - if (candidate == Refresh_Limit_Source::Unknown) - return; - if (state_value.source == Refresh_Limit_Source::Unknown) { - state_value.source = candidate; - candidate_source = candidate; - candidate_count = 0; - return; - } - std::uint64_t current_period = 0; - switch (state_value.source) { - case Refresh_Limit_Source::Render: - current_period = state_value.render_period_ns; - break; - case Refresh_Limit_Source::Paint: - current_period = state_value.paint_period_ns; - break; - case Refresh_Limit_Source::User: - current_period = state_value.user_period_ns; - break; - case Refresh_Limit_Source::Unknown: - break; - } - if (candidate == state_value.source || (current_period && static_cast(candidate_period_ns) <= static_cast(current_period) * Ratio_Threshold)) { - candidate_source = candidate; - candidate_count = 0; - return; - } - if (candidate != candidate_source) { - candidate_source = candidate; - candidate_count = 1; - return; - } - if (++candidate_count >= Switch_Samples) { - state_value.source = candidate; - candidate_count = 0; - } -} - -double Frame_Feedback_Controller::duration_ms(std::uint64_t begin_ns, std::uint64_t end_ns) { - return end_ns > begin_ns ? static_cast(end_ns - begin_ns) / 1000000.0 : 0.0; -} - -std::uint64_t Frame_Feedback_Controller::ms_to_ns(double ms) { - if (ms <= 0.0 || !std::isfinite(ms)) - return 0; - return static_cast(ms * 1000000.0); -} - } // namespace renderive diff --git a/Core/architecture/Frame_Scheduler.h b/Core/architecture/Frame_Scheduler.h index 4dca874..5606011 100644 --- a/Core/architecture/Frame_Scheduler.h +++ b/Core/architecture/Frame_Scheduler.h @@ -4,14 +4,6 @@ #include #include namespace renderive { -struct Smoothed_Interval { - double srtt_ms{}; - double jitter_ms{}; - std::uint64_t sample_count{}; - void update(double sample_ms); - [[nodiscard]] double safe_ms(double jitter_factor = 2.0) const; - [[nodiscard]] bool valid() const; -}; struct Refresh_Control_Snapshot { Refresh_Limit_Source source = Refresh_Limit_Source::Unknown; std::uint64_t render_period_ns{}; @@ -66,10 +58,10 @@ struct Frame_Lifecycle_Record { std::uint64_t paint_begin_ns{}; std::uint64_t paint_end_ns{}; std::uint64_t transition_32_ns{}; - std::uint64_t wait_21_ns{}; - std::uint64_t wait_12_ns{}; - std::uint64_t wait_23_ns{}; - std::uint64_t wait_32_ns{}; + std::uint64_t render_queue_wait_ns{}; + std::uint64_t ready_queue_wait_ns{}; + std::uint64_t present_queue_wait_ns{}; + std::uint64_t paint_return_wait_ns{}; std::uint64_t superseded_time_ns{}; Frame_Outcome outcome = Frame_Outcome::Render_Acquire_Dropped; Refresh_Control_Snapshot refresh; @@ -106,38 +98,4 @@ inline std::uint64_t renderable_cache_subtree_compose_time(const std::vector -namespace renderive { -std::shared_ptr Plot_Render_Snapshot::timeline_snapshot(const std::shared_ptr& stream, int time_point_size, int label_tick_interval, bool start_coord_to_end_coord) const { - if (!stream) - return {}; - time_point_size = std::max(1, time_point_size); - label_tick_interval = std::max(1, label_tick_interval); - if (!timeline_cache) - timeline_cache = std::make_shared(); - for (auto& item : timeline_cache->items) { - if (item.stream.lock() == stream && item.time_point_size == time_point_size && item.tick_space == label_tick_interval && item.start_coord_to_end_coord == start_coord_to_end_coord) - return item.snapshot; - } - auto snapshot = std::make_shared(stream->capture(time_point_size, label_tick_interval, start_coord_to_end_coord)); - timeline_cache->items.push_back({stream, time_point_size, label_tick_interval, start_coord_to_end_coord, snapshot}); - return snapshot; -} -} diff --git a/Core/architecture/Plot_Render_Context.h b/Core/architecture/Plot_Render_Context.h index 2fb6152..f908643 100644 --- a/Core/architecture/Plot_Render_Context.h +++ b/Core/architecture/Plot_Render_Context.h @@ -1,6 +1,4 @@ #pragma once -#include "../base/Color.h" -#include "../base/Geometry.h" #include "Frame_Scheduler.h" #include #include @@ -14,7 +12,6 @@ namespace renderive { class Plot_Core; class Timeline_Stream; -struct Update_State; struct Frame_Lifecycle_Observer { virtual ~Frame_Lifecycle_Observer() = default; @@ -48,19 +45,6 @@ struct Plot_Render_Context { std::atomic owner{}; }; -struct Plot_Render_Snapshot { - std::shared_ptr context; - Size size; - Color background_color = Color::black(); - std::uint64_t frame_time_ns{}; - std::uint64_t previous_frame_time_ns{}; - Frame_Lifecycle_Record* frame_metadata{}; - std::pmr::vector>* frame_update_states{}; - 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, int time_point_size, int label_tick_interval, bool start_coord_to_end_coord) const; -}; - } // namespace renderive + +#include "../render/Render_Frame_Snapshot.h" diff --git a/Core/architecture/Render_Config.h b/Core/architecture/Render_Config.h index d578fae..65bb65e 100644 --- a/Core/architecture/Render_Config.h +++ b/Core/architecture/Render_Config.h @@ -53,11 +53,6 @@ enum class Refresh_Limit_Source : std::uint8_t { Paint, User }; -enum Frame_Role : std::uint8_t { - Frame_Rendering, - Frame_Middle, - Frame_Painting -}; enum class Frame_Outcome : std::uint8_t { Presented, Superseded, @@ -74,26 +69,4 @@ enum class Renderable_Cache_Mode : std::uint8_t { /// @brief Rebuild a local image cache when state changes, then compose it. Local_Pixel }; -/// @brief Internal state triple-buffer role. -enum State_Role : std::uint8_t { - /// @brief Editable configuration state. - State_Edit, - /// @brief Committed state ready for the render thread. - State_Ready, - /// @brief State currently used by the render thread. - State_Render -}; -/// @brief Internal input triple-buffer role. -enum Input_Role : std::uint8_t { - /// @brief Input buffer receiving caller submissions. - Input_Edit, - /// @brief Input buffer ready for render-thread consumption. - Input_Ready, - /// @brief Input buffer currently consumed by the render thread. - Input_Render -}; -/// @brief Internal color-frame triple-buffer role. -inline bool buffer_version_newer(std::uint64_t a, std::uint64_t b) { - return static_cast(a - b) > 0; -} } // namespace renderive diff --git a/Core/architecture/Renderable.cpp b/Core/architecture/Renderable.cpp index 5982883..7da1aa6 100644 --- a/Core/architecture/Renderable.cpp +++ b/Core/architecture/Renderable.cpp @@ -106,23 +106,15 @@ void Renderable::init_common(Plot_Core* plot) { d_owner = create_render_data(); d_ptr = d_owner.get(); } - d_ptr->clear_state_buffers(); - d_ptr->clear_input_buffers(); - d_ptr->state_buffer_owners[State_Edit] = create_render_state(); - d_ptr->state_buffer_owners[State_Ready] = create_render_state(); - d_ptr->state_buffer_owners[State_Render] = create_render_state(); - d_ptr->state_buffers[State_Edit] = d_ptr->state_buffer_owners[State_Edit].get(); - d_ptr->state_buffers[State_Ready] = d_ptr->state_buffer_owners[State_Ready].get(); - d_ptr->state_buffers[State_Render] = d_ptr->state_buffer_owners[State_Render].get(); - d_ptr->input_buffer_owners[Input_Edit] = create_input_data(); - d_ptr->input_buffer_owners[Input_Ready] = create_input_data(); - d_ptr->input_buffer_owners[Input_Render] = create_input_data(); - d_ptr->input_buffers[Input_Edit].data = d_ptr->input_buffer_owners[Input_Edit].get(); - d_ptr->input_buffers[Input_Ready].data = d_ptr->input_buffer_owners[Input_Ready].get(); - d_ptr->input_buffers[Input_Render].data = d_ptr->input_buffer_owners[Input_Render].get(); d_ptr->q_ptr = this; - d_ptr->state_buffers[State_Edit]->version = 1; - plot->mark_render_state_dirty(); + d_ptr->initialize_runtime_buffers( + create_render_state(), + create_render_state(), + create_render_state(), + create_input_data(), + create_input_data(), + create_input_data()); + plot->notify_model_dirty(); } void Renderable::add_child(const std::shared_ptr& child) { if (!child || child.get() == this || child->plot != plot || !child->parent_renderable.expired() || get_retiring() || child->get_retiring()) { @@ -161,10 +153,10 @@ std::vector> Renderable::children_snapshot() const { std::lock_guard lock(children_mutex); return children; } -void Renderable::append_tree_snapshot(Renderable_Tree_Snapshot& snapshot, const std::string& parent_object_name, int render_tree_depth, const std::string& cache_parent_object_name, int cache_tree_depth) { +void Renderable::append_tree_snapshot(Renderable_Frame_Tree& snapshot, const std::string& parent_object_name, int render_tree_depth, const std::string& cache_parent_object_name, int cache_tree_depth) { std::size_t index = snapshot.size(); Renderable_Cache_Mode mode = get_cache_mode(); - snapshot.push_back(Renderable_Tree_Node{ + snapshot.push_back(Renderable_Frame_Node{ shared_from_this(), index + 1, parent_object_name, @@ -202,7 +194,7 @@ void Renderable::append_hit_renderables(std::vector> if (select_test(pos)) hits.push_back(shared_from_this()); } -void Renderable::render(Canvas& canvas, const Plot_Render_Snapshot& snapshot) { +void Renderable::render(Canvas& canvas, const Render_Frame_Snapshot& snapshot) { if (get_retiring() || !visible_) return; Renderable_Cache_Mode mode = get_cache_mode(); @@ -214,21 +206,21 @@ void Renderable::render(Canvas& canvas, const Plot_Render_Snapshot& 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_tree_snapshot(Canvas& canvas, const Plot_Render_Snapshot& snapshot, const Renderable_Tree_Snapshot& tree, std::size_t index) { +void Renderable::render_tree_snapshot(Canvas& canvas, const Render_Frame_Snapshot& snapshot, const Renderable_Frame_Tree& tree, std::size_t index) { render_tree_node(canvas, snapshot, tree, index); } -void Renderable::render_direct(Canvas& canvas, const Plot_Render_Snapshot& snapshot) { +void Renderable::render_direct(Canvas& canvas, const Render_Frame_Snapshot& snapshot) { render_self(canvas, snapshot); render_children(canvas, snapshot); } -void Renderable::render_children(Canvas& canvas, const Plot_Render_Snapshot& snapshot) { +void Renderable::render_children(Canvas& canvas, const Render_Frame_Snapshot& snapshot) { std::vector> children = children_snapshot(); for (const auto& child : children) { if (child) child->render(canvas, snapshot); } } -void Renderable::render_cached(Canvas& canvas, const Plot_Render_Snapshot& snapshot) { +void Renderable::render_cached(Canvas& canvas, const Render_Frame_Snapshot& snapshot) { Size size = snapshot.size; Rect bounds = d_ptr ? d_ptr->pixel_bounds(size).intersected(Rect{0, 0, size.width, size.height}) : Rect{0, 0, size.width, size.height}; if (bounds.empty()) @@ -259,10 +251,10 @@ void Renderable::render_cached(Canvas& canvas, const Plot_Render_Snapshot& snaps snapshot.frame_metadata->record_renderable_cache_compose(object_name_, parent_object_name, tree_depth, steady_now_ns() - begin_time, static_cast(cache_image.byte_size()), static_cast(bounds.width) * static_cast(bounds.height), edit_version, cache_ready_version.load(std::memory_order_acquire)); } } -std::size_t Renderable::render_tree_node(Canvas& canvas, const Plot_Render_Snapshot& snapshot, const Renderable_Tree_Snapshot& tree, std::size_t index) { +std::size_t Renderable::render_tree_node(Canvas& canvas, const Render_Frame_Snapshot& snapshot, const Renderable_Frame_Tree& tree, std::size_t index) { if (index >= tree.size()) return tree.size(); - const Renderable_Tree_Node& node = tree[index]; + const Renderable_Frame_Node& node = tree[index]; Renderable* current = node.renderable.get(); if (!current) return std::max(index + 1, node.subtree_end); @@ -280,7 +272,7 @@ std::size_t Renderable::render_tree_node(Canvas& canvas, const Plot_Render_Snaps snapshot.frame_metadata->record_renderable_render(object_name_, node.parent_object_name, node.render_tree_depth, mode, steady_now_ns() - begin_time); return std::max(index + 1, node.subtree_end); } -void Renderable::render_direct_tree_node(Canvas& canvas, const Plot_Render_Snapshot& snapshot, const Renderable_Tree_Snapshot& tree, std::size_t index) { +void Renderable::render_direct_tree_node(Canvas& canvas, const Render_Frame_Snapshot& snapshot, const Renderable_Frame_Tree& tree, std::size_t index) { render_self(canvas, snapshot, &tree[index]); std::size_t child_index = index + 1; while (child_index < tree[index].subtree_end && child_index < tree.size()) { @@ -292,12 +284,12 @@ void Renderable::render_direct_tree_node(Canvas& canvas, const Plot_Render_Snaps child_index = child->render_tree_node(canvas, snapshot, tree, child_index); } } -void Renderable::render_cached_tree_node(Canvas& canvas, const Plot_Render_Snapshot& snapshot, const Renderable_Tree_Snapshot& tree, std::size_t index) { +void Renderable::render_cached_tree_node(Canvas& canvas, const Render_Frame_Snapshot& snapshot, const Renderable_Frame_Tree& tree, std::size_t index) { Size size = snapshot.size; Rect bounds = d_ptr ? d_ptr->pixel_bounds(size).intersected(Rect{0, 0, size.width, size.height}) : Rect{0, 0, size.width, size.height}; if (bounds.empty()) return; - const Renderable_Tree_Node& node = tree[index]; + const Renderable_Frame_Node& node = tree[index]; std::uint64_t edit_version = cache_edit_version.load(std::memory_order_acquire); bool ready = cache_ready_version.load(std::memory_order_acquire) == edit_version && cache_image.size() == Size{bounds.width, bounds.height} && cache_bounds.x == bounds.x && cache_bounds.y == bounds.y && cache_bounds.width == bounds.width && cache_bounds.height == bounds.height; if (ready) { @@ -322,7 +314,7 @@ void Renderable::render_cached_tree_node(Canvas& canvas, const Plot_Render_Snaps snapshot.frame_metadata->record_renderable_cache_compose(object_name_, node.cache_parent_object_name, node.cache_tree_depth, steady_now_ns() - begin_time, static_cast(cache_image.byte_size()), static_cast(bounds.width) * static_cast(bounds.height), edit_version, cache_ready_version.load(std::memory_order_acquire)); } } -void Renderable::render_cache_image_tree_node(const Rect& bounds, const Plot_Render_Snapshot& snapshot, const Renderable_Tree_Snapshot& tree, std::size_t index, std::uint64_t version) { +void Renderable::render_cache_image_tree_node(const Rect& bounds, const Render_Frame_Snapshot& snapshot, const Renderable_Frame_Tree& tree, std::size_t index, std::uint64_t version) { if (get_retiring()) return; Image temp_image; @@ -337,7 +329,7 @@ void Renderable::render_cache_image_tree_node(const Rect& bounds, const Plot_Ren this->cache_image = std::move(temp_image); cache_ready_version.store(version, std::memory_order_release); } -void Renderable::render_cache_image(const Rect& bounds, const Plot_Render_Snapshot& snapshot, std::uint64_t version) { +void Renderable::render_cache_image(const Rect& bounds, const Render_Frame_Snapshot& snapshot, std::uint64_t version) { if (get_retiring()) return; Image temp_image; @@ -352,13 +344,13 @@ void Renderable::render_cache_image(const Rect& bounds, const Plot_Render_Snapsh this->cache_image = std::move(temp_image); cache_ready_version.store(version, std::memory_order_release); } -void Renderable::render_self(Canvas& canvas, const Plot_Render_Snapshot& snapshot) { +void Renderable::render_self(Canvas& canvas, const Render_Frame_Snapshot& snapshot) { render_self(canvas, snapshot, nullptr); } -void Renderable::render_self(Canvas& canvas, const Plot_Render_Snapshot& snapshot, const Renderable_Tree_Node* node) { +void Renderable::render_self(Canvas& canvas, const Render_Frame_Snapshot& snapshot, const Renderable_Frame_Node* node) { if (!d_ptr) return; - auto lease = d_ptr->try_mark_state_role(State_Render); + auto lease = d_ptr->try_mark_render_state(); if (!lease) return; Renderable_Cache_Mode mode = get_cache_mode(); @@ -372,12 +364,12 @@ void Renderable::render_self(Canvas& canvas, const Plot_Render_Snapshot& snapsho snapshot.frame_metadata->record_renderable_self_draw(object_name_, parent_name, depth, mode, draw_time); } } -void Renderable::draw(Canvas& canvas, const Plot_Render_Snapshot& snapshot) { +void Renderable::draw(Canvas& canvas, const Render_Frame_Snapshot& snapshot) { if (get_retiring()) return; d_ptr->draw(canvas, snapshot); } -void Renderable::prepare_data(const Plot_Render_Snapshot& snapshot) { +void Renderable::prepare_data(const Render_Frame_Snapshot& snapshot) { if (get_retiring()) return; d_ptr->prepare_data(snapshot); @@ -486,7 +478,7 @@ void Renderable::mark_render_dirty() { } Plot_Core* target_plot = context->owner.load(std::memory_order_acquire); if (!context->destroying.load(std::memory_order_acquire) && target_plot) - target_plot->mark_render_state_dirty(); + target_plot->notify_model_dirty(); } bool Renderable::ok() const { auto context = plot_context.lock(); diff --git a/Core/architecture/Renderable.h b/Core/architecture/Renderable.h index a97ab04..d43d195 100644 --- a/Core/architecture/Renderable.h +++ b/Core/architecture/Renderable.h @@ -9,8 +9,9 @@ #include "../event/Wheel_Event.h" #include "../render/Canvas.h" #include "../render/Image.h" +#include "../render/Renderable_Frame_Node.h" #include "Render_Config.h" -#include "Render_Data.h" +#include "../flow/low_latency/Render_Data.h" #include #include #include @@ -23,15 +24,6 @@ namespace renderive { class Plot_Core; class Renderable; -struct Renderable_Tree_Node { - std::shared_ptr renderable; - std::size_t subtree_end{}; - std::string parent_object_name; - int render_tree_depth{}; - std::string cache_parent_object_name; - int cache_tree_depth{}; -}; -using Renderable_Tree_Snapshot = std::vector; template concept Render_State_Type = std::derived_from && std::is_copy_assignable_v; template @@ -51,7 +43,7 @@ public: return make_render_object(); } virtual Render_Object_Owner create_input_data() { - return make_render_object(); + return {}; } virtual void init(const std::shared_ptr& parent); virtual void init_root(Plot_Core* plot); @@ -59,11 +51,11 @@ public: void remove_child(const std::shared_ptr& child); std::vector> children_snapshot() const; std::vector> hit_renderables(const PointF& pos); - void append_tree_snapshot(Renderable_Tree_Snapshot& snapshot, const std::string& parent_object_name = {}, int render_tree_depth = 0, const std::string& cache_parent_object_name = {}, int cache_tree_depth = 0); - void render(Canvas& canvas, const Plot_Render_Snapshot& snapshot); - void render_tree_snapshot(Canvas& canvas, const Plot_Render_Snapshot& snapshot, const Renderable_Tree_Snapshot& tree, std::size_t index); - void draw(Canvas& canvas, const Plot_Render_Snapshot& snapshot); - void prepare_data(const Plot_Render_Snapshot& snapshot); + void append_tree_snapshot(Renderable_Frame_Tree& snapshot, const std::string& parent_object_name = {}, int render_tree_depth = 0, const std::string& cache_parent_object_name = {}, int cache_tree_depth = 0); + void render(Canvas& canvas, const Render_Frame_Snapshot& snapshot); + void render_tree_snapshot(Canvas& canvas, const Render_Frame_Snapshot& snapshot, const Renderable_Frame_Tree& tree, std::size_t index); + void draw(Canvas& canvas, const Render_Frame_Snapshot& snapshot); + void prepare_data(const Render_Frame_Snapshot& snapshot); bool select_test(const PointF& pos); void plot_event(const Event& event); void mouse_press_event(const Pointer_Event& event); @@ -114,16 +106,16 @@ private: std::atomic_bool retiring{true}; std::atomic_bool initialized{false}; void init_common(Plot_Core* plot); - void render_direct(Canvas& canvas, const Plot_Render_Snapshot& snapshot); - void render_cached(Canvas& canvas, const Plot_Render_Snapshot& snapshot); - void render_self(Canvas& canvas, const Plot_Render_Snapshot& snapshot); - void render_children(Canvas& canvas, const Plot_Render_Snapshot& snapshot); - void render_cache_image(const Rect& bounds, const Plot_Render_Snapshot& snapshot, std::uint64_t version); - std::size_t render_tree_node(Canvas& canvas, const Plot_Render_Snapshot& snapshot, const Renderable_Tree_Snapshot& tree, std::size_t index); - void render_direct_tree_node(Canvas& canvas, const Plot_Render_Snapshot& snapshot, const Renderable_Tree_Snapshot& tree, std::size_t index); - void render_cached_tree_node(Canvas& canvas, const Plot_Render_Snapshot& snapshot, const Renderable_Tree_Snapshot& tree, std::size_t index); - void render_cache_image_tree_node(const Rect& bounds, const Plot_Render_Snapshot& snapshot, const Renderable_Tree_Snapshot& tree, std::size_t index, std::uint64_t version); - void render_self(Canvas& canvas, const Plot_Render_Snapshot& snapshot, const Renderable_Tree_Node* node); + void render_direct(Canvas& canvas, const Render_Frame_Snapshot& snapshot); + void render_cached(Canvas& canvas, const Render_Frame_Snapshot& snapshot); + void render_self(Canvas& canvas, const Render_Frame_Snapshot& snapshot); + void render_children(Canvas& canvas, const Render_Frame_Snapshot& snapshot); + void render_cache_image(const Rect& bounds, const Render_Frame_Snapshot& snapshot, std::uint64_t version); + std::size_t render_tree_node(Canvas& canvas, const Render_Frame_Snapshot& snapshot, const Renderable_Frame_Tree& tree, std::size_t index); + void render_direct_tree_node(Canvas& canvas, const Render_Frame_Snapshot& snapshot, const Renderable_Frame_Tree& tree, std::size_t index); + void render_cached_tree_node(Canvas& canvas, const Render_Frame_Snapshot& snapshot, const Renderable_Frame_Tree& tree, std::size_t index); + void render_cache_image_tree_node(const Rect& bounds, const Render_Frame_Snapshot& snapshot, const Renderable_Frame_Tree& tree, std::size_t index, std::uint64_t version); + void render_self(Canvas& canvas, const Render_Frame_Snapshot& snapshot, const Renderable_Frame_Node* node); void append_hit_renderables(std::vector>& hits, const PointF& pos); void detach_from_plot_tree(); }; diff --git a/Core/base/global.cpp b/Core/base/global.cpp index c4c2927..a29c9c7 100644 --- a/Core/base/global.cpp +++ b/Core/base/global.cpp @@ -192,19 +192,19 @@ void Render_Scheduler::register_plot_on_scheduler(Plot_Core* plot) { void Render_Scheduler::remove_plot_on_scheduler(Plot_Core* plot) { d->plots.erase(std::remove(d->plots.begin(), d->plots.end(), plot), d->plots.end()); - plot->pause_render(); + plot->deactivate_view(); } void Render_Scheduler::shutdown_on_scheduler() { for (Plot_Core* plot : d->plots) - plot->pause_render(); + plot->deactivate_view(); d->plots.clear(); } void Render_Scheduler::initialize_plot_on_scheduler(Plot_Core* plot) { plot->set_viewport_size(plot->viewport_size()); - if (plot->is_rendering()) - plot->start_render(); + if (plot->view_active()) + plot->activate_view(); } Render_Scheduler& Global::render_scheduler() { diff --git a/Core/flow/Flow_Diagnostics.h b/Core/flow/Flow_Diagnostics.h new file mode 100644 index 0000000..04a4b29 --- /dev/null +++ b/Core/flow/Flow_Diagnostics.h @@ -0,0 +1,28 @@ +#pragma once +#include "../architecture/Frame_Scheduler.h" +#include +#include + +namespace renderive { + +struct Common_Flow_Diagnostics { + bool active{}; +}; + +struct Low_Latency_Diagnostics { + bool active{}; + double max_render_fps{}; + Refresh_Control_Snapshot refresh; +}; + +struct Explicit_Diagnostics { + bool active{}; + std::uint64_t pending_request_count{}; +}; + +struct Flow_Diagnostics { + Common_Flow_Diagnostics common; + std::variant strategy; +}; + +} // namespace renderive diff --git a/Core/flow/Frame_Flow.h b/Core/flow/Frame_Flow.h new file mode 100644 index 0000000..a6e039c --- /dev/null +++ b/Core/flow/Frame_Flow.h @@ -0,0 +1,45 @@ +#pragma once +#include "../architecture/Frame_Scheduler.h" +#include "../architecture/Update_Completion.h" +#include "../base/Geometry.h" +#include "Flow_Diagnostics.h" +#include "Frame_Flow_Host.h" +#include "Present_Surface_Lease.h" +#include "Render_Surface.h" +#include +#include +#include +#include + +namespace renderive { + +class Frame_Flow { +public: + virtual ~Frame_Flow() = default; + + virtual void attach(Frame_Flow_Host& host) = 0; + [[nodiscard]] virtual Frame_Flow_Runtime_Snapshot runtime_snapshot(std::uint64_t now_ns, bool destroying) const = 0; + virtual void advance_edit_version() = 0; + virtual void cancel_all_update_states() = 0; + [[nodiscard]] virtual Render_Frame_Surface begin_render_frame() = 0; + virtual void cancel_render_frame(Render_Frame_Surface&& surface) = 0; + virtual void discard_render_frame(Render_Frame_Surface&& surface) = 0; + [[nodiscard]] virtual Render_Surface_Publish_Result publish_rendered_frame(Render_Frame_Surface&& surface, std::uint64_t now_ns) = 0; + [[nodiscard]] virtual Render_Surface_Publish_Result request_present(std::uint64_t now_ns) = 0; + [[nodiscard]] virtual Present_Surface_Lease begin_present(std::uint64_t now_ns, std::shared_ptr& returned_frame_record) = 0; + [[nodiscard]] virtual std::shared_ptr end_present(Present_Surface_Lease&& surface, std::uint64_t paint_begin_ns, std::uint64_t paint_end_ns) = 0; + virtual void activate_view(std::uint64_t now_ns) = 0; + virtual void deactivate_view() = 0; + [[nodiscard]] virtual bool view_active() const = 0; + virtual void notify_model_dirty(std::uint64_t now_ns) = 0; + virtual void notify_viewport_changed(Size viewport, std::uint64_t now_ns) = 0; + virtual void notify_render_finished(Frame_Lifecycle_Record& frame, std::uint64_t now_ns) = 0; + virtual void notify_paint_finished(std::uint64_t now_ns) = 0; + virtual void notify_frame_returned(Frame_Lifecycle_Record& frame, std::uint64_t now_ns) = 0; + virtual void notify_timer_fired(std::uint64_t now_ns) = 0; + virtual void drain_frame_update_states(std::pmr::vector>& output) = 0; + [[nodiscard]] virtual Flow_Diagnostics diagnostics() const = 0; + virtual void shutdown() = 0; +}; + +} // namespace renderive diff --git a/Core/flow/Frame_Flow_Host.h b/Core/flow/Frame_Flow_Host.h new file mode 100644 index 0000000..a9d1beb --- /dev/null +++ b/Core/flow/Frame_Flow_Host.h @@ -0,0 +1,26 @@ +#pragma once +#include + +namespace renderive { + +struct Frame_Flow_Runtime_Snapshot { + bool destroying{}; + bool dirty{}; + bool render_job_active{}; + bool middle_has_new_frame{}; + bool paint_active{}; + std::uint64_t now_ns{}; +}; + +class Frame_Flow_Host { +public: + virtual ~Frame_Flow_Host() = default; + + [[nodiscard]] virtual Frame_Flow_Runtime_Snapshot runtime_snapshot(std::uint64_t now_ns) const = 0; + [[nodiscard]] virtual bool try_render_once() = 0; + virtual void request_paint_from_middle() = 0; + virtual void schedule_render_deadline(std::uint64_t deadline_ns) = 0; + virtual void cancel_render_deadline() = 0; +}; + +} // namespace renderive diff --git a/Core/flow/Present_Surface_Lease.cpp b/Core/flow/Present_Surface_Lease.cpp new file mode 100644 index 0000000..757dba4 --- /dev/null +++ b/Core/flow/Present_Surface_Lease.cpp @@ -0,0 +1,47 @@ +#include "Present_Surface_Lease.h" + +namespace renderive { + +Present_Surface_Lease::Present_Surface_Lease() = default; +Present_Surface_Lease::Present_Surface_Lease(Present_Surface_Lease&&) noexcept = default; +Present_Surface_Lease& Present_Surface_Lease::operator=(Present_Surface_Lease&&) noexcept = default; +Present_Surface_Lease::~Present_Surface_Lease() = default; + +Present_Surface_Lease::operator bool() const { + return output() != nullptr; +} + +bool Present_Surface_Lease::new_frame() const { + return is_new_frame; +} + +bool Present_Surface_Lease::request_was_pending() const { + return update_was_pending; +} + +Image_View Present_Surface_Lease::image() const { + Render_Output* current = output(); + if (!current) + return {}; + return current->image.view(); +} + +Present_Surface_Lease Present_Surface_Lease::from_handle(std::unique_ptr handle, bool is_new_frame, bool update_was_pending) { + Present_Surface_Lease lease; + lease.handle_ = std::move(handle); + lease.is_new_frame = is_new_frame; + lease.update_was_pending = update_was_pending; + return lease; +} + +Render_Output* Present_Surface_Lease::output() const { + return handle_ ? handle_->output() : nullptr; +} + +void Present_Surface_Lease::reset() { + handle_.reset(); + is_new_frame = false; + update_was_pending = false; +} + +} // namespace renderive diff --git a/Core/flow/Present_Surface_Lease.h b/Core/flow/Present_Surface_Lease.h new file mode 100644 index 0000000..478f47f --- /dev/null +++ b/Core/flow/Present_Surface_Lease.h @@ -0,0 +1,51 @@ +#pragma once +#include "../render/Render_Output.h" +#include "../render/Image_View.h" +#include +#include +#include + +namespace renderive { + +struct Present_Surface_Lease_Access; + +struct Present_Surface_Handle { + virtual ~Present_Surface_Handle() = default; + [[nodiscard]] virtual Render_Output* output() const = 0; +}; + +class Present_Surface_Lease { +public: + Present_Surface_Lease(); + Present_Surface_Lease(Present_Surface_Lease&&) noexcept; + Present_Surface_Lease& operator=(Present_Surface_Lease&&) noexcept; + ~Present_Surface_Lease(); + + [[nodiscard]] explicit operator bool() const; + [[nodiscard]] bool new_frame() const; + [[nodiscard]] bool request_was_pending() const; + [[nodiscard]] Image_View image() const; + +private: + friend struct Present_Surface_Lease_Access; + static Present_Surface_Lease from_handle(std::unique_ptr handle, bool is_new_frame, bool update_was_pending); + [[nodiscard]] Render_Output* output() const; + void reset(); + std::unique_ptr handle_; + bool is_new_frame{}; + bool update_was_pending{}; +}; + +struct Present_Surface_Lease_Access { + static Present_Surface_Lease from_handle(std::unique_ptr handle, bool is_new_frame, bool update_was_pending) { + return Present_Surface_Lease::from_handle(std::move(handle), is_new_frame, update_was_pending); + } + static Render_Output* output(const Present_Surface_Lease& lease) { + return lease.output(); + } + static void reset(Present_Surface_Lease& lease) { + lease.reset(); + } +}; + +} // namespace renderive diff --git a/Core/flow/Render_Surface.h b/Core/flow/Render_Surface.h new file mode 100644 index 0000000..14c353c --- /dev/null +++ b/Core/flow/Render_Surface.h @@ -0,0 +1,42 @@ +#pragma once +#include "../base/Geometry.h" +#include "../base/Object_Semantics.h" +#include "../architecture/Frame_Scheduler.h" +#include "../render/Render_Output.h" +#include +#include +#include + +namespace renderive { + +struct Render_Frame_Surface_Handle { + virtual ~Render_Frame_Surface_Handle() = default; +}; + +struct Render_Frame_Surface : Move_Only { + Render_Frame_Surface() = default; + Render_Frame_Surface(Render_Output* output, std::uint64_t input_version, std::unique_ptr handle) + : output(output), input_version(input_version), handle(std::move(handle)) {} + Render_Frame_Surface(Render_Frame_Surface&&) noexcept = default; + Render_Frame_Surface& operator=(Render_Frame_Surface&&) noexcept = default; + ~Render_Frame_Surface() = default; + + Render_Output* output{}; + std::uint64_t input_version{}; + std::unique_ptr handle; + + [[nodiscard]] explicit operator bool() const { + return output && handle; + } +}; + +struct Render_Surface_Publish_Result { + Rect dirty_rect; + std::shared_ptr superseded_frame_record; + std::shared_ptr dropped_frame_record; + std::uint64_t update_request_id{}; + std::uint64_t update_request_time{}; + bool request_present{}; +}; + +} // namespace renderive diff --git a/Core/flow/Render_Ticket.h b/Core/flow/Render_Ticket.h new file mode 100644 index 0000000..4023b24 --- /dev/null +++ b/Core/flow/Render_Ticket.h @@ -0,0 +1,8 @@ +#pragma once +#include "../architecture/Update_Completion.h" + +namespace renderive { + +using Render_Ticket = Update_Ticket; + +} // namespace renderive diff --git a/Core/flow/explicit/Explicit_Frame_Flow.cpp b/Core/flow/explicit/Explicit_Frame_Flow.cpp new file mode 100644 index 0000000..67e96ce --- /dev/null +++ b/Core/flow/explicit/Explicit_Frame_Flow.cpp @@ -0,0 +1,275 @@ +#include "Explicit_Frame_Flow.h" +#include "../../architecture/Render_Time.h" +#include "../../architecture/Update_Completion.h" + +namespace renderive { + +namespace { +struct Explicit_Render_Surface_Handle final : Render_Frame_Surface_Handle { + explicit Explicit_Render_Surface_Handle(std::shared_ptr output) : output(std::move(output)) {} + std::shared_ptr output; +}; + +struct Explicit_Present_Surface_Handle final : Present_Surface_Handle { + explicit Explicit_Present_Surface_Handle(std::shared_ptr output) : output_value(std::move(output)) {} + + [[nodiscard]] Render_Output* output() const override { + return output_value.get(); + } + + std::shared_ptr output_value; +}; + +Explicit_Render_Surface_Handle* explicit_handle(Render_Frame_Surface& surface) { + return static_cast(surface.handle.get()); +} +} // namespace + +Explicit_Frame_Flow::Explicit_Frame_Flow() + : pending_requests(memory_resource(Memory_Domain::Update_Completion)) {} + +Render_Ticket Explicit_Frame_Flow::request_render() { + auto state = make_update_state(); + { + std::lock_guard lock(request_mutex); + pending_requests.push_back(state); + } + manual_request_pending.store(true, std::memory_order_release); + return Render_Ticket(std::move(state)); +} + +void Explicit_Frame_Flow::attach(Frame_Flow_Host& host_value) { + host = &host_value; +} + +Frame_Flow_Runtime_Snapshot Explicit_Frame_Flow::runtime_snapshot(std::uint64_t now_ns, bool destroying) const { + return { + destroying, + manual_request_pending.load(std::memory_order_acquire), + render_job_active.load(std::memory_order_acquire), + frame_ready.load(std::memory_order_acquire), + present_active.load(std::memory_order_acquire), + now_ns + }; +} + +void Explicit_Frame_Flow::advance_edit_version() { + edit_version.fetch_add(1, std::memory_order_acq_rel); +} + +void Explicit_Frame_Flow::cancel_all_update_states() { + { + std::lock_guard lock(request_mutex); + for (auto& state : pending_requests) { + if (state) + state->complete_all(std::make_error_code(std::errc::operation_canceled), Update_Outcome::Cancelled); + } + pending_requests.clear(); + } + std::shared_ptr output; + { + std::lock_guard lock(surface_mutex); + output = std::move(present_surface); + } + if (output) + cancel_update_states(output->presented_update_states); +} + +Render_Frame_Surface Explicit_Frame_Flow::begin_render_frame() { + if (render_job_active.exchange(true, std::memory_order_acq_rel)) + return {}; + auto output = std::make_shared(); + auto handle = std::make_unique(output); + auto* output_pointer = output.get(); + std::uint64_t input_version = edit_version.load(std::memory_order_acquire); + return Render_Frame_Surface(output_pointer, input_version, std::move(handle)); +} + +void Explicit_Frame_Flow::cancel_render_frame(Render_Frame_Surface&& surface) { + (void)surface; + render_job_active.store(false, std::memory_order_release); +} + +void Explicit_Frame_Flow::discard_render_frame(Render_Frame_Surface&& surface) { + (void)surface; + render_job_active.store(false, std::memory_order_release); +} + +Render_Surface_Publish_Result Explicit_Frame_Flow::publish_rendered_frame(Render_Frame_Surface&& surface, std::uint64_t now_ns) { + Render_Surface_Publish_Result result; + auto* handle = explicit_handle(surface); + if (!handle || !handle->output) { + render_job_active.store(false, std::memory_order_release); + return result; + } + if (handle->output->metadata) { + handle->output->metadata->transition_12_ns = now_ns; + handle->output->metadata->ready_queue_wait_ns = now_ns > handle->output->metadata->render_end_ns ? now_ns - handle->output->metadata->render_end_ns : 0; + handle->output->metadata->update_request_id = ++next_present_request_id; + handle->output->metadata->update_request_time_ns = now_ns; + result.update_request_id = handle->output->metadata->update_request_id; + result.update_request_time = now_ns; + } + { + std::lock_guard lock(surface_mutex); + present_surface = std::move(handle->output); + } + rendered_version = surface.input_version; + frame_ready.store(true, std::memory_order_release); + paint_request_pending.store(true, std::memory_order_release); + render_job_active.store(false, std::memory_order_release); + Size size = present_surface ? present_surface->image.size() : Size{}; + result.dirty_rect = Rect{0, 0, size.width, size.height}; + result.request_present = true; + return result; +} + +Render_Surface_Publish_Result Explicit_Frame_Flow::request_present(std::uint64_t now_ns) { + Render_Surface_Publish_Result result; + std::shared_ptr output; + { + std::lock_guard lock(surface_mutex); + output = present_surface; + } + if (!output) + return result; + if (output->metadata) { + output->metadata->update_request_id = ++next_present_request_id; + output->metadata->update_request_time_ns = now_ns; + result.update_request_id = output->metadata->update_request_id; + result.update_request_time = now_ns; + } + paint_request_pending.store(true, std::memory_order_release); + Size size = output->image.size(); + result.dirty_rect = Rect{0, 0, size.width, size.height}; + result.request_present = true; + return result; +} + +Present_Surface_Lease Explicit_Frame_Flow::begin_present(std::uint64_t now_ns, std::shared_ptr& returned_frame_record) { + returned_frame_record.reset(); + std::shared_ptr output; + { + std::lock_guard lock(surface_mutex); + output = present_surface; + } + if (!output || present_active.exchange(true, std::memory_order_acq_rel)) + return {}; + bool is_new = frame_ready.exchange(false, std::memory_order_acq_rel); + bool pending = paint_request_pending.exchange(false, std::memory_order_acq_rel); + if (is_new && output->metadata) { + output->metadata->transition_23_ns = now_ns; + output->metadata->present_queue_wait_ns = now_ns > output->metadata->transition_12_ns ? now_ns - output->metadata->transition_12_ns : 0; + } + return Present_Surface_Lease_Access::from_handle( + std::make_unique(std::move(output)), + is_new, + pending); +} + +std::shared_ptr Explicit_Frame_Flow::end_present(Present_Surface_Lease&& surface, std::uint64_t paint_begin_ns, std::uint64_t paint_end_ns) { + std::shared_ptr record; + Render_Output* output = Present_Surface_Lease_Access::output(surface); + if (output && surface.new_frame()) { + record = output->metadata; + if (record) { + record->paint_begin_ns = paint_begin_ns; + record->paint_end_ns = paint_end_ns; + record->outcome = Frame_Outcome::Presented; + } + complete_update_states(output->presented_update_states, Update_Stage::Presented); + } + Present_Surface_Lease_Access::reset(surface); + present_active.store(false, std::memory_order_release); + return record; +} + +void Explicit_Frame_Flow::activate_view(std::uint64_t now_ns) { + active.store(true, std::memory_order_release); + evaluate(now_ns); +} + +void Explicit_Frame_Flow::deactivate_view() { + active.store(false, std::memory_order_release); +} + +bool Explicit_Frame_Flow::view_active() const { + return active.load(std::memory_order_acquire); +} + +void Explicit_Frame_Flow::notify_model_dirty(std::uint64_t now_ns) { + evaluate(now_ns); +} + +void Explicit_Frame_Flow::notify_viewport_changed(Size, std::uint64_t now_ns) { + evaluate(now_ns); +} + +void Explicit_Frame_Flow::notify_render_finished(Frame_Lifecycle_Record&, std::uint64_t now_ns) { + evaluate(now_ns); +} + +void Explicit_Frame_Flow::notify_paint_finished(std::uint64_t now_ns) { + evaluate(now_ns); +} + +void Explicit_Frame_Flow::notify_frame_returned(Frame_Lifecycle_Record&, std::uint64_t now_ns) { + evaluate(now_ns); +} + +void Explicit_Frame_Flow::notify_timer_fired(std::uint64_t now_ns) { + evaluate(now_ns); +} + +void Explicit_Frame_Flow::drain_frame_update_states(std::pmr::vector>& output) { + std::lock_guard lock(request_mutex); + for (auto& state : pending_requests) + output.push_back(std::move(state)); + pending_requests.clear(); + manual_request_pending.store(false, std::memory_order_release); +} + +Explicit_Diagnostics Explicit_Frame_Flow::explicit_diagnostics() const { + std::lock_guard lock(request_mutex); + return Explicit_Diagnostics{ + active.load(std::memory_order_acquire), + static_cast(pending_requests.size()) + }; +} + +Flow_Diagnostics Explicit_Frame_Flow::diagnostics() const { + Explicit_Diagnostics typed = explicit_diagnostics(); + return Flow_Diagnostics{ + Common_Flow_Diagnostics{typed.active}, + typed + }; +} + +void Explicit_Frame_Flow::shutdown() { + host = nullptr; + cancel_all_update_states(); + active.store(false, std::memory_order_release); + manual_request_pending.store(false, std::memory_order_release); + render_job_active.store(false, std::memory_order_release); + frame_ready.store(false, std::memory_order_release); + present_active.store(false, std::memory_order_release); + paint_request_pending.store(false, std::memory_order_release); +} + +void Explicit_Frame_Flow::evaluate(std::uint64_t now_ns) { + if (!host) + return; + Frame_Flow_Runtime_Snapshot runtime = host->runtime_snapshot(now_ns); + if (runtime.destroying) + return; + if (runtime.middle_has_new_frame && !runtime.paint_active) + host->request_paint_from_middle(); + if (!manual_request_pending.load(std::memory_order_acquire)) + return; + if (runtime.render_job_active) + return; + if (host->try_render_once()) + manual_request_pending.store(false, std::memory_order_release); +} + +} // namespace renderive diff --git a/Core/flow/explicit/Explicit_Frame_Flow.h b/Core/flow/explicit/Explicit_Frame_Flow.h new file mode 100644 index 0000000..693ece2 --- /dev/null +++ b/Core/flow/explicit/Explicit_Frame_Flow.h @@ -0,0 +1,58 @@ +#pragma once +#include "../Frame_Flow.h" +#include "../Render_Ticket.h" +#include +#include +#include + +namespace renderive { + +class Explicit_Frame_Flow final : public Frame_Flow { +public: + Explicit_Frame_Flow(); + [[nodiscard]] Render_Ticket request_render(); + + void attach(Frame_Flow_Host& host) override; + [[nodiscard]] Frame_Flow_Runtime_Snapshot runtime_snapshot(std::uint64_t now_ns, bool destroying) const override; + void advance_edit_version() override; + void cancel_all_update_states() override; + [[nodiscard]] Render_Frame_Surface begin_render_frame() override; + void cancel_render_frame(Render_Frame_Surface&& surface) override; + void discard_render_frame(Render_Frame_Surface&& surface) override; + [[nodiscard]] Render_Surface_Publish_Result publish_rendered_frame(Render_Frame_Surface&& surface, std::uint64_t now_ns) override; + [[nodiscard]] Render_Surface_Publish_Result request_present(std::uint64_t now_ns) override; + [[nodiscard]] Present_Surface_Lease begin_present(std::uint64_t now_ns, std::shared_ptr& returned_frame_record) override; + [[nodiscard]] std::shared_ptr end_present(Present_Surface_Lease&& surface, std::uint64_t paint_begin_ns, std::uint64_t paint_end_ns) override; + void activate_view(std::uint64_t now_ns) override; + void deactivate_view() override; + [[nodiscard]] bool view_active() const override; + void notify_model_dirty(std::uint64_t now_ns) override; + void notify_viewport_changed(Size viewport, std::uint64_t now_ns) override; + void notify_render_finished(Frame_Lifecycle_Record& frame, std::uint64_t now_ns) override; + void notify_paint_finished(std::uint64_t now_ns) override; + void notify_frame_returned(Frame_Lifecycle_Record& frame, std::uint64_t now_ns) override; + void notify_timer_fired(std::uint64_t now_ns) override; + void drain_frame_update_states(std::pmr::vector>& output) override; + [[nodiscard]] Explicit_Diagnostics explicit_diagnostics() const; + [[nodiscard]] Flow_Diagnostics diagnostics() const override; + void shutdown() override; + +private: + void evaluate(std::uint64_t now_ns); + Frame_Flow_Host* host{}; + std::atomic_bool active{false}; + std::atomic_bool manual_request_pending{false}; + std::atomic_bool render_job_active{false}; + std::atomic_bool frame_ready{false}; + std::atomic_bool present_active{false}; + std::atomic_bool paint_request_pending{false}; + mutable std::mutex request_mutex; + mutable std::mutex surface_mutex; + std::pmr::vector> pending_requests; + std::shared_ptr present_surface; + std::atomic edit_version{1}; + std::uint64_t rendered_version{}; + std::uint64_t next_present_request_id{}; +}; + +} // namespace renderive diff --git a/Core/flow/low_latency/Low_Latency_Feedback_Controller.cpp b/Core/flow/low_latency/Low_Latency_Feedback_Controller.cpp new file mode 100644 index 0000000..474c279 --- /dev/null +++ b/Core/flow/low_latency/Low_Latency_Feedback_Controller.cpp @@ -0,0 +1,195 @@ +#include "Low_Latency_Feedback_Controller.h" +#include +#include + +namespace renderive { +namespace { +constexpr double Ratio_Threshold = 1.03; +constexpr int Switch_Samples = 3; +} + +void Low_Latency_Smoothed_Interval::update(double sample_ms) { + if (sample_ms <= 0.0 || !std::isfinite(sample_ms)) + return; + if (!sample_count) { + srtt_ms = sample_ms; + jitter_ms = sample_ms * 0.5; + sample_count = 1; + return; + } + double error = std::abs(sample_ms - srtt_ms); + jitter_ms += 0.25 * (error - jitter_ms); + srtt_ms += 0.125 * (sample_ms - srtt_ms); + ++sample_count; +} + +double Low_Latency_Smoothed_Interval::safe_ms(double jitter_factor) const { + return sample_count ? srtt_ms + jitter_factor * jitter_ms : 0.0; +} + +bool Low_Latency_Smoothed_Interval::valid() const { + return sample_count > 0; +} + +void Low_Latency_Feedback_Controller::reset() { + std::uint64_t user_period = state_value.user_period_ns; + *this = Low_Latency_Feedback_Controller(); + state_value.user_period_ns = user_period; + refresh_control_snapshot(); +} + +void Low_Latency_Feedback_Controller::set_max_render_fps(double fps) { + state_value.user_period_ns = fps > 0.0 ? ms_to_ns(1000.0 / fps) : 0; + refresh_control_snapshot(); +} + +void Low_Latency_Feedback_Controller::on_input_changed(std::uint64_t, std::uint64_t input_version) { + if (input_version) + latest_input_version = input_version; + else + ++latest_input_version; +} + +void Low_Latency_Feedback_Controller::on_render_started(Frame_Lifecycle_Record& frame, std::uint64_t now_ns) { + last_render_begin_ns = now_ns; + frame.render_begin_ns = now_ns; + frame.render_queue_wait_ns = now_ns > frame.render_role_enter_ns ? now_ns - frame.render_role_enter_ns : 0; + refresh_control_snapshot(); + write_state(frame); +} + +void Low_Latency_Feedback_Controller::on_render_completed(Frame_Lifecycle_Record& frame) { + std::uint64_t render_duration_ns = frame.render_end_ns > frame.render_begin_ns ? frame.render_end_ns - frame.render_begin_ns : 0; + last_render_begin_ns = frame.render_begin_ns; + render_period.update(static_cast(render_duration_ns + frame.render_queue_wait_ns) / 1000000.0); + refresh_control_snapshot(); + write_state(frame); +} + +void Low_Latency_Feedback_Controller::on_frame_presented(Frame_Lifecycle_Record& frame) { + std::uint64_t paint_duration_ns = frame.paint_end_ns > frame.paint_begin_ns ? frame.paint_end_ns - frame.paint_begin_ns : 0; + paint_period.update(static_cast(paint_duration_ns + frame.present_queue_wait_ns) / 1000000.0); + frame.outcome = Frame_Outcome::Presented; + refresh_control_snapshot(); + write_state(frame); +} + +void Low_Latency_Feedback_Controller::on_frame_superseded(Frame_Lifecycle_Record& frame) { + frame.outcome = Frame_Outcome::Superseded; + refresh_control_snapshot(); + write_state(frame); +} + +void Low_Latency_Feedback_Controller::on_frame_dropped(Frame_Lifecycle_Record& frame, Frame_Outcome outcome) { + frame.outcome = outcome; + refresh_control_snapshot(); + write_state(frame); +} + +bool Low_Latency_Feedback_Controller::rate_limit_ready(std::uint64_t now_ns) const { + return now_ns >= next_render_time; +} + +std::uint64_t Low_Latency_Feedback_Controller::next_render_time_ns() const { + return next_render_time; +} + +const Refresh_Control_Snapshot& Low_Latency_Feedback_Controller::state() const { + return state_value; +} + +void Low_Latency_Feedback_Controller::write_state(Frame_Lifecycle_Record& frame) const { + frame.refresh = state_value; +} + +void Low_Latency_Feedback_Controller::refresh_control_snapshot() { + std::uint64_t render_period_ns = render_period.valid() ? ms_to_ns(render_period.safe_ms(0.5)) : 0; + std::uint64_t paint_period_ns = paint_period.valid() ? ms_to_ns(paint_period.safe_ms(0.5)) : 0; + std::uint64_t user_period_ns = state_value.user_period_ns; + Refresh_Limit_Source candidate = classify_limit_source(render_period_ns, paint_period_ns, user_period_ns); + std::uint64_t candidate_period = 0; + switch (candidate) { + case Refresh_Limit_Source::Render: + candidate_period = render_period_ns; + break; + case Refresh_Limit_Source::Paint: + candidate_period = paint_period_ns; + break; + case Refresh_Limit_Source::User: + candidate_period = user_period_ns; + break; + case Refresh_Limit_Source::Unknown: + break; + } + update_limit_source(candidate, candidate_period); + std::uint64_t target_period = std::max({render_period_ns, paint_period_ns, user_period_ns}); + state_value.render_period_ns = render_period_ns; + state_value.paint_period_ns = paint_period_ns; + state_value.target_period_ns = target_period; + if (state_value.source == Refresh_Limit_Source::Unknown) + state_value.source = candidate; + if (state_value.source == Refresh_Limit_Source::User && last_render_begin_ns) + next_render_time = last_render_begin_ns + user_period_ns; + else + next_render_time = 0; +} + +Refresh_Limit_Source Low_Latency_Feedback_Controller::classify_limit_source(std::uint64_t render_period_ns, std::uint64_t paint_period_ns, std::uint64_t user_period_ns) const { + if (!render_period_ns && !paint_period_ns) + return user_period_ns ? Refresh_Limit_Source::User : Refresh_Limit_Source::Unknown; + std::uint64_t max_period = std::max({render_period_ns, paint_period_ns, user_period_ns}); + if (max_period == user_period_ns && user_period_ns) + return Refresh_Limit_Source::User; + if (max_period == paint_period_ns && paint_period_ns) + return Refresh_Limit_Source::Paint; + if (max_period == render_period_ns && render_period_ns) + return Refresh_Limit_Source::Render; + return Refresh_Limit_Source::Unknown; +} + +void Low_Latency_Feedback_Controller::update_limit_source(Refresh_Limit_Source candidate, std::uint64_t candidate_period_ns) { + if (candidate == Refresh_Limit_Source::Unknown) + return; + if (state_value.source == Refresh_Limit_Source::Unknown) { + state_value.source = candidate; + candidate_source = candidate; + candidate_count = 0; + return; + } + std::uint64_t current_period = 0; + switch (state_value.source) { + case Refresh_Limit_Source::Render: + current_period = state_value.render_period_ns; + break; + case Refresh_Limit_Source::Paint: + current_period = state_value.paint_period_ns; + break; + case Refresh_Limit_Source::User: + current_period = state_value.user_period_ns; + break; + case Refresh_Limit_Source::Unknown: + break; + } + if (candidate == state_value.source || (current_period && static_cast(candidate_period_ns) <= static_cast(current_period) * Ratio_Threshold)) { + candidate_source = candidate; + candidate_count = 0; + return; + } + if (candidate != candidate_source) { + candidate_source = candidate; + candidate_count = 1; + return; + } + if (++candidate_count >= Switch_Samples) { + state_value.source = candidate; + candidate_count = 0; + } +} + +std::uint64_t Low_Latency_Feedback_Controller::ms_to_ns(double ms) { + if (ms <= 0.0 || !std::isfinite(ms)) + return 0; + return static_cast(ms * 1000000.0); +} + +} // namespace renderive diff --git a/Core/flow/low_latency/Low_Latency_Feedback_Controller.h b/Core/flow/low_latency/Low_Latency_Feedback_Controller.h new file mode 100644 index 0000000..68915e5 --- /dev/null +++ b/Core/flow/low_latency/Low_Latency_Feedback_Controller.h @@ -0,0 +1,53 @@ +#pragma once +#include "../../architecture/Frame_Scheduler.h" +#include + +namespace renderive { + +struct Low_Latency_Smoothed_Interval { + double srtt_ms{}; + double jitter_ms{}; + std::uint64_t sample_count{}; + void update(double sample_ms); + [[nodiscard]] double safe_ms(double jitter_factor = 2.0) const; + [[nodiscard]] bool valid() const; +}; + +struct Low_Latency_Frame_Attempt_Accumulator { + std::uint64_t render_role_busy_count{}; + std::uint64_t publish_dropped_count{}; + std::uint64_t paint_role_busy_count{}; +}; + +class Low_Latency_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_Lifecycle_Record& frame, std::uint64_t now_ns); + void on_render_completed(Frame_Lifecycle_Record& frame); + void on_frame_presented(Frame_Lifecycle_Record& frame); + void on_frame_superseded(Frame_Lifecycle_Record& frame); + void on_frame_dropped(Frame_Lifecycle_Record& frame, Frame_Outcome outcome); + [[nodiscard]] bool rate_limit_ready(std::uint64_t now_ns) const; + [[nodiscard]] std::uint64_t next_render_time_ns() const; + [[nodiscard]] const Refresh_Control_Snapshot& state() const; + void write_state(Frame_Lifecycle_Record& frame) const; + +private: + void refresh_control_snapshot(); + [[nodiscard]] Refresh_Limit_Source classify_limit_source(std::uint64_t render_period_ns, std::uint64_t paint_period_ns, std::uint64_t user_period_ns) const; + void update_limit_source(Refresh_Limit_Source candidate, std::uint64_t candidate_period_ns); + static std::uint64_t ms_to_ns(double ms); + + Refresh_Control_Snapshot state_value; + Low_Latency_Smoothed_Interval render_period; + Low_Latency_Smoothed_Interval paint_period; + Refresh_Limit_Source candidate_source = Refresh_Limit_Source::Unknown; + int candidate_count{}; + std::uint64_t latest_input_version{}; + std::uint64_t last_render_begin_ns{}; + std::uint64_t next_render_time{}; +}; + +} // namespace renderive diff --git a/Core/flow/low_latency/Low_Latency_Frame_Flow.cpp b/Core/flow/low_latency/Low_Latency_Frame_Flow.cpp new file mode 100644 index 0000000..d364032 --- /dev/null +++ b/Core/flow/low_latency/Low_Latency_Frame_Flow.cpp @@ -0,0 +1,268 @@ +#include "Low_Latency_Frame_Flow.h" +#include "Low_Latency_Feedback_Controller.h" +#include "Low_Latency_Frame_Pipeline.h" +#include "../../architecture/Render_Time.h" +#include "../../architecture/Update_Completion.h" + +namespace renderive { + +namespace { +struct Low_Latency_Render_Surface_Handle final : Render_Frame_Surface_Handle { + Low_Latency_Frame_Lease lease; + std::uint64_t previous_rendered_edit_version{}; +}; + +struct Low_Latency_Present_Surface_Handle final : Present_Surface_Handle { + explicit Low_Latency_Present_Surface_Handle(Low_Latency_Frame_Lease lease) : lease(std::move(lease)) {} + + [[nodiscard]] Render_Output* output() const override { + return lease.frame; + } + + Low_Latency_Frame_Lease lease; +}; + +Low_Latency_Render_Surface_Handle* low_latency_handle(Render_Frame_Surface& surface) { + return static_cast(surface.handle.get()); +} + +Render_Surface_Publish_Result to_surface_result(Low_Latency_Frame_Pipeline::Frame_Publish_Result result) { + return Render_Surface_Publish_Result{ + result.dirty_rect, + std::move(result.superseded_frame_record), + std::move(result.dropped_frame_record), + result.update_request_id, + result.update_request_time, + result.request_present + }; +} +} // namespace + +struct Low_Latency_Frame_Flow::Private { + std::atomic max_render_fps{60.0}; + std::atomic_bool active{false}; + Frame_Flow_Host* host{}; + Low_Latency_Feedback_Controller feedback; + Low_Latency_Frame_Pipeline pipeline; + + void evaluate(std::uint64_t now_ns) { + if (!host) + return; + feedback.set_max_render_fps(max_render_fps.load(std::memory_order_acquire)); + Frame_Flow_Runtime_Snapshot runtime = host->runtime_snapshot(now_ns); + if (!active.load(std::memory_order_acquire) || runtime.destroying) { + host->cancel_render_deadline(); + return; + } + if (runtime.middle_has_new_frame && !runtime.paint_active) + host->request_paint_from_middle(); + if (!runtime.dirty || runtime.render_job_active) { + if (!runtime.dirty) + host->cancel_render_deadline(); + return; + } + Refresh_Control_Snapshot control = feedback.state(); + switch (control.source) { + case Refresh_Limit_Source::Paint: + if (!runtime.middle_has_new_frame) + (void)host->try_render_once(); + break; + case Refresh_Limit_Source::User: { + std::uint64_t next_ns = feedback.next_render_time_ns(); + if (next_ns && !feedback.rate_limit_ready(runtime.now_ns)) + host->schedule_render_deadline(next_ns); + else + (void)host->try_render_once(); + break; + } + case Refresh_Limit_Source::Render: + case Refresh_Limit_Source::Unknown: + (void)host->try_render_once(); + break; + } + } +}; + +static constexpr double k_default_max_fps = 60.0; + +Low_Latency_Frame_Flow::Low_Latency_Frame_Flow() + : d(std::make_unique()) {} + +Low_Latency_Frame_Flow::~Low_Latency_Frame_Flow() = default; + +double Low_Latency_Frame_Flow::max_render_fps() const { + return d->max_render_fps.load(std::memory_order_acquire); +} + +void Low_Latency_Frame_Flow::set_max_render_fps(double fps) { + if (fps <= 0.0) + fps = k_default_max_fps; + d->max_render_fps.store(fps, std::memory_order_release); +} + +Refresh_Control_Snapshot Low_Latency_Frame_Flow::feedback_state() const { + return d->feedback.state(); +} + +Low_Latency_Diagnostics Low_Latency_Frame_Flow::low_latency_diagnostics() const { + return Low_Latency_Diagnostics{ + d->active.load(std::memory_order_acquire), + max_render_fps(), + d->feedback.state() + }; +} + +void Low_Latency_Frame_Flow::attach(Frame_Flow_Host& host) { + d->host = &host; +} + +Frame_Flow_Runtime_Snapshot Low_Latency_Frame_Flow::runtime_snapshot(std::uint64_t now_ns, bool destroying) const { + return { + destroying, + d->pipeline.has_unrendered_edit(), + d->pipeline.render_job_state != Frame_Job_State::Idle, + d->pipeline.middle_frame_ready(), + d->pipeline.painting_frame_in_use(), + now_ns + }; +} + +void Low_Latency_Frame_Flow::advance_edit_version() { + d->pipeline.advance_edit_version(); +} + +void Low_Latency_Frame_Flow::cancel_all_update_states() { + d->pipeline.cancel_all_update_states(); +} + +Render_Frame_Surface Low_Latency_Frame_Flow::begin_render_frame() { + auto frame_lease = d->pipeline.try_acquire_render_frame(); + if (!frame_lease) + return {}; + auto handle = std::make_unique(); + handle->previous_rendered_edit_version = d->pipeline.rendered_edit_version; + handle->lease = std::move(frame_lease); + d->pipeline.render_job_state = Frame_Job_State::Running; + d->pipeline.rendered_edit_version = d->pipeline.edit_version.load(std::memory_order_acquire); + auto* output = handle->lease.frame; + std::uint64_t input_version = d->pipeline.rendered_edit_version; + return Render_Frame_Surface(output, input_version, std::move(handle)); +} + +void Low_Latency_Frame_Flow::cancel_render_frame(Render_Frame_Surface&& surface) { + if (auto* handle = low_latency_handle(surface)) + handle->lease.reset(); + d->pipeline.render_job_state = Frame_Job_State::Idle; +} + +void Low_Latency_Frame_Flow::discard_render_frame(Render_Frame_Surface&& surface) { + if (auto* handle = low_latency_handle(surface)) { + handle->lease.reset(); + d->pipeline.rendered_edit_version = handle->previous_rendered_edit_version; + } + d->pipeline.render_job_state = Frame_Job_State::Idle; +} + +Render_Surface_Publish_Result Low_Latency_Frame_Flow::publish_rendered_frame(Render_Frame_Surface&& surface, std::uint64_t now_ns) { + Render_Surface_Publish_Result result; + if (auto* handle = low_latency_handle(surface)) { + if (handle->lease.frame && handle->lease.frame->metadata) + handle->lease.frame->version.store(handle->lease.frame->metadata->frame_id, std::memory_order_release); + result = to_surface_result(d->pipeline.try_publish_rendered_frame(std::move(handle->lease), now_ns)); + } + d->pipeline.render_job_state = Frame_Job_State::Idle; + return result; +} + +Render_Surface_Publish_Result Low_Latency_Frame_Flow::request_present(std::uint64_t now_ns) { + return to_surface_result(d->pipeline.request_present(now_ns)); +} + +Present_Surface_Lease Low_Latency_Frame_Flow::begin_present(std::uint64_t now_ns, std::shared_ptr& returned_frame_record) { + auto consumed = d->pipeline.try_acquire_paint_frame(now_ns); + returned_frame_record = std::move(consumed.returned_frame_record); + if (!consumed.lease) + return Present_Surface_Lease_Access::from_handle({}, consumed.has_new_frame, consumed.present_request_was_pending); + return Present_Surface_Lease_Access::from_handle( + std::make_unique(std::move(consumed.lease)), + consumed.has_new_frame, + consumed.present_request_was_pending); +} + +std::shared_ptr Low_Latency_Frame_Flow::end_present(Present_Surface_Lease&& surface, std::uint64_t paint_begin_ns, std::uint64_t paint_end_ns) { + Render_Output* output = Present_Surface_Lease_Access::output(surface); + if (output) { + if (output->metadata) { + output->metadata->paint_begin_ns = paint_begin_ns; + output->metadata->paint_end_ns = paint_end_ns; + output->metadata->present_queue_wait_ns = paint_begin_ns > output->metadata->transition_12_ns ? paint_begin_ns - output->metadata->transition_12_ns : 0; + } + complete_update_states(output->presented_update_states, Update_Stage::Presented); + } + Present_Surface_Lease_Access::reset(surface); + return {}; +} + +void Low_Latency_Frame_Flow::activate_view(std::uint64_t now_ns) { + bool was_active = d->active.exchange(true, std::memory_order_acq_rel); + if (!was_active) + d->feedback.reset(); + d->evaluate(now_ns); +} + +void Low_Latency_Frame_Flow::deactivate_view() { + d->active.store(false, std::memory_order_release); + if (d->host) + d->host->cancel_render_deadline(); +} + +bool Low_Latency_Frame_Flow::view_active() const { + return d->active.load(std::memory_order_acquire); +} + +void Low_Latency_Frame_Flow::notify_model_dirty(std::uint64_t now_ns) { + d->feedback.on_input_changed(now_ns); + d->evaluate(now_ns); +} + +void Low_Latency_Frame_Flow::notify_viewport_changed(Size, std::uint64_t now_ns) { + d->evaluate(now_ns); +} + +void Low_Latency_Frame_Flow::notify_render_finished(Frame_Lifecycle_Record& frame, std::uint64_t now_ns) { + d->feedback.on_render_completed(frame); + d->evaluate(now_ns); +} + +void Low_Latency_Frame_Flow::notify_paint_finished(std::uint64_t now_ns) { + d->evaluate(now_ns); +} + +void Low_Latency_Frame_Flow::notify_frame_returned(Frame_Lifecycle_Record& frame, std::uint64_t now_ns) { + if (frame.outcome == Frame_Outcome::Superseded) + d->feedback.on_frame_superseded(frame); + else + d->feedback.on_frame_presented(frame); + d->evaluate(now_ns); +} + +void Low_Latency_Frame_Flow::notify_timer_fired(std::uint64_t now_ns) { + d->evaluate(now_ns); +} + +void Low_Latency_Frame_Flow::drain_frame_update_states(std::pmr::vector>&) {} + +Flow_Diagnostics Low_Latency_Frame_Flow::diagnostics() const { + Low_Latency_Diagnostics typed = low_latency_diagnostics(); + return Flow_Diagnostics{ + Common_Flow_Diagnostics{typed.active}, + typed + }; +} + +void Low_Latency_Frame_Flow::shutdown() { + deactivate_view(); + d->host = nullptr; +} + +} // namespace renderive diff --git a/Core/flow/low_latency/Low_Latency_Frame_Flow.h b/Core/flow/low_latency/Low_Latency_Frame_Flow.h new file mode 100644 index 0000000..b5de8bd --- /dev/null +++ b/Core/flow/low_latency/Low_Latency_Frame_Flow.h @@ -0,0 +1,48 @@ +#pragma once +#include "../Frame_Flow.h" +#include +#include +#include + +namespace renderive { + +class Low_Latency_Frame_Flow final : public Frame_Flow { +public: + Low_Latency_Frame_Flow(); + ~Low_Latency_Frame_Flow() override; + + [[nodiscard]] double max_render_fps() const; + void set_max_render_fps(double fps); + [[nodiscard]] Refresh_Control_Snapshot feedback_state() const; + [[nodiscard]] Low_Latency_Diagnostics low_latency_diagnostics() const; + + void attach(Frame_Flow_Host& host) override; + [[nodiscard]] Frame_Flow_Runtime_Snapshot runtime_snapshot(std::uint64_t now_ns, bool destroying) const override; + void advance_edit_version() override; + void cancel_all_update_states() override; + [[nodiscard]] Render_Frame_Surface begin_render_frame() override; + void cancel_render_frame(Render_Frame_Surface&& surface) override; + void discard_render_frame(Render_Frame_Surface&& surface) override; + [[nodiscard]] Render_Surface_Publish_Result publish_rendered_frame(Render_Frame_Surface&& surface, std::uint64_t now_ns) override; + [[nodiscard]] Render_Surface_Publish_Result request_present(std::uint64_t now_ns) override; + [[nodiscard]] Present_Surface_Lease begin_present(std::uint64_t now_ns, std::shared_ptr& returned_frame_record) override; + [[nodiscard]] std::shared_ptr end_present(Present_Surface_Lease&& surface, std::uint64_t paint_begin_ns, std::uint64_t paint_end_ns) override; + void activate_view(std::uint64_t now_ns) override; + void deactivate_view() override; + [[nodiscard]] bool view_active() const override; + void notify_model_dirty(std::uint64_t now_ns) override; + void notify_viewport_changed(Size viewport, std::uint64_t now_ns) override; + void notify_render_finished(Frame_Lifecycle_Record& frame, std::uint64_t now_ns) override; + void notify_paint_finished(std::uint64_t now_ns) override; + void notify_frame_returned(Frame_Lifecycle_Record& frame, std::uint64_t now_ns) override; + void notify_timer_fired(std::uint64_t now_ns) override; + void drain_frame_update_states(std::pmr::vector>& output) override; + [[nodiscard]] Flow_Diagnostics diagnostics() const override; + void shutdown() override; + +private: + struct Private; + std::unique_ptr d; +}; + +} // namespace renderive diff --git a/Core/plot/Plot_Frame_Pipeline.cpp b/Core/flow/low_latency/Low_Latency_Frame_Pipeline.cpp similarity index 63% rename from Core/plot/Plot_Frame_Pipeline.cpp rename to Core/flow/low_latency/Low_Latency_Frame_Pipeline.cpp index b3537a6..d228c39 100644 --- a/Core/plot/Plot_Frame_Pipeline.cpp +++ b/Core/flow/low_latency/Low_Latency_Frame_Pipeline.cpp @@ -1,22 +1,21 @@ -#include "Plot_Frame_Pipeline.h" -#include "../architecture/Update_Completion.h" +#include "Low_Latency_Frame_Pipeline.h" +#include "../../architecture/Update_Completion.h" #include namespace renderive { -Plot_Frame::Plot_Frame() - : presented_update_states(memory_resource(Memory_Domain::Update_Completion)), - metadata(std::make_shared()) {} +Low_Latency_Frame::Low_Latency_Frame() + = default; -Plot_Frame_Lease::Plot_Frame_Lease(Plot_Frame* buffer, Triple_Role_Buffer_Control* control, Triple_Buffer_Lease lease) +Low_Latency_Frame_Lease::Low_Latency_Frame_Lease(Low_Latency_Frame* buffer, Triple_Role_Buffer_Control* control, Triple_Buffer_Lease lease) : frame(buffer), control(control), lease(lease) {} -Plot_Frame_Lease::Plot_Frame_Lease(Plot_Frame_Lease&& other) noexcept +Low_Latency_Frame_Lease::Low_Latency_Frame_Lease(Low_Latency_Frame_Lease&& other) noexcept : frame(std::exchange(other.frame, nullptr)), control(std::exchange(other.control, nullptr)), lease(std::exchange(other.lease, Triple_Buffer_Lease{})) {} -Plot_Frame_Lease& Plot_Frame_Lease::operator=(Plot_Frame_Lease&& other) noexcept { +Low_Latency_Frame_Lease& Low_Latency_Frame_Lease::operator=(Low_Latency_Frame_Lease&& other) noexcept { if (this == &other) return *this; reset(); @@ -26,11 +25,11 @@ Plot_Frame_Lease& Plot_Frame_Lease::operator=(Plot_Frame_Lease&& other) noexcept return *this; } -Plot_Frame_Lease::~Plot_Frame_Lease() { +Low_Latency_Frame_Lease::~Low_Latency_Frame_Lease() { reset(); } -void Plot_Frame_Lease::reset() { +void Low_Latency_Frame_Lease::reset() { if (!control) return; control->unmark_use(lease); @@ -39,39 +38,39 @@ void Plot_Frame_Lease::reset() { lease = {}; } -Plot_Frame_Pipeline::Plot_Frame_Pipeline() { +Low_Latency_Frame_Pipeline::Low_Latency_Frame_Pipeline() { frame_control.reset(); } -Plot_Frame_Pipeline::~Plot_Frame_Pipeline() { +Low_Latency_Frame_Pipeline::~Low_Latency_Frame_Pipeline() { cancel_all_update_states(); } -void Plot_Frame_Pipeline::advance_edit_version() { +void Low_Latency_Frame_Pipeline::advance_edit_version() { edit_version.fetch_add(1, std::memory_order_release); } -bool Plot_Frame_Pipeline::has_unrendered_edit() const { +bool Low_Latency_Frame_Pipeline::has_unrendered_edit() const { return buffer_version_newer(edit_version.load(std::memory_order_acquire), rendered_edit_version); } -Plot_Frame* Plot_Frame_Pipeline::frame_by_index(std::uint8_t index) { +Low_Latency_Frame* Low_Latency_Frame_Pipeline::frame_by_index(std::uint8_t index) { return &frames[index]; } -const Plot_Frame* Plot_Frame_Pipeline::frame_by_index(std::uint8_t index) const { +const Low_Latency_Frame* Low_Latency_Frame_Pipeline::frame_by_index(std::uint8_t index) const { return &frames[index]; } -Plot_Frame* Plot_Frame_Pipeline::frame_by_role(std::uint8_t role) { +Low_Latency_Frame* Low_Latency_Frame_Pipeline::frame_by_role(std::uint8_t role) { return frame_by_index(frame_control.read_view()[role]); } -const Plot_Frame* Plot_Frame_Pipeline::frame_by_role(std::uint8_t role) const { +const Low_Latency_Frame* Low_Latency_Frame_Pipeline::frame_by_role(std::uint8_t role) const { return frame_by_index(frame_control.read_view()[role]); } -Plot_Frame_Lease Plot_Frame_Pipeline::try_acquire_render_frame() { +Low_Latency_Frame_Lease Low_Latency_Frame_Pipeline::try_acquire_render_frame() { auto record = frame_control.try_acquire_role(Frame_Rendering); if (record.result != Triple_Buffer_Result::Success) { ++attempt_counters.render_role_busy_count; @@ -80,24 +79,24 @@ Plot_Frame_Lease Plot_Frame_Pipeline::try_acquire_render_frame() { return {frame_by_index(record.lease.index), &frame_control, record.lease}; } -Plot_Frame_Pipeline::Frame_Publish_Result Plot_Frame_Pipeline::try_publish_rendered_frame(Plot_Frame_Lease rendering_lease, std::uint64_t now_ns) { +Low_Latency_Frame_Pipeline::Frame_Publish_Result Low_Latency_Frame_Pipeline::try_publish_rendered_frame(Low_Latency_Frame_Lease rendering_lease, std::uint64_t now_ns) { Frame_Publish_Result result; if (!rendering_lease) return result; - Plot_Frame* rendered = rendering_lease.frame; + Low_Latency_Frame* rendered = rendering_lease.frame; auto swap = frame_control.try_swap_role_with_left_lease(Frame_Rendering, Frame_Middle, rendering_lease.lease, [this](const Triple_Buffer_View& view) { - const Plot_Frame* rendering = frame_by_index(view[Frame_Rendering]); - const Plot_Frame* middle = frame_by_index(view[Frame_Middle]); + const Low_Latency_Frame* rendering = frame_by_index(view[Frame_Rendering]); + const Low_Latency_Frame* middle = frame_by_index(view[Frame_Middle]); return buffer_version_newer(rendering->version.load(std::memory_order_acquire), middle->version.load(std::memory_order_acquire)); }, [this, &result, now_ns](const Triple_Buffer_View& old_view, const Triple_Buffer_View& new_view) { - Plot_Frame* rendered = frame_by_index(old_view[Frame_Rendering]); - Plot_Frame* old_middle = frame_by_index(old_view[Frame_Middle]); - const Plot_Frame* painting = frame_by_index(old_view[Frame_Painting]); + Low_Latency_Frame* rendered = frame_by_index(old_view[Frame_Rendering]); + Low_Latency_Frame* old_middle = frame_by_index(old_view[Frame_Middle]); + const Low_Latency_Frame* painting = frame_by_index(old_view[Frame_Painting]); bool supersede_middle = buffer_version_newer(old_middle->version.load(std::memory_order_acquire), painting->version.load(std::memory_order_acquire)); rendered->metadata->transition_12_ns = now_ns; - rendered->metadata->wait_12_ns = now_ns > rendered->metadata->render_end_ns ? now_ns - rendered->metadata->render_end_ns : 0; + rendered->metadata->ready_queue_wait_ns = now_ns > rendered->metadata->render_end_ns ? now_ns - rendered->metadata->render_end_ns : 0; if (supersede_middle && old_middle->metadata && old_middle->metadata->frame_id) { old_middle->metadata->superseded_time_ns = now_ns; @@ -106,7 +105,7 @@ Plot_Frame_Pipeline::Frame_Publish_Result Plot_Frame_Pipeline::try_publish_rende supersede_update_states(old_middle->presented_update_states); } - Plot_Frame* next_rendering = frame_by_index(new_view[Frame_Rendering]); + Low_Latency_Frame* next_rendering = frame_by_index(new_view[Frame_Rendering]); next_rendering->metadata = std::make_shared(); next_rendering->metadata->render_role_enter_ns = now_ns; }); @@ -125,15 +124,15 @@ Plot_Frame_Pipeline::Frame_Publish_Result Plot_Frame_Pipeline::try_publish_rende return update_result; } -Plot_Frame_Pipeline::Frame_Publish_Result Plot_Frame_Pipeline::request_present(std::uint64_t now_ns) { +Low_Latency_Frame_Pipeline::Frame_Publish_Result Low_Latency_Frame_Pipeline::request_present(std::uint64_t now_ns) { Frame_Publish_Result result; auto record = frame_control.try_acquire_role(Frame_Middle); if (record.result != Triple_Buffer_Result::Success) return result; - Plot_Frame_Lease middle_lease{frame_by_index(record.lease.index), &frame_control, record.lease}; - Plot_Frame* middle = middle_lease.frame; - const Plot_Frame* painting = frame_by_role(Frame_Painting); + Low_Latency_Frame_Lease middle_lease{frame_by_index(record.lease.index), &frame_control, record.lease}; + Low_Latency_Frame* middle = middle_lease.frame; + const Low_Latency_Frame* painting = frame_by_role(Frame_Painting); if (!buffer_version_newer(middle->version.load(std::memory_order_acquire), painting->version.load(std::memory_order_acquire))) return result; @@ -155,26 +154,26 @@ Plot_Frame_Pipeline::Frame_Publish_Result Plot_Frame_Pipeline::request_present(s return result; } -Plot_Frame_Pipeline::Frame_Consume_Result Plot_Frame_Pipeline::try_acquire_paint_frame(std::uint64_t now_ns) { +Low_Latency_Frame_Pipeline::Frame_Consume_Result Low_Latency_Frame_Pipeline::try_acquire_paint_frame(std::uint64_t now_ns) { Frame_Consume_Result result; bool pending = paint_request_pending.load(std::memory_order_acquire); if (middle_frame_ready()) { auto swap = frame_control.try_swap_role(Frame_Middle, Frame_Painting, [this](const Triple_Buffer_View& view) { - const Plot_Frame* middle = frame_by_index(view[Frame_Middle]); - const Plot_Frame* painting = frame_by_index(view[Frame_Painting]); + const Low_Latency_Frame* middle = frame_by_index(view[Frame_Middle]); + const Low_Latency_Frame* painting = frame_by_index(view[Frame_Painting]); return buffer_version_newer(middle->version.load(std::memory_order_acquire), painting->version.load(std::memory_order_acquire)); }, [this, &result, now_ns](const Triple_Buffer_View& old_view, const Triple_Buffer_View&) { - Plot_Frame* old_painting = frame_by_index(old_view[Frame_Painting]); - Plot_Frame* new_painting = frame_by_index(old_view[Frame_Middle]); + Low_Latency_Frame* old_painting = frame_by_index(old_view[Frame_Painting]); + Low_Latency_Frame* new_painting = frame_by_index(old_view[Frame_Middle]); result.has_new_frame = true; if (old_painting->metadata && old_painting->metadata->frame_id && old_painting->metadata->paint_end_ns) { old_painting->metadata->transition_32_ns = now_ns; - old_painting->metadata->wait_32_ns = now_ns > old_painting->metadata->paint_end_ns ? now_ns - old_painting->metadata->paint_end_ns : 0; + old_painting->metadata->paint_return_wait_ns = now_ns > old_painting->metadata->paint_end_ns ? now_ns - old_painting->metadata->paint_end_ns : 0; result.returned_frame_record = old_painting->metadata; } new_painting->metadata->transition_23_ns = now_ns; - new_painting->metadata->wait_23_ns = now_ns > new_painting->metadata->transition_12_ns ? now_ns - new_painting->metadata->transition_12_ns : 0; + new_painting->metadata->present_queue_wait_ns = now_ns > new_painting->metadata->transition_12_ns ? now_ns - new_painting->metadata->transition_12_ns : 0; }); if (swap.result != Triple_Buffer_Result::Success) { result.paint_frame_unavailable = true; @@ -194,18 +193,18 @@ Plot_Frame_Pipeline::Frame_Consume_Result Plot_Frame_Pipeline::try_acquire_paint return result; } -bool Plot_Frame_Pipeline::middle_frame_ready() const { - const Plot_Frame* middle = frame_by_role(Frame_Middle); - const Plot_Frame* painting = frame_by_role(Frame_Painting); +bool Low_Latency_Frame_Pipeline::middle_frame_ready() const { + const Low_Latency_Frame* middle = frame_by_role(Frame_Middle); + const Low_Latency_Frame* painting = frame_by_role(Frame_Painting); return buffer_version_newer(middle->version.load(std::memory_order_acquire), painting->version.load(std::memory_order_acquire)); } -bool Plot_Frame_Pipeline::painting_frame_in_use() const { +bool Low_Latency_Frame_Pipeline::painting_frame_in_use() const { return frame_control.role_busy(Frame_Painting); } -void Plot_Frame_Pipeline::cancel_all_update_states() { - for (Plot_Frame& frame : frames) +void Low_Latency_Frame_Pipeline::cancel_all_update_states() { + for (Low_Latency_Frame& frame : frames) cancel_update_states(frame.presented_update_states); } diff --git a/Core/plot/Plot_Frame_Pipeline.h b/Core/flow/low_latency/Low_Latency_Frame_Pipeline.h similarity index 55% rename from Core/plot/Plot_Frame_Pipeline.h rename to Core/flow/low_latency/Low_Latency_Frame_Pipeline.h index e548728..4950c57 100644 --- a/Core/plot/Plot_Frame_Pipeline.h +++ b/Core/flow/low_latency/Low_Latency_Frame_Pipeline.h @@ -1,10 +1,12 @@ #pragma once -#include "../architecture/Frame_Scheduler.h" -#include "../architecture/Render_Config.h" -#include "../architecture/Triple_Buffer.h" -#include "../base/Geometry.h" -#include "../base/Object_Semantics.h" -#include "../render/Image.h" +#include "../../architecture/Frame_Scheduler.h" +#include "../../architecture/Render_Config.h" +#include "../../base/Geometry.h" +#include "../../base/Object_Semantics.h" +#include "../../render/Render_Output.h" +#include "Low_Latency_Feedback_Controller.h" +#include "Low_Latency_Roles.h" +#include "Triple_Buffer.h" #include #include #include @@ -16,28 +18,25 @@ enum class Frame_Job_State { Idle, Running }; -struct Plot_Frame { - Plot_Frame(); +struct Low_Latency_Frame : Render_Output { + Low_Latency_Frame(); std::atomic version{0}; - Image image; - std::pmr::vector> presented_update_states; - std::shared_ptr metadata; }; -struct Plot_Frame_Lease : Move_Only { - Plot_Frame_Lease() = default; - Plot_Frame_Lease(Plot_Frame* buffer, Triple_Role_Buffer_Control* control, Triple_Buffer_Lease lease); - Plot_Frame_Lease(Plot_Frame_Lease&& other) noexcept; - Plot_Frame_Lease& operator=(Plot_Frame_Lease&& other) noexcept; - ~Plot_Frame_Lease(); +struct Low_Latency_Frame_Lease : Move_Only { + Low_Latency_Frame_Lease() = default; + Low_Latency_Frame_Lease(Low_Latency_Frame* buffer, Triple_Role_Buffer_Control* control, Triple_Buffer_Lease lease); + Low_Latency_Frame_Lease(Low_Latency_Frame_Lease&& other) noexcept; + Low_Latency_Frame_Lease& operator=(Low_Latency_Frame_Lease&& other) noexcept; + ~Low_Latency_Frame_Lease(); void reset(); - Plot_Frame* frame{}; + Low_Latency_Frame* frame{}; Triple_Role_Buffer_Control* control{}; Triple_Buffer_Lease lease{}; explicit operator bool() const { return frame && lease; } }; -class Plot_Frame_Pipeline { +class Low_Latency_Frame_Pipeline { public: struct Frame_Publish_Result { Rect dirty_rect; @@ -48,33 +47,33 @@ public: bool request_present{}; }; struct Frame_Consume_Result { - Plot_Frame_Lease lease; + Low_Latency_Frame_Lease lease; std::shared_ptr returned_frame_record; bool has_new_frame{}; bool present_request_was_pending{}; bool paint_frame_unavailable{}; }; - Plot_Frame_Pipeline(); - ~Plot_Frame_Pipeline(); + Low_Latency_Frame_Pipeline(); + ~Low_Latency_Frame_Pipeline(); void advance_edit_version(); [[nodiscard]] bool has_unrendered_edit() const; - [[nodiscard]] Plot_Frame* frame_by_index(std::uint8_t index); - [[nodiscard]] const Plot_Frame* frame_by_index(std::uint8_t index) const; - [[nodiscard]] Plot_Frame* frame_by_role(std::uint8_t role); - [[nodiscard]] const Plot_Frame* frame_by_role(std::uint8_t role) const; - [[nodiscard]] Plot_Frame_Lease try_acquire_render_frame(); - [[nodiscard]] Frame_Publish_Result try_publish_rendered_frame(Plot_Frame_Lease rendering_lease, std::uint64_t now_ns); + [[nodiscard]] Low_Latency_Frame* frame_by_index(std::uint8_t index); + [[nodiscard]] const Low_Latency_Frame* frame_by_index(std::uint8_t index) const; + [[nodiscard]] Low_Latency_Frame* frame_by_role(std::uint8_t role); + [[nodiscard]] const Low_Latency_Frame* frame_by_role(std::uint8_t role) const; + [[nodiscard]] Low_Latency_Frame_Lease try_acquire_render_frame(); + [[nodiscard]] Frame_Publish_Result try_publish_rendered_frame(Low_Latency_Frame_Lease rendering_lease, std::uint64_t now_ns); [[nodiscard]] Frame_Publish_Result request_present(std::uint64_t now_ns); [[nodiscard]] Frame_Consume_Result try_acquire_paint_frame(std::uint64_t now_ns); [[nodiscard]] bool middle_frame_ready() const; [[nodiscard]] bool painting_frame_in_use() const; void cancel_all_update_states(); Triple_Role_Buffer_Control frame_control; - std::array frames; + std::array frames; std::atomic edit_version{1}; std::uint64_t rendered_edit_version = 0; Frame_Job_State render_job_state = Frame_Job_State::Idle; - Frame_Attempt_Accumulator attempt_counters; + Low_Latency_Frame_Attempt_Accumulator attempt_counters; std::atomic_bool paint_request_pending{false}; std::atomic pending_present_request_id{0}; std::atomic pending_present_request_time_ns{0}; diff --git a/Core/flow/low_latency/Low_Latency_Roles.h b/Core/flow/low_latency/Low_Latency_Roles.h new file mode 100644 index 0000000..028fbd7 --- /dev/null +++ b/Core/flow/low_latency/Low_Latency_Roles.h @@ -0,0 +1,28 @@ +#pragma once +#include + +namespace renderive { + +enum Frame_Role : std::uint8_t { + Frame_Rendering, + Frame_Middle, + Frame_Painting +}; + +enum State_Role : std::uint8_t { + State_Edit, + State_Ready, + State_Render +}; + +enum Input_Role : std::uint8_t { + Input_Edit, + Input_Ready, + Input_Render +}; + +inline bool buffer_version_newer(std::uint64_t a, std::uint64_t b) { + return static_cast(a - b) > 0; +} + +} // namespace renderive diff --git a/Core/architecture/Render_Data.cpp b/Core/flow/low_latency/Render_Data.cpp similarity index 58% rename from Core/architecture/Render_Data.cpp rename to Core/flow/low_latency/Render_Data.cpp index 6229a8b..ed6ec58 100644 --- a/Core/architecture/Render_Data.cpp +++ b/Core/flow/low_latency/Render_Data.cpp @@ -1,5 +1,7 @@ -#include "Renderable.h" +#include "Render_Data.h" +#include "../../architecture/Renderable.h" #include +#include namespace renderive { std::ostream& operator<<(std::ostream& stream, const Range& range) { stream << "Range(" << range.origin << ", " << range.target << ")"; @@ -7,7 +9,7 @@ std::ostream& operator<<(std::ostream& stream, const Range& range) { } Render_Edit_Guard::Render_Edit_Guard(Render_Data* data, Triple_Buffer_Lease lease) : data(data), lease(lease) {} Render_Edit_Guard::~Render_Edit_Guard() { - if (!data) + if (!data || !lease) return; ++data->state_by_index(lease.index)->version; auto q = data->q_ptr; @@ -33,6 +35,33 @@ void Render_Data::drain_committed_update_states(std::pmr::vector(); + state_buffers[State_Ready] = state_buffer_owners[State_Ready].get(); + state_buffers[State_Render] = state_buffer_owners[State_Render].get(); + input_buffer_owners[Input_Edit] = std::move(edit_input); + input_buffer_owners[Input_Ready] = std::move(ready_input); + input_buffer_owners[Input_Render] = std::move(render_input); + if (input_buffer_owners[Input_Edit]) + input_buffers[Input_Edit].data = input_buffer_owners[Input_Edit].get(); + if (input_buffer_owners[Input_Ready]) + input_buffers[Input_Ready].data = input_buffer_owners[Input_Ready].get(); + if (input_buffer_owners[Input_Render]) + input_buffers[Input_Render].data = input_buffer_owners[Input_Render].get(); + if (state_buffers[State_Edit]) + state_buffers[State_Edit]->version = 1; +} void Render_Data::clear_input_role(std::uint8_t role) { Input_Buffer_Slot& slot = input_slot(role); if (slot.completion_state) { @@ -45,7 +74,8 @@ void Render_Data::clear_input_role(std::uint8_t role) { slot.completion_state.reset(); } } - slot.data->clear(); + if (slot.data) + slot.data->clear(); slot.pending = false; } void Render_Data::cancel_pending_completions() { diff --git a/Core/architecture/Render_Data.h b/Core/flow/low_latency/Render_Data.h similarity index 86% rename from Core/architecture/Render_Data.h rename to Core/flow/low_latency/Render_Data.h index f29c30a..398b7ad 100644 --- a/Core/architecture/Render_Data.h +++ b/Core/flow/low_latency/Render_Data.h @@ -1,21 +1,23 @@ #pragma once -#include "../base/Geometry.h" -#include "../base/Object_Semantics.h" -#include "../event/Event.h" -#include "../event/Key_Event.h" -#include "../event/Pointer_Event.h" -#include "../event/Resize_Event.h" -#include "../event/Wheel_Event.h" -#include "../render/Canvas.h" -#include "Buffer_Role.h" -#include "Plot_Render_Context.h" -#include "Render_Config.h" +#include "../../architecture/Buffer_Role.h" +#include "../../architecture/Plot_Render_Context.h" +#include "../../architecture/Render_Config.h" +#include "../../architecture/Update_Completion.h" +#include "../../architecture/global.h" +#include "../../base/Geometry.h" +#include "../../base/Object_Semantics.h" +#include "../../event/Event.h" +#include "../../event/Key_Event.h" +#include "../../event/Pointer_Event.h" +#include "../../event/Resize_Event.h" +#include "../../event/Wheel_Event.h" +#include "../../render/Canvas.h" +#include "Low_Latency_Roles.h" #include "Triple_Buffer.h" -#include "Update_Completion.h" -#include "global.h" #include #include #include +#include #include #include #include @@ -153,7 +155,7 @@ struct Render_Data { state_control.unmark_use(lease); } void clear_state_buffers() { - state_control.close(); + state_control.close_and_wait(); for (std::size_t i = 0; i < state_buffers.size(); ++i) { state_buffer_owners[i].reset(); state_buffers[i] = nullptr; @@ -181,12 +183,25 @@ struct Render_Data { void mark_input_dirty(Triple_Buffer_Lease lease, bool notify_render = true); void set_input_completion(Triple_Buffer_Lease lease, std::shared_ptr state); void drain_committed_update_states(std::pmr::vector>& output); + void initialize_runtime_buffers( + Render_Object_Owner edit_state, + Render_Object_Owner ready_state, + Render_Object_Owner render_state, + Render_Object_Owner edit_input, + Render_Object_Owner ready_input, + Render_Object_Owner render_input); void clear_input_role(std::uint8_t role); void clear_render_input() { clear_input_role(Input_Render); } + Triple_Buffer_Lease try_mark_render_state() { + return try_mark_state_role(State_Render); + } + Triple_Buffer_Lease try_mark_edit_state() { + return try_mark_state_role(State_Edit); + } void clear_input_buffers() { - input_control.close(); + input_control.close_and_wait(); cancel_pending_completions(); for (std::size_t i = 0; i < input_buffers.size(); ++i) { input_buffer_owners[i].reset(); @@ -210,8 +225,8 @@ public: clear_state_buffers(); clear_input_buffers(); } - virtual void draw(Canvas& canvas, const Plot_Render_Snapshot& snapshot) {} - virtual void prepare_data(const Plot_Render_Snapshot& snapshot) { + virtual void draw(Canvas& canvas, const Render_Frame_Snapshot& snapshot) {} + virtual void prepare_data(const Render_Frame_Snapshot& snapshot) { sync_state_pipeline(); } virtual Rect pixel_bounds(const Size& plot_size) { diff --git a/Core/architecture/Render_Lease.h b/Core/flow/low_latency/Render_Lease.h similarity index 96% rename from Core/architecture/Render_Lease.h rename to Core/flow/low_latency/Render_Lease.h index f62e7f1..49ab057 100644 --- a/Core/architecture/Render_Lease.h +++ b/Core/flow/low_latency/Render_Lease.h @@ -1,7 +1,8 @@ #pragma once +#include "../../architecture/Buffer_Role.h" +#include "../../architecture/Renderable.h" +#include "Low_Latency_Roles.h" #include -#include "Buffer_Role.h" -#include "Renderable.h" namespace renderive { template struct Typed_Render_Data_Base : Base { @@ -123,6 +124,8 @@ struct Renderable_Binding { return make_render_object(); } static Render_Object_Owner create_input_data() { + if constexpr (std::is_same_v) + return {}; return make_render_object(); } }; @@ -207,6 +210,10 @@ struct Render_Input_Lease : Immovable { if (record.result == Triple_Buffer_Result::Success) { lease = record.lease; input = reinterpret_cast(data->input_data_by_index(lease.index)); + if (!input) { + data->input_control.unmark_use(lease); + lease = {}; + } } } ~Render_Input_Lease() { diff --git a/Core/architecture/Triple_Buffer.cpp b/Core/flow/low_latency/Triple_Buffer.cpp similarity index 100% rename from Core/architecture/Triple_Buffer.cpp rename to Core/flow/low_latency/Triple_Buffer.cpp diff --git a/Core/architecture/Triple_Buffer.h b/Core/flow/low_latency/Triple_Buffer.h similarity index 96% rename from Core/architecture/Triple_Buffer.h rename to Core/flow/low_latency/Triple_Buffer.h index 3078cd7..818d785 100644 --- a/Core/architecture/Triple_Buffer.h +++ b/Core/flow/low_latency/Triple_Buffer.h @@ -61,15 +61,31 @@ public: void close() { closed.store(true, std::memory_order_release); - for (auto& item : buffer_busy) { - item.busy.store(false, std::memory_order_release); - item.busy.notify_all(); - } wake_generation.fetch_add(1, std::memory_order_acq_rel); wake_generation.notify_all(); slot_state.notify_all(); } + void wait_until_idle() { + for (;;) { + bool any_busy = false; + for (auto& item : buffer_busy) { + if (!item.busy.load(std::memory_order_acquire)) + continue; + any_busy = true; + item.busy.wait(true, std::memory_order_acquire); + break; + } + if (!any_busy) + return; + } + } + + void close_and_wait() { + close(); + wait_until_idle(); + } + Triple_Buffer_View read_view() const { return decode_order(slot_state.load(std::memory_order_acquire)); } diff --git a/Core/plot/Explicit_Refresh_Strategy.cpp b/Core/plot/Explicit_Refresh_Strategy.cpp deleted file mode 100644 index 2a3c8af..0000000 --- a/Core/plot/Explicit_Refresh_Strategy.cpp +++ /dev/null @@ -1,25 +0,0 @@ -#include "Explicit_Refresh_Strategy.h" - -namespace renderive { - -void Explicit_Refresh_Strategy::request_render() { - manual_request_pending.store(true, std::memory_order_release); -} - -Refresh_Action Explicit_Refresh_Strategy::on_trigger( - Refresh_Trigger, - const Refresh_Runtime_Snapshot& runtime, - Frame_Lifecycle_Record*) { - Refresh_Action action; - if (!manual_request_pending.load(std::memory_order_acquire)) - return action; - if (!runtime.enabled || runtime.destroying || runtime.render_job_active) - return action; - manual_request_pending.store(false, std::memory_order_release); - action.try_render = true; - if (runtime.middle_has_new_frame && !runtime.paint_active) - action.request_paint = true; - return action; -} - -} // namespace renderive diff --git a/Core/plot/Explicit_Refresh_Strategy.h b/Core/plot/Explicit_Refresh_Strategy.h deleted file mode 100644 index 9ea8052..0000000 --- a/Core/plot/Explicit_Refresh_Strategy.h +++ /dev/null @@ -1,20 +0,0 @@ -#pragma once -#include "Refresh_Strategy.h" -#include - -namespace renderive { - -class Explicit_Refresh_Strategy final : public Refresh_Strategy { -public: - void request_render(); - -private: - [[nodiscard]] Refresh_Action on_trigger( - Refresh_Trigger trigger, - const Refresh_Runtime_Snapshot& runtime, - Frame_Lifecycle_Record* frame) override; - - std::atomic_bool manual_request_pending{false}; -}; - -} // namespace renderive diff --git a/Core/plot/Latency_Eager_Refresh_Strategy.cpp b/Core/plot/Latency_Eager_Refresh_Strategy.cpp deleted file mode 100644 index 5683a16..0000000 --- a/Core/plot/Latency_Eager_Refresh_Strategy.cpp +++ /dev/null @@ -1,90 +0,0 @@ -#include "Latency_Eager_Refresh_Strategy.h" -#include "../architecture/Render_Time.h" - -namespace renderive { - -struct Latency_Eager_Refresh_Strategy::Private { - std::atomic max_render_fps{60.0}; - Frame_Feedback_Controller feedback; -}; - -static constexpr double k_default_max_fps = 60.0; - -Latency_Eager_Refresh_Strategy::Latency_Eager_Refresh_Strategy() - : d(std::make_unique()) {} - -Latency_Eager_Refresh_Strategy::~Latency_Eager_Refresh_Strategy() = default; - -double Latency_Eager_Refresh_Strategy::max_render_fps() const { - return d->max_render_fps.load(std::memory_order_acquire); -} - -void Latency_Eager_Refresh_Strategy::set_max_render_fps(double fps) { - if (fps <= 0.0) - fps = k_default_max_fps; - d->max_render_fps.store(fps, std::memory_order_release); -} - -Refresh_Control_Snapshot Latency_Eager_Refresh_Strategy::feedback_state() const { - return d->feedback.state(); -} - -Refresh_Action Latency_Eager_Refresh_Strategy::on_trigger( - Refresh_Trigger trigger, - const Refresh_Runtime_Snapshot& runtime, - Frame_Lifecycle_Record* frame) { - d->feedback.set_max_render_fps(max_render_fps()); - - if (trigger == Refresh_Trigger::Started) - d->feedback.reset(); - if (trigger == Refresh_Trigger::Input_Dirty) - d->feedback.on_input_changed(runtime.now_ns); - if (frame) { - switch (trigger) { - case Refresh_Trigger::Render_Finished: - d->feedback.on_render_completed(*frame); - break; - case Refresh_Trigger::Frame_Returned: - if (frame->outcome == Frame_Outcome::Superseded) - d->feedback.on_frame_superseded(*frame); - else - d->feedback.on_frame_presented(*frame); - break; - default: - break; - } - } - - Refresh_Action action; - if (!runtime.enabled || runtime.destroying || trigger == Refresh_Trigger::Paused) - return action; - - if (runtime.middle_has_new_frame && !runtime.paint_active) - action.request_paint = true; - - if (!runtime.dirty || runtime.render_job_active) - return action; - - Refresh_Control_Snapshot control = d->feedback.state(); - switch (control.source) { - case Refresh_Limit_Source::Paint: - if (!runtime.middle_has_new_frame) - action.try_render = true; - break; - case Refresh_Limit_Source::User: { - std::uint64_t next_ns = d->feedback.next_render_time_ns(); - if (next_ns && !d->feedback.rate_limit_ready(runtime.now_ns)) - action.render_deadline_ns = next_ns; - else - action.try_render = true; - break; - } - case Refresh_Limit_Source::Render: - case Refresh_Limit_Source::Unknown: - action.try_render = true; - break; - } - return action; -} - -} // namespace renderive diff --git a/Core/plot/Latency_Eager_Refresh_Strategy.h b/Core/plot/Latency_Eager_Refresh_Strategy.h deleted file mode 100644 index 56bf0f6..0000000 --- a/Core/plot/Latency_Eager_Refresh_Strategy.h +++ /dev/null @@ -1,28 +0,0 @@ -#pragma once -#include "Refresh_Strategy.h" -#include -#include -#include - -namespace renderive { - -class Latency_Eager_Refresh_Strategy final : public Refresh_Strategy { -public: - Latency_Eager_Refresh_Strategy(); - ~Latency_Eager_Refresh_Strategy() override; - - [[nodiscard]] double max_render_fps() const; - void set_max_render_fps(double fps); - [[nodiscard]] Refresh_Control_Snapshot feedback_state() const; - -private: - [[nodiscard]] Refresh_Action on_trigger( - Refresh_Trigger trigger, - const Refresh_Runtime_Snapshot& runtime, - Frame_Lifecycle_Record* frame) override; - - struct Private; - std::unique_ptr d; -}; - -} // namespace renderive diff --git a/Core/plot/Plot_Core.cpp b/Core/plot/Plot_Core.cpp index 9fc2248..73e2a43 100644 --- a/Core/plot/Plot_Core.cpp +++ b/Core/plot/Plot_Core.cpp @@ -1,5 +1,4 @@ #include "Plot_Core.h" -#include "Plot_Frame_Pipeline.h" #include "../architecture/Renderable.h" #include "../architecture/Render_Time.h" #include "../architecture/Update_Completion.h" @@ -13,37 +12,26 @@ class Plot_Core_Private; struct Frame_Render_Job { std::shared_ptr owner; std::uint64_t generation{}; - Plot_Frame_Lease frame_lease; - Plot_Frame* buffer{}; + Render_Frame_Surface frame_surface; + Render_Output* buffer{}; std::shared_ptr lifecycle; - Plot_Render_Snapshot render_snapshot; - Renderable_Tree_Snapshot renderables; + Render_Frame_Snapshot render_snapshot; + Renderable_Frame_Tree renderables; std::shared_ptr worker_stat; }; -class Plot_Core_Private final : public std::enable_shared_from_this { +class Plot_Core_Private final : public std::enable_shared_from_this, public Frame_Flow_Host { public: - explicit Plot_Core_Private(std::unique_ptr s) - : strategy(std::move(s)) {} + explicit Plot_Core_Private(std::unique_ptr selected_flow) + : flow(std::move(selected_flow)) {} ~Plot_Core_Private() = default; - bool render_enabled() const { - return rendering.load(std::memory_order_acquire); - } bool destroying() const { return !context || context->destroying.load(std::memory_order_acquire); } - Refresh_Runtime_Snapshot refresh_runtime_snapshot(std::uint64_t now_ns = steady_now_ns()) const { - return { - render_enabled(), - destroying(), - pipeline.has_unrendered_edit(), - pipeline.render_job_state != Frame_Job_State::Idle, - pipeline.middle_frame_ready(), - pipeline.painting_frame_in_use(), - now_ns - }; + Frame_Flow_Runtime_Snapshot runtime_snapshot(std::uint64_t now_ns) const override { + return flow ? flow->runtime_snapshot(now_ns, destroying()) : Frame_Flow_Runtime_Snapshot{destroying(), false, false, false, false, now_ns}; } bool submit_render_once() { - if (!rendering.load(std::memory_order_acquire) || destroying()) + if (destroying()) return false; if (!root) return false; @@ -53,18 +41,20 @@ public: }; if (size.empty()) return false; - auto frame_lease = pipeline.try_acquire_render_frame(); - if (!frame_lease) + auto frame_surface = flow ? flow->begin_render_frame() : Render_Frame_Surface{}; + if (!frame_surface) return false; - Plot_Frame* buffer = frame_lease.frame; + Render_Output* buffer = frame_surface.output; cancel_update_states(buffer->presented_update_states); + if (flow) + flow->drain_frame_update_states(buffer->presented_update_states); std::uint64_t render_role_enter_ns = buffer->metadata ? buffer->metadata->render_role_enter_ns : 0; buffer->metadata = std::make_shared(); auto frame_record = buffer->metadata; Frame_Lifecycle_Record& frame = *frame_record; std::uint64_t submit_time = steady_now_ns(); frame.frame_id = ++next_frame_id; - frame.input_version = pipeline.edit_version.load(std::memory_order_acquire); + frame.input_version = frame_surface.input_version; frame.render_request_ns = submit_time; frame.render_role_enter_ns = render_role_enter_ns ? render_role_enter_ns : submit_time; frame.core_data_time_ns = latest_input_time_ns.load(std::memory_order_acquire); @@ -72,15 +62,12 @@ public: frame.pixel_width = static_cast(size.width); frame.pixel_height = static_cast(size.height); frame.viewport_version = context->viewport_version.load(std::memory_order_acquire); - frame.wait_21_ns = submit_time > frame.render_role_enter_ns ? submit_time - frame.render_role_enter_ns : 0; + frame.render_queue_wait_ns = submit_time > frame.render_role_enter_ns ? submit_time - frame.render_role_enter_ns : 0; frame.render_begin_ns = submit_time; - std::uint64_t previous_render_state_version = pipeline.rendered_edit_version; - pipeline.render_job_state = Frame_Job_State::Running; - pipeline.rendered_edit_version = frame.input_version; auto job = std::make_shared(); job->owner = shared_from_this(); job->generation = generation.load(std::memory_order_acquire); - job->frame_lease = std::move(frame_lease); + job->frame_surface = std::move(frame_surface); job->buffer = buffer; job->lifecycle = frame_record; job->worker_stat = std::make_shared(); @@ -127,11 +114,10 @@ public: return true; frame.outcome = Frame_Outcome::Worker_Budget_Dropped; frame.worker.rejected_task_count++; - job->frame_lease.reset(); - pipeline.rendered_edit_version = previous_render_state_version; - pipeline.render_job_state = Frame_Job_State::Idle; + if (flow) + flow->discard_render_frame(std::move(job->frame_surface)); collect_frame(frame_record); - trigger_refresh_strategy(Refresh_Trigger::Render_Finished, &frame); + notify_render_finished(frame); return false; } static bool frame_job_valid(const std::shared_ptr& job) { @@ -169,7 +155,6 @@ public: if (!frame_job_valid(job) || !job->lifecycle->render_end_ns) return; Frame_Lifecycle_Record& frame = *job->lifecycle; - job->buffer->version.store(frame.frame_id, std::memory_order_release); for (const auto& update_state : job->buffer->presented_update_states) { if (update_state) update_state->complete_until(Update_Stage::Rendered); @@ -191,25 +176,23 @@ public: bool valid_generation = job->generation == generation.load(std::memory_order_acquire); bool valid_context = !destroying(); if (!valid_generation || !valid_context || !frame.render_end_ns) { - job->frame_lease.reset(); + if (flow) + flow->cancel_render_frame(std::move(job->frame_surface)); frame.outcome = Frame_Outcome::Cancelled; - pipeline.render_job_state = Frame_Job_State::Idle; supersede_update_states(job->buffer->presented_update_states); - trigger_refresh_strategy(Refresh_Trigger::Render_Finished, &frame); + notify_render_finished(frame); return; } - auto publish_result = pipeline.try_publish_rendered_frame(std::move(job->frame_lease), steady_now_ns()); + auto publish_result = flow ? flow->publish_rendered_frame(std::move(job->frame_surface), steady_now_ns()) : Render_Surface_Publish_Result{}; if (publish_result.superseded_frame_record) { - trigger_refresh_strategy(Refresh_Trigger::Frame_Returned, publish_result.superseded_frame_record.get()); - collect_frame(publish_result.superseded_frame_record); + notify_frame_returned(publish_result.superseded_frame_record); } if (publish_result.dropped_frame_record) { collect_frame(publish_result.dropped_frame_record); } - pipeline.render_job_state = Frame_Job_State::Idle; if (publish_result.request_present) request_present(publish_result.dirty_rect); - trigger_refresh_strategy(Refresh_Trigger::Render_Finished, &frame); + notify_render_finished(frame); } void post_scheduler_action(Task task) { if (!task) @@ -227,85 +210,72 @@ public: } scheduler.post(std::move(action)); } - void trigger_refresh_strategy(Refresh_Trigger trigger, Frame_Lifecycle_Record* frame = nullptr) { - if (!strategy) - return; - std::uint64_t now_ns = steady_now_ns(); - Refresh_Runtime_Snapshot runtime = refresh_runtime_snapshot(now_ns); - Refresh_Action action = strategy->on_trigger(trigger, runtime, frame); - apply_refresh_action(action, runtime); + bool try_render_once() override { + cancel_render_deadline(); + return submit_render_once(); } - void notify_input_changed(std::uint64_t now_ns) { - if (!strategy) - return; - Refresh_Runtime_Snapshot runtime = refresh_runtime_snapshot(now_ns); - Refresh_Action action = strategy->on_trigger(Refresh_Trigger::Input_Dirty, runtime, nullptr); - apply_refresh_action(action, runtime); + void request_paint_from_middle() override { + auto update_result = flow ? flow->request_present(steady_now_ns()) : Render_Surface_Publish_Result{}; + if (update_result.request_present) + request_present(update_result.dirty_rect); + } + void notify_model_dirty(std::uint64_t now_ns) { + if (flow) + flow->notify_model_dirty(now_ns); + } + void notify_viewport_changed(Size viewport, std::uint64_t now_ns) { + if (flow) + flow->notify_viewport_changed(viewport, now_ns); + } + void notify_render_finished(Frame_Lifecycle_Record& frame) { + if (flow) + flow->notify_render_finished(frame, steady_now_ns()); } void notify_started() { - if (!strategy_started) { - strategy_started = true; - trigger_refresh_strategy(Refresh_Trigger::Started); - } - else { - trigger_refresh_strategy(Refresh_Trigger::Input_Dirty); - } + if (flow) + flow->activate_view(steady_now_ns()); } void notify_paused() { - invalidate_refresh_deadline(); - if (!strategy_started) - return; - strategy_started = false; - trigger_refresh_strategy(Refresh_Trigger::Paused); + if (flow) + flow->deactivate_view(); } void notify_frame_returned(std::shared_ptr frame) { if (!frame) return; - trigger_refresh_strategy(Refresh_Trigger::Frame_Returned, frame.get()); + if (flow) + flow->notify_frame_returned(*frame, steady_now_ns()); collect_frame(frame); } - void apply_refresh_action(Refresh_Action action, const Refresh_Runtime_Snapshot& runtime) { - if (action.request_paint) { - auto update_result = pipeline.request_present(steady_now_ns()); - if (update_result.request_present) - request_present(update_result.dirty_rect); - } - if (action.try_render) { - invalidate_refresh_deadline(); - submit_render_once(); - return; - } - if (action.render_deadline_ns) { - schedule_refresh_deadline(action.render_deadline_ns); - return; - } - if (!runtime.enabled || runtime.destroying || !runtime.dirty) - invalidate_refresh_deadline(); + void notify_paint_finished() { + if (flow) + flow->notify_paint_finished(steady_now_ns()); } - void schedule_refresh_deadline(std::uint64_t deadline_ns) { + void schedule_render_deadline(std::uint64_t deadline_ns) override { if (deadline_ns <= steady_now_ns()) { - invalidate_refresh_deadline(); - trigger_refresh_strategy(Refresh_Trigger::Timer_Fired); + cancel_render_deadline(); + if (flow) + flow->notify_timer_fired(steady_now_ns()); return; } - if (scheduled_refresh_deadline_ns && scheduled_refresh_deadline_ns <= deadline_ns) + if (scheduled_flow_deadline_ns && scheduled_flow_deadline_ns <= deadline_ns) return; - scheduled_refresh_deadline_ns = deadline_ns; - std::uint64_t generation = ++refresh_deadline_generation; + scheduled_flow_deadline_ns = deadline_ns; + std::uint64_t generation = ++flow_deadline_generation; auto guard = context; auto self = shared_from_this(); Global::instance()->render_scheduler().post_at(deadline_ns, [guard, self, generation, deadline_ns]() { if (!guard || guard->destroying.load(std::memory_order_acquire)) return; - if (self->refresh_deadline_generation != generation || self->scheduled_refresh_deadline_ns != deadline_ns) + if (self->flow_deadline_generation != generation || self->scheduled_flow_deadline_ns != deadline_ns) return; - self->scheduled_refresh_deadline_ns = 0; - self->trigger_refresh_strategy(Refresh_Trigger::Timer_Fired); + self->scheduled_flow_deadline_ns = 0; + if (self->flow) + self->flow->notify_timer_fired(steady_now_ns()); }); } - void invalidate_refresh_deadline() { - scheduled_refresh_deadline_ns = 0; - ++refresh_deadline_generation; + void cancel_render_deadline() override { + scheduled_flow_deadline_ns = 0; + ++flow_deadline_generation; } void dispatch_event(const Event& event) { if (!root) @@ -346,8 +316,8 @@ public: append_renderable_snapshot(result, root); return result; } - Renderable_Tree_Snapshot renderable_tree_snapshot() const { - Renderable_Tree_Snapshot result; + Renderable_Frame_Tree renderable_tree_snapshot() const { + Renderable_Frame_Tree result; if (root) root->append_tree_snapshot(result); return result; @@ -413,18 +383,15 @@ public: Pixel p = context->background_rgba.load(std::memory_order_acquire); return premultiplied_to_color(p); } - std::unique_ptr strategy; + std::unique_ptr flow; std::shared_ptr context = std::make_shared(); std::shared_ptr root; std::vector> hover_targets; - Plot_Frame_Pipeline pipeline; std::weak_ptr sink; - std::atomic_bool rendering{false}; std::atomic latest_input_time_ns{0}; std::atomic pending_input_count{0}; - bool strategy_started{}; - std::uint64_t refresh_deadline_generation{}; - std::uint64_t scheduled_refresh_deadline_ns{}; + std::uint64_t flow_deadline_generation{}; + std::uint64_t scheduled_flow_deadline_ns{}; std::uint64_t next_frame_id{}; std::uint64_t previous_frame_time_ns{}; Plot_Execution_Id plot_execution_id{}; @@ -436,17 +403,22 @@ Plot_Execution_Id next_plot_execution_id() { return next_id.fetch_add(1, std::memory_order_acq_rel); } } // namespace -Plot_Core::Plot_Core(std::unique_ptr strategy) - : d(std::make_shared(std::move(strategy))) { +Plot_Core::Plot_Core(std::unique_ptr flow) + : d(std::make_shared(std::move(flow))) { d->plot_execution_id = next_plot_execution_id(); d->context->owner.store(this, std::memory_order_release); + if (d->flow) + d->flow->attach(*d); Global::instance()->render_scheduler().register_plot(this); } Plot_Core::~Plot_Core() { d->context->destroying.store(true, std::memory_order_release); d->generation.fetch_add(1, std::memory_order_acq_rel); + if (d->flow) + d->flow->shutdown(); Global::instance()->render_scheduler().remove_plot(this); - d->pipeline.cancel_all_update_states(); + if (d->flow) + d->flow->cancel_all_update_states(); d->context->owner.store(nullptr, std::memory_order_release); } void Plot_Core::init() { @@ -479,11 +451,11 @@ void Plot_Core::remove_renderable(const std::shared_ptr& renderable) d->root.reset(); else renderable->detach_from_parent(); - mark_render_state_dirty(); + notify_model_dirty(); } void Plot_Core::set_background_color(Color color) { d->context->background_rgba.store(premultiply(color), std::memory_order_release); - mark_render_state_dirty(); + notify_model_dirty(); } Color Plot_Core::background_color() const { return d->background_color(); @@ -499,6 +471,11 @@ void Plot_Core::set_viewport_size(Size size) { d->context->viewport_version.fetch_add(1, std::memory_order_acq_rel); if (!size.empty()) d->context->first_resize.store(false, std::memory_order_release); + if (previous != size) { + d->post_scheduler_action([data = d, size]() { + data->notify_viewport_changed(size, steady_now_ns()); + }); + } } Size Plot_Core::viewport_size() const { return { @@ -509,78 +486,68 @@ Size Plot_Core::viewport_size() const { void Plot_Core::dispatch_event(const Event& event) { d->dispatch_event(event); } -void Plot_Core::mark_render_state_dirty() { +void Plot_Core::notify_model_dirty() { if (d->destroying()) return; std::uint64_t now_ns = steady_now_ns(); - d->pipeline.advance_edit_version(); + if (d->flow) + d->flow->advance_edit_version(); d->latest_input_time_ns.store(now_ns, std::memory_order_release); d->pending_input_count.fetch_add(1, std::memory_order_relaxed); d->post_scheduler_action([data = d, now_ns]() { - data->notify_input_changed(now_ns); + data->notify_model_dirty(now_ns); }); } -void Plot_Core::request_manual_refresh() { +void Plot_Core::request_explicit_render() { if (d->destroying()) return; d->post_scheduler_action([data = d]() { - data->trigger_refresh_strategy(Refresh_Trigger::Manual_Request); + data->notify_model_dirty(steady_now_ns()); }); } -void Plot_Core::start_render() { +void Plot_Core::activate_view() { if (d->destroying()) return; - d->rendering.store(true, std::memory_order_release); d->post_scheduler_action([data = d]() { data->notify_started(); }); } -void Plot_Core::pause_render() { - d->rendering.store(false, std::memory_order_release); +void Plot_Core::deactivate_view() { d->post_scheduler_action([data = d]() { data->notify_paused(); }); } -bool Plot_Core::is_rendering() const { - return d->rendering.load(std::memory_order_acquire); +bool Plot_Core::view_active() const { + return d->flow && d->flow->view_active(); } void Plot_Core::set_presentation_sink(std::weak_ptr sink) { d->sink = std::move(sink); } -Present_Frame_Lease Plot_Core::begin_present() { - Present_Frame_Lease result; - if (!d->root) +Present_Surface_Lease Plot_Core::begin_present() { + Present_Surface_Lease result; + if (!d->root || !d->flow) return result; - auto consumed = d->pipeline.try_acquire_paint_frame(steady_now_ns()); - if (consumed.returned_frame_record) { - d->post_scheduler_action([data = d, frame = consumed.returned_frame_record]() mutable { + std::shared_ptr returned_frame; + result = d->flow->begin_present(steady_now_ns(), returned_frame); + if (returned_frame) { + d->post_scheduler_action([data = d, frame = std::move(returned_frame)]() mutable { data->notify_frame_returned(frame); }); } - result.is_new_frame = consumed.has_new_frame; - result.update_was_pending = consumed.present_request_was_pending; - if (consumed.lease) - result.lease_.emplace(std::move(consumed.lease)); if (!result.new_frame() && result.request_was_pending()) { d->post_scheduler_action([data = d]() { - data->trigger_refresh_strategy(Refresh_Trigger::Paint_Finished); + data->notify_paint_finished(); }); } return result; } -void Plot_Core::end_present(Present_Frame_Lease&& frame, std::uint64_t paint_begin_ns, std::uint64_t paint_end_ns) { - if (frame.lease_ && frame.lease_->frame) { - auto* rendered_frame = frame.lease_->frame; - if (rendered_frame->metadata) { - rendered_frame->metadata->paint_begin_ns = paint_begin_ns; - rendered_frame->metadata->paint_end_ns = paint_end_ns; - rendered_frame->metadata->wait_23_ns = paint_begin_ns > rendered_frame->metadata->transition_12_ns ? paint_begin_ns - rendered_frame->metadata->transition_12_ns : 0; - } - complete_update_states(rendered_frame->presented_update_states, Update_Stage::Presented); +void Plot_Core::end_present(Present_Surface_Lease&& frame, std::uint64_t paint_begin_ns, std::uint64_t paint_end_ns) { + if (d->flow) { + if (auto returned = d->flow->end_present(std::move(frame), paint_begin_ns, paint_end_ns)) + d->collect_frame(returned); } - frame.lease_.reset(); d->post_scheduler_action([data = d]() mutable { - data->trigger_refresh_strategy(Refresh_Trigger::Paint_Finished); + data->notify_paint_finished(); }); } } // namespace renderive diff --git a/Core/plot/Plot_Core.h b/Core/plot/Plot_Core.h index 01352c7..ac78f4d 100644 --- a/Core/plot/Plot_Core.h +++ b/Core/plot/Plot_Core.h @@ -4,9 +4,9 @@ #include "../base/Color.h" #include "../base/Geometry.h" #include "../event/Event.h" -#include "Present_Frame_Lease.h" +#include "../flow/Frame_Flow.h" +#include "../flow/Present_Surface_Lease.h" #include "Presentation_Sink.h" -#include "Refresh_Strategy.h" #include #include @@ -16,7 +16,7 @@ class Plot_Core_Private; class Plot_Core { public: - explicit Plot_Core(std::unique_ptr strategy); + explicit Plot_Core(std::unique_ptr flow); ~Plot_Core(); void init(); @@ -35,17 +35,17 @@ public: [[nodiscard]] Size viewport_size() const; void dispatch_event(const Event& event); - void mark_render_state_dirty(); - void request_manual_refresh(); + void notify_model_dirty(); + void request_explicit_render(); - void start_render(); - void pause_render(); - [[nodiscard]] bool is_rendering() const; + void activate_view(); + void deactivate_view(); + [[nodiscard]] bool view_active() const; void set_presentation_sink(std::weak_ptr sink); - Present_Frame_Lease begin_present(); - void end_present(Present_Frame_Lease&& frame, std::uint64_t paint_begin_ns, std::uint64_t paint_end_ns); + Present_Surface_Lease begin_present(); + void end_present(Present_Surface_Lease&& frame, std::uint64_t paint_begin_ns, std::uint64_t paint_end_ns); private: std::shared_ptr d; diff --git a/Core/plot/Present_Frame_Lease.cpp b/Core/plot/Present_Frame_Lease.cpp deleted file mode 100644 index 96d14d4..0000000 --- a/Core/plot/Present_Frame_Lease.cpp +++ /dev/null @@ -1,21 +0,0 @@ -#include "Present_Frame_Lease.h" -namespace renderive { -Present_Frame_Lease::Present_Frame_Lease() = default; -Present_Frame_Lease::Present_Frame_Lease(Present_Frame_Lease&&) noexcept = default; -Present_Frame_Lease& Present_Frame_Lease::operator=(Present_Frame_Lease&&) noexcept = default; -Present_Frame_Lease::~Present_Frame_Lease() = default; -Present_Frame_Lease::operator bool() const { - return lease_ && lease_->operator bool(); -} -bool Present_Frame_Lease::new_frame() const { - return is_new_frame; -} -bool Present_Frame_Lease::request_was_pending() const { - return update_was_pending; -} -Image_View Present_Frame_Lease::image() const { - if (!lease_ || !lease_->frame) - return {}; - return lease_->frame->image.view(); -} -} // namespace renderive diff --git a/Core/plot/Present_Frame_Lease.h b/Core/plot/Present_Frame_Lease.h deleted file mode 100644 index 66da5c3..0000000 --- a/Core/plot/Present_Frame_Lease.h +++ /dev/null @@ -1,23 +0,0 @@ -#pragma once -#include "Plot_Frame_Pipeline.h" -#include "../render/Image_View.h" -#include -#include -namespace renderive { -class Present_Frame_Lease { -public: - Present_Frame_Lease(); - Present_Frame_Lease(Present_Frame_Lease&&) noexcept; - Present_Frame_Lease& operator=(Present_Frame_Lease&&) noexcept; - ~Present_Frame_Lease(); - [[nodiscard]] explicit operator bool() const; - [[nodiscard]] bool new_frame() const; - [[nodiscard]] bool request_was_pending() const; - [[nodiscard]] Image_View image() const; -private: - friend class Plot_Core; - std::optional lease_; - bool is_new_frame{}; - bool update_was_pending{}; -}; -} // namespace renderive diff --git a/Core/plot/Refresh_Strategy.h b/Core/plot/Refresh_Strategy.h deleted file mode 100644 index 52140e4..0000000 --- a/Core/plot/Refresh_Strategy.h +++ /dev/null @@ -1,43 +0,0 @@ -#pragma once -#include "../architecture/Frame_Scheduler.h" -#include - -namespace renderive { - -enum class Refresh_Trigger : std::uint8_t { - Started, - Input_Dirty, - Render_Finished, - Paint_Finished, - Frame_Returned, - Timer_Fired, - Manual_Request, - Paused -}; - -struct Refresh_Runtime_Snapshot { - bool enabled{}; - bool destroying{}; - bool dirty{}; - bool render_job_active{}; - bool middle_has_new_frame{}; - bool paint_active{}; - std::uint64_t now_ns{}; -}; - -struct Refresh_Action { - bool try_render{}; - bool request_paint{}; - std::uint64_t render_deadline_ns{}; -}; - -class Refresh_Strategy { -public: - virtual ~Refresh_Strategy() = default; - [[nodiscard]] virtual Refresh_Action on_trigger( - Refresh_Trigger trigger, - const Refresh_Runtime_Snapshot& runtime, - Frame_Lifecycle_Record* frame) = 0; -}; - -} // namespace renderive diff --git a/Core/plottable/Afterglow_p.h b/Core/plottable/Afterglow_p.h index c8d3d4d..d11015a 100644 --- a/Core/plottable/Afterglow_p.h +++ b/Core/plottable/Afterglow_p.h @@ -64,12 +64,12 @@ struct Afterglow_Private : Typed_Render_Data(image.width()), static_cast(image.height())}); canvas.restore(); } - void draw(Canvas& canvas, const Plot_Render_Snapshot&) override { + void draw(Canvas& canvas, const Render_Frame_Snapshot&) override { Afterglow_Render_State* s = render_state(); auto color_scale = s->color_scale.snapshot(); draw_image(canvas, s, *color_scale); } - void prepare_data(const Plot_Render_Snapshot&) override { + void prepare_data(const Render_Frame_Snapshot&) override { sync_state_pipeline(); Afterglow_Render_State* s = render_state(); if (s->frequency_bin_count <= 0 || s->power_bin_count <= 0) @@ -120,7 +120,7 @@ void Afterglow::Builder_T::set(Afterglow* ret) { ret->init(parent); std::shared_ptr owner = ret->shared_from_this(); Afterglow_Private* pd = ret->d(); - Afterglow_Render_State* sc = reinterpret_cast(pd->state(State_Edit)); + Afterglow_Render_State* sc = reinterpret_cast(pd->edit_state()); PROP_RT(std::weak_ptr, frequency_axis) PROP_RT(std::weak_ptr, power_axis) PROP_RT(Range, frequency_range) diff --git a/Core/plottable/Constellation_Diagram.cpp b/Core/plottable/Constellation_Diagram.cpp index 873ecc1..c94f164 100644 --- a/Core/plottable/Constellation_Diagram.cpp +++ b/Core/plottable/Constellation_Diagram.cpp @@ -84,7 +84,7 @@ std::shared_ptr Constellation_Diagram::Builder::build() { auto ret = renderive::make_shared(); ret->init(parent); Constellation_Diagram_Private* pd = ret->d(); - Constellation_Diagram_State* sc = reinterpret_cast(pd->state(State_Edit)); + Constellation_Diagram_State* sc = reinterpret_cast(pd->edit_state()); PROP_R(std::weak_ptr, i_axis) PROP_R(std::weak_ptr, q_axis) PROP_R(Range, i_range) @@ -102,7 +102,7 @@ std::shared_ptr Constellation_Diagram::Builder::build() { return ret; } void Constellation_Diagram::fit_square_to_axes() { - Constellation_Diagram_State* s = reinterpret_cast(d()->state(State_Edit)); + Constellation_Diagram_State* s = reinterpret_cast(d()->edit_state()); auto i_axis = s->i_axis.lock(); auto q_axis = s->q_axis.lock(); if (!i_axis || !q_axis) diff --git a/Core/plottable/Constellation_Diagram_p.h b/Core/plottable/Constellation_Diagram_p.h index f9a2697..0934f56 100644 --- a/Core/plottable/Constellation_Diagram_p.h +++ b/Core/plottable/Constellation_Diagram_p.h @@ -5,7 +5,7 @@ #include #include #include -#include "../architecture/Render_Lease.h" +#include "../flow/low_latency/Render_Lease.h" #include "../Axis/Axis_p.h" #include "../architecture/Bounded_Input_Buffer.h" #include "../base/Frame_Memory.h" @@ -75,7 +75,7 @@ struct Constellation_Diagram_Private : Typed_Render_Datai_axis.lock(); auto q_axis = s->q_axis.lock(); diff --git a/Core/plottable/Frequency_Trace.cpp b/Core/plottable/Frequency_Trace.cpp index d422d5a..5ee4674 100644 --- a/Core/plottable/Frequency_Trace.cpp +++ b/Core/plottable/Frequency_Trace.cpp @@ -30,7 +30,7 @@ std::shared_ptr Frequency_Trace::Builder::build() { auto ret = renderive::make_shared(); ret->init(parent); Frequency_Trace_Private* pd = ret->d(); - Frequency_Trace_Render_State* sc = reinterpret_cast(pd->state(State_Edit)); + Frequency_Trace_Render_State* sc = reinterpret_cast(pd->edit_state()); PROP_R(std::weak_ptr, time_axis) PROP_R(std::weak_ptr, value_axis) sc->pen = Pen{.color = color, .width = 1.0}; diff --git a/Core/plottable/Frequency_Trace_p.h b/Core/plottable/Frequency_Trace_p.h index 4ed3f38..628a499 100644 --- a/Core/plottable/Frequency_Trace_p.h +++ b/Core/plottable/Frequency_Trace_p.h @@ -61,7 +61,7 @@ struct Frequency_Trace_Private : Typed_Render_Datatime_axis.lock(); auto value_axis = s->value_axis.lock(); diff --git a/Core/plottable/Hover_Tooltip.h b/Core/plottable/Hover_Tooltip.h index f8fd3f3..0b3b9ca 100644 --- a/Core/plottable/Hover_Tooltip.h +++ b/Core/plottable/Hover_Tooltip.h @@ -32,17 +32,17 @@ struct LIB_DECL Hover_Tooltip_State { bool can_show_hover(Renderable* able) const; }; struct LIB_DECL Hover_Tooltip_Provider { - struct Hover_State_Edit { - Hover_State_Edit() = default; - Hover_State_Edit(Hover_Tooltip_Provider* owner, Hover_Tooltip_State* state, std::function on_finish) + struct Hover_Edit_Guard { + Hover_Edit_Guard() = default; + Hover_Edit_Guard(Hover_Tooltip_Provider* owner, Hover_Tooltip_State* state, std::function on_finish) : owner(owner), state(state), on_finish(std::move(on_finish)) {} - Hover_State_Edit(const Hover_State_Edit&) = delete; - Hover_State_Edit& operator=(const Hover_State_Edit&) = delete; - Hover_State_Edit(Hover_State_Edit&& other) noexcept + Hover_Edit_Guard(const Hover_Edit_Guard&) = delete; + Hover_Edit_Guard& operator=(const Hover_Edit_Guard&) = delete; + Hover_Edit_Guard(Hover_Edit_Guard&& other) noexcept : owner(std::exchange(other.owner, nullptr)), state(std::exchange(other.state, nullptr)), on_finish(std::move(other.on_finish)) {} - Hover_State_Edit& operator=(Hover_State_Edit&& other) noexcept { + Hover_Edit_Guard& operator=(Hover_Edit_Guard&& other) noexcept { if (this == &other) return *this; finish(); @@ -51,7 +51,7 @@ struct LIB_DECL Hover_Tooltip_Provider { on_finish = std::move(other.on_finish); return *this; } - ~Hover_State_Edit() { + ~Hover_Edit_Guard() { finish(); } Hover_Tooltip_State* operator->() const { @@ -125,13 +125,13 @@ struct LIB_DECL Hover_Tooltip_Provider { edit->hover_info_right = right; edit->hover_info_bottom = bottom; } - Hover_State_Edit begin_hover_edit() { + Hover_Edit_Guard begin_hover_edit() { return begin_hover_edit_impl(); } private: virtual Hover_Tooltip_State* hover_edit_state() = 0; virtual Hover_Tooltip_State* hover_render_state() = 0; - virtual Hover_State_Edit begin_hover_edit_impl() = 0; + virtual Hover_Edit_Guard begin_hover_edit_impl() = 0; }; template struct LIB_DECL Hover_Tooltip_Mixin : Hover_Tooltip_Provider { @@ -141,21 +141,20 @@ private: } Hover_Tooltip_State* hover_edit_state() override { auto t = that(); - return dynamic_cast(t->d_ptr->state(State_Edit)); + return dynamic_cast(t->d_ptr->edit_state()); } Hover_Tooltip_State* hover_render_state() override { auto t = that(); - return dynamic_cast(t->d_ptr->state(State_Render)); + return dynamic_cast(t->d_ptr->render_state()); } - Hover_State_Edit begin_hover_edit_impl() override { + Hover_Edit_Guard begin_hover_edit_impl() override { auto t = that(); - auto record = t->d_ptr->state_control.try_acquire_role(State_Edit); - if (record.result != Triple_Buffer_Result::Success) + auto lease = t->d_ptr->try_mark_edit_state(); + if (!lease) return {}; - auto lease = record.lease; auto* data = t->d_ptr; auto* state = dynamic_cast(data->state_by_index(lease.index)); - return Hover_State_Edit(this, state, [data, lease]() mutable { + return Hover_Edit_Guard(this, state, [data, lease]() mutable { ++data->state_by_index(lease.index)->version; if (data->q_ptr) data->q_ptr->mark_render_dirty(); diff --git a/Core/plottable/Interpolation_p.h b/Core/plottable/Interpolation_p.h index a3b8c2d..2ec7c8a 100644 --- a/Core/plottable/Interpolation_p.h +++ b/Core/plottable/Interpolation_p.h @@ -1,6 +1,7 @@ #pragma once #include #include +#include #include #include #include "../Axis/Abs_Axis_p.h" @@ -91,18 +92,31 @@ double interpolate_sample_value(double index, int count, Line_Interpolation_Mode return left_value; } template -std::pmr::vector build_sample_line_points(const Axis_Mapping_2D& mapping, const Range& data_range, int total_count, int available_count, bool visible_range_only, Value_At value_at, std::pmr::memory_resource* resource = frame_memory_resource()) { +std::pmr::vector build_sample_line_points(const Axis_Mapping_2D& mapping, const Range& data_range, int total_count, int available_count, bool visible_range_only, Line_Interpolation_Mode mode, Value_At value_at, std::pmr::memory_resource* resource = frame_memory_resource()) { Visible_Sample_Window window = compute_visible_sample_window(mapping.domain, data_range, total_count, available_count, visible_range_only); if (!window) return {}; - int first_source = std::clamp(static_cast(std::ceil(std::min(window.first_index, window.last_index))), 0, available_count - 1); - int last_source = std::clamp(static_cast(std::floor(std::max(window.first_index, window.last_index))), first_source, available_count - 1); + double first_index = std::clamp(std::min(window.first_index, window.last_index), 0.0, static_cast(available_count - 1)); + double last_index = std::clamp(std::max(window.first_index, window.last_index), first_index, static_cast(available_count - 1)); + int first_source = std::clamp(static_cast(std::ceil(first_index)), 0, available_count - 1); + int last_source = std::clamp(static_cast(std::floor(last_index)), first_source, available_count - 1); std::pmr::vector points(resource); - points.reserve(static_cast(std::max(0, last_source - first_source + 1))); - for (int i = first_source; i <= last_source; ++i) { - double coord = source_index_to_coordinate(data_range, total_count, i); - points.push_back(mapping.map(coord, value_at(i))); - } + points.reserve(static_cast(std::max(0, last_source - first_source + 3))); + double last_appended = -std::numeric_limits::infinity(); + auto append_index = [&](double index) { + if (std::abs(index - last_appended) <= 1e-9) + return; + double value = interpolate_sample_value(index, available_count, mode, value_at); + if (!std::isfinite(value)) + return; + double coord = source_index_to_coordinate(data_range, total_count, index); + points.push_back(mapping.map(coord, value)); + last_appended = index; + }; + append_index(first_index); + for (int i = first_source; i <= last_source; ++i) + append_index(static_cast(i)); + append_index(last_index); return points; } template diff --git a/Core/plottable/Performance_Overlay.cpp b/Core/plottable/Performance_Overlay.cpp index 8d27f46..760a844 100644 --- a/Core/plottable/Performance_Overlay.cpp +++ b/Core/plottable/Performance_Overlay.cpp @@ -159,10 +159,10 @@ public: << duration_ms(frame.update_request_time_ns, frame.paint_begin_ns) << ',' << duration_ms(frame.paint_begin_ns, frame.paint_end_ns) << ',' << duration_ms(frame.core_data_time_ns, frame.paint_end_ns) << ',' - << frame.wait_21_ns << ',' - << frame.wait_12_ns << ',' - << frame.wait_23_ns << ',' - << frame.wait_32_ns << ',' + << frame.render_queue_wait_ns << ',' + << frame.ready_queue_wait_ns << ',' + << frame.present_queue_wait_ns << ',' + << frame.paint_return_wait_ns << ',' << frame.raw_input_count << ',' << frame.prepared_output_count << ',' << frame.renderable_cache_hit_count << ',' @@ -207,7 +207,7 @@ private: return value; } static std::string header() { - return "frame_id,input_version,outcome,limit_source,render_period_ns,paint_period_ns,user_period_ns,target_period_ns,request_wait_ms,prepare_ms,draw_ms,render_ms,middle_wait_ms,update_wait_ms,paint_ms,frame_age_ms,wait_21_ns,wait_12_ns,wait_23_ns,wait_32_ns,raw_input_count,prepared_output_count,cache_hit_count,cache_miss_count,cache_rebuild_count,cache_rebuild_time_ns,cache_compose_time_ns,cache_image_bytes,cache_image_area,cache_hit_ratio,pixel_width,pixel_height,core_data_time_ns,render_begin_time_ns,render_end_time_ns,transition_12_ns,transition_23_ns,paint_begin_time_ns,paint_end_time_ns,transition_32_ns,superseded_time_ns,renderable_stats,renderable_cache_stats"; + return "frame_id,input_version,outcome,limit_source,render_period_ns,paint_period_ns,user_period_ns,target_period_ns,request_wait_ms,prepare_ms,draw_ms,render_ms,middle_wait_ms,update_wait_ms,paint_ms,frame_age_ms,render_queue_wait_ns,ready_queue_wait_ns,present_queue_wait_ns,paint_return_wait_ns,raw_input_count,prepared_output_count,cache_hit_count,cache_miss_count,cache_rebuild_count,cache_rebuild_time_ns,cache_compose_time_ns,cache_image_bytes,cache_image_area,cache_hit_ratio,pixel_width,pixel_height,core_data_time_ns,render_begin_time_ns,render_end_time_ns,transition_12_ns,transition_23_ns,paint_begin_time_ns,paint_end_time_ns,transition_32_ns,superseded_time_ns,renderable_stats,renderable_cache_stats"; } }; Frame_Performance_Logger& frame_log() { diff --git a/Core/plottable/Performance_Overlay_p.h b/Core/plottable/Performance_Overlay_p.h index 52adc66..271ff81 100644 --- a/Core/plottable/Performance_Overlay_p.h +++ b/Core/plottable/Performance_Overlay_p.h @@ -16,7 +16,7 @@ #include #include "../architecture/Frame_Scheduler.h" #include "../architecture/Plot_Render_Context.h" -#include "../architecture/Render_Data.h" +#include "../flow/low_latency/Render_Data.h" #include "../base/Color.h" #include "../base/Geometry.h" #include "../base/Memory.h" @@ -119,10 +119,10 @@ struct Performance_Aggregate { summary.paint_begin_ns = frame.paint_begin_ns; summary.paint_end_ns = frame.paint_end_ns; summary.transition_32_ns = frame.transition_32_ns; - summary.wait_21_ns = frame.wait_21_ns; - summary.wait_12_ns = frame.wait_12_ns; - summary.wait_23_ns = frame.wait_23_ns; - summary.wait_32_ns = frame.wait_32_ns; + summary.render_queue_wait_ns = frame.render_queue_wait_ns; + summary.ready_queue_wait_ns = frame.ready_queue_wait_ns; + summary.present_queue_wait_ns = frame.present_queue_wait_ns; + summary.paint_return_wait_ns = frame.paint_return_wait_ns; summary.superseded_time_ns = frame.superseded_time_ns; summary.outcome = frame.outcome; summary.refresh = frame.refresh; @@ -276,10 +276,10 @@ enum class Performance_Metric_Id : std::uint16_t { Paint_Period, User_Period, Target_Period, - Wait_21, - Wait_12, - Wait_23, - Wait_32, + Render_Queue_Wait, + Ready_Queue_Wait, + Present_Queue_Wait, + Paint_Return_Wait, Worker_Count, Active_Workers, Queued_Frame_Jobs, @@ -396,10 +396,8 @@ inline void performance_atomic_add(std::atomic& value, double delta) { while (!value.compare_exchange_weak(current, current + delta, std::memory_order_acq_rel, std::memory_order_acquire)) {} } void schedule_performance_metrics_worker(const std::shared_ptr& state); -struct Performance_Overlay_Private { +struct Performance_Layout_Builder { Performance_Aggregate aggregate; - std::atomic_bool enabled_value{false}; - std::shared_ptr metrics_worker; std::vector lines; std::vector render_lines; std::array field_layout_states{}; @@ -407,215 +405,6 @@ struct Performance_Overlay_Private { RectF scroll_bar_rect; RectF scroll_thumb_rect; double content_width{}; - explicit Performance_Overlay_Private(bool create_worker = true) { - if (create_worker) - metrics_worker = std::make_shared(); - } - ~Performance_Overlay_Private() { - if (!metrics_worker) - return; - metrics_worker->cancelled.store(true, std::memory_order_release); - Performance_Frame_Record frame; - while (metrics_worker->metric_queue.try_pop(frame)) {} - } - void enqueue_frame_metric(std::shared_ptr frame) { - if (!frame) - return; - if (!metrics_worker->metric_queue.try_push(std::move(frame))) { - metrics_worker->dropped_frame_record_count.fetch_add(1, std::memory_order_relaxed); - return; - } - schedule_performance_metrics_worker(metrics_worker); - } - static void run_metrics_worker(const std::shared_ptr& state) { - if (!state) - return; - while (!state->cancelled.load(std::memory_order_acquire)) { - state->work_requested.store(false, std::memory_order_release); - bool changed = false; - if (state->reset_requested.exchange(false, std::memory_order_acq_rel)) { - state->aggregate.clear(); - state->scroll_offset.store(0.0, std::memory_order_release); - changed = true; - } - if (state->snapshot_rebuild_requested.exchange(false, std::memory_order_acq_rel)) - changed = true; - if (consume_scroll_commands(*state)) - changed = true; - std::uint64_t dropped = state->dropped_frame_record_count.exchange(0, std::memory_order_acq_rel); - if (dropped) { - state->aggregate.input_capacity_dropped += dropped; - changed = true; - } - Performance_Frame_Record frame; - while (state->metric_queue.try_pop(frame)) { - if (frame) - state->aggregate.update(*frame); - changed = true; - } - if (changed && state->enabled.load(std::memory_order_acquire)) { - state->published_snapshot.store(build_display_snapshot(*state), std::memory_order_release); - invoke_update_callback(*state); - } - if (state->work_requested.load(std::memory_order_acquire)) - continue; - state->worker_active.store(false, std::memory_order_release); - if (!state->work_requested.load(std::memory_order_acquire) || state->worker_active.exchange(true, std::memory_order_acq_rel)) - return; - } - state->worker_active.store(false, std::memory_order_release); - } - static std::shared_ptr build_display_snapshot(Performance_Worker_State& worker_state) { - Performance_Overlay_Private layout(false); - layout.aggregate = worker_state.aggregate; - layout.field_layout_states = worker_state.field_layout_states; - layout.rebuild_lines(); - auto snapshot = std::make_shared(); - snapshot->content_version = worker_state.next_content_version.fetch_add(1, std::memory_order_acq_rel); - Size viewport = current_viewport(worker_state); - snapshot->viewport_size = viewport; - snapshot->viewport_version = current_viewport_version(worker_state); - snapshot->viewport_rect = RectF{0.0, 0.0, static_cast(viewport.width), static_cast(viewport.height)}; - if (viewport.empty() || layout.lines.empty()) { - snapshot->lines = layout.stripped_lines(); - return snapshot; - } - Performance_Overlay_Render_State state; - { - std::lock_guard lock(worker_state.options_mutex); - static_cast(state) = worker_state.options; - } - Image measure_image(1, 1); - measure_image.fill(Color::transparent()); - Text_Metrics metrics; - { - Canvas measure_canvas(measure_image); - measure_canvas.set_font(state.font); - metrics = measure_canvas.measure_text("0123456789 ABC \xE4\xB8\xAD\xE6\x96\x87"); - if (metrics.line_height() <= 0.0) - metrics = measure_canvas.measure_text("M"); - layout.seed_static_field_widths(measure_canvas); - int available_text_width = viewport.width - state.left_margin - state.right_margin - state.scroll_bar_width - state.scroll_bar_margin * 2; - layout.build_render_lines(measure_canvas, static_cast(std::max(1, available_text_width))); - } - worker_state.field_layout_states = layout.field_layout_states; - snapshot->lines = layout.display_lines(); - layout.update_content_width(&state, worker_state.maximum_content_width, viewport.width); - double line_height = std::max(1.0, metrics.line_height()); - double panel_width = std::min(layout.content_width, static_cast(std::max(0, viewport.width))); - double height = layout.panel_height(line_height, &state, viewport.height); - if (panel_width <= 0.0 || height <= 0.0) - return snapshot; - double content_height = layout.content_height(line_height, &state); - double content_viewport_height = layout.content_viewport_height(&state, height); - double max_scroll = std::max(0.0, content_height - content_viewport_height); - double scroll = performance_clamp_scroll_offset(worker_state.scroll_offset.load(std::memory_order_acquire), max_scroll); - worker_state.scroll_offset.store(scroll, std::memory_order_release); - layout.panel_rect = RectF{0.0, 0.0, panel_width, height}; - Image image(static_cast(std::ceil(panel_width)), static_cast(std::ceil(height))); - image.fill(Color::transparent()); - Canvas canvas(image); - canvas.save(); - canvas.set_font(state.font); - canvas.set_brush(Brush{state.background, Brush_Style::Solid}); - canvas.fill_rect(RectF{0.0, 0.0, panel_width, height}); - canvas.set_pen(Pen{state.foreground}); - RectF text_rect{ - static_cast(state.left_margin), - static_cast(state.top_margin), - std::max(0.0, panel_width - state.left_margin - state.right_margin - state.scroll_bar_width - state.scroll_bar_margin * 2), - std::max(0.0, height - state.top_margin - state.bottom_margin) - }; - canvas.set_clip_rect(text_rect); - int first_line = std::max(0, static_cast(std::floor(scroll / line_height))); - double y = static_cast(state.top_margin) - (scroll - static_cast(first_line) * line_height); - for (int i = first_line; i < static_cast(layout.render_lines.size()) && y < text_rect.bottom(); ++i, y += line_height) { - if (y + line_height < text_rect.y) - continue; - layout.draw_render_line(canvas, layout.render_lines[static_cast(i)], text_rect.x, y); - } - canvas.reset_clip(); - layout.update_scroll_bar_rects(&state, max_scroll, scroll, content_viewport_height, content_height); - if (!layout.scroll_bar_rect.empty()) { - canvas.set_brush(Brush{state.scroll_track, Brush_Style::Solid}); - canvas.fill_rect(layout.scroll_bar_rect); - canvas.set_brush(Brush{state.scroll_thumb, Brush_Style::Solid}); - canvas.fill_rect(layout.scroll_thumb_rect); - } - canvas.restore(); - snapshot->destination_rect = layout.panel_rect; - snapshot->scroll_track_rect = layout.scroll_bar_rect; - snapshot->scroll_thumb_rect = layout.scroll_thumb_rect; - snapshot->content_height = content_height; - snapshot->scroll_offset = scroll; - snapshot->max_scroll_offset = max_scroll; - snapshot->line_height = line_height; - snapshot->image = std::move(image); - worker_state.last_scroll_track_rect = layout.scroll_bar_rect; - worker_state.last_scroll_thumb_rect = layout.scroll_thumb_rect; - worker_state.last_content_viewport_height = content_viewport_height; - worker_state.last_line_height = line_height; - worker_state.maximum_scroll_offset = max_scroll; - return snapshot; - } -private: - static Size current_viewport(const Performance_Worker_State& worker_state) { - int width = worker_state.viewport_width.load(std::memory_order_acquire); - int height = worker_state.viewport_height.load(std::memory_order_acquire); - if (width > 0 && height > 0) - return {width, height}; - if (!worker_state.aggregate.has_frame) - return {}; - const Frame_Lifecycle_Record& frame = worker_state.aggregate.latest; - return { - static_cast(std::min(frame.pixel_width, static_cast(std::numeric_limits::max()))), - static_cast(std::min(frame.pixel_height, static_cast(std::numeric_limits::max()))) - }; - } - static std::uint64_t current_viewport_version(const Performance_Worker_State& worker_state) { - std::uint64_t version = worker_state.viewport_version.load(std::memory_order_acquire); - if (version || !worker_state.aggregate.has_frame) - return version; - return worker_state.aggregate.latest.viewport_version; - } - static void invoke_update_callback(Performance_Worker_State& worker_state) { - std::function callback; - { - std::lock_guard lock(worker_state.update_callback_mutex); - callback = worker_state.update_callback; - } - if (callback) - callback(); - } - static bool consume_scroll_commands(Performance_Worker_State& worker_state) { - double offset = worker_state.scroll_offset.load(std::memory_order_acquire); - double previous = offset; - double line_height = std::max(1.0, worker_state.last_line_height); - double wheel_delta = performance_atomic_exchange(worker_state.pending_wheel_delta_px); - if (wheel_delta != 0.0) - offset += wheel_delta; - int page_delta = worker_state.pending_page_delta.exchange(0, std::memory_order_acq_rel); - if (page_delta) - offset += static_cast(page_delta) * std::max(line_height, worker_state.last_content_viewport_height * 0.8); - bool dragging = worker_state.drag_active.load(std::memory_order_acquire); - bool drag_end = worker_state.drag_end_requested.exchange(false, std::memory_order_acq_rel); - if (dragging && worker_state.maximum_scroll_offset > 0.0) { - const RectF& track = worker_state.last_scroll_track_rect; - const RectF& thumb = worker_state.last_scroll_thumb_rect; - double travel = track.height - thumb.height; - if (track.height > 0.0 && thumb.height > 0.0 && travel > 0.0) { - double thumb_top = worker_state.drag_position_y.load(std::memory_order_acquire) - - worker_state.drag_anchor_y.load(std::memory_order_acquire); - double ratio = (thumb_top - track.y) / travel; - offset = std::clamp(ratio, 0.0, 1.0) * worker_state.maximum_scroll_offset; - } - } - if (drag_end) - worker_state.drag_active.store(false, std::memory_order_release); - offset = performance_clamp_scroll_offset(offset, worker_state.maximum_scroll_offset); - worker_state.scroll_offset.store(offset, std::memory_order_release); - return wheel_delta != 0.0 || page_delta != 0 || drag_end || std::abs(offset - previous) > 0.01; - } static std::string value(double number, int precision = 2) { return format_number(number, {.precision = precision}); } @@ -666,14 +455,14 @@ private: return Performance_Metric_Id::User_Period; if (key == "target_period") return Performance_Metric_Id::Target_Period; - if (key == "wait_21") - return Performance_Metric_Id::Wait_21; - if (key == "wait_12") - return Performance_Metric_Id::Wait_12; - if (key == "wait_23") - return Performance_Metric_Id::Wait_23; - if (key == "wait_32") - return Performance_Metric_Id::Wait_32; + if (key == "render_queue_wait") + return Performance_Metric_Id::Render_Queue_Wait; + if (key == "ready_queue_wait") + return Performance_Metric_Id::Ready_Queue_Wait; + if (key == "present_queue_wait") + return Performance_Metric_Id::Present_Queue_Wait; + if (key == "paint_return_wait") + return Performance_Metric_Id::Paint_Return_Wait; if (key == "taskflow_workers") return Performance_Metric_Id::Worker_Count; if (key == "taskflow_active") @@ -991,7 +780,7 @@ private: lines.clear(); lines.push_back("frame:" + count_text("frame_id", frame.frame_id) + " input_ver:" + count_text("input_version", frame.input_version) + " outcome:" + marked_value(Performance_Metric_Id::Outcome, outcome_text(frame.outcome)) + " limit:" + limit_source_text(refresh.source)); lines.push_back("period render:" + period_text("render_period", refresh.render_period_ns) + " paint:" + period_text("paint_period", refresh.paint_period_ns) + " user:" + period_text("user_period", refresh.user_period_ns) + " target:" + period_text("target_period", refresh.target_period_ns)); - lines.push_back("wait 21:" + ns_text("wait_21", frame.wait_21_ns) + "ms 12:" + ns_text("wait_12", frame.wait_12_ns) + "ms 23:" + ns_text("wait_23", frame.wait_23_ns) + "ms 32:" + ns_text("wait_32", frame.wait_32_ns) + "ms"); + lines.push_back("queue render:" + ns_text("render_queue_wait", frame.render_queue_wait_ns) + "ms ready:" + ns_text("ready_queue_wait", frame.ready_queue_wait_ns) + "ms present:" + ns_text("present_queue_wait", frame.present_queue_wait_ns) + "ms return:" + ns_text("paint_return_wait", frame.paint_return_wait_ns) + "ms"); lines.push_back("taskflow workers:" + count_text("taskflow_workers", static_cast(executor.worker_count)) + " active:" + count_text("taskflow_active", static_cast(executor.active_workers)) + " queued_frames:" + count_text("taskflow_queued_frames", static_cast(executor.queued_frame_jobs)) + " active_frames:" + count_text("taskflow_active_frames", static_cast(executor.active_frame_jobs))); lines.push_back("taskflow tasks submitted:" + count_text("taskflow_submitted", executor.submitted_jobs) + " started:" + count_text("taskflow_started", executor.started_task_nodes) + " completed:" + count_text("taskflow_completed", executor.completed_task_nodes) + " rejected_frames:" + count_text("taskflow_rejected", executor.rejected_frame_jobs)); lines.push_back("taskflow wait avg:" + ns_text("taskflow_queue_wait_avg", executor.average_queue_wait_ns) + "ms p95:" + ns_text("taskflow_queue_wait_p95", executor.max_queue_wait_ns) + "ms run avg:" + ns_text("taskflow_run_avg", executor.average_task_duration_ns) + "ms p95:" + ns_text("taskflow_run_p95", executor.max_task_duration_ns) + "ms"); @@ -1231,4 +1020,225 @@ private: return text; } }; + +struct Performance_Overlay_Private { + std::atomic_bool enabled_value{false}; + std::shared_ptr metrics_worker; + + Performance_Overlay_Private() + : metrics_worker(std::make_shared()) {} + + ~Performance_Overlay_Private() { + if (!metrics_worker) + return; + metrics_worker->cancelled.store(true, std::memory_order_release); + Performance_Frame_Record frame; + while (metrics_worker->metric_queue.try_pop(frame)) {} + } + + void enqueue_frame_metric(std::shared_ptr frame) { + if (!frame) + return; + if (!metrics_worker->metric_queue.try_push(std::move(frame))) { + metrics_worker->dropped_frame_record_count.fetch_add(1, std::memory_order_relaxed); + return; + } + schedule_performance_metrics_worker(metrics_worker); + } + + static void run_metrics_worker(const std::shared_ptr& state) { + if (!state) + return; + while (!state->cancelled.load(std::memory_order_acquire)) { + state->work_requested.store(false, std::memory_order_release); + bool changed = false; + if (state->reset_requested.exchange(false, std::memory_order_acq_rel)) { + state->aggregate.clear(); + state->scroll_offset.store(0.0, std::memory_order_release); + changed = true; + } + if (state->snapshot_rebuild_requested.exchange(false, std::memory_order_acq_rel)) + changed = true; + if (consume_scroll_commands(*state)) + changed = true; + std::uint64_t dropped = state->dropped_frame_record_count.exchange(0, std::memory_order_acq_rel); + if (dropped) { + state->aggregate.input_capacity_dropped += dropped; + changed = true; + } + Performance_Frame_Record frame; + while (state->metric_queue.try_pop(frame)) { + if (frame) + state->aggregate.update(*frame); + changed = true; + } + if (changed && state->enabled.load(std::memory_order_acquire)) { + state->published_snapshot.store(build_display_snapshot(*state), std::memory_order_release); + invoke_update_callback(*state); + } + if (state->work_requested.load(std::memory_order_acquire)) + continue; + state->worker_active.store(false, std::memory_order_release); + if (!state->work_requested.load(std::memory_order_acquire) || state->worker_active.exchange(true, std::memory_order_acq_rel)) + return; + } + state->worker_active.store(false, std::memory_order_release); + } + + static std::shared_ptr build_display_snapshot(Performance_Worker_State& worker_state) { + Performance_Layout_Builder layout; + layout.aggregate = worker_state.aggregate; + layout.field_layout_states = worker_state.field_layout_states; + layout.rebuild_lines(); + auto snapshot = std::make_shared(); + snapshot->content_version = worker_state.next_content_version.fetch_add(1, std::memory_order_acq_rel); + Size viewport = current_viewport(worker_state); + snapshot->viewport_size = viewport; + snapshot->viewport_version = current_viewport_version(worker_state); + snapshot->viewport_rect = RectF{0.0, 0.0, static_cast(viewport.width), static_cast(viewport.height)}; + if (viewport.empty() || layout.lines.empty()) { + snapshot->lines = layout.stripped_lines(); + return snapshot; + } + Performance_Overlay_Render_State state; + { + std::lock_guard lock(worker_state.options_mutex); + static_cast(state) = worker_state.options; + } + Image measure_image(1, 1); + measure_image.fill(Color::transparent()); + Text_Metrics metrics; + { + Canvas measure_canvas(measure_image); + measure_canvas.set_font(state.font); + metrics = measure_canvas.measure_text("0123456789 ABC \xE4\xB8\xAD\xE6\x96\x87"); + if (metrics.line_height() <= 0.0) + metrics = measure_canvas.measure_text("M"); + layout.seed_static_field_widths(measure_canvas); + int available_text_width = viewport.width - state.left_margin - state.right_margin - state.scroll_bar_width - state.scroll_bar_margin * 2; + layout.build_render_lines(measure_canvas, static_cast(std::max(1, available_text_width))); + } + worker_state.field_layout_states = layout.field_layout_states; + snapshot->lines = layout.display_lines(); + layout.update_content_width(&state, worker_state.maximum_content_width, viewport.width); + double line_height = std::max(1.0, metrics.line_height()); + double panel_width = std::min(layout.content_width, static_cast(std::max(0, viewport.width))); + double height = layout.panel_height(line_height, &state, viewport.height); + if (panel_width <= 0.0 || height <= 0.0) + return snapshot; + double content_height = layout.content_height(line_height, &state); + double content_viewport_height = layout.content_viewport_height(&state, height); + double max_scroll = std::max(0.0, content_height - content_viewport_height); + double scroll = performance_clamp_scroll_offset(worker_state.scroll_offset.load(std::memory_order_acquire), max_scroll); + worker_state.scroll_offset.store(scroll, std::memory_order_release); + layout.panel_rect = RectF{0.0, 0.0, panel_width, height}; + Image image(static_cast(std::ceil(panel_width)), static_cast(std::ceil(height))); + image.fill(Color::transparent()); + Canvas canvas(image); + canvas.save(); + canvas.set_font(state.font); + canvas.set_brush(Brush{state.background, Brush_Style::Solid}); + canvas.fill_rect(RectF{0.0, 0.0, panel_width, height}); + canvas.set_pen(Pen{state.foreground}); + RectF text_rect{ + static_cast(state.left_margin), + static_cast(state.top_margin), + std::max(0.0, panel_width - state.left_margin - state.right_margin - state.scroll_bar_width - state.scroll_bar_margin * 2), + std::max(0.0, height - state.top_margin - state.bottom_margin) + }; + canvas.set_clip_rect(text_rect); + int first_line = std::max(0, static_cast(std::floor(scroll / line_height))); + double y = static_cast(state.top_margin) - (scroll - static_cast(first_line) * line_height); + for (int i = first_line; i < static_cast(layout.render_lines.size()) && y < text_rect.bottom(); ++i, y += line_height) { + if (y + line_height < text_rect.y) + continue; + layout.draw_render_line(canvas, layout.render_lines[static_cast(i)], text_rect.x, y); + } + canvas.reset_clip(); + layout.update_scroll_bar_rects(&state, max_scroll, scroll, content_viewport_height, content_height); + if (!layout.scroll_bar_rect.empty()) { + canvas.set_brush(Brush{state.scroll_track, Brush_Style::Solid}); + canvas.fill_rect(layout.scroll_bar_rect); + canvas.set_brush(Brush{state.scroll_thumb, Brush_Style::Solid}); + canvas.fill_rect(layout.scroll_thumb_rect); + } + canvas.restore(); + snapshot->destination_rect = layout.panel_rect; + snapshot->scroll_track_rect = layout.scroll_bar_rect; + snapshot->scroll_thumb_rect = layout.scroll_thumb_rect; + snapshot->content_height = content_height; + snapshot->scroll_offset = scroll; + snapshot->max_scroll_offset = max_scroll; + snapshot->line_height = line_height; + snapshot->image = std::move(image); + worker_state.last_scroll_track_rect = layout.scroll_bar_rect; + worker_state.last_scroll_thumb_rect = layout.scroll_thumb_rect; + worker_state.last_content_viewport_height = content_viewport_height; + worker_state.last_line_height = line_height; + worker_state.maximum_scroll_offset = max_scroll; + return snapshot; + } + +private: + static Size current_viewport(const Performance_Worker_State& worker_state) { + int width = worker_state.viewport_width.load(std::memory_order_acquire); + int height = worker_state.viewport_height.load(std::memory_order_acquire); + if (width > 0 && height > 0) + return {width, height}; + if (!worker_state.aggregate.has_frame) + return {}; + const Frame_Lifecycle_Record& frame = worker_state.aggregate.latest; + return { + static_cast(std::min(frame.pixel_width, static_cast(std::numeric_limits::max()))), + static_cast(std::min(frame.pixel_height, static_cast(std::numeric_limits::max()))) + }; + } + + static std::uint64_t current_viewport_version(const Performance_Worker_State& worker_state) { + std::uint64_t version = worker_state.viewport_version.load(std::memory_order_acquire); + if (version || !worker_state.aggregate.has_frame) + return version; + return worker_state.aggregate.latest.viewport_version; + } + + static void invoke_update_callback(Performance_Worker_State& worker_state) { + std::function callback; + { + std::lock_guard lock(worker_state.update_callback_mutex); + callback = worker_state.update_callback; + } + if (callback) + callback(); + } + + static bool consume_scroll_commands(Performance_Worker_State& worker_state) { + double offset = worker_state.scroll_offset.load(std::memory_order_acquire); + double previous = offset; + double line_height = std::max(1.0, worker_state.last_line_height); + double wheel_delta = performance_atomic_exchange(worker_state.pending_wheel_delta_px); + if (wheel_delta != 0.0) + offset += wheel_delta; + int page_delta = worker_state.pending_page_delta.exchange(0, std::memory_order_acq_rel); + if (page_delta) + offset += static_cast(page_delta) * std::max(line_height, worker_state.last_content_viewport_height * 0.8); + bool dragging = worker_state.drag_active.load(std::memory_order_acquire); + bool drag_end = worker_state.drag_end_requested.exchange(false, std::memory_order_acq_rel); + if (dragging && worker_state.maximum_scroll_offset > 0.0) { + const RectF& track = worker_state.last_scroll_track_rect; + const RectF& thumb = worker_state.last_scroll_thumb_rect; + double travel = track.height - thumb.height; + if (track.height > 0.0 && thumb.height > 0.0 && travel > 0.0) { + double thumb_top = worker_state.drag_position_y.load(std::memory_order_acquire) + - worker_state.drag_anchor_y.load(std::memory_order_acquire); + double ratio = (thumb_top - track.y) / travel; + offset = std::clamp(ratio, 0.0, 1.0) * worker_state.maximum_scroll_offset; + } + } + if (drag_end) + worker_state.drag_active.store(false, std::memory_order_release); + offset = performance_clamp_scroll_offset(offset, worker_state.maximum_scroll_offset); + worker_state.scroll_offset.store(offset, std::memory_order_release); + return wheel_delta != 0.0 || page_delta != 0 || drag_end || std::abs(offset - previous) > 0.01; + } +}; } // namespace renderive diff --git a/Core/plottable/Selection_Rectangle_Overlay.cpp b/Core/plottable/Selection_Rectangle_Overlay.cpp index 011f8cf..51613a0 100644 --- a/Core/plottable/Selection_Rectangle_Overlay.cpp +++ b/Core/plottable/Selection_Rectangle_Overlay.cpp @@ -17,7 +17,7 @@ std::shared_ptr Selection_Rectangle_Overlay::Builde auto ret = renderive::make_shared(); ret->init(parent); Selection_Rectangle_Overlay_Private* pd = ret->d(); - Selection_Rectangle_Overlay_Render_State* sc = reinterpret_cast(pd->state(State_Edit)); + Selection_Rectangle_Overlay_Render_State* sc = reinterpret_cast(pd->edit_state()); sc->horizontal_axis = domain_axis; sc->vertical_axis = value_axis; sc->label_font = font; @@ -35,16 +35,16 @@ void Selection_Rectangle_Overlay::set_vertical_axis(std::shared_ptr ax edit->vertical_axis = std::weak_ptr(axis); } Font Selection_Rectangle_Overlay::label_font() { - return d()->state_data(State_Edit)->label_font; + return d()->edit_state()->label_font; } Pen Selection_Rectangle_Overlay::label_pen() { - return d()->state_data(State_Edit)->label_pen; + return d()->edit_state()->label_pen; } Brush Selection_Rectangle_Overlay::selection_brush() { - return d()->state_data(State_Edit)->selection_brush; + return d()->edit_state()->selection_brush; } Pen Selection_Rectangle_Overlay::selection_border_pen() { - return d()->state_data(State_Edit)->selection_border_pen; + return d()->edit_state()->selection_border_pen; } void Selection_Rectangle_Overlay::set_label_font(const Font& font) { Render_Edit_Lease edit(d()); @@ -63,7 +63,7 @@ void Selection_Rectangle_Overlay::set_selection_border_pen(const Pen& pen) { edit->selection_border_pen = pen.color.a != 0 ? pen : Pen{.style = Line_Style::None}; } std::vector Selection_Rectangle_Overlay::selected_regions() { - return d()->state_data(State_Edit)->rects; + return d()->edit_state()->rects; } void Selection_Rectangle_Overlay::clear_selected_regions() { Render_Edit_Lease edit(d()); diff --git a/Core/plottable/Selection_Rectangle_Overlay_p.h b/Core/plottable/Selection_Rectangle_Overlay_p.h index c5fe13c..74c8ada 100644 --- a/Core/plottable/Selection_Rectangle_Overlay_p.h +++ b/Core/plottable/Selection_Rectangle_Overlay_p.h @@ -111,7 +111,7 @@ struct Selection_Rectangle_Overlay_Private : Typed_Render_Datahorizontal_axis.lock(); auto vertical_axis = s->vertical_axis.lock(); diff --git a/Core/plottable/Spectrum.cpp b/Core/plottable/Spectrum.cpp index 5d673d2..2d20e5c 100644 --- a/Core/plottable/Spectrum.cpp +++ b/Core/plottable/Spectrum.cpp @@ -174,7 +174,7 @@ std::shared_ptr Spectrum::Builder::build() { auto ret = renderive::make_shared(); ret->init(parent); Spectrum_Private* pd = ret->d(); - Spectrum_Render_State* sc = reinterpret_cast(pd->state(State_Edit)); + Spectrum_Render_State* sc = reinterpret_cast(pd->edit_state()); PROP_R(std::weak_ptr, frequency_axis) PROP_R(std::weak_ptr, power_axis); PROP_R(int, frequency_point_size); @@ -424,7 +424,7 @@ void Spectrum_Private::draw_label_items(Canvas& canvas, Spectrum_Render_State* s canvas.restore(); } } -void Spectrum_Private::draw(Canvas& canvas, const Plot_Render_Snapshot&) { +void Spectrum_Private::draw(Canvas& canvas, const Render_Frame_Snapshot&) { auto s = render_state(); draw_power_curve(canvas, s, cur_powers, s->cur_pen, s->current_brush); draw_spectrum(canvas, s); @@ -562,7 +562,7 @@ void Spectrum::clear_custom_markers() { edit->marker_list.clear(); } double Spectrum::marker_frequency(int mark_index) { - auto render_state = d()->state_data(State_Edit); + auto render_state = d()->edit_state(); if (mark_index < 0 || mark_index >= (int)render_state->marker_list.size()) return -1; return render_state->marker_list[mark_index].frequency; @@ -579,11 +579,11 @@ void Spectrum::set_current_marker_frequency(double frequency) { edit->set_cur_frequency(frequency); } int Spectrum::selectable_line_marker_count() { - auto render_state = d()->state_data(State_Edit); + auto render_state = d()->edit_state(); return (int)render_state->marker_list.size(); } int Spectrum::selected_marker_index() { - auto render_state = d()->state_data(State_Edit); + auto render_state = d()->edit_state(); return render_state->cur_select_index; } void Spectrum::set_selected_marker_index(int marker_index) { diff --git a/Core/plottable/Spectrum_p.h b/Core/plottable/Spectrum_p.h index 066da55..395aa49 100644 --- a/Core/plottable/Spectrum_p.h +++ b/Core/plottable/Spectrum_p.h @@ -183,8 +183,8 @@ struct Spectrum_Private : Typed_Render_Datafrequency_point_size || cache_frequency_range != s->frequency_range) { diff --git a/Core/plottable/Sweep_Spectrum.cpp b/Core/plottable/Sweep_Spectrum.cpp index 0a2c469..5e88a86 100644 --- a/Core/plottable/Sweep_Spectrum.cpp +++ b/Core/plottable/Sweep_Spectrum.cpp @@ -103,7 +103,7 @@ std::shared_ptr Sweep_Spectrum::Builder::build() { auto ret = renderive::make_shared(); ret->init(parent); Sweep_Spectrum_Private* pd = ret->d(); - Sweep_Spectrum_Render_State* sc = reinterpret_cast(pd->state(State_Edit)); + Sweep_Spectrum_Render_State* sc = reinterpret_cast(pd->edit_state()); PROP_R(std::weak_ptr, frequency_axis) PROP_R(std::weak_ptr, power_axis) PROP_R(Range, frequency_range) diff --git a/Core/plottable/Sweep_Spectrum_p.h b/Core/plottable/Sweep_Spectrum_p.h index 5ef2ed1..6f27524 100644 --- a/Core/plottable/Sweep_Spectrum_p.h +++ b/Core/plottable/Sweep_Spectrum_p.h @@ -51,7 +51,7 @@ struct Sweep_Spectrum_Private : renderive::Typed_Render_Datafrequency_axis.lock(); diff --git a/Core/plottable/Waterfall.cpp b/Core/plottable/Waterfall.cpp index 2183984..855a151 100644 --- a/Core/plottable/Waterfall.cpp +++ b/Core/plottable/Waterfall.cpp @@ -139,7 +139,7 @@ std::shared_ptr Waterfall::Builder::build() { auto ret = renderive::make_shared(); ret->init(parent); Waterfall_Private* pd = ret->d(); - Waterfall_Render_State* sc = reinterpret_cast(pd->state(State_Edit)); + Waterfall_Render_State* sc = reinterpret_cast(pd->edit_state()); PROP_R(std::weak_ptr, frequency_axis) PROP_R(std::weak_ptr, time_axis) PROP_R(Range, frequency_range) diff --git a/Core/plottable/Waterfall_p.h b/Core/plottable/Waterfall_p.h index 12ecaf1..6b55b2a 100644 --- a/Core/plottable/Waterfall_p.h +++ b/Core/plottable/Waterfall_p.h @@ -10,6 +10,8 @@ #include #include #include +#include +#include #include #include #include "../Axis/Frequency_Axis_p.h" @@ -137,15 +139,18 @@ struct Waterfall_Image_Request { std::pmr::vector> update_states; }; struct Waterfall_Image_Tile { + int x{}; int y{}; Image image; }; struct Waterfall_Image_Job { - explicit Waterfall_Image_Job(std::shared_ptr request, int tile_count) - : request(std::move(request)), remaining(tile_count) { - tiles.resize(static_cast(tile_count)); + explicit Waterfall_Image_Job(std::shared_ptr request, int columns, int rows) + : request(std::move(request)), tile_columns(columns), tile_rows(rows), remaining(columns * rows) { + tiles.resize(static_cast(columns * rows)); } std::shared_ptr request; + int tile_columns{}; + int tile_rows{}; std::vector tiles; std::atomic remaining; }; @@ -174,21 +179,29 @@ static bool waterfall_request_coord_by_stream_tick(const Waterfall_Image_Request coord = request.key.start_tick + (request.key.height - request.visible_time_count) + (stream_tick - request.lower_stream_tick); return coord >= request.key.start_tick && coord < request.key.start_tick + request.key.height; } -static int waterfall_tile_count(int height) { +static int waterfall_tile_column_count(int width) { + static constexpr int Tile_Width = 128; + return std::max(1, (width + Tile_Width - 1) / Tile_Width); +} +static int waterfall_tile_row_count(int height) { static constexpr int Tile_Height = 64; return std::max(1, (height + Tile_Height - 1) / Tile_Height); } static void write_waterfall_tile(Waterfall_Image_Job& job, int tile_index) { const Waterfall_Image_Request& request = *job.request; - int tile_count = static_cast(job.tiles.size()); - int y_begin = request.key.height * tile_index / tile_count; - int y_end = request.key.height * (tile_index + 1) / tile_count; + int tile_x = tile_index % job.tile_columns; + int tile_y = tile_index / job.tile_columns; + int x_begin = request.key.width * tile_x / job.tile_columns; + int x_end = request.key.width * (tile_x + 1) / job.tile_columns; + int y_begin = request.key.height * tile_y / job.tile_rows; + int y_end = request.key.height * (tile_y + 1) / job.tile_rows; Waterfall_Image_Tile& tile = job.tiles[static_cast(tile_index)]; + tile.x = x_begin; tile.y = y_begin; + int tile_width = std::max(0, x_end - x_begin); int tile_height = std::max(0, y_end - y_begin); - tile.image.resize(request.key.width, tile_height); + tile.image.resize(tile_width, tile_height); tile.image.fill(request.key.background); - Pixel bg = premultiply(request.key.background); for (const Waterfall_Data& row : request.rows) { int coord{}; if (!waterfall_request_coord_by_stream_tick(request, row.tick, coord)) @@ -199,20 +212,41 @@ static void write_waterfall_tile(Waterfall_Image_Job& job, int tile_index) { Pixel* line = tile.image.row(image_y - y_begin); if (!line) continue; - for (int col = 0; col < request.key.width; ++col) - line[col] = bg; - for (int col = request.key.first_col; col <= request.key.last_col; ++col) { - int color_offset = static_cast((row.frequency[static_cast(col)] - request.value_start_coord) * request.rate); - line[col] = request.color_map.at_offset(color_offset); + int first_col = std::max(request.key.first_col, x_begin); + int last_col = std::min(request.key.last_col, x_end - 1); + for (int col = first_col; col <= last_col; ++col) { + std::size_t source_col = static_cast(col - request.key.first_col); + if (source_col >= row.frequency.size()) + continue; + int color_offset = static_cast((row.frequency[source_col] - request.value_start_coord) * request.rate); + line[col - x_begin] = request.color_map.at_offset(color_offset); } } } +static void write_waterfall_tiles(Waterfall_Image_Job& job) { + int tile_count = static_cast(job.tiles.size()); + if (tile_count <= 1) { + write_waterfall_tile(job, 0); + return; + } + unsigned hardware_threads = std::max(1u, std::thread::hardware_concurrency()); + unsigned worker_count = std::min(static_cast(tile_count), hardware_threads); + tf::Executor executor(worker_count); + tf::Taskflow taskflow; + for (int tile_index = 0; tile_index < tile_count; ++tile_index) { + taskflow.emplace([&job, tile_index]() { + write_waterfall_tile(job, tile_index); + }); + } + executor.run(taskflow).wait(); +} static Waterfall_Image_Snapshot build_waterfall_image_snapshot(const std::shared_ptr& request_ptr) { Waterfall_Image_Request& request = *request_ptr; - int tile_count = waterfall_tile_count(request.key.height); - Waterfall_Image_Job job(request_ptr, tile_count); - for (int tile_index = 0; tile_index < tile_count; ++tile_index) - write_waterfall_tile(job, tile_index); + Waterfall_Image_Job job( + request_ptr, + waterfall_tile_column_count(request.key.width), + waterfall_tile_row_count(request.key.height)); + write_waterfall_tiles(job); Waterfall_Image_Snapshot snapshot; snapshot.key = request.key; snapshot.frequency_range = request.draw_frequency_range; @@ -224,7 +258,7 @@ static Waterfall_Image_Snapshot build_waterfall_image_snapshot(const std::shared Pixel* target = snapshot.image.row(tile.y + y); const Pixel* source = tile.image.row(y); if (target && source) - std::memcpy(target, source, static_cast(request.key.width) * sizeof(Pixel)); + std::memcpy(target + tile.x, source, static_cast(tile.image.width()) * sizeof(Pixel)); } } snapshot.update_states = std::move(request.update_states); @@ -335,7 +369,7 @@ struct Waterfall_Private : Typed_Render_Datatime_axis.lock(); @@ -404,7 +438,7 @@ struct Waterfall_Private : Typed_Render_Datafrequency_axis.lock(); auto time_axis = s->time_axis.lock(); @@ -462,10 +496,14 @@ struct Waterfall_Private : Typed_Render_Datarows.reserve(static_cast(buffer_data_count)); for (int row = 0; row < buffer_data_count; ++row) { int buffer_row = buffer_data_count - row - 1; - const double* list = ring_buffer.frequency_data(buffer_row); + int stream_tick = ring_buffer.tick(buffer_row); + int image_coord{}; + if (!waterfall_request_coord_by_stream_tick(*request, stream_tick, image_coord)) + continue; + const double* list = ring_buffer.frequency_data(buffer_row, first_col, last_col); Waterfall_Data image_row; - image_row.tick = ring_buffer.tick(buffer_row); - image_row.frequency.assign(list, list + col_count); + image_row.tick = stream_tick; + image_row.frequency.assign(list + first_col, list + last_col + 1); request->rows.push_back(std::move(image_row)); } request->update_states = std::move(pending_image_update_states); @@ -494,7 +532,7 @@ struct Waterfall_Private : Typed_Render_Datasource_rect.width, image_snapshot->source_rect.height}, image_snapshot->image, image_snapshot->source_rect); canvas.restore(); } - void draw(Canvas& canvas, const Plot_Render_Snapshot& snapshot) override { + void draw(Canvas& canvas, const Render_Frame_Snapshot& snapshot) override { Waterfall_Render_State* s = render_state(); auto frequency_axis = s->frequency_axis.lock(); auto time_axis = s->time_axis.lock(); diff --git a/Core/primitive/Band_Region.cpp b/Core/primitive/Band_Region.cpp index 5f0ee3d..4f33515 100644 --- a/Core/primitive/Band_Region.cpp +++ b/Core/primitive/Band_Region.cpp @@ -9,7 +9,7 @@ namespace renderive { struct Band_Region_Render_State : Render_State, Band_Region_Prop {}; struct Band_Region_Private : Typed_Render_Data { - void draw(Canvas& canvas, const Plot_Render_Snapshot&) override { + void draw(Canvas& canvas, const Render_Frame_Snapshot&) override { Band_Region_Render_State* s = render_state(); auto domain_axis = s->domain_axis.lock(); auto value_axis = s->value_axis.lock(); @@ -67,7 +67,7 @@ std::shared_ptr Band_Region::Builder::build() { auto ret = renderive::make_shared(); ret->init(parent); Band_Region_Private* pd = ret->d(); - Band_Region_Render_State* sc = reinterpret_cast(pd->state(State_Edit)); + Band_Region_Render_State* sc = reinterpret_cast(pd->edit_state()); PROP_R(std::weak_ptr, domain_axis) PROP_R(std::weak_ptr, value_axis) PROP_R(Range, range) diff --git a/Core/primitive/Color_Bar.cpp b/Core/primitive/Color_Bar.cpp index ab3e02b..7831436 100644 --- a/Core/primitive/Color_Bar.cpp +++ b/Core/primitive/Color_Bar.cpp @@ -5,7 +5,7 @@ #include #include "../base/Memory.h" #include "../base/String_Format.h" -#include "../architecture/Render_Lease.h" +#include "../flow/low_latency/Render_Lease.h" #include "../render/Canvas.h" #include "../render/Image.h" @@ -47,7 +47,7 @@ struct Color_Bar_Private : Typed_Render_Datacolor_scale.snapshot(); if (scale->range.length() == 0.0) @@ -146,7 +146,7 @@ std::shared_ptr Color_Bar::Builder::build() { auto ret = renderive::make_shared(); ret->init(parent); Color_Bar_Private* pd = ret->d(); - Color_Bar_Render_State* sc = reinterpret_cast(pd->state(State_Edit)); + Color_Bar_Render_State* sc = reinterpret_cast(pd->edit_state()); sc->color_scale = color_scale.clone(); PROP_R(std::string, name) PROP_R(std::string, unit) diff --git a/Core/primitive/Curve.cpp b/Core/primitive/Curve.cpp index ec41427..e4b51f8 100644 --- a/Core/primitive/Curve.cpp +++ b/Core/primitive/Curve.cpp @@ -9,6 +9,7 @@ #include #include #include +#include #include "../base/Memory.h" #include "../render/Canvas.h" namespace renderive { @@ -19,36 +20,62 @@ enum class Curve_Input_Mode : std::uint8_t { }; struct Curve_Input_Data : Input_Data {}; struct Curve_Render_State : Render_State, Curve_Prop {}; +struct Curve_Sample_Source { + Curve_Sample_Source() : values(memory_resource(Memory_Domain::Curve)) {} + explicit Curve_Sample_Source(std::span input) : values(memory_resource(Memory_Domain::Curve)) { + values.assign(input.begin(), input.end()); + } + explicit Curve_Sample_Source(std::pmr::vector&& input) : values(std::move(input)) {} + std::pmr::vector values; +}; +struct Curve_Point_Source { + Curve_Point_Source() : values(memory_resource(Memory_Domain::Curve)) {} + explicit Curve_Point_Source(std::span input) : values(memory_resource(Memory_Domain::Curve)) { + values.assign(input.begin(), input.end()); + } + explicit Curve_Point_Source(std::pmr::vector&& input) : values(std::move(input)) {} + std::pmr::vector values; +}; +using Curve_Source = std::variant; +static Curve_Input_Mode curve_source_mode(const Curve_Source& source) { + if (std::holds_alternative(source)) + return Curve_Input_Mode::Samples; + if (std::holds_alternative(source)) + return Curve_Input_Mode::Points; + return Curve_Input_Mode::None; +} +static std::size_t curve_sample_count(const Curve_Source& source) { + const auto* samples = std::get_if(&source); + return samples ? samples->values.size() : 0u; +} +static std::size_t curve_point_count(const Curve_Source& source) { + const auto* points = std::get_if(&source); + return points ? points->values.size() : 0u; +} struct Curve_Input_Block { - Curve_Input_Mode mode = Curve_Input_Mode::None; - std::pmr::vector samples{memory_resource(Memory_Domain::Curve)}; - std::pmr::vector points{memory_resource(Memory_Domain::Curve)}; + std::shared_ptr source = std::make_shared(); std::shared_ptr completion_state; static std::shared_ptr samples_block(std::span values, std::shared_ptr state = {}) { auto block = std::make_shared(); - block->mode = Curve_Input_Mode::Samples; - block->samples.assign(values.begin(), values.end()); + block->source = std::make_shared(Curve_Sample_Source(values)); block->completion_state = std::move(state); return block; } static std::shared_ptr samples_block(std::pmr::vector&& values, std::shared_ptr state = {}) { auto block = std::make_shared(); - block->mode = Curve_Input_Mode::Samples; - block->samples = std::move(values); + block->source = std::make_shared(Curve_Sample_Source(std::move(values))); block->completion_state = std::move(state); return block; } static std::shared_ptr points_block(std::span values, std::shared_ptr state = {}) { auto block = std::make_shared(); - block->mode = Curve_Input_Mode::Points; - block->points.assign(values.begin(), values.end()); + block->source = std::make_shared(Curve_Point_Source(values)); block->completion_state = std::move(state); return block; } static std::shared_ptr points_block(std::pmr::vector&& values, std::shared_ptr state = {}) { auto block = std::make_shared(); - block->mode = Curve_Input_Mode::Points; - block->points = std::move(values); + block->source = std::make_shared(Curve_Point_Source(std::move(values))); block->completion_state = std::move(state); return block; } @@ -94,15 +121,14 @@ struct Curve_Geometry_Result { std::pmr::vector> update_states; }; struct Curve_Geometry_Request { - Curve_Geometry_Request() : samples(memory_resource(Memory_Domain::Curve)), data_points(memory_resource(Memory_Domain::Curve)), update_states(memory_resource(Memory_Domain::Update_Completion)) {} + Curve_Geometry_Request() : update_states(memory_resource(Memory_Domain::Update_Completion)) {} Curve_Input_Mode mode = Curve_Input_Mode::None; Axis_Mapping_2D mapping; Range data_range{0.0, 1.0}; bool visible_range_only{}; Line_Interpolation_Mode interpolation_mode = Line_Interpolation_Mode::Linear_Value; Line_Sampling_Strategy sample_mode = Line_Sampling_Strategy::Pixel_Min_Max_Envelope; - std::pmr::vector samples; - std::pmr::vector data_points; + std::shared_ptr source; std::pmr::vector> update_states; }; static Curve_Axis_Geometry_Key curve_axis_key(const Axis_Frame_Snapshot& axis) { @@ -124,29 +150,30 @@ static void complete_curve_result_states(Curve_Geometry_Result& result, std::err } static void build_curve_geometry(Curve_Geometry_Result& result, const Curve_Geometry_Request& request) { std::pmr::vector& points = result.points; - if (request.mode == Curve_Input_Mode::Points) { - points.reserve(request.data_points.size()); - for (std::size_t i = 0; i < request.data_points.size(); ++i) - points.push_back(request.mapping.map(request.data_points[i])); + if (!request.source) + return; + if (const auto* data_points = std::get_if(request.source.get())) { + points.reserve(data_points->values.size()); + for (std::size_t i = 0; i < data_points->values.size(); ++i) + points.push_back(request.mapping.map(data_points->values[i])); return; } - if (request.mode != Curve_Input_Mode::Samples || request.samples.size() < 2 || request.data_range.length() == 0.0) + const auto* samples = std::get_if(request.source.get()); + if (!samples || samples->values.size() < 2 || request.data_range.length() == 0.0) return; if (request.sample_mode == Line_Sampling_Strategy::Pixel_Min_Max_Envelope) { - points = Curve_Utils::build_pixel_envelope_points(request.mapping, request.data_range, static_cast(request.samples.size()), static_cast(request.samples.size()), request.visible_range_only, [&request](int i) { - return request.samples[static_cast(i)]; + points = Curve_Utils::build_pixel_envelope_points(request.mapping, request.data_range, static_cast(samples->values.size()), static_cast(samples->values.size()), request.visible_range_only, [samples](int i) { + return samples->values[static_cast(i)]; }, memory_resource(Memory_Domain::Curve)); return; } - points = Curve_Utils::build_resampled_points(request.mapping, request.data_range, static_cast(request.samples.size()), static_cast(request.samples.size()), request.visible_range_only, request.interpolation_mode, [&request](int i) { - return request.samples[static_cast(i)]; + points = Curve_Utils::build_resampled_points(request.mapping, request.data_range, static_cast(samples->values.size()), static_cast(samples->values.size()), request.visible_range_only, request.interpolation_mode, [samples](int i) { + return samples->values[static_cast(i)]; }, memory_resource(Memory_Domain::Curve)); } struct Curve_Private : Typed_Render_Data { static constexpr std::size_t Input_Queue_Capacity = 8; - std::pmr::vector samples{memory_resource(Memory_Domain::Curve)}; - std::pmr::vector data_points{memory_resource(Memory_Domain::Curve)}; - Curve_Input_Mode mode = Curve_Input_Mode::None; + std::shared_ptr source = std::make_shared(); rigtorp::MPMCQueue> input_queue; std::atomic_uint64_t dropped_input_count{0}; std::pmr::vector> pending_geometry_update_states{memory_resource(Memory_Domain::Update_Completion)}; @@ -195,16 +222,14 @@ struct Curve_Private : Typed_Render_Datamode; - samples = std::move(selected->samples); - data_points = std::move(selected->points); + source = std::move(selected->source); ++input_generation; if (selected->completion_state) { selected->completion_state->complete_until(Update_Stage::Committed); pending_geometry_update_states.push_back(std::move(selected->completion_state)); } } - void prepare_data(const Plot_Render_Snapshot& snapshot) override { + void prepare_data(const Render_Frame_Snapshot& snapshot) override { (void)snapshot; sync_state_pipeline(); consume_input_queue(); @@ -226,27 +251,26 @@ struct Curve_Private : Typed_Render_Dataversion; - key.mode = mode; + key.mode = source ? curve_source_mode(*source) : Curve_Input_Mode::None; key.sample_mode = s->sample_mode; key.interpolation_mode = s->interpolation_mode; key.visible_range_only = s->visible_range_only; key.data_range = s->data_range; - key.sample_count = samples.size(); - key.point_count = data_points.size(); + key.sample_count = source ? curve_sample_count(*source) : 0u; + key.point_count = source ? curve_point_count(*source) : 0u; key.domain = curve_axis_key(mapping.domain); key.value = curve_axis_key(mapping.value); if (submitted_geometry_key && key == *submitted_geometry_key) return; submitted_geometry_key = key; auto request = std::make_shared(); - request->mode = mode; + request->mode = key.mode; request->mapping = mapping; request->data_range = s->data_range; request->visible_range_only = s->visible_range_only; request->interpolation_mode = s->interpolation_mode; request->sample_mode = s->sample_mode; - request->samples = samples; - request->data_points = data_points; + request->source = source; request->update_states = std::move(pending_geometry_update_states); pending_geometry_update_states = std::pmr::vector>(memory_resource(Memory_Domain::Update_Completion)); geometry_result.emplace(); @@ -254,7 +278,7 @@ struct Curve_Private : Typed_Render_Dataupdate_states = std::move(request->update_states); } - void draw(Canvas& canvas, const Plot_Render_Snapshot& snapshot) override { + void draw(Canvas& canvas, const Render_Frame_Snapshot& snapshot) override { Curve_Render_State* s = render_state(); if (!geometry_result) return; @@ -361,7 +385,7 @@ std::shared_ptr Curve::Builder::build() { auto ret = renderive::make_shared(); ret->init(parent); Curve_Private* pd = ret->d(); - Curve_Render_State* sc = reinterpret_cast(pd->state(State_Edit)); + Curve_Render_State* sc = reinterpret_cast(pd->edit_state()); PROP_R(std::weak_ptr, domain_axis) PROP_R(std::weak_ptr, value_axis) PROP_R(Range, data_range) @@ -397,6 +421,8 @@ void Curve_Utils::draw_fill_to_value(Canvas* canvas, std::span poi void Curve_Utils::append_envelope_point(std::pmr::vector& points, const Axis_Mapping_2D& mapping, const Range& data_range, int total_count, int index, double value) { if (index < 0) return; + if (!std::isfinite(value)) + return; PointF point = mapping.map(source_index_to_coordinate(data_range, total_count, index), value); if (!points.empty() && points.back().x == point.x && points.back().y == point.y) return; diff --git a/Core/primitive/Curve_Utils_p.h b/Core/primitive/Curve_Utils_p.h index 0f4b35c..6517dea 100644 --- a/Core/primitive/Curve_Utils_p.h +++ b/Core/primitive/Curve_Utils_p.h @@ -32,35 +32,36 @@ public: } template static std::pmr::vector build_resampled_points(Abs_Axis* domain_axis, Abs_Axis* value_axis, const Range& sample_domain_range, int source_sample_count, int available_sample_count, bool visible_range_only, Line_Interpolation_Mode mode, Value_At value_at) { - (void)mode; - return build_sample_line_points(Axis_Render_Access::mapping(domain_axis, value_axis), sample_domain_range, source_sample_count, available_sample_count, visible_range_only, value_at, frame_memory_resource()); + return build_sample_line_points(Axis_Render_Access::mapping(domain_axis, value_axis), sample_domain_range, source_sample_count, available_sample_count, visible_range_only, mode, value_at, frame_memory_resource()); } template static std::pmr::vector build_resampled_points(const Axis_Mapping_2D& mapping, const Range& sample_domain_range, int source_sample_count, int available_sample_count, bool visible_range_only, Line_Interpolation_Mode mode, Value_At value_at, std::pmr::memory_resource* resource = frame_memory_resource()) { - (void)mode; - return build_sample_line_points(mapping, sample_domain_range, source_sample_count, available_sample_count, visible_range_only, value_at, resource); + return build_sample_line_points(mapping, sample_domain_range, source_sample_count, available_sample_count, visible_range_only, mode, value_at, resource); } template static std::pmr::vector build_pixel_envelope_points(const Axis_Mapping_2D& mapping, const Range& sample_domain_range, int source_sample_count, int available_sample_count, bool visible_range_only, Value_At value_at, std::pmr::memory_resource* resource = frame_memory_resource()) { Visible_Sample_Window window = compute_visible_sample_window(mapping.domain, sample_domain_range, source_sample_count, available_sample_count, visible_range_only); if (!window) return {}; - int first_source = std::clamp(static_cast(std::floor(std::min(window.first_index, window.last_index))), 0, available_sample_count - 1); - int last_source = std::clamp(static_cast(std::ceil(std::max(window.first_index, window.last_index))), first_source, available_sample_count - 1); - double first_pixel = mapping.domain.coord_to_pixel(source_index_to_coordinate(sample_domain_range, source_sample_count, window.first_index)); - double last_pixel = mapping.domain.coord_to_pixel(source_index_to_coordinate(sample_domain_range, source_sample_count, window.last_index)); - double pixel_min = std::min(first_pixel, last_pixel); - double pixel_max = std::max(first_pixel, last_pixel); - int bucket_count = std::max(1, static_cast(std::ceil(pixel_max - pixel_min)) + 1); + double first_index = std::clamp(std::min(window.first_index, window.last_index), 0.0, static_cast(available_sample_count - 1)); + double last_index = std::clamp(std::max(window.first_index, window.last_index), first_index, static_cast(available_sample_count - 1)); + int first_source = std::clamp(static_cast(std::ceil(first_index)), 0, available_sample_count - 1); + int last_source = std::clamp(static_cast(std::floor(last_index)), first_source, available_sample_count - 1); + double first_pixel = mapping.domain.coord_to_pixel(source_index_to_coordinate(sample_domain_range, source_sample_count, first_index)); + double last_pixel = mapping.domain.coord_to_pixel(source_index_to_coordinate(sample_domain_range, source_sample_count, last_index)); + int pixel_floor_min = static_cast(std::floor(std::min(first_pixel, last_pixel))); + int pixel_floor_max = static_cast(std::floor(std::max(first_pixel, last_pixel))); + int bucket_count = std::max(1, pixel_floor_max - pixel_floor_min + 1); bucket_count = std::min(bucket_count, mapping.domain.pixel_size() + 1); std::pmr::vector buckets(bucket_count, resource); - double pixel_span = std::max(1.0, pixel_max - pixel_min); for (int i = first_source; i <= last_source; ++i) { + double value = value_at(i); + if (!std::isfinite(value)) + continue; double coord = source_index_to_coordinate(sample_domain_range, source_sample_count, i); double pixel = mapping.domain.coord_to_pixel(coord); - int bucket_index = std::clamp(static_cast((pixel - pixel_min) * static_cast(bucket_count - 1) / pixel_span), 0, bucket_count - 1); + int bucket_index = std::clamp(static_cast(std::floor(pixel)) - pixel_floor_min, 0, bucket_count - 1); Pixel_Envelope_Bucket& bucket = buckets[bucket_index]; - double value = value_at(i); if (!bucket.has_samples) { bucket.has_samples = true; bucket.first_index = i; @@ -80,9 +81,25 @@ public: } } std::pmr::vector points(resource); - points.reserve(bucket_count * 4); - bool pixel_forward = first_pixel <= last_pixel; - for (int b = pixel_forward ? 0 : bucket_count - 1; pixel_forward ? b < bucket_count : b >= 0; pixel_forward ? ++b : --b) { + points.reserve(bucket_count * 4 + 2); + double last_appended_index = -std::numeric_limits::infinity(); + auto append_index = [&](double index) { + if (std::abs(index - last_appended_index) <= 1e-9) + return; + double value = interpolate_sample_value(index, available_sample_count, Line_Interpolation_Mode::Linear_Value, value_at); + if (!std::isfinite(value)) + return; + PointF point = mapping.map(source_index_to_coordinate(sample_domain_range, source_sample_count, index), value); + if (!points.empty() && points.back().x == point.x && points.back().y == point.y) + return; + points.push_back(point); + last_appended_index = index; + }; + append_index(first_index); + int first_bucket = std::clamp(static_cast(std::floor(first_pixel)) - pixel_floor_min, 0, bucket_count - 1); + int last_bucket = std::clamp(static_cast(std::floor(last_pixel)) - pixel_floor_min, 0, bucket_count - 1); + bool bucket_forward = first_bucket <= last_bucket; + for (int b = bucket_forward ? 0 : bucket_count - 1; bucket_forward ? b < bucket_count : b >= 0; bucket_forward ? ++b : --b) { const Pixel_Envelope_Bucket& bucket = buckets[b]; if (!bucket.has_samples) continue; @@ -90,8 +107,9 @@ public: std::sort(indexes.begin(), indexes.end()); auto end = std::unique(indexes.begin(), indexes.end()); for (auto it = indexes.begin(); it != end; ++it) - append_envelope_point(points, mapping, sample_domain_range, source_sample_count, *it, value_at(*it)); + append_index(static_cast(*it)); } + append_index(last_index); return points; } template diff --git a/Core/primitive/Fill_Area.cpp b/Core/primitive/Fill_Area.cpp index d765c0f..40c422b 100644 --- a/Core/primitive/Fill_Area.cpp +++ b/Core/primitive/Fill_Area.cpp @@ -28,7 +28,7 @@ struct Fill_Area_Input_Data : Latest_Input_Data { struct Fill_Area_Render_State : Render_State, Fill_Area_Prop {}; struct Fill_Area_Private : Typed_Render_Data { std::pmr::vector data_points{memory_resource(Memory_Domain::Curve)}; - void prepare_data(const Plot_Render_Snapshot&) override { + void prepare_data(const Render_Frame_Snapshot&) override { sync_state_pipeline(); Fill_Area_Input_Data* input = render_input_data(); input->read_all([this](std::pmr::vector& points) { @@ -36,7 +36,7 @@ struct Fill_Area_Private : Typed_Render_Datadomain_axis.lock(); auto value_axis = s->value_axis.lock(); @@ -103,7 +103,7 @@ std::shared_ptr Fill_Area::Builder::build() { auto ret = renderive::make_shared(); ret->init(parent); Fill_Area_Private* pd = ret->d(); - Fill_Area_Render_State* sc = reinterpret_cast(pd->state(State_Edit)); + Fill_Area_Render_State* sc = reinterpret_cast(pd->edit_state()); PROP_R(std::weak_ptr, domain_axis) PROP_R(std::weak_ptr, value_axis) PROP_R(double, baseline) diff --git a/Core/primitive/Grid.cpp b/Core/primitive/Grid.cpp index a8f2a91..a84ea13 100644 --- a/Core/primitive/Grid.cpp +++ b/Core/primitive/Grid.cpp @@ -53,7 +53,7 @@ struct Grid_Private : Typed_Render_Data { canvas.draw_line(PointF{cross0, pixel}, PointF{cross1, pixel}); } } - void draw(Canvas& canvas, const Plot_Render_Snapshot&) override { + void draw(Canvas& canvas, const Render_Frame_Snapshot&) override { Grid_Render_State* s = render_state(); auto domain_axis = s->domain_axis.lock(); auto value_axis = s->value_axis.lock(); @@ -180,7 +180,7 @@ std::shared_ptr Grid::Builder::build() { auto ret = renderive::make_shared(); ret->init(parent); Grid_Private* pd = ret->d(); - Grid_Render_State* sc = reinterpret_cast(pd->state(State_Edit)); + Grid_Render_State* sc = reinterpret_cast(pd->edit_state()); PROP_R(std::weak_ptr, domain_axis) PROP_R(std::weak_ptr, value_axis) PROP_R(bool, domain_grid) diff --git a/Core/primitive/Label.cpp b/Core/primitive/Label.cpp index 74c2588..b394840 100644 --- a/Core/primitive/Label.cpp +++ b/Core/primitive/Label.cpp @@ -22,7 +22,7 @@ struct Label_Input_Data : Latest_Input_Data { struct Label_Private : Typed_Render_Data { std::pmr::vector prepared_items{memory_resource(Memory_Domain::Other)}; bool use_prepared_items = false; - void prepare_data(const Plot_Render_Snapshot&) override { + void prepare_data(const Render_Frame_Snapshot&) override { sync_state_pipeline(); Label_Input_Data* input = render_input_data(); if (!input->pending) @@ -59,7 +59,7 @@ struct Label_Private : Typed_Render_Datadomain_axis.lock(); auto value_axis = s->value_axis.lock(); @@ -122,7 +122,7 @@ std::shared_ptr