mirror of
https://github.com/TwilitRealm/dusklight
synced 2026-07-07 12:03:24 -04:00
Add FPS counter
This commit is contained in:
@@ -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
@@ -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("" 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;
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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 &&
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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,
|
||||
|
||||
Reference in New Issue
Block a user