From 26b37f5d1c3518125ff8b269b5bdfcf452870500 Mon Sep 17 00:00:00 2001
From: Sam Lantinga <[EMAIL REDACTED]>
Date: Sat, 12 Sep 2026 11:49:44 -0700
Subject: [PATCH] Perform Vulkan YUV color conversion explicitly in shaders
This makes the Vulkan code match the D3D/Metal/GPU renderers, and avoids bugs and unsupported format/sampler combinations on various vendor's drivers (including NVIDIA!)
---
include/SDL3/SDL_render.h | 25 +-
src/render/SDL_render.c | 4 +
src/render/vulkan/SDL_render_vulkan.c | 613 +++++++-----
src/render/vulkan/SDL_shaders_vulkan.c | 6 -
src/render/vulkan/SDL_shaders_vulkan.h | 2 -
.../vulkan/VULKAN_PixelShader_Advanced.h | 927 ++++++++++--------
.../vulkan/VULKAN_PixelShader_Advanced_YUV.h | 415 --------
src/render/vulkan/VULKAN_PixelShader_Colors.h | 53 +-
.../vulkan/VULKAN_PixelShader_Colors_PQ.h | 77 +-
.../vulkan/VULKAN_PixelShader_Common.hlsli | 48 +-
.../vulkan/VULKAN_PixelShader_Textures.h | 69 +-
.../vulkan/VULKAN_PixelShader_Textures_PQ.h | 765 ++++++++-------
.../VULKAN_PixelShader_Textures_Simple.h | 6 +-
.../vulkan/VULKAN_PixelShader_Textures_YUV.h | 52 -
src/render/vulkan/compile_shaders.bat | 2 -
15 files changed, 1471 insertions(+), 1593 deletions(-)
delete mode 100644 src/render/vulkan/VULKAN_PixelShader_Advanced_YUV.h
delete mode 100644 src/render/vulkan/VULKAN_PixelShader_Textures_YUV.h
diff --git a/include/SDL3/SDL_render.h b/include/SDL3/SDL_render.h
index 8baaf143d32f4..579cea3dac0af 100644
--- a/include/SDL3/SDL_render.h
+++ b/include/SDL3/SDL_render.h
@@ -803,9 +803,19 @@ extern SDL_DECLSPEC SDL_Texture * SDLCALL SDL_CreateTextureFromSurface(SDL_Rende
* With the vulkan renderer:
*
* - `SDL_PROP_TEXTURE_CREATE_VULKAN_TEXTURE_NUMBER`: the VkImage associated
- * with the texture, if you want to wrap an existing texture.
+ * with the texture, if you want to wrap an existing texture. For NV12 style
+ * textures this is the single two plane VkImage holding both the Y and UV
+ * planes, and for YUV style textures it is the VkImage holding the Y plane.
+ * - `SDL_PROP_TEXTURE_CREATE_VULKAN_TEXTURE_U_NUMBER`: the VkImage associated
+ * with the U plane of a YUV texture, if you want to wrap an existing
+ * texture.
+ * - `SDL_PROP_TEXTURE_CREATE_VULKAN_TEXTURE_V_NUMBER`: the VkImage associated
+ * with the V plane of a YUV texture, if you want to wrap an existing
+ * texture.
* - `SDL_PROP_TEXTURE_CREATE_VULKAN_LAYOUT_NUMBER`: the VkImageLayout for the
* VkImage, defaults to VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL.
+ * - `SDL_PROP_TEXTURE_CREATE_VULKAN_USAGE_NUMBER`: additional VK_IMAGE_USAGE bits that should be used when creating the texture.
+ * VkImage, defaults to VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL.
* - `SDL_PROP_TEXTURE_CREATE_VULKAN_ANDROID_HARDWARE_BUFFER_POINTER`: the AHardwareBuffer to sample from, if you want to use an existing Android hardware buffer as the texture. You must use SDL_PIXELFORMAT_EXTERNAL_OES for the texture format.
* You can't directly update the texture or use it as a render target. If the Android buffer contents change, you must recreate the texture to pick up the changes.
* The texture holds a reference to the buffer, so you can release your own reference once the texture has been created.
@@ -871,7 +881,10 @@ extern SDL_DECLSPEC SDL_Texture * SDLCALL SDL_CreateTextureWithProperties(SDL_Re
#define SDL_PROP_TEXTURE_CREATE_OPENGLES2_TEXTURE_U_NUMBER "SDL.texture.create.opengles2.texture_u"
#define SDL_PROP_TEXTURE_CREATE_OPENGLES2_TEXTURE_V_NUMBER "SDL.texture.create.opengles2.texture_v"
#define SDL_PROP_TEXTURE_CREATE_VULKAN_TEXTURE_NUMBER "SDL.texture.create.vulkan.texture"
+#define SDL_PROP_TEXTURE_CREATE_VULKAN_TEXTURE_U_NUMBER "SDL.texture.create.vulkan.texture_u"
+#define SDL_PROP_TEXTURE_CREATE_VULKAN_TEXTURE_V_NUMBER "SDL.texture.create.vulkan.texture_v"
#define SDL_PROP_TEXTURE_CREATE_VULKAN_LAYOUT_NUMBER "SDL.texture.create.vulkan.layout"
+#define SDL_PROP_TEXTURE_CREATE_VULKAN_USAGE_NUMBER "SDL.texture.create.vulkan.usage"
#define SDL_PROP_TEXTURE_CREATE_VULKAN_ANDROID_HARDWARE_BUFFER_POINTER "SDL.texture.create.vulkan.android_hardware_buffer"
#define SDL_PROP_TEXTURE_CREATE_GPU_TEXTURE_POINTER "SDL.texture.create.gpu.texture"
#define SDL_PROP_TEXTURE_CREATE_GPU_TEXTURE_UV_POINTER "SDL.texture.create.gpu.texture_uv"
@@ -935,7 +948,13 @@ extern SDL_DECLSPEC SDL_Texture * SDLCALL SDL_CreateTextureWithProperties(SDL_Re
* With the vulkan renderer:
*
* - `SDL_PROP_TEXTURE_VULKAN_TEXTURE_NUMBER`: the VkImage associated with the
- * texture
+ * texture. For NV12 style textures this is the single two plane VkImage
+ * holding both the Y and UV planes, and for YUV style textures it is the
+ * VkImage holding the Y plane.
+ * - `SDL_PROP_TEXTURE_VULKAN_TEXTURE_U_NUMBER`: the VkImage associated with
+ * the U plane of a YUV texture
+ * - `SDL_PROP_TEXTURE_VULKAN_TEXTURE_V_NUMBER`: the VkImage associated with
+ * the V plane of a YUV texture
*
* With the opengl renderer:
*
@@ -1018,6 +1037,8 @@ extern SDL_DECLSPEC SDL_PropertiesID SDLCALL SDL_GetTextureProperties(SDL_Textur
#define SDL_PROP_TEXTURE_OPENGLES2_TEXTURE_V_NUMBER "SDL.texture.opengles2.texture_v"
#define SDL_PROP_TEXTURE_OPENGLES2_TEXTURE_TARGET_NUMBER "SDL.texture.opengles2.target"
#define SDL_PROP_TEXTURE_VULKAN_TEXTURE_NUMBER "SDL.texture.vulkan.texture"
+#define SDL_PROP_TEXTURE_VULKAN_TEXTURE_U_NUMBER "SDL.texture.vulkan.texture_u"
+#define SDL_PROP_TEXTURE_VULKAN_TEXTURE_V_NUMBER "SDL.texture.vulkan.texture_v"
#define SDL_PROP_TEXTURE_GPU_TEXTURE_POINTER "SDL.texture.gpu.texture"
#define SDL_PROP_TEXTURE_GPU_TEXTURE_UV_POINTER "SDL.texture.gpu.texture_uv"
#define SDL_PROP_TEXTURE_GPU_TEXTURE_U_POINTER "SDL.texture.gpu.texture_u"
diff --git a/src/render/SDL_render.c b/src/render/SDL_render.c
index 54dadec545d18..059eaf5f6d804 100644
--- a/src/render/SDL_render.c
+++ b/src/render/SDL_render.c
@@ -1523,6 +1523,10 @@ SDL_Texture *SDL_CreateTextureWithProperties(SDL_Renderer *renderer, SDL_Propert
SDL_SetError("Palettized textures can't be render targets");
return NULL;
}
+ CHECK_PARAM(SDL_ISPIXELFORMAT_FOURCC(format) && access == SDL_TEXTUREACCESS_TARGET) {
+ SDL_SetError("%s textures can't be render targets", SDL_GetPixelFormatName(format));
+ return NULL;
+ }
CHECK_PARAM(w <= 0 || h <= 0) {
SDL_SetError("Texture dimensions can't be 0");
return NULL;
diff --git a/src/render/vulkan/SDL_render_vulkan.c b/src/render/vulkan/SDL_render_vulkan.c
index 392a31d45c23a..39abd1ca49da9 100644
--- a/src/render/vulkan/SDL_render_vulkan.c
+++ b/src/render/vulkan/SDL_render_vulkan.c
@@ -28,6 +28,7 @@
#define SDL_VULKAN_CONSTANT_BUFFER_DEFAULT_SIZE 65536
#define SDL_VULKAN_NUM_UPLOAD_BUFFERS 32
#define SDL_VULKAN_MAX_DESCRIPTOR_SETS 4096
+#define SDL_VULKAN_NUM_TEXTURE_BINDINGS 3
#define SDL_VULKAN_VALIDATION_LAYER_NAME "VK_LAYER_KHRONOS_validation"
@@ -202,6 +203,9 @@ static const float TEXTURETYPE_RGB_PIXELART = 2;
static const float TEXTURETYPE_PALETTE_NEAREST = 3;
static const float TEXTURETYPE_PALETTE_LINEAR = 4;
static const float TEXTURETYPE_PALETTE_PIXELART = 5;
+static const float TEXTURETYPE_NV12 = 6;
+static const float TEXTURETYPE_NV21 = 7;
+static const float TEXTURETYPE_YUV = 8;
static const float INPUTTYPE_UNSPECIFIED = 0;
static const float INPUTTYPE_SRGB = 1;
@@ -225,6 +229,8 @@ typedef struct
float tonemap_factor1;
float tonemap_factor2;
float sdr_white_point;
+
+ float YCbCr_matrix[16];
} VULKAN_PixelShaderConstants;
// Per-vertex data
@@ -263,7 +269,6 @@ typedef struct
{
bool allocatedImage;
VkImage image;
- VkImageView imageView;
VkDeviceMemory deviceMemory;
VkImageLayout imageLayout;
VkFormat format;
@@ -273,6 +278,7 @@ typedef struct
typedef struct
{
VULKAN_Image image;
+ VkImageView imageView;
} VULKAN_PaletteData;
// YUV conversion data
@@ -307,19 +313,23 @@ typedef struct VULKAN_YUVPipeline
// Per-texture data
typedef struct
{
- VULKAN_Image mainImage;
+ int numImages;
+ VULKAN_Image images[SDL_VULKAN_NUM_TEXTURE_BINDINGS];
+ int numImageViews;
+ VkImageView imageViews[SDL_VULKAN_NUM_TEXTURE_BINDINGS];
VkRenderPass mainRenderpasses[VULKAN_RENDERPASS_COUNT];
VkFramebuffer mainFramebuffer;
VULKAN_Buffer stagingBuffer;
- SDL_Rect lockedRect;
int width;
int height;
+ const float *YCbCr_matrix;
- bool yuv;
+ // YCbCr conversion support
VULKAN_YUVPipeline *yuvPipeline;
Uint8 *pixels;
int pitch;
+ SDL_Rect lockedRect;
} VULKAN_TextureData;
// Pipeline State Object data
@@ -485,6 +495,38 @@ static SDL_PixelFormat VULKAN_VkFormatToSDLPixelFormat(VkFormat vkFormat)
return SDL_PIXELFORMAT_UNKNOWN;
}
+static int VULKAN_GetFormatImageCount(SDL_PixelFormat format)
+{
+ switch (format) {
+ case SDL_PIXELFORMAT_YV12:
+ case SDL_PIXELFORMAT_IYUV:
+ case SDL_PIXELFORMAT_I444:
+ case SDL_PIXELFORMAT_I0FL:
+ case SDL_PIXELFORMAT_I4FL:
+ return 3;
+ default:
+ return 1;
+ }
+}
+
+static int VULKAN_GetFormatImageViewCount(SDL_PixelFormat format)
+{
+ switch (format) {
+ case SDL_PIXELFORMAT_NV12:
+ case SDL_PIXELFORMAT_NV21:
+ case SDL_PIXELFORMAT_P010:
+ return 2;
+ case SDL_PIXELFORMAT_YV12:
+ case SDL_PIXELFORMAT_IYUV:
+ case SDL_PIXELFORMAT_I444:
+ case SDL_PIXELFORMAT_I0FL:
+ case SDL_PIXELFORMAT_I4FL:
+ return 3;
+ default:
+ return 1;
+ }
+}
+
static int VULKAN_VkFormatGetNumPlanes(VkFormat vkFormat)
{
switch (vkFormat) {
@@ -536,18 +578,16 @@ static VkFormat SDLPixelFormatToVkTextureFormat(SDL_PixelFormat format, Uint32 o
return VK_FORMAT_B8G8R8G8_422_UNORM;
case SDL_PIXELFORMAT_YV12:
case SDL_PIXELFORMAT_IYUV:
- return VK_FORMAT_G8_B8_R8_3PLANE_420_UNORM;
case SDL_PIXELFORMAT_I444:
- return VK_FORMAT_G8_B8_R8_3PLANE_444_UNORM;
+ return VK_FORMAT_R8_UNORM;
case SDL_PIXELFORMAT_NV12:
case SDL_PIXELFORMAT_NV21:
return VK_FORMAT_G8_B8R8_2PLANE_420_UNORM;
case SDL_PIXELFORMAT_P010:
return VK_FORMAT_G10X6_B10X6R10X6_2PLANE_420_UNORM_3PACK16;
case SDL_PIXELFORMAT_I0FL:
- return VK_FORMAT_G16_B16_R16_3PLANE_420_UNORM;
case SDL_PIXELFORMAT_I4FL:
- return VK_FORMAT_G16_B16_R16_3PLANE_444_UNORM;
+ return VK_FORMAT_R16_UNORM;
default:
for (int i = 0; i < SDL_arraysize(vk_format_map); i++) {
if (vk_format_map[i].sdl == format) {
@@ -563,6 +603,63 @@ static VkFormat SDLPixelFormatToVkTextureFormat(SDL_PixelFormat format, Uint32 o
}
}
+static VkFormat VULKAN_GetVkImageFormat(SDL_PixelFormat format, Uint32 output_colorspace)
+{
+ switch (format) {
+ case SDL_PIXELFORMAT_INDEX8:
+ return VK_FORMAT_R8_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_I444:
+ return VK_FORMAT_R8_UNORM;
+ case SDL_PIXELFORMAT_NV12:
+ case SDL_PIXELFORMAT_NV21:
+ return VK_FORMAT_G8_B8R8_2PLANE_420_UNORM;
+ case SDL_PIXELFORMAT_P010:
+ return VK_FORMAT_G10X6_B10X6R10X6_2PLANE_420_UNORM_3PACK16;
+ case SDL_PIXELFORMAT_I0FL:
+ case SDL_PIXELFORMAT_I4FL:
+ return VK_FORMAT_R16_UNORM;
+ default:
+ for (int i = 0; i < SDL_arraysize(vk_format_map); i++) {
+ if (vk_format_map[i].sdl == format) {
+ if (output_colorspace == SDL_COLORSPACE_SRGB_LINEAR ||
+ output_colorspace == SDL_COLORSPACE_HDR10) {
+ return vk_format_map[i].srgb;
+ } else {
+ return vk_format_map[i].unorm;
+ }
+ }
+ }
+ return VK_FORMAT_UNDEFINED;
+ }
+}
+
+static VkFormat VULKAN_GetVkImageViewFormat(SDL_PixelFormat format, int plane, Uint32 output_colorspace)
+{
+ switch (format) {
+ case SDL_PIXELFORMAT_NV12:
+ case SDL_PIXELFORMAT_NV21:
+ if (plane == 0) {
+ return VK_FORMAT_R8_UNORM;
+ } else {
+ return VK_FORMAT_R8G8_UNORM;
+ }
+ case SDL_PIXELFORMAT_P010:
+ if (plane == 0) {
+ return VK_FORMAT_R8_UNORM;
+ } else {
+ return VK_FORMAT_R16G16_UNORM;
+ }
+ default:
+ return VULKAN_GetVkImageFormat(format, output_colorspace);
+ }
+}
+
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);
@@ -574,7 +671,9 @@ static bool VULKAN_FindMemoryTypeIndex(VULKAN_RenderData *rendererData, uint32_t
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);
+#ifdef SDL_PLATFORM_ANDROID
static VULKAN_YUVPipeline *VULKAN_GetYUVPipeline(VULKAN_RenderData *rendererData, const VULKAN_YUVPipelineCreateInfo *createInfo);
+#endif
static void VULKAN_CleanupYUVPipeline(VULKAN_RenderData *rendererData, VULKAN_YUVPipeline *pipeline);
static VkSurfaceTransformFlagBitsKHR VULKAN_GetRotationForCurrentRenderTarget(VULKAN_RenderData *rendererData);
static bool VULKAN_IsDisplayRotated90Degrees(VkSurfaceTransformFlagBitsKHR rotation);
@@ -854,10 +953,6 @@ static VkResult VULKAN_AllocateBuffer(VULKAN_RenderData *rendererData, VkDeviceS
static void VULKAN_DestroyImage(VULKAN_RenderData *rendererData, VULKAN_Image *vulkanImage)
{
- if (vulkanImage->imageView != VK_NULL_HANDLE) {
- vkDestroyImageView(rendererData->device, vulkanImage->imageView, NULL);
- vulkanImage->imageView = VK_NULL_HANDLE;
- }
if (vulkanImage->image != VK_NULL_HANDLE) {
if (vulkanImage->allocatedImage) {
vkDestroyImage(rendererData->device, vulkanImage->image, NULL);
@@ -871,17 +966,15 @@ static void VULKAN_DestroyImage(VULKAN_RenderData *rendererData, VULKAN_Image *v
}
vulkanImage->deviceMemory = VK_NULL_HANDLE;
}
- SDL_zerop(vulkanImage);
}
-static VkResult VULKAN_AllocateImage(VULKAN_RenderData *rendererData, SDL_PropertiesID create_props, uint32_t width, uint32_t height, VkFormat format, VkImageUsageFlags imageUsage, VkComponentMapping swizzle, VkSamplerYcbcrConversionKHR samplerYcbcrConversion, VULKAN_ExternalBufferInfo *external, VULKAN_Image *imageOut)
+static VkResult VULKAN_AllocateImage(VULKAN_RenderData *rendererData, SDL_PropertiesID create_props, const char *property, uint32_t width, uint32_t height, VkFormat format, VkImageUsageFlags imageUsage, VkSamplerYcbcrConversionKHR samplerYcbcrConversion, VULKAN_ExternalBufferInfo *external, VULKAN_Image *imageOut)
{
VkResult result;
- VkSamplerYcbcrConversionInfoKHR samplerYcbcrConversionInfo = { 0 };
SDL_zerop(imageOut);
imageOut->format = format;
- imageOut->image = (VkImage)SDL_GetNumberProperty(create_props, SDL_PROP_TEXTURE_CREATE_VULKAN_TEXTURE_NUMBER, 0);
+ imageOut->image = (VkImage)SDL_GetNumberProperty(create_props, property, 0);
if (imageOut->image == VK_NULL_HANDLE) {
imageOut->allocatedImage = VK_TRUE;
@@ -903,6 +996,11 @@ static VkResult VULKAN_AllocateImage(VULKAN_RenderData *rendererData, SDL_Proper
imageCreateInfo.queueFamilyIndexCount = 0;
imageCreateInfo.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;
+ if (VULKAN_VkFormatGetNumPlanes(format) > 0) {
+ // We'll take image views with a different format
+ imageCreateInfo.flags |= VK_IMAGE_CREATE_MUTABLE_FORMAT_BIT;
+ }
+
#ifdef SDL_PLATFORM_ANDROID
VkExternalMemoryImageCreateInfoKHR externalMemoryImageCreateInfo = { 0 };
if (external) {
@@ -913,12 +1011,6 @@ static VkResult VULKAN_AllocateImage(VULKAN_RenderData *rendererData, SDL_Proper
}
#endif
- // Allow writing to planar YUV textures from application code
- if (VULKAN_VkFormatGetNumPlanes(format) > 1 && !external) {
- imageCreateInfo.flags |= (VK_IMAGE_CREATE_MUTABLE_FORMAT_BIT | VK_IMAGE_CREATE_EXTENDED_USAGE_BIT);
- imageCreateInfo.usage |= VK_IMAGE_USAGE_STORAGE_BIT;
- }
-
result = vkCreateImage(rendererData->device, &imageCreateInfo, NULL, &imageOut->image);
if (result != VK_SUCCESS) {
VULKAN_DestroyImage(rendererData, imageOut);
@@ -1006,13 +1098,28 @@ static VkResult VULKAN_AllocateImage(VULKAN_RenderData *rendererData, SDL_Proper
}
}
+ return VK_SUCCESS;
+}
+
+static VkResult VULKAN_AllocateImageView(VULKAN_RenderData *rendererData, VkImage image, int plane, VkFormat format, VkImageUsageFlags imageUsage, VkComponentMapping swizzle, VkSamplerYcbcrConversionKHR samplerYcbcrConversion, VkImageView *imageViewOut)
+{
+ VkResult result;
+ VkSamplerYcbcrConversionInfoKHR samplerYcbcrConversionInfo = { 0 };
+ VkImageAspectFlags aspectMask;
+
+ if (plane < 0) {
+ aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
+ } else {
+ aspectMask = (VkImageAspectFlags)(VK_IMAGE_ASPECT_PLANE_0_BIT << plane);
+ }
+
VkImageViewCreateInfo imageViewCreateInfo = { 0 };
imageViewCreateInfo.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO;
- imageViewCreateInfo.image = imageOut->image;
+ imageViewCreateInfo.image = image;
imageViewCreateInfo.viewType = VK_IMAGE_VIEW_TYPE_2D;
imageViewCreateInfo.format = format;
imageViewCreateInfo.components = swizzle;
- imageViewCreateInfo.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
+ imageViewCreateInfo.subresourceRange.aspectMask = aspectMask;
imageViewCreateInfo.subresourceRange.baseMipLevel = 0;
imageViewCreateInfo.subresourceRange.levelCount = 1;
imageViewCreateInfo.subresourceRange.baseArrayLayer = 0;
@@ -1023,24 +1130,24 @@ static VkResult VULKAN_AllocateImage(VULKAN_RenderData *rendererData, SDL_Proper
imageViewUsageCreateInfo.usage = imageUsage;
imageViewCreateInfo.pNext = &imageViewUsageCreateInfo;
- // If it's a YCbCr image, we need to pass the conversion info to the VkImageView (and the VkSampler)
+ // If it's a YCbCr image, we need to pass the conversion info to the VkImageView
if (samplerYcbcrConversion != VK_NULL_HANDLE) {
samplerYcbcrConversionInfo.sType = VK_STRUCTURE_TYPE_SAMPLER_YCBCR_CONVERSION_INFO_KHR;
samplerYcbcrConversionInfo.conversion = samplerYcbcrConversion;
imageViewUsageCreateInfo.pNext = &samplerYcbcrConversionInfo;
}
- result = vkCreateImageView(rendererData->device, &imageViewCreateInfo, NULL, &imageOut->imageView);
+ result = vkCreateImageView(rendererData->device, &imageViewCreateInfo, NULL, imageViewOut);
if (result != VK_SUCCESS) {
- VULKAN_DestroyImage(rendererData, imageOut);
SET_ERROR_CODE("vkCreateImageView()", result);
return result;
}
- return result;
+ return VK_SUCCESS;
}
+
static void VULKAN_RecordExternalImageBarrier(VULKAN_RenderData *rendererData, VkAccessFlags sourceAccessMask, VkAccessFlags destAccessMask,
VkPipelineStageFlags srcStageFlags, VkPipelineStageFlags dstStageFlags, VkImageLayout destLayout, VkImage image, VkImageLayout *imageLayout)
{
@@ -2863,7 +2970,12 @@ static bool VULKAN_CreatePalette(SDL_Renderer *renderer, SDL_TexturePalette *pal
VkFormat format = SDLPixelFormatToVkTextureFormat(SDL_PIXELFORMAT_RGBA32, renderer->output_colorspace);
VkImageUsageFlags usage = VK_IMAGE_USAGE_SAMPLED_BIT | VK_IMAGE_USAGE_TRANSFER_SRC_BIT | VK_IMAGE_USAGE_TRANSFER_DST_BIT;
VkComponentMapping imageViewSwizzle = data->identitySwizzle;
- VkResult result = VULKAN_AllocateImage(data, 0, 256, 1, format, usage, imageViewSwizzle, VK_NULL_HANDLE, NULL, &palettedata->image);
+ VkResult result = VULKAN_AllocateImage(data, 0, NULL, 256, 1, format, usage, VK_NULL_HANDLE, NULL, &palettedata->image);
+ if (result != VK_SUCCESS) {
+ SET_ERROR_CODE("VULKAN_AllocateImage()", result);
+ return false;
+ }
+ result = VULKAN_AllocateImageView(data, palettedata->image.image, -1, format, usage, imageViewSwizzle, VK_NULL_HANDLE, &palettedata->imageView);
if (result != VK_SUCCESS) {
SET_ERROR_CODE("VULKAN_AllocateImage()", result);
return false;
@@ -2894,6 +3006,11 @@ static void VULKAN_DestroyPalette(SDL_Renderer *renderer, SDL_TexturePalette *pa
VULKAN_WaitForGPU(data);
VULKAN_DestroyImage(data, &palettedata->image);
+
+ if (palettedata->imageView != VK_NULL_HANDLE) {
+ vkDestroyImageView(data->device, palettedata->imageView, NULL);
+ palettedata->imageView = VK_NULL_HANDLE;
+ }
SDL_free(palettedata);
}
@@ -2926,9 +3043,13 @@ static bool VULKAN_CreateTexture(SDL_Renderer *renderer, SDL_Texture *texture, S
VULKAN_RenderData *rendererData = (VULKAN_RenderData *)renderer->internal;
VULKAN_TextureData *textureData;
VkResult result;
- VkFormat textureFormat = SDLPixelFormatToVkTextureFormat(texture->format, renderer->output_colorspace);
+ int numImages = VULKAN_GetFormatImageCount(texture->format);
+ int numImageViews = VULKAN_GetFormatImageViewCount(texture->format);
+ VkFormat textureFormat = VULKAN_GetVkImageFormat(texture->format, renderer->output_colorspace);
uint32_t width = texture->w;
uint32_t height = texture->h;
+ uint32_t chromaWidth = width;
+ uint32_t chromaHeight = height;
VkComponentMapping imageViewSwizzle = rendererData->identitySwizzle;
VULKAN_ExternalBufferInfo externalBufferInfo;
VULKAN_ExternalBufferInfo *external = NULL;
@@ -2937,10 +3058,6 @@ static bool VULKAN_CreateTexture(SDL_Renderer *renderer, SDL_Texture *texture, S
return SDL_SetError("Device lost and couldn't be recovered");
}
- if (textureFormat == VK_FORMAT_UNDEFINED && texture->format != SDL_PIXELFORMAT_EXTERNAL_OES) {
- return SDL_SetError("%s, An unsupported SDL pixel format (0x%x) was specified", SDL_FUNCTION, texture->format);
- }
-
textureData = (VULKAN_TextureData *)SDL_calloc(1, sizeof(*textureData));
if (!textureData) {
return false;
@@ -2951,10 +3068,6 @@ static bool VULKAN_CreateTexture(SDL_Renderer *renderer, SDL_Texture *texture, S
SDL_zero(externalBufferInfo);
external = &externalBufferInfo;
- if (texture->access == SDL_TEXTUREACCESS_TARGET) {
- return SDL_SetError("SDL_PIXELFORMAT_EXTERNAL_OES textures can't be render targets");
- }
-
#ifdef SDL_PLATFORM_ANDROID
external->buffer = (struct AHardwareBuffer *)SDL_GetPointerProperty(create_props, SDL_PROP_TEXTURE_CREATE_VULKAN_ANDROID_HARDWARE_BUFFER_POINTER, NULL);
if (!external->buffer) {
@@ -3061,132 +3174,97 @@ static bool VULKAN_CreateTexture(SDL_Renderer *renderer, SDL_Texture *texture, S
if (!textureData->yuvPipeline) {
return false;
}
-
- textureData->yuv = true;
}
#endif // SDL_PLATFORM_ANDROID
- } else if (texture->format == SDL_PIXELFORMAT_YV12 ||
- texture->format == SDL_PIXELFORMAT_IYUV ||
- texture->format == SDL_PIXELFORMAT_I444 ||
- texture->format == SDL_PIXELFORMAT_NV12 ||
- texture->format == SDL_PIXELFORMAT_NV21 ||
- texture->format == SDL_PIXELFORMAT_P010 ||
- texture->format == SDL_PIXELFORMAT_I0FL ||
- texture->format == SDL_PIXELFORMAT_I4FL) {
- const uint32_t YUV_SD_THRESHOLD = 576;
-
- VULKAN_YUVPipelineCreateInfo createInfo;
- SDL_zero(createInfo); // Must zero padding bytes for cache
- createInfo.filter = VK_FILTER_LINEAR;
-
- VkSamplerYcbcrConversionCreateInfoKHR *samplerYcbcrConversionCreateInfo = &createInfo.samplerYcbcrConversionCreateInfo;
- samplerYcbcrConversionCreateInfo->sType = VK_STRUCTURE_TYPE_SAMPLER_YCBCR_CONVERSION_CREATE_INFO_KHR;
-
- if (texture->format != SDL_PIXELFORMAT_I444 &&
- texture->format != SDL_PIXELFORMAT_I4FL) {
+ } else if (SDL_ISPIXELFORMAT_FOURCC(texture->format)) {
+ 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 ||
+ texture->format == SDL_PIXELFORMAT_I0FL) {
// 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_COLORSPACEMATRIX(texture->colorspace)) {
- case SDL_MATRIX_COEFFICIENTS_BT470BG:
- case SDL_MATRIX_COEFFICIENTS_BT601:
- samplerYcbcrConversionCreateInfo->ycbcrModel = VK_SAMPLER_YCBCR_MODEL_CONVERSION_YCBCR_601_KHR;
- break;
- case SDL_MATRIX_COEFFICIENTS_BT709:
- samplerYcbcrConversionCreateInfo->ycbcrModel = VK_SAMPLER_YCBCR_MODEL_CONVERSION_YCBCR_709_KHR;
- break;
- case SDL_MATRIX_COEFFICIENTS_BT2020_NCL:
- samplerYcbcrConversionCreateInfo->ycbcrModel = VK_SAMPLER_YCBCR_MODEL_CONVERSION_YCBCR_2020_KHR;
- break;
- case SDL_MATRIX_COEFFICIENTS_UNSPECIFIED:
- if (texture->format == SDL_PIXELFORMAT_P010) {
- samplerYcbcrConversionCreateInfo->ycbcrModel = VK_SAMPLER_YCBCR_MODEL_CONVERSION_YCBCR_2020_KHR;
- } else if (height > YUV_SD_THRESHOLD) {
- samplerYcbcrConversionCreateInfo->ycbcrModel = VK_SAMPLER_YCBCR_MODEL_CONVERSION_YCBCR_709_KHR;
- } else {
- samplerYcbcrConversionCreateInfo->ycbcrModel = VK_SAMPLER_YCBCR_MODEL_CONVERSION_YCBCR_601_KHR;
- }
- break;
- default:
- return SDL_SetError("Unsupported Ycbcr colorspace: %d", SDL_COLORSPACEMATRIX(texture->colorspace));
- }
- 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;
+ texture->format == SDL_PIXELFORMAT_IYUV ||
+ texture->format == SDL_PIXELFORMAT_I0FL) {
+ // YUV 4:2:0 formats
+ chromaWidth = (width / 2);
+ chromaHeight = (height / 2);
}
- 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;
+ int bits_per_pixel;
+ switch (texture->format) {
+ case SDL_PIXELFORMAT_I0FL:
+ case SDL_PIXELFORMAT_I4FL:
+ bits_per_pixel = 16;
break;
- case SDL_CHROMA_LOCATION_TOPLEFT:
- samplerYcbcrConversionCreateInfo->xChromaOffset = VK_CHROMA_LOCATION_COSITED_EVEN_KHR;
- samplerYcbcrConversionCreateInfo->yChromaOffset = VK_CHROMA_LOCATION_COSITED_EVEN_KHR;
+ case SDL_PIXELFORMAT_P010:
+ bits_per_pixel = 10;
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;
+ bits_per_pixel = 8;
break;
}
- samplerYcbcrConversionCreateInfo->chromaFilter = VK_FILTER_LINEAR;
-
- textureData->yuvPipeline = VULKAN_GetYUVPipeline(rendererData, &createInfo);
- if (!textureData->yuvPipeline) {
- return false;
+ textureData->YCbCr_matrix = SDL_GetYCbCRtoRGBConversionMatrix(texture->colorspace, texture->w, texture->h, bits_per_pixel);
+ if (!textureData->YCbCr_matrix) {
+ return SDL_SetError("Unsupported YUV colorspace");
}
-
- textureData->yuv = true;
}
textureData->width = width;
textureData->height = height;
- VkImageUsageFlags usage = VK_IMAGE_USAGE_SAMPLED_BIT;
- if (!external) {
- usage |= (VK_IMAGE_USAGE_TRANSFER_SRC_BIT | VK_IMAGE_USAGE_TRANSFER_DST_BIT);
- }
+ VkImageUsageFlags usage = (VK_IMAGE_USAGE_SAMPLED_BIT | VK_IMAGE_USAGE_TRANSFER_DST_BIT);
if (texture->access == SDL_TEXTUREACCESS_TARGET) {
- usage |= VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT;
+ usage |= (VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT | VK_IMAGE_USAGE_TRANSFER_SRC_BIT);
}
+ usage |= (VkImageUsageFlags)SDL_GetNumberProperty(create_props, SDL_PROP_TEXTURE_CREATE_VULKAN_USAGE_NUMBER, 0);
+
+ static const char *imageCreateProperties[] = {
+ SDL_PROP_TEXTURE_CREATE_VULKAN_TEXTURE_NUMBER,
+ SDL_PROP_TEXTURE_CREATE_VULKAN_TEXTURE_U_NUMBER,
+ SDL_PROP_TEXTURE_CREATE_VULKAN_TEXTURE_V_NUMBER
+ };
+ SDL_COMPILE_TIME_ASSERT(imageCreateProperties, SDL_arraysize(imageCreateProperties) == SDL_VULKAN_NUM_TEXTURE_BINDINGS);
VkSamplerYcbcrConversionKHR samplerYcbcrConversion = (textureData->yuvPipeline ? textureData->yuvPipeline->samplerYcbcrConversion : VK_NULL_HANDLE);
- result = VULKAN_AllocateImage(rendererData, create_props, width, height, textureFormat, usage, imageViewSwizzle, samplerYcbcrConversion, external, &textureData->mainImage);
- if (result != VK_SUCCESS) {
- SET_ERROR_CODE("VULKAN_AllocateImage()", result);
- return false;
+ for (int i = 0; i < numImages; ++i) {
+ uint32_t imageWidth = (i == 0 ? width : chromaWidth);
+ uint32_t imageHeight = (i == 0 ? height : chromaHeight);
+ result = VULKAN_AllocateImage(rendererData, create_props, imageCreateProperties[i], imageWidth, imageHeight, textureFormat, usage, samplerYcbcrConversion, external, &tex
(Patch may be truncated, please check the link at the top of this post.)