diff options
| author | sumuel <samuel@yakubos.org> | 2026-08-17 20:44:55 +0000 |
|---|---|---|
| committer | sumuel <samuel@yakubos.org> | 2026-08-17 20:44:55 +0000 |
| commit | 76424950e373d3b04ac3dd13019151bfba3e8423 (patch) | |
| tree | 4c3cfdbda039e592b9186be3e28f8d7cfd439e8a /src/detection/codec/codec_vulkan.c | |
Add the files
Diffstat (limited to 'src/detection/codec/codec_vulkan.c')
| -rw-r--r-- | src/detection/codec/codec_vulkan.c | 270 |
1 files changed, 270 insertions, 0 deletions
diff --git a/src/detection/codec/codec_vulkan.c b/src/detection/codec/codec_vulkan.c new file mode 100644 index 0000000..eabfe26 --- /dev/null +++ b/src/detection/codec/codec_vulkan.c @@ -0,0 +1,270 @@ +#include "codec.h" + +#ifdef FF_HAVE_VULKAN + #include <vulkan/vulkan.h> + #include "common/strutil.h" + + #if VK_KHR_video_decode_queue && VK_KHR_video_encode_queue + + #include "common/debug.h" + #include "common/io.h" + #include "common/library.h" + #include "common/mallocHelper.h" + +static FFCodecType ffCodecDecodeOperationsToTypes(VkVideoCodecOperationFlagsKHR operations) { + FFCodecType types = FF_CODEC_TYPE_NONE; + + if (operations & 0x00000001 /*VK_VIDEO_CODEC_OPERATION_DECODE_H264_BIT_KHR*/) { + types |= FF_CODEC_TYPE_H264; + } + if (operations & 0x00000002 /*VK_VIDEO_CODEC_OPERATION_DECODE_H265_BIT_KHR*/) { + types |= FF_CODEC_TYPE_HEVC; + } + if (operations & 0x00000004 /*VK_VIDEO_CODEC_OPERATION_DECODE_AV1_BIT_KHR*/) { + types |= FF_CODEC_TYPE_AV1; + } + if (operations & 0x00000008 /*VK_VIDEO_CODEC_OPERATION_DECODE_VP9_BIT_KHR*/) { + types |= FF_CODEC_TYPE_VP9; + } + + return types; +} + +static FFCodecType ffCodecEncodeOperationsToTypes(VkVideoCodecOperationFlagsKHR operations) { + FFCodecType types = FF_CODEC_TYPE_NONE; + + if (operations & 0x00010000 /*VK_VIDEO_CODEC_OPERATION_ENCODE_H264_BIT_KHR*/) { + types |= FF_CODEC_TYPE_H264; + } + if (operations & 0x00020000 /*VK_VIDEO_CODEC_OPERATION_ENCODE_H265_BIT_KHR*/) { + types |= FF_CODEC_TYPE_HEVC; + } + if (operations & 0x00040000 /*VK_VIDEO_CODEC_OPERATION_ENCODE_AV1_BIT_KHR*/) { + types |= FF_CODEC_TYPE_AV1; + } + + return types; +} + +static bool ffCodecHasDeviceExtension(const VkExtensionProperties* extensions, uint32_t extensionCount, const char* extensionName) { + for (uint32_t i = 0; i < extensionCount; ++i) { + if (ffStrEquals(extensions[i].extensionName, extensionName)) { + return true; + } + } + + return false; +} + +const char* ffDetectCodecVulkan(FFCodecOptions* options, FFlist* result /*list of FFCodecResult*/) { + FF_DEBUG("Starting Vulkan codec 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) + FF_LIBRARY_LOAD_SYMBOL_MESSAGE(vulkan, vkEnumerateDeviceExtensionProperties) + + FF_SUPPRESS_IO(); + + uint32_t apiVersion = VK_API_VERSION_1_0; + PFN_vkEnumerateInstanceVersion ffvkEnumerateInstanceVersion = (PFN_vkEnumerateInstanceVersion) ffvkGetInstanceProcAddr(NULL, "vkEnumerateInstanceVersion"); + if (ffvkEnumerateInstanceVersion != NULL) { + uint32_t detectedApiVersion = 0; + if (ffvkEnumerateInstanceVersion(&detectedApiVersion) == VK_SUCCESS) { + apiVersion = detectedApiVersion; + } + } + + const uint32_t projectVersion = VK_MAKE_VERSION( + FASTFETCH_PROJECT_VERSION_MAJOR, + FASTFETCH_PROJECT_VERSION_MINOR, + FASTFETCH_PROJECT_VERSION_PATCH); + + VkInstance vkInstance = VK_NULL_HANDLE; + VkResult res = ffvkCreateInstance(&(VkInstanceCreateInfo){ + .sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO, + .pApplicationInfo = &(VkApplicationInfo){ + .sType = VK_STRUCTURE_TYPE_APPLICATION_INFO, + .pApplicationName = FASTFETCH_PROJECT_NAME, + .applicationVersion = projectVersion, + .pEngineName = "fastfetch-codec-vulkan", + .engineVersion = projectVersion, + .apiVersion = apiVersion, + }, + }, + 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"; + } + } + + PFN_vkGetPhysicalDeviceProperties ffvkGetPhysicalDeviceProperties = (PFN_vkGetPhysicalDeviceProperties) ffvkGetInstanceProcAddr(vkInstance, "vkGetPhysicalDeviceProperties"); + if (!ffvkGetPhysicalDeviceProperties) { + ffvkDestroyInstance(vkInstance, NULL); + return "vkGetPhysicalDeviceProperties is not available"; + } + + PFN_vkGetPhysicalDeviceQueueFamilyProperties2 ffvkGetPhysicalDeviceQueueFamilyProperties2 = (PFN_vkGetPhysicalDeviceQueueFamilyProperties2) ffvkGetInstanceProcAddr(vkInstance, "vkGetPhysicalDeviceQueueFamilyProperties2"); + if (!ffvkGetPhysicalDeviceQueueFamilyProperties2) { + ffvkDestroyInstance(vkInstance, NULL); + return "vkGetPhysicalDeviceQueueFamilyProperties2 is not available"; + } + + uint32_t physicalDeviceCount = 0; + res = ffvkEnumeratePhysicalDevices(vkInstance, &physicalDeviceCount, NULL); + if (res != VK_SUCCESS) { + FF_DEBUG("ffvkEnumeratePhysicalDevices(count) failed with VkResult=%d", res); + ffvkDestroyInstance(vkInstance, NULL); + return "ffvkEnumeratePhysicalDevices() failed during Vulkan codec detection"; + } + if (physicalDeviceCount == 0) { + ffvkDestroyInstance(vkInstance, NULL); + return "No Vulkan physical devices found"; + } + + FF_AUTO_FREE VkPhysicalDevice* physicalDevices = (VkPhysicalDevice*) malloc(sizeof(VkPhysicalDevice) * (size_t) physicalDeviceCount); + + res = ffvkEnumeratePhysicalDevices(vkInstance, &physicalDeviceCount, physicalDevices); + if (res != VK_SUCCESS) { + FF_DEBUG("ffvkEnumeratePhysicalDevices(list) failed with VkResult=%d", res); + ffvkDestroyInstance(vkInstance, NULL); + return "ffvkEnumeratePhysicalDevices() failed during Vulkan codec detection"; + } + + bool sawVideoQueueExtension = false; + + for (uint32_t i = 0; i < physicalDeviceCount; ++i) { + VkPhysicalDeviceProperties properties = {}; + ffvkGetPhysicalDeviceProperties(physicalDevices[i], &properties); + + uint32_t extensionCount = 0; + res = ffvkEnumerateDeviceExtensionProperties(physicalDevices[i], NULL, &extensionCount, NULL); + if (res != VK_SUCCESS) { + FF_DEBUG("vkEnumerateDeviceExtensionProperties(count) failed for '%s' with VkResult=%d", properties.deviceName, res); + continue; + } + + if (extensionCount == 0) { + FF_DEBUG("Skipping Vulkan device '%s' because it has no extensions", properties.deviceName); + continue; + } + + FF_AUTO_FREE VkExtensionProperties* extensions = (VkExtensionProperties*) malloc(sizeof(VkExtensionProperties) * (size_t) extensionCount); + + res = ffvkEnumerateDeviceExtensionProperties(physicalDevices[i], NULL, &extensionCount, extensions); + if (res != VK_SUCCESS) { + FF_DEBUG("vkEnumerateDeviceExtensionProperties(list) failed for '%s' with VkResult=%d", properties.deviceName, res); + continue; + } + + bool hasVideoDecode = (options->showType & FF_CODEC_SHOW_TYPE_DECODER) && + ffCodecHasDeviceExtension(extensions, extensionCount, VK_KHR_VIDEO_DECODE_QUEUE_EXTENSION_NAME); + bool hasVideoEncode = (options->showType & FF_CODEC_SHOW_TYPE_ENCODER) && + ffCodecHasDeviceExtension(extensions, extensionCount, VK_KHR_VIDEO_ENCODE_QUEUE_EXTENSION_NAME); + + if (!hasVideoDecode && !hasVideoEncode) { + FF_DEBUG("Skipping Vulkan device '%s' because it does not support video queue extensions", properties.deviceName); + continue; + } + sawVideoQueueExtension = true; + + uint32_t queueFamilyCount = 0; + ffvkGetPhysicalDeviceQueueFamilyProperties2(physicalDevices[i], &queueFamilyCount, NULL); + if (queueFamilyCount == 0) { + FF_DEBUG("Skipping Vulkan device '%s' because it has no queue families", properties.deviceName); + continue; + } + + FF_AUTO_FREE VkQueueFamilyProperties2* queueFamilyProperties = (VkQueueFamilyProperties2*) malloc(sizeof(VkQueueFamilyProperties2) * (size_t) queueFamilyCount); + FF_AUTO_FREE VkQueueFamilyVideoPropertiesKHR* queueFamilyVideoProperties = (VkQueueFamilyVideoPropertiesKHR*) malloc(sizeof(VkQueueFamilyVideoPropertiesKHR) * (size_t) queueFamilyCount); + + for (uint32_t queueIndex = 0; queueIndex < queueFamilyCount; ++queueIndex) { + queueFamilyVideoProperties[queueIndex] = (VkQueueFamilyVideoPropertiesKHR){ + .sType = VK_STRUCTURE_TYPE_QUEUE_FAMILY_VIDEO_PROPERTIES_KHR, + }; + queueFamilyProperties[queueIndex] = (VkQueueFamilyProperties2){ + .sType = VK_STRUCTURE_TYPE_QUEUE_FAMILY_PROPERTIES_2, + .pNext = &queueFamilyVideoProperties[queueIndex], + }; + } + + ffvkGetPhysicalDeviceQueueFamilyProperties2(physicalDevices[i], &queueFamilyCount, queueFamilyProperties); + + FFCodecType decoders = FF_CODEC_TYPE_NONE; + FFCodecType encoders = FF_CODEC_TYPE_NONE; + + for (uint32_t queueIndex = 0; queueIndex < queueFamilyCount; ++queueIndex) { + const VkQueueFlags queueFlags = queueFamilyProperties[queueIndex].queueFamilyProperties.queueFlags; + const VkVideoCodecOperationFlagsKHR operations = queueFamilyVideoProperties[queueIndex].videoCodecOperations; + + if (hasVideoDecode && (queueFlags & VK_QUEUE_VIDEO_DECODE_BIT_KHR)) { + decoders |= ffCodecDecodeOperationsToTypes(operations); + } + + if (hasVideoEncode && (queueFlags & VK_QUEUE_VIDEO_ENCODE_BIT_KHR)) { + encoders |= ffCodecEncodeOperationsToTypes(operations); + } + } + + if (decoders == FF_CODEC_TYPE_NONE && encoders == FF_CODEC_TYPE_NONE) { + FF_DEBUG("Skipping Vulkan device '%s' because no supported codec operations were reported", properties.deviceName); + continue; + } + + FFCodecResult* item = FF_LIST_ADD(FFCodecResult, *result); + ffStrbufInitS(&item->gpu, properties.deviceName); + item->decoders = decoders; + item->encoders = encoders; + item->platformApi = "Vulkan Video"; + FF_DEBUG("Added Vulkan codec result for '%s': decoders=%u encoders=%u", properties.deviceName, (unsigned) decoders, (unsigned) encoders); + } + + ffvkDestroyInstance(vkInstance, NULL); + + if (result->length > 0) { + return NULL; + } + + return sawVideoQueueExtension ? "No supported Vulkan video codec operations found" + : "VK_KHR_video_queue is not supported by any Vulkan physical device"; +} + + #else + +const char* ffDetectCodecVulkan(FFCodecOptions* options, FFlist* result) { + FF_UNUSED(options, result); + return "Vulkan video queue extensions are not supported by this Vulkan implementation"; +} + + #endif + +#endif |