From 3a54617196820e3301a7232c304bd8728864296b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9D=8E=E9=80=9A=E6=B4=B2?= Date: Sun, 31 May 2026 23:31:45 +0800 Subject: [PATCH] Codec: adds vulkan support --- CMakeLists.txt | 2 + doc/json_schema.json | 5 + src/detection/codec/codec.c | 14 ++ src/detection/codec/codec.h | 5 + src/detection/codec/codec_android.c | 4 +- src/detection/codec/codec_apple.c | 2 +- src/detection/codec/codec_linux.c | 6 +- src/detection/codec/codec_vulkan.c | 267 ++++++++++++++++++++++++++ src/detection/codec/codec_windows.cpp | 2 +- src/modules/codec/codec.c | 11 +- src/modules/codec/option.h | 1 + 11 files changed, 310 insertions(+), 9 deletions(-) create mode 100644 src/detection/codec/codec.c create mode 100644 src/detection/codec/codec_vulkan.c diff --git a/CMakeLists.txt b/CMakeLists.txt index 5917c9091..bfe774508 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -423,6 +423,8 @@ set(LIBFASTFETCH_SRC src/detection/bluetoothradio/bluetoothradio.c src/detection/bootmgr/bootmgr.c src/detection/chassis/chassis.c + src/detection/codec/codec.c + src/detection/codec/codec_vulkan.c src/detection/cpu/cpu.c src/detection/cpuusage/cpuusage.c src/detection/command/command.c diff --git a/doc/json_schema.json b/doc/json_schema.json index 18b52479a..505d27633 100644 --- a/doc/json_schema.json +++ b/doc/json_schema.json @@ -1909,6 +1909,11 @@ "type": "boolean", "default": false }, + "useVulkan": { + "description": "Whether to use Vulkan for codec detection instead of other platform-specific APIs", + "type": "boolean", + "default": false + }, "key": { "$ref": "#/$defs/key" }, diff --git a/src/detection/codec/codec.c b/src/detection/codec/codec.c new file mode 100644 index 000000000..cd64fad98 --- /dev/null +++ b/src/detection/codec/codec.c @@ -0,0 +1,14 @@ +#include "codec.h" + +const char* ffDetectCodec(FFCodecOptions* options, FFlist* result /*list of FFCodecResult*/) { + if (options->useVulkan) { +#ifdef FF_HAVE_VULKAN + return ffDetectCodecVulkan(result); +#else + FF_UNUSED(result); + return "Fastfetch was built without Vulkan support"; +#endif + } + + return ffDetectCodecNative(options, result); +} diff --git a/src/detection/codec/codec.h b/src/detection/codec/codec.h index 9377d8d34..d23c14f56 100644 --- a/src/detection/codec/codec.h +++ b/src/detection/codec/codec.h @@ -37,3 +37,8 @@ typedef struct FFCodecResult { } FFCodecResult; const char* ffDetectCodec(FFCodecOptions* options, FFlist* result /*list of FFCodecResult*/); +const char* ffDetectCodecNative(FFCodecOptions* options, FFlist* result /*list of FFCodecResult*/); + +#ifdef FF_HAVE_VULKAN +const char* ffDetectCodecVulkan(FFlist* result /*list of FFCodecResult*/); +#endif diff --git a/src/detection/codec/codec_android.c b/src/detection/codec/codec_android.c index 6b6b8df78..ab4b9f552 100644 --- a/src/detection/codec/codec_android.c +++ b/src/detection/codec/codec_android.c @@ -54,7 +54,7 @@ static bool ffCodecIsHardwareAccelerated( return isHardware; } -const char* ffDetectCodec(FF_A_UNUSED FFCodecOptions* options, FFlist* result /*list of FFCodecResult*/) { +const char* ffDetectCodecNative(FF_A_UNUSED FFCodecOptions* options, FFlist* result /*list of FFCodecResult*/) { FF_LIBRARY_LOAD_MESSAGE(mediandk, "libmediandk.so", 0) FF_LIBRARY_LOAD_SYMBOL_MESSAGE(mediandk, AMediaCodec_createDecoderByType) FF_LIBRARY_LOAD_SYMBOL_MESSAGE(mediandk, AMediaCodec_createEncoderByType) @@ -93,6 +93,6 @@ const char* ffDetectCodec(FF_A_UNUSED FFCodecOptions* options, FFlist* result /* item->encoders = encoders; item->platformApi = "AMediaCodec"; } - + return NULL; } diff --git a/src/detection/codec/codec_apple.c b/src/detection/codec/codec_apple.c index f657de78e..6a6097b9f 100644 --- a/src/detection/codec/codec_apple.c +++ b/src/detection/codec/codec_apple.c @@ -80,7 +80,7 @@ static FFCodecType ffCodecDetectDecoders() { return types; } -const char* ffDetectCodec(FF_A_UNUSED FFCodecOptions* options, FFlist* result /* list of FFCodecResult */) { +const char* ffDetectCodecNative(FF_A_UNUSED FFCodecOptions* options, FFlist* result /* list of FFCodecResult */) { FFCodecType decoders = ffCodecDetectDecoders(); FFCodecType encoders = ffCodecDetectEncoders(); diff --git a/src/detection/codec/codec_linux.c b/src/detection/codec/codec_linux.c index b103e5334..0e00c4646 100644 --- a/src/detection/codec/codec_linux.c +++ b/src/detection/codec/codec_linux.c @@ -12,10 +12,10 @@ #include "common/library.h" #include "common/mallocHelper.h" + #include "common/io.h" #if FF_CODEC_HAVE_VA #include "detection/gpu/gpu.h" - #include "common/io.h" #include #include @@ -529,7 +529,7 @@ static const char* ffDetectCodecByVdpau(FFlist* result) { } #endif -const char* ffDetectCodec(FF_A_UNUSED FFCodecOptions* options, FFlist* result /* list of FFCodecResult */) { +const char* ffDetectCodecNative(FF_A_UNUSED FFCodecOptions* options, FFlist* result /* list of FFCodecResult */) { FF_SUPPRESS_IO(); #if FF_CODEC_HAVE_VA @@ -549,7 +549,7 @@ const char* ffDetectCodec(FF_A_UNUSED FFCodecOptions* options, FFlist* result /* #else -const char* ffDetectCodec(FFCodecOptions* options, FFlist* result /* list of FFCodecResult */) { +const char* ffDetectCodecNative(FFCodecOptions* options, FFlist* result /* list of FFCodecResult */) { FF_UNUSED(options, result); return "Fastfetch was built without DRM / VA-API / VDPAU headers"; } diff --git a/src/detection/codec/codec_vulkan.c b/src/detection/codec/codec_vulkan.c new file mode 100644 index 000000000..d15098a42 --- /dev/null +++ b/src/detection/codec/codec_vulkan.c @@ -0,0 +1,267 @@ +#include "codec.h" + +#ifdef FF_HAVE_VULKAN + + #include "common/debug.h" + #include "common/io.h" + #include "common/library.h" + #include "common/mallocHelper.h" + + #include + +static FFCodecType ffCodecDecodeOperationsToTypes(VkVideoCodecOperationFlagsKHR operations) { + FFCodecType types = FF_CODEC_TYPE_NONE; + + if (operations & VK_VIDEO_CODEC_OPERATION_DECODE_H264_BIT_KHR) { + types |= FF_CODEC_TYPE_H264; + } + if (operations & VK_VIDEO_CODEC_OPERATION_DECODE_H265_BIT_KHR) { + types |= FF_CODEC_TYPE_HEVC; + } + if (operations & 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 & VK_VIDEO_CODEC_OPERATION_ENCODE_H264_BIT_KHR) { + types |= FF_CODEC_TYPE_H264; + } + if (operations & VK_VIDEO_CODEC_OPERATION_ENCODE_H265_BIT_KHR) { + types |= FF_CODEC_TYPE_HEVC; + } + if (operations & 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 (strcmp(extensions[i].extensionName, extensionName) == 0) { + return true; + } + } + + return false; +} + +const char* ffDetectCodecVulkan(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); + if (!physicalDevices) { + ffvkDestroyInstance(vkInstance, NULL); + return "malloc() failed"; + } + + 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; + } + + FF_AUTO_FREE VkExtensionProperties* extensions = NULL; + if (extensionCount > 0) { + extensions = (VkExtensionProperties*) malloc(sizeof(VkExtensionProperties) * (size_t) extensionCount); + if (!extensions) { + ffvkDestroyInstance(vkInstance, NULL); + return "malloc() failed"; + } + + 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 = ffCodecHasDeviceExtension(extensions, extensionCount, VK_KHR_VIDEO_DECODE_QUEUE_EXTENSION_NAME); + bool hasVideoEncode = 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); + if (!queueFamilyProperties || !queueFamilyVideoProperties) { + ffvkDestroyInstance(vkInstance, NULL); + return "malloc() failed"; + } + + 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"; +} + +#endif diff --git a/src/detection/codec/codec_windows.cpp b/src/detection/codec/codec_windows.cpp index b4dd83210..7c7c37b30 100644 --- a/src/detection/codec/codec_windows.cpp +++ b/src/detection/codec/codec_windows.cpp @@ -422,7 +422,7 @@ const char* detectD3d12va(FFlist* result /*list of FFCodecResult*/, IDXGIFactory return nullptr; } -const char* ffDetectCodec(FF_A_UNUSED FFCodecOptions* options, FFlist* result /*list of FFCodecResult*/) { +const char* ffDetectCodecNative(FF_A_UNUSED FFCodecOptions* options, FFlist* result /*list of FFCodecResult*/) { FF_LIBRARY_LOAD_MESSAGE(dxgi, "dxgi" FF_LIBRARY_EXTENSION, 1) FF_LIBRARY_LOAD_SYMBOL_MESSAGE(dxgi, CreateDXGIFactory1) diff --git a/src/modules/codec/codec.c b/src/modules/codec/codec.c index 91a703bbc..d8a267f74 100644 --- a/src/modules/codec/codec.c +++ b/src/modules/codec/codec.c @@ -154,6 +154,11 @@ void ffParseCodecJsonObject(FFCodecOptions* options, yyjson_val* module) { continue; } + if (unsafe_yyjson_equals_str(key, "useVulkan")) { + options->useVulkan = yyjson_get_bool(val); + continue; + } + ffPrintError(FF_CODEC_MODULE_NAME, 0, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT, "Unknown JSON key %s", unsafe_yyjson_get_str(key)); } } @@ -161,11 +166,12 @@ void ffParseCodecJsonObject(FFCodecOptions* options, yyjson_val* module) { void ffGenerateCodecJsonConfig(FFCodecOptions* options, yyjson_mut_doc* doc, yyjson_mut_val* module) { ffJsonConfigGenerateModuleArgsConfig(doc, module, &options->moduleArgs); yyjson_mut_obj_add_bool(doc, module, "splitGPU", options->splitGPU); + yyjson_mut_obj_add_bool(doc, module, "useVulkan", options->useVulkan); } -bool ffGenerateCodecJsonResult(FF_A_UNUSED FFCodecOptions* options, yyjson_mut_doc* doc, yyjson_mut_val* module) { +bool ffGenerateCodecJsonResult(FFCodecOptions* options, yyjson_mut_doc* doc, yyjson_mut_val* module) { FF_LIST_AUTO_DESTROY result = ffListCreate(); - const char* error = ffDetectCodec(NULL, &result); + const char* error = ffDetectCodec(options, &result); if (error) { yyjson_mut_obj_add_str(doc, module, "error", error); @@ -201,6 +207,7 @@ bool ffGenerateCodecJsonResult(FF_A_UNUSED FFCodecOptions* options, yyjson_mut_d void ffInitCodecOptions(FFCodecOptions* options) { ffOptionInitModuleArg(&options->moduleArgs, "󰈫"); options->splitGPU = false; + options->useVulkan = false; } void ffDestroyCodecOptions(FFCodecOptions* options) { diff --git a/src/modules/codec/option.h b/src/modules/codec/option.h index fb03d5b2a..f4db5c481 100644 --- a/src/modules/codec/option.h +++ b/src/modules/codec/option.h @@ -5,6 +5,7 @@ typedef struct FFCodecOptions { FFModuleArgs moduleArgs; bool splitGPU; + bool useVulkan; } FFCodecOptions; static_assert(sizeof(FFCodecOptions) <= FF_OPTION_MAX_SIZE, "FFCodecOptions size exceeds maximum allowed size");