Files
fastfetch/src/detection/codec/codec_vulkan.c
T
2026-05-31 23:45:20 +08:00

268 lines
11 KiB
C

#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