refactor(cleanup): new config system

This commit is contained in:
PancakeTAS 2025-12-14 23:35:13 +01:00
parent 69a9767551
commit 5fa3ddc8e3
13 changed files with 646 additions and 559 deletions

View file

@ -1,67 +0,0 @@
#pragma once
#include <vulkan/vulkan_core.h>
#include <filesystem>
#include <chrono>
#include <cstddef>
#include <string>
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<std::chrono::file_clock> 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<GameConfiguration> 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<std::string, std::string>& name
);
///
/// Check if the configuration file is still up-to-date
///
/// @return Whether the configuration is up-to-date or not.
///
bool checkStatus();
}

View file

@ -1,38 +0,0 @@
#pragma once
#include <string>
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
)";

View file

@ -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)

View file

@ -0,0 +1,238 @@
#include "config.hpp"
#include "lsfg-vk-backend/lsfgvk.hpp"
#include <cstdlib>
#include <filesystem>
#include <fstream>
#include <optional>
#include <string>
#include <utility>
#include <vector>
#define TOML_ENABLE_FORMATTERS 0
#include <toml.hpp>
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<std::string> activityFromString(const toml::node_view<const toml::node>& val) {
std::vector<std::string> active_in{};
if (const auto& as_str = val.value<std::string>()) {
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<std::string>())
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<std::string>(),
.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<std::string>("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<std::string>("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<size_t>(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<GameConf> 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;
}

View file

@ -0,0 +1,69 @@
#pragma once
#include <chrono>
#include <filesystem>
#include <optional>
#include <string>
#include <vector>
namespace lsfgvk::layer {
/// global configuration
struct GlobalConf {
/// optional dll override
std::optional<std::string> 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<std::string> 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<GameConf>& getProfiles() const { return profiles; }
private:
std::filesystem::path path;
std::chrono::time_point<std::chrono::file_clock> timestamp;
bool from_env{};
GlobalConf global;
std::vector<GameConf> profiles;
};
}

View file

@ -0,0 +1,121 @@
#include "detection.hpp"
#include "config.hpp"
#include <array>
#include <cstdlib>
#include <fstream>
#include <optional>
#include <string>
#include <unistd.h>
#include <utility>
#include <vector>
#include <sys/types.h>
using namespace lsfgvk;
using namespace lsfgvk::layer;
namespace {
// try to match a profile by id
std::optional<GameConf> match(const std::vector<GameConf>& 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<char, 4096> buf{};
const ssize_t len = readlink("/proc/self/exe", buf.data(), buf.size() - 1);
if (len > 0) {
buf.at(static_cast<size_t>(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<size_t>(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<std::pair<IdentType, GameConf>> 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;
}

View file

@ -0,0 +1,40 @@
#pragma once
#include "config.hpp"
#include <optional>
#include <string>
#include <utility>
namespace lsfgvk::layer {
/// identification data for a process
struct Identification {
/// optional override name
std::optional<std::string> override;
/// path to exe file
std::string executable;
/// path to exe file when running under wine
std::optional<std::string> 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<std::pair<IdentType, GameConf>> findProfile(
const Configuration& config, const Identification& id);
}

View file

@ -5,6 +5,7 @@
#include <cstdint>
#include <exception>
#include <iostream>
#include <optional>
#include <string>
#include <unordered_map>
#include <vector>
@ -12,6 +13,8 @@
#include <vulkan/vk_layer.h>
#include <vulkan/vulkan_core.h>
using namespace lsfgvk;
namespace {
/// reinterpret cast helper with const_cast
template<typename T, typename U>
@ -42,6 +45,11 @@ namespace {
PFN_vkGetInstanceProcAddr nxvkGetInstanceProcAddr{nullptr};
PFN_vkGetDeviceProcAddr nxvkGetDeviceProcAddr = nullptr;
auto& layer() {
static std::optional<layer::Layer> 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<VkLayerInstanceCreateInfo*>(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<uint32_t>(extensions.size());
@ -109,8 +129,8 @@ namespace {
}
// map of devices to layer instances
std::unordered_map<VkDevice, lsfgvk::LayerInstance>& device2InstanceMap() {
static std::unordered_map<VkDevice, lsfgvk::LayerInstance> map; // NOLINT
std::unordered_map<VkDevice, layer::LayerInstance>& device2InstanceMap() {
static std::unordered_map<VkDevice, layer::LayerInstance> 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<uint32_t>(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';

View file

@ -1,4 +1,5 @@
#include "layer.hpp"
#include "detection.hpp"
#include <iostream>
#include <string>
@ -8,16 +9,71 @@
#include <vulkan/vulkan_core.h>
using namespace lsfgvk;
using namespace lsfgvk::layer;
std::vector<std::string> 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<std::string> 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<std::string> 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<std::string> 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';

View file

@ -1,30 +1,61 @@
#pragma once
#include "config.hpp"
#include "detection.hpp"
#include <optional>
#include <string>
#include <vector>
#include <vulkan/vk_layer.h>
#include <vulkan/vulkan_core.h>
namespace lsfgvk {
namespace lsfgvk::layer {
/// required instance extensions
/// @return list of extension names
std::vector<std::string> 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<std::string> instanceExtensions() const;
/// required device extensions
/// @return list of extension names
[[nodiscard]] std::vector<std::string> 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<GameConf> profile;
};
/// required device extensions
/// @return list of extension names
std::vector<std::string> 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;
};
}

View file

@ -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<void>(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<void>(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<std::string_view> 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<type, date_time>)
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<unwrapped_type, date_time>)
return as_date_time();
TOML_UNREACHABLE;
}
template <typename T>
@ -4042,8 +3930,6 @@ TOML_NAMESPACE_START
return as_time();
else if constexpr (std::is_same_v<unwrapped_type, date_time>)
return as_date_time();
TOML_UNREACHABLE;
}
template <typename T>
@ -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<value<ValueType>, impl::remove_cvref<Args>...>::value),
typename... Args)
TOML_NODISCARD_CTOR
explicit value(Args&&... args)
#if !TOML_DISABLE_NOEXCEPT_NOEXCEPT
noexcept(noexcept(value_type(
impl::native_value_maker<value_type, std::decay_t<Args>...>::make(static_cast<Args&&>(args)...))))
#endif
explicit value(Args&&... args) noexcept(noexcept(value_type(
impl::native_value_maker<value_type, std::decay_t<Args>...>::make(static_cast<Args&&>(args)...))))
: val_(impl::native_value_maker<value_type, std::decay_t<Args>...>::make(static_cast<Args&&>(args)...))
{
#if TOML_LIFETIME_HOOKS
@ -5193,7 +5074,7 @@ TOML_NAMESPACE_START
return ntype == node_type::none || ntype == impl::node_type_of<value_type>;
}
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<value_type>)
@ -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<value_type>)
@ -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<T> || can_represent_native<T>) && !is_cvref<T>,
static_assert((is_native<T> || can_represent_native<T>)&&!is_cvref<T>,
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<T> || can_represent_native<T>) && !is_cvref<T>)
if constexpr ((is_native<T> || can_represent_native<T>)&&!is_cvref<T>)
{
if (type() == node_type_of<T>)
return { this->get_value_exact<T>() };
@ -5740,7 +5621,7 @@ TOML_NAMESPACE_START
static_assert(!is_wide_string<T> || 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<T> || can_represent_native<T> || can_partially_represent_native<T>) && !is_cvref<T>,
static_assert((is_native<T> || can_represent_native<T> || can_partially_represent_native<T>)&&!is_cvref<T>,
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<node_ref>(static_cast<Array&&>(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<Func&&, Array&&, decltype(elem)>::value)
#endif
{
using elem_ref = for_each_elem_ref<decltype(elem), Array&&>;
static_assert(std::is_reference_v<elem_ref>);
@ -7145,8 +7023,8 @@ TOML_NAMESPACE_START
{
using raw_elem_type = impl::remove_cvref<ElemType>;
using elem_type = std::conditional_t<std::is_void_v<raw_elem_type>, //
impl::emplaced_type_of<Args&&...>,
raw_elem_type>;
impl::emplaced_type_of<Args&&...>,
raw_elem_type>;
using type = impl::remove_cvref<impl::unwrap_node<elem_type>>;
static_assert(impl::is_native<type> || impl::is_one_of<type, table, array>,
@ -7183,8 +7061,8 @@ TOML_NAMESPACE_START
{
using raw_elem_type = impl::remove_cvref<ElemType>;
using elem_type = std::conditional_t<std::is_void_v<raw_elem_type>, //
impl::emplaced_type_of<Args&&...>,
raw_elem_type>;
impl::emplaced_type_of<Args&&...>,
raw_elem_type>;
static constexpr auto moving_node_ptr = std::is_same_v<elem_type, impl::node_ptr> //
&& 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<node_ref>(*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<Func&&, Table&&, decltype(v)>::value)
#endif
{
using value_ref = for_each_value_ref<decltype(v), Table&&>;
static_assert(std::is_reference_v<value_ref>);
@ -8541,8 +8416,6 @@ TOML_NAMESPACE_START
}
return iterator{ ipos };
}
TOML_UNREACHABLE;
}
TOML_CONSTRAINED_TEMPLATE((is_key_or_convertible<KeyType&&> || impl::is_wide_string<KeyType>),
@ -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<utf8_codepoint> && std::is_trivially_copyable_v<utf8_codepoint>);
static_assert(std::is_trivial_v<utf8_codepoint>);
static_assert(std::is_standard_layout_v<utf8_codepoint>);
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<const std::string>(std::move(path)))
throw parse_error{ msg, source_position{}, std::make_shared<const std::string>(std::move(path)) }
#else
#define TOML_PARSE_FILE_ERROR(msg, path) \
return parse_result(parse_error(msg, source_position{}, std::make_shared<const std::string>(std::move(path))))
return parse_result \
{ \
parse_error \
{ \
msg, source_position{}, std::make_shared<const std::string>(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<size_t>(abs(log10(val))) + 1u;
return weight + static_cast<size_t>(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

View file

@ -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 <toml.hpp>
#include <vulkan/vulkan_core.h>
#include <filesystem>
#include <exception>
#include <stdexcept>
#include <iostream>
#include <optional>
#include <fstream>
#include <cstdint>
#include <cstdlib>
#include <utility>
#include <chrono>
#include <thread>
#include <string>
using namespace Config;
GlobalConfiguration Config::globalConf{};
std::optional<GameConfiguration> 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<std::string, std::string>& 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<toml::value<int64_t>>("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<toml::table>("global")) {
if (const auto* val = global->get_as<toml::value<std::string>>("dll"))
globalConf.dll = val->get();
if (const auto* val = global->get_as<toml::value<bool>>("no_fp16"))
globalConf.no_fp16 = val->get();
}
// parse game-specific configuration
std::optional<GameConfiguration> 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<toml::value<int64_t>>("multiplier"))
gameConf->multiplier = static_cast<size_t>(val->get());
if (const auto* val = game->get_as<toml::value<double>>("flow_scale"))
gameConf->flowScale = static_cast<float>(val->get());
if (const auto* val = game->get_as<toml::value<bool>>("performance_mode"))
gameConf->performance = val->get();
if (const auto* val = game->get_as<toml::value<bool>>("hdr_mode"))
gameConf->hdr = val->get();
if (const auto* val = game->get_as<toml::value<std::string>>("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;
}

View file

@ -1,85 +0,0 @@
#include "config/config.hpp"
#include "extract/extract.hpp"
#include "utils/benchmark.hpp"
#include "utils/utils.hpp"
#include <exception>
#include <stdexcept>
#include <iostream>
#include <cstdint>
#include <cstdlib>
#include <string>
#include <thread>
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<uint32_t>(w);
height = static_cast<uint32_t>(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();
}
}