From c516b91476cbdcfde16fe0b57d7bfc6fcb37fab6 Mon Sep 17 00:00:00 2001 From: Irastris Date: Sun, 17 May 2026 22:26:10 -0400 Subject: [PATCH] Add "Output Resampling" option (#1541) Adds an Output Resampling option to the Video tab to allow choosing between the old Bilinear sampler and a new Area sampler. Area sampling produces a much cleaner, softer image when downscaling, and a significantly sharper image when upscaling. This can also serve as a halfway decent anti-aliasing substitute until we have a more proper implementation. --- extern/aurora | 2 +- include/dusk/settings.h | 12 ++++++++++++ src/dusk/config.cpp | 1 + src/dusk/settings.cpp | 2 ++ src/dusk/ui/graphics_tuner.cpp | 27 +++++++++++++++++++++++++++ src/dusk/ui/graphics_tuner.hpp | 1 + src/dusk/ui/settings.cpp | 11 +++++++++++ src/m_Do/m_Do_main.cpp | 9 +++++++++ 8 files changed, 64 insertions(+), 1 deletion(-) diff --git a/extern/aurora b/extern/aurora index f93b9e5bc2..10006618ee 160000 --- a/extern/aurora +++ b/extern/aurora @@ -1 +1 @@ -Subproject commit f93b9e5bc20850198538ccd3abc69ab2b128ecf7 +Subproject commit 10006618ee493f248b8597e4dfa1d2871d76a1d9 diff --git a/include/dusk/settings.h b/include/dusk/settings.h index f89aeeb875..bd6f30683b 100644 --- a/include/dusk/settings.h +++ b/include/dusk/settings.h @@ -15,6 +15,11 @@ enum class BloomMode : int { Dusk = 2, }; +enum class Resampler : int { + Bilinear = 0, + Area = 1, +}; + enum class GameLanguage : u8 { English = OS_LANGUAGE_ENGLISH, German = OS_LANGUAGE_GERMAN, @@ -47,6 +52,12 @@ struct ConfigEnumRange { static constexpr auto max = BloomMode::Dusk; }; +template <> +struct ConfigEnumRange { + static constexpr auto min = Resampler::Bilinear; + static constexpr auto max = Resampler::Area; +}; + template <> struct ConfigEnumRange { static constexpr auto min = GameLanguage::English; @@ -140,6 +151,7 @@ struct UserSettings { ConfigVar enableFrameInterpolation; ConfigVar internalResolutionScale; ConfigVar shadowResolutionMultiplier; + ConfigVar resampler; ConfigVar enableDepthOfField; ConfigVar enableMapBackground; ConfigVar disableCutscenePillarboxing; diff --git a/src/dusk/config.cpp b/src/dusk/config.cpp index 3d15f1fded..1f9e7c54a6 100644 --- a/src/dusk/config.cpp +++ b/src/dusk/config.cpp @@ -176,6 +176,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/settings.cpp b/src/dusk/settings.cpp index e3eae37aad..5861241fe3 100644 --- a/src/dusk/settings.cpp +++ b/src/dusk/settings.cpp @@ -63,6 +63,7 @@ UserSettings g_userSettings = { .enableFrameInterpolation {"game.enableFrameInterpolation", FrameInterpMode::Off}, .internalResolutionScale {"game.internalResolutionScale", 0}, .shadowResolutionMultiplier {"game.shadowResolutionMultiplier", 1}, + .resampler {"game.resampler", Resampler::Bilinear}, .enableDepthOfField {"game.enableDepthOfField", true}, .enableMapBackground {"game.enableMapBackground", true}, .disableCutscenePillarboxing {"game.disableCutscenePillarboxing", false}, @@ -223,6 +224,7 @@ void registerSettings() { Register(g_userSettings.game.disableWaterRefraction); Register(g_userSettings.game.enableTextureReplacements); Register(g_userSettings.game.internalResolutionScale); + Register(g_userSettings.game.resampler); Register(g_userSettings.game.shadowResolutionMultiplier); Register(g_userSettings.game.enableDepthOfField); Register(g_userSettings.game.enableMapBackground); diff --git a/src/dusk/ui/graphics_tuner.cpp b/src/dusk/ui/graphics_tuner.cpp index 4b70bab592..0b75b27a89 100644 --- a/src/dusk/ui/graphics_tuner.cpp +++ b/src/dusk/ui/graphics_tuner.cpp @@ -3,6 +3,7 @@ #include "Z2AudioLib/Z2SeMgr.h" #include "m_Do/m_Do_audio.h" +#include #include #include #include @@ -43,6 +44,8 @@ int get_value(GraphicsOption option) { return getSettings().game.internalResolutionScale.getValue(); case GraphicsOption::ShadowResolution: return getSettings().game.shadowResolutionMultiplier.getValue(); + case GraphicsOption::Resampler: + return static_cast(getSettings().game.resampler.getValue()); case GraphicsOption::BloomMode: return static_cast(getSettings().game.bloomMode.getValue()); case GraphicsOption::BloomMultiplier: @@ -62,6 +65,22 @@ void set_value(GraphicsOption option, int value) { case GraphicsOption::ShadowResolution: getSettings().game.shadowResolutionMultiplier.setValue(value); break; + case GraphicsOption::Resampler: { + const auto sampler = static_cast(std::clamp(value, + static_cast(Resampler::Bilinear), + static_cast(Resampler::Area))); + getSettings().game.resampler.setValue(sampler); + switch (sampler) { + case Resampler::Area: + aurora_set_resampler(SAMPLER_AREA); + break; + case Resampler::Bilinear: + default: + aurora_set_resampler(SAMPLER_BILINEAR); + break; + } + break; + } case GraphicsOption::BloomMode: getSettings().game.bloomMode.setValue(static_cast(std::clamp( value, static_cast(BloomMode::Off), static_cast(BloomMode::Dusk)))); @@ -177,6 +196,14 @@ Rml::String format_graphics_setting_value(GraphicsOption option, int value) { } case GraphicsOption::ShadowResolution: return fmt::format("{}×", value); + case GraphicsOption::Resampler: + switch (static_cast(value)) { + case Resampler::Bilinear: + return "Bilinear"; + case Resampler::Area: + return "Area"; + } + break; case GraphicsOption::BloomMode: switch (static_cast(value)) { case BloomMode::Off: diff --git a/src/dusk/ui/graphics_tuner.hpp b/src/dusk/ui/graphics_tuner.hpp index 36932bcbbb..6f8f113d13 100644 --- a/src/dusk/ui/graphics_tuner.hpp +++ b/src/dusk/ui/graphics_tuner.hpp @@ -42,6 +42,7 @@ private: enum class GraphicsOption { InternalResolution, ShadowResolution, + Resampler, BloomMode, BloomMultiplier, }; diff --git a/src/dusk/ui/settings.cpp b/src/dusk/ui/settings.cpp index 780622e13a..9cf8a2f5d2 100644 --- a/src/dusk/ui/settings.cpp +++ b/src/dusk/ui/settings.cpp @@ -357,6 +357,8 @@ const Rml::String kInternalResolutionHelpText = const Rml::String kShadowResolutionHelpText = "Configure the shadow-map resolution. Higher values improve shadow quality but increase GPU " "and memory usage."; +const Rml::String kResamplerHelpText = + "Configure the sampling method used when scaling the internal resolution for final presentation."; const Rml::String kBloomHelpText = "Configure the post-processing bloom effect. Classic uses the original bloom pass; Dusklight uses " "a higher-quality bloom pass."; @@ -805,6 +807,15 @@ SettingsWindow::SettingsWindow(bool prelaunch) : mPrelaunch(prelaunch) { .valueMax = 8, .defaultValue = 1, }, mPrelaunch); + graphics_tuner_control(*this, leftPane, rightPane, getSettings().game.resampler, + GraphicsTunerProps{ + .option = GraphicsOption::Resampler, + .title = "Output Resampling", + .helpText = kResamplerHelpText, + .valueMin = static_cast(Resampler::Bilinear), + .valueMax = static_cast(Resampler::Area), + .defaultValue = static_cast(Resampler::Bilinear), + }, mPrelaunch); leftPane.add_section("Post-Processing"); graphics_tuner_control(*this, leftPane, rightPane, getSettings().game.bloomMode, diff --git a/src/m_Do/m_Do_main.cpp b/src/m_Do/m_Do_main.cpp index 6593d78162..490c0b8980 100644 --- a/src/m_Do/m_Do_main.cpp +++ b/src/m_Do/m_Do_main.cpp @@ -604,6 +604,15 @@ int game_main(int argc, char* argv[]) { AuroraSetViewportPolicy(AURORA_VIEWPORT_STRETCH); } VISetFrameBufferScale(dusk::getSettings().game.internalResolutionScale.getValue()); + switch (dusk::getSettings().game.resampler.getValue()) { + case dusk::Resampler::Area: + aurora_set_resampler(SAMPLER_AREA); + break; + case dusk::Resampler::Bilinear: + default: + aurora_set_resampler(SAMPLER_BILINEAR); + break; + } dusk::audio::SetMasterVolume(dusk::audio::MasterVolumeToLinear(dusk::getSettings().audio.masterVolume / 100.0f)); dusk::audio::SetEnableReverb(dusk::getSettings().audio.enableReverb);