Display: port FPS viewer to GUI2

Splits the FPS viewer out of the display class and into its own modeless dialog. This serves as a proof-of-concept for further modeless GUI in-game dialogs. Admittedly, it only works because I explicitly disconnect it from the UI event context, since it doesn't need to receive events.

This also introduces a new tracked_drawable interface that encapsulates the fps logic. Any class that inherits from it can have its render stats displayed using the new fps_report dialog.
This commit is contained in:
Charles Dang 2025-03-21 21:32:51 -04:00
parent a46bae0f80
commit a907af7e55
12 changed files with 523 additions and 135 deletions

View File

@ -0,0 +1,54 @@
#textdomain wesnoth-lib
#define _GUI_WIDTH
360 #enddef
[window]
id = "fps_report"
description = "Displays the in-game FPS."
[resolution]
definition = "debug_overlay"
click_dismiss = false
automatic_placement = false
x = 5
y = 35
width = {_GUI_WIDTH}
height = 140
[tooltip]
id = "tooltip"
[/tooltip]
[helptip]
id = "tooltip"
[/helptip]
[grid]
[row]
[column]
horizontal_grow = true
vertical_grow = true
[rich_label]
id = "values"
definition = "monospace"
width = 320
[/rich_label]
[/column]
[/row]
[/grid]
[/resolution]
[/window]
#undef _GUI_WIDTH

View File

@ -0,0 +1,58 @@
#textdomain wesnoth-lib
[window_definition]
id = "debug_overlay"
description = "A simple, semi-transparent rectangle."
[resolution]
left_border = 10
right_border = 13
top_border = 10
bottom_border = 13
[background]
[draw]
[rectangle]
x = 0
y = 0
w = "(width)"
h = "(height)"
fill_color = "0, 0, 0, 160"
[/rectangle]
[/draw]
[/background]
[foreground]
[draw]
[/draw]
[/foreground]
[grid]
[row]
grow_factor = 1
[column]
horizontal_grow = true
vertical_grow = true
[grid]
id = "_window_content_grid"
[/grid]
[/column]
[/row]
[/grid]
[/resolution]
[/window_definition]

View File

@ -553,6 +553,8 @@
<Unit filename="../../src/gui/core/timer.hpp" />
<Unit filename="../../src/gui/core/top_level_drawable.cpp" />
<Unit filename="../../src/gui/core/top_level_drawable.hpp" />
<Unit filename="../../src/gui/core/tracked_drawable.cpp" />
<Unit filename="../../src/gui/core/tracked_drawable.hpp" />
<Unit filename="../../src/gui/core/widget_definition.cpp" />
<Unit filename="../../src/gui/core/widget_definition.hpp" />
<Unit filename="../../src/gui/core/window_builder.cpp" />
@ -627,6 +629,8 @@
<Unit filename="../../src/gui/dialogs/editor/tod_new_schedule.hpp" />
<Unit filename="../../src/gui/dialogs/end_credits.cpp" />
<Unit filename="../../src/gui/dialogs/end_credits.hpp" />
<Unit filename="../../src/gui/dialogs/fps_report.cpp" />
<Unit filename="../../src/gui/dialogs/fps_report.hpp" />
<Unit filename="../../src/gui/dialogs/gui_test_dialog.cpp" />
<Unit filename="../../src/gui/dialogs/gui_test_dialog.hpp" />
<Unit filename="../../src/gui/dialogs/prompt.cpp" />

View File

