展示优化

This commit is contained in:
2026-08-03 11:22:22 +08:00
parent 44cf49c0e2
commit 86c01e4cc5
15 changed files with 384 additions and 285 deletions
+118 -80
View File
@@ -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);