/* * Copyright (C) 2020 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #include "host/libs/graphics_detector/graphics_detector.h" #include #include #include #include #include #include #include #include #include namespace cuttlefish { namespace { constexpr const char kEglLib[] = "libEGL.so.1"; constexpr const char kGlLib[] = "libOpenGL.so.0"; constexpr const char kGles1Lib[] = "libGLESv1_CM.so.1"; constexpr const char kGles2Lib[] = "libGLESv2.so.2"; constexpr const char kVulkanLib[] = "libvulkan.so.1"; constexpr const char kSurfacelessContextExt[] = "EGL_KHR_surfaceless_context"; class Closer { public: Closer(std::function on_close) : on_close_(on_close) {} ~Closer() { on_close_(); } private: std::function on_close_; }; struct LibraryCloser { public: void operator()(void* library) { dlclose(library); } }; using ManagedLibrary = std::unique_ptr; void PopulateGlAvailability(GraphicsAvailability* availability) { ManagedLibrary gl_lib(dlopen(kGlLib, RTLD_NOW | RTLD_LOCAL)); if (!gl_lib) { LOG(VERBOSE) << "Failed to dlopen " << kGlLib << "."; return; } LOG(VERBOSE) << "Loaded " << kGlLib << "."; availability->has_gl = true; } void PopulateGles1Availability(GraphicsAvailability* availability) { ManagedLibrary gles1_lib(dlopen(kGles1Lib, RTLD_NOW | RTLD_LOCAL)); if (!gles1_lib) { LOG(VERBOSE) << "Failed to dlopen " << kGles1Lib << "."; return; } LOG(VERBOSE) << "Loaded " << kGles1Lib << "."; availability->has_gles1 = true; } void PopulateGles2Availability(GraphicsAvailability* availability) { ManagedLibrary gles2_lib(dlopen(kGles2Lib, RTLD_NOW | RTLD_LOCAL)); if (!gles2_lib) { LOG(VERBOSE) << "Failed to dlopen " << kGles2Lib << "."; return; } LOG(VERBOSE) << "Loaded " << kGles2Lib << "."; availability->has_gles2 = true; } void PopulateEglAvailability(GraphicsAvailability* availability) { ManagedLibrary egllib(dlopen(kEglLib, RTLD_NOW | RTLD_LOCAL)); if (!egllib) { LOG(VERBOSE) << "Failed to dlopen " << kEglLib << "."; return; } LOG(VERBOSE) << "Loaded " << kEglLib << "."; availability->has_egl = true; PFNEGLGETPROCADDRESSPROC eglGetProcAddress = reinterpret_cast( dlsym(egllib.get(), "eglGetProcAddress")); if (eglGetProcAddress == nullptr) { LOG(VERBOSE) << "Failed to find function eglGetProcAddress."; return; } LOG(VERBOSE) << "Loaded eglGetProcAddress."; // Some implementations have it so that eglGetProcAddress is only for // loading EXT functions. auto EglLoadFunction = [&](const char* name) { void* func = dlsym(egllib.get(), name); if (func == NULL) { func = reinterpret_cast(eglGetProcAddress(name)); } return func; }; PFNEGLGETERRORPROC eglGetError = reinterpret_cast(EglLoadFunction("eglGetError")); if (eglGetError == nullptr) { LOG(VERBOSE) << "Failed to find function eglGetError."; return; } LOG(VERBOSE) << "Loaded eglGetError."; PFNEGLGETDISPLAYPROC eglGetDisplay = reinterpret_cast(EglLoadFunction("eglGetDisplay")); if (eglGetDisplay == nullptr) { LOG(VERBOSE) << "Failed to find function eglGetDisplay."; return; } LOG(VERBOSE) << "Loaded eglGetDisplay."; EGLDisplay default_display = eglGetDisplay(EGL_DEFAULT_DISPLAY); if (default_display == EGL_NO_DISPLAY) { LOG(VERBOSE) << "Failed to get default display. " << eglGetError(); return; } LOG(VERBOSE) << "Found default display."; availability->has_egl_default_display = true; PFNEGLINITIALIZEPROC eglInitialize = reinterpret_cast(EglLoadFunction("eglInitialize")); if (eglInitialize == nullptr) { LOG(VERBOSE) << "Failed to find function eglQueryString"; return; } EGLint client_version_major = 0; EGLint client_version_minor = 0; if (eglInitialize(default_display, &client_version_major, &client_version_minor) != EGL_TRUE) { LOG(VERBOSE) << "Failed to initialize default display."; return; } LOG(VERBOSE) << "Initialized default display."; PFNEGLQUERYSTRINGPROC eglQueryString = reinterpret_cast(EglLoadFunction("eglQueryString")); if (eglQueryString == nullptr) { LOG(VERBOSE) << "Failed to find function eglQueryString"; return; } LOG(VERBOSE) << "Loaded eglQueryString."; std::string client_extensions; if (client_version_major >= 1 && client_version_minor >= 5) { client_extensions = eglQueryString(EGL_NO_DISPLAY, EGL_EXTENSIONS); } availability->egl_client_extensions = client_extensions; EGLDisplay display = EGL_NO_DISPLAY; if (client_extensions.find("EGL_EXT_platform_base") != std::string::npos) { LOG(VERBOSE) << "Client extension EGL_EXT_platform_base is supported."; PFNEGLGETPLATFORMDISPLAYEXTPROC eglGetPlatformDisplayEXT = reinterpret_cast( EglLoadFunction("eglGetPlatformDisplayEXT")); if (eglGetPlatformDisplayEXT == nullptr) { LOG(VERBOSE) << "Failed to find function eglGetPlatformDisplayEXT"; return; } display = eglGetPlatformDisplayEXT(EGL_PLATFORM_SURFACELESS_MESA, EGL_DEFAULT_DISPLAY, NULL); } else { LOG(VERBOSE) << "Failed to find client extension EGL_EXT_platform_base."; } if (display == EGL_NO_DISPLAY) { LOG(VERBOSE) << "Failed to get EGL_PLATFORM_SURFACELESS_MESA display..." << "failing back to EGL_DEFAULT_DISPLAY display."; display = default_display; } if (display == EGL_NO_DISPLAY) { LOG(VERBOSE) << "Failed to find display."; return; } if (eglInitialize(display, &client_version_major, &client_version_minor) != EGL_TRUE) { LOG(VERBOSE) << "Failed to initialize surfaceless display."; return; } LOG(VERBOSE) << "Initialized surfaceless display."; const std::string version_string = eglQueryString(display, EGL_VERSION); if (version_string.empty()) { LOG(VERBOSE) << "Failed to query client version."; return; } LOG(VERBOSE) << "Found version: " << version_string; availability->egl_version = version_string; const std::string vendor_string = eglQueryString(display, EGL_VENDOR); if (vendor_string.empty()) { LOG(VERBOSE) << "Failed to query vendor."; return; } LOG(VERBOSE) << "Found vendor: " << vendor_string; availability->egl_vendor = vendor_string; const std::string extensions_string = eglQueryString(display, EGL_EXTENSIONS); if (extensions_string.empty()) { LOG(VERBOSE) << "Failed to query extensions."; return; } LOG(VERBOSE) << "Found extensions: " << extensions_string; availability->egl_extensions = extensions_string; if (extensions_string.find(kSurfacelessContextExt) == std::string::npos) { LOG(VERBOSE) << "Failed to find extension EGL_KHR_surfaceless_context."; return; } const std::string display_apis_string = eglQueryString(display, EGL_CLIENT_APIS); if (display_apis_string.empty()) { LOG(VERBOSE) << "Failed to query display apis."; return; } LOG(VERBOSE) << "Found display apis: " << display_apis_string; PFNEGLBINDAPIPROC eglBindAPI = reinterpret_cast(EglLoadFunction("eglBindAPI")); if (eglBindAPI == nullptr) { LOG(VERBOSE) << "Failed to find function eglBindAPI"; return; } LOG(VERBOSE) << "Loaded eglBindAPI."; if (eglBindAPI(EGL_OPENGL_ES_API) == EGL_FALSE) { LOG(VERBOSE) << "Failed to bind GLES API."; return; } LOG(VERBOSE) << "Bound GLES API."; PFNEGLCHOOSECONFIGPROC eglChooseConfig = reinterpret_cast( EglLoadFunction("eglChooseConfig")); if (eglChooseConfig == nullptr) { LOG(VERBOSE) << "Failed to find function eglChooseConfig"; return; } LOG(VERBOSE) << "Loaded eglChooseConfig."; const EGLint framebuffer_config_attributes[] = { EGL_SURFACE_TYPE, EGL_PBUFFER_BIT, EGL_RENDERABLE_TYPE, EGL_OPENGL_ES2_BIT, EGL_RED_SIZE, 1, EGL_GREEN_SIZE, 1, EGL_BLUE_SIZE, 1, EGL_ALPHA_SIZE, 0, EGL_NONE, }; EGLConfig framebuffer_config; EGLint num_framebuffer_configs = 0; if (eglChooseConfig(display, framebuffer_config_attributes, &framebuffer_config, 1, &num_framebuffer_configs) != EGL_TRUE) { LOG(VERBOSE) << "Failed to find matching framebuffer config."; return; } LOG(VERBOSE) << "Found matching framebuffer config."; PFNEGLCREATECONTEXTPROC eglCreateContext = reinterpret_cast( EglLoadFunction("eglCreateContext")); if (eglCreateContext == nullptr) { LOG(VERBOSE) << "Failed to find function eglCreateContext"; return; } LOG(VERBOSE) << "Loaded eglCreateContext."; PFNEGLDESTROYCONTEXTPROC eglDestroyContext = reinterpret_cast( EglLoadFunction("eglDestroyContext")); if (eglDestroyContext == nullptr) { LOG(VERBOSE) << "Failed to find function eglDestroyContext"; return; } LOG(VERBOSE) << "Loaded eglDestroyContext."; const EGLint context_attributes[] = { EGL_CONTEXT_CLIENT_VERSION, 2, EGL_NONE }; EGLContext context = eglCreateContext(display, framebuffer_config, EGL_NO_CONTEXT, context_attributes); if (context == EGL_NO_CONTEXT) { LOG(VERBOSE) << "Failed to create EGL context."; return; } LOG(VERBOSE) << "Created EGL context."; Closer context_closer([&]() { eglDestroyContext(display, context); }); PFNEGLMAKECURRENTPROC eglMakeCurrent = reinterpret_cast(EglLoadFunction("eglMakeCurrent")); if (eglMakeCurrent == nullptr) { LOG(VERBOSE) << "Failed to find function eglMakeCurrent"; return; } LOG(VERBOSE) << "Loaded eglMakeCurrent."; if (eglMakeCurrent(display, EGL_NO_SURFACE, EGL_NO_SURFACE, context) != EGL_TRUE) { LOG(VERBOSE) << "Failed to make EGL context current."; return; } LOG(VERBOSE) << "Make EGL context current."; availability->has_egl_surfaceless_with_gles = true; } void PopulateVulkanAvailability(GraphicsAvailability* availability) { ManagedLibrary vklib(dlopen(kVulkanLib, RTLD_NOW | RTLD_LOCAL)); if (!vklib) { LOG(VERBOSE) << "Failed to dlopen " << kVulkanLib << "."; return; } LOG(VERBOSE) << "Loaded " << kVulkanLib << "."; availability->has_vulkan = true; uint32_t instance_version = 0; PFN_vkGetInstanceProcAddr vkGetInstanceProcAddr = reinterpret_cast( dlsym(vklib.get(), "vkGetInstanceProcAddr")); if (vkGetInstanceProcAddr == nullptr) { LOG(VERBOSE) << "Failed to find symbol vkGetInstanceProcAddr."; return; } PFN_vkEnumerateInstanceVersion vkEnumerateInstanceVersion = reinterpret_cast( dlsym(vklib.get(), "vkEnumerateInstanceVersion")); if (vkEnumerateInstanceVersion == nullptr || vkEnumerateInstanceVersion(&instance_version) != VK_SUCCESS) { instance_version = VK_API_VERSION_1_0; } PFN_vkCreateInstance vkCreateInstance = reinterpret_cast( vkGetInstanceProcAddr(VK_NULL_HANDLE, "vkCreateInstance")); if (vkCreateInstance == nullptr) { LOG(VERBOSE) << "Failed to get function vkCreateInstance."; return; } VkApplicationInfo application_info; application_info.sType = VK_STRUCTURE_TYPE_APPLICATION_INFO; application_info.pNext = nullptr; application_info.pApplicationName = ""; application_info.applicationVersion = 1; application_info.pEngineName = ""; application_info.engineVersion = 1; application_info.apiVersion = instance_version; VkInstanceCreateInfo instance_create_info = {}; instance_create_info.sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO; instance_create_info.pNext = nullptr; instance_create_info.flags = 0; instance_create_info.pApplicationInfo = &application_info; instance_create_info.enabledLayerCount = 0; instance_create_info.ppEnabledLayerNames = nullptr; instance_create_info.enabledExtensionCount = 0; instance_create_info.ppEnabledExtensionNames = nullptr; VkInstance instance = VK_NULL_HANDLE; VkResult result = vkCreateInstance(&instance_create_info, nullptr, &instance); if (result != VK_SUCCESS) { if (result == VK_ERROR_OUT_OF_HOST_MEMORY) { LOG(VERBOSE) << "Failed to create Vulkan instance: " << "VK_ERROR_OUT_OF_HOST_MEMORY."; } else if (result == VK_ERROR_OUT_OF_DEVICE_MEMORY) { LOG(VERBOSE) << "Failed to create Vulkan instance: " << "VK_ERROR_OUT_OF_DEVICE_MEMORY."; } else if (result == VK_ERROR_INITIALIZATION_FAILED) { LOG(VERBOSE) << "Failed to create Vulkan instance: " << "VK_ERROR_INITIALIZATION_FAILED."; } else if (result == VK_ERROR_LAYER_NOT_PRESENT) { LOG(VERBOSE) << "Failed to create Vulkan instance: " << "VK_ERROR_LAYER_NOT_PRESENT."; } else if (result == VK_ERROR_EXTENSION_NOT_PRESENT) { LOG(VERBOSE) << "Failed to create Vulkan instance: " << "VK_ERROR_EXTENSION_NOT_PRESENT."; } else if (result == VK_ERROR_INCOMPATIBLE_DRIVER) { LOG(VERBOSE) << "Failed to create Vulkan instance: " << "VK_ERROR_INCOMPATIBLE_DRIVER."; } else { LOG(VERBOSE) << "Failed to create Vulkan instance."; } return; } PFN_vkDestroyInstance vkDestroyInstance = reinterpret_cast( vkGetInstanceProcAddr(instance, "vkDestroyInstance")); if (vkDestroyInstance == nullptr) { LOG(VERBOSE) << "Failed to get function vkDestroyInstance."; return; } Closer instancecloser([&]() {vkDestroyInstance(instance, nullptr); }); PFN_vkEnumeratePhysicalDevices vkEnumeratePhysicalDevices = reinterpret_cast( vkGetInstanceProcAddr(instance, "vkEnumeratePhysicalDevices")); if (vkEnumeratePhysicalDevices == nullptr) { LOG(VERBOSE) << "Failed to " << "vkGetInstanceProcAddr(vkEnumeratePhysicalDevices)."; return; } PFN_vkGetPhysicalDeviceProperties vkGetPhysicalDeviceProperties = reinterpret_cast( vkGetInstanceProcAddr(instance, "vkGetPhysicalDeviceProperties")); if (vkGetPhysicalDeviceProperties == nullptr) { LOG(VERBOSE) << "Failed to " << "vkGetInstanceProcAddr(vkGetPhysicalDeviceProperties)."; return; } auto vkEnumerateDeviceExtensionProperties = reinterpret_cast( vkGetInstanceProcAddr(instance, "vkEnumerateDeviceExtensionProperties")); if (vkEnumerateDeviceExtensionProperties == nullptr) { LOG(VERBOSE) << "Failed to " << "vkGetInstanceProcAddr(" << "vkEnumerateDeviceExtensionProperties" << ")."; return; } uint32_t device_count = 0; result = vkEnumeratePhysicalDevices(instance, &device_count, nullptr); if (result != VK_SUCCESS) { if (result == VK_INCOMPLETE) { LOG(VERBOSE) << "Failed to enumerate physical device count: " << "VK_INCOMPLETE"; } else if (result == VK_ERROR_OUT_OF_HOST_MEMORY) { LOG(VERBOSE) << "Failed to enumerate physical device count: " << "VK_ERROR_OUT_OF_HOST_MEMORY"; } else if (result == VK_ERROR_OUT_OF_DEVICE_MEMORY) { LOG(VERBOSE) << "Failed to enumerate physical device count: " << "VK_ERROR_OUT_OF_DEVICE_MEMORY"; } else if (result == VK_ERROR_INITIALIZATION_FAILED) { LOG(VERBOSE) << "Failed to enumerate physical device count: " << "VK_ERROR_INITIALIZATION_FAILED"; } else { LOG(VERBOSE) << "Failed to enumerate physical device count."; } return; } if (device_count == 0) { LOG(VERBOSE) << "No physical devices present."; return; } std::vector devices(device_count, VK_NULL_HANDLE); result = vkEnumeratePhysicalDevices(instance, &device_count, devices.data()); if (result != VK_SUCCESS) { LOG(VERBOSE) << "Failed to enumerate physical devices."; return; } for (VkPhysicalDevice device : devices) { VkPhysicalDeviceProperties device_properties = {}; vkGetPhysicalDeviceProperties(device, &device_properties); uint32_t device_extensions_count = 0; vkEnumerateDeviceExtensionProperties(device, nullptr, &device_extensions_count, nullptr); std::vector device_extensions; device_extensions.resize(device_extensions_count); vkEnumerateDeviceExtensionProperties(device, nullptr, &device_extensions_count, device_extensions.data()); std::vector device_extensions_strings; for (const VkExtensionProperties& device_extension : device_extensions) { device_extensions_strings.push_back(device_extension.extensionName); } std::string device_extensions_string = android::base::Join(device_extensions_strings, ' '); if (device_properties.deviceType == VK_PHYSICAL_DEVICE_TYPE_DISCRETE_GPU) { availability->has_discrete_gpu = true; availability->discrete_gpu_device_name = device_properties.deviceName; availability->discrete_gpu_device_extensions = device_extensions_string; break; } } } GraphicsAvailability GetGraphicsAvailability() { GraphicsAvailability availability; PopulateEglAvailability(&availability); PopulateGlAvailability(&availability); PopulateGles1Availability(&availability); PopulateGles2Availability(&availability); PopulateVulkanAvailability(&availability); return availability; } } // namespace bool ShouldEnableAcceleratedRendering( const GraphicsAvailability& availability) { return availability.has_egl && availability.has_egl_surfaceless_with_gles && availability.has_discrete_gpu; } // Runs GetGraphicsAvailability() inside of a subprocess first to ensure that // GetGraphicsAvailability() can complete successfully without crashing // assemble_cvd. Configurations such as GCE instances without a GPU but with GPU // drivers for example have seen crashes. GraphicsAvailability GetGraphicsAvailabilityWithSubprocessCheck() { pid_t pid = fork(); if (pid == 0) { GetGraphicsAvailability(); std::exit(0); } int status; if (waitpid(pid, &status, 0) != pid) { PLOG(ERROR) << "Failed to wait for graphics check subprocess"; return GraphicsAvailability{}; } if (WIFEXITED(status) && WEXITSTATUS(status) == 0) { return GetGraphicsAvailability(); } LOG(VERBOSE) << "Subprocess for detect_graphics failed with " << status; return GraphicsAvailability{}; } std::ostream& operator<<(std::ostream& stream, const GraphicsAvailability& availability) { std::ios_base::fmtflags flags_backup(stream.flags()); stream << std::boolalpha; stream << "Graphics Availability:\n"; stream << "OpenGL available: " << availability.has_gl << "\n"; stream << "OpenGL ES1 available: " << availability.has_gles1 << "\n"; stream << "OpenGL ES2 available: " << availability.has_gles2 << "\n"; stream << "EGL available: " << availability.has_egl << "\n"; stream << "EGL client extensions: " << availability.egl_client_extensions << "\n"; stream << "EGL default display available: " << availability.has_egl_default_display << "\n"; stream << "EGL display vendor: " << availability.egl_vendor << "\n"; stream << "EGL display version: " << availability.egl_version << "\n"; stream << "EGL display extensions: " << availability.egl_extensions << "\n"; stream << "EGL surfaceless display with GLES: " << availability.has_egl_surfaceless_with_gles << "\n"; stream << "Vulkan available: " << availability.has_vulkan << "\n"; stream << "Vulkan discrete GPU detected: " << availability.has_discrete_gpu << "\n"; if (availability.has_discrete_gpu) { stream << "Vulkan discrete GPU device name: " << availability.discrete_gpu_device_name << "\n"; stream << "Vulkan discrete GPU device extensions: " << availability.discrete_gpu_device_extensions << "\n"; } stream.flags(flags_backup); return stream; } } // namespace cuttlefish