36#include "table/strings.h"
40static std::mutex _sound_perf_lock;
41static std::atomic<bool> _sound_perf_pending;
42static std::vector<TimingMeasurement> _sound_perf_measurements;
59 std::array<TimingMeasurement, NUM_FRAMERATE_POINTS> durations{};
61 std::array<TimingMeasurement, NUM_FRAMERATE_POINTS> timestamps{};
63 double expected_rate = 0;
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;
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;
105 this->acc_duration = 0;
106 this->acc_timestamp = start_time;
112 this->acc_duration += duration;
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;
124 this->num_valid += 1;
131 count = std::min(count, this->num_valid);
133 int first_point = this->prev_index - count;
138 for (
int i = first_point; i < first_point + count; i++) {
140 if (d != INVALID_DURATION) {
148 if (count == 0)
return 0;
156 int point = this->prev_index;
157 int last_point = this->next_index - this->num_valid;
171 while (point != last_point) {
173 if (this->durations[point] != INVALID_DURATION) {
174 total += last - this->timestamps[point];
177 last = this->timestamps[point];
183 if (total == 0 || count == 0)
return 0;
238 using namespace std::chrono;
239 return (
TimingMeasurement)time_point_cast<microseconds>(high_resolution_clock::now()).time_since_epoch().count();
261 for (uint e =
PFE_AI0; e <
PFE_MAX; e++) any_active |= _pf_data[e].num_valid > 0;
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);
373static std::string_view GetAIName(
int ai_index)
380static constexpr NWidgetPart _framerate_window_widgets[] = {
415 int num_displayed = 0;
421 inline void SetRate(
double value,
double target)
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;
429 inline void SetTime(
double value,
double target)
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;
437 inline uint32_t GetValue()
const {
return this->value; }
438 inline uint32_t GetDecimals()
const {
return 2; }
453 this->num_displayed = this->num_active;
483 if (new_active != this->num_active) {
484 this->num_active = new_active;
487 sb->
SetCapacity(std::min(this->num_displayed, this->num_active));
494 case WID_FRW_CAPTION:
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());
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());
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());
506 case WID_FRW_RATE_FACTOR:
507 return GetString(STR_FRAMERATE_SPEED_FACTOR, this->
speed_gameloop.GetValue(), this->speed_gameloop.GetDecimals());
509 case WID_FRW_INFO_DATA_POINTS:
520 case WID_FRW_RATE_GAMELOOP:
523 case WID_FRW_RATE_DRAWING:
526 case WID_FRW_RATE_FACTOR:
530 case WID_FRW_TIMES_NAMES: {
536 if (
_pf_data[e].num_valid == 0)
continue;
543 size.width = std::max(size.width, line_size.width);
548 case WID_FRW_TIMES_CURRENT:
549 case WID_FRW_TIMES_AVERAGE:
550 case WID_FRW_ALLOCSIZE: {
553 size.width = std::max(size.width, item_size.width);
567 int drawable = this->num_displayed;
572 if (
_pf_data[e].num_valid == 0)
continue;
579 if (drawable == 0)
break;
584 void DrawElementAllocationsColumn(
const Rect &r)
const
588 int drawable = this->num_displayed;
593 if (
_pf_data[e].num_valid == 0)
continue;
601 if (drawable == 0)
break;
606 if (drawable == 0)
break;
611 if (drawable == 0)
break;
619 case WID_FRW_TIMES_NAMES: {
623 int drawable = this->num_displayed;
626 if (
_pf_data[e].num_valid == 0)
continue;
631 DrawString(r.left, r.right, y, STR_FRAMERATE_GAMELOOP + e, TC_FROMSTRING,
SA_LEFT);
637 if (drawable == 0)
break;
642 case WID_FRW_TIMES_CURRENT:
646 case WID_FRW_TIMES_AVERAGE:
650 case WID_FRW_ALLOCSIZE:
651 DrawElementAllocationsColumn(r);
659 case WID_FRW_TIMES_NAMES:
660 case WID_FRW_TIMES_CURRENT:
661 case WID_FRW_TIMES_AVERAGE: {
665 if (line != INT32_MAX) {
669 if (
_pf_data[e].num_valid > 0) line--;
683 auto *wid = this->GetWidget<NWidgetResizeBase>(WID_FRW_TIMES_NAMES);
690 WDP_AUTO,
"framerate_display", 0, 0,
693 _framerate_window_widgets
698static constexpr NWidgetPart _frametime_graph_window_widgets[] = {
727 case WID_FGW_CAPTION:
740 if (widget == WID_FGW_GRAPH) {
766 static const std::initializer_list<ScaleDef> hscales = {
773 for (
const auto &sc : hscales) {
774 if (range < sc.range) this->horizontal_scale = sc.scale;
781 static const std::initializer_list<TimingMeasurement> vscales = {
792 for (
const auto &sc : vscales) {
793 if (range < sc) this->vertical_scale = (int)sc;
811 this->horizontal_scale = 4;
813 for (
int i = 1; i < num_valid; i++) {
820 lastts = timestamps[point];
823 if (value > peak_value) peak_value = value;
827 time_sum += lastts - timestamps[point];
828 lastts = timestamps[point];
831 if (count == 60) this->SelectHorizontalScale(time_sum);
834 if (count >= 60 && time_sum >= (this->horizontal_scale + 2) *
TIMESTAMP_PRECISION / 2)
break;
837 this->SelectVerticalScale(peak_value);
851 template <
typename T>
852 static inline T
Scinterlate(T dst_min, T dst_max, T src_min, T src_max, T value)
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;
852 static inline T
Scinterlate(T dst_min, T dst_max, T src_min, T src_max, T value) {
…}
861 if (widget == WID_FGW_GRAPH) {
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;
878 const uint horz_div_scl = (this->horizontal_scale <= 20) ? 1 : 10;
880 const uint horz_divisions = this->horizontal_scale / horz_div_scl;
882 const uint vert_divisions = 10;
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) {
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) {
906 GetString(STR_FRAMERATE_GRAPH_SECONDS, division * horz_div_scl / 2),
914 (int)Scinterlate<int64_t>(y_zero, y_max, 0, this->vertical_scale, durations[point])
920 Point peak_point = { 0, 0 };
923 int points_drawn = 0;
932 lastts = timestamps[point];
937 time_sum += lastts - timestamps[point];
938 lastts = timestamps[point];
940 if (time_sum > draw_horz_scale)
break;
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)
947 if (newpoint.x > lastpoint.x)
continue;
948 GfxDrawLine(lastpoint.x, lastpoint.y, newpoint.x, newpoint.y, c_lines);
949 lastpoint = newpoint;
954 if (value > peak_value) {
956 peak_point = newpoint;
961 if (points_drawn > 0 && peak_value >
TIMESTAMP_PRECISION / 100 && 2 * peak_value > 3 * value_sum / points_drawn) {
963 GfxFillRect(peak_point.x - 1, peak_point.y - 1, peak_point.x + 1, peak_point.y + 1, c_peak);
966 if (peak_point.x - x_zero > (
int)this->graph_size.width / 2) {
976static WindowDesc _frametime_graph_window_desc(
977 WDP_AUTO,
"frametime_graph", 140, 90,
980 _frametime_graph_window_widgets
988 AllocateWindowDescFront<FramerateWindow>(_framerate_display_desc, 0);
994 if (elem < PFE_FIRST || elem >=
PFE_MAX)
return;
995 AllocateWindowDescFront<FrametimeGraphWindow>(_frametime_graph_window_desc, elem);
1005 IConsolePrint(TC_SILVER,
"Based on num. data points: {} {} {}", count1, count2, count3);
1007 static const std::array<std::string_view, PFE_MAX> MEASUREMENT_NAMES = {
1009 " GL station ticks",
1011 " GL road vehicle ticks",
1013 " GL aircraft ticks",
1014 " GL landscape ticks",
1015 " GL link graph delays",
1017 " Viewport drawing",
1020 "AI/GS scripts total",
1023 std::string ai_name_buf;
1025 bool printed_anything =
false;
1029 if (pf.num_valid == 0)
continue;
1030 IConsolePrint(TC_GREEN,
"{} rate: {:.2f}fps (expected: {:.2f}fps)",
1031 MEASUREMENT_NAMES[e],
1034 printed_anything =
true;
1039 if (pf.num_valid == 0)
continue;
1040 std::string_view name;
1042 name = MEASUREMENT_NAMES[e];
1044 ai_name_buf = fmt::format(
"AI {} {}", e -
PFE_AI0 + 1, GetAIName(e -
PFE_AI0));
1047 IConsolePrint(TC_LIGHT_BLUE,
"{} times: {:.2f}ms {:.2f}ms {:.2f}ms",
1049 pf.GetAverageDurationMilliseconds(count1),
1050 pf.GetAverageDurationMilliseconds(count2),
1051 pf.GetAverageDurationMilliseconds(count3));
1052 printed_anything =
true;
1055 if (!printed_anything) {
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) {
1074 _sound_perf_measurements.clear();
1075 _sound_perf_pending.store(
false, std::memory_order_relaxed);
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.
An interval timer will fire every interval, and will continue to fire until it is deleted.
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.
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.
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.
Base functions for all Games.
The GameInstance tracks games.
Dimension GetStringBoundingBox(std::string_view str, FontSize start_fontsize)
Return the string dimension in pixels.
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.
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.
Functions related to the gfx engine.
@ FS_SMALL
Index of the small font in the font tables.
@ FS_NORMAL
Index of the normal font in the font tables.
@ SA_LEFT
Left align the text.
@ SA_RIGHT
Right align the text (must be a single bit).
@ SA_FORCE
Force the alignment, i.e. don't swap for RTL languages.
@ SA_CENTER
Center both horizontally and vertically.
TextColour
Colour of the strings, see _string_colourmap in table/string_colours.h or docs/ottd-colourtext-palett...
@ TC_IS_PALETTE_COLOUR
Colour value is already a real palette colour index, not an index of a StringColour.
static const uint MILLISECONDS_PER_TICK
The number of milliseconds per game tick.
void SetDirty() const
Mark entire window as dirty (in need of re-paint)
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 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.
uint64_t GetParamMaxDigits(uint count, FontSize size)
Get some number that is suitable for string size computations.
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).
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.
Number to differentiate different windows of the same class.
Data structure for an opened window.
virtual std::string GetWidgetString(WidgetID widget, StringID stringid) const
Get the raw string for a widget.
ResizeInfo resize
Resize information.
bool IsShaded() const
Is window shaded currently?
void InitNested(WindowNumber number=0)
Perform complete initialization of the Window with nested widgets, to allow use.
const Scrollbar * GetScrollbar(WidgetID widnum) const
Return the Scrollbar to a widget index.
Definition of Interval and OneShot timers.
Definition of the Window system.
void ResizeWindow(Window *w, int delta_x, int delta_y, bool clamp_to_screen, bool schedule_resize)
Resize the window.
Window functions not directly related to making/drawing windows.
Functions, definitions and such used only by the GUI.
@ WDP_AUTO
Find a place automatically.
@ WC_FRAMETIME_GRAPH
Frame time graph; Window numbers:
@ WC_NONE
No window, redirects to WC_MAIN_WINDOW.
@ WC_FRAMERATE_DISPLAY
Framerate display; Window numbers:
Functions related to zooming.