diff --git a/src/engine/graph_node.h b/src/engine/graph_node.h index 274a0412e..6aa5f546c 100644 --- a/src/engine/graph_node.h +++ b/src/engine/graph_node.h @@ -124,7 +124,7 @@ struct GraphNodePerspective struct DisplayListNode { Mtx *transform; - void *transformPrev; + Mtx *transformPrev; void *displayList; struct DisplayListNode *next; }; diff --git a/src/game/rendering_graph_node.c b/src/game/rendering_graph_node.c index 62aa63430..374d900fc 100644 --- a/src/game/rendering_graph_node.c +++ b/src/game/rendering_graph_node.c @@ -162,9 +162,9 @@ static u8 sShadowInterpCount = 0; struct { Gfx *pos; - void *mtx; + Mtx *mtx; + Mtx *mtxPrev; void *displayList; - void *mtxPrev; Mtx interp; } gMtxTbl[6400]; s32 gMtxTblSize; @@ -300,11 +300,13 @@ static void geo_process_master_list_sub(struct GraphNodeMasterList *node) { if ((currList = node->listHeads[i]) != NULL) { gDPSetRenderMode(gDisplayListHead++, modeList->modes[i], mode2List->modes[i]); while (currList != NULL) { + detect_and_skip_mtx_interpolation(&currList->transform, &currList->transformPrev); if ((u32) gMtxTblSize < sizeof(gMtxTbl) / sizeof(gMtxTbl[0])) { gMtxTbl[gMtxTblSize].pos = gDisplayListHead; gMtxTbl[gMtxTblSize].mtx = currList->transform; gMtxTbl[gMtxTblSize].mtxPrev = currList->transformPrev; - gMtxTbl[gMtxTblSize++].displayList = currList->displayList; + gMtxTbl[gMtxTblSize].displayList = currList->displayList; + gMtxTblSize++; } gSPMatrix(gDisplayListHead++, VIRTUAL_TO_PHYSICAL(currList->transformPrev), G_MTX_MODELVIEW | G_MTX_LOAD | G_MTX_NOPUSH); diff --git a/src/pc/configfile.c b/src/pc/configfile.c index 42ad39eaa..128bb1f4d 100644 --- a/src/pc/configfile.c +++ b/src/pc/configfile.c @@ -131,7 +131,8 @@ unsigned int configNetworkSystem = 0; char configPlayerName[MAX_PLAYER_STRING] = ""; unsigned int configPlayerModel = 0; unsigned int configPlayerPalette = 0; -unsigned int config60Fps = 1; +bool configUncappedFramerate = true; +unsigned int configFrameLimit = 60; unsigned int configDrawDistance = 5; bool configDisablePopups = 0; bool configDisableDownloadedModels = 0; @@ -196,9 +197,10 @@ static const struct ConfigOption options[] = { {.name = "debug_offset", .type = CONFIG_TYPE_U64 , .u64Value = &gPcDebug.bhvOffset}, {.name = "debug_tags", .type = CONFIG_TYPE_U64 , .u64Value = gPcDebug.tags}, // coop-specific + {.name = "uncapped_framerate", .type = CONFIG_TYPE_BOOL , .boolValue = &configUncappedFramerate}, + {.name = "frame_limit" , .type = CONFIG_TYPE_UINT , .uintValue = &configFrameLimit}, {.name = "amount_of_players", .type = CONFIG_TYPE_UINT , .uintValue = &configAmountofPlayers}, {.name = "bubble_death", .type = CONFIG_TYPE_BOOL , .boolValue = &configBubbleDeath}, - {.name = "coop_60fps", .type = CONFIG_TYPE_UINT , .uintValue = &config60Fps}, {.name = "coop_draw_distance", .type = CONFIG_TYPE_UINT , .uintValue = &configDrawDistance}, {.name = "coop_host_port", .type = CONFIG_TYPE_UINT , .uintValue = &configHostPort}, {.name = "coop_host_save_slot", .type = CONFIG_TYPE_UINT , .uintValue = &configHostSaveSlot}, diff --git a/src/pc/configfile.h b/src/pc/configfile.h index e1f54e6f0..3a1ad89e2 100644 --- a/src/pc/configfile.h +++ b/src/pc/configfile.h @@ -87,7 +87,8 @@ extern unsigned int configNetworkSystem; extern char configPlayerName[]; extern unsigned int configPlayerModel; extern unsigned int configPlayerPalette; -extern unsigned int config60Fps; +extern bool configUncappedFramerate; +extern unsigned int configFrameLimit; extern unsigned int configDrawDistance; extern bool configDisablePopups; extern bool configDisableDownloadedModels; diff --git a/src/pc/djui/djui_panel_display.c b/src/pc/djui/djui_panel_display.c index 245ab94a6..bc5113f09 100644 --- a/src/pc/djui/djui_panel_display.c +++ b/src/pc/djui/djui_panel_display.c @@ -22,10 +22,27 @@ // The full height for the body. #define BODY_HEIGHT CHECKBOXES_FULL_SIZE + SELECTION_BOXES_FULL_SIZE + BUTTON_SIZES +static struct DjuiInputbox* sFrameLimitInput = NULL; + static void djui_panel_display_apply(UNUSED struct DjuiBase* caller) { configWindow.settings_changed = true; } +static void djui_panel_display_uncapped_change(UNUSED struct DjuiBase* caller) { + djui_base_set_enabled(&sFrameLimitInput->base, !configUncappedFramerate); +} + +static void djui_panel_display_frame_limit_text_change(struct DjuiBase* caller) { + struct DjuiInputbox* inputbox1 = (struct DjuiInputbox*)caller; + s32 frameLimit = atoi(inputbox1->buffer); + if (frameLimit >= 30 && frameLimit <= 3000) { + djui_inputbox_set_text_color(inputbox1, 0, 0, 0, 255); + configFrameLimit = frameLimit; + } else { + djui_inputbox_set_text_color(inputbox1, 255, 0, 0, 255); + } +} + void djui_panel_display_create(struct DjuiBase* caller) { f32 bodyHeight = BODY_HEIGHT; @@ -40,39 +57,60 @@ void djui_panel_display_create(struct DjuiBase* caller) { djui_interactable_hook_value_change(&checkbox1->base, djui_panel_display_apply); defaultBase = &checkbox1->base; + struct DjuiCheckbox* checkbox5 = djui_checkbox_create(&body->base, "Disable Popups", &configDisablePopups); + djui_base_set_size_type(&checkbox5->base, DJUI_SVT_RELATIVE, DJUI_SVT_ABSOLUTE); + djui_base_set_size(&checkbox5->base, 1.0f, 32); + + struct DjuiCheckbox* checkbox6 = djui_checkbox_create(&body->base, "Disable Downloaded Models", &configDisableDownloadedModels); + djui_base_set_size_type(&checkbox6->base, DJUI_SVT_RELATIVE, DJUI_SVT_ABSOLUTE); + djui_base_set_size(&checkbox6->base, 1.0f, 32); + + #ifdef EXTERNAL_DATA + struct DjuiCheckbox* checkbox7 = djui_checkbox_create(&body->base, "Preload Textures", &configPrecacheRes); + djui_base_set_size_type(&checkbox7->base, DJUI_SVT_RELATIVE, DJUI_SVT_ABSOLUTE); + djui_base_set_size(&checkbox7->base, 1.0f, 32); + #endif + struct DjuiCheckbox* checkbox2 = djui_checkbox_create(&body->base, "VSync", &configWindow.vsync); djui_base_set_size_type(&checkbox2->base, DJUI_SVT_RELATIVE, DJUI_SVT_ABSOLUTE); djui_base_set_size(&checkbox2->base, 1.0f, 32); djui_interactable_hook_value_change(&checkbox2->base, djui_panel_display_apply); - struct DjuiCheckbox* checkbox3 = djui_checkbox_create(&body->base, "HUD", &configHUD); + struct DjuiCheckbox* checkbox3 = djui_checkbox_create(&body->base, "Uncapped Framerate", &configUncappedFramerate); djui_base_set_size_type(&checkbox3->base, DJUI_SVT_RELATIVE, DJUI_SVT_ABSOLUTE); djui_base_set_size(&checkbox3->base, 1.0f, 32); + djui_interactable_hook_value_change(&checkbox3->base, djui_panel_display_uncapped_change); - struct DjuiCheckbox* checkbox4 = djui_checkbox_create(&body->base, "Disable Popups", &configDisablePopups); - djui_base_set_size_type(&checkbox4->base, DJUI_SVT_RELATIVE, DJUI_SVT_ABSOLUTE); - djui_base_set_size(&checkbox4->base, 1.0f, 32); + struct DjuiRect* rect1 = djui_rect_create(&body->base); + djui_base_set_size_type(&rect1->base, DJUI_SVT_RELATIVE, DJUI_SVT_ABSOLUTE); + djui_base_set_size(&rect1->base, 1.0f, 32); + djui_base_set_color(&rect1->base, 0, 0, 0, 0); + { + if (configFrameLimit < 30) { configFrameLimit = 30; } + if (configFrameLimit > 3000) { configFrameLimit = 3000; } + struct DjuiText* text1 = djui_text_create(&rect1->base, "Frame Limit"); + djui_base_set_size_type(&text1->base, DJUI_SVT_RELATIVE, DJUI_SVT_ABSOLUTE); + djui_base_set_color(&text1->base, 200, 200, 200, 255); + djui_base_set_size(&text1->base, 0.485f, 64); + djui_base_set_alignment(&text1->base, DJUI_HALIGN_LEFT, DJUI_VALIGN_TOP); - struct DjuiCheckbox* checkbox5 = djui_checkbox_create(&body->base, "Disable Downloaded Models", &configDisableDownloadedModels); - djui_base_set_size_type(&checkbox5->base, DJUI_SVT_RELATIVE, DJUI_SVT_ABSOLUTE); - djui_base_set_size(&checkbox5->base, 1.0f, 32); - - #ifdef EXTERNAL_DATA - struct DjuiCheckbox* checkbox6 = djui_checkbox_create(&body->base, "Preload Textures", &configPrecacheRes); - djui_base_set_size_type(&checkbox6->base, DJUI_SVT_RELATIVE, DJUI_SVT_ABSOLUTE); - djui_base_set_size(&checkbox6->base, 1.0f, 32); - #endif + struct DjuiInputbox* inputbox1 = djui_inputbox_create(&rect1->base, 32); + djui_base_set_size_type(&inputbox1->base, DJUI_SVT_RELATIVE, DJUI_SVT_ABSOLUTE); + djui_base_set_size(&inputbox1->base, 0.5f, 32); + djui_base_set_alignment(&inputbox1->base, DJUI_HALIGN_RIGHT, DJUI_VALIGN_TOP); + char frameLimitString[32] = { 0 }; + snprintf(frameLimitString, 32, "%d", configFrameLimit); + djui_inputbox_set_text(inputbox1, frameLimitString); + djui_interactable_hook_value_change(&inputbox1->base, djui_panel_display_frame_limit_text_change); + djui_base_set_enabled(&inputbox1->base, !configUncappedFramerate); + sFrameLimitInput = inputbox1; + } char* filterChoices[3] = { "Nearest", "Linear", "Tripoint" }; struct DjuiSelectionbox* selectionbox1 = djui_selectionbox_create(&body->base, "Filtering", filterChoices, 3, &configFiltering); djui_base_set_size_type(&selectionbox1->base, DJUI_SVT_RELATIVE, DJUI_SVT_ABSOLUTE); djui_base_set_size(&selectionbox1->base, 1.0f, 32); - char* fpsChoices[3] = { "30", "60" }; - struct DjuiSelectionbox* selectionbox2 = djui_selectionbox_create(&body->base, "FPS", fpsChoices, 2, &config60Fps); - djui_base_set_size_type(&selectionbox2->base, DJUI_SVT_RELATIVE, DJUI_SVT_ABSOLUTE); - djui_base_set_size(&selectionbox2->base, 1.0f, 32); - char* drawDistanceChoices[6] = { "0.5x", "1x", "1.5x", "3x", "10x", "100x" }; struct DjuiSelectionbox* selectionbox3 = djui_selectionbox_create(&body->base, "Draw Distance", drawDistanceChoices, 6, &configDrawDistance); djui_base_set_size_type(&selectionbox3->base, DJUI_SVT_RELATIVE, DJUI_SVT_ABSOLUTE); diff --git a/src/pc/gfx/gfx_dummy.c b/src/pc/gfx/gfx_dummy.c index 628951565..1844b3647 100644 --- a/src/pc/gfx/gfx_dummy.c +++ b/src/pc/gfx/gfx_dummy.c @@ -76,20 +76,11 @@ static bool gfx_dummy_wm_start_frame(void) { return true; } -static inline void sync_framerate_with_timer(void) { - f64 curTime = clock_elapsed_f64(); - if (curTime < sFrameTargetTime) { - u32 delayMs = (sFrameTargetTime - curTime) * 1000.0; - if (delayMs > 0) { - sleep_ms(delayMs); - } - } - f64 frameTime = config60Fps ? (sFrameTime / 2.0) : sFrameTime; - sFrameTargetTime += frameTime; +static void gfx_dummy_wm_delay(u32 ms) { + sleep_ms(ms); } static void gfx_dummy_wm_swap_buffers_begin(void) { - sync_framerate_with_timer(); } static void gfx_dummy_wm_swap_buffers_end(void) { @@ -209,7 +200,8 @@ struct GfxWindowManagerAPI gfx_dummy_wm_api = { gfx_dummy_wm_stop_text_input, gfx_dummy_wm_get_clipboard_text, gfx_dummy_wm_set_clipboard_text, - gfx_dummy_wm_set_cursor_visible + gfx_dummy_wm_set_cursor_visible, + gfx_dummy_wm_delay }; struct GfxRenderingAPI gfx_dummy_renderer_api = { diff --git a/src/pc/gfx/gfx_dxgi.cpp b/src/pc/gfx/gfx_dxgi.cpp index 22135af59..529a0e1f0 100644 --- a/src/pc/gfx/gfx_dxgi.cpp +++ b/src/pc/gfx/gfx_dxgi.cpp @@ -522,35 +522,12 @@ static bool gfx_dxgi_start_frame(void) { }*/ dxgi.length_in_vsync_frames = configWindow.vsync; - f64 curTime = clock_elapsed_f64(); - f64 frameTime = config60Fps ? (sFrameTime / 2.0) : sFrameTime; - if (curTime > sFrameTargetTime) { - sFrameTargetTime += frameTime; - if (curTime > sFrameTargetTime + frameTime * 3) { - sFrameTargetTime = curTime; - } - dxgi.dropped_frame = true; - return false; - } dxgi.dropped_frame = false; return true; } -static inline void sync_framerate_with_timer(void) { - f64 curTime = clock_elapsed_f64(); - if (curTime < sFrameTargetTime) { - u32 delayMs = (sFrameTargetTime - curTime) * 1000.0; - if (delayMs > 0) { - Sleep(delayMs); - } - } - f64 frameTime = config60Fps ? (sFrameTime / 2.0) : sFrameTime; - sFrameTargetTime += frameTime; -} - static void gfx_dxgi_swap_buffers_begin(void) { - sync_framerate_with_timer(); ThrowIfFailed(dxgi.swap_chain->Present(dxgi.length_in_vsync_frames, 0)); UINT this_present_id; if (dxgi.swap_chain->GetLastPresentCount(&this_present_id) == S_OK) { @@ -654,6 +631,10 @@ ComPtr gfx_dxgi_create_swap_chain(IUnknown *device) { return dxgi.swap_chain; } +void gfx_dxgi_delay(u32 ms) { + Sleep(ms); +} + HWND gfx_dxgi_get_h_wnd(void) { return dxgi.h_wnd; } @@ -722,6 +703,7 @@ struct GfxWindowManagerAPI gfx_dxgi = { gfx_dxgi_get_clipboard_text, gfx_dxgi_set_clipboard_text, gfx_dxgi_set_cursor_visible, + gfx_dxgi_delay, }; #endif diff --git a/src/pc/gfx/gfx_sdl1.c b/src/pc/gfx/gfx_sdl1.c index da046283a..23cd3f120 100644 --- a/src/pc/gfx/gfx_sdl1.c +++ b/src/pc/gfx/gfx_sdl1.c @@ -168,18 +168,7 @@ static bool gfx_sdl_start_frame(void) { return true; } -static inline void sync_framerate_with_timer(void) { - static Uint32 last_time = 0; - // get base timestamp on the first frame (might be different from 0) - if (last_time == 0) last_time = SDL_GetTicks(); - const int elapsed = SDL_GetTicks() - last_time; - if (elapsed < frame_time) - SDL_Delay(frame_time - elapsed); - last_time += frame_time; -} - static void gfx_sdl_swap_buffers_begin(void) { - sync_framerate_with_timer(); SDL_GL_SwapBuffers(); } @@ -190,6 +179,9 @@ static double gfx_sdl_get_time(void) { return 0.0; } +static void gfx_sdl_delay(u32 ms) { + SDL_Delay(ms); +} static void gfx_sdl_shutdown(void) { if (SDL_WasInit(0)) @@ -218,6 +210,7 @@ struct GfxWindowManagerAPI gfx_sdl = { gfx_sdl_get_clipboard_text, gfx_sdl_set_clipboard_text, gfx_sdl_set_cursor_visible, + gfx_sdl_delay, }; #endif // BACKEND_WM diff --git a/src/pc/gfx/gfx_sdl2.c b/src/pc/gfx/gfx_sdl2.c index fc0c60c93..e63c755af 100644 --- a/src/pc/gfx/gfx_sdl2.c +++ b/src/pc/gfx/gfx_sdl2.c @@ -58,37 +58,6 @@ static void (*kb_text_input)(char*) = NULL; #define IS_FULLSCREEN() ((SDL_GetWindowFlags(wnd) & SDL_WINDOW_FULLSCREEN_DESKTOP) != 0) -int test_vsync(void) { - // Even if SDL_GL_SetSwapInterval succeeds, it doesn't mean that VSync actually works. - // A 60 Hz monitor should have a swap interval of 16.67 milliseconds. - // Try to detect the length of a vsync by swapping buffers some times. - // Since the graphics card may enqueue a fixed number of frames, - // first send in four dummy frames to hopefully fill the queue. - // This method will fail if the refresh rate is changed, which, in - // combination with that we can't control the queue size (i.e. lag) - // is a reason this generic SDL2 backend should only be used as last resort. - - for (int32_t i = 0; i < 8; ++i) - SDL_GL_SwapWindow(wnd); - - Uint32 start = SDL_GetTicks(); - SDL_GL_SwapWindow(wnd); - SDL_GL_SwapWindow(wnd); - SDL_GL_SwapWindow(wnd); - SDL_GL_SwapWindow(wnd); - Uint32 end = SDL_GetTicks(); - - const float average = 4.0 * 1000.0 / (end - start); - - if (average > 27.0f && average < 33.0f) return 1; - if (average > 57.0f && average < 63.0f) return 2; - if (average > 86.0f && average < 94.0f) return 3; - if (average > 115.0f && average < 125.0f) return 4; - if (average > 234.0f && average < 246.0f) return 8; - - return 0; -} - static inline void gfx_sdl_set_vsync(const bool enabled) { SDL_GL_SetSwapInterval(enabled); } @@ -248,33 +217,9 @@ static void gfx_sdl_set_keyboard_callbacks(kb_callback_t on_key_down, kb_callbac static bool gfx_sdl_start_frame(void) { return true; - f64 curTime = clock_elapsed_f64(); - f64 frameTime = config60Fps ? (sFrameTime / 2.0) : sFrameTime; - if (curTime > sFrameTargetTime) { - sFrameTargetTime += frameTime; - if (curTime > sFrameTargetTime + frameTime * 3) { - sFrameTargetTime = curTime; - } - return false; - } - return true; -} - -static inline void sync_framerate_with_timer(void) { - return; - f64 curTime = clock_elapsed_f64(); - if (curTime < sFrameTargetTime) { - u32 delayMs = (sFrameTargetTime - curTime) * 1000.0; - if (delayMs > 0) { - SDL_Delay(delayMs); - } - } - f64 frameTime = config60Fps ? (sFrameTime / 2.0) : sFrameTime; - sFrameTargetTime += frameTime; } static void gfx_sdl_swap_buffers_begin(void) { - sync_framerate_with_timer(); SDL_GL_SwapWindow(wnd); } @@ -285,6 +230,9 @@ static double gfx_sdl_get_time(void) { return 0.0; } +static void gfx_sdl_delay(u32 ms) { + SDL_Delay(ms); +} static void gfx_sdl_shutdown(void) { if (SDL_WasInit(0)) { @@ -316,6 +264,7 @@ struct GfxWindowManagerAPI gfx_sdl = { gfx_sdl_get_clipboard_text, gfx_sdl_set_clipboard_text, gfx_sdl_set_cursor_visible, + gfx_sdl_delay, }; #endif // BACKEND_WM diff --git a/src/pc/gfx/gfx_window_manager_api.h b/src/pc/gfx/gfx_window_manager_api.h index dff33f33e..f2d457eb9 100644 --- a/src/pc/gfx/gfx_window_manager_api.h +++ b/src/pc/gfx/gfx_window_manager_api.h @@ -25,6 +25,7 @@ struct GfxWindowManagerAPI { char* (*get_clipboard_text)(void); void (*set_clipboard_text)(char*); void (*set_cursor_visible)(bool); + void (*delay)(unsigned int ms); }; #endif diff --git a/src/pc/pc_main.c b/src/pc/pc_main.c index 34b1cd1a2..330d1d5b7 100644 --- a/src/pc/pc_main.c +++ b/src/pc/pc_main.c @@ -69,6 +69,14 @@ f32 gRenderingDelta = 0; f32 gGameSpeed = 1.0f; // DO NOT COMMIT +#define FRAMERATE 30 +static const f64 sFrameTime = (1.0 / ((double)FRAMERATE)); +static f64 sFrameTargetTime = 0; +static f64 sFrameTimeStart; +static f64 sLastFrameTimeStart; +static f32 sAvgFrames = 1; +static f32 sAvgFps = 0; + static struct AudioAPI *audio_api; struct GfxWindowManagerAPI *wm_api; static struct GfxRenderingAPI *rendering_api; @@ -142,37 +150,59 @@ static inline void patch_interpolations(f32 delta) { patch_djui_interpolated(delta); } -void produce_uncapped_frames(void) { - #define FRAMERATE 30 - static const f64 sFrameTime = (1.0 / ((double)FRAMERATE)); - static f64 sFrameTargetTime = 0; +static void delay_frame(void) { + if (configUncappedFramerate) { return; } + static f32 sDelayRounding = 0; + f64 targetDelta = 1.0 / (f64)configFrameLimit; + f64 actualDelta = clock_elapsed_f64() - sLastFrameTimeStart; + if (actualDelta < targetDelta) { + f64 delay = sDelayRounding + ((targetDelta - actualDelta) * 1000.0); + sDelayRounding = delay - (u32)delay; + wm_api->delay((u32)delay); + } +} + +void produce_interpolation_frames_and_delay(void) { gRenderingInterpolated = true; - f64 startTime = clock_elapsed_f64(); - f64 curTime = startTime; - u64 frames = 0; + // sanity check target time to deal with hangs and such + f64 curTime = clock_elapsed_f64(); + if (fabs(sFrameTargetTime - curTime) > 1) { + sFrameTargetTime = curTime; + } + delay_frame(); + + u64 frames = 1; while ((curTime = clock_elapsed_f64()) < sFrameTargetTime) { + sLastFrameTimeStart = curTime; gfx_start_frame(); - f32 delta = MIN((curTime - startTime) / (sFrameTargetTime - startTime), 1); + f32 delta = (configWindow.vsync || !configUncappedFramerate) + ? frames / sAvgFrames + : MIN((curTime - sFrameTimeStart) / (sFrameTargetTime - sFrameTimeStart), 1); gRenderingDelta = delta; patch_interpolations(delta); send_display_list(gGfxSPTask); gfx_end_frame(); frames++; + delay_frame(); } + f32 fps = frames / (clock_elapsed_f64() - sFrameTimeStart); + sAvgFps = sAvgFps * 0.99 + fps * 0.01; + sAvgFrames = sAvgFrames * 0.9 + frames * 0.1; + sFrameTargetTime += sFrameTime * gGameSpeed; gRenderingInterpolated = false; - printf(">> frames %llu | %f\n", frames, gGameSpeed); - fflush(stdout); - - sFrameTargetTime += sFrameTime * gGameSpeed; + //printf(">>> fpt: %llu, fps: %f :: %f\n", frames, sAvgFps, fps); } void produce_one_frame(void) { network_update(); + sFrameTimeStart = clock_elapsed_f64(); + sLastFrameTimeStart = sFrameTimeStart; + patch_interpolations_before(); gfx_start_frame(); @@ -202,10 +232,7 @@ void produce_one_frame(void) { gfx_end_frame(); - // uncapped - if (config60Fps) { - produce_uncapped_frames(); - } + produce_interpolation_frames_and_delay(); } void audio_shutdown(void) { @@ -334,7 +361,7 @@ void main_func(void) { wm_api->set_keyboard_callbacks(keyboard_on_key_down, keyboard_on_key_up, keyboard_on_all_keys_up, keyboard_on_text_input); #if defined(AAPI_SDL1) || defined(AAPI_SDL2) - if (audio_api == NULL && audio_sdl.init()) + if (audio_api == NULL && audio_sdl.init()) audio_api = &audio_sdl; #endif diff --git a/src/pc/utils/misc.c b/src/pc/utils/misc.c index 578799ca4..d09aecc16 100644 --- a/src/pc/utils/misc.c +++ b/src/pc/utils/misc.c @@ -10,6 +10,7 @@ #include "game/area.h" #include "game/level_update.h" #include "game/save_file.h" +#include "engine/math_util.h" float smoothstep(float edge0, float edge1, float x) { float t = (x - edge0) / (edge1 - edge0); @@ -116,6 +117,49 @@ next_get: ///////////////// +static f32 sm64_to_radians(f32 val) { + return val * M_PI / 0x8000; +} + +static f32 radians_to_sm64(f32 val) { + return val * 0x8000 / M_PI; +} + +static f32 asins(f32 val) { + return radians_to_sm64(asin(sm64_to_radians(val))); +} + +void mtx_to_euler1(Mtx* mat, Vec3s rot) { + if (mat->m[1][1] == 1.0f) { + rot[0] = 0; + rot[1] = atan2(mat->m[1][3], mat->m[3][4]); + rot[2] = 0; + } else if (mat->m[1][1] == -1.0f) { + rot[0] = 0; + rot[1] = atan2(mat->m[1][3], mat->m[3][4]); + rot[2] = 0; + } else { + rot[0] = asin(mat->m[2][1]); + rot[1] = atan2(-mat->m[3][1], mat->m[1][1]); + rot[2] = atan2(-mat->m[2][3], mat->m[2][2]); + } +} +void mtx_to_euler2(Mtx* mat, Vec3s rot) { + if (mat->m[1][1] == 1.0f) { + rot[0] = 0; + rot[1] = atan2(mat->m[3][1], mat->m[4][3]); + rot[2] = 0; + } else if (mat->m[1][1] == -1.0f) { + rot[0] = 0; + rot[1] = atan2(mat->m[3][1], mat->m[4][3]); + rot[2] = 0; + } else { + rot[0] = asin(mat->m[1][2]); + rot[1] = atan2(-mat->m[1][3], mat->m[1][1]); + rot[2] = atan2(-mat->m[3][2], mat->m[2][2]); + } +} + f32 delta_interpolate_f32(f32 start, f32 end, f32 delta) { return start * (1.0f - delta) + end * delta; } @@ -145,15 +189,6 @@ void delta_interpolate_normal(s8* res, s8* a, s8* b, f32 delta) { res[2] = ((a[2] * antiDelta) + (b[2] * delta)); } -void delta_interpolate_mtx(Mtx* out, Mtx* a, Mtx* b, f32 delta) { - f32 antiDelta = 1.0f - delta; - for (s32 i = 0; i < 4; i++) { - for (s32 j = 0; j < 4; j++) { - out->m[i][j] = (a->m[i][j] * antiDelta) + (b->m[i][j] * delta); - } - } -} - void delta_interpolate_rgba(u8* res, u8* a, u8* b, f32 delta) { f32 antiDelta = 1.0f - delta; res[0] = ((a[0] * antiDelta) + (b[0] * delta)); @@ -162,21 +197,7 @@ void delta_interpolate_rgba(u8* res, u8* a, u8* b, f32 delta) { res[3] = ((a[3] * antiDelta) + (b[3] * delta)); } -/* - -void interpolate_vectors(Vec3f res, Vec3f a, Vec3f b) { - res[0] = (a[0] + b[0]) / 2.0f; - res[1] = (a[1] + b[1]) / 2.0f; - res[2] = (a[2] + b[2]) / 2.0f; -} - -void interpolate_vectors_s16(Vec3s res, Vec3s a, Vec3s b) { - res[0] = (a[0] + b[0]) / 2; - res[1] = (a[1] + b[1]) / 2; - res[2] = (a[2] + b[2]) / 2; -} - -static s16 interpolate_angle(s16 a, s16 b) { +static s16 delta_interpolate_angle(s16 a, s16 b, f32 delta) { s32 absDiff = b - a; if (absDiff < 0) { absDiff = -absDiff; @@ -184,16 +205,47 @@ static s16 interpolate_angle(s16 a, s16 b) { if (absDiff >= 0x4000 && absDiff <= 0xC000) { return b; } + + f32 antiDelta = 1.0f - delta; if (absDiff <= 0x8000) { - return (a + b) / 2; + return (a * antiDelta) + (b * delta); } else { - return (a + b) / 2 + 0x8000; + return (a * antiDelta) + (b * delta) + 0x8000; } } -static void interpolate_angles(Vec3s res, Vec3s a, Vec3s b) { - res[0] = interpolate_angle(a[0], b[0]); - res[1] = interpolate_angle(a[1], b[1]); - res[2] = interpolate_angle(a[2], b[2]); +static void delta_interpolate_angles(Vec3s res, Vec3s a, Vec3s b, f32 delta) { + res[0] = delta_interpolate_angle(a[0], b[0], delta); + res[1] = delta_interpolate_angle(a[1], b[1], delta); + res[2] = delta_interpolate_angle(a[2], b[2], delta); +} + +void delta_interpolate_mtx(Mtx* out, Mtx* a, Mtx* b, f32 delta) { + // this isn't the right way to do things. + f32 antiDelta = 1.0f - delta; + for (s32 i = 0; i < 4; i++) { + for (s32 j = 0; j < 4; j++) { + out->m[i][j] = (a->m[i][j] * antiDelta) + (b->m[i][j] * delta); + } + } +} + +void detect_and_skip_mtx_interpolation(Mtx** mtxPrev, Mtx** mtx) { + // if the matrix has changed "too much", then skip interpolation + const f32 minDot = sqrt(2.0f) / -2.0f; + Vec3f prevX; vec3f_copy(prevX, (f32*)(*mtxPrev)->m[0]); vec3f_normalize(prevX); + Vec3f prevY; vec3f_copy(prevY, (f32*)(*mtxPrev)->m[1]); vec3f_normalize(prevY); + Vec3f prevZ; vec3f_copy(prevZ, (f32*)(*mtxPrev)->m[2]); vec3f_normalize(prevZ); + + Vec3f nextX; vec3f_copy(nextX, (f32*)(*mtx)->m[0]); vec3f_normalize(nextX); + Vec3f nextY; vec3f_copy(nextY, (f32*)(*mtx)->m[1]); vec3f_normalize(nextY); + Vec3f nextZ; vec3f_copy(nextZ, (f32*)(*mtx)->m[2]); vec3f_normalize(nextZ); + + f32 dotX = vec3f_dot(prevX, nextX); + f32 dotY = vec3f_dot(prevY, nextY); + f32 dotZ = vec3f_dot(prevZ, nextZ); + + if ((dotX < minDot) | (dotY < minDot) || (dotZ < minDot)) { + *mtx = *mtxPrev; + } } -*/ \ No newline at end of file diff --git a/src/pc/utils/misc.h b/src/pc/utils/misc.h index da1b1fc2e..1c65bba34 100644 --- a/src/pc/utils/misc.h +++ b/src/pc/utils/misc.h @@ -19,7 +19,8 @@ s32 delta_interpolate_s32(s32 a, s32 b, f32 delta); void delta_interpolate_vec3f(Vec3f res, Vec3f a, Vec3f b, f32 delta); void delta_interpolate_vec3s(Vec3s res, Vec3s a, Vec3s b, f32 delta); void delta_interpolate_normal(s8* res, s8* a, s8* b, f32 delta); -void delta_interpolate_mtx(Mtx* out, Mtx* a, Mtx* b, f32 delta); void delta_interpolate_rgba(u8* res, u8* a, u8* b, f32 delta); +void delta_interpolate_mtx(Mtx* out, Mtx* a, Mtx* b, f32 delta); +void detect_and_skip_mtx_interpolation(Mtx** mtxPrev, Mtx** mtx); #endif \ No newline at end of file