diff options
Diffstat (limited to 'toolkit/xre/glxtest.cpp')
-rw-r--r-- | toolkit/xre/glxtest.cpp | 1281 |
1 files changed, 1281 insertions, 0 deletions
diff --git a/toolkit/xre/glxtest.cpp b/toolkit/xre/glxtest.cpp new file mode 100644 index 0000000000..e2b8c09e14 --- /dev/null +++ b/toolkit/xre/glxtest.cpp @@ -0,0 +1,1281 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * vim: sw=2 ts=8 et : + */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +////////////////////////////////////////////////////////////////////////////// +// +// Explanation: See bug 639842. Safely getting GL driver info on X11 is hard, +// because the only way to do that is to create a GL context and call +// glGetString(), but with bad drivers, just creating a GL context may crash. +// +// This file implements the idea to do that in a separate process. +// +// The only non-static function here is fire_glxtest_process(). It creates a +// pipe, publishes its 'read' end as the mozilla::widget::glxtest_pipe global +// variable, forks, and runs that GLX probe in the child process, which runs the +// childgltest() static function. This creates a X connection, a GLX context, +// calls glGetString, and writes that to the 'write' end of the pipe. + +#include <cstdio> +#include <cstdlib> +#include <dlfcn.h> +#include <fcntl.h> +#include <unistd.h> + +#if defined(MOZ_ASAN) || defined(FUZZING) +# include <signal.h> +#endif + +#include "mozilla/Unused.h" +#include "nsAppRunner.h" // for IsWaylandEnabled on IsX11EGLEnabled +#include "stdint.h" + +#ifdef __SUNPRO_CC +# include <stdio.h> +#endif + +#ifdef MOZ_X11 +# include "X11/Xlib.h" +# include "X11/Xutil.h" +# include <X11/extensions/Xrandr.h> +#endif + +#ifdef MOZ_WAYLAND +# include "mozilla/widget/mozwayland.h" +# include "mozilla/widget/xdg-output-unstable-v1-client-protocol.h" +# include "prlink.h" +# include "va/va.h" +#endif + +#ifdef MOZ_X11 +// stuff from glx.h +typedef struct __GLXcontextRec* GLXContext; +typedef XID GLXPixmap; +typedef XID GLXDrawable; +/* GLX 1.3 and later */ +typedef struct __GLXFBConfigRec* GLXFBConfig; +typedef XID GLXFBConfigID; +typedef XID GLXContextID; +typedef XID GLXWindow; +typedef XID GLXPbuffer; +# define GLX_RGBA 4 +# define GLX_RED_SIZE 8 +# define GLX_GREEN_SIZE 9 +# define GLX_BLUE_SIZE 10 +# define GLX_DOUBLEBUFFER 5 +#endif + +// stuff from gl.h +typedef uint8_t GLubyte; +typedef uint32_t GLenum; +#define GL_VENDOR 0x1F00 +#define GL_RENDERER 0x1F01 +#define GL_VERSION 0x1F02 + +// GLX_MESA_query_renderer +// clang-format off +#define GLX_RENDERER_VENDOR_ID_MESA 0x8183 +#define GLX_RENDERER_DEVICE_ID_MESA 0x8184 +#define GLX_RENDERER_VERSION_MESA 0x8185 +#define GLX_RENDERER_ACCELERATED_MESA 0x8186 +#define GLX_RENDERER_VIDEO_MEMORY_MESA 0x8187 +#define GLX_RENDERER_UNIFIED_MEMORY_ARCHITECTURE_MESA 0x8188 +#define GLX_RENDERER_PREFERRED_PROFILE_MESA 0x8189 +#define GLX_RENDERER_OPENGL_CORE_PROFILE_VERSION_MESA 0x818A +#define GLX_RENDERER_OPENGL_COMPATIBILITY_PROFILE_VERSION_MESA 0x818B +#define GLX_RENDERER_OPENGL_ES_PROFILE_VERSION_MESA 0x818C +#define GLX_RENDERER_OPENGL_ES2_PROFILE_VERSION_MESA 0x818D +#define GLX_RENDERER_ID_MESA 0x818E +// clang-format on + +// stuff from egl.h +typedef intptr_t EGLAttrib; +typedef int EGLBoolean; +typedef void* EGLConfig; +typedef void* EGLContext; +typedef void* EGLDeviceEXT; +typedef void* EGLDisplay; +typedef unsigned int EGLenum; +typedef int EGLint; +typedef void* EGLNativeDisplayType; +typedef void* EGLSurface; +typedef void* (*PFNEGLGETPROCADDRESS)(const char*); + +#define EGL_NO_CONTEXT nullptr +#define EGL_NO_SURFACE nullptr +#define EGL_FALSE 0 +#define EGL_TRUE 1 +#define EGL_OPENGL_ES2_BIT 0x0004 +#define EGL_BLUE_SIZE 0x3022 +#define EGL_GREEN_SIZE 0x3023 +#define EGL_RED_SIZE 0x3024 +#define EGL_NONE 0x3038 +#define EGL_RENDERABLE_TYPE 0x3040 +#define EGL_VENDOR 0x3053 +#define EGL_EXTENSIONS 0x3055 +#define EGL_CONTEXT_MAJOR_VERSION 0x3098 +#define EGL_OPENGL_ES_API 0x30A0 +#define EGL_OPENGL_API 0x30A2 +#define EGL_DEVICE_EXT 0x322C +#define EGL_DRM_DEVICE_FILE_EXT 0x3233 +#define EGL_DRM_RENDER_NODE_FILE_EXT 0x3377 + +// stuff from xf86drm.h +#define DRM_NODE_RENDER 2 +#define DRM_NODE_MAX 3 + +typedef struct _drmPciDeviceInfo { + uint16_t vendor_id; + uint16_t device_id; + uint16_t subvendor_id; + uint16_t subdevice_id; + uint8_t revision_id; +} drmPciDeviceInfo, *drmPciDeviceInfoPtr; + +typedef struct _drmDevice { + char** nodes; + int available_nodes; + int bustype; + union { + void* pci; + void* usb; + void* platform; + void* host1x; + } businfo; + union { + drmPciDeviceInfoPtr pci; + void* usb; + void* platform; + void* host1x; + } deviceinfo; +} drmDevice, *drmDevicePtr; + +// Open libGL and load needed symbols +#if defined(__OpenBSD__) || defined(__NetBSD__) +# define LIBGL_FILENAME "libGL.so" +# define LIBGLES_FILENAME "libGLESv2.so" +# define LIBEGL_FILENAME "libEGL.so" +# define LIBDRM_FILENAME "libdrm.so" +#else +# define LIBGL_FILENAME "libGL.so.1" +# define LIBGLES_FILENAME "libGLESv2.so.2" +# define LIBEGL_FILENAME "libEGL.so.1" +# define LIBDRM_FILENAME "libdrm.so.2" +#endif + +#define EXIT_FAILURE_BUFFER_TOO_SMALL 2 + +namespace mozilla { +namespace widget { +// the read end of the pipe, which will be used by GfxInfo +extern int glxtest_pipe; +// the PID of the glxtest process, to pass to waitpid() +extern pid_t glxtest_pid; +} // namespace widget +} // namespace mozilla + +// bits to use decoding childvaapitest() return values. +constexpr int CODEC_HW_H264 = 1 << 4; +constexpr int CODEC_HW_VP8 = 1 << 5; +constexpr int CODEC_HW_VP9 = 1 << 6; +constexpr int CODEC_HW_AV1 = 1 << 7; + +// the write end of the pipe, which we're going to write to +static int write_end_of_the_pipe = -1; + +// our buffer, size and used length +static char* glxtest_buf = nullptr; +static int glxtest_bufsize = 0; +static int glxtest_length = 0; + +static char* glxtest_render_device_path = nullptr; + +// C++ standard collides with C standard in that it doesn't allow casting void* +// to function pointer types. So the work-around is to convert first to size_t. +// http://www.trilithium.com/johan/2004/12/problem-with-dlsym/ +template <typename func_ptr_type> +static func_ptr_type cast(void* ptr) { + return reinterpret_cast<func_ptr_type>(reinterpret_cast<size_t>(ptr)); +} + +static void record_value(const char* format, ...) { + // Don't add more if the buffer is full. + if (glxtest_bufsize <= glxtest_length) { + return; + } + + // Append the new values to the buffer, not to exceed the remaining space. + int remaining = glxtest_bufsize - glxtest_length; + va_list args; + va_start(args, format); + int max_added = + vsnprintf(glxtest_buf + glxtest_length, remaining, format, args); + va_end(args); + + // snprintf returns how many char it could have added, not how many it added. + // It is important to get this right since it will control how many chars we + // will attempt to write to the pipe fd. + if (max_added > remaining) { + glxtest_length += remaining; + } else { + glxtest_length += max_added; + } +} + +static void record_error(const char* str) { record_value("ERROR\n%s\n", str); } + +static void record_warning(const char* str) { + record_value("WARNING\n%s\n", str); +} + +static void record_flush() { + mozilla::Unused << write(write_end_of_the_pipe, glxtest_buf, glxtest_length); +} + +#ifdef MOZ_X11 +static int x_error_handler(Display*, XErrorEvent* ev) { + record_value( + "ERROR\nX error, error_code=%d, " + "request_code=%d, minor_code=%d\n", + ev->error_code, ev->request_code, ev->minor_code); + record_flush(); + _exit(EXIT_FAILURE); + return 0; +} +#endif + +// childgltest is declared inside extern "C" so that the name is not mangled. +// The name is used in build/valgrind/x86_64-pc-linux-gnu.sup to suppress +// memory leak errors because we run it inside a short lived fork and we don't +// care about leaking memory +extern "C" { + +static void close_logging() { + // we want to redirect to /dev/null stdout, stderr, and while we're at it, + // any PR logging file descriptors. To that effect, we redirect all positive + // file descriptors up to what open() returns here. In particular, 1 is stdout + // and 2 is stderr. + int fd = open("/dev/null", O_WRONLY); + for (int i = 1; i < fd; i++) { + dup2(fd, i); + } + close(fd); + + if (getenv("MOZ_AVOID_OPENGL_ALTOGETHER")) { + const char* msg = "ERROR\nMOZ_AVOID_OPENGL_ALTOGETHER envvar set"; + mozilla::Unused << write(write_end_of_the_pipe, msg, strlen(msg)); + exit(EXIT_FAILURE); + } +} + +#define PCI_FILL_IDENT 0x0001 +#define PCI_FILL_CLASS 0x0020 +#define PCI_BASE_CLASS_DISPLAY 0x03 + +static void get_pci_status() { + if (access("/sys/bus/pci/", F_OK) != 0 && + access("/sys/bus/pci_express/", F_OK) != 0) { + record_warning("cannot access /sys/bus/pci"); + return; + } + + void* libpci = dlopen("libpci.so.3", RTLD_LAZY); + if (!libpci) { + libpci = dlopen("libpci.so", RTLD_LAZY); + } + if (!libpci) { + record_warning("libpci missing"); + return; + } + + typedef struct pci_dev { + struct pci_dev* next; + uint16_t domain_16; + uint8_t bus, dev, func; + unsigned int known_fields; + uint16_t vendor_id, device_id; + uint16_t device_class; + } pci_dev; + + typedef struct pci_access { + unsigned int method; + int writeable; + int buscentric; + char* id_file_name; + int free_id_name; + int numeric_ids; + unsigned int id_lookup_mode; + int debugging; + void* error; + void* warning; + void* debug; + pci_dev* devices; + } pci_access; + + typedef pci_access* (*PCIALLOC)(void); + PCIALLOC pci_alloc = cast<PCIALLOC>(dlsym(libpci, "pci_alloc")); + + typedef void (*PCIINIT)(pci_access*); + PCIINIT pci_init = cast<PCIINIT>(dlsym(libpci, "pci_init")); + + typedef void (*PCICLEANUP)(pci_access*); + PCICLEANUP pci_cleanup = cast<PCICLEANUP>(dlsym(libpci, "pci_cleanup")); + + typedef void (*PCISCANBUS)(pci_access*); + PCISCANBUS pci_scan_bus = cast<PCISCANBUS>(dlsym(libpci, "pci_scan_bus")); + + typedef void (*PCIFILLINFO)(pci_dev*, int); + PCIFILLINFO pci_fill_info = cast<PCIFILLINFO>(dlsym(libpci, "pci_fill_info")); + + if (!pci_alloc || !pci_cleanup || !pci_scan_bus || !pci_fill_info) { + dlclose(libpci); + record_warning("libpci missing methods"); + return; + } + + pci_access* pacc = pci_alloc(); + if (!pacc) { + dlclose(libpci); + record_warning("libpci alloc failed"); + return; + } + + pci_init(pacc); + pci_scan_bus(pacc); + + for (pci_dev* dev = pacc->devices; dev; dev = dev->next) { + pci_fill_info(dev, PCI_FILL_IDENT | PCI_FILL_CLASS); + if (dev->device_class >> 8 == PCI_BASE_CLASS_DISPLAY && dev->vendor_id && + dev->device_id) { + record_value("PCI_VENDOR_ID\n0x%04x\nPCI_DEVICE_ID\n0x%04x\n", + dev->vendor_id, dev->device_id); + } + } + + pci_cleanup(pacc); + dlclose(libpci); +} + +#ifdef MOZ_WAYLAND +static void set_render_device_path(const char* render_device_path) { + record_value("DRM_RENDERDEVICE\n%s\n", render_device_path); + glxtest_render_device_path = strdup(render_device_path); +} + +static bool device_has_name(const drmDevice* device, const char* name) { + for (size_t i = 0; i < DRM_NODE_MAX; i++) { + if (!(device->available_nodes & (1 << i))) { + continue; + } + if (strcmp(device->nodes[i], name) == 0) { + return true; + } + } + return false; +} + +static bool get_render_name(const char* name) { + void* libdrm = dlopen(LIBDRM_FILENAME, RTLD_LAZY); + if (!libdrm) { + record_warning("Failed to open libdrm"); + return false; + } + + typedef int (*DRMGETDEVICES2)(uint32_t, drmDevicePtr*, int); + DRMGETDEVICES2 drmGetDevices2 = + cast<DRMGETDEVICES2>(dlsym(libdrm, "drmGetDevices2")); + + typedef void (*DRMFREEDEVICE)(drmDevicePtr*); + DRMFREEDEVICE drmFreeDevice = + cast<DRMFREEDEVICE>(dlsym(libdrm, "drmFreeDevice")); + + if (!drmGetDevices2 || !drmFreeDevice) { + record_warning( + "libdrm missing methods for drmGetDevices2 or drmFreeDevice"); + dlclose(libdrm); + return false; + } + + uint32_t flags = 0; + int devices_len = drmGetDevices2(flags, nullptr, 0); + if (devices_len < 0) { + record_warning("drmGetDevices2 failed"); + dlclose(libdrm); + return false; + } + drmDevice** devices = (drmDevice**)calloc(devices_len, sizeof(drmDevice*)); + if (!devices) { + record_warning("Allocation error"); + dlclose(libdrm); + return false; + } + devices_len = drmGetDevices2(flags, devices, devices_len); + if (devices_len < 0) { + free(devices); + record_warning("drmGetDevices2 failed"); + dlclose(libdrm); + return false; + } + + const drmDevice* match = nullptr; + for (int i = 0; i < devices_len; i++) { + if (device_has_name(devices[i], name)) { + match = devices[i]; + break; + } + } + + // Fallback path for split kms/render devices - if only one drm render node + // exists it's most likely the one we're looking for. + if (match && !(match->available_nodes & (1 << DRM_NODE_RENDER))) { + match = nullptr; + for (int i = 0; i < devices_len; i++) { + if (devices[i]->available_nodes & (1 << DRM_NODE_RENDER)) { + if (!match) { + match = devices[i]; + } else { + // more than one candidate found, stop trying. + match = nullptr; + break; + } + } + } + if (match) { + record_warning( + "DRM render node not clearly detectable. Falling back to using the " + "only one that was found."); + } else { + record_warning("DRM device has no render node"); + } + } + + bool result = false; + if (!match) { + record_warning("Cannot find DRM device"); + } else { + set_render_device_path(match->nodes[DRM_NODE_RENDER]); + record_value( + "MESA_VENDOR_ID\n0x%04x\n" + "MESA_DEVICE_ID\n0x%04x\n", + match->deviceinfo.pci->vendor_id, match->deviceinfo.pci->device_id); + result = true; + } + + for (int i = 0; i < devices_len; i++) { + drmFreeDevice(&devices[i]); + } + free(devices); + + dlclose(libdrm); + return result; +} +#endif + +static bool get_egl_gl_status(EGLDisplay dpy, + PFNEGLGETPROCADDRESS eglGetProcAddress) { + typedef EGLBoolean (*PFNEGLCHOOSECONFIGPROC)( + EGLDisplay dpy, EGLint const* attrib_list, EGLConfig* configs, + EGLint config_size, EGLint* num_config); + PFNEGLCHOOSECONFIGPROC eglChooseConfig = + cast<PFNEGLCHOOSECONFIGPROC>(eglGetProcAddress("eglChooseConfig")); + + typedef EGLBoolean (*PFNEGLBINDAPIPROC)(EGLint api); + PFNEGLBINDAPIPROC eglBindAPI = + cast<PFNEGLBINDAPIPROC>(eglGetProcAddress("eglBindAPI")); + + typedef EGLContext (*PFNEGLCREATECONTEXTPROC)( + EGLDisplay dpy, EGLConfig config, EGLContext share_context, + EGLint const* attrib_list); + PFNEGLCREATECONTEXTPROC eglCreateContext = + cast<PFNEGLCREATECONTEXTPROC>(eglGetProcAddress("eglCreateContext")); + + typedef EGLBoolean (*PFNEGLMAKECURRENTPROC)( + EGLDisplay dpy, EGLSurface draw, EGLSurface read, EGLContext context); + PFNEGLMAKECURRENTPROC eglMakeCurrent = + cast<PFNEGLMAKECURRENTPROC>(eglGetProcAddress("eglMakeCurrent")); + + typedef const char* (*PFNEGLQUERYDEVICESTRINGEXTPROC)(EGLDeviceEXT device, + EGLint name); + PFNEGLQUERYDEVICESTRINGEXTPROC eglQueryDeviceStringEXT = + cast<PFNEGLQUERYDEVICESTRINGEXTPROC>( + eglGetProcAddress("eglQueryDeviceStringEXT")); + + typedef EGLBoolean (*PFNEGLQUERYDISPLAYATTRIBEXTPROC)( + EGLDisplay dpy, EGLint name, EGLAttrib * value); + PFNEGLQUERYDISPLAYATTRIBEXTPROC eglQueryDisplayAttribEXT = + cast<PFNEGLQUERYDISPLAYATTRIBEXTPROC>( + eglGetProcAddress("eglQueryDisplayAttribEXT")); + + if (!eglChooseConfig || !eglCreateContext || !eglMakeCurrent || + !eglQueryDeviceStringEXT) { + record_warning("libEGL missing methods for GL test"); + return false; + } + + typedef GLubyte* (*PFNGLGETSTRING)(GLenum); + PFNGLGETSTRING glGetString = + cast<PFNGLGETSTRING>(eglGetProcAddress("glGetString")); + +#if defined(__aarch64__) + bool useGles = true; +#else + bool useGles = false; +#endif + + std::vector<EGLint> attribs; + attribs.push_back(EGL_RED_SIZE); + attribs.push_back(8); + attribs.push_back(EGL_GREEN_SIZE); + attribs.push_back(8); + attribs.push_back(EGL_BLUE_SIZE); + attribs.push_back(8); + if (useGles) { + attribs.push_back(EGL_RENDERABLE_TYPE); + attribs.push_back(EGL_OPENGL_ES2_BIT); + } + attribs.push_back(EGL_NONE); + + EGLConfig config; + EGLint num_config; + if (eglChooseConfig(dpy, attribs.data(), &config, 1, &num_config) == + EGL_FALSE) { + record_warning("eglChooseConfig returned an error"); + return false; + } + + EGLenum api = useGles ? EGL_OPENGL_ES_API : EGL_OPENGL_API; + if (eglBindAPI(api) == EGL_FALSE) { + record_warning("eglBindAPI returned an error"); + return false; + } + + EGLint ctx_attrs[] = {EGL_CONTEXT_MAJOR_VERSION, 3, EGL_NONE}; + EGLContext ectx = eglCreateContext(dpy, config, EGL_NO_CONTEXT, ctx_attrs); + if (!ectx) { + EGLint ctx_attrs_fallback[] = {EGL_CONTEXT_MAJOR_VERSION, 2, EGL_NONE}; + ectx = eglCreateContext(dpy, config, EGL_NO_CONTEXT, ctx_attrs_fallback); + if (!ectx) { + record_warning("eglCreateContext returned an error"); + return false; + } + } + + if (eglMakeCurrent(dpy, EGL_NO_SURFACE, EGL_NO_SURFACE, ectx) == EGL_FALSE) { + record_warning("eglMakeCurrent returned an error"); + return false; + } + + // Implementations disagree about whether eglGetProcAddress or dlsym + // should be used for getting functions from the actual API, see + // https://github.com/anholt/libepoxy/commit/14f24485e33816139398d1bd170d617703473738 + void* libgl = nullptr; + if (!glGetString) { + libgl = dlopen(LIBGL_FILENAME, RTLD_LAZY); + if (!libgl) { + libgl = dlopen(LIBGLES_FILENAME, RTLD_LAZY); + if (!libgl) { + record_warning(LIBGL_FILENAME " and " LIBGLES_FILENAME " missing"); + return false; + } + } + + glGetString = cast<PFNGLGETSTRING>(dlsym(libgl, "glGetString")); + if (!glGetString) { + dlclose(libgl); + record_warning("libEGL, libGL and libGLESv2 are missing glGetString"); + return false; + } + } + + const GLubyte* versionString = glGetString(GL_VERSION); + const GLubyte* vendorString = glGetString(GL_VENDOR); + const GLubyte* rendererString = glGetString(GL_RENDERER); + + if (versionString && vendorString && rendererString) { + record_value("VENDOR\n%s\nRENDERER\n%s\nVERSION\n%s\nTFP\nTRUE\n", + vendorString, rendererString, versionString); + } else { + record_warning("EGL glGetString returned null"); + if (libgl) { + dlclose(libgl); + } + return false; + } + + EGLDeviceEXT device; + if (eglQueryDisplayAttribEXT(dpy, EGL_DEVICE_EXT, (EGLAttrib*)&device) == + EGL_TRUE) { + const char* deviceExtensions = + eglQueryDeviceStringEXT(device, EGL_EXTENSIONS); + if (deviceExtensions && + strstr(deviceExtensions, "EGL_MESA_device_software")) { + record_value("MESA_ACCELERATED\nFALSE\n"); + } else { +#ifdef MOZ_WAYLAND + const char* deviceString = + eglQueryDeviceStringEXT(device, EGL_DRM_DEVICE_FILE_EXT); + if (!deviceString || !get_render_name(deviceString)) { + const char* renderNodeString = + eglQueryDeviceStringEXT(device, EGL_DRM_RENDER_NODE_FILE_EXT); + if (renderNodeString) { + set_render_device_path(renderNodeString); + } + } +#endif + } + } + + if (libgl) { + dlclose(libgl); + } + return true; +} + +static bool get_egl_status(EGLNativeDisplayType native_dpy) { + void* libegl = dlopen(LIBEGL_FILENAME, RTLD_LAZY); + if (!libegl) { + record_warning("libEGL missing"); + return false; + } + + PFNEGLGETPROCADDRESS eglGetProcAddress = + cast<PFNEGLGETPROCADDRESS>(dlsym(libegl, "eglGetProcAddress")); + + if (!eglGetProcAddress) { + dlclose(libegl); + record_warning("no eglGetProcAddress"); + return false; + } + + typedef EGLDisplay (*PFNEGLGETDISPLAYPROC)(void* native_display); + PFNEGLGETDISPLAYPROC eglGetDisplay = + cast<PFNEGLGETDISPLAYPROC>(eglGetProcAddress("eglGetDisplay")); + + typedef EGLBoolean (*PFNEGLINITIALIZEPROC)(EGLDisplay dpy, EGLint * major, + EGLint * minor); + PFNEGLINITIALIZEPROC eglInitialize = + cast<PFNEGLINITIALIZEPROC>(eglGetProcAddress("eglInitialize")); + + typedef EGLBoolean (*PFNEGLTERMINATEPROC)(EGLDisplay dpy); + PFNEGLTERMINATEPROC eglTerminate = + cast<PFNEGLTERMINATEPROC>(eglGetProcAddress("eglTerminate")); + + if (!eglGetDisplay || !eglInitialize || !eglTerminate) { + dlclose(libegl); + record_warning("libEGL missing methods"); + return false; + } + + EGLDisplay dpy = eglGetDisplay(native_dpy); + if (!dpy) { + dlclose(libegl); + record_warning("libEGL no display"); + return false; + } + + EGLint major, minor; + if (!eglInitialize(dpy, &major, &minor)) { + dlclose(libegl); + record_warning("libEGL initialize failed"); + return false; + } + + typedef const char* (*PFNEGLGETDISPLAYDRIVERNAMEPROC)(EGLDisplay dpy); + PFNEGLGETDISPLAYDRIVERNAMEPROC eglGetDisplayDriverName = + cast<PFNEGLGETDISPLAYDRIVERNAMEPROC>( + eglGetProcAddress("eglGetDisplayDriverName")); + if (eglGetDisplayDriverName) { + const char* driDriver = eglGetDisplayDriverName(dpy); + if (driDriver) { + record_value("DRI_DRIVER\n%s\n", driDriver); + } + } + + if (!get_egl_gl_status(dpy, eglGetProcAddress)) { + eglTerminate(dpy); + dlclose(libegl); + return false; + } + + eglTerminate(dpy); + dlclose(libegl); + return true; +} + +#ifdef MOZ_X11 +static void get_xrandr_info(Display* dpy) { + // When running on remote X11 the xrandr version may be stuck on an ancient + // version. There are still setups using remote X11 out there, so make sure we + // don't crash. + int eventBase, errorBase, major, minor; + if (!XRRQueryExtension(dpy, &eventBase, &errorBase) || + !XRRQueryVersion(dpy, &major, &minor) || + !(major > 1 || (major == 1 && minor >= 4))) { + return; + } + + Window root = RootWindow(dpy, DefaultScreen(dpy)); + XRRProviderResources* pr = XRRGetProviderResources(dpy, root); + XRRScreenResources* res = XRRGetScreenResourcesCurrent(dpy, root); + if (pr->nproviders != 0) { + record_value("DDX_DRIVER\n"); + for (int i = 0; i < pr->nproviders; i++) { + XRRProviderInfo* info = XRRGetProviderInfo(dpy, res, pr->providers[i]); + record_value("%s%s", info->name, i == pr->nproviders - 1 ? ";\n" : ";"); + } + } +} + +static void glxtest() { + void* libgl = dlopen(LIBGL_FILENAME, RTLD_LAZY); + if (!libgl) { + record_error(LIBGL_FILENAME " missing"); + return; + } + + typedef void* (*PFNGLXGETPROCADDRESS)(const char*); + PFNGLXGETPROCADDRESS glXGetProcAddress = + cast<PFNGLXGETPROCADDRESS>(dlsym(libgl, "glXGetProcAddress")); + + if (!glXGetProcAddress) { + record_error("no glXGetProcAddress"); + return; + } + + typedef GLXFBConfig* (*PFNGLXQUERYEXTENSION)(Display*, int*, int*); + PFNGLXQUERYEXTENSION glXQueryExtension = + cast<PFNGLXQUERYEXTENSION>(glXGetProcAddress("glXQueryExtension")); + + typedef GLXFBConfig* (*PFNGLXQUERYVERSION)(Display*, int*, int*); + PFNGLXQUERYVERSION glXQueryVersion = + cast<PFNGLXQUERYVERSION>(dlsym(libgl, "glXQueryVersion")); + + typedef XVisualInfo* (*PFNGLXCHOOSEVISUAL)(Display*, int, int*); + PFNGLXCHOOSEVISUAL glXChooseVisual = + cast<PFNGLXCHOOSEVISUAL>(glXGetProcAddress("glXChooseVisual")); + + typedef GLXContext (*PFNGLXCREATECONTEXT)(Display*, XVisualInfo*, GLXContext, + Bool); + PFNGLXCREATECONTEXT glXCreateContext = + cast<PFNGLXCREATECONTEXT>(glXGetProcAddress("glXCreateContext")); + + typedef Bool (*PFNGLXMAKECURRENT)(Display*, GLXDrawable, GLXContext); + PFNGLXMAKECURRENT glXMakeCurrent = + cast<PFNGLXMAKECURRENT>(glXGetProcAddress("glXMakeCurrent")); + + typedef void (*PFNGLXDESTROYCONTEXT)(Display*, GLXContext); + PFNGLXDESTROYCONTEXT glXDestroyContext = + cast<PFNGLXDESTROYCONTEXT>(glXGetProcAddress("glXDestroyContext")); + + typedef GLubyte* (*PFNGLGETSTRING)(GLenum); + PFNGLGETSTRING glGetString = + cast<PFNGLGETSTRING>(glXGetProcAddress("glGetString")); + + if (!glXQueryExtension || !glXQueryVersion || !glXChooseVisual || + !glXCreateContext || !glXMakeCurrent || !glXDestroyContext || + !glGetString) { + record_error(LIBGL_FILENAME " missing methods"); + return; + } + + ///// Open a connection to the X server ///// + Display* dpy = XOpenDisplay(nullptr); + if (!dpy) { + record_error("Unable to open a connection to the X server"); + return; + } + + ///// Check that the GLX extension is present ///// + if (!glXQueryExtension(dpy, nullptr, nullptr)) { + record_error("GLX extension missing"); + return; + } + + XSetErrorHandler(x_error_handler); + + ///// Get a visual ///// + int attribs[] = {GLX_RGBA, GLX_RED_SIZE, 1, GLX_GREEN_SIZE, + 1, GLX_BLUE_SIZE, 1, None}; + XVisualInfo* vInfo = glXChooseVisual(dpy, DefaultScreen(dpy), attribs); + if (!vInfo) { + int attribs2[] = {GLX_RGBA, GLX_RED_SIZE, 1, GLX_GREEN_SIZE, + 1, GLX_BLUE_SIZE, 1, GLX_DOUBLEBUFFER, + None}; + vInfo = glXChooseVisual(dpy, DefaultScreen(dpy), attribs2); + if (!vInfo) { + record_error("No visuals found"); + return; + } + } + + // using a X11 Window instead of a GLXPixmap does not crash + // fglrx in indirect rendering. bug 680644 + Window window; + XSetWindowAttributes swa; + swa.colormap = XCreateColormap(dpy, RootWindow(dpy, vInfo->screen), + vInfo->visual, AllocNone); + + swa.border_pixel = 0; + window = XCreateWindow(dpy, RootWindow(dpy, vInfo->screen), 0, 0, 16, 16, 0, + vInfo->depth, InputOutput, vInfo->visual, + CWBorderPixel | CWColormap, &swa); + + ///// Get a GL context and make it current ////// + GLXContext context = glXCreateContext(dpy, vInfo, nullptr, True); + glXMakeCurrent(dpy, window, context); + + ///// Look for this symbol to determine texture_from_pixmap support ///// + void* glXBindTexImageEXT = glXGetProcAddress("glXBindTexImageEXT"); + + ///// Get GL vendor/renderer/versions strings ///// + const GLubyte* versionString = glGetString(GL_VERSION); + const GLubyte* vendorString = glGetString(GL_VENDOR); + const GLubyte* rendererString = glGetString(GL_RENDERER); + + if (versionString && vendorString && rendererString) { + record_value("VENDOR\n%s\nRENDERER\n%s\nVERSION\n%s\nTFP\n%s\n", + vendorString, rendererString, versionString, + glXBindTexImageEXT ? "TRUE" : "FALSE"); + } else { + record_error("glGetString returned null"); + } + + // If GLX_MESA_query_renderer is available, populate additional data. + typedef Bool (*PFNGLXQUERYCURRENTRENDERERINTEGERMESAPROC)( + int attribute, unsigned int* value); + PFNGLXQUERYCURRENTRENDERERINTEGERMESAPROC + glXQueryCurrentRendererIntegerMESAProc = + cast<PFNGLXQUERYCURRENTRENDERERINTEGERMESAPROC>( + glXGetProcAddress("glXQueryCurrentRendererIntegerMESA")); + if (glXQueryCurrentRendererIntegerMESAProc) { + unsigned int vendorId, deviceId, accelerated, videoMemoryMB; + glXQueryCurrentRendererIntegerMESAProc(GLX_RENDERER_VENDOR_ID_MESA, + &vendorId); + glXQueryCurrentRendererIntegerMESAProc(GLX_RENDERER_DEVICE_ID_MESA, + &deviceId); + glXQueryCurrentRendererIntegerMESAProc(GLX_RENDERER_ACCELERATED_MESA, + &accelerated); + glXQueryCurrentRendererIntegerMESAProc(GLX_RENDERER_VIDEO_MEMORY_MESA, + &videoMemoryMB); + + // Truncate IDs to 4 digits- that's all PCI IDs are. + vendorId &= 0xFFFF; + deviceId &= 0xFFFF; + + record_value( + "MESA_VENDOR_ID\n0x%04x\n" + "MESA_DEVICE_ID\n0x%04x\n" + "MESA_ACCELERATED\n%s\n" + "MESA_VRAM\n%dMB\n", + vendorId, deviceId, accelerated ? "TRUE" : "FALSE", videoMemoryMB); + } + + // From Mesa's GL/internal/dri_interface.h, to be used by DRI clients. + typedef const char* (*PFNGLXGETSCREENDRIVERPROC)(Display * dpy, int scrNum); + PFNGLXGETSCREENDRIVERPROC glXGetScreenDriverProc = + cast<PFNGLXGETSCREENDRIVERPROC>(glXGetProcAddress("glXGetScreenDriver")); + if (glXGetScreenDriverProc) { + const char* driDriver = glXGetScreenDriverProc(dpy, DefaultScreen(dpy)); + if (driDriver) { + record_value("DRI_DRIVER\n%s\n", driDriver); + } + } + + // Get monitor and DDX driver information + get_xrandr_info(dpy); + + ///// Clean up. Indeed, the parent process might fail to kill us (e.g. if it + ///// doesn't need to check GL info) so we might be staying alive for longer + ///// than expected, so it's important to consume as little memory as + ///// possible. Also we want to check that we're able to do that too without + ///// generating X errors. + glXMakeCurrent(dpy, None, + nullptr); // must release the GL context before destroying it + glXDestroyContext(dpy, context); + XDestroyWindow(dpy, window); + XFreeColormap(dpy, swa.colormap); + +# ifdef NS_FREE_PERMANENT_DATA // conditionally defined in nscore.h, don't + // forget to #include it above + XCloseDisplay(dpy); +# else + // This XSync call wanted to be instead: + // XCloseDisplay(dpy); + // but this can cause 1-minute stalls on certain setups using Nouveau, see bug + // 973192 + XSync(dpy, False); +# endif + + dlclose(libgl); + + record_value("TEST_TYPE\nGLX\n"); +} + +static bool x11_egltest() { + Display* dpy = XOpenDisplay(nullptr); + if (!dpy) { + return false; + } + XSetErrorHandler(x_error_handler); + + if (!get_egl_status(dpy)) { + return false; + } + + // Bug 1667621: 30bit "Deep Color" is broken on EGL on Mesa (as of 2021/10). + // Disable all non-standard depths for the initial EGL roleout. + int screenCount = ScreenCount(dpy); + for (int idx = 0; idx < screenCount; idx++) { + if (DefaultDepth(dpy, idx) != 24) { + return false; + } + } + + // Get monitor and DDX driver information + get_xrandr_info(dpy); + + // Bug 1715245: Closing the display connection here crashes on NV prop. + // drivers. Just leave it open, the process will exit shortly after anyway. + // XCloseDisplay(dpy); + + record_value("TEST_TYPE\nEGL\n"); + return true; +} +#endif + +#ifdef MOZ_WAYLAND +static void wayland_egltest() { + // NOTE: returns false to fall back to X11 when the Wayland socket doesn't + // exist but fails with record_error if something actually went wrong + struct wl_display* dpy = wl_display_connect(nullptr); + if (!dpy) { + record_error("Could not connect to wayland socket"); + return; + } + + if (!get_egl_status((EGLNativeDisplayType)dpy)) { + record_error("EGL test failed"); + } + + // This is enough to crash some broken NVIDIA prime + Wayland setups, see + // https://github.com/NVIDIA/egl-wayland/issues/41 and bug 1768260. + wl_display_roundtrip(dpy); + + wl_display_disconnect(dpy); + record_value("TEST_TYPE\nEGL\n"); +} + +static constexpr struct { + VAProfile mVAProfile; + nsLiteralCString mName; +} kVAAPiProfileName[] = { +# define MAP(v) \ + { VAProfile##v, nsLiteralCString(#v) } + MAP(H264ConstrainedBaseline), + MAP(H264Main), + MAP(H264High), + MAP(VP8Version0_3), + MAP(VP9Profile0), + MAP(VP9Profile2), + MAP(AV1Profile0), + MAP(AV1Profile1), +# undef MAP +}; + +static const char* VAProfileName(VAProfile aVAProfile) { + for (const auto& profile : kVAAPiProfileName) { + if (profile.mVAProfile == aVAProfile) { + return profile.mName.get(); + } + } + return nullptr; +} + +int childvaapitest() { + int renderDeviceFD = -1; + VAProfile* profiles = nullptr; + VAEntrypoint* entryPoints = nullptr; + PRLibrary* libDrm = nullptr; + VADisplay display = nullptr; + + auto autoRelease = mozilla::MakeScopeExit([&] { + if (renderDeviceFD > -1) { + close(renderDeviceFD); + } + delete[] profiles; + delete[] entryPoints; + if (display) { + vaTerminate(display); + } + if (libDrm) { + PR_UnloadLibrary(libDrm); + } + }); + + renderDeviceFD = open(glxtest_render_device_path, O_RDWR); + if (renderDeviceFD == -1) { + return 3; + } + + PRLibSpec lspec; + lspec.type = PR_LibSpec_Pathname; + const char* libName = "libva-drm.so.2"; + lspec.value.pathname = libName; + libDrm = PR_LoadLibraryWithFlags(lspec, PR_LD_NOW | PR_LD_LOCAL); + if (!libDrm) { + return 4; + } + + static auto sVaGetDisplayDRM = + (void* (*)(int fd))PR_FindSymbol(libDrm, "vaGetDisplayDRM"); + if (!sVaGetDisplayDRM) { + return 5; + } + + display = sVaGetDisplayDRM(renderDeviceFD); + if (!display) { + return 6; + } + + int major, minor; + VAStatus status = vaInitialize(display, &major, &minor); + if (status != VA_STATUS_SUCCESS) { + return 7; + } + + int maxProfiles = vaMaxNumProfiles(display); + int maxEntryPoints = vaMaxNumEntrypoints(display); + if (MOZ_UNLIKELY(maxProfiles <= 0 || maxEntryPoints <= 0)) { + return 8; + } + + profiles = new VAProfile[maxProfiles]; + int numProfiles = 0; + status = vaQueryConfigProfiles(display, profiles, &numProfiles); + if (status != VA_STATUS_SUCCESS) { + return 9; + } + numProfiles = std::min(numProfiles, maxProfiles); + + entryPoints = new VAEntrypoint[maxEntryPoints]; + int codecs = 0; + bool foundProfile = false; + for (int p = 0; p < numProfiles; p++) { + VAProfile profile = profiles[p]; + + // Check only supported profiles + if (!VAProfileName(profile)) { + continue; + } + + int numEntryPoints = 0; + status = vaQueryConfigEntrypoints(display, profile, entryPoints, + &numEntryPoints); + if (status != VA_STATUS_SUCCESS) { + continue; + } + numEntryPoints = std::min(numEntryPoints, maxEntryPoints); + + for (int entry = 0; entry < numEntryPoints; entry++) { + if (entryPoints[entry] != VAEntrypointVLD) { + continue; + } + VAConfigID config = VA_INVALID_ID; + status = vaCreateConfig(display, profile, entryPoints[entry], nullptr, 0, + &config); + if (status == VA_STATUS_SUCCESS) { + const char* profstr = VAProfileName(profile); + // VAProfileName returns null on failure, making the below calls safe + if (!strncmp(profstr, "H264", 4)) { + codecs |= CODEC_HW_H264; + } else if (!strncmp(profstr, "VP8", 3)) { + codecs |= CODEC_HW_VP8; + } else if (!strncmp(profstr, "VP9", 3)) { + codecs |= CODEC_HW_VP9; + } else if (!strncmp(profstr, "AV1", 3)) { + codecs |= CODEC_HW_AV1; + } else { + char warnbuf[128]; + SprintfBuf(warnbuf, 128, "VA-API test unknown profile: %s", profstr); + record_warning(warnbuf); + } + vaDestroyConfig(display, config); + foundProfile = true; + } + } + } + if (foundProfile) { + return codecs; + } + return 10; +} + +static void vaapitest() { + if (!glxtest_render_device_path) { + return; + } + + pid_t vaapitest_pid = fork(); + if (vaapitest_pid == 0) { +# if defined(MOZ_ASAN) || defined(FUZZING) + // If handle_segv=1 (default), then glxtest crash will print a sanitizer + // report which can confuse the harness in fuzzing automation. + signal(SIGSEGV, SIG_DFL); +# endif + int vaapirv = childvaapitest(); + _exit(vaapirv); + } else if (vaapitest_pid > 0) { + int vaapitest_status = 0; + bool wait_for_vaapitest_process = true; + + while (wait_for_vaapitest_process) { + if (waitpid(vaapitest_pid, &vaapitest_status, 0) == -1) { + wait_for_vaapitest_process = false; + record_warning( + "VA-API test failed: waiting for VA-API process failed."); + } else if (WIFEXITED(vaapitest_status) || WIFSIGNALED(vaapitest_status)) { + wait_for_vaapitest_process = false; + } + } + + if (WIFEXITED(vaapitest_status)) { + // Note that WEXITSTATUS only returns least significant 8 bits + // of the exit code, despite returning type int. + int exitcode = WEXITSTATUS(vaapitest_status); + int codecs = exitcode & 0b1111'0000; + exitcode &= 0b0000'1111; + switch (exitcode) { + case 0: + char codecstr[80]; + record_value("VAAPI_SUPPORTED\nTRUE\n"); + SprintfBuf(codecstr, 80, "%d", codecs); + record_value("VAAPI_HWCODECS\n"); + record_value(codecstr); + record_value("\n"); + break; + case 3: + record_warning( + "VA-API test failed: opening render device path failed."); + break; + case 4: + record_warning( + "VA-API test failed: missing or old libva-drm library."); + break; + case 5: + record_warning("VA-API test failed: missing vaGetDisplayDRM."); + break; + case 6: + record_warning("VA-API test failed: failed to get vaGetDisplayDRM."); + break; + case 7: + record_warning( + "VA-API test failed: failed to initialise VAAPI connection."); + break; + case 8: + record_warning( + "VA-API test failed: wrong VAAPI profiles/entry point nums."); + break; + case 9: + record_warning("VA-API test failed: vaQueryConfigProfiles() failed."); + break; + case 10: + record_warning( + "VA-API test failed: no supported VAAPI profile found."); + break; + default: + record_warning( + "VA-API test failed: Something unexpected went wrong."); + break; + } + } else { + record_warning( + "VA-API test failed: process crashed. Please check your VA-API " + "drivers."); + } + } else { + record_warning("VA-API test failed: Could not fork process."); + } +} +#endif + +int childgltest() { + enum { bufsize = 2048 }; + char buf[bufsize]; + + // We save it as a global so that the X error handler can flush the buffer + // before early exiting. + glxtest_buf = buf; + glxtest_bufsize = bufsize; + + // Get a list of all GPUs from the PCI bus. + get_pci_status(); + +#ifdef MOZ_WAYLAND + if (IsWaylandEnabled()) { + wayland_egltest(); + } else +#endif + { +#ifdef MOZ_X11 + // TODO: --display command line argument is not properly handled + if (!x11_egltest()) { + glxtest(); + } +#endif + } + +#ifdef MOZ_WAYLAND + vaapitest(); +#endif + + // Finally write buffered data to the pipe. + record_flush(); + + // If we completely filled the buffer, we need to tell the parent. + if (glxtest_length >= glxtest_bufsize) { + return EXIT_FAILURE_BUFFER_TOO_SMALL; + } + + return EXIT_SUCCESS; +} + +} // extern "C" + +/** \returns true in the child glxtest process, false in the parent process */ +bool fire_glxtest_process() { + int pfd[2]; + if (pipe(pfd) == -1) { + perror("pipe"); + return false; + } + pid_t pid = fork(); + if (pid < 0) { + perror("fork"); + close(pfd[0]); + close(pfd[1]); + return false; + } + // The child exits early to avoid running the full shutdown sequence and avoid + // conflicting with threads we have already spawned (like the profiler). + if (pid == 0) { + close(pfd[0]); + write_end_of_the_pipe = pfd[1]; + close_logging(); +#if defined(MOZ_ASAN) || defined(FUZZING) + // If handle_segv=1 (default), then glxtest crash will print a sanitizer + // report which can confuse the harness in fuzzing automation. + signal(SIGSEGV, SIG_DFL); +#endif + int rv = childgltest(); + close(pfd[1]); + _exit(rv); + } + + close(pfd[1]); + mozilla::widget::glxtest_pipe = pfd[0]; + mozilla::widget::glxtest_pid = pid; + return false; +} |