summaryrefslogtreecommitdiffstats
path: root/src/detection/vulkan/vulkan.c
diff options
context:
space:
mode:
authorsumuel <samuel@yakubos.org>2026-08-17 20:44:55 +0000
committersumuel <samuel@yakubos.org>2026-08-17 20:44:55 +0000
commit76424950e373d3b04ac3dd13019151bfba3e8423 (patch)
tree4c3cfdbda039e592b9186be3e28f8d7cfd439e8a /src/detection/vulkan/vulkan.c
Add the files
Diffstat (limited to 'src/detection/vulkan/vulkan.c')
-rw-r--r--src/detection/vulkan/vulkan.c332
1 files changed, 332 insertions, 0 deletions
diff --git a/src/detection/vulkan/vulkan.c b/src/detection/vulkan/vulkan.c
new file mode 100644
index 0000000..013c457
--- /dev/null
+++ b/src/detection/vulkan/vulkan.c
@@ -0,0 +1,332 @@
+#include "fastfetch.h"
+#include "common/debug.h"
+#include "detection/gpu/gpu.h"
+#include "detection/vulkan/vulkan.h"
+
+#ifdef FF_HAVE_VULKAN
+ #include "common/library.h"
+ #include "common/io.h"
+ #include "common/parsing.h"
+ #include "common/strutil.h"
+
+ #include <stdlib.h>
+ #include <vulkan/vulkan.h>
+
+static inline void applyVulkanVersion(uint32_t vulkanVersion, FFVersion* ffVersion) {
+ ffVersion->major = VK_VERSION_MAJOR(vulkanVersion);
+ ffVersion->minor = VK_VERSION_MINOR(vulkanVersion);
+ ffVersion->patch = VK_VERSION_PATCH(vulkanVersion);
+}
+
+static void applyDriverName(VkPhysicalDeviceDriverPropertiesKHR* properties, FFstrbuf* result) {
+ if (!ffStrSet(properties->driverName)) {
+ return;
+ }
+
+ ffStrbufAppendS(result, properties->driverName);
+
+ /*
+ * Some drivers (android for example) expose a multiline string as driver info.
+ * It contains too much info anyways, so we just don't append it.
+ */
+ if (!ffStrSet(properties->driverInfo) || strchr(properties->driverInfo, '\n') != NULL) {
+ return;
+ }
+
+ ffStrbufAppendS(result, " [");
+ ffStrbufAppendS(result, properties->driverInfo);
+ ffStrbufAppendC(result, ']');
+}
+
+static const char* detectVulkan(FFVulkanResult* result) {
+ FF_DEBUG("Starting Vulkan detection");
+
+ FF_LIBRARY_LOAD_MESSAGE(vulkan,
+ #if __APPLE__
+ "libMoltenVK" FF_LIBRARY_EXTENSION,
+ -1
+ #elif _WIN32
+ "vulkan-1" FF_LIBRARY_EXTENSION,
+ -1
+ #else
+ "libvulkan" FF_LIBRARY_EXTENSION,
+ 2
+ #endif
+ )
+ FF_LIBRARY_LOAD_SYMBOL_MESSAGE(vulkan, vkGetInstanceProcAddr)
+ FF_LIBRARY_LOAD_SYMBOL_MESSAGE(vulkan, vkCreateInstance)
+ FF_LIBRARY_LOAD_SYMBOL_MESSAGE(vulkan, vkDestroyInstance)
+ FF_LIBRARY_LOAD_SYMBOL_MESSAGE(vulkan, vkEnumeratePhysicalDevices)
+
+ // Some drivers (nvdc) print messages to stdout
+ // and that is the best way I found to disable that
+ FF_SUPPRESS_IO();
+ FF_DEBUG("Suppressed stdout/stderr during Vulkan probing to avoid noisy drivers");
+
+ FFVersion instanceVersion = FF_VERSION_INIT;
+
+ // We need to get the function pointer this way, because it is only provided by vulkan 1.1 and higher.
+ // a dlsym would fail on 1.0 implementations
+ PFN_vkEnumerateInstanceVersion ffvkEnumerateInstanceVersion = (PFN_vkEnumerateInstanceVersion) ffvkGetInstanceProcAddr(NULL, "vkEnumerateInstanceVersion");
+ if (ffvkEnumerateInstanceVersion != NULL) {
+ uint32_t version;
+ if (ffvkEnumerateInstanceVersion(&version) == VK_SUCCESS) {
+ applyVulkanVersion(version, &instanceVersion);
+ FF_DEBUG("Detected Vulkan instance version: %u.%u.%u", instanceVersion.major, instanceVersion.minor, instanceVersion.patch);
+ } else {
+ FF_DEBUG("vkEnumerateInstanceVersion() is available but returned a non-success status");
+ }
+ } else {
+ FF_DEBUG("vkEnumerateInstanceVersion() is unavailable (likely Vulkan 1.0 runtime)");
+ }
+
+ const uint32_t projectVersion = VK_MAKE_VERSION(
+ FASTFETCH_PROJECT_VERSION_MAJOR,
+ FASTFETCH_PROJECT_VERSION_MINOR,
+ FASTFETCH_PROJECT_VERSION_PATCH);
+
+ VkInstance vkInstance;
+ FF_DEBUG("Creating Vulkan instance with requested API version %s", instanceVersion.minor >= 1 ? "1.1" : "1.0");
+ VkResult res = ffvkCreateInstance(&(VkInstanceCreateInfo) {
+ .sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO,
+ .pNext = NULL,
+ .pApplicationInfo = &(VkApplicationInfo) {
+ .sType = VK_STRUCTURE_TYPE_APPLICATION_INFO,
+ .pNext = NULL,
+ .pApplicationName = FASTFETCH_PROJECT_NAME,
+ .applicationVersion = projectVersion,
+ .pEngineName = "vulkanPrintGPUs",
+ .engineVersion = projectVersion,
+
+ // We need to request 1.1 to get physicalDeviceDriverProperties
+ .apiVersion = instanceVersion.minor >= 1 ? VK_API_VERSION_1_1 : VK_API_VERSION_1_0 },
+ .enabledLayerCount = 0,
+ .ppEnabledLayerNames = NULL,
+ .enabledExtensionCount = 0,
+ .ppEnabledExtensionNames = NULL,
+ .flags = 0 },
+ NULL,
+ &vkInstance);
+ if (res != VK_SUCCESS) {
+ FF_DEBUG("ffvkCreateInstance() failed with VkResult=%d", res);
+ switch (res) {
+ case VK_ERROR_OUT_OF_HOST_MEMORY:
+ return "ffvkCreateInstance() failed: VK_ERROR_OUT_OF_HOST_MEMORY";
+ case VK_ERROR_OUT_OF_DEVICE_MEMORY:
+ return "ffvkCreateInstance() failed: VK_ERROR_OUT_OF_DEVICE_MEMORY";
+ case VK_ERROR_INITIALIZATION_FAILED:
+ return "ffvkCreateInstance() failed: VK_ERROR_INITIALIZATION_FAILED";
+ case VK_ERROR_LAYER_NOT_PRESENT:
+ return "ffvkCreateInstance() failed: VK_ERROR_LAYER_NOT_PRESENT";
+ case VK_ERROR_EXTENSION_NOT_PRESENT:
+ return "ffvkCreateInstance() failed: VK_ERROR_EXTENSION_NOT_PRESENT";
+ case VK_ERROR_INCOMPATIBLE_DRIVER:
+ return "ffvkCreateInstance() failed: VK_ERROR_INCOMPATIBLE_DRIVER";
+ default:
+ return "ffvkCreateInstance() failed: unknown error";
+ }
+ }
+ FF_DEBUG("Vulkan instance created successfully");
+
+ // if instance creation succeeded, but vkEnumerateInstanceVersion didn't, this means we are running against a vulkan 1.0 implementation
+ // explicitly set this version, if no device is found, so we still have at least this info
+ if (instanceVersion.major == 0 && instanceVersion.minor == 0 && instanceVersion.patch == 0) {
+ instanceVersion.major = 1;
+ FF_DEBUG("Falling back to Vulkan instance version 1.0 due to unavailable enumerate call");
+ }
+
+ VkPhysicalDevice physicalDevices[128];
+ uint32_t physicalDeviceCount = (uint32_t) ARRAY_SIZE(physicalDevices);
+ res = ffvkEnumeratePhysicalDevices(vkInstance, &physicalDeviceCount, physicalDevices);
+ if (res != VK_SUCCESS) {
+ FF_DEBUG("ffvkEnumeratePhysicalDevices() failed with VkResult=%d", res);
+ ffvkDestroyInstance(vkInstance, NULL);
+ switch (res) {
+ case VK_ERROR_OUT_OF_HOST_MEMORY:
+ return "ffvkEnumeratePhysicalDevices() failed: VK_ERROR_OUT_OF_HOST_MEMORY";
+ case VK_ERROR_OUT_OF_DEVICE_MEMORY:
+ return "ffvkEnumeratePhysicalDevices() failed: VK_ERROR_OUT_OF_DEVICE_MEMORY";
+ case VK_ERROR_INITIALIZATION_FAILED:
+ return "ffvkEnumeratePhysicalDevices() failed: VK_ERROR_INITIALIZATION_FAILED";
+ case VK_INCOMPLETE:
+ return "ffvkEnumeratePhysicalDevices() failed: VK_INCOMPLETE";
+ default:
+ return "ffvkEnumeratePhysicalDevices() failed";
+ }
+ }
+ FF_DEBUG("Enumerated %u Vulkan physical device(s)", physicalDeviceCount);
+
+ PFN_vkGetPhysicalDeviceProperties ffvkGetPhysicalDeviceProperties = NULL;
+ PFN_vkGetPhysicalDeviceProperties2 ffvkGetPhysicalDeviceProperties2 = (PFN_vkGetPhysicalDeviceProperties2) ffvkGetInstanceProcAddr(vkInstance, "vkGetPhysicalDeviceProperties2"); // 1.1
+ if (!ffvkGetPhysicalDeviceProperties2) {
+ ffvkGetPhysicalDeviceProperties = (PFN_vkGetPhysicalDeviceProperties) ffvkGetInstanceProcAddr(vkInstance, "vkGetPhysicalDeviceProperties");
+ }
+
+ FF_DEBUG("Using %s for querying physical device properties", ffvkGetPhysicalDeviceProperties2 ? "vkGetPhysicalDeviceProperties2" : "vkGetPhysicalDeviceProperties");
+
+ PFN_vkGetPhysicalDeviceMemoryProperties ffvkGetPhysicalDeviceMemoryProperties = (PFN_vkGetPhysicalDeviceMemoryProperties) ffvkGetInstanceProcAddr(vkInstance, "vkGetPhysicalDeviceMemoryProperties");
+ if (!ffvkGetPhysicalDeviceMemoryProperties) {
+ FF_DEBUG("vkGetPhysicalDeviceMemoryProperties is unavailable");
+ ffvkDestroyInstance(vkInstance, NULL);
+ return "vkGetPhysicalDeviceMemoryProperties is not available";
+ }
+
+ FFVersion maxDeviceApiVersion = FF_VERSION_INIT;
+ FFVersion maxDeviceConformanceVersion = FF_VERSION_INIT;
+
+ for (uint32_t i = 0; i < physicalDeviceCount; i++) {
+ // Get device properties.
+ // On VK 1.1 and up, we use vkGetPhysicalDeviceProperties2, so we can put VkPhysicalDeviceDriverProperties in the pNext chain.
+ // This is required to get the driver name and conformance version.
+
+ VkPhysicalDeviceDriverPropertiesKHR driverProperties = {
+ .sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_DRIVER_PROPERTIES_KHR,
+ };
+ VkPhysicalDeviceProperties2 physicalDeviceProperties = {
+ .sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PROPERTIES_2,
+ .pNext = &driverProperties,
+ };
+
+ if (ffvkGetPhysicalDeviceProperties2 != NULL) {
+ ffvkGetPhysicalDeviceProperties2(physicalDevices[i], &physicalDeviceProperties);
+ } else {
+ ffvkGetPhysicalDeviceProperties(physicalDevices[i], &physicalDeviceProperties.properties);
+ }
+
+ 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);
+
+ // We don't want software rasterizers to show up as physical gpu
+ if (physicalDeviceProperties.properties.deviceType == VK_PHYSICAL_DEVICE_TYPE_CPU) {
+ FF_DEBUG("Skipping CPU Vulkan device '%s'", physicalDeviceProperties.properties.deviceName);
+ continue;
+ }
+
+ // If the device api version is higher than the current highest device api version, overwrite it
+ // In this case, also use the current device driver name as the shown driver name
+
+ FFVersion deviceAPIVersion = FF_VERSION_INIT;
+ applyVulkanVersion(physicalDeviceProperties.properties.apiVersion, &deviceAPIVersion);
+ if (ffVersionCompare(&deviceAPIVersion, &maxDeviceApiVersion) > 0) {
+ maxDeviceApiVersion = deviceAPIVersion;
+ applyDriverName(&driverProperties, &result->driver);
+ FF_DEBUG("Updated max Vulkan device API version to %u.%u.%u (driver='%s')",
+ maxDeviceApiVersion.major,
+ maxDeviceApiVersion.minor,
+ maxDeviceApiVersion.patch,
+ result->driver.chars);
+ }
+
+ // If the device conformance version is higher than the current highest device conformance version, overwrite it
+ if (ffvkGetPhysicalDeviceProperties2) {
+ FFVersion deviceConformanceVersion = {
+ .major = driverProperties.conformanceVersion.major,
+ .minor = driverProperties.conformanceVersion.minor,
+ .patch = driverProperties.conformanceVersion.patch,
+ };
+
+ if (ffVersionCompare(&deviceConformanceVersion, &maxDeviceConformanceVersion) > 0) {
+ maxDeviceConformanceVersion = deviceConformanceVersion;
+ FF_DEBUG("Updated max Vulkan conformance version to %u.%u.%u",
+ maxDeviceConformanceVersion.major,
+ maxDeviceConformanceVersion.minor,
+ maxDeviceConformanceVersion.patch);
+ }
+ }
+
+ // Add the device to the list of devices shown by the GPU module
+
+ // #456
+ FF_LIST_FOR_EACH (FFGPUResult, gpu, result->gpus) {
+ if (gpu->deviceId == physicalDeviceProperties.properties.deviceID) {
+ FF_DEBUG("Skipping duplicate Vulkan GPU entry for deviceId=0x%04X", physicalDeviceProperties.properties.deviceID);
+ goto next;
+ }
+ }
+
+ FFGPUResult* gpu = FF_LIST_ADD(FFGPUResult, result->gpus);
+
+ ffStrbufInitF(&gpu->platformApi, "Vulkan %u.%u.%u", deviceAPIVersion.major, deviceAPIVersion.minor, deviceAPIVersion.patch);
+ gpu->deviceId = physicalDeviceProperties.properties.deviceID;
+
+ ffStrbufInitS(&gpu->name, physicalDeviceProperties.properties.deviceName);
+
+ gpu->type = physicalDeviceProperties.properties.deviceType == VK_PHYSICAL_DEVICE_TYPE_DISCRETE_GPU ? FF_GPU_TYPE_DISCRETE : FF_GPU_TYPE_INTEGRATED;
+ ffStrbufInitS(&gpu->vendor, ffGPUGetVendorString(physicalDeviceProperties.properties.vendorID));
+ ffStrbufInitS(&gpu->driver, driverProperties.driverInfo);
+ ffStrbufInit(&gpu->memoryType);
+ FF_DEBUG("Added Vulkan GPU '%s' (vendor='%s', type=%s)",
+ gpu->name.chars,
+ gpu->vendor.chars,
+ gpu->type == FF_GPU_TYPE_DISCRETE ? "discrete" : "integrated");
+
+ VkPhysicalDeviceMemoryProperties memoryProperties = {};
+ ffvkGetPhysicalDeviceMemoryProperties(physicalDevices[i], &memoryProperties);
+
+ gpu->dedicated.total = gpu->shared.total = 0;
+ gpu->dedicated.used = gpu->shared.used = FF_GPU_VMEM_SIZE_UNSET;
+ for (uint32_t index = 0; index < memoryProperties.memoryHeapCount; ++index) {
+ const VkMemoryHeap* heap = &memoryProperties.memoryHeaps[index];
+ FFGPUMemory* vmem = gpu->type == FF_GPU_TYPE_DISCRETE && (heap->flags & VK_MEMORY_HEAP_DEVICE_LOCAL_BIT) ? &gpu->dedicated : &gpu->shared;
+ vmem->total += heap->size;
+ }
+ FF_DEBUG("Computed memory for '%s': dedicatedTotal=%llu, sharedTotal=%llu",
+ gpu->name.chars,
+ (unsigned long long) gpu->dedicated.total,
+ (unsigned long long) gpu->shared.total);
+
+ // No way to detect those using vulkan
+ gpu->index = FF_GPU_INDEX_UNSET;
+ gpu->coreCount = FF_GPU_CORE_COUNT_UNSET;
+ gpu->temperature = FF_GPU_TEMP_UNSET;
+ gpu->frequency = FF_GPU_FREQUENCY_UNSET;
+ gpu->coreUsage = FF_GPU_CORE_USAGE_UNSET;
+
+ next:
+ continue;
+ }
+
+ ffVersionToPretty(&instanceVersion, &result->instanceVersion);
+ ffVersionToPretty(&maxDeviceApiVersion, &result->apiVersion);
+ ffVersionToPretty(&maxDeviceConformanceVersion, &result->conformanceVersion);
+
+ FF_DEBUG("Vulkan detection finished: instanceVersion=%s, apiVersion=%s, conformanceVersion=%s, gpuCount=%u",
+ result->instanceVersion.chars,
+ result->apiVersion.chars,
+ result->conformanceVersion.chars,
+ result->gpus.length);
+
+ ffvkDestroyInstance(vkInstance, NULL);
+ FF_DEBUG("Destroyed Vulkan instance");
+ return NULL;
+}
+
+#endif
+
+FFVulkanResult* ffDetectVulkan(void) {
+ static FFVulkanResult result;
+ static bool initialized;
+
+ if (!initialized) {
+ FF_DEBUG("Initializing Vulkan detection cache");
+ initialized = true;
+ ffStrbufInit(&result.driver);
+ ffStrbufInit(&result.apiVersion);
+ ffStrbufInit(&result.conformanceVersion);
+ ffStrbufInit(&result.instanceVersion);
+ ffListInit(&result.gpus);
+
+#ifdef FF_HAVE_VULKAN
+ result.error = detectVulkan(&result);
+ if (result.error) {
+ FF_DEBUG("Vulkan detection returned error: %s", result.error);
+ }
+#else
+ result.error = "fastfetch was compiled without vulkan support";
+ FF_DEBUG("Vulkan support is disabled at compile time");
+#endif
+ } else {
+ FF_DEBUG("Reusing cached Vulkan detection result");
+ }
+
+ return &result;
+}