结构优化
This commit is contained in:
@@ -1,7 +1,12 @@
|
||||
#include <memory>
|
||||
int main() {
|
||||
#include <gtest/gtest.h>
|
||||
TEST(ASan_Runtime_Smoke, AllocatesAndReadsArray) {
|
||||
auto values = std::make_unique<int[]>(64);
|
||||
for (int i = 0; i < 64; ++i)
|
||||
values[i] = i;
|
||||
return values[63] == 63 ? 0 : 1;
|
||||
EXPECT_EQ(values[63], 63);
|
||||
}
|
||||
int main(int argc, char** argv) {
|
||||
testing::InitGoogleTest(&argc, argv);
|
||||
return RUN_ALL_TESTS();
|
||||
}
|
||||
|
||||
@@ -1,10 +1,12 @@
|
||||
#include "../Renderive/plottable/Performance_Shower_p.h"
|
||||
#include <QDir>
|
||||
#include <QFile>
|
||||
#include <gtest/gtest.h>
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
#include <chrono>
|
||||
#include <cmath>
|
||||
#include <cstdint>
|
||||
#include <iostream>
|
||||
#include <limits>
|
||||
#include <optional>
|
||||
#include <thread>
|
||||
@@ -28,7 +30,7 @@ struct Scenario_Config {
|
||||
double maximum_display_fps{};
|
||||
};
|
||||
struct Scenario_Result {
|
||||
renderive::Frame_Feedback_State state;
|
||||
renderive::Frame_Feedback_Snapshot state;
|
||||
std::uint64_t returned_frame_count{};
|
||||
};
|
||||
class Scenario_Simulation {
|
||||
@@ -80,7 +82,7 @@ private:
|
||||
void start_render() {
|
||||
if (!can_start_render() || !controller.rate_limit_ready(now))
|
||||
return;
|
||||
renderive::Frame_Metadata frame;
|
||||
renderive::Frame_Lifecycle_Record frame;
|
||||
frame.frame_id = config.id * 1000000 + ++next_frame_id;
|
||||
frame.input_version = latest_input_version;
|
||||
frame.snapshot_version = latest_input_version;
|
||||
@@ -101,7 +103,7 @@ private:
|
||||
render_complete_time = now + to_nanoseconds(config.render_duration_ms);
|
||||
}
|
||||
void complete_render() {
|
||||
renderive::Frame_Metadata frame = *rendering_frame;
|
||||
renderive::Frame_Lifecycle_Record frame = *rendering_frame;
|
||||
frame.draw_begin_time = frame.render_begin_time;
|
||||
frame.draw_end_time = now;
|
||||
frame.render_end_time = now;
|
||||
@@ -148,17 +150,17 @@ private:
|
||||
event_time = std::min(event_time, controller.next_render_time_ns());
|
||||
return event_time;
|
||||
}
|
||||
void return_frame(renderive::Frame_Metadata& frame) {
|
||||
void return_frame(renderive::Frame_Lifecycle_Record& frame) {
|
||||
renderive::write_frame_performance_log(frame);
|
||||
returned_frames.push_back(frame);
|
||||
}
|
||||
static constexpr std::uint64_t Infinite_Time = std::numeric_limits<std::uint64_t>::max();
|
||||
Scenario_Config config;
|
||||
renderive::Frame_Feedback_Controller controller;
|
||||
std::optional<renderive::Frame_Metadata> rendering_frame;
|
||||
std::optional<renderive::Frame_Metadata> ready_frame;
|
||||
std::optional<renderive::Frame_Metadata> consuming_frame;
|
||||
std::vector<renderive::Frame_Metadata> returned_frames;
|
||||
std::optional<renderive::Frame_Lifecycle_Record> rendering_frame;
|
||||
std::optional<renderive::Frame_Lifecycle_Record> ready_frame;
|
||||
std::optional<renderive::Frame_Lifecycle_Record> consuming_frame;
|
||||
std::vector<renderive::Frame_Lifecycle_Record> returned_frames;
|
||||
std::uint64_t now{};
|
||||
std::uint64_t end_time{};
|
||||
std::uint64_t next_input_time{};
|
||||
@@ -172,97 +174,177 @@ private:
|
||||
bool in_range(double value, double minimum, double maximum) {
|
||||
return value >= minimum && value <= maximum;
|
||||
}
|
||||
int validate_scenario(const Scenario_Config& config, const Scenario_Result& result, int index) {
|
||||
const renderive::Frame_Feedback_State& state = result.state;
|
||||
std::cout << config.name
|
||||
<< " state=" << renderive::bottleneck_state_name(state.bottleneck_state)
|
||||
<< " production_fps=" << state.production_rate_fps
|
||||
<< " display_fps=" << state.display_rate_fps
|
||||
<< " adaptive_interval_ms=" << state.adaptive_min_render_interval_ms
|
||||
<< " superseded_ratio=" << state.superseded_ready_ratio
|
||||
<< " display_efficiency=" << state.display_efficiency
|
||||
<< " returned=" << result.returned_frame_count
|
||||
<< '\n';
|
||||
if (state.bottleneck_state != config.expected_state)
|
||||
return index * 10 + 1;
|
||||
if (!in_range(state.production_rate_fps, config.minimum_production_fps, config.maximum_production_fps))
|
||||
return index * 10 + 2;
|
||||
if (!in_range(state.display_rate_fps, config.minimum_display_fps, config.maximum_display_fps))
|
||||
return index * 10 + 3;
|
||||
std::vector<QString> read_log_lines(const QString& path) {
|
||||
QFile file(path);
|
||||
std::vector<QString> lines;
|
||||
if (!file.open(QIODevice::ReadOnly | QIODevice::Text))
|
||||
return lines;
|
||||
while (!file.atEnd()) {
|
||||
QString line = QString::fromUtf8(file.readLine()).trimmed();
|
||||
if (!line.isEmpty())
|
||||
lines.push_back(line);
|
||||
}
|
||||
return lines;
|
||||
}
|
||||
bool has_line_containing(const std::vector<QString>& lines, const QString& text) {
|
||||
return std::any_of(lines.begin(), lines.end(), [&text](const QString& line) {
|
||||
return line.contains(text);
|
||||
});
|
||||
}
|
||||
bool has_all_bottleneck_states(const std::vector<QString>& lines) {
|
||||
return has_line_containing(lines, "Input_Limited")
|
||||
&& has_line_containing(lines, "Producer_Limited")
|
||||
&& has_line_containing(lines, "Consumer_Limited")
|
||||
&& has_line_containing(lines, "Manual_Limited");
|
||||
}
|
||||
std::vector<QString> wait_log_lines(const QString& path, std::size_t minimum_line_count) {
|
||||
for (int i = 0; i < 50; ++i) {
|
||||
std::vector<QString> lines = read_log_lines(path);
|
||||
if (lines.size() >= minimum_line_count && has_all_bottleneck_states(lines))
|
||||
return lines;
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(20));
|
||||
}
|
||||
return read_log_lines(path);
|
||||
}
|
||||
void expect_header_field(const QString& header, const QString& field) {
|
||||
EXPECT_TRUE(header.split(',').contains(field)) << field.toStdString();
|
||||
}
|
||||
void validate_scenario(const Scenario_Config& config, const Scenario_Result& result) {
|
||||
const renderive::Frame_Feedback_Snapshot& state = result.state;
|
||||
SCOPED_TRACE(config.name);
|
||||
EXPECT_EQ(state.bottleneck_state, config.expected_state);
|
||||
EXPECT_TRUE(in_range(state.production_rate_fps, config.minimum_production_fps, config.maximum_production_fps)) << state.production_rate_fps;
|
||||
EXPECT_TRUE(in_range(state.display_rate_fps, config.minimum_display_fps, config.maximum_display_fps)) << state.display_rate_fps;
|
||||
EXPECT_GT(result.returned_frame_count, 0);
|
||||
if (config.expected_state == renderive::Bottleneck_State::Consumer_Limited) {
|
||||
if (std::abs(state.production_rate_fps - state.display_rate_fps) > 6.0)
|
||||
return index * 10 + 4;
|
||||
if (state.adaptive_min_render_interval_ms < config.paint_duration_ms * 0.8)
|
||||
return index * 10 + 5;
|
||||
EXPECT_LE(std::abs(state.production_rate_fps - state.display_rate_fps), 6.0);
|
||||
EXPECT_GE(state.adaptive_min_render_interval_ms, config.paint_duration_ms * 0.8);
|
||||
}
|
||||
if (config.expected_state == renderive::Bottleneck_State::Manual_Limited) {
|
||||
double expected_interval = 1000.0 / config.max_render_fps;
|
||||
if (std::abs(state.effective_min_render_interval_ms - expected_interval) > 0.2)
|
||||
return index * 10 + 6;
|
||||
EXPECT_NEAR(state.effective_min_render_interval_ms, expected_interval, 0.2);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
int validate_frame_input_ring() {
|
||||
void validate_performance_log(const QString& log_path) {
|
||||
std::vector<QString> lines = wait_log_lines(log_path, 8);
|
||||
ASSERT_GT(lines.size(), 1);
|
||||
const QString& header = lines.front();
|
||||
EXPECT_TRUE(header.startsWith("frame_id,input_version,snapshot_version,slot_generation,outcome,bottleneck"));
|
||||
EXPECT_FALSE(header.contains("policy"));
|
||||
expect_header_field(header, "production_fps");
|
||||
expect_header_field(header, "display_fps");
|
||||
expect_header_field(header, "adaptive_interval_ms");
|
||||
expect_header_field(header, "effective_interval_ms");
|
||||
expect_header_field(header, "superseded_ratio");
|
||||
expect_header_field(header, "display_efficiency");
|
||||
expect_header_field(header, "renderable_stats");
|
||||
expect_header_field(header, "renderable_cache_stats");
|
||||
expect_header_field(header, "cache_rebuild_count");
|
||||
expect_header_field(header, "cache_rebuild_time_ns");
|
||||
expect_header_field(header, "cache_compose_time_ns");
|
||||
int header_column_count = header.split(',').size();
|
||||
EXPECT_EQ(lines[1].split(',').size(), header_column_count);
|
||||
EXPECT_TRUE(has_all_bottleneck_states(lines));
|
||||
}
|
||||
}
|
||||
TEST(Frame_Scheduler_Feedback, KeepsPerformanceShowerInputRingBounded) {
|
||||
renderive::Performance_Shower_Input_Data input;
|
||||
for (std::uint64_t frame_id = 1; frame_id <= 72; ++frame_id) {
|
||||
renderive::Frame_Metadata frame;
|
||||
renderive::Frame_Lifecycle_Record frame;
|
||||
frame.frame_id = frame_id;
|
||||
input.push(frame);
|
||||
}
|
||||
if (input.get_pending_count() != renderive::Performance_Shower_Input_Data::Capacity)
|
||||
return 1;
|
||||
if (input.get_capacity_dropped_count() != 8)
|
||||
return 2;
|
||||
EXPECT_EQ(input.get_pending_count(), renderive::Performance_Shower_Input_Data::Capacity);
|
||||
EXPECT_EQ(input.get_capacity_dropped_count(), 8);
|
||||
std::vector<std::uint64_t> frame_ids;
|
||||
input.read_all([&frame_ids](const renderive::Frame_Metadata& frame) {
|
||||
input.read_all([&frame_ids](const renderive::Frame_Lifecycle_Record& frame) {
|
||||
frame_ids.push_back(frame.frame_id);
|
||||
});
|
||||
if (frame_ids.front() != 9 || frame_ids.back() != 72)
|
||||
return 3;
|
||||
ASSERT_FALSE(frame_ids.empty());
|
||||
EXPECT_EQ(frame_ids.front(), 9);
|
||||
EXPECT_EQ(frame_ids.back(), 72);
|
||||
input.request_reset();
|
||||
if (input.get_pending_count() != 0 || !input.reset_pending)
|
||||
return 4;
|
||||
return 0;
|
||||
EXPECT_EQ(input.get_pending_count(), 0);
|
||||
EXPECT_TRUE(input.reset_pending);
|
||||
}
|
||||
int validate_renderable_cache_stats() {
|
||||
renderive::Frame_Metadata frame;
|
||||
frame.record_renderable_cache_hit("cache_a");
|
||||
frame.record_renderable_cache_stale_hit("cache_a");
|
||||
frame.record_renderable_cache_miss("cache_b");
|
||||
if (frame.renderable_cache_hit_count != 1 || frame.renderable_cache_stale_hit_count != 1 || frame.renderable_cache_miss_count != 1)
|
||||
return 1;
|
||||
TEST(Frame_Scheduler_Feedback, AggregatesRenderableCacheStats) {
|
||||
renderive::Frame_Lifecycle_Record frame;
|
||||
frame.render_begin_time = 1000;
|
||||
frame.render_end_time = 5000;
|
||||
frame.record_renderable_render("root", {}, 0, renderive::Renderable_Cache_Mode::Direct, 1000);
|
||||
frame.record_renderable_self_draw("root", {}, 0, renderive::Renderable_Cache_Mode::Direct, 300);
|
||||
frame.record_renderable_render("cache_b", "cache_a", 1, renderive::Renderable_Cache_Mode::Local_Pixel, 2000);
|
||||
frame.record_renderable_cache_hit("cache_a", {}, 0);
|
||||
frame.record_renderable_cache_stale_hit("cache_a", {}, 0);
|
||||
frame.record_renderable_cache_miss("cache_b", "cache_a", 1);
|
||||
frame.record_renderable_cache_rebuild("cache_b", "cache_a", 1, 1000, 4096, 1024, 3, 3, false, true);
|
||||
frame.record_renderable_cache_rebuild("cache_a", {}, 0, 2000, 2048, 512, 4, 4, true, false);
|
||||
frame.record_renderable_cache_compose("cache_a", {}, 0, 300, 2048, 512, 4, 4);
|
||||
frame.record_renderable_cache_compose("cache_b", "cache_a", 1, 700, 4096, 1024, 3, 3);
|
||||
EXPECT_EQ(frame.renderable_cache_hit_count, 1);
|
||||
EXPECT_EQ(frame.renderable_cache_stale_hit_count, 1);
|
||||
EXPECT_EQ(frame.renderable_cache_miss_count, 1);
|
||||
EXPECT_EQ(frame.renderable_cache_rebuild_count, 2);
|
||||
EXPECT_EQ(frame.renderable_cache_stale_rebuild_count, 1);
|
||||
EXPECT_EQ(frame.renderable_cache_miss_rebuild_count, 1);
|
||||
EXPECT_EQ(frame.renderable_cache_rebuild_time_ns, 3000);
|
||||
EXPECT_EQ(frame.renderable_cache_compose_time_ns, 1000);
|
||||
ASSERT_EQ(frame.renderable_stats.size(), 2);
|
||||
EXPECT_FALSE(frame.renderable_stats[0].cache_enabled);
|
||||
EXPECT_TRUE(frame.renderable_stats[1].cache_enabled);
|
||||
EXPECT_EQ(frame.renderable_stats[1].tree_depth, 1);
|
||||
renderive::Frame_Performance_Aggregate aggregate;
|
||||
aggregate.update(frame);
|
||||
if (aggregate.cache_hit_count != 1 || aggregate.cache_stale_hit_count != 1 || aggregate.cache_miss_count != 1)
|
||||
return 2;
|
||||
if (aggregate.renderable_cache_stats.size() != 2)
|
||||
return 3;
|
||||
return 0;
|
||||
EXPECT_EQ(aggregate.render_time_ns, 4000);
|
||||
ASSERT_EQ(aggregate.renderable_stats.size(), 2);
|
||||
EXPECT_EQ(aggregate.renderable_stats[1].parent_object_name, "cache_a");
|
||||
EXPECT_EQ(aggregate.renderable_stats[1].tree_depth, 1);
|
||||
EXPECT_TRUE(aggregate.renderable_stats[1].cache_enabled);
|
||||
EXPECT_GT(aggregate.renderable_stats[0].total_render_ms.times, 0);
|
||||
EXPECT_GT(aggregate.renderable_stats[0].self_draw_ms.times, 0);
|
||||
EXPECT_GT(aggregate.renderable_stats[1].total_render_ratio.times, 0);
|
||||
EXPECT_EQ(aggregate.cache_hit_count, 1);
|
||||
EXPECT_EQ(aggregate.cache_stale_hit_count, 1);
|
||||
EXPECT_EQ(aggregate.cache_miss_count, 1);
|
||||
EXPECT_EQ(aggregate.cache_rebuild_count, 2);
|
||||
EXPECT_EQ(aggregate.cache_rebuild_time_ns, 3000);
|
||||
EXPECT_EQ(aggregate.cache_compose_time_ns, 1000);
|
||||
EXPECT_EQ(aggregate.renderable_cache_stats.size(), 2);
|
||||
EXPECT_EQ(aggregate.renderable_cache_stats[1].parent_object_name, "cache_a");
|
||||
EXPECT_EQ(aggregate.renderable_cache_stats[1].tree_depth, 1);
|
||||
EXPECT_GT(aggregate.renderable_cache_stats[0].rebuild_ms.times, 0);
|
||||
EXPECT_GT(aggregate.renderable_cache_stats[0].compose_ms.times, 0);
|
||||
EXPECT_GT(aggregate.cache_rebuild_ms.times, 0);
|
||||
EXPECT_GT(aggregate.cache_compose_ms.times, 0);
|
||||
EXPECT_GT(aggregate.cache_rebuild_ratio.times, 0);
|
||||
EXPECT_GT(aggregate.cache_compose_ratio.times, 0);
|
||||
EXPECT_EQ(aggregate.renderable_cache_stats[0].last_subtree_compose_time_ns, 1000);
|
||||
EXPECT_EQ(aggregate.renderable_cache_stats[1].last_subtree_compose_time_ns, 700);
|
||||
EXPECT_GT(aggregate.renderable_cache_stats[0].subtree_compose_ms.times, 0);
|
||||
}
|
||||
}
|
||||
int main() {
|
||||
TEST(Frame_Scheduler_Feedback, ClassifiesFourBottlenecksAndWritesUsableLog) {
|
||||
QString log_path = qEnvironmentVariable("RENDERIVE_PERFORMANCE_LOG_PATH");
|
||||
if (log_path.isEmpty()) {
|
||||
log_path = QDir::current().filePath("renderive_feedback_test_log.csv");
|
||||
qputenv("RENDERIVE_PERFORMANCE_LOG_PATH", log_path.toUtf8());
|
||||
}
|
||||
qputenv("RENDERIVE_PERFORMANCE_LOG", "1");
|
||||
if (!log_path.isEmpty())
|
||||
QFile::remove(log_path);
|
||||
int ring_result = validate_frame_input_ring();
|
||||
if (ring_result)
|
||||
return ring_result;
|
||||
int cache_result = validate_renderable_cache_stats();
|
||||
if (cache_result)
|
||||
return 100 + cache_result;
|
||||
const std::array<Scenario_Config, 4> scenarios{{
|
||||
{"Input_Limited", 1, 50.0, 2.0, 2.0, 0.0, renderive::Bottleneck_State::Input_Limited, 18.0, 22.0, 18.0, 22.0},
|
||||
{"Producer_Limited", 2, 1.0, 20.0, 2.0, 0.0, renderive::Bottleneck_State::Producer_Limited, 45.0, 55.0, 45.0, 55.0},
|
||||
{"Consumer_Limited", 3, 1.0, 2.0, 25.0, 0.0, renderive::Bottleneck_State::Consumer_Limited, 34.0, 46.0, 36.0, 44.0},
|
||||
{"Manual_Limited", 4, 1.0, 2.0, 2.0, 30.0, renderive::Bottleneck_State::Manual_Limited, 28.0, 32.0, 28.0, 32.0}
|
||||
}};
|
||||
for (int i = 0; i < scenarios.size(); ++i) {
|
||||
Scenario_Simulation simulation(scenarios[i]);
|
||||
for (const Scenario_Config& scenario : scenarios) {
|
||||
Scenario_Simulation simulation(scenario);
|
||||
Scenario_Result result = simulation.run(8000.0);
|
||||
int validation_result = validate_scenario(scenarios[i], result, i + 1);
|
||||
if (validation_result)
|
||||
return validation_result;
|
||||
validate_scenario(scenario, result);
|
||||
}
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(250));
|
||||
return 0;
|
||||
validate_performance_log(log_path);
|
||||
}
|
||||
int main(int argc, char** argv) {
|
||||
testing::InitGoogleTest(&argc, argv);
|
||||
return RUN_ALL_TESTS();
|
||||
}
|
||||
|
||||
+15
-27
@@ -2,6 +2,7 @@
|
||||
#include <array>
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <gtest/gtest.h>
|
||||
#include <memory_resource>
|
||||
#include <stdexcept>
|
||||
#include <thread>
|
||||
@@ -44,33 +45,21 @@ struct Large_Payload {
|
||||
std::array<std::byte, 128 * 1024> data{};
|
||||
};
|
||||
}
|
||||
int main() {
|
||||
TEST(Memory_Callbacks, ValidatesCallbacksAndTracksDeallocationThread) {
|
||||
callback_state.main_thread = std::this_thread::get_id();
|
||||
renderive::Memory_Callbacks invalid_callbacks;
|
||||
invalid_callbacks.context = &callback_state;
|
||||
invalid_callbacks.allocate = callback_allocate;
|
||||
try {
|
||||
renderive::set_memory_callbacks(invalid_callbacks);
|
||||
return 1;
|
||||
}
|
||||
catch (const std::invalid_argument&) {}
|
||||
EXPECT_THROW(renderive::set_memory_callbacks(invalid_callbacks), std::invalid_argument);
|
||||
invalid_callbacks.allocate = nullptr;
|
||||
invalid_callbacks.deallocate = callback_deallocate;
|
||||
try {
|
||||
renderive::set_memory_callbacks(invalid_callbacks);
|
||||
return 2;
|
||||
}
|
||||
catch (const std::invalid_argument&) {}
|
||||
EXPECT_THROW(renderive::set_memory_callbacks(invalid_callbacks), std::invalid_argument);
|
||||
renderive::Memory_Callbacks callbacks;
|
||||
callbacks.context = &callback_state;
|
||||
callbacks.allocate = callback_allocate;
|
||||
callbacks.deallocate = callback_deallocate;
|
||||
renderive::set_memory_callbacks(callbacks);
|
||||
try {
|
||||
renderive::set_memory_callbacks(callbacks);
|
||||
return 3;
|
||||
}
|
||||
catch (const std::logic_error&) {}
|
||||
EXPECT_THROW(renderive::set_memory_callbacks(callbacks), std::logic_error);
|
||||
{
|
||||
std::pmr::vector<std::uint64_t> values(renderive::memory_resource());
|
||||
values.resize(8192, 11);
|
||||
@@ -85,16 +74,15 @@ int main() {
|
||||
}
|
||||
renderive::release_unused_memory();
|
||||
renderive::Memory_Stats stats = renderive::memory_stats();
|
||||
if (callback_state.alignment_error.load(std::memory_order_relaxed))
|
||||
return 4;
|
||||
if (!callback_state.allocation_count.load(std::memory_order_relaxed))
|
||||
return 5;
|
||||
if (stats.upstream_allocation_count != callback_state.allocation_count.load(std::memory_order_relaxed))
|
||||
return 6;
|
||||
if (!callback_state.deallocation_count.load(std::memory_order_relaxed) || !callback_state.deallocated_off_main_thread.load(std::memory_order_relaxed))
|
||||
return 7;
|
||||
EXPECT_FALSE(callback_state.alignment_error.load(std::memory_order_relaxed));
|
||||
EXPECT_GT(callback_state.allocation_count.load(std::memory_order_relaxed), 0);
|
||||
EXPECT_EQ(stats.upstream_allocation_count, callback_state.allocation_count.load(std::memory_order_relaxed));
|
||||
EXPECT_GT(callback_state.deallocation_count.load(std::memory_order_relaxed), 0);
|
||||
EXPECT_TRUE(callback_state.deallocated_off_main_thread.load(std::memory_order_relaxed));
|
||||
renderive::shutdown_memory();
|
||||
if (renderive::memory_stats().upstream_current_bytes)
|
||||
return 8;
|
||||
return 0;
|
||||
EXPECT_EQ(renderive::memory_stats().upstream_current_bytes, 0);
|
||||
}
|
||||
int main(int argc, char** argv) {
|
||||
testing::InitGoogleTest(&argc, argv);
|
||||
return RUN_ALL_TESTS();
|
||||
}
|
||||
|
||||
@@ -3,14 +3,16 @@
|
||||
#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/Axis/Time_Axis_p.h"
|
||||
#include "../Renderive/architecture/Plot.h"
|
||||
#include "../Renderive/architecture/Plot_p.h"
|
||||
#include "../Renderive/plot/Latency_Eager_Plot.h"
|
||||
#include "../Renderive/plot/Plot_p.h"
|
||||
#include "../Renderive/architecture/Timeline_Stream.h"
|
||||
#include "../Renderive/base/Memory.h"
|
||||
#include "../Renderive/plottable/Audio_Frequency.h"
|
||||
#include "../Renderive/plottable/Audio_Frequency_p.h"
|
||||
@@ -44,19 +46,30 @@ bool wait_ready(const std::shared_ptr<renderive::Waterfall>& waterfall, const st
|
||||
}
|
||||
return false;
|
||||
}
|
||||
bool wait_idle(renderive::Plot& plot) {
|
||||
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 (!plot.is_rendering() && !plot.d->active_render_tasks.load(std::memory_order_acquire))
|
||||
if (waterfall->ok() && time_axis->ok())
|
||||
return true;
|
||||
QThread::msleep(1);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
int stress_data_paths() {
|
||||
renderive::Plot plot;
|
||||
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");
|
||||
@@ -71,8 +84,7 @@ int stress_data_paths() {
|
||||
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);
|
||||
if (!wait_ready(waterfall, time_axis, audio, planisphere))
|
||||
return 1;
|
||||
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_Update_Mode update_modes[] = {
|
||||
@@ -96,7 +108,7 @@ int stress_data_paths() {
|
||||
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->give_data(expected_time);
|
||||
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()];
|
||||
@@ -109,48 +121,44 @@ int stress_data_paths() {
|
||||
}
|
||||
process_events(60);
|
||||
plot.pause_render();
|
||||
if (!wait_idle(plot))
|
||||
return 2;
|
||||
auto* waterfall_data = waterfall->d();
|
||||
if (waterfall_data->ring_buffer.col_count != frequency_sizes[stage] || waterfall_data->ring_buffer.row_count != time_sizes[stage])
|
||||
return 3;
|
||||
if (waterfall_data->image.width() != frequency_sizes[stage] || waterfall_data->image.height() != time_sizes[stage])
|
||||
return 4;
|
||||
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->image.width(), frequency_sizes[stage]);
|
||||
ASSERT_EQ(waterfall_data->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);
|
||||
if (expected == expected_frequency.end())
|
||||
return 5;
|
||||
if (std::abs(waterfall_data->ring_buffer.frequency_data(row, 0, 0)[0] - expected->second) > 0.000001)
|
||||
return 6;
|
||||
if (row && tick >= previous_tick)
|
||||
return 7;
|
||||
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 = time_axis->d();
|
||||
if (time_data->time_ticker.time_point_size != time_sizes[stage] || time_data->time_snapshot.size() != static_cast<std::size_t>(time_sizes[stage]))
|
||||
return 8;
|
||||
if (!time_data->time_data_count || time_data->time_snapshot[time_data->time_data_count - 1] != expected_time)
|
||||
return 9;
|
||||
int lower = qMin(time_data->time_ticker.lower, time_data->time_ticker.upper());
|
||||
int upper = qMax(time_data->time_ticker.lower, time_data->time_ticker.upper());
|
||||
for (const renderive::Time_Tick& tick : time_data->data) {
|
||||
if (tick.tick < lower || tick.tick > upper)
|
||||
return 10;
|
||||
ASSERT_EQ(time_data->timeline_snapshot.time_point_size, time_sizes[stage]);
|
||||
ASSERT_EQ(time_data->time_snapshot.size(), static_cast<std::size_t>(time_sizes[stage]));
|
||||
ASSERT_NE(time_data->time_data_count, 0);
|
||||
EXPECT_EQ(time_data->time_snapshot[time_data->time_data_count - 1], 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());
|
||||
for (const renderive::Timeline_Tick& tick : time_data->data) {
|
||||
EXPECT_GE(tick.tick, lower);
|
||||
EXPECT_LE(tick.tick, upper);
|
||||
}
|
||||
auto* audio_data = audio->d();
|
||||
if (audio_data->point_count != time_sizes[stage] || audio_data->power_snapshot.size() != static_cast<std::size_t>(time_sizes[stage]))
|
||||
return 11;
|
||||
if (!audio_data->data_count || std::abs(audio_data->power_snapshot[audio_data->data_count - 1] - expected_power) > 0.000001)
|
||||
return 12;
|
||||
auto* planisphere_data = planisphere->d();
|
||||
if (planisphere_data->data_list.empty())
|
||||
return 13;
|
||||
auto* audio_data = renderive::Audio_Frequency_Private_Access::data(audio.get());
|
||||
ASSERT_EQ(audio_data->point_count, time_sizes[stage]);
|
||||
ASSERT_EQ(audio_data->power_snapshot.size(), static_cast<std::size_t>(time_sizes[stage]));
|
||||
ASSERT_NE(audio_data->data_count, 0);
|
||||
EXPECT_NEAR(audio_data->power_snapshot[audio_data->data_count - 1], 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;
|
||||
if (std::abs(actual_position.x() - expected_position.x()) > 0.000001 || std::abs(actual_position.y() - expected_position.y()) > 0.000001)
|
||||
return 14;
|
||||
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) {
|
||||
@@ -158,16 +166,109 @@ int stress_data_paths() {
|
||||
process_events(8);
|
||||
}
|
||||
plot.pause_render();
|
||||
if (!wait_idle(plot))
|
||||
return 15;
|
||||
if (!planisphere->d()->data_list.empty())
|
||||
return 16;
|
||||
ASSERT_TRUE(wait_idle(plot));
|
||||
EXPECT_TRUE(renderive::Planisphere_Private_Access::data(planisphere.get())->data_list.empty());
|
||||
renderive::Memory_Stats stats = renderive::memory_stats();
|
||||
return stats.upstream_allocation_count && stats.upstream_peak_bytes ? 0 : 17;
|
||||
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->image.width(), 1);
|
||||
ASSERT_EQ(data->image.height(), 4);
|
||||
EXPECT_EQ(qRed(data->image.image().pixel(0, 0)), 10);
|
||||
EXPECT_EQ(qRed(data->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->image.width(), 1);
|
||||
ASSERT_EQ(data->image.height(), 4);
|
||||
EXPECT_EQ(qRed(data->image.image().pixel(0, 0)), 40);
|
||||
EXPECT_EQ(qRed(data->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(cache_node->cache_image.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));
|
||||
ASSERT_EQ(cache_node->cache_image.width(), 1);
|
||||
ASSERT_EQ(cache_node->cache_image.height(), 4);
|
||||
EXPECT_EQ(qRed(cache_node->cache_image.image().pixel(0, 3)), 40);
|
||||
}
|
||||
int main(int argc, char** argv) {
|
||||
qputenv("QT_QPA_PLATFORM", "offscreen");
|
||||
QApplication app(argc, argv);
|
||||
testing::InitGoogleTest(&argc, argv);
|
||||
renderive::start_render_scheduler();
|
||||
return stress_data_paths();
|
||||
return RUN_ALL_TESTS();
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
#include <cstdint>
|
||||
#include <gtest/gtest.h>
|
||||
#include <thread>
|
||||
#include <vector>
|
||||
#include "../Renderive/base/Memory.h"
|
||||
@@ -7,19 +8,22 @@ struct Payload {
|
||||
std::uint64_t value{};
|
||||
};
|
||||
}
|
||||
int main() {
|
||||
TEST(Memory_Default, AllocatesAndTracksDefaultResource) {
|
||||
std::pmr::vector<std::uint64_t> values(renderive::memory_resource());
|
||||
values.resize(4096, 7);
|
||||
auto owner = renderive::make_shared<Payload>();
|
||||
owner->value = values.front();
|
||||
if (owner->value != 7)
|
||||
return 1;
|
||||
EXPECT_EQ(owner->value, 7);
|
||||
std::thread release_thread([owner = std::move(owner)]() mutable {
|
||||
owner.reset();
|
||||
});
|
||||
release_thread.join();
|
||||
renderive::Memory_Stats stats = renderive::memory_stats();
|
||||
if (!stats.upstream_allocation_count || !stats.upstream_current_bytes || !stats.upstream_peak_bytes)
|
||||
return 2;
|
||||
return 0;
|
||||
EXPECT_GT(stats.upstream_allocation_count, 0);
|
||||
EXPECT_GT(stats.upstream_current_bytes, 0);
|
||||
EXPECT_GT(stats.upstream_peak_bytes, 0);
|
||||
}
|
||||
int main(int argc, char** argv) {
|
||||
testing::InitGoogleTest(&argc, argv);
|
||||
return RUN_ALL_TESTS();
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
#include <atomic>
|
||||
#include <gtest/gtest.h>
|
||||
#include <memory_resource>
|
||||
#include <new>
|
||||
#include <vector>
|
||||
@@ -20,7 +21,7 @@ void callback_deallocate(void*, void* address, std::size_t size, std::size_t ali
|
||||
std::pmr::new_delete_resource()->deallocate(address, size, alignment);
|
||||
}
|
||||
}
|
||||
int main() {
|
||||
TEST(Memory_Failure, ConvertsCallbackFailureToBadAlloc) {
|
||||
renderive::Memory_Callbacks callbacks;
|
||||
callbacks.context = &callback_state;
|
||||
callbacks.allocate = callback_allocate;
|
||||
@@ -71,5 +72,12 @@ int main() {
|
||||
catch (const std::bad_alloc&) {
|
||||
ring_buffer_failure_caught = true;
|
||||
}
|
||||
return direct_failure_caught && container_failure_caught && frame_failure_caught && ring_buffer_failure_caught ? 0 : 1;
|
||||
EXPECT_TRUE(direct_failure_caught);
|
||||
EXPECT_TRUE(container_failure_caught);
|
||||
EXPECT_TRUE(frame_failure_caught);
|
||||
EXPECT_TRUE(ring_buffer_failure_caught);
|
||||
}
|
||||
int main(int argc, char** argv) {
|
||||
testing::InitGoogleTest(&argc, argv);
|
||||
return RUN_ALL_TESTS();
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
#include <stdexcept>
|
||||
#include "../Renderive/architecture/Plot.h"
|
||||
#include <gtest/gtest.h>
|
||||
#include "../Renderive/plot/Plot.h"
|
||||
#include "../Renderive/base/Memory.h"
|
||||
namespace {
|
||||
void* callback_allocate(void*, std::size_t size, std::size_t alignment) {
|
||||
@@ -9,16 +10,14 @@ void callback_deallocate(void*, void* address, std::size_t size, std::size_t ali
|
||||
std::pmr::new_delete_resource()->deallocate(address, size, alignment);
|
||||
}
|
||||
}
|
||||
int main() {
|
||||
TEST(Memory_Late_Config, RejectsCallbacksAfterSchedulerStart) {
|
||||
renderive::start_render_scheduler();
|
||||
renderive::Memory_Callbacks callbacks;
|
||||
callbacks.allocate = callback_allocate;
|
||||
callbacks.deallocate = callback_deallocate;
|
||||
try {
|
||||
renderive::set_memory_callbacks(callbacks);
|
||||
}
|
||||
catch (const std::logic_error&) {
|
||||
return 0;
|
||||
}
|
||||
return 1;
|
||||
EXPECT_THROW(renderive::set_memory_callbacks(callbacks), std::logic_error);
|
||||
}
|
||||
int main(int argc, char** argv) {
|
||||
testing::InitGoogleTest(&argc, argv);
|
||||
return RUN_ALL_TESTS();
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
#include <QApplication>
|
||||
#include <stdexcept>
|
||||
#include "../Renderive/architecture/Plot.h"
|
||||
#include <gtest/gtest.h>
|
||||
#include "../Renderive/plot/Latency_Eager_Plot.h"
|
||||
#include "../Renderive/base/Memory.h"
|
||||
namespace {
|
||||
void* callback_allocate(void*, std::size_t size, std::size_t alignment) {
|
||||
@@ -10,19 +11,18 @@ void callback_deallocate(void*, void* address, std::size_t size, std::size_t ali
|
||||
std::pmr::new_delete_resource()->deallocate(address, size, alignment);
|
||||
}
|
||||
}
|
||||
int main(int argc, char** argv) {
|
||||
QApplication app(argc, argv);
|
||||
TEST(Memory_Plot_Late_Config, RejectsCallbacksAfterPlotCreatesMemory) {
|
||||
renderive::start_render_scheduler();
|
||||
renderive::Plot plot;
|
||||
renderive::Latency_Eager_Plot plot;
|
||||
plot.init();
|
||||
renderive::Memory_Callbacks callbacks;
|
||||
callbacks.allocate = callback_allocate;
|
||||
callbacks.deallocate = callback_deallocate;
|
||||
try {
|
||||
renderive::set_memory_callbacks(callbacks);
|
||||
}
|
||||
catch (const std::logic_error&) {
|
||||
return 0;
|
||||
}
|
||||
return 1;
|
||||
EXPECT_THROW(renderive::set_memory_callbacks(callbacks), std::logic_error);
|
||||
}
|
||||
int main(int argc, char** argv) {
|
||||
qputenv("QT_QPA_PLATFORM", "offscreen");
|
||||
QApplication app(argc, argv);
|
||||
testing::InitGoogleTest(&argc, argv);
|
||||
return RUN_ALL_TESTS();
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
#include <QEventLoop>
|
||||
#include <QThread>
|
||||
#include <QTime>
|
||||
#include <gtest/gtest.h>
|
||||
#include <atomic>
|
||||
#include <cmath>
|
||||
#include <thread>
|
||||
@@ -10,8 +11,9 @@
|
||||
#include "../Renderive/Axis/Axis.h"
|
||||
#include "../Renderive/Axis/Frequency_Axis.h"
|
||||
#include "../Renderive/Axis/Time_Axis.h"
|
||||
#include "../Renderive/architecture/Plot.h"
|
||||
#include "../Renderive/architecture/Plot_p.h"
|
||||
#include "../Renderive/architecture/Timeline_Stream.h"
|
||||
#include "../Renderive/plot/Latency_Eager_Plot.h"
|
||||
#include "../Renderive/plot/Plot_p.h"
|
||||
#include "../Renderive/base/Memory.h"
|
||||
#include "../Renderive/plottable/Audio_Frequency.h"
|
||||
#include "../Renderive/plottable/Spectrum.h"
|
||||
@@ -43,19 +45,19 @@ bool wait_ready(const std::shared_ptr<renderive::Waterfall>& waterfall, const st
|
||||
}
|
||||
return false;
|
||||
}
|
||||
bool wait_idle(renderive::Plot& plot) {
|
||||
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() && !plot.d->active_render_tasks.load(std::memory_order_acquire))
|
||||
if (!plot.is_rendering() && !renderive::Abs_Plot_Private_Access::active_render_tasks(plot))
|
||||
return true;
|
||||
QThread::msleep(1);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
int run_stress() {
|
||||
renderive::Plot plot;
|
||||
void run_stress() {
|
||||
renderive::Latency_Eager_Plot plot;
|
||||
plot.init();
|
||||
plot.resize(640, 360);
|
||||
plot.set_max_render_fps(1000);
|
||||
@@ -69,8 +71,7 @@ int run_stress() {
|
||||
auto audio = renderive::Audio_Frequency::Builder(data_node, time_axis, power_axis).set_time_point_size(128).set_key_range({-120.0, 0.0}).build();
|
||||
plot.show();
|
||||
plot.start_render(1000);
|
||||
if (!wait_ready(waterfall, spectrum, audio, time_axis))
|
||||
return 1;
|
||||
ASSERT_TRUE(wait_ready(waterfall, spectrum, audio, time_axis));
|
||||
std::atomic_bool running{true};
|
||||
std::atomic<int> pushes{0};
|
||||
std::vector<std::thread> workers;
|
||||
@@ -79,7 +80,7 @@ int run_stress() {
|
||||
for (int frame = 0; running.load(std::memory_order_acquire) && frame < 2500; ++frame) {
|
||||
int seed = frame + thread_index * 10000;
|
||||
QTime time = QTime::fromMSecsSinceStartOfDay(seed % 86400000);
|
||||
int tick = time_axis->give_data(time);
|
||||
int tick = time_axis->timeline_stream()->push_time(time);
|
||||
waterfall->give_data(tick, make_data(256, seed, renderive::Memory_Domain::Waterfall));
|
||||
spectrum->give_data(make_data(256, seed, renderive::Memory_Domain::Spectrum));
|
||||
audio->give_data(tick, -90.0 + static_cast<double>((seed % 120)));
|
||||
@@ -98,14 +99,17 @@ int run_stress() {
|
||||
worker.join();
|
||||
process_events(300);
|
||||
plot.pause_render();
|
||||
if (!wait_idle(plot))
|
||||
return 2;
|
||||
return pushes.load(std::memory_order_relaxed) > 1000 ? 0 : 3;
|
||||
ASSERT_TRUE(wait_idle(plot));
|
||||
EXPECT_GT(pushes.load(std::memory_order_relaxed), 1000);
|
||||
}
|
||||
}
|
||||
TEST(Multithread_Input_Stress, AcceptsConcurrentRenderableInput) {
|
||||
run_stress();
|
||||
}
|
||||
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_stress();
|
||||
return RUN_ALL_TESTS();
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
#include <QPushButton>
|
||||
#include <QThread>
|
||||
#include <QWheelEvent>
|
||||
#include <gtest/gtest.h>
|
||||
#include <memory>
|
||||
namespace {
|
||||
QPushButton* find_performance_button(QWidget* control) {
|
||||
@@ -41,7 +42,7 @@ void wheel_plot(QWidget* plot, const QPoint& pos, int delta) {
|
||||
QWheelEvent event(pos, plot->mapToGlobal(pos), QPoint(), QPoint(0, delta), Qt::NoButton, Qt::NoModifier, Qt::NoScrollPhase, false);
|
||||
QApplication::sendEvent(plot, &event);
|
||||
}
|
||||
bool pump(QApplication& app, renderive::Plot& plot, int milliseconds) {
|
||||
bool pump(QApplication& app, renderive::Latency_Eager_Plot& plot, int milliseconds) {
|
||||
QElapsedTimer timer;
|
||||
timer.start();
|
||||
while (timer.elapsed() < milliseconds) {
|
||||
@@ -52,12 +53,10 @@ bool pump(QApplication& app, renderive::Plot& plot, int milliseconds) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
int main(int argc, char** argv) {
|
||||
qputenv("QT_QPA_PLATFORM", "offscreen");
|
||||
qputenv("RENDERIVE_PERFORMANCE_LOG", "0");
|
||||
QApplication app(argc, argv);
|
||||
TEST(Performance_Shower_Interaction, RepeatedToggleDoubleClickAndWheelStayResponsive) {
|
||||
QApplication& app = *qobject_cast<QApplication*>(QApplication::instance());
|
||||
renderive::start_render_scheduler();
|
||||
renderive::Plot plot;
|
||||
renderive::Latency_Eager_Plot plot;
|
||||
plot.resize(320, 240);
|
||||
plot.init();
|
||||
plot.show();
|
||||
@@ -66,15 +65,13 @@ int main(int argc, char** argv) {
|
||||
control->show();
|
||||
app.processEvents();
|
||||
QPushButton* performance_button = find_performance_button(control.get());
|
||||
if (!performance_button)
|
||||
return 1;
|
||||
ASSERT_NE(performance_button, nullptr);
|
||||
QElapsedTimer total;
|
||||
total.start();
|
||||
for (int i = 0; i < 40; ++i) {
|
||||
click_button(performance_button);
|
||||
pump(app, plot, 15);
|
||||
if (total.elapsed() > 5000)
|
||||
return 2;
|
||||
ASSERT_LE(total.elapsed(), 5000);
|
||||
}
|
||||
double_click_button(performance_button);
|
||||
pump(app, plot, 200);
|
||||
@@ -93,8 +90,13 @@ int main(int argc, char** argv) {
|
||||
pump(app, plot, 10);
|
||||
}
|
||||
pump(app, plot, 200);
|
||||
if (!plot.get_use_performance_shower())
|
||||
return 3;
|
||||
EXPECT_TRUE(plot.get_use_performance_shower());
|
||||
plot.pause_render();
|
||||
return 0;
|
||||
}
|
||||
int main(int argc, char** argv) {
|
||||
qputenv("QT_QPA_PLATFORM", "offscreen");
|
||||
qputenv("RENDERIVE_PERFORMANCE_LOG", "0");
|
||||
QApplication app(argc, argv);
|
||||
testing::InitGoogleTest(&argc, argv);
|
||||
return RUN_ALL_TESTS();
|
||||
}
|
||||
|
||||
@@ -2,12 +2,13 @@
|
||||
#include <QElapsedTimer>
|
||||
#include <QEventLoop>
|
||||
#include <QThread>
|
||||
#include <gtest/gtest.h>
|
||||
#include <cmath>
|
||||
#include <memory>
|
||||
#include <thread>
|
||||
#include "../Renderive/Axis/Axis.h"
|
||||
#include "../Renderive/Axis/Frequency_Axis.h"
|
||||
#include "../Renderive/architecture/Plot.h"
|
||||
#include "../Renderive/plot/Latency_Eager_Plot.h"
|
||||
#include "../Renderive/plottable/Spectrum.h"
|
||||
namespace {
|
||||
std::vector<double> make_spectrum_data(int size, int frame) {
|
||||
@@ -25,7 +26,7 @@ void process_events(int ms) {
|
||||
}
|
||||
}
|
||||
void stress_remove_local_cache_renderable() {
|
||||
renderive::Plot plot;
|
||||
renderive::Latency_Eager_Plot plot;
|
||||
plot.init();
|
||||
plot.resize(640, 360);
|
||||
auto data = plot.create_renderable_node(plot.get_root_renderable(), "Data_Renderable");
|
||||
@@ -45,7 +46,7 @@ void stress_remove_local_cache_renderable() {
|
||||
}
|
||||
void stress_plot_destroy_with_pending_tasks() {
|
||||
for (int round = 0; round < 32; ++round) {
|
||||
auto plot = std::make_unique<renderive::Plot>();
|
||||
auto plot = std::make_unique<renderive::Latency_Eager_Plot>();
|
||||
plot->init();
|
||||
plot->resize(480, 240);
|
||||
auto data = plot->create_renderable_node(plot->get_root_renderable(), "Data_Renderable");
|
||||
@@ -69,7 +70,7 @@ void stress_plot_destroy_with_pending_tasks() {
|
||||
void stress_release_renderable_after_plot_destroy() {
|
||||
std::shared_ptr<renderive::Renderable> survivor;
|
||||
{
|
||||
renderive::Plot plot;
|
||||
renderive::Latency_Eager_Plot plot;
|
||||
plot.init();
|
||||
survivor = plot.create_renderable_node(plot.get_root_renderable(), "Survivor");
|
||||
}
|
||||
@@ -79,12 +80,16 @@ void stress_release_renderable_after_plot_destroy() {
|
||||
release_thread.join();
|
||||
}
|
||||
}
|
||||
int main(int argc, char** argv) {
|
||||
QApplication app(argc, argv);
|
||||
renderive::start_render_scheduler();
|
||||
TEST(Renderable_Lifecycle_Stress, HandlesRemovalDestroyAndLateRelease) {
|
||||
stress_remove_local_cache_renderable();
|
||||
stress_plot_destroy_with_pending_tasks();
|
||||
stress_release_renderable_after_plot_destroy();
|
||||
process_events(250);
|
||||
return 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();
|
||||
}
|
||||
|
||||
@@ -0,0 +1,56 @@
|
||||
#include <QTime>
|
||||
#include <gtest/gtest.h>
|
||||
#include "../Renderive/architecture/Plot_Render_Context.h"
|
||||
#include "../Renderive/architecture/Timeline_Stream.h"
|
||||
#include "../Renderive/base/Memory.h"
|
||||
TEST(Timeline_Stream_Consistency, ReusesOneTimelineSnapshotInsideFrame) {
|
||||
auto stream = renderive::make_shared<renderive::Timeline_Stream>();
|
||||
stream->set_time_point_size(8);
|
||||
int first_tick = stream->push_time(QTime(10, 0, 0));
|
||||
renderive::Plot_Render_Snapshot frame;
|
||||
auto first_snapshot = frame.timeline_snapshot(stream);
|
||||
int second_tick = stream->push_time(QTime(10, 0, 1));
|
||||
auto second_snapshot = frame.timeline_snapshot(stream);
|
||||
EXPECT_EQ(first_snapshot, second_snapshot);
|
||||
EXPECT_EQ(first_snapshot->time_by_tick(first_tick), QTime(10, 0, 0));
|
||||
EXPECT_NE(first_snapshot->time_by_tick(second_tick), QTime(10, 0, 1));
|
||||
renderive::Plot_Render_Snapshot next_frame;
|
||||
auto next_snapshot = next_frame.timeline_snapshot(stream);
|
||||
EXPECT_NE(next_snapshot->version, first_snapshot->version);
|
||||
EXPECT_EQ(next_snapshot->time_by_tick(second_tick), QTime(10, 0, 1));
|
||||
}
|
||||
TEST(Timeline_Stream_Consistency, KeepsTickAndTimePairedInSnapshot) {
|
||||
auto stream = renderive::make_shared<renderive::Timeline_Stream>();
|
||||
stream->set_time_point_size(16);
|
||||
stream->set_tick_space(2);
|
||||
for (int i = 0; i < 12; ++i)
|
||||
stream->push_time(QTime::fromMSecsSinceStartOfDay(i * 1000));
|
||||
renderive::Timeline_Stream_Snapshot snapshot = stream->capture();
|
||||
ASSERT_FALSE(snapshot.ticks.empty());
|
||||
for (const renderive::Timeline_Tick& tick : snapshot.ticks)
|
||||
EXPECT_EQ(snapshot.time_by_tick(tick.tick), tick.time);
|
||||
}
|
||||
TEST(Timeline_Stream_Consistency, DefaultsNewTimeToCoordEnd) {
|
||||
auto stream = renderive::make_shared<renderive::Timeline_Stream>();
|
||||
stream->set_time_point_size(4);
|
||||
int tick = stream->push_time(QTime(10, 0, 0));
|
||||
renderive::Timeline_Stream_Snapshot snapshot = stream->capture();
|
||||
EXPECT_FALSE(snapshot.start_coord_to_end_coord);
|
||||
EXPECT_EQ(tick, snapshot.upper());
|
||||
EXPECT_EQ(snapshot.time_by_tick(snapshot.upper()), QTime(10, 0, 0));
|
||||
}
|
||||
TEST(Timeline_Stream_Consistency, SupportsNewTimeToCoordStart) {
|
||||
auto stream = renderive::make_shared<renderive::Timeline_Stream>();
|
||||
stream->set_time_point_size(4);
|
||||
stream->set_start_coord_to_end_coord(true);
|
||||
for (int i = 0; i < 4; ++i)
|
||||
stream->push_time(QTime::fromMSecsSinceStartOfDay(i));
|
||||
renderive::Timeline_Stream_Snapshot snapshot = stream->capture();
|
||||
EXPECT_TRUE(snapshot.start_coord_to_end_coord);
|
||||
EXPECT_EQ(snapshot.time_by_tick(snapshot.lower), QTime::fromMSecsSinceStartOfDay(3));
|
||||
EXPECT_EQ(snapshot.time_by_tick(snapshot.upper()), QTime::fromMSecsSinceStartOfDay(0));
|
||||
}
|
||||
int main(int argc, char** argv) {
|
||||
testing::InitGoogleTest(&argc, argv);
|
||||
return RUN_ALL_TESTS();
|
||||
}
|
||||
@@ -1,8 +1,9 @@
|
||||
#include "../Renderive/architecture/Update_Completion.h"
|
||||
#include "asio.hpp"
|
||||
#include <gtest/gtest.h>
|
||||
#include <atomic>
|
||||
#include <future>
|
||||
int main() {
|
||||
TEST(Update_Completion, CompletesStageWaitersAndCancelledWaiters) {
|
||||
asio::io_context io;
|
||||
auto state = renderive::make_update_state();
|
||||
renderive::Update_Ticket ticket(state);
|
||||
@@ -15,13 +16,11 @@ int main() {
|
||||
}));
|
||||
state->complete_until(renderive::Update_Stage::Input_Released);
|
||||
io.poll();
|
||||
if (rendered_count.load(std::memory_order_relaxed) != 0)
|
||||
return 1;
|
||||
EXPECT_EQ(rendered_count.load(std::memory_order_relaxed), 0);
|
||||
state->complete_until(renderive::Update_Stage::Rendered);
|
||||
io.restart();
|
||||
io.run();
|
||||
if (rendered_count.load(std::memory_order_relaxed) != 1)
|
||||
return 2;
|
||||
EXPECT_EQ(rendered_count.load(std::memory_order_relaxed), 1);
|
||||
ticket.async_wait(renderive::Update_Stage::Input_Released, asio::bind_executor(io.get_executor(), [&](std::error_code error, renderive::Update_Outcome outcome) {
|
||||
if (error || outcome != renderive::Update_Outcome::Completed)
|
||||
std::terminate();
|
||||
@@ -29,8 +28,7 @@ int main() {
|
||||
}));
|
||||
io.restart();
|
||||
io.run();
|
||||
if (input_count.load(std::memory_order_relaxed) != 1)
|
||||
return 3;
|
||||
EXPECT_EQ(input_count.load(std::memory_order_relaxed), 1);
|
||||
auto cancelled_state = renderive::make_update_state();
|
||||
renderive::Update_Ticket cancelled_ticket(cancelled_state);
|
||||
std::atomic_int cancel_count{};
|
||||
@@ -42,5 +40,9 @@ int main() {
|
||||
cancelled_state->complete_all(std::make_error_code(std::errc::operation_canceled), renderive::Update_Outcome::Cancelled);
|
||||
io.restart();
|
||||
io.run();
|
||||
return cancel_count.load(std::memory_order_relaxed) == 1 ? 0 : 4;
|
||||
EXPECT_EQ(cancel_count.load(std::memory_order_relaxed), 1);
|
||||
}
|
||||
int main(int argc, char** argv) {
|
||||
testing::InitGoogleTest(&argc, argv);
|
||||
return RUN_ALL_TESTS();
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user