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