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/bitset_utils.h"
15 #include "common/debug.h"
16 #include "common/system_utils.h"
17 
18 #include "common/vulkan/vk_google_filtering_precision.h"
19 
20 namespace
21 {
ResetEnvironmentVar(const char * variableName,const Optional<std::string> & value)22 void ResetEnvironmentVar(const char *variableName, const Optional<std::string> &value)
23 {
24     if (!value.valid())
25     {
26         return;
27     }
28 
29     if (value.value().empty())
30     {
31         angle::UnsetEnvironmentVar(variableName);
32     }
33     else
34     {
35         angle::SetEnvironmentVar(variableName, value.value().c_str());
36     }
37 }
38 }  // namespace
39 
40 namespace angle
41 {
42 
43 namespace vk
44 {
45 
46 namespace
47 {
48 
49 // This function is unused on Android/Fuschia/GGP
50 #if !defined(ANGLE_PLATFORM_ANDROID) && !defined(ANGLE_PLATFORM_FUCHSIA) && \
51     !defined(ANGLE_PLATFORM_GGP)
WrapICDEnvironment(const char * icdEnvironment)52 const std::string WrapICDEnvironment(const char *icdEnvironment)
53 {
54     // The libraries are bundled into the module directory
55     std::string ret = ConcatenatePath(angle::GetModuleDirectory(), icdEnvironment);
56     return ret;
57 }
58 
59 constexpr char kLoaderLayersPathEnv[] = "VK_LAYER_PATH";
60 #endif
61 
62 constexpr char kLoaderICDFilenamesEnv[]              = "VK_ICD_FILENAMES";
63 constexpr char kANGLEPreferredDeviceEnv[]            = "ANGLE_PREFERRED_DEVICE";
64 constexpr char kValidationLayersCustomSTypeListEnv[] = "VK_LAYER_CUSTOM_STYPE_LIST";
65 
66 constexpr uint32_t kMockVendorID = 0xba5eba11;
67 constexpr uint32_t kMockDeviceID = 0xf005ba11;
68 constexpr char kMockDeviceName[] = "Vulkan Mock Device";
69 
70 constexpr uint32_t kGoogleVendorID      = 0x1AE0;
71 constexpr uint32_t kSwiftShaderDeviceID = 0xC0DE;
72 constexpr char kSwiftShaderDeviceName[] = "SwiftShader Device";
73 
74 using ICDFilterFunc = std::function<bool(const VkPhysicalDeviceProperties &)>;
75 
GetFilterForICD(vk::ICD preferredICD)76 ICDFilterFunc GetFilterForICD(vk::ICD preferredICD)
77 {
78     switch (preferredICD)
79     {
80         case vk::ICD::Mock:
81             return [](const VkPhysicalDeviceProperties &deviceProperties) {
82                 return ((deviceProperties.vendorID == kMockVendorID) &&
83                         (deviceProperties.deviceID == kMockDeviceID) &&
84                         (strcmp(deviceProperties.deviceName, kMockDeviceName) == 0));
85             };
86         case vk::ICD::SwiftShader:
87             return [](const VkPhysicalDeviceProperties &deviceProperties) {
88                 return ((deviceProperties.vendorID == kGoogleVendorID) &&
89                         (deviceProperties.deviceID == kSwiftShaderDeviceID) &&
90                         (strncmp(deviceProperties.deviceName, kSwiftShaderDeviceName,
91                                  strlen(kSwiftShaderDeviceName)) == 0));
92             };
93         default:
94             const std::string anglePreferredDevice =
95                 angle::GetEnvironmentVar(kANGLEPreferredDeviceEnv);
96             return [anglePreferredDevice](const VkPhysicalDeviceProperties &deviceProperties) {
97                 return (anglePreferredDevice.empty() ||
98                         anglePreferredDevice == deviceProperties.deviceName);
99             };
100     }
101 }
102 
103 }  // namespace
104 
105 // If we're loading the validation layers, we could be running from any random directory.
106 // Change to the executable directory so we can find the layers, then change back to the
107 // previous directory to be safe we don't disrupt the application.
ScopedVkLoaderEnvironment(bool enableValidationLayers,vk::ICD icd)108 ScopedVkLoaderEnvironment::ScopedVkLoaderEnvironment(bool enableValidationLayers, vk::ICD icd)
109     : mEnableValidationLayers(enableValidationLayers),
110       mICD(icd),
111       mChangedCWD(false),
112       mChangedICDEnv(false)
113 {
114 // Changing CWD and setting environment variables makes no sense on Android,
115 // since this code is a part of Java application there.
116 // Android Vulkan loader doesn't need this either.
117 #if !defined(ANGLE_PLATFORM_ANDROID) && !defined(ANGLE_PLATFORM_FUCHSIA) && \
118     !defined(ANGLE_PLATFORM_GGP)
119     if (icd == vk::ICD::Mock)
120     {
121         if (!setICDEnvironment(WrapICDEnvironment(ANGLE_VK_MOCK_ICD_JSON).c_str()))
122         {
123             ERR() << "Error setting environment for Mock/Null Driver.";
124         }
125     }
126 #    if defined(ANGLE_VK_SWIFTSHADER_ICD_JSON)
127     else if (icd == vk::ICD::SwiftShader)
128     {
129         if (!setICDEnvironment(WrapICDEnvironment(ANGLE_VK_SWIFTSHADER_ICD_JSON).c_str()))
130         {
131             ERR() << "Error setting environment for SwiftShader.";
132         }
133     }
134 #    endif  // defined(ANGLE_VK_SWIFTSHADER_ICD_JSON)
135     if (mEnableValidationLayers || icd != vk::ICD::Default)
136     {
137         const auto &cwd = angle::GetCWD();
138         if (!cwd.valid())
139         {
140             ERR() << "Error getting CWD for Vulkan layers init.";
141             mEnableValidationLayers = false;
142             mICD                    = vk::ICD::Default;
143         }
144         else
145         {
146             mPreviousCWD          = cwd.value();
147             std::string moduleDir = angle::GetModuleDirectory();
148             mChangedCWD           = angle::SetCWD(moduleDir.c_str());
149             if (!mChangedCWD)
150             {
151                 ERR() << "Error setting CWD for Vulkan layers init.";
152                 mEnableValidationLayers = false;
153                 mICD                    = vk::ICD::Default;
154             }
155         }
156     }
157 
158     // Override environment variable to use the ANGLE layers.
159     if (mEnableValidationLayers)
160     {
161         if (!angle::PrependPathToEnvironmentVar(kLoaderLayersPathEnv, ANGLE_VK_LAYERS_DIR))
162         {
163             ERR() << "Error setting environment for Vulkan layers init.";
164             mEnableValidationLayers = false;
165         }
166 
167         if (!setCustomExtensionsEnvironment())
168         {
169             ERR() << "Error setting custom list for custom extensions for Vulkan layers init.";
170             mEnableValidationLayers = false;
171         }
172     }
173 #endif  // !defined(ANGLE_PLATFORM_ANDROID)
174 }
175 
~ScopedVkLoaderEnvironment()176 ScopedVkLoaderEnvironment::~ScopedVkLoaderEnvironment()
177 {
178     if (mChangedCWD)
179     {
180 #if !defined(ANGLE_PLATFORM_ANDROID)
181         ASSERT(mPreviousCWD.valid());
182         angle::SetCWD(mPreviousCWD.value().c_str());
183 #endif  // !defined(ANGLE_PLATFORM_ANDROID)
184     }
185     if (mChangedICDEnv)
186     {
187         ResetEnvironmentVar(kLoaderICDFilenamesEnv, mPreviousICDEnv);
188     }
189 
190     ResetEnvironmentVar(kValidationLayersCustomSTypeListEnv, mPreviousCustomExtensionsEnv);
191 }
192 
setICDEnvironment(const char * icd)193 bool ScopedVkLoaderEnvironment::setICDEnvironment(const char *icd)
194 {
195     // Override environment variable to use built Mock ICD
196     // ANGLE_VK_ICD_JSON gets set to the built mock ICD in BUILD.gn
197     mPreviousICDEnv = angle::GetEnvironmentVar(kLoaderICDFilenamesEnv);
198     mChangedICDEnv  = angle::SetEnvironmentVar(kLoaderICDFilenamesEnv, icd);
199 
200     if (!mChangedICDEnv)
201     {
202         mICD = vk::ICD::Default;
203     }
204     return mChangedICDEnv;
205 }
206 
setCustomExtensionsEnvironment()207 bool ScopedVkLoaderEnvironment::setCustomExtensionsEnvironment()
208 {
209     struct CustomExtension
210     {
211         VkStructureType type;
212         size_t size;
213     };
214 
215     CustomExtension customExtensions[] = {
216 
217         {VK_STRUCTURE_TYPE_SAMPLER_FILTERING_PRECISION_GOOGLE,
218          sizeof(VkSamplerFilteringPrecisionGOOGLE)},
219 
220         {VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PROVOKING_VERTEX_FEATURES_EXT,
221          sizeof(VkPhysicalDeviceProvokingVertexFeaturesEXT)},
222 
223         {VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PROVOKING_VERTEX_PROPERTIES_EXT,
224          sizeof(VkPhysicalDeviceProvokingVertexPropertiesEXT)},
225 
226         {VK_STRUCTURE_TYPE_PIPELINE_RASTERIZATION_PROVOKING_VERTEX_STATE_CREATE_INFO_EXT,
227          sizeof(VkPipelineRasterizationProvokingVertexStateCreateInfoEXT)},
228     };
229 
230     mPreviousCustomExtensionsEnv = angle::GetEnvironmentVar(kValidationLayersCustomSTypeListEnv);
231 
232     std::stringstream strstr;
233     for (CustomExtension &extension : customExtensions)
234     {
235         if (strstr.tellp() != std::streampos(0))
236         {
237             strstr << angle::GetPathSeparatorForEnvironmentVar();
238         }
239 
240         strstr << extension.type << angle::GetPathSeparatorForEnvironmentVar() << extension.size;
241     }
242 
243     return angle::PrependPathToEnvironmentVar(kValidationLayersCustomSTypeListEnv,
244                                               strstr.str().c_str());
245 }
246 
ChoosePhysicalDevice(const std::vector<VkPhysicalDevice> & physicalDevices,vk::ICD preferredICD,VkPhysicalDevice * physicalDeviceOut,VkPhysicalDeviceProperties * physicalDevicePropertiesOut)247 void ChoosePhysicalDevice(const std::vector<VkPhysicalDevice> &physicalDevices,
248                           vk::ICD preferredICD,
249                           VkPhysicalDevice *physicalDeviceOut,
250                           VkPhysicalDeviceProperties *physicalDevicePropertiesOut)
251 {
252     ASSERT(!physicalDevices.empty());
253 
254     ICDFilterFunc filter = GetFilterForICD(preferredICD);
255 
256     for (const VkPhysicalDevice &physicalDevice : physicalDevices)
257     {
258         vkGetPhysicalDeviceProperties(physicalDevice, physicalDevicePropertiesOut);
259         if (filter(*physicalDevicePropertiesOut))
260         {
261             *physicalDeviceOut = physicalDevice;
262             return;
263         }
264     }
265     WARN() << "Preferred device ICD not found. Using default physicalDevice instead.";
266 
267     // Fall back to first device.
268     *physicalDeviceOut = physicalDevices[0];
269     vkGetPhysicalDeviceProperties(*physicalDeviceOut, physicalDevicePropertiesOut);
270 }
271 
272 }  // namespace vk
273 
274 }  // namespace angle
275