diff --git a/Core/Axis/Abs_Axis_p.h b/Core/Axis/Abs_Axis_p.h index 25375b8..5c2ed44 100644 --- a/Core/Axis/Abs_Axis_p.h +++ b/Core/Axis/Abs_Axis_p.h @@ -2,7 +2,7 @@ #include #include #include -#include "../flow/low_latency/Render_Lease.h" +#include "../architecture/Render_Lease.h" #include "../render/Canvas.h" #include "Abs_Axis.h" #include "blend2d/blend2d.h" @@ -266,7 +266,8 @@ template void Abs_Axis::Builder_T::set(Abs_Axis* ret) { ret->init(parent); Abs_Axis_Private* pd = ret->d(); - Abs_Axis_Render_State* sc = reinterpret_cast(pd->edit_state()); + Render_Edit_Lease edit(pd); + Abs_Axis_Render_State* sc = edit.operator->(); sc->x = this->x; sc->y = this->y; sc->orientation = this->orientation; diff --git a/Core/Axis/Axis_p.h b/Core/Axis/Axis_p.h index bfd1d5b..5917b12 100644 --- a/Core/Axis/Axis_p.h +++ b/Core/Axis/Axis_p.h @@ -74,7 +74,8 @@ template void Axis::Builder_T::set(Axis* ret) { Abs_Axis::Builder_T::set(ret); Axis_Private* pd = ret->d(); - Axis_Render_State* sc = reinterpret_cast(pd->edit_state()); + Render_Edit_Lease edit(pd); + Axis_Render_State* sc = edit.operator->(); PROP_RT(int, label_precision) PROP_RT(double, coord_start) PROP_RT(double, coord_length) diff --git a/Core/Axis/Time_Axis.cpp b/Core/Axis/Time_Axis.cpp index 2bcc744..6fb8103 100644 --- a/Core/Axis/Time_Axis.cpp +++ b/Core/Axis/Time_Axis.cpp @@ -66,7 +66,8 @@ std::shared_ptr Time_Axis::Builder::build() { auto ret = renderive::make_shared(); set(ret.get()); Time_Axis_Private* pd = ret->d(); - Time_Axis_Render_State* sc = reinterpret_cast(pd->edit_state()); + Render_Edit_Lease edit(pd); + Time_Axis_Render_State* sc = edit.operator->(); PROP_R(int, visible_time_point_count) PROP_R(int, tick_label_spacing_px) PROP_R(std::string, time_format) diff --git a/Core/Axis/Time_Axis_p.h b/Core/Axis/Time_Axis_p.h index 73098a2..d21fccb 100644 --- a/Core/Axis/Time_Axis_p.h +++ b/Core/Axis/Time_Axis_p.h @@ -3,7 +3,6 @@ #include #include #include -#include "../architecture/Plot_Render_Context.h" #include "../architecture/Timeline_Stream.h" #include "../base/Memory.h" #include "../base/String_Format.h" diff --git a/Core/architecture/Frame_Scheduler.cpp b/Core/architecture/Frame_Scheduler.cpp index 6b49c9e..32fb050 100644 --- a/Core/architecture/Frame_Scheduler.cpp +++ b/Core/architecture/Frame_Scheduler.cpp @@ -3,35 +3,35 @@ #include namespace renderive { -void Frame_Lifecycle_Record::record_renderable_render(const std::string& object_name, const std::string& parent_object_name, int tree_depth, Renderable_Cache_Mode cache_mode, std::uint64_t render_time_ns) { - Renderable_Frame_Stat& stat = renderable_stat(object_name, parent_object_name, tree_depth); +void Frame_Lifecycle_Record::record_renderable_render(std::uint64_t node_id, std::uint64_t parent_node_id, std::string_view object_name, std::string_view parent_object_name, int tree_depth, Renderable_Cache_Mode cache_mode, std::uint64_t render_time_ns) { + Renderable_Frame_Stat& stat = renderable_stat(node_id, parent_node_id, object_name, parent_object_name, tree_depth); stat.cache_enabled = cache_mode == Renderable_Cache_Mode::Local_Pixel; ++stat.render_count; stat.total_render_time_ns += render_time_ns; stat.last_total_render_time_ns = render_time_ns; } -void Frame_Lifecycle_Record::record_renderable_self_draw(const std::string& object_name, const std::string& parent_object_name, int tree_depth, Renderable_Cache_Mode cache_mode, std::uint64_t draw_time_ns) { - Renderable_Frame_Stat& stat = renderable_stat(object_name, parent_object_name, tree_depth); +void Frame_Lifecycle_Record::record_renderable_self_draw(std::uint64_t node_id, std::uint64_t parent_node_id, std::string_view object_name, std::string_view parent_object_name, int tree_depth, Renderable_Cache_Mode cache_mode, std::uint64_t draw_time_ns) { + Renderable_Frame_Stat& stat = renderable_stat(node_id, parent_node_id, object_name, parent_object_name, tree_depth); stat.cache_enabled = cache_mode == Renderable_Cache_Mode::Local_Pixel; ++stat.self_draw_count; stat.self_draw_time_ns += draw_time_ns; stat.last_self_draw_time_ns = draw_time_ns; } -void Frame_Lifecycle_Record::record_renderable_cache_hit(const std::string& object_name, const std::string& parent_object_name, int tree_depth) { +void Frame_Lifecycle_Record::record_renderable_cache_hit(std::uint64_t node_id, std::uint64_t parent_node_id, std::string_view object_name, std::string_view parent_object_name, int tree_depth) { ++renderable_cache_hit_count; - ++cache_stat(object_name, parent_object_name, tree_depth).hit_count; + ++cache_stat(node_id, parent_node_id, object_name, parent_object_name, tree_depth).hit_count; } -void Frame_Lifecycle_Record::record_renderable_cache_miss(const std::string& object_name, const std::string& parent_object_name, int tree_depth) { +void Frame_Lifecycle_Record::record_renderable_cache_miss(std::uint64_t node_id, std::uint64_t parent_node_id, std::string_view object_name, std::string_view parent_object_name, int tree_depth) { ++renderable_cache_miss_count; - ++cache_stat(object_name, parent_object_name, tree_depth).miss_count; + ++cache_stat(node_id, parent_node_id, object_name, parent_object_name, tree_depth).miss_count; } -void Frame_Lifecycle_Record::record_renderable_cache_rebuild(const std::string& object_name, const std::string& parent_object_name, int tree_depth, std::uint64_t rebuild_time_ns, std::uint64_t image_bytes, std::uint64_t image_area, std::uint64_t edit_version, std::uint64_t ready_version) { +void Frame_Lifecycle_Record::record_renderable_cache_rebuild(std::uint64_t node_id, std::uint64_t parent_node_id, std::string_view object_name, std::string_view parent_object_name, int tree_depth, std::uint64_t rebuild_time_ns, std::uint64_t image_bytes, std::uint64_t image_area, std::uint64_t edit_version, std::uint64_t ready_version) { ++renderable_cache_rebuild_count; - Renderable_Cache_Frame_Stats& stat = cache_stat(object_name, parent_object_name, tree_depth); + Renderable_Cache_Frame_Stats& stat = cache_stat(node_id, parent_node_id, object_name, parent_object_name, tree_depth); renderable_cache_rebuild_time_ns += rebuild_time_ns; renderable_cache_image_bytes += image_bytes; renderable_cache_image_area += image_area; @@ -44,9 +44,9 @@ void Frame_Lifecycle_Record::record_renderable_cache_rebuild(const std::string& stat.last_ready_version = ready_version; } -void Frame_Lifecycle_Record::record_renderable_cache_compose(const std::string& object_name, const std::string& parent_object_name, int tree_depth, std::uint64_t compose_time_ns, std::uint64_t image_bytes, std::uint64_t image_area, std::uint64_t edit_version, std::uint64_t ready_version) { +void Frame_Lifecycle_Record::record_renderable_cache_compose(std::uint64_t node_id, std::uint64_t parent_node_id, std::string_view object_name, std::string_view parent_object_name, int tree_depth, std::uint64_t compose_time_ns, std::uint64_t image_bytes, std::uint64_t image_area, std::uint64_t edit_version, std::uint64_t ready_version) { renderable_cache_compose_time_ns += compose_time_ns; - Renderable_Cache_Frame_Stats& stat = cache_stat(object_name, parent_object_name, tree_depth); + Renderable_Cache_Frame_Stats& stat = cache_stat(node_id, parent_node_id, object_name, parent_object_name, tree_depth); stat.compose_time_ns += compose_time_ns; stat.last_compose_time_ns = compose_time_ns; stat.last_image_bytes = image_bytes; @@ -55,27 +55,31 @@ void Frame_Lifecycle_Record::record_renderable_cache_compose(const std::string& stat.last_ready_version = ready_version; } -Renderable_Frame_Stat& Frame_Lifecycle_Record::renderable_stat(const std::string& object_name, const std::string& parent_object_name, int tree_depth) { +Renderable_Frame_Stat& Frame_Lifecycle_Record::renderable_stat(std::uint64_t node_id, std::uint64_t parent_node_id, std::string_view object_name, std::string_view parent_object_name, int tree_depth) { for (auto& stat : renderable_stats) { - if (stat.object_name == object_name && stat.parent_object_name == parent_object_name && stat.tree_depth == tree_depth) + if (stat.node_id == node_id && stat.parent_node_id == parent_node_id && stat.tree_depth == tree_depth) return stat; } Renderable_Frame_Stat stat; - stat.object_name = object_name; - stat.parent_object_name = parent_object_name; + stat.node_id = node_id; + stat.parent_node_id = parent_node_id; + stat.object_name = std::string(object_name); + stat.parent_object_name = std::string(parent_object_name); stat.tree_depth = tree_depth; renderable_stats.push_back(std::move(stat)); return renderable_stats.back(); } -Renderable_Cache_Frame_Stats& Frame_Lifecycle_Record::cache_stat(const std::string& object_name, const std::string& parent_object_name, int tree_depth) { +Renderable_Cache_Frame_Stats& Frame_Lifecycle_Record::cache_stat(std::uint64_t node_id, std::uint64_t parent_node_id, std::string_view object_name, std::string_view parent_object_name, int tree_depth) { for (auto& stat : renderable_cache_stats) { - if (stat.object_name == object_name && stat.parent_object_name == parent_object_name && stat.tree_depth == tree_depth) + if (stat.node_id == node_id && stat.parent_node_id == parent_node_id && stat.tree_depth == tree_depth) return stat; } Renderable_Cache_Frame_Stats stat; - stat.object_name = object_name; - stat.parent_object_name = parent_object_name; + stat.node_id = node_id; + stat.parent_node_id = parent_node_id; + stat.object_name = std::string(object_name); + stat.parent_object_name = std::string(parent_object_name); stat.tree_depth = tree_depth; renderable_cache_stats.push_back(std::move(stat)); return renderable_cache_stats.back(); diff --git a/Core/architecture/Frame_Scheduler.h b/Core/architecture/Frame_Scheduler.h index 5606011..7d5b3d6 100644 --- a/Core/architecture/Frame_Scheduler.h +++ b/Core/architecture/Frame_Scheduler.h @@ -1,17 +1,53 @@ #pragma once #include "Render_Config.h" #include +#include #include +#include #include namespace renderive { +struct Frame_Lifecycle_Record; + +struct Frame_Lifecycle_Observer { + virtual ~Frame_Lifecycle_Observer() = default; + virtual bool enabled() const = 0; + virtual void collect_frame(std::shared_ptr frame) = 0; +}; + struct Refresh_Control_Snapshot { Refresh_Limit_Source source = Refresh_Limit_Source::Unknown; + Low_Latency_Bottleneck_State bottleneck = Low_Latency_Bottleneck_State::Unknown; + Low_Latency_Production_Strategy production_strategy = Low_Latency_Production_Strategy::Latency_Eager; + std::uint64_t input_period_ns{}; + std::uint64_t input_jitter_ns{}; std::uint64_t render_period_ns{}; + std::uint64_t render_jitter_ns{}; std::uint64_t paint_period_ns{}; + std::uint64_t paint_jitter_ns{}; + std::uint64_t ready_queue_srtt_ns{}; + std::uint64_t ready_queue_jitter_ns{}; + std::uint64_t gui_srtt_ns{}; + std::uint64_t gui_jitter_ns{}; + std::uint64_t consumption_srtt_ns{}; + std::uint64_t consumption_jitter_ns{}; + std::uint64_t frame_age_srtt_ns{}; + std::uint64_t frame_age_jitter_ns{}; std::uint64_t user_period_ns{}; std::uint64_t target_period_ns{}; + std::uint64_t presented_frame_count{}; + std::uint64_t superseded_frame_count{}; + std::uint64_t repeated_frame_count{}; + std::uint64_t underrun_count{}; + double display_efficiency = 1.0; }; + +inline std::uint32_t dirty_reason_bit(Dirty_Reason reason) { + return 1u << static_cast(reason); +} + struct Renderable_Cache_Frame_Stats { + std::uint64_t node_id{}; + std::uint64_t parent_node_id{}; std::string object_name; std::string parent_object_name; int tree_depth{}; @@ -28,6 +64,8 @@ struct Renderable_Cache_Frame_Stats { std::uint64_t last_ready_version{}; }; struct Renderable_Frame_Stat { + std::uint64_t node_id{}; + std::uint64_t parent_node_id{}; std::string object_name; std::string parent_object_name; int tree_depth{}; @@ -78,22 +116,23 @@ struct Frame_Lifecycle_Record { std::uint64_t pixel_width{}; std::uint64_t pixel_height{}; std::uint64_t viewport_version{}; + std::uint32_t dirty_reason_mask{}; std::vector renderable_stats; std::vector renderable_cache_stats; - void record_renderable_render(const std::string& object_name, const std::string& parent_object_name, int tree_depth, Renderable_Cache_Mode cache_mode, std::uint64_t render_time_ns); - void record_renderable_self_draw(const std::string& object_name, const std::string& parent_object_name, int tree_depth, Renderable_Cache_Mode cache_mode, std::uint64_t draw_time_ns); - void record_renderable_cache_hit(const std::string& object_name, const std::string& parent_object_name, int tree_depth); - void record_renderable_cache_miss(const std::string& object_name, const std::string& parent_object_name, int tree_depth); - void record_renderable_cache_rebuild(const std::string& object_name, const std::string& parent_object_name, int tree_depth, std::uint64_t rebuild_time_ns, std::uint64_t image_bytes, std::uint64_t image_area, std::uint64_t edit_version, std::uint64_t ready_version); - void record_renderable_cache_compose(const std::string& object_name, const std::string& parent_object_name, int tree_depth, std::uint64_t compose_time_ns, std::uint64_t image_bytes, std::uint64_t image_area, std::uint64_t edit_version, std::uint64_t ready_version); + void record_renderable_render(std::uint64_t node_id, std::uint64_t parent_node_id, std::string_view object_name, std::string_view parent_object_name, int tree_depth, Renderable_Cache_Mode cache_mode, std::uint64_t render_time_ns); + void record_renderable_self_draw(std::uint64_t node_id, std::uint64_t parent_node_id, std::string_view object_name, std::string_view parent_object_name, int tree_depth, Renderable_Cache_Mode cache_mode, std::uint64_t draw_time_ns); + void record_renderable_cache_hit(std::uint64_t node_id, std::uint64_t parent_node_id, std::string_view object_name, std::string_view parent_object_name, int tree_depth); + void record_renderable_cache_miss(std::uint64_t node_id, std::uint64_t parent_node_id, std::string_view object_name, std::string_view parent_object_name, int tree_depth); + void record_renderable_cache_rebuild(std::uint64_t node_id, std::uint64_t parent_node_id, std::string_view object_name, std::string_view parent_object_name, int tree_depth, std::uint64_t rebuild_time_ns, std::uint64_t image_bytes, std::uint64_t image_area, std::uint64_t edit_version, std::uint64_t ready_version); + void record_renderable_cache_compose(std::uint64_t node_id, std::uint64_t parent_node_id, std::string_view object_name, std::string_view parent_object_name, int tree_depth, std::uint64_t compose_time_ns, std::uint64_t image_bytes, std::uint64_t image_area, std::uint64_t edit_version, std::uint64_t ready_version); private: - Renderable_Frame_Stat& renderable_stat(const std::string& object_name, const std::string& parent_object_name, int tree_depth); - Renderable_Cache_Frame_Stats& cache_stat(const std::string& object_name, const std::string& parent_object_name, int tree_depth); + Renderable_Frame_Stat& renderable_stat(std::uint64_t node_id, std::uint64_t parent_node_id, std::string_view object_name, std::string_view parent_object_name, int tree_depth); + Renderable_Cache_Frame_Stats& cache_stat(std::uint64_t node_id, std::uint64_t parent_node_id, std::string_view object_name, std::string_view parent_object_name, int tree_depth); }; inline std::uint64_t renderable_cache_subtree_compose_time(const std::vector& stats, const Renderable_Cache_Frame_Stats& node) { std::uint64_t compose_time_ns = node.compose_time_ns; for (const auto& stat : stats) { - if (stat.parent_object_name == node.object_name && stat.tree_depth == node.tree_depth + 1) + if (stat.parent_node_id == node.node_id && stat.tree_depth == node.tree_depth + 1) compose_time_ns += renderable_cache_subtree_compose_time(stats, stat); } return compose_time_ns; diff --git a/Core/architecture/Plot_Render_Context.h b/Core/architecture/Plot_Render_Context.h index f908643..5357a8e 100644 --- a/Core/architecture/Plot_Render_Context.h +++ b/Core/architecture/Plot_Render_Context.h @@ -6,31 +6,12 @@ #include #include #include -#include namespace renderive { class Plot_Core; class Timeline_Stream; -struct Frame_Lifecycle_Observer { - virtual ~Frame_Lifecycle_Observer() = default; - virtual bool enabled() const = 0; - virtual void collect_frame(std::shared_ptr frame) = 0; -}; - -struct Timeline_Stream_Snapshot; -struct Timeline_Stream_Cache_Item { - std::weak_ptr stream; - int time_point_size = 1; - int tick_space = 1; - bool start_coord_to_end_coord = false; - std::shared_ptr snapshot; -}; -struct Timeline_Stream_Frame_Cache { - std::vector items; -}; - struct Plot_Render_Context { std::atomic_bool destroying{false}; std::atomic_bool first_resize{true}; @@ -46,5 +27,3 @@ struct Plot_Render_Context { }; } // namespace renderive - -#include "../render/Render_Frame_Snapshot.h" diff --git a/Core/architecture/Render_Config.h b/Core/architecture/Render_Config.h index 65bb65e..dcab07c 100644 --- a/Core/architecture/Render_Config.h +++ b/Core/architecture/Render_Config.h @@ -3,6 +3,15 @@ #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, + Viewport +}; enum class Render_Task_Kind : std::uint8_t { Frame, Curve, @@ -53,6 +62,17 @@ enum class Refresh_Limit_Source : std::uint8_t { Paint, User }; +enum class Low_Latency_Bottleneck_State : std::uint8_t { + Unknown, + Input_Limited, + Producer_Limited, + Consumer_Limited, + User_Limited +}; +enum class Low_Latency_Production_Strategy : std::uint8_t { + Latency_Eager, + Cost_Balanced +}; enum class Frame_Outcome : std::uint8_t { Presented, Superseded, diff --git a/Core/architecture/Render_Data.h b/Core/architecture/Render_Data.h index 36c34b6..4b5104a 100644 --- a/Core/architecture/Render_Data.h +++ b/Core/architecture/Render_Data.h @@ -7,6 +7,7 @@ #include "../event/Pointer_Event.h" #include "../event/Resize_Event.h" #include "../event/Wheel_Event.h" +#include "Render_Config.h" #include "Update_Completion.h" #include "global.h" #include @@ -151,6 +152,13 @@ struct Render_State_Edit_Handle { virtual ~Render_State_Edit_Handle() = default; }; +struct Render_Input_Edit_Handle { + virtual ~Render_Input_Edit_Handle() = default; + virtual void set_completion(std::shared_ptr state) { + (void)state; + } +}; + struct Render_State_Read_Guard : Move_Only { Render_State_Read_Guard() = default; Render_State_Read_Guard(Render_State* state, std::unique_ptr handle) @@ -183,26 +191,58 @@ struct Render_State_Edit_Guard : Move_Only { 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) + : 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; + + explicit operator bool() const noexcept { + return input && handle; + } + + Input_Data* input{}; + std::unique_ptr handle; + + void set_completion(std::shared_ptr state) { + if (handle) + handle->set_completion(std::move(state)); + } +}; + +enum class Renderable_State_Role : std::uint8_t { + Edit, + Ready, + Render +}; + +enum class Renderable_Input_Role : std::uint8_t { + Edit, + Ready, + Render +}; + struct Render_Data { Renderable* q_ptr{}; - virtual void initialize_runtime(Renderable& owner) = 0; - virtual void initialize_runtime_buffers( - Render_Object_Owner edit_state, - Render_Object_Owner ready_state, - Render_Object_Owner render_state, - Render_Object_Owner edit_input, - Render_Object_Owner ready_input, - Render_Object_Owner render_input) = 0; + virtual void initialize_runtime(Renderable& owner, Renderable_Runtime_Strategy strategy) = 0; + [[nodiscard]] virtual Renderable_Runtime_Strategy runtime_strategy() const = 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* state(Renderable_State_Role role) = 0; virtual Render_State* render_state() = 0; virtual Render_State* ready_state() = 0; virtual Render_State* edit_state() = 0; + virtual Input_Data* input_data(Renderable_Input_Role role) = 0; virtual Input_Data* render_input_data() = 0; virtual Input_Data* edit_input_data() = 0; virtual ~Render_Data() = default; @@ -213,22 +253,40 @@ struct Render_Data { virtual Rect pixel_bounds(const Size& plot_size) { return {0, 0, plot_size.width, plot_size.height}; } +protected: + virtual void copy_render_state_object(Render_State& target, const Render_State& source) { + target.version = source.version; + } }; struct Base_Render_Data : Render_Data { - void initialize_runtime(Renderable&) override {} - void initialize_runtime_buffers(Render_Object_Owner, Render_Object_Owner, Render_Object_Owner, Render_Object_Owner, Render_Object_Owner, Render_Object_Owner) override {} + void initialize_runtime(Renderable&, Renderable_Runtime_Strategy) override {} + [[nodiscard]] Renderable_Runtime_Strategy runtime_strategy() const override { + return Renderable_Runtime_Strategy::Explicit; + } 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 { + return {}; + } Render_State_Read_Guard acquire_render_state_read() override { return {}; } Render_State_Read_Guard acquire_edit_state_read() override { return {}; } + Render_State_Read_Guard acquire_prepare_state_read() override { + return {}; + } Render_State_Edit_Guard acquire_edit_state() override { return {}; } + Render_Input_Edit_Guard acquire_edit_input(bool) override { + return {}; + } + Render_State* state(Renderable_State_Role) override { + return nullptr; + } Render_State* render_state() override { return nullptr; } @@ -244,6 +302,9 @@ struct Base_Render_Data : Render_Data { Input_Data* edit_input_data() override { return nullptr; } + Input_Data* input_data(Renderable_Input_Role) override { + return nullptr; + } void sync_state_pipeline() override {} }; diff --git a/Core/architecture/Render_Lease.h b/Core/architecture/Render_Lease.h new file mode 100644 index 0000000..3c2e95a --- /dev/null +++ b/Core/architecture/Render_Lease.h @@ -0,0 +1,185 @@ +#pragma once +#include "Renderable.h" +#include "Strategy_Render_Data.h" +#include + +namespace renderive { + +template + requires std::derived_from +struct Typed_Render_Data_Base : Base { + Owner* q() { + return reinterpret_cast(this->q_ptr); + } + State* render_state() override { + return reinterpret_cast(this->state(Renderable_State_Role::Render)); + } + State* ready_state() override { + return reinterpret_cast(this->state(Renderable_State_Role::Ready)); + } + State* edit_state() override { + return reinterpret_cast(this->state(Renderable_State_Role::Edit)); + } + Input* render_input_data() override { + return reinterpret_cast(this->input_data(Renderable_Input_Role::Render)); + } + Input* edit_input_data() override { + return reinterpret_cast(this->input_data(Renderable_Input_Role::Edit)); + } + State* state_data(Renderable_State_Role role) { + return reinterpret_cast(this->state(role)); + } + template + requires std::derived_from + Field state_value(Field Source_State::* field, Renderable_State_Role role) { + auto guard = this->acquire_state_read(role); + if (!guard) + return {}; + auto state = reinterpret_cast(guard.state); + return state->*field; + } + template + requires std::derived_from + Field edit_state_value(Field Source_State::* field) { + return state_value(field, Renderable_State_Role::Edit); + } + template + requires std::derived_from + Field ready_state_value(Field Source_State::* field) { + return state_value(field, Renderable_State_Role::Ready); + } + template + requires std::derived_from + Field render_state_value(Field Source_State::* field) { + return state_value(field, Renderable_State_Role::Render); + } + template + requires std::derived_from + void set_state_value(Field Source_State::* field, Field value) { + auto guard = this->acquire_edit_state(); + if (!guard) + return; + auto state = reinterpret_cast(guard.state); + state->*field = std::move(value); + } +protected: + void copy_render_state_object(Render_State& target, const Render_State& source) override { + static_cast(target) = static_cast(source); + } +}; + +template +using Typed_Render_Data = Typed_Render_Data_Base; + +template +struct Renderable_Binding { + static void init(Owner* owner, const char* name) { + owner->d_owner = make_render_object(); + owner->d_ptr = static_cast(owner->d_owner.template get()); + owner->set_object_name(name); + } + static Data* d(Owner* owner) { + return reinterpret_cast(owner->d_ptr); + } + static Render_Object_Owner create_render_data() { + return make_render_object(); + } + static Render_Object_Owner create_render_state() { + return make_render_object(); + } + static Render_Object_Owner create_input_data() { + if constexpr (std::is_same_v) + return {}; + return make_render_object(); + } +}; + +#define RENDERIVE_DEFINE_RENDERABLE_BINDING(Owner, Data, State, Input, Name) \ + using Owner##_Binding = ::renderive::Renderable_Binding; \ + Owner::Owner() { \ + Owner##_Binding::init(this, Name); \ + } \ + Data* Owner::d() { \ + return Owner##_Binding::d(this); \ + } \ + ::renderive::Render_Object_Owner Owner::create_render_data() { \ + return Owner##_Binding::create_render_data(); \ + } \ + ::renderive::Render_Object_Owner Owner::create_render_state() { \ + return Owner##_Binding::create_render_state(); \ + } \ + ::renderive::Render_Object_Owner Owner::create_input_data() { \ + return Owner##_Binding::create_input_data(); \ + } + +template + requires std::derived_from +struct Render_State_Lease : Immovable { + explicit Render_State_Lease(Data* data) : data(data) { + guard = data->acquire_edit_state_read(); + state = reinterpret_cast(guard.state); + } + ~Render_State_Lease() = default; + State* operator->() { + return state; + } + State& operator*() { + return *state; + } + explicit operator bool() const { + return state && guard; + } + Data* data{}; + Render_State_Read_Guard guard; + State* state{}; +}; + +template + requires std::derived_from +struct Render_Edit_Lease : Immovable { + explicit Render_Edit_Lease(Data* data) : data(data) { + guard = data->acquire_edit_state(); + state = reinterpret_cast(guard.state); + } + ~Render_Edit_Lease() = default; + State* operator->() { + return state; + } + State& operator*() { + return *state; + } + explicit operator bool() const { + return state && guard; + } + Data* data{}; + Render_State_Edit_Guard guard; + State* state{}; +}; + +template + requires std::derived_from +struct Render_Input_Lease : Immovable { + explicit Render_Input_Lease(Data* data, bool notify_render = true) : data(data), notify_render(notify_render) { + guard = data->acquire_edit_input(notify_render); + input = reinterpret_cast(guard.input); + } + ~Render_Input_Lease() = default; + Input* operator->() { + return input; + } + Input& operator*() { + return *input; + } + explicit operator bool() const { + return input && guard; + } + void set_completion(std::shared_ptr state) { + guard.set_completion(std::move(state)); + } + Data* data{}; + bool notify_render{}; + Render_Input_Edit_Guard guard; + Input* input{}; +}; + +} // namespace renderive diff --git a/Core/architecture/Renderable.cpp b/Core/architecture/Renderable.cpp index ea0b66f..1e5113e 100644 --- a/Core/architecture/Renderable.cpp +++ b/Core/architecture/Renderable.cpp @@ -1,16 +1,26 @@ #include #include #include "../base/Memory.h" +#include "Plot_Render_Context.h" #include "../plot/Plot_Core.h" +#include "../plot/Plot_Core_Access.h" +#include "../render/Render_Frame_Snapshot.h" #include "Renderable.h" #include "Render_Time.h" namespace renderive { +namespace { +std::uint64_t next_renderable_node_id() { + static std::atomic next_id{1}; + return next_id.fetch_add(1, std::memory_order_acq_rel); +} +} // namespace + Renderable_Cache_Mode Renderable::get_cache_mode() const { return cache_mode.load(std::memory_order_acquire); } void Renderable::set_cache_mode(Renderable_Cache_Mode mode) { cache_mode.store(mode, std::memory_order_release); - mark_render_dirty(); + mark_render_dirty(Dirty_Reason::Config); } bool Renderable::attached_to_plot() const { return plot != nullptr; @@ -21,6 +31,9 @@ bool Renderable::shares_plot_with(const Renderable& other) const { const std::string& Renderable::object_name() const { return object_name_; } +std::uint64_t Renderable::node_id() const { + return node_id_; +} void Renderable::set_object_name(std::string name) { if (initialized.load(std::memory_order_acquire) && object_name_ != name) { ASSERT(false, "Renderable object_name must be fixed before attach"); @@ -35,7 +48,7 @@ void Renderable::set_visible(bool value) { bool old = visible_.exchange(value, std::memory_order_acq_rel); if (old == value) return; - mark_render_dirty(); + mark_render_dirty(Dirty_Reason::Config); } bool Renderable::should_prepare() const { return !get_retiring() && visible_.load(std::memory_order_acquire) && d_ptr; @@ -104,15 +117,17 @@ void Renderable::init(const std::shared_ptr& parent) { } void Renderable::init_common(Plot_Core* plot) { this->plot = plot; - plot_context = plot->render_context(); + if (!node_id_) + node_id_ = next_renderable_node_id(); + plot_context = Plot_Core_Context_Access::render_context(*plot); retiring.store(false, std::memory_order_release); if (!d_ptr) { d_owner = create_render_data(); d_ptr = d_owner.get(); } d_ptr->q_ptr = this; - d_ptr->initialize_runtime(*this); - plot->notify_model_dirty(); + d_ptr->initialize_runtime(*this, plot->renderable_runtime_strategy()); + plot->notify_model_dirty(Dirty_Reason::Config); } void Renderable::add_child(const std::shared_ptr& child) { if (!child || child.get() == this || child->plot != plot || !child->parent_renderable.expired() || get_retiring() || child->get_retiring()) { @@ -129,7 +144,7 @@ void Renderable::add_child(const std::shared_ptr& child) { children.push_back(child); } // Marking dirty may render synchronously, so the child list lock must be released first. - mark_render_dirty(); + mark_render_dirty(Dirty_Reason::Config); } void Renderable::remove_child(const std::shared_ptr& child) { if (!child || child->parent_renderable.lock().get() != this) @@ -145,33 +160,46 @@ void Renderable::remove_child(const std::shared_ptr& child) { children.erase(it); } removed->detach_from_plot_tree(); - mark_render_dirty(); + mark_render_dirty(Dirty_Reason::Config); } std::vector> Renderable::children_snapshot() const { std::lock_guard lock(children_mutex); return children; } -void Renderable::append_tree_snapshot(Renderable_Frame_Tree& snapshot, const std::string& parent_object_name, int render_tree_depth, const std::string& cache_parent_object_name, int cache_tree_depth) { +void Renderable::append_tree_snapshot( + Renderable_Frame_Tree& snapshot, + std::uint64_t parent_node_id, + const std::string& parent_object_name, + int render_tree_depth, + std::uint64_t cache_parent_node_id, + const std::string& cache_parent_object_name, + int cache_tree_depth) { std::size_t index = snapshot.size(); Renderable_Cache_Mode mode = get_cache_mode(); snapshot.push_back(Renderable_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 }); 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; if (mode == Renderable_Cache_Mode::Local_Pixel) { child_cache_parent = object_name_; + child_cache_parent_id = node_id_; ++child_cache_depth; } std::vector> children = children_snapshot(); for (const auto& child : children) { if (child) - child->append_tree_snapshot(snapshot, object_name_, render_tree_depth + 1, child_cache_parent, child_cache_depth); + child->append_tree_snapshot(snapshot, node_id_, object_name_, render_tree_depth + 1, child_cache_parent_id, child_cache_parent, child_cache_depth); } snapshot[index].subtree_end = snapshot.size(); } @@ -202,7 +230,7 @@ void Renderable::render(Canvas& canvas, const Render_Frame_Snapshot& snapshot) { else render_direct(canvas, snapshot); if (snapshot.frame_metadata) - snapshot.frame_metadata->record_renderable_render(object_name_, render_parent_object_name(), render_tree_depth(), mode, steady_now_ns() - begin_time); + snapshot.frame_metadata->record_renderable_render(node_id_, 0, object_name_, render_parent_object_name(), render_tree_depth(), mode, steady_now_ns() - begin_time); } void Renderable::render_tree_snapshot(Canvas& canvas, const Render_Frame_Snapshot& snapshot, const Renderable_Frame_Tree& tree, std::size_t index) { render_tree_node(canvas, snapshot, tree, index); @@ -229,24 +257,24 @@ void Renderable::render_cached(Canvas& canvas, const Render_Frame_Snapshot& snap bool ready = cache_ready_version.load(std::memory_order_acquire) == edit_version && cache_image.size() == Size{bounds.width, bounds.height} && cache_bounds.x == bounds.x && cache_bounds.y == bounds.y && cache_bounds.width == bounds.width && cache_bounds.height == bounds.height; if (ready) { if (snapshot.frame_metadata) - snapshot.frame_metadata->record_renderable_cache_hit(object_name_, parent_object_name, tree_depth); + snapshot.frame_metadata->record_renderable_cache_hit(node_id_, 0, object_name_, parent_object_name, tree_depth); std::uint64_t begin_time = steady_now_ns(); canvas.draw_image(RectF{static_cast(bounds.x), static_cast(bounds.y), static_cast(cache_image.width()), static_cast(cache_image.height())}, cache_image); if (snapshot.frame_metadata) - snapshot.frame_metadata->record_renderable_cache_compose(object_name_, parent_object_name, tree_depth, steady_now_ns() - begin_time, static_cast(cache_image.byte_size()), static_cast(bounds.width) * static_cast(bounds.height), edit_version, cache_ready_version.load(std::memory_order_acquire)); + snapshot.frame_metadata->record_renderable_cache_compose(node_id_, 0, object_name_, parent_object_name, tree_depth, steady_now_ns() - begin_time, static_cast(cache_image.byte_size()), static_cast(bounds.width) * static_cast(bounds.height), edit_version, cache_ready_version.load(std::memory_order_acquire)); return; } if (snapshot.frame_metadata) - snapshot.frame_metadata->record_renderable_cache_miss(object_name_, parent_object_name, tree_depth); + snapshot.frame_metadata->record_renderable_cache_miss(node_id_, 0, object_name_, parent_object_name, tree_depth); std::uint64_t rebuild_begin_time = steady_now_ns(); render_cache_image(bounds, snapshot, edit_version); if (snapshot.frame_metadata) - snapshot.frame_metadata->record_renderable_cache_rebuild(object_name_, parent_object_name, tree_depth, steady_now_ns() - rebuild_begin_time, cache_image.empty() ? 0 : static_cast(cache_image.byte_size()), static_cast(bounds.width) * static_cast(bounds.height), edit_version, cache_ready_version.load(std::memory_order_acquire)); + snapshot.frame_metadata->record_renderable_cache_rebuild(node_id_, 0, object_name_, parent_object_name, tree_depth, steady_now_ns() - rebuild_begin_time, cache_image.empty() ? 0 : static_cast(cache_image.byte_size()), static_cast(bounds.width) * static_cast(bounds.height), edit_version, cache_ready_version.load(std::memory_order_acquire)); if (!cache_image.empty()) { std::uint64_t begin_time = steady_now_ns(); canvas.draw_image(RectF{static_cast(bounds.x), static_cast(bounds.y), static_cast(cache_image.width()), static_cast(cache_image.height())}, cache_image); if (snapshot.frame_metadata) - snapshot.frame_metadata->record_renderable_cache_compose(object_name_, parent_object_name, tree_depth, steady_now_ns() - begin_time, static_cast(cache_image.byte_size()), static_cast(bounds.width) * static_cast(bounds.height), edit_version, cache_ready_version.load(std::memory_order_acquire)); + snapshot.frame_metadata->record_renderable_cache_compose(node_id_, 0, object_name_, parent_object_name, tree_depth, steady_now_ns() - begin_time, static_cast(cache_image.byte_size()), static_cast(bounds.width) * static_cast(bounds.height), edit_version, cache_ready_version.load(std::memory_order_acquire)); } } std::size_t Renderable::render_tree_node(Canvas& canvas, const Render_Frame_Snapshot& snapshot, const Renderable_Frame_Tree& tree, std::size_t index) { @@ -267,7 +295,7 @@ std::size_t Renderable::render_tree_node(Canvas& canvas, const Render_Frame_Snap else render_direct_tree_node(canvas, snapshot, tree, index); if (snapshot.frame_metadata) - snapshot.frame_metadata->record_renderable_render(object_name_, node.parent_object_name, node.render_tree_depth, mode, steady_now_ns() - begin_time); + snapshot.frame_metadata->record_renderable_render(node.node_id, node.parent_node_id, node.object_name, node.parent_object_name, node.render_tree_depth, mode, steady_now_ns() - begin_time); return std::max(index + 1, node.subtree_end); } void Renderable::render_direct_tree_node(Canvas& canvas, const Render_Frame_Snapshot& snapshot, const Renderable_Frame_Tree& tree, std::size_t index) { @@ -292,24 +320,24 @@ void Renderable::render_cached_tree_node(Canvas& canvas, const Render_Frame_Snap bool ready = cache_ready_version.load(std::memory_order_acquire) == edit_version && cache_image.size() == Size{bounds.width, bounds.height} && cache_bounds.x == bounds.x && cache_bounds.y == bounds.y && cache_bounds.width == bounds.width && cache_bounds.height == bounds.height; if (ready) { if (snapshot.frame_metadata) - snapshot.frame_metadata->record_renderable_cache_hit(object_name_, node.cache_parent_object_name, node.cache_tree_depth); + snapshot.frame_metadata->record_renderable_cache_hit(node.node_id, node.cache_parent_node_id, node.object_name, node.cache_parent_object_name, node.cache_tree_depth); std::uint64_t begin_time = steady_now_ns(); canvas.draw_image(RectF{static_cast(bounds.x), static_cast(bounds.y), static_cast(cache_image.width()), static_cast(cache_image.height())}, cache_image); if (snapshot.frame_metadata) - snapshot.frame_metadata->record_renderable_cache_compose(object_name_, node.cache_parent_object_name, node.cache_tree_depth, steady_now_ns() - begin_time, static_cast(cache_image.byte_size()), static_cast(bounds.width) * static_cast(bounds.height), edit_version, cache_ready_version.load(std::memory_order_acquire)); + snapshot.frame_metadata->record_renderable_cache_compose(node.node_id, node.cache_parent_node_id, node.object_name, node.cache_parent_object_name, node.cache_tree_depth, steady_now_ns() - begin_time, static_cast(cache_image.byte_size()), static_cast(bounds.width) * static_cast(bounds.height), edit_version, cache_ready_version.load(std::memory_order_acquire)); return; } if (snapshot.frame_metadata) - snapshot.frame_metadata->record_renderable_cache_miss(object_name_, node.cache_parent_object_name, node.cache_tree_depth); + snapshot.frame_metadata->record_renderable_cache_miss(node.node_id, node.cache_parent_node_id, node.object_name, node.cache_parent_object_name, node.cache_tree_depth); std::uint64_t rebuild_begin_time = steady_now_ns(); render_cache_image_tree_node(bounds, snapshot, tree, index, edit_version); if (snapshot.frame_metadata) - snapshot.frame_metadata->record_renderable_cache_rebuild(object_name_, node.cache_parent_object_name, node.cache_tree_depth, steady_now_ns() - rebuild_begin_time, cache_image.empty() ? 0 : static_cast(cache_image.byte_size()), static_cast(bounds.width) * static_cast(bounds.height), edit_version, cache_ready_version.load(std::memory_order_acquire)); + snapshot.frame_metadata->record_renderable_cache_rebuild(node.node_id, node.cache_parent_node_id, node.object_name, node.cache_parent_object_name, node.cache_tree_depth, steady_now_ns() - rebuild_begin_time, cache_image.empty() ? 0 : static_cast(cache_image.byte_size()), static_cast(bounds.width) * static_cast(bounds.height), edit_version, cache_ready_version.load(std::memory_order_acquire)); if (!cache_image.empty()) { std::uint64_t begin_time = steady_now_ns(); canvas.draw_image(RectF{static_cast(bounds.x), static_cast(bounds.y), static_cast(cache_image.width()), static_cast(cache_image.height())}, cache_image); if (snapshot.frame_metadata) - snapshot.frame_metadata->record_renderable_cache_compose(object_name_, node.cache_parent_object_name, node.cache_tree_depth, steady_now_ns() - begin_time, static_cast(cache_image.byte_size()), static_cast(bounds.width) * static_cast(bounds.height), edit_version, cache_ready_version.load(std::memory_order_acquire)); + snapshot.frame_metadata->record_renderable_cache_compose(node.node_id, node.cache_parent_node_id, node.object_name, node.cache_parent_object_name, node.cache_tree_depth, steady_now_ns() - begin_time, static_cast(cache_image.byte_size()), static_cast(bounds.width) * static_cast(bounds.height), edit_version, cache_ready_version.load(std::memory_order_acquire)); } } void Renderable::render_cache_image_tree_node(const Rect& bounds, const Render_Frame_Snapshot& snapshot, const Renderable_Frame_Tree& tree, std::size_t index, std::uint64_t version) { @@ -358,7 +386,7 @@ void Renderable::render_self(Canvas& canvas, const Render_Frame_Snapshot& snapsh if (snapshot.frame_metadata) { const std::string& parent_name = node ? node->parent_object_name : render_parent_object_name(); int depth = node ? node->render_tree_depth : render_tree_depth(); - snapshot.frame_metadata->record_renderable_self_draw(object_name_, parent_name, depth, mode, draw_time); + 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) { @@ -369,6 +397,7 @@ void Renderable::draw(Canvas& canvas, const Render_Frame_Snapshot& snapshot) { void Renderable::prepare_data(const Render_Frame_Snapshot& snapshot) { if (get_retiring()) return; + auto prepare_guard = d_ptr->acquire_prepare_state_read(); d_ptr->prepare_data(snapshot); if (snapshot.frame_update_states) d_ptr->drain_committed_update_states(*snapshot.frame_update_states); @@ -462,7 +491,7 @@ void Renderable::wheel_event(const Wheel_Event& event) { if (interactive) interactive->wheel_event(event); } -void Renderable::mark_render_dirty() { +void Renderable::mark_render_dirty(Dirty_Reason reason) { if (get_retiring()) return; auto context = plot_context.lock(); @@ -470,12 +499,12 @@ void Renderable::mark_render_dirty() { return; cache_edit_version.fetch_add(1, std::memory_order_acq_rel); if (auto parent = parent_renderable.lock()) { - parent->mark_render_dirty(); + parent->mark_render_dirty(reason); return; } Plot_Core* target_plot = context->owner.load(std::memory_order_acquire); if (!context->destroying.load(std::memory_order_acquire) && target_plot) - target_plot->notify_model_dirty(); + target_plot->notify_model_dirty(reason); } bool Renderable::ok() const { auto context = plot_context.lock(); diff --git a/Core/architecture/Renderable.h b/Core/architecture/Renderable.h index b01d09b..c8c5a81 100644 --- a/Core/architecture/Renderable.h +++ b/Core/architecture/Renderable.h @@ -46,7 +46,14 @@ public: void remove_child(const std::shared_ptr& child); std::vector> children_snapshot() const; std::vector> hit_renderables(const PointF& pos); - void append_tree_snapshot(Renderable_Frame_Tree& snapshot, const std::string& parent_object_name = {}, int render_tree_depth = 0, const std::string& cache_parent_object_name = {}, int cache_tree_depth = 0); + void append_tree_snapshot( + Renderable_Frame_Tree& snapshot, + std::uint64_t parent_node_id = 0, + const std::string& parent_object_name = {}, + int render_tree_depth = 0, + std::uint64_t cache_parent_node_id = 0, + const std::string& cache_parent_object_name = {}, + int cache_tree_depth = 0); void render(Canvas& canvas, const Render_Frame_Snapshot& snapshot); void render_tree_snapshot(Canvas& canvas, const Render_Frame_Snapshot& snapshot, const Renderable_Frame_Tree& tree, std::size_t index); void draw(Canvas& canvas, const Render_Frame_Snapshot& snapshot); @@ -66,6 +73,7 @@ public: [[nodiscard]] bool attached_to_plot() const; [[nodiscard]] bool shares_plot_with(const Renderable& other) const; [[nodiscard]] const std::string& object_name() const; + [[nodiscard]] std::uint64_t node_id() const; void set_object_name(std::string name); [[nodiscard]] bool is_visible() const; void set_visible(bool value); @@ -75,7 +83,7 @@ public: int cache_tree_depth() const; std::string render_parent_object_name() const; int render_tree_depth() const; - void mark_render_dirty(); + void mark_render_dirty(Dirty_Reason reason = Dirty_Reason::Core_Data); [[nodiscard]] bool ok() const; [[nodiscard]] bool get_retiring() const; virtual ~Renderable(); @@ -87,6 +95,7 @@ private: Render_Object_Owner d_owner; Render_Data* d_ptr{}; Plot_Core* plot{}; + std::uint64_t node_id_{}; std::string object_name_; std::weak_ptr parent_renderable; std::atomic_bool visible_{true}; diff --git a/Core/architecture/Strategy_Render_Data.h b/Core/architecture/Strategy_Render_Data.h new file mode 100644 index 0000000..3eb5e12 --- /dev/null +++ b/Core/architecture/Strategy_Render_Data.h @@ -0,0 +1,39 @@ +#pragma once +#include "Render_Data.h" +#include "global.h" +#include +#include +#include +#include +#include + +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 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* state(Renderable_State_Role role) override; + Input_Data* input_data(Renderable_Input_Role role) override; + void clear_render_input(); + +private: + std::unique_ptr runtime; +}; + +} // namespace renderive diff --git a/Core/execution/Render_Executor.cpp b/Core/execution/Render_Executor.cpp index 8e23096..0749c7f 100644 --- a/Core/execution/Render_Executor.cpp +++ b/Core/execution/Render_Executor.cpp @@ -3,16 +3,42 @@ #include #include #include -#include #include -#include -#include -#include #include #include #include namespace renderive { +struct Taskflow_Graph_Impl { + explicit Taskflow_Graph_Impl(tf::Taskflow& taskflow) : taskflow(taskflow) {} + tf::Taskflow& taskflow; + std::vector tasks; +}; + +Render_Task_Graph::Render_Task_Graph(void* impl) noexcept : impl(impl) {} + +Render_Task_Graph_Node Render_Task_Graph::emplace(Task work) { + auto* graph = static_cast(impl); + if (!graph || !work) + return {}; + auto work_ptr = std::make_shared(std::move(work)); + auto task = graph->taskflow.emplace([work_ptr]() mutable { + if (work_ptr && *work_ptr) + (*work_ptr)(); + }); + graph->tasks.push_back(task); + return {static_cast(graph->tasks.size() - 1)}; +} + +void Render_Task_Graph::precede(Render_Task_Graph_Node before, Render_Task_Graph_Node after) { + auto* graph = static_cast(impl); + if (!graph || !before || !after) + return; + if (before.index >= graph->tasks.size() || after.index >= graph->tasks.size()) + return; + graph->tasks[before.index].precede(graph->tasks[after.index]); +} + namespace { struct Render_Executor_Metrics { @@ -114,11 +140,6 @@ class Render_Executor_Private { std::uint64_t queue_wait_ns{}; }; - struct Pending_Frame_Task { - Render_Executor_Task task; - std::uint64_t enqueue_ns{}; - }; - public: explicit Render_Executor_Private(std::size_t worker_count) : metrics(std::make_shared()), @@ -132,76 +153,20 @@ public: } bool try_submit(Render_Executor_Task task) { - if (shutting_down.load(std::memory_order_acquire) || (!task.work && !task.build_taskflow)) { + if (shutting_down.load(std::memory_order_acquire) || (!task.work && !task.build_graph)) { reject(task); return false; } std::uint64_t enqueue_ns = steady_now_ns(); - if (!task.frame_job) { - record_submission(task); - submit_to_executor(std::move(task), enqueue_ns); - return true; - } - - bool submit_now = false; - { - std::lock_guard lock(admission_mutex); - if (plot_already_pending_or_admitted_locked(task.plot_id)) { - reject(task); - return false; - } - - std::size_t limit = frame_admission_limit(); - if (admitted_frame_count < limit) { - admit_frame_locked(task.plot_id); - submit_now = true; - } - else { - std::size_t queue_limit = frame_queue_limit(); - if (pending_frame_tasks.size() >= queue_limit) { - reject(task); - return false; - } - if (task.plot_id) - queued_frame_plots.insert(task.plot_id); - record_submission(task); - pending_frame_tasks.push_back(Pending_Frame_Task{std::move(task), enqueue_ns}); - return true; - } - } - - if (submit_now) { - record_submission(task); - submit_to_executor(std::move(task), enqueue_ns); - return true; - } - - reject(task); - return false; + record_submission(task); + submit_to_executor(std::move(task), enqueue_ns); + return true; } void shutdown() { if (shutting_down.exchange(true, std::memory_order_acq_rel)) return; - std::vector cancelled_tasks; - { - std::lock_guard lock(admission_mutex); - while (!pending_frame_tasks.empty()) { - Pending_Frame_Task pending = std::move(pending_frame_tasks.front()); - pending_frame_tasks.pop_front(); - cancelled_tasks.push_back(std::move(pending.task)); - metrics->queued_frame_jobs.fetch_sub(1, std::memory_order_relaxed); - } - pending_frame_tasks.clear(); - queued_frame_plots.clear(); - } - for (auto& task : cancelled_tasks) { - if (task.frame_stat) - task.frame_stat->executor_at_finish = snapshot(); - if (task.completion) - task.completion(); - } executor.wait_for_all(); } @@ -288,8 +253,12 @@ private: if (task.frame_job) metrics->active_frame_jobs.fetch_sub(1, std::memory_order_relaxed); }); - if (task_ptr->build_taskflow) { - task_ptr->build_taskflow(*topology, entry, exit); + if (task_ptr->build_graph) { + Taskflow_Graph_Impl graph_impl(*topology); + graph_impl.tasks.push_back(entry); + graph_impl.tasks.push_back(exit); + Render_Task_Graph graph(&graph_impl); + task_ptr->build_graph(graph, {0}, {1}); } else { auto work = topology->emplace([task_ptr]() mutable { @@ -300,63 +269,12 @@ private: work.precede(exit); } executor.run(*topology, [this, topology, completion, task_ptr]() mutable { - release_and_drain_next(*task_ptr); + (void)this; if (completion && *completion) (*completion)(); }); } - std::size_t frame_admission_limit() const { - return std::max(1, metrics->worker_count); - } - - std::size_t frame_queue_limit() const { - return std::max(1, frame_admission_limit() * 2); - } - - bool plot_already_pending_or_admitted_locked(Plot_Execution_Id plot_id) const { - if (!plot_id) - return false; - return admitted_frame_plots.find(plot_id) != admitted_frame_plots.end() - || queued_frame_plots.find(plot_id) != queued_frame_plots.end(); - } - - void admit_frame_locked(Plot_Execution_Id plot_id) { - ++admitted_frame_count; - if (plot_id) - admitted_frame_plots.insert(plot_id); - } - - std::optional take_next_frame_task_locked() { - if (shutting_down.load(std::memory_order_acquire) || pending_frame_tasks.empty()) - return std::nullopt; - if (admitted_frame_count >= frame_admission_limit()) - return std::nullopt; - - Pending_Frame_Task next = std::move(pending_frame_tasks.front()); - pending_frame_tasks.pop_front(); - if (next.task.plot_id) - queued_frame_plots.erase(next.task.plot_id); - admit_frame_locked(next.task.plot_id); - return next; - } - - void release_and_drain_next(const Render_Executor_Task& task) { - if (!task.frame_job) - return; - std::optional next; - { - std::lock_guard lock(admission_mutex); - if (admitted_frame_count) - --admitted_frame_count; - if (task.plot_id) - admitted_frame_plots.erase(task.plot_id); - next = take_next_frame_task_locked(); - } - if (next) - submit_to_executor(std::move(next->task), next->enqueue_ns); - } - void reject(const Render_Executor_Task& task) { if (task.frame_job) metrics->rejected_frame_jobs.fetch_add(1, std::memory_order_relaxed); @@ -369,11 +287,6 @@ private: std::shared_ptr metrics; tf::Executor executor; std::shared_ptr observer; - std::mutex admission_mutex; - std::unordered_set admitted_frame_plots; - std::unordered_set queued_frame_plots; - std::deque pending_frame_tasks; - std::size_t admitted_frame_count{}; std::atomic_bool shutting_down{false}; }; diff --git a/Core/execution/Render_Executor.h b/Core/execution/Render_Executor.h index d4e172c..72d7016 100644 --- a/Core/execution/Render_Executor.h +++ b/Core/execution/Render_Executor.h @@ -2,17 +2,31 @@ #include "../architecture/Render_Config.h" #include "../architecture/global.h" #include "../base/Task.h" +#include #include #include -#include namespace renderive { +struct Render_Task_Graph_Node { + std::uint32_t index = UINT32_MAX; + explicit operator bool() const noexcept { + return index != UINT32_MAX; + } +}; +class LIB_DECL Render_Task_Graph { +public: + explicit Render_Task_Graph(void* impl) noexcept; + [[nodiscard]] Render_Task_Graph_Node emplace(Task work); + void precede(Render_Task_Graph_Node before, Render_Task_Graph_Node after); +private: + void* impl{}; +}; struct Render_Executor_Task { - using Taskflow_Builder = std::function; + using Graph_Builder = std::function; Plot_Execution_Id plot_id{}; std::uint64_t frame_id{}; Render_Task_Kind kind = Render_Task_Kind::Primitive; Task work; - Taskflow_Builder build_taskflow; + Graph_Builder build_graph; Task completion; std::shared_ptr frame_stat; std::uint32_t logical_task_count{1}; diff --git a/Core/flow/Frame_Flow.h b/Core/flow/Frame_Flow.h index a6e039c..8eb952d 100644 --- a/Core/flow/Frame_Flow.h +++ b/Core/flow/Frame_Flow.h @@ -18,6 +18,7 @@ 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 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; @@ -31,7 +32,7 @@ public: virtual void activate_view(std::uint64_t now_ns) = 0; virtual void deactivate_view() = 0; [[nodiscard]] virtual bool view_active() const = 0; - virtual void notify_model_dirty(std::uint64_t now_ns) = 0; + virtual void notify_model_dirty(Dirty_Reason reason, std::uint64_t now_ns) = 0; virtual void notify_viewport_changed(Size viewport, std::uint64_t now_ns) = 0; virtual void notify_render_finished(Frame_Lifecycle_Record& frame, std::uint64_t now_ns) = 0; virtual void notify_paint_finished(std::uint64_t now_ns) = 0; diff --git a/Core/flow/explicit/Explicit_Frame_Flow.cpp b/Core/flow/explicit/Explicit_Frame_Flow.cpp index e3caf36..efa207d 100644 --- a/Core/flow/explicit/Explicit_Frame_Flow.cpp +++ b/Core/flow/explicit/Explicit_Frame_Flow.cpp @@ -42,6 +42,10 @@ 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; +} + Frame_Flow_Runtime_Snapshot Explicit_Frame_Flow::runtime_snapshot(std::uint64_t now_ns, bool destroying) const { return { destroying, @@ -197,7 +201,7 @@ bool Explicit_Frame_Flow::view_active() const { return view_active_state.load(std::memory_order_acquire); } -void Explicit_Frame_Flow::notify_model_dirty(std::uint64_t now_ns) { +void Explicit_Frame_Flow::notify_model_dirty(Dirty_Reason, std::uint64_t now_ns) { evaluate(now_ns); } diff --git a/Core/flow/explicit/Explicit_Frame_Flow.h b/Core/flow/explicit/Explicit_Frame_Flow.h index 0a2968e..7c9c4ba 100644 --- a/Core/flow/explicit/Explicit_Frame_Flow.h +++ b/Core/flow/explicit/Explicit_Frame_Flow.h @@ -13,6 +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]] 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; @@ -26,7 +27,10 @@ public: void activate_view(std::uint64_t now_ns) override; void deactivate_view() override; [[nodiscard]] bool view_active() const override; - void notify_model_dirty(std::uint64_t now_ns) override; + void notify_model_dirty(std::uint64_t now_ns) { + notify_model_dirty(Dirty_Reason::Core_Data, now_ns); + } + void notify_model_dirty(Dirty_Reason reason, std::uint64_t now_ns) override; void notify_viewport_changed(Size viewport, std::uint64_t now_ns) override; void notify_render_finished(Frame_Lifecycle_Record& frame, std::uint64_t now_ns) override; void notify_paint_finished(std::uint64_t now_ns) override; diff --git a/Core/flow/low_latency/Low_Latency_Feedback_Controller.cpp b/Core/flow/low_latency/Low_Latency_Feedback_Controller.cpp index 474c279..f4a84b4 100644 --- a/Core/flow/low_latency/Low_Latency_Feedback_Controller.cpp +++ b/Core/flow/low_latency/Low_Latency_Feedback_Controller.cpp @@ -27,6 +27,14 @@ double Low_Latency_Smoothed_Interval::safe_ms(double jitter_factor) const { return sample_count ? srtt_ms + jitter_factor * jitter_ms : 0.0; } +std::uint64_t Low_Latency_Smoothed_Interval::srtt_ns() const { + return sample_count ? static_cast(srtt_ms * 1000000.0) : 0; +} + +std::uint64_t Low_Latency_Smoothed_Interval::jitter_ns() const { + return sample_count ? static_cast(jitter_ms * 1000000.0) : 0; +} + bool Low_Latency_Smoothed_Interval::valid() const { return sample_count > 0; } @@ -43,11 +51,16 @@ void Low_Latency_Feedback_Controller::set_max_render_fps(double fps) { refresh_control_snapshot(); } -void Low_Latency_Feedback_Controller::on_input_changed(std::uint64_t, std::uint64_t input_version) { +void Low_Latency_Feedback_Controller::on_input_changed(std::uint64_t now_ns, std::uint64_t input_version, Dirty_Reason reason) { + if (reason == Dirty_Reason::Core_Data && last_input_change_ns && now_ns > last_input_change_ns) + input_period.update(static_cast(now_ns - last_input_change_ns) / 1000000.0); + if (reason == Dirty_Reason::Core_Data) + last_input_change_ns = now_ns; if (input_version) latest_input_version = input_version; else ++latest_input_version; + refresh_control_snapshot(); } void Low_Latency_Feedback_Controller::on_render_started(Frame_Lifecycle_Record& frame, std::uint64_t now_ns) { @@ -62,6 +75,8 @@ void Low_Latency_Feedback_Controller::on_render_completed(Frame_Lifecycle_Record 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(); write_state(frame); } @@ -69,18 +84,38 @@ void Low_Latency_Feedback_Controller::on_render_completed(Frame_Lifecycle_Record 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); + 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(); 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(); write_state(frame); } +void Low_Latency_Feedback_Controller::on_frame_repeated() { + ++repeated_frame_count; + refresh_control_snapshot(); +} + +void Low_Latency_Feedback_Controller::on_frame_underrun() { + ++underrun_count; + refresh_control_snapshot(); +} + void Low_Latency_Feedback_Controller::on_frame_dropped(Frame_Lifecycle_Record& frame, Frame_Outcome outcome) { + if (outcome == Frame_Outcome::Paint_Acquire_Dropped || outcome == Frame_Outcome::Render_Acquire_Dropped) + ++underrun_count; frame.outcome = outcome; refresh_control_snapshot(); write_state(frame); @@ -105,8 +140,9 @@ void Low_Latency_Feedback_Controller::write_state(Frame_Lifecycle_Record& frame) void Low_Latency_Feedback_Controller::refresh_control_snapshot() { std::uint64_t render_period_ns = render_period.valid() ? ms_to_ns(render_period.safe_ms(0.5)) : 0; std::uint64_t paint_period_ns = paint_period.valid() ? ms_to_ns(paint_period.safe_ms(0.5)) : 0; + std::uint64_t input_period_ns = input_period.valid() ? ms_to_ns(input_period.safe_ms(0.5)) : 0; std::uint64_t user_period_ns = state_value.user_period_ns; - Refresh_Limit_Source candidate = classify_limit_source(render_period_ns, paint_period_ns, user_period_ns); + 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: @@ -122,14 +158,43 @@ void Low_Latency_Feedback_Controller::refresh_control_snapshot() { break; } update_limit_source(candidate, candidate_period); - std::uint64_t target_period = std::max({render_period_ns, paint_period_ns, user_period_ns}); + 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; 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(); + state_value.gui_jitter_ns = gui_period.jitter_ns(); + state_value.consumption_srtt_ns = consumption_period.srtt_ns(); + 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.target_period_ns = target_period; + 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 && last_render_begin_ns) - next_render_time = last_render_begin_ns + user_period_ns; + 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 next_render_time = 0; } diff --git a/Core/flow/low_latency/Low_Latency_Feedback_Controller.h b/Core/flow/low_latency/Low_Latency_Feedback_Controller.h index 68915e5..455496f 100644 --- a/Core/flow/low_latency/Low_Latency_Feedback_Controller.h +++ b/Core/flow/low_latency/Low_Latency_Feedback_Controller.h @@ -10,6 +10,8 @@ struct Low_Latency_Smoothed_Interval { std::uint64_t sample_count{}; void update(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; [[nodiscard]] bool valid() const; }; @@ -23,11 +25,13 @@ class Low_Latency_Feedback_Controller { public: void reset(); void set_max_render_fps(double fps); - void on_input_changed(std::uint64_t now_ns, std::uint64_t input_version = 0); + void on_input_changed(std::uint64_t now_ns, std::uint64_t input_version = 0, Dirty_Reason reason = Dirty_Reason::Core_Data); void on_render_started(Frame_Lifecycle_Record& frame, std::uint64_t now_ns); void on_render_completed(Frame_Lifecycle_Record& frame); void on_frame_presented(Frame_Lifecycle_Record& frame); void on_frame_superseded(Frame_Lifecycle_Record& frame); + void on_frame_repeated(); + void on_frame_underrun(); void on_frame_dropped(Frame_Lifecycle_Record& frame, Frame_Outcome outcome); [[nodiscard]] bool rate_limit_ready(std::uint64_t now_ns) const; [[nodiscard]] std::uint64_t next_render_time_ns() const; @@ -43,11 +47,21 @@ private: Refresh_Control_Snapshot state_value; Low_Latency_Smoothed_Interval render_period; Low_Latency_Smoothed_Interval paint_period; + Low_Latency_Smoothed_Interval input_period; + Low_Latency_Smoothed_Interval ready_queue_period; + 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{}; std::uint64_t latest_input_version{}; + std::uint64_t last_input_change_ns{}; std::uint64_t last_render_begin_ns{}; std::uint64_t next_render_time{}; + std::uint64_t presented_frame_count{}; + std::uint64_t superseded_frame_count{}; + std::uint64_t repeated_frame_count{}; + std::uint64_t underrun_count{}; }; } // namespace renderive diff --git a/Core/flow/low_latency/Low_Latency_Frame_Flow.cpp b/Core/flow/low_latency/Low_Latency_Frame_Flow.cpp index 2e61d7b..bd939c8 100644 --- a/Core/flow/low_latency/Low_Latency_Frame_Flow.cpp +++ b/Core/flow/low_latency/Low_Latency_Frame_Flow.cpp @@ -62,6 +62,17 @@ struct Low_Latency_Frame_Flow::Private { return; } Refresh_Control_Snapshot control = feedback.state(); + if (control.production_strategy == Low_Latency_Production_Strategy::Cost_Balanced) { + if (runtime.middle_has_new_frame) + return; + std::uint64_t next_ns = feedback.next_render_time_ns(); + if (next_ns && !feedback.rate_limit_ready(runtime.now_ns)) { + host->schedule_render_deadline(next_ns); + return; + } + (void)host->try_render_once(); + return; + } switch (control.source) { case Refresh_Limit_Source::Paint: if (!runtime.middle_has_new_frame) @@ -116,6 +127,10 @@ 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; +} + Frame_Flow_Runtime_Snapshot Low_Latency_Frame_Flow::runtime_snapshot(std::uint64_t now_ns, bool destroying) const { return { destroying, @@ -169,6 +184,8 @@ Render_Surface_Publish_Result Low_Latency_Frame_Flow::publish_rendered_frame(Ren if (handle->lease.frame && handle->lease.frame->metadata) handle->lease.frame->version.store(handle->lease.frame->metadata->frame_id, std::memory_order_release); result = to_surface_result(d->pipeline.try_publish_rendered_frame(std::move(handle->lease), now_ns)); + if (result.dropped_frame_record) + d->feedback.on_frame_dropped(*result.dropped_frame_record, result.dropped_frame_record->outcome); } d->pipeline.render_job_state = Frame_Job_State::Idle; return result; @@ -181,6 +198,10 @@ 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); returned_frame_record = std::move(consumed.returned_frame_record); + if (consumed.paint_frame_unavailable) + d->feedback.on_frame_underrun(); + else if (consumed.lease && !consumed.has_new_frame) + d->feedback.on_frame_repeated(); if (!consumed.lease) return Present_Surface_Lease_Access::empty(consumed.has_new_frame, consumed.present_request_was_pending); return Present_Surface_Lease_Access::from_handle( @@ -220,8 +241,8 @@ bool Low_Latency_Frame_Flow::view_active() const { return d->active.load(std::memory_order_acquire); } -void Low_Latency_Frame_Flow::notify_model_dirty(std::uint64_t now_ns) { - d->feedback.on_input_changed(now_ns); +void Low_Latency_Frame_Flow::notify_model_dirty(Dirty_Reason reason, std::uint64_t now_ns) { + d->feedback.on_input_changed(now_ns, 0, reason); d->evaluate(now_ns); } diff --git a/Core/flow/low_latency/Low_Latency_Frame_Flow.h b/Core/flow/low_latency/Low_Latency_Frame_Flow.h index b5de8bd..13b8c4f 100644 --- a/Core/flow/low_latency/Low_Latency_Frame_Flow.h +++ b/Core/flow/low_latency/Low_Latency_Frame_Flow.h @@ -17,6 +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]] 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; @@ -30,7 +31,10 @@ public: void activate_view(std::uint64_t now_ns) override; void deactivate_view() override; [[nodiscard]] bool view_active() const override; - void notify_model_dirty(std::uint64_t now_ns) override; + void notify_model_dirty(std::uint64_t now_ns) { + notify_model_dirty(Dirty_Reason::Core_Data, now_ns); + } + void notify_model_dirty(Dirty_Reason reason, std::uint64_t now_ns) override; void notify_viewport_changed(Size viewport, std::uint64_t now_ns) override; void notify_render_finished(Frame_Lifecycle_Record& frame, std::uint64_t now_ns) override; void notify_paint_finished(std::uint64_t now_ns) override; diff --git a/Core/flow/low_latency/Render_Data.cpp b/Core/flow/low_latency/Render_Data.cpp index 5add672..26de081 100644 --- a/Core/flow/low_latency/Render_Data.cpp +++ b/Core/flow/low_latency/Render_Data.cpp @@ -1,8 +1,16 @@ -#include "Render_Data.h" +#include "../../architecture/Strategy_Render_Data.h" #include "../../architecture/Renderable.h" +#include "../../base/Memory.h" +#include "Low_Latency_Roles.h" +#include "Triple_Buffer.h" +#include +#include #include #include +#include + namespace renderive { + std::ostream& operator<<(std::ostream& stream, const Range& range) { stream << "Range(" << range.origin << ", " << range.target << ")"; return stream; @@ -10,70 +18,353 @@ std::ostream& operator<<(std::ostream& stream, const Range& range) { namespace { -struct Low_Latency_State_Read_Handle final : Render_State_Read_Handle { - Low_Latency_State_Read_Handle(Low_Latency_Render_Data* data, Triple_Buffer_Lease lease) - : data(data), lease(lease) {} +struct Input_Buffer_Slot { + Input_Data* data{}; + std::uint64_t version = 0; + bool pending = false; + std::shared_ptr completion_state; +}; - ~Low_Latency_State_Read_Handle() override { - if (data && lease) - data->unmark_state(lease); +std::uint8_t state_role_index(Renderable_State_Role role) { + switch (role) { + case Renderable_State_Role::Edit: + return State_Edit; + case Renderable_State_Role::Ready: + return State_Ready; + case Renderable_State_Role::Render: + return State_Render; } + return State_Render; +} - Low_Latency_Render_Data* data{}; +std::uint8_t input_role_index(Renderable_Input_Role role) { + switch (role) { + case Renderable_Input_Role::Edit: + return Input_Edit; + case Renderable_Input_Role::Ready: + return Input_Ready; + case Renderable_Input_Role::Render: + return Input_Render; + } + return Input_Render; +} + +} // namespace + +struct Strategy_Render_Data_Private { + Renderable_Runtime_Strategy runtime_strategy_value = Renderable_Runtime_Strategy::Low_Latency; + Triple_Role_Buffer_Control state_control; + std::array state_buffers{}; + std::array state_buffer_owners{}; + Triple_Role_Buffer_Control input_control; + std::array input_buffers{}; + 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{}; + } + 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) + continue; + 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) { + state_buffer_owners[i].reset(); + state_buffers[i] = nullptr; + } + state_control.reset(); + } + void clear_input_buffers() { + input_control.close_and_wait(); + cancel_pending_completions(); + for (std::size_t i = 0; i < input_buffers.size(); ++i) { + input_buffer_owners[i].reset(); + input_buffers[i].data = nullptr; + input_buffers[i].version = 0; + input_buffers[i].pending = false; + input_buffers[i].completion_state.reset(); + } + 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) { + 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_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); + if (input_buffer_owners[Input_Edit]) + input_buffers[Input_Edit].data = input_buffer_owners[Input_Edit].get(); + if (input_buffer_owners[Input_Ready]) + input_buffers[Input_Ready].data = input_buffer_owners[Input_Ready].get(); + if (input_buffer_owners[Input_Render]) + input_buffers[Input_Render].data = input_buffer_owners[Input_Render].get(); + if (state_buffers[State_Edit]) + state_buffers[State_Edit]->version = 1; + } + void 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; + return; + } + Input_Buffer_Slot& slot = input_slot(input_role_index(role)); + if (slot.completion_state) { + if (role == Renderable_Input_Role::Render) { + slot.completion_state->complete_until(Update_Stage::Committed); + committed_update_states.push_back(std::move(slot.completion_state)); + } + else { + slot.completion_state->complete_all({}, Update_Outcome::Superseded); + slot.completion_state.reset(); + } + } + 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) + : runtime(runtime), lease(lease) {} + ~Low_Latency_State_Read_Handle() override { + if (runtime && lease) + runtime->state_control.unmark_use(lease); + } + Strategy_Render_Data_Private* runtime{}; Triple_Buffer_Lease lease{}; }; struct Low_Latency_State_Edit_Handle final : Render_State_Edit_Handle { - Low_Latency_State_Edit_Handle(Low_Latency_Render_Data* data, Triple_Buffer_Lease lease) - : data(data), lease(lease) {} - + 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 { - if (!data || !lease) + if (!runtime || !lease) return; - ++data->state_by_index(lease.index)->version; - auto q = data->q_ptr; - data->unmark_state(lease); - if (q) - q->mark_render_dirty(); + 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); } - - Low_Latency_Render_Data* data{}; + Strategy_Render_Data_Private* runtime{}; + Renderable* owner{}; 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 { + if (!runtime || !lease) + return; + runtime->mark_input_dirty(lease); + runtime->input_control.unmark_use(lease); + if (notify_render && owner) + 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{}; + 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 -Render_Edit_Guard::Render_Edit_Guard(Low_Latency_Render_Data* data, Triple_Buffer_Lease lease) : data(data), lease(lease) {} -Render_Edit_Guard::~Render_Edit_Guard() { - if (!data || !lease) - return; - ++data->state_by_index(lease.index)->version; - auto q = data->q_ptr; - data->unmark_state(lease); - if (q) - q->mark_render_dirty(); -} -void Low_Latency_Render_Data::mark_input_dirty(Triple_Buffer_Lease lease, bool notify_render) { - Input_Buffer_Slot& slot = input_slot_by_index(lease.index); - slot.version = ++input_version; - slot.pending = true; - if (notify_render && q_ptr) - q_ptr->mark_render_dirty(); -} -void Low_Latency_Render_Data::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 Low_Latency_Render_Data::drain_committed_update_states(std::pmr::vector>& output) { - for (auto& state : committed_update_states) - output.push_back(std::move(state)); - committed_update_states.clear(); +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(); } -void Low_Latency_Render_Data::initialize_runtime(Renderable& owner) { - initialize_runtime_buffers( +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(), @@ -82,105 +373,147 @@ void Low_Latency_Render_Data::initialize_runtime(Renderable& owner) { owner.create_input_data()); } -void Low_Latency_Render_Data::initialize_runtime_buffers( - Render_Object_Owner edit_state, - Render_Object_Owner ready_state, - Render_Object_Owner render_state, - Render_Object_Owner edit_input, - Render_Object_Owner ready_input, - Render_Object_Owner render_input) { - clear_state_buffers(); - clear_input_buffers(); - 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_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); - if (input_buffer_owners[Input_Edit]) - input_buffers[Input_Edit].data = input_buffer_owners[Input_Edit].get(); - if (input_buffer_owners[Input_Ready]) - input_buffers[Input_Ready].data = input_buffer_owners[Input_Ready].get(); - if (input_buffer_owners[Input_Render]) - input_buffers[Input_Render].data = input_buffer_owners[Input_Render].get(); - if (state_buffers[State_Edit]) - state_buffers[State_Edit]->version = 1; +Renderable_Runtime_Strategy Strategy_Render_Data::runtime_strategy() const { + return runtime->runtime_strategy_value; } -Render_State_Read_Guard Low_Latency_Render_Data::acquire_render_state_read() { - Triple_Buffer_Lease lease = try_mark_state_role(State_Render); + +void Strategy_Render_Data::cancel_pending_completions() { + runtime->cancel_pending_completions(); +} + +void Strategy_Render_Data::drain_committed_update_states(std::pmr::vector>& output) { + for (auto& state : runtime->committed_update_states) + output.push_back(std::move(state)); + runtime->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)); if (!lease) return {}; - return Render_State_Read_Guard(state_by_index(lease.index), std::make_unique(this, lease)); + return Render_State_Read_Guard(runtime->state_by_index(lease.index), std::make_unique(runtime.get(), lease)); } -Render_State_Read_Guard Low_Latency_Render_Data::acquire_edit_state_read() { - Triple_Buffer_Lease lease = mark_state_role(State_Edit); +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); if (!lease) return {}; - return Render_State_Read_Guard(state_by_index(lease.index), std::make_unique(this, lease)); + return Render_State_Read_Guard(runtime->state_by_index(lease.index), std::make_unique(runtime.get(), lease)); } -Render_State_Edit_Guard Low_Latency_Render_Data::acquire_edit_state() { - Triple_Buffer_Lease lease = mark_state_role(State_Edit); +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); if (!lease) return {}; - return Render_State_Edit_Guard(state_by_index(lease.index), std::make_unique(this, lease)); + return Render_State_Read_Guard(runtime->state_by_index(lease.index), std::make_unique(runtime.get(), lease)); } -void Low_Latency_Render_Data::clear_state_buffers() { - state_control.close_and_wait(); - for (std::size_t i = 0; i < state_buffers.size(); ++i) { - state_buffer_owners[i].reset(); - state_buffers[i] = nullptr; - } - state_control.reset(); +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))); } -void Low_Latency_Render_Data::clear_input_role(std::uint8_t role) { - Input_Buffer_Slot& slot = input_slot(role); - if (slot.completion_state) { - if (role == Input_Render) { - slot.completion_state->complete_until(Update_Stage::Committed); - committed_update_states.push_back(std::move(slot.completion_state)); - } - else { - slot.completion_state->complete_all({}, Update_Outcome::Superseded); - slot.completion_state.reset(); - } +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))); } - if (slot.data) - slot.data->clear(); - slot.pending = false; -} -void Low_Latency_Render_Data::clear_input_buffers() { - input_control.close_and_wait(); - cancel_pending_completions(); - for (std::size_t i = 0; i < input_buffers.size(); ++i) { - input_buffer_owners[i].reset(); - input_buffers[i].data = nullptr; - input_buffers[i].version = 0; - input_buffers[i].pending = false; - input_buffers[i].completion_state.reset(); - } - input_version = 0; - input_control.reset(); + Triple_Buffer_Lease lease = runtime->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)); } -void Low_Latency_Render_Data::cancel_pending_completions() { - for (Input_Buffer_Slot& slot : input_buffers) { - if (slot.completion_state) { - slot.completion_state->complete_all(std::make_error_code(std::errc::operation_canceled), Update_Outcome::Cancelled); - slot.completion_state.reset(); - } +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)); } - cancel_update_states(committed_update_states); + auto record = runtime->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); + if (!input) { + runtime->input_control.unmark_use(record.lease); + return {}; + } + return Render_Input_Edit_Guard(input, std::make_unique(runtime.get(), q_ptr, record.lease, notify_render)); } -Low_Latency_Render_Data::~Low_Latency_Render_Data() { - clear_state_buffers(); - clear_input_buffers(); +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; + } + 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); + }); } + +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)); +} + +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)); +} + +void Strategy_Render_Data::clear_render_input() { + runtime->clear_input_role(Renderable_Input_Role::Render); +} + } // namespace renderive diff --git a/Core/flow/low_latency/Render_Data.h b/Core/flow/low_latency/Render_Data.h index 9712974..c3d3e43 100644 --- a/Core/flow/low_latency/Render_Data.h +++ b/Core/flow/low_latency/Render_Data.h @@ -1,117 +1,8 @@ #pragma once -#include "../../architecture/Buffer_Role.h" -#include "../../architecture/Render_Data.h" -#include "../../architecture/Update_Completion.h" -#include "Low_Latency_Roles.h" -#include "Triple_Buffer.h" -#include -#include -#include -#include -#include +#include "../../architecture/Strategy_Render_Data.h" namespace renderive { -struct Input_Buffer_Slot { - Input_Data* data{}; - std::uint64_t version = 0; - bool pending = false; - std::shared_ptr completion_state; -}; - -struct Low_Latency_Render_Data : Render_Data { - Triple_Role_Buffer_Control state_control; - std::array state_buffers{}; - std::array state_buffer_owners{}; - Triple_Role_Buffer_Control input_control; - std::array input_buffers{}; - std::array input_buffer_owners{}; - std::pmr::vector> committed_update_states{memory_resource(Memory_Domain::Update_Completion)}; - std::uint64_t input_version = 0; - - Render_State* state(std::uint8_t role) const { - return state_by_index(state_control.read_view()[role]); - } - Render_State* state(State_Role src) const { - return state(static_cast(src)); - } - Render_State* state_by_index(std::uint8_t index) const { - return state_buffers[index]; - } - Triple_Buffer_Lease try_mark_state_role(std::uint8_t role) { - auto record = state_control.try_acquire_role(role); - if (record.result != Triple_Buffer_Result::Success) - return {}; - return record.lease; - } - Triple_Buffer_Lease mark_state_role(std::uint8_t role) { - auto record = state_control.acquire_role(role); - if (record.result != Triple_Buffer_Result::Success) - return {}; - return record.lease; - } - void unmark_state(Triple_Buffer_Lease lease) { - state_control.unmark_use(lease); - } - void clear_state_buffers(); - Input_Data* input_data(std::uint8_t role) const { - return input_slot(role).data; - } - Input_Data* input_data_by_index(std::uint8_t index) const { - return input_buffers[index].data; - } - 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_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]; - } - void mark_input_dirty(Triple_Buffer_Lease lease, bool notify_render = true); - void set_input_completion(Triple_Buffer_Lease lease, std::shared_ptr state); - void drain_committed_update_states(std::pmr::vector>& output) override; - void initialize_runtime(Renderable& owner) override; - void initialize_runtime_buffers( - Render_Object_Owner edit_state, - Render_Object_Owner ready_state, - Render_Object_Owner render_state, - Render_Object_Owner edit_input, - Render_Object_Owner ready_input, - Render_Object_Owner render_input) override; - Render_State_Read_Guard acquire_render_state_read() override; - Render_State_Read_Guard acquire_edit_state_read() override; - Render_State_Edit_Guard acquire_edit_state() override; - void clear_input_role(std::uint8_t role); - void clear_render_input() { - clear_input_role(Input_Render); - } - Triple_Buffer_Lease try_mark_render_state() { - return try_mark_state_role(State_Render); - } - Triple_Buffer_Lease try_mark_edit_state() { - return try_mark_state_role(State_Edit); - } - Triple_Buffer_Lease mark_edit_state() { - return mark_state_role(State_Edit); - } - void clear_input_buffers(); - void cancel_pending_completions() override; - ~Low_Latency_Render_Data() override; -}; - -class Plot_Core; - -struct LIB_DECL Render_Edit_Guard { - Render_Edit_Guard(Low_Latency_Render_Data* data, Triple_Buffer_Lease lease); - ~Render_Edit_Guard(); - - Low_Latency_Render_Data* data{}; - Triple_Buffer_Lease lease{}; -}; +using Low_Latency_Render_Data = Strategy_Render_Data; } // namespace renderive diff --git a/Core/flow/low_latency/Render_Lease.h b/Core/flow/low_latency/Render_Lease.h index b0fc122..3845f28 100644 --- a/Core/flow/low_latency/Render_Lease.h +++ b/Core/flow/low_latency/Render_Lease.h @@ -1,246 +1,4 @@ #pragma once -#include "../../architecture/Buffer_Role.h" -#include "../../architecture/Renderable.h" -#include "Render_Data.h" +#include "../../architecture/Render_Lease.h" #include "Low_Latency_Roles.h" -#include -namespace renderive { -template - requires std::derived_from -struct Typed_Render_Data_Base : Base { - Owner* q() { - return reinterpret_cast(this->q_ptr); - } - State* render_state() override { - return reinterpret_cast(this->state(State_Render)); - } - State* ready_state() override { - return reinterpret_cast(this->state(State_Ready)); - } - State* edit_state() override { - return reinterpret_cast(this->state(State_Edit)); - } - Input* render_input_data() override { - return reinterpret_cast(this->input_data(Input_Render)); - } - Input* edit_input_data() override { - return reinterpret_cast(this->input_data(Input_Edit)); - } - State* state_data(State_Role src) { - return reinterpret_cast(this->state(src)); - } - State* state_data_by_index(std::uint8_t index) { - return reinterpret_cast(this->state_by_index(index)); - } - Input* input_data_by_index(std::uint8_t index) { - return reinterpret_cast(Base::input_data_by_index(index)); - } - template - requires std::derived_from - Field state_value(Field Source_State::* field, State_Role src) { - auto record = this->state_control.acquire_role(static_cast(src)); - if (record.result != Triple_Buffer_Result::Success) - return {}; - Triple_Buffer_Lease lease = record.lease; - auto state = state_data_by_index(lease.index); - Field result = state->*field; - this->state_control.unmark_use(lease); - return result; - } - template - requires std::derived_from - Field edit_state_value(Field Source_State::* field) { - return state_value(field, State_Edit); - } - template - requires std::derived_from - Field ready_state_value(Field Source_State::* field) { - return state_value(field, State_Ready); - } - template - requires std::derived_from - Field render_state_value(Field Source_State::* field) { - return state_value(field, State_Render); - } - template - requires std::derived_from - void set_state_value(Field Source_State::* field, Field value) { - auto record = this->state_control.acquire_role(State_Edit); - if (record.result != Triple_Buffer_Result::Success) - return; - auto lease = record.lease; - auto state = state_data_by_index(lease.index); - Render_Edit_Guard guard(this, lease); - state->*field = std::move(value); - } - void sync_state_pipeline() override { - auto edit_record = this->state_control.try_acquire_role(State_Edit); - if (edit_record.result != Triple_Buffer_Result::Success) - return; - auto ready_record = this->state_control.try_acquire_role(State_Ready); - if (ready_record.result != Triple_Buffer_Result::Success) { - this->state_control.unmark_use(edit_record.lease); - return; - } - Triple_Buffer_Lease edit_lease = edit_record.lease; - Triple_Buffer_Lease ready_lease = ready_record.lease; - auto ready = reinterpret_cast(this->state_by_index(ready_lease.index)); - auto edit = reinterpret_cast(this->state_by_index(edit_lease.index)); - if (buffer_version_newer(edit->version, ready->version)) - *ready = *edit; - this->state_control.unmark_use(ready_lease); - this->state_control.unmark_use(edit_lease); - this->state_control.try_swap_role(State_Ready, State_Render, [this](const Triple_Buffer_View& view) { - auto ready = reinterpret_cast(this->state_by_index(view[State_Ready])); - auto render = reinterpret_cast(this->state_by_index(view[State_Render])); - return buffer_version_newer(ready->version, render->version); - }); - this->input_control.try_swap_role(Input_Edit, Input_Ready, [this](const Triple_Buffer_View& view) { - const Input_Buffer_Slot& edit = this->input_slot_by_index(view[Input_Edit]); - const Input_Buffer_Slot& ready = this->input_slot_by_index(view[Input_Ready]); - return edit.pending && buffer_version_newer(edit.version, ready.version); - }); - this->input_control.try_swap_role(Input_Ready, Input_Render, [this](const Triple_Buffer_View& view) { - const Input_Buffer_Slot& ready = this->input_slot_by_index(view[Input_Ready]); - const Input_Buffer_Slot& render = this->input_slot_by_index(view[Input_Render]); - return ready.pending && buffer_version_newer(ready.version, render.version); - }); - } -}; -template -using Typed_Render_Data = Typed_Render_Data_Base; -template -struct Renderable_Binding { - static void init(Owner* owner, const char* name) { - owner->d_owner = make_render_object(); - owner->d_ptr = static_cast(owner->d_owner.template get()); - owner->set_object_name(name); - } - static Data* d(Owner* owner) { - return reinterpret_cast(owner->d_ptr); - } - static Render_Object_Owner create_render_data() { - return make_render_object(); - } - static Render_Object_Owner create_render_state() { - return make_render_object(); - } - static Render_Object_Owner create_input_data() { - if constexpr (std::is_same_v) - return {}; - return make_render_object(); - } -}; -#define RENDERIVE_DEFINE_RENDERABLE_BINDING(Owner, Data, State, Input, Name) \ - using Owner##_Binding = ::renderive::Renderable_Binding; \ - Owner::Owner() { \ - Owner##_Binding::init(this, Name); \ - } \ - Data* Owner::d() { \ - return Owner##_Binding::d(this); \ - } \ - ::renderive::Render_Object_Owner Owner::create_render_data() { \ - return Owner##_Binding::create_render_data(); \ - } \ - ::renderive::Render_Object_Owner Owner::create_render_state() { \ - return Owner##_Binding::create_render_state(); \ - } \ - ::renderive::Render_Object_Owner Owner::create_input_data() { \ - return Owner##_Binding::create_input_data(); \ - } -template - requires std::derived_from -struct Render_State_Lease : Immovable { - explicit Render_State_Lease(Data* data) : data(data) { - auto record = data->state_control.acquire_role(State_Edit); - if (record.result == Triple_Buffer_Result::Success) { - lease = record.lease; - state = reinterpret_cast(data->state_by_index(lease.index)); - } - } - ~Render_State_Lease() { - if (lease) - data->unmark_state(lease); - } - State* operator->() { - return state; - } - State& operator*() { - return *state; - } - explicit operator bool() const { - return state && lease; - } - Data* data{}; - Triple_Buffer_Lease lease{}; - State* state{}; -}; -template - requires std::derived_from -struct Render_Edit_Lease : Immovable { - explicit Render_Edit_Lease(Data* data) : data(data) { - auto record = data->state_control.acquire_role(State_Edit); - if (record.result == Triple_Buffer_Result::Success) { - lease = record.lease; - state = reinterpret_cast(data->state_by_index(lease.index)); - } - } - ~Render_Edit_Lease() { - if (!lease) - return; - ++data->state_by_index(lease.index)->version; - auto q = data->q_ptr; - data->unmark_state(lease); - if (q) - q->mark_render_dirty(); - } - State* operator->() { - return state; - } - State& operator*() { - return *state; - } - explicit operator bool() const { - return state && lease; - } - Data* data{}; - Triple_Buffer_Lease lease{}; - State* state{}; -}; -template - requires std::derived_from -struct Render_Input_Lease : Immovable { - explicit Render_Input_Lease(Data* data, bool notify_render = true) : data(data), notify_render(notify_render) { - auto record = data->input_control.acquire_role(Input_Edit); - if (record.result == Triple_Buffer_Result::Success) { - lease = record.lease; - input = reinterpret_cast(data->input_data_by_index(lease.index)); - if (!input) { - data->input_control.unmark_use(lease); - lease = {}; - } - } - } - ~Render_Input_Lease() { - if (!lease) - return; - data->mark_input_dirty(lease, false); - data->input_control.unmark_use(lease); - if (notify_render && data->q_ptr) - data->q_ptr->mark_render_dirty(); - } - Input* operator->() { - return input; - } - Input& operator*() { - return *input; - } - explicit operator bool() const { - return input && lease; - } - Data* data{}; - bool notify_render{}; - Triple_Buffer_Lease lease{}; - Input* input{}; -}; -} // namespace renderive + diff --git a/Core/plot/Plot_Core.cpp b/Core/plot/Plot_Core.cpp index 73e2a43..feed2b5 100644 --- a/Core/plot/Plot_Core.cpp +++ b/Core/plot/Plot_Core.cpp @@ -1,9 +1,12 @@ #include "Plot_Core.h" +#include "Plot_Core_Access.h" #include "../architecture/Renderable.h" +#include "../architecture/Plot_Render_Context.h" #include "../architecture/Render_Time.h" #include "../architecture/Update_Completion.h" #include "../base/global.h" #include "../render/Canvas.h" +#include "../render/Render_Frame_Snapshot.h" #include #include #include @@ -62,6 +65,7 @@ public: frame.pixel_width = static_cast(size.width); frame.pixel_height = static_cast(size.height); frame.viewport_version = context->viewport_version.load(std::memory_order_acquire); + frame.dirty_reason_mask = pending_dirty_reason_mask.exchange(0, std::memory_order_acq_rel); frame.render_queue_wait_ns = submit_time > frame.render_role_enter_ns ? submit_time - frame.render_role_enter_ns : 0; frame.render_begin_ns = submit_time; auto job = std::make_shared(); @@ -71,7 +75,7 @@ public: job->buffer = buffer; job->lifecycle = frame_record; job->worker_stat = std::make_shared(); - job->render_snapshot.context = context; + job->render_snapshot.destroying_token = destroying_token; job->render_snapshot.size = size; job->render_snapshot.background_color = background_color(); job->render_snapshot.previous_frame_time_ns = previous_frame_time_ns; @@ -86,20 +90,20 @@ public: frame.frame_id, Render_Task_Kind::Frame, Task{}, - [job](tf::Taskflow& taskflow, tf::Task entry, tf::Task exit) { - auto prepare = taskflow.emplace([job]() { + [job](Render_Task_Graph& graph, Render_Task_Graph_Node entry, Render_Task_Graph_Node exit) { + auto prepare = graph.emplace(Task([job]() { job->owner->prepare_frame_job(job); - }); - auto draw = taskflow.emplace([job]() { + })); + auto draw = graph.emplace(Task([job]() { job->owner->draw_frame_job(job); - }); - auto finalize = taskflow.emplace([job]() { + })); + auto finalize = graph.emplace(Task([job]() { job->owner->finalize_frame_job(job); - }); - entry.precede(prepare); - prepare.precede(draw); - draw.precede(finalize); - finalize.precede(exit); + })); + graph.precede(entry, prepare); + graph.precede(prepare, draw); + graph.precede(draw, finalize); + graph.precede(finalize, exit); }, Task([job]() { Global::instance()->render_scheduler().post([job]() { @@ -121,7 +125,7 @@ public: return false; } static bool frame_job_valid(const std::shared_ptr& job) { - return job && job->buffer && job->lifecycle && job->render_snapshot.context && !job->render_snapshot.destroying(); + return job && job->buffer && job->lifecycle && !job->render_snapshot.destroying(); } void prepare_frame_job(const std::shared_ptr& job) { if (!frame_job_valid(job)) @@ -136,7 +140,8 @@ public: ++frame.prepared_output_count; } frame.prepare_end_ns = steady_now_ns(); - job->render_snapshot.context->first_prepare_data.store(false, std::memory_order_release); + if (job->owner && job->owner->context) + job->owner->context->first_prepare_data.store(false, std::memory_order_release); } void draw_frame_job(const std::shared_ptr& job) { if (!frame_job_valid(job) || !job->lifecycle->prepare_end_ns) @@ -161,7 +166,7 @@ public: } } void execute_frame_job(const std::shared_ptr& job) { - if (!job || !job->buffer || !job->lifecycle || !job->render_snapshot.context || job->render_snapshot.destroying()) + if (!job || !job->buffer || !job->lifecycle || job->render_snapshot.destroying()) return; prepare_frame_job(job); draw_frame_job(job); @@ -219,9 +224,9 @@ public: if (update_result.request_present) request_present(update_result.dirty_rect); } - void notify_model_dirty(std::uint64_t now_ns) { + void notify_model_dirty(Dirty_Reason reason, std::uint64_t now_ns) { if (flow) - flow->notify_model_dirty(now_ns); + flow->notify_model_dirty(reason, now_ns); } void notify_viewport_changed(Size viewport, std::uint64_t now_ns) { if (flow) @@ -385,11 +390,13 @@ public: } std::unique_ptr flow; std::shared_ptr context = std::make_shared(); + std::shared_ptr destroying_token = std::make_shared(false); std::shared_ptr root; std::vector> hover_targets; std::weak_ptr sink; std::atomic latest_input_time_ns{0}; std::atomic pending_input_count{0}; + std::atomic pending_dirty_reason_mask{0}; std::uint64_t flow_deadline_generation{}; std::uint64_t scheduled_flow_deadline_ns{}; std::uint64_t next_frame_id{}; @@ -413,6 +420,7 @@ Plot_Core::Plot_Core(std::unique_ptr flow) } Plot_Core::~Plot_Core() { d->context->destroying.store(true, std::memory_order_release); + d->destroying_token->store(true, std::memory_order_release); d->generation.fetch_add(1, std::memory_order_acq_rel); if (d->flow) d->flow->shutdown(); @@ -429,9 +437,6 @@ void Plot_Core::init() { } d->context->first_resize.store(true, std::memory_order_release); } -std::shared_ptr Plot_Core::render_context() const { - return d->context; -} std::shared_ptr Plot_Core::root_renderable() const { return d->root; } @@ -455,7 +460,7 @@ void Plot_Core::remove_renderable(const std::shared_ptr& renderable) } void Plot_Core::set_background_color(Color color) { d->context->background_rgba.store(premultiply(color), std::memory_order_release); - notify_model_dirty(); + notify_model_dirty(Dirty_Reason::Config); } Color Plot_Core::background_color() const { return d->background_color(); @@ -472,6 +477,7 @@ void Plot_Core::set_viewport_size(Size size) { if (!size.empty()) d->context->first_resize.store(false, std::memory_order_release); if (previous != size) { + d->pending_dirty_reason_mask.fetch_or(dirty_reason_bit(Dirty_Reason::Viewport), std::memory_order_acq_rel); d->post_scheduler_action([data = d, size]() { data->notify_viewport_changed(size, steady_now_ns()); }); @@ -486,23 +492,24 @@ Size Plot_Core::viewport_size() const { void Plot_Core::dispatch_event(const Event& event) { d->dispatch_event(event); } -void Plot_Core::notify_model_dirty() { +void Plot_Core::notify_model_dirty(Dirty_Reason reason) { if (d->destroying()) return; std::uint64_t now_ns = steady_now_ns(); if (d->flow) d->flow->advance_edit_version(); + d->pending_dirty_reason_mask.fetch_or(dirty_reason_bit(reason), std::memory_order_acq_rel); d->latest_input_time_ns.store(now_ns, std::memory_order_release); d->pending_input_count.fetch_add(1, std::memory_order_relaxed); - d->post_scheduler_action([data = d, now_ns]() { - data->notify_model_dirty(now_ns); + d->post_scheduler_action([data = d, reason, now_ns]() { + data->notify_model_dirty(reason, now_ns); }); } void Plot_Core::request_explicit_render() { if (d->destroying()) return; d->post_scheduler_action([data = d]() { - data->notify_model_dirty(steady_now_ns()); + data->notify_model_dirty(Dirty_Reason::Core_Data, steady_now_ns()); }); } void Plot_Core::activate_view() { @@ -523,6 +530,9 @@ 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; +} Present_Surface_Lease Plot_Core::begin_present() { Present_Surface_Lease result; if (!d->root || !d->flow) @@ -550,4 +560,12 @@ void Plot_Core::end_present(Present_Surface_Lease&& frame, std::uint64_t paint_b data->notify_paint_finished(); }); } + +std::shared_ptr Plot_Core_Context_Access::render_context(Plot_Core& plot) { + return plot.d ? plot.d->context : std::shared_ptr{}; +} + +std::shared_ptr Plot_Core_Context_Access::render_context(const Plot_Core& plot) { + return plot.d ? plot.d->context : std::shared_ptr{}; +} } // namespace renderive diff --git a/Core/plot/Plot_Core.h b/Core/plot/Plot_Core.h index ac78f4d..d9b2d19 100644 --- a/Core/plot/Plot_Core.h +++ b/Core/plot/Plot_Core.h @@ -1,6 +1,5 @@ #pragma once -#include "../architecture/Plot_Render_Context.h" -#include "../architecture/Renderable.h" +#include "../architecture/Render_Config.h" #include "../base/Color.h" #include "../base/Geometry.h" #include "../event/Event.h" @@ -13,6 +12,8 @@ namespace renderive { class Plot_Core_Private; +struct Plot_Core_Context_Access; +class Renderable; class Plot_Core { public: @@ -21,7 +22,6 @@ public: void init(); - [[nodiscard]] std::shared_ptr render_context() const; [[nodiscard]] std::shared_ptr root_renderable() const; [[nodiscard]] std::shared_ptr create_renderable_node( const std::shared_ptr& parent, std::string object_name = {}); @@ -35,7 +35,7 @@ public: [[nodiscard]] Size viewport_size() const; void dispatch_event(const Event& event); - void notify_model_dirty(); + void notify_model_dirty(Dirty_Reason reason = Dirty_Reason::Core_Data); void request_explicit_render(); void activate_view(); @@ -43,11 +43,13 @@ public: [[nodiscard]] bool view_active() const; void set_presentation_sink(std::weak_ptr sink); + [[nodiscard]] Renderable_Runtime_Strategy renderable_runtime_strategy() 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); private: + friend struct Plot_Core_Context_Access; std::shared_ptr d; }; diff --git a/Core/plot/Plot_Core_Access.h b/Core/plot/Plot_Core_Access.h new file mode 100644 index 0000000..7a7cdd9 --- /dev/null +++ b/Core/plot/Plot_Core_Access.h @@ -0,0 +1,15 @@ +#pragma once +#include "../architecture/global.h" +#include + +namespace renderive { + +class Plot_Core; +struct Plot_Render_Context; + +struct LIB_DECL Plot_Core_Context_Access { + [[nodiscard]] static std::shared_ptr render_context(Plot_Core& plot); + [[nodiscard]] static std::shared_ptr render_context(const Plot_Core& plot); +}; + +} // namespace renderive diff --git a/Core/plottable/Afterglow_p.h b/Core/plottable/Afterglow_p.h index d11015a..ec76b13 100644 --- a/Core/plottable/Afterglow_p.h +++ b/Core/plottable/Afterglow_p.h @@ -120,7 +120,8 @@ void Afterglow::Builder_T::set(Afterglow* ret) { ret->init(parent); std::shared_ptr owner = ret->shared_from_this(); Afterglow_Private* pd = ret->d(); - Afterglow_Render_State* sc = reinterpret_cast(pd->edit_state()); + Render_Edit_Lease edit(pd); + Afterglow_Render_State* sc = edit.operator->(); PROP_RT(std::weak_ptr, frequency_axis) PROP_RT(std::weak_ptr, power_axis) PROP_RT(Range, frequency_range) diff --git a/Core/plottable/Constellation_Diagram.cpp b/Core/plottable/Constellation_Diagram.cpp index 6f3e349..e6d4261 100644 --- a/Core/plottable/Constellation_Diagram.cpp +++ b/Core/plottable/Constellation_Diagram.cpp @@ -84,7 +84,8 @@ std::shared_ptr Constellation_Diagram::Builder::build() { auto ret = renderive::make_shared(); ret->init(parent); Constellation_Diagram_Private* pd = ret->d(); - Constellation_Diagram_State* sc = reinterpret_cast(pd->edit_state()); + Render_Edit_Lease edit(pd); + Constellation_Diagram_State* sc = edit.operator->(); PROP_R(std::weak_ptr, i_axis) PROP_R(std::weak_ptr, q_axis) PROP_R(Range, i_range) diff --git a/Core/plottable/Constellation_Diagram_p.h b/Core/plottable/Constellation_Diagram_p.h index 703f7d0..30dddac 100644 --- a/Core/plottable/Constellation_Diagram_p.h +++ b/Core/plottable/Constellation_Diagram_p.h @@ -5,7 +5,7 @@ #include #include #include -#include "../flow/low_latency/Render_Lease.h" +#include "../architecture/Render_Lease.h" #include "../Axis/Axis_p.h" #include "../architecture/Bounded_Input_Buffer.h" #include "../base/Frame_Memory.h" diff --git a/Core/plottable/Frequency_Trace.cpp b/Core/plottable/Frequency_Trace.cpp index 5ee4674..ed0c586 100644 --- a/Core/plottable/Frequency_Trace.cpp +++ b/Core/plottable/Frequency_Trace.cpp @@ -30,7 +30,8 @@ std::shared_ptr Frequency_Trace::Builder::build() { auto ret = renderive::make_shared(); ret->init(parent); Frequency_Trace_Private* pd = ret->d(); - Frequency_Trace_Render_State* sc = reinterpret_cast(pd->edit_state()); + Render_Edit_Lease edit(pd); + Frequency_Trace_Render_State* sc = edit.operator->(); PROP_R(std::weak_ptr, time_axis) PROP_R(std::weak_ptr, value_axis) sc->pen = Pen{.color = color, .width = 1.0}; diff --git a/Core/plottable/Performance_Overlay.cpp b/Core/plottable/Performance_Overlay.cpp index 760a844..09fa720 100644 --- a/Core/plottable/Performance_Overlay.cpp +++ b/Core/plottable/Performance_Overlay.cpp @@ -1,4 +1,6 @@ #include "Performance_Overlay_p.h" +#include "../architecture/Plot_Render_Context.h" +#include "../plot/Plot_Core_Access.h" #include #include #include @@ -257,7 +259,7 @@ Performance_Overlay_Private* Performance_Overlay::d() { return private_data.get(); } static std::shared_ptr attach_performance_overlay_impl(Plot_Core& plot, const Performance_Overlay_Options* options) { - auto ctx = plot.render_context(); + auto ctx = Plot_Core_Context_Access::render_context(plot); if (!ctx) return {}; auto shower = performance_overlay_owner(ctx); @@ -280,10 +282,10 @@ std::shared_ptr attach_performance_overlay(Plot_Core& plot, return attach_performance_overlay_impl(plot, &options); } std::shared_ptr performance_overlay(Plot_Core& plot) { - return performance_overlay_owner(plot.render_context()); + return performance_overlay_owner(Plot_Core_Context_Access::render_context(plot)); } void detach_performance_overlay(Plot_Core& plot) { - auto ctx = plot.render_context(); + auto ctx = Plot_Core_Context_Access::render_context(plot); if (!ctx) return; auto shower = performance_overlay_owner(ctx); @@ -292,12 +294,12 @@ void detach_performance_overlay(Plot_Core& plot) { shower->set_enabled(false); } bool performance_overlay_enabled(const Plot_Core& plot) { - auto shower = performance_overlay_owner(plot.render_context()); + auto shower = performance_overlay_owner(Plot_Core_Context_Access::render_context(plot)); return shower && shower->enabled(); } void set_performance_overlay_enabled(Plot_Core& plot, bool enabled) { if (!enabled) { - auto shower = performance_overlay_owner(plot.render_context()); + auto shower = performance_overlay_owner(Plot_Core_Context_Access::render_context(plot)); if (shower) shower->set_enabled(false); return; @@ -305,17 +307,18 @@ void set_performance_overlay_enabled(Plot_Core& plot, bool enabled) { auto shower = attach_performance_overlay(plot); if (shower) { shower->set_enabled(true); - shower->set_viewport(plot.viewport_size(), plot.render_context() ? plot.render_context()->viewport_version.load(std::memory_order_acquire) : 0); + auto ctx = Plot_Core_Context_Access::render_context(plot); + shower->set_viewport(plot.viewport_size(), ctx ? ctx->viewport_version.load(std::memory_order_acquire) : 0); } } std::shared_ptr performance_overlay_snapshot(const Plot_Core& plot) { - auto shower = performance_overlay_owner(plot.render_context()); + auto shower = performance_overlay_owner(Plot_Core_Context_Access::render_context(plot)); if (!shower || !shower->enabled()) return {}; return shower->display_snapshot(); } void set_performance_overlay_update_callback(Plot_Core& plot, std::function callback) { - auto ctx = plot.render_context(); + auto ctx = Plot_Core_Context_Access::render_context(plot); if (!ctx) return; { @@ -327,26 +330,26 @@ void set_performance_overlay_update_callback(Plot_Core& plot, std::functionset_update_callback(performance_update_callback(ctx)); } void notify_performance_overlay_viewport_changed(Plot_Core& plot) { - auto shower = performance_overlay_owner(plot.render_context()); + auto shower = performance_overlay_owner(Plot_Core_Context_Access::render_context(plot)); if (!shower || !shower->enabled()) return; - auto ctx = plot.render_context(); + auto ctx = Plot_Core_Context_Access::render_context(plot); shower->set_viewport(plot.viewport_size(), ctx ? ctx->viewport_version.load(std::memory_order_acquire) : 0); } bool performance_overlay_handle_wheel(Plot_Core& plot, PointF position, double angle_delta_y, double pixel_delta_y) { - auto shower = performance_overlay_owner(plot.render_context()); + auto shower = performance_overlay_owner(Plot_Core_Context_Access::render_context(plot)); return shower && shower->enabled() && shower->handle_wheel(position, angle_delta_y, pixel_delta_y); } bool performance_overlay_handle_pointer_press(Plot_Core& plot, PointF position) { - auto shower = performance_overlay_owner(plot.render_context()); + auto shower = performance_overlay_owner(Plot_Core_Context_Access::render_context(plot)); return shower && shower->enabled() && shower->handle_pointer_press(position); } bool performance_overlay_handle_pointer_move(Plot_Core& plot, PointF position) { - auto shower = performance_overlay_owner(plot.render_context()); + auto shower = performance_overlay_owner(Plot_Core_Context_Access::render_context(plot)); return shower && shower->enabled() && shower->handle_pointer_move(position); } bool performance_overlay_handle_pointer_release(Plot_Core& plot, PointF position) { - auto shower = performance_overlay_owner(plot.render_context()); + auto shower = performance_overlay_owner(Plot_Core_Context_Access::render_context(plot)); return shower && shower->enabled() && shower->handle_pointer_release(position); } void Performance_Overlay::set_enabled(bool enabled) { diff --git a/Core/plottable/Performance_Overlay.h b/Core/plottable/Performance_Overlay.h index 25caa4a..77f6518 100644 --- a/Core/plottable/Performance_Overlay.h +++ b/Core/plottable/Performance_Overlay.h @@ -2,7 +2,7 @@ #include #include #include -#include "../architecture/Plot_Render_Context.h" +#include "../architecture/Frame_Scheduler.h" #include "../base/Color.h" #include "../base/Geometry.h" #include "../base/Text.h" diff --git a/Core/plottable/Performance_Overlay_p.h b/Core/plottable/Performance_Overlay_p.h index 6444404..d1dae71 100644 --- a/Core/plottable/Performance_Overlay_p.h +++ b/Core/plottable/Performance_Overlay_p.h @@ -15,7 +15,6 @@ #include #include #include "../architecture/Frame_Scheduler.h" -#include "../architecture/Plot_Render_Context.h" #include "../architecture/Render_Data.h" #include "../base/Color.h" #include "../base/Geometry.h" @@ -31,6 +30,8 @@ void write_frame_performance_log(std::shared_ptr f struct Performance_Overlay_Render_State : Render_State, Performance_Overlay_Options {}; struct Performance_Overlay_Input_Data : Input_Data {}; struct Renderable_Cache_Aggregate_Stat { + std::uint64_t node_id{}; + std::uint64_t parent_node_id{}; std::string object_name; std::string parent_object_name; int tree_depth{}; @@ -51,6 +52,8 @@ struct Renderable_Cache_Aggregate_Stat { Psc::Value_Statistics subtree_compose_ms; }; struct Renderable_Aggregate_Stat { + std::uint64_t node_id{}; + std::uint64_t parent_node_id{}; std::string object_name; std::string parent_object_name; int tree_depth{}; @@ -171,7 +174,7 @@ struct Performance_Aggregate { cache_compose_ratio.update(static_cast(frame.renderable_cache_compose_time_ns) * 100.0 / static_cast(frame_render_time_ns)); } for (const auto& stat : frame.renderable_stats) { - Renderable_Aggregate_Stat& aggregate_stat = renderable_stat(stat.object_name, stat.parent_object_name, stat.tree_depth); + Renderable_Aggregate_Stat& aggregate_stat = renderable_stat(stat.node_id, stat.parent_node_id, stat.object_name, stat.parent_object_name, stat.tree_depth); aggregate_stat.cache_enabled = stat.cache_enabled; aggregate_stat.render_count += stat.render_count; aggregate_stat.self_draw_count += stat.self_draw_count; @@ -189,7 +192,7 @@ struct Performance_Aggregate { } } for (const auto& stat : frame.renderable_cache_stats) { - Renderable_Cache_Aggregate_Stat& aggregate_stat = cache_stat(stat.object_name, stat.parent_object_name, stat.tree_depth); + Renderable_Cache_Aggregate_Stat& aggregate_stat = cache_stat(stat.node_id, stat.parent_node_id, stat.object_name, stat.parent_object_name, stat.tree_depth); aggregate_stat.hit_count += stat.hit_count; aggregate_stat.miss_count += stat.miss_count; aggregate_stat.rebuild_count += stat.rebuild_count; @@ -224,12 +227,14 @@ struct Performance_Aggregate { } } private: - Renderable_Aggregate_Stat& renderable_stat(const std::string& object_name, const std::string& parent_object_name, int tree_depth) { + Renderable_Aggregate_Stat& renderable_stat(std::uint64_t node_id, std::uint64_t parent_node_id, const std::string& object_name, const std::string& parent_object_name, int tree_depth) { for (auto& stat : renderable_stats) { - if (stat.object_name == object_name && stat.parent_object_name == parent_object_name && stat.tree_depth == tree_depth) + if (stat.node_id == node_id && stat.parent_node_id == parent_node_id && stat.tree_depth == tree_depth) return stat; } Renderable_Aggregate_Stat stat; + stat.node_id = node_id; + stat.parent_node_id = parent_node_id; stat.object_name = object_name; stat.parent_object_name = parent_object_name; stat.tree_depth = tree_depth; @@ -241,17 +246,19 @@ private: std::uint64_t subtree_compose_time_ns = renderable_cache_subtree_compose_time(frame_stats, stat); if (!subtree_compose_time_ns) continue; - Renderable_Cache_Aggregate_Stat& aggregate_stat = cache_stat(stat.object_name, stat.parent_object_name, stat.tree_depth); + Renderable_Cache_Aggregate_Stat& aggregate_stat = cache_stat(stat.node_id, stat.parent_node_id, stat.object_name, stat.parent_object_name, stat.tree_depth); aggregate_stat.subtree_compose_time_ns += subtree_compose_time_ns; aggregate_stat.subtree_compose_ms.update(ns_to_ms(subtree_compose_time_ns)); } } - Renderable_Cache_Aggregate_Stat& cache_stat(const std::string& object_name, const std::string& parent_object_name, int tree_depth) { + Renderable_Cache_Aggregate_Stat& cache_stat(std::uint64_t node_id, std::uint64_t parent_node_id, const std::string& object_name, const std::string& parent_object_name, int tree_depth) { for (auto& stat : renderable_cache_stats) { - if (stat.object_name == object_name && stat.parent_object_name == parent_object_name) + if (stat.node_id == node_id && stat.parent_node_id == parent_node_id) return stat; } Renderable_Cache_Aggregate_Stat stat; + stat.node_id = node_id; + stat.parent_node_id = parent_node_id; stat.object_name = object_name; stat.parent_object_name = parent_object_name; stat.tree_depth = tree_depth; @@ -626,6 +633,45 @@ struct Performance_Layout_Builder { } return "unknown"; } + static 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"; + } + static 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"; + } + static std::string dirty_reason_text(std::uint32_t mask) { + std::string result; + auto append = [&result](const char* text) { + if (!result.empty()) + result += "|"; + result += text; + }; + if (mask & dirty_reason_bit(Dirty_Reason::Core_Data)) + append("core"); + if (mask & dirty_reason_bit(Dirty_Reason::Config)) + append("config"); + if (mask & dirty_reason_bit(Dirty_Reason::Viewport)) + append("viewport"); + return result.empty() ? "none" : result; + } static std::string outcome_text(Frame_Outcome outcome) { switch (outcome) { case Frame_Outcome::Presented: @@ -711,10 +757,10 @@ struct Performance_Layout_Builder { lines.push_back(name_indent + " self " + duration_line(key + "_self_draw", "time", stat.self_draw_ms)); lines.push_back(name_indent + " self% " + percent_stat_line(key + "_self_ratio", "ratio", stat.self_draw_ratio)); } - void append_renderable_tree_lines(const std::string& parent_object_name, int tree_depth, std::vector& emitted) { + void append_renderable_tree_lines(std::uint64_t parent_node_id, int tree_depth, std::vector& emitted) { std::vector children; for (const auto& stat : aggregate.renderable_stats) { - if (stat.parent_object_name == parent_object_name && stat.tree_depth == tree_depth && should_show_renderable(stat)) + if (stat.parent_node_id == parent_node_id && stat.tree_depth == tree_depth && should_show_renderable(stat)) children.push_back(&stat); } std::stable_sort(children.begin(), children.end(), [](const auto* a, const auto* b) { @@ -725,12 +771,12 @@ struct Performance_Layout_Builder { continue; emitted.push_back(stat); append_renderable_node_lines(*stat); - append_renderable_tree_lines(stat->object_name, stat->tree_depth + 1, emitted); + append_renderable_tree_lines(stat->node_id, stat->tree_depth + 1, emitted); } } void append_renderable_tree_lines() { std::vector emitted; - append_renderable_tree_lines({}, 0, emitted); + append_renderable_tree_lines(0, 0, emitted); for (const auto& stat : aggregate.renderable_stats) { if (!should_show_renderable(stat) || std::find(emitted.begin(), emitted.end(), &stat) != emitted.end()) continue; @@ -738,10 +784,10 @@ struct Performance_Layout_Builder { append_renderable_node_lines(stat); } } - void append_cache_tree_lines(const std::string& parent_object_name, int tree_depth, std::vector& emitted) { + void append_cache_tree_lines(std::uint64_t parent_node_id, int tree_depth, std::vector& emitted) { std::vector children; for (const auto& stat : aggregate.renderable_cache_stats) { - if (stat.parent_object_name == parent_object_name && stat.tree_depth == tree_depth && should_show_cache(stat)) + if (stat.parent_node_id == parent_node_id && stat.tree_depth == tree_depth && should_show_cache(stat)) children.push_back(&stat); } std::stable_sort(children.begin(), children.end(), [](const auto* a, const auto* b) { @@ -752,12 +798,12 @@ struct Performance_Layout_Builder { continue; emitted.push_back(stat); append_cache_node_lines(*stat); - append_cache_tree_lines(stat->object_name, stat->tree_depth + 1, emitted); + append_cache_tree_lines(stat->node_id, stat->tree_depth + 1, emitted); } } void append_cache_tree_lines() { std::vector emitted; - append_cache_tree_lines({}, 0, emitted); + append_cache_tree_lines(0, 0, emitted); for (const auto& stat : aggregate.renderable_cache_stats) { if (!should_show_cache(stat) || std::find(emitted.begin(), emitted.end(), &stat) != emitted.end()) continue; @@ -778,12 +824,15 @@ struct Performance_Layout_Builder { std::uint64_t latest_render_time = frame_render_time_ns(frame); 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("frame:" + count_text("frame_id", frame.frame_id) + " input_ver:" + count_text("input_version", frame.input_version) + " outcome:" + marked_value(Performance_Metric_Id::Outcome, outcome_text(frame.outcome)) + " limit:" + limit_source_text(refresh.source)); - lines.push_back("period render:" + period_text("render_period", refresh.render_period_ns) + " paint:" + period_text("paint_period", refresh.paint_period_ns) + " user:" + period_text("user_period", refresh.user_period_ns) + " target:" + period_text("target_period", refresh.target_period_ns)); - lines.push_back("queue render:" + ns_text("render_queue_wait", frame.render_queue_wait_ns) + "ms ready:" + ns_text("ready_queue_wait", frame.ready_queue_wait_ns) + "ms present:" + ns_text("present_queue_wait", frame.present_queue_wait_ns) + "ms return:" + ns_text("paint_return_wait", frame.paint_return_wait_ns) + "ms"); - lines.push_back("taskflow workers:" + count_text("taskflow_workers", static_cast(executor.worker_count)) + " active:" + count_text("taskflow_active", static_cast(executor.active_workers)) + " queued_frames:" + count_text("taskflow_queued_frames", static_cast(executor.queued_frame_jobs)) + " active_frames:" + count_text("taskflow_active_frames", static_cast(executor.active_frame_jobs))); - lines.push_back("taskflow tasks submitted:" + count_text("taskflow_submitted", executor.submitted_jobs) + " started:" + count_text("taskflow_started", executor.started_task_nodes) + " completed:" + count_text("taskflow_completed", executor.completed_task_nodes) + " rejected_frames:" + count_text("taskflow_rejected", executor.rejected_frame_jobs)); - lines.push_back("taskflow wait avg:" + ns_text("taskflow_queue_wait_avg", executor.average_queue_wait_ns) + "ms p95:" + ns_text("taskflow_queue_wait_p95", executor.max_queue_wait_ns) + "ms run avg:" + ns_text("taskflow_run_avg", executor.average_task_duration_ns) + "ms p95:" + ns_text("taskflow_run_p95", executor.max_task_duration_ns) + "ms"); + 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] 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"); + lines.push_back("[low_latency] counts presented:" + count_text("presented_feedback", refresh.presented_frame_count) + " superseded:" + count_text("superseded_feedback", refresh.superseded_frame_count) + " repeated:" + count_text("repeated_feedback", refresh.repeated_frame_count) + " underrun:" + count_text("underrun_feedback", refresh.underrun_count)); + lines.push_back("[executor] taskflow workers:" + count_text("taskflow_workers", static_cast(executor.worker_count)) + " active:" + count_text("taskflow_active", static_cast(executor.active_workers)) + " queued_frames:" + count_text("taskflow_queued_frames", static_cast(executor.queued_frame_jobs)) + " active_frames:" + count_text("taskflow_active_frames", static_cast(executor.active_frame_jobs))); + lines.push_back("[executor] taskflow tasks submitted:" + count_text("taskflow_submitted", executor.submitted_jobs) + " started:" + count_text("taskflow_started", executor.started_task_nodes) + " completed:" + count_text("taskflow_completed", executor.completed_task_nodes) + " rejected_frames:" + count_text("taskflow_rejected", executor.rejected_frame_jobs)); + lines.push_back("[executor] taskflow wait avg:" + ns_text("taskflow_queue_wait_avg", executor.average_queue_wait_ns) + "ms p95:" + ns_text("taskflow_queue_wait_p95", executor.max_queue_wait_ns) + "ms run avg:" + ns_text("taskflow_run_avg", executor.average_task_duration_ns) + "ms p95:" + ns_text("taskflow_run_p95", executor.max_task_duration_ns) + "ms"); lines.push_back("frame worker tasks:" + count_text("frame_worker_tasks", worker.task_count) + " peak_parallel:" + count_text("frame_worker_peak", worker.peak_parallelism) + " wait_max:" + ns_text("frame_worker_wait_max", worker.queue_wait_max_ns) + "ms run_total:" + ns_text("frame_worker_run_total", worker.worker_run_total_ns) + "ms"); lines.push_back(duration_line("request_wait", "lifecycle request_wait", aggregate.request_wait_ms)); lines.push_back(duration_line("render_queue_wait", "lifecycle queue_wait", aggregate.render_queue_wait_ms)); diff --git a/Core/plottable/Selection_Rectangle_Overlay.cpp b/Core/plottable/Selection_Rectangle_Overlay.cpp index 90b406a..67e159e 100644 --- a/Core/plottable/Selection_Rectangle_Overlay.cpp +++ b/Core/plottable/Selection_Rectangle_Overlay.cpp @@ -17,7 +17,8 @@ std::shared_ptr Selection_Rectangle_Overlay::Builde auto ret = renderive::make_shared(); ret->init(parent); Selection_Rectangle_Overlay_Private* pd = ret->d(); - Selection_Rectangle_Overlay_Render_State* sc = reinterpret_cast(pd->edit_state()); + Render_Edit_Lease edit(pd); + Selection_Rectangle_Overlay_Render_State* sc = edit.operator->(); sc->horizontal_axis = domain_axis; sc->vertical_axis = value_axis; sc->label_font = font; diff --git a/Core/plottable/Spectrum.cpp b/Core/plottable/Spectrum.cpp index bcece88..887bfb8 100644 --- a/Core/plottable/Spectrum.cpp +++ b/Core/plottable/Spectrum.cpp @@ -174,7 +174,8 @@ std::shared_ptr Spectrum::Builder::build() { auto ret = renderive::make_shared(); ret->init(parent); Spectrum_Private* pd = ret->d(); - Spectrum_Render_State* sc = reinterpret_cast(pd->edit_state()); + Render_Edit_Lease edit(pd); + Spectrum_Render_State* sc = edit.operator->(); PROP_R(std::weak_ptr, frequency_axis) PROP_R(std::weak_ptr, power_axis); PROP_R(int, frequency_point_size); @@ -500,7 +501,7 @@ Update_Ticket Spectrum::submit_data(std::span line_data) { { Render_Input_Lease input(pd); input->push(line_data, use_max_line, use_min_line); - pd->set_input_completion(input.lease, update_state); + input.set_completion(update_state); } update_state->complete_until(Update_Stage::Input_Released); return Update_Ticket(update_state); @@ -528,7 +529,7 @@ Update_Ticket Spectrum::submit_data(std::pmr::vector&& line_data) { { Render_Input_Lease input(pd); input->push(std::move(line_data), use_max_line, use_min_line); - pd->set_input_completion(input.lease, update_state); + input.set_completion(update_state); } update_state->complete_until(Update_Stage::Input_Released); return Update_Ticket(update_state); diff --git a/Core/plottable/Sweep_Spectrum.cpp b/Core/plottable/Sweep_Spectrum.cpp index 5e88a86..94b649c 100644 --- a/Core/plottable/Sweep_Spectrum.cpp +++ b/Core/plottable/Sweep_Spectrum.cpp @@ -103,7 +103,8 @@ std::shared_ptr Sweep_Spectrum::Builder::build() { auto ret = renderive::make_shared(); ret->init(parent); Sweep_Spectrum_Private* pd = ret->d(); - Sweep_Spectrum_Render_State* sc = reinterpret_cast(pd->edit_state()); + Render_Edit_Lease edit(pd); + Sweep_Spectrum_Render_State* sc = edit.operator->(); PROP_R(std::weak_ptr, frequency_axis) PROP_R(std::weak_ptr, power_axis) PROP_R(Range, frequency_range) diff --git a/Core/plottable/Waterfall.cpp b/Core/plottable/Waterfall.cpp index 855a151..8b40d0f 100644 --- a/Core/plottable/Waterfall.cpp +++ b/Core/plottable/Waterfall.cpp @@ -139,7 +139,8 @@ std::shared_ptr Waterfall::Builder::build() { auto ret = renderive::make_shared(); ret->init(parent); Waterfall_Private* pd = ret->d(); - Waterfall_Render_State* sc = reinterpret_cast(pd->edit_state()); + Render_Edit_Lease edit(pd); + Waterfall_Render_State* sc = edit.operator->(); PROP_R(std::weak_ptr, frequency_axis) PROP_R(std::weak_ptr, time_axis) PROP_R(Range, frequency_range) diff --git a/Core/plottable/Waterfall_p.h b/Core/plottable/Waterfall_p.h index 78bd848..ef9d713 100644 --- a/Core/plottable/Waterfall_p.h +++ b/Core/plottable/Waterfall_p.h @@ -10,8 +10,6 @@ #include #include #include -#include -#include #include #include #include "../Axis/Frequency_Axis_p.h" @@ -226,20 +224,8 @@ static void write_waterfall_tile(Waterfall_Image_Job& job, int tile_index) { } static void write_waterfall_tiles(Waterfall_Image_Job& job) { int tile_count = static_cast(job.tiles.size()); - if (tile_count <= 1) { - write_waterfall_tile(job, 0); - return; - } - unsigned hardware_threads = std::max(1u, std::thread::hardware_concurrency()); - unsigned worker_count = std::min(static_cast(tile_count), hardware_threads); - tf::Executor executor(worker_count); - tf::Taskflow taskflow; - for (int tile_index = 0; tile_index < tile_count; ++tile_index) { - taskflow.emplace([&job, tile_index]() { - write_waterfall_tile(job, tile_index); - }); - } - executor.run(taskflow).wait(); + for (int tile_index = 0; tile_index < tile_count; ++tile_index) + write_waterfall_tile(job, tile_index); } static Waterfall_Image_Snapshot build_waterfall_image_snapshot(const std::shared_ptr& request_ptr) { Waterfall_Image_Request& request = *request_ptr; diff --git a/Core/primitive/Band_Region.cpp b/Core/primitive/Band_Region.cpp index 4f33515..d39fb58 100644 --- a/Core/primitive/Band_Region.cpp +++ b/Core/primitive/Band_Region.cpp @@ -67,7 +67,8 @@ std::shared_ptr Band_Region::Builder::build() { auto ret = renderive::make_shared(); ret->init(parent); Band_Region_Private* pd = ret->d(); - Band_Region_Render_State* sc = reinterpret_cast(pd->edit_state()); + Render_Edit_Lease edit(pd); + Band_Region_Render_State* sc = edit.operator->(); PROP_R(std::weak_ptr, domain_axis) PROP_R(std::weak_ptr, value_axis) PROP_R(Range, range) diff --git a/Core/primitive/Color_Bar.cpp b/Core/primitive/Color_Bar.cpp index a5d8fc1..fc7533a 100644 --- a/Core/primitive/Color_Bar.cpp +++ b/Core/primitive/Color_Bar.cpp @@ -5,7 +5,7 @@ #include #include "../base/Memory.h" #include "../base/String_Format.h" -#include "../flow/low_latency/Render_Lease.h" +#include "../architecture/Render_Lease.h" #include "../render/Canvas.h" #include "../render/Image.h" #include "../render/Render_Frame_Snapshot.h" @@ -147,7 +147,8 @@ std::shared_ptr Color_Bar::Builder::build() { auto ret = renderive::make_shared(); ret->init(parent); Color_Bar_Private* pd = ret->d(); - Color_Bar_Render_State* sc = reinterpret_cast(pd->edit_state()); + Render_Edit_Lease edit(pd); + Color_Bar_Render_State* sc = edit.operator->(); sc->color_scale = color_scale.clone(); PROP_R(std::string, name) PROP_R(std::string, unit) diff --git a/Core/primitive/Curve.cpp b/Core/primitive/Curve.cpp index f77be26..938eabb 100644 --- a/Core/primitive/Curve.cpp +++ b/Core/primitive/Curve.cpp @@ -460,7 +460,8 @@ std::shared_ptr Curve::Builder::build() { auto ret = renderive::make_shared(); ret->init(parent); Curve_Private* pd = ret->d(); - Curve_Render_State* sc = reinterpret_cast(pd->edit_state()); + Render_Edit_Lease edit(pd); + Curve_Render_State* sc = edit.operator->(); PROP_R(std::weak_ptr, domain_axis) PROP_R(std::weak_ptr, value_axis) PROP_R(Range, data_range) diff --git a/Core/primitive/Fill_Area.cpp b/Core/primitive/Fill_Area.cpp index 40c422b..3c7e369 100644 --- a/Core/primitive/Fill_Area.cpp +++ b/Core/primitive/Fill_Area.cpp @@ -103,7 +103,8 @@ std::shared_ptr Fill_Area::Builder::build() { auto ret = renderive::make_shared(); ret->init(parent); Fill_Area_Private* pd = ret->d(); - Fill_Area_Render_State* sc = reinterpret_cast(pd->edit_state()); + Render_Edit_Lease edit(pd); + Fill_Area_Render_State* sc = edit.operator->(); PROP_R(std::weak_ptr, domain_axis) PROP_R(std::weak_ptr, value_axis) PROP_R(double, baseline) diff --git a/Core/primitive/Grid.cpp b/Core/primitive/Grid.cpp index a84ea13..c2ac436 100644 --- a/Core/primitive/Grid.cpp +++ b/Core/primitive/Grid.cpp @@ -180,7 +180,8 @@ std::shared_ptr Grid::Builder::build() { auto ret = renderive::make_shared(); ret->init(parent); Grid_Private* pd = ret->d(); - Grid_Render_State* sc = reinterpret_cast(pd->edit_state()); + Render_Edit_Lease edit(pd); + Grid_Render_State* sc = edit.operator->(); PROP_R(std::weak_ptr, domain_axis) PROP_R(std::weak_ptr, value_axis) PROP_R(bool, domain_grid) diff --git a/Core/primitive/Label.cpp b/Core/primitive/Label.cpp index b394840..3055f23 100644 --- a/Core/primitive/Label.cpp +++ b/Core/primitive/Label.cpp @@ -122,7 +122,8 @@ std::shared_ptr