From 6d000f43b48f6a26b063b8189ca4d1ca762e04f1 Mon Sep 17 00:00:00 2001 From: wyc <1104749580@qq.com> Date: Sun, 2 Aug 2026 12:48:37 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BF=AE=E5=A4=8D=E8=8B=A5=E5=B9=B2bug?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Core/architecture/Render_Data.h | 2 + Core/architecture/Render_Lease.h | 41 ++--- Core/architecture/Renderable.cpp | 10 +- Core/architecture/Renderable.h | 1 - Core/architecture/Triple_Buffer.h | 149 ++++++++++++++--- Core/execution/Render_Executor.cpp | 14 ++ Core/plot/Explicit_Refresh_Strategy.cpp | 2 +- Core/plot/Explicit_Refresh_Strategy.h | 2 +- Core/plot/Latency_Eager_Refresh_Strategy.cpp | 8 +- Core/plot/Latency_Eager_Refresh_Strategy.h | 2 +- Core/plot/Plot_Core.cpp | 20 +-- Core/plot/Plot_Frame_Pipeline.cpp | 20 ++- Core/plot/Plot_Frame_Pipeline.h | 2 +- Core/plot/Present_Frame_Lease.h | 4 +- Core/plot/Refresh_Strategy.h | 2 +- Core/plottable/Interpolation_p.h | 15 ++ Core/plottable/Performance_Overlay_p.h | 106 ++++++++---- Core/plottable/Waterfall_p.h | 98 +++-------- Core/primitive/Curve.cpp | 48 ++---- Core/primitive/Curve_Utils_p.h | 16 +- test/Renderive_Core_Tests.cpp | 166 ++++++++++++++++++- 21 files changed, 495 insertions(+), 233 deletions(-) diff --git a/Core/architecture/Render_Data.h b/Core/architecture/Render_Data.h index dcea40b..f29c30a 100644 --- a/Core/architecture/Render_Data.h +++ b/Core/architecture/Render_Data.h @@ -153,6 +153,7 @@ struct Render_Data { state_control.unmark_use(lease); } void clear_state_buffers() { + state_control.close(); for (std::size_t i = 0; i < state_buffers.size(); ++i) { state_buffer_owners[i].reset(); state_buffers[i] = nullptr; @@ -185,6 +186,7 @@ struct Render_Data { clear_input_role(Input_Render); } void clear_input_buffers() { + input_control.close(); cancel_pending_completions(); for (std::size_t i = 0; i < input_buffers.size(); ++i) { input_buffer_owners[i].reset(); diff --git a/Core/architecture/Render_Lease.h b/Core/architecture/Render_Lease.h index 9219484..f62e7f1 100644 --- a/Core/architecture/Render_Lease.h +++ b/Core/architecture/Render_Lease.h @@ -35,7 +35,14 @@ struct Typed_Render_Data_Base : Base { template requires std::derived_from Field state_value(Field Source_State::* field, State_Role src) { - return state_data(src)->*field; + auto record = this->state_control.acquire_role(static_cast(src)); + if (record.result != Triple_Buffer_Result::Success) + return {}; + Triple_Buffer_Lease lease = record.lease; + auto state = state_data_by_index(lease.index); + Field result = state->*field; + this->state_control.unmark_use(lease); + return result; } template requires std::derived_from @@ -55,7 +62,7 @@ struct Typed_Render_Data_Base : Base { template requires std::derived_from void set_state_value(Field Source_State::* field, Field value) { - auto record = this->state_control.try_acquire_role(State_Edit); + auto record = this->state_control.acquire_role(State_Edit); if (record.result != Triple_Buffer_Result::Success) return; auto lease = record.lease; @@ -139,15 +146,11 @@ struct Renderable_Binding { template struct Render_State_Lease : Immovable { explicit Render_State_Lease(Data* data) : data(data) { - auto record = data->state_control.try_acquire_role(State_Edit); + auto record = data->state_control.acquire_role(State_Edit); if (record.result == Triple_Buffer_Result::Success) { lease = record.lease; state = reinterpret_cast(data->state_by_index(lease.index)); } - else { - fallback_owner = make_render_object(); - state = fallback_owner.get(); - } } ~Render_State_Lease() { if (lease) @@ -159,23 +162,21 @@ struct Render_State_Lease : Immovable { State& operator*() { return *state; } + explicit operator bool() const { + return state && lease; + } Data* data{}; Triple_Buffer_Lease lease{}; State* state{}; - Render_Object_Owner fallback_owner; }; template struct Render_Edit_Lease : Immovable { explicit Render_Edit_Lease(Data* data) : data(data) { - auto record = data->state_control.try_acquire_role(State_Edit); + auto record = data->state_control.acquire_role(State_Edit); if (record.result == Triple_Buffer_Result::Success) { lease = record.lease; state = reinterpret_cast(data->state_by_index(lease.index)); } - else { - fallback_owner = make_render_object(); - state = fallback_owner.get(); - } } ~Render_Edit_Lease() { if (!lease) @@ -192,23 +193,21 @@ struct Render_Edit_Lease : Immovable { State& operator*() { return *state; } + explicit operator bool() const { + return state && lease; + } Data* data{}; Triple_Buffer_Lease lease{}; State* state{}; - Render_Object_Owner fallback_owner; }; template 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.try_acquire_role(Input_Edit); + auto record = data->input_control.acquire_role(Input_Edit); if (record.result == Triple_Buffer_Result::Success) { lease = record.lease; input = reinterpret_cast(data->input_data_by_index(lease.index)); } - else { - fallback_owner = make_render_object(); - input = fallback_owner.get(); - } } ~Render_Input_Lease() { if (!lease) @@ -224,10 +223,12 @@ struct Render_Input_Lease : Immovable { Input& operator*() { return *input; } + explicit operator bool() const { + return input && lease; + } Data* data{}; bool notify_render{}; Triple_Buffer_Lease lease{}; Input* input{}; - Render_Object_Owner fallback_owner; }; } // namespace renderive diff --git a/Core/architecture/Renderable.cpp b/Core/architecture/Renderable.cpp index 5b145b7..5982883 100644 --- a/Core/architecture/Renderable.cpp +++ b/Core/architecture/Renderable.cpp @@ -10,7 +10,6 @@ Renderable_Cache_Mode Renderable::get_cache_mode() const { } void Renderable::set_cache_mode(Renderable_Cache_Mode mode) { cache_mode.store(mode, std::memory_order_release); - cache_dirty.store(true, std::memory_order_release); mark_render_dirty(); } bool Renderable::attached_to_plot() const { @@ -237,7 +236,7 @@ void Renderable::render_cached(Canvas& canvas, const Plot_Render_Snapshot& snaps std::uint64_t edit_version = cache_edit_version.load(std::memory_order_acquire); std::string parent_object_name = cache_parent_object_name(); int tree_depth = cache_tree_depth(); - bool ready = !cache_dirty.load(std::memory_order_acquire) && 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; + 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); @@ -300,7 +299,7 @@ void Renderable::render_cached_tree_node(Canvas& canvas, const Plot_Render_Snaps return; const Renderable_Tree_Node& node = tree[index]; std::uint64_t edit_version = cache_edit_version.load(std::memory_order_acquire); - bool ready = !cache_dirty.load(std::memory_order_acquire) && 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; + 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); @@ -337,8 +336,6 @@ void Renderable::render_cache_image_tree_node(const Rect& bounds, const Plot_Ren cache_bounds = bounds; this->cache_image = std::move(temp_image); cache_ready_version.store(version, std::memory_order_release); - bool dirty = cache_edit_version.load(std::memory_order_acquire) != version; - cache_dirty.store(dirty, std::memory_order_release); } void Renderable::render_cache_image(const Rect& bounds, const Plot_Render_Snapshot& snapshot, std::uint64_t version) { if (get_retiring()) @@ -354,8 +351,6 @@ void Renderable::render_cache_image(const Rect& bounds, const Plot_Render_Snapsh cache_bounds = bounds; this->cache_image = std::move(temp_image); cache_ready_version.store(version, std::memory_order_release); - bool dirty = cache_edit_version.load(std::memory_order_acquire) != version; - cache_dirty.store(dirty, std::memory_order_release); } void Renderable::render_self(Canvas& canvas, const Plot_Render_Snapshot& snapshot) { render_self(canvas, snapshot, nullptr); @@ -484,7 +479,6 @@ void Renderable::mark_render_dirty() { auto context = plot_context.lock(); if (!context || context->destroying.load(std::memory_order_acquire)) return; - cache_dirty.store(true, std::memory_order_release); cache_edit_version.fetch_add(1, std::memory_order_acq_rel); if (auto parent = parent_renderable.lock()) { parent->mark_render_dirty(); diff --git a/Core/architecture/Renderable.h b/Core/architecture/Renderable.h index ec90bf5..a97ab04 100644 --- a/Core/architecture/Renderable.h +++ b/Core/architecture/Renderable.h @@ -107,7 +107,6 @@ private: mutable std::mutex children_mutex; std::weak_ptr plot_context; std::atomic cache_mode{Renderable_Cache_Mode::Direct}; - std::atomic_bool cache_dirty{true}; std::atomic cache_edit_version{1}; std::atomic cache_ready_version{0}; Image cache_image; diff --git a/Core/architecture/Triple_Buffer.h b/Core/architecture/Triple_Buffer.h index 999a303..3078cd7 100644 --- a/Core/architecture/Triple_Buffer.h +++ b/Core/architecture/Triple_Buffer.h @@ -2,71 +2,86 @@ #include #include #include + namespace renderive { + static constexpr std::uint8_t triple_buffer_invalid_index = 255; -/// 三缓冲操作结果。 + enum class Triple_Buffer_Result : std::uint8_t { - /// 操作成功。 Success, - /// 目标物理缓冲正在被占用。 Busy, - /// 角色映射在操作期间发生变化。 State_Changed, - /// 交换条件拒绝,不代表错误。 - Condition_Failed + Condition_Failed, + Cancelled }; -/// 三个逻辑角色到三个物理缓冲的当前映射。 + struct Triple_Buffer_View { std::array index{}; - /// 返回指定角色当前对应的物理缓冲索引。 std::uint8_t operator[](std::uint8_t role) const { return index[role]; } }; -/// 一次物理缓冲占用租约。 + struct Triple_Buffer_Lease { std::uint8_t index = triple_buffer_invalid_index; - /// 判断租约是否持有有效物理缓冲。 explicit operator bool() const { return index != triple_buffer_invalid_index; } }; + struct Triple_Buffer_Mark_Record { Triple_Buffer_Result result = Triple_Buffer_Result::Condition_Failed; Triple_Buffer_Lease lease{}; std::uint8_t busy_index = triple_buffer_invalid_index; }; + struct Triple_Buffer_Swap_Record { Triple_Buffer_Result result = Triple_Buffer_Result::Condition_Failed; Triple_Buffer_View old_view{}; std::uint8_t busy_index = triple_buffer_invalid_index; }; + struct alignas(64) Triple_Buffer_Busy { std::atomic_bool busy{false}; }; + class Triple_Role_Buffer_Control { public: - /// 设置性能统计写入目标。 - /// 重置角色映射和全部 busy 标记。 void reset() { + closed.store(false, std::memory_order_release); slot_state.store(0, std::memory_order_release); for (auto& item : buffer_busy) { item.busy.store(false, std::memory_order_release); item.busy.notify_all(); } + wake_generation.fetch_add(1, std::memory_order_acq_rel); + wake_generation.notify_all(); slot_state.notify_all(); } + + void close() { + closed.store(true, std::memory_order_release); + for (auto& item : buffer_busy) { + item.busy.store(false, std::memory_order_release); + item.busy.notify_all(); + } + wake_generation.fetch_add(1, std::memory_order_acq_rel); + wake_generation.notify_all(); + slot_state.notify_all(); + } + Triple_Buffer_View read_view() const { return decode_order(slot_state.load(std::memory_order_acquire)); } - /// 尝试占用指定角色当前对应的物理缓冲。 + Triple_Buffer_Mark_Record try_acquire_role(std::uint8_t role) { + if (closed.load(std::memory_order_acquire)) + return {Triple_Buffer_Result::Cancelled, {}, triple_buffer_invalid_index}; auto order = slot_state.load(std::memory_order_acquire); auto view = decode_order(order); auto index = view[role]; - if (!try_mark_index(index)) { + if (!try_mark_index(index)) return {Triple_Buffer_Result::Busy, {}, index}; - } auto cur_order = slot_state.load(std::memory_order_acquire); if (cur_order != order) { unmark_index(index); @@ -74,28 +89,65 @@ public: } return {Triple_Buffer_Result::Success, {index}, triple_buffer_invalid_index}; } - /// 释放租约持有的物理缓冲。 - void unmark_use(Triple_Buffer_Lease lease) { - unmark_index(lease.index); + + Triple_Buffer_Mark_Record acquire_role(std::uint8_t role) { + while (!closed.load(std::memory_order_acquire)) { + auto order = slot_state.load(std::memory_order_acquire); + auto view = decode_order(order); + auto index = view[role]; + if (!try_mark_index(index)) { + auto wake = wake_generation.load(std::memory_order_acquire); + if (buffer_busy[index].busy.load(std::memory_order_acquire)) + wake_generation.wait(wake, std::memory_order_acquire); + continue; + } + auto cur_order = slot_state.load(std::memory_order_acquire); + if (cur_order != order) { + unmark_index(index); + continue; + } + if (closed.load(std::memory_order_acquire)) { + unmark_index(index); + break; + } + return {Triple_Buffer_Result::Success, {index}, triple_buffer_invalid_index}; + } + return {Triple_Buffer_Result::Cancelled, {}, triple_buffer_invalid_index}; } - /// 尝试交换两个角色的物理缓冲映射。 + + void unmark_use(Triple_Buffer_Lease lease) { + if (lease) + unmark_index(lease.index); + } + template Triple_Buffer_Swap_Record try_swap_role(std::uint8_t left_role, std::uint8_t right_role, Can_Swap can_swap) { std::uint8_t busy_index = triple_buffer_invalid_index; return try_swap_role_once(left_role, right_role, can_swap, [](const Triple_Buffer_View&, const Triple_Buffer_View&) {}, busy_index); } + template Triple_Buffer_Swap_Record try_swap_role(std::uint8_t left_role, std::uint8_t right_role, Can_Swap can_swap, On_Success on_success) { std::uint8_t busy_index = triple_buffer_invalid_index; return try_swap_role_once(left_role, right_role, can_swap, on_success, busy_index); } + + template + Triple_Buffer_Swap_Record try_swap_role_with_left_lease(std::uint8_t left_role, std::uint8_t right_role, Triple_Buffer_Lease left_lease, Can_Swap can_swap, On_Success on_success) { + return try_swap_role_with_left_lease_once(left_role, right_role, left_lease, can_swap, on_success); + } + bool role_busy(std::uint8_t role) const { auto view = read_view(); return buffer_busy[view[role]].busy.load(std::memory_order_acquire); } + private: std::atomic slot_state{0}; + std::atomic_bool closed{false}; + std::atomic wake_generation{0}; std::array buffer_busy{}; + static Triple_Buffer_View decode_order(std::uint8_t order) { switch (order) { case 0: @@ -113,15 +165,15 @@ private: } return {{0, 1, 2}}; } + static std::uint8_t encode_order(const Triple_Buffer_View& view) { - if (view.index[0] == 0) { + if (view.index[0] == 0) return view.index[1] == 1 ? 0 : 1; - } - if (view.index[0] == 1) { + if (view.index[0] == 1) return view.index[1] == 0 ? 2 : 3; - } return view.index[1] == 0 ? 4 : 5; } + static std::uint8_t swap_order(std::uint8_t order, std::uint8_t left_role, std::uint8_t right_role) { auto view = decode_order(order); auto temp = view.index[left_role]; @@ -129,14 +181,19 @@ private: view.index[right_role] = temp; return encode_order(view); } + bool try_mark_index(std::uint8_t index) { bool expected = false; return buffer_busy[index].busy.compare_exchange_strong(expected, true, std::memory_order_acq_rel, std::memory_order_acquire); } + void unmark_index(std::uint8_t index) { buffer_busy[index].busy.store(false, std::memory_order_release); buffer_busy[index].busy.notify_all(); + wake_generation.fetch_add(1, std::memory_order_acq_rel); + wake_generation.notify_all(); } + bool try_mark_two_indices(std::uint8_t left_index, std::uint8_t right_index, std::uint8_t& busy_index) { if (left_index == right_index) { if (!try_mark_index(left_index)) { @@ -158,6 +215,7 @@ private: } return true; } + void unmark_two_indices(std::uint8_t left_index, std::uint8_t right_index) { if (left_index == right_index) { unmark_index(left_index); @@ -166,15 +224,17 @@ private: unmark_index(left_index); unmark_index(right_index); } + template Triple_Buffer_Swap_Record try_swap_role_once(std::uint8_t left_role, std::uint8_t right_role, Can_Swap can_swap, On_Success on_success, std::uint8_t& busy_index) { + if (closed.load(std::memory_order_acquire)) + return {Triple_Buffer_Result::Cancelled, {}, triple_buffer_invalid_index}; auto old_order = slot_state.load(std::memory_order_acquire); auto old_view = decode_order(old_order); auto left_index = old_view[left_role]; auto right_index = old_view[right_role]; - if (!try_mark_two_indices(left_index, right_index, busy_index)) { + if (!try_mark_two_indices(left_index, right_index, busy_index)) return {Triple_Buffer_Result::Busy, old_view, busy_index}; - } auto cur_order = slot_state.load(std::memory_order_acquire); if (cur_order != old_order) { unmark_two_indices(left_index, right_index); @@ -193,7 +253,46 @@ private: on_success(old_view, decode_order(new_order)); unmark_two_indices(left_index, right_index); slot_state.notify_all(); + wake_generation.fetch_add(1, std::memory_order_acq_rel); + wake_generation.notify_all(); + return {Triple_Buffer_Result::Success, old_view, triple_buffer_invalid_index}; + } + + template + Triple_Buffer_Swap_Record try_swap_role_with_left_lease_once(std::uint8_t left_role, std::uint8_t right_role, Triple_Buffer_Lease left_lease, Can_Swap can_swap, On_Success on_success) { + if (!left_lease) + return {Triple_Buffer_Result::Condition_Failed, {}, triple_buffer_invalid_index}; + if (closed.load(std::memory_order_acquire)) + return {Triple_Buffer_Result::Cancelled, {}, triple_buffer_invalid_index}; + auto old_order = slot_state.load(std::memory_order_acquire); + auto old_view = decode_order(old_order); + if (old_view[left_role] != left_lease.index) + return {Triple_Buffer_Result::State_Changed, old_view, triple_buffer_invalid_index}; + auto right_index = old_view[right_role]; + if (!try_mark_index(right_index)) + return {Triple_Buffer_Result::Busy, old_view, right_index}; + auto cur_order = slot_state.load(std::memory_order_acquire); + if (cur_order != old_order) { + unmark_index(right_index); + return {Triple_Buffer_Result::State_Changed, old_view, triple_buffer_invalid_index}; + } + if (!can_swap(old_view)) { + unmark_index(right_index); + return {Triple_Buffer_Result::Condition_Failed, old_view, triple_buffer_invalid_index}; + } + auto new_order = swap_order(old_order, left_role, right_role); + auto expected = old_order; + if (!slot_state.compare_exchange_strong(expected, new_order, std::memory_order_acq_rel, std::memory_order_acquire)) { + unmark_index(right_index); + return {Triple_Buffer_Result::State_Changed, old_view, triple_buffer_invalid_index}; + } + on_success(old_view, decode_order(new_order)); + unmark_index(right_index); + slot_state.notify_all(); + wake_generation.fetch_add(1, std::memory_order_acq_rel); + wake_generation.notify_all(); return {Triple_Buffer_Result::Success, old_view, triple_buffer_invalid_index}; } }; + } // namespace renderive diff --git a/Core/execution/Render_Executor.cpp b/Core/execution/Render_Executor.cpp index 099b33b..8e23096 100644 --- a/Core/execution/Render_Executor.cpp +++ b/Core/execution/Render_Executor.cpp @@ -8,6 +8,7 @@ #include #include #include +#include #include #include @@ -183,11 +184,24 @@ public: void shutdown() { if (shutting_down.exchange(true, std::memory_order_acq_rel)) return; + std::vector cancelled_tasks; { std::lock_guard lock(admission_mutex); + while (!pending_frame_tasks.empty()) { + Pending_Frame_Task pending = std::move(pending_frame_tasks.front()); + pending_frame_tasks.pop_front(); + cancelled_tasks.push_back(std::move(pending.task)); + metrics->queued_frame_jobs.fetch_sub(1, std::memory_order_relaxed); + } pending_frame_tasks.clear(); queued_frame_plots.clear(); } + for (auto& task : cancelled_tasks) { + if (task.frame_stat) + task.frame_stat->executor_at_finish = snapshot(); + if (task.completion) + task.completion(); + } executor.wait_for_all(); } diff --git a/Core/plot/Explicit_Refresh_Strategy.cpp b/Core/plot/Explicit_Refresh_Strategy.cpp index f315548..2a3c8af 100644 --- a/Core/plot/Explicit_Refresh_Strategy.cpp +++ b/Core/plot/Explicit_Refresh_Strategy.cpp @@ -9,7 +9,7 @@ void Explicit_Refresh_Strategy::request_render() { Refresh_Action Explicit_Refresh_Strategy::on_trigger( Refresh_Trigger, const Refresh_Runtime_Snapshot& runtime, - const Frame_Lifecycle_Record*) { + Frame_Lifecycle_Record*) { Refresh_Action action; if (!manual_request_pending.load(std::memory_order_acquire)) return action; diff --git a/Core/plot/Explicit_Refresh_Strategy.h b/Core/plot/Explicit_Refresh_Strategy.h index 9b7e84d..9ea8052 100644 --- a/Core/plot/Explicit_Refresh_Strategy.h +++ b/Core/plot/Explicit_Refresh_Strategy.h @@ -12,7 +12,7 @@ private: [[nodiscard]] Refresh_Action on_trigger( Refresh_Trigger trigger, const Refresh_Runtime_Snapshot& runtime, - const Frame_Lifecycle_Record* frame) override; + Frame_Lifecycle_Record* frame) override; std::atomic_bool manual_request_pending{false}; }; diff --git a/Core/plot/Latency_Eager_Refresh_Strategy.cpp b/Core/plot/Latency_Eager_Refresh_Strategy.cpp index 353eda4..5683a16 100644 --- a/Core/plot/Latency_Eager_Refresh_Strategy.cpp +++ b/Core/plot/Latency_Eager_Refresh_Strategy.cpp @@ -32,7 +32,7 @@ Refresh_Control_Snapshot Latency_Eager_Refresh_Strategy::feedback_state() const Refresh_Action Latency_Eager_Refresh_Strategy::on_trigger( Refresh_Trigger trigger, const Refresh_Runtime_Snapshot& runtime, - const Frame_Lifecycle_Record* frame) { + Frame_Lifecycle_Record* frame) { d->feedback.set_max_render_fps(max_render_fps()); if (trigger == Refresh_Trigger::Started) @@ -42,13 +42,13 @@ Refresh_Action Latency_Eager_Refresh_Strategy::on_trigger( if (frame) { switch (trigger) { case Refresh_Trigger::Render_Finished: - d->feedback.on_render_completed(const_cast(*frame)); + d->feedback.on_render_completed(*frame); break; case Refresh_Trigger::Frame_Returned: if (frame->outcome == Frame_Outcome::Superseded) - d->feedback.on_frame_superseded(const_cast(*frame)); + d->feedback.on_frame_superseded(*frame); else - d->feedback.on_frame_presented(const_cast(*frame)); + d->feedback.on_frame_presented(*frame); break; default: break; diff --git a/Core/plot/Latency_Eager_Refresh_Strategy.h b/Core/plot/Latency_Eager_Refresh_Strategy.h index 1da7cb8..56bf0f6 100644 --- a/Core/plot/Latency_Eager_Refresh_Strategy.h +++ b/Core/plot/Latency_Eager_Refresh_Strategy.h @@ -19,7 +19,7 @@ private: [[nodiscard]] Refresh_Action on_trigger( Refresh_Trigger trigger, const Refresh_Runtime_Snapshot& runtime, - const Frame_Lifecycle_Record* frame) override; + Frame_Lifecycle_Record* frame) override; struct Private; std::unique_ptr d; diff --git a/Core/plot/Plot_Core.cpp b/Core/plot/Plot_Core.cpp index 1e0c1c8..9fc2248 100644 --- a/Core/plot/Plot_Core.cpp +++ b/Core/plot/Plot_Core.cpp @@ -29,7 +29,7 @@ public: return rendering.load(std::memory_order_acquire); } bool destroying() const { - return destroying_flag.load(std::memory_order_acquire); + return !context || context->destroying.load(std::memory_order_acquire); } Refresh_Runtime_Snapshot refresh_runtime_snapshot(std::uint64_t now_ns = steady_now_ns()) const { return { @@ -43,7 +43,7 @@ public: }; } bool submit_render_once() { - if (!rendering.load(std::memory_order_acquire) || destroying_flag.load(std::memory_order_acquire)) + if (!rendering.load(std::memory_order_acquire) || destroying()) return false; if (!root) return false; @@ -189,16 +189,16 @@ public: if (job->worker_stat) frame.worker = *job->worker_stat; bool valid_generation = job->generation == generation.load(std::memory_order_acquire); - bool valid_context = !destroying_flag.load(std::memory_order_acquire) && context && !context->destroying.load(std::memory_order_acquire); - job->frame_lease.reset(); + bool valid_context = !destroying(); if (!valid_generation || !valid_context || !frame.render_end_ns) { + job->frame_lease.reset(); frame.outcome = Frame_Outcome::Cancelled; pipeline.render_job_state = Frame_Job_State::Idle; supersede_update_states(job->buffer->presented_update_states); trigger_refresh_strategy(Refresh_Trigger::Render_Finished, &frame); return; } - auto publish_result = pipeline.try_publish_rendered_frame(steady_now_ns()); + auto publish_result = pipeline.try_publish_rendered_frame(std::move(job->frame_lease), steady_now_ns()); if (publish_result.superseded_frame_record) { trigger_refresh_strategy(Refresh_Trigger::Frame_Returned, publish_result.superseded_frame_record.get()); collect_frame(publish_result.superseded_frame_record); @@ -420,7 +420,6 @@ public: Plot_Frame_Pipeline pipeline; std::weak_ptr sink; std::atomic_bool rendering{false}; - std::atomic_bool destroying_flag{false}; std::atomic latest_input_time_ns{0}; std::atomic pending_input_count{0}; bool strategy_started{}; @@ -444,7 +443,6 @@ Plot_Core::Plot_Core(std::unique_ptr strategy) Global::instance()->render_scheduler().register_plot(this); } Plot_Core::~Plot_Core() { - d->destroying_flag.store(true, std::memory_order_release); d->context->destroying.store(true, std::memory_order_release); d->generation.fetch_add(1, std::memory_order_acq_rel); Global::instance()->render_scheduler().remove_plot(this); @@ -512,7 +510,7 @@ void Plot_Core::dispatch_event(const Event& event) { d->dispatch_event(event); } void Plot_Core::mark_render_state_dirty() { - if (d->destroying_flag.load(std::memory_order_acquire)) + if (d->destroying()) return; std::uint64_t now_ns = steady_now_ns(); d->pipeline.advance_edit_version(); @@ -523,14 +521,14 @@ void Plot_Core::mark_render_state_dirty() { }); } void Plot_Core::request_manual_refresh() { - if (d->destroying_flag.load(std::memory_order_acquire)) + if (d->destroying()) return; d->post_scheduler_action([data = d]() { data->trigger_refresh_strategy(Refresh_Trigger::Manual_Request); }); } void Plot_Core::start_render() { - if (d->destroying_flag.load(std::memory_order_acquire)) + if (d->destroying()) return; d->rendering.store(true, std::memory_order_release); d->post_scheduler_action([data = d]() { @@ -562,7 +560,7 @@ Present_Frame_Lease Plot_Core::begin_present() { result.is_new_frame = consumed.has_new_frame; result.update_was_pending = consumed.present_request_was_pending; if (consumed.lease) - result.lease_ = std::make_unique(std::move(consumed.lease)); + result.lease_.emplace(std::move(consumed.lease)); if (!result.new_frame() && result.request_was_pending()) { d->post_scheduler_action([data = d]() { data->trigger_refresh_strategy(Refresh_Trigger::Paint_Finished); diff --git a/Core/plot/Plot_Frame_Pipeline.cpp b/Core/plot/Plot_Frame_Pipeline.cpp index 50206b8..b3537a6 100644 --- a/Core/plot/Plot_Frame_Pipeline.cpp +++ b/Core/plot/Plot_Frame_Pipeline.cpp @@ -80,11 +80,13 @@ Plot_Frame_Lease Plot_Frame_Pipeline::try_acquire_render_frame() { return {frame_by_index(record.lease.index), &frame_control, record.lease}; } -Plot_Frame_Pipeline::Frame_Publish_Result Plot_Frame_Pipeline::try_publish_rendered_frame(std::uint64_t now_ns) { +Plot_Frame_Pipeline::Frame_Publish_Result Plot_Frame_Pipeline::try_publish_rendered_frame(Plot_Frame_Lease rendering_lease, std::uint64_t now_ns) { Frame_Publish_Result result; - Plot_Frame* rendered = frame_by_role(Frame_Rendering); + if (!rendering_lease) + return result; + Plot_Frame* rendered = rendering_lease.frame; - auto swap = frame_control.try_swap_role(Frame_Rendering, Frame_Middle, [this](const Triple_Buffer_View& view) { + auto swap = frame_control.try_swap_role_with_left_lease(Frame_Rendering, Frame_Middle, rendering_lease.lease, [this](const Triple_Buffer_View& view) { const Plot_Frame* rendering = frame_by_index(view[Frame_Rendering]); const Plot_Frame* middle = frame_by_index(view[Frame_Middle]); return buffer_version_newer(rendering->version.load(std::memory_order_acquire), middle->version.load(std::memory_order_acquire)); @@ -116,6 +118,7 @@ Plot_Frame_Pipeline::Frame_Publish_Result Plot_Frame_Pipeline::try_publish_rende return result; } + rendering_lease.reset(); Frame_Publish_Result update_result = request_present(now_ns); update_result.superseded_frame_record = result.superseded_frame_record; update_result.dropped_frame_record = result.dropped_frame_record; @@ -124,9 +127,16 @@ Plot_Frame_Pipeline::Frame_Publish_Result Plot_Frame_Pipeline::try_publish_rende Plot_Frame_Pipeline::Frame_Publish_Result Plot_Frame_Pipeline::request_present(std::uint64_t now_ns) { Frame_Publish_Result result; - if (!middle_frame_ready()) + auto record = frame_control.try_acquire_role(Frame_Middle); + if (record.result != Triple_Buffer_Result::Success) return result; - Plot_Frame* middle = frame_by_role(Frame_Middle); + + Plot_Frame_Lease middle_lease{frame_by_index(record.lease.index), &frame_control, record.lease}; + Plot_Frame* middle = middle_lease.frame; + const Plot_Frame* painting = frame_by_role(Frame_Painting); + if (!buffer_version_newer(middle->version.load(std::memory_order_acquire), painting->version.load(std::memory_order_acquire))) + return result; + if (paint_request_pending.load(std::memory_order_acquire)) { middle->metadata->update_request_id = pending_present_request_id.load(std::memory_order_acquire); middle->metadata->update_request_time_ns = pending_present_request_time_ns.load(std::memory_order_acquire); diff --git a/Core/plot/Plot_Frame_Pipeline.h b/Core/plot/Plot_Frame_Pipeline.h index 522e26d..e548728 100644 --- a/Core/plot/Plot_Frame_Pipeline.h +++ b/Core/plot/Plot_Frame_Pipeline.h @@ -63,7 +63,7 @@ public: [[nodiscard]] Plot_Frame* frame_by_role(std::uint8_t role); [[nodiscard]] const Plot_Frame* frame_by_role(std::uint8_t role) const; [[nodiscard]] Plot_Frame_Lease try_acquire_render_frame(); - [[nodiscard]] Frame_Publish_Result try_publish_rendered_frame(std::uint64_t now_ns); + [[nodiscard]] Frame_Publish_Result try_publish_rendered_frame(Plot_Frame_Lease rendering_lease, std::uint64_t now_ns); [[nodiscard]] Frame_Publish_Result request_present(std::uint64_t now_ns); [[nodiscard]] Frame_Consume_Result try_acquire_paint_frame(std::uint64_t now_ns); [[nodiscard]] bool middle_frame_ready() const; diff --git a/Core/plot/Present_Frame_Lease.h b/Core/plot/Present_Frame_Lease.h index b30ceb4..66da5c3 100644 --- a/Core/plot/Present_Frame_Lease.h +++ b/Core/plot/Present_Frame_Lease.h @@ -2,7 +2,7 @@ #include "Plot_Frame_Pipeline.h" #include "../render/Image_View.h" #include -#include +#include namespace renderive { class Present_Frame_Lease { public: @@ -16,7 +16,7 @@ public: [[nodiscard]] Image_View image() const; private: friend class Plot_Core; - std::unique_ptr lease_; + std::optional lease_; bool is_new_frame{}; bool update_was_pending{}; }; diff --git a/Core/plot/Refresh_Strategy.h b/Core/plot/Refresh_Strategy.h index 36ee2c8..52140e4 100644 --- a/Core/plot/Refresh_Strategy.h +++ b/Core/plot/Refresh_Strategy.h @@ -37,7 +37,7 @@ public: [[nodiscard]] virtual Refresh_Action on_trigger( Refresh_Trigger trigger, const Refresh_Runtime_Snapshot& runtime, - const Frame_Lifecycle_Record* frame) = 0; + Frame_Lifecycle_Record* frame) = 0; }; } // namespace renderive diff --git a/Core/plottable/Interpolation_p.h b/Core/plottable/Interpolation_p.h index c18ca29..a3b8c2d 100644 --- a/Core/plottable/Interpolation_p.h +++ b/Core/plottable/Interpolation_p.h @@ -91,6 +91,21 @@ double interpolate_sample_value(double index, int count, Line_Interpolation_Mode return left_value; } template +std::pmr::vector build_sample_line_points(const Axis_Mapping_2D& mapping, const Range& data_range, int total_count, int available_count, bool visible_range_only, Value_At value_at, std::pmr::memory_resource* resource = frame_memory_resource()) { + Visible_Sample_Window window = compute_visible_sample_window(mapping.domain, data_range, total_count, available_count, visible_range_only); + if (!window) + return {}; + int first_source = std::clamp(static_cast(std::ceil(std::min(window.first_index, window.last_index))), 0, available_count - 1); + int last_source = std::clamp(static_cast(std::floor(std::max(window.first_index, window.last_index))), first_source, available_count - 1); + std::pmr::vector points(resource); + points.reserve(static_cast(std::max(0, last_source - first_source + 1))); + for (int i = first_source; i <= last_source; ++i) { + double coord = source_index_to_coordinate(data_range, total_count, i); + points.push_back(mapping.map(coord, value_at(i))); + } + return points; +} +template std::pmr::vector build_interpolated_line_points(const Axis_Mapping_2D& mapping, const Range& data_range, int total_count, int available_count, bool visible_range_only, Line_Interpolation_Mode mode, Value_At value_at, std::pmr::memory_resource* resource = frame_memory_resource()) { Visible_Sample_Window window = compute_visible_sample_window(mapping.domain, data_range, total_count, available_count, visible_range_only); if (!window) diff --git a/Core/plottable/Performance_Overlay_p.h b/Core/plottable/Performance_Overlay_p.h index b97042e..52adc66 100644 --- a/Core/plottable/Performance_Overlay_p.h +++ b/Core/plottable/Performance_Overlay_p.h @@ -99,8 +99,50 @@ struct Performance_Aggregate { void clear() { *this = {}; } + static Frame_Lifecycle_Record summarize_frame(const Frame_Lifecycle_Record& frame) { + Frame_Lifecycle_Record summary; + summary.frame_id = frame.frame_id; + summary.input_version = frame.input_version; + summary.core_data_time_ns = frame.core_data_time_ns; + summary.render_request_ns = frame.render_request_ns; + summary.render_role_enter_ns = frame.render_role_enter_ns; + summary.render_begin_ns = frame.render_begin_ns; + summary.prepare_begin_ns = frame.prepare_begin_ns; + summary.prepare_end_ns = frame.prepare_end_ns; + summary.draw_begin_ns = frame.draw_begin_ns; + summary.draw_end_ns = frame.draw_end_ns; + summary.render_end_ns = frame.render_end_ns; + summary.transition_12_ns = frame.transition_12_ns; + summary.update_request_id = frame.update_request_id; + summary.update_request_time_ns = frame.update_request_time_ns; + summary.transition_23_ns = frame.transition_23_ns; + summary.paint_begin_ns = frame.paint_begin_ns; + summary.paint_end_ns = frame.paint_end_ns; + summary.transition_32_ns = frame.transition_32_ns; + summary.wait_21_ns = frame.wait_21_ns; + summary.wait_12_ns = frame.wait_12_ns; + summary.wait_23_ns = frame.wait_23_ns; + summary.wait_32_ns = frame.wait_32_ns; + summary.superseded_time_ns = frame.superseded_time_ns; + summary.outcome = frame.outcome; + summary.refresh = frame.refresh; + summary.worker = frame.worker; + summary.raw_input_count = frame.raw_input_count; + summary.prepared_output_count = frame.prepared_output_count; + summary.renderable_cache_hit_count = frame.renderable_cache_hit_count; + summary.renderable_cache_miss_count = frame.renderable_cache_miss_count; + summary.renderable_cache_rebuild_count = frame.renderable_cache_rebuild_count; + summary.renderable_cache_rebuild_time_ns = frame.renderable_cache_rebuild_time_ns; + summary.renderable_cache_compose_time_ns = frame.renderable_cache_compose_time_ns; + summary.renderable_cache_image_bytes = frame.renderable_cache_image_bytes; + summary.renderable_cache_image_area = frame.renderable_cache_image_area; + summary.pixel_width = frame.pixel_width; + summary.pixel_height = frame.pixel_height; + summary.viewport_version = frame.viewport_version; + return summary; + } void update(const Frame_Lifecycle_Record& frame) { - latest = frame; + latest = summarize_frame(frame); has_frame = true; ++observed_frame_count; std::uint64_t frame_render_time_ns = duration_ns(frame.render_begin_ns, frame.render_end_ns); @@ -357,7 +399,7 @@ void schedule_performance_metrics_worker(const std::shared_ptr metrics_worker = std::make_shared(); + std::shared_ptr metrics_worker; std::vector lines; std::vector render_lines; std::array field_layout_states{}; @@ -365,7 +407,13 @@ struct Performance_Overlay_Private { RectF scroll_bar_rect; RectF scroll_thumb_rect; double content_width{}; + explicit Performance_Overlay_Private(bool create_worker = true) { + if (create_worker) + metrics_worker = std::make_shared(); + } ~Performance_Overlay_Private() { + if (!metrics_worker) + return; metrics_worker->cancelled.store(true, std::memory_order_release); Performance_Frame_Record frame; while (metrics_worker->metric_queue.try_pop(frame)) {} @@ -418,18 +466,18 @@ struct Performance_Overlay_Private { state->worker_active.store(false, std::memory_order_release); } static std::shared_ptr build_display_snapshot(Performance_Worker_State& worker_state) { - Performance_Overlay_Private builder; - builder.aggregate = worker_state.aggregate; - builder.field_layout_states = worker_state.field_layout_states; - builder.rebuild_lines(); + Performance_Overlay_Private layout(false); + layout.aggregate = worker_state.aggregate; + layout.field_layout_states = worker_state.field_layout_states; + layout.rebuild_lines(); auto snapshot = std::make_shared(); snapshot->content_version = worker_state.next_content_version.fetch_add(1, std::memory_order_acq_rel); Size viewport = current_viewport(worker_state); snapshot->viewport_size = viewport; snapshot->viewport_version = current_viewport_version(worker_state); snapshot->viewport_rect = RectF{0.0, 0.0, static_cast(viewport.width), static_cast(viewport.height)}; - if (viewport.empty() || builder.lines.empty()) { - snapshot->lines = builder.stripped_lines(); + if (viewport.empty() || layout.lines.empty()) { + snapshot->lines = layout.stripped_lines(); return snapshot; } Performance_Overlay_Render_State state; @@ -446,24 +494,24 @@ struct Performance_Overlay_Private { metrics = measure_canvas.measure_text("0123456789 ABC \xE4\xB8\xAD\xE6\x96\x87"); if (metrics.line_height() <= 0.0) metrics = measure_canvas.measure_text("M"); - builder.seed_static_field_widths(measure_canvas); + layout.seed_static_field_widths(measure_canvas); int available_text_width = viewport.width - state.left_margin - state.right_margin - state.scroll_bar_width - state.scroll_bar_margin * 2; - builder.build_render_lines(measure_canvas, static_cast(std::max(1, available_text_width))); + layout.build_render_lines(measure_canvas, static_cast(std::max(1, available_text_width))); } - worker_state.field_layout_states = builder.field_layout_states; - snapshot->lines = builder.display_lines(); - builder.update_content_width(&state, worker_state.maximum_content_width, viewport.width); + worker_state.field_layout_states = layout.field_layout_states; + snapshot->lines = layout.display_lines(); + layout.update_content_width(&state, worker_state.maximum_content_width, viewport.width); double line_height = std::max(1.0, metrics.line_height()); - double panel_width = std::min(builder.content_width, static_cast(std::max(0, viewport.width))); - double height = builder.panel_height(line_height, &state, viewport.height); + double panel_width = std::min(layout.content_width, static_cast(std::max(0, viewport.width))); + double height = layout.panel_height(line_height, &state, viewport.height); if (panel_width <= 0.0 || height <= 0.0) return snapshot; - double content_height = builder.content_height(line_height, &state); - double content_viewport_height = builder.content_viewport_height(&state, height); + double content_height = layout.content_height(line_height, &state); + double content_viewport_height = layout.content_viewport_height(&state, height); double max_scroll = std::max(0.0, content_height - content_viewport_height); double scroll = performance_clamp_scroll_offset(worker_state.scroll_offset.load(std::memory_order_acquire), max_scroll); worker_state.scroll_offset.store(scroll, std::memory_order_release); - builder.panel_rect = RectF{0.0, 0.0, panel_width, height}; + layout.panel_rect = RectF{0.0, 0.0, panel_width, height}; Image image(static_cast(std::ceil(panel_width)), static_cast(std::ceil(height))); image.fill(Color::transparent()); Canvas canvas(image); @@ -481,30 +529,30 @@ struct Performance_Overlay_Private { canvas.set_clip_rect(text_rect); int first_line = std::max(0, static_cast(std::floor(scroll / line_height))); double y = static_cast(state.top_margin) - (scroll - static_cast(first_line) * line_height); - for (int i = first_line; i < static_cast(builder.render_lines.size()) && y < text_rect.bottom(); ++i, y += line_height) { + for (int i = first_line; i < static_cast(layout.render_lines.size()) && y < text_rect.bottom(); ++i, y += line_height) { if (y + line_height < text_rect.y) continue; - builder.draw_render_line(canvas, builder.render_lines[static_cast(i)], text_rect.x, y); + layout.draw_render_line(canvas, layout.render_lines[static_cast(i)], text_rect.x, y); } canvas.reset_clip(); - builder.update_scroll_bar_rects(&state, max_scroll, scroll, content_viewport_height, content_height); - if (!builder.scroll_bar_rect.empty()) { + layout.update_scroll_bar_rects(&state, max_scroll, scroll, content_viewport_height, content_height); + if (!layout.scroll_bar_rect.empty()) { canvas.set_brush(Brush{state.scroll_track, Brush_Style::Solid}); - canvas.fill_rect(builder.scroll_bar_rect); + canvas.fill_rect(layout.scroll_bar_rect); canvas.set_brush(Brush{state.scroll_thumb, Brush_Style::Solid}); - canvas.fill_rect(builder.scroll_thumb_rect); + canvas.fill_rect(layout.scroll_thumb_rect); } canvas.restore(); - snapshot->destination_rect = builder.panel_rect; - snapshot->scroll_track_rect = builder.scroll_bar_rect; - snapshot->scroll_thumb_rect = builder.scroll_thumb_rect; + snapshot->destination_rect = layout.panel_rect; + snapshot->scroll_track_rect = layout.scroll_bar_rect; + snapshot->scroll_thumb_rect = layout.scroll_thumb_rect; snapshot->content_height = content_height; snapshot->scroll_offset = scroll; snapshot->max_scroll_offset = max_scroll; snapshot->line_height = line_height; snapshot->image = std::move(image); - worker_state.last_scroll_track_rect = builder.scroll_bar_rect; - worker_state.last_scroll_thumb_rect = builder.scroll_thumb_rect; + worker_state.last_scroll_track_rect = layout.scroll_bar_rect; + worker_state.last_scroll_thumb_rect = layout.scroll_thumb_rect; worker_state.last_content_viewport_height = content_viewport_height; worker_state.last_line_height = line_height; worker_state.maximum_scroll_offset = max_scroll; diff --git a/Core/plottable/Waterfall_p.h b/Core/plottable/Waterfall_p.h index 2b745d1..12ecaf1 100644 --- a/Core/plottable/Waterfall_p.h +++ b/Core/plottable/Waterfall_p.h @@ -93,7 +93,6 @@ struct Waterfall_Image_Cache_Key { Range power_range{}; Range visible_range{}; Color background; - Waterfall_Row_Update_Policy policy = Waterfall_Row_Update_Policy::All_Pending; std::uint64_t color_map_hash{}; int width{}; int height{}; @@ -105,7 +104,6 @@ struct Waterfall_Image_Cache_Key { power_range == other.power_range && visible_range == other.visible_range && background == other.background && - policy == other.policy && color_map_hash == other.color_map_hash && width == other.width && height == other.height; @@ -235,33 +233,15 @@ static Waterfall_Image_Snapshot build_waterfall_image_snapshot(const std::shared struct Waterfall_Private : Typed_Render_Data, Hit_Testable, Hover_Interactive { static constexpr std::size_t Row_Queue_Capacity = 256; Waterfall_Ring_Buffer ring_buffer; - Image image; std::weak_ptr color_bar; std::optional pending_latest_row; - std::pmr::vector pending_image_rows{memory_resource(Memory_Domain::Waterfall)}; std::pmr::vector> pending_image_update_states{memory_resource(Memory_Domain::Update_Completion)}; rigtorp::MPMCQueue> row_queue; std::atomic_uint64_t dropped_row_count{0}; std::uint64_t last_line_commit_ns{}; - std::uint64_t image_color_map_hash{}; - Waterfall_Image_Snapshot image_snapshot; - bool has_image_snapshot{}; - Waterfall_Image_Cache_Key submitted_image_key; - bool has_submitted_image_key{}; + std::optional image_snapshot; + std::optional submitted_image_key; std::uint64_t pending_latest_deadline_ns{}; - Range m_image_frequency_range{}; - Range image_power_range{}; - Range image_visible_range{}; - Color image_background_color{}; - Waterfall_Row_Update_Policy image_update_policy = Waterfall_Row_Update_Policy::All_Pending; - int image_start_tick{}; - int image_first_col{}; - int image_last_col{}; - Range image_draw_frequency_range{}; - Range image_draw_time_range{}; - RectF image_draw_source_rect{}; - bool image_valid{}; - bool image_draw_visible{}; Waterfall_Private() : row_queue(Row_Queue_Capacity) {} bool select_test(const PointF& pos) override { @@ -273,7 +253,8 @@ struct Waterfall_Private : Typed_Render_Datahover_info_active = active; } ~Waterfall_Private() override { - complete_waterfall_result_states(image_snapshot, std::make_error_code(std::errc::operation_canceled), Update_Outcome::Cancelled); + if (image_snapshot) + complete_waterfall_result_states(*image_snapshot, std::make_error_code(std::errc::operation_canceled), Update_Outcome::Cancelled); std::shared_ptr row; while (row_queue.try_pop(row)) { if (row && row->completion_state) @@ -351,12 +332,7 @@ struct Waterfall_Private : Typed_Render_Data= row_count) - return false; - Pixel* line = image.row(tick_offset); - Pixel bg = premultiply(background_color); - for (int col = 0; col < image.width(); ++col) - line[col] = bg; - for (int col = first_col; col <= last_col; ++col) { - int color_offset = static_cast((row_data[col] - value_start_coord) * rate); - line[col] = color_map.at_offset(color_offset); - } - return true; - } - bool image_cache_match(const Waterfall_Render_State* s, int start_tick, int first_col, int last_col, const Range& h_range, const Range& power_range, Color background_color, std::uint64_t color_map_hash) const { - return image_valid && image_start_tick == start_tick && image_first_col == first_col && image_last_col == last_col && m_image_frequency_range == s->frequency_range && image_power_range == power_range && image_visible_range == h_range && image_background_color == background_color && image_update_policy == s->row_update_policy && image_color_map_hash == color_map_hash; - } void update_image_item(const Plot_Render_Snapshot& snapshot, const Timeline_Stream_Snapshot& timeline) { Waterfall_Render_State* s = render_state(); auto frequency_axis = s->frequency_axis.lock(); @@ -456,7 +415,7 @@ struct Waterfall_Private : Typed_Render_Datafrequency_range; Range v_range{static_cast(timeline.coordinate_begin), static_cast(timeline.coordinate_begin + timeline.visible_time_point_count)}; if (s->visible_range_only && !intersect_range(s->frequency_range, Axis_Render_Access::coord_range(h_axis), h_range)) { - image_draw_visible = false; + image_snapshot.reset(); return; } int col_count = s->frequency_bin_count, row_count = timeline.visible_time_point_count; @@ -481,15 +440,13 @@ struct Waterfall_Private : Typed_Render_Datarange, h_range, snapshot.background_color, - s->row_update_policy, color_map_hash, col_count, row_count }; - bool should_submit = !has_submitted_image_key || !(submitted_image_key == key) || !pending_image_update_states.empty(); + bool should_submit = !submitted_image_key || !(*submitted_image_key == key) || !pending_image_update_states.empty(); if (should_submit) { submitted_image_key = key; - has_submitted_image_key = true; auto request = std::make_shared(); request->key = key; request->color_map = color_scale->color_map; @@ -514,44 +471,27 @@ struct Waterfall_Private : Typed_Render_Dataupdate_states = std::move(pending_image_update_states); pending_image_update_states = std::pmr::vector>(memory_resource(Memory_Domain::Update_Completion)); image_snapshot = build_waterfall_image_snapshot(request); - has_image_snapshot = true; - image = image_snapshot.image; - image_valid = true; - image_start_tick = image_snapshot.key.start_tick; - image_first_col = image_snapshot.key.first_col; - image_last_col = image_snapshot.key.last_col; - m_image_frequency_range = image_snapshot.key.frequency_range; - image_power_range = image_snapshot.key.power_range; - image_visible_range = image_snapshot.key.visible_range; - image_background_color = image_snapshot.key.background; - image_update_policy = image_snapshot.key.policy; - image_color_map_hash = image_snapshot.key.color_map_hash; - image_draw_frequency_range = image_snapshot.frequency_range; - image_draw_time_range = image_snapshot.time_range; - image_draw_source_rect = image_snapshot.source_rect; - image_draw_visible = true; } - pending_image_rows.clear(); } void draw_image(Canvas& canvas, Waterfall_Render_State* s, Abs_Axis* h_axis, Time_Axis* v_axis) { - if (!image_draw_visible || image.empty()) + if (!image_snapshot || image_snapshot->image.empty()) return; - if (image_draw_frequency_range.length() == 0.0 || image_draw_time_range.length() == 0.0) + if (image_snapshot->frequency_range.length() == 0.0 || image_snapshot->time_range.length() == 0.0) return; - if (image_draw_source_rect.width == 0.0 || image_draw_source_rect.height == 0.0) + if (image_snapshot->source_rect.width == 0.0 || image_snapshot->source_rect.height == 0.0) return; Axis_Mapping_2D mapping = Axis_Render_Access::mapping(h_axis, v_axis); - Axis_Basis_2D basis = Axis_Basis_2D::from_ranges(mapping, image_draw_frequency_range, image_draw_time_range); + Axis_Basis_2D basis = Axis_Basis_2D::from_ranges(mapping, image_snapshot->frequency_range, image_snapshot->time_range); canvas.save(); canvas.set_image_interpolation(s->interpolation_mode); canvas.transform( - basis.domain_vector.x / image_draw_source_rect.width, - basis.domain_vector.y / image_draw_source_rect.width, - basis.value_vector.x / image_draw_source_rect.height, - basis.value_vector.y / image_draw_source_rect.height, + basis.domain_vector.x / image_snapshot->source_rect.width, + basis.domain_vector.y / image_snapshot->source_rect.width, + basis.value_vector.x / image_snapshot->source_rect.height, + basis.value_vector.y / image_snapshot->source_rect.height, basis.origin.x, basis.origin.y); - canvas.draw_image(RectF{0.0, 0.0, image_draw_source_rect.width, image_draw_source_rect.height}, image, image_draw_source_rect); + canvas.draw_image(RectF{0.0, 0.0, image_snapshot->source_rect.width, image_snapshot->source_rect.height}, image_snapshot->image, image_snapshot->source_rect); canvas.restore(); } void draw(Canvas& canvas, const Plot_Render_Snapshot& snapshot) override { @@ -560,10 +500,10 @@ struct Waterfall_Private : Typed_Render_Datatime_axis.lock(); if (!frequency_axis || !time_axis) return; - if (snapshot.frame_update_states && has_image_snapshot) { - for (const auto& state : image_snapshot.update_states) + if (snapshot.frame_update_states && image_snapshot) { + for (const auto& state : image_snapshot->update_states) snapshot.frame_update_states->push_back(state); - image_snapshot.update_states.clear(); + image_snapshot->update_states.clear(); } Abs_Axis* h_axis = frequency_axis.get(); Time_Axis* v_axis = time_axis.get(); diff --git a/Core/primitive/Curve.cpp b/Core/primitive/Curve.cpp index d424591..ec41427 100644 --- a/Core/primitive/Curve.cpp +++ b/Core/primitive/Curve.cpp @@ -6,6 +6,7 @@ #include #include #include +#include #include #include #include "../base/Memory.h" @@ -137,19 +138,9 @@ static void build_curve_geometry(Curve_Geometry_Result& result, const Curve_Geom }, memory_resource(Memory_Domain::Curve)); return; } - Visible_Sample_Window window = compute_visible_sample_window(request.mapping.domain, request.data_range, static_cast(request.samples.size()), static_cast(request.samples.size()), request.visible_range_only); - if (!window) - return; - points.reserve(static_cast(std::max(0, window.sample_count))); - double denominator = static_cast(std::max(window.sample_count - 1, 1)); - for (int i = 0; i < window.sample_count; ++i) { - double index = window.first_index + (window.last_index - window.first_index) * static_cast(i) / denominator; - double coord = source_index_to_coordinate(request.data_range, static_cast(request.samples.size()), index); - double value = interpolate_sample_value(index, static_cast(request.samples.size()), request.interpolation_mode, [&request](int sample_index) { - return request.samples[static_cast(sample_index)]; - }); - points.push_back(request.mapping.map(coord, value)); - } + points = Curve_Utils::build_resampled_points(request.mapping, request.data_range, static_cast(request.samples.size()), static_cast(request.samples.size()), request.visible_range_only, request.interpolation_mode, [&request](int i) { + return request.samples[static_cast(i)]; + }, memory_resource(Memory_Domain::Curve)); } struct Curve_Private : Typed_Render_Data { static constexpr std::size_t Input_Queue_Capacity = 8; @@ -159,15 +150,14 @@ struct Curve_Private : Typed_Render_Data> input_queue; std::atomic_uint64_t dropped_input_count{0}; std::pmr::vector> pending_geometry_update_states{memory_resource(Memory_Domain::Update_Completion)}; - Curve_Geometry_Result geometry_result; - bool has_geometry_result{}; - Curve_Geometry_Key submitted_geometry_key; - bool has_submitted_geometry_key{}; + std::optional geometry_result; + std::optional submitted_geometry_key; std::uint64_t input_generation{}; Curve_Private() : input_queue(Input_Queue_Capacity) {} ~Curve_Private() override { - complete_curve_result_states(geometry_result, std::make_error_code(std::errc::operation_canceled), Update_Outcome::Cancelled); + if (geometry_result) + complete_curve_result_states(*geometry_result, std::make_error_code(std::errc::operation_canceled), Update_Outcome::Cancelled); std::shared_ptr block; while (input_queue.try_pop(block)) { if (block && block->completion_state) @@ -245,10 +235,9 @@ struct Curve_Private : Typed_Render_Data(); request->mode = mode; request->mapping = mapping; @@ -260,28 +249,27 @@ struct Curve_Private : Typed_Render_Datadata_points = data_points; request->update_states = std::move(pending_geometry_update_states); pending_geometry_update_states = std::pmr::vector>(memory_resource(Memory_Domain::Update_Completion)); - geometry_result = Curve_Geometry_Result(); - geometry_result.mapping = request->mapping; - build_curve_geometry(geometry_result, *request); - geometry_result.update_states = std::move(request->update_states); - has_geometry_result = true; + geometry_result.emplace(); + geometry_result->mapping = request->mapping; + build_curve_geometry(*geometry_result, *request); + geometry_result->update_states = std::move(request->update_states); } void draw(Canvas& canvas, const Plot_Render_Snapshot& snapshot) override { Curve_Render_State* s = render_state(); - if (!has_geometry_result) + if (!geometry_result) return; if (snapshot.frame_update_states) { - for (const auto& state : geometry_result.update_states) + for (const auto& state : geometry_result->update_states) snapshot.frame_update_states->push_back(state); - geometry_result.update_states.clear(); + geometry_result->update_states.clear(); } - const auto& points = geometry_result.points; + const auto& points = geometry_result->points; if (points.empty()) return; canvas.save(); canvas.set_antialias(s->antialias); Curve_Utils::draw_polyline(&canvas, points, s->pen); - Curve_Utils::draw_fill_to_value(&canvas, points, geometry_result.mapping, geometry_result.mapping.value.coord_range.target, s->fill_brush); + Curve_Utils::draw_fill_to_value(&canvas, points, geometry_result->mapping, geometry_result->mapping.value.coord_range.target, s->fill_brush); canvas.restore(); } }; diff --git a/Core/primitive/Curve_Utils_p.h b/Core/primitive/Curve_Utils_p.h index 97422e1..0f4b35c 100644 --- a/Core/primitive/Curve_Utils_p.h +++ b/Core/primitive/Curve_Utils_p.h @@ -32,11 +32,13 @@ public: } template static std::pmr::vector build_resampled_points(Abs_Axis* domain_axis, Abs_Axis* value_axis, const Range& sample_domain_range, int source_sample_count, int available_sample_count, bool visible_range_only, Line_Interpolation_Mode mode, Value_At value_at) { - return build_interpolated_line_points(domain_axis, value_axis, sample_domain_range, source_sample_count, available_sample_count, visible_range_only, mode, value_at); + (void)mode; + return build_sample_line_points(Axis_Render_Access::mapping(domain_axis, value_axis), sample_domain_range, source_sample_count, available_sample_count, visible_range_only, value_at, frame_memory_resource()); } template static std::pmr::vector build_resampled_points(const Axis_Mapping_2D& mapping, const Range& sample_domain_range, int source_sample_count, int available_sample_count, bool visible_range_only, Line_Interpolation_Mode mode, Value_At value_at, std::pmr::memory_resource* resource = frame_memory_resource()) { - return build_interpolated_line_points(mapping, sample_domain_range, source_sample_count, available_sample_count, visible_range_only, mode, value_at, resource); + (void)mode; + return build_sample_line_points(mapping, sample_domain_range, source_sample_count, available_sample_count, visible_range_only, value_at, resource); } template static std::pmr::vector build_pixel_envelope_points(const Axis_Mapping_2D& mapping, const Range& sample_domain_range, int source_sample_count, int available_sample_count, bool visible_range_only, Value_At value_at, std::pmr::memory_resource* resource = frame_memory_resource()) { @@ -87,14 +89,8 @@ public: std::array indexes{bucket.first_index, bucket.minimum_index, bucket.maximum_index, bucket.last_index}; std::sort(indexes.begin(), indexes.end()); auto end = std::unique(indexes.begin(), indexes.end()); - if (pixel_forward) { - for (auto it = indexes.begin(); it != end; ++it) - append_envelope_point(points, mapping, sample_domain_range, source_sample_count, *it, value_at(*it)); - } - else { - for (auto it = std::make_reverse_iterator(end); it != indexes.rend(); ++it) - append_envelope_point(points, mapping, sample_domain_range, source_sample_count, *it, value_at(*it)); - } + for (auto it = indexes.begin(); it != end; ++it) + append_envelope_point(points, mapping, sample_domain_range, source_sample_count, *it, value_at(*it)); } return points; } diff --git a/test/Renderive_Core_Tests.cpp b/test/Renderive_Core_Tests.cpp index 7bdb784..9842133 100644 --- a/test/Renderive_Core_Tests.cpp +++ b/test/Renderive_Core_Tests.cpp @@ -1,9 +1,11 @@ #include "Core/architecture/Frame_Scheduler.h" +#include "Core/architecture/Render_Lease.h" #include "Core/architecture/Triple_Buffer.h" #include "Core/plot/Latency_Eager_Refresh_Strategy.h" #include "Core/plot/Plot_Frame_Pipeline.h" #include "Core/plot/Plot_Core.h" #include "Core/execution/Render_Executor.h" +#include "Core/primitive/Curve_Utils_p.h" #include "Core/plottable/Performance_Overlay_p.h" #include "Core/plottable/export.h" #include "Core/render/Image.h" @@ -13,6 +15,7 @@ #include #include #include +#include namespace renderive { namespace { bool unique_roles(const Triple_Buffer_View& view) { @@ -87,6 +90,22 @@ bool valid_utf8(const std::string& text) { } return true; } + +struct Lease_Test_Owner : Renderable {}; +struct Lease_Test_State : Render_State { + int value{}; +}; +struct Lease_Test_Input : Input_Data {}; +struct Lease_Test_Data : Typed_Render_Data {}; + +void init_lease_test_state(Lease_Test_Data& data) { + data.clear_state_buffers(); + for (std::uint8_t role = 0; role < 3; ++role) { + data.state_buffer_owners[role] = make_render_object(); + data.state_buffers[role] = data.state_buffer_owners[role].get(); + } + data.state_buffers[State_Edit]->version = 1; +} } // namespace TEST(Renderive_Canvas, TextUsesPenAndFillUsesBrush) { auto diagnostics = Canvas::default_font_diagnostics(); @@ -145,6 +164,48 @@ TEST(Renderive_Triple_Buffer, RoleSwapsAreTryOnlyAndDeterministic) { ASSERT_TRUE(unique_roles(control.read_view())); } } + +TEST(Renderive_State, CloseWakesBlockedAcquire) { + Triple_Role_Buffer_Control control; + control.reset(); + auto held = control.acquire_role(State_Edit); + ASSERT_EQ(held.result, Triple_Buffer_Result::Success); + std::atomic_bool entered{false}; + Triple_Buffer_Mark_Record blocked_result; + std::thread blocked([&]() { + entered.store(true, std::memory_order_release); + blocked_result = control.acquire_role(State_Edit); + }); + ASSERT_TRUE(wait_until([&]() { + return entered.load(std::memory_order_acquire); + })); + std::this_thread::sleep_for(std::chrono::milliseconds(10)); + control.close(); + blocked.join(); + EXPECT_EQ(blocked_result.result, Triple_Buffer_Result::Cancelled); + control.unmark_use(held.lease); +} + +TEST(Renderive_State, BusyEditSetterWaitsAndCommits) { + Lease_Test_Data data; + init_lease_test_state(data); + auto held = data.state_control.acquire_role(State_Edit); + ASSERT_EQ(held.result, Triple_Buffer_Result::Success); + + std::atomic_bool setter_returned{false}; + std::thread setter([&]() { + data.set_state_value(&Lease_Test_State::value, 42); + setter_returned.store(true, std::memory_order_release); + }); + std::this_thread::sleep_for(std::chrono::milliseconds(20)); + EXPECT_FALSE(setter_returned.load(std::memory_order_acquire)); + data.state_control.unmark_use(held.lease); + setter.join(); + + EXPECT_TRUE(setter_returned.load(std::memory_order_acquire)); + EXPECT_EQ(data.edit_state_value(&Lease_Test_State::value), 42); +} + TEST(Renderive_Frame_Pipeline, PublishAndPaintUseMiddleVersionOnly) { Plot_Frame_Pipeline pipeline; auto lease = pipeline.try_acquire_render_frame(); @@ -154,8 +215,7 @@ TEST(Renderive_Frame_Pipeline, PublishAndPaintUseMiddleVersionOnly) { lease.frame->metadata->render_begin_ns = 10; lease.frame->metadata->render_end_ns = 20; lease.frame->version.store(1, std::memory_order_release); - lease.reset(); - auto published = pipeline.try_publish_rendered_frame(25); + auto published = pipeline.try_publish_rendered_frame(std::move(lease), 25); EXPECT_TRUE(pipeline.middle_frame_ready()); EXPECT_TRUE(published.request_present); EXPECT_EQ(published.dropped_frame_record, nullptr); @@ -173,8 +233,7 @@ TEST(Renderive_Frame_Pipeline, PaintAcquireFailureKeepsPendingRequest) { lease.frame->metadata->render_begin_ns = 10; lease.frame->metadata->render_end_ns = 20; lease.frame->version.store(1, std::memory_order_release); - lease.reset(); - auto published = pipeline.try_publish_rendered_frame(25); + auto published = pipeline.try_publish_rendered_frame(std::move(lease), 25); ASSERT_TRUE(published.request_present); EXPECT_TRUE(pipeline.paint_request_pending.load(std::memory_order_acquire)); auto busy = pipeline.frame_control.try_acquire_role(Frame_Painting); @@ -188,6 +247,26 @@ TEST(Renderive_Frame_Pipeline, PaintAcquireFailureKeepsPendingRequest) { EXPECT_TRUE(consumed.present_request_was_pending); EXPECT_TRUE(static_cast(consumed.lease)); } +TEST(Renderive_Frame_Pipeline, RequestPresentDoesNotWriteBusyMiddle) { + Plot_Frame_Pipeline pipeline; + auto lease = pipeline.try_acquire_render_frame(); + ASSERT_TRUE(static_cast(lease)); + ASSERT_TRUE(lease.frame->metadata); + lease.frame->metadata->frame_id = 1; + lease.frame->metadata->render_begin_ns = 10; + lease.frame->metadata->render_end_ns = 20; + lease.frame->version.store(1, std::memory_order_release); + auto published = pipeline.try_publish_rendered_frame(std::move(lease), 25); + ASSERT_TRUE(published.request_present); + pipeline.paint_request_pending.store(false, std::memory_order_release); + + auto busy_middle = pipeline.frame_control.try_acquire_role(Frame_Middle); + ASSERT_EQ(busy_middle.result, Triple_Buffer_Result::Success); + auto blocked = pipeline.request_present(40); + EXPECT_FALSE(blocked.request_present); + EXPECT_FALSE(pipeline.paint_request_pending.load(std::memory_order_acquire)); + pipeline.frame_control.unmark_use(busy_middle.lease); +} TEST(Renderive_MPMCQueue, UsesTryOnlyBoundedCapacity) { rigtorp::MPMCQueue queue(2); EXPECT_TRUE(queue.try_push(1)); @@ -200,6 +279,36 @@ TEST(Renderive_MPMCQueue, UsesTryOnlyBoundedCapacity) { EXPECT_EQ(value, 2); EXPECT_FALSE(queue.try_pop(value)); } +TEST(Renderive_Curve_Sampling, AllSamplesKeepsSourcePointCount) { + Axis_Mapping_2D mapping{ + Axis_Frame_Snapshot{Orientation::Horizontal, Range{0.0, 4.0}, Range{0.0, 100.0}}, + Axis_Frame_Snapshot{Orientation::Vertical, Range{0.0, 10.0}, Range{100.0, 0.0}} + }; + std::array samples{0.0, 1.0, 2.0, 3.0, 4.0}; + auto points = Curve_Utils::build_resampled_points(mapping, Range{0.0, 4.0}, 5, 5, false, Line_Interpolation_Mode::Linear_Value, [&samples](int i) { + return samples[static_cast(i)]; + }); + ASSERT_EQ(points.size(), samples.size()); + EXPECT_DOUBLE_EQ(points.front().x, 0.0); + EXPECT_DOUBLE_EQ(points.back().x, 100.0); +} +TEST(Renderive_Curve_Sampling, ReverseAxisEnvelopeKeepsSourceOrderInsideBucket) { + Axis_Mapping_2D mapping{ + Axis_Frame_Snapshot{Orientation::Horizontal, Range{0.0, 4.0}, Range{1.0, 0.0}}, + Axis_Frame_Snapshot{Orientation::Vertical, Range{-20.0, 20.0}, Range{20.0, 0.0}} + }; + std::array samples{0.0, 10.0, -10.0, 20.0, -20.0}; + auto points = Curve_Utils::build_pixel_envelope_points(mapping, Range{0.0, 4.0}, 5, 5, false, [&samples](int i) { + return samples[static_cast(i)]; + }); + ASSERT_FALSE(points.empty()); + double previous = -1.0; + for (const PointF& point : points) { + double coord = mapping.domain.pixel_to_coord(point.x); + EXPECT_GE(coord, previous); + previous = coord; + } +} TEST(Renderive_Frame_Feedback, SelectsRenderPaintAndUserSources) { Frame_Feedback_Controller controller; controller.set_max_render_fps(200.0); @@ -416,6 +525,55 @@ TEST(Renderive_Render_Executor, QueuedFramesRunRoundRobinByPlot) { EXPECT_EQ(order[2], 3); executor.shutdown(); } +TEST(Renderive_Render_Executor, ShutdownCompletesAcceptedQueuedFrameAsCancelled) { + Render_Executor executor(Render_Runtime_Config{1}); + std::atomic_bool first_entered{false}; + std::atomic_bool release_first{false}; + std::atomic_bool queued_work_ran{false}; + std::atomic_bool queued_completed{false}; + ASSERT_TRUE(executor.try_submit(Render_Executor_Task{ + 1, + 1, + Render_Task_Kind::Frame, + Task([&]() { + first_entered.store(true, std::memory_order_release); + while (!release_first.load(std::memory_order_acquire)) + std::this_thread::sleep_for(std::chrono::milliseconds(1)); + }), + {}, + Task([]() {}), + std::make_shared(), + 1, + true + })); + ASSERT_TRUE(wait_until([&]() { + return first_entered.load(std::memory_order_acquire); + })); + ASSERT_TRUE(executor.try_submit(Render_Executor_Task{ + 2, + 1, + Render_Task_Kind::Frame, + Task([&]() { + queued_work_ran.store(true, std::memory_order_release); + }), + {}, + Task([&]() { + queued_completed.store(true, std::memory_order_release); + }), + std::make_shared(), + 1, + true + })); + std::thread shutdown_thread([&]() { + executor.shutdown(); + }); + ASSERT_TRUE(wait_until([&]() { + return queued_completed.load(std::memory_order_acquire); + })); + EXPECT_FALSE(queued_work_ran.load(std::memory_order_acquire)); + release_first.store(true, std::memory_order_release); + shutdown_thread.join(); +} TEST(Renderive_Performance_Shower, ToggleDoesNotReenterRenderableTreeUnsafely) { Plot_Core plot(std::make_unique()); plot.init();