Add FPS counter

This commit is contained in:
Irastris
2026-05-07 03:11:19 -04:00
parent 3968b67c5d
commit 78ed5cc716
7 changed files with 176 additions and 15 deletions
+2
View File
@@ -57,6 +57,8 @@ struct UserSettings {
ConfigVar<bool> enableFullscreen;
ConfigVar<bool> enableVsync;
ConfigVar<bool> lockAspectRatio;
ConfigVar<bool> enableFpsOverlay;
ConfigVar<int> fpsOverlayCorner;
} video;
struct {
+43 -4
View File
@@ -20,18 +20,22 @@ body {
pointer-events: none;
}
fps,
toast {
position: absolute;
border: 1dp #92875B;
backdrop-filter: blur(5dp);
box-shadow: 0 0 15dp 3dp;
background-color: rgba(21, 22, 16, 80%);
}
toast {
top: 40dp;
right: 40dp;
display: flex;
flex-flow: column;
border-radius: 14dp;
overflow: hidden;
border: 1dp #92875B;
backdrop-filter: blur(5dp);
box-shadow: 0 0 15dp 3dp;
background-color: rgba(21, 22, 16, 80%);
filter: opacity(0);
transform: scale(0.9);
transform-origin: center;
@@ -186,6 +190,41 @@ icon.warning {
decorator: text("&#xe002;" center center);
}
fps {
display: none;
z-index: 99;
font-size: 18dp;
font-weight: bold;
padding: 9dp 12dp;
border-radius: 7dp;
pointer-events: none;
white-space: nowrap;
}
fps[open] {
display: block;
}
fps[corner=tl] {
top: 12dp;
left: 12dp;
}
fps[corner=tr] {
top: 12dp;
right: 12dp;
}
fps[corner=bl] {
bottom: 12dp;
left: 12dp;
}
fps[corner=br] {
bottom: 12dp;
right: 12dp;
}
logo {
position: absolute;
width: 100dp;
-10
View File
@@ -276,16 +276,6 @@ namespace dusk {
m_menuGame.draw();
m_menuTools.draw();
const auto fpsLabel =
fmt::format(FMT_STRING("FPS: {:.2f}\n"), ImGui::GetIO().Framerate);
const auto fpsSize =
ImGui::CalcTextSize(fpsLabel.data(), fpsLabel.data() + fpsLabel.size());
ImGui::SetCursorPosX(
ImMax(ImGui::GetCursorPosX(), ImGui::GetWindowWidth() -
ImGui::GetStyle().DisplaySafeAreaPadding.x -
fpsSize.x - ImGui::GetStyle().ItemSpacing.x));
ImGuiStringViewText(fpsLabel);
ImGui::EndMainMenuBar();
}
ImGui::PopStyleColor();
+4
View File
@@ -8,6 +8,8 @@ UserSettings g_userSettings = {
.enableFullscreen {"video.enableFullscreen", false},
.enableVsync {"video.enableVsync", true},
.lockAspectRatio {"video.lockAspectRatio", false},
.enableFpsOverlay {"game.enableFpsOverlay", false},
.fpsOverlayCorner {"game.fpsOverlayCorner", 0},
},
.audio = {
@@ -128,6 +130,8 @@ void registerSettings() {
Register(g_userSettings.video.enableFullscreen);
Register(g_userSettings.video.enableVsync);
Register(g_userSettings.video.lockAspectRatio);
Register(g_userSettings.video.enableFpsOverlay);
Register(g_userSettings.video.fpsOverlayCorner);
// Audio
Register(g_userSettings.audio.masterVolume);
+70
View File
@@ -6,6 +6,7 @@
#include "window.hpp"
#include <SDL3/SDL_gamepad.h>
#include <SDL3/SDL_timer.h>
#include <algorithm>
#include <dolphin/pad.h>
@@ -19,6 +20,7 @@ const Rml::String kDocumentSource = R"RML(
<link type="text/rcss" href="res/rml/overlay.rcss" />
</head>
<body>
<fps id="fps" />
</body>
</rml>
)RML";
@@ -31,6 +33,8 @@ constexpr std::array<std::pair<const char*, const char*>, 3> kAutoSaveLayers{{
constexpr auto kMenuNotificationDuration = std::chrono::milliseconds(2500);
constexpr std::array<const char*, 4> kFpsCorners = { "tl", "tr", "bl", "br" };
Rml::Element* create_toast(Rml::Element* parent, const Toast& toast) {
if (toast.type == "autosave") {
auto* logo = append(parent, "logo");
@@ -153,7 +157,46 @@ void remove_element(Rml::Element*& elem) noexcept {
} // namespace
// https://vplesko.com/posts/how_to_implement_an_fps_counter.html
void Overlay::advance_fps_counter(float& outFps, Uint64 perfFreq) {
if (perfFreq == 0) {
outFps = 0.f;
return;
}
const Uint64 curr = SDL_GetPerformanceCounter();
if (!mFpsHavePrevCounter) {
mFpsPrevCounter = curr;
mFpsHavePrevCounter = true;
outFps = 0.f;
return;
}
const Uint64 processingTicks = curr - mFpsPrevCounter;
mFpsPrevCounter = curr;
mFpsFrameEvents.push_back({curr, processingTicks});
mFpsSumTicks += processingTicks;
while (!mFpsFrameEvents.empty() && mFpsFrameEvents.front().endCounter + perfFreq < curr) {
mFpsSumTicks -= mFpsFrameEvents.front().processingTicks;
mFpsFrameEvents.pop_front();
}
const auto n = mFpsFrameEvents.size();
if (n == 0 || mFpsSumTicks == 0) {
outFps = 0.f;
return;
}
const double avgSeconds =
static_cast<double>(mFpsSumTicks) / static_cast<double>(n) / static_cast<double>(perfFreq);
outFps = static_cast<float>(1.0 / avgSeconds);
}
Overlay::Overlay() : Document(kDocumentSource) {
mFpsCounter = mDocument->GetElementById("fps");
listen(mDocument, Rml::EventId::Focus, [](Rml::Event&) { Log.warn("Overlay received focus"); });
listen(mDocument, Rml::EventId::Transitionend, [this](Rml::Event& event) {
if (event.GetTargetElement() == mCurrentToast) {
@@ -187,6 +230,33 @@ void Overlay::update() {
return;
}
if (mFpsCounter != nullptr) {
if (getSettings().video.enableFpsOverlay.getValue()) {
const int idx = getSettings().video.fpsOverlayCorner.getValue();
mFpsCounter->SetAttribute("open", "");
mFpsCounter->SetAttribute("corner", kFpsCorners[idx]);
const Uint64 perfFreq = SDL_GetPerformanceFrequency();
float fps = 0.f;
advance_fps_counter(fps, perfFreq);
const Uint64 now = SDL_GetPerformanceCounter();
// Limit updates to twice per second
const bool refreshLabel = perfFreq == 0 || mFpsLastUpdate == 0 ||
static_cast<double>(now - mFpsLastUpdate) >= 0.5 * static_cast<double>(perfFreq);
if (refreshLabel) {
mFpsLastUpdate = now;
mFpsCounter->SetInnerRML(escape(fmt::format("{:.0f} FPS", fps)));
}
} else {
mFpsCounter->RemoveAttribute("open");
mFpsFrameEvents.clear();
mFpsSumTicks = 0;
mFpsHavePrevCounter = false;
mFpsLastUpdate = 0;
}
}
const bool showControllerWarning = PADGetIndexForPort(PAD_CHAN0) < 0 &&
PADGetKeyButtonBindings(PAD_CHAN0, nullptr) == nullptr &&
dynamic_cast<Window*>(top_document()) == nullptr &&
+15
View File
@@ -3,6 +3,7 @@
#include "document.hpp"
#include <chrono>
#include <deque>
namespace dusk::ui {
@@ -16,11 +17,25 @@ public:
protected:
bool handle_nav_command(Rml::Event& event, NavCommand cmd) override;
Rml::Element* mFpsCounter = nullptr;
Rml::Element* mCurrentToast = nullptr;
Rml::Element* mControllerWarning = nullptr;
Rml::Element* mMenuNotification = nullptr;
clock::time_point mCurrentToastStartTime;
clock::time_point mMenuNotificationStartTime;
struct FpsFrameEvent {
Uint64 endCounter;
Uint64 processingTicks;
};
std::deque<FpsFrameEvent> mFpsFrameEvents;
Uint64 mFpsSumTicks = 0;
bool mFpsHavePrevCounter = false;
Uint64 mFpsPrevCounter = 0;
Uint64 mFpsLastUpdate = 0;
void advance_fps_counter(float& outFps, Uint64 perfFreq);
};
} // namespace dusk::ui
+42 -1
View File
@@ -33,6 +33,13 @@ constexpr std::array kCardFileTypes = {
"GCI Folder",
};
constexpr std::array kFpsOverlayCornerNames = {
"Top Left",
"Top Right",
"Bottom Left",
"Bottom Right",
};
bool try_parse_backend(std::string_view backend, AuroraBackend& outBackend) {
if (backend == "auto") {
outBackend = BACKEND_AUTO;
@@ -430,7 +437,7 @@ SettingsWindow::SettingsWindow(bool prelaunch) : mPrelaunch(prelaunch) {
});
}
add_tab("Graphics", [this](Rml::Element* content) {
add_tab("Video", [this](Rml::Element* content) {
auto& leftPane = add_child<Pane>(content, Pane::Type::Controlled);
auto& rightPane = add_child<Pane>(content, Pane::Type::Uncontrolled);
@@ -472,6 +479,40 @@ SettingsWindow::SettingsWindow(bool prelaunch) : mPrelaunch(prelaunch) {
.key = "Pause on Focus Lost",
.isDisabled = [] { return IsMobile; },
});
config_bool_select(leftPane, rightPane, getSettings().video.enableFpsOverlay,
{
.key = "Enable FPS Counter",
.helpText = "Display the current framerate in a corner of the screen while playing.",
});
leftPane.register_control(leftPane.add_select_button({
.key = "FPS Counter Display Corner",
.getValue =
[] {
const int corner = getSettings().video.fpsOverlayCorner.getValue();
return Rml::String{kFpsOverlayCornerNames[corner]};
},
.isDisabled = [] { return !getSettings().video.enableFpsOverlay.getValue(); },
}),
rightPane, [](Pane& pane) {
for (int i = 0; i < static_cast<int>(kFpsOverlayCornerNames.size()); ++i) {
pane
.add_button({
.text = kFpsOverlayCornerNames[i],
.isSelected =
[i] {
return std::clamp(
getSettings().video.fpsOverlayCorner.getValue(), 0,
3) == i;
},
})
.on_pressed([i] {
mDoAud_seStartMenu(kSoundItemChange);
getSettings().video.fpsOverlayCorner.setValue(i);
config::Save();
});
}
pane.add_rml("<br/>Choose which corner the framerate counter displays in.");
});
leftPane.add_section("Resolution");
graphics_tuner_control(*this, leftPane, rightPane,