[Libreoffice-commits] core.git: desktop/Library_sofficeapp.mk desktop/source desktop/unx include/vcl vcl/inc vcl/Library_vcl.mk vcl/opengl vcl/source

Markus Mohrhard markus.mohrhard at collabora.co.uk
Thu Nov 20 04:51:27 PST 2014


 desktop/Library_sofficeapp.mk        |   15 +
 desktop/source/app/sofficemain.cxx   |   12 +
 desktop/unx/source/glxtest.cxx       |  281 ++++++++++++++++++++++++++++++
 include/vcl/opengl/OpenGLHelper.hxx  |    4 
 include/vcl/opengl/glxtest.hxx       |   21 ++
 vcl/Library_vcl.mk                   |    5 
 vcl/inc/opengl/DeviceInfo.hxx        |   23 ++
 vcl/inc/opengl/x11/X11DeviceInfo.hxx |   51 +++++
 vcl/opengl/DeviceInfo.cxx            |   16 +
 vcl/opengl/x11/X11DeviceInfo.cxx     |  326 +++++++++++++++++++++++++++++++++++
 vcl/source/opengl/OpenGLHelper.cxx   |   26 ++
 11 files changed, 779 insertions(+), 1 deletion(-)

New commits:
commit a55f740045d6d30e1ef5522889d2dc359ca5784a
Author: Markus Mohrhard <markus.mohrhard at collabora.co.uk>
Date:   Thu Nov 20 09:52:03 2014 +0100

    include the unx part for getting OpenGL driver & device information
    
    desktop/unx/source/glxtest.cxx is taken directly from the Mozilla
    project.
    
    THe whole concept is taken from Mozilla and is based on starting an
    early process that creates an OpenGL context. This prevents crashing
    drivers to crash Libreoffice.
    
    We read the information from the pipe as soon as we create the first vcl
    Window. In that place we then decide if the device/driver combination is
    blacklisted.
    
    Change-Id: I2624d4ce06d503281a4459cf3174f57cf1f7b733

diff --git a/desktop/Library_sofficeapp.mk b/desktop/Library_sofficeapp.mk
index be86dd1..f322a6c 100644
--- a/desktop/Library_sofficeapp.mk
+++ b/desktop/Library_sofficeapp.mk
@@ -94,6 +94,21 @@ $(eval $(call gb_Library_add_exception_objects,sofficeapp,\
     desktop/source/migration/migration \
 ))
 
