1111 lines
57 KiB
C++
1111 lines
57 KiB
C++
#include "../src/export.h"
|
|
#include "render_2D/plottable/Curve_Sampling.h"
|
|
#include "render_2D/render/Blend2D_Cache.h"
|
|
#include <gtest/gtest.h>
|
|
#include <algorithm>
|
|
#include <array>
|
|
#include <chrono>
|
|
#include <cstddef>
|
|
#include <functional>
|
|
#include <future>
|
|
#include <memory_resource>
|
|
#include <set>
|
|
#include <thread>
|
|
#include <tuple>
|
|
#include <utility>
|
|
#include <vector>
|
|
namespace renderive {
|
|
namespace {
|
|
template <class Scene, class Build>
|
|
decltype(auto) build_initial(Scene& scene, Build&& build) {
|
|
auto attach = scene.attach_builder();
|
|
return std::invoke(std::forward<Build>(build), attach);
|
|
}
|
|
template <class Scene, class Edit>
|
|
void apply_runtime_edit(Scene& scene, Edit&& edit) {
|
|
std::promise<void> completed;
|
|
auto future = completed.get_future();
|
|
scene.edit_renderables(
|
|
[edit = std::forward<Edit>(edit), &completed](auto& editor) mutable {
|
|
edit(editor);
|
|
completed.set_value();
|
|
});
|
|
future.wait();
|
|
}
|
|
template <class Axis_Type>
|
|
concept Legacy_Axis_Property_Api = requires(Axis_Type& axis) {
|
|
axis.x();
|
|
axis.set_x(0);
|
|
axis.coord_range();
|
|
axis.set_coord_range(Range{});
|
|
};
|
|
|
|
template <class Axis_Type>
|
|
concept Legacy_Time_Axis_Property_Api = requires(Axis_Type& axis) {
|
|
axis.visible_time_point_count();
|
|
axis.set_visible_time_point_count(1);
|
|
axis.time_format();
|
|
axis.set_time_format({});
|
|
};
|
|
|
|
static_assert(!Legacy_Axis_Property_Api<Axis>);
|
|
static_assert(!Legacy_Axis_Property_Api<Time_Axis>);
|
|
static_assert(!Legacy_Time_Axis_Property_Api<Time_Axis>);
|
|
|
|
TEST(Renderive_Core2, RootIdentityAndRefreshDiagnosticsComeFromKernelState) {
|
|
Scene2D plot;
|
|
plot.init();
|
|
const auto root = plot.root_renderable();
|
|
ASSERT_TRUE(root);
|
|
root->set_object_name("renamed-root");
|
|
EXPECT_EQ(plot.root_renderable(), root);
|
|
plot.set_viewport_size({32, 24});
|
|
plot.activate_view();
|
|
plot.set_max_render_fps(144.0);
|
|
ASSERT_TRUE(plot.render_frame());
|
|
const auto diagnostics = plot.diagnostics();
|
|
EXPECT_DOUBLE_EQ(diagnostics.refresh.frequency_hz, 144.0);
|
|
EXPECT_EQ(diagnostics.refresh.frame_count, 1u);
|
|
}
|
|
TEST(Renderive_Core2, EveryCurveInterpolationModeHasDistinctSamplingSemantics) {
|
|
const std::array<double, 2> edge_values{0.0, 10.0};
|
|
const Range domain{0.0, 1.0};
|
|
const auto nearest = detail::curve_sampling::interpolate(
|
|
edge_values, domain, Line_Interpolation_Mode::Nearest_Sample);
|
|
const auto linear = detail::curve_sampling::interpolate(
|
|
edge_values, domain, Line_Interpolation_Mode::Linear_Value);
|
|
const auto power = detail::curve_sampling::interpolate(
|
|
edge_values, domain, Line_Interpolation_Mode::Linear_Power_Domain);
|
|
const auto step_left = detail::curve_sampling::interpolate(
|
|
edge_values, domain, Line_Interpolation_Mode::Step_Left);
|
|
const auto step_right = detail::curve_sampling::interpolate(
|
|
edge_values, domain, Line_Interpolation_Mode::Step_Right);
|
|
EXPECT_EQ(nearest.size(), 4u);
|
|
EXPECT_EQ(linear.size(), 2u);
|
|
ASSERT_EQ(power.size(), 5u);
|
|
EXPECT_NEAR(power[2].value, 7.4036269, 1e-6);
|
|
ASSERT_EQ(step_left.size(), 3u);
|
|
ASSERT_EQ(step_right.size(), 3u);
|
|
EXPECT_DOUBLE_EQ(step_left[1].coordinate, 1.0);
|
|
EXPECT_DOUBLE_EQ(step_left[1].value, 0.0);
|
|
EXPECT_DOUBLE_EQ(step_right[1].coordinate, 0.0);
|
|
EXPECT_DOUBLE_EQ(step_right[1].value, 10.0);
|
|
const std::array<double, 4> curved_values{0.0, 10.0, 0.0, 10.0};
|
|
const auto cubic = detail::curve_sampling::interpolate(
|
|
curved_values, {0.0, 3.0}, Line_Interpolation_Mode::Cubic_Value);
|
|
ASSERT_EQ(cubic.size(), 13u);
|
|
EXPECT_NEAR(cubic[2].value, 5.625, 1e-9);
|
|
const std::array<double, 11> visible_values{};
|
|
const Axis_Transform visible_x{{4.0, 6.0}, 0.0, 100.0};
|
|
const Axis_Transform visible_y{{0.0, 1.0}, 0.0, 100.0, Orientation::Vertical};
|
|
const auto clipped = detail::curve_points(
|
|
visible_values, {0.0, 10.0}, visible_x, visible_y, true,
|
|
Line_Interpolation_Mode::Linear_Value);
|
|
EXPECT_LT(clipped.size(), visible_values.size());
|
|
ASSERT_GE(clipped.size(), 2u);
|
|
EXPECT_LE(clipped.front().x, 0.0);
|
|
EXPECT_GE(clipped.back().x, 100.0);
|
|
}
|
|
TEST(Renderive_Core2, PainterScopedClipRestoresPreviousClip) {
|
|
detail::Blend2D_Color_Cache cache;
|
|
{
|
|
detail::Painter painter(cache, {8, 2});
|
|
{
|
|
const auto clip = painter.scoped_clip({0.0, 0.0, 4.0, 2.0});
|
|
painter.rect({0.0, 0.0, 8.0, 2.0}, Pen{.style = Line_Style::None},
|
|
Brush{Color::red(), Brush_Style::Solid});
|
|
}
|
|
{
|
|
const auto clip = painter.scoped_clip({4.0, 0.0, 4.0, 2.0});
|
|
painter.rect({0.0, 0.0, 8.0, 2.0}, Pen{.style = Line_Style::None},
|
|
Brush{Color::green(), Brush_Style::Solid});
|
|
}
|
|
}
|
|
const Image_View view = cache.view();
|
|
ASSERT_EQ(view.width, 8);
|
|
ASSERT_EQ(view.height, 2);
|
|
const auto* row = reinterpret_cast<const Pixel*>(view.data);
|
|
EXPECT_EQ(row[1], pack_rgba(255, 0, 0));
|
|
EXPECT_EQ(row[6], pack_rgba(0, 255, 0));
|
|
}
|
|
TEST(Renderive_Core2, BicubicHeatmapUsesRealCubicResampling) {
|
|
const std::array<Pixel, 16> source{
|
|
pack_rgba(255, 0, 0), pack_rgba(0, 0, 0), pack_rgba(255, 255, 255), pack_rgba(0, 0, 255),
|
|
pack_rgba(0, 255, 0), pack_rgba(255, 255, 255), pack_rgba(0, 0, 0), pack_rgba(255, 0, 0),
|
|
pack_rgba(0, 0, 255), pack_rgba(0, 0, 0), pack_rgba(255, 255, 255), pack_rgba(0, 255, 0),
|
|
pack_rgba(255, 255, 255), pack_rgba(255, 0, 0), pack_rgba(0, 255, 0), pack_rgba(0, 0, 0)
|
|
};
|
|
const auto render = [&source](Image_Interpolation_Mode interpolation) {
|
|
detail::Blend2D_Color_Cache cache;
|
|
{
|
|
detail::Painter painter(cache, {11, 11});
|
|
painter.heatmap({0.0, 0.0, 11.0, 11.0}, 4, 4, source, interpolation);
|
|
}
|
|
const Image_View view = cache.view();
|
|
std::vector<Pixel> pixels;
|
|
pixels.reserve(static_cast<std::size_t>(view.width) * view.height);
|
|
for (int y = 0; y < view.height; ++y) {
|
|
const auto* row = reinterpret_cast<const Pixel*>(
|
|
view.data + static_cast<std::ptrdiff_t>(y) * view.stride);
|
|
pixels.insert(pixels.end(), row, row + view.width);
|
|
}
|
|
return pixels;
|
|
};
|
|
const auto bilinear = render(Image_Interpolation_Mode::Bilinear);
|
|
const auto bicubic = render(Image_Interpolation_Mode::Bicubic);
|
|
EXPECT_EQ(bilinear.size(), bicubic.size());
|
|
EXPECT_NE(bilinear, bicubic);
|
|
}
|
|
TEST(Renderive_Core2, KernelSceneRendersBusinessObjectsIntoBlend2DFrame) {
|
|
Scene2D plot;
|
|
plot.init();
|
|
plot.set_viewport_size({320, 180});
|
|
plot.activate_view();
|
|
const auto root = plot.root_renderable();
|
|
ASSERT_TRUE(root);
|
|
auto [frequency_axis, power_axis, spectrum] = build_initial(plot, [&](auto& attach) {
|
|
auto frequency = Frequency_Axis::Builder(root, Orientation::Horizontal, &attach)
|
|
.set_x(32)
|
|
.set_y(150)
|
|
.set_pixel_length(270)
|
|
.set_coord_range({88.0, 108.0})
|
|
.build();
|
|
auto power = Axis::Builder(root, Orientation::Vertical, &attach)
|
|
.set_x(32)
|
|
.set_y(8)
|
|
.set_pixel_length(142)
|
|
.set_coord_range({-120.0, 0.0})
|
|
.build();
|
|
auto value = Spectrum::Builder{&attach}
|
|
.set<&Spectrum::Properties::frequency_range>(Range{88.0, 108.0})
|
|
.set<&Spectrum::Properties::frequency_point_size>(64)
|
|
.set<&Spectrum::Properties::max_hold_visible>(true)
|
|
.build(root, frequency, power);
|
|
return std::tuple{frequency, power, value};
|
|
});
|
|
frequency_axis->set<&Axis_Base_Properties::locale>(Number_Locale{','});
|
|
frequency_axis->set<&Axis_Base_Properties::label_rotation_degrees>(15);
|
|
EXPECT_EQ(frequency_axis->tick_label(88.5), "88,5 Hz");
|
|
frequency_axis->set<&Axis_Properties::precision>(4);
|
|
EXPECT_EQ(frequency_axis->tick_label(1'234'567.0), "1,2346 MHz");
|
|
std::vector<double> samples(64);
|
|
for (std::size_t index = 0; index < samples.size(); ++index)
|
|
samples[index] = -100.0 + static_cast<double>(index % 24) * 3.0;
|
|
spectrum->update_samples(samples);
|
|
ASSERT_TRUE(plot.render_frame());
|
|
bool saw_frame = false;
|
|
bool saw_drawn_pixel = false;
|
|
plot.with_frame([&](Image_View view) {
|
|
saw_frame = !view.empty() && view.width == 320 && view.height == 180;
|
|
if (!saw_frame)
|
|
return;
|
|
for (int y = 0; y < view.height && !saw_drawn_pixel; ++y) {
|
|
const auto* row = view.data + static_cast<std::ptrdiff_t>(y) * view.stride;
|
|
for (int x = 0; x < view.width * static_cast<int>(sizeof(Pixel)); ++x) {
|
|
if (row[x] != std::byte{}) {
|
|
saw_drawn_pixel = true;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
});
|
|
EXPECT_TRUE(saw_frame);
|
|
EXPECT_TRUE(saw_drawn_pixel);
|
|
EXPECT_EQ(plot.diagnostics().refresh.frame_count, 1u);
|
|
EXPECT_FALSE(plot.render_frame());
|
|
samples.front() += 1.0;
|
|
spectrum->update_samples(samples);
|
|
EXPECT_TRUE(plot.render_frame(true));
|
|
frequency_axis->set<&Axis_Properties::wheel>(true);
|
|
const Range before_zoom = frequency_axis->get<&Axis_Properties::coordinates>();
|
|
Wheel_Event wheel;
|
|
wheel.position = {160.0, 150.0};
|
|
wheel.angle_delta_y = 120.0;
|
|
plot.dispatch_event(wheel);
|
|
EXPECT_TRUE(wheel.is_accepted());
|
|
EXPECT_LT(frequency_axis->get<&Axis_Properties::coordinates>().size(), before_zoom.size());
|
|
EXPECT_TRUE(plot.render_frame());
|
|
apply_runtime_edit(plot, [root, frequency_axis, power_axis, spectrum](auto& editor) {
|
|
editor.set_display_parent(frequency_axis, root);
|
|
editor.set_display_parent(power_axis, root);
|
|
editor.clear_display_parent(spectrum);
|
|
editor.clear_dependency_parent(spectrum);
|
|
editor.detach(spectrum);
|
|
});
|
|
EXPECT_TRUE(plot.render_frame(true));
|
|
EXPECT_FALSE(plot.render_frame());
|
|
}
|
|
TEST(Renderive_Core2, StandaloneRenderableBuilderJoinsSceneOnlyInsideRuntimeEditor) {
|
|
Scene2D plot;
|
|
plot.init();
|
|
const auto root = plot.root_renderable();
|
|
const auto [frequency, power] = build_initial(plot, [&](auto& attach) {
|
|
return std::pair{
|
|
Frequency_Axis::Builder(root, Orientation::Horizontal, &attach).build(),
|
|
Axis::Builder(root, Orientation::Vertical, &attach).build()
|
|
};
|
|
});
|
|
const auto spectrum = Spectrum::Builder{}.build(root, frequency, power);
|
|
ASSERT_TRUE(spectrum);
|
|
EXPECT_TRUE(std::ranges::none_of(
|
|
plot.topology_snapshot().renderables,
|
|
[&](const auto& renderable) {
|
|
return renderable.get() == spectrum.get();
|
|
}));
|
|
apply_runtime_edit(plot, [root, frequency, power, spectrum](auto& editor) {
|
|
editor.attach(spectrum);
|
|
editor.add_display_parent(spectrum, root);
|
|
editor.add_dependency_parent(spectrum, root);
|
|
editor.add_dependency_parent(spectrum, frequency);
|
|
editor.add_dependency_parent(spectrum, power);
|
|
editor.add_display_parent(frequency, spectrum);
|
|
editor.add_display_parent(power, spectrum);
|
|
});
|
|
EXPECT_TRUE(std::ranges::any_of(
|
|
plot.topology_snapshot().renderables,
|
|
[&](const auto& renderable) {
|
|
return renderable.get() == spectrum.get();
|
|
}));
|
|
}
|
|
TEST(Renderive_Core2, TimeAxisUsesOneFontStateAndFormatsConfiguredLabels) {
|
|
Scene2D plot;
|
|
plot.init();
|
|
const auto root = plot.root_renderable();
|
|
auto axis = build_initial(plot, [&](auto& attach) {
|
|
return Time_Axis::Builder(root, Orientation::Horizontal, &attach)
|
|
.set_time_format("hh-mm-ss.zzz")
|
|
.set_font(Font{18.0, 700, true})
|
|
.set_tick_label_spacing_px(17)
|
|
.build();
|
|
});
|
|
ASSERT_TRUE(axis);
|
|
const int tick = axis->append_time(Time_Of_Day{3'723'045});
|
|
EXPECT_EQ(axis->tick_label(tick), "01-02-03.045");
|
|
EXPECT_EQ(axis->get<&Axis_Base_Properties::unit_text_font>(), (Font{18.0, 700, true}));
|
|
EXPECT_EQ(axis->get<&Time_Axis_Properties::tick_label_spacing_px>(), 17);
|
|
}
|
|
TEST(Renderive_Core2, PerformanceOverlayConsumesKernelFrameDiagnostics) {
|
|
Scene2D plot;
|
|
plot.init();
|
|
plot.set_viewport_size({160, 90});
|
|
plot.activate_view();
|
|
plot.set_max_render_fps(120.0);
|
|
Performance_Overlay_Options options;
|
|
options.log.enabled = false;
|
|
const auto overlay = attach_performance_overlay(plot, options);
|
|
ASSERT_TRUE(overlay);
|
|
set_performance_plot_name(plot, "integration");
|
|
set_performance_overlay_enabled(plot, true);
|
|
ASSERT_TRUE(plot.render_frame(true));
|
|
const auto snapshot = overlay->display_snapshot();
|
|
ASSERT_TRUE(snapshot);
|
|
EXPECT_EQ(snapshot->plot_name, "integration");
|
|
EXPECT_EQ(snapshot->viewport_size, (Size{160, 90}));
|
|
EXPECT_FALSE(snapshot->lines.empty());
|
|
EXPECT_EQ(snapshot->frame_count, 1u);
|
|
EXPECT_DOUBLE_EQ(plot.max_render_fps(), 120.0);
|
|
}
|
|
TEST(Renderive_Core2, AllFrameControlModesUseKernelStrategiesAndExposeObservers) {
|
|
Manual_Scene2D manual;
|
|
manual.init();
|
|
manual.set_viewport_size({64, 40});
|
|
EXPECT_EQ(manual.frame_control_mode(), Frame_Control_Mode::Manual);
|
|
ASSERT_TRUE(manual.prepare_frame());
|
|
auto manual_observer = manual.frame_observer_snapshot();
|
|
EXPECT_EQ(manual_observer.last_event, "prepared");
|
|
EXPECT_EQ(manual_observer.pending_frame_count, 1u);
|
|
ASSERT_TRUE(manual.refresh_manual_frame());
|
|
ASSERT_TRUE(manual.render_prepared_frame());
|
|
manual_observer = manual.frame_observer_snapshot();
|
|
EXPECT_EQ(manual_observer.last_event, "rendered");
|
|
EXPECT_EQ(manual_observer.consumed_frame_count, 1u);
|
|
Scene2D low_latency;
|
|
low_latency.init();
|
|
low_latency.set_viewport_size({64, 40});
|
|
low_latency.activate_view();
|
|
ASSERT_TRUE(low_latency.render_frame(true));
|
|
const auto low_observer = low_latency.frame_observer_snapshot();
|
|
EXPECT_EQ(low_latency.frame_control_mode(), Frame_Control_Mode::Low_Latency);
|
|
EXPECT_EQ(low_observer.last_event, "lifecycle_completed");
|
|
EXPECT_EQ(low_observer.consumed_frame_count, 1u);
|
|
Playback_Scene2D playback;
|
|
playback.init();
|
|
playback.set_viewport_size({64, 40});
|
|
ASSERT_TRUE(playback.prepare_frame());
|
|
ASSERT_TRUE(playback.prepare_frame());
|
|
ASSERT_TRUE(playback.prepare_frame());
|
|
auto playback_observer = playback.frame_observer_snapshot();
|
|
EXPECT_EQ(playback.frame_control_mode(), Frame_Control_Mode::Playback);
|
|
EXPECT_EQ(playback_observer.last_event, "enqueued");
|
|
EXPECT_EQ(playback_observer.pending_frame_count, 3u);
|
|
ASSERT_TRUE(playback.render_prepared_frame());
|
|
playback_observer = playback.frame_observer_snapshot();
|
|
EXPECT_EQ(playback_observer.last_event, "rendered");
|
|
EXPECT_EQ(playback_observer.pending_frame_count, 2u);
|
|
EXPECT_EQ(playback_observer.consumed_frame_count, 1u);
|
|
}
|
|
TEST(Renderive_Core2, RetainedAxisAndTimeAxisApisRoundTripWithoutWebAdapters) {
|
|
Scene2D plot;
|
|
plot.init();
|
|
const auto root = plot.root_renderable();
|
|
ASSERT_TRUE(root);
|
|
const auto axis = build_initial(plot, [&](auto& attach) {
|
|
return Axis::Builder(root, Orientation::Vertical, &attach)
|
|
.set_x(31)
|
|
.set_y(17)
|
|
.set_pixel_length(240)
|
|
.set_tick_length(13)
|
|
.set_sub_tick_length(7)
|
|
.set_color({10, 20, 30, 255})
|
|
.set_unit_text("dB")
|
|
.set_unit_text_font({15.0, 650, true})
|
|
.set_unit_text_pen({Color{40, 50, 60, 255}, 2.0})
|
|
.set_unit_text_background_brush({Color{3, 4, 5, 255}, Brush_Style::Solid})
|
|
.set_label_rotation_degrees(27)
|
|
.set_coord_range({-120.0, -20.0})
|
|
.set_label_precision(4)
|
|
.set_use_wheel(true)
|
|
.set_use_drag(true)
|
|
.build();
|
|
});
|
|
ASSERT_TRUE(axis);
|
|
EXPECT_EQ(axis->get<&Axis_Base_Properties::x>(), 31);
|
|
EXPECT_EQ(axis->get<&Axis_Base_Properties::y>(), 17);
|
|
EXPECT_EQ(axis->get<&Axis_Base_Properties::orientation>(), Orientation::Vertical);
|
|
EXPECT_EQ(axis->get<&Axis_Base_Properties::pixel_length>(), 240U);
|
|
EXPECT_EQ(axis->get<&Axis_Base_Properties::tick_length>(), 13);
|
|
EXPECT_EQ(axis->get<&Axis_Base_Properties::sub_tick_length>(), 7);
|
|
EXPECT_EQ(axis->get<&Axis_Base_Properties::color>(), (Color{10, 20, 30, 255}));
|
|
EXPECT_EQ(axis->get<&Axis_Base_Properties::unit_text>(), "dB");
|
|
EXPECT_EQ(axis->get<&Axis_Base_Properties::unit_text_font>(), (Font{15.0, 650, true}));
|
|
EXPECT_EQ(axis->get<&Axis_Base_Properties::unit_text_pen>(), (Pen{Color{40, 50, 60, 255}, 2.0}));
|
|
EXPECT_EQ(axis->get<&Axis_Base_Properties::unit_text_background_brush>(),
|
|
(Brush{Color{3, 4, 5, 255}, Brush_Style::Solid}));
|
|
EXPECT_EQ(axis->get<&Axis_Base_Properties::label_rotation_degrees>(), 27);
|
|
EXPECT_EQ(axis->get<&Axis_Properties::coordinates>(), (Range{-120.0, -20.0}));
|
|
EXPECT_EQ(axis->get<&Axis_Properties::precision>(), 4);
|
|
EXPECT_TRUE(axis->get<&Axis_Properties::wheel>());
|
|
EXPECT_TRUE(axis->get<&Axis_Properties::drag>());
|
|
EXPECT_THROW(axis->set<&Axis_Properties::precision>(13), std::out_of_range);
|
|
EXPECT_THROW(axis->set<&Axis_Properties::coordinates>(Range{1.0, 1.0}),
|
|
std::invalid_argument);
|
|
axis->set<&Axis_Base_Properties::locale>(Number_Locale{','});
|
|
axis->set<&Axis_Properties::coordinates>(Range{-100.0, -20.0});
|
|
EXPECT_EQ(axis->get<&Axis_Base_Properties::locale>(), (Number_Locale{','}));
|
|
EXPECT_EQ(axis->get<&Axis_Properties::coordinates>(), (Range{-100.0, -20.0}));
|
|
const Axis_Transform numeric_transform = axis->transform();
|
|
EXPECT_DOUBLE_EQ(numeric_transform.pixel_to_coord(numeric_transform.coord_to_pixel(-60.0)), -60.0);
|
|
EXPECT_GT(static_cast<int>(numeric_transform.pixel_length) + 1, 0);
|
|
EXPECT_GT(axis->tick_step(numeric_transform.coordinate_range), 0.0);
|
|
EXPECT_GE(axis->sub_tick_count(axis->tick_step(numeric_transform.coordinate_range)), 0);
|
|
const auto time_axis = build_initial(plot, [&](auto& attach) {
|
|
return Time_Axis::Builder(root, Orientation::Horizontal, &attach)
|
|
.set_x(12)
|
|
.set_y(280)
|
|
.set_pixel_length(300)
|
|
.set_visible_time_point_count(32)
|
|
.set_tick_label_spacing_px(19)
|
|
.set_time_format("hh:mm:ss.zzz")
|
|
.set_font({16.0, 700, true})
|
|
.set_newest_at_axis_start(true)
|
|
.build();
|
|
});
|
|
ASSERT_TRUE(time_axis);
|
|
const int first = time_axis->append_time({3'723'004});
|
|
const int second = time_axis->append_time({3'724'005});
|
|
EXPECT_EQ(time_axis->get<&Time_Axis_Properties::visible_count>(), 32);
|
|
EXPECT_EQ(time_axis->get<&Time_Axis_Properties::tick_label_spacing_px>(), 19);
|
|
EXPECT_EQ(time_axis->get<&Time_Axis_Properties::format>(), "hh:mm:ss.zzz");
|
|
EXPECT_EQ(time_axis->get<&Axis_Base_Properties::unit_text_font>(), (Font{16.0, 700, true}));
|
|
EXPECT_TRUE(time_axis->get<&Time_Axis_Properties::newest_at_start>());
|
|
EXPECT_THROW(time_axis->set<&Time_Axis_Properties::visible_count>(1), std::out_of_range);
|
|
EXPECT_EQ(time_axis->time_point_count(), 2U);
|
|
EXPECT_EQ(time_axis->tick_to_time(first), (Time_Of_Day{3'723'004}));
|
|
EXPECT_EQ(time_axis->tick_to_time(second), (Time_Of_Day{3'724'005}));
|
|
EXPECT_EQ(time_axis->tick_label(first), "01:02:03.004");
|
|
}
|
|
TEST(Renderive_Core2, RetainedSpectrumApisRoundTripAndMarkersRemainObservable) {
|
|
Scene2D plot;
|
|
plot.init();
|
|
plot.set_viewport_size({360, 240});
|
|
plot.activate_view();
|
|
const auto root = plot.root_renderable();
|
|
auto [frequency, power, spectrum] = build_initial(plot, [&](auto& attach) {
|
|
auto frequency_axis = Frequency_Axis::Builder(root, Orientation::Horizontal, &attach)
|
|
.set_x(30).set_y(210).set_pixel_length(300)
|
|
.set_coord_range({90.0, 110.0}).build();
|
|
auto power_axis = Axis::Builder(root, Orientation::Vertical, &attach)
|
|
.set_x(30).set_y(20).set_pixel_length(190)
|
|
.set_coord_range({-20.0, -120.0}).build();
|
|
auto value = Spectrum::Builder{&attach}
|
|
.set<&Spectrum::Properties::frequency_range>(Range{90.0, 110.0})
|
|
.set<&Spectrum::Properties::frequency_point_size>(4)
|
|
.set<&Spectrum::Properties::center_frequency>(100.0)
|
|
.set<&Spectrum::Properties::sweep_frequency_range>(Range{96.0, 104.0})
|
|
.set<&Spectrum::Properties::max_hold_visible>(true)
|
|
.set<&Spectrum::Properties::min_hold_visible>(true)
|
|
.set<&Spectrum::Properties::max_marker_visible>(true)
|
|
.set<&Spectrum::Properties::use_min_marker>(true)
|
|
.set<&Spectrum::Properties::sweep_region_visible>(true)
|
|
.set<&Spectrum::Properties::visible_range_only>(false)
|
|
.set<&Spectrum::Properties::interpolation_mode>(Line_Interpolation_Mode::Cubic_Value)
|
|
.build(root, frequency_axis, power_axis);
|
|
return std::tuple{frequency_axis, power_axis, value};
|
|
});
|
|
ASSERT_TRUE(spectrum);
|
|
spectrum->set<&Spectrum::Properties::max_brush>(Brush{Color{1, 2, 3, 80}, Brush_Style::Solid});
|
|
spectrum->set<&Spectrum::Properties::current_brush>(Brush{Color{4, 5, 6, 90}, Brush_Style::Solid});
|
|
spectrum->set<&Spectrum::Properties::min_brush>(Brush{Color{7, 8, 9, 100}, Brush_Style::Solid});
|
|
spectrum->set<&Spectrum::Properties::max_pen>(Pen{Color{10, 20, 30, 255}, 2.0});
|
|
spectrum->set<&Spectrum::Properties::current_pen>(Pen{Color{40, 50, 60, 255}, 2.5});
|
|
spectrum->set<&Spectrum::Properties::min_pen>(Pen{Color{70, 80, 90, 255}, 3.0});
|
|
spectrum->set<&Spectrum::Properties::selected_marker_pen>(Pen{Color{11, 22, 33, 255}, 4.0});
|
|
spectrum->set<&Spectrum::Properties::marker_pen>(Pen{Color{44, 55, 66, 255}, 1.5});
|
|
spectrum->set<&Spectrum::Properties::middle_frequency_pen>(Pen{Color{77, 88, 99, 255}, 1.25});
|
|
spectrum->set<&Spectrum::Properties::sweep_region_brush>(Brush{Color{12, 34, 56, 70}, Brush_Style::Solid});
|
|
EXPECT_EQ(spectrum->get<&Spectrum::Properties::frequency_point_size>(), 4);
|
|
EXPECT_EQ(spectrum->get<&Spectrum::Properties::frequency_range>(), (Range{90.0, 110.0}));
|
|
EXPECT_DOUBLE_EQ(spectrum->get<&Spectrum::Properties::center_frequency>(), 100.0);
|
|
EXPECT_EQ(spectrum->get<&Spectrum::Properties::sweep_frequency_range>(), (Range{96.0, 104.0}));
|
|
EXPECT_TRUE(spectrum->get<&Spectrum::Properties::max_hold_visible>());
|
|
EXPECT_TRUE(spectrum->get<&Spectrum::Properties::min_hold_visible>());
|
|
EXPECT_TRUE(spectrum->get<&Spectrum::Properties::max_marker_visible>());
|
|
EXPECT_TRUE(spectrum->get<&Spectrum::Properties::use_min_marker>());
|
|
EXPECT_TRUE(spectrum->get<&Spectrum::Properties::sweep_region_visible>());
|
|
EXPECT_FALSE(spectrum->get<&Spectrum::Properties::visible_range_only>());
|
|
EXPECT_EQ(spectrum->get<&Spectrum::Properties::interpolation_mode>(), Line_Interpolation_Mode::Cubic_Value);
|
|
EXPECT_EQ(spectrum->get<&Spectrum::Properties::max_brush>(), (Brush{Color{1, 2, 3, 80}, Brush_Style::Solid}));
|
|
EXPECT_EQ(spectrum->get<&Spectrum::Properties::current_pen>(), (Pen{Color{40, 50, 60, 255}, 2.5}));
|
|
EXPECT_EQ(spectrum->get<&Spectrum::Properties::sweep_region_brush>(),
|
|
(Brush{Color{12, 34, 56, 70}, Brush_Style::Solid}));
|
|
std::pmr::vector<double> samples{std::pmr::get_default_resource()};
|
|
samples.assign({-100.0, -80.0, -70.0, -50.0});
|
|
spectrum->update_samples(std::move(samples));
|
|
EXPECT_EQ(spectrum->sample_count(), 4U);
|
|
EXPECT_GT(spectrum->rendered_point_count(), 4U);
|
|
bool valid{};
|
|
EXPECT_DOUBLE_EQ(spectrum->power_at(100.0, valid), -75.0);
|
|
EXPECT_TRUE(valid);
|
|
EXPECT_DOUBLE_EQ(spectrum->power_at(200.0, valid), 0.0);
|
|
EXPECT_FALSE(valid);
|
|
spectrum->add_custom_marker(96.0);
|
|
spectrum->add_custom_line_marker(102.0);
|
|
EXPECT_EQ(spectrum->selectable_line_marker_count(), 2);
|
|
spectrum->select_next_marker();
|
|
EXPECT_EQ(spectrum->selected_marker_index(), 0);
|
|
spectrum->set_current_marker_frequency(97.0);
|
|
EXPECT_DOUBLE_EQ(spectrum->marker_frequency(0), 97.0);
|
|
spectrum->select_previous_marker();
|
|
EXPECT_EQ(spectrum->selected_marker_index(), 1);
|
|
spectrum->remove_selected_marker();
|
|
EXPECT_EQ(spectrum->selectable_line_marker_count(), 1);
|
|
spectrum->remove_custom_marker(97.1);
|
|
EXPECT_EQ(spectrum->selectable_line_marker_count(), 0);
|
|
spectrum->clear_custom_markers();
|
|
EXPECT_EQ(spectrum->selected_marker_index(), -1);
|
|
EXPECT_TRUE(plot.render_frame(true));
|
|
}
|
|
TEST(Renderive_Core2, RetainedHeatmapSweepAndTraceApisPreserveDataShapes) {
|
|
Scene2D plot;
|
|
plot.init();
|
|
plot.set_viewport_size({420, 280});
|
|
plot.activate_view();
|
|
const auto root = plot.root_renderable();
|
|
auto [frequency, power, time, waterfall] = build_initial(plot, [&](auto& attach) {
|
|
auto frequency_axis = Frequency_Axis::Builder(root, Orientation::Horizontal, &attach)
|
|
.set_x(40).set_y(250).set_pixel_length(340)
|
|
.set_coord_range({0.0, 4.0}).build();
|
|
auto power_axis = Axis::Builder(root, Orientation::Vertical, &attach)
|
|
.set_x(40).set_y(20).set_pixel_length(230)
|
|
.set_coord_range({0.0, -120.0}).build();
|
|
auto time_axis = Time_Axis::Builder(root, Orientation::Vertical, &attach)
|
|
.set_x(40).set_y(20).set_pixel_length(230)
|
|
.set_visible_time_point_count(8).build();
|
|
auto value = Waterfall::Builder{&attach}
|
|
.set<&Waterfall::Properties::frequency_range>(Range{0.0, 4.0})
|
|
.set<&Waterfall::Properties::power_range>(Range{-120.0, 0.0})
|
|
.set<&Waterfall::Properties::frequency_bin_count>(4)
|
|
.set<&Waterfall::Properties::visible_range_only>(false)
|
|
.set<&Waterfall::Properties::interpolation_mode>(Image_Interpolation_Mode::Bicubic)
|
|
.build(root, frequency_axis, time_axis);
|
|
return std::tuple{frequency_axis, power_axis, time_axis, value};
|
|
});
|
|
ASSERT_TRUE(waterfall);
|
|
EXPECT_EQ(waterfall->get<&Waterfall::Properties::frequency_range>(), (Range{0.0, 4.0}));
|
|
EXPECT_EQ(waterfall->get<&Waterfall::Properties::power_range>(), (Range{-120.0, 0.0}));
|
|
EXPECT_EQ(waterfall->get<&Waterfall::Properties::frequency_bin_count>(), 4);
|
|
EXPECT_FALSE(waterfall->get<&Waterfall::Properties::visible_range_only>());
|
|
EXPECT_EQ(waterfall->get<&Waterfall::Properties::interpolation_mode>(), Image_Interpolation_Mode::Bicubic);
|
|
const std::array<double, 4> row1{-100.0, -80.0, -60.0, -40.0};
|
|
waterfall->append_row(Time_Of_Day{1000}, row1);
|
|
std::pmr::vector<double> row2{std::pmr::get_default_resource()};
|
|
row2.assign({-90.0, -70.0, -50.0, -30.0});
|
|
waterfall->append_row(Time_Of_Day{2000}, std::move(row2));
|
|
EXPECT_EQ(waterfall->row_count(), 2U);
|
|
EXPECT_EQ(waterfall->stored_point_count(), 8U);
|
|
EXPECT_EQ(waterfall->rendered_cell_count(), 8U);
|
|
auto afterglow = build_initial(plot, [&](auto& attach) {
|
|
return Afterglow::Builder{&attach}
|
|
.set<&Afterglow::Properties::frequency_range>(Range{0.0, 4.0})
|
|
.set<&Afterglow::Properties::power_range>(Range{-120.0, 0.0})
|
|
.set<&Afterglow::Properties::frequency_point_size>(4)
|
|
.set<&Afterglow::Properties::power_point_size>(8)
|
|
.set<&Afterglow::Properties::interpolate>(false)
|
|
.set<&Afterglow::Properties::attenuation_rate>(0.35)
|
|
.build(root, frequency, power);
|
|
});
|
|
ASSERT_TRUE(afterglow);
|
|
EXPECT_EQ(afterglow->get<&Afterglow::Properties::frequency_range>(), (Range{0.0, 4.0}));
|
|
EXPECT_EQ(afterglow->get<&Afterglow::Properties::power_range>(), (Range{-120.0, 0.0}));
|
|
EXPECT_EQ(afterglow->get<&Afterglow::Properties::frequency_point_size>(), 4);
|
|
EXPECT_EQ(afterglow->get<&Afterglow::Properties::power_point_size>(), 8);
|
|
EXPECT_FALSE(afterglow->get<&Afterglow::Properties::interpolate>());
|
|
EXPECT_DOUBLE_EQ(afterglow->get<&Afterglow::Properties::attenuation_rate>(), 0.35);
|
|
afterglow->append_spectrum(row1);
|
|
std::pmr::vector<double> glow2{std::pmr::get_default_resource()};
|
|
glow2.assign({-95.0, -75.0, -55.0, -35.0});
|
|
afterglow->append_spectrum(std::move(glow2));
|
|
EXPECT_EQ(afterglow->history_count(), 2U);
|
|
EXPECT_EQ(afterglow->latest_spectrum_point_count(), 4U);
|
|
EXPECT_EQ(afterglow->rendered_cell_count(), 32U);
|
|
afterglow->set<&Afterglow::Properties::attenuation_rate>(2.0);
|
|
EXPECT_DOUBLE_EQ(afterglow->get<&Afterglow::Properties::attenuation_rate>(), 1.0);
|
|
auto sweep = build_initial(plot, [&](auto& attach) {
|
|
return Sweep_Spectrum::Builder{&attach}
|
|
.set<&Sweep_Spectrum::Properties::frequency_range>(Range{0.0, 8.0})
|
|
.set<&Sweep_Spectrum::Properties::bins_per_block>(4)
|
|
.set<&Sweep_Spectrum::Properties::block_count>(2)
|
|
.set<&Sweep_Spectrum::Properties::pen>(Pen{Color{1, 120, 220, 255}, 2.0})
|
|
.set<&Sweep_Spectrum::Properties::current_frequency_pen>(Pen{Color{240, 80, 20, 255}, 3.0})
|
|
.set<&Sweep_Spectrum::Properties::visible_range_only>(false)
|
|
.set<&Sweep_Spectrum::Properties::interpolation_mode>(Line_Interpolation_Mode::Step_Right)
|
|
.build(root, frequency, power);
|
|
});
|
|
ASSERT_TRUE(sweep);
|
|
EXPECT_EQ(sweep->get<&Sweep_Spectrum::Properties::frequency_range>(), (Range{0.0, 8.0}));
|
|
EXPECT_EQ(sweep->get<&Sweep_Spectrum::Properties::bins_per_block>(), 4);
|
|
EXPECT_EQ(sweep->get<&Sweep_Spectrum::Properties::block_count>(), 2);
|
|
EXPECT_FALSE(sweep->get<&Sweep_Spectrum::Properties::visible_range_only>());
|
|
EXPECT_EQ(sweep->get<&Sweep_Spectrum::Properties::interpolation_mode>(), Line_Interpolation_Mode::Step_Right);
|
|
sweep->append_block(row1);
|
|
sweep->append_block(row1);
|
|
std::pmr::vector<double> block3{std::pmr::get_default_resource()};
|
|
block3.assign(row1.begin(), row1.end());
|
|
sweep->append_block(std::move(block3));
|
|
EXPECT_EQ(sweep->stored_block_count(), 2U);
|
|
EXPECT_EQ(sweep->stored_point_count(), 8U);
|
|
EXPECT_GT(sweep->rendered_point_count(), 0U);
|
|
auto trace = build_initial(plot, [&](auto& attach) {
|
|
return Frequency_Trace::Builder{&attach}
|
|
.set<&Frequency_Trace::Properties::pen>(Pen{Color{120, 240, 80, 255}, 2.0})
|
|
.build(root, time, power);
|
|
});
|
|
ASSERT_TRUE(trace);
|
|
trace->append_sample(Time_Of_Day{3000}, -80.0);
|
|
trace->append_sample(Time_Of_Day{4000}, -70.0);
|
|
EXPECT_EQ(trace->sample_count(), 2U);
|
|
EXPECT_EQ(trace->rendered_point_count(), 2U);
|
|
EXPECT_TRUE(plot.render_frame(true));
|
|
}
|
|
TEST(Renderive_Core2, RetainedSelectionAndConstellationApisDriveInteractionAndLayout) {
|
|
Scene2D plot;
|
|
plot.init();
|
|
plot.set_viewport_size({360, 260});
|
|
plot.activate_view();
|
|
const auto root = plot.root_renderable();
|
|
auto [horizontal, vertical, selection] = build_initial(plot, [&](auto& attach) {
|
|
auto horizontal_axis = Axis::Builder(root, Orientation::Horizontal, &attach)
|
|
.set_x(30).set_y(230).set_pixel_length(300)
|
|
.set_coord_range({-3.0, 3.0}).build();
|
|
auto vertical_axis = Axis::Builder(root, Orientation::Vertical, &attach)
|
|
.set_x(30).set_y(20).set_pixel_length(210)
|
|
.set_coord_range({3.0, -3.0}).build();
|
|
auto value = Selection_Rectangle_Overlay::Builder{&attach}
|
|
.set<&Selection_Rectangle_Overlay::Properties::label_font>(Font{14.0, 600, true})
|
|
.set<&Selection_Rectangle_Overlay::Properties::label_pen>(Pen{Color{20, 220, 180, 255}, 2.0})
|
|
.set<&Selection_Rectangle_Overlay::Properties::selection_brush>(Brush{Color{20, 80, 220, 60}, Brush_Style::Solid})
|
|
.set<&Selection_Rectangle_Overlay::Properties::selection_border_pen>(Pen{Color{240, 200, 60, 255}, 2.0, Line_Style::Dash})
|
|
.build(root, horizontal_axis, vertical_axis);
|
|
return std::tuple{horizontal_axis, vertical_axis, value};
|
|
});
|
|
ASSERT_TRUE(selection);
|
|
EXPECT_EQ(selection->get<&Selection_Rectangle_Overlay::Properties::label_font>(), (Font{14.0, 600, true}));
|
|
EXPECT_EQ(selection->get<&Selection_Rectangle_Overlay::Properties::label_pen>(), (Pen{Color{20, 220, 180, 255}, 2.0}));
|
|
EXPECT_EQ(selection->get<&Selection_Rectangle_Overlay::Properties::selection_brush>(),
|
|
(Brush{Color{20, 80, 220, 60}, Brush_Style::Solid}));
|
|
EXPECT_EQ(selection->get<&Selection_Rectangle_Overlay::Properties::selection_border_pen>(),
|
|
(Pen{Color{240, 200, 60, 255}, 2.0, Line_Style::Dash}));
|
|
Pointer_Event press(Event_Type::Pointer_Press);
|
|
press.position = {80.0, 70.0};
|
|
press.button = Mouse_Button::Left;
|
|
selection->handle_event(press);
|
|
EXPECT_TRUE(press.is_accepted());
|
|
Pointer_Event move(Event_Type::Pointer_Move);
|
|
move.position = {260.0, 180.0};
|
|
selection->handle_event(move);
|
|
EXPECT_TRUE(move.is_accepted());
|
|
Pointer_Event release(Event_Type::Pointer_Release);
|
|
release.position = {260.0, 180.0};
|
|
release.button = Mouse_Button::Left;
|
|
selection->handle_event(release);
|
|
EXPECT_TRUE(release.is_accepted());
|
|
ASSERT_EQ(selection->selected_regions().size(), 1U);
|
|
EXPECT_FALSE(selection->selected_regions().front().empty());
|
|
selection->clear_selected_regions();
|
|
EXPECT_TRUE(selection->selected_regions().empty());
|
|
auto constellation = build_initial(plot, [&](auto& attach) {
|
|
return Constellation_Diagram::Builder{&attach}
|
|
.set<&Constellation_Diagram::Properties::i_range>(Range{-2.0, 2.0})
|
|
.set<&Constellation_Diagram::Properties::q_range>(Range{-3.0, 3.0})
|
|
.set<&Constellation_Diagram::Properties::point_color>(Color{80, 220, 255, 255})
|
|
.set<&Constellation_Diagram::Properties::anchor_color>(Color{255, 180, 40, 255})
|
|
.set<&Constellation_Diagram::Properties::point_lifetime_ms>(2000)
|
|
.set<&Constellation_Diagram::Properties::type>(Constellation_Diagram_Type::Psk16)
|
|
.set<&Constellation_Diagram::Properties::phase_offset_radians>(0.25)
|
|
.build(root, horizontal, vertical);
|
|
});
|
|
ASSERT_TRUE(constellation);
|
|
EXPECT_EQ(constellation->get<&Constellation_Diagram::Properties::i_range>(), (Range{-2.0, 2.0}));
|
|
EXPECT_EQ(constellation->get<&Constellation_Diagram::Properties::q_range>(), (Range{-3.0, 3.0}));
|
|
EXPECT_EQ(constellation->get<&Constellation_Diagram::Properties::point_color>(), (Color{80, 220, 255, 255}));
|
|
EXPECT_EQ(constellation->get<&Constellation_Diagram::Properties::anchor_color>(), (Color{255, 180, 40, 255}));
|
|
EXPECT_EQ(constellation->get<&Constellation_Diagram::Properties::point_lifetime_ms>(), 2000);
|
|
constellation->append_point({0.5, -0.5});
|
|
constellation->append_point({-0.5, 0.5});
|
|
EXPECT_EQ(constellation->point_count(), 2U);
|
|
constellation->fit_square_to_axes();
|
|
EXPECT_EQ(horizontal->get<&Axis_Properties::coordinates>(), (Range{-3.0, 3.0}));
|
|
EXPECT_EQ(vertical->get<&Axis_Properties::coordinates>(), (Range{3.0, -3.0}));
|
|
EXPECT_TRUE(plot.render_frame(true));
|
|
}
|
|
TEST(Renderive_Core2, PlottablePropertiesPublishOnlyAtFrameBoundary) {
|
|
Scene2D plot;
|
|
plot.init();
|
|
const auto root = plot.root_renderable();
|
|
const auto [frequency, power, spectrum] = build_initial(plot, [&](auto& attach) {
|
|
auto frequency_axis = Frequency_Axis::Builder(root, Orientation::Horizontal, &attach).build();
|
|
auto power_axis = Axis::Builder(root, Orientation::Vertical, &attach).build();
|
|
auto value = Spectrum::Builder{&attach}.build(root, frequency_axis, power_axis);
|
|
return std::tuple{frequency_axis, power_axis, value};
|
|
});
|
|
ASSERT_TRUE(spectrum);
|
|
const auto state = renderive_dynamic_owner_cast<::State_Strategy_Base>(spectrum);
|
|
ASSERT_TRUE(state);
|
|
EXPECT_EQ(state->state_revision(), 0U);
|
|
spectrum->set<&Spectrum::Properties::frequency_point_size>(128);
|
|
EXPECT_EQ(spectrum->get<&Spectrum::Properties::frequency_point_size>(), 128);
|
|
EXPECT_EQ(state->state_revision(), 0U);
|
|
ASSERT_TRUE(plot.prepare_frame());
|
|
EXPECT_EQ(state->state_revision(), 1U);
|
|
ASSERT_TRUE(plot.render_prepared_frame());
|
|
}
|
|
TEST(Renderive_Core2, EveryStatefulRenderableOwnsItsStateObserver) {
|
|
Scene2D plot;
|
|
plot.init();
|
|
const auto root = plot.root_renderable();
|
|
const auto [frequency, power, spectrum] = build_initial(plot, [&](auto& attach) {
|
|
auto frequency_axis = Frequency_Axis::Builder(root, Orientation::Horizontal, &attach).build();
|
|
auto power_axis = Axis::Builder(root, Orientation::Vertical, &attach).build();
|
|
auto value = Spectrum::Builder{&attach}.build(root, frequency_axis, power_axis);
|
|
return std::tuple{frequency_axis, power_axis, value};
|
|
});
|
|
ASSERT_TRUE(frequency);
|
|
ASSERT_TRUE(power);
|
|
ASSERT_TRUE(spectrum);
|
|
|
|
EXPECT_EQ(frequency->observation().event, Renderable_Observer_Event::None);
|
|
EXPECT_EQ(power->observation().event, Renderable_Observer_Event::None);
|
|
EXPECT_EQ(spectrum->observation().event, Renderable_Observer_Event::None);
|
|
|
|
frequency->set<&Axis_Properties::coordinates>(Range{88'000'000.0, 108'000'000.0});
|
|
spectrum->set<&Spectrum::Properties::partition_count>(2);
|
|
const auto frequency_update = frequency->observation();
|
|
const auto spectrum_update = spectrum->observation();
|
|
EXPECT_EQ(frequency_update.event, Renderable_Observer_Event::Cache_Updated);
|
|
EXPECT_EQ(frequency_update.cache_update_count, 1U);
|
|
EXPECT_EQ(spectrum_update.event, Renderable_Observer_Event::Cache_Updated);
|
|
EXPECT_EQ(spectrum_update.cache_update_count, 1U);
|
|
EXPECT_EQ(power->observation().event, Renderable_Observer_Event::None);
|
|
|
|
ASSERT_TRUE(plot.prepare_frame());
|
|
const auto frequency_publish = frequency->observation();
|
|
const auto power_publish = power->observation();
|
|
const auto spectrum_publish = spectrum->observation();
|
|
EXPECT_EQ(frequency_publish.event, Renderable_Observer_Event::Published);
|
|
EXPECT_EQ(power_publish.event, Renderable_Observer_Event::Published);
|
|
EXPECT_EQ(spectrum_publish.event, Renderable_Observer_Event::Published);
|
|
EXPECT_EQ(frequency_publish.publish_count, 1U);
|
|
EXPECT_EQ(power_publish.publish_count, 1U);
|
|
EXPECT_EQ(spectrum_publish.publish_count, 1U);
|
|
EXPECT_EQ(frequency_publish.cache_update_count, 1U);
|
|
EXPECT_EQ(power_publish.cache_update_count, 0U);
|
|
EXPECT_EQ(spectrum_publish.cache_update_count, 1U);
|
|
|
|
ASSERT_TRUE(plot.render_prepared_frame());
|
|
EXPECT_EQ(frequency->observation().event, Renderable_Observer_Event::Published);
|
|
EXPECT_EQ(power->observation().event, Renderable_Observer_Event::Published);
|
|
EXPECT_EQ(spectrum->observation().event, Renderable_Observer_Event::Published);
|
|
}
|
|
TEST(Renderive_Core2, PlottableDataUpdatesReachKernelRealTimeDataStrategy) {
|
|
Scene2D plot;
|
|
plot.init();
|
|
const auto root = plot.root_renderable();
|
|
const auto [frequency, power, spectrum] = build_initial(plot, [&](auto& attach) {
|
|
auto frequency_axis = Frequency_Axis::Builder(root, Orientation::Horizontal, &attach).build();
|
|
auto power_axis = Axis::Builder(root, Orientation::Vertical, &attach).build();
|
|
auto value = Spectrum::Builder{&attach}.build(root, frequency_axis, power_axis);
|
|
return std::tuple{frequency_axis, power_axis, value};
|
|
});
|
|
const auto afterglow = build_initial(plot, [&](auto& attach) {
|
|
return Afterglow::Builder{&attach}.build(root, frequency, power);
|
|
});
|
|
ASSERT_TRUE(spectrum);
|
|
ASSERT_TRUE(afterglow);
|
|
const auto before = plot.frame_observer_snapshot().observation_count;
|
|
const std::array samples{-80.0, -70.0, -60.0, -50.0};
|
|
spectrum->update_samples(samples);
|
|
const auto latest = plot.frame_observer_snapshot();
|
|
EXPECT_EQ(latest.last_event, "real_time_data_updated");
|
|
EXPECT_GT(latest.observation_count, before);
|
|
afterglow->append_spectrum(samples);
|
|
const auto history = plot.frame_observer_snapshot();
|
|
EXPECT_EQ(history.last_event, "real_time_data_updated");
|
|
EXPECT_GT(history.observation_count, latest.observation_count);
|
|
}
|
|
TEST(Renderive_Core2, AxisRasterLayoutCoversAxisSwapAndEveryReversal) {
|
|
constexpr Range first_source{0.0, 2.0};
|
|
constexpr Range second_source{0.0, 3.0};
|
|
for (const bool swapped : {false, true}) {
|
|
for (const bool first_reversed : {false, true}) {
|
|
for (const bool second_reversed : {false, true}) {
|
|
const Axis_Transform first{
|
|
first_reversed ? Range{2.0, 0.0} : Range{0.0, 2.0},
|
|
swapped ? 20.0 : 10.0, swapped ? 30.0 : 20.0,
|
|
swapped ? Orientation::Vertical : Orientation::Horizontal};
|
|
const Axis_Transform second{
|
|
second_reversed ? Range{3.0, 0.0} : Range{0.0, 3.0},
|
|
swapped ? 10.0 : 20.0, swapped ? 20.0 : 30.0,
|
|
swapped ? Orientation::Horizontal : Orientation::Vertical};
|
|
const auto layout = detail::axis_raster_layout(
|
|
first, second, first_source, second_source, 3, 4);
|
|
ASSERT_TRUE(layout.valid());
|
|
EXPECT_EQ(layout.width, swapped ? 4 : 3);
|
|
EXPECT_EQ(layout.height, swapped ? 3 : 4);
|
|
const std::size_t origin = layout.index(0, 0, 3, 4);
|
|
const int x = static_cast<int>(origin % layout.width);
|
|
const int y = static_cast<int>(origin / layout.width);
|
|
EXPECT_EQ(x, swapped ? (second_reversed ? 3 : 0)
|
|
: (first_reversed ? 2 : 0));
|
|
EXPECT_EQ(y, swapped ? (first_reversed ? 2 : 0)
|
|
: (second_reversed ? 3 : 0));
|
|
}
|
|
}
|
|
}
|
|
|
|
const std::array<double, 2> values{1.0, 3.0};
|
|
const Axis_Transform vertical_frequency{{0.0, 1.0}, 10.0, 20.0,
|
|
Orientation::Vertical};
|
|
const Axis_Transform horizontal_power{{0.0, 4.0}, 100.0, 40.0,
|
|
Orientation::Horizontal};
|
|
const auto points = detail::curve_points(
|
|
values, {0.0, 1.0}, vertical_frequency, horizontal_power, false,
|
|
Line_Interpolation_Mode::Linear_Value);
|
|
ASSERT_EQ(points.size(), 2u);
|
|
EXPECT_DOUBLE_EQ(points.front().x, 110.0);
|
|
EXPECT_DOUBLE_EQ(points.front().y, 10.0);
|
|
EXPECT_DOUBLE_EQ(points.back().x, 130.0);
|
|
EXPECT_DOUBLE_EQ(points.back().y, 30.0);
|
|
}
|
|
TEST(Renderive_Core2, PartitionedPlotsBuildPropertyDrivenRenderGraphs) {
|
|
Scene2D plot;
|
|
plot.init();
|
|
plot.set_viewport_size({320, 180});
|
|
const auto root = plot.root_renderable();
|
|
const auto [frequency, power, time, spectrum, waterfall, afterglow] = build_initial(plot, [&](auto& attach) {
|
|
auto frequency_axis = Frequency_Axis::Builder(root, Orientation::Horizontal, &attach)
|
|
.set_pixel_length(320)
|
|
.set_coord_range({0.0, 10.0})
|
|
.build();
|
|
auto power_axis = Axis::Builder(root, Orientation::Vertical, &attach)
|
|
.set_pixel_length(180)
|
|
.set_coord_range({-120.0, 0.0})
|
|
.build();
|
|
auto time_axis = Time_Axis::Builder(root, Orientation::Vertical, &attach)
|
|
.set_pixel_length(180)
|
|
.build();
|
|
auto spectrum_value = Spectrum::Builder{&attach}
|
|
.set<&Spectrum::Properties::partition_mode>(Render_Partition_Mode::Fixed)
|
|
.set<&Spectrum::Properties::partition_count>(4)
|
|
.build(root, frequency_axis, power_axis);
|
|
auto waterfall_value = Waterfall::Builder{&attach}
|
|
.set<&Waterfall::Properties::partition_mode>(Render_Partition_Mode::Fixed)
|
|
.set<&Waterfall::Properties::partition_count>(4)
|
|
.build(root, frequency_axis, time_axis);
|
|
auto afterglow_value = Afterglow::Builder{&attach}
|
|
.set<&Afterglow::Properties::partition_mode>(Render_Partition_Mode::Fixed)
|
|
.set<&Afterglow::Properties::partition_count>(4)
|
|
.build(root, frequency_axis, power_axis);
|
|
return std::tuple{frequency_axis, power_axis, time_axis, spectrum_value, waterfall_value, afterglow_value};
|
|
});
|
|
std::array<double, 1024> samples{};
|
|
spectrum->update_samples(samples);
|
|
waterfall->append_row(0, samples);
|
|
afterglow->append_spectrum(samples);
|
|
plot.publish_frame_state();
|
|
|
|
const auto planned_node_count = [](const Render_Plan& plan,
|
|
const Renderable_Base& renderable) {
|
|
return std::count_if(plan.graph.nodes.begin(), plan.graph.nodes.end(),
|
|
[&](const Render_Node& node) {
|
|
return node.owner_id == renderable.renderable_id() &&
|
|
node.kind != Render_Node_Kind::composite;
|
|
});
|
|
};
|
|
|
|
EXPECT_EQ(spectrum->get<&Spectrum::Properties::partition_count>(), 4);
|
|
EXPECT_EQ(waterfall->get<&Waterfall::Properties::partition_count>(), 4);
|
|
EXPECT_EQ(afterglow->get<&Afterglow::Properties::partition_count>(), 4);
|
|
ASSERT_TRUE(plot.render_frame(true));
|
|
const auto partitioned_plan = plot.render_plan_snapshot();
|
|
ASSERT_TRUE(partitioned_plan);
|
|
EXPECT_EQ(planned_node_count(*partitioned_plan, *spectrum), 6u);
|
|
EXPECT_EQ(std::count_if(
|
|
partitioned_plan->graph.nodes.begin(),
|
|
partitioned_plan->graph.nodes.end(),
|
|
[&](const Render_Node& node) {
|
|
return node.owner_id == spectrum->renderable_id() &&
|
|
node.kind == Render_Node_Kind::paint;
|
|
}),
|
|
1);
|
|
EXPECT_EQ(planned_node_count(*partitioned_plan, *waterfall), 6u);
|
|
EXPECT_EQ(planned_node_count(*partitioned_plan, *afterglow), 11u);
|
|
|
|
spectrum->set<&Spectrum::Properties::partition_count>(1);
|
|
waterfall->set<&Waterfall::Properties::partition_count>(1);
|
|
afterglow->set<&Afterglow::Properties::partition_count>(1);
|
|
plot.publish_frame_state();
|
|
ASSERT_TRUE(plot.render_frame(true));
|
|
const auto single_plan = plot.render_plan_snapshot();
|
|
ASSERT_TRUE(single_plan);
|
|
EXPECT_EQ(planned_node_count(*single_plan, *spectrum), 3u);
|
|
EXPECT_EQ(planned_node_count(*single_plan, *waterfall), 3u);
|
|
EXPECT_EQ(planned_node_count(*single_plan, *afterglow), 5u);
|
|
|
|
spectrum->set<&Spectrum::Properties::partition_mode>(Render_Partition_Mode::Automatic);
|
|
waterfall->set<&Waterfall::Properties::partition_mode>(Render_Partition_Mode::Automatic);
|
|
afterglow->set<&Afterglow::Properties::partition_mode>(Render_Partition_Mode::Automatic);
|
|
plot.publish_frame_state();
|
|
ASSERT_TRUE(plot.render_frame(true));
|
|
const auto automatic_plan = plot.render_plan_snapshot();
|
|
ASSERT_TRUE(automatic_plan);
|
|
EXPECT_EQ(planned_node_count(*automatic_plan, *spectrum), 3u);
|
|
EXPECT_EQ(planned_node_count(*automatic_plan, *waterfall), 3u);
|
|
EXPECT_EQ(planned_node_count(*automatic_plan, *afterglow), 5u);
|
|
}
|
|
|
|
TEST(Renderive_Core2, AutomaticPartitioningSplitsOnlyForMeasuredBottlenecks) {
|
|
detail::Adaptive_Render_Partitioner partitioner;
|
|
EXPECT_EQ(partitioner.graph_partition_count(
|
|
Render_Partition_Mode::Automatic, 8, 8, 4096, 128),
|
|
1);
|
|
EXPECT_EQ(partitioner.begin(1, 4096), 1);
|
|
std::this_thread::sleep_for(std::chrono::milliseconds(2));
|
|
EXPECT_TRUE(partitioner.finish(Render_Partition_Mode::Automatic, 1, 0,
|
|
8, 4096, 128));
|
|
EXPECT_EQ(partitioner.graph_partition_count(
|
|
Render_Partition_Mode::Automatic, 8, 8, 4096, 128),
|
|
2);
|
|
|
|
EXPECT_EQ(partitioner.begin(2, 4096), 2);
|
|
EXPECT_TRUE(partitioner.finish(Render_Partition_Mode::Automatic, 2, 0,
|
|
8, 4096, 128));
|
|
EXPECT_EQ(partitioner.graph_partition_count(
|
|
Render_Partition_Mode::Automatic, 8, 8, 4096, 128),
|
|
1);
|
|
EXPECT_EQ(partitioner.graph_partition_count(
|
|
Render_Partition_Mode::Fixed, 7, 2, 1, 4096),
|
|
1);
|
|
EXPECT_EQ(partitioner.graph_partition_count(
|
|
Render_Partition_Mode::Fixed, 7, 2, 0, 4096),
|
|
0);
|
|
EXPECT_EQ(partitioner.begin(7, 0), 0);
|
|
}
|
|
|
|
TEST(Renderive_Core2, DynamicWaterfallCaptureStressPreservesPlansSlotsAndExactSessions) {
|
|
Scene2D plot;
|
|
plot.init();
|
|
plot.set_viewport_size({640, 360});
|
|
const auto root = plot.root_renderable();
|
|
const auto [frequency, time, waterfall] = build_initial(plot, [&](auto& attach) {
|
|
auto frequency_axis = Frequency_Axis::Builder(root, Orientation::Horizontal, &attach)
|
|
.set_pixel_length(640)
|
|
.set_coord_range({0.0, 32'767.0})
|
|
.build();
|
|
auto time_axis = Time_Axis::Builder(root, Orientation::Vertical, &attach)
|
|
.set_pixel_length(360)
|
|
.build();
|
|
auto value = Waterfall::Builder{&attach}
|
|
.set<&Waterfall::Properties::partition_mode>(Render_Partition_Mode::Fixed)
|
|
.set<&Waterfall::Properties::partition_count>(64)
|
|
.set<&Waterfall::Properties::frequency_range>(Range{0.0, 32'767.0})
|
|
.build(root, frequency_axis, time_axis);
|
|
return std::tuple{frequency_axis, time_axis, value};
|
|
});
|
|
std::array<double, 32'768> samples{};
|
|
for (std::size_t index = 0; index < samples.size(); ++index)
|
|
samples[index] = static_cast<double>(index % 256);
|
|
waterfall->append_row(0, samples);
|
|
auto& scene = plot;
|
|
constexpr std::array partition_counts{64, 16, 1};
|
|
std::uint64_t tick{1};
|
|
for (std::size_t round = 0; round < 3; ++round) {
|
|
const Capture_Session_Id session_id = scene.capture_frames(8);
|
|
for (std::size_t frame = 0; frame < 8; ++frame) {
|
|
if (frame % 2 == 0) {
|
|
waterfall->set<&Waterfall::Properties::partition_count>(
|
|
partition_counts[(round + frame / 2) % partition_counts.size()]);
|
|
}
|
|
if (frame % 4 == 0)
|
|
waterfall->append_row(static_cast<int>(tick++), samples);
|
|
ASSERT_TRUE(plot.render_frame(true));
|
|
}
|
|
const auto session = scene.capture_session(session_id);
|
|
ASSERT_TRUE(session);
|
|
ASSERT_EQ(session->captured_count(), 8u);
|
|
EXPECT_FALSE(session->active());
|
|
std::set<Render_Plan_Version> versions;
|
|
for (const auto& captured : session->frames) {
|
|
ASSERT_TRUE(captured.snapshot);
|
|
const auto plan = scene.find_render_plan(
|
|
captured.snapshot->render_plan_version);
|
|
ASSERT_TRUE(plan);
|
|
EXPECT_EQ(captured.snapshot->node_executions.size(),
|
|
plan->graph.nodes.size());
|
|
for (const auto& execution : captured.snapshot->node_executions)
|
|
EXPECT_EQ(execution.status, Node_Execution_Status::complete);
|
|
versions.insert(captured.snapshot->render_plan_version);
|
|
}
|
|
EXPECT_GE(versions.size(), 3u);
|
|
EXPECT_FALSE(scene.capture_state().enabled());
|
|
}
|
|
}
|
|
|
|
TEST(Renderive_Core2, PaintOnlyStyleChangesPreservePrepareCache) {
|
|
Scene2D plot;
|
|
plot.init();
|
|
plot.set_viewport_size({320, 180});
|
|
const auto root = plot.root_renderable();
|
|
const auto [frequency, power, spectrum] = build_initial(plot, [&](auto& attach) {
|
|
auto frequency_axis = Frequency_Axis::Builder(root, Orientation::Horizontal, &attach).build();
|
|
auto power_axis = Axis::Builder(root, Orientation::Vertical, &attach).build();
|
|
auto value = Spectrum::Builder{&attach}.build(root, frequency_axis, power_axis);
|
|
return std::tuple{frequency_axis, power_axis, value};
|
|
});
|
|
const std::array samples{-80.0, -70.0, -60.0, -50.0};
|
|
spectrum->update_samples(samples);
|
|
ASSERT_TRUE(plot.render_frame(true));
|
|
ASSERT_TRUE(plot.render_frame(true));
|
|
const auto cached_plan = plot.render_plan_snapshot();
|
|
ASSERT_TRUE(cached_plan);
|
|
const auto cached_has_kind = [&](Render_Node_Kind kind) {
|
|
return std::any_of(cached_plan->graph.nodes.begin(),
|
|
cached_plan->graph.nodes.end(),
|
|
[&](const Render_Node& node) {
|
|
return node.owner_id == spectrum->renderable_id() &&
|
|
node.kind == kind;
|
|
});
|
|
};
|
|
EXPECT_FALSE(cached_has_kind(Render_Node_Kind::prepare));
|
|
EXPECT_FALSE(cached_has_kind(Render_Node_Kind::paint));
|
|
spectrum->set<&Spectrum::Properties::current_pen>(
|
|
Pen{Color{12, 34, 56, 255}, 2.0});
|
|
|
|
ASSERT_TRUE(plot.render_frame(true));
|
|
const auto plan = plot.render_plan_snapshot();
|
|
ASSERT_TRUE(plan);
|
|
const auto has_kind = [&](Render_Node_Kind kind) {
|
|
return std::any_of(plan->graph.nodes.begin(), plan->graph.nodes.end(),
|
|
[&](const Render_Node& node) {
|
|
return node.owner_id == spectrum->renderable_id() && node.kind == kind;
|
|
});
|
|
};
|
|
EXPECT_FALSE(has_kind(Render_Node_Kind::prepare));
|
|
EXPECT_TRUE(has_kind(Render_Node_Kind::paint));
|
|
|
|
frequency->set<&Axis_Base_Properties::color>(Color{90, 100, 110, 255});
|
|
ASSERT_TRUE(plot.render_frame(true));
|
|
const auto axis_style_plan = plot.render_plan_snapshot();
|
|
ASSERT_TRUE(axis_style_plan);
|
|
EXPECT_FALSE(std::any_of(axis_style_plan->graph.nodes.begin(),
|
|
axis_style_plan->graph.nodes.end(),
|
|
[&](const Render_Node& node) {
|
|
return node.owner_id == spectrum->renderable_id() &&
|
|
node.kind == Render_Node_Kind::prepare;
|
|
}));
|
|
}
|
|
TEST(Renderive_Core2, PlottableAxesAreDataDependenciesAndPaintOverlays) {
|
|
Scene2D plot;
|
|
plot.init();
|
|
const auto root = plot.root_renderable();
|
|
const auto [frequency, power, spectrum] = build_initial(plot, [&](auto& attach) {
|
|
auto frequency_axis = Frequency_Axis::Builder(root, Orientation::Horizontal, &attach).build();
|
|
auto power_axis = Axis::Builder(root, Orientation::Vertical, &attach).build();
|
|
auto value = Spectrum::Builder{&attach}.build(root, frequency_axis, power_axis);
|
|
return std::tuple{frequency_axis, power_axis, value};
|
|
});
|
|
ASSERT_TRUE(spectrum);
|
|
const auto topology = plot.topology_snapshot();
|
|
const auto has_relationship = [](const auto& relationships, const auto* child, const auto* parent) {
|
|
return std::any_of(relationships.begin(), relationships.end(), [child, parent](const auto& relationship) {
|
|
return relationship.child.get() == child && relationship.parent.get() == parent;
|
|
});
|
|
};
|
|
EXPECT_TRUE(has_relationship(topology.dependency, spectrum.get(), frequency.get()));
|
|
EXPECT_TRUE(has_relationship(topology.dependency, spectrum.get(), power.get()));
|
|
EXPECT_TRUE(has_relationship(topology.display, frequency.get(), spectrum.get()));
|
|
EXPECT_TRUE(has_relationship(topology.display, power.get(), spectrum.get()));
|
|
const auto paint_order = plot.paint_order_snapshot();
|
|
const auto spectrum_position = std::find_if(paint_order.begin(), paint_order.end(), [&spectrum](const auto& renderable) {
|
|
return renderable.get() == spectrum.get();
|
|
});
|
|
const auto frequency_position = std::find_if(paint_order.begin(), paint_order.end(), [&frequency](const auto& renderable) {
|
|
return renderable.get() == frequency.get();
|
|
});
|
|
const auto power_position = std::find_if(paint_order.begin(), paint_order.end(), [&power](const auto& renderable) {
|
|
return renderable.get() == power.get();
|
|
});
|
|
ASSERT_NE(spectrum_position, paint_order.end());
|
|
ASSERT_NE(frequency_position, paint_order.end());
|
|
ASSERT_NE(power_position, paint_order.end());
|
|
EXPECT_LT(spectrum_position, frequency_position);
|
|
EXPECT_LT(spectrum_position, power_position);
|
|
}
|
|
TEST(Renderive_Core2, InteractionOverlayPaintsAboveItsAxes) {
|
|
Scene2D plot;
|
|
plot.init();
|
|
const auto root = plot.root_renderable();
|
|
const auto [horizontal, vertical, selection] = build_initial(plot, [&](auto& attach) {
|
|
auto horizontal_axis = Axis::Builder(root, Orientation::Horizontal, &attach).build();
|
|
auto vertical_axis = Axis::Builder(root, Orientation::Vertical, &attach).build();
|
|
auto value = Selection_Rectangle_Overlay::Builder{&attach}.build(root, horizontal_axis, vertical_axis);
|
|
return std::tuple{horizontal_axis, vertical_axis, value};
|
|
});
|
|
ASSERT_TRUE(selection);
|
|
const auto paint_order = plot.paint_order_snapshot();
|
|
const auto position = [&paint_order](const auto* target) {
|
|
return std::find_if(paint_order.begin(), paint_order.end(), [target](const auto& renderable) {
|
|
return renderable.get() == target;
|
|
});
|
|
};
|
|
const auto selection_position = position(selection.get());
|
|
const auto horizontal_position = position(horizontal.get());
|
|
const auto vertical_position = position(vertical.get());
|
|
ASSERT_NE(selection_position, paint_order.end());
|
|
ASSERT_NE(horizontal_position, paint_order.end());
|
|
ASSERT_NE(vertical_position, paint_order.end());
|
|
EXPECT_LT(horizontal_position, selection_position);
|
|
EXPECT_LT(vertical_position, selection_position);
|
|
}
|
|
} // namespace
|
|
} // namespace renderive
|