From 0fd17fe08207de81c2f6191183bba98fb1fcc0b4 Mon Sep 17 00:00:00 2001 From: Irastris Date: Tue, 1 Sep 2026 11:07:49 -0400 Subject: [PATCH] Refactor GraphicsTuner, no longer tanks performance when open (#2365) * Sort includes, move logic into ConfigVars, add reusable helpers * Refactor GraphicsTuner, only refresh labels when actually needed --- src/dusk/settings.cpp | 33 +++- src/dusk/settings.h | 3 + src/dusk/texture_replacements.cpp | 5 - src/dusk/texture_replacements.hpp | 1 - src/dusk/ui/graphics_tuner.cpp | 273 ++++++++++++++++-------------- src/dusk/ui/graphics_tuner.hpp | 36 ++-- src/dusk/ui/settings.cpp | 50 +----- src/m_Do/m_Do_main.cpp | 12 +- 8 files changed, 212 insertions(+), 201 deletions(-) diff --git a/src/dusk/settings.cpp b/src/dusk/settings.cpp index 25ae768dac..a1a0f9ce54 100644 --- a/src/dusk/settings.cpp +++ b/src/dusk/settings.cpp @@ -1,8 +1,12 @@ #include "dusk/settings.h" -#include + #include "dusk/config.hpp" -#include "dusk/ui/ui.hpp" #include "dusk/game_mode.hpp" +#include "dusk/texture_replacements.hpp" +#include "dusk/ui/ui.hpp" + +#include +#include namespace dusk { @@ -221,6 +225,22 @@ UserSettings& getSettings() { return g_userSettings; } +void applyInternalResolutionScale(int scale) { + VISetFrameBufferScale(static_cast(scale)); +} + +void applyResampler(Resampler resampler) { + switch (resampler) { + case Resampler::Area: + aurora_set_resampler(SAMPLER_AREA); + break; + case Resampler::Bilinear: + default: + aurora_set_resampler(SAMPLER_BILINEAR); + break; + } +} + void registerSettings() { // Video Register(g_userSettings.video.enableFullscreen); @@ -286,9 +306,12 @@ void registerSettings() { Register(g_userSettings.game.bloomMultiplier); Register(g_userSettings.game.depthOfFieldMode); Register(g_userSettings.game.disableWaterRefraction); - Register(g_userSettings.game.enableTextureReplacements); - Register(g_userSettings.game.internalResolutionScale); - Register(g_userSettings.game.resampler); + Register(g_userSettings.game.enableTextureReplacements, + [](const bool&, const bool&) { texture_replacements::reload(); }); + Register(g_userSettings.game.internalResolutionScale, + [](const int& value, const int&) { applyInternalResolutionScale(value); }); + Register(g_userSettings.game.resampler, + [](const Resampler& value, const Resampler&) { applyResampler(value); }); Register(g_userSettings.game.shadowResolutionMultiplier); Register(g_userSettings.game.enableMapBackground); Register(g_userSettings.game.disableCutscenePillarboxing); diff --git a/src/dusk/settings.h b/src/dusk/settings.h index 48f9ed28c6..83c2bfc2fa 100644 --- a/src/dusk/settings.h +++ b/src/dusk/settings.h @@ -318,6 +318,9 @@ UserSettings& getSettings(); void registerSettings(); +void applyInternalResolutionScale(int scale); +void applyResampler(Resampler resampler); + // Transient settings struct CollisionViewSettings { diff --git a/src/dusk/texture_replacements.cpp b/src/dusk/texture_replacements.cpp index 2866af855f..118989c6a7 100644 --- a/src/dusk/texture_replacements.cpp +++ b/src/dusk/texture_replacements.cpp @@ -26,11 +26,6 @@ void reload() { s_directoryGroup.registrations.size()); } -void set_enabled(bool enabled) { - getSettings().game.enableTextureReplacements.setValue(enabled); - reload(); -} - void shutdown() { aurora::texture::unregister_replacements(s_directoryGroup); s_directoryGroup.registrations.clear(); diff --git a/src/dusk/texture_replacements.hpp b/src/dusk/texture_replacements.hpp index 1a633d424d..99801521aa 100644 --- a/src/dusk/texture_replacements.hpp +++ b/src/dusk/texture_replacements.hpp @@ -8,7 +8,6 @@ namespace dusk::texture_replacements { inline constexpr int32_t kUserTextureReplacementPriority = -1'000'000; void reload(); -void set_enabled(bool enabled); void shutdown(); } diff --git a/src/dusk/ui/graphics_tuner.cpp b/src/dusk/ui/graphics_tuner.cpp index cddcf06330..979edccdcc 100644 --- a/src/dusk/ui/graphics_tuner.cpp +++ b/src/dusk/ui/graphics_tuner.cpp @@ -1,20 +1,18 @@ #include "graphics_tuner.hpp" -#include "Z2AudioLib/Z2SeMgr.h" -#include "m_Do/m_Do_audio.h" - -#include -#include -#include -#include -#include +#include "button.hpp" #include "dusk/config.hpp" +#include "dusk/logging.h" #include "dusk/settings.h" -#include "dusk/texture_replacements.hpp" +#include "m_Do/m_Do_audio.h" + +#include +#include #include #include +#include namespace dusk::ui { namespace { @@ -40,70 +38,84 @@ const Rml::String kDocumentSource = R"RML( )RML"; -int get_value(GraphicsOption option) { - switch (option) { - case GraphicsOption::InternalResolution: - 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: - return std::clamp( - static_cast(getSettings().game.bloomMultiplier.getValue() * 100.0f + 0.5f), 0, - 100); - case GraphicsOption::DepthOfFieldMode: - return static_cast(getSettings().game.depthOfFieldMode.getValue()); - case GraphicsOption::TextureReplacements: - return getSettings().game.enableTextureReplacements.getValue(); +Rml::String format_internal_resolution(int value) { + u32 width = 0; + u32 height = 0; + AuroraGetRenderSize(&width, &height); + if (value <= 0) { + return fmt::format("Auto ({}×{})", width, height); } - return 0; + return fmt::format("{}× ({}×{})", value, width, height); } -void set_value(GraphicsOption option, int value) { - switch (option) { - case GraphicsOption::InternalResolution: - getSettings().game.internalResolutionScale.setValue(value); - VISetFrameBufferScale(static_cast(value)); - break; - 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; +Rml::String format_resampler(int value) { + switch (static_cast(value)) { + case Resampler::Bilinear: + return "Bilinear"; + case Resampler::Area: + return "Area"; + default: + return ""; } - case GraphicsOption::BloomMode: - getSettings().game.bloomMode.setValue(static_cast(std::clamp( - value, static_cast(BloomMode::Off), static_cast(BloomMode::Dusk)))); - break; - case GraphicsOption::DepthOfFieldMode: - getSettings().game.depthOfFieldMode.setValue(static_cast(std::clamp( - value, static_cast(DepthOfFieldMode::Off), static_cast(DepthOfFieldMode::Dusk)))); - break; - case GraphicsOption::BloomMultiplier: - getSettings().game.bloomMultiplier.setValue(std::clamp(value, 0, 100) / 100.0f); - break; - case GraphicsOption::TextureReplacements: - texture_replacements::set_enabled(static_cast(value)); - break; +} + +Rml::String format_post_process_mode(int value) { + switch (static_cast(value)) { + case BloomMode::Off: + return "Off"; + case BloomMode::Classic: + return "Classic"; + case BloomMode::Dusk: + return "Dusklight"; + default: + return ""; } } +Rml::String format_times(int value) { return fmt::format("{}×", value); } + +Rml::String format_percent(int value) { return fmt::format("{}%", value); } + +Rml::String format_bool(int value) { return value ? "On" : "Off"; } + +template +int read_cvar(const ConfigVar& var) { + if constexpr (std::is_same_v) { + return static_cast(var.getValue() * 100.0f + 0.5f); + } else { + return static_cast(var.getValue()); + } +} + +template +void write_cvar(ConfigVar& var, int value) { + if constexpr (std::is_same_v) { + var.setValue(static_cast(value) / 100.0f); + } else if constexpr (std::is_same_v) { + var.setValue(static_cast(value)); + } else { + var.setValue(static_cast(value)); + } +} + +template +const GraphicsSetting& bind(Min min, Max max, Def def, int step, Rml::String (*label)(int), + bool watchSize = false) { + static const GraphicsSetting desc{ + .min = static_cast(min), + .max = static_cast(max), + .defaultValue = static_cast(def), + .step = step, + .watchesRenderSize = watchSize, + .read = []() -> int { return read_cvar(Var()); }, + .write = [](int value) { write_cvar(Var(), value); }, + .label = label, + .cvarName = []() -> const char* { return Var().getName(); }, + .isModified = []() -> bool { return Var().getValue() != Var().getDefaultValue(); }, + }; + return desc; +} + Rml::Element* create_stepped_carousel_root(Rml::Element* parent) { auto* doc = parent->GetOwnerDocument(); auto root = doc->CreateElement("div"); @@ -129,6 +141,35 @@ void update_carousel_arrow_color(Rml::Element* arrow, bool dim) { } // namespace +const GraphicsSetting& GraphicsSetting::of(GraphicsOption option) { + switch (option) { + case GraphicsOption::InternalResolution: + return bind<[]() -> auto& { return getSettings().game.internalResolutionScale; }>( + 0, 12, 0, 1, format_internal_resolution, true); + case GraphicsOption::ShadowResolution: + return bind<[]() -> auto& { return getSettings().game.shadowResolutionMultiplier; }>( + 1, 8, 1, 1, format_times); + case GraphicsOption::Resampler: + return bind<[]() -> auto& { return getSettings().game.resampler; }>( + Resampler::Bilinear, Resampler::Area, Resampler::Bilinear, 1, format_resampler); + case GraphicsOption::BloomMode: + return bind<[]() -> auto& { return getSettings().game.bloomMode; }>( + BloomMode::Off, BloomMode::Dusk, BloomMode::Classic, 1, format_post_process_mode); + case GraphicsOption::BloomMultiplier: + return bind<[]() -> auto& { return getSettings().game.bloomMultiplier; }>( + 0, 100, 100, 10, format_percent); + case GraphicsOption::DepthOfFieldMode: + return bind<[]() -> auto& { return getSettings().game.depthOfFieldMode; }>( + DepthOfFieldMode::Off, DepthOfFieldMode::Dusk, DepthOfFieldMode::Classic, 1, + format_post_process_mode); + case GraphicsOption::TextureReplacements: + return bind<[]() -> auto& { return getSettings().game.enableTextureReplacements; }>( + 0, 1, 0, 1, format_bool); + } + DuskLog.error("{} is an invalid GraphicsOption", static_cast(option)); + abort(); +} + SteppedCarousel::SteppedCarousel(Rml::Element* parent, Props props) : Component(create_stepped_carousel_root(parent)), mProps(std::move(props)) { mPrevElem = create_stepped_carousel_arrow(mRoot, "prev", ""); @@ -152,7 +193,9 @@ bool SteppedCarousel::focus() { return Component::focus(); } -void SteppedCarousel::update() { +void SteppedCarousel::update() {} + +void SteppedCarousel::refresh() { if (mValueElem == nullptr) { return; } @@ -194,59 +237,9 @@ void SteppedCarousel::apply(int value) { } } -Rml::String format_graphics_setting_value(GraphicsOption option, int value) { - switch (option) { - case GraphicsOption::InternalResolution: { - u32 width = 0; - u32 height = 0; - AuroraGetRenderSize(&width, &height); - if (value <= 0) { - return fmt::format("Auto ({}×{})", width, height); - } else { - return fmt::format("{}× ({}×{})", value, width, height); - } - } - 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: - return "Off"; - case BloomMode::Classic: - return "Classic"; - case BloomMode::Dusk: - return "Dusklight"; - } - break; - case GraphicsOption::DepthOfFieldMode: - switch (static_cast(value)) { - case DepthOfFieldMode::Off: - return "Off"; - case DepthOfFieldMode::Classic: - return "Classic"; - case DepthOfFieldMode::Dusk: - return "Dusklight"; - } - break; - case GraphicsOption::BloomMultiplier: - return fmt::format("{}%", value); - case GraphicsOption::TextureReplacements: - return static_cast(value) ? "On" : "Off"; - } - return ""; -} - GraphicsTuner::GraphicsTuner(GraphicsTunerProps props) - : Document(kDocumentSource, false, DocumentScope::GraphicsTuner), mOption(props.option), - mValueMin(props.valueMin), mValueMax(props.valueMax), mDefaultValue(props.defaultValue) { + : Document(kDocumentSource, false, DocumentScope::GraphicsTuner), + mSetting(GraphicsSetting::of(props.option)) { if (mDocument == nullptr) { return; } @@ -260,13 +253,12 @@ GraphicsTuner::GraphicsTuner(GraphicsTunerProps props) if (auto* carouselParent = mDocument->GetElementById("carousel-container")) { mCarousel = &add_component(carouselParent, SteppedCarousel::Props{ - .min = mValueMin, - .max = mValueMax, - .step = props.step, - .getValue = [this] { return get_value(mOption); }, - .onChange = [this](int value) { set_value(mOption, value); }, - .formatValue = - [this](int value) { return format_graphics_setting_value(mOption, value); }, + .min = mSetting.min, + .max = mSetting.max, + .step = mSetting.step, + .getValue = [this] { return mSetting.read(); }, + .onChange = [this](int value) { mSetting.set(value); }, + .formatValue = [this](int value) { return mSetting.label(value); }, }); } @@ -282,6 +274,17 @@ GraphicsTuner::GraphicsTuner(GraphicsTunerProps props) resetButton.root()->SetClass("reset", true); } + if (mCarousel != nullptr) { + if (const char* name = mSetting.cvarName()) { + mSubscription = config::subscribe(name, + [this](config::ConfigVarBase&, const void*) { mCarousel->refresh(); }); + } + mCarousel->refresh(); + if (mSetting.watchesRenderSize) { + AuroraGetRenderSize(&mLastRenderWidth, &mLastRenderHeight); + } + } + // Hide document after transition completion mRoot = mDocument->GetElementById("root"); listen(mRoot, Rml::EventId::Transitionend, [this](Rml::Event& event) { @@ -293,6 +296,12 @@ GraphicsTuner::GraphicsTuner(GraphicsTunerProps props) }); } +GraphicsTuner::~GraphicsTuner() { + if (mSubscription != 0) { + config::unsubscribe(mSubscription); + } +} + void GraphicsTuner::show() { Document::show(); mRoot->SetAttribute("open", ""); @@ -309,6 +318,16 @@ void GraphicsTuner::hide(bool close) { } void GraphicsTuner::update() { + if (mSetting.watchesRenderSize && mCarousel != nullptr) { + u32 width = 0; + u32 height = 0; + AuroraGetRenderSize(&width, &height); + if (width != mLastRenderWidth || height != mLastRenderHeight) { + mLastRenderWidth = width; + mLastRenderHeight = height; + mCarousel->refresh(); + } + } for (const auto& component : mComponents) { component->update(); } @@ -342,7 +361,7 @@ bool GraphicsTuner::handle_nav_command(Rml::Event& event, NavCommand cmd) { } void GraphicsTuner::reset_default() { - set_value(mOption, mDefaultValue); + mSetting.set(mSetting.defaultValue); } } // namespace dusk::ui diff --git a/src/dusk/ui/graphics_tuner.hpp b/src/dusk/ui/graphics_tuner.hpp index d778583b29..2a6b3e198a 100644 --- a/src/dusk/ui/graphics_tuner.hpp +++ b/src/dusk/ui/graphics_tuner.hpp @@ -1,10 +1,10 @@ #pragma once -#include "button.hpp" #include "component.hpp" #include "document.hpp" #include "ui.hpp" +#include #include #include #include @@ -28,6 +28,7 @@ public: bool focus() override; void update() override; + void refresh(); bool handle_nav_command(NavCommand cmd); private: @@ -49,21 +50,34 @@ enum class GraphicsOption { TextureReplacements, }; -Rml::String format_graphics_setting_value(GraphicsOption option, int value); +struct GraphicsSetting { + int min = 0; + int max = 0; + int defaultValue = 0; + int step = 1; + bool watchesRenderSize = false; + int (*read)() = nullptr; + void (*write)(int) = nullptr; + Rml::String (*label)(int) = nullptr; + const char* (*cvarName)() = nullptr; + bool (*isModified)() = nullptr; + + static const GraphicsSetting& of(GraphicsOption option); + + void set(int value) const { write(std::clamp(value, min, max)); } + Rml::String text() const { return label(read()); } +}; struct GraphicsTunerProps { GraphicsOption option; Rml::String title; Rml::String helpText; - int valueMin = 0; - int valueMax = 0; - int defaultValue = 0; - int step = 1; }; class GraphicsTuner : public Document { public: explicit GraphicsTuner(GraphicsTunerProps props); + ~GraphicsTuner() override; void show() override; void hide(bool close) override; @@ -85,13 +99,13 @@ private: void reset_default(); - GraphicsOption mOption; - int mValueMin = 0; - int mValueMax = 0; - int mDefaultValue = 0; + GraphicsSetting mSetting; std::vector > mComponents; - SteppedCarousel* mCarousel; + SteppedCarousel* mCarousel = nullptr; Rml::Element* mRoot; + u64 mSubscription = 0; + u32 mLastRenderWidth = 0; + u32 mLastRenderHeight = 0; }; } // namespace dusk::ui diff --git a/src/dusk/ui/settings.cpp b/src/dusk/ui/settings.cpp index bac67f3126..a6f549048f 100644 --- a/src/dusk/ui/settings.cpp +++ b/src/dusk/ui/settings.cpp @@ -440,24 +440,15 @@ SelectButton& config_int_select(Pane& leftPane, Pane& rightPane, ConfigVar& return button; } -template -void graphics_tuner_control(Window& window, Pane& leftPane, Pane& rightPane, ConfigVar& var, +void graphics_tuner_control(Window& window, Pane& leftPane, Pane& rightPane, const GraphicsTunerProps& props) { + const auto setting = GraphicsSetting::of(props.option); leftPane.register_control( leftPane .add_select_button({ .key = props.title, - .getValue = - [&var, option = props.option] { - if constexpr (std::is_same_v) { - return format_graphics_setting_value( - option, float_setting_percent(var)); - } else { - return format_graphics_setting_value( - option, static_cast(var.getValue())); - } - }, - .isModified = [&var] { return var.getValue() != var.getDefaultValue(); }, + .getValue = [setting] { return setting.text(); }, + .isModified = [setting] { return setting.isModified(); }, .submit = false, }) .on_nav_command([&window, props](Rml::Event&, NavCommand cmd) { @@ -779,75 +770,50 @@ SettingsWindow::SettingsWindow(bool prelaunch) : mPrelaunch(prelaunch) { leftPane.add_section("Resolution"); graphics_tuner_control(*this, leftPane, rightPane, - getSettings().game.internalResolutionScale, GraphicsTunerProps{ .option = GraphicsOption::InternalResolution, .title = "Internal Resolution", .helpText = kInternalResolutionHelpText, - .valueMin = 0, - .valueMax = 12, - .defaultValue = 0, }); graphics_tuner_control(*this, leftPane, rightPane, - getSettings().game.shadowResolutionMultiplier, GraphicsTunerProps{ .option = GraphicsOption::ShadowResolution, .title = "Shadow Resolution", .helpText = kShadowResolutionHelpText, - .valueMin = 1, - .valueMax = 8, - .defaultValue = 1, }); - graphics_tuner_control(*this, leftPane, rightPane, getSettings().game.resampler, + graphics_tuner_control(*this, leftPane, rightPane, GraphicsTunerProps{ .option = GraphicsOption::Resampler, .title = "Output Resampling", .helpText = kResamplerHelpText, - .valueMin = static_cast(Resampler::Bilinear), - .valueMax = static_cast(Resampler::Area), - .defaultValue = static_cast(Resampler::Bilinear), }); leftPane.add_section("Post-Processing"); - graphics_tuner_control(*this, leftPane, rightPane, getSettings().game.bloomMode, + graphics_tuner_control(*this, leftPane, rightPane, GraphicsTunerProps{ .option = GraphicsOption::BloomMode, .title = "Bloom", .helpText = kBloomHelpText, - .valueMin = static_cast(BloomMode::Off), - .valueMax = static_cast(BloomMode::Dusk), - .defaultValue = static_cast(BloomMode::Classic), }); - graphics_tuner_control(*this, leftPane, rightPane, getSettings().game.bloomMultiplier, + graphics_tuner_control(*this, leftPane, rightPane, GraphicsTunerProps{ .option = GraphicsOption::BloomMultiplier, .title = "Bloom Brightness", .helpText = kBloomBrightnessHelpText, - .valueMin = 0, - .valueMax = 100, - .defaultValue = 100, - .step = 10, }); - graphics_tuner_control(*this, leftPane, rightPane, getSettings().game.depthOfFieldMode, + graphics_tuner_control(*this, leftPane, rightPane, GraphicsTunerProps{ .option = GraphicsOption::DepthOfFieldMode, .title = "Depth of Field", .helpText = kDepthOfFieldHelpText, - .valueMin = static_cast(DepthOfFieldMode::Off), - .valueMax = static_cast(DepthOfFieldMode::Dusk), - .defaultValue = static_cast(DepthOfFieldMode::Classic), }); leftPane.add_section("Rendering"); graphics_tuner_control(*this, leftPane, rightPane, - getSettings().game.enableTextureReplacements, GraphicsTunerProps{ .option = GraphicsOption::TextureReplacements, .title = "Enable Texture Replacements", .helpText = kTextureReplacementHelpText, - .valueMin = static_cast(false), - .valueMax = static_cast(true), - .defaultValue = static_cast(false), }); leftPane.register_control( leftPane.add_select_button({ diff --git a/src/m_Do/m_Do_main.cpp b/src/m_Do/m_Do_main.cpp index 80ec318919..22a9d87a33 100644 --- a/src/m_Do/m_Do_main.cpp +++ b/src/m_Do/m_Do_main.cpp @@ -747,16 +747,8 @@ int game_main(int argc, char* argv[]) { } else { 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::applyInternalResolutionScale(dusk::getSettings().game.internalResolutionScale.getValue()); + dusk::applyResampler(dusk::getSettings().game.resampler.getValue()); dusk::audio::SetMasterVolume(dusk::audio::MasterVolumeToLinear(dusk::getSettings().audio.masterVolume / 100.0f)); dusk::audio::SetEnableReverb(dusk::getSettings().audio.enableReverb);