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