OpenTTD Source 20260711-master-g3fb3006dff
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), // PerformanceElement::GameLoop
219 PerformanceData(1), // PerformanceElement::GameLoopEconomy
220 PerformanceData(1), // PerformanceElement::GameLoopTrains
221 PerformanceData(1), // PerformanceElement::GameLoopRoadVehicles
222 PerformanceData(1), // PerformanceElement::GameLoopShips
223 PerformanceData(1), // PerformanceElement::GameLoopAircraft
224 PerformanceData(1), // PerformanceElement::GameLoopLandscape
225 PerformanceData(1), // PerformanceElement::GameLoopLinkGraph
226 PerformanceData(1000.0 / 30), // PerformanceElement::Drawing
227 PerformanceData(1), // PerformanceElement::ViewportDrawing
228 PerformanceData(60.0), // PerformanceElement::Video
229 PerformanceData(1000.0 * 8192 / 44100), // PerformanceElement::Sound
230 PerformanceData(1), // PerformanceElement::AllScripts
231 PerformanceData(1), // PerformanceElement::GameScript
232 PerformanceData(1), // PerformanceElement::AI0 ...
246 PerformanceData(1), // PerformanceElement::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 < PerformanceElement::End);
272
273 this->elem = elem;
274 this->start_time = GetPerformanceTimer();
275}
276
279{
280 if (this->elem == PerformanceElement::AllScripts) {
281 /* Hack to not record scripts total when no scripts are active */
282 bool any_active = _pf_data[PerformanceElement::GameScript].num_valid > 0;
283 for (PerformanceElement e : EnumRange(PerformanceElement::AI0, PerformanceElement::End)) any_active |= _pf_data[e].num_valid > 0;
284 if (!any_active) {
286 return;
287 }
288 }
289 if (this->elem == PerformanceElement::Sound) {
290 /* PerformanceElement::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 < PerformanceElement::End);
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
361{
362 _pf_data[elem].BeginAccumulate(GetPerformanceTimer());
363}
364
365
367
368
401
407static constexpr CompanyID GetAIIndex(PerformanceElement e)
408{
409 return CompanyID(e - PerformanceElement::AI0);
410}
411
417static std::string_view GetAIName(PerformanceElement e)
418{
419 CompanyID c = GetAIIndex(e);
420 if (!Company::IsValidAiID(c)) return {};
421 return Company::Get(c)->ai_info->GetName();
422}
423
425static constexpr std::initializer_list<NWidgetPart> _framerate_window_widgets = {
428 NWidget(WWT_CAPTION, Colours::Grey, WID_FRW_CAPTION),
431 EndContainer(),
434 NWidget(WWT_TEXT, Colours::Invalid, WID_FRW_RATE_GAMELOOP), SetToolTip(STR_FRAMERATE_RATE_GAMELOOP_TOOLTIP), SetFill(1, 0), SetResize(1, 0),
435 NWidget(WWT_TEXT, Colours::Invalid, WID_FRW_RATE_DRAWING), SetToolTip(STR_FRAMERATE_RATE_BLITTER_TOOLTIP), SetFill(1, 0), SetResize(1, 0),
436 NWidget(WWT_TEXT, Colours::Invalid, WID_FRW_RATE_FACTOR), SetToolTip(STR_FRAMERATE_SPEED_FACTOR_TOOLTIP), SetFill(1, 0), SetResize(1, 0),
437 EndContainer(),
438 EndContainer(),
443 NWidget(WWT_EMPTY, Colours::Invalid, WID_FRW_TIMES_NAMES), SetScrollbar(WID_FRW_SCROLLBAR),
444 NWidget(WWT_EMPTY, Colours::Invalid, WID_FRW_TIMES_CURRENT), SetScrollbar(WID_FRW_SCROLLBAR),
445 NWidget(WWT_EMPTY, Colours::Invalid, WID_FRW_TIMES_AVERAGE), SetScrollbar(WID_FRW_SCROLLBAR),
446 NWidget(WWT_EMPTY, Colours::Invalid, WID_FRW_ALLOCSIZE), SetScrollbar(WID_FRW_SCROLLBAR),
447 EndContainer(),
448 NWidget(WWT_TEXT, Colours::Invalid, WID_FRW_INFO_DATA_POINTS), SetFill(1, 0), SetResize(1, 0),
449 EndContainer(),
450 EndContainer(),
452 NWidget(NWID_VSCROLLBAR, Colours::Grey, WID_FRW_SCROLLBAR),
454 EndContainer(),
455 EndContainer(),
456};
457
458struct FramerateWindow : Window {
459 int num_active = 0;
460 int num_displayed = 0;
461
463 StringID strid;
464 uint32_t value;
465
466 inline void SetRate(double value, double target)
467 {
468 const double threshold_good = target * 0.95;
469 const double threshold_bad = target * 2 / 3;
470 this->value = (uint32_t)(value * 100);
471 this->strid = (value > threshold_good) ? STR_FRAMERATE_FPS_GOOD : (value < threshold_bad) ? STR_FRAMERATE_FPS_BAD : STR_FRAMERATE_FPS_WARN;
472 }
473
474 inline void SetTime(double value, double target)
475 {
476 const double threshold_good = target / 3;
477 const double threshold_bad = target;
478 this->value = (uint32_t)(value * 100);
479 this->strid = (value < threshold_good) ? STR_FRAMERATE_MS_GOOD : (value > threshold_bad) ? STR_FRAMERATE_MS_BAD : STR_FRAMERATE_MS_WARN;
480 }
481
482 inline uint32_t GetValue() const { return this->value; }
483 inline uint32_t GetDecimals() const { return 2; }
484 };
485
492
493 static constexpr int MIN_ELEMENTS = 5;
494
495 FramerateWindow(WindowDesc &desc, WindowNumber number) : Window(desc)
496 {
497 this->InitNested(number);
498 this->UpdateData();
499 this->num_displayed = this->num_active;
500
501 /* Window is always initialised to MIN_ELEMENTS height, resize to contain num_displayed */
502 ResizeWindow(this, 0, (std::max(MIN_ELEMENTS, this->num_displayed) - MIN_ELEMENTS) * GetCharacterHeight(FontSize::Normal));
503 }
504
506 const IntervalTimer<TimerWindow> update_interval = {std::chrono::milliseconds(100), [this](auto) {
507 this->UpdateData();
508 this->SetDirty();
509 }};
510
511 void UpdateData()
512 {
513 double gl_rate = _pf_data[PerformanceElement::GameLoop].GetRate();
514 this->rate_gameloop.SetRate(gl_rate, _pf_data[PerformanceElement::GameLoop].expected_rate);
515 this->speed_gameloop.SetRate(gl_rate / _pf_data[PerformanceElement::GameLoop].expected_rate, 1.0);
516 if (this->IsShaded()) return; // in small mode, this is everything needed
517
518 this->rate_drawing.SetRate(_pf_data[PerformanceElement::Drawing].GetRate(), _settings_client.gui.refresh_rate);
519
520 int new_active = 0;
522 this->times_shortterm[e].SetTime(_pf_data[e].GetAverageDurationMilliseconds(8), MILLISECONDS_PER_TICK);
524 if (_pf_data[e].num_valid > 0) {
525 new_active++;
526 }
527 }
528
529 if (new_active != this->num_active) {
530 this->num_active = new_active;
531 Scrollbar *sb = this->GetScrollbar(WID_FRW_SCROLLBAR);
532 sb->SetCount(this->num_active);
533 sb->SetCapacity(std::min(this->num_displayed, this->num_active));
534 }
535 }
536
537 std::string GetWidgetString(WidgetID widget, StringID stringid) const override
538 {
539 switch (widget) {
540 case WID_FRW_CAPTION:
541 /* When the window is shaded, the caption shows game loop rate and speed factor */
542 if (!this->IsShaded()) return GetString(STR_FRAMERATE_CAPTION);
543
544 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());
545
546 case WID_FRW_RATE_GAMELOOP:
547 return GetString(STR_FRAMERATE_RATE_GAMELOOP, this->rate_gameloop.strid, this->rate_gameloop.GetValue(), this->rate_gameloop.GetDecimals());
548
549 case WID_FRW_RATE_DRAWING:
550 return GetString(STR_FRAMERATE_RATE_BLITTER, this->rate_drawing.strid, this->rate_drawing.GetValue(), this->rate_drawing.GetDecimals());
551
552 case WID_FRW_RATE_FACTOR:
553 return GetString(STR_FRAMERATE_SPEED_FACTOR, this->speed_gameloop.GetValue(), this->speed_gameloop.GetDecimals());
554
555 case WID_FRW_INFO_DATA_POINTS:
556 return GetString(STR_FRAMERATE_DATA_POINTS, NUM_FRAMERATE_POINTS);
557
558 default:
559 return this->Window::GetWidgetString(widget, stringid);
560 }
561 }
562
563 void UpdateWidgetSize(WidgetID widget, Dimension &size, [[maybe_unused]] const Dimension &padding, [[maybe_unused]] Dimension &fill, [[maybe_unused]] Dimension &resize) override
564 {
565 switch (widget) {
566 case WID_FRW_RATE_GAMELOOP:
567 size = GetStringBoundingBox(GetString(STR_FRAMERATE_RATE_GAMELOOP, STR_FRAMERATE_FPS_GOOD, GetParamMaxDigits(6), 2));
568 break;
569 case WID_FRW_RATE_DRAWING:
570 size = GetStringBoundingBox(GetString(STR_FRAMERATE_RATE_BLITTER, STR_FRAMERATE_FPS_GOOD, GetParamMaxDigits(6), 2));
571 break;
572 case WID_FRW_RATE_FACTOR:
573 size = GetStringBoundingBox(GetString(STR_FRAMERATE_SPEED_FACTOR, GetParamMaxDigits(6), 2));
574 break;
575
576 case WID_FRW_TIMES_NAMES: {
577 size.width = 0;
579 resize.width = 0;
580 fill.height = resize.height = GetCharacterHeight(FontSize::Normal);
582 if (_pf_data[e].num_valid == 0) continue;
583 Dimension line_size;
584 if (e < PerformanceElement::AI0) {
585 line_size = GetStringBoundingBox(STR_FRAMERATE_GAMELOOP + to_underlying(e));
586 } else {
587 line_size = GetStringBoundingBox(GetString(STR_FRAMERATE_AI, GetAIIndex(e) + 1, GetAIName(e)));
588 }
589 size.width = std::max(size.width, line_size.width);
590 }
591 break;
592 }
593
594 case WID_FRW_TIMES_CURRENT:
595 case WID_FRW_TIMES_AVERAGE:
596 case WID_FRW_ALLOCSIZE: {
597 size = GetStringBoundingBox(STR_FRAMERATE_CURRENT + (widget - WID_FRW_TIMES_CURRENT));
598 Dimension item_size = GetStringBoundingBox(GetString(STR_FRAMERATE_MS_GOOD, GetParamMaxDigits(6), 2));
599 size.width = std::max(size.width, item_size.width);
601 resize.width = 0;
602 fill.height = resize.height = GetCharacterHeight(FontSize::Normal);
603 break;
604 }
605 }
606 }
607
614 void DrawElementTimesColumn(const Rect &r, StringID heading_str, const CachedDecimalArray &values) const
615 {
616 const Scrollbar *sb = this->GetScrollbar(WID_FRW_SCROLLBAR);
617 int32_t skip = sb->GetPosition();
618 int drawable = this->num_displayed;
619 int y = r.top;
620 DrawString(r.left, r.right, y, heading_str, TextColour::FromString, {AlignmentH::Centre, AlignmentV::Middle}, true);
623 if (_pf_data[e].num_valid == 0) continue;
624 if (skip > 0) {
625 skip--;
626 } else {
627 DrawString(r.left, r.right, y, GetString(values[e].strid, values[e].GetValue(), values[e].GetDecimals()), TextColour::FromString, AlignmentH::ForceRight);
629 drawable--;
630 if (drawable == 0) break;
631 }
632 }
633 }
634
635 void DrawElementAllocationsColumn(const Rect &r) const
636 {
637 const Scrollbar *sb = this->GetScrollbar(WID_FRW_SCROLLBAR);
638 int32_t skip = sb->GetPosition();
639 int drawable = this->num_displayed;
640 int y = r.top;
641 DrawString(r.left, r.right, y, STR_FRAMERATE_MEMORYUSE, TextColour::FromString, {AlignmentH::Centre, AlignmentV::Middle}, true);
644 if (_pf_data[e].num_valid == 0) continue;
645 if (skip > 0) {
646 skip--;
648 uint64_t value = e == PerformanceElement::GameScript ? Game::GetInstance()->GetAllocatedMemory() : Company::Get(GetAIIndex(e))->ai_instance->GetAllocatedMemory();
649 DrawString(r.left, r.right, y, GetString(STR_FRAMERATE_BYTES_GOOD, value), TextColour::FromString, AlignmentH::ForceRight);
651 drawable--;
652 if (drawable == 0) break;
653 } else if (e == PerformanceElement::Sound) {
654 DrawString(r.left, r.right, y, GetString(STR_FRAMERATE_BYTES_GOOD, GetSoundPoolAllocatedMemory()), TextColour::FromString, AlignmentH::ForceRight);
656 drawable--;
657 if (drawable == 0) break;
658 } else {
659 /* skip non-script */
661 drawable--;
662 if (drawable == 0) break;
663 }
664 }
665 }
666
667 void DrawWidget(const Rect &r, WidgetID widget) const override
668 {
669 switch (widget) {
670 case WID_FRW_TIMES_NAMES: {
671 /* Render a column of titles for performance element names */
672 const Scrollbar *sb = this->GetScrollbar(WID_FRW_SCROLLBAR);
673 int32_t skip = sb->GetPosition();
674 int drawable = this->num_displayed;
675 int y = r.top + GetCharacterHeight(FontSize::Normal) + WidgetDimensions::scaled.vsep_normal; // first line contains headings in the value columns
677 if (_pf_data[e].num_valid == 0) continue;
678 if (skip > 0) {
679 skip--;
680 } else {
681 if (e < PerformanceElement::AI0) {
682 DrawString(r.left, r.right, y, STR_FRAMERATE_GAMELOOP + to_underlying(e), TextColour::FromString, AlignmentH::Start);
683 } else {
684 DrawString(r.left, r.right, y, GetString(STR_FRAMERATE_AI, GetAIIndex(e) + 1, GetAIName(e)), TextColour::FromString, AlignmentH::Start);
685 }
687 drawable--;
688 if (drawable == 0) break;
689 }
690 }
691 break;
692 }
693 case WID_FRW_TIMES_CURRENT:
694 /* Render short-term average values */
695 DrawElementTimesColumn(r, STR_FRAMERATE_CURRENT, this->times_shortterm);
696 break;
697 case WID_FRW_TIMES_AVERAGE:
698 /* Render averages of all recorded values */
699 DrawElementTimesColumn(r, STR_FRAMERATE_AVERAGE, this->times_longterm);
700 break;
701 case WID_FRW_ALLOCSIZE:
702 DrawElementAllocationsColumn(r);
703 break;
704 }
705 }
706
707 void OnClick([[maybe_unused]] Point pt, WidgetID widget, [[maybe_unused]] int click_count) override
708 {
709 switch (widget) {
710 case WID_FRW_TIMES_NAMES:
711 case WID_FRW_TIMES_CURRENT:
712 case WID_FRW_TIMES_AVERAGE: {
713 /* Open time graph windows when clicking detail measurement lines */
714 const Scrollbar *sb = this->GetScrollbar(WID_FRW_SCROLLBAR);
715 int32_t line = sb->GetScrolledRowFromWidget(pt.y, this, widget, WidgetDimensions::scaled.vsep_normal + GetCharacterHeight(FontSize::Normal));
716 if (line != INT32_MAX) {
717 line++;
718 /* Find the visible line that was clicked */
720 if (_pf_data[e].num_valid > 0) line--;
721 if (line == 0) {
723 break;
724 }
725 }
726 }
727 break;
728 }
729 }
730 }
731
732 void OnResize() override
733 {
734 auto *wid = this->GetWidget<NWidgetResizeBase>(WID_FRW_TIMES_NAMES);
735 this->num_displayed = (wid->current_y - wid->min_y - WidgetDimensions::scaled.vsep_normal) / GetCharacterHeight(FontSize::Normal) - 1; // subtract 1 for headings
736 this->GetScrollbar(WID_FRW_SCROLLBAR)->SetCapacity(this->num_displayed);
737 }
738};
739
742 WindowPosition::Automatic, "framerate_display", 0, 0,
743 WindowClass::FramerateDisplay, WindowClass::None,
744 {},
745 _framerate_window_widgets
746);
747
748
750static constexpr std::initializer_list<NWidgetPart> _frametime_graph_window_widgets = {
755 EndContainer(),
758 NWidget(WWT_EMPTY, Colours::Invalid, WID_FGW_GRAPH),
759 EndContainer(),
760 EndContainer(),
761};
762
763struct FrametimeGraphWindow : Window {
766
769
770 FrametimeGraphWindow(WindowDesc &desc, WindowNumber number) : Window(desc), element(static_cast<PerformanceElement>(number))
771 {
772 this->InitNested(number);
773 this->UpdateScale();
774 }
775
776 std::string GetWidgetString(WidgetID widget, StringID stringid) const override
777 {
778 switch (widget) {
779 case WID_FGW_CAPTION:
780 if (this->element < PerformanceElement::AI0) {
781 return GetString(STR_FRAMETIME_CAPTION_GAMELOOP + to_underlying(this->element));
782 }
783 return GetString(STR_FRAMETIME_CAPTION_AI, GetAIIndex(this->element) + 1, GetAIName(this->element));
784
785 default:
786 return this->Window::GetWidgetString(widget, stringid);
787 }
788 }
789
790 void UpdateWidgetSize(WidgetID widget, Dimension &size, [[maybe_unused]] const Dimension &padding, [[maybe_unused]] Dimension &fill, [[maybe_unused]] Dimension &resize) override
791 {
792 if (widget == WID_FGW_GRAPH) {
793 Dimension size_ms_label = GetStringBoundingBox(GetString(STR_FRAMERATE_GRAPH_MILLISECONDS, 100));
794 Dimension size_s_label = GetStringBoundingBox(GetString(STR_FRAMERATE_GRAPH_SECONDS, 100));
795
796 /* Size graph in height to fit at least 10 vertical labels with space between, or at least 100 pixels */
797 graph_size.height = std::max<uint>(ScaleGUITrad(100), 10 * (size_ms_label.height + WidgetDimensions::scaled.vsep_normal));
798 /* Always 2:1 graph area */
799 graph_size.width = 2 * graph_size.height;
800 size = graph_size;
801
802 size.width += size_ms_label.width + WidgetDimensions::scaled.hsep_normal;
803 size.height += size_s_label.height + WidgetDimensions::scaled.vsep_normal;
804 }
805 }
806
807 void SelectHorizontalScale(TimingMeasurement range)
808 {
809 /* 60 Hz graphical drawing results in a value of approximately TIMESTAMP_PRECISION,
810 * this lands exactly on the scale = 2 vs scale = 4 boundary.
811 * To avoid excessive switching of the horizontal scale, bias these performance
812 * categories away from this scale boundary. */
813 if (this->element == PerformanceElement::Drawing || this->element == PerformanceElement::ViewportDrawing) range += (range / 2);
814
815 /* Determine horizontal scale based on period covered by 60 points
816 * (slightly less than 2 seconds at full game speed) */
817 struct ScaleDef { TimingMeasurement range; int scale; };
818 static const std::initializer_list<ScaleDef> hscales = {
819 { TIMESTAMP_PRECISION * 120, 60 },
820 { TIMESTAMP_PRECISION * 10, 20 },
821 { TIMESTAMP_PRECISION * 5, 10 },
822 { TIMESTAMP_PRECISION * 3, 4 },
823 { TIMESTAMP_PRECISION * 1, 2 },
824 };
825 for (const auto &sc : hscales) {
826 if (range < sc.range) this->horizontal_scale = sc.scale;
827 }
828 }
829
830 void SelectVerticalScale(TimingMeasurement range)
831 {
832 /* Determine vertical scale based on peak value (within the horizontal scale + a bit) */
833 static const std::initializer_list<TimingMeasurement> vscales = {
843 };
844 for (const auto &sc : vscales) {
845 if (range < sc) this->vertical_scale = (int)sc;
846 }
847 }
848
851 {
852 const auto &durations = _pf_data[this->element].durations;
853 const auto &timestamps = _pf_data[this->element].timestamps;
854 int num_valid = _pf_data[this->element].num_valid;
855 int point = _pf_data[this->element].prev_index;
856
857 TimingMeasurement lastts = timestamps[point];
858 TimingMeasurement time_sum = 0;
859 TimingMeasurement peak_value = 0;
860 int count = 0;
861
862 /* Sensible default for when too few measurements are available */
863 this->horizontal_scale = 4;
864
865 for (int i = 1; i < num_valid; i++) {
866 point--;
867 if (point < 0) point = NUM_FRAMERATE_POINTS - 1;
868
869 TimingMeasurement value = durations[point];
871 /* Skip gaps in data by pretending time is continuous across them */
872 lastts = timestamps[point];
873 continue;
874 }
875 if (value > peak_value) peak_value = value;
876 count++;
877
878 /* Accumulate period of time covered by data */
879 time_sum += lastts - timestamps[point];
880 lastts = timestamps[point];
881
882 /* Enough data to select a range and get decent data density */
883 if (count == 60) this->SelectHorizontalScale(time_sum);
884
885 /* End when enough points have been collected and the horizontal scale has been exceeded */
886 if (count >= 60 && time_sum >= (this->horizontal_scale + 2) * TIMESTAMP_PRECISION / 2) break;
887 }
888
889 this->SelectVerticalScale(peak_value);
890 }
891
893 const IntervalTimer<TimerWindow> update_interval = {std::chrono::milliseconds(500), [this](auto) {
894 this->UpdateScale();
895 }};
896
897 void OnRealtimeTick([[maybe_unused]] uint delta_ms) override
898 {
899 this->SetDirty();
900 }
901
911 template <typename T>
912 static inline T Scinterlate(T dst_min, T dst_max, T src_min, T src_max, T value)
913 {
914 T dst_diff = dst_max - dst_min;
915 T src_diff = src_max - src_min;
916 return (value - src_min) * dst_diff / src_diff + dst_min;
917 }
918
919 void DrawWidget(const Rect &r, WidgetID widget) const override
920 {
921 if (widget == WID_FGW_GRAPH) {
922 const auto &durations = _pf_data[this->element].durations;
923 const auto &timestamps = _pf_data[this->element].timestamps;
924 int point = _pf_data[this->element].prev_index;
925
926 const int x_zero = r.right - (int)this->graph_size.width;
927 const int x_max = r.right;
928 const int y_zero = r.top + (int)this->graph_size.height;
929 const int y_max = r.top;
930 const PixelColour c_grid = PC_DARK_GREY;
931 const PixelColour c_lines = PC_BLACK;
932 const PixelColour c_peak = PC_DARK_RED;
933
934 const TimingMeasurement draw_horz_scale = (TimingMeasurement)this->horizontal_scale * TIMESTAMP_PRECISION / 2;
935 const TimingMeasurement draw_vert_scale = (TimingMeasurement)this->vertical_scale;
936
937 /* Number of \c horizontal_scale units in each horizontal division */
938 const uint horz_div_scl = (this->horizontal_scale <= 20) ? 1 : 10;
939 /* Number of divisions of the horizontal axis */
940 const uint horz_divisions = this->horizontal_scale / horz_div_scl;
941 /* Number of divisions of the vertical axis */
942 const uint vert_divisions = 10;
943
944 /* Draw division lines and labels for the vertical axis */
945 for (uint division = 0; division < vert_divisions; division++) {
946 int y = Scinterlate(y_zero, y_max, 0, (int)vert_divisions, (int)division);
947 GfxDrawLine(x_zero, y, x_max, y, c_grid);
948 if (division % 2 == 0) {
949 if ((TimingMeasurement)this->vertical_scale > TIMESTAMP_PRECISION) {
951 GetString(STR_FRAMERATE_GRAPH_SECONDS, this->vertical_scale * division / 10 / TIMESTAMP_PRECISION),
953 } else {
955 GetString(STR_FRAMERATE_GRAPH_MILLISECONDS, this->vertical_scale * division / 10 * 1000 / TIMESTAMP_PRECISION),
957 }
958 }
959 }
960 /* Draw division lines and labels for the horizontal axis */
961 for (uint division = horz_divisions; division > 0; division--) {
962 int x = Scinterlate(x_zero, x_max, 0, (int)horz_divisions, (int)horz_divisions - (int)division);
963 GfxDrawLine(x, y_max, x, y_zero, c_grid);
964 if (division % 2 == 0) {
965 DrawString(x, x_max, y_zero + WidgetDimensions::scaled.vsep_normal,
966 GetString(STR_FRAMERATE_GRAPH_SECONDS, division * horz_div_scl / 2),
968 }
969 }
970
971 /* Position of last rendered data point */
972 Point lastpoint = {
973 x_max,
974 (int)Scinterlate<int64_t>(y_zero, y_max, 0, this->vertical_scale, durations[point])
975 };
976 /* Timestamp of last rendered data point */
977 TimingMeasurement lastts = timestamps[point];
978
979 TimingMeasurement peak_value = 0;
980 Point peak_point = { 0, 0 };
981 TimingMeasurement value_sum = 0;
982 TimingMeasurement time_sum = 0;
983 int points_drawn = 0;
984
985 for (int i = 1; i < NUM_FRAMERATE_POINTS; i++) {
986 point--;
987 if (point < 0) point = NUM_FRAMERATE_POINTS - 1;
988
989 TimingMeasurement value = durations[point];
991 /* Skip gaps in measurements, pretend the data points on each side are continuous */
992 lastts = timestamps[point];
993 continue;
994 }
995
996 /* Use total time period covered for value along horizontal axis */
997 time_sum += lastts - timestamps[point];
998 lastts = timestamps[point];
999 /* Stop if past the width of the graph */
1000 if (time_sum > draw_horz_scale) break;
1001
1002 /* Draw line from previous point to new point */
1003 Point newpoint = {
1004 (int)Scinterlate<int64_t>(x_zero, x_max, 0, (int64_t)draw_horz_scale, (int64_t)draw_horz_scale - (int64_t)time_sum),
1005 (int)Scinterlate<int64_t>(y_zero, y_max, 0, (int64_t)draw_vert_scale, (int64_t)value)
1006 };
1007 if (newpoint.x > lastpoint.x) continue; // don't draw backwards
1008 GfxDrawLine(lastpoint.x, lastpoint.y, newpoint.x, newpoint.y, c_lines);
1009 lastpoint = newpoint;
1010
1011 /* Record peak and average value across graphed data */
1012 value_sum += value;
1013 points_drawn++;
1014 if (value > peak_value) {
1015 peak_value = value;
1016 peak_point = newpoint;
1017 }
1018 }
1019
1020 /* If the peak value is significantly larger than the average, mark and label it */
1021 if (points_drawn > 0 && peak_value > TIMESTAMP_PRECISION / 100 && 2 * peak_value > 3 * value_sum / points_drawn) {
1022 ExtendedTextColour tc_peak{c_peak};
1023 GfxFillRect(peak_point.x - 1, peak_point.y - 1, peak_point.x + 1, peak_point.y + 1, c_peak);
1024 uint64_t value = peak_value * 1000 / TIMESTAMP_PRECISION;
1025 int label_y = std::max(y_max, peak_point.y - GetCharacterHeight(FontSize::Small));
1026 if (peak_point.x - x_zero > (int)this->graph_size.width / 2) {
1027 DrawString(x_zero, peak_point.x - WidgetDimensions::scaled.hsep_normal, label_y, GetString(STR_FRAMERATE_GRAPH_MILLISECONDS, value), tc_peak, AlignmentH::ForceRight, false, FontSize::Small);
1028 } else {
1029 DrawString(peak_point.x + WidgetDimensions::scaled.hsep_normal, x_max, label_y, GetString(STR_FRAMERATE_GRAPH_MILLISECONDS, value), tc_peak, AlignmentH::ForceLeft, false, FontSize::Small);
1030 }
1031 }
1032 }
1033 }
1034};
1035
1038 WindowPosition::Automatic, "frametime_graph", 140, 90,
1039 WindowClass::FrametimeGraph, WindowClass::None,
1040 {},
1041 _frametime_graph_window_widgets
1042);
1043
1044
1045
1051
1061
1064{
1065 const int count1 = NUM_FRAMERATE_POINTS / 8;
1066 const int count2 = NUM_FRAMERATE_POINTS / 4;
1067 const int count3 = NUM_FRAMERATE_POINTS / 1;
1068
1069 IConsolePrint(TextColour::Silver, "Based on num. data points: {} {} {}", count1, count2, count3);
1070
1072 "Game loop",
1073 " GL station ticks",
1074 " GL train ticks",
1075 " GL road vehicle ticks",
1076 " GL ship ticks",
1077 " GL aircraft ticks",
1078 " GL landscape ticks",
1079 " GL link graph delays",
1080 "Drawing",
1081 " Viewport drawing",
1082 "Video output",
1083 "Sound mixing",
1084 "AI/GS scripts total",
1085 "Game script",
1086 };
1087 std::string ai_name_buf;
1088
1089 bool printed_anything = false;
1090
1092 auto &pf = _pf_data[e];
1093 if (pf.num_valid == 0) continue;
1094 IConsolePrint(TextColour::Green, "{} rate: {:.2f}fps (expected: {:.2f}fps)",
1095 MEASUREMENT_NAMES[e],
1096 pf.GetRate(),
1097 pf.expected_rate);
1098 printed_anything = true;
1099 }
1100
1102 auto &pf = _pf_data[e];
1103 if (pf.num_valid == 0) continue;
1104 std::string_view name;
1105 if (e < PerformanceElement::AI0) {
1106 name = MEASUREMENT_NAMES[e];
1107 } else {
1108 ai_name_buf = fmt::format("AI {} {}", GetAIIndex(e) + 1, GetAIName(e));
1109 name = ai_name_buf;
1110 }
1111 IConsolePrint(TextColour::LightBlue, "{} times: {:.2f}ms {:.2f}ms {:.2f}ms",
1112 name,
1113 pf.GetAverageDurationMilliseconds(count1),
1114 pf.GetAverageDurationMilliseconds(count2),
1115 pf.GetAverageDurationMilliseconds(count3));
1116 printed_anything = true;
1117 }
1118
1119 if (!printed_anything) {
1120 IConsolePrint(CC_ERROR, "No performance measurements have been taken yet.");
1121 }
1122}
1123
1132{
1133 if (_sound_perf_pending.load(std::memory_order_acquire)) {
1134 std::lock_guard lk(_sound_perf_lock);
1135 for (size_t i = 0; i < _sound_perf_measurements.size(); i += 2) {
1136 _pf_data[PerformanceElement::Sound].Add(_sound_perf_measurements[i], _sound_perf_measurements[i + 1]);
1137 }
1138 _sound_perf_measurements.clear();
1139 _sound_perf_pending.store(false, std::memory_order_relaxed);
1140 }
1141}
AIInfo keeps track of all information of an AI, like Author, Description, ...
The AIInstance tracks an AI.
Iterate a range of enum values.
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:2462
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:95
Definition of stuff that is very close to a company, like the company struct itself.
void IConsolePrint(ExtendedTextColour 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
constexpr std::underlying_type_t< enum_type > to_underlying(enum_type e)
Implementation of std::to_underlying (from C++23).
Definition enum_type.hpp:21
EnumClassIndexContainer< std::array< T, to_underlying(N)>, Index > EnumIndexArray
A typedef for EnumClassIndexContainer using std::array as the backing container type.
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 WindowDesc _framerate_display_desc(WindowPosition::Automatic, "framerate_display", 0, 0, WindowClass::FramerateDisplay, WindowClass::None, {}, _framerate_window_widgets)
Window definition for the frame rate window.
static TimingMeasurement GetPerformanceTimer()
Return a timestamp with TIMESTAMP_PRECISION ticks per second precision.
void ProcessPendingPerformanceMeasurements()
This drains the PerformanceElement::Sound measurement data queue into _pf_data.
void ShowFrametimeGraphWindow(PerformanceElement elem)
Open a graph window for a performance element.
static constexpr CompanyID GetAIIndex(PerformanceElement e)
Get the CompanyID associated with the AI of the given PerformanceElement.
void ConPrintFramerate()
Print performance statistics to game console.
static WindowDesc _frametime_graph_window_desc(WindowPosition::Automatic, "frametime_graph", 140, 90, WindowClass::FrametimeGraph, WindowClass::None, {}, _frametime_graph_window_widgets)
Window definition for the frame rate graph window.
static std::string_view GetAIName(PerformanceElement e)
Get the name of the AI of the given PerformanceElement.
static constexpr EnumIndexArray< PerformanceElement, PerformanceElement, PerformanceElement::End > DISPLAY_ORDER_PFE
Order of the performance elements in the user interface.
Types for recording game performance data.
PerformanceElement
Elements of game performance that can be measured.
@ AI7
AI execution for player slot 8.
@ GameLoopLandscape
Time spent processing other world features.
@ AI0
AI execution for player slot 1.
@ Video
Speed of painting drawn video buffer.
@ GameLoopRoadVehicles
Time spend processing road vehicles.
@ GameLoop
Speed of gameloop processing.
@ GameLoopShips
Time spent processing ships.
@ AI9
AI execution for player slot 10.
@ Drawing
Speed of drawing world and GUI.
@ AllScripts
Sum of all GS/AI scripts.
@ GameLoopAircraft
Time spent processing aircraft.
@ End
End of enum, must be last.
@ AI11
AI execution for player slot 12.
@ GameScript
Game script execution.
@ AI4
AI execution for player slot 5.
@ GameLoopTrains
Time spent processing trains.
@ Sound
Speed of mixing audio samples.
@ AI10
AI execution for player slot 11.
@ AI12
AI execution for player slot 13.
@ GameLoopEconomy
Time spent processing cargo movement.
@ AI3
AI execution for player slot 4.
@ ViewportDrawing
Time spent drawing world viewports in GUI.
@ AI13
AI execution for player slot 14.
@ AI5
AI execution for player slot 6.
@ AI14
AI execution for player slot 15.
@ GameLoopLinkGraph
Time spent waiting for link graph background jobs.
@ AI8
AI execution for player slot 9.
@ AI2
AI execution for player slot 3.
@ AI6
AI execution for player slot 7.
@ AI1
AI execution for player slot 2.
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.
@ ForceRight
Force align to the right.
@ Start
Align to the start, LTR/RTL aware.
@ ForceLeft
Force align to the left.
Dimension GetStringBoundingBox(std::string_view str, FontSize start_fontsize)
Return the string dimension in pixels.
Definition gfx.cpp:899
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
int DrawString(int left, int right, int top, std::string_view str, ExtendedTextColour colour, Alignment align, bool underline, FontSize fontsize)
Draw string, possibly truncated to make it fit in its allocated space.
Definition gfx.cpp:668
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
@ Invalid
Invalid marker.
Definition gfx_type.h:302
@ Grey
Grey.
Definition gfx_type.h:299
@ White
White colour.
Definition gfx_type.h:330
@ LightBlue
Light blue colour.
Definition gfx_type.h:331
@ FromString
Marker for telling to use the colour from the string.
Definition gfx_type.h:317
@ Grey
Grey colour.
Definition gfx_type.h:332
@ Green
Green colour.
Definition gfx_type.h:325
@ Silver
Silver colour.
Definition gfx_type.h:319
static const uint MILLISECONDS_PER_TICK
The number of milliseconds per game tick.
Definition gfx_type.h:417
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:972
#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.
static EnumIndexArray< PerformanceData, PerformanceElement, PerformanceElement::End > _pf_data
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.
Container for the text colour and some text colour related flags for drawing.
Definition gfx_type.h:349
void OnResize() override
Called after the window got resized.
void DrawElementTimesColumn(const Rect &r, StringID heading_str, const CachedDecimalArray &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.
EnumIndexArray< CachedDecimal, PerformanceElement, PerformanceElement::End > CachedDecimalArray
Array of cached decimals.
CachedDecimalArray times_shortterm
cached short term average times
CachedDecimalArray times_longterm
cached long 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:307
static Company * Get(auto index)
Specification of a rectangle with absolute coordinates of all edges.
High level window description.
Definition window_gui.h:172
Number to differentiate different windows of the same class.
Data structure for an opened window.
Definition window_gui.h:273
virtual std::string GetWidgetString(WidgetID widget, StringID stringid) const
Get the raw string for a widget.
Definition window.cpp:510
ResizeInfo resize
Resize information.
Definition window_gui.h:314
bool IsShaded() const
Is window shaded currently?
Definition window_gui.h:562
Window(WindowDesc &desc)
Empty constructor, initialization has been moved to InitNested() called from the constructor of the d...
Definition window.cpp:1838
const NWID * GetWidget(WidgetID widnum) const
Get the nested widget with number widnum from the nested widget tree.
Definition window_gui.h:989
void InitNested(WindowNumber number=0)
Perform complete initialization of the Window with nested widgets, to allow use.
Definition window.cpp:1828
const Scrollbar * GetScrollbar(WidgetID widnum) const
Return the Scrollbar to a widget index.
Definition window.cpp:319
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:2105
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.
@ Automatic
Find a place automatically.
Definition window_gui.h:146
int WidgetID
Widget ID.
Definition window_type.h:21
Functions related to zooming.