1/**************************************************************************/
2/* detect_prime_x11.cpp */
3/**************************************************************************/
4/* This file is part of: */
5/* GODOT ENGINE */
6/* https://godotengine.org */
7/**************************************************************************/
8/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
9/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
10/* */
11/* Permission is hereby granted, free of charge, to any person obtaining */
12/* a copy of this software and associated documentation files (the */
13/* "Software"), to deal in the Software without restriction, including */
14/* without limitation the rights to use, copy, modify, merge, publish, */
15/* distribute, sublicense, and/or sell copies of the Software, and to */
16/* permit persons to whom the Software is furnished to do so, subject to */
17/* the following conditions: */
18/* */
19/* The above copyright notice and this permission notice shall be */
20/* included in all copies or substantial portions of the Software. */
21/* */
22/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
23/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
24/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
25/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
26/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
27/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
28/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
29/**************************************************************************/
30
31#if defined(X11_ENABLED) && defined(GLES3_ENABLED)
32
33#include "detect_prime_x11.h"
34
35#include "core/string/print_string.h"
36#include "core/string/ustring.h"
37
38#include "thirdparty/glad/glad/gl.h"
39#include "thirdparty/glad/glad/glx.h"
40
41#ifdef SOWRAP_ENABLED
42#include "x11/dynwrappers/xlib-so_wrap.h"
43#else
44#include <X11/XKBlib.h>
45#include <X11/Xlib.h>
46#include <X11/Xutil.h>
47#endif
48
49#include <stdlib.h>
50#include <string.h>
51#include <sys/types.h>
52#include <sys/wait.h>
53#include <unistd.h>
54
55#define GLX_CONTEXT_MAJOR_VERSION_ARB 0x2091
56#define GLX_CONTEXT_MINOR_VERSION_ARB 0x2092
57
58typedef GLXContext (*GLXCREATECONTEXTATTRIBSARBPROC)(Display *, GLXFBConfig, GLXContext, Bool, const int *);
59
60// To prevent shadowing warnings
61#undef glGetString
62
63struct vendor {
64 const char *glxvendor = nullptr;
65 int priority = 0;
66};
67
68vendor vendormap[] = {
69 { "Advanced Micro Devices, Inc.", 30 },
70 { "AMD", 30 },
71 { "NVIDIA Corporation", 30 },
72 { "X.Org", 30 },
73 { "Intel Open Source Technology Center", 20 },
74 { "Intel", 20 },
75 { "nouveau", 10 },
76 { "Mesa Project", 0 },
77 { nullptr, 0 }
78};
79
80// Runs inside a child. Exiting will not quit the engine.
81void create_context() {
82 Display *x11_display = XOpenDisplay(nullptr);
83 Window x11_window;
84 GLXContext glx_context;
85
86 static int visual_attribs[] = {
87 GLX_RENDER_TYPE, GLX_RGBA_BIT,
88 GLX_DRAWABLE_TYPE, GLX_WINDOW_BIT,
89 GLX_DOUBLEBUFFER, true,
90 GLX_RED_SIZE, 1,
91 GLX_GREEN_SIZE, 1,
92 GLX_BLUE_SIZE, 1,
93 GLX_DEPTH_SIZE, 24,
94 None
95 };
96
97 if (gladLoaderLoadGLX(x11_display, XScreenNumberOfScreen(XDefaultScreenOfDisplay(x11_display))) == 0) {
98 print_verbose("Unable to load GLX, GPU detection skipped.");
99 quick_exit(1);
100 }
101 int fbcount;
102 GLXFBConfig fbconfig = nullptr;
103 XVisualInfo *vi = nullptr;
104
105 XSetWindowAttributes swa;
106 swa.event_mask = StructureNotifyMask;
107 swa.border_pixel = 0;
108 unsigned long valuemask = CWBorderPixel | CWColormap | CWEventMask;
109
110 GLXFBConfig *fbc = glXChooseFBConfig(x11_display, DefaultScreen(x11_display), visual_attribs, &fbcount);
111 if (!fbc) {
112 quick_exit(1);
113 }
114
115 vi = glXGetVisualFromFBConfig(x11_display, fbc[0]);
116
117 fbconfig = fbc[0];
118
119 static int context_attribs[] = {
120 GLX_CONTEXT_MAJOR_VERSION_ARB, 3,
121 GLX_CONTEXT_MINOR_VERSION_ARB, 3,
122 GLX_CONTEXT_PROFILE_MASK_ARB, GLX_CONTEXT_CORE_PROFILE_BIT_ARB,
123 GLX_CONTEXT_FLAGS_ARB, GLX_CONTEXT_FORWARD_COMPATIBLE_BIT_ARB,
124 None
125 };
126
127 glx_context = glXCreateContextAttribsARB(x11_display, fbconfig, nullptr, true, context_attribs);
128
129 swa.colormap = XCreateColormap(x11_display, RootWindow(x11_display, vi->screen), vi->visual, AllocNone);
130 x11_window = XCreateWindow(x11_display, RootWindow(x11_display, vi->screen), 0, 0, 10, 10, 0, vi->depth, InputOutput, vi->visual, valuemask, &swa);
131
132 if (!x11_window) {
133 quick_exit(1);
134 }
135
136 glXMakeCurrent(x11_display, x11_window, glx_context);
137 XFree(vi);
138}
139
140int detect_prime() {
141 pid_t p;
142 int priorities[2] = {};
143 String vendors[2];
144 String renderers[2];
145
146 vendors[0] = "Unknown";
147 vendors[1] = "Unknown";
148 renderers[0] = "Unknown";
149 renderers[1] = "Unknown";
150
151 for (int i = 0; i < 2; ++i) {
152 int fdset[2];
153
154 if (pipe(fdset) == -1) {
155 print_verbose("Failed to pipe(), using default GPU");
156 return 0;
157 }
158
159 // Fork so the driver initialization can crash without taking down the engine.
160 p = fork();
161
162 if (p > 0) {
163 // Main thread
164
165 int stat_loc = 0;
166 char string[201];
167 string[200] = '\0';
168
169 close(fdset[1]);
170
171 waitpid(p, &stat_loc, 0);
172
173 if (!stat_loc) {
174 // No need to do anything complicated here. Anything less than
175 // PIPE_BUF will be delivered in one read() call.
176 // Leave it 'Unknown' otherwise.
177 if (read(fdset[0], string, sizeof(string) - 1) > 0) {
178 vendors[i] = string;
179 renderers[i] = string + strlen(string) + 1;
180 }
181 }
182
183 close(fdset[0]);
184
185 } else {
186 // In child, exit() here will not quit the engine.
187
188 // Prevent false leak reports as we will not be properly
189 // cleaning up these processes, and fork() makes a copy
190 // of all globals.
191 CoreGlobals::leak_reporting_enabled = false;
192
193 char string[201];
194
195 close(fdset[0]);
196
197 if (i) {
198 setenv("DRI_PRIME", "1", 1);
199 }
200
201 create_context();
202
203 PFNGLGETSTRINGPROC glGetString = (PFNGLGETSTRINGPROC)glXGetProcAddressARB((GLubyte *)"glGetString");
204 if (!glGetString) {
205 print_verbose("Unable to get glGetString, GPU detection skipped.");
206 quick_exit(1);
207 }
208
209 const char *vendor = (const char *)glGetString(GL_VENDOR);
210 const char *renderer = (const char *)glGetString(GL_RENDERER);
211
212 unsigned int vendor_len = strlen(vendor) + 1;
213 unsigned int renderer_len = strlen(renderer) + 1;
214
215 if (vendor_len + renderer_len >= sizeof(string)) {
216 renderer_len = 200 - vendor_len;
217 }
218
219 memcpy(&string, vendor, vendor_len);
220 memcpy(&string[vendor_len], renderer, renderer_len);
221
222 if (write(fdset[1], string, vendor_len + renderer_len) == -1) {
223 print_verbose("Couldn't write vendor/renderer string.");
224 }
225 close(fdset[1]);
226
227 // The function quick_exit() is used because exit() will call destructors on static objects copied by fork().
228 // These objects will be freed anyway when the process finishes execution.
229 quick_exit(0);
230 }
231 }
232
233 int preferred = 0;
234 int priority = 0;
235
236 if (vendors[0] == vendors[1]) {
237 print_verbose("Only one GPU found, using default.");
238 return 0;
239 }
240
241 for (int i = 1; i >= 0; --i) {
242 vendor *v = vendormap;
243 while (v->glxvendor) {
244 if (v->glxvendor == vendors[i]) {
245 priorities[i] = v->priority;
246
247 if (v->priority >= priority) {
248 priority = v->priority;
249 preferred = i;
250 }
251 }
252 ++v;
253 }
254 }
255
256 print_verbose("Found renderers:");
257 for (int i = 0; i < 2; ++i) {
258 print_verbose("Renderer " + itos(i) + ": " + renderers[i] + " with priority: " + itos(priorities[i]));
259 }
260
261 print_verbose("Using renderer: " + renderers[preferred]);
262 return preferred;
263}
264
265#endif // X11_ENABLED && GLES3_ENABLED
266