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