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/synchronization/Lock.h"
18 #include "aemu/base/files/PathUtils.h"
19 #include "aemu/base/SharedLibrary.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 namespace gfxstream {
28 namespace vk {
29 
setIcdPath(const std::string & path)30 static void setIcdPath(const std::string& path) {
31     if (android::base::pathExists(path.c_str())) {
32         // LOG(VERBOSE) << "setIcdPath: path exists: " << path;
33     } else {
34         // LOG(VERBOSE) << "setIcdPath: path doesn't exist: " << path;
35     }
36     android::base::setEnvironmentVariable("VK_ICD_FILENAMES", path);
37 }
38 
icdJsonNameToProgramAndLauncherPaths(const std::string & icdFilename)39 static std::string icdJsonNameToProgramAndLauncherPaths(const std::string& icdFilename) {
40     std::string suffix = pj({"lib64", "vulkan", icdFilename});
41 #if defined(_WIN32)
42     const char* sep = ";";
43 #else
44     const char* sep = ":";
45 #endif
46     return pj({android::base::getProgramDirectory(), suffix}) + sep +
47            pj({android::base::getLauncherDirectory(), suffix});
48 }
49 
getTestIcdFilename()50 static const char* getTestIcdFilename() {
51 #if defined(__APPLE__)
52     return "libvk_swiftshader.dylib";
53 #elif defined(__linux__)
54     return "libvk_swiftshader.so";
55 #elif defined(_WIN32) || defined(_MSC_VER)
56     return "vk_swiftshader.dll";
57 #else
58 #error Host operating system not supported
59 #endif
60 }
61 
initIcdPaths(bool forTesting)62 static void initIcdPaths(bool forTesting) {
63     auto androidIcd = android::base::getEnvironmentVariable("ANDROID_EMU_VK_ICD");
64     if (androidIcd == "") {
65         // Rely on user to set VK_ICD_FILENAMES
66         return;
67     } else {
68         if (forTesting || androidIcd == "swiftshader") {
69             auto res = pj({android::base::getProgramDirectory(), "lib64", "vulkan"});
70             // LOG(VERBOSE) << "In test environment or ICD set to swiftshader, using "
71             // "Swiftshader ICD";
72             auto libPath = pj(
73                 {android::base::getProgramDirectory(), "lib64", "vulkan", getTestIcdFilename()});
74             ;
75             if (android::base::pathExists(libPath.c_str())) {
76                 // LOG(VERBOSE) << "Swiftshader library exists";
77             } else {
78                 // LOG(VERBOSE) << "Swiftshader library doesn't exist, trying launcher path";
79                 libPath = pj({android::base::getLauncherDirectory(), "lib64", "vulkan",
80                               getTestIcdFilename()});
81                 ;
82                 if (android::base::pathExists(libPath.c_str())) {
83                     // LOG(VERBOSE) << "Swiftshader library found in launcher path";
84                 } else {
85                     // LOG(VERBOSE) << "Swiftshader library not found in program nor launcher path";
86                 }
87             }
88             setIcdPath(icdJsonNameToProgramAndLauncherPaths("vk_swiftshader_icd.json"));
89             android::base::setEnvironmentVariable("ANDROID_EMU_VK_ICD", "swiftshader");
90         } else {
91             // LOG(VERBOSE) << "Not in test environment. ICD (blank for default): ["
92             // << androidIcd << "]";
93             // Mac: Use MoltenVK by default unless GPU mode is set to swiftshader,
94             // and switch between that and gfx-rs libportability-icd depending on
95             // the environment variable setting.
96 #ifdef __APPLE__
97             if (androidIcd == "portability") {
98                 setIcdPath(icdJsonNameToProgramAndLauncherPaths("portability-macos.json"));
99             } else if (androidIcd == "portability-debug") {
100                 setIcdPath(icdJsonNameToProgramAndLauncherPaths("portability-macos-debug.json"));
101             } else {
102                 if (androidIcd == "swiftshader") {
103                     setIcdPath(icdJsonNameToProgramAndLauncherPaths("vk_swiftshader_icd.json"));
104                     android::base::setEnvironmentVariable("ANDROID_EMU_VK_ICD", "swiftshader");
105                 } else {
106                     setIcdPath(icdJsonNameToProgramAndLauncherPaths("MoltenVK_icd.json"));
107                     android::base::setEnvironmentVariable("ANDROID_EMU_VK_ICD", "moltenvk");
108                 }
109             }
110 #else
111             // By default, on other platforms, just use whatever the system
112             // is packing.
113 #endif
114         }
115     }
116 }
117 
118 #ifdef __APPLE__
119 #define VULKAN_LOADER_FILENAME "libvulkan.dylib"
120 #else
121 #ifdef _WIN32
122 #define VULKAN_LOADER_FILENAME "vulkan-1.dll"
123 #else
124 #define VULKAN_LOADER_FILENAME "libvulkan.so"
125 #endif
126 
127 #endif
getLoaderPath(const std::string & directory,bool forTesting)128 static std::string getLoaderPath(const std::string& directory, bool forTesting) {
129     auto path = android::base::getEnvironmentVariable("ANDROID_EMU_VK_LOADER_PATH");
130     if (!path.empty()) {
131         return path;
132     }
133     auto androidIcd = android::base::getEnvironmentVariable("ANDROID_EMU_VK_ICD");
134     if (forTesting || androidIcd == "mock") {
135         auto path = pj({directory, "testlib64", VULKAN_LOADER_FILENAME});
136         // LOG(VERBOSE) << "In test environment or using Swiftshader. Using loader: " << path;
137         return path;
138     } else {
139 #ifdef _WIN32
140         // LOG(VERBOSE) << "Not in test environment. Using loader: " << VULKAN_LOADER_FILENAME;
141         return VULKAN_LOADER_FILENAME;
142 #else
143         auto path = pj({directory, "lib64", "vulkan", VULKAN_LOADER_FILENAME});
144         // LOG(VERBOSE) << "Not in test environment. Using loader: " << path;
145         return path;
146 #endif
147     }
148 }
149 
150 #ifdef __APPLE__
getMoltenVkPath(const std::string & directory,bool forTesting)151 static std::string getMoltenVkPath(const std::string& directory, bool forTesting) {
152     auto path = android::base::getEnvironmentVariable("ANDROID_EMU_VK_LOADER_PATH");
153     if (!path.empty()) {
154         return path;
155     }
156     auto androidIcd = android::base::getEnvironmentVariable("ANDROID_EMU_VK_ICD");
157 
158     // Skip loader when using MoltenVK as this gives us access to
159     // VK_MVK_moltenvk, which is required for external memory support.
160     if (!forTesting && androidIcd == "moltenvk") {
161         auto path = pj({directory, "lib64", "vulkan", "libMoltenVK.dylib"});
162         // LOG(VERBOSE) << "Skipping loader and using ICD directly: " << path;
163         return path;
164     }
165     return "";
166 }
167 #endif
168 
169 class SharedLibraries {
170    public:
SharedLibraries(size_t sizeLimit=1)171     explicit SharedLibraries(size_t sizeLimit = 1) : mSizeLimit(sizeLimit) {}
172 
size() const173     size_t size() const { return mLibs.size(); }
174 
addLibrary(const std::string & path)175     bool addLibrary(const std::string& path) {
176         if (size() >= mSizeLimit) {
177             fprintf(stderr, "cannot add library %s: full\n", path.c_str());
178             return false;
179         }
180 
181         auto library = android::base::SharedLibrary::open(path.c_str());
182         if (library) {
183             mLibs.push_back(library);
184             fprintf(stderr, "added library %s\n", path.c_str());
185             return true;
186         } else {
187             fprintf(stderr, "cannot add library %s: failed\n", path.c_str());
188             return false;
189         }
190     }
191 
192     ~SharedLibraries() = default;
193 
dlsym(const char * name)194     void* dlsym(const char* name) {
195         for (const auto& lib : mLibs) {
196             void* funcPtr = reinterpret_cast<void*>(lib->findSymbol(name));
197             if (funcPtr) {
198                 return funcPtr;
199             }
200         }
201         return nullptr;
202     }
203 
204    private:
205     size_t mSizeLimit;
206     std::vector<android::base::SharedLibrary*> mLibs;
207 };
208 
getVulkanLibraryNumLimits()209 static constexpr size_t getVulkanLibraryNumLimits() {
210     // macOS may have both Vulkan loader (for non MoltenVK-specific functions) and
211     // MoltenVK library (only for MoltenVK-specific vk...MVK functions) loaded at
212     // the same time. So there could be at most 2 libraries loaded. On other systems
213     // only one Vulkan loader is allowed.
214 #ifdef __APPLE__
215     return 2;
216 #else
217     return 1;
218 #endif
219 }
220 
221 class VulkanDispatchImpl {
222    public:
VulkanDispatchImpl()223     VulkanDispatchImpl() : mVulkanLibs(getVulkanLibraryNumLimits()) {}
224 
225     void initialize(bool forTesting);
226 
dlopen()227     void* dlopen() {
228         bool sandbox = android::base::getEnvironmentVariable("ANDROID_EMU_VK_ICD") == "";
229 
230         if (mVulkanLibs.size() == 0) {
231             if (sandbox) {
232 #ifdef __linux__
233                 bool success = mVulkanLibs.addLibrary(VULKAN_LOADER_FILENAME);
234                 if (!success) {
235                     mVulkanLibs.addLibrary("libvulkan.so.1");
236                 }
237 #else
238                 mVulkanLibs.addLibrary(VULKAN_LOADER_FILENAME);
239 #endif  // __linux__
240             } else {
241                 auto loaderPath = getLoaderPath(android::base::getProgramDirectory(), mForTesting);
242                 bool success = mVulkanLibs.addLibrary(loaderPath);
243 
244                 if (!success) {
245                     loaderPath = getLoaderPath(android::base::getLauncherDirectory(), mForTesting);
246                     mVulkanLibs.addLibrary(loaderPath);
247                 }
248 
249 #ifdef __linux__
250                 // On Linux, it might not be called libvulkan.so.
251                 // Try libvulkan.so.1 if that doesn't work.
252                 if (!success) {
253                     loaderPath = pj({android::base::getLauncherDirectory(), "lib64", "vulkan",
254                                      "libvulkan.so.1"});
255                     mVulkanLibs.addLibrary(loaderPath);
256                 }
257 #endif  // __linux__
258 #ifdef __APPLE__
259                 // On macOS it is possible that we are using MoltenVK as the
260                 // ICD. In that case we need to add MoltenVK libraries to
261                 // mSharedLibs to use MoltenVK-specific functions.
262                 auto mvkPath = getMoltenVkPath(android::base::getProgramDirectory(), mForTesting);
263                 if (!mvkPath.empty()) {
264                     success = mVulkanLibs.addLibrary(mvkPath);
265                 }
266 
267                 if (!success) {
268                     mvkPath = getMoltenVkPath(android::base::getLauncherDirectory(), mForTesting);
269                     if (!mvkPath.empty()) {
270                         success = mVulkanLibs.addLibrary(mvkPath);
271                     }
272                 }
273 #endif  // __APPLE__
274             }
275         }
276         return static_cast<void*>(&mVulkanLibs);
277     }
278 
dlsym(void * lib,const char * name)279     void* dlsym(void* lib, const char* name) {
280         return (void*)((SharedLibraries*)(lib))->dlsym(name);
281     }
282 
dispatch()283     VulkanDispatch* dispatch() { return &mDispatch; }
284 
285    private:
286     Lock mLock;
287     bool mForTesting = false;
288     bool mInitialized = false;
289     VulkanDispatch mDispatch;
290     SharedLibraries mVulkanLibs;
291 };
292 
sVulkanDispatchImpl()293 VulkanDispatchImpl* sVulkanDispatchImpl() {
294     static VulkanDispatchImpl* impl = new VulkanDispatchImpl;
295     return impl;
296 }
297 
sVulkanDispatchDlOpen()298 static void* sVulkanDispatchDlOpen() { return sVulkanDispatchImpl()->dlopen(); }
299 
sVulkanDispatchDlSym(void * lib,const char * sym)300 static void* sVulkanDispatchDlSym(void* lib, const char* sym) {
301     return sVulkanDispatchImpl()->dlsym(lib, sym);
302 }
303 
initialize(bool forTesting)304 void VulkanDispatchImpl::initialize(bool forTesting) {
305     AutoLock lock(mLock);
306 
307     if (mInitialized) {
308         return;
309     }
310 
311     mForTesting = forTesting;
312     initIcdPaths(mForTesting);
313 
314     init_vulkan_dispatch_from_system_loader(sVulkanDispatchDlOpen, sVulkanDispatchDlSym,
315                                             &mDispatch);
316 
317     mInitialized = true;
318 }
319 
vkDispatch(bool forTesting)320 VulkanDispatch* vkDispatch(bool forTesting) {
321     sVulkanDispatchImpl()->initialize(forTesting);
322     return sVulkanDispatchImpl()->dispatch();
323 }
324 
vkDispatchValid(const VulkanDispatch * vk)325 bool vkDispatchValid(const VulkanDispatch* vk) {
326     return vk->vkEnumerateInstanceExtensionProperties != nullptr ||
327            vk->vkGetInstanceProcAddr != nullptr || vk->vkGetDeviceProcAddr != nullptr;
328 }
329 
330 }  // namespace vk
331 }  // namespace gfxstream
332