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