translate context to new config system

This commit is contained in:
PancakeTAS 2025-07-17 00:42:03 +02:00 committed by Pancake
parent 5e68315c2f
commit 9f528a688a
8 changed files with 201 additions and 177 deletions

View file

@ -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.
///

View file

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

View file

@ -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();
}

View file

@ -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<std::atomic_bool>(true);
auto res = updateConfig(file);
if (!res)
return false;
// parse config file
std::optional<toml::value> 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::table>(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<std::atomic_bool>(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::array>(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<std::string>(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<toml::value> 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::table>(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<std::string, Configuration> games;
const toml::value gamesList = toml::find_or_default<toml::array>(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<std::string>(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;

View file

@ -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 <vulkan/vulkan_core.h>
#include <lsfg_3_1.hpp>
#include <lsfg_3_1p.hpp>
#include <algorithm>
#include <exception>
#include <iostream>
#include <cstdint>
#include <cstdlib>
#include <atomic>
#include <string>
#include <memory>
#include <vector>
#include <array>
LsContext::LsContext(const Hooks::DeviceInfo& info, VkSwapchainKHR swapchain,
VkExtent2D extent, const std::vector<VkImage>& 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<int, 2> 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<int> 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<int> 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<int32_t>(
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<VkSemaphore>& 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<int> renderSemaphoreFds(info.frameGen);
for (size_t i = 0; i < info.frameGen; ++i)
std::vector<int> 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<VkSemaphore> 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,

View file

@ -4,6 +4,7 @@
#include "context.hpp"
#include "layer.hpp"
#include <iostream>
#include <vulkan/vulkan_core.h>
#include <unordered_map>
@ -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,

View file

@ -1,15 +1,12 @@
#include "config/config.hpp"
#include "extract/extract.hpp"
#include "utils/utils.hpp"
#include <array>
#include <cstdlib>
#include <cstring>
#include <exception>
#include <iostream>
#include <string>
#include <string.h> // NOLINT
#include <unistd.h>
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<char, 4096> 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<size_t>(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";
}
}

View file

@ -4,16 +4,21 @@
#include "layer.hpp"
#include <vulkan/vulkan_core.h>
#include <sys/types.h>
#include <string.h> // NOLINT
#include <unistd.h>
#include <unordered_map>
#include <algorithm>
#include <optional>
#include <iostream>
#include <cstdlib>
#include <cstdint>
#include <cstring>
#include <utility>
#include <string>
#include <vector>
#include <array>
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<char, 4096> 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<size_t>(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";
}