From dda25f903897871bf6ec26208c594a0303a82887 Mon Sep 17 00:00:00 2001 From: wyc <1104749580@qq.com> Date: Sun, 2 Aug 2026 14:57:24 +0800 Subject: [PATCH] =?UTF-8?q?=E7=AD=96=E7=95=A5=E6=8A=BD=E7=A6=BB?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- AGENTS.md | 1 + Core/Axis/Time_Axis.cpp | 16 +- Core/Axis/Time_Axis_p.h | 1 + Core/architecture/Render_Data.h | 259 ++++++++++++++++++ Core/architecture/Renderable.cpp | 31 +-- Core/architecture/Renderable.h | 11 +- Core/flow/Present_Surface_Lease.cpp | 51 +++- Core/flow/Present_Surface_Lease.h | 63 ++++- Core/flow/explicit/Explicit_Frame_Flow.cpp | 26 +- Core/flow/explicit/Explicit_Frame_Flow.h | 3 +- .../low_latency/Low_Latency_Frame_Flow.cpp | 12 +- .../low_latency/Low_Latency_Frame_Pipeline.h | 7 + Core/flow/low_latency/Render_Data.cpp | 110 +++++++- Core/flow/low_latency/Render_Data.h | 204 ++------------ Core/flow/low_latency/Render_Lease.h | 17 +- Core/plottable/Constellation_Diagram.cpp | 12 +- Core/plottable/Constellation_Diagram_p.h | 1 + Core/plottable/Frequency_Trace_p.h | 1 + Core/plottable/Hover_Tooltip.h | 126 +++++---- Core/plottable/Interpolation_p.h | 48 +++- Core/plottable/Performance_Overlay.h | 2 +- Core/plottable/Performance_Overlay_p.h | 2 +- .../plottable/Selection_Rectangle_Overlay.cpp | 10 +- Core/plottable/Spectrum.cpp | 13 +- Core/plottable/Waterfall_p.h | 1 + Core/primitive/Color_Bar.cpp | 1 + Core/primitive/Curve.cpp | 111 ++++++-- test/Renderive_Core_Tests.cpp | 67 ++++- 28 files changed, 849 insertions(+), 358 deletions(-) create mode 100644 Core/architecture/Render_Data.h diff --git a/AGENTS.md b/AGENTS.md index 8e0b2a5..ddb9209 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -1,5 +1,6 @@ ## 设计约束 +0. 跟你审计报告 就要大刀阔斧的改 一次性先把问题一次改完 在批量检查 要大步前进 1. 每个状态只能有一个权威来源,禁止在不同对象中重复保存并手工同步。 2. 能通过计算、查询或快照得到的数据,不要保存为成员变量。 3. 禁止增加只做值转发的成员、getter、setter 和兼容中间层。 diff --git a/Core/Axis/Time_Axis.cpp b/Core/Axis/Time_Axis.cpp index 4c25e25..2bcc744 100644 --- a/Core/Axis/Time_Axis.cpp +++ b/Core/Axis/Time_Axis.cpp @@ -40,9 +40,21 @@ int Time_Axis::append_time(Time_Of_Day time) { return tick; } Time_Of_Day Time_Axis::tick_to_time(int tick) { - auto stream = timeline_stream(); + std::shared_ptr stream; + int point_count = 1; + int interval_hint = 1; + bool newest_at_start = false; + { + Render_State_Lease state(d()); + if (state) { + stream = state->timeline_stream; + point_count = state->visible_time_point_count; + interval_hint = state->label_tick_interval_hint(); + newest_at_start = state->newest_at_axis_start; + } + } if (stream) - return stream->capture(visible_time_point_count(), d()->edit_state()->label_tick_interval_hint(), newest_at_axis_start()).time_by_tick(tick); + return stream->capture(point_count, interval_hint, newest_at_start).time_by_tick(tick); return d()->time_by_tick(tick); } Time_Axis::Builder::Builder(std::shared_ptr parent, Orientation orientation) { diff --git a/Core/Axis/Time_Axis_p.h b/Core/Axis/Time_Axis_p.h index 7b23859..73098a2 100644 --- a/Core/Axis/Time_Axis_p.h +++ b/Core/Axis/Time_Axis_p.h @@ -9,6 +9,7 @@ #include "../base/String_Format.h" #include "../base/Time_Of_Day.h" #include "../render/Canvas.h" +#include "../render/Render_Frame_Snapshot.h" #include "Abs_Axis_p.h" #include "Time_Axis.h" namespace renderive { diff --git a/Core/architecture/Render_Data.h b/Core/architecture/Render_Data.h new file mode 100644 index 0000000..36c34b6 --- /dev/null +++ b/Core/architecture/Render_Data.h @@ -0,0 +1,259 @@ +#pragma once +#include "../base/Geometry.h" +#include "../base/Memory.h" +#include "../base/Object_Semantics.h" +#include "../event/Event.h" +#include "../event/Key_Event.h" +#include "../event/Pointer_Event.h" +#include "../event/Resize_Event.h" +#include "../event/Wheel_Event.h" +#include "Update_Completion.h" +#include "global.h" +#include +#include +#include +#include +#include +#include + +namespace renderive { + +class Canvas; +struct Render_Frame_Snapshot; +struct Renderable; + +struct Input_Data { + virtual ~Input_Data() = default; + virtual void clear() {} +}; + +struct Latest_Input_Data : Input_Data { + bool pending = false; + + void mark_latest_pushed() { + pending = true; + } + + void clear_latest() { + pending = false; + } +}; + +struct Render_State { + virtual ~Render_State() = default; + std::uint64_t version = 0; +}; + +struct Hit_Testable { + virtual ~Hit_Testable() = default; + virtual bool select_test(const PointF& pos) = 0; +}; + +struct Pointer_Interactive { + virtual ~Pointer_Interactive() = default; + virtual void mouse_press_event(const Pointer_Event& event) = 0; + virtual void mouse_move_event(const Pointer_Event& event) = 0; + virtual void mouse_release_event(const Pointer_Event& event) = 0; +}; + +struct Wheel_Interactive { + virtual ~Wheel_Interactive() = default; + virtual void wheel_event(const Wheel_Event& event) = 0; +}; + +struct Key_Press_Interactive { + virtual ~Key_Press_Interactive() = default; + virtual void key_press_event(const Key_Event& event) = 0; +}; + +struct Key_Release_Interactive { + virtual ~Key_Release_Interactive() = default; + virtual void key_release_event(const Key_Event& event) = 0; +}; + +struct Hover_Interactive { + virtual ~Hover_Interactive() = default; + virtual void set_hover_state(Point pos, bool active) = 0; +}; + +struct Render_Object_Owner : Move_Only { + Render_Object_Owner() noexcept = default; + + Render_Object_Owner(void* pointer, std::pmr::memory_resource* resource, void (*destroy)(void*, std::pmr::memory_resource*)) noexcept + : pointer(pointer), resource(resource), destroy(destroy) {} + + Render_Object_Owner(Render_Object_Owner&& other) noexcept + : pointer(std::exchange(other.pointer, nullptr)), + resource(std::exchange(other.resource, nullptr)), + destroy(std::exchange(other.destroy, nullptr)) {} + + Render_Object_Owner& operator=(Render_Object_Owner&& other) noexcept { + if (this == &other) + return *this; + reset(); + pointer = std::exchange(other.pointer, nullptr); + resource = std::exchange(other.resource, nullptr); + destroy = std::exchange(other.destroy, nullptr); + return *this; + } + + ~Render_Object_Owner() { + reset(); + } + + template + T* get() const { + return static_cast(pointer); + } + + void reset() noexcept { + if (!pointer) + return; + destroy(pointer, resource); + pointer = nullptr; + resource = nullptr; + destroy = nullptr; + } + + explicit operator bool() const noexcept { + return pointer != nullptr; + } + + void* pointer{}; + std::pmr::memory_resource* resource{}; + void (*destroy)(void*, std::pmr::memory_resource*){}; +}; + +template +Render_Object_Owner make_render_object(Args&&... args) { + std::pmr::memory_resource* resource = memory_resource(Memory_Domain::Renderable); + std::pmr::polymorphic_allocator allocator(resource); + T* pointer = allocator.allocate(1); + try { + std::construct_at(pointer, std::forward(args)...); + } + catch (...) { + allocator.deallocate(pointer, 1); + throw; + } + return Render_Object_Owner(pointer, resource, [](void* object, std::pmr::memory_resource* resource) { + std::pmr::polymorphic_allocator allocator(resource); + std::destroy_at(static_cast(object)); + allocator.deallocate(static_cast(object), 1); + }); +} + +struct Render_State_Read_Handle { + virtual ~Render_State_Read_Handle() = default; +}; + +struct Render_State_Edit_Handle { + virtual ~Render_State_Edit_Handle() = default; +}; + +struct Render_State_Read_Guard : Move_Only { + Render_State_Read_Guard() = default; + Render_State_Read_Guard(Render_State* state, std::unique_ptr handle) + : state(state), handle(std::move(handle)) {} + Render_State_Read_Guard(Render_State_Read_Guard&&) noexcept = default; + Render_State_Read_Guard& operator=(Render_State_Read_Guard&&) noexcept = default; + ~Render_State_Read_Guard() = default; + + explicit operator bool() const noexcept { + return state && handle; + } + + Render_State* state{}; + std::unique_ptr handle; +}; + +struct Render_State_Edit_Guard : Move_Only { + Render_State_Edit_Guard() = default; + Render_State_Edit_Guard(Render_State* state, std::unique_ptr handle) + : state(state), handle(std::move(handle)) {} + Render_State_Edit_Guard(Render_State_Edit_Guard&&) noexcept = default; + Render_State_Edit_Guard& operator=(Render_State_Edit_Guard&&) noexcept = default; + ~Render_State_Edit_Guard() = default; + + explicit operator bool() const noexcept { + return state && handle; + } + + Render_State* state{}; + std::unique_ptr handle; +}; + +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 cancel_pending_completions() = 0; + virtual void drain_committed_update_states(std::pmr::vector>& output) = 0; + virtual Render_State_Read_Guard acquire_render_state_read() = 0; + virtual Render_State_Read_Guard acquire_edit_state_read() = 0; + virtual Render_State_Edit_Guard acquire_edit_state() = 0; + virtual void sync_state_pipeline() = 0; + virtual Render_State* render_state() = 0; + virtual Render_State* ready_state() = 0; + virtual Render_State* edit_state() = 0; + virtual Input_Data* render_input_data() = 0; + virtual Input_Data* edit_input_data() = 0; + virtual ~Render_Data() = default; + virtual void draw(Canvas& canvas, const Render_Frame_Snapshot& snapshot) {} + virtual void prepare_data(const Render_Frame_Snapshot& snapshot) { + sync_state_pipeline(); + } + virtual Rect pixel_bounds(const Size& plot_size) { + return {0, 0, plot_size.width, plot_size.height}; + } +}; + +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 cancel_pending_completions() override {} + void drain_committed_update_states(std::pmr::vector>&) override {} + Render_State_Read_Guard acquire_render_state_read() override { + return {}; + } + Render_State_Read_Guard acquire_edit_state_read() override { + return {}; + } + Render_State_Edit_Guard acquire_edit_state() override { + return {}; + } + Render_State* render_state() override { + return nullptr; + } + Render_State* ready_state() override { + return nullptr; + } + Render_State* edit_state() override { + return nullptr; + } + Input_Data* render_input_data() override { + return nullptr; + } + Input_Data* edit_input_data() override { + return nullptr; + } + void sync_state_pipeline() override {} +}; + +template +concept Render_State_Type = std::derived_from && std::is_copy_assignable_v; + +template +concept Input_Data_Type = std::derived_from; + +template +concept Render_Data_Type = std::derived_from; + +} // namespace renderive diff --git a/Core/architecture/Renderable.cpp b/Core/architecture/Renderable.cpp index 7da1aa6..ea0b66f 100644 --- a/Core/architecture/Renderable.cpp +++ b/Core/architecture/Renderable.cpp @@ -22,19 +22,23 @@ const std::string& Renderable::object_name() const { return object_name_; } 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"); + return; + } object_name_ = std::move(name); } bool Renderable::is_visible() const { - return visible_; + return visible_.load(std::memory_order_acquire); } void Renderable::set_visible(bool value) { - if (visible_ == value) + bool old = visible_.exchange(value, std::memory_order_acq_rel); + if (old == value) return; - visible_ = value; mark_render_dirty(); } bool Renderable::should_prepare() const { - return !get_retiring() && visible_ && d_ptr; + return !get_retiring() && visible_.load(std::memory_order_acquire) && d_ptr; } void Renderable::detach_from_parent() { if (auto parent = parent_renderable.lock()) @@ -107,13 +111,7 @@ void Renderable::init_common(Plot_Core* plot) { d_ptr = d_owner.get(); } d_ptr->q_ptr = this; - d_ptr->initialize_runtime_buffers( - create_render_state(), - create_render_state(), - create_render_state(), - create_input_data(), - create_input_data(), - create_input_data()); + d_ptr->initialize_runtime(*this); plot->notify_model_dirty(); } void Renderable::add_child(const std::shared_ptr& child) { @@ -183,7 +181,7 @@ std::vector> Renderable::hit_renderables(const Point return hits; } void Renderable::append_hit_renderables(std::vector>& hits, const PointF& pos) { - if (get_retiring() || !visible_) + if (get_retiring() || !visible_.load(std::memory_order_acquire)) return; std::vector> children = children_snapshot(); for (int i = static_cast(children.size()) - 1; i >= 0; --i) { @@ -195,7 +193,7 @@ void Renderable::append_hit_renderables(std::vector> hits.push_back(shared_from_this()); } void Renderable::render(Canvas& canvas, const Render_Frame_Snapshot& snapshot) { - if (get_retiring() || !visible_) + if (get_retiring() || !visible_.load(std::memory_order_acquire)) return; Renderable_Cache_Mode mode = get_cache_mode(); std::uint64_t begin_time = steady_now_ns(); @@ -260,7 +258,7 @@ std::size_t Renderable::render_tree_node(Canvas& canvas, const Render_Frame_Snap return std::max(index + 1, node.subtree_end); if (current != this) return current->render_tree_node(canvas, snapshot, tree, index); - if (get_retiring() || !visible_) + if (get_retiring() || !visible_.load(std::memory_order_acquire)) return std::max(index + 1, node.subtree_end); Renderable_Cache_Mode mode = get_cache_mode(); std::uint64_t begin_time = steady_now_ns(); @@ -350,14 +348,13 @@ void Renderable::render_self(Canvas& canvas, const Render_Frame_Snapshot& snapsh void Renderable::render_self(Canvas& canvas, const Render_Frame_Snapshot& snapshot, const Renderable_Frame_Node* node) { if (!d_ptr) return; - auto lease = d_ptr->try_mark_render_state(); - if (!lease) + auto guard = d_ptr->acquire_render_state_read(); + if (!guard) return; Renderable_Cache_Mode mode = get_cache_mode(); std::uint64_t begin_time = steady_now_ns(); draw(canvas, snapshot); std::uint64_t draw_time = steady_now_ns() - begin_time; - d_ptr->unmark_state(lease); 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(); diff --git a/Core/architecture/Renderable.h b/Core/architecture/Renderable.h index d43d195..b01d09b 100644 --- a/Core/architecture/Renderable.h +++ b/Core/architecture/Renderable.h @@ -10,8 +10,8 @@ #include "../render/Canvas.h" #include "../render/Image.h" #include "../render/Renderable_Frame_Node.h" +#include "Render_Data.h" #include "Render_Config.h" -#include "../flow/low_latency/Render_Data.h" #include #include #include @@ -24,12 +24,7 @@ namespace renderive { class Plot_Core; class Renderable; -template -concept Render_State_Type = std::derived_from && std::is_copy_assignable_v; -template -concept Input_Data_Type = std::derived_from; -template -concept Render_Data_Type = std::derived_from; +struct Plot_Render_Context; template struct Renderable_Binding; template @@ -94,7 +89,7 @@ private: Plot_Core* plot{}; std::string object_name_; std::weak_ptr parent_renderable; - bool visible_ = true; + std::atomic_bool visible_{true}; std::vector> children; mutable std::mutex children_mutex; std::weak_ptr plot_context; diff --git a/Core/flow/Present_Surface_Lease.cpp b/Core/flow/Present_Surface_Lease.cpp index 757dba4..2c80726 100644 --- a/Core/flow/Present_Surface_Lease.cpp +++ b/Core/flow/Present_Surface_Lease.cpp @@ -3,9 +3,22 @@ namespace renderive { Present_Surface_Lease::Present_Surface_Lease() = default; -Present_Surface_Lease::Present_Surface_Lease(Present_Surface_Lease&&) noexcept = default; -Present_Surface_Lease& Present_Surface_Lease::operator=(Present_Surface_Lease&&) noexcept = default; -Present_Surface_Lease::~Present_Surface_Lease() = default; + +Present_Surface_Lease::Present_Surface_Lease(Present_Surface_Lease&& other) noexcept { + move_from(std::move(other)); +} + +Present_Surface_Lease& Present_Surface_Lease::operator=(Present_Surface_Lease&& other) noexcept { + if (this == &other) + return *this; + reset(); + move_from(std::move(other)); + return *this; +} + +Present_Surface_Lease::~Present_Surface_Lease() { + reset(); +} Present_Surface_Lease::operator bool() const { return output() != nullptr; @@ -26,22 +39,46 @@ Image_View Present_Surface_Lease::image() const { return current->image.view(); } -Present_Surface_Lease Present_Surface_Lease::from_handle(std::unique_ptr handle, bool is_new_frame, bool update_was_pending) { +Present_Surface_Lease Present_Surface_Lease::empty(bool is_new_frame, bool update_was_pending) { Present_Surface_Lease lease; - lease.handle_ = std::move(handle); lease.is_new_frame = is_new_frame; lease.update_was_pending = update_was_pending; return lease; } Render_Output* Present_Surface_Lease::output() const { - return handle_ ? handle_->output() : nullptr; + return output_ ? output_(&storage_) : nullptr; } void Present_Surface_Lease::reset() { - handle_.reset(); + if (destroy_) + destroy_(&storage_); + output_ = nullptr; + destroy_ = nullptr; + move_ = nullptr; is_new_frame = false; update_was_pending = false; } +void Present_Surface_Lease::move_from(Present_Surface_Lease&& other) noexcept { + if (!other.move_) { + is_new_frame = other.is_new_frame; + update_was_pending = other.update_was_pending; + other.is_new_frame = false; + other.update_was_pending = false; + return; + } + other.move_(&storage_, &other.storage_); + output_ = other.output_; + destroy_ = other.destroy_; + move_ = other.move_; + is_new_frame = other.is_new_frame; + update_was_pending = other.update_was_pending; + other.output_ = nullptr; + other.destroy_ = nullptr; + other.move_ = nullptr; + other.is_new_frame = false; + other.update_was_pending = false; +} + } // namespace renderive diff --git a/Core/flow/Present_Surface_Lease.h b/Core/flow/Present_Surface_Lease.h index 478f47f..67abdbc 100644 --- a/Core/flow/Present_Surface_Lease.h +++ b/Core/flow/Present_Surface_Lease.h @@ -1,24 +1,22 @@ #pragma once -#include "../render/Render_Output.h" #include "../render/Image_View.h" +#include "../render/Render_Output.h" +#include #include #include +#include +#include #include namespace renderive { struct Present_Surface_Lease_Access; -struct Present_Surface_Handle { - virtual ~Present_Surface_Handle() = default; - [[nodiscard]] virtual Render_Output* output() const = 0; -}; - class Present_Surface_Lease { public: Present_Surface_Lease(); - Present_Surface_Lease(Present_Surface_Lease&&) noexcept; - Present_Surface_Lease& operator=(Present_Surface_Lease&&) noexcept; + Present_Surface_Lease(Present_Surface_Lease&& other) noexcept; + Present_Surface_Lease& operator=(Present_Surface_Lease&& other) noexcept; ~Present_Surface_Lease(); [[nodiscard]] explicit operator bool() const; @@ -28,21 +26,62 @@ public: private: friend struct Present_Surface_Lease_Access; - static Present_Surface_Lease from_handle(std::unique_ptr handle, bool is_new_frame, bool update_was_pending); + + static constexpr std::size_t Storage_Size = 96; + static constexpr std::size_t Storage_Align = alignof(std::max_align_t); + using Storage = std::aligned_storage_t; + using Output_Fn = Render_Output* (*)(const void*); + using Destroy_Fn = void (*)(void*); + using Move_Fn = void (*)(void*, void*); + + template + static Present_Surface_Lease from_handle(bool is_new_frame, bool update_was_pending, Args&&... args) { + static_assert(sizeof(Handle) <= Storage_Size, "Present surface handle exceeds inline lease storage"); + static_assert(alignof(Handle) <= Storage_Align, "Present surface handle exceeds inline lease alignment"); + Present_Surface_Lease lease; + std::construct_at(reinterpret_cast(&lease.storage_), std::forward(args)...); + lease.output_ = [](const void* storage) -> Render_Output* { + return reinterpret_cast(storage)->output(); + }; + lease.destroy_ = [](void* storage) { + std::destroy_at(reinterpret_cast(storage)); + }; + lease.move_ = [](void* destination, void* source) { + std::construct_at(reinterpret_cast(destination), std::move(*reinterpret_cast(source))); + std::destroy_at(reinterpret_cast(source)); + }; + lease.is_new_frame = is_new_frame; + lease.update_was_pending = update_was_pending; + return lease; + } + + static Present_Surface_Lease empty(bool is_new_frame, bool update_was_pending); [[nodiscard]] Render_Output* output() const; void reset(); - std::unique_ptr handle_; + void move_from(Present_Surface_Lease&& other) noexcept; + + Storage storage_{}; + Output_Fn output_{}; + Destroy_Fn destroy_{}; + Move_Fn move_{}; bool is_new_frame{}; bool update_was_pending{}; }; struct Present_Surface_Lease_Access { - static Present_Surface_Lease from_handle(std::unique_ptr handle, bool is_new_frame, bool update_was_pending) { - return Present_Surface_Lease::from_handle(std::move(handle), is_new_frame, update_was_pending); + template + static Present_Surface_Lease from_handle(bool is_new_frame, bool update_was_pending, Args&&... args) { + return Present_Surface_Lease::from_handle(is_new_frame, update_was_pending, std::forward(args)...); } + + static Present_Surface_Lease empty(bool is_new_frame, bool update_was_pending) { + return Present_Surface_Lease::empty(is_new_frame, update_was_pending); + } + static Render_Output* output(const Present_Surface_Lease& lease) { return lease.output(); } + static void reset(Present_Surface_Lease& lease) { lease.reset(); } diff --git a/Core/flow/explicit/Explicit_Frame_Flow.cpp b/Core/flow/explicit/Explicit_Frame_Flow.cpp index 67e96ce..e3caf36 100644 --- a/Core/flow/explicit/Explicit_Frame_Flow.cpp +++ b/Core/flow/explicit/Explicit_Frame_Flow.cpp @@ -10,10 +10,10 @@ struct Explicit_Render_Surface_Handle final : Render_Frame_Surface_Handle { std::shared_ptr output; }; -struct Explicit_Present_Surface_Handle final : Present_Surface_Handle { +struct Explicit_Present_Surface_Handle final { explicit Explicit_Present_Surface_Handle(std::shared_ptr output) : output_value(std::move(output)) {} - [[nodiscard]] Render_Output* output() const override { + [[nodiscard]] Render_Output* output() const { return output_value.get(); } @@ -110,15 +110,15 @@ Render_Surface_Publish_Result Explicit_Frame_Flow::publish_rendered_frame(Render result.update_request_id = handle->output->metadata->update_request_id; result.update_request_time = now_ns; } + std::shared_ptr output = std::move(handle->output); + Size size = output ? output->image.size() : Size{}; { std::lock_guard lock(surface_mutex); - present_surface = std::move(handle->output); + present_surface = output; } - rendered_version = surface.input_version; frame_ready.store(true, std::memory_order_release); paint_request_pending.store(true, std::memory_order_release); render_job_active.store(false, std::memory_order_release); - Size size = present_surface ? present_surface->image.size() : Size{}; result.dirty_rect = Rect{0, 0, size.width, size.height}; result.request_present = true; return result; @@ -161,10 +161,10 @@ Present_Surface_Lease Explicit_Frame_Flow::begin_present(std::uint64_t now_ns, s output->metadata->transition_23_ns = now_ns; output->metadata->present_queue_wait_ns = now_ns > output->metadata->transition_12_ns ? now_ns - output->metadata->transition_12_ns : 0; } - return Present_Surface_Lease_Access::from_handle( - std::make_unique(std::move(output)), + return Present_Surface_Lease_Access::from_handle( is_new, - pending); + pending, + std::move(output)); } std::shared_ptr Explicit_Frame_Flow::end_present(Present_Surface_Lease&& surface, std::uint64_t paint_begin_ns, std::uint64_t paint_end_ns) { @@ -185,16 +185,16 @@ std::shared_ptr Explicit_Frame_Flow::end_present(Present } void Explicit_Frame_Flow::activate_view(std::uint64_t now_ns) { - active.store(true, std::memory_order_release); + view_active_state.store(true, std::memory_order_release); evaluate(now_ns); } void Explicit_Frame_Flow::deactivate_view() { - active.store(false, std::memory_order_release); + view_active_state.store(false, std::memory_order_release); } bool Explicit_Frame_Flow::view_active() const { - return active.load(std::memory_order_acquire); + return view_active_state.load(std::memory_order_acquire); } void Explicit_Frame_Flow::notify_model_dirty(std::uint64_t now_ns) { @@ -232,7 +232,7 @@ void Explicit_Frame_Flow::drain_frame_update_states(std::pmr::vector lock(request_mutex); return Explicit_Diagnostics{ - active.load(std::memory_order_acquire), + view_active_state.load(std::memory_order_acquire), static_cast(pending_requests.size()) }; } @@ -248,7 +248,7 @@ Flow_Diagnostics Explicit_Frame_Flow::diagnostics() const { void Explicit_Frame_Flow::shutdown() { host = nullptr; cancel_all_update_states(); - active.store(false, std::memory_order_release); + view_active_state.store(false, std::memory_order_release); manual_request_pending.store(false, std::memory_order_release); render_job_active.store(false, std::memory_order_release); frame_ready.store(false, std::memory_order_release); diff --git a/Core/flow/explicit/Explicit_Frame_Flow.h b/Core/flow/explicit/Explicit_Frame_Flow.h index 693ece2..0a2968e 100644 --- a/Core/flow/explicit/Explicit_Frame_Flow.h +++ b/Core/flow/explicit/Explicit_Frame_Flow.h @@ -40,7 +40,7 @@ public: private: void evaluate(std::uint64_t now_ns); Frame_Flow_Host* host{}; - std::atomic_bool active{false}; + std::atomic_bool view_active_state{false}; std::atomic_bool manual_request_pending{false}; std::atomic_bool render_job_active{false}; std::atomic_bool frame_ready{false}; @@ -51,7 +51,6 @@ private: std::pmr::vector> pending_requests; std::shared_ptr present_surface; std::atomic edit_version{1}; - std::uint64_t rendered_version{}; std::uint64_t next_present_request_id{}; }; diff --git a/Core/flow/low_latency/Low_Latency_Frame_Flow.cpp b/Core/flow/low_latency/Low_Latency_Frame_Flow.cpp index d364032..2e61d7b 100644 --- a/Core/flow/low_latency/Low_Latency_Frame_Flow.cpp +++ b/Core/flow/low_latency/Low_Latency_Frame_Flow.cpp @@ -12,10 +12,10 @@ struct Low_Latency_Render_Surface_Handle final : Render_Frame_Surface_Handle { std::uint64_t previous_rendered_edit_version{}; }; -struct Low_Latency_Present_Surface_Handle final : Present_Surface_Handle { +struct Low_Latency_Present_Surface_Handle final { explicit Low_Latency_Present_Surface_Handle(Low_Latency_Frame_Lease lease) : lease(std::move(lease)) {} - [[nodiscard]] Render_Output* output() const override { + [[nodiscard]] Render_Output* output() const { return lease.frame; } @@ -182,11 +182,11 @@ Present_Surface_Lease Low_Latency_Frame_Flow::begin_present(std::uint64_t now_ns auto consumed = d->pipeline.try_acquire_paint_frame(now_ns); returned_frame_record = std::move(consumed.returned_frame_record); if (!consumed.lease) - return Present_Surface_Lease_Access::from_handle({}, consumed.has_new_frame, consumed.present_request_was_pending); - return Present_Surface_Lease_Access::from_handle( - std::make_unique(std::move(consumed.lease)), + return Present_Surface_Lease_Access::empty(consumed.has_new_frame, consumed.present_request_was_pending); + return Present_Surface_Lease_Access::from_handle( consumed.has_new_frame, - consumed.present_request_was_pending); + consumed.present_request_was_pending, + std::move(consumed.lease)); } std::shared_ptr Low_Latency_Frame_Flow::end_present(Present_Surface_Lease&& surface, std::uint64_t paint_begin_ns, std::uint64_t paint_end_ns) { diff --git a/Core/flow/low_latency/Low_Latency_Frame_Pipeline.h b/Core/flow/low_latency/Low_Latency_Frame_Pipeline.h index 4950c57..5b60f0f 100644 --- a/Core/flow/low_latency/Low_Latency_Frame_Pipeline.h +++ b/Core/flow/low_latency/Low_Latency_Frame_Pipeline.h @@ -14,6 +14,8 @@ #include namespace renderive { struct Update_State; +class Low_Latency_Frame_Flow; +struct Low_Latency_Frame_Pipeline_Test_Probe; enum class Frame_Job_State { Idle, Running @@ -68,6 +70,11 @@ public: [[nodiscard]] bool middle_frame_ready() const; [[nodiscard]] bool painting_frame_in_use() const; void cancel_all_update_states(); + +private: + friend class Low_Latency_Frame_Flow; + friend struct Low_Latency_Frame_Pipeline_Test_Probe; + Triple_Role_Buffer_Control frame_control; std::array frames; std::atomic edit_version{1}; diff --git a/Core/flow/low_latency/Render_Data.cpp b/Core/flow/low_latency/Render_Data.cpp index ed6ec58..5add672 100644 --- a/Core/flow/low_latency/Render_Data.cpp +++ b/Core/flow/low_latency/Render_Data.cpp @@ -7,7 +7,43 @@ std::ostream& operator<<(std::ostream& stream, const Range& range) { stream << "Range(" << range.origin << ", " << range.target << ")"; return stream; } -Render_Edit_Guard::Render_Edit_Guard(Render_Data* data, Triple_Buffer_Lease lease) : data(data), lease(lease) {} + +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) {} + + ~Low_Latency_State_Read_Handle() override { + if (data && lease) + data->unmark_state(lease); + } + + Low_Latency_Render_Data* data{}; + 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() override { + 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(); + } + + Low_Latency_Render_Data* data{}; + Triple_Buffer_Lease lease{}; +}; + +} // 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; @@ -17,25 +53,36 @@ Render_Edit_Guard::~Render_Edit_Guard() { if (q) q->mark_render_dirty(); } -void Render_Data::mark_input_dirty(Triple_Buffer_Lease lease, bool notify_render) { +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 Render_Data::set_input_completion(Triple_Buffer_Lease lease, std::shared_ptr state) { +void Low_Latency_Render_Data::set_input_completion(Triple_Buffer_Lease lease, std::shared_ptr state) { Input_Buffer_Slot& slot = input_slot_by_index(lease.index); if (slot.completion_state && slot.completion_state != state) slot.completion_state->complete_all({}, Update_Outcome::Superseded); slot.completion_state = std::move(state); } -void Render_Data::drain_committed_update_states(std::pmr::vector>& output) { +void Low_Latency_Render_Data::drain_committed_update_states(std::pmr::vector>& output) { for (auto& state : committed_update_states) output.push_back(std::move(state)); committed_update_states.clear(); } -void Render_Data::initialize_runtime_buffers( + +void Low_Latency_Render_Data::initialize_runtime(Renderable& owner) { + initialize_runtime_buffers( + owner.create_render_state(), + owner.create_render_state(), + owner.create_render_state(), + owner.create_input_data(), + owner.create_input_data(), + owner.create_input_data()); +} + +void Low_Latency_Render_Data::initialize_runtime_buffers( Render_Object_Owner edit_state, Render_Object_Owner ready_state, Render_Object_Owner render_state, @@ -62,7 +109,37 @@ void Render_Data::initialize_runtime_buffers( if (state_buffers[State_Edit]) state_buffers[State_Edit]->version = 1; } -void Render_Data::clear_input_role(std::uint8_t role) { +Render_State_Read_Guard Low_Latency_Render_Data::acquire_render_state_read() { + Triple_Buffer_Lease lease = try_mark_state_role(State_Render); + if (!lease) + return {}; + return Render_State_Read_Guard(state_by_index(lease.index), std::make_unique(this, lease)); +} + +Render_State_Read_Guard Low_Latency_Render_Data::acquire_edit_state_read() { + Triple_Buffer_Lease lease = mark_state_role(State_Edit); + if (!lease) + return {}; + return Render_State_Read_Guard(state_by_index(lease.index), std::make_unique(this, lease)); +} + +Render_State_Edit_Guard Low_Latency_Render_Data::acquire_edit_state() { + Triple_Buffer_Lease lease = mark_state_role(State_Edit); + if (!lease) + return {}; + return Render_State_Edit_Guard(state_by_index(lease.index), std::make_unique(this, 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(); +} + +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) { @@ -78,7 +155,21 @@ void Render_Data::clear_input_role(std::uint8_t role) { slot.data->clear(); slot.pending = false; } -void Render_Data::cancel_pending_completions() { +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(); +} + +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); @@ -87,4 +178,9 @@ void Render_Data::cancel_pending_completions() { } cancel_update_states(committed_update_states); } + +Low_Latency_Render_Data::~Low_Latency_Render_Data() { + clear_state_buffers(); + clear_input_buffers(); +} } // namespace renderive diff --git a/Core/flow/low_latency/Render_Data.h b/Core/flow/low_latency/Render_Data.h index 398b7ad..9712974 100644 --- a/Core/flow/low_latency/Render_Data.h +++ b/Core/flow/low_latency/Render_Data.h @@ -1,17 +1,7 @@ #pragma once #include "../../architecture/Buffer_Role.h" -#include "../../architecture/Plot_Render_Context.h" -#include "../../architecture/Render_Config.h" +#include "../../architecture/Render_Data.h" #include "../../architecture/Update_Completion.h" -#include "../../architecture/global.h" -#include "../../base/Geometry.h" -#include "../../base/Object_Semantics.h" -#include "../../event/Event.h" -#include "../../event/Key_Event.h" -#include "../../event/Pointer_Event.h" -#include "../../event/Resize_Event.h" -#include "../../event/Wheel_Event.h" -#include "../../render/Canvas.h" #include "Low_Latency_Roles.h" #include "Triple_Buffer.h" #include @@ -19,115 +9,17 @@ #include #include #include -#include -#include + namespace renderive { -struct Render_Data; -struct Renderable; -struct Input_Data { - virtual ~Input_Data() = default; - virtual void clear() {} -}; -struct Latest_Input_Data : Input_Data { - bool pending = false; - void mark_latest_pushed() { - pending = true; - } - void clear_latest() { - pending = false; - } -}; -struct Render_State { - virtual ~Render_State() = default; - std::uint64_t version = 0; -}; -struct Hit_Testable { - virtual ~Hit_Testable() = default; - virtual bool select_test(const PointF& pos) = 0; -}; -struct Pointer_Interactive { - virtual ~Pointer_Interactive() = default; - virtual void mouse_press_event(const Pointer_Event& event) = 0; - virtual void mouse_move_event(const Pointer_Event& event) = 0; - virtual void mouse_release_event(const Pointer_Event& event) = 0; -}; -struct Wheel_Interactive { - virtual ~Wheel_Interactive() = default; - virtual void wheel_event(const Wheel_Event& event) = 0; -}; -struct Key_Press_Interactive { - virtual ~Key_Press_Interactive() = default; - virtual void key_press_event(const Key_Event& event) = 0; -}; -struct Key_Release_Interactive { - virtual ~Key_Release_Interactive() = default; - virtual void key_release_event(const Key_Event& event) = 0; -}; -struct Hover_Interactive { - virtual ~Hover_Interactive() = default; - virtual void set_hover_state(Point pos, bool active) = 0; -}; -struct Render_Object_Owner : Move_Only { - Render_Object_Owner() noexcept = default; - Render_Object_Owner(void* pointer, std::pmr::memory_resource* resource, void (*destroy)(void*, std::pmr::memory_resource*)) noexcept : pointer(pointer), resource(resource), destroy(destroy) {} - Render_Object_Owner(Render_Object_Owner&& other) noexcept : pointer(std::exchange(other.pointer, nullptr)), resource(std::exchange(other.resource, nullptr)), destroy(std::exchange(other.destroy, nullptr)) {} - Render_Object_Owner& operator=(Render_Object_Owner&& other) noexcept { - if (this == &other) - return *this; - reset(); - pointer = std::exchange(other.pointer, nullptr); - resource = std::exchange(other.resource, nullptr); - destroy = std::exchange(other.destroy, nullptr); - return *this; - } - ~Render_Object_Owner() { - reset(); - } - template - T* get() const { - return static_cast(pointer); - } - void reset() noexcept { - if (!pointer) - return; - destroy(pointer, resource); - pointer = nullptr; - resource = nullptr; - destroy = nullptr; - } - explicit operator bool() const noexcept { - return pointer != nullptr; - } - void* pointer{}; - std::pmr::memory_resource* resource{}; - void (*destroy)(void*, std::pmr::memory_resource*){}; -}; -template -Render_Object_Owner make_render_object(Args&&... args) { - std::pmr::memory_resource* resource = memory_resource(Memory_Domain::Renderable); - std::pmr::polymorphic_allocator allocator(resource); - T* pointer = allocator.allocate(1); - try { - std::construct_at(pointer, std::forward(args)...); - } - catch (...) { - allocator.deallocate(pointer, 1); - throw; - } - return Render_Object_Owner(pointer, resource, [](void* object, std::pmr::memory_resource* resource) { - std::pmr::polymorphic_allocator allocator(resource); - std::destroy_at(static_cast(object)); - allocator.deallocate(static_cast(object), 1); - }); -} + struct Input_Buffer_Slot { Input_Data* data{}; std::uint64_t version = 0; bool pending = false; std::shared_ptr completion_state; }; -struct Render_Data { - Renderable* q_ptr{}; + +struct Low_Latency_Render_Data : Render_Data { Triple_Role_Buffer_Control state_control; std::array state_buffers{}; std::array state_buffer_owners{}; @@ -136,6 +28,7 @@ struct Render_Data { std::array input_buffer_owners{}; std::pmr::vector> committed_update_states{memory_resource(Memory_Domain::Update_Completion)}; std::uint64_t input_version = 0; + Render_State* state(std::uint8_t role) const { return state_by_index(state_control.read_view()[role]); } @@ -151,17 +44,16 @@ struct Render_Data { 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() { - 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_state_buffers(); Input_Data* input_data(std::uint8_t role) const { return input_slot(role).data; } @@ -182,14 +74,18 @@ struct Render_Data { } void mark_input_dirty(Triple_Buffer_Lease lease, bool notify_render = true); void set_input_completion(Triple_Buffer_Lease lease, std::shared_ptr state); - void drain_committed_update_states(std::pmr::vector>& output); + void drain_committed_update_states(std::pmr::vector>& output) override; + void initialize_runtime(Renderable& owner) override; void initialize_runtime_buffers( Render_Object_Owner edit_state, Render_Object_Owner ready_state, Render_Object_Owner render_state, Render_Object_Owner edit_input, Render_Object_Owner ready_input, - Render_Object_Owner render_input); + 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); @@ -200,62 +96,22 @@ struct Render_Data { Triple_Buffer_Lease try_mark_edit_state() { return try_mark_state_role(State_Edit); } - 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 cancel_pending_completions(); -public: - virtual void sync_state_pipeline() = 0; - virtual Render_State* render_state() = 0; - virtual Render_State* ready_state() = 0; - virtual Render_State* edit_state() = 0; - virtual Input_Data* render_input_data() = 0; - virtual Input_Data* edit_input_data() = 0; - virtual ~Render_Data() { - clear_state_buffers(); - clear_input_buffers(); - } - virtual void draw(Canvas& canvas, const Render_Frame_Snapshot& snapshot) {} - virtual void prepare_data(const Render_Frame_Snapshot& snapshot) { - sync_state_pipeline(); - } - virtual Rect pixel_bounds(const Size& plot_size) { - return {0, 0, plot_size.width, plot_size.height}; + 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; }; -struct Base_Render_Data : Render_Data { - Render_State* render_state() override { - return nullptr; - } - Render_State* ready_state() override { - return nullptr; - } - Render_State* edit_state() override { - return nullptr; - } - Input_Data* render_input_data() override { - return nullptr; - } - Input_Data* edit_input_data() override { - return nullptr; - } - void sync_state_pipeline() override {} -}; + class Plot_Core; + struct LIB_DECL Render_Edit_Guard { - Render_Edit_Guard(Render_Data* data, Triple_Buffer_Lease lease); + Render_Edit_Guard(Low_Latency_Render_Data* data, Triple_Buffer_Lease lease); ~Render_Edit_Guard(); - Render_Data* data{}; + + Low_Latency_Render_Data* data{}; Triple_Buffer_Lease lease{}; }; + } // namespace renderive diff --git a/Core/flow/low_latency/Render_Lease.h b/Core/flow/low_latency/Render_Lease.h index 49ab057..b0fc122 100644 --- a/Core/flow/low_latency/Render_Lease.h +++ b/Core/flow/low_latency/Render_Lease.h @@ -1,10 +1,12 @@ #pragma once #include "../../architecture/Buffer_Role.h" #include "../../architecture/Renderable.h" +#include "Render_Data.h" #include "Low_Latency_Roles.h" #include namespace renderive { -template +template + requires std::derived_from struct Typed_Render_Data_Base : Base { Owner* q() { return reinterpret_cast(this->q_ptr); @@ -106,12 +108,12 @@ struct Typed_Render_Data_Base : Base { } }; template -using Typed_Render_Data = Typed_Render_Data_Base; +using Typed_Render_Data = Typed_Render_Data_Base; template struct Renderable_Binding { static void init(Owner* owner, const char* name) { owner->d_owner = make_render_object(); - owner->d_ptr = owner->d_owner.template get(); + owner->d_ptr = static_cast(owner->d_owner.template get()); owner->set_object_name(name); } static Data* d(Owner* owner) { @@ -146,7 +148,8 @@ struct Renderable_Binding { ::renderive::Render_Object_Owner Owner::create_input_data() { \ return Owner##_Binding::create_input_data(); \ } -template +template + requires std::derived_from struct Render_State_Lease : Immovable { explicit Render_State_Lease(Data* data) : data(data) { auto record = data->state_control.acquire_role(State_Edit); @@ -172,7 +175,8 @@ struct Render_State_Lease : Immovable { Triple_Buffer_Lease lease{}; State* state{}; }; -template +template + requires std::derived_from struct Render_Edit_Lease : Immovable { explicit Render_Edit_Lease(Data* data) : data(data) { auto record = data->state_control.acquire_role(State_Edit); @@ -203,7 +207,8 @@ struct Render_Edit_Lease : Immovable { Triple_Buffer_Lease lease{}; State* state{}; }; -template +template + requires std::derived_from struct Render_Input_Lease : Immovable { explicit Render_Input_Lease(Data* data, bool notify_render = true) : data(data), notify_render(notify_render) { auto record = data->input_control.acquire_role(Input_Edit); diff --git a/Core/plottable/Constellation_Diagram.cpp b/Core/plottable/Constellation_Diagram.cpp index c94f164..6f3e349 100644 --- a/Core/plottable/Constellation_Diagram.cpp +++ b/Core/plottable/Constellation_Diagram.cpp @@ -102,9 +102,15 @@ std::shared_ptr Constellation_Diagram::Builder::build() { return ret; } void Constellation_Diagram::fit_square_to_axes() { - Constellation_Diagram_State* s = reinterpret_cast(d()->edit_state()); - auto i_axis = s->i_axis.lock(); - auto q_axis = s->q_axis.lock(); + std::shared_ptr i_axis; + std::shared_ptr q_axis; + { + Render_State_Lease state(d()); + if (state) { + i_axis = state->i_axis.lock(); + q_axis = state->q_axis.lock(); + } + } if (!i_axis || !q_axis) return; double w = i_axis->pixel_length(), h = q_axis->pixel_length(); diff --git a/Core/plottable/Constellation_Diagram_p.h b/Core/plottable/Constellation_Diagram_p.h index 0934f56..703f7d0 100644 --- a/Core/plottable/Constellation_Diagram_p.h +++ b/Core/plottable/Constellation_Diagram_p.h @@ -14,6 +14,7 @@ #include "../base/Geometry.h" #include "../base/Style.h" #include "../render/Canvas.h" +#include "../render/Render_Frame_Snapshot.h" #include "Constellation_Diagram.h" namespace renderive { struct Constellation_Diagram_Data { diff --git a/Core/plottable/Frequency_Trace_p.h b/Core/plottable/Frequency_Trace_p.h index 628a499..becff4a 100644 --- a/Core/plottable/Frequency_Trace_p.h +++ b/Core/plottable/Frequency_Trace_p.h @@ -8,6 +8,7 @@ #include "../architecture/Renderable.h" #include "../base/Memory.h" #include "../primitive/Curve_Utils_p.h" +#include "../render/Render_Frame_Snapshot.h" #include "Psc_Cpp_Core/Base/RingBuffer.hpp" #include "Frequency_Trace.h" namespace renderive { diff --git a/Core/plottable/Hover_Tooltip.h b/Core/plottable/Hover_Tooltip.h index 0b3b9ca..f4f9043 100644 --- a/Core/plottable/Hover_Tooltip.h +++ b/Core/plottable/Hover_Tooltip.h @@ -4,6 +4,7 @@ #include #include #include "../Axis/export.h" +#include "../architecture/Render_Data.h" #include "../base/Color.h" #include "../base/Geometry.h" #include "../base/Style.h" @@ -32,44 +33,44 @@ struct LIB_DECL Hover_Tooltip_State { bool can_show_hover(Renderable* able) const; }; struct LIB_DECL Hover_Tooltip_Provider { - struct Hover_Edit_Guard { - Hover_Edit_Guard() = default; - Hover_Edit_Guard(Hover_Tooltip_Provider* owner, Hover_Tooltip_State* state, std::function on_finish) - : owner(owner), state(state), on_finish(std::move(on_finish)) {} - Hover_Edit_Guard(const Hover_Edit_Guard&) = delete; - Hover_Edit_Guard& operator=(const Hover_Edit_Guard&) = delete; - Hover_Edit_Guard(Hover_Edit_Guard&& other) noexcept - : owner(std::exchange(other.owner, nullptr)), - state(std::exchange(other.state, nullptr)), - on_finish(std::move(other.on_finish)) {} - Hover_Edit_Guard& operator=(Hover_Edit_Guard&& other) noexcept { - if (this == &other) - return *this; - finish(); - owner = std::exchange(other.owner, nullptr); - state = std::exchange(other.state, nullptr); - on_finish = std::move(other.on_finish); - return *this; - } - ~Hover_Edit_Guard() { - finish(); - } + struct Hover_Read_Guard { + Hover_Read_Guard() = default; + Hover_Read_Guard(Hover_Tooltip_State* state, Render_State_Read_Guard guard) + : state(state), guard(std::move(guard)) {} + Hover_Read_Guard(const Hover_Read_Guard&) = delete; + Hover_Read_Guard& operator=(const Hover_Read_Guard&) = delete; + Hover_Read_Guard(Hover_Read_Guard&&) noexcept = default; + Hover_Read_Guard& operator=(Hover_Read_Guard&&) noexcept = default; + ~Hover_Read_Guard() = default; Hover_Tooltip_State* operator->() const { return state; } - private: - void finish() { - if (!owner) - return; - if (on_finish) - on_finish(); - on_finish = {}; - owner = nullptr; - state = nullptr; + explicit operator bool() const { + return state && guard; } - Hover_Tooltip_Provider* owner{}; + Hover_Tooltip_State* state{}; - std::function on_finish{}; + Render_State_Read_Guard guard; + }; + + struct Hover_Edit_Guard { + Hover_Edit_Guard() = default; + Hover_Edit_Guard(Hover_Tooltip_State* state, Render_State_Edit_Guard guard) + : state(state), guard(std::move(guard)) {} + Hover_Edit_Guard(const Hover_Edit_Guard&) = delete; + Hover_Edit_Guard& operator=(const Hover_Edit_Guard&) = delete; + Hover_Edit_Guard(Hover_Edit_Guard&&) noexcept = default; + Hover_Edit_Guard& operator=(Hover_Edit_Guard&&) noexcept = default; + ~Hover_Edit_Guard() = default; + Hover_Tooltip_State* operator->() const { + return state; + } + explicit operator bool() const { + return state && guard; + } + + Hover_Tooltip_State* state{}; + Render_State_Edit_Guard guard; }; virtual std::vector build_hover_lines(Abs_Axis* h, Abs_Axis* v, Point pos); virtual ~Hover_Tooltip_Provider() = default; @@ -84,35 +85,51 @@ struct LIB_DECL Hover_Tooltip_Provider { return true; } bool hover_tooltip_enabled() { - return hover_edit_state()->use_hover_tooltip; + auto state = begin_hover_read(); + return state && state->use_hover_tooltip; } void set_use_hover_info(bool use) { auto edit = begin_hover_edit(); + if (!edit) + return; edit->use_hover_tooltip = use; } void set_hover_tooltip_font(const Font& font) { auto edit = begin_hover_edit(); + if (!edit) + return; edit->hover_info_font = font; } Font hover_tooltip_font() { - return hover_edit_state()->hover_info_font; + auto state = begin_hover_read(); + return state ? state->hover_info_font : Font{}; } Brush hover_info_background_brush() { - return hover_edit_state()->tooltip_background_brush; + auto state = begin_hover_read(); + return state ? state->tooltip_background_brush : Brush{}; } void set_hover_tooltip_background_brush(const Brush& brush) { auto edit = begin_hover_edit(); + if (!edit) + return; edit->tooltip_background_brush = brush.color.a != 0 ? brush : Brush{}; } Pen tooltip_text_pen() { - return hover_edit_state()->tooltip_text_pen; + auto state = begin_hover_read(); + return state ? state->tooltip_text_pen : Pen{.style = Line_Style::None}; } void set_tooltip_text_pen(const Pen& pen) { auto edit = begin_hover_edit(); + if (!edit) + return; edit->tooltip_text_pen = pen.color.a != 0 ? pen : Pen{.style = Line_Style::None}; } void get_hover_tooltip_contents_margins(int& left, int& top, int& right, int& bottom) { - auto s = hover_edit_state(); + auto s = begin_hover_read(); + if (!s) { + left = top = right = bottom = 0; + return; + } left = s->hover_info_left; top = s->hover_info_top; right = s->hover_info_right; @@ -120,6 +137,8 @@ struct LIB_DECL Hover_Tooltip_Provider { } void set_hover_tooltip_contents_margins(int left, int top, int right, int bottom) { auto edit = begin_hover_edit(); + if (!edit) + return; edit->hover_info_left = left; edit->hover_info_top = top; edit->hover_info_right = right; @@ -128,9 +147,12 @@ struct LIB_DECL Hover_Tooltip_Provider { Hover_Edit_Guard begin_hover_edit() { return begin_hover_edit_impl(); } + Hover_Read_Guard begin_hover_read() { + return begin_hover_read_impl(); + } private: - virtual Hover_Tooltip_State* hover_edit_state() = 0; virtual Hover_Tooltip_State* hover_render_state() = 0; + virtual Hover_Read_Guard begin_hover_read_impl() = 0; virtual Hover_Edit_Guard begin_hover_edit_impl() = 0; }; template @@ -139,27 +161,25 @@ private: That* that() { return static_cast(this); } - Hover_Tooltip_State* hover_edit_state() override { - auto t = that(); - return dynamic_cast(t->d_ptr->edit_state()); - } Hover_Tooltip_State* hover_render_state() override { auto t = that(); return dynamic_cast(t->d_ptr->render_state()); } + Hover_Read_Guard begin_hover_read_impl() override { + auto t = that(); + auto guard = t->d_ptr->acquire_edit_state_read(); + auto* state = dynamic_cast(guard.state); + if (!state) + return {}; + return Hover_Read_Guard(state, std::move(guard)); + } Hover_Edit_Guard begin_hover_edit_impl() override { auto t = that(); - auto lease = t->d_ptr->try_mark_edit_state(); - if (!lease) + auto guard = t->d_ptr->acquire_edit_state(); + auto* state = dynamic_cast(guard.state); + if (!state) return {}; - auto* data = t->d_ptr; - auto* state = dynamic_cast(data->state_by_index(lease.index)); - return Hover_Edit_Guard(this, state, [data, lease]() mutable { - ++data->state_by_index(lease.index)->version; - if (data->q_ptr) - data->q_ptr->mark_render_dirty(); - data->state_control.unmark_use(lease); - }); + return Hover_Edit_Guard(state, std::move(guard)); } }; } // namespace renderive diff --git a/Core/plottable/Interpolation_p.h b/Core/plottable/Interpolation_p.h index 2ec7c8a..9a54e0d 100644 --- a/Core/plottable/Interpolation_p.h +++ b/Core/plottable/Interpolation_p.h @@ -58,6 +58,8 @@ inline Visible_Sample_Window compute_visible_sample_window(Abs_Axis* axis, const } template double interpolate_sample_value(double index, int count, Line_Interpolation_Mode mode, Value_At value_at) { + if (count <= 0) + return std::numeric_limits::quiet_NaN(); index = std::clamp(index, 0.0, static_cast(count - 1)); int left = static_cast(std::floor(index)); int right = std::min(left + 1, count - 1); @@ -65,24 +67,41 @@ double interpolate_sample_value(double index, int count, Line_Interpolation_Mode double left_value = value_at(left); double right_value = value_at(right); switch (mode) { - case Line_Interpolation_Mode::Nearest_Sample: - return value_at(static_cast(std::round(index))); + case Line_Interpolation_Mode::Nearest_Sample: { + double value = value_at(static_cast(std::round(index))); + return std::isfinite(value) ? value : std::numeric_limits::quiet_NaN(); + } case Line_Interpolation_Mode::Linear_Value: + if (!std::isfinite(left_value) || !std::isfinite(right_value)) + return std::numeric_limits::quiet_NaN(); return left_value + (right_value - left_value) * t; case Line_Interpolation_Mode::Linear_Power_Domain: { + if (!std::isfinite(left_value) || !std::isfinite(right_value)) + return std::numeric_limits::quiet_NaN(); + constexpr double min_db = -3000.0; + constexpr double max_db = 3000.0; + left_value = std::clamp(left_value, min_db, max_db); + right_value = std::clamp(right_value, min_db, max_db); double left_power = std::pow(10.0, left_value / 10.0); double right_power = std::pow(10.0, right_value / 10.0); - return 10.0 * std::log10(left_power + (right_power - left_power) * t); + double mixed_power = left_power + (right_power - left_power) * t; + if (!std::isfinite(mixed_power) || mixed_power <= 0.0) + return min_db; + return std::clamp(10.0 * std::log10(mixed_power), min_db, max_db); } case Line_Interpolation_Mode::Step_Left: - return left_value; + return std::isfinite(left_value) ? left_value : std::numeric_limits::quiet_NaN(); case Line_Interpolation_Mode::Step_Right: - return t == 0.0 ? left_value : right_value; + if (t <= 1e-12) + return std::isfinite(left_value) ? left_value : std::numeric_limits::quiet_NaN(); + return std::isfinite(right_value) ? right_value : std::numeric_limits::quiet_NaN(); case Line_Interpolation_Mode::Cubic_Value: { double p0 = value_at(std::max(left - 1, 0)); double p1 = left_value; double p2 = right_value; double p3 = value_at(std::min(right + 1, count - 1)); + if (!std::isfinite(p0) || !std::isfinite(p1) || !std::isfinite(p2) || !std::isfinite(p3)) + return std::numeric_limits::quiet_NaN(); double a = -0.5 * p0 + 1.5 * p1 - 1.5 * p2 + 0.5 * p3; double b = p0 - 2.5 * p1 + 2.0 * p2 - 0.5 * p3; double c = -0.5 * p0 + 0.5 * p2; @@ -103,15 +122,30 @@ std::pmr::vector build_sample_line_points(const Axis_Mapping_2D& mapping std::pmr::vector points(resource); points.reserve(static_cast(std::max(0, last_source - first_source + 3))); double last_appended = -std::numeric_limits::infinity(); + double last_coord = 0.0; + double last_value = 0.0; + bool has_last_point = false; auto append_index = [&](double index) { if (std::abs(index - last_appended) <= 1e-9) return; double value = interpolate_sample_value(index, available_count, mode, value_at); - if (!std::isfinite(value)) + if (!std::isfinite(value)) { + has_last_point = false; return; + } double coord = source_index_to_coordinate(data_range, total_count, index); - points.push_back(mapping.map(coord, value)); + PointF point = mapping.map(coord, value); + if (has_last_point && (mode == Line_Interpolation_Mode::Step_Left || mode == Line_Interpolation_Mode::Step_Right)) { + PointF elbow = mode == Line_Interpolation_Mode::Step_Left ? mapping.map(coord, last_value) : mapping.map(last_coord, value); + if (points.empty() || points.back().x != elbow.x || points.back().y != elbow.y) + points.push_back(elbow); + } + if (points.empty() || points.back().x != point.x || points.back().y != point.y) + points.push_back(point); last_appended = index; + last_coord = coord; + last_value = value; + has_last_point = true; }; append_index(first_index); for (int i = first_source; i <= last_source; ++i) diff --git a/Core/plottable/Performance_Overlay.h b/Core/plottable/Performance_Overlay.h index 77f6518..25caa4a 100644 --- a/Core/plottable/Performance_Overlay.h +++ b/Core/plottable/Performance_Overlay.h @@ -2,7 +2,7 @@ #include #include #include -#include "../architecture/Frame_Scheduler.h" +#include "../architecture/Plot_Render_Context.h" #include "../base/Color.h" #include "../base/Geometry.h" #include "../base/Text.h" diff --git a/Core/plottable/Performance_Overlay_p.h b/Core/plottable/Performance_Overlay_p.h index 271ff81..6444404 100644 --- a/Core/plottable/Performance_Overlay_p.h +++ b/Core/plottable/Performance_Overlay_p.h @@ -16,7 +16,7 @@ #include #include "../architecture/Frame_Scheduler.h" #include "../architecture/Plot_Render_Context.h" -#include "../flow/low_latency/Render_Data.h" +#include "../architecture/Render_Data.h" #include "../base/Color.h" #include "../base/Geometry.h" #include "../base/Memory.h" diff --git a/Core/plottable/Selection_Rectangle_Overlay.cpp b/Core/plottable/Selection_Rectangle_Overlay.cpp index 51613a0..90b406a 100644 --- a/Core/plottable/Selection_Rectangle_Overlay.cpp +++ b/Core/plottable/Selection_Rectangle_Overlay.cpp @@ -35,16 +35,16 @@ void Selection_Rectangle_Overlay::set_vertical_axis(std::shared_ptr ax edit->vertical_axis = std::weak_ptr(axis); } Font Selection_Rectangle_Overlay::label_font() { - return d()->edit_state()->label_font; + return d()->edit_state_value(&Selection_Rectangle_Overlay_Render_State::label_font); } Pen Selection_Rectangle_Overlay::label_pen() { - return d()->edit_state()->label_pen; + return d()->edit_state_value(&Selection_Rectangle_Overlay_Render_State::label_pen); } Brush Selection_Rectangle_Overlay::selection_brush() { - return d()->edit_state()->selection_brush; + return d()->edit_state_value(&Selection_Rectangle_Overlay_Render_State::selection_brush); } Pen Selection_Rectangle_Overlay::selection_border_pen() { - return d()->edit_state()->selection_border_pen; + return d()->edit_state_value(&Selection_Rectangle_Overlay_Render_State::selection_border_pen); } void Selection_Rectangle_Overlay::set_label_font(const Font& font) { Render_Edit_Lease edit(d()); @@ -63,7 +63,7 @@ void Selection_Rectangle_Overlay::set_selection_border_pen(const Pen& pen) { edit->selection_border_pen = pen.color.a != 0 ? pen : Pen{.style = Line_Style::None}; } std::vector Selection_Rectangle_Overlay::selected_regions() { - return d()->edit_state()->rects; + return d()->edit_state_value(&Selection_Rectangle_Overlay_Render_State::rects); } void Selection_Rectangle_Overlay::clear_selected_regions() { Render_Edit_Lease edit(d()); diff --git a/Core/plottable/Spectrum.cpp b/Core/plottable/Spectrum.cpp index 2d20e5c..bcece88 100644 --- a/Core/plottable/Spectrum.cpp +++ b/Core/plottable/Spectrum.cpp @@ -562,10 +562,10 @@ void Spectrum::clear_custom_markers() { edit->marker_list.clear(); } double Spectrum::marker_frequency(int mark_index) { - auto render_state = d()->edit_state(); - if (mark_index < 0 || mark_index >= (int)render_state->marker_list.size()) + Render_State_Lease state(d()); + if (!state || mark_index < 0 || mark_index >= static_cast(state->marker_list.size())) return -1; - return render_state->marker_list[mark_index].frequency; + return state->marker_list[static_cast(mark_index)].frequency; } void Spectrum::set_marker_frequency(int marker_index, double frequency) { Render_Edit_Lease edit(d()); @@ -579,12 +579,11 @@ void Spectrum::set_current_marker_frequency(double frequency) { edit->set_cur_frequency(frequency); } int Spectrum::selectable_line_marker_count() { - auto render_state = d()->edit_state(); - return (int)render_state->marker_list.size(); + Render_State_Lease state(d()); + return state ? static_cast(state->marker_list.size()) : 0; } int Spectrum::selected_marker_index() { - auto render_state = d()->edit_state(); - return render_state->cur_select_index; + return d()->edit_state_value(&Spectrum_Render_State::cur_select_index); } void Spectrum::set_selected_marker_index(int marker_index) { Render_Edit_Lease edit(d()); diff --git a/Core/plottable/Waterfall_p.h b/Core/plottable/Waterfall_p.h index 6b55b2a..78bd848 100644 --- a/Core/plottable/Waterfall_p.h +++ b/Core/plottable/Waterfall_p.h @@ -22,6 +22,7 @@ #include "../base/global.h" #include "../render/Canvas.h" #include "../render/Image.h" +#include "../render/Render_Frame_Snapshot.h" #include "Psc_Cpp_Core/Base/RingBuffer.hpp" #include "Interpolation_p.h" #include "Waterfall.h" diff --git a/Core/primitive/Color_Bar.cpp b/Core/primitive/Color_Bar.cpp index 7831436..a5d8fc1 100644 --- a/Core/primitive/Color_Bar.cpp +++ b/Core/primitive/Color_Bar.cpp @@ -8,6 +8,7 @@ #include "../flow/low_latency/Render_Lease.h" #include "../render/Canvas.h" #include "../render/Image.h" +#include "../render/Render_Frame_Snapshot.h" namespace renderive { diff --git a/Core/primitive/Curve.cpp b/Core/primitive/Curve.cpp index e4b51f8..f77be26 100644 --- a/Core/primitive/Curve.cpp +++ b/Core/primitive/Curve.cpp @@ -12,6 +12,7 @@ #include #include "../base/Memory.h" #include "../render/Canvas.h" +#include "../render/Render_Frame_Snapshot.h" namespace renderive { enum class Curve_Input_Mode : std::uint8_t { None, @@ -115,9 +116,14 @@ struct Curve_Geometry_Key { } }; struct Curve_Geometry_Result { - Curve_Geometry_Result() : points(memory_resource(Memory_Domain::Curve)), update_states(memory_resource(Memory_Domain::Update_Completion)) {} + struct Segment { + explicit Segment(std::pmr::memory_resource* resource = memory_resource(Memory_Domain::Curve)) : points(resource) {} + std::pmr::vector points; + }; + + Curve_Geometry_Result() : segments(memory_resource(Memory_Domain::Curve)), update_states(memory_resource(Memory_Domain::Update_Completion)) {} Axis_Mapping_2D mapping; - std::pmr::vector points; + std::pmr::vector segments; std::pmr::vector> update_states; }; struct Curve_Geometry_Request { @@ -148,28 +154,94 @@ static void complete_curve_result_states(Curve_Geometry_Result& result, std::err } result.update_states.clear(); } + +static bool finite_point(const PointF& point) { + return std::isfinite(point.x) && std::isfinite(point.y); +} + +static void append_curve_point_segment(Curve_Geometry_Result& result, Curve_Geometry_Result::Segment*& segment, const Axis_Mapping_2D& mapping, const PointF& point) { + if (!finite_point(point)) { + segment = nullptr; + return; + } + PointF mapped = mapping.map(point); + if (!finite_point(mapped)) { + segment = nullptr; + return; + } + if (!segment) { + result.segments.emplace_back(memory_resource(Memory_Domain::Curve)); + segment = &result.segments.back(); + } + if (!segment->points.empty() && segment->points.back().x == mapped.x && segment->points.back().y == mapped.y) + return; + segment->points.push_back(mapped); +} + +static void append_curve_sample_run(Curve_Geometry_Result& result, const Curve_Geometry_Request& request, const Curve_Sample_Source& samples, int run_start, int run_end) { + if (run_start > run_end) + return; + auto coord_at = [&](int index) { + return source_index_to_coordinate(request.data_range, static_cast(samples.values.size()), index); + }; + if (run_start == run_end) { + double coord = coord_at(run_start); + if (request.visible_range_only && !request.mapping.domain.coord_range.contains(coord)) + return; + PointF point = request.mapping.map(coord, samples.values[static_cast(run_start)]); + if (!finite_point(point)) + return; + result.segments.emplace_back(memory_resource(Memory_Domain::Curve)); + result.segments.back().points.push_back(point); + return; + } + int run_count = run_end - run_start + 1; + Range run_range{coord_at(run_start), coord_at(run_end)}; + auto value_at = [&samples, run_start](int i) { + return samples.values[static_cast(run_start + i)]; + }; + std::pmr::vector points(memory_resource(Memory_Domain::Curve)); + if (request.sample_mode == Line_Sampling_Strategy::Pixel_Min_Max_Envelope) { + points = Curve_Utils::build_pixel_envelope_points(request.mapping, run_range, run_count, run_count, request.visible_range_only, value_at, memory_resource(Memory_Domain::Curve)); + } + else { + points = Curve_Utils::build_resampled_points(request.mapping, run_range, run_count, run_count, request.visible_range_only, request.interpolation_mode, value_at, memory_resource(Memory_Domain::Curve)); + } + if (points.empty()) + return; + result.segments.emplace_back(memory_resource(Memory_Domain::Curve)); + result.segments.back().points = std::move(points); +} + static void build_curve_geometry(Curve_Geometry_Result& result, const Curve_Geometry_Request& request) { - std::pmr::vector& points = result.points; + result.segments.clear(); if (!request.source) return; if (const auto* data_points = std::get_if(request.source.get())) { - points.reserve(data_points->values.size()); - for (std::size_t i = 0; i < data_points->values.size(); ++i) - points.push_back(request.mapping.map(data_points->values[i])); + result.segments.reserve(data_points->values.size()); + Curve_Geometry_Result::Segment* segment = nullptr; + for (const PointF& point : data_points->values) + append_curve_point_segment(result, segment, request.mapping, point); return; } const auto* samples = std::get_if(request.source.get()); if (!samples || samples->values.size() < 2 || request.data_range.length() == 0.0) return; - if (request.sample_mode == Line_Sampling_Strategy::Pixel_Min_Max_Envelope) { - points = Curve_Utils::build_pixel_envelope_points(request.mapping, request.data_range, static_cast(samples->values.size()), static_cast(samples->values.size()), request.visible_range_only, [samples](int i) { - return samples->values[static_cast(i)]; - }, memory_resource(Memory_Domain::Curve)); - return; + int run_start = -1; + int sample_count = static_cast(samples->values.size()); + for (int i = 0; i < sample_count; ++i) { + double value = samples->values[static_cast(i)]; + if (std::isfinite(value)) { + if (run_start < 0) + run_start = i; + continue; + } + if (run_start >= 0) + append_curve_sample_run(result, request, *samples, run_start, i - 1); + run_start = -1; } - points = Curve_Utils::build_resampled_points(request.mapping, request.data_range, static_cast(samples->values.size()), static_cast(samples->values.size()), request.visible_range_only, request.interpolation_mode, [samples](int i) { - return samples->values[static_cast(i)]; - }, memory_resource(Memory_Domain::Curve)); + if (run_start >= 0) + append_curve_sample_run(result, request, *samples, run_start, sample_count - 1); } struct Curve_Private : Typed_Render_Data { static constexpr std::size_t Input_Queue_Capacity = 8; @@ -287,13 +359,16 @@ struct Curve_Private : Typed_Render_Datapush_back(state); geometry_result->update_states.clear(); } - const auto& points = geometry_result->points; - if (points.empty()) + if (geometry_result->segments.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); + for (const auto& segment : geometry_result->segments) { + if (segment.points.empty()) + continue; + Curve_Utils::draw_polyline(&canvas, segment.points, s->pen); + Curve_Utils::draw_fill_to_value(&canvas, segment.points, geometry_result->mapping, geometry_result->mapping.value.coord_range.target, s->fill_brush); + } canvas.restore(); } }; diff --git a/test/Renderive_Core_Tests.cpp b/test/Renderive_Core_Tests.cpp index 4da6131..42b7980 100644 --- a/test/Renderive_Core_Tests.cpp +++ b/test/Renderive_Core_Tests.cpp @@ -28,6 +28,25 @@ #include #include namespace renderive { + +struct Low_Latency_Frame_Pipeline_Test_Probe { + static bool paint_request_pending(const Low_Latency_Frame_Pipeline& pipeline) { + return pipeline.paint_request_pending.load(std::memory_order_acquire); + } + + static void clear_paint_request(Low_Latency_Frame_Pipeline& pipeline) { + pipeline.paint_request_pending.store(false, std::memory_order_release); + } + + static Triple_Buffer_Mark_Record acquire_frame_role(Low_Latency_Frame_Pipeline& pipeline, std::uint8_t role) { + return pipeline.frame_control.try_acquire_role(role); + } + + static void release_frame_role(Low_Latency_Frame_Pipeline& pipeline, Triple_Buffer_Lease lease) { + pipeline.frame_control.unmark_use(lease); + } +}; + namespace { bool unique_roles(const Triple_Buffer_View& view) { std::array seen{}; @@ -291,6 +310,32 @@ TEST(Renderive_State, BusyEditSetterWaitsAndCommits) { EXPECT_TRUE(setter_returned.load(std::memory_order_acquire)); EXPECT_EQ(data.edit_state_value(&Lease_Test_State::value), 42); } +TEST(Renderive_State, GetterWaitsForWriter) { + 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 getter_started{false}; + std::atomic_bool getter_returned{false}; + int observed = -1; + std::thread getter([&]() { + getter_started.store(true, std::memory_order_release); + observed = data.edit_state_value(&Lease_Test_State::value); + getter_returned.store(true, std::memory_order_release); + }); + ASSERT_TRUE(wait_until([&]() { + return getter_started.load(std::memory_order_acquire); + })); + std::this_thread::sleep_for(std::chrono::milliseconds(20)); + EXPECT_FALSE(getter_returned.load(std::memory_order_acquire)); + data.state_data_by_index(held.lease.index)->value = 77; + data.state_control.unmark_use(held.lease); + getter.join(); + + EXPECT_TRUE(getter_returned.load(std::memory_order_acquire)); + EXPECT_EQ(observed, 77); +} TEST(Renderive_Input_Runtime, ExactInputDataBindingSkipsAllocation) { using No_Input_Binding = Renderable_Binding; using Custom_Input_Binding = Renderable_Binding; @@ -331,14 +376,14 @@ TEST(Renderive_Frame_Pipeline, PaintAcquireFailureKeepsPendingRequest) { lease.frame->version.store(1, std::memory_order_release); auto published = pipeline.try_publish_rendered_frame(std::move(lease), 25); ASSERT_TRUE(published.request_present); - EXPECT_TRUE(pipeline.paint_request_pending.load(std::memory_order_acquire)); - auto busy = pipeline.frame_control.try_acquire_role(Frame_Painting); + EXPECT_TRUE(Low_Latency_Frame_Pipeline_Test_Probe::paint_request_pending(pipeline)); + auto busy = Low_Latency_Frame_Pipeline_Test_Probe::acquire_frame_role(pipeline, Frame_Painting); ASSERT_EQ(busy.result, Triple_Buffer_Result::Success); auto failed = pipeline.try_acquire_paint_frame(30); EXPECT_TRUE(failed.paint_frame_unavailable); EXPECT_FALSE(failed.present_request_was_pending); - EXPECT_TRUE(pipeline.paint_request_pending.load(std::memory_order_acquire)); - pipeline.frame_control.unmark_use(busy.lease); + EXPECT_TRUE(Low_Latency_Frame_Pipeline_Test_Probe::paint_request_pending(pipeline)); + Low_Latency_Frame_Pipeline_Test_Probe::release_frame_role(pipeline, busy.lease); auto consumed = pipeline.try_acquire_paint_frame(35); EXPECT_TRUE(consumed.present_request_was_pending); EXPECT_TRUE(static_cast(consumed.lease)); @@ -354,14 +399,14 @@ TEST(Renderive_Frame_Pipeline, RequestPresentDoesNotWriteBusyMiddle) { 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); + Low_Latency_Frame_Pipeline_Test_Probe::clear_paint_request(pipeline); - auto busy_middle = pipeline.frame_control.try_acquire_role(Frame_Middle); + auto busy_middle = Low_Latency_Frame_Pipeline_Test_Probe::acquire_frame_role(pipeline, Frame_Middle); ASSERT_EQ(busy_middle.result, Triple_Buffer_Result::Success); auto 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); + EXPECT_FALSE(Low_Latency_Frame_Pipeline_Test_Probe::paint_request_pending(pipeline)); + Low_Latency_Frame_Pipeline_Test_Probe::release_frame_role(pipeline, busy_middle.lease); } TEST(Renderive_Render_Model, NeutralSnapshotAndOutputTypesAreWired) { static_assert(std::is_default_constructible_v); @@ -371,6 +416,7 @@ TEST(Renderive_Render_Model, NeutralSnapshotAndOutputTypesAreWired) { static_assert(std::is_move_constructible_v); static_assert(!std::is_copy_constructible_v); static_assert(!std::is_same_v); + static_assert(sizeof(Present_Surface_Lease) <= 160); Low_Latency_Frame frame; Render_Output& output = frame; @@ -426,8 +472,11 @@ TEST(Renderive_Curve_Sampling, AllSamplesUsesInterpolationModeForBoundaryPoints) auto points = Curve_Utils::build_resampled_points(mapping, Range{0.0, 2.0}, 3, 3, true, Line_Interpolation_Mode::Step_Left, [&samples](int i) { return samples[static_cast(i)]; }); - ASSERT_EQ(points.size(), 3u); + ASSERT_EQ(points.size(), 4u); EXPECT_DOUBLE_EQ(points.front().y, 0.0); + EXPECT_DOUBLE_EQ(points[1].y, 0.0); + EXPECT_DOUBLE_EQ(points[2].y, 10.0); + EXPECT_DOUBLE_EQ(points[1].x, points[2].x); EXPECT_DOUBLE_EQ(points.back().y, 10.0); } TEST(Renderive_Curve_Sampling, PixelEnvelopeUsesPhysicalPixelBuckets) {