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 #include "common/vulkan/vk_google_filtering_precision.h"
20
21 namespace
22 {
ResetEnvironmentVar(const char * variableName,const Optional<std::string> & value)23 void ResetEnvironmentVar(const char *variableName, const Optional<std::string> &value)
24 {
25 if (!value.valid())
26 {
27 return;
28 }
29
30 if (value.value().empty())
31 {
32 angle::UnsetEnvironmentVar(variableName);
33 }
34 else
35 {
36 angle::SetEnvironmentVar(variableName, value.value().c_str());
37 }
38 }
39 } // namespace
40
41 namespace angle
42 {
43
44 namespace vk
45 {
46
47 namespace
48 {
49
WrapICDEnvironment(const char * icdEnvironment)50 [[maybe_unused]] const std::string WrapICDEnvironment(const char *icdEnvironment)
51 {
52 // The libraries are bundled into the module directory
53 std::string moduleDir = angle::GetModuleDirectory();
54 std::string ret = ConcatenatePath(moduleDir, icdEnvironment);
55 #if defined(ANGLE_PLATFORM_MACOS)
56 std::string moduleDirWithLibraries = ConcatenatePath(moduleDir, "Libraries");
57 ret += ":" + ConcatenatePath(moduleDirWithLibraries, icdEnvironment);
58 #endif
59 return ret;
60 }
61
62 [[maybe_unused]] constexpr char kLoaderLayersPathEnv[] = "VK_LAYER_PATH";
63 [[maybe_unused]] constexpr char kLayerEnablesEnv[] = "VK_LAYER_ENABLES";
64
65 constexpr char kLoaderICDFilenamesEnv[] = "VK_ICD_FILENAMES";
66 constexpr char kANGLEPreferredDeviceEnv[] = "ANGLE_PREFERRED_DEVICE";
67 constexpr char kValidationLayersCustomSTypeListEnv[] = "VK_LAYER_CUSTOM_STYPE_LIST";
68 constexpr char kNoDeviceSelect[] = "NODEVICE_SELECT";
69
70 constexpr uint32_t kMockVendorID = 0xba5eba11;
71 constexpr uint32_t kMockDeviceID = 0xf005ba11;
72 constexpr char kMockDeviceName[] = "Vulkan Mock Device";
73
74 constexpr uint32_t kGoogleVendorID = 0x1AE0;
75 constexpr uint32_t kSwiftShaderDeviceID = 0xC0DE;
76 constexpr char kSwiftShaderDeviceName[] = "SwiftShader Device";
77
78 using ICDFilterFunc = std::function<bool(const VkPhysicalDeviceProperties &)>;
79
GetFilterForICD(vk::ICD preferredICD)80 ICDFilterFunc GetFilterForICD(vk::ICD preferredICD)
81 {
82 switch (preferredICD)
83 {
84 case vk::ICD::Mock:
85 return [](const VkPhysicalDeviceProperties &deviceProperties) {
86 return ((deviceProperties.vendorID == kMockVendorID) &&
87 (deviceProperties.deviceID == kMockDeviceID) &&
88 (strcmp(deviceProperties.deviceName, kMockDeviceName) == 0));
89 };
90 case vk::ICD::SwiftShader:
91 return [](const VkPhysicalDeviceProperties &deviceProperties) {
92 return ((deviceProperties.vendorID == kGoogleVendorID) &&
93 (deviceProperties.deviceID == kSwiftShaderDeviceID) &&
94 (strncmp(deviceProperties.deviceName, kSwiftShaderDeviceName,
95 strlen(kSwiftShaderDeviceName)) == 0));
96 };
97 default:
98 const std::string anglePreferredDevice =
99 angle::GetEnvironmentVar(kANGLEPreferredDeviceEnv);
100 return [anglePreferredDevice](const VkPhysicalDeviceProperties &deviceProperties) {
101 return (anglePreferredDevice == deviceProperties.deviceName);
102 };
103 }
104 }
105
106 } // namespace
107
108 // If we're loading the validation layers, we could be running from any random directory.
109 // Change to the executable directory so we can find the layers, then change back to the
110 // previous directory to be safe we don't disrupt the application.
ScopedVkLoaderEnvironment(bool enableValidationLayers,vk::ICD icd)111 ScopedVkLoaderEnvironment::ScopedVkLoaderEnvironment(bool enableValidationLayers, vk::ICD icd)
112 : mEnableValidationLayers(enableValidationLayers),
113 mICD(icd),
114 mChangedCWD(false),
115 mChangedICDEnv(false),
116 mChangedNoDeviceSelect(false)
117 {
118 // Changing CWD and setting environment variables makes no sense on Android,
119 // since this code is a part of Java application there.
120 // Android Vulkan loader doesn't need this either.
121 #if !defined(ANGLE_PLATFORM_ANDROID) && !defined(ANGLE_PLATFORM_GGP)
122 if (icd == vk::ICD::Mock)
123 {
124 if (!setICDEnvironment(WrapICDEnvironment(ANGLE_VK_MOCK_ICD_JSON).c_str()))
125 {
126 ERR() << "Error setting environment for Mock/Null Driver.";
127 }
128 }
129 # if defined(ANGLE_VK_SWIFTSHADER_ICD_JSON)
130 else if (icd == vk::ICD::SwiftShader)
131 {
132 if (!setICDEnvironment(WrapICDEnvironment(ANGLE_VK_SWIFTSHADER_ICD_JSON).c_str()))
133 {
134 ERR() << "Error setting environment for SwiftShader.";
135 }
136 }
137 # endif // defined(ANGLE_VK_SWIFTSHADER_ICD_JSON)
138
139 # if !defined(ANGLE_PLATFORM_MACOS)
140 if (mEnableValidationLayers || icd != vk::ICD::Default)
141 {
142 const auto &cwd = angle::GetCWD();
143 if (!cwd.valid())
144 {
145 ERR() << "Error getting CWD for Vulkan layers init.";
146 mEnableValidationLayers = false;
147 mICD = vk::ICD::Default;
148 }
149 else
150 {
151 mPreviousCWD = cwd.value();
152 std::string moduleDir = angle::GetModuleDirectory();
153 mChangedCWD = angle::SetCWD(moduleDir.c_str());
154 if (!mChangedCWD)
155 {
156 ERR() << "Error setting CWD for Vulkan layers init.";
157 mEnableValidationLayers = false;
158 mICD = vk::ICD::Default;
159 }
160 }
161 }
162 # endif // defined(ANGLE_PLATFORM_MACOS)
163
164 // Override environment variable to use the ANGLE layers.
165 if (mEnableValidationLayers)
166 {
167 # if defined(ANGLE_VK_LAYERS_DIR)
168 if (!angle::PrependPathToEnvironmentVar(kLoaderLayersPathEnv, ANGLE_VK_LAYERS_DIR))
169 {
170 ERR() << "Error setting environment for Vulkan layers init.";
171 mEnableValidationLayers = false;
172 }
173 # endif // defined(ANGLE_VK_LAYERS_DIR)
174
175 if (!angle::PrependPathToEnvironmentVar(
176 kLayerEnablesEnv, "VK_VALIDATION_FEATURE_ENABLE_SYNCHRONIZATION_VALIDATION"))
177 {
178 ERR() << "Error setting synchronization validation environment for Vulkan validation "
179 "layers init.";
180 }
181
182 if (!setCustomExtensionsEnvironment())
183 {
184 ERR() << "Error setting custom list for custom extensions for Vulkan layers init.";
185 mEnableValidationLayers = false;
186 }
187 }
188 #endif // !defined(ANGLE_PLATFORM_ANDROID)
189
190 if (IsMSan() || IsASan())
191 {
192 // device select layer cause memory sanitizer false positive, so disable
193 // it for msan build.
194 mPreviousNoDeviceSelectEnv = angle::GetEnvironmentVar(kNoDeviceSelect);
195 angle::SetEnvironmentVar(kNoDeviceSelect, "1");
196 mChangedNoDeviceSelect = true;
197 }
198 }
199
~ScopedVkLoaderEnvironment()200 ScopedVkLoaderEnvironment::~ScopedVkLoaderEnvironment()
201 {
202 if (mChangedCWD)
203 {
204 #if !defined(ANGLE_PLATFORM_ANDROID)
205 ASSERT(mPreviousCWD.valid());
206 angle::SetCWD(mPreviousCWD.value().c_str());
207 #endif // !defined(ANGLE_PLATFORM_ANDROID)
208 }
209 if (mChangedICDEnv)
210 {
211 ResetEnvironmentVar(kLoaderICDFilenamesEnv, mPreviousICDEnv);
212 }
213
214 ResetEnvironmentVar(kValidationLayersCustomSTypeListEnv, mPreviousCustomExtensionsEnv);
215
216 if (mChangedNoDeviceSelect)
217 {
218 ResetEnvironmentVar(kNoDeviceSelect, mPreviousNoDeviceSelectEnv);
219 }
220 }
221
setICDEnvironment(const char * icd)222 bool ScopedVkLoaderEnvironment::setICDEnvironment(const char *icd)
223 {
224 // Override environment variable to use built Mock ICD
225 // ANGLE_VK_ICD_JSON gets set to the built mock ICD in BUILD.gn
226 mPreviousICDEnv = angle::GetEnvironmentVar(kLoaderICDFilenamesEnv);
227 mChangedICDEnv = angle::SetEnvironmentVar(kLoaderICDFilenamesEnv, icd);
228
229 if (!mChangedICDEnv)
230 {
231 mICD = vk::ICD::Default;
232 }
233 return mChangedICDEnv;
234 }
235
setCustomExtensionsEnvironment()236 bool ScopedVkLoaderEnvironment::setCustomExtensionsEnvironment()
237 {
238 struct CustomExtension
239 {
240 VkStructureType type;
241 size_t size;
242 };
243
244 CustomExtension customExtensions[] = {
245
246 {VK_STRUCTURE_TYPE_SAMPLER_FILTERING_PRECISION_GOOGLE,
247 sizeof(VkSamplerFilteringPrecisionGOOGLE)},
248
249 };
250
251 mPreviousCustomExtensionsEnv = angle::GetEnvironmentVar(kValidationLayersCustomSTypeListEnv);
252
253 std::stringstream strstr;
254 for (CustomExtension &extension : customExtensions)
255 {
256 if (strstr.tellp() != std::streampos(0))
257 {
258 strstr << angle::GetPathSeparatorForEnvironmentVar();
259 }
260
261 strstr << extension.type << angle::GetPathSeparatorForEnvironmentVar() << extension.size;
262 }
263
264 return angle::PrependPathToEnvironmentVar(kValidationLayersCustomSTypeListEnv,
265 strstr.str().c_str());
266 }
267
ChoosePhysicalDevice(PFN_vkGetPhysicalDeviceProperties pGetPhysicalDeviceProperties,const std::vector<VkPhysicalDevice> & physicalDevices,vk::ICD preferredICD,uint32_t preferredVendorID,uint32_t preferredDeviceID,VkPhysicalDevice * physicalDeviceOut,VkPhysicalDeviceProperties * physicalDevicePropertiesOut)268 void ChoosePhysicalDevice(PFN_vkGetPhysicalDeviceProperties pGetPhysicalDeviceProperties,
269 const std::vector<VkPhysicalDevice> &physicalDevices,
270 vk::ICD preferredICD,
271 uint32_t preferredVendorID,
272 uint32_t preferredDeviceID,
273 VkPhysicalDevice *physicalDeviceOut,
274 VkPhysicalDeviceProperties *physicalDevicePropertiesOut)
275 {
276 ASSERT(!physicalDevices.empty());
277
278 ICDFilterFunc filter = GetFilterForICD(preferredICD);
279
280 const bool shouldChooseByID = (preferredVendorID != 0 || preferredDeviceID != 0);
281
282 for (const VkPhysicalDevice &physicalDevice : physicalDevices)
283 {
284 pGetPhysicalDeviceProperties(physicalDevice, physicalDevicePropertiesOut);
285 if (filter(*physicalDevicePropertiesOut))
286 {
287 *physicalDeviceOut = physicalDevice;
288 return;
289 }
290
291 if (shouldChooseByID)
292 {
293 // NOTE: If the system has multiple GPUs with the same vendor and
294 // device IDs, this will arbitrarily select one of them.
295 bool matchVendorID = true;
296 bool matchDeviceID = true;
297
298 if (preferredVendorID != 0 &&
299 preferredVendorID != physicalDevicePropertiesOut->vendorID)
300 {
301 matchVendorID = false;
302 }
303
304 if (preferredDeviceID != 0 &&
305 preferredDeviceID != physicalDevicePropertiesOut->deviceID)
306 {
307 matchDeviceID = false;
308 }
309
310 if (matchVendorID && matchDeviceID)
311 {
312 *physicalDeviceOut = physicalDevice;
313 return;
314 }
315 }
316 }
317
318 Optional<VkPhysicalDevice> integratedDevice;
319 VkPhysicalDeviceProperties integratedDeviceProperties;
320 for (const VkPhysicalDevice &physicalDevice : physicalDevices)
321 {
322 pGetPhysicalDeviceProperties(physicalDevice, physicalDevicePropertiesOut);
323 // If discrete GPU exists, uses it by default.
324 if (physicalDevicePropertiesOut->deviceType == VK_PHYSICAL_DEVICE_TYPE_DISCRETE_GPU)
325 {
326 *physicalDeviceOut = physicalDevice;
327 return;
328 }
329 if (physicalDevicePropertiesOut->deviceType == VK_PHYSICAL_DEVICE_TYPE_INTEGRATED_GPU &&
330 !integratedDevice.valid())
331 {
332 integratedDevice = physicalDevice;
333 integratedDeviceProperties = *physicalDevicePropertiesOut;
334 continue;
335 }
336 }
337
338 // If only integrated GPU exists, use it by default.
339 if (integratedDevice.valid())
340 {
341 *physicalDeviceOut = integratedDevice.value();
342 *physicalDevicePropertiesOut = integratedDeviceProperties;
343 return;
344 }
345
346 WARN() << "Preferred device ICD not found. Using default physicalDevice instead.";
347 // Fallback to the first device.
348 *physicalDeviceOut = physicalDevices[0];
349 pGetPhysicalDeviceProperties(*physicalDeviceOut, physicalDevicePropertiesOut);
350 }
351
352 } // namespace vk
353
354 } // namespace angle
355