+ifeq ($(OS),LINUX)
+$(eval $(call gb_Library_add_exception_objects,sofficeapp,\
+    desktop/unx/source/glxtest \
+))
+
+$(eval $(call gb_Library_add_libs,sofficeapp,\
+	-lm \
+	-ldl \
+	-lpthread \
+    -lGL \
+    -lGLU \
+    -lX11 \
+))
+endif
+
 # liblibreoffice bits
 $(eval $(call gb_Library_add_exception_objects,sofficeapp,\
 	desktop/source/lib/init \
diff --git a/desktop/source/app/sofficemain.cxx b/desktop/source/app/sofficemain.cxx
index dff16e8..606ba4f 100644
--- a/desktop/source/app/sofficemain.cxx
+++ b/desktop/source/app/sofficemain.cxx
@@ -53,8 +53,20 @@
 #include <touch/touch.h>
 #endif
 
+#if defined( UNX ) && !defined MACOSX && !defined IOS && !defined ANDROID
+bool fire_glxtest_process();
+#endif
+
 extern "C" int DESKTOP_DLLPUBLIC soffice_main()
 {
+#if defined( UNX ) && !defined MACOSX && !defined IOS && !defined ANDROID
+    /* Run test for OpenGL support in own process to avoid crash with broken
+     * OpenGL drivers. Start process as early as possible.
+     */
+    bool bSuccess = fire_glxtest_process();
+    SAL_WARN_IF(!bSuccess, "desktop.opengl", "problems with glxtest");
+#endif
+
 #if defined ANDROID
     try {
         rtl::Bootstrap::setIniFilename("file:///assets/program/lofficerc");
diff --git a/desktop/unx/source/glxtest.cxx b/desktop/unx/source/glxtest.cxx
new file mode 100644
index 0000000..d8d9c20
--- /dev/null
+++ b/desktop/unx/source/glxtest.cxx
@@ -0,0 +1,281 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * vim: sw=2 ts=8 et :
+ */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+
+//////////////////////////////////////////////////////////////////////////////
+//
+// Explanation: See bug 639842. Safely getting GL driver info on X11 is hard, because the only way to do
+// that is to create a GL context and call glGetString(), but with bad drivers,
+// just creating a GL context may crash.
+//
+// This file implements the idea to do that in a separate process.
+//
+// The only non-static function here is fire_glxtest_process(). It creates a pipe, publishes its 'read' end as the
+// mozilla::widget::glxtest_pipe global variable, forks, and runs that GLX probe in the child process,
+// which runs the glxtest() static function. This creates a X connection, a GLX context, calls glGetString, and writes that
+// to the 'write' end of the pipe.
+
+#include <cstdio>
+#include <cstdlib>
+#include <unistd.h>
+#include <dlfcn.h>
+#include <fcntl.h>
+#include "stdint.h"
+#include <string.h>
+
+#include <vcl/opengl/glxtest.hxx>
+
+#ifdef __SUNPRO_CC
+#include <stdio.h>
+#endif
+
+#include "X11/Xlib.h"
+#include "X11/Xutil.h"
+
+// stuff from glx.h
+typedef struct __GLXcontextRec *GLXContext;
+typedef XID GLXPixmap;
+typedef XID GLXDrawable;
+/* GLX 1.3 and later */
+typedef struct __GLXFBConfigRec *GLXFBConfig;
+typedef XID GLXFBConfigID;
+typedef XID GLXContextID;
+typedef XID GLXWindow;
+typedef XID GLXPbuffer;
+#define GLX_RGBA        4
+#define GLX_RED_SIZE    8
+#define GLX_GREEN_SIZE  9
+#define GLX_BLUE_SIZE   10
+
+// stuff from gl.h
+typedef uint8_t GLubyte;
+typedef uint32_t GLenum;
+#define GL_VENDOR       0x1F00
+#define GL_RENDERER     0x1F01
+#define GL_VERSION      0x1F02
+
+// the write end of the pipe, which we're going to write to
+static int write_end_of_the_pipe = -1;
+
+// C++ standard collides with C standard in that it doesn't allow casting void* to function pointer types.
+// So the work-around is to convert first to size_t.
+// http://www.trilithium.com/johan/2004/12/problem-with-dlsym/
+template<typename func_ptr_type>
+static func_ptr_type cast(void *ptr)
+{
+  return reinterpret_cast<func_ptr_type>(
+           reinterpret_cast<size_t>(ptr)
+         );
+}
+
+static void fatal_error(const char *str)
+{
+  write(write_end_of_the_pipe, str, strlen(str));
+  write(write_end_of_the_pipe, "\n", 1);
+  _exit(EXIT_FAILURE);
+}
+
+static int
+x_error_handler(Display *, XErrorEvent *ev)
+{
+  enum { bufsize = 1024 };
+  char buf[bufsize];
+  int length = snprintf(buf, bufsize,
+                        "X error occurred in GLX probe, error_code=%d, request_code=%d, minor_code=%d\n",
+                        ev->error_code,
+                        ev->request_code,
+                        ev->minor_code);
+  write(write_end_of_the_pipe, buf, length);
+  _exit(EXIT_FAILURE);
+  return 0;
+}
+
+
+// glxtest is declared inside extern "C" so that the name is not mangled.
+// The name is used in build/valgrind/x86_64-redhat-linux-gnu.sup to suppress
+// memory leak errors because we run it inside a short lived fork and we don't
+// care about leaking memory
+extern "C" {
+
+void glxtest()
+{
+  // we want to redirect to /dev/null stdout, stderr, and while we're at it,
+  // any PR logging file descriptors. To that effect, we redirect all positive
+  // file descriptors up to what open() returns here. In particular, 1 is stdout and 2 is stderr.
+  int fd = open("/dev/null", O_WRONLY);
+  for (int i = 1; i < fd; i++)
+    dup2(fd, i);
+  close(fd);
+
+  if (getenv("MOZ_AVOID_OPENGL_ALTOGETHER"))
+    fatal_error("The MOZ_AVOID_OPENGL_ALTOGETHER environment variable is defined");
+
+  ///// Open libGL and load needed symbols /////
+#ifdef __OpenBSD__
+  #define LIBGL_FILENAME "libGL.so"
+#else
+  #define LIBGL_FILENAME "libGL.so.1"
+#endif
+  void *libgl = dlopen(LIBGL_FILENAME, RTLD_LAZY);
+  if (!libgl)
+    fatal_error("Unable to load " LIBGL_FILENAME);
+
+  typedef void* (* PFNGLXGETPROCADDRESS) (const char *);
+  PFNGLXGETPROCADDRESS glXGetProcAddress = cast<PFNGLXGETPROCADDRESS>(dlsym(libgl, "glXGetProcAddress"));
+
+  if (!glXGetProcAddress)
+    fatal_error("Unable to find glXGetProcAddress in " LIBGL_FILENAME);
+
+  typedef GLXFBConfig* (* PFNGLXQUERYEXTENSION) (Display *, int *, int *);
+  PFNGLXQUERYEXTENSION glXQueryExtension = cast<PFNGLXQUERYEXTENSION>(glXGetProcAddress("glXQueryExtension"));
+
+  typedef GLXFBConfig* (* PFNGLXQUERYVERSION) (Display *, int *, int *);
+  PFNGLXQUERYVERSION glXQueryVersion = cast<PFNGLXQUERYVERSION>(dlsym(libgl, "glXQueryVersion"));
+
+  typedef XVisualInfo* (* PFNGLXCHOOSEVISUAL) (Display *, int, int *);
+  PFNGLXCHOOSEVISUAL glXChooseVisual = cast<PFNGLXCHOOSEVISUAL>(glXGetProcAddress("glXChooseVisual"));
+
+  typedef GLXContext (* PFNGLXCREATECONTEXT) (Display *, XVisualInfo *, GLXContext, Bool);
+  PFNGLXCREATECONTEXT glXCreateContext = cast<PFNGLXCREATECONTEXT>(glXGetProcAddress("glXCreateContext"));
+
+  typedef Bool (* PFNGLXMAKECURRENT) (Display*, GLXDrawable, GLXContext);
+  PFNGLXMAKECURRENT glXMakeCurrent = cast<PFNGLXMAKECURRENT>(glXGetProcAddress("glXMakeCurrent"));
+
+  typedef void (* PFNGLXDESTROYCONTEXT) (Display*, GLXContext);
+  PFNGLXDESTROYCONTEXT glXDestroyContext = cast<PFNGLXDESTROYCONTEXT>(glXGetProcAddress("glXDestroyContext"));
+
+  typedef GLubyte* (* PFNGLGETSTRING) (GLenum);
+  PFNGLGETSTRING glGetString = cast<PFNGLGETSTRING>(glXGetProcAddress("glGetString"));
+
+  if (!glXQueryExtension ||
+      !glXQueryVersion ||
+      !glXChooseVisual ||
+      !glXCreateContext ||
+      !glXMakeCurrent ||
+      !glXDestroyContext ||
+      !glGetString)
+  {
+    fatal_error("glXGetProcAddress couldn't find required functions");
+  }
+  ///// Open a connection to the X server /////
+  Display *dpy = XOpenDisplay(nullptr);
+  if (!dpy)
+    fatal_error("Unable to open a connection to the X server");
+
+  ///// Check that the GLX extension is present /////
+  if (!glXQueryExtension(dpy, nullptr, nullptr))
+    fatal_error("GLX extension missing");
+
+  XSetErrorHandler(x_error_handler);
+
+  ///// Get a visual /////
+   int attribs[] = {
+      GLX_RGBA,
+      GLX_RED_SIZE, 1,
+      GLX_GREEN_SIZE, 1,
+      GLX_BLUE_SIZE, 1,
+      None };
+  XVisualInfo *vInfo = glXChooseVisual(dpy, DefaultScreen(dpy), attribs);
+  if (!vInfo)
+    fatal_error("No visuals found");
+
+  // using a X11 Window instead of a GLXPixmap does not crash
+  // fglrx in indirect rendering. bug 680644
+  Window window;
+  XSetWindowAttributes swa;
+  swa.colormap = XCreateColormap(dpy, RootWindow(dpy, vInfo->screen),
+                                 vInfo->visual, AllocNone);
+
+  swa.border_pixel = 0;
+  window = XCreateWindow(dpy, RootWindow(dpy, vInfo->screen),
+                       0, 0, 16, 16,
+                       0, vInfo->depth, InputOutput, vInfo->visual,
+                       CWBorderPixel | CWColormap, &swa);
+
+  ///// Get a GL context and make it current //////
+  GLXContext context = glXCreateContext(dpy, vInfo, nullptr, True);
+  glXMakeCurrent(dpy, window, context);
+
+  ///// Look for this symbol to determine texture_from_pixmap support /////
+  void* glXBindTexImageEXT = glXGetProcAddress("glXBindTexImageEXT");
+
+  ///// Get GL vendor/renderer/versions strings /////
+  enum { bufsize = 1024 };
+  char buf[bufsize];
+  const GLubyte *vendorString = glGetString(GL_VENDOR);
+  const GLubyte *rendererString = glGetString(GL_RENDERER);
+  const GLubyte *versionString = glGetString(GL_VERSION);
+
+  if (!vendorString || !rendererString || !versionString)
+    fatal_error("glGetString returned null");
+
+  int length = snprintf(buf, bufsize,
+                        "VENDOR\n%s\nRENDERER\n%s\nVERSION\n%s\nTFP\n%s\n",
+                        vendorString,
+                        rendererString,
+                        versionString,
+                        glXBindTexImageEXT ? "TRUE" : "FALSE");
+  if (length >= bufsize)
+    fatal_error("GL strings length too large for buffer size");
+
+  ///// Clean up. Indeed, the parent process might fail to kill us (e.g. if it doesn't need to check GL info)
+  ///// so we might be staying alive for longer than expected, so it's important to consume as little memory as
+  ///// possible. Also we want to check that we're able to do that too without generating X errors.
+  glXMakeCurrent(dpy, None, nullptr); // must release the GL context before destroying it
+  glXDestroyContext(dpy, context);
+  XDestroyWindow(dpy, window);
+  XFreeColormap(dpy, swa.colormap);
+
+#ifdef NS_FREE_PERMANENT_DATA // conditionally defined in nscore.h, don't forget to #include it above
+  XCloseDisplay(dpy);
+#else
+  // This XSync call wanted to be instead:
+  //   XCloseDisplay(dpy);
+  // but this can cause 1-minute stalls on certain setups using Nouveau, see bug 973192
+  XSync(dpy, False);
+#endif
+
+  dlclose(libgl);
+
+  ///// Finally write data to the pipe
+  write(write_end_of_the_pipe, buf, length);
+}
+
+}
+
+/** \returns true in the child glxtest process, false in the parent process */
+bool fire_glxtest_process()
+{
+  int pfd[2];
+  if (pipe(pfd) == -1) {
+      perror("pipe");
+      return false;
+  }
+  pid_t pid = fork();
+  if (pid < 0) {
+      perror("fork");
+      close(pfd[0]);
+      close(pfd[1]);
+      return false;
+  }
+  // The child exits early to avoid running the full shutdown sequence and avoid conflicting with threads
+  // we have already spawned (like the profiler).
+  if (pid == 0) {
+      close(pfd[0]);
+      write_end_of_the_pipe = pfd[1];
+      glxtest();
+      close(pfd[1]);
+      _exit(0);
+  }
+
+  close(pfd[1]);
+  int* glxtest_pipe = getGlxPipe();
+  *glxtest_pipe = pfd[0];
+  pid_t* glxtest_pid = getGlxPid();
+  *glxtest_pid = pid;
+  return true;
+}
diff --git a/include/vcl/opengl/OpenGLHelper.hxx b/include/vcl/opengl/OpenGLHelper.hxx
index ee5a9f8..2430515 100644
--- a/include/vcl/opengl/OpenGLHelper.hxx
+++ b/include/vcl/opengl/OpenGLHelper.hxx
@@ -54,6 +54,10 @@ public:
     static void checkGLError(const char* aFile, size_t nLine);
 
     /**
+     * checks if the device/driver pair is on our OpenGL blacklist
+     */
+    static bool isDeviceBlacklisted();
+    /**
      * checks if the system supports all features that are necessary for the OpenGL VCL support
      */
     static bool supportsVCLOpenGL();
diff --git a/include/vcl/opengl/glxtest.hxx b/include/vcl/opengl/glxtest.hxx
new file mode 100644
index 0000000..0889cdd
--- /dev/null
+++ b/include/vcl/opengl/glxtest.hxx
@@ -0,0 +1,21 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ */
+
+#ifndef INCLUDED_VCL_OPENGL_GLXTEST_HXX
+#define INCLUDED_VCL_OPENGL_GLXTEST_HXX
+
+#include <vcl/dllapi.h>
+
+VCL_DLLPUBLIC int* getGlxPipe();
+
+VCL_DLLPUBLIC pid_t* getGlxPid();
+
+#endif
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/Library_vcl.mk b/vcl/Library_vcl.mk
index 81f774a..932ca4c 100644
--- a/vcl/Library_vcl.mk
+++ b/vcl/Library_vcl.mk
@@ -122,6 +122,7 @@ $(eval $(call gb_Library_use_externals,vcl,\
 ))
 
 $(eval $(call gb_Library_add_exception_objects,vcl,\
+	vcl/opengl/DeviceInfo \
 	vcl/opengl/gdiimpl \
 	vcl/opengl/salbmp \
 	vcl/opengl/scale \
@@ -705,6 +706,10 @@ $(eval $(call gb_Library_add_libs,vcl,\
     -lGLU \
     -lX11 \
 ))
+
+$(eval $(call gb_Library_add_exception_objects,vcl,\
+	vcl/opengl/x11/X11DeviceInfo \
+))
 endif
 
 ifeq ($(OS),SOLARIS)
diff --git a/vcl/inc/opengl/DeviceInfo.hxx b/vcl/inc/opengl/DeviceInfo.hxx
new file mode 100644
index 0000000..402a3d0
--- /dev/null
+++ b/vcl/inc/opengl/DeviceInfo.hxx
@@ -0,0 +1,23 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ */
+
+#ifndef INCLUDED_VCL_INC_OPENGL_DEVICEINFO_HXX
+#define INCLUDED_VCL_INC_OPENGL_DEVICEINFO_HXX
+
+class OpenGLDeviceInfo
+{
+public:
+    virtual ~OpenGLDeviceInfo();
+
+    virtual bool isDeviceBlocked() = 0;
+};
+
+#endif
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/inc/opengl/x11/X11DeviceInfo.hxx b/vcl/inc/opengl/x11/X11DeviceInfo.hxx
new file mode 100644
index 0000000..5e41b6b
--- /dev/null
+++ b/vcl/inc/opengl/x11/X11DeviceInfo.hxx
@@ -0,0 +1,51 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ */
+
+#ifndef INCLUDED_VCL_INC_OPENGL_X11_X11DEVICEINFO_HXX
+#define INCLUDED_VCL_INC_OPENGL_X11_X11DEVICEINFO_HXX
+
+#include "opengl/DeviceInfo.hxx"
+
+#include <rtl/string.hxx>
+
+class X11OpenGLDeviceInfo : public OpenGLDeviceInfo
+{
+private:
+    bool mbIsMesa;
+    bool mbIsNVIDIA;
+    bool mbIsFGLRX;
+    bool mbIsNouveau;
+    bool mbIsIntel;
+    bool mbIsOldSwrast;
+    bool mbIsLlvmpipe;
+    bool mbHasTextureFromPixmap;
+
+    OString maVendor;
+    OString maRenderer;
+    OString maVersion;
+    OString maOS;
+    OString maOSRelease;
+
+    size_t mnGLMajorVersion;
+    size_t mnMajorVersion;
+    size_t mnMinorVersion;
+    size_t mnRevisionVersion;
+
+    void GetData();
+
+public:
+    X11OpenGLDeviceInfo();
+    virtual ~X11OpenGLDeviceInfo();
+
+    virtual bool isDeviceBlocked();
+};
+
+#endif
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/opengl/DeviceInfo.cxx b/vcl/opengl/DeviceInfo.cxx
new file mode 100644
index 0000000..135e0e7
--- /dev/null
+++ b/vcl/opengl/DeviceInfo.cxx
@@ -0,0 +1,16 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ */
+
+#include "opengl/DeviceInfo.hxx"
+
+OpenGLDeviceInfo::~OpenGLDeviceInfo()
+{
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/opengl/x11/X11DeviceInfo.cxx b/vcl/opengl/x11/X11DeviceInfo.cxx
new file mode 100644
index 0000000..24fd279
--- /dev/null
+++ b/vcl/opengl/x11/X11DeviceInfo.cxx
@@ -0,0 +1,326 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ */
+
+#include "opengl/x11/X11DeviceInfo.hxx"
+
+#include <vcl/opengl/glxtest.hxx>
+#include <rtl/ustring.hxx>
+
+#include <unistd.h>
+#include <sys/types.h>
+#include <sys/wait.h>
+#include <errno.h>
+#include <sys/utsname.h>
+
+namespace glx {
+
+static int glxtest_pipe = 0;
+
+static pid_t glxtest_pid = 0;
+
+}
+
+pid_t* getGlxPid()
+{
+    return &glx::glxtest_pid;
+}
+
+int* getGlxPipe()
+{
+    return &glx::glxtest_pipe;
+}
+
+namespace {
+
+const char*
+strspnp_wrapper(const char* aDelims, const char* aStr)
+{
+  const char* d;
+  do {
+    for (d = aDelims; *d != '\0'; ++d) {
+      if (*aStr == *d) {
+        ++aStr;
+        break;
+      }
+    }
+  } while (*d);
+
+  return aStr;
+}
+
+char* strtok_wrapper(const char* aDelims, char** aStr)
+{
+    if (!*aStr) {
+        return nullptr;
+    }
+
+    char* ret = (char*)strspnp_wrapper(aDelims, *aStr);
+
+    if (!*ret) {
+        *aStr = ret;
+        return nullptr;
+    }
+
+    char* i = ret;
+    do {
+        for (const char* d = aDelims; *d != '\0'; ++d) {
+            if (*i == *d) {
+                *i = '\0';
+                *aStr = ++i;
+                return ret;
+            }
+        }
+        ++i;
+    } while (*i);
+
+    *aStr = nullptr;
+    return ret;
+}
+
+uint64_t version(uint32_t major, uint32_t minor, uint32_t revision = 0)
+{
+    return (uint64_t(major) << 32) + (uint64_t(minor) << 16) + uint64_t(revision);
+}
+
+}
+
+X11OpenGLDeviceInfo::X11OpenGLDeviceInfo():
+    mbIsMesa(false),
+    mbIsNVIDIA(false),
+    mbIsFGLRX(false),
+    mbIsNouveau(false),
+    mbIsIntel(false),
+    mbIsOldSwrast(false),
+    mbIsLlvmpipe(false),
+    mbHasTextureFromPixmap(false),
+    mnGLMajorVersion(0),
+    mnMajorVersion(0),
+    mnMinorVersion(0),
+    mnRevisionVersion(0)
+{
+    GetData();
+}
+
+X11OpenGLDeviceInfo::~X11OpenGLDeviceInfo()
+{
+}
+
+void X11OpenGLDeviceInfo::GetData()
+{
+    if (!glx::glxtest_pipe)
+       return;
+
+    // to understand this function, see bug 639842. We retrieve the OpenGL driver information in a
+    // separate process to protect against bad drivers.
+
+    enum { buf_size = 1024 };
+    char buf[buf_size];
+    ssize_t bytesread = read(glx::glxtest_pipe,
+            &buf,
+            buf_size-1); // -1 because we'll append a zero
+    close(glx::glxtest_pipe);
+    glx::glxtest_pipe = 0;
+
+    // bytesread < 0 would mean that the above read() call failed.
+    // This should never happen. If it did, the outcome would be to blacklist anyway.
+    if (bytesread < 0)
+        bytesread = 0;
+
+    // let buf be a zero-terminated string
+    buf[bytesread] = 0;
+
+    // Wait for the glxtest process to finish. This serves 2 purposes:
+    // * avoid having a zombie glxtest process laying around
+    // * get the glxtest process status info.
+    int glxtest_status = 0;
+    bool wait_for_glxtest_process = true;
+    bool waiting_for_glxtest_process_failed = false;
+    int waitpid_errno = 0;
+    while(wait_for_glxtest_process) {
+        wait_for_glxtest_process = false;
+        if (waitpid(glx::glxtest_pid, &glxtest_status, 0) == -1) {
+            waitpid_errno = errno;
+            if (waitpid_errno == EINTR) {
+                wait_for_glxtest_process = true;
+            } else {
+                // Bug 718629
+                // ECHILD happens when the glxtest process got reaped got reaped after a PR_CreateProcess
+                // as per bug 227246. This shouldn't matter, as we still seem to get the data
+                // from the pipe, and if we didn't, the outcome would be to blacklist anyway.
+                waiting_for_glxtest_process_failed = (waitpid_errno != ECHILD);
+            }
+        }
+    }
+
+    bool exited_with_error_code = !waiting_for_glxtest_process_failed &&
+        WIFEXITED(glxtest_status) &&
+        WEXITSTATUS(glxtest_status) != EXIT_SUCCESS;
+    bool received_signal = !waiting_for_glxtest_process_failed &&
+        WIFSIGNALED(glxtest_status);
+
+    bool error = waiting_for_glxtest_process_failed || exited_with_error_code || received_signal;
+
+    OString textureFromPixmap;
+    OString *stringToFill = nullptr;
+    char *bufptr = buf;
+    if (!error) {
+        while(true) {
+            char *line = strtok_wrapper("\n", &bufptr);
+            if (!line)
+                break;
+            if (stringToFill) {
+                *stringToFill = OString(line);
+                stringToFill = nullptr;
+            }
+            else if(!strcmp(line, "VENDOR"))
+                stringToFill = &maVendor;
+            else if(!strcmp(line, "RENDERER"))
+                stringToFill = &maRenderer;
+            else if(!strcmp(line, "VERSION"))
+                stringToFill = &maVersion;
+            else if(!strcmp(line, "TFP"))
+                stringToFill = &textureFromPixmap;
+        }
+    }
+
+    if (!strcmp(textureFromPixmap.getStr(), "TRUE"))
+        mbHasTextureFromPixmap = true;
+
+    // only useful for Linux kernel version check for FGLRX driver.
+    // assumes X client == X server, which is sad.
+    struct utsname unameobj;
+    if (!uname(&unameobj))
+    {
+        maOS = OString(unameobj.sysname);
+        maOSRelease = OString(unameobj.release);
+    }
+
+    // determine the major OpenGL version. That's the first integer in the version string.
+    mnGLMajorVersion = strtol(maVersion.getStr(), 0, 10);
+
+    // determine driver type (vendor) and where in the version string
+    // the actual driver version numbers should be expected to be found (whereToReadVersionNumbers)
+    const char *whereToReadVersionNumbers = nullptr;
+    const char *Mesa_in_version_string = strstr(maVersion.getStr(), "Mesa");
+    if (Mesa_in_version_string) {
+        mbIsMesa = true;
+        // with Mesa, the version string contains "Mesa major.minor" and that's all the version information we get:
+        // there is no actual driver version info.
+        whereToReadVersionNumbers = Mesa_in_version_string + strlen("Mesa");
+        if (strcasestr(maVendor.getStr(), "nouveau"))
+            mbIsNouveau = true;
+        if (strcasestr(maRenderer.getStr(), "intel")) // yes, intel is in the renderer string
+            mbIsIntel = true;
+        if (strcasestr(maRenderer.getStr(), "llvmpipe"))
+            mbIsLlvmpipe = true;
+        if (strcasestr(maRenderer.getStr(), "software rasterizer"))
+            mbIsOldSwrast = true;
+    } else if (strstr(maVendor.getStr(), "NVIDIA Corporation")) {
+        mbIsNVIDIA = true;
+        // with the NVIDIA driver, the version string contains "NVIDIA major.minor"
+        // note that here the vendor and version strings behave differently, that's why we don't put this above
+        // alongside Mesa_in_version_string.
+        const char *NVIDIA_in_version_string = strstr(maVersion.getStr(), "NVIDIA");
+        if (NVIDIA_in_version_string)
+            whereToReadVersionNumbers = NVIDIA_in_version_string + strlen("NVIDIA");
+    } else if (strstr(maVendor.getStr(), "ATI Technologies Inc")) {
+        mbIsFGLRX = true;
+        // with the FGLRX driver, the version string only gives a OpenGL version :/ so let's return that.
+        // that can at least give a rough idea of how old the driver is.
+        whereToReadVersionNumbers = maVersion.getStr();
+    }
+
+    // read major.minor version numbers of the driver (not to be confused with the OpenGL version)
+    if (whereToReadVersionNumbers) {
+        // copy into writable buffer, for tokenization
+        strncpy(buf, whereToReadVersionNumbers, buf_size);
+        bufptr = buf;
+
+        // now try to read major.minor version numbers. In case of failure, gracefully exit: these numbers have
+        // been initialized as 0 anyways
+        char *token = strtok_wrapper(".", &bufptr);
+        if (token) {
+            mnMajorVersion = strtol(token, 0, 10);
+            token = strtok_wrapper(".", &bufptr);
+            if (token) {
+                mnMinorVersion = strtol(token, 0, 10);
+                token = strtok_wrapper(".", &bufptr);
+                if (token)
+                    mnRevisionVersion = strtol(token, 0, 10);
+            }
+        }
+    }
+}
+
+bool X11OpenGLDeviceInfo::isDeviceBlocked()
+{
+    // don't even try to use OpenGL 1.x
+    if (mnGLMajorVersion == 1)
+        return true;
+
+    SAL_INFO("vcl.opengl", "Vendor: " << maVendor);
+    SAL_INFO("vcl.opengl", "Renderer: " << maRenderer);
+    SAL_INFO("vcl.opengl", "Version: " << maVersion);
+    SAL_INFO("vcl.opengl", "OS: " << maOS);
+    SAL_INFO("vcl.opengl", "OSRelease: " << maOSRelease);
+
+
+    if (mbIsMesa) {
+        if (mbIsNouveau && version(mnMajorVersion, mnMinorVersion) < version(8,0)) {
+            SAL_WARN("vcl.opengl", "blocked driver version: old nouveau driver (requires mesa 8.0+)");
+            return true;
+        }
+        else if (version(mnMajorVersion, mnMinorVersion, mnRevisionVersion) < version(7,10,3)) {
+            SAL_WARN("vcl.opengl", "blocked driver version: requires at least mesa 7.10.3");
+            return true;
+        }
+        else if (mbIsOldSwrast) {
+            SAL_WARN("vcl.opengl", "blocked driver version: software rasterizer");
+            return true;
+        }
+        else if (mbIsLlvmpipe && version(mnMajorVersion, mnMinorVersion) < version(9, 1)) {
+            // bug 791905, Mesa bug 57733, fixed in Mesa 9.1 according to
+            // https://bugs.freedesktop.org/show_bug.cgi?id=57733#c3
+            SAL_WARN("vcl.opengl", "blocked driver version: fdo#57733");
+            return true;
+        }
+
+    } else if (mbIsNVIDIA) {
+        if (version(mnMajorVersion, mnMinorVersion, mnRevisionVersion) < version(257,21)) {
+            SAL_WARN("vcl.opengl", "blocked driver version: nvidia requires at least 257.21");
+            return true;
+        }
+    } else if (mbIsFGLRX) {
+        // FGLRX does not report a driver version number, so we have the OpenGL version instead.
+        // by requiring OpenGL 3, we effectively require recent drivers.
+        if (version(mnMajorVersion, mnMinorVersion, mnRevisionVersion) < version(3, 0)) {
+            SAL_WARN("vcl.opengl", "blocked driver version: require at least OpenGL 3 for fglrx");
+            return true;
+        }
+        // Bug 724640: FGLRX + Linux 2.6.32 is a crashy combo
+        bool unknownOS = maOS.isEmpty() || maOSRelease.isEmpty();
+        OUString aOS = rtl::OStringToOUString(maOS, RTL_TEXTENCODING_UTF8);
+        OUString aOSRelease = rtl::OStringToOUString(maOSRelease, RTL_TEXTENCODING_UTF8);
+        bool badOS = aOS.indexOf("Linux", true) != -1 &&
+            maOSRelease.indexOf("2.6.32") != -1;
+        if (unknownOS || badOS) {
+            SAL_WARN("vcl.opengl", "blocked OS version with fglrx");
+            return true;
+        }
+    } else {
+        // like on windows, let's block unknown vendors. Think of virtual machines.
+        // Also, this case is hit whenever the GLXtest probe failed to get driver info or crashed.
+        SAL_WARN("vcl.opengl", "unknown vendor => blocked");
+        return true;
+    }
+
+    return false;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/opengl/OpenGLHelper.cxx b/vcl/source/opengl/OpenGLHelper.cxx
index 202ac2b..df8b6d8 100644
--- a/vcl/source/opengl/OpenGLHelper.cxx
+++ b/vcl/source/opengl/OpenGLHelper.cxx
@@ -22,6 +22,10 @@
 
 #include <vector>
 
+#if defined UNX && !defined MACOSX && !defined IOS && !defined ANDROID
+#include "opengl/x11/X11DeviceInfo.hxx"
+#endif
+
 namespace {
 
 OUString getShaderFolder()
@@ -360,11 +364,31 @@ void OpenGLHelper::checkGLError(const char* pFile, size_t nLine)
     }
 }
 
+bool OpenGLHelper::isDeviceBlacklisted()
+{
+    static bool bSet = false;
+    static bool bBlacklisted = true; // assume the worst
+    if (!bSet)
+    {
+#if defined UNX && !defined MACOSX && !defined IOS && !defined ANDROID
+        X11OpenGLDeviceInfo aInfo;
+        bBlacklisted = aInfo.isDeviceBlocked();
+        SAL_INFO("vcl.opengl", "blacklisted: " << bBlacklisted);
+#else
+        bBlacklisted = false;
+#endif
+        bSet = true;
+    }
+
+    return bBlacklisted;
+}
+
 bool OpenGLHelper::supportsVCLOpenGL()
 {
     static bool bDisableGL = !!getenv("SAL_DISABLEGL");
+    bool bBlacklisted = isDeviceBlacklisted();
 
-    if (bDisableGL)
+    if (bDisableGL || bBlacklisted)
         return false;
     else
         return true;


More information about the Libreoffice-commits mailing list