From dbec2150d0e52b54498ac78e8d9d2d8d804c85f8 Mon Sep 17 00:00:00 2001
From: Sam Lantinga <[EMAIL REDACTED]>
Date: Sat, 2 Mar 2024 10:05:38 -0800
Subject: [PATCH] testffmpeg: added support for Vulkan rendering
---
test/CMakeLists.txt | 7 +-
test/testffmpeg.c | 105 ++++-
test/testffmpeg_vulkan.c | 974 +++++++++++++++++++++++++++++++++++++++
test/testffmpeg_vulkan.h | 22 +
4 files changed, 1088 insertions(+), 20 deletions(-)
create mode 100644 test/testffmpeg_vulkan.c
create mode 100644 test/testffmpeg_vulkan.h
diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt
index d09c81e0666f8..4f0fc5331b25e 100644
--- a/test/CMakeLists.txt
+++ b/test/CMakeLists.txt
@@ -224,11 +224,13 @@ include("${SDL3_SOURCE_DIR}/cmake/FindFFmpeg.cmake")
if(FFmpeg_FOUND)
cmake_push_check_state()
list(APPEND CMAKE_REQUIRED_INCLUDES "${FFmpeg_AVUTIL_INCLUDE_DIRS}")
+ list(APPEND CMAKE_REQUIRED_INCLUDES "${SDL3_SOURCE_DIR}/src/video/khronos")
check_struct_has_member("AVFrame" "ch_layout" "libavutil/frame.h" LIBAVUTIL_AVFRAME_HAS_CH_LAYOUT)
+ check_struct_has_member("AVVulkanFramesContext" "format" "libavutil/hwcontext_vulkan.h" LIBAVUTIL_AVFULKANFRAMESCONTEXT_HAS_FORMAT)
cmake_pop_check_state()
endif()
-if(FFmpeg_FOUND AND LIBAVUTIL_AVFRAME_HAS_CH_LAYOUT)
- add_sdl_test_executable(testffmpeg NO_C90 SOURCES testffmpeg.c ${icon_bmp_header})
+if(FFmpeg_FOUND AND LIBAVUTIL_AVFRAME_HAS_CH_LAYOUT AND LIBAVUTIL_AVFULKANFRAMESCONTEXT_HAS_FORMAT)
+ add_sdl_test_executable(testffmpeg NO_C90 SOURCES testffmpeg.c testffmpeg_vulkan.c ${icon_bmp_header})
if(APPLE)
target_link_options(testffmpeg PRIVATE "-Wl,-framework,CoreVideo")
endif()
@@ -237,6 +239,7 @@ if(FFmpeg_FOUND AND LIBAVUTIL_AVFRAME_HAS_CH_LAYOUT)
target_link_libraries(testffmpeg PRIVATE OpenGL::EGL)
target_compile_definitions(testffmpeg PRIVATE HAVE_EGL)
endif()
+ target_include_directories(testffmpeg BEFORE PRIVATE ${SDL3_SOURCE_DIR}/src/video/khronos)
target_link_libraries(testffmpeg PRIVATE ${FFMPEG_LIBRARIES})
else()
message(STATUS "Can't find ffmpeg 5.1.3 or newer, skipping testffmpeg")
diff --git a/test/testffmpeg.c b/test/testffmpeg.c
index cad60bfb70fb9..35e91265c11b2 100644
--- a/test/testffmpeg.c
+++ b/test/testffmpeg.c
@@ -55,6 +55,8 @@
#include <libavutil/hwcontext_d3d11va.h>
#endif /* SDL_PLATFORM_WIN32 */
+#include "testffmpeg_vulkan.h"
+
#include "icon.h"
@@ -82,6 +84,7 @@ static ID3D11Device *d3d11_device;
static ID3D11DeviceContext *d3d11_context;
static const GUID SDL_IID_ID3D11Resource = { 0xdc8e63f3, 0xd12b, 0x4952, { 0xb4, 0x7b, 0x5e, 0x45, 0x02, 0x6a, 0x86, 0x2d } };
#endif
+static VulkanVideoContext *vulkan_context;
struct SwsContextContainer
{
struct SwsContext *context;
@@ -93,35 +96,59 @@ static SDL_bool CreateWindowAndRenderer(Uint32 window_flags, const char *driver)
{
SDL_PropertiesID props;
SDL_RendererInfo info;
+ SDL_bool useOpenGL = (driver && (SDL_strcmp(driver, "opengl") == 0 || SDL_strcmp(driver, "opengles2") == 0));
SDL_bool useEGL = (driver && SDL_strcmp(driver, "opengles2") == 0);
+ SDL_bool useVulkan = (driver && SDL_strcmp(driver, "vulkan") == 0);
+ Uint32 flags = SDL_WINDOW_HIDDEN;
+
+ if (useOpenGL) {
+ if (useEGL) {
+ SDL_SetHint(SDL_HINT_VIDEO_FORCE_EGL, "1");
+ SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_ES);
+ SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 2);
+ SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 0);
+ } else {
+ SDL_SetHint(SDL_HINT_VIDEO_FORCE_EGL, "0");
+ SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, 0);
+ SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 2);
+ SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 1);
+ }
+ SDL_GL_SetAttribute(SDL_GL_RED_SIZE, 5);
+ SDL_GL_SetAttribute(SDL_GL_GREEN_SIZE, 6);
+ SDL_GL_SetAttribute(SDL_GL_BLUE_SIZE, 5);
- SDL_SetHint(SDL_HINT_RENDER_DRIVER, driver);
- if (useEGL) {
- SDL_SetHint(SDL_HINT_VIDEO_FORCE_EGL, "1");
- SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_ES);
- SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 2);
- SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 0);
- } else {
- SDL_SetHint(SDL_HINT_VIDEO_FORCE_EGL, "0");
- SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, 0);
- SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 2);
- SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 1);
+ flags |= SDL_WINDOW_OPENGL;
+ }
+ if (useVulkan) {
+ flags |= SDL_WINDOW_VULKAN;
}
- SDL_GL_SetAttribute(SDL_GL_RED_SIZE, 5);
- SDL_GL_SetAttribute(SDL_GL_GREEN_SIZE, 6);
- SDL_GL_SetAttribute(SDL_GL_BLUE_SIZE, 5);
/* The window will be resized to the video size when it's loaded, in OpenVideoStream() */
- window = SDL_CreateWindow("testffmpeg", 1920, 1080, 0);
+ window = SDL_CreateWindow("testffmpeg", 1920, 1080, flags);
if (!window) {
return SDL_FALSE;
}
+ if (useVulkan) {
+ vulkan_context = CreateVulkanVideoContext(window);
+ if (!vulkan_context) {
+ SDL_DestroyWindow(window);
+ window = NULL;
+ return SDL_FALSE;
+ }
+ }
+
props = SDL_CreateProperties();
SDL_SetStringProperty(props, SDL_PROP_RENDERER_CREATE_NAME_STRING, driver);
SDL_SetProperty(props, SDL_PROP_RENDERER_CREATE_WINDOW_POINTER, window);
- SDL_SetNumberProperty(props, SDL_PROP_RENDERER_CREATE_OUTPUT_COLORSPACE_NUMBER, SDL_COLORSPACE_SRGB_LINEAR);
- renderer = SDL_CreateRendererWithProperties(props);
+ if (useVulkan) {
+ SetupVulkanRenderProperties(vulkan_context, props);
+ }
+ if (SDL_GetBooleanProperty(SDL_GetDisplayProperties(SDL_GetDisplayForWindow(window)), SDL_PROP_DISPLAY_HDR_ENABLED_BOOLEAN, SDL_FALSE)) {
+ /* Try to create an HDR capable renderer */
+ SDL_SetNumberProperty(props, SDL_PROP_RENDERER_CREATE_OUTPUT_COLORSPACE_NUMBER, SDL_COLORSPACE_SRGB_LINEAR);
+ renderer = SDL_CreateRendererWithProperties(props);
+ }
if (!renderer) {
/* Try again with the sRGB colorspace */
SDL_SetNumberProperty(props, SDL_PROP_RENDERER_CREATE_OUTPUT_COLORSPACE_NUMBER, SDL_COLORSPACE_SRGB);
@@ -129,6 +156,8 @@ static SDL_bool CreateWindowAndRenderer(Uint32 window_flags, const char *driver)
}
SDL_DestroyProperties(props);
if (!renderer) {
+ SDL_DestroyWindow(window);
+ window = NULL;
return SDL_FALSE;
}
@@ -285,6 +314,8 @@ static Uint32 GetTextureFormat(enum AVPixelFormat format)
return SDL_PIXELFORMAT_NV12;
case AV_PIX_FMT_NV21:
return SDL_PIXELFORMAT_NV21;
+ case AV_PIX_FMT_P010:
+ return SDL_PIXELFORMAT_P010;
default:
return SDL_PIXELFORMAT_UNKNOWN;
}
@@ -307,6 +338,9 @@ static SDL_bool SupportedPixelFormat(enum AVPixelFormat format)
return SDL_TRUE;
}
#endif
+ if (format == AV_PIX_FMT_VULKAN) {
+ return SDL_TRUE;
+ }
}
if (GetTextureFormat(format) != SDL_PIXELFORMAT_UNKNOWN) {
@@ -409,7 +443,21 @@ static AVCodecContext *OpenVideoStream(AVFormatContext *ic, int stream, const AV
}
} else
#endif
- {
+ if (vulkan_context && type == AV_HWDEVICE_TYPE_VULKAN) {
+ AVVulkanDeviceContext *device_context;
+
+ context->hw_device_ctx = av_hwdevice_ctx_alloc(type);
+
+ device_context = (AVVulkanDeviceContext *)((AVHWDeviceContext *)context->hw_device_ctx->data)->hwctx;
+ SetupVulkanDeviceContextData(vulkan_context, device_context);
+
+ result = av_hwdevice_ctx_init(context->hw_device_ctx);
+ if (result < 0) {
+ SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Couldn't create hardware device context: %s", av_err2str(result));
+ } else {
+ SDL_Log("Using %s hardware acceleration with pixel format %s\n", av_hwdevice_get_type_name(config->device_type), av_get_pix_fmt_name(config->pix_fmt));
+ }
+ } else {
result = av_hwdevice_ctx_create(&context->hw_device_ctx, type, NULL, NULL, 0);
if (result < 0) {
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Couldn't create hardware device context: %s", av_err2str(result));
@@ -799,6 +847,22 @@ static SDL_bool GetTextureForVideoToolboxFrame(AVFrame *frame, SDL_Texture **tex
#endif
}
+static SDL_bool GetTextureForVulkanFrame(AVFrame *frame, SDL_Texture **texture)
+{
+ SDL_PropertiesID props;
+ if (*texture) {
+ SDL_DestroyTexture(*texture);
+ }
+
+ props = CreateVideoTextureProperties(frame, SDL_PIXELFORMAT_UNKNOWN, SDL_TEXTUREACCESS_STATIC, frame->width, frame->height);
+ *texture = CreateVulkanVideoTexture(vulkan_context, frame, renderer, props);
+ SDL_DestroyProperties(props);
+ if (!*texture) {
+ return SDL_FALSE;
+ }
+ return SDL_TRUE;
+}
+
static SDL_bool GetTextureForFrame(AVFrame *frame, SDL_Texture **texture)
{
switch (frame->format) {
@@ -810,6 +874,8 @@ static SDL_bool GetTextureForFrame(AVFrame *frame, SDL_Texture **texture)
return GetTextureForD3D11Frame(frame, texture);
case AV_PIX_FMT_VIDEOTOOLBOX:
return GetTextureForVideoToolboxFrame(frame, texture);
+ case AV_PIX_FMT_VULKAN:
+ return GetTextureForVulkanFrame(frame, texture);
default:
return GetTextureForMemoryFrame(frame, texture);
}
@@ -1277,6 +1343,9 @@ int main(int argc, char *argv[])
avcodec_free_context(&video_context);
avformat_close_input(&ic);
SDL_DestroyRenderer(renderer);
+ if (vulkan_context) {
+ DestroyVulkanVideoContext(vulkan_context);
+ }
SDL_DestroyWindow(window);
SDL_Quit();
SDLTest_CommonDestroyState(state);
diff --git a/test/testffmpeg_vulkan.c b/test/testffmpeg_vulkan.c
new file mode 100644
index 0000000000000..5d7fbc5d02788
--- /dev/null
+++ b/test/testffmpeg_vulkan.c
@@ -0,0 +1,974 @@
+/*
+ Copyright (C) 1997-2024 Sam Lantinga <slouken@libsdl.org>
+
+ This software is provided 'as-is', without any express or implied
+ warranty. In no event will the authors be held liable for any damages
+ arising from the use of this software.
+
+ Permission is granted to anyone to use this software for any purpose,
+ including commercial applications, and to alter it and redistribute it
+ freely.
+*/
+#include <SDL3/SDL.h>
+#include <SDL3/SDL_vulkan.h>
+
+#include <libavutil/hwcontext.h>
+#include <libavutil/hwcontext_vulkan.h>
+
+#include "testffmpeg_vulkan.h"
+
+#define VULKAN_FUNCTIONS() \
+ VULKAN_GLOBAL_FUNCTION(vkCreateInstance) \
+ VULKAN_GLOBAL_FUNCTION(vkEnumerateInstanceExtensionProperties) \
+ VULKAN_GLOBAL_FUNCTION(vkEnumerateInstanceLayerProperties) \
+ VULKAN_INSTANCE_FUNCTION(vkCreateDevice) \
+ VULKAN_INSTANCE_FUNCTION(vkDestroyInstance) \
+ VULKAN_INSTANCE_FUNCTION(vkDestroySurfaceKHR) \
+ VULKAN_INSTANCE_FUNCTION(vkEnumerateDeviceExtensionProperties) \
+ VULKAN_INSTANCE_FUNCTION(vkEnumeratePhysicalDevices) \
+ VULKAN_INSTANCE_FUNCTION(vkGetDeviceProcAddr) \
+ VULKAN_INSTANCE_FUNCTION(vkGetPhysicalDeviceFeatures2) \
+ VULKAN_INSTANCE_FUNCTION(vkGetPhysicalDeviceQueueFamilyProperties) \
+ VULKAN_INSTANCE_FUNCTION(vkGetPhysicalDeviceSurfaceSupportKHR) \
+ VULKAN_INSTANCE_FUNCTION(vkQueueWaitIdle) \
+ VULKAN_DEVICE_FUNCTION(vkAllocateCommandBuffers) \
+ VULKAN_DEVICE_FUNCTION(vkBeginCommandBuffer) \
+ VULKAN_DEVICE_FUNCTION(vkCmdPipelineBarrier2) \
+ VULKAN_DEVICE_FUNCTION(vkCreateCommandPool) \
+ VULKAN_DEVICE_FUNCTION(vkCreateSemaphore) \
+ VULKAN_DEVICE_FUNCTION(vkDestroyCommandPool) \
+ VULKAN_DEVICE_FUNCTION(vkDestroyDevice) \
+ VULKAN_DEVICE_FUNCTION(vkDestroySemaphore) \
+ VULKAN_DEVICE_FUNCTION(vkDeviceWaitIdle) \
+ VULKAN_DEVICE_FUNCTION(vkEndCommandBuffer) \
+ VULKAN_DEVICE_FUNCTION(vkFreeCommandBuffers) \
+ VULKAN_DEVICE_FUNCTION(vkGetDeviceQueue) \
+ VULKAN_DEVICE_FUNCTION(vkQueueSubmit) \
+\
+VULKAN_INSTANCE_FUNCTION(vkGetPhysicalDeviceVideoFormatPropertiesKHR) \
+
+typedef struct
+{
+ VkPhysicalDeviceFeatures2 device_features;
+ VkPhysicalDeviceVulkan11Features device_features_1_1;
+ VkPhysicalDeviceVulkan12Features device_features_1_2;
+ VkPhysicalDeviceVulkan13Features device_features_1_3;
+ VkPhysicalDeviceDescriptorBufferFeaturesEXT desc_buf_features;
+ VkPhysicalDeviceShaderAtomicFloatFeaturesEXT atomic_float_features;
+ VkPhysicalDeviceCooperativeMatrixFeaturesKHR coop_matrix_features;
+} VulkanDeviceFeatures;
+
+struct VulkanVideoContext
+{
+ VkInstance instance;
+ VkSurfaceKHR surface;
+ VkPhysicalDevice physicalDevice;
+ int presentQueueFamilyIndex;
+ int presentQueueCount;
+ int graphicsQueueFamilyIndex;
+ int graphicsQueueCount;
+ int transferQueueFamilyIndex;
+ int transferQueueCount;
+ int computeQueueFamilyIndex;
+ int computeQueueCount;
+ int decodeQueueFamilyIndex;
+ int decodeQueueCount;
+ VkDevice device;
+ VkQueue graphicsQueue;
+ VkCommandPool commandPool;
+ VkCommandBuffer *commandBuffers;
+ uint32_t commandBufferCount;
+ uint32_t commandBufferIndex;
+ VkSemaphore *waitSemaphores;
+ uint32_t waitSemaphoreCount;
+ VkSemaphore *signalSemaphores;
+ uint32_t signalSemaphoreCount;
+
+ const char **instanceExtensions;
+ int instanceExtensionsCount;
+
+ const char **deviceExtensions;
+ int deviceExtensionsCount;
+
+ VulkanDeviceFeatures features;
+
+ PFN_vkGetInstanceProcAddr vkGetInstanceProcAddr;
+#define VULKAN_GLOBAL_FUNCTION(name) PFN_##name name;
+#define VULKAN_INSTANCE_FUNCTION(name) PFN_##name name;
+#define VULKAN_DEVICE_FUNCTION(name) PFN_##name name;
+ VULKAN_FUNCTIONS()
+#undef VULKAN_GLOBAL_FUNCTION
+#undef VULKAN_INSTANCE_FUNCTION
+#undef VULKAN_DEVICE_FUNCTION
+};
+
+
+static int loadGlobalFunctions(VulkanVideoContext *context)
+{
+ context->vkGetInstanceProcAddr = (PFN_vkGetInstanceProcAddr)SDL_Vulkan_GetVkGetInstanceProcAddr();
+ if (!context->vkGetInstanceProcAddr) {
+ return -1;
+ }
+
+#define VULKAN_GLOBAL_FUNCTION(name) \
+ context->name = (PFN_##name)context->vkGetInstanceProcAddr(VK_NULL_HANDLE, #name); \
+ if (!context->name) { \
+ return SDL_SetError("vkGetInstanceProcAddr(VK_NULL_HANDLE, \"" #name "\") failed"); \
+ }
+#define VULKAN_INSTANCE_FUNCTION(name)
+#define VULKAN_DEVICE_FUNCTION(name)
+ VULKAN_FUNCTIONS()
+#undef VULKAN_GLOBAL_FUNCTION
+#undef VULKAN_INSTANCE_FUNCTION
+#undef VULKAN_DEVICE_FUNCTION
+ return 0;
+}
+
+static int loadInstanceFunctions(VulkanVideoContext *context)
+{
+#define VULKAN_GLOBAL_FUNCTION(name)
+#define VULKAN_INSTANCE_FUNCTION(name) \
+ context->name = (PFN_##name)context->vkGetInstanceProcAddr(context->instance, #name); \
+ if (!context->name) { \
+ return SDL_SetError("vkGetInstanceProcAddr(instance, \"" #name "\") failed"); \
+ }
+#define VULKAN_DEVICE_FUNCTION(name)
+ VULKAN_FUNCTIONS()
+#undef VULKAN_GLOBAL_FUNCTION
+#undef VULKAN_INSTANCE_FUNCTION
+#undef VULKAN_DEVICE_FUNCTION
+ return 0;
+}
+
+static int loadDeviceFunctions(VulkanVideoContext *context)
+{
+#define VULKAN_GLOBAL_FUNCTION(name)
+#define VULKAN_INSTANCE_FUNCTION(name)
+#define VULKAN_DEVICE_FUNCTION(name) \
+ context->name = (PFN_##name)context->vkGetDeviceProcAddr(context->device, #name); \
+ if (!context->name) { \
+ return SDL_SetError("vkGetDeviceProcAddr(device, \"" #name "\") failed\n"); \
+ }
+ VULKAN_FUNCTIONS()
+#undef VULKAN_GLOBAL_FUNCTION
+#undef VULKAN_INSTANCE_FUNCTION
+#undef VULKAN_DEVICE_FUNCTION
+ return 0;
+}
+
+#undef VULKAN_FUNCTIONS
+
+static const char *getVulkanResultString(VkResult result)
+{
+ switch ((int)result) {
+#define RESULT_CASE(x) \
+ case x: \
+ return #x
+ RESULT_CASE(VK_SUCCESS);
+ RESULT_CASE(VK_NOT_READY);
+ RESULT_CASE(VK_TIMEOUT);
+ RESULT_CASE(VK_EVENT_SET);
+ RESULT_CASE(VK_EVENT_RESET);
+ RESULT_CASE(VK_INCOMPLETE);
+ RESULT_CASE(VK_ERROR_OUT_OF_HOST_MEMORY);
+ RESULT_CASE(VK_ERROR_OUT_OF_DEVICE_MEMORY);
+ RESULT_CASE(VK_ERROR_INITIALIZATION_FAILED);
+ RESULT_CASE(VK_ERROR_DEVICE_LOST);
+ RESULT_CASE(VK_ERROR_MEMORY_MAP_FAILED);
+ RESULT_CASE(VK_ERROR_LAYER_NOT_PRESENT);
+ RESULT_CASE(VK_ERROR_EXTENSION_NOT_PRESENT);
+ RESULT_CASE(VK_ERROR_FEATURE_NOT_PRESENT);
+ RESULT_CASE(VK_ERROR_INCOMPATIBLE_DRIVER);
+ RESULT_CASE(VK_ERROR_TOO_MANY_OBJECTS);
+ RESULT_CASE(VK_ERROR_FORMAT_NOT_SUPPORTED);
+ RESULT_CASE(VK_ERROR_FRAGMENTED_POOL);
+ RESULT_CASE(VK_ERROR_SURFACE_LOST_KHR);
+ RESULT_CASE(VK_ERROR_NATIVE_WINDOW_IN_USE_KHR);
+ RESULT_CASE(VK_SUBOPTIMAL_KHR);
+ RESULT_CASE(VK_ERROR_OUT_OF_DATE_KHR);
+ RESULT_CASE(VK_ERROR_INCOMPATIBLE_DISPLAY_KHR);
+ RESULT_CASE(VK_ERROR_VALIDATION_FAILED_EXT);
+ RESULT_CASE(VK_ERROR_OUT_OF_POOL_MEMORY_KHR);
+ RESULT_CASE(VK_ERROR_INVALID_SHADER_NV);
+#undef RESULT_CASE
+ default:
+ break;
+ }
+ return (result < 0) ? "VK_ERROR_<Unknown>" : "VK_<Unknown>";
+}
+
+static int createInstance(VulkanVideoContext *context)
+{
+ static const char *optional_extensions[] = {
+ VK_EXT_SWAPCHAIN_COLOR_SPACE_EXTENSION_NAME,
+ VK_KHR_GET_PHYSICAL_DEVICE_PROPERTIES_2_EXTENSION_NAME
+ };
+ VkApplicationInfo appInfo = { 0 };
+ VkInstanceCreateInfo instanceCreateInfo = { 0 };
+ VkResult result;
+ char const *const *instanceExtensions = SDL_Vulkan_GetInstanceExtensions(&instanceCreateInfo.enabledExtensionCount);
+
+ appInfo.sType = VK_STRUCTURE_TYPE_APPLICATION_INFO;
+ appInfo.apiVersion = VK_API_VERSION_1_3;
+ instanceCreateInfo.sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO;
+ instanceCreateInfo.pApplicationInfo = &appInfo;
+
+ const char **instanceExtensionsCopy = SDL_calloc(instanceCreateInfo.enabledExtensionCount + SDL_arraysize(optional_extensions), sizeof(const char *));
+ for (uint32_t i = 0; i < instanceCreateInfo.enabledExtensionCount; i++) {
+ instanceExtensionsCopy[i] = instanceExtensions[i];
+ }
+
+ // Get the rest of the optional extensions
+ {
+ uint32_t extensionCount;
+ if (context->vkEnumerateInstanceExtensionProperties(NULL, &extensionCount, NULL) == VK_SUCCESS && extensionCount > 0) {
+ VkExtensionProperties *extensionProperties = SDL_calloc(sizeof(VkExtensionProperties), extensionCount);
+ if (context->vkEnumerateInstanceExtensionProperties(NULL, &extensionCount, extensionProperties) == VK_SUCCESS) {
+ for (uint32_t i = 0; i < SDL_arraysize(optional_extensions); ++i) {
+ for (uint32_t j = 0; j < extensionCount; ++j) {
+ if (SDL_strcmp(extensionProperties[j].extensionName, optional_extensions[i]) == 0) {
+ instanceExtensionsCopy[instanceCreateInfo.enabledExtensionCount++] = optional_extensions[i];
+ break;
+ }
+ }
+ }
+ }
+ SDL_free(extensionProperties);
+ }
+ }
+ instanceCreateInfo.ppEnabledExtensionNames = instanceExtensionsCopy;
+
+ context->instanceExtensions = instanceExtensionsCopy;
+ context->instanceExtensionsCount = instanceCreateInfo.enabledExtensionCount;
+
+ result = context->vkCreateInstance(&instanceCreateInfo, NULL, &context->instance);
+ SDL_free((void *)instanceExtensionsCopy);
+ if (result != VK_SUCCESS) {
+ context->instance = VK_NULL_HANDLE;
+ return SDL_SetError("vkCreateInstance(): %s\n", getVulkanResultString(result));
+ }
+ if (loadInstanceFunctions(context) < 0) {
+ return -1;
+ }
+ return 0;
+}
+
+static int createSurface(VulkanVideoContext *context, SDL_Window *window)
+{
+ if (!SDL_Vulkan_CreateSurface(window,
+ context->instance,
+ NULL,
+ &context->surface)) {
+ context->surface = VK_NULL_HANDLE;
+ return -1;
+ }
+ return 0;
+}
+
+// Use the same queue scoring algorithm as ffmpeg to make sure we get the same device configuration
+static int selectQueueFamily(VkQueueFamilyProperties *queueFamiliesProperties, uint32_t queueFamiliesCount, VkQueueFlagBits flags, int *queueCount)
+{
+ uint32_t queueFamilyIndex;
+ uint32_t selectedQueueFamilyIndex = queueFamiliesCount;
+ uint32_t min_score = ~0u;
+
+ for (queueFamilyIndex = 0; queueFamilyIndex < queueFamiliesCount; ++queueFamilyIndex) {
+ VkQueueFlagBits current_flags = queueFamiliesProperties[queueFamilyIndex].queueFlags;
+ if (current_flags & flags) {
+ uint32_t score = av_popcount(current_flags) + queueFamiliesProperties[queueFamilyIndex].timestampValidBits;
+ if (score < min_score) {
+ selectedQueueFamilyIndex = queueFamilyIndex;
+ min_score = score;
+ }
+ }
+ }
+
+ if (selectedQueueFamilyIndex != queueFamiliesCount) {
+ VkQueueFamilyProperties *selectedQueueFamily = &queueFamiliesProperties[selectedQueueFamilyIndex];
+ *queueCount = (int)selectedQueueFamily->queueCount;
+ ++selectedQueueFamily->timestampValidBits;
+ return (int)selectedQueueFamilyIndex;
+ } else {
+ *queueCount = 0;
+ return -1;
+ }
+}
+
+static int findPhysicalDevice(VulkanVideoContext *context)
+{
+ uint32_t physicalDeviceCount = 0;
+ VkPhysicalDevice *physicalDevices;
+ VkQueueFamilyProperties *queueFamiliesProperties = NULL;
+ uint32_t queueFamiliesPropertiesAllocatedSize = 0;
+ VkExtensionProperties *deviceExtensions = NULL;
+ uint32_t deviceExtensionsAllocatedSize = 0;
+ uint32_t physicalDeviceIndex;
+ VkResult result;
+
+ result = context->vkEnumeratePhysicalDevices(context->instance, &physicalDeviceCount, NULL);
+ if (result != VK_SUCCESS) {
+ return SDL_SetError("vkEnumeratePhysicalDevices(): %s", getVulkanResultString(result));
+ }
+ if (physicalDeviceCount == 0) {
+ return SDL_SetError("vkEnumeratePhysicalDevices(): no physical devices");
+ }
+ physicalDevices = (VkPhysicalDevice *)SDL_malloc(sizeof(VkPhysicalDevice) * physicalDeviceCount);
+ if (!physicalDevices) {
+ return -1;
+ }
+ result = context->vkEnumeratePhysicalDevices(context->instance, &physicalDeviceCount, physicalDevices);
+ if (result != VK_SUCCESS) {
+ SDL_free(physicalDevices);
+ return SDL_SetError("vkEnumeratePhysicalDevices(): %s", getVulkanResultString(result));
+ }
+ context->physicalDevice = NULL;
+ for (physicalDeviceIndex = 0; physicalDeviceIndex < physicalDeviceCount; physicalDeviceIndex++) {
+ uint32_t queueFamiliesCount = 0;
+ uint32_t queueFamilyIndex;
+ uint32_t deviceExtensionCount = 0;
+ SDL_bool hasSwapchainExtension = SDL_FALSE;
+ uint32_t i;
+
+ VkPhysicalDevice physicalDevice = physicalDevices[physicalDeviceIndex];
+ context->vkGetPhysicalDeviceQueueFamilyProperties(physicalDevice, &queueFamiliesCount, NULL);
+ if (queueFamiliesCount == 0) {
+ continue;
+ }
+ if (queueFamiliesPropertiesAllocatedSize < queueFamiliesCount) {
+ SDL_free(queueFamiliesProperties);
+ queueFamiliesPropertiesAllocatedSize = queueFamiliesCount;
+ queueFamiliesProperties = (VkQueueFamilyProperties *)SDL_malloc(sizeof(VkQueueFamilyProperties) * queueFamiliesPropertiesAllocatedSize);
+ if (!queueFamiliesProperties) {
+ SDL_free(physicalDevices);
+ SDL_free(deviceExtensions);
+ return -1;
+ }
+ }
+ context->vkGetPhysicalDeviceQueueFamilyProperties(physicalDevice, &queueFamiliesCount, queueFamiliesProperties);
+
+ // Initialize timestampValidBits for scoring in selectQueueFamily
+ for (queueFamilyIndex = 0; queueFamilyIndex < queueFamiliesCount; queueFamilyIndex++) {
+ queueFamiliesProperties[queueFamilyIndex].timestampValidBits = 0;
+ }
+ context->presentQueueFamilyIndex = -1;
+ context->graphicsQueueFamilyIndex = -1;
+ for (queueFamilyIndex = 0; queueFamilyIndex < queueFamiliesCount; queueFamilyIndex++) {
+ VkBool32 supported = 0;
+
+ if (queueFamiliesProperties[queueFamilyIndex].queueCount == 0) {
+ continue;
+ }
+
+ if (queueFamiliesProperties[queueFamilyIndex].queueFlags & VK_QUEUE_GRAPHICS_BIT) {
+ context->graphicsQueueFamilyIndex = queueFamilyIndex;
+ }
+
+ result = context->vkGetPhysicalDeviceSurfaceSupportKHR(physicalDevice, queueFamilyIndex, context->surface, &supported);
+ if (result == VK_SUCCESS) {
+ if (supported) {
+ context->presentQueueFamilyIndex = queueFamilyIndex;
+ if (queueFamiliesProperties[queueFamilyIndex].queueFlags & VK_QUEUE_GRAPHICS_BIT) {
+ break; // use this queue because it can present and do graphics
+ }
+ }
+ }
+ }
+ if (context->presentQueueFamilyIndex < 0 || context->graphicsQueueFamilyIndex < 0) {
+ // We can't render and present on this device
+ continue;
+ }
+
+ context->presentQueueCount = queueFamiliesProperties[context->presentQueueFamilyIndex].queueCount;
+ ++queueFamiliesProperties[context->presentQueueFamilyIndex].timestampValidBits;
+ context->graphicsQueueCount = queueFamiliesProperties[context->graphicsQueueFamilyIndex].queueCount;
+ ++queueFamiliesProperties[context->graphicsQueueFamilyIndex].timestampValidBits;
+
+ context->transferQueueFamilyIndex = selectQueueFamily(queueFamiliesProperties, queueFamiliesCount, VK_QUEUE_TRANSFER_BIT, &context->transferQueueCount);
+ context->computeQueueFamilyIndex = selectQueueFamily(queueFamiliesProperties, queueFamiliesCount, VK_QUEUE_COMPUTE_BIT, &context->computeQueueCount);
+ context->decodeQueueFamilyIndex = selectQueueFamily(queueFamiliesProperties, queueFamiliesCount, VK_QUEUE_VIDEO_DECODE_BIT_KHR, &context->decodeQueueCount);
+ if (context->transferQueueFamilyIndex < 0) {
+ // ffmpeg can fall back to the compute or graphics queues for this
+ context->transferQueueFamilyIndex = selectQueueFamily(queueFamiliesProperties, queueFamiliesCount, VK_QUEUE_COMPUTE_BIT, &context->transferQueueCount);
+ if (context->transferQueueFamilyIndex < 0) {
+ context->transferQueueFamilyIndex = selectQueueFamily(queueFamiliesProperties, queueFamiliesCount, VK_QUEUE_GRAPHICS_BIT, &context->transferQueueCount);
+ }
+ }
+
+ if (context->transferQueueFamilyIndex < 0 ||
+ context->computeQueueFamilyIndex < 0) {
+ // This device doesn't have the queues we need for video decoding
+ continue;
+ }
+
+ result = context->vkEnumerateDeviceExtensionProperties(physicalDevice, NULL, &deviceExtensionCount, NULL);
+ if (result != VK_SUCCESS) {
+ SDL_free(physicalDevices);
+ SDL_free(queueFamiliesProperties);
+ SDL_free(deviceExtensions);
+ return SDL_SetError("vkEnumerateDeviceExtensionProperties(): %s", getVulkanResultString(result));
+ }
+ if (deviceExtensionCount == 0) {
+ continue;
+ }
+ if (deviceExtensionsAllocatedSize < deviceExtensionCount) {
+ SDL_free(deviceExtensions);
+ deviceExtensionsAllocatedSize = deviceExtensionCount;
+ deviceExtensions = SDL_malloc(sizeof(VkExtensionProperties) * deviceExtensionsAllocatedSize);
+ if (!deviceExtensions) {
+ SDL_free(physicalDevices);
+ SDL_free(queueFamiliesProperties);
+ return -1;
+ }
+ }
+ result = context->vkEnumerateDeviceExtensionProperties(physicalDevice, NULL, &deviceExtensionCount, deviceExtensions);
+ if (result != VK_SUCCESS) {
+ SDL_free(physicalDevices);
+ SDL_free(queueFamiliesProperties);
+ SDL_free(deviceExtensions);
+ return SDL_SetError("vkEnumerateDeviceExtensionProperties(): %s", getVulkanResultString(result));
+ }
+ for (i = 0; i < deviceExtensionCount; i++) {
+ if (SDL_strcmp(deviceExtensions[i].extensionName, VK_KHR_SWAPCHAIN_EXTENSION_NAME) == 0) {
+ hasSwapchainExtension = SDL_TRUE;
+ break;
+ }
+ }
+ if (!hasSwapchainExtension) {
+ continue;
+ }
+ context->physicalDevice = physicalDevice;
+ break;
+ }
+ SDL_free(physicalDevices);
+ SDL_free(queueFamiliesProperties);
+ SDL_free(deviceExtensions);
+ if (!context->physicalDevice) {
+ return SDL_SetError("Vulkan: no viable physical devices found");
+ }
+ return 0;
+}
+
+static void initDeviceFeatures(VulkanDeviceFeatures *features)
+{
+ features->device_features.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FEATURES_2;
+ features->device_features.pNext = &features->device_features_1_1;
+ features->device_features_1_1.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_VULKAN_1_1_FEATURES;
+ features->device_features_1_1.pNext = &features->device_features_1_2;
+ features->device_features_1_2.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_VULKAN_1_2_FEATURES;
+ features->device_features_1_2.pNext = &features->device_features_1_3;
+ features->device_features_1_3.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_VULKAN_1_3_FEATURES;
+ features->device_features_1_3.pNext = &features->desc_buf_features;
+ features->desc_buf_features.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_DESCRIPTOR_BUFFER_FEATURES_EXT;
+ features->desc_buf_features.pNext = &features->atomic_float_features;
+ features->atomic_float_features.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_SHADER_ATOMIC_FLOAT_FEATURES_EXT;
+ features->atomic_float_features.pNext = &features->coop_matrix_features;
+ features->coop_matrix_features.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_COOPERATIVE_MATRIX_FEATURES_KHR;
+ features->coop_matrix_features.pNext = NULL;
+}
+
+static void copyDeviceFeatures(VulkanDeviceFeatures *supported_features, VulkanD
(Patch may be truncated, please check the link at the top of this post.)