OpenTTD Source 20250612-master-gb012d9e3dc
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;
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;
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 int c_grid = PC_DARK_GREY;
871 const int c_lines = PC_BLACK;
872 const int 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 = (TextColour)(TC_IS_PALETTE_COLOUR | c_peak);
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:2434
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:77
void ShowFramerateWindow()
Open the general framerate window.
static TimingMeasurement GetPerformanceTimer()
Return a timestamp with TIMESTAMP_PRECISION ticks per second precision.
void ProcessPendingPerformanceMeasurements()
This drains the PFE_SOUND measurement data queue into _pf_data.
void ShowFrametimeGraphWindow(PerformanceElement elem)
Open a graph window for a performance element.
void ConPrintFramerate()
Print performance statistics to game console.
Types for recording game performance data.
PerformanceElement
Elements of game performance that can be measured.
@ PFE_AI6
AI execution for player slot 7.
@ PFE_AI1
AI execution for player slot 2.
@ PFE_GAMELOOP
Speed of gameloop processing.
@ PFE_AI9
AI execution for player slot 10.
@ PFE_GL_SHIPS
Time spent processing ships.
@ PFE_AI3
AI execution for player slot 4.
@ PFE_AI4
AI execution for player slot 5.
@ PFE_AI11
AI execution for player slot 12.
@ PFE_AI8
AI execution for player slot 9.
@ PFE_GAMESCRIPT
Game script execution.
@ PFE_VIDEO
Speed of painting drawn video buffer.
@ PFE_GL_LINKGRAPH
Time spent waiting for link graph background jobs.
@ PFE_AI13
AI execution for player slot 14.
@ PFE_AI7
AI execution for player slot 8.
@ PFE_GL_AIRCRAFT
Time spent processing aircraft.
@ PFE_GL_ECONOMY
Time spent processing cargo movement.
@ PFE_AI0
AI execution for player slot 1.
@ PFE_DRAWING
Speed of drawing world and GUI.
@ PFE_AI12
AI execution for player slot 13.
@ PFE_AI2
AI execution for player slot 3.
@ PFE_AI10
AI execution for player slot 11.
@ PFE_GL_LANDSCAPE
Time spent processing other world features.
@ PFE_GL_ROADVEHS
Time spend processing road vehicles.
@ PFE_GL_TRAINS
Time spent processing trains.
@ PFE_MAX
End of enum, must be last.
@ PFE_ALLSCRIPTS
Sum of all GS/AI scripts.
@ PFE_SOUND
Speed of mixing audio samples.
@ PFE_DRAWWORLD
Time spent drawing world viewports in GUI.
@ PFE_AI14
AI execution for player slot 15.
@ PFE_AI5
AI execution for player slot 6.
uint64_t TimingMeasurement
Type used to hold a performance timing measurement.
Types related to the framerate windows widgets.
Base functions for all Games.
The GameInstance tracks games.
Dimension GetStringBoundingBox(std::string_view str, FontSize start_fontsize)
Return the string dimension in pixels.
Definition gfx.cpp:887
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:658
void GfxFillRect(int left, int top, int right, int bottom, int colour, FillRectMode mode)
Applies a certain FillRectMode-operation to a rectangle [left, right] x [top, bottom] on the screen.
Definition gfx.cpp:115
Functions related to the gfx engine.
@ FS_SMALL
Index of the small font in the font tables.
Definition gfx_type.h:252
@ FS_NORMAL
Index of the normal font in the font tables.
Definition gfx_type.h:251
@ SA_LEFT
Left align the text.
Definition gfx_type.h:383
@ SA_RIGHT
Right align the text (must be a single bit).
Definition gfx_type.h:385
@ SA_FORCE
Force the alignment, i.e. don't swap for RTL languages.
Definition gfx_type.h:395
@ SA_CENTER
Center both horizontally and vertically.
Definition gfx_type.h:393
TextColour
Colour of the strings, see _string_colourmap in table/string_colours.h or docs/ottd-colourtext-palett...
Definition gfx_type.h:302
@ TC_IS_PALETTE_COLOUR
Colour value is already a real palette colour index, not an index of a StringColour.
Definition gfx_type.h:325
static const uint MILLISECONDS_PER_TICK
The number of milliseconds per game tick.
Definition gfx_type.h:365
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 NWidget(WidgetType tp, Colours col, WidgetID idx=-1)
Widget part function for starting a new 'real' widget.
constexpr NWidgetPart SetToolTip(StringID tip)
Widget part function for setting tooltip and clearing the widget data.
constexpr NWidgetPart EndContainer()
Widget part function for denoting the end of a container (horizontal, vertical, WWT_FRAME,...
constexpr NWidgetPart 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:955
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 const uint8_t PC_DARK_GREY
Dark grey palette colour.
static const uint8_t PC_BLACK
Black palette colour.
static const uint8_t PC_DARK_RED
Dark red 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:415
uint64_t GetParamMaxDigits(uint count, FontSize size)
Get some number that is suitable for string size computations.
Definition strings.cpp:219
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)?
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.
Coordinates of a point in 2D.
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:503
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:1791
const Scrollbar * GetScrollbar(WidgetID widnum) const
Return the Scrollbar to a widget index.
Definition window.cpp:312
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:2067
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:47
@ WC_FRAMERATE_DISPLAY
Framerate display; Window numbers:
Functions related to zooming.