1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*-
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/. */
8 //////////////////////////////////////////////////////////////////////////////
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.
14 // This file implements the idea to do that in a separate process.
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.
32 #include <opengl/x11/glxtest.hxx>
39 #include <X11/Xutil.h>
41 #include <sal/log.hxx>
44 typedef struct __GLXcontextRec
*GLXContext
;
45 typedef XID GLXPixmap
;
46 typedef XID GLXDrawable
;
47 /* GLX 1.3 and later */
48 typedef struct __GLXFBConfigRec
*GLXFBConfig
;
50 #define GLX_RED_SIZE 8
51 #define GLX_GREEN_SIZE 9
52 #define GLX_BLUE_SIZE 10
55 typedef uint8_t GLubyte
;
56 typedef uint32_t GLenum
;
57 #define GL_VENDOR 0x1F00
58 #define GL_RENDERER 0x1F01
59 #define GL_VERSION 0x1F02
61 // the write end of the pipe, which we're going to write to
62 static int write_end_of_the_pipe
= -1;
64 // C++ standard collides with C standard in that it doesn't allow casting void* to function pointer types.
65 // So the work-around is to convert first to size_t.
66 // http://www.trilithium.com/johan/2004/12/problem-with-dlsym/
67 template<typename func_ptr_type
>
68 static func_ptr_type
cast(void *ptr
)
70 return reinterpret_cast<func_ptr_type
>(
71 reinterpret_cast<size_t>(ptr
)
75 static void fatal_error(const char *str
)
77 int length
= strlen(str
);
78 if (write(write_end_of_the_pipe
, str
, length
) != length
79 || write(write_end_of_the_pipe
, "\n", 1) != 1)
81 /* Cannot write to pipe. Fall through to call _exit */
87 x_error_handler(Display
*, XErrorEvent
*ev
)
89 enum { bufsize
= 1024 };
91 int length
= snprintf(buf
, bufsize
,
92 "X error occurred in GLX probe, error_code=%d, request_code=%d, minor_code=%d\n",
96 if (write(write_end_of_the_pipe
, buf
, length
) != length
)
98 /* Cannot write to pipe. Fall through to call _exit */
104 static void glxtest()
106 signal(SIGPIPE
, SIG_IGN
);
107 // we want to redirect to /dev/null stdout, stderr, and while we're at it,
108 // any PR logging file descriptors. To that effect, we redirect all positive
109 // file descriptors up to what open() returns here. In particular, 1 is stdout and 2 is stderr.
110 int fd
= open("/dev/null", O_WRONLY
);
112 fatal_error("could not redirect stdout+stderr");
113 for (int i
= 1; i
< fd
; i
++)
117 ///// Open libGL and load needed symbols /////
119 #define LIBGL_FILENAME "libGL.so"
121 #define LIBGL_FILENAME "libGL.so.1"
123 void *libgl
= dlopen(LIBGL_FILENAME
, RTLD_LAZY
);
125 fatal_error("Unable to load " LIBGL_FILENAME
);
127 typedef void* (* PFNGLXGETPROCADDRESS
) (const char *);
128 PFNGLXGETPROCADDRESS glXGetProcAddress
= cast
<PFNGLXGETPROCADDRESS
>(dlsym(libgl
, "glXGetProcAddress"));
130 if (!glXGetProcAddress
)
131 fatal_error("Unable to find glXGetProcAddress in " LIBGL_FILENAME
);
133 typedef GLXFBConfig
* (* PFNGLXQUERYEXTENSION
) (Display
*, int *, int *);
134 PFNGLXQUERYEXTENSION glXQueryExtension
= cast
<PFNGLXQUERYEXTENSION
>(glXGetProcAddress("glXQueryExtension"));
136 typedef GLXFBConfig
* (* PFNGLXQUERYVERSION
) (Display
*, int *, int *);
137 PFNGLXQUERYVERSION glXQueryVersion
= cast
<PFNGLXQUERYVERSION
>(dlsym(libgl
, "glXQueryVersion"));
139 typedef XVisualInfo
* (* PFNGLXCHOOSEVISUAL
) (Display
*, int, int *);
140 PFNGLXCHOOSEVISUAL glXChooseVisual
= cast
<PFNGLXCHOOSEVISUAL
>(glXGetProcAddress("glXChooseVisual"));
142 typedef GLXContext (* PFNGLXCREATECONTEXT
) (Display
*, XVisualInfo
*, GLXContext
, Bool
);
143 PFNGLXCREATECONTEXT glXCreateContext
= cast
<PFNGLXCREATECONTEXT
>(glXGetProcAddress("glXCreateContext"));
145 typedef Bool (* PFNGLXMAKECURRENT
) (Display
*, GLXDrawable
, GLXContext
);
146 PFNGLXMAKECURRENT glXMakeCurrent
= cast
<PFNGLXMAKECURRENT
>(glXGetProcAddress("glXMakeCurrent"));
148 typedef void (* PFNGLXDESTROYCONTEXT
) (Display
*, GLXContext
);
149 PFNGLXDESTROYCONTEXT glXDestroyContext
= cast
<PFNGLXDESTROYCONTEXT
>(glXGetProcAddress("glXDestroyContext"));
151 typedef GLubyte
* (* PFNGLGETSTRING
) (GLenum
);
152 PFNGLGETSTRING glGetString
= cast
<PFNGLGETSTRING
>(glXGetProcAddress("glGetString"));
154 if (!glXQueryExtension
||
159 !glXDestroyContext
||
162 fatal_error("glXGetProcAddress couldn't find required functions");
164 ///// Open a connection to the X server /////
165 Display
*dpy
= XOpenDisplay(nullptr);
167 fatal_error("Unable to open a connection to the X server");
169 ///// Check that the GLX extension is present /////
170 if (!glXQueryExtension(dpy
, nullptr, nullptr))
171 fatal_error("GLX extension missing");
173 XSetErrorHandler(x_error_handler
);
175 ///// Get a visual /////
182 XVisualInfo
*vInfo
= glXChooseVisual(dpy
, DefaultScreen(dpy
), attribs
);
184 fatal_error("No visuals found");
186 // using a X11 Window instead of a GLXPixmap does not crash
187 // fglrx in indirect rendering. bug 680644
189 XSetWindowAttributes swa
;
190 swa
.colormap
= XCreateColormap(dpy
, RootWindow(dpy
, vInfo
->screen
),
191 vInfo
->visual
, AllocNone
);
193 swa
.border_pixel
= 0;
194 window
= XCreateWindow(dpy
, RootWindow(dpy
, vInfo
->screen
),
196 0, vInfo
->depth
, InputOutput
, vInfo
->visual
,
197 CWBorderPixel
| CWColormap
, &swa
);
199 ///// Get a GL context and make it current //////
200 GLXContext context
= glXCreateContext(dpy
, vInfo
, nullptr, True
);
201 glXMakeCurrent(dpy
, window
, context
);
203 ///// Look for this symbol to determine texture_from_pixmap support /////
204 void* glXBindTexImageEXT
= glXGetProcAddress("glXBindTexImageEXT");
206 ///// Get GL vendor/renderer/versions strings /////
207 enum { bufsize
= 1024 };
209 const GLubyte
*vendorString
= glGetString(GL_VENDOR
);
210 const GLubyte
*rendererString
= glGetString(GL_RENDERER
);
211 const GLubyte
*versionString
= glGetString(GL_VERSION
);
213 if (!vendorString
|| !rendererString
|| !versionString
)
214 fatal_error("glGetString returned null");
216 int length
= snprintf(buf
, bufsize
,
217 "VENDOR\n%s\nRENDERER\n%s\nVERSION\n%s\nTFP\n%s\n",
221 glXBindTexImageEXT
? "TRUE" : "FALSE");
222 if (length
>= bufsize
)
223 fatal_error("GL strings length too large for buffer size");
225 ///// Clean up. Indeed, the parent process might fail to kill us (e.g. if it doesn't need to check GL info)
226 ///// so we might be staying alive for longer than expected, so it's important to consume as little memory as
227 ///// possible. Also we want to check that we're able to do that too without generating X errors.
228 glXMakeCurrent(dpy
, None
, nullptr); // must release the GL context before destroying it
229 glXDestroyContext(dpy
, context
);
230 XDestroyWindow(dpy
, window
);
231 XFreeColormap(dpy
, swa
.colormap
);
234 #ifdef NS_FREE_PERMANENT_DATA // conditionally defined in nscore.h, don't forget to #include it above
237 // This XSync call wanted to be instead:
238 // XCloseDisplay(dpy);
239 // but this can cause 1-minute stalls on certain setups using Nouveau, see bug 973192
245 ///// Finally write data to the pipe
246 if (write(write_end_of_the_pipe
, buf
, length
) != length
)
247 fatal_error("Could not write to pipe");
250 /** \returns true in the child glxtest process, false in the parent process */
251 bool fire_glxtest_process()
254 if (pipe(pfd
) == -1) {
265 // The child exits early to avoid running the full shutdown sequence and avoid conflicting with threads
266 // we have already spawned (like the profiler).
269 write_end_of_the_pipe
= pfd
[1];
276 int* glxtest_pipe
= getGlxPipe();
277 *glxtest_pipe
= pfd
[0];
278 pid_t
* glxtest_pid
= getGlxPid();
283 void reap_glxtest_process() {
284 pid_t
* pid
= getGlxPid();
286 // Use WNOHANG, as it is probably better to have a (rather harmless) zombie child process
287 // hanging around for the duration of the calling process, than to potentially block the
288 // calling process here:
289 pid_t e
= waitpid(*pid
, nullptr, WNOHANG
);
291 e
<= 0, "vcl.opengl", "waiting for glxtest process " << *pid
<< " failed with " << e
);