diff --git a/helpers/check-gl.cpp b/helpers/check-gl.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..be98a20ed959cce0644f3088d4df9b3ef862be85
--- /dev/null
+++ b/helpers/check-gl.cpp
@@ -0,0 +1,263 @@
+/*
+ * Copyright © 2019 Collabora Ltd.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <iostream>
+#include <fstream>
+#include <stdexcept>
+#include <algorithm>
+#include <vector>
+#include <cstring>
+#include <cstdlib>
+#include <cstdint>
+#include <set>
+
+#include <X11/Xlib.h>
+#include <GL/gl.h>
+#include <GL/glx.h>
+
+extern "C" {
+#include <getopt.h>
+};
+
+const int WIDTH = 200;
+const int HEIGHT = 200;
+
+static const char *argv0;
+
+class HelloTriangleGLApplication {
+public:
+    HelloTriangleGLApplication(bool visible)
+      : m_visible(visible),
+        m_display(nullptr),
+        m_window(0),
+        m_context(0)
+    {
+    }
+
+    void run() {
+        initGL();
+        mainLoop();
+        cleanup();
+    }
+
+private:
+    bool m_visible;
+    Display *m_display;
+    Window m_window;
+    GLXContext m_context;
+
+    void initGL() {
+        m_display = XOpenDisplay(nullptr);
+        if (!m_display)
+          {
+            throw std::runtime_error("Unable to open display");
+          }
+
+        makeWindow();
+
+        if (m_visible)
+          XMapWindow(m_display, m_window);
+
+        glXMakeCurrent(m_display, m_window, m_context);
+    }
+
+    void drawTriangle()
+    {
+        //clear color and depth buffer
+        glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
+        glLoadIdentity();
+
+        // Make it red
+        glColor3f(1.0f,0.0f,0.0f);
+
+        glBegin(GL_TRIANGLES);
+        glVertex3f(-0.8f,-0.8f,0.0f);
+        glVertex3f(0.8f,-0.8f, 0.0f);
+        glVertex3f(0.0f, 0.6f,0.0f);
+        glEnd();
+
+        glEndList();
+
+        glEnable(GL_NORMALIZE);
+    }
+
+    void makeWindow()
+    {
+       int attribs[64];
+       int i = 0;
+
+       int scrnum;
+       XSetWindowAttributes attr;
+       unsigned long mask;
+       Window root;
+       XVisualInfo *visinfo;
+
+       /* Singleton attributes. */
+       attribs[i++] = GLX_RGBA;
+       attribs[i++] = GLX_DOUBLEBUFFER;
+
+       /* Key/value attributes. */
+       attribs[i++] = GLX_RED_SIZE;
+       attribs[i++] = 1;
+       attribs[i++] = GLX_GREEN_SIZE;
+       attribs[i++] = 1;
+       attribs[i++] = GLX_BLUE_SIZE;
+       attribs[i++] = 1;
+       attribs[i++] = GLX_DEPTH_SIZE;
+       attribs[i++] = 1;
+
+       attribs[i++] = None;
+
+       scrnum = DefaultScreen(m_display);
+       root = RootWindow(m_display, scrnum);
+
+       visinfo = glXChooseVisual(m_display, scrnum, attribs);
+       if (!visinfo)
+        {
+          throw std::runtime_error("Error: couldn't get an RGB, Double-buffered visual");
+          exit(1);
+        }
+
+       /* window attributes */
+       attr.background_pixel = 0;
+       attr.border_pixel = 0;
+       attr.colormap = XCreateColormap(m_display, root, visinfo->visual, AllocNone);
+       attr.event_mask = StructureNotifyMask | ExposureMask | KeyPressMask;
+       mask = CWBackPixel | CWBorderPixel | CWColormap | CWEventMask;
+
+       m_window = XCreateWindow( m_display, root, 0, 0, WIDTH, HEIGHT,
+                            0, visinfo->depth, InputOutput,
+                            visinfo->visual, mask, &attr );
+
+       /* set hints and properties */
+         {
+            XSizeHints sizehints;
+            sizehints.x = 0;
+            sizehints.y = 0;
+            sizehints.width  = WIDTH;
+            sizehints.height = HEIGHT;
+            sizehints.flags = USSize | USPosition;
+            XSetNormalHints(m_display, m_window, &sizehints);
+            XSetStandardProperties(m_display, m_window, "check-gl", "check-gl",
+                                    None, (char **)NULL, 0, &sizehints);
+         }
+
+       m_context = glXCreateContext(m_display, visinfo, NULL, True );
+       if (!m_context)
+        {
+          throw std::runtime_error("Error: glXCreateContext failed");
+        }
+
+       XFree(visinfo);
+    }
+
+    void mainLoop() {
+        for (int i = 0; i < (m_visible ? 10000 : 10); ++i) {
+          drawFrame();
+        }
+    }
+
+    void cleanup() {
+        if (m_display) {
+          glXMakeCurrent(m_display, None, nullptr);
+          if (m_context) {
+              glXDestroyContext(m_display, m_context);
+          }
+          if (m_window) {
+              XDestroyWindow(m_display, m_window);
+          }
+          XCloseDisplay(m_display);
+        }
+    }
+
+    void drawFrame() {
+        drawTriangle();
+
+        glXSwapBuffers(m_display, m_window);
+    }
+
+};
+
+enum {
+    OPTION_HELP = 1,
+    OPTION_VERSION,
+    OPTION_VISIBLE,
+};
+
+static struct option long_options[] = {
+    { "help", no_argument, NULL, OPTION_HELP },
+    { "version", no_argument, NULL, OPTION_VERSION },
+    { "visible", no_argument, NULL, OPTION_VISIBLE },
+    { NULL, 0, NULL, 0 }
+};
+
+static void usage(int code) __attribute__((__noreturn__));
+static void usage(int code) {
+    std::ostream& stream = (code == EXIT_SUCCESS ? std::cout : std::cerr);
+
+    stream << "Usage: " << argv0 << " [OPTIONS]" << std::endl;
+    stream << "Options:" << std::endl;
+    stream << "--help\t\tShow this help and exit" << std::endl;
+    stream << "--visible\tMake test window visible" << std::endl;
+    stream << "--version\tShow version and exit" << std::endl;
+    std::exit(code);
+}
+
+int main(int argc, char** argv) {
+    int opt;
+    bool visible = false;
+
+    argv0 = argv[0];
+
+    while ((opt = getopt_long(argc, argv, "", long_options, NULL)) != -1) {
+      switch (opt) {
+        case OPTION_HELP:
+          usage(0);
+          break;  // not reached
+
+        case OPTION_VERSION:
+          /* Output version number as YAML for machine-readability,
+           * inspired by `ostree --version` and `docker version` */
+          std::cout << argv[0] << ":" << std::endl
+                    << " Package: steam-runtime-tools" << std::endl
+                    << " Version: " << VERSION << std::endl;
+          return EXIT_SUCCESS;
+
+        case OPTION_VISIBLE:
+          visible = true;
+          break;
+
+        case '?':
+        default:
+          usage(2);
+          break;  // not reached
+      }
+    }
+
+    HelloTriangleGLApplication app(visible);
+
+    try {
+        app.run();
+    } catch (const std::exception& e) {
+        std::cerr << e.what() << std::endl;
+        return EXIT_FAILURE;
+    }
+
+    return EXIT_SUCCESS;
+}
+
diff --git a/helpers/meson.build b/helpers/meson.build
index 65eed6ed89972bc232c8e25d2b89f523ebdd3dfb..75dfd1544f4d15c876f0025efdd8f95a8bc8613e 100644
--- a/helpers/meson.build
+++ b/helpers/meson.build
@@ -109,4 +109,16 @@ custom_target(
   )
 )
 
+executable(
+  multiarch + '-check-gl',
+  'check-gl.cpp',
+  dependencies : [xlib, gl],
+  include_directories : project_include_dirs,
+  install : true,
+  install_dir : join_paths(
+    get_option('libexecdir'),
+    'steam-runtime-tools-' + api_major,
+  )
+)
+
 # vim:set sw=2 sts=2 et:
diff --git a/meson.build b/meson.build
index 13bd831d069b59cc8b43635a58ff8358a51e1d9b..45d9c62c94b176225b368a752b59e07fadec0f14 100644
--- a/meson.build
+++ b/meson.build
@@ -190,6 +190,14 @@ xcb = dependency(
   'xcb',
 )
 
+gl = dependency(
+  'gl'
+)
+
+xlib = dependency(
+  'x11'
+)
+
 libdl = c_compiler.find_library('dl', required : false)
 
 project_include_dirs = include_directories('.')