SDL: Vulkan Renderer - implement YcBcCr using VK_KHR_sampler_ycbcr_conversion. (#9169)

From ad036d43e97c5cce1d196c0f85b0fc10c69fa70d Mon Sep 17 00:00:00 2001
From: Dan Ginsburg <[EMAIL REDACTED]>
Date: Wed, 28 Feb 2024 11:57:09 -0500
Subject: [PATCH] Vulkan Renderer - implement YcBcCr using
 VK_KHR_sampler_ycbcr_conversion. (#9169)

* Vulkan Renderer - implement YcBcCr using VK_KHR_sampler_ycbcr_conversion.  This simplifies the shader code and will also allow support for additional formats that we don't yet support (such as SDL_PIXELFORMAT_P010 for ffmpeg). The renderer now queries for VK_KHR_sampler_ycbcr_conversion and dependent extensions and will enable it if it's present. It reimplements YUV/NV12 texture support using this extension (these formats are no longer supported if the extension is not present). For each YUV/NV12 texture, a VkSamplerYcbcrConversion object is created from SDL_Colorspace parameters.  This is passed to the VkImageView and also an additional sampler is created for Ycbcr.  Instead of using 1-3 textures, the shaders now all use 1 texture with a combined image sampler (required by the extension).  Further, when using Ycbcr, a separate descriptor set layout is baked with the Ycbcr sampler as an immutable sampler.  The code to copy the images now copies to the individual image planes.  The swizzling between formats is handled in the VkSamplerYcbcrConversion object.
---
 src/render/vulkan/SDL_render_vulkan.c         | 752 ++++++++++--------
 .../vulkan/VULKAN_PixelShader_Advanced.h      | 554 ++++++-------
 src/render/vulkan/VULKAN_PixelShader_Colors.h |  80 +-
 .../vulkan/VULKAN_PixelShader_Common.incl     |  61 +-
 .../vulkan/VULKAN_PixelShader_Textures.h      | 102 ++-
 src/render/vulkan/compile_shaders.bat         |   6 +-
 6 files changed, 728 insertions(+), 827 deletions(-)

diff --git a/src/render/vulkan/SDL_render_vulkan.c b/src/render/vulkan/SDL_render_vulkan.c
index 61524341ec94..14c9a9103319 100644
--- a/src/render/vulkan/SDL_render_vulkan.c
+++ b/src/render/vulkan/SDL_render_vulkan.c
@@ -129,15 +129,26 @@ extern const char *SDL_Vulkan_GetResultString(VkResult result);
     VULKAN_INSTANCE_FUNCTION(vkGetPhysicalDeviceSurfaceFormatsKHR)      \
     VULKAN_INSTANCE_FUNCTION(vkGetPhysicalDeviceSurfacePresentModesKHR) \
     VULKAN_INSTANCE_FUNCTION(vkGetPhysicalDeviceSurfaceSupportKHR)      \
-    VULKAN_INSTANCE_FUNCTION(vkQueueWaitIdle)
-
-#define VULKAN_DEVICE_FUNCTION(name)   static PFN_##name name = NULL;
-#define VULKAN_GLOBAL_FUNCTION(name)   static PFN_##name name = NULL;
-#define VULKAN_INSTANCE_FUNCTION(name) static PFN_##name name = NULL;
+    VULKAN_INSTANCE_FUNCTION(vkQueueWaitIdle)                           \
+    VULKAN_OPTIONAL_INSTANCE_FUNCTION(vkGetPhysicalDeviceFeatures2KHR)              \
+    VULKAN_OPTIONAL_INSTANCE_FUNCTION(vkGetPhysicalDeviceFormatProperties2KHR)      \
+    VULKAN_OPTIONAL_INSTANCE_FUNCTION(vkGetPhysicalDeviceImageFormatProperties2KHR) \
+    VULKAN_OPTIONAL_INSTANCE_FUNCTION(vkGetPhysicalDeviceMemoryProperties2KHR)      \
+    VULKAN_OPTIONAL_INSTANCE_FUNCTION(vkGetPhysicalDeviceProperties2KHR)            \
+    VULKAN_OPTIONAL_DEVICE_FUNCTION(vkCreateSamplerYcbcrConversionKHR)              \
+    VULKAN_OPTIONAL_DEVICE_FUNCTION(vkDestroySamplerYcbcrConversionKHR)             \
+
+#define VULKAN_DEVICE_FUNCTION(name)            static PFN_##name name = NULL;
+#define VULKAN_GLOBAL_FUNCTION(name)            static PFN_##name name = NULL;
+#define VULKAN_INSTANCE_FUNCTION(name)          static PFN_##name name = NULL;
+#define VULKAN_OPTIONAL_INSTANCE_FUNCTION(name) static PFN_##name name = NULL;
+#define VULKAN_OPTIONAL_DEVICE_FUNCTION(name)   static PFN_##name name = NULL;
 VULKAN_FUNCTIONS()
 #undef VULKAN_DEVICE_FUNCTION
 #undef VULKAN_GLOBAL_FUNCTION
 #undef VULKAN_INSTANCE_FUNCTION
+#undef VULKAN_OPTIONAL_INSTANCE_FUNCTION
+#undef VULKAN_OPTIONAL_DEVICE_FUNCTION
 
 /* Renderpass types */
 typedef enum {
@@ -165,12 +176,6 @@ typedef struct
 //static const float TONEMAP_LINEAR = 1;
 static const float TONEMAP_CHROME = 2;
 
-//static const float TEXTURETYPE_NONE = 0;
-static const float TEXTURETYPE_RGB = 1;
-static const float TEXTURETYPE_NV12 = 2;
-static const float TEXTURETYPE_NV21 = 3;
-static const float TEXTURETYPE_YUV = 4;
-
 static const float INPUTTYPE_UNSPECIFIED = 0;
 static const float INPUTTYPE_SRGB = 1;
 static const float INPUTTYPE_SCRGB = 2;
@@ -180,9 +185,9 @@ static const float INPUTTYPE_HDR10 = 3;
 typedef struct
 {
     float scRGB_output;
-    float texture_type;
     float input_type;
     float color_scale;
+	float unused_pad0;
 
     float tonemap_method;
     float tonemap_factor1;
@@ -236,14 +241,14 @@ typedef struct
     const float *YCbCr_matrix;
 
 #if SDL_HAVE_YUV
-    /* YV12 texture support */
-    SDL_bool yuv;
-    VULKAN_Image mainImageU;
-    VULKAN_Image mainImageV;
-
-    /* NV12 texture support */
-    SDL_bool nv12;
-    VULKAN_Image mainImageUV;
+    /* Object passed to VkImageView and VkSampler for doing Ycbcr -> RGB conversion */
+    VkSamplerYcbcrConversion samplerYcbcrConversion;
+    /* Sampler created with samplerYcbcrConversion, passed to PSO as immutable sampler */
+    VkSampler samplerYcbcr;
+    /* Descriptor set layout with samplerYcbcr baked as immutable sampler */
+    VkDescriptorSetLayout descriptorSetLayoutYcbcr;
+    /* Pipeline layout with immutable sampler descriptor set layout */
+    VkPipelineLayout pipelineLayoutYcbcr;
 #endif
 
 } VULKAN_TextureData;
@@ -257,6 +262,7 @@ typedef struct
     VkPrimitiveTopology topology;
     VkFormat format;
     VkPipelineLayout pipelineLayout;
+    VkDescriptorSetLayout descriptorSetLayout;
     VkPipeline pipeline;
 } VULKAN_PipelineState;
 
@@ -299,8 +305,8 @@ typedef struct
 
     VkShaderModule vertexShaderModules[NUM_SHADERS];
     VkShaderModule fragmentShaderModules[NUM_SHADERS];
-    VkDescriptorSetLayout descriptorSetLayouts[NUM_SHADERS];
-    VkPipelineLayout pipelineLayouts[NUM_SHADERS];
+    VkDescriptorSetLayout descriptorSetLayout;
+    VkPipelineLayout pipelineLayout;
 
     /* Vertex buffer data */
     VULKAN_Buffer vertexBuffers[SDL_VULKAN_NUM_VERTEX_BUFFERS];
@@ -327,6 +333,8 @@ typedef struct
     VULKAN_PipelineState *currentPipelineState;
 
     SDL_bool supportsEXTSwapchainColorspace;
+    SDL_bool supportsKHRGetPhysicalDeviceProperties2;
+    SDL_bool supportsKHRSamplerYcBcrConversion;
     uint32_t surfaceFormatsAllocatedCount;
     uint32_t surfaceFormatsCount;
     uint32_t swapchainDesiredImageCount;
@@ -357,7 +365,6 @@ typedef struct
 Uint32 VULKAN_VkFormatToSDLPixelFormat(VkFormat vkFormat)
 {
     switch (vkFormat) {
-
     case VK_FORMAT_B8G8R8A8_UNORM:
         return SDL_PIXELFORMAT_ARGB8888;
     case VK_FORMAT_A2R10G10B10_UNORM_PACK32:
@@ -369,6 +376,18 @@ Uint32 VULKAN_VkFormatToSDLPixelFormat(VkFormat vkFormat)
     }
 }
 
+Uint32 VULKAN_VkFormatGetNumPlanes(VkFormat vkFormat)
+{
+    switch (vkFormat) {
+    case VK_FORMAT_G8_B8_R8_3PLANE_420_UNORM:
+        return 3;
+    case VK_FORMAT_G8_B8R8_2PLANE_420_UNORM:
+        return 2;
+    default:
+        return 1;
+    }
+}
+
 VkDeviceSize VULKAN_GetBytesPerPixel(VkFormat vkFormat)
 {
     switch (vkFormat) {
@@ -402,17 +421,23 @@ static VkFormat SDLPixelFormatToVkTextureFormat(Uint32 format, Uint32 colorspace
             return VK_FORMAT_B8G8R8A8_SRGB;
         }
         return VK_FORMAT_B8G8R8A8_UNORM;
+    case SDL_PIXELFORMAT_YUY2:
+        return VK_FORMAT_G8B8G8R8_422_UNORM;
+    case SDL_PIXELFORMAT_UYVY:
+        return VK_FORMAT_B8G8R8G8_422_UNORM;
     case SDL_PIXELFORMAT_YV12:
     case SDL_PIXELFORMAT_IYUV:
-    case SDL_PIXELFORMAT_NV12: /* Y plane */
-    case SDL_PIXELFORMAT_NV21: /* Y plane */
-        return VK_FORMAT_R8_UNORM;
+        return VK_FORMAT_G8_B8_R8_3PLANE_420_UNORM;
+    case SDL_PIXELFORMAT_NV12:
+    case SDL_PIXELFORMAT_NV21:
+        return  VK_FORMAT_G8_B8R8_2PLANE_420_UNORM;
     case SDL_PIXELFORMAT_P010:
-        return VK_FORMAT_R16_UNORM;
+        return VK_FORMAT_G10X6_B10X6R10X6_2PLANE_420_UNORM_3PACK16;
     default:
         return VK_FORMAT_UNDEFINED;
     }
 }
+
 static void VULKAN_DestroyTexture(SDL_Renderer *renderer, SDL_Texture *texture);
 static void VULKAN_DestroyBuffer(VULKAN_RenderData *rendererData, VULKAN_Buffer *vulkanBuffer);
 static void VULKAN_DestroyImage(VULKAN_RenderData *rendererData, VULKAN_Image *vulkanImage);
@@ -420,6 +445,7 @@ static void VULKAN_ResetCommandList(VULKAN_RenderData *rendererData);
 static SDL_bool VULKAN_FindMemoryTypeIndex(VULKAN_RenderData *rendererData, uint32_t typeBits, VkMemoryPropertyFlags requiredFlags, VkMemoryPropertyFlags desiredFlags, uint32_t *memoryTypeIndexOut);
 static VkResult VULKAN_CreateWindowSizeDependentResources(SDL_Renderer *renderer);
 static VkDescriptorPool VULKAN_AllocateDescriptorPool(VULKAN_RenderData *rendererData);
+static VkResult VULKAN_CreateDescriptorSetAndPipelineLayout(VULKAN_RenderData *rendererData, VkSampler samplerYcbcr, VkDescriptorSetLayout *descriptorSetLayoutOut, VkPipelineLayout *pipelineLayoutOut);
 
 static void VULKAN_DestroyAll(SDL_Renderer *renderer)
 {
@@ -531,14 +557,14 @@ static void VULKAN_DestroyAll(SDL_Renderer *renderer)
             vkDestroyShaderModule(rendererData->device, rendererData->fragmentShaderModules[i], NULL);
             rendererData->fragmentShaderModules[i] = VK_NULL_HANDLE;
         }
-        if (rendererData->descriptorSetLayouts[i] != VK_NULL_HANDLE) {
-            vkDestroyDescriptorSetLayout(rendererData->device, rendererData->descriptorSetLayouts[i], NULL);
-            rendererData->descriptorSetLayouts[i] = VK_NULL_HANDLE;
-        }
-        if (rendererData->pipelineLayouts[i] != VK_NULL_HANDLE) {
-            vkDestroyPipelineLayout(rendererData->device, rendererData->pipelineLayouts[i], NULL);
-            rendererData->pipelineLayouts[i] = VK_NULL_HANDLE;
-        }
+    }
+    if (rendererData->descriptorSetLayout != VK_NULL_HANDLE) {
+        vkDestroyDescriptorSetLayout(rendererData->device, rendererData->descriptorSetLayout, NULL);
+        rendererData->descriptorSetLayout = VK_NULL_HANDLE;
+    }
+    if (rendererData->pipelineLayout != VK_NULL_HANDLE) {
+        vkDestroyPipelineLayout(rendererData->device, rendererData->pipelineLayout, NULL);
+        rendererData->pipelineLayout = VK_NULL_HANDLE;
     }
     for (int i = 0; i < rendererData->pipelineStateCount; i++) {
         vkDestroyPipeline(rendererData->device, rendererData->pipelineStates[i].pipeline, NULL);
@@ -672,12 +698,16 @@ static void VULKAN_DestroyImage(VULKAN_RenderData *rendererData, VULKAN_Image *v
     SDL_memset(vulkanImage, 0, sizeof(VULKAN_Image));
 }
 
-static VkResult VULKAN_AllocateImage(VULKAN_RenderData *rendererData, uint32_t width, uint32_t height, VkFormat format, VkImageUsageFlags imageUsage, VkComponentMapping swizzle, VkImage externalImage, VULKAN_Image *imageOut)
+static VkResult VULKAN_AllocateImage(VULKAN_RenderData *rendererData, uint32_t width, uint32_t height, VkFormat format,
+    VkImageUsageFlags imageUsage, VkComponentMapping swizzle, VkImage externalImage,
+    VkSamplerYcbcrConversionKHR samplerYcbcrConversion,
+    VULKAN_Image *imageOut)
 {
     VkResult result;
     VkImageCreateInfo imageCreateInfo = { 0 };
+    VkSamplerYcbcrConversionInfoKHR samplerYcbcrConversionInfo = { 0 };
 
-    SDL_memset(imageOut, 0, sizeof( VULKAN_Image));
+    SDL_memset(imageOut, 0, sizeof(VULKAN_Image));
     imageOut->format = format;
     imageOut->imageLayout = VK_IMAGE_LAYOUT_UNDEFINED;
 
@@ -751,6 +781,14 @@ static VkResult VULKAN_AllocateImage(VULKAN_RenderData *rendererData, uint32_t w
     imageViewCreateInfo.subresourceRange.levelCount = 1;
     imageViewCreateInfo.subresourceRange.baseArrayLayer = 0;
     imageViewCreateInfo.subresourceRange.layerCount = 1;
+
+    /* If it's a YcBcBr image, we need to pass the conversion info to the VkImageView (and the VkSampler) */
+    if (samplerYcbcrConversion != VK_NULL_HANDLE) {
+        samplerYcbcrConversionInfo.sType = VK_STRUCTURE_TYPE_SAMPLER_YCBCR_CONVERSION_INFO_KHR;
+        samplerYcbcrConversionInfo.conversion = samplerYcbcrConversion;
+        imageViewCreateInfo.pNext = &samplerYcbcrConversionInfo;
+    }
+    
     result = vkCreateImageView(rendererData->device, &imageViewCreateInfo, NULL, &imageOut->imageView);
     if (result != VK_SUCCESS) {
         VULKAN_DestroyImage(rendererData, imageOut);
@@ -1034,7 +1072,7 @@ static VkBlendOp GetBlendOp(SDL_BlendOperation operation)
 
 
 static VULKAN_PipelineState *VULKAN_CreatePipelineState(SDL_Renderer *renderer,
-    VULKAN_Shader shader, SDL_BlendMode blendMode, VkPrimitiveTopology topology, VkFormat format)
+    VULKAN_Shader shader, VkPipelineLayout pipelineLayout, VkDescriptorSetLayout descriptorSetLayout, SDL_BlendMode blendMode, VkPrimitiveTopology topology, VkFormat format)
 {
     VULKAN_RenderData *rendererData = (VULKAN_RenderData *)renderer->driverdata;
     VULKAN_PipelineState *pipelineStates;
@@ -1159,7 +1197,7 @@ static VULKAN_PipelineState *VULKAN_CreatePipelineState(SDL_Renderer *renderer,
     /* Renderpass / layout */
     pipelineCreateInfo.renderPass = rendererData->currentRenderPass;
     pipelineCreateInfo.subpass = 0;
-    pipelineCreateInfo.layout = rendererData->pipelineLayouts[shader];
+    pipelineCreateInfo.layout = pipelineLayout;
 
     result = vkCreateGraphicsPipelines(rendererData->device, VK_NULL_HANDLE, 1, &pipelineCreateInfo, NULL, &pipeline);
     if (result != VK_SUCCESS) {
@@ -1173,6 +1211,7 @@ static VULKAN_PipelineState *VULKAN_CreatePipelineState(SDL_Renderer *renderer,
     pipelineStates[rendererData->pipelineStateCount].topology = topology;
     pipelineStates[rendererData->pipelineStateCount].format = format;
     pipelineStates[rendererData->pipelineStateCount].pipeline = pipeline;
+    pipelineStates[rendererData->pipelineStateCount].descriptorSetLayout = descriptorSetLayout;
     pipelineStates[rendererData->pipelineStateCount].pipelineLayout = pipelineCreateInfo.layout;
     rendererData->pipelineStates = pipelineStates;
     ++rendererData->pipelineStateCount;
@@ -1244,10 +1283,14 @@ static int VULKAN_LoadGlobalFunctions(VULKAN_RenderData *rendererData)
         return -1;                                                                     \
     }
 #define VULKAN_INSTANCE_FUNCTION(name)
+#define VULKAN_OPTIONAL_INSTANCE_FUNCTION(name)
+#define VULKAN_OPTIONAL_DEVICE_FUNCTION(name)
     VULKAN_FUNCTIONS()
 #undef VULKAN_DEVICE_FUNCTION
 #undef VULKAN_GLOBAL_FUNCTION
 #undef VULKAN_INSTANCE_FUNCTION
+#undef VULKAN_OPTIONAL_INSTANCE_FUNCTION
+#undef VULKAN_OPTIONAL_DEVICE_FUNCTION
 
     return 0;
 }
@@ -1263,10 +1306,16 @@ static int VULKAN_LoadInstanceFunctions(VULKAN_RenderData *rendererData)
                      "vkGetInstanceProcAddr(instance, \"" #name "\") failed\n");            \
         return -1;                                                                          \
     }
+#define VULKAN_OPTIONAL_INSTANCE_FUNCTION(name)                                             \
+    name = (PFN_##name)rendererData->vkGetInstanceProcAddr(rendererData->instance, #name);
+#define VULKAN_OPTIONAL_DEVICE_FUNCTION(name)
+
     VULKAN_FUNCTIONS()
 #undef VULKAN_DEVICE_FUNCTION
 #undef VULKAN_GLOBAL_FUNCTION
 #undef VULKAN_INSTANCE_FUNCTION
+#undef VULKAN_OPTIONAL_INSTANCE_FUNCTION
+#undef VULKAN_OPTIONAL_DEVICE_FUNCTION
 
     return 0;
 }
@@ -1281,11 +1330,16 @@ static int VULKAN_LoadDeviceFunctions(VULKAN_RenderData *rendererData)
         return -1;                                                           \
     }
 #define VULKAN_GLOBAL_FUNCTION(name)
+#define VULKAN_OPTIONAL_DEVICE_FUNCTION(name)                                \
+    name = (PFN_##name)vkGetDeviceProcAddr(rendererData->device, #name);
 #define VULKAN_INSTANCE_FUNCTION(name)
+#define VULKAN_OPTIONAL_INSTANCE_FUNCTION(name)
     VULKAN_FUNCTIONS()
 #undef VULKAN_DEVICE_FUNCTION
 #undef VULKAN_GLOBAL_FUNCTION
 #undef VULKAN_INSTANCE_FUNCTION
+#undef VULKAN_OPTIONAL_INSTANCE_FUNCTION
+#undef VULKAN_OPTIONAL_DEVICE_FUNCTION
     return 0;
 }
 
@@ -1476,6 +1530,40 @@ static VkSemaphore VULKAN_CreateSemaphore(VULKAN_RenderData *rendererData)
     return semaphore;
 }
 
+static SDL_bool VULKAN_DeviceExtensionsFound(VULKAN_RenderData *rendererData, int extensionsToCheck, const char* const* extNames)
+{
+    uint32_t extensionCount;
+    SDL_bool foundExtensions = SDL_TRUE;
+    VkResult result = vkEnumerateDeviceExtensionProperties(rendererData->physicalDevice, NULL, &extensionCount, NULL);
+    if (result != VK_SUCCESS ) {
+        SDL_LogError(SDL_LOG_CATEGORY_RENDER, "vkEnumerateDeviceExtensionProperties(): %s.\n", SDL_Vulkan_GetResultString(result));
+        return SDL_FALSE;
+    }
+    if (extensionCount > 0 ) {
+        VkExtensionProperties *extensionProperties = SDL_calloc(sizeof(VkExtensionProperties), extensionCount);
+        result = vkEnumerateDeviceExtensionProperties(rendererData->physicalDevice, NULL, &extensionCount, extensionProperties);
+        if (result != VK_SUCCESS ) {
+            SDL_LogError(SDL_LOG_CATEGORY_RENDER, "vkEnumerateDeviceExtensionProperties): %s.\n", SDL_Vulkan_GetResultString(result));
+            SDL_free(extensionProperties);
+            return SDL_FALSE;
+        }
+        for (uint32_t ext = 0; ext < extensionsToCheck && foundExtensions; ext++) {
+            SDL_bool foundExtension = SDL_FALSE;
+            for (uint32_t i = 0; i< extensionCount; i++) {
+                if (SDL_strcmp(extensionProperties[i].extensionName, extNames[ext]) == 0) {
+                    foundExtension = SDL_TRUE;
+                    break;
+                }
+            }
+            foundExtensions &= foundExtension;
+        }
+        
+        SDL_free(extensionProperties);
+    }
+
+    return foundExtensions;
+}
+
 static SDL_bool VULKAN_InstanceExtensionFound(VULKAN_RenderData *rendererData, const char *extName)
 {
     uint32_t extensionCount;
@@ -1553,6 +1641,7 @@ static VkResult VULKAN_CreateDeviceResources(SDL_Renderer *renderer, SDL_Propert
     }
 
     /* Check for colorspace extension */
+    rendererData->supportsEXTSwapchainColorspace = VK_FALSE;
     if (renderer->output_colorspace == SDL_COLORSPACE_SRGB_LINEAR ||
         renderer->output_colorspace == SDL_COLORSPACE_HDR10) {
         rendererData->supportsEXTSwapchainColorspace = VULKAN_InstanceExtensionFound(rendererData, VK_EXT_SWAPCHAIN_COLOR_SPACE_EXTENSION_NAME);
@@ -1561,6 +1650,9 @@ static VkResult VULKAN_CreateDeviceResources(SDL_Renderer *renderer, SDL_Propert
         }
     }
 
+    /* Check for VK_KHR_get_physical_device_properties2 */
+    rendererData->supportsKHRGetPhysicalDeviceProperties2 = VULKAN_InstanceExtensionFound(rendererData, VK_KHR_GET_PHYSICAL_DEVICE_PROPERTIES_2_EXTENSION_NAME);
+
     /* Create VkInstance */
     rendererData->instance = (VkInstance)SDL_GetProperty(create_props, SDL_PROP_RENDERER_CREATE_VULKAN_INSTANCE_POINTER, NULL);
     if (rendererData->instance) {
@@ -1574,7 +1666,7 @@ static VkResult VULKAN_CreateDeviceResources(SDL_Renderer *renderer, SDL_Propert
         instanceCreateInfo.pApplicationInfo = &appInfo;
         char const *const *instanceExtensions = SDL_Vulkan_GetInstanceExtensions(&instanceCreateInfo.enabledExtensionCount);
 
-        const char **instanceExtensionsCopy = SDL_calloc(instanceCreateInfo.enabledExtensionCount + 1, sizeof(const char *));
+        const char **instanceExtensionsCopy = SDL_calloc(instanceCreateInfo.enabledExtensionCount + 2, sizeof(const char *));
         for (uint32_t i = 0; i < instanceCreateInfo.enabledExtensionCount; i++) {
             instanceExtensionsCopy[i] = instanceExtensions[i];
         }
@@ -1582,6 +1674,10 @@ static VkResult VULKAN_CreateDeviceResources(SDL_Renderer *renderer, SDL_Propert
             instanceExtensionsCopy[instanceCreateInfo.enabledExtensionCount] = VK_EXT_SWAPCHAIN_COLOR_SPACE_EXTENSION_NAME;
             instanceCreateInfo.enabledExtensionCount++;
         }
+        if (rendererData->supportsKHRGetPhysicalDeviceProperties2) {
+            instanceExtensionsCopy[instanceCreateInfo.enabledExtensionCount] = VK_KHR_GET_PHYSICAL_DEVICE_PROPERTIES_2_EXTENSION_NAME;
+            instanceCreateInfo.enabledExtensionCount++;
+        }
         instanceCreateInfo.ppEnabledExtensionNames = (const char *const *)instanceExtensionsCopy;
         if (createDebug && VULKAN_ValidationLayersFound()) {
             instanceCreateInfo.ppEnabledLayerNames = validationLayerName;
@@ -1632,6 +1728,8 @@ static VkResult VULKAN_CreateDeviceResources(SDL_Renderer *renderer, SDL_Propert
         rendererData->presentQueueFamilyIndex = (uint32_t)SDL_GetNumberProperty(create_props, SDL_PROP_RENDERER_CREATE_VULKAN_PRESENT_QUEUE_FAMILY_INDEX_NUMBER, 0);
     }
 
+
+
     /* Create Vulkan device */
     rendererData->device = (VkDevice)SDL_GetProperty(create_props, SDL_PROP_RENDERER_CREATE_VULKAN_DEVICE_POINTER, NULL);
     if (rendererData->device) {
@@ -1642,13 +1740,24 @@ static VkResult VULKAN_CreateDeviceResources(SDL_Renderer *renderer, SDL_Propert
         VkDeviceCreateInfo deviceCreateInfo = { 0 };
         static const char *const deviceExtensionNames[] = {
             VK_KHR_SWAPCHAIN_EXTENSION_NAME,
+             /* VK_KHR_sampler_ycbcr_conversion + dependent extensions.
+                Note VULKAN_DeviceExtensionsFound() call below, if these get moved in this
+                array, update that check too.
+            */
+            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,
         };
-
+        if (rendererData->supportsKHRGetPhysicalDeviceProperties2 &&
+            VULKAN_DeviceExtensionsFound(rendererData, 4, &deviceExtensionNames[1])) {
+            rendererData->supportsKHRSamplerYcBcrConversion = SDL_TRUE;
+        }
         deviceCreateInfo.sType = VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO;
         deviceCreateInfo.queueCreateInfoCount = 0;
         deviceCreateInfo.pQueueCreateInfos = deviceQueueCreateInfo;
         deviceCreateInfo.pEnabledFeatures = NULL;
-        deviceCreateInfo.enabledExtensionCount = SDL_arraysize(deviceExtensionNames);
+        deviceCreateInfo.enabledExtensionCount = (rendererData->supportsKHRSamplerYcBcrConversion) ? 5 : 1;
         deviceCreateInfo.ppEnabledExtensionNames = deviceExtensionNames;
 
         deviceQueueCreateInfo[0].sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO;
@@ -1721,73 +1830,14 @@ static VkResult VULKAN_CreateDeviceResources(SDL_Renderer *renderer, SDL_Propert
             SDL_LogError(SDL_LOG_CATEGORY_RENDER, "vkCreateShaderModule(): %s\n", SDL_Vulkan_GetResultString(result));
             return result;
         }
+    }
 
-        /* Descriptor set layout */
-        VkDescriptorSetLayoutCreateInfo descriptorSetLayoutCreateInfo = { 0 };
-        descriptorSetLayoutCreateInfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO;
-        descriptorSetLayoutCreateInfo.flags = 0;
-        VkDescriptorSetLayoutBinding layoutBindings[5];
-        /* PixelShaderConstants */
-        layoutBindings[0].binding = 4;
-        layoutBindings[0].descriptorCount = 1;
-        layoutBindings[0].descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER;
-        layoutBindings[0].stageFlags = VK_SHADER_STAGE_FRAGMENT_BIT;
-        layoutBindings[0].pImmutableSamplers = NULL;
-
-        /* sampler0 */
-        layoutBindings[1].binding = 0;
-        layoutBindings[1].descriptorCount = 1;
-        layoutBindings[1].descriptorType = VK_DESCRIPTOR_TYPE_SAMPLER;
-        layoutBindings[1].stageFlags = VK_SHADER_STAGE_FRAGMENT_BIT;
-        layoutBindings[1].pImmutableSamplers = NULL;
-
-        /* texture0 */
-        layoutBindings[2].binding = 1;
-        layoutBindings[2].descriptorCount = 1;
-        layoutBindings[2].descriptorType = VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE;
-        layoutBindings[2].stageFlags = VK_SHADER_STAGE_FRAGMENT_BIT;
-        layoutBindings[2].pImmutableSamplers = NULL;
-
-        /* texture1 */
-        layoutBindings[3].binding = 2;
-        layoutBindings[3].descriptorCount = 1;
-        layoutBindings[3].descriptorType = VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE;
-        layoutBindings[3].stageFlags = VK_SHADER_STAGE_FRAGMENT_BIT;
-        layoutBindings[3].pImmutableSamplers = NULL;
-
-        /* texture2 */
-        layoutBindings[4].binding = 3;
-        layoutBindings[4].descriptorCount = 1;
-        layoutBindings[4].descriptorType = VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE;
-        layoutBindings[4].stageFlags = VK_SHADER_STAGE_FRAGMENT_BIT;
-        layoutBindings[4].pImmutableSamplers = NULL;
-
-        descriptorSetLayoutCreateInfo.bindingCount = 5;
-        descriptorSetLayoutCreateInfo.pBindings = layoutBindings;
-        result = vkCreateDescriptorSetLayout(rendererData->device, &descriptorSetLayoutCreateInfo, NULL, &rendererData->descriptorSetLayouts[i]);
-        if (result != VK_SUCCESS) {
-            VULKAN_DestroyAll(renderer);
-            SDL_LogError(SDL_LOG_CATEGORY_RENDER, "vkCreateDescriptorSetLayout(): %s\n", SDL_Vulkan_GetResultString(result));
-            return result;
-        }
-
-        /* Pipeline layout */
-        VkPipelineLayoutCreateInfo pipelineLayoutCreateInfo = { 0 };
-        VkPushConstantRange pushConstantRange;
-        pushConstantRange.size = sizeof( VertexShaderConstants );
-        pushConstantRange.offset = 0;
-        pushConstantRange.stageFlags = VK_SHADER_STAGE_VERTEX_BIT;
-        pipelineLayoutCreateInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO;
-        pipelineLayoutCreateInfo.setLayoutCount = 1;
-        pipelineLayoutCreateInfo.pSetLayouts = &rendererData->descriptorSetLayouts[i];
-        pipelineLayoutCreateInfo.pushConstantRangeCount = 1;
-        pipelineLayoutCreateInfo.pPushConstantRanges = &pushConstantRange;
-        result = vkCreatePipelineLayout(rendererData->device, &pipelineLayoutCreateInfo, NULL, &rendererData->pipelineLayouts[i]);
-        if (result != VK_SUCCESS) {
-            VULKAN_DestroyAll(renderer);
-            SDL_LogError(SDL_LOG_CATEGORY_RENDER, "vkCreatePipelineLayout(): %s\n", SDL_Vulkan_GetResultString(result));
-            return result;
-        }
+    /* Descriptor set layout / pipeline layout*/
+    result = VULKAN_CreateDescriptorSetAndPipelineLayout(rendererData, VK_NULL_HANDLE, &rendererData->descriptorSetLayout, &rendererData->pipelineLayout);
+    if (result != VK_SUCCESS) {
+        VULKAN_DestroyAll(renderer);
+        SDL_LogError(SDL_LOG_CATEGORY_RENDER, "VULKAN_CreateDescriptorSetAndPipelineLayout(): %s\n", SDL_Vulkan_GetResultString(result));
+        return result;
     }
 
     /* Create default vertex buffers  */
@@ -2367,7 +2417,7 @@ static int VULKAN_CreateTexture(SDL_Renderer *renderer, SDL_Texture *texture, SD
     VkFormat textureFormat = SDLPixelFormatToVkTextureFormat(texture->format, renderer->output_colorspace);
     uint32_t width = texture->w;
     uint32_t height = texture->h;
-
+    VkComponentMapping imageViewSwizzle = rendererData->identitySwizzle;
     if (textureFormat == VK_FORMAT_UNDEFINED) {
         return SDL_SetError("%s, An unsupported SDL pixel format (0x%x) was specified", __FUNCTION__, texture->format);
     }
@@ -2384,13 +2434,120 @@ static int VULKAN_CreateTexture(SDL_Renderer *renderer, SDL_Texture *texture, SD
     }
     textureData->scaleMode = (texture->scaleMode == SDL_SCALEMODE_NEAREST) ? VK_FILTER_NEAREST : VK_FILTER_LINEAR;
 
-    /* NV12 textures must have even width and height */
-    if (texture->format == SDL_PIXELFORMAT_NV12 ||
+#if SDL_HAVE_YUV
+    /* YUV textures must have even width and height.  Also create Ycbcr conversion */
+    if (texture->format == SDL_PIXELFORMAT_YV12 ||
+        texture->format == SDL_PIXELFORMAT_IYUV ||
+        texture->format == SDL_PIXELFORMAT_NV12 ||
         texture->format == SDL_PIXELFORMAT_NV21 ||
         texture->format == SDL_PIXELFORMAT_P010) {
+
+        /* Check that we have VK_KHR_sampler_ycbcr_conversion support */
+        if (!rendererData->supportsKHRSamplerYcBcrConversion) {
+            SDL_free(textureData);
+            return SDL_SetError("[Vulkan] YUV textures require a Vulkan device that supports VK_KHR_sampler_ycbcr_conversion");
+        }
+        VkSamplerYcbcrConversionCreateInfoKHR samplerYcbcrConversionCreateInfo = { 0 };
+        samplerYcbcrConversionCreateInfo.sType = VK_STRUCTURE_TYPE_SAMPLER_YCBCR_CONVERSION_CREATE_INFO_KHR;
+
+        /* Pad width/height to multiple of 2 */
         width = (width + 1) & ~1;
         height = (height + 1) & ~1;
+
+        /* Create samplerYcbcrConversion which will be used on the VkImageView and VkSampler */
+        samplerYcbcrConversionCreateInfo.format = textureFormat;
+        switch (SDL_COLORSPACEPRIMARIES(texture->colorspace)) {
+        case SDL_COLOR_PRIMARIES_BT709:
+            samplerYcbcrConversionCreateInfo.ycbcrModel = VK_SAMPLER_YCBCR_MODEL_CONVERSION_YCBCR_709_KHR;
+            break;
+        case SDL_COLOR_PRIMARIES_BT601:
+            samplerYcbcrConversionCreateInfo.ycbcrModel = VK_SAMPLER_YCBCR_MODEL_CONVERSION_YCBCR_601_KHR;
+            break;
+        case SDL_COLOR_PRIMARIES_BT2020:
+            samplerYcbcrConversionCreateInfo.ycbcrModel = VK_SAMPLER_YCBCR_MODEL_CONVERSION_YCBCR_2020_KHR;
+        default:
+            VULKAN_DestroyTexture(renderer, texture);
+            return SDL_SetError("[Vulkan] Unsupported Ycbcr colorspace.\n");
+        }
+        samplerYcbcrConversionCreateInfo.components.r = VK_COMPONENT_SWIZZLE_IDENTITY;
+        samplerYcbcrConversionCreateInfo.components.g = VK_COMPONENT_SWIZZLE_IDENTITY;
+        samplerYcbcrConversionCreateInfo.components.b = VK_COMPONENT_SWIZZLE_IDENTITY;
+        samplerYcbcrConversionCreateInfo.components.a = VK_COMPONENT_SWIZZLE_IDENTITY;
+        if (texture->format == SDL_PIXELFORMAT_YV12 ||
+            texture->format == SDL_PIXELFORMAT_NV21) {
+            samplerYcbcrConversionCreateInfo.components.r = VK_COMPONENT_SWIZZLE_B;
+            samplerYcbcrConversionCreateInfo.components.b = VK_COMPONENT_SWIZZLE_R;
+        }
+        
+        switch (SDL_COLORSPACERANGE(texture->colorspace)) {
+        case SDL_COLOR_RANGE_LIMITED:
+            samplerYcbcrConversionCreateInfo.ycbcrRange = VK_SAMPLER_YCBCR_RANGE_ITU_NARROW_KHR;
+            break;
+        case SDL_COLOR_RANGE_FULL:
+        default:
+            samplerYcbcrConversionCreateInfo.ycbcrRange = VK_SAMPLER_YCBCR_RANGE_ITU_FULL_KHR;
+            break;
+        }
+
+        switch (SDL_COLORSPACECHROMA(texture->colorspace)) {
+        case SDL_CHROMA_LOCATION_LEFT:
+            samplerYcbcrConversionCreateInfo.xChromaOffset = VK_CHROMA_LOCATION_COSITED_EVEN_KHR;
+            samplerYcbcrConversionCreateInfo.yChromaOffset = VK_CHROMA_LOCATION_MIDPOINT_KHR;
+            break;
+        case SDL_CHROMA_LOCATION_TOPLEFT:
+            samplerYcbcrConversionCreateInfo.xChromaOffset = VK_CHROMA_LOCATION_COSITED_EVEN_KHR;
+            samplerYcbcrConversionCreateInfo.yChromaOffset = VK_CHROMA_LOCATION_COSITED_EVEN_KHR;
+            break;
+        case SDL_CHROMA_LOCATION_NONE:
+        case SDL_CHROMA_LOCATION_CENTER:
+        default:
+            samplerYcbcrConversionCreateInfo.xChromaOffset = VK_CHROMA_LOCATION_MIDPOINT_KHR;
+            samplerYcbcrConversionCreateInfo.yChromaOffset = VK_CHROMA_LOCATION_MIDPOINT_KHR;
+            break;
+        }
+        samplerYcbcrConversionCreateInfo.chromaFilter = VK_FILTER_LINEAR;
+        samplerYcbcrConversionCreateInfo.forceExplicitReconstruction = VK_FALSE;
+
+        result = vkCreateSamplerYcbcrConversionKHR(rendererData->device, &samplerYcbcrConversionCreateInfo, NULL, &textureData->sam

(Patch may be truncated, please check the link at the top of this post.)