OpenTTD Source  20241120-master-g6d3adc6169
framerate_gui.cpp
Go to the documentation of this file.
1 /*
2  * This file is part of OpenTTD.
3  * OpenTTD is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, version 2.
4  * OpenTTD is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
5  * See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with OpenTTD. If not, see <http://www.gnu.org/licenses/>.
6  */
7 
10 #include "framerate_type.h"
11 #include <chrono>
12 #include "gfx_func.h"
13 #include "window_gui.h"
14 #include "window_func.h"
15 #include "table/sprites.h"
16 #include "string_func.h"
17 #include "strings_func.h"
18 #include "console_func.h"
19 #include "console_type.h"
20 #include "company_base.h"
21 #include "ai/ai_info.hpp"
22 #include "ai/ai_instance.hpp"
23 #include "game/game.hpp"
24 #include "game/game_instance.hpp"
25 #include "timer/timer.h"
26 #include "timer/timer_window.h"
27 
29 
30 #include <atomic>
31 #include <mutex>
32 
33 #include "safeguards.h"
34 
35 static std::mutex _sound_perf_lock;
36 static std::atomic<bool> _sound_perf_pending;
37 static std::vector<TimingMeasurement> _sound_perf_measurements;
38 
42 namespace {
43 
45  const int NUM_FRAMERATE_POINTS = 512;
48 
49  struct PerformanceData {
51  static const TimingMeasurement INVALID_DURATION = UINT64_MAX;
52 
58  double expected_rate;
64  int num_valid;
65 
70 
77  explicit PerformanceData(double expected_rate) : expected_rate(expected_rate), next_index(0), prev_index(0), num_valid(0) { }
78 
80  void Add(TimingMeasurement start_time, TimingMeasurement end_time)
81  {
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;
86  if (this->next_index >= NUM_FRAMERATE_POINTS) this->next_index = 0;
87  this->num_valid = std::min(NUM_FRAMERATE_POINTS, this->num_valid + 1);
88  }
89 
92  {
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;
97  if (this->next_index >= NUM_FRAMERATE_POINTS) this->next_index = 0;
98  this->num_valid = std::min(NUM_FRAMERATE_POINTS, this->num_valid + 1);
99 
100  this->acc_duration = 0;
101  this->acc_timestamp = start_time;
102  }
103 
106  {
107  this->acc_duration += duration;
108  }
109 
111  void AddPause(TimingMeasurement start_time)
112  {
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;
118  if (this->next_index >= NUM_FRAMERATE_POINTS) this->next_index = 0;
119  this->num_valid += 1;
120  }
121  }
122 
125  {
126  count = std::min(count, this->num_valid);
127 
128  int first_point = this->prev_index - count;
129  if (first_point < 0) first_point += NUM_FRAMERATE_POINTS;
130 
131  /* Sum durations, skipping invalid points */
132  double sumtime = 0;
133  for (int i = first_point; i < first_point + count; i++) {
134  auto d = this->durations[i % NUM_FRAMERATE_POINTS];
135  if (d != INVALID_DURATION) {
136  sumtime += d;
137  } else {
138  /* Don't count the invalid durations */
139  count--;
140  }
141  }
142 
143  if (count == 0) return 0; // avoid div by zero
144  return sumtime * 1000 / count / TIMESTAMP_PRECISION;
145  }
146 
148  double GetRate()
149  {
150  /* Start at last recorded point, end at latest when reaching the earliest recorded point */
151  int point = this->prev_index;
152  int last_point = this->next_index - this->num_valid;
153  if (last_point < 0) last_point += NUM_FRAMERATE_POINTS;
154 
155  /* Number of data points collected */
156  int count = 0;
157  /* Time of previous data point */
158  TimingMeasurement last = this->timestamps[point];
159  /* Total duration covered by collected points */
160  TimingMeasurement total = 0;
161 
162  /* We have nothing to compare the first point against */
163  point--;
164  if (point < 0) point = NUM_FRAMERATE_POINTS - 1;
165 
166  while (point != last_point) {
167  /* Only record valid data points, but pretend the gaps in measurements aren't there */
168  if (this->durations[point] != INVALID_DURATION) {
169  total += last - this->timestamps[point];
170  count++;
171  }
172  last = this->timestamps[point];
173  if (total >= TIMESTAMP_PRECISION) break; // end after 1 second has been collected
174  point--;
175  if (point < 0) point = NUM_FRAMERATE_POINTS - 1;
176  }
177 
178  if (total == 0 || count == 0) return 0;
179  return (double)count * TIMESTAMP_PRECISION / total;
180  }
181  };
182 
184  static const double GL_RATE = 1000.0 / MILLISECONDS_PER_TICK;
185 
192  PerformanceData(GL_RATE), // PFE_GAMELOOP
193  PerformanceData(1), // PFE_ACC_GL_ECONOMY
194  PerformanceData(1), // PFE_ACC_GL_TRAINS
195  PerformanceData(1), // PFE_ACC_GL_ROADVEHS
196  PerformanceData(1), // PFE_ACC_GL_SHIPS
197  PerformanceData(1), // PFE_ACC_GL_AIRCRAFT
198  PerformanceData(1), // PFE_GL_LANDSCAPE
199  PerformanceData(1), // PFE_GL_LINKGRAPH
200  PerformanceData(1000.0 / 30), // PFE_DRAWING
201  PerformanceData(1), // PFE_ACC_DRAWWORLD
202  PerformanceData(60.0), // PFE_VIDEO
203  PerformanceData(1000.0 * 8192 / 44100), // PFE_SOUND
204  PerformanceData(1), // PFE_ALLSCRIPTS
205  PerformanceData(1), // PFE_GAMESCRIPT
206  PerformanceData(1), // PFE_AI0 ...
207  PerformanceData(1),
208  PerformanceData(1),
209  PerformanceData(1),
210  PerformanceData(1),
211  PerformanceData(1),
212  PerformanceData(1),
213  PerformanceData(1),
214  PerformanceData(1),
215  PerformanceData(1),
216  PerformanceData(1),
217  PerformanceData(1),
218  PerformanceData(1),
219  PerformanceData(1),
220  PerformanceData(1), // PFE_AI14
221  };
222 
223 }
224 
225 
232 {
233  using namespace std::chrono;
234  return (TimingMeasurement)time_point_cast<microseconds>(high_resolution_clock::now()).time_since_epoch().count();
235 }
236 
237 
243 {
244  assert(elem < PFE_MAX);
245 
246  this->elem = elem;
247  this->start_time = GetPerformanceTimer();
248 }
249 
252 {
253  if (this->elem == PFE_ALLSCRIPTS) {
254  /* Hack to not record scripts total when no scripts are active */
255  bool any_active = _pf_data[PFE_GAMESCRIPT].num_valid > 0;
256  for (uint e = PFE_AI0; e < PFE_MAX; e++) any_active |= _pf_data[e].num_valid > 0;
257  if (!any_active) {
259  return;
260  }
261  }
262  if (this->elem == PFE_SOUND) {
263  /* PFE_SOUND measurements are made from the mixer thread.
264  * _pf_data cannot be concurrently accessed from the mixer thread
265  * and the main thread, so store the measurement results in a
266  * mutex-protected queue which is drained by the main thread.
267  * See: ProcessPendingPerformanceMeasurements() */
269  std::lock_guard lk(_sound_perf_lock);
270  if (_sound_perf_measurements.size() >= NUM_FRAMERATE_POINTS * 2) return;
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);
274  return;
275  }
276  _pf_data[this->elem].Add(this->start_time, GetPerformanceTimer());
277 }
278 
281 {
282  _pf_data[this->elem].expected_rate = rate;
283 }
284 
287 {
288  _pf_data[elem].num_valid = 0;
289  _pf_data[elem].next_index = 0;
290  _pf_data[elem].prev_index = 0;
291 }
292 
298 {
301 }
302 
303 
309 {
310  assert(elem < PFE_MAX);
311 
312  this->elem = elem;
313  this->start_time = GetPerformanceTimer();
314 }
315 
318 {
319  _pf_data[this->elem].AddAccumulate(GetPerformanceTimer() - this->start_time);
320 }
321 
328 {
330 }
331 
332 
334 
335 
336 static const PerformanceElement DISPLAY_ORDER_PFE[PFE_MAX] = {
337  PFE_GAMELOOP,
341  PFE_GL_SHIPS,
346  PFE_AI0,
347  PFE_AI1,
348  PFE_AI2,
349  PFE_AI3,
350  PFE_AI4,
351  PFE_AI5,
352  PFE_AI6,
353  PFE_AI7,
354  PFE_AI8,
355  PFE_AI9,
356  PFE_AI10,
357  PFE_AI11,
358  PFE_AI12,
359  PFE_AI13,
360  PFE_AI14,
362  PFE_DRAWING,
364  PFE_VIDEO,
365  PFE_SOUND,
366 };
367 
368 static const char * GetAIName(int ai_index)
369 {
370  if (!Company::IsValidAiID(ai_index)) return "";
371  return Company::Get(ai_index)->ai_info->GetName().c_str();
372 }
373 
375 static constexpr NWidgetPart _framerate_window_widgets[] = {
377  NWidget(WWT_CLOSEBOX, COLOUR_GREY),
378  NWidget(WWT_CAPTION, COLOUR_GREY, WID_FRW_CAPTION), SetDataTip(STR_FRAMERATE_CAPTION, STR_TOOLTIP_WINDOW_TITLE_DRAG_THIS),
379  NWidget(WWT_SHADEBOX, COLOUR_GREY),
380  NWidget(WWT_STICKYBOX, COLOUR_GREY),
381  EndContainer(),
382  NWidget(WWT_PANEL, COLOUR_GREY),
384  NWidget(WWT_TEXT, COLOUR_GREY, WID_FRW_RATE_GAMELOOP), SetDataTip(STR_FRAMERATE_RATE_GAMELOOP, STR_FRAMERATE_RATE_GAMELOOP_TOOLTIP), SetFill(1, 0), SetResize(1, 0),
385  NWidget(WWT_TEXT, COLOUR_GREY, WID_FRW_RATE_DRAWING), SetDataTip(STR_FRAMERATE_RATE_BLITTER, STR_FRAMERATE_RATE_BLITTER_TOOLTIP), SetFill(1, 0), SetResize(1, 0),
386  NWidget(WWT_TEXT, COLOUR_GREY, WID_FRW_RATE_FACTOR), SetDataTip(STR_FRAMERATE_SPEED_FACTOR, STR_FRAMERATE_SPEED_FACTOR_TOOLTIP), SetFill(1, 0), SetResize(1, 0),
387  EndContainer(),
388  EndContainer(),
390  NWidget(WWT_PANEL, COLOUR_GREY),
393  NWidget(WWT_EMPTY, COLOUR_GREY, WID_FRW_TIMES_NAMES), SetScrollbar(WID_FRW_SCROLLBAR),
394  NWidget(WWT_EMPTY, COLOUR_GREY, WID_FRW_TIMES_CURRENT), SetScrollbar(WID_FRW_SCROLLBAR),
395  NWidget(WWT_EMPTY, COLOUR_GREY, WID_FRW_TIMES_AVERAGE), SetScrollbar(WID_FRW_SCROLLBAR),
396  NWidget(NWID_SELECTION, INVALID_COLOUR, WID_FRW_SEL_MEMORY),
397  NWidget(WWT_EMPTY, COLOUR_GREY, WID_FRW_ALLOCSIZE), SetScrollbar(WID_FRW_SCROLLBAR),
398  EndContainer(),
399  EndContainer(),
400  NWidget(WWT_TEXT, COLOUR_GREY, WID_FRW_INFO_DATA_POINTS), SetDataTip(STR_FRAMERATE_DATA_POINTS, 0x0), SetFill(1, 0), SetResize(1, 0),
401  EndContainer(),
402  EndContainer(),
404  NWidget(NWID_VSCROLLBAR, COLOUR_GREY, WID_FRW_SCROLLBAR),
405  NWidget(WWT_RESIZEBOX, COLOUR_GREY),
406  EndContainer(),
407  EndContainer(),
408 };
409 
411  bool small;
412  bool showing_memory;
413  int num_active;
414  int num_displayed;
415 
416  struct CachedDecimal {
417  StringID strid;
418  uint32_t value;
419 
420  inline void SetRate(double value, double target)
421  {
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;
426  }
427 
428  inline void SetTime(double value, double target)
429  {
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;
434  }
435 
436  inline void InsertDParams(uint n) const
437  {
438  SetDParam(n, this->value);
439  SetDParam(n + 1, 2);
440  }
441  };
442 
448 
449  static constexpr int MIN_ELEMENTS = 5;
450 
451  FramerateWindow(WindowDesc &desc, WindowNumber number) : Window(desc)
452  {
453  this->InitNested(number);
454  this->small = this->IsShaded();
455  this->showing_memory = true;
456  this->UpdateData();
457  this->num_displayed = this->num_active;
458 
459  /* Window is always initialised to MIN_ELEMENTS height, resize to contain num_displayed */
460  ResizeWindow(this, 0, (std::max(MIN_ELEMENTS, this->num_displayed) - MIN_ELEMENTS) * GetCharacterHeight(FS_NORMAL));
461  }
462 
463  void OnRealtimeTick([[maybe_unused]] uint delta_ms) override
464  {
465  /* Check if the shaded state has changed, switch caption text if it has */
466  if (this->small != this->IsShaded()) {
467  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);
469  this->UpdateData();
470  this->SetDirty();
471  }
472  }
473 
475  IntervalTimer<TimerWindow> update_interval = {std::chrono::milliseconds(100), [this](auto) {
476  this->UpdateData();
477  this->SetDirty();
478  }};
479 
480  void UpdateData()
481  {
482  double gl_rate = _pf_data[PFE_GAMELOOP].GetRate();
483  bool have_script = false;
484  this->rate_gameloop.SetRate(gl_rate, _pf_data[PFE_GAMELOOP].expected_rate);
485  this->speed_gameloop.SetRate(gl_rate / _pf_data[PFE_GAMELOOP].expected_rate, 1.0);
486  if (this->small) return; // in small mode, this is everything needed
487 
488  this->rate_drawing.SetRate(_pf_data[PFE_DRAWING].GetRate(), _settings_client.gui.refresh_rate);
489 
490  int new_active = 0;
491  for (PerformanceElement e = PFE_FIRST; e < PFE_MAX; e++) {
492  this->times_shortterm[e].SetTime(_pf_data[e].GetAverageDurationMilliseconds(8), MILLISECONDS_PER_TICK);
493  this->times_longterm[e].SetTime(_pf_data[e].GetAverageDurationMilliseconds(NUM_FRAMERATE_POINTS), MILLISECONDS_PER_TICK);
494  if (_pf_data[e].num_valid > 0) {
495  new_active++;
496  if (e == PFE_GAMESCRIPT || e >= PFE_AI0) have_script = true;
497  }
498  }
499 
500  if (this->showing_memory != have_script) {
501  NWidgetStacked *plane = this->GetWidget<NWidgetStacked>(WID_FRW_SEL_MEMORY);
502  plane->SetDisplayedPlane(have_script ? 0 : SZSP_VERTICAL);
503  this->showing_memory = have_script;
504  }
505 
506  if (new_active != this->num_active) {
507  this->num_active = new_active;
508  Scrollbar *sb = this->GetScrollbar(WID_FRW_SCROLLBAR);
509  sb->SetCount(this->num_active);
510  sb->SetCapacity(std::min(this->num_displayed, this->num_active));
511  this->ReInit();
512  }
513  }
514 
515  void SetStringParameters(WidgetID widget) const override
516  {
517  switch (widget) {
518  case WID_FRW_CAPTION:
519  /* When the window is shaded, the caption shows game loop rate and speed factor */
520  if (!this->small) break;
521  SetDParam(0, this->rate_gameloop.strid);
522  this->rate_gameloop.InsertDParams(1);
523  this->speed_gameloop.InsertDParams(3);
524  break;
525 
526  case WID_FRW_RATE_GAMELOOP:
527  SetDParam(0, this->rate_gameloop.strid);
528  this->rate_gameloop.InsertDParams(1);
529  break;
530  case WID_FRW_RATE_DRAWING:
531  SetDParam(0, this->rate_drawing.strid);
532  this->rate_drawing.InsertDParams(1);
533  break;
534  case WID_FRW_RATE_FACTOR:
535  this->speed_gameloop.InsertDParams(0);
536  break;
537  case WID_FRW_INFO_DATA_POINTS:
539  break;
540  }
541  }
542 
543  void UpdateWidgetSize(WidgetID widget, Dimension &size, [[maybe_unused]] const Dimension &padding, [[maybe_unused]] Dimension &fill, [[maybe_unused]] Dimension &resize) override
544  {
545  switch (widget) {
546  case WID_FRW_RATE_GAMELOOP:
547  SetDParam(0, STR_FRAMERATE_FPS_GOOD);
548  SetDParamMaxDigits(1, 6);
549  SetDParam(2, 2);
550  size = GetStringBoundingBox(STR_FRAMERATE_RATE_GAMELOOP);
551  break;
552  case WID_FRW_RATE_DRAWING:
553  SetDParam(0, STR_FRAMERATE_FPS_GOOD);
554  SetDParamMaxDigits(1, 6);
555  SetDParam(2, 2);
556  size = GetStringBoundingBox(STR_FRAMERATE_RATE_BLITTER);
557  break;
558  case WID_FRW_RATE_FACTOR:
559  SetDParamMaxDigits(0, 6);
560  SetDParam(1, 2);
561  size = GetStringBoundingBox(STR_FRAMERATE_SPEED_FACTOR);
562  break;
563 
564  case WID_FRW_TIMES_NAMES: {
565  size.width = 0;
567  resize.width = 0;
569  for (PerformanceElement e : DISPLAY_ORDER_PFE) {
570  if (_pf_data[e].num_valid == 0) continue;
571  Dimension line_size;
572  if (e < PFE_AI0) {
573  line_size = GetStringBoundingBox(STR_FRAMERATE_GAMELOOP + e);
574  } else {
575  SetDParam(0, e - PFE_AI0 + 1);
576  SetDParamStr(1, GetAIName(e - PFE_AI0));
577  line_size = GetStringBoundingBox(STR_FRAMERATE_AI);
578  }
579  size.width = std::max(size.width, line_size.width);
580  }
581  break;
582  }
583 
584  case WID_FRW_TIMES_CURRENT:
585  case WID_FRW_TIMES_AVERAGE:
586  case WID_FRW_ALLOCSIZE: {
587  size = GetStringBoundingBox(STR_FRAMERATE_CURRENT + (widget - WID_FRW_TIMES_CURRENT));
588  SetDParamMaxDigits(0, 6);
589  SetDParam(1, 2);
590  Dimension item_size = GetStringBoundingBox(STR_FRAMERATE_MS_GOOD);
591  size.width = std::max(size.width, item_size.width);
593  resize.width = 0;
595  break;
596  }
597  }
598  }
599 
601  void DrawElementTimesColumn(const Rect &r, StringID heading_str, const CachedDecimal *values) const
602  {
603  const Scrollbar *sb = this->GetScrollbar(WID_FRW_SCROLLBAR);
604  int32_t skip = sb->GetPosition();
605  int drawable = this->num_displayed;
606  int y = r.top;
607  DrawString(r.left, r.right, y, heading_str, TC_FROMSTRING, SA_CENTER, true);
609  for (PerformanceElement e : DISPLAY_ORDER_PFE) {
610  if (_pf_data[e].num_valid == 0) continue;
611  if (skip > 0) {
612  skip--;
613  } else {
614  values[e].InsertDParams(0);
615  DrawString(r.left, r.right, y, values[e].strid, TC_FROMSTRING, SA_RIGHT);
617  drawable--;
618  if (drawable == 0) break;
619  }
620  }
621  }
622 
623  void DrawElementAllocationsColumn(const Rect &r) const
624  {
625  const Scrollbar *sb = this->GetScrollbar(WID_FRW_SCROLLBAR);
626  int32_t skip = sb->GetPosition();
627  int drawable = this->num_displayed;
628  int y = r.top;
629  DrawString(r.left, r.right, y, STR_FRAMERATE_MEMORYUSE, TC_FROMSTRING, SA_CENTER, true);
631  for (PerformanceElement e : DISPLAY_ORDER_PFE) {
632  if (_pf_data[e].num_valid == 0) continue;
633  if (skip > 0) {
634  skip--;
635  } else if (e == PFE_GAMESCRIPT || e >= PFE_AI0) {
636  if (e == PFE_GAMESCRIPT) {
637  SetDParam(0, Game::GetInstance()->GetAllocatedMemory());
638  } else {
639  SetDParam(0, Company::Get(e - PFE_AI0)->ai_instance->GetAllocatedMemory());
640  }
641  DrawString(r.left, r.right, y, STR_FRAMERATE_BYTES_GOOD, TC_FROMSTRING, SA_RIGHT);
643  drawable--;
644  if (drawable == 0) break;
645  } else {
646  /* skip non-script */
648  drawable--;
649  if (drawable == 0) break;
650  }
651  }
652  }
653 
654  void DrawWidget(const Rect &r, WidgetID widget) const override
655  {
656  switch (widget) {
657  case WID_FRW_TIMES_NAMES: {
658  /* Render a column of titles for performance element names */
659  const Scrollbar *sb = this->GetScrollbar(WID_FRW_SCROLLBAR);
660  int32_t skip = sb->GetPosition();
661  int drawable = this->num_displayed;
662  int y = r.top + GetCharacterHeight(FS_NORMAL) + WidgetDimensions::scaled.vsep_normal; // first line contains headings in the value columns
663  for (PerformanceElement e : DISPLAY_ORDER_PFE) {
664  if (_pf_data[e].num_valid == 0) continue;
665  if (skip > 0) {
666  skip--;
667  } else {
668  if (e < PFE_AI0) {
669  DrawString(r.left, r.right, y, STR_FRAMERATE_GAMELOOP + e, TC_FROMSTRING, SA_LEFT);
670  } else {
671  SetDParam(0, e - PFE_AI0 + 1);
672  SetDParamStr(1, GetAIName(e - PFE_AI0));
673  DrawString(r.left, r.right, y, STR_FRAMERATE_AI, TC_FROMSTRING, SA_LEFT);
674  }
676  drawable--;
677  if (drawable == 0) break;
678  }
679  }
680  break;
681  }
682  case WID_FRW_TIMES_CURRENT:
683  /* Render short-term average values */
684  DrawElementTimesColumn(r, STR_FRAMERATE_CURRENT, this->times_shortterm);
685  break;
686  case WID_FRW_TIMES_AVERAGE:
687  /* Render averages of all recorded values */
688  DrawElementTimesColumn(r, STR_FRAMERATE_AVERAGE, this->times_longterm);
689  break;
690  case WID_FRW_ALLOCSIZE:
691  DrawElementAllocationsColumn(r);
692  break;
693  }
694  }
695 
696  void OnClick([[maybe_unused]] Point pt, WidgetID widget, [[maybe_unused]] int click_count) override
697  {
698  switch (widget) {
699  case WID_FRW_TIMES_NAMES:
700  case WID_FRW_TIMES_CURRENT:
701  case WID_FRW_TIMES_AVERAGE: {
702  /* Open time graph windows when clicking detail measurement lines */
703  const Scrollbar *sb = this->GetScrollbar(WID_FRW_SCROLLBAR);
705  if (line != INT32_MAX) {
706  line++;
707  /* Find the visible line that was clicked */
708  for (PerformanceElement e : DISPLAY_ORDER_PFE) {
709  if (_pf_data[e].num_valid > 0) line--;
710  if (line == 0) {
712  break;
713  }
714  }
715  }
716  break;
717  }
718  }
719  }
720 
721  void OnResize() override
722  {
723  auto *wid = this->GetWidget<NWidgetResizeBase>(WID_FRW_TIMES_NAMES);
724  this->num_displayed = (wid->current_y - wid->min_y - WidgetDimensions::scaled.vsep_normal) / GetCharacterHeight(FS_NORMAL) - 1; // subtract 1 for headings
725  this->GetScrollbar(WID_FRW_SCROLLBAR)->SetCapacity(this->num_displayed);
726  }
727 };
728 
729 static WindowDesc _framerate_display_desc(
730  WDP_AUTO, "framerate_display", 0, 0,
732  0,
733  _framerate_window_widgets
734 );
735 
736 
738 static constexpr NWidgetPart _frametime_graph_window_widgets[] = {
740  NWidget(WWT_CLOSEBOX, COLOUR_GREY),
741  NWidget(WWT_CAPTION, COLOUR_GREY, WID_FGW_CAPTION), SetDataTip(STR_JUST_STRING2, STR_TOOLTIP_WINDOW_TITLE_DRAG_THIS), SetTextStyle(TC_WHITE),
742  NWidget(WWT_STICKYBOX, COLOUR_GREY),
743  EndContainer(),
744  NWidget(WWT_PANEL, COLOUR_GREY),
746  NWidget(WWT_EMPTY, COLOUR_GREY, WID_FGW_GRAPH),
747  EndContainer(),
748  EndContainer(),
749 };
750 
754 
757 
758  FrametimeGraphWindow(WindowDesc &desc, WindowNumber number) : Window(desc)
759  {
760  this->element = (PerformanceElement)number;
761  this->horizontal_scale = 4;
762  this->vertical_scale = TIMESTAMP_PRECISION / 10;
763 
764  this->InitNested(number);
765  this->UpdateScale();
766  }
767 
768  void SetStringParameters(WidgetID widget) const override
769  {
770  switch (widget) {
771  case WID_FGW_CAPTION:
772  if (this->element < PFE_AI0) {
773  SetDParam(0, STR_FRAMETIME_CAPTION_GAMELOOP + this->element);
774  } else {
775  SetDParam(0, STR_FRAMETIME_CAPTION_AI);
776  SetDParam(1, this->element - PFE_AI0 + 1);
777  SetDParamStr(2, GetAIName(this->element - PFE_AI0));
778  }
779  break;
780  }
781  }
782 
783  void UpdateWidgetSize(WidgetID widget, Dimension &size, [[maybe_unused]] const Dimension &padding, [[maybe_unused]] Dimension &fill, [[maybe_unused]] Dimension &resize) override
784  {
785  if (widget == WID_FGW_GRAPH) {
786  SetDParam(0, 100);
787  Dimension size_ms_label = GetStringBoundingBox(STR_FRAMERATE_GRAPH_MILLISECONDS);
788  SetDParam(0, 100);
789  Dimension size_s_label = GetStringBoundingBox(STR_FRAMERATE_GRAPH_SECONDS);
790 
791  /* Size graph in height to fit at least 10 vertical labels with space between, or at least 100 pixels */
792  graph_size.height = std::max(100u, 10 * (size_ms_label.height + 1));
793  /* Always 2:1 graph area */
794  graph_size.width = 2 * graph_size.height;
795  size = graph_size;
796 
797  size.width += size_ms_label.width + 2;
798  size.height += size_s_label.height + 2;
799  }
800  }
801 
802  void SelectHorizontalScale(TimingMeasurement range)
803  {
804  /* 60 Hz graphical drawing results in a value of approximately TIMESTAMP_PRECISION,
805  * this lands exactly on the scale = 2 vs scale = 4 boundary.
806  * To avoid excessive switching of the horizontal scale, bias these performance
807  * categories away from this scale boundary. */
808  if (this->element == PFE_DRAWING || this->element == PFE_DRAWWORLD) range += (range / 2);
809 
810  /* Determine horizontal scale based on period covered by 60 points
811  * (slightly less than 2 seconds at full game speed) */
812  struct ScaleDef { TimingMeasurement range; int scale; };
813  static const std::initializer_list<ScaleDef> hscales = {
814  { TIMESTAMP_PRECISION * 120, 60 },
815  { TIMESTAMP_PRECISION * 10, 20 },
816  { TIMESTAMP_PRECISION * 5, 10 },
817  { TIMESTAMP_PRECISION * 3, 4 },
818  { TIMESTAMP_PRECISION * 1, 2 },
819  };
820  for (const auto &sc : hscales) {
821  if (range < sc.range) this->horizontal_scale = sc.scale;
822  }
823  }
824 
825  void SelectVerticalScale(TimingMeasurement range)
826  {
827  /* Determine vertical scale based on peak value (within the horizontal scale + a bit) */
828  static const std::initializer_list<TimingMeasurement> vscales = {
829  TIMESTAMP_PRECISION * 100,
830  TIMESTAMP_PRECISION * 10,
835  TIMESTAMP_PRECISION / 10,
836  TIMESTAMP_PRECISION / 50,
837  TIMESTAMP_PRECISION / 200,
838  };
839  for (const auto &sc : vscales) {
840  if (range < sc) this->vertical_scale = (int)sc;
841  }
842  }
843 
845  void UpdateScale()
846  {
847  const TimingMeasurement *durations = _pf_data[this->element].durations;
848  const TimingMeasurement *timestamps = _pf_data[this->element].timestamps;
849  int num_valid = _pf_data[this->element].num_valid;
850  int point = _pf_data[this->element].prev_index;
851 
852  TimingMeasurement lastts = timestamps[point];
853  TimingMeasurement time_sum = 0;
854  TimingMeasurement peak_value = 0;
855  int count = 0;
856 
857  /* Sensible default for when too few measurements are available */
858  this->horizontal_scale = 4;
859 
860  for (int i = 1; i < num_valid; i++) {
861  point--;
862  if (point < 0) point = NUM_FRAMERATE_POINTS - 1;
863 
864  TimingMeasurement value = durations[point];
865  if (value == PerformanceData::INVALID_DURATION) {
866  /* Skip gaps in data by pretending time is continuous across them */
867  lastts = timestamps[point];
868  continue;
869  }
870  if (value > peak_value) peak_value = value;
871  count++;
872 
873  /* Accumulate period of time covered by data */
874  time_sum += lastts - timestamps[point];
875  lastts = timestamps[point];
876 
877  /* Enough data to select a range and get decent data density */
878  if (count == 60) this->SelectHorizontalScale(time_sum);
879 
880  /* End when enough points have been collected and the horizontal scale has been exceeded */
881  if (count >= 60 && time_sum >= (this->horizontal_scale + 2) * TIMESTAMP_PRECISION / 2) break;
882  }
883 
884  this->SelectVerticalScale(peak_value);
885  }
886 
888  IntervalTimer<TimerWindow> update_interval = {std::chrono::milliseconds(500), [this](auto) {
889  this->UpdateScale();
890  }};
891 
892  void OnRealtimeTick([[maybe_unused]] uint delta_ms) override
893  {
894  this->SetDirty();
895  }
896 
898  template<typename T>
899  static inline T Scinterlate(T dst_min, T dst_max, T src_min, T src_max, T value)
900  {
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;
904  }
905 
906  void DrawWidget(const Rect &r, WidgetID widget) const override
907  {
908  if (widget == WID_FGW_GRAPH) {
909  const TimingMeasurement *durations = _pf_data[this->element].durations;
910  const TimingMeasurement *timestamps = _pf_data[this->element].timestamps;
911  int point = _pf_data[this->element].prev_index;
912 
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;
917  const int c_grid = PC_DARK_GREY;
918  const int c_lines = PC_BLACK;
919  const int c_peak = PC_DARK_RED;
920 
921  const TimingMeasurement draw_horz_scale = (TimingMeasurement)this->horizontal_scale * TIMESTAMP_PRECISION / 2;
922  const TimingMeasurement draw_vert_scale = (TimingMeasurement)this->vertical_scale;
923 
924  /* Number of \c horizontal_scale units in each horizontal division */
925  const uint horz_div_scl = (this->horizontal_scale <= 20) ? 1 : 10;
926  /* Number of divisions of the horizontal axis */
927  const uint horz_divisions = this->horizontal_scale / horz_div_scl;
928  /* Number of divisions of the vertical axis */
929  const uint vert_divisions = 10;
930 
931  /* Draw division lines and labels for the vertical axis */
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) {
936  if ((TimingMeasurement)this->vertical_scale > TIMESTAMP_PRECISION) {
937  SetDParam(0, this->vertical_scale * division / 10 / TIMESTAMP_PRECISION);
938  DrawString(r.left, x_zero - 2, y - GetCharacterHeight(FS_SMALL), STR_FRAMERATE_GRAPH_SECONDS, TC_GREY, SA_RIGHT | SA_FORCE, false, FS_SMALL);
939  } else {
940  SetDParam(0, this->vertical_scale * division / 10 * 1000 / TIMESTAMP_PRECISION);
941  DrawString(r.left, x_zero - 2, y - GetCharacterHeight(FS_SMALL), STR_FRAMERATE_GRAPH_MILLISECONDS, TC_GREY, SA_RIGHT | SA_FORCE, false, FS_SMALL);
942  }
943  }
944  }
945  /* Draw division lines and labels for the horizontal axis */
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);
951  DrawString(x, x_max, y_zero + 2, STR_FRAMERATE_GRAPH_SECONDS, TC_GREY, SA_LEFT | SA_FORCE, false, FS_SMALL);
952  }
953  }
954 
955  /* Position of last rendered data point */
956  Point lastpoint = {
957  x_max,
958  (int)Scinterlate<int64_t>(y_zero, y_max, 0, this->vertical_scale, durations[point])
959  };
960  /* Timestamp of last rendered data point */
961  TimingMeasurement lastts = timestamps[point];
962 
963  TimingMeasurement peak_value = 0;
964  Point peak_point = { 0, 0 };
965  TimingMeasurement value_sum = 0;
966  TimingMeasurement time_sum = 0;
967  int points_drawn = 0;
968 
969  for (int i = 1; i < NUM_FRAMERATE_POINTS; i++) {
970  point--;
971  if (point < 0) point = NUM_FRAMERATE_POINTS - 1;
972 
973  TimingMeasurement value = durations[point];
974  if (value == PerformanceData::INVALID_DURATION) {
975  /* Skip gaps in measurements, pretend the data points on each side are continuous */
976  lastts = timestamps[point];
977  continue;
978  }
979 
980  /* Use total time period covered for value along horizontal axis */
981  time_sum += lastts - timestamps[point];
982  lastts = timestamps[point];
983  /* Stop if past the width of the graph */
984  if (time_sum > draw_horz_scale) break;
985 
986  /* Draw line from previous point to new point */
987  Point newpoint = {
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)
990  };
991  if (newpoint.x > lastpoint.x) continue; // don't draw backwards
992  GfxDrawLine(lastpoint.x, lastpoint.y, newpoint.x, newpoint.y, c_lines);
993  lastpoint = newpoint;
994 
995  /* Record peak and average value across graphed data */
996  value_sum += value;
997  points_drawn++;
998  if (value > peak_value) {
999  peak_value = value;
1000  peak_point = newpoint;
1001  }
1002  }
1003 
1004  /* If the peak value is significantly larger than the average, mark and label it */
1005  if (points_drawn > 0 && peak_value > TIMESTAMP_PRECISION / 100 && 2 * peak_value > 3 * value_sum / points_drawn) {
1006  TextColour tc_peak = (TextColour)(TC_IS_PALETTE_COLOUR | c_peak);
1007  GfxFillRect(peak_point.x - 1, peak_point.y - 1, peak_point.x + 1, peak_point.y + 1, c_peak);
1008  SetDParam(0, peak_value * 1000 / TIMESTAMP_PRECISION);
1009  int label_y = std::max(y_max, peak_point.y - GetCharacterHeight(FS_SMALL));
1010  if (peak_point.x - x_zero > (int)this->graph_size.width / 2) {
1011  DrawString(x_zero, peak_point.x - 2, label_y, STR_FRAMERATE_GRAPH_MILLISECONDS, tc_peak, SA_RIGHT | SA_FORCE, false, FS_SMALL);
1012  } else {
1013  DrawString(peak_point.x + 2, x_max, label_y, STR_FRAMERATE_GRAPH_MILLISECONDS, tc_peak, SA_LEFT | SA_FORCE, false, FS_SMALL);
1014  }
1015  }
1016  }
1017  }
1018 };
1019 
1020 static WindowDesc _frametime_graph_window_desc(
1021  WDP_AUTO, "frametime_graph", 140, 90,
1023  0,
1024  _frametime_graph_window_widgets
1025 );
1026 
1027 
1028 
1031 {
1032  AllocateWindowDescFront<FramerateWindow>(_framerate_display_desc, 0);
1033 }
1034 
1037 {
1038  if (elem < PFE_FIRST || elem >= PFE_MAX) return; // maybe warn?
1039  AllocateWindowDescFront<FrametimeGraphWindow>(_frametime_graph_window_desc, elem, true);
1040 }
1041 
1044 {
1045  const int count1 = NUM_FRAMERATE_POINTS / 8;
1046  const int count2 = NUM_FRAMERATE_POINTS / 4;
1047  const int count3 = NUM_FRAMERATE_POINTS / 1;
1048 
1049  IConsolePrint(TC_SILVER, "Based on num. data points: {} {} {}", count1, count2, count3);
1050 
1051  static const std::array<std::string_view, PFE_MAX> MEASUREMENT_NAMES = {
1052  "Game loop",
1053  " GL station ticks",
1054  " GL train ticks",
1055  " GL road vehicle ticks",
1056  " GL ship ticks",
1057  " GL aircraft ticks",
1058  " GL landscape ticks",
1059  " GL link graph delays",
1060  "Drawing",
1061  " Viewport drawing",
1062  "Video output",
1063  "Sound mixing",
1064  "AI/GS scripts total",
1065  "Game script",
1066  };
1067  std::string ai_name_buf;
1068 
1069  bool printed_anything = false;
1070 
1071  for (const auto &e : { PFE_GAMELOOP, PFE_DRAWING, PFE_VIDEO }) {
1072  auto &pf = _pf_data[e];
1073  if (pf.num_valid == 0) continue;
1074  IConsolePrint(TC_GREEN, "{} rate: {:.2f}fps (expected: {:.2f}fps)",
1075  MEASUREMENT_NAMES[e],
1076  pf.GetRate(),
1077  pf.expected_rate);
1078  printed_anything = true;
1079  }
1080 
1081  for (PerformanceElement e = PFE_FIRST; e < PFE_MAX; e++) {
1082  auto &pf = _pf_data[e];
1083  if (pf.num_valid == 0) continue;
1084  std::string_view name;
1085  if (e < PFE_AI0) {
1086  name = MEASUREMENT_NAMES[e];
1087  } else {
1088  ai_name_buf = fmt::format("AI {} {}", e - PFE_AI0 + 1, GetAIName(e - PFE_AI0));
1089  name = ai_name_buf;
1090  }
1091  IConsolePrint(TC_LIGHT_BLUE, "{} times: {:.2f}ms {:.2f}ms {:.2f}ms",
1092  name,
1093  pf.GetAverageDurationMilliseconds(count1),
1094  pf.GetAverageDurationMilliseconds(count2),
1095  pf.GetAverageDurationMilliseconds(count3));
1096  printed_anything = true;
1097  }
1098 
1099  if (!printed_anything) {
1100  IConsolePrint(CC_ERROR, "No performance measurements have been taken yet.");
1101  }
1102 }
1103 
1112 {
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) {
1116  _pf_data[PFE_SOUND].Add(_sound_perf_measurements[i], _sound_perf_measurements[i + 1]);
1117  }
1118  _sound_perf_measurements.clear();
1119  _sound_perf_pending.store(false, std::memory_order_relaxed);
1120  }
1121 }
AIInfo keeps track of all information of an AI, like Author, Description, ...
The AIInstance tracks an AI.
static class GameInstance * GetInstance()
Get the current active instance.
Definition: game.hpp:101
Stacked widgets, widgets all occupying the same space in the window.
Definition: widget_type.h:498
bool SetDisplayedPlane(int plane)
Select which plane to show (for NWID_SELECTION only).
Definition: widget.cpp:1342
~PerformanceAccumulator()
Finish and add one block of the accumulating value.
static void Reset(PerformanceElement elem)
Store the previous accumulator value and reset for a new cycle of accumulating measurements.
PerformanceAccumulator(PerformanceElement elem)
Begin measuring one block of the accumulating value.
static void SetInactive(PerformanceElement elem)
Mark a performance element as not currently in use.
static void Paused(PerformanceElement elem)
Indicate that a cycle of "pause" where no processing occurs.
void SetExpectedRate(double rate)
Set the rate of expected cycles per second of a performance element.
PerformanceMeasurer(PerformanceElement elem)
Begin a cycle of a measured element.
~PerformanceMeasurer()
Finish a cycle of a measured element and store the measurement taken.
Scrollbar data structure.
Definition: widget_type.h:694
void SetCount(size_t num)
Sets the number of elements in the list.
Definition: widget_type.h:780
void SetCapacity(size_t capacity)
Set the capacity of visible elements.
Definition: widget_type.h:794
size_type GetScrolledRowFromWidget(int clickpos, const Window *const w, WidgetID widget, int padding=0, int line_height=-1) const
Compute the row of a scrolled widget that a user clicked in.
Definition: widget.cpp:2320
size_type GetPosition() const
Gets the position of the first visible element in the list.
Definition: widget_type.h:740
static WidgetDimensions scaled
Widget dimensions scaled for current zoom level.
Definition: window_gui.h:68
int vsep_normal
Normal vertical spacing.
Definition: window_gui.h:60
static const WidgetDimensions unscaled
Unscaled widget dimensions.
Definition: window_gui.h:67
Definition of stuff that is very close to a company, like the company struct itself.
void IConsolePrint(TextColour colour_code, const std::string &string)
Handle the printing of text entered into the console or redirected there by any other means.
Definition: console.cpp:89
Console functions used outside of the console code.
Globally used console related types.
static const TextColour CC_ERROR
Colour for error lines.
Definition: console_type.h:24
int GetCharacterHeight(FontSize size)
Get height of a character for a given font size.
Definition: fontcache.cpp:77
void ShowFramerateWindow()
Open the general framerate window.
static TimingMeasurement GetPerformanceTimer()
Return a timestamp with TIMESTAMP_PRECISION ticks per second precision.
void ProcessPendingPerformanceMeasurements()
This drains the PFE_SOUND measurement data queue into _pf_data.
void ShowFrametimeGraphWindow(PerformanceElement elem)
Open a graph window for a performance element.
void ConPrintFramerate()
Print performance statistics to game console.
Types for recording game performance data.
PerformanceElement
Elements of game performance that can be measured.
@ PFE_AI6
AI execution for player slot 7.
@ PFE_AI1
AI execution for player slot 2.
@ PFE_GAMELOOP
Speed of gameloop processing.
@ PFE_AI9
AI execution for player slot 10.
@ PFE_GL_SHIPS
Time spent processing ships.
@ PFE_AI3
AI execution for player slot 4.
@ PFE_AI4
AI execution for player slot 5.
@ PFE_AI11
AI execution for player slot 12.
@ PFE_AI8
AI execution for player slot 9.
@ PFE_GAMESCRIPT
Game script execution.
@ PFE_VIDEO
Speed of painting drawn video buffer.
@ PFE_GL_LINKGRAPH
Time spent waiting for link graph background jobs.
@ PFE_AI13
AI execution for player slot 14.
@ PFE_AI7
AI execution for player slot 8.
@ PFE_GL_AIRCRAFT
Time spent processing aircraft.
@ PFE_GL_ECONOMY
Time spent processing cargo movement.
@ PFE_AI0
AI execution for player slot 1.
@ PFE_DRAWING
Speed of drawing world and GUI.
@ PFE_AI12
AI execution for player slot 13.
@ PFE_AI2
AI execution for player slot 3.
@ PFE_AI10
AI execution for player slot 11.
@ PFE_GL_LANDSCAPE
Time spent processing other world features.
@ PFE_GL_ROADVEHS
Time spend processing road vehicles.
@ PFE_GL_TRAINS
Time spent processing trains.
@ PFE_MAX
End of enum, must be last.
@ PFE_ALLSCRIPTS
Sum of all GS/AI scripts.
@ PFE_SOUND
Speed of mixing audio samples.
@ PFE_DRAWWORLD
Time spent drawing world viewports in GUI.
@ PFE_AI14
AI execution for player slot 15.
@ PFE_AI5
AI execution for player slot 6.
uint64_t TimingMeasurement
Type used to hold a performance timing measurement.
Types related to the framerate windows widgets.
Base functions for all Games.
The GameInstance tracks games.
Dimension GetStringBoundingBox(std::string_view str, FontSize start_fontsize)
Return the string dimension in pixels.
Definition: gfx.cpp:851
int DrawString(int left, int right, int top, std::string_view str, TextColour colour, StringAlignment align, bool underline, FontSize fontsize)
Draw string, possibly truncated to make it fit in its allocated space.
Definition: gfx.cpp:657
void GfxFillRect(int left, int top, int right, int bottom, int colour, FillRectMode mode)
Applies a certain FillRectMode-operation to a rectangle [left, right] x [top, bottom] on the screen.
Definition: gfx.cpp:114
Functions related to the gfx engine.
@ SA_LEFT
Left align the text.
Definition: gfx_type.h:343
@ SA_RIGHT
Right align the text (must be a single bit).
Definition: gfx_type.h:345
@ SA_FORCE
Force the alignment, i.e. don't swap for RTL languages.
Definition: gfx_type.h:355
@ SA_CENTER
Center both horizontally and vertically.
Definition: gfx_type.h:353
@ FS_SMALL
Index of the small font in the font tables.
Definition: gfx_type.h:210
@ FS_NORMAL
Index of the normal font in the font tables.
Definition: gfx_type.h:209
TextColour
Colour of the strings, see _string_colourmap in table/string_colours.h or docs/ottd-colourtext-palett...
Definition: gfx_type.h:260
@ TC_IS_PALETTE_COLOUR
Colour value is already a real palette colour index, not an index of a StringColour.
Definition: gfx_type.h:283
static const uint MILLISECONDS_PER_TICK
The number of milliseconds per game tick.
Definition: gfx_type.h:325
constexpr NWidgetPart SetFill(uint16_t fill_x, uint16_t fill_y)
Widget part function for setting filling.
Definition: widget_type.h:1181
constexpr NWidgetPart SetPIP(uint8_t pre, uint8_t inter, uint8_t post)
Widget part function for setting a pre/inter/post spaces.
Definition: widget_type.h:1260
constexpr NWidgetPart SetScrollbar(WidgetID index)
Attach a scrollbar to a widget.
Definition: widget_type.h:1284
constexpr NWidgetPart SetPadding(uint8_t top, uint8_t right, uint8_t bottom, uint8_t left)
Widget part function for setting additional space around a widget.
Definition: widget_type.h:1228
constexpr NWidgetPart SetDataTip(uint32_t data, StringID tip)
Widget part function for setting the data and tooltip.
Definition: widget_type.h:1202
constexpr NWidgetPart SetTextStyle(TextColour colour, FontSize size=FS_NORMAL)
Widget part function for setting the text style.
Definition: widget_type.h:1160
constexpr NWidgetPart NWidget(WidgetType tp, Colours col, WidgetID idx=-1)
Widget part function for starting a new 'real' widget.
Definition: widget_type.h:1309
constexpr NWidgetPart EndContainer()
Widget part function for denoting the end of a container (horizontal, vertical, WWT_FRAME,...
Definition: widget_type.h:1191
constexpr NWidgetPart SetResize(int16_t dx, int16_t dy)
Widget part function for setting the resize step.
Definition: widget_type.h:1126
void SetDirty() const
Mark entire window as dirty (in need of re-paint)
Definition: window.cpp:940
const TimingMeasurement TIMESTAMP_PRECISION
Units a second is divided into in performance measurements
static const double GL_RATE
Game loop rate, cycles per second
const int NUM_FRAMERATE_POINTS
Number of data points to keep in buffer for each performance measurement.
PerformanceData _pf_data[PFE_MAX]
Storage for all performance element measurements.
static const uint8_t PC_DARK_GREY
Dark grey palette colour.
Definition: palette_func.h:68
static const uint8_t PC_BLACK
Black palette colour.
Definition: palette_func.h:67
static const uint8_t PC_DARK_RED
Dark red palette colour.
Definition: palette_func.h:73
A number of safeguards to prevent using unsafe methods.
ClientSettings _settings_client
The current settings for this game.
Definition: settings.cpp:56
This file contains all sprite-related enums and defines.
Functions related to low-level strings.
void SetDParam(size_t n, uint64_t v)
Set a string parameter v at index n in the global string parameter array.
Definition: strings.cpp:104
void SetDParamStr(size_t n, const char *str)
This function is used to "bind" a C string to a OpenTTD dparam slot.
Definition: strings.cpp:357
void SetDParamMaxDigits(size_t n, uint count, FontSize size)
Set DParam n to some number that is suitable for string size computations.
Definition: strings.cpp:143
Functions related to OTTD's strings.
uint32_t StringID
Numeric value that represents a string, independent of the selected language.
Definition: strings_type.h:16
GUISettings gui
settings related to the GUI
static bool IsValidAiID(size_t index)
Is this company a valid company, controlled by the computer (a NoAI program)?
Definition: company_base.h:159
Dimensions (a width and height) of a rectangle in 2D.
void OnResize() override
Called after the window got resized.
CachedDecimal rate_drawing
cached drawing frame rate
void OnRealtimeTick([[maybe_unused]] uint delta_ms) override
Called periodically.
IntervalTimer< TimerWindow > update_interval
Update the window on a regular interval.
void DrawElementTimesColumn(const Rect &r, StringID heading_str, const CachedDecimal *values) const
Render a column of formatted average durations.
CachedDecimal speed_gameloop
cached game loop speed factor
CachedDecimal rate_gameloop
cached game loop tick rate
CachedDecimal times_longterm[PFE_MAX]
cached long term average times
static constexpr int MIN_ELEMENTS
smallest number of elements to display
CachedDecimal times_shortterm[PFE_MAX]
cached short term average times
int horizontal_scale
number of half-second units horizontally
void OnRealtimeTick([[maybe_unused]] uint delta_ms) override
Called periodically.
void UpdateScale()
Recalculate the graph scaling factors based on current recorded data.
IntervalTimer< TimerWindow > update_interval
Update the scaling on a regular interval.
Dimension graph_size
size of the main graph area (excluding axis labels)
PerformanceElement element
what element this window renders graph for
static T Scinterlate(T dst_min, T dst_max, T src_min, T src_max, T value)
Scale and interpolate a value from a source range into a destination range.
int vertical_scale
number of TIMESTAMP_PRECISION units vertically
uint16_t refresh_rate
How often we refresh the screen (time between draw-ticks).
Partial widget specification to allow NWidgets to be written nested.
Definition: widget_type.h:1075
Coordinates of a point in 2D.
static Titem * Get(size_t index)
Returns Titem with given index.
Definition: pool_type.hpp:339
Specification of a rectangle with absolute coordinates of all edges.
High level window description.
Definition: window_gui.h:159
Data structure for an opened window.
Definition: window_gui.h:273
void ReInit(int rx=0, int ry=0, bool reposition=false)
Re-initialize a window, and optionally change its size.
Definition: window.cpp:952
ResizeInfo resize
Resize information.
Definition: window_gui.h:314
int scale
Scale of this window – used to determine how to resize.
Definition: window_gui.h:304
bool IsShaded() const
Is window shaded currently?
Definition: window_gui.h:563
void InitNested(WindowNumber number=0)
Perform complete initialization of the Window with nested widgets, to allow use.
Definition: window.cpp:1746
const Scrollbar * GetScrollbar(WidgetID widnum) const
Return the Scrollbar to a widget index.
Definition: window.cpp:314
TimingMeasurement acc_duration
Current accumulated duration.
int next_index
Next index to write to in durations and timestamps.
void AddAccumulate(TimingMeasurement duration)
Accumulate a period onto the current measurement.
TimingMeasurement acc_timestamp
Start time for current accumulation cycle.
double GetAverageDurationMilliseconds(int count)
Get average cycle processing time over a number of data points.
double expected_rate
Expected number of cycles per second when the system is running without slowdowns.
int prev_index
Last index written to in durations and timestamps.
double GetRate()
Get current rate of a performance element, based on approximately the past one second of data.
int num_valid
Number of data points recorded, clamped to NUM_FRAMERATE_POINTS.
TimingMeasurement timestamps[NUM_FRAMERATE_POINTS]
Start time of each cycle of the performance element, circular buffer.
void AddPause(TimingMeasurement start_time)
Indicate a pause/expected discontinuity in processing the element.
PerformanceData(double expected_rate)
Initialize a data element with an expected collection rate.
void BeginAccumulate(TimingMeasurement start_time)
Begin an accumulation of multiple measurements into a single value, from a given start time.
void Add(TimingMeasurement start_time, TimingMeasurement end_time)
Collect a complete measurement, given start and ending times for a processing block.
TimingMeasurement durations[NUM_FRAMERATE_POINTS]
Time spent processing each cycle of the performance element, circular buffer.
Definition of Interval and OneShot timers.
Definition of the Window system.
@ SZSP_VERTICAL
Display plane with zero size horizontally, and filling and resizing vertically.
Definition: widget_type.h:481
@ NWID_HORIZONTAL
Horizontal container.
Definition: widget_type.h:75
@ WWT_PANEL
Simple depressed panel.
Definition: widget_type.h:50
@ WWT_STICKYBOX
Sticky box (at top-right of a window, after WWT_DEFSIZEBOX)
Definition: widget_type.h:66
@ WWT_SHADEBOX
Shade box (at top-right of a window, between WWT_DEBUGBOX and WWT_DEFSIZEBOX)
Definition: widget_type.h:64
@ WWT_CAPTION
Window caption (window title between closebox and stickybox)
Definition: widget_type.h:61
@ NWID_VSCROLLBAR
Vertical scrollbar.
Definition: widget_type.h:84
@ NWID_VERTICAL
Vertical container.
Definition: widget_type.h:77
@ WWT_CLOSEBOX
Close box (at top-left of a window)
Definition: widget_type.h:69
@ WWT_EMPTY
Empty widget, place holder to reserve space in widget tree.
Definition: widget_type.h:48
@ WWT_RESIZEBOX
Resize box (normally at bottom-right of a window)
Definition: widget_type.h:68
@ WWT_TEXT
Pure simple text.
Definition: widget_type.h:58
@ NWID_SELECTION
Stacked widgets, only one visible at a time (eg in a panel with tabs).
Definition: widget_type.h:80
void ResizeWindow(Window *w, int delta_x, int delta_y, bool clamp_to_screen, bool schedule_resize)
Resize the window.
Definition: window.cpp:2022
Window functions not directly related to making/drawing windows.
Functions, definitions and such used only by the GUI.
@ WDP_AUTO
Find a place automatically.
Definition: window_gui.h:147
int WidgetID
Widget ID.
Definition: window_type.h:18
int32_t WindowNumber
Number to differentiate different windows of the same class.
Definition: window_type.h:737
@ WC_FRAMETIME_GRAPH
Frame time graph; Window numbers:
Definition: window_type.h:710
@ WC_NONE
No window, redirects to WC_MAIN_WINDOW.
Definition: window_type.h:45
@ WC_FRAMERATE_DISPLAY
Framerate display; Window numbers:
Definition: window_type.h:704