策略抽离
This commit is contained in:
@@ -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
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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 {}
|
||||
};
|
||||
|
||||
|
||||
@@ -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
|
||||
@@ -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();
|
||||
|
||||
@@ -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};
|
||||
|
||||
@@ -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
|
||||
@@ -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};
|
||||
};
|
||||
|
||||
|
||||
@@ -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};
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
@@ -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
|
||||
|
||||
@@ -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;
|
||||
};
|
||||
|
||||
|
||||
@@ -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
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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};
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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,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,
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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{};
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user