glxtest.cpp 12 KB

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