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;
154 while (point != last_point) {
156 if (this->durations[point] != INVALID_DURATION) {
157 total += last - this->timestamps[point];
160 last = this->timestamps[point];
166 if (total == 0 || count == 0)
return 0;
221 using namespace std::chrono;
222 return (
TimingMeasurement)time_point_cast<microseconds>(high_resolution_clock::now()).time_since_epoch().count();
341 static const char * GetAIName(
int ai_index)
348 static const NWidgetPart _framerate_window_widgets[] = {
357 NWidget(
WWT_TEXT, COLOUR_GREY, WID_FRW_RATE_GAMELOOP),
SetDataTip(STR_FRAMERATE_RATE_GAMELOOP, STR_FRAMERATE_RATE_GAMELOOP_TOOLTIP),
358 NWidget(
WWT_TEXT, COLOUR_GREY, WID_FRW_RATE_DRAWING),
SetDataTip(STR_FRAMERATE_RATE_BLITTER, STR_FRAMERATE_RATE_BLITTER_TOOLTIP),
359 NWidget(
WWT_TEXT, COLOUR_GREY, WID_FRW_RATE_FACTOR),
SetDataTip(STR_FRAMERATE_SPEED_FACTOR, STR_FRAMERATE_SPEED_FACTOR_TOOLTIP),
394 inline void SetRate(
double value,
double target)
396 const double threshold_good = target * 0.95;
397 const double threshold_bad = target * 2 / 3;
398 value = std::min(9999.99, value);
399 this->value = (uint32)(value * 100);
400 this->strid = (value > threshold_good) ? STR_FRAMERATE_FPS_GOOD : (value < threshold_bad) ? STR_FRAMERATE_FPS_BAD : STR_FRAMERATE_FPS_WARN;
403 inline void SetTime(
double value,
double target)
405 const double threshold_good = target / 3;
406 const double threshold_bad = target;
407 value = std::min(9999.99, value);
408 this->value = (uint32)(value * 100);
409 this->strid = (value < threshold_good) ? STR_FRAMERATE_MS_GOOD : (value > threshold_bad) ? STR_FRAMERATE_MS_BAD : STR_FRAMERATE_MS_WARN;
412 inline void InsertDParams(uint n)
const
432 this->showing_memory =
true;
434 this->num_displayed = this->num_active;
435 this->next_update.SetInterval(100);
443 bool elapsed = this->next_update.
Elapsed(delta_ms);
446 if (this->small != this->
IsShaded()) {
448 this->GetWidget<NWidgetLeaf>(WID_FRW_CAPTION)->SetDataTip(this->small ? STR_FRAMERATE_CAPTION_SMALL : STR_FRAMERATE_CAPTION, STR_TOOLTIP_WINDOW_TITLE_DRAG_THIS);
455 this->next_update.SetInterval(100);
462 bool have_script =
false;
465 if (this->small)
return;
479 if (this->showing_memory != have_script) {
480 NWidgetStacked *plane = this->GetWidget<NWidgetStacked>(WID_FRW_SEL_MEMORY);
482 this->showing_memory = have_script;
485 if (new_active != this->num_active) {
486 this->num_active = new_active;
489 sb->
SetCapacity(std::min(this->num_displayed, this->num_active));
497 case WID_FRW_CAPTION:
499 if (!this->small)
break;
501 this->rate_gameloop.InsertDParams(1);
502 this->speed_gameloop.InsertDParams(3);
505 case WID_FRW_RATE_GAMELOOP:
507 this->rate_gameloop.InsertDParams(1);
509 case WID_FRW_RATE_DRAWING:
511 this->rate_drawing.InsertDParams(1);
513 case WID_FRW_RATE_FACTOR:
514 this->speed_gameloop.InsertDParams(0);
516 case WID_FRW_INFO_DATA_POINTS:
525 case WID_FRW_RATE_GAMELOOP:
531 case WID_FRW_RATE_DRAWING:
537 case WID_FRW_RATE_FACTOR:
543 case WID_FRW_TIMES_NAMES: {
549 if (
_pf_data[e].num_valid == 0)
continue;
558 size->width = std::max(size->width, line_size.width);
563 case WID_FRW_TIMES_CURRENT:
564 case WID_FRW_TIMES_AVERAGE:
565 case WID_FRW_ALLOCSIZE: {
570 size->width = std::max(size->width, item_size.width);
584 int drawable = this->num_displayed;
589 if (
_pf_data[e].num_valid == 0)
continue;
593 values[e].InsertDParams(0);
597 if (drawable == 0)
break;
602 void DrawElementAllocationsColumn(
const Rect &r)
const
606 int drawable = this->num_displayed;
611 if (
_pf_data[e].num_valid == 0)
continue;
623 if (drawable == 0)
break;
628 if (drawable == 0)
break;
636 case WID_FRW_TIMES_NAMES: {
640 int drawable = this->num_displayed;
643 if (
_pf_data[e].num_valid == 0)
continue;
648 DrawString(r.left, r.right, y, STR_FRAMERATE_GAMELOOP + e, TC_FROMSTRING,
SA_LEFT);
656 if (drawable == 0)
break;
661 case WID_FRW_TIMES_CURRENT:
665 case WID_FRW_TIMES_AVERAGE:
669 case WID_FRW_ALLOCSIZE:
670 DrawElementAllocationsColumn(r);
678 case WID_FRW_TIMES_NAMES:
679 case WID_FRW_TIMES_CURRENT:
680 case WID_FRW_TIMES_AVERAGE: {
684 if (line != INT_MAX) {
688 if (
_pf_data[e].num_valid > 0) line--;
702 auto *wid = this->GetWidget<NWidgetResizeBase>(WID_FRW_TIMES_NAMES);
709 WDP_AUTO,
"framerate_display", 0, 0,
712 _framerate_window_widgets,
lengthof(_framerate_window_widgets)
717 static const NWidgetPart _frametime_graph_window_widgets[] = {
741 this->horizontal_scale = 4;
743 this->next_scale_update.SetInterval(1);
751 case WID_FGW_CAPTION:
753 SetDParam(0, STR_FRAMETIME_CAPTION_GAMELOOP + this->element);
765 if (widget == WID_FGW_GRAPH) {
772 graph_size.height = std::max(100u, 10 * (size_ms_label.height + 1));
777 size->width += size_ms_label.width + 2;
778 size->height += size_s_label.height + 2;
787 static const ScaleDef hscales[] = {
794 for (
const ScaleDef *sc = hscales; sc < hscales +
lengthof(hscales); sc++) {
795 if (range < sc->range) this->horizontal_scale = sc->scale;
814 if (range < *sc) this->vertical_scale = (int)*sc;
832 this->horizontal_scale = 4;
834 for (
int i = 1; i < num_valid; i++) {
839 if (value == PerformanceData::INVALID_DURATION) {
841 lastts = timestamps[point];
844 if (value > peak_value) peak_value = value;
848 time_sum += lastts - timestamps[point];
849 lastts = timestamps[point];
855 if (count >= 60 && time_sum >= (this->horizontal_scale + 2) *
TIMESTAMP_PRECISION / 2)
break;
858 this->SelectVerticalScale(peak_value);
865 if (this->next_scale_update.
Elapsed(delta_ms)) {
866 this->next_scale_update.SetInterval(500);
873 static inline T
Scinterlate(T dst_min, T dst_max, T src_min, T src_max, T value)
875 T dst_diff = dst_max - dst_min;
876 T src_diff = src_max - src_min;
877 return (value - src_min) * dst_diff / src_diff + dst_min;
882 if (widget == WID_FGW_GRAPH) {
887 const int x_zero = r.right - (int)this->graph_size.width;
888 const int x_max = r.right;
889 const int y_zero = r.top + (
int)this->graph_size.height;
890 const int y_max = r.top;
899 const uint horz_div_scl = (this->horizontal_scale <= 20) ? 1 : 10;
901 const uint horz_divisions = this->horizontal_scale / horz_div_scl;
903 const uint vert_divisions = 10;
906 for (uint division = 0; division < vert_divisions; division++) {
907 int y =
Scinterlate(y_zero, y_max, 0, (
int)vert_divisions, (
int)division);
908 GfxDrawLine(x_zero, y, x_max, y, c_grid);
909 if (division % 2 == 0) {
920 for (uint division = horz_divisions; division > 0; division--) {
921 int x =
Scinterlate(x_zero, x_max, 0, (
int)horz_divisions, (
int)horz_divisions - (
int)division);
922 GfxDrawLine(x, y_max, x, y_zero, c_grid);
923 if (division % 2 == 0) {
924 SetDParam(0, division * horz_div_scl / 2);
932 (int)Scinterlate<int64>(y_zero, y_max, 0, this->vertical_scale, durations[point])
938 Point peak_point = { 0, 0 };
941 int points_drawn = 0;
948 if (value == PerformanceData::INVALID_DURATION) {
950 lastts = timestamps[point];
955 time_sum += lastts - timestamps[point];
956 lastts = timestamps[point];
958 if (time_sum > draw_horz_scale)
break;
962 (int)Scinterlate<int64>(x_zero, x_max, 0, (int64)draw_horz_scale, (int64)draw_horz_scale - (int64)time_sum),
963 (
int)Scinterlate<int64>(y_zero, y_max, 0, (int64)draw_vert_scale, (int64)value)
965 assert(newpoint.x <= lastpoint.x);
966 GfxDrawLine(lastpoint.x, lastpoint.y, newpoint.x, newpoint.y, c_lines);
967 lastpoint = newpoint;
972 if (value > peak_value) {
974 peak_point = newpoint;
979 if (points_drawn > 0 && peak_value >
TIMESTAMP_PRECISION / 100 && 2 * peak_value > 3 * value_sum / points_drawn) {
981 GfxFillRect(peak_point.x - 1, peak_point.y - 1, peak_point.x + 1, peak_point.y + 1, c_peak);
984 if (peak_point.x - x_zero > (
int)this->graph_size.width / 2) {
994 static WindowDesc _frametime_graph_window_desc(
995 WDP_AUTO,
"frametime_graph", 140, 90,
998 _frametime_graph_window_widgets,
lengthof(_frametime_graph_window_widgets)
1006 AllocateWindowDescFront<FramerateWindow>(&_framerate_display_desc, 0);
1012 if (elem < PFE_FIRST || elem >=
PFE_MAX)
return;
1013 AllocateWindowDescFront<FrametimeGraphWindow>(&_frametime_graph_window_desc, elem,
true);
1023 IConsolePrintF(TC_SILVER,
"Based on num. data points: %d %d %d", count1, count2, count3);
1025 static const char *MEASUREMENT_NAMES[
PFE_MAX] = {
1027 " GL station ticks",
1029 " GL road vehicle ticks",
1031 " GL aircraft ticks",
1032 " GL landscape ticks",
1033 " GL link graph delays",
1035 " Viewport drawing",
1038 "AI/GS scripts total",
1041 char ai_name_buf[128];
1045 bool printed_anything =
false;
1049 if (pf.num_valid == 0)
continue;
1051 MEASUREMENT_NAMES[*e],
1054 printed_anything =
true;
1059 if (pf.num_valid == 0)
continue;
1062 name = MEASUREMENT_NAMES[e];
1069 pf.GetAverageDurationMilliseconds(count1),
1070 pf.GetAverageDurationMilliseconds(count2),
1071 pf.GetAverageDurationMilliseconds(count3));
1072 printed_anything =
true;
1075 if (!printed_anything) {