diff --git a/port/fast3d/gfx_sdl2.cpp b/port/fast3d/gfx_sdl2.cpp index 8e9dcfaf7..bd6e4def7 100644 --- a/port/fast3d/gfx_sdl2.cpp +++ b/port/fast3d/gfx_sdl2.cpp @@ -9,24 +9,6 @@ #include "gfx_window_manager_api.h" #include "gfx_screen_config.h" -// define a way to yield the cpu when in a busy wait -#ifdef PLATFORM_WIN32 -// winapi provides a yield macro -#include -#define DO_YIELD() YieldProcessor() -#elif defined(PLATFORM_X86) || defined(PLATFORM_X86_64) -// this should work even if the code is not built with SSE enabled, at least on gcc and clang, -// but if it doesn't we'll have to use __builtin_ia32_pause() or something -#include -#define DO_YIELD() _mm_pause() -#elif defined(PLATFORM_ARM) -// same as YieldProcessor() on ARM Windows -#define DO_YIELD() __asm__ volatile("dmb ishst\n\tyield":::"memory") -#else -// fuck it -#define DO_YIELD() do { } while (0) -#endif - static SDL_Window* wnd; static SDL_GLContext ctx; static SDL_Renderer* renderer; @@ -44,28 +26,9 @@ static int target_fps = 120; // above 60 since vsync is enabled by default static uint64_t previous_time; static uint64_t qpc_freq; -#ifdef PLATFORM_WIN32 -// on win32 we use waitable timers instead of nanosleep -typedef HANDLE WINAPI (*CREATEWAITABLETIMEREXAFN)(LPSECURITY_ATTRIBUTES, LPCSTR, DWORD, DWORD); -static HANDLE timer; -static CREATEWAITABLETIMEREXAFN pfnCreateWaitableTimerExA; -#endif - #define FRAME_INTERVAL_US_NUMERATOR 1000000 #define FRAME_INTERVAL_US_DENOMINATOR (target_fps) -static inline void do_sleep(const int64_t left) { -#ifdef PLATFORM_WIN32 - static LARGE_INTEGER li; - li.QuadPart = -left; - SetWaitableTimer(timer, &li, 0, nullptr, nullptr, false); - WaitForSingleObject(timer, INFINITE); -#else - const timespec spec = { 0, left * 100 }; - nanosleep(&spec, nullptr); -#endif -} - static void set_fullscreen(bool on, bool call_callback) { if (fullscreen_state == on) { return; @@ -181,20 +144,6 @@ static void gfx_sdl_init(const char* game_name, const char* gfx_api_name, bool s qpc_freq = SDL_GetPerformanceFrequency(); -#ifdef PLATFORM_WIN32 - // this function is only present on Vista+, so try to import it from kernel32 by hand - pfnCreateWaitableTimerExA = (CREATEWAITABLETIMEREXAFN)GetProcAddress(GetModuleHandleA("kernel32.dll"), "CreateWaitableTimerExA"); - if (pfnCreateWaitableTimerExA) { - // function exists, try to create a hires timer - timer = pfnCreateWaitableTimerExA(nullptr, nullptr, CREATE_WAITABLE_TIMER_HIGH_RESOLUTION, TIMER_ALL_ACCESS); - } - if (!timer) { - // no function or hires timers not supported, fallback to lower resolution timer - sysLogPrintf(LOG_WARNING, "SDL: hires waitable timers not available"); - timer = CreateWaitableTimerA(nullptr, false, nullptr); - } -#endif - set_maximize_window(start_maximized); set_fullscreen(start_in_fullscreen, false); } @@ -272,11 +221,11 @@ static inline void sync_framerate_with_timer(void) { // We want to exit a bit early, so we can busy-wait the rest to never miss the deadline left -= 15000UL; if (left > 0) { - do_sleep(left); + sysSleep(left); } do { - DO_YIELD(); + sysCpuRelax(); t = qpc_to_100ns(SDL_GetPerformanceCounter()); } while ((int64_t)t < next); diff --git a/port/include/config.h b/port/include/config.h index 5bc12cf11..154745454 100644 --- a/port/include/config.h +++ b/port/include/config.h @@ -16,5 +16,6 @@ s32 configSave(const char *fname); // registers a variable in the config file // this should be done before configInit() is called, preferably in a module constructor void configRegisterInt(const char *key, s32 *var, s32 min, s32 max); +void configRegisterUInt(const char* key, u32* var, u32 min, u32 max); void configRegisterFloat(const char *key, f32 *var, f32 min, f32 max); void configRegisterString(const char *key, char *var, u32 maxstr); diff --git a/port/include/system.h b/port/include/system.h index 94158802a..84d568382 100644 --- a/port/include/system.h +++ b/port/include/system.h @@ -34,6 +34,12 @@ void *sysMemAlloc(const u32 size); void *sysMemZeroAlloc(const u32 size); void sysMemFree(void *ptr); +// hns is specified in 100ns units +void sysSleep(const s64 hns); + +// yield CPU if supported (e.g. during a busy loop) +void sysCpuRelax(void); + void crashInit(void); void crashShutdown(void); diff --git a/port/src/main.c b/port/src/main.c index 2cf5cffef..f3c47c556 100644 --- a/port/src/main.c +++ b/port/src/main.c @@ -36,6 +36,7 @@ u32 g_VmNumPageReplaces = 0; u8 g_VmShowStats = 0; s32 g_TickRateDiv = 1; +s32 g_TickExtraSleep = true; s32 g_SkipIntro = false; @@ -161,6 +162,7 @@ PD_CONSTRUCTOR static void gameConfigInit(void) configRegisterInt("Game.CenterHUD", &g_HudCenter, 0, 2); configRegisterFloat("Game.ScreenShakeIntensity", &g_ViShakeIntensityMult, 0.f, 10.f); configRegisterInt("Game.TickRateDivisor", &g_TickRateDiv, 0, 10); + configRegisterInt("Game.ExtraSleep", &g_TickExtraSleep, 0, 1); configRegisterInt("Game.SkipIntro", &g_SkipIntro, 0, 1); configRegisterInt("Game.DisableMpDeathMusic", &g_MusicDisableMpDeath, 0, 1); configRegisterInt("Game.GEMuzzleFlashes", &g_BgunGeMuzzleFlashes, 0, 1); diff --git a/port/src/pdmain.c b/port/src/pdmain.c index 9b64d8da5..aeeb6186d 100644 --- a/port/src/pdmain.c +++ b/port/src/pdmain.c @@ -73,6 +73,7 @@ #include "data.h" #include "types.h" #include "debugger.h" +#include "system.h" extern u8 *g_MempHeap; extern u32 g_MempHeapSize; @@ -500,6 +501,9 @@ void mainLoop(void) mainTick(); schedEndFrame(&g_Sched); } + if (g_TickExtraSleep) { + sysSleep(EXTRA_SLEEP_TIME); + } } lvStop(); diff --git a/port/src/system.c b/port/src/system.c index 538dd1e87..b3c9948cb 100644 --- a/port/src/system.c +++ b/port/src/system.c @@ -10,6 +10,38 @@ #include "platform.h" #include "system.h" +#ifdef PLATFORM_WIN32 + +#include + +// on win32 we use waitable timers instead of nanosleep +typedef HANDLE WINAPI (*CREATEWAITABLETIMEREXAFN)(LPSECURITY_ATTRIBUTES, LPCSTR, DWORD, DWORD); +static HANDLE timer; +static CREATEWAITABLETIMEREXAFN pfnCreateWaitableTimerExA; + +// winapi also provides a yield macro +#define DO_YIELD() YieldProcessor() + +#else + +#include + +// figure out how to yield +#if defined(PLATFORM_X86) || defined(PLATFORM_X86_64) +// this should work even if the code is not built with SSE enabled, at least on gcc and clang, +// but if it doesn't we'll have to use __builtin_ia32_pause() or something +#include +#define DO_YIELD() _mm_pause() +#elif defined(PLATFORM_ARM) +// same as YieldProcessor() on ARM Windows +#define DO_YIELD() __asm__ volatile("dmb ishst\n\tyield":::"memory") +#else +// fuck it +#define DO_YIELD() do { } while (0) +#endif + +#endif + #define LOG_FNAME "pd.log" #define CRASHLOG_FNAME "pd.crash.log" #define USEC_IN_SEC 1000000ULL @@ -60,6 +92,20 @@ void sysInit(void) const time_t curtime = time(NULL); strftime(timestr, sizeof(timestr), "%d %b %Y %H:%M:%S", localtime(&curtime)); sysLogPrintf(LOG_NOTE, "startup date: %s", timestr); + +#ifdef PLATFORM_WIN32 + // this function is only present on Vista+, so try to import it from kernel32 by hand + pfnCreateWaitableTimerExA = (CREATEWAITABLETIMEREXAFN)GetProcAddress(GetModuleHandleA("kernel32.dll"), "CreateWaitableTimerExA"); + if (pfnCreateWaitableTimerExA) { + // function exists, try to create a hires timer + timer = pfnCreateWaitableTimerExA(NULL, NULL, CREATE_WAITABLE_TIMER_HIGH_RESOLUTION, TIMER_ALL_ACCESS); + } + if (!timer) { + // no function or hires timers not supported, fallback to lower resolution timer + sysLogPrintf(LOG_WARNING, "SYS: hires waitable timers not available"); + timer = CreateWaitableTimerA(NULL, FALSE, NULL); + } +#endif } s32 sysArgCheck(const char *arg) @@ -238,3 +284,21 @@ void sysMemFree(void *ptr) { free(ptr); } + +void sysSleep(const s64 hns) +{ +#ifdef PLATFORM_WIN32 + static LARGE_INTEGER li; + li.QuadPart = -hns; + SetWaitableTimer(timer, &li, 0, NULL, NULL, FALSE); + WaitForSingleObject(timer, INFINITE); +#else + const timespec spec = { 0, hns * 100 }; + nanosleep(&spec, NULL); +#endif +} + +void sysCpuRelax(void) +{ + DO_YIELD(); +} diff --git a/src/game/timing.c b/src/game/timing.c index 9a7c8e380..747c7ed24 100644 --- a/src/game/timing.c +++ b/src/game/timing.c @@ -4,6 +4,9 @@ #include "bss.h" #include "data.h" #include "types.h" +#ifndef PLATFORM_N64 +#include "system.h" +#endif void frametimeInit(void) { @@ -41,6 +44,12 @@ void frametimeCalculate(void) diffframe60 = (g_Vars.lostframetime60t + diffframet + CYCLES_PER_FRAME / 2) / CYCLES_PER_FRAME; diffframe240 = (g_Vars.lostframetime240t + diffframet + CYCLES_PER_FRAME / 2 / 4) / (CYCLES_PER_FRAME / 4); + +#ifndef PLATFORM_N64 + if (g_TickExtraSleep) { + sysSleep(EXTRA_SLEEP_TIME); + } +#endif } while (g_Vars.mininc60 && diffframe60 < g_Vars.mininc60); g_Vars.lostframetime60t = g_Vars.lostframetime60t + diffframet - diffframe60 * CYCLES_PER_FRAME; diff --git a/src/include/constants.h b/src/include/constants.h index 9cd1e9d2c..c501b540e 100644 --- a/src/include/constants.h +++ b/src/include/constants.h @@ -4723,6 +4723,8 @@ enum weaponnum { #define CROSSHAIR_HEALTH_ON_GREEN 1 #define CROSSHAIR_HEALTH_ON_WHITE 2 +#define EXTRA_SLEEP_TIME 1000LL // 100us + #endif #endif diff --git a/src/include/data.h b/src/include/data.h index e513c6f10..c59d6ba99 100644 --- a/src/include/data.h +++ b/src/include/data.h @@ -547,6 +547,7 @@ extern s32 g_BlurFb; extern s32 g_BlurFbCapTimer; extern bool g_BlurFbDirty; extern s32 g_TickRateDiv; +extern s32 g_TickExtraSleep; extern s32 g_MusicDisableMpDeath; extern s32 g_BgunGeMuzzleFlashes; extern s32 g_FileAutoSelect;