Fixed YUV size and pitch calculations

This commit is contained in:
Sam Lantinga
2026-09-09 13:43:34 -07:00
parent 9adf3ff6d3
commit fe2bca9748
8 changed files with 85 additions and 69 deletions

View File

@@ -140,6 +140,7 @@ bool SDL_SW_UpdateYUVTexture(SDL_SW_YUVTexture *swdata, const SDL_Rect *rect,
int row;
size_t length;
const int bpp = SDL_BYTESPERPIXEL(swdata->format);
const int UVpitch = ((pitch / bpp + 1) / 2) * bpp;
// Copy the Y plane
src = (Uint8 *)pixels;
@@ -158,19 +159,19 @@ bool SDL_SW_UpdateYUVTexture(SDL_SW_YUVTexture *swdata, const SDL_Rect *rect,
length = ((rect->w + 1) / 2) * bpp;
for (row = 0; row < (rect->h + 1) / 2; ++row) {
SDL_memcpy(dst, src, length);
src += (pitch + 1) / 2;
src += UVpitch;
dst += swdata->pitches[1];
}
// Copy the next plane
src = (Uint8 *)pixels + rect->h * pitch + ((rect->h + 1) / 2) * ((pitch + 1) / 2);
src = (Uint8 *)pixels + rect->h * pitch + ((rect->h + 1) / 2) * UVpitch;
dst = swdata->pixels + swdata->h * swdata->w +
((swdata->h + 1) / 2) * ((swdata->w + 1) / 2);
dst += rect->y / 2 * ((swdata->w + 1) / 2) + rect->x / 2;
length = ((rect->w + 1) / 2) * bpp;
for (row = 0; row < (rect->h + 1) / 2; ++row) {
SDL_memcpy(dst, src, length);
src += (pitch + 1) / 2;
src += UVpitch;
dst += swdata->pitches[2];
}
}

View File

@@ -28,6 +28,7 @@
#include "../SDL_d3dmath.h"
#include "../../video/windows/SDL_windowsvideo.h"
#include "../../video/SDL_pixels_c.h"
#include "../../video/SDL_yuv_c.h"
#define D3D_DEBUG_INFO
#include <d3d9.h>
@@ -807,8 +808,12 @@ static bool D3D_LockTexture(SDL_Renderer *renderer, SDL_Texture *texture,
if (texturedata->yuv) {
// It's more efficient to upload directly...
if (!texturedata->pixels) {
texturedata->pitch = texture->w;
texturedata->pixels = (Uint8 *)SDL_malloc((texture->h * texturedata->pitch * 3) / 2);
size_t size, calculated_pitch;
if (!SDL_CalculateYUVSize(texture->format, texture->w, texture->h, &size, &calculated_pitch)) {
return false;
}
texturedata->pitch = (int)calculated_pitch;
texturedata->pixels = (Uint8 *)SDL_malloc(size);
if (!texturedata->pixels) {
return false;
}

View File

@@ -28,6 +28,7 @@
#include "../SDL_sysrender.h"
#include "../SDL_d3dmath.h"
#include "../../video/SDL_pixels_c.h"
#include "../../video/SDL_yuv_c.h"
#include <d3d11_1.h>
#ifdef HAVE_DXGI1_5_H
@@ -1608,8 +1609,9 @@ static bool D3D11_UpdateTexture(SDL_Renderer *renderer, SDL_Texture *texture,
return false;
}
} else {
int Ypitch = srcPitch;
int UVpitch = ((Ypitch + 1 * SDL_BYTESPERPIXEL(texture->format)) / 2);
const int bpp = SDL_BYTESPERPIXEL(texture->format);
const int Ypitch = srcPitch;
const int UVpitch = ((Ypitch / bpp + 1) / 2) * bpp;
const Uint8 *plane0 = (const Uint8 *)srcPixels;
const Uint8 *plane1 = plane0 + rect->h * Ypitch;
const Uint8 *plane2 = plane1 + ((rect->h + 1) / 2) * UVpitch;
@@ -1795,8 +1797,12 @@ static bool D3D11_LockTexture(SDL_Renderer *renderer, SDL_Texture *texture,
if (textureData->yuv || textureData->nv12) {
// It's more efficient to upload directly...
if (!textureData->pixels) {
textureData->pitch = texture->w;
textureData->pixels = (Uint8 *)SDL_malloc((texture->h * textureData->pitch * 3) / 2);
size_t size, calculated_calculated_pitch;
if (!SDL_CalculateYUVSize(texture->format, texture->w, texture->h, &size, &calculated_calculated_pitch)) {
return false;
}
textureData->pitch = (int)calculated_calculated_pitch;
textureData->pixels = (Uint8 *)SDL_malloc(size);
if (!textureData->pixels) {
return false;
}

View File

@@ -46,6 +46,7 @@ extern "C" {
// This must be included here as the function definitions in SDL_pixels.c/_c.h are C, not C++
#include "../../video/SDL_pixels_c.h"
#include "../../video/SDL_yuv_c.h"
/* !!! FIXME: vertex buffer bandwidth could be lower; only use UV coords when
!!! FIXME: textures are needed. */
@@ -2079,16 +2080,17 @@ static bool D3D12_UpdateTexture(SDL_Renderer *renderer, SDL_Texture *texture,
}
} else {
const int bpp = SDL_BYTESPERPIXEL(texture->format);
const int UVpitch = ((srcPitch / bpp + 1) / 2) * bpp;
// Skip to the correct offset into the next texture
srcPixels = (const void *)((const Uint8 *)srcPixels + rect->h * srcPitch);
if (!D3D12_UpdateTextureInternal(rendererData, texture->format == SDL_PIXELFORMAT_YV12 ? textureData->mainTextureV : textureData->mainTextureU, 0, rect->x / 2, rect->y / 2, (rect->w + 1) / 2, (rect->h + 1) / 2, srcPixels, (srcPitch + 1 * bpp) / 2, texture->format == SDL_PIXELFORMAT_YV12 ? &textureData->mainResourceStateV : &textureData->mainResourceStateU)) {
if (!D3D12_UpdateTextureInternal(rendererData, texture->format == SDL_PIXELFORMAT_YV12 ? textureData->mainTextureV : textureData->mainTextureU, 0, rect->x / 2, rect->y / 2, (rect->w + 1) / 2, (rect->h + 1) / 2, srcPixels, UVpitch, texture->format == SDL_PIXELFORMAT_YV12 ? &textureData->mainResourceStateV : &textureData->mainResourceStateU)) {
return false;
}
// Skip to the correct offset into the next texture
srcPixels = (const void *)((const Uint8 *)srcPixels + ((rect->h + 1) / 2) * ((srcPitch + 1 * bpp) / 2));
if (!D3D12_UpdateTextureInternal(rendererData, texture->format == SDL_PIXELFORMAT_YV12 ? textureData->mainTextureU : textureData->mainTextureV, 0, rect->x / 2, rect->y / 2, (rect->w + 1) / 2, (rect->h + 1) / 2, srcPixels, (srcPitch + 1 * bpp) / 2, texture->format == SDL_PIXELFORMAT_YV12 ? &textureData->mainResourceStateU : &textureData->mainResourceStateV)) {
srcPixels = (const void *)((const Uint8 *)srcPixels + ((rect->h + 1) / 2) * UVpitch);
if (!D3D12_UpdateTextureInternal(rendererData, texture->format == SDL_PIXELFORMAT_YV12 ? textureData->mainTextureU : textureData->mainTextureV, 0, rect->x / 2, rect->y / 2, (rect->w + 1) / 2, (rect->h + 1) / 2, srcPixels, UVpitch, texture->format == SDL_PIXELFORMAT_YV12 ? &textureData->mainResourceStateU : &textureData->mainResourceStateV)) {
return false;
}
}
@@ -2201,8 +2203,12 @@ static bool D3D12_LockTexture(SDL_Renderer *renderer, SDL_Texture *texture,
if (textureData->yuv || textureData->nv12) {
// It's more efficient to upload directly...
if (!textureData->pixels) {
textureData->pitch = texture->w;
textureData->pixels = (Uint8 *)SDL_malloc((texture->h * textureData->pitch * 3) / 2);
size_t size, calculated_pitch;
if (!SDL_CalculateYUVSize(texture->format, texture->w, texture->h, &size, &calculated_pitch)) {
return false;
}
textureData->pitch = (int)calculated_pitch;
textureData->pixels = (Uint8 *)SDL_malloc(size);
if (!textureData->pixels) {
return false;
}

View File

@@ -25,6 +25,7 @@
#include "../../events/SDL_windowevents_c.h"
#include "../../video/SDL_pixels_c.h"
#include "../../video/SDL_sysvideo.h"
#include "../../video/SDL_yuv_c.h"
#include "../SDL_d3dmath.h"
#include "../SDL_sysrender.h"
#include "SDL_gpu_util.h"
@@ -321,25 +322,16 @@ static bool GPU_CreateTexture(SDL_Renderer *renderer, SDL_Texture *texture, SDL_
data->format = format;
if (texture->access == SDL_TEXTUREACCESS_STREAMING) {
size_t size;
data->pitch = texture->w * SDL_BYTESPERPIXEL(texture->format);
size = (size_t)texture->h * data->pitch;
if (texture->format == SDL_PIXELFORMAT_YV12 ||
texture->format == SDL_PIXELFORMAT_IYUV ||
texture->format == SDL_PIXELFORMAT_I0FL) {
// Need to add size for the U and V planes
size += 2 * ((texture->h + 1) / 2) * ((data->pitch + 1) / 2);
}
if (texture->format == SDL_PIXELFORMAT_I444 ||
texture->format == SDL_PIXELFORMAT_I4FL) {
// Need to add size for the U and V planes
size += 2 * texture->h * data->pitch;
}
if (texture->format == SDL_PIXELFORMAT_NV12 ||
texture->format == SDL_PIXELFORMAT_NV21 ||
texture->format == SDL_PIXELFORMAT_P010) {
// Need to add size for the U/V plane
size += 2 * ((texture->h + 1) / 2) * ((data->pitch + 1) / 2);
size_t size, pitch;
if (SDL_ISPIXELFORMAT_FOURCC(texture->format)) {
if (!SDL_CalculateYUVSize(texture->format, texture->w, texture->h, &size, &pitch)) {
SDL_free(data);
return false;
}
data->pitch = (int)pitch;
} else {
data->pitch = texture->w * SDL_BYTESPERPIXEL(texture->format);
size = (size_t)texture->h * data->pitch;
}
data->pixels = SDL_calloc(1, size);
if (!data->pixels) {
@@ -584,8 +576,8 @@ static bool GPU_UpdateTexture(SDL_Renderer *renderer, SDL_Texture *texture, cons
retval &= GPU_UpdateTextureInternal(renderdata, cpass, data->textureU, bpp, rect->x, rect->y, rect->w, rect->h, Uplane, pitch);
retval &= GPU_UpdateTextureInternal(renderdata, cpass, data->textureV, bpp, rect->x, rect->y, rect->w, rect->h, Vplane, pitch);
} else {
int Ypitch = pitch;
int UVpitch = ((Ypitch + 1 * SDL_BYTESPERPIXEL(texture->format)) / 2);
const int Ypitch = pitch;
const int UVpitch = ((Ypitch / bpp + 1) / 2) * bpp;
const Uint8 *Yplane = (const Uint8 *)pixels;
const Uint8 *Uplane = Yplane + rect->h * Ypitch;
const Uint8 *Vplane = Uplane + ((rect->h + 1) / 2) * UVpitch;

View File

@@ -24,6 +24,7 @@
#include "../SDL_sysrender.h"
#include "../../video/SDL_pixels_c.h"
#include "../../video/SDL_yuv_c.h"
#import <CoreVideo/CoreVideo.h>
#import <Metal/Metal.h>
@@ -1003,13 +1004,14 @@ static bool METAL_UpdateTexture(SDL_Renderer *renderer, SDL_Texture *texture,
// YV12 stores V before U, so the plane order is swapped for it.
id<MTLTexture> firstplane = texture->format == SDL_PIXELFORMAT_YV12 ? texturedata.mtltextureV : texturedata.mtltextureU;
id<MTLTexture> secondplane = texture->format == SDL_PIXELFORMAT_YV12 ? texturedata.mtltextureU : texturedata.mtltextureV;
const int bpp = SDL_BYTESPERPIXEL(texture->format);
int UVpitch;
SDL_Rect UVrect;
if (texture->format == SDL_PIXELFORMAT_I444 || texture->format == SDL_PIXELFORMAT_I4FL) {
UVpitch = pitch;
UVrect = *rect;
} else {
UVpitch = (pitch + 1 * SDL_BYTESPERPIXEL(texture->format)) / 2;
UVpitch = ((pitch / bpp + 1) / 2) * bpp;
UVrect.x = rect->x / 2;
UVrect.y = rect->y / 2;
UVrect.w = (rect->w + 1) / 2;
@@ -1030,8 +1032,9 @@ static bool METAL_UpdateTexture(SDL_Renderer *renderer, SDL_Texture *texture,
}
if (texturedata.nv12) {
const int bpp = SDL_BYTESPERPIXEL(texture->format);
const int UVpitch = ((pitch / bpp + 1) / 2) * 2 * bpp;
SDL_Rect UVrect = { rect->x / 2, rect->y / 2, (rect->w + 1) / 2, (rect->h + 1) / 2 };
int UVpitch = 2 * ((pitch + 1) / 2);
// Skip to the correct offset into the next texture
pixels = (const void *)((const Uint8 *)pixels + rect->h * pitch);
@@ -1121,24 +1124,26 @@ static bool METAL_LockTexture(SDL_Renderer *renderer, SDL_Texture *texture,
@autoreleasepool {
SDL3METAL_RenderData *data = (__bridge SDL3METAL_RenderData *)renderer->internal;
SDL3METAL_TextureData *texturedata = (__bridge SDL3METAL_TextureData *)texture->internal;
int buffersize = 0;
id<MTLBuffer> lockedbuffer = nil;
size_t size, calculated_pitch;
if (rect->w <= 0 || rect->h <= 0) {
return SDL_SetError("Invalid rectangle dimensions for LockTexture.");
}
*pitch = SDL_BYTESPERPIXEL(texture->format) * rect->w;
#ifdef SDL_HAVE_YUV
if (texturedata.yuv || texturedata.nv12) {
buffersize = ((*pitch) * rect->h) + (2 * (*pitch + 1) / 2) * ((rect->h + 1) / 2);
if (!SDL_CalculateYUVSize(texture->format, rect->w, rect->h, &size, &calculated_pitch)) {
return false;
}
} else
#endif
{
buffersize = (*pitch) * rect->h;
calculated_pitch = SDL_BYTESPERPIXEL(texture->format) * rect->w;
size = rect->h * calculated_pitch;
}
lockedbuffer = [data.mtldevice newBufferWithLength:buffersize options:MTLResourceStorageModeShared];
lockedbuffer = [data.mtldevice newBufferWithLength:size options:MTLResourceStorageModeShared];
if (lockedbuffer == nil) {
return SDL_OutOfMemory();
}
@@ -1146,6 +1151,7 @@ static bool METAL_LockTexture(SDL_Renderer *renderer, SDL_Texture *texture,
texturedata.lockedrect = *rect;
texturedata.lockedbuffer = lockedbuffer;
*pixels = [lockedbuffer contents];
*pitch = (int)calculated_pitch;
return true;
}
@@ -1192,7 +1198,8 @@ static void METAL_UnlockTexture(SDL_Renderer *renderer, SDL_Texture *texture)
// YV12 stores V before U, so the plane order is swapped for it.
id<MTLTexture> firstplane = texture->format == SDL_PIXELFORMAT_YV12 ? texturedata.mtltextureV : texturedata.mtltextureU;
id<MTLTexture> secondplane = texture->format == SDL_PIXELFORMAT_YV12 ? texturedata.mtltextureU : texturedata.mtltextureV;
int UVpitch = (pitch + 1) / 2;
const int bpp = SDL_BYTESPERPIXEL(texture->format);
const int UVpitch = ((pitch / bpp + 1) / 2) * bpp;
[blitcmd copyFromBuffer:texturedata.lockedbuffer
sourceOffset:rect.h * pitch
@@ -1216,7 +1223,8 @@ static void METAL_UnlockTexture(SDL_Renderer *renderer, SDL_Texture *texture)
}
if (texturedata.nv12) {
int UVpitch = 2 * ((pitch + 1) / 2);
const int bpp = SDL_BYTESPERPIXEL(texture->format);
const int UVpitch = ((pitch / bpp + 1) / 2) * 2 * bpp;
[blitcmd copyFromBuffer:texturedata.lockedbuffer
sourceOffset:rect.h * pitch

View File

@@ -166,8 +166,9 @@ bool SDL_CalculateYUVSize(SDL_PixelFormat format, int w, int h, size_t *size, si
case SDL_PIXELFORMAT_NV12: /**< Planar mode: Y + U/V interleaved (2 planes) */
case SDL_PIXELFORMAT_NV21: /**< Planar mode: Y + V/U interleaved (2 planes) */
case SDL_PIXELFORMAT_P010: /**< Planar mode: Y + U/V interleaved, 10 bit (2 planes) */
if (pitch) {
*pitch = w;
*pitch = w * SDL_BYTESPERPIXEL(format);
}
if (size) {
@@ -237,7 +238,7 @@ static bool GetYUVPlanes(int width, int height, SDL_PixelFormat format, const vo
case SDL_PIXELFORMAT_IYUV:
case SDL_PIXELFORMAT_I0FL:
pitches[0] = yuv_pitch;
pitches[1] = (pitches[0] + 1 * SDL_BYTESPERPIXEL(format)) / 2;
pitches[1] = ((pitches[0] / SDL_BYTESPERPIXEL(format) + 1) / 2) * SDL_BYTESPERPIXEL(format);
pitches[2] = pitches[1];
planes[0] = (const Uint8 *)yuv;
planes[1] = planes[0] + pitches[0] * height;
@@ -1353,46 +1354,42 @@ static bool SDL_ConvertPixels_YUV_to_YUV_Copy(int width, int height, SDL_PixelFo
}
if (IsPlanar2x2Format(format)) {
const int bpp = SDL_BYTESPERPIXEL(format);
// Y plane
const size_t length = width * bpp;
for (i = height; i--;) {
SDL_memcpy(dst, src, width);
SDL_memcpy(dst, src, length);
src = (const Uint8 *)src + src_pitch;
dst = (Uint8 *)dst + dst_pitch;
}
if (format == SDL_PIXELFORMAT_YV12 || format == SDL_PIXELFORMAT_IYUV) {
if (format == SDL_PIXELFORMAT_YV12 ||
format == SDL_PIXELFORMAT_IYUV ||
format == SDL_PIXELFORMAT_I0FL) {
// U and V planes are a quarter the size of the Y plane, rounded up
width = (width + 1) / 2;
width = ((width + 1) / 2) * bpp;
height = (height + 1) / 2;
src_pitch = (src_pitch + 1) / 2;
dst_pitch = (dst_pitch + 1) / 2;
src_pitch = ((src_pitch / bpp + 1) / 2) * bpp;
dst_pitch = ((dst_pitch / bpp + 1) / 2) * bpp;
for (i = height * 2; i--;) {
SDL_memcpy(dst, src, width);
src = (const Uint8 *)src + src_pitch;
dst = (Uint8 *)dst + dst_pitch;
}
} else if (format == SDL_PIXELFORMAT_NV12 || format == SDL_PIXELFORMAT_NV21) {
// U/V plane is half the height of the Y plane, rounded up
} else if (format == SDL_PIXELFORMAT_NV12 ||
format == SDL_PIXELFORMAT_NV21 ||
format == SDL_PIXELFORMAT_P010) {
// U/V plane is half the height of the Y plane, rounded up, with packed CrCb
width = ((width + 1) / 2) * 2 * bpp;
height = (height + 1) / 2;
width = ((width + 1) / 2) * 2;
src_pitch = ((src_pitch + 1) / 2) * 2;
dst_pitch = ((dst_pitch + 1) / 2) * 2;
src_pitch = ((src_pitch / bpp + 1) / 2) * 2 * bpp;
dst_pitch = ((dst_pitch / bpp + 1) / 2) * 2 * bpp;
for (i = height; i--;) {
SDL_memcpy(dst, src, width);
src = (const Uint8 *)src + src_pitch;
dst = (Uint8 *)dst + dst_pitch;
}
} else if (format == SDL_PIXELFORMAT_P010) {
// U/V plane is half the height of the Y plane, rounded up
height = (height + 1) / 2;
width = ((width + 1) / 2) * 2;
src_pitch = ((src_pitch + 1) / 2) * 2;
dst_pitch = ((dst_pitch + 1) / 2) * 2;
for (i = height; i--;) {
SDL_memcpy(dst, src, width * sizeof(Uint16));
src = (const Uint8 *)src + src_pitch;
dst = (Uint8 *)dst + dst_pitch;
}
}
return true;
}

View File

@@ -416,15 +416,16 @@ static bool create_textures(SDL_Renderer *renderer, SDL_Surface *original, SDL_P
goto done;
}
if (planar && (yuv_format == SDL_PIXELFORMAT_YV12 || yuv_format == SDL_PIXELFORMAT_IYUV || yuv_format == SDL_PIXELFORMAT_I0FL)) {
const int bpp = SDL_BYTESPERPIXEL(yuv_format);
const int Yrows = original->h;
const int UVrows = ((original->h + 1) / 2);
const int src_Ypitch = pitch;
const int src_UVpitch = ((pitch + 1 * SDL_BYTESPERPIXEL(yuv_format)) / 2);
const int src_UVpitch = ((pitch / bpp + 1) / 2) * bpp;
const Uint8 *src_plane0 = (const Uint8 *)raw_yuv;
const Uint8 *src_plane1 = src_plane0 + Yrows * src_Ypitch;
const Uint8 *src_plane2 = src_plane1 + UVrows * src_UVpitch;
const int Ypitch = pitch + 37;
const int UVpitch = ((Ypitch + 1 * SDL_BYTESPERPIXEL(yuv_format)) / 2);
const int UVpitch = ((Ypitch / bpp + 1) / 2) * bpp;
Uint8 *plane0 = (Uint8 *)SDL_calloc(1, Yrows * Ypitch);
Uint8 *plane1 = (Uint8 *)SDL_calloc(1, UVrows * UVpitch);
Uint8 *plane2 = (Uint8 *)SDL_calloc(1, UVrows * UVpitch);