diff --git a/CMakeLists.txt b/CMakeLists.txt index 4be215f..c366c76 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -130,6 +130,7 @@ set (SOURCES ${CMAKE_SOURCE_DIR}/src/game/scene_table.cpp ${CMAKE_SOURCE_DIR}/src/game/debug.cpp ${CMAKE_SOURCE_DIR}/src/game/quicksaving.cpp + ${CMAKE_SOURCE_DIR}/src/game/recomp_api.cpp ${CMAKE_SOURCE_DIR}/src/ui/ui_renderer.cpp ${CMAKE_SOURCE_DIR}/src/ui/ui_launcher.cpp diff --git a/include/rt64_layer.h b/include/rt64_layer.h index 6a58f6c..fe2550a 100644 --- a/include/rt64_layer.h +++ b/include/rt64_layer.h @@ -20,6 +20,7 @@ void RT64UpdateScreen(uint32_t vi_origin); void RT64ChangeWindow(); void RT64Shutdown(); RT64::UserConfiguration::Antialiasing RT64MaxMSAA(); +uint32_t RT64GetDisplayFramerate(RT64::Application* application); void set_rt64_hooks(); diff --git a/patches/debug_patches.c b/patches/debug_patches.c index b11ad25..883db18 100644 --- a/patches/debug_patches.c +++ b/patches/debug_patches.c @@ -1,5 +1,5 @@ #include "patches.h" -#include "input.h" +#include "misc_funcs.h" void Message_FindMessage(PlayState* play, u16 textId); extern SceneEntranceTableEntry sSceneEntranceTable[]; diff --git a/patches/effect_patches.c b/patches/effect_patches.c new file mode 100644 index 0000000..4748635 --- /dev/null +++ b/patches/effect_patches.c @@ -0,0 +1,112 @@ +#include "patches.h" +#include "graphics.h" + +extern TransitionOverlay gTransitionOverlayTable[]; +extern Gfx sTransWipe3DL[]; + +#define THIS ((TransitionWipe3*)thisx) +// @recomp patched to scale the transition based on aspect ratio +void TransitionWipe3_Draw(void* thisx, Gfx** gfxP) { + Gfx* gfx = *gfxP; + Mtx* modelView = &THIS->modelView[THIS->frame]; + f32 scale = 14.8f; + Gfx* texScroll; + + // @recomp Modify the scale based on the aspect ratio to make sure the transition circle covers the whole screen + float original_aspect_ratio = ((float)SCREEN_WIDTH) / ((float)SCREEN_HEIGHT); + scale *= recomp_get_aspect_ratio(original_aspect_ratio) / original_aspect_ratio; + + THIS->frame ^= 1; + gDPPipeSync(gfx++); + texScroll = Gfx_BranchTexScroll(&gfx, THIS->scrollX, THIS->scrollY, 16, 64); + gSPSegment(gfx++, 0x09, texScroll); + gSPSegment(gfx++, 0x08, THIS->curTexture); + gDPSetColor(gfx++, G_SETPRIMCOLOR, THIS->color.rgba); + gDPSetColor(gfx++, G_SETENVCOLOR, THIS->color.rgba); + gSPMatrix(gfx++, &THIS->projection, G_MTX_NOPUSH | G_MTX_LOAD | G_MTX_PROJECTION); + gSPPerspNormalize(gfx++, THIS->normal); + gSPMatrix(gfx++, &THIS->lookAt, G_MTX_NOPUSH | G_MTX_MUL | G_MTX_PROJECTION); + + if (scale != 1.0f) { + guScale(modelView, scale, scale, 1.0f); + gSPMatrix(gfx++, modelView, G_MTX_NOPUSH | G_MTX_LOAD | G_MTX_MODELVIEW); + } + // sTransWipe3DL is an overlay symbol, so its addresses need to be offset to get the actual loaded vram address. + // TODO remove this once the recompiler is able to handle overlay symbols automatically for patch functions. + ptrdiff_t reloc_offset; + TransitionOverlay* overlay_entry = &gTransitionOverlayTable[FBDEMO_WIPE3]; + reloc_offset = (uintptr_t)Lib_PhysicalToVirtual(overlay_entry->loadInfo.addr) - (uintptr_t)overlay_entry->vramStart; + gSPDisplayList(gfx++, (Gfx*)((u8*)sTransWipe3DL + reloc_offset)); + gDPPipeSync(gfx++); + *gfxP = gfx; +} + +#undef THIS + + +typedef enum { + /* 0 */ MOTION_BLUR_OFF, + /* 1 */ MOTION_BLUR_SETUP, + /* 2 */ MOTION_BLUR_PROCESS +} MotionBlurStatus; + +extern u8 sMotionBlurStatus; +extern s32 gFramerateDivisor; + +// @recomp Motion blur works fine normally, but when running at a higher framerate the effect is much less pronounced +// as the previous frames decay quicker due to there being more frames drawn in the same period of time. +void Play_DrawMotionBlur(PlayState* this) { + GraphicsContext* gfxCtx = this->state.gfxCtx; + s32 alpha; + Gfx* gfx; + Gfx* gfxHead; + + if (R_MOTION_BLUR_PRIORITY_ENABLED) { + alpha = R_MOTION_BLUR_PRIORITY_ALPHA; + + if (sMotionBlurStatus == MOTION_BLUR_OFF) { + sMotionBlurStatus = MOTION_BLUR_SETUP; + } + } else if (R_MOTION_BLUR_ENABLED) { + alpha = R_MOTION_BLUR_ALPHA; + + if (sMotionBlurStatus == MOTION_BLUR_OFF) { + sMotionBlurStatus = MOTION_BLUR_SETUP; + } + } else { + alpha = 0; + sMotionBlurStatus = MOTION_BLUR_OFF; + } + + if (sMotionBlurStatus != MOTION_BLUR_OFF) { + OPEN_DISPS(gfxCtx); + + gfxHead = POLY_OPA_DISP; + gfx = Graph_GfxPlusOne(gfxHead); + + gSPDisplayList(OVERLAY_DISP++, gfx); + + this->pauseBgPreRender.fbuf = gfxCtx->curFrameBuffer; + this->pauseBgPreRender.fbufSave = this->unk_18E64; + + // @recomp Scale alpha based on the target framerate so that the blur effect decays at an equivalent rate + // to how it does in the original game's framerate. + alpha = (s32)(recomp_powf(alpha / 255.0f, 20.0f / recomp_get_target_framerate(gFramerateDivisor)) * 255.0f); + + if (sMotionBlurStatus == MOTION_BLUR_PROCESS) { + func_80170AE0(&this->pauseBgPreRender, &gfx, alpha); + } else { + sMotionBlurStatus = MOTION_BLUR_PROCESS; + } + + PreRender_SaveFramebuffer(&this->pauseBgPreRender, &gfx); + + gSPEndDisplayList(gfx++); + + Graph_BranchDlist(gfxHead, gfx); + + POLY_OPA_DISP = gfx; + + CLOSE_DISPS(gfxCtx); + } +} diff --git a/patches/graphics.h b/patches/graphics.h index 82d8f86..88ccb77 100644 --- a/patches/graphics.h +++ b/patches/graphics.h @@ -3,6 +3,7 @@ #include "patch_helpers.h" -DECLARE_FUNC(float, recomp_get_aspect_ratio); +DECLARE_FUNC(float, recomp_get_aspect_ratio, float); +DECLARE_FUNC(s32, recomp_get_target_framerate, s32); #endif diff --git a/patches/input.h b/patches/input.h index 8f3c1d7..ac53e46 100644 --- a/patches/input.h +++ b/patches/input.h @@ -11,11 +11,5 @@ typedef enum { extern RecompCameraMode recomp_camera_mode; DECLARE_FUNC(void, recomp_get_gyro_deltas, float* x, float* y); -// TODO move these -DECLARE_FUNC(void, recomp_puts, const char* data, u32 size); -DECLARE_FUNC(void, recomp_exit); -DECLARE_FUNC(void, recomp_handle_quicksave_actions, OSMesgQueue* enter_mq, OSMesgQueue* exit_mq); -DECLARE_FUNC(void, recomp_handle_quicksave_actions_main, OSMesgQueue* enter_mq, OSMesgQueue* exit_mq); -DECLARE_FUNC(u16, recomp_get_pending_warp); #endif diff --git a/patches/misc_funcs.h b/patches/misc_funcs.h new file mode 100644 index 0000000..8890dfe --- /dev/null +++ b/patches/misc_funcs.h @@ -0,0 +1,12 @@ +#ifndef __RECOMP_FUNCS_H__ +#define __RECOMP_FUNCS_H__ + +#include "patch_helpers.h" + +DECLARE_FUNC(void, recomp_puts, const char* data, u32 size); +DECLARE_FUNC(void, recomp_exit); +DECLARE_FUNC(void, recomp_handle_quicksave_actions, OSMesgQueue* enter_mq, OSMesgQueue* exit_mq); +DECLARE_FUNC(void, recomp_handle_quicksave_actions_main, OSMesgQueue* enter_mq, OSMesgQueue* exit_mq); +DECLARE_FUNC(u16, recomp_get_pending_warp); + +#endif diff --git a/patches/options.c b/patches/options.c index d7db2de..37b97b0 100644 --- a/patches/options.c +++ b/patches/options.c @@ -1,5 +1,5 @@ #include "patches.h" -#include "input.h" +#include "misc_funcs.h" #include "z64shrink_window.h" #include "overlays/gamestates/ovl_file_choose/z_file_select.h" diff --git a/patches/patches.h b/patches/patches.h index 0f2d92e..c88a276 100644 --- a/patches/patches.h +++ b/patches/patches.h @@ -12,6 +12,7 @@ int recomp_printf(const char* fmt, ...); +float recomp_powf(float, float); static inline void* actor_relocate(Actor* actor, void* addr) { return (void*)((uintptr_t)addr - diff --git a/patches/print.c b/patches/print.c index f118d6a..0fec414 100644 --- a/patches/print.c +++ b/patches/print.c @@ -1,5 +1,5 @@ #include "patches.h" -#include "input.h" +#include "misc_funcs.h" void* proutPrintf(void* dst, const char* fmt, size_t size) { recomp_puts(fmt, size); diff --git a/patches/syms.ld b/patches/syms.ld index dbc1b9a..3202b30 100644 --- a/patches/syms.ld +++ b/patches/syms.ld @@ -13,3 +13,5 @@ osSendMesg_recomp = 0x81000014; recomp_get_gyro_deltas = 0x81000018; recomp_get_aspect_ratio = 0x8100001C; recomp_get_pending_warp = 0x81000020; +recomp_powf = 0x81000024; +recomp_get_target_framerate = 0x81000028; diff --git a/patches/widescreen_effect_fixes.c b/patches/widescreen_effect_fixes.c deleted file mode 100644 index 5cff1e6..0000000 --- a/patches/widescreen_effect_fixes.c +++ /dev/null @@ -1,43 +0,0 @@ -#include "patches.h" -#include "graphics.h" - -extern TransitionOverlay gTransitionOverlayTable[]; -extern Gfx sTransWipe3DL[]; - -#define THIS ((TransitionWipe3*)thisx) -// @recomp patched to scale the transition based on aspect ratio -void TransitionWipe3_Draw(void* thisx, Gfx** gfxP) { - Gfx* gfx = *gfxP; - Mtx* modelView = &THIS->modelView[THIS->frame]; - f32 scale = 14.8f; - Gfx* texScroll; - - // @recomp Modify the scale based on the aspect ratio to make sure the transition circle covers the whole screen - scale *= (recomp_get_aspect_ratio() / (4.0f / 3.0f)); - - THIS->frame ^= 1; - gDPPipeSync(gfx++); - texScroll = Gfx_BranchTexScroll(&gfx, THIS->scrollX, THIS->scrollY, 16, 64); - gSPSegment(gfx++, 0x09, texScroll); - gSPSegment(gfx++, 0x08, THIS->curTexture); - gDPSetColor(gfx++, G_SETPRIMCOLOR, THIS->color.rgba); - gDPSetColor(gfx++, G_SETENVCOLOR, THIS->color.rgba); - gSPMatrix(gfx++, &THIS->projection, G_MTX_NOPUSH | G_MTX_LOAD | G_MTX_PROJECTION); - gSPPerspNormalize(gfx++, THIS->normal); - gSPMatrix(gfx++, &THIS->lookAt, G_MTX_NOPUSH | G_MTX_MUL | G_MTX_PROJECTION); - - if (scale != 1.0f) { - guScale(modelView, scale, scale, 1.0f); - gSPMatrix(gfx++, modelView, G_MTX_NOPUSH | G_MTX_LOAD | G_MTX_MODELVIEW); - } - // sTransWipe3DL is an overlay symbol, so its addresses need to be offset to get the actual loaded vram address. - // TODO remove this once the recompiler is able to handle overlay symbols automatically for patch functions. - ptrdiff_t reloc_offset; - TransitionOverlay* overlay_entry = &gTransitionOverlayTable[FBDEMO_WIPE3]; - reloc_offset = (uintptr_t)Lib_PhysicalToVirtual(overlay_entry->loadInfo.addr) - (uintptr_t)overlay_entry->vramStart; - gSPDisplayList(gfx++, (Gfx*)((u8*)sTransWipe3DL + reloc_offset)); - gDPPipeSync(gfx++); - *gfxP = gfx; -} - -#undef THIS \ No newline at end of file diff --git a/src/game/controls.cpp b/src/game/controls.cpp index c9ff3a4..278f5da 100644 --- a/src/game/controls.cpp +++ b/src/game/controls.cpp @@ -3,8 +3,6 @@ #include "recomp_helpers.h" #include "recomp_input.h" #include "../ultramodern/ultramodern.hpp" -#include "../patches/input.h" -#include "../patches/graphics.h" // Arrays that hold the mappings for every input for keyboard and controller respectively. using input_mapping = std::array; @@ -103,36 +101,3 @@ void recomp::get_n64_input(uint16_t* buttons_out, float* x_out, float* y_out) { *x_out = std::clamp(cur_x, -1.0f, 1.0f); *y_out = std::clamp(cur_y, -1.0f, 1.0f); } - -extern "C" void recomp_update_inputs(uint8_t* rdram, recomp_context* ctx) { - recomp::poll_inputs(); -} - -// TODO move these -extern "C" void recomp_puts(uint8_t* rdram, recomp_context* ctx) { - PTR(char) cur_str = _arg<0, PTR(char)>(rdram, ctx); - u32 length = _arg<1, u32>(rdram, ctx); - - for (u32 i = 0; i < length; i++) { - fputc(MEM_B(i, (gpr)cur_str), stdout); - } -} - -extern "C" void recomp_exit(uint8_t* rdram, recomp_context* ctx) { - ultramodern::quit(); -} - -extern "C" void recomp_get_gyro_deltas(uint8_t* rdram, recomp_context* ctx) { - float* x_out = _arg<0, float*>(rdram, ctx); - float* y_out = _arg<1, float*>(rdram, ctx); - - recomp::get_gyro_deltas(x_out, y_out); -} - -#include "recomp_ui.h" -extern "C" void recomp_get_aspect_ratio(uint8_t* rdram, recomp_context* ctx) { - int width, height; - recomp::get_window_size(width, height); - - _return(ctx, static_cast(width) / height); -} diff --git a/src/game/recomp_api.cpp b/src/game/recomp_api.cpp new file mode 100644 index 0000000..5e54d6d --- /dev/null +++ b/src/game/recomp_api.cpp @@ -0,0 +1,64 @@ +#include + +#include "recomp.h" +#include "recomp_input.h" +#include "recomp_ui.h" +#include "recomp_helpers.h" +#include "../patches/input.h" +#include "../patches/graphics.h" +#include "../ultramodern/ultramodern.hpp" +#include "../ultramodern/config.hpp" + +extern "C" void recomp_update_inputs(uint8_t* rdram, recomp_context* ctx) { + recomp::poll_inputs(); +} + +extern "C" void recomp_puts(uint8_t* rdram, recomp_context* ctx) { + PTR(char) cur_str = _arg<0, PTR(char)>(rdram, ctx); + u32 length = _arg<1, u32>(rdram, ctx); + + for (u32 i = 0; i < length; i++) { + fputc(MEM_B(i, (gpr)cur_str), stdout); + } +} + +extern "C" void recomp_exit(uint8_t* rdram, recomp_context* ctx) { + ultramodern::quit(); +} + +extern "C" void recomp_get_gyro_deltas(uint8_t* rdram, recomp_context* ctx) { + float* x_out = _arg<0, float*>(rdram, ctx); + float* y_out = _arg<1, float*>(rdram, ctx); + + recomp::get_gyro_deltas(x_out, y_out); +} + +extern "C" void recomp_powf(uint8_t* rdram, recomp_context* ctx) { + float a = _arg<0, float>(rdram, ctx); + float b = ctx->f14.fl; //_arg<1, float>(rdram, ctx); + + _return(ctx, std::powf(a, b)); +} + +extern "C" void recomp_get_target_framerate(uint8_t* rdram, recomp_context* ctx) { + int frame_divisor = _arg<0, u32>(rdram, ctx); + + _return(ctx, ultramodern::get_target_framerate(60 / frame_divisor)); +} + +extern "C" void recomp_get_aspect_ratio(uint8_t* rdram, recomp_context* ctx) { + ultramodern::GraphicsConfig graphics_config = ultramodern::get_graphics_config(); + float original = _arg<0, float>(rdram, ctx); + int width, height; + recomp::get_window_size(width, height); + + switch (graphics_config.ar_option) { + case RT64::UserConfiguration::AspectRatio::Original: + default: + _return(ctx, original); + return; + case RT64::UserConfiguration::AspectRatio::Expand: + _return(ctx, static_cast(width) / height); + return; + } +} diff --git a/src/recomp/rt64_layer.cpp b/src/recomp/rt64_layer.cpp index b91b447..047d6a9 100644 --- a/src/recomp/rt64_layer.cpp +++ b/src/recomp/rt64_layer.cpp @@ -265,3 +265,7 @@ void RT64EnableInstantPresent(RT64::Application* application) { RT64::UserConfiguration::Antialiasing RT64MaxMSAA() { return device_max_msaa; } + +uint32_t RT64GetDisplayFramerate(RT64::Application* application) { + return application->presentQueue->ext.sharedResources->swapChainRate; +} diff --git a/ultramodern/events.cpp b/ultramodern/events.cpp index 6ba8c45..e6eeadd 100644 --- a/ultramodern/events.cpp +++ b/ultramodern/events.cpp @@ -267,6 +267,23 @@ ultramodern::GraphicsConfig ultramodern::get_graphics_config() { return cur_config; } +std::atomic_uint32_t display_refresh_rate = 60; + +uint32_t ultramodern::get_target_framerate(uint32_t original) { + ultramodern::GraphicsConfig graphics_config = ultramodern::get_graphics_config(); + + switch (graphics_config.rr_option) { + case RT64::UserConfiguration::RefreshRate::Original: + case RT64::UserConfiguration::RefreshRate::OriginalDelay: + default: + return original; + case RT64::UserConfiguration::RefreshRate::Manual: + return graphics_config.rr_manual_value; + case RT64::UserConfiguration::RefreshRate::Display: + return display_refresh_rate.load(); + } +} + void gfx_thread_func(uint8_t* rdram, uint8_t* rom, std::atomic_flag* thread_ready, ultramodern::WindowHandle window_handle) { bool enabled_instant_present = false; using namespace std::chrono_literals; @@ -310,6 +327,7 @@ void gfx_thread_func(uint8_t* rdram, uint8_t* rom, std::atomic_flag* thread_read else if (const auto* swap_action = std::get_if(&action)) { events_context.vi.current_buffer = events_context.vi.next_buffer; RT64UpdateScreen(swap_action->origin); + display_refresh_rate = RT64GetDisplayFramerate(application); } else if (const auto* config_action = std::get_if(&action)) { ultramodern::GraphicsConfig new_config = cur_config; diff --git a/ultramodern/ultramodern.hpp b/ultramodern/ultramodern.hpp index cf36938..41b3432 100644 --- a/ultramodern/ultramodern.hpp +++ b/ultramodern/ultramodern.hpp @@ -94,6 +94,8 @@ void send_si_message(); uint32_t get_speed_multiplier(); std::chrono::system_clock::time_point get_start(); std::chrono::system_clock::duration time_since_start(); +void get_window_size(uint32_t& width, uint32_t& height); +uint32_t get_target_framerate(uint32_t original); // Audio void init_audio();