diff --git a/include/d/d_menu_collect.h b/include/d/d_menu_collect.h index 587fd5c103..55d5373c71 100644 --- a/include/d/d_menu_collect.h +++ b/include/d/d_menu_collect.h @@ -50,7 +50,11 @@ public: void changeShield(); void changeClothe(); void setArrowMaxNum(u8); +#if TARGET_PC + void setWalletSizeNum(u16); +#else void setWalletMaxNum(u16); +#endif void setSmellType(); void setHeartPiece(); void setPohMaxNum(u8); diff --git a/include/dusk/audio/DuskAudioSystem.h b/include/dusk/audio/DuskAudioSystem.h index 1b31db6e7c..724776f5f1 100644 --- a/include/dusk/audio/DuskAudioSystem.h +++ b/include/dusk/audio/DuskAudioSystem.h @@ -8,6 +8,8 @@ namespace dusk::audio { */ void Initialize(); + void SetEnableReverb(bool value); + void SetMasterVolume(f32 value); u32 GetResetCount(int channelIdx); diff --git a/include/dusk/dusk.h b/include/dusk/dusk.h index 7fc2a23070..b751990d9c 100644 --- a/include/dusk/dusk.h +++ b/include/dusk/dusk.h @@ -3,7 +3,22 @@ #include +#include "aurora/gfx.h" + extern AuroraInfo auroraInfo; extern const char* configPath; +namespace dusk { + extern AuroraStats lastFrameAuroraStats; + extern float frameUsagePct; +} + +constexpr u32 defaultWindowWidth = 608; +constexpr u32 defaultWindowHeight = 448; + +constexpr u32 defaultAspectRatioW = 19; +constexpr u32 defaultAspectRatioH = 14; + +static_assert(defaultWindowWidth / defaultAspectRatioW == defaultWindowHeight / defaultAspectRatioH); + #endif // DUSK_DUSK_H diff --git a/include/dusk/settings.h b/include/dusk/settings.h index 2436be70f5..a939191f1b 100644 --- a/include/dusk/settings.h +++ b/include/dusk/settings.h @@ -16,6 +16,7 @@ struct UserSettings { // Video ConfigVar enableFullscreen; ConfigVar enableVsync; + ConfigVar lockAspectRatio; } video; struct { @@ -43,6 +44,7 @@ struct UserSettings { ConfigVar fastClimbing; ConfigVar noMissClimbing; ConfigVar fastTears; + ConfigVar instantSaves; // Preferences ConfigVar enableMirrorMode; diff --git a/include/m_Do/m_Do_machine.h b/include/m_Do/m_Do_machine.h index 53c47c07a7..eeea7e77ce 100644 --- a/include/m_Do/m_Do_machine.h +++ b/include/m_Do/m_Do_machine.h @@ -15,6 +15,9 @@ void my_SysPrintHeap(char const*, void*, u32); void mDoMch_HeapCheckAll(); void mDoMch_HeapFreeFillAll(); int mDoMch_Create(); +#if TARGET_PC +void mDoMch_Destroy(); +#endif extern GXRenderModeObj g_ntscZeldaProg; diff --git a/libs/JSystem/include/JSystem/JFramework/JFWSystem.h b/libs/JSystem/include/JSystem/JFramework/JFWSystem.h index bb4dfa01cd..288b5f03d8 100644 --- a/libs/JSystem/include/JSystem/JFramework/JFWSystem.h +++ b/libs/JSystem/include/JSystem/JFramework/JFWSystem.h @@ -33,6 +33,9 @@ struct JFWSystem { static void firstInit(); static void init(); +#if TARGET_PC + static void shutdown(); +#endif static JUTConsole* getSystemConsole() { return systemConsole; } static JKRExpHeap* getSystemHeap() { return systemHeap; } diff --git a/libs/JSystem/include/JSystem/JKernel/JKRAram.h b/libs/JSystem/include/JSystem/JKernel/JKRAram.h index 9c8ebd88d1..dd3bf54f9c 100644 --- a/libs/JSystem/include/JSystem/JKernel/JKRAram.h +++ b/libs/JSystem/include/JSystem/JKernel/JKRAram.h @@ -39,6 +39,9 @@ public: public: static JKRAram* create(u32, u32, s32, s32, s32); +#if TARGET_PC + static void destroy(); +#endif static void checkOkAddress(u8*, u32, JKRAramBlock*, u32); static void changeGroupIdIfNeed(u8*, int); static JKRAramBlock* mainRamToAram(u8*, u32, u32, JKRExpandSwitch, u32, JKRHeap*, int, u32*); diff --git a/libs/JSystem/include/JSystem/JKernel/JKRDecomp.h b/libs/JSystem/include/JSystem/JKernel/JKRDecomp.h index 9131ecfbaa..2e7ce0ef1c 100644 --- a/libs/JSystem/include/JSystem/JKernel/JKRDecomp.h +++ b/libs/JSystem/include/JSystem/JKernel/JKRDecomp.h @@ -48,6 +48,9 @@ private: public: static JKRDecomp* create(s32); +#if TARGET_PC + static void destroy(); +#endif static JKRDecompCommand* prepareCommand(u8*, u8*, u32, u32, JKRDecompCommand::AsyncCallback); static void sendCommand(JKRDecompCommand*); static bool sync(JKRDecompCommand*, int); diff --git a/libs/JSystem/include/JSystem/JUtility/JUTResFont.h b/libs/JSystem/include/JSystem/JUtility/JUTResFont.h index 09d3303da0..8709822d7b 100644 --- a/libs/JSystem/include/JSystem/JUtility/JUTResFont.h +++ b/libs/JSystem/include/JSystem/JUtility/JUTResFont.h @@ -18,6 +18,10 @@ struct BlockHeader { BE(u32) size; }; +#if TARGET_PC +struct GlyphTextures; +#endif + /** * @ingroup jsystem-jutility * @@ -64,7 +68,11 @@ public: // some types uncertain, may need to be fixed /* 0x1C */ int mWidth; /* 0x20 */ int mHeight; +#if TARGET_PC + GlyphTextures* mGlyphTextures; +#else /* 0x24 */ TGXTexObj mTexObj; +#endif /* 0x44 */ int mTexPageIdx; /* 0x48 */ const ResFONT* mResFont; /* 0x4C */ ResFONT::INF1* mInf1Ptr; diff --git a/libs/JSystem/src/JFramework/JFWDisplay.cpp b/libs/JSystem/src/JFramework/JFWDisplay.cpp index 6a257f5244..26788f9f38 100644 --- a/libs/JSystem/src/JFramework/JFWDisplay.cpp +++ b/libs/JSystem/src/JFramework/JFWDisplay.cpp @@ -13,6 +13,7 @@ #include "JSystem/JUtility/JUTDbPrint.h" #include "JSystem/JUtility/JUTProcBar.h" #include "aurora/aurora.h" +#include "dusk/dusk.h" #include "dusk/gx_helper.h" #include "dusk/logging.h" #include "dusk/settings.h" @@ -358,10 +359,12 @@ static void waitForTick(u32 p1, u16 p2) { { static OSTime nextTick = OSGetTime(); OSTime time = OSGetTime(); + OSTime waitTime = (nextTick > time) ? (nextTick - time) : 0; while (time < nextTick) { JFWDisplay::getManager()->threadSleep((nextTick - time)); time = OSGetTime(); } + dusk::frameUsagePct = 100.0f * (1.0f - (float)waitTime / (float)p1); nextTick = time + p1; } else { static u32 nextCount = VIGetRetraceCount(); @@ -374,6 +377,7 @@ static void waitForTick(u32 p1, u16 p2) { msg = 0; } } while (((intptr_t)msg - (intptr_t)nextCount) < 0); + dusk::frameUsagePct = 100.0f; nextCount = (intptr_t)msg + uVar1; } } diff --git a/libs/JSystem/src/JFramework/JFWSystem.cpp b/libs/JSystem/src/JFramework/JFWSystem.cpp index b4effd547a..d6322f34c9 100644 --- a/libs/JSystem/src/JFramework/JFWSystem.cpp +++ b/libs/JSystem/src/JFramework/JFWSystem.cpp @@ -116,3 +116,9 @@ void JFWSystem::init() { void* buffer = systemHeap->alloc(CSetUpParam::exConsoleBufferSize, 4); JUTException::createConsole(buffer, CSetUpParam::exConsoleBufferSize); } + +#if TARGET_PC +void JFWSystem::shutdown() { + JKRAram::destroy(); +} +#endif diff --git a/libs/JSystem/src/JKernel/JKRAram.cpp b/libs/JSystem/src/JKernel/JKRAram.cpp index 32afe1cef4..2770328afa 100644 --- a/libs/JSystem/src/JKernel/JKRAram.cpp +++ b/libs/JSystem/src/JKernel/JKRAram.cpp @@ -42,6 +42,12 @@ JKRAram* JKRAram::create(u32 aram_audio_buffer_size, u32 aram_audio_graph_size, return sAramObject; } +#if TARGET_PC +void JKRAram::destroy() { + JKRDecomp::destroy(); +} +#endif + OSMessage JKRAram::sMessageBuffer[4] = { NULL, NULL, diff --git a/libs/JSystem/src/JKernel/JKRDecomp.cpp b/libs/JSystem/src/JKernel/JKRDecomp.cpp index 776823531d..a97edea0a3 100644 --- a/libs/JSystem/src/JKernel/JKRDecomp.cpp +++ b/libs/JSystem/src/JKernel/JKRDecomp.cpp @@ -20,6 +20,15 @@ JKRDecomp* JKRDecomp::create(s32 priority) { return sDecompObject; } +#if TARGET_PC +void JKRDecomp::destroy() { + if (sDecompObject) { + OSSendMessage(&sMessageQueue, nullptr, OS_MESSAGE_NOBLOCK); + OSJoinThread(sDecompObject->getThreadRecord(), nullptr); + } +} +#endif + OSMessage JKRDecomp::sMessageBuffer[8] = {0}; OSMessageQueue JKRDecomp::sMessageQueue = {0}; @@ -34,15 +43,15 @@ void* JKRDecomp::run() { OSInitMessageQueue(&sMessageQueue, sMessageBuffer, 8); for (;;) { OSMessage message; -#ifdef TARGET_PC - if (!OSReceiveMessage(&sMessageQueue, &message, OS_MESSAGE_BLOCK)) { - break; - } -#else OSReceiveMessage(&sMessageQueue, &message, OS_MESSAGE_BLOCK); -#endif JKRDecompCommand* command = (JKRDecompCommand*)message; +#if TARGET_PC + if (!command) { + break; + } +#endif + decode(command->mSrcBuffer, command->mDstBuffer, command->mSrcLength, command->mDstLength); if (command->field_0x20 != 0) { diff --git a/libs/JSystem/src/JUtility/JUTResFont.cpp b/libs/JSystem/src/JUtility/JUTResFont.cpp index e6410defdc..10443d6404 100644 --- a/libs/JSystem/src/JUtility/JUTResFont.cpp +++ b/libs/JSystem/src/JUtility/JUTResFont.cpp @@ -6,19 +6,39 @@ #include "JSystem/JUtility/JUTAssert.h" #include "JSystem/JUtility/JUTConsole.h" #include +#include "absl/container/node_hash_map.h" + +#if TARGET_PC +struct GlyphTextures { + absl::node_hash_map textures; +}; +#endif JUTResFont::JUTResFont() { +#if TARGET_PC + mGlyphTextures = new GlyphTextures(); +#endif initialize_state(); JUTFont::initialize_state(); } JUTResFont::JUTResFont(const ResFONT* pFont, JKRHeap* pHeap) { +#if TARGET_PC + mGlyphTextures = new GlyphTextures(); +#endif initialize_state(); JUTFont::initialize_state(); initiate(pFont, pHeap); } JUTResFont::~JUTResFont() { +#if TARGET_PC + for (auto& pair : mGlyphTextures->textures) { + pair.second.reset(); + } + delete mGlyphTextures; +#endif + if (mValid) { delete_and_initialize(); JUTFont::initialize_state(); @@ -414,12 +434,30 @@ void JUTResFont::loadImage(int code, GXTexMapID id){ mWidth = cellCol * cellW; mHeight = cellRow * cellH; +#if TARGET_PC + const auto found = mGlyphTextures->textures.find(code); + GXTexObj* texObj; + if (found == mGlyphTextures->textures.end()) { + texObj = &mGlyphTextures->textures[code]; + void* pImg = &mpGlyphBlocks[i]->data[pageIdx * mpGlyphBlocks[i]->textureSize]; + GXInitTexObj(texObj, pImg, mpGlyphBlocks[i]->textureWidth, + mpGlyphBlocks[i]->textureHeight, (GXTexFmt)(u16)mpGlyphBlocks[i]->textureFormat, + GX_CLAMP, GX_CLAMP, 0); + + GXInitTexObjLOD(texObj, GX_LINEAR, GX_LINEAR, 0.0f, 0.0f, 0.0f, 0U, 0U, GX_ANISO_1); + } else { + texObj = &found->second; + } + + GXLoadTexObj(texObj, id); + + // Gets used by some other code. + mTexPageIdx = pageIdx; + field_0x66 = i; +#else if (pageIdx != mTexPageIdx || i != field_0x66) { void* pImg = &mpGlyphBlocks[i]->data[pageIdx * mpGlyphBlocks[i]->textureSize]; -#ifdef TARGET_PC - mTexObj.reset(); -#endif GXInitTexObj(&mTexObj, pImg, mpGlyphBlocks[i]->textureWidth, mpGlyphBlocks[i]->textureHeight, (GXTexFmt)(u16)mpGlyphBlocks[i]->textureFormat, GX_CLAMP, GX_CLAMP, 0); @@ -430,6 +468,7 @@ void JUTResFont::loadImage(int code, GXTexMapID id){ } GXLoadTexObj(&mTexObj, id); +#endif } } diff --git a/src/SSystem/SComponent/c_cc_d.cpp b/src/SSystem/SComponent/c_cc_d.cpp index 569d64e5e7..76c4920b8f 100644 --- a/src/SSystem/SComponent/c_cc_d.cpp +++ b/src/SSystem/SComponent/c_cc_d.cpp @@ -6,6 +6,7 @@ #include "SSystem/SComponent/c_cc_d.h" #include "JSystem/JUtility/JUTAssert.h" #include +#include "dusk/logging.h" #define CHECK_FLOAT_RANGE(line, x) JUT_ASSERT(line, -1.0e32f < x && x < 1.0e32f); @@ -65,9 +66,23 @@ void cCcD_DivideArea::CalcDivideInfo(cCcD_DivideInfo* pDivideInfo, const cM3dGAa if (!mXDiffIsZero) { s32 var1 = mInvScaledXDiff * (aab.GetMinP()->x - GetMinP()->x); s32 var3 = mInvScaledXDiff * (aab.GetMaxP()->x - GetMinP()->x); +#if TARGET_PC + // yDiv seems to be the problematic part here, but I clamped xDiv and zDiv too just in case. + // Unlike var1, var3 being greater than 31 isn't logged because it is every frame and the original code accounted for that. + // This goes for yDiv and zDiv as well. + if (var1 < 0 || var1 > 31) { + DuskLog.warn("CalcDivideInfo: xDiv var1 out of range: {}", var1); + } + if (var3 < 0) { + DuskLog.warn("CalcDivideInfo: xDiv var3 out of range: {}", var3); + } + var1 = var1 < 0 ? 0 : var1 > 31 ? 31 : var1; + var3 = var3 < 0 ? 0 : var3 > 31 ? 31 : var3; +#else if (31 < var3) { var3 = 31; } +#endif xDivInfo = l_base[var3]; if (0 < var1) { @@ -81,9 +96,22 @@ void cCcD_DivideArea::CalcDivideInfo(cCcD_DivideInfo* pDivideInfo, const cM3dGAa if (!mYDiffIsZero) { s32 var1 = mInvScaledYDiff * (aab.GetMinP()->y - GetMinP()->y); s32 var3 = mInvScaledYDiff * (aab.GetMaxP()->y - GetMinP()->y); +#if TARGET_PC + // When the Epona taming minigame starts as Wolf Link, yDiv var1 can be 32. + // When the Epona taming minigame ends, yDiv var1 and var3 can both be INT_MIN. + if (var1 < 0 || var1 > 31) { + DuskLog.warn("CalcDivideInfo: yDiv var1 out of range: {}", var1); + } + if (var3 < 0) { + DuskLog.warn("CalcDivideInfo: yDiv var3 out of range: {}", var3); + } + var1 = var1 < 0 ? 0 : var1 > 31 ? 31 : var1; + var3 = var3 < 0 ? 0 : var3 > 31 ? 31 : var3; +#else if (31 < var3) { var3 = 31; } +#endif yDivInfo = l_base[var3]; if (0 < var1) { @@ -97,9 +125,20 @@ void cCcD_DivideArea::CalcDivideInfo(cCcD_DivideInfo* pDivideInfo, const cM3dGAa if (!mZDiffIsZero) { s32 var1 = mInvScaledZDiff * (aab.GetMinP()->z - GetMinP()->z); s32 var3 = mInvScaledZDiff * (aab.GetMaxP()->z - GetMinP()->z); +#if TARGET_PC + if (var1 < 0 || var1 > 31) { + DuskLog.warn("CalcDivideInfo: zDiv var1 out of range: {}", var1); + } + if (var3 < 0) { + DuskLog.warn("CalcDivideInfo: zDiv var3 out of range: {}", var3); + } + var1 = var1 < 0 ? 0 : var1 > 31 ? 31 : var1; + var3 = var3 < 0 ? 0 : var3 > 31 ? 31 : var3; +#else if (31 < var3) { var3 = 31; } +#endif zDivInfo = l_base[var3]; if (0 < var1) { diff --git a/src/SSystem/SComponent/c_math.cpp b/src/SSystem/SComponent/c_math.cpp index 2cc54fece9..7baeea689c 100644 --- a/src/SSystem/SComponent/c_math.cpp +++ b/src/SSystem/SComponent/c_math.cpp @@ -7,6 +7,7 @@ #include "SSystem/SComponent/c_m3d.h" #include #include +#include "dusk/logging.h" s16 cM_rad2s(f32 rad) { s32 s = (std::fmod(rad, 2 * M_PI) * (0x8000 / M_PI)); @@ -109,6 +110,13 @@ static u16 atntable[1025] = { u16 U_GetAtanTable(f32 f0, f32 f1) { int idx = f0 / f1 * 0x400; +#if TARGET_PC + // When Wolf Link is playing a Human Link animation, idx can be INT_MIN every frame + if (idx < 0 || idx > 0x400) { + DuskLog.warn("U_GetAtanTable: idx out of range: {}", idx); + } + idx = idx < 0 ? 0 : idx > 0x400 ? 0x400 : idx; +#endif return atntable[idx]; } diff --git a/src/d/actor/d_a_movie_player.cpp b/src/d/actor/d_a_movie_player.cpp index 4573684b2f..c3074ac877 100644 --- a/src/d/actor/d_a_movie_player.cpp +++ b/src/d/actor/d_a_movie_player.cpp @@ -4473,7 +4473,9 @@ int daMP_c::daMP_c_Get_arg_movieNo() { int daMP_c::daMP_c_Init() { JUT_ASSERT(9469, m_myObj == NULL); +#if !TARGET_PC // We don't properly simulate retrace interval so this doesn't work. mDoGph_gInf_c::setFrameRate(1); +#endif daMP_Fail_alloc = FALSE; int demoNo = daMP_c_Get_arg_demoNo(); diff --git a/src/d/d_file_select.cpp b/src/d/d_file_select.cpp index f63403d886..8d4f98187b 100644 --- a/src/d/d_file_select.cpp +++ b/src/d/d_file_select.cpp @@ -1193,7 +1193,13 @@ void dFile_select_c::menuSelect() { // Handles copy / start / delete actions depending on which menu is selected from menuSelect void dFile_select_c::menuSelectStart() { - mDoAud_seStart(Z2SE_SY_CURSOR_OK, NULL, 0, 0); + #if TARGET_PC + if (!dusk::getSettings().game.hideTvSettingsScreen || mSelectMenuNum != 1) { + mDoAud_seStart(Z2SE_SY_CURSOR_OK, NULL, 0, 0); + } + #else + mDoAud_seStart(Z2SE_SY_CURSOR_OK, NULL, 0, 0); + #endif if (mSelectMenuNum == 1) { dComIfGs_setCardToMemory((u8*)mSaveData, mSelectNum); diff --git a/src/d/d_gameover.cpp b/src/d/d_gameover.cpp index 693bcf371e..baec970a32 100644 --- a/src/d/d_gameover.cpp +++ b/src/d/d_gameover.cpp @@ -432,7 +432,14 @@ void dDlst_GameOverScrnDraw_c::draw() { img_white.a = 255; mpBackImg->setBlackWhite(img_black, img_white); + + #if TARGET_PC + mpBackImg->draw(mDoGph_gInf_c::getMinXF(), mDoGph_gInf_c::getMinYF(), + mDoGph_gInf_c::getWidthF(), mDoGph_gInf_c::getHeightF(), false, false, + false); + #else mpBackImg->draw(0.0f, 0.0f, FB_WIDTH, FB_HEIGHT, false, false, false); + #endif } else { JUtility::TColor img_black; JUtility::TColor img_white; @@ -447,7 +454,13 @@ void dDlst_GameOverScrnDraw_c::draw() { img_white.b = l_HIO.mWhite.b; img_white.a = l_HIO.mWhite.a; + #if TARGET_PC + mpBackImg->draw(mDoGph_gInf_c::getMinXF(), mDoGph_gInf_c::getMinYF(), + mDoGph_gInf_c::getWidthF(), mDoGph_gInf_c::getHeightF(), false, false, + false); + #else mpBackImg->draw(0.0f, 0.0f, FB_WIDTH, FB_HEIGHT, false, false, false); + #endif static f32 offset[8] = {-138.0f, -96.0f, -56.0f, -18.0f, 42.0f, 75.0f, 110.0f, 143.0f}; diff --git a/src/d/d_menu_collect.cpp b/src/d/d_menu_collect.cpp index ed5586e866..72a390a0cc 100644 --- a/src/d/d_menu_collect.cpp +++ b/src/d/d_menu_collect.cpp @@ -590,6 +590,15 @@ void dMenu_Collect2D_c::screenSet() { field_0x184[4][2] = 0x196; field_0x184[5][2] = 0x195; field_0x184[6][2] = 0; +#if TARGET_PC // Since we allow changing wallet sizes, do something more robust. + if (dComIfGs_getWalletSize() == WALLET) { + field_0x184[0][3] = 0x199; + } else if (dComIfGs_getWalletSize() == BIG_WALLET) { + field_0x184[0][3] = 0x19a; + } else { + field_0x184[0][3] = 0x19b; + } +#else if (dComIfGs_getRupeeMax() == WALLET_MAX) { field_0x184[0][3] = 0x199; } else if (dComIfGs_getRupeeMax() == BIG_WALLET_MAX) { @@ -597,6 +606,7 @@ void dMenu_Collect2D_c::screenSet() { } else { field_0x184[0][3] = 0x19b; } +#endif if (dComIfGs_getArrowMax() == QUIVER_MAX) { field_0x184[1][3] = 0x1b9; } else if (dComIfGs_getArrowMax() == BIG_QUIVER_MAX) { @@ -757,7 +767,11 @@ void dMenu_Collect2D_c::screenSet() { setItemNameString(mCursorX, mCursorY); cursorPosSet(); setArrowMaxNum(dComIfGs_getArrowMax()); +#if TARGET_PC + setWalletSizeNum(dComIfGs_getWalletSize()); +#else setWalletMaxNum(dComIfGs_getRupeeMax()); +#endif setSmellType(); setHeartPiece(); setPohMaxNum(dComIfGs_getPohSpiritNum()); @@ -1242,6 +1256,27 @@ void dMenu_Collect2D_c::setArrowMaxNum(u8 param_0) { } } +#if TARGET_PC +void dMenu_Collect2D_c::setWalletSizeNum(u16 i_walletSize) { + switch (i_walletSize) { + case WALLET: + mpScreen->search(MULTI_CHAR('item_1_0'))->show(); + mpScreen->search(MULTI_CHAR('item_1_1'))->hide(); + mpScreen->search(MULTI_CHAR('item_1_2'))->hide(); + break; + case BIG_WALLET: + mpScreen->search(MULTI_CHAR('item_1_0'))->hide(); + mpScreen->search(MULTI_CHAR('item_1_1'))->show(); + mpScreen->search(MULTI_CHAR('item_1_2'))->hide(); + break; + case GIANT_WALLET: + mpScreen->search(MULTI_CHAR('item_1_0'))->hide(); + mpScreen->search(MULTI_CHAR('item_1_1'))->hide(); + mpScreen->search(MULTI_CHAR('item_1_2'))->show(); + break; + } +} +#else void dMenu_Collect2D_c::setWalletMaxNum(u16 i_walletSize) { switch (i_walletSize) { case 300: @@ -1261,6 +1296,7 @@ void dMenu_Collect2D_c::setWalletMaxNum(u16 i_walletSize) { break; } } +#endif void dMenu_Collect2D_c::setSmellType() { static const u64 smell_tag[5] = { diff --git a/src/d/d_menu_save.cpp b/src/d/d_menu_save.cpp index eb7b6a35b8..618677866b 100644 --- a/src/d/d_menu_save.cpp +++ b/src/d/d_menu_save.cpp @@ -18,6 +18,7 @@ #include "m_Do/m_Do_controller_pad.h" #include "m_Do/m_Do_graphic.h" #include "d/d_msg_scrn_explain.h" +#include "dusk/settings.h" #include "JSystem/J2DGraph/J2DAnmLoader.h" #include "f_op/f_op_msg_mng.h" @@ -56,11 +57,7 @@ static dMs_HIO_c g_msHIO; dMs_HIO_c::dMs_HIO_c() { mDisplayWaitFrames = 15; - #if TARGET_PC - mCardWaitFrames = 0; - #else mCardWaitFrames = 90; - #endif mEffectDispFrames = 5; mCharSwitchFrames = 5; mSelectIcon = 5; @@ -1186,7 +1183,7 @@ void dMenu_save_c::cardFormatYesSel2Disp() { bool moveAnm = yesnoMenuMoveAnm(); if (txtChangeAnm == true && moveAnm == true) { - mWaitTimer = g_msHIO.mCardWaitFrames; + mWaitTimer = dusk::getSettings().game.instantSaves ? 0 : g_msHIO.mCardWaitFrames; g_mDoMemCd_control.command_format(); mMenuProc = PROC_MEMCARD_FORMAT; } @@ -1258,7 +1255,7 @@ void dMenu_save_c::makeGameFileDisp() { bool ketteiDispAnm = ketteiTxtDispAnm(); if (txtChangeAnm == true && moveAnm == true && ketteiDispAnm == true) { - mWaitTimer = g_msHIO.mCardWaitFrames; + mWaitTimer = dusk::getSettings().game.instantSaves ? 0 : g_msHIO.mCardWaitFrames; setInitSaveData(); dataSave(); mMenuProc = PROC_MEMCARD_MAKE_GAME_FILE; @@ -1952,7 +1949,7 @@ void dMenu_save_c::saveMoveDisp() { if (headerTxtChanged == true && yesnoAnmComplete == true && ketteiAnmComplete == true && modoruAnmComplete == 1 && check == 1) { - mWaitTimer = g_msHIO.mCardWaitFrames; + mWaitTimer = dusk::getSettings().game.instantSaves ? 0 : g_msHIO.mCardWaitFrames; dataWrite(); mMenuProc = PROC_MEMCARD_DATA_SAVE_WAIT; } @@ -1970,7 +1967,7 @@ void dMenu_save_c::saveMoveDisp2() { if (headerTxtChanged == true && dataMoveAnm == true && wakuAnmComplete == true && ketteiAnmComplete == true && modoruAnmComplete == 1 && check == 1) { - mWaitTimer = g_msHIO.mCardWaitFrames; + mWaitTimer = dusk::getSettings().game.instantSaves ? 0 : g_msHIO.mCardWaitFrames; dataWrite(); mMenuProc = PROC_MEMCARD_DATA_SAVE_WAIT; } diff --git a/src/d/d_meter_HIO.cpp b/src/d/d_meter_HIO.cpp index 94b1c79420..dbfe11cf53 100644 --- a/src/d/d_meter_HIO.cpp +++ b/src/d/d_meter_HIO.cpp @@ -2292,6 +2292,7 @@ void dMeter_drawHIO_c::updateOnWide() { g_drawHIO = {}; // this might be a bad idea g_drawHIO.mMainHUDButtonsPosX = mDoGph_gInf_c::ScaleHUDXRight(g_drawHIO.mMainHUDButtonsPosX); + g_drawHIO.mRingHUDButtonsPosX = mDoGph_gInf_c::ScaleHUDXRight(g_drawHIO.mRingHUDButtonsPosX); g_drawHIO.mRupeeKeyPosX = mDoGph_gInf_c::ScaleHUDXRight(g_drawHIO.mRupeeKeyPosX); g_drawHIO.mButtonCrossOFFPosX = mDoGph_gInf_c::ScaleHUDXLeft(g_drawHIO.mButtonCrossOFFPosX); g_drawHIO.mButtonCrossONPosX = mDoGph_gInf_c::ScaleHUDXLeft(g_drawHIO.mButtonCrossONPosX); diff --git a/src/d/d_meter_map.cpp b/src/d/d_meter_map.cpp index 6b78e2242b..b5900a4be5 100644 --- a/src/d/d_meter_map.cpp +++ b/src/d/d_meter_map.cpp @@ -596,7 +596,8 @@ void dMeterMap_c::_draw() { #if TARGET_PC // Optimization: don't draw map if it's off-screen/invisible. // Especially useful in debug builds on Hyrule field etc., it's slow! - if ((!mMapIsInside && mSlidePositionOffset == getDispPosOutSide_OffsetX()) || mMapAlpha == 0) { + // That +3 is an arbitrary bias to avoid rounding issues causing this to fail. + if ((!mMapIsInside && mSlidePositionOffset <= getDispPosOutSide_OffsetX() + 3) || mMapAlpha == 0) { return; } #endif diff --git a/src/dusk/audio/DuskAudioSystem.cpp b/src/dusk/audio/DuskAudioSystem.cpp index c7313749c2..7b464e4f0e 100644 --- a/src/dusk/audio/DuskAudioSystem.cpp +++ b/src/dusk/audio/DuskAudioSystem.cpp @@ -76,6 +76,12 @@ void dusk::audio::SetMasterVolume(const f32 value) { MasterVolume = value; } +void dusk::audio::SetEnableReverb(const bool value) { + JASCriticalSection section; + + EnableReverb = value; +} + void SDLCALL GetNewAudio( void*, SDL_AudioStream*, diff --git a/src/dusk/imgui/ImGuiConfig.hpp b/src/dusk/imgui/ImGuiConfig.hpp index fc5aa34bbd..e3e80b9920 100644 --- a/src/dusk/imgui/ImGuiConfig.hpp +++ b/src/dusk/imgui/ImGuiConfig.hpp @@ -5,36 +5,48 @@ #include "imgui.h" namespace dusk::config { - inline void ImGuiCheckbox(const char* title, ConfigVar& var) { + inline bool ImGuiCheckbox(const char* title, ConfigVar& var) { bool copy = var.getValue(); if (ImGui::Checkbox(title, ©)) { var.setValue(copy); Save(); + return true; } + + return false; } - static void ImGuiSliderFloat(const char* label, ConfigVar& var, float v_min, float v_max, const char* format = "%.3f", ImGuiSliderFlags flags = 0) { + static bool ImGuiSliderFloat(const char* label, ConfigVar& var, float v_min, float v_max, const char* format = "%.3f", ImGuiSliderFlags flags = 0) { float val = var; if (ImGui::SliderFloat(label, &val, v_min, v_max, format, flags)) { var.setValue(val); Save(); + return true; } + + return false; } - static void ImGuiSliderInt(const char* label, ConfigVar& var, int v_min, int v_max, const char* format = "%d", ImGuiSliderFlags flags = 0) { + static bool ImGuiSliderInt(const char* label, ConfigVar& var, int v_min, int v_max, const char* format = "%d", ImGuiSliderFlags flags = 0) { int val = var; if (ImGui::SliderInt(label, &val, v_min, v_max, format, flags)) { var.setValue(val); Save(); + return true; } + + return false; } - static void ImGuiMenuItem(const char* label, const char* shortcut, ConfigVar& p_selected, bool enabled = true) { + static bool ImGuiMenuItem(const char* label, const char* shortcut, ConfigVar& p_selected, bool enabled = true) { bool copy = p_selected.getValue(); if (ImGui::MenuItem(label, shortcut, ©, enabled)) { p_selected.setValue(copy); Save(); + return true; } + + return false; } } diff --git a/src/dusk/imgui/ImGuiConsole.cpp b/src/dusk/imgui/ImGuiConsole.cpp index fe5d3e7c9a..93b5b4a787 100644 --- a/src/dusk/imgui/ImGuiConsole.cpp +++ b/src/dusk/imgui/ImGuiConsole.cpp @@ -7,14 +7,16 @@ #include "fmt/format.h" #include "imgui.h" -#include "aurora/gfx.h" #include #include "ImGuiConsole.hpp" #include "JSystem/JUtility/JUTGamePad.h" +#include "SDL3/SDL_mouse.h" #include "dusk/config.hpp" #include "dusk/settings.h" +#include "dusk/audio/DuskAudioSystem.h" +#include "dusk/dusk.h" #if _WIN32 #define NOMINMAX @@ -178,13 +180,31 @@ namespace dusk { ImGuiConsole::ImGuiConsole() {} + void ImGuiConsole::InitSettings() { + bool lockAspect = getSettings().video.lockAspectRatio; + if (lockAspect) { + VILockAspectRatio(defaultAspectRatioW, defaultAspectRatioH); + } else { + VIUnlockAspectRatio(); + } + + dusk::audio::SetMasterVolume(getSettings().audio.masterVolume / 100.0f); + dusk::audio::SetEnableReverb(getSettings().audio.enableReverb); + } + + void ImGuiConsole::UpdateSettings() { + getTransientSettings().skipFrameRateLimit = getSettings().game.enableTurboKeybind && ImGui::IsKeyDown(ImGuiKey_Tab); + } + void ImGuiConsole::PreDraw() { if (!m_isLaunchInitialized) { + InitSettings(); + m_toasts.emplace_back("Press F1 to toggle menu"s, 5.f); m_isLaunchInitialized = true; } - getTransientSettings().skipFrameRateLimit = getSettings().game.enableTurboKeybind && ImGui::IsKeyDown(ImGuiKey_Tab); + UpdateSettings(); if ((ImGui::IsKeyDown(ImGuiKey_LeftCtrl) || ImGui::IsKeyDown(ImGuiKey_RightCtrl)) && ImGui::IsKeyPressed(ImGuiKey_R)) @@ -198,9 +218,14 @@ namespace dusk { if (CheckMenuViewToggle(ImGuiKey_F1, m_isHidden)) { ShowToasts(); + ImGui::GetIO().ConfigFlags |= ImGuiConfigFlags_NoMouseCursorChange; + SDL_HideCursor(); return; } + ImGui::GetIO().ConfigFlags &= ~ImGuiConfigFlags_NoMouseCursorChange; + // Imgui will re-show cursor. + // TODO: we need to be able to render the menu bar & any overlays separately // The code currently ties them all together, so hiding the menu hides all windows diff --git a/src/dusk/imgui/ImGuiConsole.hpp b/src/dusk/imgui/ImGuiConsole.hpp index 27032ed39b..3d13e2ed6d 100644 --- a/src/dusk/imgui/ImGuiConsole.hpp +++ b/src/dusk/imgui/ImGuiConsole.hpp @@ -14,6 +14,8 @@ namespace dusk { class ImGuiConsole { public: ImGuiConsole(); + void InitSettings(); + void UpdateSettings(); void PreDraw(); void PostDraw(); diff --git a/src/dusk/imgui/ImGuiMenuEnhancements.cpp b/src/dusk/imgui/ImGuiMenuEnhancements.cpp index e9404e2476..b917fefd39 100644 --- a/src/dusk/imgui/ImGuiMenuEnhancements.cpp +++ b/src/dusk/imgui/ImGuiMenuEnhancements.cpp @@ -52,6 +52,11 @@ 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"); + } ImGui::EndMenu(); } diff --git a/src/dusk/imgui/ImGuiMenuGame.cpp b/src/dusk/imgui/ImGuiMenuGame.cpp index f200f734d4..cbe26ba81c 100644 --- a/src/dusk/imgui/ImGuiMenuGame.cpp +++ b/src/dusk/imgui/ImGuiMenuGame.cpp @@ -7,8 +7,9 @@ #include #include "JSystem/JUtility/JUTGamePad.h" -#include "dusk/audio/DuskDsp.hpp" #include "dusk/audio/DuskAudioSystem.h" +#include "dusk/audio/DuskDsp.hpp" +#include "dusk/dusk.h" #include "dusk/hotkeys.h" #include "dusk/settings.h" #include "m_Do/m_Do_controller_pad.h" @@ -38,13 +39,6 @@ namespace dusk { ToggleFullscreen(); } - bool vsync = getSettings().video.enableVsync; - if (ImGui::Checkbox("Enable Vsync", &vsync)) { - getSettings().video.enableVsync.setValue(vsync); - aurora_enable_vsync(vsync); - config::Save(); - } - if (ImGui::MenuItem("Default Window Size")) { getSettings().video.enableFullscreen.setValue(false); VISetWindowFullscreen(false); @@ -52,13 +46,38 @@ namespace dusk { VICenterWindow(); } + bool vsync = getSettings().video.enableVsync; + if (ImGui::Checkbox("Enable Vsync", &vsync)) { + getSettings().video.enableVsync.setValue(vsync); + aurora_enable_vsync(vsync); + config::Save(); + } + + bool lockAspect = getSettings().video.lockAspectRatio; + if (ImGui::Checkbox("Force 4:3 Aspect Ratio", &lockAspect)) { + getSettings().video.lockAspectRatio.setValue(lockAspect); + + if (lockAspect) { + VILockAspectRatio(defaultAspectRatioW, defaultAspectRatioH); + } else { + VIUnlockAspectRatio(); + } + + config::Save(); + } + ImGui::EndMenu(); } if (ImGui::BeginMenu("Audio")) { ImGui::Text("Master Volume"); - config::ImGuiSliderInt("##masterVolume", getSettings().audio.masterVolume, 0, 100); - config::ImGuiCheckbox("Enable Reverb", getSettings().audio.enableReverb); + if (config::ImGuiSliderInt("##masterVolume", getSettings().audio.masterVolume, 0, 100)) { + dusk::audio::SetMasterVolume(getSettings().audio.masterVolume / 100.0f); + } + + if (config::ImGuiCheckbox("Enable Reverb", getSettings().audio.enableReverb)) { + dusk::audio::SetEnableReverb(getSettings().audio.enableReverb); + } /* // TODO: Implement additional settings ImGui::Text("Main Music Volume"); @@ -78,9 +97,6 @@ namespace dusk { } */ - audio::SetMasterVolume(getSettings().audio.masterVolume / 100.0f); - audio::EnableReverb = getSettings().audio.enableReverb; - ImGui::EndMenu(); } diff --git a/src/dusk/imgui/ImGuiMenuTools.cpp b/src/dusk/imgui/ImGuiMenuTools.cpp index 7478adc0e5..44e92a4082 100644 --- a/src/dusk/imgui/ImGuiMenuTools.cpp +++ b/src/dusk/imgui/ImGuiMenuTools.cpp @@ -10,6 +10,7 @@ #include "d/actor/d_a_alink.h" #include "d/actor/d_a_horse.h" #include "d/d_com_inf_game.h" +#include "dusk/dusk.h" #include "m_Do/m_Do_main.h" namespace dusk { @@ -96,6 +97,7 @@ namespace dusk { hasPrevious = true; ImGuiStringViewText(fmt::format(FMT_STRING("FPS: {:.2f}\n"), io.Framerate)); + ImGuiStringViewText(fmt::format(FMT_STRING("Frame usage: {:.1f}%\n"), frameUsagePct)); if (hasPrevious) { ImGui::Separator(); @@ -109,31 +111,31 @@ namespace dusk { } hasPrevious = true; - AuroraStats const* stats = aurora_get_stats(); + const auto& stats = lastFrameAuroraStats; ImGuiStringViewText( - fmt::format(FMT_STRING("Queued pipelines: {}\n"), stats->queuedPipelines)); + fmt::format(FMT_STRING("Queued pipelines: {}\n"), stats.queuedPipelines)); ImGuiStringViewText( - fmt::format(FMT_STRING("Done pipelines: {}\n"), stats->createdPipelines)); + fmt::format(FMT_STRING("Done pipelines: {}\n"), stats.createdPipelines)); ImGuiStringViewText( - fmt::format(FMT_STRING("Draw call count: {}\n"), stats->drawCallCount)); + fmt::format(FMT_STRING("Draw call count: {}\n"), stats.drawCallCount)); ImGuiStringViewText(fmt::format(FMT_STRING("Merged draw calls: {}\n"), - stats->mergedDrawCallCount)); + stats.mergedDrawCallCount)); ImGuiStringViewText(fmt::format(FMT_STRING("Vertex size: {}\n"), - BytesToString(stats->lastVertSize))); + BytesToString(stats.lastVertSize))); ImGuiStringViewText(fmt::format(FMT_STRING("Uniform size: {}\n"), - BytesToString(stats->lastUniformSize))); + BytesToString(stats.lastUniformSize))); ImGuiStringViewText(fmt::format(FMT_STRING("Index size: {}\n"), - BytesToString(stats->lastIndexSize))); + BytesToString(stats.lastIndexSize))); ImGuiStringViewText(fmt::format(FMT_STRING("Storage size: {}\n"), - BytesToString(stats->lastStorageSize))); + BytesToString(stats.lastStorageSize))); ImGuiStringViewText(fmt::format(FMT_STRING("Tex upload size: {}\n"), - BytesToString(stats->lastTextureUploadSize))); + BytesToString(stats.lastTextureUploadSize))); ImGuiStringViewText(fmt::format( FMT_STRING("Total: {}\n"), - BytesToString(stats->lastVertSize + stats->lastUniformSize + - stats->lastIndexSize + stats->lastStorageSize + - stats->lastTextureUploadSize))); + BytesToString(stats.lastVertSize + stats.lastUniformSize + + stats.lastIndexSize + stats.lastStorageSize + + stats.lastTextureUploadSize))); // TODO: persist to config ShowCornerContextMenu(m_debugOverlayCorner, m_cameraOverlayCorner); diff --git a/src/dusk/settings.cpp b/src/dusk/settings.cpp index d923a2b00e..1820f018a1 100644 --- a/src/dusk/settings.cpp +++ b/src/dusk/settings.cpp @@ -7,6 +7,7 @@ UserSettings g_userSettings = { .video = { .enableFullscreen {"video.enableFullscreen", false}, .enableVsync {"video.enableVsync", true}, + .lockAspectRatio {"video.lockAspectRatio", false}, }, .audio = { @@ -31,6 +32,7 @@ UserSettings g_userSettings = { .fastClimbing {"game.fastClimbing", false}, .noMissClimbing {"game.noMissClimbing", false}, .fastTears {"game.fastTears", false}, + .instantSaves {"game.instantSaves", false}, // Preferences .enableMirrorMode {"game.enableMirrorMode", false}, @@ -65,6 +67,7 @@ void registerSettings() { // Video Register(g_userSettings.video.enableFullscreen); Register(g_userSettings.video.enableVsync); + Register(g_userSettings.video.lockAspectRatio); // Audio Register(g_userSettings.audio.masterVolume); @@ -85,6 +88,7 @@ void registerSettings() { Register(g_userSettings.game.instantDeath); Register(g_userSettings.game.fastClimbing); Register(g_userSettings.game.fastTears); + Register(g_userSettings.game.instantSaves); Register(g_userSettings.game.enableMirrorMode); Register(g_userSettings.game.invertCameraXAxis); Register(g_userSettings.game.enableBloom); diff --git a/src/m_Do/m_Do_graphic.cpp b/src/m_Do/m_Do_graphic.cpp index 5c535c2eb1..5887c22495 100644 --- a/src/m_Do/m_Do_graphic.cpp +++ b/src/m_Do/m_Do_graphic.cpp @@ -940,8 +940,15 @@ static void drawDepth2(view_class* param_0, view_port_class* param_1, int param_ GX_FALSE, 0); } + #if TARGET_PC + // use full size for pc for higher quality background elements + u16 halfWidth = width; + u16 halfHeight = height; + #else u16 halfWidth = width >> 1; u16 halfHeight = height >> 1; + #endif + GXRenderModeObj* sp24 = JUTGetVideoManager()->getRenderMode(); GXSetCopyFilter(GX_FALSE, NULL, GX_TRUE, sp24->vfilter); GXSetTexCopySrc(x_orig, y_orig_pos, width, height); diff --git a/src/m_Do/m_Do_machine.cpp b/src/m_Do/m_Do_machine.cpp index fdab588957..6b186a77e6 100644 --- a/src/m_Do/m_Do_machine.cpp +++ b/src/m_Do/m_Do_machine.cpp @@ -1010,3 +1010,9 @@ int mDoMch_Create() { return 1; } + +#if TARGET_PC +void mDoMch_Destroy() { + JFWSystem::shutdown(); +} +#endif diff --git a/src/m_Do/m_Do_main.cpp b/src/m_Do/m_Do_main.cpp index b9554fa09c..6b905be1ed 100644 --- a/src/m_Do/m_Do_main.cpp +++ b/src/m_Do/m_Do_main.cpp @@ -114,6 +114,8 @@ s32 LOAD_COPYDATE(void*) { } AuroraInfo auroraInfo; +AuroraStats dusk::lastFrameAuroraStats; +float dusk::frameUsagePct = 0.0f; const char* configPath; void main01(void) { @@ -186,6 +188,7 @@ void main01(void) { 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; @@ -333,8 +336,8 @@ int game_main(int argc, char* argv[]) { config.startFullscreen = dusk::getSettings().video.enableFullscreen; config.windowPosX = -1; config.windowPosY = -1; - config.windowWidth = FB_WIDTH * 2; - config.windowHeight = FB_HEIGHT * 2; + config.windowWidth = defaultWindowWidth * 2; + config.windowHeight = defaultWindowHeight * 2; config.desiredBackend = ParseAuroraBackend(parsed_arg_options["backend"].as()); config.logCallback = &aurora_log_callback; config.logLevel = (AuroraLogLevel)parsed_arg_options["log-level"].as(); @@ -376,6 +379,8 @@ int game_main(int argc, char* argv[]) { fflush(stdout); fflush(stderr); + mDoMch_Destroy(); + // Notifies all CVs and causes threads to exit OSResetSystem(OS_RESET_SHUTDOWN, 0, 0);