ohmu
·
2026-10-15
1#include "gpu.h"
2#include "common/debug.h"
3#include "detection/vulkan/vulkan.h"
4#include "detection/opencl/opencl.h"
5#include "detection/opengl/opengl.h"
6#include "modules/opengl/opengl.h"
7
8const char* FF_GPU_VENDOR_NAME_APPLE = "Apple";
9const char* FF_GPU_VENDOR_NAME_AMD = "AMD";
10const char* FF_GPU_VENDOR_NAME_INTEL = "Intel";
11const char* FF_GPU_VENDOR_NAME_NVIDIA = "NVIDIA";
12const char* FF_GPU_VENDOR_NAME_MTHREADS = "Moore Threads";
13const char* FF_GPU_VENDOR_NAME_QUALCOMM = "Qualcomm";
14const char* FF_GPU_VENDOR_NAME_MTK = "MTK";
15const char* FF_GPU_VENDOR_NAME_VMWARE = "VMware";
16const char* FF_GPU_VENDOR_NAME_PARALLELS = "Parallels";
17const char* FF_GPU_VENDOR_NAME_MICROSOFT = "Microsoft";
18const char* FF_GPU_VENDOR_NAME_REDHAT = "RedHat";
19const char* FF_GPU_VENDOR_NAME_ORACLE = "Oracle";
20const char* FF_GPU_VENDOR_NAME_BROADCOM = "Broadcom";
21const char* FF_GPU_VENDOR_NAME_LOONGSON = "Loongson";
22const char* FF_GPU_VENDOR_NAME_JINGJIA_MICRO = "Jingjia Micro";
23const char* FF_GPU_VENDOR_NAME_HUAWEI = "Huawei";
24const char* FF_GPU_VENDOR_NAME_ZHAOXIN = "Zhaoxin";
25const char* FF_GPU_VENDOR_NAME_QEMU = "QEMU";
26
27const char* ffGPUGetVendorString(unsigned vendorId) {
28 // https://devicehunt.com/all-pci-vendors
29 switch (vendorId) {
30 case 0x106b:
31 return FF_GPU_VENDOR_NAME_APPLE;
32 case 0x1002:
33 case 0x1022:
34 case 0x1dd8:
35 return FF_GPU_VENDOR_NAME_AMD;
36 case 0x8086:
37 case 0x8087:
38 case 0x03e7:
39 return FF_GPU_VENDOR_NAME_INTEL;
40 case 0x0955:
41 case 0x10de:
42 case 0x12d2:
43 return FF_GPU_VENDOR_NAME_NVIDIA;
44 case 0x1ed5:
45 return FF_GPU_VENDOR_NAME_MTHREADS;
46 case 0x17cb:
47 case 0x5143:
48 return FF_GPU_VENDOR_NAME_QUALCOMM;
49 case 0x14c3:
50 return FF_GPU_VENDOR_NAME_MTK;
51 case 0x15ad:
52 return FF_GPU_VENDOR_NAME_VMWARE;
53 case 0x1af4:
54 return FF_GPU_VENDOR_NAME_REDHAT;
55 case 0x1ab8:
56 case 0x05404c42: // PD
57 return FF_GPU_VENDOR_NAME_PARALLELS;
58 case 0x1414:
59 return FF_GPU_VENDOR_NAME_MICROSOFT;
60 case 0x108e:
61 return FF_GPU_VENDOR_NAME_ORACLE;
62 case 0x182f:
63 case 0x14e4:
64 return FF_GPU_VENDOR_NAME_BROADCOM;
65 case 0x0014:
66 return FF_GPU_VENDOR_NAME_LOONGSON;
67 case 0x0731:
68 return FF_GPU_VENDOR_NAME_JINGJIA_MICRO;
69 case 0x19e5:
70 return FF_GPU_VENDOR_NAME_HUAWEI;
71 case 0x1d17:
72 return FF_GPU_VENDOR_NAME_ZHAOXIN;
73 case 0x1234: // https://admin.pci-ids.ucw.cz/read/PC/1234
74 return FF_GPU_VENDOR_NAME_QEMU;
75 default:
76 return nullptr;
77 }
78}
79
80static bool normalizeVendorName(FFGPUResult* gpu) {
81 if (ffStrbufContainS(&gpu->name, "Apple")) {
82 ffStrbufSetStatic(&gpu->vendor, FF_GPU_VENDOR_NAME_APPLE);
83 gpu->type = FF_GPU_TYPE_INTEGRATED;
84 } else if (ffStrbufContainS(&gpu->name, "Intel")) {
85 ffStrbufSetStatic(&gpu->vendor, FF_GPU_VENDOR_NAME_INTEL);
86 } else if (ffStrbufContainS(&gpu->name, "AMD") || ffStrbufContainS(&gpu->name, "ATI")) {
87 ffStrbufSetStatic(&gpu->vendor, FF_GPU_VENDOR_NAME_AMD);
88 } else if (ffStrbufContainS(&gpu->name, "NVIDIA")) {
89 ffStrbufSetStatic(&gpu->vendor, FF_GPU_VENDOR_NAME_NVIDIA);
90 } else if (ffStrbufContainS(&gpu->name, "MTT")) {
91 ffStrbufSetStatic(&gpu->vendor, FF_GPU_VENDOR_NAME_MTHREADS);
92 } else {
93 return false;
94 }
95
96 return true;
97}
98
99const char* detectByOpenGL(FFlist* gpus) {
100 FF_DEBUG("Starting OpenGL GPU detection fallback");
101
102 FFOpenGLResult result;
103 ffStrbufInit(&result.version);
104 ffStrbufInit(&result.renderer);
105 ffStrbufInit(&result.vendor);
106 ffStrbufInit(&result.slv);
107 ffStrbufInit(&result.library);
108
109 [[gnu::cleanup(ffDestroyOpenGLOptions)]] FFOpenGLOptions options;
110 ffInitOpenGLOptions(&options);
111 const char* error = ffDetectOpenGL(&options, &result);
112 FF_DEBUG("OpenGL detection returns: %s", error ?: "success");
113
114 if (!error) {
115 FFGPUResult* gpu = FF_LIST_ADD(FFGPUResult, *gpus);
116 gpu->type = FF_GPU_TYPE_UNKNOWN;
117 ffStrbufInitMove(&gpu->vendor, &result.vendor);
118 ffStrbufInitMove(&gpu->name, &result.renderer);
119 ffStrbufInit(&gpu->driver);
120 ffStrbufInitF(&gpu->platformApi, "OpenGL %s", result.version.chars);
121 ffStrbufInit(&gpu->memoryType);
122 gpu->index = FF_GPU_INDEX_UNSET;
123 gpu->temperature = FF_GPU_TEMP_UNSET;
124 gpu->coreCount = FF_GPU_CORE_COUNT_UNSET;
125 gpu->frequency = FF_GPU_FREQUENCY_UNSET;
126 gpu->coreUsage = FF_GPU_CORE_USAGE_UNSET;
127 gpu->dedicated.total = gpu->dedicated.used = gpu->shared.total = gpu->shared.used = FF_GPU_VMEM_SIZE_UNSET;
128 gpu->deviceId = 0;
129 gpu->pcieSpeed = FF_GPU_PCIE_SPEED_UNSET;
130
131 FF_DEBUG("OpenGL reported renderer='%s', vendor='%s', version='%s'",
132 gpu->name.chars,
133 gpu->vendor.chars,
134 result.version.chars);
135
136 normalizeVendorName(gpu);
137
138 FF_DEBUG("OpenGL fallback produced GPU: name='%s', vendor='%s', type=%u",
139 gpu->name.chars,
140 gpu->vendor.chars,
141 gpu->type);
142 }
143
144 ffStrbufDestroy(&result.version);
145 ffStrbufDestroy(&result.renderer);
146 ffStrbufDestroy(&result.vendor);
147 ffStrbufDestroy(&result.slv);
148 ffStrbufDestroy(&result.library);
149 return error;
150}
151
152#if defined(FF_HAVE_EGL) || __has_include(<EGL/egl.h>)
153 #include <EGL/egl.h>
154 #include <EGL/eglext.h>
155
156 #if EGL_EXT_device_base && EGL_EXT_device_persistent_id && EGL_EXT_device_query_name
157
158 #define FF_HAVE_EGL_EXT 1
159 #include <inttypes.h>
160
161 #include "common/library.h"
162 #include "common/strutil.h"
163 #include "common/io.h"
164
165const char* detectByEglext(FFlist* result) {
166 FF_LIBRARY_LOAD_MESSAGE(egl, "libEGL" FF_LIBRARY_EXTENSION, 1);
167 FF_LIBRARY_LOAD_SYMBOL_MESSAGE(egl, eglGetProcAddress);
168
169 PFNEGLQUERYDEVICESEXTPROC ffeglQueryDevicesEXT = (PFNEGLQUERYDEVICESEXTPROC) ffeglGetProcAddress("eglQueryDevicesEXT");
170 if (!ffeglQueryDevicesEXT) {
171 return "eglQueryDevicesEXT is unavailable";
172 }
173 PFNEGLQUERYDEVICESTRINGEXTPROC ffeglQueryDeviceStringEXT = (PFNEGLQUERYDEVICESTRINGEXTPROC) ffeglGetProcAddress("eglQueryDeviceStringEXT");
174 if (!ffeglQueryDeviceStringEXT) {
175 return "eglQueryDeviceStringEXT is unavailable";
176 }
177 PFNEGLQUERYDEVICEBINARYEXTPROC ffeglQueryDeviceBinaryEXT = (PFNEGLQUERYDEVICEBINARYEXTPROC) ffeglGetProcAddress("eglQueryDeviceBinaryEXT");
178 if (!ffeglQueryDeviceBinaryEXT) {
179 return "eglQueryDeviceBinaryEXT is unavailable";
180 }
181
182 FF_DEBUG("Loaded EGL library and required symbols");
183
184 FF_SUPPRESS_IO();
185
186 EGLDeviceEXT devices[16];
187 EGLint numDevices;
188 if (ffeglQueryDevicesEXT(ARRAY_SIZE(devices), devices, &numDevices) == EGL_TRUE) {
189 FF_DEBUG("eglQueryDevicesEXT() returned %d device(s)", numDevices);
190 for (EGLint i = 0; i < numDevices; i++) {
191 const EGLDeviceEXT device = devices[i];
192 if (device == (EGLDeviceEXT) EGL_NO_DEVICE_EXT || device == (EGLDeviceEXT) EGL_BAD_DEVICE_EXT) {
193 FF_DEBUG("eglQueryDevicesEXT() returned invalid device at index %d", i);
194 continue;
195 }
196 const char* exts = (const char*) ffeglQueryDeviceStringEXT(device, EGL_EXTENSIONS);
197 FF_DEBUG("eglQueryDeviceStringEXT() returned extensions for device %d: %s", i, exts);
198 if (exts && ffStrContains(exts, "EGL_MESA_device_software")) {
199 FF_DEBUG("eglQueryDeviceStringEXT() returned software device at index %d, skipping", i);
200 continue;
201 }
202
203 const char* name = ffeglQueryDeviceStringEXT(device, EGL_RENDERER_EXT);
204 if (!name) {
205 FF_DEBUG("eglQueryDeviceStringEXT() returned nullptr name for device %d, skipping", i);
206 continue;
207 }
208
209 FFGPUResult* gpu = FF_LIST_ADD(FFGPUResult, *result);
210 ffStrbufInitS(&gpu->name, name);
211 ffStrbufInit(&gpu->vendor);
212 ffStrbufInitS(&gpu->driver, ffeglQueryDeviceStringEXT(device, EGL_DRIVER_NAME_EXT));
213 ffStrbufInitF(&gpu->platformApi, "egl-ext");
214 ffStrbufInit(&gpu->memoryType);
215 gpu->index = FF_GPU_INDEX_UNSET;
216 gpu->temperature = FF_GPU_TEMP_UNSET;
217 gpu->coreCount = FF_GPU_CORE_COUNT_UNSET;
218 gpu->coreUsage = FF_GPU_CORE_USAGE_UNSET;
219 gpu->type = FF_GPU_TYPE_UNKNOWN;
220 gpu->dedicated.total = gpu->dedicated.used = gpu->shared.total = gpu->shared.used = FF_GPU_VMEM_SIZE_UNSET;
221 gpu->deviceId = 0;
222 gpu->frequency = FF_GPU_FREQUENCY_UNSET;
223 gpu->pcieSpeed = FF_GPU_PCIE_SPEED_UNSET;
224
225 ffeglQueryDeviceBinaryEXT(device, EGL_DEVICE_UUID_EXT, sizeof(gpu->deviceId), &gpu->deviceId, nullptr);
226
227 if (!normalizeVendorName(gpu)) {
228 ffStrbufSetS(&gpu->vendor, ffeglQueryDeviceStringEXT(device, EGL_VENDOR));
229 }
230
231 FF_DEBUG("Detected GPU %d: vendor='%s', name='%s', driver='%s', id='%16" PRIX64 "'", i, gpu->vendor.chars, gpu->name.chars, gpu->driver.chars, gpu->deviceId);
232 }
233 } else {
234 FF_DEBUG("eglQueryDevicesEXT() returned EGL_FALSE");
235 }
236
237 return nullptr;
238}
239 #endif
240
241#endif
242
243
244const char* ffDetectGPU(const FFGPUOptions* options, FFlist* result) {
245 FF_DEBUG("Starting GPU detection with method=%d", (int) options->detectionMethod);
246
247 if (options->detectionMethod <= FF_GPU_DETECTION_METHOD_PCI) {
248 FF_DEBUG("Trying PCI/native GPU detection");
249 const char* error = ffDetectGPUImpl(options, result);
250 if (!error && result->length > 0) {
251 FF_DEBUG("PCI/native GPU detection succeeded with %u GPU(s)", result->length);
252 return nullptr;
253 }
254
255 FF_DEBUG("PCI/native GPU detection did not produce results (error=%s, gpuCount=%u)",
256 error ?: "none",
257 result->length);
258 }
259 if (options->detectionMethod <= FF_GPU_DETECTION_METHOD_VULKAN) {
260 FF_DEBUG("Trying Vulkan GPU detection fallback");
261 FFVulkanResult* vulkan = ffDetectVulkan();
262 if (!vulkan->error && vulkan->gpus.length > 0) {
263 FF_DEBUG("Vulkan detection succeeded with %u GPU(s)", vulkan->gpus.length);
264 ffListDestroy(result);
265 ffListInitMove(result, &vulkan->gpus);
266
267#ifdef __ANDROID__
268 double ffGPUDetectTempFromTZ(void);
269 if (options->temp && result->length == 1) {
270 FF_DEBUG("Applying Android thermal-zone temperature to single Vulkan GPU");
271 FF_LIST_GET(FFGPUResult, *result, 0)->temperature = ffGPUDetectTempFromTZ();
272 }
273#endif
274
275 return nullptr;
276 }
277
278 FF_DEBUG("Vulkan detection did not produce results (error=%s, gpuCount=%u)",
279 vulkan->error ?: "none",
280 vulkan->gpus.length);
281 }
282 if (options->detectionMethod <= FF_GPU_DETECTION_METHOD_OPENCL) {
283 FF_DEBUG("Trying OpenCL GPU detection fallback");
284 FFOpenCLResult* opencl = ffDetectOpenCL();
285 if (!opencl->error && opencl->gpus.length > 0) {
286 FF_DEBUG("OpenCL detection succeeded with %u GPU(s)", opencl->gpus.length);
287 ffListDestroy(result);
288 ffListInitMove(result, &opencl->gpus);
289 return nullptr;
290 }
291
292 FF_DEBUG("OpenCL detection did not produce results (error=%s, gpuCount=%u)",
293 opencl->error ?: "none",
294 opencl->gpus.length);
295 }
296 if (options->detectionMethod <= FF_GPU_DETECTION_METHOD_EGL_EXT) {
297 #if FF_HAVE_EGL_EXT
298 FF_DEBUG("Trying EGL_EXT GPU detection fallback");
299 const char* error = detectByEglext(result);
300 if (!error && result->length > 0) {
301 FF_DEBUG("EGL_EXT GPU detection succeeded with %u GPU(s)", result->length);
302 return nullptr;
303 }
304
305 FF_DEBUG("EGL_EXT GPU detection did not produce results (error=%s, gpuCount=%u)",
306 error ?: "none",
307 result->length);
308 #else
309 FF_DEBUG("EGL_EXT GPU detection is unavailable");
310 #endif
311 }
312 if (options->detectionMethod <= FF_GPU_DETECTION_METHOD_OPENGL) {
313 FF_DEBUG("Trying OpenGL GPU detection fallback");
314 const char* error = detectByOpenGL(result);
315 if (error == nullptr) {
316 FF_DEBUG("OpenGL fallback succeeded with %u GPU(s)", result->length);
317 return nullptr;
318 }
319
320 FF_DEBUG("OpenGL fallback failed: %s", error);
321 }
322
323 FF_DEBUG("GPU detection failed in all enabled backends");
324 return "GPU detection failed";
325}
326
327bool ffGPUDetectTypeByVendorAndName(FFGPUResult* gpu) {
328 if (gpu->type != FF_GPU_TYPE_UNKNOWN) {
329 return true;
330 }
331
332 FF_DEBUG("Using fallback GPU type detection");
333 if (gpu->vendor.chars == FF_GPU_VENDOR_NAME_NVIDIA) {
334 if (ffStrbufStartsWithIgnCaseS(&gpu->name, "GeForce") ||
335 ffStrbufStartsWithIgnCaseS(&gpu->name, "Quadro") ||
336 ffStrbufStartsWithIgnCaseS(&gpu->name, "Tesla")) {
337 gpu->type = FF_GPU_TYPE_DISCRETE;
338 }
339 } else if (gpu->vendor.chars == FF_GPU_VENDOR_NAME_MTHREADS) {
340 if (ffStrbufStartsWithIgnCaseS(&gpu->name, "MTT ")) { gpu->type = FF_GPU_TYPE_DISCRETE; }
341 } else if (gpu->vendor.chars == FF_GPU_VENDOR_NAME_INTEL) {
342 // 0000:00:02.0 is reserved for Intel integrated graphics
343 gpu->type = gpu->deviceId == ffGPUPciAddr2Id(0, 0, 2, 0) ? FF_GPU_TYPE_INTEGRATED : FF_GPU_TYPE_DISCRETE;
344 } else if (gpu->vendor.chars == FF_GPU_VENDOR_NAME_VMWARE || gpu->vendor.chars == FF_GPU_VENDOR_NAME_PARALLELS) {
345 // Virtualized GPUs
346 gpu->type = FF_GPU_TYPE_INTEGRATED;
347 }
348
349 if (gpu->type != FF_GPU_TYPE_UNKNOWN) {
350 FF_DEBUG("Determined GPU type based on vendor (%s) and name: %u", gpu->vendor.chars, gpu->type);
351 return true;
352 }
353
354 return false;
355}