展示优化
This commit is contained in:
+118
-80
@@ -31,6 +31,7 @@
|
||||
#include <initializer_list>
|
||||
#include <latch>
|
||||
#include <limits>
|
||||
#include <map>
|
||||
#include <rigtorp/MPMCQueue.h>
|
||||
#include <sstream>
|
||||
#include <string>
|
||||
@@ -248,31 +249,33 @@ std::vector<std::string> read_lines(const std::filesystem::path& path) {
|
||||
lines.push_back(line);
|
||||
return lines;
|
||||
}
|
||||
std::vector<std::string> split_csv_row(const std::string& line) {
|
||||
std::vector<std::string> cells;
|
||||
std::string cell;
|
||||
bool in_quote = false;
|
||||
for (std::size_t i = 0; i < line.size(); ++i) {
|
||||
char ch = line[i];
|
||||
if (ch == '"') {
|
||||
if (in_quote && i + 1 < line.size() && line[i + 1] == '"') {
|
||||
cell.push_back('"');
|
||||
++i;
|
||||
}
|
||||
else {
|
||||
in_quote = !in_quote;
|
||||
}
|
||||
using Parsed_Log_Record = std::map<std::string, std::string>;
|
||||
std::vector<Parsed_Log_Record> read_log_records(const std::filesystem::path& path, std::string_view kind) {
|
||||
std::vector<Parsed_Log_Record> records;
|
||||
Parsed_Log_Record current;
|
||||
bool in_record = false;
|
||||
std::string begin = "[" + std::string(kind) + "]";
|
||||
std::string end = "[/" + std::string(kind) + "]";
|
||||
for (const auto& line : read_lines(path)) {
|
||||
if (line == begin) {
|
||||
current.clear();
|
||||
in_record = true;
|
||||
continue;
|
||||
}
|
||||
if (ch == ',' && !in_quote) {
|
||||
cells.push_back(cell);
|
||||
cell.clear();
|
||||
if (line == end) {
|
||||
if (in_record)
|
||||
records.push_back(std::move(current));
|
||||
current = {};
|
||||
in_record = false;
|
||||
continue;
|
||||
}
|
||||
cell.push_back(ch);
|
||||
if (!in_record)
|
||||
continue;
|
||||
std::size_t separator = line.find('=');
|
||||
if (separator != std::string::npos)
|
||||
current.emplace(line.substr(0, separator), line.substr(separator + 1));
|
||||
}
|
||||
cells.push_back(cell);
|
||||
return cells;
|
||||
return records;
|
||||
}
|
||||
Frame_Feedback_Event_Record feedback_event(std::string event, const Frame_Lifecycle_Record& frame, std::uint64_t timestamp_ns) {
|
||||
Frame_Feedback_Event_Record record;
|
||||
@@ -1471,7 +1474,7 @@ TEST(Renderive_Frame_Feedback, AdmissionDeadlineWaitIsNotProducerStarvation) {
|
||||
auto frame = feedback_frame(3, 100000000, 2000000, 1000000);
|
||||
controller.note_middle_published(frame.transition_12_ns);
|
||||
controller.on_render_completed(frame);
|
||||
controller.note_admission_deadline_wait(20000000, frame.transition_12_ns + 1);
|
||||
controller.note_admission_wait(20000000, frame.transition_12_ns + 1);
|
||||
controller.on_frame_presented(frame);
|
||||
|
||||
EXPECT_EQ(controller.state().consumer_sample_accepted_count, accepted + 1);
|
||||
@@ -1596,6 +1599,26 @@ TEST(Renderive_Frame_Feedback, ProducerStarvationCountsStateTransitionOnce) {
|
||||
controller.observe_runtime(deadline + 500000000, false, true, false);
|
||||
EXPECT_EQ(controller.state().producer_starvation_count, 1u);
|
||||
}
|
||||
TEST(Renderive_Frame_Feedback, ProducerIntervalUsesActualRenderStart) {
|
||||
Low_Latency_Feedback_Controller controller;
|
||||
auto first = feedback_frame(1, 100000000, 2000000, 1000000);
|
||||
auto second = feedback_frame(2, 116666667, 2000000, 1000000);
|
||||
controller.on_render_completed(first);
|
||||
controller.on_render_completed(second);
|
||||
EXPECT_NEAR(static_cast<double>(controller.state().producer_interval_current_ns), 16666667.0, 1.0);
|
||||
}
|
||||
TEST(Renderive_Frame_Feedback, AdmissionWaitRecordsActualElapsedTimeAndClears) {
|
||||
Low_Latency_Feedback_Controller controller;
|
||||
controller.note_admission_wait(5000000, 100000000);
|
||||
EXPECT_EQ(controller.state().admission_wait_remaining_ns, 5000000u);
|
||||
controller.note_render_admitted(104000000);
|
||||
EXPECT_EQ(controller.state().admission_wait_elapsed_ns, 4000000u);
|
||||
EXPECT_EQ(controller.state().admission_wait_remaining_ns, 0u);
|
||||
controller.note_admission_wait(5000000, 200000000);
|
||||
controller.cancel_admission_wait(201000000);
|
||||
EXPECT_EQ(controller.state().admission_wait_elapsed_ns, 0u);
|
||||
EXPECT_EQ(controller.state().admission_wait_remaining_ns, 0u);
|
||||
}
|
||||
|
||||
TEST(Renderive_Frame_Flow, LatencyEagerRendersWhileMiddleIsPending) {
|
||||
Low_Latency_Frame_Flow flow;
|
||||
@@ -1609,19 +1632,33 @@ TEST(Renderive_Frame_Flow, LatencyEagerRendersWhileMiddleIsPending) {
|
||||
EXPECT_EQ(host.paint_requests, 1);
|
||||
EXPECT_EQ(host.render_attempts, 1);
|
||||
}
|
||||
TEST(Renderive_Frame_Flow, PendingPresentRequestSuppressesDuplicateRequest) {
|
||||
Low_Latency_Frame_Flow flow;
|
||||
auto published = publish_low_latency_flow_frame(flow, 1, 20);
|
||||
ASSERT_TRUE(published.request_present);
|
||||
Flow_Test_Host host;
|
||||
host.snapshot.middle_has_new_frame = true;
|
||||
flow.attach(host);
|
||||
flow.activate_view(30);
|
||||
EXPECT_EQ(host.paint_requests, 0);
|
||||
}
|
||||
|
||||
TEST(Renderive_Frame_Flow, LowLatencyEmitsFeedbackEventsThroughHost) {
|
||||
Low_Latency_Frame_Flow flow;
|
||||
Flow_Test_Host host;
|
||||
host.snapshot.dirty = true;
|
||||
flow.attach(host);
|
||||
|
||||
flow.notify_model_dirty(Dirty_Reason::Core_Data, 10);
|
||||
flow.activate_view(20);
|
||||
|
||||
EXPECT_NE(std::find(host.feedback_events.begin(), host.feedback_events.end(), "InputChanged"), host.feedback_events.end());
|
||||
EXPECT_NE(std::find(host.feedback_events.begin(), host.feedback_events.end(), "RenderAdmitted"), host.feedback_events.end());
|
||||
EXPECT_EQ(std::find(host.feedback_events.begin(), host.feedback_events.end(), "RenderStarted"), host.feedback_events.end());
|
||||
Frame_Lifecycle_Record frame;
|
||||
frame.render_begin_ns = 30;
|
||||
frame.render_end_ns = 40;
|
||||
flow.notify_render_finished(frame, 40);
|
||||
EXPECT_NE(std::find(host.feedback_events.begin(), host.feedback_events.end(), "RenderStarted"), host.feedback_events.end());
|
||||
EXPECT_NE(std::find(host.feedback_events.begin(), host.feedback_events.end(), "RenderCompleted"), host.feedback_events.end());
|
||||
}
|
||||
|
||||
TEST(Renderive_Frame_Pipeline, NewRenderSupersedesOldMiddle) {
|
||||
@@ -1638,7 +1675,7 @@ TEST(Renderive_Frame_Pipeline, NewRenderSupersedesOldMiddle) {
|
||||
EXPECT_TRUE(third.middle_published);
|
||||
ASSERT_NE(third.superseded_frame_record, nullptr);
|
||||
EXPECT_EQ(third.superseded_frame_record->frame_id, 2u);
|
||||
EXPECT_TRUE(third.update_coalesced);
|
||||
EXPECT_TRUE(third.duplicate_present_request_suppressed);
|
||||
}
|
||||
|
||||
TEST(Renderive_Frame_Pipeline, OnlyOneUpdateRemainsPosted) {
|
||||
@@ -1649,7 +1686,7 @@ TEST(Renderive_Frame_Pipeline, OnlyOneUpdateRemainsPosted) {
|
||||
|
||||
auto repeated = pipeline.request_present(30);
|
||||
EXPECT_FALSE(repeated.request_present);
|
||||
EXPECT_TRUE(repeated.update_coalesced);
|
||||
EXPECT_TRUE(repeated.duplicate_present_request_suppressed);
|
||||
EXPECT_TRUE(Low_Latency_Frame_Pipeline_Test_Probe::paint_request_pending(pipeline));
|
||||
}
|
||||
TEST(Renderive_Frame_Flow, LowLatencyOwnsActiveState) {
|
||||
@@ -2208,6 +2245,27 @@ TEST(Renderive_Hover_Tooltip, DefaultTextPenContrastsWithDefaultBackground) {
|
||||
EXPECT_EQ(state.tooltip_background_brush.color, Color::white());
|
||||
EXPECT_EQ(state.tooltip_text_pen.color, Color::black());
|
||||
}
|
||||
TEST(Renderive_Performance_Overlay, MetricUpdatesDoNotRequestIndependentRepaint) {
|
||||
auto state = std::make_shared<Performance_Worker_State>();
|
||||
state->enabled.store(true, std::memory_order_release);
|
||||
std::atomic_int update_count{};
|
||||
state->update_callback = [&update_count]() {
|
||||
update_count.fetch_add(1, std::memory_order_relaxed);
|
||||
};
|
||||
Performance_Frame_Record frame = performance_frame(1);
|
||||
ASSERT_TRUE(state->metric_queue.try_push(frame));
|
||||
schedule_performance_metrics_worker(state);
|
||||
ASSERT_TRUE(wait_until([&]() {
|
||||
return state->published_snapshot.load(std::memory_order_acquire) && !state->worker_active.load(std::memory_order_acquire);
|
||||
}));
|
||||
EXPECT_EQ(update_count.load(std::memory_order_acquire), 0);
|
||||
state->snapshot_rebuild_requested.store(true, std::memory_order_release);
|
||||
schedule_performance_metrics_worker(state, true);
|
||||
ASSERT_TRUE(wait_until([&]() {
|
||||
return update_count.load(std::memory_order_acquire) == 1 && !state->worker_active.load(std::memory_order_acquire);
|
||||
}));
|
||||
state->cancelled.store(true, std::memory_order_release);
|
||||
}
|
||||
TEST(Renderive_Performance_Overlay, WorkerConsumesSharedFrameRecord) {
|
||||
auto state = std::make_shared<Performance_Worker_State>();
|
||||
state->enabled.store(true, std::memory_order_release);
|
||||
@@ -2256,7 +2314,7 @@ TEST(Renderive_Performance_Overlay, LowLatencyDiagnosticsExposePresentFeedbackFi
|
||||
frame->refresh.producer_starvation_count = 2;
|
||||
frame->refresh.middle_superseded_count = 3;
|
||||
frame->refresh.update_posted_count = 4;
|
||||
frame->refresh.update_coalesced_count = 1;
|
||||
frame->refresh.duplicate_present_request_suppressed_count = 1;
|
||||
frame->refresh.update_ack_count = 3;
|
||||
frame->refresh.update_repost_count = 1;
|
||||
shower.collect_frame(frame);
|
||||
@@ -2275,7 +2333,7 @@ TEST(Renderive_Performance_Overlay, LowLatencyDiagnosticsExposePresentFeedbackFi
|
||||
EXPECT_NE(text.find("update_to_paint"), std::string::npos);
|
||||
EXPECT_NE(text.find("producer_starvation"), std::string::npos);
|
||||
EXPECT_NE(text.find("middle_superseded"), std::string::npos);
|
||||
EXPECT_NE(text.find("coalesced:"), std::string::npos);
|
||||
EXPECT_NE(text.find("duplicate_suppressed:"), std::string::npos);
|
||||
}
|
||||
|
||||
TEST(Renderive_Performance_Overlay, PerformanceLogDisabledWritesNothing) {
|
||||
@@ -2290,8 +2348,8 @@ TEST(Renderive_Performance_Overlay, PerformanceLogDisabledWritesNothing) {
|
||||
shower.set_enabled(false);
|
||||
shower.collect_frame(performance_frame(1));
|
||||
performance_log_service().shutdown();
|
||||
EXPECT_FALSE(std::filesystem::exists(directory / "disabled_frames.csv"));
|
||||
EXPECT_FALSE(std::filesystem::exists(directory / "disabled_feedback.csv"));
|
||||
EXPECT_FALSE(std::filesystem::exists(directory / "disabled_frames.log"));
|
||||
EXPECT_FALSE(std::filesystem::exists(directory / "disabled_feedback.log"));
|
||||
EXPECT_FALSE(std::filesystem::exists(directory / "disabled_meta.json"));
|
||||
std::filesystem::remove_all(directory);
|
||||
}
|
||||
@@ -2320,18 +2378,20 @@ TEST(Renderive_Performance_Overlay, PerformanceLogWorksWhenOverlayHidden) {
|
||||
shower.collect_feedback_event(event);
|
||||
performance_log_service().shutdown();
|
||||
|
||||
auto frame_lines = read_lines(directory / "hidden_frames.csv");
|
||||
auto feedback_lines = read_lines(directory / "hidden_feedback.csv");
|
||||
ASSERT_GE(frame_lines.size(), 2u);
|
||||
ASSERT_GE(feedback_lines.size(), 2u);
|
||||
EXPECT_EQ(frame_lines.front(), performance_frame_csv_header());
|
||||
EXPECT_EQ(feedback_lines.front(), performance_feedback_csv_header());
|
||||
EXPECT_EQ(split_csv_row(frame_lines.front()).size(), split_csv_row(frame_lines[1]).size());
|
||||
EXPECT_EQ(split_csv_row(feedback_lines.front()).size(), split_csv_row(feedback_lines[1]).size());
|
||||
auto frame_cells = split_csv_row(frame_lines[1]);
|
||||
ASSERT_GT(frame_cells.size(), 4u);
|
||||
EXPECT_EQ(frame_cells[2], "42");
|
||||
EXPECT_EQ(frame_cells[3], "hidden,plot\"a");
|
||||
auto frame_records = read_log_records(directory / "hidden_frames.log", "frame");
|
||||
auto feedback_records = read_log_records(directory / "hidden_feedback.log", "feedback");
|
||||
ASSERT_EQ(frame_records.size(), 1u);
|
||||
ASSERT_EQ(feedback_records.size(), 1u);
|
||||
const auto& frame_record = frame_records.front();
|
||||
EXPECT_EQ(frame_record.at("schema_version"), "3");
|
||||
EXPECT_EQ(frame_record.at("plot_id"), "42");
|
||||
EXPECT_EQ(frame_record.at("plot_name"), "hidden,plot\"a");
|
||||
EXPECT_TRUE(frame_record.contains("render_slot_idle_wait_ns"));
|
||||
EXPECT_TRUE(frame_record.contains("executor_queue_wait_current_ns"));
|
||||
EXPECT_TRUE(frame_record.contains("executor_started_topologies"));
|
||||
EXPECT_TRUE(frame_record.contains("executor_completed_topologies"));
|
||||
EXPECT_TRUE(frame_record.contains("render_request_to_start_ns"));
|
||||
EXPECT_TRUE(frame_record.contains("duplicate_present_request_suppressed"));
|
||||
EXPECT_TRUE(std::filesystem::exists(directory / "hidden_meta.json"));
|
||||
std::filesystem::remove_all(directory);
|
||||
}
|
||||
@@ -2357,15 +2417,13 @@ TEST(Renderive_Performance_Overlay, PerformanceLogSeparatesMultiplePlots) {
|
||||
second.collect_frame(performance_frame(2));
|
||||
performance_log_service().shutdown();
|
||||
|
||||
auto lines = read_lines(directory / "multi_frames.csv");
|
||||
ASSERT_GE(lines.size(), 3u);
|
||||
auto records = read_log_records(directory / "multi_frames.log", "frame");
|
||||
ASSERT_EQ(records.size(), 2u);
|
||||
bool saw_spectrum = false;
|
||||
bool saw_waterfall = false;
|
||||
for (std::size_t i = 1; i < lines.size(); ++i) {
|
||||
auto cells = split_csv_row(lines[i]);
|
||||
ASSERT_GT(cells.size(), 4u);
|
||||
saw_spectrum = saw_spectrum || (cells[2] == "11" && cells[3] == "spectrum");
|
||||
saw_waterfall = saw_waterfall || (cells[2] == "12" && cells[3] == "waterfall");
|
||||
for (const auto& record : records) {
|
||||
saw_spectrum = saw_spectrum || (record.at("plot_id") == "11" && record.at("plot_name") == "spectrum");
|
||||
saw_waterfall = saw_waterfall || (record.at("plot_id") == "12" && record.at("plot_name") == "waterfall");
|
||||
}
|
||||
EXPECT_TRUE(saw_spectrum);
|
||||
EXPECT_TRUE(saw_waterfall);
|
||||
@@ -2398,27 +2456,20 @@ TEST(Renderive_Performance_Overlay, FeedbackEventLogUsesRuntimeEventRecord) {
|
||||
shower.collect_feedback_event(event);
|
||||
performance_log_service().shutdown();
|
||||
|
||||
auto lines = read_lines(directory / "feedback_feedback.csv");
|
||||
ASSERT_GE(lines.size(), 2u);
|
||||
auto header = split_csv_row(lines.front());
|
||||
auto row = split_csv_row(lines[1]);
|
||||
ASSERT_EQ(header.size(), row.size());
|
||||
auto index_of = [&](const std::string& name) {
|
||||
auto it = std::find(header.begin(), header.end(), name);
|
||||
EXPECT_NE(it, header.end());
|
||||
return static_cast<std::size_t>(std::distance(header.begin(), it));
|
||||
};
|
||||
EXPECT_EQ(row[index_of("event")], "ProducerStarvationEnter");
|
||||
EXPECT_EQ(row[index_of("mode_before")], "latency_eager");
|
||||
EXPECT_EQ(row[index_of("mode_after")], "consumer_paced");
|
||||
EXPECT_EQ(row[index_of("dirty")], "1");
|
||||
EXPECT_EQ(row[index_of("producer_starved")], "1");
|
||||
EXPECT_EQ(row[index_of("effective_interval_before_ns")], "16666667");
|
||||
EXPECT_EQ(row[index_of("effective_interval_after_ns")], "33333333");
|
||||
auto records = read_log_records(directory / "feedback_feedback.log", "feedback");
|
||||
ASSERT_EQ(records.size(), 1u);
|
||||
const auto& record = records.front();
|
||||
EXPECT_EQ(record.at("event"), "ProducerStarvationEnter");
|
||||
EXPECT_EQ(record.at("mode_before"), "latency_eager");
|
||||
EXPECT_EQ(record.at("mode_after"), "consumer_paced");
|
||||
EXPECT_EQ(record.at("dirty"), "1");
|
||||
EXPECT_EQ(record.at("producer_starved"), "1");
|
||||
EXPECT_EQ(record.at("effective_interval_before_ns"), "16666667");
|
||||
EXPECT_EQ(record.at("effective_interval_after_ns"), "33333333");
|
||||
std::filesystem::remove_all(directory);
|
||||
}
|
||||
|
||||
TEST(Renderive_Performance_Overlay, SummaryUsesMultipleColumnsAndTextRoles) {
|
||||
TEST(Renderive_Performance_Overlay, SummaryUsesSingleColumnAndTextRoles) {
|
||||
Performance_Overlay shower;
|
||||
Performance_Overlay_Options options;
|
||||
options.section_color = Color{10, 20, 30, 255};
|
||||
@@ -2445,26 +2496,13 @@ TEST(Renderive_Performance_Overlay, SummaryUsesMultipleColumnsAndTextRoles) {
|
||||
bool has_label = false;
|
||||
bool has_value = false;
|
||||
bool has_warning = false;
|
||||
int max_column = 0;
|
||||
std::array<double, 4> section_x{};
|
||||
std::array<bool, 4> saw_section_column{};
|
||||
for (const auto& run : snapshot->text_runs) {
|
||||
max_column = std::max(max_column, run.column_index);
|
||||
EXPECT_EQ(run.column_index, 0);
|
||||
has_section = has_section || run.role == Performance_Text_Role::Section;
|
||||
has_label = has_label || run.role == Performance_Text_Role::Label;
|
||||
has_value = has_value || run.role == Performance_Text_Role::Value;
|
||||
has_warning = has_warning || run.role == Performance_Text_Role::Warning;
|
||||
if (run.role == Performance_Text_Role::Section && run.column_index >= 0 && run.column_index < 4) {
|
||||
if (!saw_section_column[static_cast<std::size_t>(run.column_index)]) {
|
||||
section_x[static_cast<std::size_t>(run.column_index)] = run.rect.x;
|
||||
saw_section_column[static_cast<std::size_t>(run.column_index)] = true;
|
||||
}
|
||||
else {
|
||||
EXPECT_EQ(run.rect.x, section_x[static_cast<std::size_t>(run.column_index)]);
|
||||
}
|
||||
}
|
||||
}
|
||||
EXPECT_GE(max_column, 2);
|
||||
EXPECT_TRUE(has_section);
|
||||
EXPECT_TRUE(has_label);
|
||||
EXPECT_TRUE(has_value);
|
||||
|
||||
Reference in New Issue
Block a user