完美一版
This commit is contained in:
@@ -0,0 +1,14 @@
|
||||
#include "Frame_2D.hpp"
|
||||
#include <stdexcept>
|
||||
namespace aethera::render_2d {
|
||||
struct Frame_2D::Private {
|
||||
Blend2D_Cache color{}; /* Scene 直接合成且由外部 Frame 生命周期持有的最终颜色层。 */
|
||||
};
|
||||
Frame_2D::Frame_2D(Frame_Identity identity) : Render_Frame(identity), d(std::make_unique<Private>()) {}
|
||||
Frame_2D::~Frame_2D() = default;
|
||||
Image_View Frame_2D::image() const { return d->color.view(); }
|
||||
Blend2D_Cache& detail::Frame_2D_Access::render_target(Frame_2D* frame) {
|
||||
if (!frame) throw std::invalid_argument("Render_Scene_2D requires a non-null external frame");
|
||||
return frame->d->color;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
#pragma once
|
||||
#include "../render/Blend2D_Cache.hpp"
|
||||
#include <frame.hpp>
|
||||
#include <memory>
|
||||
namespace aethera::render_2d {
|
||||
class Frame_2D;
|
||||
namespace detail {
|
||||
struct Frame_2D_Access {
|
||||
[[nodiscard]] static Blend2D_Cache& render_target(Frame_2D* frame);
|
||||
};
|
||||
}
|
||||
class Frame_2D final : public Render_Frame {
|
||||
public:
|
||||
explicit Frame_2D(Frame_Identity identity);
|
||||
~Frame_2D() override;
|
||||
[[nodiscard]] Image_View image() const;
|
||||
private:
|
||||
struct Private;
|
||||
std::unique_ptr<Private> d; /* 本帧最终二维颜色结果的唯一所有权。 */
|
||||
friend struct detail::Frame_2D_Access;
|
||||
};
|
||||
}
|
||||
@@ -20,12 +20,14 @@ struct Selection_Rectangle_Overlay::Private : Prev_Private {
|
||||
Point_F press_position{}; /* 仅用于区分点击与拖动的按下像素位置。 */
|
||||
bool dragging{}; /* 是否正在构造尚未提交的选择矩形。 */
|
||||
const Dispatch* dispatch{}; /* 最终类型公开薄壳分派表。 */
|
||||
static constexpr double minimum_drag_distance_pixels{3.0}; /* 允许提交和绘制选择框的最小像素距离。 */
|
||||
/* CRTP 覆盖:绑定 Paint-only、事件能力和最终 Overlay 分派表。 */
|
||||
template <Attached Object> void bind_private_crtp(Object* object);
|
||||
void bind_sources(Abs_Axis* horizontal_axis_value, Abs_Axis* vertical_axis_value);
|
||||
template <Attached Object> [[nodiscard]] static const Dispatch& dispatch_for();
|
||||
[[nodiscard]] static Axis_Rectangle axis_rectangle(Axis_Point first, Axis_Point second);
|
||||
[[nodiscard]] static std::string range_label(const Abs_Axis* axis, Axis_Range range);
|
||||
[[nodiscard]] bool has_visible_drag() const;
|
||||
/* CRTP 覆盖:无需 Prepare 子图,直接绘制选择矩形。 */
|
||||
template <Attached Object> void paint(Object* object);
|
||||
/* CRTP 覆盖:处理拖拽并更新权威选择区域状态。 */
|
||||
@@ -81,7 +83,11 @@ void Selection_Rectangle_Overlay::Private::paint(Object* object) {
|
||||
painter.text({label_position.x, label_position.y + std::max(12.0, state.label_font.size * 1.35)}, "Y " + range_label(vertical_axis, region.vertical), state.label_font, state.label_pen);
|
||||
};
|
||||
for (const auto& region : state.selected_regions) paint_region(region);
|
||||
if (dragging) paint_region(axis_rectangle(drag_origin, drag_current));
|
||||
if (dragging && has_visible_drag()) paint_region(axis_rectangle(drag_origin, drag_current));
|
||||
}
|
||||
inline bool Selection_Rectangle_Overlay::Private::has_visible_drag() const {
|
||||
const Point_F current_position{horizontal_axis->coordinate_to_pixel(drag_current.horizontal), vertical_axis->coordinate_to_pixel(drag_current.vertical)};
|
||||
return std::hypot(current_position.x - press_position.x, current_position.y - press_position.y) >= minimum_drag_distance_pixels;
|
||||
}
|
||||
template <Attached Object>
|
||||
void Selection_Rectangle_Overlay::Private::handle_event(Object* object, const Event& event) {
|
||||
@@ -98,7 +104,7 @@ void Selection_Rectangle_Overlay::Private::handle_event(Object* object, const Ev
|
||||
if (event.type == Event_Type::pointer_move && dragging) { drag_current = axis_point; object->template mark_dirty<Paint_Tag>(); event.accept(); return; }
|
||||
if (event.type != Event_Type::pointer_release || !dragging) return;
|
||||
dragging = false; drag_current = axis_point;
|
||||
if (std::hypot(point.x - press_position.x, point.y - press_position.y) < 3.0) {
|
||||
if (std::hypot(point.x - press_position.x, point.y - press_position.y) < minimum_drag_distance_pixels) {
|
||||
object->template mark_dirty<Paint_Tag>(); event.accept(); return;
|
||||
}
|
||||
const Axis_Rectangle selected = axis_rectangle(drag_origin, drag_current);
|
||||
|
||||
@@ -15,7 +15,8 @@ struct Sweep_Spectrum : Def<Sweep_Spectrum, Renderable_2D<Renderable_2D_Cache::e
|
||||
using Power_Object = Impl<Numeric_Axis>;
|
||||
struct Prop : Prev_Prop {
|
||||
std::size_t bins_per_block{}; /* 每个扫频块期望的功率点数;零值接受首块尺寸。 */
|
||||
std::size_t block_count{1}; /* 最多保留的扫频块数;零值按 1 处理。 */
|
||||
std::size_t block_count{1}; /* 一个完整扫频周期包含的块数;零值按 1 处理。 */
|
||||
std::size_t next_block_index{}; /* 下一块覆盖的频率段下标;由 append_block() 推进。 */
|
||||
Plot_Partition_Count partition_count{1}; /* fixed 模式使用的 Prepare 子图分块数。 */
|
||||
bool visible_range_only{true}; /* 是否裁掉频率轴可见范围外的线段。 */
|
||||
Axis_Range frequency_range{}; /* 全部扫描块覆盖的频率范围。 */
|
||||
@@ -23,13 +24,13 @@ struct Sweep_Spectrum : Def<Sweep_Spectrum, Renderable_2D<Renderable_2D_Cache::e
|
||||
Pen pen{Color::yellow()}; /* 扫频折线样式。 */
|
||||
Pen current_frequency_pen{Color::red_color(), 2.0}; /* 当前扫频位置垂线样式。 */
|
||||
Line_Interpolation_Mode interpolation_mode{Line_Interpolation_Mode::linear_value}; /* 相邻功率点插值方式。 */
|
||||
std::vector<std::vector<Plot_Value>> blocks{}; /* 已提交扫描块的唯一权威集合。 */
|
||||
std::vector<std::vector<Plot_Value>> blocks{}; /* 各频率段最新数据的唯一权威槽位;下标即频率段。 */
|
||||
bool operator==(const Prop&) const;
|
||||
};
|
||||
struct State : Prev_State {
|
||||
std::size_t stored_block_count{}; /* 当前发布的扫描块数。 */
|
||||
std::size_t stored_point_count{}; /* 当前发布的扫描点总数。 */
|
||||
std::size_t rendered_point_count{}; /* 最近一次 Prepare 生成的曲线点数。 */
|
||||
std::size_t stored_block_count{}; /* 当前已有最新数据的频率段数量。 */
|
||||
std::size_t stored_point_count{}; /* 当前单条组合折线保留的总点数。 */
|
||||
std::size_t rendered_point_count{}; /* 最近一次 Prepare 为单条折线生成的点数。 */
|
||||
bool operator==(const State&) const;
|
||||
};
|
||||
struct Private;
|
||||
|
||||
@@ -5,13 +5,13 @@
|
||||
namespace aethera::render_2d {
|
||||
struct Sweep_Spectrum::Private : Prev_Private {
|
||||
struct Prepared {
|
||||
std::vector<detail::Curve_Prepared> partitions{}; /* Prepare 子图各分块的曲线输出。 */
|
||||
std::vector<Plot_Value> values{}; /* 已提交扫描块拼接后的连续功率值。 */
|
||||
Axis_Range domain{}; /* 当前已到达数据对应的频率区间。 */
|
||||
Point_F marker_first{}; /* 当前扫描位置线的首端点。 */
|
||||
Point_F marker_second{}; /* 当前扫描位置线的末端点。 */
|
||||
std::vector<detail::Curve_Prepared> partitions{}; /* 单条组合折线各 Prepare 分块的曲线输出。 */
|
||||
std::vector<Plot_Value> values{}; /* 各频率段最新数据按槽位顺序拼接的功率值。 */
|
||||
Axis_Range domain{}; /* 当前已有频率段共同覆盖的业务坐标区间。 */
|
||||
Point_F marker_first{}; /* 最新写入段末端扫描位置线的首端点。 */
|
||||
Point_F marker_second{}; /* 最新写入段末端扫描位置线的末端点。 */
|
||||
Size canvas{}; /* 当前 Scene viewport 的像素尺寸。 */
|
||||
bool valid{}; /* 两根轴是否正交且画布有效。 */
|
||||
bool valid{}; /* 两根轴正交、画布及组合折线均有效。 */
|
||||
};
|
||||
using Append_Run = void (*)(Root*, std::span<const Plot_Value>); using Count_Run = std::size_t (*)(const Root*);
|
||||
struct Dispatch {
|
||||
@@ -58,16 +58,20 @@ template <Attached Object>
|
||||
tf::Taskflow Sweep_Spectrum::Private::build_paint_graph(Object* object, const Prop&) { tf::Taskflow graph; graph.emplace([this, object] { paint_frame(object); }).name("sweep_spectrum.paint.frame"); return graph; }
|
||||
template <Attached Object>
|
||||
void Sweep_Spectrum::Private::prepare_frame(Object* object) {
|
||||
const auto& state = object->template read_prop<Sweep_Spectrum::Base_Tag>(); const auto& frequency_layout = frequency_axis->template read_prop<Abs_Axis::Base_Tag>(); const auto& power_layout = power_axis->template read_prop<Abs_Axis::Base_Tag>(); const auto& power_state = power_axis->template read_prop<Numeric_Axis::Base_Tag>(); prepared = {}; prepared.partitions.resize(graph_partition_count); prepared.canvas = scene->template read_prop<Render_Scene_2D::Base_Tag>().viewport;
|
||||
if (prepared.canvas.empty() || frequency_layout.orientation == power_layout.orientation) return; for (const auto& block : state.blocks) prepared.values.insert(prepared.values.end(), block.begin(), block.end()); if (prepared.values.empty()) return;
|
||||
const std::size_t bins_per_block = state.bins_per_block > 0
|
||||
? state.bins_per_block : state.blocks.front().size();
|
||||
const std::size_t expected_points = std::max<std::size_t>(1, bins_per_block * std::max<std::size_t>(1, state.block_count));
|
||||
const double progress = expected_points > 1
|
||||
? std::clamp(static_cast<double>(prepared.values.size() - 1) / static_cast<double>(expected_points - 1), 0.0, 1.0)
|
||||
: 1.0;
|
||||
const auto latest_frequency = state.frequency_range.origin + state.frequency_range.length() * progress;
|
||||
prepared.domain = {state.frequency_range.origin, latest_frequency};
|
||||
const auto& state = object->template read_prop<Sweep_Spectrum::Base_Tag>(); const auto& frequency_layout = frequency_axis->template read_prop<Abs_Axis::Base_Tag>(); const auto& power_layout = power_axis->template read_prop<Abs_Axis::Base_Tag>(); const auto& power_state = power_axis->template read_prop<Numeric_Axis::Base_Tag>(); prepared = {}; prepared.canvas = scene->template read_prop<Render_Scene_2D::Base_Tag>().viewport;
|
||||
if (prepared.canvas.empty() || frequency_layout.orientation == power_layout.orientation || state.blocks.empty()) return;
|
||||
const std::size_t block_count = std::max<std::size_t>(1, state.block_count);
|
||||
const std::size_t stored_block_count = std::min(block_count, state.blocks.size());
|
||||
prepared.partitions.resize(graph_partition_count);
|
||||
for (std::size_t index = 0; index < stored_block_count; ++index) prepared.values.insert(prepared.values.end(), state.blocks[index].begin(), state.blocks[index].end());
|
||||
if (prepared.values.empty()) return;
|
||||
const bool complete = stored_block_count == block_count;
|
||||
const std::size_t latest_block_index = complete ? (state.next_block_index + block_count - 1) % block_count : stored_block_count - 1;
|
||||
const double domain_progress = static_cast<double>(stored_block_count) / static_cast<double>(block_count);
|
||||
const double marker_progress = static_cast<double>(latest_block_index + 1) / static_cast<double>(block_count);
|
||||
const auto domain_target = state.frequency_range.origin + state.frequency_range.length() * domain_progress;
|
||||
const auto latest_frequency = state.frequency_range.origin + state.frequency_range.length() * marker_progress;
|
||||
prepared.domain = {state.frequency_range.origin, domain_target};
|
||||
prepared.marker_first = detail::map_plot_point(frequency_axis, latest_frequency, power_axis, power_state.coordinate_range.origin, frequency_layout.orientation); prepared.marker_second = detail::map_plot_point(frequency_axis, latest_frequency, power_axis, power_state.coordinate_range.target, frequency_layout.orientation); prepared.valid = true;
|
||||
}
|
||||
template <Attached Object>
|
||||
@@ -78,13 +82,13 @@ void Sweep_Spectrum::Private::prepare_partition(Object* object, Plot_Partition_C
|
||||
template <Attached Object>
|
||||
void Sweep_Spectrum::Private::paint_frame(Object* object) { const auto& state = object->template read_prop<Sweep_Spectrum::Base_Tag>(); auto& cache = this->paint_surface(); if (!prepared.valid) return; detail::Painter painter(cache, prepared.canvas); for (const auto& curve : prepared.partitions) detail::paint_curve(painter, curve, state.pen); painter.line(prepared.marker_first, prepared.marker_second, state.current_frequency_pen); }
|
||||
template <typename Object, typename Owner, typename Member, typename Prop_Type>
|
||||
void Sweep_Spectrum::Private::after_prop_set(Object* object, Member Owner::*, Prop_Access<Prop_Type>) { if constexpr (std::same_as<Owner, Prop>) object->template mark_dirty<Prepare_Data_Tag>(); }
|
||||
void Sweep_Spectrum::Private::after_prop_set(Object* object, Member Owner::*, Prop_Access<Prop_Type> props) { if constexpr (std::same_as<Owner, Prop>) { auto& state = props.template get<Sweep_Spectrum::Base_Tag>(); const std::size_t block_count = std::max<std::size_t>(1, state.block_count); if (state.blocks.size() > block_count) state.blocks.resize(block_count); state.next_block_index = state.blocks.size() < block_count ? state.blocks.size() : state.next_block_index % block_count; object->template mark_dirty<Prepare_Data_Tag>(); } }
|
||||
template <typename Object, typename Prop_Type, typename State_Type>
|
||||
void Sweep_Spectrum::Private::before_advance(Object*, Prop_Type*, State_Access<State_Type> pending_states, const Prop_Type* current_prop, State_Access<const State_Type>) { auto& state = pending_states.template get<Sweep_Spectrum::Base_Tag>(); const auto& prop = static_cast<const Prop&>(*current_prop); state.stored_block_count = prop.blocks.size(); state.stored_point_count = 0; for (const auto& block : prop.blocks) state.stored_point_count += block.size(); state.rendered_point_count = 0; for (const auto& partition : prepared.partitions) state.rendered_point_count += partition.points.size(); }
|
||||
template <Attached Object>
|
||||
const Sweep_Spectrum::Private::Dispatch& Sweep_Spectrum::Private::dispatch_for() {
|
||||
static const Dispatch value{
|
||||
[](Root* root, std::span<const Plot_Value> values) { auto* object = static_cast<Object*>(root); object->template update_prop<&Prop::blocks>([values](Prop_Access<typename Object::Prop> props) { auto& state = props.template get<Sweep_Spectrum::Base_Tag>(); state.blocks.emplace_back(values.begin(), values.end()); const std::size_t limit = std::max<std::size_t>(1, state.block_count); while (state.blocks.size() > limit) state.blocks.erase(state.blocks.begin()); }); },
|
||||
[](Root* root, std::span<const Plot_Value> values) { auto* object = static_cast<Object*>(root); object->template update_prop<&Prop::blocks, &Prop::next_block_index>([values](Prop_Access<typename Object::Prop> props) { auto& state = props.template get<Sweep_Spectrum::Base_Tag>(); const std::size_t block_count = std::max<std::size_t>(1, state.block_count); if (state.blocks.size() > block_count) state.blocks.resize(block_count); if (state.blocks.size() < block_count) { state.next_block_index = state.blocks.size(); state.blocks.emplace_back(values.begin(), values.end()); } else { state.next_block_index %= block_count; state.blocks[state.next_block_index].assign(values.begin(), values.end()); } state.next_block_index = (state.next_block_index + 1) % block_count; }); },
|
||||
[](const Root* root) { return static_cast<const Object*>(root)->template read_prop<Sweep_Spectrum::Base_Tag>().blocks.size(); },
|
||||
[](const Root* root) { const auto& blocks = static_cast<const Object*>(root)->template read_prop<Sweep_Spectrum::Base_Tag>().blocks; std::size_t count{}; for (const auto& block : blocks) count += block.size(); return count; },
|
||||
[](const Root* root) { const auto& data = static_cast<const typename Object::Private&>(*static_cast<const Object*>(root)->d); std::size_t count{}; for (const auto& curve : data.prepared.partitions) count += curve.points.size(); return count; }
|
||||
|
||||
@@ -3,7 +3,7 @@ namespace aethera::render_2d {
|
||||
bool Render_Scene_2D::State::operator==(const State&) const = default;
|
||||
bool Render_Scene_2D::Prop::operator==(const Prop&) const = default;
|
||||
|
||||
void Render_Scene_2D::render() { static_cast<Private&>(*d).dispatch->render(this); }
|
||||
Render_Scene_2D::Render_Result Render_Scene_2D::render(Frame_2D* frame) { return static_cast<Private&>(*d).dispatch->render(this, frame); }
|
||||
void Render_Scene_2D::set_frame_callback(Frame_Callback callback) { static_cast<Private&>(*d).dispatch->set_frame_callback(this, std::move(callback)); }
|
||||
void Render_Scene_2D::dispatch_event(std::unique_ptr<Event> event) {
|
||||
static_cast<Private&>(*d).dispatch->push_event(this, std::move(event));
|
||||
@@ -14,7 +14,4 @@ void Render_Scene_2D::activate_view() {
|
||||
void Render_Scene_2D::deactivate_view() {
|
||||
static_cast<Private&>(*d).dispatch->set_active(this, false);
|
||||
}
|
||||
Image_View Render_Scene_2D::frame_view() const {
|
||||
return static_cast<const Private&>(*d).dispatch->frame_view(this);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,21 +1,20 @@
|
||||
#pragma once
|
||||
#include "../base/Frame_2D.hpp"
|
||||
#include "../base/Renderable_2D.hpp"
|
||||
#include "../render/Blend2D_Cache.hpp"
|
||||
#include <scene.hpp>
|
||||
#include <functional>
|
||||
#include <memory>
|
||||
namespace aethera::render_2d {
|
||||
struct Scene_Color_Cache_Tag {};
|
||||
/* 执行二维 Renderable 图、合成颜色层并发布最终像素帧。 */
|
||||
struct Render_Scene_2D : Def<Render_Scene_2D, Scene,
|
||||
Tagged_Buffer<Scene_Color_Cache_Tag, Blend2D_Cache>,
|
||||
Dependency_Graph_Type<Paint_Tag, Renderable_2D_Base>,
|
||||
Dependency_Graph_Type<Paint_Cache_Tag, Renderable_2D_Base>
|
||||
> {
|
||||
struct Prop : Prev_Prop {
|
||||
Size viewport{}; /* 最终帧的像素尺寸;空尺寸不执行渲染。 */
|
||||
Color background{Color::black()}; /* 每帧合成前写入的背景颜色。 */
|
||||
bool view_active{}; /* 视图是否接受 render() 产生新的完成帧。 */
|
||||
bool view_active{}; /* 视图是否接受 render(frame*) 产生新的完成帧。 */
|
||||
bool operator==(const Prop&) const;
|
||||
};
|
||||
struct State : Prev_State {
|
||||
@@ -34,10 +33,11 @@ struct Render_Scene_2D : Def<Render_Scene_2D, Scene,
|
||||
private:
|
||||
std::vector<std::function<std::expected<void, Dependency_Graph_Error>(Object*)>> attachments{}; /* 仅在 build 期间绑定已构造 Renderable。 */
|
||||
};
|
||||
using Frame_Callback = std::function<void(Image_View)>;
|
||||
/* 合成一帧;执行期间的重复调用合并为一次后继帧。 */
|
||||
void render();
|
||||
/* 安装完成帧回调;Image_View 仅在回调执行期间有效。 */
|
||||
enum class Render_Result { completed, view_inactive, empty_viewport };
|
||||
using Frame_Callback = std::function<void(Frame_2D*)>;
|
||||
/* 向调用方拥有的帧合成一次;frame 必须存活到完成回调返回。 */
|
||||
[[nodiscard]] Render_Result render(Frame_2D* frame);
|
||||
/* 安装完成帧回调;回调收到的就是对应 render(frame) 传入的对象。 */
|
||||
void set_frame_callback(Frame_Callback callback);
|
||||
/* 无锁提交事件所有权;下一次 Prepare 开始时按事件区域和 Paint 逆序派发,首个接受者终止传播。 */
|
||||
void dispatch_event(std::unique_ptr<Event> event);
|
||||
@@ -45,8 +45,6 @@ struct Render_Scene_2D : Def<Render_Scene_2D, Scene,
|
||||
void activate_view();
|
||||
/* 停止后续 render 调用,不清除最后一帧。 */
|
||||
void deactivate_view();
|
||||
/* 返回最后一次成功合成的只读像素视图;下次渲染后失效。 */
|
||||
[[nodiscard]] Image_View frame_view() const;
|
||||
};
|
||||
}
|
||||
#include "Render_Scene_2D.ipp"
|
||||
|
||||
@@ -32,11 +32,10 @@ std::expected<std::unique_ptr<Object>, Dependency_Graph_Error> Render_Scene_2D::
|
||||
return scene;
|
||||
}
|
||||
struct Render_Scene_2D::Private : Prev_Private {
|
||||
using Render_Run = void (*)(Root*);
|
||||
using Render_Run = Render_Result (*)(Root*, Frame_2D*);
|
||||
using Callback_Run = void (*)(Root*, Frame_Callback);
|
||||
using Event_Run = void (*)(Root*, std::unique_ptr<Event>);
|
||||
using Active_Run = void (*)(Root*, bool);
|
||||
using Frame_View_Run = Image_View (*)(const Root*);
|
||||
struct Paint_Node {
|
||||
Root* object{}; /* Paint 拓扑位置对应的最终对象;Scene 不拥有。 */
|
||||
Renderable_2D_Base::Private* private_data{}; /* 对象的二维能力层;对象存活期间有效。 */
|
||||
@@ -54,17 +53,16 @@ struct Render_Scene_2D::Private : Prev_Private {
|
||||
Renderable_2D_Base::Private* private_data{}; /* 候选对象的二维能力层;仅在本次 Prepare 分发期间有效。 */
|
||||
};
|
||||
struct Dispatch {
|
||||
Render_Run render; /* 执行最终 Scene 并合成颜色层。 */
|
||||
Render_Run render; /* 向外部帧执行最终 Scene 并合成颜色层。 */
|
||||
Callback_Run set_frame_callback; /* 安装最终完成帧回调。 */
|
||||
Event_Run push_event; /* 向最终 Scene 的无锁待处理批次转移事件所有权。 */
|
||||
Active_Run set_active; /* 修改最终 Scene 的视图活动状态。 */
|
||||
Frame_View_Run frame_view; /* 访问最终 Scene 已合成的写侧帧。 */
|
||||
};
|
||||
const Dispatch* dispatch{}; /* Builder 绑定最终 Scene 类型后的静态分派表。 */
|
||||
Frame_Callback frame_callback{}; /* 合成完成后的唯一像素发布出口。 */
|
||||
std::unique_ptr<tf::Taskflow> paint_taskflow{}; /* 仅由二维 Paint 图构建的执行图。 */
|
||||
bool rendering{}; /* 是否正在同步合成当前帧。 */
|
||||
bool render_pending{}; /* 合成期间是否又收到 render();多个调用合并。 */
|
||||
Frame_2D* active_frame{}; /* 当前同步 process 借用的外部帧;render 返回前清空。 */
|
||||
Blend2D_Cache* frame_target{}; /* 当前 render(frame) 所属外部颜色层;调用返回后清空。 */
|
||||
/* Impl CRTP 实现:在对象锁内执行 Kernel Scene,再按 Paint 图拓扑顺序合成颜色层。 */
|
||||
std::vector<Paint_Node> paint_order{}; /* Paint 图当前拓扑序及 Scene 缓存分组结果。 */
|
||||
std::vector<Cache_Group> cache_groups{}; /* 仅保存显式缓存根对应的执行分组。 */
|
||||
@@ -77,7 +75,7 @@ struct Render_Scene_2D::Private : Prev_Private {
|
||||
template <Attached Object> void prepare_paint_targets(Object* object, Blend2D_Cache& frame, Size viewport);
|
||||
/* Prepare 开始时交换 Scene 事件批次,按区域、Paint 逆序和最近目标规则逐事件完成接受链。 */
|
||||
template <Attached Object> void dispatch_events(Object* object, Size viewport);
|
||||
template <Attached Object> void render(Object* object);
|
||||
template <Attached Object> [[nodiscard]] Render_Result render(Object* object, Frame_2D* frame);
|
||||
template <Attached Object>
|
||||
[[nodiscard]] static const Dispatch& dispatch_for();
|
||||
/* CRTP 覆盖:Builder 挂接最终 Private 后安装二维 Scene 的无虚函数业务分派。 */
|
||||
@@ -174,8 +172,10 @@ void Render_Scene_2D::Private::after_advance(Object* object, Prop*, State_Access
|
||||
const auto owner_node = std::find_if(paint_order.begin(), paint_order.end(), [cache_owner](const Paint_Node& value) { return value.object == cache_owner; });
|
||||
if (group == cache_groups.end() || owner_node == paint_order.end()) return;
|
||||
if (group->rebuild) owner_node->private_data->valid_cache = owner_node->private_data->paint_target;
|
||||
if (owner_node->private_data->valid_cache)
|
||||
object->template pending_buffer<Scene_Color_Cache_Tag>().composite(*owner_node->private_data->valid_cache);
|
||||
if (owner_node->private_data->valid_cache) {
|
||||
if (!frame_target) throw std::logic_error("2D paint graph has no external frame target");
|
||||
frame_target->composite(*owner_node->private_data->valid_cache);
|
||||
}
|
||||
}).name("render_2d.paint.cache.composite");
|
||||
paint_done.precede(composite);
|
||||
previous = composite;
|
||||
@@ -228,14 +228,14 @@ void Render_Scene_2D::Private::prepare_paint_targets(Object*, Blend2D_Cache& fra
|
||||
template <Attached Object, typename Callback>
|
||||
void Render_Scene_2D::Private::process(Object* object, Callback&& callback)
|
||||
requires std::invocable<Callback> {
|
||||
Frame_2D* frame_object = active_frame;
|
||||
if (!frame_object) throw std::logic_error("2D Scene process has no external frame");
|
||||
const auto& state = static_cast<const Prop&>(*static_cast<typename Object::Private&>(*this).current);
|
||||
if (!state.view_active) {
|
||||
return;
|
||||
}
|
||||
if (state.viewport.empty()) {
|
||||
return;
|
||||
}
|
||||
if (!state.view_active || state.viewport.empty()) return;
|
||||
frame_object->mark(Frame_Trace_Marker::scene_render_started);
|
||||
frame_object->mark(Frame_Trace_Marker::event_dispatch_started);
|
||||
dispatch_events(object, state.viewport);
|
||||
frame_object->mark(Frame_Trace_Marker::event_dispatch_finished);
|
||||
object->template current_dependency_graph<Paint_Cache_Tag>().for_each([](const Dependency_Graph::Node& node) {
|
||||
if (!node.object->template take_dirty<Paint_Cache_Tag>()) return;
|
||||
node.object->template mark_dirty<Prepare_Data_Tag>();
|
||||
@@ -248,9 +248,16 @@ void Render_Scene_2D::Private::process(Object* object, Callback&& callback)
|
||||
execute = execute || !data.prepare_graph_built || data.dispatch->prepare.rebuild_predicate(renderable);
|
||||
if (execute) prepare_executions.push_back(renderable);
|
||||
});
|
||||
Prev_Private::process(object, [&](const Scene::Private::Result&) {
|
||||
Prev_Private::process(object, frame_object, [&](const Scene::Private::Result&) {
|
||||
frame_object->mark(Frame_Trace_Marker::paint_started);
|
||||
for (auto* renderable : prepare_executions) renderable->template mark_dirty<Paint_Tag>();
|
||||
auto& frame = object->template pending_buffer<Scene_Color_Cache_Tag>();
|
||||
auto& frame = detail::Frame_2D_Access::render_target(frame_object);
|
||||
struct Frame_Target_Scope {
|
||||
Blend2D_Cache*& target; /* Scene 当前帧目标槽位。 */
|
||||
Blend2D_Cache* previous{}; /* 嵌套调用前的目标;析构时恢复。 */
|
||||
~Frame_Target_Scope() { target = previous; }
|
||||
} frame_target_scope{frame_target, frame_target};
|
||||
frame_target = &frame;
|
||||
frame.ensure_size(state.viewport);
|
||||
frame.clear();
|
||||
{
|
||||
@@ -261,18 +268,31 @@ void Render_Scene_2D::Private::process(Object* object, Callback&& callback)
|
||||
}
|
||||
prepare_paint_targets(object, frame, state.viewport);
|
||||
if (paint_taskflow && !paint_taskflow->empty()) aethera::detail::run_taskflow(*paint_taskflow);
|
||||
frame_object->mark(Frame_Trace_Marker::paint_finished);
|
||||
});
|
||||
std::invoke(std::forward<Callback>(callback));
|
||||
}
|
||||
template <Attached Object>
|
||||
void Render_Scene_2D::Private::render(Object* object) {
|
||||
if (rendering) { render_pending = true; return; }
|
||||
rendering = true;
|
||||
do {
|
||||
render_pending = false;
|
||||
object->process([&] { if (frame_callback) frame_callback(object->template pending_buffer<Scene_Color_Cache_Tag>().view()); });
|
||||
} while (render_pending);
|
||||
rendering = false;
|
||||
Render_Scene_2D::Render_Result Render_Scene_2D::Private::render(Object* object, Frame_2D* frame) {
|
||||
static_cast<void>(detail::Frame_2D_Access::render_target(frame));
|
||||
frame->mark(Frame_Trace_Marker::scene_render_requested);
|
||||
struct Active_Frame_Scope {
|
||||
Frame_2D*& target; /* 最终 Private 的同步 process 帧槽位。 */
|
||||
Frame_2D* previous{}; /* 嵌套调用前的帧;析构时恢复。 */
|
||||
~Active_Frame_Scope() { target = previous; }
|
||||
} active_frame_scope{active_frame, active_frame};
|
||||
active_frame = frame;
|
||||
bool completed{};
|
||||
object->process([&] {
|
||||
completed = true;
|
||||
frame->mark(Frame_Trace_Marker::scene_render_finished);
|
||||
frame->mark(Frame_Trace_Marker::callback_started);
|
||||
if (frame_callback) frame_callback(frame);
|
||||
frame->mark(Frame_Trace_Marker::callback_finished);
|
||||
});
|
||||
if (completed) return Render_Result::completed;
|
||||
const auto& prop = object->template read_prop<Render_Scene_2D::Base_Tag>();
|
||||
return prop.view_active ? Render_Result::empty_viewport : Render_Result::view_inactive;
|
||||
}
|
||||
template <Attached Object>
|
||||
void Render_Scene_2D::Private::dispatch_events(Object* object, Size viewport) {
|
||||
@@ -303,9 +323,9 @@ void Render_Scene_2D::Private::dispatch_events(Object* object, Size viewport) {
|
||||
template <Attached Object>
|
||||
const Render_Scene_2D::Private::Dispatch& Render_Scene_2D::Private::dispatch_for() {
|
||||
static const Dispatch value{
|
||||
[](Root* root) {
|
||||
[](Root* root, Frame_2D* frame) {
|
||||
auto* object = static_cast<Object*>(root);
|
||||
static_cast<typename Object::Private&>(*object->d).render(object);
|
||||
return static_cast<typename Object::Private&>(*object->d).render(object, frame);
|
||||
},
|
||||
[](Root* root, Frame_Callback callback) {
|
||||
auto* object = static_cast<Object*>(root);
|
||||
@@ -315,14 +335,7 @@ const Render_Scene_2D::Private::Dispatch& Render_Scene_2D::Private::dispatch_for
|
||||
auto* object = static_cast<Object*>(root);
|
||||
static_cast<typename Object::Private&>(*object->d).push_event(std::move(event));
|
||||
},
|
||||
[](Root* root, bool active) {
|
||||
static_cast<Object*>(root)->template set<&Prop::view_active>(active);
|
||||
},
|
||||
[](const Root* root) {
|
||||
const auto* object = static_cast<const Object*>(root);
|
||||
const auto& private_data = static_cast<const typename Object::Private&>(*object->d);
|
||||
return private_data.buffer_storage.template get<Scene_Color_Cache_Tag>().pending->view();
|
||||
}
|
||||
[](Root* root, bool active) { static_cast<Object*>(root)->template set<&Prop::view_active>(active); }
|
||||
};
|
||||
return value;
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
#include <render_2D/axis/Axis.hpp>
|
||||
#include <render_2D/base/Frame_2D.hpp>
|
||||
#include <render_2D/event/Event.hpp>
|
||||
#include <render_2D/event/Event_Routing_Rules.hpp>
|
||||
#include <render_2D/scene/Render_Scene_2D.hpp>
|
||||
@@ -12,6 +13,15 @@ std::unique_ptr<Object> build_axis() {
|
||||
if (!result) std::terminate();
|
||||
return std::move(result).value();
|
||||
}
|
||||
template <typename Scene>
|
||||
std::unique_ptr<Frame_2D> render_frame(Scene* scene) {
|
||||
static std::uint64_t sequence{1};
|
||||
auto frame = std::make_unique<Frame_2D>(Frame_Identity{sequence++, 0});
|
||||
EXPECT_EQ(scene->render(frame.get()), Render_Scene_2D::Render_Result::completed);
|
||||
return frame;
|
||||
}
|
||||
template <typename Scene>
|
||||
void render_once(Scene* scene) { static_cast<void>(render_frame(scene)); }
|
||||
}
|
||||
TEST(event_routing_rule, keeps_layered_targets_before_nearest_scored_targets) {
|
||||
std::vector<int> targets{1, 2, 3, 4};
|
||||
@@ -82,8 +92,8 @@ TEST(axis_render, scene_prepares_and_paints_uncached_axis_into_frame) {
|
||||
prepare.add(axis.get());
|
||||
paint.add(axis.get());
|
||||
}).has_value()));
|
||||
scene->render();
|
||||
const Image_View view = scene->frame_view();
|
||||
const auto frame = render_frame(scene.get());
|
||||
const Image_View view = frame->image();
|
||||
ASSERT_FALSE(view.empty());
|
||||
bool contains_color{};
|
||||
for (int y = 0; y < view.height && !contains_color; ++y) {
|
||||
@@ -106,12 +116,12 @@ TEST(axis_event, numeric_axis_wheel_zoom_and_drag_update_authoritative_range) {
|
||||
prepare.add(axis.get()); paint.add(axis.get());
|
||||
}).has_value()));
|
||||
scene->activate_view();
|
||||
scene->render();
|
||||
render_once(scene.get());
|
||||
auto wheel = std::make_unique<Wheel_Event>();
|
||||
wheel->position = {50.0, 0.0};
|
||||
wheel->angle_delta_y = 120.0;
|
||||
scene->dispatch_event(std::move(wheel));
|
||||
scene->render();
|
||||
render_once(scene.get());
|
||||
EXPECT_EQ(axis->coordinate_range(), (Axis_Range{0.5, 9.5}));
|
||||
auto press = std::make_unique<Pointer_Event>(Event_Type::pointer_press);
|
||||
press->position = {50.0, 0.0};
|
||||
@@ -120,7 +130,7 @@ TEST(axis_event, numeric_axis_wheel_zoom_and_drag_update_authoritative_range) {
|
||||
auto move = std::make_unique<Pointer_Event>(Event_Type::pointer_move);
|
||||
move->position = {60.0, 0.0};
|
||||
scene->dispatch_event(std::move(move));
|
||||
scene->render();
|
||||
render_once(scene.get());
|
||||
EXPECT_EQ(axis->coordinate_range(), (Axis_Range{-0.4, 8.6}));
|
||||
}
|
||||
TEST(axis_event, scene_routes_pointer_interaction_to_the_nearest_axis_segment) {
|
||||
@@ -143,13 +153,13 @@ TEST(axis_event, scene_routes_pointer_interaction_to_the_nearest_axis_segment) {
|
||||
paint.add(horizontal.get()); paint.add(vertical.get());
|
||||
}).has_value()));
|
||||
scene->activate_view();
|
||||
scene->render();
|
||||
render_once(scene.get());
|
||||
|
||||
auto near_vertical = std::make_unique<Wheel_Event>();
|
||||
near_vertical->position = {22.0, 80.0};
|
||||
near_vertical->angle_delta_y = 120.0;
|
||||
scene->dispatch_event(std::move(near_vertical));
|
||||
scene->render();
|
||||
render_once(scene.get());
|
||||
EXPECT_DOUBLE_EQ(horizontal->coordinate_range().size(), 10.0);
|
||||
EXPECT_DOUBLE_EQ(vertical->coordinate_range().size(), 9.0);
|
||||
|
||||
@@ -157,7 +167,7 @@ TEST(axis_event, scene_routes_pointer_interaction_to_the_nearest_axis_segment) {
|
||||
near_horizontal->position = {120.0, 178.0};
|
||||
near_horizontal->angle_delta_y = 120.0;
|
||||
scene->dispatch_event(std::move(near_horizontal));
|
||||
scene->render();
|
||||
render_once(scene.get());
|
||||
EXPECT_DOUBLE_EQ(horizontal->coordinate_range().size(), 9.0);
|
||||
EXPECT_DOUBLE_EQ(vertical->coordinate_range().size(), 9.0);
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
#include <render_2D/plottable/Plottables.hpp>
|
||||
#include <render_2D/base/Frame_2D.hpp>
|
||||
#include <render_2D/scene/Render_Scene_2D.hpp>
|
||||
#include <gtest/gtest.h>
|
||||
#include <array>
|
||||
@@ -30,6 +31,12 @@ void configure_axis(Axis* axis, Axis_Orientation orientation, Point_F position,
|
||||
axis->template set<&Abs_Axis::Prop::position>(position);
|
||||
axis->template set<&Abs_Axis::Prop::pixel_length>(length);
|
||||
}
|
||||
template <typename Scene>
|
||||
void render_once(Scene* scene) {
|
||||
static std::uint64_t sequence{1};
|
||||
Frame_2D frame{Frame_Identity{sequence++, 0}};
|
||||
EXPECT_EQ(scene->render(&frame), Render_Scene_2D::Render_Result::completed);
|
||||
}
|
||||
}
|
||||
|
||||
TEST(plottable_migration, curve_plots_share_partitioned_rendering) {
|
||||
@@ -60,18 +67,31 @@ TEST(plottable_migration, curve_plots_share_partitioned_rendering) {
|
||||
trace->append_sample(time->append_time({2'000}), 50.0);
|
||||
trace->append_sample(time->append_time({3'000}), 90.0);
|
||||
sweep->set<&Sweep_Spectrum::Prop::frequency_range>(Axis_Range{0.0, 100.0});
|
||||
sweep->set<&Sweep_Spectrum::Prop::block_count>(2u);
|
||||
sweep->set<&Sweep_Spectrum::Prop::partition_mode>(Plot_Partition_Mode::fixed);
|
||||
sweep->set<&Sweep_Spectrum::Prop::partition_count>(2u);
|
||||
const std::array<Plot_Value, 4> block{-90.0, -60.0, -30.0, -10.0};
|
||||
sweep->append_block(block);
|
||||
const std::array<Plot_Value, 4> first_block{-90.0, -60.0, -30.0, -10.0};
|
||||
const std::array<Plot_Value, 4> second_block{-80.0, -50.0, -20.0, -5.0};
|
||||
const std::array<Plot_Value, 4> replacement_block{-70.0, -40.0, -15.0, -2.0};
|
||||
sweep->append_block(first_block);
|
||||
sweep->append_block(second_block);
|
||||
auto scene = build_scene<Scene>(trace.get(), sweep.get());
|
||||
scene->set<&Render_Scene_2D::Prop::viewport>(canvas);
|
||||
scene->activate_view();
|
||||
scene->render();
|
||||
render_once(scene.get());
|
||||
EXPECT_EQ(trace->sample_count(), 3u);
|
||||
EXPECT_GT(trace->rendered_point_count(), 0u);
|
||||
EXPECT_EQ(sweep->stored_block_count(), 1u);
|
||||
EXPECT_EQ(sweep->stored_block_count(), 2u);
|
||||
EXPECT_GT(sweep->rendered_point_count(), 0u);
|
||||
const auto completed_sweep_points = sweep->rendered_point_count();
|
||||
sweep->append_block(replacement_block);
|
||||
render_once(scene.get());
|
||||
EXPECT_EQ(sweep->stored_block_count(), 2u);
|
||||
EXPECT_EQ(sweep->rendered_point_count(), completed_sweep_points);
|
||||
const auto& sweep_prop = sweep->read_prop<Sweep_Spectrum::Base_Tag>();
|
||||
EXPECT_EQ(sweep_prop.blocks[0], std::vector<Plot_Value>(replacement_block.begin(), replacement_block.end()));
|
||||
EXPECT_EQ(sweep_prop.blocks[1], std::vector<Plot_Value>(second_block.begin(), second_block.end()));
|
||||
EXPECT_EQ(sweep_prop.next_block_index, 1u);
|
||||
}
|
||||
|
||||
TEST(plottable_migration, raster_plots_share_partitioned_color_blocks) {
|
||||
@@ -105,7 +125,7 @@ TEST(plottable_migration, raster_plots_share_partitioned_color_blocks) {
|
||||
auto scene = build_scene<Scene>(glow.get(), waterfall.get());
|
||||
scene->set<&Render_Scene_2D::Prop::viewport>(canvas);
|
||||
scene->activate_view();
|
||||
scene->render();
|
||||
render_once(scene.get());
|
||||
EXPECT_EQ(glow->history_count(), 1u);
|
||||
EXPECT_GT(glow->rendered_cell_count(), 0u);
|
||||
EXPECT_EQ(waterfall->row_count(), 2u);
|
||||
@@ -133,7 +153,7 @@ TEST(plottable_migration, direct_overlay_and_constellation_build_and_render) {
|
||||
auto scene = build_scene<Scene>(diagram.get(), overlay.get());
|
||||
scene->set<&Render_Scene_2D::Prop::viewport>(canvas);
|
||||
scene->activate_view();
|
||||
scene->render();
|
||||
render_once(scene.get());
|
||||
EXPECT_EQ(diagram->point_count(), 1u);
|
||||
EXPECT_EQ(overlay->read_state<Renderable::Base_Tag>().prepare_task_count, 0u);
|
||||
EXPECT_EQ(overlay->read_state<Renderable::Base_Tag>().paint_task_count, 1u);
|
||||
@@ -155,7 +175,7 @@ TEST(selection_overlay, control_extends_selection_and_plain_click_clears_it) {
|
||||
auto scene = build_scene<Scene>(overlay.get());
|
||||
scene->set<&Render_Scene_2D::Prop::viewport>(canvas);
|
||||
scene->activate_view();
|
||||
scene->render();
|
||||
render_once(scene.get());
|
||||
|
||||
const auto drag = [&](Point_F first, Point_F second, Keyboard_Modifier modifiers) {
|
||||
auto press = std::make_unique<Pointer_Event>(Event_Type::pointer_press);
|
||||
@@ -172,7 +192,7 @@ TEST(selection_overlay, control_extends_selection_and_plain_click_clears_it) {
|
||||
release->button = Mouse_Button::left;
|
||||
release->modifiers = modifiers;
|
||||
scene->dispatch_event(std::move(release));
|
||||
scene->render();
|
||||
render_once(scene.get());
|
||||
};
|
||||
drag({30.0, 90.0}, {60.0, 60.0}, Keyboard_Modifier::none);
|
||||
EXPECT_EQ(overlay->selected_regions().size(), 1u);
|
||||
@@ -202,7 +222,7 @@ TEST(selection_overlay, time_axis_selection_keeps_axis_coordinates_while_window_
|
||||
auto scene = build_scene<Scene>(overlay.get());
|
||||
scene->set<&Render_Scene_2D::Prop::viewport>(canvas);
|
||||
scene->activate_view();
|
||||
scene->render();
|
||||
render_once(scene.get());
|
||||
auto press = std::make_unique<Pointer_Event>(Event_Type::pointer_press);
|
||||
press->position = {92.0, 88.0};
|
||||
press->button = Mouse_Button::left;
|
||||
@@ -214,12 +234,12 @@ TEST(selection_overlay, time_axis_selection_keeps_axis_coordinates_while_window_
|
||||
release->position = {116.0, 56.0};
|
||||
release->button = Mouse_Button::left;
|
||||
scene->dispatch_event(std::move(release));
|
||||
scene->render();
|
||||
render_once(scene.get());
|
||||
ASSERT_EQ(overlay->selected_regions().size(), 1u);
|
||||
const Axis_Rectangle selected = overlay->selected_regions().front();
|
||||
const auto pixel_before = time->coordinate_to_pixel(selected.horizontal.center());
|
||||
time->append_time({4'000});
|
||||
scene->render();
|
||||
render_once(scene.get());
|
||||
ASSERT_EQ(overlay->selected_regions().size(), 1u);
|
||||
EXPECT_EQ(overlay->selected_regions().front(), selected);
|
||||
EXPECT_NE(time->coordinate_to_pixel(selected.horizontal.center()), pixel_before);
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
#include <render_2D/plottable/Spectrum.hpp>
|
||||
#include <render_2D/base/Frame_2D.hpp>
|
||||
#include <render_2D/scene/Render_Scene_2D.hpp>
|
||||
#include <gtest/gtest.h>
|
||||
namespace {
|
||||
@@ -11,6 +12,14 @@ std::unique_ptr<Object> build_object(Args&&... args) {
|
||||
if (!result) std::terminate();
|
||||
return std::move(result).value();
|
||||
}
|
||||
template <typename Scene, typename... Renderables>
|
||||
std::unique_ptr<Scene> build_scene(Renderables*... renderables) {
|
||||
typename Scene::Builder builder;
|
||||
(builder.add_renderable(renderables), ...);
|
||||
auto result = builder.build();
|
||||
if (!result) std::terminate();
|
||||
return std::move(result).value();
|
||||
}
|
||||
bool contains_color(Image_View view) {
|
||||
for (int y = 0; y < view.height; ++y) {
|
||||
const auto* row = reinterpret_cast<const Pixel*>(view.data + static_cast<std::ptrdiff_t>(y) * view.stride);
|
||||
@@ -19,16 +28,22 @@ bool contains_color(Image_View view) {
|
||||
}
|
||||
return false;
|
||||
}
|
||||
template <typename Scene>
|
||||
std::unique_ptr<Frame_2D> render_frame(Scene* scene) {
|
||||
static std::uint64_t sequence{1};
|
||||
auto frame = std::make_unique<Frame_2D>(Frame_Identity{sequence++, 0});
|
||||
EXPECT_EQ(scene->render(frame.get()), Render_Scene_2D::Render_Result::completed);
|
||||
return frame;
|
||||
}
|
||||
}
|
||||
TEST(spectrum_data, publishes_samples_and_interpolates_power) {
|
||||
using Frequency = Impl<Frequency_Axis>;
|
||||
using Power = Impl<Numeric_Axis>;
|
||||
using Object = Impl<Spectrum>;
|
||||
using Scene_Object = Impl<Render_Scene_2D>;
|
||||
auto scene = build_object<Scene_Object>();
|
||||
auto frequency = build_object<Frequency>();
|
||||
auto power = build_object<Power>();
|
||||
auto spectrum = build_object<Object>(scene.get(), frequency.get(), power.get());
|
||||
auto spectrum = build_object<Object>(frequency.get(), power.get());
|
||||
const auto missing = spectrum->power_at(25.0);
|
||||
ASSERT_FALSE(missing.has_value());
|
||||
EXPECT_EQ(missing.error(), Spectrum::Power_At_Result::no_samples);
|
||||
@@ -49,10 +64,9 @@ TEST(spectrum_markers, supports_selection_frequency_changes_and_known_results) {
|
||||
using Power = Impl<Numeric_Axis>;
|
||||
using Object = Impl<Spectrum>;
|
||||
using Scene_Object = Impl<Render_Scene_2D>;
|
||||
auto scene = build_object<Scene_Object>();
|
||||
auto frequency = build_object<Frequency>();
|
||||
auto power = build_object<Power>();
|
||||
auto spectrum = build_object<Object>(scene.get(), frequency.get(), power.get());
|
||||
auto spectrum = build_object<Object>(frequency.get(), power.get());
|
||||
spectrum->add_custom_marker(10.0);
|
||||
spectrum->add_custom_line_marker(20.0);
|
||||
spectrum->advance();
|
||||
@@ -79,10 +93,10 @@ TEST(render_scene_2d, composites_axes_and_spectrum_into_final_frame) {
|
||||
using Spectrum_Object = Impl<Spectrum>;
|
||||
using Scene_Object = Impl<Render_Scene_2D>;
|
||||
initialize_runtime(2);
|
||||
auto scene = build_object<Scene_Object>();
|
||||
auto frequency = build_object<Frequency>();
|
||||
auto power = build_object<Power>();
|
||||
auto spectrum = build_object<Spectrum_Object>(scene.get(), frequency.get(), power.get());
|
||||
auto spectrum = build_object<Spectrum_Object>(frequency.get(), power.get());
|
||||
auto scene = build_scene<Scene_Object>(spectrum.get());
|
||||
const Size canvas{160, 120};
|
||||
frequency->set<&Abs_Axis::Prop::position>(Point_F{20.0, 100.0});
|
||||
frequency->set<&Abs_Axis::Prop::pixel_length>(120.0);
|
||||
@@ -112,48 +126,49 @@ TEST(render_scene_2d, composites_axes_and_spectrum_into_final_frame) {
|
||||
EXPECT_TRUE(cache_graph.depends_on(spectrum.get(), scene.get()));
|
||||
EXPECT_TRUE(cache_graph.depends_on(spectrum.get(), frequency.get()));
|
||||
EXPECT_TRUE(cache_graph.depends_on(spectrum.get(), power.get()));
|
||||
scene->render();
|
||||
auto output_frame = render_frame(scene.get());
|
||||
EXPECT_GT(spectrum->rendered_point_count(), 0u);
|
||||
const auto& render_state = spectrum->read_state<Renderable::Base_Tag>();
|
||||
EXPECT_EQ(render_state.prepare_task_count, 4u);
|
||||
EXPECT_EQ(render_state.paint_task_count, 1u);
|
||||
const Image_View frame = scene->frame_view();
|
||||
const Image_View frame = output_frame->image();
|
||||
ASSERT_FALSE(frame.empty());
|
||||
EXPECT_TRUE(contains_color(frame));
|
||||
EXPECT_FALSE(spectrum->dirty<Paint_Cache_Tag>());
|
||||
EXPECT_FALSE(spectrum->dirty<Paint_Tag>());
|
||||
EXPECT_FALSE(frequency->dirty<Paint_Tag>());
|
||||
EXPECT_FALSE(power->dirty<Paint_Tag>());
|
||||
scene->render();
|
||||
EXPECT_TRUE(contains_color(scene->frame_view()));
|
||||
output_frame = render_frame(scene.get());
|
||||
EXPECT_TRUE(contains_color(output_frame->image()));
|
||||
frequency->set<&Numeric_Axis::Prop::precision>(3);
|
||||
EXPECT_FALSE(spectrum->dirty<Paint_Cache_Tag>());
|
||||
scene->render();
|
||||
output_frame = render_frame(scene.get());
|
||||
EXPECT_TRUE(spectrum->read_state<Renderable::Base_Tag>().paint_executed);
|
||||
EXPECT_TRUE(frequency->read_state<Renderable::Base_Tag>().paint_executed);
|
||||
EXPECT_TRUE(power->read_state<Renderable::Base_Tag>().paint_executed);
|
||||
EXPECT_TRUE(contains_color(scene->frame_view()));
|
||||
EXPECT_TRUE(contains_color(output_frame->image()));
|
||||
frequency->set<&Numeric_Axis::Prop::coordinate_range>(Axis_Range{0.0, 120.0});
|
||||
EXPECT_TRUE(spectrum->dirty<Paint_Cache_Tag>());
|
||||
scene->render();
|
||||
output_frame = render_frame(scene.get());
|
||||
EXPECT_FALSE(spectrum->dirty<Paint_Cache_Tag>());
|
||||
EXPECT_TRUE(spectrum->read_state<Renderable::Base_Tag>().prepare_executed);
|
||||
spectrum->set<&Spectrum::Prop::partition_count>(2u);
|
||||
spectrum->update_samples(samples);
|
||||
scene->render();
|
||||
output_frame = render_frame(scene.get());
|
||||
const auto& rebuilt_state = spectrum->read_state<Renderable::Base_Tag>();
|
||||
EXPECT_TRUE(rebuilt_state.prepare_graph_rebuilt);
|
||||
EXPECT_EQ(rebuilt_state.prepare_task_count, 3u);
|
||||
EXPECT_TRUE(contains_color(scene->frame_view()));
|
||||
EXPECT_TRUE(contains_color(output_frame->image()));
|
||||
const Size resized_canvas{200, 140};
|
||||
scene->set<&Render_Scene_2D::Prop::viewport>(resized_canvas);
|
||||
EXPECT_TRUE(frequency->dirty<Prepare_Data_Tag>());
|
||||
EXPECT_TRUE(power->dirty<Prepare_Data_Tag>());
|
||||
scene->render();
|
||||
EXPECT_TRUE(spectrum->dirty<Paint_Cache_Tag>());
|
||||
EXPECT_FALSE(frequency->dirty<Prepare_Data_Tag>());
|
||||
EXPECT_FALSE(power->dirty<Prepare_Data_Tag>());
|
||||
output_frame = render_frame(scene.get());
|
||||
EXPECT_EQ(scene->read_prop<Render_Scene_2D::Base_Tag>().viewport, resized_canvas);
|
||||
EXPECT_TRUE(frequency->read_state<Renderable::Base_Tag>().prepare_executed);
|
||||
EXPECT_FALSE(frequency->read_state<Renderable::Base_Tag>().prepare_executed);
|
||||
EXPECT_TRUE(frequency->read_state<Renderable::Base_Tag>().paint_executed);
|
||||
const Image_View resized_frame = scene->frame_view();
|
||||
const Image_View resized_frame = output_frame->image();
|
||||
EXPECT_EQ(resized_frame.width, resized_canvas.width);
|
||||
EXPECT_EQ(resized_frame.height, resized_canvas.height);
|
||||
EXPECT_TRUE(contains_color(resized_frame));
|
||||
|
||||
Reference in New Issue
Block a user