michael@0: /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- michael@0: * vim: sw=2 ts=8 et : michael@0: */ michael@0: /* This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this michael@0: * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: michael@0: michael@0: ////////////////////////////////////////////////////////////////////////////// michael@0: // michael@0: // Explanation: See bug 639842. Safely getting GL driver info on X11 is hard, because the only way to do michael@0: // that is to create a GL context and call glGetString(), but with bad drivers, michael@0: // just creating a GL context may crash. michael@0: // michael@0: // This file implements the idea to do that in a separate process. michael@0: // michael@0: // The only non-static function here is fire_glxtest_process(). It creates a pipe, publishes its 'read' end as the michael@0: // mozilla::widget::glxtest_pipe global variable, forks, and runs that GLX probe in the child process, michael@0: // which runs the glxtest() static function. This creates a X connection, a GLX context, calls glGetString, and writes that michael@0: // to the 'write' end of the pipe. michael@0: michael@0: #include michael@0: #include michael@0: #include michael@0: #include michael@0: #include "nscore.h" michael@0: #include michael@0: #include "stdint.h" michael@0: michael@0: #ifdef __SUNPRO_CC michael@0: #include michael@0: #endif michael@0: michael@0: #include "X11/Xlib.h" michael@0: #include "X11/Xutil.h" michael@0: michael@0: // stuff from glx.h michael@0: typedef struct __GLXcontextRec *GLXContext; michael@0: typedef XID GLXPixmap; michael@0: typedef XID GLXDrawable; michael@0: /* GLX 1.3 and later */ michael@0: typedef struct __GLXFBConfigRec *GLXFBConfig; michael@0: typedef XID GLXFBConfigID; michael@0: typedef XID GLXContextID; michael@0: typedef XID GLXWindow; michael@0: typedef XID GLXPbuffer; michael@0: #define GLX_RGBA 4 michael@0: #define GLX_RED_SIZE 8 michael@0: #define GLX_GREEN_SIZE 9 michael@0: #define GLX_BLUE_SIZE 10 michael@0: michael@0: // stuff from gl.h michael@0: typedef uint8_t GLubyte; michael@0: typedef uint32_t GLenum; michael@0: #define GL_VENDOR 0x1F00 michael@0: #define GL_RENDERER 0x1F01 michael@0: #define GL_VERSION 0x1F02 michael@0: michael@0: namespace mozilla { michael@0: namespace widget { michael@0: // the read end of the pipe, which will be used by GfxInfo michael@0: extern int glxtest_pipe; michael@0: // the PID of the glxtest process, to pass to waitpid() michael@0: extern pid_t glxtest_pid; michael@0: } michael@0: } michael@0: michael@0: // the write end of the pipe, which we're going to write to michael@0: static int write_end_of_the_pipe = -1; michael@0: michael@0: // C++ standard collides with C standard in that it doesn't allow casting void* to function pointer types. michael@0: // So the work-around is to convert first to size_t. michael@0: // http://www.trilithium.com/johan/2004/12/problem-with-dlsym/ michael@0: template michael@0: static func_ptr_type cast(void *ptr) michael@0: { michael@0: return reinterpret_cast( michael@0: reinterpret_cast(ptr) michael@0: ); michael@0: } michael@0: michael@0: static void fatal_error(const char *str) michael@0: { michael@0: write(write_end_of_the_pipe, str, strlen(str)); michael@0: write(write_end_of_the_pipe, "\n", 1); michael@0: _exit(EXIT_FAILURE); michael@0: } michael@0: michael@0: static int michael@0: x_error_handler(Display *, XErrorEvent *ev) michael@0: { michael@0: enum { bufsize = 1024 }; michael@0: char buf[bufsize]; michael@0: int length = snprintf(buf, bufsize, michael@0: "X error occurred in GLX probe, error_code=%d, request_code=%d, minor_code=%d\n", michael@0: ev->error_code, michael@0: ev->request_code, michael@0: ev->minor_code); michael@0: write(write_end_of_the_pipe, buf, length); michael@0: _exit(EXIT_FAILURE); michael@0: return 0; michael@0: } michael@0: michael@0: michael@0: // glxtest is declared inside extern "C" so that the name is not mangled. michael@0: // The name is used in build/valgrind/x86_64-redhat-linux-gnu.sup to suppress michael@0: // memory leak errors because we run it inside a short lived fork and we don't michael@0: // care about leaking memory michael@0: extern "C" { michael@0: michael@0: void glxtest() michael@0: { michael@0: // we want to redirect to /dev/null stdout, stderr, and while we're at it, michael@0: // any PR logging file descriptors. To that effect, we redirect all positive michael@0: // file descriptors up to what open() returns here. In particular, 1 is stdout and 2 is stderr. michael@0: int fd = open("/dev/null", O_WRONLY); michael@0: for (int i = 1; i < fd; i++) michael@0: dup2(fd, i); michael@0: close(fd); michael@0: michael@0: if (getenv("MOZ_AVOID_OPENGL_ALTOGETHER")) michael@0: fatal_error("The MOZ_AVOID_OPENGL_ALTOGETHER environment variable is defined"); michael@0: michael@0: ///// Open libGL and load needed symbols ///// michael@0: #ifdef __OpenBSD__ michael@0: #define LIBGL_FILENAME "libGL.so" michael@0: #else michael@0: #define LIBGL_FILENAME "libGL.so.1" michael@0: #endif michael@0: void *libgl = dlopen(LIBGL_FILENAME, RTLD_LAZY); michael@0: if (!libgl) michael@0: fatal_error("Unable to load " LIBGL_FILENAME); michael@0: michael@0: typedef void* (* PFNGLXGETPROCADDRESS) (const char *); michael@0: PFNGLXGETPROCADDRESS glXGetProcAddress = cast(dlsym(libgl, "glXGetProcAddress")); michael@0: michael@0: if (!glXGetProcAddress) michael@0: fatal_error("Unable to find glXGetProcAddress in " LIBGL_FILENAME); michael@0: michael@0: typedef GLXFBConfig* (* PFNGLXQUERYEXTENSION) (Display *, int *, int *); michael@0: PFNGLXQUERYEXTENSION glXQueryExtension = cast(glXGetProcAddress("glXQueryExtension")); michael@0: michael@0: typedef GLXFBConfig* (* PFNGLXQUERYVERSION) (Display *, int *, int *); michael@0: PFNGLXQUERYVERSION glXQueryVersion = cast(dlsym(libgl, "glXQueryVersion")); michael@0: michael@0: typedef XVisualInfo* (* PFNGLXCHOOSEVISUAL) (Display *, int, int *); michael@0: PFNGLXCHOOSEVISUAL glXChooseVisual = cast(glXGetProcAddress("glXChooseVisual")); michael@0: michael@0: typedef GLXContext (* PFNGLXCREATECONTEXT) (Display *, XVisualInfo *, GLXContext, Bool); michael@0: PFNGLXCREATECONTEXT glXCreateContext = cast(glXGetProcAddress("glXCreateContext")); michael@0: michael@0: typedef Bool (* PFNGLXMAKECURRENT) (Display*, GLXDrawable, GLXContext); michael@0: PFNGLXMAKECURRENT glXMakeCurrent = cast(glXGetProcAddress("glXMakeCurrent")); michael@0: michael@0: typedef void (* PFNGLXDESTROYCONTEXT) (Display*, GLXContext); michael@0: PFNGLXDESTROYCONTEXT glXDestroyContext = cast(glXGetProcAddress("glXDestroyContext")); michael@0: michael@0: typedef GLubyte* (* PFNGLGETSTRING) (GLenum); michael@0: PFNGLGETSTRING glGetString = cast(glXGetProcAddress("glGetString")); michael@0: michael@0: if (!glXQueryExtension || michael@0: !glXQueryVersion || michael@0: !glXChooseVisual || michael@0: !glXCreateContext || michael@0: !glXMakeCurrent || michael@0: !glXDestroyContext || michael@0: !glGetString) michael@0: { michael@0: fatal_error("glXGetProcAddress couldn't find required functions"); michael@0: } michael@0: ///// Open a connection to the X server ///// michael@0: Display *dpy = XOpenDisplay(nullptr); michael@0: if (!dpy) michael@0: fatal_error("Unable to open a connection to the X server"); michael@0: michael@0: ///// Check that the GLX extension is present ///// michael@0: if (!glXQueryExtension(dpy, nullptr, nullptr)) michael@0: fatal_error("GLX extension missing"); michael@0: michael@0: XSetErrorHandler(x_error_handler); michael@0: michael@0: ///// Get a visual ///// michael@0: int attribs[] = { michael@0: GLX_RGBA, michael@0: GLX_RED_SIZE, 1, michael@0: GLX_GREEN_SIZE, 1, michael@0: GLX_BLUE_SIZE, 1, michael@0: None }; michael@0: XVisualInfo *vInfo = glXChooseVisual(dpy, DefaultScreen(dpy), attribs); michael@0: if (!vInfo) michael@0: fatal_error("No visuals found"); michael@0: michael@0: // using a X11 Window instead of a GLXPixmap does not crash michael@0: // fglrx in indirect rendering. bug 680644 michael@0: Window window; michael@0: XSetWindowAttributes swa; michael@0: swa.colormap = XCreateColormap(dpy, RootWindow(dpy, vInfo->screen), michael@0: vInfo->visual, AllocNone); michael@0: michael@0: swa.border_pixel = 0; michael@0: window = XCreateWindow(dpy, RootWindow(dpy, vInfo->screen), michael@0: 0, 0, 16, 16, michael@0: 0, vInfo->depth, InputOutput, vInfo->visual, michael@0: CWBorderPixel | CWColormap, &swa); michael@0: michael@0: ///// Get a GL context and make it current ////// michael@0: GLXContext context = glXCreateContext(dpy, vInfo, nullptr, True); michael@0: glXMakeCurrent(dpy, window, context); michael@0: michael@0: ///// Look for this symbol to determine texture_from_pixmap support ///// michael@0: void* glXBindTexImageEXT = glXGetProcAddress("glXBindTexImageEXT"); michael@0: michael@0: ///// Get GL vendor/renderer/versions strings ///// michael@0: enum { bufsize = 1024 }; michael@0: char buf[bufsize]; michael@0: const GLubyte *vendorString = glGetString(GL_VENDOR); michael@0: const GLubyte *rendererString = glGetString(GL_RENDERER); michael@0: const GLubyte *versionString = glGetString(GL_VERSION); michael@0: michael@0: if (!vendorString || !rendererString || !versionString) michael@0: fatal_error("glGetString returned null"); michael@0: michael@0: int length = snprintf(buf, bufsize, michael@0: "VENDOR\n%s\nRENDERER\n%s\nVERSION\n%s\nTFP\n%s\n", michael@0: vendorString, michael@0: rendererString, michael@0: versionString, michael@0: glXBindTexImageEXT ? "TRUE" : "FALSE"); michael@0: if (length >= bufsize) michael@0: fatal_error("GL strings length too large for buffer size"); michael@0: michael@0: ///// Clean up. Indeed, the parent process might fail to kill us (e.g. if it doesn't need to check GL info) michael@0: ///// so we might be staying alive for longer than expected, so it's important to consume as little memory as michael@0: ///// possible. Also we want to check that we're able to do that too without generating X errors. michael@0: glXMakeCurrent(dpy, None, nullptr); // must release the GL context before destroying it michael@0: glXDestroyContext(dpy, context); michael@0: XDestroyWindow(dpy, window); michael@0: XFreeColormap(dpy, swa.colormap); michael@0: michael@0: #ifdef NS_FREE_PERMANENT_DATA // conditionally defined in nscore.h, don't forget to #include it above michael@0: XCloseDisplay(dpy); michael@0: #else michael@0: // This XSync call wanted to be instead: michael@0: // XCloseDisplay(dpy); michael@0: // but this can cause 1-minute stalls on certain setups using Nouveau, see bug 973192 michael@0: XSync(dpy, False); michael@0: #endif michael@0: michael@0: dlclose(libgl); michael@0: michael@0: ///// Finally write data to the pipe michael@0: write(write_end_of_the_pipe, buf, length); michael@0: } michael@0: michael@0: } michael@0: michael@0: /** \returns true in the child glxtest process, false in the parent process */ michael@0: bool fire_glxtest_process() michael@0: { michael@0: int pfd[2]; michael@0: if (pipe(pfd) == -1) { michael@0: perror("pipe"); michael@0: return false; michael@0: } michael@0: pid_t pid = fork(); michael@0: if (pid < 0) { michael@0: perror("fork"); michael@0: close(pfd[0]); michael@0: close(pfd[1]); michael@0: return false; michael@0: } michael@0: // The child exits early to avoid running the full shutdown sequence and avoid conflicting with threads michael@0: // we have already spawned (like the profiler). michael@0: if (pid == 0) { michael@0: close(pfd[0]); michael@0: write_end_of_the_pipe = pfd[1]; michael@0: glxtest(); michael@0: close(pfd[1]); michael@0: _exit(0); michael@0: } michael@0: michael@0: close(pfd[1]); michael@0: mozilla::widget::glxtest_pipe = pfd[0]; michael@0: mozilla::widget::glxtest_pid = pid; michael@0: return false; michael@0: }