diff --git a/Core/Axis/Abs_Axis.cpp b/Core/Axis/Abs_Axis.cpp index d64bc64..c32feba 100644 --- a/Core/Axis/Abs_Axis.cpp +++ b/Core/Axis/Abs_Axis.cpp @@ -28,7 +28,7 @@ double Abs_Axis::coord_to_pixel(double coord) { return d()->edit_coord_to_pixel(coord); } std::vector Abs_Axis_Private::create_tick_vector(double tick_step, const Range& range) { - double tick_origin = render_state()->coord_start; + double tick_origin = range.origin; std::vector result; auto first_step = static_cast(std::floor(std::abs((range.origin - tick_origin) / tick_step))); auto last_step = static_cast(std::ceil(std::abs(range.target - tick_origin) / tick_step)); diff --git a/Core/Axis/Abs_Axis_p.h b/Core/Axis/Abs_Axis_p.h index 5c2ed44..4f9b699 100644 --- a/Core/Axis/Abs_Axis_p.h +++ b/Core/Axis/Abs_Axis_p.h @@ -4,6 +4,7 @@ #include #include "../architecture/Render_Lease.h" #include "../render/Canvas.h" +#include "../render/Render_Frame_Snapshot.h" #include "Abs_Axis.h" #include "blend2d/blend2d.h" namespace renderive { @@ -16,38 +17,73 @@ struct Abs_Axis_Render_State : Render_State, Abs_Axis_Prop { } }; struct Abs_Axis_Input_Data : Input_Data {}; +struct Axis_Frame_Snapshot { + Orientation orientation = Orientation::Horizontal; + Range coord_range{0.0, 1.0}; + Range pixel_range{0.0, 1.0}; + [[nodiscard]] double coord_to_pixel(double coord) const { + if (coord_range.length() == 0.0) + return pixel_range.origin; + return pixel_range.origin + pixel_range.length() * (coord - coord_range.origin) / coord_range.length(); + } + [[nodiscard]] double pixel_to_coord(double pixel) const { + if (pixel_range.length() == 0.0) + return coord_range.origin; + return coord_range.origin + coord_range.length() * (pixel - pixel_range.origin) / pixel_range.length(); + } + [[nodiscard]] int pixel_size() const { + return static_cast(std::max(1.0, pixel_range.size())); + } +}; struct Abs_Axis_Private : Typed_Render_Data { int tick_count = 2; [[nodiscard]] Abs_Axis_Render_State* edit_axis_state() { return edit_state(); } [[nodiscard]] Abs_Axis_Render_State* render_axis_state() { - return render_state(); + return static_cast(state(Renderable_State_Role::Render)); + } + [[nodiscard]] Abs_Axis_Render_State* render_axis_state(const Renderable_Frame_View& frame_view) const { + return render_state(frame_view); } static int pixel_start_of(const Abs_Axis_Render_State* s) { return s->orientation == Orientation::Horizontal ? s->x : s->y; } + static Axis_Frame_Snapshot axis_frame_snapshot(const Abs_Axis_Render_State* s) { + if (!s) + return {}; + double pixel_start = static_cast(pixel_start_of(s)); + return Axis_Frame_Snapshot{ + s->orientation, + Range{s->coord_start, s->coord_start + s->coord_length}, + Range{pixel_start, pixel_start + static_cast(s->pixel_length)} + }; + } + virtual Axis_Frame_Snapshot frame_snapshot(const Render_Frame_Snapshot&, const Renderable_Frame_View& frame_view) { + return axis_frame_snapshot(render_axis_state(frame_view)); + } double get_mantissa(double input, double* magnitude = nullptr); [[nodiscard]] double clean_mantissa(double input); [[nodiscard]] double pick_closest(double target, const std::vector& candidates); - void prepare_data(const Render_Frame_Snapshot&) override { - sync_state_pipeline(); - } - void draw(Canvas& canvas, const Render_Frame_Snapshot&) override { - Abs_Axis_Render_State* s = render_axis_state(); + void prepare_data(const Render_Frame_Snapshot&, const Renderable_Frame_View&) override {} + void draw(Canvas& canvas, const Render_Frame_Snapshot& snapshot, const Renderable_Frame_View& frame_view) override { + Abs_Axis_Render_State* s = render_axis_state(frame_view); + if (!s) + return; + Axis_Frame_Snapshot axis_snapshot = frame_snapshot(snapshot, frame_view); const int pixel_start = pixel_start_of(s); - Range range = {s->coord_start, s->coord_start + s->coord_length}; + Range range = axis_snapshot.coord_range; double tick_step = get_tick_step(range); - std::vector ticks = create_tick_vector(tick_step, range); + std::vector ticks = create_frame_tick_vector(tick_step, range, s, snapshot, frame_view); while (!ticks.empty() && !range.contains(ticks.back())) { ticks.pop_back(); } int sub_tick_count = get_sub_tick_count(tick_step); - std::vector sub_ticks = create_sub_tick_vector(sub_tick_count, ticks, range); + std::vector sub_ticks = create_frame_sub_tick_vector(sub_tick_count, ticks, range, s, snapshot, frame_view); while (!sub_ticks.empty() && !range.contains(sub_ticks.back())) { sub_ticks.pop_back(); } - std::vector labels = render_label_vector(ticks); + std::vector labels = create_frame_label_vector(ticks, s, snapshot, frame_view); 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 +92,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 +103,7 @@ struct Abs_Axis_Private : Typed_Render_Datay}, {x, (double)(s->y + s->sub_tick_length)}); } } @@ -76,7 +112,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 +123,7 @@ struct Abs_Axis_Private : Typed_Render_Datax, y}, {(double)(s->x + s->sub_tick_length), y}); } } @@ -131,6 +167,18 @@ struct Abs_Axis_Private : Typed_Render_Data create_sub_tick_vector(int sub_tick_count, const std::vector& ticks, const Range& range); 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); + virtual std::vector create_frame_tick_vector(double tick_step, const Range& range, Abs_Axis_Render_State*, const Render_Frame_Snapshot&, const Renderable_Frame_View&) { + return create_tick_vector(tick_step, range); + } + virtual std::vector create_frame_sub_tick_vector(int sub_tick_count, const std::vector& ticks, const Range& range, Abs_Axis_Render_State*, const Render_Frame_Snapshot&, const Renderable_Frame_View&) { + return create_sub_tick_vector(sub_tick_count, ticks, range); + } + virtual std::vector create_frame_label_vector(const std::vector& ticks, Abs_Axis_Render_State* state, const Render_Frame_Snapshot&, const Renderable_Frame_View&) { + return create_label_vector(ticks, state); + } + virtual std::string frame_tick_label(double tick, const Render_Frame_Snapshot&, const Renderable_Frame_View& frame_view) { + return get_tick_label(tick, render_axis_state(frame_view)); + } [[nodiscard]] double edit_pixel_to_coord(double pixel) { return pixel_to_coord(pixel, edit_axis_state()); } @@ -167,24 +215,6 @@ struct Abs_Axis_Private : Typed_Render_Data(std::max(1.0, pixel_range.size())); - } -}; struct Axis_Mapping_2D { Axis_Frame_Snapshot domain; Axis_Frame_Snapshot value; @@ -228,18 +258,30 @@ struct Axis_Basis_2D { } }; struct Axis_Render_Access final { + static Axis_Frame_Snapshot snapshot_from_state(const Abs_Axis_Render_State* s) { + return Abs_Axis_Private::axis_frame_snapshot(s); + } static Axis_Frame_Snapshot snapshot(Abs_Axis* axis) { const auto* s = axis->d()->render_axis_state(); - double pixel_start = static_cast(Abs_Axis_Private::pixel_start_of(s)); - return Axis_Frame_Snapshot{ - s->orientation, - Range{s->coord_start, s->coord_start + s->coord_length}, - Range{pixel_start, pixel_start + static_cast(s->pixel_length)} - }; + return snapshot_from_state(s); + } + static const Abs_Axis_Render_State* frame_state(Abs_Axis* axis, const Render_Frame_Snapshot& frame_snapshot) { + const auto* node = frame_snapshot.frame_node_for(axis); + if (!node) + return nullptr; + return dynamic_cast(node->frame_view.state); + } + static Axis_Frame_Snapshot snapshot(Abs_Axis* axis, const Render_Frame_Snapshot& frame_snapshot) { + if (const auto* node = frame_snapshot.frame_node_for(axis)) + return axis->d()->frame_snapshot(frame_snapshot, node->frame_view); + return snapshot(axis); } static Axis_Mapping_2D mapping(Abs_Axis* domain_axis, Abs_Axis* value_axis) { return Axis_Mapping_2D{snapshot(domain_axis), snapshot(value_axis)}; } + static Axis_Mapping_2D mapping(Abs_Axis* domain_axis, Abs_Axis* value_axis, const Render_Frame_Snapshot& frame_snapshot) { + return Axis_Mapping_2D{snapshot(domain_axis, frame_snapshot), snapshot(value_axis, frame_snapshot)}; + } static bool orientations_orthogonal(Abs_Axis* domain_axis, Abs_Axis* value_axis) { return domain_axis && value_axis && orientation(domain_axis) != orientation(value_axis); } @@ -252,15 +294,42 @@ struct Axis_Render_Access final { static Range coord_range(Abs_Axis* axis) { return axis->d()->render_coord_range(); } + static Range coord_range(Abs_Axis* axis, const Render_Frame_Snapshot& frame_snapshot) { + Axis_Frame_Snapshot s = snapshot(axis, frame_snapshot); + return s.coord_range; + } static int pixel_size(Abs_Axis* axis) { return axis->d()->render_pixel_size(); } + static int pixel_size(Abs_Axis* axis, const Render_Frame_Snapshot& frame_snapshot) { + return snapshot(axis, frame_snapshot).pixel_size(); + } static Orientation orientation(Abs_Axis* axis) { return axis->d()->render_axis_state()->orientation; } + static Orientation orientation(Abs_Axis* axis, const Render_Frame_Snapshot& frame_snapshot) { + return snapshot(axis, frame_snapshot).orientation; + } static std::string tick_label(Abs_Axis* axis, double tick) { return axis->d()->render_tick_label(tick); } + static std::string tick_label(Abs_Axis* axis, double tick, const Render_Frame_Snapshot& frame_snapshot) { + if (axis) { + if (const auto* node = frame_snapshot.frame_node_for(axis)) + return axis->d()->frame_tick_label(tick, frame_snapshot, node->frame_view); + } + return tick_label(axis, tick); + } + static std::string unit_text(Abs_Axis* axis, const Render_Frame_Snapshot& frame_snapshot) { + if (const auto* s = frame_state(axis, frame_snapshot)) + return s->unit_text; + return axis ? axis->unit_text() : std::string{}; + } + static Font unit_text_font(Abs_Axis* axis, const Render_Frame_Snapshot& frame_snapshot) { + if (const auto* s = frame_state(axis, frame_snapshot)) + return s->unit_text_font; + return axis ? axis->unit_text_font() : Font{}; + } }; template void Abs_Axis::Builder_T::set(Abs_Axis* ret) { diff --git a/Core/Axis/Time_Axis.cpp b/Core/Axis/Time_Axis.cpp index 6fb8103..65d1950 100644 --- a/Core/Axis/Time_Axis.cpp +++ b/Core/Axis/Time_Axis.cpp @@ -55,7 +55,7 @@ Time_Of_Day Time_Axis::tick_to_time(int tick) { } if (stream) return stream->capture(point_count, interval_hint, newest_at_start).time_by_tick(tick); - return d()->time_by_tick(tick); + return {}; } Time_Axis::Builder::Builder(std::shared_ptr parent, Orientation orientation) { ASSERT(parent && parent->attached_to_plot(), "Time_Axis build error! parent invalid"); diff --git a/Core/Axis/Time_Axis_p.h b/Core/Axis/Time_Axis_p.h index d21fccb..7e14b88 100644 --- a/Core/Axis/Time_Axis_p.h +++ b/Core/Axis/Time_Axis_p.h @@ -21,60 +21,81 @@ struct Time_Axis_Render_State : Abs_Axis_Render_State, Time_Axis_Prop { } }; struct Time_Axis_Private : Typed_Render_Data_Base { - std::shared_ptr timeline_snapshot; - std::vector ticks; - Time_Of_Day time_by_tick(int tick) const { - return timeline_snapshot ? timeline_snapshot->time_by_tick(tick) : Time_Of_Day{}; - } 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 Render_Frame_Snapshot& snapshot) { - sync_state_pipeline(); - return capture_timeline(snapshot, render_state()); + std::shared_ptr capture_timeline(const Render_Frame_Snapshot& snapshot, const Renderable_Frame_View& frame_view) { + return capture_timeline(snapshot, render_state(frame_view)); } - void prepare_data(const Render_Frame_Snapshot& snapshot) override { - sync_state_pipeline(); - auto s = render_state(); + void prepare_data(const Render_Frame_Snapshot& snapshot, const Renderable_Frame_View& frame_view) override { + (void)capture_timeline(snapshot, frame_view); + } + Axis_Frame_Snapshot frame_snapshot(const Render_Frame_Snapshot& snapshot, const Renderable_Frame_View& frame_view) override { + auto s = render_state(frame_view); + Axis_Frame_Snapshot result = axis_frame_snapshot(s); auto captured = capture_timeline(snapshot, s); if (captured) - timeline_snapshot = std::move(captured); - if (!timeline_snapshot) - return; - s->coord_start = timeline_snapshot->coordinate_begin; - s->coord_length = timeline_snapshot->visible_time_point_count; - ticks.clear(); - for (const Timeline_Label_Tick& tick : timeline_snapshot->ticks) { - ticks.push_back(tick.coordinate); - } + result.coord_range = Range{static_cast(captured->coordinate_begin), static_cast(captured->coordinate_begin + captured->visible_time_point_count)}; + return result; } 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) - result.push_back(format_time(time_by_tick((int)tick), s->time_format)); + result.push_back(format_time(Time_Of_Day{}, s->time_format)); return result; } std::vector create_tick_vector(double tick_step, const Range& range) override { - return ticks; + return Abs_Axis_Private::create_tick_vector(tick_step, range); } std::vector create_sub_tick_vector(int sub_tick_count, const std::vector& ticks, const Range& range) override { return {}; } - void draw(Canvas& canvas, const Render_Frame_Snapshot& snapshot) override { - Abs_Axis_Private::draw(canvas, snapshot); + std::vector create_frame_tick_vector(double, const Range&, Abs_Axis_Render_State* state, const Render_Frame_Snapshot& snapshot, const Renderable_Frame_View&) override { + std::vector result; + auto captured = capture_timeline(snapshot, static_cast(state)); + if (!captured) + return result; + result.reserve(captured->ticks.size()); + for (const Timeline_Label_Tick& tick : captured->ticks) + result.push_back(tick.coordinate); + return result; + } + std::vector create_frame_sub_tick_vector(int, const std::vector&, const Range&, Abs_Axis_Render_State*, const Render_Frame_Snapshot&, const Renderable_Frame_View&) override { + return {}; + } + std::vector create_frame_label_vector(const std::vector& ticks, Abs_Axis_Render_State* state, const Render_Frame_Snapshot& snapshot, const Renderable_Frame_View&) override { + auto s = static_cast(state); + auto captured = capture_timeline(snapshot, s); + std::vector result; + result.reserve(ticks.size()); + for (double tick : ticks) { + Time_Of_Day time = captured ? captured->time_by_tick(static_cast(tick)) : Time_Of_Day{}; + result.push_back(format_time(time, s->time_format)); + } + return result; + } + void draw(Canvas& canvas, const Render_Frame_Snapshot& snapshot, const Renderable_Frame_View& frame_view) override { + Abs_Axis_Private::draw(canvas, snapshot, frame_view); } 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); + return format_time(Time_Of_Day{}, render_state->time_format); + } + std::string frame_tick_label(double tick, const Render_Frame_Snapshot& snapshot, const Renderable_Frame_View& frame_view) override { + auto s = render_state(frame_view); + auto captured = capture_timeline(snapshot, s); + Time_Of_Day time = captured ? captured->time_by_tick(static_cast(tick)) : Time_Of_Day{}; + return format_time(time, s ? s->time_format : std::string{}); } }; struct Time_Axis_Render_Access final { static std::shared_ptr capture_timeline(Time_Axis* axis, const Render_Frame_Snapshot& snapshot) { - return axis ? axis->d()->capture_timeline(snapshot) : std::shared_ptr{}; + const auto* node = axis ? snapshot.frame_node_for(axis) : nullptr; + return axis && node ? axis->d()->capture_timeline(snapshot, node->frame_view) : std::shared_ptr{}; } }; } diff --git a/Core/architecture/Frame_Scheduler.h b/Core/architecture/Frame_Scheduler.h index 7d5b3d6..dbedfd8 100644 --- a/Core/architecture/Frame_Scheduler.h +++ b/Core/architecture/Frame_Scheduler.h @@ -16,8 +16,13 @@ struct Frame_Lifecycle_Observer { struct Refresh_Control_Snapshot { Refresh_Limit_Source source = Refresh_Limit_Source::Unknown; + Refresh_Limit_Source candidate_source = Refresh_Limit_Source::Unknown; Low_Latency_Bottleneck_State bottleneck = Low_Latency_Bottleneck_State::Unknown; + Low_Latency_Bottleneck_State stable_bottleneck = Low_Latency_Bottleneck_State::Unknown; + Low_Latency_Bottleneck_State candidate_bottleneck = Low_Latency_Bottleneck_State::Unknown; Low_Latency_Production_Strategy production_strategy = Low_Latency_Production_Strategy::Latency_Eager; + std::uint64_t candidate_age_ns{}; + std::uint64_t candidate_samples{}; std::uint64_t input_period_ns{}; std::uint64_t input_jitter_ns{}; std::uint64_t render_period_ns{}; @@ -34,6 +39,7 @@ struct Refresh_Control_Snapshot { std::uint64_t frame_age_jitter_ns{}; std::uint64_t user_period_ns{}; std::uint64_t target_period_ns{}; + std::uint64_t effective_min_render_interval_ns{}; std::uint64_t presented_frame_count{}; std::uint64_t superseded_frame_count{}; std::uint64_t repeated_frame_count{}; diff --git a/Core/architecture/Render_Config.h b/Core/architecture/Render_Config.h index dcab07c..461d6fd 100644 --- a/Core/architecture/Render_Config.h +++ b/Core/architecture/Render_Config.h @@ -3,10 +3,6 @@ #include namespace renderive { using Plot_Execution_Id = std::uint64_t; -enum class Renderable_Runtime_Strategy : std::uint8_t { - Low_Latency, - Explicit -}; enum class Dirty_Reason : std::uint8_t { Core_Data, Config, @@ -58,6 +54,7 @@ struct Frame_Worker_Stats { }; enum class Refresh_Limit_Source : std::uint8_t { Unknown, + Input, Render, Paint, User diff --git a/Core/architecture/Render_Data.h b/Core/architecture/Render_Data.h index 4b5104a..01f023e 100644 --- a/Core/architecture/Render_Data.h +++ b/Core/architecture/Render_Data.h @@ -12,6 +12,7 @@ #include "global.h" #include #include +#include #include #include #include @@ -21,7 +22,8 @@ namespace renderive { class Canvas; struct Render_Frame_Snapshot; -struct Renderable; +class Renderable; +class Renderable_Runtime; struct Input_Data { virtual ~Input_Data() = default; @@ -148,17 +150,25 @@ struct Render_State_Read_Handle { virtual ~Render_State_Read_Handle() = default; }; -struct Render_State_Edit_Handle { - virtual ~Render_State_Edit_Handle() = default; +struct Render_State_Write_Handle { + virtual ~Render_State_Write_Handle() = default; }; -struct Render_Input_Edit_Handle { - virtual ~Render_Input_Edit_Handle() = default; +struct Render_Input_Write_Handle { + virtual ~Render_Input_Write_Handle() = default; virtual void set_completion(std::shared_ptr state) { (void)state; } }; +struct Render_Input_Frame_Handle { + virtual ~Render_Input_Frame_Handle() = default; + virtual void consume_input(Input_Data* input) { + if (input) + input->clear(); + } +}; + struct Render_State_Read_Guard : Move_Only { Render_State_Read_Guard() = default; Render_State_Read_Guard(Render_State* state, std::unique_ptr handle) @@ -175,36 +185,36 @@ struct Render_State_Read_Guard : Move_Only { std::unique_ptr handle; }; -struct Render_State_Edit_Guard : Move_Only { - Render_State_Edit_Guard() = default; - Render_State_Edit_Guard(Render_State* state, std::unique_ptr handle) +struct Render_State_Write_Guard : Move_Only { + Render_State_Write_Guard() = default; + Render_State_Write_Guard(Render_State* state, std::unique_ptr handle) : state(state), handle(std::move(handle)) {} - Render_State_Edit_Guard(Render_State_Edit_Guard&&) noexcept = default; - Render_State_Edit_Guard& operator=(Render_State_Edit_Guard&&) noexcept = default; - ~Render_State_Edit_Guard() = default; + Render_State_Write_Guard(Render_State_Write_Guard&&) noexcept = default; + Render_State_Write_Guard& operator=(Render_State_Write_Guard&&) noexcept = default; + ~Render_State_Write_Guard() = default; explicit operator bool() const noexcept { return state && handle; } Render_State* state{}; - std::unique_ptr handle; + std::unique_ptr handle; }; -struct Render_Input_Edit_Guard : Move_Only { - Render_Input_Edit_Guard() = default; - Render_Input_Edit_Guard(Input_Data* input, std::unique_ptr handle) +struct Render_Input_Write_Guard : Move_Only { + Render_Input_Write_Guard() = default; + Render_Input_Write_Guard(Input_Data* input, std::unique_ptr handle) : input(input), handle(std::move(handle)) {} - Render_Input_Edit_Guard(Render_Input_Edit_Guard&&) noexcept = default; - Render_Input_Edit_Guard& operator=(Render_Input_Edit_Guard&&) noexcept = default; - ~Render_Input_Edit_Guard() = default; + Render_Input_Write_Guard(Render_Input_Write_Guard&&) noexcept = default; + Render_Input_Write_Guard& operator=(Render_Input_Write_Guard&&) noexcept = default; + ~Render_Input_Write_Guard() = default; explicit operator bool() const noexcept { return input && handle; } Input_Data* input{}; - std::unique_ptr handle; + std::unique_ptr handle; void set_completion(std::shared_ptr state) { if (handle) @@ -212,6 +222,27 @@ struct Render_Input_Edit_Guard : Move_Only { } }; +struct Render_Input_Frame_Guard : Move_Only { + Render_Input_Frame_Guard() = default; + Render_Input_Frame_Guard(Input_Data* input, std::unique_ptr handle) + : input(input), handle(std::move(handle)) {} + Render_Input_Frame_Guard(Render_Input_Frame_Guard&&) noexcept = default; + Render_Input_Frame_Guard& operator=(Render_Input_Frame_Guard&&) noexcept = default; + ~Render_Input_Frame_Guard() = default; + + explicit operator bool() const noexcept { + return input && handle; + } + + Input_Data* input{}; + std::unique_ptr handle; + + void consume_input() const { + if (handle) + handle->consume_input(input); + } +}; + enum class Renderable_State_Role : std::uint8_t { Edit, Ready, @@ -224,20 +255,62 @@ enum class Renderable_Input_Role : std::uint8_t { Render }; -struct Render_Data { - Renderable* q_ptr{}; +struct Renderable_Frame_View : Move_Only { + Renderable_Frame_View() = default; + Renderable_Frame_View(Render_State_Read_Guard state_guard, Render_Input_Frame_Guard input_guard) + : state(state_guard.state), + input(input_guard.input), + state_guard(std::move(state_guard)), + input_guard(std::move(input_guard)) {} + Renderable_Frame_View(Renderable_Frame_View&&) noexcept = default; + Renderable_Frame_View& operator=(Renderable_Frame_View&&) noexcept = default; + ~Renderable_Frame_View() = default; - virtual void initialize_runtime(Renderable& owner, Renderable_Runtime_Strategy strategy) = 0; - [[nodiscard]] virtual Renderable_Runtime_Strategy runtime_strategy() const = 0; + explicit operator bool() const noexcept { + return state && state_guard; + } + + Render_State* state{}; + Input_Data* input{}; + Render_State_Read_Guard state_guard; + Render_Input_Frame_Guard input_guard; + + void consume_input() const { + input_guard.consume_input(); + } +}; + +class Renderable_Runtime { +public: + using Copy_Render_State_Function = std::function; + + virtual ~Renderable_Runtime() = default; + virtual void set_render_state_copier(Copy_Render_State_Function copier) = 0; virtual void cancel_pending_completions() = 0; virtual void drain_committed_update_states(std::pmr::vector>& output) = 0; virtual Render_State_Read_Guard acquire_state_read(Renderable_State_Role role) = 0; virtual Render_State_Read_Guard acquire_render_state_read() = 0; virtual Render_State_Read_Guard acquire_edit_state_read() = 0; - virtual Render_State_Read_Guard acquire_prepare_state_read() = 0; - virtual Render_State_Edit_Guard acquire_edit_state() = 0; - virtual Render_Input_Edit_Guard acquire_edit_input(bool notify_render) = 0; - virtual void sync_state_pipeline() = 0; + virtual Render_State_Write_Guard acquire_edit_state() = 0; + virtual Render_Input_Write_Guard acquire_edit_input(bool notify_render) = 0; + virtual Renderable_Frame_View capture_frame_view() = 0; + virtual Render_State* state(Renderable_State_Role role) = 0; + virtual Input_Data* input_data(Renderable_Input_Role role) = 0; + virtual void clear_render_input() = 0; +}; + +struct Render_Data { + Renderable* q_ptr{}; + + virtual void initialize_runtime(std::unique_ptr runtime) = 0; + virtual void cancel_pending_completions() = 0; + virtual void drain_committed_update_states(std::pmr::vector>& output) = 0; + virtual Render_State_Read_Guard acquire_state_read(Renderable_State_Role role) = 0; + virtual Render_State_Read_Guard acquire_render_state_read() = 0; + virtual Render_State_Read_Guard acquire_edit_state_read() = 0; + virtual Render_State_Write_Guard acquire_edit_state() = 0; + virtual Render_Input_Write_Guard acquire_edit_input(bool notify_render) = 0; + virtual Renderable_Frame_View capture_frame_view() = 0; virtual Render_State* state(Renderable_State_Role role) = 0; virtual Render_State* render_state() = 0; virtual Render_State* ready_state() = 0; @@ -246,10 +319,8 @@ struct Render_Data { virtual Input_Data* render_input_data() = 0; virtual Input_Data* edit_input_data() = 0; virtual ~Render_Data() = default; - virtual void draw(Canvas& canvas, const Render_Frame_Snapshot& snapshot) {} - virtual void prepare_data(const Render_Frame_Snapshot& snapshot) { - sync_state_pipeline(); - } + virtual void draw(Canvas& canvas, const Render_Frame_Snapshot& snapshot, const Renderable_Frame_View& frame_view) {} + virtual void prepare_data(const Render_Frame_Snapshot& snapshot, const Renderable_Frame_View& frame_view) {} virtual Rect pixel_bounds(const Size& plot_size) { return {0, 0, plot_size.width, plot_size.height}; } @@ -260,10 +331,7 @@ protected: }; struct Base_Render_Data : Render_Data { - void initialize_runtime(Renderable&, Renderable_Runtime_Strategy) override {} - [[nodiscard]] Renderable_Runtime_Strategy runtime_strategy() const override { - return Renderable_Runtime_Strategy::Explicit; - } + void initialize_runtime(std::unique_ptr) override {} void cancel_pending_completions() override {} void drain_committed_update_states(std::pmr::vector>&) override {} Render_State_Read_Guard acquire_state_read(Renderable_State_Role) override { @@ -275,13 +343,10 @@ struct Base_Render_Data : Render_Data { Render_State_Read_Guard acquire_edit_state_read() override { return {}; } - Render_State_Read_Guard acquire_prepare_state_read() override { + Render_State_Write_Guard acquire_edit_state() override { return {}; } - Render_State_Edit_Guard acquire_edit_state() override { - return {}; - } - Render_Input_Edit_Guard acquire_edit_input(bool) override { + Render_Input_Write_Guard acquire_edit_input(bool) override { return {}; } Render_State* state(Renderable_State_Role) override { @@ -305,7 +370,9 @@ struct Base_Render_Data : Render_Data { Input_Data* input_data(Renderable_Input_Role) override { return nullptr; } - void sync_state_pipeline() override {} + Renderable_Frame_View capture_frame_view() override { + return {}; + } }; template diff --git a/Core/architecture/Render_Lease.h b/Core/architecture/Render_Lease.h index 3c2e95a..b33781e 100644 --- a/Core/architecture/Render_Lease.h +++ b/Core/architecture/Render_Lease.h @@ -26,6 +26,12 @@ struct Typed_Render_Data_Base : Base { Input* edit_input_data() override { return reinterpret_cast(this->input_data(Renderable_Input_Role::Edit)); } + State* render_state(const Renderable_Frame_View& frame_view) const { + return reinterpret_cast(frame_view.state); + } + Input* render_input_data(const Renderable_Frame_View& frame_view) const { + return reinterpret_cast(frame_view.input); + } State* state_data(Renderable_State_Role role) { return reinterpret_cast(this->state(role)); } @@ -152,7 +158,7 @@ struct Render_Edit_Lease : Immovable { return state && guard; } Data* data{}; - Render_State_Edit_Guard guard; + Render_State_Write_Guard guard; State* state{}; }; @@ -178,7 +184,7 @@ struct Render_Input_Lease : Immovable { } Data* data{}; bool notify_render{}; - Render_Input_Edit_Guard guard; + Render_Input_Write_Guard guard; Input* input{}; }; diff --git a/Core/architecture/Renderable.cpp b/Core/architecture/Renderable.cpp index 1e5113e..013a8ee 100644 --- a/Core/architecture/Renderable.cpp +++ b/Core/architecture/Renderable.cpp @@ -126,7 +126,7 @@ void Renderable::init_common(Plot_Core* plot) { d_ptr = d_owner.get(); } d_ptr->q_ptr = this; - d_ptr->initialize_runtime(*this, plot->renderable_runtime_strategy()); + d_ptr->initialize_runtime(plot->create_renderable_runtime(*this)); plot->notify_model_dirty(Dirty_Reason::Config); } void Renderable::add_child(const std::shared_ptr& child) { @@ -176,18 +176,20 @@ void Renderable::append_tree_snapshot( int cache_tree_depth) { std::size_t index = snapshot.size(); Renderable_Cache_Mode mode = get_cache_mode(); - snapshot.push_back(Renderable_Frame_Node{ - shared_from_this(), - index + 1, - node_id_, - parent_node_id, - parent_object_name, - render_tree_depth, - object_name_, - cache_parent_node_id, - cache_parent_object_name, - cache_tree_depth - }); + Renderable_Frame_Node node; + node.renderable = shared_from_this(); + node.subtree_end = index + 1; + node.node_id = node_id_; + node.parent_node_id = parent_node_id; + node.parent_object_name = parent_object_name; + node.render_tree_depth = render_tree_depth; + node.object_name = object_name_; + node.cache_parent_node_id = cache_parent_node_id; + node.cache_parent_object_name = cache_parent_object_name; + node.cache_tree_depth = cache_tree_depth; + if (d_ptr && should_prepare()) + node.frame_view = d_ptr->capture_frame_view(); + snapshot.push_back(std::move(node)); std::string child_cache_parent = cache_parent_object_name; std::uint64_t child_cache_parent_id = cache_parent_node_id; int child_cache_depth = cache_tree_depth; @@ -376,12 +378,17 @@ void Renderable::render_self(Canvas& canvas, const Render_Frame_Snapshot& snapsh void Renderable::render_self(Canvas& canvas, const Render_Frame_Snapshot& snapshot, const Renderable_Frame_Node* node) { if (!d_ptr) return; - auto guard = d_ptr->acquire_render_state_read(); - if (!guard) + Renderable_Frame_View local_frame_view; + const Renderable_Frame_View* frame_view = node ? &node->frame_view : nullptr; + if (!frame_view) { + local_frame_view = d_ptr->capture_frame_view(); + frame_view = &local_frame_view; + } + if (!*frame_view) return; Renderable_Cache_Mode mode = get_cache_mode(); std::uint64_t begin_time = steady_now_ns(); - draw(canvas, snapshot); + draw(canvas, snapshot, *frame_view); std::uint64_t draw_time = steady_now_ns() - begin_time; if (snapshot.frame_metadata) { const std::string& parent_name = node ? node->parent_object_name : render_parent_object_name(); @@ -389,16 +396,17 @@ void Renderable::render_self(Canvas& canvas, const Render_Frame_Snapshot& snapsh snapshot.frame_metadata->record_renderable_self_draw(node ? node->node_id : node_id_, node ? node->parent_node_id : 0, node ? node->object_name : object_name_, parent_name, depth, mode, draw_time); } } -void Renderable::draw(Canvas& canvas, const Render_Frame_Snapshot& snapshot) { +void Renderable::draw(Canvas& canvas, const Render_Frame_Snapshot& snapshot, const Renderable_Frame_View& frame_view) { if (get_retiring()) return; - d_ptr->draw(canvas, snapshot); + d_ptr->draw(canvas, snapshot, frame_view); } -void Renderable::prepare_data(const Render_Frame_Snapshot& snapshot) { +void Renderable::prepare_data(const Render_Frame_Snapshot& snapshot, const Renderable_Frame_View& frame_view) { if (get_retiring()) return; - auto prepare_guard = d_ptr->acquire_prepare_state_read(); - d_ptr->prepare_data(snapshot); + if (!frame_view) + return; + d_ptr->prepare_data(snapshot, frame_view); if (snapshot.frame_update_states) d_ptr->drain_committed_update_states(*snapshot.frame_update_states); } diff --git a/Core/architecture/Renderable.h b/Core/architecture/Renderable.h index c8c5a81..1c1b99b 100644 --- a/Core/architecture/Renderable.h +++ b/Core/architecture/Renderable.h @@ -56,8 +56,8 @@ public: 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); + void draw(Canvas& canvas, const Render_Frame_Snapshot& snapshot, const Renderable_Frame_View& frame_view); + void prepare_data(const Render_Frame_Snapshot& snapshot, const Renderable_Frame_View& frame_view); bool select_test(const PointF& pos); void plot_event(const Event& event); void mouse_press_event(const Pointer_Event& event); diff --git a/Core/architecture/Strategy_Render_Data.cpp b/Core/architecture/Strategy_Render_Data.cpp new file mode 100644 index 0000000..43f8582 --- /dev/null +++ b/Core/architecture/Strategy_Render_Data.cpp @@ -0,0 +1,65 @@ +#include "Strategy_Render_Data.h" +#include + +namespace renderive { + +Strategy_Render_Data::Strategy_Render_Data() = default; +Strategy_Render_Data::~Strategy_Render_Data() = default; + +void Strategy_Render_Data::initialize_runtime(std::unique_ptr value) { + runtime = std::move(value); + if (!runtime) + return; + runtime->set_render_state_copier([this](Render_State& target, const Render_State& source) { + copy_render_state_object(target, source); + }); +} + +void Strategy_Render_Data::cancel_pending_completions() { + if (runtime) + runtime->cancel_pending_completions(); +} + +void Strategy_Render_Data::drain_committed_update_states(std::pmr::vector>& output) { + if (runtime) + runtime->drain_committed_update_states(output); +} + +Render_State_Read_Guard Strategy_Render_Data::acquire_state_read(Renderable_State_Role role) { + return runtime ? runtime->acquire_state_read(role) : Render_State_Read_Guard{}; +} + +Render_State_Read_Guard Strategy_Render_Data::acquire_render_state_read() { + return runtime ? runtime->acquire_render_state_read() : Render_State_Read_Guard{}; +} + +Render_State_Read_Guard Strategy_Render_Data::acquire_edit_state_read() { + return runtime ? runtime->acquire_edit_state_read() : Render_State_Read_Guard{}; +} + +Render_State_Write_Guard Strategy_Render_Data::acquire_edit_state() { + return runtime ? runtime->acquire_edit_state() : Render_State_Write_Guard{}; +} + +Render_Input_Write_Guard Strategy_Render_Data::acquire_edit_input(bool notify_render) { + return runtime ? runtime->acquire_edit_input(notify_render) : Render_Input_Write_Guard{}; +} + +Renderable_Frame_View Strategy_Render_Data::capture_frame_view() { + return runtime ? runtime->capture_frame_view() : Renderable_Frame_View{}; +} + +Render_State* Strategy_Render_Data::state(Renderable_State_Role role) { + return runtime ? runtime->state(role) : nullptr; +} + +Input_Data* Strategy_Render_Data::input_data(Renderable_Input_Role role) { + return runtime ? runtime->input_data(role) : nullptr; +} + +void Strategy_Render_Data::clear_render_input() { + if (runtime) + runtime->clear_render_input(); +} + +} // namespace renderive diff --git a/Core/architecture/Strategy_Render_Data.h b/Core/architecture/Strategy_Render_Data.h index 3eb5e12..e88a7ca 100644 --- a/Core/architecture/Strategy_Render_Data.h +++ b/Core/architecture/Strategy_Render_Data.h @@ -9,31 +9,27 @@ namespace renderive { -struct Strategy_Render_Data_Private; - struct LIB_DECL Strategy_Render_Data : Render_Data { Strategy_Render_Data(); Strategy_Render_Data(const Strategy_Render_Data&) = delete; Strategy_Render_Data& operator=(const Strategy_Render_Data&) = delete; ~Strategy_Render_Data() override; - void initialize_runtime(Renderable& owner, Renderable_Runtime_Strategy strategy) override; - [[nodiscard]] Renderable_Runtime_Strategy runtime_strategy() const override; + void initialize_runtime(std::unique_ptr runtime) override; void cancel_pending_completions() override; void drain_committed_update_states(std::pmr::vector>& output) override; Render_State_Read_Guard acquire_state_read(Renderable_State_Role role) override; Render_State_Read_Guard acquire_render_state_read() override; Render_State_Read_Guard acquire_edit_state_read() override; - Render_State_Read_Guard acquire_prepare_state_read() override; - Render_State_Edit_Guard acquire_edit_state() override; - Render_Input_Edit_Guard acquire_edit_input(bool notify_render) override; - void sync_state_pipeline() override; + Render_State_Write_Guard acquire_edit_state() override; + Render_Input_Write_Guard acquire_edit_input(bool notify_render) override; + Renderable_Frame_View capture_frame_view() override; Render_State* state(Renderable_State_Role role) override; Input_Data* input_data(Renderable_Input_Role role) override; void clear_render_input(); private: - std::unique_ptr runtime; + std::unique_ptr runtime; }; } // namespace renderive diff --git a/Core/flow/Frame_Flow.h b/Core/flow/Frame_Flow.h index 8eb952d..a0f3d06 100644 --- a/Core/flow/Frame_Flow.h +++ b/Core/flow/Frame_Flow.h @@ -13,12 +13,15 @@ namespace renderive { +class Renderable_Runtime; +class Renderable; + class Frame_Flow { public: virtual ~Frame_Flow() = default; virtual void attach(Frame_Flow_Host& host) = 0; - [[nodiscard]] virtual Renderable_Runtime_Strategy renderable_runtime_strategy() const = 0; + [[nodiscard]] virtual std::unique_ptr create_renderable_runtime(Renderable& owner) const = 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; diff --git a/Core/flow/explicit/Explicit_Frame_Flow.cpp b/Core/flow/explicit/Explicit_Frame_Flow.cpp index efa207d..ec9de0a 100644 --- a/Core/flow/explicit/Explicit_Frame_Flow.cpp +++ b/Core/flow/explicit/Explicit_Frame_Flow.cpp @@ -1,4 +1,5 @@ #include "Explicit_Frame_Flow.h" +#include "Explicit_Renderable_Runtime.h" #include "../../architecture/Render_Time.h" #include "../../architecture/Update_Completion.h" @@ -11,13 +12,13 @@ struct Explicit_Render_Surface_Handle final : Render_Frame_Surface_Handle { }; struct Explicit_Present_Surface_Handle final { - explicit Explicit_Present_Surface_Handle(std::shared_ptr output) : output_value(std::move(output)) {} + explicit Explicit_Present_Surface_Handle(std::shared_ptr output) : output_value(std::move(output)) {} [[nodiscard]] Render_Output* output() const { - return output_value.get(); + return const_cast(output_value.get()); } - std::shared_ptr output_value; + std::shared_ptr output_value; }; Explicit_Render_Surface_Handle* explicit_handle(Render_Frame_Surface& surface) { @@ -42,8 +43,8 @@ void Explicit_Frame_Flow::attach(Frame_Flow_Host& host_value) { host = &host_value; } -Renderable_Runtime_Strategy Explicit_Frame_Flow::renderable_runtime_strategy() const { - return Renderable_Runtime_Strategy::Explicit; +std::unique_ptr Explicit_Frame_Flow::create_renderable_runtime(Renderable& owner) const { + return make_explicit_renderable_runtime(owner); } Frame_Flow_Runtime_Snapshot Explicit_Frame_Flow::runtime_snapshot(std::uint64_t now_ns, bool destroying) const { @@ -52,7 +53,7 @@ Frame_Flow_Runtime_Snapshot Explicit_Frame_Flow::runtime_snapshot(std::uint64_t 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), + false, now_ns }; } @@ -70,13 +71,13 @@ void Explicit_Frame_Flow::cancel_all_update_states() { } pending_requests.clear(); } - std::shared_ptr output; + std::shared_ptr output; { std::lock_guard lock(surface_mutex); - output = std::move(present_surface); + output = present_surface; } if (output) - cancel_update_states(output->presented_update_states); + cancel_update_states(const_cast(output.get())->presented_update_states); } Render_Frame_Surface Explicit_Frame_Flow::begin_render_frame() { @@ -114,7 +115,7 @@ Render_Surface_Publish_Result Explicit_Frame_Flow::publish_rendered_frame(Render result.update_request_id = handle->output->metadata->update_request_id; result.update_request_time = now_ns; } - std::shared_ptr output = std::move(handle->output); + std::shared_ptr output = std::move(handle->output); Size size = output ? output->image.size() : Size{}; { std::lock_guard lock(surface_mutex); @@ -130,17 +131,18 @@ Render_Surface_Publish_Result Explicit_Frame_Flow::publish_rendered_frame(Render Render_Surface_Publish_Result Explicit_Frame_Flow::request_present(std::uint64_t now_ns) { Render_Surface_Publish_Result result; - std::shared_ptr output; + 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; + Render_Output* mutable_output = const_cast(output.get()); + if (mutable_output->metadata) { + mutable_output->metadata->update_request_id = ++next_present_request_id; + mutable_output->metadata->update_request_time_ns = now_ns; + result.update_request_id = mutable_output->metadata->update_request_id; result.update_request_time = now_ns; } paint_request_pending.store(true, std::memory_order_release); @@ -152,18 +154,19 @@ Render_Surface_Publish_Result Explicit_Frame_Flow::request_present(std::uint64_t 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::shared_ptr output; { std::lock_guard lock(surface_mutex); output = present_surface; } - if (!output || present_active.exchange(true, std::memory_order_acq_rel)) + if (!output) 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; + Render_Output* mutable_output = const_cast(output.get()); + if (is_new && mutable_output->metadata) { + mutable_output->metadata->transition_23_ns = now_ns; + mutable_output->metadata->present_queue_wait_ns = now_ns > mutable_output->metadata->transition_12_ns ? now_ns - mutable_output->metadata->transition_12_ns : 0; } return Present_Surface_Lease_Access::from_handle( is_new, @@ -184,7 +187,6 @@ std::shared_ptr Explicit_Frame_Flow::end_present(Present 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; } @@ -256,7 +258,6 @@ void Explicit_Frame_Flow::shutdown() { 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); } diff --git a/Core/flow/explicit/Explicit_Frame_Flow.h b/Core/flow/explicit/Explicit_Frame_Flow.h index 7c9c4ba..9055cf3 100644 --- a/Core/flow/explicit/Explicit_Frame_Flow.h +++ b/Core/flow/explicit/Explicit_Frame_Flow.h @@ -13,7 +13,7 @@ public: [[nodiscard]] Render_Ticket request_render(); void attach(Frame_Flow_Host& host) override; - [[nodiscard]] Renderable_Runtime_Strategy renderable_runtime_strategy() const override; + [[nodiscard]] std::unique_ptr create_renderable_runtime(Renderable& owner) const 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; @@ -48,12 +48,11 @@ private: 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::shared_ptr present_surface; std::atomic edit_version{1}; std::uint64_t next_present_request_id{}; }; diff --git a/Core/flow/explicit/Explicit_Renderable_Runtime.cpp b/Core/flow/explicit/Explicit_Renderable_Runtime.cpp new file mode 100644 index 0000000..4a68071 --- /dev/null +++ b/Core/flow/explicit/Explicit_Renderable_Runtime.cpp @@ -0,0 +1,236 @@ +#include "Explicit_Renderable_Runtime.h" +#include "../../architecture/Renderable.h" +#include "../../base/Memory.h" +#include +#include +#include + +namespace renderive { + +namespace { + +struct Explicit_Input_Slot { + Input_Data* data{}; + std::uint64_t version = 0; + bool pending = false; + std::shared_ptr completion_state; +}; + +} // namespace + +struct Explicit_Renderable_Runtime_Data { + Renderable* owner{}; + mutable std::mutex state_mutex; + mutable std::mutex input_mutex; + Render_Object_Owner state_owner; + Render_State* state{}; + Render_Object_Owner input_owner; + Explicit_Input_Slot input; + std::uint64_t input_version = 0; + std::pmr::vector> committed_update_states{memory_resource(Memory_Domain::Update_Completion)}; + + void initialize(Renderable& renderable) { + clear(); + state_owner = renderable.create_render_state(); + state = state_owner.get(); + if (state) + state->version = 1; + input_owner = renderable.create_input_data(); + input.data = input_owner.get(); + input.version = 0; + input.pending = false; + input.completion_state.reset(); + } + + void cancel_pending_completions() { + std::lock_guard lock(input_mutex); + if (input.completion_state) { + input.completion_state->complete_all(std::make_error_code(std::errc::operation_canceled), Update_Outcome::Cancelled); + input.completion_state.reset(); + } + cancel_update_states(committed_update_states); + } + + void clear() { + { + std::lock_guard input_lock(input_mutex); + if (input.completion_state) { + input.completion_state->complete_all(std::make_error_code(std::errc::operation_canceled), Update_Outcome::Cancelled); + input.completion_state.reset(); + } + input.data = nullptr; + input.version = 0; + input.pending = false; + input_owner.reset(); + input_version = 0; + } + { + std::lock_guard state_lock(state_mutex); + state = nullptr; + state_owner.reset(); + } + } +}; + +namespace { + +struct Explicit_State_Read_Handle final : Render_State_Read_Handle { + explicit Explicit_State_Read_Handle(std::unique_lock lock) + : lock(std::move(lock)) {} + std::unique_lock lock; +}; + +struct Explicit_Input_Frame_Handle final : Render_Input_Frame_Handle { + Explicit_Input_Frame_Handle(Explicit_Renderable_Runtime_Data* runtime, std::unique_lock lock) + : runtime(runtime), lock(std::move(lock)) {} + void consume_input(Input_Data* input) override { + if (consumed || !runtime) + return; + consumed = true; + if (runtime->input.completion_state) { + runtime->input.completion_state->complete_until(Update_Stage::Committed); + runtime->committed_update_states.push_back(std::move(runtime->input.completion_state)); + } + if (input) + input->clear(); + runtime->input.pending = false; + } + Explicit_Renderable_Runtime_Data* runtime{}; + std::unique_lock lock; + bool consumed = false; +}; + +struct Explicit_State_Write_Handle final : Render_State_Write_Handle { + Explicit_State_Write_Handle(Explicit_Renderable_Runtime_Data* runtime, std::unique_lock lock) + : runtime(runtime), lock(std::move(lock)) {} + ~Explicit_State_Write_Handle() override { + if (!runtime || !runtime->state) + return; + ++runtime->state->version; + lock.unlock(); + if (runtime->owner) + runtime->owner->mark_render_dirty(Dirty_Reason::Config); + } + Explicit_Renderable_Runtime_Data* runtime{}; + std::unique_lock lock; +}; + +struct Explicit_Input_Write_Handle final : Render_Input_Write_Handle { + Explicit_Input_Write_Handle(Explicit_Renderable_Runtime_Data* runtime, std::unique_lock lock, bool notify_render) + : runtime(runtime), lock(std::move(lock)), notify_render(notify_render) {} + ~Explicit_Input_Write_Handle() override { + if (!runtime) + return; + runtime->input.version = ++runtime->input_version; + runtime->input.pending = true; + lock.unlock(); + if (notify_render && runtime->owner) + runtime->owner->mark_render_dirty(); + } + void set_completion(std::shared_ptr state) override { + if (!runtime) + return; + if (runtime->input.completion_state && runtime->input.completion_state != state) + runtime->input.completion_state->complete_all(std::make_error_code(std::errc::operation_canceled), Update_Outcome::Cancelled); + runtime->input.completion_state = std::move(state); + } + Explicit_Renderable_Runtime_Data* runtime{}; + std::unique_lock lock; + bool notify_render{}; +}; + +} // namespace + +Explicit_Renderable_Runtime::Explicit_Renderable_Runtime(Renderable& owner) + : d(std::make_unique()) { + d->owner = &owner; + d->initialize(owner); +} + +Explicit_Renderable_Runtime::~Explicit_Renderable_Runtime() { + d->clear(); +} + +void Explicit_Renderable_Runtime::set_render_state_copier(Copy_Render_State_Function) {} + +void Explicit_Renderable_Runtime::cancel_pending_completions() { + d->cancel_pending_completions(); +} + +void Explicit_Renderable_Runtime::drain_committed_update_states(std::pmr::vector>& output) { + for (auto& state : d->committed_update_states) + output.push_back(std::move(state)); + d->committed_update_states.clear(); +} + +Render_State_Read_Guard Explicit_Renderable_Runtime::acquire_state_read(Renderable_State_Role) { + return acquire_render_state_read(); +} + +Render_State_Read_Guard Explicit_Renderable_Runtime::acquire_render_state_read() { + std::unique_lock lock(d->state_mutex); + if (!d->state) + return {}; + return Render_State_Read_Guard(d->state, std::make_unique(std::move(lock))); +} + +Render_State_Read_Guard Explicit_Renderable_Runtime::acquire_edit_state_read() { + return acquire_render_state_read(); +} + +Render_State_Write_Guard Explicit_Renderable_Runtime::acquire_edit_state() { + std::unique_lock lock(d->state_mutex); + if (!d->state) + return {}; + return Render_State_Write_Guard(d->state, std::make_unique(d.get(), std::move(lock))); +} + +Render_Input_Write_Guard Explicit_Renderable_Runtime::acquire_edit_input(bool notify_render) { + std::unique_lock lock(d->input_mutex); + if (!d->input.data) + return {}; + return Render_Input_Write_Guard(d->input.data, std::make_unique(d.get(), std::move(lock), notify_render)); +} + +Renderable_Frame_View Explicit_Renderable_Runtime::capture_frame_view() { + std::unique_lock state_lock(d->state_mutex); + if (!d->state) + return {}; + auto state_guard = Render_State_Read_Guard( + d->state, + std::make_unique(std::move(state_lock))); + + Render_Input_Frame_Guard input_guard; + std::unique_lock input_lock(d->input_mutex); + if (d->input.data) { + input_guard = Render_Input_Frame_Guard( + d->input.data, + std::make_unique(d.get(), std::move(input_lock))); + } + return Renderable_Frame_View(std::move(state_guard), std::move(input_guard)); +} + +Render_State* Explicit_Renderable_Runtime::state(Renderable_State_Role) { + return d->state; +} + +Input_Data* Explicit_Renderable_Runtime::input_data(Renderable_Input_Role) { + return d->input.data; +} + +void Explicit_Renderable_Runtime::clear_render_input() { + std::lock_guard lock(d->input_mutex); + if (d->input.completion_state) { + d->input.completion_state->complete_until(Update_Stage::Committed); + d->committed_update_states.push_back(std::move(d->input.completion_state)); + } + if (d->input.data) + d->input.data->clear(); + d->input.pending = false; +} + +std::unique_ptr make_explicit_renderable_runtime(Renderable& owner) { + return std::make_unique(owner); +} + +} // namespace renderive diff --git a/Core/flow/explicit/Explicit_Renderable_Runtime.h b/Core/flow/explicit/Explicit_Renderable_Runtime.h new file mode 100644 index 0000000..c147ad2 --- /dev/null +++ b/Core/flow/explicit/Explicit_Renderable_Runtime.h @@ -0,0 +1,35 @@ +#pragma once +#include "../../architecture/Render_Data.h" +#include + +namespace renderive { + +struct Explicit_Renderable_Runtime_Data; + +class Explicit_Renderable_Runtime final : public Renderable_Runtime { +public: + explicit Explicit_Renderable_Runtime(Renderable& owner); + Explicit_Renderable_Runtime(const Explicit_Renderable_Runtime&) = delete; + Explicit_Renderable_Runtime& operator=(const Explicit_Renderable_Runtime&) = delete; + ~Explicit_Renderable_Runtime() override; + + void set_render_state_copier(Copy_Render_State_Function copier) override; + void cancel_pending_completions() override; + void drain_committed_update_states(std::pmr::vector>& output) override; + Render_State_Read_Guard acquire_state_read(Renderable_State_Role role) override; + Render_State_Read_Guard acquire_render_state_read() override; + Render_State_Read_Guard acquire_edit_state_read() override; + Render_State_Write_Guard acquire_edit_state() override; + Render_Input_Write_Guard acquire_edit_input(bool notify_render) override; + Renderable_Frame_View capture_frame_view() override; + Render_State* state(Renderable_State_Role role) override; + Input_Data* input_data(Renderable_Input_Role role) override; + void clear_render_input() override; + +private: + std::unique_ptr d; +}; + +std::unique_ptr make_explicit_renderable_runtime(Renderable& owner); + +} // namespace renderive diff --git a/Core/flow/low_latency/Low_Latency_Feedback_Controller.cpp b/Core/flow/low_latency/Low_Latency_Feedback_Controller.cpp index f4a84b4..c65fb10 100644 --- a/Core/flow/low_latency/Low_Latency_Feedback_Controller.cpp +++ b/Core/flow/low_latency/Low_Latency_Feedback_Controller.cpp @@ -4,9 +4,15 @@ namespace renderive { namespace { -constexpr double Ratio_Threshold = 1.03; -constexpr int Switch_Samples = 3; +constexpr double Enter_Ratio_Floor = 0.15; +constexpr std::uint64_t Enter_Window_Ns = 250000000; +constexpr std::uint64_t Exit_Consumer_Window_Ns = 500000000; +constexpr std::uint64_t Switch_Samples = 16; + +std::uint64_t max_period(std::uint64_t a, std::uint64_t b, std::uint64_t c, std::uint64_t d) { + return std::max(std::max(a, b), std::max(c, d)); } +} // namespace void Low_Latency_Smoothed_Interval::update(double sample_ms) { if (sample_ms <= 0.0 || !std::isfinite(sample_ms)) @@ -23,6 +29,21 @@ void Low_Latency_Smoothed_Interval::update(double sample_ms) { ++sample_count; } +void Low_Latency_Smoothed_Interval::update_wait(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; } @@ -60,46 +81,49 @@ void Low_Latency_Feedback_Controller::on_input_changed(std::uint64_t now_ns, std latest_input_version = input_version; else ++latest_input_version; - refresh_control_snapshot(); + refresh_control_snapshot(now_ns); } 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(); + refresh_control_snapshot(now_ns); 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); - if (frame.ready_queue_wait_ns) - ready_queue_period.update(static_cast(frame.ready_queue_wait_ns) / 1000000.0); - refresh_control_snapshot(); + render_period.update(static_cast(render_duration_ns) / 1000000.0); + ready_queue_period.update_wait(static_cast(frame.ready_queue_wait_ns) / 1000000.0); + refresh_control_snapshot(frame.render_end_ns); 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); - if (frame.update_request_time_ns && frame.paint_begin_ns > frame.update_request_time_ns) - gui_period.update(static_cast(frame.paint_begin_ns - frame.update_request_time_ns) / 1000000.0); - if (frame.paint_return_wait_ns) - consumption_period.update(static_cast(frame.paint_return_wait_ns) / 1000000.0); + paint_period.update(static_cast(paint_duration_ns) / 1000000.0); + if (frame.update_request_time_ns && frame.paint_begin_ns >= frame.update_request_time_ns) + gui_period.update_wait(static_cast(frame.paint_begin_ns - frame.update_request_time_ns) / 1000000.0); + if (frame.frame_id && frame.frame_id != last_consumed_frame_id && frame.paint_begin_ns) { + if (last_consume_sample_ns && frame.paint_begin_ns > last_consume_sample_ns) + consumption_period.update(static_cast(frame.paint_begin_ns - last_consume_sample_ns) / 1000000.0); + last_consume_sample_ns = frame.paint_begin_ns; + last_consumed_frame_id = frame.frame_id; + } if (frame.core_data_time_ns && frame.paint_end_ns > frame.core_data_time_ns) frame_age_period.update(static_cast(frame.paint_end_ns - frame.core_data_time_ns) / 1000000.0); ++presented_frame_count; frame.outcome = Frame_Outcome::Presented; - refresh_control_snapshot(); + refresh_control_snapshot(frame.paint_end_ns); write_state(frame); } void Low_Latency_Feedback_Controller::on_frame_superseded(Frame_Lifecycle_Record& frame) { ++superseded_frame_count; frame.outcome = Frame_Outcome::Superseded; - refresh_control_snapshot(); + refresh_control_snapshot(frame.superseded_time_ns); write_state(frame); } @@ -117,12 +141,12 @@ void Low_Latency_Feedback_Controller::on_frame_dropped(Frame_Lifecycle_Record& f if (outcome == Frame_Outcome::Paint_Acquire_Dropped || outcome == Frame_Outcome::Render_Acquire_Dropped) ++underrun_count; frame.outcome = outcome; - refresh_control_snapshot(); + refresh_control_snapshot(frame.superseded_time_ns ? frame.superseded_time_ns : frame.render_end_ns); write_state(frame); } bool Low_Latency_Feedback_Controller::rate_limit_ready(std::uint64_t now_ns) const { - return now_ns >= next_render_time; + return next_render_time == 0 || now_ns >= next_render_time; } std::uint64_t Low_Latency_Feedback_Controller::next_render_time_ns() const { @@ -137,43 +161,34 @@ void Low_Latency_Feedback_Controller::write_state(Frame_Lifecycle_Record& frame) frame.refresh = state_value; } -void Low_Latency_Feedback_Controller::refresh_control_snapshot() { +void Low_Latency_Feedback_Controller::refresh_control_snapshot(std::uint64_t now_ns) { + if (now_ns) + latest_event_ns = now_ns; + else + now_ns = latest_event_ns; + 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 input_period_ns = input_period.valid() ? ms_to_ns(input_period.safe_ms(0.5)) : 0; + std::uint64_t consume_period_ns = consumption_period.valid() ? ms_to_ns(consumption_period.safe_ms(0.5)) : 0; + std::uint64_t consumer_period_ns = consume_period_ns ? std::max(consume_period_ns, paint_period_ns) : paint_period_ns; std::uint64_t user_period_ns = state_value.user_period_ns; - Refresh_Limit_Source candidate = classify_limit_source(std::max(render_period_ns, input_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, input_period_ns, user_period_ns}); - Low_Latency_Bottleneck_State bottleneck = Low_Latency_Bottleneck_State::Unknown; - if (candidate == Refresh_Limit_Source::User) - bottleneck = Low_Latency_Bottleneck_State::User_Limited; - else if (paint_period_ns && paint_period_ns == target_period) - bottleneck = Low_Latency_Bottleneck_State::Consumer_Limited; - else if (render_period_ns && render_period_ns == target_period) - bottleneck = Low_Latency_Bottleneck_State::Producer_Limited; - else if (input_period_ns && input_period_ns == target_period) - bottleneck = Low_Latency_Bottleneck_State::Input_Limited; + + Low_Latency_Bottleneck_State candidate = classify_bottleneck(input_period_ns, render_period_ns, consumer_period_ns, user_period_ns); + std::uint64_t candidate_period = period_for_bottleneck(candidate, input_period_ns, render_period_ns, consumer_period_ns, user_period_ns); + update_stable_bottleneck(candidate, candidate_period, now_ns, input_period_ns, render_period_ns, consumer_period_ns, user_period_ns); + + std::uint64_t target_period = max_period(input_period_ns, render_period_ns, consumer_period_ns, user_period_ns); + std::uint64_t effective_interval = user_period_ns; + if (stable_bottleneck == Low_Latency_Bottleneck_State::Consumer_Limited) + effective_interval = std::max(effective_interval, consumer_period_ns); + + state_value.input_period_ns = input_period_ns; + state_value.input_jitter_ns = input_period.jitter_ns(); state_value.render_period_ns = render_period_ns; state_value.render_jitter_ns = render_period.jitter_ns(); state_value.paint_period_ns = paint_period_ns; state_value.paint_jitter_ns = paint_period.jitter_ns(); - state_value.input_period_ns = input_period_ns; - state_value.input_jitter_ns = input_period.jitter_ns(); state_value.ready_queue_srtt_ns = ready_queue_period.srtt_ns(); state_value.ready_queue_jitter_ns = ready_queue_period.jitter_ns(); state_value.gui_srtt_ns = gui_period.srtt_ns(); @@ -182,75 +197,149 @@ void Low_Latency_Feedback_Controller::refresh_control_snapshot() { state_value.consumption_jitter_ns = consumption_period.jitter_ns(); state_value.frame_age_srtt_ns = frame_age_period.srtt_ns(); state_value.frame_age_jitter_ns = frame_age_period.jitter_ns(); - state_value.bottleneck = bottleneck; - state_value.production_strategy = bottleneck == Low_Latency_Bottleneck_State::Consumer_Limited ? Low_Latency_Production_Strategy::Cost_Balanced : Low_Latency_Production_Strategy::Latency_Eager; + state_value.source = source_for_bottleneck(stable_bottleneck); + state_value.candidate_source = source_for_bottleneck(candidate_bottleneck); + state_value.bottleneck = stable_bottleneck; + state_value.stable_bottleneck = stable_bottleneck; + state_value.candidate_bottleneck = candidate_bottleneck; + state_value.candidate_samples = candidate_bottleneck_samples; + state_value.candidate_age_ns = candidate_bottleneck_since_ns && now_ns > candidate_bottleneck_since_ns ? now_ns - candidate_bottleneck_since_ns : 0; + state_value.production_strategy = stable_bottleneck == Low_Latency_Bottleneck_State::Consumer_Limited ? Low_Latency_Production_Strategy::Cost_Balanced : Low_Latency_Production_Strategy::Latency_Eager; state_value.target_period_ns = target_period; + state_value.effective_min_render_interval_ns = effective_interval; state_value.presented_frame_count = presented_frame_count; state_value.superseded_frame_count = superseded_frame_count; state_value.repeated_frame_count = repeated_frame_count; state_value.underrun_count = underrun_count; std::uint64_t total_display_attempts = presented_frame_count + superseded_frame_count + repeated_frame_count + underrun_count; state_value.display_efficiency = total_display_attempts ? static_cast(presented_frame_count) / static_cast(total_display_attempts) : 1.0; - if (state_value.source == Refresh_Limit_Source::Unknown) - state_value.source = candidate; - if ((state_value.source == Refresh_Limit_Source::User || state_value.production_strategy == Low_Latency_Production_Strategy::Cost_Balanced) && last_render_begin_ns && state_value.target_period_ns) - next_render_time = last_render_begin_ns + state_value.target_period_ns; - else + + if (last_render_begin_ns && effective_interval) { + std::uint64_t deadline = last_render_begin_ns + effective_interval; + next_render_time = next_render_time ? std::max(next_render_time, deadline) : deadline; + } + else if (!effective_interval) { 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) +Low_Latency_Bottleneck_State Low_Latency_Feedback_Controller::classify_bottleneck(std::uint64_t input_period_ns, std::uint64_t render_period_ns, std::uint64_t consumer_period_ns, std::uint64_t user_period_ns) const { + std::uint64_t best_period = 0; + Low_Latency_Bottleneck_State best = Low_Latency_Bottleneck_State::Unknown; + auto consider = [&best, &best_period](Low_Latency_Bottleneck_State candidate, std::uint64_t period_ns) { + if (period_ns && period_ns > best_period) { + best_period = period_ns; + best = candidate; + } + }; + consider(Low_Latency_Bottleneck_State::Input_Limited, input_period_ns); + consider(Low_Latency_Bottleneck_State::Producer_Limited, render_period_ns); + if (has_consumer_backpressure()) + consider(Low_Latency_Bottleneck_State::Consumer_Limited, consumer_period_ns); + consider(Low_Latency_Bottleneck_State::User_Limited, user_period_ns); + return best; +} + +std::uint64_t Low_Latency_Feedback_Controller::period_for_bottleneck(Low_Latency_Bottleneck_State bottleneck, std::uint64_t input_period_ns, std::uint64_t render_period_ns, std::uint64_t consumer_period_ns, std::uint64_t user_period_ns) const { + switch (bottleneck) { + case Low_Latency_Bottleneck_State::Input_Limited: + return input_period_ns; + case Low_Latency_Bottleneck_State::Producer_Limited: + return render_period_ns; + case Low_Latency_Bottleneck_State::Consumer_Limited: + return consumer_period_ns; + case Low_Latency_Bottleneck_State::User_Limited: + return user_period_ns; + case Low_Latency_Bottleneck_State::Unknown: + break; + } + return 0; +} + +std::uint64_t Low_Latency_Feedback_Controller::jitter_for_bottleneck(Low_Latency_Bottleneck_State bottleneck) const { + switch (bottleneck) { + case Low_Latency_Bottleneck_State::Input_Limited: + return input_period.jitter_ns(); + case Low_Latency_Bottleneck_State::Producer_Limited: + return render_period.jitter_ns(); + case Low_Latency_Bottleneck_State::Consumer_Limited: + return std::max(consumption_period.jitter_ns(), paint_period.jitter_ns()); + case Low_Latency_Bottleneck_State::User_Limited: + case Low_Latency_Bottleneck_State::Unknown: + break; + } + return 0; +} + +bool Low_Latency_Feedback_Controller::has_consumer_backpressure() const { + if (ready_queue_period.valid() && ready_queue_period.srtt_ms > 0.25) + return true; + if (gui_period.valid() && gui_period.srtt_ms > 0.25) + return true; + std::uint64_t resolved = presented_frame_count + superseded_frame_count; + return resolved >= 16 && superseded_frame_count * 4 >= resolved; +} + +void Low_Latency_Feedback_Controller::update_stable_bottleneck(Low_Latency_Bottleneck_State candidate, std::uint64_t candidate_period_ns, std::uint64_t now_ns, std::uint64_t input_period_ns, std::uint64_t render_period_ns, std::uint64_t consumer_period_ns, std::uint64_t user_period_ns) { + if (candidate == Low_Latency_Bottleneck_State::Unknown) + return; + if (stable_bottleneck == Low_Latency_Bottleneck_State::Unknown && candidate != Low_Latency_Bottleneck_State::Consumer_Limited) { + stable_bottleneck = candidate; + candidate_bottleneck = candidate; + candidate_bottleneck_since_ns = now_ns; + candidate_bottleneck_samples = 0; + return; + } + if (candidate == stable_bottleneck) { + candidate_bottleneck = candidate; + candidate_bottleneck_since_ns = now_ns; + candidate_bottleneck_samples = 0; + return; + } + if (candidate != candidate_bottleneck) { + candidate_bottleneck = candidate; + candidate_bottleneck_since_ns = now_ns; + candidate_bottleneck_samples = 1; + return; + } + ++candidate_bottleneck_samples; + std::uint64_t age_ns = candidate_bottleneck_since_ns && now_ns > candidate_bottleneck_since_ns ? now_ns - candidate_bottleneck_since_ns : 0; + std::uint64_t required_age = stable_bottleneck == Low_Latency_Bottleneck_State::Consumer_Limited ? Exit_Consumer_Window_Ns : Enter_Window_Ns; + if (candidate_bottleneck_samples < Switch_Samples || age_ns < required_age) + return; + + std::uint64_t current_period = period_for_bottleneck(stable_bottleneck, input_period_ns, render_period_ns, consumer_period_ns, user_period_ns); + std::uint64_t current_jitter = jitter_for_bottleneck(stable_bottleneck); + double jitter_ratio = current_period ? static_cast(current_jitter) / static_cast(current_period) : 0.0; + double required_ratio = 1.0 + std::max(Enter_Ratio_Floor, jitter_ratio * 2.0); + bool period_can_enter = stable_bottleneck == Low_Latency_Bottleneck_State::Unknown + || current_period == 0 + || static_cast(candidate_period_ns) > static_cast(current_period) * required_ratio; + bool leaving_consumer = stable_bottleneck == Low_Latency_Bottleneck_State::Consumer_Limited && candidate != Low_Latency_Bottleneck_State::Consumer_Limited; + if (!period_can_enter && !leaving_consumer) + return; + + stable_bottleneck = candidate; + candidate_bottleneck_samples = 0; + candidate_bottleneck_since_ns = now_ns; +} + +Refresh_Limit_Source Low_Latency_Feedback_Controller::source_for_bottleneck(Low_Latency_Bottleneck_State bottleneck) { + switch (bottleneck) { + case Low_Latency_Bottleneck_State::Input_Limited: + return Refresh_Limit_Source::Input; + case Low_Latency_Bottleneck_State::Producer_Limited: return Refresh_Limit_Source::Render; + case Low_Latency_Bottleneck_State::Consumer_Limited: + return Refresh_Limit_Source::Paint; + case Low_Latency_Bottleneck_State::User_Limited: + return Refresh_Limit_Source::User; + case Low_Latency_Bottleneck_State::Unknown: + break; + } 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; diff --git a/Core/flow/low_latency/Low_Latency_Feedback_Controller.h b/Core/flow/low_latency/Low_Latency_Feedback_Controller.h index 455496f..fdfe85b 100644 --- a/Core/flow/low_latency/Low_Latency_Feedback_Controller.h +++ b/Core/flow/low_latency/Low_Latency_Feedback_Controller.h @@ -9,6 +9,7 @@ struct Low_Latency_Smoothed_Interval { double jitter_ms{}; std::uint64_t sample_count{}; void update(double sample_ms); + void update_wait(double sample_ms); [[nodiscard]] double safe_ms(double jitter_factor = 2.0) const; [[nodiscard]] std::uint64_t srtt_ns() const; [[nodiscard]] std::uint64_t jitter_ns() const; @@ -39,9 +40,13 @@ public: 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); + void refresh_control_snapshot(std::uint64_t now_ns = 0); + [[nodiscard]] Low_Latency_Bottleneck_State classify_bottleneck(std::uint64_t input_period_ns, std::uint64_t render_period_ns, std::uint64_t consumer_period_ns, std::uint64_t user_period_ns) const; + [[nodiscard]] std::uint64_t period_for_bottleneck(Low_Latency_Bottleneck_State bottleneck, std::uint64_t input_period_ns, std::uint64_t render_period_ns, std::uint64_t consumer_period_ns, std::uint64_t user_period_ns) const; + [[nodiscard]] std::uint64_t jitter_for_bottleneck(Low_Latency_Bottleneck_State bottleneck) const; + [[nodiscard]] bool has_consumer_backpressure() const; + void update_stable_bottleneck(Low_Latency_Bottleneck_State candidate, std::uint64_t candidate_period_ns, std::uint64_t now_ns, std::uint64_t input_period_ns, std::uint64_t render_period_ns, std::uint64_t consumer_period_ns, std::uint64_t user_period_ns); + static Refresh_Limit_Source source_for_bottleneck(Low_Latency_Bottleneck_State bottleneck); static std::uint64_t ms_to_ns(double ms); Refresh_Control_Snapshot state_value; @@ -52,11 +57,16 @@ private: Low_Latency_Smoothed_Interval gui_period; Low_Latency_Smoothed_Interval consumption_period; Low_Latency_Smoothed_Interval frame_age_period; - Refresh_Limit_Source candidate_source = Refresh_Limit_Source::Unknown; - int candidate_count{}; + Low_Latency_Bottleneck_State stable_bottleneck = Low_Latency_Bottleneck_State::Unknown; + Low_Latency_Bottleneck_State candidate_bottleneck = Low_Latency_Bottleneck_State::Unknown; + std::uint64_t candidate_bottleneck_since_ns{}; + std::uint64_t candidate_bottleneck_samples{}; std::uint64_t latest_input_version{}; std::uint64_t last_input_change_ns{}; std::uint64_t last_render_begin_ns{}; + std::uint64_t last_consume_sample_ns{}; + std::uint64_t last_consumed_frame_id{}; + std::uint64_t latest_event_ns{}; std::uint64_t next_render_time{}; std::uint64_t presented_frame_count{}; std::uint64_t superseded_frame_count{}; diff --git a/Core/flow/low_latency/Low_Latency_Frame_Flow.cpp b/Core/flow/low_latency/Low_Latency_Frame_Flow.cpp index bd939c8..c2ae86b 100644 --- a/Core/flow/low_latency/Low_Latency_Frame_Flow.cpp +++ b/Core/flow/low_latency/Low_Latency_Frame_Flow.cpp @@ -1,6 +1,7 @@ #include "Low_Latency_Frame_Flow.h" #include "Low_Latency_Feedback_Controller.h" #include "Low_Latency_Frame_Pipeline.h" +#include "Low_Latency_Renderable_Runtime.h" #include "../../architecture/Render_Time.h" #include "../../architecture/Update_Completion.h" @@ -87,6 +88,7 @@ struct Low_Latency_Frame_Flow::Private { break; } case Refresh_Limit_Source::Render: + case Refresh_Limit_Source::Input: case Refresh_Limit_Source::Unknown: (void)host->try_render_once(); break; @@ -127,8 +129,8 @@ void Low_Latency_Frame_Flow::attach(Frame_Flow_Host& host) { d->host = &host; } -Renderable_Runtime_Strategy Low_Latency_Frame_Flow::renderable_runtime_strategy() const { - return Renderable_Runtime_Strategy::Low_Latency; +std::unique_ptr Low_Latency_Frame_Flow::create_renderable_runtime(Renderable& owner) const { + return make_low_latency_renderable_runtime(owner); } Frame_Flow_Runtime_Snapshot Low_Latency_Frame_Flow::runtime_snapshot(std::uint64_t now_ns, bool destroying) const { @@ -196,9 +198,9 @@ Render_Surface_Publish_Result Low_Latency_Frame_Flow::request_present(std::uint6 } 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); + auto consumed = d->pipeline.acquire_paint_frame(now_ns); returned_frame_record = std::move(consumed.returned_frame_record); - if (consumed.paint_frame_unavailable) + if (consumed.underrun) d->feedback.on_frame_underrun(); else if (consumed.lease && !consumed.has_new_frame) d->feedback.on_frame_repeated(); diff --git a/Core/flow/low_latency/Low_Latency_Frame_Flow.h b/Core/flow/low_latency/Low_Latency_Frame_Flow.h index 13b8c4f..b734030 100644 --- a/Core/flow/low_latency/Low_Latency_Frame_Flow.h +++ b/Core/flow/low_latency/Low_Latency_Frame_Flow.h @@ -17,7 +17,7 @@ public: [[nodiscard]] Low_Latency_Diagnostics low_latency_diagnostics() const; void attach(Frame_Flow_Host& host) override; - [[nodiscard]] Renderable_Runtime_Strategy renderable_runtime_strategy() const override; + [[nodiscard]] std::unique_ptr create_renderable_runtime(Renderable& owner) const 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; diff --git a/Core/flow/low_latency/Low_Latency_Frame_Pipeline.cpp b/Core/flow/low_latency/Low_Latency_Frame_Pipeline.cpp index d228c39..0358f75 100644 --- a/Core/flow/low_latency/Low_Latency_Frame_Pipeline.cpp +++ b/Core/flow/low_latency/Low_Latency_Frame_Pipeline.cpp @@ -1,8 +1,17 @@ #include "Low_Latency_Frame_Pipeline.h" #include "../../architecture/Update_Completion.h" +#include "../../base/Assert.h" +#include #include namespace renderive { +namespace { + +bool has_complete_frame(const Low_Latency_Frame* frame) { + return frame && frame->version.load(std::memory_order_acquire) != 0 && !frame->image.empty(); +} + +} // namespace Low_Latency_Frame::Low_Latency_Frame() = default; @@ -118,6 +127,8 @@ Low_Latency_Frame_Pipeline::Frame_Publish_Result Low_Latency_Frame_Pipeline::try } rendering_lease.reset(); + if (has_complete_frame(rendered)) + has_published_frame.store(true, std::memory_order_release); Frame_Publish_Result update_result = request_present(now_ns); update_result.superseded_frame_record = result.superseded_frame_record; update_result.dropped_frame_record = result.dropped_frame_record; @@ -154,43 +165,66 @@ Low_Latency_Frame_Pipeline::Frame_Publish_Result Low_Latency_Frame_Pipeline::req return result; } -Low_Latency_Frame_Pipeline::Frame_Consume_Result Low_Latency_Frame_Pipeline::try_acquire_paint_frame(std::uint64_t now_ns) { +Low_Latency_Frame_Pipeline::Frame_Consume_Result Low_Latency_Frame_Pipeline::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 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&) { - 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->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->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; - ++attempt_counters.paint_role_busy_count; + for (;;) { + bool attempted_middle_swap = false; + Triple_Buffer_Result middle_swap_result = Triple_Buffer_Result::Condition_Failed; + if (middle_frame_ready()) { + attempted_middle_swap = true; + auto swap = frame_control.try_swap_role(Frame_Middle, Frame_Painting, [this](const Triple_Buffer_View& view) { + 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&) { + 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->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->present_queue_wait_ns = now_ns > new_painting->metadata->transition_12_ns ? now_ns - new_painting->metadata->transition_12_ns : 0; + }); + middle_swap_result = swap.result; + } + + auto record = frame_control.acquire_role(Frame_Painting); + if (record.result == Triple_Buffer_Result::Success) { + result.lease = {frame_by_index(record.lease.index), &frame_control, record.lease}; + result.has_valid_frame = has_complete_frame(result.lease.frame); + if (result.has_valid_frame) + { + if (result.has_new_frame && pending) + result.present_request_was_pending = paint_request_pending.exchange(false, std::memory_order_acq_rel); + return result; + } + result.lease.reset(); + if (!has_published_frame.load(std::memory_order_acquire)) { + result.underrun = true; + return result; + } + if (attempted_middle_swap && middle_swap_result != Triple_Buffer_Result::Success) { + std::this_thread::yield(); + continue; + } + ASSERT(false, "Low Latency invariant violated: published frame exists but Painting is empty"); + result.underrun = true; + return result; } - } - auto record = frame_control.try_acquire_role(Frame_Painting); - if (record.result == Triple_Buffer_Result::Success) { - result.lease = {frame_by_index(record.lease.index), &frame_control, record.lease}; - result.present_request_was_pending = pending && paint_request_pending.exchange(false, std::memory_order_acq_rel); - } - else { result.paint_frame_unavailable = true; - ++attempt_counters.paint_role_busy_count; + result.underrun = true; + if (record.result == Triple_Buffer_Result::Busy) + ++attempt_counters.paint_role_busy_count; + if (has_published_frame.load(std::memory_order_acquire)) + ASSERT(false, "Low Latency invariant violated: published frame exists but Painting lease is unavailable"); + return result; } - return result; } bool Low_Latency_Frame_Pipeline::middle_frame_ready() const { diff --git a/Core/flow/low_latency/Low_Latency_Frame_Pipeline.h b/Core/flow/low_latency/Low_Latency_Frame_Pipeline.h index 5b60f0f..b8aceec 100644 --- a/Core/flow/low_latency/Low_Latency_Frame_Pipeline.h +++ b/Core/flow/low_latency/Low_Latency_Frame_Pipeline.h @@ -54,6 +54,8 @@ public: bool has_new_frame{}; bool present_request_was_pending{}; bool paint_frame_unavailable{}; + bool has_valid_frame{}; + bool underrun{}; }; Low_Latency_Frame_Pipeline(); ~Low_Latency_Frame_Pipeline(); @@ -66,7 +68,7 @@ public: [[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]] Frame_Consume_Result 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(); @@ -82,6 +84,7 @@ private: Frame_Job_State render_job_state = Frame_Job_State::Idle; Low_Latency_Frame_Attempt_Accumulator attempt_counters; std::atomic_bool paint_request_pending{false}; + std::atomic_bool has_published_frame{false}; std::atomic pending_present_request_id{0}; std::atomic pending_present_request_time_ns{0}; std::uint64_t next_present_request_id{}; diff --git a/Core/flow/low_latency/Low_Latency_Renderable_Runtime.h b/Core/flow/low_latency/Low_Latency_Renderable_Runtime.h new file mode 100644 index 0000000..da9415a --- /dev/null +++ b/Core/flow/low_latency/Low_Latency_Renderable_Runtime.h @@ -0,0 +1,35 @@ +#pragma once +#include "../../architecture/Render_Data.h" +#include + +namespace renderive { + +struct Low_Latency_Renderable_Runtime_Data; + +class Low_Latency_Renderable_Runtime final : public Renderable_Runtime { +public: + explicit Low_Latency_Renderable_Runtime(Renderable& owner); + Low_Latency_Renderable_Runtime(const Low_Latency_Renderable_Runtime&) = delete; + Low_Latency_Renderable_Runtime& operator=(const Low_Latency_Renderable_Runtime&) = delete; + ~Low_Latency_Renderable_Runtime() override; + + void set_render_state_copier(Copy_Render_State_Function copier) override; + void cancel_pending_completions() override; + void drain_committed_update_states(std::pmr::vector>& output) override; + Render_State_Read_Guard acquire_state_read(Renderable_State_Role role) override; + Render_State_Read_Guard acquire_render_state_read() override; + Render_State_Read_Guard acquire_edit_state_read() override; + Render_State_Write_Guard acquire_edit_state() override; + Render_Input_Write_Guard acquire_edit_input(bool notify_render) override; + Renderable_Frame_View capture_frame_view() override; + Render_State* state(Renderable_State_Role role) override; + Input_Data* input_data(Renderable_Input_Role role) override; + void clear_render_input() override; + +private: + std::unique_ptr d; +}; + +std::unique_ptr make_low_latency_renderable_runtime(Renderable& owner); + +} // namespace renderive diff --git a/Core/flow/low_latency/Render_Data.cpp b/Core/flow/low_latency/Render_Data.cpp index 26de081..15a240b 100644 --- a/Core/flow/low_latency/Render_Data.cpp +++ b/Core/flow/low_latency/Render_Data.cpp @@ -1,10 +1,10 @@ -#include "../../architecture/Strategy_Render_Data.h" +#include "Low_Latency_Renderable_Runtime.h" #include "../../architecture/Renderable.h" #include "../../base/Memory.h" #include "Low_Latency_Roles.h" #include "Triple_Buffer.h" +#include #include -#include #include #include #include @@ -51,8 +51,9 @@ std::uint8_t input_role_index(Renderable_Input_Role role) { } // namespace -struct Strategy_Render_Data_Private { - Renderable_Runtime_Strategy runtime_strategy_value = Renderable_Runtime_Strategy::Low_Latency; +struct Low_Latency_Renderable_Runtime_Data { + Renderable* owner{}; + Renderable_Runtime::Copy_Render_State_Function copy_render_state; Triple_Role_Buffer_Control state_control; std::array state_buffers{}; std::array state_buffer_owners{}; @@ -61,61 +62,67 @@ struct Strategy_Render_Data_Private { std::array input_buffer_owners{}; std::pmr::vector> committed_update_states{memory_resource(Memory_Domain::Update_Completion)}; std::uint64_t input_version = 0; - mutable std::mutex explicit_state_mutex; - mutable std::mutex explicit_input_mutex; - Render_Object_Owner explicit_state_owner; - Render_State* explicit_state{}; - Render_Object_Owner explicit_input_owner; - Input_Buffer_Slot explicit_input; - std::uint64_t explicit_input_version = 0; Render_State* state_by_index(std::uint8_t index) const { return index < state_buffers.size() ? state_buffers[index] : nullptr; } + Render_State* state(std::uint8_t role) const { - if (runtime_strategy_value == Renderable_Runtime_Strategy::Explicit) - return explicit_state; return state_by_index(state_control.read_view()[role]); } + Input_Data* input_data_by_index(std::uint8_t index) const { return index < input_buffers.size() ? input_buffers[index].data : nullptr; } + Input_Buffer_Slot& input_slot_by_index(std::uint8_t index) { return input_buffers[index]; } + const Input_Buffer_Slot& input_slot_by_index(std::uint8_t index) const { return input_buffers[index]; } + Input_Buffer_Slot& input_slot(std::uint8_t role) { return input_slot_by_index(input_control.read_view()[role]); } + const Input_Buffer_Slot& input_slot(std::uint8_t role) const { return input_slot_by_index(input_control.read_view()[role]); } + Input_Data* input_data(std::uint8_t role) const { - if (runtime_strategy_value == Renderable_Runtime_Strategy::Explicit) - return explicit_input.data; return input_slot(role).data; } + Triple_Buffer_Lease try_mark_state_role(std::uint8_t role) { auto record = state_control.try_acquire_role(role); return record.result == Triple_Buffer_Result::Success ? record.lease : Triple_Buffer_Lease{}; } + Triple_Buffer_Lease mark_state_role(std::uint8_t role) { auto record = state_control.acquire_role(role); return record.result == Triple_Buffer_Result::Success ? record.lease : Triple_Buffer_Lease{}; } + + Triple_Buffer_Lease try_mark_input_role(std::uint8_t role) { + auto record = input_control.try_acquire_role(role); + return record.result == Triple_Buffer_Result::Success ? record.lease : Triple_Buffer_Lease{}; + } + void mark_input_dirty(Triple_Buffer_Lease lease) { Input_Buffer_Slot& slot = input_slot_by_index(lease.index); slot.version = ++input_version; slot.pending = true; } + void set_input_completion(Triple_Buffer_Lease lease, std::shared_ptr state) { Input_Buffer_Slot& slot = input_slot_by_index(lease.index); if (slot.completion_state && slot.completion_state != state) slot.completion_state->complete_all({}, Update_Outcome::Superseded); slot.completion_state = std::move(state); } + void cancel_pending_completions() { for (Input_Buffer_Slot& slot : input_buffers) { if (!slot.completion_state) @@ -123,15 +130,9 @@ struct Strategy_Render_Data_Private { slot.completion_state->complete_all(std::make_error_code(std::errc::operation_canceled), Update_Outcome::Cancelled); slot.completion_state.reset(); } - { - std::lock_guard lock(explicit_input_mutex); - if (explicit_input.completion_state) { - explicit_input.completion_state->complete_all(std::make_error_code(std::errc::operation_canceled), Update_Outcome::Cancelled); - explicit_input.completion_state.reset(); - } - } cancel_update_states(committed_update_states); } + void clear_state_buffers() { state_control.close_and_wait(); for (std::size_t i = 0; i < state_buffers.size(); ++i) { @@ -140,6 +141,7 @@ struct Strategy_Render_Data_Private { } state_control.reset(); } + void clear_input_buffers() { input_control.close_and_wait(); cancel_pending_completions(); @@ -153,44 +155,19 @@ struct Strategy_Render_Data_Private { input_version = 0; input_control.reset(); } - void clear_explicit_runtime() { - { - std::lock_guard input_lock(explicit_input_mutex); - if (explicit_input.completion_state) { - explicit_input.completion_state->complete_all(std::make_error_code(std::errc::operation_canceled), Update_Outcome::Cancelled); - explicit_input.completion_state.reset(); - } - explicit_input.data = nullptr; - explicit_input.version = 0; - explicit_input.pending = false; - explicit_input_owner.reset(); - explicit_input_version = 0; - } - { - std::lock_guard state_lock(explicit_state_mutex); - explicit_state = nullptr; - explicit_state_owner.reset(); - } - } - void initialize_low_latency_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 initialize_buffers(Renderable& renderable) { clear_state_buffers(); clear_input_buffers(); - clear_explicit_runtime(); - state_buffer_owners[State_Edit] = std::move(edit_state); - state_buffer_owners[State_Ready] = std::move(ready_state); - state_buffer_owners[State_Render] = std::move(render_state); + state_buffer_owners[State_Edit] = renderable.create_render_state(); + state_buffer_owners[State_Ready] = renderable.create_render_state(); + state_buffer_owners[State_Render] = renderable.create_render_state(); state_buffers[State_Edit] = state_buffer_owners[State_Edit].get(); 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); + input_buffer_owners[Input_Edit] = renderable.create_input_data(); + input_buffer_owners[Input_Ready] = renderable.create_input_data(); + input_buffer_owners[Input_Render] = renderable.create_input_data(); if (input_buffer_owners[Input_Edit]) input_buffers[Input_Edit].data = input_buffer_owners[Input_Edit].get(); if (input_buffer_owners[Input_Ready]) @@ -200,38 +177,44 @@ struct Strategy_Render_Data_Private { if (state_buffers[State_Edit]) state_buffers[State_Edit]->version = 1; } - void initialize_explicit_runtime_buffers(Render_Object_Owner state, Render_Object_Owner input) { - clear_state_buffers(); - clear_input_buffers(); - clear_explicit_runtime(); - explicit_state_owner = std::move(state); - explicit_state = explicit_state_owner.get(); - if (explicit_state) - explicit_state->version = 1; - explicit_input_owner = std::move(input); - explicit_input.data = explicit_input_owner.get(); - explicit_input.version = 0; - explicit_input.pending = false; - explicit_input.completion_state.reset(); - } - void clear_input_role(Renderable_Input_Role role) { - if (runtime_strategy_value == Renderable_Runtime_Strategy::Explicit) { - std::lock_guard lock(explicit_input_mutex); - if (explicit_input.completion_state) { - if (role == Renderable_Input_Role::Render) { - explicit_input.completion_state->complete_until(Update_Stage::Committed); - committed_update_states.push_back(std::move(explicit_input.completion_state)); - } - else { - explicit_input.completion_state->complete_all({}, Update_Outcome::Superseded); - explicit_input.completion_state.reset(); - } - } - if (explicit_input.data) - explicit_input.data->clear(); - explicit_input.pending = false; + + void sync_state_pipeline() { + auto edit_record = state_control.try_acquire_role(State_Edit); + if (edit_record.result != Triple_Buffer_Result::Success) + return; + auto ready_record = state_control.try_acquire_role(State_Ready); + if (ready_record.result != Triple_Buffer_Result::Success) { + state_control.unmark_use(edit_record.lease); return; } + Render_State* ready = state_by_index(ready_record.lease.index); + Render_State* edit = state_by_index(edit_record.lease.index); + if (ready && edit && buffer_version_newer(edit->version, ready->version)) { + if (copy_render_state) + copy_render_state(*ready, *edit); + else + ready->version = edit->version; + } + state_control.unmark_use(ready_record.lease); + state_control.unmark_use(edit_record.lease); + state_control.try_swap_role(State_Ready, State_Render, [this](const Triple_Buffer_View& view) { + auto* ready = state_by_index(view[State_Ready]); + auto* render = state_by_index(view[State_Render]); + return ready && render && buffer_version_newer(ready->version, render->version); + }); + input_control.try_swap_role(Input_Edit, Input_Ready, [this](const Triple_Buffer_View& view) { + const Input_Buffer_Slot& edit = input_slot_by_index(view[Input_Edit]); + const Input_Buffer_Slot& ready = input_slot_by_index(view[Input_Ready]); + return edit.pending && buffer_version_newer(edit.version, ready.version); + }); + input_control.try_swap_role(Input_Ready, Input_Render, [this](const Triple_Buffer_View& view) { + const Input_Buffer_Slot& ready = input_slot_by_index(view[Input_Ready]); + const Input_Buffer_Slot& render = input_slot_by_index(view[Input_Render]); + return ready.pending && buffer_version_newer(ready.version, render.version); + }); + } + + void clear_input_role(Renderable_Input_Role role) { Input_Buffer_Slot& slot = input_slot(input_role_index(role)); if (slot.completion_state) { if (role == Renderable_Input_Role::Render) { @@ -247,273 +230,194 @@ struct Strategy_Render_Data_Private { slot.data->clear(); slot.pending = false; } + + void consume_input_lease(Triple_Buffer_Lease lease) { + if (!lease) + return; + Input_Buffer_Slot& slot = input_slot_by_index(lease.index); + if (slot.completion_state) { + slot.completion_state->complete_until(Update_Stage::Committed); + committed_update_states.push_back(std::move(slot.completion_state)); + } + if (slot.data) + slot.data->clear(); + slot.pending = false; + } }; namespace { struct Low_Latency_State_Read_Handle final : Render_State_Read_Handle { - Low_Latency_State_Read_Handle(Strategy_Render_Data_Private* runtime, Triple_Buffer_Lease lease) + Low_Latency_State_Read_Handle(Low_Latency_Renderable_Runtime_Data* runtime, Triple_Buffer_Lease lease) : runtime(runtime), lease(lease) {} ~Low_Latency_State_Read_Handle() override { if (runtime && lease) runtime->state_control.unmark_use(lease); } - Strategy_Render_Data_Private* runtime{}; + Low_Latency_Renderable_Runtime_Data* runtime{}; Triple_Buffer_Lease lease{}; }; -struct Low_Latency_State_Edit_Handle final : Render_State_Edit_Handle { - Low_Latency_State_Edit_Handle(Strategy_Render_Data_Private* runtime, Renderable* owner, Triple_Buffer_Lease lease) - : runtime(runtime), owner(owner), lease(lease) {} - ~Low_Latency_State_Edit_Handle() override { +struct Low_Latency_Input_Frame_Handle final : Render_Input_Frame_Handle { + Low_Latency_Input_Frame_Handle(Low_Latency_Renderable_Runtime_Data* runtime, Triple_Buffer_Lease lease) + : runtime(runtime), lease(lease) {} + ~Low_Latency_Input_Frame_Handle() override { + if (runtime && lease) + runtime->input_control.unmark_use(lease); + } + void consume_input(Input_Data*) override { + if (consumed || !runtime || !lease) + return; + consumed = true; + runtime->consume_input_lease(lease); + } + Low_Latency_Renderable_Runtime_Data* runtime{}; + Triple_Buffer_Lease lease{}; + bool consumed = false; +}; + +struct Low_Latency_State_Write_Handle final : Render_State_Write_Handle { + Low_Latency_State_Write_Handle(Low_Latency_Renderable_Runtime_Data* runtime, Triple_Buffer_Lease lease) + : runtime(runtime), lease(lease) {} + ~Low_Latency_State_Write_Handle() override { if (!runtime || !lease) return; if (auto* state = runtime->state_by_index(lease.index)) ++state->version; runtime->state_control.unmark_use(lease); - if (owner) - owner->mark_render_dirty(Dirty_Reason::Config); + if (runtime->owner) + runtime->owner->mark_render_dirty(Dirty_Reason::Config); } - Strategy_Render_Data_Private* runtime{}; - Renderable* owner{}; + Low_Latency_Renderable_Runtime_Data* runtime{}; Triple_Buffer_Lease lease{}; }; -struct Low_Latency_Input_Edit_Handle final : Render_Input_Edit_Handle { - Low_Latency_Input_Edit_Handle(Strategy_Render_Data_Private* runtime, Renderable* owner, Triple_Buffer_Lease lease, bool notify_render) - : runtime(runtime), owner(owner), lease(lease), notify_render(notify_render) {} - ~Low_Latency_Input_Edit_Handle() override { +struct Low_Latency_Input_Write_Handle final : Render_Input_Write_Handle { + Low_Latency_Input_Write_Handle(Low_Latency_Renderable_Runtime_Data* runtime, Triple_Buffer_Lease lease, bool notify_render) + : runtime(runtime), lease(lease), notify_render(notify_render) {} + ~Low_Latency_Input_Write_Handle() override { if (!runtime || !lease) return; runtime->mark_input_dirty(lease); runtime->input_control.unmark_use(lease); - if (notify_render && owner) - owner->mark_render_dirty(); + if (notify_render && runtime->owner) + runtime->owner->mark_render_dirty(); } void set_completion(std::shared_ptr state) override { if (runtime && lease) runtime->set_input_completion(lease, std::move(state)); } - Strategy_Render_Data_Private* runtime{}; - Renderable* owner{}; + Low_Latency_Renderable_Runtime_Data* runtime{}; Triple_Buffer_Lease lease{}; bool notify_render{}; }; -struct Explicit_State_Read_Handle final : Render_State_Read_Handle { - explicit Explicit_State_Read_Handle(std::unique_lock lock) - : lock(std::move(lock)) {} - std::unique_lock lock; -}; - -struct Explicit_State_Edit_Handle final : Render_State_Edit_Handle { - Explicit_State_Edit_Handle(Strategy_Render_Data_Private* runtime, Renderable* owner, std::unique_lock lock) - : runtime(runtime), owner(owner), lock(std::move(lock)) {} - ~Explicit_State_Edit_Handle() override { - if (!runtime || !runtime->explicit_state) - return; - ++runtime->explicit_state->version; - lock.unlock(); - if (owner) - owner->mark_render_dirty(Dirty_Reason::Config); - } - Strategy_Render_Data_Private* runtime{}; - Renderable* owner{}; - std::unique_lock lock; -}; - -struct Explicit_Input_Edit_Handle final : Render_Input_Edit_Handle { - Explicit_Input_Edit_Handle(Strategy_Render_Data_Private* runtime, Renderable* owner, std::unique_lock lock, bool notify_render) - : runtime(runtime), owner(owner), lock(std::move(lock)), notify_render(notify_render) {} - ~Explicit_Input_Edit_Handle() override { - if (!runtime) - return; - runtime->explicit_input.version = ++runtime->explicit_input_version; - runtime->explicit_input.pending = true; - lock.unlock(); - if (notify_render && owner) - owner->mark_render_dirty(); - } - void set_completion(std::shared_ptr state) override { - if (!runtime) - return; - if (runtime->explicit_input.completion_state && runtime->explicit_input.completion_state != state) - runtime->explicit_input.completion_state->complete_all({}, Update_Outcome::Superseded); - runtime->explicit_input.completion_state = std::move(state); - } - Strategy_Render_Data_Private* runtime{}; - Renderable* owner{}; - std::unique_lock lock; - bool notify_render{}; -}; - } // namespace -Strategy_Render_Data::Strategy_Render_Data() - : runtime(std::make_unique()) {} - -Strategy_Render_Data::~Strategy_Render_Data() { - runtime->clear_state_buffers(); - runtime->clear_input_buffers(); - runtime->clear_explicit_runtime(); +Low_Latency_Renderable_Runtime::Low_Latency_Renderable_Runtime(Renderable& owner) + : d(std::make_unique()) { + d->owner = &owner; + d->initialize_buffers(owner); } -void Strategy_Render_Data::initialize_runtime(Renderable& owner, Renderable_Runtime_Strategy strategy) { - runtime->runtime_strategy_value = strategy; - if (strategy == Renderable_Runtime_Strategy::Explicit) { - runtime->initialize_explicit_runtime_buffers(owner.create_render_state(), owner.create_input_data()); - return; - } - runtime->initialize_low_latency_runtime_buffers( - owner.create_render_state(), - owner.create_render_state(), - owner.create_render_state(), - owner.create_input_data(), - owner.create_input_data(), - owner.create_input_data()); +Low_Latency_Renderable_Runtime::~Low_Latency_Renderable_Runtime() { + d->clear_state_buffers(); + d->clear_input_buffers(); } -Renderable_Runtime_Strategy Strategy_Render_Data::runtime_strategy() const { - return runtime->runtime_strategy_value; +void Low_Latency_Renderable_Runtime::set_render_state_copier(Copy_Render_State_Function copier) { + d->copy_render_state = std::move(copier); } -void Strategy_Render_Data::cancel_pending_completions() { - runtime->cancel_pending_completions(); +void Low_Latency_Renderable_Runtime::cancel_pending_completions() { + d->cancel_pending_completions(); } -void Strategy_Render_Data::drain_committed_update_states(std::pmr::vector>& output) { - for (auto& state : runtime->committed_update_states) +void Low_Latency_Renderable_Runtime::drain_committed_update_states(std::pmr::vector>& output) { + for (auto& state : d->committed_update_states) output.push_back(std::move(state)); - runtime->committed_update_states.clear(); + d->committed_update_states.clear(); } -Render_State_Read_Guard Strategy_Render_Data::acquire_state_read(Renderable_State_Role role) { - if (runtime->runtime_strategy_value == Renderable_Runtime_Strategy::Explicit) { - std::unique_lock lock(runtime->explicit_state_mutex); - if (!runtime->explicit_state) - return {}; - return Render_State_Read_Guard(runtime->explicit_state, std::make_unique(std::move(lock))); - } - Triple_Buffer_Lease lease = runtime->mark_state_role(state_role_index(role)); +Render_State_Read_Guard Low_Latency_Renderable_Runtime::acquire_state_read(Renderable_State_Role role) { + Triple_Buffer_Lease lease = d->mark_state_role(state_role_index(role)); if (!lease) return {}; - return Render_State_Read_Guard(runtime->state_by_index(lease.index), std::make_unique(runtime.get(), lease)); + return Render_State_Read_Guard(d->state_by_index(lease.index), std::make_unique(d.get(), lease)); } -Render_State_Read_Guard Strategy_Render_Data::acquire_render_state_read() { - if (runtime->runtime_strategy_value == Renderable_Runtime_Strategy::Explicit) { - std::unique_lock lock(runtime->explicit_state_mutex); - if (!runtime->explicit_state) - return {}; - return Render_State_Read_Guard(runtime->explicit_state, std::make_unique(std::move(lock))); - } - Triple_Buffer_Lease lease = runtime->try_mark_state_role(State_Render); +Render_State_Read_Guard Low_Latency_Renderable_Runtime::acquire_render_state_read() { + Triple_Buffer_Lease lease = d->try_mark_state_role(State_Render); if (!lease) return {}; - return Render_State_Read_Guard(runtime->state_by_index(lease.index), std::make_unique(runtime.get(), lease)); + return Render_State_Read_Guard(d->state_by_index(lease.index), std::make_unique(d.get(), lease)); } -Render_State_Read_Guard Strategy_Render_Data::acquire_edit_state_read() { - if (runtime->runtime_strategy_value == Renderable_Runtime_Strategy::Explicit) { - std::unique_lock lock(runtime->explicit_state_mutex); - if (!runtime->explicit_state) - return {}; - return Render_State_Read_Guard(runtime->explicit_state, std::make_unique(std::move(lock))); - } - Triple_Buffer_Lease lease = runtime->mark_state_role(State_Edit); +Render_State_Read_Guard Low_Latency_Renderable_Runtime::acquire_edit_state_read() { + Triple_Buffer_Lease lease = d->mark_state_role(State_Edit); if (!lease) return {}; - return Render_State_Read_Guard(runtime->state_by_index(lease.index), std::make_unique(runtime.get(), lease)); + return Render_State_Read_Guard(d->state_by_index(lease.index), std::make_unique(d.get(), lease)); } -Render_State_Read_Guard Strategy_Render_Data::acquire_prepare_state_read() { - if (runtime->runtime_strategy_value != Renderable_Runtime_Strategy::Explicit) - return {}; - std::unique_lock lock(runtime->explicit_state_mutex); - if (!runtime->explicit_state) - return {}; - return Render_State_Read_Guard(runtime->explicit_state, std::make_unique(std::move(lock))); -} - -Render_State_Edit_Guard Strategy_Render_Data::acquire_edit_state() { - if (runtime->runtime_strategy_value == Renderable_Runtime_Strategy::Explicit) { - std::unique_lock lock(runtime->explicit_state_mutex); - if (!runtime->explicit_state) - return {}; - return Render_State_Edit_Guard(runtime->explicit_state, std::make_unique(runtime.get(), q_ptr, std::move(lock))); - } - Triple_Buffer_Lease lease = runtime->mark_state_role(State_Edit); +Render_State_Write_Guard Low_Latency_Renderable_Runtime::acquire_edit_state() { + Triple_Buffer_Lease lease = d->mark_state_role(State_Edit); if (!lease) return {}; - return Render_State_Edit_Guard(runtime->state_by_index(lease.index), std::make_unique(runtime.get(), q_ptr, lease)); + return Render_State_Write_Guard(d->state_by_index(lease.index), std::make_unique(d.get(), lease)); } -Render_Input_Edit_Guard Strategy_Render_Data::acquire_edit_input(bool notify_render) { - if (runtime->runtime_strategy_value == Renderable_Runtime_Strategy::Explicit) { - std::unique_lock lock(runtime->explicit_input_mutex); - if (!runtime->explicit_input.data) - return {}; - return Render_Input_Edit_Guard(runtime->explicit_input.data, std::make_unique(runtime.get(), q_ptr, std::move(lock), notify_render)); - } - auto record = runtime->input_control.acquire_role(Input_Edit); +Render_Input_Write_Guard Low_Latency_Renderable_Runtime::acquire_edit_input(bool notify_render) { + auto record = d->input_control.acquire_role(Input_Edit); if (record.result != Triple_Buffer_Result::Success) return {}; - Input_Data* input = runtime->input_data_by_index(record.lease.index); + Input_Data* input = d->input_data_by_index(record.lease.index); if (!input) { - runtime->input_control.unmark_use(record.lease); + d->input_control.unmark_use(record.lease); return {}; } - return Render_Input_Edit_Guard(input, std::make_unique(runtime.get(), q_ptr, record.lease, notify_render)); + return Render_Input_Write_Guard(input, std::make_unique(d.get(), record.lease, notify_render)); } -void Strategy_Render_Data::sync_state_pipeline() { - if (runtime->runtime_strategy_value == Renderable_Runtime_Strategy::Explicit) - return; - auto edit_record = runtime->state_control.try_acquire_role(State_Edit); - if (edit_record.result != Triple_Buffer_Result::Success) - return; - auto ready_record = runtime->state_control.try_acquire_role(State_Ready); - if (ready_record.result != Triple_Buffer_Result::Success) { - runtime->state_control.unmark_use(edit_record.lease); - return; +Renderable_Frame_View Low_Latency_Renderable_Runtime::capture_frame_view() { + d->sync_state_pipeline(); + Triple_Buffer_Lease state_lease = d->try_mark_state_role(State_Render); + if (!state_lease) + return {}; + auto state_guard = Render_State_Read_Guard( + d->state_by_index(state_lease.index), + std::make_unique(d.get(), state_lease)); + + Render_Input_Frame_Guard input_guard; + Triple_Buffer_Lease input_lease = d->try_mark_input_role(Input_Render); + if (input_lease) { + Input_Data* input = d->input_data_by_index(input_lease.index); + if (input) { + input_guard = Render_Input_Frame_Guard( + input, + std::make_unique(d.get(), input_lease)); + } + else { + d->input_control.unmark_use(input_lease); + } } - Render_State* ready = runtime->state_by_index(ready_record.lease.index); - Render_State* edit = runtime->state_by_index(edit_record.lease.index); - if (ready && edit && buffer_version_newer(edit->version, ready->version)) - copy_render_state_object(*ready, *edit); - runtime->state_control.unmark_use(ready_record.lease); - runtime->state_control.unmark_use(edit_record.lease); - runtime->state_control.try_swap_role(State_Ready, State_Render, [this](const Triple_Buffer_View& view) { - auto* ready = runtime->state_by_index(view[State_Ready]); - auto* render = runtime->state_by_index(view[State_Render]); - return ready && render && buffer_version_newer(ready->version, render->version); - }); - runtime->input_control.try_swap_role(Input_Edit, Input_Ready, [this](const Triple_Buffer_View& view) { - const Input_Buffer_Slot& edit = runtime->input_slot_by_index(view[Input_Edit]); - const Input_Buffer_Slot& ready = runtime->input_slot_by_index(view[Input_Ready]); - return edit.pending && buffer_version_newer(edit.version, ready.version); - }); - runtime->input_control.try_swap_role(Input_Ready, Input_Render, [this](const Triple_Buffer_View& view) { - const Input_Buffer_Slot& ready = runtime->input_slot_by_index(view[Input_Ready]); - const Input_Buffer_Slot& render = runtime->input_slot_by_index(view[Input_Render]); - return ready.pending && buffer_version_newer(ready.version, render.version); - }); + return Renderable_Frame_View(std::move(state_guard), std::move(input_guard)); } -Render_State* Strategy_Render_Data::state(Renderable_State_Role role) { - return runtime->runtime_strategy_value == Renderable_Runtime_Strategy::Explicit - ? runtime->explicit_state - : runtime->state(state_role_index(role)); +Render_State* Low_Latency_Renderable_Runtime::state(Renderable_State_Role role) { + return d->state(state_role_index(role)); } -Input_Data* Strategy_Render_Data::input_data(Renderable_Input_Role role) { - return runtime->runtime_strategy_value == Renderable_Runtime_Strategy::Explicit - ? runtime->explicit_input.data - : runtime->input_data(input_role_index(role)); +Input_Data* Low_Latency_Renderable_Runtime::input_data(Renderable_Input_Role role) { + return d->input_data(input_role_index(role)); } -void Strategy_Render_Data::clear_render_input() { - runtime->clear_input_role(Renderable_Input_Role::Render); +void Low_Latency_Renderable_Runtime::clear_render_input() { + d->clear_input_role(Renderable_Input_Role::Render); +} + +std::unique_ptr make_low_latency_renderable_runtime(Renderable& owner) { + return std::make_unique(owner); } } // namespace renderive diff --git a/Core/plot/Plot_Core.cpp b/Core/plot/Plot_Core.cpp index feed2b5..320a183 100644 --- a/Core/plot/Plot_Core.cpp +++ b/Core/plot/Plot_Core.cpp @@ -84,6 +84,7 @@ public: job->render_snapshot.frame_update_states = &buffer->presented_update_states; previous_frame_time_ns = submit_time; job->renderables = renderable_tree_snapshot(); + job->render_snapshot.frame_tree = &job->renderables; auto& scheduler = Global::instance()->render_scheduler(); bool submitted = scheduler.try_submit_render_task(Render_Executor_Task{ plot_execution_id, @@ -136,7 +137,7 @@ public: const auto& renderable = node.renderable; if (!renderable || !renderable->should_prepare()) continue; - renderable->prepare_data(job->render_snapshot); + renderable->prepare_data(job->render_snapshot, node.frame_view); ++frame.prepared_output_count; } frame.prepare_end_ns = steady_now_ns(); @@ -530,8 +531,8 @@ bool Plot_Core::view_active() const { void Plot_Core::set_presentation_sink(std::weak_ptr sink) { d->sink = std::move(sink); } -Renderable_Runtime_Strategy Plot_Core::renderable_runtime_strategy() const { - return d->flow ? d->flow->renderable_runtime_strategy() : Renderable_Runtime_Strategy::Explicit; +std::unique_ptr Plot_Core::create_renderable_runtime(Renderable& owner) const { + return d->flow ? d->flow->create_renderable_runtime(owner) : nullptr; } Present_Surface_Lease Plot_Core::begin_present() { Present_Surface_Lease result; diff --git a/Core/plot/Plot_Core.h b/Core/plot/Plot_Core.h index d9b2d19..d509223 100644 --- a/Core/plot/Plot_Core.h +++ b/Core/plot/Plot_Core.h @@ -14,6 +14,7 @@ namespace renderive { class Plot_Core_Private; struct Plot_Core_Context_Access; class Renderable; +class Renderable_Runtime; class Plot_Core { public: @@ -43,7 +44,7 @@ public: [[nodiscard]] bool view_active() const; void set_presentation_sink(std::weak_ptr sink); - [[nodiscard]] Renderable_Runtime_Strategy renderable_runtime_strategy() const; + [[nodiscard]] std::unique_ptr create_renderable_runtime(Renderable& owner) const; Present_Surface_Lease begin_present(); void end_present(Present_Surface_Lease&& frame, std::uint64_t paint_begin_ns, std::uint64_t paint_end_ns); diff --git a/Core/plottable/Afterglow_p.h b/Core/plottable/Afterglow_p.h index ec76b13..6db765d 100644 --- a/Core/plottable/Afterglow_p.h +++ b/Core/plottable/Afterglow_p.h @@ -44,14 +44,14 @@ struct Afterglow_Private : Typed_Render_Data power_range_data, const Afterglow_Render_State* s, const Color_Scale_Snapshot& color_scale); - void draw_image(Canvas& canvas, const Afterglow_Render_State* s, const Color_Scale_Snapshot& color_scale) { + void draw_image(Canvas& canvas, const Render_Frame_Snapshot& snapshot, const Afterglow_Render_State* s, const Color_Scale_Snapshot& color_scale) { auto frequency_axis = s->frequency_axis.lock(); auto power_axis = s->power_axis.lock(); if (!frequency_axis || !power_axis || image.is_null()) return; if (s->frequency_range.length() == 0.0 || color_scale.range.length() == 0.0) return; - Axis_Mapping_2D mapping = Axis_Render_Access::mapping(frequency_axis.get(), power_axis.get()); + Axis_Mapping_2D mapping = Axis_Render_Access::mapping(frequency_axis.get(), power_axis.get(), snapshot); Axis_Basis_2D basis = Axis_Basis_2D::from_ranges(mapping, s->frequency_range, color_scale.range); canvas.save(); canvas.transform( @@ -64,14 +64,18 @@ struct Afterglow_Private : Typed_Render_Data(image.width()), static_cast(image.height())}); canvas.restore(); } - void draw(Canvas& canvas, const Render_Frame_Snapshot&) override { - Afterglow_Render_State* s = render_state(); + void draw(Canvas& canvas, const Render_Frame_Snapshot& snapshot, const Renderable_Frame_View& frame_view) override { + Afterglow_Render_State* s = render_state(frame_view); + if (!s) + return; auto color_scale = s->color_scale.snapshot(); - draw_image(canvas, s, *color_scale); + draw_image(canvas, snapshot, s, *color_scale); } - void prepare_data(const Render_Frame_Snapshot&) override { - sync_state_pipeline(); - Afterglow_Render_State* s = render_state(); + void prepare_data(const Render_Frame_Snapshot&, const Renderable_Frame_View& frame_view) override { + Afterglow_Render_State* s = render_state(frame_view); + Afterglow_Input_Data* input = render_input_data(frame_view); + if (!s || !input) + return; if (s->frequency_bin_count <= 0 || s->power_bin_count <= 0) return; auto color_scale = s->color_scale.snapshot(); @@ -89,7 +93,6 @@ struct Afterglow_Private : Typed_Render_Datapending_spectra.read_all([this, s, color_scale](const auto& data) { if (data.size() != s->frequency_bin_count) { return; @@ -100,7 +103,7 @@ struct Afterglow_Private : Typed_Render_Datafrequency_bin_count, s->power_bin_count, color_scale->color_map); image_dirty = false; } - clear_render_input(); + frame_view.consume_input(); } }; template diff --git a/Core/plottable/Constellation_Diagram_p.h b/Core/plottable/Constellation_Diagram_p.h index 30dddac..51a6d7b 100644 --- a/Core/plottable/Constellation_Diagram_p.h +++ b/Core/plottable/Constellation_Diagram_p.h @@ -76,10 +76,11 @@ struct Constellation_Diagram_Private : Typed_Render_Data(std::max(1, s->point_lifetime_ms)) * 1000000ull; d->read_all([this, now_ns, lifetime_ns](const PointF& pos) { @@ -89,7 +90,7 @@ struct Constellation_Diagram_Private : Typed_Render_Datai_axis.lock(); auto q_axis = s->q_axis.lock(); if (!i_axis || !q_axis) return; - Axis_Mapping_2D mapping = Axis_Render_Access::mapping(i_axis.get(), q_axis.get()); + Axis_Mapping_2D mapping = Axis_Render_Access::mapping(i_axis.get(), q_axis.get(), snapshot); canvas.save(); draw_grid(canvas, mapping, s); draw_points(canvas, mapping); diff --git a/Core/plottable/Frequency_Trace_p.h b/Core/plottable/Frequency_Trace_p.h index becff4a..f0df955 100644 --- a/Core/plottable/Frequency_Trace_p.h +++ b/Core/plottable/Frequency_Trace_p.h @@ -1,4 +1,6 @@ #pragma once +#include +#include #include #include #include @@ -41,8 +43,19 @@ struct Frequency_Trace_Private : Typed_Render_Data data_snapshot{memory_resource(Memory_Domain::Audio)}; std::pmr::vector prepared_points{memory_resource(Memory_Domain::Audio)}; + std::pmr::vector segment_offsets{memory_resource(Memory_Domain::Audio)}; int data_count{}; int point_count{}; + int tick_gap_count{}; + int non_monotonic_tick_count{}; + int segment_count{}; + static bool trace_debug_enabled() { + static const bool enabled = []() { + const char* value = std::getenv("RENDERIVE_FREQUENCY_TRACE_DEBUG"); + return value && std::atoi(value) != 0; + }(); + return enabled; + } void resize_power_buffer(int count) { point_count = count; data_count = 0; @@ -60,12 +73,64 @@ struct Frequency_Trace_Private : Typed_Render_Data(copied / sizeof(Frequency_Sample)); } - void prepare_data(const Render_Frame_Snapshot& snapshot) override { - sync_state_pipeline(); - Frequency_Trace_Render_State* s = render_state(); - Frequency_Trace_Input_Data* d = render_input_data(); + void rebuild_prepared_points(const Timeline_Stream_Snapshot& timeline) { + prepared_points.clear(); + segment_offsets.clear(); + tick_gap_count = 0; + non_monotonic_tick_count = 0; + segment_count = 0; + prepared_points.reserve(static_cast(data_count)); + segment_offsets.reserve(static_cast(data_count)); + + bool in_segment = false; + bool has_previous_tick = false; + int previous_tick = 0; + auto start_segment = [this, &in_segment]() { + if (in_segment) + return; + segment_offsets.push_back(prepared_points.size()); + in_segment = true; + }; + for (int i = 0; i < data_count; ++i) { + const Frequency_Sample& item = data_snapshot[static_cast(i)]; + int coord{}; + if (!timeline.coord_by_stream_tick(item.tick, coord)) { + in_segment = false; + has_previous_tick = false; + continue; + } + if (has_previous_tick) { + if (item.tick <= previous_tick) { + ++non_monotonic_tick_count; + in_segment = false; + } + else if (item.tick != previous_tick + 1) { + ++tick_gap_count; + in_segment = false; + } + } + start_segment(); + prepared_points.emplace_back(static_cast(coord), item.power); + previous_tick = item.tick; + has_previous_tick = true; + } + segment_count = static_cast(segment_offsets.size()); + if (trace_debug_enabled()) { + std::fprintf(stderr, + "frequency_tick_gap_count=%d frequency_non_monotonic_count=%d frequency_segment_count=%d\n", + tick_gap_count, + non_monotonic_tick_count, + segment_count); + } + } + void prepare_data(const Render_Frame_Snapshot& snapshot, const Renderable_Frame_View& frame_view) override { + Frequency_Trace_Render_State* s = render_state(frame_view); + Frequency_Trace_Input_Data* d = render_input_data(frame_view); + if (!s || !d) + return; auto time_axis = s->time_axis.lock(); auto value_axis = s->value_axis.lock(); if (!time_axis || !value_axis) @@ -79,30 +144,31 @@ struct Frequency_Trace_Private : Typed_Render_Data(data_count)); - for (int i = 0; i < data_count; ++i) { - const Frequency_Sample& item = data_snapshot[static_cast(i)]; - int coord{}; - if (timeline->coord_by_stream_tick(item.tick, coord)) - prepared_points.emplace_back(static_cast(coord), item.power); - } - clear_render_input(); + rebuild_prepared_points(*timeline); + frame_view.consume_input(); } - void draw(Canvas& canvas, const Render_Frame_Snapshot&) override { - Frequency_Trace_Render_State* s = render_state(); + void draw(Canvas& canvas, const Render_Frame_Snapshot& snapshot, const Renderable_Frame_View& frame_view) override { + Frequency_Trace_Render_State* s = render_state(frame_view); + if (!s) + return; auto time_axis = s->time_axis.lock(); auto value_axis = s->value_axis.lock(); if (!time_axis || !value_axis || prepared_points.empty()) return; - Axis_Mapping_2D mapping = Axis_Render_Access::mapping(time_axis.get(), value_axis.get()); - std::pmr::vector pixels(frame_memory_resource()); - pixels.reserve(prepared_points.size()); - for (const PointF& point : prepared_points) { - pixels.push_back(mapping.map(point)); - } + Axis_Mapping_2D mapping = Axis_Render_Access::mapping(time_axis.get(), value_axis.get(), snapshot); canvas.save(); - Curve_Utils::draw_polyline(&canvas, pixels, s->pen); + std::pmr::vector pixels(frame_memory_resource()); + for (std::size_t i = 0; i < segment_offsets.size(); ++i) { + const std::size_t begin = segment_offsets[i]; + const std::size_t end = i + 1 < segment_offsets.size() ? segment_offsets[i + 1] : prepared_points.size(); + if (end <= begin + 1) + continue; + pixels.clear(); + pixels.reserve(end - begin); + for (std::size_t point_index = begin; point_index < end; ++point_index) + pixels.push_back(mapping.map(prepared_points[point_index])); + Curve_Utils::draw_polyline(&canvas, pixels, s->pen); + } canvas.restore(); } }; diff --git a/Core/plottable/Hover_Tooltip.cpp b/Core/plottable/Hover_Tooltip.cpp index 64a7c57..bbcc8af 100644 --- a/Core/plottable/Hover_Tooltip.cpp +++ b/Core/plottable/Hover_Tooltip.cpp @@ -1,9 +1,9 @@ #include "Hover_Tooltip.h" #include "../Axis/Abs_Axis_p.h" namespace renderive { -void Hover_Tooltip_State::draw_hover_tooltip(Canvas* canvas, Abs_Axis* domain_axis, Abs_Axis* value_axis, Hover_Tooltip_Provider* renderable) { +void Hover_Tooltip_State::draw_hover_tooltip(Canvas* canvas, Abs_Axis* domain_axis, Abs_Axis* value_axis, Hover_Tooltip_Provider* renderable, const Render_Frame_Snapshot* snapshot) { Point pos = hover_position; - std::vector lines = renderable->build_hover_lines(domain_axis, value_axis, pos); + std::vector lines = renderable->build_hover_lines(domain_axis, value_axis, pos, snapshot); double max_width = 0; double line_height = 0; for (const std::string& s : lines) { @@ -28,13 +28,15 @@ bool Hover_Tooltip_State::can_show_hover(Renderable* able) const { auto tmp = dynamic_cast(able); return use_hover_tooltip && hover_info_active && tmp->hit_test_hover(hover_position); } -std::vector Hover_Tooltip_Provider::build_hover_lines(Abs_Axis* h, Abs_Axis* v, Point pos) { - Axis_Mapping_2D mapping = Axis_Render_Access::mapping(h, v); +std::vector Hover_Tooltip_Provider::build_hover_lines(Abs_Axis* h, Abs_Axis* v, Point pos, const Render_Frame_Snapshot* snapshot) { + Axis_Mapping_2D mapping = snapshot ? Axis_Render_Access::mapping(h, v, *snapshot) : Axis_Render_Access::mapping(h, v); PointF screen_point{static_cast(pos.x), static_cast(pos.y)}; double tick1 = mapping.domain.pixel_to_coord(mapping.domain_pixel(screen_point)); double tick2 = mapping.value.pixel_to_coord(mapping.value_pixel(screen_point)); - std::string line1 = Axis_Render_Access::tick_label(h, tick1) + h->unit_text(); - std::string line2 = Axis_Render_Access::tick_label(v, tick2) + v->unit_text(); + std::string line1 = (snapshot ? Axis_Render_Access::tick_label(h, tick1, *snapshot) : Axis_Render_Access::tick_label(h, tick1)) + + (snapshot ? Axis_Render_Access::unit_text(h, *snapshot) : h->unit_text()); + std::string line2 = (snapshot ? Axis_Render_Access::tick_label(v, tick2, *snapshot) : Axis_Render_Access::tick_label(v, tick2)) + + (snapshot ? Axis_Render_Access::unit_text(v, *snapshot) : v->unit_text()); return {line1, line2}; } } // namespace renderive diff --git a/Core/plottable/Hover_Tooltip.h b/Core/plottable/Hover_Tooltip.h index f4f9043..1ddc907 100644 --- a/Core/plottable/Hover_Tooltip.h +++ b/Core/plottable/Hover_Tooltip.h @@ -11,6 +11,7 @@ #include "../base/Text.h" #include "../render/Canvas.h" namespace renderive { +struct Render_Frame_Snapshot; struct Hover_Tooltip_State; struct Hover_Tooltip_Provider; struct LIB_DECL Hover_Tooltip_State { @@ -29,7 +30,7 @@ struct LIB_DECL Hover_Tooltip_State { bool use_hover_tooltip = true; bool hover_info_active = false; Point hover_position; - void draw_hover_tooltip(Canvas* canvas, Abs_Axis* domain_axis, Abs_Axis* value_axis, Hover_Tooltip_Provider* renderable); + void draw_hover_tooltip(Canvas* canvas, Abs_Axis* domain_axis, Abs_Axis* value_axis, Hover_Tooltip_Provider* renderable, const Render_Frame_Snapshot* snapshot = nullptr); bool can_show_hover(Renderable* able) const; }; struct LIB_DECL Hover_Tooltip_Provider { @@ -55,7 +56,7 @@ struct LIB_DECL Hover_Tooltip_Provider { struct Hover_Edit_Guard { Hover_Edit_Guard() = default; - Hover_Edit_Guard(Hover_Tooltip_State* state, Render_State_Edit_Guard guard) + Hover_Edit_Guard(Hover_Tooltip_State* state, Render_State_Write_Guard guard) : state(state), guard(std::move(guard)) {} Hover_Edit_Guard(const Hover_Edit_Guard&) = delete; Hover_Edit_Guard& operator=(const Hover_Edit_Guard&) = delete; @@ -70,15 +71,18 @@ struct LIB_DECL Hover_Tooltip_Provider { } Hover_Tooltip_State* state{}; - Render_State_Edit_Guard guard; + Render_State_Write_Guard guard; }; - virtual std::vector build_hover_lines(Abs_Axis* h, Abs_Axis* v, Point pos); + virtual std::vector build_hover_lines(Abs_Axis* h, Abs_Axis* v, Point pos, const Render_Frame_Snapshot* snapshot = nullptr); virtual ~Hover_Tooltip_Provider() = default; virtual void draw_hover_tooltip(Canvas* canvas, Abs_Axis* h, Abs_Axis* v) { - return hover_render_state()->draw_hover_tooltip(canvas, h, v, this); + auto state = begin_hover_read(); + if (state) + state->draw_hover_tooltip(canvas, h, v, this); } virtual bool hover_ok(Renderable* able) { - return hover_render_state()->can_show_hover(able); + auto state = begin_hover_read(); + return state && state->can_show_hover(able); } virtual bool hit_test_hover(Point pos) { (void)pos; @@ -151,7 +155,6 @@ struct LIB_DECL Hover_Tooltip_Provider { return begin_hover_read_impl(); } private: - virtual Hover_Tooltip_State* hover_render_state() = 0; virtual Hover_Read_Guard begin_hover_read_impl() = 0; virtual Hover_Edit_Guard begin_hover_edit_impl() = 0; }; @@ -161,13 +164,9 @@ private: That* that() { return static_cast(this); } - Hover_Tooltip_State* hover_render_state() override { - auto t = that(); - return dynamic_cast(t->d_ptr->render_state()); - } Hover_Read_Guard begin_hover_read_impl() override { auto t = that(); - auto guard = t->d_ptr->acquire_edit_state_read(); + auto guard = t->d_ptr->acquire_state_read(Renderable_State_Role::Edit); auto* state = dynamic_cast(guard.state); if (!state) return {}; diff --git a/Core/plottable/Performance_Overlay.cpp b/Core/plottable/Performance_Overlay.cpp index 09fa720..cb436b2 100644 --- a/Core/plottable/Performance_Overlay.cpp +++ b/Core/plottable/Performance_Overlay.cpp @@ -50,6 +50,8 @@ std::string source_text(Refresh_Limit_Source source) { switch (source) { case Refresh_Limit_Source::Unknown: return "unknown"; + case Refresh_Limit_Source::Input: + return "input"; case Refresh_Limit_Source::Render: return "render"; case Refresh_Limit_Source::Paint: @@ -78,6 +80,30 @@ std::string outcome_text(Frame_Outcome outcome) { } return "unknown"; } +std::string bottleneck_text(Low_Latency_Bottleneck_State state) { + switch (state) { + case Low_Latency_Bottleneck_State::Unknown: + return "unknown"; + case Low_Latency_Bottleneck_State::Input_Limited: + return "input"; + case Low_Latency_Bottleneck_State::Producer_Limited: + return "producer"; + case Low_Latency_Bottleneck_State::Consumer_Limited: + return "consumer"; + case Low_Latency_Bottleneck_State::User_Limited: + return "user"; + } + return "unknown"; +} +std::string production_strategy_text(Low_Latency_Production_Strategy strategy) { + switch (strategy) { + case Low_Latency_Production_Strategy::Latency_Eager: + return "latency_eager"; + case Low_Latency_Production_Strategy::Cost_Balanced: + return "cost_balanced"; + } + return "latency_eager"; +} std::string csv_escape(const std::string& value) { std::string escaped; escaped.reserve(value.size() + 2); @@ -149,10 +175,19 @@ public: << frame.input_version << ',' << outcome_text(frame.outcome) << ',' << source_text(refresh.source) << ',' + << source_text(refresh.candidate_source) << ',' + << bottleneck_text(refresh.stable_bottleneck) << ',' + << bottleneck_text(refresh.candidate_bottleneck) << ',' + << refresh.candidate_age_ns << ',' + << refresh.candidate_samples << ',' + << production_strategy_text(refresh.production_strategy) << ',' << refresh.render_period_ns << ',' << refresh.paint_period_ns << ',' << refresh.user_period_ns << ',' << refresh.target_period_ns << ',' + << refresh.effective_min_render_interval_ns << ',' + << refresh.repeated_frame_count << ',' + << refresh.underrun_count << ',' << duration_ms(frame.render_request_ns, frame.render_begin_ns) << ',' << duration_ms(frame.prepare_begin_ns, frame.prepare_end_ns) << ',' << duration_ms(frame.draw_begin_ns, frame.draw_end_ns) << ',' @@ -209,7 +244,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,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"; + return "frame_id,input_version,outcome,limit_source,candidate_source,stable_bottleneck,candidate_bottleneck,candidate_age_ns,candidate_samples,production_strategy,render_period_ns,paint_period_ns,user_period_ns,target_period_ns,effective_min_render_interval_ns,repeated_frame_count,underrun_count,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 d1dae71..5ed68f3 100644 --- a/Core/plottable/Performance_Overlay_p.h +++ b/Core/plottable/Performance_Overlay_p.h @@ -624,6 +624,8 @@ struct Performance_Layout_Builder { switch (source) { case Refresh_Limit_Source::Unknown: return "unknown"; + case Refresh_Limit_Source::Input: + return "input"; case Refresh_Limit_Source::Render: return "render"; case Refresh_Limit_Source::Paint: @@ -825,7 +827,8 @@ struct Performance_Layout_Builder { double cache_hit_ratio = aggregate_cache_total ? static_cast(aggregate.cache_hit_count) * 100.0 / static_cast(aggregate_cache_total) : 0.0; lines.clear(); lines.push_back("[common] 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)) + " dirty:" + dirty_reason_text(frame.dirty_reason_mask)); - lines.push_back("[low_latency] limit:" + limit_source_text(refresh.source) + " bottleneck:" + bottleneck_text(refresh.bottleneck) + " strategy:" + production_strategy_text(refresh.production_strategy) + " efficiency:" + number_text("display_efficiency", refresh.display_efficiency * 100.0) + "%"); + lines.push_back("[low_latency] limit:" + limit_source_text(refresh.source) + " stable:" + bottleneck_text(refresh.stable_bottleneck) + " candidate:" + bottleneck_text(refresh.candidate_bottleneck) + " candidate_age:" + period_text("candidate_age", refresh.candidate_age_ns) + " samples:" + count_text("candidate_samples", refresh.candidate_samples)); + lines.push_back("[low_latency] bottleneck:" + bottleneck_text(refresh.bottleneck) + " strategy:" + production_strategy_text(refresh.production_strategy) + " effective:" + period_text("effective_interval", refresh.effective_min_render_interval_ns) + " efficiency:" + number_text("display_efficiency", refresh.display_efficiency * 100.0) + "%"); lines.push_back("[low_latency] period input:" + period_text("input_period", refresh.input_period_ns) + " 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("[low_latency] jitter input:" + ns_text("input_jitter", refresh.input_jitter_ns) + "ms render:" + ns_text("render_jitter", refresh.render_jitter_ns) + "ms paint:" + ns_text("paint_jitter", refresh.paint_jitter_ns) + "ms age:" + ns_text("frame_age_jitter", refresh.frame_age_jitter_ns) + "ms"); lines.push_back("[low_latency] 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"); diff --git a/Core/plottable/Selection_Rectangle_Overlay_p.h b/Core/plottable/Selection_Rectangle_Overlay_p.h index 74c8ada..5689acc 100644 --- a/Core/plottable/Selection_Rectangle_Overlay_p.h +++ b/Core/plottable/Selection_Rectangle_Overlay_p.h @@ -27,7 +27,7 @@ struct Selection_Rectangle_Overlay_Input_Data : Input_Data {}; struct Selection_Rectangle_Overlay_Private : Typed_Render_Data, Hit_Testable, Pointer_Interactive, Key_Press_Interactive { std::atomic_bool active{false}; bool select_test(const PointF& pos) override { - auto s = render_state(); + auto s = static_cast(state(Renderable_State_Role::Render)); auto horizontal_axis = s->horizontal_axis.lock(); auto vertical_axis = s->vertical_axis.lock(); if (!horizontal_axis || !vertical_axis) @@ -111,13 +111,15 @@ struct Selection_Rectangle_Overlay_Private : Typed_Render_Datahorizontal_axis.lock(); auto vertical_axis = s->vertical_axis.lock(); if (!horizontal_axis || !vertical_axis) return; - Axis_Mapping_2D mapping = Axis_Render_Access::mapping(horizontal_axis.get(), vertical_axis.get()); + Axis_Mapping_2D mapping = Axis_Render_Access::mapping(horizontal_axis.get(), vertical_axis.get(), snapshot); std::vector draw_rect_list; std::vector coord_rect_list; for (RectF r : s->rects) { @@ -130,9 +132,9 @@ struct Selection_Rectangle_Overlay_Private : Typed_Render_Dataunit_text_font()); + canvas.set_font(Axis_Render_Access::unit_text_font(horizontal_axis.get(), snapshot)); Text_Metrics xfm = canvas.measure_text("M"); - canvas.set_font(vertical_axis->unit_text_font()); + canvas.set_font(Axis_Render_Access::unit_text_font(vertical_axis.get(), snapshot)); Text_Metrics yfm = canvas.measure_text("M"); canvas.set_pen(s->selection_border_pen); canvas.set_brush(s->selection_brush); @@ -147,9 +149,9 @@ struct Selection_Rectangle_Overlay_Private : Typed_Render_Dataunit_text(); + + Axis_Render_Access::unit_text(horizontal_axis.get(), snapshot); std::string text2 = "offset_y == " + format_number(offset_y, Number_Format{Number_Notation::General, 6}) - + vertical_axis->unit_text(); + + Axis_Render_Access::unit_text(vertical_axis.get(), snapshot); double width1 = canvas.measure_text(text1).width + 32.0; double width2 = canvas.measure_text(text2).width + 32.0; double rect1_x = rect.x + rect.width / 2.0 - width1 / 2.0; diff --git a/Core/plottable/Spectrum.cpp b/Core/plottable/Spectrum.cpp index 887bfb8..e62b97e 100644 --- a/Core/plottable/Spectrum.cpp +++ b/Core/plottable/Spectrum.cpp @@ -290,14 +290,14 @@ double Spectrum_Private::get_y(double x, bool& ok, Line_Interpolation_Mode mode) return cur_powers.at(i); }); } -void Spectrum_Private::draw_power_curve(Canvas& canvas, Spectrum_Render_State* s, const std::pmr::vector& powers, const Pen& pen, const Brush& brush) { +void Spectrum_Private::draw_power_curve(Canvas& canvas, const Render_Frame_Snapshot& snapshot, Spectrum_Render_State* s, const std::pmr::vector& powers, const Pen& pen, const Brush& brush) { auto frequency_axis = s->frequency_axis.lock(); auto power_axis = s->power_axis.lock(); if (!frequency_axis || !power_axis) return; if (powers.size() < 2 || s->frequency_range.length() == 0.0) return; - Axis_Mapping_2D mapping = Axis_Render_Access::mapping(frequency_axis.get(), power_axis.get()); + Axis_Mapping_2D mapping = Axis_Render_Access::mapping(frequency_axis.get(), power_axis.get(), snapshot); std::pmr::vector points(frame_memory_resource()); int point_count = static_cast(powers.size()); if (s->sampling_strategy == Line_Sampling_Strategy::Pixel_Min_Max_Envelope) { @@ -315,7 +315,7 @@ void Spectrum_Private::draw_power_curve(Canvas& canvas, Spectrum_Render_State* s Curve_Utils::draw_fill_to_value(&canvas, points, mapping, mapping.value.coord_range.target, brush); canvas.restore(); } -void Spectrum_Private::update_marker_items(const Spectrum_Render_State* s) { +void Spectrum_Private::update_marker_items(const Spectrum_Render_State* s, const Render_Frame_Snapshot& snapshot) { auto frequency_axis = s->frequency_axis.lock(); auto power_axis = s->power_axis.lock(); prepared_marker_items.clear(); @@ -330,8 +330,8 @@ void Spectrum_Private::update_marker_items(const Spectrum_Render_State* s) { marker.pen = pen; marker.mode = draw_line ? Marker_Mode::Domain_Line : Marker_Mode::Point; prepared_marker_items.push_back(marker); - std::string frequency_text = std::to_string(frequency) + frequency_axis->unit_text(); - std::string power_text = std::to_string(power) + power_axis->unit_text(); + std::string frequency_text = std::to_string(frequency) + Axis_Render_Access::unit_text(frequency_axis.get(), snapshot); + std::string power_text = std::to_string(power) + Axis_Render_Access::unit_text(power_axis.get(), snapshot); Label_Item frequency_label; frequency_label.position = marker.position; frequency_label.offset = PointF(0.0, -28.0); @@ -356,17 +356,17 @@ void Spectrum_Private::update_marker_items(const Spectrum_Render_State* s) { append_marker(custom_marker.frequency, value, custom_marker.is_line_marker, i == s->cur_select_index); } } -void Spectrum_Private::draw_spectrum(Canvas& canvas, Spectrum_Render_State* s) { +void Spectrum_Private::draw_spectrum(Canvas& canvas, const Render_Frame_Snapshot& snapshot, Spectrum_Render_State* s) { if (s->sweep_region_visible) { - s->draw_axis_rect(canvas, s->sweep_frequency_range, s->center_frequency); + s->draw_axis_rect(canvas, snapshot, s->sweep_frequency_range, s->center_frequency); } } -void Spectrum_Private::draw_marker_items(Canvas& canvas, Spectrum_Render_State* s) { +void Spectrum_Private::draw_marker_items(Canvas& canvas, const Render_Frame_Snapshot& snapshot, Spectrum_Render_State* s) { auto frequency_axis = s->frequency_axis.lock(); auto power_axis = s->power_axis.lock(); if (!frequency_axis || !power_axis) return; - Axis_Mapping_2D mapping = Axis_Render_Access::mapping(frequency_axis.get(), power_axis.get()); + Axis_Mapping_2D mapping = Axis_Render_Access::mapping(frequency_axis.get(), power_axis.get(), snapshot); canvas.save(); for (const Marker_Item& item : prepared_marker_items) { PointF point = mapping.map(item.position); @@ -392,12 +392,12 @@ void Spectrum_Private::draw_marker_items(Canvas& canvas, Spectrum_Render_State* } canvas.restore(); } -void Spectrum_Private::draw_label_items(Canvas& canvas, Spectrum_Render_State* s) { +void Spectrum_Private::draw_label_items(Canvas& canvas, const Render_Frame_Snapshot& snapshot, Spectrum_Render_State* s) { auto frequency_axis = s->frequency_axis.lock(); auto power_axis = s->power_axis.lock(); if (!frequency_axis || !power_axis) return; - Axis_Mapping_2D mapping = Axis_Render_Access::mapping(frequency_axis.get(), power_axis.get()); + Axis_Mapping_2D mapping = Axis_Render_Access::mapping(frequency_axis.get(), power_axis.get(), snapshot); for (const Label_Item& item : prepared_label_items) { if (item.text.empty()) continue; @@ -425,21 +425,23 @@ void Spectrum_Private::draw_label_items(Canvas& canvas, Spectrum_Render_State* s canvas.restore(); } } -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); +void Spectrum_Private::draw(Canvas& canvas, const Render_Frame_Snapshot& snapshot, const Renderable_Frame_View& frame_view) { + auto s = render_state(frame_view); + if (!s) + return; + draw_power_curve(canvas, snapshot, s, cur_powers, s->cur_pen, s->current_brush); + draw_spectrum(canvas, snapshot, s); if (s->max_hold_visible) - draw_power_curve(canvas, s, max_powers, s->max_pen, s->max_brush); + draw_power_curve(canvas, snapshot, s, max_powers, s->max_pen, s->max_brush); if (s->min_hold_visible) - draw_power_curve(canvas, s, min_powers, s->min_pen, s->min_brush); - draw_marker_items(canvas, s); - draw_label_items(canvas, s); - if (q()->hover_ok(q())) { + draw_power_curve(canvas, snapshot, s, min_powers, s->min_pen, s->min_brush); + draw_marker_items(canvas, snapshot, s); + draw_label_items(canvas, snapshot, s); + if (s->can_show_hover(q())) { auto frequency_axis = s->frequency_axis.lock(); auto power_axis = s->power_axis.lock(); if (frequency_axis && power_axis) - q()->draw_hover_tooltip(&canvas, frequency_axis.get(), power_axis.get()); + s->draw_hover_tooltip(&canvas, frequency_axis.get(), power_axis.get(), q(), &snapshot); } } void Spectrum::update_samples(std::span line_data) { diff --git a/Core/plottable/Spectrum_p.h b/Core/plottable/Spectrum_p.h index 395aa49..bfe35fc 100644 --- a/Core/plottable/Spectrum_p.h +++ b/Core/plottable/Spectrum_p.h @@ -138,12 +138,12 @@ struct Spectrum_Render_State : Render_State, Hover_Tooltip_State, Spectrum_Prop return; marker_list[index].frequency = frequency; } - void draw_axis_rect(Canvas& canvas, Range range, double middle_x) const { + void draw_axis_rect(Canvas& canvas, const Render_Frame_Snapshot& snapshot, Range range, double middle_x) const { auto frequency_axis = this->frequency_axis.lock(); auto power_axis = this->power_axis.lock(); if (!frequency_axis || !power_axis) return; - Axis_Mapping_2D mapping = Axis_Render_Access::mapping(frequency_axis.get(), power_axis.get()); + Axis_Mapping_2D mapping = Axis_Render_Access::mapping(frequency_axis.get(), power_axis.get(), snapshot); canvas.save(); canvas.set_pen(Pen{.style = Line_Style::None}); canvas.set_brush(sweep_region_brush); @@ -177,27 +177,28 @@ struct Spectrum_Private : Typed_Render_Data prepared_label_items{memory_resource(Memory_Domain::Other)}; void update_frequency_point_size(int frequency_point_size, Range frequency_range); void set_frequency_data(std::pmr::vector& line_data, const std::pmr::vector* max_data, const std::pmr::vector* min_data, const Spectrum_Render_State* s); - void update_marker_items(const Spectrum_Render_State* s); + void update_marker_items(const Spectrum_Render_State* s, const Render_Frame_Snapshot& snapshot); [[nodiscard]] double get_y(double x, bool& ok, Line_Interpolation_Mode mode); - void draw_power_curve(Canvas& canvas, Spectrum_Render_State* s, const std::pmr::vector& powers, const Pen& pen, const Brush& brush); - void draw_spectrum(Canvas& canvas, Spectrum_Render_State* s); - void draw_marker_items(Canvas& canvas, Spectrum_Render_State* s); - void draw_label_items(Canvas& canvas, Spectrum_Render_State* s); - void draw(Canvas& canvas, const Render_Frame_Snapshot& snapshot) override; - void prepare_data(const Render_Frame_Snapshot&) override { - sync_state_pipeline(); - auto s = render_state(); + void draw_power_curve(Canvas& canvas, const Render_Frame_Snapshot& snapshot, Spectrum_Render_State* s, const std::pmr::vector& powers, const Pen& pen, const Brush& brush); + void draw_spectrum(Canvas& canvas, const Render_Frame_Snapshot& snapshot, Spectrum_Render_State* s); + void draw_marker_items(Canvas& canvas, const Render_Frame_Snapshot& snapshot, Spectrum_Render_State* s); + void draw_label_items(Canvas& canvas, const Render_Frame_Snapshot& snapshot, Spectrum_Render_State* s); + void draw(Canvas& canvas, const Render_Frame_Snapshot& snapshot, const Renderable_Frame_View& frame_view) override; + void prepare_data(const Render_Frame_Snapshot& snapshot, const Renderable_Frame_View& frame_view) override { + auto s = render_state(frame_view); + auto input = render_input_data(frame_view); + if (!s || !input) + return; if (cache_frequency_point_size != s->frequency_point_size || cache_frequency_range != s->frequency_range) { update_frequency_point_size(s->frequency_point_size, s->frequency_range); } - auto input = render_input_data(); input->read_all([this, s](std::pmr::vector& data, const std::pmr::vector* max_data, const std::pmr::vector* min_data) { set_frequency_data(data, max_data, min_data, s); }); if (has_power_data) { - update_marker_items(s); + update_marker_items(s, snapshot); } - clear_render_input(); + frame_view.consume_input(); } bool select_test(const PointF& pos) override { const auto sc = edit_state(); diff --git a/Core/plottable/Sweep_Spectrum_p.h b/Core/plottable/Sweep_Spectrum_p.h index 6f27524..2bb6bf5 100644 --- a/Core/plottable/Sweep_Spectrum_p.h +++ b/Core/plottable/Sweep_Spectrum_p.h @@ -51,10 +51,11 @@ struct Sweep_Spectrum_Private : renderive::Typed_Render_Databins_per_block || block_count != s->block_num || @@ -76,16 +77,17 @@ struct Sweep_Spectrum_Private : renderive::Typed_Render_Datafrequency_axis.lock(); auto power_axis = s->power_axis.lock(); if (!frequency_axis || !power_axis) return; - Axis_Mapping_2D mapping = Axis_Render_Access::mapping(frequency_axis.get(), power_axis.get()); + Axis_Mapping_2D mapping = Axis_Render_Access::mapping(frequency_axis.get(), power_axis.get(), snapshot); int total_point_count = (int)sweep_points.size(); int available_count = !first_sweep_incomplete ? total_point_count : next_block_index * bins_per_block; std::pmr::vector data(frame_memory_resource()); diff --git a/Core/plottable/Waterfall_p.h b/Core/plottable/Waterfall_p.h index ef9d713..4fac452 100644 --- a/Core/plottable/Waterfall_p.h +++ b/Core/plottable/Waterfall_p.h @@ -356,9 +356,10 @@ struct Waterfall_Private : Typed_Render_Datatime_axis.lock(); if (!time_axis) return; @@ -423,10 +424,11 @@ struct Waterfall_Private : Typed_Render_Datafrequency_axis.lock(); auto time_axis = s->time_axis.lock(); if (!frequency_axis || !time_axis) @@ -435,7 +437,7 @@ struct Waterfall_Private : Typed_Render_Datafrequency_range; Range v_range{static_cast(timeline.coordinate_begin), static_cast(timeline.coordinate_begin + timeline.visible_time_point_count)}; - if (s->visible_range_only && !intersect_range(s->frequency_range, Axis_Render_Access::coord_range(h_axis), h_range)) { + if (s->visible_range_only && !intersect_range(s->frequency_range, Axis_Render_Access::coord_range(h_axis, snapshot), h_range)) { image_snapshot.reset(); return; } @@ -498,14 +500,14 @@ struct Waterfall_Private : Typed_Render_Dataimage.empty()) return; if (image_snapshot->frequency_range.length() == 0.0 || image_snapshot->time_range.length() == 0.0) return; if (image_snapshot->source_rect.width == 0.0 || image_snapshot->source_rect.height == 0.0) return; - Axis_Mapping_2D mapping = Axis_Render_Access::mapping(h_axis, v_axis); + Axis_Mapping_2D mapping = Axis_Render_Access::mapping(h_axis, v_axis, snapshot); Axis_Basis_2D basis = Axis_Basis_2D::from_ranges(mapping, image_snapshot->frequency_range, image_snapshot->time_range); canvas.save(); canvas.set_image_interpolation(s->interpolation_mode); @@ -519,8 +521,10 @@ 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 Render_Frame_Snapshot& snapshot) override { - Waterfall_Render_State* s = render_state(); + void draw(Canvas& canvas, const Render_Frame_Snapshot& snapshot, const Renderable_Frame_View& frame_view) override { + Waterfall_Render_State* s = render_state(frame_view); + if (!s) + return; auto frequency_axis = s->frequency_axis.lock(); auto time_axis = s->time_axis.lock(); if (!frequency_axis || !time_axis) @@ -532,9 +536,9 @@ struct Waterfall_Private : Typed_Render_Datahover_ok(q())) { - q()->draw_hover_tooltip(&canvas, h_axis, v_axis); + draw_image(canvas, snapshot, s, h_axis, v_axis); + if (s->can_show_hover(q())) { + s->draw_hover_tooltip(&canvas, h_axis, v_axis, q(), &snapshot); } } }; diff --git a/Core/primitive/Band_Region.cpp b/Core/primitive/Band_Region.cpp index d39fb58..966e210 100644 --- a/Core/primitive/Band_Region.cpp +++ b/Core/primitive/Band_Region.cpp @@ -9,13 +9,15 @@ namespace renderive { struct Band_Region_Render_State : Render_State, Band_Region_Prop {}; struct Band_Region_Private : Typed_Render_Data { - void draw(Canvas& canvas, const Render_Frame_Snapshot&) override { - Band_Region_Render_State* s = render_state(); + void draw(Canvas& canvas, const Render_Frame_Snapshot& snapshot, const Renderable_Frame_View& frame_view) override { + Band_Region_Render_State* s = render_state(frame_view); + if (!s) + return; auto domain_axis = s->domain_axis.lock(); auto value_axis = s->value_axis.lock(); if (!domain_axis || !value_axis || s->range.length() == 0.0) return; - Axis_Mapping_2D mapping = Axis_Render_Access::mapping(domain_axis.get(), value_axis.get()); + Axis_Mapping_2D mapping = Axis_Render_Access::mapping(domain_axis.get(), value_axis.get(), snapshot); std::array points{}; if (s->orientation == Band_Axis::Domain) { points = { diff --git a/Core/primitive/Color_Bar.cpp b/Core/primitive/Color_Bar.cpp index fc7533a..967586c 100644 --- a/Core/primitive/Color_Bar.cpp +++ b/Core/primitive/Color_Bar.cpp @@ -48,8 +48,10 @@ struct Color_Bar_Private : Typed_Render_Datacolor_scale.snapshot(); if (scale->range.length() == 0.0) return; diff --git a/Core/primitive/Curve.cpp b/Core/primitive/Curve.cpp index 938eabb..91c9c4d 100644 --- a/Core/primitive/Curve.cpp +++ b/Core/primitive/Curve.cpp @@ -301,14 +301,15 @@ struct Curve_Private : Typed_Render_Datacompletion_state)); } } - void prepare_data(const Render_Frame_Snapshot& snapshot) override { + void prepare_data(const Render_Frame_Snapshot& snapshot, const Renderable_Frame_View& frame_view) override { (void)snapshot; - sync_state_pipeline(); consume_input_queue(); - submit_geometry_if_needed(); + submit_geometry_if_needed(snapshot, frame_view); } - void submit_geometry_if_needed() { - Curve_Render_State* s = render_state(); + void submit_geometry_if_needed(const Render_Frame_Snapshot& snapshot, const Renderable_Frame_View& frame_view) { + Curve_Render_State* s = render_state(frame_view); + if (!s) + return; auto domain_axis = s->domain_axis.lock(); auto value_axis = s->value_axis.lock(); if (!domain_axis || !value_axis) { @@ -319,7 +320,7 @@ struct Curve_Private : Typed_Render_Dataversion; @@ -350,8 +351,10 @@ struct Curve_Private : Typed_Render_Dataupdate_states = std::move(request->update_states); } - void draw(Canvas& canvas, const Render_Frame_Snapshot& snapshot) override { - Curve_Render_State* s = render_state(); + void draw(Canvas& canvas, const Render_Frame_Snapshot& snapshot, const Renderable_Frame_View& frame_view) override { + Curve_Render_State* s = render_state(frame_view); + if (!s) + return; if (!geometry_result) return; if (snapshot.frame_update_states) { diff --git a/Core/primitive/Fill_Area.cpp b/Core/primitive/Fill_Area.cpp index 3c7e369..6bf86c8 100644 --- a/Core/primitive/Fill_Area.cpp +++ b/Core/primitive/Fill_Area.cpp @@ -28,21 +28,24 @@ 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 Render_Frame_Snapshot&) override { - sync_state_pipeline(); - Fill_Area_Input_Data* input = render_input_data(); + void prepare_data(const Render_Frame_Snapshot&, const Renderable_Frame_View& frame_view) override { + Fill_Area_Input_Data* input = render_input_data(frame_view); + if (!input) + return; input->read_all([this](std::pmr::vector& points) { data_points.swap(points); }); - clear_render_input(); + frame_view.consume_input(); } - void draw(Canvas& canvas, const Render_Frame_Snapshot&) override { - Fill_Area_Render_State* s = render_state(); + void draw(Canvas& canvas, const Render_Frame_Snapshot& snapshot, const Renderable_Frame_View& frame_view) override { + Fill_Area_Render_State* s = render_state(frame_view); + if (!s) + return; auto domain_axis = s->domain_axis.lock(); auto value_axis = s->value_axis.lock(); if (!domain_axis || !value_axis || data_points.empty()) return; - Axis_Mapping_2D mapping = Axis_Render_Access::mapping(domain_axis.get(), value_axis.get()); + Axis_Mapping_2D mapping = Axis_Render_Access::mapping(domain_axis.get(), value_axis.get(), snapshot); std::pmr::vector polygon(frame_memory_resource()); if (s->mode == Fill_Area_Mode::To_Baseline) { polygon.resize(data_points.size() + 2); diff --git a/Core/primitive/Grid.cpp b/Core/primitive/Grid.cpp index c2ac436..182d4df 100644 --- a/Core/primitive/Grid.cpp +++ b/Core/primitive/Grid.cpp @@ -53,13 +53,15 @@ struct Grid_Private : Typed_Render_Data { canvas.draw_line(PointF{cross0, pixel}, PointF{cross1, pixel}); } } - void draw(Canvas& canvas, const Render_Frame_Snapshot&) override { - Grid_Render_State* s = render_state(); + void draw(Canvas& canvas, const Render_Frame_Snapshot& snapshot, const Renderable_Frame_View& frame_view) override { + Grid_Render_State* s = render_state(frame_view); + if (!s) + return; auto domain_axis = s->domain_axis.lock(); auto value_axis = s->value_axis.lock(); if (!domain_axis || !value_axis) return; - Axis_Mapping_2D mapping = Axis_Render_Access::mapping(domain_axis.get(), value_axis.get()); + Axis_Mapping_2D mapping = Axis_Render_Access::mapping(domain_axis.get(), value_axis.get(), snapshot); Range domain_range = mapping.domain.coord_range; Range value_range = mapping.value.coord_range; if (s->domain_range.length() != 0.0) diff --git a/Core/primitive/Label.cpp b/Core/primitive/Label.cpp index 3055f23..617bdf0 100644 --- a/Core/primitive/Label.cpp +++ b/Core/primitive/Label.cpp @@ -22,14 +22,15 @@ 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 Render_Frame_Snapshot&) override { - sync_state_pipeline(); - Label_Input_Data* input = render_input_data(); + void prepare_data(const Render_Frame_Snapshot&, const Renderable_Frame_View& frame_view) override { + Label_Input_Data* input = render_input_data(frame_view); + if (!input) + return; if (!input->pending) return; prepared_items.swap(input->items); use_prepared_items = true; - clear_render_input(); + frame_view.consume_input(); } void draw_item(Canvas& canvas, const Label_Item& item, const Axis_Mapping_2D& mapping) { if (item.text.empty()) @@ -59,13 +60,15 @@ struct Label_Private : Typed_Render_Datadomain_axis.lock(); auto value_axis = s->value_axis.lock(); if (!domain_axis || !value_axis) return; - Axis_Mapping_2D mapping = Axis_Render_Access::mapping(domain_axis.get(), value_axis.get()); + Axis_Mapping_2D mapping = Axis_Render_Access::mapping(domain_axis.get(), value_axis.get(), snapshot); if (use_prepared_items) { for (const Label_Item& item : prepared_items) draw_item(canvas, item, mapping); diff --git a/Core/primitive/Marker.cpp b/Core/primitive/Marker.cpp index 105a375..75c62bc 100644 --- a/Core/primitive/Marker.cpp +++ b/Core/primitive/Marker.cpp @@ -45,22 +45,25 @@ struct Marker_Private : Typed_Render_Datapending) return; prepared_items.swap(input->items); use_prepared_items = true; - clear_render_input(); + frame_view.consume_input(); } - void draw(Canvas& canvas, const Render_Frame_Snapshot&) override { - Marker_Render_State* s = render_state(); + void draw(Canvas& canvas, const Render_Frame_Snapshot& snapshot, const Renderable_Frame_View& frame_view) override { + Marker_Render_State* s = render_state(frame_view); + if (!s) + return; auto domain_axis = s->domain_axis.lock(); auto value_axis = s->value_axis.lock(); if (!domain_axis || !value_axis) return; - Axis_Mapping_2D mapping = Axis_Render_Access::mapping(domain_axis.get(), value_axis.get()); + Axis_Mapping_2D mapping = Axis_Render_Access::mapping(domain_axis.get(), value_axis.get(), snapshot); canvas.save(); if (use_prepared_items) { for (const Marker_Item& item : prepared_items) diff --git a/Core/primitive/Point_Set.cpp b/Core/primitive/Point_Set.cpp index dd259d1..198a525 100644 --- a/Core/primitive/Point_Set.cpp +++ b/Core/primitive/Point_Set.cpp @@ -44,14 +44,15 @@ struct Point_Set_Render_State : Render_State, Point_Set_Prop {}; struct Point_Set_Private : Typed_Render_Data { std::pmr::vector data_points{memory_resource(Memory_Domain::Point_Set)}; std::pmr::vector data_colors{memory_resource(Memory_Domain::Point_Set)}; - void prepare_data(const Render_Frame_Snapshot&) override { - sync_state_pipeline(); - Point_Set_Input_Data* input = render_input_data(); + void prepare_data(const Render_Frame_Snapshot&, const Renderable_Frame_View& frame_view) override { + Point_Set_Input_Data* input = render_input_data(frame_view); + if (!input) + return; input->read_all([this](std::pmr::vector& points, std::pmr::vector& colors) { data_points.swap(points); data_colors.swap(colors); }); - clear_render_input(); + frame_view.consume_input(); } void draw_point(Canvas& canvas, const PointF& point, const Point_Set_Render_State* s) { double half = s->size / 2.0; @@ -66,13 +67,15 @@ struct Point_Set_Private : Typed_Render_Datadomain_axis.lock(); auto value_axis = s->value_axis.lock(); if (!domain_axis || !value_axis || data_points.empty()) return; - Axis_Mapping_2D mapping = Axis_Render_Access::mapping(domain_axis.get(), value_axis.get()); + Axis_Mapping_2D mapping = Axis_Render_Access::mapping(domain_axis.get(), value_axis.get(), snapshot); std::pmr::vector points(frame_memory_resource()); points.reserve(data_points.size()); for (int i = 0; i < data_points.size(); ++i) { diff --git a/Core/primitive/Raster_Image.cpp b/Core/primitive/Raster_Image.cpp index edff5fa..08fef4a 100644 --- a/Core/primitive/Raster_Image.cpp +++ b/Core/primitive/Raster_Image.cpp @@ -22,23 +22,26 @@ struct Raster_Image_Input_Data : Latest_Input_Data { struct Raster_Image_Render_State : Render_State, Raster_Image_Prop {}; struct Raster_Image_Private : Typed_Render_Data { Image image; - void prepare_data(const Render_Frame_Snapshot&) override { - sync_state_pipeline(); - Raster_Image_Input_Data* input = render_input_data(); + void prepare_data(const Render_Frame_Snapshot&, const Renderable_Frame_View& frame_view) override { + Raster_Image_Input_Data* input = render_input_data(frame_view); + if (!input) + return; input->read_all([this](const Image& value) { image = value; }); - clear_render_input(); + frame_view.consume_input(); } - void draw(Canvas& canvas, const Render_Frame_Snapshot&) override { - Raster_Image_Render_State* s = render_state(); + void draw(Canvas& canvas, const Render_Frame_Snapshot& snapshot, const Renderable_Frame_View& frame_view) override { + Raster_Image_Render_State* s = render_state(frame_view); + if (!s) + return; auto domain_axis = s->domain_axis.lock(); auto value_axis = s->value_axis.lock(); if (!domain_axis || !value_axis || image.empty()) return; if (s->domain_range.length() == 0.0 || s->value_range.length() == 0.0) return; - Axis_Mapping_2D mapping = Axis_Render_Access::mapping(domain_axis.get(), value_axis.get()); + Axis_Mapping_2D mapping = Axis_Render_Access::mapping(domain_axis.get(), value_axis.get(), snapshot); Axis_Basis_2D basis = Axis_Basis_2D::from_ranges(mapping, s->domain_range, s->value_range); RectF source_rect = s->source_rect.empty() ? RectF{0.0, 0.0, (double)image.width(), (double)image.height()} : s->source_rect; if (source_rect.width == 0.0 || source_rect.height == 0.0) diff --git a/Core/render/Render_Frame_Snapshot.cpp b/Core/render/Render_Frame_Snapshot.cpp index 5952bc6..faffa85 100644 --- a/Core/render/Render_Frame_Snapshot.cpp +++ b/Core/render/Render_Frame_Snapshot.cpp @@ -1,4 +1,5 @@ #include "Render_Frame_Snapshot.h" +#include "../architecture/Renderable.h" #include "../architecture/Timeline_Stream.h" #include @@ -8,6 +9,28 @@ bool Render_Frame_Snapshot::destroying() const { return !destroying_token || destroying_token->load(std::memory_order_acquire); } +const Renderable_Frame_Node* Render_Frame_Snapshot::frame_node_for(const Renderable* renderable) const { + if (!renderable || !frame_tree) + return nullptr; + if (const auto* node = find_node(renderable->node_id())) + return node; + for (const auto& node : *frame_tree) { + if (node.renderable.get() == renderable) + return &node; + } + return nullptr; +} + +const Renderable_Frame_Node* Render_Frame_Snapshot::find_node(Renderable_Id id) const { + if (!id || !frame_tree) + return nullptr; + for (const auto& node : *frame_tree) { + if (node.node_id == id) + return &node; + } + return nullptr; +} + std::shared_ptr Render_Frame_Snapshot::timeline_snapshot( const std::shared_ptr& stream, int time_point_size, diff --git a/Core/render/Render_Frame_Snapshot.h b/Core/render/Render_Frame_Snapshot.h index 5f722fd..75ee867 100644 --- a/Core/render/Render_Frame_Snapshot.h +++ b/Core/render/Render_Frame_Snapshot.h @@ -2,6 +2,7 @@ #include "../architecture/Update_Completion.h" #include "../base/Color.h" #include "../base/Geometry.h" +#include "Renderable_Frame_Node.h" #include #include #include @@ -32,9 +33,12 @@ struct Render_Frame_Snapshot { std::uint64_t previous_frame_time_ns{}; Frame_Lifecycle_Record* frame_metadata{}; std::pmr::vector>* frame_update_states{}; + const Renderable_Frame_Tree* frame_tree{}; mutable std::shared_ptr timeline_cache = std::make_shared(); [[nodiscard]] bool destroying() const; + [[nodiscard]] const Renderable_Frame_Node* find_node(Renderable_Id id) const; + [[nodiscard]] const Renderable_Frame_Node* frame_node_for(const Renderable* renderable) const; [[nodiscard]] std::shared_ptr timeline_snapshot( const std::shared_ptr& stream, diff --git a/Core/render/Renderable_Frame_Node.h b/Core/render/Renderable_Frame_Node.h index 2b5ff1e..676b24f 100644 --- a/Core/render/Renderable_Frame_Node.h +++ b/Core/render/Renderable_Frame_Node.h @@ -1,4 +1,5 @@ #pragma once +#include "../architecture/Render_Data.h" #include #include #include @@ -8,18 +9,27 @@ namespace renderive { class Renderable; +using Renderable_Id = std::uint64_t; struct Renderable_Frame_Node { + Renderable_Frame_Node() = default; + Renderable_Frame_Node(const Renderable_Frame_Node&) = delete; + Renderable_Frame_Node& operator=(const Renderable_Frame_Node&) = delete; + Renderable_Frame_Node(Renderable_Frame_Node&&) noexcept = default; + Renderable_Frame_Node& operator=(Renderable_Frame_Node&&) noexcept = default; + ~Renderable_Frame_Node() = default; + std::shared_ptr renderable; std::size_t subtree_end{}; - std::uint64_t node_id{}; - std::uint64_t parent_node_id{}; + Renderable_Id node_id{}; + Renderable_Id parent_node_id{}; std::string parent_object_name; int render_tree_depth{}; std::string object_name; - std::uint64_t cache_parent_node_id{}; + Renderable_Id cache_parent_node_id{}; std::string cache_parent_object_name; int cache_tree_depth{}; + Renderable_Frame_View frame_view; }; using Renderable_Frame_Tree = std::vector; diff --git a/Renderive_实时渲染调度与三帧反馈设计.md b/Renderive_实时渲染调度与三帧反馈设计.md deleted file mode 100644 index b33dca3..0000000 --- a/Renderive_实时渲染调度与三帧反馈设计.md +++ /dev/null @@ -1,1523 +0,0 @@ -# Renderive 实时渲染调度与三帧反馈设计 - -## 1. 文档目标 - -本文定义 Renderive 实时图表的生产、发布、消费、反馈和限流模型。 - -该设计面向以下场景: - -- 核心数据持续更新; -- 后台线程生成完整图像帧; -- Qt 主线程通过 `paintEvent()` 消费帧; -- 系统采用三个可循环复用的帧槽位; -- 默认追求低延迟和最新状态; -- 不要求每个输入版本都被渲染; -- 不要求每个已生成帧都被显示; -- 需要根据输入、生产和消费能力自动调整生产频率; -- 反馈路径不得引入热路径锁竞争(反馈数据直接记录在帧槽位自身的元信息中,并随帧槽位所有权无锁返回); - -默认语义为: - -```text -Data_Driven 数据驱动 -Latest_Wins 最新数据优先 -Triple_Frame 三帧机制 -Bounded_In_Flight 有界在途帧 -Max_Render_Rate 最大渲染频率限制 -Lock_Free_Frame_Feedback 无锁帧反馈 -``` - -## 2. 核心目标 - -系统需要满足以下目标: - -1. 核心数据变化后尽快生成新帧; -2. 数据输入、后台生产和前端消费可以并行; -3. 输入过快时合并中间版本; -4. 生产者过慢时不积压渲染任务; -5. 消费者过慢时不积压历史帧; -6. 用户可以设置最大生产速率; -7. 生产速率根据消费反馈自动调节; -8. 生产能力不足时由完成事件自时钟运行; -9. 所有阶段的耗时和排队信息随帧流转; -10. 帧返回生产侧时完成统计采集; -11. 热路径不创建字符串、动态事件对象或无界队列; -12. 控制器避免瞬时样本导致频繁振荡。 - -## 3. 基本原则 - -### 3.1 Latest_Wins(最新数据优先) - -系统只关心当前最新有效状态。 - -当后台正在渲染版本 100,而输入端连续产生: - -```text -101 -102 -103 -104 -``` - -下一帧直接选择版本 104。 - -未开始渲染的 101、102、103 被合并,不进入待执行渲染任务队列。 - -### 3.2 Bounded_In_Flight(有界在途帧) - -系统只允许三个帧槽位参与流水线: - -```text -Triple_Frame 三帧机制 -├── Frame_Slot_0 第一帧槽位 -├── Frame_Slot_1 第二帧槽位 -└── Frame_Slot_2 第三帧槽位 -``` - -三个帧槽位结构完全相同,通过 `Frame_State` 动态表示空闲、生产、就绪和消费状态,不绑定固定物理角色。 - -同一时刻最多存在: - -```text -一个 Producing 状态帧槽位 生产端正在写入 -一个 Ready 状态帧槽位 等待消费者取得 -一个 Consuming 状态帧槽位 消费者正在读取 -``` - -不建立大容量历史帧 FIFO。 - -### 3.3 Event_Driven(事件驱动) - -调度器不依赖持续轮询。 - -核心事件包括: - -```text -Input_Changed 输入数据已更新 -Render_Completed 渲染生产已完成 -Frame_Consumed 帧已消费 -Rate_Limit_Deadline_Reached 频率限制截止时间已到达 -Control_Updated 控制参数已更新 -``` - -### 3.4 State_Decides_Now,Statistics_Decide_Trend(状态决定当前,统计决定趋势) - -即时行为由当前状态决定: - -```text -是否有未渲染的新数据 -是否正在渲染 -是否存在 Ready 状态帧槽位 -消费者是否正在使用帧 -是否达到最大生产速率允许时间 -当前策略是否允许替代旧的 Ready 状态帧 -``` - -长期调节由平滑统计决定: - -```text -输入间隔 -生产耗时 -消费间隔 -GUI 排队时间 -Ready 状态帧等待时间 -帧年龄 -覆盖率 -断供率 -``` - -## 4. 命名约定 - -所有状态、事件、策略和字段使用下划线命名。 - -### 4.2 瓶颈状态 - -```text -Bottleneck_State 瓶颈状态 -├── Unknown 未知 -├── Input_Limited 输入受限 -├── Producer_Limited 生产端受限 -├── Consumer_Limited 消费端受限 -├── Manual_Limited 手动上限受限 -└── Balanced 均衡状态 -``` - -### 4.3 调度事件 - -```text -Scheduler_Event 调度事件 -├── Input_Changed 输入数据已更新 -├── Render_Completed 渲染生产已完成 -├── Frame_Consumed 帧已消费 -├── Rate_Limit_Deadline_Reached 频率限制截止时间已到达 -└── Control_Updated 控制参数已更新 -``` - -### 4.4 三帧槽位 - -三帧机制不是三个具有固定角色的缓冲区,而是三个结构完全相同、可循环复用的帧槽位: - -```text -Triple_Frame 三帧机制 -├── Frame_Slot_0 第一帧槽位 -├── Frame_Slot_1 第二帧槽位 -└── Frame_Slot_2 第三帧槽位 -``` - -每个帧槽位都是一份完整帧记录: - -```text -Frame_Slot 帧槽位 -├── Frame_Data 帧数据 -├── Frame_Metadata 帧元信息 -└── Frame_State 帧生命周期状态 -``` - -其中: - -- `Frame_Data`:当前帧实际使用的数据,例如渲染结果、绘制命令、顶点数据、索引数据、实例数据或场景快照; -- `Frame_Metadata`:该帧自身携带的版本、时间、耗时、工作量和反馈信息; -- `Frame_State`:该帧槽位当前所处的生命周期状态,并表示当前所有权阶段。 - -帧槽位状态为: - -```text -Frame_State 帧生命周期状态 -├── Free 空闲 -├── Producing 生产中 -├── Ready 已就绪 -└── Consuming 消费中 -``` - -状态流转为: - -```text -Free 空闲 - ↓ -Producing 生产中 - ↓ -Ready 已就绪 - ↓ -Consuming 消费中 - ↓ -Free 空闲 -``` - -`Returned_Frame` 可以描述消费者刚完成并归还的帧: - -```text -Returned_Frame 已归还帧 -``` - -`Returned_Frame` 只是所有权交接过程中的瞬时逻辑角色,不是第四个物理帧槽位,也不必成为持久的 `Frame_State`。 - -## 5. 系统角色 - -### 5.1 数据输入端 - -数据输入端负责接收所有会改变最终图像的状态: - -```text -核心数据 -坐标范围 -图表尺寸 -样式属性 -布局状态 -交互状态 -可见性状态 -其他渲染依赖 -``` - -每次有效变化更新: - -```text -latest_input_version -``` - -数据输入端只保存当前最新状态,不为每个版本创建独立渲染任务。 - -需要区分: - -```text -原始提交次数 -有效核心数据变化次数 -``` - -只有会改变最终图像的有效变化才参与输入速率统计。 - -### 5.2 生产端 - -生产端负责一张帧的完整生成过程: - -```text -收集一致快照 -→ 准备绘制数据 -→ 执行颜色缓冲绘制 -→ 填写生产阶段元数据 -→ 发布完整帧 -``` - -同一时间只允许一个完整帧生产任务运行。 - -生产完成后发出: - -```text -Render_Completed 渲染生产已完成 -``` - -生产端不直接决定是否继续生产下一帧。 - -### 5.3 消费端 - -消费端对应 Qt 主线程中的 `paintEvent()`。 - -消费端负责: - -```text -取得最新完整帧 -记录实际消费版本 -记录 paintEvent 开始时间 -执行图像显示 -记录 paintEvent 结束时间 -释放当前 `Consuming` 状态帧槽位 -``` - -消费端只上报事实,不直接控制后台生产线程。 - -`paintEvent()` 结束后不会无条件再次调用 `update()`。 - -### 5.4 Plot 调度器 - -Plot 调度器是唯一的全局决策者。 - -核心状态包括: - -```text -latest_input_version -rendering_input_version -published_input_version -consumed_input_version - -render_busy -ready_valid -consumer_busy -update_pending - -next_render_time -manual_min_render_interval -adaptive_min_render_interval - -production_policy -bottleneck_state -``` - -调度器负责决定: - -```text -立即开始生产 -等待最大速率期限 -等待消费者释放 -请求一次 QWidget::update() -保持等待并继续合并输入 -``` - -## 6. 三帧生命周期 - -三个帧槽位结构完全相同,不预先绑定生产端、就绪区或消费端。 - -每个帧槽位都按照以下状态循环: - -```text -Frame_State 帧生命周期状态 -├── Free 空闲,可由生产端取得 -├── Producing 生产中,由生产端独占写入 -├── Ready 已就绪,等待消费者取得 -└── Consuming 消费中,由消费者独占读取 -``` - -正常状态流转为: - -```text -Free 空闲 -→ Producing 生产中 -→ Ready 已就绪 -→ Consuming 消费中 -→ Free 空闲 -``` - -任意时刻,三个帧槽位可以分别处于不同状态,例如: - -```text -Frame_Slot_0 Consuming 正在显示 Frame_N -Frame_Slot_1 Ready 保存 Frame_N_Plus_1 -Frame_Slot_2 Producing 正在生成 Frame_N_Plus_2 -``` - -下一时刻,各槽位可以继续交换状态: - -```text -Frame_Slot_0 Free 已完成消费并归还 -Frame_Slot_1 Consuming 消费者正在显示 -Frame_Slot_2 Ready 新帧已经发布 -``` - -三帧机制不保存长历史帧序列。 - -处于 `Ready` 状态的帧槽位表示当前最新可消费帧,不表示 FIFO 中下一张必须显示的历史帧。 - -生产、就绪和消费是帧槽位的动态生命周期状态,不是三个物理帧槽位的固定身份。 - -## 7. Frame_Metadata(帧元信息) - -每个帧槽位携带固定大小的 `Frame_Metadata`。 - -```text -Frame_Metadata 帧元信息 -├── frame_id 帧唯一标识 -├── input_version 核心输入数据版本 -├── snapshot_version 完整状态快照版本 -├── frame_slot_generation 帧槽位复用代次 -├── core_data_time 核心数据时间 -├── source_data_time_begin 最早有效源数据时间 -├── source_data_time_end 最新有效源数据时间 -├── snapshot_capture_begin_time 快照采集开始时间 -├── snapshot_capture_end_time 快照采集结束时间 -├── render_request_time 调度器批准生产时间 -├── render_queue_enter_time 渲染任务进入后台队列时间 -├── render_begin_time 后台线程开始执行时间 -├── prepare_begin_time 绘制数据准备开始时间 -├── prepare_end_time 绘制数据准备结束时间 -├── draw_begin_time 最终绘制开始时间 -├── draw_end_time 最终绘制结束时间 -├── render_end_time 完整帧生产结束时间 -├── publish_time 帧发布为 Ready 状态的时间 -├── update_request_id GUI 更新请求标识 -├── update_request_time GUI 更新请求入队时间 -├── paint_begin_time paintEvent 开始时间 -├── paint_end_time paintEvent 结束时间 -├── consumer_release_time 消费者释放帧槽位时间 -├── superseded_time 帧被更新帧替代的时间 -├── frame_outcome 帧最终结果 -├── raw_input_count 原始输入数量 -├── visible_input_count 可见输入数量 -├── prepared_output_count 准备后的输出数量 -├── draw_point_count 绘制点数量 -├── draw_segment_count 绘制线段数量 -├── pixel_width 帧像素宽度 -├── pixel_height 帧像素高度 -└── renderable_count 可渲染对象数量 -``` - -`Frame_Metadata` 与帧数据一起经历: - -```text -生产 -→ 发布 -→ 消费 -→ 返回 -→ 聚合统计 -→ 清理 -→ 下一代复用 -``` - -热路径中不通过独立动态事件对象传递帧级统计。 - -反馈属于具体帧,并随帧槽位所有权一起返回生产侧。 - -## 8. 帧身份信息 - -每个帧携带: - -```text -frame_id 帧唯一标识 -input_version 核心输入数据版本 -snapshot_version 完整状态快照版本 -frame_slot_generation 帧槽位复用代次 -``` - -### frame_id(帧唯一标识) - -标识一次实际完成的帧生产。 - -### input_version(输入版本) - -标识该帧使用的核心输入数据版本。 - -### snapshot_version(快照版本) - -标识该帧使用的完整状态快照版本。 - -### frame_slot_generation(帧槽位复用代次) - -标识同一个物理帧槽位的复用代次,避免旧反馈与新帧混淆。 - -## 9. 数据时间信息 - -每个帧携带: - -```text -core_data_time -source_data_time_begin -source_data_time_end -snapshot_capture_begin_time -snapshot_capture_end_time -``` - -含义: - -```text -core_data_time -触发该帧生产的核心数据时间 - -source_data_time_begin -该帧使用的最早有效数据时间 - -source_data_time_end -该帧使用的最新有效数据时间 - -snapshot_capture_begin_time -开始收集一致状态快照的时间 - -snapshot_capture_end_time -完成一致状态快照收集的时间 -``` - -可派生: - -```text -snapshot_capture_duration -data_age_at_snapshot -``` - -## 10. 生产阶段时间信息 - -每个帧携带: - -```text -render_request_time -render_queue_enter_time -render_begin_time -prepare_begin_time -prepare_end_time -draw_begin_time -draw_end_time -render_end_time -publish_time -``` - -含义: - -```text -render_request_time -调度器批准生产该帧的时间 - -render_queue_enter_time -任务进入后台任务队列的时间 - -render_begin_time -后台工作线程实际开始执行的时间 - -prepare_begin_time -开始准备绘制数据的时间 - -prepare_end_time -完成绘制数据准备的时间 - -draw_begin_time -开始执行最终颜色缓冲绘制的时间 - -draw_end_time -完成最终颜色缓冲绘制的时间 - -render_end_time -完整帧生产任务结束的时间 - -publish_time -帧槽位从 `Producing` 状态发布为 `Ready` 状态的时间 -``` - -可派生: - -```text -render_queue_wait -= render_begin_time - render_queue_enter_time - -snapshot_to_render_wait -= render_begin_time - snapshot_capture_end_time - -prepare_duration -= prepare_end_time - prepare_begin_time - -draw_duration -= draw_end_time - draw_begin_time - -render_duration -= render_end_time - render_begin_time - -production_lifecycle_duration -= publish_time - render_request_time -``` - -## 11. 发布和 GUI 排队时间信息 - -每个帧携带: - -```text -update_request_id -update_request_time -paint_begin_time -``` - -含义: - -```text -update_request_id -标识实际对应当前 paintEvent 的待处理 update 请求 - -update_request_time -该 update 请求进入 GUI 事件队列的时间 - -paint_begin_time -消费者实际开始执行 paintEvent 的时间 -``` - -可派生: - -```text -publish_to_paint_wait -= paint_begin_time - publish_time - -update_queue_wait -= paint_begin_time - update_request_time - -ready_frame_wait -= paint_begin_time - publish_time -``` - -当多个帧在同一个待处理 `update()` 期间发生替换时,最终被消费帧关联到实际触发本次 `paintEvent()` 的 `update_request_id` 和 -`update_request_time`。 - -## 12. 消费阶段时间信息 - -消费者填写: - -```text -paint_begin_time -paint_end_time -consumer_release_time -``` - -含义: - -```text -paint_begin_time -开始读取和绘制该帧的时间 - -paint_end_time -完成该帧显示的时间 - -consumer_release_time -消费者完成全部访问并释放当前 `Consuming` 状态帧槽位的时间 -``` - -可派生: - -```text -paint_duration -= paint_end_time - paint_begin_time - -consumer_hold_duration -= consumer_release_time - paint_begin_time - -data_to_paint_begin -= paint_begin_time - core_data_time - -data_to_paint_end -= paint_end_time - core_data_time - -frame_total_lifecycle -= consumer_release_time - render_request_time -``` - -## 13. 帧结果信息 - -每个帧最终记录: - -```text -Frame_Outcome 帧结果 -├── Produced 已生产 -├── Consumed 已消费 -├── Superseded 已被更新帧替代 -├── Cancelled 已取消 -└── Repeated 重复显示上一帧 -``` - -### Produced(已生产) - -帧已经生产完成并发布。 - -### Consumed(已消费) - -帧被消费者实际取得并完成显示。 - -### Superseded(已被更新帧替代) - -帧已经生产完成,但在被消费者取得之前被更新帧替代。 - -### Cancelled(已取消) - -帧在完成前因 Plot 销毁、尺寸失效或明确终止条件被取消。 - -### Repeated(重复显示上一帧) - -消费者没有取得新帧,继续显示上一张已经消费的图像。 - -实时模式重点统计: - -```text -Consumed 已消费 -Superseded 已被更新帧替代 -Repeated 重复显示上一帧 -``` - -## 14. 帧工作量信息 - -每个帧携带: - -```text -raw_input_count -visible_input_count -prepared_output_count -draw_point_count -draw_segment_count -pixel_width -pixel_height -renderable_count -``` - -用于关联性能变化与实际工作量: - -```text -输入规模变化 -抽点结果变化 -可见范围变化 -图表尺寸变化 -Renderable 数量变化 -``` - -## 15. 无锁元数据写入规则 - -帧元信息采用阶段所有权写入。 - -### 15.1 Producing(生产阶段) - -处于 `Producing` 状态的帧槽位由生产端独占。 - -生产端填写: - -```text -帧身份信息 -数据时间信息 -快照时间信息 -后台排队信息 -prepare 时间信息 -draw 时间信息 -publish 时间信息 -工作量信息 -``` - -其他线程不修改这些字段。 - -### 15.2 Ready(发布阶段) - -生产端完成全部写入后,将帧槽位状态从: - -```text -Producing 生产中 -``` - -发布为: - -```text -Ready 已就绪 -``` - -状态发布建立完整内存可见性边界。 - -### 15.3 Consuming(消费阶段) - -消费者取得处于 `Ready` 状态的帧槽位后,将状态变为: - -```text -Consuming 消费中 -``` - -消费者独占填写: - -```text -update_request_id GUI 更新请求标识 -update_request_time GUI 更新请求入队时间 -paint_begin_time paintEvent 开始时间 -paint_end_time paintEvent 结束时间 -consumer_release_time 消费者释放时间 -Frame_Outcome::Consumed 已消费 -``` - -生产端在消费者释放该帧槽位前不再访问该帧。 - -### 15.4 Returned_Frame(返回阶段) - -消费者完成访问后,将帧槽位及完整反馈返回生产侧。 - -返回路径: - -```text -消费者填写消费信息 -→ 发布消费完成反馈 -→ 帧槽位所有权返回生产侧 -→ 调度侧读取完整 Frame_Metadata -→ 聚合统计 -→ 清理上一代元数据 -→ 递增 frame_slot_generation -→ 将帧槽位重新置为 Free -→ 下一代生产时切换为 Producing -``` - -不需要为每个时间字段单独使用原子变量。 - -无锁同步边界只放在: - -```text -Frame_State 发布 帧状态发布 -frame_slot_generation 发布 帧槽位复用代次发布 -帧版本发布 -``` - -## 16. Superseded(已被更新帧替代)帧反馈 - -`Latency_Eager`(低延迟优先)模式下,旧的 Ready 状态帧可能被更新帧替代。 - -被替代帧在复用前标记: - -```text -Frame_Outcome::Superseded 已被更新帧替代 -``` - -需要采集: - -```text -frame_id -input_version -publish_time -superseded_time -ready_frame_wait -render_duration -production_lifecycle_duration -``` - -可派生: - -```text -superseded_ready_count -superseded_ready_ratio -superseded_frame_age -wasted_render_time -``` - -其中: - -```text -wasted_render_time -``` - -表示已经完成渲染但最终没有显示的生产时间。 - -## 17. 重复显示反馈 - -当 `paintEvent()` 没有取得新帧,只能继续显示上一帧时,记录: - -```text -consumer_underrun_count -repeated_frame_count -repeat_time -last_consumed_frame_id -last_consumed_input_version -``` - -可派生: - -```text -consumer_underrun_ratio -repeated_frame_ratio -displayed_frame_age -``` - -重复绘制上一帧不创建新的完整 `Frame_Metadata`。 - -## 18. 调度事件 - -### 18.1 Input_Changed(输入数据已更新) - -处理路径: - -```text -更新 latest_input_version -更新有效输入间隔统计 -尝试启动生产 -``` - -### 18.2 Render_Completed(渲染生产已完成) - -处理路径: - -```text -完成生产阶段元数据 -将帧槽位发布为 `Ready` 状态 -请求一次 update() -更新生产能力统计 -执行负反馈计算 -尝试启动下一帧 -``` - -### 18.3 Frame_Consumed(帧已消费) - -处理路径: - -```text -完成消费阶段元数据 -释放当前 `Consuming` 状态帧槽位 -采集返回帧的 Frame_Metadata -更新消费能力统计 -执行负反馈计算 -检查是否存在更新的 Ready 状态帧 -尝试启动下一帧 -``` - -### 18.4 Rate_Limit_Deadline_Reached(频率限制截止时间已到达) - -处理路径: - -```text -检查最新数据版本 -检查生产状态 -检查三帧槽位状态 -检查生产策略 -满足条件则启动生产 -``` - -### 18.5 Control_Updated(控制参数已更新) - -低频执行: - -```text -更新平滑间隔 -更新抖动估计 -识别瓶颈 -应用滞回 -调整 adaptive_min_render_interval -``` - -## 19. 两个完成事件回路 - -### 19.1 生产完成自时钟 - -生产者受限时: - -```text -Frame_N 生产完成 -→ 发布 Frame_N -→ 检查是否存在更新数据 -→ 检查人工速率限制 -→ 检查三帧槽位状态 -→ 启动 Frame_N_Plus_1 -``` - -生产循环由: - -```text -Render_Completed 渲染生产已完成 -``` - -驱动。 - -### 19.2 消费完成反馈 - -消费者完成后: - -```text -Frame_N 消费完成 -→ 填写消费阶段元数据 -→ 释放当前 `Consuming` 状态帧槽位 -→ 返回 Frame_Metadata -→ 更新消费能力 -→ 调度器重新决策 -``` - -只有存在更新的 Ready 状态帧时才再次请求: - -```text -update() -``` - -## 20. 速率模型 - -调度器维护: - -```text -D:有效数据更新速率 -P:后台生产能力 -C:前端消费能力 -M:人工最大生产速率 -``` - -定义: - -```text -D 有效数据更新速率,由有效输入间隔估计 -P 后台生产能力,由完整帧生产耗时估计 -C 前端消费能力,由不同帧被实际消费的间隔估计 -M 人工最大生产速率,由用户配置 -``` - -长期有效显示速率近似为: - -```text -display_rate = min(D, P, C, M) -``` - -该公式描述稳定结果,不直接替代每次调度判断。 - -## 21. 能力测量有效条件 - -### 21.1 生产能力有效条件 - -生产耗时可解释为生产能力时,需要满足: - -```text -持续存在未渲染的新数据 -生产完成后立即具备下一次生产资格 -没有消费者背压 -没有人工最大速率等待 -``` - -### 21.2 消费能力有效条件 - -消费间隔可解释为消费者能力时,需要满足: - -```text -Ready 状态帧持续可用 -消费者一直有新帧可取 -生产端没有断供 -``` - -### 21.3 输入速率有效条件 - -输入速率只统计会改变最终图像的有效核心版本。 - -## 22. 平滑平均和抖动 - -不直接平滑 FPS。 - -分别平滑: - -```text -input_interval -render_queue_wait -render_duration -ready_frame_wait -update_queue_wait -paint_duration -consume_interval -data_to_paint_end -``` - -每个指标维护: - -```text -srtt -jitter -``` - -控制侧使用: - -```text -safe_interval = srtt + K × jitter -``` - -控制方向: - -```text -发现过度生产 -→ 快速增加生产间隔 - -发现消费者断供 -→ 缓慢缩短生产间隔 -``` - -## 23. 快反馈 - -每次调度事件立即检查: - -```text -latest_input_version > rendered_input_version -render_busy -ready_valid -consumer_busy -update_pending -current_time >= next_render_time -production_policy -``` - -直接决定: - -```text -立即生产 -等待生产完成 -等待消费者 -等待最大速率期限 -请求 update() -保持空闲 -``` - -## 24. 慢反馈 - -从返回的 `Frame_Metadata` 聚合: - -```text -input_interval_srtt -input_interval_jitter - -render_queue_wait_srtt -render_queue_wait_jitter - -render_duration_srtt -render_duration_jitter - -ready_frame_wait_srtt -ready_frame_wait_jitter - -update_queue_wait_srtt -update_queue_wait_jitter - -paint_duration_srtt -paint_duration_jitter - -consume_interval_srtt -consume_interval_jitter - -frame_age_srtt -frame_age_jitter -``` - -同时维护: - -```text -producer_busy_ratio -ready_frame_occupancy_ratio -consumer_busy_ratio - -superseded_ready_ratio -consumer_underrun_ratio -unique_consumed_ratio -display_efficiency -``` - -其中: - -```text -display_efficiency -= consumed_frame_count / produced_frame_count -``` - -## 25. 瓶颈状态 - -### 25.1 Input_Limited(输入受限) - -特征: - -```text -有效输入间隔最长 -生产者经常空闲 -不存在 Ready 状态帧的时间较多 -消费者经常空闲 -``` - -行为: - -```text -有新核心数据立即生产 -没有新核心数据完全停止 -不重复生成相同版本 -``` - -### 25.2 Producer_Limited(生产端受限) - -特征: - -```text -render_duration 最大 -处于 `Producing` 状态的帧槽位长期繁忙 -不存在 Ready 状态帧的时间较多 -consumer_underrun_ratio 上升 -``` - -行为: - -```text -不增加额外自适应等待 -生产完成后立即检查最新数据 -存在更新则直接启动下一帧 -渲染期间的中间输入版本全部合并 -消费者没有新帧时继续显示上一帧 -``` - -人工最大速率仍然有效。 - -### 25.3 Consumer_Limited(消费端受限) - -特征: - -```text -ready_frame_wait 增加 -update_queue_wait 增加 -Ready 状态帧槽位长期占用 -superseded_ready_ratio 上升 -display_efficiency 下降 -``` - -#### Cost_Balanced(成本均衡) - -```text -已有 Ready 状态帧时停止继续生产 -输入端继续更新 latest_input_version -消费者释放帧槽位后再生产当前最新版本 -``` - -#### Latency_Eager(低延迟优先) - -```text -允许有限更新 Ready 状态帧 -允许新的 Ready 状态帧替代旧的 Ready 状态帧 -持续统计 superseded_ready_ratio -持续统计 wasted_render_time -覆盖率或浪费时间过高时增加生产间隔 -``` - -### 25.4 Manual_Limited(手动上限受限) - -特征: - -```text -manual_min_render_interval -大于输入、生产和消费安全周期 -``` - -行为: - -```text -只有达到 next_render_time 后才能启动生产 -限速期间的输入变化全部合并 -每次生产使用当时最新状态 -``` - -用户配置的是: - -```text -max_render_rate -``` - -不是必须达到的目标帧率。 - -### 25.5 Balanced(均衡状态) - -特征: - -```text -生产能力和消费能力差异处于抖动安全带内 -Ready 状态帧槽位占用稳定 -superseded_ready_ratio 较低 -consumer_underrun_ratio 较低 -``` - -行为: - -```text -保持当前生产间隔 -保持当前策略 -允许生产端有限领先一个 Ready 状态帧 -不根据单次样本切换瓶颈 -``` - -## 26. 滞回控制 - -新的瓶颈状态必须同时满足: - -```text -平滑差异超过抖动安全带 -持续多个控制周期 -帧槽位占用状态与候选瓶颈一致 -帧级反馈指标与候选瓶颈一致 -``` - -进入 `Consumer_Limited` 时需要同时观察: - -```text -ready_frame_wait 上升 -superseded_ready_ratio 上升 -update_queue_wait 上升 -display_efficiency 下降 -``` - -进入 `Producer_Limited` 时需要同时观察: - -```text -render_duration 较高 -producer_busy_ratio 接近满载 -ready_frame_occupancy_ratio 较低 -consumer_underrun_ratio 上升 -``` - -处于安全带内时保持原状态。 - -## 27. 负反馈调节 - -### 27.1 过度生产反馈 - -触发特征: - -```text -superseded_ready_ratio 持续升高 -wasted_render_time 持续升高 -ready_frame_wait 持续增加 -display_efficiency 持续下降 -frame_age 没有因为增加生产而改善 -``` - -调节动作: - -```text -快速增加 adaptive_min_render_interval -减少 Ready 状态帧替换频率 -必要时退化到 `Cost_Balanced`(成本均衡)行为 -``` - -### 27.2 消费断供反馈 - -触发特征: - -```text -consumer_underrun_ratio 持续升高 -不存在 Ready 状态帧的时间较多 -消费者经常重复显示上一帧 -存在持续未渲染的新输入 -生产端未满载 -``` - -调节动作: - -```text -缓慢缩短 adaptive_min_render_interval -直到人工最大速率或生产能力边界 -``` - -### 27.3 生产满载反馈 - -触发特征: - -```text -producer_busy_ratio 接近满载 -render_queue_wait 上升 -render_duration 达到稳定上限 -``` - -调节动作: - -```text -停止继续提速 -不堆积渲染任务 -生产完成后按最新数据自时钟运行 -``` - -## 28. 最大生产速率 - -最大生产速率属于生产端准入条件。 - -定义: - -```text -相邻两次完整帧生产开始之间允许的最小时间 -``` - -生产必须同时满足: - -```text -存在未渲染的新数据 -当前没有生产任务 -三帧槽位状态允许生产 -当前策略允许生产 -最大速率等待时间已经结束 -``` - -配置: - -```text -无限制 -30 FPS -60 FPS -120 FPS -其他用户指定值 -``` - -无限制表示不增加人工等待,不表示绕过数据、消费者和三帧槽位约束。 - -## 29. Production_Policy(生产策略) - -### 29.1 Cost_Balanced(成本均衡) - -目标: - -```text -减少无意义计算 -提高已生成帧的实际显示比例 -``` - -行为: - -```text -已有 Ready 状态帧时停止生产 -消费者消费后再生产最新状态 -长期生产速率接近有效消费速率 -``` - -### 29.2 Latency_Eager(低延迟优先) - -目标: - -```text -降低最新数据到显示完成的年龄 -``` - -行为: - -```text -消费者显示当前帧时允许生产下一帧 -允许新的 Ready 状态帧替代旧的 Ready 状态帧 -仍受最大生产速率约束 -连续覆盖过多时触发负反馈降速 -``` - -## 30. update () 调度 - -完整帧发布后: - -```text -ready_valid -且 update_pending 为 false -→ 请求一次 QWidget::update() -``` - -连续发布多个帧时,只保持一个待处理的 `update()`。 - -`paintEvent()` 开始时取得当前最新完整帧。 - -`paintEvent()` 完成后: - -```text -存在更新的 Ready 状态帧 -→ 再请求一次 update() - -不存在更新的 Ready 状态帧 -→ 不请求 update() -``` - -系统自身触发的窗口暴露、resize 或重复重绘不计为新帧消费。 - -## 31. 数据和帧丢弃规则 - -### 31.1 输入版本 - -未开始渲染的旧输入版本直接合并。 - -```text -正在渲染 100 -收到 101、102、103 -下一帧直接使用 103 -``` - -### 31.2 正在生产的帧 - -默认完成,不因新输入到达而中途取消。 - -### 31.3 已完成未消费帧 - -`Cost_Balanced`(成本均衡): - -```text -已有 Ready 状态帧时暂停继续生产 -``` - -`Latency_Eager`(低延迟优先): - -```text -允许新的 Ready 状态帧替代旧的 Ready 状态帧 -旧帧标记为 `Superseded`(已被更新帧替代) -``` - -### 31.4 正在消费的帧 - -处于 `Consuming` 状态的帧槽位在释放前不会被覆盖或修改。 - -## 32. 完整运行路径 - -```text -核心数据变化 -→ 更新 latest_input_version -→ 触发 Input_Changed -→ 调度器检查生产资格 -→ 收集一致快照 -→ 填写数据和快照时间 -→ 进入后台任务队列 -→ 记录 render_queue_wait -→ 执行 prepare -→ 执行 draw -→ 填写生产时间 -→ 将帧槽位发布为 `Ready` 状态 -→ 请求 update() -→ 记录 update_request_time -→ paintEvent 开始 -→ 取得最新 Ready 状态帧 -→ 填写 paint_begin_time -→ 执行绘制 -→ 填写 paint_end_time -→ 释放当前 `Consuming` 状态帧槽位 -→ 帧槽位所有权返回 -→ 调度侧读取完整 Frame_Metadata -→ 更新 srtt、jitter 和比例统计 -→ 更新 Bottleneck_State(瓶颈状态) -→ 更新 adaptive_min_render_interval -→ 重新判断是否生产下一帧 -``` - -## 33. 最终设计结论 - -默认实时图表采用: - -```text -Data_Driven 数据驱动 -Latest_Wins 最新数据优先 -Triple_Frame 三帧机制 -Max_Render_Rate 最大渲染频率限制 -Render_Completed_Self_Clock 渲染完成事件自时钟 -Frame_Consumed_Feedback 帧消费完成反馈 -Frame_Metadata_Lock_Free_Return 帧元信息无锁返回 -SRTT_And_Jitter 平滑时间与抖动估计 -Bottleneck_Detection 瓶颈识别 -Hysteresis_Control 滞回控制 -Fast_Backoff 快速退避 -Slow_Recovery 缓慢恢复 -``` - -最终控制原则: - -> 使用三帧槽位状态和版本状态决定当前动作,使用帧自身携带的完整生命周期信息形成无锁反馈,使用平滑平均和抖动识别长期瓶颈,使用已消费率、帧覆盖率、排队时间、渲染时间、 -> `paintEvent()` 时间和帧年龄控制生产频率。 diff --git a/test/Renderive_Core_Tests.cpp b/test/Renderive_Core_Tests.cpp index 86d9c18..2f86bad 100644 --- a/test/Renderive_Core_Tests.cpp +++ b/test/Renderive_Core_Tests.cpp @@ -1,15 +1,19 @@ #include "Core/architecture/Frame_Scheduler.h" #include "Core/architecture/Render_Lease.h" +#include "Core/Axis/Axis_p.h" #include "Core/flow/low_latency/Low_Latency_Roles.h" #include "Core/flow/low_latency/Triple_Buffer.h" #include "Core/flow/explicit/Explicit_Frame_Flow.h" +#include "Core/flow/explicit/Explicit_Renderable_Runtime.h" #include "Core/flow/low_latency/Low_Latency_Feedback_Controller.h" #include "Core/flow/low_latency/Low_Latency_Frame_Flow.h" #include "Core/flow/low_latency/Low_Latency_Frame_Pipeline.h" +#include "Core/flow/low_latency/Low_Latency_Renderable_Runtime.h" #include "Core/flow/Present_Surface_Lease.h" #include "Core/plot/Plot_Core.h" #include "Core/execution/Render_Executor.h" #include "Core/primitive/Curve_Utils_p.h" +#include "Core/plottable/Frequency_Trace_p.h" #include "Core/plottable/Performance_Overlay_p.h" #include "Core/plottable/export.h" #include "Core/render/Image.h" @@ -21,6 +25,7 @@ #include #include #include +#include #include #include #include @@ -46,6 +51,10 @@ struct Low_Latency_Frame_Pipeline_Test_Probe { static void release_frame_role(Low_Latency_Frame_Pipeline& pipeline, Triple_Buffer_Lease lease) { pipeline.frame_control.unmark_use(lease); } + + static bool has_published_frame(const Low_Latency_Frame_Pipeline& pipeline) { + return pipeline.has_published_frame.load(std::memory_order_acquire); + } }; namespace { @@ -65,6 +74,69 @@ Frame_Lifecycle_Record render_frame(std::uint64_t begin_ns, std::uint64_t end_ns frame.render_end_ns = end_ns; return frame; } +Timeline_Stream_Snapshot timeline_for_ticks(int first_tick, int last_tick, int visible_count = 0) { + Timeline_Stream_Snapshot timeline; + int count = std::max(0, last_tick - first_tick + 1); + timeline.visible_time_point_count = visible_count > 0 ? visible_count : std::max(1, count); + timeline.first_stream_tick = first_tick; + timeline.last_stream_tick = last_tick; + timeline.coordinate_begin = first_tick; + timeline.times.resize(static_cast(count)); + return timeline; +} +void prepare_frequency_trace(Frequency_Trace_Private& trace, std::initializer_list ticks, const Timeline_Stream_Snapshot& timeline) { + trace.resize_power_buffer(static_cast(ticks.size())); + for (int tick : ticks) + trace.push_power(Frequency_Sample{tick, static_cast(tick)}); + trace.snapshot_power_buffer(); + trace.rebuild_prepared_points(timeline); +} +Low_Latency_Frame_Pipeline::Frame_Publish_Result publish_pipeline_frame(Low_Latency_Frame_Pipeline& pipeline, std::uint64_t frame_id, std::uint64_t now_ns) { + auto lease = pipeline.try_acquire_render_frame(); + EXPECT_TRUE(static_cast(lease)); + if (!lease) + return {}; + EXPECT_TRUE(lease.frame->metadata); + lease.frame->metadata->frame_id = frame_id; + lease.frame->metadata->render_begin_ns = now_ns >= 10 ? now_ns - 10 : now_ns; + lease.frame->metadata->render_end_ns = now_ns >= 5 ? now_ns - 5 : now_ns; + lease.frame->image.resize(2, 2); + lease.frame->image.fill(Color::black()); + lease.frame->version.store(frame_id, std::memory_order_release); + return pipeline.try_publish_rendered_frame(std::move(lease), now_ns); +} +Render_Surface_Publish_Result publish_explicit_frame(Explicit_Frame_Flow& flow, std::uint64_t frame_id, std::uint64_t now_ns, int width = 2, int height = 2, Color color = Color::green()) { + auto surface = flow.begin_render_frame(); + EXPECT_TRUE(static_cast(surface)); + if (!surface) + return {}; + EXPECT_TRUE(surface.output && surface.output->metadata); + surface.output->metadata->frame_id = frame_id; + surface.output->metadata->render_begin_ns = now_ns >= 10 ? now_ns - 10 : now_ns; + surface.output->metadata->render_end_ns = now_ns >= 5 ? now_ns - 5 : now_ns; + surface.output->image.resize(width, height); + surface.output->image.fill(color); + return flow.publish_rendered_frame(std::move(surface), now_ns); +} +Frame_Lifecycle_Record feedback_frame(std::uint64_t frame_id, std::uint64_t base_ns, std::uint64_t render_ns, std::uint64_t paint_ns, std::uint64_t ready_wait_ns = 0, std::uint64_t gui_wait_ns = 0) { + Frame_Lifecycle_Record frame; + frame.frame_id = frame_id; + frame.core_data_time_ns = base_ns; + frame.render_role_enter_ns = base_ns; + frame.render_begin_ns = base_ns; + frame.render_end_ns = base_ns + render_ns; + frame.ready_queue_wait_ns = ready_wait_ns; + frame.transition_12_ns = frame.render_end_ns + ready_wait_ns; + frame.update_request_time_ns = frame.transition_12_ns; + frame.paint_begin_ns = frame.transition_12_ns + gui_wait_ns; + frame.paint_end_ns = frame.paint_begin_ns + paint_ns; + return frame; +} +void feed_feedback_frame(Low_Latency_Feedback_Controller& controller, std::uint64_t frame_id, std::uint64_t base_ns, std::uint64_t render_ns, std::uint64_t paint_ns, std::uint64_t ready_wait_ns = 0, std::uint64_t gui_wait_ns = 0) { + auto frame = feedback_frame(frame_id, base_ns, render_ns, paint_ns, ready_wait_ns, gui_wait_ns); + controller.on_render_completed(frame); + controller.on_frame_presented(frame); +} bool wait_until(std::function predicate) { auto deadline = std::chrono::steady_clock::now() + std::chrono::seconds(2); while (std::chrono::steady_clock::now() < deadline) { @@ -134,7 +206,17 @@ struct Lease_Test_Owner : Renderable { return make_render_object(); } }; -struct Lease_Test_Data : Typed_Render_Data {}; +struct Lease_Test_Data : Typed_Render_Data { + Lease_Test_Owner runtime_owner; + void initialize_low_latency() { + q_ptr = &runtime_owner; + initialize_runtime(make_low_latency_renderable_runtime(runtime_owner)); + } + void initialize_explicit() { + q_ptr = &runtime_owner; + initialize_runtime(make_explicit_renderable_runtime(runtime_owner)); + } +}; struct No_Input_Test_Owner : Renderable {}; struct No_Input_Test_Data : Typed_Render_Data {}; struct Runtime_Count_Owner : Renderable { @@ -150,10 +232,24 @@ struct Runtime_Count_Owner : Renderable { } }; struct Runtime_Count_Data : Typed_Render_Data {}; +struct Counting_Test_Input : Input_Data { + int clear_count{}; + void clear() override { + ++clear_count; + } +}; +struct Counting_Input_Owner : Renderable { + Render_Object_Owner create_render_state() override { + return make_render_object(); + } + Render_Object_Owner create_input_data() override { + return make_render_object(); + } +}; +struct Counting_Input_Data : Typed_Render_Data {}; void init_lease_test_state(Lease_Test_Data& data) { - Lease_Test_Owner owner; - data.initialize_runtime(owner, Renderable_Runtime_Strategy::Low_Latency); + data.initialize_low_latency(); } struct Flow_Test_Host : Frame_Flow_Host { @@ -290,10 +386,10 @@ TEST(Renderive_State, ReinitializeWaitsForActiveLease) { std::atomic_bool reinitialize_returned{false}; std::latch reinitialize_started(1); + Lease_Test_Owner explicit_owner; std::thread reinitializer([&]() { - Lease_Test_Owner owner; reinitialize_started.count_down(); - data.initialize_runtime(owner, Renderable_Runtime_Strategy::Explicit); + data.initialize_runtime(make_explicit_renderable_runtime(explicit_owner)); reinitialize_returned.store(true, std::memory_order_release); }); @@ -303,7 +399,7 @@ TEST(Renderive_State, ReinitializeWaitsForActiveLease) { held = {}; reinitializer.join(); EXPECT_TRUE(reinitialize_returned.load(std::memory_order_acquire)); - EXPECT_EQ(data.runtime_strategy(), Renderable_Runtime_Strategy::Explicit); + EXPECT_NE(data.edit_state(), nullptr); } TEST(Renderive_State, BusyEditSetterWaitsAndCommits) { @@ -364,11 +460,10 @@ TEST(Renderive_Input_Runtime, ExactInputDataBindingSkipsAllocation) { EXPECT_FALSE(static_cast(lease)); } -TEST(Renderive_Input_Runtime, ExplicitRuntimeAllocatesSingleStateAndInput) { +TEST(Renderive_Input_Runtime, ExplicitRuntimeCreatesOneStateAndInput) { Runtime_Count_Owner owner; Runtime_Count_Data explicit_data; - explicit_data.initialize_runtime(owner, Renderable_Runtime_Strategy::Explicit); - EXPECT_EQ(explicit_data.runtime_strategy(), Renderable_Runtime_Strategy::Explicit); + explicit_data.initialize_runtime(make_explicit_renderable_runtime(owner)); EXPECT_EQ(owner.state_create_count, 1); EXPECT_EQ(owner.input_create_count, 1); EXPECT_NE(explicit_data.edit_state(), nullptr); @@ -378,68 +473,455 @@ TEST(Renderive_Input_Runtime, ExplicitRuntimeAllocatesSingleStateAndInput) { edit->value = 42; } EXPECT_EQ(explicit_data.edit_state_value(&Lease_Test_State::value), 42); +} +TEST(Renderive_Input_Runtime, LowLatencyRuntimeCreatesThreeStatesAndInputs) { + Runtime_Count_Owner owner; Runtime_Count_Data low_latency_data; - low_latency_data.initialize_runtime(owner, Renderable_Runtime_Strategy::Low_Latency); - EXPECT_EQ(low_latency_data.runtime_strategy(), Renderable_Runtime_Strategy::Low_Latency); - EXPECT_EQ(owner.state_create_count, 4); - EXPECT_EQ(owner.input_create_count, 4); + low_latency_data.initialize_runtime(make_low_latency_renderable_runtime(owner)); + EXPECT_EQ(owner.state_create_count, 3); + EXPECT_EQ(owner.input_create_count, 3); EXPECT_NE(low_latency_data.edit_state(), nullptr); EXPECT_NE(low_latency_data.ready_state(), nullptr); EXPECT_NE(low_latency_data.render_state(), nullptr); } +TEST(Renderive_Input_Runtime, ExplicitRuntimeContainsNoTripleBuffer) { + Runtime_Count_Owner owner; + Runtime_Count_Data data; + data.initialize_runtime(make_explicit_renderable_runtime(owner)); + auto frame_view = data.capture_frame_view(); + ASSERT_TRUE(static_cast(frame_view)); + EXPECT_EQ(frame_view.state, data.render_state()); + EXPECT_EQ(frame_view.input, data.render_input_data()); +} + +TEST(Renderive_Input_Runtime, ExplicitInputFrameDoesNotDeadlock) { + Runtime_Count_Owner owner; + Runtime_Count_Data data; + data.initialize_runtime(make_explicit_renderable_runtime(owner)); + auto completion = make_update_state(); + { + auto edit = data.acquire_edit_input(true); + ASSERT_TRUE(static_cast(edit)); + edit.set_completion(completion); + } + + auto frame_view = data.capture_frame_view(); + ASSERT_TRUE(static_cast(frame_view)); + frame_view.consume_input(); + EXPECT_TRUE(completion->stage_complete(Update_Stage::Committed)); + + std::atomic_bool setter_returned{false}; + std::thread setter([&]() { + auto edit = data.acquire_edit_input(true); + setter_returned.store(true, std::memory_order_release); + }); + std::this_thread::sleep_for(std::chrono::milliseconds(20)); + EXPECT_FALSE(setter_returned.load(std::memory_order_acquire)); + frame_view = {}; + setter.join(); + EXPECT_TRUE(setter_returned.load(std::memory_order_acquire)); +} + +TEST(Renderive_Input_Runtime, ExplicitInputConsumedExactlyOnce) { + Counting_Input_Owner owner; + Counting_Input_Data data; + data.initialize_runtime(make_explicit_renderable_runtime(owner)); + auto completion = make_update_state(); + { + auto edit = data.acquire_edit_input(true); + ASSERT_TRUE(static_cast(edit)); + edit.set_completion(completion); + } + + auto frame_view = data.capture_frame_view(); + ASSERT_TRUE(static_cast(frame_view)); + auto* input = static_cast(frame_view.input); + ASSERT_NE(input, nullptr); + frame_view.consume_input(); + frame_view.consume_input(); + EXPECT_EQ(input->clear_count, 1); + EXPECT_TRUE(completion->stage_complete(Update_Stage::Committed)); + + std::pmr::vector> committed(memory_resource(Memory_Domain::Update_Completion)); + data.drain_committed_update_states(committed); + EXPECT_EQ(committed.size(), 1u); + committed.clear(); + data.drain_committed_update_states(committed); + EXPECT_TRUE(committed.empty()); +} + +TEST(Renderive_Input_Runtime, ExplicitPrepareAndDrawUseSameState) { + Runtime_Count_Owner owner; + Runtime_Count_Data data; + data.initialize_runtime(make_explicit_renderable_runtime(owner)); + auto frame_view = data.capture_frame_view(); + ASSERT_TRUE(static_cast(frame_view)); + auto* prepare_state = static_cast(frame_view.state); + auto* draw_state = static_cast(frame_view.state); + ASSERT_NE(prepare_state, nullptr); + EXPECT_EQ(prepare_state, draw_state); + + std::atomic_bool setter_returned{false}; + std::thread setter([&]() { + auto edit = data.acquire_edit_state(); + setter_returned.store(true, std::memory_order_release); + }); + std::this_thread::sleep_for(std::chrono::milliseconds(20)); + EXPECT_FALSE(setter_returned.load(std::memory_order_acquire)); + frame_view = {}; + setter.join(); + EXPECT_TRUE(setter_returned.load(std::memory_order_acquire)); +} + +TEST(Renderive_Input_Runtime, ExplicitPrepareAndDrawUseSameInput) { + Runtime_Count_Owner owner; + Runtime_Count_Data data; + data.initialize_runtime(make_explicit_renderable_runtime(owner)); + auto frame_view = data.capture_frame_view(); + ASSERT_TRUE(static_cast(frame_view)); + auto* prepare_input = static_cast(frame_view.input); + auto* draw_input = static_cast(frame_view.input); + ASSERT_NE(prepare_input, nullptr); + EXPECT_EQ(prepare_input, draw_input); + + std::atomic_bool setter_returned{false}; + std::thread setter([&]() { + auto edit = data.acquire_edit_input(true); + setter_returned.store(true, std::memory_order_release); + }); + std::this_thread::sleep_for(std::chrono::milliseconds(20)); + EXPECT_FALSE(setter_returned.load(std::memory_order_acquire)); + frame_view = {}; + setter.join(); + EXPECT_TRUE(setter_returned.load(std::memory_order_acquire)); +} + +TEST(Renderive_Input_Runtime, CrossRenderableReadUsesFrameSnapshot) { + auto axis = std::make_shared(); + Axis_Render_State frame_state; + frame_state.orientation = Orientation::Horizontal; + frame_state.x = 10; + frame_state.pixel_length = 50; + frame_state.coord_start = 100.0; + frame_state.coord_length = 25.0; + frame_state.unit_text = "Hz"; + + Renderable_Frame_Tree tree; + auto& node = tree.emplace_back(); + node.renderable = axis; + node.node_id = 77; + node.frame_view.state = &frame_state; + + Render_Frame_Snapshot snapshot; + snapshot.frame_tree = &tree; + + ASSERT_EQ(snapshot.find_node(77), &node); + Axis_Frame_Snapshot axis_snapshot = Axis_Render_Access::snapshot(axis.get(), snapshot); + EXPECT_EQ(axis_snapshot.coord_range.origin, 100.0); + EXPECT_EQ(axis_snapshot.coord_range.target, 125.0); + EXPECT_EQ(axis_snapshot.pixel_range.origin, 10.0); + EXPECT_EQ(axis_snapshot.pixel_range.target, 60.0); + EXPECT_EQ(Axis_Render_Access::unit_text(axis.get(), snapshot), "Hz"); +} + +TEST(Renderive_Input_Runtime, LowLatencyRenderLeaseHeldUntilFrameEnd) { + Runtime_Count_Owner owner; + Runtime_Count_Data data; + data.initialize_runtime(make_low_latency_renderable_runtime(owner)); + auto first = data.capture_frame_view(); + ASSERT_TRUE(static_cast(first)); + auto second = data.capture_frame_view(); + EXPECT_FALSE(static_cast(second)); + first = {}; + auto third = data.capture_frame_view(); + EXPECT_TRUE(static_cast(third)); +} + +TEST(Renderive_Input_Runtime, FlowCreatesRenderableRuntime) { + Runtime_Count_Owner low_owner; + Runtime_Count_Owner explicit_owner; + Low_Latency_Frame_Flow low_latency_flow; + Explicit_Frame_Flow explicit_flow; + auto low_runtime = low_latency_flow.create_renderable_runtime(low_owner); + auto explicit_runtime = explicit_flow.create_renderable_runtime(explicit_owner); + EXPECT_NE(dynamic_cast(low_runtime.get()), nullptr); + EXPECT_NE(dynamic_cast(explicit_runtime.get()), nullptr); +} + +TEST(Renderive_Frequency_Trace, FrequencyTraceContinuousTicksUseOneSegment) { + Frequency_Trace_Private trace; + auto timeline = timeline_for_ticks(0, 3); + prepare_frequency_trace(trace, {0, 1, 2, 3}, timeline); + EXPECT_EQ(trace.segment_count, 1); + EXPECT_EQ(trace.tick_gap_count, 0); + EXPECT_EQ(trace.non_monotonic_tick_count, 0); + ASSERT_EQ(trace.segment_offsets.size(), 1u); + EXPECT_EQ(trace.prepared_points.size(), 4u); +} + +TEST(Renderive_Frequency_Trace, FrequencyTraceTickGapSplitsSegment) { + Frequency_Trace_Private trace; + auto timeline = timeline_for_ticks(0, 4); + prepare_frequency_trace(trace, {0, 1, 3, 4}, timeline); + EXPECT_EQ(trace.segment_count, 2); + EXPECT_EQ(trace.tick_gap_count, 1); + EXPECT_EQ(trace.non_monotonic_tick_count, 0); + ASSERT_EQ(trace.segment_offsets.size(), 2u); + EXPECT_EQ(trace.segment_offsets[0], 0u); + EXPECT_EQ(trace.segment_offsets[1], 2u); +} + +TEST(Renderive_Frequency_Trace, FrequencyTraceNonMonotonicTickSplitsSegment) { + Frequency_Trace_Private trace; + auto timeline = timeline_for_ticks(0, 3); + prepare_frequency_trace(trace, {0, 1, 1, 2}, timeline); + EXPECT_EQ(trace.segment_count, 2); + EXPECT_EQ(trace.tick_gap_count, 0); + EXPECT_EQ(trace.non_monotonic_tick_count, 1); + ASSERT_EQ(trace.segment_offsets.size(), 2u); + EXPECT_EQ(trace.segment_offsets[1], 2u); +} + +TEST(Renderive_Frequency_Trace, FrequencyTraceMappingFailureSplitsSegment) { + Frequency_Trace_Private trace; + auto timeline = timeline_for_ticks(0, 2); + prepare_frequency_trace(trace, {0, 1, 5, 2}, timeline); + EXPECT_EQ(trace.segment_count, 2); + EXPECT_EQ(trace.tick_gap_count, 0); + EXPECT_EQ(trace.non_monotonic_tick_count, 0); + ASSERT_EQ(trace.segment_offsets.size(), 2u); + EXPECT_EQ(trace.segment_offsets[1], 2u); +} + +TEST(Renderive_Frequency_Trace, FrequencyTraceRingWrapKeepsLogicalOrder) { + Frequency_Trace_Private trace; + trace.resize_power_buffer(3); + trace.push_power(Frequency_Sample{0, 0.0}); + trace.push_power(Frequency_Sample{1, 1.0}); + trace.push_power(Frequency_Sample{2, 2.0}); + trace.push_power(Frequency_Sample{3, 3.0}); + trace.snapshot_power_buffer(); + ASSERT_EQ(trace.data_count, 3); + EXPECT_EQ(trace.data_snapshot[0].tick, 1); + EXPECT_EQ(trace.data_snapshot[1].tick, 2); + EXPECT_EQ(trace.data_snapshot[2].tick, 3); +} + TEST(Renderive_Frame_Pipeline, PublishAndPaintUseMiddleVersionOnly) { Low_Latency_Frame_Pipeline pipeline; - auto lease = pipeline.try_acquire_render_frame(); - ASSERT_TRUE(static_cast(lease)); - ASSERT_TRUE(lease.frame->metadata); - lease.frame->metadata->frame_id = 1; - lease.frame->metadata->render_begin_ns = 10; - lease.frame->metadata->render_end_ns = 20; - lease.frame->version.store(1, std::memory_order_release); - auto published = pipeline.try_publish_rendered_frame(std::move(lease), 25); + auto published = publish_pipeline_frame(pipeline, 1, 25); EXPECT_TRUE(pipeline.middle_frame_ready()); EXPECT_TRUE(published.request_present); EXPECT_EQ(published.dropped_frame_record, nullptr); - auto painting = pipeline.try_acquire_paint_frame(30); + auto painting = pipeline.acquire_paint_frame(30); EXPECT_TRUE(painting.has_new_frame); + EXPECT_TRUE(painting.has_valid_frame); EXPECT_TRUE(static_cast(painting.lease)); EXPECT_FALSE(pipeline.middle_frame_ready()); } -TEST(Renderive_Frame_Pipeline, PaintAcquireFailureKeepsPendingRequest) { + +TEST(Renderive_Frame_Pipeline, PresentBeforeFirstFrameReturnsEmpty) { Low_Latency_Frame_Pipeline pipeline; - auto lease = pipeline.try_acquire_render_frame(); - ASSERT_TRUE(static_cast(lease)); - ASSERT_TRUE(lease.frame->metadata); - lease.frame->metadata->frame_id = 1; - lease.frame->metadata->render_begin_ns = 10; - lease.frame->metadata->render_end_ns = 20; - lease.frame->version.store(1, std::memory_order_release); - auto published = pipeline.try_publish_rendered_frame(std::move(lease), 25); - ASSERT_TRUE(published.request_present); - EXPECT_TRUE(Low_Latency_Frame_Pipeline_Test_Probe::paint_request_pending(pipeline)); - auto busy = Low_Latency_Frame_Pipeline_Test_Probe::acquire_frame_role(pipeline, Frame_Painting); - ASSERT_EQ(busy.result, Triple_Buffer_Result::Success); - auto failed = pipeline.try_acquire_paint_frame(30); - EXPECT_TRUE(failed.paint_frame_unavailable); - EXPECT_FALSE(failed.present_request_was_pending); - EXPECT_TRUE(Low_Latency_Frame_Pipeline_Test_Probe::paint_request_pending(pipeline)); - Low_Latency_Frame_Pipeline_Test_Probe::release_frame_role(pipeline, busy.lease); - auto consumed = pipeline.try_acquire_paint_frame(35); - EXPECT_TRUE(consumed.present_request_was_pending); - EXPECT_TRUE(static_cast(consumed.lease)); + auto consumed = pipeline.acquire_paint_frame(10); + EXPECT_FALSE(static_cast(consumed.lease)); + EXPECT_FALSE(consumed.has_valid_frame); + EXPECT_TRUE(consumed.underrun); } + +TEST(Renderive_Frame_Pipeline, PresentWithoutNewFrameRepeatsPainting) { + Low_Latency_Frame_Pipeline pipeline; + ASSERT_TRUE(publish_pipeline_frame(pipeline, 1, 25).request_present); + auto first = pipeline.acquire_paint_frame(30); + ASSERT_TRUE(static_cast(first.lease)); + EXPECT_TRUE(first.has_new_frame); + first.lease.reset(); + auto repeated = pipeline.acquire_paint_frame(35); + EXPECT_TRUE(static_cast(repeated.lease)); + EXPECT_FALSE(repeated.has_new_frame); + EXPECT_TRUE(repeated.has_valid_frame); + EXPECT_FALSE(repeated.underrun); +} + +TEST(Renderive_Frame_Pipeline, PresentDuringMiddleBusyRepeatsPainting) { + Low_Latency_Frame_Pipeline pipeline; + ASSERT_TRUE(publish_pipeline_frame(pipeline, 1, 25).request_present); + auto first = pipeline.acquire_paint_frame(30); + ASSERT_TRUE(static_cast(first.lease)); + first.lease.reset(); + ASSERT_TRUE(publish_pipeline_frame(pipeline, 2, 40).request_present); + auto busy_middle = Low_Latency_Frame_Pipeline_Test_Probe::acquire_frame_role(pipeline, Frame_Middle); + ASSERT_EQ(busy_middle.result, Triple_Buffer_Result::Success); + auto repeated = pipeline.acquire_paint_frame(45); + EXPECT_TRUE(static_cast(repeated.lease)); + EXPECT_FALSE(repeated.has_new_frame); + EXPECT_TRUE(repeated.has_valid_frame); + EXPECT_FALSE(repeated.underrun); + EXPECT_TRUE(Low_Latency_Frame_Pipeline_Test_Probe::paint_request_pending(pipeline)); + repeated.lease.reset(); + Low_Latency_Frame_Pipeline_Test_Probe::release_frame_role(pipeline, busy_middle.lease); +} + +TEST(Renderive_Frame_Pipeline, PaintingLeaseBlocksUntilAvailable) { + Low_Latency_Frame_Pipeline pipeline; + ASSERT_TRUE(publish_pipeline_frame(pipeline, 1, 25).request_present); + auto first = pipeline.acquire_paint_frame(30); + ASSERT_TRUE(static_cast(first.lease)); + first.lease.reset(); + + auto busy_painting = Low_Latency_Frame_Pipeline_Test_Probe::acquire_frame_role(pipeline, Frame_Painting); + ASSERT_EQ(busy_painting.result, Triple_Buffer_Result::Success); + std::atomic_bool returned{false}; + std::atomic_bool lease_ok{false}; + std::atomic_bool underrun_seen{false}; + std::thread waiter([&]() { + auto consumed = pipeline.acquire_paint_frame(40); + lease_ok.store(static_cast(consumed.lease), std::memory_order_release); + underrun_seen.store(consumed.underrun, std::memory_order_release); + returned.store(true, std::memory_order_release); + }); + std::this_thread::sleep_for(std::chrono::milliseconds(20)); + EXPECT_FALSE(returned.load(std::memory_order_acquire)); + Low_Latency_Frame_Pipeline_Test_Probe::release_frame_role(pipeline, busy_painting.lease); + waiter.join(); + EXPECT_TRUE(returned.load(std::memory_order_acquire)); + EXPECT_TRUE(lease_ok.load(std::memory_order_acquire)); + EXPECT_FALSE(underrun_seen.load(std::memory_order_acquire)); +} + +TEST(Renderive_Frame_Pipeline, TransientPaintBusyDoesNotCountUnderrun) { + Low_Latency_Frame_Pipeline pipeline; + ASSERT_TRUE(publish_pipeline_frame(pipeline, 1, 25).request_present); + auto first = pipeline.acquire_paint_frame(30); + ASSERT_TRUE(static_cast(first.lease)); + first.lease.reset(); + auto repeated = pipeline.acquire_paint_frame(35); + EXPECT_TRUE(static_cast(repeated.lease)); + EXPECT_FALSE(repeated.underrun); + EXPECT_FALSE(repeated.paint_frame_unavailable); +} + +TEST(Renderive_Frame_Pipeline, RepeatedFrameDoesNotRequestBackgroundFallback) { + Low_Latency_Frame_Pipeline pipeline; + ASSERT_TRUE(publish_pipeline_frame(pipeline, 1, 25).request_present); + auto first = pipeline.acquire_paint_frame(30); + ASSERT_TRUE(static_cast(first.lease)); + ASSERT_FALSE(first.lease.frame->image.empty()); + first.lease.reset(); + auto repeated = pipeline.acquire_paint_frame(35); + ASSERT_TRUE(static_cast(repeated.lease)); + EXPECT_FALSE(repeated.has_new_frame); + EXPECT_FALSE(repeated.lease.frame->image.empty()); + EXPECT_FALSE(repeated.underrun); +} + +TEST(Renderive_Frame_Pipeline, BeforeFirstPublishReturnsEmpty) { + Low_Latency_Frame_Pipeline pipeline; + auto consumed = pipeline.acquire_paint_frame(10); + EXPECT_FALSE(static_cast(consumed.lease)); + EXPECT_FALSE(Low_Latency_Frame_Pipeline_Test_Probe::has_published_frame(pipeline)); + EXPECT_TRUE(consumed.underrun); +} + +TEST(Renderive_Frame_Pipeline, FirstPublishCreatesPersistentPainting) { + Low_Latency_Frame_Pipeline pipeline; + ASSERT_TRUE(publish_pipeline_frame(pipeline, 1, 25).request_present); + EXPECT_TRUE(Low_Latency_Frame_Pipeline_Test_Probe::has_published_frame(pipeline)); + auto first = pipeline.acquire_paint_frame(30); + ASSERT_TRUE(static_cast(first.lease)); + EXPECT_TRUE(first.has_new_frame); + EXPECT_TRUE(first.has_valid_frame); + EXPECT_EQ(first.lease.frame->version.load(std::memory_order_acquire), 1u); + EXPECT_FALSE(first.lease.frame->image.empty()); +} + +TEST(Renderive_Frame_Pipeline, NoNewMiddleReturnsSamePainting) { + Low_Latency_Frame_Pipeline pipeline; + ASSERT_TRUE(publish_pipeline_frame(pipeline, 1, 25).request_present); + auto first = pipeline.acquire_paint_frame(30); + ASSERT_TRUE(static_cast(first.lease)); + Low_Latency_Frame* first_frame = first.lease.frame; + auto first_version = first_frame->version.load(std::memory_order_acquire); + first.lease.reset(); + + auto repeated = pipeline.acquire_paint_frame(35); + ASSERT_TRUE(static_cast(repeated.lease)); + EXPECT_EQ(repeated.lease.frame, first_frame); + EXPECT_EQ(repeated.lease.frame->version.load(std::memory_order_acquire), first_version); + EXPECT_FALSE(repeated.lease.frame->image.empty()); +} + +TEST(Renderive_Frame_Pipeline, RenderFailureKeepsPreviousPainting) { + Low_Latency_Frame_Pipeline pipeline; + ASSERT_TRUE(publish_pipeline_frame(pipeline, 1, 25).request_present); + auto first = pipeline.acquire_paint_frame(30); + ASSERT_TRUE(static_cast(first.lease)); + Low_Latency_Frame* first_frame = first.lease.frame; + auto first_version = first_frame->version.load(std::memory_order_acquire); + first.lease.reset(); + + auto render_lease = pipeline.try_acquire_render_frame(); + ASSERT_TRUE(static_cast(render_lease)); + render_lease.reset(); + auto repeated = pipeline.acquire_paint_frame(40); + ASSERT_TRUE(static_cast(repeated.lease)); + EXPECT_EQ(repeated.lease.frame, first_frame); + EXPECT_EQ(repeated.lease.frame->version.load(std::memory_order_acquire), first_version); + EXPECT_FALSE(repeated.lease.frame->image.empty()); +} + +TEST(Renderive_Frame_Pipeline, PublishFailureKeepsPreviousPainting) { + Low_Latency_Frame_Pipeline pipeline; + ASSERT_TRUE(publish_pipeline_frame(pipeline, 1, 25).request_present); + auto first = pipeline.acquire_paint_frame(30); + ASSERT_TRUE(static_cast(first.lease)); + Low_Latency_Frame* first_frame = first.lease.frame; + auto first_version = first_frame->version.load(std::memory_order_acquire); + first.lease.reset(); + + auto busy_middle = Low_Latency_Frame_Pipeline_Test_Probe::acquire_frame_role(pipeline, Frame_Middle); + ASSERT_EQ(busy_middle.result, Triple_Buffer_Result::Success); + auto render_lease = pipeline.try_acquire_render_frame(); + ASSERT_TRUE(static_cast(render_lease)); + render_lease.frame->metadata->frame_id = 2; + render_lease.frame->metadata->render_begin_ns = 35; + render_lease.frame->metadata->render_end_ns = 38; + render_lease.frame->image.resize(2, 2); + render_lease.frame->image.fill(Color::red()); + render_lease.frame->version.store(2, std::memory_order_release); + auto failed_publish = pipeline.try_publish_rendered_frame(std::move(render_lease), 40); + EXPECT_FALSE(failed_publish.request_present); + EXPECT_NE(failed_publish.dropped_frame_record, nullptr); + Low_Latency_Frame_Pipeline_Test_Probe::release_frame_role(pipeline, busy_middle.lease); + + auto repeated = pipeline.acquire_paint_frame(45); + ASSERT_TRUE(static_cast(repeated.lease)); + EXPECT_EQ(repeated.lease.frame, first_frame); + EXPECT_EQ(repeated.lease.frame->version.load(std::memory_order_acquire), first_version); + EXPECT_FALSE(repeated.lease.frame->image.empty()); +} + +TEST(Renderive_Frame_Pipeline, RepeatedPresentNeverReturnsEmpty) { + Low_Latency_Frame_Pipeline pipeline; + ASSERT_TRUE(publish_pipeline_frame(pipeline, 1, 25).request_present); + auto first = pipeline.acquire_paint_frame(30); + ASSERT_TRUE(static_cast(first.lease)); + auto first_version = first.lease.frame->version.load(std::memory_order_acquire); + first.lease.reset(); + + for (int i = 0; i < 1000; ++i) { + auto repeated = pipeline.acquire_paint_frame(31 + static_cast(i)); + ASSERT_TRUE(static_cast(repeated.lease)); + EXPECT_FALSE(repeated.lease.frame->image.empty()); + EXPECT_EQ(repeated.lease.frame->version.load(std::memory_order_acquire), first_version); + EXPECT_FALSE(repeated.underrun); + } +} + TEST(Renderive_Frame_Pipeline, RequestPresentDoesNotWriteBusyMiddle) { Low_Latency_Frame_Pipeline pipeline; - auto lease = pipeline.try_acquire_render_frame(); - ASSERT_TRUE(static_cast(lease)); - ASSERT_TRUE(lease.frame->metadata); - lease.frame->metadata->frame_id = 1; - lease.frame->metadata->render_begin_ns = 10; - lease.frame->metadata->render_end_ns = 20; - lease.frame->version.store(1, std::memory_order_release); - auto published = pipeline.try_publish_rendered_frame(std::move(lease), 25); + auto published = publish_pipeline_frame(pipeline, 1, 25); ASSERT_TRUE(published.request_present); Low_Latency_Frame_Pipeline_Test_Probe::clear_paint_request(pipeline); @@ -564,35 +1046,99 @@ TEST(Renderive_Curve_Sampling, ReverseAxisEnvelopeKeepsSourceOrderInsideBucket) previous = coord; } } -TEST(Renderive_Frame_Feedback, SelectsRenderPaintAndUserSources) { +TEST(Renderive_Frame_Feedback, AlternatingRenderPaintJitterDoesNotFlap) { Low_Latency_Feedback_Controller controller; - controller.set_max_render_fps(200.0); - for (int i = 0; i < 4; ++i) { - auto render_limited = render_frame(0, 20000000); - controller.on_render_completed(render_limited); - } - EXPECT_EQ(controller.state().source, Refresh_Limit_Source::Render); - controller.reset(); - controller.set_max_render_fps(200.0); - for (int i = 0; i < 4; ++i) { - auto frame = render_frame(0, 5000000); - controller.on_render_completed(frame); - frame.transition_12_ns = 0; - frame.paint_begin_ns = 0; - frame.paint_end_ns = 25000000; - controller.on_frame_presented(frame); + controller.set_max_render_fps(1000.0); + std::uint64_t base = 0; + for (std::uint64_t i = 0; i < 40; ++i, base += 20000000) { + std::uint64_t render_ns = i % 2 ? 10000000 : 11000000; + std::uint64_t paint_ns = i % 2 ? 11000000 : 10000000; + feed_feedback_frame(controller, i + 1, base, render_ns, paint_ns); } + EXPECT_NE(controller.state().bottleneck, Low_Latency_Bottleneck_State::Consumer_Limited); + EXPECT_EQ(controller.state().production_strategy, Low_Latency_Production_Strategy::Latency_Eager); +} + +TEST(Renderive_Frame_Feedback, TransientPaintSpikeDoesNotChangeStrategy) { + Low_Latency_Feedback_Controller controller; + controller.set_max_render_fps(1000.0); + std::uint64_t base = 0; + for (std::uint64_t i = 0; i < 24; ++i, base += 20000000) + feed_feedback_frame(controller, i + 1, base, 10000000, 5000000); + ASSERT_EQ(controller.state().production_strategy, Low_Latency_Production_Strategy::Latency_Eager); + auto spike = feedback_frame(100, base, 5000000, 60000000, 20000000, 20000000); + controller.on_render_completed(spike); + controller.on_frame_presented(spike); + EXPECT_EQ(controller.state().production_strategy, Low_Latency_Production_Strategy::Latency_Eager); + EXPECT_NE(controller.state().bottleneck, Low_Latency_Bottleneck_State::Consumer_Limited); +} + +TEST(Renderive_Frame_Feedback, SustainedConsumerLimitEntersCostBalanced) { + Low_Latency_Feedback_Controller controller; + controller.set_max_render_fps(1000.0); + std::uint64_t base = 0; + for (std::uint64_t i = 0; i < 24; ++i, base += 25000000) + feed_feedback_frame(controller, i + 1, base, 5000000, 5000000, 5000000, 5000000); + EXPECT_EQ(controller.state().bottleneck, Low_Latency_Bottleneck_State::Consumer_Limited); EXPECT_EQ(controller.state().source, Refresh_Limit_Source::Paint); - controller.reset(); - controller.set_max_render_fps(30.0); - for (int i = 0; i < 4; ++i) { - auto frame = render_frame(0, 5000000); - controller.on_render_completed(frame); - frame.paint_begin_ns = 0; - frame.paint_end_ns = 8000000; - controller.on_frame_presented(frame); - } - EXPECT_EQ(controller.state().source, Refresh_Limit_Source::User); + EXPECT_EQ(controller.state().production_strategy, Low_Latency_Production_Strategy::Cost_Balanced); +} + +TEST(Renderive_Frame_Feedback, ConsumerRecoveryUsesExitHysteresis) { + Low_Latency_Feedback_Controller controller; + controller.set_max_render_fps(1000.0); + std::uint64_t base = 0; + for (std::uint64_t i = 0; i < 24; ++i, base += 25000000) + feed_feedback_frame(controller, i + 1, base, 5000000, 5000000, 5000000, 5000000); + ASSERT_EQ(controller.state().production_strategy, Low_Latency_Production_Strategy::Cost_Balanced); + + for (std::uint64_t i = 0; i < 40; ++i, base += 4000000) + feed_feedback_frame(controller, 100 + i, base, 1000000, 1000000); + EXPECT_EQ(controller.state().production_strategy, Low_Latency_Production_Strategy::Cost_Balanced); + + for (std::uint64_t i = 0; i < 160; ++i, base += 4000000) + feed_feedback_frame(controller, 200 + i, base, 1000000, 1000000); + EXPECT_EQ(controller.state().production_strategy, Low_Latency_Production_Strategy::Latency_Eager); + EXPECT_NE(controller.state().bottleneck, Low_Latency_Bottleneck_State::Consumer_Limited); +} + +TEST(Renderive_Frame_Feedback, QueueWaitDoesNotChangePaintService) { + Low_Latency_Feedback_Controller controller; + controller.set_max_render_fps(1000.0); + auto frame = feedback_frame(1, 0, 5000000, 5000000); + frame.present_queue_wait_ns = 50000000; + controller.on_render_completed(frame); + controller.on_frame_presented(frame); + EXPECT_LT(controller.state().paint_period_ns, 10000000u); +} + +TEST(Renderive_Frame_Feedback, InputLimitedIsNotReportedAsRenderLimited) { + Low_Latency_Feedback_Controller controller; + controller.set_max_render_fps(1000.0); + for (std::uint64_t i = 1; i <= 24; ++i) + controller.on_input_changed(i * 30000000, i, Dirty_Reason::Core_Data); + std::uint64_t base = 800000000; + for (std::uint64_t i = 0; i < 24; ++i, base += 30000000) + feed_feedback_frame(controller, i + 1, base, 5000000, 5000000); + EXPECT_EQ(controller.state().source, Refresh_Limit_Source::Input); + EXPECT_EQ(controller.state().bottleneck, Low_Latency_Bottleneck_State::Input_Limited); +} + +TEST(Renderive_Frame_Feedback, DeadlineRemainsMonotonicAcrossCandidateChanges) { + Low_Latency_Feedback_Controller controller; + controller.set_max_render_fps(60.0); + Frame_Lifecycle_Record frame; + frame.render_role_enter_ns = 100000000; + controller.on_render_started(frame, 100000000); + std::uint64_t first_deadline = controller.next_render_time_ns(); + ASSERT_GT(first_deadline, 100000000u); + + controller.set_max_render_fps(1000.0); + EXPECT_GE(controller.next_render_time_ns(), first_deadline); + std::uint64_t base = 120000000; + for (std::uint64_t i = 0; i < 8; ++i, base += 20000000) + feed_feedback_frame(controller, i + 1, base, i % 2 ? 30000000 : 5000000, i % 2 ? 5000000 : 30000000, 1000000, 1000000); + EXPECT_GE(controller.next_render_time_ns(), first_deadline); } TEST(Renderive_Frame_Flow, LowLatencyOwnsActiveState) { Low_Latency_Frame_Flow flow; @@ -650,6 +1196,97 @@ TEST(Renderive_Frame_Flow, ExplicitTicketsDrainIntoAcceptedFrame) { complete_update_states(states, Update_Stage::Presented); EXPECT_TRUE(ticket.is_complete()); } + +TEST(Renderive_Explicit_Present, BeforeFirstRenderReturnsEmpty) { + Explicit_Frame_Flow flow; + std::shared_ptr returned; + auto lease = flow.begin_present(10, returned); + EXPECT_FALSE(static_cast(lease)); + EXPECT_EQ(returned, nullptr); +} + +TEST(Renderive_Explicit_Present, PublishedOutputRemainsPresentable) { + Explicit_Frame_Flow flow; + ASSERT_TRUE(publish_explicit_frame(flow, 1, 25).request_present); + std::shared_ptr returned; + auto lease = flow.begin_present(30, returned); + ASSERT_TRUE(static_cast(lease)); + EXPECT_FALSE(lease.image().empty()); + (void)flow.end_present(std::move(lease), 31, 32); +} + +TEST(Renderive_Explicit_Present, RepeatedPresentReturnsSameOutput) { + Explicit_Frame_Flow flow; + ASSERT_TRUE(publish_explicit_frame(flow, 1, 25).request_present); + std::shared_ptr returned; + auto first = flow.begin_present(30, returned); + ASSERT_TRUE(static_cast(first)); + Render_Output* first_output = Present_Surface_Lease_Access::output(first); + (void)flow.end_present(std::move(first), 31, 32); + + auto repeated = flow.begin_present(40, returned); + ASSERT_TRUE(static_cast(repeated)); + EXPECT_EQ(Present_Surface_Lease_Access::output(repeated), first_output); + EXPECT_FALSE(repeated.image().empty()); + (void)flow.end_present(std::move(repeated), 41, 42); +} + +TEST(Renderive_Explicit_Present, ActivePresentLeaseDoesNotHideOutput) { + Explicit_Frame_Flow flow; + ASSERT_TRUE(publish_explicit_frame(flow, 1, 25).request_present); + std::shared_ptr returned; + auto first = flow.begin_present(30, returned); + ASSERT_TRUE(static_cast(first)); + auto second = flow.begin_present(31, returned); + ASSERT_TRUE(static_cast(second)); + EXPECT_EQ(Present_Surface_Lease_Access::output(first), Present_Surface_Lease_Access::output(second)); + EXPECT_FALSE(first.image().empty()); + EXPECT_FALSE(second.image().empty()); + (void)flow.end_present(std::move(second), 32, 33); + (void)flow.end_present(std::move(first), 34, 35); +} + +TEST(Renderive_Explicit_Present, FailedRenderKeepsPreviousOutput) { + Explicit_Frame_Flow flow; + ASSERT_TRUE(publish_explicit_frame(flow, 1, 25).request_present); + std::shared_ptr returned; + auto first = flow.begin_present(30, returned); + ASSERT_TRUE(static_cast(first)); + Render_Output* first_output = Present_Surface_Lease_Access::output(first); + (void)flow.end_present(std::move(first), 31, 32); + + auto failed_surface = flow.begin_render_frame(); + ASSERT_TRUE(static_cast(failed_surface)); + flow.discard_render_frame(std::move(failed_surface)); + + auto repeated = flow.begin_present(40, returned); + ASSERT_TRUE(static_cast(repeated)); + EXPECT_EQ(Present_Surface_Lease_Access::output(repeated), first_output); + EXPECT_FALSE(repeated.image().empty()); + (void)flow.end_present(std::move(repeated), 41, 42); +} + +TEST(Renderive_Explicit_Present, NewOutputAtomicallyReplacesPreviousOutput) { + Explicit_Frame_Flow flow; + ASSERT_TRUE(publish_explicit_frame(flow, 1, 25).request_present); + std::shared_ptr returned; + auto old_lease = flow.begin_present(30, returned); + ASSERT_TRUE(static_cast(old_lease)); + Render_Output* old_output = Present_Surface_Lease_Access::output(old_lease); + + ASSERT_TRUE(publish_explicit_frame(flow, 2, 50, 3, 3, Color::red()).request_present); + auto new_lease = flow.begin_present(55, returned); + ASSERT_TRUE(static_cast(new_lease)); + Render_Output* new_output = Present_Surface_Lease_Access::output(new_lease); + EXPECT_NE(new_output, old_output); + EXPECT_FALSE(old_lease.image().empty()); + EXPECT_FALSE(new_lease.image().empty()); + EXPECT_EQ(new_lease.image().width, 3); + EXPECT_EQ(new_lease.image().height, 3); + (void)flow.end_present(std::move(new_lease), 56, 57); + (void)flow.end_present(std::move(old_lease), 58, 59); +} + TEST(Renderive_Render_Executor, ExecutorDoesNotOwnPlotAdmission) { Render_Executor executor(Render_Runtime_Config{1}); std::atomic_bool entered{false}; diff --git a/重构计划 b/重构计划 deleted file mode 100644 index 4224886..0000000 --- a/重构计划 +++ /dev/null @@ -1,52 +0,0 @@ - -三帧策略 -就是 三个帧 分为 1正在渲染的帧 2中间等待的帧 3正在被paint帧(paintEvent里面的) -帧上记录着各种信息 渲染耗时啊 paint耗时啊 改帧在 12 交换的等待时间 在 23的等待时间 32的等待时间 21的等待时间 -这三个帧就是在这个圈子里面转 - -负反馈调节机制 -在三帧策略里 -根据1渲染耗时 + 21等待时间 可以计算出后台渲染的理论最大帧率 -根据paint耗时 + 23 等待时间 可以计算 前台纯paint的理论最大帧率 -和 用户手动设置的 最大帧率 -这三个取最小值就是 实际后台要控制的帧率 并且渲染策略要记录当前是受三种策略哪一种频率限制、 -同时有快渲染策略 当当前状态是 渲染受限 时候 那么渲染出来新的帧 就马上去检查新的状态有没有脏,脏的话就直接 -执行这个快速渲染 而不是等待 实际后台要控制的帧率去更新 -如果是 正在被paint的帧受限 那么当paint完成了就立即 自己发布postEvent 尽快在刷新 (当然同一时刻 qt事件循环 paint只能有一个) -只有当用户手动频率受限的时候才是 走综合的 后台计算出来的频率 -这样均衡调度 -在用户手动刷新策略 就自己判断实现就行 - -performance shower 会在 帧从 3到2 的时候 在2的状态 内部无锁的 给帧统计信息 然后自己开后台渲染 有一个快照机制 -这样不形成观察者效应 - -其他的 图都是直接在后台渲染的 - - - - -这俩图元的 各种方向 上下 左右 - - - -所有 plottable primitive 绘制模型要和 轴强绑定 而不是假定一定x轴y轴 -所有图都不能假定轴的方向和流向 -比如瀑布图 时间轴是横轴那么 瀑布图就是左右方向 具体是左还是右边 流向跟随时间轴的流向 - 时间轴是纵轴那么 瀑布图就是上下方向 具体是上还是下 流向跟随时间轴的流向 -频谱图 带时间轴的Audio_Frequency 也是一样 - - - - -performance_shower 也是开线程渲染 然后独立频率只是展示快照 来破除观察者效应 -curve图的 多线程渲染 无锁 -瀑布图的 多线程 分tiles渲染 无锁 -这三个需要后台渲染提供多线程能力 - - - - - - - -