1
0
mirror of synced 2025-01-10 21:41:53 +01:00

impr: Revamp frame rate limiting system to make ImHex feel less laggy in certain cases (#2049)

This commit is contained in:
Nik 2025-01-04 15:35:19 +01:00 committed by GitHub
parent 6009b5013b
commit d975019a7b
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 81 additions and 68 deletions

View File

@ -139,8 +139,8 @@ if (NOT IMHEX_EXTERNAL_PLUGIN_BUILD)
target_link_libraries(libimhex PUBLIC ${FOUNDATION}) target_link_libraries(libimhex PUBLIC ${FOUNDATION})
endif () endif ()
target_link_libraries(libimhex PRIVATE microtar libwolv ${NFD_LIBRARIES} magic dl ${JTHREAD_LIBRARIES}) target_link_libraries(libimhex PRIVATE microtar libwolv ${NFD_LIBRARIES} magic dl)
target_link_libraries(libimhex PUBLIC libpl ${IMGUI_LIBRARIES}) target_link_libraries(libimhex PUBLIC libpl ${IMGUI_LIBRARIES} ${JTHREAD_LIBRARIES})
precompileHeaders(libimhex "${CMAKE_CURRENT_SOURCE_DIR}/include") precompileHeaders(libimhex "${CMAKE_CURRENT_SOURCE_DIR}/include")
endif() endif()

View File

@ -1,5 +1,6 @@
#pragma once #pragma once
#include <condition_variable>
#include <filesystem> #include <filesystem>
#include <memory> #include <memory>
#include <string> #include <string>
@ -7,6 +8,7 @@
#include <vector> #include <vector>
#include <hex/ui/view.hpp> #include <hex/ui/view.hpp>
#include <jthread.hpp>
struct GLFWwindow; struct GLFWwindow;
struct ImGuiSettingsHandler; struct ImGuiSettingsHandler;
@ -59,12 +61,17 @@ namespace hex {
std::list<std::string> m_popupsToOpen; std::list<std::string> m_popupsToOpen;
std::vector<int> m_pressedKeys; std::vector<int> m_pressedKeys;
bool m_unlockFrameRate = false; std::atomic<bool> m_unlockFrameRate = false;
ImGuiExt::ImHexCustomData m_imguiCustomData; ImGuiExt::ImHexCustomData m_imguiCustomData;
u32 m_searchBarPosition = 0; u32 m_searchBarPosition = 0;
bool m_emergencyPopupOpen = false; bool m_emergencyPopupOpen = false;
std::jthread m_frameRateThread;
std::atomic<bool> m_sleepFlag;
std::condition_variable m_sleepCondVar;
std::mutex m_sleepMutex;
}; };
} }

View File

