OpenTTD Source 20260218-master-g2123fca5ea
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 for (int i = first_point; i < first_point + count; i++) {
156 auto d = this->durations[i % NUM_FRAMERATE_POINTS];
157 if (d != INVALID_DURATION) {
158 sumtime += d;
159 } else {
160 /* Don't count the invalid durations */
161 count--;
162 }
163 }
164
165 if (count == 0) return 0; // avoid div by zero
166 return sumtime * 1000 / count / TIMESTAMP_PRECISION;
167 }
168
173 double GetRate()
174 {
175 /* Start at last recorded point, end at latest when reaching the earliest recorded point */
176 int point = this->prev_index;
177 int last_point = this->next_index - this->num_valid;
178 if (last_point < 0) last_point += NUM_FRAMERATE_POINTS;
179
180 /* Number of data points collected */
181 int count = 0;
182 /* Time of previous data point */
183 TimingMeasurement last = this->timestamps[point];
184 /* Total duration covered by collected points */
185 TimingMeasurement total = 0;
186
187 /* We have nothing to compare the first point against */
188 point--;
189 if (point < 0) point = NUM_FRAMERATE_POINTS - 1;
190
191 while (point != last_point) {
192 /* Only record valid data points, but pretend the gaps in measurements aren't there */
193 if (this->durations[point] != INVALID_DURATION) {
194 total += last - this->timestamps[point];
195 count++;
196 }
197 last = this->timestamps[point];
198 if (total >= TIMESTAMP_PRECISION) break; // end after 1 second has been collected
199 point--;
200 if (point < 0) point = NUM_FRAMERATE_POINTS - 1;
201 }
202
203 if (total == 0 || count == 0) return 0;
204 return (double)count * TIMESTAMP_PRECISION / total;
205 }
206 };
207
209 static const double GL_RATE = 1000.0 / MILLISECONDS_PER_TICK;
210
217 PerformanceData(GL_RATE), // PFE_GAMELOOP
218 PerformanceData(1), // PFE_ACC_GL_ECONOMY
219 PerformanceData(1), // PFE_ACC_GL_TRAINS
220 PerformanceData(1), // PFE_ACC_GL_ROADVEHS
221 PerformanceData(1), // PFE_ACC_GL_SHIPS
222 PerformanceData(1), // PFE_ACC_GL_AIRCRAFT
223 PerformanceData(1), // PFE_GL_LANDSCAPE
224 PerformanceData(1), // PFE_GL_LINKGRAPH
225 PerformanceData(1000.0 / 30), // PFE_DRAWING
226 PerformanceData(1), // PFE_ACC_DRAWWORLD
227 PerformanceData(60.0), // PFE_VIDEO
228 PerformanceData(1000.0 * 8192 / 44100), // PFE_SOUND
229 PerformanceData(1), // PFE_ALLSCRIPTS
230 PerformanceData(1), // PFE_GAMESCRIPT
231 PerformanceData(1), // PFE_AI0 ...
245 PerformanceData(1), // PFE_AI14
246 };
247
248}
249
250
258{
259 using namespace std::chrono;
260 return (TimingMeasurement)time_point_cast<microseconds>(high_resolution_clock::now()).time_since_epoch().count();
261}
262
263
269{
270 assert(elem < PFE_MAX);
271
272 this->elem = elem;
273 this->start_time = GetPerformanceTimer();
274}
275
278{
279 if (this->elem == PFE_ALLSCRIPTS) {
280 /* Hack to not record scripts total when no scripts are active */
281 bool any_active = _pf_data[PFE_GAMESCRIPT].num_valid > 0;
282 for (uint e = PFE_AI0; e < PFE_MAX; e++) any_active |= _pf_data[e].num_valid > 0;
283 if (!any_active) {
285 return;
286 }
287 }
288 if (this->elem == PFE_SOUND) {
289 /* PFE_SOUND measurements are made from the mixer thread.
290 * _pf_data cannot be concurrently accessed from the mixer thread
291 * and the main thread, so store the measurement results in a
292 * mutex-protected queue which is drained by the main thread.
293 * See: ProcessPendingPerformanceMeasurements() */
295 std::lock_guard lk(_sound_perf_lock);
296 if (_sound_perf_measurements.size() >= NUM_FRAMERATE_POINTS * 2) return;
297 _sound_perf_measurements.push_back(this->start_time);
298 _sound_perf_measurements.push_back(end);
299 _sound_perf_pending.store(true, std::memory_order_release);
300 return;
301 }
302 _pf_data[this->elem].Add(this->start_time, GetPerformanceTimer());
303}
304
310{
311 _pf_data[this->elem].expected_rate = rate;
312}
313
319{
320 _pf_data[elem].num_valid = 0;
321 _pf_data[elem].next_index = 0;
322 _pf_data[elem].prev_index = 0;
323}
324
330{
332 _pf_data[elem].AddPause(GetPerformanceTimer());
333}
334
335
341{
342 assert(elem < PFE_MAX);
343
344 this->elem = elem;
345 this->start_time = GetPerformanceTimer();
346}
347
350{
351 _pf_data[this->elem].AddAccumulate(GetPerformanceTimer() - this->start_time);
352}
353
363
364
366
367
368static const PerformanceElement DISPLAY_ORDER_PFE[PFE_MAX] = {
378 PFE_AI0,
379 PFE_AI1,
380 PFE_AI2,
381 PFE_AI3,
382 PFE_AI4,
383 PFE_AI5,
384 PFE_AI6,
385 PFE_AI7,
386 PFE_AI8,
387 PFE_AI9,
388 PFE_AI10,
389 PFE_AI11,
390 PFE_AI12,
391 PFE_AI13,
392 PFE_AI14,
396 PFE_VIDEO,
397 PFE_SOUND,
398};
399
400static std::string_view GetAIName(int ai_index)
401{
402 if (!Company::IsValidAiID(ai_index)) return {};
403 return Company::Get(ai_index)->ai_info->GetName();
404}
405
407static constexpr std::initializer_list<NWidgetPart> _framerate_window_widgets = {
409 NWidget(WWT_CLOSEBOX, COLOUR_GREY),
410 NWidget(WWT_CAPTION, COLOUR_GREY, WID_FRW_CAPTION),
411 NWidget(WWT_SHADEBOX, COLOUR_GREY),
412 NWidget(WWT_STICKYBOX, COLOUR_GREY),
413 EndContainer(),
414 NWidget(WWT_PANEL, COLOUR_GREY),
416 NWidget(WWT_TEXT, INVALID_COLOUR, WID_FRW_RATE_GAMELOOP), SetToolTip(STR_FRAMERATE_RATE_GAMELOOP_TOOLTIP), SetFill(1, 0), SetResize(1, 0),
417 NWidget(WWT_TEXT, INVALID_COLOUR, WID_FRW_RATE_DRAWING), SetToolTip(STR_FRAMERATE_RATE_BLITTER_TOOLTIP), SetFill(1, 0), SetResize(1, 0),
418 NWidget(WWT_TEXT, INVALID_COLOUR, WID_FRW_RATE_FACTOR), SetToolTip(STR_FRAMERATE_SPEED_FACTOR_TOOLTIP), SetFill(1, 0), SetResize(1, 0),
419 EndContainer(),
420 EndContainer(),
422 NWidget(WWT_PANEL, COLOUR_GREY),
425 NWidget(WWT_EMPTY, INVALID_COLOUR, WID_FRW_TIMES_NAMES), SetScrollbar(WID_FRW_SCROLLBAR),
426 NWidget(WWT_EMPTY, INVALID_COLOUR, WID_FRW_TIMES_CURRENT), SetScrollbar(WID_FRW_SCROLLBAR),
427 NWidget(WWT_EMPTY, INVALID_COLOUR, WID_FRW_TIMES_AVERAGE), SetScrollbar(WID_FRW_SCROLLBAR),
428 NWidget(WWT_EMPTY, INVALID_COLOUR, WID_FRW_ALLOCSIZE), SetScrollbar(WID_FRW_SCROLLBAR),
429 EndContainer(),
430 NWidget(WWT_TEXT, INVALID_COLOUR, WID_FRW_INFO_DATA_POINTS), SetFill(1, 0), SetResize(1, 0),
431 EndContainer(),
432 EndContainer(),
434 NWidget(NWID_VSCROLLBAR, COLOUR_GREY, WID_FRW_SCROLLBAR),
435 NWidget(WWT_RESIZEBOX, COLOUR_GREY),
436 EndContainer(),
437 EndContainer(),
438};
439
440struct FramerateWindow : Window {
441 int num_active = 0;
442 int num_displayed = 0;
443
445 StringID strid;
446 uint32_t value;
447
448 inline void SetRate(double value, double target)
449 {
450 const double threshold_good = target * 0.95;
451 const double threshold_bad = target * 2 / 3;
452 this->value = (uint32_t)(value * 100);
453 this->strid = (value > threshold_good) ? STR_FRAMERATE_FPS_GOOD : (value < threshold_bad) ? STR_FRAMERATE_FPS_BAD : STR_FRAMERATE_FPS_WARN;
454 }
455
456 inline void SetTime(double value, double target)
457 {
458 const double threshold_good = target / 3;
459 const double threshold_bad = target;
460 this->value = (uint32_t)(value * 100);
461 this->strid = (value < threshold_good) ? STR_FRAMERATE_MS_GOOD : (value > threshold_bad) ? STR_FRAMERATE_MS_BAD : STR_FRAMERATE_MS_WARN;
462 }
463
464 inline uint32_t GetValue() const { return this->value; }
465 inline uint32_t GetDecimals() const { return 2; }
466 };
467
471 std::array<CachedDecimal, PFE_MAX> times_shortterm{};
472 std::array<CachedDecimal, PFE_MAX> times_longterm{};
473
474 static constexpr int MIN_ELEMENTS = 5;
475
476 FramerateWindow(WindowDesc &desc, WindowNumber number) : Window(desc)
477 {
478 this->InitNested(number);
479 this->UpdateData();
480 this->num_displayed = this->num_active;
481
482 /* Window is always initialised to MIN_ELEMENTS height, resize to contain num_displayed */
483 ResizeWindow(this, 0, (std::max(MIN_ELEMENTS, this->num_displayed) - MIN_ELEMENTS) * GetCharacterHeight(FS_NORMAL));
484 }
485
487 const IntervalTimer<TimerWindow> update_interval = {std::chrono::milliseconds(100), [this](auto) {
488 this->UpdateData();
489 this->SetDirty();
490 }};
491
492 void UpdateData()
493 {
494 double gl_rate = _pf_data[PFE_GAMELOOP].GetRate();
495 this->rate_gameloop.SetRate(gl_rate, _pf_data[PFE_GAMELOOP].expected_rate);
496 this->speed_gameloop.SetRate(gl_rate / _pf_data[PFE_GAMELOOP].expected_rate, 1.0);
497 if (this->IsShaded()) return; // in small mode, this is everything needed
498
499 this->rate_drawing.SetRate(_pf_data[PFE_DRAWING].GetRate(), _settings_client.gui.refresh_rate);
500
501 int new_active = 0;
502 for (PerformanceElement e = PFE_FIRST; e < PFE_MAX; e++) {
503 this->times_shortterm[e].SetTime(_pf_data[e].GetAverageDurationMilliseconds(8), MILLISECONDS_PER_TICK);
505 if (_pf_data[e].num_valid > 0) {
506 new_active++;
507 }
508 }
509
510 if (new_active != this->num_active) {
511 this->num_active = new_active;
512 Scrollbar *sb = this->GetScrollbar(WID_FRW_SCROLLBAR);
513 sb->SetCount(this->num_active);
514 sb->SetCapacity(std::min(this->num_displayed, this->num_active));
515 }
516 }
517
518 std::string GetWidgetString(WidgetID widget, StringID stringid) const override
519 {
520 switch (widget) {
521 case WID_FRW_CAPTION:
522 /* When the window is shaded, the caption shows game loop rate and speed factor */
523 if (!this->IsShaded()) return GetString(STR_FRAMERATE_CAPTION);
524
525 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());
526
527 case WID_FRW_RATE_GAMELOOP:
528 return GetString(STR_FRAMERATE_RATE_GAMELOOP, this->rate_gameloop.strid, this->rate_gameloop.GetValue(), this->rate_gameloop.GetDecimals());
529
530 case WID_FRW_RATE_DRAWING:
531 return GetString(STR_FRAMERATE_RATE_BLITTER, this->rate_drawing.strid, this->rate_drawing.GetValue(), this->rate_drawing.GetDecimals());
532
533 case WID_FRW_RATE_FACTOR:
534 return GetString(STR_FRAMERATE_SPEED_FACTOR, this->speed_gameloop.GetValue(), this->speed_gameloop.GetDecimals());
535
536 case WID_FRW_INFO_DATA_POINTS:
537 return GetString(STR_FRAMERATE_DATA_POINTS, NUM_FRAMERATE_POINTS);
538
539 default:
540 return this->Window::GetWidgetString(widget, stringid);
541 }
542 }
543
544 void UpdateWidgetSize(WidgetID widget, Dimension &size, [[maybe_unused]] const Dimension &padding, [[maybe_unused]] Dimension &fill, [[maybe_unused]] Dimension &resize) override
545 {
546 switch (widget) {
547 case WID_FRW_RATE_GAMELOOP:
548 size = GetStringBoundingBox(GetString(STR_FRAMERATE_RATE_GAMELOOP, STR_FRAMERATE_FPS_GOOD, GetParamMaxDigits(6), 2));
549 break;
550 case WID_FRW_RATE_DRAWING:
551 size = GetStringBoundingBox(GetString(STR_FRAMERATE_RATE_BLITTER, STR_FRAMERATE_FPS_GOOD, GetParamMaxDigits(6), 2));
552 break;
553 case WID_FRW_RATE_FACTOR:
554 size = GetStringBoundingBox(GetString(STR_FRAMERATE_SPEED_FACTOR, GetParamMaxDigits(6), 2));
555 break;
556
557 case WID_FRW_TIMES_NAMES: {
558 size.width = 0;
560 resize.width = 0;
561 fill.height = resize.height = GetCharacterHeight(FS_NORMAL);
562 for (PerformanceElement e : DISPLAY_ORDER_PFE) {
563 if (_pf_data[e].num_valid == 0) continue;
564 Dimension line_size;
565 if (e < PFE_AI0) {
566 line_size = GetStringBoundingBox(STR_FRAMERATE_GAMELOOP + e);
567 } else {
568 line_size = GetStringBoundingBox(GetString(STR_FRAMERATE_AI, e - PFE_AI0 + 1, GetAIName(e - PFE_AI0)));
569 }
570 size.width = std::max(size.width, line_size.width);
571 }
572 break;
573 }
574
575 case WID_FRW_TIMES_CURRENT:
576 case WID_FRW_TIMES_AVERAGE:
577 case WID_FRW_ALLOCSIZE: {
578 size = GetStringBoundingBox(STR_FRAMERATE_CURRENT + (widget - WID_FRW_TIMES_CURRENT));
579 Dimension item_size = GetStringBoundingBox(GetString(STR_FRAMERATE_MS_GOOD, GetParamMaxDigits(6), 2));
580 size.width = std::max(size.width, item_size.width);
582 resize.width = 0;
583 fill.height = resize.height = GetCharacterHeight(FS_NORMAL);
584 break;
585 }
586 }
587 }
588
595 void DrawElementTimesColumn(const Rect &r, StringID heading_str, std::span<const CachedDecimal> values) const
596 {
597 const Scrollbar *sb = this->GetScrollbar(WID_FRW_SCROLLBAR);
598 int32_t skip = sb->GetPosition();
599 int drawable = this->num_displayed;
600 int y = r.top;
601 DrawString(r.left, r.right, y, heading_str, TC_FROMSTRING, SA_CENTER, true);
603 for (PerformanceElement e : DISPLAY_ORDER_PFE) {
604 if (_pf_data[e].num_valid == 0) continue;
605 if (skip > 0) {
606 skip--;
607 } else {
608 DrawString(r.left, r.right, y, GetString(values[e].strid, values[e].GetValue(), values[e].GetDecimals()), TC_FROMSTRING, SA_RIGHT | SA_FORCE);
610 drawable--;
611 if (drawable == 0) break;
612 }
613 }
614 }
615
616 void DrawElementAllocationsColumn(const Rect &r) const
617 {
618 const Scrollbar *sb = this->GetScrollbar(WID_FRW_SCROLLBAR);
619 int32_t skip = sb->GetPosition();
620 int drawable = this->num_displayed;
621 int y = r.top;
622 DrawString(r.left, r.right, y, STR_FRAMERATE_MEMORYUSE, TC_FROMSTRING, SA_CENTER, true);
624 for (PerformanceElement e : DISPLAY_ORDER_PFE) {
625 if (_pf_data[e].num_valid == 0) continue;
626 if (skip > 0) {
627 skip--;
628 } else if (e == PFE_GAMESCRIPT || e >= PFE_AI0) {
629 uint64_t value = e == PFE_GAMESCRIPT ? Game::GetInstance()->GetAllocatedMemory() : Company::Get(e - PFE_AI0)->ai_instance->GetAllocatedMemory();
630 DrawString(r.left, r.right, y, GetString(STR_FRAMERATE_BYTES_GOOD, value), TC_FROMSTRING, SA_RIGHT | SA_FORCE);
632 drawable--;
633 if (drawable == 0) break;
634 } else if (e == PFE_SOUND) {
635 DrawString(r.left, r.right, y, GetString(STR_FRAMERATE_BYTES_GOOD, GetSoundPoolAllocatedMemory()), TC_FROMSTRING, SA_RIGHT | SA_FORCE);
637 drawable--;
638 if (drawable == 0) break;
639 } else {
640 /* skip non-script */
642 drawable--;
643 if (drawable == 0) break;
644 }
645 }
646 }
647
648 void DrawWidget(const Rect &r, WidgetID widget) const override
649 {
650 switch (widget) {
651 case WID_FRW_TIMES_NAMES: {
652 /* Render a column of titles for performance element names */
653 const Scrollbar *sb = this->GetScrollbar(WID_FRW_SCROLLBAR);
654 int32_t skip = sb->GetPosition();
655 int drawable = this->num_displayed;
656 int y = r.top + GetCharacterHeight(FS_NORMAL) + WidgetDimensions::scaled.vsep_normal; // first line contains headings in the value columns
657 for (PerformanceElement e : DISPLAY_ORDER_PFE) {
658 if (_pf_data[e].num_valid == 0) continue;
659 if (skip > 0) {
660 skip--;
661 } else {
662 if (e < PFE_AI0) {
663 DrawString(r.left, r.right, y, STR_FRAMERATE_GAMELOOP + e, TC_FROMSTRING, SA_LEFT);
664 } else {
665 DrawString(r.left, r.right, y, GetString(STR_FRAMERATE_AI, e - PFE_AI0 + 1, GetAIName(e - PFE_AI0)), TC_FROMSTRING, SA_LEFT);
666 }
668 drawable--;
669 if (drawable == 0) break;
670 }
671 }
672 break;
673 }
674 case WID_FRW_TIMES_CURRENT:
675 /* Render short-term average values */
676 DrawElementTimesColumn(r, STR_FRAMERATE_CURRENT, this->times_shortterm);
677 break;
678 case WID_FRW_TIMES_AVERAGE:
679 /* Render averages of all recorded values */
680 DrawElementTimesColumn(r, STR_FRAMERATE_AVERAGE, this->times_longterm);
681 break;
682 case WID_FRW_ALLOCSIZE:
683 DrawElementAllocationsColumn(r);
684 break;
685 }
686 }
687
688 void OnClick([[maybe_unused]] Point pt, WidgetID widget, [[maybe_unused]] int click_count) override
689 {
690 switch (widget) {
691 case WID_FRW_TIMES_NAMES:
692 case WID_FRW_TIMES_CURRENT:
693 case WID_FRW_TIMES_AVERAGE: {
694 /* Open time graph windows when clicking detail measurement lines */
695 const Scrollbar *sb = this->GetScrollbar(WID_FRW_SCROLLBAR);
696 int32_t line = sb->GetScrolledRowFromWidget(pt.y, this, widget, WidgetDimensions::scaled.vsep_normal + GetCharacterHeight(FS_NORMAL));
697 if (line != INT32_MAX) {
698 line++;
699 /* Find the visible line that was clicked */
700 for (PerformanceElement e : DISPLAY_ORDER_PFE) {
701 if (_pf_data[e].num_valid > 0) line--;
702 if (line == 0) {
704 break;
705 }
706 }
707 }
708 break;
709 }
710 }
711 }
712
713 void OnResize() override
714 {
715 auto *wid = this->GetWidget<NWidgetResizeBase>(WID_FRW_TIMES_NAMES);
716 this->num_displayed = (wid->current_y - wid->min_y - WidgetDimensions::scaled.vsep_normal) / GetCharacterHeight(FS_NORMAL) - 1; // subtract 1 for headings
717 this->GetScrollbar(WID_FRW_SCROLLBAR)->SetCapacity(this->num_displayed);
718 }
719};
720
721static WindowDesc _framerate_display_desc(
722 WDP_AUTO, "framerate_display", 0, 0,
724 {},
725 _framerate_window_widgets
726);
727
728
730static constexpr std::initializer_list<NWidgetPart> _frametime_graph_window_widgets = {
732 NWidget(WWT_CLOSEBOX, COLOUR_GREY),
733 NWidget(WWT_CAPTION, COLOUR_GREY, WID_FGW_CAPTION), SetTextStyle(TC_WHITE),
734 NWidget(WWT_STICKYBOX, COLOUR_GREY),
735 EndContainer(),
736 NWidget(WWT_PANEL, COLOUR_GREY),
738 NWidget(WWT_EMPTY, INVALID_COLOUR, WID_FGW_GRAPH),
739 EndContainer(),
740 EndContainer(),
741};
742
743struct FrametimeGraphWindow : Window {
746
749
750 FrametimeGraphWindow(WindowDesc &desc, WindowNumber number) : Window(desc), element(static_cast<PerformanceElement>(number))
751 {
752 this->InitNested(number);
753 this->UpdateScale();
754 }
755
756 std::string GetWidgetString(WidgetID widget, StringID stringid) const override
757 {
758 switch (widget) {
759 case WID_FGW_CAPTION:
760 if (this->element < PFE_AI0) {
761 return GetString(STR_FRAMETIME_CAPTION_GAMELOOP + this->element);
762 }
763 return GetString(STR_FRAMETIME_CAPTION_AI, this->element - PFE_AI0 + 1, GetAIName(this->element - PFE_AI0));
764
765 default:
766 return this->Window::GetWidgetString(widget, stringid);
767 }
768 }
769
770 void UpdateWidgetSize(WidgetID widget, Dimension &size, [[maybe_unused]] const Dimension &padding, [[maybe_unused]] Dimension &fill, [[maybe_unused]] Dimension &resize) override
771 {
772 if (widget == WID_FGW_GRAPH) {
773 Dimension size_ms_label = GetStringBoundingBox(GetString(STR_FRAMERATE_GRAPH_MILLISECONDS, 100));
774 Dimension size_s_label = GetStringBoundingBox(GetString(STR_FRAMERATE_GRAPH_SECONDS, 100));
775
776 /* Size graph in height to fit at least 10 vertical labels with space between, or at least 100 pixels */
777 graph_size.height = std::max<uint>(ScaleGUITrad(100), 10 * (size_ms_label.height + WidgetDimensions::scaled.vsep_normal));
778 /* Always 2:1 graph area */
779 graph_size.width = 2 * graph_size.height;
780 size = graph_size;
781
782 size.width += size_ms_label.width + WidgetDimensions::scaled.hsep_normal;
783 size.height += size_s_label.height + WidgetDimensions::scaled.vsep_normal;
784 }
785 }
786
787 void SelectHorizontalScale(TimingMeasurement range)
788 {
789 /* 60 Hz graphical drawing results in a value of approximately TIMESTAMP_PRECISION,
790 * this lands exactly on the scale = 2 vs scale = 4 boundary.
791 * To avoid excessive switching of the horizontal scale, bias these performance
792 * categories away from this scale boundary. */
793 if (this->element == PFE_DRAWING || this->element == PFE_DRAWWORLD) range += (range / 2);
794
795 /* Determine horizontal scale based on period covered by 60 points
796 * (slightly less than 2 seconds at full game speed) */
797 struct ScaleDef { TimingMeasurement range; int scale; };
798 static const std::initializer_list<ScaleDef> hscales = {
799 { TIMESTAMP_PRECISION * 120, 60 },
800 { TIMESTAMP_PRECISION * 10, 20 },
801 { TIMESTAMP_PRECISION * 5, 10 },
802 { TIMESTAMP_PRECISION * 3, 4 },
803 { TIMESTAMP_PRECISION * 1, 2 },
804 };
805 for (const auto &sc : hscales) {
806 if (range < sc.range) this->horizontal_scale = sc.scale;
807 }
808 }
809
810 void SelectVerticalScale(TimingMeasurement range)
811 {
812 /* Determine vertical scale based on peak value (within the horizontal scale + a bit) */
813 static const std::initializer_list<TimingMeasurement> vscales = {
823 };
824 for (const auto &sc : vscales) {
825 if (range < sc) this->vertical_scale = (int)sc;
826 }
827 }
828
831 {
832 const auto &durations = _pf_data[this->element].durations;
833 const auto &timestamps = _pf_data[this->element].timestamps;
834 int num_valid = _pf_data[this->element].num_valid;
835 int point = _pf_data[this->element].prev_index;
836
837 TimingMeasurement lastts = timestamps[point];
838 TimingMeasurement time_sum = 0;
839 TimingMeasurement peak_value = 0;
840 int count = 0;
841
842 /* Sensible default for when too few measurements are available */
843 this->horizontal_scale = 4;
844
845 for (int i = 1; i < num_valid; i++) {
846 point--;
847 if (point < 0) point = NUM_FRAMERATE_POINTS - 1;
848
849 TimingMeasurement value = durations[point];
851 /* Skip gaps in data by pretending time is continuous across them */
852 lastts = timestamps[point];
853 continue;
854 }
855 if (value > peak_value) peak_value = value;
856 count++;
857
858 /* Accumulate period of time covered by data */
859 time_sum += lastts - timestamps[point];
860 lastts = timestamps[point];
861
862 /* Enough data to select a range and get decent data density */
863 if (count == 60) this->SelectHorizontalScale(time_sum);
864
865 /* End when enough points have been collected and the horizontal scale has been exceeded */
866 if (count >= 60 && time_sum >= (this->horizontal_scale + 2) * TIMESTAMP_PRECISION / 2) break;
867 }
868
869 this->SelectVerticalScale(peak_value);
870 }
871
873 const IntervalTimer<TimerWindow> update_interval = {std::chrono::milliseconds(500), [this](auto) {
874 this->UpdateScale();
875 }};
876
877 void OnRealtimeTick([[maybe_unused]] uint delta_ms) override
878 {
879 this->SetDirty();
880 }
881
891 template <typename T>
892 static inline T Scinterlate(T dst_min, T dst_max, T src_min, T src_max, T value)
893 {
894 T dst_diff = dst_max - dst_min;
895 T src_diff = src_max - src_min;
896 return (value - src_min) * dst_diff / src_diff + dst_min;
897 }
898
899 void DrawWidget(const Rect &r, WidgetID widget) const override
900 {
901 if (widget == WID_FGW_GRAPH) {
902 const auto &durations = _pf_data[this->element].durations;
903 const auto &timestamps = _pf_data[this->element].timestamps;
904 int point = _pf_data[this->element].prev_index;
905
906 const int x_zero = r.right - (int)this->graph_size.width;
907 const int x_max = r.right;
908 const int y_zero = r.top + (int)this->graph_size.height;
909 const int y_max = r.top;
910 const PixelColour c_grid = PC_DARK_GREY;
911 const PixelColour c_lines = PC_BLACK;
912 const PixelColour c_peak = PC_DARK_RED;
913
914 const TimingMeasurement draw_horz_scale = (TimingMeasurement)this->horizontal_scale * TIMESTAMP_PRECISION / 2;
915 const TimingMeasurement draw_vert_scale = (TimingMeasurement)this->vertical_scale;
916
917 /* Number of \c horizontal_scale units in each horizontal division */
918 const uint horz_div_scl = (this->horizontal_scale <= 20) ? 1 : 10;
919 /* Number of divisions of the horizontal axis */
920 const uint horz_divisions = this->horizontal_scale / horz_div_scl;
921 /* Number of divisions of the vertical axis */
922 const uint vert_divisions = 10;
923
924 /* Draw division lines and labels for the vertical axis */
925 for (uint division = 0; division < vert_divisions; division++) {
926 int y = Scinterlate(y_zero, y_max, 0, (int)vert_divisions, (int)division);
927 GfxDrawLine(x_zero, y, x_max, y, c_grid);
928 if (division % 2 == 0) {
929 if ((TimingMeasurement)this->vertical_scale > TIMESTAMP_PRECISION) {
930 DrawString(r.left, x_zero - WidgetDimensions::scaled.hsep_normal, y - GetCharacterHeight(FS_SMALL),
931 GetString(STR_FRAMERATE_GRAPH_SECONDS, this->vertical_scale * division / 10 / TIMESTAMP_PRECISION),
932 TC_GREY, SA_RIGHT | SA_FORCE, false, FS_SMALL);
933 } else {
934 DrawString(r.left, x_zero - WidgetDimensions::scaled.hsep_normal, y - GetCharacterHeight(FS_SMALL),
935 GetString(STR_FRAMERATE_GRAPH_MILLISECONDS, this->vertical_scale * division / 10 * 1000 / TIMESTAMP_PRECISION),
936 TC_GREY, SA_RIGHT | SA_FORCE, false, FS_SMALL);
937 }
938 }
939 }
940 /* Draw division lines and labels for the horizontal axis */
941 for (uint division = horz_divisions; division > 0; division--) {
942 int x = Scinterlate(x_zero, x_max, 0, (int)horz_divisions, (int)horz_divisions - (int)division);
943 GfxDrawLine(x, y_max, x, y_zero, c_grid);
944 if (division % 2 == 0) {
945 DrawString(x, x_max, y_zero + WidgetDimensions::scaled.vsep_normal,
946 GetString(STR_FRAMERATE_GRAPH_SECONDS, division * horz_div_scl / 2),
947 TC_GREY, SA_LEFT | SA_FORCE, false, FS_SMALL);
948 }
949 }
950
951 /* Position of last rendered data point */
952 Point lastpoint = {
953 x_max,
954 (int)Scinterlate<int64_t>(y_zero, y_max, 0, this->vertical_scale, durations[point])
955 };
956 /* Timestamp of last rendered data point */
957 TimingMeasurement lastts = timestamps[point];
958
959 TimingMeasurement peak_value = 0;
960 Point peak_point = { 0, 0 };
961 TimingMeasurement value_sum = 0;
962 TimingMeasurement time_sum = 0;
963 int points_drawn = 0;
964
965 for (int i = 1; i < NUM_FRAMERATE_POINTS; i++) {
966 point--;
967 if (point < 0) point = NUM_FRAMERATE_POINTS - 1;
968
969 TimingMeasurement value = durations[point];
971 /* Skip gaps in measurements, pretend the data points on each side are continuous */
972 lastts = timestamps[point];
973 continue;
974 }
975
976 /* Use total time period covered for value along horizontal axis */
977 time_sum += lastts - timestamps[point];
978 lastts = timestamps[point];
979 /* Stop if past the width of the graph */
980 if (time_sum > draw_horz_scale) break;
981
982 /* Draw line from previous point to new point */
983 Point newpoint = {
984 (int)Scinterlate<int64_t>(x_zero, x_max, 0, (int64_t)draw_horz_scale, (int64_t)draw_horz_scale - (int64_t)time_sum),
985 (int)Scinterlate<int64_t>(y_zero, y_max, 0, (int64_t)draw_vert_scale, (int64_t)value)
986 };
987 if (newpoint.x > lastpoint.x) continue; // don't draw backwards
988 GfxDrawLine(lastpoint.x, lastpoint.y, newpoint.x, newpoint.y, c_lines);
989 lastpoint = newpoint;
990
991 /* Record peak and average value across graphed data */
992 value_sum += value;
993 points_drawn++;
994 if (value > peak_value) {
995 peak_value = value;
996 peak_point = newpoint;
997 }
998 }
999
1000 /* If the peak value is significantly larger than the average, mark and label it */
1001 if (points_drawn > 0 && peak_value > TIMESTAMP_PRECISION / 100 && 2 * peak_value > 3 * value_sum / points_drawn) {
1002 TextColour tc_peak = c_peak.ToTextColour();
1003 GfxFillRect(peak_point.x - 1, peak_point.y - 1, peak_point.x + 1, peak_point.y + 1, c_peak);
1004 uint64_t value = peak_value * 1000 / TIMESTAMP_PRECISION;
1005 int label_y = std::max(y_max, peak_point.y - GetCharacterHeight(FS_SMALL));
1006 if (peak_point.x - x_zero > (int)this->graph_size.width / 2) {
1007 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, FS_SMALL);
1008 } else {
1009 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, FS_SMALL);
1010 }
1011 }
1012 }
1013 }
1014};
1015
1016static WindowDesc _frametime_graph_window_desc(
1017 WDP_AUTO, "frametime_graph", 140, 90,
1019 {},
1020 _frametime_graph_window_widgets
1021);
1022
1023
1024
1027{
1028 AllocateWindowDescFront<FramerateWindow>(_framerate_display_desc, 0);
1029}
1030
1036{
1037 if (elem < PFE_FIRST || elem >= PFE_MAX) return; // maybe warn?
1038 AllocateWindowDescFront<FrametimeGraphWindow>(_frametime_graph_window_desc, elem);
1039}
1040
1043{
1044 const int count1 = NUM_FRAMERATE_POINTS / 8;
1045 const int count2 = NUM_FRAMERATE_POINTS / 4;
1046 const int count3 = NUM_FRAMERATE_POINTS / 1;
1047
1048 IConsolePrint(TC_SILVER, "Based on num. data points: {} {} {}", count1, count2, count3);
1049
1050 static const std::array<std::string_view, PFE_MAX> MEASUREMENT_NAMES = {
1051 "Game loop",
1052 " GL station ticks",
1053 " GL train ticks",
1054 " GL road vehicle ticks",
1055 " GL ship ticks",
1056 " GL aircraft ticks",
1057 " GL landscape ticks",
1058 " GL link graph delays",
1059 "Drawing",
1060 " Viewport drawing",
1061 "Video output",
1062 "Sound mixing",
1063 "AI/GS scripts total",
1064 "Game script",
1065 };
1066 std::string ai_name_buf;
1067
1068 bool printed_anything = false;
1069
1070 for (const auto &e : { PFE_GAMELOOP, PFE_DRAWING, PFE_VIDEO }) {
1071 auto &pf = _pf_data[e];
1072 if (pf.num_valid == 0) continue;
1073 IConsolePrint(TC_GREEN, "{} rate: {:.2f}fps (expected: {:.2f}fps)",
1074 MEASUREMENT_NAMES[e],
1075 pf.GetRate(),
1076 pf.expected_rate);
1077 printed_anything = true;
1078 }
1079
1080 for (PerformanceElement e = PFE_FIRST; e < PFE_MAX; e++) {
1081 auto &pf = _pf_data[e];
1082 if (pf.num_valid == 0) continue;
1083 std::string_view name;
1084 if (e < PFE_AI0) {
1085 name = MEASUREMENT_NAMES[e];
1086 } else {
1087 ai_name_buf = fmt::format("AI {} {}", e - PFE_AI0 + 1, GetAIName(e - PFE_AI0));
1088 name = ai_name_buf;
1089 }
1090 IConsolePrint(TC_LIGHT_BLUE, "{} times: {:.2f}ms {:.2f}ms {:.2f}ms",
1091 name,
1092 pf.GetAverageDurationMilliseconds(count1),
1093 pf.GetAverageDurationMilliseconds(count2),
1094 pf.GetAverageDurationMilliseconds(count3));
1095 printed_anything = true;
1096 }
1097
1098 if (!printed_anything) {
1099 IConsolePrint(CC_ERROR, "No performance measurements have been taken yet.");
1100 }
1101}
1102
1111{
1112 if (_sound_perf_pending.load(std::memory_order_acquire)) {
1113 std::lock_guard lk(_sound_perf_lock);
1114 for (size_t i = 0; i < _sound_perf_measurements.size(); i += 2) {
1115 _pf_data[PFE_SOUND].Add(_sound_perf_measurements[i], _sound_perf_measurements[i + 1]);
1116 }
1117 _sound_perf_measurements.clear();
1118 _sound_perf_pending.store(false, std::memory_order_relaxed);
1119 }
1120}
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:2426
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.
int GetCharacterHeight(FontSize size)
Get height of a character for a given font size.
Definition fontcache.cpp:87
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.
@ FS_SMALL
Index of the small font in the font tables.
Definition gfx_type.h:250
@ FS_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
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 SetTextStyle(TextColour colour, FontSize size=FS_NORMAL)
Widget part function for setting the text style.
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 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:967
#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:505
ResizeInfo resize
Resize information.
Definition window_gui.h:315
bool IsShaded() const
Is window shaded currently?
Definition window_gui.h:560
Window(WindowDesc &desc)
Empty constructor, initialization has been moved to InitNested() called from the constructor of the d...
Definition window.cpp:1832
const NWID * GetWidget(WidgetID widnum) const
Get the nested widget with number widnum from the nested widget tree.
Definition window_gui.h:986
void InitNested(WindowNumber number=0)
Perform complete initialization of the Window with nested widgets, to allow use.
Definition window.cpp:1822
const Scrollbar * GetScrollbar(WidgetID widnum) const
Return the Scrollbar to a widget index.
Definition window.cpp:314
TimingMeasurement acc_duration
Current accumulated duration.
int next_index
Next index to write to in durations and timestamps.
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:2099
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:20
@ WC_FRAMETIME_GRAPH
Frame time graph; Window numbers:
@ WC_NONE
No window, redirects to WC_MAIN_WINDOW.
Definition window_type.h:50
@ WC_FRAMERATE_DISPLAY
Framerate display; Window numbers:
Functions related to zooming.