423 lines
23 KiB
C++
423 lines
23 KiB
C++
#include <QApplication>
|
|
#include <QElapsedTimer>
|
|
#include <QEventLoop>
|
|
#include <QThread>
|
|
#include <QTime>
|
|
#include <gtest/gtest.h>
|
|
#include <cmath>
|
|
#include <unordered_map>
|
|
#include "../Renderive/Axis/Axis.h"
|
|
#include "../Renderive/Axis/Frequency_Axis.h"
|
|
#include "../Renderive/Axis/Time_Axis.h"
|
|
#include "../Renderive/plot/Latency_Eager_Plot.h"
|
|
#include "../Renderive/architecture/Timeline_Stream.h"
|
|
#include "../Renderive/base/Memory.h"
|
|
#include "../Renderive/base/Frame_Memory.h"
|
|
#include "../Renderive/architecture/Frame_Raster_Context.h"
|
|
#include "../Renderive/primitive/Curve_Utils_p.h"
|
|
#include "../Renderive/plottable/Audio_Frequency.h"
|
|
#include "../Renderive/plottable/Planisphere.h"
|
|
#include "../Renderive/plottable/Waterfall.h"
|
|
#include "internal/Renderive_Private_Access.h"
|
|
namespace {
|
|
void process_events(int milliseconds) {
|
|
QElapsedTimer timer;
|
|
timer.start();
|
|
while (timer.elapsed() < milliseconds) {
|
|
QApplication::processEvents(QEventLoop::AllEvents, 10);
|
|
QThread::msleep(1);
|
|
}
|
|
}
|
|
std::vector<double> make_frequency_data(int size, int frame) {
|
|
std::vector<double> data(size);
|
|
for (int i = 0; i < size; ++i)
|
|
data[i] = -90.0 + 30.0 * std::sin(static_cast<double>(i + frame) * 0.017);
|
|
return data;
|
|
}
|
|
bool wait_ready(const std::shared_ptr<renderive::Waterfall>& waterfall, const std::shared_ptr<renderive::Time_Axis>& time_axis, const std::shared_ptr<renderive::Audio_Frequency>& audio, const std::shared_ptr<renderive::Planisphere>& planisphere) {
|
|
QElapsedTimer timer;
|
|
timer.start();
|
|
while (timer.elapsed() < 3000) {
|
|
QApplication::processEvents(QEventLoop::AllEvents, 10);
|
|
if (waterfall->ok() && time_axis->ok() && audio->ok() && planisphere->ok())
|
|
return true;
|
|
QThread::msleep(1);
|
|
}
|
|
return false;
|
|
}
|
|
bool wait_ready(const std::shared_ptr<renderive::Waterfall>& waterfall, const std::shared_ptr<renderive::Time_Axis>& time_axis) {
|
|
QElapsedTimer timer;
|
|
timer.start();
|
|
while (timer.elapsed() < 3000) {
|
|
QApplication::processEvents(QEventLoop::AllEvents, 10);
|
|
if (waterfall->ok() && time_axis->ok())
|
|
return true;
|
|
QThread::msleep(1);
|
|
}
|
|
return false;
|
|
}
|
|
bool wait_idle(renderive::Latency_Eager_Plot& plot) {
|
|
QElapsedTimer timer;
|
|
timer.start();
|
|
while (timer.elapsed() < 3000) {
|
|
QApplication::processEvents(QEventLoop::AllEvents, 10);
|
|
if (!plot.is_rendering() && !renderive::Abs_Plot_Private_Access::active_render_tasks(plot))
|
|
return true;
|
|
QThread::msleep(1);
|
|
}
|
|
return false;
|
|
}
|
|
void stress_data_paths() {
|
|
renderive::Latency_Eager_Plot plot;
|
|
plot.init();
|
|
plot.resize(720, 420);
|
|
auto data_node = plot.create_renderable_node(plot.get_root_renderable(), "Data_Renderable");
|
|
auto axis_node = plot.create_renderable_node(plot.get_root_renderable(), "Axis_Renderable");
|
|
auto frequency_axis = renderive::Frequency_Axis::Builder(axis_node, Qt::Horizontal).set_pixel_size(700).set_coord_range({0.0, 100.0}).build();
|
|
auto time_axis = renderive::Time_Axis::Builder(axis_node, Qt::Vertical).set_pixel_size(400).set_time_point_size(32).build();
|
|
auto value_axis = renderive::Axis::Builder(axis_node, Qt::Vertical).set_pixel_size(400).set_coord_range({-120.0, 0.0}).build();
|
|
auto i_axis = renderive::Axis::Builder(axis_node, Qt::Horizontal).set_pixel_size(700).set_coord_range({-2.0, 2.0}).build();
|
|
auto q_axis = renderive::Axis::Builder(axis_node, Qt::Vertical).set_pixel_size(400).set_coord_range({-2.0, 2.0}).build();
|
|
auto waterfall = renderive::Waterfall::Builder(data_node, frequency_axis, time_axis).set_frequency_range({0.0, 100.0}).set_power_range({-120.0, 0.0}).set_frequency_point_size(64).build();
|
|
auto audio = renderive::Audio_Frequency::Builder(data_node, time_axis, value_axis).build();
|
|
auto planisphere = renderive::Planisphere::Builder(data_node, i_axis, q_axis).set_i_range({-2.0, 2.0}).set_q_range({-2.0, 2.0}).set_continue_millisecond(500).build();
|
|
plot.show();
|
|
plot.start_render(240);
|
|
ASSERT_TRUE(wait_ready(waterfall, time_axis, audio, planisphere));
|
|
const int frequency_sizes[] = {64, 512, 8192, 8193, 128};
|
|
const int time_sizes[] = {32, 96, 48, 64, 24};
|
|
const renderive::Waterfall_Row_Update_Policy update_modes[] = {
|
|
renderive::Waterfall_Row_Update_Policy::All_Pending,
|
|
renderive::Waterfall_Row_Update_Policy::Latest_Only,
|
|
renderive::Waterfall_Row_Update_Policy::Rate_Limited_Latest,
|
|
renderive::Waterfall_Row_Update_Policy::Latest_Only,
|
|
renderive::Waterfall_Row_Update_Policy::All_Pending
|
|
};
|
|
for (int stage = 0; stage < 5; ++stage) {
|
|
waterfall->set_frequency_point_size(frequency_sizes[stage]);
|
|
waterfall->set_row_update_policy(update_modes[stage]);
|
|
waterfall->set_latest_row_min_interval_ms(2);
|
|
time_axis->set_time_point_size(time_sizes[stage]);
|
|
process_events(100);
|
|
std::unordered_map<int, double> expected_frequency;
|
|
QTime expected_time;
|
|
double expected_power{};
|
|
QPointF expected_position;
|
|
for (int frame = 0; frame < time_sizes[stage] * 2; ++frame) {
|
|
std::vector<double> frequency_data = make_frequency_data(frequency_sizes[stage], frame);
|
|
expected_time = QTime::fromMSecsSinceStartOfDay((stage * 10000 + frame * 4) % 86400000);
|
|
int tick = time_axis->timeline_stream()->push_time(expected_time);
|
|
waterfall->give_data(tick, frequency_data);
|
|
expected_frequency[tick] = frequency_data.front();
|
|
expected_power = frequency_data[frame % frequency_data.size()];
|
|
audio->give_data(tick, expected_power);
|
|
double phase = static_cast<double>(frame) * 0.13;
|
|
expected_position = {1.5 * std::cos(phase), 1.5 * std::sin(phase)};
|
|
planisphere->give_data(expected_position);
|
|
if ((frame & 3) == 3)
|
|
process_events(8);
|
|
}
|
|
process_events(60);
|
|
plot.pause_render();
|
|
ASSERT_TRUE(wait_idle(plot));
|
|
auto* waterfall_data = renderive::Waterfall_Private_Access::data(waterfall.get());
|
|
ASSERT_EQ(waterfall_data->ring_buffer.col_count, frequency_sizes[stage]);
|
|
ASSERT_EQ(waterfall_data->ring_buffer.row_count, time_sizes[stage]);
|
|
ASSERT_EQ(waterfall_data->raster_cache.image.width(), 700);
|
|
ASSERT_EQ(waterfall_data->raster_cache.image.height(), time_sizes[stage]);
|
|
int row_count = waterfall_data->ring_buffer.snapshot();
|
|
int previous_tick{};
|
|
for (int row = 0; row < row_count; ++row) {
|
|
int tick = waterfall_data->ring_buffer.tick(row);
|
|
auto expected = expected_frequency.find(tick);
|
|
ASSERT_NE(expected, expected_frequency.end());
|
|
EXPECT_NEAR(waterfall_data->ring_buffer.frequency_data(row, 0, 0)[0], expected->second, 0.000001);
|
|
if (row)
|
|
EXPECT_GT(tick, previous_tick);
|
|
previous_tick = tick;
|
|
}
|
|
auto* time_data = renderive::Time_Axis_Private_Access::data(time_axis.get());
|
|
ASSERT_TRUE(time_data->timeline_snapshot);
|
|
ASSERT_EQ(time_data->timeline_snapshot->time_point_size, time_sizes[stage]);
|
|
ASSERT_EQ(time_data->timeline_snapshot->times.size(), static_cast<std::size_t>(time_sizes[stage]));
|
|
ASSERT_FALSE(time_data->timeline_snapshot->times.empty());
|
|
EXPECT_EQ(time_data->timeline_snapshot->times.back(), expected_time);
|
|
int lower = qMin(time_data->timeline_snapshot->lower, time_data->timeline_snapshot->upper());
|
|
int upper = qMax(time_data->timeline_snapshot->lower, time_data->timeline_snapshot->upper());
|
|
ASSERT_EQ(time_data->ticks.size(), time_data->timeline_snapshot->ticks.size());
|
|
for (const renderive::Timeline_Tick& tick : time_data->timeline_snapshot->ticks) {
|
|
EXPECT_GE(tick.tick, lower);
|
|
EXPECT_LE(tick.tick, upper);
|
|
}
|
|
auto* audio_data = renderive::Audio_Frequency_Private_Access::data(audio.get());
|
|
ASSERT_EQ(audio_data->point_count, time_sizes[stage]);
|
|
ASSERT_EQ(audio_data->history.size(), static_cast<std::size_t>(time_sizes[stage]));
|
|
ASSERT_NE(audio_data->data_count, 0);
|
|
EXPECT_NEAR(audio_data->history_value(audio_data->data_count - 1).power, expected_power, 0.000001);
|
|
auto* planisphere_data = renderive::Planisphere_Private_Access::data(planisphere.get());
|
|
ASSERT_FALSE(planisphere_data->data_list.empty());
|
|
QPointF actual_position = planisphere_data->data_list.back().pos;
|
|
EXPECT_NEAR(actual_position.x(), expected_position.x(), 0.000001);
|
|
EXPECT_NEAR(actual_position.y(), expected_position.y(), 0.000001);
|
|
plot.start_render(240);
|
|
}
|
|
for (int frame = 0; frame < 180; ++frame) {
|
|
planisphere->set_continue_millisecond(500 + (frame & 1));
|
|
process_events(8);
|
|
}
|
|
plot.pause_render();
|
|
ASSERT_TRUE(wait_idle(plot));
|
|
EXPECT_TRUE(renderive::Planisphere_Private_Access::data(planisphere.get())->data_list.empty());
|
|
renderive::Memory_Stats stats = renderive::memory_stats();
|
|
EXPECT_GT(stats.upstream_allocation_count, 0);
|
|
EXPECT_GT(stats.upstream_peak_bytes, 0);
|
|
}
|
|
}
|
|
TEST(Memory_Data_Path_Stress, KeepsRingAndRenderableDataConsistentAcrossResizes) {
|
|
stress_data_paths();
|
|
}
|
|
TEST(Memory_Data_Path_Stress, WaterfallWritesNewestTimelineRowAtBottom) {
|
|
renderive::Latency_Eager_Plot plot;
|
|
plot.init();
|
|
plot.resize(1, 4);
|
|
auto data_node = plot.create_renderable_node(plot.get_root_renderable(), "Data_Renderable");
|
|
auto axis_node = plot.create_renderable_node(plot.get_root_renderable(), "Axis_Renderable");
|
|
auto frequency_axis = renderive::Frequency_Axis::Builder(axis_node, Qt::Horizontal).set_pixel_size(1).set_coord_range({0.0, 1.0}).build();
|
|
auto time_axis = renderive::Time_Axis::Builder(axis_node, Qt::Vertical).set_pixel_size(4).set_time_point_size(4).build();
|
|
renderive::Color_Map color_map({qRgb(10, 0, 0), qRgb(20, 0, 0), qRgb(30, 0, 0), qRgb(40, 0, 0)});
|
|
auto waterfall = renderive::Waterfall::Builder(data_node, frequency_axis, time_axis).set_frequency_range({0.0, 1.0}).set_power_range({0.0, 4.0}).set_frequency_point_size(1).set_color_map(color_map).build();
|
|
plot.show();
|
|
plot.start_render(240);
|
|
ASSERT_TRUE(wait_ready(waterfall, time_axis));
|
|
for (int frame = 0; frame < 4; ++frame) {
|
|
std::pmr::vector<double> row(renderive::memory_resource(renderive::Memory_Domain::Waterfall));
|
|
row.resize(1);
|
|
row[0] = static_cast<double>(frame);
|
|
waterfall->give_data(QTime::fromMSecsSinceStartOfDay(frame), std::move(row));
|
|
}
|
|
process_events(100);
|
|
plot.pause_render();
|
|
ASSERT_TRUE(wait_idle(plot));
|
|
auto* data = renderive::Waterfall_Private_Access::data(waterfall.get());
|
|
ASSERT_EQ(data->raster_cache.image.width(), 1);
|
|
ASSERT_EQ(data->raster_cache.image.height(), 4);
|
|
EXPECT_EQ(qRed(data->raster_cache.image.image().pixel(0, 0)), 10);
|
|
EXPECT_EQ(qRed(data->raster_cache.image.image().pixel(0, 3)), 40);
|
|
}
|
|
TEST(Memory_Data_Path_Stress, WaterfallWritesNewestTimelineRowAtTopWhenTimeAxisPushesToStart) {
|
|
renderive::Latency_Eager_Plot plot;
|
|
plot.init();
|
|
plot.resize(1, 4);
|
|
auto data_node = plot.create_renderable_node(plot.get_root_renderable(), "Data_Renderable");
|
|
auto axis_node = plot.create_renderable_node(plot.get_root_renderable(), "Axis_Renderable");
|
|
auto frequency_axis = renderive::Frequency_Axis::Builder(axis_node, Qt::Horizontal).set_pixel_size(1).set_coord_range({0.0, 1.0}).build();
|
|
auto time_axis = renderive::Time_Axis::Builder(axis_node, Qt::Vertical).set_pixel_size(4).set_time_point_size(4).set_start_coord_to_end_coord(true).build();
|
|
renderive::Color_Map color_map({qRgb(10, 0, 0), qRgb(20, 0, 0), qRgb(30, 0, 0), qRgb(40, 0, 0)});
|
|
auto waterfall = renderive::Waterfall::Builder(data_node, frequency_axis, time_axis).set_frequency_range({0.0, 1.0}).set_power_range({0.0, 4.0}).set_frequency_point_size(1).set_color_map(color_map).build();
|
|
plot.show();
|
|
plot.start_render(240);
|
|
ASSERT_TRUE(wait_ready(waterfall, time_axis));
|
|
for (int frame = 0; frame < 4; ++frame) {
|
|
std::pmr::vector<double> row(renderive::memory_resource(renderive::Memory_Domain::Waterfall));
|
|
row.resize(1);
|
|
row[0] = static_cast<double>(frame);
|
|
waterfall->give_data(QTime::fromMSecsSinceStartOfDay(frame), std::move(row));
|
|
}
|
|
process_events(100);
|
|
plot.pause_render();
|
|
ASSERT_TRUE(wait_idle(plot));
|
|
auto* data = renderive::Waterfall_Private_Access::data(waterfall.get());
|
|
ASSERT_EQ(data->raster_cache.image.width(), 1);
|
|
ASSERT_EQ(data->raster_cache.image.height(), 4);
|
|
EXPECT_EQ(qRed(data->raster_cache.image.image().pixel(0, 0)), 40);
|
|
EXPECT_EQ(qRed(data->raster_cache.image.image().pixel(0, 3)), 10);
|
|
}
|
|
TEST(Memory_Data_Path_Stress, DirtyLocalPixelCacheRebuildsAfterInput) {
|
|
renderive::Latency_Eager_Plot plot;
|
|
plot.init();
|
|
plot.resize(1, 4);
|
|
auto cache_node = plot.create_renderable_node(plot.get_root_renderable(), "Stream_Cache");
|
|
auto data_node = plot.create_renderable_node(cache_node, "Data_Renderable");
|
|
auto axis_node = plot.create_renderable_node(plot.get_root_renderable(), "Axis_Renderable");
|
|
cache_node->set_cache_mode(renderive::Renderable_Cache_Mode::Local_Pixel);
|
|
auto frequency_axis = renderive::Frequency_Axis::Builder(axis_node, Qt::Horizontal).set_pixel_size(1).set_coord_range({0.0, 1.0}).build();
|
|
auto time_axis = renderive::Time_Axis::Builder(axis_node, Qt::Vertical).set_pixel_size(4).set_time_point_size(4).build();
|
|
renderive::Color_Map color_map({qRgb(10, 0, 0), qRgb(20, 0, 0), qRgb(30, 0, 0), qRgb(40, 0, 0)});
|
|
auto waterfall = renderive::Waterfall::Builder(data_node, frequency_axis, time_axis).set_frequency_range({0.0, 1.0}).set_power_range({0.0, 4.0}).set_frequency_point_size(1).set_color_map(color_map).build();
|
|
plot.show();
|
|
plot.start_render(240);
|
|
ASSERT_TRUE(wait_ready(waterfall, time_axis));
|
|
process_events(80);
|
|
plot.pause_render();
|
|
ASSERT_TRUE(wait_idle(plot));
|
|
ASSERT_FALSE(renderive::Renderable_Private_Access::cache(*cache_node).isNull());
|
|
plot.start_render(240);
|
|
for (int frame = 0; frame < 4; ++frame) {
|
|
std::pmr::vector<double> row(renderive::memory_resource(renderive::Memory_Domain::Waterfall));
|
|
row.resize(1);
|
|
row[0] = static_cast<double>(frame);
|
|
waterfall->give_data(QTime::fromMSecsSinceStartOfDay(frame), std::move(row));
|
|
}
|
|
process_events(100);
|
|
plot.pause_render();
|
|
ASSERT_TRUE(wait_idle(plot));
|
|
const auto& cache = renderive::Renderable_Private_Access::cache(*cache_node);
|
|
ASSERT_EQ(cache.width(), 1);
|
|
ASSERT_EQ(cache.height(), 4);
|
|
EXPECT_EQ(qRed(cache.image().pixel(0, 3)), 40);
|
|
}
|
|
TEST(Memory_Data_Path_Stress, WaterfallPendingRowsOverwriteInSubmissionOrder) {
|
|
renderive::Waterfall_Input_Data input;
|
|
for (int tick = 0; tick < 300; ++tick) {
|
|
const double value = static_cast<double>(tick);
|
|
input.push(tick, std::span<const double>(&value, 1));
|
|
}
|
|
std::vector<int> ticks;
|
|
input.read_all([&ticks](int tick, const std::pmr::vector<double>& values) {
|
|
ticks.push_back(tick);
|
|
ASSERT_EQ(values.size(), 1u);
|
|
EXPECT_EQ(values.front(), static_cast<double>(tick));
|
|
});
|
|
ASSERT_EQ(ticks.size(), renderive::Waterfall_Input_Data::Max_Pending_Row_Count);
|
|
EXPECT_EQ(ticks.front(), 44);
|
|
EXPECT_EQ(ticks.back(), 299);
|
|
}
|
|
TEST(Memory_Data_Path_Stress, AllPendingUsesIncrementalScanlineMapping) {
|
|
renderive::Latency_Eager_Plot plot;
|
|
plot.init();
|
|
plot.resize(8, 4);
|
|
auto data_node = plot.create_renderable_node(plot.get_root_renderable(), "Data_Renderable");
|
|
auto axis_node = plot.create_renderable_node(plot.get_root_renderable(), "Axis_Renderable");
|
|
auto frequency_axis = renderive::Frequency_Axis::Builder(axis_node, Qt::Horizontal).set_pixel_size(8).set_coord_range({0.0, 8.0}).build();
|
|
auto time_axis = renderive::Time_Axis::Builder(axis_node, Qt::Vertical).set_pixel_size(4).set_time_point_size(4).build();
|
|
auto waterfall = renderive::Waterfall::Builder(data_node, frequency_axis, time_axis).set_frequency_range({0.0, 8.0}).set_power_range({0.0, 8.0}).set_frequency_point_size(8).set_row_update_policy(renderive::Waterfall_Row_Update_Policy::All_Pending).build();
|
|
plot.show();
|
|
plot.start_render(240);
|
|
ASSERT_TRUE(wait_ready(waterfall, time_axis));
|
|
std::vector<double> row(8, 1.0);
|
|
waterfall->give_data(time_axis->timeline_stream()->push_time(QTime(0, 0, 0, 1)), row);
|
|
process_events(80);
|
|
auto* data = renderive::Waterfall_Private_Access::data(waterfall.get());
|
|
const std::uint64_t rebuild_count = data->raster_cache.full_rebuild_count;
|
|
row.assign(8, 2.0);
|
|
waterfall->give_data(time_axis->timeline_stream()->push_time(QTime(0, 0, 0, 2)), row);
|
|
process_events(80);
|
|
plot.pause_render();
|
|
ASSERT_TRUE(wait_idle(plot));
|
|
EXPECT_EQ(data->raster_cache.full_rebuild_count, rebuild_count);
|
|
EXPECT_GT(data->raster_cache.incremental_row_count, 0u);
|
|
}
|
|
TEST(Memory_Data_Path_Stress, WaterfallRasterUsesDisplayHeight) {
|
|
renderive::Latency_Eager_Plot plot;
|
|
plot.init();
|
|
plot.resize(8, 4);
|
|
auto data_node = plot.create_renderable_node(plot.get_root_renderable(), "Data_Renderable");
|
|
auto axis_node = plot.create_renderable_node(plot.get_root_renderable(), "Axis_Renderable");
|
|
auto frequency_axis = renderive::Frequency_Axis::Builder(axis_node, Qt::Horizontal).set_pixel_size(8).set_coord_range({0.0, 8.0}).build();
|
|
auto time_axis = renderive::Time_Axis::Builder(axis_node, Qt::Vertical).set_pixel_size(4).set_time_point_size(16).build();
|
|
auto waterfall = renderive::Waterfall::Builder(data_node, frequency_axis, time_axis).set_frequency_range({0.0, 8.0}).set_power_range({0.0, 8.0}).set_frequency_point_size(8).build();
|
|
plot.show();
|
|
plot.start_render(240);
|
|
ASSERT_TRUE(wait_ready(waterfall, time_axis));
|
|
for (int index = 0; index < 16; ++index) {
|
|
std::vector<double> row(8, static_cast<double>(index));
|
|
waterfall->give_data(time_axis->timeline_stream()->push_time(QTime(0, 0, 0, index)), row);
|
|
}
|
|
process_events(100);
|
|
plot.pause_render();
|
|
ASSERT_TRUE(wait_idle(plot));
|
|
auto* data = renderive::Waterfall_Private_Access::data(waterfall.get());
|
|
EXPECT_EQ(data->ring_buffer.row_count, 16);
|
|
EXPECT_EQ(data->raster_cache.image.height(), 4);
|
|
}
|
|
TEST(Memory_Data_Path_Stress, RateLimitedLatestCommitsDeferredTailWithoutNewInput) {
|
|
renderive::Latency_Eager_Plot plot;
|
|
plot.init();
|
|
plot.resize(8, 4);
|
|
auto data_node = plot.create_renderable_node(plot.get_root_renderable(), "Data_Renderable");
|
|
auto axis_node = plot.create_renderable_node(plot.get_root_renderable(), "Axis_Renderable");
|
|
auto frequency_axis = renderive::Frequency_Axis::Builder(axis_node, Qt::Horizontal).set_pixel_size(8).set_coord_range({0.0, 8.0}).build();
|
|
auto time_axis = renderive::Time_Axis::Builder(axis_node, Qt::Vertical).set_pixel_size(4).set_time_point_size(4).build();
|
|
auto waterfall = renderive::Waterfall::Builder(data_node, frequency_axis, time_axis).set_frequency_range({0.0, 8.0}).set_power_range({0.0, 8.0}).set_frequency_point_size(8).set_row_update_policy(renderive::Waterfall_Row_Update_Policy::Rate_Limited_Latest).set_latest_row_min_interval_ms(100).build();
|
|
plot.show();
|
|
plot.start_render(240);
|
|
ASSERT_TRUE(wait_ready(waterfall, time_axis));
|
|
std::vector<double> row(8, 1.0);
|
|
const int first_tick = time_axis->timeline_stream()->push_time(QTime(0, 0, 0, 1));
|
|
waterfall->give_data(first_tick, row);
|
|
process_events(30);
|
|
row.assign(8, 2.0);
|
|
const int deferred_tick = time_axis->timeline_stream()->push_time(QTime(0, 0, 0, 2));
|
|
waterfall->give_data(deferred_tick, row);
|
|
process_events(180);
|
|
plot.pause_render();
|
|
ASSERT_TRUE(wait_idle(plot));
|
|
auto* data = renderive::Waterfall_Private_Access::data(waterfall.get());
|
|
ASSERT_GT(data->ring_buffer.data_count, 0);
|
|
EXPECT_EQ(data->ring_buffer.tick(data->ring_buffer.data_count - 1), deferred_tick);
|
|
EXPECT_FALSE(data->deferred_latest.has_value());
|
|
}
|
|
TEST(Memory_Data_Path_Stress, ExplicitMonotonicCurveClipsAndReducesToPixelWidth) {
|
|
renderive::Frame_Arena arena;
|
|
std::vector<QPointF> source;
|
|
source.reserve(10000);
|
|
for (int index = 0; index < 10000; ++index)
|
|
source.emplace_back(index, index == 4500 ? 100.0 : 0.0);
|
|
const renderive::Axis_Transform_Snapshot x_transform{{4000.0, 5000.0}, 0.0, 100.0, Qt::Horizontal};
|
|
const renderive::Axis_Transform_Snapshot y_transform{{0.0, 100.0}, 100.0, -100.0, Qt::Vertical};
|
|
auto exact = renderive::Curve_Utils::create_explicit_points(source, x_transform, y_transform, true, renderive::Line_Sampling_Mode::Exact);
|
|
auto reduced = renderive::Curve_Utils::create_explicit_points(source, x_transform, y_transform, true, renderive::Line_Sampling_Mode::Preserve_Extrema);
|
|
EXPECT_LE(exact.size(), 1003u);
|
|
EXPECT_LE(reduced.size(), 404u);
|
|
EXPECT_TRUE(std::any_of(reduced.begin(), reduced.end(), [](const QPointF& point) { return point.y() == 0.0; }));
|
|
}
|
|
TEST(Memory_Data_Path_Stress, ExplicitNonMonotonicCurveClipsSegmentsAndBreaksMissingData) {
|
|
renderive::Frame_Arena arena;
|
|
const std::vector<QPointF> source{{-2.0, 0.0}, {0.5, 1.0}, {2.0, 0.0},
|
|
{std::numeric_limits<double>::quiet_NaN(), std::numeric_limits<double>::quiet_NaN()},
|
|
{2.0, 2.0}, {0.5, 3.0}, {-2.0, 2.0}};
|
|
const renderive::Axis_Transform_Snapshot x_transform{{0.0, 1.0}, 0.0, 100.0, Qt::Horizontal};
|
|
const renderive::Axis_Transform_Snapshot y_transform{{0.0, 4.0}, 100.0, -100.0, Qt::Vertical};
|
|
auto points = renderive::Curve_Utils::create_explicit_points(source, x_transform, y_transform, true, renderive::Line_Sampling_Mode::Preserve_Extrema);
|
|
bool saw_break = false;
|
|
for (const QPointF& point : points) {
|
|
if (!std::isfinite(point.x())) {
|
|
saw_break = true;
|
|
continue;
|
|
}
|
|
EXPECT_GE(point.x(), 0.0);
|
|
EXPECT_LE(point.x(), 100.0);
|
|
}
|
|
EXPECT_TRUE(saw_break);
|
|
}
|
|
TEST(Memory_Data_Path_Stress, FrameRasterContextPreservesDrawOrder) {
|
|
QImage image(32, 32, QImage::Format_ARGB32_Premultiplied);
|
|
image.fill(Qt::transparent);
|
|
renderive::Frame_Raster_Context context(image);
|
|
context.canvas().fillRect(QRect(0, 0, 32, 32), Qt::red);
|
|
const std::array<QPointF, 2> line{{QPointF(2.0, 16.0), QPointF(29.0, 16.0)}};
|
|
context.stroke_polyline(line, QPen(Qt::blue, 3.0, Qt::SolidLine, Qt::FlatCap));
|
|
context.canvas().fillRect(QRect(0, 0, 4, 4), Qt::green);
|
|
context.finish();
|
|
EXPECT_EQ(image.pixelColor(8, 8), QColor(Qt::red));
|
|
EXPECT_GT(image.pixelColor(16, 16).blue(), 200);
|
|
EXPECT_EQ(image.pixelColor(1, 1), QColor(Qt::green));
|
|
}
|
|
TEST(Memory_Data_Path_Stress, FrameRasterContextAppliesCacheTranslation) {
|
|
QImage image(8, 8, QImage::Format_ARGB32_Premultiplied);
|
|
image.fill(Qt::transparent);
|
|
renderive::Frame_Raster_Context context(image, QPointF(-10.0, -10.0));
|
|
const std::array<QPointF, 2> line{{QPointF(10.0, 12.0), QPointF(17.0, 12.0)}};
|
|
context.stroke_polyline(line, QPen(Qt::white, 1.0, Qt::SolidLine, Qt::FlatCap));
|
|
context.finish();
|
|
EXPECT_GT(image.pixelColor(3, 2).alpha(), 0);
|
|
EXPECT_EQ(image.pixelColor(3, 6).alpha(), 0);
|
|
}
|
|
int main(int argc, char** argv) {
|
|
qputenv("QT_QPA_PLATFORM", "offscreen");
|
|
QApplication app(argc, argv);
|
|
testing::InitGoogleTest(&argc, argv);
|
|
renderive::start_render_scheduler();
|
|
return RUN_ALL_TESTS();
|
|
}
|