优化
This commit is contained in:
@@ -19,7 +19,7 @@ concept Axis_Object = Renderable_Object<T> && std::derived_from<T, Abs_Axis> &&
|
||||
{ private_data.sub_tick_count(object, tick) } -> std::same_as<Axis_Tick_Count>;
|
||||
};
|
||||
/* 所有二维坐标轴共享的定义层;最终通过 Impl<Derived_Axis> 创建运行时对象。 */
|
||||
struct Abs_Axis : Def<Abs_Axis, Renderable_2D<>> {
|
||||
struct Abs_Axis : Def<Abs_Axis, Renderable_2D> {
|
||||
struct Prop : Prev_Prop {
|
||||
Point_F position{}; /* 坐标轴起点在画布中的二维像素位置。 */
|
||||
Axis_Pixel_Length pixel_length{}; /* 从坐标起点到终点的轴向像素跨度;为 0 时反算返回坐标起点。 */
|
||||
|
||||
@@ -4,36 +4,21 @@
|
||||
#include <expected>
|
||||
#include <functional>
|
||||
namespace aethera::render_2d {
|
||||
/* 二维 Renderable 是否拥有可跨帧复用的完整颜色缓存。 */
|
||||
enum class Renderable_2D_Cache {
|
||||
disabled,
|
||||
enabled
|
||||
};
|
||||
/*
|
||||
* 二维绘制能力层。业务类型在该模板位置选择是否拥有颜色缓存;默认不分配完整缓存。
|
||||
* 二维绘制能力层。每个对象都具备惰性颜色缓存,是否使用由当前 Prop 决定。
|
||||
* Paint 实现只向 Scene 本轮指定的绘制目标输出,不直接选择或清理双缓冲角色。
|
||||
*/
|
||||
template <Renderable_2D_Cache Cache = Renderable_2D_Cache::disabled>
|
||||
struct Renderable_2D;
|
||||
template <>
|
||||
struct Renderable_2D<Renderable_2D_Cache::disabled> : Def<Renderable_2D<Renderable_2D_Cache::disabled>, Renderable> {
|
||||
struct Prop : Prev_Prop {};
|
||||
struct Renderable_2D
|
||||
: Def<Renderable_2D, Renderable, Tagged_Buffer<Color_Cache, Blend2D_Cache>> {
|
||||
struct Prop : Prev_Prop {
|
||||
bool cache_enabled{}; /* 是否跨帧复用本对象及其缓存组的完整颜色结果。 */
|
||||
bool operator==(const Prop&) const;
|
||||
};
|
||||
struct State : Prev_State {
|
||||
bool operator==(const State&) const;
|
||||
};
|
||||
struct Private;
|
||||
};
|
||||
template <>
|
||||
struct Renderable_2D<Renderable_2D_Cache::enabled>
|
||||
: Def<Renderable_2D<Renderable_2D_Cache::enabled>,
|
||||
Renderable_2D<Renderable_2D_Cache::disabled>,
|
||||
Tagged_Buffer<Color_Cache, Blend2D_Cache>> {
|
||||
struct Prop : Prev_Prop {};
|
||||
struct State : Prev_State {
|
||||
bool operator==(const State&) const;
|
||||
};
|
||||
struct Private;
|
||||
};
|
||||
using Renderable_2D_Base = Renderable_2D<Renderable_2D_Cache::disabled>;
|
||||
using Renderable_2D_Base = Renderable_2D;
|
||||
}
|
||||
#include "Renderable_2D.ipp"
|
||||
|
||||
@@ -1,11 +1,13 @@
|
||||
#pragma once
|
||||
#include <stdexcept>
|
||||
namespace aethera::render_2d {
|
||||
struct Renderable_2D<Renderable_2D_Cache::disabled>::Private : Prev_Private {
|
||||
struct Renderable_2D::Private : Prev_Private {
|
||||
using Scene_Attach = std::function<std::expected<void, Dependency_Graph_Error>(Root*)>;
|
||||
using Cache_Access = Blend2D_Cache* (*)(Root*);
|
||||
using Cache_Enabled = bool (*)(const Root*);
|
||||
using Event_Region_Run = Rect_F (*)(const Root*, Size);
|
||||
Cache_Access pending_cache{}; /* 非空时返回最终对象本轮可写的缓存物理对象。 */
|
||||
Cache_Enabled cache_enabled{}; /* 读取最终对象当前发布的运行时缓存策略。 */
|
||||
Event_Region_Run event_region_run{}; /* 计算最终对象当前事件区域的无虚函数入口。 */
|
||||
Blend2D_Cache* paint_target{}; /* 仅在 Scene Paint 阶段有效的非拥有绘制目标。 */
|
||||
Blend2D_Cache* valid_cache{}; /* 缓存根最近一次完整重绘产生的权威物理缓存。 */
|
||||
@@ -14,24 +16,27 @@ struct Renderable_2D<Renderable_2D_Cache::disabled>::Private : Prev_Private {
|
||||
[[nodiscard]] Blend2D_Cache& paint_surface();
|
||||
/* CRTP 可覆盖:返回最终对象可接收指针与滚轮事件的画布区域;默认覆盖整个 viewport。 */
|
||||
[[nodiscard]] Rect_F event_region(const Attached auto* object, Size viewport) const;
|
||||
/* CRTP 覆盖:识别最终类型是否选择了二维颜色缓存。 */
|
||||
template <typename Object, typename Owner, typename Member, typename Prop_Type>
|
||||
void after_prop_set(Object* object, Member Owner::* member,
|
||||
Prop_Access<Prop_Type> pending_props);
|
||||
template <Attached Object> void bind_private_crtp(Object* object);
|
||||
};
|
||||
struct Renderable_2D<Renderable_2D_Cache::enabled>::Private : Prev_Private {
|
||||
/* CRTP 覆盖:继续绑定二维绘制能力;缓存机制由本定义层静态加入。 */
|
||||
template <Attached Object> void bind_private_crtp(Object* object);
|
||||
};
|
||||
inline bool Renderable_2D<Renderable_2D_Cache::disabled>::State::operator==(const State&) const = default;
|
||||
inline bool Renderable_2D<Renderable_2D_Cache::enabled>::State::operator==(const State&) const = default;
|
||||
inline Blend2D_Cache& Renderable_2D<Renderable_2D_Cache::disabled>::Private::paint_surface() {
|
||||
inline bool Renderable_2D::Prop::operator==(const Prop&) const = default;
|
||||
inline bool Renderable_2D::State::operator==(const State&) const = default;
|
||||
inline Blend2D_Cache& Renderable_2D::Private::paint_surface() {
|
||||
if (!paint_target) throw std::logic_error("2D renderable painted without a scene paint target");
|
||||
return *paint_target;
|
||||
}
|
||||
inline Rect_F Renderable_2D<Renderable_2D_Cache::disabled>::Private::event_region(const Attached auto*, Size viewport) const {
|
||||
inline Rect_F Renderable_2D::Private::event_region(const Attached auto*, Size viewport) const {
|
||||
return {0.0, 0.0, static_cast<double>(viewport.width), static_cast<double>(viewport.height)};
|
||||
}
|
||||
template <typename Object, typename Owner, typename Member, typename Prop_Type>
|
||||
void Renderable_2D::Private::after_prop_set(Object* object, Member Owner::*,
|
||||
Prop_Access<Prop_Type>) {
|
||||
if constexpr (std::same_as<Owner, Prop>) object->template mark_dirty<Paint_Tag>();
|
||||
}
|
||||
template <Attached Object>
|
||||
void Renderable_2D<Renderable_2D_Cache::disabled>::Private::bind_private_crtp(Object* object) {
|
||||
void Renderable_2D::Private::bind_private_crtp(Object* object) {
|
||||
Prev_Private::bind_private_crtp(object);
|
||||
object->template mark_dirty<Paint_Tag>();
|
||||
this->event_region_run = [](const Root* root, Size viewport) {
|
||||
@@ -39,12 +44,12 @@ void Renderable_2D<Renderable_2D_Cache::disabled>::Private::bind_private_crtp(Ob
|
||||
const auto& private_data = static_cast<const typename Object::Private&>(*value->d);
|
||||
return private_data.event_region(value, viewport);
|
||||
};
|
||||
}
|
||||
template <Attached Object>
|
||||
void Renderable_2D<Renderable_2D_Cache::enabled>::Private::bind_private_crtp(Object* object) {
|
||||
Prev_Private::bind_private_crtp(object);
|
||||
this->pending_cache = [](Root* root) {
|
||||
return &static_cast<Object*>(root)->template pending_buffer<Color_Cache>();
|
||||
};
|
||||
this->cache_enabled = [](const Root* root) {
|
||||
return static_cast<const Object*>(root)
|
||||
->template read_prop<Renderable_2D::Base_Tag>().cache_enabled;
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,11 +10,12 @@
|
||||
#include <vector>
|
||||
namespace aethera::render_2d {
|
||||
struct Afterglow_Stream_Tag {};
|
||||
struct Afterglow : Def<Afterglow, Renderable_2D<Renderable_2D_Cache::enabled>,
|
||||
struct Afterglow : Def<Afterglow, Renderable_2D,
|
||||
Mpmc_Triple_Buffer<Afterglow_Stream_Tag,
|
||||
std::shared_ptr<const std::vector<Plot_Value>>>> {
|
||||
using Scene_Object = Impl<Render_Scene_2D>; using Frequency_Object = Impl<Frequency_Axis>; using Power_Object = Impl<Numeric_Axis>;
|
||||
struct Prop : Prev_Prop {
|
||||
Prop() { cache_enabled = true; }
|
||||
std::size_t frequency_point_size{}; /* 栅格频率列数;零值使用最新频谱尺寸。 */
|
||||
std::size_t power_point_size{}; /* 栅格功率行数;零值使用默认尺寸。 */
|
||||
Plot_Partition_Count partition_count{1}; /* fixed 模式使用的 Prepare 子图分块数。 */
|
||||
|
||||
@@ -15,11 +15,12 @@ struct Constellation_Point {
|
||||
};
|
||||
struct Constellation_Stream_Tag {};
|
||||
struct Constellation_Diagram : Def<Constellation_Diagram,
|
||||
Renderable_2D<Renderable_2D_Cache::enabled>,
|
||||
Renderable_2D,
|
||||
Mpmc_Triple_Buffer<Constellation_Stream_Tag, Constellation_Point>> {
|
||||
using Scene_Object = Impl<Render_Scene_2D>;
|
||||
using Axis_Object = Impl<Numeric_Axis>;
|
||||
struct Prop : Prev_Prop {
|
||||
Prop() { cache_enabled = true; }
|
||||
Plot_Duration_Milliseconds point_lifetime_ms{1000}; /* 接收点保留时间,单位为毫秒。 */
|
||||
Constellation_Diagram_Type type{Constellation_Diagram_Type::psk8}; /* 理想 PSK 锚点数量。 */
|
||||
Plot_Ratio phase_offset_radians{}; /* 理想锚点相位偏移,单位为弧度。 */
|
||||
|
||||
@@ -13,12 +13,13 @@ struct Frequency_Trace_Sample {
|
||||
bool operator==(const Frequency_Trace_Sample&) const;
|
||||
};
|
||||
struct Frequency_Trace_Stream_Tag {};
|
||||
struct Frequency_Trace : Def<Frequency_Trace, Renderable_2D<Renderable_2D_Cache::enabled>,
|
||||
struct Frequency_Trace : Def<Frequency_Trace, Renderable_2D,
|
||||
Mpmc_Triple_Buffer<Frequency_Trace_Stream_Tag, Frequency_Trace_Sample>> {
|
||||
using Scene_Object = Impl<Render_Scene_2D>;
|
||||
using Time_Object = Impl<Time_Axis>;
|
||||
using Value_Object = Impl<Numeric_Axis>;
|
||||
struct Prop : Prev_Prop {
|
||||
Prop() { cache_enabled = true; }
|
||||
Plot_Partition_Count partition_count{1}; /* fixed 模式使用的 Prepare 子图分块数。 */
|
||||
Pen pen{Color::yellow()}; /* 频率轨迹折线样式。 */
|
||||
Plot_Partition_Mode partition_mode{Plot_Partition_Mode::automatic}; /* Prepare 子图分块策略。 */
|
||||
|
||||
@@ -20,6 +20,4 @@ std::string Selection_Rectangle_Overlay::Private::range_label(const Abs_Axis* ax
|
||||
};
|
||||
return coordinate_label(range.origin) + " .. " + coordinate_label(range.target);
|
||||
}
|
||||
std::vector<Axis_Rectangle> Selection_Rectangle_Overlay::selected_regions() const { return static_cast<const Private&>(*d).dispatch->selected_regions(this); }
|
||||
void Selection_Rectangle_Overlay::clear_selected_regions() { static_cast<Private&>(*d).dispatch->clear_selected_regions(this); }
|
||||
}
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
namespace aethera::render_2d {
|
||||
struct Selection_Rectangle_Overlay : Def<Selection_Rectangle_Overlay, Renderable_2D<>> {
|
||||
struct Selection_Rectangle_Overlay : Def<Selection_Rectangle_Overlay, Renderable_2D> {
|
||||
using Scene_Object = Impl<Render_Scene_2D>;
|
||||
struct Prop : Prev_Prop {
|
||||
Font label_font{}; /* 选择范围标签使用的字体。 */
|
||||
@@ -32,8 +32,6 @@ struct Selection_Rectangle_Overlay : Def<Selection_Rectangle_Overlay, Renderable
|
||||
private:
|
||||
std::function<void(Object*)> initialize{}; /* build 成功后绑定两根最终轴及其 Scene 拓扑。 */
|
||||
};
|
||||
[[nodiscard]] std::vector<Axis_Rectangle> selected_regions() const;
|
||||
void clear_selected_regions();
|
||||
};
|
||||
}
|
||||
#include "Selection_Rectangle_Overlay.ipp"
|
||||
|
||||
@@ -6,12 +6,6 @@
|
||||
namespace aethera::render_2d {
|
||||
struct Selection_Rectangle_Overlay::Private : Prev_Private {
|
||||
using No_Prepare = void;
|
||||
using Regions_Get = std::vector<Axis_Rectangle> (*)(const Root*);
|
||||
using Clear_Run = void (*)(Root*);
|
||||
struct Dispatch {
|
||||
Regions_Get selected_regions; /* 查询最终对象已发布选择区域。 */
|
||||
Clear_Run clear_selected_regions; /* 清空最终对象选择区域。 */
|
||||
};
|
||||
Scene_Object* scene{}; /* 不拥有的所属 Scene。 */
|
||||
Abs_Axis* horizontal_axis{}; /* 不拥有的水平坐标轴。 */
|
||||
Abs_Axis* vertical_axis{}; /* 不拥有的垂直坐标轴。 */
|
||||
@@ -19,12 +13,8 @@ struct Selection_Rectangle_Overlay::Private : Prev_Private {
|
||||
Axis_Point drag_current{}; /* 当前拖动终点的两轴数据坐标。 */
|
||||
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;
|
||||
@@ -119,15 +109,5 @@ template <typename Object, typename Owner, typename Member, typename Prop_Type>
|
||||
void Selection_Rectangle_Overlay::Private::after_prop_set(Object* object, Member Owner::*, Prop_Access<Prop_Type>) { if constexpr (std::same_as<Owner, Prop>) object->template mark_dirty<Paint_Tag>(); }
|
||||
template <typename Object, typename Prop_Type, typename State_Type>
|
||||
void Selection_Rectangle_Overlay::Private::before_advance(Object*, Prop_Type*, State_Access<State_Type> pending_states, const Prop_Type* current_prop, State_Access<const State_Type>) { pending_states.template get<Selection_Rectangle_Overlay::Base_Tag>().selected_region_count = static_cast<const Prop&>(*current_prop).selected_regions.size(); }
|
||||
template <Attached Object>
|
||||
const Selection_Rectangle_Overlay::Private::Dispatch& Selection_Rectangle_Overlay::Private::dispatch_for() {
|
||||
static const Dispatch value{
|
||||
[](const Root* root) { return static_cast<const Object*>(root)->template read_prop<Selection_Rectangle_Overlay::Base_Tag>().selected_regions; },
|
||||
[](Root* root) { static_cast<Object*>(root)->template set<&Prop::selected_regions>(std::vector<Axis_Rectangle>{}); }
|
||||
};
|
||||
return value;
|
||||
}
|
||||
template <Attached Object>
|
||||
void Selection_Rectangle_Overlay::Private::bind_private_crtp(Object* object) { Prev_Private::bind_private_crtp(object); dispatch = &dispatch_for<Object>(); }
|
||||
inline void Selection_Rectangle_Overlay::Private::bind_sources(Abs_Axis* horizontal_axis_value, Abs_Axis* vertical_axis_value) { horizontal_axis = horizontal_axis_value; vertical_axis = vertical_axis_value; }
|
||||
}
|
||||
|
||||
@@ -3,18 +3,6 @@ namespace aethera::render_2d {
|
||||
bool Spectrum_Frame::operator==(const Spectrum_Frame&) const = default;
|
||||
bool Spectrum::Prop::operator==(const Prop&) const = default;
|
||||
bool Spectrum::State::operator==(const State&) const = default;
|
||||
void Spectrum::update_samples(std::span<const Spectrum_Power> values) {
|
||||
static_cast<Private&>(*d).dispatch->update_samples(this, values);
|
||||
}
|
||||
void Spectrum::update_samples(std::pmr::vector<Spectrum_Power>&& values) {
|
||||
update_samples(std::span<const Spectrum_Power>(values.data(), values.size()));
|
||||
}
|
||||
std::size_t Spectrum::sample_count() const {
|
||||
return static_cast<const Private&>(*d).dispatch->sample_count(this);
|
||||
}
|
||||
std::size_t Spectrum::rendered_point_count() const {
|
||||
return static_cast<const Private&>(*d).dispatch->rendered_point_count(this);
|
||||
}
|
||||
std::expected<Spectrum_Power, Spectrum::Power_At_Result> Spectrum::power_at(Spectrum_Frequency frequency) const {
|
||||
return static_cast<const Private&>(*d).dispatch->power_at(this, frequency);
|
||||
}
|
||||
|
||||
@@ -5,8 +5,6 @@
|
||||
#include "Plot_Types.hpp"
|
||||
#include <expected>
|
||||
#include <memory>
|
||||
#include <memory_resource>
|
||||
#include <span>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
namespace aethera::render_2d {
|
||||
@@ -17,47 +15,47 @@ using Spectrum_Marker_Index = Plot_Index;
|
||||
using Spectrum_Partition_Mode = Plot_Partition_Mode;
|
||||
struct Spectrum_Frame_Tag {};
|
||||
struct Spectrum_Frame {
|
||||
std::vector<Spectrum_Power> samples{}; /* 最近提交的当前频谱功率样本。 */
|
||||
std::vector<Spectrum_Power> maxima{}; /* 与 samples 同尺寸的逐点历史最大值。 */
|
||||
std::vector<Spectrum_Power> minima{}; /* 与 samples 同尺寸的逐点历史最小值。 */
|
||||
std::vector<Spectrum_Power> samples{}; /* 最近提交的当前频谱功率样本。 */
|
||||
bool operator==(const Spectrum_Frame&) const;
|
||||
};
|
||||
/* 使用频率轴和功率轴分块准备、绘制当前值、保持曲线及频率标记。 */
|
||||
struct Spectrum : Def<Spectrum, Renderable_2D<Renderable_2D_Cache::enabled>, Tagged_Buffer<Spectrum_Frame_Tag, Spectrum_Frame>> {
|
||||
struct Spectrum : Def<Spectrum, Renderable_2D,
|
||||
Tagged_Buffer<Spectrum_Frame_Tag, Spectrum_Frame>> {
|
||||
using Scene_Object = Impl<Render_Scene_2D>;
|
||||
using Frequency_Object = Impl<Frequency_Axis>;
|
||||
using Power_Object = Impl<Numeric_Axis>;
|
||||
struct Prop : Prev_Prop {
|
||||
Spectrum_Frequency center_frequency{50.0}; /* 中心频率标记位置,单位为 Hz。 */
|
||||
std::size_t partition_count{1}; /* fixed 模式使用的 Prepare 子图分块数。 */
|
||||
bool max_hold_visible{}; /* 是否绘制逐点历史最大值曲线。 */
|
||||
bool min_hold_visible{}; /* 是否绘制逐点历史最小值曲线。 */
|
||||
bool max_marker_visible{}; /* 是否标记当前样本的最大值位置。 */
|
||||
bool min_marker_visible{}; /* 是否标记当前样本的最小值位置。 */
|
||||
bool sweep_region_visible{}; /* 是否绘制扫频范围背景。 */
|
||||
bool visible_range_only{true}; /* 是否裁掉频率轴当前范围外的线段。 */
|
||||
Axis_Range frequency_range{}; /* 输入样本首尾对应的有向频率范围,单位为 Hz。 */
|
||||
Axis_Range sweep_frequency_range{40.0, 60.0}; /* 扫频背景覆盖的频率范围,单位为 Hz。 */
|
||||
Prop() { cache_enabled = true; }
|
||||
Spectrum_Frequency center_frequency{50.0}; /* 中心频率标记位置,单位为 Hz。 */
|
||||
std::size_t partition_count{1}; /* fixed 模式使用的 Prepare 子图分块数。 */
|
||||
bool max_hold_visible{}; /* 是否绘制逐点历史最大值曲线。 */
|
||||
bool min_hold_visible{}; /* 是否绘制逐点历史最小值曲线。 */
|
||||
bool max_marker_visible{}; /* 是否标记当前样本的最大值位置。 */
|
||||
bool min_marker_visible{}; /* 是否标记当前样本的最小值位置。 */
|
||||
bool sweep_region_visible{}; /* 是否绘制扫频范围背景。 */
|
||||
bool visible_range_only{true}; /* 是否裁掉频率轴当前范围外的线段。 */
|
||||
Axis_Range frequency_range{}; /* 输入样本首尾对应的有向频率范围,单位为 Hz。 */
|
||||
Axis_Range sweep_frequency_range{40.0, 60.0}; /* 扫频背景覆盖的频率范围,单位为 Hz。 */
|
||||
Spectrum_Partition_Mode partition_mode{Spectrum_Partition_Mode::automatic}; /* Prepare 子图的分块策略。 */
|
||||
Line_Interpolation_Mode interpolation_mode{Line_Interpolation_Mode::linear_value}; /* 相邻样本间的插值规则。 */
|
||||
Brush max_brush{}; /* 最大保持曲线下方的填充样式。 */
|
||||
Brush current_brush{}; /* 当前频谱曲线下方的填充样式。 */
|
||||
Brush min_brush{}; /* 最小保持曲线下方的填充样式。 */
|
||||
Pen max_pen{Color::red_color()}; /* 最大保持曲线及最大值标记样式。 */
|
||||
Pen current_pen{Color::green_color()}; /* 当前频谱曲线样式。 */
|
||||
Pen min_pen{Color::white()}; /* 最小保持曲线及最小值标记样式。 */
|
||||
Pen selected_marker_pen{Color{0, 0, 139, 255}, 2.0}; /* 当前选中自定义标记的线条样式。 */
|
||||
Pen marker_pen{Color::red_color()}; /* 未选中自定义标记的线条样式。 */
|
||||
Pen middle_frequency_pen{Color::red_color()}; /* 中心频率垂线样式。 */
|
||||
Brush max_brush{}; /* 最大保持曲线下方的填充样式。 */
|
||||
Brush current_brush{}; /* 当前频谱曲线下方的填充样式。 */
|
||||
Brush min_brush{}; /* 最小保持曲线下方的填充样式。 */
|
||||
Pen max_pen{Color::red_color()}; /* 最大保持曲线及最大值标记样式。 */
|
||||
Pen current_pen{Color::green_color()}; /* 当前频谱曲线样式。 */
|
||||
Pen min_pen{Color::white()}; /* 最小保持曲线及最小值标记样式。 */
|
||||
Pen selected_marker_pen{Color{0, 0, 139, 255}, 2.0}; /* 当前选中自定义标记的线条样式。 */
|
||||
Pen marker_pen{Color::red_color()}; /* 未选中自定义标记的线条样式。 */
|
||||
Pen middle_frequency_pen{Color::red_color()}; /* 中心频率垂线样式。 */
|
||||
Brush sweep_region_brush{Color{255, 255, 0, 100}, Brush_Style::solid}; /* 扫频区域背景样式。 */
|
||||
std::vector<Spectrum_Frequency> custom_markers{}; /* 自定义频率标记的唯一权威集合,单位为 Hz。 */
|
||||
Spectrum_Marker_Index selected_marker{-1}; /* 当前选中标记下标;-1 表示未选择。 */
|
||||
std::vector<Spectrum_Frequency> custom_markers{}; /* 自定义频率标记的唯一权威集合,单位为 Hz。 */
|
||||
Spectrum_Marker_Index selected_marker{-1}; /* 当前选中标记下标;-1 表示未选择。 */
|
||||
bool operator==(const Prop&) const;
|
||||
};
|
||||
struct State : Prev_State {
|
||||
std::size_t sample_count{}; /* 最近一次发布帧的样本数。 */
|
||||
std::size_t rendered_point_count{}; /* 最近一次 Prepare 生成的曲线点数。 */
|
||||
std::size_t selectable_marker_count{}; /* 当前可选择的自定义标记数。 */
|
||||
std::size_t sample_count{}; /* 最近一次发布帧的样本数。 */
|
||||
std::size_t rendered_point_count{}; /* 最近一次 Prepare 生成的曲线点数。 */
|
||||
std::size_t selectable_marker_count{}; /* 当前可选择的自定义标记数。 */
|
||||
bool operator==(const State&) const;
|
||||
};
|
||||
/* 完整声明、分块子图、内部状态操作和 CRTP 分派见 Spectrum.ipp。 */
|
||||
@@ -68,20 +66,9 @@ struct Spectrum : Def<Spectrum, Renderable_2D<Renderable_2D_Cache::enabled>, Tag
|
||||
Builder(Frequency_Object* frequency_axis, Power_Object* power_axis);
|
||||
[[nodiscard]] std::expected<std::unique_ptr<Object>, Dependency_Graph_Error> build();
|
||||
private:
|
||||
Frequency_Object* frequency_axis{}; /* 不拥有的频率轴;生命周期必须覆盖 Spectrum。 */
|
||||
Power_Object* power_axis{}; /* 不拥有的功率轴;生命周期必须覆盖 Spectrum。 */
|
||||
Frequency_Object* frequency_axis{}; /* 不拥有的频率轴;生命周期必须覆盖 Spectrum。 */
|
||||
Power_Object* power_axis{}; /* 不拥有的功率轴;生命周期必须覆盖 Spectrum。 */
|
||||
};
|
||||
/* 提交新一帧样本并更新逐点最大、最小保持。 */
|
||||
void update_samples(std::span<const Spectrum_Power> values);
|
||||
/* 接收 PMR 连续样本容器;提交完成后调用方仍拥有容器。 */
|
||||
void update_samples(std::pmr::vector<Spectrum_Power>&& values);
|
||||
/* 接收具有 data() 和 size() 的连续样本容器。 */
|
||||
template <typename Values>
|
||||
void update_samples(const Values& values);
|
||||
/* 返回最近一次双缓冲交换后发布的样本数。 */
|
||||
[[nodiscard]] std::size_t sample_count() const;
|
||||
/* 返回最近一次 Prepare 子图得到的当前曲线点数。 */
|
||||
[[nodiscard]] std::size_t rendered_point_count() const;
|
||||
enum class Power_At_Result {
|
||||
no_samples,
|
||||
invalid_frequency_range,
|
||||
|
||||
@@ -30,9 +30,8 @@ struct Spectrum::Private : Prev_Private {
|
||||
Size canvas_size{}; /* 所属 Scene viewport 决定的颜色层尺寸。 */
|
||||
bool valid{}; /* 两根轴与画布是否足以生成绘制数据。 */
|
||||
};
|
||||
using Update_Run = void (*)(Root*, std::span<const Spectrum_Power>);
|
||||
using Count_Run = std::size_t (*)(const Root*);
|
||||
using Power_Run = std::expected<Spectrum_Power, Power_At_Result> (*)(const Root*, Spectrum_Frequency);
|
||||
using Count_Run = std::size_t (*)(const Root*);
|
||||
using Frequency_Run = void (*)(Root*, Spectrum_Frequency);
|
||||
using Void_Run = void (*)(Root*);
|
||||
using Index_Get_Run = Spectrum_Marker_Index (*)(const Root*);
|
||||
@@ -41,9 +40,6 @@ struct Spectrum::Private : Prev_Private {
|
||||
using Set_Marker_Frequency_Run = Set_Marker_Frequency_Result (*)(Root*, Spectrum_Marker_Index, Spectrum_Frequency);
|
||||
using Set_Current_Marker_Frequency_Run = Set_Current_Marker_Frequency_Result (*)(Root*, Spectrum_Frequency);
|
||||
struct Dispatch {
|
||||
Update_Run update_samples; /* 向最终对象写入 Spectrum_Frame_Tag。 */
|
||||
Count_Run sample_count; /* 查询最终对象已发布样本数量。 */
|
||||
Count_Run rendered_point_count; /* 查询 Prepare 子图生成的曲线点数。 */
|
||||
Power_Run power_at; /* 查询最终对象已发布帧的插值功率。 */
|
||||
Frequency_Run add_marker; /* 添加自定义标记。 */
|
||||
Frequency_Run remove_marker; /* 删除最接近指定频率的标记。 */
|
||||
@@ -62,6 +58,8 @@ struct Spectrum::Private : Prev_Private {
|
||||
Frequency_Object* frequency_axis{}; /* 不拥有的频率轴;Prepare 直接读取其当前状态。 */
|
||||
Power_Object* power_axis{}; /* 不拥有的功率轴;Prepare 直接读取其当前状态。 */
|
||||
Prepared prepared{}; /* 当前权威 State、Frame 和轴状态推导出的 Paint 输入。 */
|
||||
std::vector<Spectrum_Power> maxima{}; /* 当前样本历史逐点最大值;只由 Prepare 更新。 */
|
||||
std::vector<Spectrum_Power> minima{}; /* 当前样本历史逐点最小值;只由 Prepare 更新。 */
|
||||
std::size_t prepare_graph_partition_count{}; /* 当前 Prepare 子图实际固化的任务分块数。 */
|
||||
const Dispatch* dispatch{}; /* Builder 绑定最终 Spectrum 类型后的静态分派表。 */
|
||||
/* CRTP 覆盖:绑定 Renderable 机制和 Spectrum 公开薄壳分派;派生 Private 必须先调用此实现。 */
|
||||
@@ -69,7 +67,6 @@ struct Spectrum::Private : Prev_Private {
|
||||
/* Builder 内部绑定 Scene 与两根轴;三个来源都必须比 Spectrum 生命周期更长。 */
|
||||
void bind_render_sources(Frequency_Object* frequency_axis_value, Power_Object* power_axis_value);
|
||||
template <Attached Object> [[nodiscard]] static const Dispatch& dispatch_for();
|
||||
template <Attached Object> void update_samples(Object* object, std::span<const Spectrum_Power> values);
|
||||
template <Attached Object> void add_marker(Object* object, Spectrum_Frequency frequency);
|
||||
template <Attached Object> void remove_marker(Object* object, Spectrum_Frequency frequency);
|
||||
template <Attached Object> void remove_selected_marker(Object* object);
|
||||
@@ -128,10 +125,6 @@ std::expected<std::unique_ptr<Object>, Dependency_Graph_Error> Spectrum::Builder
|
||||
};
|
||||
return spectrum;
|
||||
}
|
||||
template <typename Values>
|
||||
void Spectrum::update_samples(const Values& values) {
|
||||
update_samples(std::span<const Spectrum_Power>(std::data(values), std::size(values)));
|
||||
}
|
||||
inline std::expected<Spectrum_Power, Spectrum::Power_At_Result> Spectrum::Private::power_at(const Prop& state, const Spectrum_Frame& frame, Spectrum_Frequency frequency) {
|
||||
if (frame.samples.empty()) return std::unexpected(Power_At_Result::no_samples);
|
||||
if (state.frequency_range.length() == 0.0) return std::unexpected(Power_At_Result::invalid_frequency_range);
|
||||
@@ -174,6 +167,15 @@ void Spectrum::Private::prepare_frame(Object* object, std::size_t partition_coun
|
||||
auto& private_data = static_cast<typename Object::Private&>(*this);
|
||||
const auto& state = static_cast<const Prop&>(*private_data.current);
|
||||
const auto& frame = object->template current_buffer<Spectrum_Frame_Tag>();
|
||||
if (maxima.size() != frame.samples.size()) {
|
||||
maxima = frame.samples;
|
||||
minima = frame.samples;
|
||||
} else {
|
||||
for (std::size_t index = 0; index < frame.samples.size(); ++index) {
|
||||
maxima[index] = std::max(maxima[index], frame.samples[index]);
|
||||
minima[index] = std::min(minima[index], frame.samples[index]);
|
||||
}
|
||||
}
|
||||
const auto& scene_state = scene->template read_prop<Render_Scene_2D::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>();
|
||||
@@ -218,8 +220,8 @@ void Spectrum::Private::prepare_partition(Object* object, std::size_t partition_
|
||||
auto& partition = prepared.partitions[partition_index];
|
||||
partition.clip = detail::map_plot_rect(frequency_axis, range.domain, power_axis, power_state.coordinate_range, frequency_layout.orientation);
|
||||
partition.current = detail::prepare_curve(std::span<const Spectrum_Power>(frame.samples).subspan(range.first_sample, range.sample_count), range.domain, state.interpolation_mode, state.visible_range_only, frequency_state.coordinate_range, power_state.coordinate_range, frequency_axis, power_axis, frequency_layout.orientation, power_layout.orientation);
|
||||
if (state.max_hold_visible && frame.maxima.size() == frame.samples.size()) partition.maximum = detail::prepare_curve(std::span<const Spectrum_Power>(frame.maxima).subspan(range.first_sample, range.sample_count), range.domain, state.interpolation_mode, state.visible_range_only, frequency_state.coordinate_range, power_state.coordinate_range, frequency_axis, power_axis, frequency_layout.orientation, power_layout.orientation);
|
||||
if (state.min_hold_visible && frame.minima.size() == frame.samples.size()) partition.minimum = detail::prepare_curve(std::span<const Spectrum_Power>(frame.minima).subspan(range.first_sample, range.sample_count), range.domain, state.interpolation_mode, state.visible_range_only, frequency_state.coordinate_range, power_state.coordinate_range, frequency_axis, power_axis, frequency_layout.orientation, power_layout.orientation);
|
||||
if (state.max_hold_visible && maxima.size() == frame.samples.size()) partition.maximum = detail::prepare_curve(std::span<const Spectrum_Power>(maxima).subspan(range.first_sample, range.sample_count), range.domain, state.interpolation_mode, state.visible_range_only, frequency_state.coordinate_range, power_state.coordinate_range, frequency_axis, power_axis, frequency_layout.orientation, power_layout.orientation);
|
||||
if (state.min_hold_visible && minima.size() == frame.samples.size()) partition.minimum = detail::prepare_curve(std::span<const Spectrum_Power>(minima).subspan(range.first_sample, range.sample_count), range.domain, state.interpolation_mode, state.visible_range_only, frequency_state.coordinate_range, power_state.coordinate_range, frequency_axis, power_axis, frequency_layout.orientation, power_layout.orientation);
|
||||
}
|
||||
template <Attached Object>
|
||||
void Spectrum::Private::paint_frame(Object* object) {
|
||||
@@ -246,20 +248,6 @@ void Spectrum::Private::paint_frame(Object* object) {
|
||||
}
|
||||
}
|
||||
template <Attached Object>
|
||||
void Spectrum::Private::update_samples(Object* object, std::span<const Spectrum_Power> values) {
|
||||
const Spectrum_Frame& previous = object->template current_buffer<Spectrum_Frame_Tag>();
|
||||
Spectrum_Frame next;
|
||||
next.samples.assign(values.begin(), values.end());
|
||||
next.maxima.resize(values.size());
|
||||
next.minima.resize(values.size());
|
||||
for (std::size_t index = 0; index < values.size(); ++index) {
|
||||
next.maxima[index] = previous.maxima.size() == values.size() ? std::max(previous.maxima[index], values[index]) : values[index];
|
||||
next.minima[index] = previous.minima.size() == values.size() ? std::min(previous.minima[index], values[index]) : values[index];
|
||||
}
|
||||
object->template pending_buffer<Spectrum_Frame_Tag>() = std::move(next);
|
||||
object->template mark_dirty<Prepare_Data_Tag>();
|
||||
}
|
||||
template <Attached Object>
|
||||
void Spectrum::Private::add_marker(Object* object, Spectrum_Frequency frequency) {
|
||||
object->template update_prop<&Prop::custom_markers>([frequency](Prop_Access<typename Object::Prop> props) { props.template get<Spectrum::Base_Tag>().custom_markers.push_back(frequency); });
|
||||
object->template mark_dirty<Prepare_Data_Tag>();
|
||||
@@ -336,9 +324,6 @@ void Spectrum::Private::before_advance(Object*, Prop_Type*, State_Access<State_T
|
||||
template <Attached Object>
|
||||
const Spectrum::Private::Dispatch& Spectrum::Private::dispatch_for() {
|
||||
static const Dispatch value{
|
||||
[](Root* root, std::span<const Spectrum_Power> values) { auto* object = static_cast<Object*>(root); static_cast<typename Object::Private&>(*object->d).update_samples(object, values); },
|
||||
[](const Root* root) { return static_cast<const Object*>(root)->template current_buffer<Spectrum_Frame_Tag>().samples.size(); },
|
||||
[](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& partition : data.prepared.partitions) count += partition.current.points.size(); return count; },
|
||||
[](const Root* root, Spectrum_Frequency frequency) { const auto* object = static_cast<const Object*>(root); const auto& data = static_cast<const typename Object::Private&>(*object->d); return Private::power_at(static_cast<const Prop&>(*data.current), object->template current_buffer<Spectrum_Frame_Tag>(), frequency); },
|
||||
[](Root* root, Spectrum_Frequency frequency) { auto* object = static_cast<Object*>(root); static_cast<typename Object::Private&>(*object->d).add_marker(object, frequency); },
|
||||
[](Root* root, Spectrum_Frequency frequency) { auto* object = static_cast<Object*>(root); static_cast<typename Object::Private&>(*object->d).remove_marker(object, frequency); },
|
||||
|
||||
@@ -10,13 +10,14 @@
|
||||
#include <vector>
|
||||
namespace aethera::render_2d {
|
||||
struct Sweep_Spectrum_Stream_Tag {};
|
||||
struct Sweep_Spectrum : Def<Sweep_Spectrum, Renderable_2D<Renderable_2D_Cache::enabled>,
|
||||
struct Sweep_Spectrum : Def<Sweep_Spectrum, Renderable_2D,
|
||||
Mpmc_Triple_Buffer<Sweep_Spectrum_Stream_Tag,
|
||||
std::shared_ptr<const std::vector<Plot_Value>>>> {
|
||||
using Scene_Object = Impl<Render_Scene_2D>;
|
||||
using Frequency_Object = Impl<Frequency_Axis>;
|
||||
using Power_Object = Impl<Numeric_Axis>;
|
||||
struct Prop : Prev_Prop {
|
||||
Prop() { cache_enabled = true; }
|
||||
std::size_t bins_per_block{}; /* 每个扫频块期望的功率点数;零值接受首块尺寸。 */
|
||||
std::size_t block_count{1}; /* 一个完整扫频周期包含的块数;零值按 1 处理。 */
|
||||
Plot_Partition_Count partition_count{1}; /* fixed 模式使用的 Prepare 子图分块数。 */
|
||||
|
||||
@@ -16,12 +16,13 @@ struct Waterfall_Row {
|
||||
bool operator==(const Waterfall_Row&) const;
|
||||
};
|
||||
struct Waterfall_Stream_Tag {};
|
||||
struct Waterfall : Def<Waterfall, Renderable_2D<Renderable_2D_Cache::enabled>,
|
||||
struct Waterfall : Def<Waterfall, Renderable_2D,
|
||||
Mpmc_Triple_Buffer<Waterfall_Stream_Tag, std::shared_ptr<const Waterfall_Row>>> {
|
||||
using Scene_Object = Impl<Render_Scene_2D>;
|
||||
using Frequency_Object = Impl<Frequency_Axis>;
|
||||
using Time_Object = Impl<Time_Axis>;
|
||||
struct Prop : Prev_Prop {
|
||||
Prop() { cache_enabled = true; }
|
||||
bool tooltip_enabled{true}; /* 是否响应指针位置显示提示。 */
|
||||
Font tooltip_font{}; /* 提示文字字体。 */
|
||||
Pen tooltip_text_pen{Color::white()}; /* 提示文字样式。 */
|
||||
|
||||
@@ -86,6 +86,13 @@ template <Attached Object>
|
||||
void Render_Scene_2D::Private::after_advance(Object* object, Prop*, State_Access<State>, const Prop*, State_Access<State>) {
|
||||
bool rebuild = !paint_taskflow;
|
||||
object->template access_pending_dependency_graph<Paint_Tag>([&](auto& paint_state) { rebuild = rebuild || paint_state.dirty(); });
|
||||
for (auto& node : paint_order) {
|
||||
const bool enabled = node.private_data->cache_enabled &&
|
||||
node.private_data->cache_enabled(node.object);
|
||||
if (enabled == (node.cache_owner == node.object)) continue;
|
||||
node.private_data->valid_cache = nullptr;
|
||||
rebuild = true;
|
||||
}
|
||||
if (!rebuild) return;
|
||||
if (!paint_taskflow) paint_taskflow = std::make_unique<tf::Taskflow>();
|
||||
auto& taskflow = *paint_taskflow;
|
||||
@@ -100,7 +107,7 @@ void Render_Scene_2D::Private::after_advance(Object* object, Prop*, State_Access
|
||||
auto* data = view.private_data(node);
|
||||
if (!data) return;
|
||||
Root* cache_owner{};
|
||||
if (data->pending_cache) cache_owner = node.object;
|
||||
if (data->cache_enabled && data->cache_enabled(node.object)) cache_owner = node.object;
|
||||
else {
|
||||
bool conflict{};
|
||||
for (const auto* dependency : node.dependencies) {
|
||||
|
||||
@@ -207,11 +207,11 @@ TEST(selection_overlay, control_extends_selection_and_plain_click_clears_it) {
|
||||
render_once(scene.get());
|
||||
};
|
||||
drag({30.0, 90.0}, {60.0, 60.0}, Keyboard_Modifier::none);
|
||||
EXPECT_EQ(overlay->selected_regions().size(), 1u);
|
||||
EXPECT_EQ(overlay->read_prop<Selection_Rectangle_Overlay::Base_Tag>().selected_regions.size(), 1u);
|
||||
drag({70.0, 90.0}, {100.0, 60.0}, Keyboard_Modifier::control);
|
||||
EXPECT_EQ(overlay->selected_regions().size(), 2u);
|
||||
EXPECT_EQ(overlay->read_prop<Selection_Rectangle_Overlay::Base_Tag>().selected_regions.size(), 2u);
|
||||
drag({120.0, 40.0}, {120.0, 40.0}, Keyboard_Modifier::none);
|
||||
EXPECT_TRUE(overlay->selected_regions().empty());
|
||||
EXPECT_TRUE(overlay->read_prop<Selection_Rectangle_Overlay::Base_Tag>().selected_regions.empty());
|
||||
}
|
||||
|
||||
TEST(selection_overlay, time_axis_selection_keeps_axis_coordinates_while_window_moves) {
|
||||
@@ -247,12 +247,12 @@ TEST(selection_overlay, time_axis_selection_keeps_axis_coordinates_while_window_
|
||||
release->button = Mouse_Button::left;
|
||||
scene->submit_stream<Scene_Event_Stream_Tag>(release);
|
||||
render_once(scene.get());
|
||||
ASSERT_EQ(overlay->selected_regions().size(), 1u);
|
||||
const Axis_Rectangle selected = overlay->selected_regions().front();
|
||||
ASSERT_EQ(overlay->read_prop<Selection_Rectangle_Overlay::Base_Tag>().selected_regions.size(), 1u);
|
||||
const Axis_Rectangle selected = overlay->read_prop<Selection_Rectangle_Overlay::Base_Tag>().selected_regions.front();
|
||||
const auto pixel_before = time->coordinate_to_pixel(selected.horizontal.center());
|
||||
time->append_time({4'000});
|
||||
render_once(scene.get());
|
||||
ASSERT_EQ(overlay->selected_regions().size(), 1u);
|
||||
EXPECT_EQ(overlay->selected_regions().front(), selected);
|
||||
ASSERT_EQ(overlay->read_prop<Selection_Rectangle_Overlay::Base_Tag>().selected_regions.size(), 1u);
|
||||
EXPECT_EQ(overlay->read_prop<Selection_Rectangle_Overlay::Base_Tag>().selected_regions.front(), selected);
|
||||
EXPECT_NE(time->coordinate_to_pixel(selected.horizontal.center()), pixel_before);
|
||||
}
|
||||
|
||||
@@ -49,9 +49,11 @@ TEST(spectrum_data, publishes_samples_and_interpolates_power) {
|
||||
EXPECT_EQ(missing.error(), Spectrum::Power_At_Result::no_samples);
|
||||
spectrum->set<&Spectrum::Prop::frequency_range>(Axis_Range{0.0, 100.0});
|
||||
const double samples[]{-100.0, -50.0, 0.0};
|
||||
spectrum->update_samples(samples);
|
||||
spectrum->pending_buffer<Spectrum_Frame_Tag>() =
|
||||
Spectrum_Frame{std::vector<Spectrum_Power>(std::begin(samples), std::end(samples))};
|
||||
spectrum->mark_dirty<Prepare_Data_Tag>();
|
||||
spectrum->advance();
|
||||
EXPECT_EQ(spectrum->sample_count(), 3u);
|
||||
EXPECT_EQ(spectrum->current_buffer<Spectrum_Frame_Tag>().samples.size(), 3u);
|
||||
const auto value = spectrum->power_at(25.0);
|
||||
ASSERT_TRUE(value.has_value());
|
||||
EXPECT_DOUBLE_EQ(*value, -75.0);
|
||||
@@ -111,7 +113,9 @@ TEST(render_scene_2d, composites_axes_and_spectrum_into_final_frame) {
|
||||
spectrum->set<&Spectrum::Prop::sweep_region_visible>(true);
|
||||
spectrum->set<&Spectrum::Prop::max_hold_visible>(true);
|
||||
const double samples[]{-90.0, -65.0, -20.0, -45.0, -75.0};
|
||||
spectrum->update_samples(samples);
|
||||
spectrum->pending_buffer<Spectrum_Frame_Tag>() =
|
||||
Spectrum_Frame{std::vector<Spectrum_Power>(std::begin(samples), std::end(samples))};
|
||||
spectrum->mark_dirty<Prepare_Data_Tag>();
|
||||
scene->set<&Render_Scene_2D::Prop::viewport>(canvas);
|
||||
scene->set<&Render_Scene_2D::Prop::background>(Color::transparent());
|
||||
scene->activate_view();
|
||||
@@ -127,7 +131,7 @@ TEST(render_scene_2d, composites_axes_and_spectrum_into_final_frame) {
|
||||
EXPECT_TRUE(cache_graph.depends_on(spectrum.get(), frequency.get()));
|
||||
EXPECT_TRUE(cache_graph.depends_on(spectrum.get(), power.get()));
|
||||
auto output_frame = render_frame(scene.get());
|
||||
EXPECT_GT(spectrum->rendered_point_count(), 0u);
|
||||
EXPECT_GT(spectrum->read_state<Spectrum::Base_Tag>().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);
|
||||
@@ -153,7 +157,9 @@ TEST(render_scene_2d, composites_axes_and_spectrum_into_final_frame) {
|
||||
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);
|
||||
spectrum->pending_buffer<Spectrum_Frame_Tag>() =
|
||||
Spectrum_Frame{std::vector<Spectrum_Power>(std::begin(samples), std::end(samples))};
|
||||
spectrum->mark_dirty<Prepare_Data_Tag>();
|
||||
output_frame = render_frame(scene.get());
|
||||
const auto& rebuilt_state = spectrum->read_state<Renderable::Base_Tag>();
|
||||
EXPECT_TRUE(rebuilt_state.prepare_graph_rebuilt);
|
||||
|
||||
Reference in New Issue
Block a user