策略抽离

This commit is contained in:
2026-08-02 16:02:15 +08:00
parent dda25f9038
commit ffaca5b05f
55 changed files with 1431 additions and 886 deletions
+3 -2
View File
@@ -2,7 +2,7 @@
#include <algorithm>
#include <string>
#include <vector>
#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 <typename That>
void Abs_Axis::Builder_T<That>::set(Abs_Axis* ret) {
ret->init(parent);
Abs_Axis_Private* pd = ret->d();
Abs_Axis_Render_State* sc = reinterpret_cast<Abs_Axis_Render_State*>(pd->edit_state());
Render_Edit_Lease<Abs_Axis_Private, Abs_Axis_Render_State> edit(pd);
Abs_Axis_Render_State* sc = edit.operator->();
sc->x = this->x;
sc->y = this->y;
sc->orientation = this->orientation;
+2 -1
View File
@@ -74,7 +74,8 @@ template <typename That>
void Axis::Builder_T<That>::set(Axis* ret) {
Abs_Axis::Builder_T<That>::set(ret);
Axis_Private* pd = ret->d();
Axis_Render_State* sc = reinterpret_cast<Axis_Render_State*>(pd->edit_state());
Render_Edit_Lease<Axis_Private, Axis_Render_State> edit(pd);
Axis_Render_State* sc = edit.operator->();
PROP_RT(int, label_precision)
PROP_RT(double, coord_start)
PROP_RT(double, coord_length)
+2 -1
View File
@@ -66,7 +66,8 @@ std::shared_ptr<Time_Axis> Time_Axis::Builder::build() {
auto ret = renderive::make_shared<Time_Axis>();
set(ret.get());
Time_Axis_Private* pd = ret->d();
Time_Axis_Render_State* sc = reinterpret_cast<Time_Axis_Render_State*>(pd->edit_state());
Render_Edit_Lease<Time_Axis_Private, Time_Axis_Render_State> 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)
-1
View File
@@ -3,7 +3,6 @@
#include <memory_resource>
#include <string>
#include <vector>
#include "../architecture/Plot_Render_Context.h"
#include "../architecture/Timeline_Stream.h"
#include "../base/Memory.h"
#include "../base/String_Format.h"
+24 -20
View File
@@ -3,35 +3,35 @@
#include <utility>
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();
+48 -9
View File
@@ -1,17 +1,53 @@
#pragma once
#include "Render_Config.h"
#include <cstdint>
#include <memory>
#include <string>
#include <string_view>
#include <vector>
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<const Frame_Lifecycle_Record> 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<std::uint8_t>(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_Frame_Stat> renderable_stats;
std::vector<Renderable_Cache_Frame_Stats> 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<Renderable_Cache_Frame_Stats>& 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;
-21
View File
@@ -6,31 +6,12 @@
#include <memory>
#include <memory_resource>
#include <mutex>
#include <vector>
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<const Frame_Lifecycle_Record> frame) = 0;
};
struct Timeline_Stream_Snapshot;
struct Timeline_Stream_Cache_Item {
std::weak_ptr<Timeline_Stream> stream;
int time_point_size = 1;
int tick_space = 1;
bool start_coord_to_end_coord = false;
std::shared_ptr<const Timeline_Stream_Snapshot> snapshot;
};
struct Timeline_Stream_Frame_Cache {
std::vector<Timeline_Stream_Cache_Item> 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"
+20
View File
@@ -3,6 +3,15 @@
#include <cstdint>
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,
+71 -10
View File
@@ -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 <concepts>
@@ -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<Update_State> 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<Render_State_Read_Handle> handle)
@@ -183,26 +191,58 @@ struct Render_State_Edit_Guard : Move_Only {
std::unique_ptr<Render_State_Edit_Handle> handle;
};
struct Render_Input_Edit_Guard : Move_Only {
Render_Input_Edit_Guard() = default;
Render_Input_Edit_Guard(Input_Data* input, std::unique_ptr<Render_Input_Edit_Handle> 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<Render_Input_Edit_Handle> handle;
void set_completion(std::shared_ptr<Update_State> 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<std::shared_ptr<Update_State>>& 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<std::shared_ptr<Update_State>>&) 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 {}
};
+185
View File
@@ -0,0 +1,185 @@
#pragma once
#include "Renderable.h"
#include "Strategy_Render_Data.h"
#include <utility>
namespace renderive {
template <typename Base, typename Owner, Render_State_Type State, Input_Data_Type Input>
requires std::derived_from<Base, Render_Data>
struct Typed_Render_Data_Base : Base {
Owner* q() {
return reinterpret_cast<Owner*>(this->q_ptr);
}
State* render_state() override {
return reinterpret_cast<State*>(this->state(Renderable_State_Role::Render));
}
State* ready_state() override {
return reinterpret_cast<State*>(this->state(Renderable_State_Role::Ready));
}
State* edit_state() override {
return reinterpret_cast<State*>(this->state(Renderable_State_Role::Edit));
}
Input* render_input_data() override {
return reinterpret_cast<Input*>(this->input_data(Renderable_Input_Role::Render));
}
Input* edit_input_data() override {
return reinterpret_cast<Input*>(this->input_data(Renderable_Input_Role::Edit));
}
State* state_data(Renderable_State_Role role) {
return reinterpret_cast<State*>(this->state(role));
}
template <typename Field, typename Source_State>
requires std::derived_from<State, Source_State>
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<State*>(guard.state);
return state->*field;
}
template <typename Field, typename Source_State>
requires std::derived_from<State, Source_State>
Field edit_state_value(Field Source_State::* field) {
return state_value(field, Renderable_State_Role::Edit);
}
template <typename Field, typename Source_State>
requires std::derived_from<State, Source_State>
Field ready_state_value(Field Source_State::* field) {
return state_value(field, Renderable_State_Role::Ready);
}
template <typename Field, typename Source_State>
requires std::derived_from<State, Source_State>
Field render_state_value(Field Source_State::* field) {
return state_value(field, Renderable_State_Role::Render);
}
template <typename Field, typename Source_State>
requires std::derived_from<State, Source_State>
void set_state_value(Field Source_State::* field, Field value) {
auto guard = this->acquire_edit_state();
if (!guard)
return;
auto state = reinterpret_cast<State*>(guard.state);
state->*field = std::move(value);
}
protected:
void copy_render_state_object(Render_State& target, const Render_State& source) override {
static_cast<State&>(target) = static_cast<const State&>(source);
}
};
template <typename Owner, Render_State_Type State, Input_Data_Type Input>
using Typed_Render_Data = Typed_Render_Data_Base<Strategy_Render_Data, Owner, State, Input>;
template <typename Owner, Render_Data_Type Data, Render_State_Type State, Input_Data_Type Input>
struct Renderable_Binding {
static void init(Owner* owner, const char* name) {
owner->d_owner = make_render_object<Data>();
owner->d_ptr = static_cast<Render_Data*>(owner->d_owner.template get<Data>());
owner->set_object_name(name);
}
static Data* d(Owner* owner) {
return reinterpret_cast<Data*>(owner->d_ptr);
}
static Render_Object_Owner create_render_data() {
return make_render_object<Data>();
}
static Render_Object_Owner create_render_state() {
return make_render_object<State>();
}
static Render_Object_Owner create_input_data() {
if constexpr (std::is_same_v<Input, Input_Data>)
return {};
return make_render_object<Input>();
}
};
#define RENDERIVE_DEFINE_RENDERABLE_BINDING(Owner, Data, State, Input, Name) \
using Owner##_Binding = ::renderive::Renderable_Binding<Owner, Data, State, Input>; \
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 <typename Data, Render_State_Type State>
requires std::derived_from<Data, Render_Data>
struct Render_State_Lease : Immovable {
explicit Render_State_Lease(Data* data) : data(data) {
guard = data->acquire_edit_state_read();
state = reinterpret_cast<State*>(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 <typename Data, Render_State_Type State>
requires std::derived_from<Data, Render_Data>
struct Render_Edit_Lease : Immovable {
explicit Render_Edit_Lease(Data* data) : data(data) {
guard = data->acquire_edit_state();
state = reinterpret_cast<State*>(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 <typename Data, Input_Data_Type Input>
requires std::derived_from<Data, Render_Data>
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<Input*>(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<Update_State> state) {
guard.set_completion(std::move(state));
}
Data* data{};
bool notify_render{};
Render_Input_Edit_Guard guard;
Input* input{};
};
} // namespace renderive
+54 -25
View File
@@ -1,16 +1,26 @@
#include <algorithm>
#include <utility>
#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<std::uint64_t> 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<Renderable>& 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<Render_Data>();
}
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<Renderable>& 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<Renderable>& 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<Renderable>& child) {
if (!child || child->parent_renderable.lock().get() != this)
@@ -145,33 +160,46 @@ void Renderable::remove_child(const std::shared_ptr<Renderable>& child) {
children.erase(it);
}
removed->detach_from_plot_tree();
mark_render_dirty();
mark_render_dirty(Dirty_Reason::Config);
}
std::vector<std::shared_ptr<Renderable>> Renderable::children_snapshot() const {
std::lock_guard<std::mutex> 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<std::shared_ptr<Renderable>> 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<double>(bounds.x), static_cast<double>(bounds.y), static_cast<double>(cache_image.width()), static_cast<double>(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<std::uint64_t>(cache_image.byte_size()), static_cast<std::uint64_t>(bounds.width) * static_cast<std::uint64_t>(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<std::uint64_t>(cache_image.byte_size()), static_cast<std::uint64_t>(bounds.width) * static_cast<std::uint64_t>(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<std::uint64_t>(cache_image.byte_size()), static_cast<std::uint64_t>(bounds.width) * static_cast<std::uint64_t>(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<std::uint64_t>(cache_image.byte_size()), static_cast<std::uint64_t>(bounds.width) * static_cast<std::uint64_t>(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<double>(bounds.x), static_cast<double>(bounds.y), static_cast<double>(cache_image.width()), static_cast<double>(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<std::uint64_t>(cache_image.byte_size()), static_cast<std::uint64_t>(bounds.width) * static_cast<std::uint64_t>(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<std::uint64_t>(cache_image.byte_size()), static_cast<std::uint64_t>(bounds.width) * static_cast<std::uint64_t>(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<double>(bounds.x), static_cast<double>(bounds.y), static_cast<double>(cache_image.width()), static_cast<double>(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<std::uint64_t>(cache_image.byte_size()), static_cast<std::uint64_t>(bounds.width) * static_cast<std::uint64_t>(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<std::uint64_t>(cache_image.byte_size()), static_cast<std::uint64_t>(bounds.width) * static_cast<std::uint64_t>(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<std::uint64_t>(cache_image.byte_size()), static_cast<std::uint64_t>(bounds.width) * static_cast<std::uint64_t>(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<std::uint64_t>(cache_image.byte_size()), static_cast<std::uint64_t>(bounds.width) * static_cast<std::uint64_t>(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<double>(bounds.x), static_cast<double>(bounds.y), static_cast<double>(cache_image.width()), static_cast<double>(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<std::uint64_t>(cache_image.byte_size()), static_cast<std::uint64_t>(bounds.width) * static_cast<std::uint64_t>(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<std::uint64_t>(cache_image.byte_size()), static_cast<std::uint64_t>(bounds.width) * static_cast<std::uint64_t>(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();
+11 -2
View File
@@ -46,7 +46,14 @@ public:
void remove_child(const std::shared_ptr<Renderable>& child);
std::vector<std::shared_ptr<Renderable>> children_snapshot() const;
std::vector<std::shared_ptr<Renderable>> 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<Renderable> parent_renderable;
std::atomic_bool visible_{true};
+39
View File
@@ -0,0 +1,39 @@
#pragma once
#include "Render_Data.h"
#include "global.h"
#include <cstddef>
#include <cstdint>
#include <memory>
#include <memory_resource>
#include <vector>
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<std::shared_ptr<Update_State>>& 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<Strategy_Render_Data_Private> runtime;
};
} // namespace renderive
+41 -128
View File
@@ -3,16 +3,42 @@
#include <algorithm>
#include <array>
#include <atomic>
#include <deque>
#include <memory>
#include <mutex>
#include <optional>
#include <unordered_set>
#include <vector>
#include <taskflow/taskflow.hpp>
#include <thread>
namespace renderive {
struct Taskflow_Graph_Impl {
explicit Taskflow_Graph_Impl(tf::Taskflow& taskflow) : taskflow(taskflow) {}
tf::Taskflow& taskflow;
std::vector<tf::Task> 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<Taskflow_Graph_Impl*>(impl);
if (!graph || !work)
return {};
auto work_ptr = std::make_shared<Task>(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<std::uint32_t>(graph->tasks.size() - 1)};
}
void Render_Task_Graph::precede(Render_Task_Graph_Node before, Render_Task_Graph_Node after) {
auto* graph = static_cast<Taskflow_Graph_Impl*>(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<Render_Executor_Metrics>()),
@@ -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<std::mutex> 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<Render_Executor_Task> cancelled_tasks;
{
std::lock_guard<std::mutex> 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<std::size_t>(1, metrics->worker_count);
}
std::size_t frame_queue_limit() const {
return std::max<std::size_t>(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<Pending_Frame_Task> 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<Pending_Frame_Task> next;
{
std::lock_guard<std::mutex> 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<Render_Executor_Metrics> metrics;
tf::Executor executor;
std::shared_ptr<tf::ObserverInterface> observer;
std::mutex admission_mutex;
std::unordered_set<Plot_Execution_Id> admitted_frame_plots;
std::unordered_set<Plot_Execution_Id> queued_frame_plots;
std::deque<Pending_Frame_Task> pending_frame_tasks;
std::size_t admitted_frame_count{};
std::atomic_bool shutting_down{false};
};
+17 -3
View File
@@ -2,17 +2,31 @@
#include "../architecture/Render_Config.h"
#include "../architecture/global.h"
#include "../base/Task.h"
#include <cstdint>
#include <functional>
#include <memory>
#include <taskflow/taskflow.hpp>
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<void(tf::Taskflow&, tf::Task, tf::Task)>;
using Graph_Builder = std::function<void(Render_Task_Graph&, Render_Task_Graph_Node, Render_Task_Graph_Node)>;
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_Worker_Stats> frame_stat;
std::uint32_t logical_task_count{1};
+2 -1
View File
@@ -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;
+5 -1
View File
@@ -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);
}
+5 -1
View File
@@ -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;
@@ -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<std::uint64_t>(srtt_ms * 1000000.0) : 0;
}
std::uint64_t Low_Latency_Smoothed_Interval::jitter_ns() const {
return sample_count ? static_cast<std::uint64_t>(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<double>(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<double>(render_duration_ns + frame.render_queue_wait_ns) / 1000000.0);
if (frame.ready_queue_wait_ns)
ready_queue_period.update(static_cast<double>(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<double>(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<double>(frame.paint_begin_ns - frame.update_request_time_ns) / 1000000.0);
if (frame.paint_return_wait_ns)
consumption_period.update(static_cast<double>(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<double>(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<double>(presented_frame_count) / static_cast<double>(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;
}
@@ -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
@@ -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<Frame_Lifecycle_Record>& 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<Low_Latency_Present_Surface_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);
}
@@ -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;
+460 -127
View File
@@ -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 <array>
#include <mutex>
#include <ostream>
#include <system_error>
#include <utility>
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<Update_State> 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<Render_State*, 3> state_buffers{};
std::array<Render_Object_Owner, 3> state_buffer_owners{};
Triple_Role_Buffer_Control input_control;
std::array<Input_Buffer_Slot, 3> input_buffers{};
std::array<Render_Object_Owner, 3> input_buffer_owners{};
std::pmr::vector<std::shared_ptr<Update_State>> 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<Update_State> 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<std::mutex> 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<std::mutex> 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<std::mutex> 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<Render_State>();
state_buffers[State_Ready] = state_buffer_owners[State_Ready].get<Render_State>();
state_buffers[State_Render] = state_buffer_owners[State_Render].get<Render_State>();
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<Input_Data>();
if (input_buffer_owners[Input_Ready])
input_buffers[Input_Ready].data = input_buffer_owners[Input_Ready].get<Input_Data>();
if (input_buffer_owners[Input_Render])
input_buffers[Input_Render].data = input_buffer_owners[Input_Render].get<Input_Data>();
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<Render_State>();
if (explicit_state)
explicit_state->version = 1;
explicit_input_owner = std::move(input);
explicit_input.data = explicit_input_owner.get<Input_Data>();
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<std::mutex> 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<Update_State> 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<std::mutex> lock)
: lock(std::move(lock)) {}
std::unique_lock<std::mutex> 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<std::mutex> 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<std::mutex> 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<std::mutex> 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<Update_State> 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<std::mutex> 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<Update_State> 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<std::shared_ptr<Update_State>>& 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_Private>()) {}
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<Render_State>();
state_buffers[State_Ready] = state_buffer_owners[State_Ready].get<Render_State>();
state_buffers[State_Render] = state_buffer_owners[State_Render].get<Render_State>();
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<Input_Data>();
if (input_buffer_owners[Input_Ready])
input_buffers[Input_Ready].data = input_buffer_owners[Input_Ready].get<Input_Data>();
if (input_buffer_owners[Input_Render])
input_buffers[Input_Render].data = input_buffer_owners[Input_Render].get<Input_Data>();
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<std::shared_ptr<Update_State>>& 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<std::mutex> lock(runtime->explicit_state_mutex);
if (!runtime->explicit_state)
return {};
return Render_State_Read_Guard(runtime->explicit_state, std::make_unique<Explicit_State_Read_Handle>(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<Low_Latency_State_Read_Handle>(this, lease));
return Render_State_Read_Guard(runtime->state_by_index(lease.index), std::make_unique<Low_Latency_State_Read_Handle>(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<std::mutex> lock(runtime->explicit_state_mutex);
if (!runtime->explicit_state)
return {};
return Render_State_Read_Guard(runtime->explicit_state, std::make_unique<Explicit_State_Read_Handle>(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<Low_Latency_State_Read_Handle>(this, lease));
return Render_State_Read_Guard(runtime->state_by_index(lease.index), std::make_unique<Low_Latency_State_Read_Handle>(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<std::mutex> lock(runtime->explicit_state_mutex);
if (!runtime->explicit_state)
return {};
return Render_State_Read_Guard(runtime->explicit_state, std::make_unique<Explicit_State_Read_Handle>(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<Low_Latency_State_Edit_Handle>(this, lease));
return Render_State_Read_Guard(runtime->state_by_index(lease.index), std::make_unique<Low_Latency_State_Read_Handle>(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<std::mutex> lock(runtime->explicit_state_mutex);
if (!runtime->explicit_state)
return {};
return Render_State_Read_Guard(runtime->explicit_state, std::make_unique<Explicit_State_Read_Handle>(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<std::mutex> lock(runtime->explicit_state_mutex);
if (!runtime->explicit_state)
return {};
return Render_State_Edit_Guard(runtime->explicit_state, std::make_unique<Explicit_State_Edit_Handle>(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<Low_Latency_State_Edit_Handle>(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<std::mutex> lock(runtime->explicit_input_mutex);
if (!runtime->explicit_input.data)
return {};
return Render_Input_Edit_Guard(runtime->explicit_input.data, std::make_unique<Explicit_Input_Edit_Handle>(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<Low_Latency_Input_Edit_Handle>(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
+2 -111
View File
@@ -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 <array>
#include <atomic>
#include <cstdint>
#include <memory>
#include <memory_resource>
#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<Update_State> completion_state;
};
struct Low_Latency_Render_Data : Render_Data {
Triple_Role_Buffer_Control state_control;
std::array<Render_State*, 3> state_buffers{};
std::array<Render_Object_Owner, 3> state_buffer_owners{};
Triple_Role_Buffer_Control input_control;
std::array<Input_Buffer_Slot, 3> input_buffers{};
std::array<Render_Object_Owner, 3> input_buffer_owners{};
std::pmr::vector<std::shared_ptr<Update_State>> 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<std::uint8_t>(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<Update_State> state);
void drain_committed_update_states(std::pmr::vector<std::shared_ptr<Update_State>>& 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
+2 -244
View File
@@ -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 <utility>
namespace renderive {
template <typename Base, typename Owner, Render_State_Type State, Input_Data_Type Input>
requires std::derived_from<Base, Low_Latency_Render_Data>
struct Typed_Render_Data_Base : Base {
Owner* q() {
return reinterpret_cast<Owner*>(this->q_ptr);
}
State* render_state() override {
return reinterpret_cast<State*>(this->state(State_Render));
}
State* ready_state() override {
return reinterpret_cast<State*>(this->state(State_Ready));
}
State* edit_state() override {
return reinterpret_cast<State*>(this->state(State_Edit));
}
Input* render_input_data() override {
return reinterpret_cast<Input*>(this->input_data(Input_Render));
}
Input* edit_input_data() override {
return reinterpret_cast<Input*>(this->input_data(Input_Edit));
}
State* state_data(State_Role src) {
return reinterpret_cast<State*>(this->state(src));
}
State* state_data_by_index(std::uint8_t index) {
return reinterpret_cast<State*>(this->state_by_index(index));
}
Input* input_data_by_index(std::uint8_t index) {
return reinterpret_cast<Input*>(Base::input_data_by_index(index));
}
template <typename Field, typename Source_State>
requires std::derived_from<State, Source_State>
Field state_value(Field Source_State::* field, State_Role src) {
auto record = this->state_control.acquire_role(static_cast<std::uint8_t>(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 <typename Field, typename Source_State>
requires std::derived_from<State, Source_State>
Field edit_state_value(Field Source_State::* field) {
return state_value(field, State_Edit);
}
template <typename Field, typename Source_State>
requires std::derived_from<State, Source_State>
Field ready_state_value(Field Source_State::* field) {
return state_value(field, State_Ready);
}
template <typename Field, typename Source_State>
requires std::derived_from<State, Source_State>
Field render_state_value(Field Source_State::* field) {
return state_value(field, State_Render);
}
template <typename Field, typename Source_State>
requires std::derived_from<State, Source_State>
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<State*>(this->state_by_index(ready_lease.index));
auto edit = reinterpret_cast<State*>(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<State*>(this->state_by_index(view[State_Ready]));
auto render = reinterpret_cast<State*>(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 <typename Owner, Render_State_Type State, Input_Data_Type Input>
using Typed_Render_Data = Typed_Render_Data_Base<Low_Latency_Render_Data, Owner, State, Input>;
template <typename Owner, Render_Data_Type Data, Render_State_Type State, Input_Data_Type Input>
struct Renderable_Binding {
static void init(Owner* owner, const char* name) {
owner->d_owner = make_render_object<Data>();
owner->d_ptr = static_cast<Render_Data*>(owner->d_owner.template get<Data>());
owner->set_object_name(name);
}
static Data* d(Owner* owner) {
return reinterpret_cast<Data*>(owner->d_ptr);
}
static Render_Object_Owner create_render_data() {
return make_render_object<Data>();
}
static Render_Object_Owner create_render_state() {
return make_render_object<State>();
}
static Render_Object_Owner create_input_data() {
if constexpr (std::is_same_v<Input, Input_Data>)
return {};
return make_render_object<Input>();
}
};
#define RENDERIVE_DEFINE_RENDERABLE_BINDING(Owner, Data, State, Input, Name) \
using Owner##_Binding = ::renderive::Renderable_Binding<Owner, Data, State, Input>; \
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 <typename Data, Render_State_Type State>
requires std::derived_from<Data, Low_Latency_Render_Data>
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<State*>(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 <typename Data, Render_State_Type State>
requires std::derived_from<Data, Low_Latency_Render_Data>
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<State*>(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 <typename Data, Input_Data_Type Input>
requires std::derived_from<Data, Low_Latency_Render_Data>
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<Input*>(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
+43 -25
View File
@@ -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 <algorithm>
#include <memory>
#include <utility>
@@ -62,6 +65,7 @@ public:
frame.pixel_width = static_cast<std::uint64_t>(size.width);
frame.pixel_height = static_cast<std::uint64_t>(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<Frame_Render_Job>();
@@ -71,7 +75,7 @@ public:
job->buffer = buffer;
job->lifecycle = frame_record;
job->worker_stat = std::make_shared<Frame_Worker_Stats>();
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<Frame_Render_Job>& 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<Frame_Render_Job>& 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<Frame_Render_Job>& job) {
if (!frame_job_valid(job) || !job->lifecycle->prepare_end_ns)
@@ -161,7 +166,7 @@ public:
}
}
void execute_frame_job(const std::shared_ptr<Frame_Render_Job>& 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<Frame_Flow> flow;
std::shared_ptr<Plot_Render_Context> context = std::make_shared<Plot_Render_Context>();
std::shared_ptr<std::atomic_bool> destroying_token = std::make_shared<std::atomic_bool>(false);
std::shared_ptr<Renderable> root;
std::vector<std::weak_ptr<Renderable>> hover_targets;
std::weak_ptr<Presentation_Sink> sink;
std::atomic<std::uint64_t> latest_input_time_ns{0};
std::atomic<std::uint64_t> pending_input_count{0};
std::atomic<std::uint32_t> 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<Frame_Flow> 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_Render_Context> Plot_Core::render_context() const {
return d->context;
}
std::shared_ptr<Renderable> Plot_Core::root_renderable() const {
return d->root;
}
@@ -455,7 +460,7 @@ void Plot_Core::remove_renderable(const std::shared_ptr<Renderable>& 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<Presentation_Sink> 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_Render_Context> Plot_Core_Context_Access::render_context(Plot_Core& plot) {
return plot.d ? plot.d->context : std::shared_ptr<Plot_Render_Context>{};
}
std::shared_ptr<Plot_Render_Context> Plot_Core_Context_Access::render_context(const Plot_Core& plot) {
return plot.d ? plot.d->context : std::shared_ptr<Plot_Render_Context>{};
}
} // namespace renderive
+6 -4
View File
@@ -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<Plot_Render_Context> render_context() const;
[[nodiscard]] std::shared_ptr<Renderable> root_renderable() const;
[[nodiscard]] std::shared_ptr<Renderable> create_renderable_node(
const std::shared_ptr<Renderable>& 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<Presentation_Sink> 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<Plot_Core_Private> d;
};
+15
View File
@@ -0,0 +1,15 @@
#pragma once
#include "../architecture/global.h"
#include <memory>
namespace renderive {
class Plot_Core;
struct Plot_Render_Context;
struct LIB_DECL Plot_Core_Context_Access {
[[nodiscard]] static std::shared_ptr<Plot_Render_Context> render_context(Plot_Core& plot);
[[nodiscard]] static std::shared_ptr<Plot_Render_Context> render_context(const Plot_Core& plot);
};
} // namespace renderive
+2 -1
View File
@@ -120,7 +120,8 @@ void Afterglow::Builder_T<That>::set(Afterglow* ret) {
ret->init(parent);
std::shared_ptr<Renderable> owner = ret->shared_from_this();
Afterglow_Private* pd = ret->d();
Afterglow_Render_State* sc = reinterpret_cast<Afterglow_Render_State*>(pd->edit_state());
Render_Edit_Lease<Afterglow_Private, Afterglow_Render_State> edit(pd);
Afterglow_Render_State* sc = edit.operator->();
PROP_RT(std::weak_ptr<Frequency_Axis>, frequency_axis)
PROP_RT(std::weak_ptr<Axis>, power_axis)
PROP_RT(Range, frequency_range)
+2 -1
View File
@@ -84,7 +84,8 @@ std::shared_ptr<Constellation_Diagram> Constellation_Diagram::Builder::build() {
auto ret = renderive::make_shared<Constellation_Diagram>();
ret->init(parent);
Constellation_Diagram_Private* pd = ret->d();
Constellation_Diagram_State* sc = reinterpret_cast<Constellation_Diagram_State*>(pd->edit_state());
Render_Edit_Lease<Constellation_Diagram_Private, Constellation_Diagram_State> edit(pd);
Constellation_Diagram_State* sc = edit.operator->();
PROP_R(std::weak_ptr<Axis>, i_axis)
PROP_R(std::weak_ptr<Axis>, q_axis)
PROP_R(Range, i_range)
+1 -1
View File
@@ -5,7 +5,7 @@
#include <cmath>
#include <list>
#include <memory_resource>
#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"
+2 -1
View File
@@ -30,7 +30,8 @@ std::shared_ptr<Frequency_Trace> Frequency_Trace::Builder::build() {
auto ret = renderive::make_shared<Frequency_Trace>();
ret->init(parent);
Frequency_Trace_Private* pd = ret->d();
Frequency_Trace_Render_State* sc = reinterpret_cast<Frequency_Trace_Render_State*>(pd->edit_state());
Render_Edit_Lease<Frequency_Trace_Private, Frequency_Trace_Render_State> edit(pd);
Frequency_Trace_Render_State* sc = edit.operator->();
PROP_R(std::weak_ptr<Time_Axis>, time_axis)
PROP_R(std::weak_ptr<Axis>, value_axis)
sc->pen = Pen{.color = color, .width = 1.0};
+17 -14
View File
@@ -1,4 +1,6 @@
#include "Performance_Overlay_p.h"
#include "../architecture/Plot_Render_Context.h"
#include "../plot/Plot_Core_Access.h"
#include <cstdlib>
#include <filesystem>
#include <iomanip>
@@ -257,7 +259,7 @@ Performance_Overlay_Private* Performance_Overlay::d() {
return private_data.get();
}
static std::shared_ptr<Performance_Overlay> 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<Performance_Overlay> attach_performance_overlay(Plot_Core& plot,
return attach_performance_overlay_impl(plot, &options);
}
std::shared_ptr<Performance_Overlay> 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<const Performance_Display_Snapshot> 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<void()> 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::function<void
shower->set_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) {
+1 -1
View File
@@ -2,7 +2,7 @@
#include <cstdint>
#include <functional>
#include <memory>
#include "../architecture/Plot_Render_Context.h"
#include "../architecture/Frame_Scheduler.h"
#include "../base/Color.h"
#include "../base/Geometry.h"
#include "../base/Text.h"
+71 -22
View File
@@ -15,7 +15,6 @@
#include <vector>
#include <rigtorp/MPMCQueue.h>
#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<const Frame_Lifecycle_Record> 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<double>(frame.renderable_cache_compose_time_ns) * 100.0 / static_cast<double>(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<const Renderable_Aggregate_Stat*>& emitted) {
void append_renderable_tree_lines(std::uint64_t parent_node_id, int tree_depth, std::vector<const Renderable_Aggregate_Stat*>& emitted) {
std::vector<const Renderable_Aggregate_Stat*> 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<const Renderable_Aggregate_Stat*> 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<const Renderable_Cache_Aggregate_Stat*>& emitted) {
void append_cache_tree_lines(std::uint64_t parent_node_id, int tree_depth, std::vector<const Renderable_Cache_Aggregate_Stat*>& emitted) {
std::vector<const Renderable_Cache_Aggregate_Stat*> 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<const Renderable_Cache_Aggregate_Stat*> 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<double>(aggregate.cache_hit_count) * 100.0 / static_cast<double>(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<std::uint64_t>(executor.worker_count)) + " active:" + count_text("taskflow_active", static_cast<std::uint64_t>(executor.active_workers)) + " queued_frames:" + count_text("taskflow_queued_frames", static_cast<std::uint64_t>(executor.queued_frame_jobs)) + " active_frames:" + count_text("taskflow_active_frames", static_cast<std::uint64_t>(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<std::uint64_t>(executor.worker_count)) + " active:" + count_text("taskflow_active", static_cast<std::uint64_t>(executor.active_workers)) + " queued_frames:" + count_text("taskflow_queued_frames", static_cast<std::uint64_t>(executor.queued_frame_jobs)) + " active_frames:" + count_text("taskflow_active_frames", static_cast<std::uint64_t>(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));
@@ -17,7 +17,8 @@ std::shared_ptr<Selection_Rectangle_Overlay> Selection_Rectangle_Overlay::Builde
auto ret = renderive::make_shared<Selection_Rectangle_Overlay>();
ret->init(parent);
Selection_Rectangle_Overlay_Private* pd = ret->d();
Selection_Rectangle_Overlay_Render_State* sc = reinterpret_cast<Selection_Rectangle_Overlay_Render_State*>(pd->edit_state());
Render_Edit_Lease<Selection_Rectangle_Overlay_Private, Selection_Rectangle_Overlay_Render_State> edit(pd);
Selection_Rectangle_Overlay_Render_State* sc = edit.operator->();
sc->horizontal_axis = domain_axis;
sc->vertical_axis = value_axis;
sc->label_font = font;
+4 -3
View File
@@ -174,7 +174,8 @@ std::shared_ptr<Spectrum> Spectrum::Builder::build() {
auto ret = renderive::make_shared<Spectrum>();
ret->init(parent);
Spectrum_Private* pd = ret->d();
Spectrum_Render_State* sc = reinterpret_cast<Spectrum_Render_State*>(pd->edit_state());
Render_Edit_Lease<Spectrum_Private, Spectrum_Render_State> edit(pd);
Spectrum_Render_State* sc = edit.operator->();
PROP_R(std::weak_ptr<Frequency_Axis>, frequency_axis)
PROP_R(std::weak_ptr<Axis>, power_axis);
PROP_R(int, frequency_point_size);
@@ -500,7 +501,7 @@ Update_Ticket Spectrum::submit_data(std::span<const double> line_data) {
{
Render_Input_Lease<Spectrum_Private, Spectrum_Input_Data> 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<double>&& line_data) {
{
Render_Input_Lease<Spectrum_Private, Spectrum_Input_Data> 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);
+2 -1
View File
@@ -103,7 +103,8 @@ std::shared_ptr<Sweep_Spectrum> Sweep_Spectrum::Builder::build() {
auto ret = renderive::make_shared<Sweep_Spectrum>();
ret->init(parent);
Sweep_Spectrum_Private* pd = ret->d();
Sweep_Spectrum_Render_State* sc = reinterpret_cast<Sweep_Spectrum_Render_State*>(pd->edit_state());
Render_Edit_Lease<Sweep_Spectrum_Private, Sweep_Spectrum_Render_State> edit(pd);
Sweep_Spectrum_Render_State* sc = edit.operator->();
PROP_R(std::weak_ptr<Axis>, frequency_axis)
PROP_R(std::weak_ptr<Axis>, power_axis)
PROP_R(Range, frequency_range)
+2 -1
View File
@@ -139,7 +139,8 @@ std::shared_ptr<Waterfall> Waterfall::Builder::build() {
auto ret = renderive::make_shared<Waterfall>();
ret->init(parent);
Waterfall_Private* pd = ret->d();
Waterfall_Render_State* sc = reinterpret_cast<Waterfall_Render_State*>(pd->edit_state());
Render_Edit_Lease<Waterfall_Private, Waterfall_Render_State> edit(pd);
Waterfall_Render_State* sc = edit.operator->();
PROP_R(std::weak_ptr<Frequency_Axis>, frequency_axis)
PROP_R(std::weak_ptr<Time_Axis>, time_axis)
PROP_R(Range, frequency_range)
+2 -16
View File
@@ -10,8 +10,6 @@
#include <rigtorp/MPMCQueue.h>
#include <span>
#include <system_error>
#include <taskflow/taskflow.hpp>
#include <thread>
#include <utility>
#include <vector>
#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<int>(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<unsigned>(static_cast<unsigned>(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<Waterfall_Image_Request>& request_ptr) {
Waterfall_Image_Request& request = *request_ptr;
+2 -1
View File
@@ -67,7 +67,8 @@ std::shared_ptr<Band_Region> Band_Region::Builder::build() {
auto ret = renderive::make_shared<Band_Region>();
ret->init(parent);
Band_Region_Private* pd = ret->d();
Band_Region_Render_State* sc = reinterpret_cast<Band_Region_Render_State*>(pd->edit_state());
Render_Edit_Lease<Band_Region_Private, Band_Region_Render_State> edit(pd);
Band_Region_Render_State* sc = edit.operator->();
PROP_R(std::weak_ptr<Abs_Axis>, domain_axis)
PROP_R(std::weak_ptr<Abs_Axis>, value_axis)
PROP_R(Range, range)
+3 -2
View File
@@ -5,7 +5,7 @@
#include <utility>
#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> Color_Bar::Builder::build() {
auto ret = renderive::make_shared<Color_Bar>();
ret->init(parent);
Color_Bar_Private* pd = ret->d();
Color_Bar_Render_State* sc = reinterpret_cast<Color_Bar_Render_State*>(pd->edit_state());
Render_Edit_Lease<Color_Bar_Private, Color_Bar_Render_State> 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)
+2 -1
View File
@@ -460,7 +460,8 @@ std::shared_ptr<Curve> Curve::Builder::build() {
auto ret = renderive::make_shared<Curve>();
ret->init(parent);
Curve_Private* pd = ret->d();
Curve_Render_State* sc = reinterpret_cast<Curve_Render_State*>(pd->edit_state());
Render_Edit_Lease<Curve_Private, Curve_Render_State> edit(pd);
Curve_Render_State* sc = edit.operator->();
PROP_R(std::weak_ptr<Abs_Axis>, domain_axis)
PROP_R(std::weak_ptr<Abs_Axis>, value_axis)
PROP_R(Range, data_range)
+2 -1
View File
@@ -103,7 +103,8 @@ std::shared_ptr<Fill_Area> Fill_Area::Builder::build() {
auto ret = renderive::make_shared<Fill_Area>();
ret->init(parent);
Fill_Area_Private* pd = ret->d();
Fill_Area_Render_State* sc = reinterpret_cast<Fill_Area_Render_State*>(pd->edit_state());
Render_Edit_Lease<Fill_Area_Private, Fill_Area_Render_State> edit(pd);
Fill_Area_Render_State* sc = edit.operator->();
PROP_R(std::weak_ptr<Abs_Axis>, domain_axis)
PROP_R(std::weak_ptr<Abs_Axis>, value_axis)
PROP_R(double, baseline)
+2 -1
View File
@@ -180,7 +180,8 @@ std::shared_ptr<Grid> Grid::Builder::build() {
auto ret = renderive::make_shared<Grid>();
ret->init(parent);
Grid_Private* pd = ret->d();
Grid_Render_State* sc = reinterpret_cast<Grid_Render_State*>(pd->edit_state());
Render_Edit_Lease<Grid_Private, Grid_Render_State> edit(pd);
Grid_Render_State* sc = edit.operator->();
PROP_R(std::weak_ptr<Abs_Axis>, domain_axis)
PROP_R(std::weak_ptr<Abs_Axis>, value_axis)
PROP_R(bool, domain_grid)
+2 -1
View File
@@ -122,7 +122,8 @@ std::shared_ptr<Label> Label::Builder::build() {
auto ret = renderive::make_shared<Label>();
ret->init(parent);
Label_Private* pd = ret->d();
Label_Render_State* sc = reinterpret_cast<Label_Render_State*>(pd->edit_state());
Render_Edit_Lease<Label_Private, Label_Render_State> edit(pd);
Label_Render_State* sc = edit.operator->();
PROP_R(std::weak_ptr<Abs_Axis>, domain_axis)
PROP_R(std::weak_ptr<Abs_Axis>, value_axis)
PROP_R(PointF, position)
+2 -1
View File
@@ -118,7 +118,8 @@ std::shared_ptr<Marker> Marker::Builder::build() {
auto ret = renderive::make_shared<Marker>();
ret->init(parent);
Marker_Private* pd = ret->d();
Marker_Render_State* sc = reinterpret_cast<Marker_Render_State*>(pd->edit_state());
Render_Edit_Lease<Marker_Private, Marker_Render_State> edit(pd);
Marker_Render_State* sc = edit.operator->();
PROP_R(std::weak_ptr<Abs_Axis>, domain_axis)
PROP_R(std::weak_ptr<Abs_Axis>, value_axis)
PROP_R(PointF, position)
+2 -1
View File
@@ -164,7 +164,8 @@ std::shared_ptr<Point_Set> Point_Set::Builder::build() {
auto ret = renderive::make_shared<Point_Set>();
ret->init(parent);
Point_Set_Private* pd = ret->d();
Point_Set_Render_State* sc = reinterpret_cast<Point_Set_Render_State*>(pd->edit_state());
Render_Edit_Lease<Point_Set_Private, Point_Set_Render_State> edit(pd);
Point_Set_Render_State* sc = edit.operator->();
PROP_R(std::weak_ptr<Abs_Axis>, domain_axis)
PROP_R(std::weak_ptr<Abs_Axis>, value_axis)
PROP_R(double, size)
+2 -1
View File
@@ -114,7 +114,8 @@ std::shared_ptr<Raster_Image> Raster_Image::Builder::build() {
auto ret = renderive::make_shared<Raster_Image>();
ret->init(parent);
Raster_Image_Private* pd = ret->d();
Raster_Image_Render_State* sc = reinterpret_cast<Raster_Image_Render_State*>(pd->edit_state());
Render_Edit_Lease<Raster_Image_Private, Raster_Image_Render_State> edit(pd);
Raster_Image_Render_State* sc = edit.operator->();
PROP_R(std::weak_ptr<Abs_Axis>, domain_axis)
PROP_R(std::weak_ptr<Abs_Axis>, value_axis)
PROP_R(Range, domain_range)
+1 -1
View File
@@ -2,7 +2,7 @@
#include <algorithm>
#include <cmath>
#include <utility>
#include "../flow/low_latency/Render_Lease.h"
#include "../architecture/Render_Lease.h"
#include "../base/Color.h"
#include "../base/Geometry.h"
#include "../plottable/Interpolation_p.h"
+4
View File
@@ -4,6 +4,10 @@
namespace renderive {
bool Render_Frame_Snapshot::destroying() const {
return !destroying_token || destroying_token->load(std::memory_order_acquire);
}
std::shared_ptr<const Timeline_Stream_Snapshot> Render_Frame_Snapshot::timeline_snapshot(
const std::shared_ptr<Timeline_Stream>& stream,
int time_point_size,
+17 -5
View File
@@ -1,5 +1,4 @@
#pragma once
#include "../architecture/Plot_Render_Context.h"
#include "../architecture/Update_Completion.h"
#include "../base/Color.h"
#include "../base/Geometry.h"
@@ -7,11 +6,26 @@
#include <cstdint>
#include <memory>
#include <memory_resource>
#include <vector>
namespace renderive {
struct Frame_Lifecycle_Record;
class Timeline_Stream;
struct Timeline_Stream_Snapshot;
struct Timeline_Stream_Cache_Item {
std::weak_ptr<Timeline_Stream> stream;
int time_point_size = 1;
int tick_space = 1;
bool start_coord_to_end_coord = false;
std::shared_ptr<const Timeline_Stream_Snapshot> snapshot;
};
struct Timeline_Stream_Frame_Cache {
std::vector<Timeline_Stream_Cache_Item> items;
};
struct Render_Frame_Snapshot {
std::shared_ptr<Plot_Render_Context> context;
std::shared_ptr<std::atomic_bool> destroying_token;
Size size;
Color background_color = Color::black();
std::uint64_t frame_time_ns{};
@@ -20,9 +34,7 @@ struct Render_Frame_Snapshot {
std::pmr::vector<std::shared_ptr<Update_State>>* frame_update_states{};
mutable std::shared_ptr<Timeline_Stream_Frame_Cache> timeline_cache = std::make_shared<Timeline_Stream_Frame_Cache>();
[[nodiscard]] bool destroying() const {
return !context || context->destroying.load(std::memory_order_acquire);
}
[[nodiscard]] bool destroying() const;
[[nodiscard]] std::shared_ptr<const Timeline_Stream_Snapshot> timeline_snapshot(
const std::shared_ptr<Timeline_Stream>& stream,
+5
View File
@@ -1,5 +1,6 @@
#pragma once
#include <cstddef>
#include <cstdint>
#include <memory>
#include <string>
#include <vector>
@@ -11,8 +12,12 @@ class Renderable;
struct Renderable_Frame_Node {
std::shared_ptr<Renderable> renderable;
std::size_t subtree_end{};
std::uint64_t node_id{};
std::uint64_t parent_node_id{};
std::string parent_object_name;
int render_tree_depth{};
std::string object_name;
std::uint64_t cache_parent_node_id{};
std::string cache_parent_object_name;
int cache_tree_depth{};
};