1 // Copyright 2017 The Android Open Source Project
2 //
3 // Licensed under the Apache License, Version 2.0 (the "License");
4 // you may not use this file except in compliance with the License.
5 // You may obtain a copy of the License at
6 //
7 // http://www.apache.org/licenses/LICENSE-2.0
8 //
9 // Unless required by applicable law or agreed to in writing, software
10 // distributed under the License is distributed on an "AS IS" BASIS,
11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 // See the License for the specific language governing permissions and
13 // limitations under the License.
14
15 #include "host-common/opengl/NativeGpuInfo.h"
16
17 #include "aemu/base/StringFormat.h"
18 #include "aemu/base/containers/SmallVector.h"
19 #include "aemu/base/StringFormat.h"
20 #include "aemu/base/files/PathUtils.h"
21 #include "aemu/base/system/System.h"
22 #include "aemu/base/system/Win32UnicodeString.h"
23
24 #include <windows.h>
25 #include <d3d9.h>
26
27 #include <ctype.h>
28
29 #include <algorithm>
30 #include <string>
31 #include <tuple>
32
33 using android::base::PathUtils;
34 using android::base::SmallFixedVector;
35 using android::base::StringFormat;
36 using android::base::Win32UnicodeString;
37
toLower(std::string & s)38 static std::string& toLower(std::string& s) {
39 std::transform(s.begin(), s.end(), s.begin(), ::tolower);
40 return s;
41 }
42
parse_windows_gpu_ids(const std::string & val,GpuInfoList * gpulist)43 static void parse_windows_gpu_ids(const std::string& val,
44 GpuInfoList* gpulist) {
45 std::string result;
46 size_t key_start = 0;
47 size_t key_end = 0;
48
49 key_start = val.find("VEN_", key_start);
50 if (key_start == std::string::npos) {
51 return;
52 }
53 key_end = val.find("&", key_start);
54 if (key_end == std::string::npos) {
55 return;
56 }
57 result = val.substr(key_start + 4, key_end - key_start - 4);
58 gpulist->currGpu().make = std::move(toLower(result));
59
60 key_start = val.find("DEV_", key_start);
61 if (key_start == std::string::npos) {
62 return;
63 }
64 key_end = val.find("&", key_start);
65 if (key_end == std::string::npos) {
66 return;
67 }
68 result = val.substr(key_start + 4, key_end - key_start - 4);
69 gpulist->currGpu().device_id = std::move(toLower(result));
70 }
71
startsWith(const std::string & string,const std::string & prefix)72 static bool startsWith(const std::string& string, const std::string& prefix) {
73 return string.size() >= prefix.size() &&
74 memcmp(string.data(), prefix.data(), prefix.size()) == 0;
75 }
76
add_predefined_gpu_dlls(GpuInfo * gpu)77 static void add_predefined_gpu_dlls(GpuInfo* gpu) {
78 const std::string& currMake = gpu->make;
79 if (currMake == "NVIDIA" || startsWith(gpu->model, "NVIDIA")) {
80 gpu->addDll("nvoglv32.dll");
81 gpu->addDll("nvoglv64.dll");
82 } else if (currMake == "Advanced Micro Devices, Inc." ||
83 startsWith(gpu->model, "Advanced Micro Devices, Inc.")) {
84 gpu->addDll("atioglxx.dll");
85 gpu->addDll("atig6txx.dll");
86 }
87 }
88
parse_windows_gpu_dlls(int line_loc,int val_pos,const std::string & contents,GpuInfoList * gpulist)89 static void parse_windows_gpu_dlls(int line_loc,
90 int val_pos,
91 const std::string& contents,
92 GpuInfoList* gpulist) {
93 if (line_loc - val_pos != 0) {
94 const std::string& dll_str =
95 contents.substr(val_pos, line_loc - val_pos);
96
97 size_t vp = 0;
98 size_t dll_sep_loc = dll_str.find(",", vp);
99 size_t dll_end = (dll_sep_loc != std::string::npos)
100 ? dll_sep_loc
101 : dll_str.size() - vp;
102 gpulist->currGpu().addDll(dll_str.substr(vp, dll_end - vp));
103
104 while (dll_sep_loc != std::string::npos) {
105 vp = dll_sep_loc + 1;
106 dll_sep_loc = dll_str.find(",", vp);
107 dll_end = (dll_sep_loc != std::string::npos) ? dll_sep_loc
108 : dll_str.size() - vp;
109 gpulist->currGpu().addDll(dll_str.substr(vp, dll_end - vp));
110 }
111 }
112
113 add_predefined_gpu_dlls(&gpulist->currGpu());
114 }
115
load_gpu_registry_info(const wchar_t * keyName,GpuInfo * gpu)116 static void load_gpu_registry_info(const wchar_t* keyName, GpuInfo* gpu) {
117 HKEY hkey;
118 if (::RegOpenKeyW(HKEY_LOCAL_MACHINE, keyName, &hkey) != ERROR_SUCCESS) {
119 return;
120 }
121
122 SmallFixedVector<wchar_t, 256> name;
123 SmallFixedVector<BYTE, 1024> value;
124 for (int i = 0;; ++i) {
125 name.resize_noinit(name.capacity());
126 value.resize_noinit(value.capacity());
127 DWORD nameLen = name.size();
128 DWORD valueLen = value.size();
129 DWORD type;
130 auto res = RegEnumValueW(hkey, i, name.data(), &nameLen, nullptr, &type,
131 value.data(), &valueLen);
132 if (res == ERROR_NO_MORE_ITEMS) {
133 break;
134 } else if (res == ERROR_MORE_DATA) {
135 if (type != REG_SZ && type != REG_MULTI_SZ) {
136 // we don't care about other types for now, so let's not even
137 // try
138 continue;
139 }
140 name.resize_noinit(nameLen + 1);
141 value.resize_noinit(valueLen + 1);
142 nameLen = name.size();
143 valueLen = value.size();
144 res = ::RegEnumValueW(hkey, i, name.data(), &nameLen, nullptr,
145 &type, value.data(), &valueLen);
146 if (res != ERROR_SUCCESS) {
147 break;
148 }
149 }
150 if (res != ERROR_SUCCESS) {
151 break; // well, what can we do here?
152 }
153
154 name[nameLen] = L'\0';
155
156 if (type == REG_SZ && wcscmp(name.data(), L"DriverVersion") == 0) {
157 const auto strVal = (wchar_t*)value.data();
158 const auto strLen = valueLen / sizeof(wchar_t);
159 strVal[strLen] = L'\0';
160 gpu->version = Win32UnicodeString::convertToUtf8(strVal, strLen);
161 } else if (type == REG_MULTI_SZ &&
162 (wcscmp(name.data(), L"UserModeDriverName") == 0 ||
163 wcscmp(name.data(), L"UserModeDriverNameWoW") == 0)) {
164 const auto strVal = (wchar_t*)value.data();
165 const auto strLen = valueLen / sizeof(wchar_t);
166 strVal[strLen] = L'\0';
167 // Iterate over the '0'-delimited list of strings,
168 // stopping at double '0' (AKA empty string after the
169 // delimiter).
170 for (const wchar_t* ptr = strVal;;) {
171 auto len = wcslen(ptr);
172 if (!len) {
173 break;
174 }
175 gpu->dlls.emplace_back(
176 Win32UnicodeString::convertToUtf8(ptr, len));
177 ptr += len + 1;
178 }
179 }
180 }
181
182 ::RegCloseKey(hkey);
183 }
184
185 // static const int kGPUInfoQueryTimeoutMs = 5000;
186 // static std::string load_gpu_info_wmic() {
187 // auto guid = Uuid::generateFast().toString();
188 // // WMIC doesn't allow one to have any unquoted '-' characters in file name,
189 // // so let's get rid of them.
190 // guid.erase(std::remove(guid.begin(), guid.end(), '-'), guid.end());
191 // auto tempName = PathUtils::join(System::get()->getTempDir(),
192 // StringFormat("gpuinfo_%s.txt", guid));
193 //
194 // auto deleteTempFile = makeCustomScopedPtr(
195 // &tempName,
196 // [](const std::string* name) { path_delete_file(name->c_str()); });
197 // if (!System::get()->runCommand(
198 // {"wmic", StringFormat("/OUTPUT:%s", tempName), "path",
199 // "Win32_VideoController", "get", "/value"},
200 // RunOptions::WaitForCompletion | RunOptions::TerminateOnTimeout,
201 // kGPUInfoQueryTimeoutMs)) {
202 // return {};
203 // }
204 // auto res = android::readFileIntoString(tempName);
205 // return res ? Win32UnicodeString::convertToUtf8(
206 // (const wchar_t*)res->c_str(),
207 // res->size() / sizeof(wchar_t))
208 // : std::string{};
209 // }
210
parse_gpu_info_list_windows(const std::string & contents,GpuInfoList * gpulist)211 void parse_gpu_info_list_windows(const std::string& contents,
212 GpuInfoList* gpulist) {
213 size_t line_loc = contents.find("\r\n");
214 if (line_loc == std::string::npos) {
215 line_loc = contents.size();
216 }
217 size_t p = 0;
218 size_t equals_pos = 0;
219 size_t val_pos = 0;
220 std::string key;
221 std::string val;
222
223 // Windows: We use `wmic path Win32_VideoController get /value`
224 // to get a reasonably detailed list of '<key>=<val>'
225 // pairs. From these, we can get the make/model
226 // of the GPU, the driver version, and all DLLs involved.
227 while (line_loc != std::string::npos) {
228 equals_pos = contents.find("=", p);
229 if ((equals_pos != std::string::npos) && (equals_pos < line_loc)) {
230 key = contents.substr(p, equals_pos - p);
231 val_pos = equals_pos + 1;
232 val = contents.substr(val_pos, line_loc - val_pos);
233
234 if (key.find("AdapterCompatibility") != std::string::npos) {
235 gpulist->addGpu();
236 gpulist->currGpu().os = "W";
237 // 'make' will be overwritten in parsing 'PNPDeviceID'
238 // later. Set it here because we need it in paring
239 // 'InstalledDisplayDrivers' which comes before
240 // 'PNPDeviceID'.
241 gpulist->currGpu().make = val;
242 } else if (key.find("Caption") != std::string::npos) {
243 gpulist->currGpu().model = val;
244 } else if (key.find("PNPDeviceID") != std::string::npos) {
245 parse_windows_gpu_ids(val, gpulist);
246 } else if (key.find("DriverVersion") != std::string::npos) {
247 gpulist->currGpu().version = val;
248 } else if (key.find("InstalledDisplayDrivers") !=
249 std::string::npos) {
250 parse_windows_gpu_dlls(line_loc, val_pos, contents, gpulist);
251 }
252 }
253 if (line_loc == contents.size()) {
254 break;
255 }
256 p = line_loc + 2;
257 line_loc = contents.find("\r\n", p);
258 if (line_loc == std::string::npos) {
259 line_loc = contents.size();
260 }
261 }
262 }
263
queryGpuInfoD3D(GpuInfoList * gpus)264 static bool queryGpuInfoD3D(GpuInfoList* gpus) {
265 LPDIRECT3D9 pD3D = Direct3DCreate9(D3D_SDK_VERSION);
266 UINT numAdapters = pD3D->GetAdapterCount();
267
268 char vendoridBuf[16] = {};
269 char deviceidBuf[16] = {};
270
271 // MAX_DEVICE_IDENTIFIER_STRING can be pretty big,
272 // don't allocate on stack.
273 std::vector<char> descriptionBuf(MAX_DEVICE_IDENTIFIER_STRING + 1, '\0');
274
275 if (numAdapters == 0) return false;
276
277 // The adapter that is equal to D3DADAPTER_DEFAULT is the primary display adapter.
278 // D3DADAPTER_DEFAULT is currently defined to be 0, btw---but this is more future proof
279 for (UINT i = 0; i < numAdapters; i++) {
280 if (i == D3DADAPTER_DEFAULT) {
281 gpus->addGpu();
282 GpuInfo& gpu = gpus->currGpu();
283 gpu.os = "W";
284
285 D3DADAPTER_IDENTIFIER9 id;
286 pD3D->GetAdapterIdentifier(0, 0, &id);
287 snprintf(vendoridBuf, sizeof(vendoridBuf), "%04x", (unsigned int)id.VendorId);
288 snprintf(deviceidBuf, sizeof(deviceidBuf), "%04x", (unsigned int)id.DeviceId);
289 snprintf(&descriptionBuf[0], MAX_DEVICE_IDENTIFIER_STRING, "%s", id.Description);
290 gpu.make = vendoridBuf;
291 gpu.device_id = deviceidBuf;
292 gpu.model = &descriptionBuf[0];
293 // crashhandler_append_message_format(
294 // "gpu found. vendor id %04x device id 0x%04x\n",
295 // (unsigned int)(id.VendorId),
296 // (unsigned int)(id.DeviceId));
297 return true;
298 }
299 }
300
301 return false;
302 }
303
getGpuInfoListNative(GpuInfoList * gpus)304 void getGpuInfoListNative(GpuInfoList* gpus) {
305 if (queryGpuInfoD3D(gpus)) return;
306
307 // crashhandler_append_message_format("d3d gpu query failed.\n");
308
309 DISPLAY_DEVICEW device = { sizeof(device) };
310
311 for (int i = 0; EnumDisplayDevicesW(nullptr, i, &device, 0); ++i) {
312 gpus->addGpu();
313 GpuInfo& gpu = gpus->currGpu();
314 gpu.os = "W";
315 gpu.current_gpu =
316 (device.StateFlags & DISPLAY_DEVICE_PRIMARY_DEVICE) != 0;
317 gpu.model = Win32UnicodeString::convertToUtf8(device.DeviceString);
318 parse_windows_gpu_ids(
319 Win32UnicodeString::convertToUtf8(device.DeviceID), gpus);
320
321 // Now try inspecting the registry directly; |device|.DeviceKey can be a
322 // path to the GPU information key.
323 static const std::string prefix = "\\Registry\\Machine\\";
324 if (startsWith(Win32UnicodeString::convertToUtf8(device.DeviceKey),
325 prefix)) {
326 load_gpu_registry_info(device.DeviceKey + prefix.size(), &gpu);
327 }
328 add_predefined_gpu_dlls(&gpu);
329 }
330
331 if (gpus->infos.empty()) {
332 // Everything failed; bail.
333 // Everything failed - fall back to the good^Wbad old WMIC command.
334 // auto gpuInfoWmic = load_gpu_info_wmic();
335 // parse_gpu_info_list_windows(gpuInfoWmic, gpus);
336 }
337 }
338
339 // windows: blacklist depending on amdvlk and certain versions of vulkan-1.dll
340 // Based on chromium/src/gpu/config/gpu_info_collector_win.cc
badAmdVulkanDriverVersion()341 bool badAmdVulkanDriverVersion() {
342 int major, minor, build_1, build_2;
343
344 // crashhandler_append_message_format(
345 // "checking for bad AMD Vulkan driver version...\n");
346
347 if (!android::base::queryFileVersionInfo("amdvlk64.dll", &major, &minor, &build_1, &build_2)) {
348 // crashhandler_append_message_format(
349 // "amdvlk64.dll not found. Checking for amdvlk32...\n");
350 if (!android::base::queryFileVersionInfo("amdvlk32.dll", &major, &minor, &build_1, &build_2)) {
351 // crashhandler_append_message_format(
352 // "amdvlk32.dll not found. No bad AMD Vulkan driver versions found.\n");
353 // Information about amdvlk64 not availble; not blacklisted
354 return false;
355 }
356 }
357
358 // crashhandler_append_message_format(
359 // "AMD driver info found. Version: %d.%d.%d.%d\n",
360 // major, minor, build_1, build_2);
361
362 bool isBad = (major == 1 && minor == 0 && build_1 <= 54);
363
364 if (isBad) {
365 // crashhandler_append_message_format(
366 // "Is bad AMD driver version; blacklisting.\n");
367 } else {
368 // crashhandler_append_message_format(
369 // "Not known bad AMD driver version; passing.\n");
370 }
371
372 return isBad;
373 }
374
375 using WindowsDllVersion = std::tuple<int, int, int, int>;
376
badVulkanDllVersion()377 bool badVulkanDllVersion() {
378 int major, minor, build_1, build_2;
379
380 // crashhandler_append_message_format(
381 // "checking for bad vulkan-1.dll version...\n");
382
383 if (!android::base::queryFileVersionInfo("vulkan-1.dll", &major, &minor, &build_1, &build_2)) {
384 // crashhandler_append_message_format(
385 // "info on vulkan-1.dll cannot be found, continue.\n");
386 // Information about vulkan-1.dll not available; not blacklisted
387 return false;
388 }
389
390 // crashhandler_append_message_format(
391 // "vulkan-1.dll version: %d.%d.%d.%d\n",
392 // major, minor, build_1, build_2);
393
394 // Ban all Windows Vulkan drivers < 1.1;
395 // they sometimes advertise vkEnumerateInstanceVersion
396 // and then running that function pointer causes a segfault.
397 // In any case, properly updated GPU drivers for Windows
398 // should all be 1.1 now for the major manufacturers
399 // (even Intel; see https://www.intel.com/content/www/us/en/support/articles/000005524/graphics-drivers.html)
400 bool isBad =
401 major == 1 && minor == 0;
402
403 if (isBad) {
404 // crashhandler_append_message_format(
405 // "Is bad vulkan-1.dll version; blacklisting.\n");
406 } else {
407 // crashhandler_append_message_format(
408 // "Not known bad vulkan-1.dll version; continue.\n");
409 }
410
411 return isBad;
412 }
413
isVulkanSafeToUseNative()414 bool isVulkanSafeToUseNative() {
415 return !badAmdVulkanDriverVersion() && !badVulkanDllVersion();
416 }
417