1 //
2 // Copyright 2020 The ANGLE Project Authors. All rights reserved.
3 // Use of this source code is governed by a BSD-style license that can be
4 // found in the LICENSE file.
5 //
6
7 // vulkan_icd.cpp : Helper for creating vulkan instances & selecting physical device.
8
9 #include "common/vulkan/vulkan_icd.h"
10
11 #include <functional>
12 #include <vector>
13
14 #include "common/Optional.h"
15 #include "common/bitset_utils.h"
16 #include "common/debug.h"
17 #include "common/system_utils.h"
18
19 namespace
20 {
ResetEnvironmentVar(const char * variableName,const Optional<std::string> & value)21 void ResetEnvironmentVar(const char *variableName, const Optional<std::string> &value)
22 {
23 if (!value.valid())
24 {
25 return;
26 }
27
28 if (value.value().empty())
29 {
30 angle::UnsetEnvironmentVar(variableName);
31 }
32 else
33 {
34 angle::SetEnvironmentVar(variableName, value.value().c_str());
35 }
36 }
37 } // namespace
38
39 namespace angle
40 {
41
42 namespace vk
43 {
44
45 namespace
46 {
47
WrapICDEnvironment(const char * icdEnvironment)48 [[maybe_unused]] const std::string WrapICDEnvironment(const char *icdEnvironment)
49 {
50 // The libraries are bundled into the module directory
51 std::string moduleDir = angle::GetModuleDirectory();
52 std::string ret = ConcatenatePath(moduleDir, icdEnvironment);
53 #if defined(ANGLE_PLATFORM_MACOS)
54 std::string moduleDirWithLibraries = ConcatenatePath(moduleDir, "Libraries");
55 ret += ":" + ConcatenatePath(moduleDirWithLibraries, icdEnvironment);
56 #endif
57 return ret;
58 }
59
60 [[maybe_unused]] constexpr char kLoaderLayersPathEnv[] = "VK_LAYER_PATH";
61 [[maybe_unused]] constexpr char kLayerEnablesEnv[] = "VK_LAYER_ENABLES";
62
63 constexpr char kLoaderICDFilenamesEnv[] = "VK_ICD_FILENAMES";
64 constexpr char kANGLEPreferredDeviceEnv[] = "ANGLE_PREFERRED_DEVICE";
65 constexpr char kValidationLayersCustomSTypeListEnv[] = "VK_LAYER_CUSTOM_STYPE_LIST";
66 constexpr char kNoDeviceSelect[] = "NODEVICE_SELECT";
67
68 constexpr uint32_t kMockVendorID = 0xba5eba11;
69 constexpr uint32_t kMockDeviceID = 0xf005ba11;
70 constexpr char kMockDeviceName[] = "Vulkan Mock Device";
71
72 constexpr uint32_t kGoogleVendorID = 0x1AE0;
73 constexpr uint32_t kSwiftShaderDeviceID = 0xC0DE;
74 constexpr char kSwiftShaderDeviceName[] = "SwiftShader Device";
75
76 using ICDFilterFunc = std::function<bool(const VkPhysicalDeviceProperties &)>;
77
GetFilterForICD(vk::ICD preferredICD)78 ICDFilterFunc GetFilterForICD(vk::ICD preferredICD)
79 {
80 switch (preferredICD)
81 {
82 case vk::ICD::Mock:
83 return [](const VkPhysicalDeviceProperties &deviceProperties) {
84 return ((deviceProperties.vendorID == kMockVendorID) &&
85 (deviceProperties.deviceID == kMockDeviceID) &&
86 (strcmp(deviceProperties.deviceName, kMockDeviceName) == 0));
87 };
88 case vk::ICD::SwiftShader:
89 return [](const VkPhysicalDeviceProperties &deviceProperties) {
90 return ((deviceProperties.vendorID == kGoogleVendorID) &&
91 (deviceProperties.deviceID == kSwiftShaderDeviceID) &&
92 (strncmp(deviceProperties.deviceName, kSwiftShaderDeviceName,
93 strlen(kSwiftShaderDeviceName)) == 0));
94 };
95 default:
96 const std::string anglePreferredDevice =
97 angle::GetEnvironmentVar(kANGLEPreferredDeviceEnv);
98 return [anglePreferredDevice](const VkPhysicalDeviceProperties &deviceProperties) {
99 return (anglePreferredDevice == deviceProperties.deviceName);
100 };
101 }
102 }
103
104 } // namespace
105
106 // If we're loading the vulkan layers, we could be running from any random directory.
107 // Change to the executable directory so we can find the layers, then change back to the
108 // previous directory to be safe we don't disrupt the application.
ScopedVkLoaderEnvironment(bool enableDebugLayers,vk::ICD icd)109 ScopedVkLoaderEnvironment::ScopedVkLoaderEnvironment(bool enableDebugLayers, vk::ICD icd)
110 : mEnableDebugLayers(enableDebugLayers),
111 mICD(icd),
112 mChangedCWD(false),
113 mChangedICDEnv(false),
114 mChangedNoDeviceSelect(false)
115 {
116 // Changing CWD and setting environment variables makes no sense on Android,
117 // since this code is a part of Java application there.
118 // Android Vulkan loader doesn't need this either.
119 #if !defined(ANGLE_PLATFORM_ANDROID)
120 if (icd == vk::ICD::Mock)
121 {
122 if (!setICDEnvironment(WrapICDEnvironment(ANGLE_VK_MOCK_ICD_JSON).c_str()))
123 {
124 ERR() << "Error setting environment for Mock/Null Driver.";
125 }
126 }
127 # if defined(ANGLE_VK_SWIFTSHADER_ICD_JSON)
128 else if (icd == vk::ICD::SwiftShader)
129 {
130 if (!setICDEnvironment(WrapICDEnvironment(ANGLE_VK_SWIFTSHADER_ICD_JSON).c_str()))
131 {
132 ERR() << "Error setting environment for SwiftShader.";
133 }
134 }
135 # endif // defined(ANGLE_VK_SWIFTSHADER_ICD_JSON)
136
137 # if !defined(ANGLE_PLATFORM_MACOS)
138 if (mEnableDebugLayers || icd != vk::ICD::Default)
139 {
140 const auto &cwd = angle::GetCWD();
141 if (!cwd.valid())
142 {
143 ERR() << "Error getting CWD for Vulkan layers init.";
144 mEnableDebugLayers = false;
145 mICD = vk::ICD::Default;
146 }
147 else
148 {
149 mPreviousCWD = cwd.value();
150 std::string moduleDir = angle::GetModuleDirectory();
151 mChangedCWD = angle::SetCWD(moduleDir.c_str());
152 if (!mChangedCWD)
153 {
154 ERR() << "Error setting CWD for Vulkan layers init.";
155 mEnableDebugLayers = false;
156 mICD = vk::ICD::Default;
157 }
158 }
159 }
160 # endif // defined(ANGLE_PLATFORM_MACOS)
161
162 // Override environment variable to use the ANGLE layers.
163 if (mEnableDebugLayers)
164 {
165 # if defined(ANGLE_VK_LAYERS_DIR)
166 if (!angle::PrependPathToEnvironmentVar(kLoaderLayersPathEnv, ANGLE_VK_LAYERS_DIR))
167 {
168 ERR() << "Error setting environment for Vulkan layers init.";
169 mEnableDebugLayers = false;
170 }
171 # endif // defined(ANGLE_VK_LAYERS_DIR)
172 }
173 #endif // !defined(ANGLE_PLATFORM_ANDROID)
174
175 if (IsMSan() || IsASan())
176 {
177 // device select layer cause memory sanitizer false positive, so disable
178 // it for msan build.
179 mPreviousNoDeviceSelectEnv = angle::GetEnvironmentVar(kNoDeviceSelect);
180 angle::SetEnvironmentVar(kNoDeviceSelect, "1");
181 mChangedNoDeviceSelect = true;
182 }
183 }
184
~ScopedVkLoaderEnvironment()185 ScopedVkLoaderEnvironment::~ScopedVkLoaderEnvironment()
186 {
187 if (mChangedCWD)
188 {
189 #if !defined(ANGLE_PLATFORM_ANDROID)
190 ASSERT(mPreviousCWD.valid());
191 angle::SetCWD(mPreviousCWD.value().c_str());
192 #endif // !defined(ANGLE_PLATFORM_ANDROID)
193 }
194 if (mChangedICDEnv)
195 {
196 ResetEnvironmentVar(kLoaderICDFilenamesEnv, mPreviousICDEnv);
197 }
198
199 ResetEnvironmentVar(kValidationLayersCustomSTypeListEnv, mPreviousCustomExtensionsEnv);
200
201 if (mChangedNoDeviceSelect)
202 {
203 ResetEnvironmentVar(kNoDeviceSelect, mPreviousNoDeviceSelectEnv);
204 }
205 }
206
setICDEnvironment(const char * icd)207 bool ScopedVkLoaderEnvironment::setICDEnvironment(const char *icd)
208 {
209 // Override environment variable to use built Mock ICD
210 // ANGLE_VK_ICD_JSON gets set to the built mock ICD in BUILD.gn
211 mPreviousICDEnv = angle::GetEnvironmentVar(kLoaderICDFilenamesEnv);
212 mChangedICDEnv = angle::SetEnvironmentVar(kLoaderICDFilenamesEnv, icd);
213
214 if (!mChangedICDEnv)
215 {
216 mICD = vk::ICD::Default;
217 }
218 return mChangedICDEnv;
219 }
220
ChoosePhysicalDevice(PFN_vkGetPhysicalDeviceProperties2 pGetPhysicalDeviceProperties2,const std::vector<VkPhysicalDevice> & physicalDevices,vk::ICD preferredICD,uint32_t preferredVendorID,uint32_t preferredDeviceID,const uint8_t * preferredDeviceUUID,const uint8_t * preferredDriverUUID,VkDriverId preferredDriverID,VkPhysicalDevice * physicalDeviceOut,VkPhysicalDeviceProperties2 * physicalDeviceProperties2Out,VkPhysicalDeviceIDProperties * physicalDeviceIDPropertiesOut,VkPhysicalDeviceDriverProperties * physicalDeviceDriverPropertiesOut)221 void ChoosePhysicalDevice(PFN_vkGetPhysicalDeviceProperties2 pGetPhysicalDeviceProperties2,
222 const std::vector<VkPhysicalDevice> &physicalDevices,
223 vk::ICD preferredICD,
224 uint32_t preferredVendorID,
225 uint32_t preferredDeviceID,
226 const uint8_t *preferredDeviceUUID,
227 const uint8_t *preferredDriverUUID,
228 VkDriverId preferredDriverID,
229 VkPhysicalDevice *physicalDeviceOut,
230 VkPhysicalDeviceProperties2 *physicalDeviceProperties2Out,
231 VkPhysicalDeviceIDProperties *physicalDeviceIDPropertiesOut,
232 VkPhysicalDeviceDriverProperties *physicalDeviceDriverPropertiesOut)
233 {
234 ASSERT(!physicalDevices.empty());
235
236 VkPhysicalDeviceProperties const *deviceProps = &physicalDeviceProperties2Out->properties;
237
238 ICDFilterFunc filter = GetFilterForICD(preferredICD);
239
240 const bool shouldChooseByPciId = (preferredVendorID != 0 || preferredDeviceID != 0);
241 const bool shouldChooseByUUIDs = (preferredDeviceUUID != nullptr ||
242 preferredDriverUUID != nullptr || preferredDriverID != 0);
243
244 for (const VkPhysicalDevice &physicalDevice : physicalDevices)
245 {
246 *physicalDeviceProperties2Out = {};
247 physicalDeviceProperties2Out->sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PROPERTIES_2;
248 physicalDeviceProperties2Out->pNext = physicalDeviceIDPropertiesOut;
249
250 *physicalDeviceIDPropertiesOut = {};
251 physicalDeviceIDPropertiesOut->sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_ID_PROPERTIES;
252 physicalDeviceIDPropertiesOut->pNext = physicalDeviceDriverPropertiesOut;
253
254 *physicalDeviceDriverPropertiesOut = {};
255 physicalDeviceDriverPropertiesOut->sType =
256 VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_DRIVER_PROPERTIES;
257
258 pGetPhysicalDeviceProperties2(physicalDevice, physicalDeviceProperties2Out);
259
260 if (deviceProps->apiVersion < kMinimumVulkanAPIVersion)
261 {
262 // Skip any devices that don't support our minimum API version. This
263 // takes precedence over all other considerations.
264 continue;
265 }
266
267 if (filter(*deviceProps))
268 {
269 *physicalDeviceOut = physicalDevice;
270 return;
271 }
272
273 if (shouldChooseByUUIDs)
274 {
275 bool matched = true;
276
277 if (preferredDriverID != 0 &&
278 preferredDriverID != physicalDeviceDriverPropertiesOut->driverID)
279 {
280 matched = false;
281 }
282 else if (preferredDeviceUUID != nullptr &&
283 memcmp(preferredDeviceUUID, physicalDeviceIDPropertiesOut->deviceUUID,
284 VK_UUID_SIZE) != 0)
285 {
286 matched = false;
287 }
288 else if (preferredDriverUUID != nullptr &&
289 memcmp(preferredDriverUUID, physicalDeviceIDPropertiesOut->driverUUID,
290 VK_UUID_SIZE) != 0)
291 {
292 matched = false;
293 }
294
295 if (matched)
296 {
297 *physicalDeviceOut = physicalDevice;
298 return;
299 }
300 }
301
302 if (shouldChooseByPciId)
303 {
304 // NOTE: If the system has multiple GPUs with the same vendor and
305 // device IDs, this will arbitrarily select one of them.
306 bool matchVendorID = true;
307 bool matchDeviceID = true;
308
309 if (preferredVendorID != 0 && preferredVendorID != deviceProps->vendorID)
310 {
311 matchVendorID = false;
312 }
313
314 if (preferredDeviceID != 0 && preferredDeviceID != deviceProps->deviceID)
315 {
316 matchDeviceID = false;
317 }
318
319 if (matchVendorID && matchDeviceID)
320 {
321 *physicalDeviceOut = physicalDevice;
322 return;
323 }
324 }
325 }
326
327 Optional<VkPhysicalDevice> integratedDevice;
328 VkPhysicalDeviceProperties2 integratedDeviceProperties2;
329 VkPhysicalDeviceIDProperties integratedDeviceIDProperties;
330 VkPhysicalDeviceDriverProperties integratedDeviceDriverProperties;
331
332 for (const VkPhysicalDevice &physicalDevice : physicalDevices)
333 {
334 *physicalDeviceProperties2Out = {};
335 physicalDeviceProperties2Out->sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PROPERTIES_2;
336 physicalDeviceProperties2Out->pNext = physicalDeviceIDPropertiesOut;
337
338 *physicalDeviceIDPropertiesOut = {};
339 physicalDeviceIDPropertiesOut->sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_ID_PROPERTIES;
340 physicalDeviceIDPropertiesOut->pNext = physicalDeviceDriverPropertiesOut;
341
342 *physicalDeviceDriverPropertiesOut = {};
343 physicalDeviceDriverPropertiesOut->sType =
344 VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_DRIVER_PROPERTIES;
345
346 pGetPhysicalDeviceProperties2(physicalDevice, physicalDeviceProperties2Out);
347
348 if (deviceProps->apiVersion < kMinimumVulkanAPIVersion)
349 {
350 // Skip any devices that don't support our minimum API version. This
351 // takes precedence over all other considerations.
352 continue;
353 }
354
355 // If discrete GPU exists, uses it by default.
356 if (deviceProps->deviceType == VK_PHYSICAL_DEVICE_TYPE_DISCRETE_GPU)
357 {
358 *physicalDeviceOut = physicalDevice;
359 return;
360 }
361 if (deviceProps->deviceType == VK_PHYSICAL_DEVICE_TYPE_INTEGRATED_GPU &&
362 !integratedDevice.valid())
363 {
364 integratedDevice = physicalDevice;
365 integratedDeviceProperties2 = *physicalDeviceProperties2Out;
366 integratedDeviceIDProperties = *physicalDeviceIDPropertiesOut;
367 integratedDeviceDriverProperties = *physicalDeviceDriverPropertiesOut;
368 integratedDeviceProperties2.pNext = nullptr;
369 integratedDeviceIDProperties.pNext = nullptr;
370 integratedDeviceDriverProperties.pNext = nullptr;
371 continue;
372 }
373 }
374
375 // If only integrated GPU exists, use it by default.
376 if (integratedDevice.valid())
377 {
378 *physicalDeviceOut = integratedDevice.value();
379 *physicalDeviceProperties2Out = integratedDeviceProperties2;
380 *physicalDeviceIDPropertiesOut = integratedDeviceIDProperties;
381 return;
382 }
383
384 WARN() << "Preferred device ICD not found. Using default physicalDevice instead.";
385 // Fallback to the first device.
386 *physicalDeviceOut = physicalDevices[0];
387 pGetPhysicalDeviceProperties2(*physicalDeviceOut, physicalDeviceProperties2Out);
388 }
389
390 } // namespace vk
391
392 } // namespace angle
393