diff options
Diffstat (limited to 'src/video_core/post_processing/fx_chain.cpp')
| -rw-r--r-- | src/video_core/post_processing/fx_chain.cpp | 321 |
1 files changed, 321 insertions, 0 deletions
diff --git a/src/video_core/post_processing/fx_chain.cpp b/src/video_core/post_processing/fx_chain.cpp new file mode 100644 index 0000000000..882fdaf726 --- /dev/null +++ b/src/video_core/post_processing/fx_chain.cpp @@ -0,0 +1,321 @@ +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + +#include <algorithm> +#include <cstdlib> + +#include <fmt/format.h> + +#include "common/settings.h" +#include "video_core/post_processing/fx_chain.h" +#include "video_core/post_processing/fx_effect.h" + +namespace VideoCore { + +namespace { + +std::vector<std::string_view> Split(std::string_view value, char separator) { + std::vector<std::string_view> out; + size_t start = 0; + while (start <= value.size()) { + size_t end = value.find(separator, start); + if (end == std::string_view::npos) { + end = value.size(); + } + out.push_back(value.substr(start, end - start)); + start = end + 1; + } + return out; +} + +bool IsSerializableName(std::string_view value) { + return value.find_first_of(";|,=") == std::string_view::npos; +} + +} // Anonymous namespace + +std::vector<FxChainEntry> ParseFxChain(std::string_view value) { + std::vector<FxChainEntry> parsed; + + for (const std::string_view record : Split(value, ';')) { + if (record.empty()) { + continue; + } + + const auto fields = Split(record, '|'); + if (fields.size() < 2 || fields[0].empty() || fields[1].empty()) { + continue; + } + + FxChainEntry entry; + entry.file = std::string(fields[0]); + entry.technique = std::string(fields[1]); + + if (fields.size() >= 3) { + for (const std::string_view assignment : Split(fields[2], ',')) { + const size_t equals = assignment.find('='); + if (equals == std::string_view::npos) { + continue; + } + const std::string name(assignment.substr(0, equals)); + if (name.empty()) { + continue; + } + + std::array<f32, 4> components{}; + size_t index = 0; + for (const std::string_view piece : Split(assignment.substr(equals + 1), '/')) { + if (index >= components.size()) { + break; + } + const std::string text(piece); + if (!text.empty()) { + components[index] = std::strtof(text.c_str(), nullptr); + } + ++index; + } + entry.values.emplace(name, components); + } + } + + parsed.push_back(std::move(entry)); + } + + return parsed; +} + +std::string SerializeFxChain(std::span<const FxChainEntry> entries) { + std::string out; + + for (const auto& entry : entries) { + if (!IsSerializableName(entry.file) || !IsSerializableName(entry.technique)) { + continue; + } + if (!out.empty()) { + out += ';'; + } + out += fmt::format("{}|{}|", entry.file, entry.technique); + + bool first = true; + for (const auto& [name, value] : entry.values) { + if (!IsSerializableName(name)) { + continue; + } + if (!first) { + out += ','; + } + first = false; + out += name; + out += '='; + for (size_t i = 0; i < value.size(); ++i) { + if (i > 0) { + out += '/'; + } + out += fmt::format("{}", value[i]); + } + } + } + + return out; +} + +void UseGlobalFxSettings() { + Settings::values.post_shader_chain.SetGlobal(true); + Settings::values.post_shader_preset.SetGlobal(true); + Settings::values.post_shader_enabled.SetGlobal(true); +} + +void UsePerGameFxSettings() { + const std::string chain = Settings::values.post_shader_chain.GetValue(); + const std::string preset = Settings::values.post_shader_preset.GetValue(); + const bool enabled = Settings::values.post_shader_enabled.GetValue(); + + Settings::values.post_shader_chain.SetGlobal(false); + Settings::values.post_shader_preset.SetGlobal(false); + Settings::values.post_shader_enabled.SetGlobal(false); + + Settings::values.post_shader_chain.SetValue(chain); + Settings::values.post_shader_preset.SetValue(preset); + Settings::values.post_shader_enabled.SetValue(enabled); +} + +FxChain& FxChain::Instance() { + static FxChain instance; + return instance; +} + +FxChainSnapshot FxChain::Snapshot() const { + std::scoped_lock lock{mutex}; + return FxChainSnapshot{ + .entries = entries, + .generation = generation.load(std::memory_order_relaxed), + }; +} + +std::vector<FxChainEntry> FxChain::Entries() const { + std::scoped_lock lock{mutex}; + return entries; +} + +size_t FxChain::Size() const { + std::scoped_lock lock{mutex}; + return entries.size(); +} + +void FxChain::Append(std::string_view file, std::string_view technique) { + { + std::scoped_lock lock{mutex}; + FxChainEntry entry; + entry.file = std::string(file); + entry.technique = std::string(technique); + entries.push_back(std::move(entry)); + } + generation.fetch_add(1, std::memory_order_relaxed); +} + +void FxChain::Replace(size_t index, std::string_view file, std::string_view technique) { + { + std::scoped_lock lock{mutex}; + if (index >= entries.size()) { + return; + } + if (entries[index].file == file && entries[index].technique == technique) { + return; + } + FxChainEntry entry; + entry.file = std::string(file); + entry.technique = std::string(technique); + entries[index] = std::move(entry); + } + generation.fetch_add(1, std::memory_order_relaxed); +} + +void FxChain::Remove(size_t index) { + { + std::scoped_lock lock{mutex}; + if (index >= entries.size()) { + return; + } + entries.erase(entries.begin() + static_cast<std::ptrdiff_t>(index)); + } + generation.fetch_add(1, std::memory_order_relaxed); +} + +void FxChain::Move(size_t index, int delta) { + { + std::scoped_lock lock{mutex}; + if (index >= entries.size()) { + return; + } + const std::ptrdiff_t target = static_cast<std::ptrdiff_t>(index) + delta; + if (target < 0 || target >= static_cast<std::ptrdiff_t>(entries.size())) { + return; + } + std::swap(entries[index], entries[static_cast<size_t>(target)]); + } + generation.fetch_add(1, std::memory_order_relaxed); +} + +void FxChain::Clear() { + { + std::scoped_lock lock{mutex}; + entries.clear(); + } + generation.fetch_add(1, std::memory_order_relaxed); +} + +void FxChain::SetEntries(std::vector<FxChainEntry> next) { + { + std::scoped_lock lock{mutex}; + entries = std::move(next); + } + generation.fetch_add(1, std::memory_order_relaxed); +} + +void FxChain::SetValue(size_t index, std::string_view uniform, const std::array<f32, 4>& value) { + std::scoped_lock lock{mutex}; + if (index >= entries.size()) { + return; + } + entries[index].values[std::string(uniform)] = value; +} + +std::array<f32, 4> FxChain::GetValue(size_t index, std::string_view uniform) const { + std::scoped_lock lock{mutex}; + if (index >= entries.size()) { + return {}; + } + const auto it = entries[index].values.find(std::string(uniform)); + if (it == entries[index].values.end()) { + return {}; + } + return it->second; +} + +std::map<std::string, std::array<f32, 4>> FxChain::EntryValues(size_t index) const { + std::scoped_lock lock{mutex}; + if (index >= entries.size()) { + return {}; + } + return entries[index].values; +} + +bool FxChain::HasValue(size_t index, std::string_view uniform) const { + std::scoped_lock lock{mutex}; + if (index >= entries.size()) { + return false; + } + return entries[index].values.contains(std::string(uniform)); +} + +void FxChain::ResetValues(size_t index) { + std::scoped_lock lock{mutex}; + if (index >= entries.size()) { + return; + } + entries[index].values.clear(); +} + +void FxChain::LoadFromSettings() { + auto parsed = ParseFxChain(Settings::values.post_shader_chain.GetValue()); + + std::scoped_lock lock{mutex}; + if (entries == parsed) { + return; + } + entries = std::move(parsed); + generation.fetch_add(1, std::memory_order_relaxed); +} + +void FxChain::StoreToSettings() const { + std::string serialized; + { + std::scoped_lock lock{mutex}; + serialized = SerializeFxChain(entries); + } + Settings::values.post_shader_chain.SetValue(serialized); +} + +void FxChain::DropUnknownEntries() { + bool changed = false; + { + std::scoped_lock lock{mutex}; + const auto removed = std::remove_if(entries.begin(), entries.end(), [](const FxChainEntry& entry) { + const FxEffectDesc* effect = FindFxEffect(entry.file); + if (effect == nullptr || !effect->Valid()) { + return true; + } + return std::find(effect->techniques.begin(), effect->techniques.end(), + entry.technique) == effect->techniques.end(); + }); + if (removed != entries.end()) { + entries.erase(removed, entries.end()); + changed = true; + } + } + if (changed) { + generation.fetch_add(1, std::memory_order_relaxed); + } +} + +} // namespace VideoCore |
