#include #include #include #include "config.h" #include "../log/log.h" #include "../util_env.h" namespace dxvk { const static std::unordered_map g_appDefaults = {{ /* Anno 1800 */ { "Anno1800.exe", {{ { "d3d11.allowMapFlagNoWait", "True" } }} }, /* Assassin's Creed Syndicate: amdags issues */ { "ACS.exe", {{ { "dxgi.customVendorId", "10de" }, }} }, /* Dishonored 2 */ { "Dishonored2.exe", {{ { "d3d11.allowMapFlagNoWait", "True" } }} }, /* Dissidia Final Fantasy NT Free Edition */ { "dffnt.exe", {{ { "dxgi.deferSurfaceCreation", "True" }, }} }, /* Elite Dangerous: Compiles weird shaders * * when running on AMD hardware */ { "EliteDangerous64.exe", {{ { "dxgi.customVendorId", "10de" }, }} }, /* The Vanishing of Ethan Carter Redux */ { "EthanCarter-Win64-Shipping.exe", {{ { "dxgi.customVendorId", "10de" }, }} }, /* The Evil Within: Submits command lists * * multiple times */ { "EvilWithin.exe", {{ { "d3d11.dcSingleUseMode", "False" }, }} }, /* The Evil Within Demo */ { "EvilWithinDemo.exe", {{ { "d3d11.dcSingleUseMode", "False" }, }} }, /* Far Cry 3: Assumes clear(0.5) on an UNORM * * format to result in 128 on AMD and 127 on * * Nvidia. Most Vulkan drivers clear to 127, * * assuming higher values causes artifacts. */ { "farcry3_d3d11.exe", {{ { "dxgi.customVendorId", "10de" }, }} }, { "fc3_blooddragon_d3d11.exe", {{ { "dxgi.customVendorId", "10de" }, }} }, /* Far Cry 4: Same as Far Cry 3 */ { "FarCry4.exe", {{ { "dxgi.customVendorId", "10de" }, }} }, /* Far Cry 5: Avoid CPU <-> GPU sync */ { "FarCry5.exe", {{ { "d3d11.allowMapFlagNoWait", "True" } }} }, /* Far Cry Primal: Nvidia performance */ { "FCPrimal.exe", {{ { "dxgi.nvapiHack", "False" }, } }}, /* Frostpunk: Renders one frame with D3D9 * * after creating the DXGI swap chain */ { "Frostpunk.exe", {{ { "dxgi.deferSurfaceCreation", "True" }, }} }, /* Nioh: See Frostpunk, apparently? */ { "nioh.exe", {{ { "dxgi.deferSurfaceCreation", "True" }, }} }, /* Quantum Break: Mever initializes shared * * memory in one of its compute shaders */ { "QuantumBreak.exe", {{ { "d3d11.zeroInitWorkgroupMemory", "True" }, }} }, /* Anno 2205: Random crashes with state cache */ { "anno2205.exe", {{ { "dxvk.enableStateCache", "False" }, }} }, /* Fifa '19: Binds typed buffer SRV to shader * * that expects raw/structured buffer SRV */ { "FIFA19.exe", {{ { "dxvk.useRawSsbo", "True" }, }} }, /* Final Fantasy XIV: Fix random black blocks */ { "ffxiv_dx11.exe", {{ { "d3d11.strictDivision", "True" }, }} }, /* Fifa '19 Demo */ { "FIFA19_demo.exe", {{ { "dxvk.useRawSsbo", "True" }, }} }, /* Resident Evil 2: Improve GPU performance */ { "re2.exe", {{ { "d3d11.relaxedBarriers", "True" }, }} }, /* Resident Evil 7 */ { "re7.exe", {{ { "d3d11.relaxedBarriers", "True" }, }} }, /* Devil May Cry 5 */ { "DevilMayCry5.exe", {{ { "d3d11.relaxedBarriers", "True" }, }} }, /* Call of Duty WW2 */ { "s2_sp64_ship.exe", {{ { "dxgi.nvapiHack", "False" }, }} }, /* Need for Speed 2015 */ { "NFS16.exe", {{ { "dxgi.nvapiHack", "False" }, }} }, /* Mass Effect Andromeda */ { "MassEffectAndromeda.exe", {{ { "dxgi.nvapiHack", "False" }, }} }, /* Mirror`s Edge Catalyst */ { "MirrorsEdgeCatalyst.exe", {{ { "dxgi.nvapiHack", "False" }, }} }, /* Mirror`s Edge Catalyst Trial */ { "MirrorsEdgeCatalystTrial.exe", {{ { "dxgi.nvapiHack", "False" }, }} }, /* Star Wars Battlefront (2015) */ { "starwarsbattlefront.exe", {{ { "dxgi.nvapiHack", "False" }, }} }, /* Star Wars Battlefront (2015) Trial */ { "starwarsbattlefronttrial.exe", {{ { "dxgi.nvapiHack", "False" }, }} }, /* Dark Souls Remastered */ { "DarkSoulsRemastered.exe", {{ { "d3d11.constantBufferRangeCheck", "True" }, }} }, /* Grim Dawn */ { "Grim Dawn.exe", {{ { "d3d11.constantBufferRangeCheck", "True" }, }} }, /* NieR:Automata */ { "NieRAutomata.exe", {{ { "d3d11.constantBufferRangeCheck", "True" }, }} }, }}; static bool isWhitespace(char ch) { return ch == ' ' || ch == '\x9' || ch == '\r'; } static bool isValidKeyChar(char ch) { return (ch >= '0' && ch <= '9') || (ch >= 'A' && ch <= 'Z') || (ch >= 'a' && ch <= 'z') || (ch == '.' || ch == '_'); } static size_t skipWhitespace(const std::string& line, size_t n) { while (n < line.size() && isWhitespace(line[n])) n += 1; return n; } struct ConfigContext { bool active; }; static void parseUserConfigLine(Config& config, ConfigContext& ctx, const std::string& line) { std::stringstream key; std::stringstream value; // Extract the key size_t n = skipWhitespace(line, 0); if (n < line.size() && line[n] == '[') { n += 1; size_t e = line.size() - 1; while (e > n && line[e] != ']') e -= 1; while (n < e) key << line[n++]; ctx.active = key.str() == env::getExeName(); } else { while (n < line.size() && isValidKeyChar(line[n])) key << line[n++]; // Check whether the next char is a '=' n = skipWhitespace(line, n); if (n >= line.size() || line[n] != '=') return; // Extract the value n = skipWhitespace(line, n + 1); while (n < line.size() && !isWhitespace(line[n])) value << line[n++]; if (ctx.active) config.setOption(key.str(), value.str()); } } Config::Config() { } Config::~Config() { } Config::Config(OptionMap&& options) : m_options(std::move(options)) { } void Config::merge(const Config& other) { for (auto& pair : other.m_options) m_options.insert(pair); } void Config::setOption(const std::string& key, const std::string& value) { m_options.insert_or_assign(key, value); } std::string Config::getOptionValue(const char* option) const { auto iter = m_options.find(option); return iter != m_options.end() ? iter->second : std::string(); } bool Config::parseOptionValue( const std::string& value, std::string& result) { result = value; return true; } bool Config::parseOptionValue( const std::string& value, bool& result) { if (value == "True") { result = true; return true; } else if (value == "False") { result = false; return true; } else { return false; } } bool Config::parseOptionValue( const std::string& value, int32_t& result) { if (value.size() == 0) return false; // Parse sign, don't allow '+' int32_t sign = 1; size_t start = 0; if (value[0] == '-') { sign = -1; start = 1; } // Parse absolute number int32_t intval = 0; for (size_t i = start; i < value.size(); i++) { if (value[i] < '0' || value[i] > '9') return false; intval *= 10; intval += value[i] - '0'; } // Apply sign and return result = sign * intval; return true; } bool Config::parseOptionValue( const std::string& value, Tristate& result) { if (value == "True") { result = Tristate::True; return true; } else if (value == "False") { result = Tristate::False; return true; } else if (value == "Auto") { result = Tristate::Auto; return true; } else { return false; } } Config Config::getAppConfig(const std::string& appName) { auto appConfig = g_appDefaults.find(appName); if (appConfig != g_appDefaults.end()) { // Inform the user that we loaded a default config Logger::info(str::format("Found built-in config: ", appName)); return appConfig->second; } return Config(); } Config Config::getUserConfig() { Config config; // Load either $DXVK_CONFIG_FILE or $PWD/dxvk.conf std::string filePath = env::getEnvVar("DXVK_CONFIG_FILE"); if (filePath == "") filePath = "dxvk.conf"; // Open the file if it exists std::ifstream stream(filePath); if (!stream) return config; // Inform the user that we loaded a file, might // help when debugging configuration issues Logger::info(str::format("Found config file: ", filePath)); // Initialize parser context ConfigContext ctx; ctx.active = true; // Parse the file line by line std::string line; while (std::getline(stream, line)) parseUserConfigLine(config, ctx, line); return config; } void Config::logOptions() const { if (!m_options.empty()) { Logger::info("Effective configuration:"); for (auto& pair : m_options) Logger::info(str::format(" ", pair.first, " = ", pair.second)); } } }