diff --git a/src/spice2x/CMakeLists.txt b/src/spice2x/CMakeLists.txt index 0836a18..ccca346 100644 --- a/src/spice2x/CMakeLists.txt +++ b/src/spice2x/CMakeLists.txt @@ -636,6 +636,7 @@ set(SOURCE_FILES ${SOURCE_FILES} util/time.cpp util/cpuutils.cpp util/netutils.cpp + util/precise_timer.cpp util/sysutils.cpp util/lz77.cpp util/tapeled.cpp diff --git a/src/spice2x/acio/mdxf/mdxf.cpp b/src/spice2x/acio/mdxf/mdxf.cpp index 67648fc..655c46e 100644 --- a/src/spice2x/acio/mdxf/mdxf.cpp +++ b/src/spice2x/acio/mdxf/mdxf.cpp @@ -1,14 +1,16 @@ #include "mdxf.h" #include "mdxf_poll.h" +#include + #include "avs/game.h" #include "games/ddr/ddr.h" #include "games/ddr/io.h" #include "launcher/launcher.h" #include "rawinput/rawinput.h" #include "util/logging.h" +#include "util/precise_timer.h" #include "util/utils.h" -#include #define MDFX_DEBUG_VERBOSE 0 @@ -109,9 +111,10 @@ static void mdxf_thread_start() { MDXF_THREAD = std::thread([] { SetThreadPriority(GetCurrentThread(), THREAD_PRIORITY_BELOW_NORMAL); + timeutils::PreciseSleepTimer timer; while (MDXF_THREAD_RUNNING.load(std::memory_order_acquire)) { mdxf_poll(false); - std::this_thread::sleep_for(THREAD_PERIOD); + timer.sleep(THREAD_PERIOD); } }); } @@ -512,4 +515,4 @@ acio::MDXFModule::~MDXFModule() { if (IS_THREAD_NEEDED) { mdxf_thread_stop(); } -} \ No newline at end of file +} diff --git a/src/spice2x/acioemu/icca.cpp b/src/spice2x/acioemu/icca.cpp index 4454177..ff003ed 100644 --- a/src/spice2x/acioemu/icca.cpp +++ b/src/spice2x/acioemu/icca.cpp @@ -5,6 +5,7 @@ #include "games/sdvx/sdvx.h" #include "misc/eamuse.h" #include "util/logging.h" +#include "util/precise_timer.h" #include "util/utils.h" using namespace acioemu; @@ -46,11 +47,12 @@ ICCADevice::ICCADevice(bool flip_order, bool keypad_thread, uint8_t node_count) // keypad thread for faster polling if (keypad_thread) { this->keypad_thread = new std::thread([this]() { + timeutils::PreciseSleepTimer timer; while (this->cards) { for (int unit = 0; unit < this->node_count; unit++) { this->update_keypad(unit, false); } - Sleep(7); + timer.sleep(7); } }); } diff --git a/src/spice2x/api/modules/keypads.cpp b/src/spice2x/api/modules/keypads.cpp index 8980f03..0e20e4b 100644 --- a/src/spice2x/api/modules/keypads.cpp +++ b/src/spice2x/api/modules/keypads.cpp @@ -7,6 +7,7 @@ #include "avs/game.h" #include "external/rapidjson/document.h" #include "misc/eamuse.h" +#include "util/precise_timer.h" using namespace std::placeholders; using namespace rapidjson; @@ -58,6 +59,7 @@ namespace api::modules { // get params auto keypad = req.params[0].GetUint(); auto input = std::string(req.params[1].GetString()); + timeutils::PreciseSleepTimer timer; // process all chars for (auto c : input) { @@ -91,11 +93,11 @@ namespace api::modules { // set eamuse_set_keypad_overrides(keypad, state); - Sleep(sleep_time); + timer.sleep(sleep_time); // unset eamuse_set_keypad_overrides(keypad, 0); - Sleep(sleep_time); + timer.sleep(sleep_time); } } diff --git a/src/spice2x/api/serial.cpp b/src/spice2x/api/serial.cpp index b1beee6..70ba1b1 100644 --- a/src/spice2x/api/serial.cpp +++ b/src/spice2x/api/serial.cpp @@ -5,6 +5,7 @@ #include #include "util/logging.h" +#include "util/precise_timer.h" #include "util/utils.h" @@ -16,6 +17,7 @@ namespace api { controller->init_state(this->state); this->thread = new std::thread([this] () { log_warning("api::serial", "listening on {} (baud: {})", this->port, this->baud); + timeutils::PreciseSleepTimer timer; // read buffer uint8_t read_buffer[16*1024]; @@ -160,7 +162,7 @@ namespace api { // slow down on reconnect if (this->running) { - Sleep(retry_time); + timer.sleep(retry_time); } } }); diff --git a/src/spice2x/games/ccj/trackball.cpp b/src/spice2x/games/ccj/trackball.cpp index 5485f94..3c6a320 100644 --- a/src/spice2x/games/ccj/trackball.cpp +++ b/src/spice2x/games/ccj/trackball.cpp @@ -6,6 +6,7 @@ #include "util/logging.h" #include "rawinput/rawinput.h" #include "games/io.h" +#include "util/precise_timer.h" #include "util/utils.h" #include "io.h" @@ -243,6 +244,7 @@ namespace games::ccj { log_info("trackball", "thread start, use mouse: {}, toggle: {}", MOUSE_TRACKBALL, MOUSE_TRACKBALL_USE_TOGGLE); tbThread = new std::thread([&] { + timeutils::PreciseSleepTimer timer; while (tbThreadRunning) { if (hWnd && wndProc) { wndProc(hWnd, WM_INPUT, RIM_INPUT, (LPARAM)fakeHandle); @@ -251,7 +253,7 @@ namespace games::ccj { if (!tbThreadRunning) break; - std::this_thread::sleep_for(10ms); + timer.sleep(10); } }); } diff --git a/src/spice2x/games/ddr/p3io/p3io.cpp b/src/spice2x/games/ddr/p3io/p3io.cpp index 2c2f624..06ae29c 100644 --- a/src/spice2x/games/ddr/p3io/p3io.cpp +++ b/src/spice2x/games/ddr/p3io/p3io.cpp @@ -3,6 +3,7 @@ #include "cfg/api.h" #include "rawinput/rawinput.h" #include "util/logging.h" +#include "util/precise_timer.h" #include "util/utils.h" #include "../ddr.h" @@ -459,7 +460,8 @@ int games::ddr::DDRP3IOHandle::device_io( if (nOutBufferSize >= 4) { // cool down - Sleep(1); + static thread_local timeutils::PreciseSleepTimer timer; + timer.sleep(1); // get controls as single variable (4 bytes) auto &controls = *(uint32_t*) lpOutBuffer; diff --git a/src/spice2x/games/ddr/p4io/p4io.cpp b/src/spice2x/games/ddr/p4io/p4io.cpp index dd13a90..4b9c2ef 100644 --- a/src/spice2x/games/ddr/p4io/p4io.cpp +++ b/src/spice2x/games/ddr/p4io/p4io.cpp @@ -2,6 +2,7 @@ #include "rawinput/rawinput.h" #include "util/logging.h" +#include "util/precise_timer.h" #include "util/utils.h" #include "../io.h" @@ -255,7 +256,8 @@ namespace games::ddr { case P4IO_IOCTL_GET_INPUTS: { // Prevents this function from being called at its normal 2000-2500 kHz cadence and overloading the CPU, instead reduces it to 250 Hz - Sleep(4); + static thread_local timeutils::PreciseSleepTimer timer; + timer.sleep(4); memset(lpOutBuffer, 0, 16); auto controls = (uint32_t*) lpOutBuffer; diff --git a/src/spice2x/games/drs/drs.cpp b/src/spice2x/games/drs/drs.cpp index 585e2b1..7d2bf6a 100644 --- a/src/spice2x/games/drs/drs.cpp +++ b/src/spice2x/games/drs/drs.cpp @@ -7,6 +7,7 @@ #include "games/game.h" #include "util/detour.h" #include "util/logging.h" +#include "util/precise_timer.h" #include "util/memutils.h" #include "rgb_cam.h" @@ -221,10 +222,11 @@ namespace games::drs { void start_touch() { std::thread t([] { log_info("drs", "starting touch input thread"); + timeutils::PreciseSleepTimer timer; // main loop while (TRUE) { - std::this_thread::sleep_for(std::chrono::milliseconds(1)); + timer.sleep(1); TOUCH_EVENTS.clear(); touch_get_events(TOUCH_EVENTS); diff --git a/src/spice2x/games/iidx/bi2a.cpp b/src/spice2x/games/iidx/bi2a.cpp index fff4962..6e8531d 100644 --- a/src/spice2x/games/iidx/bi2a.cpp +++ b/src/spice2x/games/iidx/bi2a.cpp @@ -2,6 +2,7 @@ #include "misc/eamuse.h" #include "rawinput/rawinput.h" +#include "util/precise_timer.h" #include "util/utils.h" #include "iidx.h" @@ -82,7 +83,8 @@ bool games::iidx::IIDXFMSerialHandle::FMSerialDevice::parse_msg( } // sleep - otherwise the IO thread will go too hard on the CPU - Sleep(1); + static thread_local timeutils::PreciseSleepTimer timer; + timer.sleep(1); // generate message auto msg = this->create_msg(msg_in, 0x2E); diff --git a/src/spice2x/games/iidx/bi2x.cpp b/src/spice2x/games/iidx/bi2x.cpp index b719701..71270c3 100644 --- a/src/spice2x/games/iidx/bi2x.cpp +++ b/src/spice2x/games/iidx/bi2x.cpp @@ -2,6 +2,7 @@ #include "misc/eamuse.h" #include "rawinput/rawinput.h" +#include "util/precise_timer.h" #include "util/utils.h" #include "iidx.h" @@ -82,7 +83,8 @@ bool games::iidx::BI2XSerialHandle::BI2XDevice::parse_msg( } // sleep - otherwise the IO thread will go too hard on the CPU - Sleep(1); + static thread_local timeutils::PreciseSleepTimer timer; + timer.sleep(1); // generate message auto msg = this->create_msg(msg_in, 0x2E); diff --git a/src/spice2x/games/iidx/local_camera.cpp b/src/spice2x/games/iidx/local_camera.cpp index 4e01d42..0d64239 100644 --- a/src/spice2x/games/iidx/local_camera.cpp +++ b/src/spice2x/games/iidx/local_camera.cpp @@ -3,6 +3,7 @@ #if SPICE64 && !SPICE_XP #include "util/logging.h" +#include "util/precise_timer.h" #include "util/utils.h" #include "mf_wrappers.h" @@ -399,6 +400,7 @@ namespace games::iidx { void IIDXLocalCamera::CreateThread() { // Create thread m_drawThread = new std::thread([this]() { + timeutils::PreciseSleepTimer timer; SetThreadPriority(GetCurrentThread(), THREAD_PRIORITY_BELOW_NORMAL); double accumulator = 0.0; @@ -412,7 +414,7 @@ namespace games::iidx { accumulator -= 1.0; floorFrameTimeMicroSec += 1; } - std::this_thread::sleep_for(std::chrono::microseconds(floorFrameTimeMicroSec)); + timer.sleep(std::chrono::microseconds(floorFrameTimeMicroSec)); } }); } diff --git a/src/spice2x/games/iidx/poke.cpp b/src/spice2x/games/iidx/poke.cpp index e0dc816..97a133c 100644 --- a/src/spice2x/games/iidx/poke.cpp +++ b/src/spice2x/games/iidx/poke.cpp @@ -15,6 +15,7 @@ #include "touch/touch.h" #include "util/libutils.h" #include "util/logging.h" +#include "util/precise_timer.h" #define POKE_NATIVE_TOUCH 0 @@ -200,6 +201,7 @@ namespace games::iidx::poke { // create new thread THREAD_RUNNING = true; THREAD = new std::thread([] { + timeutils::PreciseSleepTimer timer; // log log_info("poke", "enabled"); @@ -319,7 +321,7 @@ namespace games::iidx::poke { } // slow down - Sleep(50); + timer.sleep(50); } return nullptr; diff --git a/src/spice2x/games/nost/poke.cpp b/src/spice2x/games/nost/poke.cpp index 64876cc..799a6e4 100644 --- a/src/spice2x/games/nost/poke.cpp +++ b/src/spice2x/games/nost/poke.cpp @@ -9,6 +9,7 @@ #include "misc/eamuse.h" #include "touch/touch.h" #include "util/logging.h" +#include "util/precise_timer.h" namespace games::nost::poke { @@ -63,6 +64,7 @@ namespace games::nost::poke { // create new thread THREAD_RUNNING = true; THREAD = new std::thread([] { + timeutils::PreciseSleepTimer timer; const DWORD touch_id = (DWORD)(0xFFFFFFFE); const int swipe_anim_total_frames = 6; @@ -178,7 +180,7 @@ namespace games::nost::poke { } // slow down - Sleep(30); + timer.sleep(30); } return nullptr; diff --git a/src/spice2x/games/onpara/touchpanel.cpp b/src/spice2x/games/onpara/touchpanel.cpp index 0805658..a153d7f 100644 --- a/src/spice2x/games/onpara/touchpanel.cpp +++ b/src/spice2x/games/onpara/touchpanel.cpp @@ -4,6 +4,7 @@ #include "touchpanel.h" #include "util/logging.h" +#include "util/precise_timer.h" #include "touch/touch.h" using namespace std::chrono_literals; @@ -44,6 +45,9 @@ bool games::onpara::TouchPanelHandle::open(LPCWSTR lpFileName) { } int games::onpara::TouchPanelHandle::read(LPVOID lpBuffer, DWORD nNumberOfBytesToRead) { + + static thread_local timeutils::PreciseSleepTimer timer; + DWORD i; auto buffer = reinterpret_cast(lpBuffer); @@ -67,7 +71,7 @@ int games::onpara::TouchPanelHandle::read(LPVOID lpBuffer, DWORD nNumberOfBytesT enqueue_packet(report); // prevent cpu bullying - std::this_thread::sleep_for(1ms); + timer.sleep(1); } // copy from output queue @@ -133,4 +137,4 @@ bool games::onpara::TouchPanelHandle::close() { log_info("touchpanel", "Closed COM1 (Touch Panel)"); return true; -} \ No newline at end of file +} diff --git a/src/spice2x/hooks/audio/implementations/wave_out.cpp b/src/spice2x/hooks/audio/implementations/wave_out.cpp index cee1697..ab58de3 100644 --- a/src/spice2x/hooks/audio/implementations/wave_out.cpp +++ b/src/spice2x/hooks/audio/implementations/wave_out.cpp @@ -3,6 +3,7 @@ #include "hooks/audio/audio.h" #include "hooks/audio/backends/wasapi/audio_client.h" #include "hooks/audio/backends/wasapi/defs.h" +#include "util/precise_timer.h" static REFERENCE_TIME WASAPI_TARGET_REFTIME = TARGET_REFTIME; @@ -185,6 +186,7 @@ HRESULT WaveOutBackend::on_get_buffer(uint32_t num_frames_requested, BYTE **ppDa } HRESULT WaveOutBackend::on_release_buffer(uint32_t num_frames_written, DWORD dwFlags) { bool written = false; + timeutils::PreciseSleepTimer timer; // reset the dispatcher event ResetEvent(this->dispatcher_event); @@ -209,7 +211,7 @@ HRESULT WaveOutBackend::on_release_buffer(uint32_t num_frames_written, DWORD dwF // avoid pegging the CPU if (!written) { - Sleep(1); + timer.sleep(1); } } diff --git a/src/spice2x/launcher/launcher.cpp b/src/spice2x/launcher/launcher.cpp index e3a7db1..57ca956 100644 --- a/src/spice2x/launcher/launcher.cpp +++ b/src/spice2x/launcher/launcher.cpp @@ -108,6 +108,7 @@ #include "util/libutils.h" #include "util/logging.h" #include "util/peb.h" +#include "util/precise_timer.h" #include "util/socd_cleaner.h" #include "util/sysutils.h" #include "util/tapeled.h" @@ -1273,6 +1274,10 @@ int main_implementation(int argc, char *argv[]) { games::loveplus::CAMERA_ENABLE = true; } + if (options[launcher::Options::DisableHighResTimer].value_bool()) { + timeutils::TIMER_HACKS_DISABLE = true; + } + // API debugging if (api_debug && !cfg::CONFIGURATOR_STANDALONE) { API_CONTROLLER = std::make_unique(api_port, api_pass, api_pretty); @@ -2227,6 +2232,9 @@ int main_implementation(int argc, char *argv[]) { hooks::lang::early_init(); } + // use high resolution timer for this process + timeutils::set_timer_resolution(); + // load DLLs avs::core::load_dll(); avs::ea3::load_dll(); diff --git a/src/spice2x/launcher/options.cpp b/src/spice2x/launcher/options.cpp index 42944cf..43b3175 100644 --- a/src/spice2x/launcher/options.cpp +++ b/src/spice2x/launcher/options.cpp @@ -2769,7 +2769,17 @@ static const std::vector OPTION_DEFINITIONS = { .type = OptionType::Bool, .game_name = "Otoca D'or", .category = "Game Options (Peripherals)", - } + }, + { + // DisableHighResTimer + .title = "Use Legacy Timers", + .name = "notimerhacks", + .desc = "Disables high resolution timers, reverting to legacy behavior. " + "Recommended that you leave this OFF for optimal input latency, " + "unless you're on a resource-constrained system (e.g., old cabinet PC)", + .type = OptionType::Bool, + .category = "Development" + }, }; const std::vector &launcher::get_categories(Options::OptionsCategory category) { diff --git a/src/spice2x/launcher/options.h b/src/spice2x/launcher/options.h index 49974c5..e023cc3 100644 --- a/src/spice2x/launcher/options.h +++ b/src/spice2x/launcher/options.h @@ -279,6 +279,7 @@ namespace launcher { LovePlusPrinterJPGQuality, OptionConflictResolution, OtocaCamHook, + DisableHighResTimer, }; enum class OptionsCategory { diff --git a/src/spice2x/misc/eamuse.cpp b/src/spice2x/misc/eamuse.cpp index d1c9ec8..467d812 100644 --- a/src/spice2x/misc/eamuse.cpp +++ b/src/spice2x/misc/eamuse.cpp @@ -9,6 +9,7 @@ #include "rawinput/rawinput.h" #include "games/sdvx/sdvx.h" #include "util/logging.h" +#include "util/precise_timer.h" #include "util/time.h" #include "util/utils.h" #include "overlay/overlay.h" @@ -286,6 +287,7 @@ void eamuse_coin_start_thread() { COIN_INPUT_THREAD = new std::thread([]() { auto overlay_buttons = games::get_buttons_overlay(eamuse_get_game()); static bool COIN_INPUT_KEY_STATE = false; + timeutils::PreciseSleepTimer timer; while (COIN_INPUT_THREAD_ACTIVE) { // check input key @@ -305,7 +307,7 @@ void eamuse_coin_start_thread() { } // once every two frames - Sleep(1000 / 30); + timer.sleep(1000 / 30); } }); } @@ -340,6 +342,7 @@ void eamuse_pin_macro_start_thread() { size_t pin_index[2] = {PIN_MACRO_VALUES[0].length(), PIN_MACRO_VALUES[1].length()}; std::optional active_unit = std::nullopt; + timeutils::PreciseSleepTimer timer; while (PIN_MACRO_THREAD_ACTIVE) { // wait for key press @@ -359,7 +362,7 @@ void eamuse_pin_macro_start_thread() { } if (!active_unit.has_value()) { - Sleep(20); + timer.sleep(20); continue; } } @@ -375,22 +378,22 @@ void eamuse_pin_macro_start_thread() { eamuse_set_keypad_overrides(unit, keypad_overrides[char_index]); } pin_index[unit]++; - Sleep(100); + timer.sleep(100); // clear eamuse_set_keypad_overrides(unit, 0); - Sleep(50); + timer.sleep(50); // end of PIN if (pin_index[unit] == PIN_MACRO_VALUES[unit].length()) { active_unit = std::nullopt; - Sleep(120); + timer.sleep(120); } continue; } - Sleep(200); + timer.sleep(200); } }); } diff --git a/src/spice2x/rawinput/rawinput.cpp b/src/spice2x/rawinput/rawinput.cpp index e13c9e5..7dbf27c 100644 --- a/src/spice2x/rawinput/rawinput.cpp +++ b/src/spice2x/rawinput/rawinput.cpp @@ -9,6 +9,7 @@ #include "util/logging.h" #include "external/robin_hood.h" +#include "util/precise_timer.h" #include "util/time.h" #include "util/utils.h" @@ -155,8 +156,9 @@ void rawinput::RawInputManager::input_hwnd_create() { }); // wait for window creation being done + timeutils::PreciseSleepTimer timer; while (!this->input_hwnd) { - Sleep(1); + timer.sleep(1); } } @@ -2983,4 +2985,4 @@ void rawinput::RawInputManager::remove_callback_midi(void * data, const std::fun [data, callback](MidiCallback const &cb) { return cb.data == data && cb.f.target() == callback.target(); }), this->callback_midi.end()); -} \ No newline at end of file +} diff --git a/src/spice2x/reader/reader.cpp b/src/spice2x/reader/reader.cpp index 7fbe5b0..690ad01 100644 --- a/src/spice2x/reader/reader.cpp +++ b/src/spice2x/reader/reader.cpp @@ -7,6 +7,7 @@ #include "util/logging.h" #include "misc/eamuse.h" +#include "util/precise_timer.h" #include "util/utils.h" #include "structuredmessage.h" @@ -217,6 +218,7 @@ bool Reader::set_comm_state(DWORD BaudRate) { } bool Reader::wait_for_handshake() { + timeutils::PreciseSleepTimer timer; // baud rates DWORD baud_rates[] = { CBR_57600, CBR_38400, CBR_19200, CBR_9600 }; @@ -268,7 +270,7 @@ bool Reader::wait_for_handshake() { } // sleep - Sleep(50); + timer.sleep(50); } log_warning("reader", "{}: no handshake received for {} baud", this->port, baud_rates[i]); @@ -405,6 +407,7 @@ void start_reader_thread(const std::string &port, int id) { READER_THREAD_RUNNING = true; READER_THREADS.push_back(new std::thread([port, id]() { log_info("reader", "{}: starting reader thread", port); + timeutils::PreciseSleepTimer timer; while (READER_THREAD_RUNNING) { @@ -442,21 +445,22 @@ void start_reader_thread(const std::string &port, int id) { } if (did_read_card) { - Sleep(2500); + timer.sleep(2500); } - Sleep(20); + timer.sleep(20); } } // sleep between reader connection retries if (READER_THREAD_RUNNING) - Sleep(5000); + timer.sleep(5000); } })); // wait for thread to start - Sleep(10); + timeutils::PreciseSleepTimer timer; + timer.sleep(10); } void stop_reader_thread() { diff --git a/src/spice2x/sdk/sample/v0/cpp/v0_cpp.cpp b/src/spice2x/sdk/sample/v0/cpp/v0_cpp.cpp index c18c0d8..f6c0441 100644 --- a/src/spice2x/sdk/sample/v0/cpp/v0_cpp.cpp +++ b/src/spice2x/sdk/sample/v0/cpp/v0_cpp.cpp @@ -93,6 +93,6 @@ static void worker_thread_main(std::stop_token stop_token) { } } - std::this_thread::sleep_for(std::chrono::milliseconds(1)); + Sleep(1); } } diff --git a/src/spice2x/util/precise_timer.cpp b/src/spice2x/util/precise_timer.cpp new file mode 100644 index 0000000..394a607 --- /dev/null +++ b/src/spice2x/util/precise_timer.cpp @@ -0,0 +1,188 @@ +#define _WIN32_WINNT 0x0602 + +#include "precise_timer.h" + +#define WIN32_NO_STATUS +#include +#undef WIN32_NO_STATUS + +#include + +#if !SPICE_XP +#include +#endif + +#include +#include +#include +#include "util/logging.h" + +#ifdef min +#undef min +#endif + +#ifdef max +#undef max +#endif + +#define PERIOD 1 +#define TOLERANCE 0.02 + +#ifndef PROCESS_POWER_THROTTLING_IGNORE_TIMER_RESOLUTION +#define PROCESS_POWER_THROTTLING_IGNORE_TIMER_RESOLUTION 0x4 +#endif + +namespace timeutils { + + bool TIMER_HACKS_DISABLE = false; + + static TimerHandle get_precise_sleep_timer(); + static void destroy_precise_sleep_timer(TimerHandle timer); + static void precise_sleep(TimerHandle timer, double seconds); + + void set_timer_resolution() { + if (TIMER_HACKS_DISABLE) { + return; + } + +#if !SPICE_XP + + // make a call to opt out of power throttling + // (requested timer resolution being ignored when window is occluded or minimized) + // SetProcessInformation is win8+ + const auto kernel32 = GetModuleHandleA("kernel32.dll"); + if (kernel32) { + const auto SetProcessInformation_addr = + reinterpret_cast( + GetProcAddress(kernel32, "SetProcessInformation")); + + if (SetProcessInformation_addr) { + PROCESS_POWER_THROTTLING_STATE state {}; + state.Version = PROCESS_POWER_THROTTLING_CURRENT_VERSION; + state.ControlMask = PROCESS_POWER_THROTTLING_IGNORE_TIMER_RESOLUTION; + state.StateMask = 0; + const auto ret = SetProcessInformation_addr( + GetCurrentProcess(), + ProcessPowerThrottling, + &state, + sizeof(state)); + + log_info( + "timeutils", + "SetProcessInformation called to disable timer resolution throttling, returned {}", + ret); + } + } + +#endif + + const auto ret = timeBeginPeriod(PERIOD); + log_info( + "timeutils", + "timeBeginPeriod({}) called, returned {}", + PERIOD, + ret); + // and then we never call timeEndPeriod(); + // the game will also call this but the OS will honor the lowest value (1 above) + } + +#if !SPICE_XP + + // timerSleep from https://blog.bearcats.nl/perfect-sleep-function/ + static void timerSleep(HANDLE timer, double seconds) { + using namespace std::chrono; + + auto t = high_resolution_clock::now(); + const auto target = t + nanoseconds(int64_t(seconds * 1e9)); + const int64_t maxTicks = PERIOD * 9'500; + for (;;) { + const int64_t remaining = (target - t).count(); + int64_t ticks = (remaining - TOLERANCE) / 100; + if (ticks <= 0) { + break; + } + if (ticks > maxTicks) { + ticks = maxTicks; + } + + LARGE_INTEGER due; + due.QuadPart = -ticks; + SetWaitableTimerEx(timer, &due, 0, NULL, NULL, NULL, 0); + WaitForSingleObject(timer, INFINITE); + t = high_resolution_clock::now(); + } + + while (high_resolution_clock::now() < target) { + YieldProcessor(); + } + } + +#endif + + static TimerHandle get_precise_sleep_timer() { + TimerHandle timer = nullptr; + if (TIMER_HACKS_DISABLE) { + return timer; + } + + // CreateWaitableTimerExW is Vista+ + // CREATE_WAITABLE_TIMER_HIGH_RESOLUTION win10 1803+ + +#if !SPICE_XP + + timer = CreateWaitableTimerExW( + NULL, + NULL, + CREATE_WAITABLE_TIMER_HIGH_RESOLUTION, + TIMER_ALL_ACCESS); + +#endif + + return timer; + } + + static void destroy_precise_sleep_timer(TimerHandle timer) { + if (timer) { + CloseHandle(timer); + } + } + + static void precise_sleep(TimerHandle timer, double seconds) { + if (!std::isfinite(seconds) || seconds <= 0.0) { + return; + } + +#if !SPICE_XP + if (timer) { + timerSleep(timer, seconds); + return; + } +#endif + + // robustSleep is too CPU heavy so we will stick to Sleep() with + // timeBeginPeriod, which has an error at most 1ms + const auto milliseconds = std::ceil(seconds * 1000.0); + if (milliseconds >= std::numeric_limits::max()) { + Sleep(std::numeric_limits::max()); + return; + } + + Sleep(static_cast(milliseconds)); + } + + // RAII wrapper + + PreciseSleepTimer::PreciseSleepTimer() : timer(get_precise_sleep_timer()) {} + + PreciseSleepTimer::~PreciseSleepTimer() { + destroy_precise_sleep_timer(timer); + } + + void PreciseSleepTimer::sleep(uint32_t ms) const { + sleep(std::chrono::milliseconds(ms)); + } + + void PreciseSleepTimer::sleep_seconds(double seconds) const { + precise_sleep(timer, seconds); + } +} diff --git a/src/spice2x/util/precise_timer.h b/src/spice2x/util/precise_timer.h new file mode 100644 index 0000000..788a47a --- /dev/null +++ b/src/spice2x/util/precise_timer.h @@ -0,0 +1,36 @@ +#pragma once + +#include +#include + +namespace timeutils { + + extern bool TIMER_HACKS_DISABLE; + + void set_timer_resolution(); + + using TimerHandle = void *; + + class PreciseSleepTimer { + public: + PreciseSleepTimer(); + ~PreciseSleepTimer(); + + PreciseSleepTimer(const PreciseSleepTimer &) = delete; + PreciseSleepTimer &operator=(const PreciseSleepTimer &) = delete; + PreciseSleepTimer(PreciseSleepTimer &&) = delete; + PreciseSleepTimer &operator=(PreciseSleepTimer &&) = delete; + + void sleep(uint32_t ms) const; + + template + void sleep(std::chrono::duration duration) const { + sleep_seconds(std::chrono::duration(duration).count()); + } + + private: + void sleep_seconds(double seconds) const; + + TimerHandle timer; + }; +}