diff --git a/include/config/config.hpp b/include/config/config.hpp index 69a0d41..bc77cc7 100644 --- a/include/config/config.hpp +++ b/include/config/config.hpp @@ -41,6 +41,17 @@ namespace Config { /// bool loadAndWatchConfig(const std::string& file); + /// + /// Reread the configuration file while preserving the old configuration + /// in case of an error. + /// + /// @param file The path to the configuration file. + /// @return Whether a configuration exists or not. + /// + /// @throws std::runtime_error if an error occurs while loading the configuration file. + /// + bool updateConfig(const std::string& file); + /// /// Get the configuration for a game. /// diff --git a/include/context.hpp b/include/context.hpp index 37269b2..3438e48 100644 --- a/include/context.hpp +++ b/include/context.hpp @@ -63,7 +63,6 @@ private: Mini::CommandPool cmdPool; uint64_t frameIdx{0}; - bool isPerfMode{false}; struct RenderPassInfo { Mini::CommandBuffer preCopyBuf; // copy from swapchain image to frame_0/frame_1 diff --git a/include/utils/utils.hpp b/include/utils/utils.hpp index 27b8264..90185de 100644 --- a/include/utils/utils.hpp +++ b/include/utils/utils.hpp @@ -85,4 +85,18 @@ namespace Utils { /// void resetLimitN(const std::string& id) noexcept; + /// + /// Get the process name of the current executable. + /// + /// @return The name of the process. + /// + std::string getProcessName(); + + /// + /// Get the configuration file path. + /// + /// @return The path to the configuration file. + /// + std::string getConfigFile(); + } diff --git a/src/config/config.cpp b/src/config/config.cpp index 8a63cf2..9b14e2f 100644 --- a/src/config/config.cpp +++ b/src/config/config.cpp @@ -36,65 +36,14 @@ namespace { Configuration Config::activeConf{}; bool Config::loadAndWatchConfig(const std::string& file) { - if (!std::filesystem::exists(file)) + globalConf.valid = std::make_shared(true); + + auto res = updateConfig(file); + if (!res) return false; - // parse config file - std::optional parsed; - try { - parsed.emplace(toml::parse(file)); - } catch (const std::exception& e) { - throw LSFG::rethrowable_error("Unable to parse configuration file", e); - } - auto& toml = *parsed; - - // parse global configuration - auto& global = globalConf; - const toml::value globalTable = toml::find_or_default(toml, "global"); - global.enable = toml::find_or(globalTable, "enable", false); - global.dll = toml::find_or(globalTable, "dll", std::string()); - global.multiplier = toml::find_or(globalTable, "multiplier", size_t(2)); - global.flowScale = toml::find_or(globalTable, "flow_scale", 1.0F); - global.performance = toml::find_or(globalTable, "performance_mode", false); - global.hdr = toml::find_or(globalTable, "hdr_mode", false); - global.valid = std::make_shared(true); - - // validate global configuration - if (global.multiplier < 2) - throw std::runtime_error("Multiplier cannot be less than 2"); - if (global.flowScale < 0.25F || global.flowScale > 1.0F) - throw std::runtime_error("Flow scale must be between 0.25 and 1.0"); - - // parse game-specific configuration - auto& games = gameConfs.emplace(); - const toml::value gamesList = toml::find_or_default(toml, "game"); - for (const auto& gameTable : gamesList.as_array()) { - if (!gameTable.is_table()) - throw std::runtime_error("Invalid game configuration entry"); - if (!gameTable.contains("exe")) - throw std::runtime_error("Game override missing 'exe' field"); - - const std::string exe = toml::find(gameTable, "exe"); - Configuration game{ - .enable = toml::find_or(gameTable, "enable", global.enable), - .dll = toml::find_or(gameTable, "dll", global.dll), - .multiplier = toml::find_or(gameTable, "multiplier", global.multiplier), - .flowScale = toml::find_or(gameTable, "flow_scale", global.flowScale), - .performance = toml::find_or(gameTable, "performance_mode", global.performance), - .hdr = toml::find_or(gameTable, "hdr_mode", global.hdr), - .valid = global.valid // only need a single validity flag - }; - - // validate the configuration - if (game.multiplier < 2) - throw std::runtime_error("Multiplier cannot be less than 2"); - if (game.flowScale < 0.25F || game.flowScale > 1.0F) - throw std::runtime_error("Flow scale must be between 0.25 and 1.0"); - games[exe] = std::move(game); - } - // prepare config watcher - std::thread([file = file, valid = global.valid]() { + std::thread([file = file, valid = globalConf.valid]() { try { const int fd = inotify_init(); if (fd < 0) @@ -131,6 +80,7 @@ bool Config::loadAndWatchConfig(const std::string& file) { continue; // stall a bit, then mark as invalid + std::cerr << "lsfg-vk: Configuration file changed, invalidating config...\n"; discard_until.emplace(std::chrono::steady_clock::now() + std::chrono::milliseconds(500)); } @@ -156,6 +106,72 @@ bool Config::loadAndWatchConfig(const std::string& file) { return true; } +bool Config::updateConfig(const std::string& file) { + if (!std::filesystem::exists(file)) + return false; + + // parse config file + std::optional parsed; + try { + parsed.emplace(toml::parse(file)); + } catch (const std::exception& e) { + throw LSFG::rethrowable_error("Unable to parse configuration file", e); + } + auto& toml = *parsed; + + // parse global configuration + const toml::value globalTable = toml::find_or_default(toml, "global"); + const Configuration global{ + .enable = toml::find_or(globalTable, "enable", false), + .dll = toml::find_or(globalTable, "dll", std::string()), + .multiplier = toml::find_or(globalTable, "multiplier", size_t(2)), + .flowScale = toml::find_or(globalTable, "flow_scale", 1.0F), + .performance = toml::find_or(globalTable, "performance_mode", false), + .hdr = toml::find_or(globalTable, "hdr_mode", false), + .valid = globalConf.valid // use the same validity flag + }; + + // validate global configuration + if (global.multiplier < 2) + throw std::runtime_error("Multiplier cannot be less than 2"); + if (global.flowScale < 0.25F || global.flowScale > 1.0F) + throw std::runtime_error("Flow scale must be between 0.25 and 1.0"); + + // parse game-specific configuration + std::unordered_map games; + const toml::value gamesList = toml::find_or_default(toml, "game"); + for (const auto& gameTable : gamesList.as_array()) { + if (!gameTable.is_table()) + throw std::runtime_error("Invalid game configuration entry"); + if (!gameTable.contains("exe")) + throw std::runtime_error("Game override missing 'exe' field"); + + const std::string exe = toml::find(gameTable, "exe"); + Configuration game{ + .enable = toml::find_or(gameTable, "enable", global.enable), + .dll = toml::find_or(gameTable, "dll", global.dll), + .multiplier = toml::find_or(gameTable, "multiplier", global.multiplier), + .flowScale = toml::find_or(gameTable, "flow_scale", global.flowScale), + .performance = toml::find_or(gameTable, "performance_mode", global.performance), + .hdr = toml::find_or(gameTable, "hdr_mode", global.hdr), + .valid = global.valid // only need a single validity flag + }; + + // validate the configuration + if (game.multiplier < 2) + throw std::runtime_error("Multiplier cannot be less than 2"); + if (game.flowScale < 0.25F || game.flowScale > 1.0F) + throw std::runtime_error("Flow scale must be between 0.25 and 1.0"); + games[exe] = std::move(game); + } + + // store configurations + global.valid->store(true, std::memory_order_release); + globalConf = global; + gameConfs = std::move(games); + return true; +} + Configuration Config::getConfig(std::string_view name) { if (name.empty() || !gameConfs.has_value()) return globalConf; diff --git a/src/context.cpp b/src/context.cpp index 712ef23..ee203bd 100644 --- a/src/context.cpp +++ b/src/context.cpp @@ -1,147 +1,124 @@ #include "context.hpp" +#include "config/config.hpp" +#include "common/exception.hpp" #include "extract/extract.hpp" #include "extract/trans.hpp" #include "utils/utils.hpp" -#include "utils/log.hpp" #include "hooks.hpp" #include "layer.hpp" -#include "common/exception.hpp" #include #include #include -#include +#include +#include #include #include +#include #include #include #include +#include LsContext::LsContext(const Hooks::DeviceInfo& info, VkSwapchainKHR swapchain, VkExtent2D extent, const std::vector& swapchainImages) : swapchain(swapchain), swapchainImages(swapchainImages), extent(extent) { - // read environment variables - const char* lsfgFlowScaleStr = getenv("LSFG_FLOW_SCALE"); - float flowScale = lsfgFlowScaleStr - ? std::stof(lsfgFlowScaleStr) - : 1.0F; - flowScale = std::max(0.25F, std::min(flowScale, 1.0F)); + // get updated configuration + const auto& conf = Config::activeConf; + if (!conf.valid->load(std::memory_order_relaxed)) { + std::cerr << "lsfg-vk: Configuration is no longer valid, rereading...\n"; - const char* lsfgHdrStr = getenv("LSFG_HDR"); - const bool isHdr = lsfgHdrStr - ? *lsfgHdrStr == '1' - : false; + const bool wasPerformance = conf.performance; - const char* lsfgPerfModeStr = getenv("LSFG_PERF_MODE"); - const bool perfMode = lsfgPerfModeStr - ? *lsfgPerfModeStr == '1' - : false; + // reread configuration + try { + const std::string file = Utils::getConfigFile(); + Config::updateConfig(file); + const std::string name = Utils::getProcessName(); + Config::activeConf = Config::getConfig(name); + } catch (const std::exception& e) { + std::cerr << "lsfg-vk: Failed to update configuration, continuing using old:\n"; + std::cerr << "- " << e.what() << '\n'; + } + if (wasPerformance && !Config::activeConf.performance) + LSFG_3_1P::finalize(); + if (!wasPerformance && Config::activeConf.performance) + LSFG_3_1::finalize(); + } // we could take the format from the swapchain, // but honestly this is safer. - const VkFormat format = isHdr + const VkFormat format = conf.hdr ? VK_FORMAT_R8G8B8A8_UNORM : VK_FORMAT_R16G16B16A16_SFLOAT; // prepare textures for lsfg - int frame_0_fd{}; - this->frame_0 = Mini::Image( - info.device, info.physicalDevice, - extent, format, - VK_IMAGE_USAGE_TRANSFER_DST_BIT, - VK_IMAGE_ASPECT_COLOR_BIT, - &frame_0_fd); - Log::info("context", "Created frame_0 image and obtained fd: {}", - frame_0_fd); + std::array fds{}; + this->frame_0 = Mini::Image(info.device, info.physicalDevice, + extent, format, VK_IMAGE_USAGE_TRANSFER_DST_BIT, VK_IMAGE_ASPECT_COLOR_BIT, + &fds.at(0)); + this->frame_1 = Mini::Image(info.device, info.physicalDevice, + extent, format, VK_IMAGE_USAGE_TRANSFER_DST_BIT, VK_IMAGE_ASPECT_COLOR_BIT, + &fds.at(1)); - int frame_1_fd{}; - this->frame_1 = Mini::Image( - info.device, info.physicalDevice, - extent, format, - VK_IMAGE_USAGE_TRANSFER_DST_BIT, - VK_IMAGE_ASPECT_COLOR_BIT, - &frame_1_fd); - Log::info("context", "Created frame_1 image and obtained fd: {}", - frame_1_fd); - - std::vector out_n_fds(info.frameGen); - for (size_t i = 0; i < info.frameGen; ++i) { - this->out_n.emplace_back( - info.device, info.physicalDevice, + std::vector outFds(conf.multiplier - 1); + for (size_t i = 0; i < (conf.multiplier - 1); ++i) + this->out_n.emplace_back(info.device, info.physicalDevice, extent, format, - VK_IMAGE_USAGE_TRANSFER_SRC_BIT, - VK_IMAGE_ASPECT_COLOR_BIT, - &out_n_fds.at(i)); - Log::info("context", "Created out_n[{}] image and obtained fd: {}", - i, out_n_fds.at(i)); - } + VK_IMAGE_USAGE_TRANSFER_SRC_BIT, VK_IMAGE_ASPECT_COLOR_BIT, + &outFds.at(i)); - this->isPerfMode = false; + // initialize lsfg auto* lsfgInitialize = LSFG_3_1::initialize; auto* lsfgCreateContext = LSFG_3_1::createContext; auto* lsfgDeleteContext = LSFG_3_1::deleteContext; - if (perfMode) { - Log::debug("context", "Using performance mode"); - this->isPerfMode = true; + if (conf.performance) { lsfgInitialize = LSFG_3_1P::initialize; lsfgCreateContext = LSFG_3_1P::createContext; lsfgDeleteContext = LSFG_3_1P::deleteContext; } - // initialize lsfg - Log::debug("context", "(entering LSFG initialization)"); + setenv("DISABLE_LSFG", "1", 1); // NOLINT - Extract::extractShaders(); + lsfgInitialize( Utils::getDeviceUUID(info.physicalDevice), - isHdr, 1.0F / flowScale, info.frameGen, + conf.hdr, 1.0F / conf.flowScale, conf.multiplier - 1, [](const std::string& name) { auto dxbc = Extract::getShader(name); auto spirv = Extract::translateShader(dxbc); return spirv; } ); - unsetenv("DISABLE_LSFG"); // NOLINT - Log::debug("context", "(exiting LSFG initialization)"); - // create lsfg context - Log::debug("context", "(entering LSFG context creation)"); this->lsfgCtxId = std::shared_ptr( - new int32_t(lsfgCreateContext(frame_0_fd, frame_1_fd, out_n_fds, - extent, format)), + new int32_t(lsfgCreateContext(fds.at(0), fds.at(1), outFds, extent, format)), [lsfgDeleteContext = lsfgDeleteContext](const int32_t* id) { - Log::info("context", - "(entering LSFG context deletion with id: {})", *id); lsfgDeleteContext(*id); - Log::info("context", - "(exiting LSFG context deletion with id: {})", *id); } ); - Log::info("context", "(exiting LSFG context creation with id: {})", - *this->lsfgCtxId); + + unsetenv("DISABLE_LSFG"); // NOLINT // prepare render passes this->cmdPool = Mini::CommandPool(info.device, info.queue.first); for (size_t i = 0; i < 8; i++) { auto& pass = this->passInfos.at(i); - pass.renderSemaphores.resize(info.frameGen); - pass.acquireSemaphores.resize(info.frameGen); - pass.postCopyBufs.resize(info.frameGen); - pass.postCopySemaphores.resize(info.frameGen); - pass.prevPostCopySemaphores.resize(info.frameGen); + pass.renderSemaphores.resize(conf.multiplier - 1); + pass.acquireSemaphores.resize(conf.multiplier - 1); + pass.postCopyBufs.resize(conf.multiplier - 1); + pass.postCopySemaphores.resize(conf.multiplier - 1); + pass.prevPostCopySemaphores.resize(conf.multiplier - 1); } - - Log::info("context", "Remaining misc context init finished successfully."); } VkResult LsContext::present(const Hooks::DeviceInfo& info, const void* pNext, VkQueue queue, const std::vector& gameRenderSemaphores, uint32_t presentIdx) { + const auto& conf = Config::activeConf; auto& pass = this->passInfos.at(this->frameIdx % 8); // 1. copy swapchain image to frame_0/frame_1 - Log::debug("context2", "1. Copying swapchain image {} to frame {}", - presentIdx, this->frameIdx % 2 == 0 ? "0" : "1"); int preCopySemaphoreFd{}; pass.preCopySemaphores.at(0) = Mini::Semaphore(info.device, &preCopySemaphoreFd); pass.preCopySemaphores.at(1) = Mini::Semaphore(info.device); @@ -167,28 +144,21 @@ VkResult LsContext::present(const Hooks::DeviceInfo& info, const void* pNext, Vk pass.preCopySemaphores.at(1).handle() }); // 2. render intermediary frames - Log::debug("context2", "2. Rendering intermediary frames"); - std::vector renderSemaphoreFds(info.frameGen); - for (size_t i = 0; i < info.frameGen; ++i) + std::vector renderSemaphoreFds(conf.multiplier - 1); + for (size_t i = 0; i < (conf.multiplier - 1); ++i) pass.renderSemaphores.at(i) = Mini::Semaphore(info.device, &renderSemaphoreFds.at(i)); - Log::debug("context2", - "(entering LSFG present with id: {})", *this->lsfgCtxId); - if (this->isPerfMode) { + if (conf.performance) LSFG_3_1P::presentContext(*this->lsfgCtxId, preCopySemaphoreFd, renderSemaphoreFds); - } else { + else LSFG_3_1::presentContext(*this->lsfgCtxId, preCopySemaphoreFd, renderSemaphoreFds); - } - Log::debug("context2", - "(exiting LSFG present with id: {})", *this->lsfgCtxId); - for (size_t i = 0; i < info.frameGen; i++) { + for (size_t i = 0; i < (conf.multiplier - 1); i++) { // 3. acquire next swapchain image - Log::debug("context2", "3. Acquiring next swapchain image for frame {}", i); pass.acquireSemaphores.at(i) = Mini::Semaphore(info.device); uint32_t imageIdx{}; auto res = Layer::ovkAcquireNextImageKHR(info.device, this->swapchain, UINT64_MAX, @@ -197,7 +167,6 @@ VkResult LsContext::present(const Hooks::DeviceInfo& info, const void* pNext, Vk throw LSFG::vulkan_error(res, "Failed to acquire next swapchain image"); // 4. copy output image to swapchain image - Log::debug("context2", "4. Copying output image to swapchain image for frame {}", i); pass.postCopySemaphores.at(i) = Mini::Semaphore(info.device); pass.prevPostCopySemaphores.at(i) = Mini::Semaphore(info.device); pass.postCopyBufs.at(i) = Mini::CommandBuffer(info.device, this->cmdPool); @@ -218,7 +187,6 @@ VkResult LsContext::present(const Hooks::DeviceInfo& info, const void* pNext, Vk pass.prevPostCopySemaphores.at(i).handle() }); // 5. present swapchain image - Log::debug("context2", "5. Presenting swapchain image for frame {}", i); std::vector waitSemaphores{ pass.postCopySemaphores.at(i).handle() }; if (i != 0) waitSemaphores.emplace_back(pass.prevPostCopySemaphores.at(i - 1).handle()); @@ -237,9 +205,8 @@ VkResult LsContext::present(const Hooks::DeviceInfo& info, const void* pNext, Vk } // 6. present actual next frame - Log::debug("context2", "6. Presenting actual next frame"); VkSemaphore lastPrevPostCopySemaphore = - pass.prevPostCopySemaphores.at(info.frameGen - 1).handle(); + pass.prevPostCopySemaphores.at(conf.multiplier - 1 - 1).handle(); const VkPresentInfoKHR presentInfo{ .sType = VK_STRUCTURE_TYPE_PRESENT_INFO_KHR, .waitSemaphoreCount = 1, diff --git a/src/hooks.cpp b/src/hooks.cpp index 6393aa3..4615aed 100644 --- a/src/hooks.cpp +++ b/src/hooks.cpp @@ -4,6 +4,7 @@ #include "context.hpp" #include "layer.hpp" +#include #include #include @@ -179,6 +180,10 @@ namespace { swapchainImages )); + std::cerr << "lsfg-vk: Swapchain context " << + (createInfo.oldSwapchain ? "recreated" : "created") + << " (using " << imageCount << " images).\n"; + Utils::resetLimitN("swapCtxCreate"); } catch (const std::exception& e) { Utils::logLimitN("swapCtxCreate", 5, diff --git a/src/main.cpp b/src/main.cpp index 47c3a68..7b26d73 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -1,15 +1,12 @@ #include "config/config.hpp" +#include "extract/extract.hpp" +#include "utils/utils.hpp" -#include #include -#include #include #include #include -#include // NOLINT -#include - namespace { /// Check if the library is preloaded or Vulkan loaded. bool isPreload() { @@ -29,27 +26,6 @@ namespace { return benchmark_str == "1"; } - /// Get the process name - std::string getProcessName() { - std::array exe{}; - const ssize_t exe_len = readlink("/proc/self/exe", exe.data(), exe.size() - 1); - if (exe_len <= 0) - return "Unknown Process"; - exe.at(static_cast(exe_len)) = '\0'; - return{basename(exe.data())}; - } - - /// Get the config file - std::string getConfigFile() { - const char* configFile = std::getenv("LSFG_CONFIG"); - if (configFile && *configFile != '\0') - return{configFile}; - const char* homePath = std::getenv("HOME"); - if (homePath && *homePath != '\0') - return std::string(homePath) + "/.config/lsfg-vk.toml"; - return "/etc/lsfg-vk.toml"; - } - __attribute__((constructor)) void lsfgvk_init() { if (isPreload()) { if (requestedBenchmark()) { @@ -63,7 +39,7 @@ namespace { } // read configuration (might block) - const std::string file = getConfigFile(); + const std::string file = Utils::getConfigFile(); try { Config::loadAndWatchConfig(file); } catch (const std::exception& e) { @@ -72,7 +48,7 @@ namespace { exit(0); } - const std::string name = getProcessName(); + const std::string name = Utils::getProcessName(); try { Config::activeConf = Config::getConfig(name); } catch (const std::exception& e) { @@ -93,5 +69,15 @@ namespace { std::cerr << " Flow Scale: " << conf.flowScale << '\n'; std::cerr << " Performance Mode: " << (conf.performance ? "Enabled" : "Disabled") << '\n'; std::cerr << " HDR Mode: " << (conf.hdr ? "Enabled" : "Disabled") << '\n'; + + // 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(0); + } + std::cerr << "lsfg-vk: Shaders extracted successfully.\n"; } } diff --git a/src/utils/utils.cpp b/src/utils/utils.cpp index 4d10e08..855d5a9 100644 --- a/src/utils/utils.cpp +++ b/src/utils/utils.cpp @@ -4,16 +4,21 @@ #include "layer.hpp" #include +#include +#include // NOLINT +#include #include #include #include #include +#include #include #include #include #include #include +#include using namespace Utils; @@ -206,3 +211,24 @@ void Utils::logLimitN(const std::string& id, size_t n, const std::string& messag void Utils::resetLimitN(const std::string& id) noexcept { logCounts().erase(id); } + +/// Get the process name +std::string Utils::getProcessName() { + std::array exe{}; + const ssize_t exe_len = readlink("/proc/self/exe", exe.data(), exe.size() - 1); + if (exe_len <= 0) + return "Unknown Process"; + exe.at(static_cast(exe_len)) = '\0'; + return{basename(exe.data())}; +} + +/// Get the config file +std::string Utils::getConfigFile() { + const char* configFile = std::getenv("LSFG_CONFIG"); + if (configFile && *configFile != '\0') + return{configFile}; + const char* homePath = std::getenv("HOME"); + if (homePath && *homePath != '\0') + return std::string(homePath) + "/.config/lsfg-vk.toml"; + return "/etc/lsfg-vk.toml"; +}