diff options
Diffstat (limited to 'src/video_core/post_processing/fx_preset.cpp')
| -rw-r--r-- | src/video_core/post_processing/fx_preset.cpp | 261 |
1 files changed, 261 insertions, 0 deletions
diff --git a/src/video_core/post_processing/fx_preset.cpp b/src/video_core/post_processing/fx_preset.cpp new file mode 100644 index 0000000000..bd66a7d807 --- /dev/null +++ b/src/video_core/post_processing/fx_preset.cpp @@ -0,0 +1,261 @@ +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + +#include <algorithm> + +#include "bundled_fx_presets.h" +#include "common/fs/file.h" +#include "common/fs/fs.h" +#include "common/fs/path_util.h" +#include "common/settings.h" +#include "video_core/post_processing/fx_chain.h" +#include "video_core/post_processing/fx_preset.h" + +namespace VideoCore { + +namespace { + +std::vector<FxPresetDesc> preset_catalog; +bool preset_catalog_scanned = false; + +std::string Trim(std::string_view value) { + size_t begin = 0; + while (begin < value.size() && (value[begin] == ' ' || value[begin] == '\t')) { + ++begin; + } + size_t end = value.size(); + while (end > begin) { + const char c = value[end - 1]; + if (c != ' ' && c != '\t' && c != '\r' && c != '\n') { + break; + } + --end; + } + return std::string(value.substr(begin, end - begin)); +} + +FxPresetDesc ParsePreset(std::string_view text) { + FxPresetDesc desc; + + size_t start = 0; + while (start <= text.size()) { + size_t end = text.find('\n', start); + if (end == std::string_view::npos) { + end = text.size(); + } + const std::string_view line = text.substr(start, end - start); + start = end + 1; + + const size_t equals = line.find('='); + if (equals == std::string_view::npos) { + continue; + } + + const std::string key = Trim(line.substr(0, equals)); + const std::string value = Trim(line.substr(equals + 1)); + + if (key == "name") { + desc.name = value; + } else if (key == "description") { + desc.description = value; + } else if (key == "chain") { + desc.chain = value; + } + } + + return desc; +} + +std::string PresetFileName(std::string_view name) { + std::string out; + for (const char c : name) { + if (c == ' ') { + out += '_'; + continue; + } + const bool keep = (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || + (c >= '0' && c <= '9') || c == '-' || c == '_'; + if (keep) { + out += c; + } + } + if (out.empty()) { + out = "preset"; + } + return out + ".fxp"; +} + +std::string ComposePreset(const FxPresetDesc& desc) { + std::string out; + out += "name="; + out += desc.name; + out += '\n'; + out += "description="; + out += desc.description; + out += '\n'; + out += "chain="; + out += desc.chain; + out += '\n'; + return out; +} + +} // Anonymous namespace + +std::filesystem::path GetFxPresetDirectory() { + return Common::FS::GetEdenPath(Common::FS::EdenPath::PostPresetDir); +} + +void ReloadFxPresetCatalog() { + preset_catalog.clear(); + preset_catalog_scanned = true; + + const auto root = GetFxPresetDirectory(); + if (!Common::FS::Exists(root) && !Common::FS::CreateDirs(root)) { + return; + } + + std::vector<std::string> bundled_names; + for (const auto& bundled : BUNDLED_FX_PRESETS) { + const auto path = root / bundled.name; + bundled_names.emplace_back(bundled.name); + if (Common::FS::Exists(path)) { + continue; + } + void(Common::FS::WriteStringToFile(path, Common::FS::FileType::TextFile, bundled.source)); + } + + std::vector<std::filesystem::path> preset_files; + Common::FS::IterateDirEntriesRecursively( + root, + [&](const std::filesystem::directory_entry& entry) { + if (entry.path().extension() == ".fxp") { + preset_files.push_back(entry.path()); + } + return true; + }, + Common::FS::DirEntryFilter::File); + + std::sort(preset_files.begin(), preset_files.end()); + + for (const auto& file : preset_files) { + const std::string text = + Common::FS::ReadStringFromFile(file, Common::FS::FileType::TextFile); + + FxPresetDesc desc = ParsePreset(text); + if (desc.name.empty() || desc.chain.empty()) { + continue; + } + + desc.file = Common::FS::PathToUTF8String(file.filename()); + desc.bundled = std::find(bundled_names.begin(), bundled_names.end(), desc.file) != + bundled_names.end(); + preset_catalog.push_back(std::move(desc)); + } +} + +const std::vector<FxPresetDesc>& GetFxPresetCatalog() { + if (!preset_catalog_scanned) { + ReloadFxPresetCatalog(); + } + return preset_catalog; +} + +const FxPresetDesc* FindFxPreset(std::string_view name) { + const auto& presets = GetFxPresetCatalog(); + const auto it = std::find_if(presets.begin(), presets.end(), + [&](const FxPresetDesc& p) { return p.name == name; }); + if (it == presets.end()) { + return nullptr; + } + return &*it; +} + +bool ApplyFxPreset(std::string_view name) { + const FxPresetDesc* preset = FindFxPreset(name); + if (preset == nullptr) { + return false; + } + + FxChain::Instance().SetEntries(ParseFxChain(preset->chain)); + FxChain::Instance().DropUnknownEntries(); + FxChain::Instance().StoreToSettings(); + SetActiveFxPreset(preset->name); + return true; +} + +bool SaveFxPreset(std::string_view name, std::string_view description) { + if (!IsFxPresetName(name)) { + return false; + } + + const auto root = GetFxPresetDirectory(); + if (!Common::FS::Exists(root) && !Common::FS::CreateDirs(root)) { + return false; + } + + FxPresetDesc desc; + desc.name = std::string(name); + desc.description = std::string(description); + desc.chain = SerializeFxChain(FxChain::Instance().Entries()); + + const std::string body = ComposePreset(desc); + const std::string file = PresetFileName(name); + if (Common::FS::WriteStringToFile(root / file, Common::FS::FileType::TextFile, body) != + body.size()) { + return false; + } + + ReloadFxPresetCatalog(); + SetActiveFxPreset(name); + return true; +} + +bool DeleteFxPreset(std::string_view name) { + const FxPresetDesc* preset = FindFxPreset(name); + if (preset == nullptr || preset->bundled) { + return false; + } + + const auto path = GetFxPresetDirectory() / preset->file; + if (!Common::FS::RemoveFile(path)) { + return false; + } + + if (GetActiveFxPreset() == name) { + SetActiveFxPreset(std::string_view()); + } + ReloadFxPresetCatalog(); + return true; +} + +bool IsFxPresetName(std::string_view name) { + if (name.empty()) { + return false; + } + return name.find_first_of("\r\n=") == std::string_view::npos; +} + +std::string GetActiveFxPreset() { + return Settings::values.post_shader_preset.GetValue(); +} + +void SetActiveFxPreset(std::string_view name) { + Settings::values.post_shader_preset.SetValue(std::string(name)); +} + +bool IsActiveFxPresetModified() { + const std::string active = GetActiveFxPreset(); + if (active.empty()) { + return false; + } + + const FxPresetDesc* preset = FindFxPreset(active); + if (preset == nullptr) { + return true; + } + + return SerializeFxChain(FxChain::Instance().Entries()) != + SerializeFxChain(ParseFxChain(preset->chain)); +} + +} // namespace VideoCore |
