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