From 569d86d9014e661704c8202c62e712f70e71f3fc Mon Sep 17 00:00:00 2001 From: Mr-Wiseguy Date: Sat, 2 Mar 2024 23:23:29 -0500 Subject: [PATCH] Added heuristics to determine when to skip camera interpolation, added ability to enable developer mode in RT64 --- include/rt64_layer.h | 2 +- lib/RT64-HLE | 2 +- patches/camera_transform_tagging.c | 169 +++++++++++++++++++++++++++-- patches/debug_patches.c | 41 ------- patches/play_patches.c | 44 ++++++++ patches/play_patches.h | 10 ++ patches/transform_ids.h | 3 + src/game/config.cpp | 46 ++++++-- src/recomp/rt64_layer.cpp | 22 +--- ultramodern/config.hpp | 1 + ultramodern/events.cpp | 2 +- 11 files changed, 257 insertions(+), 85 deletions(-) create mode 100644 patches/play_patches.c create mode 100644 patches/play_patches.h diff --git a/include/rt64_layer.h b/include/rt64_layer.h index f7b0c41..6a58f6c 100644 --- a/include/rt64_layer.h +++ b/include/rt64_layer.h @@ -12,7 +12,7 @@ namespace ultramodern { struct WindowHandle; } -RT64::Application* RT64Init(uint8_t* rom, uint8_t* rdram, ultramodern::WindowHandle window_handle); +RT64::Application* RT64Init(uint8_t* rom, uint8_t* rdram, ultramodern::WindowHandle window_handle, bool developer_mode); void RT64UpdateConfig(RT64::Application* application, const ultramodern::GraphicsConfig& old_config, const ultramodern::GraphicsConfig& new_config); void RT64EnableInstantPresent(RT64::Application* application); void RT64SendDL(uint8_t* rdram, const OSTask* task); diff --git a/lib/RT64-HLE b/lib/RT64-HLE index ad88e63..e95a717 160000 --- a/lib/RT64-HLE +++ b/lib/RT64-HLE @@ -1 +1 @@ -Subproject commit ad88e63572251bc18daa5d5055a80c4c5f42c630 +Subproject commit e95a71768e6efb55dcc9daa02843879a9f79faa6 diff --git a/patches/camera_transform_tagging.c b/patches/camera_transform_tagging.c index 6a22584..0a6e533 100644 --- a/patches/camera_transform_tagging.c +++ b/patches/camera_transform_tagging.c @@ -1,36 +1,181 @@ #include "patches.h" #include "transform_ids.h" #include "z64cutscene.h" +#include "overlays/kaleido_scope/ovl_kaleido_scope/z_kaleido_scope.h" +#include "overlays/gamestates/ovl_file_choose/z_file_select.h" -static bool interpolate_camera = true; +static bool camera_interpolation_forced = false; +static bool camera_skip_interpolation_forced = false; +static bool camera_ignore_tracking = false; +static bool in_kaleido = false; +static bool prev_in_kaleido = false; -s32 Play_ChangeCameraStatus(PlayState* this, s16 camId, s16 status) { - s16 camIdx = (camId == CAM_ID_NONE) ? this->activeCamId : camId; +void camera_pre_play_update(PlayState* play) { +} - if (status == CAM_STATUS_ACTIVE) { - this->activeCamId = camIdx; +void camera_post_play_update(PlayState* play) { + // Track whether the game is in kaleido. + prev_in_kaleido = in_kaleido; + + if ((play->pauseCtx.state != 0) || (play->pauseCtx.debugEditor != DEBUG_EDITOR_NONE)) { + in_kaleido = true; + } + else { + in_kaleido = false; } - - recomp_printf("Changed play camera status %d %d\n", camId, status); - - return Camera_ChangeStatus(this->cameraPtrs[camIdx], status); } s32 View_ApplyPerspective(View* view); s32 View_ApplyOrtho(View* view); +void force_camera_interpolation() { + camera_interpolation_forced = true; +} + +void force_camera_skip_interpolation() { + camera_skip_interpolation_forced = true; +} + +void force_camera_ignore_tracking() { + camera_ignore_tracking = true; +} + +void KaleidoScope_SetView(PauseContext* pauseCtx, f32 eyeX, f32 eyeY, f32 eyeZ) { + Vec3f eye; + Vec3f at; + Vec3f up; + + eye.x = eyeX; + eye.y = eyeY; + eye.z = eyeZ; + at.x = at.y = at.z = 0.0f; + up.x = up.z = 0.0f; + up.y = 1.0f; + + // @recomp Force interpolation for this view and skip tracking positions. + force_camera_interpolation(); + force_camera_ignore_tracking(); + + View_LookAt(&pauseCtx->view, &eye, &at, &up); + View_Apply(&pauseCtx->view, + VIEW_ALL | VIEW_FORCE_VIEWING | VIEW_FORCE_VIEWPORT | VIEW_FORCE_PROJECTION_PERSPECTIVE); +} + + +void FileSelect_SetView(FileSelectState* this, f32 eyeX, f32 eyeY, f32 eyeZ) { + Vec3f eye; + Vec3f lookAt; + Vec3f up; + + eye.x = eyeX; + eye.y = eyeY; + eye.z = eyeZ; + + lookAt.x = lookAt.y = lookAt.z = 0.0f; + + up.x = up.z = 0.0f; + up.y = 1.0f; + + // @recomp Force interpolation for this view and skip tracking positions. + force_camera_interpolation(); + force_camera_ignore_tracking(); + + View_LookAt(&this->view, &eye, &lookAt, &up); + View_Apply(&this->view, VIEW_ALL | VIEW_FORCE_VIEWING | VIEW_FORCE_VIEWPORT | VIEW_FORCE_PROJECTION_PERSPECTIVE); +} + +bool should_interpolate_perspective(Vec3f* eye, Vec3f* at) { + static Vec3f prev_eye = {0,0,0}; + static Vec3f prev_at = {0,0,0}; + static Vec3f eye_velocity = {0,0,0}; + static Vec3f at_velocity = {0,0,0}; + + Vec3f predicted_eye; + Vec3f predicted_at; + // Predict the new eye and at positions based on the previous velocity and positions. + Math_Vec3f_Sum(&prev_eye, &eye_velocity, &predicted_eye); + Math_Vec3f_Sum(&prev_at, &at_velocity, &predicted_at); + + // Calculate the current velocities from the previous and current positions. + Math_Vec3f_Diff(eye, &prev_eye, &eye_velocity); + Math_Vec3f_Diff(at, &prev_at, &at_velocity); + + // Compare the predicted positions to the real positions. + float eye_dist = Math_Vec3f_DistXYZ(&predicted_eye, eye); + float at_dist = Math_Vec3f_DistXYZ(&predicted_at, at); + + // Compare the velocities of the eye and at positions. + float velocity_diff = Math_Vec3f_DistXYZ(&eye_velocity, &at_velocity); + + // Update the tracking for the previous positions with the new ones. + prev_eye = *eye; + prev_at = *at; + + // These numbers are all picked via testing. + + // If the velocity of both positions was the same, then they're moving together and should interpolate. + if (velocity_diff <= 3.0f && eye_dist <= 100.0f && at_dist <= 100.0f) { + return true; + } + + // If the focus or position are basically the same across frames and the eye didn't move too far then it should probably be interpolated. + if (at_dist <= 10.0f && eye_dist <= 300.0f) { + return true; + } + if (eye_dist <= 10.0f && at_dist <= 300.0f) { + return true; + } + + if (velocity_diff > 50.0f) { + return false; + } + + if (at_dist > 50.0f) { + return false; + } + + if (eye_dist > 300.0f) { + return false; + } + + return true; +} + /** * Apply view to POLY_OPA_DISP, POLY_XLU_DISP (and OVERLAY_DISP if ortho) */ void View_Apply(View* view, s32 mask) { mask = (view->flags & mask) | (mask >> 4); + + // @recomp Determine if the camera should be interpolated this frame. + bool interpolate_camera = false; if (mask & VIEW_PROJECTION_ORTHO) { View_ApplyOrtho(view); } else { View_ApplyPerspective(view); + + // @recomp Determine if interpolation should occur based on the new eye and at positions. + if (!camera_ignore_tracking) { + interpolate_camera = should_interpolate_perspective(&view->eye, &view->at); + } } - + camera_ignore_tracking = false; + + // Force skipping interpolation if the previous view was kaleido and this one isn't. + if (prev_in_kaleido && !in_kaleido) { + camera_skip_interpolation_forced = true; + recomp_printf("exited kaleido\n"); + } + + // @recomp Apply camera interpolation overrides. + if (camera_skip_interpolation_forced) { + interpolate_camera = false; + } + else if (camera_interpolation_forced) { + interpolate_camera = true; + } + // @recomp Tag the camera matrices GraphicsContext* gfxCtx = view->gfxCtx; OPEN_DISPS(gfxCtx); @@ -48,10 +193,10 @@ void View_Apply(View* view, s32 mask) { G_EX_COMPONENT_SKIP, G_EX_COMPONENT_SKIP, G_EX_COMPONENT_SKIP, G_EX_COMPONENT_SKIP, G_EX_COMPONENT_INTERPOLATE, G_EX_ORDER_LINEAR); gEXMatrixGroupSimple(POLY_XLU_DISP++, CAMERA_TRANSFORM_ID_START, G_EX_NOPUSH, G_MTX_PROJECTION, G_EX_COMPONENT_SKIP, G_EX_COMPONENT_SKIP, G_EX_COMPONENT_SKIP, G_EX_COMPONENT_SKIP, G_EX_COMPONENT_INTERPOLATE, G_EX_ORDER_LINEAR); - recomp_printf("Skipped camera interpolation\n"); } - interpolate_camera = true; + camera_interpolation_forced = false; + camera_skip_interpolation_forced = false; CLOSE_DISPS(gfxCtx); } diff --git a/patches/debug_patches.c b/patches/debug_patches.c index c9a51c7..b11ad25 100644 --- a/patches/debug_patches.c +++ b/patches/debug_patches.c @@ -1,6 +1,5 @@ #include "patches.h" #include "input.h" -#include "z64debug_display.h" void Message_FindMessage(PlayState* play, u16 textId); extern SceneEntranceTableEntry sSceneEntranceTable[]; @@ -55,43 +54,3 @@ void debug_play_update(PlayState* play) { do_warp(play, pending_warp); } } - -extern Input D_801F6C18; - -// @recomp Patched to add a point to modify state before the frame's update. -void Play_Main(GameState* thisx) { - static Input* prevInput = NULL; - PlayState* this = (PlayState*)thisx; - - // @recomp - debug_play_update(this); - - // @recomp avoid unused variable warning - (void)prevInput; - - prevInput = CONTROLLER1(&this->state); - DebugDisplay_Init(); - - { - GraphicsContext* gfxCtx = this->state.gfxCtx; - - if (1) { - this->state.gfxCtx = NULL; - } - Play_Update(this); - this->state.gfxCtx = gfxCtx; - } - - { - Input input = *CONTROLLER1(&this->state); - - if (1) { - *CONTROLLER1(&this->state) = D_801F6C18; - } - Play_Draw(this); - *CONTROLLER1(&this->state) = input; - } - - CutsceneManager_Update(); - CutsceneManager_ClearWaiting(); -} diff --git a/patches/play_patches.c b/patches/play_patches.c new file mode 100644 index 0000000..804a351 --- /dev/null +++ b/patches/play_patches.c @@ -0,0 +1,44 @@ +#include "play_patches.h" +#include "z64debug_display.h" + +extern Input D_801F6C18; + +// @recomp Patched to add hooks for various added functionality. +void Play_Main(GameState* thisx) { + static Input* prevInput = NULL; + PlayState* this = (PlayState*)thisx; + + // @recomp + debug_play_update(this); + + // @recomp avoid unused variable warning + (void)prevInput; + + prevInput = CONTROLLER1(&this->state); + DebugDisplay_Init(); + + { + GraphicsContext* gfxCtx = this->state.gfxCtx; + + if (1) { + this->state.gfxCtx = NULL; + } + camera_pre_play_update(this); + Play_Update(this); + camera_post_play_update(this); + this->state.gfxCtx = gfxCtx; + } + + { + Input input = *CONTROLLER1(&this->state); + + if (1) { + *CONTROLLER1(&this->state) = D_801F6C18; + } + Play_Draw(this); + *CONTROLLER1(&this->state) = input; + } + + CutsceneManager_Update(); + CutsceneManager_ClearWaiting(); +} diff --git a/patches/play_patches.h b/patches/play_patches.h new file mode 100644 index 0000000..3af7df4 --- /dev/null +++ b/patches/play_patches.h @@ -0,0 +1,10 @@ +#ifndef __PLAY_PATCHES_H__ +#define __PLAY_PATCHES_H__ + +#include "patches.h" + +void debug_play_update(PlayState* play); +void camera_pre_play_update(PlayState* play); +void camera_post_play_update(PlayState* play); + +#endif diff --git a/patches/transform_ids.h b/patches/transform_ids.h index 5dc0f10..3a29f31 100644 --- a/patches/transform_ids.h +++ b/patches/transform_ids.h @@ -34,4 +34,7 @@ static inline u32 actor_transform_id(Actor* actor) { return (actor_id * ACTOR_TRANSFORM_ID_COUNT) + ACTOR_TRANSFORM_ID_START; } +void force_camera_interpolation(); +void force_camera_skip_interpolation(); + #endif diff --git a/src/game/config.cpp b/src/game/config.cpp index f67575d..6de4d16 100644 --- a/src/game/config.cpp +++ b/src/game/config.cpp @@ -15,6 +15,14 @@ constexpr std::u8string_view graphics_filename = u8"graphics.json"; constexpr std::u8string_view controls_filename = u8"controls.json"; +constexpr auto res_default = ultramodern::Resolution::Auto; +constexpr auto wm_default = ultramodern::WindowMode::Windowed; +constexpr auto ar_default = RT64::UserConfiguration::AspectRatio::Expand; +constexpr auto msaa_default = RT64::UserConfiguration::Antialiasing::MSAA4X; +constexpr auto rr_default = RT64::UserConfiguration::RefreshRate::Display; +constexpr int rr_manual_default = 60; +constexpr bool developer_mode_default = false; + namespace ultramodern { void to_json(json& j, const GraphicsConfig& config) { j = json{ @@ -24,16 +32,29 @@ namespace ultramodern { {"msaa_option", config.msaa_option}, {"rr_option", config.rr_option}, {"rr_manual_value", config.rr_manual_value}, + {"developer_mode", config.developer_mode}, }; } + template + void from_or_default(const json& j, const std::string& key, T& out, T default_value) { + auto find_it = j.find(key); + if (find_it != j.end()) { + find_it->get_to(out); + } + else { + out = default_value; + } + } + void from_json(const json& j, GraphicsConfig& config) { - j.at("res_option") .get_to(config.res_option); - j.at("wm_option") .get_to(config.wm_option); - j.at("ar_option") .get_to(config.ar_option); - j.at("msaa_option") .get_to(config.msaa_option); - j.at("rr_option") .get_to(config.rr_option); - j.at("rr_manual_value").get_to(config.rr_manual_value); + from_or_default(j, "res_option", config.res_option, res_default); + from_or_default(j, "wm_option", config.wm_option, wm_default); + from_or_default(j, "ar_option", config.ar_option, ar_default); + from_or_default(j, "msaa_option", config.msaa_option, msaa_default); + from_or_default(j, "rr_option", config.rr_option, rr_default); + from_or_default(j, "rr_manual_value", config.rr_manual_value, rr_manual_default); + from_or_default(j, "developer_mode", config.developer_mode, developer_mode_default); } } @@ -110,12 +131,13 @@ void recomp::reset_input_bindings() { void recomp::reset_graphics_options() { ultramodern::GraphicsConfig new_config{}; - new_config.res_option = ultramodern::Resolution::Auto; - new_config.wm_option = ultramodern::WindowMode::Windowed; - new_config.ar_option = RT64::UserConfiguration::AspectRatio::Expand; - new_config.msaa_option = RT64::UserConfiguration::Antialiasing::MSAA4X; - new_config.rr_option = RT64::UserConfiguration::RefreshRate::Display; - new_config.rr_manual_value = 60; + new_config.res_option = res_default; + new_config.wm_option = wm_default; + new_config.ar_option = ar_default; + new_config.msaa_option = msaa_default; + new_config.rr_option = rr_default; + new_config.rr_manual_value = rr_manual_default; + new_config.developer_mode = developer_mode_default; ultramodern::set_graphics_config(new_config); } diff --git a/src/recomp/rt64_layer.cpp b/src/recomp/rt64_layer.cpp index 8280fd7..b91b447 100644 --- a/src/recomp/rt64_layer.cpp +++ b/src/recomp/rt64_layer.cpp @@ -61,11 +61,11 @@ typedef struct { #endif #if defined(_WIN32) -extern "C" RT64::Application* InitiateGFXWindows(GFX_INFO Gfx_Info, HWND hwnd, DWORD threadId); +extern "C" RT64::Application* InitiateGFXWindows(GFX_INFO Gfx_Info, HWND hwnd, DWORD threadId, uint8_t debug); #elif defined(__ANDROID__) static_assert(false && "Unimplemented"); #elif defined(__linux__) -extern "C" RT64::Application* InitiateGFXLinux(GFX_INFO Gfx_Info, Window window, Display *display); +extern "C" RT64::Application* InitiateGFXLinux(GFX_INFO Gfx_Info, Window window, Display *display, uint8_t debug); #else static_assert(false && "Unimplemented"); #endif @@ -130,22 +130,10 @@ RT64::UserConfiguration::Antialiasing compute_max_supported_aa(RT64::RenderSampl return RT64::UserConfiguration::Antialiasing::None; } -RT64::Application* RT64Init(uint8_t* rom, uint8_t* rdram, ultramodern::WindowHandle window_handle) { +RT64::Application* RT64Init(uint8_t* rom, uint8_t* rdram, ultramodern::WindowHandle window_handle, bool debug) { set_rt64_hooks(); - // Dynamic loading - //auto RT64 = LoadLibrary("RT64.dll"); - //if (RT64 == 0) { - // fprintf(stdout, "Failed to load RT64\n"); - // std::exit(EXIT_FAILURE); - //} - //GET_FUNC(RT64, InitiateGFX); - //GET_FUNC(RT64, ProcessRDPList); - //GET_FUNC(RT64, ProcessDList); - //GET_FUNC(RT64, UpdateScreen); GFX_INFO gfx_info{}; - // gfx_info.hWnd = window_handle; - // gfx_info.hStatusBar = nullptr; gfx_info.HEADER = rom; gfx_info.RDRAM = rdram; @@ -185,11 +173,11 @@ RT64::Application* RT64Init(uint8_t* rom, uint8_t* rdram, ultramodern::WindowHan gfx_info.RDRAM_SIZE = &RDRAM_SIZE; #if defined(_WIN32) - RT64::Application* ret = InitiateGFXWindows(gfx_info, window_handle.window, window_handle.thread_id); + RT64::Application* ret = InitiateGFXWindows(gfx_info, window_handle.window, window_handle.thread_id, debug); #elif defined(__ANDROID__) static_assert(false && "Unimplemented"); #elif defined(__linux__) - RT64::Application* ret = InitiateGFXLinux(gfx_info, window_handle.window, window_handle.display); + RT64::Application* ret = InitiateGFXLinux(gfx_info, window_handle.window, window_handle.display, debug); #else static_assert(false && "Unimplemented"); #endif diff --git a/ultramodern/config.hpp b/ultramodern/config.hpp index 1086d75..cbaaf16 100644 --- a/ultramodern/config.hpp +++ b/ultramodern/config.hpp @@ -23,6 +23,7 @@ namespace ultramodern { RT64::UserConfiguration::Antialiasing msaa_option; RT64::UserConfiguration::RefreshRate rr_option; int rr_manual_value; + bool developer_mode; auto operator<=>(const GraphicsConfig& rhs) const = default; }; diff --git a/ultramodern/events.cpp b/ultramodern/events.cpp index cc025ff..6ba8c45 100644 --- a/ultramodern/events.cpp +++ b/ultramodern/events.cpp @@ -276,7 +276,7 @@ void gfx_thread_func(uint8_t* rdram, uint8_t* rom, std::atomic_flag* thread_read ultramodern::GraphicsConfig old_config; - RT64::Application* application = RT64Init(rom, rdram, window_handle); + RT64::Application* application = RT64Init(rom, rdram, window_handle, cur_config.load().developer_mode); if (application == nullptr) { throw std::runtime_error("Failed to initialize RT64!");