From 09304831f600c4bde992125dd7d910673c547aa7 Mon Sep 17 00:00:00 2001
From: Sam Lantinga <[EMAIL REDACTED]>
Date: Thu, 4 Dec 2025 07:59:19 -0800
Subject: [PATCH] Fixed issues with YUV texture updates in the 2D renderer
---
src/render/direct3d11/SDL_render_d3d11.c | 24 +++++----
src/render/direct3d12/SDL_render_d3d12.c | 6 +--
src/render/gpu/SDL_render_gpu.c | 10 ++--
src/render/vulkan/SDL_render_vulkan.c | 69 ++++++++++++++----------
4 files changed, 65 insertions(+), 44 deletions(-)
diff --git a/src/render/direct3d11/SDL_render_d3d11.c b/src/render/direct3d11/SDL_render_d3d11.c
index 3890488055fb2..532329e573017 100644
--- a/src/render/direct3d11/SDL_render_d3d11.c
+++ b/src/render/direct3d11/SDL_render_d3d11.c
@@ -1585,22 +1585,25 @@ static bool D3D11_UpdateTexture(SDL_Renderer *renderer, SDL_Texture *texture,
#ifdef SDL_HAVE_YUV
if (textureData->nv12) {
- const Uint8 *Yplane = (const Uint8 *)srcPixels;
- const Uint8 *UVplane = Yplane + rect->h * srcPitch;
+ int UVbpp = SDL_BYTESPERPIXEL(texture->format) * 2;
+ int Ypitch = srcPitch;
+ int UVpitch = (srcPitch + (UVbpp - 1)) & ~(UVbpp - 1);
+ const Uint8 *plane0 = (const Uint8 *)srcPixels;
+ const Uint8 *plane1 = plane0 + rect->h * srcPitch;
- return D3D11_UpdateTextureNV(renderer, texture, rect, Yplane, srcPitch, UVplane, srcPitch);
+ return D3D11_UpdateTextureNV(renderer, texture, rect, plane0, Ypitch, plane1, UVpitch);
} else if (textureData->yuv) {
int Ypitch = srcPitch;
int UVpitch = ((Ypitch + 1) / 2);
- const Uint8 *Yplane = (const Uint8 *)srcPixels;
- const Uint8 *Uplane = Yplane + rect->h * Ypitch;
- const Uint8 *Vplane = Uplane + ((rect->h + 1) / 2) * UVpitch;
+ const Uint8 *plane0 = (const Uint8 *)srcPixels;
+ const Uint8 *plane1 = plane0 + rect->h * Ypitch;
+ const Uint8 *plane2 = plane1 + ((rect->h + 1) / 2) * UVpitch;
if (texture->format == SDL_PIXELFORMAT_YV12) {
- return D3D11_UpdateTextureYUV(renderer, texture, rect, Yplane, Ypitch, Vplane, UVpitch, Uplane, UVpitch);
+ return D3D11_UpdateTextureYUV(renderer, texture, rect, plane0, Ypitch, plane2, UVpitch, plane1, UVpitch);
} else {
- return D3D11_UpdateTextureYUV(renderer, texture, rect, Yplane, Ypitch, Uplane, UVpitch, Vplane, UVpitch);
+ return D3D11_UpdateTextureYUV(renderer, texture, rect, plane0, Ypitch, plane1, UVpitch, plane2, UVpitch);
}
}
#endif
@@ -1652,6 +1655,7 @@ static bool D3D11_UpdateTextureNV(SDL_Renderer *renderer, SDL_Texture *texture,
HRESULT result;
D3D11_TEXTURE2D_DESC stagingTextureDesc;
D3D11_MAPPED_SUBRESOURCE textureMemory;
+ int bpp = SDL_BYTESPERPIXEL(texture->format);
if (!textureData) {
return SDL_SetError("Texture is not currently available");
@@ -1695,7 +1699,7 @@ static bool D3D11_UpdateTextureNV(SDL_Renderer *renderer, SDL_Texture *texture,
src = Yplane;
dst = (Uint8 *)textureMemory.pData;
- length = w;
+ length = w * bpp;
if (length == (UINT)Ypitch && length == textureMemory.RowPitch) {
SDL_memcpy(dst, src, (size_t)length * h);
} else {
@@ -1713,7 +1717,7 @@ static bool D3D11_UpdateTextureNV(SDL_Renderer *renderer, SDL_Texture *texture,
}
src = UVplane;
- length = w;
+ length = ((w + 1) / 2) * 2 * bpp;
h = (h + 1) / 2;
if (stagingTextureDesc.Format == DXGI_FORMAT_P010) {
length = (length + 3) & ~3;
diff --git a/src/render/direct3d12/SDL_render_d3d12.c b/src/render/direct3d12/SDL_render_d3d12.c
index 032ae4daee989..43260ae555bb5 100644
--- a/src/render/direct3d12/SDL_render_d3d12.c
+++ b/src/render/direct3d12/SDL_render_d3d12.c
@@ -2087,10 +2087,10 @@ static bool D3D12_UpdateTextureYUV(SDL_Renderer *renderer, SDL_Texture *texture,
if (!D3D12_UpdateTextureInternal(rendererData, textureData->mainTexture, 0, rect->x, rect->y, rect->w, rect->h, Yplane, Ypitch, &textureData->mainResourceState)) {
return false;
}
- if (!D3D12_UpdateTextureInternal(rendererData, textureData->mainTextureU, 0, rect->x / 2, rect->y / 2, rect->w / 2, rect->h / 2, Uplane, Upitch, &textureData->mainResourceStateU)) {
+ if (!D3D12_UpdateTextureInternal(rendererData, textureData->mainTextureU, 0, rect->x / 2, rect->y / 2, (rect->w + 1) / 2, (rect->h + 1) / 2, Uplane, Upitch, &textureData->mainResourceStateU)) {
return false;
}
- if (!D3D12_UpdateTextureInternal(rendererData, textureData->mainTextureV, 0, rect->x / 2, rect->y / 2, rect->w / 2, rect->h / 2, Vplane, Vpitch, &textureData->mainResourceStateV)) {
+ if (!D3D12_UpdateTextureInternal(rendererData, textureData->mainTextureV, 0, rect->x / 2, rect->y / 2, (rect->w + 1) / 2, (rect->h + 1) / 2, Vplane, Vpitch, &textureData->mainResourceStateV)) {
return false;
}
if (textureData->mainTextureResourceView.ptr == rendererData->currentShaderResource.ptr) {
@@ -2115,7 +2115,7 @@ static bool D3D12_UpdateTextureNV(SDL_Renderer *renderer, SDL_Texture *texture,
if (!D3D12_UpdateTextureInternal(rendererData, textureData->mainTexture, 0, rect->x, rect->y, rect->w, rect->h, Yplane, Ypitch, &textureData->mainResourceState)) {
return false;
}
- if (!D3D12_UpdateTextureInternal(rendererData, textureData->mainTexture, 1, rect->x, rect->y, rect->w, rect->h, UVplane, UVpitch, &textureData->mainResourceState)) {
+ if (!D3D12_UpdateTextureInternal(rendererData, textureData->mainTexture, 1, rect->x, rect->y, (rect->w + 1) & ~1, (rect->h + 1) & ~1, UVplane, UVpitch, &textureData->mainResourceState)) {
return false;
}
if (textureData->mainTextureResourceView.ptr == rendererData->currentShaderResource.ptr) {
diff --git a/src/render/gpu/SDL_render_gpu.c b/src/render/gpu/SDL_render_gpu.c
index 9ef9993d887c1..06a06ee9669b5 100644
--- a/src/render/gpu/SDL_render_gpu.c
+++ b/src/render/gpu/SDL_render_gpu.c
@@ -522,7 +522,6 @@ static bool GPU_UpdateTexture(SDL_Renderer *renderer, SDL_Texture *texture, cons
if (texture->format == SDL_PIXELFORMAT_P010) {
UVpitch = (pitch + 3) & ~3;
} else {
- bpp = 1;
UVpitch = (pitch + 1) & ~1;
}
retval &= GPU_UpdateTextureInternal(renderdata, cpass, data->textureNV, bpp, rect->x / 2, rect->y / 2, (rect->w + 1) / 2, (rect->h + 1) / 2, UVplane, UVpitch);
@@ -534,8 +533,13 @@ static bool GPU_UpdateTexture(SDL_Renderer *renderer, SDL_Texture *texture, cons
const Uint8 *Uplane = Yplane + rect->h * Ypitch;
const Uint8 *Vplane = Uplane + ((rect->h + 1) / 2) * UVpitch;
- retval &= GPU_UpdateTextureInternal(renderdata, cpass, data->textureU, bpp, rect->x / 2, rect->y / 2, (rect->w + 1) / 2, (rect->h + 1) / 2, Uplane, UVpitch);
- retval &= GPU_UpdateTextureInternal(renderdata, cpass, data->textureV, bpp, rect->x / 2, rect->y / 2, (rect->w + 1) / 2, (rect->h + 1) / 2, Vplane, UVpitch);
+ if (texture->format == SDL_PIXELFORMAT_YV12) {
+ retval &= GPU_UpdateTextureInternal(renderdata, cpass, data->textureV, bpp, rect->x / 2, rect->y / 2, (rect->w + 1) / 2, (rect->h + 1) / 2, Uplane, UVpitch);
+ retval &= GPU_UpdateTextureInternal(renderdata, cpass, data->textureU, bpp, rect->x / 2, rect->y / 2, (rect->w + 1) / 2, (rect->h + 1) / 2, Vplane, UVpitch);
+ } else {
+ retval &= GPU_UpdateTextureInternal(renderdata, cpass, data->textureU, bpp, rect->x / 2, rect->y / 2, (rect->w + 1) / 2, (rect->h + 1) / 2, Uplane, UVpitch);
+ retval &= GPU_UpdateTextureInternal(renderdata, cpass, data->textureV, bpp, rect->x / 2, rect->y / 2, (rect->w + 1) / 2, (rect->h + 1) / 2, Vplane, UVpitch);
+ }
}
#endif
diff --git a/src/render/vulkan/SDL_render_vulkan.c b/src/render/vulkan/SDL_render_vulkan.c
index 562a9e2986ae7..0dd5f74a1384d 100644
--- a/src/render/vulkan/SDL_render_vulkan.c
+++ b/src/render/vulkan/SDL_render_vulkan.c
@@ -440,6 +440,8 @@ static VkDeviceSize VULKAN_GetBytesPerPixel(VkFormat vkFormat, int plane)
return 1;
case VK_FORMAT_G8_B8R8_2PLANE_420_UNORM:
return (plane == 0) ? 1 : 2;
+ case VK_FORMAT_G10X6_B10X6R10X6_2PLANE_420_UNORM_3PACK16:
+ return (plane == 0) ? 2 : 4;
default:
return 4;
}
@@ -2708,8 +2710,8 @@ static bool VULKAN_CreateTexture(SDL_Renderer *renderer, SDL_Texture *texture, S
samplerCreateInfo.magFilter = VK_FILTER_NEAREST;
samplerCreateInfo.minFilter = VK_FILTER_NEAREST;
samplerCreateInfo.mipmapMode = VK_SAMPLER_MIPMAP_MODE_NEAREST;
- samplerCreateInfo.addressModeU = VK_SAMPLER_ADDRESS_MODE_REPEAT;
- samplerCreateInfo.addressModeV = VK_SAMPLER_ADDRESS_MODE_REPEAT;
+ samplerCreateInfo.addressModeU = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE;
+ samplerCreateInfo.addressModeV = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE;
samplerCreateInfo.addressModeW = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE;
samplerCreateInfo.mipLodBias = 0.0f;
samplerCreateInfo.anisotropyEnable = VK_FALSE;
@@ -2911,6 +2913,19 @@ static bool VULKAN_UpdateTextureInternal(VULKAN_RenderData *rendererData, VkImag
return true;
}
+#ifdef SDL_HAVE_YUV
+static bool VULKAN_UpdateTextureNV(SDL_Renderer *renderer, SDL_Texture *texture,
+ const SDL_Rect *rect,
+ const Uint8 *Yplane, int Ypitch,
+ const Uint8 *UVplane, int UVpitch);
+
+static bool VULKAN_UpdateTextureYUV(SDL_Renderer *renderer, SDL_Texture *texture,
+ const SDL_Rect *rect,
+ const Uint8 *Yplane, int Ypitch,
+ const Uint8 *Uplane, int Upitch,
+ const Uint8 *Vplane, int Vpitch);
+#endif
+
static bool VULKAN_UpdateTexture(SDL_Renderer *renderer, SDL_Texture *texture,
const SDL_Rect *rect, const void *srcPixels,
int srcPitch)
@@ -2922,38 +2937,36 @@ static bool VULKAN_UpdateTexture(SDL_Renderer *renderer, SDL_Texture *texture,
return SDL_SetError("Texture is not currently available");
}
- if (!VULKAN_UpdateTextureInternal(rendererData, textureData->mainImage.image, textureData->mainImage.format, 0, rect->x, rect->y, rect->w, rect->h, srcPixels, srcPitch, &textureData->mainImage.imageLayout)) {
- return false;
- }
#ifdef SDL_HAVE_YUV
Uint32 numPlanes = VULKAN_VkFormatGetNumPlanes(textureData->mainImage.format);
- // Skip to the correct offset into the next texture
- srcPixels = (const void *)((const Uint8 *)srcPixels + rect->h * srcPitch);
- // YUV data
- if (numPlanes == 3) {
- for (Uint32 plane = 1; plane < numPlanes; plane++) {
- if (!VULKAN_UpdateTextureInternal(rendererData, textureData->mainImage.image, textureData->mainImage.format, plane, rect->x / 2, rect->y / 2, (rect->w + 1) / 2, (rect->h + 1) / 2, srcPixels, (srcPitch + 1) / 2, &textureData->mainImage.imageLayout)) {
- return false;
- }
-
- // Skip to the correct offset into the next texture
- srcPixels = (const void *)((const Uint8 *)srcPixels + ((rect->h + 1) / 2) * ((srcPitch + 1) / 2));
- }
- }
- // NV12/NV21 data
- else if (numPlanes == 2)
- {
- if (texture->format == SDL_PIXELFORMAT_P010) {
- srcPitch = (srcPitch + 3) & ~3;
+ if (numPlanes == 2) {
+ // NV12/NV21 data
+ int UVbpp = VULKAN_GetBytesPerPixel(textureData->mainImage.format, 1);
+ int Ypitch = srcPitch;
+ int UVpitch = (srcPitch + (UVbpp - 1)) & ~(UVbpp - 1);
+ const Uint8 *plane0 = (const Uint8 *)srcPixels;
+ const Uint8 *plane1 = plane0 + rect->h * srcPitch;
+
+ return VULKAN_UpdateTextureNV(renderer, texture, rect, plane0, Ypitch, plane1, UVpitch);
+
+ } else if (numPlanes == 3) {
+ // YUV data
+ int Ypitch = srcPitch;
+ int UVpitch = ((Ypitch + 1) / 2);
+ const Uint8 *plane0 = (const Uint8 *)srcPixels;
+ const Uint8 *plane1 = plane0 + rect->h * Ypitch;
+ const Uint8 *plane2 = plane1 + ((rect->h + 1) / 2) * UVpitch;
+
+ if (texture->format == SDL_PIXELFORMAT_YV12) {
+ return VULKAN_UpdateTextureYUV(renderer, texture, rect, plane0, Ypitch, plane2, UVpitch, plane1, UVpitch);
} else {
- srcPitch = (srcPitch + 1) & ~1;
- }
-
- if (!VULKAN_UpdateTextureInternal(rendererData, textureData->mainImage.image, textureData->mainImage.format, 1, rect->x / 2, rect->y / 2, (rect->w + 1) / 2, (rect->h + 1) / 2, srcPixels, srcPitch, &textureData->mainImage.imageLayout)) {
- return false;
+ return VULKAN_UpdateTextureYUV(renderer, texture, rect, plane0, Ypitch, plane1, UVpitch, plane2, UVpitch);
}
}
#endif
+ if (!VULKAN_UpdateTextureInternal(rendererData, textureData->mainImage.image, textureData->mainImage.format, 0, rect->x, rect->y, rect->w, rect->h, srcPixels, srcPitch, &textureData->mainImage.imageLayout)) {
+ return false;
+ }
return true;
}