master ohmu/fastfetch-openbsd / src / detection / codec / codec_vulkan.c
ohmu  ·  2026-10-15
  1#include "codec.h"
  2
  3#ifdef FF_HAVE_VULKAN
  4    #include <vulkan/vulkan.h>
  5    #include "common/strutil.h"
  6
  7    #if VK_KHR_video_decode_queue && VK_KHR_video_encode_queue
  8
  9        #include "common/debug.h"
 10        #include "common/io.h"
 11        #include "common/library.h"
 12        #include "common/mallocHelper.h"
 13
 14static FFCodecType ffCodecDecodeOperationsToTypes(VkVideoCodecOperationFlagsKHR operations) {
 15    FFCodecType types = FF_CODEC_TYPE_NONE;
 16
 17    if (operations & 0x00000001 /*VK_VIDEO_CODEC_OPERATION_DECODE_H264_BIT_KHR*/) {
 18        types |= FF_CODEC_TYPE_H264;
 19    }
 20    if (operations & 0x00000002 /*VK_VIDEO_CODEC_OPERATION_DECODE_H265_BIT_KHR*/) {
 21        types |= FF_CODEC_TYPE_HEVC;
 22    }
 23    if (operations & 0x00000004 /*VK_VIDEO_CODEC_OPERATION_DECODE_AV1_BIT_KHR*/) {
 24        types |= FF_CODEC_TYPE_AV1;
 25    }
 26    if (operations & 0x00000008 /*VK_VIDEO_CODEC_OPERATION_DECODE_VP9_BIT_KHR*/) {
 27        types |= FF_CODEC_TYPE_VP9;
 28    }
 29
 30    return types;
 31}
 32
 33static FFCodecType ffCodecEncodeOperationsToTypes(VkVideoCodecOperationFlagsKHR operations) {
 34    FFCodecType types = FF_CODEC_TYPE_NONE;
 35
 36    if (operations & 0x00010000 /*VK_VIDEO_CODEC_OPERATION_ENCODE_H264_BIT_KHR*/) {
 37        types |= FF_CODEC_TYPE_H264;
 38    }
 39    if (operations & 0x00020000 /*VK_VIDEO_CODEC_OPERATION_ENCODE_H265_BIT_KHR*/) {
 40        types |= FF_CODEC_TYPE_HEVC;
 41    }
 42    if (operations & 0x00040000 /*VK_VIDEO_CODEC_OPERATION_ENCODE_AV1_BIT_KHR*/) {
 43        types |= FF_CODEC_TYPE_AV1;
 44    }
 45
 46    return types;
 47}
 48
 49static bool ffCodecHasDeviceExtension(const VkExtensionProperties* extensions, uint32_t extensionCount, const char* extensionName) {
 50    for (uint32_t i = 0; i < extensionCount; ++i) {
 51        if (ffStrEquals(extensions[i].extensionName, extensionName)) {
 52            return true;
 53        }
 54    }
 55
 56    return false;
 57}
 58
 59const char* ffDetectCodecVulkan(FFCodecOptions* options, FFlist* result /*list of FFCodecResult*/) {
 60    FF_DEBUG("Starting Vulkan codec detection");
 61
 62    FF_LIBRARY_LOAD_MESSAGE(vulkan,
 63        #if __APPLE__
 64        "libMoltenVK" FF_LIBRARY_EXTENSION,
 65        -1
 66        #elif _WIN32
 67        "vulkan-1" FF_LIBRARY_EXTENSION,
 68        -1
 69        #else
 70        "libvulkan" FF_LIBRARY_EXTENSION,
 71        2
 72        #endif
 73    )
 74    FF_LIBRARY_LOAD_SYMBOL_MESSAGE(vulkan, vkGetInstanceProcAddr)
 75    FF_LIBRARY_LOAD_SYMBOL_MESSAGE(vulkan, vkCreateInstance)
 76    FF_LIBRARY_LOAD_SYMBOL_MESSAGE(vulkan, vkDestroyInstance)
 77    FF_LIBRARY_LOAD_SYMBOL_MESSAGE(vulkan, vkEnumeratePhysicalDevices)
 78    FF_LIBRARY_LOAD_SYMBOL_MESSAGE(vulkan, vkEnumerateDeviceExtensionProperties)
 79
 80    FF_SUPPRESS_IO();
 81
 82    uint32_t apiVersion = VK_API_VERSION_1_0;
 83    PFN_vkEnumerateInstanceVersion ffvkEnumerateInstanceVersion = (PFN_vkEnumerateInstanceVersion) ffvkGetInstanceProcAddr(nullptr, "vkEnumerateInstanceVersion");
 84    if (ffvkEnumerateInstanceVersion != nullptr) {
 85        uint32_t detectedApiVersion = 0;
 86        if (ffvkEnumerateInstanceVersion(&detectedApiVersion) == VK_SUCCESS) {
 87            apiVersion = detectedApiVersion;
 88        }
 89    }
 90
 91    const uint32_t projectVersion = VK_MAKE_VERSION(
 92        FASTFETCH_PROJECT_VERSION_MAJOR,
 93        FASTFETCH_PROJECT_VERSION_MINOR,
 94        FASTFETCH_PROJECT_VERSION_PATCH);
 95
 96    VkInstance vkInstance = VK_NULL_HANDLE;
 97    VkResult res = ffvkCreateInstance(&(VkInstanceCreateInfo){
 98                                          .sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO,
 99                                          .pApplicationInfo = &(VkApplicationInfo){
100                                              .sType = VK_STRUCTURE_TYPE_APPLICATION_INFO,
101                                              .pApplicationName = FASTFETCH_PROJECT_NAME,
102                                              .applicationVersion = projectVersion,
103                                              .pEngineName = "fastfetch-codec-vulkan",
104                                              .engineVersion = projectVersion,
105                                              .apiVersion = apiVersion,
106                                          },
107                                      },
108        nullptr,
109        &vkInstance);
110    if (res != VK_SUCCESS) {
111        FF_DEBUG("ffvkCreateInstance() failed with VkResult=%d", res);
112        switch (res) {
113            case VK_ERROR_OUT_OF_HOST_MEMORY:
114                return "ffvkCreateInstance() failed: VK_ERROR_OUT_OF_HOST_MEMORY";
115            case VK_ERROR_OUT_OF_DEVICE_MEMORY:
116                return "ffvkCreateInstance() failed: VK_ERROR_OUT_OF_DEVICE_MEMORY";
117            case VK_ERROR_INITIALIZATION_FAILED:
118                return "ffvkCreateInstance() failed: VK_ERROR_INITIALIZATION_FAILED";
119            case VK_ERROR_LAYER_NOT_PRESENT:
120                return "ffvkCreateInstance() failed: VK_ERROR_LAYER_NOT_PRESENT";
121            case VK_ERROR_EXTENSION_NOT_PRESENT:
122                return "ffvkCreateInstance() failed: VK_ERROR_EXTENSION_NOT_PRESENT";
123            case VK_ERROR_INCOMPATIBLE_DRIVER:
124                return "ffvkCreateInstance() failed: VK_ERROR_INCOMPATIBLE_DRIVER";
125            default:
126                return "ffvkCreateInstance() failed: unknown error";
127        }
128    }
129
130    PFN_vkGetPhysicalDeviceProperties ffvkGetPhysicalDeviceProperties = (PFN_vkGetPhysicalDeviceProperties) ffvkGetInstanceProcAddr(vkInstance, "vkGetPhysicalDeviceProperties");
131    if (!ffvkGetPhysicalDeviceProperties) {
132        ffvkDestroyInstance(vkInstance, nullptr);
133        return "vkGetPhysicalDeviceProperties is not available";
134    }
135
136    PFN_vkGetPhysicalDeviceQueueFamilyProperties2 ffvkGetPhysicalDeviceQueueFamilyProperties2 = (PFN_vkGetPhysicalDeviceQueueFamilyProperties2) ffvkGetInstanceProcAddr(vkInstance, "vkGetPhysicalDeviceQueueFamilyProperties2");
137    if (!ffvkGetPhysicalDeviceQueueFamilyProperties2) {
138        ffvkDestroyInstance(vkInstance, nullptr);
139        return "vkGetPhysicalDeviceQueueFamilyProperties2 is not available";
140    }
141
142    uint32_t physicalDeviceCount = 0;
143    res = ffvkEnumeratePhysicalDevices(vkInstance, &physicalDeviceCount, nullptr);
144    if (res != VK_SUCCESS) {
145        FF_DEBUG("ffvkEnumeratePhysicalDevices(count) failed with VkResult=%d", res);
146        ffvkDestroyInstance(vkInstance, nullptr);
147        return "ffvkEnumeratePhysicalDevices() failed during Vulkan codec detection";
148    }
149    if (physicalDeviceCount == 0) {
150        ffvkDestroyInstance(vkInstance, nullptr);
151        return "No Vulkan physical devices found";
152    }
153
154    FF_AUTO_FREE VkPhysicalDevice* physicalDevices = (VkPhysicalDevice*) malloc(sizeof(VkPhysicalDevice) * (size_t) physicalDeviceCount);
155
156    res = ffvkEnumeratePhysicalDevices(vkInstance, &physicalDeviceCount, physicalDevices);
157    if (res != VK_SUCCESS) {
158        FF_DEBUG("ffvkEnumeratePhysicalDevices(list) failed with VkResult=%d", res);
159        ffvkDestroyInstance(vkInstance, nullptr);
160        return "ffvkEnumeratePhysicalDevices() failed during Vulkan codec detection";
161    }
162
163    bool sawVideoQueueExtension = false;
164
165    for (uint32_t i = 0; i < physicalDeviceCount; ++i) {
166        VkPhysicalDeviceProperties properties = {};
167        ffvkGetPhysicalDeviceProperties(physicalDevices[i], &properties);
168
169        uint32_t extensionCount = 0;
170        res = ffvkEnumerateDeviceExtensionProperties(physicalDevices[i], nullptr, &extensionCount, nullptr);
171        if (res != VK_SUCCESS) {
172            FF_DEBUG("vkEnumerateDeviceExtensionProperties(count) failed for '%s' with VkResult=%d", properties.deviceName, res);
173            continue;
174        }
175
176        if (extensionCount == 0) {
177            FF_DEBUG("Skipping Vulkan device '%s' because it has no extensions", properties.deviceName);
178            continue;
179        }
180
181        FF_AUTO_FREE VkExtensionProperties* extensions = (VkExtensionProperties*) malloc(sizeof(VkExtensionProperties) * (size_t) extensionCount);
182
183        res = ffvkEnumerateDeviceExtensionProperties(physicalDevices[i], nullptr, &extensionCount, extensions);
184        if (res != VK_SUCCESS) {
185            FF_DEBUG("vkEnumerateDeviceExtensionProperties(list) failed for '%s' with VkResult=%d", properties.deviceName, res);
186            continue;
187        }
188
189        bool hasVideoDecode = (options->showType & FF_CODEC_SHOW_TYPE_DECODER) &&
190            ffCodecHasDeviceExtension(extensions, extensionCount, VK_KHR_VIDEO_DECODE_QUEUE_EXTENSION_NAME);
191        bool hasVideoEncode = (options->showType & FF_CODEC_SHOW_TYPE_ENCODER) &&
192            ffCodecHasDeviceExtension(extensions, extensionCount, VK_KHR_VIDEO_ENCODE_QUEUE_EXTENSION_NAME);
193
194        if (!hasVideoDecode && !hasVideoEncode) {
195            FF_DEBUG("Skipping Vulkan device '%s' because it does not support video queue extensions", properties.deviceName);
196            continue;
197        }
198        sawVideoQueueExtension = true;
199
200        uint32_t queueFamilyCount = 0;
201        ffvkGetPhysicalDeviceQueueFamilyProperties2(physicalDevices[i], &queueFamilyCount, nullptr);
202        if (queueFamilyCount == 0) {
203            FF_DEBUG("Skipping Vulkan device '%s' because it has no queue families", properties.deviceName);
204            continue;
205        }
206
207        FF_AUTO_FREE VkQueueFamilyProperties2* queueFamilyProperties = (VkQueueFamilyProperties2*) malloc(sizeof(VkQueueFamilyProperties2) * (size_t) queueFamilyCount);
208        FF_AUTO_FREE VkQueueFamilyVideoPropertiesKHR* queueFamilyVideoProperties = (VkQueueFamilyVideoPropertiesKHR*) malloc(sizeof(VkQueueFamilyVideoPropertiesKHR) * (size_t) queueFamilyCount);
209
210        for (uint32_t queueIndex = 0; queueIndex < queueFamilyCount; ++queueIndex) {
211            queueFamilyVideoProperties[queueIndex] = (VkQueueFamilyVideoPropertiesKHR){
212                .sType = VK_STRUCTURE_TYPE_QUEUE_FAMILY_VIDEO_PROPERTIES_KHR,
213            };
214            queueFamilyProperties[queueIndex] = (VkQueueFamilyProperties2){
215                .sType = VK_STRUCTURE_TYPE_QUEUE_FAMILY_PROPERTIES_2,
216                .pNext = &queueFamilyVideoProperties[queueIndex],
217            };
218        }
219
220        ffvkGetPhysicalDeviceQueueFamilyProperties2(physicalDevices[i], &queueFamilyCount, queueFamilyProperties);
221
222        FFCodecType decoders = FF_CODEC_TYPE_NONE;
223        FFCodecType encoders = FF_CODEC_TYPE_NONE;
224
225        for (uint32_t queueIndex = 0; queueIndex < queueFamilyCount; ++queueIndex) {
226            const VkQueueFlags queueFlags = queueFamilyProperties[queueIndex].queueFamilyProperties.queueFlags;
227            const VkVideoCodecOperationFlagsKHR operations = queueFamilyVideoProperties[queueIndex].videoCodecOperations;
228
229            if (hasVideoDecode && (queueFlags & VK_QUEUE_VIDEO_DECODE_BIT_KHR)) {
230                decoders |= ffCodecDecodeOperationsToTypes(operations);
231            }
232
233            if (hasVideoEncode && (queueFlags & VK_QUEUE_VIDEO_ENCODE_BIT_KHR)) {
234                encoders |= ffCodecEncodeOperationsToTypes(operations);
235            }
236        }
237
238        if (decoders == FF_CODEC_TYPE_NONE && encoders == FF_CODEC_TYPE_NONE) {
239            FF_DEBUG("Skipping Vulkan device '%s' because no supported codec operations were reported", properties.deviceName);
240            continue;
241        }
242
243        FFCodecResult* item = FF_LIST_ADD(FFCodecResult, *result);
244        ffStrbufInitS(&item->gpu, properties.deviceName);
245        item->decoders = decoders;
246        item->encoders = encoders;
247        item->platformApi = "Vulkan Video";
248        FF_DEBUG("Added Vulkan codec result for '%s': decoders=%u encoders=%u", properties.deviceName, (unsigned) decoders, (unsigned) encoders);
249    }
250
251    ffvkDestroyInstance(vkInstance, nullptr);
252
253    if (result->length > 0) {
254        return nullptr;
255    }
256
257    return sawVideoQueueExtension ? "No supported Vulkan video codec operations found"
258                                  : "VK_KHR_video_queue is not supported by any Vulkan physical device";
259}
260
261    #else
262
263const char* ffDetectCodecVulkan(FFCodecOptions* options, FFlist* result) {
264    FF_UNUSED(options, result);
265    return "Vulkan video queue extensions are not supported by this Vulkan implementation";
266}
267
268    #endif
269
270#endif