From b00b7f8f1b91bb05303fdd42b4a0432a91d19ff5 Mon Sep 17 00:00:00 2001 From: Luke Street Date: Thu, 4 Jun 2026 01:21:40 -0600 Subject: [PATCH 01/12] More texture caching --- include/d/actor/d_a_alink.h | 4 + include/d/actor/d_a_player.h | 4 + include/dusk/gx_helper.h | 17 ++++ include/m_Do/m_Do_lib.h | 2 +- src/d/actor/d_a_alink_effect.inc | 18 +++- src/d/actor/d_a_mant.cpp | 30 +++++- src/d/actor/d_a_player.cpp | 16 ++++ src/d/d_drawlist.cpp | 8 +- src/d/d_kankyo_rain.cpp | 153 ++++++++++++++++++++++++++++++- src/m_Do/m_Do_lib.cpp | 5 +- 10 files changed, 240 insertions(+), 17 deletions(-) diff --git a/include/d/actor/d_a_alink.h b/include/d/actor/d_a_alink.h index 4fb8abd929..1c2963b8e8 100644 --- a/include/d/actor/d_a_alink.h +++ b/include/d/actor/d_a_alink.h @@ -88,6 +88,10 @@ public: /* 0x02C */ cXyz field_0x2c; /* 0x038 */ cXyz field_0x38[60]; /* 0x308 */ cXyz field_0x308[60]; +#if TARGET_PC + TGXTexObj mBlurTexObj; + ResTIMG* mpCachedBlurTex = nullptr; +#endif }; // Size = 0x5D8 class dAlink_bottleWaterPcallBack_c : public JPAParticleCallBack { diff --git a/include/d/actor/d_a_player.h b/include/d/actor/d_a_player.h index 698e3b03cd..1dfbec8783 100644 --- a/include/d/actor/d_a_player.h +++ b/include/d/actor/d_a_player.h @@ -50,6 +50,10 @@ public: /* 0x14 */ Mtx mProjMtx; /* 0x44 */ ResTIMG* mpImg; /* 0x48 */ u8* mpData; +#if TARGET_PC + TGXTexObj mTexObj; + ResTIMG* mpCachedImg = nullptr; +#endif }; class daPy_boomerangMove_c { diff --git a/include/dusk/gx_helper.h b/include/dusk/gx_helper.h index 8ed97e917a..bf5f3c4d3a 100644 --- a/include/dusk/gx_helper.h +++ b/include/dusk/gx_helper.h @@ -43,8 +43,25 @@ public: static_assert(sizeof(GXTexObjRAII) == sizeof(GXTexObj), "GXTexObjRAII should have the same size as GXTexObj"); typedef GXTexObjRAII TGXTexObj; + +class GXTlutObjRAII : public GXTlutObj { +public: + GXTlutObjRAII() : GXTlutObj() {} + ~GXTlutObjRAII() { GXDestroyTlutObj(this); } + + void reset() { GXDestroyTlutObj(this); } + + GXTlutObjRAII(const GXTlutObjRAII&) = delete; + GXTlutObjRAII& operator=(const GXTlutObjRAII&) = delete; + GXTlutObjRAII(GXTlutObjRAII&&) = delete; + GXTlutObjRAII& operator=(GXTlutObjRAII&&) = delete; +}; +static_assert(sizeof(GXTlutObjRAII) == sizeof(GXTlutObj), + "GXTlutObjRAII should have the same size as GXTlutObj"); +typedef GXTlutObjRAII TGXTlutObj; #else typedef GXTexObj TGXTexObj; +typedef GXTlutObj TGXTlutObj; #endif struct GXScopedDebugGroup { diff --git a/include/m_Do/m_Do_lib.h b/include/m_Do/m_Do_lib.h index 14e57ddf5a..f194a4591b 100644 --- a/include/m_Do/m_Do_lib.h +++ b/include/m_Do/m_Do_lib.h @@ -44,7 +44,7 @@ struct mDoLib_clipper { void mDoLib_project(Vec* src, Vec* dst); u32 mDoLib_setResTimgObj(ResTIMG const* res, TGXTexObj* o_texObj, u32 tlut_name, - GXTlutObj* o_tlutObj); + TGXTlutObj* o_tlutObj); void mDoLib_pos2camera(Vec* src, Vec* dst); #if PLATFORM_WII diff --git a/src/d/actor/d_a_alink_effect.inc b/src/d/actor/d_a_alink_effect.inc index a5ac1083d7..3461568f19 100644 --- a/src/d/actor/d_a_alink_effect.inc +++ b/src/d/actor/d_a_alink_effect.inc @@ -2031,9 +2031,7 @@ void daAlink_blur_c::draw() { ZoneScoped; j3dSys.reinitGX(); -#ifdef TARGET_PC - TGXTexObj texObj; -#else +#if !TARGET_PC static TGXTexObj texObj; #endif static GXColor nColor0 = {0xFF, 0xFF, 0xFF, 0x14}; @@ -2041,11 +2039,25 @@ void daAlink_blur_c::draw() { GXSetNumIndStages(0); nColor0.a = field_0x20; +#if TARGET_PC + if (mpCachedBlurTex != m_blurTex) { + mBlurTexObj.reset(); + GXInitTexObj(&mBlurTexObj, + reinterpret_cast( + reinterpret_cast(m_blurTex) + m_blurTex->imageOffset), + 16, 4, GX_TF_I4, GX_CLAMP, GX_CLAMP, GX_FALSE); + GXInitTexObjLOD( + &mBlurTexObj, GX_LINEAR, GX_LINEAR, 0.0f, 0.0f, 0.0f, GX_FALSE, GX_FALSE, GX_ANISO_1); + mpCachedBlurTex = m_blurTex; + } + GXLoadTexObj(&mBlurTexObj, GX_TEXMAP0); +#else GXInitTexObj(&texObj, (void*)((uintptr_t)m_blurTex + m_blurTex->imageOffset), 16, 4, GX_TF_I4, GX_CLAMP, GX_CLAMP, GX_FALSE); GXInitTexObjLOD(&texObj, GX_LINEAR, GX_LINEAR, 0.0f, 0.0f, 0.0f, GX_FALSE, GX_FALSE, GX_ANISO_1); GXLoadTexObj(&texObj, GX_TEXMAP0); +#endif GXSetVtxAttrFmt(GX_VTXFMT0, GX_VA_POS, GX_CLR_RGBA, GX_F32, 0); GXSetVtxAttrFmt(GX_VTXFMT0, GX_VA_TEX0, GX_CLR_RGBA, GX_RGBA4, 8); GXClearVtxDesc(); diff --git a/src/d/actor/d_a_mant.cpp b/src/d/actor/d_a_mant.cpp index b8607a2acf..a308ad4209 100644 --- a/src/d/actor/d_a_mant.cpp +++ b/src/d/actor/d_a_mant.cpp @@ -419,15 +419,36 @@ void daMant_packet_c::draw() { GXSetTevKAlphaSel(GX_TEVSTAGE0, GX_TEV_KASEL_K3_A); GXSetAlphaCompare(GX_GREATER, 0, GX_AOP_OR, GX_GREATER, 0); +#if TARGET_PC + static bool textureObjsInitialized = false; + static TGXTlutObj tlutObj; + static TGXTexObj mainTexObj; + static TGXTexObj undersideTexObj; + if (!textureObjsInitialized) { + GXInitTlutObj(&tlutObj, lut, GX_TL_RGB5A3, 0x100); + GXInitTexObjCI(&mainTexObj, image, 0x80, 0x80, GX_TF_C8, GX_CLAMP, GX_CLAMP, 0, 0); + GXInitTexObjLOD(&mainTexObj, GX_LINEAR, GX_LINEAR, 0.0, 0.0, 0.0, 0, 0, GX_ANISO_1); + GXInitTexObjCI( + &undersideTexObj, l_Egnd_mantTEX_U, 0x80, 0x80, GX_TF_C8, GX_CLAMP, GX_CLAMP, 0, 0); + GXInitTexObjLOD(&undersideTexObj, GX_LINEAR, GX_LINEAR, 0.0, 0.0, 0.0, 0, 0, GX_ANISO_1); + textureObjsInitialized = true; + } +#else GXTlutObj GStack_80; GXInitTlutObj(&GStack_80, lut, GX_TL_RGB5A3, 0x100); TGXTexObj GStack_74; GXInitTexObjCI(&GStack_74, image, 0x80, 0x80, GX_TF_C8, GX_CLAMP, GX_CLAMP, 0, 0); GXInitTexObjLOD(&GStack_74, GX_LINEAR, GX_LINEAR, 0.0, 0.0, 0.0, 0, 0, GX_ANISO_1); +#endif - GXLoadTlut(&GStack_80, 0); +#if TARGET_PC + GXLoadTlut(&tlutObj, GX_TLUT0); + GXLoadTexObj(&mainTexObj, GX_TEXMAP0); +#else + GXLoadTlut(&GStack_80, GX_TLUT0); GXLoadTexObj(&GStack_74, GX_TEXMAP0); +#endif GXSetCullMode(GX_CULL_BACK); @@ -443,12 +464,13 @@ void daMant_packet_c::draw() { GXLoadNrmMtxImm(MStack_54, GX_PNMTX0); GXCallDisplayList(l_Egnd_mantDL, 0x3e0); -#ifdef TARGET_PC - GStack_74.reset(); -#endif +#if TARGET_PC + GXLoadTexObj(&undersideTexObj, GX_TEXMAP0); +#else GXInitTexObjCI(&GStack_74, l_Egnd_mantTEX_U, 0x80, 0x80, GX_TF_C8, GX_CLAMP, GX_CLAMP, 0, 0); GXInitTexObjLOD(&GStack_74, GX_LINEAR, GX_LINEAR, 0.0, 0.0, 0.0, 0, 0, GX_ANISO_1); GXLoadTexObj(&GStack_74, GX_TEXMAP0); +#endif GXSetTevColor(GX_TEVREG0, COMPOUND_LITERAL(GXColor){0, 0, 0, 0}); GXSetTevKColor(GX_KCOLOR0, COMPOUND_LITERAL(GXColor){0, 0, 0, 0}); diff --git a/src/d/actor/d_a_player.cpp b/src/d/actor/d_a_player.cpp index 68e65fed81..e2cb035811 100644 --- a/src/d/actor/d_a_player.cpp +++ b/src/d/actor/d_a_player.cpp @@ -393,7 +393,9 @@ static const u8* l_sightDL_get() { void daPy_sightPacket_c::draw() { ZoneScoped; +#if !TARGET_PC TGXTexObj texObj; +#endif j3dSys.reinitGX(); GXSetNumIndStages(0); @@ -408,10 +410,24 @@ void daPy_sightPacket_c::draw() { GXSetTevColor(GX_TEVREG0, reg0); GXSetTevColor(GX_TEVREG1, reg1); +#if TARGET_PC + if (mpCachedImg != mpImg) { + mTexObj.reset(); + GXInitTexObj(&mTexObj, mpData, mpImg->width, mpImg->height, + static_cast(mpImg->format), static_cast(mpImg->wrapS), + static_cast(mpImg->wrapT), + mpImg->mipmapCount > 1 ? GX_ENABLE : GX_DISABLE); + GXInitTexObjLOD( + &mTexObj, GX_LINEAR, GX_LINEAR, 0.0, 0.0, 0.0, GX_FALSE, GX_FALSE, GX_ANISO_1); + mpCachedImg = mpImg; + } + GXLoadTexObj(&mTexObj, GX_TEXMAP0); +#else GXInitTexObj(&texObj, mpData, mpImg->width, mpImg->height, (GXTexFmt)mpImg->format, (GXTexWrapMode)mpImg->wrapS, (GXTexWrapMode)mpImg->wrapT, mpImg->mipmapCount > 1 ? GX_ENABLE : GX_DISABLE); GXInitTexObjLOD(&texObj, GX_LINEAR, GX_LINEAR, 0.0, 0.0, 0.0, GX_FALSE, GX_FALSE, GX_ANISO_1); GXLoadTexObj(&texObj, GX_TEXMAP0); +#endif GXLoadPosMtxImm(mProjMtx, GX_PNMTX0); GXSetCurrentMtx(0); GXSetTevOrder(GX_TEVSTAGE0, GX_TEXCOORD0, GX_TEXMAP0, GX_COLOR_NULL); diff --git a/src/d/d_drawlist.cpp b/src/d/d_drawlist.cpp index 8387047252..b3c685092f 100644 --- a/src/d/d_drawlist.cpp +++ b/src/d/d_drawlist.cpp @@ -41,12 +41,12 @@ public: /* 0x14 */ GXColor field_0x14; /* 0x18 */ GXColor field_0x18; /* 0x1C */ TGXTexObj field_0x1c; - /* 0x3C */ GXTlutObj field_0x3c; + /* 0x3C */ TGXTlutObj field_0x3c; /* 0x48 */ s16 field_0x48; /* 0x4A */ s16 field_0x4a; /* 0x4C */ u8 field_0x4c; /* 0x50 */ TGXTexObj field_0x50; - /* 0x70 */ GXTlutObj field_0x70; + /* 0x70 */ TGXTlutObj field_0x70; /* 0x7C */ s16 field_0x7c; /* 0x7E */ s16 field_0x7e; /* 0x80 */ u8 field_0x80; @@ -100,7 +100,7 @@ public: u8 check() { return field_0x0; } int getCI() { return mCI; } TGXTexObj* getTexObj() { return &mTexObj; } - GXTlutObj* getTlutObj() { return &mTlutObj; } + TGXTlutObj* getTlutObj() { return &mTlutObj; } GXColor* getColor() { return &mColor; } f32 getS() { return mS; } f32 getT() { return mT; } @@ -110,7 +110,7 @@ public: /* 0x00 */ u8 field_0x0; /* 0x01 */ u8 mCI; /* 0x04 */ TGXTexObj mTexObj; - /* 0x24 */ GXTlutObj mTlutObj; + /* 0x24 */ TGXTlutObj mTlutObj; /* 0x30 */ GXColor mColor; /* 0x34 */ f32 mS; /* 0x38 */ f32 mT; diff --git a/src/d/d_kankyo_rain.cpp b/src/d/d_kankyo_rain.cpp index 41f45c8315..bcc8b228e6 100644 --- a/src/d/d_kankyo_rain.cpp +++ b/src/d/d_kankyo_rain.cpp @@ -94,6 +94,39 @@ static void dKyr_set_btitex(TGXTexObj* i_obj, ResTIMG* i_img) { dKyr_set_btitex_common(i_obj, i_img, GX_TEXMAP0); } +#if TARGET_PC +template +struct CachedTexObjs { + TGXTexObj texObj[N]; + ResTIMG* timg[N] = {}; +}; + +template +static GXTexObj* load_cached_tex(CachedTexObjs& cache, ResTIMG* img, GXTexMapID mapID) { + for (int i = 0; i < N; i++) { + if (img != nullptr && cache.timg[i] == img) { + GXLoadTexObj(&cache.texObj[i], mapID); + return &cache.texObj[i]; + } + } + + int slot = 0; + for (int i = 0; i < N; i++) { + if (cache.timg[i] == nullptr) { + slot = i; + break; + } + } + + if (cache.timg[slot] != nullptr) { + cache.texObj[slot].reset(); + } + cache.timg[slot] = img; + dKyr_set_btitex_common(&cache.texObj[slot], img, mapID); + return &cache.texObj[slot]; +} +#endif + void dKyr_lenzflare_move() { dKankyo_sun_Packet* sun_packet = g_env_light.mpSunPacket; dKankyo_sunlenz_Packet* lenz_packet = g_env_light.mpSunLenzPacket; @@ -2133,10 +2166,17 @@ static void dKyr_draw_rev_moon(Mtx drawMtx, u8** tex) { return; } +#if TARGET_PC + static CachedTexObjs<8> texobj; + load_cached_tex(texobj, (ResTIMG*)tex[0], GX_TEXMAP0); + load_cached_tex(texobj, (ResTIMG*)tex[1], GX_TEXMAP1); + load_cached_tex(texobj, (ResTIMG*)tex[texidx + 2], GX_TEXMAP2); +#else TGXTexObj texobj; dKyr_set_btitex_common(&texobj, (ResTIMG*)tex[0], GX_TEXMAP0); dKyr_set_btitex_common(&texobj, (ResTIMG*)tex[1], GX_TEXMAP1); dKyr_set_btitex_common(&texobj, (ResTIMG*)tex[texidx + 2], GX_TEXMAP2); +#endif GXSetNumChans(0); GXSetTevColor(GX_TEVREG0, color_reg0); @@ -2216,7 +2256,11 @@ static void dKyr_draw_rev_moon(Mtx drawMtx, u8** tex) { for (int i = 0; i < 2; i++) { if (i == 1) { +#if TARGET_PC + load_cached_tex(texobj, (ResTIMG*)lenz_packet->mpResBall, GX_TEXMAP0); +#else dKyr_set_btitex(&texobj, (ResTIMG*)lenz_packet->mpResBall); +#endif GXClearVtxDesc(); GXSetVtxDesc(GX_VA_POS, GX_DIRECT); GXSetVtxDesc(GX_VA_TEX0, GX_DIRECT); @@ -2481,10 +2525,17 @@ void dKyr_drawSun(Mtx drawMtx, cXyz* ppos, GXColor& unused, u8** tex) { return; } +#if TARGET_PC + static CachedTexObjs<8> texobj; + load_cached_tex(texobj, (ResTIMG*)tex[0], GX_TEXMAP0); + load_cached_tex(texobj, (ResTIMG*)tex[1], GX_TEXMAP1); + load_cached_tex(texobj, (ResTIMG*)tex[texidx + 2], GX_TEXMAP2); +#else TGXTexObj texobj; dKyr_set_btitex_common(&texobj, (ResTIMG*)tex[0], GX_TEXMAP0); dKyr_set_btitex_common(&texobj, (ResTIMG*)tex[1], GX_TEXMAP1); dKyr_set_btitex_common(&texobj, (ResTIMG*)tex[texidx + 2], GX_TEXMAP2); +#endif GXSetNumChans(0); GXSetTevColor(GX_TEVREG0, color_reg0); @@ -2578,7 +2629,11 @@ void dKyr_drawSun(Mtx drawMtx, cXyz* ppos, GXColor& unused, u8** tex) { for (int i = 0; i < 2; i++) { if (i == 1) { +#if TARGET_PC + load_cached_tex(texobj, (ResTIMG*)lenz_packet->mpResBall, GX_TEXMAP0); +#else dKyr_set_btitex(&texobj, (ResTIMG*)lenz_packet->mpResBall); +#endif GXClearVtxDesc(); GXSetVtxDesc(GX_VA_POS, GX_DIRECT); GXSetVtxDesc(GX_VA_TEX0, GX_DIRECT); @@ -2742,8 +2797,13 @@ void dKyr_drawLenzflare(Mtx drawMtx, cXyz* ppos, GXColor& param_2, u8** tex) { j3dSys.reinitGX(); +#if TARGET_PC + static CachedTexObjs<3> texobj; + load_cached_tex(texobj, (ResTIMG*)tex[0], GX_TEXMAP0); +#else TGXTexObj texobj; dKyr_set_btitex(&texobj, (ResTIMG*)tex[0]); +#endif GXSetNumChans(0); GXSetTevColor(GX_TEVREG0, color_reg0); GXSetTevColor(GX_TEVREG1, color_reg1); @@ -3048,11 +3108,23 @@ void dKyr_drawLenzflare(Mtx drawMtx, cXyz* ppos, GXColor& param_2, u8** tex) { } if (i == 1) { +#if TARGET_PC + load_cached_tex(texobj, (ResTIMG*)tex[2], GX_TEXMAP0); +#else dKyr_set_btitex(&texobj, (ResTIMG*)tex[2]); +#endif } else if (i == 2) { +#if TARGET_PC + load_cached_tex(texobj, (ResTIMG*)tex[3], GX_TEXMAP0); +#else dKyr_set_btitex(&texobj, (ResTIMG*)tex[3]); +#endif } else { +#if TARGET_PC + load_cached_tex(texobj, (ResTIMG*)tex[0], GX_TEXMAP0); +#else dKyr_set_btitex(&texobj, (ResTIMG*)tex[0]); +#endif } spE4.x = -var_f31; @@ -3143,8 +3215,13 @@ void dKyr_drawRain(Mtx drawMtx, u8** tex) { return; } +#if TARGET_PC + static CachedTexObjs<1> texobj; + load_cached_tex(texobj, (ResTIMG*)tex[0], GX_TEXMAP0); +#else TGXTexObj texobj; dKyr_set_btitex(&texobj, (ResTIMG*)tex[0]); +#endif GXSetNumChans(0); GXSetTevColor(GX_TEVREG0, color_reg0); GXSetNumTexGens(1); @@ -3308,8 +3385,13 @@ void dKyr_drawSibuki(Mtx drawMtx, u8** tex) { color.b = 0xC8; color.a = rain_packet->mSibukiAlpha * alphaFade; +#if TARGET_PC + static CachedTexObjs<1> texobj; + load_cached_tex(texobj, (ResTIMG*)tex[1], GX_TEXMAP0); +#else TGXTexObj texobj; dKyr_set_btitex(&texobj, (ResTIMG*)tex[1]); +#endif GXSetNumChans(0); GXSetTevColor(GX_TEVREG0, color); GXSetTevColor(GX_TEVREG1, color); @@ -3397,7 +3479,11 @@ void dKyr_drawHousi(Mtx drawMtx, u8** tex) { Mtx camMtx; Mtx rotMtx; cXyz pos[4]; +#if TARGET_PC + static CachedTexObjs<1> texobj; +#else TGXTexObj spDC; +#endif cXyz spD0; Vec spC4; Vec spB8; @@ -3484,7 +3570,11 @@ void dKyr_drawHousi(Mtx drawMtx, u8** tex) { f32 temp_f24 = 6.5f; for (int i = 0; i < 1; i++) { - dKyr_set_btitex(&spDC, (ResTIMG*)*tex); +#if TARGET_PC + load_cached_tex(texobj, (ResTIMG*)*tex, GX_TEXMAP0); +#else + dKyr_set_btitex(&texobj, (ResTIMG*)*tex); +#endif #if TARGET_PC GXSetNumChans(1); GXSetChanCtrl(GX_COLOR0, GX_DISABLE, GX_SRC_REG, GX_SRC_VTX, GX_LIGHT_NULL, GX_DF_CLAMP, GX_AF_NONE); @@ -3582,7 +3672,7 @@ void dKyr_drawHousi(Mtx drawMtx, u8** tex) { block_14: #if !TARGET_PC // GXLoadTextObj does nothing, TEV colors replaced with vertex colors - GXLoadTexObj(&spDC, GX_TEXMAP0); + GXLoadTexObj(&texobj, GX_TEXMAP0); GXSetTevColor(GX_TEVREG0, color_reg0); #endif @@ -3842,8 +3932,13 @@ void dKyr_drawSnow(Mtx drawMtx, u8** tex) { } if (tex[0] != NULL) { - TGXTexObj spA0; - dKyr_set_btitex(&spA0, (ResTIMG*)tex[0]); +#if TARGET_PC + static CachedTexObjs<1> texobj; + load_cached_tex(texobj, (ResTIMG*)tex[0], GX_TEXMAP0); +#else + TGXTexObj texobj; + dKyr_set_btitex(&texobj, (ResTIMG*)tex[0]); +#endif #if TARGET_PC // Dusklight optimization: enable draw call merging // by using vertex color instead of GX_TEVREG0 @@ -4445,7 +4540,12 @@ void drawCloudShadow(Mtx drawMtx, u8** tex) { GXSetClipMode(GX_CLIP_DISABLE); +#if TARGET_PC + static CachedTexObjs<1> texobj; + TGXTexObj fb_texobj; +#else TGXTexObj texobj, fb_texobj; +#endif if (g_env_light.mMoyaMode < 50) { dKy_ParticleColor_get_bg(&camera->view.lookat.eye, NULL, &sp48, &sp44, &sp40, &sp3C, 0.0f); f32 temp_f30 = 0.4f; @@ -4458,7 +4558,11 @@ void drawCloudShadow(Mtx drawMtx, u8** tex) { color_reg1.g = (0.45f * sp38.g) + (0.55f * sp44.g); color_reg1.b = (0.45f * sp38.b) + (0.55f * sp44.b); +#if TARGET_PC + load_cached_tex(texobj, (ResTIMG*)tex[0], GX_TEXMAP0); +#else dKyr_set_btitex(&texobj, (ResTIMG*)tex[0]); +#endif GXSetNumChans(0); GXSetTevColor(GX_TEVREG0, color_reg0); GXSetTevColor(GX_TEVREG1, color_reg1); @@ -4503,7 +4607,11 @@ void drawCloudShadow(Mtx drawMtx, u8** tex) { color_reg1.b = 0; color_reg1.a = 0xFF; +#if TARGET_PC + load_cached_tex(texobj, (ResTIMG*)tex[0], GX_TEXMAP1); +#else dKyr_set_btitex_common(&texobj, (ResTIMG*)tex[0], GX_TEXMAP1); +#endif ResTIMG* fb_timg = mDoGph_gInf_c::getFrameBufferTimg(); dDlst_window_c* window = dComIfGp_getWindow(0); @@ -4650,7 +4758,11 @@ void drawVrkumo(Mtx drawMtx, GXColor& color, u8** tex) { Mtx camMtx; Mtx rotMtx; +#if TARGET_PC + static CachedTexObjs<3> texobj; +#else TGXTexObj texobj; +#endif cXyz proj; f32 rot; @@ -4760,7 +4872,11 @@ void drawVrkumo(Mtx drawMtx, GXColor& color, u8** tex) { color_reg1.r = 0; color_reg1.g = 0; color_reg1.b = 0; +#if TARGET_PC + auto* loaded_texobj = load_cached_tex(texobj, (ResTIMG*)tex[j], GX_TEXMAP0); +#else dKyr_set_btitex(&texobj, (ResTIMG*)tex[j]); +#endif GXSetNumChans(0); GXSetTevColor(GX_TEVREG0, color); @@ -4865,7 +4981,11 @@ void drawVrkumo(Mtx drawMtx, GXColor& color, u8** tex) { } if (!(vrkumo_packet->mVrkumoEff[k].mAlpha <= 0.000001f)) { +#if TARGET_PC + GXLoadTexObj(loaded_texobj, GX_TEXMAP0); +#else GXLoadTexObj(&texobj, GX_TEXMAP0); +#endif GXSetTevColor(GX_TEVREG0, color); sp60 = sp68 * (0.2f + (0.2f * (k / 100.0f))); @@ -5487,8 +5607,14 @@ void dKyr_odour_draw(Mtx drawMtx, u8** tex) { break; } +#if TARGET_PC + static CachedTexObjs<1> texobj; + TGXTexObj fb_texobj; + load_cached_tex(texobj, (ResTIMG*)tex[0], GX_TEXMAP1); +#else TGXTexObj texobj, fb_texobj; dKyr_set_btitex_common(&texobj, (ResTIMG*)tex[0], GX_TEXMAP1); +#endif ResTIMG* fb_timg = mDoGph_gInf_c::getFrameBufferTimg(); dDlst_window_c* window = dComIfGp_getWindow(0); @@ -5891,8 +6017,13 @@ void dKyr_mud_draw(Mtx drawMtx, u8** tex) { if (g_env_light.camera_water_in_status == 0) { for (int i = 0; i < 1; i++) { +#if TARGET_PC + static CachedTexObjs<1> texobj_cache; + auto* texobj = load_cached_tex(texobj_cache, (ResTIMG*)tex[0], GX_TEXMAP0); +#else TGXTexObj texobj; dKyr_set_btitex(&texobj, (ResTIMG*)tex[0]); +#endif GXSetNumChans(0); GXSetTevColor(GX_TEVREG0, color_reg0); @@ -5932,7 +6063,11 @@ void dKyr_mud_draw(Mtx drawMtx, u8** tex) { color_reg0.a = mud_packet->mEffect[j].field_0x38 * var_f31; +#if TARGET_PC + GXLoadTexObj(texobj, GX_TEXMAP0); +#else GXLoadTexObj(&texobj, GX_TEXMAP0); +#endif GXSetTevColor(GX_TEVREG0, color_reg0); f32 sp30 = 1.0f; @@ -6054,8 +6189,13 @@ static void dKyr_evil_draw2(Mtx drawMtx, u8** tex) { color_reg0.b = 0x87; color_reg0.a = 0xFF; +#if TARGET_PC + static CachedTexObjs<1> texobj; + load_cached_tex(texobj, (ResTIMG*)tex[1], GX_TEXMAP0); +#else TGXTexObj texobj; dKyr_set_btitex(&texobj, (ResTIMG*)tex[1]); +#endif #if TARGET_PC if (dusk::frame_interp::get_ui_tick_pending()) @@ -6293,8 +6433,13 @@ void dKyr_evil_draw(Mtx drawMtx, u8** tex) { color_reg1.b = 10; color_reg1.a = 255; +#if TARGET_PC + static CachedTexObjs<1> texobj; + load_cached_tex(texobj, (ResTIMG*)tex[0], GX_TEXMAP0); +#else TGXTexObj texobj; dKyr_set_btitex(&texobj, (ResTIMG*)tex[0]); +#endif #if TARGET_PC if (dusk::frame_interp::get_ui_tick_pending()) diff --git a/src/m_Do/m_Do_lib.cpp b/src/m_Do/m_Do_lib.cpp index 3a32c4c342..b46527b11c 100644 --- a/src/m_Do/m_Do_lib.cpp +++ b/src/m_Do/m_Do_lib.cpp @@ -11,12 +11,15 @@ #include u32 mDoLib_setResTimgObj(ResTIMG const* i_img, TGXTexObj* o_texObj, u32 tlut_name, - GXTlutObj* o_tlutObj) { + TGXTlutObj* o_tlutObj) { #ifdef TARGET_PC o_texObj->reset(); #endif if (i_img->indexTexture) { JUT_ASSERT(44, o_tlutObj != NULL); +#ifdef TARGET_PC + o_tlutObj->reset(); +#endif GXInitTlutObj(o_tlutObj, (void*)((u8*)i_img + i_img->paletteOffset), (GXTlutFmt)i_img->colorFormat, (u16)i_img->numColors); GXInitTexObjCI(o_texObj, (void*)((u8*)i_img + i_img->imageOffset), i_img->width, i_img->height, From 09361154834a54be19cc94866e657641be180b05 Mon Sep 17 00:00:00 2001 From: Luke Street Date: Thu, 4 Jun 2026 23:27:30 -0600 Subject: [PATCH 02/12] Optimize display lists in J3DShapeDraw This is a stop-gap until DL optimization is upstreamed to Aurora. --- .../JSystem/J3DGraphBase/J3DShapeDraw.h | 4 + .../JSystem/src/J3DGraphBase/J3DShapeDraw.cpp | 343 +++++++++++++++++- .../src/J3DGraphLoader/J3DShapeFactory.cpp | 7 +- 3 files changed, 349 insertions(+), 5 deletions(-) diff --git a/libs/JSystem/include/JSystem/J3DGraphBase/J3DShapeDraw.h b/libs/JSystem/include/JSystem/J3DGraphBase/J3DShapeDraw.h index e6e8d61786..feff2b4991 100644 --- a/libs/JSystem/include/JSystem/J3DGraphBase/J3DShapeDraw.h +++ b/libs/JSystem/include/JSystem/J3DGraphBase/J3DShapeDraw.h @@ -1,6 +1,7 @@ #ifndef J3DSHAPEDRAW_H #define J3DSHAPEDRAW_H +#include #include /** @@ -12,6 +13,9 @@ public: u32 countVertex(u32); void addTexMtxIndexInDL(u32, u32, u32); J3DShapeDraw(u8 const*, u32); +#if TARGET_PC + J3DShapeDraw(u8 const*, u32, const GXVtxDescList*); +#endif void draw() const; virtual ~J3DShapeDraw(); diff --git a/libs/JSystem/src/J3DGraphBase/J3DShapeDraw.cpp b/libs/JSystem/src/J3DGraphBase/J3DShapeDraw.cpp index a1bd69438c..b40f577d79 100644 --- a/libs/JSystem/src/J3DGraphBase/J3DShapeDraw.cpp +++ b/libs/JSystem/src/J3DGraphBase/J3DShapeDraw.cpp @@ -1,15 +1,310 @@ -#include "JSystem/JSystem.h" // IWYU pragma: keep +#include "JSystem/JSystem.h" // IWYU pragma: keep +#include +#include +#include #include "JSystem/J3DGraphBase/J3DShapeDraw.h" #include "JSystem/JKernel/JKRHeap.h" -#include -#include -#include + +#if TARGET_PC +#include +#include +#include +#include "dusk/logging.h" + +namespace { + +u16 read_be16(const u8* data) { + return (u16(data[0]) << 8) | data[1]; +} + +void append_be16(std::vector& out, u16 value) { + out.push_back(value >> 8); + out.push_back(value & 0xFF); +} + +void append_bytes(std::vector& out, const u8* data, u32 size) { + out.insert(out.end(), data, data + size); +} + +bool is_matrix_idx_attr(GXAttr attr) { + return attr >= GX_VA_PNMTXIDX && attr <= GX_VA_TEX7MTXIDX; +} + +bool is_draw_opcode(u8 opcode) { + return opcode == GX_QUADS || opcode == GX_TRIANGLES || opcode == GX_TRIANGLESTRIP || + opcode == GX_TRIANGLEFAN || opcode == GX_LINES || opcode == GX_LINESTRIP || + opcode == GX_POINTS; +} + +bool is_mergeable_draw_opcode(u8 opcode) { + return opcode == GX_QUADS || opcode == GX_TRIANGLES || opcode == GX_TRIANGLESTRIP || + opcode == GX_TRIANGLEFAN; +} + +bool calc_vtx_stride(const GXVtxDescList* vtxDesc, u32& stride) { + stride = 0; + for (; vtxDesc->attr != GX_VA_NULL; vtxDesc++) { + switch (vtxDesc->type) { + case GX_NONE: + break; + case GX_DIRECT: + if (!is_matrix_idx_attr(vtxDesc->attr)) { + return false; + } + stride += 1; + break; + case GX_INDEX8: + stride += 1; + break; + case GX_INDEX16: + stride += 2; + break; + default: + return false; + } + } + return stride != 0; +} + +bool get_command_size(const u8* dlStart, u32 dlSize, u32 offset, u32 stride, u32& cmdSize) { + if (offset >= dlSize) { + return false; + } + + const u8 cmd = dlStart[offset]; + const u8 opcode = cmd & GX_OPCODE_MASK; + switch (opcode) { + case GX_NOP: + case GX_CMD_INVL_VC: + cmdSize = 1; + return true; + case (GX_LOAD_BP_REG & GX_OPCODE_MASK): + cmdSize = 5; + return offset + cmdSize <= dlSize; + case GX_LOAD_CP_REG: + cmdSize = 6; + return offset + cmdSize <= dlSize; + case GX_LOAD_XF_REG: { + if (offset + 5 > dlSize) { + return false; + } + const u16 count = read_be16(dlStart + offset + 1) + 1; + cmdSize = 5 + count * 4; + return offset + cmdSize <= dlSize; + } + case GX_LOAD_INDX_A: + case GX_LOAD_INDX_B: + case GX_LOAD_INDX_C: + case GX_LOAD_INDX_D: + cmdSize = 5; + return offset + cmdSize <= dlSize; + case GX_CMD_CALL_DL: + cmdSize = 9; + return offset + cmdSize <= dlSize; + default: + if (is_draw_opcode(opcode)) { + if (offset + 3 > dlSize) { + return false; + } + const u16 vtxCount = read_be16(dlStart + offset + 1); + cmdSize = 3 + vtxCount * stride; + return offset + cmdSize <= dlSize; + } + return false; + } +} + +struct MergeRun { + u8 cmd = 0; + u16 vtxCount = 0; + std::vector vertices; +}; + +void flush_merge_run(std::vector& out, MergeRun& run) { + if (run.vtxCount == 0) { + return; + } + + out.push_back(run.cmd); + append_be16(out, run.vtxCount); + append_bytes(out, run.vertices.data(), run.vertices.size()); + run.vertices.clear(); + run.vtxCount = 0; +} + +void append_vertex(std::vector& out, const u8* vertices, u32 stride, u16 idx) { + append_bytes(out, vertices + idx * stride, stride); +} + +bool triangulate_draw( + std::vector& out, u8 opcode, const u8* vertices, u32 stride, u16 vtxCount) { + switch (opcode) { + case GX_TRIANGLES: + append_bytes(out, vertices, vtxCount * stride); + return true; + case GX_TRIANGLEFAN: + if (vtxCount < 3) { + return false; + } + for (u16 v = 2; v < vtxCount; v++) { + append_vertex(out, vertices, stride, 0); + append_vertex(out, vertices, stride, v - 1); + append_vertex(out, vertices, stride, v); + } + return true; + case GX_TRIANGLESTRIP: + if (vtxCount < 3) { + return false; + } + for (u16 v = 2; v < vtxCount; v++) { + if ((v & 1) == 0) { + append_vertex(out, vertices, stride, v - 2); + append_vertex(out, vertices, stride, v - 1); + } else { + append_vertex(out, vertices, stride, v - 1); + append_vertex(out, vertices, stride, v - 2); + } + append_vertex(out, vertices, stride, v); + } + return true; + case GX_QUADS: + if ((vtxCount & 3) != 0) { + return false; + } + for (u16 v = 0; v < vtxCount; v += 4) { + append_vertex(out, vertices, stride, v); + append_vertex(out, vertices, stride, v + 1); + append_vertex(out, vertices, stride, v + 2); + append_vertex(out, vertices, stride, v + 2); + append_vertex(out, vertices, stride, v + 3); + append_vertex(out, vertices, stride, v); + } + return true; + default: + return false; + } +} + +void append_triangles_to_run( + std::vector& out, MergeRun& run, u8 cmd, const std::vector& vertices, u32 stride) { + u32 offset = 0; + u32 remaining = vertices.size() / stride; + while (remaining != 0) { + if (run.vtxCount != 0 && run.cmd != cmd) { + flush_merge_run(out, run); + } + + if (run.vtxCount == 0) { + run.cmd = cmd; + } + + u32 available = 0xFFFF - run.vtxCount; + if (available == 0) { + flush_merge_run(out, run); + continue; + } + + u32 toCopy = std::min(remaining, available); + append_bytes(run.vertices, vertices.data() + offset * stride, toCopy * stride); + run.vtxCount += toCopy; + offset += toCopy; + remaining -= toCopy; + + if (run.vtxCount == 0xFFFF) { + flush_merge_run(out, run); + } + } +} + +bool optimize_display_list(const u8* dlStart, u32 dlSize, u32 stride, std::vector& out) { + MergeRun run; + out.reserve(dlSize); + + for (u32 offset = 0; offset < dlSize;) { + u32 cmdSize = 0; + if (!get_command_size(dlStart, dlSize, offset, stride, cmdSize)) { + return false; + } + + const u8 cmd = dlStart[offset]; + const u8 opcode = cmd & GX_OPCODE_MASK; + if (opcode == GX_NOP) { + offset += cmdSize; + continue; + } + + if (!is_draw_opcode(opcode)) { + flush_merge_run(out, run); + append_bytes(out, dlStart + offset, cmdSize); + offset += cmdSize; + continue; + } + + if (!is_mergeable_draw_opcode(opcode)) { + flush_merge_run(out, run); + append_bytes(out, dlStart + offset, cmdSize); + offset += cmdSize; + continue; + } + + const u16 vtxCount = read_be16(dlStart + offset + 1); + const u8* vertices = dlStart + offset + 3; + std::vector triangles; + if (!triangulate_draw(triangles, opcode, vertices, stride, vtxCount)) { + flush_merge_run(out, run); + append_bytes(out, dlStart + offset, cmdSize); + offset += cmdSize; + continue; + } + + append_triangles_to_run(out, run, (GX_TRIANGLES | (cmd & GX_VAT_MASK)), triangles, stride); + offset += cmdSize; + } + + flush_merge_run(out, run); + return true; +} + +void set_display_list_copy(void*& displayList, u32& displayListSize, const u8* data, u32 size) { + const u32 alignedSize = ALIGN_NEXT(size, 0x20); + u8* newDL = JKR_NEW_ARRAY_ARGS(u8, alignedSize, 0x20); + if (size != 0) { + std::memcpy(newDL, data, size); + } + for (u32 i = size; i < alignedSize; i++) { + newDL[i] = 0; + } + + displayList = newDL; + displayListSize = alignedSize; + DCStoreRange(newDL, displayListSize); +} + +} // namespace +#endif u32 J3DShapeDraw::countVertex(u32 stride) { u32 count = 0; u8* dlStart = (u8*)getDisplayList(); +#if TARGET_PC + for (u32 offset = 0; offset < getDisplayListSize();) { + u8 cmd = dlStart[offset]; + u8 opcode = cmd & GX_OPCODE_MASK; + u32 cmdSize = 0; + if (!get_command_size(dlStart, getDisplayListSize(), offset, stride, cmdSize)) { + break; + } + if (!is_draw_opcode(opcode)) { + offset += cmdSize; + continue; + } + int vtxNum = be16(*reinterpret_cast(dlStart + offset + 1)); + count += vtxNum; + offset += 3 + stride * vtxNum; + } +#else for (u8* dl = dlStart; (dl - dlStart) < getDisplayListSize();) { u8 cmd = *(u8*)dl; dl++; @@ -20,6 +315,7 @@ u32 J3DShapeDraw::countVertex(u32 stride) { count += vtxNum; dl = (u8*)dl + stride * vtxNum; } +#endif return count; } @@ -34,13 +330,32 @@ void J3DShapeDraw::addTexMtxIndexInDL(u32 stride, u32 attrOffs, u32 valueBase) { u8* newDL = newDLStart; for (; (oldDL - oldDLStart) < mDisplayListSize;) { +#if TARGET_PC + u32 oldOffset = oldDL - oldDLStart; + u32 cmdSize = 0; + if (!get_command_size(oldDLStart, mDisplayListSize, oldOffset, stride, cmdSize)) { + memcpy(newDL, oldDL, mDisplayListSize - oldOffset); + newDL += mDisplayListSize - oldOffset; + break; + } +#endif // Copy command u8 cmd = *(u8*)oldDL; oldDL++; *newDL++ = cmd; +#if TARGET_PC + u8 opcode = cmd & GX_OPCODE_MASK; + if (!is_draw_opcode(opcode)) { + memcpy(newDL, oldDL, cmdSize - 1); + oldDL += cmdSize - 1; + newDL += cmdSize - 1; + continue; + } +#else if (cmd != GX_TRIANGLEFAN && cmd != GX_TRIANGLESTRIP) break; +#endif // Copy count int vtxNum = *(u16*)oldDL; @@ -71,11 +386,31 @@ void J3DShapeDraw::addTexMtxIndexInDL(u32 stride, u32 attrOffs, u32 valueBase) { } J3DShapeDraw::J3DShapeDraw(const u8* displayList, u32 displayListSize) { +#if TARGET_PC + set_display_list_copy(mDisplayList, mDisplayListSize, displayList, displayListSize); +#else mDisplayList = (void*)displayList; mDisplayListSize = displayListSize; +#endif } +#if TARGET_PC +J3DShapeDraw::J3DShapeDraw( + const u8* displayList, u32 displayListSize, const GXVtxDescList* vtxDesc) { + u32 stride = 0; + std::vector optimized; + if (calc_vtx_stride(vtxDesc, stride) && + optimize_display_list(displayList, displayListSize, stride, optimized)) + { + set_display_list_copy(mDisplayList, mDisplayListSize, optimized.data(), optimized.size()); + } else { + set_display_list_copy(mDisplayList, mDisplayListSize, displayList, displayListSize); + } +} +#endif + void J3DShapeDraw::draw() const { + ZoneScoped; GXCallDisplayList(mDisplayList, mDisplayListSize); } diff --git a/libs/JSystem/src/J3DGraphLoader/J3DShapeFactory.cpp b/libs/JSystem/src/J3DGraphLoader/J3DShapeFactory.cpp index f283cb8932..b8b3b14c36 100644 --- a/libs/JSystem/src/J3DGraphLoader/J3DShapeFactory.cpp +++ b/libs/JSystem/src/J3DGraphLoader/J3DShapeFactory.cpp @@ -132,7 +132,12 @@ J3DShapeDraw* J3DShapeFactory::newShapeDraw(int shapeNo, int mtxGroupNo) const { const J3DShapeInitData& shapeInitData = mShapeInitData[mIndexTable[shapeNo]]; const J3DShapeDrawInitData& drawInitData = (&mDrawInitData[shapeInitData.mDrawInitDataIndex])[mtxGroupNo]; +#if TARGET_PC + shapeDraw = JKR_NEW J3DShapeDraw(&mDisplayListData[drawInitData.mDisplayListIndex], drawInitData.mDisplayListSize, + getVtxDescList(shapeNo)); +#else shapeDraw = JKR_NEW J3DShapeDraw(&mDisplayListData[drawInitData.mDisplayListIndex], drawInitData.mDisplayListSize); +#endif J3D_ASSERT_ALLOCMEM(193, shapeDraw); return shapeDraw; } @@ -154,7 +159,7 @@ s32 J3DShapeFactory::calcSize(int shapeNo, u32 flag) { for (u32 i = 0; i < mtxGroupNo; i++) { size += calcSizeShapeMtx(flag, shapeNo, i); - size += 0x0C; + size += sizeof(J3DShapeDraw); } return size; From e484a100180a24ed9d4fac9ed3cc5792e0ddfe1a Mon Sep 17 00:00:00 2001 From: Luke Street Date: Thu, 4 Jun 2026 23:28:44 -0600 Subject: [PATCH 03/12] tvOS fixes --- CMakePresets.json | 15 ++++++--------- src/dusk/crash_handler.cpp | 3 ++- 2 files changed, 8 insertions(+), 10 deletions(-) diff --git a/CMakePresets.json b/CMakePresets.json index 90970138f6..2a6285443f 100644 --- a/CMakePresets.json +++ b/CMakePresets.json @@ -292,24 +292,21 @@ "type": "BOOL", "value": false }, + "ENABLE_ARC": { + "type": "BOOL", + "value": false + }, "Rust_CARGO_TARGET": "aarch64-apple-tvos", "Rust_TOOLCHAIN": "nightly", "BUILD_SHARED_LIBS": { "type": "BOOL", "value": false }, - "CMAKE_DISABLE_FIND_PACKAGE_BZip2": { + "CMAKE_DISABLE_FIND_PACKAGE_PkgConfig": { "type": "BOOL", "value": true }, - "CMAKE_DISABLE_FIND_PACKAGE_LibLZMA": { - "type": "BOOL", - "value": true - }, - "CMAKE_DISABLE_FIND_PACKAGE_zstd": { - "type": "BOOL", - "value": true - } + "CMAKE_IGNORE_PREFIX_PATH": "/opt/homebrew" }, "vendor": { "microsoft.com/VisualStudioSettings/CMake/1.0": { diff --git a/src/dusk/crash_handler.cpp b/src/dusk/crash_handler.cpp index 8562d5f14b..b520a6ff29 100644 --- a/src/dusk/crash_handler.cpp +++ b/src/dusk/crash_handler.cpp @@ -34,6 +34,7 @@ #if defined(__APPLE__) #include #include +#include #else #include #include @@ -929,7 +930,7 @@ void install() { SymInitialize(GetCurrentProcess(), nullptr, TRUE); #endif g_prevFilter = SetUnhandledExceptionFilter(&windowsHandler); -#else +#elif !defined(__APPLE__) || !TARGET_OS_TV Dl_info moduleInfo; if (dladdr(reinterpret_cast(&install), &moduleInfo) != 0) { g_ctx.moduleBase = reinterpret_cast(moduleInfo.dli_fbase); From 358de645704d9d79059c61d7985b50123b8febd4 Mon Sep 17 00:00:00 2001 From: Luke Street Date: Thu, 4 Jun 2026 23:30:30 -0600 Subject: [PATCH 04/12] ci: Use mold linker on Linux --- .github/workflows/build.yml | 2 +- CMakePresets.json | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 88e20dcdf8..80257624c5 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -52,7 +52,7 @@ jobs: - name: Install dependencies run: | sudo apt-get update - sudo apt-get -y install ninja-build clang lld openssl libcurl4-openssl-dev \ + sudo apt-get -y install ninja-build clang lld mold openssl libcurl4-openssl-dev \ zlib1g-dev libglu1-mesa-dev libdbus-1-dev libvulkan-dev libxi-dev libxrandr-dev libasound2-dev \ libpulse-dev libudev-dev libpng-dev libncurses5-dev libx11-xcb-dev libfreetype-dev \ libxinerama-dev libxcursor-dev python3-markupsafe libgtk-3-dev libssl-dev \ diff --git a/CMakePresets.json b/CMakePresets.json index 2a6285443f..88c5aee56a 100644 --- a/CMakePresets.json +++ b/CMakePresets.json @@ -395,7 +395,8 @@ "ci" ], "cacheVariables": { - "AURORA_SDL3_PROVIDER": "vendor" + "AURORA_SDL3_PROVIDER": "vendor", + "CMAKE_LINKER_TYPE": "MOLD" } }, { From 7f306fe1ec4d3ca71e40107fa46849767d75b085 Mon Sep 17 00:00:00 2001 From: Luke Street Date: Fri, 5 Jun 2026 00:03:29 -0600 Subject: [PATCH 05/12] Update aurora --- extern/aurora | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/extern/aurora b/extern/aurora index 04cd3b3ac7..c31d6dadcb 160000 --- a/extern/aurora +++ b/extern/aurora @@ -1 +1 @@ -Subproject commit 04cd3b3ac7eac46a7df384ff7b6f355012610388 +Subproject commit c31d6dadcbe8d6131b90ea30b756553d8ee5ae6e From da3ac9f5467b3700354e096491fb8959f711c6d6 Mon Sep 17 00:00:00 2001 From: Luke Street Date: Fri, 5 Jun 2026 00:03:43 -0600 Subject: [PATCH 06/12] ci: Build with shared vcruntime --- CMakePresets.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CMakePresets.json b/CMakePresets.json index 88c5aee56a..292055a756 100644 --- a/CMakePresets.json +++ b/CMakePresets.json @@ -19,7 +19,7 @@ "hidden": true, "cacheVariables": { "CMAKE_BUILD_TYPE": "RelWithDebInfo", - "CMAKE_MSVC_RUNTIME_LIBRARY": "MultiThreaded" + "CMAKE_MSVC_RUNTIME_LIBRARY": "MultiThreadedDLL" } }, { From eefa69b53dba28d760061301df72bfe6163b0daa Mon Sep 17 00:00:00 2001 From: doop <56421834+dooplecks@users.noreply.github.com> Date: Fri, 5 Jun 2026 02:05:20 -0400 Subject: [PATCH 07/12] Re-enable JParticle interpolation and fix emitter direction issue (#1968) * Re-enable JParticle interpolation * Ensure emitter direction is valid for JPA interp Fixes #618. * Don't `calcWorkData` if we don't need to --- libs/JSystem/src/JParticle/JPAParticle.cpp | 3 +-- libs/JSystem/src/JParticle/JPAResource.cpp | 9 +++++++++ 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/libs/JSystem/src/JParticle/JPAParticle.cpp b/libs/JSystem/src/JParticle/JPAParticle.cpp index d3490ab294..eb2395e346 100644 --- a/libs/JSystem/src/JParticle/JPAParticle.cpp +++ b/libs/JSystem/src/JParticle/JPAParticle.cpp @@ -206,8 +206,7 @@ void JPABaseParticle::init_c(JPAEmitterWorkData* work, JPABaseParticle* parent) #if TARGET_PC void JPABaseParticle::interp(JPAEmitterWorkData* work, void const* drawFunc) { - static bool enable = false; - if (!enable) + if (!dusk::frame_interp::is_enabled()) return; // don't interpolate the first frame diff --git a/libs/JSystem/src/JParticle/JPAResource.cpp b/libs/JSystem/src/JParticle/JPAResource.cpp index 2ae4709fc3..59e679d449 100644 --- a/libs/JSystem/src/JParticle/JPAResource.cpp +++ b/libs/JSystem/src/JParticle/JPAResource.cpp @@ -761,6 +761,15 @@ bool JPAResource::calc(JPAEmitterWorkData* work, JPABaseEmitter* emtr) { } } +#ifdef TARGET_PC + if (((pBsp && pBsp->getDirType() == 3) || (pCsp && pCsp->getDirType() == 3)) && + dusk::frame_interp::is_enabled()) + { + // ensure mGlobalEmtrDir is valid + calcWorkData_d(work); + } +#endif + JPANode* next = NULL; for (JPANode* node = emtr->mAlivePtclBase.getFirst(); node != emtr->mAlivePtclBase.getEnd(); node = next) { next = node->getNext(); From 24ca190029f510a3eb3728e5a2c5844bfddf6e42 Mon Sep 17 00:00:00 2001 From: SuperDude88 <82904174+SuperDude88@users.noreply.github.com> Date: Fri, 5 Jun 2026 02:05:47 -0400 Subject: [PATCH 08/12] Adjust Total Achievement Count (#1924) - Don't count the glitched achievements towards the total so that they appear over 100% --- src/dusk/ui/achievements.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/dusk/ui/achievements.cpp b/src/dusk/ui/achievements.cpp index b631d444f5..9d25f1d82a 100644 --- a/src/dusk/ui/achievements.cpp +++ b/src/dusk/ui/achievements.cpp @@ -217,7 +217,7 @@ void AchievementsWindow::updateTotal() { return; } const auto all = AchievementSystem::get().getAchievements(); - int total = static_cast(all.size()); + const int total = std::count_if(all.begin(), all.end(), [](const Achievement& achievement){ return achievement.category != AchievementCategory::Glitched;}); int unlocked = 0; for (const auto& a : all) { if (a.unlocked) { From 1b42c4ecac4a9d45335ef91e291371e8984fe77d Mon Sep 17 00:00:00 2001 From: Luke Street Date: Fri, 5 Jun 2026 00:09:07 -0600 Subject: [PATCH 09/12] ci: Build Android w/ release & LTO --- CMakePresets.json | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/CMakePresets.json b/CMakePresets.json index 292055a756..6c3a2c46ef 100644 --- a/CMakePresets.json +++ b/CMakePresets.json @@ -22,6 +22,18 @@ "CMAKE_MSVC_RUNTIME_LIBRARY": "MultiThreadedDLL" } }, + { + "name": "release", + "hidden": true, + "cacheVariables": { + "CMAKE_BUILD_TYPE": "Release", + "CMAKE_MSVC_RUNTIME_LIBRARY": "MultiThreadedDLL", + "CMAKE_INTERPROCEDURAL_OPTIMIZATION": { + "type": "BOOL", + "value": true + } + } + }, { "name": "ci", "hidden": true, @@ -367,7 +379,8 @@ "hidden": true, "inherits": [ "android-base", - "ci" + "ci", + "release" ], "cacheVariables": { "DUSK_ENABLE_SENTRY_NATIVE": { From 8705e75b9d5084c64012a69e97c3a5f590500db7 Mon Sep 17 00:00:00 2001 From: Kevin Lema Date: Fri, 5 Jun 2026 08:14:09 +0200 Subject: [PATCH 10/12] Add HUD scale setting (#1387) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Add HUD scale setting Adds a "HUD Scale" preference (50%–200%) that scales the gameplay HUD (hearts, magic/lantern meter, light drops, rupees/keys, action buttons and the mini-map) without affecting dialog boxes or menus. Each HUD group is scaled around its own pane origin and nudged toward its anchor corner (via dApplyHudCorner) so it stays put against the screen edge instead of drifting toward the centre when shrunk. The mini-map is scaled and shifted in d_meter_map so its bottom-left corner stays anchored. The setting is clamped to a safe range and is a no-op on non-PC targets. It is disabled in the menu while Minimal HUD is enabled. Signed-off-by: kevin Lema * Scale remaining gameplay HUD elements with HUD scale Extends the HUD Scale setting to the item ammo counters, the lantern oil gauge and the small-key counter, and gives the oil/magic meter a reduced horizontal anchor pull so it stays on-screen at small scales. Signed-off-by: kevin Lema * Update settings.cpp Signed-off-by: kevin Lema --------- Signed-off-by: kevin Lema --- include/dusk/settings.h | 1 + src/d/d_meter2_draw.cpp | 161 +++++++++++++++++++++++++++++++++++++++ src/d/d_meter_map.cpp | 16 +++- src/dusk/settings.cpp | 2 + src/dusk/ui/settings.cpp | 5 ++ 5 files changed, 183 insertions(+), 2 deletions(-) diff --git a/include/dusk/settings.h b/include/dusk/settings.h index bc1007f8e1..30138b0042 100644 --- a/include/dusk/settings.h +++ b/include/dusk/settings.h @@ -163,6 +163,7 @@ struct UserSettings { // Preferences ConfigVar enableMirrorMode; ConfigVar minimalHUD; + ConfigVar hudScale; ConfigVar pauseOnFocusLost; ConfigVar enableLinkDollRotation; ConfigVar enableAchievementToasts; diff --git a/src/d/d_meter2_draw.cpp b/src/d/d_meter2_draw.cpp index 3c342ea7d4..900367d7f3 100644 --- a/src/d/d_meter2_draw.cpp +++ b/src/d/d_meter2_draw.cpp @@ -23,6 +23,39 @@ #include "dusk/frame_interpolation.h" #include +#if TARGET_PC +#include "dusk/settings.h" +#include + +namespace { + +// Reads the user HUD scale setting, clamped to a safe range. +f32 dGetUserHudScale() { + return std::clamp(dusk::getSettings().game.hudScale.getValue(), 0.5f, 2.0f); +} + +// The screen corner each HUD group is anchored to. A pane scales around its own origin, +// so without correction it drifts away from the screen edge; this names the corner that +// must stay put. +enum class HudCorner { TopLeft, TopRight, BottomLeft, BottomRight }; + +// Adds the paneTrans offset that keeps i_corner pinned in place while the user HUD scale +// grows or shrinks the pane. The shift is half the change in size pushed toward the +// anchor corner, so it depends only on the pane's size (not its on-screen position) and +// works whether the HUD is scaled down or up. i_pull < 1 applies a partial horizontal +// push for a pane whose content sits inset from its box edge (the heart row). +void dAnchorHudScale(CPaneMgr* i_pane, HudCorner i_corner, f32* io_x, f32* io_y, f32 i_pull = 1.0f) { + const f32 half = (1.0f - dGetUserHudScale()) * 0.5f; + const f32 dirX = + (i_corner == HudCorner::TopRight || i_corner == HudCorner::BottomRight) ? 1.0f : -1.0f; + const f32 dirY = + (i_corner == HudCorner::BottomLeft || i_corner == HudCorner::BottomRight) ? 1.0f : -1.0f; + *io_x += dirX * i_pane->getInitSizeX() * half * i_pull; + *io_y += dirY * i_pane->getInitSizeY() * half; +} + +} // namespace +#endif dMeter2Draw_c::dMeter2Draw_c(JKRExpHeap* mp_heap) { OS_REPORT("enter dMeter2Draw_c::dMeter2Draw_c(JKRExpHeap *mp_heap)\n"); @@ -536,6 +569,12 @@ void dMeter2Draw_c::init() { } void dMeter2Draw_c::exec(u32 i_status) { +#if TARGET_PC + // n_all keeps the vanilla scale. Scaling the root pane shrinks every child toward + // its centred origin; per-child scaling in each drawXxx() path keeps each HUD group + // anchored to its own pane origin and also pulls it toward the screen corner. + const f32 userHudScale = dGetUserHudScale(); +#endif if (mParentScale != g_drawHIO.mParentScale) { mParentScale = g_drawHIO.mParentScale; mpParent->scale(g_drawHIO.mParentScale, g_drawHIO.mParentScale); @@ -546,6 +585,39 @@ void dMeter2Draw_c::exec(u32 i_status) { mpParent->setAlphaRate(g_drawHIO.mParentAlpha); } +#if TARGET_PC + if (i_status & 0x1000000) { + f32 ringPosX = g_drawHIO.mRingHUDButtonsPosX; + f32 ringPosY = g_drawHIO.mRingHUDButtonsPosY; + dAnchorHudScale(mpButtonParent, HudCorner::TopRight, &ringPosX, &ringPosY); + if (mButtonsPosX != ringPosX || mButtonsPosY != ringPosY) { + mButtonsPosX = ringPosX; + mButtonsPosY = ringPosY; + mpButtonParent->paneTrans(ringPosX, ringPosY); + } + + const f32 ringButtonsScale = g_drawHIO.mRingHUDButtonsScale * userHudScale; + if (mButtonsScale != ringButtonsScale) { + mButtonsScale = ringButtonsScale; + mpButtonParent->scale(ringButtonsScale, ringButtonsScale); + } + } else { + f32 mainPosX = g_drawHIO.mMainHUDButtonsPosX; + f32 mainPosY = g_drawHIO.mMainHUDButtonsPosY; + dAnchorHudScale(mpButtonParent, HudCorner::TopRight, &mainPosX, &mainPosY); + if (mButtonsPosX != mainPosX || mButtonsPosY != mainPosY) { + mButtonsPosX = mainPosX; + mButtonsPosY = mainPosY; + mpButtonParent->paneTrans(mainPosX, mainPosY); + } + + const f32 mainButtonsScale = g_drawHIO.mMainHUDButtonsScale * userHudScale; + if (mButtonsScale != mainButtonsScale) { + mButtonsScale = mainButtonsScale; + mpButtonParent->scale(mainButtonsScale, mainButtonsScale); + } + } +#else if (i_status & 0x1000000) { if (mButtonsPosX != g_drawHIO.mRingHUDButtonsPosX || mButtonsPosY != g_drawHIO.mRingHUDButtonsPosY) @@ -574,6 +646,7 @@ void dMeter2Draw_c::exec(u32 i_status) { mpButtonParent->scale(g_drawHIO.mMainHUDButtonsScale, g_drawHIO.mMainHUDButtonsScale); } } +#endif } void dMeter2Draw_c::draw() { @@ -588,6 +661,9 @@ void dMeter2Draw_c::draw() { if (mpItemXY[i] != NULL) { for (int j = 0; j < 3; j++) { f32 temp_f30 = mItemParams[i].num_scale * 16.0f; +#if TARGET_PC + temp_f30 *= dGetUserHudScale(); +#endif Vec vtx0 = mpItemXY[i]->getPanePtr()->getGlbVtx(0); Vec vtx3 = mpItemXY[i]->getPanePtr()->getGlbVtx(3); @@ -1478,7 +1554,12 @@ void dMeter2Draw_c::drawLife(s16 i_maxLife, s16 i_life, f32 i_posX, f32 i_posY) } } +#if TARGET_PC + const f32 lifeParentScale = g_drawHIO.mLifeParentScale * dGetUserHudScale(); + mpLifeParent->scale(lifeParentScale, lifeParentScale); +#else mpLifeParent->scale(g_drawHIO.mLifeParentScale, g_drawHIO.mLifeParentScale); +#endif for (int i = 0; i < 20; i++) { mpHeartMark[i]->scale(g_drawHIO.mHeartMarkScale, g_drawHIO.mHeartMarkScale); @@ -1488,7 +1569,16 @@ void dMeter2Draw_c::drawLife(s16 i_maxLife, s16 i_life, f32 i_posX, f32 i_posY) mpBigHeart->scale(g_drawHIO.mBigHeartScale, g_drawHIO.mBigHeartScale); } +#if TARGET_PC + f32 lifePosX = i_posX; + f32 lifePosY = i_posY; + // The heart row sits inset from its box's left edge, so use a partial horizontal pull + // to keep it from jamming against the screen edge. + dAnchorHudScale(mpLifeParent, HudCorner::TopLeft, &lifePosX, &lifePosY, 0.6f); + mpLifeParent->paneTrans(lifePosX, lifePosY); +#else mpLifeParent->paneTrans(i_posX, i_posY); +#endif } void dMeter2Draw_c::setAlphaLifeChange(bool param_0) { @@ -1601,9 +1691,22 @@ void dMeter2Draw_c::drawKanteraScreen(u8 i_meterType) { mpMagicMeter->resize(field_0x584[i_meterType], field_0x590[i_meterType]); mpMagicFrameR->move(field_0x59c[i_meterType], field_0x5a8[i_meterType]); mpMagicBase->resize(field_0x5b4[i_meterType], field_0x5c0[i_meterType]); +#if TARGET_PC + const f32 magicUserScale = dGetUserHudScale(); + mpMagicParent->scale(field_0x5cc[i_meterType] * magicUserScale, + field_0x5d8[i_meterType] * magicUserScale); + + f32 magicPosX = field_0x5e4[i_meterType]; + f32 magicPosY = field_0x5f0[i_meterType]; + // The oil/magic bar sits inset within its pane box, so use a reduced horizontal pull + // (like the heart row) to keep it from overshooting off the left edge when shrunk. + dAnchorHudScale(mpMagicParent, HudCorner::TopLeft, &magicPosX, &magicPosY, 0.3f); + mpMagicParent->paneTrans(magicPosX, magicPosY); +#else mpMagicParent->scale(field_0x5cc[i_meterType], field_0x5d8[i_meterType]); mpMagicParent->paneTrans(field_0x5e4[i_meterType], field_0x5f0[i_meterType]); +#endif mpKanteraScreen->draw(0.0f, 0.0f, graf_ctx); } @@ -1867,10 +1970,21 @@ void dMeter2Draw_c::drawLightDrop(u8 i_num, u8 i_needNum, f32 i_posX, f32 i_posY field_0x6fc = param_5; mLightDropVesselScale = i_vesselScale; +#if TARGET_PC + const f32 lightDropUserScale = dGetUserHudScale(); + const f32 lightDropScale = mLightDropVesselScale * field_0x6f8 * lightDropUserScale; + mpLightDropParent->scale(lightDropScale, lightDropScale); + + f32 lightDropPosX = i_posX; + f32 lightDropPosY = i_posY; + dAnchorHudScale(mpLightDropParent, HudCorner::TopRight, &lightDropPosX, &lightDropPosY); + mpLightDropParent->paneTrans(lightDropPosX, lightDropPosY); +#else mpLightDropParent->scale(mLightDropVesselScale * field_0x6f8, mLightDropVesselScale * field_0x6f8); mpLightDropParent->paneTrans(i_posX, i_posY); +#endif } void dMeter2Draw_c::setAlphaLightDropChange(bool unused) {} @@ -1943,8 +2057,13 @@ void dMeter2Draw_c::setAlphaLightDropAnimeMax() { field_0x6f8 = 1.0f; } +#if TARGET_PC + const f32 dropAnimScale = mLightDropVesselScale * field_0x6f8 * dGetUserHudScale(); + mpLightDropParent->scale(dropAnimScale, dropAnimScale); +#else mpLightDropParent->scale(mLightDropVesselScale * field_0x6f8, mLightDropVesselScale * field_0x6f8); +#endif if (g_drawHIO.mLightDrop.mDropGetScaleAnimFrameNum == mpLightDropParent->getAlphaTimer()) { dMeter2Info_setLightDropGetFlag(dComIfGp_getStartStageDarkArea(), 0xFF); @@ -2015,10 +2134,22 @@ void dMeter2Draw_c::drawRupee(s16 i_rupeeNum) { static_cast(mpRupeeTexture[0][0]->getPanePtr())->changeTexture(timg, 0); static_cast(mpRupeeTexture[0][1]->getPanePtr())->changeTexture(timg, 0); +#if TARGET_PC + const f32 rupeeKeyUserScale = dGetUserHudScale(); + const f32 rupeeKeyScale = g_drawHIO.mRupeeKeyScale * field_0x718 * rupeeKeyUserScale; + mpRupeeKeyParent->scale(rupeeKeyScale, rupeeKeyScale); + + f32 rupeeKeyPosX = g_drawHIO.mRupeeKeyPosX; + f32 rupeeKeyPosY = g_drawHIO.mRupeeKeyPosY; + // Rupees/keys read better anchored to the bottom-right corner than the top-right. + dAnchorHudScale(mpRupeeKeyParent, HudCorner::BottomRight, &rupeeKeyPosX, &rupeeKeyPosY); + mpRupeeKeyParent->paneTrans(rupeeKeyPosX, rupeeKeyPosY); +#else mpRupeeKeyParent->scale(g_drawHIO.mRupeeKeyScale * field_0x718, g_drawHIO.mRupeeKeyScale * field_0x718); mpRupeeKeyParent->paneTrans(g_drawHIO.mRupeeKeyPosX, g_drawHIO.mRupeeKeyPosY); +#endif mpRupeeParent[0]->scale(g_drawHIO.mRupeeScale, g_drawHIO.mRupeeScale); mpRupeeParent[0]->paneTrans(g_drawHIO.mRupeePosX, g_drawHIO.mRupeePosY); @@ -2137,8 +2268,18 @@ void dMeter2Draw_c::drawKey(s16 i_keyNum) { } } +#if TARGET_PC + const f32 keyScale = g_drawHIO.mKeyScale * dGetUserHudScale(); + mpKeyParent->scale(keyScale, keyScale); + + f32 keyPosX = g_drawHIO.mKeyPosX; + f32 keyPosY = g_drawHIO.mKeyPosY; + dAnchorHudScale(mpKeyParent, HudCorner::BottomRight, &keyPosX, &keyPosY); + mpKeyParent->paneTrans(keyPosX, keyPosY); +#else mpKeyParent->scale(g_drawHIO.mKeyScale, g_drawHIO.mKeyScale); mpKeyParent->paneTrans(g_drawHIO.mKeyPosX, g_drawHIO.mKeyPosY); +#endif } void dMeter2Draw_c::setAlphaKeyChange(bool param_0) { @@ -2596,11 +2737,24 @@ f32 dMeter2Draw_c::getButtonCrossParentInitTransY() { } void dMeter2Draw_c::drawButtonCross(f32 i_posX, f32 i_posY) { +#if TARGET_PC + const f32 buttonCrossUserScale = dGetUserHudScale(); + const f32 buttonCrossScale = g_drawHIO.mButtonCrossScale * buttonCrossUserScale; + mpButtonCrossParent->scale(buttonCrossScale, buttonCrossScale); +#else mpButtonCrossParent->scale(g_drawHIO.mButtonCrossScale, g_drawHIO.mButtonCrossScale); +#endif mpTextI->scale(g_drawHIO.mButtonCrossTextScale, g_drawHIO.mButtonCrossTextScale); mpTextM->scale(g_drawHIO.mButtonCrossTextScale, g_drawHIO.mButtonCrossTextScale); +#if TARGET_PC + f32 buttonCrossPosX = i_posX; + f32 buttonCrossPosY = i_posY; + dAnchorHudScale(mpButtonCrossParent, HudCorner::TopLeft, &buttonCrossPosX, &buttonCrossPosY); + mpButtonCrossParent->paneTrans(buttonCrossPosX, buttonCrossPosY); +#else mpButtonCrossParent->paneTrans(i_posX, i_posY); +#endif } void dMeter2Draw_c::setAlphaButtonCrossAnimeMin() { @@ -3505,9 +3659,16 @@ void dMeter2Draw_c::drawKanteraMeter(u8 i_button, f32 i_alphaRate) { Vec vtx0 = pane->getPanePtr()->getGlbVtx(0); Vec vtx3 = pane->getPanePtr()->getGlbVtx(3); +#if TARGET_PC + const f32 oilUserScale = dGetUserHudScale(); + mpKanteraMeter[i_button]->setPos(((vtx0.x + vtx3.x) * 0.5f) + 9.0f * oilUserScale + sp10[i_button], + vtx3.y + sp8[i_button]); + mpKanteraMeter[i_button]->setScale(0.6f * oilUserScale, 0.6f * oilUserScale); +#else mpKanteraMeter[i_button]->setPos(((vtx0.x + vtx3.x) * 0.5f) + 9.0f + sp10[i_button], vtx3.y + sp8[i_button]); mpKanteraMeter[i_button]->setScale(0.6f, 0.6f); +#endif mpKanteraMeter[i_button]->setNowGauge(dComIfGs_getMaxOil(), dComIfGs_getOil()); mpKanteraMeter[i_button]->setAlphaRate(i_alphaRate); } diff --git a/src/d/d_meter_map.cpp b/src/d/d_meter_map.cpp index 419235c970..c03214b963 100644 --- a/src/d/d_meter_map.cpp +++ b/src/d/d_meter_map.cpp @@ -16,6 +16,10 @@ #include "f_op/f_op_overlap_mng.h" #include "m_Do/m_Do_controller_pad.h" #include "d/d_camera.h" +#if TARGET_PC +#include "dusk/settings.h" +#include +#endif #include #if (PLATFORM_WII || PLATFORM_SHIELD) @@ -621,8 +625,16 @@ void dMeterMap_c::draw() { mMapJ2DPicture->setAlpha(alpha); #if TARGET_PC - mMapJ2DPicture->draw(mDoGph_gInf_c::ScaleHUDXLeft(drawPosX), drawPosY, sizeX, sizeY, false, - false, false); + // Scale the minimap with the user HUD scale and shift down so its bottom-left + // corner stays anchored to the same screen position as at scale 1.0. + const f32 userHudScale = + std::clamp(dusk::getSettings().game.hudScale.getValue(), 0.5f, 2.0f); + const f32 scaledSizeX = sizeX * userHudScale; + const f32 scaledSizeY = sizeY * userHudScale; + const f32 mapBottomShift = sizeY - scaledSizeY; + mMapJ2DPicture->draw(mDoGph_gInf_c::ScaleHUDXLeft(drawPosX), + drawPosY + mapBottomShift, scaledSizeX, scaledSizeY, + false, false, false); #else mMapJ2DPicture->draw(drawPosX, drawPosY, sizeX, sizeY, false, false, false); #endif diff --git a/src/dusk/settings.cpp b/src/dusk/settings.cpp index 402001a1dc..7a34264f48 100644 --- a/src/dusk/settings.cpp +++ b/src/dusk/settings.cpp @@ -51,6 +51,7 @@ UserSettings g_userSettings = { // Preferences .enableMirrorMode {"game.enableMirrorMode", false}, .minimalHUD {"game.minimalHUD", false}, + .hudScale {"game.hudScale", 1.0f}, .pauseOnFocusLost {"game.pauseOnFocusLost", false}, .enableLinkDollRotation {"game.enableLinkDollRotation", false}, .enableAchievementToasts {"game.enableAchievementToasts", true}, @@ -240,6 +241,7 @@ void registerSettings() { Register(g_userSettings.game.freeCameraXSensitivity); Register(g_userSettings.game.freeCameraYSensitivity); Register(g_userSettings.game.minimalHUD); + Register(g_userSettings.game.hudScale); Register(g_userSettings.game.pauseOnFocusLost); Register(g_userSettings.game.enableDiscordPresence); Register(g_userSettings.game.bloomMode); diff --git a/src/dusk/ui/settings.cpp b/src/dusk/ui/settings.cpp index b11875f48c..d65cab3370 100644 --- a/src/dusk/ui/settings.cpp +++ b/src/dusk/ui/settings.cpp @@ -1113,6 +1113,11 @@ SettingsWindow::SettingsWindow(bool prelaunch) : mPrelaunch(prelaunch) { addOption("Minimal HUD", getSettings().game.minimalHUD, "Disables the elements of the main HUD of the game.
Useful for a more immersive " "experience."); + config_percent_select(leftPane, rightPane, getSettings().game.hudScale, + "HUD Scale", + "Scales the size of the gameplay HUD (hearts, buttons, mini-map, etc.). Does not affect dialog boxes or menus.", + 50, 200, 5, + [] { return getSettings().game.minimalHUD.getValue(); }); addOption("Restore Wii 1.0 Glitches", getSettings().game.restoreWiiGlitches, "Restores patched glitches from Wii USA 1.0, the first released version."); addOption("Enable Rotating Link Doll", getSettings().game.enableLinkDollRotation, From d9d9966f8f325472ee486235401b4fd366a25643 Mon Sep 17 00:00:00 2001 From: MelonSpeedruns Date: Fri, 5 Jun 2026 02:17:06 -0400 Subject: [PATCH 11/12] Time Freezing Camera (#1787) Thanks to @bkd89 for the idea! Co-authored-by: MelonSpeedruns --- src/dusk/imgui/ImGuiCameraOverlay.cpp | 4 ++-- src/f_op/f_op_camera.cpp | 11 +++++++++++ 2 files changed, 13 insertions(+), 2 deletions(-) diff --git a/src/dusk/imgui/ImGuiCameraOverlay.cpp b/src/dusk/imgui/ImGuiCameraOverlay.cpp index 0d2168924c..8963b88ede 100644 --- a/src/dusk/imgui/ImGuiCameraOverlay.cpp +++ b/src/dusk/imgui/ImGuiCameraOverlay.cpp @@ -75,12 +75,12 @@ namespace dusk { if (!getSettings().game.debugFlyCam) { ImGui::BeginDisabled(); } - config::ImGuiCheckbox("Lock Events", getSettings().game.debugFlyCamLockEvents); + config::ImGuiCheckbox("Freeze Time", getSettings().game.debugFlyCamLockEvents); if (ImGui::IsItemHovered(ImGuiHoveredFlags_AllowWhenDisabled)) { if (!getSettings().game.debugFlyCam) { ImGui::SetTooltip("Enable Fly Mode first."); } else { - ImGui::SetTooltip("Freeze game events while flying."); + ImGui::SetTooltip("Freezes the game while flying."); } } if (!getSettings().game.debugFlyCam) { diff --git a/src/f_op/f_op_camera.cpp b/src/f_op/f_op_camera.cpp index 48045aeae7..567f80904f 100644 --- a/src/f_op/f_op_camera.cpp +++ b/src/f_op/f_op_camera.cpp @@ -36,9 +36,20 @@ static int fopCam_Execute(camera_class* i_this) { fapGm_HIO_c::startCpuTimer(); #endif + #if TARGET_PC + if (dusk::getSettings().game.debugFlyCam && dusk::getSettings().game.debugFlyCamLockEvents) { + dScnPly_c::setPauseTimer(1); + ret = fpcMtd_Execute((process_method_class*)i_this->submethod, i_this); + } else { + if (!dComIfGp_isPauseFlag() && !dScnPly_c::isPause()) { + ret = fpcMtd_Execute((process_method_class*)i_this->submethod, i_this); + } + } + #else if (!dComIfGp_isPauseFlag() && !dScnPly_c::isPause()) { ret = fpcMtd_Execute((process_method_class*)i_this->submethod, i_this); } + #endif #if DEBUG fapGm_HIO_c::stopCpuTimer("カメラ(計算処理)"); // Camera (computational processing) From 7a900471bf257dbe0d73d8683554e0a8876e6ae2 Mon Sep 17 00:00:00 2001 From: doop <56421834+dooplecks@users.noreply.github.com> Date: Fri, 5 Jun 2026 17:05:54 -0400 Subject: [PATCH 12/12] Clamp flycam FOV (#1996) --- src/dusk/imgui/ImGuiCameraOverlay.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/dusk/imgui/ImGuiCameraOverlay.cpp b/src/dusk/imgui/ImGuiCameraOverlay.cpp index 8963b88ede..320c41ec92 100644 --- a/src/dusk/imgui/ImGuiCameraOverlay.cpp +++ b/src/dusk/imgui/ImGuiCameraOverlay.cpp @@ -49,7 +49,9 @@ namespace dusk { dCam->Reset(center, eye); } - ImGui::InputFloat("Camera FOV", &dCam->mFovy); + if (ImGui::InputFloat("Camera FOV", &dCam->mFovy)) { + dCam->mFovy = std::clamp(dCam->mFovy, 0.1f, 179.9f); + } ImGui::SeparatorText("Options");