From fb9178cac902854ca896c24e83bcbd47b183e665 Mon Sep 17 00:00:00 2001 From: Irastris Date: Sat, 11 Apr 2026 03:06:25 -0400 Subject: [PATCH] Implement unlocked framerates via interpolation (#315) * Disable waitForTick and waitBlanking * Initial frame interpolation implementation * Initial batch of speed fixes * Fix Iron Boots * Strip dead code once used for debugging * Interpolate shadows * Revert overzealous/redundant lookups * Fix JUTFader * Fix field map cursor * Fix various particle effects * Fix Midna when riding Wolf Link * Fix title logo * Title Logo 2: Electric Boogaloo * Fixed grass and flowers * "Unlock Framerate" config option (WIP) * Wrap more things in TARGET_PC * Finish wrapping things in TARGET_PC * Missed one * Disable dComIfGd_drawXluListInvisible when interpolating --------- Co-authored-by: Luke Street --- files.cmake | 1 + include/d/d_com_inf_game.h | 9 +- include/dusk/frame_interpolation.h | 34 ++ include/dusk/settings.h | 1 + include/m_Do/m_Do_graphic.h | 1 + .../JSystem/J3DGraphAnimator/J3DModel.h | 10 +- .../include/JSystem/J3DGraphBase/J3DSys.h | 11 +- .../include/JSystem/JFramework/JFWDisplay.h | 4 + .../JSystem/src/J3DGraphAnimator/J3DModel.cpp | 23 + libs/JSystem/src/J3DGraphBase/J3DShapeMtx.cpp | 33 +- libs/JSystem/src/JFramework/JFWDisplay.cpp | 36 +- libs/JSystem/src/JUtility/JUTFader.cpp | 28 +- src/d/actor/d_a_alink.cpp | 34 +- src/d/actor/d_a_midna.cpp | 7 + src/d/actor/d_a_title.cpp | 20 +- src/d/actor/d_flower.inc | 27 +- src/d/actor/d_grass.inc | 16 +- src/d/d_camera.cpp | 11 +- src/d/d_drawlist.cpp | 53 ++- src/d/d_menu_fmap2D.cpp | 13 +- src/d/d_meter2_draw.cpp | 87 ++-- src/d/d_msg_scrn_light.cpp | 27 +- src/d/d_select_cursor.cpp | 67 ++- src/d/d_select_icon.cpp | 26 +- src/dusk/frame_interpolation.cpp | 398 ++++++++++++++++++ src/dusk/imgui/ImGuiMenuEnhancements.cpp | 16 +- src/dusk/imgui/ImGuiMenuGame.cpp | 8 + src/dusk/settings.cpp | 2 + src/f_ap/f_ap_game.cpp | 18 +- src/f_pc/f_pc_draw.cpp | 7 + src/f_pc/f_pc_manager.cpp | 11 +- src/m_Do/m_Do_graphic.cpp | 103 ++++- src/m_Do/m_Do_main.cpp | 44 +- 33 files changed, 1039 insertions(+), 147 deletions(-) create mode 100644 include/dusk/frame_interpolation.h create mode 100644 src/dusk/frame_interpolation.cpp diff --git a/files.cmake b/files.cmake index 2f14dd49f0..40d8eb9540 100644 --- a/files.cmake +++ b/files.cmake @@ -1340,6 +1340,7 @@ set(DUSK_FILES src/dusk/config.cpp src/dusk/settings.cpp src/dusk/logging.cpp + src/dusk/frame_interpolation.cpp src/dusk/layout.cpp src/dusk/stubs.cpp src/dusk/endian.cpp diff --git a/include/d/d_com_inf_game.h b/include/d/d_com_inf_game.h index 78ab356500..d64226c0db 100644 --- a/include/d/d_com_inf_game.h +++ b/include/d/d_com_inf_game.h @@ -4833,7 +4833,14 @@ inline void dComIfGd_drawXluListDark() { inline void dComIfGd_drawXluListInvisible() { ZoneScoped; - g_dComIfG_gameInfo.drawlist.drawXluListInvisible(); +#ifdef TARGET_PC + // FIXME: Water rendering hack for frame interpolation + if (!dusk::getSettings().game.enableFrameInterpolation) { +#endif + g_dComIfG_gameInfo.drawlist.drawXluListInvisible(); +#ifdef TARGET_PC + } +#endif } inline void dComIfGd_drawOpaListInvisible() { diff --git a/include/dusk/frame_interpolation.h b/include/dusk/frame_interpolation.h new file mode 100644 index 0000000000..3dfa00d53b --- /dev/null +++ b/include/dusk/frame_interpolation.h @@ -0,0 +1,34 @@ +#ifndef DUSK_FRAME_INTERP_H +#define DUSK_FRAME_INTERP_H + +#include +#include +#include +#include + +#ifdef __cplusplus +namespace dusk { +namespace frame_interp { + +void ensure_initialized(); + +void begin_record(); +void end_record(); +void interpolate(float step); +void notify_sim_tick_complete(); +uint32_t begin_presentation_ui_pass(); +uint32_t get_presentation_ui_advance_ticks(); +void end_presentation_ui_pass(); + +void open_child(const void* key, int32_t id); +void close_child(); +void record_final_mtx_raw(const Mtx* dest, const Mtx src); + +bool lookup_replacement(const void* source, Mtx out); +bool lookup_concat_replacement(const void* lhs, const void* rhs, Mtx out); + +} // namespace frame_interp +} // namespace dusk +#endif + +#endif diff --git a/include/dusk/settings.h b/include/dusk/settings.h index 7724948fd5..4a1250e312 100644 --- a/include/dusk/settings.h +++ b/include/dusk/settings.h @@ -53,6 +53,7 @@ struct UserSettings { // Graphics ConfigVar enableBloom; ConfigVar useWaterProjectionOffset; + ConfigVar enableFrameInterpolation; // Audio ConfigVar noLowHpSound; diff --git a/include/m_Do/m_Do_graphic.h b/include/m_Do/m_Do_graphic.h index 421e1ae47f..1d3d0d16fb 100644 --- a/include/m_Do/m_Do_graphic.h +++ b/include/m_Do/m_Do_graphic.h @@ -237,6 +237,7 @@ public: static void* getZbufferTex() { return mZbufferTex; } static void setFadeRate(f32 rate) { mFadeRate = rate; } static f32 getFadeRate() { return mFadeRate; } + static f32 getFadeSpeed() { return mFadeSpeed; } static bloom_c* getBloom() { return &m_bloom; } static GXColor& getFadeColor() { return mFadeColor; } static GXColor& getBackColor() { return mBackColor; } diff --git a/libs/JSystem/include/JSystem/J3DGraphAnimator/J3DModel.h b/libs/JSystem/include/JSystem/J3DGraphAnimator/J3DModel.h index 5ec2aa5f2e..a27db4fa00 100644 --- a/libs/JSystem/include/JSystem/J3DGraphAnimator/J3DModel.h +++ b/libs/JSystem/include/JSystem/J3DGraphAnimator/J3DModel.h @@ -3,6 +3,7 @@ #include "JSystem/J3DGraphAnimator/J3DSkinDeform.h" #include "JSystem/J3DGraphBase/J3DPacket.h" +#include "dusk/frame_interpolation.h" #include enum J3DMdlFlag { @@ -101,7 +102,14 @@ public: void setUserArea(uintptr_t area) { mUserArea = area; } uintptr_t getUserArea() const { return mUserArea; } Vec* getBaseScale() { return &mBaseScale; } - void setAnmMtx(int jointNo, Mtx m) { mMtxBuffer->setAnmMtx(jointNo, m); } + void setAnmMtx(int jointNo, Mtx m) { + mMtxBuffer->setAnmMtx(jointNo, m); +#ifdef TARGET_PC + dusk::frame_interp::record_final_mtx_raw( + reinterpret_cast(mMtxBuffer->getAnmMtx(jointNo)), + mMtxBuffer->getAnmMtx(jointNo)); +#endif + } MtxP getAnmMtx(int jointNo) { return mMtxBuffer->getAnmMtx(jointNo); } MtxP getWeightAnmMtx(int i) { return mMtxBuffer->getWeightAnmMtx(i); } J3DSkinDeform* getSkinDeform() { return mSkinDeform; } diff --git a/libs/JSystem/include/JSystem/J3DGraphBase/J3DSys.h b/libs/JSystem/include/JSystem/J3DGraphBase/J3DSys.h index b3c4f1eb16..8c4bbd06e3 100644 --- a/libs/JSystem/include/JSystem/J3DGraphBase/J3DSys.h +++ b/libs/JSystem/include/JSystem/J3DGraphBase/J3DSys.h @@ -6,6 +6,7 @@ #include "JSystem/J3DAssert.h" #include "JSystem/JMath/JMath.h" +#include "dusk/frame_interpolation.h" #include "dusk/endian.h" enum J3DSysDrawBuf { @@ -188,7 +189,15 @@ struct J3DSys { Mtx& getModelDrawMtx(u16 no) { return mModelDrawMtx[no]; } J3DShapePacket* getShapePacket() { return mShapePacket; } - void setViewMtx(const Mtx m) { MTXCopy(m, mViewMtx); } + void setViewMtx(const Mtx m) { +#ifdef TARGET_PC + Mtx patched; + if (dusk::frame_interp::lookup_replacement(m, patched)) { + m = patched; + } +#endif + MTXCopy(m, mViewMtx); + } J3DModel* getModel() { return mModel; } diff --git a/libs/JSystem/include/JSystem/JFramework/JFWDisplay.h b/libs/JSystem/include/JSystem/JFramework/JFWDisplay.h index 28967cc0a2..fab39a296b 100644 --- a/libs/JSystem/include/JSystem/JFramework/JFWDisplay.h +++ b/libs/JSystem/include/JSystem/JFramework/JFWDisplay.h @@ -101,6 +101,10 @@ public: void setDrawDoneMethod(EDrawDone drawDone) { mDrawDoneMethod = drawDone; } void setFader(JUTFader* fader) { mFader = fader; } +#ifdef TARGET_PC + // For frame interpolation + void setFaderSimSteps(u32 steps); +#endif void resetFader() { setFader(NULL); } JUTFader* getFader() const { return mFader; } void setClearColor(JUtility::TColor color) { mClearColor = color; } diff --git a/libs/JSystem/src/J3DGraphAnimator/J3DModel.cpp b/libs/JSystem/src/J3DGraphAnimator/J3DModel.cpp index c0f6dda58b..6233d6dff7 100644 --- a/libs/JSystem/src/J3DGraphAnimator/J3DModel.cpp +++ b/libs/JSystem/src/J3DGraphAnimator/J3DModel.cpp @@ -8,6 +8,7 @@ #include "JSystem/J3DGraphBase/J3DShapeMtx.h" #include "JSystem/J3DGraphBase/J3DSys.h" #include "JSystem/JKernel/JKRHeap.h" +#include "dusk/frame_interpolation.h" #define J3D_ASSERTMSG(LINE, COND, MSG) JUT_ASSERT_MSG(LINE, (COND) != 0, MSG) #define J3D_WARN1(LINE, MSG, ARG1) JUT_WARN(LINE, MSG, ARG1) @@ -448,6 +449,16 @@ void J3DModel::calc() { if (mCalcCallBack != NULL) { mCalcCallBack(this, 0); } + +#ifdef TARGET_PC + for (u16 i = 0; i < mModelData->getJointNum(); ++i) { + dusk::frame_interp::record_final_mtx_raw(reinterpret_cast(getAnmMtx(i)), getAnmMtx(i)); + } + + for (u16 i = 0; i < mModelData->getWEvlpMtxNum(); ++i) { + dusk::frame_interp::record_final_mtx_raw(reinterpret_cast(getWeightAnmMtx(i)), getWeightAnmMtx(i)); + } +#endif } void J3DModel::entry() { @@ -484,11 +495,17 @@ void J3DModel::viewCalc() { if (getMtxCalcMode() == 2) { J3DCalcViewBaseMtx(j3dSys.getViewMtx(), mBaseScale, mBaseTransformMtx, (MtxP)&mInternalView); +#ifdef TARGET_PC + dusk::frame_interp::record_final_mtx_raw(&mInternalView, mInternalView); +#endif } } else if (isCpuSkinningOn()) { if (getMtxCalcMode() == 2) { J3DCalcViewBaseMtx(j3dSys.getViewMtx(), mBaseScale, mBaseTransformMtx, (MtxP)&mInternalView); +#ifdef TARGET_PC + dusk::frame_interp::record_final_mtx_raw(&mInternalView, mInternalView); +#endif } } else if (checkFlag(J3DMdlFlag_SkinPosCpu)) { mMtxBuffer->calcDrawMtx(getMtxCalcMode(), mBaseScale, mBaseTransformMtx); @@ -509,6 +526,12 @@ void J3DModel::viewCalc() { DCStoreRange(getNrmMtxPtr(), mModelData->getDrawMtxNum() * sizeof(Mtx33)); } +#ifdef TARGET_PC + for (u16 i = 0; i < mModelData->getDrawMtxNum(); ++i) { + dusk::frame_interp::record_final_mtx_raw(&getDrawMtxPtr()[i], getDrawMtxPtr()[i]); + } +#endif + prepareShapePackets(); } diff --git a/libs/JSystem/src/J3DGraphBase/J3DShapeMtx.cpp b/libs/JSystem/src/J3DGraphBase/J3DShapeMtx.cpp index 6048ea45ee..d9af135999 100644 --- a/libs/JSystem/src/J3DGraphBase/J3DShapeMtx.cpp +++ b/libs/JSystem/src/J3DGraphBase/J3DShapeMtx.cpp @@ -6,9 +6,20 @@ #include "JSystem/J3DGraphBase/J3DMatBlock.h" #include "JSystem/J3DGraphBase/J3DSys.h" #include "JSystem/J3DGraphBase/J3DTexture.h" +#include "dusk/frame_interpolation.h" u16 J3DShapeMtx::sMtxLoadCache[10]; +#ifdef TARGET_PC +static void J3DFrameInterpConcat(MtxP lhs, MtxP rhs, Mtx out) { + if (!dusk::frame_interp::lookup_concat_replacement(lhs, rhs, out)) { + MTXConcat(lhs, rhs, out); + } +} +#else +#define J3DFrameInterpConcat MTXConcat +#endif + void J3DShapeMtx::resetMtxLoadCache() { sMtxLoadCache[0] = sMtxLoadCache[1] = @@ -290,7 +301,7 @@ void J3DDifferedTexMtx::loadExecute(f32 const (*param_0)[4]) { void J3DShapeMtxConcatView::loadMtxConcatView_PNGP(int slot, u16 drw) const { Mtx m; - MTXConcat(*j3dSys.getShapePacket()->getBaseMtxPtr(), j3dSys.getModelDrawMtx(drw), m); + J3DFrameInterpConcat(*j3dSys.getShapePacket()->getBaseMtxPtr(), j3dSys.getModelDrawMtx(drw), m); J3DDifferedTexMtx::load(m); J3DFifoLoadPosMtxImm(m, slot * 3); loadNrmMtx(slot, drw, m); @@ -298,7 +309,7 @@ void J3DShapeMtxConcatView::loadMtxConcatView_PNGP(int slot, u16 drw) const { void J3DShapeMtxConcatView::loadMtxConcatView_PCPU(int slot, u16 drw) const { Mtx m; - MTXConcat(*j3dSys.getShapePacket()->getBaseMtxPtr(), j3dSys.getModelDrawMtx(drw), m); + J3DFrameInterpConcat(*j3dSys.getShapePacket()->getBaseMtxPtr(), j3dSys.getModelDrawMtx(drw), m); J3DDifferedTexMtx::load(m); J3DFifoLoadPosMtxImm(*j3dSys.getShapePacket()->getBaseMtxPtr(), slot * 3); loadNrmMtx(slot, drw, m); @@ -306,7 +317,7 @@ void J3DShapeMtxConcatView::loadMtxConcatView_PCPU(int slot, u16 drw) const { void J3DShapeMtxConcatView::loadMtxConcatView_NCPU(int slot, u16 drw) const { Mtx m; - MTXConcat(*j3dSys.getShapePacket()->getBaseMtxPtr(), j3dSys.getModelDrawMtx(drw), m); + J3DFrameInterpConcat(*j3dSys.getShapePacket()->getBaseMtxPtr(), j3dSys.getModelDrawMtx(drw), m); J3DDifferedTexMtx::load(m); J3DFifoLoadPosMtxImm(m, slot * 3); J3DFifoLoadNrmMtxImm(*j3dSys.getShapePacket()->getBaseMtxPtr(), slot * 3); @@ -318,7 +329,7 @@ void J3DShapeMtxConcatView::loadMtxConcatView_NCPU(int slot, u16 drw) const { void J3DShapeMtxConcatView::loadMtxConcatView_PNCPU(int slot, u16 drw) const { if (J3DDifferedTexMtx::sTexGenBlock != NULL) { Mtx m; - MTXConcat(*j3dSys.getShapePacket()->getBaseMtxPtr(), j3dSys.getModelDrawMtx(drw), m); + J3DFrameInterpConcat(*j3dSys.getShapePacket()->getBaseMtxPtr(), j3dSys.getModelDrawMtx(drw), m); J3DDifferedTexMtx::loadExecute(m); } @@ -331,7 +342,7 @@ void J3DShapeMtxConcatView::loadMtxConcatView_PNCPU(int slot, u16 drw) const { void J3DShapeMtxConcatView::loadMtxConcatView_PNGP_LOD(int slot, u16 drw) const { Mtx m; - MTXConcat(*j3dSys.getShapePacket()->getBaseMtxPtr(), j3dSys.getModelDrawMtx(drw), m); + J3DFrameInterpConcat(*j3dSys.getShapePacket()->getBaseMtxPtr(), j3dSys.getModelDrawMtx(drw), m); Mtx copy; j3dSys.getModel()->getModelData()->getInvJointMtx(drw).to_host(copy); MTXConcat(m, copy, m); @@ -490,9 +501,11 @@ void J3DShapeMtxBBoardConcatView::load() const { u16 draw_mtx_index = j3dSys.getModel()->getModelData()->getDrawMtxIndex(mUseMtxIndex); if (j3dSys.getModel()->getModelData()->getDrawMtxFlag(mUseMtxIndex) == 0) { - MTXConcat(j3dSys.getViewMtx(), j3dSys.getModel()->getMtxBuffer()->getUserAnmMtx(draw_mtx_index), mtx); + J3DFrameInterpConcat(j3dSys.getViewMtx(), + j3dSys.getModel()->getMtxBuffer()->getUserAnmMtx(draw_mtx_index), mtx); } else { - MTXConcat(j3dSys.getViewMtx(), j3dSys.getModel()->getWeightAnmMtx(draw_mtx_index), mtx); + J3DFrameInterpConcat(j3dSys.getViewMtx(), + j3dSys.getModel()->getWeightAnmMtx(draw_mtx_index), mtx); } J3DCalcBBoardMtx(mtx); @@ -521,9 +534,11 @@ void J3DShapeMtxYBBoardConcatView::load() const { u16 draw_mtx_index = j3dSys.getModel()->getModelData()->getDrawMtxIndex(mUseMtxIndex); if (j3dSys.getModel()->getModelData()->getDrawMtxFlag(mUseMtxIndex) == 0) { - MTXConcat(j3dSys.getViewMtx(), j3dSys.getModel()->getMtxBuffer()->getUserAnmMtx(draw_mtx_index), mtx1); + J3DFrameInterpConcat(j3dSys.getViewMtx(), + j3dSys.getModel()->getMtxBuffer()->getUserAnmMtx(draw_mtx_index), mtx1); } else { - MTXConcat(j3dSys.getViewMtx(), j3dSys.getModel()->getWeightAnmMtx(draw_mtx_index), mtx1); + J3DFrameInterpConcat(j3dSys.getViewMtx(), + j3dSys.getModel()->getWeightAnmMtx(draw_mtx_index), mtx1); } J3DCalcYBBoardMtx(mtx1); diff --git a/libs/JSystem/src/JFramework/JFWDisplay.cpp b/libs/JSystem/src/JFramework/JFWDisplay.cpp index 27873ecbe3..0ae8e95fa7 100644 --- a/libs/JSystem/src/JFramework/JFWDisplay.cpp +++ b/libs/JSystem/src/JFramework/JFWDisplay.cpp @@ -201,6 +201,14 @@ void JFWDisplay::preGX() { } } +#ifdef TARGET_PC +static s32 s_faderSimSteps = -1; + +void JFWDisplay::setFaderSimSteps(u32 steps) { + s_faderSimSteps = static_cast(steps); +} +#endif + void JFWDisplay::endGX() { s32 bufferNum = JUTXfb::getManager()->getBufferNum(); u16 width = JUTVideo::getManager()->getFbWidth(); @@ -211,7 +219,26 @@ void JFWDisplay::endGX() { if (mFader != NULL) { ortho.setPort(); +#ifdef TARGET_PC + if (dusk::getSettings().game.enableFrameInterpolation) { + u32 advance_count = 1; + if (s_faderSimSteps >= 0) { + advance_count = static_cast(s_faderSimSteps); + s_faderSimSteps = -1; + } + for (u32 i = 0; i < advance_count; i++) { + mFader->control(); + } + if (mFader->getStatus() != 1) { + mFader->draw(); + } + } else { + mFader->control(); + mFader->draw(); + } +#else mFader->control(); +#endif } ortho.setPort(); JUTDbPrint::getManager()->flush(); @@ -351,13 +378,16 @@ void JFWDisplay::waitBlanking(int param_0) { } static void waitForTick(u32 p1, u16 p2) { - ZoneScopedC(tracy::Color::DimGray); - #if TARGET_PC +#if TARGET_PC + if (dusk::getSettings().game.enableFrameInterpolation) { + return; + } if (dusk::getTransientSettings().skipFrameRateLimit) { p1 = OS_TIMER_CLOCK / 120; } - #endif +#endif + ZoneScopedC(tracy::Color::DimGray); if (p1 != 0) { static OSTime nextTick = OSGetTime(); diff --git a/libs/JSystem/src/JUtility/JUTFader.cpp b/libs/JSystem/src/JUtility/JUTFader.cpp index 754979115c..1848e92787 100644 --- a/libs/JSystem/src/JUtility/JUTFader.cpp +++ b/libs/JSystem/src/JUtility/JUTFader.cpp @@ -19,24 +19,24 @@ JUTFader::JUTFader(int x, int y, int width, int height, JUtility::TColor pColor) void JUTFader::control() { if (0 <= mEStatus && mEStatus-- == 0) { - mStatus = field_0x24; - } + mStatus = field_0x24; + } - if (mStatus == 1) { - return; - } + if (mStatus == 1) { + return; + } - switch (mStatus) { + switch (mStatus) { case 0: mColor.a = 0xFF; break; case 2: - #if AVOID_UB +#if AVOID_UB if (field_0x8 == 0) { mStatus = 1; break; } - #endif +#endif mColor.a = 0xFF - ((++field_0xa * 0xFF) / field_0x8); if (field_0xa >= field_0x8) { @@ -45,12 +45,12 @@ void JUTFader::control() { break; case 3: - #if AVOID_UB +#if AVOID_UB if (field_0x8 == 0) { mStatus = 0; break; } - #endif +#endif mColor.a = ((++field_0xa * 0xFF) / field_0x8); if (field_0xa >= field_0x8) { @@ -58,8 +58,12 @@ void JUTFader::control() { } break; - } - draw(); + } + +#ifndef TARGET_PC + // Frame interpolation: draw call moved to JFWDisplay + draw(); +#endif } void JUTFader::draw() { diff --git a/src/d/actor/d_a_alink.cpp b/src/d/actor/d_a_alink.cpp index 670dd169c3..e754d7522f 100644 --- a/src/d/actor/d_a_alink.cpp +++ b/src/d/actor/d_a_alink.cpp @@ -850,7 +850,7 @@ daAlink_FaceTexData const daAlink_c::m_faceTexDataTable[] = { {dRes_ID_ALANM_BTP_FDAM01_e, dRes_ID_ALANM_BTK_FFINISHA_e}, {dRes_ID_ALANM_BTP_FFINISHA_e, dRes_ID_ALANM_BTK_FFINISHED_e}, {dRes_ID_ALANM_BTP_FARELORD_e, dRes_ID_ALANM_BTK_FARELORD_e}, - {dRes_ID_ALANM_BTP_FARELORDTAME_e, dRes_ID_ALANM_BTK_FARELORDTAME_e}, + {dRes_ID_ALANM_BTP_FARELORDTAME_e, dRes_ID_ALANM_BTK_FARELORDTAME_e}, {dRes_ID_ALANM_BTP_FPUSHW_e, dRes_ID_ALANM_BTK_FPUSHW_e}, {dRes_ID_ALANM_BTP_FPULLW_e, dRes_ID_ALANM_BTK_FPULLW_e}, {dRes_ID_ALANM_BTP_FWAITST_e, dRes_ID_ALANM_BTK_FWAITST_e}, @@ -1001,7 +1001,7 @@ daAlink_FaceTexData const daAlink_c::m_faceTexDataTable[] = { {dRes_ID_ALANM_BTP_WL_FSWIMDIEA_e, dRes_ID_ALANM_BTK_WL_FA_e}, {dRes_ID_ALANM_BTP_WL_FSWIMDIEP_e, dRes_ID_ALANM_BTK_WL_FA_e}, {dRes_ID_ALANM_BTP_WL_FMDSHOCK_e, dRes_ID_ALANM_BTK_WL_FMDSHOCK_e}, - {dRes_ID_ALANM_BTP_WL_FENTRANCE_e, dRes_ID_ALANM_BTK_WL_FENTRANCE_e}, + {dRes_ID_ALANM_BTP_WL_FENTRANCE_e, dRes_ID_ALANM_BTK_WL_FENTRANCE_e}, {dRes_ID_ALANM_BTP_WL_FHOWLC_e, dRes_ID_ALANM_BTK_WL_FA_e}, {dRes_ID_ALANM_BTP_WL_FC_e, dRes_ID_ALANM_BTK_WL_FA_e}, }; @@ -4943,7 +4943,7 @@ int daAlink_c::create() { if (dComIfG_resLoad(&mShieldPhaseReq, mShieldArcName, mpShieldArcHeap) != cPhs_COMPLEATE_e) { return cPhs_INIT_e; } - + u32 heapSize = 0x3E930; heapSize |= 0x80000000; heapSize |= 0x40000000; @@ -5885,7 +5885,7 @@ void daAlink_c::setItemMatrix(int param_0) { || (mProcID == PROC_CUT_REVERSE && mProcVar2.field_0x300c != 0) || mProcID == PROC_GUARD_BREAK || (mEquipItem == 0x103 && !checkEndResetFlg1(ERFLG1_SHIELD_BACKBONE) && !checkModeFlg(0x400)) - ) + ) { mShieldModel->setBaseTRMtx(mpLinkModel->getAnmMtx(mRightItemJntNo)); @@ -5949,9 +5949,23 @@ void daAlink_c::setItemMatrix(int param_0) { mpLinkBootModels[0]->setAnmMtx(3, mpLinkModel->getAnmMtx(0x15)); mDoMtx_stack_c::XrotS(-0x8000); - mDoMtx_concat(mpLinkModel->getAnmMtx(0x18), mDoMtx_stack_c::get(), mpLinkBootModels[1]->getAnmMtx(1)); - mDoMtx_concat(mpLinkModel->getAnmMtx(0x19), mDoMtx_stack_c::get(), mpLinkBootModels[1]->getAnmMtx(2)); - mDoMtx_concat(mpLinkModel->getAnmMtx(0x1A), mDoMtx_stack_c::get(), mpLinkBootModels[1]->getAnmMtx(3)); +#ifdef TARGET_PC + if (dusk::getSettings().game.enableFrameInterpolation) { + Mtx boot_mtx; + mDoMtx_concat(mpLinkModel->getAnmMtx(0x18), mDoMtx_stack_c::get(), boot_mtx); + mpLinkBootModels[1]->setAnmMtx(1, boot_mtx); + mDoMtx_concat(mpLinkModel->getAnmMtx(0x19), mDoMtx_stack_c::get(), boot_mtx); + mpLinkBootModels[1]->setAnmMtx(2, boot_mtx); + mDoMtx_concat(mpLinkModel->getAnmMtx(0x1A), mDoMtx_stack_c::get(), boot_mtx); + mpLinkBootModels[1]->setAnmMtx(3, boot_mtx); + } else { +#endif + mDoMtx_concat(mpLinkModel->getAnmMtx(0x18), mDoMtx_stack_c::get(), mpLinkBootModels[1]->getAnmMtx(1)); + mDoMtx_concat(mpLinkModel->getAnmMtx(0x19), mDoMtx_stack_c::get(), mpLinkBootModels[1]->getAnmMtx(2)); + mDoMtx_concat(mpLinkModel->getAnmMtx(0x1A), mDoMtx_stack_c::get(), mpLinkBootModels[1]->getAnmMtx(3)); +#ifdef TARGET_PC + } +#endif } if (!checkNoResetFlg2(FLG2_STATUS_WINDOW_DRAW)) { @@ -6279,7 +6293,7 @@ void daAlink_c::setWolfAtCollision() { void daAlink_c::resetAtCollision(BOOL param_0) { if (checkNoResetFlg0(FLG0_CUT_AT_FLG)) { - if (param_0 + if (param_0 && !setSwordHitVibration(&mAtCps[0]) && !setSwordHitVibration(&mAtCps[1]) && !setSwordHitVibration(&mAtCps[2]) @@ -14055,7 +14069,7 @@ void daAlink_c::setMetamorphoseModel(BOOL i_isChangeToWolf) { JKRHeap* heap = setItemHeap(); mHeldItemModel = initModel(loadAramBmd(dRes_ID_ALANM_BMD_AL_WF_e, 0x6000), 0); - + if (!mItemBck.init(bck, FALSE, 2, 1.0f, 0, -1, false)) { JUT_ASSERT(20842, FALSE); } @@ -16727,7 +16741,7 @@ int daAlink_c::procAutoJump() { #if VERSION == VERSION_SHIELD_DEBUG if (!checkStageName("F_SP115") && mGrabItemAcKeep.getActor() != NULL) { if ((fopAcM_GetName(mGrabItemAcKeep.getActor()) == fpcNm_NI_e && ((ni_class*)mGrabItemAcKeep.getActor())->checkGold() != TRUE) || - (fopAcM_GetName(mGrabItemAcKeep.getActor()) == fpcNm_NPC_TKJ2_e)) + (fopAcM_GetName(mGrabItemAcKeep.getActor()) == fpcNm_NPC_TKJ2_e)) { mMaxSpeed = mpHIO->mAutoJump.m.mCuccoJumpMaxSpeed; field_0x3478 = mpHIO->mAutoJump.m.mCuccoFallMaxSpeed; diff --git a/src/d/actor/d_a_midna.cpp b/src/d/actor/d_a_midna.cpp index c63f4e5251..01d472e075 100644 --- a/src/d/actor/d_a_midna.cpp +++ b/src/d/actor/d_a_midna.cpp @@ -14,6 +14,7 @@ #include "d/d_msg_object.h" #include "d/d_s_play.h" #include "d/d_debug_viewer.h" +#include "dusk/frame_interpolation.h" static f32 dummy_lit_3777(int idx, u8 foo) { Vec dummy_vec = {0.0f, 0.0f, 0.0f}; @@ -1052,6 +1053,12 @@ void daMidna_c::setBodyPartMatrix() { mpModel->setAnmMtx(i, mpShadowModel->getAnmMtx(i)); } mpModel->calcWeightEnvelopeMtx(); +#ifdef TARGET_PC + // Frame interpolation: Record weight envelopes for Midna here, as they are otherwise missed causing distortion + for (u16 i = 0; i < mpModel->getModelData()->getWEvlpMtxNum(); i++) { + dusk::frame_interp::record_final_mtx_raw(reinterpret_cast(mpModel->getWeightAnmMtx(i)), mpModel->getWeightAnmMtx(i)); + } +#endif } mDoMtx_stack_c::transS(mpShadowModel->getAnmMtx(JNT_BACKBONE1)[0][3], diff --git a/src/d/actor/d_a_title.cpp b/src/d/actor/d_a_title.cpp index 88ce62ec5c..fc727a1248 100644 --- a/src/d/actor/d_a_title.cpp +++ b/src/d/actor/d_a_title.cpp @@ -122,7 +122,7 @@ static procFunc daTitleProc[6] = { int daTitle_c::create() { fopAcM_ct(this, daTitle_c); - + int phase_state = dComIfG_resLoad(&mPhaseReq, l_arcName); if (phase_state != cPhs_COMPLEATE_e) { return phase_state; @@ -152,15 +152,21 @@ int daTitle_c::createHeapCallBack(fopAc_ac_c* actor) { } int daTitle_c::Execute() { - #if PLATFORM_WII || PLATFORM_SHIELD +#if PLATFORM_WII || PLATFORM_SHIELD mDoGph_gInf_c::resetDimming(); - #endif +#endif if (fopOvlpM_IsPeek()) { return 1; } - dMenu_Collect3D_c::setViewPortOffsetY(0.0f); +#ifdef TARGET_PC + if (!dusk::getSettings().game.enableFrameInterpolation) { +#endif + dMenu_Collect3D_c::setViewPortOffsetY(0.0f); +#ifdef TARGET_PC + } +#endif if (mDoRst::isReset()) { return 1; @@ -169,9 +175,9 @@ int daTitle_c::Execute() { (this->*daTitleProc[mProcID])(); KeyWaitAnm(); - #if VERSION == VERSION_SHIELD_DEBUG +#if VERSION == VERSION_SHIELD_DEBUG KeyWaitPosMove(); - #endif +#endif return 1; } @@ -390,7 +396,7 @@ int daTitle_c::Delete() { dComIfG_resDelete(&mPhaseReq, l_arcName); JKR_DELETE(mTitle.Scr); JKR_DELETE(field_0x600); - + mpMount->getArchive()->removeResourceAll(); JKRUnmountArchive(mpMount->getArchive()); mpMount->destroy(); diff --git a/src/d/actor/d_flower.inc b/src/d/actor/d_flower.inc index 4fb53ffaa7..034d35961e 100644 --- a/src/d/actor/d_flower.inc +++ b/src/d/actor/d_flower.inc @@ -5,6 +5,8 @@ #include "JSystem/J3DGraphBase/J3DDrawBuffer.h" #include "SSystem/SComponent/c_counter.h" +#include "dusk/frame_interpolation.h" + #if TARGET_PC const u16 l_J_Ohana00_64TEX__width = 64; const u16 l_J_Ohana00_64TEX__height = 64; @@ -695,7 +697,16 @@ void dFlower_packet_c::draw() { GXSetChanAmbColor(GX_COLOR0A0, sp64); if (!cLib_checkBit(sp44->m_state, 4) && !cLib_checkBit(sp44->m_state, 0x40)) { - GXLoadPosMtxImm(sp44->m_modelMtx, 0); +#ifdef TARGET_PC + Mtx flower_mtx; + if (dusk::frame_interp::lookup_replacement(reinterpret_cast(&sp44->m_modelMtx), flower_mtx)) { + GXLoadPosMtxImm(flower_mtx, 0); + } else { +#endif + GXLoadPosMtxImm(sp44->m_modelMtx, 0); +#ifdef TARGET_PC + } +#endif GXLoadNrmMtxImm(j3dSys.getViewMtx(), 0); #if TARGET_PC @@ -841,7 +852,16 @@ void dFlower_packet_c::draw() { sp30++; if (!cLib_checkBit(sp34->m_state, 4) && cLib_checkBit(sp34->m_state, 0x40)) { - GXLoadPosMtxImm(sp34->m_modelMtx, 0); +#ifdef TARGET_PC + Mtx flower_mtx; + if (dusk::frame_interp::lookup_replacement(reinterpret_cast(&sp34->m_modelMtx), flower_mtx)) { + GXLoadPosMtxImm(flower_mtx, 0); + } else { +#endif + GXLoadPosMtxImm(sp34->m_modelMtx, 0); +#ifdef TARGET_PC + } +#endif GXLoadNrmMtxImm(j3dSys.getViewMtx(), 0); #if TARGET_PC @@ -973,6 +993,9 @@ void dFlower_packet_c::update() { mDoMtx_stack_c::copy(temp_r28); mDoMtx_stack_c::scaleM(temp_f31, temp_f31, temp_f31); cMtx_concat(j3dSys.getViewMtx(), temp_r28, data_p->m_modelMtx); +#ifdef TARGET_PC + dusk::frame_interp::record_final_mtx_raw(reinterpret_cast(&data_p->m_modelMtx), data_p->m_modelMtx); +#endif } } diff --git a/src/d/actor/d_grass.inc b/src/d/actor/d_grass.inc index 3645488b0f..0f651a3203 100644 --- a/src/d/actor/d_grass.inc +++ b/src/d/actor/d_grass.inc @@ -11,6 +11,8 @@ #include "d/d_camera.h" #include "f_op/f_op_camera_mng.h" +#include "dusk/frame_interpolation.h" + const u16 l_M_Hijiki00TEX__width = 31; const u16 l_M_Hijiki00TEX__height = 31; const u16 l_M_kusa05_RGBATEX__width = 31; @@ -751,7 +753,16 @@ void dGrass_packet_c::draw() { GXSetChanAmbColor(GX_COLOR0A0, sp38); if (!cLib_checkBit(var_r29->field_0x01, 2)) { - GXLoadPosMtxImm(var_r29->m_modelMtx, 0); +#ifdef TARGET_PC + Mtx grass_mtx; + if (dusk::frame_interp::lookup_replacement(reinterpret_cast(&var_r29->m_modelMtx), grass_mtx)) { + GXLoadPosMtxImm(grass_mtx, 0); + } else { +#endif + GXLoadPosMtxImm(var_r29->m_modelMtx, 0); +#ifdef TARGET_PC + } +#endif GXLoadNrmMtxImm(j3dSys.getViewMtx(), 0); if (var_r29->field_0x05 <= 3 || var_r29->field_0x05 >= 10) { if (var_r29->field_0x02 < -1) { @@ -1006,6 +1017,9 @@ void dGrass_packet_c::update() { mDoMtx_stack_c::scaleM(scale, scale, scale); cMtx_concat(j3dSys.getViewMtx(), mDoMtx_stack_c::get(), data_p->m_modelMtx); } +#ifdef TARGET_PC + dusk::frame_interp::record_final_mtx_raw(reinterpret_cast(&data_p->m_modelMtx), data_p->m_modelMtx); +#endif } } data_p++; diff --git a/src/d/d_camera.cpp b/src/d/d_camera.cpp index 70e8c105bb..760556e7ae 100644 --- a/src/d/d_camera.cpp +++ b/src/d/d_camera.cpp @@ -20,6 +20,7 @@ #include "m_Do/m_Do_controller_pad.h" #include "m_Do/m_Do_graphic.h" #include "m_Do/m_Do_lib.h" +#include "dusk/frame_interpolation.h" #include #include @@ -11030,16 +11031,20 @@ static int camera_draw(camera_process_class* i_this) { int trim_height = body->TrimHeight(); - #if TARGET_PC +#if TARGET_PC trim_height *= viewport->height / FB_HEIGHT; window->setScissor(0.0f, trim_height, viewport->width, viewport->height - trim_height * 2.0f); - #else +#else window->setScissor(0.0f, trim_height, FB_WIDTH, FB_HEIGHT - trim_height * 2.0f); - #endif +#endif C_MTXPerspective(process->view.projMtx, process->view.fovy, process->view.aspect, process->view.near_, process->view.far_); mDoMtx_lookAt(process->view.viewMtx, &process->view.lookat.eye, &process->view.lookat.center, &process->view.lookat.up, process->view.bank); +#ifdef TARGET_PC + dusk::frame_interp::record_final_mtx_raw(reinterpret_cast(process->view.viewMtx), + process->view.viewMtx); +#endif #if WIDESCREEN_SUPPORT mDoGph_gInf_c::setWideZoomProjection(process->view.projMtx); diff --git a/src/d/d_drawlist.cpp b/src/d/d_drawlist.cpp index 8b7457637d..8a564914c0 100644 --- a/src/d/d_drawlist.cpp +++ b/src/d/d_drawlist.cpp @@ -11,6 +11,7 @@ #include "d/d_com_inf_game.h" #include "d/d_drawlist.h" #include "d/d_s_play.h" +#include "dusk/frame_interpolation.h" #include "dusk/gx_helper.h" #include "dusk/logging.h" #include "m_Do/m_Do_graphic.h" @@ -1089,7 +1090,16 @@ void dDlst_shadowReal_c::draw() { GXSetVtxDesc(GX_VA_POS, GX_DIRECT); GXSetVtxAttrFmt(GX_VTXFMT0, GX_VA_POS, GX_POS_XYZ, GX_F32, 0); GXSetCurrentMtx(GX_PNMTX0); - GXLoadTexMtxImm(mReceiverProjMtx, GX_TEXMTX0, GX_MTX3x4); +#ifdef TARGET_PC + Mtx receiver_proj_mtx; + if (dusk::frame_interp::lookup_replacement(&mReceiverProjMtx, receiver_proj_mtx)) { + GXLoadTexMtxImm(receiver_proj_mtx, GX_TEXMTX0, GX_MTX3x4); + } else { +#endif + GXLoadTexMtxImm(mReceiverProjMtx, GX_TEXMTX0, GX_MTX3x4); +#ifdef TARGET_PC + } +#endif mShadowRealPoly.draw(); } @@ -1247,6 +1257,10 @@ u8 dDlst_shadowReal_c::setShadowRealMtx(cXyz* param_0, cXyz* param_1, f32 param_ C_MTXOrtho(mRenderProjMtx, param_2, -param_2, -param_2, param_2, 1.0f, 10000.0f); C_MTXLightOrtho(mReceiverProjMtx, param_2, -param_2, -param_2, param_2, 0.5f, -0.5f, 0.5f, 0.5f); cMtx_concat(mReceiverProjMtx, mViewMtx, mReceiverProjMtx); +#ifdef TARGET_PC + dusk::frame_interp::record_final_mtx_raw(&mViewMtx, mViewMtx); + dusk::frame_interp::record_final_mtx_raw(&mReceiverProjMtx, mReceiverProjMtx); +#endif return r29; } @@ -1309,13 +1323,31 @@ void dDlst_shadowSimple_c::draw() { GXSetTevColor(GX_TEVREG0, l_color); GXClearVtxDesc(); GXSetVtxDesc(GX_VA_POS, GX_INDEX8); - GXLoadPosMtxImm(mVolumeMtx, GX_PNMTX0); +#ifdef TARGET_PC + Mtx volume_mtx; + if (dusk::frame_interp::lookup_replacement(&mVolumeMtx, volume_mtx)) { + GXLoadPosMtxImm(volume_mtx, GX_PNMTX0); + } else { +#endif + GXLoadPosMtxImm(mVolumeMtx, GX_PNMTX0); +#ifdef TARGET_PC + } +#endif GXSetCurrentMtx(GX_PNMTX0); GXCallDisplayList(l_frontMat, 0x40); GXCallDisplayList(l_shadowVolumeDL, 0x40); GXCallDisplayList(l_backSubMat, 0x20); GXCallDisplayList(l_shadowVolumeDL, 0x40); - GXLoadPosMtxImm(mMtx, GX_PNMTX1); +#ifdef TARGET_PC + Mtx shadow_mtx; + if (dusk::frame_interp::lookup_replacement(&mMtx, shadow_mtx)) { + GXLoadPosMtxImm(shadow_mtx, GX_PNMTX1); + } else { +#endif + GXLoadPosMtxImm(mMtx, GX_PNMTX1); +#ifdef TARGET_PC + } +#endif GXSetCurrentMtx(GX_PNMTX1); if (mpTexObj != NULL) { @@ -1394,6 +1426,10 @@ void dDlst_shadowSimple_c::set(cXyz* param_0, f32 param_1, f32 param_2, cXyz* pa mDoMtx_stack_c::YrotM(param_4); mDoMtx_stack_c::scaleM(param_2, 1.0f, param_2 * param_5); cMtx_concat(j3dSys.getViewMtx(), mDoMtx_stack_c::get(), mMtx); +#ifdef TARGET_PC + dusk::frame_interp::record_final_mtx_raw(&mVolumeMtx, mVolumeMtx); + dusk::frame_interp::record_final_mtx_raw(&mMtx, mMtx); +#endif mpTexObj = param_6; } @@ -1541,7 +1577,16 @@ void dDlst_shadowControl_c::draw(Mtx param_0) { GXSetTexCoordGen(GX_TEXCOORD0, GX_TG_MTX3x4, GX_TG_POS, GX_TEXMTX0); GXSetNumTevStages(1); GXSetVtxAttrFmt(GX_VTXFMT0, GX_VA_POS, GX_POS_XYZ, GX_F32, 0); - GXLoadPosMtxImm(param_0, GX_PNMTX0); +#ifdef TARGET_PC + Mtx draw_mtx; + if (dusk::frame_interp::lookup_replacement(param_0, draw_mtx)) { + GXLoadPosMtxImm(draw_mtx, GX_PNMTX0); + } else { +#endif + GXLoadPosMtxImm(param_0, GX_PNMTX0); +#ifdef TARGET_PC + } +#endif GXColor matColor = {0, 0, 0, 0x20}; GXSetChanMatColor(GX_ALPHA0, matColor); diff --git a/src/d/d_menu_fmap2D.cpp b/src/d/d_menu_fmap2D.cpp index c9ab812400..e016ed14dd 100644 --- a/src/d/d_menu_fmap2D.cpp +++ b/src/d/d_menu_fmap2D.cpp @@ -17,6 +17,7 @@ #include "d/d_msg_scrn_explain.h" #include "m_Do/m_Do_graphic.h" #include "d/actor/d_a_midna.h" +#include "dusk/frame_interpolation.h" #include dMenu_Fmap2DBack_c::dMenu_Fmap2DBack_c() { @@ -390,11 +391,17 @@ void dMenu_Fmap2DBack_c::draw() { (mArrowPos3DZ + control_ypos + fVar3) - fVar5, &mArrowPos2DX, &mArrowPos2DY); - field_0x11e0 -= g_fmapHIO.mCursorSpeed; +#ifdef TARGET_PC + for (u32 i = 0; i < dusk::frame_interp::get_presentation_ui_advance_ticks(); ++i) { +#endif + field_0x11e0 -= g_fmapHIO.mCursorSpeed; - if (field_0x11e0 < 0.0f) { - field_0x11e0 += 360.0f; + if (field_0x11e0 < 0.0f) { + field_0x11e0 += 360.0f; + } +#ifdef TARGET_PC } +#endif mpPointParent->getPanePtr()->rotate(mpPointParent->getSizeX() / 2.0f, mpPointParent->getSizeY() / 2.0f, ROTATE_Z, diff --git a/src/d/d_meter2_draw.cpp b/src/d/d_meter2_draw.cpp index 58c6e24dc9..28f25aa0cd 100644 --- a/src/d/d_meter2_draw.cpp +++ b/src/d/d_meter2_draw.cpp @@ -20,6 +20,7 @@ #include "d/d_msg_class.h" #include "d/d_msg_object.h" #include "d/d_pane_class.h" +#include "dusk/frame_interpolation.h" #include dMeter2Draw_c::dMeter2Draw_c(JKRExpHeap* mp_heap) { @@ -636,38 +637,49 @@ void dMeter2Draw_c::draw() { if (field_0x756 >= 0) { var_f29 = g_drawHIO.mLightDrop.mDropPikariAnimSpeed_Completed; int temp_r5_2 = g_drawHIO.mLightDrop.mPikariInterval * 15; +#ifdef TARGET_PC + // Set even if not advancing + var_f28 = g_drawHIO.mLightDrop.mPikariScaleComplete; - if (field_0x756 <= temp_r5_2) { - int temp_r4 = (field_0x756 % g_drawHIO.mLightDrop.mPikariInterval); - int temp_r3_5 = field_0x756 / g_drawHIO.mLightDrop.mPikariInterval; + const u32 ui_advance_ticks = dusk::frame_interp::get_presentation_ui_advance_ticks(); + for (u32 tick = 0; tick < ui_advance_ticks; ++tick) { +#endif + if (field_0x756 <= temp_r5_2) { + int temp_r4 = (field_0x756 % g_drawHIO.mLightDrop.mPikariInterval); + int temp_r3_5 = field_0x756 / g_drawHIO.mLightDrop.mPikariInterval; - if (temp_r4 == 0 && field_0x62c[temp_r3_5] == 0.0f) { - field_0x62c[temp_r3_5] = 18.0f; - } - - var_f28 = g_drawHIO.mLightDrop.mPikariScaleComplete; - field_0x756++; - } else { - int temp_r5_3 = temp_r5_2 + 1; - - if (field_0x756 == temp_r5_3) { - if (field_0x62c[15] == 0.0f) { - field_0x756++; + if (temp_r4 == 0 && field_0x62c[temp_r3_5] == 0.0f) { + field_0x62c[temp_r3_5] = 18.0f; } var_f28 = g_drawHIO.mLightDrop.mPikariScaleComplete; - } else if (field_0x756 >= g_drawHIO.mLightDrop.field_0x54 + temp_r5_3) { - for (int i = 0; i < 16; i++) { - field_0x62c[i] = 18.0f - var_f29; - field_0x66c[i] = 18.0f - g_drawHIO.mLightDrop.mPikariLoopAnimSpeed; - } - - field_0x756 = -1; - } else { field_0x756++; + } else { + int temp_r5_3 = temp_r5_2 + 1; + + if (field_0x756 == temp_r5_3) { + if (field_0x62c[15] == 0.0f) { + field_0x756++; + } + var_f28 = g_drawHIO.mLightDrop.mPikariScaleComplete; + } else if (field_0x756 >= g_drawHIO.mLightDrop.field_0x54 + temp_r5_3) { + for (int i = 0; i < 16; i++) { + field_0x62c[i] = 18.0f - var_f29; + field_0x66c[i] = 18.0f - g_drawHIO.mLightDrop.mPikariLoopAnimSpeed; + } + + field_0x756 = -1; +#ifdef TARGET_PC + break; +#endif + } else { + field_0x756++; + } } } +#ifdef TARGET_PC } +#endif for (int i = 0; i < 16; i++) { if (field_0x66c[i] > 0.0f) { @@ -1336,20 +1348,27 @@ void dMeter2Draw_c::drawPikari(f32 i_posX, f32 i_posY, f32* i_framep, f32 i_scal if (param_9 != 3 && param_9 != 4 && param_9 != 5 && dMsgObject_isTalkNowCheck()) { *i_framep = 0.0f; } else { - *i_framep += param_8; - if (*i_framep > var_f31) { - if (param_9 == 1 || param_9 == 2 || param_9 == 3) { - *i_framep = 18.0f; - } else { - *i_framep = 0.0f; +#ifdef TARGET_PC + const u32 ui_advance_ticks = dusk::frame_interp::get_presentation_ui_advance_ticks(); + for (u32 i = 0; i < ui_advance_ticks; ++i) { +#endif + *i_framep += param_8; + if (*i_framep > var_f31) { + if (param_9 == 1 || param_9 == 2 || param_9 == 3) { + *i_framep = 18.0f; + } else { + *i_framep = 0.0f; + } } - } - if (*i_framep == 18.0f && param_9 == 1) { - mDoAud_seStart(Z2SE_NAVI_BLINK, NULL, 0, 0); - } else if (*i_framep == 18.0f && param_9 == 2) { - mDoAud_seStart(Z2SE_SY_ITEM_COMBINE_ICON, NULL, 0, 0); + if (*i_framep == 18.0f && param_9 == 1) { + mDoAud_seStart(Z2SE_NAVI_BLINK, NULL, 0, 0); + } else if (*i_framep == 18.0f && param_9 == 2) { + mDoAud_seStart(Z2SE_SY_ITEM_COMBINE_ICON, NULL, 0, 0); + } +#ifdef TARGET_PC } +#endif playPikariBckAnimation(*i_framep); playPikariBpkAnimation(*i_framep); diff --git a/src/d/d_msg_scrn_light.cpp b/src/d/d_msg_scrn_light.cpp index 4635a8e680..c34397aad2 100644 --- a/src/d/d_msg_scrn_light.cpp +++ b/src/d/d_msg_scrn_light.cpp @@ -6,6 +6,7 @@ #include "JSystem/J2DGraph/J2DScreen.h" #include "d/d_com_inf_game.h" #include "d/d_pane_class.h" +#include "dusk/frame_interpolation.h" class dMsgScrnLight_HIO_c { public: @@ -202,10 +203,17 @@ void dMsgScrnLight_c::draw(f32* i_anmFrame, f32 i_posX, f32 i_posY, f32 i_scaleX } if (mPlayAnim) { - *i_anmFrame += 1.0f; - if (*i_anmFrame >= mpBck->getFrameMax()) { - *i_anmFrame = 0.0f; +#ifdef TARGET_PC + const u32 ui_advance_ticks = dusk::frame_interp::get_presentation_ui_advance_ticks(); + for (u32 i = 0; i < ui_advance_ticks; ++i) { +#endif + *i_anmFrame += 1.0f; + if (*i_anmFrame >= mpBck->getFrameMax()) { + *i_anmFrame = 0.0f; + } +#ifdef TARGET_PC } +#endif mBckFrame = *i_anmFrame; mBpkFrame = *i_anmFrame; @@ -220,11 +228,18 @@ void dMsgScrnLight_c::draw(f32* i_anmFrame, f32 i_posX, f32 i_posY, f32 i_scaleX mpParent_c->setBlackWhite(i_black, i_white); if (mPlayAnim) { - *i_anmFrame += i_anmRate; +#ifdef TARGET_PC + const u32 ui_advance_ticks = dusk::frame_interp::get_presentation_ui_advance_ticks(); + for (u32 i = 0; i < ui_advance_ticks; ++i) { +#endif + *i_anmFrame += i_anmRate; - if (*i_anmFrame >= mpBck->getFrameMax()) { - *i_anmFrame = 0.0f; + if (*i_anmFrame >= mpBck->getFrameMax()) { + *i_anmFrame = 0.0f; + } +#ifdef TARGET_PC } +#endif mBckFrame = *i_anmFrame; mBpkFrame = *i_anmFrame; diff --git a/src/d/d_select_cursor.cpp b/src/d/d_select_cursor.cpp index 17ee9ca451..2cdd454737 100644 --- a/src/d/d_select_cursor.cpp +++ b/src/d/d_select_cursor.cpp @@ -5,6 +5,7 @@ #include "d/d_com_inf_game.h" #include "JSystem/J2DGraph/J2DAnimation.h" #include "JSystem/J2DGraph/J2DAnmLoader.h" +#include "dusk/frame_interpolation.h" #include dSelect_cursorHIO_c::dSelect_cursorHIO_c() { @@ -270,15 +271,22 @@ void dSelect_cursor_c::update() { if (mUpdateFlag) { if (field_0x30) { if (chkPlayAnime(0)) { - if (mNameIdx == 1) { - field_0x44 += mpCursorHIO->field_0x8 * fVar1; - } else { - field_0x44 += fVar1; - } +#ifdef TARGET_PC + const u32 ui_advance_ticks = dusk::frame_interp::get_presentation_ui_advance_ticks(); + for (u32 tick = 0; tick < ui_advance_ticks; ++tick) { +#endif + if (mNameIdx == 1) { + field_0x44 += mpCursorHIO->field_0x8 * fVar1; + } else { + field_0x44 += fVar1; + } - if (field_0x44 >= field_0x30->getFrameMax()) { - field_0x44 -= field_0x30->getFrameMax(); + if (field_0x44 >= field_0x30->getFrameMax()) { + field_0x44 -= field_0x30->getFrameMax(); + } +#ifdef TARGET_PC } +#endif field_0x30->setFrame(field_0x44); setBpkAnimation(field_0x30); @@ -294,14 +302,21 @@ void dSelect_cursor_c::update() { for (int i = 0; i < 2; i++) { if (field_0x34[i]) { if ((i == 0 && chkPlayAnime(2)) || (i == 1 && chkPlayAnime(3))) { - if (mNameIdx == 1) { - field_0x48[i] += mpCursorHIO->field_0x8 * fVar1; - } else { - field_0x48[i] += fVar1; - } - if (field_0x48[i] >= field_0x34[i]->getFrameMax()) { - field_0x48[i] -= field_0x34[i]->getFrameMax(); +#ifdef TARGET_PC + const u32 ui_advance_ticks = dusk::frame_interp::get_presentation_ui_advance_ticks(); + for (u32 tick = 0; tick < ui_advance_ticks; ++tick) { +#endif + if (mNameIdx == 1) { + field_0x48[i] += mpCursorHIO->field_0x8 * fVar1; + } else { + field_0x48[i] += fVar1; + } + if (field_0x48[i] >= field_0x34[i]->getFrameMax()) { + field_0x48[i] -= field_0x34[i]->getFrameMax(); + } +#ifdef TARGET_PC } +#endif field_0x34[i]->setFrame(field_0x48[i]); } @@ -310,7 +325,11 @@ void dSelect_cursor_c::update() { } if (field_0x2C && chkPlayAnime(1)) { - if (mNameIdx == 1) { +#ifdef TARGET_PC + const u32 ui_advance_ticks = dusk::frame_interp::get_presentation_ui_advance_ticks(); + for (u32 tick = 0; tick < ui_advance_ticks; ++tick) { +#endif + if (mNameIdx == 1) { field_0x40 += mpCursorHIO->field_0x8 * fVar1; } else { field_0x40 += fVar1; @@ -318,14 +337,24 @@ void dSelect_cursor_c::update() { if (field_0x40 >= field_0x2C->getFrameMax()) { field_0x40 -= field_0x2C->getFrameMax(); } - - field_0x2C->setFrame(field_0x40); - setBckAnimation(field_0x2C); +#ifdef TARGET_PC + } +#endif + + field_0x2C->setFrame(field_0x40); + setBckAnimation(field_0x2C); } if (chkPlayAnime(1) && mNameIdx == 0) { - setCursorAnimation(); +#ifdef TARGET_PC + const u32 ui_advance_ticks = dusk::frame_interp::get_presentation_ui_advance_ticks(); + for (u32 tick = 0; tick < ui_advance_ticks; ++tick) { +#endif + setCursorAnimation(); +#ifdef TARGET_PC + } +#endif } mpScreen->animation(); diff --git a/src/d/d_select_icon.cpp b/src/d/d_select_icon.cpp index cbf5f44381..3a4671fefa 100644 --- a/src/d/d_select_icon.cpp +++ b/src/d/d_select_icon.cpp @@ -2,21 +2,31 @@ #include "d/d_select_icon.h" #include "JSystem/J2DGraph/J2DAnimation.h" +#include "dusk/frame_interpolation.h" dSi_HIO_c::dSi_HIO_c() {} void dSelect_icon_c::animation() { if (field_0x10->getAlpha() != 0) { - field_0x20 += field_0x2c; - if (field_0x20 >= field_0x1c->getFrameMax()) { - field_0x20 = 0.0f; - } - field_0x1c->setFrame(field_0x20); +#ifdef TARGET_PC + const u32 ui_advance_ticks = dusk::frame_interp::get_presentation_ui_advance_ticks(); + for (u32 i = 0; i < ui_advance_ticks; ++i) { +#endif + field_0x20 += field_0x2c; + if (field_0x20 >= field_0x1c->getFrameMax()) { + field_0x20 = 0.0f; + } + field_0x1c->setFrame(field_0x20); - field_0x28 += field_0x2c; - if (field_0x28 >= field_0x24->getFrameMax()) { - field_0x28 = 0.0f; + field_0x28 += field_0x2c; + if (field_0x28 >= field_0x24->getFrameMax()) { + field_0x28 = 0.0f; + } +#ifdef TARGET_PC } + // Set even if not advancing + field_0x1c->setFrame(field_0x20); +#endif field_0x24->setFrame(field_0x28); field_0x8->animation(); diff --git a/src/dusk/frame_interpolation.cpp b/src/dusk/frame_interpolation.cpp new file mode 100644 index 0000000000..a3777dea7e --- /dev/null +++ b/src/dusk/frame_interpolation.cpp @@ -0,0 +1,398 @@ +#include "dusk/frame_interpolation.h" + +#include +#include +#include +#include +#include +#include + +namespace { + +enum class Op : uint8_t { + OpenChild, + FinalMtx, +}; + +struct Label { + const void* key = nullptr; + int32_t id = 0; + + bool operator==(const Label& other) const { + return key == other.key && id == other.id; + } +}; + +struct Data { + Label child_label{}; + size_t child_index = 0; + Mtx matrix{}; + const Mtx* dest = nullptr; +}; + +struct Path; + +struct ChildBucket { + Label label{}; + std::vector> nodes; +}; + +struct OpBucket { + Op op = Op::OpenChild; + std::vector values; +}; + +struct Path { + std::vector children; + std::vector ops; + std::vector> items; +}; + +struct Recording { + Path root; +}; + +struct MatrixValue { + Mtx value; +}; + +using FinalMtxLookup = std::unordered_map; + +bool s_initialized = false; + +bool g_enabled = false; +bool g_recording = false; +bool g_interpolating = false; +float g_step = 0.0f; +uint32_t g_pending_presentation_ui_ticks = 0; +uint32_t g_current_presentation_ui_ticks = 0; + +Recording g_current_recording; +Recording g_previous_recording; +std::vector g_current_path; + +std::unordered_map g_replacements; + +inline void copy_matrix(const Mtx src, Mtx dst) { + MTXCopy(src, dst); +} + +inline void concat_matrix(const Mtx lhs, const Mtx rhs, Mtx out) { + MTXConcat(lhs, rhs, out); +} + +inline void lerp_matrix(Mtx out, const Mtx lhs, const Mtx rhs, float step) { + const float old_weight = 1.0f - step; + for (size_t row = 0; row < 3; ++row) { + for (size_t col = 0; col < 4; ++col) { + out[row][col] = lhs[row][col] * old_weight + rhs[row][col] * step; + } + } +} + +inline bool matrix_differs(const Mtx lhs, const Mtx rhs, float epsilon = 0.0001f) { + for (size_t row = 0; row < 3; ++row) { + for (size_t col = 0; col < 4; ++col) { + if (std::abs(lhs[row][col] - rhs[row][col]) > epsilon) { + return true; + } + } + } + return false; +} + +Data& append_op(Op op) { + auto& items = g_current_path.back()->items; + auto& buckets = g_current_path.back()->ops; + auto it = std::find_if(buckets.begin(), buckets.end(), + [op](const OpBucket& bucket) { return bucket.op == op; }); + if (it == buckets.end()) { + buckets.push_back({op, {}}); + it = buckets.end() - 1; + } + items.emplace_back(op, it->values.size()); + return it->values.emplace_back(); +} + +const Data* find_matching_data(const Path& path, Op op, size_t index) { + auto it = std::find_if(path.ops.begin(), path.ops.end(), + [op](const OpBucket& bucket) { return bucket.op == op; }); + if (it == path.ops.end() || index >= it->values.size()) { + return nullptr; + } + return &it->values[index]; +} + +const OpBucket* find_op_bucket(const Path& path, Op op) { + auto it = std::find_if(path.ops.begin(), path.ops.end(), + [op](const OpBucket& bucket) { return bucket.op == op; }); + if (it == path.ops.end()) { + return nullptr; + } + return &*it; +} + +void build_final_mtx_lookup(const Path& path, FinalMtxLookup& lookup) { + lookup.clear(); + + const OpBucket* bucket = find_op_bucket(path, Op::FinalMtx); + if (bucket == nullptr) { + return; + } + + for (const Data& data : bucket->values) { + if (data.dest == nullptr) { + continue; + } + lookup[data.dest] = &data; + } +} + +const Data* find_matching_final_mtx(const FinalMtxLookup& lookup, const Data& new_data) { + if (new_data.dest == nullptr) { + return nullptr; + } + + auto it = lookup.find(new_data.dest); + if (it == lookup.end()) { + return nullptr; + } + return it->second; +} + +ChildBucket& get_child_bucket(Path& path, const Label& label) { + auto it = std::find_if(path.children.begin(), path.children.end(), + [&label](const ChildBucket& bucket) { return bucket.label == label; }); + if (it == path.children.end()) { + path.children.push_back({}); + it = path.children.end() - 1; + it->label = label; + } + return *it; +} + +const ChildBucket* find_child_bucket(const Path& path, const Label& label) { + auto it = std::find_if(path.children.begin(), path.children.end(), + [&label](const ChildBucket& bucket) { return bucket.label == label; }); + if (it == path.children.end()) { + return nullptr; + } + return &*it; +} + +void store_replacement(const Data& old_data, const Data& new_data, float step) { + if (new_data.dest == nullptr) { + return; + } + + auto& replacement = g_replacements[new_data.dest]; + lerp_matrix(replacement.value, old_data.matrix, new_data.matrix, step); +} + +void interpolate_branch(const Path& old_path, const Path& new_path, float step) { + FinalMtxLookup old_final_mtx_lookup; + build_final_mtx_lookup(old_path, old_final_mtx_lookup); + + for (const auto& item : new_path.items) { + const Op op = item.first; + const size_t index = item.second; + const Data* new_data = find_matching_data(new_path, op, index); + if (new_data == nullptr) { + continue; + } + + if (op == Op::OpenChild) { + const ChildBucket* new_children = find_child_bucket(new_path, new_data->child_label); + if (new_children == nullptr || new_data->child_index >= new_children->nodes.size()) + { + continue; + } + + const Path& new_child = *new_children->nodes[new_data->child_index]; + const ChildBucket* old_children = find_child_bucket(old_path, new_data->child_label); + if (old_children != nullptr && new_data->child_index < old_children->nodes.size()) + { + interpolate_branch(*old_children->nodes[new_data->child_index], new_child, step); + } else { + interpolate_branch(new_child, new_child, step); + } + continue; + } + + const Data* indexed_old_data = find_matching_data(old_path, op, index); + const Data* old_data = op == Op::FinalMtx ? find_matching_final_mtx(old_final_mtx_lookup, *new_data) : indexed_old_data; + if (op == Op::FinalMtx) { + store_replacement(old_data != nullptr ? *old_data : *new_data, *new_data, step); + } + } +} + +const Mtx* resolve_replacement(const Mtx* source, Mtx* scratch) { + if (!g_interpolating || source == nullptr) { + return source; + } + + auto it = g_replacements.find(source); + if (it == g_replacements.end()) { + return source; + } + + copy_matrix(it->second.value, *scratch); + return scratch; +} + +bool has_recording_data(const Recording& recording) { + return !recording.root.items.empty() || !recording.root.children.empty(); +} + +void clear_replacements() { + g_replacements.clear(); +} + +} // namespace + +namespace dusk { +namespace frame_interp { + +void ensure_initialized() { + g_enabled = getSettings().game.enableFrameInterpolation; + s_initialized = true; +} + +void begin_record() { + ensure_initialized(); + if (!g_enabled) { + g_interpolating = false; + g_previous_recording = {}; + g_current_recording = {}; + g_current_path.clear(); + clear_replacements(); + return; + } + + g_previous_recording = std::move(g_current_recording); + g_current_recording = {}; + g_current_path.clear(); + g_current_path.push_back(&g_current_recording.root); + g_recording = true; + g_interpolating = false; + clear_replacements(); +} + +void end_record() { + g_recording = false; +} + +void interpolate(float step) { + ensure_initialized(); + clear_replacements(); + g_step = std::clamp(step, 0.0f, 1.0f); + g_interpolating = g_enabled && !g_recording && has_recording_data(g_current_recording); + if (!g_interpolating) { + return; + } + + if (!has_recording_data(g_previous_recording)) { + interpolate_branch(g_current_recording.root, g_current_recording.root, g_step); + return; + } + + interpolate_branch(g_previous_recording.root, g_current_recording.root, g_step); +} + +void notify_sim_tick_complete() { + ensure_initialized(); + g_pending_presentation_ui_ticks++; +} + +uint32_t begin_presentation_ui_pass() { + ensure_initialized(); + g_current_presentation_ui_ticks = g_pending_presentation_ui_ticks; + g_pending_presentation_ui_ticks = 0; + return g_current_presentation_ui_ticks; +} + +uint32_t get_presentation_ui_advance_ticks() { + if (!s_initialized) { + return 0; + } + if (!g_enabled) { + return 1; + } + return g_current_presentation_ui_ticks; +} + +void end_presentation_ui_pass() { + if (!s_initialized) { + return; + } + g_current_presentation_ui_ticks = 0; +} + +void open_child(const void* key, int32_t id) { + if (!s_initialized || !g_recording) { + return; + } + + Label label{key, id}; + auto& siblings = get_child_bucket(*g_current_path.back(), label).nodes; + Data& data = append_op(Op::OpenChild); + data.child_label = label; + data.child_index = siblings.size(); + siblings.emplace_back(std::make_unique()); + g_current_path.push_back(siblings.back().get()); +} + +void close_child() { + if (!s_initialized || !g_recording || g_current_path.size() <= 1) { + return; + } + + g_current_path.pop_back(); +} + +void record_final_mtx_raw(const Mtx* dest, const Mtx src) { + if (!s_initialized || !g_recording || dest == nullptr) { + return; + } + + Data& data = append_op(Op::FinalMtx); + data.dest = dest; + copy_matrix(src, data.matrix); +} + +bool lookup_replacement(const void* source, Mtx out) { + if (!s_initialized || !g_interpolating || source == nullptr) { + return false; + } + + auto it = g_replacements.find(reinterpret_cast(source)); + if (it == g_replacements.end()) { + return false; + } + + copy_matrix(it->second.value, out); + return true; +} + +bool lookup_concat_replacement(const void* lhs, const void* rhs, Mtx out) { + if (!s_initialized || !g_interpolating || lhs == nullptr || rhs == nullptr) { + return false; + } + + Mtx lhs_scratch; + Mtx rhs_scratch; + const Mtx* resolved_lhs = resolve_replacement(reinterpret_cast(lhs), &lhs_scratch); + const Mtx* resolved_rhs = resolve_replacement(reinterpret_cast(rhs), &rhs_scratch); + if (resolved_lhs == reinterpret_cast(lhs) && + resolved_rhs == reinterpret_cast(rhs)) + { + return false; + } + + concat_matrix(*resolved_lhs, *resolved_rhs, out); + return true; +} + +} // namespace frame_interp +} // namespace dusk diff --git a/src/dusk/imgui/ImGuiMenuEnhancements.cpp b/src/dusk/imgui/ImGuiMenuEnhancements.cpp index c623a910e0..f93ed14630 100644 --- a/src/dusk/imgui/ImGuiMenuEnhancements.cpp +++ b/src/dusk/imgui/ImGuiMenuEnhancements.cpp @@ -52,7 +52,7 @@ namespace dusk { if (ImGui::IsItemHovered()) { ImGui::SetTooltip("Hides the TV calibration screen shown when loading a save"); } - + config::ImGuiCheckbox("Instant Saves", getSettings().game.instantSaves); if (ImGui::IsItemHovered()) { ImGui::SetTooltip("Skip the delay when writing to the Memory Card"); @@ -73,12 +73,16 @@ namespace dusk { } if (ImGui::BeginMenu("Graphics")) { - config::ImGuiCheckbox("Native Bloom", getSettings().game.enableBloom); + config::ImGuiCheckbox("Unlock Framerate", getSettings().game.enableFrameInterpolation); + const bool frameInterpolationHovered = ImGui::IsItemHovered(); - config::ImGuiCheckbox("Water Projection Offset", getSettings().game.useWaterProjectionOffset); - if (ImGui::IsItemHovered()) { - ImGui::SetTooltip("Adds GC-specific -0.01 transS offset\n" - "that causes ~6px ghost artifacts in water reflections"); + ImGui::SameLine(); + ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(1.0f, 0.72f, 0.2f, 1.0f)); + ImGui::TextUnformatted("[EXPERIMENTAL]"); + ImGui::PopStyleColor(); + + if (frameInterpolationHovered || ImGui::IsItemHovered()) { + ImGui::SetTooltip("Uses inter-frame interpolation to enable higher frame rates.\nVisual artifacts, animation glitches, or instability may occur."); } ImGui::EndMenu(); diff --git a/src/dusk/imgui/ImGuiMenuGame.cpp b/src/dusk/imgui/ImGuiMenuGame.cpp index 494dbe1c22..eeb6521b61 100644 --- a/src/dusk/imgui/ImGuiMenuGame.cpp +++ b/src/dusk/imgui/ImGuiMenuGame.cpp @@ -62,6 +62,14 @@ namespace dusk { config::Save(); } + config::ImGuiCheckbox("Native Bloom", getSettings().game.enableBloom); + + config::ImGuiCheckbox("Water Projection Offset", getSettings().game.useWaterProjectionOffset); + if (ImGui::IsItemHovered()) { + ImGui::SetTooltip("Adds GC-specific -0.01 transS offset\n" + "that causes ~6px ghost artifacts in water reflections"); + } + ImGui::EndMenu(); } diff --git a/src/dusk/settings.cpp b/src/dusk/settings.cpp index a8a6c26522..f67eee6fbb 100644 --- a/src/dusk/settings.cpp +++ b/src/dusk/settings.cpp @@ -41,6 +41,7 @@ UserSettings g_userSettings = { // Graphics .enableBloom {"game.enableBloom", true}, .useWaterProjectionOffset {"game.useWaterProjectionOffset", false}, + .enableFrameInterpolation = {"game.enableFrameInterpolation", false}, // Audio .noLowHpSound {"game.noLowHpSound", false}, @@ -110,6 +111,7 @@ void registerSettings() { Register(g_userSettings.game.midnasLamentNonStop); Register(g_userSettings.game.enableTurboKeybind); Register(g_userSettings.game.fastSpinner); + Register(g_userSettings.game.enableFrameInterpolation); Register(g_userSettings.backend.isoPath); Register(g_userSettings.backend.graphicsBackend); diff --git a/src/f_ap/f_ap_game.cpp b/src/f_ap/f_ap_game.cpp index 180e6bc361..8e2b0a367c 100644 --- a/src/f_ap/f_ap_game.cpp +++ b/src/f_ap/f_ap_game.cpp @@ -14,6 +14,7 @@ #include "d/actor/d_a_midna.h" #include "d/d_model.h" #include "d/d_tresure.h" +#include "dusk/frame_interpolation.h" #include "dusk/logging.h" #include "f_op/f_op_camera_mng.h" #include "f_op/f_op_draw_tag.h" @@ -722,6 +723,17 @@ void fapGm_After() { fopCamM_Management(); } +#ifdef TARGET_PC +static void fapGm_Before() { + dusk::frame_interp::begin_record(); +} + +static void fapGm_AfterRecord() { + dusk::frame_interp::end_record(); + fapGm_After(); +} +#endif + void fapGm_Execute() { ZoneScoped; static u32 sExecCount = 0; @@ -752,7 +764,11 @@ void fapGm_Execute() { } #endif - fpcM_Management(NULL, fapGm_After); +#ifdef TARGET_PC + fpcM_Management(fapGm_Before, fapGm_AfterRecord); +#else + fpcM_ManagementFunc(NULL, fapGm_After); +#endif cCt_Counter(0); } diff --git a/src/f_pc/f_pc_draw.cpp b/src/f_pc/f_pc_draw.cpp index 0677a47827..697d19cf29 100644 --- a/src/f_pc/f_pc_draw.cpp +++ b/src/f_pc/f_pc_draw.cpp @@ -8,6 +8,7 @@ #include "f_pc/f_pc_leaf.h" #include "f_pc/f_pc_node.h" #include "f_pc/f_pc_pause.h" +#include "dusk/frame_interpolation.h" #include #include "dusk/logging.h" @@ -25,7 +26,13 @@ int fpcDw_Execute(base_process_class* i_proc) { } fpcLy_SetCurrentLayer(i_proc->layer_tag.layer); +#ifdef TARGET_PC + dusk::frame_interp::open_child(i_proc, 0); +#endif ret = draw_func(i_proc); +#ifdef TARGET_PC + dusk::frame_interp::close_child(); +#endif fpcLy_SetCurrentLayer(save_layer); return ret; } diff --git a/src/f_pc/f_pc_manager.cpp b/src/f_pc/f_pc_manager.cpp index a3d85d4df6..03319db3d8 100644 --- a/src/f_pc/f_pc_manager.cpp +++ b/src/f_pc/f_pc_manager.cpp @@ -20,7 +20,6 @@ #include "f_pc/f_pc_pause.h" #include "f_pc/f_pc_priority.h" #include "m_Do/m_Do_controller_pad.h" -#include #include "tracy/Tracy.hpp" @@ -64,7 +63,14 @@ void fpcM_Management(fpcM_ManagementFunc i_preExecuteFn, fpcM_ManagementFunc i_p l_dvdError = false; } - cAPIGph_Painter(); +#ifdef TARGET_PC + // Frame interpolation: call moved to m_Do_main + if (!dusk::getSettings().game.enableFrameInterpolation) { +#endif + cAPIGph_Painter(); +#ifdef TARGET_PC + } +#endif if (!dPa_control_c::isStatus(1)) { fpcDt_Handler(); @@ -153,4 +159,3 @@ void* fpcM_JudgeInLayer(fpc_ProcID i_layerID, fpcCtIt_JudgeFunc i_judgeFunc, voi return NULL; } - diff --git a/src/m_Do/m_Do_graphic.cpp b/src/m_Do/m_Do_graphic.cpp index a5bd4640c8..9f3a6f7210 100644 --- a/src/m_Do/m_Do_graphic.cpp +++ b/src/m_Do/m_Do_graphic.cpp @@ -32,11 +32,14 @@ #include "dusk/gx_helper.h" #include "dusk/logging.h" #include "f_ap/f_ap_game.h" +#include "f_op/f_op_actor_mng.h" #include "f_op/f_op_camera_mng.h" +#include "f_pc/f_pc_name.h" #include "m_Do/m_Do_controller_pad.h" #include "m_Do/m_Do_graphic.h" #include "m_Do/m_Do_machine.h" #include "m_Do/m_Do_main.h" +#include "dusk/frame_interpolation.h" #include "tracy/Tracy.hpp" #if PLATFORM_WII || PLATFORM_SHIELD @@ -465,6 +468,51 @@ void darwFilter(GXColor matColor) { GXEnd(); } +#ifdef TARGET_PC +static void mDoGph_AdvanceFadeState() { + if (mDoGph_gInf_c::isFade() != 0) { + f32 fade_rate = mDoGph_gInf_c::getFadeRate() + mDoGph_gInf_c::getFadeSpeed(); + + if (fade_rate < 0.0f) { + fade_rate = 0.0f; + mDoGph_gInf_c::offFade(); + } else if (fade_rate > 1.0f) { + fade_rate = 1.0f; + } + + mDoGph_gInf_c::setFadeRate(fade_rate); + mDoGph_gInf_c::getFadeColor().a = 255.0f * fade_rate; + } else { + GXColor& fade_color = mDoGph_gInf_c::getFadeColor(); + if (dComIfG_getBrightness() != 255) { + fade_color.r = 0; + fade_color.g = 0; + fade_color.b = 0; + fade_color.a = 255 - dComIfG_getBrightness(); + } else { + fade_color.a = 0; + } + } +} + +static void mDoGph_AdvanceFadeState(u32 tick_count) { + for (u32 i = 0; i < tick_count; ++i) { + mDoGph_AdvanceFadeState(); + } +} + +static void mDoGph_DrawStoredFade() { + GXColor& fade_color = mDoGph_gInf_c::getFadeColor(); + if (fade_color.a != 0) { + darwFilter(fade_color); + } +} + +void mDoGph_gInf_c::calcFade() { + mDoGph_AdvanceFadeState(); + mDoGph_DrawStoredFade(); +} +#else void mDoGph_gInf_c::calcFade() { if (mDoGph_gInf_c::mFade != 0) { mFadeRate += mFadeSpeed; @@ -493,6 +541,7 @@ void mDoGph_gInf_c::calcFade() { darwFilter(mFadeColor); } } +#endif #if PLATFORM_WII || PLATFORM_SHIELD u32 mDoGph_gInf_c::csr_c::m_blurID; @@ -819,6 +868,9 @@ int mDoGph_AfterOfDraw() { JUTVideo::getManager()->setRenderMode(mDoMch_render_c::getRenderModeObj()); mDoGph_gInf_c::endFrame(); +#ifdef TARGET_PC + dusk::frame_interp::notify_sim_tick_complete(); +#endif return 1; } @@ -1661,6 +1713,15 @@ static void captureScreenPerspDrawInfo(JPADrawInfo& info) { static void drawItem3D() { ZoneScoped; +#ifdef TARGET_PC + // Frame interpolation: Title screen needs 0.0f while everything else that runs through this is -100.0f. + // Running presentation faster than logic revealed the problem. Thanks, Nintendo. + if (fopAcM_SearchByName(fpcNm_TITLE_e) != nullptr) { + dMenu_Collect3D_c::setViewPortOffsetY(0.0f); + } else { + dMenu_Collect3D_c::setViewPortOffsetY(-100.0f); + } +#endif Mtx item_mtx; dMenu_Collect3D_c::setupItem3D(item_mtx); @@ -1688,13 +1749,21 @@ int mDoGph_Painter() { #if TARGET_PC dusk::g_imguiConsole.PreDraw(); + + const u32 pending_ui_ticks = dusk::frame_interp::begin_presentation_ui_pass(); #endif #if DEBUG drawHeapMap(); #endif - dComIfGp_particle_calcMenu(); +#ifdef TARGET_PC + for (u32 i = 0; i < pending_ui_ticks; ++i) { +#endif + dComIfGp_particle_calcMenu(); +#ifdef TARGET_PC + } +#endif JFWDisplay::getManager()->setFader(mDoGph_gInf_c::getFader()); mDoGph_gInf_c::setClearColor(mDoGph_gInf_c::getBackColor()); @@ -1780,7 +1849,13 @@ int mDoGph_Painter() { GXSetScissor(view_port->x_orig, view_port->y_orig, view_port->width, view_port->height); +#ifdef TARGET_PC + // Frame interpolation: Call setViewMtx earlier so that it's interpolated in time for draw_info to use it + j3dSys.setViewMtx(camera_p->view.viewMtx); + JPADrawInfo draw_info(j3dSys.getViewMtx(), camera_p->view.fovy, camera_p->view.aspect); +#else JPADrawInfo draw_info(camera_p->view.viewMtx, camera_p->view.fovy, camera_p->view.aspect); +#endif #if WIDESCREEN_SUPPORT if (mDoGph_gInf_c::isWideZoom()) { @@ -1818,7 +1893,9 @@ int mDoGph_Painter() { PPCSync(); +#ifndef TARGET_PC j3dSys.setViewMtx(camera_p->view.viewMtx); +#endif dKy_setLight(); GX_DEBUG_GROUP(dComIfGd_drawOpaListSky); GX_DEBUG_GROUP(dComIfGd_drawXluListSky); @@ -2159,7 +2236,12 @@ int mDoGph_Painter() { if (strcmp(dComIfGp_getStartStageName(), "F_SP127") != 0 && (mDoGph_gInf_c::isFade() & 0x80) == 0) { +#ifdef TARGET_PC + mDoGph_AdvanceFadeState(pending_ui_ticks); + mDoGph_DrawStoredFade(); +#else mDoGph_gInf_c::calcFade(); +#endif } #if DEBUG @@ -2224,7 +2306,13 @@ int mDoGph_Painter() { #endif GXSetClipMode(GX_CLIP_ENABLE); - dDlst_list_c::calcWipe(); +#ifdef TARGET_PC + for (u32 i = 0; i < pending_ui_ticks; ++i) { +#endif + dDlst_list_c::calcWipe(); +#ifdef TARGET_PC + } +#endif j3dSys.reinitGX(); ortho.setOrtho(mDoGph_gInf_c::getMinXF(), mDoGph_gInf_c::getMinYF(), @@ -2274,7 +2362,12 @@ int mDoGph_Painter() { if (strcmp(dComIfGp_getStartStageName(), "F_SP127") == 0 || (mDoGph_gInf_c::isFade() & 0x80) != 0) { +#ifdef TARGET_PC + mDoGph_AdvanceFadeState(pending_ui_ticks); + mDoGph_DrawStoredFade(); +#else mDoGph_gInf_c::calcFade(); +#endif } GX_DEBUG_GROUP(dComIfGp_particle_draw2DmenuFore, &draw_info3); @@ -2307,10 +2400,16 @@ int mDoGph_Painter() { #if TARGET_PC dusk::g_imguiConsole.PostDraw(); + + JFWDisplay::getManager()->setFaderSimSteps(pending_ui_ticks); #endif mDoGph_gInf_c::endRender(); +#ifdef TARGET_PC + dusk::frame_interp::end_presentation_ui_pass(); +#endif + #if WIDESCREEN_SUPPORT mDoGph_gInf_c::offWideZoom(); #endif diff --git a/src/m_Do/m_Do_main.cpp b/src/m_Do/m_Do_main.cpp index 1fc8cc2128..1be3a06b44 100644 --- a/src/m_Do/m_Do_main.cpp +++ b/src/m_Do/m_Do_main.cpp @@ -20,6 +20,7 @@ #include "JSystem/JUtility/JUTProcBar.h" #include "JSystem/JUtility/JUTReport.h" #include "SSystem/SComponent/c_counter.h" +#include "SSystem/SComponent/c_API_graphic.h" #include "Z2AudioLib/Z2WolfHowlMgr.h" #include "c/c_dylink.h" #include "d/d_com_inf_game.h" @@ -46,6 +47,7 @@ #include "SSystem/SComponent/c_API.h" #include "dusk/app_info.hpp" #include "dusk/dusk.h" +#include "dusk/frame_interpolation.h" #include "dusk/imgui/ImGuiEngine.hpp" #include "dusk/logging.h" #include "dusk/main.h" @@ -197,6 +199,11 @@ void main01(void) { if (preLaunchUIWindowSize.width != 0) mDoGph_gInf_c::setWindowSize(preLaunchUIWindowSize); + using clock = std::chrono::steady_clock; + constexpr double kSimStepSeconds = 1.0 / 30.0; + auto previous_time = clock::now(); + double accumulator = kSimStepSeconds; + do { // 1. Update Window Events const AuroraEvent* event = aurora_update(); @@ -219,27 +226,42 @@ void main01(void) { eventsDone:; - static u32 frame = 0; - frame++; - - // Game Inputs - mDoCPd_c::read(); + auto current_time = clock::now(); + double frame_seconds = std::chrono::duration(current_time - previous_time).count(); + previous_time = current_time; + accumulator += frame_seconds; VIWaitForRetrace(); -#if TARGET_PC dusk::lastFrameAuroraStats = *aurora_get_stats(); if (!aurora_begin_frame()) { DuskLog.debug("aurora_begin_frame returned false, skipping draw this frame"); continue; } -#endif - // EXECUTE GAME LOGIC & RENDER - // This calls mDoGph_Painter -> JFWDisplay -> GX Functions - fapGm_Execute(); + if (dusk::getSettings().game.enableFrameInterpolation) { + while (accumulator >= kSimStepSeconds) { + mDoCPd_c::read(); + fapGm_Execute(); + mDoAud_Execute(); + accumulator -= kSimStepSeconds; + } - mDoAud_Execute(); + float interp_alpha = static_cast(accumulator / kSimStepSeconds); + dusk::frame_interp::interpolate(interp_alpha); + cAPIGph_Painter(); + } else { + accumulator = 0.0; + + // Game Inputs + mDoCPd_c::read(); + + // EXECUTE GAME LOGIC & RENDER + // This calls mDoGph_Painter -> JFWDisplay -> GX Functions + fapGm_Execute(); + + mDoAud_Execute(); + } aurora_end_frame();