mirror of https://github.com/libsdl-org/SDL
975 lines
43 KiB
C
975 lines
43 KiB
C
|
/*
|
||
|
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, VulkanDeviceFeatures *requested_features)
|
||
|
{
|
||
|
#define COPY_OPTIONAL_FEATURE(X) requested_features->X = supported_features->X
|
||
|
COPY_OPTIONAL_FEATURE(device_features.features.shaderImageGatherExtended);
|
||
|
COPY_OPTIONAL_FEATURE(device_features.features.shaderStorageImageReadWithoutFormat);
|
||
|
COPY_OPTIONAL_FEATURE(device_features.features.shaderStorageImageWriteWithoutFormat);
|
||
|
COPY_OPTIONAL_FEATURE(device_features.features.fragmentStoresAndAtomics);
|
||
|
COPY_OPTIONAL_FEATURE(device_features.features.vertexPipelineStoresAndAtomics);
|
||
|
COPY_OPTIONAL_FEATURE(device_features.features.shaderInt64);
|
||
|
COPY_OPTIONAL_FEATURE(device_features.features.shaderInt16);
|
||
|
COPY_OPTIONAL_FEATURE(device_features.features.shaderFloat64);
|
||
|
COPY_OPTIONAL_FEATURE(device_features_1_1.samplerYcbcrConversion);
|
||
|
COPY_OPTIONAL_FEATURE(device_features_1_1.storagePushConstant16);
|
||
|
COPY_OPTIONAL_FEATURE(device_features_1_2.bufferDeviceAddress);
|
||
|
COPY_OPTIONAL_FEATURE(device_features_1_2.hostQueryReset);
|
||
|
COPY_OPTIONAL_FEATURE(device_features_1_2.storagePushConstant8);
|
||
|
COPY_OPTIONAL_FEATURE(device_features_1_2.shaderInt8);
|
||
|
COPY_OPTIONAL_FEATURE(device_features_1_2.storageBuffer8BitAccess);
|
||
|
COPY_OPTIONAL_FEATURE(device_features_1_2.uniformAndStorageBuffer8BitAccess);
|
||
|
COPY_OPTIONAL_FEATURE(device_features_1_2.shaderFloat16);
|
||
|
COPY_OPTIONAL_FEATURE(device_features_1_2.shaderSharedInt64Atomics);
|
||
|
COPY_OPTIONAL_FEATURE(device_features_1_2.vulkanMemoryModel);
|
||
|
COPY_OPTIONAL_FEATURE(device_features_1_2.vulkanMemoryModelDeviceScope);
|
||
|
COPY_OPTIONAL_FEATURE(device_features_1_2.hostQueryReset);
|
||
|
COPY_OPTIONAL_FEATURE(device_features_1_3.dynamicRendering);
|
||
|
COPY_OPTIONAL_FEATURE(device_features_1_3.maintenance4);
|
||
|
COPY_OPTIONAL_FEATURE(device_features_1_3.synchronization2);
|
||
|
COPY_OPTIONAL_FEATURE(device_features_1_3.computeFullSubgroups);
|
||
|
COPY_OPTIONAL_FEATURE(device_features_1_3.shaderZeroInitializeWorkgroupMemory);
|
||
|
COPY_OPTIONAL_FEATURE(desc_buf_features.descriptorBuffer);
|
||
|
COPY_OPTIONAL_FEATURE(desc_buf_features.descriptorBufferPushDescriptors);
|
||
|
COPY_OPTIONAL_FEATURE(atomic_float_features.shaderBufferFloat32Atomics);
|
||
|
COPY_OPTIONAL_FEATURE(atomic_float_features.shaderBufferFloat32AtomicAdd);
|
||
|
COPY_OPTIONAL_FEATURE(coop_matrix_features.cooperativeMatrix);
|
||
|
#undef COPY_OPTIONAL_FEATURE
|
||
|
|
||
|
// timeline semaphores is required by ffmpeg
|
||
|
requested_features->device_features_1_2.timelineSemaphore = 1;
|
||
|
}
|
||
|
|
||
|
static int addQueueFamily(VkDeviceQueueCreateInfo **pQueueCreateInfos, uint32_t *pQueueCreateInfoCount, uint32_t queueFamilyIndex, uint32_t queueCount)
|
||
|
{
|
||
|
VkDeviceQueueCreateInfo *queueCreateInfo;
|
||
|
VkDeviceQueueCreateInfo *queueCreateInfos = *pQueueCreateInfos;
|
||
|
uint32_t queueCreateInfoCount = *pQueueCreateInfoCount;
|
||
|
float *queuePriorities;
|
||
|
|
||
|
if (queueCount == 0) {
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
for (uint32_t i = 0; i < queueCreateInfoCount; ++i) {
|
||
|
if (queueCreateInfos[i].queueFamilyIndex == queueFamilyIndex) {
|
||
|
return 0;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
queueCreateInfos = (VkDeviceQueueCreateInfo *)SDL_realloc(queueCreateInfos, (queueCreateInfoCount + 1) * sizeof(*queueCreateInfos));
|
||
|
if (!queueCreateInfos) {
|
||
|
return -1;
|
||
|
}
|
||
|
|
||
|
queuePriorities = (float *)SDL_malloc(queueCount * sizeof(*queuePriorities));
|
||
|
if (!queuePriorities) {
|
||
|
return -1;
|
||
|
}
|
||
|
|
||
|
for (uint32_t i = 0; i < queueCount; ++i) {
|
||
|
queuePriorities[i] = 1.0f / queueCount;
|
||
|
}
|
||
|
|
||
|
queueCreateInfo = &queueCreateInfos[queueCreateInfoCount++];
|
||
|
SDL_zerop(queueCreateInfo);
|
||
|
queueCreateInfo->sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO;
|
||
|
queueCreateInfo->queueFamilyIndex = queueFamilyIndex;
|
||
|
queueCreateInfo->queueCount = queueCount;
|
||
|
queueCreateInfo->pQueuePriorities = queuePriorities;
|
||
|
|
||
|
*pQueueCreateInfos = queueCreateInfos;
|
||
|
*pQueueCreateInfoCount = queueCreateInfoCount;
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
static int createDevice(VulkanVideoContext *context)
|
||
|
{
|
||
|
static const char *const deviceExtensionNames[] = {
|
||
|
VK_KHR_SWAPCHAIN_EXTENSION_NAME,
|
||
|
VK_KHR_SAMPLER_YCBCR_CONVERSION_EXTENSION_NAME,
|
||
|
VK_KHR_MAINTENANCE1_EXTENSION_NAME,
|
||
|
VK_KHR_BIND_MEMORY_2_EXTENSION_NAME,
|
||
|
VK_KHR_GET_MEMORY_REQUIREMENTS_2_EXTENSION_NAME,
|
||
|
};
|
||
|
static const char *optional_extensions[] = {
|
||
|
VK_KHR_VIDEO_QUEUE_EXTENSION_NAME,
|
||
|
VK_KHR_VIDEO_DECODE_QUEUE_EXTENSION_NAME,
|
||
|
VK_KHR_VIDEO_DECODE_H264_EXTENSION_NAME,
|
||
|
VK_KHR_VIDEO_DECODE_H265_EXTENSION_NAME,
|
||
|
"VK_MESA_video_decode_av1"
|
||
|
};
|
||
|
VkDeviceCreateInfo deviceCreateInfo = { 0 };
|
||
|
VkDeviceQueueCreateInfo *queueCreateInfos = NULL;
|
||
|
uint32_t queueCreateInfoCount = 0;
|
||
|
VulkanDeviceFeatures supported_features;
|
||
|
VkResult result = VK_ERROR_UNKNOWN;
|
||
|
|
||
|
if (addQueueFamily(&queueCreateInfos, &queueCreateInfoCount, context->presentQueueFamilyIndex, context->presentQueueCount) < 0 ||
|
||
|
addQueueFamily(&queueCreateInfos, &queueCreateInfoCount, context->graphicsQueueFamilyIndex, context->graphicsQueueCount) < 0 ||
|
||
|
addQueueFamily(&queueCreateInfos, &queueCreateInfoCount, context->transferQueueFamilyIndex, context->transferQueueCount) < 0 ||
|
||
|
addQueueFamily(&queueCreateInfos, &queueCreateInfoCount, context->computeQueueFamilyIndex, context->computeQueueCount) < 0 ||
|
||
|
addQueueFamily(&queueCreateInfos, &queueCreateInfoCount, context->decodeQueueFamilyIndex, context->decodeQueueCount) < 0) {
|
||
|
goto done;
|
||
|
}
|
||
|
|
||
|
initDeviceFeatures(&supported_features);
|
||
|
initDeviceFeatures(&context->features);
|
||
|
context->vkGetPhysicalDeviceFeatures2(context->physicalDevice, &supported_features.device_features);
|
||
|
copyDeviceFeatures(&supported_features, &context->features);
|
||
|
|
||
|
deviceCreateInfo.sType = VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO;
|
||
|
deviceCreateInfo.queueCreateInfoCount = queueCreateInfoCount;
|
||
|
deviceCreateInfo.pQueueCreateInfos = queueCreateInfos;
|
||
|
deviceCreateInfo.pEnabledFeatures = NULL;
|
||
|
deviceCreateInfo.enabledExtensionCount = SDL_arraysize(deviceExtensionNames);
|
||
|
deviceCreateInfo.pNext = &context->features.device_features;
|
||
|
|
||
|
const char **deviceExtensionsCopy = SDL_calloc(deviceCreateInfo.enabledExtensionCount + SDL_arraysize(optional_extensions), sizeof(const char *));
|
||
|
for (uint32_t i = 0; i < deviceCreateInfo.enabledExtensionCount; i++) {
|
||
|
deviceExtensionsCopy[i] = deviceExtensionNames[i];
|
||
|
}
|
||
|
|
||
|
// Get the rest of the optional extensions
|
||
|
{
|
||
|
uint32_t extensionCount;
|
||
|
if (context->vkEnumerateDeviceExtensionProperties(context->physicalDevice, NULL, &extensionCount, NULL) == VK_SUCCESS && extensionCount > 0) {
|
||
|
VkExtensionProperties *extensionProperties = SDL_calloc(sizeof(VkExtensionProperties), extensionCount);
|
||
|
if (context->vkEnumerateDeviceExtensionProperties(context->physicalDevice, 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) {
|
||
|
deviceExtensionsCopy[deviceCreateInfo.enabledExtensionCount++] = optional_extensions[i];
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
SDL_free(extensionProperties);
|
||
|
}
|
||
|
}
|
||
|
deviceCreateInfo.ppEnabledExtensionNames = deviceExtensionsCopy;
|
||
|
|
||
|
context->deviceExtensions = deviceExtensionsCopy;
|
||
|
context->deviceExtensionsCount = deviceCreateInfo.enabledExtensionCount;
|
||
|
|
||
|
result = context->vkCreateDevice(context->physicalDevice, &deviceCreateInfo, NULL, &context->device);
|
||
|
if (result != VK_SUCCESS) {
|
||
|
SDL_SetError("vkCreateDevice(): %s", getVulkanResultString(result));
|
||
|
goto done;
|
||
|
}
|
||
|
|
||
|
if (loadDeviceFunctions(context) < 0) {
|
||
|
result = VK_ERROR_UNKNOWN;
|
||
|
context->device = VK_NULL_HANDLE;
|
||
|
goto done;
|
||
|
}
|
||
|
|
||
|
// Get the graphics queue that SDL will use
|
||
|
context->vkGetDeviceQueue(context->device, context->graphicsQueueFamilyIndex, 0, &context->graphicsQueue);
|
||
|
|
||
|
// Create a command pool
|
||
|
VkCommandPoolCreateInfo commandPoolCreateInfo = { 0 };
|
||
|
commandPoolCreateInfo.sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO;
|
||
|
commandPoolCreateInfo.flags = VK_COMMAND_POOL_CREATE_RESET_COMMAND_BUFFER_BIT;
|
||
|
commandPoolCreateInfo.queueFamilyIndex = context->graphicsQueueFamilyIndex;
|
||
|
result = context->vkCreateCommandPool(context->device, &commandPoolCreateInfo, NULL, &context->commandPool);
|
||
|
if (result != VK_SUCCESS) {
|
||
|
SDL_SetError("vkCreateCommandPool(): %s", getVulkanResultString(result));
|
||
|
goto done;
|
||
|
}
|
||
|
|
||
|
done:
|
||
|
for (uint32_t i = 0; i < queueCreateInfoCount; ++i) {
|
||
|
SDL_free((void *)queueCreateInfos[i].pQueuePriorities);
|
||
|
}
|
||
|
SDL_free(queueCreateInfos);
|
||
|
|
||
|
if (result != VK_SUCCESS) {
|
||
|
return -1;
|
||
|
}
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
VulkanVideoContext *CreateVulkanVideoContext(SDL_Window *window)
|
||
|
{
|
||
|
VulkanVideoContext *context = SDL_calloc(1, sizeof(*context));
|
||
|
if (!context) {
|
||
|
return NULL;
|
||
|
}
|
||
|
if (loadGlobalFunctions(context) < 0 ||
|
||
|
createInstance(context) < 0 ||
|
||
|
createSurface(context, window) < 0 ||
|
||
|
findPhysicalDevice(context) < 0 ||
|
||
|
createDevice(context) < 0) {
|
||
|
DestroyVulkanVideoContext(context);
|
||
|
return NULL;
|
||
|
}
|
||
|
return context;
|
||
|
}
|
||
|
|
||
|
void SetupVulkanRenderProperties(VulkanVideoContext *context, SDL_PropertiesID props)
|
||
|
{
|
||
|
SDL_SetProperty(props, SDL_PROP_RENDERER_CREATE_VULKAN_INSTANCE_POINTER, context->instance);
|
||
|
SDL_SetNumberProperty(props, SDL_PROP_RENDERER_CREATE_VULKAN_SURFACE_NUMBER, (Sint64)context->surface);
|
||
|
SDL_SetProperty(props, SDL_PROP_RENDERER_CREATE_VULKAN_PHYSICAL_DEVICE_POINTER, context->physicalDevice);
|
||
|
SDL_SetProperty(props, SDL_PROP_RENDERER_CREATE_VULKAN_DEVICE_POINTER, context->device);
|
||
|
SDL_SetNumberProperty(props, SDL_PROP_RENDERER_CREATE_VULKAN_PRESENT_QUEUE_FAMILY_INDEX_NUMBER, context->presentQueueFamilyIndex);
|
||
|
SDL_SetNumberProperty(props, SDL_PROP_RENDERER_CREATE_VULKAN_GRAPHICS_QUEUE_FAMILY_INDEX_NUMBER, context->graphicsQueueFamilyIndex);
|
||
|
}
|
||
|
|
||
|
void SetupVulkanDeviceContextData(VulkanVideoContext *context, AVVulkanDeviceContext *ctx)
|
||
|
{
|
||
|
ctx->get_proc_addr = context->vkGetInstanceProcAddr;
|
||
|
ctx->inst = context->instance;
|
||
|
ctx->phys_dev = context->physicalDevice;
|
||
|
ctx->act_dev = context->device;
|
||
|
ctx->device_features = context->features.device_features;
|
||
|
ctx->enabled_inst_extensions = context->instanceExtensions;
|
||
|
ctx->nb_enabled_inst_extensions = context->instanceExtensionsCount;
|
||
|
ctx->enabled_dev_extensions = context->deviceExtensions;
|
||
|
ctx->nb_enabled_dev_extensions = context->deviceExtensionsCount;
|
||
|
ctx->queue_family_index = context->graphicsQueueFamilyIndex;
|
||
|
ctx->nb_graphics_queues = context->graphicsQueueCount;
|
||
|
ctx->queue_family_tx_index = context->transferQueueFamilyIndex;
|
||
|
ctx->nb_tx_queues = context->transferQueueCount;
|
||
|
ctx->queue_family_comp_index = context->computeQueueFamilyIndex;
|
||
|
ctx->nb_comp_queues = context->computeQueueCount;
|
||
|
ctx->queue_family_encode_index = -1;
|
||
|
ctx->nb_encode_queues = 0;
|
||
|
ctx->queue_family_decode_index = context->decodeQueueFamilyIndex;
|
||
|
ctx->nb_decode_queues = context->decodeQueueCount;
|
||
|
}
|
||
|
|
||
|
static int CreateCommandBuffers(VulkanVideoContext *context, SDL_Renderer *renderer)
|
||
|
{
|
||
|
uint32_t commandBufferCount = (uint32_t)SDL_GetNumberProperty(SDL_GetRendererProperties(renderer), SDL_PROP_RENDERER_VULKAN_SWAPCHAIN_IMAGE_COUNT_NUMBER, 1);
|
||
|
|
||
|
if (commandBufferCount > context->waitSemaphoreCount) {
|
||
|
VkSemaphore *semaphores = (VkSemaphore *)SDL_realloc(context->waitSemaphores, commandBufferCount * sizeof(*semaphores));
|
||
|
if (!semaphores) {
|
||
|
return -1;
|
||
|
}
|
||
|
context->waitSemaphores = semaphores;
|
||
|
|
||
|
VkSemaphoreCreateInfo semaphoreCreateInfo = { 0 };
|
||
|
semaphoreCreateInfo.sType = VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO;
|
||
|
while (context->waitSemaphoreCount < commandBufferCount) {
|
||
|
VkResult result = context->vkCreateSemaphore(context->device, &semaphoreCreateInfo, NULL, &context->waitSemaphores[context->waitSemaphoreCount]);
|
||
|
if (result != VK_SUCCESS) {
|
||
|
SDL_SetError("vkCreateSemaphore(): %s", getVulkanResultString(result));
|
||
|
return -1;
|
||
|
}
|
||
|
++context->waitSemaphoreCount;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if (commandBufferCount > context->signalSemaphoreCount) {
|
||
|
VkSemaphore *semaphores = (VkSemaphore *)SDL_realloc(context->signalSemaphores, commandBufferCount * sizeof(*semaphores));
|
||
|
if (!semaphores) {
|
||
|
return -1;
|
||
|
}
|
||
|
context->signalSemaphores = semaphores;
|
||
|
|
||
|
VkSemaphoreCreateInfo semaphoreCreateInfo = { 0 };
|
||
|
semaphoreCreateInfo.sType = VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO;
|
||
|
while (context->signalSemaphoreCount < commandBufferCount) {
|
||
|
VkResult result = context->vkCreateSemaphore(context->device, &semaphoreCreateInfo, NULL, &context->signalSemaphores[context->signalSemaphoreCount]);
|
||
|
if (result != VK_SUCCESS) {
|
||
|
SDL_SetError("vkCreateSemaphore(): %s", getVulkanResultString(result));
|
||
|
return -1;
|
||
|
}
|
||
|
++context->signalSemaphoreCount;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if (commandBufferCount > context->commandBufferCount) {
|
||
|
uint32_t needed = (commandBufferCount - context->commandBufferCount);
|
||
|
VkCommandBuffer *commandBuffers = (VkCommandBuffer *)SDL_realloc(context->commandBuffers, commandBufferCount * sizeof(*commandBuffers));
|
||
|
if (!commandBuffers) {
|
||
|
return -1;
|
||
|
}
|
||
|
context->commandBuffers = commandBuffers;
|
||
|
|
||
|
VkCommandBufferAllocateInfo commandBufferAllocateInfo = { 0 };
|
||
|
commandBufferAllocateInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO;
|
||
|
commandBufferAllocateInfo.commandPool = context->commandPool;
|
||
|
commandBufferAllocateInfo.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY;
|
||
|
commandBufferAllocateInfo.commandBufferCount = needed;
|
||
|
VkResult result = context->vkAllocateCommandBuffers(context->device, &commandBufferAllocateInfo, &context->commandBuffers[context->commandBufferCount]);
|
||
|
if (result != VK_SUCCESS) {
|
||
|
SDL_SetError("vkAllocateCommandBuffers(): %s", getVulkanResultString(result));
|
||
|
return -1;
|
||
|
}
|
||
|
|
||
|
context->commandBufferCount = commandBufferCount;
|
||
|
}
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
static int ScheduleFrameTransferToSDL(VulkanVideoContext *context, AVFrame *frame)
|
||
|
{
|
||
|
AVVkFrame *pVkFrame = (AVVkFrame *)frame->data[0];
|
||
|
|
||
|
VkTimelineSemaphoreSubmitInfo timeline = { 0 };
|
||
|
timeline.sType = VK_STRUCTURE_TYPE_TIMELINE_SEMAPHORE_SUBMIT_INFO;
|
||
|
timeline.waitSemaphoreValueCount = 1;
|
||
|
timeline.pWaitSemaphoreValues = pVkFrame->sem_value;
|
||
|
|
||
|
VkPipelineStageFlags pipelineStageMask = VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT;
|
||
|
VkSubmitInfo submitInfo = { 0 };
|
||
|
submitInfo.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO;
|
||
|
submitInfo.waitSemaphoreCount = 1;
|
||
|
submitInfo.pWaitSemaphores = pVkFrame->sem;
|
||
|
submitInfo.pWaitDstStageMask = &pipelineStageMask;
|
||
|
submitInfo.signalSemaphoreCount = 1;
|
||
|
submitInfo.pSignalSemaphores = &context->waitSemaphores[context->commandBufferIndex];
|
||
|
submitInfo.pNext = &timeline;
|
||
|
|
||
|
if (pVkFrame->layout[0] != VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL) {
|
||
|
VkCommandBuffer commandBuffer = context->commandBuffers[context->commandBufferIndex];
|
||
|
|
||
|
VkCommandBufferBeginInfo beginInfo = { 0 };
|
||
|
beginInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO;
|
||
|
beginInfo.flags = 0;
|
||
|
context->vkBeginCommandBuffer(commandBuffer, &beginInfo);
|
||
|
|
||
|
VkImageMemoryBarrier2 barrier = { 0 };
|
||
|
barrier.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER_2;
|
||
|
barrier.srcAccessMask = VK_ACCESS_2_NONE;
|
||
|
barrier.dstAccessMask = VK_ACCESS_2_SHADER_SAMPLED_READ_BIT;
|
||
|
barrier.oldLayout = pVkFrame->layout[0];
|
||
|
barrier.newLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
|
||
|
barrier.image = pVkFrame->img[0];
|
||
|
barrier.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
|
||
|
barrier.subresourceRange.levelCount = 1;
|
||
|
barrier.subresourceRange.layerCount = 1;
|
||
|
barrier.srcQueueFamilyIndex = pVkFrame->queue_family[0];
|
||
|
barrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
|
||
|
barrier.srcStageMask = VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT;
|
||
|
barrier.dstStageMask = VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT;
|
||
|
|
||
|
VkDependencyInfo dep = { 0 };
|
||
|
dep.sType = VK_STRUCTURE_TYPE_DEPENDENCY_INFO;
|
||
|
dep.dependencyFlags = VK_DEPENDENCY_BY_REGION_BIT;
|
||
|
dep.imageMemoryBarrierCount = 1;
|
||
|
dep.pImageMemoryBarriers = &barrier;
|
||
|
context->vkCmdPipelineBarrier2(commandBuffer, &dep);
|
||
|
|
||
|
context->vkEndCommandBuffer(commandBuffer);
|
||
|
|
||
|
// Add the image barrier to the submit info
|
||
|
submitInfo.commandBufferCount = 1;
|
||
|
submitInfo.pCommandBuffers = &context->commandBuffers[context->commandBufferIndex];
|
||
|
|
||
|
pVkFrame->layout[0] = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
|
||
|
pVkFrame->queue_family[0] = VK_QUEUE_FAMILY_IGNORED;
|
||
|
}
|
||
|
|
||
|
VkResult result = context->vkQueueSubmit(context->graphicsQueue, 1, &submitInfo, 0);
|
||
|
if (result != VK_SUCCESS) {
|
||
|
return SDL_SetError("vkQueueSubmit(): %s", getVulkanResultString(result));
|
||
|
}
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
static int ScheduleFrameTransferToFFMPEG(VulkanVideoContext *context, AVFrame *frame)
|
||
|
{
|
||
|
AVVkFrame *pVkFrame = (AVVkFrame *)frame->data[0];
|
||
|
|
||
|
++pVkFrame->sem_value[0];
|
||
|
|
||
|
VkTimelineSemaphoreSubmitInfo timeline = { 0 };
|
||
|
timeline.sType = VK_STRUCTURE_TYPE_TIMELINE_SEMAPHORE_SUBMIT_INFO;
|
||
|
timeline.signalSemaphoreValueCount = 1;
|
||
|
timeline.pSignalSemaphoreValues = pVkFrame->sem_value;
|
||
|
|
||
|
VkPipelineStageFlags pipelineStageMask = VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT;
|
||
|
VkSubmitInfo submitInfo = { 0 };
|
||
|
submitInfo.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO;
|
||
|
submitInfo.waitSemaphoreCount = 1;
|
||
|
submitInfo.pWaitSemaphores = &context->signalSemaphores[context->commandBufferIndex];
|
||
|
submitInfo.pWaitDstStageMask = &pipelineStageMask;
|
||
|
submitInfo.signalSemaphoreCount = 1;
|
||
|
submitInfo.pSignalSemaphores = pVkFrame->sem;
|
||
|
submitInfo.pNext = &timeline;
|
||
|
|
||
|
VkResult result = context->vkQueueSubmit(context->graphicsQueue, 1, &submitInfo, 0);
|
||
|
if (result != VK_SUCCESS) {
|
||
|
return SDL_SetError("vkQueueSubmit(): %s", getVulkanResultString(result));
|
||
|
}
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
static int PrepareFrameRendering(VulkanVideoContext *context, AVFrame *frame, SDL_Renderer *renderer)
|
||
|
{
|
||
|
AVHWFramesContext *frames = (AVHWFramesContext *)(frame->hw_frames_ctx->data);
|
||
|
AVVulkanFramesContext *vk = (AVVulkanFramesContext *)(frames->hwctx);
|
||
|
AVVkFrame *pVkFrame = (AVVkFrame *)frame->data[0];
|
||
|
|
||
|
if (CreateCommandBuffers(context, renderer) < 0) {
|
||
|
return -1;
|
||
|
}
|
||
|
|
||
|
vk->lock_frame(frames, pVkFrame);
|
||
|
ScheduleFrameTransferToSDL(context, frame);
|
||
|
ScheduleFrameTransferToFFMPEG(context, frame);
|
||
|
vk->unlock_frame(frames, pVkFrame);
|
||
|
|
||
|
SDL_AddVulkanRenderSemaphores(renderer, VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT, (Sint64)context->waitSemaphores[context->commandBufferIndex], (Sint64)context->signalSemaphores[context->commandBufferIndex]);
|
||
|
|
||
|
context->commandBufferIndex = (context->commandBufferIndex + 1) % context->commandBufferCount;
|
||
|
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
SDL_Texture *CreateVulkanVideoTexture(VulkanVideoContext *context, AVFrame *frame, SDL_Renderer *renderer, SDL_PropertiesID props)
|
||
|
{
|
||
|
AVHWFramesContext *frames = (AVHWFramesContext *)(frame->hw_frames_ctx->data);
|
||
|
AVVulkanFramesContext *vk = (AVVulkanFramesContext *)(frames->hwctx);
|
||
|
AVVkFrame *pVkFrame = (AVVkFrame *)frame->data[0];
|
||
|
Uint32 format;
|
||
|
|
||
|
if (PrepareFrameRendering(context, frame, renderer) < 0) {
|
||
|
return NULL;
|
||
|
}
|
||
|
|
||
|
switch (vk->format[0]) {
|
||
|
case VK_FORMAT_G8B8G8R8_422_UNORM:
|
||
|
format = SDL_PIXELFORMAT_YUY2;
|
||
|
break;
|
||
|
case VK_FORMAT_B8G8R8G8_422_UNORM:
|
||
|
format = SDL_PIXELFORMAT_UYVY;
|
||
|
break;
|
||
|
case VK_FORMAT_G8_B8_R8_3PLANE_420_UNORM:
|
||
|
format = SDL_PIXELFORMAT_IYUV;
|
||
|
break;
|
||
|
case VK_FORMAT_G8_B8R8_2PLANE_420_UNORM:
|
||
|
format = SDL_PIXELFORMAT_NV12;
|
||
|
break;
|
||
|
case VK_FORMAT_G10X6_B10X6R10X6_2PLANE_420_UNORM_3PACK16:
|
||
|
format = SDL_PIXELFORMAT_P010;
|
||
|
break;
|
||
|
default:
|
||
|
format = SDL_PIXELFORMAT_UNKNOWN;
|
||
|
break;
|
||
|
}
|
||
|
SDL_SetNumberProperty(props, SDL_PROP_TEXTURE_CREATE_FORMAT_NUMBER, format);
|
||
|
SDL_SetNumberProperty(props, SDL_PROP_TEXTURE_CREATE_VULKAN_TEXTURE_NUMBER, (Sint64)pVkFrame->img[0]);
|
||
|
return SDL_CreateTextureWithProperties(renderer, props);
|
||
|
}
|
||
|
|
||
|
void DestroyVulkanVideoContext(VulkanVideoContext *context)
|
||
|
{
|
||
|
if (context) {
|
||
|
if (context->device) {
|
||
|
context->vkDeviceWaitIdle(context->device);
|
||
|
}
|
||
|
if (context->instanceExtensions) {
|
||
|
SDL_free(context->instanceExtensions);
|
||
|
}
|
||
|
if (context->deviceExtensions) {
|
||
|
SDL_free(context->deviceExtensions);
|
||
|
}
|
||
|
if (context->waitSemaphores) {
|
||
|
for (uint32_t i = 0; i < context->waitSemaphoreCount; ++i) {
|
||
|
context->vkDestroySemaphore(context->device, context->waitSemaphores[i], NULL);
|
||
|
}
|
||
|
SDL_free(context->waitSemaphores);
|
||
|
context->waitSemaphores = NULL;
|
||
|
}
|
||
|
if (context->signalSemaphores) {
|
||
|
for (uint32_t i = 0; i < context->signalSemaphoreCount; ++i) {
|
||
|
context->vkDestroySemaphore(context->device, context->signalSemaphores[i], NULL);
|
||
|
}
|
||
|
SDL_free(context->signalSemaphores);
|
||
|
context->signalSemaphores = NULL;
|
||
|
}
|
||
|
if (context->commandBuffers) {
|
||
|
context->vkFreeCommandBuffers(context->device, context->commandPool, context->commandBufferCount, context->commandBuffers);
|
||
|
SDL_free(context->commandBuffers);
|
||
|
context->commandBuffers = NULL;
|
||
|
}
|
||
|
if (context->commandPool) {
|
||
|
context->vkDestroyCommandPool(context->device, context->commandPool, NULL);
|
||
|
context->commandPool = VK_NULL_HANDLE;
|
||
|
}
|
||
|
if (context->device) {
|
||
|
context->vkDestroyDevice(context->device, NULL);
|
||
|
}
|
||
|
if (context->surface) {
|
||
|
context->vkDestroySurfaceKHR(context->instance, context->surface, NULL);
|
||
|
}
|
||
|
if (context->instance) {
|
||
|
context->vkDestroyInstance(context->instance, NULL);
|
||
|
}
|
||
|
SDL_free(context);
|
||
|
}
|
||
|
}
|