1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 1.2 +++ b/toolkit/xre/glxtest.cpp Wed Dec 31 06:09:35 2014 +0100 1.3 @@ -0,0 +1,286 @@ 1.4 +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- 1.5 + * vim: sw=2 ts=8 et : 1.6 + */ 1.7 +/* This Source Code Form is subject to the terms of the Mozilla Public 1.8 + * License, v. 2.0. If a copy of the MPL was not distributed with this 1.9 + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 1.10 + 1.11 + 1.12 +////////////////////////////////////////////////////////////////////////////// 1.13 +// 1.14 +// Explanation: See bug 639842. Safely getting GL driver info on X11 is hard, because the only way to do 1.15 +// that is to create a GL context and call glGetString(), but with bad drivers, 1.16 +// just creating a GL context may crash. 1.17 +// 1.18 +// This file implements the idea to do that in a separate process. 1.19 +// 1.20 +// The only non-static function here is fire_glxtest_process(). It creates a pipe, publishes its 'read' end as the 1.21 +// mozilla::widget::glxtest_pipe global variable, forks, and runs that GLX probe in the child process, 1.22 +// which runs the glxtest() static function. This creates a X connection, a GLX context, calls glGetString, and writes that 1.23 +// to the 'write' end of the pipe. 1.24 + 1.25 +#include <cstdio> 1.26 +#include <cstdlib> 1.27 +#include <unistd.h> 1.28 +#include <dlfcn.h> 1.29 +#include "nscore.h" 1.30 +#include <fcntl.h> 1.31 +#include "stdint.h" 1.32 + 1.33 +#ifdef __SUNPRO_CC 1.34 +#include <stdio.h> 1.35 +#endif 1.36 + 1.37 +#include "X11/Xlib.h" 1.38 +#include "X11/Xutil.h" 1.39 + 1.40 +// stuff from glx.h 1.41 +typedef struct __GLXcontextRec *GLXContext; 1.42 +typedef XID GLXPixmap; 1.43 +typedef XID GLXDrawable; 1.44 +/* GLX 1.3 and later */ 1.45 +typedef struct __GLXFBConfigRec *GLXFBConfig; 1.46 +typedef XID GLXFBConfigID; 1.47 +typedef XID GLXContextID; 1.48 +typedef XID GLXWindow; 1.49 +typedef XID GLXPbuffer; 1.50 +#define GLX_RGBA 4 1.51 +#define GLX_RED_SIZE 8 1.52 +#define GLX_GREEN_SIZE 9 1.53 +#define GLX_BLUE_SIZE 10 1.54 + 1.55 +// stuff from gl.h 1.56 +typedef uint8_t GLubyte; 1.57 +typedef uint32_t GLenum; 1.58 +#define GL_VENDOR 0x1F00 1.59 +#define GL_RENDERER 0x1F01 1.60 +#define GL_VERSION 0x1F02 1.61 + 1.62 +namespace mozilla { 1.63 +namespace widget { 1.64 +// the read end of the pipe, which will be used by GfxInfo 1.65 +extern int glxtest_pipe; 1.66 +// the PID of the glxtest process, to pass to waitpid() 1.67 +extern pid_t glxtest_pid; 1.68 +} 1.69 +} 1.70 + 1.71 +// the write end of the pipe, which we're going to write to 1.72 +static int write_end_of_the_pipe = -1; 1.73 + 1.74 +// C++ standard collides with C standard in that it doesn't allow casting void* to function pointer types. 1.75 +// So the work-around is to convert first to size_t. 1.76 +// http://www.trilithium.com/johan/2004/12/problem-with-dlsym/ 1.77 +template<typename func_ptr_type> 1.78 +static func_ptr_type cast(void *ptr) 1.79 +{ 1.80 + return reinterpret_cast<func_ptr_type>( 1.81 + reinterpret_cast<size_t>(ptr) 1.82 + ); 1.83 +} 1.84 + 1.85 +static void fatal_error(const char *str) 1.86 +{ 1.87 + write(write_end_of_the_pipe, str, strlen(str)); 1.88 + write(write_end_of_the_pipe, "\n", 1); 1.89 + _exit(EXIT_FAILURE); 1.90 +} 1.91 + 1.92 +static int 1.93 +x_error_handler(Display *, XErrorEvent *ev) 1.94 +{ 1.95 + enum { bufsize = 1024 }; 1.96 + char buf[bufsize]; 1.97 + int length = snprintf(buf, bufsize, 1.98 + "X error occurred in GLX probe, error_code=%d, request_code=%d, minor_code=%d\n", 1.99 + ev->error_code, 1.100 + ev->request_code, 1.101 + ev->minor_code); 1.102 + write(write_end_of_the_pipe, buf, length); 1.103 + _exit(EXIT_FAILURE); 1.104 + return 0; 1.105 +} 1.106 + 1.107 + 1.108 +// glxtest is declared inside extern "C" so that the name is not mangled. 1.109 +// The name is used in build/valgrind/x86_64-redhat-linux-gnu.sup to suppress 1.110 +// memory leak errors because we run it inside a short lived fork and we don't 1.111 +// care about leaking memory 1.112 +extern "C" { 1.113 + 1.114 +void glxtest() 1.115 +{ 1.116 + // we want to redirect to /dev/null stdout, stderr, and while we're at it, 1.117 + // any PR logging file descriptors. To that effect, we redirect all positive 1.118 + // file descriptors up to what open() returns here. In particular, 1 is stdout and 2 is stderr. 1.119 + int fd = open("/dev/null", O_WRONLY); 1.120 + for (int i = 1; i < fd; i++) 1.121 + dup2(fd, i); 1.122 + close(fd); 1.123 + 1.124 + if (getenv("MOZ_AVOID_OPENGL_ALTOGETHER")) 1.125 + fatal_error("The MOZ_AVOID_OPENGL_ALTOGETHER environment variable is defined"); 1.126 + 1.127 + ///// Open libGL and load needed symbols ///// 1.128 +#ifdef __OpenBSD__ 1.129 + #define LIBGL_FILENAME "libGL.so" 1.130 +#else 1.131 + #define LIBGL_FILENAME "libGL.so.1" 1.132 +#endif 1.133 + void *libgl = dlopen(LIBGL_FILENAME, RTLD_LAZY); 1.134 + if (!libgl) 1.135 + fatal_error("Unable to load " LIBGL_FILENAME); 1.136 + 1.137 + typedef void* (* PFNGLXGETPROCADDRESS) (const char *); 1.138 + PFNGLXGETPROCADDRESS glXGetProcAddress = cast<PFNGLXGETPROCADDRESS>(dlsym(libgl, "glXGetProcAddress")); 1.139 + 1.140 + if (!glXGetProcAddress) 1.141 + fatal_error("Unable to find glXGetProcAddress in " LIBGL_FILENAME); 1.142 + 1.143 + typedef GLXFBConfig* (* PFNGLXQUERYEXTENSION) (Display *, int *, int *); 1.144 + PFNGLXQUERYEXTENSION glXQueryExtension = cast<PFNGLXQUERYEXTENSION>(glXGetProcAddress("glXQueryExtension")); 1.145 + 1.146 + typedef GLXFBConfig* (* PFNGLXQUERYVERSION) (Display *, int *, int *); 1.147 + PFNGLXQUERYVERSION glXQueryVersion = cast<PFNGLXQUERYVERSION>(dlsym(libgl, "glXQueryVersion")); 1.148 + 1.149 + typedef XVisualInfo* (* PFNGLXCHOOSEVISUAL) (Display *, int, int *); 1.150 + PFNGLXCHOOSEVISUAL glXChooseVisual = cast<PFNGLXCHOOSEVISUAL>(glXGetProcAddress("glXChooseVisual")); 1.151 + 1.152 + typedef GLXContext (* PFNGLXCREATECONTEXT) (Display *, XVisualInfo *, GLXContext, Bool); 1.153 + PFNGLXCREATECONTEXT glXCreateContext = cast<PFNGLXCREATECONTEXT>(glXGetProcAddress("glXCreateContext")); 1.154 + 1.155 + typedef Bool (* PFNGLXMAKECURRENT) (Display*, GLXDrawable, GLXContext); 1.156 + PFNGLXMAKECURRENT glXMakeCurrent = cast<PFNGLXMAKECURRENT>(glXGetProcAddress("glXMakeCurrent")); 1.157 + 1.158 + typedef void (* PFNGLXDESTROYCONTEXT) (Display*, GLXContext); 1.159 + PFNGLXDESTROYCONTEXT glXDestroyContext = cast<PFNGLXDESTROYCONTEXT>(glXGetProcAddress("glXDestroyContext")); 1.160 + 1.161 + typedef GLubyte* (* PFNGLGETSTRING) (GLenum); 1.162 + PFNGLGETSTRING glGetString = cast<PFNGLGETSTRING>(glXGetProcAddress("glGetString")); 1.163 + 1.164 + if (!glXQueryExtension || 1.165 + !glXQueryVersion || 1.166 + !glXChooseVisual || 1.167 + !glXCreateContext || 1.168 + !glXMakeCurrent || 1.169 + !glXDestroyContext || 1.170 + !glGetString) 1.171 + { 1.172 + fatal_error("glXGetProcAddress couldn't find required functions"); 1.173 + } 1.174 + ///// Open a connection to the X server ///// 1.175 + Display *dpy = XOpenDisplay(nullptr); 1.176 + if (!dpy) 1.177 + fatal_error("Unable to open a connection to the X server"); 1.178 + 1.179 + ///// Check that the GLX extension is present ///// 1.180 + if (!glXQueryExtension(dpy, nullptr, nullptr)) 1.181 + fatal_error("GLX extension missing"); 1.182 + 1.183 + XSetErrorHandler(x_error_handler); 1.184 + 1.185 + ///// Get a visual ///// 1.186 + int attribs[] = { 1.187 + GLX_RGBA, 1.188 + GLX_RED_SIZE, 1, 1.189 + GLX_GREEN_SIZE, 1, 1.190 + GLX_BLUE_SIZE, 1, 1.191 + None }; 1.192 + XVisualInfo *vInfo = glXChooseVisual(dpy, DefaultScreen(dpy), attribs); 1.193 + if (!vInfo) 1.194 + fatal_error("No visuals found"); 1.195 + 1.196 + // using a X11 Window instead of a GLXPixmap does not crash 1.197 + // fglrx in indirect rendering. bug 680644 1.198 + Window window; 1.199 + XSetWindowAttributes swa; 1.200 + swa.colormap = XCreateColormap(dpy, RootWindow(dpy, vInfo->screen), 1.201 + vInfo->visual, AllocNone); 1.202 + 1.203 + swa.border_pixel = 0; 1.204 + window = XCreateWindow(dpy, RootWindow(dpy, vInfo->screen), 1.205 + 0, 0, 16, 16, 1.206 + 0, vInfo->depth, InputOutput, vInfo->visual, 1.207 + CWBorderPixel | CWColormap, &swa); 1.208 + 1.209 + ///// Get a GL context and make it current ////// 1.210 + GLXContext context = glXCreateContext(dpy, vInfo, nullptr, True); 1.211 + glXMakeCurrent(dpy, window, context); 1.212 + 1.213 + ///// Look for this symbol to determine texture_from_pixmap support ///// 1.214 + void* glXBindTexImageEXT = glXGetProcAddress("glXBindTexImageEXT"); 1.215 + 1.216 + ///// Get GL vendor/renderer/versions strings ///// 1.217 + enum { bufsize = 1024 }; 1.218 + char buf[bufsize]; 1.219 + const GLubyte *vendorString = glGetString(GL_VENDOR); 1.220 + const GLubyte *rendererString = glGetString(GL_RENDERER); 1.221 + const GLubyte *versionString = glGetString(GL_VERSION); 1.222 + 1.223 + if (!vendorString || !rendererString || !versionString) 1.224 + fatal_error("glGetString returned null"); 1.225 + 1.226 + int length = snprintf(buf, bufsize, 1.227 + "VENDOR\n%s\nRENDERER\n%s\nVERSION\n%s\nTFP\n%s\n", 1.228 + vendorString, 1.229 + rendererString, 1.230 + versionString, 1.231 + glXBindTexImageEXT ? "TRUE" : "FALSE"); 1.232 + if (length >= bufsize) 1.233 + fatal_error("GL strings length too large for buffer size"); 1.234 + 1.235 + ///// Clean up. Indeed, the parent process might fail to kill us (e.g. if it doesn't need to check GL info) 1.236 + ///// so we might be staying alive for longer than expected, so it's important to consume as little memory as 1.237 + ///// possible. Also we want to check that we're able to do that too without generating X errors. 1.238 + glXMakeCurrent(dpy, None, nullptr); // must release the GL context before destroying it 1.239 + glXDestroyContext(dpy, context); 1.240 + XDestroyWindow(dpy, window); 1.241 + XFreeColormap(dpy, swa.colormap); 1.242 + 1.243 +#ifdef NS_FREE_PERMANENT_DATA // conditionally defined in nscore.h, don't forget to #include it above 1.244 + XCloseDisplay(dpy); 1.245 +#else 1.246 + // This XSync call wanted to be instead: 1.247 + // XCloseDisplay(dpy); 1.248 + // but this can cause 1-minute stalls on certain setups using Nouveau, see bug 973192 1.249 + XSync(dpy, False); 1.250 +#endif 1.251 + 1.252 + dlclose(libgl); 1.253 + 1.254 + ///// Finally write data to the pipe 1.255 + write(write_end_of_the_pipe, buf, length); 1.256 +} 1.257 + 1.258 +} 1.259 + 1.260 +/** \returns true in the child glxtest process, false in the parent process */ 1.261 +bool fire_glxtest_process() 1.262 +{ 1.263 + int pfd[2]; 1.264 + if (pipe(pfd) == -1) { 1.265 + perror("pipe"); 1.266 + return false; 1.267 + } 1.268 + pid_t pid = fork(); 1.269 + if (pid < 0) { 1.270 + perror("fork"); 1.271 + close(pfd[0]); 1.272 + close(pfd[1]); 1.273 + return false; 1.274 + } 1.275 + // The child exits early to avoid running the full shutdown sequence and avoid conflicting with threads 1.276 + // we have already spawned (like the profiler). 1.277 + if (pid == 0) { 1.278 + close(pfd[0]); 1.279 + write_end_of_the_pipe = pfd[1]; 1.280 + glxtest(); 1.281 + close(pfd[1]); 1.282 + _exit(0); 1.283 + } 1.284 + 1.285 + close(pfd[1]); 1.286 + mozilla::widget::glxtest_pipe = pfd[0]; 1.287 + mozilla::widget::glxtest_pid = pid; 1.288 + return false; 1.289 +}