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
This commit is contained in:
Irastris
2026-09-01 11:07:49 -04:00
committed by GitHub
parent 455150ad90
commit 0fd17fe082
8 changed files with 212 additions and 201 deletions
+28 -5
View File
@@ -1,8 +1,12 @@
#include "dusk/settings.h"
#include <aurora/aurora.h>
#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 <aurora/aurora.h>
#include <dolphin/vi.h>
namespace dusk {
@@ -221,6 +225,22 @@ UserSettings& getSettings() {
return g_userSettings;
}
void applyInternalResolutionScale(int scale) {
VISetFrameBufferScale(static_cast<float>(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);
+3
View File
@@ -318,6 +318,9 @@ UserSettings& getSettings();
void registerSettings();
void applyInternalResolutionScale(int scale);
void applyResampler(Resampler resampler);
// Transient settings
struct CollisionViewSettings {
-5
View File
@@ -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();
-1
View File
@@ -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();
}
+146 -127
View File
@@ -1,20 +1,18 @@
#include "graphics_tuner.hpp"
#include "Z2AudioLib/Z2SeMgr.h"
#include "m_Do/m_Do_audio.h"
#include <aurora/aurora.h>
#include <aurora/gfx.h>
#include <dolphin/gx/GXAurora.h>
#include <dolphin/vi.h>
#include <fmt/format.h>
#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 <dolphin/gx/GXAurora.h>
#include <fmt/format.h>
#include <algorithm>
#include <string>
#include <type_traits>
namespace dusk::ui {
namespace {
@@ -40,70 +38,84 @@ const Rml::String kDocumentSource = R"RML(
</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<int>(getSettings().game.resampler.getValue());
case GraphicsOption::BloomMode:
return static_cast<int>(getSettings().game.bloomMode.getValue());
case GraphicsOption::BloomMultiplier:
return std::clamp(
static_cast<int>(getSettings().game.bloomMultiplier.getValue() * 100.0f + 0.5f), 0,
100);
case GraphicsOption::DepthOfFieldMode:
return static_cast<int>(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<float>(value));
break;
case GraphicsOption::ShadowResolution:
getSettings().game.shadowResolutionMultiplier.setValue(value);
break;
case GraphicsOption::Resampler: {
const auto sampler = static_cast<Resampler>(std::clamp(value,
static_cast<int>(Resampler::Bilinear),
static_cast<int>(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<Resampler>(value)) {
case Resampler::Bilinear:
return "Bilinear";
case Resampler::Area:
return "Area";
default:
return "";
}
case GraphicsOption::BloomMode:
getSettings().game.bloomMode.setValue(static_cast<BloomMode>(std::clamp(
value, static_cast<int>(BloomMode::Off), static_cast<int>(BloomMode::Dusk))));
break;
case GraphicsOption::DepthOfFieldMode:
getSettings().game.depthOfFieldMode.setValue(static_cast<DepthOfFieldMode>(std::clamp(
value, static_cast<int>(DepthOfFieldMode::Off), static_cast<int>(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<bool>(value));
break;
}
Rml::String format_post_process_mode(int value) {
switch (static_cast<BloomMode>(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 <typename T>
int read_cvar(const ConfigVar<T>& var) {
if constexpr (std::is_same_v<T, float>) {
return static_cast<int>(var.getValue() * 100.0f + 0.5f);
} else {
return static_cast<int>(var.getValue());
}
}
template <typename T>
void write_cvar(ConfigVar<T>& var, int value) {
if constexpr (std::is_same_v<T, float>) {
var.setValue(static_cast<float>(value) / 100.0f);
} else if constexpr (std::is_same_v<T, bool>) {
var.setValue(static_cast<bool>(value));
} else {
var.setValue(static_cast<T>(value));
}
}
template <auto Var, typename Min, typename Max, typename Def>
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<int>(min),
.max = static_cast<int>(max),
.defaultValue = static_cast<int>(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<int>(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", "&#xe5cb;");
@@ -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<Resampler>(value)) {
case Resampler::Bilinear:
return "Bilinear";
case Resampler::Area:
return "Area";
}
break;
case GraphicsOption::BloomMode:
switch (static_cast<BloomMode>(value)) {
case BloomMode::Off:
return "Off";
case BloomMode::Classic:
return "Classic";
case BloomMode::Dusk:
return "Dusklight";
}
break;
case GraphicsOption::DepthOfFieldMode:
switch (static_cast<DepthOfFieldMode>(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<bool>(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<SteppedCarousel>(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
+25 -11
View File
@@ -1,10 +1,10 @@
#pragma once
#include "button.hpp"
#include "component.hpp"
#include "document.hpp"
#include "ui.hpp"
#include <algorithm>
#include <functional>
#include <memory>
#include <type_traits>
@@ -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<std::unique_ptr<Component> > mComponents;
SteppedCarousel* mCarousel;
SteppedCarousel* mCarousel = nullptr;
Rml::Element* mRoot;
u64 mSubscription = 0;
u32 mLastRenderWidth = 0;
u32 mLastRenderHeight = 0;
};
} // namespace dusk::ui
+8 -42
View File
@@ -440,24 +440,15 @@ SelectButton& config_int_select(Pane& leftPane, Pane& rightPane, ConfigVar<int>&
return button;
}
template <typename T>
void graphics_tuner_control(Window& window, Pane& leftPane, Pane& rightPane, ConfigVar<T>& 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<T, float>) {
return format_graphics_setting_value(
option, float_setting_percent(var));
} else {
return format_graphics_setting_value(
option, static_cast<int>(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<int>(Resampler::Bilinear),
.valueMax = static_cast<int>(Resampler::Area),
.defaultValue = static_cast<int>(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<int>(BloomMode::Off),
.valueMax = static_cast<int>(BloomMode::Dusk),
.defaultValue = static_cast<int>(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<int>(DepthOfFieldMode::Off),
.valueMax = static_cast<int>(DepthOfFieldMode::Dusk),
.defaultValue = static_cast<int>(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<int>(false),
.valueMax = static_cast<int>(true),
.defaultValue = static_cast<int>(false),
});
leftPane.register_control(
leftPane.add_select_button({
+2 -10
View File
@@ -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);