Codec: adds vulkan support

This commit is contained in:
李通洲
2026-05-31 23:31:45 +08:00
parent 3e3a03a233
commit 3a54617196
11 changed files with 310 additions and 9 deletions
+2
View File
@@ -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
+5
View File
@@ -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"
},
+14
View File
@@ -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);
}
+5
View File
@@ -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
+2 -2
View File
@@ -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;
}
+1 -1
View File
@@ -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();
+3 -3
View File
@@ -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 <fcntl.h>
#include <va/va.h>
@@ -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";
}
+267
View File
@@ -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 <vulkan/vulkan.h>
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
+1 -1
View File
@@ -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)
+9 -2
View File
@@ -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) {
+1
View File
@@ -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");