Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
[Info]
Version = 4-1-0
Version = 4-2-0

[Nexus]
nexusmodid = 130375
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
[Info]
Version = 3-1-0
Version = 3-2-0

[Nexus]
nexusmodid = 112739
Expand Down
109 changes: 109 additions & 0 deletions src/Feature.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -334,6 +334,115 @@ bool Feature::ReapplyOverrideSettings()
return false;
}

void Feature::DrawSimpleSettings()
{
bool* runtimeEnabled = GetEnabledFlag();
auto presets = GetQualityPresets();
const std::string shortName = GetShortName();

// On/off uses the runtime flag when available, otherwise the boot-toggle (every
// feature supports it). Boot path requires a restart; only mark it as such once
// the user actually flips it from the value captured at first render.
const bool runtimeAvailable = runtimeEnabled != nullptr;
const bool currentlyOn = runtimeAvailable ?
*runtimeEnabled :
!globals::state->IsFeatureDisabled(shortName);

// Loaded features were enabled at boot by definition; a restart is needed only
// if the user has since toggled the boot setting to disabled.
const bool restartPending = !runtimeAvailable && globals::state->IsFeatureDisabled(shortName);

const auto& palette = globals::menu->GetTheme().StatusPalette;
if (restartPending)
ImGui::PushStyleColor(ImGuiCol_Text, palette.RestartNeeded);

// Slider label includes the version (e.g. "Screen Space GI v2.1.0").
std::string label = GetName();
if (!version.empty()) {
label += " v";
label += version;
}

const int n = static_cast<int>(presets.size());

// Resolve "Custom" on first render by asking the feature to match its settings
// against its presets; saves the user from a "Custom" label when settings still
// match a preset (e.g. after JSON load).
if (currentlyOn && n > 0 && lastAppliedQualityIdx < 0)
lastAppliedQualityIdx = DetectCurrentQuality();

// When in Custom state with presets available, give the slider an extra stop at
// n+1 so dragging back to position 1 still fires SliderInt's change event and
// applies the first preset. Without this, Custom and "preset 0" would share
// position 1 and clicking 1 would be a no-op.
const bool inCustom = currentlyOn && n > 0 && (lastAppliedQualityIdx < 0 || lastAppliedQualityIdx >= n);
const int sliderMax = inCustom ? n + 1 : std::max(1, n);

int currentValue;
std::string overlay;
if (!currentlyOn) {
currentValue = 0;
overlay = "Off";
} else if (n == 0) {
currentValue = 1;
overlay = "On";
} else if (inCustom) {
currentValue = n + 1;
overlay = "Custom";
} else {
currentValue = lastAppliedQualityIdx + 1;
overlay.assign(presets[lastAppliedQualityIdx].label.data(), presets[lastAppliedQualityIdx].label.size());
}

int v = currentValue;
if (ImGui::SliderInt(label.c_str(), &v, 0, sliderMax, overlay.c_str(), ImGuiSliderFlags_AlwaysClamp)) {
const bool wantOn = v >= 1;
if (currentlyOn != wantOn) {
if (runtimeAvailable)
*runtimeEnabled = wantOn;
else
ToggleAtBootSetting();
}
if (wantOn && n > 0 && v <= n) {
const int idx = v - 1;
ApplyPreset(this, presets[idx]);
lastAppliedQualityIdx = idx;
} else if (!wantOn) {
lastAppliedQualityIdx = -1;
}
// v == n+1 (Custom slot): leave lastAppliedQualityIdx as-is.
}
Comment thread
coderabbitai[bot] marked this conversation as resolved.

if (auto _tt = Util::HoverTooltipWrapper()) {
auto [description, keyFeatures] = GetFeatureSummary();
if (!description.empty())
ImGui::TextWrapped("%s", description.c_str());
if (!keyFeatures.empty()) {
if (!description.empty())
ImGui::Spacing();
ImGui::TextUnformatted("Key features:");
for (const auto& k : keyFeatures)
ImGui::BulletText("%s", k.c_str());
}
if (currentValue >= 1 && currentValue <= n) {
const auto& desc = presets[currentValue - 1].description;
if (!desc.empty()) {
ImGui::Separator();
ImGui::TextWrapped("%s: %.*s", overlay.c_str(),
static_cast<int>(desc.size()), desc.data());
}
}
if (!runtimeAvailable) {
ImGui::Separator();
ImGui::TextColored(palette.RestartNeeded,
"Toggling Off/On for this feature requires a game restart.");
}
}

if (restartPending)
ImGui::PopStyleColor();
}

