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)
375 static const NWidgetPart _framerate_window_widgets[] = {
421 inline void SetRate(
double value,
double target)
423 const double threshold_good = target * 0.95;
424 const double threshold_bad = target * 2 / 3;
425 this->value = (uint32)(value * 100);
426 this->strid = (value > threshold_good) ? STR_FRAMERATE_FPS_GOOD : (value < threshold_bad) ? STR_FRAMERATE_FPS_BAD : STR_FRAMERATE_FPS_WARN;
429 inline void SetTime(
double value,
double target)
431 const double threshold_good = target / 3;
432 const double threshold_bad = target;
433 this->value = (uint32)(value * 100);
434 this->strid = (value < threshold_good) ? STR_FRAMERATE_MS_GOOD : (value > threshold_bad) ? STR_FRAMERATE_MS_BAD : STR_FRAMERATE_MS_WARN;
437 inline void InsertDParams(uint n)
const
456 this->showing_memory =
true;
458 this->num_displayed = this->num_active;
459 this->next_update.SetInterval(100);
467 bool elapsed = this->next_update.
Elapsed(delta_ms);
470 if (this->small != this->
IsShaded()) {
472 this->GetWidget<NWidgetLeaf>(WID_FRW_CAPTION)->SetDataTip(this->small ? STR_FRAMERATE_CAPTION_SMALL : STR_FRAMERATE_CAPTION, STR_TOOLTIP_WINDOW_TITLE_DRAG_THIS);
479 this->next_update.SetInterval(100);
486 bool have_script =
false;
489 if (this->small)
return;
503 if (this->showing_memory != have_script) {
504 NWidgetStacked *plane = this->GetWidget<NWidgetStacked>(WID_FRW_SEL_MEMORY);
506 this->showing_memory = have_script;
509 if (new_active != this->num_active) {
510 this->num_active = new_active;
513 sb->
SetCapacity(std::min(this->num_displayed, this->num_active));
521 case WID_FRW_CAPTION:
523 if (!this->small)
break;
525 this->rate_gameloop.InsertDParams(1);
526 this->speed_gameloop.InsertDParams(3);
529 case WID_FRW_RATE_GAMELOOP:
531 this->rate_gameloop.InsertDParams(1);
533 case WID_FRW_RATE_DRAWING:
535 this->rate_drawing.InsertDParams(1);
537 case WID_FRW_RATE_FACTOR:
538 this->speed_gameloop.InsertDParams(0);
540 case WID_FRW_INFO_DATA_POINTS:
549 case WID_FRW_RATE_GAMELOOP:
555 case WID_FRW_RATE_DRAWING:
561 case WID_FRW_RATE_FACTOR:
567 case WID_FRW_TIMES_NAMES: {
573 if (
_pf_data[e].num_valid == 0)
continue;
582 size->width = std::max(size->width, line_size.width);
587 case WID_FRW_TIMES_CURRENT:
588 case WID_FRW_TIMES_AVERAGE:
589 case WID_FRW_ALLOCSIZE: {
594 size->width = std::max(size->width, item_size.width);
608 int drawable = this->num_displayed;
613 if (
_pf_data[e].num_valid == 0)
continue;
617 values[e].InsertDParams(0);
621 if (drawable == 0)
break;
626 void DrawElementAllocationsColumn(
const Rect &r)
const
630 int drawable = this->num_displayed;
635 if (
_pf_data[e].num_valid == 0)
continue;
647 if (drawable == 0)
break;
652 if (drawable == 0)
break;
660 case WID_FRW_TIMES_NAMES: {
664 int drawable = this->num_displayed;
667 if (
_pf_data[e].num_valid == 0)
continue;
672 DrawString(r.left, r.right, y, STR_FRAMERATE_GAMELOOP + e, TC_FROMSTRING,
SA_LEFT);
680 if (drawable == 0)
break;
685 case WID_FRW_TIMES_CURRENT:
689 case WID_FRW_TIMES_AVERAGE:
693 case WID_FRW_ALLOCSIZE:
694 DrawElementAllocationsColumn(r);
702 case WID_FRW_TIMES_NAMES:
703 case WID_FRW_TIMES_CURRENT:
704 case WID_FRW_TIMES_AVERAGE: {
708 if (line != INT_MAX) {
712 if (
_pf_data[e].num_valid > 0) line--;
726 auto *wid = this->GetWidget<NWidgetResizeBase>(WID_FRW_TIMES_NAMES);
733 WDP_AUTO,
"framerate_display", 0, 0,
736 _framerate_window_widgets,
lengthof(_framerate_window_widgets)
741 static const NWidgetPart _frametime_graph_window_widgets[] = {
765 this->horizontal_scale = 4;
767 this->next_scale_update.SetInterval(1);
775 case WID_FGW_CAPTION:
777 SetDParam(0, STR_FRAMETIME_CAPTION_GAMELOOP + this->element);
789 if (widget == WID_FGW_GRAPH) {
796 graph_size.height = std::max(100u, 10 * (size_ms_label.height + 1));
801 size->width += size_ms_label.width + 2;
802 size->height += size_s_label.height + 2;
811 static const ScaleDef hscales[] = {
818 for (
const ScaleDef *sc = hscales; sc < hscales +
lengthof(hscales); sc++) {
819 if (range < sc->range) this->horizontal_scale = sc->scale;
838 if (range < *sc) this->vertical_scale = (int)*sc;
856 this->horizontal_scale = 4;
858 for (
int i = 1; i < num_valid; i++) {
863 if (value == PerformanceData::INVALID_DURATION) {
865 lastts = timestamps[point];
868 if (value > peak_value) peak_value = value;
872 time_sum += lastts - timestamps[point];
873 lastts = timestamps[point];
879 if (count >= 60 && time_sum >= (this->horizontal_scale + 2) *
TIMESTAMP_PRECISION / 2)
break;
882 this->SelectVerticalScale(peak_value);
889 if (this->next_scale_update.
Elapsed(delta_ms)) {
890 this->next_scale_update.SetInterval(500);
897 static inline T
Scinterlate(T dst_min, T dst_max, T src_min, T src_max, T value)
899 T dst_diff = dst_max - dst_min;
900 T src_diff = src_max - src_min;
901 return (value - src_min) * dst_diff / src_diff + dst_min;
906 if (widget == WID_FGW_GRAPH) {
911 const int x_zero = r.right - (int)this->graph_size.width;
912 const int x_max = r.right;
913 const int y_zero = r.top + (
int)this->graph_size.height;
914 const int y_max = r.top;
923 const uint horz_div_scl = (this->horizontal_scale <= 20) ? 1 : 10;
925 const uint horz_divisions = this->horizontal_scale / horz_div_scl;
927 const uint vert_divisions = 10;
930 for (uint division = 0; division < vert_divisions; division++) {
931 int y =
Scinterlate(y_zero, y_max, 0, (
int)vert_divisions, (
int)division);
932 GfxDrawLine(x_zero, y, x_max, y, c_grid);
933 if (division % 2 == 0) {
944 for (uint division = horz_divisions; division > 0; division--) {
945 int x =
Scinterlate(x_zero, x_max, 0, (
int)horz_divisions, (
int)horz_divisions - (
int)division);
946 GfxDrawLine(x, y_max, x, y_zero, c_grid);
947 if (division % 2 == 0) {
948 SetDParam(0, division * horz_div_scl / 2);
956 (int)Scinterlate<int64>(y_zero, y_max, 0, this->vertical_scale, durations[point])
962 Point peak_point = { 0, 0 };
965 int points_drawn = 0;
972 if (value == PerformanceData::INVALID_DURATION) {
974 lastts = timestamps[point];
979 time_sum += lastts - timestamps[point];
980 lastts = timestamps[point];
982 if (time_sum > draw_horz_scale)
break;
986 (int)Scinterlate<int64>(x_zero, x_max, 0, (int64)draw_horz_scale, (int64)draw_horz_scale - (int64)time_sum),
987 (
int)Scinterlate<int64>(y_zero, y_max, 0, (int64)draw_vert_scale, (int64)value)
989 if (newpoint.x > lastpoint.x)
continue;
990 GfxDrawLine(lastpoint.x, lastpoint.y, newpoint.x, newpoint.y, c_lines);
991 lastpoint = newpoint;
996 if (value > peak_value) {
998 peak_point = newpoint;
1003 if (points_drawn > 0 && peak_value >
TIMESTAMP_PRECISION / 100 && 2 * peak_value > 3 * value_sum / points_drawn) {
1005 GfxFillRect(peak_point.x - 1, peak_point.y - 1, peak_point.x + 1, peak_point.y + 1, c_peak);
1008 if (peak_point.x - x_zero > (
int)this->graph_size.width / 2) {
1018 static WindowDesc _frametime_graph_window_desc(
1019 WDP_AUTO,
"frametime_graph", 140, 90,
1022 _frametime_graph_window_widgets,
lengthof(_frametime_graph_window_widgets)
1030 AllocateWindowDescFront<FramerateWindow>(&_framerate_display_desc, 0);
1036 if (elem < PFE_FIRST || elem >=
PFE_MAX)
return;
1037 AllocateWindowDescFront<FrametimeGraphWindow>(&_frametime_graph_window_desc, elem,
true);
1047 IConsolePrint(TC_SILVER,
"Based on num. data points: {} {} {}", count1, count2, count3);
1049 static const char *MEASUREMENT_NAMES[
PFE_MAX] = {
1051 " GL station ticks",
1053 " GL road vehicle ticks",
1055 " GL aircraft ticks",
1056 " GL landscape ticks",
1057 " GL link graph delays",
1059 " Viewport drawing",
1062 "AI/GS scripts total",
1065 char ai_name_buf[128];
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;
1086 name = MEASUREMENT_NAMES[e];
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);