ohmu
·
2026-10-15
1#include "fastfetch.h"
2#include "common/debug.h"
3#include "detection/gpu/gpu.h"
4#include "detection/vulkan/vulkan.h"
5
6#ifdef FF_HAVE_VULKAN
7 #include "common/library.h"
8 #include "common/io.h"
9 #include "common/parsing.h"
10 #include "common/strutil.h"
11
12 #include <stdlib.h>
13 #include <vulkan/vulkan.h>
14
15static inline void applyVulkanVersion(uint32_t vulkanVersion, FFVersion* ffVersion) {
16 ffVersion->major = VK_VERSION_MAJOR(vulkanVersion);
17 ffVersion->minor = VK_VERSION_MINOR(vulkanVersion);
18 ffVersion->patch = VK_VERSION_PATCH(vulkanVersion);
19}
20
21static void applyDriverName(VkPhysicalDeviceDriverPropertiesKHR* properties, FFstrbuf* result) {
22 ffStrbufClear(result);
23
24 if (!ffStrSet(properties->driverName)) {
25 return;
26 }
27
28 ffStrbufAppendS(result, properties->driverName);
29
30 /*
31 * Some drivers (android for example) expose a multiline string as driver info.
32 * It contains too much info anyways, so we just don't append it.
33 */
34 if (!ffStrSet(properties->driverInfo) || strchr(properties->driverInfo, '\n') != nullptr) {
35 return;
36 }
37
38 ffStrbufAppendS(result, " [");
39 ffStrbufAppendS(result, properties->driverInfo);
40 ffStrbufAppendC(result, ']');
41}
42
43static const char* detectVulkan(FFVulkanResult* result) {
44 FF_DEBUG("Starting Vulkan detection");
45
46 FF_LIBRARY_LOAD_MESSAGE(vulkan,
47 #if __APPLE__
48 "libMoltenVK" FF_LIBRARY_EXTENSION,
49 -1
50 #elif _WIN32
51 "vulkan-1" FF_LIBRARY_EXTENSION,
52 -1
53 #else
54 "libvulkan" FF_LIBRARY_EXTENSION,
55 2
56 #endif
57 )
58 FF_LIBRARY_LOAD_SYMBOL_MESSAGE(vulkan, vkGetInstanceProcAddr)
59 FF_LIBRARY_LOAD_SYMBOL_MESSAGE(vulkan, vkCreateInstance)
60 FF_LIBRARY_LOAD_SYMBOL_MESSAGE(vulkan, vkDestroyInstance)
61 FF_LIBRARY_LOAD_SYMBOL_MESSAGE(vulkan, vkEnumeratePhysicalDevices)
62
63 // Some drivers (nvdc) print messages to stdout
64 // and that is the best way I found to disable that
65 FF_SUPPRESS_IO();
66 FF_DEBUG("Suppressed stdout/stderr during Vulkan probing to avoid noisy drivers");
67
68 FFVersion instanceVersion = FF_VERSION_INIT;
69
70 // We need to get the function pointer this way, because it is only provided by vulkan 1.1 and higher.
71 // a dlsym would fail on 1.0 implementations
72 PFN_vkEnumerateInstanceVersion ffvkEnumerateInstanceVersion = (PFN_vkEnumerateInstanceVersion) ffvkGetInstanceProcAddr(nullptr, "vkEnumerateInstanceVersion");
73 if (ffvkEnumerateInstanceVersion != nullptr) {
74 uint32_t version;
75 if (ffvkEnumerateInstanceVersion(&version) == VK_SUCCESS) {
76 applyVulkanVersion(version, &instanceVersion);
77 FF_DEBUG("Detected Vulkan instance version: %u.%u.%u", instanceVersion.major, instanceVersion.minor, instanceVersion.patch);
78 } else {
79 FF_DEBUG("vkEnumerateInstanceVersion() is available but returned a non-success status");
80 }
81 } else {
82 FF_DEBUG("vkEnumerateInstanceVersion() is unavailable (likely Vulkan 1.0 runtime)");
83 }
84
85 const uint32_t projectVersion = VK_MAKE_VERSION(
86 FASTFETCH_PROJECT_VERSION_MAJOR,
87 FASTFETCH_PROJECT_VERSION_MINOR,
88 FASTFETCH_PROJECT_VERSION_PATCH);
89
90 VkInstance vkInstance;
91 FF_DEBUG("Creating Vulkan instance with requested API version %s", instanceVersion.minor >= 1 ? "1.1" : "1.0");
92 VkResult res = ffvkCreateInstance(&(VkInstanceCreateInfo) {
93 .sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO,
94 .pNext = nullptr,
95 .pApplicationInfo = &(VkApplicationInfo) {
96 .sType = VK_STRUCTURE_TYPE_APPLICATION_INFO,
97 .pNext = nullptr,
98 .pApplicationName = FASTFETCH_PROJECT_NAME,
99 .applicationVersion = projectVersion,
100 .pEngineName = "vulkanPrintGPUs",
101 .engineVersion = projectVersion,
102
103 // We need to request 1.1 to get physicalDeviceDriverProperties
104 .apiVersion = instanceVersion.minor >= 1 ? VK_API_VERSION_1_1 : VK_API_VERSION_1_0 },
105 .enabledLayerCount = 0,
106 .ppEnabledLayerNames = nullptr,
107 .enabledExtensionCount = 0,
108 .ppEnabledExtensionNames = nullptr,
109 .flags = 0 },
110 nullptr,
111 &vkInstance);
112 if (res != VK_SUCCESS) {
113 FF_DEBUG("ffvkCreateInstance() failed with VkResult=%d", res);
114 switch (res) {
115 case VK_ERROR_OUT_OF_HOST_MEMORY:
116 return "ffvkCreateInstance() failed: VK_ERROR_OUT_OF_HOST_MEMORY";
117 case VK_ERROR_OUT_OF_DEVICE_MEMORY:
118 return "ffvkCreateInstance() failed: VK_ERROR_OUT_OF_DEVICE_MEMORY";
119 case VK_ERROR_INITIALIZATION_FAILED:
120 return "ffvkCreateInstance() failed: VK_ERROR_INITIALIZATION_FAILED";
121 case VK_ERROR_LAYER_NOT_PRESENT:
122 return "ffvkCreateInstance() failed: VK_ERROR_LAYER_NOT_PRESENT";
123 case VK_ERROR_EXTENSION_NOT_PRESENT:
124 return "ffvkCreateInstance() failed: VK_ERROR_EXTENSION_NOT_PRESENT";
125 case VK_ERROR_INCOMPATIBLE_DRIVER:
126 return "ffvkCreateInstance() failed: VK_ERROR_INCOMPATIBLE_DRIVER";
127 default:
128 return "ffvkCreateInstance() failed: unknown error";
129 }
130 }
131 FF_DEBUG("Vulkan instance created successfully");
132
133 // if instance creation succeeded, but vkEnumerateInstanceVersion didn't, this means we are running against a vulkan 1.0 implementation
134 // explicitly set this version, if no device is found, so we still have at least this info
135 if (instanceVersion.major == 0 && instanceVersion.minor == 0 && instanceVersion.patch == 0) {
136 instanceVersion.major = 1;
137 FF_DEBUG("Falling back to Vulkan instance version 1.0 due to unavailable enumerate call");
138 }
139
140 VkPhysicalDevice physicalDevices[128];
141 uint32_t physicalDeviceCount = (uint32_t) ARRAY_SIZE(physicalDevices);
142 res = ffvkEnumeratePhysicalDevices(vkInstance, &physicalDeviceCount, physicalDevices);
143 if (res != VK_SUCCESS) {
144 FF_DEBUG("ffvkEnumeratePhysicalDevices() failed with VkResult=%d", res);
145 ffvkDestroyInstance(vkInstance, nullptr);
146 switch (res) {
147 case VK_ERROR_OUT_OF_HOST_MEMORY:
148 return "ffvkEnumeratePhysicalDevices() failed: VK_ERROR_OUT_OF_HOST_MEMORY";
149 case VK_ERROR_OUT_OF_DEVICE_MEMORY:
150 return "ffvkEnumeratePhysicalDevices() failed: VK_ERROR_OUT_OF_DEVICE_MEMORY";
151 case VK_ERROR_INITIALIZATION_FAILED:
152 return "ffvkEnumeratePhysicalDevices() failed: VK_ERROR_INITIALIZATION_FAILED";
153 case VK_INCOMPLETE:
154 return "ffvkEnumeratePhysicalDevices() failed: VK_INCOMPLETE";
155 default:
156 return "ffvkEnumeratePhysicalDevices() failed";
157 }
158 }
159 FF_DEBUG("Enumerated %u Vulkan physical device(s)", physicalDeviceCount);
160
161 PFN_vkGetPhysicalDeviceProperties ffvkGetPhysicalDeviceProperties = nullptr;
162 PFN_vkGetPhysicalDeviceProperties2 ffvkGetPhysicalDeviceProperties2 = (PFN_vkGetPhysicalDeviceProperties2) ffvkGetInstanceProcAddr(vkInstance, "vkGetPhysicalDeviceProperties2"); // 1.1
163 if (!ffvkGetPhysicalDeviceProperties2) {
164 ffvkGetPhysicalDeviceProperties = (PFN_vkGetPhysicalDeviceProperties) ffvkGetInstanceProcAddr(vkInstance, "vkGetPhysicalDeviceProperties");
165 }
166
167 FF_DEBUG("Using %s for querying physical device properties", ffvkGetPhysicalDeviceProperties2 ? "vkGetPhysicalDeviceProperties2" : "vkGetPhysicalDeviceProperties");
168
169 PFN_vkGetPhysicalDeviceMemoryProperties ffvkGetPhysicalDeviceMemoryProperties = (PFN_vkGetPhysicalDeviceMemoryProperties) ffvkGetInstanceProcAddr(vkInstance, "vkGetPhysicalDeviceMemoryProperties");
170 if (!ffvkGetPhysicalDeviceMemoryProperties) {
171 FF_DEBUG("vkGetPhysicalDeviceMemoryProperties is unavailable");
172 ffvkDestroyInstance(vkInstance, nullptr);
173 return "vkGetPhysicalDeviceMemoryProperties is not available";
174 }
175
176 FFVersion maxDeviceApiVersion = FF_VERSION_INIT;
177 FFVersion maxDeviceConformanceVersion = FF_VERSION_INIT;
178
179 for (uint32_t i = 0; i < physicalDeviceCount; i++) {
180 // Get device properties.
181 // On VK 1.1 and up, we use vkGetPhysicalDeviceProperties2, so we can put VkPhysicalDeviceDriverProperties in the pNext chain.
182 // This is required to get the driver name and conformance version.
183
184 VkPhysicalDeviceDriverPropertiesKHR driverProperties = {
185 .sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_DRIVER_PROPERTIES_KHR,
186 };
187 VkPhysicalDeviceProperties2 physicalDeviceProperties = {
188 .sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PROPERTIES_2,
189 .pNext = &driverProperties,
190 };
191
192 if (ffvkGetPhysicalDeviceProperties2 != nullptr) {
193 ffvkGetPhysicalDeviceProperties2(physicalDevices[i], &physicalDeviceProperties);
194 } else {
195 ffvkGetPhysicalDeviceProperties(physicalDevices[i], &physicalDeviceProperties.properties);
196 }
197
198 FF_DEBUG("Processing Vulkan device #%u: name='%s', vendorId=0x%04X, deviceId=0x%04X, type=%u", i, physicalDeviceProperties.properties.deviceName, physicalDeviceProperties.properties.vendorID, physicalDeviceProperties.properties.deviceID, physicalDeviceProperties.properties.deviceType);
199
200 // We don't want software rasterizers to show up as physical gpu
201 if (physicalDeviceProperties.properties.deviceType == VK_PHYSICAL_DEVICE_TYPE_CPU) {
202 FF_DEBUG("Skipping CPU Vulkan device '%s'", physicalDeviceProperties.properties.deviceName);
203 continue;
204 }
205
206 // If the device api version is higher than the current highest device api version, overwrite it
207 // In this case, also use the current device driver name as the shown driver name
208
209 FFVersion deviceAPIVersion = FF_VERSION_INIT;
210 applyVulkanVersion(physicalDeviceProperties.properties.apiVersion, &deviceAPIVersion);
211 if (ffVersionCompare(&deviceAPIVersion, &maxDeviceApiVersion) > 0) {
212 maxDeviceApiVersion = deviceAPIVersion;
213 applyDriverName(&driverProperties, &result->driver);
214 FF_DEBUG("Updated max Vulkan device API version to %u.%u.%u (driver='%s')",
215 maxDeviceApiVersion.major,
216 maxDeviceApiVersion.minor,
217 maxDeviceApiVersion.patch,
218 result->driver.chars);
219 }
220
221 // If the device conformance version is higher than the current highest device conformance version, overwrite it
222 if (ffvkGetPhysicalDeviceProperties2) {
223 FFVersion deviceConformanceVersion = {
224 .major = driverProperties.conformanceVersion.major,
225 .minor = driverProperties.conformanceVersion.minor,
226 .patch = driverProperties.conformanceVersion.patch,
227 };
228
229 if (ffVersionCompare(&deviceConformanceVersion, &maxDeviceConformanceVersion) > 0) {
230 maxDeviceConformanceVersion = deviceConformanceVersion;
231 FF_DEBUG("Updated max Vulkan conformance version to %u.%u.%u",
232 maxDeviceConformanceVersion.major,
233 maxDeviceConformanceVersion.minor,
234 maxDeviceConformanceVersion.patch);
235 }
236 }
237
238 // Add the device to the list of devices shown by the GPU module
239
240 // #456
241 FF_LIST_FOR_EACH (FFGPUResult, gpu, result->gpus) {
242 if (gpu->deviceId == physicalDeviceProperties.properties.deviceID) {
243 FF_DEBUG("Skipping duplicate Vulkan GPU entry for deviceId=0x%04X", physicalDeviceProperties.properties.deviceID);
244 goto next;
245 }
246 }
247
248 FFGPUResult* gpu = FF_LIST_ADD(FFGPUResult, result->gpus);
249
250 ffStrbufInitF(&gpu->platformApi, "Vulkan %u.%u.%u", deviceAPIVersion.major, deviceAPIVersion.minor, deviceAPIVersion.patch);
251 gpu->deviceId = physicalDeviceProperties.properties.deviceID;
252
253 ffStrbufInitS(&gpu->name, physicalDeviceProperties.properties.deviceName);
254
255 switch (physicalDeviceProperties.properties.deviceType) {
256 case VK_PHYSICAL_DEVICE_TYPE_DISCRETE_GPU: gpu->type = FF_GPU_TYPE_DISCRETE; break;
257 case VK_PHYSICAL_DEVICE_TYPE_INTEGRATED_GPU:
258 case VK_PHYSICAL_DEVICE_TYPE_VIRTUAL_GPU: gpu->type = FF_GPU_TYPE_INTEGRATED; break; // Virtual machine?
259 default: gpu->type = FF_GPU_TYPE_UNKNOWN; break;
260 }
261 ffStrbufInitS(&gpu->vendor, ffGPUGetVendorString(physicalDeviceProperties.properties.vendorID));
262 ffStrbufInitS(&gpu->driver, driverProperties.driverInfo);
263 ffStrbufInit(&gpu->memoryType);
264 FF_DEBUG("Added Vulkan GPU '%s' (vendor='%s', type=%s)",
265 gpu->name.chars,
266 gpu->vendor.chars,
267 gpu->type == FF_GPU_TYPE_DISCRETE ? "discrete" : "integrated");
268
269 VkPhysicalDeviceMemoryProperties memoryProperties = {};
270 ffvkGetPhysicalDeviceMemoryProperties(physicalDevices[i], &memoryProperties);
271
272 gpu->dedicated.total = gpu->shared.total = 0;
273 gpu->dedicated.used = gpu->shared.used = FF_GPU_VMEM_SIZE_UNSET;
274 for (uint32_t index = 0; index < memoryProperties.memoryHeapCount; ++index) {
275 const VkMemoryHeap* heap = &memoryProperties.memoryHeaps[index];
276 FFGPUMemory* vmem = gpu->type == FF_GPU_TYPE_DISCRETE && (heap->flags & VK_MEMORY_HEAP_DEVICE_LOCAL_BIT) ? &gpu->dedicated : &gpu->shared;
277 vmem->total += heap->size;
278 }
279 FF_DEBUG("Computed memory for '%s': dedicatedTotal=%llu, sharedTotal=%llu",
280 gpu->name.chars,
281 (unsigned long long) gpu->dedicated.total,
282 (unsigned long long) gpu->shared.total);
283
284 // No way to detect those using vulkan
285 gpu->index = FF_GPU_INDEX_UNSET;
286 gpu->coreCount = FF_GPU_CORE_COUNT_UNSET;
287 gpu->temperature = FF_GPU_TEMP_UNSET;
288 gpu->frequency = FF_GPU_FREQUENCY_UNSET;
289 gpu->coreUsage = FF_GPU_CORE_USAGE_UNSET;
290 gpu->pcieSpeed = FF_GPU_PCIE_SPEED_UNSET;
291
292 next:
293 continue;
294 }
295
296 ffVersionToPretty(&instanceVersion, &result->instanceVersion);
297 ffVersionToPretty(&maxDeviceApiVersion, &result->apiVersion);
298 ffVersionToPretty(&maxDeviceConformanceVersion, &result->conformanceVersion);
299
300 FF_DEBUG("Vulkan detection finished: instanceVersion=%s, apiVersion=%s, conformanceVersion=%s, gpuCount=%u",
301 result->instanceVersion.chars,
302 result->apiVersion.chars,
303 result->conformanceVersion.chars,
304 result->gpus.length);
305
306 ffvkDestroyInstance(vkInstance, nullptr);
307 FF_DEBUG("Destroyed Vulkan instance");
308 return nullptr;
309}
310
311#endif
312
313FFVulkanResult* ffDetectVulkan(void) {
314 static FFVulkanResult result;
315 static bool initialized;
316
317 if (!initialized) {
318 FF_DEBUG("Initializing Vulkan detection cache");
319 initialized = true;
320 ffStrbufInit(&result.driver);
321 ffStrbufInit(&result.apiVersion);
322 ffStrbufInit(&result.conformanceVersion);
323 ffStrbufInit(&result.instanceVersion);
324 ffListInit(&result.gpus);
325
326#ifdef FF_HAVE_VULKAN
327 result.error = detectVulkan(&result);
328 if (result.error) {
329 FF_DEBUG("Vulkan detection returned error: %s", result.error);
330 }
331#else
332 result.error = "fastfetch was compiled without vulkan support";
333 FF_DEBUG("Vulkan support is disabled at compile time");
334#endif
335 } else {
336 FF_DEBUG("Reusing cached Vulkan detection result");
337 }
338
339 return &result;
340}