OpenTTD Source 20260421-master-gc2fbc6fdeb
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 <https://www.gnu.org/licenses/old-licenses/gpl-2.0>.
6 */
7
9
10#include "stdafx.h"
11
12#include "framerate_type.h"
13#include <chrono>
14#include "gfx_func.h"
15#include "newgrf_sound.h"
16#include "window_gui.h"
17#include "window_func.h"
18#include "string_func.h"
19#include "strings_func.h"
20#include "console_func.h"
21#include "console_type.h"
22#include "company_base.h"
23#include "ai/ai_info.hpp"
24#include "ai/ai_instance.hpp"
25#include "game/game.hpp"
27#include "timer/timer.h"
28#include "timer/timer_window.h"
29#include "zoom_func.h"
30
32
33#include <atomic>
34#include <mutex>
35
36#include "table/strings.h"
37
38#include "safeguards.h"
39
40static std::mutex _sound_perf_lock;
41static std::atomic<bool> _sound_perf_pending;
42static std::vector<TimingMeasurement> _sound_perf_measurements;
43
47namespace {
48
50 const int NUM_FRAMERATE_POINTS = 512;
53
56 static const TimingMeasurement INVALID_DURATION = UINT64_MAX;
57
59 std::array<TimingMeasurement, NUM_FRAMERATE_POINTS> durations{};
61 std::array<TimingMeasurement, NUM_FRAMERATE_POINTS> timestamps{};
63 double expected_rate = 0;
65 int next_index = 0;
67 int prev_index = 0;
69 int num_valid = 0;
70
75
83
89 void Add(TimingMeasurement start_time, TimingMeasurement end_time)
90 {
91 this->durations[this->next_index] = end_time - start_time;
92 this->timestamps[this->next_index] = start_time;
93 this->prev_index = this->next_index;
94 this->next_index += 1;
95 if (this->next_index >= NUM_FRAMERATE_POINTS) this->next_index = 0;
96 this->num_valid = std::min(NUM_FRAMERATE_POINTS, this->num_valid + 1);
97 }
98
104 {
105 this->timestamps[this->next_index] = this->acc_timestamp;
106 this->durations[this->next_index] = this->acc_duration;
107 this->prev_index = this->next_index;
108 this->next_index += 1;
109 if (this->next_index >= NUM_FRAMERATE_POINTS) this->next_index = 0;
110 this->num_valid = std::min(NUM_FRAMERATE_POINTS, this->num_valid + 1);
111
112 this->acc_duration = 0;
113 this->acc_timestamp = start_time;
114 }
115
121 {
122 this->acc_duration += duration;
123 }
124
130 {
131 if (this->durations[this->prev_index] != INVALID_DURATION) {
132 this->timestamps[this->next_index] = start_time;
133 this->durations[this->next_index] = INVALID_DURATION;
134 this->prev_index = this->next_index;
135 this->next_index += 1;
136 if (this->next_index >= NUM_FRAMERATE_POINTS) this->next_index = 0;
137 this->num_valid += 1;
138 }
139 }
140
147 {
148 count = std::min(count, this->num_valid);
149
150 int first_point = this->prev_index - count;
151 if (first_point < 0) first_point += NUM_FRAMERATE_POINTS;
152
153 /* Sum durations, skipping invalid points */
154 double sumtime = 0;
155 const int last_point = first_point + count;
156 for (int i = first_point; i < last_point; i++) {
157 auto d = this->durations[i % NUM_FRAMERATE_POINTS];
158 if (d != INVALID_DURATION) {
159 sumtime += d;
160 } else {
161 /* Don't count the invalid durations */
162 count--;
163 }
164 }
165
166 if (count == 0) return 0; // avoid div by zero
167 return sumtime * 1000 / count / TIMESTAMP_PRECISION;
168 }
169
174 double GetRate()
175 {
176 /* Start at last recorded point, end at latest when reaching the earliest recorded point */
177 int point = this->prev_index;
178 int last_point = this->next_index - this->num_valid;
179 if (last_point < 0) last_point += NUM_FRAMERATE_POINTS;
180
181 /* Number of data points collected */
182 int count = 0;
183 /* Time of previous data point */
184 TimingMeasurement last = this->timestamps[point];
185 /* Total duration covered by collected points */
186 TimingMeasurement total = 0;
187
188 /* We have nothing to compare the first point against */
189 point--;
190 if (point < 0) point = NUM_FRAMERATE_POINTS - 1;
191
192 while (point != last_point) {
193 /* Only record valid data points, but pretend the gaps in measurements aren't there */
194 if (this->durations[point] != INVALID_DURATION) {
195 total += last - this->timestamps[point];
196 count++;
197 }
198 last = this->timestamps[point];
199 if (total >= TIMESTAMP_PRECISION) break; // end after 1 second has been collected
200 point--;
201 if (point < 0) point = NUM_FRAMERATE_POINTS - 1;
202 }
203
204 if (total == 0 || count == 0) return 0;
205 return (double)count * TIMESTAMP_PRECISION / total;
206 }
207 };
208
210 static const double GL_RATE = 1000.0 / MILLISECONDS_PER_TICK;
211
218 PerformanceData(GL_RATE), // PFE_GAMELOOP
219 PerformanceData(1), // PFE_ACC_GL_ECONOMY
220 PerformanceData(1), // PFE_ACC_GL_TRAINS
221 PerformanceData(1), // PFE_ACC_GL_ROADVEHS
222 PerformanceData(1), // PFE_ACC_GL_SHIPS
223 PerformanceData(1), // PFE_ACC_GL_AIRCRAFT
224 PerformanceData(1), // PFE_GL_LANDSCAPE
225 PerformanceData(1), // PFE_GL_LINKGRAPH
226 PerformanceData(1000.0 / 30), // PFE_DRAWING
227 PerformanceData(1), // PFE_ACC_DRAWWORLD
228 PerformanceData(60.0), // PFE_VIDEO
229 PerformanceData(1000.0 * 8192 / 44100), // PFE_SOUND
230 PerformanceData(1), // PFE_ALLSCRIPTS
231 PerformanceData(1), // PFE_GAMESCRIPT
232 PerformanceData(1), // PFE_AI0 ...
246 PerformanceData(1), // PFE_AI14
247 };
248
249}
250
251
259{
260 using namespace std::chrono;
261 return (TimingMeasurement)time_point_cast<microseconds>(high_resolution_clock::now()).time_since_epoch().count();
262}
263
264
270{
271 assert(elem < PFE_MAX);
272
273 this->elem = elem;
274 this->start_time = GetPerformanceTimer();
275}
276
279{
280 if (this->elem == PFE_ALLSCRIPTS) {
281 /* Hack to not record scripts total when no scripts are active */
282 bool any_active = _pf_data[PFE_GAMESCRIPT].num_valid > 0;
283 for (uint e = PFE_AI0; e < PFE_MAX; e++) any_active |= _pf_data[e].num_valid > 0;
284 if (!any_active) {
286 return;
287 }
288 }
289 if (this->elem == PFE_SOUND) {
290 /* PFE_SOUND measurements are made from the mixer thread.
291 * _pf_data cannot be concurrently accessed from the mixer thread
292 * and the main thread, so store the measurement results in a
293 * mutex-protected queue which is drained by the main thread.
294 * See: ProcessPendingPerformanceMeasurements() */
296 std::lock_guard lk(_sound_perf_lock);
297 if (_sound_perf_measurements.size() >= NUM_FRAMERATE_POINTS * 2) return;
298 _sound_perf_measurements.push_back(this->start_time);
299 _sound_perf_measurements.push_back(end);
300 _sound_perf_pending.store(true, std::memory_order_release);
301 return;
302 }
303 _pf_data[this->elem].Add(this->start_time, GetPerformanceTimer());
304}
305
311{
312 _pf_data[this->elem].expected_rate = rate;
313}
314
320{
321 _pf_data[elem].num_valid = 0;
322 _pf_data[elem].next_index = 0;
323 _pf_data[elem].prev_index = 0;
324}
325
331{
333 _pf_data[elem].AddPause(GetPerformanceTimer());
334}
335
336
342{
343 assert(elem < PFE_MAX);
344
345 this->elem = elem;
346 this->start_time = GetPerformanceTimer();
347}
348
351{
352 _pf_data[this->elem].AddAccumulate(GetPerformanceTimer() - this->start_time);
353}
354
364
365
367
368
369static const PerformanceElement DISPLAY_ORDER_PFE[PFE_MAX] = {
379 PFE_AI0,
380 PFE_AI1,
381 PFE_AI2,
382 PFE_AI3,
383 PFE_AI4,
384 PFE_AI5,
385 PFE_AI6,
386 PFE_AI7,
387 PFE_AI8,
388 PFE_AI9,
389 PFE_AI10,
390 PFE_AI11,
391 PFE_AI12,
392 PFE_AI13,
393 PFE_AI14,
397 PFE_VIDEO,
398 PFE_SOUND,
399};
400
401static std::string_view GetAIName(int ai_index)
402{
403 if (!Company::IsValidAiID(ai_index)) return {};
404 return Company::Get(ai_index)->ai_info->GetName();
405}
406
408static constexpr std::initializer_list<NWidgetPart> _framerate_window_widgets = {
411 NWidget(WWT_CAPTION, Colours::Grey, WID_FRW_CAPTION),
414 EndContainer(),
417 NWidget(WWT_TEXT, Colours::Invalid, WID_FRW_RATE_GAMELOOP), SetToolTip(STR_FRAMERATE_RATE_GAMELOOP_TOOLTIP), SetFill(1, 0), SetResize(1, 0),
418 NWidget(WWT_TEXT, Colours::Invalid, WID_FRW_RATE_DRAWING), SetToolTip(STR_FRAMERATE_RATE_BLITTER_TOOLTIP), SetFill(1, 0), SetResize(1, 0),
419 NWidget(WWT_TEXT, Colours::Invalid, WID_FRW_RATE_FACTOR), SetToolTip(STR_FRAMERATE_SPEED_FACTOR_TOOLTIP), SetFill(1, 0), SetResize(1, 0),
420 EndContainer(),
421 EndContainer(),
426 NWidget(WWT_EMPTY, Colours::Invalid, WID_FRW_TIMES_NAMES), SetScrollbar(WID_FRW_SCROLLBAR),
427 NWidget(WWT_EMPTY, Colours::Invalid, WID_FRW_TIMES_CURRENT), SetScrollbar(WID_FRW_SCROLLBAR),
428 NWidget(WWT_EMPTY, Colours::Invalid, WID_FRW_TIMES_AVERAGE), SetScrollbar(WID_FRW_SCROLLBAR),
429 NWidget(WWT_EMPTY, Colours::Invalid, WID_FRW_ALLOCSIZE), SetScrollbar(WID_FRW_SCROLLBAR),
430 EndContainer(),
431 NWidget(WWT_TEXT, Colours::Invalid, WID_FRW_INFO_DATA_POINTS), SetFill(1, 0), SetResize(1, 0),
432 EndContainer(),
433 EndContainer(),
435 NWidget(NWID_VSCROLLBAR, Colours::Grey, WID_FRW_SCROLLBAR),
437 EndContainer(),
438 EndContainer(),
439};
440
441struct FramerateWindow : Window {
442 int num_active = 0;
443 int num_displayed = 0;
444
446 StringID strid;
447 uint32_t value;
448
449 inline void SetRate(double value, double target)
450 {
451 const double threshold_good = target * 0.95;
452 const double threshold_bad = target * 2 / 3;
453 this->value = (uint32_t)(value * 100);
454 this->strid = (value > threshold_good) ? STR_FRAMERATE_FPS_GOOD : (value < threshold_bad) ? STR_FRAMERATE_FPS_BAD : STR_FRAMERATE_FPS_WARN;
455 }
456
457 inline void SetTime(double value, double target)
458 {
459 const double threshold_good = target / 3;
460 const double threshold_bad = target;
461 this->value = (uint32_t)(value * 100);
462 this->strid = (value < threshold_good) ? STR_FRAMERATE_MS_GOOD : (value > threshold_bad) ? STR_FRAMERATE_MS_BAD : STR_FRAMERATE_MS_WARN;
463 }
464
465 inline uint32_t GetValue() const { return this->value; }
466 inline uint32_t GetDecimals() const { return 2; }
467 };
468
472 std::array<CachedDecimal, PFE_MAX> times_shortterm{};
473 std::array<CachedDecimal, PFE_MAX> times_longterm{};
474
475 static constexpr int MIN_ELEMENTS = 5;
476
477 FramerateWindow(WindowDesc &desc, WindowNumber number) : Window(desc)
478 {
479 this->InitNested(number);
480 this->UpdateData();
481 this->num_displayed = this->num_active;
482
483 /* Window is always initialised to MIN_ELEMENTS height, resize to contain num_displayed */
484 ResizeWindow(this, 0, (std::max(MIN_ELEMENTS, this->num_displayed) - MIN_ELEMENTS) * GetCharacterHeight(FontSize::Normal));
485 }
486
488 const IntervalTimer<TimerWindow> update_interval = {std::chrono::milliseconds(100), [this](auto) {
489 this->UpdateData();
490 this->SetDirty();
491 }};
492
493 void UpdateData()
494 {
495 double gl_rate = _pf_data[PFE_GAMELOOP].GetRate();
496 this->rate_gameloop.SetRate(gl_rate, _pf_data[PFE_GAMELOOP].expected_rate);
497 this->speed_gameloop.SetRate(gl_rate / _pf_data[PFE_GAMELOOP].expected_rate, 1.0);
498 if (this->IsShaded()) return; // in small mode, this is everything needed
499
500 this->rate_drawing.SetRate(_pf_data[PFE_DRAWING].GetRate(), _settings_client.gui.refresh_rate);
501
502 int new_active = 0;
503 for (PerformanceElement e = PFE_FIRST; e < PFE_MAX; e++) {
504 this->times_shortterm[e].SetTime(_pf_data[e].GetAverageDurationMilliseconds(8), MILLISECONDS_PER_TICK);
506 if (_pf_data[e].num_valid > 0) {
507 new_active++;
508 }
509 }
510
511 if (new_active != this->num_active) {
512 this->num_active = new_active;
513 Scrollbar *sb = this->GetScrollbar(WID_FRW_SCROLLBAR);
514 sb->SetCount(this->num_active);
515 sb->SetCapacity(std::min(this->num_displayed, this->num_active));
516 }
517 }
518
519 std::string GetWidgetString(WidgetID widget, StringID stringid) const override
520 {
521 switch (widget) {
522 case WID_FRW_CAPTION:
523 /* When the window is shaded, the caption shows game loop rate and speed factor */
524 if (!this->IsShaded()) return GetString(STR_FRAMERATE_CAPTION);
525
526 return GetString(STR_FRAMERATE_CAPTION_SMALL, this->rate_gameloop.strid, this->rate_gameloop.GetValue(), this->rate_gameloop.GetDecimals(), this->speed_gameloop.GetValue(), this->speed_gameloop.GetDecimals());
527
528 case WID_FRW_RATE_GAMELOOP:
529 return GetString(STR_FRAMERATE_RATE_GAMELOOP, this->rate_gameloop.strid, this->rate_gameloop.GetValue(), this->rate_gameloop.GetDecimals());
530
531 case WID_FRW_RATE_DRAWING:
532 return GetString(STR_FRAMERATE_RATE_BLITTER, this->rate_drawing.strid, this->rate_drawing.GetValue(), this->rate_drawing.GetDecimals());
533
534 case WID_FRW_RATE_FACTOR:
535 return GetString(STR_FRAMERATE_SPEED_FACTOR, this->speed_gameloop.GetValue(), this->speed_gameloop.GetDecimals());
536
537 case WID_FRW_INFO_DATA_POINTS:
538 return GetString(STR_FRAMERATE_DATA_POINTS, NUM_FRAMERATE_POINTS);
539
540 default:
541 return this->Window::GetWidgetString(widget, stringid);
542 }
543 }
544
545 void UpdateWidgetSize(WidgetID widget, Dimension &size, [[maybe_unused]] const Dimension &padding, [[maybe_unused]] Dimension &fill, [[maybe_unused]] Dimension &resize) override
546 {
547 switch (widget) {
548 case WID_FRW_RATE_GAMELOOP:
549 size = GetStringBoundingBox(GetString(STR_FRAMERATE_RATE_GAMELOOP, STR_FRAMERATE_FPS_GOOD, GetParamMaxDigits(6), 2));
550 break;
551 case WID_FRW_RATE_DRAWING:
552 size = GetStringBoundingBox(GetString(STR_FRAMERATE_RATE_BLITTER, STR_FRAMERATE_FPS_GOOD, GetParamMaxDigits(6), 2));
553 break;
554 case WID_FRW_RATE_FACTOR:
555 size = GetStringBoundingBox(GetString(STR_FRAMERATE_SPEED_FACTOR, GetParamMaxDigits(6), 2));
556 break;
557
558 case WID_FRW_TIMES_NAMES: {
559 size.width = 0;
561 resize.width = 0;
562 fill.height = resize.height = GetCharacterHeight(FontSize::Normal);
563 for (PerformanceElement e : DISPLAY_ORDER_PFE) {
564 if (_pf_data[e].num_valid == 0) continue;
565 Dimension line_size;
566 if (e < PFE_AI0) {
567 line_size = GetStringBoundingBox(STR_FRAMERATE_GAMELOOP + e);
568 } else {
569 line_size = GetStringBoundingBox(GetString(STR_FRAMERATE_AI, e - PFE_AI0 + 1, GetAIName(e - PFE_AI0)));
570 }
571 size.width = std::max(size.width, line_size.width);
572 }
573 break;
574 }
575
576 case WID_FRW_TIMES_CURRENT:
577 case WID_FRW_TIMES_AVERAGE:
578 case WID_FRW_ALLOCSIZE: {
579 size = GetStringBoundingBox(STR_FRAMERATE_CURRENT + (widget - WID_FRW_TIMES_CURRENT));
580 Dimension item_size = GetStringBoundingBox(GetString(STR_FRAMERATE_MS_GOOD, GetParamMaxDigits(6), 2));
581 size.width = std::max(size.width, item_size.width);
583 resize.width = 0;
584 fill.height = resize.height = GetCharacterHeight(FontSize::Normal);
585 break;
586 }
587 }
588 }
589
596 void DrawElementTimesColumn(const Rect &r, StringID heading_str, std::span<const CachedDecimal> values) const
597 {
598 const Scrollbar *sb = this->GetScrollbar(WID_FRW_SCROLLBAR);
599 int32_t skip = sb->GetPosition();
600 int drawable = this->num_displayed;
601 int y = r.top;
602 DrawString(r.left, r.right, y, heading_str, TC_FROMSTRING, SA_CENTER, true);
604 for (PerformanceElement e : DISPLAY_ORDER_PFE) {
605 if (_pf_data[e].num_valid == 0) continue;
606 if (skip > 0) {
607 skip--;
608 } else {
609 DrawString(r.left, r.right, y, GetString(values[e].strid, values[e].GetValue(), values[e].GetDecimals()), TC_FROMSTRING, SA_RIGHT | SA_FORCE);
611 drawable--;
612 if (drawable == 0) break;
613 }
614 }
615 }
616
617 void DrawElementAllocationsColumn(const Rect &r) const
618 {
619 const Scrollbar *sb = this->GetScrollbar(WID_FRW_SCROLLBAR);
620 int32_t skip = sb->GetPosition();
621 int drawable = this->num_displayed;
622 int y = r.top;
623 DrawString(r.left, r.right, y, STR_FRAMERATE_MEMORYUSE, TC_FROMSTRING, SA_CENTER, true);
625 for (PerformanceElement e : DISPLAY_ORDER_PFE) {
626 if (_pf_data[e].num_valid == 0) continue;
627 if (skip > 0) {
628 skip--;
629 } else if (e == PFE_GAMESCRIPT || e >= PFE_AI0) {
630 uint64_t value = e == PFE_GAMESCRIPT ? Game::GetInstance()->GetAllocatedMemory() : Company::Get(e - PFE_AI0)->ai_instance->GetAllocatedMemory();
631 DrawString(r.left, r.right, y, GetString(STR_FRAMERATE_BYTES_GOOD, value), TC_FROMSTRING, SA_RIGHT | SA_FORCE);
633 drawable--;
634 if (drawable == 0) break;
635 } else if (e == PFE_SOUND) {
636 DrawString(r.left, r.right, y, GetString(STR_FRAMERATE_BYTES_GOOD, GetSoundPoolAllocatedMemory()), TC_FROMSTRING, SA_RIGHT | SA_FORCE);
638 drawable--;
639 if (drawable == 0) break;
640 } else {
641 /* skip non-script */
643 drawable--;
644 if (drawable == 0) break;
645 }
646 }
647 }
648
649 void DrawWidget(const Rect &r, WidgetID widget) const override
650 {
651 switch (widget) {
652 case WID_FRW_TIMES_NAMES: {
653 /* Render a column of titles for performance element names */
654 const Scrollbar *sb = this->GetScrollbar(WID_FRW_SCROLLBAR);
655 int32_t skip = sb->GetPosition();
656 int drawable = this->num_displayed;
657 int y = r.top + GetCharacterHeight(FontSize::Normal) + WidgetDimensions::scaled.vsep_normal; // first line contains headings in the value columns
658 for (PerformanceElement e : DISPLAY_ORDER_PFE) {
659 if (_pf_data[e].num_valid == 0) continue;
660 if (skip > 0) {
661 skip--;
662 } else {
663 if (e < PFE_AI0) {
664 DrawString(r.left, r.right, y, STR_FRAMERATE_GAMELOOP + e, TC_FROMSTRING, SA_LEFT);
665 } else {
666 DrawString(r.left, r.right, y, GetString(STR_FRAMERATE_AI, e - PFE_AI0 + 1, GetAIName(e - PFE_AI0)), TC_FROMSTRING, SA_LEFT);
667 }
669 drawable--;
670 if (drawable == 0) break;
671 }
672 }
673 break;
674 }
675 case WID_FRW_TIMES_CURRENT:
676 /* Render short-term average values */
677 DrawElementTimesColumn(r, STR_FRAMERATE_CURRENT, this->times_shortterm);
678 break;
679 case WID_FRW_TIMES_AVERAGE:
680 /* Render averages of all recorded values */
681 DrawElementTimesColumn(r, STR_FRAMERATE_AVERAGE, this->times_longterm);
682 break;
683 case WID_FRW_ALLOCSIZE:
684 DrawElementAllocationsColumn(r);
685 break;
686 }
687 }
688
689 void OnClick([[maybe_unused]] Point pt, WidgetID widget, [[maybe_unused]] int click_count) override
690 {
691 switch (widget) {
692 case WID_FRW_TIMES_NAMES:
693 case WID_FRW_TIMES_CURRENT:
694 case WID_FRW_TIMES_AVERAGE: {
695 /* Open time graph windows when clicking detail measurement lines */
696 const Scrollbar *sb = this->GetScrollbar(WID_FRW_SCROLLBAR);
697 int32_t line = sb->GetScrolledRowFromWidget(pt.y, this, widget, WidgetDimensions::scaled.vsep_normal + GetCharacterHeight(FontSize::Normal));
698 if (line != INT32_MAX) {
699 line++;
700 /* Find the visible line that was clicked */
701 for (PerformanceElement e : DISPLAY_ORDER_PFE) {
702 if (_pf_data[e].num_valid > 0) line--;
703 if (line == 0) {
705 break;
706 }
707 }
708 }
709 break;
710 }
711 }
712 }
713
714 void OnResize() override
715 {
716 auto *wid = this->GetWidget<NWidgetResizeBase>(WID_FRW_TIMES_NAMES);
717 this->num_displayed = (wid->current_y - wid->min_y - WidgetDimensions::scaled.vsep_normal) / GetCharacterHeight(FontSize::Normal) - 1; // subtract 1 for headings
718 this->GetScrollbar(WID_FRW_SCROLLBAR)->SetCapacity(this->num_displayed);
719 }
720};
721
722static WindowDesc _framerate_display_desc(
723 WDP_AUTO, "framerate_display", 0, 0,
725 {},
726 _framerate_window_widgets
727);
728
729
731static constexpr std::initializer_list<NWidgetPart> _frametime_graph_window_widgets = {
734 NWidget(WWT_CAPTION, Colours::Grey, WID_FGW_CAPTION), SetTextStyle(TC_WHITE),
736 EndContainer(),
739 NWidget(WWT_EMPTY, Colours::Invalid, WID_FGW_GRAPH),
740 EndContainer(),
741 EndContainer(),
742};
743
744struct FrametimeGraphWindow : Window {
747
750
751 FrametimeGraphWindow(WindowDesc &desc, WindowNumber number) : Window(desc), element(static_cast<PerformanceElement>(number))
752 {
753 this->InitNested(number);
754 this->UpdateScale();
755 }
756
757 std::string GetWidgetString(WidgetID widget, StringID stringid) const override
758 {
759 switch (widget) {
760 case WID_FGW_CAPTION:
761 if (this->element < PFE_AI0) {
762 return GetString(STR_FRAMETIME_CAPTION_GAMELOOP + this->element);
763 }
764 return GetString(STR_FRAMETIME_CAPTION_AI, this->element - PFE_AI0 + 1, GetAIName(this->element - PFE_AI0));
765
766 default:
767 return this->Window::GetWidgetString(widget, stringid);
768 }
769 }
770
771 void UpdateWidgetSize(WidgetID widget, Dimension &size, [[maybe_unused]] const Dimension &padding, [[maybe_unused]] Dimension &fill, [[maybe_unused]] Dimension &resize) override
772 {
773 if (widget == WID_FGW_GRAPH) {
774 Dimension size_ms_label = GetStringBoundingBox(GetString(STR_FRAMERATE_GRAPH_MILLISECONDS, 100));
775 Dimension size_s_label = GetStringBoundingBox(GetString(STR_FRAMERATE_GRAPH_SECONDS, 100));
776
777 /* Size graph in height to fit at least 10 vertical labels with space between, or at least 100 pixels */
778 graph_size.height = std::max<uint>(ScaleGUITrad(100), 10 * (size_ms_label.height + WidgetDimensions::scaled.vsep_normal));
779 /* Always 2:1 graph area */
780 graph_size.width = 2 * graph_size.height;
781 size = graph_size;
782
783 size.width += size_ms_label.width + WidgetDimensions::scaled.hsep_normal;
784 size.height += size_s_label.height + WidgetDimensions::scaled.vsep_normal;
785 }
786 }
787
788 void SelectHorizontalScale(TimingMeasurement range)
789 {
790 /* 60 Hz graphical drawing results in a value of approximately TIMESTAMP_PRECISION,
791 * this lands exactly on the scale = 2 vs scale = 4 boundary.
792 * To avoid excessive switching of the horizontal scale, bias these performance
793 * categories away from this scale boundary. */
794 if (this->element == PFE_DRAWING || this->element == PFE_DRAWWORLD) range += (range / 2);
795
796 /* Determine horizontal scale based on period covered by 60 points
797 * (slightly less than 2 seconds at full game speed) */
798 struct ScaleDef { TimingMeasurement range; int scale; };
799 static const std::initializer_list<ScaleDef> hscales = {
800 { TIMESTAMP_PRECISION * 120, 60 },
801 { TIMESTAMP_PRECISION * 10, 20 },
802 { TIMESTAMP_PRECISION * 5, 10 },
803 { TIMESTAMP_PRECISION * 3, 4 },
804 { TIMESTAMP_PRECISION * 1, 2 },
805 };
806 for (const auto &sc : hscales) {
807 if (range < sc.range) this->horizontal_scale = sc.scale;
808 }
809 }
810
811 void SelectVerticalScale(TimingMeasurement range)
812 {
813 /* Determine vertical scale based on peak value (within the horizontal scale + a bit) */
814 static const std::initializer_list<TimingMeasurement> vscales = {
824 };
825 for (const auto &sc : vscales) {
826 if (range < sc) this->vertical_scale = (int)sc;
827 }
828 }
829
832 {
833 const auto &durations = _pf_data[this->element].durations;
834 const auto &timestamps = _pf_data[this->element].timestamps;
835 int num_valid = _pf_data[this->element].num_valid;
836 int point = _pf_data[this->element].prev_index;
837
838 TimingMeasurement lastts = timestamps[point];
839 TimingMeasurement time_sum = 0;
840 TimingMeasurement peak_value = 0;
841 int count = 0;
842
843 /* Sensible default for when too few measurements are available */
844 this->horizontal_scale = 4;
845
846 for (int i = 1; i < num_valid; i++) {
847 point--;
848 if (point < 0) point = NUM_FRAMERATE_POINTS - 1;
849
850 TimingMeasurement value = durations[point];
852 /* Skip gaps in data by pretending time is continuous across them */
853 lastts = timestamps[point];
854 continue;
855 }
856 if (value > peak_value) peak_value = value;
857 count++;
858
859 /* Accumulate period of time covered by data */
860 time_sum += lastts - timestamps[point];
861 lastts = timestamps[point];
862
863 /* Enough data to select a range and get decent data density */
864 if (count == 60) this->SelectHorizontalScale(time_sum);
865
866 /* End when enough points have been collected and the horizontal scale has been exceeded */
867 if (count >= 60 && time_sum >= (this->horizontal_scale + 2) * TIMESTAMP_PRECISION / 2) break;
868 }
869
870 this->SelectVerticalScale(peak_value);
871 }
872
874 const IntervalTimer<TimerWindow> update_interval = {std::chrono::milliseconds(500), [this](auto) {
875 this->UpdateScale();
876 }};
877
878 void OnRealtimeTick([[maybe_unused]] uint delta_ms) override
879 {
880 this->SetDirty();
881 }
882
892 template <typename T>
893 static inline T Scinterlate(T dst_min, T dst_max, T src_min, T src_max, T value)
894 {
895 T dst_diff = dst_max - dst_min;
896 T src_diff = src_max - src_min;
897 return (value - src_min) * dst_diff / src_diff + dst_min;
898 }
899
900 void DrawWidget(const Rect &r, WidgetID widget) const override
901 {
902 if (widget == WID_FGW_GRAPH) {
903 const auto &durations = _pf_data[this->element].durations;
904 const auto &timestamps = _pf_data[this->element].timestamps;
905 int point = _pf_data[this->element].prev_index;
906
907 const int x_zero = r.right - (int)this->graph_size.width;
908 const int x_max = r.right;
909 const int y_zero = r.top + (int)this->graph_size.height;
910 const int y_max = r.top;
911 const PixelColour c_grid = PC_DARK_GREY;
912 const PixelColour c_lines = PC_BLACK;
913 const PixelColour c_peak = PC_DARK_RED;
914
915 const TimingMeasurement draw_horz_scale = (TimingMeasurement)this->horizontal_scale * TIMESTAMP_PRECISION / 2;
916 const TimingMeasurement draw_vert_scale = (TimingMeasurement)this->vertical_scale;
917
918 /* Number of \c horizontal_scale units in each horizontal division */
919 const uint horz_div_scl = (this->horizontal_scale <= 20) ? 1 : 10;
920 /* Number of divisions of the horizontal axis */
921 const uint horz_divisions = this->horizontal_scale / horz_div_scl;
922 /* Number of divisions of the vertical axis */
923 const uint vert_divisions = 10;
924
925 /* Draw division lines and labels for the vertical axis */
926 for (uint division = 0; division < vert_divisions; division++) {
927 int y = Scinterlate(y_zero, y_max, 0, (int)vert_divisions, (int)division);
928 GfxDrawLine(x_zero, y, x_max, y, c_grid);
929 if (division % 2 == 0) {
930 if ((TimingMeasurement)this->vertical_scale > TIMESTAMP_PRECISION) {
932 GetString(STR_FRAMERATE_GRAPH_SECONDS, this->vertical_scale * division / 10 / TIMESTAMP_PRECISION),
933 TC_GREY, SA_RIGHT | SA_FORCE, false, FontSize::Small);
934 } else {
936 GetString(STR_FRAMERATE_GRAPH_MILLISECONDS, this->vertical_scale * division / 10 * 1000 / TIMESTAMP_PRECISION),
937 TC_GREY, SA_RIGHT | SA_FORCE, false, FontSize::Small);
938 }
939 }
940 }
941 /* Draw division lines and labels for the horizontal axis */
942 for (uint division = horz_divisions; division > 0; division--) {
943 int x = Scinterlate(x_zero, x_max, 0, (int)horz_divisions, (int)horz_divisions - (int)division);
944 GfxDrawLine(x, y_max, x, y_zero, c_grid);
945 if (division % 2 == 0) {
946 DrawString(x, x_max, y_zero + WidgetDimensions::scaled.vsep_normal,
947 GetString(STR_FRAMERATE_GRAPH_SECONDS, division * horz_div_scl / 2),
948 TC_GREY, SA_LEFT | SA_FORCE, false, FontSize::Small);
949 }
950 }
951
952 /* Position of last rendered data point */
953 Point lastpoint = {
954 x_max,
955 (int)Scinterlate<int64_t>(y_zero, y_max, 0, this->vertical_scale, durations[point])
956 };
957 /* Timestamp of last rendered data point */
958 TimingMeasurement lastts = timestamps[point];
959
960 TimingMeasurement peak_value = 0;
961 Point peak_point = { 0, 0 };
962 TimingMeasurement value_sum = 0;
963 TimingMeasurement time_sum = 0;
964 int points_drawn = 0;
965
966 for (int i = 1; i < NUM_FRAMERATE_POINTS; i++) {
967 point--;
968 if (point < 0) point = NUM_FRAMERATE_POINTS - 1;
969
970 TimingMeasurement value = durations[point];
972 /* Skip gaps in measurements, pretend the data points on each side are continuous */
973 lastts = timestamps[point];
974 continue;
975 }
976
977 /* Use total time period covered for value along horizontal axis */
978 time_sum += lastts - timestamps[point];
979 lastts = timestamps[point];
980 /* Stop if past the width of the graph */
981 if (time_sum > draw_horz_scale) break;
982
983 /* Draw line from previous point to new point */
984 Point newpoint = {
985 (int)Scinterlate<int64_t>(x_zero, x_max, 0, (int64_t)draw_horz_scale, (int64_t)draw_horz_scale - (int64_t)time_sum),
986 (int)Scinterlate<int64_t>(y_zero, y_max, 0, (int64_t)draw_vert_scale, (int64_t)value)
987 };
988 if (newpoint.x > lastpoint.x) continue; // don't draw backwards
989 GfxDrawLine(lastpoint.x, lastpoint.y, newpoint.x, newpoint.y, c_lines);
990 lastpoint = newpoint;
991
992 /* Record peak and average value across graphed data */
993 value_sum += value;
994 points_drawn++;
995 if (value > peak_value) {
996 peak_value = value;
997 peak_point = newpoint;
998 }
999 }
1000
1001 /* If the peak value is significantly larger than the average, mark and label it */
1002 if (points_drawn > 0 && peak_value > TIMESTAMP_PRECISION / 100 && 2 * peak_value > 3 * value_sum / points_drawn) {
1003 TextColour tc_peak = c_peak.ToTextColour();
1004 GfxFillRect(peak_point.x - 1, peak_point.y - 1, peak_point.x + 1, peak_point.y + 1, c_peak);
1005 uint64_t value = peak_value * 1000 / TIMESTAMP_PRECISION;
1006 int label_y = std::max(y_max, peak_point.y - GetCharacterHeight(FontSize::Small));
1007 if (peak_point.x - x_zero > (int)this->graph_size.width / 2) {
1008 DrawString(x_zero, peak_point.x - WidgetDimensions::scaled.hsep_normal, label_y, GetString(STR_FRAMERATE_GRAPH_MILLISECONDS, value), tc_peak, SA_RIGHT | SA_FORCE, false, FontSize::Small);
1009 } else {
1010 DrawString(peak_point.x + WidgetDimensions::scaled.hsep_normal, x_max, label_y, GetString(STR_FRAMERATE_GRAPH_MILLISECONDS, value), tc_peak, SA_LEFT | SA_FORCE, false, FontSize::Small);
1011 }
1012 }
1013 }
1014 }
1015};
1016
1017static WindowDesc _frametime_graph_window_desc(
1018 WDP_AUTO, "frametime_graph", 140, 90,
1020 {},
1021 _frametime_graph_window_widgets
1022);
1023
1024
1025
1028{
1029 AllocateWindowDescFront<FramerateWindow>(_framerate_display_desc, 0);
1030}
1031
1037{
1038 if (elem < PFE_FIRST || elem >= PFE_MAX) return; // maybe warn?
1039 AllocateWindowDescFront<FrametimeGraphWindow>(_frametime_graph_window_desc, elem);
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:109
An interval timer will fire every interval, and will continue to fire until it is deleted.
Definition timer.h:76
~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.
void SetCount(size_t num)
Sets the number of elements in the list.
void SetCapacity(size_t capacity)
Set the capacity of visible elements.
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:2458
size_type GetPosition() const
Gets the position of the first visible element in the list.
static WidgetDimensions scaled
Widget dimensions scaled for current zoom level.
Definition window_gui.h:30
static const WidgetDimensions unscaled
Unscaled widget dimensions.
Definition window_gui.h:93
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:90
Console functions used outside of the console code.
Globally used console related types.
static const TextColour CC_ERROR
Colour for error lines.
#define T
Climate temperate.
Definition engines.h:91
int GetCharacterHeight(FontSize size)
Get height of a character for a given font size.
Definition fontcache.cpp:88
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:900
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:669
void GfxFillRect(int left, int top, int right, int bottom, const std::variant< PixelColour, PaletteID > &colour, FillRectMode mode)
Applies a certain FillRectMode-operation to a rectangle [left, right] x [top, bottom] on the screen.
Definition gfx.cpp:116
Functions related to the gfx engine.
@ Small
Index of the small font in the font tables.
Definition gfx_type.h:250
@ Normal
Index of the normal font in the font tables.
Definition gfx_type.h:249
@ SA_LEFT
Left align the text.
Definition gfx_type.h:388
@ SA_RIGHT
Right align the text (must be a single bit).
Definition gfx_type.h:390
@ SA_FORCE
Force the alignment, i.e. don't swap for RTL languages.
Definition gfx_type.h:400
@ SA_CENTER
Center both horizontally and vertically.
Definition gfx_type.h:398
@ Invalid
Invalid marker.
Definition gfx_type.h:302
@ Grey
Grey.
Definition gfx_type.h:299
TextColour
Colour of the strings, see _string_colourmap in table/string_colours.h or docs/ottd-colourtext-palett...
Definition gfx_type.h:307
static const uint MILLISECONDS_PER_TICK
The number of milliseconds per game tick.
Definition gfx_type.h:370
constexpr NWidgetPart SetFill(uint16_t fill_x, uint16_t fill_y)
Widget part function for setting filling.
constexpr NWidgetPart SetPIP(uint8_t pre, uint8_t inter, uint8_t post)
Widget part function for setting a pre/inter/post spaces.
constexpr NWidgetPart SetScrollbar(WidgetID index)
Attach a scrollbar to a widget.
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.
constexpr NWidgetPart SetToolTip(StringID tip)
Widget part function for setting tooltip and clearing the widget data.
constexpr NWidgetPart EndContainer()
Widget part function for denoting the end of a container (horizontal, vertical, WWT_FRAME,...
constexpr NWidgetPart SetTextStyle(TextColour colour, FontSize size=FontSize::Normal)
Widget part function for setting the text style.
constexpr NWidgetPart NWidget(WidgetType tp, Colours col, WidgetID idx=INVALID_WIDGET)
Widget part function for starting a new 'real' widget.
constexpr NWidgetPart SetResize(int16_t dx, int16_t dy)
Widget part function for setting the resize step.
void SetDirty() const
Mark entire window as dirty (in need of re-paint).
Definition window.cpp:980
#define Rect
Macro that prevents name conflicts between included headers.
#define Point
Macro that prevents name conflicts between included headers.
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.
size_t GetSoundPoolAllocatedMemory()
Get size of memory allocated to sound effects.
Functions related to NewGRF provided sounds.
static constexpr PixelColour PC_DARK_RED
Dark red palette colour.
static constexpr PixelColour PC_DARK_GREY
Dark grey palette colour.
static constexpr PixelColour PC_BLACK
Black palette colour.
A number of safeguards to prevent using unsafe methods.
ClientSettings _settings_client
The current settings for this game.
Definition settings.cpp:60
Definition of base types and functions in a cross-platform compatible way.
Functions related to low-level strings.
std::string GetString(StringID string)
Resolve the given StringID into a std::string with formatting but no parameters.
Definition strings.cpp:424
uint64_t GetParamMaxDigits(uint count, FontSize size)
Get some number that is suitable for string size computations.
Definition strings.cpp:218
Functions related to OTTD's strings.
uint32_t StringID
Numeric value that represents a string, independent of the selected language.
static bool IsValidAiID(auto index)
Is this company a valid company, controlled by the computer (a NoAI program)?
T y
Y coordinate.
T x
X coordinate.
Dimensions (a width and height) of a rectangle in 2D.
void OnResize() override
Called after the window got resized.
void DrawElementTimesColumn(const Rect &r, StringID heading_str, std::span< const CachedDecimal > values) const
Render a column of formatted average durations.
CachedDecimal rate_drawing
cached drawing frame rate
void UpdateWidgetSize(WidgetID widget, Dimension &size, const Dimension &padding, Dimension &fill, Dimension &resize) override
Update size and resize step of a widget in the window.
std::array< CachedDecimal, PFE_MAX > times_longterm
cached long term average times
std::array< CachedDecimal, PFE_MAX > times_shortterm
cached short term average times
CachedDecimal speed_gameloop
cached game loop speed factor
void OnClick(Point pt, WidgetID widget, int click_count) override
A click with the left mouse button has been made on the window.
std::string GetWidgetString(WidgetID widget, StringID stringid) const override
Get the raw string for a widget.
const IntervalTimer< TimerWindow > update_interval
Update the window on a regular interval.
CachedDecimal rate_gameloop
cached game loop tick rate
void DrawWidget(const Rect &r, WidgetID widget) const override
Draw the contents of a nested widget.
static constexpr int MIN_ELEMENTS
smallest number of elements to display
int horizontal_scale
number of half-second units horizontally
void OnRealtimeTick(uint delta_ms) override
Called periodically.
void UpdateScale()
Recalculate the graph scaling factors based on current recorded data.
const IntervalTimer< TimerWindow > update_interval
Update the scaling on a regular interval.
void DrawWidget(const Rect &r, WidgetID widget) const override
Draw the contents of a nested widget.
Dimension graph_size
size of the main graph area (excluding axis labels)
PerformanceElement element
what element this window renders graph for
void UpdateWidgetSize(WidgetID widget, Dimension &size, const Dimension &padding, Dimension &fill, Dimension &resize) override
Update size and resize step of a widget in the window.
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.
std::string GetWidgetString(WidgetID widget, StringID stringid) const override
Get the raw string for a widget.
int vertical_scale
number of TIMESTAMP_PRECISION units vertically
Colour for pixel/line drawing.
Definition gfx_type.h:405
static Company * Get(auto index)
Specification of a rectangle with absolute coordinates of all edges.
High level window description.
Definition window_gui.h:168
Number to differentiate different windows of the same class.
Data structure for an opened window.
Definition window_gui.h:274
virtual std::string GetWidgetString(WidgetID widget, StringID stringid) const
Get the raw string for a widget.
Definition window.cpp:518
ResizeInfo resize
Resize information.
Definition window_gui.h:315
bool IsShaded() const
Is window shaded currently?
Definition window_gui.h:563
Window(WindowDesc &desc)
Empty constructor, initialization has been moved to InitNested() called from the constructor of the d...
Definition window.cpp:1846
const NWID * GetWidget(WidgetID widnum) const
Get the nested widget with number widnum from the nested widget tree.
Definition window_gui.h:990
void InitNested(WindowNumber number=0)
Perform complete initialization of the Window with nested widgets, to allow use.
Definition window.cpp:1836
const Scrollbar * GetScrollbar(WidgetID widnum) const
Return the Scrollbar to a widget index.
Definition window.cpp:327
TimingMeasurement acc_duration
Current accumulated duration.
int next_index
Next index to write to in durations and timestamps.
std::array< TimingMeasurement, NUM_FRAMERATE_POINTS > durations
Time spent processing each cycle of the performance element, circular buffer.
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.
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.
static const TimingMeasurement INVALID_DURATION
Duration value indicating the value is not valid should be considered a gap in measurements.
void BeginAccumulate(TimingMeasurement start_time)
Begin an accumulation of multiple measurements into a single value, from a given start time.
std::array< TimingMeasurement, NUM_FRAMERATE_POINTS > timestamps
Start time of each cycle of the performance element, circular buffer.
void Add(TimingMeasurement start_time, TimingMeasurement end_time)
Collect a complete measurement, given start and ending times for a processing block.
Definition of Interval and OneShot timers.
Definition of the Window system.
static RectPadding ScaleGUITrad(const RectPadding &r)
Scale a RectPadding to GUI zoom level.
Definition widget.cpp:49
@ NWID_HORIZONTAL
Horizontal container.
Definition widget_type.h:66
@ WWT_PANEL
Simple depressed panel.
Definition widget_type.h:39
@ WWT_STICKYBOX
Sticky box (at top-right of a window, after WWT_DEFSIZEBOX).
Definition widget_type.h:57
@ WWT_SHADEBOX
Shade box (at top-right of a window, between WWT_DEBUGBOX and WWT_DEFSIZEBOX).
Definition widget_type.h:55
@ WWT_CAPTION
Window caption (window title between closebox and stickybox).
Definition widget_type.h:52
@ NWID_VSCROLLBAR
Vertical scrollbar.
Definition widget_type.h:76
@ NWID_VERTICAL
Vertical container.
Definition widget_type.h:68
@ WWT_CLOSEBOX
Close box (at top-left of a window).
Definition widget_type.h:60
@ WWT_EMPTY
Empty widget, place holder to reserve space in widget tree.
Definition widget_type.h:37
@ WWT_RESIZEBOX
Resize box (normally at bottom-right of a window).
Definition widget_type.h:59
@ WWT_TEXT
Pure simple text.
Definition widget_type.h:49
void ResizeWindow(Window *w, int delta_x, int delta_y, bool clamp_to_screen, bool schedule_resize)
Resize the window.
Definition window.cpp:2113
Window functions not directly related to making/drawing windows.
Functions, definitions and such used only by the GUI.
Twindow * AllocateWindowDescFront(WindowDesc &desc, WindowNumber window_number, Targs... extra_arguments)
Open a new window.
@ WDP_AUTO
Find a place automatically.
Definition window_gui.h:144
int WidgetID
Widget ID.
Definition window_type.h:21
@ WC_FRAMETIME_GRAPH
Frame time graph; Window numbers:
@ WC_NONE
No window, redirects to WC_MAIN_WINDOW.
Definition window_type.h:51
@ WC_FRAMERATE_DISPLAY
Framerate display; Window numbers:
Functions related to zooming.