Added heuristics to determine when to skip camera interpolation, added ability to enable developer mode in RT64

This commit is contained in:
Mr-Wiseguy 2024-03-02 23:23:29 -05:00
parent 45a13d28c2
commit 569d86d901
11 changed files with 257 additions and 85 deletions

View File

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

@ -1 +1 @@
Subproject commit ad88e63572251bc18daa5d5055a80c4c5f42c630
Subproject commit e95a71768e6efb55dcc9daa02843879a9f79faa6

View File

@ -1,34 +1,179 @@
#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;
if (status == CAM_STATUS_ACTIVE) {
this->activeCamId = camIdx;
void camera_pre_play_update(PlayState* play) {
}
recomp_printf("Changed play camera status %d %d\n", camId, status);
void camera_post_play_update(PlayState* play) {
// Track whether the game is in kaleido.
prev_in_kaleido = in_kaleido;
return Camera_ChangeStatus(this->cameraPtrs[camIdx], status);
if ((play->pauseCtx.state != 0) || (play->pauseCtx.debugEditor != DEBUG_EDITOR_NONE)) {
in_kaleido = true;
}
else {
in_kaleido = false;
}
}
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
@ -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);
}

View File

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

44
patches/play_patches.c Normal file
View File

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

10
patches/play_patches.h Normal file
View File

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

View File

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

View File

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

View File

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

View File

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

View File

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