diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 19:33:14 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 19:33:14 +0000 |
commit | 36d22d82aa202bb199967e9512281e9a53db42c9 (patch) | |
tree | 105e8c98ddea1c1e4784a60a5a6410fa416be2de /toolkit/xre/glxtest/glxtest.cpp | |
parent | Initial commit. (diff) | |
download | firefox-esr-36d22d82aa202bb199967e9512281e9a53db42c9.tar.xz firefox-esr-36d22d82aa202bb199967e9512281e9a53db42c9.zip |
Adding upstream version 115.7.0esr.upstream/115.7.0esrupstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'toolkit/xre/glxtest/glxtest.cpp')
-rw-r--r-- | toolkit/xre/glxtest/glxtest.cpp | 1034 |
1 files changed, 1034 insertions, 0 deletions
diff --git a/toolkit/xre/glxtest/glxtest.cpp b/toolkit/xre/glxtest/glxtest.cpp new file mode 100644 index 0000000000..33a21c11f7 --- /dev/null +++ b/toolkit/xre/glxtest/glxtest.cpp @@ -0,0 +1,1034 @@ +/* -*- 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> +#include <getopt.h> +#include <vector> +#include <stdint.h> +#include <string.h> +#include <stdarg.h> + +#ifdef MOZ_WAYLAND +# include <gdk/gdk.h> +#endif + +#if defined(MOZ_ASAN) || defined(FUZZING) +# include <signal.h> +#endif + +#ifdef __SUNPRO_CC +# include <stdio.h> +#endif + +#ifdef MOZ_X11 +# include "X11/Xlib.h" +# include "X11/Xutil.h" +# include <X11/extensions/Xrandr.h> +#endif + +#include <vector> +#include <sys/wait.h> +#include "mozilla/ScopeExit.h" +#include "mozilla/Types.h" + +#include "mozilla/GfxInfoUtils.h" + +#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 + +#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); +} +#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" { + +#define PCI_FILL_IDENT 0x0001 +#define PCI_FILL_CLASS 0x0020 +#define PCI_BASE_CLASS_DISPLAY 0x03 + +static void get_pci_status() { + log("GLX_TEST: get_pci_status start\n"); + + 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; + } + auto release = mozilla::MakeScopeExit([&] { dlclose(libpci); }); + + 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) { + 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); + + log("GLX_TEST: get_pci_status finished\n"); +} + +#ifdef MOZ_WAYLAND +static void set_render_device_path(const char* render_device_path) { + record_value("DRM_RENDERDEVICE\n%s\n", 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; + } + auto release = mozilla::MakeScopeExit([&] { dlclose(libdrm); }); + + 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"); + return false; + } + + uint32_t flags = 0; + int devices_len = drmGetDevices2(flags, nullptr, 0); + if (devices_len < 0) { + record_warning("drmGetDevices2 failed"); + return false; + } + drmDevice** devices = (drmDevice**)calloc(devices_len, sizeof(drmDevice*)); + if (!devices) { + record_warning("Allocation error"); + return false; + } + devices_len = drmGetDevices2(flags, devices, devices_len); + if (devices_len < 0) { + free(devices); + record_warning("drmGetDevices2 failed"); + 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); + 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 (*PFNEGLDESTROYCONTEXTPROC)(EGLDisplay dpy, + EGLContext ctx); + PFNEGLDESTROYCONTEXTPROC eglDestroyContext = + cast<PFNEGLDESTROYCONTEXTPROC>(eglGetProcAddress("eglDestroyContext")); + + 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")); + + log("GLX_TEST: get_egl_gl_status start\n"); + + if (!eglChooseConfig || !eglCreateContext || !eglDestroyContext || + !eglMakeCurrent || !eglQueryDeviceStringEXT) { + record_warning("libEGL missing methods for GL test"); + return false; + } + + typedef GLubyte* (*PFNGLGETSTRING)(GLenum); + PFNGLGETSTRING glGetString = + cast<PFNGLGETSTRING>(eglGetProcAddress("glGetString")); + +#if defined(__arm__) || 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) { + eglDestroyContext(dpy, ectx); + record_warning("eglMakeCurrent returned an error"); + return false; + } + eglDestroyContext(dpy, ectx); + + // 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; + } + } + auto release = mozilla::MakeScopeExit([&] { + if (libgl) { + dlclose(libgl); + } + }); + + 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"); + 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 + } + } + + log("GLX_TEST: get_egl_gl_status finished\n"); + return true; +} + +static bool get_egl_status(EGLNativeDisplayType native_dpy) { + log("GLX_TEST: get_egl_status start\n"); + + EGLDisplay dpy = nullptr; + + typedef EGLBoolean (*PFNEGLTERMINATEPROC)(EGLDisplay dpy); + PFNEGLTERMINATEPROC eglTerminate = nullptr; + + void* libegl = dlopen(LIBEGL_FILENAME, RTLD_LAZY); + if (!libegl) { + record_warning("libEGL missing"); + return false; + } + auto release = mozilla::MakeScopeExit([&] { + if (dpy) { + eglTerminate(dpy); + } + // Unload libegl here causes ASAN/MemLeaks failures on Linux + // as libegl may not be meant to be unloaded runtime. + // See 1304156 for reference. + // dlclose(libegl); + }); + + PFNEGLGETPROCADDRESS eglGetProcAddress = + cast<PFNEGLGETPROCADDRESS>(dlsym(libegl, "eglGetProcAddress")); + + if (!eglGetProcAddress) { + 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")); + eglTerminate = cast<PFNEGLTERMINATEPROC>(eglGetProcAddress("eglTerminate")); + + if (!eglGetDisplay || !eglInitialize || !eglTerminate) { + record_warning("libEGL missing methods"); + return false; + } + + dpy = eglGetDisplay(native_dpy); + if (!dpy) { + record_warning("libEGL no display"); + return false; + } + + EGLint major, minor; + if (!eglInitialize(dpy, &major, &minor)) { + 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); + } + } + + bool ret = get_egl_gl_status(dpy, eglGetProcAddress); + log("GLX_TEST: get_egl_status finished with return: %d\n", ret); + + return ret; +} + +#ifdef MOZ_X11 +static void get_xrandr_info(Display* dpy) { + log("GLX_TEST: get_xrandr_info start\n"); + + // 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))) { + log("GLX_TEST: get_xrandr_info failed, old version.\n"); + return; + } + + Window root = RootWindow(dpy, DefaultScreen(dpy)); + XRRProviderResources* pr = XRRGetProviderResources(dpy, root); + if (!pr) { + return; + } + XRRScreenResources* res = XRRGetScreenResourcesCurrent(dpy, root); + if (!res) { + XRRFreeProviderResources(pr); + return; + } + 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]); + if (info) { + record_value("%s%s", info->name, i == pr->nproviders - 1 ? ";\n" : ";"); + XRRFreeProviderInfo(info); + } + } + } + XRRFreeScreenResources(res); + XRRFreeProviderResources(pr); + + log("GLX_TEST: get_xrandr_info finished\n"); +} + +void glxtest() { + log("GLX_TEST: glxtest start\n"); + + Display* dpy = nullptr; + void* libgl = dlopen(LIBGL_FILENAME, RTLD_LAZY); + if (!libgl) { + record_error(LIBGL_FILENAME " missing"); + return; + } + auto release = mozilla::MakeScopeExit([&] { + if (dpy) { +# ifdef MOZ_ASAN + 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); + }); + + 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 ///// + 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); + XFree(vInfo); + + record_value("TEST_TYPE\nGLX\n"); + log("GLX_TEST: glxtest finished\n"); +} + +bool x11_egltest() { + log("GLX_TEST: x11_egltest start\n"); + + Display* dpy = XOpenDisplay(nullptr); + if (!dpy) { + return false; + } +# ifdef MOZ_ASAN + auto release = mozilla::MakeScopeExit([&] { + // 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); + }); +# endif + + 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); + + record_value("TEST_TYPE\nEGL\n"); + + log("GLX_TEST: x11_egltest finished\n"); + return true; +} +#endif + +#ifdef MOZ_WAYLAND +void wayland_egltest() { + log("GLX_TEST: wayland_egltest start\n"); + + static auto sWlDisplayConnect = (struct wl_display * (*)(const char*)) + dlsym(RTLD_DEFAULT, "wl_display_connect"); + static auto sWlDisplayRoundtrip = + (int (*)(struct wl_display*))dlsym(RTLD_DEFAULT, "wl_display_roundtrip"); + static auto sWlDisplayDisconnect = (void (*)(struct wl_display*))dlsym( + RTLD_DEFAULT, "wl_display_disconnect"); + + if (!sWlDisplayConnect || !sWlDisplayRoundtrip || !sWlDisplayDisconnect) { + record_error("Missing Wayland libraries"); + return; + } + + // 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 = sWlDisplayConnect(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. + sWlDisplayRoundtrip(dpy); + + sWlDisplayDisconnect(dpy); + record_value("TEST_TYPE\nEGL\n"); + log("GLX_TEST: wayland_egltest finished\n"); +} +#endif + +int childgltest(bool aWayland) { + log("GLX_TEST: childgltest start\n"); + + // Get a list of all GPUs from the PCI bus. + get_pci_status(); + +#ifdef MOZ_WAYLAND + if (aWayland) { + wayland_egltest(); + } +#endif +#ifdef MOZ_X11 + if (!aWayland) { + // TODO: --display command line argument is not properly handled + if (!x11_egltest()) { + glxtest(); + } + } +#endif + // Finally write buffered data to the pipe. + record_flush(); + + log("GLX_TEST: childgltest finished\n"); + return EXIT_SUCCESS; +} + +} // extern "C" + +static void PrintUsage() { + printf( + "Firefox OpenGL probe utility\n" + "\n" + "usage: glxtest [options]\n" + "\n" + "Options:\n" + "\n" + " -h --help show this message\n" + " -f --fd num where to print output, default it stdout\n" + " -w --wayland probe OpenGL/EGL on Wayland (default is " + "X11)\n" + "\n"); +} + +int main(int argc, char** argv) { + struct option longOptions[] = {{"help", no_argument, nullptr, 'h'}, + {"fd", required_argument, nullptr, 'f'}, + {"wayland", no_argument, nullptr, 'w'}, + {nullptr, 0, nullptr, 0}}; + const char* shortOptions = "hf:w"; + int c; + bool wayland = false; + while ((c = getopt_long(argc, argv, shortOptions, longOptions, nullptr)) != + -1) { + switch (c) { + case 'w': + wayland = true; + break; + case 'f': + output_pipe = atoi(optarg); + break; + case 'h': +#ifdef MOZ_WAYLAND + // Dummy call to mozgtk to prevent the linker from removing + // the dependency with --as-needed. + // see toolkit/library/moz.build for details. + gdk_display_get_default(); +#endif + PrintUsage(); + return 0; + default: + break; + } + } + if (getenv("MOZ_AVOID_OPENGL_ALTOGETHER")) { + const char* msg = "ERROR\nMOZ_AVOID_OPENGL_ALTOGETHER envvar set"; + MOZ_UNUSED(write(output_pipe, msg, strlen(msg))); + exit(EXIT_FAILURE); + } + const char* env = getenv("MOZ_GFX_DEBUG"); + enable_logging = env && *env == '1'; + if (!enable_logging) { + 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 + return childgltest(wayland); +} |