From a3a36508d6e74d2305d507ddd8a64e554a6a3428 Mon Sep 17 00:00:00 2001 From: "Jasper St. Pierre" Date: Mon, 13 Apr 2026 11:26:50 -0700 Subject: [PATCH 1/4] bloom2 work --- src/m_Do/m_Do_graphic.cpp | 33 +++++++++++++++++---------------- 1 file changed, 17 insertions(+), 16 deletions(-) diff --git a/src/m_Do/m_Do_graphic.cpp b/src/m_Do/m_Do_graphic.cpp index 8c6b5e7c45..84a839fd4a 100644 --- a/src/m_Do/m_Do_graphic.cpp +++ b/src/m_Do/m_Do_graphic.cpp @@ -1409,18 +1409,16 @@ void mDoGph_gInf_c::bloom_c::draw2() { // Setup blur filter TEV. GXSetNumTexGens(8); - auto SetupBlurMtx = [](float scale) { u32 texMtxID = GX_TEXMTX0; int angle = 0; for (int texCoord = (int)GX_TEXCOORD0; texCoord < (int)GX_MAX_TEXCOORD; texCoord++) { GXSetTexCoordGen((GXTexCoordID)texCoord, GX_TG_MTX2x4, GX_TG_TEX0, texMtxID); - mDoMtx_stack_c::transS((scale * cM_scos(angle)) * getInvScale(), - scale * cM_ssin(angle), 0.0f); + mDoMtx_stack_c::transS((blurScale * cM_scos(angle)) * getInvScale(), + blurScale * cM_ssin(angle), 0.0f); GXLoadTexMtxImm(mDoMtx_stack_c::get(), texMtxID, GX_MTX2x4); texMtxID += 3; angle += 0x2000; } - }; GXSetNumTevStages(8); for (int stage = 0; stage < 8; stage++) { @@ -1433,19 +1431,20 @@ void mDoGph_gInf_c::bloom_c::draw2() { } // Successively downsample and apply blurs. - int divStart = 2; - int divNum = 6; + static int divStart = 2; + static int divNum = 6; // inclusive - // Distribute the brightness through each pass. - int totalNumPasses = (divNum - divStart) * 2; // down, up - float brightnessF32 = (mBlureRatio / 255.0f); + // The original mBlureRatio is multiplied into each sample, of which there are 8 samples originally. + // This is applied over two passes, the second one with an alpha of 25%; however, the clipping that this introduces is a bit integral to the look, + // so we do the same thing, letting it clip. + float brightnessF32 = (mBlureRatio * 16 / 255.0f); + + // Distribute the brightness through the total number of passes. + f32 totalNumPasses = (divNum - divStart + 1); float brightnessPerPass = 255.0f * powf(brightnessF32, 1.0f / totalNumPasses); GXSetTevColorS10(GX_TEVREG1, {0, 0, 0, s16(brightnessPerPass / 8)}); for (int i = divStart; i < divNum; i++) { - float blurStrength = 1.0f + (i - divStart) * 5.0f; - SetupBlurMtx(blurScale * blurStrength); - // Apply blur filter. divQuad(i); @@ -1457,16 +1456,18 @@ void mDoGph_gInf_c::bloom_c::draw2() { GXLoadTexObj(blurTex, GX_TEXMAP0); } - // All the way down at the bottom. Instead of blurring the bottom layer by itself, we blur when going up to the next layer. - // The remaining upscales are all just normal alpha blending. + // All the way down at the bottom. divQuad(divNum); // Now successively alpha blend back up, don't blur anymore. GXSetNumTevStages(1); - GXSetTevColorS10(GX_TEVREG1, {0, 0, 0, s16(brightnessPerPass)}); + GXSetTevColorS10(GX_TEVREG1, {0, 0, 0, s16(255)}); GXSetTexCoordGen(GX_TEXCOORD0, GX_TG_MTX2x4, GX_TG_TEX0, GX_IDENTITY); - GXSetBlendMode(GX_BM_BLEND, GX_BL_ONE, GX_BL_ONE, GX_LO_OR); + GXSetBlendMode(GX_BM_BLEND, GX_BL_SRCALPHA, GX_BL_ONE, GX_LO_OR); for (int i = divNum; i > divStart; i--) { + float alpha = 255.0f * powf(0.25f, 1.0f / (divNum - i + 1)); + GXSetTevColorS10(GX_TEVREG0, {0, 0, 0, s16(alpha)}); + divCopySrc(i); GXTexObj* upTex = divCopyTex(BlurPass0 + i, i); GXLoadTexObj(upTex, GX_TEXMAP0); From b3cc9ba02e370f71700a7866a4e57e19f6cb59f1 Mon Sep 17 00:00:00 2001 From: Luke Street Date: Mon, 13 Apr 2026 13:06:14 -0600 Subject: [PATCH 2/4] Add "Bloom Mode" config option (Off, Classic, Dusk) --- include/dusk/config_var.hpp | 11 +++++++ include/dusk/settings.h | 16 +++++++++- src/dusk/config.cpp | 41 +++++++++++++++++++++++++- src/dusk/imgui/ImGuiFirstRunPreset.cpp | 3 ++ src/dusk/imgui/ImGuiMenuGame.cpp | 16 +++++++++- src/dusk/settings.cpp | 4 +-- src/m_Do/m_Do_graphic.cpp | 8 ++--- 7 files changed, 89 insertions(+), 10 deletions(-) diff --git a/include/dusk/config_var.hpp b/include/dusk/config_var.hpp index 6b78ee2e33..a480887fb2 100644 --- a/include/dusk/config_var.hpp +++ b/include/dusk/config_var.hpp @@ -146,6 +146,12 @@ concept ConfigValue = template const ConfigImplBase* GetConfigImpl(); +template +struct ConfigEnumRange { + static constexpr auto min = std::numeric_limits>::min(); + static constexpr auto max = std::numeric_limits>::max(); +}; + /** * \brief A CVar storing values. * @@ -189,6 +195,11 @@ public: } } + [[nodiscard]] constexpr const T& getDefaultValue() const noexcept { + checkRegistered(); + return defaultValue; + } + /** * \brief Change the value of a CVar. * diff --git a/include/dusk/settings.h b/include/dusk/settings.h index 68ea49c752..3ec1bac876 100644 --- a/include/dusk/settings.h +++ b/include/dusk/settings.h @@ -7,6 +7,20 @@ namespace dusk { using namespace config; +enum class BloomMode : int { + Off = 0, + Classic = 1, + Dusk = 2, +}; + +namespace config { +template <> +struct ConfigEnumRange { + static constexpr auto min = BloomMode::Off; + static constexpr auto max = BloomMode::Dusk; +}; +} + // Persistent user settings struct UserSettings { @@ -53,7 +67,7 @@ struct UserSettings { ConfigVar invertCameraXAxis; // Graphics - ConfigVar enableBloom; + ConfigVar bloomMode; ConfigVar enableWaterRefraction; ConfigVar enableFrameInterpolation; ConfigVar shadowResolutionMultiplier; diff --git a/src/dusk/config.cpp b/src/dusk/config.cpp index 98f32cf8cd..c377d1ba54 100644 --- a/src/dusk/config.cpp +++ b/src/dusk/config.cpp @@ -5,6 +5,7 @@ #include "aurora/lib/logging.hpp" #include "dusk/io.hpp" +#include "dusk/settings.h" #include #include @@ -37,9 +38,24 @@ const ConfigImplBase* ConfigVarBase::getImpl() const noexcept { return impl; } +template +static T sanitizeEnumValue(const ConfigVar& cVar, T value) { + if constexpr (std::is_enum_v) { + using Underlying = std::underlying_type_t; + const Underlying raw = static_cast(value); + const Underlying min = static_cast(ConfigEnumRange::min); + const Underlying max = static_cast(ConfigEnumRange::max); + if (raw < min || raw > max) { + return cVar.getDefaultValue(); + } + } + + return value; +} + template void ConfigImpl::loadFromJson(ConfigVar& cVar, const json& jsonValue) { - cVar.setValue(jsonValue.get(), false); + cVar.setValue(sanitizeEnumValue(cVar, jsonValue.get()), false); } template @@ -85,6 +101,28 @@ static void loadFromArgImpl(ConfigVar& cVar, const std::string_view cVar.setOverrideValue(std::string(stringValue)); } +template requires std::is_enum_v +static void loadFromArgImpl(ConfigVar& cVar, const std::string_view stringValue) { + using Underlying = std::underlying_type_t; + const std::string str(stringValue); + + if constexpr (std::is_signed_v) { + const auto result = std::stoll(str); + if (result >= std::numeric_limits::min() && result <= std::numeric_limits::max()) { + cVar.setOverrideValue(sanitizeEnumValue(cVar, static_cast(result))); + } else { + throw std::out_of_range("Value is too large"); + } + } else { + const auto result = std::stoull(str); + if (result <= std::numeric_limits::max()) { + cVar.setOverrideValue(sanitizeEnumValue(cVar, static_cast(result))); + } else { + throw std::out_of_range("Value is too large"); + } + } +} + template void ConfigImpl::loadFromArg(ConfigVar& cVar, const std::string_view stringValue) { loadFromArgImpl(cVar, stringValue); @@ -115,6 +153,7 @@ namespace dusk::config { template class ConfigImpl; template class ConfigImpl; template class ConfigImpl; + template class ConfigImpl; } void dusk::config::Register(ConfigVarBase& configVar) { diff --git a/src/dusk/imgui/ImGuiFirstRunPreset.cpp b/src/dusk/imgui/ImGuiFirstRunPreset.cpp index b73eaff6e6..99c77dac70 100644 --- a/src/dusk/imgui/ImGuiFirstRunPreset.cpp +++ b/src/dusk/imgui/ImGuiFirstRunPreset.cpp @@ -12,11 +12,13 @@ namespace dusk { static void ApplyPresetClassic() { auto& s = getSettings(); s.video.lockAspectRatio.setValue(true); + s.game.bloomMode.setValue(BloomMode::Classic); VILockAspectRatio(defaultAspectRatioW, defaultAspectRatioH); } static void ApplyPresetHD() { auto& s = getSettings(); + s.game.bloomMode.setValue(BloomMode::Classic); s.game.hideTvSettingsScreen.setValue(true); s.game.skipWarningScreen.setValue(true); s.game.noReturnRupees.setValue(true); @@ -37,6 +39,7 @@ static void ApplyPresetDusk() { s.game.instantSaves.setValue(true); s.game.midnasLamentNonStop.setValue(true); s.game.enableFrameInterpolation.setValue(true); + s.game.bloomMode.setValue(BloomMode::Dusk); } // ========================================================================= diff --git a/src/dusk/imgui/ImGuiMenuGame.cpp b/src/dusk/imgui/ImGuiMenuGame.cpp index d2b893c884..3d658a5d10 100644 --- a/src/dusk/imgui/ImGuiMenuGame.cpp +++ b/src/dusk/imgui/ImGuiMenuGame.cpp @@ -62,7 +62,21 @@ namespace dusk { config::Save(); } - config::ImGuiCheckbox("Native Bloom", getSettings().game.enableBloom); + constexpr const char* bloomModeNames[] = {"Off", "Classic", "Dusk"}; + int bloomMode = static_cast(getSettings().game.bloomMode.getValue()); + if (ImGui::BeginCombo("Bloom", bloomModeNames[bloomMode])) { + for (int i = 0; i < IM_ARRAYSIZE(bloomModeNames); i++) { + const bool selected = bloomMode == i; + if (ImGui::Selectable(bloomModeNames[i], selected)) { + getSettings().game.bloomMode.setValue(static_cast(i)); + config::Save(); + } + if (selected) { + ImGui::SetItemDefaultFocus(); + } + } + ImGui::EndCombo(); + } config::ImGuiCheckbox("Enable Water Refraction", getSettings().game.enableWaterRefraction); diff --git a/src/dusk/settings.cpp b/src/dusk/settings.cpp index 9d3d873acc..fbac3fc6d3 100644 --- a/src/dusk/settings.cpp +++ b/src/dusk/settings.cpp @@ -41,7 +41,7 @@ UserSettings g_userSettings = { .invertCameraXAxis {"game.invertCameraXAxis", false}, // Graphics - .enableBloom {"game.enableBloom", true}, + .bloomMode {"game.bloomMode", BloomMode::Classic}, .enableWaterRefraction {"game.enableWaterRefraction", true}, .enableFrameInterpolation = {"game.enableFrameInterpolation", false}, .shadowResolutionMultiplier {"game.shadowResolutionMultiplier", 1}, @@ -113,7 +113,7 @@ void registerSettings() { Register(g_userSettings.game.instantSaves); Register(g_userSettings.game.enableMirrorMode); Register(g_userSettings.game.invertCameraXAxis); - Register(g_userSettings.game.enableBloom); + Register(g_userSettings.game.bloomMode); Register(g_userSettings.game.enableWaterRefraction); Register(g_userSettings.game.shadowResolutionMultiplier); Register(g_userSettings.game.enableFastIronBoots); diff --git a/src/m_Do/m_Do_graphic.cpp b/src/m_Do/m_Do_graphic.cpp index 84a839fd4a..144ebd5873 100644 --- a/src/m_Do/m_Do_graphic.cpp +++ b/src/m_Do/m_Do_graphic.cpp @@ -1501,13 +1501,11 @@ void mDoGph_gInf_c::bloom_c::draw2() { void mDoGph_gInf_c::bloom_c::draw() { ZoneScoped; - if (!dusk::getSettings().game.enableBloom) { + if (dusk::getSettings().game.bloomMode.getValue() == dusk::BloomMode::Dusk) { + draw2(); return; } - - static bool s_bloom2 = false; - if (s_bloom2) { - draw2(); + if (dusk::getSettings().game.bloomMode.getValue() != dusk::BloomMode::Classic) { return; } From fba3114d4fff78d9c2b17fbd4ccbeda29f9318ab Mon Sep 17 00:00:00 2001 From: Luke Street Date: Mon, 13 Apr 2026 14:00:32 -0600 Subject: [PATCH 3/4] bloom2: Ensure draws/copies are pixel aligned --- src/m_Do/m_Do_graphic.cpp | 33 ++++++++++++++++++++++++++------- 1 file changed, 26 insertions(+), 7 deletions(-) diff --git a/src/m_Do/m_Do_graphic.cpp b/src/m_Do/m_Do_graphic.cpp index 144ebd5873..bb6746e977 100644 --- a/src/m_Do/m_Do_graphic.cpp +++ b/src/m_Do/m_Do_graphic.cpp @@ -1311,6 +1311,12 @@ void mDoGph_gInf_c::bloom_c::draw2() { BlurPass0, BlurPassN = BlurPass0 + MaxDivNum, MaxTexNum, }; + struct bloom_rect { + u16 x; + u16 y; + u16 w; + u16 h; + }; scissor_class divPorts[MaxDivNum]; divPorts[0] = {0.0f, 0.0f, 1.0f, 1.0f}; // full-size texture divPorts[1] = {0.0f, 0.0f, 0.5f, 0.5f}; // bloom texture (wide enough, half-tall) @@ -1324,22 +1330,35 @@ void mDoGph_gInf_c::bloom_c::draw2() { port.height = prev.height * 0.5f; } + bloom_rect divRects[MaxDivNum]; + for (int i = 0; i < ARRAY_SIZE(divPorts); i++) { + auto const& port = divPorts[i]; + divRects[i] = { + static_cast(port.x_orig * width), + static_cast(port.y_orig * height), + static_cast(port.width * width), + static_cast(port.height * height), + }; + } + auto divCopySrc = [&](int divNo) { - auto const& port = divPorts[divNo]; - GXSetTexCopySrc(port.x_orig * width, port.y_orig * height, port.width * width, port.height * height); + auto const& rect = divRects[divNo]; + GXSetTexCopySrc(rect.x, rect.y, rect.w, rect.h); }; TGXTexObj tmpTex[MaxTexNum]; auto divCopyTex = [&](uintptr_t texNo, int divNo) -> GXTexObj * { - auto const& port = divPorts[divNo]; - CopyToTexObj(&tmpTex[texNo], texNo, port.width * width, port.height * height); + auto const& rect = divRects[divNo]; + CopyToTexObj(&tmpTex[texNo], texNo, rect.w, rect.h); return &tmpTex[texNo]; }; auto divQuad = [&](int divNo) { - auto const& port = divPorts[divNo]; - f32 x0 = port.x_orig, y0 = port.y_orig; - f32 x1 = x0 + port.width, y1 = y0 + port.height; + auto const& rect = divRects[divNo]; + f32 x0 = rect.x / width; + f32 y0 = rect.y / height; + f32 x1 = (rect.x + rect.w) / width; + f32 y1 = (rect.y + rect.h) / height; GXBegin(GX_QUADS, GX_VTXFMT0, 4); GXPosition3f32(x0, y0, -5); GXTexCoord2s8(0, 0); From 49215dbc7ba4303c76c9add0fb4a411479b212ab Mon Sep 17 00:00:00 2001 From: Luke Street Date: Mon, 13 Apr 2026 14:14:19 -0600 Subject: [PATCH 4/4] bloom2: Rework divRects calc --- src/m_Do/m_Do_graphic.cpp | 48 +++++++++++++++++++++------------------ 1 file changed, 26 insertions(+), 22 deletions(-) diff --git a/src/m_Do/m_Do_graphic.cpp b/src/m_Do/m_Do_graphic.cpp index bb6746e977..363507ae09 100644 --- a/src/m_Do/m_Do_graphic.cpp +++ b/src/m_Do/m_Do_graphic.cpp @@ -1259,7 +1259,7 @@ void mDoGph_gInf_c::bloom_c::remove() { } #if TARGET_PC -static void CopyToTexObj(GXTexObj* pDst, uintptr_t texID, int dstWidth, int dstHeight, GXTexFmt dstFmt = GX_TF_RGBA8) { +static void CopyToTexObj(GXTexObj* pDst, uintptr_t texID, u16 dstWidth, u16 dstHeight, GXTexFmt dstFmt = GX_TF_RGBA8) { GXSetTexCopyDst(dstWidth, dstHeight, dstFmt, FALSE); GXCopyTex((void*)texID, false); GXInitTexObj(pDst, (void*)texID, dstWidth, dstHeight, dstFmt, GX_CLAMP, GX_CLAMP, GX_FALSE); @@ -1311,33 +1311,37 @@ void mDoGph_gInf_c::bloom_c::draw2() { BlurPass0, BlurPassN = BlurPass0 + MaxDivNum, MaxTexNum, }; - struct bloom_rect { + struct { u16 x; u16 y; u16 w; u16 h; + } divRects[MaxDivNum]; + divRects[0] = { + 0, + 0, + static_cast(width), + static_cast(height), }; - scissor_class divPorts[MaxDivNum]; - divPorts[0] = {0.0f, 0.0f, 1.0f, 1.0f}; // full-size texture - divPorts[1] = {0.0f, 0.0f, 0.5f, 0.5f}; // bloom texture (wide enough, half-tall) - divPorts[2] = {0.5f, 0.0f, 0.25f, 0.25f}; - for (int i = 3; i < ARRAY_SIZE(divPorts); i++) { - auto& port = divPorts[i]; - auto const& prev = divPorts[i - 1]; - port.x_orig = prev.x_orig; - port.y_orig = prev.y_orig + prev.height; - port.width = prev.width * 0.5f; - port.height = prev.height * 0.5f; - } - - bloom_rect divRects[MaxDivNum]; - for (int i = 0; i < ARRAY_SIZE(divPorts); i++) { - auto const& port = divPorts[i]; + divRects[1] = { + 0, + 0, + static_cast(divRects[0].w / 2), + static_cast(divRects[0].h / 2), + }; + divRects[2] = { + divRects[1].w, + 0, + static_cast(divRects[1].w / 2), + static_cast(divRects[1].h / 2), + }; + for (int i = 3; i < ARRAY_SIZE(divRects); i++) { + const auto& prev = divRects[i - 1]; divRects[i] = { - static_cast(port.x_orig * width), - static_cast(port.y_orig * height), - static_cast(port.width * width), - static_cast(port.height * height), + prev.x, + static_cast(prev.y + prev.h), + static_cast(prev.w / 2), + static_cast(prev.h / 2), }; }