1 // Copyright (C) 2018 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 "VulkanDispatch.h"
16
17 #include "aemu/base/SharedLibrary.h"
18 #include "aemu/base/files/PathUtils.h"
19 #include "aemu/base/synchronization/Lock.h"
20 #include "aemu/base/system/System.h"
21 #include "host-common/misc.h"
22
23 using android::base::AutoLock;
24 using android::base::Lock;
25 using android::base::pj;
26
27 #ifndef VERBOSE
28 #define VERBOSE INFO
29 #endif
30
31 namespace gfxstream {
32 namespace vk {
33
icdJsonNameToProgramAndLauncherPaths(const std::string & icdFilename)34 static std::string icdJsonNameToProgramAndLauncherPaths(const std::string& icdFilename) {
35 std::string suffix = pj({"lib64", "vulkan", icdFilename});
36 #if defined(_WIN32)
37 const char* sep = ";";
38 #else
39 const char* sep = ":";
40 #endif
41 return pj({android::base::getProgramDirectory(), suffix}) + sep +
42 pj({android::base::getLauncherDirectory(), suffix});
43 }
44
setIcdPaths(const std::string & icdFilename)45 static void setIcdPaths(const std::string& icdFilename) {
46 const std::string paths = icdJsonNameToProgramAndLauncherPaths(icdFilename);
47 INFO("Setting ICD filenames for the loader = %s", paths.c_str());
48 // Set both for backwards compatibility
49 android::base::setEnvironmentVariable("VK_DRIVER_FILES", paths);
50 android::base::setEnvironmentVariable("VK_ICD_FILENAMES", paths);
51 }
52
initIcdPaths(bool forTesting)53 static void initIcdPaths(bool forTesting) {
54 auto androidIcd = android::base::getEnvironmentVariable("ANDROID_EMU_VK_ICD");
55 if (androidIcd == "") {
56 // Rely on user to set VK_DRIVER_FILES
57 return;
58 }
59
60 if (forTesting) {
61 const char* testingICD = "swiftshader";
62 INFO("%s: In test environment, enforcing %s ICD.", __func__, testingICD);
63 android::base::setEnvironmentVariable("ANDROID_EMU_VK_ICD", testingICD);
64 androidIcd = testingICD;
65 }
66 if (androidIcd == "lavapipe") {
67 INFO("%s: ICD set to 'lavapipe', using Lavapipe ICD", __func__);
68 setIcdPaths("lvp_icd.x86_64.json");
69 } else if (androidIcd == "swiftshader") {
70 INFO("%s: ICD set to 'swiftshader', using Swiftshader ICD", __func__);
71 setIcdPaths("vk_swiftshader_icd.json");
72 } else {
73 #ifdef __APPLE__
74 // Mac: Use MoltenVK by default unless GPU mode is set to swiftshader
75 if (androidIcd != "moltenvk") {
76 WARN("%s: Unknown ICD, resetting to MoltenVK", __func__);
77 android::base::setEnvironmentVariable("ANDROID_EMU_VK_ICD", "moltenvk");
78 }
79 setIcdPaths("MoltenVK_icd.json");
80
81 // Configure MoltenVK library with environment variables
82 // 0: No logging.
83 // 1: Log errors only.
84 // 2: Log errors and warning messages.
85 // 3: Log errors, warnings and informational messages.
86 // 4: Log errors, warnings, infos and debug messages.
87 const bool verboseLogs =
88 (android::base::getEnvironmentVariable("ANDROID_EMUGL_VERBOSE") == "1");
89 const char* logLevelValue = verboseLogs ? "4" : "1";
90 android::base::setEnvironmentVariable("MVK_CONFIG_LOG_LEVEL", logLevelValue);
91
92 // Limit MoltenVK to use single queue, as some older ANGLE versions
93 // expect this for -guest-angle to work.
94 // 0: Limit Vulkan to a single queue, with no explicit semaphore
95 // synchronization, and use Metal's implicit guarantees that all operations
96 // submitted to a queue will give the same result as if they had been run in
97 // submission order.
98 android::base::setEnvironmentVariable("MVK_CONFIG_VK_SEMAPHORE_SUPPORT_STYLE", "0");
99
100 // TODO(b/364055067)
101 // MVK_CONFIG_USE_METAL_ARGUMENT_BUFFERS is not working correctly
102 android::base::setEnvironmentVariable("MVK_CONFIG_USE_METAL_ARGUMENT_BUFFERS", "0");
103
104 // MVK_CONFIG_USE_MTLHEAP is required for VK_EXT_external_memory_metal
105 android::base::setEnvironmentVariable("MVK_CONFIG_USE_MTLHEAP", "1");
106
107 // TODO(b/351765838): VVL won't work with MoltenVK due to the current
108 // way of external memory handling, add it into disable list to
109 // avoid users enabling it implicitly (i.e. via vkconfig).
110 // It can be enabled with VK_LOADER_LAYERS_ALLOW=VK_LAYER_KHRONOS_validation
111 INFO("Vulkan Validation Layers won't be enabled with MoltenVK");
112 android::base::setEnvironmentVariable("VK_LOADER_LAYERS_DISABLE",
113 "VK_LAYER_KHRONOS_validation");
114 #else
115 // By default, on other platforms, just use whatever the system
116 // is packing.
117 #endif
118 }
119 }
120
121 class SharedLibraries {
122 public:
SharedLibraries(size_t sizeLimit=1)123 explicit SharedLibraries(size_t sizeLimit = 1) : mSizeLimit(sizeLimit) {}
124
size() const125 size_t size() const { return mLibs.size(); }
126
addLibrary(const std::string & path)127 bool addLibrary(const std::string& path) {
128 if (size() >= mSizeLimit) {
129 WARN("Cannot add library %s due to size limit(%d)", path.c_str(), mSizeLimit);
130 return false;
131 }
132
133 auto library = android::base::SharedLibrary::open(path.c_str());
134 if (library) {
135 mLibs.push_back(library);
136 INFO("Added library: %s", path.c_str());
137 return true;
138 } else {
139 // This is expected when searching for a valid library path
140 VERBOSE("Library cannot be added: %s", path.c_str());
141 return false;
142 }
143 }
144
addFirstAvailableLibrary(const std::vector<std::string> & possiblePaths)145 bool addFirstAvailableLibrary(const std::vector<std::string>& possiblePaths) {
146 for (const std::string& possiblePath : possiblePaths) {
147 if (addLibrary(possiblePath)) {
148 return true;
149 }
150 }
151 return false;
152 }
153
154 ~SharedLibraries() = default;
155
dlsym(const char * name)156 void* dlsym(const char* name) {
157 for (const auto& lib : mLibs) {
158 void* funcPtr = reinterpret_cast<void*>(lib->findSymbol(name));
159 if (funcPtr) {
160 return funcPtr;
161 }
162 }
163 return nullptr;
164 }
165
166 private:
167 size_t mSizeLimit;
168 std::vector<android::base::SharedLibrary*> mLibs;
169 };
170
getVulkanLibraryNumLimits()171 static constexpr size_t getVulkanLibraryNumLimits() {
172 return 1;
173 }
174
175 class VulkanDispatchImpl {
176 public:
VulkanDispatchImpl()177 VulkanDispatchImpl() : mVulkanLibs(getVulkanLibraryNumLimits()) {}
178
179 void initialize(bool forTesting);
180
getPossibleLoaderPathBasenames()181 static std::vector<std::string> getPossibleLoaderPathBasenames() {
182 #if defined(__APPLE__)
183 return std::vector<std::string>{"libvulkan.dylib"};
184 #elif defined(__linux__)
185 return std::vector<std::string>{
186 "libvulkan.so",
187 "libvulkan.so.1",
188 };
189 #elif defined(_WIN32)
190 return std::vector<std::string>{"vulkan-1.dll"};
191 #elif defined(__QNX__)
192 return std::vector<std::string>{
193 "libvulkan.so",
194 "libvulkan.so.1",
195 };
196 #else
197 #error "Unhandled platform in VulkanDispatchImpl."
198 #endif
199 }
200
getPossibleLoaderPaths()201 std::vector<std::string> getPossibleLoaderPaths() {
202 const std::string explicitPath =
203 android::base::getEnvironmentVariable("ANDROID_EMU_VK_LOADER_PATH");
204 if (!explicitPath.empty()) {
205 return {
206 explicitPath,
207 };
208 }
209
210 const std::vector<std::string> possibleBasenames = getPossibleLoaderPathBasenames();
211
212 const std::string explicitIcd = android::base::getEnvironmentVariable("ANDROID_EMU_VK_ICD");
213
214 #ifdef _WIN32
215 constexpr const bool isWindows = true;
216 #else
217 constexpr const bool isWindows = false;
218 #endif
219 if (explicitIcd.empty() || isWindows) {
220 return possibleBasenames;
221 }
222
223 std::vector<std::string> possibleDirectories;
224
225 if (mForTesting || explicitIcd == "mock") {
226 possibleDirectories = {
227 pj({android::base::getProgramDirectory(), "testlib64"}),
228 pj({android::base::getLauncherDirectory(), "testlib64"}),
229 };
230 }
231
232 possibleDirectories.push_back(
233 pj({android::base::getProgramDirectory(), "lib64", "vulkan"}));
234 possibleDirectories.push_back(
235 pj({android::base::getLauncherDirectory(), "lib64", "vulkan"}));
236
237 std::vector<std::string> possiblePaths;
238 for (const std::string& possibleDirectory : possibleDirectories) {
239 for (const std::string& possibleBasename : possibleBasenames) {
240 possiblePaths.push_back(pj({possibleDirectory, possibleBasename}));
241 }
242 }
243 return possiblePaths;
244 }
245
dlopen()246 void* dlopen() {
247 if (mVulkanLibs.size() == 0) {
248 const std::vector<std::string> possiblePaths = getPossibleLoaderPaths();
249 if (!mVulkanLibs.addFirstAvailableLibrary(possiblePaths)) {
250 ERR("Cannot add any library for Vulkan loader from the list of %d items",
251 possiblePaths.size());
252 }
253 }
254 return static_cast<void*>(&mVulkanLibs);
255 }
256
dlsym(void * lib,const char * name)257 void* dlsym(void* lib, const char* name) {
258 return (void*)((SharedLibraries*)(lib))->dlsym(name);
259 }
260
dispatch()261 VulkanDispatch* dispatch() { return &mDispatch; }
262
263 private:
264 Lock mLock;
265 bool mForTesting = false;
266 bool mInitialized = false;
267 VulkanDispatch mDispatch;
268 SharedLibraries mVulkanLibs;
269 };
270
sVulkanDispatchImpl()271 VulkanDispatchImpl* sVulkanDispatchImpl() {
272 static VulkanDispatchImpl* impl = new VulkanDispatchImpl;
273 return impl;
274 }
275
sVulkanDispatchDlOpen()276 static void* sVulkanDispatchDlOpen() { return sVulkanDispatchImpl()->dlopen(); }
277
sVulkanDispatchDlSym(void * lib,const char * sym)278 static void* sVulkanDispatchDlSym(void* lib, const char* sym) {
279 return sVulkanDispatchImpl()->dlsym(lib, sym);
280 }
281
initialize(bool forTesting)282 void VulkanDispatchImpl::initialize(bool forTesting) {
283 AutoLock lock(mLock);
284
285 if (mInitialized) {
286 return;
287 }
288
289 mForTesting = forTesting;
290 initIcdPaths(mForTesting);
291
292 init_vulkan_dispatch_from_system_loader(sVulkanDispatchDlOpen, sVulkanDispatchDlSym,
293 &mDispatch);
294
295 mInitialized = true;
296 }
297
vkDispatch(bool forTesting)298 VulkanDispatch* vkDispatch(bool forTesting) {
299 sVulkanDispatchImpl()->initialize(forTesting);
300 return sVulkanDispatchImpl()->dispatch();
301 }
302
vkDispatchValid(const VulkanDispatch * vk)303 bool vkDispatchValid(const VulkanDispatch* vk) {
304 return vk->vkEnumerateInstanceExtensionProperties != nullptr ||
305 vk->vkGetInstanceProcAddr != nullptr || vk->vkGetDeviceProcAddr != nullptr;
306 }
307
308 } // namespace vk
309 } // namespace gfxstream
310