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();
345 static const char * GetAIName(
int ai_index)
352 static const NWidgetPart _framerate_window_widgets[] = {
361 NWidget(
WWT_TEXT, COLOUR_GREY, WID_FRW_RATE_GAMELOOP),
SetDataTip(STR_FRAMERATE_RATE_GAMELOOP, STR_FRAMERATE_RATE_GAMELOOP_TOOLTIP),
362 NWidget(
WWT_TEXT, COLOUR_GREY, WID_FRW_RATE_DRAWING),
SetDataTip(STR_FRAMERATE_RATE_BLITTER, STR_FRAMERATE_RATE_BLITTER_TOOLTIP),
363 NWidget(
WWT_TEXT, COLOUR_GREY, WID_FRW_RATE_FACTOR),
SetDataTip(STR_FRAMERATE_SPEED_FACTOR, STR_FRAMERATE_SPEED_FACTOR_TOOLTIP),
398 inline void SetRate(
double value,
double target)
400 const double threshold_good = target * 0.95;
401 const double threshold_bad = target * 2 / 3;
402 value = std::min(9999.99, value);
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 value = std::min(9999.99, value);
412 this->value = (uint32)(value * 100);
413 this->strid = (value < threshold_good) ? STR_FRAMERATE_MS_GOOD : (value > threshold_bad) ? STR_FRAMERATE_MS_BAD : STR_FRAMERATE_MS_WARN;
416 inline void InsertDParams(uint n)
const
436 this->showing_memory =
true;
438 this->num_displayed = this->num_active;
439 this->next_update.SetInterval(100);
447 bool elapsed = this->next_update.
Elapsed(delta_ms);
450 if (this->small != this->
IsShaded()) {
452 this->GetWidget<NWidgetLeaf>(WID_FRW_CAPTION)->SetDataTip(this->small ? STR_FRAMERATE_CAPTION_SMALL : STR_FRAMERATE_CAPTION, STR_TOOLTIP_WINDOW_TITLE_DRAG_THIS);
459 this->next_update.SetInterval(100);
466 bool have_script =
false;
469 if (this->small)
return;
483 if (this->showing_memory != have_script) {
484 NWidgetStacked *plane = this->GetWidget<NWidgetStacked>(WID_FRW_SEL_MEMORY);
486 this->showing_memory = have_script;
489 if (new_active != this->num_active) {
490 this->num_active = new_active;
493 sb->
SetCapacity(std::min(this->num_displayed, this->num_active));
501 case WID_FRW_CAPTION:
503 if (!this->small)
break;
505 this->rate_gameloop.InsertDParams(1);
506 this->speed_gameloop.InsertDParams(3);
509 case WID_FRW_RATE_GAMELOOP:
511 this->rate_gameloop.InsertDParams(1);
513 case WID_FRW_RATE_DRAWING:
515 this->rate_drawing.InsertDParams(1);
517 case WID_FRW_RATE_FACTOR:
518 this->speed_gameloop.InsertDParams(0);
520 case WID_FRW_INFO_DATA_POINTS:
529 case WID_FRW_RATE_GAMELOOP:
535 case WID_FRW_RATE_DRAWING:
541 case WID_FRW_RATE_FACTOR:
547 case WID_FRW_TIMES_NAMES: {
553 if (
_pf_data[e].num_valid == 0)
continue;
562 size->width = std::max(size->width, line_size.width);
567 case WID_FRW_TIMES_CURRENT:
568 case WID_FRW_TIMES_AVERAGE:
569 case WID_FRW_ALLOCSIZE: {
574 size->width = std::max(size->width, item_size.width);
588 int drawable = this->num_displayed;
593 if (
_pf_data[e].num_valid == 0)
continue;
597 values[e].InsertDParams(0);
601 if (drawable == 0)
break;
606 void DrawElementAllocationsColumn(
const Rect &r)
const
610 int drawable = this->num_displayed;
615 if (
_pf_data[e].num_valid == 0)
continue;
627 if (drawable == 0)
break;
632 if (drawable == 0)
break;
640 case WID_FRW_TIMES_NAMES: {
644 int drawable = this->num_displayed;
647 if (
_pf_data[e].num_valid == 0)
continue;
652 DrawString(r.left, r.right, y, STR_FRAMERATE_GAMELOOP + e, TC_FROMSTRING,
SA_LEFT);
660 if (drawable == 0)
break;
665 case WID_FRW_TIMES_CURRENT:
669 case WID_FRW_TIMES_AVERAGE:
673 case WID_FRW_ALLOCSIZE:
674 DrawElementAllocationsColumn(r);
682 case WID_FRW_TIMES_NAMES:
683 case WID_FRW_TIMES_CURRENT:
684 case WID_FRW_TIMES_AVERAGE: {
688 if (line != INT_MAX) {
692 if (
_pf_data[e].num_valid > 0) line--;
706 auto *wid = this->GetWidget<NWidgetResizeBase>(WID_FRW_TIMES_NAMES);
713 WDP_AUTO,
"framerate_display", 0, 0,
716 _framerate_window_widgets,
lengthof(_framerate_window_widgets)
721 static const NWidgetPart _frametime_graph_window_widgets[] = {
745 this->horizontal_scale = 4;
747 this->next_scale_update.SetInterval(1);
755 case WID_FGW_CAPTION:
757 SetDParam(0, STR_FRAMETIME_CAPTION_GAMELOOP + this->element);
769 if (widget == WID_FGW_GRAPH) {
776 graph_size.height = std::max(100u, 10 * (size_ms_label.height + 1));
781 size->width += size_ms_label.width + 2;
782 size->height += size_s_label.height + 2;
791 static const ScaleDef hscales[] = {
798 for (
const ScaleDef *sc = hscales; sc < hscales +
lengthof(hscales); sc++) {
799 if (range < sc->range) this->horizontal_scale = sc->scale;
818 if (range < *sc) this->vertical_scale = (int)*sc;
836 this->horizontal_scale = 4;
838 for (
int i = 1; i < num_valid; i++) {
843 if (value == PerformanceData::INVALID_DURATION) {
845 lastts = timestamps[point];
848 if (value > peak_value) peak_value = value;
852 time_sum += lastts - timestamps[point];
853 lastts = timestamps[point];
859 if (count >= 60 && time_sum >= (this->horizontal_scale + 2) *
TIMESTAMP_PRECISION / 2)
break;
862 this->SelectVerticalScale(peak_value);
869 if (this->next_scale_update.
Elapsed(delta_ms)) {
870 this->next_scale_update.SetInterval(500);
877 static inline T
Scinterlate(T dst_min, T dst_max, T src_min, T src_max, T value)
879 T dst_diff = dst_max - dst_min;
880 T src_diff = src_max - src_min;
881 return (value - src_min) * dst_diff / src_diff + dst_min;
886 if (widget == WID_FGW_GRAPH) {
891 const int x_zero = r.right - (int)this->graph_size.width;
892 const int x_max = r.right;
893 const int y_zero = r.top + (
int)this->graph_size.height;
894 const int y_max = r.top;
903 const uint horz_div_scl = (this->horizontal_scale <= 20) ? 1 : 10;
905 const uint horz_divisions = this->horizontal_scale / horz_div_scl;
907 const uint vert_divisions = 10;
910 for (uint division = 0; division < vert_divisions; division++) {
911 int y =
Scinterlate(y_zero, y_max, 0, (
int)vert_divisions, (
int)division);
912 GfxDrawLine(x_zero, y, x_max, y, c_grid);
913 if (division % 2 == 0) {
924 for (uint division = horz_divisions; division > 0; division--) {
925 int x =
Scinterlate(x_zero, x_max, 0, (
int)horz_divisions, (
int)horz_divisions - (
int)division);
926 GfxDrawLine(x, y_max, x, y_zero, c_grid);
927 if (division % 2 == 0) {
928 SetDParam(0, division * horz_div_scl / 2);
936 (int)Scinterlate<int64>(y_zero, y_max, 0, this->vertical_scale, durations[point])
942 Point peak_point = { 0, 0 };
945 int points_drawn = 0;
952 if (value == PerformanceData::INVALID_DURATION) {
954 lastts = timestamps[point];
959 time_sum += lastts - timestamps[point];
960 lastts = timestamps[point];
962 if (time_sum > draw_horz_scale)
break;
966 (int)Scinterlate<int64>(x_zero, x_max, 0, (int64)draw_horz_scale, (int64)draw_horz_scale - (int64)time_sum),
967 (
int)Scinterlate<int64>(y_zero, y_max, 0, (int64)draw_vert_scale, (int64)value)
969 assert(newpoint.x <= lastpoint.x);
970 GfxDrawLine(lastpoint.x, lastpoint.y, newpoint.x, newpoint.y, c_lines);
971 lastpoint = newpoint;
976 if (value > peak_value) {
978 peak_point = newpoint;
983 if (points_drawn > 0 && peak_value >
TIMESTAMP_PRECISION / 100 && 2 * peak_value > 3 * value_sum / points_drawn) {
985 GfxFillRect(peak_point.x - 1, peak_point.y - 1, peak_point.x + 1, peak_point.y + 1, c_peak);
988 if (peak_point.x - x_zero > (
int)this->graph_size.width / 2) {
998 static WindowDesc _frametime_graph_window_desc(
999 WDP_AUTO,
"frametime_graph", 140, 90,
1002 _frametime_graph_window_widgets,
lengthof(_frametime_graph_window_widgets)
1010 AllocateWindowDescFront<FramerateWindow>(&_framerate_display_desc, 0);
1016 if (elem < PFE_FIRST || elem >=
PFE_MAX)
return;
1017 AllocateWindowDescFront<FrametimeGraphWindow>(&_frametime_graph_window_desc, elem,
true);
1027 IConsolePrintF(TC_SILVER,
"Based on num. data points: %d %d %d", count1, count2, count3);
1029 static const char *MEASUREMENT_NAMES[
PFE_MAX] = {
1031 " GL station ticks",
1033 " GL road vehicle ticks",
1035 " GL aircraft ticks",
1036 " GL landscape ticks",
1037 " GL link graph delays",
1039 " Viewport drawing",
1042 "AI/GS scripts total",
1045 char ai_name_buf[128];
1049 bool printed_anything =
false;
1053 if (pf.num_valid == 0)
continue;
1055 MEASUREMENT_NAMES[*e],
1058 printed_anything =
true;
1063 if (pf.num_valid == 0)
continue;
1066 name = MEASUREMENT_NAMES[e];
1073 pf.GetAverageDurationMilliseconds(count1),
1074 pf.GetAverageDurationMilliseconds(count2),
1075 pf.GetAverageDurationMilliseconds(count3));
1076 printed_anything =
true;
1079 if (!printed_anything) {