summaryrefslogtreecommitdiff
path: root/desktop
diff options
context:
space:
mode:
authorMarkus Mohrhard <markus.mohrhard@collabora.co.uk>2014-11-20 09:52:03 +0100
committerMarkus Mohrhard <markus.mohrhard@collabora.co.uk>2014-11-20 13:50:51 +0100
commita55f740045d6d30e1ef5522889d2dc359ca5784a (patch)
treecf05fa9cd35419dc488e96f23c67d4eb308f71e0 /desktop
parentdcdc8df2e6d740dd75ff6e28727ca757e4882f86 (diff)
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
Diffstat (limited to 'desktop')
-rw-r--r--desktop/Library_sofficeapp.mk15
-rw-r--r--desktop/source/app/sofficemain.cxx12
-rw-r--r--desktop/unx/source/glxtest.cxx281
3 files changed, 308 insertions, 0 deletions
diff --git a/desktop/Library_sofficeapp.mk b/desktop/Library_sofficeapp.mk
index be86dd10e663..f322a6ca74b6 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 dff16e88a7d9..606ba4fdcc22 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 000000000000..d8d9c2059e84
--- /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;
+}