diff --git a/port/fast3d/gfx_sdl2.cpp b/port/fast3d/gfx_sdl2.cpp index d6eefb1c6..274bf1230 100644 --- a/port/fast3d/gfx_sdl2.cpp +++ b/port/fast3d/gfx_sdl2.cpp @@ -1,19 +1,31 @@ #include #include -#ifdef __MINGW32__ -// mingw has its nanosleep implementation in pthread.h for some reson -#include -#else #include #include -#endif +#include "platform.h" #include "system.h" #include "gfx_window_manager_api.h" #include "gfx_screen_config.h" -#define GFX_BACKEND_NAME "SDL" +// 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; @@ -27,15 +39,32 @@ static bool fullscreen_state; static bool is_running = true; static void (*on_fullscreen_changed_callback)(bool is_now_fullscreen); -static uint64_t previous_time; - 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; @@ -55,11 +84,6 @@ static void gfx_sdl_get_active_window_refresh_rate(uint32_t* refresh_rate) { *refresh_rate = mode.refresh_rate; } -static inline void do_sleep(const uint64_t ns) { - const timespec spec = { 0, ns }; - nanosleep(&spec, nullptr); -} - static void gfx_sdl_init(const char* game_name, const char* gfx_api_name, bool start_in_fullscreen, uint32_t width, uint32_t height, int32_t posX, int32_t posY) { window_width = width; @@ -143,6 +167,20 @@ 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_fullscreen(start_in_fullscreen, false); } @@ -211,11 +249,18 @@ static inline void sync_framerate_with_timer(void) { t = qpc_to_100ns(SDL_GetPerformanceCounter()); const int64_t next = previous_time + 10 * FRAME_INTERVAL_US_NUMERATOR / FRAME_INTERVAL_US_DENOMINATOR; - const int64_t left = next - t; + int64_t left = next - t; + // 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 * 100); + do_sleep(left); } + do { + DO_YIELD(); + t = qpc_to_100ns(SDL_GetPerformanceCounter()); + } while ((int64_t)t < next); + t = qpc_to_100ns(SDL_GetPerformanceCounter()); if (left > 0 && t - next < 10000) { // In case it takes some time for the application to wake up after sleep, diff --git a/port/src/main.c b/port/src/main.c index b063f707d..c104ce5a1 100644 --- a/port/src/main.c +++ b/port/src/main.c @@ -35,6 +35,8 @@ u32 g_VmNumPageMisses = 0; u32 g_VmNumPageReplaces = 0; u8 g_VmShowStats = 0; +s32 g_TickRateDiv = 1; + extern s32 g_StageNum; s32 bootGetMemSize(void) @@ -136,6 +138,7 @@ PD_CONSTRUCTOR static void gameConfigInit(void) configRegisterInt("Game.MemorySize", &g_OsMemSizeMb, 4, 2048); configRegisterInt("Game.CenterHUD", &g_HudCenter, 0, 1); configRegisterFloat("Game.ScreenShakeIntensity", &g_ViShakeIntensityMult, 0.f, 10.f); + configRegisterInt("Game.TickRateDivisor", &g_TickRateDiv, 0, 10); for (s32 j = 0; j < MAX_PLAYERS; ++j) { const s32 i = j + 1; configRegisterFloat(strFmt("Game.Player%d.FovY", i), &g_PlayerExtCfg[j].fovy, 5.f, 175.f); diff --git a/port/src/pdmain.c b/port/src/pdmain.c index 410ce8529..9b64d8da5 100644 --- a/port/src/pdmain.c +++ b/port/src/pdmain.c @@ -495,7 +495,7 @@ void mainLoop(void) while (g_MainChangeToStageNum < 0) { const s32 cycles = osGetCount() - g_Vars.thisframestartt; - if (cycles >= g_Vars.mininc60 * CYCLES_PER_FRAME - CYCLES_PER_FRAME / 2) { + if (!g_Vars.mininc60 || (cycles >= g_Vars.mininc60 * CYCLES_PER_FRAME - CYCLES_PER_FRAME / 2)) { schedStartFrame(&g_Sched); mainTick(); schedEndFrame(&g_Sched); diff --git a/src/game/activemenutick.c b/src/game/activemenutick.c index 29d09993b..39b66f02c 100644 --- a/src/game/activemenutick.c +++ b/src/game/activemenutick.c @@ -411,7 +411,7 @@ void amTick(void) // Update alpha of slots so they fade in if (g_AmMenus[g_AmIndex].alphafrac < 1) { - g_AmMenus[g_AmIndex].alphafrac += LVUPDATE60FREAL() / 30.0f; + g_AmMenus[g_AmIndex].alphafrac += (f32)g_Vars.lvupdate240 / (4.f * 30.0f); } if (g_AmMenus[g_AmIndex].alphafrac > 1) { @@ -419,7 +419,7 @@ void amTick(void) } // Make selection border pulsate - g_AmMenus[g_AmIndex].selpulse += LVUPDATE60FREAL() / 5.0f; + g_AmMenus[g_AmIndex].selpulse += (f32)g_Vars.lvupdate240 / (4.f * 5.0f); if (g_AmMenus[g_AmIndex].selpulse > 18.849555969238f) { g_AmMenus[g_AmIndex].selpulse -= 18.849555969238f; diff --git a/src/game/bondeyespy.c b/src/game/bondeyespy.c index bd9439e0c..fe281cc84 100644 --- a/src/game/bondeyespy.c +++ b/src/game/bondeyespy.c @@ -922,8 +922,6 @@ void eyespyProcessInput(bool allowbuttons) f32 mdx, mdy; inputMouseGetScaledDelta(&mdx, &mdy); if (mdx || mdy) { - mdx *= g_Vars.lvupdate60freal; - mdy *= g_Vars.lvupdate60freal; if (g_Vars.currentplayerstats && !optionsGetForwardPitch(g_Vars.currentplayerstats->mpindex)) { mdy = -mdy; } diff --git a/src/game/bondgun.c b/src/game/bondgun.c index c7b3b4048..efe979624 100644 --- a/src/game/bondgun.c +++ b/src/game/bondgun.c @@ -10918,6 +10918,7 @@ void bgunRender(Gfx **gdlptr) struct modelrenderdata renderdata = {NULL, true, 3}; // 10c struct player *player; s32 i; + const u32 wnorm = mtx00016dcc(0, 300); static bool renderhand = true; // var800702dc @@ -10983,7 +10984,7 @@ void bgunRender(Gfx **gdlptr) gSPLookAt(gdl++, camGetLookAt()); } - gSPPerspNormalize(gdl++, mtx00016dcc(0, 300)); + gSPPerspNormalize(gdl++, wnorm); // There is support for guns having a TV screen on them // but no guns have this model part so it's not used. @@ -11158,7 +11159,17 @@ void bgunRender(Gfx **gdlptr) } } +#ifndef PLATFORM_N64 + // put the casings into the same Z range as the gun + gSPPerspNormalize(gdl++, wnorm); +#endif + casingsRender(&gdl); + +#ifndef PLATFORM_N64 + gSPPerspNormalize(gdl++, viGetPerspScale()); +#endif + zbufSwap(); gdl = zbufConfigureRdp(gdl); diff --git a/src/game/bondmove.c b/src/game/bondmove.c index 1a74a6fd2..45f75ea6c 100644 --- a/src/game/bondmove.c +++ b/src/game/bondmove.c @@ -714,6 +714,7 @@ void bmoveProcessInput(bool allowc1x, bool allowc1y, bool allowc1buttons, bool i #ifndef PLATFORM_N64 const bool allowmlook = (g_Vars.currentplayernum == 0) && (allowc1x || allowc1y); bool allowmcross = false; + const f32 mlookscale = g_Vars.lvupdate240 ? (4.f / (f32)g_Vars.lvupdate240) : 4.f; #endif controlmode = optionsGetControlMode(g_Vars.currentplayerstats->mpindex); @@ -2017,7 +2018,7 @@ void bmoveProcessInput(bool allowc1x, bool allowc1y, bool allowc1buttons, bool i } #ifndef PLATFORM_N64 - fVar25 += movedata.freelookdy; + fVar25 += movedata.freelookdy * mlookscale; #endif g_Vars.currentplayer->speedverta = -fVar25 * tmp; @@ -2058,7 +2059,7 @@ void bmoveProcessInput(bool allowc1x, bool allowc1y, bool allowc1buttons, bool i } #ifndef PLATFORM_N64 - fVar25 += movedata.freelookdx; + fVar25 += movedata.freelookdx * mlookscale; #endif g_Vars.currentplayer->speedthetacontrol = fVar25 * tmp; diff --git a/src/game/footstep.c b/src/game/footstep.c index ddbcc3546..2480ad470 100644 --- a/src/game/footstep.c +++ b/src/game/footstep.c @@ -177,9 +177,10 @@ void footstepCheckDefault(struct chrdata *chr) chr->footstep = 2; } } else { -#ifndef PLATFORM_N64 // fix spamming footsteps for 60fps port - if (g_Vars.lvupdate240 == LV_SLOMO_TICK_CAP) { - s32 roundedframe = (s32)(frame*2); +#ifndef PLATFORM_N64 + // fix spamming footsteps at >= 60fps + if (g_Vars.lvupdate240 < 4) { + const s32 roundedframe = (s32)(frame * (4.f / (f32)g_Vars.lvupdate240)); if (roundedframe == (g_FootstepAnims[i].frame1*2) && prevframe < g_FootstepAnims[i].frame1) { chr->footstep = 1; } else if (roundedframe == (g_FootstepAnims[i].frame2*2) && prevframe < g_FootstepAnims[i].frame2) { diff --git a/src/game/lv.c b/src/game/lv.c index 9f3fe3c0c..779d2569b 100644 --- a/src/game/lv.c +++ b/src/game/lv.c @@ -2057,7 +2057,7 @@ void lvTick(void) g_Vars.lvupdate240 = 0; for (j = 0; j < PLAYERCOUNT(); j++) { - g_Vars.players[j]->joybutinhibit = 0xefffffff; + g_Vars.players[j]->joybutinhibit = 0xffffefff; } } else { s32 slowmo = lvGetSlowMotionType(); @@ -2066,7 +2066,7 @@ void lvTick(void) if (slowmo == SLOWMOTION_ON) { if (g_Vars.speedpillon == false || g_Vars.in_cutscene) { if (g_Vars.lvupdate240 > LV_SLOMO_TICK_CAP) { - g_Vars.lvupdate240 = LV_SLOMO_TICK_CAP; + g_Vars.lvupdate240 = LV_SLOMO_TICK_RATE; } } } else if (slowmo == SLOWMOTION_SMART) { @@ -2097,7 +2097,7 @@ void lvTick(void) if (foundnearbychr) { if (g_Vars.lvupdate240 > LV_SLOMO_TICK_CAP) { - g_Vars.lvupdate240 = LV_SLOMO_TICK_CAP; + g_Vars.lvupdate240 = LV_SLOMO_TICK_RATE; } } else { if (g_Vars.lvupdate240 > TICKS(8)) { @@ -2106,7 +2106,7 @@ void lvTick(void) } } else { if (g_Vars.lvupdate240 > LV_SLOMO_TICK_CAP) { - g_Vars.lvupdate240 = LV_SLOMO_TICK_CAP; + g_Vars.lvupdate240 = LV_SLOMO_TICK_RATE; } } } @@ -2114,7 +2114,7 @@ void lvTick(void) // Slow motion settings are off if (g_Vars.speedpillon && g_Vars.in_cutscene == false) { if (g_Vars.lvupdate240 > LV_SLOMO_TICK_CAP) { - g_Vars.lvupdate240 = LV_SLOMO_TICK_CAP; + g_Vars.lvupdate240 = LV_SLOMO_TICK_RATE; } } } diff --git a/src/game/player.c b/src/game/player.c index 759d97d9f..0b609db8a 100644 --- a/src/game/player.c +++ b/src/game/player.c @@ -3593,8 +3593,8 @@ void playerTick(bool arg0) if (g_Vars.currentplayerstats && !optionsGetForwardPitch(g_Vars.currentplayerstats->mpindex)) { mdy = -mdy; } - sp178 += mdy * LVUPDATE60FREAL() * 0.00025f; - sp174 -= mdx * LVUPDATE60FREAL() * 0.00025f; + sp178 += mdy * 0.00025f; + sp174 -= mdx * 0.00025f; } } #endif diff --git a/src/game/propobj.c b/src/game/propobj.c index 6b5922a6c..c3bf9b5bc 100644 --- a/src/game/propobj.c +++ b/src/game/propobj.c @@ -7679,7 +7679,12 @@ void doorTick(struct prop *doorprop) } // Update frac +#ifdef PLATFORM_N64 if (door->lastcalc60 < g_Vars.lvframe60 || g_Vars.lvupdate240 == 0) { +#else + // lastcalc60 actually stores lvframe240 + if (door->lastcalc60 < g_Vars.lvframe240 || g_Vars.lvupdate240 == 0) { +#endif doorsCalcFrac(door); } @@ -9010,6 +9015,12 @@ void autogunInitMatrices(struct prop *prop, Mtxf *mtx) void autogunTickShoot(struct prop *autogunprop) { +#ifndef PLATFORM_N64 + // HACK: fire every other tick at 60+ fps and every tick at <60 fps + if (g_Vars.lvupdate240 <= TICKS(4) && (g_Vars.lvframe60 & 1)) { + return; + } +#endif if (!lvIsPaused()) { struct autogunobj *autogun = (struct autogunobj *) autogunprop->obj; struct defaultobj *obj = autogunprop->obj; @@ -20345,7 +20356,11 @@ void doorsCalcFrac(struct doorobj *door) func0f08d460(loopdoor); } +#ifdef PLATFORM_N64 loopdoor->lastcalc60 = g_Vars.lvframe60; +#else + loopdoor->lastcalc60 = g_Vars.lvframe240; // actually use the 240hz counter for higher framerates +#endif loopdoor = loopdoor->sibling; if (loopdoor == door) { diff --git a/src/game/timing.c b/src/game/timing.c index 3739af362..9a7c8e380 100644 --- a/src/game/timing.c +++ b/src/game/timing.c @@ -41,12 +41,16 @@ 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); - } while (diffframe60 < g_Vars.mininc60); + } while (g_Vars.mininc60 && diffframe60 < g_Vars.mininc60); g_Vars.lostframetime60t = g_Vars.lostframetime60t + diffframet - diffframe60 * CYCLES_PER_FRAME; g_Vars.lostframetime240t = g_Vars.lostframetime240t + diffframet - diffframe240 * (CYCLES_PER_FRAME / 4); +#ifdef PLATFORM_N64 g_Vars.mininc60 = 1; +#else + g_Vars.mininc60 = g_TickRateDiv; +#endif frametimeApply(diffframe60, diffframe240, count); } diff --git a/src/include/data.h b/src/include/data.h index 5a897577d..e94aaa7c5 100644 --- a/src/include/data.h +++ b/src/include/data.h @@ -545,6 +545,7 @@ extern u32 g_HudAlignModeR; extern s32 g_PrevFrameFb; extern s32 g_BlurFb; extern s32 g_BlurFbCapTimer; +extern s32 g_TickRateDiv; #define PLAYER_EXTCFG() g_PlayerExtCfg[g_Vars.currentplayerstats->mpindex & 3] #define PLAYER_DEFAULT_FOV (PLAYER_EXTCFG().fovy) diff --git a/src/include/game/lv.h b/src/include/game/lv.h index 961af91d0..f4f6dc808 100644 --- a/src/include/game/lv.h +++ b/src/include/game/lv.h @@ -6,10 +6,12 @@ #ifdef PLATFORM_N64 // game runs at ~30, so slomo = 1/2 of 30fps -#define LV_SLOMO_TICK_CAP TICKS(4) +#define LV_SLOMO_TICK_CAP 4 +#define LV_SLOMO_TICK_RATE LV_SLOMO_TICK_CAP #else -// game runs at 60+, so slomo = 1/2 of 60fps -#define LV_SLOMO_TICK_CAP TICKS(2) +// game runs at 60+, so slomo = 1/2 of whatever framerate we're running at +#define LV_SLOMO_TICK_CAP 1 // capping at >= 240fps would make it explode +#define LV_SLOMO_TICK_RATE (g_Vars.lvupdate240 / 2) #endif u32 getVar80084040(void); diff --git a/src/include/types.h b/src/include/types.h index 9917f21f7..171b0eda7 100644 --- a/src/include/types.h +++ b/src/include/types.h @@ -1523,7 +1523,7 @@ struct doorobj { // objtype 0x01 /*0xc4*/ s16 portalnum; /*0xc6*/ s8 soundtype; /*0xc7*/ s8 fadetime60; // counts down - /*0xc8*/ s32 lastcalc60; + /*0xc8*/ s32 lastcalc60; // port: actually stores 240hz frame number /*0xcc*/ u8 laserfade; /*0xcd*/ u8 unusedmaybe[3]; /*0xd0*/ u8 shadeinfo1[4]; // player 1 diff --git a/src/lib/varsinit.c b/src/lib/varsinit.c index 0d7925fe7..c64418044 100644 --- a/src/lib/varsinit.c +++ b/src/lib/varsinit.c @@ -28,7 +28,11 @@ void varsInit(void) g_Vars.lostframetime60t = 0; g_Vars.lostframetime240t = 0; g_Vars.lvupdate240rem = 2; +#ifdef PLATFORM_N64 g_Vars.mininc60 = 1; +#else + g_Vars.mininc60 = g_TickRateDiv; +#endif g_Vars.roomportalrecursionlimit = 254; g_Vars.diffframe240f = 4; g_Vars.diffframe240freal = 4;