OpenTTD Source 20250924-master-gbec4e71d53
framerate_gui.cpp
Go to the documentation of this file.
1/*
2 * This file is part of OpenTTD.
3 * OpenTTD is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, version 2.
4 * OpenTTD is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
5 * See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with OpenTTD. If not, see <http://www.gnu.org/licenses/>.
6 */
7
10#include "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
72 TimingMeasurement acc_duration{};
74 TimingMeasurement acc_timestamp{};
75
82 explicit PerformanceData(double expected_rate) : expected_rate(expected_rate) { }
83
85 void Add(TimingMeasurement start_time, TimingMeasurement end_time)
86 {
87 this->durations[this->next_index] = end_time - start_time;
88 this->timestamps[this->next_index] = start_time;
89 this->prev_index = this->next_index;
90 this->next_index += 1;
91 if (this->next_index >= NUM_FRAMERATE_POINTS) this->next_index = 0;
92 this->num_valid = std::min(NUM_FRAMERATE_POINTS, this->num_valid + 1);
93 }
94
97 {
98 this->timestamps[this->next_index] = this->acc_timestamp;
99 this->durations[this->next_index] = this->acc_duration;
100 this->prev_index = this->next_index;
101 this->next_index += 1;
102 if (this->next_index >= NUM_FRAMERATE_POINTS) this->next_index = 0;
103 this->num_valid = std::min(NUM_FRAMERATE_POINTS, this->num_valid + 1);
104
105 this->acc_duration = 0;
106 this->acc_timestamp = start_time;
107 }
108
111 {
112 this->acc_duration += duration;
113 }
114
117 {
118 if (this->durations[this->prev_index] != INVALID_DURATION) {
119 this->timestamps[this->next_index] = start_time;
120 this->durations[this->next_index] = INVALID_DURATION;
121 this->prev_index = this->next_index;
122 this->next_index += 1;
123 if (this->next_index >= NUM_FRAMERATE_POINTS) this->next_index = 0;
124 this->num_valid += 1;
125 }
126 }
127
130 {
131 count = std::min(count, this->num_valid);
132
133 int first_point = this->prev_index - count;
134 if (first_point < 0) first_point += NUM_FRAMERATE_POINTS;
135
136 /* Sum durations, skipping invalid points */
137 double sumtime = 0;
138 for (int i = first_point; i < first_point + count; i++) {
139 auto d = this->durations[i % NUM_FRAMERATE_POINTS];
140 if (d != INVALID_DURATION) {
141 sumtime += d;
142 } else {
143 /* Don't count the invalid durations */
144 count--;
145 }
146 }
147
148 if (count == 0) return 0; // avoid div by zero
149 return sumtime * 1000 / count / TIMESTAMP_PRECISION;
150 }
151
153 double GetRate()
154 {
155 /* Start at last recorded point, end at latest when reaching the earliest recorded point */
156 int point = this->prev_index;
157 int last_point = this->next_index - this->num_valid;
158 if (last_point < 0) last_point += NUM_FRAMERATE_POINTS;
159
160 /* Number of data points collected */
161 int count = 0;
162 /* Time of previous data point */
163 TimingMeasurement last = this->timestamps[point];
164 /* Total duration covered by collected points */
165 TimingMeasurement total = 0;
166
167 /* We have nothing to compare the first point against */
168 point--;
169 if (point < 0) point = NUM_FRAMERATE_POINTS - 1;
170
171 while (point != last_point) {
172 /* Only record valid data points, but pretend the gaps in measurements aren't there */
173 if (this->durations[point] != INVALID_DURATION) {
174 total += last - this->timestamps[point];
175 count++;
176 }
177 last = this->timestamps[point];
178 if (total >= TIMESTAMP_PRECISION) break; // end after 1 second has been collected
179 point--;
180 if (point < 0) point = NUM_FRAMERATE_POINTS - 1;
181 }
182
183 if (total == 0 || count == 0) return 0;
184 return (double)count * TIMESTAMP_PRECISION / total;
185 }
186 };
187
189 static const double GL_RATE = 1000.0 / MILLISECONDS_PER_TICK;
190
197 PerformanceData(GL_RATE), // PFE_GAMELOOP
198 PerformanceData(1), // PFE_ACC_GL_ECONOMY
199 PerformanceData(1), // PFE_ACC_GL_TRAINS
200 PerformanceData(1), // PFE_ACC_GL_ROADVEHS
201 PerformanceData(1), // PFE_ACC_GL_SHIPS
202 PerformanceData(1), // PFE_ACC_GL_AIRCRAFT
203 PerformanceData(1), // PFE_GL_LANDSCAPE
204 PerformanceData(1), // PFE_GL_LINKGRAPH
205 PerformanceData(1000.0 / 30), // PFE_DRAWING
206 PerformanceData(1), // PFE_ACC_DRAWWORLD
207 PerformanceData(60.0), // PFE_VIDEO
208 PerformanceData(1000.0 * 8192 / 44100), // PFE_SOUND
209 PerformanceData(1), // PFE_ALLSCRIPTS
210 PerformanceData(1), // PFE_GAMESCRIPT
211 PerformanceData(1), // PFE_AI0 ...
225 PerformanceData(1), // PFE_AI14
226 };
227
228}
229
230
237{
238 using namespace std::chrono;
239 return (TimingMeasurement)time_point_cast<microseconds>(high_resolution_clock::now()).time_since_epoch().count();
240}
241
242
248{
249 assert(elem < PFE_MAX);
250
251 this->elem = elem;
252 this->start_time = GetPerformanceTimer();
253}
254
257{
258 if (this->elem == PFE_ALLSCRIPTS) {
259 /* Hack to not record scripts total when no scripts are active */
260 bool any_active = _pf_data[PFE_GAMESCRIPT].num_valid > 0;
261 for (uint e = PFE_AI0; e < PFE_MAX; e++) any_active |= _pf_data[e].num_valid > 0;
262 if (!any_active) {
264 return;
265 }
266 }
267 if (this->elem == PFE_SOUND) {
268 /* PFE_SOUND measurements are made from the mixer thread.
269 * _pf_data cannot be concurrently accessed from the mixer thread
270 * and the main thread, so store the measurement results in a
271 * mutex-protected queue which is drained by the main thread.
272 * See: ProcessPendingPerformanceMeasurements() */
274 std::lock_guard lk(_sound_perf_lock);
275 if (_sound_perf_measurements.size() >= NUM_FRAMERATE_POINTS * 2) return;
276 _sound_perf_measurements.push_back(this->start_time);
277 _sound_perf_measurements.push_back(end);
278 _sound_perf_pending.store(true, std::memory_order_release);
279 return;
280 }
281 _pf_data[this->elem].Add(this->start_time, GetPerformanceTimer());
282}
283
286{
287 _pf_data[this->elem].expected_rate = rate;
288}
289
292{
293 _pf_data[elem].num_valid = 0;
294 _pf_data[elem].next_index = 0;
295 _pf_data[elem].prev_index = 0;
296}
297
303{
305 _pf_data[elem].AddPause(GetPerformanceTimer());
306}
307
308
314{
315 assert(elem < PFE_MAX);
316
317 this->elem = elem;
318 this->start_time = GetPerformanceTimer();
319}
320
323{
324 _pf_data[this->elem].AddAccumulate(GetPerformanceTimer() - this->start_time);
325}
326
336
337
339
340
341static const PerformanceElement DISPLAY_ORDER_PFE[PFE_MAX] = {
351 PFE_AI0,
352 PFE_AI1,
353 PFE_AI2,
354 PFE_AI3,
355 PFE_AI4,
356 PFE_AI5,
357 PFE_AI6,
358 PFE_AI7,
359 PFE_AI8,
360 PFE_AI9,
361 PFE_AI10,
362 PFE_AI11,
363 PFE_AI12,
364 PFE_AI13,
365 PFE_AI14,
369 PFE_VIDEO,
370 PFE_SOUND,
371};
372
373static std::string_view GetAIName(int ai_index)
374{
375 if (!Company::IsValidAiID(ai_index)) return {};
376 return Company::Get(ai_index)->ai_info->GetName();
377}
378
380static constexpr NWidgetPart _framerate_window_widgets[] = {
382 NWidget(WWT_CLOSEBOX, COLOUR_GREY),
383 NWidget(WWT_CAPTION, COLOUR_GREY, WID_FRW_CAPTION),
384 NWidget(WWT_SHADEBOX, COLOUR_GREY),
385 NWidget(WWT_STICKYBOX, COLOUR_GREY),
386 EndContainer(),
387 NWidget(WWT_PANEL, COLOUR_GREY),
389 NWidget(WWT_TEXT, INVALID_COLOUR, WID_FRW_RATE_GAMELOOP), SetToolTip(STR_FRAMERATE_RATE_GAMELOOP_TOOLTIP), SetFill(1, 0), SetResize(1, 0),
390 NWidget(WWT_TEXT, INVALID_COLOUR, WID_FRW_RATE_DRAWING), SetToolTip(STR_FRAMERATE_RATE_BLITTER_TOOLTIP), SetFill(1, 0), SetResize(1, 0),
391 NWidget(WWT_TEXT, INVALID_COLOUR, WID_FRW_RATE_FACTOR), SetToolTip(STR_FRAMERATE_SPEED_FACTOR_TOOLTIP), SetFill(1, 0), SetResize(1, 0),
392 EndContainer(),
393 EndContainer(),
395 NWidget(WWT_PANEL, COLOUR_GREY),
398 NWidget(WWT_EMPTY, INVALID_COLOUR, WID_FRW_TIMES_NAMES), SetScrollbar(WID_FRW_SCROLLBAR),
399 NWidget(WWT_EMPTY, INVALID_COLOUR, WID_FRW_TIMES_CURRENT), SetScrollbar(WID_FRW_SCROLLBAR),
400 NWidget(WWT_EMPTY, INVALID_COLOUR, WID_FRW_TIMES_AVERAGE), SetScrollbar(WID_FRW_SCROLLBAR),
401 NWidget(WWT_EMPTY, INVALID_COLOUR, WID_FRW_ALLOCSIZE), SetScrollbar(WID_FRW_SCROLLBAR),
402 EndContainer(),
403 NWidget(WWT_TEXT, INVALID_COLOUR, WID_FRW_INFO_DATA_POINTS), SetFill(1, 0), SetResize(1, 0),
404 EndContainer(),
405 EndContainer(),
407 NWidget(NWID_VSCROLLBAR, COLOUR_GREY, WID_FRW_SCROLLBAR),
408 NWidget(WWT_RESIZEBOX, COLOUR_GREY),
409 EndContainer(),
410 EndContainer(),
411};
412
414 int num_active = 0;
415 int num_displayed = 0;
416
418 StringID strid;
419 uint32_t value;
420
421 inline void SetRate(double value, double target)
422 {
423 const double threshold_good = target * 0.95;
424 const double threshold_bad = target * 2 / 3;
425 this->value = (uint32_t)(value * 100);
426 this->strid = (value > threshold_good) ? STR_FRAMERATE_FPS_GOOD : (value < threshold_bad) ? STR_FRAMERATE_FPS_BAD : STR_FRAMERATE_FPS_WARN;
427 }
428
429 inline void SetTime(double value, double target)
430 {
431 const double threshold_good = target / 3;
432 const double threshold_bad = target;
433 this->value = (uint32_t)(value * 100);
434 this->strid = (value < threshold_good) ? STR_FRAMERATE_MS_GOOD : (value > threshold_bad) ? STR_FRAMERATE_MS_BAD : STR_FRAMERATE_MS_WARN;
435 }
436
437 inline uint32_t GetValue() const { return this->value; }
438 inline uint32_t GetDecimals() const { return 2; }
439 };
440
444 std::array<CachedDecimal, PFE_MAX> times_shortterm{};
445 std::array<CachedDecimal, PFE_MAX> times_longterm{};
446
447 static constexpr int MIN_ELEMENTS = 5;
448
449 FramerateWindow(WindowDesc &desc, WindowNumber number) : Window(desc)
450 {
451 this->InitNested(number);
452 this->UpdateData();
453 this->num_displayed = this->num_active;
454
455 /* Window is always initialised to MIN_ELEMENTS height, resize to contain num_displayed */
456 ResizeWindow(this, 0, (std::max(MIN_ELEMENTS, this->num_displayed) - MIN_ELEMENTS) * GetCharacterHeight(FS_NORMAL));
457 }
458
460 const IntervalTimer<TimerWindow> update_interval = {std::chrono::milliseconds(100), [this](auto) {
461 this->UpdateData();
462 this->SetDirty();
463 }};
464
465 void UpdateData()
466 {
467 double gl_rate = _pf_data[PFE_GAMELOOP].GetRate();
468 this->rate_gameloop.SetRate(gl_rate, _pf_data[PFE_GAMELOOP].expected_rate);
469 this->speed_gameloop.SetRate(gl_rate / _pf_data[PFE_GAMELOOP].expected_rate, 1.0);
470 if (this->IsShaded()) return; // in small mode, this is everything needed
471
473
474 int new_active = 0;
475 for (PerformanceElement e = PFE_FIRST; e < PFE_MAX; e++) {
478 if (_pf_data[e].num_valid > 0) {
479 new_active++;
480 }
481 }
482
483 if (new_active != this->num_active) {
484 this->num_active = new_active;
485 Scrollbar *sb = this->GetScrollbar(WID_FRW_SCROLLBAR);
486 sb->SetCount(this->num_active);
487 sb->SetCapacity(std::min(this->num_displayed, this->num_active));
488 }
489 }
490
491 std::string GetWidgetString(WidgetID widget, StringID stringid) const override
492 {
493 switch (widget) {
494 case WID_FRW_CAPTION:
495 /* When the window is shaded, the caption shows game loop rate and speed factor */
496 if (!this->IsShaded()) return GetString(STR_FRAMERATE_CAPTION);
497
498 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());
499
500 case WID_FRW_RATE_GAMELOOP:
501 return GetString(STR_FRAMERATE_RATE_GAMELOOP, this->rate_gameloop.strid, this->rate_gameloop.GetValue(), this->rate_gameloop.GetDecimals());
502
503 case WID_FRW_RATE_DRAWING:
504 return GetString(STR_FRAMERATE_RATE_BLITTER, this->rate_drawing.strid, this->rate_drawing.GetValue(), this->rate_drawing.GetDecimals());
505
506 case WID_FRW_RATE_FACTOR:
507 return GetString(STR_FRAMERATE_SPEED_FACTOR, this->speed_gameloop.GetValue(), this->speed_gameloop.GetDecimals());
508
509 case WID_FRW_INFO_DATA_POINTS:
510 return GetString(STR_FRAMERATE_DATA_POINTS, NUM_FRAMERATE_POINTS);
511
512 default:
513 return this->Window::GetWidgetString(widget, stringid);
514 }
515 }
516
517 void UpdateWidgetSize(WidgetID widget, Dimension &size, [[maybe_unused]] const Dimension &padding, [[maybe_unused]] Dimension &fill, [[maybe_unused]] Dimension &resize) override
518 {
519 switch (widget) {
520 case WID_FRW_RATE_GAMELOOP:
521 size = GetStringBoundingBox(GetString(STR_FRAMERATE_RATE_GAMELOOP, STR_FRAMERATE_FPS_GOOD, GetParamMaxDigits(6), 2));
522 break;
523 case WID_FRW_RATE_DRAWING:
524 size = GetStringBoundingBox(GetString(STR_FRAMERATE_RATE_BLITTER, STR_FRAMERATE_FPS_GOOD, GetParamMaxDigits(6), 2));
525 break;
526 case WID_FRW_RATE_FACTOR:
527 size = GetStringBoundingBox(GetString(STR_FRAMERATE_SPEED_FACTOR, GetParamMaxDigits(6), 2));
528 break;
529
530 case WID_FRW_TIMES_NAMES: {
531 size.width = 0;
533 resize.width = 0;
534 fill.height = resize.height = GetCharacterHeight(FS_NORMAL);
535 for (PerformanceElement e : DISPLAY_ORDER_PFE) {
536 if (_pf_data[e].num_valid == 0) continue;
537 Dimension line_size;
538 if (e < PFE_AI0) {
539 line_size = GetStringBoundingBox(STR_FRAMERATE_GAMELOOP + e);
540 } else {
541 line_size = GetStringBoundingBox(GetString(STR_FRAMERATE_AI, e - PFE_AI0 + 1, GetAIName(e - PFE_AI0)));
542 }
543 size.width = std::max(size.width, line_size.width);
544 }
545 break;
546 }
547
548 case WID_FRW_TIMES_CURRENT:
549 case WID_FRW_TIMES_AVERAGE:
550 case WID_FRW_ALLOCSIZE: {
551 size = GetStringBoundingBox(STR_FRAMERATE_CURRENT + (widget - WID_FRW_TIMES_CURRENT));
552 Dimension item_size = GetStringBoundingBox(GetString(STR_FRAMERATE_MS_GOOD, GetParamMaxDigits(6), 2));
553 size.width = std::max(size.width, item_size.width);
555 resize.width = 0;
556 fill.height = resize.height = GetCharacterHeight(FS_NORMAL);
557 break;
558 }
559 }
560 }
561
563 void DrawElementTimesColumn(const Rect &r, StringID heading_str, std::span<const CachedDecimal> values) const
564 {
565 const Scrollbar *sb = this->GetScrollbar(WID_FRW_SCROLLBAR);
566 int32_t skip = sb->GetPosition();
567 int drawable = this->num_displayed;
568 int y = r.top;
569 DrawString(r.left, r.right, y, heading_str, TC_FROMSTRING, SA_CENTER, true);
571 for (PerformanceElement e : DISPLAY_ORDER_PFE) {
572 if (_pf_data[e].num_valid == 0) continue;
573 if (skip > 0) {
574 skip--;
575 } else {
576 DrawString(r.left, r.right, y, GetString(values[e].strid, values[e].GetValue(), values[e].GetDecimals()), TC_FROMSTRING, SA_RIGHT | SA_FORCE);
578 drawable--;
579 if (drawable == 0) break;
580 }
581 }
582 }
583
584 void DrawElementAllocationsColumn(const Rect &r) const
585 {
586 const Scrollbar *sb = this->GetScrollbar(WID_FRW_SCROLLBAR);
587 int32_t skip = sb->GetPosition();
588 int drawable = this->num_displayed;
589 int y = r.top;
590 DrawString(r.left, r.right, y, STR_FRAMERATE_MEMORYUSE, TC_FROMSTRING, SA_CENTER, true);
592 for (PerformanceElement e : DISPLAY_ORDER_PFE) {
593 if (_pf_data[e].num_valid == 0) continue;
594 if (skip > 0) {
595 skip--;
596 } else if (e == PFE_GAMESCRIPT || e >= PFE_AI0) {
597 uint64_t value = e == PFE_GAMESCRIPT ? Game::GetInstance()->GetAllocatedMemory() : Company::Get(e - PFE_AI0)->ai_instance->GetAllocatedMemory();
598 DrawString(r.left, r.right, y, GetString(STR_FRAMERATE_BYTES_GOOD, value), TC_FROMSTRING, SA_RIGHT | SA_FORCE);
600 drawable--;
601 if (drawable == 0) break;
602 } else if (e == PFE_SOUND) {
603 DrawString(r.left, r.right, y, GetString(STR_FRAMERATE_BYTES_GOOD, GetSoundPoolAllocatedMemory()), TC_FROMSTRING, SA_RIGHT | SA_FORCE);
605 drawable--;
606 if (drawable == 0) break;
607 } else {
608 /* skip non-script */
610 drawable--;
611 if (drawable == 0) break;
612 }
613 }
614 }
615
616 void DrawWidget(const Rect &r, WidgetID widget) const override
617 {
618 switch (widget) {
619 case WID_FRW_TIMES_NAMES: {
620 /* Render a column of titles for performance element names */
621 const Scrollbar *sb = this->GetScrollbar(WID_FRW_SCROLLBAR);
622 int32_t skip = sb->GetPosition();
623 int drawable = this->num_displayed;
624 int y = r.top + GetCharacterHeight(FS_NORMAL) + WidgetDimensions::scaled.vsep_normal; // first line contains headings in the value columns
625 for (PerformanceElement e : DISPLAY_ORDER_PFE) {
626 if (_pf_data[e].num_valid == 0) continue;
627 if (skip > 0) {
628 skip--;
629 } else {
630 if (e < PFE_AI0) {
631 DrawString(r.left, r.right, y, STR_FRAMERATE_GAMELOOP + e, TC_FROMSTRING, SA_LEFT);
632 } else {
633 DrawString(r.left, r.right, y, GetString(STR_FRAMERATE_AI, e - PFE_AI0 + 1, GetAIName(e - PFE_AI0)), TC_FROMSTRING, SA_LEFT);
634 }
636 drawable--;
637 if (drawable == 0) break;
638 }
639 }
640 break;
641 }
642 case WID_FRW_TIMES_CURRENT:
643 /* Render short-term average values */
644 DrawElementTimesColumn(r, STR_FRAMERATE_CURRENT, this->times_shortterm);
645 break;
646 case WID_FRW_TIMES_AVERAGE:
647 /* Render averages of all recorded values */
648 DrawElementTimesColumn(r, STR_FRAMERATE_AVERAGE, this->times_longterm);
649 break;
650 case WID_FRW_ALLOCSIZE:
651 DrawElementAllocationsColumn(r);
652 break;
653 }
654 }
655
656 void OnClick([[maybe_unused]] Point pt, WidgetID widget, [[maybe_unused]] int click_count) override
657 {
658 switch (widget) {
659 case WID_FRW_TIMES_NAMES:
660 case WID_FRW_TIMES_CURRENT:
661 case WID_FRW_TIMES_AVERAGE: {
662 /* Open time graph windows when clicking detail measurement lines */
663 const Scrollbar *sb = this->GetScrollbar(WID_FRW_SCROLLBAR);
665 if (line != INT32_MAX) {
666 line++;
667 /* Find the visible line that was clicked */
668 for (PerformanceElement e : DISPLAY_ORDER_PFE) {
669 if (_pf_data[e].num_valid > 0) line--;
670 if (line == 0) {
672 break;
673 }
674 }
675 }
676 break;
677 }
678 }
679 }
680
681 void OnResize() override
682 {
683 auto *wid = this->GetWidget<NWidgetResizeBase>(WID_FRW_TIMES_NAMES);
684 this->num_displayed = (wid->current_y - wid->min_y - WidgetDimensions::scaled.vsep_normal) / GetCharacterHeight(FS_NORMAL) - 1; // subtract 1 for headings
685 this->GetScrollbar(WID_FRW_SCROLLBAR)->SetCapacity(this->num_displayed);
686 }
687};
688
689static WindowDesc _framerate_display_desc(
690 WDP_AUTO, "framerate_display", 0, 0,
692 {},
693 _framerate_window_widgets
694);
695
696
698static constexpr NWidgetPart _frametime_graph_window_widgets[] = {
700 NWidget(WWT_CLOSEBOX, COLOUR_GREY),
701 NWidget(WWT_CAPTION, COLOUR_GREY, WID_FGW_CAPTION), SetTextStyle(TC_WHITE),
702 NWidget(WWT_STICKYBOX, COLOUR_GREY),
703 EndContainer(),
704 NWidget(WWT_PANEL, COLOUR_GREY),
706 NWidget(WWT_EMPTY, INVALID_COLOUR, WID_FGW_GRAPH),
707 EndContainer(),
708 EndContainer(),
709};
710
714
717
718 FrametimeGraphWindow(WindowDesc &desc, WindowNumber number) : Window(desc), element(static_cast<PerformanceElement>(number))
719 {
720 this->InitNested(number);
721 this->UpdateScale();
722 }
723
724 std::string GetWidgetString(WidgetID widget, StringID stringid) const override
725 {
726 switch (widget) {
727 case WID_FGW_CAPTION:
728 if (this->element < PFE_AI0) {
729 return GetString(STR_FRAMETIME_CAPTION_GAMELOOP + this->element);
730 }
731 return GetString(STR_FRAMETIME_CAPTION_AI, this->element - PFE_AI0 + 1, GetAIName(this->element - PFE_AI0));
732
733 default:
734 return this->Window::GetWidgetString(widget, stringid);
735 }
736 }
737
738 void UpdateWidgetSize(WidgetID widget, Dimension &size, [[maybe_unused]] const Dimension &padding, [[maybe_unused]] Dimension &fill, [[maybe_unused]] Dimension &resize) override
739 {
740 if (widget == WID_FGW_GRAPH) {
741 Dimension size_ms_label = GetStringBoundingBox(GetString(STR_FRAMERATE_GRAPH_MILLISECONDS, 100));
742 Dimension size_s_label = GetStringBoundingBox(GetString(STR_FRAMERATE_GRAPH_SECONDS, 100));
743
744 /* Size graph in height to fit at least 10 vertical labels with space between, or at least 100 pixels */
745 graph_size.height = std::max<uint>(ScaleGUITrad(100), 10 * (size_ms_label.height + WidgetDimensions::scaled.vsep_normal));
746 /* Always 2:1 graph area */
747 graph_size.width = 2 * graph_size.height;
748 size = graph_size;
749
750 size.width += size_ms_label.width + WidgetDimensions::scaled.hsep_normal;
751 size.height += size_s_label.height + WidgetDimensions::scaled.vsep_normal;
752 }
753 }
754
755 void SelectHorizontalScale(TimingMeasurement range)
756 {
757 /* 60 Hz graphical drawing results in a value of approximately TIMESTAMP_PRECISION,
758 * this lands exactly on the scale = 2 vs scale = 4 boundary.
759 * To avoid excessive switching of the horizontal scale, bias these performance
760 * categories away from this scale boundary. */
761 if (this->element == PFE_DRAWING || this->element == PFE_DRAWWORLD) range += (range / 2);
762
763 /* Determine horizontal scale based on period covered by 60 points
764 * (slightly less than 2 seconds at full game speed) */
765 struct ScaleDef { TimingMeasurement range; int scale; };
766 static const std::initializer_list<ScaleDef> hscales = {
767 { TIMESTAMP_PRECISION * 120, 60 },
768 { TIMESTAMP_PRECISION * 10, 20 },
769 { TIMESTAMP_PRECISION * 5, 10 },
770 { TIMESTAMP_PRECISION * 3, 4 },
771 { TIMESTAMP_PRECISION * 1, 2 },
772 };
773 for (const auto &sc : hscales) {
774 if (range < sc.range) this->horizontal_scale = sc.scale;
775 }
776 }
777
778 void SelectVerticalScale(TimingMeasurement range)
779 {
780 /* Determine vertical scale based on peak value (within the horizontal scale + a bit) */
781 static const std::initializer_list<TimingMeasurement> vscales = {
791 };
792 for (const auto &sc : vscales) {
793 if (range < sc) this->vertical_scale = (int)sc;
794 }
795 }
796
799 {
800 const auto &durations = _pf_data[this->element].durations;
801 const auto &timestamps = _pf_data[this->element].timestamps;
802 int num_valid = _pf_data[this->element].num_valid;
803 int point = _pf_data[this->element].prev_index;
804
805 TimingMeasurement lastts = timestamps[point];
806 TimingMeasurement time_sum = 0;
807 TimingMeasurement peak_value = 0;
808 int count = 0;
809
810 /* Sensible default for when too few measurements are available */
811 this->horizontal_scale = 4;
812
813 for (int i = 1; i < num_valid; i++) {
814 point--;
815 if (point < 0) point = NUM_FRAMERATE_POINTS - 1;
816
817 TimingMeasurement value = durations[point];
819 /* Skip gaps in data by pretending time is continuous across them */
820 lastts = timestamps[point];
821 continue;
822 }
823 if (value > peak_value) peak_value = value;
824 count++;
825
826 /* Accumulate period of time covered by data */
827 time_sum += lastts - timestamps[point];
828 lastts = timestamps[point];
829
830 /* Enough data to select a range and get decent data density */
831 if (count == 60) this->SelectHorizontalScale(time_sum);
832
833 /* End when enough points have been collected and the horizontal scale has been exceeded */
834 if (count >= 60 && time_sum >= (this->horizontal_scale + 2) * TIMESTAMP_PRECISION / 2) break;
835 }
836
837 this->SelectVerticalScale(peak_value);
838 }
839
841 const IntervalTimer<TimerWindow> update_interval = {std::chrono::milliseconds(500), [this](auto) {
842 this->UpdateScale();
843 }};
844
845 void OnRealtimeTick([[maybe_unused]] uint delta_ms) override
846 {
847 this->SetDirty();
848 }
849
851 template <typename T>
852 static inline T Scinterlate(T dst_min, T dst_max, T src_min, T src_max, T value)
853 {
854 T dst_diff = dst_max - dst_min;
855 T src_diff = src_max - src_min;
856 return (value - src_min) * dst_diff / src_diff + dst_min;
857 }
858
859 void DrawWidget(const Rect &r, WidgetID widget) const override
860 {
861 if (widget == WID_FGW_GRAPH) {
862 const auto &durations = _pf_data[this->element].durations;
863 const auto &timestamps = _pf_data[this->element].timestamps;
864 int point = _pf_data[this->element].prev_index;
865
866 const int x_zero = r.right - (int)this->graph_size.width;
867 const int x_max = r.right;
868 const int y_zero = r.top + (int)this->graph_size.height;
869 const int y_max = r.top;
870 const PixelColour c_grid = PC_DARK_GREY;
871 const PixelColour c_lines = PC_BLACK;
872 const PixelColour c_peak = PC_DARK_RED;
873
874 const TimingMeasurement draw_horz_scale = (TimingMeasurement)this->horizontal_scale * TIMESTAMP_PRECISION / 2;
875 const TimingMeasurement draw_vert_scale = (TimingMeasurement)this->vertical_scale;
876
877 /* Number of \c horizontal_scale units in each horizontal division */
878 const uint horz_div_scl = (this->horizontal_scale <= 20) ? 1 : 10;
879 /* Number of divisions of the horizontal axis */
880 const uint horz_divisions = this->horizontal_scale / horz_div_scl;
881 /* Number of divisions of the vertical axis */
882 const uint vert_divisions = 10;
883
884 /* Draw division lines and labels for the vertical axis */
885 for (uint division = 0; division < vert_divisions; division++) {
886 int y = Scinterlate(y_zero, y_max, 0, (int)vert_divisions, (int)division);
887 GfxDrawLine(x_zero, y, x_max, y, c_grid);
888 if (division % 2 == 0) {
889 if ((TimingMeasurement)this->vertical_scale > TIMESTAMP_PRECISION) {
891 GetString(STR_FRAMERATE_GRAPH_SECONDS, this->vertical_scale * division / 10 / TIMESTAMP_PRECISION),
892 TC_GREY, SA_RIGHT | SA_FORCE, false, FS_SMALL);
893 } else {
895 GetString(STR_FRAMERATE_GRAPH_MILLISECONDS, this->vertical_scale * division / 10 * 1000 / TIMESTAMP_PRECISION),
896 TC_GREY, SA_RIGHT | SA_FORCE, false, FS_SMALL);
897 }
898 }
899 }
900 /* Draw division lines and labels for the horizontal axis */
901 for (uint division = horz_divisions; division > 0; division--) {
902 int x = Scinterlate(x_zero, x_max, 0, (int)horz_divisions, (int)horz_divisions - (int)division);
903 GfxDrawLine(x, y_max, x, y_zero, c_grid);
904 if (division % 2 == 0) {
905 DrawString(x, x_max, y_zero + WidgetDimensions::scaled.vsep_normal,
906 GetString(STR_FRAMERATE_GRAPH_SECONDS, division * horz_div_scl / 2),
907 TC_GREY, SA_LEFT | SA_FORCE, false, FS_SMALL);
908 }
909 }
910
911 /* Position of last rendered data point */
912 Point lastpoint = {
913 x_max,
914 (int)Scinterlate<int64_t>(y_zero, y_max, 0, this->vertical_scale, durations[point])
915 };
916 /* Timestamp of last rendered data point */
917 TimingMeasurement lastts = timestamps[point];
918
919 TimingMeasurement peak_value = 0;
920 Point peak_point = { 0, 0 };
921 TimingMeasurement value_sum = 0;
922 TimingMeasurement time_sum = 0;
923 int points_drawn = 0;
924
925 for (int i = 1; i < NUM_FRAMERATE_POINTS; i++) {
926 point--;
927 if (point < 0) point = NUM_FRAMERATE_POINTS - 1;
928
929 TimingMeasurement value = durations[point];
931 /* Skip gaps in measurements, pretend the data points on each side are continuous */
932 lastts = timestamps[point];
933 continue;
934 }
935
936 /* Use total time period covered for value along horizontal axis */
937 time_sum += lastts - timestamps[point];
938 lastts = timestamps[point];
939 /* Stop if past the width of the graph */
940 if (time_sum > draw_horz_scale) break;
941
942 /* Draw line from previous point to new point */
943 Point newpoint = {
944 (int)Scinterlate<int64_t>(x_zero, x_max, 0, (int64_t)draw_horz_scale, (int64_t)draw_horz_scale - (int64_t)time_sum),
945 (int)Scinterlate<int64_t>(y_zero, y_max, 0, (int64_t)draw_vert_scale, (int64_t)value)
946 };
947 if (newpoint.x > lastpoint.x) continue; // don't draw backwards
948 GfxDrawLine(lastpoint.x, lastpoint.y, newpoint.x, newpoint.y, c_lines);
949 lastpoint = newpoint;
950
951 /* Record peak and average value across graphed data */
952 value_sum += value;
953 points_drawn++;
954 if (value > peak_value) {
955 peak_value = value;
956 peak_point = newpoint;
957 }
958 }
959
960 /* If the peak value is significantly larger than the average, mark and label it */
961 if (points_drawn > 0 && peak_value > TIMESTAMP_PRECISION / 100 && 2 * peak_value > 3 * value_sum / points_drawn) {
962 TextColour tc_peak = c_peak.ToTextColour();
963 GfxFillRect(peak_point.x - 1, peak_point.y - 1, peak_point.x + 1, peak_point.y + 1, c_peak);
964 uint64_t value = peak_value * 1000 / TIMESTAMP_PRECISION;
965 int label_y = std::max(y_max, peak_point.y - GetCharacterHeight(FS_SMALL));
966 if (peak_point.x - x_zero > (int)this->graph_size.width / 2) {
967 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);
968 } else {
969 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);
970 }
971 }
972 }
973 }
974};
975
976static WindowDesc _frametime_graph_window_desc(
977 WDP_AUTO, "frametime_graph", 140, 90,
979 {},
980 _frametime_graph_window_widgets
981);
982
983
984
987{
988 AllocateWindowDescFront<FramerateWindow>(_framerate_display_desc, 0);
989}
990
993{
994 if (elem < PFE_FIRST || elem >= PFE_MAX) return; // maybe warn?
995 AllocateWindowDescFront<FrametimeGraphWindow>(_frametime_graph_window_desc, elem);
996}
997
1000{
1001 const int count1 = NUM_FRAMERATE_POINTS / 8;
1002 const int count2 = NUM_FRAMERATE_POINTS / 4;
1003 const int count3 = NUM_FRAMERATE_POINTS / 1;
1004
1005 IConsolePrint(TC_SILVER, "Based on num. data points: {} {} {}", count1, count2, count3);
1006
1007 static const std::array<std::string_view, PFE_MAX> MEASUREMENT_NAMES = {
1008 "Game loop",
1009 " GL station ticks",
1010 " GL train ticks",
1011 " GL road vehicle ticks",
1012 " GL ship ticks",
1013 " GL aircraft ticks",
1014 " GL landscape ticks",
1015 " GL link graph delays",
1016 "Drawing",
1017 " Viewport drawing",
1018 "Video output",
1019 "Sound mixing",
1020 "AI/GS scripts total",
1021 "Game script",
1022 };
1023 std::string ai_name_buf;
1024
1025 bool printed_anything = false;
1026
1027 for (const auto &e : { PFE_GAMELOOP, PFE_DRAWING, PFE_VIDEO }) {
1028 auto &pf = _pf_data[e];
1029 if (pf.num_valid == 0) continue;
1030 IConsolePrint(TC_GREEN, "{} rate: {:.2f}fps (expected: {:.2f}fps)",
1031 MEASUREMENT_NAMES[e],
1032 pf.GetRate(),
1033 pf.expected_rate);
1034 printed_anything = true;
1035 }
1036
1037 for (PerformanceElement e = PFE_FIRST; e < PFE_MAX; e++) {
1038 auto &pf = _pf_data[e];
1039 if (pf.num_valid == 0) continue;
1040 std::string_view name;
1041 if (e < PFE_AI0) {
1042 name = MEASUREMENT_NAMES[e];
1043 } else {
1044 ai_name_buf = fmt::format("AI {} {}", e - PFE_AI0 + 1, GetAIName(e - PFE_AI0));
1045 name = ai_name_buf;
1046 }
1047 IConsolePrint(TC_LIGHT_BLUE, "{} times: {:.2f}ms {:.2f}ms {:.2f}ms",
1048 name,
1049 pf.GetAverageDurationMilliseconds(count1),
1050 pf.GetAverageDurationMilliseconds(count2),
1051 pf.GetAverageDurationMilliseconds(count3));
1052 printed_anything = true;
1053 }
1054
1055 if (!printed_anything) {
1056 IConsolePrint(CC_ERROR, "No performance measurements have been taken yet.");
1057 }
1058}
1059
1068{
1069 if (_sound_perf_pending.load(std::memory_order_acquire)) {
1070 std::lock_guard lk(_sound_perf_lock);
1071 for (size_t i = 0; i < _sound_perf_measurements.size(); i += 2) {
1072 _pf_data[PFE_SOUND].Add(_sound_perf_measurements[i], _sound_perf_measurements[i + 1]);
1073 }
1074 _sound_perf_measurements.clear();
1075 _sound_perf_pending.store(false, std::memory_order_relaxed);
1076 }
1077}
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:96
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:2423
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
int vsep_normal
Normal vertical spacing.
Definition window_gui.h:58
static const WidgetDimensions unscaled
Unscaled widget dimensions.
Definition window_gui.h:93
int hsep_normal
Normal horizontal spacing.
Definition window_gui.h:61
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:895
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:666
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:966
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.
GUISettings gui
settings related to the GUI
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
uint16_t refresh_rate
How often we refresh the screen (time between draw-ticks).
Partial widget specification to allow NWidgets to be written nested.
Colour for pixel/line drawing.
Definition gfx_type.h:405
static Titem * Get(auto index)
Returns Titem with given index.
Specification of a rectangle with absolute coordinates of all edges.
High level window description.
Definition window_gui.h:167
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:504
ResizeInfo resize
Resize information.
Definition window_gui.h:314
bool IsShaded() const
Is window shaded currently?
Definition window_gui.h:559
void InitNested(WindowNumber number=0)
Perform complete initialization of the Window with nested widgets, to allow use.
Definition window.cpp:1802
const Scrollbar * GetScrollbar(WidgetID widnum) const
Return the Scrollbar to a widget index.
Definition window.cpp:313
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.
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:67
@ WWT_PANEL
Simple depressed panel.
Definition widget_type.h:40
@ WWT_STICKYBOX
Sticky box (at top-right of a window, after WWT_DEFSIZEBOX)
Definition widget_type.h:58
@ WWT_SHADEBOX
Shade box (at top-right of a window, between WWT_DEBUGBOX and WWT_DEFSIZEBOX)
Definition widget_type.h:56
@ WWT_CAPTION
Window caption (window title between closebox and stickybox)
Definition widget_type.h:53
@ NWID_VSCROLLBAR
Vertical scrollbar.
Definition widget_type.h:77
@ NWID_VERTICAL
Vertical container.
Definition widget_type.h:69
@ WWT_CLOSEBOX
Close box (at top-left of a window)
Definition widget_type.h:61
@ WWT_EMPTY
Empty widget, place holder to reserve space in widget tree.
Definition widget_type.h:38
@ WWT_RESIZEBOX
Resize box (normally at bottom-right of a window)
Definition widget_type.h:60
@ WWT_TEXT
Pure simple text.
Definition widget_type.h:50
void ResizeWindow(Window *w, int delta_x, int delta_y, bool clamp_to_screen, bool schedule_resize)
Resize the window.
Definition window.cpp:2078
Window functions not directly related to making/drawing windows.
Functions, definitions and such used only by the GUI.
@ WDP_AUTO
Find a place automatically.
Definition window_gui.h: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.