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 168c5ca31b..b6a54d3115 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 { @@ -54,7 +68,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 f42b444ec7..ec11511c3c 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); @@ -38,6 +40,7 @@ static void ApplyPresetDusk() { s.game.midnasLamentNonStop.setValue(true); s.game.enableFrameInterpolation.setValue(true); s.game.timeStones.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 a1773b1346..a6d5037db7 100644 --- a/src/dusk/settings.cpp +++ b/src/dusk/settings.cpp @@ -42,7 +42,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}, @@ -115,7 +115,7 @@ void registerSettings() { Register(g_userSettings.game.timeStones); 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 8c6b5e7c45..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,35 +1311,58 @@ void mDoGph_gInf_c::bloom_c::draw2() { BlurPass0, BlurPassN = BlurPass0 + MaxDivNum, MaxTexNum, }; - 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; + struct { + u16 x; + u16 y; + u16 w; + u16 h; + } divRects[MaxDivNum]; + divRects[0] = { + 0, + 0, + static_cast(width), + static_cast(height), + }; + 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] = { + prev.x, + static_cast(prev.y + prev.h), + static_cast(prev.w / 2), + static_cast(prev.h / 2), + }; } 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); @@ -1409,18 +1432,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 +1454,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 +1479,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); @@ -1500,13 +1524,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; }