35 static std::mutex _sound_perf_lock;
36 static std::atomic<bool> _sound_perf_pending;
37 static std::vector<TimingMeasurement> _sound_perf_measurements;
77 explicit PerformanceData(
double expected_rate) : expected_rate(expected_rate), next_index(0), prev_index(0), num_valid(0) { }
82 this->durations[this->next_index] = end_time - start_time;
83 this->timestamps[this->next_index] = start_time;
84 this->prev_index = this->next_index;
85 this->next_index += 1;
93 this->timestamps[this->next_index] = this->acc_timestamp;
94 this->durations[this->next_index] = this->acc_duration;
95 this->prev_index = this->next_index;
96 this->next_index += 1;
100 this->acc_duration = 0;
101 this->acc_timestamp = start_time;
107 this->acc_duration += duration;
113 if (this->durations[this->prev_index] != INVALID_DURATION) {
114 this->timestamps[this->next_index] = start_time;
115 this->durations[this->next_index] = INVALID_DURATION;
116 this->prev_index = this->next_index;
117 this->next_index += 1;
119 this->num_valid += 1;
126 count = std::min(count, this->num_valid);
128 int first_point = this->prev_index - count;
133 for (
int i = first_point; i < first_point + count; i++) {
135 if (d != INVALID_DURATION) {
143 if (count == 0)
return 0;
151 int point = this->prev_index;
152 int last_point = this->next_index - this->num_valid;
166 while (point != last_point) {
168 if (this->durations[point] != INVALID_DURATION) {
169 total += last - this->timestamps[point];
172 last = this->timestamps[point];
178 if (total == 0 || count == 0)
return 0;
233 using namespace std::chrono;
234 return (
TimingMeasurement)time_point_cast<microseconds>(high_resolution_clock::now()).time_since_epoch().count();
269 std::lock_guard lk(_sound_perf_lock);
271 _sound_perf_measurements.push_back(this->start_time);
272 _sound_perf_measurements.push_back(end);
273 _sound_perf_pending.store(
true, std::memory_order_release);
368 static const char * GetAIName(
int ai_index)
371 return Company::Get(ai_index)->ai_info->GetName().c_str();
375 static constexpr
NWidgetPart _framerate_window_widgets[] = {
420 inline void SetRate(
double value,
double target)
422 const double threshold_good = target * 0.95;
423 const double threshold_bad = target * 2 / 3;
424 this->value = (uint32_t)(value * 100);
425 this->strid = (value > threshold_good) ? STR_FRAMERATE_FPS_GOOD : (value < threshold_bad) ? STR_FRAMERATE_FPS_BAD : STR_FRAMERATE_FPS_WARN;
428 inline void SetTime(
double value,
double target)
430 const double threshold_good = target / 3;
431 const double threshold_bad = target;
432 this->value = (uint32_t)(value * 100);
433 this->strid = (value < threshold_good) ? STR_FRAMERATE_MS_GOOD : (value > threshold_bad) ? STR_FRAMERATE_MS_BAD : STR_FRAMERATE_MS_WARN;
436 inline void InsertDParams(uint n)
const
455 this->showing_memory =
true;
457 this->num_displayed = this->num_active;
466 if (this->small != this->
IsShaded()) {
468 this->GetWidget<NWidgetLeaf>(WID_FRW_CAPTION)->SetDataTip(this->small ? STR_FRAMERATE_CAPTION_SMALL : STR_FRAMERATE_CAPTION, STR_TOOLTIP_WINDOW_TITLE_DRAG_THIS);
483 bool have_script =
false;
486 if (this->small)
return;
500 if (this->showing_memory != have_script) {
501 NWidgetStacked *plane = this->GetWidget<NWidgetStacked>(WID_FRW_SEL_MEMORY);
503 this->showing_memory = have_script;
506 if (new_active != this->num_active) {
507 this->num_active = new_active;
510 sb->
SetCapacity(std::min(this->num_displayed, this->num_active));
515 void SetStringParameters(
WidgetID widget)
const override
518 case WID_FRW_CAPTION:
520 if (!this->small)
break;
522 this->rate_gameloop.InsertDParams(1);
523 this->speed_gameloop.InsertDParams(3);
526 case WID_FRW_RATE_GAMELOOP:
528 this->rate_gameloop.InsertDParams(1);
530 case WID_FRW_RATE_DRAWING:
532 this->rate_drawing.InsertDParams(1);
534 case WID_FRW_RATE_FACTOR:
535 this->speed_gameloop.InsertDParams(0);
537 case WID_FRW_INFO_DATA_POINTS:
546 case WID_FRW_RATE_GAMELOOP:
552 case WID_FRW_RATE_DRAWING:
558 case WID_FRW_RATE_FACTOR:
564 case WID_FRW_TIMES_NAMES: {
570 if (
_pf_data[e].num_valid == 0)
continue;
579 size.width = std::max(size.width, line_size.width);
584 case WID_FRW_TIMES_CURRENT:
585 case WID_FRW_TIMES_AVERAGE:
586 case WID_FRW_ALLOCSIZE: {
591 size.width = std::max(size.width, item_size.width);
605 int drawable = this->num_displayed;
610 if (
_pf_data[e].num_valid == 0)
continue;
614 values[e].InsertDParams(0);
618 if (drawable == 0)
break;
623 void DrawElementAllocationsColumn(
const Rect &r)
const
627 int drawable = this->num_displayed;
632 if (
_pf_data[e].num_valid == 0)
continue;
644 if (drawable == 0)
break;
649 if (drawable == 0)
break;
654 void DrawWidget(
const Rect &r,
WidgetID widget)
const override
657 case WID_FRW_TIMES_NAMES: {
661 int drawable = this->num_displayed;
664 if (
_pf_data[e].num_valid == 0)
continue;
669 DrawString(r.left, r.right, y, STR_FRAMERATE_GAMELOOP + e, TC_FROMSTRING,
SA_LEFT);
677 if (drawable == 0)
break;
682 case WID_FRW_TIMES_CURRENT:
686 case WID_FRW_TIMES_AVERAGE:
690 case WID_FRW_ALLOCSIZE:
691 DrawElementAllocationsColumn(r);
696 void OnClick([[maybe_unused]]
Point pt,
WidgetID widget, [[maybe_unused]]
int click_count)
override
699 case WID_FRW_TIMES_NAMES:
700 case WID_FRW_TIMES_CURRENT:
701 case WID_FRW_TIMES_AVERAGE: {
705 if (line != INT32_MAX) {
709 if (
_pf_data[e].num_valid > 0) line--;
723 auto *wid = this->GetWidget<NWidgetResizeBase>(WID_FRW_TIMES_NAMES);
730 WDP_AUTO,
"framerate_display", 0, 0,
733 _framerate_window_widgets
738 static constexpr
NWidgetPart _frametime_graph_window_widgets[] = {
761 this->horizontal_scale = 4;
768 void SetStringParameters(
WidgetID widget)
const override
771 case WID_FGW_CAPTION:
773 SetDParam(0, STR_FRAMETIME_CAPTION_GAMELOOP + this->element);
785 if (widget == WID_FGW_GRAPH) {
792 graph_size.height = std::max(100u, 10 * (size_ms_label.height + 1));
797 size.width += size_ms_label.width + 2;
798 size.height += size_s_label.height + 2;
813 static const std::initializer_list<ScaleDef> hscales = {
820 for (
const auto &sc : hscales) {
821 if (range < sc.range) this->horizontal_scale = sc.scale;
828 static const std::initializer_list<TimingMeasurement> vscales = {
839 for (
const auto &sc : vscales) {
840 if (range < sc) this->vertical_scale = (int)sc;
858 this->horizontal_scale = 4;
860 for (
int i = 1; i < num_valid; i++) {
865 if (value == PerformanceData::INVALID_DURATION) {
867 lastts = timestamps[point];
870 if (value > peak_value) peak_value = value;
874 time_sum += lastts - timestamps[point];
875 lastts = timestamps[point];
878 if (count == 60) this->SelectHorizontalScale(time_sum);
881 if (count >= 60 && time_sum >= (this->horizontal_scale + 2) *
TIMESTAMP_PRECISION / 2)
break;
884 this->SelectVerticalScale(peak_value);
899 static inline T
Scinterlate(T dst_min, T dst_max, T src_min, T src_max, T value)
901 T dst_diff = dst_max - dst_min;
902 T src_diff = src_max - src_min;
903 return (value - src_min) * dst_diff / src_diff + dst_min;
906 void DrawWidget(
const Rect &r,
WidgetID widget)
const override
908 if (widget == WID_FGW_GRAPH) {
913 const int x_zero = r.right - (int)this->graph_size.width;
914 const int x_max = r.right;
915 const int y_zero = r.top + (
int)this->graph_size.height;
916 const int y_max = r.top;
925 const uint horz_div_scl = (this->horizontal_scale <= 20) ? 1 : 10;
927 const uint horz_divisions = this->horizontal_scale / horz_div_scl;
929 const uint vert_divisions = 10;
932 for (uint division = 0; division < vert_divisions; division++) {
933 int y =
Scinterlate(y_zero, y_max, 0, (
int)vert_divisions, (
int)division);
934 GfxDrawLine(x_zero, y, x_max, y, c_grid);
935 if (division % 2 == 0) {
946 for (uint division = horz_divisions; division > 0; division--) {
947 int x =
Scinterlate(x_zero, x_max, 0, (
int)horz_divisions, (
int)horz_divisions - (
int)division);
948 GfxDrawLine(x, y_max, x, y_zero, c_grid);
949 if (division % 2 == 0) {
950 SetDParam(0, division * horz_div_scl / 2);
958 (int)Scinterlate<int64_t>(y_zero, y_max, 0, this->vertical_scale, durations[point])
964 Point peak_point = { 0, 0 };
967 int points_drawn = 0;
974 if (value == PerformanceData::INVALID_DURATION) {
976 lastts = timestamps[point];
981 time_sum += lastts - timestamps[point];
982 lastts = timestamps[point];
984 if (time_sum > draw_horz_scale)
break;
988 (int)Scinterlate<int64_t>(x_zero, x_max, 0, (int64_t)draw_horz_scale, (int64_t)draw_horz_scale - (int64_t)time_sum),
989 (
int)Scinterlate<int64_t>(y_zero, y_max, 0, (int64_t)draw_vert_scale, (int64_t)value)
991 if (newpoint.x > lastpoint.x)
continue;
992 GfxDrawLine(lastpoint.x, lastpoint.y, newpoint.x, newpoint.y, c_lines);
993 lastpoint = newpoint;
998 if (value > peak_value) {
1000 peak_point = newpoint;
1005 if (points_drawn > 0 && peak_value >
TIMESTAMP_PRECISION / 100 && 2 * peak_value > 3 * value_sum / points_drawn) {
1007 GfxFillRect(peak_point.x - 1, peak_point.y - 1, peak_point.x + 1, peak_point.y + 1, c_peak);
1010 if (peak_point.x - x_zero > (
int)this->graph_size.width / 2) {
1020 static WindowDesc _frametime_graph_window_desc(
1021 WDP_AUTO,
"frametime_graph", 140, 90,
1024 _frametime_graph_window_widgets
1032 AllocateWindowDescFront<FramerateWindow>(_framerate_display_desc, 0);
1038 if (elem < PFE_FIRST || elem >=
PFE_MAX)
return;
1039 AllocateWindowDescFront<FrametimeGraphWindow>(_frametime_graph_window_desc, elem,
true);
1049 IConsolePrint(TC_SILVER,
"Based on num. data points: {} {} {}", count1, count2, count3);
1051 static const std::array<std::string_view, PFE_MAX> MEASUREMENT_NAMES = {
1053 " GL station ticks",
1055 " GL road vehicle ticks",
1057 " GL aircraft ticks",
1058 " GL landscape ticks",
1059 " GL link graph delays",
1061 " Viewport drawing",
1064 "AI/GS scripts total",
1067 std::string ai_name_buf;
1069 bool printed_anything =
false;
1073 if (pf.num_valid == 0)
continue;
1074 IConsolePrint(TC_GREEN,
"{} rate: {:.2f}fps (expected: {:.2f}fps)",
1075 MEASUREMENT_NAMES[e],
1078 printed_anything =
true;
1083 if (pf.num_valid == 0)
continue;
1084 std::string_view name;
1086 name = MEASUREMENT_NAMES[e];
1088 ai_name_buf = fmt::format(
"AI {} {}", e -
PFE_AI0 + 1, GetAIName(e -
PFE_AI0));
1091 IConsolePrint(TC_LIGHT_BLUE,
"{} times: {:.2f}ms {:.2f}ms {:.2f}ms",
1093 pf.GetAverageDurationMilliseconds(count1),
1094 pf.GetAverageDurationMilliseconds(count2),
1095 pf.GetAverageDurationMilliseconds(count3));
1096 printed_anything =
true;
1099 if (!printed_anything) {
1113 if (_sound_perf_pending.load(std::memory_order_acquire)) {
1114 std::lock_guard lk(_sound_perf_lock);
1115 for (
size_t i = 0; i < _sound_perf_measurements.size(); i += 2) {
1118 _sound_perf_measurements.clear();
1119 _sound_perf_pending.store(
false, std::memory_order_relaxed);