toolkit/xre/glxtest.cpp

changeset 0
6474c204b198
     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 +}

mercurial