| /* |
| * Copyright (c) 2023 The Khronos Group Inc. |
| * Copyright (c) 2023 Valve Corporation |
| * Copyright (c) 2023 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 <christophe@lunarg.com> |
| * - Mark Lobodzinski <mark@lunarg.com> |
| * - Jon Ashburn |
| * - Courtney Goeltzenleuchter |
| * - Tobin Ehlis |
| */ |
| #include "vulkan/layer/vk_layer_settings.h" |
| |
| #include <cstdlib> |
| #include <cassert> |
| #include <cstring> |
| #include <cctype> |
| #include <cstdarg> |
| #include <fstream> |
| #include <array> |
| #include <map> |
| #include <sstream> |
| #include <regex> |
| #include <sys/stat.h> |
| |
| #include <vulkan/vk_layer.h> |
| |
| #if defined(_WIN32) |
| #include <windows.h> |
| #include <direct.h> |
| #define GetCurrentDir _getcwd |
| #else |
| #include <unistd.h> |
| #define GetCurrentDir getcwd |
| #endif |
| |
| #ifdef __ANDROID__ |
| #include <sys/system_properties.h> |
| #endif |
| |
| namespace vku { |
| |
| static std::string format(const char *message, ...) { |
| std::size_t const STRING_BUFFER(4096); |
| |
| assert(message != nullptr); |
| assert(strlen(message) >= 1 && 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<std::string, std::string> 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<std::string*>(cookie)->assign(value); |
| }, |
| reinterpret_cast<void*>(&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<TrimMode>(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<TrimMode>(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)); |
| |
| return GetLayerSettingData(layer_key, setting_key); |
| } |
| |
| 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<std::string> Split(const std::string &value, const std::string &delimiter) { |
| std::vector<std::string> 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<std::string> inputs = GetLayerSettingStrings(layer_key, setting_key); |
| |
| List result; |
| for (std::size_t i = 0, n = inputs.size(); i < n; ++i) { |
| std::pair<std::string, int> 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<std::string, std::string>::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<std::string, std::string>::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<HKEY, 2> 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<LPBYTE>(&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 |