修复若干bug
This commit is contained in:
@@ -60,7 +60,7 @@ std::vector<double> Abs_Axis_Private::create_sub_tick_vector(int sub_tick_count,
|
||||
for (int k = 1; k <= sub_tick_count; ++k)
|
||||
result.push_back(ticks.at(i - 1) + k * sub_tick_step);
|
||||
}
|
||||
while (!result.empty() && !range.contain(result.back())) {
|
||||
while (!result.empty() && !range.contains(result.back())) {
|
||||
result.pop_back();
|
||||
}
|
||||
return result;
|
||||
|
||||
@@ -39,12 +39,12 @@ struct Abs_Axis_Private : Typed_Render_Data<Abs_Axis, Abs_Axis_Render_State, Abs
|
||||
Range range = {s->coord_start, s->coord_start + s->coord_length};
|
||||
double tick_step = get_tick_step(range);
|
||||
std::vector<double> ticks = create_tick_vector(tick_step, range);
|
||||
while (!ticks.empty() && !range.contain(ticks.back())) {
|
||||
while (!ticks.empty() && !range.contains(ticks.back())) {
|
||||
ticks.pop_back();
|
||||
}
|
||||
int sub_tick_count = get_sub_tick_count(tick_step);
|
||||
std::vector<double> sub_ticks = create_sub_tick_vector(sub_tick_count, ticks, range);
|
||||
while (!sub_ticks.empty() && !range.contain(sub_ticks.back())) {
|
||||
while (!sub_ticks.empty() && !range.contains(sub_ticks.back())) {
|
||||
sub_ticks.pop_back();
|
||||
}
|
||||
std::vector<std::string> labels = create_label_vector(ticks, State_Render);
|
||||
|
||||
@@ -23,11 +23,11 @@ std::shared_ptr<Timeline_Stream> Time_Axis::timeline_stream() {
|
||||
void Time_Axis::set_timeline_stream(std::shared_ptr<Timeline_Stream> value) {
|
||||
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);
|
||||
bool Time_Axis::newest_at_axis_start() {
|
||||
return d()->edit_state_value(&Time_Axis_Render_State::newest_at_axis_start);
|
||||
}
|
||||
void Time_Axis::set_start_coord_to_end_coord(bool value) {
|
||||
d()->set_state_value(&Time_Axis_Render_State::start_coord_to_end_coord, value);
|
||||
void Time_Axis::set_newest_at_axis_start(bool value) {
|
||||
d()->set_state_value(&Time_Axis_Render_State::newest_at_axis_start, value);
|
||||
}
|
||||
int Time_Axis::append_time(Time_Of_Day time) {
|
||||
if (!ok())
|
||||
@@ -42,7 +42,7 @@ int Time_Axis::append_time(Time_Of_Day time) {
|
||||
Time_Of_Day Time_Axis::tick_to_time(int tick) {
|
||||
auto stream = timeline_stream();
|
||||
if (stream)
|
||||
return stream->capture(visible_time_point_count(), d()->state_data(State_Edit)->label_tick_interval_hint(), start_coord_to_end_coord()).time_by_tick(tick);
|
||||
return stream->capture(visible_time_point_count(), d()->state_data(State_Edit)->label_tick_interval_hint(), newest_at_axis_start()).time_by_tick(tick);
|
||||
return d()->time_by_tick(tick);
|
||||
}
|
||||
Time_Axis::Builder::Builder(std::shared_ptr<Renderable> parent, Orientation orientation) {
|
||||
@@ -60,7 +60,7 @@ std::shared_ptr<Time_Axis> Time_Axis::Builder::build() {
|
||||
PROP_R(std::string, time_format)
|
||||
PROP_R(Font, font)
|
||||
PROP_R(std::shared_ptr<Timeline_Stream>, timeline_stream)
|
||||
PROP_R(bool, start_coord_to_end_coord)
|
||||
PROP_R(bool, newest_at_axis_start)
|
||||
sc->visible_time_point_count = std::max(1, sc->visible_time_point_count);
|
||||
sc->tick_label_spacing_px = std::max(1, sc->tick_label_spacing_px);
|
||||
if (!sc->timeline_stream)
|
||||
|
||||
@@ -20,7 +20,7 @@ public:
|
||||
PROP(std::string, time_format)
|
||||
PROP(Font, font)
|
||||
PROP(std::shared_ptr<Timeline_Stream>, timeline_stream)
|
||||
PROP(bool, start_coord_to_end_coord)
|
||||
PROP(bool, newest_at_axis_start)
|
||||
/// 推入一个新的时间点并返回对应 tick。
|
||||
int append_time(Time_Of_Day time);
|
||||
/// 将 tick 转换成时间。
|
||||
@@ -36,7 +36,7 @@ struct Time_Axis_Prop {
|
||||
std::string time_format = "mm:ss.zzz";
|
||||
Font font;
|
||||
std::shared_ptr<Timeline_Stream> timeline_stream;
|
||||
bool start_coord_to_end_coord = false;
|
||||
bool newest_at_axis_start = false;
|
||||
};
|
||||
struct LIB_DECL Time_Axis::Builder : Abs_Axis::Builder_T<Time_Axis::Builder>,
|
||||
public
|
||||
@@ -46,7 +46,7 @@ struct LIB_DECL Time_Axis::Builder : Abs_Axis::Builder_T<Time_Axis::Builder>,
|
||||
PROP_B(std::string, time_format)
|
||||
PROP_B(Font, font)
|
||||
PROP_B(std::shared_ptr<Timeline_Stream>, timeline_stream)
|
||||
PROP_B(bool, start_coord_to_end_coord)
|
||||
PROP_B(bool, newest_at_axis_start)
|
||||
/// 创建时间轴构造器。
|
||||
Builder(std::shared_ptr<Renderable> parent, Orientation orientation);
|
||||
/// 构建并返回时间轴对象。
|
||||
|
||||
@@ -29,7 +29,7 @@ struct Time_Axis_Private : Typed_Render_Data_Base<Abs_Axis_Private, Time_Axis, T
|
||||
std::shared_ptr<const Timeline_Stream_Snapshot> 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->visible_time_point_count, state->label_tick_interval_hint(), state->start_coord_to_end_coord);
|
||||
return snapshot.timeline_snapshot(state->timeline_stream, state->visible_time_point_count, state->label_tick_interval_hint(), state->newest_at_axis_start);
|
||||
}
|
||||
std::shared_ptr<const Timeline_Stream_Snapshot> capture_timeline(const Plot_Render_Snapshot& snapshot) {
|
||||
sync_state_pipeline();
|
||||
@@ -44,7 +44,7 @@ struct Time_Axis_Private : Typed_Render_Data_Base<Abs_Axis_Private, Time_Axis, T
|
||||
if (!timeline_snapshot)
|
||||
return;
|
||||
s->coord_start = timeline_snapshot->coordinate_begin;
|
||||
s->coord_length = timeline_snapshot->time_point_size;
|
||||
s->coord_length = timeline_snapshot->visible_time_point_count;
|
||||
ticks.clear();
|
||||
for (const Timeline_Label_Tick& tick : timeline_snapshot->ticks) {
|
||||
ticks.push_back(tick.coordinate);
|
||||
|
||||
@@ -2,19 +2,19 @@
|
||||
#include "Timeline_Stream.h"
|
||||
#include <algorithm>
|
||||
namespace renderive {
|
||||
std::shared_ptr<const Timeline_Stream_Snapshot> Plot_Render_Snapshot::timeline_snapshot(const std::shared_ptr<Timeline_Stream>& stream, int time_point_size, int tick_space, bool start_coord_to_end_coord) const {
|
||||
std::shared_ptr<const Timeline_Stream_Snapshot> Plot_Render_Snapshot::timeline_snapshot(const std::shared_ptr<Timeline_Stream>& stream, int time_point_size, int label_tick_interval, 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);
|
||||
label_tick_interval = std::max(1, label_tick_interval);
|
||||
if (!timeline_cache)
|
||||
timeline_cache = std::make_shared<Timeline_Stream_Frame_Cache>();
|
||||
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)
|
||||
if (item.stream.lock() == stream && item.time_point_size == time_point_size && item.tick_space == label_tick_interval && item.start_coord_to_end_coord == start_coord_to_end_coord)
|
||||
return item.snapshot;
|
||||
}
|
||||
auto snapshot = std::make_shared<Timeline_Stream_Snapshot>(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});
|
||||
auto snapshot = std::make_shared<Timeline_Stream_Snapshot>(stream->capture(time_point_size, label_tick_interval, start_coord_to_end_coord));
|
||||
timeline_cache->items.push_back({stream, time_point_size, label_tick_interval, start_coord_to_end_coord, snapshot});
|
||||
return snapshot;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -60,7 +60,7 @@ struct Plot_Render_Snapshot {
|
||||
bool destroying() const {
|
||||
return !context || context->destroying.load(std::memory_order_acquire);
|
||||
}
|
||||
std::shared_ptr<const Timeline_Stream_Snapshot> timeline_snapshot(const std::shared_ptr<Timeline_Stream>& stream, int time_point_size, int tick_space, bool start_coord_to_end_coord) const;
|
||||
std::shared_ptr<const Timeline_Stream_Snapshot> timeline_snapshot(const std::shared_ptr<Timeline_Stream>& stream, int time_point_size, int label_tick_interval, bool start_coord_to_end_coord) const;
|
||||
};
|
||||
|
||||
} // namespace renderive
|
||||
|
||||
@@ -20,12 +20,9 @@
|
||||
#include <string>
|
||||
#include <type_traits>
|
||||
#include <vector>
|
||||
|
||||
namespace renderive {
|
||||
|
||||
class Plot_Core;
|
||||
class Renderable;
|
||||
|
||||
struct Renderable_Tree_Node {
|
||||
std::shared_ptr<Renderable> renderable;
|
||||
std::size_t subtree_end{};
|
||||
@@ -34,9 +31,7 @@ struct Renderable_Tree_Node {
|
||||
std::string cache_parent_object_name;
|
||||
int cache_tree_depth{};
|
||||
};
|
||||
|
||||
using Renderable_Tree_Snapshot = std::vector<Renderable_Tree_Node>;
|
||||
|
||||
template <typename T>
|
||||
concept Render_State_Type = std::derived_from<T, Render_State> && std::is_copy_assignable_v<T>;
|
||||
template <typename T>
|
||||
@@ -46,8 +41,7 @@ concept Render_Data_Type = std::derived_from<T, Render_Data>;
|
||||
template <typename Owner, Render_Data_Type Data, Render_State_Type State, Input_Data_Type Input>
|
||||
struct Renderable_Binding;
|
||||
template <typename That>
|
||||
struct Hover_Info_Renderable;
|
||||
|
||||
struct Hover_Tooltip_Mixin;
|
||||
class Renderable : public std::enable_shared_from_this<Renderable> {
|
||||
public:
|
||||
virtual Render_Object_Owner create_render_data() {
|
||||
@@ -59,23 +53,18 @@ public:
|
||||
virtual Render_Object_Owner create_input_data() {
|
||||
return make_render_object<Input_Data>();
|
||||
}
|
||||
|
||||
virtual void init(const std::shared_ptr<Renderable>& parent);
|
||||
virtual void init_root(Plot_Core* plot);
|
||||
|
||||
void add_child(const std::shared_ptr<Renderable>& child);
|
||||
void remove_child(const std::shared_ptr<Renderable>& child);
|
||||
|
||||
std::vector<std::shared_ptr<Renderable>> children_snapshot() const;
|
||||
std::vector<std::shared_ptr<Renderable>> 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);
|
||||
@@ -85,7 +74,6 @@ public:
|
||||
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;
|
||||
@@ -96,24 +84,19 @@ public:
|
||||
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 <typename Owner, Render_Data_Type Data, Render_State_Type State, Input_Data_Type Input>
|
||||
friend struct Renderable_Binding;
|
||||
template <typename That>
|
||||
friend struct Hover_Info_Renderable;
|
||||
|
||||
friend struct Hover_Tooltip_Mixin;
|
||||
Render_Object_Owner d_owner;
|
||||
Render_Data* d_ptr{};
|
||||
Plot_Core* plot{};
|
||||
@@ -131,7 +114,6 @@ private:
|
||||
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);
|
||||
@@ -146,5 +128,4 @@ private:
|
||||
void append_hit_renderables(std::vector<std::shared_ptr<Renderable>>& hits, const PointF& pos);
|
||||
void detach_from_plot_tree();
|
||||
};
|
||||
|
||||
} // namespace renderive
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
#include <memory_resource>
|
||||
namespace renderive {
|
||||
int Timeline_Stream_Snapshot::coordinate_end() const {
|
||||
return coordinate_begin + time_point_size - 1;
|
||||
return coordinate_begin + visible_time_point_count - 1;
|
||||
}
|
||||
bool Timeline_Stream_Snapshot::coord_by_stream_tick(int stream_tick, int& coord) const {
|
||||
if (times.empty())
|
||||
@@ -15,13 +15,13 @@ bool Timeline_Stream_Snapshot::coord_by_stream_tick(int stream_tick, int& coord)
|
||||
if (newest_at_axis_start)
|
||||
coord = coordinate_begin + (last_stream_tick - stream_tick);
|
||||
else
|
||||
coord = coordinate_begin + (time_point_size - count) + (stream_tick - first_stream_tick);
|
||||
coord = coordinate_begin + (visible_time_point_count - count) + (stream_tick - first_stream_tick);
|
||||
return coord >= coordinate_begin && coord <= coordinate_end();
|
||||
}
|
||||
Time_Of_Day Timeline_Stream_Snapshot::time_by_tick(int tick) const {
|
||||
int offset = tick - coordinate_begin;
|
||||
int count = static_cast<int>(times.size());
|
||||
int index = newest_at_axis_start ? count - 1 - offset : offset - (time_point_size - count);
|
||||
int index = newest_at_axis_start ? count - 1 - offset : offset - (visible_time_point_count - count);
|
||||
if (index < 0 || index >= count)
|
||||
return {};
|
||||
return times[static_cast<std::size_t>(index)];
|
||||
@@ -59,7 +59,7 @@ Timeline_Stream_Snapshot Timeline_Stream::capture(int time_point_size, int label
|
||||
captured.push_back(*it);
|
||||
}
|
||||
snapshot.newest_at_axis_start = start_coord_to_end_coord;
|
||||
snapshot.time_point_size = time_point_size;
|
||||
snapshot.visible_time_point_count = time_point_size;
|
||||
if (captured.empty()) {
|
||||
snapshot.coordinate_begin = 0;
|
||||
return snapshot;
|
||||
|
||||
@@ -16,7 +16,7 @@ struct LIB_DECL Timeline_Stream_Snapshot {
|
||||
std::uint64_t version{};
|
||||
bool newest_at_axis_start = false; // 当前 true 时 代表最新时间映射到起始坐标
|
||||
int coordinate_begin{};
|
||||
int time_point_size = 1;
|
||||
int visible_time_point_count = 1;
|
||||
int first_stream_tick{};
|
||||
int last_stream_tick = -1;
|
||||
std::vector<Time_Of_Day> times;
|
||||
|
||||
@@ -125,7 +125,7 @@ struct Range {
|
||||
bool operator!=(const Range& other) const {
|
||||
return !(*this == other);
|
||||
}
|
||||
[[nodiscard]] bool contain(double value) const {
|
||||
[[nodiscard]] bool contains(double value) const {
|
||||
if (origin < target)
|
||||
return value > origin - 0.0001 && value < target + 0.0001;
|
||||
return value > target - 0.0001 && value < origin + 0.0001;
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
#include "../base/Memory.h"
|
||||
#include "Constellation_Diagram_p.h"
|
||||
namespace renderive {
|
||||
RENDERIVE_DEFINE_RENDERABLE_BINDING(Constellation_Diagram, Constellation_Diagram_Private, Constellation_Diagram_State, Constellation_Diagram_Input_Data, "Planisphere")
|
||||
RENDERIVE_DEFINE_RENDERABLE_BINDING(Constellation_Diagram, Constellation_Diagram_Private, Constellation_Diagram_State, Constellation_Diagram_Input_Data, "Constellation_Diagram")
|
||||
std::shared_ptr<Axis> Constellation_Diagram::i_axis() {
|
||||
return d()->edit_state_value(&Constellation_Diagram_State::i_axis).lock();
|
||||
}
|
||||
|
||||
@@ -50,7 +50,7 @@ private:
|
||||
Render_Object_Owner create_render_state() override;
|
||||
Render_Object_Owner create_input_data() override;
|
||||
};
|
||||
struct Planisphere_Prop {
|
||||
struct Constellation_Diagram_Prop {
|
||||
std::weak_ptr<Axis> i_axis{};
|
||||
std::weak_ptr<Axis> q_axis{};
|
||||
Range i_range{0, 100};
|
||||
@@ -62,7 +62,7 @@ struct Planisphere_Prop {
|
||||
double phase_offset_radians = 0.0;
|
||||
};
|
||||
/// @brief 星座图构造器。
|
||||
struct LIB_DECL Constellation_Diagram::Builder : Planisphere_Prop {
|
||||
struct LIB_DECL Constellation_Diagram::Builder : Constellation_Diagram_Prop {
|
||||
PROP_B(std::shared_ptr<Renderable>, parent)
|
||||
PROP_B(std::shared_ptr<Axis>, i_axis)
|
||||
PROP_B(std::shared_ptr<Axis>, q_axis)
|
||||
|
||||
@@ -45,7 +45,7 @@ struct Constellation_Diagram_Input_Data : Input_Data {
|
||||
data_list.clear();
|
||||
}
|
||||
};
|
||||
struct Constellation_Diagram_State : Render_State, Planisphere_Prop {};
|
||||
struct Constellation_Diagram_State : Render_State, Constellation_Diagram_Prop {};
|
||||
struct Constellation_Diagram_Private : Typed_Render_Data<Constellation_Diagram, Constellation_Diagram_State, Constellation_Diagram_Input_Data> {
|
||||
std::pmr::list<Constellation_Diagram_Data> data_list{memory_resource(Memory_Domain::Constellation_Diagram)};
|
||||
std::pmr::vector<PointF> prepared_points{memory_resource(Memory_Domain::Constellation_Diagram)};
|
||||
|
||||
@@ -2,18 +2,18 @@
|
||||
#include "Frequency_Trace_p.h"
|
||||
#include "../base/Memory.h"
|
||||
namespace renderive {
|
||||
RENDERIVE_DEFINE_RENDERABLE_BINDING(Frequency_Trace, Frequency_Trace_Private, Audio_Frequency_Render_State, Audio_Frequency_Input_Data, "Audio_Frequency")
|
||||
RENDERIVE_DEFINE_RENDERABLE_BINDING(Frequency_Trace, Frequency_Trace_Private, Frequency_Trace_Render_State, Frequency_Trace_Input_Data, "Frequency_Trace")
|
||||
std::shared_ptr<Time_Axis> Frequency_Trace::time_axis() {
|
||||
return d()->edit_state_value(&Audio_Frequency_Render_State::time_axis).lock();
|
||||
return d()->edit_state_value(&Frequency_Trace_Render_State::time_axis).lock();
|
||||
}
|
||||
void Frequency_Trace::set_time_axis(std::shared_ptr<Time_Axis> value) {
|
||||
d()->set_state_value(&Audio_Frequency_Render_State::time_axis, std::weak_ptr<Time_Axis>(value));
|
||||
d()->set_state_value(&Frequency_Trace_Render_State::time_axis, std::weak_ptr<Time_Axis>(value));
|
||||
}
|
||||
std::shared_ptr<Axis> Frequency_Trace::value_axis() {
|
||||
return d()->edit_state_value(&Audio_Frequency_Render_State::value_axis).lock();
|
||||
return d()->edit_state_value(&Frequency_Trace_Render_State::value_axis).lock();
|
||||
}
|
||||
void Frequency_Trace::set_value_axis(std::shared_ptr<Axis> value) {
|
||||
d()->set_state_value(&Audio_Frequency_Render_State::value_axis, std::weak_ptr<Axis>(value));
|
||||
d()->set_state_value(&Frequency_Trace_Render_State::value_axis, std::weak_ptr<Axis>(value));
|
||||
}
|
||||
Frequency_Trace::Builder::Builder(std::shared_ptr<Renderable> parent, std::shared_ptr<Time_Axis> time_axis, std::shared_ptr<Axis> value_axis) {
|
||||
ASSERT(parent && parent->attached_to_plot(), "Audio_Frequency build error! parent invalid");
|
||||
@@ -30,7 +30,7 @@ std::shared_ptr<Frequency_Trace> Frequency_Trace::Builder::build() {
|
||||
auto ret = renderive::make_shared<Frequency_Trace>();
|
||||
ret->init(parent);
|
||||
Frequency_Trace_Private* pd = ret->d();
|
||||
Audio_Frequency_Render_State* sc = reinterpret_cast<Audio_Frequency_Render_State*>(pd->state(State_Edit));
|
||||
Frequency_Trace_Render_State* sc = reinterpret_cast<Frequency_Trace_Render_State*>(pd->state(State_Edit));
|
||||
PROP_R(std::weak_ptr<Time_Axis>, time_axis)
|
||||
PROP_R(std::weak_ptr<Axis>, value_axis)
|
||||
sc->pen = Pen{.color = color, .width = 1.0};
|
||||
@@ -39,7 +39,7 @@ std::shared_ptr<Frequency_Trace> Frequency_Trace::Builder::build() {
|
||||
void Frequency_Trace::append_sample(int tick, double data) {
|
||||
if (!ok())
|
||||
return;
|
||||
Render_Input_Lease<Frequency_Trace_Private, Audio_Frequency_Input_Data> input(d());
|
||||
Render_Input_Lease<Frequency_Trace_Private, Frequency_Trace_Input_Data> input(d());
|
||||
input->push(tick, data);
|
||||
}
|
||||
void Frequency_Trace::append_sample(Time_Of_Day time, double data) {
|
||||
@@ -52,7 +52,7 @@ void Frequency_Trace::append_sample(Time_Of_Day time, double data) {
|
||||
if (!stream)
|
||||
return;
|
||||
int tick = stream->append_time(time);
|
||||
Render_Input_Lease<Frequency_Trace_Private, Audio_Frequency_Input_Data> input(d());
|
||||
Render_Input_Lease<Frequency_Trace_Private, Frequency_Trace_Input_Data> input(d());
|
||||
input->push(tick, data);
|
||||
}
|
||||
} // namespace renderive
|
||||
|
||||
@@ -11,22 +11,22 @@
|
||||
#include "Psc_Cpp_Core/Base/RingBuffer.hpp"
|
||||
#include "Frequency_Trace.h"
|
||||
namespace renderive {
|
||||
struct Audio_Frequency_Data {
|
||||
struct Frequency_Sample {
|
||||
int tick{};
|
||||
double power{};
|
||||
};
|
||||
struct Audio_Frequency_Input_Data : Input_Data {
|
||||
struct Frequency_Trace_Input_Data : Input_Data {
|
||||
static constexpr std::size_t Max_Pending_Value_Count = 256;
|
||||
Bounded_Input_Buffer data_list;
|
||||
void push(int tick, double power) {
|
||||
Audio_Frequency_Data data{tick, power};
|
||||
Frequency_Sample data{tick, power};
|
||||
data_list.push(&data, sizeof(data), Max_Pending_Value_Count);
|
||||
}
|
||||
template <typename Handler>
|
||||
void read_all(Handler handler) {
|
||||
data_list.read_all([&handler](const void* data, std::size_t size) {
|
||||
(void)size;
|
||||
Audio_Frequency_Data value;
|
||||
Frequency_Sample value;
|
||||
std::memcpy(&value, data, sizeof(value));
|
||||
handler(value);
|
||||
});
|
||||
@@ -35,10 +35,10 @@ struct Audio_Frequency_Input_Data : Input_Data {
|
||||
data_list.clear();
|
||||
}
|
||||
};
|
||||
struct Audio_Frequency_Render_State : Render_State, Frequency_Trace_Prop {};
|
||||
struct Frequency_Trace_Private : Typed_Render_Data<Frequency_Trace, Audio_Frequency_Render_State, Audio_Frequency_Input_Data> {
|
||||
struct Frequency_Trace_Render_State : Render_State, Frequency_Trace_Prop {};
|
||||
struct Frequency_Trace_Private : Typed_Render_Data<Frequency_Trace, Frequency_Trace_Render_State, Frequency_Trace_Input_Data> {
|
||||
Psc::StreamRingBuffer_ST buffer{memory_resource(Memory_Domain::Audio)};
|
||||
std::pmr::vector<Audio_Frequency_Data> data_snapshot{memory_resource(Memory_Domain::Audio)};
|
||||
std::pmr::vector<Frequency_Sample> data_snapshot{memory_resource(Memory_Domain::Audio)};
|
||||
std::pmr::vector<PointF> prepared_points{memory_resource(Memory_Domain::Audio)};
|
||||
int data_count{};
|
||||
int point_count{};
|
||||
@@ -46,12 +46,12 @@ struct Frequency_Trace_Private : Typed_Render_Data<Frequency_Trace, Audio_Freque
|
||||
point_count = count;
|
||||
data_count = 0;
|
||||
data_snapshot.resize(count);
|
||||
buffer.init(sizeof(Audio_Frequency_Data) * count);
|
||||
buffer.init(sizeof(Frequency_Sample) * count);
|
||||
}
|
||||
void push_power(const Audio_Frequency_Data& data) {
|
||||
void push_power(const Frequency_Sample& data) {
|
||||
if (data_count == point_count) {
|
||||
Audio_Frequency_Data ignored;
|
||||
std::size_t len = sizeof(Audio_Frequency_Data);
|
||||
Frequency_Sample ignored;
|
||||
std::size_t len = sizeof(Frequency_Sample);
|
||||
buffer.read(&ignored, len);
|
||||
--data_count;
|
||||
}
|
||||
@@ -59,12 +59,12 @@ struct Frequency_Trace_Private : Typed_Render_Data<Frequency_Trace, Audio_Freque
|
||||
++data_count;
|
||||
}
|
||||
void snapshot_power_buffer() {
|
||||
buffer.peek_best_effort(data_snapshot.data(), sizeof(Audio_Frequency_Data) * data_count);
|
||||
buffer.peek_best_effort(data_snapshot.data(), sizeof(Frequency_Sample) * data_count);
|
||||
}
|
||||
void prepare_data(const Plot_Render_Snapshot& snapshot) override {
|
||||
sync_state_pipeline();
|
||||
Audio_Frequency_Render_State* s = render_state();
|
||||
Audio_Frequency_Input_Data* d = render_input_data();
|
||||
Frequency_Trace_Render_State* s = render_state();
|
||||
Frequency_Trace_Input_Data* d = render_input_data();
|
||||
auto time_axis = s->time_axis.lock();
|
||||
auto value_axis = s->value_axis.lock();
|
||||
if (!time_axis || !value_axis)
|
||||
@@ -72,16 +72,16 @@ struct Frequency_Trace_Private : Typed_Render_Data<Frequency_Trace, Audio_Freque
|
||||
auto timeline = Time_Axis_Render_Access::capture_timeline(time_axis.get(), snapshot);
|
||||
if (!timeline)
|
||||
return;
|
||||
if (point_count != timeline->time_point_size)
|
||||
resize_power_buffer(timeline->time_point_size);
|
||||
d->read_all([this](const Audio_Frequency_Data& data) {
|
||||
if (point_count != timeline->visible_time_point_count)
|
||||
resize_power_buffer(timeline->visible_time_point_count);
|
||||
d->read_all([this](const Frequency_Sample& data) {
|
||||
push_power(data);
|
||||
});
|
||||
snapshot_power_buffer();
|
||||
prepared_points.clear();
|
||||
prepared_points.reserve(static_cast<std::size_t>(data_count));
|
||||
for (int i = 0; i < data_count; ++i) {
|
||||
const Audio_Frequency_Data& item = data_snapshot[static_cast<std::size_t>(i)];
|
||||
const Frequency_Sample& item = data_snapshot[static_cast<std::size_t>(i)];
|
||||
int coord{};
|
||||
if (timeline->coord_by_stream_tick(item.tick, coord))
|
||||
prepared_points.emplace_back(static_cast<double>(coord), item.power);
|
||||
@@ -89,7 +89,7 @@ struct Frequency_Trace_Private : Typed_Render_Data<Frequency_Trace, Audio_Freque
|
||||
clear_render_input();
|
||||
}
|
||||
void draw(Canvas& canvas, const Plot_Render_Snapshot&) override {
|
||||
Audio_Frequency_Render_State* s = render_state();
|
||||
Frequency_Trace_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())
|
||||
|
||||
@@ -134,7 +134,7 @@ private:
|
||||
virtual Hover_State_Edit begin_hover_edit_impl() = 0;
|
||||
};
|
||||
template <typename That>
|
||||
struct LIB_DECL Hover_Info_Renderable : Hover_Tooltip_Provider {
|
||||
struct LIB_DECL Hover_Tooltip_Mixin : Hover_Tooltip_Provider {
|
||||
private:
|
||||
That* that() {
|
||||
return static_cast<That*>(this);
|
||||
|
||||
@@ -279,7 +279,7 @@ void Spectrum_Private::set_frequency_data(std::pmr::vector<double>& line_data, c
|
||||
min_marker_value = cur_powers[min_index];
|
||||
}
|
||||
double Spectrum_Private::get_y(double x, bool& ok, Line_Interpolation_Mode mode) {
|
||||
if (cur_powers.size() < 2 || cache_frequency_range.length() == 0.0 || !cache_frequency_range.contain(x)) {
|
||||
if (cur_powers.size() < 2 || cache_frequency_range.length() == 0.0 || !cache_frequency_range.contains(x)) {
|
||||
ok = false;
|
||||
return -1;
|
||||
}
|
||||
|
||||
@@ -16,7 +16,7 @@ struct Spectrum_Private;
|
||||
class Label;
|
||||
class Marker;
|
||||
/// @brief 单条频谱线图元,支持可见范围裁剪和多种线插值策略。
|
||||
class LIB_DECL Spectrum : public Renderable, public Hover_Info_Renderable<Spectrum> {
|
||||
class LIB_DECL Spectrum : public Renderable, public Hover_Tooltip_Mixin<Spectrum> {
|
||||
public:
|
||||
/// @brief 创建频谱线图元。
|
||||
Spectrum();
|
||||
|
||||
@@ -25,7 +25,7 @@ enum class Waterfall_Row_Update_Policy : std::uint8_t {
|
||||
Rate_Limited_Latest
|
||||
};
|
||||
/// @brief 瀑布图图元,按时间行保存二维频谱强度。
|
||||
class LIB_DECL Waterfall : public Renderable, public Hover_Info_Renderable<Waterfall> {
|
||||
class LIB_DECL Waterfall : public Renderable, public Hover_Tooltip_Mixin<Waterfall> {
|
||||
public:
|
||||
/// @brief 创建瀑布图图元。
|
||||
Waterfall();
|
||||
|
||||
@@ -368,7 +368,7 @@ struct Waterfall_Private : Typed_Render_Data<Waterfall, Waterfall_Render_State,
|
||||
auto timeline = Time_Axis_Render_Access::capture_timeline(time_axis.get(), snapshot);
|
||||
if (!timeline)
|
||||
return;
|
||||
int col_count = s->frequency_bin_count, row_count = timeline->time_point_size;
|
||||
int col_count = s->frequency_bin_count, row_count = timeline->visible_time_point_count;
|
||||
if (col_count <= 0 || row_count <= 0)
|
||||
return;
|
||||
if (!ring_buffer.match(col_count, row_count)) {
|
||||
@@ -454,12 +454,12 @@ struct Waterfall_Private : Typed_Render_Data<Waterfall, Waterfall_Render_State,
|
||||
auto color_scale = s->color_scale.snapshot();
|
||||
Abs_Axis* h_axis = frequency_axis.get();
|
||||
Range h_range = s->frequency_range;
|
||||
Range v_range{static_cast<double>(timeline.coordinate_begin), static_cast<double>(timeline.coordinate_begin + timeline.time_point_size)};
|
||||
Range v_range{static_cast<double>(timeline.coordinate_begin), static_cast<double>(timeline.coordinate_begin + timeline.visible_time_point_count)};
|
||||
if (s->visible_range_only && !intersect_range(s->frequency_range, Axis_Render_Access::coord_range(h_axis), h_range)) {
|
||||
image_draw_visible = false;
|
||||
return;
|
||||
}
|
||||
int col_count = s->frequency_bin_count, row_count = timeline.time_point_size;
|
||||
int col_count = s->frequency_bin_count, row_count = timeline.visible_time_point_count;
|
||||
if (col_count <= 0 || row_count <= 0 || s->frequency_range.length() == 0.0 || color_scale->range.size() == 0.0)
|
||||
return;
|
||||
double source_start = static_cast<double>(col_count) * (h_range.origin - s->frequency_range.origin) / s->frequency_range.length();
|
||||
|
||||
+50
-107
@@ -13,10 +13,8 @@
|
||||
#include <gtest/gtest.h>
|
||||
#include <rigtorp/MPMCQueue.h>
|
||||
#include <thread>
|
||||
|
||||
namespace renderive {
|
||||
namespace {
|
||||
|
||||
bool unique_roles(const Triple_Buffer_View& view) {
|
||||
std::array<bool, 3> seen{};
|
||||
for (std::uint8_t index : view.index) {
|
||||
@@ -26,7 +24,6 @@ bool unique_roles(const Triple_Buffer_View& view) {
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
Frame_Lifecycle_Record render_frame(std::uint64_t begin_ns, std::uint64_t end_ns) {
|
||||
Frame_Lifecycle_Record frame;
|
||||
frame.render_role_enter_ns = begin_ns;
|
||||
@@ -34,7 +31,6 @@ Frame_Lifecycle_Record render_frame(std::uint64_t begin_ns, std::uint64_t end_ns
|
||||
frame.render_end_ns = end_ns;
|
||||
return frame;
|
||||
}
|
||||
|
||||
bool wait_until(std::function<bool()> predicate) {
|
||||
auto deadline = std::chrono::steady_clock::now() + std::chrono::seconds(2);
|
||||
while (std::chrono::steady_clock::now() < deadline) {
|
||||
@@ -44,7 +40,6 @@ bool wait_until(std::function<bool()> predicate) {
|
||||
}
|
||||
return predicate();
|
||||
}
|
||||
|
||||
std::shared_ptr<Frame_Lifecycle_Record> performance_frame(std::uint64_t frame_id, int width = 240, int height = 80) {
|
||||
auto frame = std::make_shared<Frame_Lifecycle_Record>();
|
||||
frame->frame_id = frame_id;
|
||||
@@ -68,7 +63,6 @@ std::shared_ptr<Frame_Lifecycle_Record> performance_frame(std::uint64_t frame_id
|
||||
frame->worker.executor_at_finish.completed_task_nodes = frame_id;
|
||||
return frame;
|
||||
}
|
||||
|
||||
bool valid_utf8(const std::string& text) {
|
||||
for (std::size_t i = 0; i < text.size();) {
|
||||
unsigned char c = static_cast<unsigned char>(text[i]);
|
||||
@@ -93,16 +87,13 @@ bool valid_utf8(const std::string& text) {
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
TEST(Renderive_Canvas, TextUsesPenAndFillUsesBrush) {
|
||||
auto diagnostics = Canvas::default_font_diagnostics();
|
||||
ASSERT_TRUE(diagnostics.face_valid);
|
||||
ASSERT_TRUE(diagnostics.font_valid);
|
||||
ASSERT_GT(diagnostics.test_width, 0.0);
|
||||
ASSERT_GT(diagnostics.test_height, 0.0);
|
||||
|
||||
Image image(400, 100);
|
||||
image.fill(Color::transparent());
|
||||
{
|
||||
@@ -113,7 +104,6 @@ TEST(Renderive_Canvas, TextUsesPenAndFillUsesBrush) {
|
||||
canvas.draw_text(PointF{10.0, 10.0}, "0123456789 ABC \xE4\xB8\xAD\xE6\x96\x87");
|
||||
canvas.draw_text(RectF{10.0, 50.0, 300.0, 30.0}, Text_Align::Left | Text_Align::Top, "0123");
|
||||
}
|
||||
|
||||
EXPECT_EQ(premultiplied_to_color(image.row(2)[2]), Color::white());
|
||||
int dark_pixels = 0;
|
||||
for (int y = 8; y < 82; ++y) {
|
||||
@@ -127,18 +117,15 @@ TEST(Renderive_Canvas, TextUsesPenAndFillUsesBrush) {
|
||||
}
|
||||
EXPECT_GT(dark_pixels, 20);
|
||||
}
|
||||
|
||||
TEST(Renderive_Triple_Buffer, RoleSwapsAreTryOnlyAndDeterministic) {
|
||||
Triple_Role_Buffer_Control control;
|
||||
control.reset();
|
||||
EXPECT_TRUE(unique_roles(control.read_view()));
|
||||
|
||||
auto first = control.try_swap_role(Frame_Rendering, Frame_Middle, [](const Triple_Buffer_View&) {
|
||||
return true;
|
||||
});
|
||||
EXPECT_EQ(first.result, Triple_Buffer_Result::Success);
|
||||
EXPECT_TRUE(unique_roles(control.read_view()));
|
||||
|
||||
auto painting = control.try_acquire_role(Frame_Painting);
|
||||
ASSERT_EQ(painting.result, Triple_Buffer_Result::Success);
|
||||
auto blocked = control.try_swap_role(Frame_Middle, Frame_Painting, [](const Triple_Buffer_View&) {
|
||||
@@ -146,7 +133,6 @@ TEST(Renderive_Triple_Buffer, RoleSwapsAreTryOnlyAndDeterministic) {
|
||||
});
|
||||
EXPECT_EQ(blocked.result, Triple_Buffer_Result::Busy);
|
||||
control.unmark_use(painting.lease);
|
||||
|
||||
for (int i = 0; i < 1000000; ++i) {
|
||||
auto a = control.try_swap_role(Frame_Rendering, Frame_Middle, [](const Triple_Buffer_View&) {
|
||||
return true;
|
||||
@@ -159,7 +145,6 @@ TEST(Renderive_Triple_Buffer, RoleSwapsAreTryOnlyAndDeterministic) {
|
||||
ASSERT_TRUE(unique_roles(control.read_view()));
|
||||
}
|
||||
}
|
||||
|
||||
TEST(Renderive_Frame_Pipeline, PublishAndPaintUseMiddleVersionOnly) {
|
||||
Plot_Frame_Pipeline pipeline;
|
||||
auto lease = pipeline.try_acquire_render_frame();
|
||||
@@ -170,18 +155,15 @@ TEST(Renderive_Frame_Pipeline, PublishAndPaintUseMiddleVersionOnly) {
|
||||
lease.frame->metadata->render_end_ns = 20;
|
||||
lease.frame->version.store(1, std::memory_order_release);
|
||||
lease.reset();
|
||||
|
||||
auto published = pipeline.try_publish_rendered_frame(25);
|
||||
EXPECT_TRUE(pipeline.middle_frame_ready());
|
||||
EXPECT_TRUE(published.request_present);
|
||||
EXPECT_EQ(published.dropped_frame_record, nullptr);
|
||||
|
||||
auto painting = pipeline.try_acquire_paint_frame(30);
|
||||
EXPECT_TRUE(painting.has_new_frame);
|
||||
EXPECT_TRUE(static_cast<bool>(painting.lease));
|
||||
EXPECT_FALSE(pipeline.middle_frame_ready());
|
||||
}
|
||||
|
||||
TEST(Renderive_Frame_Pipeline, PaintAcquireFailureKeepsPendingRequest) {
|
||||
Plot_Frame_Pipeline pipeline;
|
||||
auto lease = pipeline.try_acquire_render_frame();
|
||||
@@ -192,11 +174,9 @@ TEST(Renderive_Frame_Pipeline, PaintAcquireFailureKeepsPendingRequest) {
|
||||
lease.frame->metadata->render_end_ns = 20;
|
||||
lease.frame->version.store(1, std::memory_order_release);
|
||||
lease.reset();
|
||||
|
||||
auto published = pipeline.try_publish_rendered_frame(25);
|
||||
ASSERT_TRUE(published.request_present);
|
||||
EXPECT_TRUE(pipeline.paint_request_pending.load(std::memory_order_acquire));
|
||||
|
||||
auto busy = pipeline.frame_control.try_acquire_role(Frame_Painting);
|
||||
ASSERT_EQ(busy.result, Triple_Buffer_Result::Success);
|
||||
auto failed = pipeline.try_acquire_paint_frame(30);
|
||||
@@ -204,18 +184,15 @@ TEST(Renderive_Frame_Pipeline, PaintAcquireFailureKeepsPendingRequest) {
|
||||
EXPECT_FALSE(failed.present_request_was_pending);
|
||||
EXPECT_TRUE(pipeline.paint_request_pending.load(std::memory_order_acquire));
|
||||
pipeline.frame_control.unmark_use(busy.lease);
|
||||
|
||||
auto consumed = pipeline.try_acquire_paint_frame(35);
|
||||
EXPECT_TRUE(consumed.present_request_was_pending);
|
||||
EXPECT_TRUE(static_cast<bool>(consumed.lease));
|
||||
}
|
||||
|
||||
TEST(Renderive_MPMCQueue, UsesTryOnlyBoundedCapacity) {
|
||||
rigtorp::MPMCQueue<int> queue(2);
|
||||
EXPECT_TRUE(queue.try_push(1));
|
||||
EXPECT_TRUE(queue.try_push(2));
|
||||
EXPECT_FALSE(queue.try_push(3));
|
||||
|
||||
int value = 0;
|
||||
EXPECT_TRUE(queue.try_pop(value));
|
||||
EXPECT_EQ(value, 1);
|
||||
@@ -223,7 +200,6 @@ TEST(Renderive_MPMCQueue, UsesTryOnlyBoundedCapacity) {
|
||||
EXPECT_EQ(value, 2);
|
||||
EXPECT_FALSE(queue.try_pop(value));
|
||||
}
|
||||
|
||||
TEST(Renderive_Frame_Feedback, SelectsRenderPaintAndUserSources) {
|
||||
Frame_Feedback_Controller controller;
|
||||
controller.set_max_render_fps(200.0);
|
||||
@@ -232,7 +208,6 @@ TEST(Renderive_Frame_Feedback, SelectsRenderPaintAndUserSources) {
|
||||
controller.on_render_completed(render_limited);
|
||||
}
|
||||
EXPECT_EQ(controller.state().source, Refresh_Limit_Source::Render);
|
||||
|
||||
controller.reset();
|
||||
controller.set_max_render_fps(200.0);
|
||||
for (int i = 0; i < 4; ++i) {
|
||||
@@ -244,7 +219,6 @@ TEST(Renderive_Frame_Feedback, SelectsRenderPaintAndUserSources) {
|
||||
controller.on_frame_presented(frame);
|
||||
}
|
||||
EXPECT_EQ(controller.state().source, Refresh_Limit_Source::Paint);
|
||||
|
||||
controller.reset();
|
||||
controller.set_max_render_fps(30.0);
|
||||
for (int i = 0; i < 4; ++i) {
|
||||
@@ -256,7 +230,6 @@ TEST(Renderive_Frame_Feedback, SelectsRenderPaintAndUserSources) {
|
||||
}
|
||||
EXPECT_EQ(controller.state().source, Refresh_Limit_Source::User);
|
||||
}
|
||||
|
||||
TEST(Renderive_Render_Executor, FrameAdmissionIsBounded) {
|
||||
Render_Executor executor(Render_Runtime_Config{1});
|
||||
std::atomic_bool entered{false};
|
||||
@@ -270,19 +243,19 @@ TEST(Renderive_Render_Executor, FrameAdmissionIsBounded) {
|
||||
Task([&]() {
|
||||
entered.store(true, std::memory_order_release);
|
||||
while (!release.load(std::memory_order_acquire))
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(1));
|
||||
}),
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(1));
|
||||
}),
|
||||
{},
|
||||
Task([&]() {
|
||||
completed.fetch_add(1, std::memory_order_acq_rel);
|
||||
}),
|
||||
}),
|
||||
first_stat,
|
||||
1,
|
||||
true
|
||||
}));
|
||||
}));
|
||||
ASSERT_TRUE(wait_until([&]() {
|
||||
return entered.load(std::memory_order_acquire);
|
||||
}));
|
||||
}));
|
||||
auto second_stat = std::make_shared<Frame_Worker_Stats>();
|
||||
EXPECT_TRUE(executor.try_submit(Render_Executor_Task{
|
||||
2,
|
||||
@@ -292,11 +265,11 @@ TEST(Renderive_Render_Executor, FrameAdmissionIsBounded) {
|
||||
{},
|
||||
Task([&]() {
|
||||
completed.fetch_add(1, std::memory_order_acq_rel);
|
||||
}),
|
||||
}),
|
||||
second_stat,
|
||||
1,
|
||||
true
|
||||
}));
|
||||
}));
|
||||
auto third_stat = std::make_shared<Frame_Worker_Stats>();
|
||||
EXPECT_TRUE(executor.try_submit(Render_Executor_Task{
|
||||
3,
|
||||
@@ -306,11 +279,11 @@ TEST(Renderive_Render_Executor, FrameAdmissionIsBounded) {
|
||||
{},
|
||||
Task([&]() {
|
||||
completed.fetch_add(1, std::memory_order_acq_rel);
|
||||
}),
|
||||
}),
|
||||
third_stat,
|
||||
1,
|
||||
true
|
||||
}));
|
||||
}));
|
||||
auto duplicate_stat = std::make_shared<Frame_Worker_Stats>();
|
||||
EXPECT_FALSE(executor.try_submit(Render_Executor_Task{
|
||||
2,
|
||||
@@ -322,7 +295,7 @@ TEST(Renderive_Render_Executor, FrameAdmissionIsBounded) {
|
||||
duplicate_stat,
|
||||
1,
|
||||
true
|
||||
}));
|
||||
}));
|
||||
auto overflow_stat = std::make_shared<Frame_Worker_Stats>();
|
||||
EXPECT_FALSE(executor.try_submit(Render_Executor_Task{
|
||||
4,
|
||||
@@ -334,66 +307,61 @@ TEST(Renderive_Render_Executor, FrameAdmissionIsBounded) {
|
||||
overflow_stat,
|
||||
1,
|
||||
true
|
||||
}));
|
||||
}));
|
||||
release.store(true, std::memory_order_release);
|
||||
ASSERT_TRUE(wait_until([&]() {
|
||||
return completed.load(std::memory_order_acquire) == 3;
|
||||
}));
|
||||
}));
|
||||
executor.shutdown();
|
||||
}
|
||||
|
||||
TEST(Renderive_Render_Executor, TaskflowTopologyRunsInFrameOrder) {
|
||||
Render_Executor executor(Render_Runtime_Config{2});
|
||||
std::atomic_int sequence{0};
|
||||
std::atomic_bool completed{false};
|
||||
auto stat = std::make_shared<Frame_Worker_Stats>();
|
||||
|
||||
ASSERT_TRUE(executor.try_submit(Render_Executor_Task{
|
||||
1,
|
||||
1,
|
||||
Render_Task_Kind::Frame,
|
||||
Task{},
|
||||
[&sequence](tf::Taskflow& taskflow, tf::Task entry, tf::Task exit) {
|
||||
auto prepare = taskflow.emplace([&sequence]() {
|
||||
int expected = 0;
|
||||
sequence.compare_exchange_strong(expected, 1, std::memory_order_acq_rel);
|
||||
auto prepare = taskflow.emplace([&sequence]() {
|
||||
int expected = 0;
|
||||
sequence.compare_exchange_strong(expected, 1, std::memory_order_acq_rel);
|
||||
});
|
||||
auto draw = taskflow.emplace([&sequence]() {
|
||||
int expected = 1;
|
||||
sequence.compare_exchange_strong(expected, 2, std::memory_order_acq_rel);
|
||||
auto draw = taskflow.emplace([&sequence]() {
|
||||
int expected = 1;
|
||||
sequence.compare_exchange_strong(expected, 2, std::memory_order_acq_rel);
|
||||
});
|
||||
auto finalize = taskflow.emplace([&sequence]() {
|
||||
int expected = 2;
|
||||
sequence.compare_exchange_strong(expected, 3, std::memory_order_acq_rel);
|
||||
auto finalize = taskflow.emplace([&sequence]() {
|
||||
int expected = 2;
|
||||
sequence.compare_exchange_strong(expected, 3, std::memory_order_acq_rel);
|
||||
});
|
||||
entry.precede(prepare);
|
||||
prepare.precede(draw);
|
||||
draw.precede(finalize);
|
||||
finalize.precede(exit);
|
||||
entry.precede(prepare);
|
||||
prepare.precede(draw);
|
||||
draw.precede(finalize);
|
||||
finalize.precede(exit);
|
||||
},
|
||||
Task([&completed]() {
|
||||
completed.store(true, std::memory_order_release);
|
||||
}),
|
||||
}),
|
||||
stat,
|
||||
3,
|
||||
true
|
||||
}));
|
||||
|
||||
}));
|
||||
ASSERT_TRUE(wait_until([&]() {
|
||||
return completed.load(std::memory_order_acquire);
|
||||
}));
|
||||
}));
|
||||
EXPECT_EQ(sequence.load(std::memory_order_acquire), 3);
|
||||
EXPECT_EQ(stat->task_count, 3u);
|
||||
executor.shutdown();
|
||||
}
|
||||
|
||||
TEST(Renderive_Render_Executor, QueuedFramesRunRoundRobinByPlot) {
|
||||
Render_Executor executor(Render_Runtime_Config{1});
|
||||
std::atomic_bool first_entered{false};
|
||||
std::atomic_bool release_first{false};
|
||||
std::atomic_int order_index{0};
|
||||
std::array<int, 3> order{};
|
||||
|
||||
ASSERT_TRUE(executor.try_submit(Render_Executor_Task{
|
||||
1,
|
||||
1,
|
||||
@@ -401,56 +369,53 @@ TEST(Renderive_Render_Executor, QueuedFramesRunRoundRobinByPlot) {
|
||||
Task([&]() {
|
||||
first_entered.store(true, std::memory_order_release);
|
||||
while (!release_first.load(std::memory_order_acquire))
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(1));
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(1));
|
||||
order[order_index.fetch_add(1, std::memory_order_acq_rel)] = 1;
|
||||
}),
|
||||
}),
|
||||
{},
|
||||
Task([]() {}),
|
||||
std::make_shared<Frame_Worker_Stats>(),
|
||||
1,
|
||||
true
|
||||
}));
|
||||
}));
|
||||
ASSERT_TRUE(wait_until([&]() {
|
||||
return first_entered.load(std::memory_order_acquire);
|
||||
}));
|
||||
|
||||
}));
|
||||
ASSERT_TRUE(executor.try_submit(Render_Executor_Task{
|
||||
2,
|
||||
1,
|
||||
Render_Task_Kind::Frame,
|
||||
Task([&]() {
|
||||
order[order_index.fetch_add(1, std::memory_order_acq_rel)] = 2;
|
||||
}),
|
||||
}),
|
||||
{},
|
||||
Task([]() {}),
|
||||
std::make_shared<Frame_Worker_Stats>(),
|
||||
1,
|
||||
true
|
||||
}));
|
||||
}));
|
||||
ASSERT_TRUE(executor.try_submit(Render_Executor_Task{
|
||||
3,
|
||||
1,
|
||||
Render_Task_Kind::Frame,
|
||||
Task([&]() {
|
||||
order[order_index.fetch_add(1, std::memory_order_acq_rel)] = 3;
|
||||
}),
|
||||
}),
|
||||
{},
|
||||
Task([]() {}),
|
||||
std::make_shared<Frame_Worker_Stats>(),
|
||||
1,
|
||||
true
|
||||
}));
|
||||
|
||||
}));
|
||||
release_first.store(true, std::memory_order_release);
|
||||
ASSERT_TRUE(wait_until([&]() {
|
||||
return order_index.load(std::memory_order_acquire) == 3;
|
||||
}));
|
||||
}));
|
||||
EXPECT_EQ(order[0], 1);
|
||||
EXPECT_EQ(order[1], 2);
|
||||
EXPECT_EQ(order[2], 3);
|
||||
executor.shutdown();
|
||||
}
|
||||
|
||||
TEST(Renderive_Performance_Shower, ToggleDoesNotReenterRenderableTreeUnsafely) {
|
||||
Plot_Core plot(std::make_unique<Latency_Eager_Refresh_Strategy>());
|
||||
plot.init();
|
||||
@@ -464,7 +429,6 @@ TEST(Renderive_Performance_Shower, ToggleDoesNotReenterRenderableTreeUnsafely) {
|
||||
EXPECT_FALSE(performance_overlay_enabled(plot));
|
||||
EXPECT_TRUE(root->children_snapshot().empty());
|
||||
}
|
||||
|
||||
TEST(Renderive_Performance_Shower, TogglePreservesAttachedOptions) {
|
||||
Plot_Core plot(std::make_unique<Latency_Eager_Refresh_Strategy>());
|
||||
plot.init();
|
||||
@@ -472,7 +436,6 @@ TEST(Renderive_Performance_Shower, TogglePreservesAttachedOptions) {
|
||||
options.font.size = 17.0;
|
||||
options.background = Color{10, 20, 30, 255};
|
||||
options.foreground = Color{240, 240, 240, 255};
|
||||
|
||||
auto original = attach_performance_overlay(plot, options);
|
||||
ASSERT_TRUE(original);
|
||||
set_performance_overlay_enabled(plot, true);
|
||||
@@ -480,21 +443,18 @@ TEST(Renderive_Performance_Shower, TogglePreservesAttachedOptions) {
|
||||
set_performance_overlay_enabled(plot, false);
|
||||
EXPECT_FALSE(performance_overlay_enabled(plot));
|
||||
set_performance_overlay_enabled(plot, true);
|
||||
|
||||
auto toggled = performance_overlay(plot);
|
||||
ASSERT_EQ(toggled, original);
|
||||
EXPECT_EQ(toggled->options().font.size, options.font.size);
|
||||
EXPECT_EQ(toggled->options().background, options.background);
|
||||
EXPECT_EQ(toggled->options().foreground, options.foreground);
|
||||
}
|
||||
|
||||
TEST(Renderive_Hover_Info, DefaultTextPenContrastsWithDefaultBackground) {
|
||||
TEST(Renderive_Hover_Tooltip, DefaultTextPenContrastsWithDefaultBackground) {
|
||||
Hover_Tooltip_State state;
|
||||
EXPECT_EQ(state.tooltip_background_brush.color, Color::white());
|
||||
EXPECT_EQ(state.tooltip_text_pen.color, Color::black());
|
||||
}
|
||||
|
||||
TEST(Renderive_Performance_Shower, WorkerConsumesSharedFrameRecord) {
|
||||
TEST(Renderive_Performance_Overlay, WorkerConsumesSharedFrameRecord) {
|
||||
auto state = std::make_shared<Performance_Worker_State>();
|
||||
state->enabled.store(true, std::memory_order_release);
|
||||
auto frame = std::make_shared<Frame_Lifecycle_Record>();
|
||||
@@ -511,7 +471,6 @@ TEST(Renderive_Performance_Shower, WorkerConsumesSharedFrameRecord) {
|
||||
frame->outcome = Frame_Outcome::Presented;
|
||||
Performance_Frame_Record record = frame;
|
||||
ASSERT_TRUE(state->metric_queue.try_push(record));
|
||||
|
||||
schedule_performance_metrics_worker(state);
|
||||
auto deadline = std::chrono::steady_clock::now() + std::chrono::seconds(2);
|
||||
std::shared_ptr<const Performance_Display_Snapshot> snapshot;
|
||||
@@ -522,13 +481,11 @@ TEST(Renderive_Performance_Shower, WorkerConsumesSharedFrameRecord) {
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(1));
|
||||
}
|
||||
state->cancelled.store(true, std::memory_order_release);
|
||||
|
||||
ASSERT_TRUE(snapshot);
|
||||
EXPECT_FALSE(snapshot->lines.empty());
|
||||
EXPECT_TRUE(state->aggregate.has_frame);
|
||||
EXPECT_EQ(state->aggregate.latest.frame_id, 7);
|
||||
}
|
||||
|
||||
TEST(Renderive_Performance_Shower, OptionsAffectSnapshotRendering) {
|
||||
Performance_Overlay shower;
|
||||
Performance_Overlay_Options options;
|
||||
@@ -539,11 +496,10 @@ TEST(Renderive_Performance_Shower, OptionsAffectSnapshotRendering) {
|
||||
shower.set_enabled(true);
|
||||
shower.set_viewport(Size{260, 120}, 1);
|
||||
shower.collect_frame(performance_frame(1, 260, 120));
|
||||
|
||||
ASSERT_TRUE(wait_until([&]() {
|
||||
auto snapshot = shower.display_snapshot();
|
||||
return snapshot && !snapshot->image.empty();
|
||||
}));
|
||||
}));
|
||||
auto snapshot = shower.display_snapshot();
|
||||
ASSERT_TRUE(snapshot);
|
||||
ASSERT_FALSE(snapshot->image.empty());
|
||||
@@ -551,35 +507,31 @@ TEST(Renderive_Performance_Shower, OptionsAffectSnapshotRendering) {
|
||||
EXPECT_GT(snapshot->line_height, 12.0);
|
||||
EXPECT_EQ(premultiplied_to_color(snapshot->image.row(1)[1]), options.background);
|
||||
}
|
||||
|
||||
TEST(Renderive_Performance_Shower, OutcomeWidthIsSeededWithLongestEnumText) {
|
||||
auto state = std::make_shared<Performance_Worker_State>();
|
||||
state->enabled.store(true, std::memory_order_release);
|
||||
auto outcome_width = [&]() {
|
||||
return state->field_layout_states[static_cast<std::size_t>(Performance_Metric_Id::Outcome)].max_value_width;
|
||||
};
|
||||
|
||||
auto presented = performance_frame(1, 260, 120);
|
||||
presented->outcome = Frame_Outcome::Presented;
|
||||
ASSERT_TRUE(state->metric_queue.try_push(Performance_Frame_Record{presented}));
|
||||
schedule_performance_metrics_worker(state);
|
||||
ASSERT_TRUE(wait_until([&]() {
|
||||
return state->aggregate.latest.frame_id == 1 && !state->worker_active.load(std::memory_order_acquire);
|
||||
}));
|
||||
}));
|
||||
double seeded_width = outcome_width();
|
||||
EXPECT_GT(seeded_width, 0.0);
|
||||
|
||||
auto dropped = performance_frame(2, 260, 120);
|
||||
dropped->outcome = Frame_Outcome::Worker_Budget_Dropped;
|
||||
ASSERT_TRUE(state->metric_queue.try_push(Performance_Frame_Record{dropped}));
|
||||
schedule_performance_metrics_worker(state);
|
||||
ASSERT_TRUE(wait_until([&]() {
|
||||
return state->aggregate.latest.frame_id == 2 && !state->worker_active.load(std::memory_order_acquire);
|
||||
}));
|
||||
}));
|
||||
EXPECT_EQ(outcome_width(), seeded_width);
|
||||
state->cancelled.store(true, std::memory_order_release);
|
||||
}
|
||||
|
||||
TEST(Renderive_Performance_Shower, NumericFieldWidthIsMonotonicAcrossResetAndResize) {
|
||||
auto state = std::make_shared<Performance_Worker_State>();
|
||||
state->enabled.store(true, std::memory_order_release);
|
||||
@@ -595,7 +547,7 @@ TEST(Renderive_Performance_Shower, NumericFieldWidthIsMonotonicAcrossResetAndRes
|
||||
ASSERT_TRUE(wait_until([&]() {
|
||||
auto snapshot = state->published_snapshot.load(std::memory_order_acquire);
|
||||
return snapshot && state->aggregate.latest.frame_id == frame_id && !state->worker_active.load(std::memory_order_acquire);
|
||||
}));
|
||||
}));
|
||||
double current = frame_width();
|
||||
EXPECT_GE(current, previous);
|
||||
previous = current;
|
||||
@@ -604,14 +556,12 @@ TEST(Renderive_Performance_Shower, NumericFieldWidthIsMonotonicAcrossResetAndRes
|
||||
if (frame_id == 2 || frame_id == 8)
|
||||
EXPECT_EQ(current, wide);
|
||||
}
|
||||
|
||||
state->reset_requested.store(true, std::memory_order_release);
|
||||
schedule_performance_metrics_worker(state);
|
||||
ASSERT_TRUE(wait_until([&]() {
|
||||
return !state->worker_active.load(std::memory_order_acquire);
|
||||
}));
|
||||
}));
|
||||
EXPECT_EQ(frame_width(), wide);
|
||||
|
||||
state->viewport_width.store(180, std::memory_order_release);
|
||||
state->viewport_height.store(90, std::memory_order_release);
|
||||
state->viewport_version.store(2, std::memory_order_release);
|
||||
@@ -620,11 +570,10 @@ TEST(Renderive_Performance_Shower, NumericFieldWidthIsMonotonicAcrossResetAndRes
|
||||
ASSERT_TRUE(wait_until([&]() {
|
||||
auto snapshot = state->published_snapshot.load(std::memory_order_acquire);
|
||||
return snapshot && snapshot->viewport_version == 2 && !state->worker_active.load(std::memory_order_acquire);
|
||||
}));
|
||||
}));
|
||||
EXPECT_EQ(frame_width(), wide);
|
||||
state->cancelled.store(true, std::memory_order_release);
|
||||
}
|
||||
|
||||
TEST(Renderive_Performance_Shower, ScrollCommandsUpdateOffsetAndClamp) {
|
||||
Performance_Overlay shower;
|
||||
shower.set_enabled(true);
|
||||
@@ -634,16 +583,14 @@ TEST(Renderive_Performance_Shower, ScrollCommandsUpdateOffsetAndClamp) {
|
||||
ASSERT_TRUE(wait_until([&]() {
|
||||
auto snapshot = shower.display_snapshot();
|
||||
return snapshot && snapshot->max_scroll_offset > 0.0;
|
||||
}));
|
||||
|
||||
}));
|
||||
auto snapshot = shower.display_snapshot();
|
||||
ASSERT_TRUE(snapshot);
|
||||
EXPECT_TRUE(shower.handle_wheel(PointF{5.0, 5.0}, -120.0, 0.0));
|
||||
ASSERT_TRUE(wait_until([&]() {
|
||||
auto next = shower.display_snapshot();
|
||||
return next && next->scroll_offset > snapshot->scroll_offset;
|
||||
}));
|
||||
|
||||
}));
|
||||
snapshot = shower.display_snapshot();
|
||||
ASSERT_TRUE(snapshot);
|
||||
PointF page_down{snapshot->scroll_track_rect.x + 1.0, snapshot->scroll_thumb_rect.bottom() + 1.0};
|
||||
@@ -651,8 +598,7 @@ TEST(Renderive_Performance_Shower, ScrollCommandsUpdateOffsetAndClamp) {
|
||||
ASSERT_TRUE(wait_until([&]() {
|
||||
auto next = shower.display_snapshot();
|
||||
return next && next->scroll_offset > snapshot->scroll_offset;
|
||||
}));
|
||||
|
||||
}));
|
||||
snapshot = shower.display_snapshot();
|
||||
ASSERT_TRUE(snapshot);
|
||||
PointF thumb_center{snapshot->scroll_thumb_rect.x + 1.0, snapshot->scroll_thumb_rect.y + snapshot->scroll_thumb_rect.height * 0.5};
|
||||
@@ -662,9 +608,8 @@ TEST(Renderive_Performance_Shower, ScrollCommandsUpdateOffsetAndClamp) {
|
||||
ASSERT_TRUE(wait_until([&]() {
|
||||
auto next = shower.display_snapshot();
|
||||
return next && next->max_scroll_offset > 0.0 && next->scroll_offset > next->max_scroll_offset * 0.8;
|
||||
}));
|
||||
}));
|
||||
}
|
||||
|
||||
TEST(Renderive_Performance_Shower, Utf8WrappingKeepsValidText) {
|
||||
Performance_Overlay shower;
|
||||
shower.set_enabled(true);
|
||||
@@ -680,15 +625,13 @@ TEST(Renderive_Performance_Shower, Utf8WrappingKeepsValidText) {
|
||||
ASSERT_TRUE(wait_until([&]() {
|
||||
auto snapshot = shower.display_snapshot();
|
||||
return snapshot && !snapshot->lines.empty();
|
||||
}));
|
||||
}));
|
||||
auto snapshot = shower.display_snapshot();
|
||||
ASSERT_TRUE(snapshot);
|
||||
for (const auto& line : snapshot->lines)
|
||||
EXPECT_TRUE(valid_utf8(line)) << line;
|
||||
}
|
||||
|
||||
} // namespace renderive
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
testing::InitGoogleTest(&argc, argv);
|
||||
return RUN_ALL_TESTS();
|
||||
|
||||
Reference in New Issue
Block a user