#include "Curve.h" #include "Curve_Utils_p.h" #include #include #include #include #include #include #include #include #include "../base/Memory.h" #include "../render/Canvas.h" namespace renderive { enum class Curve_Input_Mode : std::uint8_t { None, Samples, Points }; struct Curve_Input_Data : Input_Data {}; struct Curve_Render_State : Render_State, Curve_Prop {}; struct Curve_Input_Block { Curve_Input_Mode mode = Curve_Input_Mode::None; std::pmr::vector samples{memory_resource(Memory_Domain::Curve)}; std::pmr::vector points{memory_resource(Memory_Domain::Curve)}; std::shared_ptr completion_state; static std::shared_ptr samples_block(std::span values, std::shared_ptr state = {}) { auto block = std::make_shared(); block->mode = Curve_Input_Mode::Samples; block->samples.assign(values.begin(), values.end()); block->completion_state = std::move(state); return block; } static std::shared_ptr samples_block(std::pmr::vector&& values, std::shared_ptr state = {}) { auto block = std::make_shared(); block->mode = Curve_Input_Mode::Samples; block->samples = std::move(values); block->completion_state = std::move(state); return block; } static std::shared_ptr points_block(std::span values, std::shared_ptr state = {}) { auto block = std::make_shared(); block->mode = Curve_Input_Mode::Points; block->points.assign(values.begin(), values.end()); block->completion_state = std::move(state); return block; } static std::shared_ptr points_block(std::pmr::vector&& values, std::shared_ptr state = {}) { auto block = std::make_shared(); block->mode = Curve_Input_Mode::Points; block->points = std::move(values); block->completion_state = std::move(state); return block; } }; struct Curve_Axis_Geometry_Key { Orientation orientation = Orientation::Horizontal; Range coord_range{0.0, 1.0}; Range pixel_range{0.0, 1.0}; bool operator==(const Curve_Axis_Geometry_Key& other) const { return orientation == other.orientation && coord_range == other.coord_range && pixel_range == other.pixel_range; } }; struct Curve_Geometry_Key { std::uint64_t input_generation{}; std::uint64_t state_version{}; Curve_Input_Mode mode = Curve_Input_Mode::None; Line_Sampling_Mode sample_mode = Line_Sampling_Mode::Pixel_Peak_Envelope; Line_Interpolation_Mode interpolation_mode = Line_Interpolation_Mode::Linear_Display; bool visible_range_only{}; Range data_range{0.0, 1.0}; std::size_t sample_count{}; std::size_t point_count{}; Curve_Axis_Geometry_Key domain; Curve_Axis_Geometry_Key value; bool operator==(const Curve_Geometry_Key& other) const { return input_generation == other.input_generation && state_version == other.state_version && mode == other.mode && sample_mode == other.sample_mode && interpolation_mode == other.interpolation_mode && visible_range_only == other.visible_range_only && data_range == other.data_range && sample_count == other.sample_count && point_count == other.point_count && domain == other.domain && value == other.value; } }; struct Curve_Geometry_Result { Curve_Geometry_Result() : points(memory_resource(Memory_Domain::Curve)), update_states(memory_resource(Memory_Domain::Update_Completion)) {} Axis_Mapping_2D mapping; std::pmr::vector points; std::pmr::vector> update_states; }; struct Curve_Geometry_Request { Curve_Geometry_Request() : samples(memory_resource(Memory_Domain::Curve)), data_points(memory_resource(Memory_Domain::Curve)), update_states(memory_resource(Memory_Domain::Update_Completion)) {} Curve_Input_Mode mode = Curve_Input_Mode::None; Axis_Mapping_2D mapping; Range data_range{0.0, 1.0}; bool visible_range_only{}; Line_Interpolation_Mode interpolation_mode = Line_Interpolation_Mode::Linear_Display; Line_Sampling_Mode sample_mode = Line_Sampling_Mode::Pixel_Peak_Envelope; std::pmr::vector samples; std::pmr::vector data_points; std::pmr::vector> update_states; }; static Curve_Axis_Geometry_Key curve_axis_key(const Axis_Frame_Snapshot& axis) { return {axis.orientation, axis.coord_range, axis.pixel_range}; } static void complete_curve_update_states(Curve_Geometry_Request& request, std::error_code error, Update_Outcome outcome) { for (auto& state : request.update_states) { if (state) state->complete_all(error, outcome); } request.update_states.clear(); } static void complete_curve_result_states(Curve_Geometry_Result& result, std::error_code error, Update_Outcome outcome) { for (const auto& state : result.update_states) { if (state) state->complete_all(error, outcome); } result.update_states.clear(); } static void build_curve_geometry(Curve_Geometry_Result& result, const Curve_Geometry_Request& request) { std::pmr::vector& points = result.points; if (request.mode == Curve_Input_Mode::Points) { points.reserve(request.data_points.size()); for (std::size_t i = 0; i < request.data_points.size(); ++i) points.push_back(request.mapping.map(request.data_points[i])); return; } if (request.mode != Curve_Input_Mode::Samples || request.samples.size() < 2 || request.data_range.length() == 0.0) return; if (request.sample_mode == Line_Sampling_Mode::Pixel_Peak_Envelope) { points = Curve_Utils::create_peak_bucket_points(request.mapping, request.data_range, static_cast(request.samples.size()), static_cast(request.samples.size()), request.visible_range_only, [&request](int i) { return request.samples[static_cast(i)]; }, memory_resource(Memory_Domain::Curve)); return; } Line_Sample_Window window = line_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_coord(request.data_range, static_cast(request.samples.size()), index); double value = interpolate_line_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)); } } struct Curve_Private : Typed_Render_Data { static constexpr std::size_t Input_Queue_Capacity = 8; std::pmr::vector samples{memory_resource(Memory_Domain::Curve)}; std::pmr::vector data_points{memory_resource(Memory_Domain::Curve)}; Curve_Input_Mode mode = Curve_Input_Mode::None; rigtorp::MPMCQueue> 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::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); std::shared_ptr block; while (input_queue.try_pop(block)) { if (block && block->completion_state) block->completion_state->complete_all(std::make_error_code(std::errc::operation_canceled), Update_Outcome::Cancelled); } for (auto& state : pending_geometry_update_states) { if (state) state->complete_all(std::make_error_code(std::errc::operation_canceled), Update_Outcome::Cancelled); } pending_geometry_update_states.clear(); } void enqueue_input(std::shared_ptr block) { if (!block) return; if (!input_queue.try_push(block)) { if (block->completion_state) block->completion_state->complete_all({}, Update_Outcome::Superseded); dropped_input_count.fetch_add(1, std::memory_order_relaxed); return; } if (block->completion_state) block->completion_state->complete_until(Update_Stage::Input_Released); if (q()) q()->mark_render_dirty(); } void consume_input_queue() { std::shared_ptr selected; std::shared_ptr block; while (input_queue.try_pop(block)) { if (!block) continue; if (selected && selected->completion_state) selected->completion_state->complete_all({}, Update_Outcome::Superseded); selected = std::move(block); } if (!selected) return; mode = selected->mode; samples = std::move(selected->samples); data_points = std::move(selected->points); ++input_generation; if (selected->completion_state) { selected->completion_state->complete_until(Update_Stage::Committed); pending_geometry_update_states.push_back(std::move(selected->completion_state)); } } void prepare_data(const Plot_Render_Snapshot& snapshot) override { (void)snapshot; sync_state_pipeline(); consume_input_queue(); submit_geometry_if_needed(); } void submit_geometry_if_needed() { Curve_Render_State* s = render_state(); auto domain_axis = s->domain_axis.lock(); auto value_axis = s->value_axis.lock(); if (!domain_axis || !value_axis) { for (auto& state : pending_geometry_update_states) { if (state) state->complete_all(std::make_error_code(std::errc::operation_canceled), Update_Outcome::Cancelled); } pending_geometry_update_states.clear(); return; } Axis_Mapping_2D mapping = Axis_Render_Access::mapping(domain_axis.get(), value_axis.get()); Curve_Geometry_Key key; key.input_generation = input_generation; key.state_version = s->version; key.mode = mode; key.sample_mode = s->sample_mode; key.interpolation_mode = s->interpolation_mode; key.visible_range_only = s->visible_range_only; key.data_range = s->data_range; key.sample_count = samples.size(); key.point_count = data_points.size(); key.domain = curve_axis_key(mapping.domain); key.value = curve_axis_key(mapping.value); if (has_submitted_geometry_key && key == submitted_geometry_key) return; submitted_geometry_key = key; has_submitted_geometry_key = true; auto request = std::make_shared(); request->mode = mode; request->mapping = mapping; request->data_range = s->data_range; request->visible_range_only = s->visible_range_only; request->interpolation_mode = s->interpolation_mode; request->sample_mode = s->sample_mode; request->samples = samples; request->data_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; } void draw(Canvas& canvas, const Plot_Render_Snapshot& snapshot) override { Curve_Render_State* s = render_state(); if (!has_geometry_result) return; if (snapshot.frame_update_states) { for (const auto& state : geometry_result.update_states) snapshot.frame_update_states->push_back(state); geometry_result.update_states.clear(); } 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); canvas.restore(); } }; RENDERIVE_DEFINE_RENDERABLE_BINDING(Curve, Curve_Private, Curve_Render_State, Curve_Input_Data, "Curve") void Curve::give_samples(std::span values) { if (!ok()) return; d()->enqueue_input(Curve_Input_Block::samples_block(values)); } void Curve::give_samples(std::pmr::vector&& values) { if (!ok()) return; d()->enqueue_input(Curve_Input_Block::samples_block(std::move(values))); } Update_Ticket Curve::submit_samples(std::span values) { auto state = make_update_state(); if (!ok()) { state->complete_all(std::make_error_code(std::errc::operation_canceled), Update_Outcome::Cancelled); return Update_Ticket(state); } d()->enqueue_input(Curve_Input_Block::samples_block(values, state)); return Update_Ticket(state); } Update_Ticket Curve::submit_samples(std::pmr::vector&& values) { auto state = make_update_state(); if (!ok()) { state->complete_all(std::make_error_code(std::errc::operation_canceled), Update_Outcome::Cancelled); return Update_Ticket(state); } d()->enqueue_input(Curve_Input_Block::samples_block(std::move(values), state)); return Update_Ticket(state); } void Curve::give_points(std::span points) { if (!ok()) return; d()->enqueue_input(Curve_Input_Block::points_block(points)); } void Curve::give_points(std::pmr::vector&& points) { if (!ok()) return; d()->enqueue_input(Curve_Input_Block::points_block(std::move(points))); } Update_Ticket Curve::submit_points(std::span points) { auto state = make_update_state(); if (!ok()) { state->complete_all(std::make_error_code(std::errc::operation_canceled), Update_Outcome::Cancelled); return Update_Ticket(state); } d()->enqueue_input(Curve_Input_Block::points_block(points, state)); return Update_Ticket(state); } Update_Ticket Curve::submit_points(std::pmr::vector&& points) { auto state = make_update_state(); if (!ok()) { state->complete_all(std::make_error_code(std::errc::operation_canceled), Update_Outcome::Cancelled); return Update_Ticket(state); } d()->enqueue_input(Curve_Input_Block::points_block(std::move(points), state)); return Update_Ticket(state); } RENDERIVE_DEFINE_WEAK_STATE_PROP(Curve, Curve_Render_State, Abs_Axis, domain_axis) RENDERIVE_DEFINE_WEAK_STATE_PROP(Curve, Curve_Render_State, Abs_Axis, value_axis) Range Curve::data_range() { return d()->edit_state_value(&Curve_Render_State::data_range); } void Curve::set_data_range(Range value) { if (value.length() == 0.0) return; d()->set_state_value(&Curve_Render_State::data_range, value); } RENDERIVE_DEFINE_STATE_PROP(Curve, Curve_Render_State, Pen, pen) RENDERIVE_DEFINE_STATE_PROP(Curve, Curve_Render_State, Brush, fill_brush) RENDERIVE_DEFINE_STATE_PROP(Curve, Curve_Render_State, bool, visible_range_only) RENDERIVE_DEFINE_STATE_PROP(Curve, Curve_Render_State, bool, antialias) RENDERIVE_DEFINE_STATE_PROP(Curve, Curve_Render_State, Line_Interpolation_Mode, interpolation_mode) RENDERIVE_DEFINE_STATE_PROP(Curve, Curve_Render_State, Line_Sampling_Mode, sample_mode) Curve::Builder::Builder(std::shared_ptr parent, std::shared_ptr domain_axis, std::shared_ptr value_axis) { ASSERT(parent, "Curve build error! parent == nullptr"); ASSERT(domain_axis && value_axis, "Curve build error! axis == nullptr"); ASSERT(domain_axis->attached_to_plot(), "Curve build error! domain_axis->plot == nullptr"); ASSERT(domain_axis->shares_plot_with(*value_axis), "Curve build error! domain_axis->plot != value_axis->plot"); ASSERT(parent->shares_plot_with(*domain_axis), "Curve build error! parent->plot != axis->plot"); ASSERT(domain_axis->orientation() != value_axis->orientation(), "Curve build error! axes must be orthogonal"); this->parent = parent; this->domain_axis = domain_axis; this->value_axis = value_axis; } std::shared_ptr Curve::Builder::build() { auto ret = renderive::make_shared(); ret->init(parent); Curve_Private* pd = ret->d(); Curve_Render_State* sc = reinterpret_cast(pd->state(State_Edit)); PROP_R(std::weak_ptr, domain_axis) PROP_R(std::weak_ptr, value_axis) PROP_R(Range, data_range) PROP_R(Pen, pen) PROP_R(Brush, fill_brush) PROP_R(bool, visible_range_only) PROP_R(bool, antialias) PROP_R(Line_Interpolation_Mode, interpolation_mode) PROP_R(Line_Sampling_Mode, sample_mode) if (sc->data_range.length() == 0.0) sc->data_range = {0.0, 1.0}; return ret; } void Curve_Utils::draw_polyline(Canvas* canvas, std::span points, const Pen& pen) { if (points.empty() || pen.style == Line_Style::None) return; canvas->set_pen(pen); canvas->draw_polyline(points); } void Curve_Utils::draw_fill_to_value(Canvas* canvas, std::span points, const Axis_Mapping_2D& mapping, double baseline_value, const Brush& brush) { if (points.empty() || brush.style == Brush_Style::None) return; double first_domain = mapping.domain.pixel_to_coord(mapping.domain_pixel(points.front())); double last_domain = mapping.domain.pixel_to_coord(mapping.domain_pixel(points.back())); std::pmr::vector fill_points(points.size() + 2, frame_memory_resource()); fill_points.front() = mapping.map(first_domain, baseline_value); fill_points.back() = mapping.map(last_domain, baseline_value); for (int i = 0; i < points.size(); ++i) fill_points[i + 1] = points[i]; canvas->set_brush(brush); canvas->fill_polygon(std::span(fill_points.data(), fill_points.size())); } void Curve_Utils::append_peak_point(std::pmr::vector& points, const Axis_Mapping_2D& mapping, const Range& data_range, int total_count, int index, double value) { if (index < 0) return; PointF point = mapping.map(source_index_to_coord(data_range, total_count, index), value); if (!points.empty() && points.back().x == point.x && points.back().y == point.y) return; points.push_back(point); } } // namespace renderive