• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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__) || defined(__QNX__)
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 class SharedLibraries {
119    public:
SharedLibraries(size_t sizeLimit=1)120     explicit SharedLibraries(size_t sizeLimit = 1) : mSizeLimit(sizeLimit) {}
121 
size() const122     size_t size() const { return mLibs.size(); }
123 
addLibrary(const std::string & path)124     bool addLibrary(const std::string& path) {
125         if (size() >= mSizeLimit) {
126             fprintf(stderr, "cannot add library %s: full\n", path.c_str());
127             return false;
128         }
129 
130         auto library = android::base::SharedLibrary::open(path.c_str());
131         if (library) {
132             mLibs.push_back(library);
133             fprintf(stderr, "added library %s\n", path.c_str());
134             return true;
135         } else {
136             fprintf(stderr, "cannot add library %s: failed\n", path.c_str());
137             return false;
138         }
139     }
140 
addFirstAvailableLibrary(const std::vector<std::string> & possiblePaths)141     bool addFirstAvailableLibrary(const std::vector<std::string>& possiblePaths) {
142         for (const std::string& possiblePath : possiblePaths) {
143             if (addLibrary(possiblePath)) {
144                 return true;
145             }
146         }
147         return false;
148     }
149 
150     ~SharedLibraries() = default;
151 
dlsym(const char * name)152     void* dlsym(const char* name) {
153         for (const auto& lib : mLibs) {
154             void* funcPtr = reinterpret_cast<void*>(lib->findSymbol(name));
155             if (funcPtr) {
156                 return funcPtr;
157             }
158         }
159         return nullptr;
160     }
161 
162    private:
163     size_t mSizeLimit;
164     std::vector<android::base::SharedLibrary*> mLibs;
165 };
166 
getVulkanLibraryNumLimits()167 static constexpr size_t getVulkanLibraryNumLimits() {
168     // macOS may have both Vulkan loader (for non MoltenVK-specific functions) and
169     // MoltenVK library (only for MoltenVK-specific vk...MVK functions) loaded at
170     // the same time. So there could be at most 2 libraries loaded. On other systems
171     // only one Vulkan loader is allowed.
172 #ifdef __APPLE__
173     return 2;
174 #else
175     return 1;
176 #endif
177 }
178 
179 class VulkanDispatchImpl {
180    public:
VulkanDispatchImpl()181     VulkanDispatchImpl() : mVulkanLibs(getVulkanLibraryNumLimits()) {}
182 
183     void initialize(bool forTesting);
184 
getPossibleLoaderPathBasenames()185     static std::vector<std::string> getPossibleLoaderPathBasenames() {
186 #if defined(__APPLE__)
187         return std::vector<std::string>{"libvulkan.dylib"};
188 #elif defined(__linux__)
189         // When running applications with Gfxstream as the Vulkan ICD, i.e. with
190         //
191         //   App -> Vulkan Loader -> Gfxstream ICD -> Vulkan Loader -> Driver ICD
192         //
193         // Gfxstream needs to use a different nested loader library to avoid issues with
194         // conflating/deadlock with the first level loader. Detect that here and look for
195         // a "libvulkan_gfxstream" which can be generated with the provided
196         // scripts/build-nested-vulkan-loader.sh script.
197         const std::vector<std::string> nestedVulkanLoaderVars = {
198             "GFXSTREAM_VK_ADD_DRIVER_FILES",
199             "GFXSTREAM_VK_ADD_LAYER_PATH",
200             "GFXSTREAM_VK_DRIVER_FILES",
201             "GFXSTREAM_VK_ICD_FILENAMES",
202             "GFXSTREAM_VK_INSTANCE_LAYERS",
203             "GFXSTREAM_VK_LAYER_PATH",
204             "GFXSTREAM_VK_LAYER_PATH",
205             "GFXSTREAM_VK_LOADER_DEBUG",
206             "GFXSTREAM_VK_LOADER_DRIVERS_DISABLE",
207             "GFXSTREAM_VK_LOADER_DRIVERS_SELECT",
208             "GFXSTREAM_VK_LOADER_LAYERS_ALLOW",
209             "GFXSTREAM_VK_LOADER_LAYERS_DISABLE",
210             "GFXSTREAM_VK_LOADER_LAYERS_ENABLE",
211         };
212         bool usesNestedVulkanLoader = false;
213         for (const std::string& var : nestedVulkanLoaderVars) {
214             if (android::base::getEnvironmentVariable(var) != "") {
215                 usesNestedVulkanLoader = true;
216                 break;
217             }
218         }
219         if (usesNestedVulkanLoader) {
220             return std::vector<std::string>{
221                 "libvulkan_gfxstream.so",
222                 "libvulkan_gfxstream.so.1",
223             };
224         } else {
225             return std::vector<std::string>{
226                 "libvulkan.so",
227                 "libvulkan.so.1",
228             };
229         }
230 
231 #elif defined(_WIN32)
232         return std::vector<std::string>{"vulkan-1.dll"};
233 #else
234 #error "Unhandled platform in VulkanDispatchImpl."
235 #endif
236     }
237 
getPossibleLoaderPaths()238     std::vector<std::string> getPossibleLoaderPaths() {
239         const std::string explicitPath =
240             android::base::getEnvironmentVariable("ANDROID_EMU_VK_LOADER_PATH");
241         if (!explicitPath.empty()) {
242             return {
243                 explicitPath,
244             };
245         }
246 
247         const std::vector<std::string> possibleBasenames =
248             getPossibleLoaderPathBasenames();
249 
250         const std::string explicitIcd =
251             android::base::getEnvironmentVariable("ANDROID_EMU_VK_ICD");
252 
253 #ifdef _WIN32
254         constexpr const bool isWindows = true;
255 #else
256         constexpr const bool isWindows = false;
257 #endif
258         if (explicitIcd.empty() || isWindows) {
259             return possibleBasenames;
260         }
261 
262         std::vector<std::string> possibleDirectories;
263 
264         if (mForTesting || explicitIcd == "mock") {
265             possibleDirectories = {
266                 pj({android::base::getProgramDirectory(), "testlib64"}),
267                 pj({android::base::getLauncherDirectory(), "testlib64"}),
268             };
269         }
270 
271         possibleDirectories.push_back(
272             pj({android::base::getProgramDirectory(), "lib64", "vulkan"}));
273         possibleDirectories.push_back(
274             pj({android::base::getLauncherDirectory(), "lib64", "vulkan"}));
275 
276         std::vector<std::string> possiblePaths;
277         for (const std::string& possibleDirectory : possibleDirectories) {
278             for (const std::string& possibleBasename : possibleBasenames) {
279                 possiblePaths.push_back(pj({possibleDirectory, possibleBasename}));
280             }
281         }
282         return possiblePaths;
283     }
284 
285 #ifdef __APPLE__
getPossibleMoltenVkPaths()286     std::vector<std::string> getPossibleMoltenVkPaths() {
287         const std::string explicitPath =
288             android::base::getEnvironmentVariable("ANDROID_EMU_VK_LOADER_PATH");
289         if (!explicitPath.empty()) {
290             return {
291                 explicitPath,
292             };
293         }
294 
295         const std::string& customIcd = android::base::getEnvironmentVariable("ANDROID_EMU_VK_ICD");
296 
297         // Skip loader when using MoltenVK as this gives us access to
298         // VK_MVK_moltenvk, which is required for external memory support.
299         if (!mForTesting && customIcd == "moltenvk") {
300             return {
301                 pj({android::base::getProgramDirectory(), "lib64", "vulkan", "libMoltenVK.dylib"}),
302                 pj({android::base::getLauncherDirectory(), "lib64", "vulkan", "libMoltenVK.dylib"}),
303             };
304         }
305 
306         return {};
307     }
308 #endif
309 
dlopen()310     void* dlopen() {
311         if (mVulkanLibs.size() == 0) {
312             mVulkanLibs.addFirstAvailableLibrary(getPossibleLoaderPaths());
313 
314 #ifdef __APPLE__
315             // On macOS it is possible that we are using MoltenVK as the
316             // ICD. In that case we need to add MoltenVK libraries to
317             // mSharedLibs to use MoltenVK-specific functions.
318             mVulkanLibs.addFirstAvailableLibrary(getPossibleMoltenVkPaths());
319 #endif
320         }
321         return static_cast<void*>(&mVulkanLibs);
322     }
323 
dlsym(void * lib,const char * name)324     void* dlsym(void* lib, const char* name) {
325         return (void*)((SharedLibraries*)(lib))->dlsym(name);
326     }
327 
dispatch()328     VulkanDispatch* dispatch() { return &mDispatch; }
329 
330    private:
331     Lock mLock;
332     bool mForTesting = false;
333     bool mInitialized = false;
334     VulkanDispatch mDispatch;
335     SharedLibraries mVulkanLibs;
336 };
337 
sVulkanDispatchImpl()338 VulkanDispatchImpl* sVulkanDispatchImpl() {
339     static VulkanDispatchImpl* impl = new VulkanDispatchImpl;
340     return impl;
341 }
342 
sVulkanDispatchDlOpen()343 static void* sVulkanDispatchDlOpen() { return sVulkanDispatchImpl()->dlopen(); }
344 
sVulkanDispatchDlSym(void * lib,const char * sym)345 static void* sVulkanDispatchDlSym(void* lib, const char* sym) {
346     return sVulkanDispatchImpl()->dlsym(lib, sym);
347 }
348 
initialize(bool forTesting)349 void VulkanDispatchImpl::initialize(bool forTesting) {
350     AutoLock lock(mLock);
351 
352     if (mInitialized) {
353         return;
354     }
355 
356     mForTesting = forTesting;
357     initIcdPaths(mForTesting);
358 
359     init_vulkan_dispatch_from_system_loader(sVulkanDispatchDlOpen, sVulkanDispatchDlSym,
360                                             &mDispatch);
361 
362     mInitialized = true;
363 }
364 
vkDispatch(bool forTesting)365 VulkanDispatch* vkDispatch(bool forTesting) {
366     sVulkanDispatchImpl()->initialize(forTesting);
367     return sVulkanDispatchImpl()->dispatch();
368 }
369 
vkDispatchValid(const VulkanDispatch * vk)370 bool vkDispatchValid(const VulkanDispatch* vk) {
371     return vk->vkEnumerateInstanceExtensionProperties != nullptr ||
372            vk->vkGetInstanceProcAddr != nullptr || vk->vkGetDeviceProcAddr != nullptr;
373 }
374 
375 }  // namespace vk
376 }  // namespace gfxstream
377