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