Line data Source code
1 : /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2 : /*
3 : * This file is part of the LibreOffice project.
4 : *
5 : * This Source Code Form is subject to the terms of the Mozilla Public
6 : * License, v. 2.0. If a copy of the MPL was not distributed with this
7 : * file, You can obtain one at http://mozilla.org/MPL/2.0/.
8 : */
9 :
10 : #include "opengl/x11/X11DeviceInfo.hxx"
11 :
12 : #include <vcl/opengl/glxtest.hxx>
13 : #include <rtl/ustring.hxx>
14 :
15 : #include <unistd.h>
16 : #include <sys/types.h>
17 : #include <sys/wait.h>
18 : #include <errno.h>
19 : #include <sys/utsname.h>
20 :
21 : namespace glx {
22 :
23 : static int glxtest_pipe = 0;
24 :
25 : static pid_t glxtest_pid = 0;
26 :
27 : }
28 :
29 127 : pid_t* getGlxPid()
30 : {
31 127 : return &glx::glxtest_pid;
32 : }
33 :
34 127 : int* getGlxPipe()
35 : {
36 127 : return &glx::glxtest_pipe;
37 : }
38 :
39 : namespace {
40 :
41 : const char*
42 88 : strspnp_wrapper(const char* aDelims, const char* aStr)
43 : {
44 : const char* d;
45 88 : do
46 : {
47 176 : for (d = aDelims; *d != '\0'; ++d)
48 : {
49 88 : if (*aStr == *d)
50 : {
51 0 : ++aStr;
52 0 : break;
53 : }
54 : }
55 : } while (*d);
56 :
57 88 : return aStr;
58 : }
59 :
60 96 : char* strtok_wrapper(const char* aDelims, char** aStr)
61 : {
62 96 : if (!*aStr)
63 : {
64 8 : return nullptr;
65 : }
66 :
67 88 : char* ret = const_cast<char*>(strspnp_wrapper(aDelims, *aStr));
68 :
69 88 : if (!*ret)
70 : {
71 8 : *aStr = ret;
72 8 : return nullptr;
73 : }
74 :
75 80 : char* i = ret;
76 792 : do
77 : {
78 1656 : for (const char* d = aDelims; *d != '\0'; ++d)
79 : {
80 864 : if (*i == *d) {
81 72 : *i = '\0';
82 72 : *aStr = ++i;
83 72 : return ret;
84 : }
85 : }
86 792 : ++i;
87 : } while (*i);
88 :
89 8 : *aStr = nullptr;
90 8 : return ret;
91 : }
92 :
93 16 : uint64_t version(uint32_t major, uint32_t minor, uint32_t revision = 0)
94 : {
95 16 : return (uint64_t(major) << 32) + (uint64_t(minor) << 16) + uint64_t(revision);
96 : }
97 :
98 : }
99 :
100 12 : X11OpenGLDeviceInfo::X11OpenGLDeviceInfo():
101 : mbIsMesa(false),
102 : mbIsNVIDIA(false),
103 : mbIsFGLRX(false),
104 : mbIsNouveau(false),
105 : mbIsIntel(false),
106 : mbIsOldSwrast(false),
107 : mbIsLlvmpipe(false),
108 : mbHasTextureFromPixmap(false),
109 : mnGLMajorVersion(0),
110 : mnMajorVersion(0),
111 : mnMinorVersion(0),
112 12 : mnRevisionVersion(0)
113 : {
114 12 : GetData();
115 12 : }
116 :
117 12 : X11OpenGLDeviceInfo::~X11OpenGLDeviceInfo()
118 : {
119 12 : }
120 :
121 12 : void X11OpenGLDeviceInfo::GetData()
122 : {
123 12 : if (!glx::glxtest_pipe)
124 16 : return;
125 :
126 : // to understand this function, see bug moz#639842. We retrieve the OpenGL driver information in a
127 : // separate process to protect against bad drivers.
128 : enum { buf_size = 1024 };
129 : char buf[buf_size];
130 : ssize_t bytesread = read(glx::glxtest_pipe,
131 : &buf,
132 8 : buf_size-1); // -1 because we'll append a zero
133 8 : close(glx::glxtest_pipe);
134 8 : glx::glxtest_pipe = 0;
135 :
136 : // bytesread < 0 would mean that the above read() call failed.
137 : // This should never happen. If it did, the outcome would be to blacklist anyway.
138 8 : if (bytesread < 0)
139 0 : bytesread = 0;
140 :
141 : // let buf be a zero-terminated string
142 8 : buf[bytesread] = 0;
143 :
144 : // Wait for the glxtest process to finish. This serves 2 purposes:
145 : // * avoid having a zombie glxtest process laying around
146 : // * get the glxtest process status info.
147 8 : int glxtest_status = 0;
148 8 : bool wait_for_glxtest_process = true;
149 8 : bool waiting_for_glxtest_process_failed = false;
150 8 : int waitpid_errno = 0;
151 24 : while(wait_for_glxtest_process)
152 : {
153 8 : wait_for_glxtest_process = false;
154 8 : if (waitpid(glx::glxtest_pid, &glxtest_status, 0) == -1)
155 : {
156 0 : waitpid_errno = errno;
157 0 : if (waitpid_errno == EINTR)
158 : {
159 0 : wait_for_glxtest_process = true;
160 : }
161 : else
162 : {
163 : // Bug moz#718629
164 : // ECHILD happens when the glxtest process got reaped got reaped after a PR_CreateProcess
165 : // as per bug moz#227246. This shouldn't matter, as we still seem to get the data
166 : // from the pipe, and if we didn't, the outcome would be to blacklist anyway.
167 0 : waiting_for_glxtest_process_failed = (waitpid_errno != ECHILD);
168 : }
169 : }
170 : }
171 :
172 16 : bool exited_with_error_code = !waiting_for_glxtest_process_failed &&
173 16 : WIFEXITED(glxtest_status) &&
174 16 : WEXITSTATUS(glxtest_status) != EXIT_SUCCESS;
175 16 : bool received_signal = !waiting_for_glxtest_process_failed &&
176 16 : WIFSIGNALED(glxtest_status);
177 :
178 8 : bool error = waiting_for_glxtest_process_failed || exited_with_error_code || received_signal;
179 :
180 8 : OString textureFromPixmap;
181 8 : OString *stringToFill = nullptr;
182 8 : char *bufptr = buf;
183 8 : if (!error)
184 : {
185 : while(true)
186 : {
187 72 : char *line = strtok_wrapper("\n", &bufptr);
188 72 : if (!line)
189 8 : break;
190 64 : if (stringToFill) {
191 32 : *stringToFill = OString(line);
192 32 : stringToFill = nullptr;
193 : }
194 32 : else if(!strcmp(line, "VENDOR"))
195 8 : stringToFill = &maVendor;
196 24 : else if(!strcmp(line, "RENDERER"))
197 8 : stringToFill = &maRenderer;
198 16 : else if(!strcmp(line, "VERSION"))
199 8 : stringToFill = &maVersion;
200 8 : else if(!strcmp(line, "TFP"))
201 8 : stringToFill = &textureFromPixmap;
202 64 : }
203 : }
204 :
205 8 : if (!strcmp(textureFromPixmap.getStr(), "TRUE"))
206 8 : mbHasTextureFromPixmap = true;
207 :
208 : // only useful for Linux kernel version check for FGLRX driver.
209 : // assumes X client == X server, which is sad.
210 : struct utsname unameobj;
211 8 : if (!uname(&unameobj))
212 : {
213 8 : maOS = OString(unameobj.sysname);
214 8 : maOSRelease = OString(unameobj.release);
215 : }
216 :
217 : // determine the major OpenGL version. That's the first integer in the version string.
218 8 : mnGLMajorVersion = strtol(maVersion.getStr(), 0, 10);
219 :
220 : // determine driver type (vendor) and where in the version string
221 : // the actual driver version numbers should be expected to be found (whereToReadVersionNumbers)
222 8 : const char *whereToReadVersionNumbers = nullptr;
223 8 : const char *Mesa_in_version_string = strstr(maVersion.getStr(), "Mesa");
224 8 : if (Mesa_in_version_string)
225 : {
226 0 : mbIsMesa = true;
227 : // with Mesa, the version string contains "Mesa major.minor" and that's all the version information we get:
228 : // there is no actual driver version info.
229 0 : whereToReadVersionNumbers = Mesa_in_version_string + strlen("Mesa");
230 0 : if (strcasestr(maVendor.getStr(), "nouveau"))
231 0 : mbIsNouveau = true;
232 0 : if (strcasestr(maRenderer.getStr(), "intel")) // yes, intel is in the renderer string
233 0 : mbIsIntel = true;
234 0 : if (strcasestr(maRenderer.getStr(), "llvmpipe"))
235 0 : mbIsLlvmpipe = true;
236 0 : if (strcasestr(maRenderer.getStr(), "software rasterizer"))
237 0 : mbIsOldSwrast = true;
238 : }
239 8 : else if (strstr(maVendor.getStr(), "NVIDIA Corporation"))
240 : {
241 8 : mbIsNVIDIA = true;
242 : // with the NVIDIA driver, the version string contains "NVIDIA major.minor"
243 : // note that here the vendor and version strings behave differently, that's why we don't put this above
244 : // alongside Mesa_in_version_string.
245 8 : const char *NVIDIA_in_version_string = strstr(maVersion.getStr(), "NVIDIA");
246 8 : if (NVIDIA_in_version_string)
247 8 : whereToReadVersionNumbers = NVIDIA_in_version_string + strlen("NVIDIA");
248 : }
249 0 : else if (strstr(maVendor.getStr(), "ATI Technologies Inc"))
250 : {
251 0 : mbIsFGLRX = true;
252 : // with the FGLRX driver, the version string only gives a OpenGL version :/ so let's return that.
253 : // that can at least give a rough idea of how old the driver is.
254 0 : whereToReadVersionNumbers = maVersion.getStr();
255 : }
256 :
257 : // read major.minor version numbers of the driver (not to be confused with the OpenGL version)
258 8 : if (whereToReadVersionNumbers)
259 : {
260 : // copy into writable buffer, for tokenization
261 8 : strncpy(buf, whereToReadVersionNumbers, buf_size-1);
262 8 : buf[buf_size-1] = 0;
263 8 : bufptr = buf;
264 :
265 : // now try to read major.minor version numbers. In case of failure, gracefully exit: these numbers have
266 : // been initialized as 0 anyways
267 8 : char *token = strtok_wrapper(".", &bufptr);
268 8 : if (token)
269 : {
270 8 : mnMajorVersion = strtol(token, 0, 10);
271 8 : token = strtok_wrapper(".", &bufptr);
272 8 : if (token)
273 : {
274 8 : mnMinorVersion = strtol(token, 0, 10);
275 8 : token = strtok_wrapper(".", &bufptr);
276 8 : if (token)
277 0 : mnRevisionVersion = strtol(token, 0, 10);
278 : }
279 : }
280 8 : }
281 : }
282 :
283 12 : bool X11OpenGLDeviceInfo::isDeviceBlocked()
284 : {
285 : // don't even try to use OpenGL 1.x
286 12 : if (mnGLMajorVersion == 1)
287 0 : return true;
288 :
289 : SAL_INFO("vcl.opengl", "Vendor: " << maVendor);
290 : SAL_INFO("vcl.opengl", "Renderer: " << maRenderer);
291 : SAL_INFO("vcl.opengl", "Version: " << maVersion);
292 : SAL_INFO("vcl.opengl", "OS: " << maOS);
293 : SAL_INFO("vcl.opengl", "OSRelease: " << maOSRelease);
294 :
295 12 : if (mbIsMesa)
296 : {
297 0 : if (mbIsNouveau && version(mnMajorVersion, mnMinorVersion) < version(8,0))
298 : {
299 : SAL_WARN("vcl.opengl", "blocked driver version: old nouveau driver (requires mesa 8.0+)");
300 0 : return true;
301 : }
302 0 : else if (version(mnMajorVersion, mnMinorVersion, mnRevisionVersion) < version(7,10,3))
303 : {
304 : SAL_WARN("vcl.opengl", "blocked driver version: requires at least mesa 7.10.3");
305 0 : return true;
306 : }
307 0 : else if (mbIsIntel && version(mnMajorVersion, mnMinorVersion, mnRevisionVersion) == version(9,0,2))
308 : {
309 : SAL_WARN("vcl.opengl", "blocked driver version: my broken intel driver Mesa 9.0.2");
310 0 : return true;
311 : }
312 0 : else if (mbIsOldSwrast)
313 : {
314 : SAL_WARN("vcl.opengl", "blocked driver version: software rasterizer");
315 0 : return true;
316 : }
317 0 : else if (mbIsLlvmpipe && version(mnMajorVersion, mnMinorVersion) < version(9, 1))
318 : {
319 : // bug moz#791905, Mesa bug 57733, fixed in Mesa 9.1 according to
320 : // https://bugs.freedesktop.org/show_bug.cgi?id=57733#c3
321 : SAL_WARN("vcl.opengl", "blocked driver version: fdo#57733");
322 0 : return true;
323 : }
324 : }
325 12 : else if (mbIsNVIDIA)
326 : {
327 8 : if (version(mnMajorVersion, mnMinorVersion, mnRevisionVersion) < version(257,21))
328 : {
329 : SAL_WARN("vcl.opengl", "blocked driver version: nvidia requires at least 257.21");
330 0 : return true;
331 : }
332 : }
333 4 : else if (mbIsFGLRX)
334 : {
335 : // FGLRX does not report a driver version number, so we have the OpenGL version instead.
336 : // by requiring OpenGL 3, we effectively require recent drivers.
337 0 : if (version(mnMajorVersion, mnMinorVersion, mnRevisionVersion) < version(3, 0))
338 : {
339 : SAL_WARN("vcl.opengl", "blocked driver version: require at least OpenGL 3 for fglrx");
340 0 : return true;
341 : }
342 : // Bug moz#724640: FGLRX + Linux 2.6.32 is a crashy combo
343 0 : bool unknownOS = maOS.isEmpty() || maOSRelease.isEmpty();
344 0 : bool badOS = maOS.indexOf("Linux") != -1 &&
345 0 : maOSRelease.indexOf("2.6.32") != -1;
346 0 : if (unknownOS || badOS)
347 : {
348 : SAL_WARN("vcl.opengl", "blocked OS version with fglrx");
349 0 : return true;
350 : }
351 : }
352 : else
353 : {
354 : // like on windows, let's block unknown vendors. Think of virtual machines.
355 : // Also, this case is hit whenever the GLXtest probe failed to get driver info or crashed.
356 : SAL_WARN("vcl.opengl", "unknown vendor => blocked");
357 4 : return true;
358 : }
359 :
360 8 : return false;
361 : }
362 :
363 : /* vim:set shiftwidth=4 softtabstop=4 expandtab: */
|