1 : /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*-
2 : * vim: sw=2 ts=8 et :
3 : */
4 : /* ***** BEGIN LICENSE BLOCK *****
5 : * Version: MPL 1.1/GPL 2.0/LGPL 2.1
6 : *
7 : * The contents of this file are subject to the Mozilla Public License Version
8 : * 1.1 (the "License"); you may not use this file except in compliance with
9 : * the License. You may obtain a copy of the License at:
10 : * http://www.mozilla.org/MPL/
11 : *
12 : * Software distributed under the License is distributed on an "AS IS" basis,
13 : * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
14 : * for the specific language governing rights and limitations under the
15 : * License.
16 : *
17 : * The Original Code is Mozilla Code.
18 : *
19 : * The Initial Developer of the Original Code is
20 : * The Mozilla Foundation
21 : * Portions created by the Initial Developer are Copyright (C) 2010
22 : * the Initial Developer. All Rights Reserved.
23 : *
24 : * Contributor(s):
25 : *
26 : * Alternatively, the contents of this file may be used under the terms of
27 : * either the GNU General Public License Version 2 or later (the "GPL"), or
28 : * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
29 : * in which case the provisions of the GPL or the LGPL are applicable instead
30 : * of those above. If you wish to allow use of your version of this file only
31 : * under the terms of either the GPL or the LGPL, and not to allow others to
32 : * use your version of this file under the terms of the MPL, indicate your
33 : * decision by deleting the provisions above and replace them with the notice
34 : * and other provisions required by the GPL or the LGPL. If you do not delete
35 : * the provisions above, a recipient may use your version of this file under
36 : * the terms of any one of the MPL, the GPL or the LGPL.
37 : *
38 : * ***** END LICENSE BLOCK ***** */
39 :
40 :
41 : //////////////////////////////////////////////////////////////////////////////
42 : //
43 : // Explanation: See bug 639842. Safely getting GL driver info on X11 is hard, because the only way to do
44 : // that is to create a GL context and call glGetString(), but with bad drivers,
45 : // just creating a GL context may crash.
46 : //
47 : // This file implements the idea to do that in a separate process.
48 : //
49 : // The only non-static function here is fire_glxtest_process(). It creates a pipe, publishes its 'read' end as the
50 : // mozilla::widget::glxtest_pipe global variable, forks, and runs that GLX probe in the child process,
51 : // which runs the glxtest() static function. This creates a X connection, a GLX context, calls glGetString, and writes that
52 : // to the 'write' end of the pipe.
53 :
54 : #include <cstdio>
55 : #include <cstdlib>
56 : #include <unistd.h>
57 : #include <GL/gl.h>
58 : #include <GL/glx.h>
59 : #include <dlfcn.h>
60 : #include "nscore.h"
61 :
62 : #include <fcntl.h>
63 :
64 : #ifdef __SUNPRO_CC
65 : #include <stdio.h>
66 : #endif
67 :
68 : namespace mozilla {
69 : namespace widget {
70 : // the read end of the pipe, which will be used by GfxInfo
71 : extern int glxtest_pipe;
72 : // the PID of the glxtest process, to pass to waitpid()
73 : extern pid_t glxtest_pid;
74 : }
75 : }
76 :
77 : // the write end of the pipe, which we're going to write to
78 : static int write_end_of_the_pipe = -1;
79 :
80 : // C++ standard collides with C standard in that it doesn't allow casting void* to function pointer types.
81 : // So the work-around is to convert first to size_t.
82 : // http://www.trilithium.com/johan/2004/12/problem-with-dlsym/
83 : template<typename func_ptr_type>
84 253 : static func_ptr_type cast(void *ptr)
85 : {
86 : return reinterpret_cast<func_ptr_type>(
87 : reinterpret_cast<size_t>(ptr)
88 253 : );
89 : }
90 :
91 23 : static void fatal_error(const char *str)
92 : {
93 23 : write(write_end_of_the_pipe, str, strlen(str));
94 23 : write(write_end_of_the_pipe, "\n", 1);
95 23 : exit(EXIT_FAILURE);
96 : }
97 :
98 : static int
99 0 : x_error_handler(Display *, XErrorEvent *ev)
100 : {
101 : enum { bufsize = 1024 };
102 : char buf[bufsize];
103 : int length = snprintf(buf, bufsize,
104 : "X error occurred in GLX probe, error_code=%d, request_code=%d, minor_code=%d\n",
105 : ev->error_code,
106 : ev->request_code,
107 0 : ev->minor_code);
108 0 : write(write_end_of_the_pipe, buf, length);
109 0 : exit(EXIT_FAILURE);
110 : return 0;
111 : }
112 :
113 23 : static void glxtest()
114 : {
115 : // we want to redirect to /dev/null stdout, stderr, and while we're at it,
116 : // any PR logging file descriptors. To that effect, we redirect all positive
117 : // file descriptors up to what open() returns here. In particular, 1 is stdout and 2 is stderr.
118 23 : int fd = open("/dev/null", O_WRONLY);
119 71 : for (int i = 1; i < fd; i++)
120 48 : dup2(fd, i);
121 23 : close(fd);
122 :
123 : ///// Open libGL and load needed symbols /////
124 : #ifdef __OpenBSD__
125 : #define LIBGL_FILENAME "libGL.so"
126 : #else
127 : #define LIBGL_FILENAME "libGL.so.1"
128 : #endif
129 23 : void *libgl = dlopen(LIBGL_FILENAME, RTLD_LAZY);
130 23 : if (!libgl)
131 0 : fatal_error("Unable to load " LIBGL_FILENAME);
132 :
133 : typedef void* (* PFNGLXGETPROCADDRESS) (const char *);
134 23 : PFNGLXGETPROCADDRESS glXGetProcAddress = cast<PFNGLXGETPROCADDRESS>(dlsym(libgl, "glXGetProcAddress"));
135 :
136 23 : if (!glXGetProcAddress)
137 0 : fatal_error("Unable to find glXGetProcAddress in " LIBGL_FILENAME);
138 :
139 : typedef GLXFBConfig* (* PFNGLXQUERYEXTENSION) (Display *, int *, int *);
140 23 : PFNGLXQUERYEXTENSION glXQueryExtension = cast<PFNGLXQUERYEXTENSION>(glXGetProcAddress("glXQueryExtension"));
141 :
142 : typedef GLXFBConfig* (* PFNGLXQUERYVERSION) (Display *, int *, int *);
143 23 : PFNGLXQUERYVERSION glXQueryVersion = cast<PFNGLXQUERYVERSION>(dlsym(libgl, "glXQueryVersion"));
144 :
145 : typedef GLXFBConfig* (* PFNGLXCHOOSEFBCONFIG) (Display *, int, const int *, int *);
146 23 : PFNGLXCHOOSEFBCONFIG glXChooseFBConfig = cast<PFNGLXCHOOSEFBCONFIG>(glXGetProcAddress("glXChooseFBConfig"));
147 :
148 : typedef XVisualInfo* (* PFNGLXGETVISUALFROMFBCONFIG) (Display *, GLXFBConfig);
149 23 : PFNGLXGETVISUALFROMFBCONFIG glXGetVisualFromFBConfig = cast<PFNGLXGETVISUALFROMFBCONFIG>(glXGetProcAddress("glXGetVisualFromFBConfig"));
150 :
151 : typedef GLXPixmap (* PFNGLXCREATEPIXMAP) (Display *, GLXFBConfig, Pixmap, const int *);
152 23 : PFNGLXCREATEPIXMAP glXCreatePixmap = cast<PFNGLXCREATEPIXMAP>(glXGetProcAddress("glXCreatePixmap"));
153 :
154 : typedef GLXContext (* PFNGLXCREATENEWCONTEXT) (Display *, GLXFBConfig, int, GLXContext, Bool);
155 23 : PFNGLXCREATENEWCONTEXT glXCreateNewContext = cast<PFNGLXCREATENEWCONTEXT>(glXGetProcAddress("glXCreateNewContext"));
156 :
157 : typedef Bool (* PFNGLXMAKECURRENT) (Display*, GLXDrawable, GLXContext);
158 23 : PFNGLXMAKECURRENT glXMakeCurrent = cast<PFNGLXMAKECURRENT>(glXGetProcAddress("glXMakeCurrent"));
159 :
160 : typedef void (* PFNGLXDESTROYPIXMAP) (Display *, GLXPixmap);
161 23 : PFNGLXDESTROYPIXMAP glXDestroyPixmap = cast<PFNGLXDESTROYPIXMAP>(glXGetProcAddress("glXDestroyPixmap"));
162 :
163 : typedef void (* PFNGLXDESTROYCONTEXT) (Display*, GLXContext);
164 23 : PFNGLXDESTROYCONTEXT glXDestroyContext = cast<PFNGLXDESTROYCONTEXT>(glXGetProcAddress("glXDestroyContext"));
165 :
166 : typedef GLubyte* (* PFNGLGETSTRING) (GLenum);
167 23 : PFNGLGETSTRING glGetString = cast<PFNGLGETSTRING>(glXGetProcAddress("glGetString"));
168 :
169 23 : if (!glXQueryExtension ||
170 : !glXQueryVersion ||
171 : !glXChooseFBConfig ||
172 : !glXGetVisualFromFBConfig ||
173 : !glXCreatePixmap ||
174 : !glXCreateNewContext ||
175 : !glXMakeCurrent ||
176 : !glXDestroyPixmap ||
177 : !glXDestroyContext ||
178 : !glGetString)
179 : {
180 0 : fatal_error("glXGetProcAddress couldn't find required functions");
181 : }
182 : ///// Open a connection to the X server /////
183 23 : Display *dpy = XOpenDisplay(NULL);
184 23 : if (!dpy)
185 23 : fatal_error("Unable to open a connection to the X server");
186 :
187 : ///// Check that the GLX extension is present /////
188 0 : if (!glXQueryExtension(dpy, NULL, NULL))
189 0 : fatal_error("GLX extension missing");
190 :
191 : ///// Check that the GLX version is >= 1.3, needed for glXCreatePixmap, bug 659932 /////
192 : int majorVersion, minorVersion;
193 0 : if (!glXQueryVersion(dpy, &majorVersion, &minorVersion))
194 0 : fatal_error("Unable to query GLX version");
195 :
196 0 : if (majorVersion < 1 || (majorVersion == 1 && minorVersion < 3))
197 0 : fatal_error("GLX version older than the required 1.3");
198 :
199 0 : XSetErrorHandler(x_error_handler);
200 :
201 : ///// Get a FBConfig and a visual /////
202 : int attribs[] = {
203 : GLX_DRAWABLE_TYPE, GLX_PIXMAP_BIT,
204 : GLX_X_RENDERABLE, True,
205 : 0
206 0 : };
207 : int numReturned;
208 0 : GLXFBConfig *fbConfigs = glXChooseFBConfig(dpy, DefaultScreen(dpy), attribs, &numReturned );
209 0 : if (!fbConfigs)
210 0 : fatal_error("No FBConfigs found");
211 0 : XVisualInfo *vInfo = glXGetVisualFromFBConfig(dpy, fbConfigs[0]);
212 0 : if (!vInfo)
213 0 : fatal_error("No visual found for first FBConfig");
214 :
215 : ///// Get a Pixmap and a GLXPixmap /////
216 0 : Pixmap pixmap = XCreatePixmap(dpy, RootWindow(dpy, vInfo->screen), 4, 4, vInfo->depth);
217 0 : GLXPixmap glxpixmap = glXCreatePixmap(dpy, fbConfigs[0], pixmap, NULL);
218 :
219 : ///// Get a GL context and make it current //////
220 0 : GLXContext context = glXCreateNewContext(dpy, fbConfigs[0], GLX_RGBA_TYPE, NULL, True);
221 0 : glXMakeCurrent(dpy, glxpixmap, context);
222 :
223 : ///// Look for this symbol to determine texture_from_pixmap support /////
224 0 : void* glXBindTexImageEXT = glXGetProcAddress("glXBindTexImageEXT");
225 :
226 : ///// Get GL vendor/renderer/versions strings /////
227 : enum { bufsize = 1024 };
228 : char buf[bufsize];
229 0 : const GLubyte *vendorString = glGetString(GL_VENDOR);
230 0 : const GLubyte *rendererString = glGetString(GL_RENDERER);
231 0 : const GLubyte *versionString = glGetString(GL_VERSION);
232 :
233 0 : if (!vendorString || !rendererString || !versionString)
234 0 : fatal_error("glGetString returned null");
235 :
236 : int length = snprintf(buf, bufsize,
237 : "VENDOR\n%s\nRENDERER\n%s\nVERSION\n%s\nTFP\n%s\n",
238 : vendorString,
239 : rendererString,
240 : versionString,
241 0 : glXBindTexImageEXT ? "TRUE" : "FALSE");
242 0 : if (length >= bufsize)
243 0 : fatal_error("GL strings length too large for buffer size");
244 :
245 : ///// Clean up. Indeed, the parent process might fail to kill us (e.g. if it doesn't need to check GL info)
246 : ///// so we might be staying alive for longer than expected, so it's important to consume as little memory as
247 : ///// possible. Also we want to check that we're able to do that too without generating X errors.
248 0 : glXMakeCurrent(dpy, None, NULL); // must release the GL context before destroying it
249 0 : glXDestroyContext(dpy, context);
250 0 : glXDestroyPixmap(dpy, glxpixmap);
251 0 : XFreePixmap(dpy, pixmap);
252 0 : XCloseDisplay(dpy);
253 0 : dlclose(libgl);
254 :
255 : ///// Finally write data to the pipe
256 0 : write(write_end_of_the_pipe, buf, length);
257 0 : }
258 :
259 : /** \returns true in the child glxtest process, false in the parent process */
260 23 : bool fire_glxtest_process()
261 : {
262 : int pfd[2];
263 23 : if (pipe(pfd) == -1) {
264 0 : perror("pipe");
265 0 : return false;
266 : }
267 23 : pid_t pid = fork();
268 46 : if (pid < 0) {
269 0 : perror("fork");
270 0 : close(pfd[0]);
271 0 : close(pfd[1]);
272 0 : return false;
273 : }
274 46 : if (pid == 0) {
275 23 : close(pfd[0]);
276 23 : write_end_of_the_pipe = pfd[1];
277 23 : glxtest();
278 0 : close(pfd[1]);
279 0 : return true;
280 : }
281 :
282 23 : close(pfd[1]);
283 23 : mozilla::widget::glxtest_pipe = pfd[0];
284 23 : mozilla::widget::glxtest_pid = pid;
285 23 : return false;
286 : }
|