@ -41,6 +41,7 @@
#include <hex/ui/toast.hpp> #include <hex/ui/toast.hpp>
#include <wolv/utils/guards.hpp> #include <wolv/utils/guards.hpp>
#include <fmt/printf.h> #include <fmt/printf.h>
#include <fmt/chrono.h>
namespace hex { namespace hex {
@ -231,40 +232,9 @@ namespace hex {
ImHexApi::System::impl::setMainWindowSize(width, height); ImHexApi::System::impl::setMainWindowSize(width, height);
} }
// Determine if the application should be in long sleep mode
bool shouldLongSleep = !m_unlockFrameRate;
static double lockTimeout = 0;
if (!shouldLongSleep) {
lockTimeout = 0.05;
} else if (lockTimeout > 0) {
lockTimeout -= m_lastFrameTime;
}
if (shouldLongSleep && lockTimeout > 0)
shouldLongSleep = false;
m_unlockFrameRate = false;
if (!glfwGetWindowAttrib(m_window, GLFW_VISIBLE) || glfwGetWindowAttrib(m_window, GLFW_ICONIFIED)) { if (!glfwGetWindowAttrib(m_window, GLFW_VISIBLE) || glfwGetWindowAttrib(m_window, GLFW_ICONIFIED)) {
// If the application is minimized or not visible, don't render anything // If the application is minimized or not visible, don't render anything
glfwWaitEvents(); glfwWaitEvents();
} else {
// If the application is visible, render a frame
// If the application is in long sleep mode, only render a frame every 200ms
// Long sleep mode is enabled automatically after a few frames if the window content hasn't changed
// and no events have been received
if (shouldLongSleep) {
// Calculate the time until the next frame
constexpr static auto LongSleepFPS = 5.0;
const double timeout = std::max(0.0, (1.0 / LongSleepFPS) - (glfwGetTime() - m_lastStartFrameTime));
glfwPollEvents();
glfwWaitEventsTimeout(timeout);
} else {
glfwPollEvents();
}
} }
m_lastStartFrameTime = glfwGetTime(); m_lastStartFrameTime = glfwGetTime();
@ -281,40 +251,18 @@ namespace hex {
ImHexApi::System::impl::setLastFrameTime(glfwGetTime() - m_lastStartFrameTime); ImHexApi::System::impl::setLastFrameTime(glfwGetTime() - m_lastStartFrameTime);
// Limit frame rate {
// If the target FPS are below 15, use the monitor refresh rate, if it's above 200, don't limit the frame rate while (true) {
auto targetFPS = ImHexApi::System::getTargetFPS(); glfwPollEvents();
if (targetFPS >= 200) {
// Let it rip
} else {
// If the target frame rate is below 15, use the current monitor's refresh rate
if (targetFPS < 15) {
// Fall back to 60 FPS if the monitor refresh rate cannot be determined
targetFPS = 60;
if (auto monitor = glfwGetWindowMonitor(m_window); monitor != nullptr) { if (ImHexApi::System::getTargetFPS() >= 200)
if (auto videoMode = glfwGetVideoMode(monitor); videoMode != nullptr) { break;
targetFPS = videoMode->refreshRate;
}
}
}
// Sleep if we're not in long sleep mode {
if (!shouldLongSleep) { std::unique_lock lock(m_sleepMutex);
// If anything goes wrong with these checks, make sure that we're sleeping for at least 1ms m_sleepCondVar.wait_for(lock, std::chrono::microseconds(100));
std::this_thread::sleep_for(std::chrono::milliseconds(1)); if (m_sleepFlag.exchange(false))
break;
// Sleep for the remaining time if the frame rate is above the target frame rate
const auto frameTime = glfwGetTime() - m_lastStartFrameTime;
const auto targetFrameTime = 1.0 / targetFPS;
if (frameTime < targetFrameTime) {
glfwWaitEventsTimeout(targetFrameTime - frameTime);
// glfwWaitEventsTimeout might return early if there's an event
if (frameTime < targetFrameTime) {
const auto timeToSleepMs = (int)((targetFrameTime - frameTime) * 1000);
std::this_thread::sleep_for(std::chrono::milliseconds(timeToSleepMs));
}
} }
} }
} }
@ -741,8 +689,6 @@ namespace hex {
glfwSwapBuffers(m_window); glfwSwapBuffers(m_window);
} }
m_unlockFrameRate = true;
} }
// Process layout load requests // Process layout load requests
@ -887,6 +833,11 @@ namespace hex {
win->m_unlockFrameRate = true; win->m_unlockFrameRate = true;
}); });
glfwSetMouseButtonCallback(m_window, [](GLFWwindow *window, int, int, int) {
auto win = static_cast<Window *>(glfwGetWindowUserPointer(window));
win->m_unlockFrameRate = true;
});
glfwSetWindowFocusCallback(m_window, [](GLFWwindow *, int focused) { glfwSetWindowFocusCallback(m_window, [](GLFWwindow *, int focused) {
EventWindowFocused::post(focused == GLFW_TRUE); EventWindowFocused::post(focused == GLFW_TRUE);
}); });
@ -950,6 +901,61 @@ namespace hex {
}); });
glfwSetWindowSizeLimits(m_window, 480_scaled, 360_scaled, GLFW_DONT_CARE, GLFW_DONT_CARE); glfwSetWindowSizeLimits(m_window, 480_scaled, 360_scaled, GLFW_DONT_CARE, GLFW_DONT_CARE);
m_frameRateThread = std::jthread([this](const std::stop_token &stopToken) {
using Duration = std::chrono::duration<double, std::nano>;
Duration passedTime = {};
std::chrono::steady_clock::time_point startTime = {}, endTime = {};
Duration requestedFrameTime = {}, remainingUnlockedTime = {};
float targetFps = 0;
const auto nativeFps = [] -> float {
if (const auto monitor = glfwGetPrimaryMonitor(); monitor != nullptr) {
if (const auto videoMode = glfwGetVideoMode(monitor); videoMode != nullptr) {
return videoMode->refreshRate;
}
}
return 60;
}();
while (!stopToken.stop_requested()) {
const auto iterationTime = endTime - startTime;
startTime = std::chrono::steady_clock::now();
targetFps = ImHexApi::System::getTargetFPS();
if (m_unlockFrameRate.exchange(false)) {
remainingUnlockedTime = std::chrono::seconds(2);
}
// If the target frame rate is below 15, use the current monitor's refresh rate
if (targetFps < 15) {
targetFps = nativeFps;
}
passedTime += iterationTime;
if (remainingUnlockedTime > std::chrono::nanoseconds(0)) {
remainingUnlockedTime -= iterationTime;
} else {
targetFps = 5;
}
requestedFrameTime = (Duration(1.0E9) / targetFps) / 1.3;
if (passedTime >= requestedFrameTime) {
std::scoped_lock lock(m_sleepMutex);
m_sleepFlag = true;
m_sleepCondVar.notify_all();
passedTime = {};
}
std::this_thread::sleep_for(std::chrono::microseconds(100));
endTime = std::chrono::steady_clock::now();
}
});
} }
void Window::resize(i32 width, i32 height) { void Window::resize(i32 width, i32 height) {