Version 6.4.0.0.beta1, tag libreoffice-6.4.0.0.beta1
[LibreOffice.git] / vcl / unx / glxtest.cxx
blobb3c6a7521de79f1f7b62ac942d8b79dac6ddab3d
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/. */
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.
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.
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 #include <signal.h>
30 #include <sys/wait.h>
32 #include <opengl/x11/glxtest.hxx>
34 #ifdef __SUNPRO_CC
35 #include <stdio.h>
36 #endif
38 #include <X11/Xlib.h>
39 #include <X11/Xutil.h>
41 #include <sal/log.hxx>
42 #include <vcl/glxtestprocess.hxx>
44 // stuff from glx.h
45 typedef struct __GLXcontextRec *GLXContext;
46 typedef XID GLXPixmap;
47 typedef XID GLXDrawable;
48 /* GLX 1.3 and later */
49 typedef struct __GLXFBConfigRec *GLXFBConfig;
50 #define GLX_RGBA 4
51 #define GLX_RED_SIZE 8
52 #define GLX_GREEN_SIZE 9
53 #define GLX_BLUE_SIZE 10
55 // stuff from gl.h
56 typedef uint8_t GLubyte;
57 typedef uint32_t GLenum;
58 #define GL_VENDOR 0x1F00
59 #define GL_RENDERER 0x1F01
60 #define GL_VERSION 0x1F02
62 // the write end of the pipe, which we're going to write to
63 static int write_end_of_the_pipe = -1;
65 // C++ standard collides with C standard in that it doesn't allow casting void* to function pointer types.
66 // So the work-around is to convert first to size_t.
67 // http://www.trilithium.com/johan/2004/12/problem-with-dlsym/
68 template<typename func_ptr_type>
69 static func_ptr_type cast(void *ptr)
71 return reinterpret_cast<func_ptr_type>(
72 reinterpret_cast<size_t>(ptr)
76 static void fatal_error(const char *str)
78 int length = strlen(str);
79 if (write(write_end_of_the_pipe, str, length) != length
80 || write(write_end_of_the_pipe, "\n", 1) != 1)
82 /* Cannot write to pipe. Fall through to call _exit */
84 _exit(EXIT_FAILURE);
87 static int
88 x_error_handler(Display *, XErrorEvent *ev)
90 enum { bufsize = 1024 };
91 char buf[bufsize];
92 int length = snprintf(buf, bufsize,
93 "X error occurred in GLX probe, error_code=%d, request_code=%d, minor_code=%d\n",
94 ev->error_code,
95 ev->request_code,
96 ev->minor_code);
97 if (write(write_end_of_the_pipe, buf, length) != length)
99 /* Cannot write to pipe. Fall through to call _exit */
101 _exit(EXIT_FAILURE);
102 return 0;
105 static void glxtest()
107 signal(SIGPIPE, SIG_IGN);
108 // we want to redirect to /dev/null stdout, stderr, and while we're at it,
109 // any PR logging file descriptors. To that effect, we redirect all positive
110 // file descriptors up to what open() returns here. In particular, 1 is stdout and 2 is stderr.
111 int fd = open("/dev/null", O_WRONLY);
112 if (fd == -1)
113 fatal_error("could not redirect stdout+stderr");
114 for (int i = 1; i < fd; i++)
115 dup2(fd, i);
116 close(fd);
118 ///// Open libGL and load needed symbols /////
119 #ifdef __OpenBSD__
120 #define LIBGL_FILENAME "libGL.so"
121 #else
122 #define LIBGL_FILENAME "libGL.so.1"
123 #endif
124 void *libgl = dlopen(LIBGL_FILENAME, RTLD_LAZY);
125 if (!libgl)
126 fatal_error("Unable to load " LIBGL_FILENAME);
128 typedef void* (* PFNGLXGETPROCADDRESS) (const char *);
129 PFNGLXGETPROCADDRESS glXGetProcAddress = cast<PFNGLXGETPROCADDRESS>(dlsym(libgl, "glXGetProcAddress"));
131 if (!glXGetProcAddress)
132 fatal_error("Unable to find glXGetProcAddress in " LIBGL_FILENAME);
134 typedef GLXFBConfig* (* PFNGLXQUERYEXTENSION) (Display *, int *, int *);
135 PFNGLXQUERYEXTENSION glXQueryExtension = cast<PFNGLXQUERYEXTENSION>(glXGetProcAddress("glXQueryExtension"));
137 typedef GLXFBConfig* (* PFNGLXQUERYVERSION) (Display *, int *, int *);
138 PFNGLXQUERYVERSION glXQueryVersion = cast<PFNGLXQUERYVERSION>(dlsym(libgl, "glXQueryVersion"));
140 typedef XVisualInfo* (* PFNGLXCHOOSEVISUAL) (Display *, int, int *);
141 PFNGLXCHOOSEVISUAL glXChooseVisual = cast<PFNGLXCHOOSEVISUAL>(glXGetProcAddress("glXChooseVisual"));
143 typedef GLXContext (* PFNGLXCREATECONTEXT) (Display *, XVisualInfo *, GLXContext, Bool);
144 PFNGLXCREATECONTEXT glXCreateContext = cast<PFNGLXCREATECONTEXT>(glXGetProcAddress("glXCreateContext"));
146 typedef Bool (* PFNGLXMAKECURRENT) (Display*, GLXDrawable, GLXContext);
147 PFNGLXMAKECURRENT glXMakeCurrent = cast<PFNGLXMAKECURRENT>(glXGetProcAddress("glXMakeCurrent"));
149 typedef void (* PFNGLXDESTROYCONTEXT) (Display*, GLXContext);
150 PFNGLXDESTROYCONTEXT glXDestroyContext = cast<PFNGLXDESTROYCONTEXT>(glXGetProcAddress("glXDestroyContext"));
152 typedef GLubyte* (* PFNGLGETSTRING) (GLenum);
153 PFNGLGETSTRING glGetString = cast<PFNGLGETSTRING>(glXGetProcAddress("glGetString"));
155 if (!glXQueryExtension ||
156 !glXQueryVersion ||
157 !glXChooseVisual ||
158 !glXCreateContext ||
159 !glXMakeCurrent ||
160 !glXDestroyContext ||
161 !glGetString)
163 fatal_error("glXGetProcAddress couldn't find required functions");
165 ///// Open a connection to the X server /////
166 Display *dpy = XOpenDisplay(nullptr);
167 if (!dpy)
168 fatal_error("Unable to open a connection to the X server");
170 ///// Check that the GLX extension is present /////
171 if (!glXQueryExtension(dpy, nullptr, nullptr))
172 fatal_error("GLX extension missing");
174 XSetErrorHandler(x_error_handler);
176 ///// Get a visual /////
177 int attribs[] = {
178 GLX_RGBA,
179 GLX_RED_SIZE, 1,
180 GLX_GREEN_SIZE, 1,
181 GLX_BLUE_SIZE, 1,
182 None };
183 XVisualInfo *vInfo = glXChooseVisual(dpy, DefaultScreen(dpy), attribs);
184 if (!vInfo)
185 fatal_error("No visuals found");
187 // using a X11 Window instead of a GLXPixmap does not crash
188 // fglrx in indirect rendering. bug 680644
189 Window window;
190 XSetWindowAttributes swa;
191 swa.colormap = XCreateColormap(dpy, RootWindow(dpy, vInfo->screen),
192 vInfo->visual, AllocNone);
194 swa.border_pixel = 0;
195 window = XCreateWindow(dpy, RootWindow(dpy, vInfo->screen),
196 0, 0, 16, 16,
197 0, vInfo->depth, InputOutput, vInfo->visual,
198 CWBorderPixel | CWColormap, &swa);
200 ///// Get a GL context and make it current //////
201 GLXContext context = glXCreateContext(dpy, vInfo, nullptr, True);
202 glXMakeCurrent(dpy, window, context);
204 ///// Look for this symbol to determine texture_from_pixmap support /////
205 void* glXBindTexImageEXT = glXGetProcAddress("glXBindTexImageEXT");
207 ///// Get GL vendor/renderer/versions strings /////
208 enum { bufsize = 1024 };
209 char buf[bufsize];
210 const GLubyte *vendorString = glGetString(GL_VENDOR);
211 const GLubyte *rendererString = glGetString(GL_RENDERER);
212 const GLubyte *versionString = glGetString(GL_VERSION);
214 if (!vendorString || !rendererString || !versionString)
215 fatal_error("glGetString returned null");
217 int length = snprintf(buf, bufsize,
218 "VENDOR\n%s\nRENDERER\n%s\nVERSION\n%s\nTFP\n%s\n",
219 vendorString,
220 rendererString,
221 versionString,
222 glXBindTexImageEXT ? "TRUE" : "FALSE");
223 if (length >= bufsize)
224 fatal_error("GL strings length too large for buffer size");
226 ///// Clean up. Indeed, the parent process might fail to kill us (e.g. if it doesn't need to check GL info)
227 ///// so we might be staying alive for longer than expected, so it's important to consume as little memory as
228 ///// possible. Also we want to check that we're able to do that too without generating X errors.
229 glXMakeCurrent(dpy, None, nullptr); // must release the GL context before destroying it
230 glXDestroyContext(dpy, context);
231 XDestroyWindow(dpy, window);
232 XFreeColormap(dpy, swa.colormap);
233 XFree(vInfo);
235 #ifdef NS_FREE_PERMANENT_DATA // conditionally defined in nscore.h, don't forget to #include it above
236 XCloseDisplay(dpy);
237 #else
238 // This XSync call wanted to be instead:
239 // XCloseDisplay(dpy);
240 // but this can cause 1-minute stalls on certain setups using Nouveau, see bug 973192
241 XSync(dpy, False);
242 #endif
244 dlclose(libgl);
246 ///// Finally write data to the pipe
247 if (write(write_end_of_the_pipe, buf, length) != length)
248 fatal_error("Could not write to pipe");
251 /** \returns true in the child glxtest process, false in the parent process */
252 bool fire_glxtest_process()
254 int pfd[2];
255 if (pipe(pfd) == -1) {
256 perror("pipe");
257 return false;
259 pid_t pid = fork();
260 if (pid < 0) {
261 perror("fork");
262 close(pfd[0]);
263 close(pfd[1]);
264 return false;
266 // The child exits early to avoid running the full shutdown sequence and avoid conflicting with threads
267 // we have already spawned (like the profiler).
268 if (pid == 0) {
269 close(pfd[0]);
270 write_end_of_the_pipe = pfd[1];
271 glxtest();
272 close(pfd[1]);
273 _exit(0);
276 close(pfd[1]);
277 int* glxtest_pipe = getGlxPipe();
278 *glxtest_pipe = pfd[0];
279 pid_t* glxtest_pid = getGlxPid();
280 *glxtest_pid = pid;
281 return true;
284 void reap_glxtest_process() {
285 pid_t * pid = getGlxPid();
286 if (*pid != 0) {
287 // Use WNOHANG, as it is probably better to have a (rather harmless) zombie child process
288 // hanging around for the duration of the calling process, than to potentially block the
289 // calling process here:
290 pid_t e = waitpid(*pid, nullptr, WNOHANG);
291 SAL_INFO_IF(
292 e <= 0, "vcl.opengl", "waiting for glxtest process " << *pid << " failed with " << e);