From fe3a4a6128cfe06f010ab9f8a463da9274609fe8 Mon Sep 17 00:00:00 2001 From: wyc <1104749580@qq.com> Date: Fri, 31 Jul 2026 06:21:10 +0800 Subject: [PATCH] =?UTF-8?q?=E5=8A=A0blend2d=20=20=E5=9B=BE=E4=BE=8B?= =?UTF-8?q?=E5=88=86=E5=BC=80=E6=98=BE=E7=A4=BA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Demo_Gallery/base/Tool.h | 30 +- Renderive/Axis/Abs_Axis.cpp | 9 +- Renderive/Axis/Abs_Axis.h | 11 +- Renderive/Axis/Abs_Axis_p.h | 86 ++- Renderive/Axis/Axis.cpp | 2 +- Renderive/Axis/Axis.h | 9 +- Renderive/Axis/Axis_p.h | 4 +- Renderive/Axis/Frequency_Axis.cpp | 2 +- Renderive/Axis/Frequency_Axis.h | 5 +- Renderive/Axis/Frequency_Axis_p.h | 8 +- Renderive/Axis/Time_Axis.cpp | 6 +- Renderive/Axis/Time_Axis.h | 11 +- Renderive/Axis/Time_Axis_p.h | 18 +- Renderive/architecture/Buffer_Role.h | 10 - .../architecture/Frame_Raster_Context.cpp | 162 +++++ Renderive/architecture/Frame_Raster_Context.h | 44 ++ Renderive/architecture/Render_Config.h | 39 +- Renderive/architecture/Render_Data.cpp | 19 +- Renderive/architecture/Render_Data.h | 23 +- Renderive/architecture/Render_Lease.h | 69 +- Renderive/architecture/Render_Pipeline.cpp | 36 +- Renderive/architecture/Renderable.cpp | 160 ++--- Renderive/architecture/Renderable.h | 44 +- Renderive/architecture/Triple_Buffer.h | 47 +- Renderive/architecture/export.h | 7 +- Renderive/base/Interpolation.h | 34 + Renderive/base/Interpolation_p.h | 101 +++ Renderive/base/global.cpp | 6 +- Renderive/plot/Plot.cpp | 37 +- Renderive/plot/Plot.h | 7 +- Renderive/plot/Plot_p.h | 11 + Renderive/plottable/Afterglow_p.h | 27 +- Renderive/plottable/Audio_Frequency.cpp | 49 +- Renderive/plottable/Audio_Frequency.h | 40 +- Renderive/plottable/Audio_Frequency_p.h | 163 +++-- Renderive/plottable/Hover_Info.cpp | 8 +- Renderive/plottable/Hover_Info.h | 8 +- Renderive/plottable/Interpolation.h | 28 - Renderive/plottable/Interpolation_p.h | 147 ----- Renderive/plottable/Multi_Select_Rect.cpp | 20 +- Renderive/plottable/Multi_Select_Rect_p.h | 35 +- Renderive/plottable/Performance_Shower.cpp | 10 +- Renderive/plottable/Performance_Shower_p.h | 3 +- Renderive/plottable/Planisphere.cpp | 10 +- Renderive/plottable/Planisphere.h | 2 + Renderive/plottable/Planisphere_p.h | 3 +- Renderive/plottable/Spectrum.cpp | 162 +++-- Renderive/plottable/Spectrum.h | 15 +- Renderive/plottable/Spectrum_p.h | 140 +--- Renderive/plottable/Sweep_Frequency.cpp | 14 +- Renderive/plottable/Sweep_Frequency.h | 8 +- Renderive/plottable/Sweep_Frequency_p.h | 13 +- Renderive/plottable/Waterfall.cpp | 17 +- Renderive/plottable/Waterfall.h | 15 +- Renderive/plottable/Waterfall_p.h | 597 +++++++++++------- Renderive/plottable/export.h | 2 +- Renderive/primitive/Band_Region.cpp | 33 +- Renderive/primitive/Color_Bar.cpp | 7 +- Renderive/primitive/Curve.cpp | 290 +++++++-- Renderive/primitive/Curve.h | 173 +---- Renderive/primitive/Curve_Utils_p.h | 122 ++++ Renderive/primitive/Fill_Area.cpp | 23 +- Renderive/primitive/Grid.cpp | 30 +- Renderive/primitive/Label.cpp | 19 +- Renderive/primitive/Marker.cpp | 33 +- Renderive/primitive/Point_Set.cpp | 23 +- Renderive/primitive/Raster_Image.cpp | 31 +- Renderive/primitive/Raster_Image.h | 2 +- Renderive/primitive/Raster_Image_p.h | 13 +- tests/Frame_Scheduler_Feedback.cpp | 3 +- tests/Memory_Data_Path_Stress.cpp | 188 +++++- tests/Memory_Pool_Benchmark.cpp | 10 +- tests/Multithread_Input_Stress.cpp | 2 +- tests/Renderable_Lifecycle_Stress.cpp | 4 +- tests/internal/Renderive_Private_Access.h | 17 +- 75 files changed, 2191 insertions(+), 1425 deletions(-) delete mode 100644 Renderive/architecture/Buffer_Role.h create mode 100644 Renderive/architecture/Frame_Raster_Context.cpp create mode 100644 Renderive/architecture/Frame_Raster_Context.h create mode 100644 Renderive/base/Interpolation.h create mode 100644 Renderive/base/Interpolation_p.h delete mode 100644 Renderive/plottable/Interpolation.h delete mode 100644 Renderive/plottable/Interpolation_p.h create mode 100644 Renderive/primitive/Curve_Utils_p.h diff --git a/Demo_Gallery/base/Tool.h b/Demo_Gallery/base/Tool.h index 6b7e970..d90d3bf 100644 --- a/Demo_Gallery/base/Tool.h +++ b/Demo_Gallery/base/Tool.h @@ -1,4 +1,5 @@ #pragma once +#include "../../Renderive/architecture/export.h" #include #include #include @@ -15,7 +16,6 @@ #include #include "../../Renderive/Axis/export.h" #include "../../Widget/base/Generate_Mock_Data.h" -#include "../../Renderive/architecture/export.h" #include "../../Widget/base/export.h" #include "../../Renderive/plottable/export.h" #include "Renderive/plot/Latency_Eager_Plot.h" @@ -237,15 +237,14 @@ static QWidget* create_line_interpolation_combo(T* renderable) { auto combo = new QComboBox; label->setStyleSheet(renderive::get_button_style_sheet()); combo->setStyleSheet(renderive::get_button_style_sheet()); - combo->addItem("Nearest_Bin", static_cast(renderive::Line_Interpolation_Mode::Nearest_Bin)); - combo->addItem("Linear_Display", static_cast(renderive::Line_Interpolation_Mode::Linear_Display)); - combo->addItem("Linear_Power", static_cast(renderive::Line_Interpolation_Mode::Linear_Power)); - combo->addItem("Step_Left", static_cast(renderive::Line_Interpolation_Mode::Step_Left)); - combo->addItem("Step_Right", static_cast(renderive::Line_Interpolation_Mode::Step_Right)); - combo->addItem("Cubic_Display", static_cast(renderive::Line_Interpolation_Mode::Cubic_Display)); + combo->addItem("Nearest_Sample", static_cast(renderive::Line_Interpolation::Nearest_Sample)); + combo->addItem("Linear", static_cast(renderive::Line_Interpolation::Linear)); + combo->addItem("Step_Previous", static_cast(renderive::Line_Interpolation::Step_Previous)); + combo->addItem("Step_Next", static_cast(renderive::Line_Interpolation::Step_Next)); + combo->addItem("Cubic", static_cast(renderive::Line_Interpolation::Cubic)); combo->setCurrentIndex(combo->findData(static_cast(renderable->interpolation_mode()))); QObject::connect(combo, QOverload::of(&QComboBox::currentIndexChanged), [renderable, combo](int index) { - renderable->set_interpolation_mode(static_cast(combo->itemData(index).toInt())); + renderable->set_interpolation_mode(static_cast(combo->itemData(index).toInt())); }); layout->addWidget(label); layout->addWidget(combo); @@ -261,7 +260,6 @@ static QWidget* create_image_interpolation_combo(renderive::Waterfall* waterfall combo->setStyleSheet(renderive::get_button_style_sheet()); combo->addItem("Nearest", static_cast(renderive::Image_Interpolation_Mode::Nearest)); combo->addItem("Bilinear", static_cast(renderive::Image_Interpolation_Mode::Bilinear)); - combo->addItem("Bicubic", static_cast(renderive::Image_Interpolation_Mode::Bicubic)); combo->setCurrentIndex(combo->findData(static_cast(waterfall->interpolation_mode()))); QObject::connect(combo, QOverload::of(&QComboBox::currentIndexChanged), [waterfall, combo](int index) { waterfall->set_interpolation_mode(static_cast(combo->itemData(index).toInt())); @@ -357,7 +355,7 @@ static std::vector cw(renderive::Time_Axis* it, int num_pre_second = - }, {it->tick_pixel_space()}), }; - auto timer = new QTimer(it->plot); + auto timer = new QTimer(QApplication::instance()); if (num_pre_second > 0) timer->start(static_cast(1000.0 / static_cast(num_pre_second))); QObject::connect(timer, &QTimer::timeout, [it]() { @@ -421,7 +419,7 @@ static std::vector cw(renderive::Hover_Info_Renderable* it) { static std::vector cw(renderive::Waterfall* it, int num_pre_second = -1) { if (it == nullptr) return {}; - auto timer = new QTimer(it->plot); + auto timer = new QTimer(QApplication::instance()); if (num_pre_second > 0) timer->start(static_cast(1000.0 / static_cast(num_pre_second))); QObject::connect(timer, &QTimer::timeout, [it]() { @@ -449,7 +447,7 @@ static std::vector cw(renderive::Waterfall* it, int num_pre_second = - static std::vector cw(renderive::Audio_Frequency* it, int num_pre_second = -1) { if (it == nullptr) return {}; - auto timer = new QTimer(it->plot); + auto timer = new QTimer(QApplication::instance()); if (num_pre_second > 0) timer->start(static_cast(1000.0 / static_cast(num_pre_second))); QObject::connect(timer, &QTimer::timeout, [it]() { @@ -471,7 +469,7 @@ static std::vector cw(renderive::Audio_Frequency* it, int num_pre_seco static std::vector cw(renderive::Planisphere* it, int num_pre_second = -1) { if (it == nullptr) return {}; - auto timer = new QTimer(it->plot); + auto timer = new QTimer(QApplication::instance()); QObject::connect(timer, &QTimer::timeout, [it]() { for (int i = 0; i < 10; ++i) { double x = renderive::get_data(it->i_range()); @@ -534,7 +532,7 @@ static std::vector cw(renderive::Afterglow* it, int num_pre_second = - return ret; }; static std::vector base_data = create_base_data(); - auto timer = new QTimer(it->plot); + auto timer = new QTimer(QApplication::instance()); QObject::connect(timer, &QTimer::timeout, [it]() { renderive::Range r = it->power_range(); int n = base_data.size(); @@ -574,7 +572,7 @@ static std::vector cw(renderive::Afterglow* it, int num_pre_second = - static std::vector cw(renderive::Spectrum* it, int num_pre_second = -1) { if (it == nullptr) return {}; - auto timer = new QTimer(it->plot); + auto timer = new QTimer(QApplication::instance()); if (num_pre_second > 0) timer->start(static_cast(1000.0 / static_cast(num_pre_second))); QObject::connect(timer, &QTimer::timeout, [it]() { @@ -689,7 +687,7 @@ static std::vector cw(renderive::Spectrum* it, int num_pre_second = -1 static std::vector cw(renderive::Sweep_Frequency* it, int num_pre_second = -1) { if (it == nullptr) return {}; - auto timer = new QTimer(it->plot); + auto timer = new QTimer(QApplication::instance()); if (num_pre_second > 0) timer->start(static_cast(1000.0 / static_cast(num_pre_second))); QObject::connect(timer, &QTimer::timeout, [it]() { diff --git a/Renderive/Axis/Abs_Axis.cpp b/Renderive/Axis/Abs_Axis.cpp index 21f1de3..233c251 100644 --- a/Renderive/Axis/Abs_Axis.cpp +++ b/Renderive/Axis/Abs_Axis.cpp @@ -51,11 +51,11 @@ std::vector Abs_Axis_Private::create_tick_vector(double tick_step, const // } return result; } -std::vector Abs_Axis_Private::create_label_vector(const std::vector& ticks, SRC src) { +std::vector Abs_Axis_Private::create_label_vector(const std::vector& ticks, const Abs_Axis_Render_State& state) { std::vector result; result.reserve(ticks.size()); for (double tick_coord : ticks) - result.push_back(get_tick_label(tick_coord, src)); + result.push_back(get_tick_label(tick_coord, state)); return result; } std::vector Abs_Axis_Private::create_sub_tick_vector(int sub_tick_count, const std::vector& ticks, const Range& range) { @@ -177,9 +177,8 @@ double Abs_Axis_Private::pick_closest(double target, const std::vector& else return target - *(it - 1) < *it - target ? *(it - 1) : *it; } -QString Abs_Axis_Private::get_tick_label(double tick, SRC src) { - auto render_state = reinterpret_cast(state(src)); - return render_state->locale.toString(tick, render_state->format_char.toLatin1(), render_state->number_precision); +QString Abs_Axis_Private::get_tick_label(double tick, const Abs_Axis_Render_State& state) { + return state.locale.toString(tick, state.format_char.toLatin1(), state.number_precision); } double Abs_Axis::start_coord() { return d()->edit_axis_state()->coord_start; diff --git a/Renderive/Axis/Abs_Axis.h b/Renderive/Axis/Abs_Axis.h index 7cbe5dc..ff5e8c8 100644 --- a/Renderive/Axis/Abs_Axis.h +++ b/Renderive/Axis/Abs_Axis.h @@ -19,12 +19,9 @@ struct Abs_Axis_Prop { int rotate = 0; }; struct Abs_Axis_Private; +struct Axis_Internal_Access; class LIB_DECL Abs_Axis : public Renderable { public: - Abs_Axis_Private* d(); - Render_Object_Owner create_render_data() override; - Render_Object_Owner create_render_state() override; - Render_Object_Owner create_input_data() override; Abs_Axis(); PROP(int, x) PROP(int, y) @@ -55,6 +52,12 @@ public: int get_pixel_point_size(); template struct Builder_T; +private: + friend struct Axis_Internal_Access; + Abs_Axis_Private* d(); + Render_Object_Owner create_render_data() override; + Render_Object_Owner create_render_state() override; + Render_Object_Owner create_input_data() override; }; template struct Abs_Axis::Builder_T : protected Abs_Axis_Prop { diff --git a/Renderive/Axis/Abs_Axis_p.h b/Renderive/Axis/Abs_Axis_p.h index bf58018..9a3ff0b 100644 --- a/Renderive/Axis/Abs_Axis_p.h +++ b/Renderive/Axis/Abs_Axis_p.h @@ -12,10 +12,26 @@ struct Abs_Axis_Render_State : Render_State, Abs_Axis_Prop { } }; struct Abs_Axis_Input_Data : Input_Data {}; +struct Axis_Transform_Snapshot { + Range coordinate_range{}; + double pixel_origin{}; + double pixel_length{}; + Qt::Orientation orientation = Qt::Horizontal; + + [[nodiscard]] double to_pixel(double coordinate) const { + return pixel_origin + pixel_length * (coordinate - coordinate_range.origin) / coordinate_range.length(); + } + [[nodiscard]] double to_coordinate(double pixel) const { + return coordinate_range.origin + coordinate_range.length() * (pixel - pixel_origin) / pixel_length; + } + [[nodiscard]] int pixel_size() const { + return qAbs(static_cast(pixel_length)); + } +}; struct Abs_Axis_Private : Typed_Render_Data { int tick_count = 2; [[nodiscard]] Abs_Axis_Render_State* edit_axis_state() { - return state_data(SRC::Edit); + return edit_state(); } [[nodiscard]] Abs_Axis_Render_State* render_axis_state() { return render_state(); @@ -29,7 +45,8 @@ struct Abs_Axis_Private : Typed_Render_Datacoord_start, s->coord_start + s->coord_length}; @@ -43,7 +60,8 @@ struct Abs_Axis_Private : Typed_Render_Data labels = create_label_vector(ticks, SRC::Render); + const Axis_Transform_Snapshot transform = transform_of(*s); + std::vector labels = create_label_vector(ticks, *s); QFontMetrics fm(painter->font()); int h = fm.height(); painter->setPen(s->color); @@ -52,7 +70,7 @@ struct Abs_Axis_Private : Typed_Render_DatadrawLine((int)x, s->y, (int)x, s->y + s->tick_length); const QString& label = labels[i]; QPoint start((int)x, s->y + s->tick_length); @@ -62,7 +80,7 @@ struct Abs_Axis_Private : Typed_Render_DataresetTransform(); } for (double tick : sub_ticks) { - double x = coord_to_pixel(tick, SRC::Render); + double x = transform.to_pixel(tick); painter->drawLine((int)x, s->y, (int)x, s->y + s->sub_tick_length); } } @@ -71,7 +89,7 @@ struct Abs_Axis_Private : Typed_Render_DatadrawLine(s->x, (int)y, s->x + s->tick_length, (int)y); QPoint start = QPoint(s->x + s->tick_length * 2, (int)y + h / 2); @@ -81,7 +99,7 @@ struct Abs_Axis_Private : Typed_Render_DataresetTransform(); } for (double tick : sub_ticks) { - double y = coord_to_pixel(tick, SRC::Render); + double y = transform.to_pixel(tick); painter->drawLine(s->x, (int)y, s->x + s->sub_tick_length, (int)y); } } @@ -110,13 +128,15 @@ struct Abs_Axis_Private : Typed_Render_DatadrawText(area, Qt::AlignCenter, s->unit_text); } } - [[nodiscard]] virtual double pixel_to_coord(double pixel, SRC src) { - auto s = static_cast(state(src)); - return s->coord_start + s->coord_length * (pixel - (double)pixel_start_of(s)) / (double)s->pixel_size; + [[nodiscard]] static Axis_Transform_Snapshot transform_of(const Abs_Axis_Render_State& state) { + return {{state.coord_start, state.coord_start + state.coord_length}, + static_cast(pixel_start_of(&state)), static_cast(state.pixel_size), state.orientation}; } - [[nodiscard]] virtual double coord_to_pixel(double coord, SRC src) { - auto s = static_cast(state(src)); - return pixel_start_of(s) + (double)s->pixel_size * (coord - s->coord_start) / s->coord_length; + [[nodiscard]] Axis_Transform_Snapshot edit_transform() { + return transform_of(*edit_axis_state()); + } + [[nodiscard]] Axis_Transform_Snapshot render_transform() { + return transform_of(*render_axis_state()); } virtual double get_tick_step(const Range& range) { double exact_step = range.size() / double(tick_count + 1e-10); @@ -125,19 +145,19 @@ struct Abs_Axis_Private : Typed_Render_Data create_tick_vector(double tick_step, const Range& range); virtual std::vector create_sub_tick_vector(int sub_tick_count, const std::vector& ticks, const Range& range); - virtual QString get_tick_label(double tick, SRC); - virtual std::vector create_label_vector(const std::vector& ticks, SRC); + virtual QString get_tick_label(double tick, const Abs_Axis_Render_State& state); + virtual std::vector create_label_vector(const std::vector& ticks, const Abs_Axis_Render_State& state); [[nodiscard]] double edit_pixel_to_coord(double pixel) { - return pixel_to_coord(pixel, SRC::Edit); + return edit_transform().to_coordinate(pixel); } [[nodiscard]] double render_pixel_to_coord(double pixel) { - return pixel_to_coord(pixel, SRC::Render); + return render_transform().to_coordinate(pixel); } [[nodiscard]] double edit_coord_to_pixel(double coord) { - return coord_to_pixel(coord, SRC::Edit); + return edit_transform().to_pixel(coord); } [[nodiscard]] double render_coord_to_pixel(double coord) { - return coord_to_pixel(coord, SRC::Render); + return render_transform().to_pixel(coord); } [[nodiscard]] Range edit_coord_range() { const auto* s = edit_axis_state(); @@ -151,23 +171,41 @@ struct Abs_Axis_Private : Typed_Render_Data(render_axis_state()->pixel_size)); } [[nodiscard]] QString edit_tick_label(double tick) { - return get_tick_label(tick, SRC::Edit); + const auto* state = edit_axis_state(); + return get_tick_label(tick, *state); } [[nodiscard]] QString render_tick_label(double tick) { - return get_tick_label(tick, SRC::Render); + const auto* state = render_axis_state(); + return get_tick_label(tick, *state); } [[nodiscard]] std::vector edit_label_vector(const std::vector& ticks) { - return create_label_vector(ticks, SRC::Edit); + const auto* state = edit_axis_state(); + return create_label_vector(ticks, *state); } [[nodiscard]] std::vector render_label_vector(const std::vector& ticks) { - return create_label_vector(ticks, SRC::Render); + const auto* state = render_axis_state(); + return create_label_vector(ticks, *state); } }; +struct Axis_Internal_Access { + [[nodiscard]] static Axis_Transform_Snapshot render_transform(const Abs_Axis& axis) { + return const_cast(axis).d()->render_transform(); + } + [[nodiscard]] static QString render_tick_label(const Abs_Axis& axis, double tick) { + return const_cast(axis).d()->render_tick_label(tick); + } +}; +[[nodiscard]] inline Axis_Transform_Snapshot render_axis_transform(const Abs_Axis& axis) { + return Axis_Internal_Access::render_transform(axis); +} +[[nodiscard]] inline QString render_axis_tick_label(const Abs_Axis& axis, double tick) { + return Axis_Internal_Access::render_tick_label(axis, tick); +} template void Abs_Axis::Builder_T::set(Abs_Axis* ret) { ret->init(parent); Abs_Axis_Private* pd = ret->d(); - Abs_Axis_Render_State* sc = reinterpret_cast(pd->state(State_Edit)); + Abs_Axis_Render_State* sc = reinterpret_cast(pd->state(Data_Buffer_Role::Edit)); sc->x = this->x; sc->y = this->y; sc->orientation = this->orientation; diff --git a/Renderive/Axis/Axis.cpp b/Renderive/Axis/Axis.cpp index 6fccfcf..8dd5f9a 100644 --- a/Renderive/Axis/Axis.cpp +++ b/Renderive/Axis/Axis.cpp @@ -20,7 +20,7 @@ void Axis::set_coord_range(Range range) { edit->coord_length = range.target - range.origin; } Axis::Builder::Builder(std::shared_ptr parent, Qt::Orientation orientation) { - ASSERT(parent && parent->plot, "Axis build error! parent invalid"); + ASSERT(parent && parent->is_attached(), "Axis build error! parent invalid"); this->parent = parent; this->orientation = orientation; } diff --git a/Renderive/Axis/Axis.h b/Renderive/Axis/Axis.h index f2deb71..24e3271 100644 --- a/Renderive/Axis/Axis.h +++ b/Renderive/Axis/Axis.h @@ -9,10 +9,6 @@ struct Axis_Private; class LIB_DECL Axis : public Abs_Axis { public: /// 返回私有渲染数据。 - Axis_Private* d(); - Render_Object_Owner create_render_data() override; - Render_Object_Owner create_render_state() override; - Render_Object_Owner create_input_data() override; Axis(); PROP(int, number_precision) PROP(double, coord_start) @@ -22,6 +18,11 @@ public: template struct Builder_T; struct Builder; +private: + Axis_Private* d(); + Render_Object_Owner create_render_data() override; + Render_Object_Owner create_render_state() override; + Render_Object_Owner create_input_data() override; }; template struct Axis::Builder_T : Abs_Axis::Builder_T { diff --git a/Renderive/Axis/Axis_p.h b/Renderive/Axis/Axis_p.h index d2004c2..445321d 100644 --- a/Renderive/Axis/Axis_p.h +++ b/Renderive/Axis/Axis_p.h @@ -18,7 +18,7 @@ struct Axis_Private : Typed_Render_Data_Baseorientation == Qt::Horizontal ? event->position().x() : event->position().y(); - double cursor_coord = pixel_to_coord(cursor_pixel, SRC::Edit); + double cursor_coord = transform_of(*edit.state).to_coordinate(cursor_pixel); double new_coord_length = edit->coord_length * factor; double new_coord_start = cursor_coord - new_coord_length * (cursor_coord - edit->coord_start) / edit->coord_length; edit->coord_start = new_coord_start; @@ -68,7 +68,7 @@ template void Axis::Builder_T::set(Axis* ret) { Abs_Axis::Builder_T::set(ret); Axis_Private* pd = ret->d(); - Axis_Render_State* sc = reinterpret_cast(pd->state(State_Edit)); + Axis_Render_State* sc = reinterpret_cast(pd->state(Data_Buffer_Role::Edit)); PROP_RT(int, number_precision) PROP_RT(double, coord_start) PROP_RT(double, coord_length) diff --git a/Renderive/Axis/Frequency_Axis.cpp b/Renderive/Axis/Frequency_Axis.cpp index 4bb99d4..54d1c4d 100644 --- a/Renderive/Axis/Frequency_Axis.cpp +++ b/Renderive/Axis/Frequency_Axis.cpp @@ -4,7 +4,7 @@ namespace renderive { RENDERIVE_DEFINE_RENDERABLE_BINDING(Frequency_Axis, Frequency_Axis_Private, Frequency_Axis_Render_State, Frequency_Axis_Input_Data, "Frequency_Axis") Frequency_Axis::Builder::Builder(std::shared_ptr parent, Qt::Orientation orientation) { - ASSERT(parent && parent->plot, "Frequency_Axis build error! parent invalid"); + ASSERT(parent && parent->is_attached(), "Frequency_Axis build error! parent invalid"); this->parent = parent; this->orientation = orientation; } diff --git a/Renderive/Axis/Frequency_Axis.h b/Renderive/Axis/Frequency_Axis.h index 01c47a2..8e33271 100644 --- a/Renderive/Axis/Frequency_Axis.h +++ b/Renderive/Axis/Frequency_Axis.h @@ -7,12 +7,13 @@ struct Frequency_Axis_Private; class LIB_DECL Frequency_Axis : public Axis { public: /// 返回私有渲染数据。 + Frequency_Axis(); + struct Builder; +private: Frequency_Axis_Private* d(); Render_Object_Owner create_render_data() override; Render_Object_Owner create_render_state() override; Render_Object_Owner create_input_data() override; - Frequency_Axis(); - struct Builder; }; struct LIB_DECL Frequency_Axis::Builder : Builder_T{ /// 创建频率轴构造器。 diff --git a/Renderive/Axis/Frequency_Axis_p.h b/Renderive/Axis/Frequency_Axis_p.h index 1c6ec12..04a9a35 100644 --- a/Renderive/Axis/Frequency_Axis_p.h +++ b/Renderive/Axis/Frequency_Axis_p.h @@ -6,14 +6,14 @@ namespace renderive { struct Frequency_Axis_Input_Data : Axis_Input_Data {}; struct Frequency_Axis_Render_State : Axis_Render_State {}; struct Frequency_Axis_Private : Typed_Render_Data_Base { - std::vector create_label_vector(const std::vector& ticks, SRC src) override { + std::vector create_label_vector(const std::vector& ticks, const Abs_Axis_Render_State& state) override { const int exponent = unit_exponent(ticks); if (!ticks.empty()) render_state()->unit_text = "Frequency/" + unit_text(exponent); std::vector result; result.reserve(ticks.size()); for (const double& tick_coord : ticks) - result.push_back(get_true_value(get_tick_label(tick_coord, src), exponent)); + result.push_back(get_true_value(get_tick_label(tick_coord, state), exponent, state.number_precision)); return result; } static int unit_exponent(const std::vector& ticks) { @@ -41,10 +41,8 @@ struct Frequency_Axis_Private : Typed_Render_Data_Basenumber_precision; int dot_index = value.indexOf('.'); if (dot_index == -1) dot_index = value.size(); diff --git a/Renderive/Axis/Time_Axis.cpp b/Renderive/Axis/Time_Axis.cpp index 08063e7..4895d6a 100644 --- a/Renderive/Axis/Time_Axis.cpp +++ b/Renderive/Axis/Time_Axis.cpp @@ -25,7 +25,7 @@ std::shared_ptr Time_Axis::timeline_stream() { void Time_Axis::set_timeline_stream(std::shared_ptr value) { if (value) { value->set_time_point_size(time_point_size()); - value->set_tick_space(d()->state_data(SRC::Edit)->get_tick_space_hint()); + value->set_tick_space(d()->edit_state()->get_tick_space_hint()); value->set_start_coord_to_end_coord(start_coord_to_end_coord()); } d()->set_state_value(&Time_Axis_Render_State::timeline_stream, std::move(value)); @@ -55,7 +55,7 @@ QTime Time_Axis::tick_to_time(int tick) { return d()->time_by_tick(tick); } Time_Axis::Builder::Builder(std::shared_ptr parent, Qt::Orientation orientation) { - ASSERT(parent && parent->plot, "Time_Axis build error! parent invalid"); + ASSERT(parent && parent->is_attached(), "Time_Axis build error! parent invalid"); this->parent = parent; this->orientation = orientation; } @@ -63,7 +63,7 @@ std::shared_ptr Time_Axis::Builder::build() { auto ret = renderive::make_shared(); set(ret.get()); Time_Axis_Private* pd = ret->d(); - Time_Axis_Render_State* sc = reinterpret_cast(pd->state(State_Edit)); + Time_Axis_Render_State* sc = reinterpret_cast(pd->state(Data_Buffer_Role::Edit)); PROP_R(int, time_point_size) PROP_R(int, tick_pixel_space) PROP_R(QString, time_format) diff --git a/Renderive/Axis/Time_Axis.h b/Renderive/Axis/Time_Axis.h index 6f7fce1..a3005b1 100644 --- a/Renderive/Axis/Time_Axis.h +++ b/Renderive/Axis/Time_Axis.h @@ -3,15 +3,12 @@ #include namespace renderive { struct Time_Axis_Private; +struct Time_Axis_Private_Access; class Timeline_Stream; /// 时间轴,输入 tick 后按格式显示时间标签。 class LIB_DECL Time_Axis : public Abs_Axis { public: /// 返回私有渲染数据。 - Time_Axis_Private* d(); - Render_Object_Owner create_render_data() override; - Render_Object_Owner create_render_state() override; - Render_Object_Owner create_input_data() override; Time_Axis(); PROP(int, time_point_size) PROP(int, tick_pixel_space) @@ -24,6 +21,12 @@ public: /// 将 tick 转换成时间。 QTime tick_to_time(int tick); struct Builder; +private: + friend struct Time_Axis_Private_Access; + Time_Axis_Private* d(); + Render_Object_Owner create_render_data() override; + Render_Object_Owner create_render_state() override; + Render_Object_Owner create_input_data() override; }; struct Time_Axis_Prop { int time_point_size = 100; diff --git a/Renderive/Axis/Time_Axis_p.h b/Renderive/Axis/Time_Axis_p.h index 3ab1571..46bc64e 100644 --- a/Renderive/Axis/Time_Axis_p.h +++ b/Renderive/Axis/Time_Axis_p.h @@ -30,7 +30,7 @@ struct Time_Axis_Private : Typed_Render_Data_Basetimeline_stream; if (!stream) return; - Triple_Buffer_Lease state_lease = state_control.wait_mark_use_role(State_Edit); + Triple_Buffer_Lease state_lease = state_control.wait_mark_use_role(Data_Buffer_Role::Edit); auto sc = reinterpret_cast(state_by_index(state_lease.index)); int tick_space = s->get_tick_space_hint(); stream->set_time_point_size(s->time_point_size); @@ -53,12 +53,12 @@ struct Time_Axis_Private : Typed_Render_Data_Base create_label_vector(const std::vector& ticks, SRC src) override { - auto s = reinterpret_cast(state(src)); + std::vector create_label_vector(const std::vector& ticks, const Abs_Axis_Render_State& state) override { + const auto& s = static_cast(state); std::vector result; result.reserve(ticks.size()); for (double tick : ticks) - result.push_back(time_by_tick((int)tick).toString(s->time_format)); + result.push_back(time_by_tick((int)tick).toString(s.time_format)); return result; } std::vector create_tick_vector(double tick_step, const Range& range) override { @@ -67,12 +67,12 @@ struct Time_Axis_Private : Typed_Render_Data_Base create_sub_tick_vector(int sub_tick_count, const std::vector& ticks, const Range& range) override { return {}; } - void draw(QPainter* painter, const Plot_Render_Snapshot& snapshot) override { - Abs_Axis_Private::draw(painter, snapshot); + void rasterize(Frame_Raster_Context& context, const Plot_Render_Snapshot& snapshot) override { + Abs_Axis_Private::rasterize(context, snapshot); } - QString get_tick_label(double tick, SRC src) override { - auto render_state = reinterpret_cast(state(src)); - return time_by_tick((int)tick).toString(render_state->time_format); + QString get_tick_label(double tick, const Abs_Axis_Render_State& state) override { + const auto& time_state = static_cast(state); + return time_by_tick((int)tick).toString(time_state.time_format); } }; } diff --git a/Renderive/architecture/Buffer_Role.h b/Renderive/architecture/Buffer_Role.h deleted file mode 100644 index 1da520a..0000000 --- a/Renderive/architecture/Buffer_Role.h +++ /dev/null @@ -1,10 +0,0 @@ -#pragma once -#include - -namespace renderive { -enum class SRC : std::uint8_t { - Edit, - Ready, - Render -}; -} // namespace renderive diff --git a/Renderive/architecture/Frame_Raster_Context.cpp b/Renderive/architecture/Frame_Raster_Context.cpp new file mode 100644 index 0000000..270a582 --- /dev/null +++ b/Renderive/architecture/Frame_Raster_Context.cpp @@ -0,0 +1,162 @@ +#include "Frame_Raster_Context.h" +#include +#include + +namespace renderive { +Frame_Raster_Context::Frame_Raster_Context(QImage& target, QPointF base_translation) + : target_(target), base_translation_(base_translation) { + if (target_.format() == QImage::Format_ARGB32_Premultiplied && !target_.isNull()) { + blend_image_ready_ = bl_image_.create_from_data( + target_.width(), target_.height(), BL_FORMAT_PRGB32, target_.bits(), + static_cast(target_.bytesPerLine()), BL_DATA_ACCESS_RW) == BL_SUCCESS; + } +} + +Frame_Raster_Context::~Frame_Raster_Context() { + finish(); +} + +QPainter& Frame_Raster_Context::qt() { + if (backend_ == Backend::Blend2D) + end_blend(); + if (backend_ != Backend::Qt) { + qt_painter_.begin(&target_); + qt_painter_.translate(base_translation_); + backend_ = Backend::Qt; + } + return qt_painter_; +} + +BLContext* Frame_Raster_Context::blend() { + if (!blend_image_ready_) + return nullptr; + if (backend_ == Backend::Qt) + end_qt(); + if (backend_ != Backend::Blend2D) { + BLContextCreateInfo create_info{}; + create_info.thread_count = 0; + if (bl_context_.begin(bl_image_, create_info) != BL_SUCCESS) + return nullptr; + bl_context_.translate(base_translation_.x(), base_translation_.y()); + backend_ = Backend::Blend2D; + } + return &bl_context_; +} + +void Frame_Raster_Context::end_qt() { + if (backend_ == Backend::Qt) + qt_painter_.end(); + backend_ = Backend::None; +} + +void Frame_Raster_Context::end_blend() { + if (backend_ == Backend::Blend2D) + bl_context_.end(); + backend_ = Backend::None; +} + +void Frame_Raster_Context::finish() { + if (backend_ == Backend::Qt) + end_qt(); + else if (backend_ == Backend::Blend2D) + end_blend(); +} + +bool Frame_Raster_Context::solid_color(const QBrush& brush, BLRgba32& color) { + if (brush.style() != Qt::SolidPattern) + return false; + const QColor value = brush.color(); + color = BLRgba32(value.red(), value.green(), value.blue(), value.alpha()); + return true; +} + +BLStrokeCap Frame_Raster_Context::stroke_cap(Qt::PenCapStyle style) { + switch (style) { + case Qt::FlatCap: return BL_STROKE_CAP_BUTT; + case Qt::SquareCap: return BL_STROKE_CAP_SQUARE; + case Qt::RoundCap: return BL_STROKE_CAP_ROUND; + default: return BL_STROKE_CAP_BUTT; + } +} + +BLStrokeJoin Frame_Raster_Context::stroke_join(Qt::PenJoinStyle style) { + switch (style) { + case Qt::MiterJoin: return BL_STROKE_JOIN_MITER_CLIP; + case Qt::BevelJoin: return BL_STROKE_JOIN_BEVEL; + case Qt::RoundJoin: return BL_STROKE_JOIN_ROUND; + case Qt::SvgMiterJoin: return BL_STROKE_JOIN_MITER_BEVEL; + default: return BL_STROKE_JOIN_MITER_CLIP; + } +} + +bool Frame_Raster_Context::configure_stroke(const QPen& pen) { + BLRgba32 color; + if (pen.style() == Qt::NoPen || !solid_color(pen.brush(), color)) + return false; + BLContext* context = blend(); + if (!context) + return false; + const double width = pen.widthF() == 0.0 ? 1.0 : pen.widthF(); + context->set_stroke_style(color); + context->set_stroke_width(width); + context->set_stroke_caps(stroke_cap(pen.capStyle())); + context->set_stroke_join(stroke_join(pen.joinStyle())); + context->set_stroke_miter_limit(pen.miterLimit()); + context->set_stroke_dash_offset(pen.dashOffset() * width); + BLArray dashes; + const QVector pattern = pen.dashPattern(); + for (qreal dash : pattern) + dashes.append(static_cast(dash) * width); + context->set_stroke_dash_array(dashes); + return true; +} + +bool Frame_Raster_Context::stroke_polyline(std::span points, const QPen& pen) { + if (points.size() < 2 || pen.style() == Qt::NoPen) + return true; + if (!configure_stroke(pen)) { + QPainter& painter = qt(); + painter.setPen(pen); + painter.drawPolyline(points.data(), static_cast(points.size())); + return false; + } + BLPath path; + bool active = false; + for (const QPointF& point : points) { + if (!std::isfinite(point.x()) || !std::isfinite(point.y())) { + active = false; + continue; + } + if (active) + path.line_to(point.x(), point.y()); + else { + path.move_to(point.x(), point.y()); + active = true; + } + } + if (!path.is_empty()) + bl_context_.stroke_path(path); + return true; +} + +bool Frame_Raster_Context::fill_polygon(std::span points, const QBrush& brush) { + if (points.size() < 3 || brush.style() == Qt::NoBrush) + return true; + BLRgba32 color; + BLContext* context = solid_color(brush, color) ? blend() : nullptr; + if (!context) { + QPainter& painter = qt(); + painter.setPen(Qt::NoPen); + painter.setBrush(brush); + painter.drawPolygon(points.data(), static_cast(points.size())); + return false; + } + BLPath path; + path.move_to(points.front().x(), points.front().y()); + for (std::size_t i = 1; i < points.size(); ++i) + path.line_to(points[i].x(), points[i].y()); + path.close(); + context->fill_path(path, color); + return true; +} +} // namespace renderive diff --git a/Renderive/architecture/Frame_Raster_Context.h b/Renderive/architecture/Frame_Raster_Context.h new file mode 100644 index 0000000..9738534 --- /dev/null +++ b/Renderive/architecture/Frame_Raster_Context.h @@ -0,0 +1,44 @@ +#pragma once +#include +#include +#include +#include +#include +#include +#include + +namespace renderive { +/// @brief Owns mutually exclusive Qt and Blend2D sessions for one pixel target. +class Frame_Raster_Context { +public: + explicit Frame_Raster_Context(QImage& target, QPointF base_translation = {}); + ~Frame_Raster_Context(); + + Frame_Raster_Context(const Frame_Raster_Context&) = delete; + Frame_Raster_Context& operator=(const Frame_Raster_Context&) = delete; + + QPainter& qt(); + bool stroke_polyline(std::span points, const QPen& pen); + bool fill_polygon(std::span points, const QBrush& brush); + void finish(); + +private: + enum class Backend : std::uint8_t { None, Qt, Blend2D }; + + BLContext* blend(); + void end_qt(); + void end_blend(); + static bool solid_color(const QBrush& brush, BLRgba32& color); + static BLStrokeCap stroke_cap(Qt::PenCapStyle style); + static BLStrokeJoin stroke_join(Qt::PenJoinStyle style); + bool configure_stroke(const QPen& pen); + + QImage& target_; + QPointF base_translation_; + QPainter qt_painter_; + BLImage bl_image_; + BLContext bl_context_; + Backend backend_ = Backend::None; + bool blend_image_ready_{}; +}; +} // namespace renderive diff --git a/Renderive/architecture/Render_Config.h b/Renderive/architecture/Render_Config.h index ede37f0..49fd84b 100644 --- a/Renderive/architecture/Render_Config.h +++ b/Renderive/architecture/Render_Config.h @@ -45,33 +45,28 @@ enum class Renderable_Cache_Mode : std::uint8_t { /// @brief Rebuild a local image cache when state changes, then compose it. Local_Pixel }; -/// @brief Internal state triple-buffer role. -enum State_Role : std::uint8_t { - /// @brief Editable configuration state. - State_Edit, - /// @brief Committed state ready for the render thread. - State_Ready, - /// @brief State currently used by the render thread. - State_Render +/// @brief Logical role of configuration and input triple buffers. +enum class Data_Buffer_Role : std::uint8_t { + /// @brief Buffer receiving caller edits or input submissions. + Edit, + /// @brief Committed buffer waiting for render-thread consumption. + Ready, + /// @brief Immutable buffer currently consumed by the render thread. + Render }; -/// @brief Internal input triple-buffer role. -enum Input_Role : std::uint8_t { - /// @brief Input buffer receiving caller submissions. - Input_Edit, - /// @brief Input buffer ready for render-thread consumption. - Input_Ready, - /// @brief Input buffer currently consumed by the render thread. - Input_Render -}; -/// @brief Internal color-frame triple-buffer role. -enum Color_Role : std::uint8_t { +/// @brief Logical role of frame-color triple buffers. +enum class Frame_Buffer_Role : std::uint8_t { /// @brief Frame currently owned by the frontend. - Color_Front, + Front, /// @brief Frame ready to be presented. - Color_Ready, + Ready, /// @brief Frame currently rendered by the producer. - Color_Render + Render }; +template +constexpr std::uint8_t buffer_role_index(Role role) noexcept { + return static_cast(role); +} inline bool buffer_version_newer(std::uint64_t a, std::uint64_t b) { return static_cast(a - b) > 0; } diff --git a/Renderive/architecture/Render_Data.cpp b/Renderive/architecture/Render_Data.cpp index ae0dc0b..3e3e0ea 100644 --- a/Renderive/architecture/Render_Data.cpp +++ b/Renderive/architecture/Render_Data.cpp @@ -1,4 +1,6 @@ #include "Renderable.h" +#include +#include "../base/global.h" namespace renderive { QDebug operator<<(QDebug debug, const Range& range) { QDebugStateSaver saver(debug); @@ -21,6 +23,19 @@ void Render_Data::mark_input_dirty(Triple_Buffer_Lease lease, bool notify_render if (notify_render && q_ptr) q_ptr->mark_render_dirty(); } +void Render_Data::request_render_after(std::chrono::milliseconds delay) { + if (!q_ptr) + return; + std::weak_ptr renderable = q_ptr->weak_from_this(); + auto timer = std::make_shared(Global::instance()->render_scheduler().io_context()); + timer->expires_after(std::max(delay, std::chrono::milliseconds(1))); + timer->async_wait([renderable = std::move(renderable), timer = std::move(timer)](const asio::error_code& error) { + if (error) + return; + if (auto target = renderable.lock()) + target->mark_render_dirty(); + }); +} void 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) @@ -32,10 +47,10 @@ void Render_Data::drain_committed_update_states(std::pmr::vectorcomplete_until(Update_Stage::Committed); committed_update_states.push_back(std::move(slot.completion_state)); } diff --git a/Renderive/architecture/Render_Data.h b/Renderive/architecture/Render_Data.h index 72ab1b1..1d74de1 100644 --- a/Renderive/architecture/Render_Data.h +++ b/Renderive/architecture/Render_Data.h @@ -3,13 +3,13 @@ #include #include #include +#include #include #include #include #include #include #include "../base/Memory.h" -#include "Buffer_Role.h" #include "../base/Object_Semantics.h" #include "Render_Config.h" #include "Plot_Render_Context.h" @@ -24,6 +24,7 @@ class QPointF; class QResizeEvent; class QWheelEvent; namespace renderive { +class Frame_Raster_Context; struct Render_Data; struct Renderable; struct Input_Data { @@ -112,16 +113,13 @@ 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 { + Render_State* state(Data_Buffer_Role role) const { return state_by_index(state_control.read_view()[role]); } - Render_State* state(SRC src) const { - return state(static_cast(src)); - } Render_State* state_by_index(std::uint8_t index) const { return state_buffers[index]; } - Triple_Buffer_Lease mark_state_role(std::uint8_t role) { + Triple_Buffer_Lease mark_state_role(Data_Buffer_Role role) { return state_control.wait_mark_use_role(role); } void unmark_state(Triple_Buffer_Lease lease) { @@ -134,16 +132,16 @@ struct Render_Data { } state_control.reset(); } - Input_Data* input_data(std::uint8_t role) const { + Input_Data* input_data(Data_Buffer_Role role) const { return input_slot(role).data; } Input_Data* input_data_by_index(std::uint8_t index) const { return input_buffers[index].data; } - Input_Buffer_Slot& input_slot(std::uint8_t role) { + Input_Buffer_Slot& input_slot(Data_Buffer_Role role) { return input_slot_by_index(input_control.read_view()[role]); } - const Input_Buffer_Slot& input_slot(std::uint8_t role) const { + const Input_Buffer_Slot& input_slot(Data_Buffer_Role role) const { return input_slot_by_index(input_control.read_view()[role]); } Input_Buffer_Slot& input_slot_by_index(std::uint8_t index) { @@ -153,11 +151,12 @@ struct Render_Data { return input_buffers[index]; } void mark_input_dirty(Triple_Buffer_Lease lease, bool notify_render = true); + void request_render_after(std::chrono::milliseconds delay); void set_input_completion(Triple_Buffer_Lease lease, std::shared_ptr state); void drain_committed_update_states(std::pmr::vector>& output); - void clear_input_role(std::uint8_t role); + void clear_input_role(Data_Buffer_Role role); void clear_render_input() { - clear_input_role(Input_Render); + clear_input_role(Data_Buffer_Role::Render); } virtual std::size_t estimated_frame_memory(const Plot_Render_Snapshot&) { return 0; @@ -193,7 +192,7 @@ public: clear_input_buffers(); } /// 绘制当前已准备好的数据。 - virtual void draw(QPainter* painter, const Plot_Render_Snapshot& snapshot) {} + virtual void rasterize(Frame_Raster_Context& context, const Plot_Render_Snapshot& snapshot) {} /// 准备当前帧数据,默认只同步三缓冲管线。 virtual void prepare_data(const Plot_Render_Snapshot& snapshot) { sync_state_pipeline(); diff --git a/Renderive/architecture/Render_Lease.h b/Renderive/architecture/Render_Lease.h index f2b2b9d..93ed30b 100644 --- a/Renderive/architecture/Render_Lease.h +++ b/Renderive/architecture/Render_Lease.h @@ -2,7 +2,7 @@ #include #include #include -#include "Buffer_Role.h" +#include "Frame_Raster_Context.h" #include "Renderable.h" namespace renderive { template @@ -17,22 +17,19 @@ struct Typed_Render_Data_Base : Base { return reinterpret_cast(this->q_ptr); } State* render_state() override { - return reinterpret_cast(this->state(State_Render)); + return reinterpret_cast(this->state(Data_Buffer_Role::Render)); } State* ready_state() override { - return reinterpret_cast(this->state(State_Ready)); + return reinterpret_cast(this->state(Data_Buffer_Role::Ready)); } State* edit_state() override { - return reinterpret_cast(this->state(State_Edit)); + return reinterpret_cast(this->state(Data_Buffer_Role::Edit)); } Input* render_input_data() override { - return reinterpret_cast(this->input_data(Input_Render)); + return reinterpret_cast(this->input_data(Data_Buffer_Role::Render)); } Input* edit_input_data() override { - return reinterpret_cast(this->input_data(Input_Edit)); - } - State* state_data(SRC src) { - return reinterpret_cast(this->state(src)); + return reinterpret_cast(this->input_data(Data_Buffer_Role::Edit)); } State* state_data_by_index(std::uint8_t index) { return reinterpret_cast(this->state_by_index(index)); @@ -40,56 +37,51 @@ struct Typed_Render_Data_Base : Base { Input* input_data_by_index(std::uint8_t index) { return reinterpret_cast(Base::input_data_by_index(index)); } - template - requires std::derived_from - Field state_value(Field Source_State::* field, SRC src) { - return state_data(src)->*field; - } template requires std::derived_from Field edit_state_value(Field Source_State::* field) { - return state_value(field, SRC::Edit); + return edit_state()->*field; } template requires std::derived_from Field ready_state_value(Field Source_State::* field) { - return state_value(field, SRC::Ready); + return ready_state()->*field; } template requires std::derived_from Field render_state_value(Field Source_State::* field) { - return state_value(field, SRC::Render); + return render_state()->*field; } template requires std::derived_from void set_state_value(Field Source_State::* field, Field value) { - auto lease = this->state_control.wait_mark_use_role(State_Edit); + auto lease = this->state_control.wait_mark_use_role(Data_Buffer_Role::Edit); auto state = state_data_by_index(lease.index); Render_Edit_Guard guard(this, lease); state->*field = std::move(value); } void sync_state_pipeline() override { - Triple_Buffer_Lease edit_lease = this->state_control.wait_mark_use_role(State_Edit); - Triple_Buffer_Lease ready_lease = this->state_control.wait_mark_use_role(State_Ready); + Triple_Buffer_Lease edit_lease = this->state_control.wait_mark_use_role(Data_Buffer_Role::Edit); + Triple_Buffer_Lease ready_lease = this->state_control.wait_mark_use_role(Data_Buffer_Role::Ready); auto ready = reinterpret_cast(this->state_by_index(ready_lease.index)); auto edit = reinterpret_cast(this->state_by_index(edit_lease.index)); if (buffer_version_newer(edit->version, ready->version)) *ready = *edit; this->state_control.unmark_use(ready_lease); this->state_control.unmark_use(edit_lease); - this->state_control.wait_swap_role(State_Ready, State_Render, [this](const Triple_Buffer_View& view) { - auto ready = reinterpret_cast(this->state_by_index(view[State_Ready])); - auto render = reinterpret_cast(this->state_by_index(view[State_Render])); + this->state_control.wait_swap_role(Data_Buffer_Role::Ready, Data_Buffer_Role::Render, [this](const Triple_Buffer_View& view) { + auto ready = reinterpret_cast(this->state_by_index(view[Data_Buffer_Role::Ready])); + auto render = reinterpret_cast(this->state_by_index(view[Data_Buffer_Role::Render])); return buffer_version_newer(ready->version, render->version); }); - this->input_control.wait_swap_role(Input_Edit, Input_Ready, [this](const Triple_Buffer_View& view) { - const Input_Buffer_Slot& edit = this->input_slot_by_index(view[Input_Edit]); - const Input_Buffer_Slot& ready = this->input_slot_by_index(view[Input_Ready]); + this->input_control.wait_swap_role(Data_Buffer_Role::Edit, Data_Buffer_Role::Ready, [this](const Triple_Buffer_View& view) { + const Input_Buffer_Slot& edit = this->input_slot_by_index(view[Data_Buffer_Role::Edit]); + const Input_Buffer_Slot& ready = this->input_slot_by_index(view[Data_Buffer_Role::Ready]); return edit.pending && buffer_version_newer(edit.version, ready.version); }); - this->input_control.wait_swap_role(Input_Ready, Input_Render, [this](const Triple_Buffer_View& view) { - const Input_Buffer_Slot& ready = this->input_slot_by_index(view[Input_Ready]); - const Input_Buffer_Slot& render = this->input_slot_by_index(view[Input_Render]); + this->input_control.wait_swap_role(Data_Buffer_Role::Ready, Data_Buffer_Role::Render, [this](const Triple_Buffer_View& view) { + const Input_Buffer_Slot& ready = this->input_slot_by_index(view[Data_Buffer_Role::Ready]); + const Input_Buffer_Slot& render = this->input_slot_by_index(view[Data_Buffer_Role::Render]); return ready.pending && buffer_version_newer(ready.version, render.version); }); } @@ -98,14 +90,6 @@ template 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->object_name = name; - } - static Data* d(Owner* owner) { - return reinterpret_cast(owner->d_ptr); - } static Render_Object_Owner create_render_data() { return make_render_object(); } @@ -119,10 +103,11 @@ struct Renderable_Binding { #define RENDERIVE_DEFINE_RENDERABLE_BINDING(Owner, Data, State, Input, Name) \ using Owner##_Binding = ::renderive::Renderable_Binding; \ Owner::Owner() { \ - Owner##_Binding::init(this, Name); \ + d_owner = ::renderive::make_render_object(); \ + object_name = Name; \ } \ Data* Owner::d() { \ - return Owner##_Binding::d(this); \ + return reinterpret_cast(render_data()); \ } \ ::renderive::Render_Object_Owner Owner::create_render_data() { \ return Owner##_Binding::create_render_data(); \ @@ -135,7 +120,7 @@ struct Renderable_Binding { } template struct Render_State_Lease : Immovable { - explicit Render_State_Lease(Data* data) : data(data), lease(data->state_control.wait_mark_use_role(State_Edit)), state(reinterpret_cast(data->state_by_index(lease.index))) {} + explicit Render_State_Lease(Data* data) : data(data), lease(data->state_control.wait_mark_use_role(Data_Buffer_Role::Edit)), state(reinterpret_cast(data->state_by_index(lease.index))) {} ~Render_State_Lease() { data->unmark_state(lease); } @@ -151,7 +136,7 @@ struct Render_State_Lease : Immovable { }; template struct Render_Edit_Lease : Immovable { - explicit Render_Edit_Lease(Data* data) : data(data), lease(data->state_control.wait_mark_use_role(State_Edit)), state(reinterpret_cast(data->state_by_index(lease.index))) {} + explicit Render_Edit_Lease(Data* data) : data(data), lease(data->state_control.wait_mark_use_role(Data_Buffer_Role::Edit)), state(reinterpret_cast(data->state_by_index(lease.index))) {} ~Render_Edit_Lease() { ++data->state_by_index(lease.index)->version; if (data->q_ptr) @@ -170,7 +155,7 @@ struct Render_Edit_Lease : Immovable { }; template struct Render_Input_Lease : Immovable { - explicit Render_Input_Lease(Data* data, bool notify_render = true) : data(data), notify_render(notify_render), lease(data->input_control.wait_mark_use_role(Input_Edit)), input(reinterpret_cast(data->input_data_by_index(lease.index))) {} + explicit Render_Input_Lease(Data* data, bool notify_render = true) : data(data), notify_render(notify_render), lease(data->input_control.wait_mark_use_role(Data_Buffer_Role::Edit)), input(reinterpret_cast(data->input_data_by_index(lease.index))) {} ~Render_Input_Lease() { data->mark_input_dirty(lease, notify_render); data->input_control.unmark_use(lease); diff --git a/Renderive/architecture/Render_Pipeline.cpp b/Renderive/architecture/Render_Pipeline.cpp index 99f15e0..e6423b8 100644 --- a/Renderive/architecture/Render_Pipeline.cpp +++ b/Renderive/architecture/Render_Pipeline.cpp @@ -17,29 +17,29 @@ Renderable::Frame* Renderable::Render_Pipeline::color_by_index(std::uint8_t inde const Renderable::Frame* Renderable::Render_Pipeline::color_by_index(std::uint8_t index) const { return &color_buffers[index]; } -Renderable::Frame* Renderable::Render_Pipeline::color_by_role(std::uint8_t role) { +Renderable::Frame* Renderable::Render_Pipeline::color_by_role(Frame_Buffer_Role role) { return color_by_index(color_control.read_view()[role]); } -const Renderable::Frame* Renderable::Render_Pipeline::color_by_role(std::uint8_t role) const { +const Renderable::Frame* Renderable::Render_Pipeline::color_by_role(Frame_Buffer_Role role) const { return color_by_index(color_control.read_view()[role]); } Renderable::Frame_Lease Renderable::Render_Pipeline::try_render_color() { - auto record = color_control.try_mark_use_role(Color_Render); + auto record = color_control.try_mark_use_role(Frame_Buffer_Role::Render); if (record.result != Triple_Buffer_Result::Success) return {}; return {color_by_index(record.lease.index), &color_control, record.lease}; } Renderable::Frame_Lease Renderable::Render_Pipeline::try_front_color() { - auto record = color_control.try_mark_use_role(Color_Front); + auto record = color_control.try_mark_use_role(Frame_Buffer_Role::Front); if (record.result != Triple_Buffer_Result::Success) return {}; return {color_by_index(record.lease.index), &color_control, record.lease}; } Renderable::Render_Pipeline::Frame_Publish_Result Renderable::Render_Pipeline::publish_render_color(std::uint64_t now_ns) { Frame_Publish_Result result; - Frame* render = color_by_role(Color_Render); - Frame* old_ready = color_by_role(Color_Ready); - const Frame* front = color_by_role(Color_Front); + Frame* render = color_by_role(Frame_Buffer_Role::Render); + Frame* old_ready = color_by_role(Frame_Buffer_Role::Ready); + const Frame* front = color_by_role(Frame_Buffer_Role::Front); bool replace_ready = old_ready->state.load(std::memory_order_acquire) == Frame_Buffer_State::Ready && buffer_version_newer(old_ready->version.load(std::memory_order_acquire), front->version.load(std::memory_order_acquire)); if (paint_request_pending.load(std::memory_order_acquire)) { render->metadata.update_request_id = pending_update_request_id.load(std::memory_order_acquire); @@ -48,9 +48,9 @@ Renderable::Render_Pipeline::Frame_Publish_Result Renderable::Render_Pipeline::p render->metadata.publish_time = now_ns; render->metadata.frame_outcome = Frame_Outcome::Produced; render->state.store(Frame_Buffer_State::Ready, std::memory_order_release); - auto swap = color_control.wait_swap_role(Color_Ready, Color_Render, [this](const Triple_Buffer_View& view) { - const Frame* render_buffer = color_by_index(view[Color_Render]); - const Frame* ready_buffer = color_by_index(view[Color_Ready]); + auto swap = color_control.wait_swap_role(Frame_Buffer_Role::Ready, Frame_Buffer_Role::Render, [this](const Triple_Buffer_View& view) { + const Frame* render_buffer = color_by_index(view[Frame_Buffer_Role::Render]); + const Frame* ready_buffer = color_by_index(view[Frame_Buffer_Role::Ready]); return buffer_version_newer(render_buffer->version.load(std::memory_order_acquire), ready_buffer->version.load(std::memory_order_acquire)); }); if (replace_ready && swap.result == Triple_Buffer_Result::Success) @@ -61,8 +61,8 @@ Renderable::Render_Pipeline::Frame_Publish_Result Renderable::Render_Pipeline::p } Renderable::Render_Pipeline::Frame_Publish_Result Renderable::Render_Pipeline::request_ready_update(std::uint64_t now_ns) { Frame_Publish_Result result; - Frame* ready = color_by_role(Color_Ready); - const Frame* front = color_by_role(Color_Front); + Frame* ready = color_by_role(Frame_Buffer_Role::Ready); + const Frame* front = color_by_role(Frame_Buffer_Role::Front); if (ready->state.load(std::memory_order_acquire) != Frame_Buffer_State::Ready) return result; if (!buffer_version_newer(ready->version.load(std::memory_order_acquire), front->version.load(std::memory_order_acquire))) @@ -87,11 +87,11 @@ Renderable::Render_Pipeline::Frame_Consume_Result Renderable::Render_Pipeline::c Frame_Consume_Result result; result.update_was_pending = paint_request_pending.exchange(false, std::memory_order_acq_rel); auto can_swap = [this](const Triple_Buffer_View& view) { - const Frame* ready = color_by_index(view[Color_Ready]); - const Frame* front = color_by_index(view[Color_Front]); + const Frame* ready = color_by_index(view[Frame_Buffer_Role::Ready]); + const Frame* front = color_by_index(view[Frame_Buffer_Role::Front]); return ready->state.load(std::memory_order_acquire) == Frame_Buffer_State::Ready && buffer_version_newer(ready->version.load(std::memory_order_acquire), front->version.load(std::memory_order_acquire)); }; - Triple_Buffer_Swap_Record swap = color_control.try_swap_role(Color_Front, Color_Ready, can_swap); + Triple_Buffer_Swap_Record swap = color_control.try_swap_role(Frame_Buffer_Role::Front, Frame_Buffer_Role::Ready, can_swap); result.new_frame = swap.result == Triple_Buffer_Result::Success; result.lease = try_front_color(); if (result.new_frame && result.lease) { @@ -100,11 +100,11 @@ Renderable::Render_Pipeline::Frame_Consume_Result Renderable::Render_Pipeline::c return result; } bool Renderable::Render_Pipeline::has_ready_frame() const { - const Frame* ready = color_by_role(Color_Ready); - const Frame* front = color_by_role(Color_Front); + const Frame* ready = color_by_role(Frame_Buffer_Role::Ready); + const Frame* front = color_by_role(Frame_Buffer_Role::Front); return ready->state.load(std::memory_order_acquire) == Frame_Buffer_State::Ready && buffer_version_newer(ready->version.load(std::memory_order_acquire), front->version.load(std::memory_order_acquire)); } bool Renderable::Render_Pipeline::consumer_busy() const { - return color_by_role(Color_Front)->state.load(std::memory_order_acquire) == Frame_Buffer_State::Consuming; + return color_by_role(Frame_Buffer_Role::Front)->state.load(std::memory_order_acquire) == Frame_Buffer_State::Consuming; } } // namespace renderive diff --git a/Renderive/architecture/Renderable.cpp b/Renderive/architecture/Renderable.cpp index b267f2a..9fd0212 100644 --- a/Renderive/architecture/Renderable.cpp +++ b/Renderive/architecture/Renderable.cpp @@ -3,6 +3,7 @@ #include "../base/Memory.h" #include "../plot/Plot.h" #include "Renderable.h" +#include "Frame_Raster_Context.h" #include "Render_Time.h" namespace renderive { Renderable::Frame_Lease::Frame_Lease(Frame* buffer, Triple_Role_Buffer_Control* control, Triple_Buffer_Lease lease) : frame(buffer), control(control), lease(lease) {} @@ -81,8 +82,8 @@ void Renderable::init_root(Abs_Plot* plot) { init_common(plot); } void Renderable::init(const std::shared_ptr& parent) { - if (!parent || !parent->plot) { - ASSERT(parent && parent->plot, "Renderable init error! parent invalid"); + if (!parent || !parent->is_attached()) { + ASSERT(parent && parent->is_attached(), "Renderable init error! parent invalid"); return; } bool initialized = this->initialized.exchange(true, std::memory_order_acq_rel); @@ -90,38 +91,40 @@ void Renderable::init(const std::shared_ptr& parent) { ASSERT(!initialized, "Renderable init error! Renderable already initialized"); return; } - init_common(parent->plot); + auto context = parent->plot_context.lock(); + init_common(context ? context->plot.load(std::memory_order_acquire) : nullptr); parent->add_child(shared_from_this()); } void Renderable::init_common(Abs_Plot* plot) { - this->plot = plot; plot_context = plot->get_render_context(); retiring.store(false, std::memory_order_release); - if (!d_ptr) { + if (!render_data()) d_owner = create_render_data(); - d_ptr = d_owner.get(); - } - d_ptr->clear_state_buffers(); - d_ptr->clear_input_buffers(); - d_ptr->state_buffer_owners[State_Edit] = create_render_state(); - d_ptr->state_buffer_owners[State_Ready] = create_render_state(); - d_ptr->state_buffer_owners[State_Render] = create_render_state(); - d_ptr->state_buffers[State_Edit] = d_ptr->state_buffer_owners[State_Edit].get(); - d_ptr->state_buffers[State_Ready] = d_ptr->state_buffer_owners[State_Ready].get(); - d_ptr->state_buffers[State_Render] = d_ptr->state_buffer_owners[State_Render].get(); - d_ptr->input_buffer_owners[Input_Edit] = create_input_data(); - d_ptr->input_buffer_owners[Input_Ready] = create_input_data(); - d_ptr->input_buffer_owners[Input_Render] = create_input_data(); - d_ptr->input_buffers[Input_Edit].data = d_ptr->input_buffer_owners[Input_Edit].get(); - d_ptr->input_buffers[Input_Ready].data = d_ptr->input_buffer_owners[Input_Ready].get(); - d_ptr->input_buffers[Input_Render].data = d_ptr->input_buffer_owners[Input_Render].get(); - d_ptr->q_ptr = this; - d_ptr->state_buffers[State_Edit]->version = 1; + Render_Data* data = render_data(); + data->clear_state_buffers(); + data->clear_input_buffers(); + const auto edit = buffer_role_index(Data_Buffer_Role::Edit); + const auto ready = buffer_role_index(Data_Buffer_Role::Ready); + const auto render = buffer_role_index(Data_Buffer_Role::Render); + data->state_buffer_owners[edit] = create_render_state(); + data->state_buffer_owners[ready] = create_render_state(); + data->state_buffer_owners[render] = create_render_state(); + data->state_buffers[edit] = data->state_buffer_owners[edit].get(); + data->state_buffers[ready] = data->state_buffer_owners[ready].get(); + data->state_buffers[render] = data->state_buffer_owners[render].get(); + data->input_buffer_owners[edit] = create_input_data(); + data->input_buffer_owners[ready] = create_input_data(); + data->input_buffer_owners[render] = create_input_data(); + data->input_buffers[edit].data = data->input_buffer_owners[edit].get(); + data->input_buffers[ready].data = data->input_buffer_owners[ready].get(); + data->input_buffers[render].data = data->input_buffer_owners[render].get(); + data->q_ptr = this; + data->state_buffers[edit]->version = 1; plot->mark_render_state_dirty(); } void Renderable::add_child(const std::shared_ptr& child) { - if (!child || child.get() == this || child->plot != plot || !child->parent_renderable.expired()) { - ASSERT(child && child.get() != this && child->plot == plot && child->parent_renderable.expired(), "Renderable add_child error! invalid child"); + if (!child || child.get() == this || !shares_plot_with(*child) || !child->parent_renderable.expired()) { + ASSERT(child && child.get() != this && shares_plot_with(*child) && child->parent_renderable.expired(), "Renderable add_child error! invalid child"); return; } std::lock_guard lock(children_mutex); @@ -151,7 +154,7 @@ std::vector> Renderable::hit_renderables(const QPoin return hits; } void Renderable::append_hit_renderables(std::vector>& hits, const QPointF& pos) { - if (get_retiring() || !visible) + if (get_retiring() || !is_visible()) return; std::vector> children = children_snapshot(); for (int i = static_cast(children.size()) - 1; i >= 0; --i) { @@ -162,43 +165,44 @@ void Renderable::append_hit_renderables(std::vector> if (select_test(pos)) hits.push_back(shared_from_this()); } -void Renderable::render(QPainter* painter, const Plot_Render_Snapshot& snapshot) { - if (get_retiring() || !visible) +void Renderable::render(Frame_Raster_Context& context, const Plot_Render_Snapshot& snapshot) { + if (get_retiring() || !is_visible()) return; Renderable_Cache_Mode mode = get_cache_mode(); std::uint64_t begin_time = steady_now_ns(); if (mode == Renderable_Cache_Mode::Local_Pixel) - render_cached(painter, snapshot); + render_cached(context, snapshot); else - render_direct(painter, snapshot); + render_direct(context, snapshot); if (snapshot.frame_metadata) snapshot.frame_metadata->record_renderable_render(object_name, render_parent_object_name(), render_tree_depth(), mode, steady_now_ns() - begin_time); } -void Renderable::render_direct(QPainter* painter, const Plot_Render_Snapshot& snapshot) { - render_self(painter, snapshot); - render_children(painter, snapshot); +void Renderable::render_direct(Frame_Raster_Context& context, const Plot_Render_Snapshot& snapshot) { + render_self(context, snapshot); + render_children(context, snapshot); } -void Renderable::render_children(QPainter* painter, const Plot_Render_Snapshot& snapshot) { +void Renderable::render_children(Frame_Raster_Context& context, const Plot_Render_Snapshot& snapshot) { std::vector> children = children_snapshot(); for (const auto& child : children) { if (child) - child->render(painter, snapshot); + child->render(context, snapshot); } } -void Renderable::render_cached(QPainter* painter, const Plot_Render_Snapshot& snapshot) { +void Renderable::render_cached(Frame_Raster_Context& context, const Plot_Render_Snapshot& snapshot) { QSize size = snapshot.size; - QRect bounds = d_ptr ? d_ptr->pixel_bounds(size).intersected(QRect(QPoint(0, 0), size)) : QRect(QPoint(0, 0), size); + Render_Data* data = render_data(); + QRect bounds = data ? data->pixel_bounds(size).intersected(QRect(QPoint(0, 0), size)) : QRect(QPoint(0, 0), size); if (bounds.isEmpty()) return; std::uint64_t edit_version = cache_edit_version.load(std::memory_order_acquire); QString 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() == bounds.size() && cache_image.format() == QImage::Format_ARGB32 && cache_bounds == bounds; + bool ready = !cache_dirty.load(std::memory_order_acquire) && cache_ready_version.load(std::memory_order_acquire) == edit_version && cache_image.size() == bounds.size() && cache_image.format() == QImage::Format_ARGB32_Premultiplied && cache_bounds == bounds; if (ready) { if (snapshot.frame_metadata) snapshot.frame_metadata->record_renderable_cache_hit(object_name, parent_object_name, tree_depth); std::uint64_t begin_time = steady_now_ns(); - painter->drawImage(bounds.topLeft(), cache_image.image()); + context.qt().drawImage(bounds.topLeft(), cache_image.image()); if (snapshot.frame_metadata) snapshot.frame_metadata->record_renderable_cache_compose(object_name, parent_object_name, tree_depth, steady_now_ns() - begin_time, static_cast(cache_image.sizeInBytes()), static_cast(bounds.width()) * static_cast(bounds.height()), edit_version, cache_ready_version.load(std::memory_order_acquire)); return; @@ -211,7 +215,7 @@ void Renderable::render_cached(QPainter* painter, const Plot_Render_Snapshot& sn snapshot.frame_metadata->record_renderable_cache_rebuild(object_name, parent_object_name, tree_depth, steady_now_ns() - rebuild_begin_time, cache_image.isNull() ? 0 : static_cast(cache_image.sizeInBytes()), static_cast(bounds.width()) * static_cast(bounds.height()), edit_version, cache_ready_version.load(std::memory_order_acquire)); if (!cache_image.isNull()) { std::uint64_t begin_time = steady_now_ns(); - painter->drawImage(bounds.topLeft(), cache_image.image()); + context.qt().drawImage(bounds.topLeft(), cache_image.image()); if (snapshot.frame_metadata) snapshot.frame_metadata->record_renderable_cache_compose(object_name, parent_object_name, tree_depth, steady_now_ns() - begin_time, static_cast(cache_image.sizeInBytes()), static_cast(bounds.width()) * static_cast(bounds.height()), edit_version, cache_ready_version.load(std::memory_order_acquire)); } @@ -220,13 +224,12 @@ void Renderable::render_cache_image(const QRect& bounds, const Plot_Render_Snaps if (get_retiring()) return; Pmr_QImage_Buffer cache_image(Memory_Domain::Renderable_Cache_Image); - cache_image.resize(bounds.size(), QImage::Format_ARGB32); + cache_image.resize(bounds.size(), QImage::Format_ARGB32_Premultiplied); cache_image.fill(Qt::transparent); { - QPainter cache_painter(&cache_image.writable_image()); - cache_painter.translate(-bounds.topLeft()); - render_direct(&cache_painter, snapshot); - cache_painter.end(); + Frame_Raster_Context cache_context(cache_image.writable_image(), -bounds.topLeft()); + render_direct(cache_context, snapshot); + cache_context.finish(); } cache_bounds = bounds; this->cache_image = std::move(cache_image); @@ -234,84 +237,80 @@ void Renderable::render_cache_image(const QRect& bounds, const Plot_Render_Snaps bool dirty = cache_edit_version.load(std::memory_order_acquire) != version; cache_dirty.store(dirty, std::memory_order_release); } -void Renderable::render_self(QPainter* painter, const Plot_Render_Snapshot& snapshot) { - if (!d_ptr) +void Renderable::render_self(Frame_Raster_Context& context, const Plot_Render_Snapshot& snapshot) { + Render_Data* data = render_data(); + if (!data) return; - auto lease = d_ptr->mark_state_role(State_Render); + auto lease = data->mark_state_role(Data_Buffer_Role::Render); if (!lease) return; Renderable_Cache_Mode mode = get_cache_mode(); std::uint64_t begin_time = steady_now_ns(); - draw(painter, snapshot); + data->rasterize(context, snapshot); std::uint64_t draw_time = steady_now_ns() - begin_time; - d_ptr->unmark_state(lease); + data->unmark_state(lease); if (snapshot.frame_metadata) snapshot.frame_metadata->record_renderable_self_draw(object_name, render_parent_object_name(), render_tree_depth(), mode, draw_time); } -void Renderable::draw(QPainter* painter, const Plot_Render_Snapshot& snapshot) { - if (get_retiring()) - return; - d_ptr->draw(painter, snapshot); -} void Renderable::prepare_data(const Plot_Render_Snapshot& snapshot) { if (get_retiring()) return; - d_ptr->prepare_data(snapshot); + render_data()->prepare_data(snapshot); } bool Renderable::select_test(const QPointF& pos) { if (get_retiring()) return false; - return d_ptr->select_test(pos); + return render_data()->select_test(pos); } void Renderable::plot_event(QEvent* event) { if (get_retiring()) return; - d_ptr->plot_event(event); + render_data()->plot_event(event); } void Renderable::mousePressEvent(QMouseEvent* event) { if (get_retiring()) return; - d_ptr->mousePressEvent(event); + render_data()->mousePressEvent(event); } void Renderable::mouseMoveEvent(QMouseEvent* event) { if (get_retiring()) return; - d_ptr->mouseMoveEvent(event); + render_data()->mouseMoveEvent(event); } void Renderable::mouseReleaseEvent(QMouseEvent* event) { if (get_retiring()) return; - d_ptr->mouseReleaseEvent(event); + render_data()->mouseReleaseEvent(event); } void Renderable::set_hover_state(const QPoint& pos, bool active) { if (get_retiring()) return; - d_ptr->set_hover_state(pos, active); + render_data()->set_hover_state(pos, active); } void Renderable::keyPressEvent(QKeyEvent* event) { if (get_retiring()) return; - d_ptr->keyPressEvent(event); + render_data()->keyPressEvent(event); } void Renderable::keyReleaseEvent(QKeyEvent* event) { if (get_retiring()) return; - d_ptr->keyReleaseEvent(event); + render_data()->keyReleaseEvent(event); } void Renderable::first_resize_event(QResizeEvent* event) { if (get_retiring()) return; - d_ptr->first_resize_event(event); + render_data()->first_resize_event(event); } void Renderable::resizeEvent(QResizeEvent* event) { if (get_retiring()) return; - d_ptr->resizeEvent(event); + render_data()->resizeEvent(event); } void Renderable::wheelEvent(QWheelEvent* event) { if (get_retiring()) return; - d_ptr->wheelEvent(event); + render_data()->wheelEvent(event); } void Renderable::mark_render_dirty() { if (get_retiring()) @@ -331,15 +330,31 @@ void Renderable::mark_render_dirty() { } bool Renderable::ok() const { auto context = plot_context.lock(); - return d_ptr && initialized.load(std::memory_order_acquire) && !get_retiring() && context && !context->destroying.load(std::memory_order_acquire) && !context->first_resize.load(std::memory_order_acquire) && !context->first_prepare_data.load(std::memory_order_acquire); + return render_data() && initialized.load(std::memory_order_acquire) && !get_retiring() && context && !context->destroying.load(std::memory_order_acquire) && !context->first_resize.load(std::memory_order_acquire) && !context->first_prepare_data.load(std::memory_order_acquire); } bool Renderable::get_retiring() const { return retiring.load(std::memory_order_acquire); } +bool Renderable::is_attached() const { + auto context = plot_context.lock(); + return context && !context->destroying.load(std::memory_order_acquire) && context->plot.load(std::memory_order_acquire); +} +bool Renderable::shares_plot_with(const Renderable& other) const { + auto context = plot_context.lock(); + auto other_context = other.plot_context.lock(); + return context && context == other_context && !context->destroying.load(std::memory_order_acquire) && context->plot.load(std::memory_order_acquire); +} +bool Renderable::is_visible() const { + return visible.load(std::memory_order_acquire); +} +void Renderable::set_visible(bool value) { + if (visible.exchange(value, std::memory_order_acq_rel) != value) + mark_render_dirty(); +} void Renderable::detach_from_plot_tree() { retiring.store(true, std::memory_order_release); - if (d_ptr) - d_ptr->cancel_pending_completions(); + if (Render_Data* data = render_data()) + data->cancel_pending_completions(); std::vector> children_snapshot_value = children_snapshot(); { std::lock_guard lock(children_mutex); @@ -349,13 +364,14 @@ void Renderable::detach_from_plot_tree() { if (child) child->detach_from_plot_tree(); } - plot = nullptr; cache_image.clear(); cache_bounds = {}; } Renderable::~Renderable() { detach_from_plot_tree(); d_owner.reset(); - d_ptr = nullptr; +} +Render_Data* Renderable::render_data() const { + return d_owner.get(); } } // namespace renderive diff --git a/Renderive/architecture/Renderable.h b/Renderive/architecture/Renderable.h index bf47fbb..34e7c22 100644 --- a/Renderive/architecture/Renderable.h +++ b/Renderive/architecture/Renderable.h @@ -11,9 +11,13 @@ #include "Frame_Scheduler.h" #include "Render_Data.h" namespace renderive { +class Frame_Raster_Context; struct Update_State; +struct Renderable_Private_Access; +template +struct Hover_Info_Renderable; struct Renderable : std::enable_shared_from_this { - enum class Job_State { + enum class Job_State : std::uint8_t { Idle, Queued, Running @@ -67,8 +71,8 @@ struct Renderable : std::enable_shared_from_this { bool has_new_state() const; Frame* color_by_index(std::uint8_t index); const Frame* color_by_index(std::uint8_t index) const; - Frame* color_by_role(std::uint8_t role); - const Frame* color_by_role(std::uint8_t role) const; + Frame* color_by_role(Frame_Buffer_Role role); + const Frame* color_by_role(Frame_Buffer_Role role) const; Frame_Lease try_render_color(); Frame_Lease try_front_color(); Frame_Publish_Result publish_render_color(std::uint64_t now_ns); @@ -92,8 +96,7 @@ struct Renderable : std::enable_shared_from_this { void remove_child(const std::shared_ptr& child); std::vector> children_snapshot() const; std::vector> hit_renderables(const QPointF& pos); - void render(QPainter* painter, const Plot_Render_Snapshot& snapshot); - virtual void draw(QPainter* painter, const Plot_Render_Snapshot& snapshot); + void render(Frame_Raster_Context& context, const Plot_Render_Snapshot& snapshot); virtual void prepare_data(const Plot_Render_Snapshot& snapshot); virtual bool select_test(const QPointF& pos); virtual void plot_event(QEvent* event); @@ -114,17 +117,18 @@ struct Renderable : std::enable_shared_from_this { int render_tree_depth() const; void mark_render_dirty(); QString object_name; - Render_Object_Owner d_owner; - Render_Data* d_ptr{}; - Abs_Plot* plot{}; - std::weak_ptr parent_renderable; [[nodiscard]] bool ok() const; [[nodiscard]] bool get_retiring() const; + [[nodiscard]] bool is_attached() const; + [[nodiscard]] bool shares_plot_with(const Renderable& other) const; + [[nodiscard]] bool is_visible() const; + void set_visible(bool value); virtual ~Renderable(); - bool visible = true; +private: + std::atomic_bool visible{true}; + std::weak_ptr parent_renderable; std::vector> children; 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}; @@ -133,11 +137,21 @@ struct Renderable : std::enable_shared_from_this { QRect cache_bounds; std::atomic_bool retiring{true}; std::atomic_bool initialized{false}; +protected: + [[nodiscard]] Render_Data* render_data() const; + Render_Object_Owner d_owner; + std::weak_ptr plot_context; +private: + friend class Abs_Plot; + friend struct Abs_Plot_Private; + friend struct Renderable_Private_Access; + template + friend struct Hover_Info_Renderable; void init_common(Abs_Plot* plot); - void render_direct(QPainter* painter, const Plot_Render_Snapshot& snapshot); - void render_cached(QPainter* painter, const Plot_Render_Snapshot& snapshot); - void render_self(QPainter* painter, const Plot_Render_Snapshot& snapshot); - void render_children(QPainter* painter, const Plot_Render_Snapshot& snapshot); + void render_direct(Frame_Raster_Context& context, const Plot_Render_Snapshot& snapshot); + void render_cached(Frame_Raster_Context& context, const Plot_Render_Snapshot& snapshot); + void render_self(Frame_Raster_Context& context, const Plot_Render_Snapshot& snapshot); + void render_children(Frame_Raster_Context& context, const Plot_Render_Snapshot& snapshot); void render_cache_image(const QRect& bounds, const Plot_Render_Snapshot& snapshot, std::uint64_t version); void append_hit_renderables(std::vector>& hits, const QPointF& pos); void detach_from_plot_tree(); diff --git a/Renderive/architecture/Triple_Buffer.h b/Renderive/architecture/Triple_Buffer.h index 0e706d1..1b4ac8b 100644 --- a/Renderive/architecture/Triple_Buffer.h +++ b/Renderive/architecture/Triple_Buffer.h @@ -2,6 +2,7 @@ #include #include #include +#include namespace renderive { static constexpr std::uint8_t triple_buffer_invalid_index = 255; /// 三缓冲操作结果。 @@ -11,9 +12,9 @@ enum class Triple_Buffer_Result : std::uint8_t { /// 目标物理缓冲正在被占用。 Busy, /// 角色映射在操作期间发生变化。 - State_Changed, + Mapping_Changed, /// 交换条件拒绝,不代表错误。 - Condition_Failed + Condition_Rejected }; /// 三个逻辑角色到三个物理缓冲的当前映射。 struct Triple_Buffer_View { @@ -22,6 +23,11 @@ struct Triple_Buffer_View { std::uint8_t operator[](std::uint8_t role) const { return index[role]; } + template + requires std::is_enum_v + std::uint8_t operator[](Role role) const { + return index[static_cast(role)]; + } }; /// 一次物理缓冲占用租约。 struct Triple_Buffer_Lease { @@ -32,12 +38,12 @@ struct Triple_Buffer_Lease { } }; struct Triple_Buffer_Mark_Record { - Triple_Buffer_Result result = Triple_Buffer_Result::Condition_Failed; + Triple_Buffer_Result result = Triple_Buffer_Result::Condition_Rejected; 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_Result result = Triple_Buffer_Result::Condition_Rejected; Triple_Buffer_View old_view{}; std::uint8_t busy_index = triple_buffer_invalid_index; }; @@ -64,22 +70,25 @@ public: /// 等待并占用指定物理缓冲。 /// 尝试占用指定角色当前对应的物理缓冲。 - Triple_Buffer_Mark_Record try_mark_use_role(std::uint8_t role) { + template + Triple_Buffer_Mark_Record try_mark_use_role(Role role) { + const auto role_index = static_cast(role); auto order = slot_state.load(std::memory_order_acquire); auto view = decode_order(order); - auto index = view[role]; + auto index = view[role_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); - return {Triple_Buffer_Result::State_Changed, {}, triple_buffer_invalid_index}; + return {Triple_Buffer_Result::Mapping_Changed, {}, triple_buffer_invalid_index}; } return {Triple_Buffer_Result::Success, {index}, triple_buffer_invalid_index}; } /// 等待并占用指定角色当前对应的物理缓冲。 - Triple_Buffer_Lease wait_mark_use_role(std::uint8_t role) { + template + Triple_Buffer_Lease wait_mark_use_role(Role role) { for (;;) { auto record = try_mark_use_role(role); if (record.result == Triple_Buffer_Result::Success) { @@ -95,18 +104,20 @@ public: 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) { + template + Triple_Buffer_Swap_Record try_swap_role(Left_Role left_role, Right_Role 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, busy_index); + return try_swap_role_once(static_cast(left_role), static_cast(right_role), can_swap, busy_index); } /// 等待必要缓冲后交换两个角色的物理缓冲映射。 - template - Triple_Buffer_Swap_Record wait_swap_role(std::uint8_t left_role, std::uint8_t right_role, Can_Swap can_swap) { + template + Triple_Buffer_Swap_Record wait_swap_role(Left_Role left_role, Right_Role right_role, Can_Swap can_swap) { + const auto left_index = static_cast(left_role); + const auto right_index = static_cast(right_role); for (;;) { std::uint8_t busy_index = triple_buffer_invalid_index; - auto record = try_swap_role_once(left_role, right_role, can_swap, busy_index); - if (record.result == Triple_Buffer_Result::Success || record.result == Triple_Buffer_Result::Condition_Failed) { + auto record = try_swap_role_once(left_index, right_index, can_swap, busy_index); + if (record.result == Triple_Buffer_Result::Success || record.result == Triple_Buffer_Result::Condition_Rejected) { return record; } if (record.result == Triple_Buffer_Result::Busy) { @@ -207,17 +218,17 @@ private: auto cur_order = slot_state.load(std::memory_order_acquire); if (cur_order != old_order) { unmark_two_indices(left_index, right_index); - return {Triple_Buffer_Result::State_Changed, old_view, triple_buffer_invalid_index}; + return {Triple_Buffer_Result::Mapping_Changed, old_view, triple_buffer_invalid_index}; } if (!can_swap(old_view)) { unmark_two_indices(left_index, right_index); - return {Triple_Buffer_Result::Condition_Failed, old_view, triple_buffer_invalid_index}; + return {Triple_Buffer_Result::Condition_Rejected, 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_two_indices(left_index, right_index); - return {Triple_Buffer_Result::State_Changed, old_view, triple_buffer_invalid_index}; + return {Triple_Buffer_Result::Mapping_Changed, old_view, triple_buffer_invalid_index}; } unmark_two_indices(left_index, right_index); slot_state.notify_all(); diff --git a/Renderive/architecture/export.h b/Renderive/architecture/export.h index 95c5bfd..2fd7547 100644 --- a/Renderive/architecture/export.h +++ b/Renderive/architecture/export.h @@ -1,8 +1,5 @@ #pragma once -#include "Render_Config.h" -#include "Render_Data.h" -#include "Render_Lease.h" #include "Renderable.h" -#include "Triple_Buffer.h" -#include "global.h" +#include "Timeline_Stream.h" +#include "Update_Completion.h" #include "../plot/Plot.h" diff --git a/Renderive/base/Interpolation.h b/Renderive/base/Interpolation.h new file mode 100644 index 0000000..52b5376 --- /dev/null +++ b/Renderive/base/Interpolation.h @@ -0,0 +1,34 @@ +#pragma once +#include + +namespace renderive { +/// @brief Geometric connection used between adjacent line samples. +enum class Line_Interpolation : std::uint8_t { + /// @brief Use the nearest source sample. + Nearest_Sample, + /// @brief Connect adjacent samples linearly. + Linear, + /// @brief Hold the previous sample until the next sample boundary. + Step_Previous, + /// @brief Use the next sample immediately after the previous boundary. + Step_Next, + /// @brief Use cubic interpolation over adjacent source samples. + Cubic +}; + +/// @brief Numeric domain in which interpolation is evaluated. +enum class Interpolation_Value_Domain : std::uint8_t { + /// @brief Interpolate values directly in their display domain. + Display, + /// @brief Convert dB values to linear power, interpolate, then convert back. + Linear_Power +}; + +/// @brief Upsampling filter used when an image is mapped to a larger target. +enum class Image_Interpolation_Mode : std::uint8_t { + /// @brief Use the nearest source pixel. + Nearest, + /// @brief Blend the nearest four source pixels. + Bilinear +}; +} // namespace renderive diff --git a/Renderive/base/Interpolation_p.h b/Renderive/base/Interpolation_p.h new file mode 100644 index 0000000..da95de6 --- /dev/null +++ b/Renderive/base/Interpolation_p.h @@ -0,0 +1,101 @@ +#pragma once +#include +#include +#include +#include +#include +#include +#include "../Axis/Abs_Axis_p.h" +#include "Frame_Memory.h" +#include "Interpolation.h" +namespace renderive { +struct Line_Sample_Window { + double first_index{}; + double last_index{}; + int sample_count{}; + explicit operator bool() const { + return sample_count > 0; + } +}; +inline bool intersect_range(const Range& data_range, const Range& clip_range, Range& result) { + double data_min = std::min(data_range.origin, data_range.target); + double data_max = std::max(data_range.origin, data_range.target); + double clip_min = std::min(clip_range.origin, clip_range.target); + double clip_max = std::max(clip_range.origin, clip_range.target); + double lower = std::max(data_min, clip_min); + double upper = std::min(data_max, clip_max); + if (lower > upper) + return false; + result = data_range.origin <= data_range.target ? Range{lower, upper} : Range{upper, lower}; + return true; +} +inline double source_index_to_coord(const Range& data_range, int total_count, double index) { + return data_range.origin + data_range.length() * index / static_cast(total_count - 1); +} +inline double coord_to_source_index(const Range& data_range, int total_count, double coord) { + return static_cast(total_count - 1) * (coord - data_range.origin) / data_range.length(); +} +inline Line_Sample_Window line_sample_window(const Axis_Transform_Snapshot& transform, const Range& data_range, int total_count, int available_count, bool visible_range_only) { + if (total_count < 2 || available_count < 1 || data_range.length() == 0.0) + return {}; + Range available_range{data_range.origin, source_index_to_coord(data_range, total_count, available_count - 1)}; + Range sample_range = available_range; + if (visible_range_only && !intersect_range(available_range, transform.coordinate_range, sample_range)) + return {}; + const double range_first = coord_to_source_index(data_range, total_count, sample_range.origin); + const double range_last = coord_to_source_index(data_range, total_count, sample_range.target); + const int first_index = std::clamp(static_cast(std::floor(std::min(range_first, range_last))), 0, available_count - 1); + const int last_index = std::clamp(static_cast(std::ceil(std::max(range_first, range_last))), first_index, available_count - 1); + return {static_cast(first_index), static_cast(last_index), last_index - first_index + 1}; +} +template +double interpolate_line_value(double index, int count, Line_Interpolation mode, Interpolation_Value_Domain domain, Value_At value_at) { + 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); + double t = index - left; + double left_value = value_at(left); + double right_value = value_at(right); + switch (mode) { + case Line_Interpolation::Nearest_Sample: + return value_at(static_cast(std::round(index))); + case Line_Interpolation::Linear: + if (domain == Interpolation_Value_Domain::Linear_Power) { + const double left_power = std::pow(10.0, left_value / 10.0); + const double right_power = std::pow(10.0, right_value / 10.0); + return 10.0 * std::log10(left_power + (right_power - left_power) * t); + } + return left_value + (right_value - left_value) * t; + case Line_Interpolation::Step_Previous: + return left_value; + case Line_Interpolation::Step_Next: + return t == 0.0 ? left_value : right_value; + case Line_Interpolation::Cubic: { + 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)); + 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; + return ((a * t + b) * t + c) * t + p1; + } + } + return left_value; +} +template +std::pmr::vector create_line_points(const Axis_Transform_Snapshot& x_transform, const Axis_Transform_Snapshot& y_transform, const Range& data_range, int total_count, int available_count, bool visible_range_only, Line_Interpolation mode, Interpolation_Value_Domain domain, Value_At value_at) { + Line_Sample_Window window = line_sample_window(x_transform, data_range, total_count, available_count, visible_range_only); + if (!window) + return {}; + std::pmr::vector points(window.sample_count, frame_memory_resource()); + 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(data_range, total_count, index); + double value = interpolate_line_value(index, available_count, mode, domain, value_at); + points[i] = QPointF(x_transform.to_pixel(coord), y_transform.to_pixel(value)); + } + return points; +} +} // namespace renderive diff --git a/Renderive/base/global.cpp b/Renderive/base/global.cpp index 05e2034..9ba690c 100644 --- a/Renderive/base/global.cpp +++ b/Renderive/base/global.cpp @@ -141,13 +141,13 @@ void Render_Scheduler::register_plot_on_scheduler(Abs_Plot* plot) { } void Render_Scheduler::remove_plot_on_scheduler(Abs_Plot* plot) { d->plots.erase(std::remove(d->plots.begin(), d->plots.end(), plot), d->plots.end()); - Abs_Plot_Private* data = plot->d; + Abs_Plot_Private* data = Abs_Plot_Private_Access::data(*plot); data->render_enabled.store(false, std::memory_order_release); data->on_render_paused(); } void Render_Scheduler::shutdown_on_scheduler() { for (Abs_Plot* plot : d->plots) { - Abs_Plot_Private* data = plot->d; + Abs_Plot_Private* data = Abs_Plot_Private_Access::data(*plot); data->destroying.store(true, std::memory_order_release); data->render_enabled.store(false, std::memory_order_release); data->on_render_paused(); @@ -155,7 +155,7 @@ void Render_Scheduler::shutdown_on_scheduler() { d->plots.clear(); } void Render_Scheduler::initialize_plot_on_scheduler(Abs_Plot* plot) { - Abs_Plot_Private* data = plot->d; + Abs_Plot_Private* data = Abs_Plot_Private_Access::data(*plot); data->render_context->render_width.store(plot->size().width(), std::memory_order_release); data->render_context->render_height.store(plot->size().height(), std::memory_order_release); if (plot->isVisible()) { diff --git a/Renderive/plot/Plot.cpp b/Renderive/plot/Plot.cpp index dac80fa..daf9658 100644 --- a/Renderive/plot/Plot.cpp +++ b/Renderive/plot/Plot.cpp @@ -17,20 +17,13 @@ #include "../base/global.h" #include "../architecture/Render_Time.h" #include "Plot_p.h" +#include "../architecture/Frame_Raster_Context.h" #include "../architecture/Update_Completion.h" namespace renderive { namespace { int renderable_prepare_priority(const std::shared_ptr& able) { return dynamic_cast(able.get()) ? 0 : 1; } -std::size_t estimate_frame_memory(const std::vector>& snapshot, const Plot_Render_Snapshot& frame_snapshot) { - std::size_t bytes = 64 * 1024; - for (const auto& able : snapshot) { - if (able && able->d_ptr) - bytes += able->d_ptr->estimated_frame_memory(frame_snapshot); - } - return bytes; -} template struct Cpu_Task_State { Cpu_Task_State(Handler&& handler, asio::any_io_executor executor) : handler(std::move(handler)), executor(std::move(executor)) {} @@ -112,7 +105,7 @@ void Abs_Plot::init_renderable_tree() { d->root_renderable->init_root(this); } void Abs_Plot::remove_renderable(const std::shared_ptr& able) { - if (!able || able->plot != this || able == d->root_renderable) + if (!able || !able->is_attached() || !d->root_renderable->shares_plot_with(*able) || able == d->root_renderable) return; std::shared_ptr parent = able->parent_renderable.lock(); if (parent) @@ -129,8 +122,8 @@ std::shared_ptr Abs_Plot::get_root_renderable() const { return d->root_renderable; } std::shared_ptr Abs_Plot::create_renderable_node(const std::shared_ptr& parent, const QString& object_name) const { - if (!parent || parent->plot != this) { - ASSERT(parent && parent->plot == this, "Abs_Plot create_renderable_node error! parent invalid"); + if (!parent || !parent->is_attached() || !d->root_renderable->shares_plot_with(*parent)) { + ASSERT(parent && parent->is_attached() && d->root_renderable->shares_plot_with(*parent), "Abs_Plot create_renderable_node error! parent invalid"); return {}; } auto able = renderive::make_shared(); @@ -305,15 +298,19 @@ bool Abs_Plot_Private::submit_render_once(std::uint64_t render_request_time) { std::pmr::vector> frame_update_states(memory_resource(Memory_Domain::Update_Completion)); frame.prepare_begin_time = steady_now_ns(); for (const auto& cur : snapshot) { - if (!cur || cur->get_retiring() || !cur->visible || !cur->d_ptr) + if (!cur || cur->get_retiring() || !cur->is_visible() || !cur->render_data()) continue; cur->prepare_data(*frame_snapshot); - cur->d_ptr->drain_committed_update_states(frame_update_states); + cur->render_data()->drain_committed_update_states(frame_update_states); ++frame.prepared_output_count; } frame.prepare_end_time = steady_now_ns(); render_context->first_prepare_data.exchange(false, std::memory_order_acq_rel); - std::size_t frame_memory_hint = estimate_frame_memory(snapshot, *frame_snapshot); + std::size_t frame_memory_hint = 64 * 1024; + for (const auto& able : snapshot) { + if (able && able->render_data()) + frame_memory_hint += able->render_data()->estimated_frame_memory(*frame_snapshot); + } struct Render_Color_Task_Context { Abs_Plot_Private* private_data{}; Renderable::Frame_Lease color_lease; @@ -372,7 +369,7 @@ bool Abs_Plot_Private::submit_render_once(std::uint64_t render_request_time) { void Abs_Plot_Private::finish_render() { if (destroying.load(std::memory_order_acquire)) return; - Renderable::Frame* render_color = pipeline.color_by_role(Color_Render); + Renderable::Frame* render_color = pipeline.color_by_role(Frame_Buffer_Role::Render); if (!render_color || render_color->state.load(std::memory_order_acquire) != Frame_Buffer_State::Producing) return; bool ready_occupied = pipeline.has_ready_frame(); @@ -433,12 +430,12 @@ void Abs_Plot_Private::render_color(Renderable::Frame* color, const std::shared_ frame.render_begin_time = steady_now_ns(); QSize size = render_snapshot_data.size; supersede_update_states(color->presented_update_states); - color->image.resize(size, QImage::Format_ARGB32); + color->image.resize(size, QImage::Format_ARGB32_Premultiplied); color->image.fill(render_snapshot_data.background_color); - QPainter painter(&color->image.writable_image()); + Frame_Raster_Context raster_context(color->image.writable_image()); for (const auto& cur : snapshot) { ++frame.renderable_count; - if (!cur || cur->get_retiring() || !cur->visible) + if (!cur || cur->get_retiring() || !cur->is_visible()) continue; ++frame.visible_renderable_count; if (cur->get_cache_mode() == Renderable_Cache_Mode::Local_Pixel) @@ -448,8 +445,8 @@ void Abs_Plot_Private::render_color(Renderable::Frame* color, const std::shared_ } frame.draw_begin_time = steady_now_ns(); if (root) - root->render(&painter, render_snapshot_data); - painter.end(); + root->render(raster_context, render_snapshot_data); + raster_context.finish(); frame.draw_end_time = steady_now_ns(); frame.render_end_time = steady_now_ns(); color->version.store(frame.frame_id, std::memory_order_release); diff --git a/Renderive/plot/Plot.h b/Renderive/plot/Plot.h index e364a80..4705974 100644 --- a/Renderive/plot/Plot.h +++ b/Renderive/plot/Plot.h @@ -15,7 +15,6 @@ class LIB_DECL Abs_Plot : public QWidget { public: virtual void init(); QColor background_color(); - [[nodiscard]] std::shared_ptr get_render_context() const; [[nodiscard]] std::shared_ptr get_root_renderable() const; [[nodiscard]] std::shared_ptr create_renderable_node(const std::shared_ptr& parent, const QString& object_name = {}) const; QString object_name; @@ -28,9 +27,13 @@ public: void pause_render(); ~Abs_Plot() override; explicit Abs_Plot(Abs_Plot_Private* private_data); - Abs_Plot_Private* d{}; protected: + Abs_Plot_Private* d{}; bool event(QEvent* event) final; void paintEvent(QPaintEvent* event) final; +private: + friend struct Renderable; + friend struct Abs_Plot_Private_Access; + [[nodiscard]] std::shared_ptr get_render_context() const; }; } // namespace renderive diff --git a/Renderive/plot/Plot_p.h b/Renderive/plot/Plot_p.h index c0ea279..8a06010 100644 --- a/Renderive/plot/Plot_p.h +++ b/Renderive/plot/Plot_p.h @@ -53,4 +53,15 @@ struct Abs_Plot_Private { virtual void try_schedule_render() = 0; static void render_color(Renderable::Frame* color, const std::shared_ptr& root, const Renderable_Snapshot& snapshot, const Plot_Render_Snapshot& render_snapshot_data, std::pmr::vector>&& update_states); }; +struct Abs_Plot_Private_Access { + static Abs_Plot_Private* data(Abs_Plot& plot) { + return plot.d; + } + static const Abs_Plot_Private* data(const Abs_Plot& plot) { + return plot.d; + } + static int active_render_tasks(const Abs_Plot& plot) { + return plot.d->active_render_tasks.load(std::memory_order_acquire); + } +}; } // namespace renderive diff --git a/Renderive/plottable/Afterglow_p.h b/Renderive/plottable/Afterglow_p.h index 0bb8fcd..b5f37cc 100644 --- a/Renderive/plottable/Afterglow_p.h +++ b/Renderive/plottable/Afterglow_p.h @@ -52,12 +52,14 @@ struct Afterglow_Private : Typed_Render_Datafrequency_range.length() == 0.0 || s->power_range.length() == 0.0) return; - double x = frequency_axis->d()->render_coord_to_pixel(s->frequency_range.origin); - double y = power_axis->d()->render_coord_to_pixel(s->power_range.origin); - double w = frequency_axis->d()->render_coord_to_pixel(s->frequency_range.target) - x; - double h = power_axis->d()->render_coord_to_pixel(s->power_range.target) - y; - Range x_axis_range = frequency_axis->d()->render_coord_range(); - Range y_axis_range = power_axis->d()->render_coord_range(); + const auto x_transform = render_axis_transform(*frequency_axis); + const auto y_transform = render_axis_transform(*power_axis); + double x = x_transform.to_pixel(s->frequency_range.origin); + double y = y_transform.to_pixel(s->power_range.origin); + double w = x_transform.to_pixel(s->frequency_range.target) - x; + double h = y_transform.to_pixel(s->power_range.target) - y; + Range x_axis_range = x_transform.coordinate_range; + Range y_axis_range = y_transform.coordinate_range; double hr = ((x_axis_range.origin < x_axis_range.target) ^ (s->frequency_range.origin < s->frequency_range.target)) ? -1.0 : 1.0; double vr = ((y_axis_range.origin < y_axis_range.target) ^ (s->power_range.origin < s->power_range.target)) ? -1.0 : 1.0; painter->save(); @@ -65,7 +67,8 @@ struct Afterglow_Private : Typed_Render_Datarestore(); } - void draw(QPainter* painter, const Plot_Render_Snapshot&) override { + void rasterize(Frame_Raster_Context& context, const Plot_Render_Snapshot&) override { + QPainter* painter = &context.qt(); Afterglow_Render_State* s = render_state(); draw_image(painter, s); } @@ -108,11 +111,11 @@ struct Afterglow_Private : Typed_Render_Data void Afterglow::Builder_T::init(std::shared_ptr parent, std::shared_ptr frequency_axis, std::shared_ptr power_axis) { - ASSERT(parent && parent->plot, "Afterglow build error! parent invalid"); + ASSERT(parent && parent->is_attached(), "Afterglow build error! parent invalid"); ASSERT(frequency_axis && power_axis, "Afterglow build error! axis == nullptr"); - ASSERT(frequency_axis->plot != nullptr, "Afterglow build error! frequency_axis->plot == nullptr"); - ASSERT(frequency_axis->plot == power_axis->plot, "Afterglow build error! frequency_axis->plot != power_axis->plot"); - ASSERT(parent->plot == frequency_axis->plot, "Afterglow build error! parent->plot != frequency_axis->plot"); + ASSERT(frequency_axis->is_attached(), "Afterglow build error! frequency_axis is detached"); + ASSERT(frequency_axis->shares_plot_with(*power_axis), "Afterglow build error! axes belong to different plots"); + ASSERT(parent->shares_plot_with(*frequency_axis), "Afterglow build error! parent and axes belong to different plots"); this->parent = parent; static_cast(this)->frequency_axis = frequency_axis; static_cast(this)->power_axis = power_axis; @@ -122,7 +125,7 @@ void Afterglow::Builder_T::set(Afterglow* ret) { ret->init(parent); std::shared_ptr owner = ret->shared_from_this(); Afterglow_Private* pd = ret->d(); - Afterglow_Render_State* sc = reinterpret_cast(pd->state(State_Edit)); + Afterglow_Render_State* sc = reinterpret_cast(pd->state(Data_Buffer_Role::Edit)); PROP_RT(std::weak_ptr, frequency_axis) PROP_RT(std::weak_ptr, power_axis) PROP_RT(Range, frequency_range) diff --git a/Renderive/plottable/Audio_Frequency.cpp b/Renderive/plottable/Audio_Frequency.cpp index 2d0b8cf..0cc9a3c 100644 --- a/Renderive/plottable/Audio_Frequency.cpp +++ b/Renderive/plottable/Audio_Frequency.cpp @@ -3,49 +3,25 @@ #include "../base/Memory.h" namespace renderive { RENDERIVE_DEFINE_RENDERABLE_BINDING(Audio_Frequency, Audio_Frequency_Private, Audio_Frequency_Render_State, Audio_Frequency_Input_Data, "Audio_Frequency") -int Audio_Frequency::time_point_size() { - auto axis = time_axis(); - return axis ? axis->time_point_size() : 0; -} -void Audio_Frequency::set_time_point_size(int value) { - value = qMax(1, value); - if (auto axis = time_axis()) - axis->set_time_point_size(value); -} std::shared_ptr Audio_Frequency::time_axis() { return d()->edit_state_value(&Audio_Frequency_Render_State::time_axis).lock(); } void Audio_Frequency::set_time_axis(std::shared_ptr value) { d()->set_state_value(&Audio_Frequency_Render_State::time_axis, std::weak_ptr(value)); - if (auto curve = d()->curve_item.lock()) - curve->set_x_axis(value); } std::shared_ptr Audio_Frequency::value_axis() { return d()->edit_state_value(&Audio_Frequency_Render_State::value_axis).lock(); } void Audio_Frequency::set_value_axis(std::shared_ptr value) { d()->set_state_value(&Audio_Frequency_Render_State::value_axis, std::weak_ptr(value)); - if (auto curve = d()->curve_item.lock()) - curve->set_y_axis(value); -} -QColor Audio_Frequency::color() { - if (auto curve = d()->curve_item.lock()) - return curve->pen().color(); - return {}; -} -void Audio_Frequency::set_color(QColor value) { - if (auto curve = d()->curve_item.lock()) { - QPen pen(value); - pen.setWidth(0); - curve->set_pen(pen); - } } +RENDERIVE_DEFINE_STATE_PROP(Audio_Frequency, Audio_Frequency_Render_State, QColor, color) Audio_Frequency::Builder::Builder(std::shared_ptr parent, std::shared_ptr time_axis, std::shared_ptr value_axis) { - ASSERT(parent && parent->plot, "Audio_Frequency build error! parent invalid"); + ASSERT(parent && parent->is_attached(), "Audio_Frequency build error! parent invalid"); ASSERT(time_axis && value_axis, "Audio_Frequency build error! axis == nullptr"); - ASSERT(time_axis->plot != nullptr, "Audio_Frequency build error! time_axis->plot == nullptr"); - ASSERT(time_axis->plot == value_axis->plot, "Audio_Frequency build error! time_axis->plot != value_axis->plot"); - ASSERT(parent->plot == time_axis->plot, "Audio_Frequency build error! parent->plot != time_axis->plot"); + ASSERT(time_axis->is_attached(), "Audio_Frequency build error! time_axis is detached"); + ASSERT(time_axis->shares_plot_with(*value_axis), "Audio_Frequency build error! axes belong to different plots"); + ASSERT(parent->shares_plot_with(*time_axis), "Audio_Frequency build error! parent and axes belong to different plots"); this->parent = parent; this->time_axis = time_axis; this->value_axis = value_axis; @@ -54,22 +30,11 @@ std::shared_ptr Audio_Frequency::Builder::build() { auto ret = renderive::make_shared(); ret->init(parent); Audio_Frequency_Private* pd = ret->d(); - Audio_Frequency_Render_State* sc = reinterpret_cast(pd->state(State_Edit)); + Audio_Frequency_Render_State* sc = reinterpret_cast(pd->state(Data_Buffer_Role::Edit)); auto time_axis = this->time_axis.lock(); - if (!time_point_size_set && time_axis) - time_point_size = time_axis->time_point_size(); PROP_R(std::weak_ptr, time_axis) PROP_R(std::weak_ptr, value_axis) - auto value_axis = this->value_axis.lock(); - if (time_axis) - time_axis->set_time_point_size(qMax(1, time_point_size)); - if (time_axis && value_axis) { - QPen pen(color); - pen.setWidth(0); - auto curve = Curve::Builder(ret, time_axis, value_axis).set_pen(pen).build(); - curve->object_name = "Audio_Frequency_Curve"; - pd->curve_item = curve; - } + PROP_R(QColor, color) return ret; } void Audio_Frequency::give_data(int tick, double data) { diff --git a/Renderive/plottable/Audio_Frequency.h b/Renderive/plottable/Audio_Frequency.h index a42c0fd..e487d88 100644 --- a/Renderive/plottable/Audio_Frequency.h +++ b/Renderive/plottable/Audio_Frequency.h @@ -1,56 +1,48 @@ #pragma once +#include +#include #include "global.h" -#include "../primitive/Curve.h" + namespace renderive { +class Axis; class Time_Axis; struct Audio_Frequency_Private; -/// @brief 音频频率随时间变化的折线图元。 +struct Audio_Frequency_Private_Access; + +/// @brief Streaming audio value history bound to a timeline and value axis. class LIB_DECL Audio_Frequency : public Renderable { public: - /// @brief 创建音频频率图元。 Audio_Frequency(); - /// @brief 写入指定 tick 对应的音频频率值。 void give_data(int tick, double data); - /// @brief 写入指定时间对应的音频频率值。 void give_data(QTime time, double data); - /// @brief 时间窗口内保留的点数。 - int time_point_size(); - void set_time_point_size(int value); - /// @brief 绑定时间轴。 + PROP(std::shared_ptr, time_axis) - /// @brief 绑定数值轴。 PROP(std::shared_ptr, value_axis) - /// @brief 曲线数据值范围。 - QColor color(); - void set_color(QColor value); - /// @brief 曲线颜色。 + PROP(QColor, color) + struct Builder; + private: + friend struct Audio_Frequency_Private_Access; Audio_Frequency_Private* d(); Render_Object_Owner create_render_data() override; Render_Object_Owner create_render_state() override; Render_Object_Owner create_input_data() override; }; + struct Audio_Frequency_Prop { std::weak_ptr time_axis; std::weak_ptr value_axis{}; + QColor color = Qt::yellow; }; + struct LIB_DECL Audio_Frequency::Builder : Audio_Frequency_Prop { PROP_B(std::shared_ptr, time_axis) PROP_B(std::shared_ptr, value_axis) PROP_B(QColor, color) - Builder& set_time_point_size(int value) { - time_point_size = value; - time_point_size_set = true; - return *this; - } - /// 创建音频频率构造器。 + Builder(std::shared_ptr parent, std::shared_ptr time_axis, std::shared_ptr value_axis); - /// 构建并返回音频频率对象。 std::shared_ptr build(); std::shared_ptr parent{}; - int time_point_size = 100; - QColor color = Qt::yellow; - bool time_point_size_set{}; }; } // namespace renderive diff --git a/Renderive/plottable/Audio_Frequency_p.h b/Renderive/plottable/Audio_Frequency_p.h index 8da3c34..e78c08c 100644 --- a/Renderive/plottable/Audio_Frequency_p.h +++ b/Renderive/plottable/Audio_Frequency_p.h @@ -1,32 +1,39 @@ #pragma once +#include +#include +#include +#include #include +#include #include #include #include "../Axis/Axis_p.h" #include "../Axis/Time_Axis_p.h" #include "../architecture/Bounded_Input_Buffer.h" -#include "../plot/Plot_p.h" -#include "../architecture/Renderable.h" +#include "../base/Frame_Memory.h" #include "../base/Memory.h" -#include "../primitive/Curve.h" -#include "Core/Base/RingBuffer.hpp" +#include "../plot/Plot_p.h" #include "Audio_Frequency.h" + namespace renderive { struct Audio_Frequency_Data { int tick{}; double power{}; }; + struct Audio_Frequency_Input_Data : Input_Data { static constexpr std::size_t Max_Pending_Value_Count = 256; Bounded_Input_Buffer data_list; + void push(int tick, double power) { - Audio_Frequency_Data data{tick, power}; + const Audio_Frequency_Data data{tick, power}; data_list.push(&data, sizeof(data), Max_Pending_Value_Count); } template void read_all(Handler handler) { data_list.read_all([&handler](const void* data, std::size_t size) { - (void)size; + if (size != sizeof(Audio_Frequency_Data)) + return; Audio_Frequency_Data value; std::memcpy(&value, data, sizeof(value)); handler(value); @@ -36,65 +43,117 @@ struct Audio_Frequency_Input_Data : Input_Data { data_list.clear(); } }; + struct Audio_Frequency_Render_State : Render_State, Audio_Frequency_Prop {}; + +struct Audio_Pixel_Bucket { + bool used{}; + std::array indexes{-1, -1, -1, -1}; + double min = std::numeric_limits::max(); + double max = std::numeric_limits::lowest(); +}; + struct Audio_Frequency_Private : Typed_Render_Data { - Psc::StreamRingBuffer_ST buffer{memory_resource(Memory_Domain::Audio)}; - std::pmr::vector data_snapshot{memory_resource(Memory_Domain::Audio)}; - std::pmr::vector power_snapshot{memory_resource(Memory_Domain::Audio)}; - std::weak_ptr curve_item; + std::pmr::vector history{memory_resource(Memory_Domain::Audio)}; + int history_head{}; int data_count{}; int point_count{}; - void resize_power_buffer(int count) { - point_count = count; + int visible_lower{}; + int visible_upper{}; + + void resize_buffer(int count) { + point_count = std::max(1, count); + history.resize(static_cast(point_count)); + history_head = 0; data_count = 0; - data_snapshot.resize(count); - power_snapshot.resize(count); - buffer.init(sizeof(Audio_Frequency_Data) * count); } - void push_power(const Audio_Frequency_Data& data) { - if (data_count == point_count) { - Audio_Frequency_Data ignored; - std::size_t len = sizeof(Audio_Frequency_Data); - buffer.read(&ignored, len); - --data_count; + void push_value(const Audio_Frequency_Data& data) { + if (data_count < point_count) { + const int index = (history_head + data_count) % point_count; + history[static_cast(index)] = data; + ++data_count; + return; } - buffer.write(&data, sizeof(data)); - ++data_count; + history[static_cast(history_head)] = data; + history_head = (history_head + 1) % point_count; } - void snapshot_power_buffer() { - buffer.peek_best_effort(data_snapshot.data(), sizeof(Audio_Frequency_Data) * data_count); - for (int i = 0; i < data_count; ++i) - power_snapshot[static_cast(i)] = data_snapshot[static_cast(i)].power; + const Audio_Frequency_Data& history_value(int logical_index) const { + const int index = (history_head + logical_index) % point_count; + return history[static_cast(index)]; } void prepare_data(const Plot_Render_Snapshot& snapshot) override { sync_state_pipeline(); - Audio_Frequency_Render_State* s = render_state(); - Audio_Frequency_Input_Data* d = render_input_data(); - auto time_axis = s->time_axis.lock(); - auto value_axis = s->value_axis.lock(); - if (!time_axis || !value_axis) + auto* state = render_state(); + auto* input = render_input_data(); + auto time_axis = state->time_axis.lock(); + if (!time_axis) { + clear_render_input(); return; - auto timeline = snapshot.timeline_snapshot(time_axis->timeline_stream()); - if (!timeline) - return; - if (point_count != timeline->time_point_size) - resize_power_buffer(timeline->time_point_size); - d->read_all([this](const Audio_Frequency_Data& data) { - push_power(data); - }); - snapshot_power_buffer(); - int lower = std::min(timeline->lower, timeline->upper()); - int upper = std::max(timeline->lower, timeline->upper()); - std::pmr::vector points(memory_resource(Memory_Domain::Curve)); - points.reserve(static_cast(data_count)); - for (int i = 0; i < data_count; ++i) { - const Audio_Frequency_Data& item = data_snapshot[static_cast(i)]; - if (item.tick >= lower && item.tick <= upper) - points.emplace_back(item.tick, item.power); } - if (auto curve = curve_item.lock()) - curve->give_points(points, false); + auto timeline = snapshot.timeline_snapshot(time_axis->timeline_stream()); + if (!timeline) { + clear_render_input(); + return; + } + if (point_count != timeline->time_point_size) + resize_buffer(timeline->time_point_size); + input->read_all([this](const Audio_Frequency_Data& data) { push_value(data); }); + visible_lower = std::min(timeline->lower, timeline->upper()); + visible_upper = std::max(timeline->lower, timeline->upper()); clear_render_input(); } + void rasterize(Frame_Raster_Context& context, const Plot_Render_Snapshot&) override { + const auto* state = render_state(); + auto time_axis = state->time_axis.lock(); + auto value_axis = state->value_axis.lock(); + if (!time_axis || !value_axis || data_count == 0) + return; + const auto x_transform = render_axis_transform(*time_axis); + const auto y_transform = render_axis_transform(*value_axis); + const int bucket_count = std::max(1, x_transform.pixel_size() + 1); + std::pmr::vector buckets(bucket_count, frame_memory_resource()); + const double pixel_min = std::min(x_transform.to_pixel(visible_lower), x_transform.to_pixel(visible_upper)); + for (int i = 0; i < data_count; ++i) { + const auto& item = history_value(i); + if (item.tick < visible_lower || item.tick > visible_upper || !std::isfinite(item.power)) + continue; + const int bucket_index = std::clamp(static_cast(std::floor(x_transform.to_pixel(item.tick) - pixel_min)), 0, bucket_count - 1); + auto& bucket = buckets[bucket_index]; + if (!bucket.used) { + bucket.used = true; + bucket.indexes = {i, i, i, i}; + bucket.min = bucket.max = item.power; + } + bucket.indexes[3] = i; + if (item.power < bucket.min) { + bucket.min = item.power; + bucket.indexes[1] = i; + } + if (item.power > bucket.max) { + bucket.max = item.power; + bucket.indexes[2] = i; + } + } + std::pmr::vector points(frame_memory_resource()); + points.reserve(static_cast(bucket_count) * 4); + for (auto& bucket : buckets) { + if (!bucket.used) + continue; + std::sort(bucket.indexes.begin(), bucket.indexes.end()); + auto end = std::unique(bucket.indexes.begin(), bucket.indexes.end()); + for (auto it = bucket.indexes.begin(); it != end; ++it) { + const auto& item = history_value(*it); + points.emplace_back(x_transform.to_pixel(item.tick), y_transform.to_pixel(item.power)); + } + } + if (points.size() < 2) + return; + QPen pen(state->color); + pen.setWidth(0); + context.stroke_polyline(points, pen); + } + std::size_t estimated_frame_memory(const Plot_Render_Snapshot& snapshot) override { + return static_cast(snapshot.size.width() + 1) * (sizeof(Audio_Pixel_Bucket) + 4 * sizeof(QPointF)); + } }; -} +} // namespace renderive diff --git a/Renderive/plottable/Hover_Info.cpp b/Renderive/plottable/Hover_Info.cpp index 526666f..022b537 100644 --- a/Renderive/plottable/Hover_Info.cpp +++ b/Renderive/plottable/Hover_Info.cpp @@ -24,10 +24,10 @@ bool Hover_Info_Render_State::hover_ok(Renderable* able) const { return use_hover_info && hover_info_active && tmp->hover_test(hover_info_pos); } std::vector Hover_Info_Renderable_Interface::create_hover_string(Abs_Axis* h, Abs_Axis* v, QPoint pos) { - double tick1 = h->d()->render_pixel_to_coord(pos.x()); - double tick2 = v->d()->render_pixel_to_coord(pos.y()); - QString line1 = h->d()->render_tick_label(tick1) + h->unit_text(); - QString line2 = v->d()->render_tick_label(tick2) + v->unit_text(); + double tick1 = render_axis_transform(*h).to_coordinate(pos.x()); + double tick2 = render_axis_transform(*v).to_coordinate(pos.y()); + QString line1 = render_axis_tick_label(*h, tick1) + h->unit_text(); + QString line2 = render_axis_tick_label(*v, tick2) + v->unit_text(); return {line1, line2}; } } // namespace renderive diff --git a/Renderive/plottable/Hover_Info.h b/Renderive/plottable/Hover_Info.h index 135408e..a7a6c6e 100644 --- a/Renderive/plottable/Hover_Info.h +++ b/Renderive/plottable/Hover_Info.h @@ -133,16 +133,16 @@ private: } Hover_Info_Render_State* hover_edit_state() override { auto t = that(); - return dynamic_cast(t->d_ptr->state(State_Edit)); + return dynamic_cast(t->render_data()->state(Data_Buffer_Role::Edit)); } Hover_Info_Render_State* hover_render_state() override { auto t = that(); - return dynamic_cast(t->d_ptr->state(State_Render)); + return dynamic_cast(t->render_data()->state(Data_Buffer_Role::Render)); } Hover_Edit begin_hover_edit_impl() override { auto t = that(); - auto lease = t->d_ptr->state_control.wait_mark_use_role(State_Edit); - auto* data = t->d_ptr; + auto* data = t->render_data(); + auto lease = data->state_control.wait_mark_use_role(Data_Buffer_Role::Edit); auto* state = dynamic_cast(data->state_by_index(lease.index)); return Hover_Edit(this, state, [data, lease]() mutable { ++data->state_by_index(lease.index)->version; diff --git a/Renderive/plottable/Interpolation.h b/Renderive/plottable/Interpolation.h deleted file mode 100644 index 085a7d1..0000000 --- a/Renderive/plottable/Interpolation.h +++ /dev/null @@ -1,28 +0,0 @@ -#pragma once -#include -namespace renderive { -/// 频谱线从输入采样点生成屏幕曲线时使用的插值方式。 -enum class Line_Interpolation_Mode : std::uint8_t { - /// 使用最近的频点值,不做连续插值。 - Nearest_Bin, - /// 在显示功率值上做线性插值。 - Linear_Display, - /// 在线性功率域插值,再转换回显示功率。 - Linear_Power, - /// 使用左侧频点值形成阶梯线。 - Step_Left, - /// 使用右侧频点值形成阶梯线。 - Step_Right, - /// 在显示功率值上做三次插值。 - Cubic_Display -}; -/// 二维频谱图像从原始列行生成显示图像时使用的插值方式。 -enum class Image_Interpolation_Mode : std::uint8_t { - /// 使用最近像素。 - Nearest, - /// 双线性插值。 - Bilinear, - /// 双三次插值。 - Bicubic -}; -} // namespace renderive diff --git a/Renderive/plottable/Interpolation_p.h b/Renderive/plottable/Interpolation_p.h deleted file mode 100644 index 7ed090b..0000000 --- a/Renderive/plottable/Interpolation_p.h +++ /dev/null @@ -1,147 +0,0 @@ -#pragma once -#include -#include -#include -#include -#include -#include -#include "../Axis/Abs_Axis_p.h" -#include "../base/Frame_Memory.h" -#include "Interpolation.h" -namespace renderive { -struct Line_Sample_Window { - double first_index{}; - double last_index{}; - int sample_count{}; - explicit operator bool() const { - return sample_count > 0; - } -}; -inline bool intersect_range(const Range& data_range, const Range& clip_range, Range& result) { - double data_min = std::min(data_range.origin, data_range.target); - double data_max = std::max(data_range.origin, data_range.target); - double clip_min = std::min(clip_range.origin, clip_range.target); - double clip_max = std::max(clip_range.origin, clip_range.target); - double lower = std::max(data_min, clip_min); - double upper = std::min(data_max, clip_max); - if (lower > upper) - return false; - result = data_range.origin <= data_range.target ? Range{lower, upper} : Range{upper, lower}; - return true; -} -inline double source_index_to_coord(const Range& data_range, int total_count, double index) { - return data_range.origin + data_range.length() * index / static_cast(total_count - 1); -} -inline double coord_to_source_index(const Range& data_range, int total_count, double coord) { - return static_cast(total_count - 1) * (coord - data_range.origin) / data_range.length(); -} -inline Line_Sample_Window line_sample_window(Abs_Axis* axis, const Range& data_range, int total_count, int available_count, bool visible_range_only) { - if (total_count < 2 || available_count < 1 || data_range.length() == 0.0) - return {}; - Range available_range{data_range.origin, source_index_to_coord(data_range, total_count, available_count - 1)}; - Range sample_range = available_range; - if (visible_range_only && !intersect_range(available_range, axis->d()->render_coord_range(), sample_range)) - return {}; - double first_index = coord_to_source_index(data_range, total_count, sample_range.origin); - double last_index = coord_to_source_index(data_range, total_count, sample_range.target); - double first_pixel = axis->d()->render_coord_to_pixel(sample_range.origin); - double last_pixel = axis->d()->render_coord_to_pixel(sample_range.target); - int source_samples = static_cast(std::ceil(std::abs(last_index - first_index))) + 1; - int pixel_samples = static_cast(std::ceil(std::abs(last_pixel - first_pixel))) + 1; - pixel_samples = std::min(pixel_samples, axis->d()->render_pixel_size() + 1); - return {first_index, last_index, std::max(source_samples, pixel_samples)}; -} -template -double interpolate_line_value(double index, int count, Line_Interpolation_Mode mode, Value_At value_at) { - 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); - double t = index - left; - double left_value = value_at(left); - double right_value = value_at(right); - switch (mode) { - case Line_Interpolation_Mode::Nearest_Bin: - return value_at(static_cast(std::round(index))); - case Line_Interpolation_Mode::Linear_Display: - return left_value + (right_value - left_value) * t; - case Line_Interpolation_Mode::Linear_Power: { - 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); - } - case Line_Interpolation_Mode::Step_Left: - return left_value; - case Line_Interpolation_Mode::Step_Right: - return t == 0.0 ? left_value : right_value; - case Line_Interpolation_Mode::Cubic_Display: { - 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)); - 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; - return ((a * t + b) * t + c) * t + p1; - } - } - return left_value; -} -template -std::pmr::vector create_line_points(Abs_Axis* x_axis, Abs_Axis* y_axis, const Range& data_range, int total_count, int available_count, bool visible_range_only, Line_Interpolation_Mode mode, Value_At value_at) { - Line_Sample_Window window = line_sample_window(x_axis, data_range, total_count, available_count, visible_range_only); - if (!window) - return {}; - std::pmr::vector points(window.sample_count, frame_memory_resource()); - 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(data_range, total_count, index); - double value = interpolate_line_value(index, available_count, mode, value_at); - points[i] = QPointF(x_axis->d()->render_coord_to_pixel(coord), y_axis->d()->render_coord_to_pixel(value)); - } - return points; -} -inline double cubic_channel(double p0, double p1, double p2, double p3, double t) { - 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; - return ((a * t + b) * t + c) * t + p1; -} -inline QRgb bicubic_pixel(const QImage& source, double x, double y) { - int x1 = static_cast(std::floor(x)); - int y1 = static_cast(std::floor(y)); - double tx = x - x1; - double ty = y - y1; - double rows[4][4]{}; - for (int row = -1; row <= 2; ++row) { - int source_y = std::clamp(y1 + row, 0, source.height() - 1); - const QRgb* source_line = reinterpret_cast(source.constScanLine(source_y)); - QRgb pixels[4]{}; - for (int col = -1; col <= 2; ++col) { - int source_x = std::clamp(x1 + col, 0, source.width() - 1); - pixels[col + 1] = source_line[source_x]; - } - rows[0][row + 1] = cubic_channel(qAlpha(pixels[0]), qAlpha(pixels[1]), qAlpha(pixels[2]), qAlpha(pixels[3]), tx); - rows[1][row + 1] = cubic_channel(qRed(pixels[0]), qRed(pixels[1]), qRed(pixels[2]), qRed(pixels[3]), tx); - rows[2][row + 1] = cubic_channel(qGreen(pixels[0]), qGreen(pixels[1]), qGreen(pixels[2]), qGreen(pixels[3]), tx); - rows[3][row + 1] = cubic_channel(qBlue(pixels[0]), qBlue(pixels[1]), qBlue(pixels[2]), qBlue(pixels[3]), tx); - } - double channels[4]{}; - for (int channel = 0; channel < 4; ++channel) { - channels[channel] = std::clamp(cubic_channel(rows[channel][0], rows[channel][1], rows[channel][2], rows[channel][3], ty), 0.0, 255.0); - } - return qRgba(qRound(channels[1]), qRound(channels[2]), qRound(channels[3]), qRound(channels[0])); -} -inline QImage bicubic_scale(const QImage& source, const QRectF& source_rect, const QSize& target_size) { - QImage target(target_size, QImage::Format_ARGB32_Premultiplied); - for (int y = 0; y < target.height(); ++y) { - QRgb* line = reinterpret_cast(target.scanLine(y)); - double source_y = source_rect.top() + (static_cast(y) + 0.5) * source_rect.height() / target.height() - 0.5; - for (int x = 0; x < target.width(); ++x) { - double source_x = source_rect.left() + (static_cast(x) + 0.5) * source_rect.width() / target.width() - 0.5; - line[x] = bicubic_pixel(source, source_x, source_y); - } - } - return target; -} -} // namespace renderive diff --git a/Renderive/plottable/Multi_Select_Rect.cpp b/Renderive/plottable/Multi_Select_Rect.cpp index 4466ed6..518078b 100644 --- a/Renderive/plottable/Multi_Select_Rect.cpp +++ b/Renderive/plottable/Multi_Select_Rect.cpp @@ -3,11 +3,11 @@ namespace renderive { RENDERIVE_DEFINE_RENDERABLE_BINDING(Multi_Select_Rect, Multi_Select_Rect_Private, Multi_Select_Rect_Render_State, Multi_Select_Rect_Input_Data, "Multi_Select_Rect") Multi_Select_Rect::Builder::Builder(std::shared_ptr parent, std::shared_ptr key_axis, std::shared_ptr value_axis) { - ASSERT(parent && parent->plot, "Multi_Select_Rect build error! parent invalid"); + ASSERT(parent && parent->is_attached(), "Multi_Select_Rect build error! parent invalid"); ASSERT(key_axis && value_axis, "Multi_Select_Rect build error! axis == nullptr"); - ASSERT(key_axis->plot != nullptr, "Multi_Select_Rect build error! key_axis->plot == nullptr"); - ASSERT(key_axis->plot == value_axis->plot, "Multi_Select_Rect build error! key_axis->plot != value_axis->plot"); - ASSERT(parent->plot == key_axis->plot, "Multi_Select_Rect build error! parent->plot != key_axis->plot"); + ASSERT(key_axis->is_attached(), "Multi_Select_Rect build error! key_axis is detached"); + ASSERT(key_axis->shares_plot_with(*value_axis), "Multi_Select_Rect build error! axes belong to different plots"); + ASSERT(parent->shares_plot_with(*key_axis), "Multi_Select_Rect build error! parent and axis belong to different plots"); this->parent = parent; this->key_axis = key_axis; this->value_axis = value_axis; @@ -16,7 +16,7 @@ std::shared_ptr Multi_Select_Rect::Builder::build() { auto ret = renderive::make_shared(); ret->init(parent); Multi_Select_Rect_Private* pd = ret->d(); - Multi_Select_Rect_Render_State* sc = reinterpret_cast(pd->state(State_Edit)); + Multi_Select_Rect_Render_State* sc = reinterpret_cast(pd->state(Data_Buffer_Role::Edit)); sc->horizontal_axis = key_axis; sc->vertical_axis = value_axis; sc->font = font; @@ -34,16 +34,16 @@ void Multi_Select_Rect::set_vertical_axis(std::shared_ptr axis) { edit->vertical_axis = std::weak_ptr(axis); } QFont Multi_Select_Rect::font() { - return d()->state_data(SRC::Edit)->font; + return d()->edit_state()->font; } QPen Multi_Select_Rect::font_pen() { - return d()->state_data(SRC::Edit)->font_pen; + return d()->edit_state()->font_pen; } QBrush Multi_Select_Rect::rect_brush() { - return d()->state_data(SRC::Edit)->rect_brush; + return d()->edit_state()->rect_brush; } QPen Multi_Select_Rect::border_pen() { - return d()->state_data(SRC::Edit)->border_pen; + return d()->edit_state()->border_pen; } void Multi_Select_Rect::set_font(const QFont& font) { Render_Edit_Lease edit(d()); @@ -62,7 +62,7 @@ void Multi_Select_Rect::set_border_pen(const QPen& pen) { edit->border_pen = pen.color().isValid() ? pen : Qt::NoPen; } std::vector Multi_Select_Rect::selection_rects() { - return d()->state_data(SRC::Edit)->rects; + return d()->edit_state()->rects; } void Multi_Select_Rect::clear_selection() { Render_Edit_Lease edit(d()); diff --git a/Renderive/plottable/Multi_Select_Rect_p.h b/Renderive/plottable/Multi_Select_Rect_p.h index dfc5a55..9a0058d 100644 --- a/Renderive/plottable/Multi_Select_Rect_p.h +++ b/Renderive/plottable/Multi_Select_Rect_p.h @@ -25,12 +25,14 @@ struct Multi_Select_Rect_Private : renderive::Typed_Render_Datavertical_axis.lock(); if (!horizontal_axis || !vertical_axis) return false; - Range h = horizontal_axis->d()->render_coord_range(); - Range v = vertical_axis->d()->render_coord_range(); - double left = horizontal_axis->d()->render_coord_to_pixel(h.origin); - double right = horizontal_axis->d()->render_coord_to_pixel(h.target); - double top = vertical_axis->d()->render_coord_to_pixel(v.origin); - double bottom = vertical_axis->d()->render_coord_to_pixel(v.target); + const auto horizontal_transform = render_axis_transform(*horizontal_axis); + const auto vertical_transform = render_axis_transform(*vertical_axis); + const Range h = horizontal_transform.coordinate_range; + const Range v = vertical_transform.coordinate_range; + double left = horizontal_transform.to_pixel(h.origin); + double right = horizontal_transform.to_pixel(h.target); + double top = vertical_transform.to_pixel(v.origin); + double bottom = vertical_transform.to_pixel(v.target); return QRectF(QPointF(std::min(left, right), std::min(top, bottom)), QPointF(std::max(left, right), std::max(top, bottom))).contains(pos); } @@ -55,8 +57,8 @@ struct Multi_Select_Rect_Private : renderive::Typed_Render_Datad()->render_pixel_to_coord(event->pos().x()); - double y = vertical_axis->d()->render_pixel_to_coord(event->pos().y()); + double x = render_axis_transform(*horizontal_axis).to_coordinate(event->pos().x()); + double y = render_axis_transform(*vertical_axis).to_coordinate(event->pos().y()); rect_list.push_back(QRectF(QPointF(x, y), QPointF(x, y))); active.store(true, std::memory_order_release); } @@ -73,8 +75,8 @@ struct Multi_Select_Rect_Private : renderive::Typed_Render_Data& rect_list = edit->rects; if (rect_list.empty()) return; - double x = horizontal_axis->d()->render_pixel_to_coord(event->pos().x()); - double y = vertical_axis->d()->render_pixel_to_coord(event->pos().y()); + double x = render_axis_transform(*horizontal_axis).to_coordinate(event->pos().x()); + double y = render_axis_transform(*vertical_axis).to_coordinate(event->pos().y()); rect_list.back().setBottomRight(QPointF(x, y)); } void mouseReleaseEvent(QMouseEvent* event) override { @@ -95,22 +97,25 @@ struct Multi_Select_Rect_Private : renderive::Typed_Render_Datahorizontal_axis.lock(); auto vertical_axis = s->vertical_axis.lock(); if (!horizontal_axis || !vertical_axis) return; + const auto horizontal_transform = render_axis_transform(*horizontal_axis); + const auto vertical_transform = render_axis_transform(*vertical_axis); std::vector draw_rect_list; std::vector coord_rect_list; for (QRectF r : s->rects) { if (qFuzzyCompare(r.width(), 0.0) || qFuzzyCompare(r.height(), 0.0)) continue; coord_rect_list.push_back(r.normalized()); - r.setX(horizontal_axis->d()->render_coord_to_pixel(r.x())); - r.setY(vertical_axis->d()->render_coord_to_pixel(r.y())); - r.setRight(horizontal_axis->d()->render_coord_to_pixel(r.right())); - r.setBottom(vertical_axis->d()->render_coord_to_pixel(r.bottom())); + r.setX(horizontal_transform.to_pixel(r.x())); + r.setY(vertical_transform.to_pixel(r.y())); + r.setRight(horizontal_transform.to_pixel(r.right())); + r.setBottom(vertical_transform.to_pixel(r.bottom())); draw_rect_list.push_back(r.normalized()); } painter->save(); diff --git a/Renderive/plottable/Performance_Shower.cpp b/Renderive/plottable/Performance_Shower.cpp index a28c421..cd5da6f 100644 --- a/Renderive/plottable/Performance_Shower.cpp +++ b/Renderive/plottable/Performance_Shower.cpp @@ -190,14 +190,14 @@ private: return value; } static std::string header() { - return "frame_id,input_version,slot_generation,outcome,bottleneck,input_fps,production_fps,producer_capacity_fps,display_fps,consumer_capacity_fps,manual_interval_ms,adaptive_interval_ms,effective_interval_ms,input_srtt_ms,input_jitter_ms,render_interval_srtt_ms,render_interval_jitter_ms,render_queue_srtt_ms,render_queue_jitter_ms,render_duration_srtt_ms,render_duration_jitter_ms,ready_wait_srtt_ms,ready_wait_jitter_ms,update_wait_srtt_ms,update_wait_jitter_ms,paint_srtt_ms,paint_jitter_ms,consume_interval_srtt_ms,consume_interval_jitter_ms,frame_age_srtt_ms,frame_age_jitter_ms,producer_busy_ratio,ready_occupancy_ratio,consumer_busy_ratio,input_pending_ratio,superseded_ratio,underrun_ratio,unique_consumed_ratio,display_efficiency,wasted_render_ms,latest_input_version,rendering_input_version,published_input_version,consumed_input_version,produced_frames,consumed_frames,superseded_frames,repeated_frames,request_wait_ms,render_queue_wait_ms,prepare_ms,draw_ms,render_ms,ready_wait_ms,update_wait_ms,paint_ms,frame_age_ms,frame_total_ms,raw_input_count,prepared_output_count,cache_hit_count,cache_miss_count,cache_rebuild_count,cache_rebuild_time_ns,cache_compose_time_ns,cache_image_bytes,cache_image_area,cache_hit_ratio,pixel_width,pixel_height,renderable_count,visible_renderable_count,direct_draw_count,local_cache_count,core_data_time_ns,render_begin_time_ns,render_end_time_ns,publish_time_ns,paint_begin_time_ns,paint_end_time_ns,consumer_release_time_ns,superseded_time_ns,renderable_stats,renderable_cache_stats"; + return "frame_id,input_version,outcome,bottleneck,input_fps,production_fps,producer_capacity_fps,display_fps,consumer_capacity_fps,manual_interval_ms,adaptive_interval_ms,effective_interval_ms,input_srtt_ms,input_jitter_ms,render_interval_srtt_ms,render_interval_jitter_ms,render_queue_srtt_ms,render_queue_jitter_ms,render_duration_srtt_ms,render_duration_jitter_ms,ready_wait_srtt_ms,ready_wait_jitter_ms,update_wait_srtt_ms,update_wait_jitter_ms,paint_srtt_ms,paint_jitter_ms,consume_interval_srtt_ms,consume_interval_jitter_ms,frame_age_srtt_ms,frame_age_jitter_ms,producer_busy_ratio,ready_occupancy_ratio,consumer_busy_ratio,input_pending_ratio,superseded_ratio,underrun_ratio,unique_consumed_ratio,display_efficiency,wasted_render_ms,latest_input_version,rendering_input_version,published_input_version,consumed_input_version,produced_frames,consumed_frames,superseded_frames,reused_front_frames,request_wait_ms,render_queue_wait_ms,prepare_ms,draw_ms,render_ms,ready_wait_ms,update_wait_ms,paint_ms,frame_age_ms,frame_total_ms,raw_input_count,prepared_output_count,cache_hit_count,cache_miss_count,cache_rebuild_count,cache_rebuild_time_ns,cache_compose_time_ns,cache_image_bytes,cache_image_area,cache_hit_ratio,pixel_width,pixel_height,renderable_count,visible_renderable_count,direct_draw_count,local_cache_count,core_data_time_ns,render_begin_time_ns,render_end_time_ns,publish_time_ns,paint_begin_time_ns,paint_end_time_ns,consumer_release_time_ns,superseded_time_ns,renderable_stats,renderable_cache_stats"; } }; Performance_Frame_Log& frame_log() { static Performance_Frame_Log value; return value; } -std::shared_ptr performance_shower_owner(Abs_Plot_Private* data) { +std::shared_ptr performance_shower_owner(const Abs_Plot_Private* data) { if (!data || !data->render_context) return {}; auto observer = std::atomic_load_explicit(&data->render_context->frame_lifecycle_observer, std::memory_order_acquire); @@ -206,7 +206,7 @@ std::shared_ptr performance_shower_owner(Abs_Plot_Private* d } RENDERIVE_DEFINE_RENDERABLE_BINDING(Performance_Shower, Performance_Shower_Private, Performance_Shower_Render_State, Performance_Shower_Input_Data, "Performance_Shower") std::shared_ptr attach_performance_shower(Abs_Plot& plot) { - auto* data = plot.d; + auto* data = Abs_Plot_Private_Access::data(plot); if (!data) return {}; auto shower = performance_shower_owner(data); @@ -218,7 +218,7 @@ std::shared_ptr attach_performance_shower(Abs_Plot& plot) { return created_shower; } void detach_performance_shower(Abs_Plot& plot) { - auto* data = plot.d; + auto* data = Abs_Plot_Private_Access::data(plot); if (!data) return; auto shower = performance_shower_owner(data); @@ -229,7 +229,7 @@ void detach_performance_shower(Abs_Plot& plot) { } } bool performance_shower_enabled(const Abs_Plot& plot) { - auto shower = performance_shower_owner(plot.d); + auto shower = performance_shower_owner(Abs_Plot_Private_Access::data(plot)); return shower && shower->enabled(); } void set_performance_shower_enabled(Abs_Plot& plot, bool enabled) { diff --git a/Renderive/plottable/Performance_Shower_p.h b/Renderive/plottable/Performance_Shower_p.h index a8b5fb9..d2aba30 100644 --- a/Renderive/plottable/Performance_Shower_p.h +++ b/Renderive/plottable/Performance_Shower_p.h @@ -306,7 +306,8 @@ struct Performance_Shower_Private : Typed_Render_Dataenabled() && panel_rect.contains(pos.toPoint()); } - void draw(QPainter* painter, const Plot_Render_Snapshot& snapshot) override { + void rasterize(Frame_Raster_Context& context, const Plot_Render_Snapshot& snapshot) override { + QPainter* painter = &context.qt(); if (!q()->enabled() || lines.empty()) return; Performance_Shower_Render_State* state = render_state(); diff --git a/Renderive/plottable/Planisphere.cpp b/Renderive/plottable/Planisphere.cpp index 6506810..b60f922 100644 --- a/Renderive/plottable/Planisphere.cpp +++ b/Renderive/plottable/Planisphere.cpp @@ -94,9 +94,9 @@ void Planisphere::give_data(QPointF pos) { Planisphere::Builder::Builder(std::shared_ptr parent, std::shared_ptr i_axis, std::shared_ptr q_axis) { ASSERT(parent, "Planisphere build error! parent == nullptr"); ASSERT(i_axis && q_axis, "Planisphere build error! axis == nullptr"); - ASSERT(i_axis->plot != nullptr, "Planisphere build error! i_axis->plot == nullptr"); - ASSERT(i_axis->plot == q_axis->plot, "Planisphere build error! i_axis->plot != q_axis->plot"); - ASSERT(parent->plot == i_axis->plot, "Planisphere build error! parent->plot != axis->plot"); + ASSERT(i_axis->is_attached(), "Planisphere build error! i_axis is detached"); + ASSERT(i_axis->shares_plot_with(*q_axis), "Planisphere build error! axes belong to different plots"); + ASSERT(parent->shares_plot_with(*i_axis), "Planisphere build error! parent and axes belong to different plots"); this->parent = parent; this->i_axis = i_axis; this->q_axis = q_axis; @@ -105,7 +105,7 @@ std::shared_ptr Planisphere::Builder::build() { auto ret = renderive::make_shared(); ret->init(parent); Planisphere_Private* pd = ret->d(); - Planisphere_Render_State* sc = reinterpret_cast(pd->state(State_Edit)); + Planisphere_Render_State* sc = reinterpret_cast(pd->state(Data_Buffer_Role::Edit)); PROP_R(std::weak_ptr, i_axis) PROP_R(std::weak_ptr, q_axis) PROP_R(Range, i_range) @@ -136,7 +136,7 @@ std::shared_ptr Planisphere::Builder::build() { return ret; } void Planisphere::set_to_axis_center() { - Planisphere_Render_State* s = reinterpret_cast(d()->state(State_Edit)); + Planisphere_Render_State* s = reinterpret_cast(d()->state(Data_Buffer_Role::Edit)); auto i_axis = s->i_axis.lock(); auto q_axis = s->q_axis.lock(); if (!i_axis || !q_axis) diff --git a/Renderive/plottable/Planisphere.h b/Renderive/plottable/Planisphere.h index 53e0091..74cfce7 100644 --- a/Renderive/plottable/Planisphere.h +++ b/Renderive/plottable/Planisphere.h @@ -14,6 +14,7 @@ enum class Planisphere_Type : std::uint8_t { Psk16 = 16 }; struct Planisphere_Private; +struct Planisphere_Private_Access; class Grid; /// @brief I/Q 星座图元。 class LIB_DECL Planisphere : public Renderable { @@ -44,6 +45,7 @@ public: void set_to_axis_center(); struct Builder; private: + friend struct Planisphere_Private_Access; Planisphere_Private* d(); Render_Object_Owner create_render_data() override; Render_Object_Owner create_render_state() override; diff --git a/Renderive/plottable/Planisphere_p.h b/Renderive/plottable/Planisphere_p.h index 42950db..5980553 100644 --- a/Renderive/plottable/Planisphere_p.h +++ b/Renderive/plottable/Planisphere_p.h @@ -101,7 +101,8 @@ struct Planisphere_Private : Typed_Render_Dataanchor_set.lock()) anchor_set->give_points(anchor_points(s), false); } - void draw(QPainter* painter, const Plot_Render_Snapshot&) override { + void rasterize(Frame_Raster_Context& context, const Plot_Render_Snapshot&) override { + QPainter* painter = &context.qt(); Q_UNUSED(painter) } }; diff --git a/Renderive/plottable/Spectrum.cpp b/Renderive/plottable/Spectrum.cpp index ee9ed7a..b33e133 100644 --- a/Renderive/plottable/Spectrum.cpp +++ b/Renderive/plottable/Spectrum.cpp @@ -84,15 +84,21 @@ bool Spectrum::visible_range_only() { void Spectrum::set_visible_range_only(bool value) { d()->set_state_value(&Spectrum_Render_State::visible_range_only, value); } -Line_Interpolation_Mode Spectrum::interpolation_mode() { +Line_Interpolation Spectrum::interpolation_mode() { return d()->edit_state_value(&Spectrum_Render_State::interpolation_mode); } -void Spectrum::set_interpolation_mode(Line_Interpolation_Mode value) { +void Spectrum::set_interpolation_mode(Line_Interpolation value) { d()->set_state_value(&Spectrum_Render_State::interpolation_mode, value); } Line_Sampling_Mode Spectrum::sample_mode() { return d()->edit_state_value(&Spectrum_Render_State::sample_mode); } +Interpolation_Value_Domain Spectrum::interpolation_value_domain() { + return d()->edit_state_value(&Spectrum_Render_State::interpolation_value_domain); +} +void Spectrum::set_interpolation_value_domain(Interpolation_Value_Domain value) { + d()->set_state_value(&Spectrum_Render_State::interpolation_value_domain, value); +} void Spectrum::set_sample_mode(Line_Sampling_Mode value) { d()->set_state_value(&Spectrum_Render_State::sample_mode, value); } @@ -169,9 +175,9 @@ void Spectrum::set_frequency_range(Range frequency_range) { Spectrum::Builder::Builder(std::shared_ptr parent, std::shared_ptr frequency_axis, std::shared_ptr power_axis) { ASSERT(parent, "Spectrum build error! parent == nullptr"); ASSERT(frequency_axis && power_axis, "Spectrum build error! axis == nullptr"); - ASSERT(frequency_axis->plot != nullptr, "Spectrum build error! frequency_axis->plot == nullptr"); - ASSERT(frequency_axis->plot == power_axis->plot, "Spectrum build error! frequency_axis->plot != power_axis->plot"); - ASSERT(parent->plot == frequency_axis->plot, "Spectrum build error! parent->plot != axis->plot"); + ASSERT(frequency_axis->is_attached(), "Spectrum build error! frequency_axis is detached"); + ASSERT(frequency_axis->shares_plot_with(*power_axis), "Spectrum build error! axes belong to different plots"); + ASSERT(parent->shares_plot_with(*frequency_axis), "Spectrum build error! parent and axes belong to different plots"); this->parent = parent; this->frequency_axis = frequency_axis; this->power_axis = power_axis; @@ -180,7 +186,7 @@ std::shared_ptr Spectrum::Builder::build() { auto ret = renderive::make_shared(); ret->init(parent); Spectrum_Private* pd = ret->d(); - Spectrum_Render_State* sc = reinterpret_cast(pd->state(State_Edit)); + Spectrum_Render_State* sc = reinterpret_cast(pd->state(Data_Buffer_Role::Edit)); PROP_R(std::weak_ptr, frequency_axis) PROP_R(std::weak_ptr, power_axis); PROP_R(int, frequency_point_size); @@ -193,7 +199,8 @@ std::shared_ptr Spectrum::Builder::build() { PROP_R(bool, use_min_marker); PROP_R(bool, use_sweep_frequency_rect); PROP_R(bool, visible_range_only); - PROP_R(Line_Interpolation_Mode, interpolation_mode); + PROP_R(Line_Interpolation, interpolation_mode); + PROP_R(Interpolation_Value_Domain, interpolation_value_domain); PROP_R(Line_Sampling_Mode, sample_mode); PROP_R(QBrush, max_brush); PROP_R(QBrush, cur_brush); @@ -230,7 +237,6 @@ void Spectrum_Private::update_frequency_point_size(int frequency_point_size, Ran max_powers.clear(); cur_powers.clear(); min_powers.clear(); - frequency.clear(); return; } cache_frequency_point_size = frequency_point_size; @@ -239,28 +245,19 @@ void Spectrum_Private::update_frequency_point_size(int frequency_point_size, Ran max_powers.resize(frequency_point_size); cur_powers.resize(frequency_point_size); min_powers.resize(frequency_point_size); - frequency.resize(frequency_point_size); - if (frequency_point_size == 1) { - frequency[0] = frequency_range.origin; - return; - } - double cur = frequency_range.origin; - double step = frequency_range.length() / (frequency_point_size - 1); - frequency[0] = frequency_range.origin; - for (int i = 1; i < frequency_point_size; ++i) { - cur += step; - frequency[i] = cur; - } } -void Spectrum_Private::set_frequency_data(std::pmr::vector& line_data, const std::pmr::vector* max_data, const std::pmr::vector* min_data, const Spectrum_Render_State* s) { +double Spectrum_Private::frequency_at(int index) const { + if (cache_frequency_point_size <= 1) + return cache_frequency_range.origin; + return cache_frequency_range.origin + cache_frequency_range.length() * static_cast(index) / static_cast(cache_frequency_point_size - 1); +} +void Spectrum_Private::set_frequency_data(std::pmr::vector& line_data, const Spectrum_Render_State* s) { if (s->frequency_point_size <= 0) return; if (line_data.size() != s->frequency_point_size) return; - if ((max_data && max_data->size() != s->frequency_point_size) || (min_data && min_data->size() != s->frequency_point_size)) - return; cur_powers.swap(line_data); - if (!has_power_data) { + if (!has_power_data || hold_reset_requested.exchange(false, std::memory_order_acq_rel)) { max_powers.assign(cur_powers.begin(), cur_powers.end()); min_powers.assign(cur_powers.begin(), cur_powers.end()); has_power_data = true; @@ -271,14 +268,12 @@ void Spectrum_Private::set_frequency_data(std::pmr::vector& line_data, c for (int i = 0; i < s->frequency_point_size; ++i) { double& cur_data = cur_powers[i]; if (s->use_max_line) { - double hold_max = max_data ? max_data->at(i) : cur_data; - if (hold_max > max_powers.at(i)) - max_powers[i] = hold_max; + if (cur_data > max_powers.at(i)) + max_powers[i] = cur_data; } if (s->use_min_line) { - double hold_min = min_data ? min_data->at(i) : cur_data; - if (hold_min < min_powers.at(i)) - min_powers[i] = hold_min; + if (cur_data < min_powers.at(i)) + min_powers[i] = cur_data; } if (s->use_max_marker && cur_data > max) { max = cur_data; @@ -289,23 +284,23 @@ void Spectrum_Private::set_frequency_data(std::pmr::vector& line_data, c min_index = i; } } - max_marker_key = frequency[max_index]; + max_marker_key = frequency_at(max_index); max_marker_value = cur_powers[max_index]; - min_marker_key = frequency[min_index]; + min_marker_key = frequency_at(min_index); min_marker_value = cur_powers[min_index]; } -double Spectrum_Private::get_y(double x, bool& ok, Line_Interpolation_Mode mode) { +double Spectrum_Private::get_y(double x, bool& ok, Line_Interpolation mode, Interpolation_Value_Domain domain) { if (cur_powers.size() < 2 || cache_frequency_range.length() == 0.0 || !cache_frequency_range.contain(x)) { ok = false; return -1; } double index = coord_to_source_index(cache_frequency_range, cur_powers.size(), x); ok = true; - return interpolate_line_value(index, cur_powers.size(), mode, [this](int i) { + return interpolate_line_value(index, cur_powers.size(), mode, domain, [this](int i) { return cur_powers.at(i); }); } -void Spectrum_Private::draw_power_curve(QPainter* painter, Spectrum_Render_State* s, const std::pmr::vector& powers, const QPen& pen, const QBrush& brush) { +void Spectrum_Private::draw_power_curve(Frame_Raster_Context& context, Spectrum_Render_State* s, const std::pmr::vector& powers, const QPen& pen, const QBrush& brush) { auto frequency_axis = s->frequency_axis.lock(); auto power_axis = s->power_axis.lock(); if (!frequency_axis || !power_axis) @@ -314,21 +309,26 @@ void Spectrum_Private::draw_power_curve(QPainter* painter, Spectrum_Render_State return; std::pmr::vector points(frame_memory_resource()); int point_count = static_cast(powers.size()); - if (s->sample_mode == Line_Sampling_Mode::Pixel_Peak_Envelope) { + if (s->sample_mode == Line_Sampling_Mode::Preserve_Extrema) { points = Curve_Utils::create_peak_bucket_points(frequency_axis.get(), power_axis.get(), s->frequency_range, point_count, point_count, s->visible_range_only, [&powers](int i) { return powers.at(i); }); } else { - points = Curve_Utils::create_sampled_points(frequency_axis.get(), power_axis.get(), s->frequency_range, point_count, point_count, s->visible_range_only, s->interpolation_mode, [&powers](int i) { + points = Curve_Utils::create_sampled_points(frequency_axis.get(), power_axis.get(), s->frequency_range, point_count, point_count, s->visible_range_only, s->interpolation_mode, s->interpolation_value_domain, [&powers](int i) { return powers.at(i); }); } - painter->save(); - Curve_Utils::draw_polyline(painter, points, pen); - double baseline_y = power_axis->d()->render_coord_to_pixel(power_axis->d()->render_coord_range().target); - Curve_Utils::draw_fill_to_y(painter, points, baseline_y, brush); - painter->restore(); + Curve_Utils::draw_polyline(context, points, pen); + const auto power_transform = render_axis_transform(*power_axis); + double baseline_y = power_transform.to_pixel(power_transform.coordinate_range.target); + if (s->sample_mode == Line_Sampling_Mode::Preserve_Extrema) { + const auto fill_points = Curve_Utils::representative_fill_points(points); + Curve_Utils::draw_fill_to_y(context, fill_points, baseline_y, brush); + } + else { + Curve_Utils::draw_fill_to_y(context, points, baseline_y, brush); + } } void Spectrum_Private::update_marker_items(const Spectrum_Render_State* s) { auto frequency_axis = s->frequency_axis.lock(); @@ -371,7 +371,7 @@ void Spectrum_Private::update_marker_items(const Spectrum_Render_State* s) { for (int i = 0; i < s->marker_list.size(); ++i) { const Spectrum_Marker& custom_marker = s->marker_list.at(i); bool ok; - double value = get_y(custom_marker.frequency, ok, s->interpolation_mode); + double value = get_y(custom_marker.frequency, ok, s->interpolation_mode, s->interpolation_value_domain); if (ok) append_marker(custom_marker.frequency, value, custom_marker.draw_line, i == s->cur_select_index); } @@ -380,31 +380,51 @@ void Spectrum_Private::update_marker_items(const Spectrum_Render_State* s) { if (auto label_item = this->label_item.lock()) label_item->give_items(std::move(label_items), false); } -void Spectrum_Private::draw_spectrum(QPainter* painter, Spectrum_Render_State* s) { - if (s->use_sweep_frequency_rect) { - s->draw_axis_rect(painter, s->sweep_frequency_range, s->middle_sweep_frequency); - } +void Spectrum_Private::draw_spectrum(Frame_Raster_Context& context, Spectrum_Render_State* s) { + if (s->use_sweep_frequency_rect) + draw_axis_rect(&context.qt(), s); } -void Spectrum_Private::draw(QPainter* painter, const Plot_Render_Snapshot&) { +void Spectrum_Private::draw_axis_rect(QPainter* painter, const Spectrum_Render_State* s) const { + auto frequency_axis = s->frequency_axis.lock(); + auto power_axis = s->power_axis.lock(); + if (!frequency_axis || !power_axis) + return; + const auto frequency_transform = render_axis_transform(*frequency_axis); + const auto power_transform = render_axis_transform(*power_axis); + const Range power_range = power_transform.coordinate_range; + const double pixel_min = frequency_transform.to_pixel(s->sweep_frequency_range.origin); + const double pixel_max = frequency_transform.to_pixel(s->sweep_frequency_range.target); + const double pixel_middle = frequency_transform.to_pixel(s->middle_sweep_frequency); + const double min = power_transform.to_pixel(power_range.origin); + const double max = power_transform.to_pixel(power_range.target); + painter->save(); + painter->setPen(Qt::NoPen); + painter->setBrush(s->sweep_rect_brush); + painter->drawRect(QRectF(pixel_min, min, pixel_max - pixel_min, max - min)); + painter->setPen(s->middle_frequency_pen); + painter->drawLine(QPointF(pixel_middle, min), QPointF(pixel_middle, max)); + painter->restore(); +} +void Spectrum_Private::rasterize(Frame_Raster_Context& context, const Plot_Render_Snapshot&) { auto s = render_state(); - draw_power_curve(painter, s, cur_powers, s->cur_pen, s->cur_brush); - draw_spectrum(painter, s); + draw_power_curve(context, s, cur_powers, s->cur_pen, s->cur_brush); + draw_spectrum(context, s); if (s->use_max_line) - draw_power_curve(painter, s, max_powers, s->max_pen, s->max_brush); + draw_power_curve(context, s, max_powers, s->max_pen, s->max_brush); if (s->use_min_line) - draw_power_curve(painter, s, min_powers, s->min_pen, s->min_brush); + draw_power_curve(context, s, min_powers, s->min_pen, s->min_brush); if (q()->hover_ok(q())) { auto frequency_axis = s->frequency_axis.lock(); auto power_axis = s->power_axis.lock(); if (frequency_axis && power_axis) - q()->draw_hover(painter, frequency_axis.get(), power_axis.get()); + q()->draw_hover(&context.qt(), frequency_axis.get(), power_axis.get()); } } std::size_t Spectrum_Private::estimated_frame_memory(const Plot_Render_Snapshot& snapshot) { const Spectrum_Render_State* s = render_state(); int line_count = 1 + (s->use_max_line ? 1 : 0) + (s->use_min_line ? 1 : 0); int point_count = static_cast(cur_powers.size()); - if (s->sample_mode == Line_Sampling_Mode::Pixel_Peak_Envelope) + if (s->sample_mode == Line_Sampling_Mode::Preserve_Extrema) point_count = std::min(point_count, snapshot.size.width() * 4 + 4); return static_cast(line_count) * static_cast(std::max(0, point_count)) * sizeof(QPointF) + 32 * 1024; } @@ -417,7 +437,7 @@ void Spectrum::give_data(std::span line_data) { if (line_data.size() != frequency_point_size) return; Render_Input_Lease input(pd); - input->push(line_data, state->use_max_line, state->use_min_line); + input->push(line_data); } void Spectrum::give_data(std::pmr::vector&& line_data) { if (!ok()) @@ -428,7 +448,7 @@ void Spectrum::give_data(std::pmr::vector&& line_data) { if (line_data.size() != frequency_point_size) return; Render_Input_Lease input(pd); - input->push(std::move(line_data), state->use_max_line, state->use_min_line); + input->push(std::move(line_data)); } Update_Ticket Spectrum::submit_data(std::span line_data) { auto update_state = make_update_state(); @@ -445,7 +465,7 @@ Update_Ticket Spectrum::submit_data(std::span line_data) { } { Render_Input_Lease input(pd); - input->push(line_data, state->use_max_line, state->use_min_line); + input->push(line_data); pd->set_input_completion(input.lease, update_state); } update_state->complete_until(Update_Stage::Input_Released); @@ -466,12 +486,16 @@ Update_Ticket Spectrum::submit_data(std::pmr::vector&& line_data) { } { Render_Input_Lease input(pd); - input->push(std::move(line_data), state->use_max_line, state->use_min_line); + input->push(std::move(line_data)); pd->set_input_completion(input.lease, update_state); } update_state->complete_until(Update_Stage::Input_Released); return Update_Ticket(update_state); } +void Spectrum::reset_holds() { + d()->hold_reset_requested.store(true, std::memory_order_release); + mark_render_dirty(); +} void Spectrum::add_custom_marker(double frequency) { Render_Edit_Lease edit(d()); edit->marker_list.push_back(Spectrum_Marker{frequency, false}); @@ -501,7 +525,7 @@ void Spectrum::clear_all_custom_marker() { edit->marker_list.clear(); } double Spectrum::get_mark_frequency(int mark_index) { - auto render_state = d()->state_data(SRC::Edit); + auto render_state = d()->edit_state(); if (mark_index < 0 || mark_index >= render_state->marker_list.size()) return -1; return render_state->marker_list[mark_index].frequency; @@ -515,33 +539,37 @@ void Spectrum::set_marker_frequency(int marker_index, double frequency) { } void Spectrum::set_current_marker_frequency(double frequency) { Render_Edit_Lease edit(d()); - edit->set_cur_frequency(frequency); + if (edit->cur_select_index >= 0 && edit->cur_select_index < edit->marker_list.size()) + edit->marker_list[edit->cur_select_index].frequency = frequency; } int Spectrum::select_line_marker_size() { - auto render_state = d()->state_data(SRC::Edit); + auto render_state = d()->edit_state(); return render_state->marker_list.size(); } int Spectrum::cur_select_marker_index() { - auto render_state = d()->state_data(SRC::Edit); + auto render_state = d()->edit_state(); return render_state->cur_select_index; } void Spectrum::set_selected_line_marker(int marker_index) { Render_Edit_Lease edit(d()); - edit->set_select_index(marker_index); + if (marker_index >= 0 && marker_index < edit->marker_list.size()) + edit->cur_select_index = marker_index; } void Spectrum::select_next() { Render_Edit_Lease edit(d()); - edit->select_next_marker(); + if (!edit->marker_list.empty()) + edit->cur_select_index = (edit->cur_select_index + 1) % edit->marker_list.size(); } void Spectrum::select_previous() { Render_Edit_Lease edit(d()); - edit->select_prev_marker(); + if (!edit->marker_list.empty()) + edit->cur_select_index = (edit->cur_select_index - 1 + edit->marker_list.size()) % edit->marker_list.size(); } void Spectrum::un_select_marker() { Render_Edit_Lease edit(d()); - edit->un_select(); + edit->cur_select_index = -1; } double Spectrum::get_power(double frequency, bool& ok) { - return d()->get_y(frequency, ok, interpolation_mode()); + return d()->get_y(frequency, ok, interpolation_mode(), interpolation_value_domain()); } } // namespace renderive diff --git a/Renderive/plottable/Spectrum.h b/Renderive/plottable/Spectrum.h index 7487fb7..5c4a819 100644 --- a/Renderive/plottable/Spectrum.h +++ b/Renderive/plottable/Spectrum.h @@ -8,7 +8,7 @@ #include "../base/Memory.h" #include "../primitive/Curve.h" #include "Hover_Info.h" -#include "Interpolation.h" +#include "../base/Interpolation.h" namespace renderive { struct Spectrum_Private; class Label; @@ -43,7 +43,8 @@ public: /// @brief 是否只生成当前可见频率范围。 PROP(bool, visible_range_only); /// @brief 折线插值模式。 - PROP(Line_Interpolation_Mode, interpolation_mode); + PROP(Line_Interpolation, interpolation_mode); + PROP(Interpolation_Value_Domain, interpolation_value_domain); /// @brief 采样降采样模式。 PROP(Line_Sampling_Mode, sample_mode); /// @brief 最大保持线填充刷。 @@ -74,6 +75,8 @@ public: Update_Ticket submit_data(std::span line_data); /// @brief 移动提交一帧频谱线数据并返回完成票据。 Update_Ticket submit_data(std::pmr::vector&& line_data); + /// @brief Reset persistent maximum and minimum hold lines to the next consumed frame. + void reset_holds(); /// @brief 用 writer 填充并写入一帧频谱线数据。 template void write_data(std::size_t count, Writer&& writer) { @@ -132,8 +135,9 @@ struct Spectrum_PROP { bool use_min_marker = false; bool use_sweep_frequency_rect = false; bool visible_range_only = true; - Line_Interpolation_Mode interpolation_mode = Line_Interpolation_Mode::Linear_Display; - Line_Sampling_Mode sample_mode = Line_Sampling_Mode::All_Samples; + Line_Interpolation interpolation_mode = Line_Interpolation::Linear; + Interpolation_Value_Domain interpolation_value_domain = Interpolation_Value_Domain::Display; + Line_Sampling_Mode sample_mode = Line_Sampling_Mode::Exact; QBrush max_brush; QBrush cur_brush; QBrush min_brush; @@ -165,7 +169,8 @@ struct LIB_DECL Spectrum::Builder : Spectrum_PROP { PROP_B(bool, use_min_marker); PROP_B(bool, use_sweep_frequency_rect); PROP_B(bool, visible_range_only); - PROP_B(Line_Interpolation_Mode, interpolation_mode); + PROP_B(Line_Interpolation, interpolation_mode); + PROP_B(Interpolation_Value_Domain, interpolation_value_domain); PROP_B(Line_Sampling_Mode, sample_mode); PROP_B(QBrush, max_brush); PROP_B(QBrush, cur_brush); diff --git a/Renderive/plottable/Spectrum_p.h b/Renderive/plottable/Spectrum_p.h index 8ea8fee..3df0819 100644 --- a/Renderive/plottable/Spectrum_p.h +++ b/Renderive/plottable/Spectrum_p.h @@ -8,8 +8,9 @@ #include "../architecture/Renderable.h" #include "../base/Frame_Memory.h" #include "../base/Memory.h" +#include "../primitive/Curve_Utils_p.h" #include "Hover_Info.h" -#include "Interpolation_p.h" +#include "../base/Interpolation_p.h" #include "Spectrum.h" namespace renderive { struct Spectrum_Marker { @@ -23,133 +24,28 @@ struct Spectrum_Marker { }; struct Spectrum_Input_Data : Latest_Input_Data { std::pmr::vector cur_data{memory_resource(Memory_Domain::Spectrum)}; - std::pmr::vector max_data{memory_resource(Memory_Domain::Spectrum)}; - std::pmr::vector min_data{memory_resource(Memory_Domain::Spectrum)}; - bool has_data = false; - bool has_max_data = false; - bool has_min_data = false; - void push(std::span data, bool use_max, bool use_min) { + void push(std::span data) { cur_data.assign(data.begin(), data.end()); - has_data = true; - if (use_max) { - if (!has_max_data || max_data.size() != data.size()) { - max_data.assign(data.begin(), data.end()); - has_max_data = true; - } - else { - for (int i = 0; i < data.size(); ++i) { - if (data[i] > max_data[i]) - max_data[i] = data[i]; - } - } - } - if (use_min) { - if (!has_min_data || min_data.size() != data.size()) { - min_data.assign(data.begin(), data.end()); - has_min_data = true; - } - else { - for (int i = 0; i < data.size(); ++i) { - if (data[i] < min_data[i]) - min_data[i] = data[i]; - } - } - } mark_latest_pushed(); } - void push(std::pmr::vector&& data, bool use_max, bool use_min) { + void push(std::pmr::vector&& data) { cur_data = std::move(data); - has_data = true; - if (use_max) { - if (!has_max_data || max_data.size() != cur_data.size()) { - max_data.assign(cur_data.begin(), cur_data.end()); - has_max_data = true; - } - else { - for (int i = 0; i < cur_data.size(); ++i) { - if (cur_data[i] > max_data[i]) - max_data[i] = cur_data[i]; - } - } - } - if (use_min) { - if (!has_min_data || min_data.size() != cur_data.size()) { - min_data.assign(cur_data.begin(), cur_data.end()); - has_min_data = true; - } - else { - for (int i = 0; i < cur_data.size(); ++i) { - if (cur_data[i] < min_data[i]) - min_data[i] = cur_data[i]; - } - } - } mark_latest_pushed(); } template void read_all(Handler handler) { - if (!has_data) + if (!pending) return; - handler(cur_data, has_max_data ? &max_data : nullptr, has_min_data ? &min_data : nullptr); + handler(cur_data); } void clear() override { cur_data.clear(); - max_data.clear(); - min_data.clear(); - has_data = false; - has_max_data = false; - has_min_data = false; clear_latest(); } }; struct Spectrum_Render_State : Render_State, Hover_Info_Render_State, Spectrum_PROP { std::pmr::vector marker_list{memory_resource(Memory_Domain::Spectrum)}; int cur_select_index = -1; - void un_select() { - cur_select_index = -1; - } - void select_next_marker() { - if (marker_list.empty()) - return; - cur_select_index = (cur_select_index + 1) % marker_list.size(); - } - void select_prev_marker() { - if (marker_list.empty()) - return; - cur_select_index = (cur_select_index - 1 + marker_list.size()) % marker_list.size(); - } - void set_select_index(int index) { - if (index < 0 || index >= marker_list.size()) - return; - cur_select_index = index; - } - void set_cur_frequency(double frequency) { - set_frequency(cur_select_index, frequency); - } - void set_frequency(int index, double frequency) { - if (index < 0 || index >= marker_list.size()) - return; - marker_list[index].frequency = frequency; - } - void draw_axis_rect(QPainter* painter, Range range, double middle_x) const { - auto frequency_axis = this->frequency_axis.lock(); - auto power_axis = this->power_axis.lock(); - if (!frequency_axis || !power_axis) - return; - painter->save(); - painter->setPen(Qt::NoPen); - painter->setBrush(sweep_rect_brush); - double pixel_min = frequency_axis->d()->render_coord_to_pixel(range.origin); - double pixel_max = frequency_axis->d()->render_coord_to_pixel(range.target); - double pixel_middle = frequency_axis->d()->render_coord_to_pixel(middle_x); - Range r = power_axis->coord_range(); - double min = power_axis->d()->render_coord_to_pixel(r.origin); - double max = power_axis->d()->render_coord_to_pixel(r.target); - painter->drawRect(QRectF(pixel_min, min, pixel_max - pixel_min, max - min)); - painter->setPen(middle_frequency_pen); - painter->drawLine(QPointF(pixel_middle, min), QPointF(pixel_middle, max)); - painter->restore(); - } }; struct Spectrum_Private : Typed_Render_Data { std::weak_ptr marker_item; @@ -161,17 +57,19 @@ struct Spectrum_Private : Typed_Render_Data frequency{memory_resource(Memory_Domain::Spectrum)}; + std::atomic_bool hold_reset_requested{false}; std::pmr::vector max_powers{memory_resource(Memory_Domain::Spectrum)}; std::pmr::vector cur_powers{memory_resource(Memory_Domain::Spectrum)}; std::pmr::vector min_powers{memory_resource(Memory_Domain::Spectrum)}; void update_frequency_point_size(int frequency_point_size, Range frequency_range); - void set_frequency_data(std::pmr::vector& line_data, const std::pmr::vector* max_data, const std::pmr::vector* min_data, const Spectrum_Render_State* s); + void set_frequency_data(std::pmr::vector& line_data, const Spectrum_Render_State* s); + [[nodiscard]] double frequency_at(int index) const; + void draw_axis_rect(QPainter* painter, const Spectrum_Render_State* s) const; void update_marker_items(const Spectrum_Render_State* s); - [[nodiscard]] double get_y(double x, bool& ok, Line_Interpolation_Mode mode); - void draw_power_curve(QPainter* painter, Spectrum_Render_State* s, const std::pmr::vector& powers, const QPen& pen, const QBrush& brush); - void draw_spectrum(QPainter* painter, Spectrum_Render_State* s); - void draw(QPainter* painter, const Plot_Render_Snapshot& snapshot) override; + [[nodiscard]] double get_y(double x, bool& ok, Line_Interpolation mode, Interpolation_Value_Domain domain); + void draw_power_curve(Frame_Raster_Context& context, Spectrum_Render_State* s, const std::pmr::vector& powers, const QPen& pen, const QBrush& brush); + void draw_spectrum(Frame_Raster_Context& context, Spectrum_Render_State* s); + void rasterize(Frame_Raster_Context& context, const Plot_Render_Snapshot& snapshot) override; std::size_t estimated_frame_memory(const Plot_Render_Snapshot& snapshot) override; void prepare_data(const Plot_Render_Snapshot&) override { sync_state_pipeline(); @@ -180,8 +78,8 @@ struct Spectrum_Private : Typed_Render_Datafrequency_point_size, s->frequency_range); } auto input = render_input_data(); - input->read_all([this, s](std::pmr::vector& data, const std::pmr::vector* max_data, const std::pmr::vector* min_data) { - set_frequency_data(data, max_data, min_data, s); + input->read_all([this, s](std::pmr::vector& data) { + set_frequency_data(data, s); }); if (has_power_data) { update_marker_items(s); @@ -194,12 +92,12 @@ struct Spectrum_Private : Typed_Render_Datapower_axis.lock(); if (!frequency_axis || !power_axis) return false; - const double frequency = frequency_axis->d()->render_pixel_to_coord(pos.x()); + const double frequency = render_axis_transform(*frequency_axis).to_coordinate(pos.x()); bool ok; - const double power = get_y(frequency, ok, sc->interpolation_mode); + const double power = get_y(frequency, ok, sc->interpolation_mode, sc->interpolation_value_domain); if (!ok) return false; - return qAbs(power_axis->d()->render_coord_to_pixel(power) - pos.y()) < 4; + return qAbs(render_axis_transform(*power_axis).to_pixel(power) - pos.y()) < 4; } void set_hover_state(const QPoint& pos, bool active) override { Render_Edit_Lease edit(this); diff --git a/Renderive/plottable/Sweep_Frequency.cpp b/Renderive/plottable/Sweep_Frequency.cpp index 858e3b2..bc6d197 100644 --- a/Renderive/plottable/Sweep_Frequency.cpp +++ b/Renderive/plottable/Sweep_Frequency.cpp @@ -54,10 +54,10 @@ bool Sweep_Frequency::visible_range_only() { void Sweep_Frequency::set_visible_range_only(bool value) { d()->set_state_value(&Sweep_Frequency_Render_State::visible_range_only, value); } -Line_Interpolation_Mode Sweep_Frequency::interpolation_mode() { +Line_Interpolation Sweep_Frequency::interpolation_mode() { return d()->edit_state_value(&Sweep_Frequency_Render_State::interpolation_mode); } -void Sweep_Frequency::set_interpolation_mode(Line_Interpolation_Mode value) { +void Sweep_Frequency::set_interpolation_mode(Line_Interpolation value) { d()->set_state_value(&Sweep_Frequency_Render_State::interpolation_mode, value); } void Sweep_Frequency::give_data(std::span data) { @@ -85,9 +85,9 @@ void Sweep_Frequency::give_data(std::pmr::vector&& data) { Sweep_Frequency::Builder::Builder(std::shared_ptr parent, std::shared_ptr frequency_axis, std::shared_ptr power_axis) { ASSERT(parent, "Sweep_Frequency build error! parent == nullptr"); ASSERT(frequency_axis && power_axis, "Sweep_Frequency build error! axis == nullptr"); - ASSERT(frequency_axis->plot != nullptr, "Sweep_Frequency build error! frequency_axis->plot == nullptr"); - ASSERT(frequency_axis->plot == power_axis->plot, "Sweep_Frequency build error! frequency_axis->plot != power_axis->plot"); - ASSERT(parent->plot == frequency_axis->plot, "Sweep_Frequency build error! parent->plot != axis->plot"); + ASSERT(frequency_axis->is_attached(), "Sweep_Frequency build error! frequency_axis is detached"); + ASSERT(frequency_axis->shares_plot_with(*power_axis), "Sweep_Frequency build error! axes belong to different plots"); + ASSERT(parent->shares_plot_with(*frequency_axis), "Sweep_Frequency build error! parent and axes belong to different plots"); this->parent = parent; this->frequency_axis = frequency_axis; this->power_axis = power_axis; @@ -96,7 +96,7 @@ std::shared_ptr Sweep_Frequency::Builder::build() { auto ret = renderive::make_shared(); ret->init(parent); Sweep_Frequency_Private* pd = ret->d(); - Sweep_Frequency_Render_State* sc = reinterpret_cast(pd->state(State_Edit)); + Sweep_Frequency_Render_State* sc = reinterpret_cast(pd->state(Data_Buffer_Role::Edit)); PROP_R(std::weak_ptr, frequency_axis) PROP_R(std::weak_ptr, power_axis) PROP_R(Range, frequency_range) @@ -105,7 +105,7 @@ std::shared_ptr Sweep_Frequency::Builder::build() { PROP_R(QPen, pen) PROP_R(QPen, cur_frequency_pen) PROP_R(bool, visible_range_only) - PROP_R(Line_Interpolation_Mode, interpolation_mode) + PROP_R(Line_Interpolation, interpolation_mode) sc->block_frequency_point_size = qMax(1, sc->block_frequency_point_size); sc->block_num = qMax(1, sc->block_num); if (sc->frequency_range.length() == 0.0) diff --git a/Renderive/plottable/Sweep_Frequency.h b/Renderive/plottable/Sweep_Frequency.h index 0af2b8e..f6e0feb 100644 --- a/Renderive/plottable/Sweep_Frequency.h +++ b/Renderive/plottable/Sweep_Frequency.h @@ -5,7 +5,7 @@ #include #include #include "../base/Memory.h" -#include "Interpolation.h" +#include "../base/Interpolation.h" #include "global.h" namespace renderive { struct Sweep_Frequency_Private; @@ -43,7 +43,7 @@ public: /// @brief 是否只生成当前可见频率范围。 PROP(bool, visible_range_only) /// @brief 折线插值模式。 - PROP(Line_Interpolation_Mode, interpolation_mode) + PROP(Line_Interpolation, interpolation_mode) struct Builder; private: Sweep_Frequency_Private* d(); @@ -60,7 +60,7 @@ struct Sweep_Frequency_Prop { QPen pen; QPen cur_frequency_pen; bool visible_range_only = true; - Line_Interpolation_Mode interpolation_mode = Line_Interpolation_Mode::Linear_Display; + Line_Interpolation interpolation_mode = Line_Interpolation::Linear; Sweep_Frequency_Prop() { pen = QPen(Qt::yellow); cur_frequency_pen = QPen(Qt::red); @@ -78,7 +78,7 @@ struct LIB_DECL Sweep_Frequency::Builder : Sweep_Frequency_Prop { PROP_B(QPen, pen) PROP_B(QPen, cur_frequency_pen) PROP_B(bool, visible_range_only) - PROP_B(Line_Interpolation_Mode, interpolation_mode) + PROP_B(Line_Interpolation, interpolation_mode) /// 创建扫频构造器。 Builder(std::shared_ptr parent, std::shared_ptr frequency_axis, std::shared_ptr power_axis); /// 构建并返回扫频对象。 diff --git a/Renderive/plottable/Sweep_Frequency_p.h b/Renderive/plottable/Sweep_Frequency_p.h index d60daec..3d6f69e 100644 --- a/Renderive/plottable/Sweep_Frequency_p.h +++ b/Renderive/plottable/Sweep_Frequency_p.h @@ -3,8 +3,8 @@ #include "../architecture/Bounded_Input_Buffer.h" #include "../plot/Plot_p.h" #include "../base/Frame_Memory.h" -#include "../primitive/Curve.h" -#include "Interpolation_p.h" +#include "../primitive/Curve_Utils_p.h" +#include "../base/Interpolation_p.h" #include "Sweep_Frequency.h" namespace renderive { struct Sweep_Frequency_Render_State : renderive::Render_State, Sweep_Frequency_Prop {}; @@ -75,7 +75,7 @@ struct Sweep_Frequency_Private : renderive::Typed_Render_Datafrequency_axis.lock(); auto power_axis = s->power_axis.lock(); @@ -84,13 +84,14 @@ struct Sweep_Frequency_Private : renderive::Typed_Render_Data data(frame_memory_resource()); - data = Curve_Utils::create_sampled_points(frequency_axis.get(), power_axis.get(), s->frequency_range, n, available_count, s->visible_range_only, s->interpolation_mode, [this](int i) { + data = Curve_Utils::create_sampled_points(frequency_axis.get(), power_axis.get(), s->frequency_range, n, available_count, s->visible_range_only, s->interpolation_mode, Interpolation_Value_Domain::Display, [this](int i) { return frequency_list.at(i).y(); }); - Curve_Utils::draw_polyline(painter, data, s->pen); + Curve_Utils::draw_polyline(context, data, s->pen); + QPainter* painter = &context.qt(); painter->setPen(s->cur_frequency_pen); double f = block_num > 1 ? offset * frequency_range.length() / (block_num - 1.0) : frequency_range.origin; - double ff = frequency_axis->d()->render_coord_to_pixel(f); + double ff = render_axis_transform(*frequency_axis).to_pixel(f); painter->drawLine(QPointF(ff, 0), QPointF(ff, snapshot.size.height())); } std::size_t estimated_frame_memory(const Plot_Render_Snapshot& snapshot) override { diff --git a/Renderive/plottable/Waterfall.cpp b/Renderive/plottable/Waterfall.cpp index 057058e..8206161 100644 --- a/Renderive/plottable/Waterfall.cpp +++ b/Renderive/plottable/Waterfall.cpp @@ -61,6 +61,12 @@ void Waterfall::set_row_update_policy(Waterfall_Row_Update_Policy value) { int Waterfall::latest_row_min_interval_ms() { return d()->edit_state_value(&Waterfall_Render_State::latest_row_min_interval_ms); } +Waterfall_Frequency_Reduction Waterfall::frequency_reduction() { + return d()->edit_state_value(&Waterfall_Render_State::frequency_reduction); +} +void Waterfall::set_frequency_reduction(Waterfall_Frequency_Reduction value) { + d()->set_state_value(&Waterfall_Render_State::frequency_reduction, value); +} void Waterfall::set_latest_row_min_interval_ms(int value) { d()->set_state_value(&Waterfall_Render_State::latest_row_min_interval_ms, qMax(1, value)); } @@ -143,11 +149,11 @@ Update_Ticket Waterfall::submit_data(int tick, std::pmr::vector&& data) return Update_Ticket(state); } Waterfall::Builder::Builder(std::shared_ptr parent, std::shared_ptr frequency_axis, std::shared_ptr time_axis) { - ASSERT(parent && parent->plot, "Waterfall build error! parent invalid"); + ASSERT(parent && parent->is_attached(), "Waterfall build error! parent invalid"); ASSERT(frequency_axis && time_axis, "Waterfall build error! axis == nullptr"); - ASSERT(frequency_axis->plot != nullptr, "Waterfall build error! frequency_axis->plot == nullptr"); - ASSERT(frequency_axis->plot == time_axis->plot, "Waterfall build error! frequency_axis->plot != time_axis->plot"); - ASSERT(parent->plot == frequency_axis->plot, "Waterfall build error! parent->plot != frequency_axis->plot"); + ASSERT(frequency_axis->is_attached(), "Waterfall build error! frequency_axis is detached"); + ASSERT(frequency_axis->shares_plot_with(*time_axis), "Waterfall build error! axes belong to different plots"); + ASSERT(parent->shares_plot_with(*frequency_axis), "Waterfall build error! parent and axes belong to different plots"); this->parent = parent; this->frequency_axis = frequency_axis; this->time_axis = time_axis; @@ -156,7 +162,7 @@ std::shared_ptr Waterfall::Builder::build() { auto ret = renderive::make_shared(); ret->init(parent); Waterfall_Private* pd = ret->d(); - Waterfall_Render_State* sc = reinterpret_cast(pd->state(State_Edit)); + Waterfall_Render_State* sc = reinterpret_cast(pd->state(Data_Buffer_Role::Edit)); PROP_R(std::weak_ptr, frequency_axis) PROP_R(std::weak_ptr, time_axis) PROP_R(Range, frequency_range) @@ -166,6 +172,7 @@ std::shared_ptr Waterfall::Builder::build() { PROP_R(Image_Interpolation_Mode, interpolation_mode) PROP_R(Waterfall_Row_Update_Policy, row_update_policy) PROP_R(int, latest_row_min_interval_ms) + PROP_R(Waterfall_Frequency_Reduction, frequency_reduction) PROP_R(Color_Map, color_map) sc->frequency_point_size = qMax(1, sc->frequency_point_size); sc->latest_row_min_interval_ms = qMax(1, sc->latest_row_min_interval_ms); diff --git a/Renderive/plottable/Waterfall.h b/Renderive/plottable/Waterfall.h index c0ca275..197fbb6 100644 --- a/Renderive/plottable/Waterfall.h +++ b/Renderive/plottable/Waterfall.h @@ -8,11 +8,12 @@ #include "../architecture/Update_Completion.h" #include "../base/Memory.h" #include "Hover_Info.h" -#include "Interpolation.h" +#include "../base/Interpolation.h" #include "../primitive/Color_Bar.h" #include "../primitive/Color_Map.h" namespace renderive { struct Waterfall_Private; +struct Waterfall_Private_Access; class Frequency_Axis; class Time_Axis; /// @brief Policy for consuming pending waterfall rows. @@ -24,6 +25,13 @@ enum class Waterfall_Row_Update_Policy : std::uint8_t { /// @brief Consume only the latest row while enforcing a minimum interval between commits. Rate_Limited_Latest }; +/// @brief Reduction used when several frequency bins map to one output pixel. +enum class Waterfall_Frequency_Reduction : std::uint8_t { + /// @brief Preserve the strongest power value in each output pixel bucket. + Peak_Power, + /// @brief Average dB values in linear-power space before converting back to dB. + Mean_Linear_Power +}; /// @brief 瀑布图图元,按时间行保存二维频谱强度。 class LIB_DECL Waterfall : public Renderable, public Hover_Info_Renderable { public: @@ -67,12 +75,15 @@ public: PROP(Waterfall_Row_Update_Policy, row_update_policy) /// @brief Timed_Latest 模式下的最小更新间隔。 PROP(int, latest_row_min_interval_ms) + /// @brief Frequency-bin reduction used when the source is wider than the display. + PROP(Waterfall_Frequency_Reduction, frequency_reduction) /// @brief 颜色映射表。 PROP(Color_Map, color_map) /// @brief 返回构造时启用的颜色条。 std::shared_ptr get_color_bar() const; struct Builder; private: + friend struct Waterfall_Private_Access; Waterfall_Private* d(); Render_Object_Owner create_render_data() override; Render_Object_Owner create_render_state() override; @@ -88,6 +99,7 @@ struct Waterfall_Prop { Image_Interpolation_Mode interpolation_mode = Image_Interpolation_Mode::Nearest; Waterfall_Row_Update_Policy row_update_policy = Waterfall_Row_Update_Policy::All_Pending; int latest_row_min_interval_ms = 10; + Waterfall_Frequency_Reduction frequency_reduction = Waterfall_Frequency_Reduction::Peak_Power; Color_Map color_map{}; bool color_bar_enabled = false; }; @@ -102,6 +114,7 @@ struct LIB_DECL Waterfall::Builder : Waterfall_Prop { PROP_B(Image_Interpolation_Mode, interpolation_mode) PROP_B(Waterfall_Row_Update_Policy, row_update_policy) PROP_B(int, latest_row_min_interval_ms) + PROP_B(Waterfall_Frequency_Reduction, frequency_reduction) PROP_B(Color_Map, color_map) PROP_B(bool, color_bar_enabled) /// 创建瀑布图构造器。 diff --git a/Renderive/plottable/Waterfall_p.h b/Renderive/plottable/Waterfall_p.h index dce0874..3d3fc17 100644 --- a/Renderive/plottable/Waterfall_p.h +++ b/Renderive/plottable/Waterfall_p.h @@ -1,9 +1,10 @@ #pragma once #include #include -#include -#include +#include #include +#include +#include #include #include #include @@ -12,135 +13,174 @@ #include "../Axis/Frequency_Axis_p.h" #include "../Axis/Time_Axis_p.h" #include "../architecture/Render_Time.h" -#include "../plot/Plot_p.h" #include "../architecture/Renderable.h" #include "../base/Memory.h" -#include "../base/global.h" #include "../primitive/Raster_Image_p.h" -#include "Core/Base/RingBuffer.hpp" -#include "Interpolation_p.h" #include "Waterfall.h" + namespace renderive { struct Waterfall_Data { - int tick; + int tick{}; std::pmr::vector frequency{memory_resource(Memory_Domain::Waterfall)}; }; + struct Waterfall_Input_Data : Input_Data { static constexpr std::size_t Max_Pending_Row_Count = 256; std::array, Max_Pending_Row_Count> rows; + std::size_t head{}; std::size_t row_count{}; + void push(int tick, std::span data) { - while (row_count >= Max_Pending_Row_Count) - drop_front(); - rows[row_count].emplace(); - rows[row_count]->tick = tick; - rows[row_count]->frequency.assign(data.begin(), data.end()); - ++row_count; + Waterfall_Data& row = append_row(); + row.tick = tick; + row.frequency.assign(data.begin(), data.end()); } void push(int tick, std::pmr::vector&& data) { - while (row_count >= Max_Pending_Row_Count) - drop_front(); - rows[row_count].emplace(); - rows[row_count]->tick = tick; - rows[row_count]->frequency = std::move(data); - ++row_count; + Waterfall_Data& row = append_row(); + row.tick = tick; + row.frequency = std::move(data); } template - void read_all(Handler handler) { - for (std::size_t i = 0; i < row_count; ++i) - handler(rows[i]->tick, rows[i]->frequency); + void read_all(Handler&& handler) { + for (std::size_t i = 0; i < row_count; ++i) { + auto& row = *rows[(head + i) % Max_Pending_Row_Count]; + handler(row.tick, row.frequency); + } clear_rows(); } void clear() override { clear_rows(); } + private: + Waterfall_Data& append_row() { + if (row_count == Max_Pending_Row_Count) { + rows[head].reset(); + head = (head + 1) % Max_Pending_Row_Count; + --row_count; + } + const std::size_t slot = (head + row_count) % Max_Pending_Row_Count; + rows[slot].emplace(); + ++row_count; + return *rows[slot]; + } void clear_rows() { for (std::size_t i = 0; i < row_count; ++i) - rows[i].reset(); + rows[(head + i) % Max_Pending_Row_Count].reset(); + head = 0; row_count = 0; } - void drop_front() { - rows[0].reset(); - for (std::size_t i = 1; i < row_count; ++i) { - rows[i - 1].emplace(std::move(*rows[i])); - rows[i].reset(); - } - --row_count; - } }; + struct Waterfall_Render_State : Render_State, Hover_Info_Render_State, Waterfall_Prop {}; -struct Waterfall_Ring_Buffer { - Psc::StreamRingBuffer_ST buffer{memory_resource(Memory_Domain::Waterfall)}; - std::pmr::vector record_buffer{memory_resource(Memory_Domain::Waterfall)}; - std::pmr::vector snapshot_buffer{memory_resource(Memory_Domain::Waterfall)}; - std::pmr::vector row_buffer{memory_resource(Memory_Domain::Waterfall)}; - std::size_t record_byte_size{}; - std::size_t row_byte_size{}; + +struct Waterfall_Row_Matrix { + std::pmr::vector values{memory_resource(Memory_Domain::Waterfall)}; + std::pmr::vector ticks{memory_resource(Memory_Domain::Waterfall)}; + std::pmr::vector sequences{memory_resource(Memory_Domain::Waterfall)}; int row_count{}; int col_count{}; int data_count{}; - bool match(int col, int row) const { - return col_count == col && row_count == row && record_byte_size; + int head{}; + std::uint64_t generation{}; + + [[nodiscard]] bool match(int col, int row) const { + return col_count == col && row_count == row && values.size() == static_cast(col) * static_cast(row); } void resize(int col, int row) { col_count = col; row_count = row; data_count = 0; - row_byte_size = sizeof(double) * col_count; - record_byte_size = row_byte_size + sizeof(int); - record_buffer.resize(record_byte_size); - snapshot_buffer.resize(record_byte_size * row_count); - row_buffer.resize(col_count); - buffer.init(record_byte_size * row_count); + head = 0; + values.assign(static_cast(col) * static_cast(row), 0.0); + ticks.assign(static_cast(row), 0); + sequences.assign(static_cast(row), 0); + ++generation; } - void push_data(const double* data, int tick) { - if (data_count == row_count) { - buffer.skip(record_byte_size); - --data_count; + void push_data(std::span data, int tick) { + if (static_cast(data.size()) != col_count || row_count <= 0) + return; + int slot{}; + if (data_count < row_count) { + slot = (head + data_count) % row_count; + ++data_count; } - std::memcpy(record_buffer.data(), data, row_byte_size); - std::memcpy(record_buffer.data() + row_byte_size, &tick, sizeof(tick)); - buffer.write(record_buffer.data(), record_byte_size); - ++data_count; + else { + slot = head; + head = (head + 1) % row_count; + } + std::copy(data.begin(), data.end(), values.begin() + static_cast(slot) * col_count); + ticks[static_cast(slot)] = tick; + sequences[static_cast(slot)] = ++generation; } - int snapshot() { - buffer.peek_best_effort(snapshot_buffer.data(), record_byte_size * data_count); + [[nodiscard]] int snapshot() const { return data_count; } - const double* frequency_data(int row, int first_col, int last_col) { - std::size_t offset = static_cast(first_col) * sizeof(double); - std::size_t size = static_cast(last_col - first_col + 1) * sizeof(double); - std::memcpy(reinterpret_cast(row_buffer.data()) + offset, snapshot_buffer.data() + row * record_byte_size + offset, size); - return row_buffer.data(); + [[nodiscard]] int physical_index(int row) const { + return (head + row) % row_count; } - int tick(int row) const { - int value; - std::memcpy(&value, snapshot_buffer.data() + row * record_byte_size + row_byte_size, sizeof(value)); - return value; + [[nodiscard]] std::span row(int row) const { + const int slot = physical_index(row); + return std::span(values.data() + static_cast(slot) * col_count, static_cast(col_count)); + } + [[nodiscard]] const double* frequency_data(int row, int, int) const { + return this->row(row).data(); + } + [[nodiscard]] int tick(int row) const { + return ticks[static_cast(physical_index(row))]; + } + [[nodiscard]] std::uint64_t sequence(int row) const { + return sequences[static_cast(physical_index(row))]; } }; -struct Waterfall_Private : Typed_Render_Data { - Waterfall_Ring_Buffer ring_buffer; + +struct Waterfall_Raster_Cache_Key { + int image_width{}; + int image_height{}; + int source_column_count{}; + Range source_frequency_range{}; + Range visible_frequency_range{}; + Range power_range{}; + QColor background_color{}; + std::uint64_t color_map_hash{}; + Waterfall_Frequency_Reduction frequency_reduction = Waterfall_Frequency_Reduction::Peak_Power; + Image_Interpolation_Mode interpolation_mode = Image_Interpolation_Mode::Nearest; + + bool operator==(const Waterfall_Raster_Cache_Key&) const = default; +}; + +struct Waterfall_Raster_Cache { Raster_Image_Buffer image; + Waterfall_Raster_Cache_Key key{}; + std::pmr::vector image_bucket_ids{memory_resource(Memory_Domain::Waterfall)}; + std::uint64_t mapped_generation{}; + std::uint64_t full_rebuild_count{}; + std::uint64_t incremental_row_count{}; + Range draw_frequency_range{}; + double draw_time_origin{}; + double draw_time_length{}; + int first_source_row{}; + int draw_row_count{}; + bool valid{}; + bool draw_visible{}; + + void invalidate() { + valid = false; + draw_visible = false; + mapped_generation = 0; + } +}; + +struct Waterfall_Private : Typed_Render_Data { + Waterfall_Row_Matrix ring_buffer; + Waterfall_Raster_Cache raster_cache; std::weak_ptr color_bar; - std::pmr::vector pending_image_rows{memory_resource(Memory_Domain::Waterfall)}; + std::shared_ptr frame_timeline; + std::optional deferred_latest; std::uint64_t last_line_commit_ns{}; - std::uint64_t image_color_map_hash{}; - Range m_image_frequency_range{}; - Range image_power_range{}; - Range image_visible_range{}; - QColor 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{}; - QRectF image_draw_source_rect{}; - bool image_valid{}; - bool image_draw_visible{}; - bool select_test(const QPointF& pos) override { + std::uint64_t deferred_deadline_ns{}; + + bool select_test(const QPointF&) override { return true; } void set_hover_state(const QPoint& pos, bool active) override { @@ -148,182 +188,299 @@ struct Waterfall_Private : Typed_Render_Datahover_info_pos = pos; edit->hover_info_active = active; } - bool push_row(Waterfall_Input_Data* data, std::span frequency_data, int tick, int col_count) { - if (frequency_data.size() != col_count) { + bool push_row(std::span frequency_data, int tick, int col_count) { + if (static_cast(frequency_data.size()) != col_count) return false; - } - ring_buffer.push_data(frequency_data.data(), tick); + ring_buffer.push_data(frequency_data, tick); return true; } - bool push_owned_row(Waterfall_Input_Data* data, std::pmr::vector& frequency_data, int tick, int col_count, Waterfall_Row_Update_Policy update_policy) { - if (frequency_data.size() != col_count) { - return false; + bool push_owned_row(Waterfall_Data& row, int col_count) { + return push_row(row.frequency, row.tick, col_count); + } + void schedule_deferred_commit(std::uint64_t deadline_ns, std::uint64_t now_ns) { + if (deferred_deadline_ns == deadline_ns) + return; + deferred_deadline_ns = deadline_ns; + const std::uint64_t remaining_ns = deadline_ns > now_ns ? deadline_ns - now_ns : 1; + const auto delay_ms = std::chrono::milliseconds((remaining_ns + 999999ull) / 1000000ull); + request_render_after(delay_ms); + } + void consume_deferred(const Waterfall_Render_State& state, int col_count, std::uint64_t now_ns) { + if (!deferred_latest) + return; + if (static_cast(deferred_latest->frequency.size()) != col_count) { + deferred_latest.reset(); + deferred_deadline_ns = 0; + return; } - ring_buffer.push_data(frequency_data.data(), tick); - if (update_policy != Waterfall_Row_Update_Policy::All_Pending) { - Waterfall_Data row; - row.tick = tick; - row.frequency.swap(frequency_data); - pending_image_rows.push_back(std::move(row)); + if (state.row_update_policy != Waterfall_Row_Update_Policy::Rate_Limited_Latest) { + push_owned_row(*deferred_latest, col_count); + deferred_latest.reset(); + deferred_deadline_ns = 0; + return; } - return true; + const std::uint64_t interval_ns = static_cast(std::max(1, state.latest_row_min_interval_ms)) * 1000000ull; + const std::uint64_t deadline_ns = last_line_commit_ns == 0 ? now_ns : last_line_commit_ns + interval_ns; + if (now_ns >= deadline_ns) { + if (push_owned_row(*deferred_latest, col_count)) + last_line_commit_ns = now_ns; + deferred_latest.reset(); + deferred_deadline_ns = 0; + return; + } + schedule_deferred_commit(deadline_ns, now_ns); } void prepare_data(const Plot_Render_Snapshot& snapshot) override { sync_state_pipeline(); - Waterfall_Input_Data* data = render_input_data(); - const Waterfall_Render_State* s = render_state(); - auto time_axis = s->time_axis.lock(); + Waterfall_Input_Data* input = render_input_data(); + const Waterfall_Render_State* state = render_state(); + auto time_axis = state->time_axis.lock(); if (!time_axis) return; - auto timeline = snapshot.timeline_snapshot(time_axis->timeline_stream()); - if (!timeline) + frame_timeline = snapshot.timeline_snapshot(time_axis->timeline_stream()); + if (!frame_timeline) return; - int col_count = s->frequency_point_size, row_count = timeline->time_point_size; + const int col_count = state->frequency_point_size; + const int row_count = frame_timeline->time_point_size; if (col_count <= 0 || row_count <= 0) return; if (!ring_buffer.match(col_count, row_count)) { ring_buffer.resize(col_count, row_count); + raster_cache.invalidate(); + deferred_latest.reset(); + deferred_deadline_ns = 0; + last_line_commit_ns = 0; } - switch (s->row_update_policy) { + + const std::uint64_t now_ns = steady_now_ns(); + consume_deferred(*state, col_count, now_ns); + switch (state->row_update_policy) { case Waterfall_Row_Update_Policy::All_Pending: - data->read_all([this, data, col_count](int tick, const auto& frequency_data) { - push_row(data, frequency_data, tick, col_count); + input->read_all([this, col_count](int tick, const std::pmr::vector& frequency) { + push_row(frequency, tick, col_count); }); break; - case Waterfall_Row_Update_Policy::Latest_Only: - case Waterfall_Row_Update_Policy::Rate_Limited_Latest: { - Waterfall_Data latest{}; - bool has_latest = false; - data->read_all([col_count, &latest, &has_latest](int tick, std::pmr::vector& frequency_data) { - if (frequency_data.size() != col_count) { + case Waterfall_Row_Update_Policy::Latest_Only: { + std::optional latest; + input->read_all([col_count, &latest](int tick, std::pmr::vector& frequency) { + if (static_cast(frequency.size()) != col_count) return; - } - latest.tick = tick; - latest.frequency.swap(frequency_data); - has_latest = true; + latest.emplace(); + latest->tick = tick; + latest->frequency.swap(frequency); }); - if (has_latest) { - bool allow_commit = true; - std::uint64_t now_ns = steady_now_ns(); - if (s->row_update_policy == Waterfall_Row_Update_Policy::Rate_Limited_Latest && last_line_commit_ns != 0) { - std::uint64_t interval_ns = static_cast(qMax(1, s->latest_row_min_interval_ms)) * 1000000ull; - allow_commit = now_ns - last_line_commit_ns >= interval_ns; - } - if (allow_commit) { - if (push_owned_row(data, latest.frequency, latest.tick, col_count, s->row_update_policy)) { - last_line_commit_ns = now_ns; - } - } - } + if (latest) + push_owned_row(*latest, col_count); break; } + case Waterfall_Row_Update_Policy::Rate_Limited_Latest: + input->read_all([col_count, this](int tick, std::pmr::vector& frequency) { + if (static_cast(frequency.size()) != col_count) + return; + deferred_latest.emplace(); + deferred_latest->tick = tick; + deferred_latest->frequency.swap(frequency); + }); + consume_deferred(*state, col_count, steady_now_ns()); + break; } clear_render_input(); - update_image_item(snapshot, *timeline); } - bool write_image_row(int tick, const double* row_data, int first_col, int last_col, int start_tick, int row_count, double value_start_coord, double rate, const Color_Map& color_map, const QColor& background_color) { - int tick_offset = tick - start_tick; - if (tick_offset < 0 || tick_offset >= row_count) - return false; - image.write_color_mapped_row(tick_offset, row_data, first_col, last_col, value_start_coord, rate, color_map, background_color); - return true; + + static int positive_modulo(int value, int modulus) { + const int result = value % modulus; + return result < 0 ? result + modulus : result; } - bool image_cache_match(const Waterfall_Render_State* s, int start_tick, int first_col, int last_col, const Range& h_range, const QColor& 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 == s->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; + static std::int64_t floor_divide(std::int64_t numerator, std::int64_t denominator) { + std::int64_t quotient = numerator / denominator; + if (numerator % denominator < 0) + --quotient; + return quotient; } - 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(); - auto time_axis = s->time_axis.lock(); - if (!frequency_axis || !time_axis) - return; - Abs_Axis* h_axis = frequency_axis.get(); - Range h_range = s->frequency_range; - Range v_range{static_cast(timeline.lower), static_cast(timeline.lower + timeline.time_point_size)}; - if (s->visible_range_only && !intersect_range(s->frequency_range, h_axis->d()->render_coord_range(), h_range)) { - image_draw_visible = false; + static std::int64_t time_bucket_id(int tick, int image_height, int timeline_row_count) { + return floor_divide(static_cast(tick) * image_height, timeline_row_count); + } + static int bucket_row(std::int64_t bucket_id, int image_height) { + const std::int64_t result = bucket_id % image_height; + return static_cast(result < 0 ? result + image_height : result); + } + static double mean_linear_power(std::span values, int first, int last) { + double sum{}; + int count{}; + for (int i = first; i <= last; ++i) { + if (!std::isfinite(values[static_cast(i)])) + continue; + sum += std::pow(10.0, values[static_cast(i)] / 10.0); + ++count; + } + return count && sum > 0.0 ? 10.0 * std::log10(sum / static_cast(count)) : std::numeric_limits::quiet_NaN(); + } + static double peak_power(std::span values, int first, int last) { + double peak = -std::numeric_limits::infinity(); + for (int i = first; i <= last; ++i) { + const double value = values[static_cast(i)]; + if (std::isfinite(value)) + peak = std::max(peak, value); + } + return std::isfinite(peak) ? peak : std::numeric_limits::quiet_NaN(); + } + static double interpolated_sample(std::span values, double source_position, Image_Interpolation_Mode mode) { + const int count = static_cast(values.size()); + if (count <= 0) + return std::numeric_limits::quiet_NaN(); + const double sample_position = std::clamp(source_position - 0.5, 0.0, static_cast(count - 1)); + const int first = static_cast(std::floor(sample_position)); + if (mode == Image_Interpolation_Mode::Nearest || first == count - 1) + return values[static_cast(static_cast(std::round(sample_position)))]; + const int second = first + 1; + const double fraction = sample_position - first; + return values[static_cast(first)] * (1.0 - fraction) + values[static_cast(second)] * fraction; + } + static double reduce_frequency_bucket(std::span values, double source_begin, double source_end, const Waterfall_Render_State& state) { + const double bucket_min = std::min(source_begin, source_end); + const double bucket_max = std::max(source_begin, source_end); + if (bucket_max - bucket_min <= 1.0) + return interpolated_sample(values, (source_begin + source_end) * 0.5, state.interpolation_mode); + const int first = std::clamp(static_cast(std::floor(bucket_min)), 0, static_cast(values.size()) - 1); + const int last = std::clamp(static_cast(std::ceil(bucket_max)) - 1, first, static_cast(values.size()) - 1); + switch (state.frequency_reduction) { + case Waterfall_Frequency_Reduction::Peak_Power: + return peak_power(values, first, last); + case Waterfall_Frequency_Reduction::Mean_Linear_Power: + return mean_linear_power(values, first, last); + } + return std::numeric_limits::quiet_NaN(); + } + void clear_scanline(int row, const QColor& color) { + QRgb* output = raster_cache.image.scan_line(row); + std::fill(output, output + raster_cache.image.width(), qPremultiply(color.rgba())); + } + void map_row(int tick, int timeline_row_count, std::span values, const Waterfall_Render_State& state, double source_start, double source_end) { + const std::int64_t bucket_id = time_bucket_id(tick, raster_cache.image.height(), timeline_row_count); + const int image_row = bucket_row(bucket_id, raster_cache.image.height()); + QRgb* output = raster_cache.image.scan_line(image_row); + const int width = raster_cache.image.width(); + const double source_span = source_end - source_start; + const double color_rate = static_cast(state.color_map.size()) / state.power_range.size(); + for (int x = 0; x < width; ++x) { + const double begin = source_start + source_span * static_cast(x) / width; + const double end = source_start + source_span * static_cast(x + 1) / width; + const double value = reduce_frequency_bucket(values, begin, end, state); + output[x] = std::isfinite(value) + ? state.color_map.at_offset(static_cast((value - state.power_range.origin) * color_rate)) + : qPremultiply(raster_cache.key.background_color.rgba()); + } + raster_cache.image_bucket_ids[static_cast(image_row)] = bucket_id; + } + void update_raster_cache(const Plot_Render_Snapshot& snapshot, const Timeline_Stream_Snapshot& timeline, const Waterfall_Render_State& state, const Axis_Transform_Snapshot& h_transform, const Axis_Transform_Snapshot& v_transform) { + Range visible_range = state.frequency_range; + if (state.visible_range_only && !intersect_range(state.frequency_range, h_transform.coordinate_range, visible_range)) { + raster_cache.draw_visible = false; return; } - int col_count = s->frequency_point_size, row_count = timeline.time_point_size; - if (col_count <= 0 || row_count <= 0 || s->frequency_range.length() == 0.0 || s->power_range.size() == 0.0) + const int row_count = timeline.time_point_size; + if (ring_buffer.col_count <= 0 || row_count <= 0 || state.frequency_range.length() == 0.0 || state.power_range.size() == 0.0) { + raster_cache.draw_visible = false; return; - bool image_size_changed = image.width() != col_count || image.height() != row_count; - if (image_size_changed) - image.resize(col_count, row_count); - double source_start = static_cast(col_count) * (h_range.origin - s->frequency_range.origin) / s->frequency_range.length(); - double source_end = static_cast(col_count) * (h_range.target - s->frequency_range.origin) / s->frequency_range.length(); - double source_min = std::min(source_start, source_end); - double source_max = std::max(source_start, source_end); - int first_col = std::clamp(static_cast(std::floor(source_min)), 0, col_count - 1); - int last_col = std::clamp(static_cast(std::ceil(source_max)) - 1, first_col, col_count - 1); - double value_start_coord = s->power_range.origin; - double rate = static_cast(s->color_map.size()) / s->power_range.size(); - int buffer_data_count = ring_buffer.snapshot(); - int start_tick = timeline.lower; - std::uint64_t color_map_hash = s->color_map.hash(); - bool full_rebuild = s->row_update_policy == Waterfall_Row_Update_Policy::All_Pending || image_size_changed || !image_cache_match(s, start_tick, first_col, last_col, h_range, snapshot.background_color, color_map_hash); + } + const double pixel_start = h_transform.to_pixel(visible_range.origin); + const double pixel_end = h_transform.to_pixel(visible_range.target); + const int image_width = std::clamp(static_cast(std::ceil(std::abs(pixel_end - pixel_start))), 1, std::max(1, snapshot.size.width())); + const double time_pixel_start = v_transform.to_pixel(static_cast(timeline.lower)); + const double time_pixel_end = v_transform.to_pixel(static_cast(timeline.lower + row_count)); + const int image_height = std::clamp(static_cast(std::ceil(std::abs(time_pixel_end - time_pixel_start))), 1, std::min(row_count, std::max(1, snapshot.size.height()))); + Waterfall_Raster_Cache_Key key; + key.image_width = image_width; + key.image_height = image_height; + key.source_column_count = ring_buffer.col_count; + key.source_frequency_range = state.frequency_range; + key.visible_frequency_range = visible_range; + key.power_range = state.power_range; + key.background_color = snapshot.background_color; + key.color_map_hash = state.color_map.hash(); + key.frequency_reduction = state.frequency_reduction; + key.interpolation_mode = state.interpolation_mode; + + const bool full_rebuild = !raster_cache.valid || !(raster_cache.key == key); if (full_rebuild) { - image.fill(snapshot.background_color); - for (int row = 0; row < buffer_data_count; ++row) { - int buffer_row = buffer_data_count - row - 1; - auto list = ring_buffer.frequency_data(buffer_row, first_col, last_col); - int tick = ring_buffer.tick(buffer_row); - write_image_row(tick, list, first_col, last_col, start_tick, row_count, value_start_coord, rate, s->color_map, snapshot.background_color); + raster_cache.image.resize(key.image_width, key.image_height); + raster_cache.image.fill(key.background_color); + raster_cache.image_bucket_ids.assign(static_cast(key.image_height), std::numeric_limits::min()); + raster_cache.key = key; + raster_cache.mapped_generation = 0; + ++raster_cache.full_rebuild_count; + } + + const std::int64_t first_bucket_id = time_bucket_id(timeline.lower, image_height, row_count); + for (int offset = 0; offset < image_height; ++offset) { + const std::int64_t bucket_id = first_bucket_id + offset; + const int image_row = bucket_row(bucket_id, image_height); + if (raster_cache.image_bucket_ids[static_cast(image_row)] != bucket_id) { + clear_scanline(image_row, key.background_color); + raster_cache.image_bucket_ids[static_cast(image_row)] = bucket_id; } } - else { - for (const Waterfall_Data& row : pending_image_rows) { - write_image_row(row.tick, row.frequency.data(), first_col, last_col, start_tick, row_count, value_start_coord, rate, s->color_map, snapshot.background_color); - } + + const double source_start = static_cast(ring_buffer.col_count) * (visible_range.origin - state.frequency_range.origin) / state.frequency_range.length(); + const double source_end = static_cast(ring_buffer.col_count) * (visible_range.target - state.frequency_range.origin) / state.frequency_range.length(); + for (int row = 0; row < ring_buffer.data_count; ++row) { + if (!full_rebuild && ring_buffer.sequence(row) <= raster_cache.mapped_generation) + continue; + const int tick = ring_buffer.tick(row); + if (tick < timeline.lower || tick >= timeline.lower + row_count) + continue; + map_row(tick, row_count, ring_buffer.row(row), state, source_start, source_end); + if (!full_rebuild) + ++raster_cache.incremental_row_count; } - pending_image_rows.clear(); - image_valid = true; - 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 = s->power_range; - image_visible_range = h_range; - image_background_color = snapshot.background_color; - image_update_policy = s->row_update_policy; - image_color_map_hash = color_map_hash; - QRectF source_rect(source_start, 0.0, source_end - source_start, static_cast(row_count)); - image_draw_frequency_range = h_range; - image_draw_time_range = v_range; - image_draw_source_rect = source_rect; - image_draw_visible = true; + raster_cache.mapped_generation = ring_buffer.generation; + raster_cache.draw_frequency_range = visible_range; + raster_cache.draw_time_origin = timeline.lower; + raster_cache.draw_time_length = row_count; + raster_cache.first_source_row = bucket_row(first_bucket_id, image_height); + raster_cache.draw_row_count = image_height; + raster_cache.valid = true; + raster_cache.draw_visible = true; } - void draw_image(QPainter* painter, Waterfall_Render_State* s, Abs_Axis* h_axis, Time_Axis* v_axis) { - if (!image_draw_visible || image.is_null()) + void draw_band(QPainter& painter, const Axis_Transform_Snapshot& h_transform, const Axis_Transform_Snapshot& v_transform, double logical_start, double logical_length, int source_row, int count) { + if (count <= 0) return; - if (image_draw_frequency_range.length() == 0.0 || image_draw_time_range.length() == 0.0) - return; - double x = h_axis->d()->render_coord_to_pixel(image_draw_frequency_range.origin); - double y = v_axis->d()->render_coord_to_pixel(image_draw_time_range.origin); - double w = h_axis->d()->render_coord_to_pixel(image_draw_frequency_range.target) - x; - double h = v_axis->d()->render_coord_to_pixel(image_draw_time_range.target) - y; - Range x_axis_range = h_axis->d()->render_coord_range(); - Range y_axis_range = v_axis->d()->render_coord_range(); - double hr = ((x_axis_range.origin < x_axis_range.target) ^ (image_draw_frequency_range.origin < image_draw_frequency_range.target)) ? -1.0 : 1.0; - double vr = ((y_axis_range.origin < y_axis_range.target) ^ (image_draw_time_range.origin < image_draw_time_range.target)) ? -1.0 : 1.0; - painter->save(); - painter->scale(hr, vr); - image.draw(painter, QRectF(hr * x, vr * y, hr * w, vr * h), image_draw_source_rect, s->interpolation_mode); - painter->restore(); + const double x = h_transform.to_pixel(raster_cache.draw_frequency_range.origin); + const double y = v_transform.to_pixel(logical_start); + const double width = h_transform.to_pixel(raster_cache.draw_frequency_range.target) - x; + const double height = v_transform.to_pixel(logical_start + logical_length) - y; + const double horizontal_sign = width < 0.0 ? -1.0 : 1.0; + const double vertical_sign = height < 0.0 ? -1.0 : 1.0; + painter.save(); + painter.scale(horizontal_sign, vertical_sign); + raster_cache.image.draw(&painter, + QRectF(horizontal_sign * x, vertical_sign * y, horizontal_sign * width, vertical_sign * height), + QRectF(0.0, static_cast(source_row), static_cast(raster_cache.image.width()), static_cast(count)), + render_state()->interpolation_mode); + painter.restore(); } - void draw(QPainter* painter, const Plot_Render_Snapshot& snapshot) override { - Q_UNUSED(snapshot) - Waterfall_Render_State* s = render_state(); - auto frequency_axis = s->frequency_axis.lock(); - auto time_axis = s->time_axis.lock(); - if (!frequency_axis || !time_axis) + void draw_image(QPainter& painter, const Axis_Transform_Snapshot& h_transform, const Axis_Transform_Snapshot& v_transform) { + if (!raster_cache.draw_visible || raster_cache.image.is_null() || raster_cache.draw_row_count <= 0) return; - Abs_Axis* h_axis = frequency_axis.get(); - Time_Axis* v_axis = time_axis.get(); - draw_image(painter, s, h_axis, v_axis); - if (q()->hover_ok(q())) { - q()->draw_hover(painter, h_axis, v_axis); - } + const int first_count = std::min(raster_cache.draw_row_count, raster_cache.draw_row_count - raster_cache.first_source_row); + const double coordinate_per_row = raster_cache.draw_time_length / raster_cache.draw_row_count; + draw_band(painter, h_transform, v_transform, raster_cache.draw_time_origin, first_count * coordinate_per_row, raster_cache.first_source_row, first_count); + draw_band(painter, h_transform, v_transform, raster_cache.draw_time_origin + first_count * coordinate_per_row, + (raster_cache.draw_row_count - first_count) * coordinate_per_row, 0, raster_cache.draw_row_count - first_count); + } + void rasterize(Frame_Raster_Context& context, const Plot_Render_Snapshot& snapshot) override { + Waterfall_Render_State* state = render_state(); + auto frequency_axis = state->frequency_axis.lock(); + auto time_axis = state->time_axis.lock(); + if (!frequency_axis || !time_axis || !frame_timeline) + return; + const Axis_Transform_Snapshot h_transform = render_axis_transform(*frequency_axis); + const Axis_Transform_Snapshot v_transform = render_axis_transform(*time_axis); + update_raster_cache(snapshot, *frame_timeline, *state, h_transform, v_transform); + QPainter& painter = context.qt(); + draw_image(painter, h_transform, v_transform); + if (q()->hover_ok(q())) + q()->draw_hover(&painter, frequency_axis.get(), time_axis.get()); } }; } // namespace renderive diff --git a/Renderive/plottable/export.h b/Renderive/plottable/export.h index a584acc..0532039 100644 --- a/Renderive/plottable/export.h +++ b/Renderive/plottable/export.h @@ -2,7 +2,7 @@ #include "Afterglow.h" #include "Audio_Frequency.h" #include "Hover_Info.h" -#include "Interpolation.h" +#include "../base/Interpolation.h" #include "Multi_Select_Rect.h" #include "Planisphere.h" #include "Spectrum.h" diff --git a/Renderive/primitive/Band_Region.cpp b/Renderive/primitive/Band_Region.cpp index 7dee6a7..89317fb 100644 --- a/Renderive/primitive/Band_Region.cpp +++ b/Renderive/primitive/Band_Region.cpp @@ -4,29 +4,32 @@ namespace renderive { struct Band_Region_Render_State : Render_State, Band_Region_Prop {}; struct Band_Region_Private : Typed_Render_Data { - void draw(QPainter* painter, const Plot_Render_Snapshot&) override { + void rasterize(Frame_Raster_Context& context, const Plot_Render_Snapshot&) override { + QPainter* painter = &context.qt(); Band_Region_Render_State* s = render_state(); auto x_axis = s->x_axis.lock(); auto y_axis = s->y_axis.lock(); if (!x_axis || !y_axis || s->range.length() == 0.0) return; - Range x_range = x_axis->d()->render_coord_range(); - Range y_range = y_axis->d()->render_coord_range(); + const auto x_transform = render_axis_transform(*x_axis); + const auto y_transform = render_axis_transform(*y_axis); + Range x_range = x_transform.coordinate_range; + Range y_range = y_transform.coordinate_range; double x0{}; double x1{}; double y0{}; double y1{}; if (s->orientation == Band_Region_Orientation::X) { - x0 = x_axis->d()->render_coord_to_pixel(s->range.origin); - x1 = x_axis->d()->render_coord_to_pixel(s->range.target); - y0 = y_axis->d()->render_coord_to_pixel(y_range.origin); - y1 = y_axis->d()->render_coord_to_pixel(y_range.target); + x0 = x_transform.to_pixel(s->range.origin); + x1 = x_transform.to_pixel(s->range.target); + y0 = y_transform.to_pixel(y_range.origin); + y1 = y_transform.to_pixel(y_range.target); } else { - x0 = x_axis->d()->render_coord_to_pixel(x_range.origin); - x1 = x_axis->d()->render_coord_to_pixel(x_range.target); - y0 = y_axis->d()->render_coord_to_pixel(s->range.origin); - y1 = y_axis->d()->render_coord_to_pixel(s->range.target); + x0 = x_transform.to_pixel(x_range.origin); + x1 = x_transform.to_pixel(x_range.target); + y0 = y_transform.to_pixel(s->range.origin); + y1 = y_transform.to_pixel(s->range.target); } painter->save(); painter->setPen(s->pen); @@ -52,9 +55,9 @@ RENDERIVE_DEFINE_STATE_PROP(Band_Region, Band_Region_Render_State, QPen, pen) Band_Region::Builder::Builder(std::shared_ptr parent, std::shared_ptr x_axis, std::shared_ptr y_axis) { ASSERT(parent, "Band_Region build error! parent == nullptr"); ASSERT(x_axis && y_axis, "Band_Region build error! axis == nullptr"); - ASSERT(x_axis->plot != nullptr, "Band_Region build error! x_axis->plot == nullptr"); - ASSERT(x_axis->plot == y_axis->plot, "Band_Region build error! x_axis->plot != y_axis->plot"); - ASSERT(parent->plot == x_axis->plot, "Band_Region build error! parent->plot != axis->plot"); + ASSERT(x_axis->is_attached(), "Band_Region build error! x_axis is detached"); + ASSERT(x_axis->shares_plot_with(*y_axis), "Band_Region build error! axes belong to different plots"); + ASSERT(parent->shares_plot_with(*x_axis), "Band_Region build error! parent and axes belong to different plots"); this->parent = parent; this->x_axis = x_axis; this->y_axis = y_axis; @@ -63,7 +66,7 @@ std::shared_ptr Band_Region::Builder::build() { auto ret = renderive::make_shared(); ret->init(parent); Band_Region_Private* pd = ret->d(); - Band_Region_Render_State* sc = reinterpret_cast(pd->state(State_Edit)); + Band_Region_Render_State* sc = reinterpret_cast(pd->state(Data_Buffer_Role::Edit)); PROP_R(std::weak_ptr, x_axis) PROP_R(std::weak_ptr, y_axis) PROP_R(Range, range) diff --git a/Renderive/primitive/Color_Bar.cpp b/Renderive/primitive/Color_Bar.cpp index 0d5f1ef..85f7105 100644 --- a/Renderive/primitive/Color_Bar.cpp +++ b/Renderive/primitive/Color_Bar.cpp @@ -36,7 +36,8 @@ struct Color_Bar_Private : Typed_Render_Datarange.length() == 0.0) return; @@ -89,14 +90,14 @@ RENDERIVE_DEFINE_STATE_PROP(Color_Bar, Color_Bar_Render_State, QFont, font) RENDERIVE_DEFINE_STATE_PROP(Color_Bar, Color_Bar_Render_State, QPen, text_pen) RENDERIVE_DEFINE_STATE_PROP(Color_Bar, Color_Bar_Render_State, QPen, border_pen) Color_Bar::Builder::Builder(std::shared_ptr parent) { - ASSERT(parent && parent->plot, "Color_Bar build error! parent invalid"); + ASSERT(parent && parent->is_attached(), "Color_Bar build error! parent invalid"); this->parent = parent; } std::shared_ptr Color_Bar::Builder::build() { auto ret = renderive::make_shared(); ret->init(parent); Color_Bar_Private* pd = ret->d(); - Color_Bar_Render_State* sc = reinterpret_cast(pd->state(State_Edit)); + Color_Bar_Render_State* sc = reinterpret_cast(pd->state(Data_Buffer_Role::Edit)); PROP_R(Color_Map, color_map) PROP_R(Range, range) PROP_R(QString, name) diff --git a/Renderive/primitive/Curve.cpp b/Renderive/primitive/Curve.cpp index 6928728..ad47dbf 100644 --- a/Renderive/primitive/Curve.cpp +++ b/Renderive/primitive/Curve.cpp @@ -1,4 +1,5 @@ #include "Curve.h" +#include "Curve_Utils_p.h" #include "../base/Memory.h" namespace renderive { enum class Curve_Input_Mode : std::uint8_t { @@ -62,7 +63,7 @@ struct Curve_Private : Typed_Render_Datax_axis.lock(); auto y_axis = s->y_axis.lock(); @@ -72,33 +73,36 @@ struct Curve_Private : Typed_Render_Datadata_range.length() == 0.0) return; - if (s->sample_mode == Line_Sampling_Mode::Pixel_Peak_Envelope) { + if (s->sample_mode == Line_Sampling_Mode::Preserve_Extrema) { points = Curve_Utils::create_peak_bucket_points(x_axis.get(), y_axis.get(), s->data_range, samples.size(), samples.size(), s->visible_range_only, [this](int i) { return samples.at(i); }); } else { - points = Curve_Utils::create_sampled_points(x_axis.get(), y_axis.get(), s->data_range, samples.size(), samples.size(), s->visible_range_only, s->interpolation_mode, [this](int i) { + points = Curve_Utils::create_sampled_points(x_axis.get(), y_axis.get(), s->data_range, samples.size(), samples.size(), s->visible_range_only, s->interpolation_mode, Interpolation_Value_Domain::Display, [this](int i) { return samples.at(i); }); } } else if (mode == Curve_Input_Mode::Points) { - points = Curve_Utils::create_points(data_points.size(), [this, x_axis, y_axis](int i) { - const QPointF& point = data_points.at(i); - return QPointF(x_axis->d()->render_coord_to_pixel(point.x()), y_axis->d()->render_coord_to_pixel(point.y())); - }); + const auto x_transform = render_axis_transform(*x_axis); + const auto y_transform = render_axis_transform(*y_axis); + points = Curve_Utils::create_explicit_points(data_points, x_transform, y_transform, s->visible_range_only, s->sample_mode); + } + Curve_Utils::draw_polyline(context, points, s->pen); + const auto y_transform = render_axis_transform(*y_axis); + if (s->sample_mode == Line_Sampling_Mode::Preserve_Extrema) { + const auto fill_points = Curve_Utils::representative_fill_points(points); + Curve_Utils::draw_fill_to_y(context, fill_points, y_transform.to_pixel(y_transform.coordinate_range.target), s->fill_brush); + } + else { + Curve_Utils::draw_fill_to_y(context, points, y_transform.to_pixel(y_transform.coordinate_range.target), s->fill_brush); } - painter->save(); - painter->setRenderHint(QPainter::Antialiasing, s->antialias); - Curve_Utils::draw_polyline(painter, points, s->pen); - Curve_Utils::draw_fill_to_y(painter, points, y_axis->d()->render_coord_to_pixel(y_axis->d()->render_coord_range().target), s->fill_brush); - painter->restore(); } std::size_t estimated_frame_memory(const Plot_Render_Snapshot& snapshot) override { Curve_Render_State* s = render_state(); std::size_t count = mode == Curve_Input_Mode::Points ? data_points.size() : samples.size(); - if (mode == Curve_Input_Mode::Samples && s->sample_mode == Line_Sampling_Mode::Pixel_Peak_Envelope) + if (mode == Curve_Input_Mode::Samples && s->sample_mode == Line_Sampling_Mode::Preserve_Extrema) count = std::min(count, static_cast(snapshot.size.width() * 4 + 4)); return count * sizeof(QPointF) + 16 * 1024; } @@ -108,7 +112,7 @@ void Curve::give_samples(std::span values, bool notify_render) { if (notify_render && !ok()) return; auto context = plot_context.lock(); - if (!notify_render && (get_retiring() || !d_ptr || !context || context->destroying.load(std::memory_order_acquire))) + if (!notify_render && (get_retiring() || !render_data() || !context || context->destroying.load(std::memory_order_acquire))) return; Render_Input_Lease input(d(), notify_render); input->push_samples(values); @@ -117,7 +121,7 @@ void Curve::give_samples(std::pmr::vector&& values, bool notify_render) if (notify_render && !ok()) return; auto context = plot_context.lock(); - if (!notify_render && (get_retiring() || !d_ptr || !context || context->destroying.load(std::memory_order_acquire))) + if (!notify_render && (get_retiring() || !render_data() || !context || context->destroying.load(std::memory_order_acquire))) return; Render_Input_Lease input(d(), notify_render); input->push_samples(std::move(values)); @@ -154,7 +158,7 @@ void Curve::give_points(std::span points, bool notify_render) { if (notify_render && !ok()) return; auto context = plot_context.lock(); - if (!notify_render && (get_retiring() || !d_ptr || !context || context->destroying.load(std::memory_order_acquire))) + if (!notify_render && (get_retiring() || !render_data() || !context || context->destroying.load(std::memory_order_acquire))) return; Render_Input_Lease input(d(), notify_render); input->push_points(points); @@ -163,7 +167,7 @@ void Curve::give_points(std::pmr::vector&& points, bool notify_render) if (notify_render && !ok()) return; auto context = plot_context.lock(); - if (!notify_render && (get_retiring() || !d_ptr || !context || context->destroying.load(std::memory_order_acquire))) + if (!notify_render && (get_retiring() || !render_data() || !context || context->destroying.load(std::memory_order_acquire))) return; Render_Input_Lease input(d(), notify_render); input->push_points(std::move(points)); @@ -209,15 +213,14 @@ void Curve::set_data_range(Range value) { RENDERIVE_DEFINE_STATE_PROP(Curve, Curve_Render_State, QPen, pen) RENDERIVE_DEFINE_STATE_PROP(Curve, Curve_Render_State, QBrush, 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_Interpolation, 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 x_axis, std::shared_ptr y_axis) { ASSERT(parent, "Curve build error! parent == nullptr"); ASSERT(x_axis && y_axis, "Curve build error! axis == nullptr"); - ASSERT(x_axis->plot != nullptr, "Curve build error! x_axis->plot == nullptr"); - ASSERT(x_axis->plot == y_axis->plot, "Curve build error! x_axis->plot != y_axis->plot"); - ASSERT(parent->plot == x_axis->plot, "Curve build error! parent->plot != axis->plot"); + ASSERT(x_axis->is_attached(), "Curve build error! x_axis is detached"); + ASSERT(x_axis->shares_plot_with(*y_axis), "Curve build error! axes belong to different plots"); + ASSERT(parent->shares_plot_with(*x_axis), "Curve build error! parent and axes belong to different plots"); this->parent = parent; this->x_axis = x_axis; this->y_axis = y_axis; @@ -226,41 +229,252 @@ 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)); + Curve_Render_State* sc = reinterpret_cast(pd->state(Data_Buffer_Role::Edit)); PROP_R(std::weak_ptr, x_axis) PROP_R(std::weak_ptr, y_axis) PROP_R(Range, data_range) PROP_R(QPen, pen) PROP_R(QBrush, fill_brush) PROP_R(bool, visible_range_only) - PROP_R(bool, antialias) - PROP_R(Line_Interpolation_Mode, interpolation_mode) + PROP_R(Line_Interpolation, 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(QPainter* painter, std::span points, const QPen& pen) { +void Curve_Utils::draw_polyline(Frame_Raster_Context& context, std::span points, const QPen& pen) { if (points.empty() || pen.style() == Qt::NoPen) return; - painter->setPen(pen); - painter->drawPolyline(points.data(), points.size()); + context.stroke_polyline(points, pen); } -void Curve_Utils::draw_fill_to_y(QPainter* painter, std::span points, double baseline_y, const QBrush& brush) { +void Curve_Utils::draw_fill_to_y(Frame_Raster_Context& context, std::span points, double baseline_y, const QBrush& brush) { if (points.empty() || brush.style() == Qt::NoBrush) return; - std::pmr::vector fill_points(points.size() + 2, frame_memory_resource()); - fill_points.front() = QPointF(points.front().x(), baseline_y); - fill_points.back() = QPointF(points.back().x(), baseline_y); - for (int i = 0; i < points.size(); ++i) - fill_points[i + 1] = points[i]; - painter->setBrush(brush); - painter->drawPolygon(fill_points.data(), fill_points.size()); + std::size_t segment_begin = 0; + while (segment_begin < points.size()) { + while (segment_begin < points.size() && (!std::isfinite(points[segment_begin].x()) || !std::isfinite(points[segment_begin].y()))) + ++segment_begin; + std::size_t segment_end = segment_begin; + while (segment_end < points.size() && std::isfinite(points[segment_end].x()) && std::isfinite(points[segment_end].y())) + ++segment_end; + if (segment_end - segment_begin >= 2) { + std::pmr::vector fill_points(segment_end - segment_begin + 2, frame_memory_resource()); + fill_points.front() = QPointF(points[segment_begin].x(), baseline_y); + fill_points.back() = QPointF(points[segment_end - 1].x(), baseline_y); + for (std::size_t i = segment_begin; i < segment_end; ++i) + fill_points[i - segment_begin + 1] = points[i]; + context.fill_polygon(fill_points, brush); + } + segment_begin = segment_end + 1; + } } -void Curve_Utils::append_peak_point(std::pmr::vector& points, Abs_Axis* x_axis, Abs_Axis* y_axis, const Range& data_range, int total_count, int index, double value) { +std::pmr::vector Curve_Utils::representative_fill_points(std::span line_points) { + std::pmr::vector result(frame_memory_resource()); + result.reserve(line_points.size()); + int previous_bucket = std::numeric_limits::min(); + for (const QPointF& point : line_points) { + if (!std::isfinite(point.x()) || !std::isfinite(point.y())) { + result.push_back(point); + previous_bucket = std::numeric_limits::min(); + continue; + } + const int bucket = static_cast(std::floor(point.x())); + if (!result.empty() && bucket == previous_bucket) + result.back() = point; + else + result.push_back(point); + previous_bucket = bucket; + } + return result; +} +namespace { +constexpr double curve_nan = std::numeric_limits::quiet_NaN(); + +bool finite_point(const QPointF& point) { + return std::isfinite(point.x()) && std::isfinite(point.y()); +} + +void append_path_break(std::pmr::vector& output) { + if (!output.empty() && finite_point(output.back())) + output.emplace_back(curve_nan, curve_nan); +} + +void append_unique(std::pmr::vector& output, const QPointF& point) { + if (output.empty() || output.back() != point) + output.push_back(point); +} + +int monotonic_direction(std::span source) { + int direction = 0; + for (std::size_t i = 0; i < source.size(); ++i) { + if (!finite_point(source[i])) + return 0; + if (i == 0 || source[i].x() == source[i - 1].x()) + continue; + const int current = source[i].x() > source[i - 1].x() ? 1 : -1; + if (direction && current != direction) + return 0; + direction = current; + } + return direction; +} + +std::pair monotonic_visible_window(std::span source, double minimum, double maximum, int direction) { + auto less_x = [direction](const QPointF& point, double value) { + return direction > 0 ? point.x() < value : point.x() > value; + }; + auto less_value = [direction](double value, const QPointF& point) { + return direction > 0 ? value < point.x() : value > point.x(); + }; + const double first_value = direction > 0 ? minimum : maximum; + const double last_value = direction > 0 ? maximum : minimum; + auto first = std::lower_bound(source.begin(), source.end(), first_value, less_x); + auto last = std::upper_bound(source.begin(), source.end(), last_value, less_value); + if (first != source.begin()) + --first; + if (last != source.end()) + ++last; + return {static_cast(first - source.begin()), static_cast(last - source.begin())}; +} + +std::pmr::vector transform_monotonic_points(std::span source, + std::size_t begin, + std::size_t end, + const Axis_Transform_Snapshot& x_transform, + const Axis_Transform_Snapshot& y_transform, + Line_Sampling_Mode sampling_mode) { + std::pmr::vector output(frame_memory_resource()); + if (end <= begin) + return output; + if (sampling_mode == Line_Sampling_Mode::Exact || end - begin <= static_cast(x_transform.pixel_size() + 1)) { + output.reserve(end - begin); + for (std::size_t i = begin; i < end; ++i) + output.emplace_back(x_transform.to_pixel(source[i].x()), y_transform.to_pixel(source[i].y())); + return output; + } + + const double first_pixel = x_transform.to_pixel(source[begin].x()); + const double last_pixel = x_transform.to_pixel(source[end - 1].x()); + const double pixel_min = std::min(first_pixel, last_pixel); + const double pixel_max = std::max(first_pixel, last_pixel); + const int bucket_count = std::max(1, std::min(x_transform.pixel_size() + 1, static_cast(std::ceil(pixel_max - pixel_min)) + 1)); + std::pmr::vector buckets(static_cast(bucket_count), frame_memory_resource()); + const double pixel_span = std::max(1.0, pixel_max - pixel_min); + for (std::size_t i = begin; i < end; ++i) { + const double pixel = x_transform.to_pixel(source[i].x()); + const int bucket_index = std::clamp(static_cast((pixel - pixel_min) * static_cast(bucket_count - 1) / pixel_span), 0, bucket_count - 1); + Curve_Peak_Bucket& bucket = buckets[static_cast(bucket_index)]; + const double value = source[i].y(); + if (!bucket.used) { + bucket.used = true; + bucket.first = bucket.min = bucket.max = static_cast(i); + bucket.min_value = bucket.max_value = value; + } + bucket.last = static_cast(i); + if (value < bucket.min_value) { + bucket.min_value = value; + bucket.min = static_cast(i); + } + if (value > bucket.max_value) { + bucket.max_value = value; + bucket.max = static_cast(i); + } + } + output.reserve(static_cast(bucket_count) * 4); + const bool forward = first_pixel <= last_pixel; + for (int bucket_index = forward ? 0 : bucket_count - 1; + forward ? bucket_index < bucket_count : bucket_index >= 0; + forward ? ++bucket_index : --bucket_index) { + const Curve_Peak_Bucket& bucket = buckets[static_cast(bucket_index)]; + if (!bucket.used) + continue; + std::array indexes{bucket.first, bucket.min, bucket.max, bucket.last}; + std::sort(indexes.begin(), indexes.end()); + const auto unique_end = std::unique(indexes.begin(), indexes.end()); + for (auto it = indexes.begin(); it != unique_end; ++it) { + const QPointF& point = source[static_cast(*it)]; + append_unique(output, QPointF(x_transform.to_pixel(point.x()), y_transform.to_pixel(point.y()))); + } + } + return output; +} + +std::pmr::vector clip_non_monotonic_points(std::span source, + const Axis_Transform_Snapshot& x_transform, + const Axis_Transform_Snapshot& y_transform) { + std::pmr::vector output(frame_memory_resource()); + if (source.size() < 2) + return output; + const double minimum = std::min(x_transform.coordinate_range.origin, x_transform.coordinate_range.target); + const double maximum = std::max(x_transform.coordinate_range.origin, x_transform.coordinate_range.target); + bool active = false; + for (std::size_t i = 1; i < source.size(); ++i) { + const QPointF& first = source[i - 1]; + const QPointF& second = source[i]; + if (!finite_point(first) || !finite_point(second)) { + append_path_break(output); + active = false; + continue; + } + const double dx = second.x() - first.x(); + double t0 = 0.0; + double t1 = 1.0; + if (dx == 0.0) { + if (first.x() < minimum || first.x() > maximum) { + active = false; + continue; + } + } + else { + const double a = (minimum - first.x()) / dx; + const double b = (maximum - first.x()) / dx; + t0 = std::max(0.0, std::min(a, b)); + t1 = std::min(1.0, std::max(a, b)); + if (t0 > t1) { + active = false; + continue; + } + } + const QPointF clipped_first(first.x() + dx * t0, first.y() + (second.y() - first.y()) * t0); + const QPointF clipped_second(first.x() + dx * t1, first.y() + (second.y() - first.y()) * t1); + const QPointF pixel_first(x_transform.to_pixel(clipped_first.x()), y_transform.to_pixel(clipped_first.y())); + const QPointF pixel_second(x_transform.to_pixel(clipped_second.x()), y_transform.to_pixel(clipped_second.y())); + if (!active || output.empty() || output.back() != pixel_first) { + append_path_break(output); + append_unique(output, pixel_first); + } + append_unique(output, pixel_second); + active = t1 == 1.0; + } + return output; +} +} // namespace + +std::pmr::vector Curve_Utils::create_explicit_points(std::span source, + const Axis_Transform_Snapshot& x_transform, + const Axis_Transform_Snapshot& y_transform, + bool visible_range_only, + Line_Sampling_Mode sampling_mode) { + if (source.empty()) + return {}; + if (!visible_range_only) { + return create_points(static_cast(source.size()), [source, x_transform, y_transform](int index) { + const QPointF& point = source[static_cast(index)]; + return finite_point(point) ? QPointF(x_transform.to_pixel(point.x()), y_transform.to_pixel(point.y())) : QPointF(curve_nan, curve_nan); + }); + } + const int direction = monotonic_direction(source); + if (!direction) + return clip_non_monotonic_points(source, x_transform, y_transform); + const double minimum = std::min(x_transform.coordinate_range.origin, x_transform.coordinate_range.target); + const double maximum = std::max(x_transform.coordinate_range.origin, x_transform.coordinate_range.target); + const auto [begin, end] = monotonic_visible_window(source, minimum, maximum, direction); + return transform_monotonic_points(source, begin, end, x_transform, y_transform, sampling_mode); +} +void Curve_Utils::append_peak_point(std::pmr::vector& points, const Axis_Transform_Snapshot& x_transform, const Axis_Transform_Snapshot& y_transform, const Range& data_range, int total_count, int index, double value) { if (index < 0) return; - QPointF point(x_axis->d()->render_coord_to_pixel(source_index_to_coord(data_range, total_count, index)), y_axis->d()->render_coord_to_pixel(value)); + QPointF point(x_transform.to_pixel(source_index_to_coord(data_range, total_count, index)), y_transform.to_pixel(value)); if (!points.empty() && points.back() == point) return; points.push_back(point); diff --git a/Renderive/primitive/Curve.h b/Renderive/primitive/Curve.h index f5173c6..fd29d76 100644 --- a/Renderive/primitive/Curve.h +++ b/Renderive/primitive/Curve.h @@ -1,34 +1,29 @@ #pragma once -#include -#include -#include +#include +#include +#include #include -#include -#include -#include #include #include #include #include +#include "../Axis/Abs_Axis.h" #include "../architecture/Renderable.h" #include "../architecture/Update_Completion.h" -#include "../Axis/Abs_Axis_p.h" -#include "../base/Frame_Memory.h" #include "../base/Memory.h" -#include "../plottable/Interpolation_p.h" +#include "../base/Interpolation.h" + namespace renderive { struct Curve_Private; -/// @brief 曲线采样模式。 -/// @brief Sampling strategy used when converting a continuous sample sequence to screen polyline points. + +/// @brief Reduction used when converting source samples to line geometry. enum class Line_Sampling_Mode : std::uint8_t { - /// @brief 直接按输入数据采样。 - /// @brief Use every input sample. - All_Samples, - /// @brief 按像素峰值桶降采样。 - /// @brief Keep a local peak envelope per screen-pixel bucket to limit the number of drawn points. - Pixel_Peak_Envelope + /// @brief Use each visible source sample exactly once; never upsample. + Exact, + /// @brief Preserve first, minimum, maximum, and last values per pixel bucket. + Preserve_Extrema }; -/// @brief 曲线构造属性。 + struct Curve_Prop { std::weak_ptr x_axis{}; std::weak_ptr y_axis{}; @@ -36,37 +31,22 @@ struct Curve_Prop { QPen pen = QPen(Qt::white); QBrush fill_brush = QBrush(Qt::NoBrush); bool visible_range_only = true; - bool antialias = false; - Line_Interpolation_Mode interpolation_mode = Line_Interpolation_Mode::Linear_Display; - Line_Sampling_Mode sample_mode = Line_Sampling_Mode::All_Samples; + Line_Interpolation interpolation_mode = Line_Interpolation::Linear; + Line_Sampling_Mode sample_mode = Line_Sampling_Mode::Exact; }; -/// @brief 峰值桶采样过程中的单桶统计。 -struct Curve_Peak_Bucket { - bool used = false; - int first = -1; - int last = -1; - int min = -1; - int max = -1; - double min_value = std::numeric_limits::max(); - double max_value = std::numeric_limits::lowest(); -}; -/// @brief 按坐标轴绘制折线或填充曲线的图元。 + +/// @brief A line or filled curve bound to two coordinate axes. class LIB_DECL Curve : public Renderable { public: - /// @brief 写入按 data_range 均匀分布的采样值。 void give_samples(std::span values, bool notify_render = true); - /// @brief 移动写入按 data_range 均匀分布的采样值。 void give_samples(std::pmr::vector&& values, bool notify_render = true); - /// @brief 提交采样值并返回完成票据。 Update_Ticket submit_samples(std::span values); - /// @brief 移动提交采样值并返回完成票据。 Update_Ticket submit_samples(std::pmr::vector&& values); - /// @brief 写入容器中的采样值。 + template void give_samples(const Values& values, bool notify_render = true) { give_samples(std::span(values.data(), values.size()), notify_render); } - /// @brief 用 writer 填充并写入采样值。 template void write_samples(std::size_t count, Writer&& writer, bool notify_render = true) { std::pmr::vector values(memory_resource(Memory_Domain::Curve)); @@ -74,20 +54,16 @@ public: std::forward(writer)(std::span(values.data(), values.size())); give_samples(std::move(values), notify_render); } - /// @brief 写入显式坐标点。 + void give_points(std::span points, bool notify_render = true); - /// @brief 移动写入显式坐标点。 void give_points(std::pmr::vector&& points, bool notify_render = true); - /// @brief 提交显式坐标点并返回完成票据。 Update_Ticket submit_points(std::span points); - /// @brief 移动提交显式坐标点并返回完成票据。 Update_Ticket submit_points(std::pmr::vector&& points); - /// @brief 写入容器中的显式坐标点。 + template void give_points(const Points& points, bool notify_render = true) { give_points(std::span(points.data(), points.size()), notify_render); } - /// @brief 用 writer 填充并写入显式坐标点。 template void write_points(std::size_t count, Writer&& writer, bool notify_render = true) { std::pmr::vector points(memory_resource(Memory_Domain::Curve)); @@ -95,34 +71,26 @@ public: std::forward(writer)(std::span(points.data(), points.size())); give_points(std::move(points), notify_render); } - /// @brief 绑定 X 轴。 + PROP(std::shared_ptr, x_axis) - /// @brief 绑定 Y 轴。 PROP(std::shared_ptr, y_axis) - /// @brief 采样值映射到 X 轴的数据范围。 PROP(Range, data_range) - /// @brief 折线画笔。 PROP(QPen, pen) - /// @brief 填充到 Y 轴目标边界的画刷。 PROP(QBrush, fill_brush) - /// @brief 是否只生成当前可见范围。 PROP(bool, visible_range_only) - /// @brief 是否启用抗锯齿。 - PROP(bool, antialias) - /// @brief 折线插值模式。 - PROP(Line_Interpolation_Mode, interpolation_mode) - /// @brief 曲线采样模式。 + PROP(Line_Interpolation, interpolation_mode) PROP(Line_Sampling_Mode, sample_mode) + struct Builder; - /// @brief 创建曲线图元。 Curve(); + private: Curve_Private* d(); Render_Object_Owner create_render_data() override; Render_Object_Owner create_render_state() override; Render_Object_Owner create_input_data() override; }; -/// @brief 曲线构造器。 + struct LIB_DECL Curve::Builder : Curve_Prop { PROP_B(std::shared_ptr, parent) PROP_B(std::shared_ptr, x_axis) @@ -131,98 +99,11 @@ struct LIB_DECL Curve::Builder : Curve_Prop { PROP_B(QPen, pen) PROP_B(QBrush, fill_brush) PROP_B(bool, visible_range_only) - PROP_B(bool, antialias) - PROP_B(Line_Interpolation_Mode, interpolation_mode) + PROP_B(Line_Interpolation, interpolation_mode) PROP_B(Line_Sampling_Mode, sample_mode) - /// @brief 创建曲线构造器。 + Builder(std::shared_ptr parent, std::shared_ptr x_axis, std::shared_ptr y_axis); - /// @brief 构建并返回曲线对象。 std::shared_ptr build(); std::shared_ptr parent{}; }; -/// @brief 曲线绘制算法工具。 -class Curve_Utils { -public: - /// @brief 按索引生成一组坐标点。 - template - static std::pmr::vector create_points(int count, Point_At point_at) { - std::pmr::vector points(frame_memory_resource()); - points.reserve(count); - for (int i = 0; i < count; ++i) - points.push_back(point_at(i)); - return points; - } - /// @brief 根据坐标轴和插值模式生成采样折线点。 - template - static std::pmr::vector create_sampled_points(Abs_Axis* x_axis, Abs_Axis* y_axis, const Range& data_range, int total_count, int available_count, bool visible_range_only, Line_Interpolation_Mode mode, Value_At value_at) { - return create_line_points(x_axis, y_axis, data_range, total_count, available_count, visible_range_only, mode, value_at); - } - /// @brief 根据像素峰值桶生成降采样折线点。 - template - static std::pmr::vector create_peak_bucket_points(Abs_Axis* x_axis, Abs_Axis* y_axis, const Range& data_range, int total_count, int available_count, bool visible_range_only, Value_At value_at) { - Line_Sample_Window window = line_sample_window(x_axis, data_range, total_count, available_count, visible_range_only); - if (!window) - return {}; - int first_source = std::clamp(static_cast(std::floor(std::min(window.first_index, window.last_index))), 0, available_count - 1); - int last_source = std::clamp(static_cast(std::ceil(std::max(window.first_index, window.last_index))), first_source, available_count - 1); - double first_pixel = x_axis->d()->render_coord_to_pixel(source_index_to_coord(data_range, total_count, window.first_index)); - double last_pixel = x_axis->d()->render_coord_to_pixel(source_index_to_coord(data_range, total_count, window.last_index)); - double pixel_min = std::min(first_pixel, last_pixel); - double pixel_max = std::max(first_pixel, last_pixel); - int bucket_count = std::max(1, static_cast(std::ceil(pixel_max - pixel_min)) + 1); - bucket_count = std::min(bucket_count, x_axis->d()->render_pixel_size() + 1); - std::pmr::vector buckets(bucket_count, frame_memory_resource()); - double pixel_span = std::max(1.0, pixel_max - pixel_min); - for (int i = first_source; i <= last_source; ++i) { - double coord = source_index_to_coord(data_range, total_count, i); - double pixel = x_axis->d()->render_coord_to_pixel(coord); - int bucket_index = std::clamp(static_cast((pixel - pixel_min) * static_cast(bucket_count - 1) / pixel_span), 0, bucket_count - 1); - Curve_Peak_Bucket& bucket = buckets[bucket_index]; - double value = value_at(i); - if (!bucket.used) { - bucket.used = true; - bucket.first = i; - bucket.min = i; - bucket.max = i; - bucket.min_value = value; - bucket.max_value = value; - } - bucket.last = i; - if (value < bucket.min_value) { - bucket.min_value = value; - bucket.min = i; - } - if (value > bucket.max_value) { - bucket.max_value = value; - bucket.max = i; - } - } - std::pmr::vector points(frame_memory_resource()); - points.reserve(bucket_count * 4); - bool pixel_forward = first_pixel <= last_pixel; - for (int b = pixel_forward ? 0 : bucket_count - 1; pixel_forward ? b < bucket_count : b >= 0; pixel_forward ? ++b : --b) { - const Curve_Peak_Bucket& bucket = buckets[b]; - if (!bucket.used) - continue; - std::array indexes{bucket.first, bucket.min, bucket.max, bucket.last}; - 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_peak_point(points, x_axis, y_axis, data_range, total_count, *it, value_at(*it)); - } - else { - for (auto it = std::make_reverse_iterator(end); it != indexes.rend(); ++it) - append_peak_point(points, x_axis, y_axis, data_range, total_count, *it, value_at(*it)); - } - } - return points; - } - /// @brief 绘制折线。 - static void draw_polyline(QPainter* painter, std::span points, const QPen& pen); - /// @brief 绘制曲线到指定 Y 像素基线的填充区域。 - static void draw_fill_to_y(QPainter* painter, std::span points, double baseline_y, const QBrush& brush); -private: - static void append_peak_point(std::pmr::vector& points, Abs_Axis* x_axis, Abs_Axis* y_axis, const Range& data_range, int total_count, int index, double value); -}; } // namespace renderive diff --git a/Renderive/primitive/Curve_Utils_p.h b/Renderive/primitive/Curve_Utils_p.h new file mode 100644 index 0000000..43a12d3 --- /dev/null +++ b/Renderive/primitive/Curve_Utils_p.h @@ -0,0 +1,122 @@ +#pragma once +#include +#include +#include +#include +#include +#include +#include +#include +#include "../Axis/Abs_Axis_p.h" +#include "../base/Frame_Memory.h" +#include "../base/Interpolation_p.h" +#include "Curve.h" + +namespace renderive { +struct Curve_Peak_Bucket { + bool used = false; + int first = -1; + int last = -1; + int min = -1; + int max = -1; + double min_value = std::numeric_limits::max(); + double max_value = std::numeric_limits::lowest(); +}; + +class Curve_Utils { +public: + template + static std::pmr::vector create_points(int count, Point_At point_at) { + std::pmr::vector points(frame_memory_resource()); + points.reserve(count); + for (int i = 0; i < count; ++i) + points.push_back(point_at(i)); + return points; + } + + template + static std::pmr::vector create_sampled_points(Abs_Axis* x_axis, Abs_Axis* y_axis, const Range& data_range, int total_count, int available_count, bool visible_range_only, Line_Interpolation mode, Interpolation_Value_Domain domain, Value_At value_at) { + const auto x_transform = render_axis_transform(*x_axis); + const auto y_transform = render_axis_transform(*y_axis); + return create_line_points(x_transform, y_transform, data_range, total_count, available_count, visible_range_only, mode, domain, value_at); + } + + template + static std::pmr::vector create_peak_bucket_points(Abs_Axis* x_axis, Abs_Axis* y_axis, const Range& data_range, int total_count, int available_count, bool visible_range_only, Value_At value_at) { + const auto x_transform = render_axis_transform(*x_axis); + const auto y_transform = render_axis_transform(*y_axis); + Line_Sample_Window window = line_sample_window(x_transform, data_range, total_count, available_count, visible_range_only); + if (!window) + return {}; + const int first_source = std::clamp(static_cast(std::floor(std::min(window.first_index, window.last_index))), 0, available_count - 1); + const int last_source = std::clamp(static_cast(std::ceil(std::max(window.first_index, window.last_index))), first_source, available_count - 1); + const double first_pixel = x_transform.to_pixel(source_index_to_coord(data_range, total_count, window.first_index)); + const double last_pixel = x_transform.to_pixel(source_index_to_coord(data_range, total_count, window.last_index)); + const double pixel_min = std::min(first_pixel, last_pixel); + const double pixel_max = std::max(first_pixel, last_pixel); + int bucket_count = std::max(1, static_cast(std::ceil(pixel_max - pixel_min)) + 1); + bucket_count = std::min({bucket_count, x_transform.pixel_size() + 1, last_source - first_source + 1}); + std::pmr::vector points(frame_memory_resource()); + points.reserve(bucket_count * 4); + const int source_count = last_source - first_source + 1; + for (int bucket_index = 0; bucket_index < bucket_count; ++bucket_index) { + const int bucket_begin = first_source + static_cast(static_cast(bucket_index) * source_count / bucket_count); + const int bucket_end = first_source + static_cast(static_cast(bucket_index + 1) * source_count / bucket_count) - 1; + int segment_begin = bucket_begin; + while (segment_begin <= bucket_end) { + const int gap_begin = segment_begin; + while (segment_begin <= bucket_end && !std::isfinite(value_at(segment_begin))) + ++segment_begin; + if (segment_begin != gap_begin && !points.empty() && std::isfinite(points.back().x())) + points.emplace_back(std::numeric_limits::quiet_NaN(), std::numeric_limits::quiet_NaN()); + if (segment_begin > bucket_end) + break; + Curve_Peak_Bucket segment; + segment.used = true; + segment.first = segment.last = segment.min = segment.max = segment_begin; + segment.min_value = segment.max_value = value_at(segment_begin); + int index = segment_begin + 1; + for (; index <= bucket_end; ++index) { + const double value = value_at(index); + if (!std::isfinite(value)) + break; + segment.last = index; + if (value < segment.min_value) { + segment.min_value = value; + segment.min = index; + } + if (value > segment.max_value) { + segment.max_value = value; + segment.max = index; + } + } + std::array indexes{segment.first, segment.min, segment.max, segment.last}; + std::sort(indexes.begin(), indexes.end()); + const auto unique_end = std::unique(indexes.begin(), indexes.end()); + for (auto it = indexes.begin(); it != unique_end; ++it) + append_peak_point(points, x_transform, y_transform, data_range, total_count, *it, value_at(*it)); + if (index <= bucket_end) { + points.emplace_back(std::numeric_limits::quiet_NaN(), std::numeric_limits::quiet_NaN()); + segment_begin = index + 1; + } + else { + segment_begin = bucket_end + 1; + } + } + } + return points; + } + + static void draw_polyline(Frame_Raster_Context& context, std::span points, const QPen& pen); + static void draw_fill_to_y(Frame_Raster_Context& context, std::span points, double baseline_y, const QBrush& brush); + static std::pmr::vector representative_fill_points(std::span line_points); + static std::pmr::vector create_explicit_points(std::span source, + const Axis_Transform_Snapshot& x_transform, + const Axis_Transform_Snapshot& y_transform, + bool visible_range_only, + Line_Sampling_Mode sampling_mode); + +private: + static void append_peak_point(std::pmr::vector& points, const Axis_Transform_Snapshot& x_transform, const Axis_Transform_Snapshot& y_transform, const Range& data_range, int total_count, int index, double value); +}; +} // namespace renderive diff --git a/Renderive/primitive/Fill_Area.cpp b/Renderive/primitive/Fill_Area.cpp index 86638c9..ad83d84 100644 --- a/Renderive/primitive/Fill_Area.cpp +++ b/Renderive/primitive/Fill_Area.cpp @@ -35,30 +35,33 @@ struct Fill_Area_Private : Typed_Render_Datax_axis.lock(); auto y_axis = s->y_axis.lock(); if (!x_axis || !y_axis || data_points.empty()) return; + const auto x_transform = render_axis_transform(*x_axis); + const auto y_transform = render_axis_transform(*y_axis); std::pmr::vector polygon(frame_memory_resource()); if (s->mode == Fill_Area_Mode::To_Baseline) { polygon.resize(data_points.size() + 2); - double baseline_pixel = y_axis->d()->render_coord_to_pixel(s->baseline); - double first_x = x_axis->d()->render_coord_to_pixel(data_points.front().x()); - double last_x = x_axis->d()->render_coord_to_pixel(data_points.back().x()); + double baseline_pixel = y_transform.to_pixel(s->baseline); + double first_x = x_transform.to_pixel(data_points.front().x()); + double last_x = x_transform.to_pixel(data_points.back().x()); polygon.front() = QPointF(first_x, baseline_pixel); polygon.back() = QPointF(last_x, baseline_pixel); for (int i = 0; i < data_points.size(); ++i) { const QPointF& point = data_points.at(i); - polygon[i + 1] = QPointF(x_axis->d()->render_coord_to_pixel(point.x()), y_axis->d()->render_coord_to_pixel(point.y())); + polygon[i + 1] = QPointF(x_transform.to_pixel(point.x()), y_transform.to_pixel(point.y())); } } else { polygon.resize(data_points.size()); for (int i = 0; i < data_points.size(); ++i) { const QPointF& point = data_points.at(i); - polygon[i] = QPointF(x_axis->d()->render_coord_to_pixel(point.x()), y_axis->d()->render_coord_to_pixel(point.y())); + polygon[i] = QPointF(x_transform.to_pixel(point.x()), y_transform.to_pixel(point.y())); } } painter->save(); @@ -95,9 +98,9 @@ RENDERIVE_DEFINE_STATE_PROP(Fill_Area, Fill_Area_Render_State, Fill_Area_Mode, m Fill_Area::Builder::Builder(std::shared_ptr parent, std::shared_ptr x_axis, std::shared_ptr y_axis) { ASSERT(parent, "Fill_Area build error! parent == nullptr"); ASSERT(x_axis && y_axis, "Fill_Area build error! axis == nullptr"); - ASSERT(x_axis->plot != nullptr, "Fill_Area build error! x_axis->plot == nullptr"); - ASSERT(x_axis->plot == y_axis->plot, "Fill_Area build error! x_axis->plot != y_axis->plot"); - ASSERT(parent->plot == x_axis->plot, "Fill_Area build error! parent->plot != axis->plot"); + ASSERT(x_axis->is_attached(), "Fill_Area build error! x_axis is detached"); + ASSERT(x_axis->shares_plot_with(*y_axis), "Fill_Area build error! axes belong to different plots"); + ASSERT(parent->shares_plot_with(*x_axis), "Fill_Area build error! parent and axes belong to different plots"); this->parent = parent; this->x_axis = x_axis; this->y_axis = y_axis; @@ -106,7 +109,7 @@ std::shared_ptr Fill_Area::Builder::build() { auto ret = renderive::make_shared(); ret->init(parent); Fill_Area_Private* pd = ret->d(); - Fill_Area_Render_State* sc = reinterpret_cast(pd->state(State_Edit)); + Fill_Area_Render_State* sc = reinterpret_cast(pd->state(Data_Buffer_Role::Edit)); PROP_R(std::weak_ptr, x_axis) PROP_R(std::weak_ptr, y_axis) PROP_R(double, baseline) diff --git a/Renderive/primitive/Grid.cpp b/Renderive/primitive/Grid.cpp index f09d753..1bb4ae1 100644 --- a/Renderive/primitive/Grid.cpp +++ b/Renderive/primitive/Grid.cpp @@ -18,8 +18,9 @@ struct Grid_Private : Typed_Render_Data { void draw_axis_grid(QPainter* painter, Abs_Axis* axis, Abs_Axis* cross_axis, const Range& axis_range, const Range& cross_range, const QPen& pen, const QPen& sub_pen, bool sub_grid) { if (pen.style() == Qt::NoPen && (!sub_grid || sub_pen.style() == Qt::NoPen)) return; - double cross0 = cross_axis->d()->render_coord_to_pixel(cross_range.origin); - double cross1 = cross_axis->d()->render_coord_to_pixel(cross_range.target); + const auto cross_transform = render_axis_transform(*cross_axis); + double cross0 = cross_transform.to_pixel(cross_range.origin); + double cross1 = cross_transform.to_pixel(cross_range.target); double step = axis->get_tick_step(axis_range); std::vector ticks = axis->create_tick_vector(step, axis_range); if (sub_grid && sub_pen.style() != Qt::NoPen) { @@ -35,30 +36,33 @@ struct Grid_Private : Typed_Render_Data { void draw_division_grid(QPainter* painter, Abs_Axis* axis, Abs_Axis* cross_axis, const Range& axis_range, const Range& cross_range, const QPen& pen, int divisions) { if (pen.style() == Qt::NoPen) return; - double cross0 = cross_axis->d()->render_coord_to_pixel(cross_range.origin); - double cross1 = cross_axis->d()->render_coord_to_pixel(cross_range.target); + const auto cross_transform = render_axis_transform(*cross_axis); + double cross0 = cross_transform.to_pixel(cross_range.origin); + double cross1 = cross_transform.to_pixel(cross_range.target); std::vector ticks = create_division_ticks(axis_range, divisions); painter->setPen(pen); draw_ticks(painter, axis, cross0, cross1, ticks); } void draw_ticks(QPainter* painter, Abs_Axis* axis, double cross0, double cross1, const std::vector& ticks) { - bool vertical = axis->d()->render_axis_state()->orientation == Qt::Horizontal; + const auto transform = render_axis_transform(*axis); + bool vertical = transform.orientation == Qt::Horizontal; for (double tick : ticks) { - double pixel = axis->d()->render_coord_to_pixel(tick); + double pixel = transform.to_pixel(tick); if (vertical) painter->drawLine(QPointF(pixel, cross0), QPointF(pixel, cross1)); else painter->drawLine(QPointF(cross0, pixel), QPointF(cross1, pixel)); } } - void draw(QPainter* painter, const Plot_Render_Snapshot&) override { + void rasterize(Frame_Raster_Context& context, const Plot_Render_Snapshot&) override { + QPainter* painter = &context.qt(); Grid_Render_State* s = render_state(); auto x_axis = s->x_axis.lock(); auto y_axis = s->y_axis.lock(); if (!x_axis || !y_axis) return; - Range x_range = x_axis->d()->render_coord_range(); - Range y_range = y_axis->d()->render_coord_range(); + Range x_range = render_axis_transform(*x_axis).coordinate_range; + Range y_range = render_axis_transform(*y_axis).coordinate_range; if (s->x_range.length() != 0.0) x_range = s->x_range; if (s->y_range.length() != 0.0) @@ -165,9 +169,9 @@ void Grid::set_sub_pen(QPen value) { Grid::Builder::Builder(std::shared_ptr parent, std::shared_ptr x_axis, std::shared_ptr y_axis) { ASSERT(parent, "Grid build error! parent == nullptr"); ASSERT(x_axis && y_axis, "Grid build error! axis == nullptr"); - ASSERT(x_axis->plot != nullptr, "Grid build error! x_axis->plot == nullptr"); - ASSERT(x_axis->plot == y_axis->plot, "Grid build error! x_axis->plot != y_axis->plot"); - ASSERT(parent->plot == x_axis->plot, "Grid build error! parent->plot != axis->plot"); + ASSERT(x_axis->is_attached(), "Grid build error! x_axis is detached"); + ASSERT(x_axis->shares_plot_with(*y_axis), "Grid build error! axes belong to different plots"); + ASSERT(parent->shares_plot_with(*x_axis), "Grid build error! parent and axes belong to different plots"); this->parent = parent; this->x_axis = x_axis; this->y_axis = y_axis; @@ -176,7 +180,7 @@ std::shared_ptr Grid::Builder::build() { auto ret = renderive::make_shared(); ret->init(parent); Grid_Private* pd = ret->d(); - Grid_Render_State* sc = reinterpret_cast(pd->state(State_Edit)); + Grid_Render_State* sc = reinterpret_cast(pd->state(Data_Buffer_Role::Edit)); PROP_R(std::weak_ptr, x_axis) PROP_R(std::weak_ptr, y_axis) PROP_R(bool, x_grid) diff --git a/Renderive/primitive/Label.cpp b/Renderive/primitive/Label.cpp index 33bce8b..7d1debf 100644 --- a/Renderive/primitive/Label.cpp +++ b/Renderive/primitive/Label.cpp @@ -39,7 +39,9 @@ struct Label_Private : Typed_Render_Datafont()); QRectF text_bounds = metrics.boundingRect(QRectF(0.0, 0.0, 4096.0, 4096.0), Qt::AlignLeft | Qt::AlignTop, item.text); QSizeF text_size = text_bounds.size(); - QPointF anchor(x_axis->d()->render_coord_to_pixel(item.position.x()), y_axis->d()->render_coord_to_pixel(item.position.y())); + const auto x_transform = render_axis_transform(*x_axis); + const auto y_transform = render_axis_transform(*y_axis); + QPointF anchor(x_transform.to_pixel(item.position.x()), y_transform.to_pixel(item.position.y())); QPointF top_left = anchor + item.offset; if (item.alignment & Qt::AlignHCenter) top_left.rx() -= text_size.width() / 2.0; @@ -59,7 +61,8 @@ struct Label_Private : Typed_Render_DatadrawText(rect, Qt::AlignLeft | Qt::AlignVCenter, item.text); painter->restore(); } - void draw(QPainter* painter, const Plot_Render_Snapshot&) override { + void rasterize(Frame_Raster_Context& context, const Plot_Render_Snapshot&) override { + QPainter* painter = &context.qt(); Label_Render_State* s = render_state(); auto x_axis = s->x_axis.lock(); auto y_axis = s->y_axis.lock(); @@ -88,7 +91,7 @@ void Label::give_items(std::span items, bool notify_render) { if (notify_render && !ok()) return; auto context = plot_context.lock(); - if (!notify_render && (get_retiring() || !d_ptr || !context || context->destroying.load(std::memory_order_acquire))) + if (!notify_render && (get_retiring() || !render_data() || !context || context->destroying.load(std::memory_order_acquire))) return; Render_Input_Lease input(d(), notify_render); input->push(items); @@ -97,7 +100,7 @@ void Label::give_items(std::pmr::vector&& items, bool notify_render) if (notify_render && !ok()) return; auto context = plot_context.lock(); - if (!notify_render && (get_retiring() || !d_ptr || !context || context->destroying.load(std::memory_order_acquire))) + if (!notify_render && (get_retiring() || !render_data() || !context || context->destroying.load(std::memory_order_acquire))) return; Render_Input_Lease input(d(), notify_render); input->push(std::move(items)); @@ -115,9 +118,9 @@ RENDERIVE_DEFINE_STATE_PROP(Label, Label_Render_State, QPen, border_pen) Label::Builder::Builder(std::shared_ptr parent, std::shared_ptr x_axis, std::shared_ptr y_axis) { ASSERT(parent, "Label build error! parent == nullptr"); ASSERT(x_axis && y_axis, "Label build error! axis == nullptr"); - ASSERT(x_axis->plot != nullptr, "Label build error! x_axis->plot == nullptr"); - ASSERT(x_axis->plot == y_axis->plot, "Label build error! x_axis->plot != y_axis->plot"); - ASSERT(parent->plot == x_axis->plot, "Label build error! parent->plot != axis->plot"); + ASSERT(x_axis->is_attached(), "Label build error! x_axis is detached"); + ASSERT(x_axis->shares_plot_with(*y_axis), "Label build error! axes belong to different plots"); + ASSERT(parent->shares_plot_with(*x_axis), "Label build error! parent and axes belong to different plots"); this->parent = parent; this->x_axis = x_axis; this->y_axis = y_axis; @@ -126,7 +129,7 @@ std::shared_ptr