From 5fa3ddc8e3b3a3a753db2975d9c0cdb9e618d1f0 Mon Sep 17 00:00:00 2001 From: PancakeTAS Date: Sun, 14 Dec 2025 23:35:13 +0100 Subject: [PATCH] refactor(cleanup): new config system --- include/config/config.hpp | 67 ----- include/config/default_conf.hpp | 38 --- lsfg-vk-layer/CMakeLists.txt | 5 + lsfg-vk-layer/src/config.cpp | 238 ++++++++++++++++++ lsfg-vk-layer/src/config.hpp | 69 +++++ lsfg-vk-layer/src/detection.cpp | 121 +++++++++ lsfg-vk-layer/src/detection.hpp | 40 +++ lsfg-vk-layer/src/entrypoint.cpp | 37 ++- lsfg-vk-layer/src/layer.cpp | 66 ++++- lsfg-vk-layer/src/layer.hpp | 47 +++- .../thirdparty/include}/toml.hpp | 218 ++++------------ src/config/config.cpp | 174 ------------- src/main.cpp | 85 ------- 13 files changed, 646 insertions(+), 559 deletions(-) delete mode 100644 include/config/config.hpp delete mode 100644 include/config/default_conf.hpp create mode 100644 lsfg-vk-layer/src/config.cpp create mode 100644 lsfg-vk-layer/src/config.hpp create mode 100644 lsfg-vk-layer/src/detection.cpp create mode 100644 lsfg-vk-layer/src/detection.hpp rename {include/thirdparty => lsfg-vk-layer/thirdparty/include}/toml.hpp (98%) delete mode 100644 src/config/config.cpp delete mode 100644 src/main.cpp diff --git a/include/config/config.hpp b/include/config/config.hpp deleted file mode 100644 index b16deb2..0000000 --- a/include/config/config.hpp +++ /dev/null @@ -1,67 +0,0 @@ -#pragma once - -#include - -#include -#include -#include -#include - -namespace Config { - - /// Global lsfg-vk configuration. - struct GlobalConfiguration { - /// Path to Lossless.dll. - std::string dll; - /// Whether FP16 is force-disabled - bool no_fp16{false}; - - /// Path to the configuration file. - std::filesystem::path config_file; - /// File timestamp of the configuration file - std::chrono::time_point timestamp; - }; - - /// Per-application lsfg-vk configuration. - struct GameConfiguration { - /// The frame generation muliplier - size_t multiplier{2}; - /// The internal flow scale factor - float flowScale{1.0F}; - /// Whether performance mode is enabled - bool performance{false}; - /// Whether HDR is enabled - bool hdr{false}; - - /// Experimental flag for overriding the synchronization method. - VkPresentModeKHR e_present{ VK_PRESENT_MODE_FIFO_KHR }; - - }; - - /// Global configuration. - extern GlobalConfiguration globalConf; - /// Currently active configuration. - extern std::optional currentConf; - - /// - /// Read the configuration file while preserving the previous configuration - /// in case of an error. - /// - /// @param file The path to the configuration file. - /// @param name The preset to activate - /// - /// @throws std::runtime_error if an error occurs while loading the configuration file. - /// - void updateConfig( - const std::string& file, - const std::pair& name - ); - - /// - /// Check if the configuration file is still up-to-date - /// - /// @return Whether the configuration is up-to-date or not. - /// - bool checkStatus(); - -} diff --git a/include/config/default_conf.hpp b/include/config/default_conf.hpp deleted file mode 100644 index 58e6a17..0000000 --- a/include/config/default_conf.hpp +++ /dev/null @@ -1,38 +0,0 @@ -#pragma once - -#include - -const std::string DEFAULT_CONFIG = R"(version = 1 -[global] -# override the location of Lossless Scaling -# dll = "/games/Lossless Scaling/Lossless.dll" -# force-disable fp16 (use on older nvidia cards) -# no_fp16 = true - -# [[game]] # example entry -# exe = "Game.exe" -# -# multiplier = 3 -# flow_scale = 0.7 -# performance_mode = true -# hdr_mode = false -# -# experimental_present_mode = "fifo" - -[[game]] # default vkcube entry -exe = "vkcube" - -multiplier = 4 -performance_mode = true - -[[game]] # default benchmark entry -exe = "benchmark" - -multiplier = 4 -performance_mode = false - -[[game]] # override Genshin Impact -exe = "GenshinImpact.exe" - -multiplier = 3 -)"; diff --git a/lsfg-vk-layer/CMakeLists.txt b/lsfg-vk-layer/CMakeLists.txt index 6606bda..34c4a8a 100644 --- a/lsfg-vk-layer/CMakeLists.txt +++ b/lsfg-vk-layer/CMakeLists.txt @@ -1,4 +1,6 @@ set(LAYER_SOURCES + "src/config.cpp" + "src/detection.cpp" "src/entrypoint.cpp" "src/layer.cpp") @@ -8,5 +10,8 @@ target_link_libraries(lsfg-vk-layer PUBLIC lsfg-vk-common PUBLIC lsfg-vk-backend) +target_include_directories(lsfg-vk-layer SYSTEM + PRIVATE thirdparty/include) + set_target_properties(lsfg-vk-layer PROPERTIES CXX_VISIBILITY_PRESET hidden) diff --git a/lsfg-vk-layer/src/config.cpp b/lsfg-vk-layer/src/config.cpp new file mode 100644 index 0000000..9bbba91 --- /dev/null +++ b/lsfg-vk-layer/src/config.cpp @@ -0,0 +1,238 @@ +#include "config.hpp" +#include "lsfg-vk-backend/lsfgvk.hpp" + +#include +#include +#include +#include +#include +#include +#include + +#define TOML_ENABLE_FORMATTERS 0 +#include + +using namespace lsfgvk::layer; + +namespace { + const char* DEFAULT_CONFIG = R"(version = 2 + +[global] +# dll = '/media/games/Lossless Scaling/Lossless.dll' # if you don't have LS in the default location +allow_fp16 = true # this will improve give a MASSIVE performance boost on AMD, but be super slow on older (!) NVIDIA GPUs + +[[profile]] +name = "4x FG / 85% [Performance]" +active_in = [ # see the wiki for more info + 'vkcube', + 'vkcubepp' +] +multiplier = 4 +flow_scale = 0.85 +performance_mode = true +pacing = 'none' # see the wiki for more info + +[[profile]] +name = "2x FG / 100%" +active_in = 'GenshinImpact.exe' +multiplier = 2 +)"; + + /// parse an activity array from toml value + std::vector activityFromString(const toml::node_view& val) { + std::vector active_in{}; + + if (const auto& as_str = val.value()) { + active_in.push_back(*as_str); + } + + if (const auto& as_arr = val.as_array()) { + for (const auto& item : *as_arr) { + if (const auto& item_str = item.value()) + active_in.push_back(*item_str); + } + } + + return active_in; + } + /// parse a pacing method from string + Pacing parcingFromString(const std::string& str) { + if (str == "none") + return Pacing::None; + throw lsfgvk::error("unknown pacing method: " + str); + } + /// try to find the config + std::filesystem::path findPath() { + // always honor LSFGVK_CONFIG if set + const char* envPath = std::getenv("LSFGVK_CONFIG"); + if (envPath && *envPath != '\0') + return{envPath}; + + // then check the XDG overriden location + const char* xdgPath = std::getenv("XDG_CONFIG_HOME"); + if (xdgPath && *xdgPath != '\0') + return std::filesystem::path(xdgPath) + / "lsfg-vk" / "conf.toml"; + + // fallback to typical user home + const char* homePath = std::getenv("HOME"); + if (homePath && *homePath != '\0') + return std::filesystem::path(homePath) + / ".config" / "lsfg-vk" / "conf.toml"; + + // finally, use system-wide config + return "/etc/lsfg-vk/conf.toml"; + } + /// parse the global configuration + GlobalConf parseGlobalConf(const toml::table& tbl) { + const GlobalConf conf{ + .dll = tbl["dll"].value(), + .allow_fp16 = tbl["allow_fp16"].value_or(true) + }; + + if (conf.dll && !std::filesystem::exists(*conf.dll)) + throw lsfgvk::error("path to dll is invalid"); + + return conf; + } + /// parse a game profile configuration + GameConf parseGameConf(const toml::table& tbl) { + const GameConf conf{ + .name = tbl["name"].value_or("unnamed"), + .active_in = activityFromString(tbl["active_in"]), + .multiplier = tbl["multiplier"].value_or(2U), + .flow_scale = tbl["flow_scale"].value_or(1.0F), + .performance_mode = tbl["performance_mode"].value_or(false), + .pacing = parcingFromString(tbl["pacing"].value_or("none")) + }; + + if (conf.multiplier <= 1) + throw lsfgvk::error("multiplier must be greater than 1"); + if (conf.flow_scale < 0.25F || conf.flow_scale > 1.0F) + throw lsfgvk::error("flow_scale must be between 0.25 and 1.0"); + + return conf; + } + /// parse the global configuration from the environment + GlobalConf parseGlobalConfFromEnv() { + GlobalConf conf{ + .dll = std::nullopt, + .allow_fp16 = true + }; + + const char* dll = std::getenv("LSFGVK_DLL_PATH"); + if (dll && *dll != '\0') + conf.dll = std::string(dll); + const char* no_fp16 = std::getenv("LSFGVK_NO_FP16"); + if (no_fp16 && *no_fp16 != '\0') + conf.allow_fp16 = std::string(no_fp16) != "1"; + + if (conf.dll && !std::filesystem::exists(*conf.dll)) + throw lsfgvk::error("path to dll is invalid"); + + return conf; + } + /// parse a game profile configuration from the environment + GameConf parseGameConfFromEnv() { + GameConf conf{ + .name = "(environment)", + .active_in = {}, + + .multiplier = 2, + .flow_scale = 1.0F, + .performance_mode = false, + .pacing = Pacing::None + }; + + const char* multiplier = std::getenv("LSFGVK_MULTIPLIER"); + if (multiplier) conf.multiplier = static_cast(std::stoul(multiplier)); + const char* flow_scale = std::getenv("LSFGVK_FLOW_SCALE"); + if (flow_scale) conf.flow_scale = std::stof(flow_scale); + const char* performance = std::getenv("LSFGVK_PERFORMANCE_MODE"); + if (performance) conf.performance_mode = std::string(performance) == "1"; + const char* pacing = std::getenv("LSFGVK_PACING"); + if (pacing) conf.pacing = parcingFromString(std::string(pacing)); + + if (conf.multiplier <= 1) + throw lsfgvk::error("multiplier must be greater than 1"); + if (conf.flow_scale < 0.25F || conf.flow_scale > 1.0F) + throw lsfgvk::error("flow_scale must be between 0.25 and 1.0"); + + return conf; + } +} + +Configuration::Configuration() : + path(findPath()), + from_env(std::getenv("LSFGVK_ENV") != nullptr) { + if (std::filesystem::exists(this->path) || this->from_env) + return; + + try { + std::filesystem::create_directories(this->path.parent_path()); + if (!std::filesystem::exists(this->path.parent_path())) + throw lsfgvk::error("unable to create configuration directory"); + + std::ofstream ofs(this->path); + if (!ofs.is_open()) + throw lsfgvk::error("unable to create default configuration file"); + + ofs << DEFAULT_CONFIG; + ofs.close(); + } catch (const std::filesystem::filesystem_error& e) { + throw lsfgvk::error("unable to create default configuration file", e); + } +} + +bool Configuration::tick() { + if (this->from_env) { + if (this->profiles.empty()) { + this->global = parseGlobalConfFromEnv(); + this->profiles.push_back(parseGameConfFromEnv()); + return true; + } + + return false; // no need to tick if from env + } + + // check for updates + try { + auto time = std::filesystem::last_write_time(this->path); + if (time == this->timestamp) + return false; + + this->timestamp = time; + } catch (const std::filesystem::filesystem_error& e) { + throw lsfgvk::error("unable to access configuration file", e); + } + + // parse configuration + GlobalConf global{}; + std::vector profiles{}; + + toml::table tbl; + try { + tbl = toml::parse_file(this->path.string()); + } catch (const toml::parse_error& e) { + throw lsfgvk::error("unable to parse configuration", e); + } + + auto vrs = tbl["version"]; + if (!vrs || !vrs.is_integer() || *vrs.as_integer() != 2) + throw lsfgvk::error("unsupported configuration version"); + + auto gbl = tbl["global"]; + if (gbl && gbl.is_table()) { + global = parseGlobalConf(*gbl.as_table()); + } + + auto pfls = tbl["profile"]; + if (pfls && pfls.is_array_of_tables()) { + for (const auto& pfl : *pfls.as_array()) + profiles.push_back(parseGameConf(*pfl.as_table())); + } + + this->global = std::move(global); + this->profiles = std::move(profiles); + return true; +} diff --git a/lsfg-vk-layer/src/config.hpp b/lsfg-vk-layer/src/config.hpp new file mode 100644 index 0000000..0c7a8b4 --- /dev/null +++ b/lsfg-vk-layer/src/config.hpp @@ -0,0 +1,69 @@ +#pragma once + +#include +#include +#include +#include +#include + +namespace lsfgvk::layer { + + /// global configuration + struct GlobalConf { + /// optional dll override + std::optional dll; + /// should fp16 be allowed + bool allow_fp16; + }; + + /// pacing methods + enum class Pacing { + /// do not perform any pacing (vsync+novrr) + None + }; + + /// game profile configuration + struct GameConf { + /// name of the profile + std::string name; + /// optional activation string/array + std::vector active_in; + /// multiplier for frame generation + size_t multiplier; + /// non-inverted flow scale + float flow_scale; + /// use performance mode + bool performance_mode; + /// pacing method + Pacing pacing; + }; + + /// automatically updating configuration + class Configuration { + public: + /// create a new configuration + /// @throws lsfgvk::error on failure + Configuration(); + + /// reload the configuration from disk if the file has changed + /// @throws lsfgvk::error on failure + /// @return true if the configuration was reloaded + bool tick(); + + /// get the global configuration + /// @return global configuration + [[nodiscard]] const GlobalConf& getGlobalConf() const { return global; } + + /// get the game profiles + /// @return list of game profiles + [[nodiscard]] const std::vector& getProfiles() const { return profiles; } + private: + std::filesystem::path path; + std::chrono::time_point timestamp; + bool from_env{}; + + GlobalConf global; + std::vector profiles; + }; + +} diff --git a/lsfg-vk-layer/src/detection.cpp b/lsfg-vk-layer/src/detection.cpp new file mode 100644 index 0000000..e4144d5 --- /dev/null +++ b/lsfg-vk-layer/src/detection.cpp @@ -0,0 +1,121 @@ +#include "detection.hpp" +#include "config.hpp" + +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +using namespace lsfgvk; +using namespace lsfgvk::layer; + +namespace { + // try to match a profile by id + std::optional match(const std::vector& profiles, const std::string& id) { + for (const auto& profile : profiles) + for (const auto& activation : profile.active_in) + if (activation == id) + return profile; + return std::nullopt; + } +} + +Identification layer::identify() { + Identification id{}; + + // fetch LSFGVK_PROFILE + const char* override = std::getenv("LSFGVK_PROFILE"); + if (override && *override != '\0') + id.override = std::string(override); + + // fetch process exe path + std::array buf{}; + const ssize_t len = readlink("/proc/self/exe", buf.data(), buf.size() - 1); + if (len > 0) { + buf.at(static_cast(len)) = '\0'; + id.executable = std::string(buf.data()); + } + + // if running under wine, fetch the actual exe path + if (id.executable.find("wine") != std::string::npos + || id.executable.find("proton") != std::string::npos) { + + std::ifstream maps("/proc/self/maps"); + std::string line; + while (maps.is_open() && std::getline(maps, line)) { + if (!line.ends_with(".exe")) + continue; + + size_t pos = line.find_first_of('/'); + if (pos == std::string::npos) { + pos = line.find_last_of(' '); + if (pos == std::string::npos) + continue; + pos += 1; // skip space + } + + const std::string wine_executable = line.substr(pos); + if (wine_executable.empty()) + continue; + + id.wine_executable = wine_executable; + break; + } + } + + // fetch process name + std::ifstream comm("/proc/self/comm"); + if (comm.is_open()) { + comm.read(buf.data(), buf.size() - 1); + buf.at(static_cast(comm.gcount())) = '\0'; + + id.process_name = std::string(buf.data()); + if (id.process_name.back() == '\n') + id.process_name.pop_back(); + } + + return id; +} + +std::optional> layer::findProfile( + const Configuration& config, const Identification& id) { + const auto& profiles = config.getProfiles(); + + // check for the environment option first + if (std::getenv("LSFGVK_ENV") != nullptr) + return std::make_pair(IdentType::OVERRIDE, config.getProfiles().front()); + + // then override first + if (id.override.has_value()) { + const auto profile = match(profiles, id.override.value()); + if (profile.has_value()) + return std::make_pair(IdentType::OVERRIDE, profile.value()); + } + + // then check executable + const auto exe_profile = match(profiles, id.executable); + if (exe_profile.has_value()) + return std::make_pair(IdentType::EXECUTABLE, exe_profile.value()); + + // if present, check wine executable next + if (id.wine_executable.has_value()) { + const auto wine_profile = match(profiles, id.wine_executable.value()); + if (wine_profile.has_value()) + return std::make_pair(IdentType::WINE_EXECUTABLE, wine_profile.value()); + } + + // finally, fallback to process name + if (!id.process_name.empty()) { + const auto proc_profile = match(profiles, id.process_name); + if (proc_profile.has_value()) + return std::make_pair(IdentType::PROCESS_NAME, proc_profile.value()); + } + + return std::nullopt; +} diff --git a/lsfg-vk-layer/src/detection.hpp b/lsfg-vk-layer/src/detection.hpp new file mode 100644 index 0000000..5ef9f32 --- /dev/null +++ b/lsfg-vk-layer/src/detection.hpp @@ -0,0 +1,40 @@ +#pragma once + +#include "config.hpp" + +#include +#include +#include + +namespace lsfgvk::layer { + + /// identification data for a process + struct Identification { + /// optional override name + std::optional override; + /// path to exe file + std::string executable; + /// path to exe file when running under wine + std::optional wine_executable; + /// traditional process name (e.g. GameThread) + std::string process_name; + }; + + /// enum describing which identification method was used + enum class IdentType { + OVERRIDE, // identified by override + EXECUTABLE, // identified by executable path + WINE_EXECUTABLE, // identified by wine executable path + PROCESS_NAME // identified by process name + }; + + /// identify the current process + Identification identify(); + + /// find a profile for the current process + /// @param config configuration to search in + /// @param id identification data + /// @return ident pair if found + std::optional> findProfile( + const Configuration& config, const Identification& id); +} diff --git a/lsfg-vk-layer/src/entrypoint.cpp b/lsfg-vk-layer/src/entrypoint.cpp index 68f14ce..3617e67 100644 --- a/lsfg-vk-layer/src/entrypoint.cpp +++ b/lsfg-vk-layer/src/entrypoint.cpp @@ -5,6 +5,7 @@ #include #include #include +#include #include #include #include @@ -12,6 +13,8 @@ #include #include +using namespace lsfgvk; + namespace { /// reinterpret cast helper with const_cast template @@ -42,6 +45,11 @@ namespace { PFN_vkGetInstanceProcAddr nxvkGetInstanceProcAddr{nullptr}; PFN_vkGetDeviceProcAddr nxvkGetDeviceProcAddr = nullptr; + auto& layer() { + static std::optional instance; // NOLINT + return instance; + } + VkInstance gInstance{VK_NULL_HANDLE}; // if there are multiple instances, we scream out loud, oke? // create instance @@ -49,6 +57,15 @@ namespace { const VkInstanceCreateInfo* info, const VkAllocationCallbacks* alloc, VkInstance* instance) { + // try to load lsfg-vk layer + try { + if (!layer().has_value()) + layer().emplace(); + } catch (const std::exception& e) { + std::cerr << "lsfg-vk: something went wrong during lsfg-vk layer initialization:\n"; + std::cerr << "- " << e.what() << '\n'; + } + // apply layer chaining auto* layerInfo = vcast(info->pNext); while (layerInfo && (layerInfo->sType != VK_STRUCTURE_TYPE_LOADER_INSTANCE_CREATE_INFO @@ -86,11 +103,14 @@ namespace { return VK_ERROR_INITIALIZATION_FAILED; } - auto requiredExtensions = lsfgvk::requiredInstanceExtensions(); + const auto& l = layer(); + if (!l.has_value() || !l->active()) // skip inactive + return vkCreateInstance(info, alloc, instance); + auto extensions = add_extensions( info->ppEnabledExtensionNames, info->enabledExtensionCount, - requiredExtensions); + l->instanceExtensions()); VkInstanceCreateInfo newInfo = *info; newInfo.enabledExtensionCount = static_cast(extensions.size()); @@ -109,8 +129,8 @@ namespace { } // map of devices to layer instances - std::unordered_map& device2InstanceMap() { - static std::unordered_map map; // NOLINT + std::unordered_map& device2InstanceMap() { + static std::unordered_map map; // NOLINT return map; } @@ -174,11 +194,14 @@ namespace { return VK_ERROR_INITIALIZATION_FAILED; } - auto requiredExtensions = lsfgvk::requiredDeviceExtensions(); + const auto& l = layer(); + if (!l.has_value() || !l->active()) // skip inactive + return vkCreateDevice(physicalDevice, info, alloc, device); + auto extensions = add_extensions( info->ppEnabledExtensionNames, info->enabledExtensionCount, - requiredExtensions); + l->deviceExtensions()); VkDeviceCreateInfo newInfo = *info; newInfo.enabledExtensionCount = static_cast(extensions.size()); @@ -195,7 +218,7 @@ namespace { // create layer instance try { device2InstanceMap().emplace(*device, - lsfgvk::LayerInstance(gInstance, *device, setLoaderData)); + layer::LayerInstance(*l, gInstance, *device, setLoaderData)); } catch (const std::exception& e) { std::cerr << "lsfg-vk: something went wrong during lsfg-vk initialization:\n"; std::cerr << "- " << e.what() << '\n'; diff --git a/lsfg-vk-layer/src/layer.cpp b/lsfg-vk-layer/src/layer.cpp index ff9f0a2..1889786 100644 --- a/lsfg-vk-layer/src/layer.cpp +++ b/lsfg-vk-layer/src/layer.cpp @@ -1,4 +1,5 @@ #include "layer.hpp" +#include "detection.hpp" #include #include @@ -8,16 +9,71 @@ #include using namespace lsfgvk; +using namespace lsfgvk::layer; -std::vector lsfgvk::requiredInstanceExtensions() noexcept { - return {}; +Layer::Layer() : identification(identify()) { + this->tick(); + + if (!this->profile.has_value()) + return; + + std::cerr << "lsfg-vk: using profile with name '" << this->profile->name << "' "; + switch (this->identType) { + case IdentType::OVERRIDE: + std::cerr << "(identified via override)\n"; + break; + case IdentType::EXECUTABLE: + std::cerr << "(identified via executable)\n"; + break; + case IdentType::WINE_EXECUTABLE: + std::cerr << "(identified via wine executable)\n"; + break; + case IdentType::PROCESS_NAME: + std::cerr << "(identified via process name)\n"; + break; + } } -std::vector lsfgvk::requiredDeviceExtensions() noexcept { - return {}; +bool Layer::tick() { + auto res = this->config.tick(); + if (!res) + return false; + + // try to find a profile + const auto& detec = findProfile(this->config, identification); + if (!detec.has_value()) + return this->profile.has_value(); + + this->identType = detec->first; + this->profile = detec->second; + return true; } -lsfgvk::LayerInstance::LayerInstance(VkInstance instance, VkDevice device, +std::vector Layer::instanceExtensions() const { + if (!this->profile.has_value()) + return {}; + + return { + "VK_KHR_get_physical_device_properties2", + "VK_KHR_external_memory_capabilities", + "VK_KHR_external_semaphore_capabilities" + }; +} + +std::vector Layer::deviceExtensions() const { + if (!this->profile.has_value()) + return {}; + + return { + "VK_KHR_external_memory", + "VK_KHR_external_memory_fd", + "VK_KHR_external_semaphore", + "VK_KHR_external_semaphore_fd" + }; +} + +layer::LayerInstance::LayerInstance(const Layer& layer, + VkInstance instance, VkDevice device, PFN_vkSetDeviceLoaderData setLoaderData) { std::cerr << "lsfg-vk: Hello, world!\n"; std::cerr << "lsfg-vk: instance=" << instance << ", device=" << device << '\n'; diff --git a/lsfg-vk-layer/src/layer.hpp b/lsfg-vk-layer/src/layer.hpp index ddfde4e..d3f7aae 100644 --- a/lsfg-vk-layer/src/layer.hpp +++ b/lsfg-vk-layer/src/layer.hpp @@ -1,30 +1,61 @@ #pragma once +#include "config.hpp" +#include "detection.hpp" + +#include #include #include #include #include -namespace lsfgvk { +namespace lsfgvk::layer { - /// required instance extensions - /// @return list of extension names - std::vector requiredInstanceExtensions() noexcept; + /// lsfg-vk layer + class Layer { + public: + /// create a new layer + /// @throws lsfgvk::error on failure + Layer(); + + /// get the active profile + /// @return game configuration + [[nodiscard]] const auto& active() const { return profile; } + + /// required instance extensions + /// @return list of extension names + [[nodiscard]] std::vector instanceExtensions() const; + /// required device extensions + /// @return list of extension names + [[nodiscard]] std::vector deviceExtensions() const; + + /// tick the layer + /// @throws lsfgvk::error on failure + /// @return true if profile changed + bool tick(); + private: + Configuration config; + Identification identification; + + IdentType identType{}; // type used to deduce the profile + std::optional profile; + }; - /// required device extensions - /// @return list of extension names - std::vector requiredDeviceExtensions() noexcept; /// instance of the lsfg-vk layer on a VkInstance/VkDevice pair. class LayerInstance { public: /// create a new layer instance + /// @param layer parent layer /// @param instance Vulkan instance /// @param device Vulkan device /// @param setLoaderData function to set device loader data - LayerInstance(VkInstance instance, VkDevice device, + LayerInstance(const Layer& layer, + VkInstance instance, VkDevice device, PFN_vkSetDeviceLoaderData setLoaderData); + private: + Configuration config; }; } diff --git a/include/thirdparty/toml.hpp b/lsfg-vk-layer/thirdparty/include/toml.hpp similarity index 98% rename from include/thirdparty/toml.hpp rename to lsfg-vk-layer/thirdparty/include/toml.hpp index 8fe1a0d..0599bf5 100644 --- a/include/thirdparty/toml.hpp +++ b/lsfg-vk-layer/thirdparty/include/toml.hpp @@ -212,14 +212,6 @@ #endif #endif -#ifndef TOML_NVCC -#ifdef __NVCOMPILER_MAJOR__ -#define TOML_NVCC __NVCOMPILER_MAJOR__ -#else -#define TOML_NVCC 0 -#endif -#endif - #ifndef TOML_ARCH_ITANIUM #if defined(__ia64__) || defined(__ia64) || defined(_IA64) || defined(__IA64__) || defined(_M_IA64) #define TOML_ARCH_ITANIUM 1 @@ -428,7 +420,6 @@ #endif // TOML_ALWAYS_INLINE -#ifndef TOML_ALWAYS_INLINE #ifdef _MSC_VER #define TOML_ALWAYS_INLINE __forceinline #elif TOML_GCC || TOML_CLANG || TOML_HAS_ATTR(__always_inline__) @@ -438,10 +429,8 @@ #else #define TOML_ALWAYS_INLINE inline #endif -#endif // TOML_NEVER_INLINE -#ifndef TOML_NEVER_INLINE #ifdef _MSC_VER #define TOML_NEVER_INLINE TOML_DECLSPEC(noinline) #elif TOML_CUDA // https://gitlab.gnome.org/GNOME/glib/-/issues/2555 @@ -454,27 +443,19 @@ #ifndef TOML_NEVER_INLINE #define TOML_NEVER_INLINE #endif -#endif // MSVC attributes -#ifndef TOML_ABSTRACT_INTERFACE #define TOML_ABSTRACT_INTERFACE TOML_DECLSPEC(novtable) -#endif -#ifndef TOML_EMPTY_BASES #define TOML_EMPTY_BASES TOML_DECLSPEC(empty_bases) -#endif // TOML_TRIVIAL_ABI -#ifndef TOML_TRIVIAL_ABI #if TOML_CLANG || TOML_HAS_ATTR(__trivial_abi__) #define TOML_TRIVIAL_ABI TOML_ATTR(__trivial_abi__) #else #define TOML_TRIVIAL_ABI #endif -#endif // TOML_NODISCARD -#ifndef TOML_NODISCARD #if TOML_CPP >= 17 && TOML_HAS_CPP_ATTR(nodiscard) >= 201603 #define TOML_NODISCARD [[nodiscard]] #elif TOML_CLANG || TOML_GCC || TOML_HAS_ATTR(__warn_unused_result__) @@ -482,16 +463,13 @@ #else #define TOML_NODISCARD #endif -#endif // TOML_NODISCARD_CTOR -#ifndef TOML_NODISCARD_CTOR #if TOML_CPP >= 17 && TOML_HAS_CPP_ATTR(nodiscard) >= 201907 #define TOML_NODISCARD_CTOR [[nodiscard]] #else #define TOML_NODISCARD_CTOR #endif -#endif // pure + const #ifndef TOML_PURE @@ -541,7 +519,6 @@ #endif // TOML_ASSUME -#ifndef TOML_ASSUME #ifdef _MSC_VER #define TOML_ASSUME(expr) __assume(expr) #elif TOML_ICC || TOML_CLANG || TOML_HAS_BUILTIN(__builtin_assume) @@ -553,10 +530,8 @@ #else #define TOML_ASSUME(expr) static_cast(0) #endif -#endif // TOML_UNREACHABLE -#ifndef TOML_UNREACHABLE #ifdef _MSC_VER #define TOML_UNREACHABLE __assume(0) #elif TOML_ICC || TOML_CLANG || TOML_GCC || TOML_HAS_BUILTIN(__builtin_unreachable) @@ -564,7 +539,6 @@ #else #define TOML_UNREACHABLE static_cast(0) #endif -#endif // TOML_LIKELY #if TOML_CPP >= 20 && TOML_HAS_CPP_ATTR(likely) >= 201803 @@ -1091,10 +1065,6 @@ // 256 is crazy high! if you're hitting this limit with real input, TOML is probably the wrong tool for the job... #endif -#ifndef TOML_MAX_DOTTED_KEYS_DEPTH -#define TOML_MAX_DOTTED_KEYS_DEPTH 1024 -#endif - #ifdef TOML_CHAR_8_STRINGS #if TOML_CHAR_8_STRINGS #error TOML_CHAR_8_STRINGS was removed in toml++ 2.0.0; all value setters and getters now work with char8_t strings implicitly. @@ -1133,20 +1103,6 @@ TOML_ENABLE_WARNINGS; #define TOML_ENABLE_FLOAT16 0 #endif -#ifndef TOML_DISABLE_CONDITIONAL_NOEXCEPT_LAMBDA -#define TOML_DISABLE_CONDITIONAL_NOEXCEPT_LAMBDA 0 -#endif - -#ifndef TOML_DISABLE_NOEXCEPT_NOEXCEPT -#define TOML_DISABLE_NOEXCEPT_NOEXCEPT 0 - #ifdef _MSC_VER - #if _MSC_VER <= 1943 // Up to Visual Studio 2022 Version 17.13.6 - #undef TOML_DISABLE_NOEXCEPT_NOEXCEPT - #define TOML_DISABLE_NOEXCEPT_NOEXCEPT 1 - #endif - #endif -#endif - #if !defined(TOML_FLOAT_CHARCONV) && (TOML_GCC || TOML_CLANG || (TOML_ICC && !TOML_ICC_CL)) // not supported by any version of GCC or Clang as of 26/11/2020 // not supported by any version of ICC on Linux as of 11/01/2021 @@ -2644,60 +2600,6 @@ TOML_NAMESPACE_START return lhs; } }; - - TOML_NODISCARD - constexpr optional get_line(std::string_view doc, source_index line_num) noexcept - { - if (line_num == 0) - { - // Invalid line number. Should be greater than zero. - return {}; - } - - // The position of the first character of the specified line. - const auto begin_of_line = [doc, line_num]() -> std::size_t - { - if (line_num == 1) - { - return 0; - } - - const auto num_chars_of_doc = doc.size(); - std::size_t current_line_num{ 1 }; - - for (std::size_t i{}; i < num_chars_of_doc; ++i) - { - if (doc[i] == '\n') - { - ++current_line_num; - - if (current_line_num == line_num) - { - return i + 1; - } - } - } - return std::string_view::npos; - }(); - - if (begin_of_line >= doc.size()) - { - return {}; - } - - if (const auto end_of_line = doc.find('\n', begin_of_line); end_of_line != std::string_view::npos) - { - const auto num_chars_of_line = end_of_line - begin_of_line; - - // Trim an optional trailing carriage return. - return doc.substr(begin_of_line, - ((num_chars_of_line > 0) && (doc[end_of_line - 1] == '\r')) ? num_chars_of_line - 1 - : num_chars_of_line); - } - - // Return the last line. Apparently this doc has no trailing line break character at the end. - return doc.substr(begin_of_line); - } } TOML_NAMESPACE_END; @@ -3668,7 +3570,7 @@ TOML_NAMESPACE_START { TOML_NODISCARD TOML_ALWAYS_INLINE - path operator""_tpath(const char* str, size_t len) + path operator"" _tpath(const char* str, size_t len) { return path(std::string_view{ str, len }); } @@ -3712,16 +3614,6 @@ TOML_PUSH_WARNINGS; #undef max #endif -// workaround for this: https://github.com/marzer/tomlplusplus/issues/220 -#if TOML_NVCC -#define TOML_NVCC_WORKAROUND \ - { \ - return {}; \ - } -#else -#define TOML_NVCC_WORKAROUND = 0 -#endif - TOML_NAMESPACE_START { class TOML_ABSTRACT_INTERFACE TOML_EXPORTED_CLASS node @@ -3842,10 +3734,10 @@ TOML_NAMESPACE_START TOML_EXPORTED_MEMBER_FUNCTION virtual ~node() noexcept; - TOML_NODISCARD + TOML_PURE_GETTER virtual bool is_homogeneous(node_type ntype, node*& first_nonmatch) noexcept = 0; - TOML_NODISCARD + TOML_PURE_GETTER virtual bool is_homogeneous(node_type ntype, const node*& first_nonmatch) const noexcept = 0; TOML_PURE_GETTER @@ -3864,16 +3756,16 @@ TOML_NAMESPACE_START } TOML_PURE_GETTER - virtual node_type type() const noexcept TOML_NVCC_WORKAROUND; + virtual node_type type() const noexcept = 0; TOML_PURE_GETTER - virtual bool is_table() const noexcept TOML_NVCC_WORKAROUND; + virtual bool is_table() const noexcept = 0; TOML_PURE_GETTER virtual bool is_array() const noexcept = 0; TOML_PURE_GETTER - virtual bool is_array_of_tables() const noexcept TOML_NVCC_WORKAROUND; + virtual bool is_array_of_tables() const noexcept = 0; TOML_PURE_GETTER virtual bool is_value() const noexcept = 0; @@ -3928,8 +3820,6 @@ TOML_NAMESPACE_START return is_time(); else if constexpr (std::is_same_v) return is_date_time(); - - TOML_UNREACHABLE; } TOML_PURE_GETTER @@ -4012,8 +3902,6 @@ TOML_NAMESPACE_START return as_time(); else if constexpr (std::is_same_v) return as_date_time(); - - TOML_UNREACHABLE; } template @@ -4042,8 +3930,6 @@ TOML_NAMESPACE_START return as_time(); else if constexpr (std::is_same_v) return as_date_time(); - - TOML_UNREACHABLE; } template @@ -4321,8 +4207,6 @@ TOML_IMPL_NAMESPACE_START } TOML_IMPL_NAMESPACE_END; -#undef TOML_NVCC_WORKAROUND - #ifdef _MSC_VER #pragma pop_macro("min") #pragma pop_macro("max") @@ -4504,7 +4388,7 @@ TOML_NAMESPACE_START return node_->is_homogeneous(ntype, first_nonmatch); } - TOML_PURE_GETTER + TOML_NODISCARD bool is_homogeneous(node_type ntype) const noexcept { return node_ ? node_->is_homogeneous(ntype) : false; @@ -5098,11 +4982,8 @@ TOML_NAMESPACE_START (impl::value_variadic_ctor_allowed, impl::remove_cvref...>::value), typename... Args) TOML_NODISCARD_CTOR - explicit value(Args&&... args) -#if !TOML_DISABLE_NOEXCEPT_NOEXCEPT - noexcept(noexcept(value_type( - impl::native_value_maker...>::make(static_cast(args)...)))) -#endif + explicit value(Args&&... args) noexcept(noexcept(value_type( + impl::native_value_maker...>::make(static_cast(args)...)))) : val_(impl::native_value_maker...>::make(static_cast(args)...)) { #if TOML_LIFETIME_HOOKS @@ -5193,7 +5074,7 @@ TOML_NAMESPACE_START return ntype == node_type::none || ntype == impl::node_type_of; } - TOML_NODISCARD + TOML_PURE_GETTER bool is_homogeneous(node_type ntype, node*& first_nonmatch) noexcept final { if (ntype != node_type::none && ntype != impl::node_type_of) @@ -5204,7 +5085,7 @@ TOML_NAMESPACE_START return true; } - TOML_NODISCARD + TOML_PURE_GETTER bool is_homogeneous(node_type ntype, const node*& first_nonmatch) const noexcept final { if (ntype != node_type::none && ntype != impl::node_type_of) @@ -5718,11 +5599,11 @@ TOML_NAMESPACE_START "Retrieving values as wide-character strings with node::value_exact() is only " "supported on Windows with TOML_ENABLE_WINDOWS_COMPAT enabled."); - static_assert((is_native || can_represent_native) && !is_cvref, + static_assert((is_native || can_represent_native)&&!is_cvref, TOML_SA_VALUE_EXACT_FUNC_MESSAGE("return type of node::value_exact()")); // prevent additional compiler error spam when the static_assert fails by gating behind if constexpr - if constexpr ((is_native || can_represent_native) && !is_cvref) + if constexpr ((is_native || can_represent_native)&&!is_cvref) { if (type() == node_type_of) return { this->get_value_exact() }; @@ -5740,7 +5621,7 @@ TOML_NAMESPACE_START static_assert(!is_wide_string || TOML_ENABLE_WINDOWS_COMPAT, "Retrieving values as wide-character strings with node::value() is only " "supported on Windows with TOML_ENABLE_WINDOWS_COMPAT enabled."); - static_assert((is_native || can_represent_native || can_partially_represent_native) && !is_cvref, + static_assert((is_native || can_represent_native || can_partially_represent_native)&&!is_cvref, TOML_SA_VALUE_FUNC_MESSAGE("return type of node::value()")); // when asking for strings, dates, times and date_times there's no 'fuzzy' conversion @@ -6453,11 +6334,11 @@ TOML_NAMESPACE_START TOML_EXPORTED_MEMBER_FUNCTION bool is_homogeneous(node_type ntype) const noexcept final; - TOML_NODISCARD + TOML_PURE_GETTER TOML_EXPORTED_MEMBER_FUNCTION bool is_homogeneous(node_type ntype, node*& first_nonmatch) noexcept final; - TOML_NODISCARD + TOML_PURE_GETTER TOML_EXPORTED_MEMBER_FUNCTION bool is_homogeneous(node_type ntype, const node*& first_nonmatch) const noexcept final; @@ -6870,10 +6751,7 @@ TOML_NAMESPACE_START static_cast(static_cast(arr)[i]) .visit( [&]([[maybe_unused]] auto&& elem) // -// Define this macro as a workaround to compile errors caused by a bug in MSVC's "legacy lambda processor". -#if !TOML_DISABLE_CONDITIONAL_NOEXCEPT_LAMBDA noexcept(for_each_is_nothrow_one::value) -#endif { using elem_ref = for_each_elem_ref; static_assert(std::is_reference_v); @@ -7145,8 +7023,8 @@ TOML_NAMESPACE_START { using raw_elem_type = impl::remove_cvref; using elem_type = std::conditional_t, // - impl::emplaced_type_of, - raw_elem_type>; + impl::emplaced_type_of, + raw_elem_type>; using type = impl::remove_cvref>; static_assert(impl::is_native || impl::is_one_of, @@ -7183,8 +7061,8 @@ TOML_NAMESPACE_START { using raw_elem_type = impl::remove_cvref; using elem_type = std::conditional_t, // - impl::emplaced_type_of, - raw_elem_type>; + impl::emplaced_type_of, + raw_elem_type>; static constexpr auto moving_node_ptr = std::is_same_v // && sizeof...(Args) == 1u // @@ -7799,11 +7677,11 @@ TOML_NAMESPACE_START TOML_EXPORTED_MEMBER_FUNCTION bool is_homogeneous(node_type ntype) const noexcept final; - TOML_NODISCARD + TOML_PURE_GETTER TOML_EXPORTED_MEMBER_FUNCTION bool is_homogeneous(node_type ntype, node*& first_nonmatch) noexcept final; - TOML_NODISCARD + TOML_PURE_GETTER TOML_EXPORTED_MEMBER_FUNCTION bool is_homogeneous(node_type ntype, const node*& first_nonmatch) const noexcept final; @@ -8235,10 +8113,7 @@ TOML_NAMESPACE_START static_cast(*kvp.second) .visit( [&]([[maybe_unused]] auto&& v) // -// Define this macro as a workaround to compile errors caused by a bug in MSVC's "legacy lambda processor". -#if !TOML_DISABLE_CONDITIONAL_NOEXCEPT_LAMBDA noexcept(for_each_is_nothrow_one::value) -#endif { using value_ref = for_each_value_ref; static_assert(std::is_reference_v); @@ -8541,8 +8416,6 @@ TOML_NAMESPACE_START } return iterator{ ipos }; } - - TOML_UNREACHABLE; } TOML_CONSTRAINED_TEMPLATE((is_key_or_convertible || impl::is_wide_string), @@ -9725,7 +9598,7 @@ TOML_NAMESPACE_START TOML_NODISCARD TOML_ALWAYS_INLINE - parse_result operator""_toml(const char* str, size_t len) + parse_result operator"" _toml(const char* str, size_t len) { return parse(std::string_view{ str, len }); } @@ -9734,7 +9607,7 @@ TOML_NAMESPACE_START TOML_NODISCARD TOML_ALWAYS_INLINE - parse_result operator""_toml(const char8_t* str, size_t len) + parse_result operator"" _toml(const char8_t* str, size_t len) { return parse(std::u8string_view{ str, len }); } @@ -10771,11 +10644,6 @@ TOML_IMPL_NAMESPACE_START void TOML_CALLCONV print_to_stream(std::ostream & stream, const source_region& val) { print_to_stream(stream, val.begin); - if (val.begin != val.end) - { - print_to_stream(stream, " to "sv); - print_to_stream(stream, val.end); - } if (val.path) { print_to_stream(stream, " of '"sv); @@ -11945,7 +11813,7 @@ TOML_NAMESPACE_START return true; } - TOML_NODISCARD + TOML_PURE_GETTER TOML_EXTERNAL_LINKAGE bool array::is_homogeneous(node_type ntype, node * &first_nonmatch) noexcept { @@ -11967,7 +11835,7 @@ TOML_NAMESPACE_START return true; } - TOML_NODISCARD + TOML_PURE_GETTER TOML_EXTERNAL_LINKAGE bool array::is_homogeneous(node_type ntype, const node*& first_nonmatch) const noexcept { @@ -12300,7 +12168,7 @@ TOML_NAMESPACE_START return true; } - TOML_NODISCARD + TOML_PURE_GETTER TOML_EXTERNAL_LINKAGE bool table::is_homogeneous(node_type ntype, node * &first_nonmatch) noexcept { @@ -12323,7 +12191,7 @@ TOML_NAMESPACE_START return true; } - TOML_NODISCARD + TOML_PURE_GETTER TOML_EXTERNAL_LINKAGE bool table::is_homogeneous(node_type ntype, const node*& first_nonmatch) const noexcept { @@ -12763,7 +12631,7 @@ TOML_ANON_NAMESPACE_START return value; } }; - static_assert(std::is_trivially_default_constructible_v && std::is_trivially_copyable_v); + static_assert(std::is_trivial_v); static_assert(std::is_standard_layout_v); struct TOML_ABSTRACT_INTERFACE utf8_reader_interface @@ -13640,8 +13508,7 @@ TOML_IMPL_NAMESPACE_START class parser { private: - static constexpr size_t max_nested_values = TOML_MAX_NESTED_VALUES; - static constexpr size_t max_dotted_keys_depth = TOML_MAX_DOTTED_KEYS_DEPTH; + static constexpr size_t max_nested_values = TOML_MAX_NESTED_VALUES; utf8_buffered_reader reader; table root; @@ -14821,7 +14688,7 @@ TOML_IMPL_NAMESPACE_START set_error_and_return_default("'"sv, traits::full_prefix, std::string_view{ digits, length }, - "' is not representable as a signed 64-bit integer"sv); + "' is not representable in 64 bits"sv); // do the thing { @@ -14845,7 +14712,7 @@ TOML_IMPL_NAMESPACE_START set_error_and_return_default("'"sv, traits::full_prefix, std::string_view{ digits, length }, - "' is not representable as a signed 64-bit integer"sv); + "' is not representable in 64 bits"sv); if constexpr (traits::is_signed) { @@ -15662,11 +15529,6 @@ TOML_IMPL_NAMESPACE_START // store segment key_buffer.push_back(key_segment, key_begin, key_end); - if TOML_UNLIKELY(key_buffer.size() > max_dotted_keys_depth) - set_error_and_return_default("exceeded maximum dotted keys depth of "sv, - max_dotted_keys_depth, - " (TOML_MAX_DOTTED_KEYS_DEPTH)"sv); - // eof or no more key to come if (is_eof() || *cp != U'.') break; @@ -16343,10 +16205,16 @@ TOML_ANON_NAMESPACE_START { #if TOML_EXCEPTIONS #define TOML_PARSE_FILE_ERROR(msg, path) \ - throw parse_error(msg, source_position{}, std::make_shared(std::move(path))) + throw parse_error{ msg, source_position{}, std::make_shared(std::move(path)) } #else #define TOML_PARSE_FILE_ERROR(msg, path) \ - return parse_result(parse_error(msg, source_position{}, std::make_shared(std::move(path)))) + return parse_result \ + { \ + parse_error \ + { \ + msg, source_position{}, std::make_shared(std::move(path)) \ + } \ + } #endif std::string file_path_str(file_path); @@ -16355,7 +16223,7 @@ TOML_ANON_NAMESPACE_START std::ifstream file; TOML_OVERALIGNED char file_buffer[sizeof(void*) * 1024u]; file.rdbuf()->pubsetbuf(file_buffer, sizeof(file_buffer)); -#if TOML_WINDOWS && !(defined(__MINGW32__) || defined(__MINGW64__)) +#if TOML_WINDOWS file.open(impl::widen(file_path_str).c_str(), std::ifstream::in | std::ifstream::binary | std::ifstream::ate); #else file.open(file_path_str, std::ifstream::in | std::ifstream::binary | std::ifstream::ate); @@ -17107,7 +16975,8 @@ TOML_ANON_NAMESPACE_START weight += 1u; val *= -1.0; } - return weight + static_cast(abs(log10(val))) + 1u; + return weight + static_cast(log10(val)) + 1u; + break; } case node_type::boolean: return 5u; @@ -17832,7 +17701,6 @@ TOML_POP_WARNINGS; #undef TOML_NEVER_INLINE #undef TOML_NODISCARD #undef TOML_NODISCARD_CTOR -#undef TOML_NVCC #undef TOML_OPEN_ENUM #undef TOML_OPEN_FLAGS_ENUM #undef TOML_PARSER_TYPENAME diff --git a/src/config/config.cpp b/src/config/config.cpp deleted file mode 100644 index b4406e6..0000000 --- a/src/config/config.cpp +++ /dev/null @@ -1,174 +0,0 @@ -#include "config/config.hpp" -#include "common/exception.hpp" -#include "config/default_conf.hpp" -#include "utils/utils.hpp" - -#define TOML_ENABLE_FORMATTERS 0 // NOLINT -#include - -#include - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -using namespace Config; - -GlobalConfiguration Config::globalConf{}; -std::optional Config::currentConf{}; - -namespace { - /// Turn a string into a VkPresentModeKHR enum value. - VkPresentModeKHR into_present(const std::string& mode) { - if (mode == "fifo" || mode == "vsync") - return VkPresentModeKHR::VK_PRESENT_MODE_FIFO_KHR; - if (mode == "mailbox") - return VkPresentModeKHR::VK_PRESENT_MODE_MAILBOX_KHR; - if (mode == "immediate") - return VkPresentModeKHR::VK_PRESENT_MODE_IMMEDIATE_KHR; - return VkPresentModeKHR::VK_PRESENT_MODE_FIFO_KHR; - } -} - -void Config::updateConfig( - const std::string& file, - const std::pair& name) { - // process unchecked legacy environment variables - if (std::getenv("LSFG_LEGACY")) { - const char* dll = std::getenv("LSFG_DLL_PATH"); - if (dll) globalConf.dll = std::string(dll); - - currentConf.emplace(); - const char* multiplier = std::getenv("LSFG_MULTIPLIER"); - if (multiplier) currentConf->multiplier = std::stoul(multiplier); - const char* flow_scale = std::getenv("LSFG_FLOW_SCALE"); - if (flow_scale) currentConf->flowScale = std::stof(flow_scale); - const char* performance = std::getenv("LSFG_PERFORMANCE_MODE"); - if (performance) currentConf->performance = std::string(performance) == "1"; - const char* hdr = std::getenv("LSFG_HDR_MODE"); - if (hdr) currentConf->hdr = std::string(hdr) == "1"; - const char* e_present = std::getenv("LSFG_EXPERIMENTAL_PRESENT_MODE"); - if (e_present) currentConf->e_present = into_present(std::string(e_present)); - - return; - } - - // ensure configuration file exists - if (!std::filesystem::exists(file)) { - std::cerr << "lsfg-vk: Placing default configuration file at " << file << '\n'; - const auto parent = std::filesystem::path(file).parent_path(); - if (!std::filesystem::exists(parent)) - if (!std::filesystem::create_directories(parent)) - throw std::runtime_error("Unable to create configuration directory at " + parent.string()); - - std::ofstream out(file); - if (!out.is_open()) - throw std::runtime_error("Unable to create configuration file at " + file); - out << DEFAULT_CONFIG; - out.close(); - } - - // parse config file - toml::table config{}; - try { - config = toml::parse_file(file); - - const auto* version = config.get_as>("version"); - if (!version || *version != 1) - throw std::runtime_error("Configuration file version is not supported, expected 1"); - } catch (const std::exception& e) { - throw LSFG::rethrowable_error("Unable to parse configuration file", e); - } - - // parse global configuration - Config::globalConf = { - .config_file = file, - .timestamp = std::filesystem::last_write_time(file) - }; - if (const auto* global = config.get_as("global")) { - if (const auto* val = global->get_as>("dll")) - globalConf.dll = val->get(); - if (const auto* val = global->get_as>("no_fp16")) - globalConf.no_fp16 = val->get(); - } - - // parse game-specific configuration - std::optional gameConf; - if (const auto* games = config["game"].as_array()) { - for (auto&& elem : *games) { - if (!elem.is_table()) - throw std::runtime_error("Invalid game configuration entry"); - const auto* game = elem.as_table(); - - const auto* exe = game->at("exe").value_or("?"); - if (!name.first.ends_with(exe) && name.second != exe) - continue; - - gameConf = Config::currentConf; - if (!gameConf.has_value()) gameConf.emplace(); - - if (const auto* val = game->get_as>("multiplier")) - gameConf->multiplier = static_cast(val->get()); - if (const auto* val = game->get_as>("flow_scale")) - gameConf->flowScale = static_cast(val->get()); - if (const auto* val = game->get_as>("performance_mode")) - gameConf->performance = val->get(); - if (const auto* val = game->get_as>("hdr_mode")) - gameConf->hdr = val->get(); - if (const auto* val = game->get_as>("experimental_present_mode")) - gameConf->e_present = into_present(val->get()); - - break; - } - } - - if (!gameConf.has_value()) { - Config::currentConf.reset(); - std::cerr << "lsfg-vk: Configuration entry disappeared, disabling.\n"; - return; - } - - Config::currentConf = *gameConf; - - // print updated config info - std::cerr << "lsfg-vk: Loaded configuration for " << name.first << ":\n"; - if (!globalConf.dll.empty()) std::cerr << " Using DLL from: " << globalConf.dll << '\n'; - if (globalConf.no_fp16) std::cerr << " FP16 Acceleration: Force-disabled\n"; - std::cerr << " Multiplier: " << gameConf->multiplier << '\n'; - std::cerr << " Flow Scale: " << gameConf->flowScale << '\n'; - std::cerr << " Performance Mode: " << (gameConf->performance ? "Enabled" : "Disabled") << '\n'; - std::cerr << " HDR Mode: " << (gameConf->hdr ? "Enabled" : "Disabled") << '\n'; - if (gameConf->e_present != 2) std::cerr << " ! Present Mode: " << gameConf->e_present << '\n'; -} - -bool Config::checkStatus() { - // check if config is up-to-date - auto& globalConf = Config::globalConf; - if (globalConf.config_file.empty()) - return true; - if (!std::filesystem::exists(globalConf.config_file)) - return true; // ignore deletion - if (std::filesystem::last_write_time(globalConf.config_file) == globalConf.timestamp) - return true; - - // reload config - std::cerr << "lsfg-vk: Rereading configuration, as it is no longer valid.\n"; - std::this_thread::sleep_for(std::chrono::milliseconds(73)); - try { - Config::updateConfig(Utils::getConfigFile(), Utils::getProcessName()); - } catch (const std::exception& e) { - std::cerr << "lsfg-vk: Failed to update configuration, continuing using old:\n"; - std::cerr << "- " << e.what() << '\n'; - } - - return false; -} diff --git a/src/main.cpp b/src/main.cpp deleted file mode 100644 index 34154c1..0000000 --- a/src/main.cpp +++ /dev/null @@ -1,85 +0,0 @@ -#include "config/config.hpp" -#include "extract/extract.hpp" -#include "utils/benchmark.hpp" -#include "utils/utils.hpp" - -#include -#include -#include -#include -#include -#include -#include - -namespace { - [[gnu::constructor]] - [[gnu::visibility("default")]] - void lsfgvk_init() { - std::cerr << std::unitbuf; - - // read configuration - try { - Config::updateConfig(Utils::getConfigFile(), Utils::getProcessName()); - } catch (const std::exception& e) { - std::cerr << "lsfg-vk: An error occured while trying to parse the configuration, IGNORING:\n"; - std::cerr << "- " << e.what() << '\n'; - return; - } - - // exit silently if not enabled - if (!Config::currentConf.has_value()) - return; - - // load shaders - try { - Extract::extractShaders(); - } catch (const std::exception& e) { - std::cerr << "lsfg-vk: An error occurred while trying to extract the shaders, exiting:\n"; - std::cerr << "- " << e.what() << '\n'; - exit(EXIT_FAILURE); - } - std::cerr << "lsfg-vk: Shaders extracted successfully.\n"; - - // run benchmark if requested - const char* benchmark_flag = std::getenv("LSFG_BENCHMARK"); - if (!benchmark_flag) - return; - - const std::string resolution(benchmark_flag); - uint32_t width{}; - uint32_t height{}; - try { - const size_t x = resolution.find('x'); - if (x == std::string::npos) - throw std::runtime_error("Unable to find 'x' in benchmark string"); - - const std::string width_str = resolution.substr(0, x); - const std::string height_str = resolution.substr(x + 1); - if (width_str.empty() || height_str.empty()) - throw std::runtime_error("Invalid resolution"); - - const int32_t w = std::stoi(width_str); - const int32_t h = std::stoi(height_str); - if (w < 0 || h < 0) - throw std::runtime_error("Resolution cannot be negative"); - - width = static_cast(w); - height = static_cast(h); - } catch (const std::exception& e) { - std::cerr << "lsfg-vk: An error occurred while trying to parse the resolution, exiting:\n"; - std::cerr << "- " << e.what() << '\n'; - exit(EXIT_FAILURE); - } - - std::thread benchmark([width, height]() { - try { - Benchmark::run(width, height); - } catch (const std::exception& e) { - std::cerr << "lsfg-vk: An error occurred during the benchmark:\n"; - std::cerr << "- " << e.what() << '\n'; - exit(EXIT_FAILURE); - } - }); - benchmark.detach(); - } -}