Files
SDL/test/testyuv.c
Sam Lantinga 0f5a7cbd2c testyuv: verify the YUV surface locking path
Also implemented YUV surface locking for Vulkan
2026-09-10 13:03:31 -07:00

1344 lines
51 KiB
C

/*
Copyright (C) 1997-2026 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_main.h>
#include <SDL3/SDL_test.h>
#include "testyuv_cvt.h"
#include "testutils.h"
/* 422 (YUY2, etc) and I4FL formats are the largest */
#define MAX_YUV_SURFACE_SIZE(W, H, P) ((H + 1) * ((W + 1) + P) * 3 * 2)
/* Return true if the YUV format is packed pixels */
static bool is_packed_yuv_format(Uint32 format)
{
return format == SDL_PIXELFORMAT_YUY2 || format == SDL_PIXELFORMAT_UYVY || format == SDL_PIXELFORMAT_YVYU;
}
/* Create a surface with a good pattern for verifying YUV conversion */
static SDL_Surface *generate_test_pattern(int pattern_size)
{
SDL_Surface *pattern = SDL_CreateSurface(pattern_size, pattern_size, SDL_PIXELFORMAT_RGB24);
if (pattern) {
int i, x, y;
Uint8 *p, c;
const int thickness = 2; /* Important so 2x2 blocks of color are the same, to avoid Cr/Cb interpolation over pixels */
/* R, G, B in alternating horizontal bands */
for (y = 0; y < pattern->h - (thickness - 1); y += thickness) {
for (i = 0; i < thickness; ++i) {
p = (Uint8 *)pattern->pixels + (y + i) * pattern->pitch + ((y / thickness) % 3);
for (x = 0; x < pattern->w; ++x) {
*p = 0xFF;
p += 3;
}
}
}
/* Black and white in alternating vertical bands */
c = 0xFF;
for (x = 1 * thickness; x < pattern->w; x += 2 * thickness) {
for (i = 0; i < thickness; ++i) {
p = (Uint8 *)pattern->pixels + (x + i) * 3;
for (y = 0; y < pattern->h; ++y) {
SDL_memset(p, c, 3);
p += pattern->pitch;
}
}
if (c) {
c = 0x00;
} else {
c = 0xFF;
}
}
}
return pattern;
}
static bool verify_yuv_data(Uint32 format, SDL_Colorspace colorspace, const Uint8 *yuv, int yuv_pitch, SDL_Surface *surface, int tolerance)
{
const int size = (surface->h * surface->pitch);
Uint8 *rgb;
bool result = false;
rgb = (Uint8 *)SDL_malloc(size);
if (!rgb) {
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Out of memory");
return false;
}
if (SDL_ConvertPixelsAndColorspace(surface->w, surface->h, format, colorspace, 0, yuv, yuv_pitch, surface->format, SDL_COLORSPACE_SRGB, 0, rgb, surface->pitch)) {
int x, y;
result = true;
for (y = 0; y < surface->h; ++y) {
const Uint8 *actual = rgb + y * surface->pitch;
const Uint8 *expected = (const Uint8 *)surface->pixels + y * surface->pitch;
for (x = 0; x < surface->w; ++x) {
int deltaR = (int)actual[0] - expected[0];
int deltaG = (int)actual[1] - expected[1];
int deltaB = (int)actual[2] - expected[2];
int distance = (deltaR * deltaR + deltaG * deltaG + deltaB * deltaB);
if (distance > tolerance) {
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Pixel at %d,%d was 0x%.2x,0x%.2x,0x%.2x, expected 0x%.2x,0x%.2x,0x%.2x, distance = %d", x, y, actual[0], actual[1], actual[2], expected[0], expected[1], expected[2], distance);
result = false;
}
actual += 3;
expected += 3;
}
}
} else {
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Couldn't convert %s to %s: %s", SDL_GetPixelFormatName(format), SDL_GetPixelFormatName(surface->format), SDL_GetError());
}
SDL_free(rgb);
return result;
}
static bool run_automated_tests(int pattern_size, int extra_pitch)
{
const Uint32 formats[] = {
SDL_PIXELFORMAT_YV12,
SDL_PIXELFORMAT_IYUV,
SDL_PIXELFORMAT_I444,
SDL_PIXELFORMAT_NV12,
SDL_PIXELFORMAT_NV21,
SDL_PIXELFORMAT_YUY2,
SDL_PIXELFORMAT_UYVY,
SDL_PIXELFORMAT_YVYU
};
int i, j;
SDL_Surface *pattern = generate_test_pattern(pattern_size);
const int yuv_len = MAX_YUV_SURFACE_SIZE(pattern->w, pattern->h, extra_pitch);
Uint8 *yuv1 = (Uint8 *)SDL_malloc(yuv_len);
Uint8 *yuv2 = (Uint8 *)SDL_malloc(yuv_len);
int yuv1_pitch, yuv2_pitch;
YUV_CONVERSION_MODE mode;
SDL_Colorspace colorspace;
const int tight_tolerance = 20;
const int loose_tolerance = 333;
bool result = false;
if (!pattern || !yuv1 || !yuv2) {
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Couldn't allocate test surfaces");
goto done;
}
mode = GetYUVConversionModeForResolution(pattern->w, pattern->h);
colorspace = GetColorspaceForYUVConversionMode(mode);
/* Verify conversion from YUV formats */
for (i = 0; i < SDL_arraysize(formats); ++i) {
if (!ConvertRGBtoYUV(formats[i], pattern->pixels, pattern->pitch, yuv1, pattern->w, pattern->h, mode, 0, 100)) {
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "ConvertRGBtoYUV() doesn't support converting to %s", SDL_GetPixelFormatName(formats[i]));
goto done;
}
yuv1_pitch = CalculateYUVPitch(formats[i], pattern->w);
if (!verify_yuv_data(formats[i], colorspace, yuv1, yuv1_pitch, pattern, tight_tolerance)) {
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Failed conversion from %s to RGB", SDL_GetPixelFormatName(formats[i]));
goto done;
}
}
/* Verify conversion to YUV formats */
for (i = 0; i < SDL_arraysize(formats); ++i) {
yuv1_pitch = CalculateYUVPitch(formats[i], pattern->w) + extra_pitch;
if (!SDL_ConvertPixelsAndColorspace(pattern->w, pattern->h, pattern->format, SDL_COLORSPACE_SRGB, 0, pattern->pixels, pattern->pitch, formats[i], colorspace, 0, yuv1, yuv1_pitch)) {
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Couldn't convert %s to %s: %s", SDL_GetPixelFormatName(pattern->format), SDL_GetPixelFormatName(formats[i]), SDL_GetError());
goto done;
}
if (!verify_yuv_data(formats[i], colorspace, yuv1, yuv1_pitch, pattern, tight_tolerance)) {
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Failed conversion from RGB to %s", SDL_GetPixelFormatName(formats[i]));
goto done;
}
}
/* Verify conversion between YUV formats */
for (i = 0; i < SDL_arraysize(formats); ++i) {
for (j = 0; j < SDL_arraysize(formats); ++j) {
if (formats[i] != formats[j] && (formats[i] == SDL_PIXELFORMAT_I444 || formats[j] == SDL_PIXELFORMAT_I444)) {
// Converting between 444 and 420 formats is lossy and not currently supported
continue;
}
yuv1_pitch = CalculateYUVPitch(formats[i], pattern->w) + extra_pitch;
yuv2_pitch = CalculateYUVPitch(formats[j], pattern->w) + extra_pitch;
if (!SDL_ConvertPixelsAndColorspace(pattern->w, pattern->h, pattern->format, SDL_COLORSPACE_SRGB, 0, pattern->pixels, pattern->pitch, formats[i], colorspace, 0, yuv1, yuv1_pitch)) {
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Couldn't convert %s to %s: %s", SDL_GetPixelFormatName(pattern->format), SDL_GetPixelFormatName(formats[i]), SDL_GetError());
goto done;
}
if (!SDL_ConvertPixelsAndColorspace(pattern->w, pattern->h, formats[i], colorspace, 0, yuv1, yuv1_pitch, formats[j], colorspace, 0, yuv2, yuv2_pitch)) {
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Couldn't convert %s to %s: %s", SDL_GetPixelFormatName(formats[i]), SDL_GetPixelFormatName(formats[j]), SDL_GetError());
goto done;
}
if (!verify_yuv_data(formats[j], colorspace, yuv2, yuv2_pitch, pattern, tight_tolerance)) {
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Failed conversion from %s to %s", SDL_GetPixelFormatName(formats[i]), SDL_GetPixelFormatName(formats[j]));
goto done;
}
}
}
/* Verify conversion between YUV formats in-place */
for (i = 0; i < SDL_arraysize(formats); ++i) {
for (j = 0; j < SDL_arraysize(formats); ++j) {
if (is_packed_yuv_format(formats[i]) != is_packed_yuv_format(formats[j])) {
/* Can't change plane vs packed pixel layout in-place */
continue;
}
if (formats[i] != formats[j] && (formats[i] == SDL_PIXELFORMAT_I444 || formats[j] == SDL_PIXELFORMAT_I444)) {
// Converting between 444 and 420 formats is lossy and not currently supported
continue;
}
yuv1_pitch = CalculateYUVPitch(formats[i], pattern->w) + extra_pitch;
yuv2_pitch = CalculateYUVPitch(formats[j], pattern->w) + extra_pitch;
if (!SDL_ConvertPixelsAndColorspace(pattern->w, pattern->h, pattern->format, SDL_COLORSPACE_SRGB, 0, pattern->pixels, pattern->pitch, formats[i], colorspace, 0, yuv1, yuv1_pitch)) {
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Couldn't convert %s to %s: %s", SDL_GetPixelFormatName(pattern->format), SDL_GetPixelFormatName(formats[i]), SDL_GetError());
goto done;
}
if (!SDL_ConvertPixelsAndColorspace(pattern->w, pattern->h, formats[i], colorspace, 0, yuv1, yuv1_pitch, formats[j], colorspace, 0, yuv1, yuv2_pitch)) {
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Couldn't convert %s to %s: %s", SDL_GetPixelFormatName(formats[i]), SDL_GetPixelFormatName(formats[j]), SDL_GetError());
goto done;
}
if (!verify_yuv_data(formats[j], colorspace, yuv1, yuv2_pitch, pattern, tight_tolerance)) {
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Failed conversion from %s to %s", SDL_GetPixelFormatName(formats[i]), SDL_GetPixelFormatName(formats[j]));
goto done;
}
}
}
/* Verify round trip through BT.2020 */
colorspace = SDL_COLORSPACE_BT2020_FULL;
if (!ConvertRGBtoYUV(SDL_PIXELFORMAT_P010, pattern->pixels, pattern->pitch, yuv1, pattern->w, pattern->h, YUV_CONVERSION_BT2020, 0, 100)) {
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "ConvertRGBtoYUV() doesn't support converting to %s", SDL_GetPixelFormatName(SDL_PIXELFORMAT_P010));
goto done;
}
yuv1_pitch = CalculateYUVPitch(SDL_PIXELFORMAT_P010, pattern->w);
if (!verify_yuv_data(SDL_PIXELFORMAT_P010, colorspace, yuv1, yuv1_pitch, pattern, tight_tolerance)) {
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Failed conversion from %s to RGB", SDL_GetPixelFormatName(SDL_PIXELFORMAT_P010));
goto done;
}
/* The pitch needs to be Uint16 aligned for P010 pixels */
yuv1_pitch = CalculateYUVPitch(SDL_PIXELFORMAT_P010, pattern->w) + ((extra_pitch + 1) & ~1);
if (!SDL_ConvertPixelsAndColorspace(pattern->w, pattern->h, pattern->format, SDL_COLORSPACE_SRGB, 0, pattern->pixels, pattern->pitch, SDL_PIXELFORMAT_P010, colorspace, 0, yuv1, yuv1_pitch)) {
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Couldn't convert %s to %s: %s", SDL_GetPixelFormatName(pattern->format), SDL_GetPixelFormatName(SDL_PIXELFORMAT_P010), SDL_GetError());
goto done;
}
/* Going through XRGB2101010 format during P010 conversion is slightly lossy, so use looser tolerance here */
if (!verify_yuv_data(SDL_PIXELFORMAT_P010, colorspace, yuv1, yuv1_pitch, pattern, loose_tolerance)) {
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Failed conversion from RGB to %s", SDL_GetPixelFormatName(SDL_PIXELFORMAT_P010));
goto done;
}
result = true;
done:
SDL_free(yuv1);
SDL_free(yuv2);
SDL_DestroySurface(pattern);
return result;
}
static bool run_colorspace_test(void)
{
bool result = false;
SDL_Window *window;
SDL_Renderer *renderer;
struct {
const char *name;
SDL_Colorspace colorspace;
} colorspaces[] = {
#define COLORSPACE(X) { #X, X }
COLORSPACE(SDL_COLORSPACE_JPEG),
#if 0 /* We don't support converting color primaries here */
COLORSPACE(SDL_COLORSPACE_BT601_LIMITED),
COLORSPACE(SDL_COLORSPACE_BT601_FULL),
#endif
COLORSPACE(SDL_COLORSPACE_BT709_LIMITED),
COLORSPACE(SDL_COLORSPACE_BT709_FULL)
#undef COLORSPACE
};
SDL_Surface *rgb = NULL;
SDL_Surface *yuv = NULL;
SDL_Texture *texture = NULL;
int allowed_error = 2;
int i;
if (!SDL_CreateWindowAndRenderer("testyuv", 320, 240, 0, &window, &renderer)) {
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Couldn't create window and renderer: %s", SDL_GetError());
goto done;
}
rgb = SDL_CreateSurface(32, 32, SDL_PIXELFORMAT_XRGB8888);
if (!rgb) {
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Couldn't create RGB surface: %s", SDL_GetError());
goto done;
}
SDL_FillSurfaceRect(rgb, NULL, SDL_MapSurfaceRGB(rgb, 255, 0, 0));
for (i = 0; i < SDL_arraysize(colorspaces); ++i) {
bool next = false;
Uint8 r, g, b, a;
SDL_Log("Checking colorspace %s", colorspaces[i].name);
yuv = SDL_ConvertSurfaceAndColorspace(rgb, SDL_PIXELFORMAT_NV12, NULL, colorspaces[i].colorspace, 0);
if (!yuv) {
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Couldn't create YUV surface: %s", SDL_GetError());
goto done;
}
if (!SDL_ReadSurfacePixel(yuv, 0, 0, &r, &g, &b, &a)) {
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Couldn't read YUV surface: %s", SDL_GetError());
goto done;
}
if (SDL_abs((int)r - 255) > allowed_error ||
SDL_abs((int)g - 0) > allowed_error ||
SDL_abs((int)b - 0) > allowed_error) {
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Failed color conversion, expected 255,0,0, got %d,%d,%d", r, g, b);
}
texture = SDL_CreateTextureFromSurface(renderer, yuv);
if (!texture) {
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Couldn't create YUV texture: %s", SDL_GetError());
goto done;
}
SDL_DestroySurface(yuv);
yuv = NULL;
SDL_RenderTexture(renderer, texture, NULL, NULL);
yuv = SDL_RenderReadPixels(renderer, NULL);
if (!SDL_ReadSurfacePixel(yuv, 0, 0, &r, &g, &b, &a)) {
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Couldn't read YUV surface: %s", SDL_GetError());
goto done;
}
if (SDL_abs((int)r - 255) > allowed_error ||
SDL_abs((int)g - 0) > allowed_error ||
SDL_abs((int)b - 0) > allowed_error) {
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Failed renderer color conversion, expected 255,0,0, got %d,%d,%d", r, g, b);
}
while (!next) {
SDL_Event event;
while (SDL_PollEvent(&event)) {
switch (event.type) {
case SDL_EVENT_KEY_DOWN:
if (event.key.key == SDLK_ESCAPE) {
result = true;
goto done;
}
if (event.key.key == SDLK_SPACE) {
next = true;
}
break;
case SDL_EVENT_QUIT:
result = true;
goto done;
}
}
SDL_RenderTexture(renderer, texture, NULL, NULL);
SDL_SetRenderDrawColor(renderer, 255, 255, 255, 255);
SDL_RenderDebugText(renderer, 4, 4, colorspaces[i].name);
SDL_RenderPresent(renderer);
SDL_Delay(10);
}
SDL_DestroyTexture(texture);
texture = NULL;
}
result = true;
done:
SDL_DestroySurface(rgb);
SDL_DestroySurface(yuv);
SDL_DestroyTexture(texture);
SDL_Quit();
return result;
}
// Copied from SDL_yuv_sw.c to verify functionality
struct SW_YUVTexture
{
SDL_PixelFormat format;
int w, h;
Uint8 *pixels;
int pitches[3];
Uint8 *planes[3];
};
typedef struct SW_YUVTexture SW_YUVTexture;
static SW_YUVTexture *SW_CreateYUVTexture(SDL_PixelFormat format, int w, int h, void *pixels, int pitch)
{
SW_YUVTexture *swdata;
switch (format) {
case SDL_PIXELFORMAT_YV12:
case SDL_PIXELFORMAT_IYUV:
case SDL_PIXELFORMAT_I444:
case SDL_PIXELFORMAT_I0FL:
case SDL_PIXELFORMAT_I4FL:
case SDL_PIXELFORMAT_YUY2:
case SDL_PIXELFORMAT_UYVY:
case SDL_PIXELFORMAT_YVYU:
case SDL_PIXELFORMAT_NV12:
case SDL_PIXELFORMAT_NV21:
break;
default:
SDL_SetError("Unsupported YUV format");
return NULL;
}
swdata = (SW_YUVTexture *)SDL_calloc(1, sizeof(*swdata));
if (!swdata) {
return NULL;
}
swdata->format = format;
swdata->w = w;
swdata->h = h;
swdata->pixels = (Uint8 *)pixels;
// Find the pitch and offset values for the texture
const int bpp = SDL_BYTESPERPIXEL(format);
switch (format) {
case SDL_PIXELFORMAT_YV12:
case SDL_PIXELFORMAT_IYUV:
case SDL_PIXELFORMAT_I0FL:
swdata->pitches[0] = pitch;
swdata->pitches[1] = (((swdata->pitches[0] / bpp) + 1) / 2) * bpp;
swdata->pitches[2] = swdata->pitches[1];
swdata->planes[0] = swdata->pixels;
swdata->planes[1] = swdata->planes[0] + swdata->pitches[0] * h;
swdata->planes[2] = swdata->planes[1] + swdata->pitches[1] * ((h + 1) / 2);
break;
case SDL_PIXELFORMAT_I444:
case SDL_PIXELFORMAT_I4FL:
swdata->pitches[0] = w * bpp;
swdata->pitches[1] = swdata->pitches[0];
swdata->pitches[2] = swdata->pitches[1];
swdata->planes[0] = swdata->pixels;
swdata->planes[1] = swdata->planes[0] + swdata->pitches[0] * h;
swdata->planes[2] = swdata->planes[1] + swdata->pitches[1] * h;
break;
case SDL_PIXELFORMAT_YUY2:
case SDL_PIXELFORMAT_UYVY:
case SDL_PIXELFORMAT_YVYU:
swdata->pitches[0] = ((w + 1) / 2) * 4;
swdata->planes[0] = swdata->pixels;
break;
case SDL_PIXELFORMAT_NV12:
case SDL_PIXELFORMAT_NV21:
swdata->pitches[0] = w;
swdata->pitches[1] = 2 * ((swdata->pitches[0] + 1) / 2);
swdata->planes[0] = swdata->pixels;
swdata->planes[1] = swdata->planes[0] + swdata->pitches[0] * h;
break;
default:
SDL_assert(!"We should never get here (caught above)");
break;
}
// We're all done..
return swdata;
}
static bool SW_UpdateYUVTexture(SW_YUVTexture *swdata, const SDL_Rect *rect, const void *pixels, int pitch)
{
const int bpp = SDL_BYTESPERPIXEL(swdata->format);
switch (swdata->format) {
case SDL_PIXELFORMAT_YV12:
case SDL_PIXELFORMAT_IYUV:
case SDL_PIXELFORMAT_I0FL:
if (rect->x == 0 && rect->y == 0 &&
rect->w == swdata->w && rect->h == swdata->h && pitch == swdata->pitches[0]) {
SDL_memcpy(swdata->pixels, pixels,
(size_t)(swdata->h * swdata->w * bpp) + 2 * ((swdata->h + 1) / 2) * ((swdata->w + 1) / 2) * bpp);
} else {
Uint8 *src, *dst;
int row;
size_t length;
const int UVpitch = ((pitch / bpp + 1) / 2) * bpp;
// Copy the Y plane
src = (Uint8 *)pixels;
dst = swdata->pixels + rect->y * swdata->w * bpp + rect->x * bpp;
length = rect->w * bpp;
for (row = 0; row < rect->h; ++row) {
SDL_memcpy(dst, src, length);
src += pitch;
dst += swdata->pitches[0];
}
// Copy the next plane
src = (Uint8 *)pixels + rect->h * pitch;
dst = swdata->pixels + swdata->h * swdata->pitches[0];
dst += (rect->y / 2) * swdata->pitches[1] + (rect->x / 2) * bpp;
length = ((rect->w + 1) / 2) * bpp;
for (row = 0; row < (rect->h + 1) / 2; ++row) {
SDL_memcpy(dst, src, length);
src += UVpitch;
dst += swdata->pitches[1];
}
// Copy the next plane
src = (Uint8 *)pixels + rect->h * pitch + ((rect->h + 1) / 2) * UVpitch;
dst = swdata->pixels + swdata->h * swdata->pitches[0] + ((rect->h + 1) / 2) * swdata->pitches[1];
dst += (rect->y / 2) * swdata->pitches[2] + (rect->x / 2) * bpp;
length = ((rect->w + 1) / 2) * bpp;
for (row = 0; row < (rect->h + 1) / 2; ++row) {
SDL_memcpy(dst, src, length);
src += UVpitch;
dst += swdata->pitches[2];
}
}
break;
case SDL_PIXELFORMAT_I444:
case SDL_PIXELFORMAT_I4FL:
if (rect->x == 0 && rect->y == 0 &&
rect->w == swdata->w && rect->h == swdata->h && pitch == swdata->pitches[0]) {
SDL_memcpy(swdata->pixels, pixels, (size_t)(swdata->h * pitch * 3));
} else {
Uint8 *src, *dst;
int row;
size_t length;
// Copy the Y plane
src = (Uint8 *)pixels;
dst = swdata->pixels;
dst += rect->y * swdata->pitches[0] + rect->x * bpp;
length = rect->w * bpp;
for (row = 0; row < rect->h; ++row) {
SDL_memcpy(dst, src, length);
src += pitch;
dst += swdata->pitches[0];
}
// Copy the next plane
dst = swdata->pixels + swdata->h * swdata->pitches[0];
dst += rect->y * swdata->pitches[1] + rect->x * bpp;
for (row = 0; row < rect->h; ++row) {
SDL_memcpy(dst, src, length);
src += pitch;
dst += swdata->pitches[1];
}
// Copy the next plane
dst = swdata->pixels + swdata->h * swdata->pitches[0] + swdata->h * swdata->pitches[1];
dst += rect->y * swdata->pitches[2] + rect->x * bpp;
for (row = 0; row < rect->h; ++row) {
SDL_memcpy(dst, src, length);
src += pitch;
dst += swdata->pitches[2];
}
}
break;
case SDL_PIXELFORMAT_YUY2:
case SDL_PIXELFORMAT_UYVY:
case SDL_PIXELFORMAT_YVYU:
{
Uint8 *src, *dst;
int row;
size_t length;
src = (Uint8 *)pixels;
dst =
swdata->planes[0] + rect->y * swdata->pitches[0] +
rect->x * 2;
length = 4 * (((size_t)rect->w + 1) / 2);
for (row = 0; row < rect->h; ++row) {
SDL_memcpy(dst, src, length);
src += pitch;
dst += swdata->pitches[0];
}
} break;
case SDL_PIXELFORMAT_NV12:
case SDL_PIXELFORMAT_NV21:
{
if (rect->x == 0 && rect->y == 0 && rect->w == swdata->w && rect->h == swdata->h) {
SDL_memcpy(swdata->pixels, pixels,
(size_t)(swdata->h * swdata->w) + 2 * ((swdata->h + 1) / 2) * ((swdata->w + 1) / 2));
} else {
Uint8 *src, *dst;
int row;
size_t length;
// Copy the Y plane
src = (Uint8 *)pixels;
dst = swdata->pixels + rect->y * swdata->w + rect->x;
length = rect->w;
for (row = 0; row < rect->h; ++row) {
SDL_memcpy(dst, src, length);
src += pitch;
dst += swdata->w;
}
// Copy the next plane
src = (Uint8 *)pixels + rect->h * pitch;
dst = swdata->pixels + swdata->h * swdata->w;
dst += 2 * ((rect->y + 1) / 2) * ((swdata->w + 1) / 2) + 2 * (rect->x / 2);
length = 2 * (((size_t)rect->w + 1) / 2);
for (row = 0; row < (rect->h + 1) / 2; ++row) {
SDL_memcpy(dst, src, length);
src += 2 * ((pitch + 1) / 2);
dst += 2 * ((swdata->w + 1) / 2);
}
}
} break;
default:
return SDL_SetError("Unsupported YUV format");
}
return true;
}
static void SW_DestroyYUVTexture(SW_YUVTexture *swdata)
{
if (swdata) {
SDL_free(swdata);
}
}
static bool update_locked_texture(SDL_PixelFormat format, int w, int h, const void *pixels, int pitch, void *output_pixels, int output_pitch)
{
SW_YUVTexture *swdata = SW_CreateYUVTexture(format, w, h, output_pixels, output_pitch);
if (!swdata) {
return false;
}
SDL_Rect rect = { 0, 0, w, h };
bool result = SW_UpdateYUVTexture(swdata, &rect, pixels, pitch);
SW_DestroyYUVTexture(swdata);
return result;
}
static bool create_textures(SDL_Renderer *renderer, SDL_Surface *original, SDL_PixelFormat yuv_format, SDL_PixelFormat rgb_format, bool planar, bool lock, bool monochrome, int luminance, SDL_Texture *output[3])
{
SDL_Colorspace rgb_colorspace = SDL_COLORSPACE_SRGB;
SDL_Colorspace yuv_colorspace;
Uint8 *raw_yuv = NULL;
int pitch;
SDL_Surface *converted = NULL;
bool result = false;
size_t max_size;
YUV_CONVERSION_MODE yuv_mode = GetYUVConversionModeForResolution(original->w, original->h);
if (yuv_mode == YUV_CONVERSION_BT2020) {
rgb_colorspace = SDL_COLORSPACE_HDR10;
}
yuv_colorspace = GetColorspaceForYUVConversionMode(yuv_mode);
max_size = MAX_YUV_SURFACE_SIZE(original->w, original->h, 0);
raw_yuv = SDL_calloc(1, max_size);
ConvertRGBtoYUV(yuv_format, original->pixels, original->pitch, raw_yuv, original->w, original->h, yuv_mode, monochrome, luminance);
pitch = CalculateYUVPitch(yuv_format, original->w);
converted = SDL_CreateSurface(original->w, original->h, rgb_format);
if (!converted) {
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Couldn't create converted surface: %s", SDL_GetError());
goto done;
}
SDL_ConvertPixelsAndColorspace(original->w, original->h, yuv_format, yuv_colorspace, 0, raw_yuv, pitch, rgb_format, rgb_colorspace, 0, converted->pixels, converted->pitch);
output[0] = SDL_CreateTextureFromSurface(renderer, original);
output[1] = SDL_CreateTextureFromSurface(renderer, converted);
SDL_PropertiesID props = SDL_CreateProperties();
SDL_SetNumberProperty(props, SDL_PROP_TEXTURE_CREATE_COLORSPACE_NUMBER, yuv_colorspace);
SDL_SetNumberProperty(props, SDL_PROP_TEXTURE_CREATE_FORMAT_NUMBER, yuv_format);
SDL_SetNumberProperty(props, SDL_PROP_TEXTURE_CREATE_ACCESS_NUMBER, SDL_TEXTUREACCESS_STREAMING);
SDL_SetNumberProperty(props, SDL_PROP_TEXTURE_CREATE_WIDTH_NUMBER, original->w);
SDL_SetNumberProperty(props, SDL_PROP_TEXTURE_CREATE_HEIGHT_NUMBER, original->h);
output[2] = SDL_CreateTextureWithProperties(renderer, props);
SDL_DestroyProperties(props);
if (!output[0] || !output[1] || !output[2]) {
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Couldn't set create texture: %s", SDL_GetError());
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 / 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 / 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);
int row;
const Uint8 *src;
Uint8 *dst;
if (!plane0 || !plane1 || !plane0) {
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Couldn't create YUV planes: %s", SDL_GetError());
goto done;
}
src = src_plane0;
dst = plane0;
for (row = 0; row < Yrows; ++row) {
SDL_memcpy(dst, src, src_Ypitch);
src += src_Ypitch;
dst += Ypitch;
}
src = src_plane1;
dst = plane1;
for (row = 0; row < UVrows; ++row) {
SDL_memcpy(dst, src, src_UVpitch);
src += src_UVpitch;
dst += UVpitch;
}
src = src_plane2;
dst = plane2;
for (row = 0; row < UVrows; ++row) {
SDL_memcpy(dst, src, src_UVpitch);
src += src_UVpitch;
dst += UVpitch;
}
if (yuv_format == SDL_PIXELFORMAT_YV12) {
SDL_UpdateYUVTexture(output[2], NULL, plane0, Ypitch, plane2, UVpitch, plane1, UVpitch);
} else {
SDL_UpdateYUVTexture(output[2], NULL, plane0, Ypitch, plane1, UVpitch, plane2, UVpitch);
}
SDL_free(plane0);
SDL_free(plane1);
SDL_free(plane2);
} else if (planar && (yuv_format == SDL_PIXELFORMAT_I444 || yuv_format == SDL_PIXELFORMAT_I4FL)) {
const int rows = original->h;
const Uint8 *src_plane0 = (const Uint8 *)raw_yuv;
const Uint8 *src_plane1 = src_plane0 + rows * pitch;
const Uint8 *src_plane2 = src_plane1 + rows * pitch;
const int Ypitch = pitch + 37;
const int UVpitch = Ypitch;
Uint8 *plane0 = (Uint8 *)SDL_calloc(1, rows * Ypitch);
Uint8 *plane1 = (Uint8 *)SDL_calloc(1, rows * UVpitch);
Uint8 *plane2 = (Uint8 *)SDL_calloc(1, rows * UVpitch);
int row;
const Uint8 *src;
Uint8 *dst;
if (!plane0 || !plane1 || !plane0) {
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Couldn't create YUV planes: %s", SDL_GetError());
goto done;
}
src = src_plane0;
dst = plane0;
for (row = 0; row < rows; ++row) {
SDL_memcpy(dst, src, pitch);
src += pitch;
dst += Ypitch;
}
src = src_plane1;
dst = plane1;
for (row = 0; row < rows; ++row) {
SDL_memcpy(dst, src, pitch);
src += pitch;
dst += UVpitch;
}
src = src_plane2;
dst = plane2;
for (row = 0; row < rows; ++row) {
SDL_memcpy(dst, src, pitch);
src += pitch;
dst += UVpitch;
}
SDL_UpdateYUVTexture(output[2], NULL, plane0, Ypitch, plane1, UVpitch, plane2, UVpitch);
SDL_free(plane0);
SDL_free(plane1);
SDL_free(plane2);
} else if (planar && (yuv_format == SDL_PIXELFORMAT_NV12 || yuv_format == SDL_PIXELFORMAT_NV21 || yuv_format == SDL_PIXELFORMAT_P010)) {
const int Yrows = original->h;
const int UVrows = ((original->h + 1) / 2);
const int src_Ypitch = pitch;
const int src_UVpitch = (yuv_format == SDL_PIXELFORMAT_P010) ? ((pitch + 3) & ~3) : ((pitch + 1) & ~1);
const Uint8 *src_plane0 = (const Uint8 *)raw_yuv;
const Uint8 *src_plane1 = src_plane0 + Yrows * src_Ypitch;
const int Ypitch = pitch + 37;
const int UVpitch = ((Ypitch + 1) / 2) * 2;
Uint8 *plane0 = (Uint8 *)SDL_calloc(1, Yrows * Ypitch);
Uint8 *plane1 = (Uint8 *)SDL_calloc(1, UVrows * UVpitch);
int row;
const Uint8 *src;
Uint8 *dst;
if (!plane0 || !plane1) {
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Couldn't create YUV planes: %s", SDL_GetError());
goto done;
}
src = src_plane0;
dst = plane0;
for (row = 0; row < Yrows; ++row) {
SDL_memcpy(dst, src, src_Ypitch);
src += src_Ypitch;
dst += Ypitch;
}
src = src_plane1;
dst = plane1;
for (row = 0; row < UVrows; ++row) {
SDL_memcpy(dst, src, src_UVpitch);
src += src_UVpitch;
dst += UVpitch;
}
SDL_UpdateNVTexture(output[2], NULL, plane0, Ypitch, plane1, UVpitch);
SDL_free(plane0);
SDL_free(plane1);
} else if (lock) {
void *dst;
int dst_pitch;
if (!SDL_LockTexture(output[2], NULL, &dst, &dst_pitch)) {
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Couldn't lock YUV texture: %s", SDL_GetError());
goto done;
}
bool updated = update_locked_texture(yuv_format, original->w, original->h, raw_yuv, pitch, dst, dst_pitch);
SDL_UnlockTexture(output[2]);
if (!updated) {
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Couldn't update YUV texture: %s", SDL_GetError());
goto done;
}
} else {
SDL_UpdateTexture(output[2], NULL, raw_yuv, pitch);
}
result = true;
done:
SDL_DestroySurface(converted);
SDL_free(raw_yuv);
return result;
}
static bool has_10bit_texture_format(SDL_Renderer *renderer)
{
const SDL_PixelFormat *texture_formats = (const SDL_PixelFormat *)SDL_GetPointerProperty(SDL_GetRendererProperties(renderer), SDL_PROP_RENDERER_TEXTURE_FORMATS_POINTER, NULL);
if (texture_formats) {
for (int i = 0; texture_formats[i] != SDL_PIXELFORMAT_UNKNOWN; ++i) {
if (SDL_ISPIXELFORMAT_10BIT(texture_formats[i])) {
return true;
}
}
}
return false;
}
static bool check_output(SDL_Renderer *renderer, SDL_Surface *original, SDL_Texture *texture)
{
// Clear to yellow to clearly see unfilled pixels
SDL_SetRenderDrawColor(renderer, 255, 255, 0, SDL_ALPHA_OPAQUE);
SDL_RenderClear(renderer);
SDL_Rect rect = { 0, 0, texture->w, texture->h };
SDL_FRect frect = { 0.0f, 0.0f, (float)texture->w, (float)texture->h };
SDL_RenderTexture(renderer, texture, &frect, &frect);
SDL_Surface *output = SDL_RenderReadPixels(renderer, &rect);
if (!output) {
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Couldn't read pixels: %s", SDL_GetError());
return false;
}
SDL_RenderPresent(renderer);
// Allow some error for colorspace conversion and differences in color depth
const int MAX_ALLOWABLE_ERROR = 4096;
bool result;
if (SDLTest_CompareSurfaces(output, original, MAX_ALLOWABLE_ERROR) == 0) {
result = true;
} else {
result = false;
}
SDL_DestroySurface(output);
return result;
}
static bool run_single_format_test(SDL_Renderer *renderer, SDL_Surface *original, SDL_PixelFormat yuv_format, SDL_PixelFormat rgb_format, bool planar, bool lock)
{
SDL_Texture *output[3];
bool result = true;
if (!create_textures(renderer, original, yuv_format, rgb_format, planar, lock, false, 100, output)) {
return false;
}
if (!check_output(renderer, original, output[0])) {
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Original texture didn't match source data, failing");
result = false;
}
if (!check_output(renderer, original, output[1])) {
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "RGB output didn't match source data, failing");
result = false;
}
if (!check_output(renderer, original, output[2])) {
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "YUV output didn't match source data, failing");
result = false;
}
for (int i = 0; i < SDL_arraysize(output); ++i) {
SDL_DestroyTexture(output[i]);
}
return result;
}
static bool run_all_format_test(SDL_Window *window, const char *requested_renderer, SDL_Surface *original)
{
const SDL_PixelFormat yuv_formats[] = {
SDL_PIXELFORMAT_YV12,
SDL_PIXELFORMAT_IYUV,
SDL_PIXELFORMAT_I444,
SDL_PIXELFORMAT_YUY2,
SDL_PIXELFORMAT_UYVY,
SDL_PIXELFORMAT_YVYU,
SDL_PIXELFORMAT_NV12,
SDL_PIXELFORMAT_NV21
};
const SDL_PixelFormat rgb_formats[] = {
SDL_PIXELFORMAT_XRGB1555,
SDL_PIXELFORMAT_RGB565,
SDL_PIXELFORMAT_RGB24,
SDL_PIXELFORMAT_ABGR8888,
SDL_PIXELFORMAT_RGBA8888,
SDL_PIXELFORMAT_BGRA8888
};
const struct
{
YUV_CONVERSION_MODE mode;
const char *name;
} colorspaces[] = {
{ YUV_CONVERSION_JPEG, "JPEG" },
{ YUV_CONVERSION_BT601, "BT601" },
{ YUV_CONVERSION_BT709, "BT709" },
//{ YUV_CONVERSION_BT2020, "BT2020" }
};
bool quit = false;
bool result = true;
for (int i = 0; i < SDL_GetNumRenderDrivers() && !quit; ++i) {
const char *renderer_name = SDL_GetRenderDriver(i);
if (requested_renderer && SDL_strcmp(renderer_name, requested_renderer) != 0) {
continue;
}
SDL_Renderer *renderer = SDL_CreateRenderer(window, renderer_name);
if (!renderer) {
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Couldn't create %s renderer: %s", renderer_name, SDL_GetError());
result = false;
continue;
}
for (int j = 0; j < SDL_arraysize(colorspaces) && !quit; ++j) {
if (colorspaces[j].mode == YUV_CONVERSION_BT2020 &&
!has_10bit_texture_format(renderer)) {
SDL_Log("Skipping %s %s, unsupported", renderer_name, colorspaces[j].name);
continue;
}
SetYUVConversionMode(colorspaces[j].mode);
for (int m = 0; m < SDL_arraysize(yuv_formats) && !quit; ++m) {
SDL_PixelFormat yuv_format = yuv_formats[m];
for (int n = 0; n < SDL_arraysize(rgb_formats) && !quit; ++n) {
SDL_PixelFormat rgb_format = rgb_formats[n];
SDL_Log("Testing: %s %s %s %s (planar)", renderer_name, colorspaces[j].name, SDL_GetPixelFormatName(yuv_format), SDL_GetPixelFormatName(rgb_format));
result &= run_single_format_test(renderer, original, yuv_format, rgb_format, true, false);
SDL_Log("Testing: %s %s %s %s (packed)", renderer_name, colorspaces[j].name, SDL_GetPixelFormatName(yuv_format), SDL_GetPixelFormatName(rgb_format));
result &= run_single_format_test(renderer, original, yuv_format, rgb_format, false, false);
SDL_Log("Testing: %s %s %s %s (locked)", renderer_name, colorspaces[j].name, SDL_GetPixelFormatName(yuv_format), SDL_GetPixelFormatName(rgb_format));
result &= run_single_format_test(renderer, original, yuv_format, rgb_format, false, true);
SDL_Event event;
while (SDL_PollEvent(&event)) {
if (event.type == SDL_EVENT_QUIT ||
(event.type == SDL_EVENT_KEY_DOWN && event.key.key == SDLK_ESCAPE)) {
quit = true;
}
}
}
}
}
SDL_DestroyRenderer(renderer);
}
return result;
}
static bool run_interactive(SDL_Window *window, const char *renderer_name, SDL_Surface *original, SDL_PixelFormat yuv_format, SDL_PixelFormat rgb_format, bool planar, bool lock, bool monochrome, int luminance)
{
const char *titles[3] = { "ORIGINAL", "SOFTWARE", "HARDWARE" };
char title[128];
const char *yuv_mode_name;
YUV_CONVERSION_MODE yuv_mode;
const char *yuv_format_name;
int current = 0;
bool quit = false;
bool result = false;
SDL_Renderer *renderer = SDL_CreateRenderer(window, renderer_name);
if (!renderer) {
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Couldn't create renderer: %s", SDL_GetError());
return false;
}
renderer_name = SDL_GetRendererName(renderer);
SDL_Texture *output[3];
if (!create_textures(renderer, original, yuv_format, rgb_format, planar, lock, monochrome, luminance, output)) {
goto done;
}
yuv_mode = GetYUVConversionModeForResolution(original->w, original->h);
switch (yuv_mode) {
case YUV_CONVERSION_JPEG:
yuv_mode_name = "JPEG";
break;
case YUV_CONVERSION_BT601:
yuv_mode_name = "BT.601";
break;
case YUV_CONVERSION_BT709:
yuv_mode_name = "BT.709";
break;
case YUV_CONVERSION_BT2020:
yuv_mode_name = "BT.2020";
break;
default:
yuv_mode_name = "UNKNOWN";
break;
}
yuv_format_name = SDL_GetPixelFormatName(yuv_format);
if (SDL_strncmp(yuv_format_name, "SDL_PIXELFORMAT_", 16) == 0) {
yuv_format_name += 16;
}
while (!quit) {
SDL_Event event;
while (SDL_PollEvent(&event) > 0) {
if (event.type == SDL_EVENT_QUIT) {
quit = true;
}
if (event.type == SDL_EVENT_KEY_DOWN) {
if (event.key.key == SDLK_ESCAPE) {
quit = true;
} else if (event.key.key == SDLK_LEFT) {
--current;
} else if (event.key.key == SDLK_RIGHT) {
++current;
}
}
if (event.type == SDL_EVENT_MOUSE_BUTTON_DOWN) {
if (event.button.x < (original->w / 2)) {
--current;
} else {
++current;
}
}
}
/* Handle wrapping */
if (current < 0) {
current += SDL_arraysize(output);
}
if (current >= SDL_arraysize(output)) {
current -= SDL_arraysize(output);
}
SDL_RenderClear(renderer);
SDL_RenderTexture(renderer, output[current], NULL, NULL);
SDL_SetRenderDrawColor(renderer, 0xFF, 0xFF, 0xFF, 0xFF);
if (current == 0) {
SDLTest_DrawString(renderer, 4, 4, titles[current]);
} else {
if (SDL_snprintf(title, sizeof(title), "%s %s %s %s", titles[current], yuv_format_name, yuv_mode_name, renderer_name) > 0) {
SDLTest_DrawString(renderer, 4, 4, title);
}
}
SDL_RenderPresent(renderer);
SDL_Delay(10);
}
result = true;
done:
for (int i = 0; i < SDL_arraysize(output); ++i) {
SDL_DestroyTexture(output[i]);
}
SDLTest_CleanupTextDrawing();
SDL_DestroyRenderer(renderer);
return result;
}
int main(int argc, char **argv)
{
static const struct
{
bool enable_intrinsics;
int pattern_size;
int extra_pitch;
} automated_test_params[] = {
/* Test: single pixel */
{ false, 1, 0 },
/* Test: even width and height */
{ false, 2, 0 },
{ false, 4, 0 },
/* Test: odd width and height */
{ false, 1, 0 },
{ false, 3, 0 },
/* Test: even width and height, extra pitch */
{ false, 2, 3 },
{ false, 4, 3 },
/* Test: odd width and height, extra pitch */
{ false, 1, 3 },
{ false, 3, 3 },
/* Test: even width and height with intrinsics */
{ true, 32, 0 },
/* Test: odd width and height with intrinsics */
{ true, 33, 0 },
{ true, 37, 0 },
/* Test: even width and height with intrinsics, extra pitch */
{ true, 32, 3 },
/* Test: odd width and height with intrinsics, extra pitch */
{ true, 33, 3 },
{ true, 37, 3 },
};
char *filename = NULL;
SDL_Surface *original = NULL;
SDL_Surface *png = NULL;
SDL_Window *window = NULL;
const char *renderer_name = NULL;
Uint32 yuv_format = SDL_PIXELFORMAT_YV12;
Uint32 rgb_format = SDL_PIXELFORMAT_RGBX8888;
bool planar = false;
bool lock = false;
bool monochrome = false;
int luminance = 100;
int i;
bool should_run_automated_tests = false;
bool should_run_colorspace_test = false;
bool should_test_all_formats = false;
SDLTest_CommonState *state;
int result = 0;
/* Initialize test framework */
state = SDLTest_CommonCreateState(argv, 0);
if (!state) {
return 1;
}
/* Parse command line */
for (i = 1; i < argc;) {
int consumed;
consumed = SDLTest_CommonArg(state, i);
if (!consumed) {
if (SDL_strcmp(argv[i], "--all") == 0) {
should_test_all_formats = true;
consumed = 1;
} else if (SDL_strcmp(argv[i], "--jpeg") == 0) {
SetYUVConversionMode(YUV_CONVERSION_JPEG);
consumed = 1;
} else if (SDL_strcmp(argv[i], "--bt601") == 0) {
SetYUVConversionMode(YUV_CONVERSION_BT601);
consumed = 1;
} else if (SDL_strcmp(argv[i], "--bt709") == 0) {
SetYUVConversionMode(YUV_CONVERSION_BT709);
consumed = 1;
} else if (SDL_strcmp(argv[i], "--auto") == 0) {
SetYUVConversionMode(YUV_CONVERSION_AUTOMATIC);
consumed = 1;
} else if (SDL_strcmp(argv[i], "--yv12") == 0) {
yuv_format = SDL_PIXELFORMAT_YV12;
consumed = 1;
} else if (SDL_strcmp(argv[i], "--iyuv") == 0) {
yuv_format = SDL_PIXELFORMAT_IYUV;
consumed = 1;
} else if (SDL_strcmp(argv[i], "--i444") == 0) {
yuv_format = SDL_PIXELFORMAT_I444;
consumed = 1;
} else if (SDL_strcmp(argv[i], "--yuy2") == 0) {
yuv_format = SDL_PIXELFORMAT_YUY2;
consumed = 1;
} else if (SDL_strcmp(argv[i], "--uyvy") == 0) {
yuv_format = SDL_PIXELFORMAT_UYVY;
consumed = 1;
} else if (SDL_strcmp(argv[i], "--yvyu") == 0) {
yuv_format = SDL_PIXELFORMAT_YVYU;
consumed = 1;
} else if (SDL_strcmp(argv[i], "--nv12") == 0) {
yuv_format = SDL_PIXELFORMAT_NV12;
consumed = 1;
} else if (SDL_strcmp(argv[i], "--nv21") == 0) {
yuv_format = SDL_PIXELFORMAT_NV21;
consumed = 1;
} else if (SDL_strcmp(argv[i], "--p010") == 0) {
yuv_format = SDL_PIXELFORMAT_P010;
rgb_format = SDL_PIXELFORMAT_XBGR2101010;
SetYUVConversionMode(YUV_CONVERSION_BT2020);
consumed = 1;
} else if (SDL_strcmp(argv[i], "--i0fl") == 0) {
yuv_format = SDL_PIXELFORMAT_I0FL;
rgb_format = SDL_PIXELFORMAT_XBGR2101010;
SetYUVConversionMode(YUV_CONVERSION_BT2020);
consumed = 1;
} else if (SDL_strcmp(argv[i], "--i4fl") == 0) {
yuv_format = SDL_PIXELFORMAT_I4FL;
rgb_format = SDL_PIXELFORMAT_XBGR2101010;
SetYUVConversionMode(YUV_CONVERSION_BT2020);
consumed = 1;
} else if (SDL_strcmp(argv[i], "--rgb555") == 0) {
rgb_format = SDL_PIXELFORMAT_XRGB1555;
consumed = 1;
} else if (SDL_strcmp(argv[i], "--rgb565") == 0) {
rgb_format = SDL_PIXELFORMAT_RGB565;
consumed = 1;
} else if (SDL_strcmp(argv[i], "--rgb24") == 0) {
rgb_format = SDL_PIXELFORMAT_RGB24;
consumed = 1;
} else if (SDL_strcmp(argv[i], "--argb") == 0) {
rgb_format = SDL_PIXELFORMAT_ARGB8888;
consumed = 1;
} else if (SDL_strcmp(argv[i], "--abgr") == 0) {
rgb_format = SDL_PIXELFORMAT_ABGR8888;
consumed = 1;
} else if (SDL_strcmp(argv[i], "--rgba") == 0) {
rgb_format = SDL_PIXELFORMAT_RGBA8888;
consumed = 1;
} else if (SDL_strcmp(argv[i], "--bgra") == 0) {
rgb_format = SDL_PIXELFORMAT_BGRA8888;
consumed = 1;
} else if (SDL_strcmp(argv[i], "--planar") == 0) {
planar = true;
consumed = 1;
} else if (SDL_strcmp(argv[i], "--lock") == 0) {
lock = true;
consumed = 1;
} else if (SDL_strcmp(argv[i], "--monochrome") == 0) {
monochrome = true;
consumed = 1;
} else if (SDL_strcmp(argv[i], "--luminance") == 0 && argv[i+1]) {
luminance = SDL_atoi(argv[i+1]);
consumed = 2;
} else if (SDL_strcmp(argv[i], "--automated") == 0) {
should_run_automated_tests = true;
consumed = 1;
} else if (SDL_strcmp(argv[i], "--colorspace-test") == 0) {
should_run_colorspace_test = true;
consumed = 1;
} else if (SDL_strcmp(argv[i], "--renderer") == 0 && argv[i + 1]) {
renderer_name = argv[i + 1];
consumed = 2;
} else if (!filename) {
filename = argv[i];
consumed = 1;
}
}
if (consumed <= 0) {
static const char *options[] = {
"[--jpeg|--bt601|--bt709|--auto]",
"[--yv12|--iyuv|--i444|--yuy2|--uyvy|--yvyu|--nv12|--nv21|--p010|--i4fl]",
"[--rgb555|--rgb565|--rgb24|--argb|--abgr|--rgba|--bgra]",
"[--monochrome] [--luminance N%] [--planar] [--lock]",
"[--automated] [--colorspace-test] [--renderer NAME]",
"[sample.png]",
NULL,
};
SDLTest_CommonLogUsage(state, argv[0], options);
SDL_Quit();
SDLTest_CommonDestroyState(state);
return 1;
}
i += consumed;
}
/* Run automated tests */
if (should_run_automated_tests) {
for (i = 0; i < (int)SDL_arraysize(automated_test_params); ++i) {
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION, "Running automated test, pattern size %d, extra pitch %d, intrinsics %s",
automated_test_params[i].pattern_size,
automated_test_params[i].extra_pitch,
automated_test_params[i].enable_intrinsics ? "enabled" : "disabled");
if (!run_automated_tests(automated_test_params[i].pattern_size, automated_test_params[i].extra_pitch)) {
result = 2;
}
}
goto done;
}
if (should_run_colorspace_test) {
if (!run_colorspace_test()) {
result = 2;
}
goto done;
}
filename = GetResourceFilename(filename, "testyuv.png");
png = SDL_LoadSurface(filename);
if (png) {
original = SDL_ConvertSurface(png, SDL_PIXELFORMAT_RGB24);
SDL_DestroySurface(png);
}
if (!original) {
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Couldn't load %s: %s", filename, SDL_GetError());
result = 3;
goto done;
}
window = SDL_CreateWindow("YUV test", original->w, original->h, 0);
if (!window) {
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Couldn't create window: %s", SDL_GetError());
result = 4;
goto done;
}
if (should_test_all_formats) {
if (!run_all_format_test(window, renderer_name, original)) {
result = 5;
}
} else {
if (!run_interactive(window, renderer_name, original, yuv_format, rgb_format, planar, lock, monochrome, luminance)) {
result = 5;
}
}
done:
SDL_free(filename);
SDL_DestroySurface(original);
if (window) {
SDL_DestroyWindow(window);
}
SDL_Quit();
SDLTest_CommonDestroyState(state);
return result;
}