diff options
author | 3gg <3gg@shellblade.net> | 2025-08-30 16:53:58 -0700 |
---|---|---|
committer | 3gg <3gg@shellblade.net> | 2025-08-30 16:53:58 -0700 |
commit | 6aaedb813fa11ba0679c3051bc2eb28646b9506c (patch) | |
tree | 34acbfc9840e02cb4753e6306ea7ce978bf8b58e /src/contrib/SDL-3.2.20/test/testffmpeg_vulkan.c | |
parent | 8f228ade99dd3d4c8da9b78ade1815c9adf85c8f (diff) |
Update to SDL3
Diffstat (limited to 'src/contrib/SDL-3.2.20/test/testffmpeg_vulkan.c')
-rw-r--r-- | src/contrib/SDL-3.2.20/test/testffmpeg_vulkan.c | 1018 |
1 files changed, 1018 insertions, 0 deletions
diff --git a/src/contrib/SDL-3.2.20/test/testffmpeg_vulkan.c b/src/contrib/SDL-3.2.20/test/testffmpeg_vulkan.c new file mode 100644 index 0000000..a6da6cb --- /dev/null +++ b/src/contrib/SDL-3.2.20/test/testffmpeg_vulkan.c | |||
@@ -0,0 +1,1018 @@ | |||
1 | /* | ||
2 | Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org> | ||
3 | |||
4 | This software is provided 'as-is', without any express or implied | ||
5 | warranty. In no event will the authors be held liable for any damages | ||
6 | arising from the use of this software. | ||
7 | |||
8 | Permission is granted to anyone to use this software for any purpose, | ||
9 | including commercial applications, and to alter it and redistribute it | ||
10 | freely. | ||
11 | */ | ||
12 | #include <SDL3/SDL.h> | ||
13 | #include <SDL3/SDL_vulkan.h> | ||
14 | |||
15 | #include "testffmpeg_vulkan.h" | ||
16 | |||
17 | #ifdef FFMPEG_VULKAN_SUPPORT | ||
18 | |||
19 | #define VULKAN_FUNCTIONS() \ | ||
20 | VULKAN_GLOBAL_FUNCTION(vkCreateInstance) \ | ||
21 | VULKAN_GLOBAL_FUNCTION(vkEnumerateInstanceExtensionProperties) \ | ||
22 | VULKAN_GLOBAL_FUNCTION(vkEnumerateInstanceLayerProperties) \ | ||
23 | VULKAN_INSTANCE_FUNCTION(vkCreateDevice) \ | ||
24 | VULKAN_INSTANCE_FUNCTION(vkDestroyInstance) \ | ||
25 | VULKAN_INSTANCE_FUNCTION(vkDestroySurfaceKHR) \ | ||
26 | VULKAN_INSTANCE_FUNCTION(vkEnumerateDeviceExtensionProperties) \ | ||
27 | VULKAN_INSTANCE_FUNCTION(vkEnumeratePhysicalDevices) \ | ||
28 | VULKAN_INSTANCE_FUNCTION(vkGetDeviceProcAddr) \ | ||
29 | VULKAN_INSTANCE_FUNCTION(vkGetPhysicalDeviceFeatures2) \ | ||
30 | VULKAN_INSTANCE_FUNCTION(vkGetPhysicalDeviceQueueFamilyProperties) \ | ||
31 | VULKAN_INSTANCE_FUNCTION(vkGetPhysicalDeviceSurfaceSupportKHR) \ | ||
32 | VULKAN_INSTANCE_FUNCTION(vkQueueWaitIdle) \ | ||
33 | VULKAN_DEVICE_FUNCTION(vkAllocateCommandBuffers) \ | ||
34 | VULKAN_DEVICE_FUNCTION(vkBeginCommandBuffer) \ | ||
35 | VULKAN_DEVICE_FUNCTION(vkCmdPipelineBarrier2) \ | ||
36 | VULKAN_DEVICE_FUNCTION(vkCreateCommandPool) \ | ||
37 | VULKAN_DEVICE_FUNCTION(vkCreateSemaphore) \ | ||
38 | VULKAN_DEVICE_FUNCTION(vkDestroyCommandPool) \ | ||
39 | VULKAN_DEVICE_FUNCTION(vkDestroyDevice) \ | ||
40 | VULKAN_DEVICE_FUNCTION(vkDestroySemaphore) \ | ||
41 | VULKAN_DEVICE_FUNCTION(vkDeviceWaitIdle) \ | ||
42 | VULKAN_DEVICE_FUNCTION(vkEndCommandBuffer) \ | ||
43 | VULKAN_DEVICE_FUNCTION(vkFreeCommandBuffers) \ | ||
44 | VULKAN_DEVICE_FUNCTION(vkGetDeviceQueue) \ | ||
45 | VULKAN_DEVICE_FUNCTION(vkQueueSubmit) \ | ||
46 | \ | ||
47 | VULKAN_INSTANCE_FUNCTION(vkGetPhysicalDeviceVideoFormatPropertiesKHR) \ | ||
48 | |||
49 | typedef struct | ||
50 | { | ||
51 | VkPhysicalDeviceFeatures2 device_features; | ||
52 | VkPhysicalDeviceVulkan11Features device_features_1_1; | ||
53 | VkPhysicalDeviceVulkan12Features device_features_1_2; | ||
54 | VkPhysicalDeviceVulkan13Features device_features_1_3; | ||
55 | VkPhysicalDeviceDescriptorBufferFeaturesEXT desc_buf_features; | ||
56 | VkPhysicalDeviceShaderAtomicFloatFeaturesEXT atomic_float_features; | ||
57 | VkPhysicalDeviceCooperativeMatrixFeaturesKHR coop_matrix_features; | ||
58 | } VulkanDeviceFeatures; | ||
59 | |||
60 | struct VulkanVideoContext | ||
61 | { | ||
62 | VkInstance instance; | ||
63 | VkSurfaceKHR surface; | ||
64 | VkPhysicalDevice physicalDevice; | ||
65 | int presentQueueFamilyIndex; | ||
66 | int presentQueueCount; | ||
67 | int graphicsQueueFamilyIndex; | ||
68 | int graphicsQueueCount; | ||
69 | int transferQueueFamilyIndex; | ||
70 | int transferQueueCount; | ||
71 | int computeQueueFamilyIndex; | ||
72 | int computeQueueCount; | ||
73 | int decodeQueueFamilyIndex; | ||
74 | int decodeQueueCount; | ||
75 | VkDevice device; | ||
76 | VkQueue graphicsQueue; | ||
77 | VkCommandPool commandPool; | ||
78 | VkCommandBuffer *commandBuffers; | ||
79 | uint32_t commandBufferCount; | ||
80 | uint32_t commandBufferIndex; | ||
81 | VkSemaphore *waitSemaphores; | ||
82 | uint32_t waitSemaphoreCount; | ||
83 | VkSemaphore *signalSemaphores; | ||
84 | uint32_t signalSemaphoreCount; | ||
85 | |||
86 | const char **instanceExtensions; | ||
87 | int instanceExtensionsCount; | ||
88 | |||
89 | const char **deviceExtensions; | ||
90 | int deviceExtensionsCount; | ||
91 | |||
92 | VulkanDeviceFeatures features; | ||
93 | |||
94 | PFN_vkGetInstanceProcAddr vkGetInstanceProcAddr; | ||
95 | #define VULKAN_GLOBAL_FUNCTION(name) PFN_##name name; | ||
96 | #define VULKAN_INSTANCE_FUNCTION(name) PFN_##name name; | ||
97 | #define VULKAN_DEVICE_FUNCTION(name) PFN_##name name; | ||
98 | VULKAN_FUNCTIONS() | ||
99 | #undef VULKAN_GLOBAL_FUNCTION | ||
100 | #undef VULKAN_INSTANCE_FUNCTION | ||
101 | #undef VULKAN_DEVICE_FUNCTION | ||
102 | }; | ||
103 | |||
104 | |||
105 | static int loadGlobalFunctions(VulkanVideoContext *context) | ||
106 | { | ||
107 | context->vkGetInstanceProcAddr = (PFN_vkGetInstanceProcAddr)SDL_Vulkan_GetVkGetInstanceProcAddr(); | ||
108 | if (!context->vkGetInstanceProcAddr) { | ||
109 | return -1; | ||
110 | } | ||
111 | |||
112 | #define VULKAN_GLOBAL_FUNCTION(name) \ | ||
113 | context->name = (PFN_##name)context->vkGetInstanceProcAddr(VK_NULL_HANDLE, #name); \ | ||
114 | if (!context->name) { \ | ||
115 | return SDL_SetError("vkGetInstanceProcAddr(VK_NULL_HANDLE, \"" #name "\") failed"); \ | ||
116 | } | ||
117 | #define VULKAN_INSTANCE_FUNCTION(name) | ||
118 | #define VULKAN_DEVICE_FUNCTION(name) | ||
119 | VULKAN_FUNCTIONS() | ||
120 | #undef VULKAN_GLOBAL_FUNCTION | ||
121 | #undef VULKAN_INSTANCE_FUNCTION | ||
122 | #undef VULKAN_DEVICE_FUNCTION | ||
123 | return 0; | ||
124 | } | ||
125 | |||
126 | static int loadInstanceFunctions(VulkanVideoContext *context) | ||
127 | { | ||
128 | #define VULKAN_GLOBAL_FUNCTION(name) | ||
129 | #define VULKAN_INSTANCE_FUNCTION(name) \ | ||
130 | context->name = (PFN_##name)context->vkGetInstanceProcAddr(context->instance, #name); \ | ||
131 | if (!context->name) { \ | ||
132 | return SDL_SetError("vkGetInstanceProcAddr(instance, \"" #name "\") failed"); \ | ||
133 | } | ||
134 | #define VULKAN_DEVICE_FUNCTION(name) | ||
135 | VULKAN_FUNCTIONS() | ||
136 | #undef VULKAN_GLOBAL_FUNCTION | ||
137 | #undef VULKAN_INSTANCE_FUNCTION | ||
138 | #undef VULKAN_DEVICE_FUNCTION | ||
139 | return 0; | ||
140 | } | ||
141 | |||
142 | static int loadDeviceFunctions(VulkanVideoContext *context) | ||
143 | { | ||
144 | #define VULKAN_GLOBAL_FUNCTION(name) | ||
145 | #define VULKAN_INSTANCE_FUNCTION(name) | ||
146 | #define VULKAN_DEVICE_FUNCTION(name) \ | ||
147 | context->name = (PFN_##name)context->vkGetDeviceProcAddr(context->device, #name); \ | ||
148 | if (!context->name) { \ | ||
149 | return SDL_SetError("vkGetDeviceProcAddr(device, \"" #name "\") failed"); \ | ||
150 | } | ||
151 | VULKAN_FUNCTIONS() | ||
152 | #undef VULKAN_GLOBAL_FUNCTION | ||
153 | #undef VULKAN_INSTANCE_FUNCTION | ||
154 | #undef VULKAN_DEVICE_FUNCTION | ||
155 | return 0; | ||
156 | } | ||
157 | |||
158 | #undef VULKAN_FUNCTIONS | ||
159 | |||
160 | static const char *getVulkanResultString(VkResult result) | ||
161 | { | ||
162 | switch ((int)result) { | ||
163 | #define RESULT_CASE(x) \ | ||
164 | case x: \ | ||
165 | return #x | ||
166 | RESULT_CASE(VK_SUCCESS); | ||
167 | RESULT_CASE(VK_NOT_READY); | ||
168 | RESULT_CASE(VK_TIMEOUT); | ||
169 | RESULT_CASE(VK_EVENT_SET); | ||
170 | RESULT_CASE(VK_EVENT_RESET); | ||
171 | RESULT_CASE(VK_INCOMPLETE); | ||
172 | RESULT_CASE(VK_ERROR_OUT_OF_HOST_MEMORY); | ||
173 | RESULT_CASE(VK_ERROR_OUT_OF_DEVICE_MEMORY); | ||
174 | RESULT_CASE(VK_ERROR_INITIALIZATION_FAILED); | ||
175 | RESULT_CASE(VK_ERROR_DEVICE_LOST); | ||
176 | RESULT_CASE(VK_ERROR_MEMORY_MAP_FAILED); | ||
177 | RESULT_CASE(VK_ERROR_LAYER_NOT_PRESENT); | ||
178 | RESULT_CASE(VK_ERROR_EXTENSION_NOT_PRESENT); | ||
179 | RESULT_CASE(VK_ERROR_FEATURE_NOT_PRESENT); | ||
180 | RESULT_CASE(VK_ERROR_INCOMPATIBLE_DRIVER); | ||
181 | RESULT_CASE(VK_ERROR_TOO_MANY_OBJECTS); | ||
182 | RESULT_CASE(VK_ERROR_FORMAT_NOT_SUPPORTED); | ||
183 | RESULT_CASE(VK_ERROR_FRAGMENTED_POOL); | ||
184 | RESULT_CASE(VK_ERROR_SURFACE_LOST_KHR); | ||
185 | RESULT_CASE(VK_ERROR_NATIVE_WINDOW_IN_USE_KHR); | ||
186 | RESULT_CASE(VK_SUBOPTIMAL_KHR); | ||
187 | RESULT_CASE(VK_ERROR_OUT_OF_DATE_KHR); | ||
188 | RESULT_CASE(VK_ERROR_INCOMPATIBLE_DISPLAY_KHR); | ||
189 | RESULT_CASE(VK_ERROR_VALIDATION_FAILED_EXT); | ||
190 | RESULT_CASE(VK_ERROR_OUT_OF_POOL_MEMORY_KHR); | ||
191 | RESULT_CASE(VK_ERROR_INVALID_SHADER_NV); | ||
192 | #undef RESULT_CASE | ||
193 | default: | ||
194 | break; | ||
195 | } | ||
196 | return (result < 0) ? "VK_ERROR_<Unknown>" : "VK_<Unknown>"; | ||
197 | } | ||
198 | |||
199 | static int createInstance(VulkanVideoContext *context) | ||
200 | { | ||
201 | static const char *optional_extensions[] = { | ||
202 | VK_EXT_SWAPCHAIN_COLOR_SPACE_EXTENSION_NAME, | ||
203 | VK_KHR_GET_PHYSICAL_DEVICE_PROPERTIES_2_EXTENSION_NAME | ||
204 | }; | ||
205 | VkApplicationInfo appInfo = { 0 }; | ||
206 | VkInstanceCreateInfo instanceCreateInfo = { 0 }; | ||
207 | VkResult result; | ||
208 | char const *const *instanceExtensions = SDL_Vulkan_GetInstanceExtensions(&instanceCreateInfo.enabledExtensionCount); | ||
209 | |||
210 | appInfo.sType = VK_STRUCTURE_TYPE_APPLICATION_INFO; | ||
211 | appInfo.apiVersion = VK_API_VERSION_1_3; | ||
212 | instanceCreateInfo.sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO; | ||
213 | instanceCreateInfo.pApplicationInfo = &appInfo; | ||
214 | |||
215 | const char **instanceExtensionsCopy = SDL_calloc(instanceCreateInfo.enabledExtensionCount + SDL_arraysize(optional_extensions), sizeof(const char *)); | ||
216 | for (uint32_t i = 0; i < instanceCreateInfo.enabledExtensionCount; i++) { | ||
217 | instanceExtensionsCopy[i] = instanceExtensions[i]; | ||
218 | } | ||
219 | |||
220 | // Get the rest of the optional extensions | ||
221 | { | ||
222 | uint32_t extensionCount; | ||
223 | if (context->vkEnumerateInstanceExtensionProperties(NULL, &extensionCount, NULL) == VK_SUCCESS && extensionCount > 0) { | ||
224 | VkExtensionProperties *extensionProperties = SDL_calloc(extensionCount, sizeof(VkExtensionProperties)); | ||
225 | if (context->vkEnumerateInstanceExtensionProperties(NULL, &extensionCount, extensionProperties) == VK_SUCCESS) { | ||
226 | for (uint32_t i = 0; i < SDL_arraysize(optional_extensions); ++i) { | ||
227 | for (uint32_t j = 0; j < extensionCount; ++j) { | ||
228 | if (SDL_strcmp(extensionProperties[j].extensionName, optional_extensions[i]) == 0) { | ||
229 | instanceExtensionsCopy[instanceCreateInfo.enabledExtensionCount++] = optional_extensions[i]; | ||
230 | break; | ||
231 | } | ||
232 | } | ||
233 | } | ||
234 | } | ||
235 | SDL_free(extensionProperties); | ||
236 | } | ||
237 | } | ||
238 | instanceCreateInfo.ppEnabledExtensionNames = instanceExtensionsCopy; | ||
239 | |||
240 | context->instanceExtensions = instanceExtensionsCopy; | ||
241 | context->instanceExtensionsCount = instanceCreateInfo.enabledExtensionCount; | ||
242 | |||
243 | result = context->vkCreateInstance(&instanceCreateInfo, NULL, &context->instance); | ||
244 | if (result != VK_SUCCESS) { | ||
245 | context->instance = VK_NULL_HANDLE; | ||
246 | return SDL_SetError("vkCreateInstance(): %s", getVulkanResultString(result)); | ||
247 | } | ||
248 | if (loadInstanceFunctions(context) < 0) { | ||
249 | return -1; | ||
250 | } | ||
251 | return 0; | ||
252 | } | ||
253 | |||
254 | static int createSurface(VulkanVideoContext *context, SDL_Window *window) | ||
255 | { | ||
256 | if (!SDL_Vulkan_CreateSurface(window, context->instance, NULL, &context->surface)) { | ||
257 | context->surface = VK_NULL_HANDLE; | ||
258 | return -1; | ||
259 | } | ||
260 | return 0; | ||
261 | } | ||
262 | |||
263 | // Use the same queue scoring algorithm as ffmpeg to make sure we get the same device configuration | ||
264 | static int selectQueueFamily(VkQueueFamilyProperties *queueFamiliesProperties, uint32_t queueFamiliesCount, VkQueueFlagBits flags, int *queueCount) | ||
265 | { | ||
266 | uint32_t queueFamilyIndex; | ||
267 | uint32_t selectedQueueFamilyIndex = queueFamiliesCount; | ||
268 | uint32_t min_score = ~0u; | ||
269 | |||
270 | for (queueFamilyIndex = 0; queueFamilyIndex < queueFamiliesCount; ++queueFamilyIndex) { | ||
271 | VkQueueFlagBits current_flags = queueFamiliesProperties[queueFamilyIndex].queueFlags; | ||
272 | if (current_flags & flags) { | ||
273 | uint32_t score = av_popcount(current_flags) + queueFamiliesProperties[queueFamilyIndex].timestampValidBits; | ||
274 | if (score < min_score) { | ||
275 | selectedQueueFamilyIndex = queueFamilyIndex; | ||
276 | min_score = score; | ||
277 | } | ||
278 | } | ||
279 | } | ||
280 | |||
281 | if (selectedQueueFamilyIndex != queueFamiliesCount) { | ||
282 | VkQueueFamilyProperties *selectedQueueFamily = &queueFamiliesProperties[selectedQueueFamilyIndex]; | ||
283 | *queueCount = (int)selectedQueueFamily->queueCount; | ||
284 | ++selectedQueueFamily->timestampValidBits; | ||
285 | return (int)selectedQueueFamilyIndex; | ||
286 | } else { | ||
287 | *queueCount = 0; | ||
288 | return -1; | ||
289 | } | ||
290 | } | ||
291 | |||
292 | static int findPhysicalDevice(VulkanVideoContext *context) | ||
293 | { | ||
294 | uint32_t physicalDeviceCount = 0; | ||
295 | VkPhysicalDevice *physicalDevices; | ||
296 | VkQueueFamilyProperties *queueFamiliesProperties = NULL; | ||
297 | uint32_t queueFamiliesPropertiesAllocatedSize = 0; | ||
298 | VkExtensionProperties *deviceExtensions = NULL; | ||
299 | uint32_t deviceExtensionsAllocatedSize = 0; | ||
300 | uint32_t physicalDeviceIndex; | ||
301 | VkResult result; | ||
302 | |||
303 | result = context->vkEnumeratePhysicalDevices(context->instance, &physicalDeviceCount, NULL); | ||
304 | if (result != VK_SUCCESS) { | ||
305 | return SDL_SetError("vkEnumeratePhysicalDevices(): %s", getVulkanResultString(result)); | ||
306 | } | ||
307 | if (physicalDeviceCount == 0) { | ||
308 | return SDL_SetError("vkEnumeratePhysicalDevices(): no physical devices"); | ||
309 | } | ||
310 | physicalDevices = (VkPhysicalDevice *)SDL_malloc(sizeof(VkPhysicalDevice) * physicalDeviceCount); | ||
311 | if (!physicalDevices) { | ||
312 | return -1; | ||
313 | } | ||
314 | result = context->vkEnumeratePhysicalDevices(context->instance, &physicalDeviceCount, physicalDevices); | ||
315 | if (result != VK_SUCCESS) { | ||
316 | SDL_free(physicalDevices); | ||
317 | return SDL_SetError("vkEnumeratePhysicalDevices(): %s", getVulkanResultString(result)); | ||
318 | } | ||
319 | context->physicalDevice = NULL; | ||
320 | for (physicalDeviceIndex = 0; physicalDeviceIndex < physicalDeviceCount; physicalDeviceIndex++) { | ||
321 | uint32_t queueFamiliesCount = 0; | ||
322 | uint32_t queueFamilyIndex; | ||
323 | uint32_t deviceExtensionCount = 0; | ||
324 | bool hasSwapchainExtension = false; | ||
325 | uint32_t i; | ||
326 | |||
327 | VkPhysicalDevice physicalDevice = physicalDevices[physicalDeviceIndex]; | ||
328 | context->vkGetPhysicalDeviceQueueFamilyProperties(physicalDevice, &queueFamiliesCount, NULL); | ||
329 | if (queueFamiliesCount == 0) { | ||
330 | continue; | ||
331 | } | ||
332 | if (queueFamiliesPropertiesAllocatedSize < queueFamiliesCount) { | ||
333 | SDL_free(queueFamiliesProperties); | ||
334 | queueFamiliesPropertiesAllocatedSize = queueFamiliesCount; | ||
335 | queueFamiliesProperties = (VkQueueFamilyProperties *)SDL_malloc(sizeof(VkQueueFamilyProperties) * queueFamiliesPropertiesAllocatedSize); | ||
336 | if (!queueFamiliesProperties) { | ||
337 | SDL_free(physicalDevices); | ||
338 | SDL_free(deviceExtensions); | ||
339 | return -1; | ||
340 | } | ||
341 | } | ||
342 | context->vkGetPhysicalDeviceQueueFamilyProperties(physicalDevice, &queueFamiliesCount, queueFamiliesProperties); | ||
343 | |||
344 | // Initialize timestampValidBits for scoring in selectQueueFamily | ||
345 | for (queueFamilyIndex = 0; queueFamilyIndex < queueFamiliesCount; queueFamilyIndex++) { | ||
346 | queueFamiliesProperties[queueFamilyIndex].timestampValidBits = 0; | ||
347 | } | ||
348 | context->presentQueueFamilyIndex = -1; | ||
349 | context->graphicsQueueFamilyIndex = -1; | ||
350 | for (queueFamilyIndex = 0; queueFamilyIndex < queueFamiliesCount; queueFamilyIndex++) { | ||
351 | VkBool32 supported = 0; | ||
352 | |||
353 | if (queueFamiliesProperties[queueFamilyIndex].queueCount == 0) { | ||
354 | continue; | ||
355 | } | ||
356 | |||
357 | if (queueFamiliesProperties[queueFamilyIndex].queueFlags & VK_QUEUE_GRAPHICS_BIT) { | ||
358 | context->graphicsQueueFamilyIndex = queueFamilyIndex; | ||
359 | } | ||
360 | |||
361 | result = context->vkGetPhysicalDeviceSurfaceSupportKHR(physicalDevice, queueFamilyIndex, context->surface, &supported); | ||
362 | if (result == VK_SUCCESS) { | ||
363 | if (supported) { | ||
364 | context->presentQueueFamilyIndex = queueFamilyIndex; | ||
365 | if (queueFamiliesProperties[queueFamilyIndex].queueFlags & VK_QUEUE_GRAPHICS_BIT) { | ||
366 | break; // use this queue because it can present and do graphics | ||
367 | } | ||
368 | } | ||
369 | } | ||
370 | } | ||
371 | if (context->presentQueueFamilyIndex < 0 || context->graphicsQueueFamilyIndex < 0) { | ||
372 | // We can't render and present on this device | ||
373 | continue; | ||
374 | } | ||
375 | |||
376 | context->presentQueueCount = queueFamiliesProperties[context->presentQueueFamilyIndex].queueCount; | ||
377 | ++queueFamiliesProperties[context->presentQueueFamilyIndex].timestampValidBits; | ||
378 | context->graphicsQueueCount = queueFamiliesProperties[context->graphicsQueueFamilyIndex].queueCount; | ||
379 | ++queueFamiliesProperties[context->graphicsQueueFamilyIndex].timestampValidBits; | ||
380 | |||
381 | context->transferQueueFamilyIndex = selectQueueFamily(queueFamiliesProperties, queueFamiliesCount, VK_QUEUE_TRANSFER_BIT, &context->transferQueueCount); | ||
382 | context->computeQueueFamilyIndex = selectQueueFamily(queueFamiliesProperties, queueFamiliesCount, VK_QUEUE_COMPUTE_BIT, &context->computeQueueCount); | ||
383 | context->decodeQueueFamilyIndex = selectQueueFamily(queueFamiliesProperties, queueFamiliesCount, VK_QUEUE_VIDEO_DECODE_BIT_KHR, &context->decodeQueueCount); | ||
384 | if (context->transferQueueFamilyIndex < 0) { | ||
385 | // ffmpeg can fall back to the compute or graphics queues for this | ||
386 | context->transferQueueFamilyIndex = selectQueueFamily(queueFamiliesProperties, queueFamiliesCount, VK_QUEUE_COMPUTE_BIT, &context->transferQueueCount); | ||
387 | if (context->transferQueueFamilyIndex < 0) { | ||
388 | context->transferQueueFamilyIndex = selectQueueFamily(queueFamiliesProperties, queueFamiliesCount, VK_QUEUE_GRAPHICS_BIT, &context->transferQueueCount); | ||
389 | } | ||
390 | } | ||
391 | |||
392 | if (context->transferQueueFamilyIndex < 0 || | ||
393 | context->computeQueueFamilyIndex < 0) { | ||
394 | // This device doesn't have the queues we need for video decoding | ||
395 | continue; | ||
396 | } | ||
397 | |||
398 | result = context->vkEnumerateDeviceExtensionProperties(physicalDevice, NULL, &deviceExtensionCount, NULL); | ||
399 | if (result != VK_SUCCESS) { | ||
400 | SDL_free(physicalDevices); | ||
401 | SDL_free(queueFamiliesProperties); | ||
402 | SDL_free(deviceExtensions); | ||
403 | return SDL_SetError("vkEnumerateDeviceExtensionProperties(): %s", getVulkanResultString(result)); | ||
404 | } | ||
405 | if (deviceExtensionCount == 0) { | ||
406 | continue; | ||
407 | } | ||
408 | if (deviceExtensionsAllocatedSize < deviceExtensionCount) { | ||
409 | SDL_free(deviceExtensions); | ||
410 | deviceExtensionsAllocatedSize = deviceExtensionCount; | ||
411 | deviceExtensions = SDL_malloc(sizeof(VkExtensionProperties) * deviceExtensionsAllocatedSize); | ||
412 | if (!deviceExtensions) { | ||
413 | SDL_free(physicalDevices); | ||
414 | SDL_free(queueFamiliesProperties); | ||
415 | return -1; | ||
416 | } | ||
417 | } | ||
418 | result = context->vkEnumerateDeviceExtensionProperties(physicalDevice, NULL, &deviceExtensionCount, deviceExtensions); | ||
419 | if (result != VK_SUCCESS) { | ||
420 | SDL_free(physicalDevices); | ||
421 | SDL_free(queueFamiliesProperties); | ||
422 | SDL_free(deviceExtensions); | ||
423 | return SDL_SetError("vkEnumerateDeviceExtensionProperties(): %s", getVulkanResultString(result)); | ||
424 | } | ||
425 | for (i = 0; i < deviceExtensionCount; i++) { | ||
426 | if (SDL_strcmp(deviceExtensions[i].extensionName, VK_KHR_SWAPCHAIN_EXTENSION_NAME) == 0) { | ||
427 | hasSwapchainExtension = true; | ||
428 | break; | ||
429 | } | ||
430 | } | ||
431 | if (!hasSwapchainExtension) { | ||
432 | continue; | ||
433 | } | ||
434 | context->physicalDevice = physicalDevice; | ||
435 | break; | ||
436 | } | ||
437 | SDL_free(physicalDevices); | ||
438 | SDL_free(queueFamiliesProperties); | ||
439 | SDL_free(deviceExtensions); | ||
440 | if (!context->physicalDevice) { | ||
441 | return SDL_SetError("Vulkan: no viable physical devices found"); | ||
442 | } | ||
443 | return 0; | ||
444 | } | ||
445 | |||
446 | static void initDeviceFeatures(VulkanDeviceFeatures *features) | ||
447 | { | ||
448 | features->device_features.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FEATURES_2; | ||
449 | features->device_features.pNext = &features->device_features_1_1; | ||
450 | features->device_features_1_1.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_VULKAN_1_1_FEATURES; | ||
451 | features->device_features_1_1.pNext = &features->device_features_1_2; | ||
452 | features->device_features_1_2.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_VULKAN_1_2_FEATURES; | ||
453 | features->device_features_1_2.pNext = &features->device_features_1_3; | ||
454 | features->device_features_1_3.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_VULKAN_1_3_FEATURES; | ||
455 | features->device_features_1_3.pNext = &features->desc_buf_features; | ||
456 | features->desc_buf_features.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_DESCRIPTOR_BUFFER_FEATURES_EXT; | ||
457 | features->desc_buf_features.pNext = &features->atomic_float_features; | ||
458 | features->atomic_float_features.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_SHADER_ATOMIC_FLOAT_FEATURES_EXT; | ||
459 | features->atomic_float_features.pNext = &features->coop_matrix_features; | ||
460 | features->coop_matrix_features.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_COOPERATIVE_MATRIX_FEATURES_KHR; | ||
461 | features->coop_matrix_features.pNext = NULL; | ||
462 | } | ||
463 | |||
464 | static void copyDeviceFeatures(VulkanDeviceFeatures *supported_features, VulkanDeviceFeatures *requested_features) | ||
465 | { | ||
466 | #define COPY_OPTIONAL_FEATURE(X) requested_features->X = supported_features->X | ||
467 | COPY_OPTIONAL_FEATURE(device_features.features.shaderImageGatherExtended); | ||
468 | COPY_OPTIONAL_FEATURE(device_features.features.shaderStorageImageReadWithoutFormat); | ||
469 | COPY_OPTIONAL_FEATURE(device_features.features.shaderStorageImageWriteWithoutFormat); | ||
470 | COPY_OPTIONAL_FEATURE(device_features.features.fragmentStoresAndAtomics); | ||
471 | COPY_OPTIONAL_FEATURE(device_features.features.vertexPipelineStoresAndAtomics); | ||
472 | COPY_OPTIONAL_FEATURE(device_features.features.shaderInt64); | ||
473 | COPY_OPTIONAL_FEATURE(device_features.features.shaderInt16); | ||
474 | COPY_OPTIONAL_FEATURE(device_features.features.shaderFloat64); | ||
475 | COPY_OPTIONAL_FEATURE(device_features_1_1.samplerYcbcrConversion); | ||
476 | COPY_OPTIONAL_FEATURE(device_features_1_1.storagePushConstant16); | ||
477 | COPY_OPTIONAL_FEATURE(device_features_1_2.bufferDeviceAddress); | ||
478 | COPY_OPTIONAL_FEATURE(device_features_1_2.hostQueryReset); | ||
479 | COPY_OPTIONAL_FEATURE(device_features_1_2.storagePushConstant8); | ||
480 | COPY_OPTIONAL_FEATURE(device_features_1_2.shaderInt8); | ||
481 | COPY_OPTIONAL_FEATURE(device_features_1_2.storageBuffer8BitAccess); | ||
482 | COPY_OPTIONAL_FEATURE(device_features_1_2.uniformAndStorageBuffer8BitAccess); | ||
483 | COPY_OPTIONAL_FEATURE(device_features_1_2.shaderFloat16); | ||
484 | COPY_OPTIONAL_FEATURE(device_features_1_2.shaderSharedInt64Atomics); | ||
485 | COPY_OPTIONAL_FEATURE(device_features_1_2.vulkanMemoryModel); | ||
486 | COPY_OPTIONAL_FEATURE(device_features_1_2.vulkanMemoryModelDeviceScope); | ||
487 | COPY_OPTIONAL_FEATURE(device_features_1_2.hostQueryReset); | ||
488 | COPY_OPTIONAL_FEATURE(device_features_1_3.dynamicRendering); | ||
489 | COPY_OPTIONAL_FEATURE(device_features_1_3.maintenance4); | ||
490 | COPY_OPTIONAL_FEATURE(device_features_1_3.synchronization2); | ||
491 | COPY_OPTIONAL_FEATURE(device_features_1_3.computeFullSubgroups); | ||
492 | COPY_OPTIONAL_FEATURE(device_features_1_3.shaderZeroInitializeWorkgroupMemory); | ||
493 | COPY_OPTIONAL_FEATURE(desc_buf_features.descriptorBuffer); | ||
494 | COPY_OPTIONAL_FEATURE(desc_buf_features.descriptorBufferPushDescriptors); | ||
495 | COPY_OPTIONAL_FEATURE(atomic_float_features.shaderBufferFloat32Atomics); | ||
496 | COPY_OPTIONAL_FEATURE(atomic_float_features.shaderBufferFloat32AtomicAdd); | ||
497 | COPY_OPTIONAL_FEATURE(coop_matrix_features.cooperativeMatrix); | ||
498 | #undef COPY_OPTIONAL_FEATURE | ||
499 | |||
500 | // timeline semaphores is required by ffmpeg | ||
501 | requested_features->device_features_1_2.timelineSemaphore = 1; | ||
502 | } | ||
503 | |||
504 | static int addQueueFamily(VkDeviceQueueCreateInfo **pQueueCreateInfos, uint32_t *pQueueCreateInfoCount, uint32_t queueFamilyIndex, uint32_t queueCount) | ||
505 | { | ||
506 | VkDeviceQueueCreateInfo *queueCreateInfo; | ||
507 | VkDeviceQueueCreateInfo *queueCreateInfos = *pQueueCreateInfos; | ||
508 | uint32_t queueCreateInfoCount = *pQueueCreateInfoCount; | ||
509 | float *queuePriorities; | ||
510 | |||
511 | if (queueCount == 0) { | ||
512 | return 0; | ||
513 | } | ||
514 | |||
515 | for (uint32_t i = 0; i < queueCreateInfoCount; ++i) { | ||
516 | if (queueCreateInfos[i].queueFamilyIndex == queueFamilyIndex) { | ||
517 | return 0; | ||
518 | } | ||
519 | } | ||
520 | |||
521 | queueCreateInfos = (VkDeviceQueueCreateInfo *)SDL_realloc(queueCreateInfos, (queueCreateInfoCount + 1) * sizeof(*queueCreateInfos)); | ||
522 | if (!queueCreateInfos) { | ||
523 | return -1; | ||
524 | } | ||
525 | |||
526 | queuePriorities = (float *)SDL_malloc(queueCount * sizeof(*queuePriorities)); | ||
527 | if (!queuePriorities) { | ||
528 | return -1; | ||
529 | } | ||
530 | |||
531 | for (uint32_t i = 0; i < queueCount; ++i) { | ||
532 | queuePriorities[i] = 1.0f / queueCount; | ||
533 | } | ||
534 | |||
535 | queueCreateInfo = &queueCreateInfos[queueCreateInfoCount++]; | ||
536 | SDL_zerop(queueCreateInfo); | ||
537 | queueCreateInfo->sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO; | ||
538 | queueCreateInfo->queueFamilyIndex = queueFamilyIndex; | ||
539 | queueCreateInfo->queueCount = queueCount; | ||
540 | queueCreateInfo->pQueuePriorities = queuePriorities; | ||
541 | |||
542 | *pQueueCreateInfos = queueCreateInfos; | ||
543 | *pQueueCreateInfoCount = queueCreateInfoCount; | ||
544 | return 0; | ||
545 | } | ||
546 | |||
547 | static int createDevice(VulkanVideoContext *context) | ||
548 | { | ||
549 | static const char *const deviceExtensionNames[] = { | ||
550 | VK_KHR_SWAPCHAIN_EXTENSION_NAME, | ||
551 | VK_KHR_SAMPLER_YCBCR_CONVERSION_EXTENSION_NAME, | ||
552 | VK_KHR_MAINTENANCE1_EXTENSION_NAME, | ||
553 | VK_KHR_BIND_MEMORY_2_EXTENSION_NAME, | ||
554 | VK_KHR_GET_MEMORY_REQUIREMENTS_2_EXTENSION_NAME, | ||
555 | }; | ||
556 | static const char *optional_extensions[] = { | ||
557 | VK_KHR_VIDEO_QUEUE_EXTENSION_NAME, | ||
558 | VK_KHR_VIDEO_DECODE_QUEUE_EXTENSION_NAME, | ||
559 | VK_KHR_VIDEO_DECODE_H264_EXTENSION_NAME, | ||
560 | VK_KHR_VIDEO_DECODE_H265_EXTENSION_NAME, | ||
561 | VK_KHR_VIDEO_DECODE_AV1_EXTENSION_NAME | ||
562 | }; | ||
563 | VkDeviceCreateInfo deviceCreateInfo = { 0 }; | ||
564 | VkDeviceQueueCreateInfo *queueCreateInfos = NULL; | ||
565 | uint32_t queueCreateInfoCount = 0; | ||
566 | VulkanDeviceFeatures supported_features; | ||
567 | VkResult result = VK_ERROR_UNKNOWN; | ||
568 | |||
569 | if (addQueueFamily(&queueCreateInfos, &queueCreateInfoCount, context->presentQueueFamilyIndex, context->presentQueueCount) < 0 || | ||
570 | addQueueFamily(&queueCreateInfos, &queueCreateInfoCount, context->graphicsQueueFamilyIndex, context->graphicsQueueCount) < 0 || | ||
571 | addQueueFamily(&queueCreateInfos, &queueCreateInfoCount, context->transferQueueFamilyIndex, context->transferQueueCount) < 0 || | ||
572 | addQueueFamily(&queueCreateInfos, &queueCreateInfoCount, context->computeQueueFamilyIndex, context->computeQueueCount) < 0 || | ||
573 | addQueueFamily(&queueCreateInfos, &queueCreateInfoCount, context->decodeQueueFamilyIndex, context->decodeQueueCount) < 0) { | ||
574 | goto done; | ||
575 | } | ||
576 | |||
577 | initDeviceFeatures(&supported_features); | ||
578 | initDeviceFeatures(&context->features); | ||
579 | context->vkGetPhysicalDeviceFeatures2(context->physicalDevice, &supported_features.device_features); | ||
580 | copyDeviceFeatures(&supported_features, &context->features); | ||
581 | |||
582 | deviceCreateInfo.sType = VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO; | ||
583 | deviceCreateInfo.queueCreateInfoCount = queueCreateInfoCount; | ||
584 | deviceCreateInfo.pQueueCreateInfos = queueCreateInfos; | ||
585 | deviceCreateInfo.pEnabledFeatures = NULL; | ||
586 | deviceCreateInfo.enabledExtensionCount = SDL_arraysize(deviceExtensionNames); | ||
587 | deviceCreateInfo.pNext = &context->features.device_features; | ||
588 | |||
589 | const char **deviceExtensionsCopy = SDL_calloc(deviceCreateInfo.enabledExtensionCount + SDL_arraysize(optional_extensions), sizeof(const char *)); | ||
590 | for (uint32_t i = 0; i < deviceCreateInfo.enabledExtensionCount; i++) { | ||
591 | deviceExtensionsCopy[i] = deviceExtensionNames[i]; | ||
592 | } | ||
593 | |||
594 | // Get the rest of the optional extensions | ||
595 | { | ||
596 | uint32_t extensionCount; | ||
597 | if (context->vkEnumerateDeviceExtensionProperties(context->physicalDevice, NULL, &extensionCount, NULL) == VK_SUCCESS && extensionCount > 0) { | ||
598 | VkExtensionProperties *extensionProperties = SDL_calloc(extensionCount, sizeof(VkExtensionProperties)); | ||
599 | if (context->vkEnumerateDeviceExtensionProperties(context->physicalDevice, NULL, &extensionCount, extensionProperties) == VK_SUCCESS) { | ||
600 | for (uint32_t i = 0; i < SDL_arraysize(optional_extensions); ++i) { | ||
601 | for (uint32_t j = 0; j < extensionCount; ++j) { | ||
602 | if (SDL_strcmp(extensionProperties[j].extensionName, optional_extensions[i]) == 0) { | ||
603 | deviceExtensionsCopy[deviceCreateInfo.enabledExtensionCount++] = optional_extensions[i]; | ||
604 | break; | ||
605 | } | ||
606 | } | ||
607 | } | ||
608 | } | ||
609 | SDL_free(extensionProperties); | ||
610 | } | ||
611 | } | ||
612 | deviceCreateInfo.ppEnabledExtensionNames = deviceExtensionsCopy; | ||
613 | |||
614 | context->deviceExtensions = deviceExtensionsCopy; | ||
615 | context->deviceExtensionsCount = deviceCreateInfo.enabledExtensionCount; | ||
616 | |||
617 | result = context->vkCreateDevice(context->physicalDevice, &deviceCreateInfo, NULL, &context->device); | ||
618 | if (result != VK_SUCCESS) { | ||
619 | SDL_SetError("vkCreateDevice(): %s", getVulkanResultString(result)); | ||
620 | goto done; | ||
621 | } | ||
622 | |||
623 | if (loadDeviceFunctions(context) < 0) { | ||
624 | result = VK_ERROR_UNKNOWN; | ||
625 | context->device = VK_NULL_HANDLE; | ||
626 | goto done; | ||
627 | } | ||
628 | |||
629 | // Get the graphics queue that SDL will use | ||
630 | context->vkGetDeviceQueue(context->device, context->graphicsQueueFamilyIndex, 0, &context->graphicsQueue); | ||
631 | |||
632 | // Create a command pool | ||
633 | VkCommandPoolCreateInfo commandPoolCreateInfo = { 0 }; | ||
634 | commandPoolCreateInfo.sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO; | ||
635 | commandPoolCreateInfo.flags = VK_COMMAND_POOL_CREATE_RESET_COMMAND_BUFFER_BIT; | ||
636 | commandPoolCreateInfo.queueFamilyIndex = context->graphicsQueueFamilyIndex; | ||
637 | result = context->vkCreateCommandPool(context->device, &commandPoolCreateInfo, NULL, &context->commandPool); | ||
638 | if (result != VK_SUCCESS) { | ||
639 | SDL_SetError("vkCreateCommandPool(): %s", getVulkanResultString(result)); | ||
640 | goto done; | ||
641 | } | ||
642 | |||
643 | done: | ||
644 | for (uint32_t i = 0; i < queueCreateInfoCount; ++i) { | ||
645 | SDL_free((void *)queueCreateInfos[i].pQueuePriorities); | ||
646 | } | ||
647 | SDL_free(queueCreateInfos); | ||
648 | |||
649 | if (result != VK_SUCCESS) { | ||
650 | return -1; | ||
651 | } | ||
652 | return 0; | ||
653 | } | ||
654 | |||
655 | VulkanVideoContext *CreateVulkanVideoContext(SDL_Window *window) | ||
656 | { | ||
657 | VulkanVideoContext *context = SDL_calloc(1, sizeof(*context)); | ||
658 | if (!context) { | ||
659 | return NULL; | ||
660 | } | ||
661 | if (loadGlobalFunctions(context) < 0 || | ||
662 | createInstance(context) < 0 || | ||
663 | createSurface(context, window) < 0 || | ||
664 | findPhysicalDevice(context) < 0 || | ||
665 | createDevice(context) < 0) { | ||
666 | DestroyVulkanVideoContext(context); | ||
667 | return NULL; | ||
668 | } | ||
669 | return context; | ||
670 | } | ||
671 | |||
672 | void SetupVulkanRenderProperties(VulkanVideoContext *context, SDL_PropertiesID props) | ||
673 | { | ||
674 | SDL_SetPointerProperty(props, SDL_PROP_RENDERER_CREATE_VULKAN_INSTANCE_POINTER, context->instance); | ||
675 | SDL_SetNumberProperty(props, SDL_PROP_RENDERER_CREATE_VULKAN_SURFACE_NUMBER, (Sint64)context->surface); | ||
676 | SDL_SetPointerProperty(props, SDL_PROP_RENDERER_CREATE_VULKAN_PHYSICAL_DEVICE_POINTER, context->physicalDevice); | ||
677 | SDL_SetPointerProperty(props, SDL_PROP_RENDERER_CREATE_VULKAN_DEVICE_POINTER, context->device); | ||
678 | SDL_SetNumberProperty(props, SDL_PROP_RENDERER_CREATE_VULKAN_PRESENT_QUEUE_FAMILY_INDEX_NUMBER, context->presentQueueFamilyIndex); | ||
679 | SDL_SetNumberProperty(props, SDL_PROP_RENDERER_CREATE_VULKAN_GRAPHICS_QUEUE_FAMILY_INDEX_NUMBER, context->graphicsQueueFamilyIndex); | ||
680 | } | ||
681 | |||
682 | #if LIBAVUTIL_VERSION_MAJOR >= 59 | ||
683 | static void AddQueueFamily(AVVulkanDeviceContext *ctx, int idx, int num, VkQueueFlagBits flags) | ||
684 | { | ||
685 | AVVulkanDeviceQueueFamily *entry = &ctx->qf[ctx->nb_qf++]; | ||
686 | entry->idx = idx; | ||
687 | entry->num = num; | ||
688 | entry->flags = flags; | ||
689 | } | ||
690 | #endif /* LIBAVUTIL_VERSION_MAJOR */ | ||
691 | |||
692 | void SetupVulkanDeviceContextData(VulkanVideoContext *context, AVVulkanDeviceContext *ctx) | ||
693 | { | ||
694 | ctx->get_proc_addr = context->vkGetInstanceProcAddr; | ||
695 | ctx->inst = context->instance; | ||
696 | ctx->phys_dev = context->physicalDevice; | ||
697 | ctx->act_dev = context->device; | ||
698 | ctx->device_features = context->features.device_features; | ||
699 | ctx->enabled_inst_extensions = context->instanceExtensions; | ||
700 | ctx->nb_enabled_inst_extensions = context->instanceExtensionsCount; | ||
701 | ctx->enabled_dev_extensions = context->deviceExtensions; | ||
702 | ctx->nb_enabled_dev_extensions = context->deviceExtensionsCount; | ||
703 | #if LIBAVUTIL_VERSION_MAJOR >= 59 | ||
704 | AddQueueFamily(ctx, context->graphicsQueueFamilyIndex, context->graphicsQueueCount, VK_QUEUE_GRAPHICS_BIT); | ||
705 | AddQueueFamily(ctx, context->transferQueueFamilyIndex, context->transferQueueCount, VK_QUEUE_TRANSFER_BIT); | ||
706 | AddQueueFamily(ctx, context->computeQueueFamilyIndex, context->computeQueueCount, VK_QUEUE_COMPUTE_BIT); | ||
707 | AddQueueFamily(ctx, context->decodeQueueFamilyIndex, context->decodeQueueCount, VK_QUEUE_VIDEO_DECODE_BIT_KHR); | ||
708 | #else | ||
709 | ctx->queue_family_index = context->graphicsQueueFamilyIndex; | ||
710 | ctx->nb_graphics_queues = context->graphicsQueueCount; | ||
711 | ctx->queue_family_tx_index = context->transferQueueFamilyIndex; | ||
712 | ctx->nb_tx_queues = context->transferQueueCount; | ||
713 | ctx->queue_family_comp_index = context->computeQueueFamilyIndex; | ||
714 | ctx->nb_comp_queues = context->computeQueueCount; | ||
715 | ctx->queue_family_encode_index = -1; | ||
716 | ctx->nb_encode_queues = 0; | ||
717 | ctx->queue_family_decode_index = context->decodeQueueFamilyIndex; | ||
718 | ctx->nb_decode_queues = context->decodeQueueCount; | ||
719 | #endif /* LIBAVUTIL_VERSION_MAJOR */ | ||
720 | } | ||
721 | |||
722 | static int CreateCommandBuffers(VulkanVideoContext *context, SDL_Renderer *renderer) | ||
723 | { | ||
724 | uint32_t commandBufferCount = (uint32_t)SDL_GetNumberProperty(SDL_GetRendererProperties(renderer), SDL_PROP_RENDERER_VULKAN_SWAPCHAIN_IMAGE_COUNT_NUMBER, 1); | ||
725 | |||
726 | if (commandBufferCount > context->waitSemaphoreCount) { | ||
727 | VkSemaphore *semaphores = (VkSemaphore *)SDL_realloc(context->waitSemaphores, commandBufferCount * sizeof(*semaphores)); | ||
728 | if (!semaphores) { | ||
729 | return -1; | ||
730 | } | ||
731 | context->waitSemaphores = semaphores; | ||
732 | |||
733 | VkSemaphoreCreateInfo semaphoreCreateInfo = { 0 }; | ||
734 | semaphoreCreateInfo.sType = VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO; | ||
735 | while (context->waitSemaphoreCount < commandBufferCount) { | ||
736 | VkResult result = context->vkCreateSemaphore(context->device, &semaphoreCreateInfo, NULL, &context->waitSemaphores[context->waitSemaphoreCount]); | ||
737 | if (result != VK_SUCCESS) { | ||
738 | SDL_SetError("vkCreateSemaphore(): %s", getVulkanResultString(result)); | ||
739 | return -1; | ||
740 | } | ||
741 | ++context->waitSemaphoreCount; | ||
742 | } | ||
743 | } | ||
744 | |||
745 | if (commandBufferCount > context->signalSemaphoreCount) { | ||
746 | VkSemaphore *semaphores = (VkSemaphore *)SDL_realloc(context->signalSemaphores, commandBufferCount * sizeof(*semaphores)); | ||
747 | if (!semaphores) { | ||
748 | return -1; | ||
749 | } | ||
750 | context->signalSemaphores = semaphores; | ||
751 | |||
752 | VkSemaphoreCreateInfo semaphoreCreateInfo = { 0 }; | ||
753 | semaphoreCreateInfo.sType = VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO; | ||
754 | while (context->signalSemaphoreCount < commandBufferCount) { | ||
755 | VkResult result = context->vkCreateSemaphore(context->device, &semaphoreCreateInfo, NULL, &context->signalSemaphores[context->signalSemaphoreCount]); | ||
756 | if (result != VK_SUCCESS) { | ||
757 | SDL_SetError("vkCreateSemaphore(): %s", getVulkanResultString(result)); | ||
758 | return -1; | ||
759 | } | ||
760 | ++context->signalSemaphoreCount; | ||
761 | } | ||
762 | } | ||
763 | |||
764 | if (commandBufferCount > context->commandBufferCount) { | ||
765 | uint32_t needed = (commandBufferCount - context->commandBufferCount); | ||
766 | VkCommandBuffer *commandBuffers = (VkCommandBuffer *)SDL_realloc(context->commandBuffers, commandBufferCount * sizeof(*commandBuffers)); | ||
767 | if (!commandBuffers) { | ||
768 | return -1; | ||
769 | } | ||
770 | context->commandBuffers = commandBuffers; | ||
771 | |||
772 | VkCommandBufferAllocateInfo commandBufferAllocateInfo = { 0 }; | ||
773 | commandBufferAllocateInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO; | ||
774 | commandBufferAllocateInfo.commandPool = context->commandPool; | ||
775 | commandBufferAllocateInfo.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY; | ||
776 | commandBufferAllocateInfo.commandBufferCount = needed; | ||
777 | VkResult result = context->vkAllocateCommandBuffers(context->device, &commandBufferAllocateInfo, &context->commandBuffers[context->commandBufferCount]); | ||
778 | if (result != VK_SUCCESS) { | ||
779 | SDL_SetError("vkAllocateCommandBuffers(): %s", getVulkanResultString(result)); | ||
780 | return -1; | ||
781 | } | ||
782 | |||
783 | context->commandBufferCount = commandBufferCount; | ||
784 | } | ||
785 | return 0; | ||
786 | } | ||
787 | |||
788 | int BeginVulkanFrameRendering(VulkanVideoContext *context, AVFrame *frame, SDL_Renderer *renderer) | ||
789 | { | ||
790 | AVHWFramesContext *frames = (AVHWFramesContext *)(frame->hw_frames_ctx->data); | ||
791 | AVVulkanFramesContext *vk = (AVVulkanFramesContext *)(frames->hwctx); | ||
792 | AVVkFrame *pVkFrame = (AVVkFrame *)frame->data[0]; | ||
793 | |||
794 | if (CreateCommandBuffers(context, renderer) < 0) { | ||
795 | return -1; | ||
796 | } | ||
797 | |||
798 | vk->lock_frame(frames, pVkFrame); | ||
799 | |||
800 | VkTimelineSemaphoreSubmitInfo timeline = { 0 }; | ||
801 | timeline.sType = VK_STRUCTURE_TYPE_TIMELINE_SEMAPHORE_SUBMIT_INFO; | ||
802 | timeline.waitSemaphoreValueCount = 1; | ||
803 | timeline.pWaitSemaphoreValues = pVkFrame->sem_value; | ||
804 | |||
805 | VkPipelineStageFlags pipelineStageMask = VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT; | ||
806 | VkSubmitInfo submitInfo = { 0 }; | ||
807 | submitInfo.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO; | ||
808 | submitInfo.waitSemaphoreCount = 1; | ||
809 | submitInfo.pWaitSemaphores = pVkFrame->sem; | ||
810 | submitInfo.pWaitDstStageMask = &pipelineStageMask; | ||
811 | submitInfo.signalSemaphoreCount = 1; | ||
812 | submitInfo.pSignalSemaphores = &context->waitSemaphores[context->commandBufferIndex]; | ||
813 | submitInfo.pNext = &timeline; | ||
814 | |||
815 | if (pVkFrame->layout[0] != VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL) { | ||
816 | VkCommandBuffer commandBuffer = context->commandBuffers[context->commandBufferIndex]; | ||
817 | |||
818 | VkCommandBufferBeginInfo beginInfo = { 0 }; | ||
819 | beginInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO; | ||
820 | beginInfo.flags = 0; | ||
821 | context->vkBeginCommandBuffer(commandBuffer, &beginInfo); | ||
822 | |||
823 | VkImageMemoryBarrier2 barrier = { 0 }; | ||
824 | barrier.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER_2; | ||
825 | barrier.srcAccessMask = VK_ACCESS_2_NONE; | ||
826 | barrier.dstAccessMask = VK_ACCESS_2_SHADER_SAMPLED_READ_BIT; | ||
827 | barrier.oldLayout = pVkFrame->layout[0]; | ||
828 | barrier.newLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL; | ||
829 | barrier.image = pVkFrame->img[0]; | ||
830 | barrier.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; | ||
831 | barrier.subresourceRange.levelCount = 1; | ||
832 | barrier.subresourceRange.layerCount = 1; | ||
833 | barrier.srcQueueFamilyIndex = pVkFrame->queue_family[0]; | ||
834 | barrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; | ||
835 | barrier.srcStageMask = VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT; | ||
836 | barrier.dstStageMask = VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT; | ||
837 | |||
838 | VkDependencyInfo dep = { 0 }; | ||
839 | dep.sType = VK_STRUCTURE_TYPE_DEPENDENCY_INFO; | ||
840 | dep.dependencyFlags = VK_DEPENDENCY_BY_REGION_BIT; | ||
841 | dep.imageMemoryBarrierCount = 1; | ||
842 | dep.pImageMemoryBarriers = &barrier; | ||
843 | context->vkCmdPipelineBarrier2(commandBuffer, &dep); | ||
844 | |||
845 | context->vkEndCommandBuffer(commandBuffer); | ||
846 | |||
847 | // Add the image barrier to the submit info | ||
848 | submitInfo.commandBufferCount = 1; | ||
849 | submitInfo.pCommandBuffers = &context->commandBuffers[context->commandBufferIndex]; | ||
850 | |||
851 | pVkFrame->layout[0] = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL; | ||
852 | pVkFrame->queue_family[0] = VK_QUEUE_FAMILY_IGNORED; | ||
853 | } | ||
854 | |||
855 | VkResult result = context->vkQueueSubmit(context->graphicsQueue, 1, &submitInfo, 0); | ||
856 | if (result != VK_SUCCESS) { | ||
857 | // Don't return an error here, we need to complete the frame operation | ||
858 | SDL_LogError(SDL_LOG_CATEGORY_APPLICATION , "vkQueueSubmit(): %s", getVulkanResultString(result)); | ||
859 | } | ||
860 | |||
861 | SDL_AddVulkanRenderSemaphores(renderer, VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT, (Sint64)context->waitSemaphores[context->commandBufferIndex], (Sint64)context->signalSemaphores[context->commandBufferIndex]); | ||
862 | |||
863 | return 0; | ||
864 | } | ||
865 | |||
866 | int FinishVulkanFrameRendering(VulkanVideoContext *context, AVFrame *frame, SDL_Renderer *renderer) | ||
867 | { | ||
868 | AVHWFramesContext *frames = (AVHWFramesContext *)(frame->hw_frames_ctx->data); | ||
869 | AVVulkanFramesContext *vk = (AVVulkanFramesContext *)(frames->hwctx); | ||
870 | AVVkFrame *pVkFrame = (AVVkFrame *)frame->data[0]; | ||
871 | |||
872 | // Transition the frame back to ffmpeg | ||
873 | ++pVkFrame->sem_value[0]; | ||
874 | |||
875 | VkTimelineSemaphoreSubmitInfo timeline = { 0 }; | ||
876 | timeline.sType = VK_STRUCTURE_TYPE_TIMELINE_SEMAPHORE_SUBMIT_INFO; | ||
877 | timeline.signalSemaphoreValueCount = 1; | ||
878 | timeline.pSignalSemaphoreValues = pVkFrame->sem_value; | ||
879 | |||
880 | VkPipelineStageFlags pipelineStageMask = VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT; | ||
881 | VkSubmitInfo submitInfo = { 0 }; | ||
882 | submitInfo.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO; | ||
883 | submitInfo.waitSemaphoreCount = 1; | ||
884 | submitInfo.pWaitSemaphores = &context->signalSemaphores[context->commandBufferIndex]; | ||
885 | submitInfo.pWaitDstStageMask = &pipelineStageMask; | ||
886 | submitInfo.signalSemaphoreCount = 1; | ||
887 | submitInfo.pSignalSemaphores = pVkFrame->sem; | ||
888 | submitInfo.pNext = &timeline; | ||
889 | |||
890 | VkResult result = context->vkQueueSubmit(context->graphicsQueue, 1, &submitInfo, 0); | ||
891 | if (result != VK_SUCCESS) { | ||
892 | // Don't return an error here, we need to complete the frame operation | ||
893 | SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "vkQueueSubmit(): %s", getVulkanResultString(result)); | ||
894 | } | ||
895 | |||
896 | vk->unlock_frame(frames, pVkFrame); | ||
897 | |||
898 | context->commandBufferIndex = (context->commandBufferIndex + 1) % context->commandBufferCount; | ||
899 | |||
900 | return 0; | ||
901 | } | ||
902 | |||
903 | SDL_Texture *CreateVulkanVideoTexture(VulkanVideoContext *context, AVFrame *frame, SDL_Renderer *renderer, SDL_PropertiesID props) | ||
904 | { | ||
905 | AVHWFramesContext *frames = (AVHWFramesContext *)(frame->hw_frames_ctx->data); | ||
906 | AVVulkanFramesContext *vk = (AVVulkanFramesContext *)(frames->hwctx); | ||
907 | AVVkFrame *pVkFrame = (AVVkFrame *)frame->data[0]; | ||
908 | Uint32 format; | ||
909 | |||
910 | switch (vk->format[0]) { | ||
911 | case VK_FORMAT_G8B8G8R8_422_UNORM: | ||
912 | format = SDL_PIXELFORMAT_YUY2; | ||
913 | break; | ||
914 | case VK_FORMAT_B8G8R8G8_422_UNORM: | ||
915 | format = SDL_PIXELFORMAT_UYVY; | ||
916 | break; | ||
917 | case VK_FORMAT_G8_B8_R8_3PLANE_420_UNORM: | ||
918 | format = SDL_PIXELFORMAT_IYUV; | ||
919 | break; | ||
920 | case VK_FORMAT_G8_B8R8_2PLANE_420_UNORM: | ||
921 | format = SDL_PIXELFORMAT_NV12; | ||
922 | break; | ||
923 | case VK_FORMAT_G10X6_B10X6R10X6_2PLANE_420_UNORM_3PACK16: | ||
924 | format = SDL_PIXELFORMAT_P010; | ||
925 | break; | ||
926 | default: | ||
927 | format = SDL_PIXELFORMAT_UNKNOWN; | ||
928 | break; | ||
929 | } | ||
930 | SDL_SetNumberProperty(props, SDL_PROP_TEXTURE_CREATE_FORMAT_NUMBER, format); | ||
931 | SDL_SetNumberProperty(props, SDL_PROP_TEXTURE_CREATE_VULKAN_TEXTURE_NUMBER, (Sint64)pVkFrame->img[0]); | ||
932 | return SDL_CreateTextureWithProperties(renderer, props); | ||
933 | } | ||
934 | |||
935 | void DestroyVulkanVideoContext(VulkanVideoContext *context) | ||
936 | { | ||
937 | if (context) { | ||
938 | if (context->device) { | ||
939 | context->vkDeviceWaitIdle(context->device); | ||
940 | } | ||
941 | if (context->instanceExtensions) { | ||
942 | SDL_free(context->instanceExtensions); | ||
943 | } | ||
944 | if (context->deviceExtensions) { | ||
945 | SDL_free(context->deviceExtensions); | ||
946 | } | ||
947 | if (context->waitSemaphores) { | ||
948 | for (uint32_t i = 0; i < context->waitSemaphoreCount; ++i) { | ||
949 | context->vkDestroySemaphore(context->device, context->waitSemaphores[i], NULL); | ||
950 | } | ||
951 | SDL_free(context->waitSemaphores); | ||
952 | context->waitSemaphores = NULL; | ||
953 | } | ||
954 | if (context->signalSemaphores) { | ||
955 | for (uint32_t i = 0; i < context->signalSemaphoreCount; ++i) { | ||
956 | context->vkDestroySemaphore(context->device, context->signalSemaphores[i], NULL); | ||
957 | } | ||
958 | SDL_free(context->signalSemaphores); | ||
959 | context->signalSemaphores = NULL; | ||
960 | } | ||
961 | if (context->commandBuffers) { | ||
962 | context->vkFreeCommandBuffers(context->device, context->commandPool, context->commandBufferCount, context->commandBuffers); | ||
963 | SDL_free(context->commandBuffers); | ||
964 | context->commandBuffers = NULL; | ||
965 | } | ||
966 | if (context->commandPool) { | ||
967 | context->vkDestroyCommandPool(context->device, context->commandPool, NULL); | ||
968 | context->commandPool = VK_NULL_HANDLE; | ||
969 | } | ||
970 | if (context->device) { | ||
971 | context->vkDestroyDevice(context->device, NULL); | ||
972 | } | ||
973 | if (context->surface) { | ||
974 | context->vkDestroySurfaceKHR(context->instance, context->surface, NULL); | ||
975 | } | ||
976 | if (context->instance) { | ||
977 | context->vkDestroyInstance(context->instance, NULL); | ||
978 | } | ||
979 | SDL_free(context); | ||
980 | } | ||
981 | } | ||
982 | |||
983 | #else | ||
984 | |||
985 | VulkanVideoContext *CreateVulkanVideoContext(SDL_Window *window) | ||
986 | { | ||
987 | SDL_SetError("testffmpeg not built with Vulkan support"); | ||
988 | return NULL; | ||
989 | } | ||
990 | |||
991 | void SetupVulkanRenderProperties(VulkanVideoContext *context, SDL_PropertiesID props) | ||
992 | { | ||
993 | } | ||
994 | |||
995 | void SetupVulkanDeviceContextData(VulkanVideoContext *context, AVVulkanDeviceContext *ctx) | ||
996 | { | ||
997 | } | ||
998 | |||
999 | SDL_Texture *CreateVulkanVideoTexture(VulkanVideoContext *context, AVFrame *frame, SDL_Renderer *renderer, SDL_PropertiesID props) | ||
1000 | { | ||
1001 | return NULL; | ||
1002 | } | ||
1003 | |||
1004 | int BeginVulkanFrameRendering(VulkanVideoContext *context, AVFrame *frame, SDL_Renderer *renderer) | ||
1005 | { | ||
1006 | return -1; | ||
1007 | } | ||
1008 | |||
1009 | int FinishVulkanFrameRendering(VulkanVideoContext *context, AVFrame *frame, SDL_Renderer *renderer) | ||
1010 | { | ||
1011 | return -1; | ||
1012 | } | ||
1013 | |||
1014 | void DestroyVulkanVideoContext(VulkanVideoContext *context) | ||
1015 | { | ||
1016 | } | ||
1017 | |||
1018 | #endif // FFMPEG_VULKAN_SUPPORT | ||