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