master ohmu/fastfetch-openbsd / src / detection / vulkan / vulkan.c
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}