@ -30,6 +30,7 @@
19B14238AD52EC06ED2094F1 /* tab_container.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 162C4B1E9F7373592D0F3B89 /* tab_container.cpp */; };
1BC74FED857215A162E9E0F2 /* tab_container.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 162C4B1E9F7373592D0F3B89 /* tab_container.cpp */; };
1C3D48879EAC414AE3DB122E /* combobox.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 875E45698F8A8D5B750E7317 /* combobox.cpp */; };
262647058B8E718FB1D8264C /* tracked_drawable.hpp in Headers */ = {isa = PBXBuildFile; fileRef = 832143969FA807F7E6072A0E /* tracked_drawable.hpp */; };
285C4E7A9E891E1DCB215683 /* back_edge_detector.hpp in Headers */ = {isa = PBXBuildFile; fileRef = DA034C90BB2E6C060B0A0B93 /* back_edge_detector.hpp */; };
355942A786D57DD0A6A93E2A /* units_dialog.cpp in Sources */ = {isa = PBXBuildFile; fileRef = CAF74C3AB8D456EA3E756396 /* units_dialog.cpp */; };
362245818DDA4C7E4CC8165A /* charconv.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 0CDC443798CB3254283483D0 /* charconv.cpp */; };
@ -256,6 +257,7 @@
46D60158255AFA7E00E072F0 /* commandline_argv.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 46D60156255AFA7E00E072F0 /* commandline_argv.cpp */; };
46D60159255AFA7E00E072F0 /* commandline_argv.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 46D60156255AFA7E00E072F0 /* commandline_argv.cpp */; };
46D60162255AFDE100E072F0 /* commandline_argv.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 46D60156255AFA7E00E072F0 /* commandline_argv.cpp */; };
46DE42A486EEA7BD55757690 /* fps_report.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 03A840C5A9D9062764009C89 /* fps_report.cpp */; };
46E2D98F25022BF5003D99F3 /* lua_widget.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 46E2D98B25022BF5003D99F3 /* lua_widget.cpp */; };
46E2D99025022BF5003D99F3 /* lua_widget.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 46E2D98B25022BF5003D99F3 /* lua_widget.cpp */; };
46E2D99125022BF6003D99F3 /* lua_widget_attributes.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 46E2D98D25022BF5003D99F3 /* lua_widget_attributes.cpp */; };
@ -651,6 +653,7 @@
6A1F44688986D07EB5DBEF75 /* gui_test_dialog.hpp in Headers */ = {isa = PBXBuildFile; fileRef = 0110429EAA81AED07D53B749 /* gui_test_dialog.hpp */; };
6C4A4F7982769422C51DC3E6 /* reachmap_options.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 85514A7D81C52A912FF68AEB /* reachmap_options.cpp */; };
6D574EACA3483ABEE72819F0 /* statistics_record.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 27764FB68F02032F1C0B6748 /* statistics_record.cpp */; };
70284C5A94BEC79FC449D9E5 /* tracked_drawable.cpp in Sources */ = {isa = PBXBuildFile; fileRef = C5744948AA0B9637D5203B90 /* tracked_drawable.cpp */; };
77D94146A5FA29849D1A9BD8 /* multiline_text.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 0B0F48CE9CF65D9813BE6CDC /* multiline_text.cpp */; };
7A0347D48BDB52B1430D9E79 /* migrate_version_selection.hpp in Headers */ = {isa = PBXBuildFile; fileRef = B3DE4F95AF72C6F6BC37E695 /* migrate_version_selection.hpp */; };
7A7146D7893AA09891352019 /* test_schema_validator.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 7CF14AB694764953E2CB3AF7 /* test_schema_validator.cpp */; };
@ -1145,6 +1148,7 @@
9C6342BC8A95B6D23D384486 /* gui_test_dialog.hpp in Headers */ = {isa = PBXBuildFile; fileRef = 0110429EAA81AED07D53B749 /* gui_test_dialog.hpp */; };
9FE64884AE8121CDBABF7D8A /* preferences.cpp in Sources */ = {isa = PBXBuildFile; fileRef = C1D64406873FEB11E9758A05 /* preferences.cpp */; };
AC4242F78B39C571E34AF48F /* edit_unit.cpp in Sources */ = {isa = PBXBuildFile; fileRef = A05D48F0A2C022FC128C8B3E /* edit_unit.cpp */; };
AC6A496B8F5EC3A612C0E6D6 /* fps_report.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 03A840C5A9D9062764009C89 /* fps_report.cpp */; };
B14E4163984EED844169EF4F /* attributes.hpp in Headers */ = {isa = PBXBuildFile; fileRef = B56948C5B90E74C62F2D078F /* attributes.hpp */; };
B45C431C9B7250C3321F8BC2 /* preferences.hpp in Headers */ = {isa = PBXBuildFile; fileRef = 2CFD4922B64EA6C9F71F71A2 /* preferences.hpp */; };
B508D193100146E300B12852 /* engine_fai.cpp in Sources */ = {isa = PBXBuildFile; fileRef = B508D191100146E300B12852 /* engine_fai.cpp */; };
@ -1297,8 +1301,10 @@
B7344A69B44FC3CFB1DE78BB /* markup.hpp in Headers */ = {isa = PBXBuildFile; fileRef = D7B540678519F0EBD7C19A17 /* markup.hpp */; };
B7B34687A61290490C1616D3 /* tab_container.hpp in Headers */ = {isa = PBXBuildFile; fileRef = BF3B41AF9FEBC9C3A11736A2 /* tab_container.hpp */; };
BC624F2F923836331DCFD078 /* choose_addon.hpp in Headers */ = {isa = PBXBuildFile; fileRef = D9A141EAAE90E98B6F6171D6 /* choose_addon.hpp */; };
BEC947DAA02187CC655F20B3 /* tracked_drawable.cpp in Sources */ = {isa = PBXBuildFile; fileRef = C5744948AA0B9637D5203B90 /* tracked_drawable.cpp */; };
C0454A6592DCD67D93323D9C /* edit_pbl.hpp in Headers */ = {isa = PBXBuildFile; fileRef = B2CC45FEA71445AE817CAA6B /* edit_pbl.hpp */; };
C3854DF5A850564161932EE5 /* test_help_markup.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 3975405BB582CA290366CD21 /* test_help_markup.cpp */; };
C9074CACAE3FC9E937989C6E /* fps_report.hpp in Headers */ = {isa = PBXBuildFile; fileRef = D9044C069F0E385941347EBB /* fps_report.hpp */; };
C93048A9AE576B6AD016DFA4 /* tod_new_schedule.cpp in Sources */ = {isa = PBXBuildFile; fileRef = C61F473D9AC43768A445E218 /* tod_new_schedule.cpp */; };
C9D14DC39B87182A00397A3D /* choose_addon.hpp in Headers */ = {isa = PBXBuildFile; fileRef = D9A141EAAE90E98B6F6171D6 /* choose_addon.hpp */; };
D09A4D40A36568E32D8723F7 /* combobox.hpp in Headers */ = {isa = PBXBuildFile; fileRef = B3534BCB9BB2673B5E513D67 /* combobox.hpp */; };
@ -1313,6 +1319,7 @@
DF974010BB46B844E83F7DDA /* tod_new_schedule.cpp in Sources */ = {isa = PBXBuildFile; fileRef = C61F473D9AC43768A445E218 /* tod_new_schedule.cpp */; };
E1DA41878F0C255769B8239D /* scroll_text.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 755D4555A1DEA29125E7F338 /* scroll_text.cpp */; };
E2F24C0CBC863C3DC8A041EF /* edit_pbl.hpp in Headers */ = {isa = PBXBuildFile; fileRef = B2CC45FEA71445AE817CAA6B /* edit_pbl.hpp */; };
E460406AB8500EE18D2CE16D /* fps_report.hpp in Headers */ = {isa = PBXBuildFile; fileRef = D9044C069F0E385941347EBB /* fps_report.hpp */; };
E60E437B8712EC8D22CA2608 /* addon_server_info.hpp in Headers */ = {isa = PBXBuildFile; fileRef = 1E364CB2B7C9E22650753C72 /* addon_server_info.hpp */; };
E6CF415F9FD04C35A55FB24D /* scroll_text.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 755D4555A1DEA29125E7F338 /* scroll_text.cpp */; };
E79249078ACE777D1E219DED /* choose_addon.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 20E644DC98F26C756364EC2C /* choose_addon.cpp */; };
@ -1459,6 +1466,7 @@
ECFB9FA8193BFAD900146ED0 /* carryover.cpp in Sources */ = {isa = PBXBuildFile; fileRef = ECFB9FA7193BFAD900146ED0 /* carryover.cpp */; };
ECFB9FAA193BFB4B00146ED0 /* game_board.cpp in Sources */ = {isa = PBXBuildFile; fileRef = ECFB9FA9193BFB4B00146ED0 /* game_board.cpp */; };
ECFB9FAC193BFB6E00146ED0 /* rect.cpp in Sources */ = {isa = PBXBuildFile; fileRef = ECFB9FAB193BFB6E00146ED0 /* rect.cpp */; };
ED17477C9FA46C9D7C7BD567 /* tracked_drawable.hpp in Headers */ = {isa = PBXBuildFile; fileRef = 832143969FA807F7E6072A0E /* tracked_drawable.hpp */; };
F00C4D628A6DEFF4F2A66243 /* rich_label.cpp in Sources */ = {isa = PBXBuildFile; fileRef = E5E2430098E9A628933A1DB1 /* rich_label.cpp */; };
F13D4C33BAA4CB9E9AACFCC2 /* spinner.cpp in Sources */ = {isa = PBXBuildFile; fileRef = E4214F3DA80B54080C4B548F /* spinner.cpp */; };
F40A13BC1A3A88BA00C4D071 /* apple_notification.mm in Sources */ = {isa = PBXBuildFile; fileRef = F40A13BB1A3A88BA00C4D071 /* apple_notification.mm */; };
@ -1597,6 +1605,7 @@
000000000000000000000010 /* network_download_file.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = network_download_file.hpp; sourceTree = "<group>"; };
00574699A982AA23F12B39E0 /* edit_pbl_translation.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = edit_pbl_translation.cpp; sourceTree = "<group>"; };
0110429EAA81AED07D53B749 /* gui_test_dialog.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = gui_test_dialog.hpp; sourceTree = "<group>"; };
03A840C5A9D9062764009C89 /* fps_report.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = fps_report.cpp; path = fps_report.cpp; sourceTree = "<group>"; };
0B0F48CE9CF65D9813BE6CDC /* multiline_text.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = multiline_text.cpp; sourceTree = "<group>"; };
0CDC443798CB3254283483D0 /* charconv.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = charconv.cpp; path = charconv.cpp; sourceTree = "<group>"; };
1058C7A1FEA54F0111CA2CBB /* Cocoa.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Cocoa.framework; path = System/Library/Frameworks/Cocoa.framework; sourceTree = SDKROOT; };
@ -2210,6 +2219,7 @@
755D4555A1DEA29125E7F338 /* scroll_text.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = scroll_text.cpp; sourceTree = "<group>"; };
7CF14AB694764953E2CB3AF7 /* test_schema_validator.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = test_schema_validator.cpp; sourceTree = "<group>"; };
7FBD4033B4B52E9424819B5F /* gui_test_dialog.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = gui_test_dialog.cpp; sourceTree = "<group>"; };
832143969FA807F7E6072A0E /* tracked_drawable.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = tracked_drawable.hpp; path = tracked_drawable.hpp; sourceTree = "<group>"; };
84234C54BB84519421FD4136 /* general.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = general.cpp; sourceTree = "<group>"; };
85514A7D81C52A912FF68AEB /* reachmap_options.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = reachmap_options.cpp; path = reachmap_options.cpp; sourceTree = "<group>"; };
875E45698F8A8D5B750E7317 /* combobox.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = combobox.cpp; sourceTree = "<group>"; };
@ -2767,12 +2777,14 @@
B5EE4DE4B73C204AC0666900 /* markup.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = markup.cpp; sourceTree = "<group>"; };
BF3B41AF9FEBC9C3A11736A2 /* tab_container.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = tab_container.hpp; sourceTree = "<group>"; };
C1D64406873FEB11E9758A05 /* preferences.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = preferences.cpp; path = preferences/preferences.cpp; sourceTree = "<group>"; };
C5744948AA0B9637D5203B90 /* tracked_drawable.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = tracked_drawable.cpp; path = tracked_drawable.cpp; sourceTree = "<group>"; };
C61F473D9AC43768A445E218 /* tod_new_schedule.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = tod_new_schedule.cpp; sourceTree = "<group>"; };
C679447D91FD3623CC852FF8 /* edit_pbl_translation.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = edit_pbl_translation.hpp; sourceTree = "<group>"; };
C8FF4A8788B6B7E6BE7AB7BB /* rich_label.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = rich_label.hpp; sourceTree = "<group>"; };
CAF74C3AB8D456EA3E756396 /* units_dialog.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = units_dialog.cpp; path = units_dialog.cpp; sourceTree = "<group>"; };
D4594633BF3F8A06D6AE752F /* prompt.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = prompt.hpp; sourceTree = "<group>"; };
D7B540678519F0EBD7C19A17 /* markup.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = markup.hpp; sourceTree = "<group>"; };
D9044C069F0E385941347EBB /* fps_report.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = fps_report.hpp; path = fps_report.hpp; sourceTree = "<group>"; };
D911474D925FA88D5B856A0E /* test_sdl.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = test_sdl.cpp; sourceTree = "<group>"; };
D9A141EAAE90E98B6F6171D6 /* choose_addon.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = choose_addon.hpp; sourceTree = "<group>"; };
DA034C90BB2E6C060B0A0B93 /* back_edge_detector.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = back_edge_detector.hpp; sourceTree = "<group>"; };
@ -3701,6 +3713,8 @@
46F92C2E2174F6A300602C1C /* window_builder */,
46F92C262174F6A300602C1C /* window_builder.cpp */,
46F92C372174F6A300602C1C /* window_builder.hpp */,
C5744948AA0B9637D5203B90 /* tracked_drawable.cpp */,
832143969FA807F7E6072A0E /* tracked_drawable.hpp */,
);
path = core;
sourceTree = "<group>";
@ -3866,6 +3880,8 @@
8DAD49C8B52CC4E4FD686A60 /* units_dialog.hpp */,
85514A7D81C52A912FF68AEB /* reachmap_options.cpp */,
231C4A6BB2F1A717F0D6E2E2 /* reachmap_options.hpp */,
03A840C5A9D9062764009C89 /* fps_report.cpp */,
D9044C069F0E385941347EBB /* fps_report.hpp */,
);
path = dialogs;
sourceTree = "<group>";
@ -5205,6 +5221,8 @@
48C54CF8AD9615C43EB823E7 /* addon_server_info.hpp in Headers */,
B14E4163984EED844169EF4F /* attributes.hpp in Headers */,
08964907BF0C2F261FC984DC /* reachmap_options.hpp in Headers */,
ED17477C9FA46C9D7C7BD567 /* tracked_drawable.hpp in Headers */,
C9074CACAE3FC9E937989C6E /* fps_report.hpp in Headers */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@ -5231,6 +5249,8 @@
E60E437B8712EC8D22CA2608 /* addon_server_info.hpp in Headers */,
7C4740B081A838A09779FAAA /* attributes.hpp in Headers */,
63B0402A889C6663911DC677 /* reachmap_options.hpp in Headers */,
262647058B8E718FB1D8264C /* tracked_drawable.hpp in Headers */,
E460406AB8500EE18D2CE16D /* fps_report.hpp in Headers */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@ -5968,6 +5988,8 @@
DA2B4478B9C7AB102479C322 /* addon_server_info.cpp in Sources */,
0813449DBC67700714FA3ACD /* attributes.cpp in Sources */,
6C4A4F7982769422C51DC3E6 /* reachmap_options.cpp in Sources */,
BEC947DAA02187CC655F20B3 /* tracked_drawable.cpp in Sources */,
AC6A496B8F5EC3A612C0E6D6 /* fps_report.cpp in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@ -6642,6 +6664,8 @@
144E49509EAC409649899BD4 /* test_lua_ptr.cpp in Sources */,
38C444C497ECBE2DF9BB2319 /* attributes.cpp in Sources */,
E875402885AA34096C34E3B0 /* reachmap_options.cpp in Sources */,
70284C5A94BEC79FC449D9E5 /* tracked_drawable.cpp in Sources */,
46DE42A486EEA7BD55757690 /* fps_report.cpp in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};

View File

@ -162,6 +162,7 @@ gui/core/placer/vertical_list.cpp
gui/core/static_registry.cpp
gui/core/timer.cpp
gui/core/top_level_drawable.cpp
gui/core/tracked_drawable.cpp
gui/core/widget_definition.cpp
gui/core/window_builder.cpp
gui/core/window_builder/helper.cpp
@ -203,6 +204,7 @@ gui/dialogs/file_dialog.cpp
gui/dialogs/file_progress.cpp
gui/dialogs/folder_create.cpp
gui/dialogs/formula_debugger.cpp
gui/dialogs/fps_report.cpp
gui/dialogs/game_cache_options.cpp
gui/dialogs/game_delete.cpp
gui/dialogs/game_load.cpp

View File

@ -65,10 +65,6 @@
#include <numeric>
#include <utility>
#ifdef __cpp_lib_format
#include <format>
#endif
#ifdef _WIN32
#include <windows.h>
#endif
@ -169,10 +165,6 @@ display::display(const display_context* dc,
, map_labels_(new map_labels(nullptr))
, reports_object_(&reports_object)
, scroll_event_("scrolled")
, frametimes_(50)
, fps_counter_()
, fps_start_()
, fps_actual_()
, reportLocations_()
, reportSurfaces_()
, reports_()
@ -196,7 +188,6 @@ display::display(const display_context* dc,
, reach_map_old_()
, reach_map_changed_(true)
, reach_map_team_index_(0)
, fps_handle_(0)
, invalidated_hexes_(0)
, drawn_hexes_(0)
, redraw_observers_()
@ -1290,93 +1281,6 @@ void display::drawing_buffer_commit()
drawing_buffer_.clear();
}
static unsigned calculate_fps(std::chrono::milliseconds frametime)
{
return frametime > 0ms ? 1s / frametime : 999u;
}
void display::update_fps_label()
{
++current_frame_sample_;
constexpr int sample_freq = 10;
if(current_frame_sample_ != sample_freq) {
return;
} else {
current_frame_sample_ = 0;
}
const auto [min_iter, max_iter] = std::minmax_element(frametimes_.begin(), frametimes_.end());
const std::chrono::milliseconds render_avg = std::accumulate(frametimes_.begin(), frametimes_.end(), 0ms) / frametimes_.size();
// NOTE: max FPS corresponds to the *shortest* time between frames (that is, min_iter)
const int avg_fps = calculate_fps(render_avg);
const int max_fps = calculate_fps(*min_iter);
const int min_fps = calculate_fps(*max_iter);
fps_history_.emplace_back(min_fps, avg_fps, max_fps);
// flush out the stored fps values every so often
if(fps_history_.size() == 1000) {
std::string filename = filesystem::get_user_data_dir() + "/fps_log.csv";
auto fps_log = filesystem::ostream_file(filename, std::ios_base::binary | std::ios_base::app);
for(const auto& [min, avg, max] : fps_history_) {
*fps_log << min << "," << avg << "," << max << "\n";
}
fps_history_.clear();
}
if(fps_handle_ != 0) {
font::remove_floating_label(fps_handle_);
fps_handle_ = 0;
}
std::ostringstream stream;
#ifdef __cpp_lib_format
stream << "<tt> " << std::format("{:<5}|{:<5}|{:<5}|{:<5}", "min", "avg", "max", "act") << "</tt>\n";
stream << "<tt>FPS: " << std::format("{:<5}|{:<5}|{:<5}|{:<5}", min_fps, avg_fps, max_fps, fps_actual_) << "</tt>\n";
stream << "<tt>Time: " << std::format("{:5}|{:5}|{:5}", *max_iter, render_avg, *min_iter) << "</tt>\n";
#else
stream << "<tt> min |avg |max |act </tt>\n";
stream << "<tt>FPS: " << std::left << std::setfill(' ') << std::setw(5) << min_fps << '|' << std::setw(5) << avg_fps << '|' << std::setw(5) << max_fps << '|' << std::setw(5) << fps_actual_ << "</tt>\n";
stream << "<tt>Time: " << std::left << std::setfill(' ') << std::setw(5) << max_iter->count() << '|' << std::setw(5) << render_avg.count() << '|' << std::setw(5) << min_iter->count() << "</tt>\n";
#endif
if(game_config::debug) {
stream << "\nhex: " << drawn_hexes_ * 1.0 / sample_freq;
if(drawn_hexes_ != invalidated_hexes_) {
stream << " (" << (invalidated_hexes_ - drawn_hexes_) * 1.0 / sample_freq << ")";
}
}
drawn_hexes_ = 0;
invalidated_hexes_ = 0;
font::floating_label flabel(stream.str());
flabel.set_font_size(14);
flabel.set_color(debug_flag_set(DEBUG_BENCHMARK) ? font::BAD_COLOR : font::NORMAL_COLOR);
flabel.set_position(10, 100);
flabel.set_alignment(font::LEFT_ALIGN);
flabel.set_bg_color({0, 0, 0, float_to_color(0.6)});
flabel.set_border_size(5);
fps_handle_ = font::add_floating_label(flabel);
}
void display::clear_fps_label()
{
if(fps_handle_ != 0) {
font::remove_floating_label(fps_handle_);
fps_handle_ = 0;
drawn_hexes_ = 0;
invalidated_hexes_ = 0;
last_frame_finished_.reset();
}
}
void display::draw_panel(const theme::panel& panel)
{
// Most panels are transparent.
@ -1519,22 +1423,6 @@ void display::set_diagnostic(const std::string& msg)
}
}
void display::update_fps_count()
{
auto now = std::chrono::steady_clock::now();
if(last_frame_finished_) {
frametimes_.push_back(std::chrono::duration_cast<std::chrono::milliseconds>(now - *last_frame_finished_));
}
last_frame_finished_ = now;
++fps_counter_;
if(now - fps_start_ >= 1s) {
fps_start_ = now;
fps_actual_ = std::exchange(fps_counter_, 0);
}
}
const theme::action* display::action_pressed()
{
for(auto i = action_buttons_.begin(); i != action_buttons_.end(); ++i) {
@ -2362,13 +2250,6 @@ void display::draw()
}
drawing_buffer_commit();
}
if(prefs::get().show_fps() || debug_flag_set(DEBUG_BENCHMARK)) {
update_fps_count();
update_fps_label();
} else if(fps_handle_ != 0) {
clear_fps_label();
}
}
void display::update()
@ -2431,6 +2312,9 @@ void display::render()
return;
}
// Update our frametime values
tracked_drawable::update_count();
// render to the offscreen buffer
auto target_setter = draw::set_render_target(front_);
draw();

View File

@ -52,6 +52,7 @@ namespace wb {
#include "font/standard_colors.hpp"
#include "game_config.hpp"
#include "gui/core/top_level_drawable.hpp"
#include "gui/core/tracked_drawable.hpp"
#include "halo.hpp"
#include "picture.hpp" //only needed for enums (!)
#include "key.hpp"
@ -62,8 +63,6 @@ namespace wb {
#include "theme.hpp"
#include "widgets/button.hpp"
#include <boost/circular_buffer.hpp>
#include <bitset>
#include <functional>
#include <chrono>
@ -93,7 +92,7 @@ class gamemap;
/**
* Sort-of-Singleton that many classes, both GUI and non-GUI, use to access the game data.
*/
class display : public gui2::top_level_drawable
class display : public gui2::top_level_drawable, public gui2::tracked_drawable
{
public:
display(const display_context* dc,
@ -465,10 +464,6 @@ public:
terrain_builder& get_builder() {return *builder_;}
void update_fps_label();
void clear_fps_label();
void update_fps_count();
/** Rebuild all dynamic terrain. */
void rebuild_all();
@ -757,13 +752,6 @@ protected:
/** Event raised when the map is being scrolled */
mutable events::generic_event scroll_event_;
boost::circular_buffer<std::chrono::milliseconds> frametimes_;
int current_frame_sample_ = 0;
unsigned int fps_counter_;
std::chrono::steady_clock::time_point fps_start_;
unsigned int fps_actual_;
utils::optional<std::chrono::steady_clock::time_point> last_frame_finished_ = {};
// Not set by the initializer:
std::map<std::string, rect> reportLocations_;
std::map<std::string, texture> reportSurfaces_;
@ -907,8 +895,6 @@ protected:
virtual overlay_map& get_overlays() = 0;
private:
/** Handle for the label which displays frames per second. */
int fps_handle_;
/** Count work done for the debug info displayed under fps */
int invalidated_hexes_;
int drawn_hexes_;

View File

@ -0,0 +1,105 @@
/*
Copyright (C) 2025 - 2025
Part of the Battle for Wesnoth Project https://www.wesnoth.org/
This program 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; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY.
See the COPYING file for more details.
*/
#include "gui/core/tracked_drawable.hpp"
#include "gui/dialogs/fps_report.hpp"
#include "preferences/preferences.hpp"
#include <algorithm>
#include <numeric>
#include <utility>
namespace gui2
{
using namespace std::chrono_literals;
tracked_drawable::tracked_drawable()
: frametimes_(50)
, render_count_(0)
, render_counter_(0)
, last_lap_()
, last_render_(clock::time_point::max())
{
}
tracked_drawable::~tracked_drawable()
{
// Unconditionally close the fps report, since there currently can only be one
// report open at a time. If we need to support multiple reports, we should add
// a way to track its associated drawable and close its companion report here.
gui2::dialogs::fps::hide();
}
void tracked_drawable::process()
{
if(prefs::get().show_fps()) {
gui2::dialogs::fps::show(*this);
} else {
gui2::dialogs::fps::hide();
}
}
auto tracked_drawable::get_info() const -> utils::optional<frame_info>
{
if(frametimes_.empty()) {
return utils::nullopt;
}
using std::chrono::milliseconds;
const auto [min_time, avg_time, max_time] = get_times();
return frame_info {
std::chrono::duration_cast<milliseconds>(min_time),
std::chrono::duration_cast<milliseconds>(avg_time),
std::chrono::duration_cast<milliseconds>(max_time),
// NOTE: max fps corresponds to the *shortest* time between frames, and vice-versa
static_cast<unsigned>(1s / max_time), // min
static_cast<unsigned>(1s / avg_time), // avg
static_cast<unsigned>(1s / min_time), // max
render_count_
};
}
auto tracked_drawable::get_times() const -> times
{
const auto [min_time, max_time]
= std::minmax_element(frametimes_.begin(), frametimes_.end());
const auto total_time
= std::accumulate(frametimes_.begin(), frametimes_.end(), clock::duration{0});
return { *min_time, total_time / frametimes_.size(), *max_time };
}
void tracked_drawable::update_count()
{
auto now = clock::now();
auto elapsed = now - last_render_;
if(elapsed > clock::duration{0}) {
frametimes_.push_back(elapsed);
}
last_render_ = now;
++render_counter_;
if(now - last_lap_ >= 1s) {
last_lap_ = now;
render_count_ = std::exchange(render_counter_, 0);
}
}
} // namespace gui2

View File

@ -0,0 +1,79 @@
/*
Copyright (C) 2025 - 2025
Part of the Battle for Wesnoth Project https://www.wesnoth.org/
This program 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; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY.
See the COPYING file for more details.
*/
#pragma once
#include "events.hpp"
#include <boost/circular_buffer.hpp>
#include <chrono>
#include "utils/optional_fwd.hpp"
#include <tuple>
namespace gui2
{
/**
* Middleware class that tracks framerate and times.
*
* It should be used in conjunction with classes that implement top_level_drawable.
* Strictly, it will measure the between invocations of update_count, which should
* be invoked from the render function.
*/
class tracked_drawable : private events::pump_monitor
{
public:
tracked_drawable();
struct frame_info
{
std::chrono::milliseconds min_time{};
std::chrono::milliseconds avg_time{};
std::chrono::milliseconds max_time{};
unsigned min_fps{};
unsigned avg_fps{};
unsigned max_fps{};
unsigned act_fps{};
};
/** Returns the current frame time and info, or nullopt if no times have been recorded. */
auto get_info() const -> utils::optional<frame_info>;
protected:
~tracked_drawable();
/** Records time since last invocation. */
void update_count();
private:
/** Inherited from events::pump_monitor. */
void process() override;
using clock = std::chrono::steady_clock;
using times = std::tuple<clock::duration, clock::duration, clock::duration>;
/** Get min, average, and max frametimes in steady_clock resolution. */
auto get_times() const -> times;
boost::circular_buffer<clock::duration> frametimes_;
unsigned render_count_;
unsigned render_counter_;
clock::time_point last_lap_;
clock::time_point last_render_;
};
} // namespace gui2

View File

@ -0,0 +1,155 @@
/*
Copyright (C) 2025 - 2025
Part of the Battle for Wesnoth Project https://www.wesnoth.org/
This program 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; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY.
See the COPYING file for more details.
*/
#define GETTEXT_DOMAIN "wesnoth-lib"
#include "gui/dialogs/fps_report.hpp"
#include "filesystem.hpp"
#include "gui/core/tracked_drawable.hpp"
#include "gui/dialogs/modal_dialog.hpp"
#include "gui/dialogs/modeless_dialog.hpp"
#include "gui/widgets/rich_label.hpp"
#include "serialization/markup.hpp"
#include "utils/rate_counter.hpp"
#include <tuple>
#include <vector>
#ifdef DUMP_FPS_TO_FILE
#undef DUMP_FPS_TO_FILE
#endif
namespace gui2::dialogs
{
namespace
{
class fps_report : public modeless_dialog
{
public:
fps_report(const gui2::tracked_drawable& target)
: modeless_dialog(window_id())
, target_(target)
{
}
private:
const std::string& window_id() const override;
/* Inherited from top_level_drawable. */
void update() override;
/** The drawable whose render calls we are tracking. */
const gui2::tracked_drawable& target_;
/** Only update the displayed count every few update cycles. */
utils::rate_counter update_check_{10};
/** Holds the prior (max 1000) displayed fps values. */
std::vector<std::tuple<int, int, int>> fps_history_{};
};
/** Generates the rich_label markup for the report. */
auto generate_markup(const gui2::tracked_drawable::frame_info& info)
{
return config{ "table", config{ "width", "fill",
"row", config{
"col", config{},
"col", config{ "halign", "right", "text", "min" },
"col", config{ "halign", "right", "text", "avg" },
"col", config{ "halign", "right", "text", "max" },
"col", config{ "halign", "right", "text", "act" },
},
"row", config{
"col", config{ "halign", "right", "text", "fps" },
"col", config{ "halign", "right", "text", info.min_fps },
"col", config{ "halign", "right", "text", info.avg_fps },
"col", config{ "halign", "right", "text", info.max_fps },
"col", config{ "halign", "right", "text", info.act_fps },
},
"row", config{
"col", config{ "halign", "right", "text", "ms" },
"col", config{ "halign", "right", "text", info.max_time },
"col", config{ "halign", "right", "text", info.avg_time },
"col", config{ "halign", "right", "text", info.min_time },
"col", config{},
},
}};
}
void fps_report::update()
{
if(!update_check_.poll()) {
return;
}
// Will be null if no times have been recorded yet
auto info = target_.get_info();
if(!info) {
return;
}
find_widget<rich_label>("values").set_dom(generate_markup(*info));
#ifdef DUMP_FPS_TO_FILE
fps_history_.emplace_back(info->min_fps, info->avg_fps, info->max_fps);
// Flush out the stored values every so often
if(fps_history_.size() == 1000) {
std::string filename = filesystem::get_user_data_dir() + "/fps_log.csv";
auto fps_log = filesystem::ostream_file(filename, std::ios_base::binary | std::ios_base::app);
for(const auto& [min, avg, max] : fps_history_) {
*fps_log << min << "," << avg << "," << max << "\n";
}
fps_history_.clear();
}
#endif
}
std::unique_ptr<fps_report> report;
} // namespace
REGISTER_DIALOG(fps_report)
namespace fps
{
void show(const gui2::tracked_drawable& target)
{
// No-op if currently displayed
if(report) {
return;
}
report.reset(new fps_report(target));
report->show();
// HACK: in order that the display not prevent events from reaching the in-game
// event context, leave the UI event context. This should be removed if ever we
// get the two event contexts to play nicely...
report->disconnect();
}
void hide()
{
report.reset();
}
} // namespace fps
} // namespace gui2::dialogs

View File

@ -0,0 +1,36 @@
/*
Copyright (C) 2025 - 2025
Part of the Battle for Wesnoth Project https://www.wesnoth.org/
This program 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; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY.
See the COPYING file for more details.
*/
#pragma once
namespace gui2
{
class tracked_drawable;
namespace dialogs::fps
{
/**
* Displays the fps report popup for the given tracked_drawable.
*
* Only one popup may be active at a time, and subsequent calls
* to this function are no-op unless @ref hide is called first.
*/
void show(const gui2::tracked_drawable& target);
/** Hides the fps report popup. */
void hide();
} // namespace dialogs::fps
} // namespace gui2

View File

@ -711,6 +711,7 @@ BOOST_AUTO_TEST_CASE(test_last)
"campaign_selection",// segfault with LTO
"game_load",// segfault after disabling the above tests
"file_progress",
"fps_report", // needs something to report...
};
filesystem::delete_file(test_gui2_fixture::widgets_file);