diff --git a/port/include/video.h b/port/include/video.h index 0e4b242e2..0c468dcbb 100644 --- a/port/include/video.h +++ b/port/include/video.h @@ -21,6 +21,7 @@ void videoStartFrame(void); void videoSubmitCommands(Gfx *cmds); void videoClearScreen(void); void videoEndFrame(void); +f64 videoGetDiffTime(void); void *videoGetWindowHandle(void); @@ -45,6 +46,8 @@ s32 videoGetDisplayMode(displaymode *out, const s32 index); s32 videoGetNumDisplayModes(void); s32 videoGetVsync(void); s32 videoGetFramerateLimit(void); +s32 videoGetDisplayFPS(void); +s32 videoGetDisplayFPSDivisor(void); s32 videoGetMSAA(void); void videoSetWindowOffset(s32 x, s32 y); @@ -56,6 +59,8 @@ void videoSetDetailTextures(s32 detail); void videoSetDisplayMode(const s32 index); void videoSetVsync(const s32 vsync); void videoSetFramerateLimit(const s32 limit); +void videoSetDisplayFPS(const s32 displayfps); +void videoSetDisplayFPSDivisor(const s32 divisor); void videoSetMSAA(const s32 msaa); s32 videoCreateFramebuffer(u32 w, u32 h, s32 upscale, s32 autoresize); diff --git a/port/src/optionsmenu.c b/port/src/optionsmenu.c index 9e23290af..5835ee921 100644 --- a/port/src/optionsmenu.c +++ b/port/src/optionsmenu.c @@ -912,6 +912,57 @@ static MenuItemHandlerResult menuhandlerTexFilter2D(s32 operation, struct menuit return 0; } +static MenuItemHandlerResult menuhandlerDisplayFPS(s32 operation, struct menuitem *item, union handlerdata *data) +{ + switch (operation) { + case MENUOP_GET: + return videoGetDisplayFPS(); + case MENUOP_SET: + videoSetDisplayFPS(data->checkbox.value); + break; + } + + return 0; +} + +static MenuItemHandlerResult menuhandlerDisplayFPSInterval(s32 operation, struct menuitem *item, union handlerdata *data) +{ + s32 divisor; + static const char *opts[] = { + "1 Sec", + "1/2 Secs", + "1/4 Secs", + "1/8 Secs", + "1/16 Secs", + }; + + switch (operation) { + case MENUOP_GETOPTIONCOUNT: + data->dropdown.value = ARRAYCOUNT(opts); + break; + case MENUOP_GETOPTIONTEXT: + return (intptr_t)opts[data->dropdown.value]; + case MENUOP_SET: + videoSetDisplayFPSDivisor(1 << data->dropdown.value); + break; + case MENUOP_GETSELECTEDINDEX: + divisor = videoGetDisplayFPSDivisor(); + if (divisor < 2) { + data->dropdown.value = 0; + } else if (divisor < 4) { + data->dropdown.value = 1; + } else if (divisor < 8) { + data->dropdown.value = 2; + } else if (divisor < 16) { + data->dropdown.value = 3; + } else { + data->dropdown.value = 4; + } + } + + return 0; +} + static MenuItemHandlerResult menuhandlerGeMuzzleFlashes(s32 operation, struct menuitem *item, union handlerdata *data) { switch (operation) { @@ -1038,6 +1089,22 @@ struct menuitem g_ExtendedVideoMenuItems[] = { VIDEO_MAX_FPS, menuhandlerFramerateLimit, }, + { + MENUITEMTYPE_CHECKBOX, + 0, + MENUITEMFLAG_LITERAL_TEXT, + (uintptr_t)"Display FPS", + 0, + menuhandlerDisplayFPS, + }, + { + MENUITEMTYPE_DROPDOWN, + 0, + MENUITEMFLAG_LITERAL_TEXT, + (uintptr_t)"Display FPS Interval", + 0, + menuhandlerDisplayFPSInterval, + }, { MENUITEMTYPE_DROPDOWN, 0, diff --git a/port/src/video.c b/port/src/video.c index 0a84df49b..726096de4 100644 --- a/port/src/video.c +++ b/port/src/video.c @@ -41,9 +41,12 @@ static s32 vidAllowHiDpi = false; static s32 vidVsync = 1; static s32 vidMSAA = 1; static s32 vidFramerateLimit = 0; +static s32 vidDisplayFPS = 0; +static s32 vidDisplayFPSDivisor = 8; static displaymode vidModeDefault; static s32 vidNumModes = 1; static displaymode *vidModes = &vidModeDefault; +static f64 vidDiffTime; static s32 texFilter = FILTER_LINEAR; static s32 texFilter2D = true; @@ -51,8 +54,7 @@ static s32 texDetail = true; static u32 dlcount = 0; static u32 frames = 0; -static u32 framesPerSec = 0; -static f64 startTime, endTime, fpsTime; +static f64 startTime, endTime; static s32 videoInitDisplayModes(void); @@ -115,26 +117,33 @@ void videoSubmitCommands(Gfx *cmds) void videoEndFrame(void) { + static f64 accumTime = 0.0; + if (!initDone) { return; } gfx_end_frame(); - endTime = wmAPI->get_time(); - ++frames; - ++framesPerSec; - if (endTime >= fpsTime) { + endTime = wmAPI->get_time(); + accumTime += endTime - startTime; + + if (accumTime >= 1.0 / (f64)vidDisplayFPSDivisor) { char tmp[128]; - snprintf(tmp, sizeof(tmp), "fps %3u frt %lf frm %u", framesPerSec, endTime - startTime, frames); + vidDiffTime = endTime - startTime; + accumTime = 0.0; + snprintf(tmp, sizeof(tmp), "fps %3u frt %lf frm %u", (u32)(1.0 / vidDiffTime), vidDiffTime, frames); wmAPI->set_window_title(tmp); - framesPerSec = 0; - fpsTime = endTime + 1.0; } } +f64 videoGetDiffTime(void) +{ + return vidDiffTime; +} + void videoClearScreen(void) { videoStartFrame(); @@ -235,6 +244,16 @@ s32 videoGetFramerateLimit(void) return vidFramerateLimit; } +s32 videoGetDisplayFPS(void) +{ + return vidDisplayFPS; +} + +s32 videoGetDisplayFPSDivisor(void) +{ + return vidDisplayFPSDivisor; +} + static s32 videoInitDisplayModes(void) { if (!wmAPI->get_current_display_mode(&vidModeDefault.width, &vidModeDefault.height)) { @@ -442,6 +461,16 @@ void videoSetFramerateLimit(const s32 limit) wmAPI->set_target_fps(vidFramerateLimit); } +void videoSetDisplayFPS(const s32 displayfps) +{ + vidDisplayFPS = displayfps; +} + +void videoSetDisplayFPSDivisor(const s32 divisor) +{ + vidDisplayFPSDivisor = divisor; +} + void videoSetFramebuffer(s32 target) { return gfx_set_framebuffer(target, 1.f); @@ -495,6 +524,8 @@ PD_CONSTRUCTOR static void videoConfigInit(void) configRegisterInt("Video.VSync", &vidVsync, -1, 10); configRegisterInt("Video.FramebufferEffects", &vidFramebuffers, 0, 1); configRegisterInt("Video.FramerateLimit", &vidFramerateLimit, 0, VIDEO_MAX_FPS); + configRegisterInt("Video.DisplayFPS", &vidDisplayFPS, 0, 1); + configRegisterInt("Video.DisplayFPSDivisor", &vidDisplayFPSDivisor, 1, 16); configRegisterInt("Video.MSAA", &vidMSAA, 1, 16); configRegisterInt("Video.TextureFilter", &texFilter, 0, 2); configRegisterInt("Video.TextureFilter2D", &texFilter2D, 0, 1); diff --git a/src/game/lv.c b/src/game/lv.c index 22f3993a8..06bff3b84 100644 --- a/src/game/lv.c +++ b/src/game/lv.c @@ -96,6 +96,9 @@ #include "lib/vars.h" #include "lib/vi.h" #include "types.h" +#ifndef PLATFORM_N64 +#include "video.h" +#endif struct sndstate *g_MiscSfxAudioHandles[3]; u32 var800aa5bc; @@ -951,6 +954,34 @@ void lvFindThreats(void) } } +#ifndef PLATFORM_N64 +Gfx *lvRenderFPS(Gfx *gdl) +{ + s32 x = 27, y = 13; + f32 fps = 1.0f / videoGetDiffTime(); + s32 ifps = (s32)fps; + u8 r = (ifps <= 30) ? 255 : (ifps <= 60) ? roundf(255 * (1.0f - (ifps - 30.0f) / 30.0f)) : 0; + u8 g = (ifps <= 30) ? roundf(255 * (ifps - 1.0f) / 29.0f) : 255; + u8 b = (ifps > 60 && ifps <= 90) ? roundf(255 * (ifps - 60.0f) / 30.0f) : (ifps > 90) ? 255 : 0; + u8 a = 160; + u32 color = r << 24 | g << 16 | b << 8 | a; + char buffer[16]; + + if (g_CharsNumeric && g_FontNumeric) { + snprintf(buffer, sizeof buffer, "%.2f", fps); + + gSPSetExtraGeometryModeEXT(gdl++, g_HudAlignModeL); + + gdl = text0f153628(gdl); + gdl = textRender(gdl, &x, &y, buffer, g_CharsNumeric, g_FontNumeric, color, 0x000000a0, viGetWidth(), viGetHeight(), 0, 0); + + gSPClearExtraGeometryModeEXT(gdl++, g_HudAlignModeL); + } + + return gdl; +} +#endif + /** * Renders a complete frame for all players, and also does some other game logic * that really doesn't belong here. @@ -1745,6 +1776,12 @@ Gfx *lvRender(Gfx *gdl) gDPSetScissor(gdl++, G_SC_NON_INTERLACE, 0, 0, viGetWidth(), viGetHeight()); +#ifndef PLATFORM_N64 + if (videoGetDisplayFPS()) { + gdl = lvRenderFPS(gdl); + } +#endif + #if VERSION < VERSION_NTSC_1_0 if ((uintptr_t)gdl < (uintptr_t)g_GfxBuffers[g_GfxActiveBufferIndex] || (uintptr_t)gdl > (uintptr_t)g_GfxBuffers[g_GfxActiveBufferIndex + 1]) {