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) && !defined(ANGLE_PLATFORM_GGP)
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_vkGetPhysicalDeviceProperties pGetPhysicalDeviceProperties,const std::vector<VkPhysicalDevice> & physicalDevices,vk::ICD preferredICD,uint32_t preferredVendorID,uint32_t preferredDeviceID,VkPhysicalDevice * physicalDeviceOut,VkPhysicalDeviceProperties * physicalDevicePropertiesOut)221 void ChoosePhysicalDevice(PFN_vkGetPhysicalDeviceProperties pGetPhysicalDeviceProperties,
222 const std::vector<VkPhysicalDevice> &physicalDevices,
223 vk::ICD preferredICD,
224 uint32_t preferredVendorID,
225 uint32_t preferredDeviceID,
226 VkPhysicalDevice *physicalDeviceOut,
227 VkPhysicalDeviceProperties *physicalDevicePropertiesOut)
228 {
229 ASSERT(!physicalDevices.empty());
230
231 ICDFilterFunc filter = GetFilterForICD(preferredICD);
232
233 const bool shouldChooseByID = (preferredVendorID != 0 || preferredDeviceID != 0);
234
235 for (const VkPhysicalDevice &physicalDevice : physicalDevices)
236 {
237 pGetPhysicalDeviceProperties(physicalDevice, physicalDevicePropertiesOut);
238
239 if (physicalDevicePropertiesOut->apiVersion < kMinimumVulkanAPIVersion)
240 {
241 // Skip any devices that don't support our minimum API version. This
242 // takes precedence over all other considerations.
243 continue;
244 }
245
246 if (filter(*physicalDevicePropertiesOut))
247 {
248 *physicalDeviceOut = physicalDevice;
249 return;
250 }
251
252 if (shouldChooseByID)
253 {
254 // NOTE: If the system has multiple GPUs with the same vendor and
255 // device IDs, this will arbitrarily select one of them.
256 bool matchVendorID = true;
257 bool matchDeviceID = true;
258
259 if (preferredVendorID != 0 &&
260 preferredVendorID != physicalDevicePropertiesOut->vendorID)
261 {
262 matchVendorID = false;
263 }
264
265 if (preferredDeviceID != 0 &&
266 preferredDeviceID != physicalDevicePropertiesOut->deviceID)
267 {
268 matchDeviceID = false;
269 }
270
271 if (matchVendorID && matchDeviceID)
272 {
273 *physicalDeviceOut = physicalDevice;
274 return;
275 }
276 }
277 }
278
279 Optional<VkPhysicalDevice> integratedDevice;
280 VkPhysicalDeviceProperties integratedDeviceProperties;
281 for (const VkPhysicalDevice &physicalDevice : physicalDevices)
282 {
283 pGetPhysicalDeviceProperties(physicalDevice, physicalDevicePropertiesOut);
284
285 if (physicalDevicePropertiesOut->apiVersion < kMinimumVulkanAPIVersion)
286 {
287 // Skip any devices that don't support our minimum API version. This
288 // takes precedence over all other considerations.
289 continue;
290 }
291
292 // If discrete GPU exists, uses it by default.
293 if (physicalDevicePropertiesOut->deviceType == VK_PHYSICAL_DEVICE_TYPE_DISCRETE_GPU)
294 {
295 *physicalDeviceOut = physicalDevice;
296 return;
297 }
298 if (physicalDevicePropertiesOut->deviceType == VK_PHYSICAL_DEVICE_TYPE_INTEGRATED_GPU &&
299 !integratedDevice.valid())
300 {
301 integratedDevice = physicalDevice;
302 integratedDeviceProperties = *physicalDevicePropertiesOut;
303 continue;
304 }
305 }
306
307 // If only integrated GPU exists, use it by default.
308 if (integratedDevice.valid())
309 {
310 *physicalDeviceOut = integratedDevice.value();
311 *physicalDevicePropertiesOut = integratedDeviceProperties;
312 return;
313 }
314
315 WARN() << "Preferred device ICD not found. Using default physicalDevice instead.";
316 // Fallback to the first device.
317 *physicalDeviceOut = physicalDevices[0];
318 pGetPhysicalDeviceProperties(*physicalDeviceOut, physicalDevicePropertiesOut);
319 }
320
321 } // namespace vk
322
323 } // namespace angle
324