Merge branch 'main' into feature/time-stones

This commit is contained in:
MelonSpeedruns
2026-04-13 16:44:00 -04:00
committed by GitHub
7 changed files with 148 additions and 45 deletions
+11
View File
@@ -146,6 +146,12 @@ concept ConfigValue =
template <ConfigValue T>
const ConfigImplBase* GetConfigImpl();
template <typename T>
struct ConfigEnumRange {
static constexpr auto min = std::numeric_limits<std::underlying_type_t<T>>::min();
static constexpr auto max = std::numeric_limits<std::underlying_type_t<T>>::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.
*
+15 -1
View File
@@ -7,6 +7,20 @@ namespace dusk {
using namespace config;
enum class BloomMode : int {
Off = 0,
Classic = 1,
Dusk = 2,
};
namespace config {
template <>
struct ConfigEnumRange<BloomMode> {
static constexpr auto min = BloomMode::Off;
static constexpr auto max = BloomMode::Dusk;
};
}
// Persistent user settings
struct UserSettings {
@@ -54,7 +68,7 @@ struct UserSettings {
ConfigVar<bool> invertCameraXAxis;
// Graphics
ConfigVar<bool> enableBloom;
ConfigVar<BloomMode> bloomMode;
ConfigVar<bool> enableWaterRefraction;
ConfigVar<bool> enableFrameInterpolation;
ConfigVar<int> shadowResolutionMultiplier;
+40 -1
View File
@@ -5,6 +5,7 @@
#include "aurora/lib/logging.hpp"
#include "dusk/io.hpp"
#include "dusk/settings.h"
#include <limits>
#include <string>
@@ -37,9 +38,24 @@ const ConfigImplBase* ConfigVarBase::getImpl() const noexcept {
return impl;
}
template <typename T>
static T sanitizeEnumValue(const ConfigVar<T>& cVar, T value) {
if constexpr (std::is_enum_v<T>) {
using Underlying = std::underlying_type_t<T>;
const Underlying raw = static_cast<Underlying>(value);
const Underlying min = static_cast<Underlying>(ConfigEnumRange<T>::min);
const Underlying max = static_cast<Underlying>(ConfigEnumRange<T>::max);
if (raw < min || raw > max) {
return cVar.getDefaultValue();
}
}
return value;
}
template<ConfigValue T>
void ConfigImpl<T>::loadFromJson(ConfigVar<T>& cVar, const json& jsonValue) {
cVar.setValue(jsonValue.get<T>(), false);
cVar.setValue(sanitizeEnumValue(cVar, jsonValue.get<T>()), false);
}
template<ConfigValue T>
@@ -85,6 +101,28 @@ static void loadFromArgImpl(ConfigVar<std::string>& cVar, const std::string_view
cVar.setOverrideValue(std::string(stringValue));
}
template<ConfigValue T> requires std::is_enum_v<T>
static void loadFromArgImpl(ConfigVar<T>& cVar, const std::string_view stringValue) {
using Underlying = std::underlying_type_t<T>;
const std::string str(stringValue);
if constexpr (std::is_signed_v<Underlying>) {
const auto result = std::stoll(str);
if (result >= std::numeric_limits<Underlying>::min() && result <= std::numeric_limits<Underlying>::max()) {
cVar.setOverrideValue(sanitizeEnumValue(cVar, static_cast<T>(result)));
} else {
throw std::out_of_range("Value is too large");
}
} else {
const auto result = std::stoull(str);
if (result <= std::numeric_limits<Underlying>::max()) {
cVar.setOverrideValue(sanitizeEnumValue(cVar, static_cast<T>(result)));
} else {
throw std::out_of_range("Value is too large");
}
}
}
template<ConfigValue T>
void ConfigImpl<T>::loadFromArg(ConfigVar<T>& cVar, const std::string_view stringValue) {
loadFromArgImpl(cVar, stringValue);
@@ -115,6 +153,7 @@ namespace dusk::config {
template class ConfigImpl<f32>;
template class ConfigImpl<f64>;
template class ConfigImpl<std::string>;
template class ConfigImpl<dusk::BloomMode>;
}
void dusk::config::Register(ConfigVarBase& configVar) {
+3
View File
@@ -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);
}
// =========================================================================
+15 -1
View File
@@ -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<int>(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<BloomMode>(i));
config::Save();
}
if (selected) {
ImGui::SetItemDefaultFocus();
}
}
ImGui::EndCombo();
}
config::ImGuiCheckbox("Enable Water Refraction", getSettings().game.enableWaterRefraction);
+2 -2
View File
@@ -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);
+62 -40
View File
@@ -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<u16>(width),
static_cast<u16>(height),
};
divRects[1] = {
0,
0,
static_cast<u16>(divRects[0].w / 2),
static_cast<u16>(divRects[0].h / 2),
};
divRects[2] = {
divRects[1].w,
0,
static_cast<u16>(divRects[1].w / 2),
static_cast<u16>(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<u16>(prev.y + prev.h),
static_cast<u16>(prev.w / 2),
static_cast<u16>(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;
}