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