| // Copyright 2017 The Android Open Source Project |
| // |
| // 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. |
| |
| #include "host-common/opengl/NativeGpuInfo.h" |
| |
| #include "aemu/base/StringFormat.h" |
| #include "aemu/base/containers/SmallVector.h" |
| #include "aemu/base/StringFormat.h" |
| #include "aemu/base/files/PathUtils.h" |
| #include "aemu/base/system/System.h" |
| #include "aemu/base/system/Win32UnicodeString.h" |
| |
| #include <windows.h> |
| #include <d3d9.h> |
| |
| #include <ctype.h> |
| |
| #include <algorithm> |
| #include <string> |
| #include <tuple> |
| |
| using android::base::PathUtils; |
| using android::base::SmallFixedVector; |
| using android::base::StringFormat; |
| using android::base::Win32UnicodeString; |
| |
| static std::string& toLower(std::string& s) { |
| std::transform(s.begin(), s.end(), s.begin(), ::tolower); |
| return s; |
| } |
| |
| static void parse_windows_gpu_ids(const std::string& val, |
| GpuInfoList* gpulist) { |
| std::string result; |
| size_t key_start = 0; |
| size_t key_end = 0; |
| |
| key_start = val.find("VEN_", key_start); |
| if (key_start == std::string::npos) { |
| return; |
| } |
| key_end = val.find("&", key_start); |
| if (key_end == std::string::npos) { |
| return; |
| } |
| result = val.substr(key_start + 4, key_end - key_start - 4); |
| gpulist->currGpu().make = std::move(toLower(result)); |
| |
| key_start = val.find("DEV_", key_start); |
| if (key_start == std::string::npos) { |
| return; |
| } |
| key_end = val.find("&", key_start); |
| if (key_end == std::string::npos) { |
| return; |
| } |
| result = val.substr(key_start + 4, key_end - key_start - 4); |
| gpulist->currGpu().device_id = std::move(toLower(result)); |
| } |
| |
| static bool startsWith(const std::string& string, const std::string& prefix) { |
| return string.size() >= prefix.size() && |
| memcmp(string.data(), prefix.data(), prefix.size()) == 0; |
| } |
| |
| static void add_predefined_gpu_dlls(GpuInfo* gpu) { |
| const std::string& currMake = gpu->make; |
| if (currMake == "NVIDIA" || startsWith(gpu->model, "NVIDIA")) { |
| gpu->addDll("nvoglv32.dll"); |
| gpu->addDll("nvoglv64.dll"); |
| } else if (currMake == "Advanced Micro Devices, Inc." || |
| startsWith(gpu->model, "Advanced Micro Devices, Inc.")) { |
| gpu->addDll("atioglxx.dll"); |
| gpu->addDll("atig6txx.dll"); |
| } |
| } |
| |
| static void parse_windows_gpu_dlls(int line_loc, |
| int val_pos, |
| const std::string& contents, |
| GpuInfoList* gpulist) { |
| if (line_loc - val_pos != 0) { |
| const std::string& dll_str = |
| contents.substr(val_pos, line_loc - val_pos); |
| |
| size_t vp = 0; |
| size_t dll_sep_loc = dll_str.find(",", vp); |
| size_t dll_end = (dll_sep_loc != std::string::npos) |
| ? dll_sep_loc |
| : dll_str.size() - vp; |
| gpulist->currGpu().addDll(dll_str.substr(vp, dll_end - vp)); |
| |
| while (dll_sep_loc != std::string::npos) { |
| vp = dll_sep_loc + 1; |
| dll_sep_loc = dll_str.find(",", vp); |
| dll_end = (dll_sep_loc != std::string::npos) ? dll_sep_loc |
| : dll_str.size() - vp; |
| gpulist->currGpu().addDll(dll_str.substr(vp, dll_end - vp)); |
| } |
| } |
| |
| add_predefined_gpu_dlls(&gpulist->currGpu()); |
| } |
| |
| static void load_gpu_registry_info(const wchar_t* keyName, GpuInfo* gpu) { |
| HKEY hkey; |
| if (::RegOpenKeyW(HKEY_LOCAL_MACHINE, keyName, &hkey) != ERROR_SUCCESS) { |
| return; |
| } |
| |
| SmallFixedVector<wchar_t, 256> name; |
| SmallFixedVector<BYTE, 1024> value; |
| for (int i = 0;; ++i) { |
| name.resize_noinit(name.capacity()); |
| value.resize_noinit(value.capacity()); |
| DWORD nameLen = name.size(); |
| DWORD valueLen = value.size(); |
| DWORD type; |
| auto res = RegEnumValueW(hkey, i, name.data(), &nameLen, nullptr, &type, |
| value.data(), &valueLen); |
| if (res == ERROR_NO_MORE_ITEMS) { |
| break; |
| } else if (res == ERROR_MORE_DATA) { |
| if (type != REG_SZ && type != REG_MULTI_SZ) { |
| // we don't care about other types for now, so let's not even |
| // try |
| continue; |
| } |
| name.resize_noinit(nameLen + 1); |
| value.resize_noinit(valueLen + 1); |
| nameLen = name.size(); |
| valueLen = value.size(); |
| res = ::RegEnumValueW(hkey, i, name.data(), &nameLen, nullptr, |
| &type, value.data(), &valueLen); |
| if (res != ERROR_SUCCESS) { |
| break; |
| } |
| } |
| if (res != ERROR_SUCCESS) { |
| break; // well, what can we do here? |
| } |
| |
| name[nameLen] = L'\0'; |
| |
| if (type == REG_SZ && wcscmp(name.data(), L"DriverVersion") == 0) { |
| const auto strVal = (wchar_t*)value.data(); |
| const auto strLen = valueLen / sizeof(wchar_t); |
| strVal[strLen] = L'\0'; |
| gpu->version = Win32UnicodeString::convertToUtf8(strVal, strLen); |
| } else if (type == REG_MULTI_SZ && |
| (wcscmp(name.data(), L"UserModeDriverName") == 0 || |
| wcscmp(name.data(), L"UserModeDriverNameWoW") == 0)) { |
| const auto strVal = (wchar_t*)value.data(); |
| const auto strLen = valueLen / sizeof(wchar_t); |
| strVal[strLen] = L'\0'; |
| // Iterate over the '0'-delimited list of strings, |
| // stopping at double '0' (AKA empty string after the |
| // delimiter). |
| for (const wchar_t* ptr = strVal;;) { |
| auto len = wcslen(ptr); |
| if (!len) { |
| break; |
| } |
| gpu->dlls.emplace_back( |
| Win32UnicodeString::convertToUtf8(ptr, len)); |
| ptr += len + 1; |
| } |
| } |
| } |
| |
| ::RegCloseKey(hkey); |
| } |
| |
| // static const int kGPUInfoQueryTimeoutMs = 5000; |
| // static std::string load_gpu_info_wmic() { |
| // auto guid = Uuid::generateFast().toString(); |
| // // WMIC doesn't allow one to have any unquoted '-' characters in file name, |
| // // so let's get rid of them. |
| // guid.erase(std::remove(guid.begin(), guid.end(), '-'), guid.end()); |
| // auto tempName = PathUtils::join(System::get()->getTempDir(), |
| // StringFormat("gpuinfo_%s.txt", guid)); |
| // |
| // auto deleteTempFile = makeCustomScopedPtr( |
| // &tempName, |
| // [](const std::string* name) { path_delete_file(name->c_str()); }); |
| // if (!System::get()->runCommand( |
| // {"wmic", StringFormat("/OUTPUT:%s", tempName), "path", |
| // "Win32_VideoController", "get", "/value"}, |
| // RunOptions::WaitForCompletion | RunOptions::TerminateOnTimeout, |
| // kGPUInfoQueryTimeoutMs)) { |
| // return {}; |
| // } |
| // auto res = android::readFileIntoString(tempName); |
| // return res ? Win32UnicodeString::convertToUtf8( |
| // (const wchar_t*)res->c_str(), |
| // res->size() / sizeof(wchar_t)) |
| // : std::string{}; |
| // } |
| |
| void parse_gpu_info_list_windows(const std::string& contents, |
| GpuInfoList* gpulist) { |
| size_t line_loc = contents.find("\r\n"); |
| if (line_loc == std::string::npos) { |
| line_loc = contents.size(); |
| } |
| size_t p = 0; |
| size_t equals_pos = 0; |
| size_t val_pos = 0; |
| std::string key; |
| std::string val; |
| |
| // Windows: We use `wmic path Win32_VideoController get /value` |
| // to get a reasonably detailed list of '<key>=<val>' |
| // pairs. From these, we can get the make/model |
| // of the GPU, the driver version, and all DLLs involved. |
| while (line_loc != std::string::npos) { |
| equals_pos = contents.find("=", p); |
| if ((equals_pos != std::string::npos) && (equals_pos < line_loc)) { |
| key = contents.substr(p, equals_pos - p); |
| val_pos = equals_pos + 1; |
| val = contents.substr(val_pos, line_loc - val_pos); |
| |
| if (key.find("AdapterCompatibility") != std::string::npos) { |
| gpulist->addGpu(); |
| gpulist->currGpu().os = "W"; |
| // 'make' will be overwritten in parsing 'PNPDeviceID' |
| // later. Set it here because we need it in paring |
| // 'InstalledDisplayDrivers' which comes before |
| // 'PNPDeviceID'. |
| gpulist->currGpu().make = val; |
| } else if (key.find("Caption") != std::string::npos) { |
| gpulist->currGpu().model = val; |
| } else if (key.find("PNPDeviceID") != std::string::npos) { |
| parse_windows_gpu_ids(val, gpulist); |
| } else if (key.find("DriverVersion") != std::string::npos) { |
| gpulist->currGpu().version = val; |
| } else if (key.find("InstalledDisplayDrivers") != |
| std::string::npos) { |
| parse_windows_gpu_dlls(line_loc, val_pos, contents, gpulist); |
| } |
| } |
| if (line_loc == contents.size()) { |
| break; |
| } |
| p = line_loc + 2; |
| line_loc = contents.find("\r\n", p); |
| if (line_loc == std::string::npos) { |
| line_loc = contents.size(); |
| } |
| } |
| } |
| |
| static bool queryGpuInfoD3D(GpuInfoList* gpus) { |
| LPDIRECT3D9 pD3D = Direct3DCreate9(D3D_SDK_VERSION); |
| UINT numAdapters = pD3D->GetAdapterCount(); |
| |
| char vendoridBuf[16] = {}; |
| char deviceidBuf[16] = {}; |
| |
| // MAX_DEVICE_IDENTIFIER_STRING can be pretty big, |
| // don't allocate on stack. |
| std::vector<char> descriptionBuf(MAX_DEVICE_IDENTIFIER_STRING + 1, '\0'); |
| |
| if (numAdapters == 0) return false; |
| |
| // The adapter that is equal to D3DADAPTER_DEFAULT is the primary display adapter. |
| // D3DADAPTER_DEFAULT is currently defined to be 0, btw---but this is more future proof |
| for (UINT i = 0; i < numAdapters; i++) { |
| if (i == D3DADAPTER_DEFAULT) { |
| gpus->addGpu(); |
| GpuInfo& gpu = gpus->currGpu(); |
| gpu.os = "W"; |
| |
| D3DADAPTER_IDENTIFIER9 id; |
| pD3D->GetAdapterIdentifier(0, 0, &id); |
| snprintf(vendoridBuf, sizeof(vendoridBuf), "%04x", (unsigned int)id.VendorId); |
| snprintf(deviceidBuf, sizeof(deviceidBuf), "%04x", (unsigned int)id.DeviceId); |
| snprintf(&descriptionBuf[0], MAX_DEVICE_IDENTIFIER_STRING, "%s", id.Description); |
| gpu.make = vendoridBuf; |
| gpu.device_id = deviceidBuf; |
| gpu.model = &descriptionBuf[0]; |
| // crashhandler_append_message_format( |
| // "gpu found. vendor id %04x device id 0x%04x\n", |
| // (unsigned int)(id.VendorId), |
| // (unsigned int)(id.DeviceId)); |
| return true; |
| } |
| } |
| |
| return false; |
| } |
| |
| void getGpuInfoListNative(GpuInfoList* gpus) { |
| if (queryGpuInfoD3D(gpus)) return; |
| |
| // crashhandler_append_message_format("d3d gpu query failed.\n"); |
| |
| DISPLAY_DEVICEW device = { sizeof(device) }; |
| |
| for (int i = 0; EnumDisplayDevicesW(nullptr, i, &device, 0); ++i) { |
| gpus->addGpu(); |
| GpuInfo& gpu = gpus->currGpu(); |
| gpu.os = "W"; |
| gpu.current_gpu = |
| (device.StateFlags & DISPLAY_DEVICE_PRIMARY_DEVICE) != 0; |
| gpu.model = Win32UnicodeString::convertToUtf8(device.DeviceString); |
| parse_windows_gpu_ids( |
| Win32UnicodeString::convertToUtf8(device.DeviceID), gpus); |
| |
| // Now try inspecting the registry directly; |device|.DeviceKey can be a |
| // path to the GPU information key. |
| static const std::string prefix = "\\Registry\\Machine\\"; |
| if (startsWith(Win32UnicodeString::convertToUtf8(device.DeviceKey), |
| prefix)) { |
| load_gpu_registry_info(device.DeviceKey + prefix.size(), &gpu); |
| } |
| add_predefined_gpu_dlls(&gpu); |
| } |
| |
| if (gpus->infos.empty()) { |
| // Everything failed; bail. |
| // Everything failed - fall back to the good^Wbad old WMIC command. |
| // auto gpuInfoWmic = load_gpu_info_wmic(); |
| // parse_gpu_info_list_windows(gpuInfoWmic, gpus); |
| } |
| } |
| |
| // windows: blacklist depending on amdvlk and certain versions of vulkan-1.dll |
| // Based on chromium/src/gpu/config/gpu_info_collector_win.cc |
| bool badAmdVulkanDriverVersion() { |
| int major, minor, build_1, build_2; |
| |
| // crashhandler_append_message_format( |
| // "checking for bad AMD Vulkan driver version...\n"); |
| |
| if (!android::base::queryFileVersionInfo("amdvlk64.dll", &major, &minor, &build_1, &build_2)) { |
| // crashhandler_append_message_format( |
| // "amdvlk64.dll not found. Checking for amdvlk32...\n"); |
| if (!android::base::queryFileVersionInfo("amdvlk32.dll", &major, &minor, &build_1, &build_2)) { |
| // crashhandler_append_message_format( |
| // "amdvlk32.dll not found. No bad AMD Vulkan driver versions found.\n"); |
| // Information about amdvlk64 not availble; not blacklisted |
| return false; |
| } |
| } |
| |
| // crashhandler_append_message_format( |
| // "AMD driver info found. Version: %d.%d.%d.%d\n", |
| // major, minor, build_1, build_2); |
| |
| bool isBad = (major == 1 && minor == 0 && build_1 <= 54); |
| |
| if (isBad) { |
| // crashhandler_append_message_format( |
| // "Is bad AMD driver version; blacklisting.\n"); |
| } else { |
| // crashhandler_append_message_format( |
| // "Not known bad AMD driver version; passing.\n"); |
| } |
| |
| return isBad; |
| } |
| |
| using WindowsDllVersion = std::tuple<int, int, int, int>; |
| |
| bool badVulkanDllVersion() { |
| int major, minor, build_1, build_2; |
| |
| // crashhandler_append_message_format( |
| // "checking for bad vulkan-1.dll version...\n"); |
| |
| if (!android::base::queryFileVersionInfo("vulkan-1.dll", &major, &minor, &build_1, &build_2)) { |
| // crashhandler_append_message_format( |
| // "info on vulkan-1.dll cannot be found, continue.\n"); |
| // Information about vulkan-1.dll not available; not blacklisted |
| return false; |
| } |
| |
| // crashhandler_append_message_format( |
| // "vulkan-1.dll version: %d.%d.%d.%d\n", |
| // major, minor, build_1, build_2); |
| |
| // Ban all Windows Vulkan drivers < 1.1; |
| // they sometimes advertise vkEnumerateInstanceVersion |
| // and then running that function pointer causes a segfault. |
| // In any case, properly updated GPU drivers for Windows |
| // should all be 1.1 now for the major manufacturers |
| // (even Intel; see https://www.intel.com/content/www/us/en/support/articles/000005524/graphics-drivers.html) |
| bool isBad = |
| major == 1 && minor == 0; |
| |
| if (isBad) { |
| // crashhandler_append_message_format( |
| // "Is bad vulkan-1.dll version; blacklisting.\n"); |
| } else { |
| // crashhandler_append_message_format( |
| // "Not known bad vulkan-1.dll version; continue.\n"); |
| } |
| |
| return isBad; |
| } |
| |
| bool isVulkanSafeToUseNative() { |
| return !badAmdVulkanDriverVersion() && !badVulkanDllVersion(); |
| } |