/* * Copyright (c) 2015-2022 The Khronos Group Inc. * Copyright (c) 2015-2022 Valve Corporation * Copyright (c) 2015-2022 LunarG, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * * Authors: * - Christophe Riccio * - Mark Lobodzinski * - Jon Ashburn * - Courtney Goeltzenleuchter * - Tobin Ehlis */ #include "vk_layer_settings.h" #include #include #include #include #include #include #include #include #include #include #include #include #if defined(_WIN32) #include #include #define GetCurrentDir _getcwd #else #include #define GetCurrentDir getcwd #endif #ifdef __ANDROID__ #include #endif namespace vku { static std::string format(const char *message, ...) { std::size_t const STRING_BUFFER(4096); assert(message != nullptr); assert(strlen(message) >= 0 && strlen(message) < STRING_BUFFER); char buffer[STRING_BUFFER]; va_list list; va_start(list, message); vsnprintf(buffer, STRING_BUFFER, message, list); va_end(list); return buffer; } static bool IsFrames(const std::string &s) { static const std::regex FRAME_REGEX("^([0-9]+([-][0-9]+){0,2})(,([0-9]+([-][0-9]+){0,2}))*$"); return std::regex_search(s, FRAME_REGEX); } static bool IsNumber(const std::string &s) { static const std::regex FRAME_REGEX("^-?[0-9]*$"); return std::regex_search(s, FRAME_REGEX); } static bool IsFloat(const std::string &s) { static const std::regex FRAME_REGEX("^-?[0-9]*([.][0-9]*)?$"); return std::regex_search(s, FRAME_REGEX); } enum Source { SOURCE_VKCONFIG, SOURCE_ENV_VAR, SOURCE_LOCAL, }; struct SettingsFileInfo { SettingsFileInfo() : file_found(false), source(SOURCE_LOCAL) {} bool file_found; std::string location; Source source; }; class LayerSettings { public: LayerSettings(); ~LayerSettings(){}; void SetCallback(LAYER_SETTING_LOG_CALLBACK callback) { this->callback_ = callback; } void Log(const std::string &setting_key, const std::string &message); bool Is(const std::string &setting_key); const char *Get(const std::string &setting_key); void Set(const std::string &setting_key, const std::string &setting_value); std::string vk_layer_disables_env_var; SettingsFileInfo settings_info; private: bool file_is_parsed_; std::map value_map_; std::string last_log_setting; std::string last_log_message; std::string FindSettings(); void ParseFile(const char *filename); LAYER_SETTING_LOG_CALLBACK callback_; }; static LayerSettings vk_layer_settings; #if defined(__ANDROID__) std::string GetAndroidProperty(const char* name) { std::string output; const prop_info* pi = __system_property_find(name); if (pi) { __system_property_read_callback( pi, [](void* cookie, const char* name, const char* value, uint32_t serial) { reinterpret_cast(cookie)->assign(value); }, reinterpret_cast(&output)); } return output; } #endif static bool IsEnvironment(const char *variable) { #if defined(__ANDROID__) return !GetAndroidProperty(variable).empty(); #else return std::getenv(variable) != NULL; #endif } static std::string GetEnvironment(const char *variable) { #if defined(__ANDROID__) return GetAndroidProperty(variable); #else const char *output = std::getenv(variable); return output == NULL ? "" : output; #endif } static std::string string_tolower(const std::string &s) { std::string result = s; for (auto &c : result) { c = (char) std::tolower(c); } return result; } static std::string string_toupper(const std::string &s) { std::string result = s; for (auto &c : result) { c = (char) std::toupper(c); } return result; } const char *GetLayerEnvVar(const char *setting_env) { vk_layer_settings.vk_layer_disables_env_var = GetEnvironment(setting_env); return vk_layer_settings.vk_layer_disables_env_var.c_str(); } static std::string TrimPrefix(const std::string &layer_key) { std::string key {}; if (layer_key.find("VK_LAYER_") == 0) { std::size_t prefix = std::strlen("VK_LAYER_"); key = layer_key.substr(prefix, layer_key.size() - prefix); } else { key = layer_key; } return key; } static std::string GetSettingKey(const char *layer_key, const char *setting_key) { std::stringstream result; result << string_tolower(TrimPrefix(layer_key)) << "." << setting_key; return result.str(); } static inline std::string TrimVendor(const std::string &layer_key) { static const char *separator = "_"; const std::string &namespace_key = TrimPrefix(layer_key); const auto trimmed_beg = namespace_key.find_first_of(separator); if (trimmed_beg == std::string::npos) return namespace_key; assert(namespace_key.find_last_not_of(separator) != std::string::npos && trimmed_beg <= namespace_key.find_last_not_of(separator)); return namespace_key.substr(trimmed_beg + 1, namespace_key.size()); } enum TrimMode { TRIM_NONE, TRIM_VENDOR, TRIM_NAMESPACE, TRIM_FIRST = TRIM_NONE, TRIM_LAST = TRIM_NAMESPACE, }; static std::string GetEnvVarKey(const char *layer_key, const char *setting_key, TrimMode trim_mode) { std::stringstream result; #if defined(__ANDROID__) switch (trim_mode) { default: case TRIM_NONE: { result << "debug.vulkan." << GetSettingKey(layer_key, setting_key); break; } case TRIM_VENDOR: { result << "debug.vulkan." << GetSettingKey(TrimVendor(layer_key).c_str(), setting_key); break; } case TRIM_NAMESPACE: { result << "debug.vulkan." << setting_key; break; } } #else switch (trim_mode) { default: case TRIM_NONE: { result << "VK_" << string_toupper(TrimPrefix(layer_key)) << "_" << string_toupper(setting_key); break; } case TRIM_VENDOR: { result << "VK_" << string_toupper(TrimVendor(layer_key)) << "_" << string_toupper(setting_key); break; } case TRIM_NAMESPACE: { result << "VK_" << string_toupper(setting_key); break; } } #endif return result.str(); } void InitLayerSettingsLogCallback(LAYER_SETTING_LOG_CALLBACK callback) { vk_layer_settings.SetCallback(callback); return; } bool IsLayerSetting(const char *layer_key, const char *setting_key) { assert(layer_key); assert(!std::string(layer_key).empty()); assert(setting_key); assert(!std::string(setting_key).empty()); for (int i = TRIM_FIRST, n = TRIM_LAST; i <= n; ++i) { if (IsEnvironment(GetEnvVarKey(layer_key, setting_key, static_cast(i)).c_str())) return true; } return vk_layer_settings.Is(GetSettingKey(layer_key, setting_key).c_str()); } static std::string GetLayerSettingData(const char *layer_key, const char *setting_key) { // First search in the environment variables for (int i = TRIM_FIRST, n = TRIM_LAST; i <= n; ++i) { std::string setting = GetLayerEnvVar(GetEnvVarKey(layer_key, setting_key, static_cast(i)).c_str()); if (!setting.empty()) return setting; } // Second search in vk_layer_settings.txt return vk_layer_settings.Get(GetSettingKey(layer_key, setting_key).c_str()); } bool GetLayerSettingBool(const char *layer_key, const char *setting_key) { assert(IsLayerSetting(layer_key, setting_key)); bool result = false; // default value std::string setting = string_tolower(GetLayerSettingData(layer_key, setting_key)); if (setting.empty()) { vk_layer_settings.Log(setting_key, "The setting is used but the value is empty which is invalid for a boolean setting type."); } else if (IsNumber(setting)) { result = std::atoi(setting.c_str()) != 0; } else if (setting == "true" || setting == "false") { result = setting == "true"; } else { std::string message = format("The data provided (%s) is not a boolean value.", setting.c_str()); vk_layer_settings.Log(setting_key, message); } return result; } int GetLayerSettingInt(const char *layer_key, const char *setting_key) { assert(IsLayerSetting(layer_key, setting_key)); int result = 0; // default value std::string setting = GetLayerSettingData(layer_key, setting_key); if (setting.empty()) { std::string message = "The setting is used but the value is empty which is invalid for a integer setting type."; vk_layer_settings.Log(setting_key, message); } else if (!IsNumber(setting)) { std::string message = format("The data provided (%s) is not an integer value.", setting.c_str()); vk_layer_settings.Log(setting_key, message); } else { result = std::atoi(setting.c_str()); } return result; } double GetLayerSettingFloat(const char *layer_key, const char *setting_key) { assert(IsLayerSetting(layer_key, setting_key)); double result = 0.0; // default value std::string setting = GetLayerSettingData(layer_key, setting_key); if (setting.empty()) { std::string message = "The setting is used but the value is empty which is invalid for a floating-point setting type."; vk_layer_settings.Log(setting_key, message); } else if (!IsFloat(setting)) { std::string message = format("The data provided (%s) is not a floating-point value.", setting.c_str()); vk_layer_settings.Log(setting_key, message); } else { result = std::atof(setting.c_str()); } return result; } std::string GetLayerSettingString(const char *layer_key, const char *setting_key) { assert(IsLayerSetting(layer_key, setting_key)); std::string setting = GetLayerSettingData(layer_key, setting_key); if (setting.empty()) { std::string message = "The setting is used but the value is empty which is invalid for a string setting type."; vk_layer_settings.Log(setting_key, message); } return setting; } std::string GetLayerSettingFrames(const char *layer_key, const char *setting_key) { assert(IsLayerSetting(layer_key, setting_key)); std::string setting = GetLayerSettingData(layer_key, setting_key); if (!setting.empty() && !IsFrames(setting)) { std::string message = format("The data provided (%s) is not a frames value.", setting.c_str()); vk_layer_settings.Log(setting_key, message); } return setting; } static inline std::vector Split(const std::string &value, const std::string &delimiter) { std::vector result; std::string parse = value; std::size_t start = 0; std::size_t end = parse.find(delimiter); while (end != std::string::npos) { result.push_back(parse.substr(start, end - start)); start = end + delimiter.length(); end = parse.find(delimiter, start); } const std::string last = parse.substr(start, end); if (!last.empty()) { result.push_back(last); } return result; } Strings GetLayerSettingStrings(const char *layer_key, const char *setting_key) { assert(IsLayerSetting(layer_key, setting_key)); std::string setting = GetLayerSettingData(layer_key, setting_key); if (setting.find_first_of(",") != std::string::npos) { return Split(setting, ","); } else { #ifdef _WIN32 const char *delimiter = ";"; #else const char *delimiter = ":"; #endif return Split(setting, delimiter); } } List GetLayerSettingList(const char *layer_key, const char *setting_key) { assert(IsLayerSetting(layer_key, setting_key)); std::vector inputs = GetLayerSettingStrings(layer_key, setting_key); List result; for (std::size_t i = 0, n = inputs.size(); i < n; ++i) { std::pair value; if (IsNumber(inputs[i])) { value.second = atoi(inputs[i].c_str()); } else { value.first = inputs[i]; } result.push_back(value); } return result; } // Constructor for ConfigFile. Initialize layers to log error messages to stdout by default. If a vk_layer_settings file is present, // its settings will override the defaults. LayerSettings::LayerSettings() : file_is_parsed_(false), callback_(nullptr) {} void LayerSettings::Log(const std::string &setting_key, const std::string &message) { this->last_log_setting = setting_key; this->last_log_message = message; if (this->callback_ == nullptr) { fprintf(stderr, "LAYER SETTING (%s) error: %s\n", this->last_log_setting.c_str(), this->last_log_message.c_str()); } else { this->callback_(this->last_log_setting.c_str(), this->last_log_message.c_str()); } } bool LayerSettings::Is(const std::string &setting_key) { std::map::const_iterator it; if (!file_is_parsed_) { std::string settings_file = FindSettings(); ParseFile(settings_file.c_str()); } return value_map_.find(setting_key) != value_map_.end(); } const char *LayerSettings::Get(const std::string &setting_key) { std::map::const_iterator it; if (!file_is_parsed_) { std::string settings_file = FindSettings(); ParseFile(settings_file.c_str()); } if ((it = value_map_.find(setting_key)) == value_map_.end()) { return ""; } else { return it->second.c_str(); } } void LayerSettings::Set(const std::string &setting_key, const std::string &value) { if (!file_is_parsed_) { std::string settings_file = FindSettings(); ParseFile(settings_file.c_str()); } value_map_[setting_key] = value; } #if defined(WIN32) // Check for admin rights static inline bool IsHighIntegrity() { HANDLE process_token; if (OpenProcessToken(GetCurrentProcess(), TOKEN_QUERY | TOKEN_QUERY_SOURCE, &process_token)) { // Maximum possible size of SID_AND_ATTRIBUTES is maximum size of a SID + size of attributes DWORD. uint8_t mandatory_label_buffer[SECURITY_MAX_SID_SIZE + sizeof(DWORD)]; DWORD buffer_size; if (GetTokenInformation(process_token, TokenIntegrityLevel, mandatory_label_buffer, sizeof(mandatory_label_buffer), &buffer_size) != 0) { const TOKEN_MANDATORY_LABEL *mandatory_label = (const TOKEN_MANDATORY_LABEL *)mandatory_label_buffer; const DWORD sub_authority_count = *GetSidSubAuthorityCount(mandatory_label->Label.Sid); const DWORD integrity_level = *GetSidSubAuthority(mandatory_label->Label.Sid, sub_authority_count - 1); CloseHandle(process_token); return integrity_level > SECURITY_MANDATORY_MEDIUM_RID; } CloseHandle(process_token); } return false; } #endif std::string LayerSettings::FindSettings() { struct stat info; #if defined(WIN32) // Look for VkConfig-specific settings location specified in the windows registry HKEY key; const std::array hives = {HKEY_LOCAL_MACHINE, HKEY_CURRENT_USER}; const size_t hives_to_check_count = IsHighIntegrity() ? 1 : hives.size(); // Admin checks only the default hive for (size_t hive_index = 0; hive_index < hives_to_check_count; ++hive_index) { LSTATUS err = RegOpenKeyEx(hives[hive_index], "Software\\Khronos\\Vulkan\\Settings", 0, KEY_READ, &key); if (err == ERROR_SUCCESS) { char name[2048]; DWORD i = 0, name_size, type, value, value_size; while (ERROR_SUCCESS == RegEnumValue(key, i++, name, &(name_size = sizeof(name)), nullptr, &type, reinterpret_cast(&value), &(value_size = sizeof(value)))) { // Check if the registry entry is a dword with a value of zero if (type != REG_DWORD || value != 0) { continue; } // Check if this actually points to a file if ((stat(name, &info) != 0) || !(info.st_mode & S_IFREG)) { continue; } // Use this file RegCloseKey(key); settings_info.source = SOURCE_VKCONFIG; settings_info.location = name; return name; } RegCloseKey(key); } } #else // Look for VkConfig-specific settings location specified in a specific spot in the linux settings store std::string search_path = GetEnvironment("XDG_DATA_HOME"); if (search_path == "") { search_path = GetEnvironment("HOME"); if (search_path != "") { search_path += "/.local/share"; } } // Use the vk_layer_settings.txt file from here, if it is present if (search_path != "") { std::string home_file = search_path + "/vulkan/settings.d/vk_layer_settings.txt"; if (stat(home_file.c_str(), &info) == 0) { if (info.st_mode & S_IFREG) { settings_info.source = SOURCE_VKCONFIG; settings_info.location = home_file; return home_file; } } } #endif #ifdef __ANDROID__ std::string env_path = GetEnvironment("debug.vulkan.khronos_profiles.settings_path"); #else // Look for an environment variable override for the settings file location std::string env_path = GetEnvironment("VK_LAYER_SETTINGS_PATH"); #endif // If the path exists use it, else use vk_layer_settings if (stat(env_path.c_str(), &info) == 0) { // If this is a directory, append settings file name if (info.st_mode & S_IFDIR) { env_path.append("/vk_layer_settings.txt"); } settings_info.source = SOURCE_ENV_VAR; settings_info.location = env_path; return env_path; } // Default -- use the current working directory for the settings file location settings_info.source = SOURCE_LOCAL; char buff[512]; auto buf_ptr = GetCurrentDir(buff, 512); if (buf_ptr) { settings_info.location = buf_ptr; settings_info.location.append("/vk_layer_settings.txt"); } return "vk_layer_settings.txt"; } static inline std::string TrimWhitespace(const std::string &s) { const char *whitespace = " \t\f\v\n\r"; const auto trimmed_beg = s.find_first_not_of(whitespace); if (trimmed_beg == std::string::npos) return ""; const auto trimmed_end = s.find_last_not_of(whitespace); assert(trimmed_end != std::string::npos && trimmed_beg <= trimmed_end); return s.substr(trimmed_beg, trimmed_end - trimmed_beg + 1); } void LayerSettings::ParseFile(const char *filename) { file_is_parsed_ = true; // Extract option = value pairs from a file std::ifstream file(filename); if (file.good()) { settings_info.file_found = true; for (std::string line; std::getline(file, line);) { // discard comments, which start with '#' const auto comments_pos = line.find_first_of('#'); if (comments_pos != std::string::npos) line.erase(comments_pos); const auto value_pos = line.find_first_of('='); if (value_pos != std::string::npos) { const std::string setting_key = TrimWhitespace(line.substr(0, value_pos)); const std::string setting_value = TrimWhitespace(line.substr(value_pos + 1)); value_map_[setting_key] = setting_value; } } } } } // namespace vku