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