Merge mozilla-central to autoland. CLOSED TREE
[gecko.git] / toolkit / xre / glxtest / glxtest.cpp
blob077bf2a396c4b5e5d484ccd56f8b66e38163bd40
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,
11 // because the only way to do that is to create a GL context and call
12 // glGetString(), but with bad drivers, 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
17 // pipe, publishes its 'read' end as the mozilla::widget::glxtest_pipe global
18 // variable, forks, and runs that GLX probe in the child process, which runs the
19 // childgltest() static function. This creates a X connection, a GLX context,
20 // calls glGetString, and writes that to the 'write' end of the pipe.
22 #include <cstdio>
23 #include <cstdlib>
24 #include <dlfcn.h>
25 #include <fcntl.h>
26 #include <unistd.h>
27 #include <getopt.h>
28 #include <vector>
29 #include <stdint.h>
30 #include <string.h>
31 #include <stdarg.h>
32 #include <gdk/gdk.h>
34 #if defined(MOZ_ASAN) || defined(FUZZING)
35 # include <signal.h>
36 #endif
38 #ifdef __SUNPRO_CC
39 # include <stdio.h>
40 #endif
42 #ifdef MOZ_X11
43 # include "X11/Xlib.h"
44 # include "X11/Xutil.h"
45 # include <X11/extensions/Xrandr.h>
46 #endif
48 #include <vector>
49 #include <sys/wait.h>
50 #include "mozilla/ScopeExit.h"
51 #include "mozilla/Types.h"
53 #include "mozilla/GfxInfoUtils.h"
55 #ifdef MOZ_X11
56 // stuff from glx.h
57 typedef struct __GLXcontextRec* GLXContext;
58 typedef XID GLXPixmap;
59 typedef XID GLXDrawable;
60 /* GLX 1.3 and later */
61 typedef struct __GLXFBConfigRec* GLXFBConfig;
62 typedef XID GLXFBConfigID;
63 typedef XID GLXContextID;
64 typedef XID GLXWindow;
65 typedef XID GLXPbuffer;
66 # define GLX_RGBA 4
67 # define GLX_RED_SIZE 8
68 # define GLX_GREEN_SIZE 9
69 # define GLX_BLUE_SIZE 10
70 # define GLX_DOUBLEBUFFER 5
71 #endif
73 // stuff from gl.h
74 typedef uint8_t GLubyte;
75 typedef uint32_t GLenum;
76 #define GL_VENDOR 0x1F00
77 #define GL_RENDERER 0x1F01
78 #define GL_VERSION 0x1F02
80 // GLX_MESA_query_renderer
81 // clang-format off
82 #define GLX_RENDERER_VENDOR_ID_MESA 0x8183
83 #define GLX_RENDERER_DEVICE_ID_MESA 0x8184
84 #define GLX_RENDERER_VERSION_MESA 0x8185
85 #define GLX_RENDERER_ACCELERATED_MESA 0x8186
86 #define GLX_RENDERER_VIDEO_MEMORY_MESA 0x8187
87 #define GLX_RENDERER_UNIFIED_MEMORY_ARCHITECTURE_MESA 0x8188
88 #define GLX_RENDERER_PREFERRED_PROFILE_MESA 0x8189
89 #define GLX_RENDERER_OPENGL_CORE_PROFILE_VERSION_MESA 0x818A
90 #define GLX_RENDERER_OPENGL_COMPATIBILITY_PROFILE_VERSION_MESA 0x818B
91 #define GLX_RENDERER_OPENGL_ES_PROFILE_VERSION_MESA 0x818C
92 #define GLX_RENDERER_OPENGL_ES2_PROFILE_VERSION_MESA 0x818D
93 #define GLX_RENDERER_ID_MESA 0x818E
94 // clang-format on
96 // stuff from egl.h
97 typedef intptr_t EGLAttrib;
98 typedef int EGLBoolean;
99 typedef void* EGLConfig;
100 typedef void* EGLContext;
101 typedef void* EGLDeviceEXT;
102 typedef void* EGLDisplay;
103 typedef unsigned int EGLenum;
104 typedef int EGLint;
105 typedef void* EGLNativeDisplayType;
106 typedef void* EGLSurface;
107 typedef void* (*PFNEGLGETPROCADDRESS)(const char*);
109 #define EGL_NO_CONTEXT nullptr
110 #define EGL_NO_SURFACE nullptr
111 #define EGL_FALSE 0
112 #define EGL_TRUE 1
113 #define EGL_OPENGL_ES2_BIT 0x0004
114 #define EGL_BLUE_SIZE 0x3022
115 #define EGL_GREEN_SIZE 0x3023
116 #define EGL_RED_SIZE 0x3024
117 #define EGL_NONE 0x3038
118 #define EGL_RENDERABLE_TYPE 0x3040
119 #define EGL_VENDOR 0x3053
120 #define EGL_EXTENSIONS 0x3055
121 #define EGL_CONTEXT_MAJOR_VERSION 0x3098
122 #define EGL_OPENGL_ES_API 0x30A0
123 #define EGL_OPENGL_API 0x30A2
124 #define EGL_DEVICE_EXT 0x322C
125 #define EGL_DRM_DEVICE_FILE_EXT 0x3233
126 #define EGL_DRM_RENDER_NODE_FILE_EXT 0x3377
128 // stuff from xf86drm.h
129 #define DRM_NODE_RENDER 2
130 #define DRM_NODE_MAX 3
132 typedef struct _drmPciDeviceInfo {
133 uint16_t vendor_id;
134 uint16_t device_id;
135 uint16_t subvendor_id;
136 uint16_t subdevice_id;
137 uint8_t revision_id;
138 } drmPciDeviceInfo, *drmPciDeviceInfoPtr;
140 typedef struct _drmDevice {
141 char** nodes;
142 int available_nodes;
143 int bustype;
144 union {
145 void* pci;
146 void* usb;
147 void* platform;
148 void* host1x;
149 } businfo;
150 union {
151 drmPciDeviceInfoPtr pci;
152 void* usb;
153 void* platform;
154 void* host1x;
155 } deviceinfo;
156 } drmDevice, *drmDevicePtr;
158 // Open libGL and load needed symbols
159 #if defined(__OpenBSD__) || defined(__NetBSD__)
160 # define LIBGL_FILENAME "libGL.so"
161 # define LIBGLES_FILENAME "libGLESv2.so"
162 # define LIBEGL_FILENAME "libEGL.so"
163 # define LIBDRM_FILENAME "libdrm.so"
164 #else
165 # define LIBGL_FILENAME "libGL.so.1"
166 # define LIBGLES_FILENAME "libGLESv2.so.2"
167 # define LIBEGL_FILENAME "libEGL.so.1"
168 # define LIBDRM_FILENAME "libdrm.so.2"
169 #endif
171 #ifdef MOZ_X11
172 static int x_error_handler(Display*, XErrorEvent* ev) {
173 record_error(
174 "X error, error_code=%d, "
175 "request_code=%d, minor_code=%d",
176 ev->error_code, ev->request_code, ev->minor_code);
177 record_flush();
178 _exit(EXIT_FAILURE);
180 #endif
182 // childgltest is declared inside extern "C" so that the name is not mangled.
183 // The name is used in build/valgrind/x86_64-pc-linux-gnu.sup to suppress
184 // memory leak errors because we run it inside a short lived fork and we don't
185 // care about leaking memory
186 extern "C" {
188 #define PCI_FILL_IDENT 0x0001
189 #define PCI_FILL_CLASS 0x0020
190 #define PCI_BASE_CLASS_DISPLAY 0x03
192 static void get_pci_status() {
193 log("GLX_TEST: get_pci_status start\n");
195 #if !defined(XP_FREEBSD) && !defined(XP_NETBSD) && !defined(XP_OPENBSD) && \
196 !defined(XP_SOLARIS)
197 if (access("/sys/bus/pci/", F_OK) != 0 &&
198 access("/sys/bus/pci_express/", F_OK) != 0) {
199 log("GLX_TEST: get_pci_status failed: cannot access /sys/bus/pci\n");
200 return;
203 void* libpci = dlopen("libpci.so.3", RTLD_LAZY);
204 if (!libpci) {
205 libpci = dlopen("libpci.so", RTLD_LAZY);
207 if (!libpci) {
208 record_warning("libpci missing");
209 return;
211 auto release = mozilla::MakeScopeExit([&] { dlclose(libpci); });
213 typedef struct pci_dev {
214 struct pci_dev* next;
215 uint16_t domain_16;
216 uint8_t bus, dev, func;
217 unsigned int known_fields;
218 uint16_t vendor_id, device_id;
219 uint16_t device_class;
220 } pci_dev;
222 typedef struct pci_access {
223 unsigned int method;
224 int writeable;
225 int buscentric;
226 char* id_file_name;
227 int free_id_name;
228 int numeric_ids;
229 unsigned int id_lookup_mode;
230 int debugging;
231 void* error;
232 void* warning;
233 void* debug;
234 pci_dev* devices;
235 } pci_access;
237 typedef pci_access* (*PCIALLOC)(void);
238 PCIALLOC pci_alloc = cast<PCIALLOC>(dlsym(libpci, "pci_alloc"));
240 typedef void (*PCIINIT)(pci_access*);
241 PCIINIT pci_init = cast<PCIINIT>(dlsym(libpci, "pci_init"));
243 typedef void (*PCICLEANUP)(pci_access*);
244 PCICLEANUP pci_cleanup = cast<PCICLEANUP>(dlsym(libpci, "pci_cleanup"));
246 typedef void (*PCISCANBUS)(pci_access*);
247 PCISCANBUS pci_scan_bus = cast<PCISCANBUS>(dlsym(libpci, "pci_scan_bus"));
249 typedef void (*PCIFILLINFO)(pci_dev*, int);
250 PCIFILLINFO pci_fill_info = cast<PCIFILLINFO>(dlsym(libpci, "pci_fill_info"));
252 if (!pci_alloc || !pci_cleanup || !pci_scan_bus || !pci_fill_info) {
253 dlclose(libpci);
254 record_warning("libpci missing methods");
255 return;
258 pci_access* pacc = pci_alloc();
259 if (!pacc) {
260 record_warning("libpci alloc failed");
261 return;
264 pci_init(pacc);
265 pci_scan_bus(pacc);
267 for (pci_dev* dev = pacc->devices; dev; dev = dev->next) {
268 pci_fill_info(dev, PCI_FILL_IDENT | PCI_FILL_CLASS);
269 if (dev->device_class >> 8 == PCI_BASE_CLASS_DISPLAY && dev->vendor_id &&
270 dev->device_id) {
271 record_value("PCI_VENDOR_ID\n0x%04x\nPCI_DEVICE_ID\n0x%04x\n",
272 dev->vendor_id, dev->device_id);
276 pci_cleanup(pacc);
277 #endif
279 log("GLX_TEST: get_pci_status finished\n");
282 static void set_render_device_path(const char* render_device_path) {
283 record_value("DRM_RENDERDEVICE\n%s\n", render_device_path);
286 static bool device_has_name(const drmDevice* device, const char* name) {
287 for (size_t i = 0; i < DRM_NODE_MAX; i++) {
288 if (!(device->available_nodes & (1 << i))) {
289 continue;
291 if (strcmp(device->nodes[i], name) == 0) {
292 return true;
295 return false;
298 static bool get_render_name(const char* name) {
299 void* libdrm = dlopen(LIBDRM_FILENAME, RTLD_LAZY);
300 if (!libdrm) {
301 record_warning("Failed to open libdrm");
302 return false;
304 auto release = mozilla::MakeScopeExit([&] { dlclose(libdrm); });
306 typedef int (*DRMGETDEVICES2)(uint32_t, drmDevicePtr*, int);
307 DRMGETDEVICES2 drmGetDevices2 =
308 cast<DRMGETDEVICES2>(dlsym(libdrm, "drmGetDevices2"));
310 typedef void (*DRMFREEDEVICE)(drmDevicePtr*);
311 DRMFREEDEVICE drmFreeDevice =
312 cast<DRMFREEDEVICE>(dlsym(libdrm, "drmFreeDevice"));
314 if (!drmGetDevices2 || !drmFreeDevice) {
315 record_warning(
316 "libdrm missing methods for drmGetDevices2 or drmFreeDevice");
317 return false;
320 uint32_t flags = 0;
321 int devices_len = drmGetDevices2(flags, nullptr, 0);
322 if (devices_len < 0) {
323 record_warning("drmGetDevices2 failed");
324 return false;
326 drmDevice** devices = (drmDevice**)calloc(devices_len, sizeof(drmDevice*));
327 if (!devices) {
328 record_warning("Allocation error");
329 return false;
331 devices_len = drmGetDevices2(flags, devices, devices_len);
332 if (devices_len < 0) {
333 free(devices);
334 record_warning("drmGetDevices2 failed");
335 return false;
338 const drmDevice* match = nullptr;
339 for (int i = 0; i < devices_len; i++) {
340 if (device_has_name(devices[i], name)) {
341 match = devices[i];
342 break;
346 // Fallback path for split kms/render devices - if only one drm render node
347 // exists it's most likely the one we're looking for.
348 if (match && !(match->available_nodes & (1 << DRM_NODE_RENDER))) {
349 match = nullptr;
350 for (int i = 0; i < devices_len; i++) {
351 if (devices[i]->available_nodes & (1 << DRM_NODE_RENDER)) {
352 if (!match) {
353 match = devices[i];
354 } else {
355 // more than one candidate found, stop trying.
356 match = nullptr;
357 break;
361 if (match) {
362 record_warning(
363 "DRM render node not clearly detectable. Falling back to using the "
364 "only one that was found.");
365 } else {
366 record_warning("DRM device has no render node");
370 bool result = false;
371 if (!match) {
372 record_warning("Cannot find DRM device");
373 } else {
374 set_render_device_path(match->nodes[DRM_NODE_RENDER]);
375 record_value(
376 "MESA_VENDOR_ID\n0x%04x\n"
377 "MESA_DEVICE_ID\n0x%04x\n",
378 match->deviceinfo.pci->vendor_id, match->deviceinfo.pci->device_id);
379 result = true;
382 for (int i = 0; i < devices_len; i++) {
383 drmFreeDevice(&devices[i]);
385 free(devices);
386 return result;
389 static bool get_egl_gl_status(EGLDisplay dpy,
390 PFNEGLGETPROCADDRESS eglGetProcAddress) {
391 typedef EGLBoolean (*PFNEGLCHOOSECONFIGPROC)(
392 EGLDisplay dpy, EGLint const* attrib_list, EGLConfig* configs,
393 EGLint config_size, EGLint* num_config);
394 PFNEGLCHOOSECONFIGPROC eglChooseConfig =
395 cast<PFNEGLCHOOSECONFIGPROC>(eglGetProcAddress("eglChooseConfig"));
397 typedef EGLBoolean (*PFNEGLBINDAPIPROC)(EGLint api);
398 PFNEGLBINDAPIPROC eglBindAPI =
399 cast<PFNEGLBINDAPIPROC>(eglGetProcAddress("eglBindAPI"));
401 typedef EGLContext (*PFNEGLCREATECONTEXTPROC)(
402 EGLDisplay dpy, EGLConfig config, EGLContext share_context,
403 EGLint const* attrib_list);
404 PFNEGLCREATECONTEXTPROC eglCreateContext =
405 cast<PFNEGLCREATECONTEXTPROC>(eglGetProcAddress("eglCreateContext"));
407 typedef EGLBoolean (*PFNEGLDESTROYCONTEXTPROC)(EGLDisplay dpy,
408 EGLContext ctx);
409 PFNEGLDESTROYCONTEXTPROC eglDestroyContext =
410 cast<PFNEGLDESTROYCONTEXTPROC>(eglGetProcAddress("eglDestroyContext"));
412 typedef EGLBoolean (*PFNEGLMAKECURRENTPROC)(
413 EGLDisplay dpy, EGLSurface draw, EGLSurface read, EGLContext context);
414 PFNEGLMAKECURRENTPROC eglMakeCurrent =
415 cast<PFNEGLMAKECURRENTPROC>(eglGetProcAddress("eglMakeCurrent"));
417 typedef const char* (*PFNEGLQUERYDEVICESTRINGEXTPROC)(EGLDeviceEXT device,
418 EGLint name);
419 PFNEGLQUERYDEVICESTRINGEXTPROC eglQueryDeviceStringEXT =
420 cast<PFNEGLQUERYDEVICESTRINGEXTPROC>(
421 eglGetProcAddress("eglQueryDeviceStringEXT"));
423 typedef EGLBoolean (*PFNEGLQUERYDISPLAYATTRIBEXTPROC)(
424 EGLDisplay dpy, EGLint name, EGLAttrib* value);
425 PFNEGLQUERYDISPLAYATTRIBEXTPROC eglQueryDisplayAttribEXT =
426 cast<PFNEGLQUERYDISPLAYATTRIBEXTPROC>(
427 eglGetProcAddress("eglQueryDisplayAttribEXT"));
429 log("GLX_TEST: get_egl_gl_status start\n");
431 if (!eglChooseConfig || !eglCreateContext || !eglDestroyContext ||
432 !eglMakeCurrent || !eglQueryDeviceStringEXT) {
433 record_warning("libEGL missing methods for GL test");
434 return false;
437 typedef GLubyte* (*PFNGLGETSTRING)(GLenum);
438 PFNGLGETSTRING glGetString =
439 cast<PFNGLGETSTRING>(eglGetProcAddress("glGetString"));
441 #if defined(__arm__) || defined(__aarch64__)
442 bool useGles = true;
443 #else
444 bool useGles = false;
445 #endif
447 std::vector<EGLint> attribs;
448 attribs.push_back(EGL_RED_SIZE);
449 attribs.push_back(8);
450 attribs.push_back(EGL_GREEN_SIZE);
451 attribs.push_back(8);
452 attribs.push_back(EGL_BLUE_SIZE);
453 attribs.push_back(8);
454 if (useGles) {
455 attribs.push_back(EGL_RENDERABLE_TYPE);
456 attribs.push_back(EGL_OPENGL_ES2_BIT);
458 attribs.push_back(EGL_NONE);
460 EGLConfig config;
461 EGLint num_config;
462 if (eglChooseConfig(dpy, attribs.data(), &config, 1, &num_config) ==
463 EGL_FALSE) {
464 record_warning("eglChooseConfig returned an error");
465 return false;
468 EGLenum api = useGles ? EGL_OPENGL_ES_API : EGL_OPENGL_API;
469 if (eglBindAPI(api) == EGL_FALSE) {
470 record_warning("eglBindAPI returned an error");
471 return false;
474 EGLint ctx_attrs[] = {EGL_CONTEXT_MAJOR_VERSION, 3, EGL_NONE};
475 EGLContext ectx = eglCreateContext(dpy, config, EGL_NO_CONTEXT, ctx_attrs);
476 if (!ectx) {
477 EGLint ctx_attrs_fallback[] = {EGL_CONTEXT_MAJOR_VERSION, 2, EGL_NONE};
478 ectx = eglCreateContext(dpy, config, EGL_NO_CONTEXT, ctx_attrs_fallback);
479 if (!ectx) {
480 record_warning("eglCreateContext returned an error");
481 return false;
485 if (eglMakeCurrent(dpy, EGL_NO_SURFACE, EGL_NO_SURFACE, ectx) == EGL_FALSE) {
486 eglDestroyContext(dpy, ectx);
487 record_warning("eglMakeCurrent returned an error");
488 return false;
490 eglDestroyContext(dpy, ectx);
492 // Implementations disagree about whether eglGetProcAddress or dlsym
493 // should be used for getting functions from the actual API, see
494 // https://github.com/anholt/libepoxy/commit/14f24485e33816139398d1bd170d617703473738
495 void* libgl = nullptr;
496 if (!glGetString) {
497 libgl = dlopen(LIBGL_FILENAME, RTLD_LAZY);
498 if (!libgl) {
499 libgl = dlopen(LIBGLES_FILENAME, RTLD_LAZY);
500 if (!libgl) {
501 record_warning(LIBGL_FILENAME " and " LIBGLES_FILENAME " missing");
502 return false;
506 glGetString = cast<PFNGLGETSTRING>(dlsym(libgl, "glGetString"));
507 if (!glGetString) {
508 dlclose(libgl);
509 record_warning("libEGL, libGL and libGLESv2 are missing glGetString");
510 return false;
513 auto release = mozilla::MakeScopeExit([&] {
514 if (libgl) {
515 dlclose(libgl);
519 const GLubyte* versionString = glGetString(GL_VERSION);
520 const GLubyte* vendorString = glGetString(GL_VENDOR);
521 const GLubyte* rendererString = glGetString(GL_RENDERER);
523 if (versionString && vendorString && rendererString) {
524 record_value("VENDOR\n%s\nRENDERER\n%s\nVERSION\n%s\nTFP\nTRUE\n",
525 vendorString, rendererString, versionString);
526 } else {
527 record_warning("EGL glGetString returned null");
528 return false;
531 EGLDeviceEXT device;
532 if (eglQueryDisplayAttribEXT(dpy, EGL_DEVICE_EXT, (EGLAttrib*)&device) ==
533 EGL_TRUE) {
534 const char* deviceExtensions =
535 eglQueryDeviceStringEXT(device, EGL_EXTENSIONS);
536 if (deviceExtensions &&
537 strstr(deviceExtensions, "EGL_MESA_device_software")) {
538 record_value("MESA_ACCELERATED\nFALSE\n");
539 } else {
540 const char* deviceString =
541 eglQueryDeviceStringEXT(device, EGL_DRM_DEVICE_FILE_EXT);
542 if (!deviceString || !get_render_name(deviceString)) {
543 const char* renderNodeString =
544 eglQueryDeviceStringEXT(device, EGL_DRM_RENDER_NODE_FILE_EXT);
545 if (renderNodeString) {
546 set_render_device_path(renderNodeString);
552 log("GLX_TEST: get_egl_gl_status finished\n");
553 return true;
556 static bool get_egl_status(EGLNativeDisplayType native_dpy) {
557 log("GLX_TEST: get_egl_status start\n");
559 EGLDisplay dpy = nullptr;
561 typedef EGLBoolean (*PFNEGLTERMINATEPROC)(EGLDisplay dpy);
562 PFNEGLTERMINATEPROC eglTerminate = nullptr;
564 void* libegl = dlopen(LIBEGL_FILENAME, RTLD_LAZY);
565 if (!libegl) {
566 record_warning("libEGL missing");
567 return false;
569 auto release = mozilla::MakeScopeExit([&] {
570 if (dpy) {
571 eglTerminate(dpy);
573 // Unload libegl here causes ASAN/MemLeaks failures on Linux
574 // as libegl may not be meant to be unloaded runtime.
575 // See 1304156 for reference.
576 // dlclose(libegl);
579 PFNEGLGETPROCADDRESS eglGetProcAddress =
580 cast<PFNEGLGETPROCADDRESS>(dlsym(libegl, "eglGetProcAddress"));
582 if (!eglGetProcAddress) {
583 record_warning("no eglGetProcAddress");
584 return false;
587 typedef EGLDisplay (*PFNEGLGETDISPLAYPROC)(void* native_display);
588 PFNEGLGETDISPLAYPROC eglGetDisplay =
589 cast<PFNEGLGETDISPLAYPROC>(eglGetProcAddress("eglGetDisplay"));
591 typedef EGLBoolean (*PFNEGLINITIALIZEPROC)(EGLDisplay dpy, EGLint* major,
592 EGLint* minor);
593 PFNEGLINITIALIZEPROC eglInitialize =
594 cast<PFNEGLINITIALIZEPROC>(eglGetProcAddress("eglInitialize"));
595 eglTerminate = cast<PFNEGLTERMINATEPROC>(eglGetProcAddress("eglTerminate"));
597 if (!eglGetDisplay || !eglInitialize || !eglTerminate) {
598 record_warning("libEGL missing methods");
599 return false;
602 dpy = eglGetDisplay(native_dpy);
603 if (!dpy) {
604 record_warning("libEGL no display");
605 return false;
608 EGLint major, minor;
609 if (!eglInitialize(dpy, &major, &minor)) {
610 record_warning("libEGL initialize failed");
611 return false;
614 typedef const char* (*PFNEGLGETDISPLAYDRIVERNAMEPROC)(EGLDisplay dpy);
615 PFNEGLGETDISPLAYDRIVERNAMEPROC eglGetDisplayDriverName =
616 cast<PFNEGLGETDISPLAYDRIVERNAMEPROC>(
617 eglGetProcAddress("eglGetDisplayDriverName"));
618 if (eglGetDisplayDriverName) {
619 const char* driDriver = eglGetDisplayDriverName(dpy);
620 if (driDriver) {
621 record_value("DRI_DRIVER\n%s\n", driDriver);
625 bool ret = get_egl_gl_status(dpy, eglGetProcAddress);
626 log("GLX_TEST: get_egl_status finished with return: %d\n", ret);
628 return ret;
631 #ifdef MOZ_X11
632 static void get_xrandr_info(Display* dpy) {
633 log("GLX_TEST: get_xrandr_info start\n");
635 // When running on remote X11 the xrandr version may be stuck on an ancient
636 // version. There are still setups using remote X11 out there, so make sure we
637 // don't crash.
638 int eventBase, errorBase, major, minor;
639 if (!XRRQueryExtension(dpy, &eventBase, &errorBase) ||
640 !XRRQueryVersion(dpy, &major, &minor) ||
641 !(major > 1 || (major == 1 && minor >= 4))) {
642 log("GLX_TEST: get_xrandr_info failed, old version.\n");
643 return;
646 Window root = RootWindow(dpy, DefaultScreen(dpy));
647 XRRProviderResources* pr = XRRGetProviderResources(dpy, root);
648 if (!pr) {
649 log("GLX_TEST: XRRGetProviderResources failed.\n");
650 return;
652 XRRScreenResources* res = XRRGetScreenResourcesCurrent(dpy, root);
653 if (!res) {
654 XRRFreeProviderResources(pr);
655 log("GLX_TEST: XRRGetScreenResourcesCurrent failed.\n");
656 return;
658 if (pr->nproviders != 0) {
659 record_value("DDX_DRIVER\n");
660 for (int i = 0; i < pr->nproviders; i++) {
661 XRRProviderInfo* info = XRRGetProviderInfo(dpy, res, pr->providers[i]);
662 if (info) {
663 record_value("%s%s", info->name, i == pr->nproviders - 1 ? ";\n" : ";");
664 XRRFreeProviderInfo(info);
668 XRRFreeScreenResources(res);
669 XRRFreeProviderResources(pr);
671 log("GLX_TEST: get_xrandr_info finished\n");
674 void glxtest() {
675 log("GLX_TEST: glxtest start\n");
677 Display* dpy = nullptr;
678 void* libgl = dlopen(LIBGL_FILENAME, RTLD_LAZY);
679 if (!libgl) {
680 record_error(LIBGL_FILENAME " missing");
681 return;
683 auto release = mozilla::MakeScopeExit([&] {
684 if (dpy) {
685 # ifdef MOZ_ASAN
686 XCloseDisplay(dpy);
687 # else
688 // This XSync call wanted to be instead:
689 // XCloseDisplay(dpy);
690 // but this can cause 1-minute stalls on certain setups using Nouveau, see
691 // bug 973192
692 XSync(dpy, False);
693 # endif
695 dlclose(libgl);
698 typedef void* (*PFNGLXGETPROCADDRESS)(const char*);
699 PFNGLXGETPROCADDRESS glXGetProcAddress =
700 cast<PFNGLXGETPROCADDRESS>(dlsym(libgl, "glXGetProcAddress"));
702 if (!glXGetProcAddress) {
703 record_error("no glXGetProcAddress");
704 return;
707 typedef GLXFBConfig* (*PFNGLXQUERYEXTENSION)(Display*, int*, int*);
708 PFNGLXQUERYEXTENSION glXQueryExtension =
709 cast<PFNGLXQUERYEXTENSION>(glXGetProcAddress("glXQueryExtension"));
711 typedef GLXFBConfig* (*PFNGLXQUERYVERSION)(Display*, int*, int*);
712 PFNGLXQUERYVERSION glXQueryVersion =
713 cast<PFNGLXQUERYVERSION>(dlsym(libgl, "glXQueryVersion"));
715 typedef XVisualInfo* (*PFNGLXCHOOSEVISUAL)(Display*, int, int*);
716 PFNGLXCHOOSEVISUAL glXChooseVisual =
717 cast<PFNGLXCHOOSEVISUAL>(glXGetProcAddress("glXChooseVisual"));
719 typedef GLXContext (*PFNGLXCREATECONTEXT)(Display*, XVisualInfo*, GLXContext,
720 Bool);
721 PFNGLXCREATECONTEXT glXCreateContext =
722 cast<PFNGLXCREATECONTEXT>(glXGetProcAddress("glXCreateContext"));
724 typedef Bool (*PFNGLXMAKECURRENT)(Display*, GLXDrawable, GLXContext);
725 PFNGLXMAKECURRENT glXMakeCurrent =
726 cast<PFNGLXMAKECURRENT>(glXGetProcAddress("glXMakeCurrent"));
728 typedef void (*PFNGLXDESTROYCONTEXT)(Display*, GLXContext);
729 PFNGLXDESTROYCONTEXT glXDestroyContext =
730 cast<PFNGLXDESTROYCONTEXT>(glXGetProcAddress("glXDestroyContext"));
732 typedef GLubyte* (*PFNGLGETSTRING)(GLenum);
733 PFNGLGETSTRING glGetString =
734 cast<PFNGLGETSTRING>(glXGetProcAddress("glGetString"));
736 if (!glXQueryExtension || !glXQueryVersion || !glXChooseVisual ||
737 !glXCreateContext || !glXMakeCurrent || !glXDestroyContext ||
738 !glGetString) {
739 record_error(LIBGL_FILENAME " missing methods");
740 return;
743 ///// Open a connection to the X server /////
744 dpy = XOpenDisplay(nullptr);
745 if (!dpy) {
746 record_error("Unable to open a connection to the X server");
747 return;
750 ///// Check that the GLX extension is present /////
751 if (!glXQueryExtension(dpy, nullptr, nullptr)) {
752 record_error("GLX extension missing");
753 return;
756 XSetErrorHandler(x_error_handler);
758 ///// Get a visual /////
759 int attribs[] = {GLX_RGBA, GLX_RED_SIZE, 1, GLX_GREEN_SIZE,
760 1, GLX_BLUE_SIZE, 1, None};
761 XVisualInfo* vInfo = glXChooseVisual(dpy, DefaultScreen(dpy), attribs);
762 if (!vInfo) {
763 int attribs2[] = {GLX_RGBA, GLX_RED_SIZE, 1, GLX_GREEN_SIZE,
764 1, GLX_BLUE_SIZE, 1, GLX_DOUBLEBUFFER,
765 None};
766 vInfo = glXChooseVisual(dpy, DefaultScreen(dpy), attribs2);
767 if (!vInfo) {
768 record_error("No visuals found");
769 return;
773 // using a X11 Window instead of a GLXPixmap does not crash
774 // fglrx in indirect rendering. bug 680644
775 Window window;
776 XSetWindowAttributes swa;
777 swa.colormap = XCreateColormap(dpy, RootWindow(dpy, vInfo->screen),
778 vInfo->visual, AllocNone);
780 swa.border_pixel = 0;
781 window = XCreateWindow(dpy, RootWindow(dpy, vInfo->screen), 0, 0, 16, 16, 0,
782 vInfo->depth, InputOutput, vInfo->visual,
783 CWBorderPixel | CWColormap, &swa);
785 ///// Get a GL context and make it current //////
786 GLXContext context = glXCreateContext(dpy, vInfo, nullptr, True);
787 glXMakeCurrent(dpy, window, context);
789 ///// Look for this symbol to determine texture_from_pixmap support /////
790 void* glXBindTexImageEXT = glXGetProcAddress("glXBindTexImageEXT");
792 ///// Get GL vendor/renderer/versions strings /////
793 const GLubyte* versionString = glGetString(GL_VERSION);
794 const GLubyte* vendorString = glGetString(GL_VENDOR);
795 const GLubyte* rendererString = glGetString(GL_RENDERER);
797 if (versionString && vendorString && rendererString) {
798 record_value("VENDOR\n%s\nRENDERER\n%s\nVERSION\n%s\nTFP\n%s\n",
799 vendorString, rendererString, versionString,
800 glXBindTexImageEXT ? "TRUE" : "FALSE");
801 } else {
802 record_error("glGetString returned null");
805 // If GLX_MESA_query_renderer is available, populate additional data.
806 typedef Bool (*PFNGLXQUERYCURRENTRENDERERINTEGERMESAPROC)(
807 int attribute, unsigned int* value);
808 PFNGLXQUERYCURRENTRENDERERINTEGERMESAPROC
809 glXQueryCurrentRendererIntegerMESAProc =
810 cast<PFNGLXQUERYCURRENTRENDERERINTEGERMESAPROC>(
811 glXGetProcAddress("glXQueryCurrentRendererIntegerMESA"));
812 if (glXQueryCurrentRendererIntegerMESAProc) {
813 unsigned int vendorId, deviceId, accelerated, videoMemoryMB;
814 glXQueryCurrentRendererIntegerMESAProc(GLX_RENDERER_VENDOR_ID_MESA,
815 &vendorId);
816 glXQueryCurrentRendererIntegerMESAProc(GLX_RENDERER_DEVICE_ID_MESA,
817 &deviceId);
818 glXQueryCurrentRendererIntegerMESAProc(GLX_RENDERER_ACCELERATED_MESA,
819 &accelerated);
820 glXQueryCurrentRendererIntegerMESAProc(GLX_RENDERER_VIDEO_MEMORY_MESA,
821 &videoMemoryMB);
823 // Truncate IDs to 4 digits- that's all PCI IDs are.
824 vendorId &= 0xFFFF;
825 deviceId &= 0xFFFF;
827 record_value(
828 "MESA_VENDOR_ID\n0x%04x\n"
829 "MESA_DEVICE_ID\n0x%04x\n"
830 "MESA_ACCELERATED\n%s\n"
831 "MESA_VRAM\n%dMB\n",
832 vendorId, deviceId, accelerated ? "TRUE" : "FALSE", videoMemoryMB);
835 // From Mesa's GL/internal/dri_interface.h, to be used by DRI clients.
836 typedef const char* (*PFNGLXGETSCREENDRIVERPROC)(Display* dpy, int scrNum);
837 PFNGLXGETSCREENDRIVERPROC glXGetScreenDriverProc =
838 cast<PFNGLXGETSCREENDRIVERPROC>(glXGetProcAddress("glXGetScreenDriver"));
839 if (glXGetScreenDriverProc) {
840 const char* driDriver = glXGetScreenDriverProc(dpy, DefaultScreen(dpy));
841 if (driDriver) {
842 record_value("DRI_DRIVER\n%s\n", driDriver);
846 // Get monitor and DDX driver information
847 get_xrandr_info(dpy);
849 ///// Clean up. Indeed, the parent process might fail to kill us (e.g. if it
850 ///// doesn't need to check GL info) so we might be staying alive for longer
851 ///// than expected, so it's important to consume as little memory as
852 ///// possible. Also we want to check that we're able to do that too without
853 ///// generating X errors.
854 glXMakeCurrent(dpy, None,
855 nullptr); // must release the GL context before destroying it
856 glXDestroyContext(dpy, context);
857 XDestroyWindow(dpy, window);
858 XFreeColormap(dpy, swa.colormap);
859 XFree(vInfo);
861 record_value("TEST_TYPE\nGLX\n");
862 log("GLX_TEST: glxtest finished\n");
865 bool x11_egltest() {
866 log("GLX_TEST: x11_egltest start\n");
868 Display* dpy = XOpenDisplay(nullptr);
869 if (!dpy) {
870 log("GLX_TEST: XOpenDisplay failed.\n");
871 return false;
873 # ifdef MOZ_ASAN
874 auto release = mozilla::MakeScopeExit([&] {
875 // Bug 1715245: Closing the display connection here crashes on NV prop.
876 // drivers. Just leave it open, the process will exit shortly after anyway.
877 XCloseDisplay(dpy);
879 # endif
881 XSetErrorHandler(x_error_handler);
883 if (!get_egl_status(dpy)) {
884 return false;
887 // Bug 1667621: 30bit "Deep Color" is broken on EGL on Mesa (as of 2021/10).
888 // Disable all non-standard depths for the initial EGL roleout.
889 int screenCount = ScreenCount(dpy);
890 for (int idx = 0; idx < screenCount; idx++) {
891 int depth = DefaultDepth(dpy, idx);
892 if (depth != 24) {
893 log("GLX_TEST: DefaultDepth() is %d, expected to be 24. See Bug "
894 "1667621.\n",
895 depth);
896 return false;
900 // Get monitor and DDX driver information
901 get_xrandr_info(dpy);
903 record_value("TEST_TYPE\nEGL\n");
905 log("GLX_TEST: x11_egltest finished\n");
906 return true;
908 #endif
910 #ifdef MOZ_WAYLAND
911 void wayland_egltest() {
912 log("GLX_TEST: wayland_egltest start\n");
914 static auto sWlDisplayConnect = (struct wl_display * (*)(const char*))
915 dlsym(RTLD_DEFAULT, "wl_display_connect");
916 static auto sWlDisplayRoundtrip =
917 (int (*)(struct wl_display*))dlsym(RTLD_DEFAULT, "wl_display_roundtrip");
918 static auto sWlDisplayDisconnect = (void (*)(struct wl_display*))dlsym(
919 RTLD_DEFAULT, "wl_display_disconnect");
921 if (!sWlDisplayConnect || !sWlDisplayRoundtrip || !sWlDisplayDisconnect) {
922 record_error("Missing Wayland libraries");
923 return;
926 // NOTE: returns false to fall back to X11 when the Wayland socket doesn't
927 // exist but fails with record_error if something actually went wrong
928 struct wl_display* dpy = sWlDisplayConnect(nullptr);
929 if (!dpy) {
930 record_error("Could not connect to wayland display, WAYLAND_DISPLAY=%s",
931 getenv("WAYLAND_DISPLAY"));
932 return;
935 if (!get_egl_status((EGLNativeDisplayType)dpy)) {
936 record_error("EGL test failed");
939 // This is enough to crash some broken NVIDIA prime + Wayland setups, see
940 // https://github.com/NVIDIA/egl-wayland/issues/41 and bug 1768260.
941 sWlDisplayRoundtrip(dpy);
943 sWlDisplayDisconnect(dpy);
944 record_value("TEST_TYPE\nEGL\n");
945 log("GLX_TEST: wayland_egltest finished\n");
947 #endif
949 int childgltest(bool aWayland) {
950 log("GLX_TEST: childgltest start\n");
952 // Get a list of all GPUs from the PCI bus.
953 get_pci_status();
955 #ifdef MOZ_WAYLAND
956 if (aWayland) {
957 wayland_egltest();
959 #endif
960 #ifdef MOZ_X11
961 if (!aWayland) {
962 // TODO: --display command line argument is not properly handled
963 if (!x11_egltest()) {
964 glxtest();
967 #endif
968 // Finally write buffered data to the pipe.
969 record_flush();
971 log("GLX_TEST: childgltest finished\n");
972 return EXIT_SUCCESS;
975 } // extern "C"
977 static void PrintUsage() {
978 printf(
979 "Firefox OpenGL probe utility\n"
980 "\n"
981 "usage: glxtest [options]\n"
982 "\n"
983 "Options:\n"
984 "\n"
985 " -h --help show this message\n"
986 " -f --fd num where to print output, default it stdout\n"
987 " -w --wayland probe OpenGL/EGL on Wayland (default is "
988 "X11)\n"
989 "\n");
992 int main(int argc, char** argv) {
993 struct option longOptions[] = {{"help", no_argument, nullptr, 'h'},
994 {"fd", required_argument, nullptr, 'f'},
995 {"wayland", no_argument, nullptr, 'w'},
996 {nullptr, 0, nullptr, 0}};
997 const char* shortOptions = "hf:w";
998 int c;
999 bool wayland = false;
1000 while ((c = getopt_long(argc, argv, shortOptions, longOptions, nullptr)) !=
1001 -1) {
1002 switch (c) {
1003 case 'w':
1004 wayland = true;
1005 break;
1006 case 'f':
1007 output_pipe = atoi(optarg);
1008 break;
1009 case 'h':
1010 #ifdef MOZ_WAYLAND
1011 // Dummy call to mozgtk to prevent the linker from removing
1012 // the dependency with --as-needed.
1013 // see toolkit/library/moz.build for details.
1014 gdk_display_get_default();
1015 #endif
1016 PrintUsage();
1017 return 0;
1018 default:
1019 break;
1022 if (getenv("MOZ_AVOID_OPENGL_ALTOGETHER")) {
1023 const char* msg = "ERROR\nMOZ_AVOID_OPENGL_ALTOGETHER envvar set";
1024 MOZ_UNUSED(write(output_pipe, msg, strlen(msg)));
1025 exit(EXIT_FAILURE);
1027 const char* env = getenv("MOZ_GFX_DEBUG");
1028 enable_logging = env && *env == '1';
1029 if (!enable_logging) {
1030 close_logging();
1032 #if defined(MOZ_ASAN) || defined(FUZZING)
1033 // If handle_segv=1 (default), then glxtest crash will print a sanitizer
1034 // report which can confuse the harness in fuzzing automation.
1035 signal(SIGSEGV, SIG_DFL);
1036 #endif
1037 return childgltest(wayland);