diff options
Diffstat (limited to 'src/video_core/post_processing/fx_effect.cpp')
| -rw-r--r-- | src/video_core/post_processing/fx_effect.cpp | 307 |
1 files changed, 307 insertions, 0 deletions
diff --git a/src/video_core/post_processing/fx_effect.cpp b/src/video_core/post_processing/fx_effect.cpp new file mode 100644 index 0000000000..7bb36b0467 --- /dev/null +++ b/src/video_core/post_processing/fx_effect.cpp @@ -0,0 +1,307 @@ +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + +#include <algorithm> + +#include "bundled_fx_effects.h" +#include "common/fs/file.h" +#include "common/fs/fs.h" +#include "common/fs/fs_util.h" +#include "common/fs/path_util.h" +#include "common/logging.h" +#include "video_core/post_processing/fx_compile.h" +#include "video_core/post_processing/fx_effect.h" + +namespace VideoCore { + +namespace { + +constexpr u32 CATALOG_PROBE_WIDTH = 1280; +constexpr u32 CATALOG_PROBE_HEIGHT = 720; +constexpr u32 CATALOG_PROBE_DEPTH = 8; + +std::vector<FxEffectDesc> catalog; +bool catalog_scanned = false; + +const reshadefx::annotation* FindAnnotation(const std::vector<reshadefx::annotation>& annotations, + std::string_view name) { + const auto it = std::find_if(annotations.begin(), annotations.end(), + [&](const reshadefx::annotation& a) { return a.name == name; }); + if (it == annotations.end()) { + return nullptr; + } + return &*it; +} + +std::string AnnotationString(const std::vector<reshadefx::annotation>& annotations, + std::string_view name) { + const reshadefx::annotation* a = FindAnnotation(annotations, name); + if (a == nullptr) { + return std::string(); + } + return a->value.string_data; +} + +bool AnnotationFloat(const std::vector<reshadefx::annotation>& annotations, std::string_view name, + f32& out) { + const reshadefx::annotation* a = FindAnnotation(annotations, name); + if (a == nullptr) { + return false; + } + if (a->type.is_floating_point()) { + out = a->value.as_float[0]; + return true; + } + if (a->type.is_integral()) { + out = static_cast<f32>(a->value.as_int[0]); + return true; + } + return false; +} + +FxUiType ParseUiType(std::string_view value) { + if (value == "slider") { + return FxUiType::Slider; + } + if (value == "drag") { + return FxUiType::Drag; + } + if (value == "combo") { + return FxUiType::Combo; + } + if (value == "radio") { + return FxUiType::Radio; + } + if (value == "check" || value == "checkbox") { + return FxUiType::CheckBox; + } + if (value == "color") { + return FxUiType::Color; + } + if (value == "input") { + return FxUiType::InputBox; + } + return FxUiType::Hidden; +} + +std::vector<std::string> SplitItems(const std::string& items) { + std::vector<std::string> out; + std::string current; + for (const char c : items) { + if (c == '\0') { + out.push_back(current); + current.clear(); + continue; + } + current += c; + } + if (!current.empty()) { + out.push_back(current); + } + return out; +} + +FxUniformDesc DescribeUniform(const reshadefx::uniform& info) { + FxUniformDesc desc; + desc.name = info.name; + desc.components = std::min<u32>(info.type.components(), 4); + + if (info.type.is_boolean()) { + desc.kind = FxUniformKind::Boolean; + } else if (info.type.is_integral()) { + desc.kind = FxUniformKind::Integer; + } else { + desc.kind = FxUniformKind::Floating; + } + + desc.label = AnnotationString(info.annotations, "ui_label"); + if (desc.label.empty()) { + desc.label = info.name; + } + desc.tooltip = AnnotationString(info.annotations, "ui_tooltip"); + desc.category = AnnotationString(info.annotations, "ui_category"); + desc.ui_type = ParseUiType(AnnotationString(info.annotations, "ui_type")); + desc.items = SplitItems(AnnotationString(info.annotations, "ui_items")); + + if (desc.kind == FxUniformKind::Boolean) { + desc.ui_min = 0.0f; + desc.ui_max = 1.0f; + desc.ui_step = 1.0f; + } else if (desc.kind == FxUniformKind::Integer) { + desc.ui_min = 0.0f; + desc.ui_max = 100.0f; + desc.ui_step = 1.0f; + } + + void(AnnotationFloat(info.annotations, "ui_min", desc.ui_min)); + void(AnnotationFloat(info.annotations, "ui_max", desc.ui_max)); + void(AnnotationFloat(info.annotations, "ui_step", desc.ui_step)); + + if (desc.ui_step <= 0.0f) { + desc.ui_step = 0.01f; + if (desc.kind != FxUniformKind::Floating) { + desc.ui_step = 1.0f; + } + } + if (desc.ui_max < desc.ui_min) { + std::swap(desc.ui_min, desc.ui_max); + } + + if (info.has_initializer_value) { + for (u32 i = 0; i < desc.components; ++i) { + if (desc.kind == FxUniformKind::Floating) { + desc.default_value[i] = info.initializer_value.as_float[i]; + } else { + desc.default_value[i] = static_cast<f32>(info.initializer_value.as_int[i]); + } + } + } + + return desc; +} + +FxEffectDesc DescribeEffect(const std::filesystem::path& path, const std::filesystem::path& root) { + FxEffectDesc desc; + desc.file = Common::FS::PathToUTF8String(std::filesystem::relative(path, root)); + desc.name = Common::FS::PathToUTF8String(path.stem()); + + const auto compiled = + CompileFxEffect(path, CATALOG_PROBE_WIDTH, CATALOG_PROBE_HEIGHT, CATALOG_PROBE_DEPTH); + if (!compiled.Succeeded()) { + desc.error = compiled.error; + return desc; + } + + for (const auto& technique : compiled.module.techniques) { + desc.techniques.push_back(technique.name); + } + + if (!compiled.module.techniques.empty()) { + const auto& annotations = compiled.module.techniques.front().annotations; + desc.label = AnnotationString(annotations, "ui_label"); + desc.description = AnnotationString(annotations, "ui_tooltip"); + } + if (desc.label.empty()) { + desc.label = desc.name; + } + + for (const auto& uniform : compiled.module.uniforms) { + FxUniformDesc uniform_desc = DescribeUniform(uniform); + if (uniform_desc.ui_type == FxUiType::Hidden) { + continue; + } + desc.uniforms.push_back(std::move(uniform_desc)); + } + + return desc; +} + +} // Anonymous namespace + +std::filesystem::path GetFxRootDirectory() { + return Common::FS::GetEdenPath(Common::FS::EdenPath::PostShaderDir); +} + +std::vector<std::filesystem::path> GetFxIncludePaths(const std::filesystem::path& effect_path) { + const auto root = GetFxRootDirectory(); + std::vector<std::filesystem::path> paths; + paths.push_back(effect_path.parent_path()); + paths.push_back(root); + paths.push_back(root / "Shaders"); + + const auto last = std::unique(paths.begin(), paths.end()); + paths.erase(last, paths.end()); + return paths; +} + +std::filesystem::path ResolveFxTexturePath(const std::filesystem::path& effect_path, + std::string_view source) { + const auto root = GetFxRootDirectory(); + const std::filesystem::path name{source}; + + const std::array candidates{ + effect_path.parent_path() / name, + root / "Textures" / name, + root / name, + }; + + for (const auto& candidate : candidates) { + if (Common::FS::Exists(candidate)) { + return candidate; + } + } + + return std::filesystem::path(); +} + +void ReloadFxCatalog() { + catalog.clear(); + catalog_scanned = true; + + const auto root = GetFxRootDirectory(); + if (!Common::FS::Exists(root) && !Common::FS::CreateDirs(root)) { + return; + } + + for (const auto& bundled : BUNDLED_FX_EFFECTS) { + const auto path = root / bundled.name; + if (Common::FS::Exists(path)) { + continue; + } + void(Common::FS::WriteStringToFile(path, Common::FS::FileType::TextFile, bundled.source)); + } + + std::vector<std::filesystem::path> effect_files; + Common::FS::IterateDirEntriesRecursively( + root, + [&](const std::filesystem::directory_entry& entry) { + if (entry.path().extension() == ".fx") { + effect_files.push_back(entry.path()); + } + return true; + }, + Common::FS::DirEntryFilter::File); + + std::sort(effect_files.begin(), effect_files.end()); + + for (const auto& file : effect_files) { + FxEffectDesc desc = DescribeEffect(file, root); + if (!desc.error.empty()) { + LOG_WARNING(Render, "Post-processing effect '{}' failed to compile:\n{}", desc.file, + desc.error); + } + catalog.push_back(std::move(desc)); + } + + const size_t usable = std::count_if(catalog.begin(), catalog.end(), + [](const FxEffectDesc& d) { return d.Valid(); }); + LOG_INFO(Render, "Loaded {} post-processing effects ({} usable)", catalog.size(), usable); +} + +const std::vector<FxEffectDesc>& GetFxCatalog() { + if (!catalog_scanned) { + ReloadFxCatalog(); + } + return catalog; +} + +const FxEffectDesc* FindFxEffect(std::string_view file) { + const auto& effects = GetFxCatalog(); + const auto it = std::find_if(effects.begin(), effects.end(), + [&](const FxEffectDesc& d) { return d.file == file; }); + if (it == effects.end()) { + return nullptr; + } + return &*it; +} + +const FxUniformDesc* FindFxUniform(const FxEffectDesc& effect, std::string_view name) { + const auto it = std::find_if(effect.uniforms.begin(), effect.uniforms.end(), + [&](const FxUniformDesc& u) { return u.name == name; }); + if (it == effect.uniforms.end()) { + return nullptr; + } + return &*it; +} + +} // namespace VideoCore |
