69 explicit PerformanceData(
double expected_rate) : expected_rate(expected_rate), next_index(0), prev_index(0), num_valid(0) { }
74 this->durations[this->next_index] = end_time - start_time;
75 this->timestamps[this->next_index] = start_time;
76 this->prev_index = this->next_index;
77 this->next_index += 1;
85 this->timestamps[this->next_index] = this->acc_timestamp;
86 this->durations[this->next_index] = this->acc_duration;
87 this->prev_index = this->next_index;
88 this->next_index += 1;
92 this->acc_duration = 0;
93 this->acc_timestamp = start_time;
99 this->acc_duration += duration;
105 if (this->durations[this->prev_index] != INVALID_DURATION) {
106 this->timestamps[this->next_index] = start_time;
107 this->durations[this->next_index] = INVALID_DURATION;
108 this->prev_index = this->next_index;
109 this->next_index += 1;
111 this->num_valid += 1;
118 count = std::min(count, this->num_valid);
120 int first_point = this->prev_index - count;
125 for (
int i = first_point; i < first_point + count; i++) {
127 if (d != INVALID_DURATION) {
135 if (count == 0)
return 0;
143 int point = this->prev_index;
144 int last_point = this->next_index - this->num_valid;
158 while (point != last_point) {
160 if (this->durations[point] != INVALID_DURATION) {
161 total += last - this->timestamps[point];
164 last = this->timestamps[point];
170 if (total == 0 || count == 0)
return 0;
225 using namespace std::chrono;
226 return (
TimingMeasurement)time_point_cast<microseconds>(high_resolution_clock::now()).time_since_epoch().count();
346 static const char * GetAIName(
int ai_index)
353 static const NWidgetPart _framerate_window_widgets[] = {
362 NWidget(
WWT_TEXT, COLOUR_GREY, WID_FRW_RATE_GAMELOOP),
SetDataTip(STR_FRAMERATE_RATE_GAMELOOP, STR_FRAMERATE_RATE_GAMELOOP_TOOLTIP),
363 NWidget(
WWT_TEXT, COLOUR_GREY, WID_FRW_RATE_DRAWING),
SetDataTip(STR_FRAMERATE_RATE_BLITTER, STR_FRAMERATE_RATE_BLITTER_TOOLTIP),
364 NWidget(
WWT_TEXT, COLOUR_GREY, WID_FRW_RATE_FACTOR),
SetDataTip(STR_FRAMERATE_SPEED_FACTOR, STR_FRAMERATE_SPEED_FACTOR_TOOLTIP),
399 inline void SetRate(
double value,
double target)
401 const double threshold_good = target * 0.95;
402 const double threshold_bad = target * 2 / 3;
403 this->value = (uint32)(value * 100);
404 this->strid = (value > threshold_good) ? STR_FRAMERATE_FPS_GOOD : (value < threshold_bad) ? STR_FRAMERATE_FPS_BAD : STR_FRAMERATE_FPS_WARN;
407 inline void SetTime(
double value,
double target)
409 const double threshold_good = target / 3;
410 const double threshold_bad = target;
411 this->value = (uint32)(value * 100);
412 this->strid = (value < threshold_good) ? STR_FRAMERATE_MS_GOOD : (value > threshold_bad) ? STR_FRAMERATE_MS_BAD : STR_FRAMERATE_MS_WARN;
415 inline void InsertDParams(uint n)
const
435 this->showing_memory =
true;
437 this->num_displayed = this->num_active;
438 this->next_update.SetInterval(100);
446 bool elapsed = this->next_update.
Elapsed(delta_ms);
449 if (this->small != this->
IsShaded()) {
451 this->GetWidget<NWidgetLeaf>(WID_FRW_CAPTION)->SetDataTip(this->small ? STR_FRAMERATE_CAPTION_SMALL : STR_FRAMERATE_CAPTION, STR_TOOLTIP_WINDOW_TITLE_DRAG_THIS);
458 this->next_update.SetInterval(100);
465 bool have_script =
false;
468 if (this->small)
return;
482 if (this->showing_memory != have_script) {
483 NWidgetStacked *plane = this->GetWidget<NWidgetStacked>(WID_FRW_SEL_MEMORY);
485 this->showing_memory = have_script;
488 if (new_active != this->num_active) {
489 this->num_active = new_active;
492 sb->
SetCapacity(std::min(this->num_displayed, this->num_active));
500 case WID_FRW_CAPTION:
502 if (!this->small)
break;
504 this->rate_gameloop.InsertDParams(1);
505 this->speed_gameloop.InsertDParams(3);
508 case WID_FRW_RATE_GAMELOOP:
510 this->rate_gameloop.InsertDParams(1);
512 case WID_FRW_RATE_DRAWING:
514 this->rate_drawing.InsertDParams(1);
516 case WID_FRW_RATE_FACTOR:
517 this->speed_gameloop.InsertDParams(0);
519 case WID_FRW_INFO_DATA_POINTS:
528 case WID_FRW_RATE_GAMELOOP:
534 case WID_FRW_RATE_DRAWING:
540 case WID_FRW_RATE_FACTOR:
546 case WID_FRW_TIMES_NAMES: {
552 if (
_pf_data[e].num_valid == 0)
continue;
561 size->width = std::max(size->width, line_size.width);
566 case WID_FRW_TIMES_CURRENT:
567 case WID_FRW_TIMES_AVERAGE:
568 case WID_FRW_ALLOCSIZE: {
573 size->width = std::max(size->width, item_size.width);
587 int drawable = this->num_displayed;
592 if (
_pf_data[e].num_valid == 0)
continue;
596 values[e].InsertDParams(0);
600 if (drawable == 0)
break;
605 void DrawElementAllocationsColumn(
const Rect &r)
const
609 int drawable = this->num_displayed;
614 if (
_pf_data[e].num_valid == 0)
continue;
626 if (drawable == 0)
break;
631 if (drawable == 0)
break;
639 case WID_FRW_TIMES_NAMES: {
643 int drawable = this->num_displayed;
646 if (
_pf_data[e].num_valid == 0)
continue;
651 DrawString(r.left, r.right, y, STR_FRAMERATE_GAMELOOP + e, TC_FROMSTRING,
SA_LEFT);
659 if (drawable == 0)
break;
664 case WID_FRW_TIMES_CURRENT:
668 case WID_FRW_TIMES_AVERAGE:
672 case WID_FRW_ALLOCSIZE:
673 DrawElementAllocationsColumn(r);
681 case WID_FRW_TIMES_NAMES:
682 case WID_FRW_TIMES_CURRENT:
683 case WID_FRW_TIMES_AVERAGE: {
687 if (line != INT_MAX) {
691 if (
_pf_data[e].num_valid > 0) line--;
705 auto *wid = this->GetWidget<NWidgetResizeBase>(WID_FRW_TIMES_NAMES);
712 WDP_AUTO,
"framerate_display", 0, 0,
715 _framerate_window_widgets,
lengthof(_framerate_window_widgets)
720 static const NWidgetPart _frametime_graph_window_widgets[] = {
744 this->horizontal_scale = 4;
746 this->next_scale_update.SetInterval(1);
754 case WID_FGW_CAPTION:
756 SetDParam(0, STR_FRAMETIME_CAPTION_GAMELOOP + this->element);
768 if (widget == WID_FGW_GRAPH) {
775 graph_size.height = std::max(100u, 10 * (size_ms_label.height + 1));
780 size->width += size_ms_label.width + 2;
781 size->height += size_s_label.height + 2;
790 static const ScaleDef hscales[] = {
797 for (
const ScaleDef *sc = hscales; sc < hscales +
lengthof(hscales); sc++) {
798 if (range < sc->range) this->horizontal_scale = sc->scale;
817 if (range < *sc) this->vertical_scale = (int)*sc;
835 this->horizontal_scale = 4;
837 for (
int i = 1; i < num_valid; i++) {
842 if (value == PerformanceData::INVALID_DURATION) {
844 lastts = timestamps[point];
847 if (value > peak_value) peak_value = value;
851 time_sum += lastts - timestamps[point];
852 lastts = timestamps[point];
858 if (count >= 60 && time_sum >= (this->horizontal_scale + 2) *
TIMESTAMP_PRECISION / 2)
break;
861 this->SelectVerticalScale(peak_value);
868 if (this->next_scale_update.
Elapsed(delta_ms)) {
869 this->next_scale_update.SetInterval(500);
876 static inline T
Scinterlate(T dst_min, T dst_max, T src_min, T src_max, T value)
878 T dst_diff = dst_max - dst_min;
879 T src_diff = src_max - src_min;
880 return (value - src_min) * dst_diff / src_diff + dst_min;
885 if (widget == WID_FGW_GRAPH) {
890 const int x_zero = r.right - (int)this->graph_size.width;
891 const int x_max = r.right;
892 const int y_zero = r.top + (
int)this->graph_size.height;
893 const int y_max = r.top;
902 const uint horz_div_scl = (this->horizontal_scale <= 20) ? 1 : 10;
904 const uint horz_divisions = this->horizontal_scale / horz_div_scl;
906 const uint vert_divisions = 10;
909 for (uint division = 0; division < vert_divisions; division++) {
910 int y =
Scinterlate(y_zero, y_max, 0, (
int)vert_divisions, (
int)division);
911 GfxDrawLine(x_zero, y, x_max, y, c_grid);
912 if (division % 2 == 0) {
923 for (uint division = horz_divisions; division > 0; division--) {
924 int x =
Scinterlate(x_zero, x_max, 0, (
int)horz_divisions, (
int)horz_divisions - (
int)division);
925 GfxDrawLine(x, y_max, x, y_zero, c_grid);
926 if (division % 2 == 0) {
927 SetDParam(0, division * horz_div_scl / 2);
935 (int)Scinterlate<int64>(y_zero, y_max, 0, this->vertical_scale, durations[point])
941 Point peak_point = { 0, 0 };
944 int points_drawn = 0;
951 if (value == PerformanceData::INVALID_DURATION) {
953 lastts = timestamps[point];
958 time_sum += lastts - timestamps[point];
959 lastts = timestamps[point];
961 if (time_sum > draw_horz_scale)
break;
965 (int)Scinterlate<int64>(x_zero, x_max, 0, (int64)draw_horz_scale, (int64)draw_horz_scale - (int64)time_sum),
966 (
int)Scinterlate<int64>(y_zero, y_max, 0, (int64)draw_vert_scale, (int64)value)
968 assert(newpoint.x <= lastpoint.x);
969 GfxDrawLine(lastpoint.x, lastpoint.y, newpoint.x, newpoint.y, c_lines);
970 lastpoint = newpoint;
975 if (value > peak_value) {
977 peak_point = newpoint;
982 if (points_drawn > 0 && peak_value >
TIMESTAMP_PRECISION / 100 && 2 * peak_value > 3 * value_sum / points_drawn) {
984 GfxFillRect(peak_point.x - 1, peak_point.y - 1, peak_point.x + 1, peak_point.y + 1, c_peak);
987 if (peak_point.x - x_zero > (
int)this->graph_size.width / 2) {
997 static WindowDesc _frametime_graph_window_desc(
998 WDP_AUTO,
"frametime_graph", 140, 90,
1001 _frametime_graph_window_widgets,
lengthof(_frametime_graph_window_widgets)
1009 AllocateWindowDescFront<FramerateWindow>(&_framerate_display_desc, 0);
1015 if (elem < PFE_FIRST || elem >=
PFE_MAX)
return;
1016 AllocateWindowDescFront<FrametimeGraphWindow>(&_frametime_graph_window_desc, elem,
true);
1026 IConsolePrintF(TC_SILVER,
"Based on num. data points: %d %d %d", count1, count2, count3);
1028 static const char *MEASUREMENT_NAMES[
PFE_MAX] = {
1030 " GL station ticks",
1032 " GL road vehicle ticks",
1034 " GL aircraft ticks",
1035 " GL landscape ticks",
1036 " GL link graph delays",
1038 " Viewport drawing",
1041 "AI/GS scripts total",
1044 char ai_name_buf[128];
1048 bool printed_anything =
false;
1052 if (pf.num_valid == 0)
continue;
1054 MEASUREMENT_NAMES[*e],
1057 printed_anything =
true;
1062 if (pf.num_valid == 0)
continue;
1065 name = MEASUREMENT_NAMES[e];
1072 pf.GetAverageDurationMilliseconds(count1),
1073 pf.GetAverageDurationMilliseconds(count2),
1074 pf.GetAverageDurationMilliseconds(count3));
1075 printed_anything =
true;
1078 if (!printed_anything) {