void Feature::DrawUnloadedUI()
{
// Prioritize detailed failure message if available
Expand Down
85 changes: 85 additions & 0 deletions src/Feature.h
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,68 @@
#include "FeatureCategories.h"
#include "FeatureConstraints.h"
#include "FeatureVersions.h"
#include <span> // GetQualityPresets() return type
#ifdef TRACY_ENABLE
# include <Tracy/Tracy.hpp>
# include <Tracy/TracyD3D11.hpp>
#endif

struct Feature
{
// Generic named-preset entry. The enum type E is feature-specific; the surrounding
// shape is shared so SSGI's QualityPreset and WetnessEffects::ClimatePreset use
// the same dispatch.
template <typename E>
struct Preset
{
E id;
std::string_view label;
std::string_view description;
// apply receives the preset id so one shared thunk can dispatch all entries
// of an enum. nullptr apply is a no-op (e.g. a "Custom" sentinel).
void (*apply)(Feature*, E) = nullptr;
// Optional VR variant; falls back to apply when null.
void (*vrApply)(Feature*, E) = nullptr;
};

// Quality tiers for the simple menu. Authors declare a sparse subset; missing
// tiers don't appear in the UI.
enum class QualityLevel : uint8_t
{
Low,
Medium,
High,
Ultra
};
using QualityPreset = Preset<QualityLevel>;

// Apply a preset, picking vrApply on VR when available.
template <typename E>
static void ApplyPreset(Feature* feature, const Preset<E>& preset)
{
if (preset.vrApply && REL::Module::IsVR())
preset.vrApply(feature, preset.id);
else if (preset.apply)
preset.apply(feature, preset.id);
}

// Apply a quality tier without going through ImGui (controller menu, scripts).
// Returns false if the feature does not expose that tier.
bool ApplyQualityPreset(QualityLevel level)
{
auto presets = GetQualityPresets();
for (size_t i = 0; i < presets.size(); ++i) {
if (presets[i].id == level) {
ApplyPreset(this, presets[i]);
if (auto* en = GetEnabledFlag())
*en = true;
lastAppliedQualityIdx = static_cast<int>(i);
return true;
}
}
return false;
}

// For global settings search
struct SettingSearchEntry
{
Expand All @@ -24,6 +79,7 @@ struct Feature
// Nexus Mods base URL for Skyrim Special Edition
static constexpr std::string_view NEXUS_BASE_URL = "https://www.nexusmods.com/skyrimspecialedition/mods/";
bool loaded = false;
int lastAppliedQualityIdx = -1; // -1 = unknown/Custom
std::string version;
std::string failedLoadedMessage;

Expand Down Expand Up @@ -91,6 +147,35 @@ struct Feature
virtual void DrawSettings() {}
virtual void DrawUnloadedUI();

/**
* Quality preset list for the global Simple menu.
* Default: empty — feature renders just an Enable toggle (or nothing if no enable flag).
* Override and return a span over a `static constexpr std::array<QualityPreset, N>` to
* expose Off/Low/Medium/High etc. Missing tiers are skipped gracefully.
*/
virtual std::span<const QualityPreset> GetQualityPresets() const { return {}; }

/**
* Pointer to the feature's `Enabled` bool, used by the Simple menu's Off button.
* Return nullptr if the feature has no enable toggle (in which case Off is hidden).
*/
virtual bool* GetEnabledFlag() { return nullptr; }

/**
* Returns the index into GetQualityPresets() that matches the feature's current
* settings, or -1 when the settings don't match any preset (Custom). Used by the
* Simple menu to position the slider correctly on first render after a JSON load.
* Default returns -1; features that can compare their settings to their tiers
* should override.
*/
virtual int DetectCurrentQuality() const { return -1; }

/**
* Render the Simple-mode settings UI: Off button + quality preset row.
* Implemented in Feature.cpp; features generally do not need to override.
*/
void DrawSimpleSettings();
Comment thread
coderabbitai[bot] marked this conversation as resolved.

virtual void ReflectionsPrepass() {};
virtual void Prepass() {}
virtual void EarlyPrepass() {}
Expand Down
86 changes: 68 additions & 18 deletions src/Features/ScreenSpaceGI.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,71 @@ void ScreenSpaceGI::RestoreDefaultSettings()
recompileFlag = true;
}

namespace
{
// Per-tier values applied by ApplyTier.
struct QualityTier
{
uint numSlices;
uint numSteps;
int resolutionMode;
bool enableGI;
};

void ApplyTier(Feature* f, const QualityTier& q)
{
auto* ssgi = static_cast<ScreenSpaceGI*>(f);
ssgi->settings.Enabled = true;
ssgi->settings.NumSlices = q.numSlices;
ssgi->settings.NumSteps = q.numSteps;
ssgi->settings.ResolutionMode = q.resolutionMode;
ssgi->settings.EnableBlur = true;
ssgi->settings.EnableGI = q.enableGI;
ssgi->recompileFlag = true;
}

// Shared tier table — both the apply lambdas and DetectCurrentQuality read from here
// so a single edit keeps application and detection in sync.
// VR High intentionally mirrors flat (vrApply=nullptr -> ApplyPreset falls back to apply).
static constexpr std::array<std::pair<QualityTier, QualityTier>, 3> kTierValues = { {
{ { 10, 12, 2, true }, { 1, 6, 2, false } }, // Low (flat / VR)
{ { 4, 8, 1, true }, { 3, 8, 1, false } }, // Medium
{ { 4, 8, 0, true }, { 4, 8, 0, true } }, // High (no VR variant — uses flat)
} };

static constexpr std::array<Feature::QualityPreset, 3> kQualityPresets = { {
{ Feature::QualityLevel::Low, "Low", "Quarter resolution, blurred. AO + basic GI.",
[](Feature* f, Feature::QualityLevel) { ApplyTier(f, kTierValues[0].first); },
[](Feature* f, Feature::QualityLevel) { ApplyTier(f, kTierValues[0].second); } },
{ Feature::QualityLevel::Medium, "Medium", "Half resolution, balanced quality and performance.",
[](Feature* f, Feature::QualityLevel) { ApplyTier(f, kTierValues[1].first); },
[](Feature* f, Feature::QualityLevel) { ApplyTier(f, kTierValues[1].second); } },
{ Feature::QualityLevel::High, "High", "Full resolution, clean output with full GI.",
[](Feature* f, Feature::QualityLevel) { ApplyTier(f, kTierValues[2].first); },
nullptr },
} };
}

std::span<const Feature::QualityPreset> ScreenSpaceGI::GetQualityPresets() const
{
return kQualityPresets;
}

int ScreenSpaceGI::DetectCurrentQuality() const
{
const bool isVR = REL::Module::IsVR();
for (size_t i = 0; i < kTierValues.size(); ++i) {
const auto& q = isVR ? kTierValues[i].second : kTierValues[i].first;
if (settings.NumSlices == q.numSlices &&
settings.NumSteps == q.numSteps &&
settings.ResolutionMode == q.resolutionMode &&
settings.EnableGI == q.enableGI &&
settings.EnableBlur)
return static_cast<int>(i);
}
return -1;
}

void ScreenSpaceGI::DrawSettings()
{
static bool showAdvanced;
Expand Down Expand Up @@ -108,36 +173,21 @@ void ScreenSpaceGI::DrawSettings()

ImGui::TableNextColumn();
if (ImGui::Button("Low", { -1, 0 })) {
settings.NumSlices = 10;
settings.NumSteps = 12;
settings.ResolutionMode = 2;
settings.EnableBlur = true;
settings.EnableGI = true;
recompileFlag = true;
ApplyTier(this, kTierValues[0].first);
}
if (auto _tt = Util::HoverTooltipWrapper())
ImGui::Text("Quarter res and blurry.");

ImGui::TableNextColumn();
if (ImGui::Button("Standard", { -1, 0 })) {
settings.NumSlices = 4;
settings.NumSteps = 8;
settings.ResolutionMode = 1;
settings.EnableBlur = true;
settings.EnableGI = true;
recompileFlag = true;
ApplyTier(this, kTierValues[1].first);
}
if (auto _tt = Util::HoverTooltipWrapper())
ImGui::Text("Half res and somewhat stable.");

ImGui::TableNextColumn();
if (ImGui::Button("Extreme", { -1, 0 })) {
settings.NumSlices = 4;
settings.NumSteps = 8;
settings.ResolutionMode = 0;
settings.EnableBlur = true;
settings.EnableGI = true;
recompileFlag = true;
ApplyTier(this, kTierValues[2].first);
}
if (auto _tt = Util::HoverTooltipWrapper())
ImGui::Text("Full res and clean.");
Expand Down
4 changes: 4 additions & 0 deletions src/Features/ScreenSpaceGI.h
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,10 @@ struct ScreenSpaceGI : Feature
virtual void RestoreDefaultSettings() override;
virtual void DrawSettings() override;

virtual std::span<const QualityPreset> GetQualityPresets() const override;
virtual bool* GetEnabledFlag() override { return &settings.Enabled; }
virtual int DetectCurrentQuality() const override;

virtual void LoadSettings(json& o_json) override;
virtual void SaveSettings(json& o_json) override;

Expand Down
Loading
Loading