Wed, 31 Dec 2014 06:09:35 +0100
Cloned upstream origin tor-browser at tor-browser-31.3.0esr-4.5-1-build1
revision ID fc1c9ff7c1b2defdbc039f12214767608f46423f for hacking purpose.
michael@0 | 1 | /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- |
michael@0 | 2 | * vim: sw=2 ts=8 et : |
michael@0 | 3 | */ |
michael@0 | 4 | /* This Source Code Form is subject to the terms of the Mozilla Public |
michael@0 | 5 | * License, v. 2.0. If a copy of the MPL was not distributed with this |
michael@0 | 6 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ |
michael@0 | 7 | |
michael@0 | 8 | |
michael@0 | 9 | ////////////////////////////////////////////////////////////////////////////// |
michael@0 | 10 | // |
michael@0 | 11 | // Explanation: See bug 639842. Safely getting GL driver info on X11 is hard, because the only way to do |
michael@0 | 12 | // that is to create a GL context and call glGetString(), but with bad drivers, |
michael@0 | 13 | // just creating a GL context may crash. |
michael@0 | 14 | // |
michael@0 | 15 | // This file implements the idea to do that in a separate process. |
michael@0 | 16 | // |
michael@0 | 17 | // The only non-static function here is fire_glxtest_process(). It creates a pipe, publishes its 'read' end as the |
michael@0 | 18 | // mozilla::widget::glxtest_pipe global variable, forks, and runs that GLX probe in the child process, |
michael@0 | 19 | // which runs the glxtest() static function. This creates a X connection, a GLX context, calls glGetString, and writes that |
michael@0 | 20 | // to the 'write' end of the pipe. |
michael@0 | 21 | |
michael@0 | 22 | #include <cstdio> |
michael@0 | 23 | #include <cstdlib> |
michael@0 | 24 | #include <unistd.h> |
michael@0 | 25 | #include <dlfcn.h> |
michael@0 | 26 | #include "nscore.h" |
michael@0 | 27 | #include <fcntl.h> |
michael@0 | 28 | #include "stdint.h" |
michael@0 | 29 | |
michael@0 | 30 | #ifdef __SUNPRO_CC |
michael@0 | 31 | #include <stdio.h> |
michael@0 | 32 | #endif |
michael@0 | 33 | |
michael@0 | 34 | #include "X11/Xlib.h" |
michael@0 | 35 | #include "X11/Xutil.h" |
michael@0 | 36 | |
michael@0 | 37 | // stuff from glx.h |
michael@0 | 38 | typedef struct __GLXcontextRec *GLXContext; |
michael@0 | 39 | typedef XID GLXPixmap; |
michael@0 | 40 | typedef XID GLXDrawable; |
michael@0 | 41 | /* GLX 1.3 and later */ |
michael@0 | 42 | typedef struct __GLXFBConfigRec *GLXFBConfig; |
michael@0 | 43 | typedef XID GLXFBConfigID; |
michael@0 | 44 | typedef XID GLXContextID; |
michael@0 | 45 | typedef XID GLXWindow; |
michael@0 | 46 | typedef XID GLXPbuffer; |
michael@0 | 47 | #define GLX_RGBA 4 |
michael@0 | 48 | #define GLX_RED_SIZE 8 |
michael@0 | 49 | #define GLX_GREEN_SIZE 9 |
michael@0 | 50 | #define GLX_BLUE_SIZE 10 |
michael@0 | 51 | |
michael@0 | 52 | // stuff from gl.h |
michael@0 | 53 | typedef uint8_t GLubyte; |
michael@0 | 54 | typedef uint32_t GLenum; |
michael@0 | 55 | #define GL_VENDOR 0x1F00 |
michael@0 | 56 | #define GL_RENDERER 0x1F01 |
michael@0 | 57 | #define GL_VERSION 0x1F02 |
michael@0 | 58 | |
michael@0 | 59 | namespace mozilla { |
michael@0 | 60 | namespace widget { |
michael@0 | 61 | // the read end of the pipe, which will be used by GfxInfo |
michael@0 | 62 | extern int glxtest_pipe; |
michael@0 | 63 | // the PID of the glxtest process, to pass to waitpid() |
michael@0 | 64 | extern pid_t glxtest_pid; |
michael@0 | 65 | } |
michael@0 | 66 | } |
michael@0 | 67 | |
michael@0 | 68 | // the write end of the pipe, which we're going to write to |
michael@0 | 69 | static int write_end_of_the_pipe = -1; |
michael@0 | 70 | |
michael@0 | 71 | // C++ standard collides with C standard in that it doesn't allow casting void* to function pointer types. |
michael@0 | 72 | // So the work-around is to convert first to size_t. |
michael@0 | 73 | // http://www.trilithium.com/johan/2004/12/problem-with-dlsym/ |
michael@0 | 74 | template<typename func_ptr_type> |
michael@0 | 75 | static func_ptr_type cast(void *ptr) |
michael@0 | 76 | { |
michael@0 | 77 | return reinterpret_cast<func_ptr_type>( |
michael@0 | 78 | reinterpret_cast<size_t>(ptr) |
michael@0 | 79 | ); |
michael@0 | 80 | } |
michael@0 | 81 | |
michael@0 | 82 | static void fatal_error(const char *str) |
michael@0 | 83 | { |
michael@0 | 84 | write(write_end_of_the_pipe, str, strlen(str)); |
michael@0 | 85 | write(write_end_of_the_pipe, "\n", 1); |
michael@0 | 86 | _exit(EXIT_FAILURE); |
michael@0 | 87 | } |
michael@0 | 88 | |
michael@0 | 89 | static int |
michael@0 | 90 | x_error_handler(Display *, XErrorEvent *ev) |
michael@0 | 91 | { |
michael@0 | 92 | enum { bufsize = 1024 }; |
michael@0 | 93 | char buf[bufsize]; |
michael@0 | 94 | int length = snprintf(buf, bufsize, |
michael@0 | 95 | "X error occurred in GLX probe, error_code=%d, request_code=%d, minor_code=%d\n", |
michael@0 | 96 | ev->error_code, |
michael@0 | 97 | ev->request_code, |
michael@0 | 98 | ev->minor_code); |
michael@0 | 99 | write(write_end_of_the_pipe, buf, length); |
michael@0 | 100 | _exit(EXIT_FAILURE); |
michael@0 | 101 | return 0; |
michael@0 | 102 | } |
michael@0 | 103 | |
michael@0 | 104 | |
michael@0 | 105 | // glxtest is declared inside extern "C" so that the name is not mangled. |
michael@0 | 106 | // The name is used in build/valgrind/x86_64-redhat-linux-gnu.sup to suppress |
michael@0 | 107 | // memory leak errors because we run it inside a short lived fork and we don't |
michael@0 | 108 | // care about leaking memory |
michael@0 | 109 | extern "C" { |
michael@0 | 110 | |
michael@0 | 111 | void glxtest() |
michael@0 | 112 | { |
michael@0 | 113 | // we want to redirect to /dev/null stdout, stderr, and while we're at it, |
michael@0 | 114 | // any PR logging file descriptors. To that effect, we redirect all positive |
michael@0 | 115 | // file descriptors up to what open() returns here. In particular, 1 is stdout and 2 is stderr. |
michael@0 | 116 | int fd = open("/dev/null", O_WRONLY); |
michael@0 | 117 | for (int i = 1; i < fd; i++) |
michael@0 | 118 | dup2(fd, i); |
michael@0 | 119 | close(fd); |
michael@0 | 120 | |
michael@0 | 121 | if (getenv("MOZ_AVOID_OPENGL_ALTOGETHER")) |
michael@0 | 122 | fatal_error("The MOZ_AVOID_OPENGL_ALTOGETHER environment variable is defined"); |
michael@0 | 123 | |
michael@0 | 124 | ///// Open libGL and load needed symbols ///// |
michael@0 | 125 | #ifdef __OpenBSD__ |
michael@0 | 126 | #define LIBGL_FILENAME "libGL.so" |
michael@0 | 127 | #else |
michael@0 | 128 | #define LIBGL_FILENAME "libGL.so.1" |
michael@0 | 129 | #endif |
michael@0 | 130 | void *libgl = dlopen(LIBGL_FILENAME, RTLD_LAZY); |
michael@0 | 131 | if (!libgl) |
michael@0 | 132 | fatal_error("Unable to load " LIBGL_FILENAME); |
michael@0 | 133 | |
michael@0 | 134 | typedef void* (* PFNGLXGETPROCADDRESS) (const char *); |
michael@0 | 135 | PFNGLXGETPROCADDRESS glXGetProcAddress = cast<PFNGLXGETPROCADDRESS>(dlsym(libgl, "glXGetProcAddress")); |
michael@0 | 136 | |
michael@0 | 137 | if (!glXGetProcAddress) |
michael@0 | 138 | fatal_error("Unable to find glXGetProcAddress in " LIBGL_FILENAME); |
michael@0 | 139 | |
michael@0 | 140 | typedef GLXFBConfig* (* PFNGLXQUERYEXTENSION) (Display *, int *, int *); |
michael@0 | 141 | PFNGLXQUERYEXTENSION glXQueryExtension = cast<PFNGLXQUERYEXTENSION>(glXGetProcAddress("glXQueryExtension")); |
michael@0 | 142 | |
michael@0 | 143 | typedef GLXFBConfig* (* PFNGLXQUERYVERSION) (Display *, int *, int *); |
michael@0 | 144 | PFNGLXQUERYVERSION glXQueryVersion = cast<PFNGLXQUERYVERSION>(dlsym(libgl, "glXQueryVersion")); |
michael@0 | 145 | |
michael@0 | 146 | typedef XVisualInfo* (* PFNGLXCHOOSEVISUAL) (Display *, int, int *); |
michael@0 | 147 | PFNGLXCHOOSEVISUAL glXChooseVisual = cast<PFNGLXCHOOSEVISUAL>(glXGetProcAddress("glXChooseVisual")); |
michael@0 | 148 | |
michael@0 | 149 | typedef GLXContext (* PFNGLXCREATECONTEXT) (Display *, XVisualInfo *, GLXContext, Bool); |
michael@0 | 150 | PFNGLXCREATECONTEXT glXCreateContext = cast<PFNGLXCREATECONTEXT>(glXGetProcAddress("glXCreateContext")); |
michael@0 | 151 | |
michael@0 | 152 | typedef Bool (* PFNGLXMAKECURRENT) (Display*, GLXDrawable, GLXContext); |
michael@0 | 153 | PFNGLXMAKECURRENT glXMakeCurrent = cast<PFNGLXMAKECURRENT>(glXGetProcAddress("glXMakeCurrent")); |
michael@0 | 154 | |
michael@0 | 155 | typedef void (* PFNGLXDESTROYCONTEXT) (Display*, GLXContext); |
michael@0 | 156 | PFNGLXDESTROYCONTEXT glXDestroyContext = cast<PFNGLXDESTROYCONTEXT>(glXGetProcAddress("glXDestroyContext")); |
michael@0 | 157 | |
michael@0 | 158 | typedef GLubyte* (* PFNGLGETSTRING) (GLenum); |
michael@0 | 159 | PFNGLGETSTRING glGetString = cast<PFNGLGETSTRING>(glXGetProcAddress("glGetString")); |
michael@0 | 160 | |
michael@0 | 161 | if (!glXQueryExtension || |
michael@0 | 162 | !glXQueryVersion || |
michael@0 | 163 | !glXChooseVisual || |
michael@0 | 164 | !glXCreateContext || |
michael@0 | 165 | !glXMakeCurrent || |
michael@0 | 166 | !glXDestroyContext || |
michael@0 | 167 | !glGetString) |
michael@0 | 168 | { |
michael@0 | 169 | fatal_error("glXGetProcAddress couldn't find required functions"); |
michael@0 | 170 | } |
michael@0 | 171 | ///// Open a connection to the X server ///// |
michael@0 | 172 | Display *dpy = XOpenDisplay(nullptr); |
michael@0 | 173 | if (!dpy) |
michael@0 | 174 | fatal_error("Unable to open a connection to the X server"); |
michael@0 | 175 | |
michael@0 | 176 | ///// Check that the GLX extension is present ///// |
michael@0 | 177 | if (!glXQueryExtension(dpy, nullptr, nullptr)) |
michael@0 | 178 | fatal_error("GLX extension missing"); |
michael@0 | 179 | |
michael@0 | 180 | XSetErrorHandler(x_error_handler); |
michael@0 | 181 | |
michael@0 | 182 | ///// Get a visual ///// |
michael@0 | 183 | int attribs[] = { |
michael@0 | 184 | GLX_RGBA, |
michael@0 | 185 | GLX_RED_SIZE, 1, |
michael@0 | 186 | GLX_GREEN_SIZE, 1, |
michael@0 | 187 | GLX_BLUE_SIZE, 1, |
michael@0 | 188 | None }; |
michael@0 | 189 | XVisualInfo *vInfo = glXChooseVisual(dpy, DefaultScreen(dpy), attribs); |
michael@0 | 190 | if (!vInfo) |
michael@0 | 191 | fatal_error("No visuals found"); |
michael@0 | 192 | |
michael@0 | 193 | // using a X11 Window instead of a GLXPixmap does not crash |
michael@0 | 194 | // fglrx in indirect rendering. bug 680644 |
michael@0 | 195 | Window window; |
michael@0 | 196 | XSetWindowAttributes swa; |
michael@0 | 197 | swa.colormap = XCreateColormap(dpy, RootWindow(dpy, vInfo->screen), |
michael@0 | 198 | vInfo->visual, AllocNone); |
michael@0 | 199 | |
michael@0 | 200 | swa.border_pixel = 0; |
michael@0 | 201 | window = XCreateWindow(dpy, RootWindow(dpy, vInfo->screen), |
michael@0 | 202 | 0, 0, 16, 16, |
michael@0 | 203 | 0, vInfo->depth, InputOutput, vInfo->visual, |
michael@0 | 204 | CWBorderPixel | CWColormap, &swa); |
michael@0 | 205 | |
michael@0 | 206 | ///// Get a GL context and make it current ////// |
michael@0 | 207 | GLXContext context = glXCreateContext(dpy, vInfo, nullptr, True); |
michael@0 | 208 | glXMakeCurrent(dpy, window, context); |
michael@0 | 209 | |
michael@0 | 210 | ///// Look for this symbol to determine texture_from_pixmap support ///// |
michael@0 | 211 | void* glXBindTexImageEXT = glXGetProcAddress("glXBindTexImageEXT"); |
michael@0 | 212 | |
michael@0 | 213 | ///// Get GL vendor/renderer/versions strings ///// |
michael@0 | 214 | enum { bufsize = 1024 }; |
michael@0 | 215 | char buf[bufsize]; |
michael@0 | 216 | const GLubyte *vendorString = glGetString(GL_VENDOR); |
michael@0 | 217 | const GLubyte *rendererString = glGetString(GL_RENDERER); |
michael@0 | 218 | const GLubyte *versionString = glGetString(GL_VERSION); |
michael@0 | 219 | |
michael@0 | 220 | if (!vendorString || !rendererString || !versionString) |
michael@0 | 221 | fatal_error("glGetString returned null"); |
michael@0 | 222 | |
michael@0 | 223 | int length = snprintf(buf, bufsize, |
michael@0 | 224 | "VENDOR\n%s\nRENDERER\n%s\nVERSION\n%s\nTFP\n%s\n", |
michael@0 | 225 | vendorString, |
michael@0 | 226 | rendererString, |
michael@0 | 227 | versionString, |
michael@0 | 228 | glXBindTexImageEXT ? "TRUE" : "FALSE"); |
michael@0 | 229 | if (length >= bufsize) |
michael@0 | 230 | fatal_error("GL strings length too large for buffer size"); |
michael@0 | 231 | |
michael@0 | 232 | ///// Clean up. Indeed, the parent process might fail to kill us (e.g. if it doesn't need to check GL info) |
michael@0 | 233 | ///// so we might be staying alive for longer than expected, so it's important to consume as little memory as |
michael@0 | 234 | ///// possible. Also we want to check that we're able to do that too without generating X errors. |
michael@0 | 235 | glXMakeCurrent(dpy, None, nullptr); // must release the GL context before destroying it |
michael@0 | 236 | glXDestroyContext(dpy, context); |
michael@0 | 237 | XDestroyWindow(dpy, window); |
michael@0 | 238 | XFreeColormap(dpy, swa.colormap); |
michael@0 | 239 | |
michael@0 | 240 | #ifdef NS_FREE_PERMANENT_DATA // conditionally defined in nscore.h, don't forget to #include it above |
michael@0 | 241 | XCloseDisplay(dpy); |
michael@0 | 242 | #else |
michael@0 | 243 | // This XSync call wanted to be instead: |
michael@0 | 244 | // XCloseDisplay(dpy); |
michael@0 | 245 | // but this can cause 1-minute stalls on certain setups using Nouveau, see bug 973192 |
michael@0 | 246 | XSync(dpy, False); |
michael@0 | 247 | #endif |
michael@0 | 248 | |
michael@0 | 249 | dlclose(libgl); |
michael@0 | 250 | |
michael@0 | 251 | ///// Finally write data to the pipe |
michael@0 | 252 | write(write_end_of_the_pipe, buf, length); |
michael@0 | 253 | } |
michael@0 | 254 | |
michael@0 | 255 | } |
michael@0 | 256 | |
michael@0 | 257 | /** \returns true in the child glxtest process, false in the parent process */ |
michael@0 | 258 | bool fire_glxtest_process() |
michael@0 | 259 | { |
michael@0 | 260 | int pfd[2]; |
michael@0 | 261 | if (pipe(pfd) == -1) { |
michael@0 | 262 | perror("pipe"); |
michael@0 | 263 | return false; |
michael@0 | 264 | } |
michael@0 | 265 | pid_t pid = fork(); |
michael@0 | 266 | if (pid < 0) { |
michael@0 | 267 | perror("fork"); |
michael@0 | 268 | close(pfd[0]); |
michael@0 | 269 | close(pfd[1]); |
michael@0 | 270 | return false; |
michael@0 | 271 | } |
michael@0 | 272 | // The child exits early to avoid running the full shutdown sequence and avoid conflicting with threads |
michael@0 | 273 | // we have already spawned (like the profiler). |
michael@0 | 274 | if (pid == 0) { |
michael@0 | 275 | close(pfd[0]); |
michael@0 | 276 | write_end_of_the_pipe = pfd[1]; |
michael@0 | 277 | glxtest(); |
michael@0 | 278 | close(pfd[1]); |
michael@0 | 279 | _exit(0); |
michael@0 | 280 | } |
michael@0 | 281 | |
michael@0 | 282 | close(pfd[1]); |
michael@0 | 283 | mozilla::widget::glxtest_pipe = pfd[0]; |
michael@0 | 284 | mozilla::widget::glxtest_pid = pid; |
michael@0 | 285 | return false; |
michael@0 | 286 | } |