diff options
Diffstat (limited to 'toolkit/xre/glxtest.cpp')
-rw-r--r-- | toolkit/xre/glxtest.cpp | 1222 |
1 files changed, 1222 insertions, 0 deletions
diff --git a/toolkit/xre/glxtest.cpp b/toolkit/xre/glxtest.cpp new file mode 100644 index 0000000000..e533822a65 --- /dev/null +++ b/toolkit/xre/glxtest.cpp @@ -0,0 +1,1222 @@ +/* -*- 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 "mozilla/Unused.h" +#include "nsAppRunner.h" // for IsWaylandDisabled on IsX11EGLEnabled +#include "stdint.h" + +#ifdef __SUNPRO_CC +# include <stdio.h> +#endif + +#ifdef MOZ_X11 +# include "X11/Xlib.h" +# include "X11/Xutil.h" +#endif + +#ifdef MOZ_WAYLAND +# include "mozilla/widget/mozwayland.h" +# include "mozilla/widget/xdg-output-unstable-v1-client-protocol.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 +#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 int EGLint; +typedef void* EGLNativeDisplayType; +typedef void* EGLSurface; +typedef void* (*PFNEGLGETPROCADDRESS)(const char*); + +#define EGL_NO_CONTEXT nullptr +#define EGL_FALSE 0 +#define EGL_TRUE 1 +#define EGL_BLUE_SIZE 0x3022 +#define EGL_GREEN_SIZE 0x3023 +#define EGL_RED_SIZE 0x3024 +#define EGL_NONE 0x3038 +#define EGL_VENDOR 0x3053 +#define EGL_CONTEXT_CLIENT_VERSION 0x3098 +#define EGL_DEVICE_EXT 0x322C +#define EGL_DRM_DEVICE_FILE_EXT 0x3233 + +// stuff from xf86drm.h +#define DRM_NODE_RENDER 2 +#define DRM_NODE_MAX 3 + +typedef struct _drmDevice { + char** nodes; + int available_nodes; + int bustype; + union { + void* pci; + void* usb; + void* platform; + void* host1x; + } businfo; + union { + void* 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 + +// 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; + +// 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() { + 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 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 char* get_render_name(const char* name) { + void* libdrm = dlopen(LIBDRM_FILENAME, RTLD_LAZY); + if (!libdrm) { + record_warning("Failed to open libdrm"); + return nullptr; + } + + 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 nullptr; + } + + uint32_t flags = 0; + int devices_len = drmGetDevices2(flags, nullptr, 0); + if (devices_len < 0) { + record_warning("drmGetDevices2 failed"); + dlclose(libdrm); + return nullptr; + } + drmDevice** devices = (drmDevice**)calloc(devices_len, sizeof(drmDevice*)); + if (!devices) { + record_warning("Allocation error"); + dlclose(libdrm); + return nullptr; + } + devices_len = drmGetDevices2(flags, devices, devices_len); + if (devices_len < 0) { + free(devices); + record_warning("drmGetDevices2 failed"); + dlclose(libdrm); + return nullptr; + } + + const drmDevice* match = nullptr; + for (int i = 0; i < devices_len; i++) { + if (device_has_name(devices[i], name)) { + match = devices[i]; + break; + } + } + + char* render_name = nullptr; + if (!match) { + record_warning("Cannot find DRM device"); + } else if (!(match->available_nodes & (1 << DRM_NODE_RENDER))) { + record_warning("DRM device has no render node"); + } else { + render_name = strdup(match->nodes[DRM_NODE_RENDER]); + } + + for (int i = 0; i < devices_len; i++) { + drmFreeDevice(&devices[i]); + } + free(devices); + + dlclose(libdrm); + return render_name; +} +#endif + +static void get_gles_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 EGLContext (*PFNEGLCREATECONTEXTPROC)( + EGLDisplay dpy, EGLConfig config, EGLContext share_context, + EGLint const* attrib_list); + PFNEGLCREATECONTEXTPROC eglCreateContext = + cast<PFNEGLCREATECONTEXTPROC>(eglGetProcAddress("eglCreateContext")); + + typedef EGLSurface (*PFNEGLCREATEPBUFFERSURFACEPROC)( + EGLDisplay dpy, EGLConfig config, EGLint const* attrib_list); + PFNEGLCREATEPBUFFERSURFACEPROC eglCreatePbufferSurface = + cast<PFNEGLCREATEPBUFFERSURFACEPROC>( + eglGetProcAddress("eglCreatePbufferSurface")); + + 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 || !eglCreatePbufferSurface || + !eglMakeCurrent) { + record_error("libEGL missing methods for GLES test"); + return; + } + + typedef GLubyte* (*PFNGLGETSTRING)(GLenum); + PFNGLGETSTRING glGetString = + cast<PFNGLGETSTRING>(eglGetProcAddress("glGetString")); + + // 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_error("Unable to load " LIBGL_FILENAME " or " LIBGLES_FILENAME); + return; + } + } + + glGetString = cast<PFNGLGETSTRING>(dlsym(libgl, "glGetString")); + if (!glGetString) { + dlclose(libgl); + record_error("libGL or libGLESv2 glGetString missing"); + return; + } + } + + EGLint config_attrs[] = {EGL_RED_SIZE, 8, EGL_GREEN_SIZE, 8, + EGL_BLUE_SIZE, 8, EGL_NONE}; + EGLConfig config; + EGLint num_config; + eglChooseConfig(dpy, config_attrs, &config, 1, &num_config); + EGLint ctx_attrs[] = {EGL_CONTEXT_CLIENT_VERSION, 3, EGL_NONE}; + EGLContext ectx = eglCreateContext(dpy, config, EGL_NO_CONTEXT, ctx_attrs); + EGLSurface pbuf = eglCreatePbufferSurface(dpy, config, nullptr); + eglMakeCurrent(dpy, pbuf, pbuf, ectx); + + 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_error("libGLESv2 glGetString returned null"); + } + + if (eglQueryDeviceStringEXT) { + EGLDeviceEXT device = nullptr; + + if (eglQueryDisplayAttribEXT(dpy, EGL_DEVICE_EXT, (EGLAttrib*)&device) == + EGL_TRUE) { + const char* deviceString = + eglQueryDeviceStringEXT(device, EGL_DRM_DEVICE_FILE_EXT); + if (deviceString) { + record_value("MESA_ACCELERATED\nTRUE\n"); + +#ifdef MOZ_WAYLAND + char* renderDeviceName = get_render_name(deviceString); + if (renderDeviceName) { + record_value("DRM_RENDERDEVICE\n%s\n", renderDeviceName); + } else { + record_warning("Can't find render node name for DRM device"); + } +#endif + } + } + } + + if (libgl) { + dlclose(libgl); + } +} + +static void get_egl_status(EGLNativeDisplayType native_dpy, bool gles_test) { + void* libegl = dlopen(LIBEGL_FILENAME, RTLD_LAZY); + if (!libegl) { + record_warning("libEGL missing"); + return; + } + + PFNEGLGETPROCADDRESS eglGetProcAddress = + cast<PFNEGLGETPROCADDRESS>(dlsym(libegl, "eglGetProcAddress")); + + if (!eglGetProcAddress) { + dlclose(libegl); + record_error("no eglGetProcAddress"); + return; + } + + 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_error("libEGL missing methods"); + return; + } + + EGLDisplay dpy = eglGetDisplay(native_dpy); + if (!dpy) { + dlclose(libegl); + record_warning("libEGL no display"); + return; + } + + EGLint major, minor; + if (!eglInitialize(dpy, &major, &minor)) { + dlclose(libegl); + record_warning("libEGL initialize failed"); + return; + } + + if (gles_test) { + get_gles_status(dpy, eglGetProcAddress); + } + + 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); + } + } + + eglTerminate(dpy); + dlclose(libegl); +} + +#ifdef MOZ_X11 +static void get_x11_screen_info(Display* dpy) { + int screenCount = ScreenCount(dpy); + int defaultScreen = DefaultScreen(dpy); + if (screenCount != 0) { + record_value("SCREEN_INFO\n"); + for (int idx = 0; idx < screenCount; idx++) { + Screen* scrn = ScreenOfDisplay(dpy, idx); + int current_height = scrn->height; + int current_width = scrn->width; + + record_value("%dx%d:%d%s", current_width, current_height, + idx == defaultScreen ? 1 : 0, + idx == screenCount - 1 ? ";\n" : ";"); + } + } +} + +static void get_glx_status(int* gotGlxInfo, int* gotDriDriver) { + 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) { + 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"); + *gotGlxInfo = 1; + } 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) { + *gotDriDriver = 1; + record_value("DRI_DRIVER\n%s\n", driDriver); + } + } + + // Get monitor information + get_x11_screen_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); +} + +static bool x11_egltest() { + get_egl_status(nullptr, true); + + Display* dpy = XOpenDisplay(nullptr); + if (!dpy) { + return false; + } + XSetErrorHandler(x_error_handler); + + get_x11_screen_info(dpy); + + XCloseDisplay(dpy); + return true; +} + +static void glxtest() { + int gotGlxInfo = 0; + int gotDriDriver = 0; + + get_glx_status(&gotGlxInfo, &gotDriDriver); + if (!gotGlxInfo) { + get_egl_status(nullptr, true); + } else if (!gotDriDriver) { + // If we failed to get the driver name from X, try via + // EGL_MESA_query_driver. We are probably using Wayland. + get_egl_status(nullptr, false); + } +} +#endif + +#ifdef MOZ_WAYLAND +typedef void (*print_info_t)(void* info); +typedef void (*destroy_info_t)(void* info); + +struct global_info { + struct wl_list link; + + uint32_t id; + uint32_t version; + char* interface; + + print_info_t print; + destroy_info_t destroy; +}; + +struct output_info { + struct global_info global; + struct wl_list global_link; + + struct wl_output* output; + + int32_t version; + + int32_t scale; +}; + +struct xdg_output_v1_info { + struct wl_list link; + + struct zxdg_output_v1* xdg_output; + struct output_info* output; + + struct { + int32_t width, height; + } logical; + + char *name, *description; +}; + +struct xdg_output_manager_v1_info { + struct global_info global; + struct zxdg_output_manager_v1* manager; + struct weston_info* info; + + struct wl_list outputs; +}; + +struct weston_info { + struct wl_display* display; + struct wl_registry* registry; + + struct wl_list infos; + bool roundtrip_needed; + + struct wl_list outputs; + struct xdg_output_manager_v1_info* xdg_output_manager_v1_info; +}; + +static void init_global_info(struct weston_info* info, + struct global_info* global, uint32_t id, + const char* interface, uint32_t version) { + global->id = id; + global->version = version; + global->interface = strdup(interface); + + wl_list_insert(info->infos.prev, &global->link); +} + +static void print_output_info(void* data) {} + +static void destroy_xdg_output_v1_info(struct xdg_output_v1_info* info) { + wl_list_remove(&info->link); + zxdg_output_v1_destroy(info->xdg_output); + free(info->name); + free(info->description); + free(info); +} + +static int cmpOutputIds(const void* a, const void* b) { + return (((struct xdg_output_v1_info*)a)->output->global.id - + ((struct xdg_output_v1_info*)b)->output->global.id); +} + +static void print_xdg_output_manager_v1_info(void* data) { + struct xdg_output_manager_v1_info* info = + (struct xdg_output_manager_v1_info*)data; + struct xdg_output_v1_info* output; + + int screen_count = wl_list_length(&info->outputs); + if (screen_count > 0) { + struct xdg_output_v1_info* infos = (struct xdg_output_v1_info*)malloc( + screen_count * sizeof(xdg_output_v1_info)); + + int pos = 0; + wl_list_for_each(output, &info->outputs, link) { + infos[pos] = *output; + pos++; + } + + if (screen_count > 1) { + qsort(infos, screen_count, sizeof(struct xdg_output_v1_info), + cmpOutputIds); + } + + record_value("SCREEN_INFO\n"); + for (int i = 0; i < screen_count; i++) { + record_value("%dx%d:0;", infos[i].logical.width, infos[i].logical.height); + } + record_value("\n"); + + free(infos); + } +} + +static void destroy_xdg_output_manager_v1_info(void* data) { + struct xdg_output_manager_v1_info* info = + (struct xdg_output_manager_v1_info*)data; + struct xdg_output_v1_info *output, *tmp; + + zxdg_output_manager_v1_destroy(info->manager); + + wl_list_for_each_safe(output, tmp, &info->outputs, link) + destroy_xdg_output_v1_info(output); +} + +static void handle_xdg_output_v1_logical_position(void* data, + struct zxdg_output_v1* output, + int32_t x, int32_t y) {} + +static void handle_xdg_output_v1_logical_size(void* data, + struct zxdg_output_v1* output, + int32_t width, int32_t height) { + struct xdg_output_v1_info* xdg_output = (struct xdg_output_v1_info*)data; + xdg_output->logical.width = width; + xdg_output->logical.height = height; +} + +static void handle_xdg_output_v1_done(void* data, + struct zxdg_output_v1* output) {} + +static void handle_xdg_output_v1_name(void* data, struct zxdg_output_v1* output, + const char* name) { + struct xdg_output_v1_info* xdg_output = (struct xdg_output_v1_info*)data; + xdg_output->name = strdup(name); +} + +static void handle_xdg_output_v1_description(void* data, + struct zxdg_output_v1* output, + const char* description) { + struct xdg_output_v1_info* xdg_output = (struct xdg_output_v1_info*)data; + xdg_output->description = strdup(description); +} + +static const struct zxdg_output_v1_listener xdg_output_v1_listener = { + .logical_position = handle_xdg_output_v1_logical_position, + .logical_size = handle_xdg_output_v1_logical_size, + .done = handle_xdg_output_v1_done, + .name = handle_xdg_output_v1_name, + .description = handle_xdg_output_v1_description, +}; + +static void add_xdg_output_v1_info( + struct xdg_output_manager_v1_info* manager_info, + struct output_info* output) { + struct xdg_output_v1_info* xdg_output = + (struct xdg_output_v1_info*)malloc(sizeof *xdg_output); + + wl_list_insert(&manager_info->outputs, &xdg_output->link); + xdg_output->xdg_output = zxdg_output_manager_v1_get_xdg_output( + manager_info->manager, output->output); + zxdg_output_v1_add_listener(xdg_output->xdg_output, &xdg_output_v1_listener, + xdg_output); + + xdg_output->output = output; + + manager_info->info->roundtrip_needed = true; +} + +static void add_xdg_output_manager_v1_info(struct weston_info* info, + uint32_t id, uint32_t version) { + struct output_info* output; + struct xdg_output_manager_v1_info* manager = + (struct xdg_output_manager_v1_info*)malloc(sizeof *manager); + + wl_list_init(&manager->outputs); + manager->info = info; + + init_global_info(info, &manager->global, id, + zxdg_output_manager_v1_interface.name, version); + manager->global.print = print_xdg_output_manager_v1_info; + manager->global.destroy = destroy_xdg_output_manager_v1_info; + + manager->manager = (struct zxdg_output_manager_v1*)wl_registry_bind( + info->registry, id, &zxdg_output_manager_v1_interface, + version > 2 ? 2 : version); + + wl_list_for_each(output, &info->outputs, global_link) + add_xdg_output_v1_info(manager, output); + + info->xdg_output_manager_v1_info = manager; +} + +static void output_handle_geometry(void* data, struct wl_output* wl_output, + int32_t x, int32_t y, int32_t physical_width, + int32_t physical_height, int32_t subpixel, + const char* make, const char* model, + int32_t output_transform) {} + +static void output_handle_mode(void* data, struct wl_output* wl_output, + uint32_t flags, int32_t width, int32_t height, + int32_t refresh) {} + +static void output_handle_done(void* data, struct wl_output* wl_output) {} + +static void output_handle_scale(void* data, struct wl_output* wl_output, + int32_t scale) { + struct output_info* output = (struct output_info*)data; + + output->scale = scale; +} + +static const struct wl_output_listener output_listener = { + output_handle_geometry, + output_handle_mode, + output_handle_done, + output_handle_scale, +}; + +static void destroy_output_info(void* data) { + struct output_info* output = (struct output_info*)data; + + wl_output_destroy(output->output); +} + +static void add_output_info(struct weston_info* info, uint32_t id, + uint32_t version) { + struct output_info* output = (struct output_info*)malloc(sizeof *output); + + init_global_info(info, &output->global, id, "wl_output", version); + output->global.print = print_output_info; + output->global.destroy = destroy_output_info; + + output->version = MIN(version, 2); + output->scale = 1; + + output->output = (struct wl_output*)wl_registry_bind( + info->registry, id, &wl_output_interface, output->version); + wl_output_add_listener(output->output, &output_listener, output); + + info->roundtrip_needed = true; + wl_list_insert(&info->outputs, &output->global_link); + + if (info->xdg_output_manager_v1_info) { + add_xdg_output_v1_info(info->xdg_output_manager_v1_info, output); + } +} + +static void global_handler(void* data, struct wl_registry* registry, + uint32_t id, const char* interface, + uint32_t version) { + struct weston_info* info = (struct weston_info*)data; + + if (!strcmp(interface, "wl_output")) { + add_output_info(info, id, version); + } else if (!strcmp(interface, zxdg_output_manager_v1_interface.name)) { + add_xdg_output_manager_v1_info(info, id, version); + } +} + +static void global_remove_handler(void* data, struct wl_registry* registry, + uint32_t name) {} + +static const struct wl_registry_listener registry_listener = { + global_handler, global_remove_handler}; + +static void print_infos(struct wl_list* infos) { + struct global_info* info; + + wl_list_for_each(info, infos, link) { info->print(info); } +} + +static void destroy_info(void* data) { + struct global_info* global = (struct global_info*)data; + + global->destroy(data); + wl_list_remove(&global->link); + free(global->interface); + free(data); +} + +static void destroy_infos(struct wl_list* infos) { + struct global_info *info, *tmp; + wl_list_for_each_safe(info, tmp, infos, link) { destroy_info(info); } +} + +static void get_wayland_screen_info(struct wl_display* dpy) { + struct weston_info info = {0}; + info.display = dpy; + + info.xdg_output_manager_v1_info = NULL; + wl_list_init(&info.infos); + wl_list_init(&info.outputs); + + info.registry = wl_display_get_registry(info.display); + wl_registry_add_listener(info.registry, ®istry_listener, &info); + + do { + info.roundtrip_needed = false; + wl_display_roundtrip(info.display); + } while (info.roundtrip_needed); + + print_infos(&info.infos); + destroy_infos(&info.infos); + + wl_registry_destroy(info.registry); +} + +static bool 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) { + return false; + } + + get_egl_status((EGLNativeDisplayType)dpy, true); + get_wayland_screen_info(dpy); + + wl_display_disconnect(dpy); + return true; +} +#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(); + + bool result = false; +#ifdef MOZ_WAYLAND + if (!IsWaylandDisabled()) { + result = wayland_egltest(); + } +#endif +#ifdef MOZ_X11 + // TODO: --display command line argument is not properly handled + if (!result && IsX11EGLEnabled()) { + result = x11_egltest(); + } + if (!result) { + glxtest(); + } +#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(); + int rv = childgltest(); + close(pfd[1]); + _exit(rv); + } + + close(pfd[1]); + mozilla::widget::glxtest_pipe = pfd[0]; + mozilla::widget::glxtest_pid = pid; + return false; +} |