Improved motion blur at higher framerates, reorganized some patch code

This commit is contained in:
Mr-Wiseguy 2024-03-03 02:36:14 -05:00
parent 569d86d901
commit f6e11ed723
17 changed files with 222 additions and 88 deletions

View File

@ -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

View File

@ -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();

View File

@ -1,5 +1,5 @@
#include "patches.h"
#include "input.h"
#include "misc_funcs.h"
void Message_FindMessage(PlayState* play, u16 textId);
extern SceneEntranceTableEntry sSceneEntranceTable[];

112
patches/effect_patches.c Normal file
View File

@ -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);
}
}

View File

@ -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

View File

@ -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

12
patches/misc_funcs.h Normal file
View File

@ -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

View File

@ -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"

View File

@ -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 -

View File

@ -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);

View File

@ -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;

View File

@ -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

View File

@ -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<recomp::InputField, recomp::bindings_per_input>;
@ -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<float>(width) / height);
}

64
src/game/recomp_api.cpp Normal file
View File

@ -0,0 +1,64 @@
#include <cmath>
#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<float>(width) / height);
return;
}
}

View File

@ -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;
}

View File

@ -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<SwapBuffersAction>(&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<UpdateConfigAction>(&action)) {
ultramodern::GraphicsConfig new_config = cur_config;

View File

@ -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();