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