diff --git a/AGENTS.md b/AGENTS.md index ae42f91..8e0b2a5 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -10,3 +10,15 @@ 8. 不要提前加入空配置、无消费者统计和未完成接口。 9. 替换实现时直接删除旧实现,不保留兼容转发。 10. 修改后检查新增成员的写入者、读取者、生命周期,以及是否形成重复状态源。 + +11. 注意使用 clion提供的工具 Name Url Bearer Token Env Var Status Auth + clion-index http://127.0.0.1:29177/index-mcp/streamable-http - enabled Unsupported + +"C:\Program Files\JetBrains\CLion 2026.1\bin\cmake\win\x64\bin\cmake.exe" -DCMAKE_BUILD_TYPE=Debug +"-DCMAKE_C_COMPILER=C:/Program Files/Microsoft Visual +Studio/18/Enterprise/VC/Tools/MSVC/14.50.35717/bin/Hostx64/x64/cl.bat" "-DCMAKE_CXX_COMPILER=C:/Program Files/Microsoft +Visual Studio/18/Enterprise/VC/Tools/MSVC/14.50.35717/bin/Hostx64/x64/cl.bat" --preset vs2019_Debug -S D:\ae\proj\Radio +-B D:\ae\proj\Radio\cmake-build-vs2019_debug +"C:\Program Files\JetBrains\CLion 2026.1\bin\cmake\win\x64\bin\cmake.exe" --build D: +\ae\proj\Radio\cmake-build-vs2019_debug --target Renderive_Core -j 30 + diff --git a/Renderive/Axis/Abs_Axis.cpp b/Core/Axis/Abs_Axis.cpp similarity index 66% rename from Renderive/Axis/Abs_Axis.cpp rename to Core/Axis/Abs_Axis.cpp index 21f1de3..2f575af 100644 --- a/Renderive/Axis/Abs_Axis.cpp +++ b/Core/Axis/Abs_Axis.cpp @@ -5,7 +5,7 @@ namespace renderive { RENDERIVE_DEFINE_RENDERABLE_BINDING(Abs_Axis, Abs_Axis_Private, Abs_Axis_Render_State, Abs_Axis_Input_Data, "AbsAxis") RENDERIVE_DEFINE_STATE_PROP(Abs_Axis, Abs_Axis_Render_State, int, x) RENDERIVE_DEFINE_STATE_PROP(Abs_Axis, Abs_Axis_Render_State, int, y) -RENDERIVE_DEFINE_STATE_PROP(Abs_Axis, Abs_Axis_Render_State, Qt::Orientation, orientation) +RENDERIVE_DEFINE_STATE_PROP(Abs_Axis, Abs_Axis_Render_State, Orientation, orientation) size_t Abs_Axis::pixel_size() { return d()->edit_state_value(&Abs_Axis_Render_State::pixel_size); } @@ -14,13 +14,12 @@ void Abs_Axis::set_pixel_size(size_t value) { } RENDERIVE_DEFINE_STATE_PROP(Abs_Axis, Abs_Axis_Render_State, int, tick_length) RENDERIVE_DEFINE_STATE_PROP(Abs_Axis, Abs_Axis_Render_State, int, sub_tick_length) -RENDERIVE_DEFINE_STATE_PROP(Abs_Axis, Abs_Axis_Render_State, QColor, color) -RENDERIVE_DEFINE_STATE_PROP(Abs_Axis, Abs_Axis_Render_State, QChar, format_char) -RENDERIVE_DEFINE_STATE_PROP(Abs_Axis, Abs_Axis_Render_State, QLocale, locale) -RENDERIVE_DEFINE_STATE_PROP(Abs_Axis, Abs_Axis_Render_State, QString, unit_text) -RENDERIVE_DEFINE_STATE_PROP(Abs_Axis, Abs_Axis_Render_State, QFont, unit_text_font) -RENDERIVE_DEFINE_STATE_PROP(Abs_Axis, Abs_Axis_Render_State, QPen, unit_text_pen) -RENDERIVE_DEFINE_STATE_PROP(Abs_Axis, Abs_Axis_Render_State, QBrush, unit_text_background_brush) +RENDERIVE_DEFINE_STATE_PROP(Abs_Axis, Abs_Axis_Render_State, Color, color) +RENDERIVE_DEFINE_STATE_PROP(Abs_Axis, Abs_Axis_Render_State, Number_Locale, locale) +RENDERIVE_DEFINE_STATE_PROP(Abs_Axis, Abs_Axis_Render_State, std::string, unit_text) +RENDERIVE_DEFINE_STATE_PROP(Abs_Axis, Abs_Axis_Render_State, Font, unit_text_font) +RENDERIVE_DEFINE_STATE_PROP(Abs_Axis, Abs_Axis_Render_State, Pen, unit_text_pen) +RENDERIVE_DEFINE_STATE_PROP(Abs_Axis, Abs_Axis_Render_State, Brush, unit_text_background_brush) RENDERIVE_DEFINE_STATE_PROP(Abs_Axis, Abs_Axis_Render_State, int, rotate) double Abs_Axis::pixel_to_coord(double pixel) { return d()->edit_pixel_to_coord(pixel); @@ -31,13 +30,9 @@ double Abs_Axis::coord_to_pixel(double coord) { std::vector Abs_Axis_Private::create_tick_vector(double tick_step, const Range& range) { double tick_origin = render_state()->coord_start; std::vector result; - // Generate tick positions according to tick_step: - // do not use qFloor here, or we'll lose 64 bit precision - // qDebug() << "tick_step:" << tick_step; - // - auto first_step = qint64(floor(qAbs((range.origin - tick_origin) / tick_step))); - auto last_step = qint64(ceil((qAbs(range.target - tick_origin) / tick_step))); - int tick_count = int(qAbs(last_step - first_step) + 1); + auto first_step = static_cast(std::floor(std::abs((range.origin - tick_origin) / tick_step))); + auto last_step = static_cast(std::ceil(std::abs(range.target - tick_origin) / tick_step)); + int tick_count = static_cast(std::abs(last_step - first_step) + 1); if (tick_count < 0) tick_count = 0; if (range.origin > range.target) @@ -46,13 +41,10 @@ std::vector Abs_Axis_Private::create_tick_vector(double tick_step, const for (int i = 0; i < tick_count; ++i) { result[i] = tick_origin + (double)(first_step + i) * tick_step; } - // while (!result.empty() && !range.contain(result.last())) { - // result.removeLast(); - // } return result; } -std::vector Abs_Axis_Private::create_label_vector(const std::vector& ticks, SRC src) { - std::vector result; +std::vector Abs_Axis_Private::create_label_vector(const std::vector& ticks, SRC src) { + std::vector result; result.reserve(ticks.size()); for (double tick_coord : ticks) result.push_back(get_tick_label(tick_coord, src)); @@ -74,82 +66,77 @@ std::vector Abs_Axis_Private::create_sub_tick_vector(int sub_tick_count, return result; } int Abs_Axis_Private::get_sub_tick_count(double tick_step) { - int result = 1; // default to 1, if no proper value can be found - // separate integer and fractional part of mantissa: + int result = 1; double epsilon = 0.01; double int_partf; int int_part; double frac_part = modf(get_mantissa(tick_step), &int_partf); int_part = int(int_partf); - // handle cases with (almost) integer mantissa: if (frac_part < epsilon || 1.0 - frac_part < epsilon) { if (1.0 - frac_part < epsilon) ++int_part; switch (int_part) { case 1: result = 4; - break; // 1.0 -> 0.2 substep + break; case 2: result = 3; - break; // 2.0 -> 0.5 substep + break; case 3: result = 2; - break; // 3.0 -> 1.0 substep + break; case 4: result = 3; - break; // 4.0 -> 1.0 substep + break; case 5: result = 4; - break; // 5.0 -> 1.0 substep + break; case 6: result = 2; - break; // 6.0 -> 2.0 substep + break; case 7: result = 6; - break; // 7.0 -> 1.0 substep + break; case 8: result = 3; - break; // 8.0 -> 2.0 substep + break; case 9: result = 2; - break; // 9.0 -> 3.0 substep + break; } } else { - // handle cases with significantly fractional mantissa: - if (qAbs(frac_part - 0.5) < epsilon) // *.5 mantissa - { + if (std::abs(frac_part - 0.5) < epsilon) { switch (int_part) { case 1: result = 2; - break; // 1.5 -> 0.5 substep + break; case 2: result = 4; - break; // 2.5 -> 0.5 substep + break; case 3: result = 4; - break; // 3.5 -> 0.7 substep + break; case 4: result = 2; - break; // 4.5 -> 1.5 substep + break; case 5: result = 4; - break; // 5.5 -> 1.1 substep (won't occur with default get_tick_step from here on) + break; case 6: result = 4; - break; // 6.5 -> 1.3 substep + break; case 7: result = 2; - break; // 7.5 -> 2.5 substep + break; case 8: result = 4; - break; // 8.5 -> 1.7 substep + break; case 9: result = 4; - break; // 9.5 -> 1.9 substep + break; } } - // if mantissa fraction isn't 0.0 or 0.5, don't bother finding good sub tick marks, leave default } return result; } @@ -177,9 +164,9 @@ 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) { +std::string 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); + return format_number(tick, Number_Format{.precision = render_state->number_precision}); } double Abs_Axis::start_coord() { return d()->edit_axis_state()->coord_start; @@ -197,11 +184,11 @@ int Abs_Axis::get_pixel_point_size(Range range) { double length = coord_range().length(); if (length == 0.0) return 0; - return static_cast(qAbs(range.length() / length) * render_state->pixel_size); + return static_cast(std::abs(range.length() / length) * render_state->pixel_size); } int Abs_Axis::get_pixel_point_size() { auto render_state = d()->edit_axis_state(); - return qAbs(render_state->pixel_size); + return static_cast(render_state->pixel_size); } double Abs_Axis::get_tick_step(const Range& range) { return d()->get_tick_step(range); @@ -209,10 +196,10 @@ double Abs_Axis::get_tick_step(const Range& range) { int Abs_Axis::get_sub_tick_count(double tick_step) { return d()->get_sub_tick_count(tick_step); } -QString Abs_Axis::get_tick_label(double tick) { +std::string Abs_Axis::get_tick_label(double tick) { return d()->edit_tick_label(tick); } -std::vector Abs_Axis::create_label_vector(const std::vector& ticks) { +std::vector Abs_Axis::create_label_vector(const std::vector& ticks) { return d()->edit_label_vector(ticks); } std::vector Abs_Axis::create_tick_vector(double tick_step, const Range& range) { diff --git a/Renderive/Axis/Abs_Axis.h b/Core/Axis/Abs_Axis.h similarity index 59% rename from Renderive/Axis/Abs_Axis.h rename to Core/Axis/Abs_Axis.h index 7cbe5dc..5e78f54 100644 --- a/Renderive/Axis/Abs_Axis.h +++ b/Core/Axis/Abs_Axis.h @@ -1,53 +1,57 @@ #pragma once #include "global.h" +#include "../base/Color.h" +#include "../base/Geometry.h" +#include "../base/String_Format.h" +#include "../base/Style.h" +#include "../base/Text.h" +#include namespace renderive { +struct Axis_Render_Access; struct Abs_Axis_Prop { int x{}; int y{}; - Qt::Orientation orientation = Qt::Horizontal; + Orientation orientation = Orientation::Horizontal; int tick_length = 10; int sub_tick_length = 5; double coord_start = 0; double coord_length = 20; - QColor color = Qt::white; - QChar format_char = 'f'; - QLocale locale; - QString unit_text = ""; - QFont unit_text_font; - QPen unit_text_pen = QPen(Qt::white); - QBrush unit_text_background_brush = QBrush(Qt::black); + Color color = Color::white(); + Number_Locale locale; + std::string unit_text = ""; + Font unit_text_font; + Pen unit_text_pen = Pen{.color = Color::white()}; + Brush unit_text_background_brush = Brush{.color = Color::black()}; int rotate = 0; }; struct Abs_Axis_Private; 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) - PROP(Qt::Orientation, orientation) + PROP(Orientation, orientation) PROP(size_t, pixel_size) PROP(int, tick_length) PROP(int, sub_tick_length) - PROP(QColor, color) - PROP(QChar, format_char) - PROP(QLocale, locale) - PROP(QString, unit_text) - PROP(QFont, unit_text_font) - PROP(QPen, unit_text_pen) - PROP(QBrush, unit_text_background_brush) + PROP(Color, color) + PROP(Number_Locale, locale) + PROP(std::string, unit_text) + PROP(Font, unit_text_font) + PROP(Pen, unit_text_pen) + PROP(Brush, unit_text_background_brush) PROP(int, rotate) [[nodiscard]] virtual double pixel_to_coord(double pixel); [[nodiscard]] virtual double coord_to_pixel(double coord); [[nodiscard]] virtual double get_tick_step(const Range& range); [[nodiscard]] virtual int get_sub_tick_count(double tick_step); - virtual QString get_tick_label(double tick); + virtual std::string get_tick_label(double tick); virtual std::vector 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 std::vector create_label_vector(const std::vector& ticks); + virtual std::vector create_label_vector(const std::vector& ticks); double start_coord(); double end_coord(); Range coord_range(); @@ -55,22 +59,25 @@ public: int get_pixel_point_size(); template struct Builder_T; + +private: + friend struct Axis_Render_Access; + Abs_Axis_Private* d(); }; template struct Abs_Axis::Builder_T : protected Abs_Axis_Prop { PROP_BT(int, x) PROP_BT(int, y) - PROP_BT(Qt::Orientation, orientation) + PROP_BT(Orientation, orientation) PROP_BT(size_t, pixel_size) PROP_BT(int, tick_length) PROP_BT(int, sub_tick_length) - PROP_BT(QColor, color) - PROP_BT(QChar, format_char) - PROP_BT(QLocale, locale) - PROP_BT(QString, unit_text) - PROP_BT(QFont, unit_text_font) - PROP_BT(QPen, unit_text_pen) - PROP_BT(QBrush, unit_text_background_brush) + PROP_BT(Color, color) + PROP_BT(Number_Locale, locale) + PROP_BT(std::string, unit_text) + PROP_BT(Font, unit_text_font) + PROP_BT(Pen, unit_text_pen) + PROP_BT(Brush, unit_text_background_brush) PROP_BT(int, rotate) std::shared_ptr parent{}; size_t pixel_size{}; diff --git a/Core/Axis/Abs_Axis_p.h b/Core/Axis/Abs_Axis_p.h new file mode 100644 index 0000000..4358da3 --- /dev/null +++ b/Core/Axis/Abs_Axis_p.h @@ -0,0 +1,295 @@ +#pragma once +#include +#include +#include +#include "../architecture/Render_Lease.h" +#include "../render/Canvas.h" +#include "Abs_Axis.h" +#include "blend2d/blend2d.h" +namespace renderive { +struct Abs_Axis_Render_State : Render_State, Abs_Axis_Prop { + size_t pixel_size{}; + int number_precision = 2; + void scale_range(double factor, double center) { + coord_start = (coord_start - center) * factor + center; + coord_length *= factor; + } +}; +struct Abs_Axis_Input_Data : Input_Data {}; +struct Abs_Axis_Private : Typed_Render_Data { + int tick_count = 2; + [[nodiscard]] Abs_Axis_Render_State* edit_axis_state() { + return state_data(SRC::Edit); + } + [[nodiscard]] Abs_Axis_Render_State* render_axis_state() { + return render_state(); + } + static int pixel_start_of(const Abs_Axis_Render_State* s) { + return s->orientation == Orientation::Horizontal ? s->x : s->y; + } + double get_mantissa(double input, double* magnitude = nullptr); + [[nodiscard]] double clean_mantissa(double input); + [[nodiscard]] double pick_closest(double target, const std::vector& candidates); + void prepare_data(const Plot_Render_Snapshot&) override { + sync_state_pipeline(); + } + void draw(Canvas& canvas, const Plot_Render_Snapshot&) override { + Abs_Axis_Render_State* s = render_state(); + const int pixel_start = pixel_start_of(s); + Range range = {s->coord_start, s->coord_start + s->coord_length}; + double tick_step = get_tick_step(range); + std::vector ticks = create_tick_vector(tick_step, range); + while (!ticks.empty() && !range.contain(ticks.back())) { + ticks.pop_back(); + } + int sub_tick_count = get_sub_tick_count(tick_step); + std::vector sub_ticks = create_sub_tick_vector(sub_tick_count, ticks, range); + while (!sub_ticks.empty() && !range.contain(sub_ticks.back())) { + sub_ticks.pop_back(); + } + std::vector labels = create_label_vector(ticks, SRC::Render); + Text_Metrics tm = canvas.measure_text(!s->unit_text_font.valid() ? "0" : " "); + double h = tm.line_height(); + canvas.set_pen(Pen{.color = s->color}); + if (s->orientation == Orientation::Horizontal) { + canvas.draw_line({(double)s->x, (double)s->y}, {(double)(s->x + s->pixel_size), (double)s->y}); + int n = (int)ticks.size(); + for (int i = 0; i < n; ++i) { + double tick = ticks[i]; + double x = coord_to_pixel(tick, SRC::Render); + canvas.draw_line({x, (double)s->y}, {x, (double)(s->y + s->tick_length)}); + const std::string& label = labels[i]; + PointF start{x, (double)(s->y + s->tick_length)}; + canvas.save(); + canvas.translate(start.x, start.y); + canvas.rotate((double)s->rotate * 3.14159265358979 / 180.0); + canvas.draw_text({0.0, 0.0}, label); + canvas.restore(); + } + for (double tick : sub_ticks) { + double x = coord_to_pixel(tick, SRC::Render); + canvas.draw_line({x, (double)s->y}, {x, (double)(s->y + s->sub_tick_length)}); + } + } + else if (s->orientation == Orientation::Vertical) { + canvas.draw_line({(double)s->x, (double)s->y}, {(double)s->x, (double)(s->y + s->pixel_size)}); + int n = (int)ticks.size(); + for (int i = 0; i < n; ++i) { + double tick = ticks[i]; + double y = coord_to_pixel(tick, SRC::Render); + const std::string& label = labels[i]; + canvas.draw_line({(double)s->x, y}, {(double)(s->x + s->tick_length), y}); + PointF start{(double)(s->x + s->tick_length * 2), y + h / 2.0}; + canvas.save(); + canvas.translate(start.x, start.y); + canvas.rotate((double)s->rotate * 3.14159265358979 / 180.0); + canvas.draw_text({0.0, 0.0}, label); + canvas.restore(); + } + for (double tick : sub_ticks) { + double y = coord_to_pixel(tick, SRC::Render); + canvas.draw_line({(double)s->x, y}, {(double)(s->x + s->sub_tick_length), y}); + } + } + if (s->unit_text.empty()) + return; + Text_Metrics um = canvas.measure_text(s->unit_text_font, s->unit_text); + double w = um.width; + if (s->orientation == Orientation::Horizontal) { + double x = pixel_start + (double)s->pixel_size - w; + double y = (double)s->y - h; + RectF area(x, y, w, h); + canvas.set_brush(s->unit_text_background_brush); + canvas.fill_rect(area); + canvas.set_pen(s->unit_text_pen); + canvas.set_font(s->unit_text_font); + canvas.draw_text(area, Text_Align::Center, s->unit_text); + } + else if (s->orientation == Orientation::Vertical) { + double x = (double)s->x; + double y = (double)pixel_start; + RectF area(x, y, w, h); + canvas.set_brush(s->unit_text_background_brush); + canvas.fill_rect(area); + canvas.set_pen(s->unit_text_pen); + canvas.set_font(s->unit_text_font); + canvas.draw_text(area, Text_Align::Center, 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]] 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; + } + virtual double get_tick_step(const Range& range) { + double exact_step = range.size() / double(tick_count + 1e-10); + return clean_mantissa(exact_step); + } + virtual int get_sub_tick_count(double tick_step); + virtual std::vector 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 std::string get_tick_label(double tick, SRC); + virtual std::vector create_label_vector(const std::vector& ticks, SRC); + [[nodiscard]] double edit_pixel_to_coord(double pixel) { + return pixel_to_coord(pixel, SRC::Edit); + } + [[nodiscard]] double render_pixel_to_coord(double pixel) { + return pixel_to_coord(pixel, SRC::Render); + } + [[nodiscard]] double edit_coord_to_pixel(double coord) { + return coord_to_pixel(coord, SRC::Edit); + } + [[nodiscard]] double render_coord_to_pixel(double coord) { + return coord_to_pixel(coord, SRC::Render); + } + [[nodiscard]] Range edit_coord_range() { + const auto* s = edit_axis_state(); + return {s->coord_start, s->coord_start + s->coord_length}; + } + [[nodiscard]] Range render_coord_range() { + const auto* s = render_axis_state(); + return {s->coord_start, s->coord_start + s->coord_length}; + } + [[nodiscard]] int render_pixel_size() { + return std::abs(static_cast(render_axis_state()->pixel_size)); + } + [[nodiscard]] std::string edit_tick_label(double tick) { + return get_tick_label(tick, SRC::Edit); + } + [[nodiscard]] std::string render_tick_label(double tick) { + return get_tick_label(tick, SRC::Render); + } + [[nodiscard]] std::vector edit_label_vector(const std::vector& ticks) { + return create_label_vector(ticks, SRC::Edit); + } + [[nodiscard]] std::vector render_label_vector(const std::vector& ticks) { + return create_label_vector(ticks, SRC::Render); + } +}; +struct Axis_Frame_Snapshot { + Orientation orientation = Orientation::Horizontal; + Range coord_range{0.0, 1.0}; + Range pixel_range{0.0, 1.0}; + + [[nodiscard]] double coord_to_pixel(double coord) const { + if (coord_range.length() == 0.0) + return pixel_range.origin; + return pixel_range.origin + pixel_range.length() * (coord - coord_range.origin) / coord_range.length(); + } + + [[nodiscard]] double pixel_to_coord(double pixel) const { + if (pixel_range.length() == 0.0) + return coord_range.origin; + return coord_range.origin + coord_range.length() * (pixel - pixel_range.origin) / pixel_range.length(); + } + + [[nodiscard]] int pixel_size() const { + return static_cast(std::max(1.0, pixel_range.size())); + } +}; + +struct Axis_Mapping_2D { + Axis_Frame_Snapshot domain; + Axis_Frame_Snapshot value; + + [[nodiscard]] PointF map(double domain_coord, double value_coord) const { + PointF ret{}; + double domain_pixel = domain.coord_to_pixel(domain_coord); + double value_pixel = value.coord_to_pixel(value_coord); + if (domain.orientation == Orientation::Horizontal) { + ret.x = domain_pixel; + ret.y = value_pixel; + } + else { + ret.x = value_pixel; + ret.y = domain_pixel; + } + return ret; + } + + [[nodiscard]] PointF map(PointF data_point) const { + return map(data_point.x, data_point.y); + } + + [[nodiscard]] double domain_pixel(PointF screen_point) const { + return domain.orientation == Orientation::Horizontal ? screen_point.x : screen_point.y; + } + + [[nodiscard]] double value_pixel(PointF screen_point) const { + return value.orientation == Orientation::Horizontal ? screen_point.x : screen_point.y; + } +}; + +struct Axis_Basis_2D { + PointF origin{}; + PointF domain_vector{}; + PointF value_vector{}; + + [[nodiscard]] static Axis_Basis_2D from_ranges(const Axis_Mapping_2D& mapping, const Range& domain_range, const Range& value_range) { + PointF origin = mapping.map(domain_range.origin, value_range.origin); + PointF domain_target = mapping.map(domain_range.target, value_range.origin); + PointF value_target = mapping.map(domain_range.origin, value_range.target); + return Axis_Basis_2D{ + origin, + PointF{domain_target.x - origin.x, domain_target.y - origin.y}, + PointF{value_target.x - origin.x, value_target.y - origin.y}}; + } +}; + +struct Axis_Render_Access final { + static Axis_Frame_Snapshot snapshot(Abs_Axis* axis) { + const auto* s = axis->d()->render_axis_state(); + double pixel_start = static_cast(Abs_Axis_Private::pixel_start_of(s)); + return Axis_Frame_Snapshot{ + s->orientation, + Range{s->coord_start, s->coord_start + s->coord_length}, + Range{pixel_start, pixel_start + static_cast(s->pixel_size)}}; + } + static Axis_Mapping_2D mapping(Abs_Axis* domain_axis, Abs_Axis* value_axis) { + return Axis_Mapping_2D{snapshot(domain_axis), snapshot(value_axis)}; + } + static bool orientations_orthogonal(Abs_Axis* domain_axis, Abs_Axis* value_axis) { + return domain_axis && value_axis && orientation(domain_axis) != orientation(value_axis); + } + static double coord_to_pixel(Abs_Axis* axis, double coord) { + return axis->d()->render_coord_to_pixel(coord); + } + static double pixel_to_coord(Abs_Axis* axis, double pixel) { + return axis->d()->render_pixel_to_coord(pixel); + } + static Range coord_range(Abs_Axis* axis) { + return axis->d()->render_coord_range(); + } + static int pixel_size(Abs_Axis* axis) { + return axis->d()->render_pixel_size(); + } + static Orientation orientation(Abs_Axis* axis) { + return axis->d()->render_axis_state()->orientation; + } + static std::string tick_label(Abs_Axis* axis, double tick) { + return axis->d()->render_tick_label(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)); + sc->x = this->x; + sc->y = this->y; + sc->orientation = this->orientation; + sc->pixel_size = std::max(1, this->pixel_size); + sc->tick_length = this->tick_length; + sc->sub_tick_length = this->sub_tick_length; + sc->color = this->color; + sc->locale = this->locale; + sc->unit_text = this->unit_text; + sc->unit_text_font = this->unit_text_font; + sc->unit_text_pen = this->unit_text_pen; + sc->unit_text_background_brush = this->unit_text_background_brush; + sc->rotate = this->rotate; +} +} // namespace renderive diff --git a/Renderive/Axis/Axis.cpp b/Core/Axis/Axis.cpp similarity index 86% rename from Renderive/Axis/Axis.cpp rename to Core/Axis/Axis.cpp index 6fccfcf..816c3f5 100644 --- a/Renderive/Axis/Axis.cpp +++ b/Core/Axis/Axis.cpp @@ -19,8 +19,8 @@ void Axis::set_coord_range(Range range) { edit->coord_start = range.origin; 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"); +Axis::Builder::Builder(std::shared_ptr parent, Orientation orientation) { + ASSERT(parent && parent->attached_to_plot(), "Axis build error! parent invalid"); this->parent = parent; this->orientation = orientation; } diff --git a/Renderive/Axis/Axis.h b/Core/Axis/Axis.h similarity index 95% rename from Renderive/Axis/Axis.h rename to Core/Axis/Axis.h index f2deb71..44adbbc 100644 --- a/Renderive/Axis/Axis.h +++ b/Core/Axis/Axis.h @@ -9,7 +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; @@ -22,6 +21,9 @@ public: template struct Builder_T; struct Builder; + +private: + Axis_Private* d(); }; template struct Axis::Builder_T : Abs_Axis::Builder_T { @@ -53,7 +55,7 @@ struct Axis::Builder_T : Abs_Axis::Builder_T { }; struct LIB_DECL Axis::Builder : Builder_T{ /// 创建数值轴构造器。 - Builder(std::shared_ptr parent, Qt::Orientation orientation); + Builder(std::shared_ptr parent, Orientation orientation); /// 构建并返回数值轴对象。 std::shared_ptr build(); diff --git a/Renderive/Axis/Axis_p.h b/Core/Axis/Axis_p.h similarity index 66% rename from Renderive/Axis/Axis_p.h rename to Core/Axis/Axis_p.h index d2004c2..86676a5 100644 --- a/Renderive/Axis/Axis_p.h +++ b/Core/Axis/Axis_p.h @@ -1,67 +1,73 @@ #pragma once #include +#include "../event/Pointer_Event.h" +#include "../event/Wheel_Event.h" #include "Abs_Axis_p.h" #include "Axis.h" namespace renderive { struct Axis_Render_State : Abs_Axis_Render_State {}; struct Axis_Input_Data : Abs_Axis_Input_Data {}; -struct Axis_Private : Typed_Render_Data_Base { +struct Axis_Private : Typed_Render_Data_Base, Hit_Testable, Pointer_Interactive, Wheel_Interactive { bool use_wheel = false; bool use_drag = false; - bool select_test(const QPointF& pos) override { + bool select_test(const PointF& pos) override { return true; } - void wheelEvent(QWheelEvent* event) override { + void wheel_event(const Wheel_Event& event) override { if (use_wheel) { Render_Edit_Lease edit(this); - const double delta = event->angleDelta().y(); + const double delta = event.angle_delta_y; double wheel_steps = delta / 120.0; double factor = std::pow(0.8, wheel_steps); - double cursor_pixel = edit->orientation == Qt::Horizontal ? event->position().x() : event->position().y(); + double cursor_pixel = edit->orientation == Orientation::Horizontal ? event.position.x : event.position.y; double cursor_coord = pixel_to_coord(cursor_pixel, SRC::Edit); 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; edit->coord_length = new_coord_length; + event.accept(); } } bool dragging = false; - QPoint start_global_pos; + PointF start_global_pos; double start_coord_value; double pixel_pre_coord; - void mousePressEvent(QMouseEvent* event) override { + void mouse_press_event(const Pointer_Event& event) override { if (!use_drag) return; - if (event->button() != Qt::LeftButton) + if (event.button != Mouse_Button::Left) return; if (dragging) return; dragging = true; - start_global_pos = QCursor::pos(); + start_global_pos = event.global_position; Render_State_Lease state(this); start_coord_value = state->coord_start; pixel_pre_coord = state->coord_length / static_cast(state->pixel_size); + event.accept(); } - void mouseMoveEvent(QMouseEvent* event) override { + void mouse_move_event(const Pointer_Event& event) override { if (!use_drag) return; - if (!(event->buttons() & Qt::LeftButton)) + if (!(event.buttons & static_cast(Mouse_Button::Left))) return; if (!dragging) return; Render_Edit_Lease edit(this); - QPoint delta = start_global_pos - QCursor::pos(); - auto coord_delta = pixel_pre_coord * (edit->orientation == Qt::Horizontal ? delta.x() : delta.y()); + PointF delta{start_global_pos.x - event.global_position.x, start_global_pos.y - event.global_position.y}; + auto coord_delta = pixel_pre_coord * (edit->orientation == Orientation::Horizontal ? delta.x : delta.y); edit->coord_start = start_coord_value + coord_delta; + event.accept(); } - void mouseReleaseEvent(QMouseEvent* event) override { + void mouse_release_event(const Pointer_Event& event) override { if (!use_drag) return; - if (event->button() != Qt::LeftButton) + if (event.button != Mouse_Button::Left) return; if (!dragging) return; dragging = false; + event.accept(); } }; template diff --git a/Renderive/Axis/Frequency_Axis.cpp b/Core/Axis/Frequency_Axis.cpp similarity index 81% rename from Renderive/Axis/Frequency_Axis.cpp rename to Core/Axis/Frequency_Axis.cpp index 4bb99d4..7388412 100644 --- a/Renderive/Axis/Frequency_Axis.cpp +++ b/Core/Axis/Frequency_Axis.cpp @@ -3,8 +3,8 @@ #include "../base/Memory.h" 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"); +Frequency_Axis::Builder::Builder(std::shared_ptr parent, Orientation orientation) { + ASSERT(parent && parent->attached_to_plot(), "Frequency_Axis build error! parent invalid"); this->parent = parent; this->orientation = orientation; } diff --git a/Renderive/Axis/Frequency_Axis.h b/Core/Axis/Frequency_Axis.h similarity index 89% rename from Renderive/Axis/Frequency_Axis.h rename to Core/Axis/Frequency_Axis.h index 01c47a2..70041c3 100644 --- a/Renderive/Axis/Frequency_Axis.h +++ b/Core/Axis/Frequency_Axis.h @@ -7,16 +7,18 @@ struct Frequency_Axis_Private; class LIB_DECL Frequency_Axis : public Axis { public: /// 返回私有渲染数据。 - 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; + +private: + Frequency_Axis_Private* d(); }; struct LIB_DECL Frequency_Axis::Builder : Builder_T{ /// 创建频率轴构造器。 - Builder(std::shared_ptr parent, Qt::Orientation orientation); + Builder(std::shared_ptr parent, Orientation orientation); /// 构建并返回频率轴对象。 std::shared_ptr build(); diff --git a/Renderive/Axis/Frequency_Axis_p.h b/Core/Axis/Frequency_Axis_p.h similarity index 63% rename from Renderive/Axis/Frequency_Axis_p.h rename to Core/Axis/Frequency_Axis_p.h index 1c6ec12..d61152e 100644 --- a/Renderive/Axis/Frequency_Axis_p.h +++ b/Core/Axis/Frequency_Axis_p.h @@ -1,4 +1,7 @@ #pragma once +#include +#include +#include #include "Abs_Axis_p.h" #include "Axis_p.h" #include "Frequency_Axis.h" @@ -6,11 +9,11 @@ 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, SRC src) override { const int exponent = unit_exponent(ticks); if (!ticks.empty()) render_state()->unit_text = "Frequency/" + unit_text(exponent); - std::vector result; + 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)); @@ -19,7 +22,7 @@ struct Frequency_Axis_Private : Typed_Render_Data_Base& ticks) { if (ticks.empty()) return 0; - const double range = qAbs(ticks.back() - ticks.front()); + const double range = std::abs(ticks.back() - ticks.front()); if (range >= 1e9) return 9; if (range >= 1e6) @@ -28,7 +31,7 @@ struct Frequency_Axis_Private : Typed_Render_Data_Basenumber_precision; - int dot_index = value.indexOf('.'); - if (dot_index == -1) - dot_index = value.size(); - QString number = value.left(dot_index) + value.mid(dot_index + 1); - int after_dot_index = dot_index - exponent; + auto dot_pos = value.find('.'); + if (dot_pos == std::string::npos) + dot_pos = value.size(); + std::string number = value.substr(0, dot_pos) + value.substr(dot_pos + 1); + int after_dot_index = static_cast(dot_pos) - exponent; if (after_dot_index < 1) { for (int i = 0; i < 1 - after_dot_index; ++i) - number.prepend('0'); + number.insert(0, 1, '0'); after_dot_index = 1; } - number.insert(after_dot_index, '.'); - return QString::number(number.toDouble(), 'd', precision); + number.insert(after_dot_index, "."); + return format_number(std::stod(number), {.precision = precision}); } }; } // namespace renderive diff --git a/Renderive/Axis/Time_Axis.cpp b/Core/Axis/Time_Axis.cpp similarity index 64% rename from Renderive/Axis/Time_Axis.cpp rename to Core/Axis/Time_Axis.cpp index 08063e7..fbf4063 100644 --- a/Renderive/Axis/Time_Axis.cpp +++ b/Core/Axis/Time_Axis.cpp @@ -6,39 +6,30 @@ int Time_Axis::time_point_size() { return d()->edit_state_value(&Time_Axis_Render_State::time_point_size); } void Time_Axis::set_time_point_size(int value) { - value = qMax(1, value); - if (auto stream = timeline_stream()) - stream->set_time_point_size(value); + value = std::max(1, value); d()->set_state_value(&Time_Axis_Render_State::time_point_size, value); } int Time_Axis::tick_pixel_space() { return d()->edit_state_value(&Time_Axis_Render_State::tick_pixel_space); } void Time_Axis::set_tick_pixel_space(int value) { - d()->set_state_value(&Time_Axis_Render_State::tick_pixel_space, qMax(1, value)); + d()->set_state_value(&Time_Axis_Render_State::tick_pixel_space, std::max(1, value)); } -RENDERIVE_DEFINE_STATE_PROP(Time_Axis, Time_Axis_Render_State, QString, time_format) -RENDERIVE_DEFINE_STATE_PROP(Time_Axis, Time_Axis_Render_State, QFont, font) +RENDERIVE_DEFINE_STATE_PROP(Time_Axis, Time_Axis_Render_State, std::string, time_format) +RENDERIVE_DEFINE_STATE_PROP(Time_Axis, Time_Axis_Render_State, Font, font) std::shared_ptr Time_Axis::timeline_stream() { return d()->edit_state_value(&Time_Axis_Render_State::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_start_coord_to_end_coord(start_coord_to_end_coord()); - } d()->set_state_value(&Time_Axis_Render_State::timeline_stream, std::move(value)); } bool Time_Axis::start_coord_to_end_coord() { return d()->edit_state_value(&Time_Axis_Render_State::start_coord_to_end_coord); } void Time_Axis::set_start_coord_to_end_coord(bool value) { - if (auto stream = timeline_stream()) - stream->set_start_coord_to_end_coord(value); d()->set_state_value(&Time_Axis_Render_State::start_coord_to_end_coord, value); } -int Time_Axis::give_data(QTime time) { +int Time_Axis::give_data(Time_Of_Day time) { if (!ok()) return -1; auto stream = timeline_stream(); @@ -48,14 +39,14 @@ int Time_Axis::give_data(QTime time) { mark_render_dirty(); return tick; } -QTime Time_Axis::tick_to_time(int tick) { +Time_Of_Day Time_Axis::tick_to_time(int tick) { auto stream = timeline_stream(); if (stream) - return stream->capture().time_by_tick(tick); + return stream->capture(time_point_size(), d()->state_data(SRC::Edit)->get_tick_space_hint(), start_coord_to_end_coord()).time_by_tick(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"); +Time_Axis::Builder::Builder(std::shared_ptr parent, Orientation orientation) { + ASSERT(parent && parent->attached_to_plot(), "Time_Axis build error! parent invalid"); this->parent = parent; this->orientation = orientation; } @@ -66,17 +57,14 @@ std::shared_ptr Time_Axis::Builder::build() { Time_Axis_Render_State* sc = reinterpret_cast(pd->state(State_Edit)); PROP_R(int, time_point_size) PROP_R(int, tick_pixel_space) - PROP_R(QString, time_format) - PROP_R(QFont, font) + PROP_R(std::string, time_format) + PROP_R(Font, font) PROP_R(std::shared_ptr, timeline_stream) PROP_R(bool, start_coord_to_end_coord) - sc->time_point_size = qMax(1, sc->time_point_size); - sc->tick_pixel_space = qMax(1, sc->tick_pixel_space); + sc->time_point_size = std::max(1, sc->time_point_size); + sc->tick_pixel_space = std::max(1, sc->tick_pixel_space); if (!sc->timeline_stream) sc->timeline_stream = renderive::make_shared(); - sc->timeline_stream->set_time_point_size(sc->time_point_size); - sc->timeline_stream->set_tick_space(sc->get_tick_space_hint()); - sc->timeline_stream->set_start_coord_to_end_coord(sc->start_coord_to_end_coord); return ret; } } // namespace renderive diff --git a/Renderive/Axis/Time_Axis.h b/Core/Axis/Time_Axis.h similarity index 74% rename from Renderive/Axis/Time_Axis.h rename to Core/Axis/Time_Axis.h index 6f7fce1..b52f940 100644 --- a/Renderive/Axis/Time_Axis.h +++ b/Core/Axis/Time_Axis.h @@ -1,35 +1,41 @@ #pragma once #include "Abs_Axis.h" +#include "../base/Time_Of_Day.h" #include +#include namespace renderive { struct Time_Axis_Private; +struct Time_Axis_Render_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) - PROP(QString, time_format) - PROP(QFont, font) + PROP(std::string, time_format) + PROP(Font, font) PROP(std::shared_ptr, timeline_stream) PROP(bool, start_coord_to_end_coord) /// 推入一个新的时间点并返回对应 tick。 - int give_data(QTime time); + int give_data(Time_Of_Day time); /// 将 tick 转换成时间。 - QTime tick_to_time(int tick); + Time_Of_Day tick_to_time(int tick); struct Builder; + +private: + friend struct Time_Axis_Render_Access; + Time_Axis_Private* d(); }; struct Time_Axis_Prop { int time_point_size = 100; int tick_pixel_space = 8; - QString time_format = "mm:ss.zzz"; - QFont font; + std::string time_format = "mm:ss.zzz"; + Font font; std::shared_ptr timeline_stream; bool start_coord_to_end_coord = false; }; @@ -38,12 +44,12 @@ public Time_Axis_Prop { PROP_B(int, time_point_size) PROP_B(int, tick_pixel_space) - PROP_B(QString, time_format) - PROP_B(QFont, font) + PROP_B(std::string, time_format) + PROP_B(Font, font) PROP_B(std::shared_ptr, timeline_stream) PROP_B(bool, start_coord_to_end_coord) /// 创建时间轴构造器。 - Builder(std::shared_ptr parent, Qt::Orientation orientation); + Builder(std::shared_ptr parent, Orientation orientation); /// 构建并返回时间轴对象。 std::shared_ptr build(); }; diff --git a/Renderive/Axis/Time_Axis_p.h b/Core/Axis/Time_Axis_p.h similarity index 51% rename from Renderive/Axis/Time_Axis_p.h rename to Core/Axis/Time_Axis_p.h index 3ab1571..ae802c1 100644 --- a/Renderive/Axis/Time_Axis_p.h +++ b/Core/Axis/Time_Axis_p.h @@ -1,64 +1,61 @@ #pragma once -#include #include #include +#include #include #include "../architecture/Plot_Render_Context.h" #include "../architecture/Timeline_Stream.h" #include "../base/Memory.h" +#include "../base/String_Format.h" +#include "../base/Time_Of_Day.h" +#include "../render/Canvas.h" #include "Abs_Axis_p.h" #include "Time_Axis.h" namespace renderive { struct Time_Axis_Input_Data : Abs_Axis_Input_Data {}; struct Time_Axis_Render_State : Abs_Axis_Render_State, Time_Axis_Prop { int get_tick_space_hint() const { - QFontMetrics fm(font); double rate = (double)time_point_size / (double)pixel_size; - double ret = rate * (tick_pixel_space + (orientation == Qt::Horizontal ? fm.horizontalAdvance(time_format) : fm.height())); + double ret = rate * (tick_pixel_space + (double)(orientation == Orientation::Horizontal ? 80 : 20)); return std::max(1, (int)ret); } }; struct Time_Axis_Private : Typed_Render_Data_Base { std::shared_ptr timeline_snapshot; std::vector ticks; - QTime time_by_tick(int tick) const { - return timeline_snapshot ? timeline_snapshot->time_by_tick(tick) : QTime{}; + Time_Of_Day time_by_tick(int tick) const { + return timeline_snapshot ? timeline_snapshot->time_by_tick(tick) : Time_Of_Day{}; + } + std::shared_ptr capture_timeline(const Plot_Render_Snapshot& snapshot, const Time_Axis_Render_State* state) const { + if (!state || !state->timeline_stream) + return {}; + return snapshot.timeline_snapshot(state->timeline_stream, state->time_point_size, state->get_tick_space_hint(), state->start_coord_to_end_coord); + } + std::shared_ptr capture_timeline(const Plot_Render_Snapshot& snapshot) { + sync_state_pipeline(); + return capture_timeline(snapshot, render_state()); } void prepare_data(const Plot_Render_Snapshot& snapshot) override { sync_state_pipeline(); auto s = render_state(); - auto stream = s->timeline_stream; - if (!stream) - return; - Triple_Buffer_Lease state_lease = state_control.wait_mark_use_role(State_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); - stream->set_tick_space(tick_space); - stream->set_start_coord_to_end_coord(s->start_coord_to_end_coord); - auto captured = snapshot.timeline_snapshot(stream); + auto captured = capture_timeline(snapshot, s); if (captured) timeline_snapshot = std::move(captured); - if (!timeline_snapshot) { - state_control.unmark_use(state_lease); + if (!timeline_snapshot) return; - } s->coord_start = timeline_snapshot->lower; s->coord_length = timeline_snapshot->time_point_size; - sc->coord_start = timeline_snapshot->lower; - sc->coord_length = timeline_snapshot->time_point_size; ticks.clear(); for (const Timeline_Tick& tick : timeline_snapshot->ticks) { ticks.push_back(tick.tick); } - state_control.unmark_use(state_lease); } - std::vector create_label_vector(const std::vector& ticks, SRC src) override { + std::vector create_label_vector(const std::vector& ticks, SRC src) override { auto s = reinterpret_cast(state(src)); - std::vector result; + 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(format_time(time_by_tick((int)tick), s->time_format)); return result; } std::vector create_tick_vector(double tick_step, const Range& range) override { @@ -67,12 +64,17 @@ 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 draw(Canvas& canvas, const Plot_Render_Snapshot& snapshot) override { + Abs_Axis_Private::draw(canvas, snapshot); } - QString get_tick_label(double tick, SRC src) override { + std::string 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); + return format_time(time_by_tick((int)tick), render_state->time_format); + } +}; +struct Time_Axis_Render_Access final { + static std::shared_ptr capture_timeline(Time_Axis* axis, const Plot_Render_Snapshot& snapshot) { + return axis ? axis->d()->capture_timeline(snapshot) : std::shared_ptr{}; } }; } diff --git a/Renderive/Axis/export.h b/Core/Axis/export.h similarity index 100% rename from Renderive/Axis/export.h rename to Core/Axis/export.h diff --git a/Renderive/Axis/global.h b/Core/Axis/global.h similarity index 100% rename from Renderive/Axis/global.h rename to Core/Axis/global.h diff --git a/Renderive/architecture/Bounded_Input_Buffer.h b/Core/architecture/Bounded_Input_Buffer.h similarity index 98% rename from Renderive/architecture/Bounded_Input_Buffer.h rename to Core/architecture/Bounded_Input_Buffer.h index bac345e..cf84015 100644 --- a/Renderive/architecture/Bounded_Input_Buffer.h +++ b/Core/architecture/Bounded_Input_Buffer.h @@ -9,7 +9,7 @@ #include #include #include "../base/Memory.h" -#include "Core/Base/RingBuffer.hpp" +#include "Psc_Cpp_Core/Base/RingBuffer.hpp" namespace renderive { class Bounded_Input_Buffer { public: diff --git a/Renderive/architecture/Buffer_Role.h b/Core/architecture/Buffer_Role.h similarity index 100% rename from Renderive/architecture/Buffer_Role.h rename to Core/architecture/Buffer_Role.h diff --git a/Core/architecture/Frame_Scheduler.cpp b/Core/architecture/Frame_Scheduler.cpp new file mode 100644 index 0000000..675d0dc --- /dev/null +++ b/Core/architecture/Frame_Scheduler.cpp @@ -0,0 +1,278 @@ +#include "Frame_Scheduler.h" +#include +#include +#include + +namespace renderive { +namespace { +constexpr double Ratio_Threshold = 1.03; +constexpr int Switch_Samples = 3; +} + +void Smoothed_Interval::update(double sample_ms) { + if (sample_ms <= 0.0 || !std::isfinite(sample_ms)) + return; + if (!sample_count) { + srtt_ms = sample_ms; + jitter_ms = sample_ms * 0.5; + sample_count = 1; + return; + } + double error = std::abs(sample_ms - srtt_ms); + jitter_ms += 0.25 * (error - jitter_ms); + srtt_ms += 0.125 * (sample_ms - srtt_ms); + ++sample_count; +} + +double Smoothed_Interval::safe_ms(double jitter_factor) const { + return sample_count ? srtt_ms + jitter_factor * jitter_ms : 0.0; +} + +bool Smoothed_Interval::valid() const { + return sample_count > 0; +} + +void Frame_Lifecycle_Record::record_renderable_render(const std::string& object_name, const std::string& parent_object_name, int tree_depth, Renderable_Cache_Mode cache_mode, std::uint64_t render_time_ns) { + Renderable_Frame_Stat& stat = renderable_stat(object_name, parent_object_name, tree_depth); + stat.cache_enabled = cache_mode == Renderable_Cache_Mode::Local_Pixel; + ++stat.render_count; + stat.total_render_time_ns += render_time_ns; + stat.last_total_render_time_ns = render_time_ns; +} + +void Frame_Lifecycle_Record::record_renderable_self_draw(const std::string& object_name, const std::string& parent_object_name, int tree_depth, Renderable_Cache_Mode cache_mode, std::uint64_t draw_time_ns) { + Renderable_Frame_Stat& stat = renderable_stat(object_name, parent_object_name, tree_depth); + stat.cache_enabled = cache_mode == Renderable_Cache_Mode::Local_Pixel; + ++stat.self_draw_count; + stat.self_draw_time_ns += draw_time_ns; + stat.last_self_draw_time_ns = draw_time_ns; +} + +void Frame_Lifecycle_Record::record_renderable_cache_hit(const std::string& object_name, const std::string& parent_object_name, int tree_depth) { + ++renderable_cache_hit_count; + ++cache_stat(object_name, parent_object_name, tree_depth).hit_count; +} + +void Frame_Lifecycle_Record::record_renderable_cache_miss(const std::string& object_name, const std::string& parent_object_name, int tree_depth) { + ++renderable_cache_miss_count; + ++cache_stat(object_name, parent_object_name, tree_depth).miss_count; +} + +void Frame_Lifecycle_Record::record_renderable_cache_rebuild(const std::string& object_name, const std::string& parent_object_name, int tree_depth, std::uint64_t rebuild_time_ns, std::uint64_t image_bytes, std::uint64_t image_area, std::uint64_t edit_version, std::uint64_t ready_version) { + ++renderable_cache_rebuild_count; + Renderable_Cache_Frame_Stat& stat = cache_stat(object_name, parent_object_name, tree_depth); + renderable_cache_rebuild_time_ns += rebuild_time_ns; + renderable_cache_image_bytes += image_bytes; + renderable_cache_image_area += image_area; + ++stat.rebuild_count; + stat.rebuild_time_ns += rebuild_time_ns; + stat.last_rebuild_time_ns = rebuild_time_ns; + stat.last_image_bytes = image_bytes; + stat.last_image_area = image_area; + stat.last_edit_version = edit_version; + stat.last_ready_version = ready_version; +} + +void Frame_Lifecycle_Record::record_renderable_cache_compose(const std::string& object_name, const std::string& parent_object_name, int tree_depth, std::uint64_t compose_time_ns, std::uint64_t image_bytes, std::uint64_t image_area, std::uint64_t edit_version, std::uint64_t ready_version) { + renderable_cache_compose_time_ns += compose_time_ns; + Renderable_Cache_Frame_Stat& stat = cache_stat(object_name, parent_object_name, tree_depth); + stat.compose_time_ns += compose_time_ns; + stat.last_compose_time_ns = compose_time_ns; + stat.last_image_bytes = image_bytes; + stat.last_image_area = image_area; + stat.last_edit_version = edit_version; + stat.last_ready_version = ready_version; +} + +Renderable_Frame_Stat& Frame_Lifecycle_Record::renderable_stat(const std::string& object_name, const std::string& parent_object_name, int tree_depth) { + for (auto& stat : renderable_stats) { + if (stat.object_name == object_name && stat.parent_object_name == parent_object_name && stat.tree_depth == tree_depth) + return stat; + } + Renderable_Frame_Stat stat; + stat.object_name = object_name; + stat.parent_object_name = parent_object_name; + stat.tree_depth = tree_depth; + renderable_stats.push_back(std::move(stat)); + return renderable_stats.back(); +} + +Renderable_Cache_Frame_Stat& Frame_Lifecycle_Record::cache_stat(const std::string& object_name, const std::string& parent_object_name, int tree_depth) { + for (auto& stat : renderable_cache_stats) { + if (stat.object_name == object_name && stat.parent_object_name == parent_object_name && stat.tree_depth == tree_depth) + return stat; + } + Renderable_Cache_Frame_Stat stat; + stat.object_name = object_name; + stat.parent_object_name = parent_object_name; + stat.tree_depth = tree_depth; + renderable_cache_stats.push_back(std::move(stat)); + return renderable_cache_stats.back(); +} + +void Frame_Feedback_Controller::reset() { + std::uint64_t user_period = state_value.user_period_ns; + *this = Frame_Feedback_Controller(); + state_value.user_period_ns = user_period; + refresh_control_snapshot(); +} + +void Frame_Feedback_Controller::set_max_render_fps(double fps) { + state_value.user_period_ns = fps > 0.0 ? ms_to_ns(1000.0 / fps) : 0; + refresh_control_snapshot(); +} + +void Frame_Feedback_Controller::on_input_changed(std::uint64_t, std::uint64_t input_version) { + if (input_version) + latest_input_version = input_version; + else + ++latest_input_version; +} + +void Frame_Feedback_Controller::on_render_started(Frame_Lifecycle_Record& frame, std::uint64_t now_ns) { + last_render_begin_ns = now_ns; + frame.render_begin_ns = now_ns; + frame.wait_21_ns = now_ns > frame.render_role_enter_ns ? now_ns - frame.render_role_enter_ns : 0; + refresh_control_snapshot(); + write_state(frame); +} + +void Frame_Feedback_Controller::on_render_completed(Frame_Lifecycle_Record& frame) { + std::uint64_t render_duration_ns = frame.render_end_ns > frame.render_begin_ns ? frame.render_end_ns - frame.render_begin_ns : 0; + last_render_begin_ns = frame.render_begin_ns; + render_period.update(static_cast(render_duration_ns + frame.wait_21_ns) / 1000000.0); + refresh_control_snapshot(); + write_state(frame); +} + +void Frame_Feedback_Controller::on_frame_presented(Frame_Lifecycle_Record& frame) { + std::uint64_t paint_duration_ns = frame.paint_end_ns > frame.paint_begin_ns ? frame.paint_end_ns - frame.paint_begin_ns : 0; + paint_period.update(static_cast(paint_duration_ns + frame.wait_23_ns) / 1000000.0); + frame.outcome = Frame_Outcome::Presented; + refresh_control_snapshot(); + write_state(frame); +} + +void Frame_Feedback_Controller::on_frame_superseded(Frame_Lifecycle_Record& frame) { + frame.outcome = Frame_Outcome::Superseded; + refresh_control_snapshot(); + write_state(frame); +} + +void Frame_Feedback_Controller::on_frame_dropped(Frame_Lifecycle_Record& frame, Frame_Outcome outcome) { + frame.outcome = outcome; + refresh_control_snapshot(); + write_state(frame); +} + +bool Frame_Feedback_Controller::rate_limit_ready(std::uint64_t now_ns) const { + return now_ns >= next_render_time; +} + +std::uint64_t Frame_Feedback_Controller::next_render_time_ns() const { + return next_render_time; +} + +const Refresh_Control_Snapshot& Frame_Feedback_Controller::state() const { + return state_value; +} + +void Frame_Feedback_Controller::write_state(Frame_Lifecycle_Record& frame) const { + frame.refresh = state_value; +} + +void Frame_Feedback_Controller::refresh_control_snapshot() { + std::uint64_t render_period_ns = render_period.valid() ? ms_to_ns(render_period.safe_ms(0.5)) : 0; + std::uint64_t paint_period_ns = paint_period.valid() ? ms_to_ns(paint_period.safe_ms(0.5)) : 0; + std::uint64_t user_period_ns = state_value.user_period_ns; + Refresh_Limit_Source candidate = classify_limit_source(render_period_ns, paint_period_ns, user_period_ns); + std::uint64_t candidate_period = 0; + switch (candidate) { + case Refresh_Limit_Source::Render: + candidate_period = render_period_ns; + break; + case Refresh_Limit_Source::Paint: + candidate_period = paint_period_ns; + break; + case Refresh_Limit_Source::User: + candidate_period = user_period_ns; + break; + case Refresh_Limit_Source::Unknown: + break; + } + update_limit_source(candidate, candidate_period); + std::uint64_t target_period = std::max({render_period_ns, paint_period_ns, user_period_ns}); + state_value.render_period_ns = render_period_ns; + state_value.paint_period_ns = paint_period_ns; + state_value.target_period_ns = target_period; + if (state_value.source == Refresh_Limit_Source::Unknown) + state_value.source = candidate; + if (state_value.source == Refresh_Limit_Source::User && last_render_begin_ns) + next_render_time = last_render_begin_ns + user_period_ns; + else + next_render_time = 0; +} + +Refresh_Limit_Source Frame_Feedback_Controller::classify_limit_source(std::uint64_t render_period_ns, std::uint64_t paint_period_ns, std::uint64_t user_period_ns) const { + if (!render_period_ns && !paint_period_ns) + return user_period_ns ? Refresh_Limit_Source::User : Refresh_Limit_Source::Unknown; + std::uint64_t max_period = std::max({render_period_ns, paint_period_ns, user_period_ns}); + if (max_period == user_period_ns && user_period_ns) + return Refresh_Limit_Source::User; + if (max_period == paint_period_ns && paint_period_ns) + return Refresh_Limit_Source::Paint; + if (max_period == render_period_ns && render_period_ns) + return Refresh_Limit_Source::Render; + return Refresh_Limit_Source::Unknown; +} + +void Frame_Feedback_Controller::update_limit_source(Refresh_Limit_Source candidate, std::uint64_t candidate_period_ns) { + if (candidate == Refresh_Limit_Source::Unknown) + return; + if (state_value.source == Refresh_Limit_Source::Unknown) { + state_value.source = candidate; + candidate_source = candidate; + candidate_count = 0; + return; + } + std::uint64_t current_period = 0; + switch (state_value.source) { + case Refresh_Limit_Source::Render: + current_period = state_value.render_period_ns; + break; + case Refresh_Limit_Source::Paint: + current_period = state_value.paint_period_ns; + break; + case Refresh_Limit_Source::User: + current_period = state_value.user_period_ns; + break; + case Refresh_Limit_Source::Unknown: + break; + } + if (candidate == state_value.source || (current_period && static_cast(candidate_period_ns) <= static_cast(current_period) * Ratio_Threshold)) { + candidate_source = candidate; + candidate_count = 0; + return; + } + if (candidate != candidate_source) { + candidate_source = candidate; + candidate_count = 1; + return; + } + if (++candidate_count >= Switch_Samples) { + state_value.source = candidate; + candidate_count = 0; + } +} + +double Frame_Feedback_Controller::duration_ms(std::uint64_t begin_ns, std::uint64_t end_ns) { + return end_ns > begin_ns ? static_cast(end_ns - begin_ns) / 1000000.0 : 0.0; +} + +std::uint64_t Frame_Feedback_Controller::ms_to_ns(double ms) { + if (ms <= 0.0 || !std::isfinite(ms)) + return 0; + return static_cast(ms * 1000000.0); +} + +} // namespace renderive diff --git a/Core/architecture/Frame_Scheduler.h b/Core/architecture/Frame_Scheduler.h new file mode 100644 index 0000000..293e26f --- /dev/null +++ b/Core/architecture/Frame_Scheduler.h @@ -0,0 +1,156 @@ +#pragma once +#include "Render_Config.h" +#include +#include +#include + +namespace renderive { + +struct Smoothed_Interval { + double srtt_ms{}; + double jitter_ms{}; + std::uint64_t sample_count{}; + + void update(double sample_ms); + [[nodiscard]] double safe_ms(double jitter_factor = 2.0) const; + [[nodiscard]] bool valid() const; +}; + +struct Refresh_Control_Snapshot { + Refresh_Limit_Source source = Refresh_Limit_Source::Unknown; + std::uint64_t render_period_ns{}; + std::uint64_t paint_period_ns{}; + std::uint64_t user_period_ns{}; + std::uint64_t target_period_ns{}; +}; + +struct Renderable_Cache_Frame_Stat { + std::string object_name; + std::string parent_object_name; + int tree_depth{}; + std::uint64_t hit_count{}; + std::uint64_t miss_count{}; + std::uint64_t rebuild_count{}; + std::uint64_t rebuild_time_ns{}; + std::uint64_t compose_time_ns{}; + std::uint64_t last_rebuild_time_ns{}; + std::uint64_t last_compose_time_ns{}; + std::uint64_t last_image_bytes{}; + std::uint64_t last_image_area{}; + std::uint64_t last_edit_version{}; + std::uint64_t last_ready_version{}; +}; + +struct Renderable_Frame_Stat { + std::string object_name; + std::string parent_object_name; + int tree_depth{}; + bool cache_enabled{}; + std::uint64_t render_count{}; + std::uint64_t self_draw_count{}; + std::uint64_t total_render_time_ns{}; + std::uint64_t self_draw_time_ns{}; + std::uint64_t last_total_render_time_ns{}; + std::uint64_t last_self_draw_time_ns{}; +}; + +struct Frame_Lifecycle_Record { + std::uint64_t frame_id{}; + std::uint64_t input_version{}; + std::uint64_t core_data_time_ns{}; + std::uint64_t render_request_ns{}; + std::uint64_t render_role_enter_ns{}; + std::uint64_t render_begin_ns{}; + std::uint64_t prepare_begin_ns{}; + std::uint64_t prepare_end_ns{}; + std::uint64_t draw_begin_ns{}; + std::uint64_t draw_end_ns{}; + std::uint64_t render_end_ns{}; + std::uint64_t transition_12_ns{}; + std::uint64_t update_request_id{}; + std::uint64_t update_request_time_ns{}; + std::uint64_t transition_23_ns{}; + std::uint64_t paint_begin_ns{}; + std::uint64_t paint_end_ns{}; + std::uint64_t transition_32_ns{}; + std::uint64_t wait_21_ns{}; + std::uint64_t wait_12_ns{}; + std::uint64_t wait_23_ns{}; + std::uint64_t wait_32_ns{}; + std::uint64_t superseded_time_ns{}; + Frame_Outcome outcome = Frame_Outcome::Render_Acquire_Dropped; + Refresh_Control_Snapshot refresh; + std::uint64_t raw_input_count{}; + std::uint64_t prepared_output_count{}; + std::uint64_t renderable_cache_hit_count{}; + std::uint64_t renderable_cache_miss_count{}; + std::uint64_t renderable_cache_rebuild_count{}; + std::uint64_t renderable_cache_rebuild_time_ns{}; + std::uint64_t renderable_cache_compose_time_ns{}; + std::uint64_t renderable_cache_image_bytes{}; + std::uint64_t renderable_cache_image_area{}; + std::uint64_t pixel_width{}; + std::uint64_t pixel_height{}; + std::vector renderable_stats; + std::vector renderable_cache_stats; + + void record_renderable_render(const std::string& object_name, const std::string& parent_object_name, int tree_depth, Renderable_Cache_Mode cache_mode, std::uint64_t render_time_ns); + void record_renderable_self_draw(const std::string& object_name, const std::string& parent_object_name, int tree_depth, Renderable_Cache_Mode cache_mode, std::uint64_t draw_time_ns); + void record_renderable_cache_hit(const std::string& object_name, const std::string& parent_object_name, int tree_depth); + void record_renderable_cache_miss(const std::string& object_name, const std::string& parent_object_name, int tree_depth); + void record_renderable_cache_rebuild(const std::string& object_name, const std::string& parent_object_name, int tree_depth, std::uint64_t rebuild_time_ns, std::uint64_t image_bytes, std::uint64_t image_area, std::uint64_t edit_version, std::uint64_t ready_version); + void record_renderable_cache_compose(const std::string& object_name, const std::string& parent_object_name, int tree_depth, std::uint64_t compose_time_ns, std::uint64_t image_bytes, std::uint64_t image_area, std::uint64_t edit_version, std::uint64_t ready_version); + +private: + Renderable_Frame_Stat& renderable_stat(const std::string& object_name, const std::string& parent_object_name, int tree_depth); + Renderable_Cache_Frame_Stat& cache_stat(const std::string& object_name, const std::string& parent_object_name, int tree_depth); +}; + +inline std::uint64_t renderable_cache_subtree_compose_time(const std::vector& stats, const Renderable_Cache_Frame_Stat& node) { + std::uint64_t compose_time_ns = node.compose_time_ns; + for (const auto& stat : stats) { + if (stat.parent_object_name == node.object_name && stat.tree_depth == node.tree_depth + 1) + compose_time_ns += renderable_cache_subtree_compose_time(stats, stat); + } + return compose_time_ns; +} + +struct Frame_Attempt_Accumulator { + std::uint64_t render_role_busy_count{}; + std::uint64_t publish_dropped_count{}; + std::uint64_t paint_role_busy_count{}; +}; + +class Frame_Feedback_Controller { +public: + void reset(); + void set_max_render_fps(double fps); + void on_input_changed(std::uint64_t now_ns, std::uint64_t input_version = 0); + void on_render_started(Frame_Lifecycle_Record& frame, std::uint64_t now_ns); + void on_render_completed(Frame_Lifecycle_Record& frame); + void on_frame_presented(Frame_Lifecycle_Record& frame); + void on_frame_superseded(Frame_Lifecycle_Record& frame); + void on_frame_dropped(Frame_Lifecycle_Record& frame, Frame_Outcome outcome); + [[nodiscard]] bool rate_limit_ready(std::uint64_t now_ns) const; + [[nodiscard]] std::uint64_t next_render_time_ns() const; + [[nodiscard]] const Refresh_Control_Snapshot& state() const; + void write_state(Frame_Lifecycle_Record& frame) const; + +private: + void refresh_control_snapshot(); + [[nodiscard]] Refresh_Limit_Source classify_limit_source(std::uint64_t render_period_ns, std::uint64_t paint_period_ns, std::uint64_t user_period_ns) const; + void update_limit_source(Refresh_Limit_Source candidate, std::uint64_t candidate_period_ns); + static double duration_ms(std::uint64_t begin_ns, std::uint64_t end_ns); + static std::uint64_t ms_to_ns(double ms); + + Refresh_Control_Snapshot state_value; + Smoothed_Interval render_period; + Smoothed_Interval paint_period; + Refresh_Limit_Source candidate_source = Refresh_Limit_Source::Unknown; + int candidate_count{}; + std::uint64_t latest_input_version{}; + std::uint64_t last_render_begin_ns{}; + std::uint64_t next_render_time{}; +}; + +} // namespace renderive diff --git a/Core/architecture/Plot_Render_Context.cpp b/Core/architecture/Plot_Render_Context.cpp new file mode 100644 index 0000000..111a044 --- /dev/null +++ b/Core/architecture/Plot_Render_Context.cpp @@ -0,0 +1,20 @@ +#include "Plot_Render_Context.h" +#include "Timeline_Stream.h" +#include +namespace renderive { +std::shared_ptr Plot_Render_Snapshot::timeline_snapshot(const std::shared_ptr& stream, int time_point_size, int tick_space, bool start_coord_to_end_coord) const { + if (!stream) + return {}; + time_point_size = std::max(1, time_point_size); + tick_space = std::max(1, tick_space); + if (!timeline_cache) + timeline_cache = std::make_shared(); + for (auto& item : timeline_cache->items) { + if (item.stream.lock() == stream && item.time_point_size == time_point_size && item.tick_space == tick_space && item.start_coord_to_end_coord == start_coord_to_end_coord) + return item.snapshot; + } + auto snapshot = std::make_shared(stream->capture(time_point_size, tick_space, start_coord_to_end_coord)); + timeline_cache->items.push_back({stream, time_point_size, tick_space, start_coord_to_end_coord, snapshot}); + return snapshot; +} +} diff --git a/Renderive/architecture/Plot_Render_Context.h b/Core/architecture/Plot_Render_Context.h similarity index 66% rename from Renderive/architecture/Plot_Render_Context.h rename to Core/architecture/Plot_Render_Context.h index 72126e0..59338e9 100644 --- a/Renderive/architecture/Plot_Render_Context.h +++ b/Core/architecture/Plot_Render_Context.h @@ -1,51 +1,61 @@ #pragma once -#include -#include -#include -#include -#include +#include "../base/Color.h" +#include "../base/Geometry.h" #include "Frame_Scheduler.h" +#include +#include +#include +#include +#include + namespace renderive { -class Abs_Plot; -struct Renderable; + +class Plot_Core; class Timeline_Stream; +struct Update_State; + struct Frame_Lifecycle_Observer { virtual ~Frame_Lifecycle_Observer() = default; virtual bool enabled() const = 0; virtual void collect_frame(const Frame_Lifecycle_Record& frame) = 0; - virtual Renderable* renderable() const { - return nullptr; - } }; + struct Timeline_Stream_Snapshot; struct Timeline_Stream_Cache_Item { std::weak_ptr stream; + int time_point_size = 1; + int tick_space = 1; + bool start_coord_to_end_coord = false; std::shared_ptr snapshot; }; struct Timeline_Stream_Frame_Cache { std::vector items; }; + struct Plot_Render_Context { std::atomic_bool destroying{false}; std::atomic_bool first_resize{true}; std::atomic_bool first_prepare_data{true}; std::atomic_int render_width{0}; std::atomic_int render_height{0}; - std::atomic background_rgba{0xff000000u}; - std::atomic_int refresh_times_per_second{30}; + std::atomic background_rgba{0xff000000u}; std::shared_ptr frame_lifecycle_observer; - std::atomic plot{}; + std::atomic owner{}; }; + struct Plot_Render_Snapshot { std::shared_ptr context; - QSize size; - QColor background_color = Qt::black; - int refresh_times_per_second = 30; + Size size; + Color background_color = Color::black(); + std::uint64_t frame_time_ns{}; + std::uint64_t previous_frame_time_ns{}; Frame_Lifecycle_Record* frame_metadata{}; + std::pmr::vector>* frame_update_states{}; mutable std::shared_ptr timeline_cache = std::make_shared(); bool destroying() const { return !context || context->destroying.load(std::memory_order_acquire); } - std::shared_ptr timeline_snapshot(const std::shared_ptr& stream) const; + std::shared_ptr timeline_snapshot(const std::shared_ptr& stream, int time_point_size, int tick_space, bool start_coord_to_end_coord) const; }; + } // namespace renderive diff --git a/Core/architecture/Render_Config.h b/Core/architecture/Render_Config.h new file mode 100644 index 0000000..4dfb27f --- /dev/null +++ b/Core/architecture/Render_Config.h @@ -0,0 +1,55 @@ +#pragma once +#include +namespace renderive { +enum class Refresh_Limit_Source : std::uint8_t { + Unknown, + Render, + Paint, + User +}; + +enum Frame_Role : std::uint8_t { + Frame_Rendering, + Frame_Middle, + Frame_Painting +}; + +enum class Frame_Outcome : std::uint8_t { + Presented, + Superseded, + Render_Acquire_Dropped, + Publish_Dropped, + Paint_Acquire_Dropped, + Worker_Budget_Dropped, + Cancelled +}; +/// @brief Rendering cache strategy for a renderable subtree. +enum class Renderable_Cache_Mode : std::uint8_t { + /// @brief Draw directly into the target painter each frame. + Direct, + /// @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 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. +inline bool buffer_version_newer(std::uint64_t a, std::uint64_t b) { + return static_cast(a - b) > 0; +} +} // namespace renderive diff --git a/Renderive/architecture/Render_Data.cpp b/Core/architecture/Render_Data.cpp similarity index 89% rename from Renderive/architecture/Render_Data.cpp rename to Core/architecture/Render_Data.cpp index ae0dc0b..6229a8b 100644 --- a/Renderive/architecture/Render_Data.cpp +++ b/Core/architecture/Render_Data.cpp @@ -1,18 +1,19 @@ #include "Renderable.h" +#include namespace renderive { -QDebug operator<<(QDebug debug, const Range& range) { - QDebugStateSaver saver(debug); - debug.nospace() << "Range(" << range.origin << ", " << range.target << ")"; - return debug; +std::ostream& operator<<(std::ostream& stream, const Range& range) { + stream << "Range(" << range.origin << ", " << range.target << ")"; + return stream; } Render_Edit_Guard::Render_Edit_Guard(Render_Data* data, Triple_Buffer_Lease lease) : data(data), lease(lease) {} Render_Edit_Guard::~Render_Edit_Guard() { if (!data) return; ++data->state_by_index(lease.index)->version; - if (data->q_ptr) - data->q_ptr->mark_render_dirty(); + auto q = data->q_ptr; data->unmark_state(lease); + if (q) + q->mark_render_dirty(); } void Render_Data::mark_input_dirty(Triple_Buffer_Lease lease, bool notify_render) { Input_Buffer_Slot& slot = input_slot_by_index(lease.index); diff --git a/Renderive/architecture/Render_Data.h b/Core/architecture/Render_Data.h similarity index 81% rename from Renderive/architecture/Render_Data.h rename to Core/architecture/Render_Data.h index 72ab1b1..f044ad8 100644 --- a/Renderive/architecture/Render_Data.h +++ b/Core/architecture/Render_Data.h @@ -1,35 +1,35 @@ #pragma once -#include -#include -#include +#include "../base/Geometry.h" +#include "../base/Object_Semantics.h" +#include "../event/Event.h" +#include "../event/Key_Event.h" +#include "../event/Pointer_Event.h" +#include "../event/Resize_Event.h" +#include "../event/Wheel_Event.h" +#include "../render/Canvas.h" +#include "Buffer_Role.h" +#include "Plot_Render_Context.h" +#include "Render_Config.h" +#include "Triple_Buffer.h" +#include "Update_Completion.h" +#include "global.h" #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" -#include "Triple_Buffer.h" -#include "Update_Completion.h" -#include "global.h" -class QEvent; -class QKeyEvent; -class QMouseEvent; -class QPainter; -class QPointF; -class QResizeEvent; -class QWheelEvent; + namespace renderive { + struct Render_Data; struct Renderable; + struct Input_Data { virtual ~Input_Data() = default; virtual void clear() {} }; + struct Latest_Input_Data : Input_Data { bool pending = false; void mark_latest_pushed() { @@ -39,10 +39,44 @@ struct Latest_Input_Data : Input_Data { pending = false; } }; + struct Render_State { virtual ~Render_State() = default; std::uint64_t version = 0; }; + +struct Hit_Testable { + virtual ~Hit_Testable() = default; + virtual bool select_test(const PointF& pos) = 0; +}; + +struct Pointer_Interactive { + virtual ~Pointer_Interactive() = default; + virtual void mouse_press_event(const Pointer_Event& event) = 0; + virtual void mouse_move_event(const Pointer_Event& event) = 0; + virtual void mouse_release_event(const Pointer_Event& event) = 0; +}; + +struct Wheel_Interactive { + virtual ~Wheel_Interactive() = default; + virtual void wheel_event(const Wheel_Event& event) = 0; +}; + +struct Key_Press_Interactive { + virtual ~Key_Press_Interactive() = default; + virtual void key_press_event(const Key_Event& event) = 0; +}; + +struct Key_Release_Interactive { + virtual ~Key_Release_Interactive() = default; + virtual void key_release_event(const Key_Event& event) = 0; +}; + +struct Hover_Interactive { + virtual ~Hover_Interactive() = default; + virtual void set_hover_state(Point pos, bool active) = 0; +}; + struct Render_Object_Owner : Move_Only { Render_Object_Owner() noexcept = default; Render_Object_Owner(void* pointer, std::pmr::memory_resource* resource, void (*destroy)(void*, std::pmr::memory_resource*)) noexcept : pointer(pointer), resource(resource), destroy(destroy) {} @@ -78,6 +112,7 @@ struct Render_Object_Owner : Move_Only { std::pmr::memory_resource* resource{}; void (*destroy)(void*, std::pmr::memory_resource*){}; }; + template Render_Object_Owner make_render_object(Args&&... args) { std::pmr::memory_resource* resource = memory_resource(Memory_Domain::Renderable); @@ -96,14 +131,16 @@ Render_Object_Owner make_render_object(Args&&... args) { allocator.deallocate(static_cast(object), 1); }); } + struct Input_Buffer_Slot { Input_Data* data{}; std::uint64_t version = 0; bool pending = false; std::shared_ptr completion_state; }; + struct Render_Data { - Renderable* q_ptr{}; + Renderable* q_ptr{}; Triple_Role_Buffer_Control state_control; std::array state_buffers{}; std::array state_buffer_owners{}; @@ -112,6 +149,7 @@ struct Render_Data { std::array input_buffer_owners{}; std::pmr::vector> committed_update_states{memory_resource(Memory_Domain::Update_Completion)}; std::uint64_t input_version = 0; + Render_State* state(std::uint8_t role) const { return state_by_index(state_control.read_view()[role]); } @@ -122,7 +160,7 @@ struct Render_Data { return state_buffers[index]; } Triple_Buffer_Lease mark_state_role(std::uint8_t role) { - return state_control.wait_mark_use_role(role); + return state_control.mark_use_role(role); } void unmark_state(Triple_Buffer_Lease lease) { state_control.unmark_use(lease); @@ -159,9 +197,6 @@ struct Render_Data { void clear_render_input() { clear_input_role(Input_Render); } - virtual std::size_t estimated_frame_memory(const Plot_Render_Snapshot&) { - return 0; - } void clear_input_buffers() { cancel_pending_completions(); for (std::size_t i = 0; i < input_buffers.size(); ++i) { @@ -175,47 +210,28 @@ struct Render_Data { input_control.reset(); } void cancel_pending_completions(); + public: - /// 同步 State/Input 的 ready 角色到 render 角色。 virtual void sync_state_pipeline() = 0; - /// 返回 render 角色 State。 virtual Render_State* render_state() = 0; - /// 返回 ready 角色 State。 virtual Render_State* ready_state() = 0; - /// 返回 edit 角色 State。 virtual Render_State* edit_state() = 0; - /// 返回 render 角色 Input。 virtual Input_Data* render_input_data() = 0; - /// 返回 edit 角色 Input。 virtual Input_Data* edit_input_data() = 0; virtual ~Render_Data() { clear_state_buffers(); clear_input_buffers(); } - /// 绘制当前已准备好的数据。 - virtual void draw(QPainter* painter, const Plot_Render_Snapshot& snapshot) {} - /// 准备当前帧数据,默认只同步三缓冲管线。 + + virtual void draw(Canvas& canvas, const Plot_Render_Snapshot& snapshot) {} virtual void prepare_data(const Plot_Render_Snapshot& snapshot) { sync_state_pipeline(); } - virtual QRect pixel_bounds(const QSize& plot_size) { - return QRect(QPoint(0, 0), plot_size); + virtual Rect pixel_bounds(const Size& plot_size) { + return {0, 0, plot_size.width, plot_size.height}; } - /// 判断指定坐标是否命中对象。 - virtual bool select_test(const QPointF& pos) { - return false; - } - virtual void plot_event(QEvent* event) {} - virtual void mousePressEvent(QMouseEvent* event) {} - virtual void mouseMoveEvent(QMouseEvent* event) {} - virtual void mouseReleaseEvent(QMouseEvent* event) {} - virtual void set_hover_state(const QPoint& pos, bool active) {} - virtual void keyPressEvent(QKeyEvent* event) {} - virtual void keyReleaseEvent(QKeyEvent* event) {} - virtual void first_resize_event(QResizeEvent* event) {} - virtual void resizeEvent(QResizeEvent* event) {} - virtual void wheelEvent(QWheelEvent* event) {} }; + struct Base_Render_Data : Render_Data { Render_State* render_state() override { return nullptr; @@ -234,11 +250,13 @@ struct Base_Render_Data : Render_Data { } void sync_state_pipeline() override {} }; -class Abs_Plot; + +class Plot_Core; struct LIB_DECL Render_Edit_Guard { Render_Edit_Guard(Render_Data* data, Triple_Buffer_Lease lease); ~Render_Edit_Guard(); Render_Data* data{}; Triple_Buffer_Lease lease{}; }; + } // namespace renderive diff --git a/Renderive/architecture/Render_Lease.h b/Core/architecture/Render_Lease.h similarity index 82% rename from Renderive/architecture/Render_Lease.h rename to Core/architecture/Render_Lease.h index f2b2b9d..2763f3e 100644 --- a/Renderive/architecture/Render_Lease.h +++ b/Core/architecture/Render_Lease.h @@ -1,16 +1,8 @@ #pragma once -#include -#include #include #include "Buffer_Role.h" #include "Renderable.h" namespace renderive { -template -concept Render_State_Type = std::derived_from && std::is_copy_assignable_v; -template -concept Input_Data_Type = std::derived_from; -template -concept Render_Data_Type = std::derived_from; template struct Typed_Render_Data_Base : Base { Owner* q() { @@ -63,31 +55,31 @@ struct Typed_Render_Data_Base : Base { 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.mark_use_role(State_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.mark_use_role(State_Edit); + Triple_Buffer_Lease ready_lease = this->state_control.mark_use_role(State_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) { + this->state_control.try_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])); return buffer_version_newer(ready->version, render->version); }); - this->input_control.wait_swap_role(Input_Edit, Input_Ready, [this](const Triple_Buffer_View& view) { + this->input_control.try_swap_role(Input_Edit, Input_Ready, [this](const Triple_Buffer_View& view) { const Input_Buffer_Slot& edit = this->input_slot_by_index(view[Input_Edit]); const Input_Buffer_Slot& ready = this->input_slot_by_index(view[Input_Ready]); return edit.pending && buffer_version_newer(edit.version, ready.version); }); - this->input_control.wait_swap_role(Input_Ready, Input_Render, [this](const Triple_Buffer_View& view) { + this->input_control.try_swap_role(Input_Ready, Input_Render, [this](const Triple_Buffer_View& view) { const Input_Buffer_Slot& ready = this->input_slot_by_index(view[Input_Ready]); const Input_Buffer_Slot& render = this->input_slot_by_index(view[Input_Render]); return ready.pending && buffer_version_newer(ready.version, render.version); @@ -101,7 +93,7 @@ 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; + owner->set_object_name(name); } static Data* d(Owner* owner) { return reinterpret_cast(owner->d_ptr); @@ -135,7 +127,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.mark_use_role(State_Edit)), state(reinterpret_cast(data->state_by_index(lease.index))) {} ~Render_State_Lease() { data->unmark_state(lease); } @@ -151,12 +143,13 @@ 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.mark_use_role(State_Edit)), state(reinterpret_cast(data->state_by_index(lease.index))) {} ~Render_Edit_Lease() { ++data->state_by_index(lease.index)->version; - if (data->q_ptr) - data->q_ptr->mark_render_dirty(); + auto q = data->q_ptr; data->unmark_state(lease); + if (q) + q->mark_render_dirty(); } State* operator->() { return state; @@ -170,10 +163,12 @@ 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.mark_use_role(Input_Edit)), input(reinterpret_cast(data->input_data_by_index(lease.index))) {} ~Render_Input_Lease() { - data->mark_input_dirty(lease, notify_render); + data->mark_input_dirty(lease, false); data->input_control.unmark_use(lease); + if (notify_render && data->q_ptr) + data->q_ptr->mark_render_dirty(); } Input* operator->() { return input; diff --git a/Renderive/architecture/Render_Time.h b/Core/architecture/Render_Time.h similarity index 100% rename from Renderive/architecture/Render_Time.h rename to Core/architecture/Render_Time.h diff --git a/Core/architecture/Render_Utils.cpp b/Core/architecture/Render_Utils.cpp new file mode 100644 index 0000000..15f08f6 --- /dev/null +++ b/Core/architecture/Render_Utils.cpp @@ -0,0 +1,14 @@ +#include "Renderable.h" +#include "../resource/Turbo_Color_Map.h" + +namespace renderive { + +std::vector get_turbo_color_gradient() { + std::vector colors; + colors.reserve(Turbo_Color_Map.size()); + for (const auto& c : Turbo_Color_Map) + colors.push_back(pack_rgba(c.red, c.green, c.blue, c.alpha)); + return colors; +} + +} diff --git a/Core/architecture/Renderable.cpp b/Core/architecture/Renderable.cpp new file mode 100644 index 0000000..af1a175 --- /dev/null +++ b/Core/architecture/Renderable.cpp @@ -0,0 +1,533 @@ +#include +#include +#include "../base/Memory.h" +#include "../plot/Plot_Core.h" +#include "Renderable.h" +#include "Render_Time.h" +namespace renderive { +Renderable_Cache_Mode Renderable::get_cache_mode() const { + return cache_mode.load(std::memory_order_acquire); +} +void Renderable::set_cache_mode(Renderable_Cache_Mode mode) { + cache_mode.store(mode, std::memory_order_release); + cache_dirty.store(true, std::memory_order_release); + mark_render_dirty(); +} +bool Renderable::attached_to_plot() const { + return plot != nullptr; +} +bool Renderable::shares_plot_with(const Renderable& other) const { + return plot != nullptr && plot == other.plot; +} +const std::string& Renderable::object_name() const { + return object_name_; +} +void Renderable::set_object_name(std::string name) { + object_name_ = std::move(name); +} +bool Renderable::is_visible() const { + return visible_; +} +void Renderable::set_visible(bool value) { + if (visible_ == value) + return; + visible_ = value; + mark_render_dirty(); +} +bool Renderable::should_prepare() const { + return !get_retiring() && visible_ && d_ptr; +} +void Renderable::detach_from_parent() { + if (auto parent = parent_renderable.lock()) + parent->remove_child(shared_from_this()); +} +std::string Renderable::cache_parent_object_name() const { + std::shared_ptr parent = parent_renderable.lock(); + while (parent) { + if (parent->get_cache_mode() == Renderable_Cache_Mode::Local_Pixel) + return parent->object_name_; + parent = parent->parent_renderable.lock(); + } + return {}; +} +int Renderable::cache_tree_depth() const { + int depth = 0; + std::shared_ptr parent = parent_renderable.lock(); + while (parent) { + if (parent->get_cache_mode() == Renderable_Cache_Mode::Local_Pixel) + ++depth; + parent = parent->parent_renderable.lock(); + } + return depth; +} +std::string Renderable::render_parent_object_name() const { + if (auto parent = parent_renderable.lock()) + return parent->object_name_; + return {}; +} +int Renderable::render_tree_depth() const { + int depth = 0; + std::shared_ptr parent = parent_renderable.lock(); + while (parent) { + ++depth; + parent = parent->parent_renderable.lock(); + } + return depth; +} +void Renderable::init_root(Plot_Core* plot) { + if (!plot) { + ASSERT(plot, "Renderable root init error! plot == nullptr"); + return; + } + bool initialized = this->initialized.exchange(true, std::memory_order_acq_rel); + if (initialized) { + ASSERT(!initialized, "Renderable root init error! Renderable already initialized"); + return; + } + init_common(plot); +} +void Renderable::init(const std::shared_ptr& parent) { + if (!parent || !parent->plot) { + ASSERT(parent && parent->plot, "Renderable init error! parent invalid"); + return; + } + bool initialized = this->initialized.exchange(true, std::memory_order_acq_rel); + if (initialized) { + ASSERT(!initialized, "Renderable init error! Renderable already initialized"); + return; + } + init_common(parent->plot); + parent->add_child(shared_from_this()); +} +void Renderable::init_common(Plot_Core* plot) { + this->plot = plot; + plot_context = plot->render_context(); + retiring.store(false, std::memory_order_release); + if (!d_ptr) { + 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; + 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() || get_retiring() || child->get_retiring()) { + ASSERT(child && child.get() != this && child->plot == plot && child->parent_renderable.expired() && !get_retiring() && !child->get_retiring(), "Renderable add_child error! invalid child"); + return; + } + { + std::lock_guard lock(children_mutex); + if (get_retiring()) + return; + if (std::find(children.begin(), children.end(), child) != children.end()) + return; + child->parent_renderable = shared_from_this(); + children.push_back(child); + } + // Marking dirty may render synchronously, so the child list lock must be released first. + mark_render_dirty(); +} +void Renderable::remove_child(const std::shared_ptr& child) { + if (!child || child->parent_renderable.lock().get() != this) + return; + std::shared_ptr removed; + { + std::lock_guard lock(children_mutex); + auto it = std::find(children.begin(), children.end(), child); + if (it == children.end()) + return; + removed = *it; + removed->parent_renderable.reset(); + children.erase(it); + } + removed->detach_from_plot_tree(); + mark_render_dirty(); +} +std::vector> Renderable::children_snapshot() const { + std::lock_guard lock(children_mutex); + return children; +} +void Renderable::append_tree_snapshot(Renderable_Tree_Snapshot& snapshot, const std::string& parent_object_name, int render_tree_depth, const std::string& cache_parent_object_name, int cache_tree_depth) { + std::size_t index = snapshot.size(); + Renderable_Cache_Mode mode = get_cache_mode(); + snapshot.push_back(Renderable_Tree_Node{ + shared_from_this(), + index + 1, + parent_object_name, + render_tree_depth, + cache_parent_object_name, + cache_tree_depth + }); + std::string child_cache_parent = cache_parent_object_name; + int child_cache_depth = cache_tree_depth; + if (mode == Renderable_Cache_Mode::Local_Pixel) { + child_cache_parent = object_name_; + ++child_cache_depth; + } + std::vector> children = children_snapshot(); + for (const auto& child : children) { + if (child) + child->append_tree_snapshot(snapshot, object_name_, render_tree_depth + 1, child_cache_parent, child_cache_depth); + } + snapshot[index].subtree_end = snapshot.size(); +} +std::vector> Renderable::hit_renderables(const PointF& pos) { + std::vector> hits; + append_hit_renderables(hits, pos); + return hits; +} +void Renderable::append_hit_renderables(std::vector>& hits, const PointF& pos) { + if (get_retiring() || !visible_) + return; + std::vector> children = children_snapshot(); + for (int i = static_cast(children.size()) - 1; i >= 0; --i) { + const auto& child = children.at(i); + if (child) + child->append_hit_renderables(hits, pos); + } + if (select_test(pos)) + hits.push_back(shared_from_this()); +} +void Renderable::render(Canvas& canvas, const Plot_Render_Snapshot& snapshot) { + if (get_retiring() || !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(canvas, snapshot); + else + render_direct(canvas, snapshot); + if (snapshot.frame_metadata) + snapshot.frame_metadata->record_renderable_render(object_name_, render_parent_object_name(), render_tree_depth(), mode, steady_now_ns() - begin_time); +} +void Renderable::render_tree_snapshot(Canvas& canvas, const Plot_Render_Snapshot& snapshot, const Renderable_Tree_Snapshot& tree, std::size_t index) { + render_tree_node(canvas, snapshot, tree, index); +} +void Renderable::render_direct(Canvas& canvas, const Plot_Render_Snapshot& snapshot) { + render_self(canvas, snapshot); + render_children(canvas, snapshot); +} +void Renderable::render_children(Canvas& canvas, const Plot_Render_Snapshot& snapshot) { + std::vector> children = children_snapshot(); + for (const auto& child : children) { + if (child) + child->render(canvas, snapshot); + } +} +void Renderable::render_cached(Canvas& canvas, const Plot_Render_Snapshot& snapshot) { + Size size = snapshot.size; + Rect bounds = d_ptr ? d_ptr->pixel_bounds(size).intersected(Rect{0, 0, size.width, size.height}) : Rect{0, 0, size.width, size.height}; + if (bounds.empty()) + return; + std::uint64_t edit_version = cache_edit_version.load(std::memory_order_acquire); + std::string parent_object_name = cache_parent_object_name(); + int tree_depth = cache_tree_depth(); + bool ready = !cache_dirty.load(std::memory_order_acquire) && cache_ready_version.load(std::memory_order_acquire) == edit_version && cache_image.size() == Size{bounds.width, bounds.height} && cache_bounds.x == bounds.x && cache_bounds.y == bounds.y && cache_bounds.width == bounds.width && cache_bounds.height == bounds.height; + 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(); + canvas.draw_image(RectF{static_cast(bounds.x), static_cast(bounds.y), static_cast(cache_image.width()), static_cast(cache_image.height())}, cache_image); + if (snapshot.frame_metadata) + snapshot.frame_metadata->record_renderable_cache_compose(object_name_, parent_object_name, tree_depth, steady_now_ns() - begin_time, static_cast(cache_image.size_bytes()), static_cast(bounds.width) * static_cast(bounds.height), edit_version, cache_ready_version.load(std::memory_order_acquire)); + return; + } + if (snapshot.frame_metadata) + snapshot.frame_metadata->record_renderable_cache_miss(object_name_, parent_object_name, tree_depth); + std::uint64_t rebuild_begin_time = steady_now_ns(); + render_cache_image(bounds, snapshot, edit_version); + if (snapshot.frame_metadata) + snapshot.frame_metadata->record_renderable_cache_rebuild(object_name_, parent_object_name, tree_depth, steady_now_ns() - rebuild_begin_time, cache_image.empty() ? 0 : static_cast(cache_image.size_bytes()), static_cast(bounds.width) * static_cast(bounds.height), edit_version, cache_ready_version.load(std::memory_order_acquire)); + if (!cache_image.empty()) { + std::uint64_t begin_time = steady_now_ns(); + canvas.draw_image(RectF{static_cast(bounds.x), static_cast(bounds.y), static_cast(cache_image.width()), static_cast(cache_image.height())}, cache_image); + if (snapshot.frame_metadata) + snapshot.frame_metadata->record_renderable_cache_compose(object_name_, parent_object_name, tree_depth, steady_now_ns() - begin_time, static_cast(cache_image.size_bytes()), static_cast(bounds.width) * static_cast(bounds.height), edit_version, cache_ready_version.load(std::memory_order_acquire)); + } +} +std::size_t Renderable::render_tree_node(Canvas& canvas, const Plot_Render_Snapshot& snapshot, const Renderable_Tree_Snapshot& tree, std::size_t index) { + if (index >= tree.size()) + return tree.size(); + const Renderable_Tree_Node& node = tree[index]; + Renderable* current = node.renderable.get(); + if (!current) + return std::max(index + 1, node.subtree_end); + if (current != this) + return current->render_tree_node(canvas, snapshot, tree, index); + if (get_retiring() || !visible_) + return std::max(index + 1, node.subtree_end); + Renderable_Cache_Mode mode = get_cache_mode(); + std::uint64_t begin_time = steady_now_ns(); + if (mode == Renderable_Cache_Mode::Local_Pixel) + render_cached_tree_node(canvas, snapshot, tree, index); + else + render_direct_tree_node(canvas, snapshot, tree, index); + if (snapshot.frame_metadata) + snapshot.frame_metadata->record_renderable_render(object_name_, node.parent_object_name, node.render_tree_depth, mode, steady_now_ns() - begin_time); + return std::max(index + 1, node.subtree_end); +} +void Renderable::render_direct_tree_node(Canvas& canvas, const Plot_Render_Snapshot& snapshot, const Renderable_Tree_Snapshot& tree, std::size_t index) { + render_self(canvas, snapshot, &tree[index]); + std::size_t child_index = index + 1; + while (child_index < tree[index].subtree_end && child_index < tree.size()) { + auto child = tree[child_index].renderable; + if (!child) { + ++child_index; + continue; + } + child_index = child->render_tree_node(canvas, snapshot, tree, child_index); + } +} +void Renderable::render_cached_tree_node(Canvas& canvas, const Plot_Render_Snapshot& snapshot, const Renderable_Tree_Snapshot& tree, std::size_t index) { + Size size = snapshot.size; + Rect bounds = d_ptr ? d_ptr->pixel_bounds(size).intersected(Rect{0, 0, size.width, size.height}) : Rect{0, 0, size.width, size.height}; + if (bounds.empty()) + return; + const Renderable_Tree_Node& node = tree[index]; + std::uint64_t edit_version = cache_edit_version.load(std::memory_order_acquire); + bool ready = !cache_dirty.load(std::memory_order_acquire) && cache_ready_version.load(std::memory_order_acquire) == edit_version && cache_image.size() == Size{bounds.width, bounds.height} && cache_bounds.x == bounds.x && cache_bounds.y == bounds.y && cache_bounds.width == bounds.width && cache_bounds.height == bounds.height; + if (ready) { + if (snapshot.frame_metadata) + snapshot.frame_metadata->record_renderable_cache_hit(object_name_, node.cache_parent_object_name, node.cache_tree_depth); + std::uint64_t begin_time = steady_now_ns(); + canvas.draw_image(RectF{static_cast(bounds.x), static_cast(bounds.y), static_cast(cache_image.width()), static_cast(cache_image.height())}, cache_image); + if (snapshot.frame_metadata) + snapshot.frame_metadata->record_renderable_cache_compose(object_name_, node.cache_parent_object_name, node.cache_tree_depth, steady_now_ns() - begin_time, static_cast(cache_image.size_bytes()), static_cast(bounds.width) * static_cast(bounds.height), edit_version, cache_ready_version.load(std::memory_order_acquire)); + return; + } + if (snapshot.frame_metadata) + snapshot.frame_metadata->record_renderable_cache_miss(object_name_, node.cache_parent_object_name, node.cache_tree_depth); + std::uint64_t rebuild_begin_time = steady_now_ns(); + render_cache_image_tree_node(bounds, snapshot, tree, index, edit_version); + if (snapshot.frame_metadata) + snapshot.frame_metadata->record_renderable_cache_rebuild(object_name_, node.cache_parent_object_name, node.cache_tree_depth, steady_now_ns() - rebuild_begin_time, cache_image.empty() ? 0 : static_cast(cache_image.size_bytes()), static_cast(bounds.width) * static_cast(bounds.height), edit_version, cache_ready_version.load(std::memory_order_acquire)); + if (!cache_image.empty()) { + std::uint64_t begin_time = steady_now_ns(); + canvas.draw_image(RectF{static_cast(bounds.x), static_cast(bounds.y), static_cast(cache_image.width()), static_cast(cache_image.height())}, cache_image); + if (snapshot.frame_metadata) + snapshot.frame_metadata->record_renderable_cache_compose(object_name_, node.cache_parent_object_name, node.cache_tree_depth, steady_now_ns() - begin_time, static_cast(cache_image.size_bytes()), static_cast(bounds.width) * static_cast(bounds.height), edit_version, cache_ready_version.load(std::memory_order_acquire)); + } +} +void Renderable::render_cache_image_tree_node(const Rect& bounds, const Plot_Render_Snapshot& snapshot, const Renderable_Tree_Snapshot& tree, std::size_t index, std::uint64_t version) { + if (get_retiring()) + return; + Image temp_image; + temp_image.resize(bounds.width, bounds.height); + temp_image.fill(Color::transparent()); + { + Canvas cache_canvas(temp_image); + cache_canvas.translate(static_cast(-bounds.x), static_cast(-bounds.y)); + render_direct_tree_node(cache_canvas, snapshot, tree, index); + } + cache_bounds = bounds; + this->cache_image = std::move(temp_image); + cache_ready_version.store(version, std::memory_order_release); + bool dirty = cache_edit_version.load(std::memory_order_acquire) != version; + cache_dirty.store(dirty, std::memory_order_release); +} +void Renderable::render_cache_image(const Rect& bounds, const Plot_Render_Snapshot& snapshot, std::uint64_t version) { + if (get_retiring()) + return; + Image temp_image; + temp_image.resize(bounds.width, bounds.height); + temp_image.fill(Color::transparent()); + { + Canvas cache_canvas(temp_image); + cache_canvas.translate(static_cast(-bounds.x), static_cast(-bounds.y)); + render_direct(cache_canvas, snapshot); + } + cache_bounds = bounds; + this->cache_image = std::move(temp_image); + cache_ready_version.store(version, std::memory_order_release); + bool dirty = cache_edit_version.load(std::memory_order_acquire) != version; + cache_dirty.store(dirty, std::memory_order_release); +} +void Renderable::render_self(Canvas& canvas, const Plot_Render_Snapshot& snapshot) { + render_self(canvas, snapshot, nullptr); +} +void Renderable::render_self(Canvas& canvas, const Plot_Render_Snapshot& snapshot, const Renderable_Tree_Node* node) { + if (!d_ptr) + return; + auto lease = d_ptr->mark_state_role(State_Render); + if (!lease) + return; + Renderable_Cache_Mode mode = get_cache_mode(); + std::uint64_t begin_time = steady_now_ns(); + draw(canvas, snapshot); + std::uint64_t draw_time = steady_now_ns() - begin_time; + d_ptr->unmark_state(lease); + if (snapshot.frame_metadata) { + const std::string& parent_name = node ? node->parent_object_name : render_parent_object_name(); + int depth = node ? node->render_tree_depth : render_tree_depth(); + snapshot.frame_metadata->record_renderable_self_draw(object_name_, parent_name, depth, mode, draw_time); + } +} +void Renderable::draw(Canvas& canvas, const Plot_Render_Snapshot& snapshot) { + if (get_retiring()) + return; + d_ptr->draw(canvas, snapshot); +} +void Renderable::prepare_data(const Plot_Render_Snapshot& snapshot) { + if (get_retiring()) + return; + d_ptr->prepare_data(snapshot); + if (snapshot.frame_update_states) + d_ptr->drain_committed_update_states(*snapshot.frame_update_states); +} +bool Renderable::select_test(const PointF& pos) { + if (get_retiring()) + return false; + auto* hit_testable = dynamic_cast(d_ptr); + return hit_testable && hit_testable->select_test(pos); +} +void Renderable::plot_event(const Event& event) { + if (get_retiring() || event.is_accepted()) + return; + switch (event.type) { + case Event_Type::Resize: + resize_event(static_cast(event)); + break; + case Event_Type::Pointer_Move: + mouse_move_event(static_cast(event)); + break; + case Event_Type::Pointer_Press: + mouse_press_event(static_cast(event)); + break; + case Event_Type::Pointer_Release: + mouse_release_event(static_cast(event)); + break; + case Event_Type::Wheel: + wheel_event(static_cast(event)); + break; + case Event_Type::Key_Press: + key_press_event(static_cast(event)); + break; + case Event_Type::Key_Release: + key_release_event(static_cast(event)); + break; + default: + break; + } +} +void Renderable::mouse_press_event(const Pointer_Event& event) { + if (get_retiring() || event.is_accepted()) + return; + auto* interactive = dynamic_cast(d_ptr); + if (interactive) + interactive->mouse_press_event(event); +} +void Renderable::mouse_move_event(const Pointer_Event& event) { + if (get_retiring() || event.is_accepted()) + return; + auto* interactive = dynamic_cast(d_ptr); + if (interactive) + interactive->mouse_move_event(event); +} +void Renderable::mouse_release_event(const Pointer_Event& event) { + if (get_retiring() || event.is_accepted()) + return; + auto* interactive = dynamic_cast(d_ptr); + if (interactive) + interactive->mouse_release_event(event); +} +void Renderable::set_hover_state(Point pos, bool active) { + if (get_retiring()) + return; + auto* interactive = dynamic_cast(d_ptr); + if (interactive) + interactive->set_hover_state(pos, active); +} +void Renderable::key_press_event(const Key_Event& event) { + if (get_retiring() || event.is_accepted()) + return; + auto* interactive = dynamic_cast(d_ptr); + if (interactive) + interactive->key_press_event(event); +} +void Renderable::key_release_event(const Key_Event& event) { + if (get_retiring() || event.is_accepted()) + return; + auto* interactive = dynamic_cast(d_ptr); + if (interactive) + interactive->key_release_event(event); +} +void Renderable::resize_event(const Resize_Event& event) { + if (get_retiring()) + return; + (void)event; +} +void Renderable::wheel_event(const Wheel_Event& event) { + if (get_retiring() || event.is_accepted()) + return; + auto* interactive = dynamic_cast(d_ptr); + if (interactive) + interactive->wheel_event(event); +} +void Renderable::mark_render_dirty() { + if (get_retiring()) + return; + auto context = plot_context.lock(); + if (!context || context->destroying.load(std::memory_order_acquire)) + return; + cache_dirty.store(true, std::memory_order_release); + cache_edit_version.fetch_add(1, std::memory_order_acq_rel); + if (auto parent = parent_renderable.lock()) { + parent->mark_render_dirty(); + return; + } + Plot_Core* target_plot = context->owner.load(std::memory_order_acquire); + if (!context->destroying.load(std::memory_order_acquire) && target_plot) + target_plot->mark_render_state_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); +} +bool Renderable::get_retiring() const { + return retiring.load(std::memory_order_acquire); +} +void Renderable::detach_from_plot_tree() { + bool was_retiring = retiring.exchange(true, std::memory_order_acq_rel); + if (was_retiring && plot == nullptr) + return; + if (d_ptr) + d_ptr->cancel_pending_completions(); + std::vector> detached_children; + { + std::lock_guard lock(children_mutex); + detached_children = std::move(children); + for (const auto& child : detached_children) { + if (child) + child->parent_renderable.reset(); + } + children.clear(); + } + for (const auto& child : detached_children) { + 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; +} +} // namespace renderive diff --git a/Core/architecture/Renderable.h b/Core/architecture/Renderable.h new file mode 100644 index 0000000..21eed3e --- /dev/null +++ b/Core/architecture/Renderable.h @@ -0,0 +1,150 @@ +#pragma once +#include "../base/Geometry.h" +#include "../base/Object_Semantics.h" +#include "../base/Text.h" +#include "../event/Event.h" +#include "../event/Key_Event.h" +#include "../event/Pointer_Event.h" +#include "../event/Resize_Event.h" +#include "../event/Wheel_Event.h" +#include "../render/Canvas.h" +#include "../render/Image.h" +#include "Render_Config.h" +#include "Render_Data.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace renderive { + +class Plot_Core; +class Renderable; + +struct Renderable_Tree_Node { + std::shared_ptr renderable; + std::size_t subtree_end{}; + std::string parent_object_name; + int render_tree_depth{}; + std::string cache_parent_object_name; + int cache_tree_depth{}; +}; + +using Renderable_Tree_Snapshot = std::vector; + +template +concept Render_State_Type = std::derived_from && std::is_copy_assignable_v; +template +concept Input_Data_Type = std::derived_from; +template +concept Render_Data_Type = std::derived_from; +template +struct Renderable_Binding; +template +struct Hover_Info_Renderable; + +class Renderable : public std::enable_shared_from_this { +public: + virtual Render_Object_Owner create_render_data() { + return make_render_object(); + } + virtual Render_Object_Owner create_render_state() { + return make_render_object(); + } + virtual Render_Object_Owner create_input_data() { + return make_render_object(); + } + + virtual void init(const std::shared_ptr& parent); + virtual void init_root(Plot_Core* plot); + + void add_child(const std::shared_ptr& child); + void remove_child(const std::shared_ptr& child); + + std::vector> children_snapshot() const; + std::vector> hit_renderables(const PointF& pos); + void append_tree_snapshot(Renderable_Tree_Snapshot& snapshot, const std::string& parent_object_name = {}, int render_tree_depth = 0, const std::string& cache_parent_object_name = {}, int cache_tree_depth = 0); + + void render(Canvas& canvas, const Plot_Render_Snapshot& snapshot); + void render_tree_snapshot(Canvas& canvas, const Plot_Render_Snapshot& snapshot, const Renderable_Tree_Snapshot& tree, std::size_t index); + void draw(Canvas& canvas, const Plot_Render_Snapshot& snapshot); + void prepare_data(const Plot_Render_Snapshot& snapshot); + bool select_test(const PointF& pos); + + void plot_event(const Event& event); + void mouse_press_event(const Pointer_Event& event); + void mouse_move_event(const Pointer_Event& event); + void mouse_release_event(const Pointer_Event& event); + void set_hover_state(Point pos, bool active); + void key_press_event(const Key_Event& event); + void key_release_event(const Key_Event& event); + void resize_event(const Resize_Event& event); + void wheel_event(const Wheel_Event& event); + + Renderable_Cache_Mode get_cache_mode() const; + void set_cache_mode(Renderable_Cache_Mode mode); + [[nodiscard]] bool attached_to_plot() const; + [[nodiscard]] bool shares_plot_with(const Renderable& other) const; + [[nodiscard]] const std::string& object_name() const; + void set_object_name(std::string name); + [[nodiscard]] bool is_visible() const; + void set_visible(bool value); + [[nodiscard]] bool should_prepare() const; + void detach_from_parent(); + + std::string cache_parent_object_name() const; + int cache_tree_depth() const; + std::string render_parent_object_name() const; + int render_tree_depth() const; + + void mark_render_dirty(); + + [[nodiscard]] bool ok() const; + [[nodiscard]] bool get_retiring() const; + virtual ~Renderable(); + +private: + template + friend struct Renderable_Binding; + template + friend struct Hover_Info_Renderable; + + Render_Object_Owner d_owner; + Render_Data* d_ptr{}; + Plot_Core* plot{}; + std::string object_name_; + std::weak_ptr parent_renderable; + bool visible_ = true; + 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}; + std::atomic cache_ready_version{0}; + Image cache_image; + Rect cache_bounds; + std::atomic_bool retiring{true}; + std::atomic_bool initialized{false}; + + void init_common(Plot_Core* plot); + void render_direct(Canvas& canvas, const Plot_Render_Snapshot& snapshot); + void render_cached(Canvas& canvas, const Plot_Render_Snapshot& snapshot); + void render_self(Canvas& canvas, const Plot_Render_Snapshot& snapshot); + void render_children(Canvas& canvas, const Plot_Render_Snapshot& snapshot); + void render_cache_image(const Rect& bounds, const Plot_Render_Snapshot& snapshot, std::uint64_t version); + std::size_t render_tree_node(Canvas& canvas, const Plot_Render_Snapshot& snapshot, const Renderable_Tree_Snapshot& tree, std::size_t index); + void render_direct_tree_node(Canvas& canvas, const Plot_Render_Snapshot& snapshot, const Renderable_Tree_Snapshot& tree, std::size_t index); + void render_cached_tree_node(Canvas& canvas, const Plot_Render_Snapshot& snapshot, const Renderable_Tree_Snapshot& tree, std::size_t index); + void render_cache_image_tree_node(const Rect& bounds, const Plot_Render_Snapshot& snapshot, const Renderable_Tree_Snapshot& tree, std::size_t index, std::uint64_t version); + void render_self(Canvas& canvas, const Plot_Render_Snapshot& snapshot, const Renderable_Tree_Node* node); + void append_hit_renderables(std::vector>& hits, const PointF& pos); + void detach_from_plot_tree(); +}; + +} // namespace renderive diff --git a/Core/architecture/Timeline_Stream.cpp b/Core/architecture/Timeline_Stream.cpp new file mode 100644 index 0000000..d8c9559 --- /dev/null +++ b/Core/architecture/Timeline_Stream.cpp @@ -0,0 +1,85 @@ +#include "Timeline_Stream.h" +#include +#include +#include +namespace renderive { +int Timeline_Stream_Snapshot::upper() const { + return lower + time_point_size - 1; +} +bool Timeline_Stream_Snapshot::coord_by_stream_tick(int stream_tick, int& coord) const { + if (times.empty()) + return false; + if (stream_tick < lower_stream_tick || stream_tick > upper_stream_tick) + return false; + int count = static_cast(times.size()); + if (start_coord_to_end_coord) + coord = lower + (upper_stream_tick - stream_tick); + else + coord = lower + (time_point_size - count) + (stream_tick - lower_stream_tick); + return coord >= lower && coord <= upper(); +} +Time_Of_Day Timeline_Stream_Snapshot::time_by_tick(int tick) const { + int offset = tick - lower; + int count = static_cast(times.size()); + int index = start_coord_to_end_coord ? count - 1 - offset : offset - (time_point_size - count); + if (index < 0 || index >= count) + return {}; + return times[static_cast(index)]; +} +Timeline_Stream::Timeline_Stream() = default; +int Timeline_Stream::push_time(Time_Of_Day time) { + int tick = next_tick_value.fetch_add(1, std::memory_order_acq_rel); + if (tick == std::numeric_limits::max()) { + int expected = std::numeric_limits::min(); + next_tick_value.compare_exchange_strong(expected, 1, std::memory_order_acq_rel, std::memory_order_acquire); + tick = 0; + } + std::uint64_t sequence = write_sequence.fetch_add(1, std::memory_order_acq_rel); + auto sample = std::make_shared(Sample{sequence, tick, time}); + time_slots[sequence % Max_Buffered_Time_Count].store(std::move(sample), std::memory_order_release); + return tick; +} +Timeline_Stream_Snapshot Timeline_Stream::capture(int time_point_size, int tick_space, bool start_coord_to_end_coord) const { + time_point_size = std::max(1, time_point_size); + tick_space = std::max(1, tick_space); + Timeline_Stream_Snapshot snapshot; + std::uint64_t write = write_sequence.load(std::memory_order_acquire); + snapshot.version = write + 1; + snapshot.start_coord_to_end_coord = start_coord_to_end_coord; + snapshot.time_point_size = time_point_size; + if (write == 0) { + snapshot.lower = 0; + return snapshot; + } + std::uint64_t available = std::min(write, Max_Buffered_Time_Count); + std::uint64_t count = std::min(static_cast(time_point_size), available); + std::uint64_t first_sequence = write - count; + std::pmr::vector samples; + samples.reserve(static_cast(count)); + for (std::uint64_t sequence = first_sequence; sequence < write; ++sequence) { + auto sample = time_slots[sequence % Max_Buffered_Time_Count].load(std::memory_order_acquire); + if (sample && sample->sequence == sequence) + samples.push_back(*sample); + } + if (samples.empty()) { + snapshot.lower = 0; + return snapshot; + } + snapshot.lower_stream_tick = samples.front().tick; + snapshot.upper_stream_tick = samples.back().tick; + snapshot.lower = snapshot.upper_stream_tick - time_point_size + 1; + snapshot.times.reserve(samples.size()); + for (const auto& sample : samples) + snapshot.times.push_back(sample.time); + snapshot.ticks.reserve(samples.size() / static_cast(tick_space) + 1); + for (const auto& sample : samples) { + int coord{}; + if ((tick_space == 1 || sample.tick % tick_space == 0) && snapshot.coord_by_stream_tick(sample.tick, coord)) + snapshot.ticks.push_back({sample.time, coord}); + } + std::sort(snapshot.ticks.begin(), snapshot.ticks.end(), [](const Timeline_Tick& left, const Timeline_Tick& right) { + return left.tick < right.tick; + }); + return snapshot; +} +} diff --git a/Core/architecture/Timeline_Stream.h b/Core/architecture/Timeline_Stream.h new file mode 100644 index 0000000..ee83068 --- /dev/null +++ b/Core/architecture/Timeline_Stream.h @@ -0,0 +1,43 @@ +#pragma once +#include +#include +#include +#include +#include +#include "../base/Time_Of_Day.h" +#include "global.h" +namespace renderive { +struct Timeline_Tick { + Time_Of_Day time; + int tick{}; +}; +struct LIB_DECL Timeline_Stream_Snapshot { + std::uint64_t version{}; + bool start_coord_to_end_coord = false; + int lower{}; + int time_point_size = 1; + int lower_stream_tick{}; + int upper_stream_tick = -1; + std::vector times; + std::vector ticks; + [[nodiscard]] int upper() const; + [[nodiscard]] bool coord_by_stream_tick(int stream_tick, int& coord) const; + [[nodiscard]] Time_Of_Day time_by_tick(int tick) const; +}; +class LIB_DECL Timeline_Stream { +public: + Timeline_Stream(); + int push_time(Time_Of_Day time); + [[nodiscard]] Timeline_Stream_Snapshot capture(int time_point_size, int tick_space, bool start_coord_to_end_coord) const; +private: + struct Sample { + std::uint64_t sequence{}; + int tick{}; + Time_Of_Day time; + }; + static constexpr std::size_t Max_Buffered_Time_Count = 65536; + std::array>, Max_Buffered_Time_Count> time_slots; + std::atomic write_sequence{0}; + std::atomic next_tick_value{0}; +}; +} diff --git a/Renderive/architecture/Triple_Buffer.cpp b/Core/architecture/Triple_Buffer.cpp similarity index 100% rename from Renderive/architecture/Triple_Buffer.cpp rename to Core/architecture/Triple_Buffer.cpp diff --git a/Renderive/architecture/Triple_Buffer.h b/Core/architecture/Triple_Buffer.h similarity index 84% rename from Renderive/architecture/Triple_Buffer.h rename to Core/architecture/Triple_Buffer.h index 0e706d1..954a248 100644 --- a/Renderive/architecture/Triple_Buffer.h +++ b/Core/architecture/Triple_Buffer.h @@ -59,10 +59,6 @@ public: Triple_Buffer_View read_view() const { return decode_order(slot_state.load(std::memory_order_acquire)); } - /// 尝试占用指定物理缓冲。 - - /// 等待并占用指定物理缓冲。 - /// 尝试占用指定角色当前对应的物理缓冲。 Triple_Buffer_Mark_Record try_mark_use_role(std::uint8_t role) { auto order = slot_state.load(std::memory_order_acquire); @@ -78,16 +74,12 @@ public: } return {Triple_Buffer_Result::Success, {index}, triple_buffer_invalid_index}; } - /// 等待并占用指定角色当前对应的物理缓冲。 - Triple_Buffer_Lease wait_mark_use_role(std::uint8_t role) { + Triple_Buffer_Lease mark_use_role(std::uint8_t role) { for (;;) { auto record = try_mark_use_role(role); if (record.result == Triple_Buffer_Result::Success) { return record.lease; } - if (record.result == Triple_Buffer_Result::Busy) { - wait_index_free(record.busy_index); - } } } /// 释放租约持有的物理缓冲。 @@ -100,22 +92,9 @@ public: std::uint8_t busy_index = triple_buffer_invalid_index; return try_swap_role_once(left_role, 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) { - 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) { - return record; - } - if (record.result == Triple_Buffer_Result::Busy) { - wait_index_free(busy_index); - } - else { - slot_state.wait(encode_order(record.old_view), std::memory_order_acquire); - } - } + bool role_busy(std::uint8_t role) const { + auto view = read_view(); + return buffer_busy[view[role]].busy.load(std::memory_order_acquire); } private: std::atomic slot_state{0}; @@ -161,11 +140,6 @@ private: buffer_busy[index].busy.store(false, std::memory_order_release); buffer_busy[index].busy.notify_all(); } - void wait_index_free(std::uint8_t index) { - while (buffer_busy[index].busy.load(std::memory_order_acquire)) { - buffer_busy[index].busy.wait(true, std::memory_order_acquire); - } - } bool try_mark_two_indices(std::uint8_t left_index, std::uint8_t right_index, std::uint8_t& busy_index) { if (left_index == right_index) { if (!try_mark_index(left_index)) { diff --git a/Renderive/architecture/Update_Completion.cpp b/Core/architecture/Update_Completion.cpp similarity index 100% rename from Renderive/architecture/Update_Completion.cpp rename to Core/architecture/Update_Completion.cpp diff --git a/Renderive/architecture/Update_Completion.h b/Core/architecture/Update_Completion.h similarity index 100% rename from Renderive/architecture/Update_Completion.h rename to Core/architecture/Update_Completion.h diff --git a/Core/architecture/export.h b/Core/architecture/export.h new file mode 100644 index 0000000..b580486 --- /dev/null +++ b/Core/architecture/export.h @@ -0,0 +1,6 @@ +#pragma once +#include "Render_Config.h" +#include "Renderable.h" +#include "Timeline_Stream.h" +#include "Update_Completion.h" +#include "global.h" diff --git a/Core/architecture/global.h b/Core/architecture/global.h new file mode 100644 index 0000000..0e0c8cd --- /dev/null +++ b/Core/architecture/global.h @@ -0,0 +1,55 @@ +#pragma once +#include +#include +#include +#include +#include +#include "../base/Assert.h" +#include "../base/Color.h" +#include "../base/Export.h" +#include "../base/Geometry.h" +namespace renderive { +std::vector get_turbo_color_gradient(); +} +#define LIB_DECL RENDERIVE_API +#define PROP(Type, Name) \ + Type Name(); \ + void set_##Name(Type); +#define RENDERIVE_DEFINE_STATE_PROP(Owner, State, Type, Field) \ + Type Owner::Field() { \ + return d()->edit_state_value(&State::Field); \ + } \ + void Owner::set_##Field(Type value) { \ + d()->set_state_value(&State::Field, std::move(value)); \ + } +#define RENDERIVE_DEFINE_WEAK_STATE_PROP(Owner, State, Pointee, Field) \ + std::shared_ptr Owner::Field() { \ + return d()->edit_state_value(&State::Field).lock(); \ + } \ + void Owner::set_##Field(std::shared_ptr value) { \ + d()->set_state_value(&State::Field, std::weak_ptr(value)); \ + } +#define PROP_B(TYPE, FIELD) \ +public: \ + Builder& set_##FIELD(TYPE value) { \ + this->FIELD = std::move(value); \ + return *this; \ + } +#define PROP_BT(TYPE, FIELD) \ +public: \ + That& set_##FIELD(TYPE value) { \ + static_cast(this)->FIELD = std::move(value); \ + return *static_cast(this); \ + } +#define PROP_R(TYPE, FIELD) sc->FIELD = this->FIELD; +#define PROP_RT(TYPE, FIELD) sc->FIELD = static_cast(this)->FIELD; +#define SETTER(TYPE, FIELD, DEFAULT) \ + \ + TYPE FIELD = DEFAULT; \ +public: \ + Builder& set_##FIELD(TYPE value) { \ + this->FIELD = std::move(value); \ + return *this; \ + } +#undef min +#undef max diff --git a/Core/base/Assert.h b/Core/base/Assert.h new file mode 100644 index 0000000..b7a991f --- /dev/null +++ b/Core/base/Assert.h @@ -0,0 +1,23 @@ +#pragma once +#include +#include + +#ifdef NDEBUG +# define RENDERIVE_ASSERT(condition, message) ((void)0) +#else +# define RENDERIVE_ASSERT(condition, message) \ + do { \ + if (!(condition)) { \ + std::cerr << "Assertion failed: " << message \ + << "\nFile: " << __FILE__ \ + << "\nLine: " << __LINE__ \ + << std::endl; \ + throw std::runtime_error(message); \ + } \ + } \ + while (0) +#endif + +#ifndef ASSERT +# define ASSERT(condition, message) RENDERIVE_ASSERT(condition, message) +#endif diff --git a/Core/base/Bitmask.h b/Core/base/Bitmask.h new file mode 100644 index 0000000..252cc35 --- /dev/null +++ b/Core/base/Bitmask.h @@ -0,0 +1,43 @@ +#pragma once +#include + +template +struct enable_bitmask : std::false_type {}; + +#define RENDERIVE_ENABLE_BITMASK(E) \ + template <> \ + struct enable_bitmask : std::true_type {}; + +template +constexpr std::enable_if_t::value, E> operator|(E lhs, E rhs) { + using U = std::underlying_type_t; + return static_cast(static_cast(lhs) | static_cast(rhs)); +} + +template +constexpr std::enable_if_t::value, E> operator&(E lhs, E rhs) { + using U = std::underlying_type_t; + return static_cast(static_cast(lhs) & static_cast(rhs)); +} + +template +constexpr std::enable_if_t::value, E> operator^(E lhs, E rhs) { + using U = std::underlying_type_t; + return static_cast(static_cast(lhs) ^ static_cast(rhs)); +} + +template +constexpr std::enable_if_t::value, E> operator~(E e) { + using U = std::underlying_type_t; + return static_cast(~static_cast(e)); +} + +template +constexpr std::enable_if_t::value, E&> operator|=(E& lhs, E rhs) { + return lhs = lhs | rhs; +} + +template +constexpr std::enable_if_t::value, E&> operator&=(E& lhs, E rhs) { + return lhs = lhs & rhs; +} diff --git a/Core/base/Color.h b/Core/base/Color.h new file mode 100644 index 0000000..acf645c --- /dev/null +++ b/Core/base/Color.h @@ -0,0 +1,66 @@ +#pragma once +#include + +namespace renderive { + +struct Color { + std::uint8_t red{}; + std::uint8_t green{}; + std::uint8_t blue{}; + std::uint8_t alpha{255}; + + static constexpr Color transparent() { + return {0, 0, 0, 0}; + } + static constexpr Color black() { + return {0, 0, 0, 255}; + } + static constexpr Color white() { + return {255, 255, 255, 255}; + } + static constexpr Color red_color() { + return {255, 0, 0, 255}; + } + static constexpr Color green_color() { + return {0, 255, 0, 255}; + } + static constexpr Color yellow() { + return {255, 255, 0, 255}; + } + bool operator==(const Color& other) const { + return red == other.red && green == other.green && blue == other.blue && alpha == other.alpha; + } + bool operator!=(const Color& other) const { + return !(*this == other); + } +}; + +using Pixel = std::uint32_t; + +constexpr Pixel premultiply(Color color) { + auto r = static_cast((color.red * color.alpha + 127) / 255); + auto g = static_cast((color.green * color.alpha + 127) / 255); + auto b = static_cast((color.blue * color.alpha + 127) / 255); + return (static_cast(color.alpha) << 24) | (r << 16) | (g << 8) | b; +} + +constexpr Pixel pack_rgba(std::uint8_t r, std::uint8_t g, std::uint8_t b, std::uint8_t a = 255) { + return (static_cast(a) << 24) | (static_cast(r) << 16) | (static_cast(g) << 8) | static_cast(b); +} + +constexpr Color premultiplied_to_color(Pixel pixel) { + auto a = static_cast(pixel >> 24); + if (a == 0) + return {0, 0, 0, 0}; + auto r = static_cast((pixel >> 16) & 0xFF); + auto g = static_cast((pixel >> 8) & 0xFF); + auto b = static_cast(pixel & 0xFF); + return { + static_cast((r * 255 + a / 2) / a), + static_cast((g * 255 + a / 2) / a), + static_cast((b * 255 + a / 2) / a), + a + }; +} + +} // namespace renderive diff --git a/Core/base/Export.h b/Core/base/Export.h new file mode 100644 index 0000000..2b9f0bb --- /dev/null +++ b/Core/base/Export.h @@ -0,0 +1,13 @@ +#pragma once + +#if defined(_WIN32) +# if defined(RENDERIVE_BUILD_SHARED) +# define RENDERIVE_API __declspec(dllexport) +# elif defined(RENDERIVE_USE_SHARED) +# define RENDERIVE_API __declspec(dllimport) +# else +# define RENDERIVE_API +# endif +#else +# define RENDERIVE_API __attribute__((visibility("default"))) +#endif diff --git a/Renderive/base/Frame_Memory.cpp b/Core/base/Frame_Memory.cpp similarity index 100% rename from Renderive/base/Frame_Memory.cpp rename to Core/base/Frame_Memory.cpp diff --git a/Renderive/base/Frame_Memory.h b/Core/base/Frame_Memory.h similarity index 100% rename from Renderive/base/Frame_Memory.h rename to Core/base/Frame_Memory.h diff --git a/Core/base/Geometry.h b/Core/base/Geometry.h new file mode 100644 index 0000000..cb8c9f0 --- /dev/null +++ b/Core/base/Geometry.h @@ -0,0 +1,145 @@ +#pragma once +#include +#include +#include + +namespace renderive { + +struct Point { + int x{}; + int y{}; +}; + +struct PointF { + double x{}; + double y{}; +}; + +struct Size { + int width{}; + int height{}; + [[nodiscard]] bool empty() const { + return width <= 0 || height <= 0; + } + bool operator==(const Size& other) const { + return width == other.width && height == other.height; + } + bool operator!=(const Size& other) const { + return !(*this == other); + } +}; + +struct Rect { + int x{}; + int y{}; + int width{}; + int height{}; + [[nodiscard]] bool empty() const { + return width <= 0 || height <= 0; + } + [[nodiscard]] int right() const { + return x + width; + } + [[nodiscard]] int bottom() const { + return y + height; + } + [[nodiscard]] Point top_left() const { + return {x, y}; + } + [[nodiscard]] bool contains(Point point) const { + return point.x >= x && point.x <= right() && point.y >= y && point.y <= bottom(); + } + [[nodiscard]] Rect intersected(const Rect& other) const { + int r = std::min(right(), other.right()); + int b = std::min(bottom(), other.bottom()); + int nx = std::max(x, other.x); + int ny = std::max(y, other.y); + int w = r - nx; + int h = b - ny; + if (w <= 0 || h <= 0) + return {}; + return {nx, ny, w, h}; + } +}; + +struct RectF { + double x{}; + double y{}; + double width{}; + double height{}; + [[nodiscard]] bool empty() const { + return width <= 0.0 || height <= 0.0; + } + [[nodiscard]] double right() const { + return x + width; + } + [[nodiscard]] double bottom() const { + return y + height; + } + [[nodiscard]] PointF top_left() const { + return {x, y}; + } + [[nodiscard]] bool contains(PointF point) const { + return point.x >= x && point.x <= right() && point.y >= y && point.y <= bottom(); + } + [[nodiscard]] RectF normalized() const { + double nx = x; + double ny = y; + double nw = width; + double nh = height; + if (nw < 0.0) { + nx += nw; + nw = -nw; + } + if (nh < 0.0) { + ny += nh; + nh = -nh; + } + return {nx, ny, nw, nh}; + } + [[nodiscard]] RectF intersected(const RectF& other) const { + double r = std::min(right(), other.right()); + double b = std::min(bottom(), other.bottom()); + double nx = std::max(x, other.x); + double ny = std::max(y, other.y); + double w = r - nx; + double h = b - ny; + if (w <= 0.0 || h <= 0.0) + return {}; + return {nx, ny, w, h}; + } +}; + +enum class Orientation : std::uint8_t { + Horizontal, + Vertical +}; + +struct Range { + double origin; + double target; + [[nodiscard]] double size() const { + return target > origin ? target - origin : origin - target; + } + [[nodiscard]] double length() const { + return target - origin; + } + [[nodiscard]] double middle() const { + return origin + (target - origin) / 2.0; + } + bool operator==(const Range& other) const { + return origin == other.origin && target == other.target; + } + bool operator!=(const Range& other) const { + return !(*this == other); + } + [[nodiscard]] bool contain(double value) const { + if (origin < target) + return value > origin - 0.0001 && value < target + 0.0001; + return value > target - 0.0001 && value < origin + 0.0001; + } +}; + +std::ostream& operator<<(std::ostream& stream, const Range& range); + +} // namespace renderive diff --git a/Renderive/base/Memory.cpp b/Core/base/Memory.cpp similarity index 99% rename from Renderive/base/Memory.cpp rename to Core/base/Memory.cpp index b0a7433..64a6119 100644 --- a/Renderive/base/Memory.cpp +++ b/Core/base/Memory.cpp @@ -171,7 +171,7 @@ void set_memory_callbacks(const Memory_Callbacks& callbacks) { Memory_System& system = memory_system(); std::lock_guard lock(system.mutex); if (system.configured || system.initialized.load(std::memory_order_acquire)) - throw std::logic_error("Renderive memory resource is already configured"); + throw std::logic_error("Core memory resource is already configured"); system.upstream.set_callbacks(callbacks); system.configured = true; } diff --git a/Renderive/base/Memory.h b/Core/base/Memory.h similarity index 97% rename from Renderive/base/Memory.h rename to Core/base/Memory.h index ac7f3bf..d6fba07 100644 --- a/Renderive/base/Memory.h +++ b/Core/base/Memory.h @@ -23,7 +23,7 @@ struct Memory_Stats { std::uint64_t upstream_current_bytes{}; std::uint64_t upstream_peak_bytes{}; }; -/// @brief Allocation statistics tag used by Renderive memory resources. +/// @brief Allocation statistics tag used by Core memory resources. enum class Memory_Domain : std::uint8_t { /// @brief Renderable facade and render-data objects. Renderable, diff --git a/Renderive/base/Object_Semantics.h b/Core/base/Object_Semantics.h similarity index 100% rename from Renderive/base/Object_Semantics.h rename to Core/base/Object_Semantics.h diff --git a/Core/base/String_Format.h b/Core/base/String_Format.h new file mode 100644 index 0000000..e07f705 --- /dev/null +++ b/Core/base/String_Format.h @@ -0,0 +1,90 @@ +#pragma once +#include +#include +#include +#include "Time_Of_Day.h" + +namespace renderive { + +enum class Number_Notation : std::uint8_t { + Fixed, + Scientific, + General +}; + +struct Number_Format { + Number_Notation notation = Number_Notation::Fixed; + int precision = 2; +}; + +struct Number_Locale { + char decimal_separator = '.'; + char group_separator = ','; + bool use_group_separator = false; +}; + +inline std::string format_number(double value, const Number_Format& format) { + char buf[64]; + const char* fmt = "f"; + if (format.notation == Number_Notation::Scientific) + fmt = "e"; + else if (format.notation == Number_Notation::General) + fmt = "g"; + + int prec = format.precision; + if (prec < 0) + prec = 0; + if (prec > 20) + prec = 20; + + auto result = std::to_chars(buf, buf + sizeof(buf) - 1, value, + fmt == "f" ? std::chars_format::fixed + : fmt == "e" ? std::chars_format::scientific + : std::chars_format::general, prec); + if (result.ec == std::errc{}) + return std::string(buf, result.ptr); + return {}; +} + +inline std::string format_time(Time_Of_Day time, std::string_view pattern) { + if (!time.valid()) + return {}; + + auto ms = static_cast(time.milliseconds); + auto hours = static_cast((ms / (1000 * 60 * 60)) % 24); + auto minutes = static_cast((ms / (1000 * 60)) % 60); + auto seconds = static_cast((ms / 1000) % 60); + auto msecs = static_cast(ms % 1000); + + std::string result; + result.reserve(pattern.size() + 4); + for (std::size_t i = 0; i < pattern.size(); ++i) { + char c = pattern[i]; + if (c == 'h' && i + 1 < pattern.size() && pattern[i + 1] == 'h') { + char buf[4]; + snprintf(buf, sizeof(buf), "%02d", hours); + result += buf; + ++i; + } else if (c == 'm' && i + 1 < pattern.size() && pattern[i + 1] == 'm') { + char buf[4]; + snprintf(buf, sizeof(buf), "%02d", minutes); + result += buf; + ++i; + } else if (c == 's' && i + 1 < pattern.size() && pattern[i + 1] == 's') { + char buf[4]; + snprintf(buf, sizeof(buf), "%02d", seconds); + result += buf; + ++i; + } else if (c == 'z' && i + 2 < pattern.size() && pattern[i + 1] == 'z' && pattern[i + 2] == 'z') { + char buf[6]; + snprintf(buf, sizeof(buf), "%03d", msecs); + result += buf; + i += 2; + } else { + result += c; + } + } + return result; +} + +} // namespace renderive diff --git a/Core/base/Style.h b/Core/base/Style.h new file mode 100644 index 0000000..0573782 --- /dev/null +++ b/Core/base/Style.h @@ -0,0 +1,50 @@ +#pragma once +#include "Color.h" +#include + +namespace renderive { + +enum class Line_Style : std::uint8_t { + None, + Solid, + Dash, + Dot +}; + +enum class Line_Cap : std::uint8_t { + Butt, + Square, + Round +}; + +enum class Line_Join : std::uint8_t { + Miter, + Bevel, + Round +}; + +struct Pen { + Color color = Color::white(); + double width = 1.0; + Line_Style style = Line_Style::Solid; + Line_Cap cap = Line_Cap::Butt; + Line_Join join = Line_Join::Miter; + [[nodiscard]] bool enabled() const { + return style != Line_Style::None && width > 0.0 && color.alpha != 0; + } +}; + +enum class Brush_Style : std::uint8_t { + None, + Solid +}; + +struct Brush { + Color color = Color::transparent(); + Brush_Style style = Brush_Style::None; + [[nodiscard]] bool enabled() const { + return style != Brush_Style::None && color.alpha != 0; + } +}; + +} // namespace renderive diff --git a/Renderive/base/Task.h b/Core/base/Task.h similarity index 100% rename from Renderive/base/Task.h rename to Core/base/Task.h diff --git a/Core/base/Text.h b/Core/base/Text.h new file mode 100644 index 0000000..c54409f --- /dev/null +++ b/Core/base/Text.h @@ -0,0 +1,44 @@ +#pragma once +#include +#include +#include "Bitmask.h" + +namespace renderive { + +enum class Text_Align : std::uint16_t { + None = 0, + Left = 1 << 0, + Right = 1 << 1, + HCenter = 1 << 2, + Top = 1 << 3, + Bottom = 1 << 4, + VCenter = 1 << 5, + Center = HCenter | VCenter +}; + +struct Font_Face; + +struct Font { + std::shared_ptr face; + double size = 12.0; + int weight = 400; + bool italic = false; + [[nodiscard]] bool valid() const { + return face != nullptr && size > 0.0; + } +}; + +struct Text_Metrics { + double width{}; + double height{}; + double ascent{}; + double descent{}; + double line_gap{}; + [[nodiscard]] double line_height() const { + return ascent + descent + line_gap; + } +}; + +} // namespace renderive + +RENDERIVE_ENABLE_BITMASK(renderive::Text_Align) diff --git a/Core/base/Time_Of_Day.h b/Core/base/Time_Of_Day.h new file mode 100644 index 0000000..77995c1 --- /dev/null +++ b/Core/base/Time_Of_Day.h @@ -0,0 +1,19 @@ +#pragma once +#include + +namespace renderive { + +struct Time_Of_Day { + std::int64_t milliseconds = -1; + [[nodiscard]] bool valid() const { + return milliseconds >= 0 && milliseconds < 24LL * 60LL * 60LL * 1000LL; + } + bool operator==(const Time_Of_Day& other) const { + return milliseconds == other.milliseconds; + } + bool operator!=(const Time_Of_Day& other) const { + return !(*this == other); + } +}; + +} // namespace renderive diff --git a/Renderive/base/global.cpp b/Core/base/global.cpp similarity index 74% rename from Renderive/base/global.cpp rename to Core/base/global.cpp index 05e2034..c499a4c 100644 --- a/Renderive/base/global.cpp +++ b/Core/base/global.cpp @@ -1,31 +1,50 @@ #include "global.h" -#include #include -#include "../plot/Plot_p.h" #include "Memory.h" +#include "../plot/Plot_Core.h" #include "asio.hpp" +#include + namespace renderive { + struct Render_Scheduler_Private { asio::io_context io_context; std::unique_ptr> work_guard; std::unique_ptr cpu_pool; std::thread scheduler_thread; std::thread::id scheduler_thread_id; - std::vector plots; + std::vector plots; std::atomic_bool running{false}; std::atomic_bool cpu_joined{true}; }; -Render_Scheduler::Render_Scheduler() : d(std::make_unique()) { - QObject::connect(QApplication::instance(), &QApplication::aboutToQuit, [this]() { - shutdown(); - }); + +Render_Scheduler::Render_Scheduler() : d(std::make_unique()) {} +Global::Global() {} + +namespace { +Global*& global_instance_storage() { + static Global* value = nullptr; + return value; } -Global::Global() { - qInitResources_resource(); +} // namespace + +Global* Global::instance() { + Global*& value = global_instance_storage(); + if (!value) + value = new Global(); + return value; } + +void Global::destroy() { + Global*& value = global_instance_storage(); + delete value; + value = nullptr; +} + Render_Scheduler::~Render_Scheduler() { shutdown(); } + void Render_Scheduler::start() { memory_resource(); if (d->running.exchange(true, std::memory_order_acq_rel)) @@ -37,13 +56,13 @@ void Render_Scheduler::start() { d->cpu_joined.store(false, std::memory_order_release); d->scheduler_thread = std::thread([this]() { d->scheduler_thread_id = std::this_thread::get_id(); - for (Abs_Plot* plot : d->plots) { + for (Plot_Core* plot : d->plots) initialize_plot_on_scheduler(plot); - } d->io_context.run(); d->running.store(false, std::memory_order_release); }); } + void Render_Scheduler::shutdown() { if (!d->running.load(std::memory_order_acquire)) { join_cpu_pool(); @@ -70,18 +89,30 @@ void Render_Scheduler::shutdown() { d->scheduler_thread.join(); d->cpu_pool.reset(); } + void Render_Scheduler::post(Task task) { asio::post(d->io_context, std::move(task)); } -void Render_Scheduler::post_cpu(Task task) { - if (d->cpu_pool) { - asio::post(*d->cpu_pool, std::move(task)); - } - else { - post(std::move(task)); - } + +void Render_Scheduler::post_at(std::uint64_t steady_time_ns, Task task) { + if (!task) + return; + auto timer = std::make_shared(d->io_context); + timer->expires_at(std::chrono::steady_clock::time_point(std::chrono::nanoseconds(steady_time_ns))); + timer->async_wait([timer, task = std::move(task)](const asio::error_code& error) mutable { + if (!error) + task(); + }); } -void Render_Scheduler::register_plot(Abs_Plot* plot) { + +void Render_Scheduler::post_cpu(Task task) { + if (d->cpu_pool) + asio::post(*d->cpu_pool, std::move(task)); + else + post(std::move(task)); +} + +void Render_Scheduler::register_plot(Plot_Core* plot) { if (d->running.load(std::memory_order_acquire) && !is_scheduler_thread()) { post_and_wait([this, plot]() { register_plot_on_scheduler(plot); @@ -90,7 +121,8 @@ void Render_Scheduler::register_plot(Abs_Plot* plot) { } register_plot_on_scheduler(plot); } -void Render_Scheduler::remove_plot(Abs_Plot* plot) { + +void Render_Scheduler::remove_plot(Plot_Core* plot) { if (d->running.load(std::memory_order_acquire) && !is_scheduler_thread()) { post_and_wait([this, plot]() { remove_plot_on_scheduler(plot); @@ -99,19 +131,24 @@ void Render_Scheduler::remove_plot(Abs_Plot* plot) { } remove_plot_on_scheduler(plot); } + bool Render_Scheduler::is_scheduler_thread() const { return std::this_thread::get_id() == d->scheduler_thread_id; } + bool Render_Scheduler::is_running() const { return d->running.load(std::memory_order_acquire); } + asio::io_context& Render_Scheduler::io_context() { return d->io_context; } + void Render_Scheduler::join_cpu_pool() { if (d->cpu_pool && !d->cpu_joined.exchange(true, std::memory_order_acq_rel)) d->cpu_pool->join(); } + void Render_Scheduler::post_and_wait(Task task) { if (is_scheduler_thread() || !d->running.load(std::memory_order_acquire)) { task(); @@ -133,40 +170,37 @@ void Render_Scheduler::post_and_wait(Task task) { return done; }); } -void Render_Scheduler::register_plot_on_scheduler(Abs_Plot* plot) { + +void Render_Scheduler::register_plot_on_scheduler(Plot_Core* plot) { if (std::find(d->plots.begin(), d->plots.end(), plot) == d->plots.end()) d->plots.push_back(plot); if (d->running.load(std::memory_order_acquire)) initialize_plot_on_scheduler(plot); } -void Render_Scheduler::remove_plot_on_scheduler(Abs_Plot* plot) { + +void Render_Scheduler::remove_plot_on_scheduler(Plot_Core* plot) { d->plots.erase(std::remove(d->plots.begin(), d->plots.end(), plot), d->plots.end()); - Abs_Plot_Private* data = plot->d; - data->render_enabled.store(false, std::memory_order_release); - data->on_render_paused(); + plot->pause_render(); } + void Render_Scheduler::shutdown_on_scheduler() { - for (Abs_Plot* plot : d->plots) { - Abs_Plot_Private* data = plot->d; - data->destroying.store(true, std::memory_order_release); - data->render_enabled.store(false, std::memory_order_release); - data->on_render_paused(); - } + for (Plot_Core* plot : d->plots) + plot->pause_render(); d->plots.clear(); } -void Render_Scheduler::initialize_plot_on_scheduler(Abs_Plot* plot) { - Abs_Plot_Private* data = plot->d; - 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()) { - data->render_enabled.store(true, std::memory_order_release); - data->on_render_started(); - } + +void Render_Scheduler::initialize_plot_on_scheduler(Plot_Core* plot) { + plot->set_viewport_size(plot->viewport_size()); + if (plot->is_rendering()) + plot->start_render(); } + Render_Scheduler& Global::render_scheduler() { return render_scheduler_value; } + void Global::start_render_scheduler() { render_scheduler_value.start(); } + } // namespace renderive diff --git a/Core/base/global.h b/Core/base/global.h new file mode 100644 index 0000000..b82bf3b --- /dev/null +++ b/Core/base/global.h @@ -0,0 +1,46 @@ +#pragma once +#include "../architecture/global.h" +#include "Task.h" +#include +namespace asio { +class io_context; +} +namespace renderive { +class Plot_Core; +struct Render_Scheduler_Private; +class LIB_DECL Render_Scheduler { +public: + Render_Scheduler(); + ~Render_Scheduler(); + void start(); + void shutdown(); + void post(Task task); + void post_at(std::uint64_t steady_time_ns, Task task); + void post_cpu(Task task); + void register_plot(Plot_Core* plot); + void remove_plot(Plot_Core* plot); + [[nodiscard]] bool is_scheduler_thread() const; + [[nodiscard]] bool is_running() const; + asio::io_context& io_context(); +private: + void join_cpu_pool(); + void post_and_wait(Task task); + void register_plot_on_scheduler(Plot_Core* plot); + void remove_plot_on_scheduler(Plot_Core* plot); + void shutdown_on_scheduler(); + void initialize_plot_on_scheduler(Plot_Core* plot); + std::unique_ptr d; +}; +class LIB_DECL Global { +public: + static Global* instance(); + static void destroy(); + Global(); + Global(const Global&) = delete; + Global& operator=(const Global&) = delete; + Render_Scheduler& render_scheduler(); + void start_render_scheduler(); +private: + Render_Scheduler render_scheduler_value; +}; +} // namespace renderive diff --git a/Core/event/Event.h b/Core/event/Event.h new file mode 100644 index 0000000..f5f1e8b --- /dev/null +++ b/Core/event/Event.h @@ -0,0 +1,55 @@ +#pragma once +#include "../base/Bitmask.h" +#include + +namespace renderive { + +enum class Event_Type : std::uint8_t { + Resize, + Show, + Hide, + Leave, + Pointer_Move, + Pointer_Press, + Pointer_Release, + Wheel, + Key_Press, + Key_Release +}; + +struct Event { + explicit Event(Event_Type t) : type(t) {} + virtual ~Event() = default; + void accept() const { + accepted = true; + } + void ignore() const { + accepted = false; + } + [[nodiscard]] bool is_accepted() const { + return accepted; + } + Event_Type type; +private: + mutable bool accepted = false; +}; + +enum class Mouse_Button : std::uint8_t { + None, + Left, + Right, + Middle +}; + +enum class Keyboard_Modifier : std::uint8_t { + Ctrl = 1 << 0, + Shift = 1 << 1, + Alt = 1 << 2, + Meta = 1 << 3 +}; + +using Keyboard_Modifier_Mask = Keyboard_Modifier; + +} // namespace renderive + +RENDERIVE_ENABLE_BITMASK(renderive::Keyboard_Modifier) diff --git a/Core/event/Key_Event.h b/Core/event/Key_Event.h new file mode 100644 index 0000000..915dc39 --- /dev/null +++ b/Core/event/Key_Event.h @@ -0,0 +1,29 @@ +#pragma once +#include "Event.h" +#include + +namespace renderive { + +enum class Key : std::uint16_t { + Unknown, + Escape, + Enter, + Space, + Delete, + Backspace, + Left, + Right, + Up, + Down +}; + +struct Key_Event : Event { + Key key = Key::Unknown; + std::uint32_t native_key{}; + Keyboard_Modifier modifiers{}; + bool auto_repeat{}; + + Key_Event(Event_Type t) : Event(t) {} +}; + +} // namespace renderive diff --git a/Core/event/Pointer_Event.h b/Core/event/Pointer_Event.h new file mode 100644 index 0000000..c7fa886 --- /dev/null +++ b/Core/event/Pointer_Event.h @@ -0,0 +1,20 @@ +#pragma once +#include "../base/Geometry.h" +#include "Event.h" + +namespace renderive { + +using Mouse_Button_Mask = std::uint8_t; + +struct Pointer_Event : Event { + PointF position; + PointF global_position; + Mouse_Button button = Mouse_Button::None; + Mouse_Button_Mask buttons{}; + Keyboard_Modifier modifiers{}; + + Pointer_Event() : Event(Event_Type::Pointer_Move) {} + explicit Pointer_Event(Event_Type t) : Event(t) {} +}; + +} // namespace renderive diff --git a/Core/event/Resize_Event.h b/Core/event/Resize_Event.h new file mode 100644 index 0000000..3c98e4b --- /dev/null +++ b/Core/event/Resize_Event.h @@ -0,0 +1,14 @@ +#pragma once +#include "../base/Geometry.h" +#include "Event.h" + +namespace renderive { + +struct Resize_Event : Event { + Size old_size; + Size new_size; + + Resize_Event() : Event(Event_Type::Resize) {} +}; + +} // namespace renderive diff --git a/Core/event/Wheel_Event.h b/Core/event/Wheel_Event.h new file mode 100644 index 0000000..3a60dfe --- /dev/null +++ b/Core/event/Wheel_Event.h @@ -0,0 +1,15 @@ +#pragma once +#include "Pointer_Event.h" + +namespace renderive { + +struct Wheel_Event : Pointer_Event { + double angle_delta_x{}; + double angle_delta_y{}; + double pixel_delta_x{}; + double pixel_delta_y{}; + + Wheel_Event() : Pointer_Event(Event_Type::Wheel) {} +}; + +} // namespace renderive diff --git a/Renderive/export.h b/Core/export.h similarity index 76% rename from Renderive/export.h rename to Core/export.h index f3e84f3..a257f3d 100644 --- a/Renderive/export.h +++ b/Core/export.h @@ -1,6 +1,6 @@ #pragma once +#include "architecture/Renderable.h" #include "Axis/export.h" #include "base/Memory.h" -#include "plot/Plot.h" #include "plottable/export.h" #include "primitive/export.h" diff --git a/Core/plot/Explicit_Refresh_Strategy.cpp b/Core/plot/Explicit_Refresh_Strategy.cpp new file mode 100644 index 0000000..f315548 --- /dev/null +++ b/Core/plot/Explicit_Refresh_Strategy.cpp @@ -0,0 +1,25 @@ +#include "Explicit_Refresh_Strategy.h" + +namespace renderive { + +void Explicit_Refresh_Strategy::request_render() { + manual_request_pending.store(true, std::memory_order_release); +} + +Refresh_Action Explicit_Refresh_Strategy::on_trigger( + Refresh_Trigger, + const Refresh_Runtime_Snapshot& runtime, + const Frame_Lifecycle_Record*) { + Refresh_Action action; + if (!manual_request_pending.load(std::memory_order_acquire)) + return action; + if (!runtime.enabled || runtime.destroying || runtime.render_job_active) + return action; + manual_request_pending.store(false, std::memory_order_release); + action.try_render = true; + if (runtime.middle_has_new_frame && !runtime.paint_active) + action.request_paint = true; + return action; +} + +} // namespace renderive diff --git a/Core/plot/Explicit_Refresh_Strategy.h b/Core/plot/Explicit_Refresh_Strategy.h new file mode 100644 index 0000000..9b7e84d --- /dev/null +++ b/Core/plot/Explicit_Refresh_Strategy.h @@ -0,0 +1,20 @@ +#pragma once +#include "Refresh_Strategy.h" +#include + +namespace renderive { + +class Explicit_Refresh_Strategy final : public Refresh_Strategy { +public: + void request_render(); + +private: + [[nodiscard]] Refresh_Action on_trigger( + Refresh_Trigger trigger, + const Refresh_Runtime_Snapshot& runtime, + const Frame_Lifecycle_Record* frame) override; + + std::atomic_bool manual_request_pending{false}; +}; + +} // namespace renderive diff --git a/Core/plot/Latency_Eager_Refresh_Strategy.cpp b/Core/plot/Latency_Eager_Refresh_Strategy.cpp new file mode 100644 index 0000000..353eda4 --- /dev/null +++ b/Core/plot/Latency_Eager_Refresh_Strategy.cpp @@ -0,0 +1,90 @@ +#include "Latency_Eager_Refresh_Strategy.h" +#include "../architecture/Render_Time.h" + +namespace renderive { + +struct Latency_Eager_Refresh_Strategy::Private { + std::atomic max_render_fps{60.0}; + Frame_Feedback_Controller feedback; +}; + +static constexpr double k_default_max_fps = 60.0; + +Latency_Eager_Refresh_Strategy::Latency_Eager_Refresh_Strategy() + : d(std::make_unique()) {} + +Latency_Eager_Refresh_Strategy::~Latency_Eager_Refresh_Strategy() = default; + +double Latency_Eager_Refresh_Strategy::max_render_fps() const { + return d->max_render_fps.load(std::memory_order_acquire); +} + +void Latency_Eager_Refresh_Strategy::set_max_render_fps(double fps) { + if (fps <= 0.0) + fps = k_default_max_fps; + d->max_render_fps.store(fps, std::memory_order_release); +} + +Refresh_Control_Snapshot Latency_Eager_Refresh_Strategy::feedback_state() const { + return d->feedback.state(); +} + +Refresh_Action Latency_Eager_Refresh_Strategy::on_trigger( + Refresh_Trigger trigger, + const Refresh_Runtime_Snapshot& runtime, + const Frame_Lifecycle_Record* frame) { + d->feedback.set_max_render_fps(max_render_fps()); + + if (trigger == Refresh_Trigger::Started) + d->feedback.reset(); + if (trigger == Refresh_Trigger::Input_Dirty) + d->feedback.on_input_changed(runtime.now_ns); + if (frame) { + switch (trigger) { + case Refresh_Trigger::Render_Finished: + d->feedback.on_render_completed(const_cast(*frame)); + break; + case Refresh_Trigger::Frame_Returned: + if (frame->outcome == Frame_Outcome::Superseded) + d->feedback.on_frame_superseded(const_cast(*frame)); + else + d->feedback.on_frame_presented(const_cast(*frame)); + break; + default: + break; + } + } + + Refresh_Action action; + if (!runtime.enabled || runtime.destroying || trigger == Refresh_Trigger::Paused) + return action; + + if (runtime.middle_has_new_frame && !runtime.paint_active) + action.request_paint = true; + + if (!runtime.dirty || runtime.render_job_active) + return action; + + Refresh_Control_Snapshot control = d->feedback.state(); + switch (control.source) { + case Refresh_Limit_Source::Paint: + if (!runtime.middle_has_new_frame) + action.try_render = true; + break; + case Refresh_Limit_Source::User: { + std::uint64_t next_ns = d->feedback.next_render_time_ns(); + if (next_ns && !d->feedback.rate_limit_ready(runtime.now_ns)) + action.render_deadline_ns = next_ns; + else + action.try_render = true; + break; + } + case Refresh_Limit_Source::Render: + case Refresh_Limit_Source::Unknown: + action.try_render = true; + break; + } + return action; +} + +} // namespace renderive diff --git a/Core/plot/Latency_Eager_Refresh_Strategy.h b/Core/plot/Latency_Eager_Refresh_Strategy.h new file mode 100644 index 0000000..1da7cb8 --- /dev/null +++ b/Core/plot/Latency_Eager_Refresh_Strategy.h @@ -0,0 +1,28 @@ +#pragma once +#include "Refresh_Strategy.h" +#include +#include +#include + +namespace renderive { + +class Latency_Eager_Refresh_Strategy final : public Refresh_Strategy { +public: + Latency_Eager_Refresh_Strategy(); + ~Latency_Eager_Refresh_Strategy() override; + + [[nodiscard]] double max_render_fps() const; + void set_max_render_fps(double fps); + [[nodiscard]] Refresh_Control_Snapshot feedback_state() const; + +private: + [[nodiscard]] Refresh_Action on_trigger( + Refresh_Trigger trigger, + const Refresh_Runtime_Snapshot& runtime, + const Frame_Lifecycle_Record* frame) override; + + struct Private; + std::unique_ptr d; +}; + +} // namespace renderive diff --git a/Core/plot/Plot_Core.cpp b/Core/plot/Plot_Core.cpp new file mode 100644 index 0000000..65f1e9f --- /dev/null +++ b/Core/plot/Plot_Core.cpp @@ -0,0 +1,528 @@ +#include "Plot_Core.h" +#include "Plot_Frame_Pipeline.h" +#include "../architecture/Renderable.h" +#include "../architecture/Render_Time.h" +#include "../architecture/Update_Completion.h" +#include "../base/global.h" +#include "../render/Canvas.h" +#include +#include +#include + +namespace renderive { + +class Plot_Core_Private final { +public: + explicit Plot_Core_Private(std::unique_ptr s) + : strategy(std::move(s)) {} + + ~Plot_Core_Private() = default; + + bool render_enabled() const { + return rendering.load(std::memory_order_acquire); + } + + bool destroying() const { + return destroying_flag.load(std::memory_order_acquire); + } + + Refresh_Runtime_Snapshot refresh_runtime_snapshot(std::uint64_t now_ns = steady_now_ns()) const { + return { + render_enabled(), + destroying(), + pipeline.has_new_state(), + pipeline.job_state != Plot_Render_Job_State::Idle, + pipeline.middle_has_new_frame(), + pipeline.paint_active(), + now_ns + }; + } + + bool submit_render_once() { + if (!rendering.load(std::memory_order_acquire) || destroying_flag.load(std::memory_order_acquire)) + return false; + if (!root) + return false; + + Size size{ + context->render_width.load(std::memory_order_acquire), + context->render_height.load(std::memory_order_acquire) + }; + if (size.empty()) + return false; + + auto frame_lease = pipeline.try_begin_render(); + if (!frame_lease) + return false; + + Plot_Frame* buffer = frame_lease.frame; + cancel_update_states(buffer->presented_update_states); + std::uint64_t render_role_enter_ns = buffer->metadata.render_role_enter_ns; + buffer->metadata = {}; + Frame_Lifecycle_Record& frame = buffer->metadata; + std::uint64_t submit_time = steady_now_ns(); + frame.frame_id = ++next_frame_id; + frame.input_version = pipeline.edit_state_version.load(std::memory_order_acquire); + frame.render_request_ns = submit_time; + frame.render_role_enter_ns = render_role_enter_ns ? render_role_enter_ns : submit_time; + frame.core_data_time_ns = latest_input_time_ns.load(std::memory_order_acquire); + frame.raw_input_count = pending_input_count.exchange(0, std::memory_order_acq_rel); + frame.pixel_width = static_cast(size.width); + frame.pixel_height = static_cast(size.height); + frame.wait_21_ns = submit_time > frame.render_role_enter_ns ? submit_time - frame.render_role_enter_ns : 0; + frame.render_begin_ns = submit_time; + + pipeline.job_state = Plot_Render_Job_State::Running; + pipeline.render_state_version = frame.input_version; + + Plot_Render_Snapshot render_snapshot; + render_snapshot.context = context; + render_snapshot.size = size; + render_snapshot.background_color = background_color(); + render_snapshot.previous_frame_time_ns = previous_frame_time_ns; + render_snapshot.frame_time_ns = submit_time; + render_snapshot.frame_metadata = &frame; + render_snapshot.frame_update_states = &buffer->presented_update_states; + previous_frame_time_ns = submit_time; + + auto renderables = renderable_tree_snapshot(); + frame.prepare_begin_ns = steady_now_ns(); + for (const auto& node : renderables) { + const auto& renderable = node.renderable; + if (!renderable || !renderable->should_prepare()) + continue; + renderable->prepare_data(render_snapshot); + ++frame.prepared_output_count; + } + frame.prepare_end_ns = steady_now_ns(); + context->first_prepare_data.store(false, std::memory_order_release); + + buffer->image.resize(size.width, size.height); + buffer->image.fill(render_snapshot.background_color); + Canvas canvas(buffer->image); + + frame.draw_begin_ns = steady_now_ns(); + if (!renderables.empty() && renderables.front().renderable) + renderables.front().renderable->render_tree_snapshot(canvas, render_snapshot, renderables, 0); + frame.draw_end_ns = steady_now_ns(); + frame.render_end_ns = steady_now_ns(); + buffer->version.store(frame.frame_id, std::memory_order_release); + for (const auto& update_state : buffer->presented_update_states) { + if (update_state) + update_state->complete_until(Update_Stage::Rendered); + } + + frame_lease.reset(); + auto publish_result = pipeline.try_publish_rendered(steady_now_ns()); + if (publish_result.superseded_middle) { + Frame_Lifecycle_Record& superseded_frame = publish_result.superseded_middle->metadata; + trigger_refresh_strategy(Refresh_Trigger::Frame_Returned, &superseded_frame); + collect_frame(superseded_frame); + supersede_update_states(publish_result.superseded_middle->presented_update_states); + } + if (publish_result.dropped_frame) { + Frame_Lifecycle_Record& dropped_frame = publish_result.dropped_frame->metadata; + collect_frame(dropped_frame); + supersede_update_states(publish_result.dropped_frame->presented_update_states); + } + + pipeline.job_state = Plot_Render_Job_State::Idle; + if (publish_result.request_present) + request_present(publish_result.dirty_rect); + trigger_refresh_strategy(Refresh_Trigger::Render_Finished, &frame); + return true; + } + + void post_scheduler_action(Task task) { + if (!task) + return; + auto guard = context; + auto action = [guard, task = std::move(task)]() mutable { + if (!guard || guard->destroying.load(std::memory_order_acquire)) + return; + task(); + }; + auto& scheduler = Global::instance()->render_scheduler(); + if (scheduler.is_scheduler_thread()) { + action(); + return; + } + scheduler.post(std::move(action)); + } + + void trigger_refresh_strategy(Refresh_Trigger trigger, Frame_Lifecycle_Record* frame = nullptr) { + if (!strategy) + return; + std::uint64_t now_ns = steady_now_ns(); + Refresh_Runtime_Snapshot runtime = refresh_runtime_snapshot(now_ns); + Refresh_Action action = strategy->on_trigger(trigger, runtime, frame); + apply_refresh_action(action, runtime); + } + + void notify_input_changed(std::uint64_t now_ns) { + if (!strategy) + return; + Refresh_Runtime_Snapshot runtime = refresh_runtime_snapshot(now_ns); + Refresh_Action action = strategy->on_trigger(Refresh_Trigger::Input_Dirty, runtime, nullptr); + apply_refresh_action(action, runtime); + } + + void notify_started() { + if (!strategy_started) { + strategy_started = true; + trigger_refresh_strategy(Refresh_Trigger::Started); + } + else { + trigger_refresh_strategy(Refresh_Trigger::Input_Dirty); + } + } + + void notify_paused() { + invalidate_refresh_deadline(); + if (!strategy_started) + return; + strategy_started = false; + trigger_refresh_strategy(Refresh_Trigger::Paused); + } + + void notify_frame_returned(Frame_Lifecycle_Record frame) { + trigger_refresh_strategy(Refresh_Trigger::Frame_Returned, &frame); + collect_frame(frame); + } + + void apply_refresh_action(Refresh_Action action, const Refresh_Runtime_Snapshot& runtime) { + if (action.request_paint) { + auto update_result = pipeline.request_paint_update(steady_now_ns()); + if (update_result.request_present) + request_present(update_result.dirty_rect); + } + if (action.try_render) { + invalidate_refresh_deadline(); + submit_render_once(); + return; + } + if (action.render_deadline_ns) { + schedule_refresh_deadline(action.render_deadline_ns); + return; + } + if (!runtime.enabled || runtime.destroying || !runtime.dirty) + invalidate_refresh_deadline(); + } + + void schedule_refresh_deadline(std::uint64_t deadline_ns) { + if (deadline_ns <= steady_now_ns()) { + invalidate_refresh_deadline(); + trigger_refresh_strategy(Refresh_Trigger::Timer_Fired); + return; + } + if (scheduled_refresh_deadline_ns && scheduled_refresh_deadline_ns <= deadline_ns) + return; + scheduled_refresh_deadline_ns = deadline_ns; + std::uint64_t generation = ++refresh_deadline_generation; + auto guard = context; + Global::instance()->render_scheduler().post_at(deadline_ns, [guard, self = this, generation, deadline_ns]() { + if (!guard || guard->destroying.load(std::memory_order_acquire)) + return; + if (self->refresh_deadline_generation != generation || self->scheduled_refresh_deadline_ns != deadline_ns) + return; + self->scheduled_refresh_deadline_ns = 0; + self->trigger_refresh_strategy(Refresh_Trigger::Timer_Fired); + }); + } + + void invalidate_refresh_deadline() { + scheduled_refresh_deadline_ns = 0; + ++refresh_deadline_generation; + } + + void dispatch_event(const Event& event) { + if (!root) + return; + switch (event.type) { + case Event_Type::Pointer_Move: { + const auto& pointer_event = static_cast(event); + update_hover_targets(pointer_event); + dispatch_to_renderables(root->hit_renderables(pointer_event.position), event); + break; + } + case Event_Type::Pointer_Press: + case Event_Type::Pointer_Release: { + const auto& pointer_event = static_cast(event); + dispatch_to_renderables(root->hit_renderables(pointer_event.position), event); + break; + } + case Event_Type::Wheel: { + const auto& wheel_event = static_cast(event); + dispatch_to_renderables(root->hit_renderables(wheel_event.position), event); + break; + } + case Event_Type::Key_Press: + case Event_Type::Key_Release: + dispatch_to_renderables(reverse_renderable_snapshot(), event); + break; + case Event_Type::Leave: + clear_hover_targets(); + dispatch_to_renderables(renderable_snapshot(), event); + break; + default: + dispatch_to_renderables(renderable_snapshot(), event); + break; + } + } + + std::vector> renderable_snapshot() const { + std::vector> result; + append_renderable_snapshot(result, root); + return result; + } + + Renderable_Tree_Snapshot renderable_tree_snapshot() const { + Renderable_Tree_Snapshot result; + if (root) + root->append_tree_snapshot(result); + return result; + } + + std::vector> reverse_renderable_snapshot() const { + std::vector> result = renderable_snapshot(); + std::reverse(result.begin(), result.end()); + return result; + } + + void append_renderable_snapshot(std::vector>& result, const std::shared_ptr& renderable) const { + if (!renderable) + return; + result.push_back(renderable); + for (const auto& child : renderable->children_snapshot()) + append_renderable_snapshot(result, child); + } + + void dispatch_to_renderables(const std::vector>& renderables, const Event& event) { + for (const auto& renderable : renderables) { + if (!renderable || event.is_accepted()) + break; + renderable->plot_event(event); + } + } + + void update_hover_targets(const Pointer_Event& event) { + Point pos{static_cast(event.position.x), static_cast(event.position.y)}; + std::vector> hits = root->hit_renderables(event.position); + for (const auto& old_target : hover_targets) { + auto target = old_target.lock(); + if (!target) + continue; + if (std::find(hits.begin(), hits.end(), target) == hits.end()) + target->set_hover_state(pos, false); + } + hover_targets.clear(); + hover_targets.reserve(hits.size()); + for (const auto& target : hits) { + if (!target) + continue; + target->set_hover_state(pos, true); + hover_targets.push_back(target); + } + } + + void clear_hover_targets() { + Point pos{}; + for (const auto& old_target : hover_targets) { + if (auto target = old_target.lock()) + target->set_hover_state(pos, false); + } + hover_targets.clear(); + } + + void request_present(Rect dirty_rect) { + if (auto target = sink.lock()) + target->request_present(dirty_rect); + } + + void collect_frame(const Frame_Lifecycle_Record& frame) { + auto observer = std::atomic_load_explicit(&context->frame_lifecycle_observer, std::memory_order_acquire); + if (observer && observer->enabled()) + observer->collect_frame(frame); + } + + Color background_color() const { + Pixel p = context->background_rgba.load(std::memory_order_acquire); + return premultiplied_to_color(p); + } + + std::unique_ptr strategy; + std::shared_ptr context = std::make_shared(); + std::shared_ptr root; + std::vector> hover_targets; + Plot_Frame_Pipeline pipeline; + std::weak_ptr sink; + std::atomic_bool rendering{false}; + std::atomic_bool destroying_flag{false}; + std::atomic latest_input_time_ns{0}; + std::atomic pending_input_count{0}; + bool strategy_started{}; + std::uint64_t refresh_deadline_generation{}; + std::uint64_t scheduled_refresh_deadline_ns{}; + std::uint64_t next_frame_id{}; + std::uint64_t previous_frame_time_ns{}; +}; + +Plot_Core::Plot_Core(std::unique_ptr strategy) + : d(std::make_unique(std::move(strategy))) { + d->context->owner.store(this, std::memory_order_release); + Global::instance()->render_scheduler().register_plot(this); +} + +Plot_Core::~Plot_Core() { + d->destroying_flag.store(true, std::memory_order_release); + d->context->destroying.store(true, std::memory_order_release); + Global::instance()->render_scheduler().remove_plot(this); + d->pipeline.cancel_all_update_states(); + d->context->owner.store(nullptr, std::memory_order_release); +} + +void Plot_Core::init() { + if (!d->root) { + d->root = std::make_shared(); + d->root->set_object_name("Root_Renderable"); + d->root->init_root(this); + } + d->context->first_resize.store(true, std::memory_order_release); +} + +std::shared_ptr Plot_Core::render_context() const { + return d->context; +} + +std::shared_ptr Plot_Core::root_renderable() const { + return d->root; +} + +std::shared_ptr Plot_Core::create_renderable_node( + const std::shared_ptr& parent, std::string object_name) { + if (!parent) + return {}; + auto node = std::make_shared(); + node->set_object_name(std::move(object_name)); + node->init(parent); + return node; +} + +void Plot_Core::remove_renderable(const std::shared_ptr& renderable) { + if (!renderable) + return; + if (renderable == d->root) + d->root.reset(); + else + renderable->detach_from_parent(); + mark_render_state_dirty(); +} + +void Plot_Core::set_background_color(Color color) { + d->context->background_rgba.store(premultiply(color), std::memory_order_release); + mark_render_state_dirty(); +} + +Color Plot_Core::background_color() const { + return d->background_color(); +} + +void Plot_Core::set_viewport_size(Size size) { + d->context->render_width.store(size.width, std::memory_order_release); + d->context->render_height.store(size.height, std::memory_order_release); + if (!size.empty()) + d->context->first_resize.store(false, std::memory_order_release); +} + +Size Plot_Core::viewport_size() const { + return { + d->context->render_width.load(std::memory_order_acquire), + d->context->render_height.load(std::memory_order_acquire) + }; +} + +void Plot_Core::dispatch_event(const Event& event) { + d->dispatch_event(event); +} + +void Plot_Core::mark_render_state_dirty() { + if (d->destroying_flag.load(std::memory_order_acquire)) + return; + std::uint64_t now_ns = steady_now_ns(); + d->pipeline.mark_edit_state(); + d->latest_input_time_ns.store(now_ns, std::memory_order_release); + d->pending_input_count.fetch_add(1, std::memory_order_relaxed); + d->post_scheduler_action([data = d.get(), now_ns]() { + data->notify_input_changed(now_ns); + }); +} + +void Plot_Core::request_manual_refresh() { + if (d->destroying_flag.load(std::memory_order_acquire)) + return; + d->post_scheduler_action([data = d.get()]() { + data->trigger_refresh_strategy(Refresh_Trigger::Manual_Request); + }); +} + +void Plot_Core::start_render() { + if (d->destroying_flag.load(std::memory_order_acquire)) + return; + d->rendering.store(true, std::memory_order_release); + d->post_scheduler_action([data = d.get()]() { + data->notify_started(); + }); +} + +void Plot_Core::pause_render() { + d->rendering.store(false, std::memory_order_release); + d->post_scheduler_action([data = d.get()]() { + data->notify_paused(); + }); +} + +bool Plot_Core::is_rendering() const { + return d->rendering.load(std::memory_order_acquire); +} + +void Plot_Core::set_presentation_sink(std::weak_ptr sink) { + d->sink = std::move(sink); +} + +Present_Frame Plot_Core::begin_present() { + Present_Frame result; + if (!d->root) + return result; + auto consumed = d->pipeline.try_begin_paint(steady_now_ns()); + if (consumed.has_returned_frame) { + d->post_scheduler_action([data = d.get(), frame = consumed.returned_frame]() mutable { + data->notify_frame_returned(frame); + }); + } + result.is_new_frame = consumed.new_frame; + result.update_was_pending = consumed.update_was_pending; + if (consumed.lease) + result.lease_ = std::make_unique(std::move(consumed.lease)); + if (!result.new_frame() && result.request_was_pending()) { + d->post_scheduler_action([data = d.get()]() { + data->trigger_refresh_strategy(Refresh_Trigger::Paint_Finished); + }); + } + return result; +} + +void Plot_Core::end_present(Present_Frame&& frame, std::uint64_t paint_begin_ns, std::uint64_t paint_end_ns) { + if (frame.lease_ && frame.lease_->frame) { + auto* rendered_frame = frame.lease_->frame; + rendered_frame->metadata.paint_begin_ns = paint_begin_ns; + rendered_frame->metadata.paint_end_ns = paint_end_ns; + rendered_frame->metadata.wait_23_ns = paint_begin_ns > rendered_frame->metadata.transition_12_ns ? paint_begin_ns - rendered_frame->metadata.transition_12_ns : 0; + complete_update_states(rendered_frame->presented_update_states, Update_Stage::Presented); + } + frame.lease_.reset(); + d->post_scheduler_action([data = d.get()]() mutable { + data->trigger_refresh_strategy(Refresh_Trigger::Paint_Finished); + }); +} + +} // namespace renderive diff --git a/Core/plot/Plot_Core.h b/Core/plot/Plot_Core.h new file mode 100644 index 0000000..6256376 --- /dev/null +++ b/Core/plot/Plot_Core.h @@ -0,0 +1,54 @@ +#pragma once +#include "../architecture/Plot_Render_Context.h" +#include "../architecture/Renderable.h" +#include "../base/Color.h" +#include "../base/Geometry.h" +#include "../event/Event.h" +#include "Present_Frame.h" +#include "Presentation_Sink.h" +#include "Refresh_Strategy.h" +#include +#include + +namespace renderive { + +class Plot_Core_Private; + +class Plot_Core { +public: + explicit Plot_Core(std::unique_ptr strategy); + ~Plot_Core(); + + void init(); + + [[nodiscard]] std::shared_ptr render_context() const; + [[nodiscard]] std::shared_ptr root_renderable() const; + [[nodiscard]] std::shared_ptr create_renderable_node( + const std::shared_ptr& parent, std::string object_name = {}); + + void remove_renderable(const std::shared_ptr& renderable); + + void set_background_color(Color color); + [[nodiscard]] Color background_color() const; + + void set_viewport_size(Size size); + [[nodiscard]] Size viewport_size() const; + + void dispatch_event(const Event& event); + void mark_render_state_dirty(); + void request_manual_refresh(); + + void start_render(); + void pause_render(); + [[nodiscard]] bool is_rendering() const; + + void set_presentation_sink(std::weak_ptr sink); + + Present_Frame begin_present(); + void end_present(Present_Frame&& frame, std::uint64_t paint_begin_ns, std::uint64_t paint_end_ns); + +private: + std::unique_ptr d; +}; + +} // namespace renderive diff --git a/Core/plot/Plot_Frame_Pipeline.cpp b/Core/plot/Plot_Frame_Pipeline.cpp new file mode 100644 index 0000000..e63bbe5 --- /dev/null +++ b/Core/plot/Plot_Frame_Pipeline.cpp @@ -0,0 +1,193 @@ +#include "Plot_Frame_Pipeline.h" +#include "../architecture/Update_Completion.h" +#include + +namespace renderive { + +Plot_Frame::Plot_Frame() : presented_update_states(memory_resource(Memory_Domain::Update_Completion)) {} + +Plot_Frame_Lease::Plot_Frame_Lease(Plot_Frame* buffer, Triple_Role_Buffer_Control* control, Triple_Buffer_Lease lease) + : frame(buffer), control(control), lease(lease) {} + +Plot_Frame_Lease::Plot_Frame_Lease(Plot_Frame_Lease&& other) noexcept + : frame(std::exchange(other.frame, nullptr)), + control(std::exchange(other.control, nullptr)), + lease(std::exchange(other.lease, Triple_Buffer_Lease{})) {} + +Plot_Frame_Lease& Plot_Frame_Lease::operator=(Plot_Frame_Lease&& other) noexcept { + if (this == &other) + return *this; + reset(); + frame = std::exchange(other.frame, nullptr); + control = std::exchange(other.control, nullptr); + lease = std::exchange(other.lease, Triple_Buffer_Lease{}); + return *this; +} + +Plot_Frame_Lease::~Plot_Frame_Lease() { + reset(); +} + +void Plot_Frame_Lease::reset() { + if (!control) + return; + control->unmark_use(lease); + frame = nullptr; + control = nullptr; + lease = {}; +} + +Plot_Frame_Pipeline::Plot_Frame_Pipeline() { + frame_control.reset(); +} + +Plot_Frame_Pipeline::~Plot_Frame_Pipeline() { + cancel_all_update_states(); +} + +void Plot_Frame_Pipeline::mark_edit_state() { + edit_state_version.fetch_add(1, std::memory_order_release); +} + +bool Plot_Frame_Pipeline::has_new_state() const { + return buffer_version_newer(edit_state_version.load(std::memory_order_acquire), render_state_version); +} + +Plot_Frame* Plot_Frame_Pipeline::frame_by_index(std::uint8_t index) { + return &frames[index]; +} + +const Plot_Frame* Plot_Frame_Pipeline::frame_by_index(std::uint8_t index) const { + return &frames[index]; +} + +Plot_Frame* Plot_Frame_Pipeline::frame_by_role(std::uint8_t role) { + return frame_by_index(frame_control.read_view()[role]); +} + +const Plot_Frame* Plot_Frame_Pipeline::frame_by_role(std::uint8_t role) const { + return frame_by_index(frame_control.read_view()[role]); +} + +Plot_Frame_Lease Plot_Frame_Pipeline::try_begin_render() { + auto record = frame_control.try_mark_use_role(Frame_Rendering); + if (record.result != Triple_Buffer_Result::Success) { + ++attempts.render_role_busy_count; + return {}; + } + return {frame_by_index(record.lease.index), &frame_control, record.lease}; +} + +Plot_Frame_Pipeline::Frame_Publish_Result Plot_Frame_Pipeline::try_publish_rendered(std::uint64_t now_ns) { + Frame_Publish_Result result; + Plot_Frame* rendered = frame_by_role(Frame_Rendering); + Plot_Frame* old_middle = frame_by_role(Frame_Middle); + const Plot_Frame* painting = frame_by_role(Frame_Painting); + bool supersede_middle = buffer_version_newer(old_middle->version.load(std::memory_order_acquire), painting->version.load(std::memory_order_acquire)); + + auto swap = frame_control.try_swap_role(Frame_Rendering, Frame_Middle, [this](const Triple_Buffer_View& view) { + const Plot_Frame* rendering = frame_by_index(view[Frame_Rendering]); + const Plot_Frame* middle = frame_by_index(view[Frame_Middle]); + return buffer_version_newer(rendering->version.load(std::memory_order_acquire), middle->version.load(std::memory_order_acquire)); + }); + if (swap.result != Triple_Buffer_Result::Success) { + rendered->metadata.outcome = Frame_Outcome::Publish_Dropped; + result.dropped_frame = rendered; + ++attempts.publish_dropped_count; + return result; + } + + rendered->metadata.transition_12_ns = now_ns; + rendered->metadata.wait_12_ns = now_ns > rendered->metadata.render_end_ns ? now_ns - rendered->metadata.render_end_ns : 0; + frame_by_role(Frame_Rendering)->metadata.render_role_enter_ns = now_ns; + + if (supersede_middle && old_middle->metadata.frame_id) { + old_middle->metadata.superseded_time_ns = now_ns; + old_middle->metadata.outcome = Frame_Outcome::Superseded; + result.superseded_middle = old_middle; + } + + Frame_Publish_Result update_result = request_paint_update(now_ns); + update_result.superseded_middle = result.superseded_middle; + update_result.dropped_frame = result.dropped_frame; + return update_result; +} + +Plot_Frame_Pipeline::Frame_Publish_Result Plot_Frame_Pipeline::request_paint_update(std::uint64_t now_ns) { + Frame_Publish_Result result; + if (!middle_has_new_frame()) + return result; + Plot_Frame* middle = frame_by_role(Frame_Middle); + if (paint_request_pending.load(std::memory_order_acquire)) { + middle->metadata.update_request_id = pending_update_request_id.load(std::memory_order_acquire); + middle->metadata.update_request_time_ns = pending_update_request_time.load(std::memory_order_acquire); + return result; + } + result.update_request_id = ++next_update_request_id; + result.update_request_time = now_ns; + middle->metadata.update_request_id = result.update_request_id; + middle->metadata.update_request_time_ns = result.update_request_time; + pending_update_request_id.store(result.update_request_id, std::memory_order_release); + pending_update_request_time.store(result.update_request_time, std::memory_order_release); + paint_request_pending.store(true, std::memory_order_release); + Size size = middle->image.size(); + result.dirty_rect = Rect{0, 0, size.width, size.height}; + result.request_present = true; + return result; +} + +Plot_Frame_Pipeline::Frame_Consume_Result Plot_Frame_Pipeline::try_begin_paint(std::uint64_t now_ns) { + Frame_Consume_Result result; + result.update_was_pending = paint_request_pending.exchange(false, std::memory_order_acq_rel); + + if (middle_has_new_frame()) { + Plot_Frame* old_painting = frame_by_role(Frame_Painting); + Plot_Frame* new_painting = frame_by_role(Frame_Middle); + auto swap = frame_control.try_swap_role(Frame_Middle, Frame_Painting, [this](const Triple_Buffer_View& view) { + const Plot_Frame* middle = frame_by_index(view[Frame_Middle]); + const Plot_Frame* painting = frame_by_index(view[Frame_Painting]); + return buffer_version_newer(middle->version.load(std::memory_order_acquire), painting->version.load(std::memory_order_acquire)); + }); + if (swap.result == Triple_Buffer_Result::Success) { + result.new_frame = true; + if (old_painting->metadata.frame_id && old_painting->metadata.paint_end_ns) { + old_painting->metadata.transition_32_ns = now_ns; + old_painting->metadata.wait_32_ns = now_ns > old_painting->metadata.paint_end_ns ? now_ns - old_painting->metadata.paint_end_ns : 0; + result.returned_frame = old_painting->metadata; + result.has_returned_frame = true; + } + new_painting->metadata.transition_23_ns = now_ns; + new_painting->metadata.wait_23_ns = now_ns > new_painting->metadata.transition_12_ns ? now_ns - new_painting->metadata.transition_12_ns : 0; + } + else { + result.paint_acquire_dropped = true; + ++attempts.paint_role_busy_count; + } + } + + auto record = frame_control.try_mark_use_role(Frame_Painting); + if (record.result == Triple_Buffer_Result::Success) + result.lease = {frame_by_index(record.lease.index), &frame_control, record.lease}; + else { + result.paint_acquire_dropped = true; + ++attempts.paint_role_busy_count; + } + return result; +} + +bool Plot_Frame_Pipeline::middle_has_new_frame() const { + const Plot_Frame* middle = frame_by_role(Frame_Middle); + const Plot_Frame* painting = frame_by_role(Frame_Painting); + return buffer_version_newer(middle->version.load(std::memory_order_acquire), painting->version.load(std::memory_order_acquire)); +} + +bool Plot_Frame_Pipeline::paint_active() const { + return frame_control.role_busy(Frame_Painting); +} + +void Plot_Frame_Pipeline::cancel_all_update_states() { + for (Plot_Frame& frame : frames) + cancel_update_states(frame.presented_update_states); +} + +} // namespace renderive diff --git a/Core/plot/Plot_Frame_Pipeline.h b/Core/plot/Plot_Frame_Pipeline.h new file mode 100644 index 0000000..b130aec --- /dev/null +++ b/Core/plot/Plot_Frame_Pipeline.h @@ -0,0 +1,95 @@ +#pragma once +#include "../architecture/Frame_Scheduler.h" +#include "../architecture/Render_Config.h" +#include "../architecture/Triple_Buffer.h" +#include "../base/Geometry.h" +#include "../base/Object_Semantics.h" +#include "../render/Image.h" +#include +#include +#include +#include +#include + +namespace renderive { + +struct Update_State; + +enum class Plot_Render_Job_State { + Idle, + Running +}; + +struct Plot_Frame { + Plot_Frame(); + std::atomic version{0}; + Image image; + std::pmr::vector> presented_update_states; + Frame_Lifecycle_Record metadata; +}; + +struct Plot_Frame_Lease : Move_Only { + Plot_Frame_Lease() = default; + Plot_Frame_Lease(Plot_Frame* buffer, Triple_Role_Buffer_Control* control, Triple_Buffer_Lease lease); + Plot_Frame_Lease(Plot_Frame_Lease&& other) noexcept; + Plot_Frame_Lease& operator=(Plot_Frame_Lease&& other) noexcept; + ~Plot_Frame_Lease(); + void reset(); + Plot_Frame* frame{}; + Triple_Role_Buffer_Control* control{}; + Triple_Buffer_Lease lease{}; + explicit operator bool() const { + return frame && lease; + } +}; + +class Plot_Frame_Pipeline { +public: + struct Frame_Publish_Result { + Rect dirty_rect; + Plot_Frame* superseded_middle{}; + Plot_Frame* dropped_frame{}; + std::uint64_t update_request_id{}; + std::uint64_t update_request_time{}; + bool request_present{}; + }; + + struct Frame_Consume_Result { + Plot_Frame_Lease lease; + Frame_Lifecycle_Record returned_frame; + bool has_returned_frame{}; + bool new_frame{}; + bool update_was_pending{}; + bool paint_acquire_dropped{}; + }; + + Plot_Frame_Pipeline(); + ~Plot_Frame_Pipeline(); + + void mark_edit_state(); + [[nodiscard]] bool has_new_state() const; + [[nodiscard]] Plot_Frame* frame_by_index(std::uint8_t index); + [[nodiscard]] const Plot_Frame* frame_by_index(std::uint8_t index) const; + [[nodiscard]] Plot_Frame* frame_by_role(std::uint8_t role); + [[nodiscard]] const Plot_Frame* frame_by_role(std::uint8_t role) const; + [[nodiscard]] Plot_Frame_Lease try_begin_render(); + [[nodiscard]] Frame_Publish_Result try_publish_rendered(std::uint64_t now_ns); + [[nodiscard]] Frame_Publish_Result request_paint_update(std::uint64_t now_ns); + [[nodiscard]] Frame_Consume_Result try_begin_paint(std::uint64_t now_ns); + [[nodiscard]] bool middle_has_new_frame() const; + [[nodiscard]] bool paint_active() const; + void cancel_all_update_states(); + + Triple_Role_Buffer_Control frame_control; + std::array frames; + std::atomic edit_state_version{1}; + std::uint64_t render_state_version = 0; + Plot_Render_Job_State job_state = Plot_Render_Job_State::Idle; + Frame_Attempt_Accumulator attempts; + std::atomic_bool paint_request_pending{false}; + std::atomic pending_update_request_id{0}; + std::atomic pending_update_request_time{0}; + std::uint64_t next_update_request_id{}; +}; + +} // namespace renderive diff --git a/Core/plot/Present_Frame.cpp b/Core/plot/Present_Frame.cpp new file mode 100644 index 0000000..b8d8562 --- /dev/null +++ b/Core/plot/Present_Frame.cpp @@ -0,0 +1,28 @@ +#include "Present_Frame.h" + +namespace renderive { + +Present_Frame::Present_Frame() = default; +Present_Frame::Present_Frame(Present_Frame&&) noexcept = default; +Present_Frame& Present_Frame::operator=(Present_Frame&&) noexcept = default; +Present_Frame::~Present_Frame() = default; + +Present_Frame::operator bool() const { + return lease_ && lease_->operator bool(); +} + +bool Present_Frame::new_frame() const { + return is_new_frame; +} + +bool Present_Frame::request_was_pending() const { + return update_was_pending; +} + +Image_View Present_Frame::image() const { + if (!lease_ || !lease_->frame) + return {}; + return lease_->frame->image.view(); +} + +} // namespace renderive diff --git a/Core/plot/Present_Frame.h b/Core/plot/Present_Frame.h new file mode 100644 index 0000000..fa15b70 --- /dev/null +++ b/Core/plot/Present_Frame.h @@ -0,0 +1,28 @@ +#pragma once +#include "Plot_Frame_Pipeline.h" +#include "../render/Image_View.h" +#include +#include + +namespace renderive { + +class Present_Frame { +public: + Present_Frame(); + Present_Frame(Present_Frame&&) noexcept; + Present_Frame& operator=(Present_Frame&&) noexcept; + ~Present_Frame(); + + [[nodiscard]] explicit operator bool() const; + [[nodiscard]] bool new_frame() const; + [[nodiscard]] bool request_was_pending() const; + [[nodiscard]] Image_View image() const; + +private: + friend class Plot_Core; + std::unique_ptr lease_; + bool is_new_frame{}; + bool update_was_pending{}; +}; + +} // namespace renderive diff --git a/Core/plot/Presentation_Sink.h b/Core/plot/Presentation_Sink.h new file mode 100644 index 0000000..51d76f0 --- /dev/null +++ b/Core/plot/Presentation_Sink.h @@ -0,0 +1,12 @@ +#pragma once +#include "../base/Geometry.h" + +namespace renderive { + +class Presentation_Sink { +public: + virtual ~Presentation_Sink() = default; + virtual void request_present(Rect dirty_rect) = 0; +}; + +} // namespace renderive diff --git a/Core/plot/Refresh_Strategy.h b/Core/plot/Refresh_Strategy.h new file mode 100644 index 0000000..36ee2c8 --- /dev/null +++ b/Core/plot/Refresh_Strategy.h @@ -0,0 +1,43 @@ +#pragma once +#include "../architecture/Frame_Scheduler.h" +#include + +namespace renderive { + +enum class Refresh_Trigger : std::uint8_t { + Started, + Input_Dirty, + Render_Finished, + Paint_Finished, + Frame_Returned, + Timer_Fired, + Manual_Request, + Paused +}; + +struct Refresh_Runtime_Snapshot { + bool enabled{}; + bool destroying{}; + bool dirty{}; + bool render_job_active{}; + bool middle_has_new_frame{}; + bool paint_active{}; + std::uint64_t now_ns{}; +}; + +struct Refresh_Action { + bool try_render{}; + bool request_paint{}; + std::uint64_t render_deadline_ns{}; +}; + +class Refresh_Strategy { +public: + virtual ~Refresh_Strategy() = default; + [[nodiscard]] virtual Refresh_Action on_trigger( + Refresh_Trigger trigger, + const Refresh_Runtime_Snapshot& runtime, + const Frame_Lifecycle_Record* frame) = 0; +}; + +} // namespace renderive diff --git a/Renderive/plottable/Afterglow.cpp b/Core/plottable/Afterglow.cpp similarity index 83% rename from Renderive/plottable/Afterglow.cpp rename to Core/plottable/Afterglow.cpp index 186b16d..f69ae9a 100644 --- a/Renderive/plottable/Afterglow.cpp +++ b/Core/plottable/Afterglow.cpp @@ -25,15 +25,13 @@ void Afterglow::set_frequency_range(Range value) { d()->set_state_value(&Afterglow_Render_State::frequency_range, std::move(value)); } Range Afterglow::power_range() { - return d()->edit_state_value(&Afterglow_Render_State::power_range); + return d()->edit_state_value(&Afterglow_Render_State::color_scale).range(); } void Afterglow::set_power_range(Range value) { if (value.size() == 0.0) return; - d()->set_state_value(&Afterglow_Render_State::power_range, std::move(value)); - auto bar = d()->color_bar_item.lock(); - if (bar) - bar->set_range(power_range()); + Render_Edit_Lease edit(d()); + edit->color_scale.set_range(value); } int Afterglow::frequency_point_size() { return d()->edit_state_value(&Afterglow_Render_State::frequency_point_size); @@ -57,16 +55,14 @@ int Afterglow::init_strong_value() { return d()->edit_state_value(&Afterglow_Render_State::init_strong_value); } void Afterglow::set_init_strong_value(int value) { - d()->set_state_value(&Afterglow_Render_State::init_strong_value, qMax(1, value)); + d()->set_state_value(&Afterglow_Render_State::init_strong_value, std::max(1, value)); } Color_Map Afterglow::color_map() { - return d()->edit_state_value(&Afterglow_Render_State::color_map); + return d()->edit_state_value(&Afterglow_Render_State::color_scale).color_map(); } void Afterglow::set_color_map(Color_Map value) { - auto bar = d()->color_bar_item.lock(); - if (bar) - bar->set_color_map(value); - d()->set_state_value(&Afterglow_Render_State::color_map, std::move(value)); + Render_Edit_Lease edit(d()); + edit->color_scale.set_color_map(std::move(value)); } std::shared_ptr Afterglow::color_bar() const { return const_cast(this)->d()->color_bar_item.lock(); @@ -74,14 +70,14 @@ std::shared_ptr Afterglow::color_bar() const { void Afterglow::set_frequency_point_size(int t) { Afterglow_Private* pd = d(); Render_Edit_Lease edit(pd); - edit->frequency_point_size = qMax(1, t); - edit->power_point_size = qMax(1, edit->power_point_size); + edit->frequency_point_size = std::max(1, t); + edit->power_point_size = std::max(1, edit->power_point_size); } void Afterglow::set_power_point_size(int t) { Afterglow_Private* pd = d(); Render_Edit_Lease edit(pd); - edit->frequency_point_size = qMax(1, edit->frequency_point_size); - edit->power_point_size = qMax(1, t); + edit->frequency_point_size = std::max(1, edit->frequency_point_size); + edit->power_point_size = std::max(1, t); } Afterglow::Builder::Builder(std::shared_ptr parent, std::shared_ptr frequency_axis, std::shared_ptr power_axis) { init(parent, frequency_axis, power_axis); @@ -95,8 +91,11 @@ void Afterglow::give_data(std::span power_range_data) { if (!ok()) return; Afterglow_Private* pd = d(); - Render_State_Lease state(pd); - int frequency_point_size = state->frequency_point_size; + int frequency_point_size{}; + { + Render_State_Lease state(pd); + frequency_point_size = state->frequency_point_size; + } if (power_range_data.size() != frequency_point_size) return; Render_Input_Lease input(pd); @@ -106,24 +105,27 @@ void Afterglow::give_data(std::pmr::vector&& power_range_data) { if (!ok()) return; Afterglow_Private* pd = d(); - Render_State_Lease state(pd); - int frequency_point_size = state->frequency_point_size; + int frequency_point_size{}; + { + Render_State_Lease state(pd); + frequency_point_size = state->frequency_point_size; + } if (power_range_data.size() != frequency_point_size) return; Render_Input_Lease input(pd); input->data_list.push(std::move(power_range_data), Afterglow_Input_Data::Max_Pending_Frame_Count); } -void Afterglow_Private::push_data(std::span power_range_data, const Afterglow_Render_State* s) { +void Afterglow_Private::push_data(std::span power_range_data, const Afterglow_Render_State* s, const Color_Scale_Snapshot& color_scale) { if (power_range_data.size() != s->frequency_point_size || s->init_strong_value <= 0 || - s->power_range.size() == 0.0 || + color_scale.range.size() == 0.0 || s->frequency_point_size <= 0 || s->power_point_size <= 0) return; std::pmr::vector index_list(s->frequency_point_size, frame_memory_resource()); - double rate = (double)s->power_point_size / s->power_range.size(); + double rate = (double)s->power_point_size / color_scale.range.size(); for (int i = 0; i < s->frequency_point_size; ++i) { - int line_index = (int)((power_range_data[i] - s->power_range.origin) * rate); + int line_index = (int)((power_range_data[i] - color_scale.range.origin) * rate); if (line_index < 0) line_index = 0; if (line_index > s->power_point_size - 1) diff --git a/Renderive/plottable/Afterglow.h b/Core/plottable/Afterglow.h similarity index 89% rename from Renderive/plottable/Afterglow.h rename to Core/plottable/Afterglow.h index f7df3a8..c2ae4cb 100644 --- a/Renderive/plottable/Afterglow.h +++ b/Core/plottable/Afterglow.h @@ -7,7 +7,7 @@ #include "../base/Memory.h" #include "global.h" #include "../primitive/Color_Bar.h" -#include "../primitive/Color_Map.h" +#include "../primitive/Color_Scale.h" namespace renderive { class Frequency_Axis; class Afterglow_Plot; @@ -65,13 +65,12 @@ struct Afterglow_Prop { std::weak_ptr frequency_axis{}; std::weak_ptr power_axis{}; Range frequency_range{0, 10}; - Range power_range{0, 10}; int frequency_point_size{}; int power_point_size{}; bool interpolate = true; double attenuation_rate = 0.2; int init_strong_value = 100; - Color_Map color_map{}; + Color_Scale color_scale{Color_Map{}, Range{0, 10}}; bool color_bar_enabled = false; }; template @@ -79,13 +78,20 @@ struct Afterglow::Builder_T { PROP_BT(std::shared_ptr, frequency_axis) PROP_BT(std::shared_ptr, power_axis) PROP_BT(Range, frequency_range) - PROP_BT(Range, power_range) + That& set_power_range(Range value) { + static_cast(this)->color_scale.set_range(value); + return *static_cast(this); + } PROP_BT(int, frequency_point_size) PROP_BT(int, power_point_size) PROP_BT(bool, interpolate) PROP_BT(double, attenuation_rate) PROP_BT(int, init_strong_value) - PROP_BT(Color_Map, color_map) + That& set_color_map(Color_Map value) { + static_cast(this)->color_scale.set_color_map(std::move(value)); + return *static_cast(this); + } + PROP_BT(Color_Scale, color_scale) PROP_BT(bool, color_bar_enabled) /// 初始化残影构造器。 void init(std::shared_ptr parent, std::shared_ptr frequency_axis, std::shared_ptr power_axis); diff --git a/Renderive/plottable/Afterglow_p.h b/Core/plottable/Afterglow_p.h similarity index 65% rename from Renderive/plottable/Afterglow_p.h rename to Core/plottable/Afterglow_p.h index 0bb8fcd..e315ff9 100644 --- a/Renderive/plottable/Afterglow_p.h +++ b/Core/plottable/Afterglow_p.h @@ -6,7 +6,6 @@ #include "../Axis/Frequency_Axis.h" #include "../Axis/Frequency_Axis_p.h" #include "../architecture/Bounded_Input_Buffer.h" -#include "../plot/Plot_p.h" #include "../base/Memory.h" #include "../base/global.h" #include "../primitive/Raster_Image_p.h" @@ -44,63 +43,61 @@ struct Afterglow_Private : Typed_Render_Data power_range_data, const Afterglow_Render_State* s); - void draw_image(QPainter* painter, const Afterglow_Render_State* s) { + void push_data(std::span power_range_data, const Afterglow_Render_State* s, const Color_Scale_Snapshot& color_scale); + void draw_image(Canvas& canvas, const Afterglow_Render_State* s, const Color_Scale_Snapshot& color_scale) { auto frequency_axis = s->frequency_axis.lock(); auto power_axis = s->power_axis.lock(); if (!frequency_axis || !power_axis || image.is_null()) return; - if (s->frequency_range.length() == 0.0 || s->power_range.length() == 0.0) + if (s->frequency_range.length() == 0.0 || color_scale.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(); - 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(); - painter->scale(hr, vr); - image.draw(painter, QRectF(hr * x, vr * y, hr * w, vr * h)); - painter->restore(); + Axis_Mapping_2D mapping = Axis_Render_Access::mapping(frequency_axis.get(), power_axis.get()); + Axis_Basis_2D basis = Axis_Basis_2D::from_ranges(mapping, s->frequency_range, color_scale.range); + canvas.save(); + canvas.transform( + basis.domain_vector.x / static_cast(image.width()), + basis.domain_vector.y / static_cast(image.width()), + basis.value_vector.x / static_cast(image.height()), + basis.value_vector.y / static_cast(image.height()), + basis.origin.x, + basis.origin.y); + image.draw(canvas, RectF{0.0, 0.0, static_cast(image.width()), static_cast(image.height())}); + canvas.restore(); } - void draw(QPainter* painter, const Plot_Render_Snapshot&) override { + void draw(Canvas& canvas, const Plot_Render_Snapshot&) override { Afterglow_Render_State* s = render_state(); - draw_image(painter, s); - } - std::size_t estimated_frame_memory(const Plot_Render_Snapshot&) override { - Afterglow_Render_State* s = render_state(); - return static_cast(std::max(0, s->frequency_point_size)) * sizeof(int) + 8 * 1024; + auto color_scale = s->color_scale.snapshot(); + draw_image(canvas, s, *color_scale); } void prepare_data(const Plot_Render_Snapshot&) override { sync_state_pipeline(); Afterglow_Render_State* s = render_state(); if (s->frequency_point_size <= 0 || s->power_point_size <= 0) return; + auto color_scale = s->color_scale.snapshot(); if (cache_frequency_point_size != s->frequency_point_size || cache_power_point_point_size != s->power_point_size) { update_cache_size(s->frequency_point_size, s->power_point_size); } bool image_size_changed = image.width() != s->frequency_point_size || image.height() != s->power_point_size; - std::uint64_t color_map_hash = s->color_map.hash(); + std::uint64_t color_map_hash = color_scale->color_map.hash(); if (cache_color_map_hash != color_map_hash) { cache_color_map_hash = color_map_hash; cache_image_dirty = true; } if (image_size_changed) { image.resize(s->frequency_point_size, s->power_point_size); - image.fill(Qt::transparent); + image.fill(Color::transparent()); cache_image_dirty = true; } Afterglow_Input_Data* input = render_input_data(); - input->data_list.read_all([this, s, input](const auto& data) { + input->data_list.read_all([this, s, color_scale](const auto& data) { if (data.size() != s->frequency_point_size) { return; } - push_data(data, s); + push_data(data, s, *color_scale); }); if (cache_image_dirty) { - image.write_normalized_grid(old_cache_power_data.data(), s->frequency_point_size, s->power_point_size, s->color_map); + image.write_normalized_grid(old_cache_power_data.data(), s->frequency_point_size, s->power_point_size, color_scale->color_map); cache_image_dirty = false; } clear_render_input(); @@ -108,11 +105,12 @@ 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->attached_to_plot(), "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->attached_to_plot(), "Afterglow build error! frequency_axis->plot == nullptr"); + ASSERT(frequency_axis->shares_plot_with(*power_axis), "Afterglow build error! frequency_axis->plot != power_axis->plot"); + ASSERT(parent->shares_plot_with(*frequency_axis), "Afterglow build error! parent->plot != frequency_axis->plot"); + ASSERT(frequency_axis->orientation() != power_axis->orientation(), "Afterglow build error! axes must be orthogonal"); this->parent = parent; static_cast(this)->frequency_axis = frequency_axis; static_cast(this)->power_axis = power_axis; @@ -126,19 +124,18 @@ void Afterglow::Builder_T::set(Afterglow* ret) { PROP_RT(std::weak_ptr, frequency_axis) PROP_RT(std::weak_ptr, power_axis) PROP_RT(Range, frequency_range) - PROP_RT(Range, power_range) PROP_RT(int, frequency_point_size) PROP_RT(int, power_point_size) PROP_RT(bool, interpolate) PROP_RT(double, attenuation_rate) PROP_RT(int, init_strong_value) - PROP_RT(Color_Map, color_map) - sc->frequency_point_size = qMax(1, sc->frequency_point_size); - sc->power_point_size = qMax(1, sc->power_point_size); + sc->color_scale = static_cast(this)->color_scale.clone(); + sc->frequency_point_size = std::max(1, sc->frequency_point_size); + sc->power_point_size = std::max(1, sc->power_point_size); sc->attenuation_rate = std::clamp(sc->attenuation_rate, 0.0, 1.0); - sc->init_strong_value = qMax(1, sc->init_strong_value); + sc->init_strong_value = std::max(1, sc->init_strong_value); if (static_cast(this)->color_bar_enabled) { - auto bar = Color_Bar::Builder(owner).set_color_map(sc->color_map).set_range(sc->power_range).set_name("Afterglow").build(); + auto bar = Color_Bar::Builder(owner).set_color_scale(sc->color_scale).set_name("Afterglow").build(); pd->color_bar_item = bar; } } diff --git a/Renderive/plottable/Audio_Frequency.cpp b/Core/plottable/Audio_Frequency.cpp similarity index 55% rename from Renderive/plottable/Audio_Frequency.cpp rename to Core/plottable/Audio_Frequency.cpp index 2d0b8cf..78c6ad8 100644 --- a/Renderive/plottable/Audio_Frequency.cpp +++ b/Core/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); - } } 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->attached_to_plot(), "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->attached_to_plot(), "Audio_Frequency build error! time_axis->plot == nullptr"); + ASSERT(time_axis->shares_plot_with(*value_axis), "Audio_Frequency build error! time_axis->plot != value_axis->plot"); + ASSERT(parent->shares_plot_with(*time_axis), "Audio_Frequency build error! parent->plot != time_axis->plot"); + ASSERT(time_axis->orientation() != value_axis->orientation(), "Audio_Frequency build error! axes must be orthogonal"); this->parent = parent; this->time_axis = time_axis; this->value_axis = value_axis; @@ -55,21 +31,9 @@ std::shared_ptr Audio_Frequency::Builder::build() { ret->init(parent); Audio_Frequency_Private* pd = ret->d(); Audio_Frequency_Render_State* sc = reinterpret_cast(pd->state(State_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; - } + sc->pen = Pen{.color = color, .width = 1.0}; return ret; } void Audio_Frequency::give_data(int tick, double data) { @@ -78,7 +42,7 @@ void Audio_Frequency::give_data(int tick, double data) { Render_Input_Lease input(d()); input->push(tick, data); } -void Audio_Frequency::give_data(QTime time, double data) { +void Audio_Frequency::give_data(Time_Of_Day time, double data) { if (!ok()) return; auto axis = time_axis(); diff --git a/Renderive/plottable/Audio_Frequency.h b/Core/plottable/Audio_Frequency.h similarity index 72% rename from Renderive/plottable/Audio_Frequency.h rename to Core/plottable/Audio_Frequency.h index a42c0fd..621b563 100644 --- a/Renderive/plottable/Audio_Frequency.h +++ b/Core/plottable/Audio_Frequency.h @@ -1,6 +1,9 @@ #pragma once +#include "../Axis/Axis.h" +#include "../base/Color.h" +#include "../base/Style.h" +#include "../base/Time_Of_Day.h" #include "global.h" -#include "../primitive/Curve.h" namespace renderive { class Time_Axis; struct Audio_Frequency_Private; @@ -12,17 +15,11 @@ public: /// @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); + void give_data(Time_Of_Day time, double data); /// @brief 绑定时间轴。 PROP(std::shared_ptr, time_axis) /// @brief 绑定数值轴。 PROP(std::shared_ptr, value_axis) - /// @brief 曲线数据值范围。 - QColor color(); - void set_color(QColor value); /// @brief 曲线颜色。 struct Builder; private: @@ -34,23 +31,17 @@ private: struct Audio_Frequency_Prop { std::weak_ptr time_axis; std::weak_ptr value_axis{}; + Pen pen{.color = Color::yellow(), .width = 1.0}; }; 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; - } + PROP_B(Color, color) /// 创建音频频率构造器。 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{}; + Color color = Color::yellow(); }; } // namespace renderive diff --git a/Renderive/plottable/Audio_Frequency_p.h b/Core/plottable/Audio_Frequency_p.h similarity index 70% rename from Renderive/plottable/Audio_Frequency_p.h rename to Core/plottable/Audio_Frequency_p.h index 8da3c34..42cad1b 100644 --- a/Renderive/plottable/Audio_Frequency_p.h +++ b/Core/plottable/Audio_Frequency_p.h @@ -5,11 +5,10 @@ #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/Memory.h" -#include "../primitive/Curve.h" -#include "Core/Base/RingBuffer.hpp" +#include "../primitive/Curve_Utils_p.h" +#include "Psc_Cpp_Core/Base/RingBuffer.hpp" #include "Audio_Frequency.h" namespace renderive { struct Audio_Frequency_Data { @@ -40,15 +39,13 @@ struct Audio_Frequency_Render_State : Render_State, Audio_Frequency_Prop {}; 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 prepared_points{memory_resource(Memory_Domain::Audio)}; int data_count{}; int point_count{}; void resize_power_buffer(int count) { point_count = count; 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) { @@ -63,8 +60,6 @@ struct Audio_Frequency_Private : Typed_Render_Data(i)] = data_snapshot[static_cast(i)].power; } void prepare_data(const Plot_Render_Snapshot& snapshot) override { sync_state_pipeline(); @@ -74,7 +69,7 @@ struct Audio_Frequency_Private : Typed_Render_Datavalue_axis.lock(); if (!time_axis || !value_axis) return; - auto timeline = snapshot.timeline_snapshot(time_axis->timeline_stream()); + auto timeline = Time_Axis_Render_Access::capture_timeline(time_axis.get(), snapshot); if (!timeline) return; if (point_count != timeline->time_point_size) @@ -83,18 +78,31 @@ struct Audio_Frequency_Private : Typed_Render_Datalower, 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)); + prepared_points.clear(); + prepared_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); + int coord{}; + if (timeline->coord_by_stream_tick(item.tick, coord)) + prepared_points.emplace_back(static_cast(coord), item.power); } - if (auto curve = curve_item.lock()) - curve->give_points(points, false); clear_render_input(); } + void draw(Canvas& canvas, const Plot_Render_Snapshot&) override { + Audio_Frequency_Render_State* s = render_state(); + auto time_axis = s->time_axis.lock(); + auto value_axis = s->value_axis.lock(); + if (!time_axis || !value_axis || prepared_points.empty()) + return; + Axis_Mapping_2D mapping = Axis_Render_Access::mapping(time_axis.get(), value_axis.get()); + std::pmr::vector pixels(frame_memory_resource()); + pixels.reserve(prepared_points.size()); + for (const PointF& point : prepared_points) { + pixels.push_back(mapping.map(point)); + } + canvas.save(); + Curve_Utils::draw_polyline(&canvas, pixels, s->pen); + canvas.restore(); + } }; } diff --git a/Core/plottable/Hover_Info.cpp b/Core/plottable/Hover_Info.cpp new file mode 100644 index 0000000..b420ab8 --- /dev/null +++ b/Core/plottable/Hover_Info.cpp @@ -0,0 +1,40 @@ +#include "Hover_Info.h" +#include "../Axis/Abs_Axis_p.h" +namespace renderive { +void Hover_Info_Render_State::draw_hover(Canvas* canvas, Abs_Axis* h, Abs_Axis* v, Hover_Info_Renderable_Interface* renderable) { + Point pos = hover_info_pos; + std::vector lines = renderable->create_hover_string(h, v, pos); + double max_width = 0; + double line_height = 0; + for (const std::string& s : lines) { + Text_Metrics metrics = canvas->measure_text(hover_info_font, s); + max_width = std::max(max_width, metrics.width); + line_height = std::max(line_height, metrics.line_height()); + } + Rect rect(pos.x + hover_info_left, pos.y + hover_info_top, + static_cast(max_width) + hover_info_right + hover_info_left, + static_cast(line_height * lines.size()) + hover_info_bottom); + canvas->set_brush(hover_info_brush); + canvas->fill_rect(RectF{(double)rect.x, (double)rect.y, (double)rect.width, (double)rect.height}); + canvas->set_pen(hover_info_pen); + canvas->set_font(hover_info_font); + int lh = static_cast(line_height); + for (int i = 0; i < (int)lines.size(); ++i) { + RectF line_rect((double)rect.x, (double)(rect.y + i * lh), (double)rect.width, (double)lh); + canvas->draw_text(line_rect, Text_Align::Center, lines[i]); + } +} +bool Hover_Info_Render_State::hover_ok(Renderable* able) const { + auto tmp = dynamic_cast(able); + 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, Point pos) { + Axis_Mapping_2D mapping = Axis_Render_Access::mapping(h, v); + PointF screen_point{static_cast(pos.x), static_cast(pos.y)}; + double tick1 = mapping.domain.pixel_to_coord(mapping.domain_pixel(screen_point)); + double tick2 = mapping.value.pixel_to_coord(mapping.value_pixel(screen_point)); + std::string line1 = Axis_Render_Access::tick_label(h, tick1) + h->unit_text(); + std::string line2 = Axis_Render_Access::tick_label(v, tick2) + v->unit_text(); + return {line1, line2}; +} +} // namespace renderive diff --git a/Renderive/plottable/Hover_Info.h b/Core/plottable/Hover_Info.h similarity index 78% rename from Renderive/plottable/Hover_Info.h rename to Core/plottable/Hover_Info.h index 135408e..319dc03 100644 --- a/Renderive/plottable/Hover_Info.h +++ b/Core/plottable/Hover_Info.h @@ -1,26 +1,33 @@ #pragma once #include +#include #include +#include #include "../Axis/export.h" +#include "../base/Color.h" +#include "../base/Geometry.h" +#include "../base/Style.h" +#include "../base/Text.h" +#include "../render/Canvas.h" namespace renderive { struct Hover_Info_Render_State; struct Hover_Info_Renderable_Interface; struct LIB_DECL Hover_Info_Render_State { virtual ~Hover_Info_Render_State() = default; Hover_Info_Render_State() { - hover_info_brush = QBrush(Qt::white); + hover_info_brush = Brush{Color::white(), Brush_Style::Solid}; } int hover_info_left = 4; int hover_info_top = 4; int hover_info_right = 4; int hover_info_bottom = 4; - QBrush hover_info_brush; - QFont hover_info_font; - QPen hover_info_pen; + Brush hover_info_brush; + Font hover_info_font; + Pen hover_info_pen; bool use_hover_info = true; bool hover_info_active = false; - QPoint hover_info_pos; - void draw_hover(QPainter* painter, Abs_Axis* h, Abs_Axis* v, Hover_Info_Renderable_Interface* renderable); + Point hover_info_pos; + void draw_hover(Canvas* canvas, Abs_Axis* h, Abs_Axis* v, Hover_Info_Renderable_Interface* renderable); bool hover_ok(Renderable* able) const; }; struct LIB_DECL Hover_Info_Renderable_Interface { @@ -63,16 +70,16 @@ struct LIB_DECL Hover_Info_Renderable_Interface { Hover_Info_Render_State* state{}; std::function on_finish{}; }; - virtual std::vector create_hover_string(Abs_Axis* h, Abs_Axis* v, QPoint pos); + virtual std::vector create_hover_string(Abs_Axis* h, Abs_Axis* v, Point pos); virtual ~Hover_Info_Renderable_Interface() = default; - virtual void draw_hover(QPainter* painter, Abs_Axis* h, Abs_Axis* v) { - return hover_render_state()->draw_hover(painter, h, v, this); + virtual void draw_hover(Canvas* canvas, Abs_Axis* h, Abs_Axis* v) { + return hover_render_state()->draw_hover(canvas, h, v, this); } virtual bool hover_ok(Renderable* able) { return hover_render_state()->hover_ok(able); } - virtual bool hover_test(QPoint pos) { - Q_UNUSED(pos) + virtual bool hover_test(Point pos) { + (void)pos; return true; } bool use_hover_info() { @@ -82,26 +89,26 @@ struct LIB_DECL Hover_Info_Renderable_Interface { auto edit = begin_hover_edit(); edit->use_hover_info = use; } - void set_hover_info_font(const QFont& font) { + void set_hover_info_font(const Font& font) { auto edit = begin_hover_edit(); edit->hover_info_font = font; } - QFont hover_info_font() { + Font hover_info_font() { return hover_edit_state()->hover_info_font; } - QBrush hover_info_background_brush() { + Brush hover_info_background_brush() { return hover_edit_state()->hover_info_brush; } - void set_hover_info_background_brush(const QBrush& brush) { + void set_hover_info_background_brush(const Brush& brush) { auto edit = begin_hover_edit(); - edit->hover_info_brush = brush.color().isValid() ? brush : Qt::NoBrush; + edit->hover_info_brush = brush.color.alpha != 0 ? brush : Brush{}; } - QPen hover_info_pen() { + Pen hover_info_pen() { return hover_edit_state()->hover_info_pen; } - void set_hover_info_pen(const QPen& pen) { + void set_hover_info_pen(const Pen& pen) { auto edit = begin_hover_edit(); - edit->hover_info_pen = pen.color().isValid() ? pen : Qt::NoPen; + edit->hover_info_pen = pen.color.alpha != 0 ? pen : Pen{.style = Line_Style::None}; } void get_hover_info_contents_margins(int& left, int& top, int& right, int& bottom) { auto s = hover_edit_state(); @@ -141,7 +148,7 @@ private: } Hover_Edit begin_hover_edit_impl() override { auto t = that(); - auto lease = t->d_ptr->state_control.wait_mark_use_role(State_Edit); + auto lease = t->d_ptr->state_control.mark_use_role(State_Edit); auto* data = t->d_ptr; auto* state = dynamic_cast(data->state_by_index(lease.index)); return Hover_Edit(this, state, [data, lease]() mutable { diff --git a/Core/plottable/Interpolation.h b/Core/plottable/Interpolation.h new file mode 100644 index 0000000..b55bf90 --- /dev/null +++ b/Core/plottable/Interpolation.h @@ -0,0 +1,16 @@ +#pragma once +#include "../render/Image_Interpolation_Mode.h" +#include + +namespace renderive { + +enum class Line_Interpolation_Mode : std::uint8_t { + Nearest_Bin, + Linear_Display, + Linear_Power, + Step_Left, + Step_Right, + Cubic_Display +}; + +} // namespace renderive diff --git a/Renderive/plottable/Interpolation_p.h b/Core/plottable/Interpolation_p.h similarity index 65% rename from Renderive/plottable/Interpolation_p.h rename to Core/plottable/Interpolation_p.h index 7ed090b..f6097aa 100644 --- a/Renderive/plottable/Interpolation_p.h +++ b/Core/plottable/Interpolation_p.h @@ -1,12 +1,13 @@ #pragma once -#include -#include #include #include #include #include #include "../Axis/Abs_Axis_p.h" #include "../base/Frame_Memory.h" +#include "../base/Color.h" +#include "../base/Geometry.h" +#include "../render/Image.h" #include "Interpolation.h" namespace renderive { struct Line_Sample_Window { @@ -35,22 +36,25 @@ inline double source_index_to_coord(const Range& data_range, int total_count, do 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) { +inline Line_Sample_Window line_sample_window(const Axis_Frame_Snapshot& 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)) + if (visible_range_only && !intersect_range(available_range, axis.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); + double first_pixel = axis.coord_to_pixel(sample_range.origin); + double last_pixel = axis.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); + pixel_samples = std::min(pixel_samples, axis.pixel_size() + 1); return {first_index, last_index, std::max(source_samples, pixel_samples)}; } +inline Line_Sample_Window line_sample_window(Abs_Axis* axis, const Range& data_range, int total_count, int available_count, bool visible_range_only) { + return line_sample_window(Axis_Render_Access::snapshot(axis), data_range, total_count, available_count, visible_range_only); +} 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)); @@ -87,27 +91,31 @@ double interpolate_line_value(double index, int count, Line_Interpolation_Mode m 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); +std::pmr::vector create_line_points(const Axis_Mapping_2D& mapping, const Range& data_range, int total_count, int available_count, bool visible_range_only, Line_Interpolation_Mode mode, Value_At value_at, std::pmr::memory_resource* resource = frame_memory_resource()) { + Line_Sample_Window window = line_sample_window(mapping.domain, data_range, total_count, available_count, visible_range_only); if (!window) return {}; - std::pmr::vector points(window.sample_count, frame_memory_resource()); + std::pmr::vector points(window.sample_count, 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)); + points[i] = mapping.map(coord, value); } return points; } +template +std::pmr::vector create_line_points(Abs_Axis* domain_axis, Abs_Axis* value_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(Axis_Render_Access::mapping(domain_axis, value_axis), data_range, total_count, available_count, visible_range_only, mode, value_at, frame_memory_resource()); +} 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) { +inline Pixel bicubic_pixel(const Image& source, double x, double y) { int x1 = static_cast(std::floor(x)); int y1 = static_cast(std::floor(y)); double tx = x - x1; @@ -115,30 +123,30 @@ inline QRgb bicubic_pixel(const QImage& source, double x, double y) { 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]{}; + const Pixel* source_line = source.row(source_y); + Pixel 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); + rows[0][row + 1] = cubic_channel((pixels[0] >> 24) & 0xFF, (pixels[1] >> 24) & 0xFF, (pixels[2] >> 24) & 0xFF, (pixels[3] >> 24) & 0xFF, tx); + rows[1][row + 1] = cubic_channel((pixels[0] >> 16) & 0xFF, (pixels[1] >> 16) & 0xFF, (pixels[2] >> 16) & 0xFF, (pixels[3] >> 16) & 0xFF, tx); + rows[2][row + 1] = cubic_channel((pixels[0] >> 8) & 0xFF, (pixels[1] >> 8) & 0xFF, (pixels[2] >> 8) & 0xFF, (pixels[3] >> 8) & 0xFF, tx); + rows[3][row + 1] = cubic_channel(pixels[0] & 0xFF, pixels[1] & 0xFF, pixels[2] & 0xFF, pixels[3] & 0xFF, 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])); + return pack_rgba(static_cast(channels[1]), static_cast(channels[2]), static_cast(channels[3]), static_cast(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); +inline Image bicubic_scale(const Image& source, const RectF& source_rect, const Size& target_size) { + Image target(target_size.width, target_size.height); 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; + Pixel* line = target.row(y); + double source_y = source_rect.y + (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; + double source_x = source_rect.x + (static_cast(x) + 0.5) * source_rect.width / target.width() - 0.5; line[x] = bicubic_pixel(source, source_x, source_y); } } diff --git a/Renderive/plottable/Multi_Select_Rect.cpp b/Core/plottable/Multi_Select_Rect.cpp similarity index 66% rename from Renderive/plottable/Multi_Select_Rect.cpp rename to Core/plottable/Multi_Select_Rect.cpp index 4466ed6..13f8a50 100644 --- a/Renderive/plottable/Multi_Select_Rect.cpp +++ b/Core/plottable/Multi_Select_Rect.cpp @@ -3,11 +3,12 @@ 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->attached_to_plot(), "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->attached_to_plot(), "Multi_Select_Rect build error! key_axis->plot == nullptr"); + ASSERT(key_axis->shares_plot_with(*value_axis), "Multi_Select_Rect build error! key_axis->plot != value_axis->plot"); + ASSERT(parent->shares_plot_with(*key_axis), "Multi_Select_Rect build error! parent->plot != key_axis->plot"); + ASSERT(key_axis->orientation() != value_axis->orientation(), "Multi_Select_Rect build error! axes must be orthogonal"); this->parent = parent; this->key_axis = key_axis; this->value_axis = value_axis; @@ -33,35 +34,35 @@ void Multi_Select_Rect::set_vertical_axis(std::shared_ptr axis) { Render_Edit_Lease edit(d()); edit->vertical_axis = std::weak_ptr(axis); } -QFont Multi_Select_Rect::font() { +Font Multi_Select_Rect::font() { return d()->state_data(SRC::Edit)->font; } -QPen Multi_Select_Rect::font_pen() { +Pen Multi_Select_Rect::font_pen() { return d()->state_data(SRC::Edit)->font_pen; } -QBrush Multi_Select_Rect::rect_brush() { +Brush Multi_Select_Rect::rect_brush() { return d()->state_data(SRC::Edit)->rect_brush; } -QPen Multi_Select_Rect::border_pen() { +Pen Multi_Select_Rect::border_pen() { return d()->state_data(SRC::Edit)->border_pen; } -void Multi_Select_Rect::set_font(const QFont& font) { +void Multi_Select_Rect::set_font(const Font& font) { Render_Edit_Lease edit(d()); edit->font = font; } -void Multi_Select_Rect::set_font_pen(const QPen& pen) { +void Multi_Select_Rect::set_font_pen(const Pen& pen) { Render_Edit_Lease edit(d()); - edit->font_pen = pen.color().isValid() ? pen : Qt::NoPen; + edit->font_pen = pen.color.alpha != 0 ? pen : Pen{.style = Line_Style::None}; } -void Multi_Select_Rect::set_rect_brush(const QBrush& brush) { +void Multi_Select_Rect::set_rect_brush(const Brush& brush) { Render_Edit_Lease edit(d()); - edit->rect_brush = brush.color().isValid() ? brush : Qt::NoBrush; + edit->rect_brush = brush.color.alpha != 0 ? brush : Brush{}; } -void Multi_Select_Rect::set_border_pen(const QPen& pen) { +void Multi_Select_Rect::set_border_pen(const Pen& pen) { Render_Edit_Lease edit(d()); - edit->border_pen = pen.color().isValid() ? pen : Qt::NoPen; + edit->border_pen = pen.color.alpha != 0 ? pen : Pen{.style = Line_Style::None}; } -std::vector Multi_Select_Rect::selection_rects() { +std::vector Multi_Select_Rect::selection_rects() { return d()->state_data(SRC::Edit)->rects; } void Multi_Select_Rect::clear_selection() { diff --git a/Renderive/plottable/Multi_Select_Rect.h b/Core/plottable/Multi_Select_Rect.h similarity index 64% rename from Renderive/plottable/Multi_Select_Rect.h rename to Core/plottable/Multi_Select_Rect.h index 9088443..eb35f0b 100644 --- a/Renderive/plottable/Multi_Select_Rect.h +++ b/Core/plottable/Multi_Select_Rect.h @@ -1,7 +1,18 @@ #pragma once +#include +#include "../base/Color.h" +#include "../base/Geometry.h" +#include "../base/Style.h" +#include "../base/Text.h" #include "global.h" namespace renderive { struct Multi_Select_Rect_Private; +inline Brush default_multi_select_rect_brush() { + return Brush{Color{0, 0, 255, 50}, Brush_Style::Solid}; +} +inline Pen default_multi_select_border_pen() { + return Pen{Color::white(), 1.0, Line_Style::Dash}; +} /// @brief 多区域框选图元。 class LIB_DECL Multi_Select_Rect : public Renderable { public: @@ -12,22 +23,22 @@ public: /// @brief 设置垂直坐标轴。 void set_vertical_axis(std::shared_ptr axis); /// @brief 返回标注字体。 - QFont font(); + Font font(); /// @brief 返回标注画笔。 - QPen font_pen(); + Pen font_pen(); /// @brief 返回矩形填充刷。 - QBrush rect_brush(); + Brush rect_brush(); /// @brief 返回矩形边框画笔。 - QPen border_pen(); + Pen border_pen(); /// @brief 设置标注字体。 - void set_font(const QFont& font); + void set_font(const Font& font); /// @brief 设置标注画笔。 - void set_font_pen(const QPen& pen); + void set_font_pen(const Pen& pen); /// @brief 设置矩形填充刷。 - void set_rect_brush(const QBrush& brush); + void set_rect_brush(const Brush& brush); /// @brief 设置矩形边框画笔。 - void set_border_pen(const QPen& pen); - std::vector selection_rects(); + void set_border_pen(const Pen& pen); + std::vector selection_rects(); void clear_selection(); struct Builder; private: @@ -38,14 +49,10 @@ private: }; /// @brief 多区域框选构造器。 struct LIB_DECL Multi_Select_Rect::Builder { - SETTER(QFont, font, QFont()) - SETTER(QPen, font_pen, QPen(Qt::white)) - SETTER(QBrush, rect_brush, QColor(0, 0, 255, 50)) - SETTER(QPen, border_pen, []() { - QPen pen(Qt::white); - pen.setStyle(Qt::DashLine); - return pen; - }()) + SETTER(Font, font, Font()) + SETTER(Pen, font_pen, Pen{.color = Color::white()}) + SETTER(Brush, rect_brush, default_multi_select_rect_brush()) + SETTER(Pen, border_pen, default_multi_select_border_pen()) /// @brief 创建多区域框选构造器。 Builder(std::shared_ptr parent, std::shared_ptr key_axis, std::shared_ptr value_axis); /// @brief 构建并返回多区域框选对象。 diff --git a/Core/plottable/Multi_Select_Rect_p.h b/Core/plottable/Multi_Select_Rect_p.h new file mode 100644 index 0000000..d9eb0c1 --- /dev/null +++ b/Core/plottable/Multi_Select_Rect_p.h @@ -0,0 +1,174 @@ +#pragma once +#include +#include +#include +#include +#include +#include "../Axis/Abs_Axis_p.h" +#include "../event/Event.h" +#include "../event/Key_Event.h" +#include "../event/Pointer_Event.h" +#include "../base/String_Format.h" +#include "../render/Canvas.h" +#include "Multi_Select_Rect.h" + +namespace renderive { + +struct Multi_Select_Rect_Render_State : Render_State { + std::vector rects; + Font font; + Pen border_pen, font_pen = Pen{Color::white()}; + Brush rect_brush = Brush{Color{0, 0, 255, 50}, Brush_Style::Solid}; + std::weak_ptr horizontal_axis{}, vertical_axis{}; + Multi_Select_Rect_Render_State() { + border_pen.style = Line_Style::Dash; + border_pen.color = Color::white(); + } +}; + +struct Multi_Select_Rect_Input_Data : Input_Data {}; +struct Multi_Select_Rect_Private : Typed_Render_Data, Hit_Testable, Pointer_Interactive, Key_Press_Interactive { + std::atomic_bool active{false}; + + bool select_test(const PointF& pos) override { + auto s = render_state(); + auto horizontal_axis = s->horizontal_axis.lock(); + auto vertical_axis = s->vertical_axis.lock(); + if (!horizontal_axis || !vertical_axis) + return false; + Axis_Mapping_2D mapping = Axis_Render_Access::mapping(horizontal_axis.get(), vertical_axis.get()); + PointF first = mapping.map(mapping.domain.coord_range.origin, mapping.value.coord_range.origin); + PointF second = mapping.map(mapping.domain.coord_range.target, mapping.value.coord_range.target); + double rx = std::min(first.x, second.x); + double ry = std::min(first.y, second.y); + double rw = std::max(first.x, second.x) - rx; + double rh = std::max(first.y, second.y) - ry; + return RectF{rx, ry, rw, rh}.contains(pos); + } + + void mouse_press_event(const Pointer_Event& event) override { + if (event.button != Mouse_Button::Left) + return; + Render_Edit_Lease edit(this); + auto horizontal_axis = edit->horizontal_axis.lock(); + auto vertical_axis = edit->vertical_axis.lock(); + if (!horizontal_axis || !vertical_axis) + return; + Axis_Mapping_2D mapping = Axis_Render_Access::mapping(horizontal_axis.get(), vertical_axis.get()); + std::vector& rect_list = edit->rects; + if (!static_cast(event.modifiers & Keyboard_Modifier::Ctrl)) { + rect_list.clear(); + } else { + int n = static_cast(rect_list.size()); + for (int i = n - 1; i >= 0; i--) { + auto& rect = rect_list[i]; + if (std::abs(rect.width) < 1e-12 || std::abs(rect.height) < 1e-12) + rect_list.erase(rect_list.begin() + i); + } + } + PointF screen_point{static_cast(event.position.x), static_cast(event.position.y)}; + double x = mapping.domain.pixel_to_coord(mapping.domain_pixel(screen_point)); + double y = mapping.value.pixel_to_coord(mapping.value_pixel(screen_point)); + rect_list.push_back(RectF{x, y, 0.0, 0.0}); + active.store(true, std::memory_order_release); + event.accept(); + } + + void mouse_move_event(const Pointer_Event& event) override { + if (!active.load(std::memory_order_acquire)) + return; + if (!(event.buttons & 1)) + return; + Render_Edit_Lease edit(this); + auto horizontal_axis = edit->horizontal_axis.lock(); + auto vertical_axis = edit->vertical_axis.lock(); + if (!horizontal_axis || !vertical_axis) + return; + Axis_Mapping_2D mapping = Axis_Render_Access::mapping(horizontal_axis.get(), vertical_axis.get()); + std::vector& rect_list = edit->rects; + if (rect_list.empty()) + return; + PointF screen_point{static_cast(event.position.x), static_cast(event.position.y)}; + double x = mapping.domain.pixel_to_coord(mapping.domain_pixel(screen_point)); + double y = mapping.value.pixel_to_coord(mapping.value_pixel(screen_point)); + rect_list.back().width = x - rect_list.back().x; + rect_list.back().height = y - rect_list.back().y; + event.accept(); + } + + void mouse_release_event(const Pointer_Event& event) override { + if (event.button != Mouse_Button::Left || !active.load(std::memory_order_acquire)) + return; + active.store(false, std::memory_order_release); + Render_Edit_Lease edit(this); + if (!edit->rects.empty() && + (std::abs(edit->rects.back().width) < 1e-12 || std::abs(edit->rects.back().height) < 1e-12)) + edit->rects.pop_back(); + event.accept(); + } + + void key_press_event(const Key_Event& event) override { + if (event.key != Key::Escape || !active.load(std::memory_order_acquire)) + return; + Render_Edit_Lease edit(this); + active.store(false, std::memory_order_release); + std::vector& rect_list = edit->rects; + if (rect_list.empty()) + return; + rect_list.pop_back(); + event.accept(); + } + + void draw(Canvas& canvas, const Plot_Render_Snapshot&) override { + Multi_Select_Rect_Render_State* s = render_state(); + auto horizontal_axis = s->horizontal_axis.lock(); + auto vertical_axis = s->vertical_axis.lock(); + if (!horizontal_axis || !vertical_axis) + return; + Axis_Mapping_2D mapping = Axis_Render_Access::mapping(horizontal_axis.get(), vertical_axis.get()); + std::vector draw_rect_list; + std::vector coord_rect_list; + for (RectF r : s->rects) { + if (std::abs(r.width) < 1e-12 || std::abs(r.height) < 1e-12) + continue; + coord_rect_list.push_back(r.normalized()); + PointF first = mapping.map(r.x, r.y); + PointF second = mapping.map(r.right(), r.bottom()); + RectF dr{first.x, first.y, second.x - first.x, second.y - first.y}; + draw_rect_list.push_back(dr.normalized()); + } + canvas.save(); + canvas.set_font(horizontal_axis->unit_text_font()); + Text_Metrics xfm = canvas.measure_text("M"); + canvas.set_font(vertical_axis->unit_text_font()); + Text_Metrics yfm = canvas.measure_text("M"); + canvas.set_pen(s->border_pen); + canvas.set_brush(s->rect_brush); + for (auto& rect : draw_rect_list) + canvas.draw_rect(rect); + canvas.set_pen(s->font_pen); + canvas.set_font(s->font); + int n = static_cast(draw_rect_list.size()); + for (int i = 0; i < n; ++i) { + RectF& rect = draw_rect_list[i]; + RectF& coord_rect = coord_rect_list[i]; + double offset_x = coord_rect.right() - coord_rect.x; + double offset_y = coord_rect.bottom() - coord_rect.y; + std::string text1 = "offset_x == " + format_number(offset_x, Number_Format{Number_Notation::General, 6}) + + horizontal_axis->unit_text(); + std::string text2 = "offset_y == " + format_number(offset_y, Number_Format{Number_Notation::General, 6}) + + vertical_axis->unit_text(); + double width1 = canvas.measure_text(text1).width + 32.0; + double width2 = canvas.measure_text(text2).width + 32.0; + double rect1_x = rect.x + rect.width / 2.0 - width1 / 2.0; + double rect2_x = rect.x + rect.width / 2.0 - width2 / 2.0; + RectF rect1{rect1_x, rect.y, width1, xfm.line_height()}; + RectF rect2{rect2_x, rect1.bottom(), width2, yfm.line_height()}; + canvas.draw_text(rect1, Text_Align::Center, text1); + canvas.draw_text(rect2, Text_Align::Center, text2); + } + canvas.restore(); + } +}; + +} // namespace renderive diff --git a/Core/plottable/Performance_Shower.cpp b/Core/plottable/Performance_Shower.cpp new file mode 100644 index 0000000..b075189 --- /dev/null +++ b/Core/plottable/Performance_Shower.cpp @@ -0,0 +1,249 @@ +#include "Performance_Shower_p.h" +#include +#include +#include +#include +#include "../base/global.h" +#include "../plot/Plot_Core.h" +namespace renderive { +namespace { +double duration_ms(std::uint64_t begin_ns, std::uint64_t end_ns) { + return end_ns > begin_ns ? static_cast(end_ns - begin_ns) / 1000000.0 : 0.0; +} +double cache_ratio(std::uint64_t hit, std::uint64_t miss) { + std::uint64_t total = hit + miss; + return total ? static_cast(hit) / static_cast(total) : 0.0; +} +std::string source_text(Refresh_Limit_Source source) { + switch (source) { + case Refresh_Limit_Source::Unknown: + return "unknown"; + case Refresh_Limit_Source::Render: + return "render"; + case Refresh_Limit_Source::Paint: + return "paint"; + case Refresh_Limit_Source::User: + return "user"; + } + return "unknown"; +} +std::string outcome_text(Frame_Outcome outcome) { + switch (outcome) { + case Frame_Outcome::Presented: + return "presented"; + case Frame_Outcome::Superseded: + return "superseded"; + case Frame_Outcome::Render_Acquire_Dropped: + return "render_acquire_dropped"; + case Frame_Outcome::Publish_Dropped: + return "publish_dropped"; + case Frame_Outcome::Paint_Acquire_Dropped: + return "paint_acquire_dropped"; + case Frame_Outcome::Worker_Budget_Dropped: + return "worker_budget_dropped"; + case Frame_Outcome::Cancelled: + return "cancelled"; + } + return "unknown"; +} +std::string csv_escape(const std::string& value) { + std::string escaped; + escaped.reserve(value.size() + 2); + escaped.push_back('"'); + for (char ch : value) { + if (ch == '"') + escaped.push_back('"'); + escaped.push_back(ch); + } + escaped.push_back('"'); + return escaped; +} +std::string renderable_cache_stats_text(const Frame_Lifecycle_Record& frame) { + std::ostringstream stream; + bool first = true; + for (const auto& stat : frame.renderable_cache_stats) { + if (!first) + stream << '|'; + first = false; + stream << stat.object_name + << ":parent=" << stat.parent_object_name + << ";depth=" << stat.tree_depth + << ";hit=" << stat.hit_count + << ";miss=" << stat.miss_count + << ";rebuild=" << stat.rebuild_count + << ";rebuild_ns=" << stat.rebuild_time_ns + << ";compose_ns=" << stat.compose_time_ns + << ";subtree_compose_ns=" << renderable_cache_subtree_compose_time(frame.renderable_cache_stats, stat) + << ";last_rebuild_ns=" << stat.last_rebuild_time_ns + << ";last_compose_ns=" << stat.last_compose_time_ns + << ";image_bytes=" << stat.last_image_bytes + << ";image_area=" << stat.last_image_area + << ";version=" << stat.last_edit_version << '/' << stat.last_ready_version; + } + return stream.str(); +} +std::string renderable_stats_text(const Frame_Lifecycle_Record& frame) { + std::ostringstream stream; + bool first = true; + for (const auto& stat : frame.renderable_stats) { + if (!first) + stream << '|'; + first = false; + stream << stat.object_name + << ":parent=" << stat.parent_object_name + << ";depth=" << stat.tree_depth + << ";cache=" << (stat.cache_enabled ? 1 : 0) + << ";render=" << stat.render_count + << ";self_draw=" << stat.self_draw_count + << ";render_ns=" << stat.total_render_time_ns + << ";self_draw_ns=" << stat.self_draw_time_ns + << ";last_render_ns=" << stat.last_total_render_time_ns + << ";last_self_draw_ns=" << stat.last_self_draw_time_ns; + } + return stream.str(); +} +class Performance_Frame_Log { +public: + void write(const Frame_Lifecycle_Record& frame) { + if (!enabled()) + return; + const Refresh_Control_Snapshot& refresh = frame.refresh; + std::ostringstream stream; + stream << std::setprecision(9) + << frame.frame_id << ',' + << frame.input_version << ',' + << outcome_text(frame.outcome) << ',' + << source_text(refresh.source) << ',' + << refresh.render_period_ns << ',' + << refresh.paint_period_ns << ',' + << refresh.user_period_ns << ',' + << refresh.target_period_ns << ',' + << duration_ms(frame.render_request_ns, frame.render_begin_ns) << ',' + << duration_ms(frame.prepare_begin_ns, frame.prepare_end_ns) << ',' + << duration_ms(frame.draw_begin_ns, frame.draw_end_ns) << ',' + << duration_ms(frame.render_begin_ns, frame.render_end_ns) << ',' + << duration_ms(frame.transition_12_ns, frame.paint_begin_ns ? frame.paint_begin_ns : frame.superseded_time_ns) << ',' + << duration_ms(frame.update_request_time_ns, frame.paint_begin_ns) << ',' + << duration_ms(frame.paint_begin_ns, frame.paint_end_ns) << ',' + << duration_ms(frame.core_data_time_ns, frame.paint_end_ns) << ',' + << frame.wait_21_ns << ',' + << frame.wait_12_ns << ',' + << frame.wait_23_ns << ',' + << frame.wait_32_ns << ',' + << frame.raw_input_count << ',' + << frame.prepared_output_count << ',' + << frame.renderable_cache_hit_count << ',' + << frame.renderable_cache_miss_count << ',' + << frame.renderable_cache_rebuild_count << ',' + << frame.renderable_cache_rebuild_time_ns << ',' + << frame.renderable_cache_compose_time_ns << ',' + << frame.renderable_cache_image_bytes << ',' + << frame.renderable_cache_image_area << ',' + << cache_ratio(frame.renderable_cache_hit_count, frame.renderable_cache_miss_count) << ',' + << frame.pixel_width << ',' + << frame.pixel_height << ',' + << frame.core_data_time_ns << ',' + << frame.render_begin_ns << ',' + << frame.render_end_ns << ',' + << frame.transition_12_ns << ',' + << frame.transition_23_ns << ',' + << frame.paint_begin_ns << ',' + << frame.paint_end_ns << ',' + << frame.transition_32_ns << ',' + << frame.superseded_time_ns << ',' + << csv_escape(renderable_stats_text(frame)) << ',' + << csv_escape(renderable_cache_stats_text(frame)); + csv_log(path(), header(), stream.str()); + } +private: + static bool enabled() { + static bool value = []() { + const char* env = std::getenv("RENDERIVE_PERFORMANCE_LOG"); + return env && std::atoi(env) != 0; + }(); + return value; + } + static std::string path() { + static std::string value = []() { + const char* configured = std::getenv("RENDERIVE_PERFORMANCE_LOG_PATH"); + if (!configured || configured[0] == '\0') { + return (std::filesystem::current_path() / "renderive_performance_log.csv").string(); + } + return std::string(configured); + }(); + return value; + } + static std::string header() { + return "frame_id,input_version,outcome,limit_source,render_period_ns,paint_period_ns,user_period_ns,target_period_ns,request_wait_ms,prepare_ms,draw_ms,render_ms,middle_wait_ms,update_wait_ms,paint_ms,frame_age_ms,wait_21_ns,wait_12_ns,wait_23_ns,wait_32_ns,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,core_data_time_ns,render_begin_time_ns,render_end_time_ns,transition_12_ns,transition_23_ns,paint_begin_time_ns,paint_end_time_ns,transition_32_ns,superseded_time_ns,renderable_stats,renderable_cache_stats"; + } +}; +Performance_Frame_Log& frame_log() { + static Performance_Frame_Log value; + return value; +} +static std::shared_ptr performance_shower_owner(const std::shared_ptr& ctx) { + if (!ctx) + return {}; + auto observer = std::atomic_load_explicit(&ctx->frame_lifecycle_observer, std::memory_order_acquire); + return std::dynamic_pointer_cast(observer); +} +} +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(Plot_Core& plot) { + auto ctx = plot.render_context(); + if (!ctx) + return {}; + auto shower = performance_shower_owner(ctx); + if (shower) + return shower; + auto created_shower = renderive::make_shared(); + created_shower->init(plot.root_renderable()); + std::atomic_store_explicit(&ctx->frame_lifecycle_observer, std::static_pointer_cast(created_shower), std::memory_order_release); + return created_shower; +} +void detach_performance_shower(Plot_Core& plot) { + auto ctx = plot.render_context(); + if (!ctx) + return; + auto shower = performance_shower_owner(ctx); + std::atomic_store_explicit(&ctx->frame_lifecycle_observer, std::shared_ptr{}, std::memory_order_release); + if (shower) { + shower->setEnabled(false); + plot.remove_renderable(shower); + } +} +bool performance_shower_enabled(const Plot_Core& plot) { + auto shower = performance_shower_owner(plot.render_context()); + return shower && shower->enabled(); +} +void set_performance_shower_enabled(Plot_Core& plot, bool enabled) { + if (!enabled) { + detach_performance_shower(plot); + return; + } + auto shower = attach_performance_shower(plot); + if (shower) + shower->setEnabled(true); +} +void Performance_Shower::setEnabled(bool enabled) { + d()->enabled_value.store(enabled, std::memory_order_release); + if (enabled) { + d()->metrics_worker->reset_requested.store(true, std::memory_order_release); + d()->schedule_metrics_worker(); + } +} +bool Performance_Shower::enabled() const { + return const_cast(this)->d()->enabled_value.load(std::memory_order_acquire); +} +void Performance_Shower::collect_frame(const Frame_Lifecycle_Record& frame) { + if (!enabled()) + return; + d()->enqueue_frame_metric(frame); + write_frame_performance_log(frame); +} +void write_frame_performance_log(const Frame_Lifecycle_Record& frame) { + Global::instance()->render_scheduler().post_cpu([frame]() { + frame_log().write(frame); + }); +} +} // namespace renderive diff --git a/Core/plottable/Performance_Shower_p.h b/Core/plottable/Performance_Shower_p.h new file mode 100644 index 0000000..ca23b3a --- /dev/null +++ b/Core/plottable/Performance_Shower_p.h @@ -0,0 +1,704 @@ +#pragma once +#include +#include +#include +#include +#include +#include +#include +#include +#include "../architecture/Frame_Scheduler.h" +#include "../architecture/Renderable.h" +#include "../architecture/Render_Lease.h" +#include "../base/Color.h" +#include "../base/Geometry.h" +#include "../base/Style.h" +#include "../base/String_Format.h" +#include "../base/Text.h" +#include "../base/global.h" +#include "../event/Wheel_Event.h" +#include "../render/Canvas.h" +#include "Psc_Cpp_Core/Statistics/Statistics.h" +namespace renderive { +void write_frame_performance_log(const Frame_Lifecycle_Record& frame); +struct Performance_Shower_Render_State : Render_State { + Font font; + int top_margin = 4; + int left_margin = 6; + int right_margin = 12; + int bottom_margin = 6; + int scroll_bar_width = 8; + int scroll_bar_margin = 2; + Color background = Color::white(); + Color foreground = Color::black(); + Color scroll_track = Color{220, 220, 220}; + Color scroll_thumb = Color{80, 80, 80}; +}; +struct Performance_Shower_Input_Data : Input_Data {}; +struct Renderable_Cache_Aggregate_Stat { + std::string object_name; + std::string parent_object_name; + int tree_depth{}; + std::uint64_t hit_count{}; + std::uint64_t miss_count{}; + std::uint64_t rebuild_count{}; + std::uint64_t rebuild_time_ns{}; + std::uint64_t compose_time_ns{}; + std::uint64_t subtree_compose_time_ns{}; + std::uint64_t last_rebuild_time_ns{}; + std::uint64_t last_compose_time_ns{}; + std::uint64_t last_image_bytes{}; + std::uint64_t last_image_area{}; + std::uint64_t last_edit_version{}; + std::uint64_t last_ready_version{}; + Psc::Value_Statistics rebuild_ms; + Psc::Value_Statistics compose_ms; + Psc::Value_Statistics subtree_compose_ms; +}; +struct Renderable_Aggregate_Stat { + std::string object_name; + std::string parent_object_name; + int tree_depth{}; + bool cache_enabled{}; + std::uint64_t render_count{}; + std::uint64_t self_draw_count{}; + std::uint64_t total_render_time_ns{}; + std::uint64_t self_draw_time_ns{}; + std::uint64_t last_total_render_time_ns{}; + std::uint64_t last_self_draw_time_ns{}; + Psc::Value_Statistics total_render_ms; + Psc::Value_Statistics self_draw_ms; + Psc::Value_Statistics total_render_ratio; + Psc::Value_Statistics self_draw_ratio; +}; +struct Frame_Performance_Aggregate { + Psc::Value_Statistics request_wait_ms; + Psc::Value_Statistics render_queue_wait_ms; + Psc::Value_Statistics prepare_ms; + Psc::Value_Statistics draw_ms; + Psc::Value_Statistics render_ms; + Psc::Value_Statistics ready_wait_ms; + Psc::Value_Statistics update_wait_ms; + Psc::Value_Statistics paint_ms; + Psc::Value_Statistics frame_age_ms; + Psc::Value_Statistics frame_total_ms; + Psc::Value_Statistics cache_rebuild_ms; + Psc::Value_Statistics cache_compose_ms; + Psc::Value_Statistics cache_rebuild_ratio; + Psc::Value_Statistics cache_compose_ratio; + Frame_Lifecycle_Record latest; + std::uint64_t observed_frame_count{}; + std::uint64_t render_time_ns{}; + std::uint64_t consumed_outcome_count{}; + std::uint64_t superseded_outcome_count{}; + std::uint64_t cancelled_outcome_count{}; + std::uint64_t cache_hit_count{}; + std::uint64_t cache_miss_count{}; + std::uint64_t cache_rebuild_count{}; + std::uint64_t cache_rebuild_time_ns{}; + std::uint64_t cache_compose_time_ns{}; + std::uint64_t input_capacity_dropped{}; + bool has_frame{}; + std::vector renderable_stats; + std::vector renderable_cache_stats; + void clear() { + *this = {}; + } + void update(const Frame_Lifecycle_Record& frame) { + latest = frame; + has_frame = true; + ++observed_frame_count; + std::uint64_t frame_render_time_ns = duration_ns(frame.render_begin_ns, frame.render_end_ns); + render_time_ns += frame_render_time_ns; + update_duration(request_wait_ms, frame.render_request_ns, frame.render_begin_ns); + update_duration(render_queue_wait_ms, frame.render_role_enter_ns, frame.render_begin_ns); + update_duration(prepare_ms, frame.prepare_begin_ns, frame.prepare_end_ns); + update_duration(draw_ms, frame.draw_begin_ns, frame.draw_end_ns); + update_duration(render_ms, frame.render_begin_ns, frame.render_end_ns); + update_duration(ready_wait_ms, frame.transition_12_ns, frame.paint_begin_ns ? frame.paint_begin_ns : frame.superseded_time_ns); + update_duration(update_wait_ms, frame.update_request_time_ns, frame.paint_begin_ns); + update_duration(paint_ms, frame.paint_begin_ns, frame.paint_end_ns); + update_duration(frame_age_ms, frame.core_data_time_ns, frame.paint_end_ns); + update_duration(frame_total_ms, frame.render_request_ns, frame.transition_32_ns ? frame.transition_32_ns : frame.superseded_time_ns); + cache_hit_count += frame.renderable_cache_hit_count; + cache_miss_count += frame.renderable_cache_miss_count; + cache_rebuild_count += frame.renderable_cache_rebuild_count; + cache_rebuild_time_ns += frame.renderable_cache_rebuild_time_ns; + cache_compose_time_ns += frame.renderable_cache_compose_time_ns; + if (frame.renderable_cache_rebuild_time_ns) + cache_rebuild_ms.update(ns_to_ms(frame.renderable_cache_rebuild_time_ns)); + if (frame.renderable_cache_compose_time_ns) + cache_compose_ms.update(ns_to_ms(frame.renderable_cache_compose_time_ns)); + if (frame_render_time_ns) { + cache_rebuild_ratio.update(static_cast(frame.renderable_cache_rebuild_time_ns) * 100.0 / static_cast(frame_render_time_ns)); + cache_compose_ratio.update(static_cast(frame.renderable_cache_compose_time_ns) * 100.0 / static_cast(frame_render_time_ns)); + } + for (const auto& stat : frame.renderable_stats) { + Renderable_Aggregate_Stat& aggregate_stat = renderable_stat(stat.object_name, stat.parent_object_name, stat.tree_depth); + aggregate_stat.cache_enabled = stat.cache_enabled; + aggregate_stat.render_count += stat.render_count; + aggregate_stat.self_draw_count += stat.self_draw_count; + aggregate_stat.total_render_time_ns += stat.total_render_time_ns; + aggregate_stat.self_draw_time_ns += stat.self_draw_time_ns; + aggregate_stat.last_total_render_time_ns = stat.last_total_render_time_ns ? stat.last_total_render_time_ns : aggregate_stat.last_total_render_time_ns; + aggregate_stat.last_self_draw_time_ns = stat.last_self_draw_time_ns ? stat.last_self_draw_time_ns : aggregate_stat.last_self_draw_time_ns; + if (stat.total_render_time_ns) + aggregate_stat.total_render_ms.update(ns_to_ms(stat.total_render_time_ns)); + if (stat.self_draw_time_ns) + aggregate_stat.self_draw_ms.update(ns_to_ms(stat.self_draw_time_ns)); + if (frame_render_time_ns) { + aggregate_stat.total_render_ratio.update(static_cast(stat.total_render_time_ns) * 100.0 / static_cast(frame_render_time_ns)); + aggregate_stat.self_draw_ratio.update(static_cast(stat.self_draw_time_ns) * 100.0 / static_cast(frame_render_time_ns)); + } + } + for (const auto& stat : frame.renderable_cache_stats) { + Renderable_Cache_Aggregate_Stat& aggregate_stat = cache_stat(stat.object_name, stat.parent_object_name, stat.tree_depth); + aggregate_stat.hit_count += stat.hit_count; + aggregate_stat.miss_count += stat.miss_count; + aggregate_stat.rebuild_count += stat.rebuild_count; + aggregate_stat.rebuild_time_ns += stat.rebuild_time_ns; + aggregate_stat.compose_time_ns += stat.compose_time_ns; + aggregate_stat.last_rebuild_time_ns = stat.last_rebuild_time_ns ? stat.last_rebuild_time_ns : aggregate_stat.last_rebuild_time_ns; + aggregate_stat.last_compose_time_ns = stat.last_compose_time_ns ? stat.last_compose_time_ns : aggregate_stat.last_compose_time_ns; + aggregate_stat.last_image_bytes = stat.last_image_bytes ? stat.last_image_bytes : aggregate_stat.last_image_bytes; + aggregate_stat.last_image_area = stat.last_image_area ? stat.last_image_area : aggregate_stat.last_image_area; + aggregate_stat.last_edit_version = stat.last_edit_version ? stat.last_edit_version : aggregate_stat.last_edit_version; + aggregate_stat.last_ready_version = stat.last_ready_version ? stat.last_ready_version : aggregate_stat.last_ready_version; + if (stat.rebuild_time_ns) + aggregate_stat.rebuild_ms.update(ns_to_ms(stat.rebuild_time_ns)); + if (stat.compose_time_ns) + aggregate_stat.compose_ms.update(ns_to_ms(stat.compose_time_ns)); + } + update_cache_subtree_compose(frame.renderable_cache_stats); + switch (frame.outcome) { + case Frame_Outcome::Presented: + ++consumed_outcome_count; + break; + case Frame_Outcome::Superseded: + ++superseded_outcome_count; + break; + case Frame_Outcome::Render_Acquire_Dropped: + case Frame_Outcome::Publish_Dropped: + case Frame_Outcome::Paint_Acquire_Dropped: + case Frame_Outcome::Worker_Budget_Dropped: + case Frame_Outcome::Cancelled: + ++cancelled_outcome_count; + break; + } + } +private: + Renderable_Aggregate_Stat& renderable_stat(const std::string& object_name, const std::string& parent_object_name, int tree_depth) { + for (auto& stat : renderable_stats) { + if (stat.object_name == object_name && stat.parent_object_name == parent_object_name && stat.tree_depth == tree_depth) + return stat; + } + Renderable_Aggregate_Stat stat; + stat.object_name = object_name; + stat.parent_object_name = parent_object_name; + stat.tree_depth = tree_depth; + renderable_stats.push_back(std::move(stat)); + return renderable_stats.back(); + } + void update_cache_subtree_compose(const std::vector& frame_stats) { + for (const auto& stat : frame_stats) { + std::uint64_t subtree_compose_time_ns = renderable_cache_subtree_compose_time(frame_stats, stat); + if (!subtree_compose_time_ns) + continue; + Renderable_Cache_Aggregate_Stat& aggregate_stat = cache_stat(stat.object_name, stat.parent_object_name, stat.tree_depth); + aggregate_stat.subtree_compose_time_ns += subtree_compose_time_ns; + aggregate_stat.subtree_compose_ms.update(ns_to_ms(subtree_compose_time_ns)); + } + } + Renderable_Cache_Aggregate_Stat& cache_stat(const std::string& object_name, const std::string& parent_object_name, int tree_depth) { + for (auto& stat : renderable_cache_stats) { + if (stat.object_name == object_name && stat.parent_object_name == parent_object_name) + return stat; + } + Renderable_Cache_Aggregate_Stat stat; + stat.object_name = object_name; + stat.parent_object_name = parent_object_name; + stat.tree_depth = tree_depth; + renderable_cache_stats.push_back(std::move(stat)); + return renderable_cache_stats.back(); + } + static double ns_to_ms(std::uint64_t ns) { + return static_cast(ns) / 1000000.0; + } + static std::uint64_t duration_ns(std::uint64_t begin_ns, std::uint64_t end_ns) { + return end_ns > begin_ns ? end_ns - begin_ns : 0; + } + static void update_duration(Psc::Value_Statistics& statistics, std::uint64_t begin_ns, std::uint64_t end_ns) { + if (end_ns > begin_ns) + statistics.update(static_cast(end_ns - begin_ns) / 1000000.0); + } +}; +struct Performance_Shower_Private; +class Performance_Shower : public Renderable, public Frame_Lifecycle_Observer { +public: + Performance_Shower(); + void setEnabled(bool enabled); + bool enabled() const override; + void collect_frame(const Frame_Lifecycle_Record& frame) override; +private: + Performance_Shower_Private* d(); + Render_Object_Owner create_render_data() override; + Render_Object_Owner create_render_state() override; + Render_Object_Owner create_input_data() override; +}; + +struct Performance_Display_Snapshot { + std::vector lines; +}; + +struct Performance_Frame_Metrics_Sample { + std::uint64_t sequence{}; + Frame_Lifecycle_Record frame; +}; + +struct Performance_Metrics_Worker_State { + static constexpr std::size_t Capacity = 256; + std::array>, Capacity> metric_slots; + std::atomic metric_write_sequence{0}; + std::atomic metric_consumed_sequence{0}; + std::atomic metric_capacity_dropped{0}; + std::atomic_bool worker_active{false}; + std::atomic_bool cancelled{false}; + std::atomic_bool reset_requested{false}; + std::uint64_t metric_read_sequence{}; + Frame_Performance_Aggregate aggregate; + std::vector> stable_value_widths; + std::atomic> display_snapshot_slot; +}; + +struct Performance_Shower_Private : Typed_Render_Data, Hit_Testable, Wheel_Interactive { + Frame_Performance_Aggregate aggregate; + std::atomic_bool enabled_value{false}; + std::shared_ptr metrics_worker = std::make_shared(); + std::shared_ptr display_snapshot; + std::vector lines; + std::vector> stable_value_widths; + Rect panel_rect; + Rect scroll_bar_rect; + Rect scroll_thumb_rect; + std::atomic scroll_line{0}; + std::atomic max_scroll_line{0}; + int content_width{}; + int held_content_width{}; + ~Performance_Shower_Private() override { + metrics_worker->cancelled.store(true, std::memory_order_release); + for (auto& slot : metrics_worker->metric_slots) + slot.store({}, std::memory_order_release); + } + void enqueue_frame_metric(const Frame_Lifecycle_Record& frame) { + auto sample = std::make_shared(); + std::uint64_t sequence = metrics_worker->metric_write_sequence.fetch_add(1, std::memory_order_acq_rel); + sample->sequence = sequence; + sample->frame = frame; + auto replaced = metrics_worker->metric_slots[sequence % Performance_Metrics_Worker_State::Capacity].exchange(std::move(sample), std::memory_order_acq_rel); + if (replaced && replaced->sequence >= metrics_worker->metric_consumed_sequence.load(std::memory_order_acquire)) + metrics_worker->metric_capacity_dropped.fetch_add(1, std::memory_order_relaxed); + schedule_metrics_worker(); + } + void schedule_metrics_worker() { + if (metrics_worker->worker_active.exchange(true, std::memory_order_acq_rel)) + return; + auto state = metrics_worker; + std::weak_ptr owner = q() ? std::weak_ptr(q()->shared_from_this()) : std::weak_ptr(); + Global::instance()->render_scheduler().post_cpu([state, owner]() { + run_metrics_worker(state, owner); + }); + } + static void run_metrics_worker(const std::shared_ptr& state, const std::weak_ptr& owner) { + bool changed = false; + if (state->reset_requested.exchange(false, std::memory_order_acq_rel)) { + state->aggregate.clear(); + state->stable_value_widths.clear(); + changed = true; + } + std::uint64_t dropped = state->metric_capacity_dropped.exchange(0, std::memory_order_acq_rel); + if (dropped) { + state->aggregate.input_capacity_dropped += dropped; + changed = true; + } + std::uint64_t write_sequence = state->metric_write_sequence.load(std::memory_order_acquire); + std::uint64_t first_sequence = write_sequence > Performance_Metrics_Worker_State::Capacity ? write_sequence - Performance_Metrics_Worker_State::Capacity : 0; + if (state->metric_read_sequence < first_sequence) { + state->aggregate.input_capacity_dropped += first_sequence - state->metric_read_sequence; + state->metric_read_sequence = first_sequence; + changed = true; + } + for (std::uint64_t sequence = state->metric_read_sequence; sequence < write_sequence; ++sequence) { + auto sample = state->metric_slots[sequence % Performance_Metrics_Worker_State::Capacity].exchange({}, std::memory_order_acq_rel); + if (!sample || sample->sequence != sequence) + continue; + state->aggregate.update(sample->frame); + state->metric_consumed_sequence.store(sequence + 1, std::memory_order_release); + changed = true; + } + state->metric_read_sequence = write_sequence; + if (changed) + state->display_snapshot_slot.store(build_display_snapshot(state->aggregate, state->stable_value_widths), std::memory_order_release); + state->worker_active.store(false, std::memory_order_release); + if (!state->cancelled.load(std::memory_order_acquire) && state->metric_write_sequence.load(std::memory_order_acquire) > state->metric_read_sequence) { + if (!state->worker_active.exchange(true, std::memory_order_acq_rel)) { + Global::instance()->render_scheduler().post_cpu([state, owner]() { + run_metrics_worker(state, owner); + }); + } + } + (void)owner; + } + static std::shared_ptr build_display_snapshot(const Frame_Performance_Aggregate& aggregate, std::vector>& stable_value_widths) { + Performance_Shower_Private builder; + builder.aggregate = aggregate; + builder.stable_value_widths = stable_value_widths; + builder.rebuild_lines(); + stable_value_widths = builder.stable_value_widths; + auto snapshot = std::make_shared(); + snapshot->lines = std::move(builder.lines); + return snapshot; + } + void prepare_data(const Plot_Render_Snapshot&) override { + sync_state_pipeline(); + auto published = metrics_worker->display_snapshot_slot.load(std::memory_order_acquire); + if (published && published != display_snapshot) { + display_snapshot = std::move(published); + lines = display_snapshot->lines; + held_content_width = 0; + } + } + bool select_test(const PointF& pos) override { + return q()->enabled() && panel_rect.contains(Point{static_cast(pos.x), static_cast(pos.y)}); + } + void draw(Canvas& canvas, const Plot_Render_Snapshot& snapshot) override { + if (!q()->enabled() || lines.empty()) + return; + Performance_Shower_Render_State* state = render_state(); + canvas.save(); + canvas.set_font(state->font); + Text_Metrics metrics = canvas.measure_text("M"); + update_content_width(metrics, state); + int line_height = static_cast(metrics.line_height()); + int panel_width = std::min(content_width, std::max(0, snapshot.size.width)); + int height = panel_height(metrics, state, snapshot.size.height); + int visible_count = visible_line_count(metrics, state, height); + int max_scroll = std::max(0, static_cast(lines.size()) - visible_count); + int scroll = std::clamp(scroll_line.load(std::memory_order_acquire), 0, max_scroll); + max_scroll_line.store(max_scroll, std::memory_order_release); + scroll_line.store(scroll, std::memory_order_release); + panel_rect = Rect{0, 0, panel_width, height}; + canvas.set_brush(Brush{state->background, Brush_Style::Solid}); + canvas.fill_rect(RectF{(double)panel_rect.x, (double)panel_rect.y, (double)panel_rect.width, (double)panel_rect.height}); + canvas.set_pen(Pen{state->foreground}); + Rect text_rect{ + panel_rect.x + state->left_margin, + panel_rect.y + state->top_margin, + panel_rect.width - state->right_margin - state->scroll_bar_width - state->scroll_bar_margin * 2, + panel_rect.height - state->bottom_margin + }; + canvas.set_clip_rect(RectF{(double)text_rect.x, (double)text_rect.y, (double)text_rect.width, (double)text_rect.height}); + int last_line = std::min(static_cast(lines.size()), scroll + visible_count); + for (int i = scroll; i < last_line; ++i) + canvas.draw_text(PointF{(double)(state->left_margin), (double)(state->top_margin + (i - scroll + 1) * line_height)}, lines[i]); + canvas.reset_clip(); + update_scroll_bar_rects(state, max_scroll, scroll, visible_count); + if (scroll_bar_rect.width > 0 && scroll_bar_rect.height > 0) { + canvas.set_brush(Brush{state->scroll_track, Brush_Style::Solid}); + canvas.fill_rect(RectF{(double)scroll_bar_rect.x, (double)scroll_bar_rect.y, (double)scroll_bar_rect.width, (double)scroll_bar_rect.height}); + canvas.set_brush(Brush{state->scroll_thumb, Brush_Style::Solid}); + canvas.fill_rect(RectF{(double)scroll_thumb_rect.x, (double)scroll_thumb_rect.y, (double)scroll_thumb_rect.width, (double)scroll_thumb_rect.height}); + } + canvas.restore(); + } + Rect pixel_bounds(const Size& plot_size) override { + if (!q()->enabled() || lines.empty()) + return {}; + Performance_Shower_Render_State* state = render_state(); + Font save_font = state->font; + Text_Metrics metrics; + metrics.width = 0; + metrics.ascent = 12; + metrics.descent = 4; + metrics.line_gap = 0; + update_content_width(metrics, state); + int height = panel_height(metrics, state, plot_size.height); + return Rect{0, 0, std::min(content_width, plot_size.width), height}; + } + void wheel_event(const Wheel_Event& event) override { + if (!q()->enabled() || !panel_rect.contains(Point{static_cast(event.position.x), static_cast(event.position.y)})) + return; + int max_scroll = max_scroll_line.load(std::memory_order_acquire); + if (!max_scroll) + return; + int delta = static_cast(event.angle_delta_y); + if (!delta) + return; + int step = std::max(1, std::abs(delta) / 120) * 3; + int current = scroll_line.load(std::memory_order_acquire); + int next = std::clamp(current + (delta > 0 ? -step : step), 0, max_scroll); + if (next == current) + return; + scroll_line.store(next, std::memory_order_release); + event.accept(); + } +private: + static std::string value(double number, int precision = 2) { + return format_number(number, {.precision = precision}); + } + int panel_height(const Text_Metrics& metrics, Performance_Shower_Render_State* state, int plot_height) const { + int content_height = static_cast(metrics.line_height()) * static_cast(lines.size()) + state->top_margin + state->bottom_margin; + return std::min(content_height, std::max(0, plot_height)); + } + int visible_line_count(const Text_Metrics& metrics, Performance_Shower_Render_State* state, int height) const { + int text_height = std::max(0, height - state->top_margin - state->bottom_margin); + return std::max(1, text_height / std::max(1, static_cast(metrics.line_height()))); + } + void update_scroll_bar_rects(Performance_Shower_Render_State* state, int max_scroll, int scroll, int visible_count) { + if (!max_scroll || panel_rect.width <= 0 || panel_rect.height <= 0) { + scroll_bar_rect = {}; + scroll_thumb_rect = {}; + return; + } + int track_height = std::max(0, panel_rect.height - state->scroll_bar_margin * 2); + scroll_bar_rect = Rect{panel_rect.x + panel_rect.width - state->scroll_bar_width - state->scroll_bar_margin + 1, state->scroll_bar_margin, state->scroll_bar_width, track_height}; + int line_count = std::max(visible_count, static_cast(lines.size())); + int thumb_height = std::max(16, scroll_bar_rect.height * visible_count / std::max(1, line_count)); + thumb_height = std::min(thumb_height, scroll_bar_rect.height); + int travel = std::max(0, scroll_bar_rect.height - thumb_height); + int thumb_y = scroll_bar_rect.y + (max_scroll ? travel * scroll / max_scroll : 0); + scroll_thumb_rect = Rect{scroll_bar_rect.x, thumb_y, scroll_bar_rect.width, thumb_height}; + } + int& stable_value_width(const std::string& key) { + for (auto& field : stable_value_widths) { + if (field.first == key) + return field.second; + } + stable_value_widths.push_back({key, 0}); + return stable_value_widths.back().second; + } + std::string stable_text(const std::string& key, std::string text) { + int& width = stable_value_width(key); + width = std::max(width, static_cast(text.size())); + if (text.size() < static_cast(width)) + text.insert(0, static_cast(width) - text.size(), ' '); + return text; + } + std::string number_text(const std::string& key, double number, int precision = 2) { + return stable_text(key, value(number, precision)); + } + std::string count_text(const std::string& key, std::uint64_t count) { + return stable_text(key, std::to_string(count)); + } + std::string duration_line(const std::string& key, const std::string& name, const Psc::Value_Statistics& statistics) { + return name + " current:" + number_text(key + "_instant", statistics.instant) + "ms smooth:" + number_text(key + "_smooth", statistics.smooth) + "ms var:" + number_text(key + "_variation", statistics.variation) + "ms P95:" + number_text(key + "_p95", statistics.p95) + "ms"; + } + std::string percent_stat_line(const std::string& key, const std::string& name, const Psc::Value_Statistics& statistics) { + return name + " current:" + number_text(key + "_instant", statistics.instant) + "% smooth:" + number_text(key + "_smooth", statistics.smooth) + "% var:" + number_text(key + "_variation", statistics.variation) + "% P95:" + number_text(key + "_p95", statistics.p95) + "%"; + } + std::string interval_line(const std::string& key, const std::string& name, double srtt, double jitter, double fps) { + return name + " interval_smooth:" + number_text(key + "_srtt", srtt) + "ms var:" + number_text(key + "_jitter", jitter) + "ms actual:" + number_text(key + "_fps", fps) + "fps"; + } + static double percent(double value) { + return value * 100.0; + } + std::string percent_text(const std::string& key, double ratio) { + return number_text(key, percent(ratio)) + "%"; + } + static std::string limit_source_text(Refresh_Limit_Source source) { + switch (source) { + case Refresh_Limit_Source::Unknown: + return "unknown"; + case Refresh_Limit_Source::Render: + return "render"; + case Refresh_Limit_Source::Paint: + return "paint"; + case Refresh_Limit_Source::User: + return "user"; + } + return "unknown"; + } + static std::string outcome_text(Frame_Outcome outcome) { + switch (outcome) { + case Frame_Outcome::Presented: + return "presented"; + case Frame_Outcome::Superseded: + return "superseded"; + case Frame_Outcome::Render_Acquire_Dropped: + return "render acquire dropped"; + case Frame_Outcome::Publish_Dropped: + return "publish dropped"; + case Frame_Outcome::Paint_Acquire_Dropped: + return "paint acquire dropped"; + case Frame_Outcome::Worker_Budget_Dropped: + return "worker budget dropped"; + case Frame_Outcome::Cancelled: + return "cancelled"; + } + return "unknown"; + } + std::string period_text(const std::string& key, std::uint64_t ns) { + return number_text(key, static_cast(ns) / 1000000.0) + "ms"; + } + static double cache_total(const Renderable_Cache_Aggregate_Stat& stat) { + return static_cast(stat.hit_count + stat.miss_count); + } + std::string cache_ratio_text(const std::string& key, std::uint64_t hit, std::uint64_t miss) { + std::uint64_t total = hit + miss; + if (!total) + return stable_text(key, "0.00") + "%"; + return number_text(key, static_cast(hit) * 100.0 / static_cast(total)) + "%"; + } + std::string ns_text(const std::string& key, std::uint64_t ns) { + return number_text(key, static_cast(ns) / 1000000.0); + } + std::string cache_key(const Renderable_Cache_Aggregate_Stat& stat) const { + return stat.parent_object_name + "/" + stat.object_name; + } + std::string renderable_key(const Renderable_Aggregate_Stat& stat) const { + return stat.parent_object_name + "/" + stat.object_name + "/" + std::to_string(stat.tree_depth); + } + static std::string indent_str(int depth) { + return std::string(static_cast(std::max(0, depth)) * 2, ' '); + } + static std::string cache_mode_text(bool cache_enabled) { + return cache_enabled ? "cached" : "direct"; + } + static std::uint64_t frame_render_time_ns(const Frame_Lifecycle_Record& frame) { + return frame.render_end_ns > frame.render_begin_ns ? frame.render_end_ns - frame.render_begin_ns : 0; + } + std::string ns_ratio_text(const std::string& key, std::uint64_t numerator, std::uint64_t denominator) { + if (!denominator) + return stable_text(key, "0.00") + "%"; + return number_text(key, static_cast(numerator) * 100.0 / static_cast(denominator)) + "%"; + } + bool should_show_cache(const Renderable_Cache_Aggregate_Stat& stat) const { + return cache_total(stat) > 0.0 || stat.rebuild_count || stat.rebuild_ms.times || stat.compose_ms.times || stat.subtree_compose_ms.times; + } + bool should_show_renderable(const Renderable_Aggregate_Stat& stat) const { + return stat.render_count || stat.self_draw_count || stat.total_render_ms.times || stat.self_draw_ms.times; + } + std::string cache_duration_text(const std::string& key, const std::string& name, const Psc::Value_Statistics& statistics) { + return name + " current:" + number_text(key + "_instant", statistics.instant) + "ms smooth:" + number_text(key + "_smooth", statistics.smooth) + "ms var:" + number_text(key + "_variation", statistics.variation) + "ms P95:" + number_text(key + "_p95", statistics.p95) + "ms"; + } + void append_cache_node_lines(const Renderable_Cache_Aggregate_Stat& stat) { + std::string key = cache_key(stat); + std::string indent = indent_str(stat.tree_depth); + std::string name = stat.object_name.empty() ? "unnamed" : stat.object_name; + lines.push_back("cache tree " + indent + name + " hit:" + count_text(key + "_cache_hit", stat.hit_count) + " stale:" + count_text(key + "_cache_miss", stat.miss_count) + " miss:" + count_text(key + "_cache_rebuild", stat.rebuild_count) + " ratio:" + cache_ratio_text(key + "_cache_ratio", stat.hit_count, stat.miss_count)); + lines.push_back("cache image " + indent + name + " area:" + count_text(key + "_cache_image_area", stat.last_image_area) + " bytes:" + count_text(key + "_cache_image_bytes", stat.last_image_bytes) + " version:" + count_text(key + "_cache_edit_version", stat.last_edit_version) + "/" + count_text(key + "_cache_ready_version", stat.last_ready_version)); + lines.push_back("cache rebuild " + indent + name + " " + cache_duration_text(key + "_rebuild", "rebuild", stat.rebuild_ms)); + lines.push_back("cache compose " + indent + name + " " + cache_duration_text(key + "_compose", "self compose", stat.compose_ms)); + lines.push_back("cache subtree " + indent + name + " " + cache_duration_text(key + "_subtree_compose", "subtree compose", stat.subtree_compose_ms)); + } + void append_renderable_node_lines(const Renderable_Aggregate_Stat& stat) { + std::string key = renderable_key(stat); + std::string indent = indent_str(stat.tree_depth); + std::string name = (stat.object_name.empty() ? "unnamed" : stat.object_name); + std::uint64_t latest_render_time = frame_render_time_ns(aggregate.latest); + std::string name_indent = indent + std::string(name.size(), ' '); + lines.push_back(indent + " " + name + " cache:" + cache_mode_text(stat.cache_enabled) + " render:" + count_text(key + "_render_count", stat.render_count) + " draws:" + count_text(key + "_self_draw_count", stat.self_draw_count) + " cur%:" + ns_ratio_text(key + "_current_total_ratio", stat.last_total_render_time_ns, latest_render_time) + " total%:" + ns_ratio_text(key + "_aggregate_total_ratio", stat.total_render_time_ns, aggregate.render_time_ns)); + lines.push_back(name_indent + " total " + duration_line(key + "_total_render", "time", stat.total_render_ms)); + lines.push_back(name_indent + " total% " + percent_stat_line(key + "_total_ratio", "ratio", stat.total_render_ratio)); + lines.push_back(name_indent + " self " + duration_line(key + "_self_draw", "time", stat.self_draw_ms)); + lines.push_back(name_indent + " self% " + percent_stat_line(key + "_self_ratio", "ratio", stat.self_draw_ratio)); + } + void append_renderable_tree_lines(const std::string& parent_object_name, int tree_depth, std::vector& emitted) { + std::vector children; + for (const auto& stat : aggregate.renderable_stats) { + if (stat.parent_object_name == parent_object_name && stat.tree_depth == tree_depth && should_show_renderable(stat)) + children.push_back(&stat); + } + std::stable_sort(children.begin(), children.end(), [](const auto* a, const auto* b) { + return a->object_name < b->object_name; + }); + for (const auto* stat : children) { + if (std::find(emitted.begin(), emitted.end(), stat) != emitted.end()) + continue; + emitted.push_back(stat); + append_renderable_node_lines(*stat); + append_renderable_tree_lines(stat->object_name, stat->tree_depth + 1, emitted); + } + } + void append_renderable_tree_lines() { + std::vector emitted; + append_renderable_tree_lines({}, 0, emitted); + for (const auto& stat : aggregate.renderable_stats) { + if (!should_show_renderable(stat) || std::find(emitted.begin(), emitted.end(), &stat) != emitted.end()) + continue; + emitted.push_back(&stat); + append_renderable_node_lines(stat); + } + } + void append_cache_tree_lines(const std::string& parent_object_name, int tree_depth, std::vector& emitted) { + std::vector children; + for (const auto& stat : aggregate.renderable_cache_stats) { + if (stat.parent_object_name == parent_object_name && stat.tree_depth == tree_depth && should_show_cache(stat)) + children.push_back(&stat); + } + std::stable_sort(children.begin(), children.end(), [](const auto* a, const auto* b) { + return a->object_name < b->object_name; + }); + for (const auto* stat : children) { + if (std::find(emitted.begin(), emitted.end(), stat) != emitted.end()) + continue; + emitted.push_back(stat); + append_cache_node_lines(*stat); + append_cache_tree_lines(stat->object_name, stat->tree_depth + 1, emitted); + } + } + void append_cache_tree_lines() { + std::vector emitted; + append_cache_tree_lines({}, 0, emitted); + for (const auto& stat : aggregate.renderable_cache_stats) { + if (!should_show_cache(stat) || std::find(emitted.begin(), emitted.end(), &stat) != emitted.end()) + continue; + emitted.push_back(&stat); + append_cache_node_lines(stat); + } + } + void rebuild_lines() { + if (!aggregate.has_frame) { + lines = {"waiting for frame stats"}; + return; + } + const Frame_Lifecycle_Record& frame = aggregate.latest; + const Refresh_Control_Snapshot& refresh = frame.refresh; + std::uint64_t aggregate_cache_total = aggregate.cache_hit_count + aggregate.cache_miss_count; + std::uint64_t latest_render_time = frame_render_time_ns(frame); + double cache_hit_ratio = aggregate_cache_total ? static_cast(aggregate.cache_hit_count) * 100.0 / static_cast(aggregate_cache_total) : 0.0; + lines.clear(); + lines.push_back("frame:" + count_text("frame_id", frame.frame_id) + " input_ver:" + count_text("input_version", frame.input_version) + " outcome:" + outcome_text(frame.outcome) + " limit:" + limit_source_text(refresh.source)); + lines.push_back("period render:" + period_text("render_period", refresh.render_period_ns) + " paint:" + period_text("paint_period", refresh.paint_period_ns) + " user:" + period_text("user_period", refresh.user_period_ns) + " target:" + period_text("target_period", refresh.target_period_ns)); + lines.push_back("wait 21:" + ns_text("wait_21", frame.wait_21_ns) + "ms 12:" + ns_text("wait_12", frame.wait_12_ns) + "ms 23:" + ns_text("wait_23", frame.wait_23_ns) + "ms 32:" + ns_text("wait_32", frame.wait_32_ns) + "ms"); + lines.push_back(duration_line("request_wait", "lifecycle request_wait", aggregate.request_wait_ms)); + lines.push_back(duration_line("render_queue_wait", "lifecycle queue_wait", aggregate.render_queue_wait_ms)); + lines.push_back(duration_line("prepare", "lifecycle prepare", aggregate.prepare_ms)); + lines.push_back(duration_line("draw", "lifecycle draw", aggregate.draw_ms)); + lines.push_back(duration_line("render", "lifecycle render_total", aggregate.render_ms)); + lines.push_back(duration_line("ready_wait", "lifecycle ready_to_paint", aggregate.ready_wait_ms)); + lines.push_back(duration_line("update_wait", "lifecycle update_wait", aggregate.update_wait_ms)); + lines.push_back(duration_line("paint", "lifecycle paint", aggregate.paint_ms)); + lines.push_back(duration_line("frame_age", "lifecycle input_to_display_age", aggregate.frame_age_ms)); + lines.push_back(duration_line("frame_total", "lifecycle e2e_total", aggregate.frame_total_ms)); + lines.push_back("cache hit:" + count_text("cache_hit_count", aggregate.cache_hit_count) + " miss:" + count_text("cache_miss_count", aggregate.cache_miss_count) + " ratio:" + number_text("cache_hit_ratio", cache_hit_ratio)); + lines.push_back("cache rebuild count:" + count_text("cache_rebuild_count", aggregate.cache_rebuild_count) + " time:" + ns_text("cache_rebuild_time", aggregate.cache_rebuild_time_ns) + "ms compose:" + ns_text("cache_compose_time", aggregate.cache_compose_time_ns) + "ms"); + lines.push_back(duration_line("cache_rebuild_slide", "cache total rebuild", aggregate.cache_rebuild_ms)); + lines.push_back(duration_line("cache_compose_slide", "cache total compose", aggregate.cache_compose_ms)); + lines.push_back(percent_stat_line("cache_rebuild_ratio_slide", "cache rebuild ratio", aggregate.cache_rebuild_ratio)); + lines.push_back(percent_stat_line("cache_compose_ratio_slide", "cache compose ratio", aggregate.cache_compose_ratio)); + lines.push_back("cache cur rebuild:" + ns_ratio_text("cache_rebuild_current_ratio", frame.renderable_cache_rebuild_time_ns, latest_render_time) + " compose:" + ns_ratio_text("cache_compose_current_ratio", frame.renderable_cache_compose_time_ns, latest_render_time) + " total rebuild:" + ns_ratio_text("cache_rebuild_aggregate_ratio", aggregate.cache_rebuild_time_ns, aggregate.render_time_ns) + " compose:" + ns_ratio_text("cache_compose_aggregate_ratio", aggregate.cache_compose_time_ns, aggregate.render_time_ns)); + append_renderable_tree_lines(); + append_cache_tree_lines(); + lines.push_back("work raw:" + count_text("raw_input_count", frame.raw_input_count) + " prepared:" + count_text("prepared_output_count", frame.prepared_output_count)); + lines.push_back("observed:" + count_text("observed_frame_count", aggregate.observed_frame_count) + " consumed:" + count_text("consumed_outcome_count", aggregate.consumed_outcome_count) + " superseded:" + count_text("superseded_outcome_count", aggregate.superseded_outcome_count) + " cancelled:" + count_text("cancelled_outcome_count", aggregate.cancelled_outcome_count) + " dropped:" + count_text("input_capacity_dropped", aggregate.input_capacity_dropped)); + } + void update_content_width(const Text_Metrics& metrics, Performance_Shower_Render_State* state) { + int max_width = 0; + for (const std::string& line : lines) + max_width = std::max(max_width, static_cast(metrics.width * static_cast(line.size()))); + int measured_width = max_width + state->left_margin + state->right_margin + state->scroll_bar_width + state->scroll_bar_margin * 2; + held_content_width = std::max(held_content_width, measured_width); + content_width = held_content_width; + } +}; +} // namespace renderive diff --git a/Renderive/plottable/Planisphere.cpp b/Core/plottable/Planisphere.cpp similarity index 60% rename from Renderive/plottable/Planisphere.cpp rename to Core/plottable/Planisphere.cpp index 6506810..607a5e6 100644 --- a/Renderive/plottable/Planisphere.cpp +++ b/Core/plottable/Planisphere.cpp @@ -1,7 +1,7 @@ #include +#include #include "../Axis/Abs_Axis_p.h" #include "../base/Memory.h" -#include "../primitive/Grid.h" #include "Planisphere_p.h" namespace renderive { RENDERIVE_DEFINE_RENDERABLE_BINDING(Planisphere, Planisphere_Private, Planisphere_Render_State, Planisphere_Input_Data, "Planisphere") @@ -9,27 +9,13 @@ std::shared_ptr Planisphere::i_axis() { return d()->edit_state_value(&Planisphere_Render_State::i_axis).lock(); } void Planisphere::set_i_axis(std::shared_ptr value) { - Planisphere_Private* pd = d(); - pd->set_state_value(&Planisphere_Render_State::i_axis, std::weak_ptr(value)); - if (auto points = pd->point_set.lock()) - points->set_x_axis(value); - if (auto anchors = pd->anchor_set.lock()) - anchors->set_x_axis(value); - if (auto grid = pd->grid.lock()) - grid->set_x_axis(value); + d()->set_state_value(&Planisphere_Render_State::i_axis, std::weak_ptr(value)); } std::shared_ptr Planisphere::q_axis() { return d()->edit_state_value(&Planisphere_Render_State::q_axis).lock(); } void Planisphere::set_q_axis(std::shared_ptr value) { - Planisphere_Private* pd = d(); - pd->set_state_value(&Planisphere_Render_State::q_axis, std::weak_ptr(value)); - if (auto points = pd->point_set.lock()) - points->set_y_axis(value); - if (auto anchors = pd->anchor_set.lock()) - anchors->set_y_axis(value); - if (auto grid = pd->grid.lock()) - grid->set_y_axis(value); + d()->set_state_value(&Planisphere_Render_State::q_axis, std::weak_ptr(value)); } Range Planisphere::i_range() { return d()->edit_state_value(&Planisphere_Render_State::i_range); @@ -37,8 +23,6 @@ Range Planisphere::i_range() { void Planisphere::set_i_range(Range value) { if (value.length() == 0.0) return; - if (auto grid = d()->grid.lock()) - grid->set_x_range(value); d()->set_state_value(&Planisphere_Render_State::i_range, std::move(value)); } Range Planisphere::q_range() { @@ -47,31 +31,25 @@ Range Planisphere::q_range() { void Planisphere::set_q_range(Range value) { if (value.length() == 0.0) return; - if (auto grid = d()->grid.lock()) - grid->set_y_range(value); d()->set_state_value(&Planisphere_Render_State::q_range, std::move(value)); } -QColor Planisphere::point_color() { +Color Planisphere::point_color() { return d()->edit_state_value(&Planisphere_Render_State::point_color); } -void Planisphere::set_point_color(QColor value) { - if (auto points = d()->point_set.lock()) - points->set_brush(QBrush(value)); +void Planisphere::set_point_color(Color value) { d()->set_state_value(&Planisphere_Render_State::point_color, std::move(value)); } -QColor Planisphere::anchor_color() { +Color Planisphere::anchor_color() { return d()->edit_state_value(&Planisphere_Render_State::anchor_color); } -void Planisphere::set_anchor_color(QColor value) { - if (auto anchors = d()->anchor_set.lock()) - anchors->set_pen(QPen(value, 2)); +void Planisphere::set_anchor_color(Color value) { d()->set_state_value(&Planisphere_Render_State::anchor_color, std::move(value)); } int Planisphere::continue_millisecond() { return d()->edit_state_value(&Planisphere_Render_State::continue_millisecond); } void Planisphere::set_continue_millisecond(int value) { - d()->set_state_value(&Planisphere_Render_State::continue_millisecond, qMax(1, value)); + d()->set_state_value(&Planisphere_Render_State::continue_millisecond, std::max(1, value)); } Planisphere_Type Planisphere::type() { return d()->edit_state_value(&Planisphere_Render_State::type); @@ -85,7 +63,7 @@ double Planisphere::phase_offset_radians() { void Planisphere::set_phase_offset_radians(double value) { d()->set_state_value(&Planisphere_Render_State::phase_offset_radians, value); } -void Planisphere::give_data(QPointF pos) { +void Planisphere::give_data(PointF pos) { if (!ok()) return; Render_Input_Lease input(d()); @@ -94,9 +72,10 @@ 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->attached_to_plot(), "Planisphere build error! i_axis->plot == nullptr"); + ASSERT(i_axis->shares_plot_with(*q_axis), "Planisphere build error! i_axis->plot != q_axis->plot"); + ASSERT(parent->shares_plot_with(*i_axis), "Planisphere build error! parent->plot != axis->plot"); + ASSERT(i_axis->orientation() != q_axis->orientation(), "Planisphere build error! axes must be orthogonal"); this->parent = parent; this->i_axis = i_axis; this->q_axis = q_axis; @@ -110,8 +89,8 @@ std::shared_ptr Planisphere::Builder::build() { PROP_R(std::weak_ptr, q_axis) PROP_R(Range, i_range) PROP_R(Range, q_range) - PROP_R(QColor, point_color) - PROP_R(QColor, anchor_color) + PROP_R(Color, point_color) + PROP_R(Color, anchor_color) PROP_R(int, continue_millisecond) PROP_R(Planisphere_Type, type) PROP_R(double, phase_offset_radians) @@ -119,20 +98,7 @@ std::shared_ptr Planisphere::Builder::build() { sc->i_range = {0.0, 1.0}; if (sc->q_range.length() == 0.0) sc->q_range = {0.0, 1.0}; - sc->continue_millisecond = qMax(1, sc->continue_millisecond); - auto i_axis = this->i_axis.lock(); - auto q_axis = this->q_axis.lock(); - if (i_axis && q_axis) { - auto grid = Grid::Builder(ret, i_axis, q_axis).set_line_mode(Grid_Line_Mode::Range_Division).set_x_range(sc->i_range).set_y_range(sc->q_range).set_x_divisions(4).set_y_divisions(4).set_x_pen(QPen(Qt::white, 1, Qt::DotLine)).set_y_pen(QPen(Qt::white, 1, Qt::DotLine)).build(); - auto points = Point_Set::Builder(ret, i_axis, q_axis).set_size(4.0).set_pen(QPen(Qt::NoPen)).set_brush(QBrush(sc->point_color)).set_shape(Point_Set_Shape::Circle).set_antialias(true).build(); - auto anchors = Point_Set::Builder(ret, i_axis, q_axis).set_size(12.0).set_pen(QPen(sc->anchor_color, 2)).set_brush(QBrush(Qt::NoBrush)).set_shape(Point_Set_Shape::Cross).set_antialias(true).build(); - grid->object_name = "PlanisphereGrid"; - points->object_name = "PlanispherePoints"; - anchors->object_name = "PlanisphereAnchors"; - pd->grid = grid; - pd->point_set = points; - pd->anchor_set = anchors; - } + sc->continue_millisecond = std::max(1, sc->continue_millisecond); return ret; } void Planisphere::set_to_axis_center() { diff --git a/Renderive/plottable/Planisphere.h b/Core/plottable/Planisphere.h similarity index 89% rename from Renderive/plottable/Planisphere.h rename to Core/plottable/Planisphere.h index 53e0091..4051436 100644 --- a/Renderive/plottable/Planisphere.h +++ b/Core/plottable/Planisphere.h @@ -1,6 +1,7 @@ #pragma once +#include "../base/Color.h" +#include "../base/Geometry.h" #include "global.h" -#include "../primitive/Point_Set.h" namespace renderive { /// @brief 星座图调制类型。 /// @brief PSK constellation phase-state count. @@ -21,7 +22,7 @@ public: /// @brief 创建 I/Q 星座图元。 Planisphere(); /// @brief 写入一个 I/Q 点。 - void give_data(QPointF pos); + void give_data(PointF pos); /// @brief 绑定 I 轴。 PROP(std::shared_ptr, i_axis) /// @brief 绑定 Q 轴。 @@ -31,9 +32,9 @@ public: /// @brief Q 轴星座范围。 PROP(Range, q_range) /// @brief 输入点颜色。 - PROP(QColor, point_color) + PROP(Color, point_color) /// @brief 锚点颜色。 - PROP(QColor, anchor_color) + PROP(Color, anchor_color) /// @brief 输入点保留时间。 PROP(int, continue_millisecond) /// @brief 星座调制类型。 @@ -54,8 +55,8 @@ struct Planisphere_Prop { std::weak_ptr q_axis{}; Range i_range{0, 100}; Range q_range{0, 100}; - QColor point_color = Qt::red; - QColor anchor_color = Qt::yellow; + Color point_color = Color::red_color(); + Color anchor_color = Color::yellow(); int continue_millisecond = 1000; Planisphere_Type type = Planisphere_Type::Psk8; double phase_offset_radians = 0.0; @@ -67,8 +68,8 @@ struct LIB_DECL Planisphere::Builder : Planisphere_Prop { PROP_B(std::shared_ptr, q_axis) PROP_B(Range, i_range) PROP_B(Range, q_range) - PROP_B(QColor, point_color) - PROP_B(QColor, anchor_color) + PROP_B(Color, point_color) + PROP_B(Color, anchor_color) PROP_B(int, continue_millisecond) PROP_B(Planisphere_Type, type) PROP_B(double, phase_offset_radians) diff --git a/Core/plottable/Planisphere_p.h b/Core/plottable/Planisphere_p.h new file mode 100644 index 0000000..fde07de --- /dev/null +++ b/Core/plottable/Planisphere_p.h @@ -0,0 +1,155 @@ +#pragma once +#include +#include +#include +#include +#include +#include +#include "../architecture/Render_Lease.h" +#include "../Axis/Axis_p.h" +#include "../architecture/Bounded_Input_Buffer.h" +#include "../base/Frame_Memory.h" +#include "../base/Memory.h" +#include "../base/Color.h" +#include "../base/Geometry.h" +#include "../base/Style.h" +#include "../render/Canvas.h" +#include "Planisphere.h" +namespace renderive { +struct Planisphere_Data { + PointF pos; + std::uint64_t birth_time_ns{}; + std::uint64_t expire_time_ns{}; +}; +struct Planisphere_Input_Data : Input_Data { + static constexpr std::size_t Max_Pending_Point_Count = 128; + struct Packet { + double x{}; + double y{}; + }; + Bounded_Input_Buffer data_list; + void push(PointF pos) { + Packet packet{pos.x, pos.y}; + data_list.push(&packet, sizeof(packet), Max_Pending_Point_Count); + } + template + void read_all(Handler handler) { + data_list.read_all([&handler](const void* data, std::size_t size) { + (void)size; + Packet packet; + std::memcpy(&packet, data, sizeof(packet)); + handler(PointF{packet.x, packet.y}); + }); + } + void clear() override { + data_list.clear(); + } +}; +struct Planisphere_Render_State : Render_State, Planisphere_Prop {}; +struct Planisphere_Private : Typed_Render_Data { + std::pmr::list data_list{memory_resource(Memory_Domain::Planisphere)}; + std::pmr::vector prepared_points{memory_resource(Memory_Domain::Planisphere)}; + std::pmr::vector prepared_colors{memory_resource(Memory_Domain::Planisphere)}; + std::pmr::vector prepared_anchor_points{memory_resource(Memory_Domain::Planisphere)}; + std::pmr::vector anchor_points(const Planisphere_Render_State* s) const { + std::pmr::vector points(frame_memory_resource()); + const unsigned order = static_cast(s->type); + if (order == 0) + return points; + constexpr double pi = 3.14159265358979323846; + const double center_x = s->i_range.middle(); + const double center_y = s->q_range.middle(); + const double radius = std::min(s->i_range.size(), s->q_range.size()) / 3.0; + points.reserve(order); + for (unsigned i = 0; i < order; ++i) { + const double angle = s->phase_offset_radians + 2.0 * pi * static_cast(i) / static_cast(order); + points.push_back({center_x + radius * std::cos(angle), center_y + radius * std::sin(angle)}); + } + return points; + } + void refresh(std::uint64_t now_ns) { + for (auto it = data_list.begin(); it != data_list.end();) { + if (it->expire_time_ns <= now_ns) + data_list.erase(it++); + else + ++it; + } + } + void prepare_data(const Plot_Render_Snapshot& snapshot) override { + sync_state_pipeline(); + Planisphere_Input_Data* d = render_input_data(); + Planisphere_Render_State* s = render_state(); + std::uint64_t now_ns = snapshot.frame_time_ns; + const std::uint64_t lifetime_ns = static_cast(std::max(1, s->continue_millisecond)) * 1000000ull; + d->read_all([this, now_ns, lifetime_ns](const PointF& pos) { + Planisphere_Data data; + data.pos = pos; + data.birth_time_ns = now_ns; + data.expire_time_ns = now_ns + lifetime_ns; + data_list.push_back(data); + }); + clear_render_input(); + refresh(now_ns); + prepared_points.clear(); + prepared_colors.clear(); + prepared_points.reserve(static_cast(data_list.size())); + prepared_colors.reserve(static_cast(data_list.size())); + for (const Planisphere_Data& data : data_list) { + Color color = s->point_color; + const std::uint64_t total_ns = data.expire_time_ns > data.birth_time_ns ? data.expire_time_ns - data.birth_time_ns : 1; + const std::uint64_t remain_ns = data.expire_time_ns > now_ns ? data.expire_time_ns - now_ns : 0; + color.alpha = static_cast(static_cast(color.alpha) * remain_ns / total_ns); + prepared_points.push_back(data.pos); + prepared_colors.push_back(color); + } + prepared_anchor_points = anchor_points(s); + } + PointF map_point(const Axis_Mapping_2D& mapping, const PointF& point) const { + return mapping.map(point); + } + void draw_grid(Canvas& canvas, const Axis_Mapping_2D& mapping, const Planisphere_Render_State* s) const { + canvas.set_pen(Pen{Color::white(), 1.0, Line_Style::Dot}); + constexpr int divisions = 4; + for (int i = 0; i <= divisions; ++i) { + const double t = static_cast(i) / static_cast(divisions); + const double i_coord = s->i_range.origin + s->i_range.length() * t; + const double q_coord = s->q_range.origin + s->q_range.length() * t; + canvas.draw_line(mapping.map(i_coord, s->q_range.origin), mapping.map(i_coord, s->q_range.target)); + canvas.draw_line(mapping.map(s->i_range.origin, q_coord), mapping.map(s->i_range.target, q_coord)); + } + } + void draw_points(Canvas& canvas, const Axis_Mapping_2D& mapping) const { + canvas.set_antialias(true); + canvas.set_pen(Pen{.style = Line_Style::None}); + for (std::size_t i = 0; i < prepared_points.size(); ++i) { + const Color color = i < prepared_colors.size() ? prepared_colors[i] : Color::red_color(); + canvas.set_brush(Brush{color, Brush_Style::Solid}); + canvas.draw_ellipse(map_point(mapping, prepared_points[i]), 2.0, 2.0); + } + } + void draw_anchors(Canvas& canvas, const Axis_Mapping_2D& mapping, Color color) const { + canvas.set_antialias(true); + canvas.set_pen(Pen{color, 2.0}); + canvas.set_brush(Brush{}); + constexpr double half = 6.0; + for (const PointF& point : prepared_anchor_points) { + PointF pixel = map_point(mapping, point); + canvas.draw_line(PointF{pixel.x - half, pixel.y}, PointF{pixel.x + half, pixel.y}); + canvas.draw_line(PointF{pixel.x, pixel.y - half}, PointF{pixel.x, pixel.y + half}); + } + } + void draw(Canvas& canvas, const Plot_Render_Snapshot&) override { + Planisphere_Render_State* s = render_state(); + auto i_axis = s->i_axis.lock(); + auto q_axis = s->q_axis.lock(); + if (!i_axis || !q_axis) + return; + Axis_Mapping_2D mapping = Axis_Render_Access::mapping(i_axis.get(), q_axis.get()); + canvas.save(); + draw_grid(canvas, mapping, s); + draw_points(canvas, mapping); + draw_anchors(canvas, mapping, s->anchor_color); + canvas.restore(); + } +}; +} // namespace renderive diff --git a/Renderive/plottable/Spectrum.cpp b/Core/plottable/Spectrum.cpp similarity index 66% rename from Renderive/plottable/Spectrum.cpp rename to Core/plottable/Spectrum.cpp index ee9ed7a..93c621f 100644 --- a/Renderive/plottable/Spectrum.cpp +++ b/Core/plottable/Spectrum.cpp @@ -3,6 +3,7 @@ #include "../base/Frame_Memory.h" #include "../base/Memory.h" #include "../primitive/Curve.h" +#include "../primitive/Curve_Utils_p.h" #include "../primitive/Label.h" #include "../primitive/Marker.h" #include "Spectrum_p.h" @@ -13,20 +14,12 @@ std::shared_ptr Spectrum::frequency_axis() { } void Spectrum::set_frequency_axis(std::shared_ptr value) { d()->set_state_value(&Spectrum_Render_State::frequency_axis, std::weak_ptr(value)); - if (auto item = d()->marker_item.lock()) - item->set_x_axis(value); - if (auto item = d()->label_item.lock()) - item->set_x_axis(value); } std::shared_ptr Spectrum::power_axis() { return d()->edit_state_value(&Spectrum_Render_State::power_axis).lock(); } void Spectrum::set_power_axis(std::shared_ptr value) { d()->set_state_value(&Spectrum_Render_State::power_axis, std::weak_ptr(value)); - if (auto item = d()->marker_item.lock()) - item->set_y_axis(value); - if (auto item = d()->label_item.lock()) - item->set_y_axis(value); } int Spectrum::frequency_point_size() { return d()->edit_state_value(&Spectrum_Render_State::frequency_point_size); @@ -96,69 +89,69 @@ Line_Sampling_Mode Spectrum::sample_mode() { void Spectrum::set_sample_mode(Line_Sampling_Mode value) { d()->set_state_value(&Spectrum_Render_State::sample_mode, value); } -QBrush Spectrum::max_brush() { +Brush Spectrum::max_brush() { return d()->edit_state_value(&Spectrum_Render_State::max_brush); } -void Spectrum::set_max_brush(QBrush value) { +void Spectrum::set_max_brush(Brush value) { d()->set_state_value(&Spectrum_Render_State::max_brush, std::move(value)); } -QBrush Spectrum::cur_brush() { +Brush Spectrum::cur_brush() { return d()->edit_state_value(&Spectrum_Render_State::cur_brush); } -void Spectrum::set_cur_brush(QBrush value) { +void Spectrum::set_cur_brush(Brush value) { d()->set_state_value(&Spectrum_Render_State::cur_brush, std::move(value)); } -QBrush Spectrum::min_brush() { +Brush Spectrum::min_brush() { return d()->edit_state_value(&Spectrum_Render_State::min_brush); } -void Spectrum::set_min_brush(QBrush value) { +void Spectrum::set_min_brush(Brush value) { d()->set_state_value(&Spectrum_Render_State::min_brush, std::move(value)); } -QPen Spectrum::max_pen() { +Pen Spectrum::max_pen() { return d()->edit_state_value(&Spectrum_Render_State::max_pen); } -void Spectrum::set_max_pen(QPen value) { +void Spectrum::set_max_pen(Pen value) { d()->set_state_value(&Spectrum_Render_State::max_pen, std::move(value)); } -QPen Spectrum::cur_pen() { +Pen Spectrum::cur_pen() { return d()->edit_state_value(&Spectrum_Render_State::cur_pen); } -void Spectrum::set_cur_pen(QPen value) { +void Spectrum::set_cur_pen(Pen value) { d()->set_state_value(&Spectrum_Render_State::cur_pen, std::move(value)); } -QPen Spectrum::min_pen() { +Pen Spectrum::min_pen() { return d()->edit_state_value(&Spectrum_Render_State::min_pen); } -void Spectrum::set_min_pen(QPen value) { +void Spectrum::set_min_pen(Pen value) { d()->set_state_value(&Spectrum_Render_State::min_pen, std::move(value)); } -QPen Spectrum::select_marker_pen() { +Pen Spectrum::select_marker_pen() { return d()->edit_state_value(&Spectrum_Render_State::select_marker_pen); } -void Spectrum::set_select_marker_pen(QPen value) { +void Spectrum::set_select_marker_pen(Pen value) { d()->set_state_value(&Spectrum_Render_State::select_marker_pen, std::move(value)); } -QPen Spectrum::marker_pen() { +Pen Spectrum::marker_pen() { return d()->edit_state_value(&Spectrum_Render_State::marker_pen); } -void Spectrum::set_marker_pen(QPen value) { +void Spectrum::set_marker_pen(Pen value) { d()->set_state_value(&Spectrum_Render_State::marker_pen, std::move(value)); } -QPen Spectrum::middle_frequency_pen() { +Pen Spectrum::middle_frequency_pen() { return d()->edit_state_value(&Spectrum_Render_State::middle_frequency_pen); } -void Spectrum::set_middle_frequency_pen(QPen value) { +void Spectrum::set_middle_frequency_pen(Pen value) { d()->set_state_value(&Spectrum_Render_State::middle_frequency_pen, std::move(value)); } -QBrush Spectrum::sweep_rect_brush() { +Brush Spectrum::sweep_rect_brush() { return d()->edit_state_value(&Spectrum_Render_State::sweep_rect_brush); } -void Spectrum::set_sweep_rect_brush(QBrush value) { +void Spectrum::set_sweep_rect_brush(Brush value) { d()->set_state_value(&Spectrum_Render_State::sweep_rect_brush, std::move(value)); } void Spectrum::set_frequency_point_size(int frequency_point_size) { Render_Edit_Lease edit(d()); - edit->frequency_point_size = qMax(1, frequency_point_size); + edit->frequency_point_size = std::max(1, frequency_point_size); } void Spectrum::set_frequency_range(Range frequency_range) { if (frequency_range.length() == 0.0) @@ -169,9 +162,10 @@ 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->attached_to_plot(), "Spectrum build error! frequency_axis->plot == nullptr"); + ASSERT(frequency_axis->shares_plot_with(*power_axis), "Spectrum build error! frequency_axis->plot != power_axis->plot"); + ASSERT(parent->shares_plot_with(*frequency_axis), "Spectrum build error! parent->plot != axis->plot"); + ASSERT(frequency_axis->orientation() != power_axis->orientation(), "Spectrum build error! axes must be orthogonal"); this->parent = parent; this->frequency_axis = frequency_axis; this->power_axis = power_axis; @@ -195,31 +189,21 @@ std::shared_ptr Spectrum::Builder::build() { PROP_R(bool, visible_range_only); PROP_R(Line_Interpolation_Mode, interpolation_mode); PROP_R(Line_Sampling_Mode, sample_mode); - PROP_R(QBrush, max_brush); - PROP_R(QBrush, cur_brush); - PROP_R(QBrush, min_brush); - PROP_R(QPen, max_pen); - PROP_R(QPen, cur_pen); - PROP_R(QPen, min_pen); - PROP_R(QPen, select_marker_pen); - PROP_R(QPen, marker_pen); - PROP_R(QPen, middle_frequency_pen); - PROP_R(QBrush, sweep_rect_brush); - sc->frequency_point_size = qMax(1, sc->frequency_point_size); + PROP_R(Brush, max_brush); + PROP_R(Brush, cur_brush); + PROP_R(Brush, min_brush); + PROP_R(Pen, max_pen); + PROP_R(Pen, cur_pen); + PROP_R(Pen, min_pen); + PROP_R(Pen, select_marker_pen); + PROP_R(Pen, marker_pen); + PROP_R(Pen, middle_frequency_pen); + PROP_R(Brush, sweep_rect_brush); + sc->frequency_point_size = std::max(1, sc->frequency_point_size); if (sc->frequency_range.length() == 0.0) sc->frequency_range = {0.0, 1.0}; if (sc->sweep_frequency_range.length() == 0.0) sc->sweep_frequency_range = {0.0, 1.0}; - auto frequency_axis = this->frequency_axis.lock(); - auto power_axis = this->power_axis.lock(); - if (frequency_axis && power_axis) { - auto marker_item = Marker::Builder(ret, frequency_axis, power_axis).build(); - auto label_item = Label::Builder(ret, frequency_axis, power_axis).build(); - marker_item->object_name = "SpectrumMarker"; - label_item->object_name = "SpectrumLabel"; - pd->marker_item = marker_item; - pd->label_item = label_item; - } return ret; } void Spectrum_Private::update_frequency_point_size(int frequency_point_size, Range frequency_range) { @@ -305,130 +289,193 @@ double Spectrum_Private::get_y(double x, bool& ok, Line_Interpolation_Mode mode) 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(Canvas& canvas, Spectrum_Render_State* s, const std::pmr::vector& powers, const Pen& pen, const Brush& brush) { auto frequency_axis = s->frequency_axis.lock(); auto power_axis = s->power_axis.lock(); if (!frequency_axis || !power_axis) return; if (powers.size() < 2 || s->frequency_range.length() == 0.0) return; - std::pmr::vector points(frame_memory_resource()); + Axis_Mapping_2D mapping = Axis_Render_Access::mapping(frequency_axis.get(), power_axis.get()); + std::pmr::vector points(frame_memory_resource()); int point_count = static_cast(powers.size()); if (s->sample_mode == Line_Sampling_Mode::Pixel_Peak_Envelope) { - 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) { + points = Curve_Utils::create_peak_bucket_points(mapping, 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(mapping, s->frequency_range, point_count, point_count, s->visible_range_only, s->interpolation_mode, [&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(); + canvas.save(); + Curve_Utils::draw_polyline(&canvas, points, pen); + Curve_Utils::draw_fill_to_value(&canvas, points, mapping, mapping.value.coord_range.target, brush); + canvas.restore(); } void Spectrum_Private::update_marker_items(const Spectrum_Render_State* s) { auto frequency_axis = s->frequency_axis.lock(); auto power_axis = s->power_axis.lock(); - std::pmr::vector marker_items(memory_resource(Memory_Domain::Other)); - std::pmr::vector label_items(memory_resource(Memory_Domain::Other)); - if (!frequency_axis || !power_axis) { - if (auto marker_item = this->marker_item.lock()) - marker_item->give_items(std::move(marker_items), false); - if (auto label_item = this->label_item.lock()) - label_item->give_items(std::move(label_items), false); + prepared_marker_items.clear(); + prepared_label_items.clear(); + if (!frequency_axis || !power_axis) return; - } auto append_marker = [&](double frequency, double power, bool drawLine, bool selected) { - QPen pen = selected ? s->select_marker_pen : s->marker_pen; + Pen pen = selected ? s->select_marker_pen : s->marker_pen; Marker_Item marker; - marker.position = QPointF(frequency, power); + marker.position = PointF(frequency, power); marker.radius = 2.0; marker.pen = pen; - marker.mode = drawLine ? Marker_Mode::Vertical_Line : Marker_Mode::Point; - marker_items.push_back(marker); - QString frequency_text = QString::number(frequency, 'f', 2) + frequency_axis->unit_text(); - QString power_text = QString::number(power, 'f', 2) + power_axis->unit_text(); + marker.mode = drawLine ? Marker_Mode::Domain_Line : Marker_Mode::Point; + prepared_marker_items.push_back(marker); + std::string frequency_text = std::to_string(frequency) + frequency_axis->unit_text(); + std::string power_text = std::to_string(power) + power_axis->unit_text(); Label_Item frequency_label; frequency_label.position = marker.position; - frequency_label.offset = QPointF(0.0, -28.0); + frequency_label.offset = PointF(0.0, -28.0); frequency_label.text = frequency_text; - frequency_label.alignment = Qt::AlignHCenter | Qt::AlignBottom; + frequency_label.alignment = Text_Align::HCenter | Text_Align::Bottom; frequency_label.text_pen = pen; - label_items.push_back(frequency_label); + prepared_label_items.push_back(frequency_label); Label_Item power_label = frequency_label; - power_label.offset = QPointF(0.0, -14.0); + power_label.offset = PointF(0.0, -14.0); power_label.text = power_text; - label_items.push_back(power_label); + prepared_label_items.push_back(power_label); }; if (s->use_max_marker) append_marker(max_marker_key, max_marker_value, false, false); if (s->use_min_marker) append_marker(min_marker_key, min_marker_value, false, false); - for (int i = 0; i < s->marker_list.size(); ++i) { + for (int i = 0; i < (int)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); if (ok) append_marker(custom_marker.frequency, value, custom_marker.draw_line, i == s->cur_select_index); } - if (auto marker_item = this->marker_item.lock()) - marker_item->give_items(std::move(marker_items), false); - 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) { +void Spectrum_Private::draw_spectrum(Canvas& canvas, Spectrum_Render_State* s) { if (s->use_sweep_frequency_rect) { - s->draw_axis_rect(painter, s->sweep_frequency_range, s->middle_sweep_frequency); + s->draw_axis_rect(canvas, s->sweep_frequency_range, s->middle_sweep_frequency); } } -void Spectrum_Private::draw(QPainter* painter, const Plot_Render_Snapshot&) { +void Spectrum_Private::draw_marker_items(Canvas& canvas, Spectrum_Render_State* s) { + auto frequency_axis = s->frequency_axis.lock(); + auto power_axis = s->power_axis.lock(); + if (!frequency_axis || !power_axis) + return; + Axis_Mapping_2D mapping = Axis_Render_Access::mapping(frequency_axis.get(), power_axis.get()); + canvas.save(); + for (const Marker_Item& item : prepared_marker_items) { + PointF point = mapping.map(item.position); + PointF domain_start = mapping.map(item.position.x, mapping.value.coord_range.origin); + PointF domain_end = mapping.map(item.position.x, mapping.value.coord_range.target); + PointF value_start = mapping.map(mapping.domain.coord_range.origin, item.position.y); + PointF value_end = mapping.map(mapping.domain.coord_range.target, item.position.y); + canvas.set_pen(item.pen); + canvas.set_brush(item.brush); + if (item.mode == Marker_Mode::Point) { + canvas.draw_ellipse(point, item.radius, item.radius); + } + else if (item.mode == Marker_Mode::Domain_Line) { + canvas.draw_line(domain_start, domain_end); + } + else if (item.mode == Marker_Mode::Value_Line) { + canvas.draw_line(value_start, value_end); + } + else { + canvas.draw_line(domain_start, domain_end); + canvas.draw_line(value_start, value_end); + } + } + canvas.restore(); +} +void Spectrum_Private::draw_label_items(Canvas& canvas, Spectrum_Render_State* s) { + auto frequency_axis = s->frequency_axis.lock(); + auto power_axis = s->power_axis.lock(); + if (!frequency_axis || !power_axis) + return; + Axis_Mapping_2D mapping = Axis_Render_Access::mapping(frequency_axis.get(), power_axis.get()); + for (const Label_Item& item : prepared_label_items) { + if (item.text.empty()) + continue; + canvas.save(); + canvas.set_font(item.font); + Text_Metrics tm = canvas.measure_text(item.text); + PointF anchor = mapping.map(item.position); + PointF top_left{anchor.x + item.offset.x, anchor.y + item.offset.y}; + if ((item.alignment & Text_Align::HCenter) == Text_Align::HCenter) + top_left.x -= tm.width / 2.0; + else if ((item.alignment & Text_Align::Right) == Text_Align::Right) + top_left.x -= tm.width; + if ((item.alignment & Text_Align::VCenter) == Text_Align::VCenter) + top_left.y -= tm.height / 2.0; + else if ((item.alignment & Text_Align::Bottom) == Text_Align::Bottom) + top_left.y -= tm.height; + RectF rect{top_left.x, top_left.y, tm.width, tm.height}; + if (item.background_brush.style != Brush_Style::None || item.border_pen.style != Line_Style::None) { + canvas.set_pen(item.border_pen); + canvas.set_brush(item.background_brush); + canvas.draw_rect(rect); + } + canvas.set_pen(item.text_pen); + canvas.draw_text(rect, Text_Align::Left | Text_Align::VCenter, item.text); + canvas.restore(); + } +} +void Spectrum_Private::draw(Canvas& canvas, 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(canvas, s, cur_powers, s->cur_pen, s->cur_brush); + draw_spectrum(canvas, s); if (s->use_max_line) - draw_power_curve(painter, s, max_powers, s->max_pen, s->max_brush); + draw_power_curve(canvas, 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(canvas, s, min_powers, s->min_pen, s->min_brush); + draw_marker_items(canvas, s); + draw_label_items(canvas, s); 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(&canvas, 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) - 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; -} void Spectrum::give_data(std::span line_data) { if (!ok()) return; Spectrum_Private* pd = d(); - Render_State_Lease state(pd); - int frequency_point_size = state->frequency_point_size; + int frequency_point_size{}; + bool use_max_line{}; + bool use_min_line{}; + { + Render_State_Lease state(pd); + frequency_point_size = state->frequency_point_size; + use_max_line = state->use_max_line; + use_min_line = state->use_min_line; + } 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, use_max_line, use_min_line); } void Spectrum::give_data(std::pmr::vector&& line_data) { if (!ok()) return; Spectrum_Private* pd = d(); - Render_State_Lease state(pd); - int frequency_point_size = state->frequency_point_size; + int frequency_point_size{}; + bool use_max_line{}; + bool use_min_line{}; + { + Render_State_Lease state(pd); + frequency_point_size = state->frequency_point_size; + use_max_line = state->use_max_line; + use_min_line = state->use_min_line; + } 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), use_max_line, use_min_line); } Update_Ticket Spectrum::submit_data(std::span line_data) { auto update_state = make_update_state(); @@ -437,15 +484,22 @@ Update_Ticket Spectrum::submit_data(std::span line_data) { return Update_Ticket(update_state); } Spectrum_Private* pd = d(); - Render_State_Lease state(pd); - int frequency_point_size = state->frequency_point_size; + int frequency_point_size{}; + bool use_max_line{}; + bool use_min_line{}; + { + Render_State_Lease state(pd); + frequency_point_size = state->frequency_point_size; + use_max_line = state->use_max_line; + use_min_line = state->use_min_line; + } if (line_data.size() != frequency_point_size) { update_state->complete_all(std::make_error_code(std::errc::invalid_argument), Update_Outcome::Cancelled); return Update_Ticket(update_state); } { Render_Input_Lease input(pd); - input->push(line_data, state->use_max_line, state->use_min_line); + input->push(line_data, use_max_line, use_min_line); pd->set_input_completion(input.lease, update_state); } update_state->complete_until(Update_Stage::Input_Released); @@ -458,15 +512,22 @@ Update_Ticket Spectrum::submit_data(std::pmr::vector&& line_data) { return Update_Ticket(update_state); } Spectrum_Private* pd = d(); - Render_State_Lease state(pd); - int frequency_point_size = state->frequency_point_size; + int frequency_point_size{}; + bool use_max_line{}; + bool use_min_line{}; + { + Render_State_Lease state(pd); + frequency_point_size = state->frequency_point_size; + use_max_line = state->use_max_line; + use_min_line = state->use_min_line; + } if (line_data.size() != frequency_point_size) { update_state->complete_all(std::make_error_code(std::errc::invalid_argument), Update_Outcome::Cancelled); return Update_Ticket(update_state); } { 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), use_max_line, use_min_line); pd->set_input_completion(input.lease, update_state); } update_state->complete_until(Update_Stage::Input_Released); @@ -483,7 +544,7 @@ void Spectrum::add_custom_line_marker(double frequency) { void Spectrum::remove_custom_marker(double frequency) { Render_Edit_Lease edit(d()); for (auto marker = edit->marker_list.begin(); marker != edit->marker_list.end(); ++marker) { - if (qFuzzyCompare(marker->frequency, frequency)) { + if (std::abs(marker->frequency - frequency) < 1e-12) { edit->marker_list.erase(marker); break; } @@ -491,7 +552,7 @@ void Spectrum::remove_custom_marker(double frequency) { } void Spectrum::remove_current_marker() { Render_Edit_Lease edit(d()); - if (edit->cur_select_index < 0 || edit->cur_select_index >= edit->marker_list.size()) + if (edit->cur_select_index < 0 || edit->cur_select_index >= (int)edit->marker_list.size()) return; edit->marker_list.erase(edit->marker_list.begin() + edit->cur_select_index); edit->cur_select_index = -1; @@ -502,14 +563,14 @@ void Spectrum::clear_all_custom_marker() { } double Spectrum::get_mark_frequency(int mark_index) { auto render_state = d()->state_data(SRC::Edit); - if (mark_index < 0 || mark_index >= render_state->marker_list.size()) + if (mark_index < 0 || mark_index >= (int)render_state->marker_list.size()) return -1; return render_state->marker_list[mark_index].frequency; } void Spectrum::set_marker_frequency(int marker_index, double frequency) { Render_Edit_Lease edit(d()); auto& markers = edit->marker_list; - if (marker_index < 0 || marker_index >= markers.size()) + if (marker_index < 0 || marker_index >= (int)markers.size()) return; markers[marker_index].frequency = frequency; } @@ -519,7 +580,7 @@ void Spectrum::set_current_marker_frequency(double frequency) { } int Spectrum::select_line_marker_size() { auto render_state = d()->state_data(SRC::Edit); - return render_state->marker_list.size(); + return (int)render_state->marker_list.size(); } int Spectrum::cur_select_marker_index() { auto render_state = d()->state_data(SRC::Edit); diff --git a/Renderive/plottable/Spectrum.h b/Core/plottable/Spectrum.h similarity index 84% rename from Renderive/plottable/Spectrum.h rename to Core/plottable/Spectrum.h index 7487fb7..b6d10ab 100644 --- a/Renderive/plottable/Spectrum.h +++ b/Core/plottable/Spectrum.h @@ -5,7 +5,9 @@ #include #include #include "../architecture/Update_Completion.h" +#include "../base/Color.h" #include "../base/Memory.h" +#include "../base/Style.h" #include "../primitive/Curve.h" #include "Hover_Info.h" #include "Interpolation.h" @@ -47,25 +49,25 @@ public: /// @brief 采样降采样模式。 PROP(Line_Sampling_Mode, sample_mode); /// @brief 最大保持线填充刷。 - PROP(QBrush, max_brush); + PROP(Brush, max_brush); /// @brief 当前频谱线填充刷。 - PROP(QBrush, cur_brush); + PROP(Brush, cur_brush); /// @brief 最小保持线填充刷。 - PROP(QBrush, min_brush); + PROP(Brush, min_brush); /// @brief 最大保持线画笔。 - PROP(QPen, max_pen); + PROP(Pen, max_pen); /// @brief 当前频谱线画笔。 - PROP(QPen, cur_pen); + PROP(Pen, cur_pen); /// @brief 最小保持线画笔。 - PROP(QPen, min_pen); + PROP(Pen, min_pen); /// @brief 选中标记画笔。 - PROP(QPen, select_marker_pen); + PROP(Pen, select_marker_pen); /// @brief 普通标记画笔。 - PROP(QPen, marker_pen); + PROP(Pen, marker_pen); /// @brief 中心频率线画笔。 - PROP(QPen, middle_frequency_pen); + PROP(Pen, middle_frequency_pen); /// @brief 扫频区域填充刷。 - PROP(QBrush, sweep_rect_brush); + PROP(Brush, sweep_rect_brush); /// @brief 写入一帧频谱线数据。 void give_data(std::span line_data); /// @brief 移动写入一帧频谱线数据。 @@ -134,20 +136,20 @@ struct Spectrum_PROP { 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; - QBrush max_brush; - QBrush cur_brush; - QBrush min_brush; - QPen max_pen = QPen(Qt::red); - QPen cur_pen = QPen(Qt::green); - QPen min_pen = QPen(Qt::white); - QPen select_marker_pen = QPen(Qt::darkBlue, 2); - QPen marker_pen = QPen(Qt::red); - QPen middle_frequency_pen = QPen(Qt::red); - QBrush sweep_rect_brush = QBrush(QColor(255, 255, 0, 100)); + Brush max_brush; + Brush cur_brush; + Brush min_brush; + Pen max_pen = Pen{Color::red_color()}; + Pen cur_pen = Pen{Color::green_color()}; + Pen min_pen = Pen{Color::white()}; + Pen select_marker_pen = Pen{Color{0, 0, 139, 255}, 2.0}; + Pen marker_pen = Pen{Color::red_color()}; + Pen middle_frequency_pen = Pen{Color::red_color()}; + Brush sweep_rect_brush = Brush{Color{255, 255, 0, 100}, Brush_Style::Solid}; Spectrum_PROP() { - max_brush = QBrush(QColor(), Qt::NoBrush); - cur_brush = QBrush(QColor(), Qt::NoBrush); - min_brush = QBrush(QColor(), Qt::NoBrush); + max_brush = Brush{}; + cur_brush = Brush{}; + min_brush = Brush{}; } }; /// @brief 频谱线构造器。 @@ -167,16 +169,16 @@ struct LIB_DECL Spectrum::Builder : Spectrum_PROP { PROP_B(bool, visible_range_only); PROP_B(Line_Interpolation_Mode, interpolation_mode); PROP_B(Line_Sampling_Mode, sample_mode); - PROP_B(QBrush, max_brush); - PROP_B(QBrush, cur_brush); - PROP_B(QBrush, min_brush); - PROP_B(QPen, max_pen); - PROP_B(QPen, cur_pen); - PROP_B(QPen, min_pen); - PROP_B(QPen, select_marker_pen); - PROP_B(QPen, marker_pen); - PROP_B(QPen, middle_frequency_pen); - PROP_B(QBrush, sweep_rect_brush); + PROP_B(Brush, max_brush); + PROP_B(Brush, cur_brush); + PROP_B(Brush, min_brush); + PROP_B(Pen, max_pen); + PROP_B(Pen, cur_pen); + PROP_B(Pen, min_pen); + PROP_B(Pen, select_marker_pen); + PROP_B(Pen, marker_pen); + PROP_B(Pen, middle_frequency_pen); + PROP_B(Brush, sweep_rect_brush); /// 创建频谱线构造器。 Builder(std::shared_ptr parent, std::shared_ptr frequency_axis, std::shared_ptr axis); /// 构建并返回频谱线对象。 diff --git a/Renderive/plottable/Spectrum_p.h b/Core/plottable/Spectrum_p.h similarity index 67% rename from Renderive/plottable/Spectrum_p.h rename to Core/plottable/Spectrum_p.h index 8ea8fee..02b0ee2 100644 --- a/Renderive/plottable/Spectrum_p.h +++ b/Core/plottable/Spectrum_p.h @@ -1,15 +1,22 @@ #pragma once #include +#include +#include #include #include #include "../Axis/Frequency_Axis_p.h" #include "../Axis/Axis_p.h" -#include "../plot/Plot_p.h" #include "../architecture/Renderable.h" #include "../base/Frame_Memory.h" #include "../base/Memory.h" +#include "../base/Color.h" +#include "../base/Geometry.h" +#include "../base/Style.h" +#include "../render/Canvas.h" #include "Hover_Info.h" #include "Interpolation_p.h" +#include "../primitive/Label.h" +#include "../primitive/Marker.h" #include "Spectrum.h" namespace renderive { struct Spectrum_Marker { @@ -18,7 +25,7 @@ struct Spectrum_Marker { double frequency{}; bool draw_line = true; bool operator==(const Spectrum_Marker& other) const { - return qFuzzyCompare(frequency, other.frequency) && draw_line == other.draw_line; + return std::abs(frequency - other.frequency) < 1e-12 && draw_line == other.draw_line; } }; struct Spectrum_Input_Data : Latest_Input_Data { @@ -37,7 +44,7 @@ struct Spectrum_Input_Data : Latest_Input_Data { has_max_data = true; } else { - for (int i = 0; i < data.size(); ++i) { + for (size_t i = 0; i < data.size(); ++i) { if (data[i] > max_data[i]) max_data[i] = data[i]; } @@ -49,7 +56,7 @@ struct Spectrum_Input_Data : Latest_Input_Data { has_min_data = true; } else { - for (int i = 0; i < data.size(); ++i) { + for (size_t i = 0; i < data.size(); ++i) { if (data[i] < min_data[i]) min_data[i] = data[i]; } @@ -66,7 +73,7 @@ struct Spectrum_Input_Data : Latest_Input_Data { has_max_data = true; } else { - for (int i = 0; i < cur_data.size(); ++i) { + for (size_t i = 0; i < cur_data.size(); ++i) { if (cur_data[i] > max_data[i]) max_data[i] = cur_data[i]; } @@ -78,7 +85,7 @@ struct Spectrum_Input_Data : Latest_Input_Data { has_min_data = true; } else { - for (int i = 0; i < cur_data.size(); ++i) { + for (size_t i = 0; i < cur_data.size(); ++i) { if (cur_data[i] < min_data[i]) min_data[i] = cur_data[i]; } @@ -111,15 +118,15 @@ struct Spectrum_Render_State : Render_State, Hover_Info_Render_State, Spectrum_P void select_next_marker() { if (marker_list.empty()) return; - cur_select_index = (cur_select_index + 1) % marker_list.size(); + cur_select_index = (cur_select_index + 1) % static_cast(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(); + cur_select_index = (cur_select_index - 1 + static_cast(marker_list.size())) % static_cast(marker_list.size()); } void set_select_index(int index) { - if (index < 0 || index >= marker_list.size()) + if (index < 0 || index >= static_cast(marker_list.size())) return; cur_select_index = index; } @@ -127,33 +134,33 @@ struct Spectrum_Render_State : Render_State, Hover_Info_Render_State, Spectrum_P set_frequency(cur_select_index, frequency); } void set_frequency(int index, double frequency) { - if (index < 0 || index >= marker_list.size()) + if (index < 0 || index >= static_cast(marker_list.size())) return; marker_list[index].frequency = frequency; } - void draw_axis_rect(QPainter* painter, Range range, double middle_x) const { + void draw_axis_rect(Canvas& canvas, 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(); + Axis_Mapping_2D mapping = Axis_Render_Access::mapping(frequency_axis.get(), power_axis.get()); + canvas.save(); + canvas.set_pen(Pen{.style = Line_Style::None}); + canvas.set_brush(sweep_rect_brush); + std::array rect_points{ + mapping.map(range.origin, mapping.value.coord_range.origin), + mapping.map(range.target, mapping.value.coord_range.origin), + mapping.map(range.target, mapping.value.coord_range.target), + mapping.map(range.origin, mapping.value.coord_range.target)}; + canvas.draw_polygon(std::span(rect_points.data(), rect_points.size()), true); + canvas.set_pen(middle_frequency_pen); + canvas.draw_line( + mapping.map(middle_x, mapping.value.coord_range.origin), + mapping.map(middle_x, mapping.value.coord_range.target)); + canvas.restore(); } }; -struct Spectrum_Private : Typed_Render_Data { - std::weak_ptr marker_item; - std::weak_ptr