13
#include <SDL3/SDL_main.h>
14
#include <SDL3/SDL_test.h>
15
#include "testyuv_cvt.h"
19
#define MAX_YUV_SURFACE_SIZE(W, H, P) ((H + 1) * ((W + 1) + P) * 4)
22
static SDL_bool is_packed_yuv_format(Uint32 format)
24
return format == SDL_PIXELFORMAT_YUY2 || format == SDL_PIXELFORMAT_UYVY || format == SDL_PIXELFORMAT_YVYU;
28
static SDL_Surface *generate_test_pattern(int pattern_size)
30
SDL_Surface *pattern = SDL_CreateSurface(pattern_size, pattern_size, SDL_PIXELFORMAT_RGB24);
35
const int thickness = 2;
38
for (y = 0; y < pattern->h - (thickness - 1); y += thickness) {
39
for (i = 0; i < thickness; ++i) {
40
p = (Uint8 *)pattern->pixels + (y + i) * pattern->pitch + ((y / thickness) % 3);
41
for (x = 0; x < pattern->w; ++x) {
50
for (x = 1 * thickness; x < pattern->w; x += 2 * thickness) {
51
for (i = 0; i < thickness; ++i) {
52
p = (Uint8 *)pattern->pixels + (x + i) * 3;
53
for (y = 0; y < pattern->h; ++y) {
68
static SDL_bool verify_yuv_data(Uint32 format, SDL_Colorspace colorspace, const Uint8 *yuv, int yuv_pitch, SDL_Surface *surface, int tolerance)
70
const int size = (surface->h * surface->pitch);
72
SDL_bool result = SDL_FALSE;
74
rgb = (Uint8 *)SDL_malloc(size);
76
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Out of memory");
80
if (SDL_ConvertPixelsAndColorspace(surface->w, surface->h, format, colorspace, 0, yuv, yuv_pitch, surface->format, SDL_COLORSPACE_SRGB, 0, rgb, surface->pitch) == 0) {
83
for (y = 0; y < surface->h; ++y) {
84
const Uint8 *actual = rgb + y * surface->pitch;
85
const Uint8 *expected = (const Uint8 *)surface->pixels + y * surface->pitch;
86
for (x = 0; x < surface->w; ++x) {
87
int deltaR = (int)actual[0] - expected[0];
88
int deltaG = (int)actual[1] - expected[1];
89
int deltaB = (int)actual[2] - expected[2];
90
int distance = (deltaR * deltaR + deltaG * deltaG + deltaB * deltaB);
91
if (distance > tolerance) {
92
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\n", x, y, actual[0], actual[1], actual[2], expected[0], expected[1], expected[2], distance);
100
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Couldn't convert %s to %s: %s\n", SDL_GetPixelFormatName(format), SDL_GetPixelFormatName(surface->format), SDL_GetError());
107
static int run_automated_tests(int pattern_size, int extra_pitch)
109
const Uint32 formats[] = {
110
SDL_PIXELFORMAT_YV12,
111
SDL_PIXELFORMAT_IYUV,
112
SDL_PIXELFORMAT_NV12,
113
SDL_PIXELFORMAT_NV21,
114
SDL_PIXELFORMAT_YUY2,
115
SDL_PIXELFORMAT_UYVY,
119
SDL_Surface *pattern = generate_test_pattern(pattern_size);
120
const int yuv_len = MAX_YUV_SURFACE_SIZE(pattern->w, pattern->h, extra_pitch);
121
Uint8 *yuv1 = (Uint8 *)SDL_malloc(yuv_len);
122
Uint8 *yuv2 = (Uint8 *)SDL_malloc(yuv_len);
123
int yuv1_pitch, yuv2_pitch;
124
YUV_CONVERSION_MODE mode;
125
SDL_Colorspace colorspace;
126
const int tight_tolerance = 20;
127
const int loose_tolerance = 333;
130
if (!pattern || !yuv1 || !yuv2) {
131
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Couldn't allocate test surfaces");
135
mode = GetYUVConversionModeForResolution(pattern->w, pattern->h);
136
colorspace = GetColorspaceForYUVConversionMode(mode);
139
for (i = 0; i < SDL_arraysize(formats); ++i) {
140
if (!ConvertRGBtoYUV(formats[i], pattern->pixels, pattern->pitch, yuv1, pattern->w, pattern->h, mode, 0, 100)) {
141
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "ConvertRGBtoYUV() doesn't support converting to %s\n", SDL_GetPixelFormatName(formats[i]));
144
yuv1_pitch = CalculateYUVPitch(formats[i], pattern->w);
145
if (!verify_yuv_data(formats[i], colorspace, yuv1, yuv1_pitch, pattern, tight_tolerance)) {
146
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Failed conversion from %s to RGB\n", SDL_GetPixelFormatName(formats[i]));
152
for (i = 0; i < SDL_arraysize(formats); ++i) {
153
yuv1_pitch = CalculateYUVPitch(formats[i], pattern->w) + extra_pitch;
154
if (SDL_ConvertPixelsAndColorspace(pattern->w, pattern->h, pattern->format, SDL_COLORSPACE_SRGB, 0, pattern->pixels, pattern->pitch, formats[i], colorspace, 0, yuv1, yuv1_pitch) < 0) {
155
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Couldn't convert %s to %s: %s\n", SDL_GetPixelFormatName(pattern->format), SDL_GetPixelFormatName(formats[i]), SDL_GetError());
158
if (!verify_yuv_data(formats[i], colorspace, yuv1, yuv1_pitch, pattern, tight_tolerance)) {
159
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Failed conversion from RGB to %s\n", SDL_GetPixelFormatName(formats[i]));
165
for (i = 0; i < SDL_arraysize(formats); ++i) {
166
for (j = 0; j < SDL_arraysize(formats); ++j) {
167
yuv1_pitch = CalculateYUVPitch(formats[i], pattern->w) + extra_pitch;
168
yuv2_pitch = CalculateYUVPitch(formats[j], pattern->w) + extra_pitch;
169
if (SDL_ConvertPixelsAndColorspace(pattern->w, pattern->h, pattern->format, SDL_COLORSPACE_SRGB, 0, pattern->pixels, pattern->pitch, formats[i], colorspace, 0, yuv1, yuv1_pitch) < 0) {
170
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Couldn't convert %s to %s: %s\n", SDL_GetPixelFormatName(pattern->format), SDL_GetPixelFormatName(formats[i]), SDL_GetError());
173
if (SDL_ConvertPixelsAndColorspace(pattern->w, pattern->h, formats[i], colorspace, 0, yuv1, yuv1_pitch, formats[j], colorspace, 0, yuv2, yuv2_pitch) < 0) {
174
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Couldn't convert %s to %s: %s\n", SDL_GetPixelFormatName(formats[i]), SDL_GetPixelFormatName(formats[j]), SDL_GetError());
177
if (!verify_yuv_data(formats[j], colorspace, yuv2, yuv2_pitch, pattern, tight_tolerance)) {
178
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Failed conversion from %s to %s\n", SDL_GetPixelFormatName(formats[i]), SDL_GetPixelFormatName(formats[j]));
185
for (i = 0; i < SDL_arraysize(formats); ++i) {
186
for (j = 0; j < SDL_arraysize(formats); ++j) {
187
if (is_packed_yuv_format(formats[i]) != is_packed_yuv_format(formats[j])) {
192
yuv1_pitch = CalculateYUVPitch(formats[i], pattern->w) + extra_pitch;
193
yuv2_pitch = CalculateYUVPitch(formats[j], pattern->w) + extra_pitch;
194
if (SDL_ConvertPixelsAndColorspace(pattern->w, pattern->h, pattern->format, SDL_COLORSPACE_SRGB, 0, pattern->pixels, pattern->pitch, formats[i], colorspace, 0, yuv1, yuv1_pitch) < 0) {
195
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Couldn't convert %s to %s: %s\n", SDL_GetPixelFormatName(pattern->format), SDL_GetPixelFormatName(formats[i]), SDL_GetError());
198
if (SDL_ConvertPixelsAndColorspace(pattern->w, pattern->h, formats[i], colorspace, 0, yuv1, yuv1_pitch, formats[j], colorspace, 0, yuv1, yuv2_pitch) < 0) {
199
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Couldn't convert %s to %s: %s\n", SDL_GetPixelFormatName(formats[i]), SDL_GetPixelFormatName(formats[j]), SDL_GetError());
202
if (!verify_yuv_data(formats[j], colorspace, yuv1, yuv2_pitch, pattern, tight_tolerance)) {
203
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Failed conversion from %s to %s\n", SDL_GetPixelFormatName(formats[i]), SDL_GetPixelFormatName(formats[j]));
210
colorspace = SDL_COLORSPACE_BT2020_FULL;
211
if (!ConvertRGBtoYUV(SDL_PIXELFORMAT_P010, pattern->pixels, pattern->pitch, yuv1, pattern->w, pattern->h, YUV_CONVERSION_BT2020, 0, 100)) {
212
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "ConvertRGBtoYUV() doesn't support converting to %s\n", SDL_GetPixelFormatName(SDL_PIXELFORMAT_P010));
215
yuv1_pitch = CalculateYUVPitch(SDL_PIXELFORMAT_P010, pattern->w);
216
if (!verify_yuv_data(SDL_PIXELFORMAT_P010, colorspace, yuv1, yuv1_pitch, pattern, tight_tolerance)) {
217
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Failed conversion from %s to RGB\n", SDL_GetPixelFormatName(SDL_PIXELFORMAT_P010));
222
yuv1_pitch = CalculateYUVPitch(SDL_PIXELFORMAT_P010, pattern->w) + ((extra_pitch + 1) & ~1);
223
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) < 0) {
224
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Couldn't convert %s to %s: %s\n", SDL_GetPixelFormatName(pattern->format), SDL_GetPixelFormatName(SDL_PIXELFORMAT_P010), SDL_GetError());
228
if (!verify_yuv_data(SDL_PIXELFORMAT_P010, colorspace, yuv1, yuv1_pitch, pattern, loose_tolerance)) {
229
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Failed conversion from RGB to %s\n", SDL_GetPixelFormatName(SDL_PIXELFORMAT_P010));
238
SDL_DestroySurface(pattern);
242
int main(int argc, char **argv)
246
SDL_bool enable_intrinsics;
249
} automated_test_params[] = {
275
char *filename = NULL;
276
SDL_Surface *original;
277
SDL_Surface *converted;
280
SDL_Renderer *renderer;
281
SDL_Texture *output[3];
282
const char *titles[3] = { "ORIGINAL", "SOFTWARE", "HARDWARE" };
284
YUV_CONVERSION_MODE yuv_mode;
285
const char *yuv_mode_name;
286
Uint32 yuv_format = SDL_PIXELFORMAT_YV12;
287
const char *yuv_format_name;
288
SDL_Colorspace yuv_colorspace;
289
Uint32 rgb_format = SDL_PIXELFORMAT_RGBX8888;
290
SDL_Colorspace rgb_colorspace = SDL_COLORSPACE_SRGB;
291
SDL_PropertiesID props;
292
SDL_bool monochrome = SDL_FALSE;
298
int i, iterations = 100;
299
SDL_bool should_run_automated_tests = SDL_FALSE;
300
SDLTest_CommonState *state;
303
state = SDLTest_CommonCreateState(argv, 0);
309
SDL_SetLogPriority(SDL_LOG_CATEGORY_APPLICATION, SDL_LOG_PRIORITY_INFO);
312
for (i = 1; i < argc;) {
315
consumed = SDLTest_CommonArg(state, i);
317
if (SDL_strcmp(argv[i], "--jpeg") == 0) {
318
SetYUVConversionMode(YUV_CONVERSION_JPEG);
320
} else if (SDL_strcmp(argv[i], "--bt601") == 0) {
321
SetYUVConversionMode(YUV_CONVERSION_BT601);
323
} else if (SDL_strcmp(argv[i], "--bt709") == 0) {
324
SetYUVConversionMode(YUV_CONVERSION_BT709);
326
} else if (SDL_strcmp(argv[i], "--bt2020") == 0) {
327
SetYUVConversionMode(YUV_CONVERSION_BT2020);
329
} else if (SDL_strcmp(argv[i], "--auto") == 0) {
330
SetYUVConversionMode(YUV_CONVERSION_AUTOMATIC);
332
} else if (SDL_strcmp(argv[i], "--yv12") == 0) {
333
yuv_format = SDL_PIXELFORMAT_YV12;
335
} else if (SDL_strcmp(argv[i], "--iyuv") == 0) {
336
yuv_format = SDL_PIXELFORMAT_IYUV;
338
} else if (SDL_strcmp(argv[i], "--yuy2") == 0) {
339
yuv_format = SDL_PIXELFORMAT_YUY2;
341
} else if (SDL_strcmp(argv[i], "--uyvy") == 0) {
342
yuv_format = SDL_PIXELFORMAT_UYVY;
344
} else if (SDL_strcmp(argv[i], "--yvyu") == 0) {
345
yuv_format = SDL_PIXELFORMAT_YVYU;
347
} else if (SDL_strcmp(argv[i], "--nv12") == 0) {
348
yuv_format = SDL_PIXELFORMAT_NV12;
350
} else if (SDL_strcmp(argv[i], "--nv21") == 0) {
351
yuv_format = SDL_PIXELFORMAT_NV21;
353
} else if (SDL_strcmp(argv[i], "--rgb555") == 0) {
354
rgb_format = SDL_PIXELFORMAT_XRGB1555;
356
} else if (SDL_strcmp(argv[i], "--rgb565") == 0) {
357
rgb_format = SDL_PIXELFORMAT_RGB565;
359
} else if (SDL_strcmp(argv[i], "--rgb24") == 0) {
360
rgb_format = SDL_PIXELFORMAT_RGB24;
362
} else if (SDL_strcmp(argv[i], "--argb") == 0) {
363
rgb_format = SDL_PIXELFORMAT_ARGB8888;
365
} else if (SDL_strcmp(argv[i], "--abgr") == 0) {
366
rgb_format = SDL_PIXELFORMAT_ABGR8888;
368
} else if (SDL_strcmp(argv[i], "--rgba") == 0) {
369
rgb_format = SDL_PIXELFORMAT_RGBA8888;
371
} else if (SDL_strcmp(argv[i], "--bgra") == 0) {
372
rgb_format = SDL_PIXELFORMAT_BGRA8888;
374
} else if (SDL_strcmp(argv[i], "--monochrome") == 0) {
375
monochrome = SDL_TRUE;
377
} else if (SDL_strcmp(argv[i], "--luminance") == 0 && argv[i+1]) {
378
luminance = SDL_atoi(argv[i+1]);
380
} else if (SDL_strcmp(argv[i], "--automated") == 0) {
381
should_run_automated_tests = SDL_TRUE;
383
} else if (!filename) {
389
static const char *options[] = {
390
"[--jpeg|--bt601|--bt709|--bt2020|--auto]",
391
"[--yv12|--iyuv|--yuy2|--uyvy|--yvyu|--nv12|--nv21]",
392
"[--rgb555|--rgb565|--rgb24|--argb|--abgr|--rgba|--bgra]",
393
"[--monochrome] [--luminance N%]",
398
SDLTest_CommonLogUsage(state, argv[0], options);
400
SDLTest_CommonDestroyState(state);
407
if (should_run_automated_tests) {
408
for (i = 0; i < (int)SDL_arraysize(automated_test_params); ++i) {
409
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION, "Running automated test, pattern size %d, extra pitch %d, intrinsics %s\n",
410
automated_test_params[i].pattern_size,
411
automated_test_params[i].extra_pitch,
412
automated_test_params[i].enable_intrinsics ? "enabled" : "disabled");
413
if (run_automated_tests(automated_test_params[i].pattern_size, automated_test_params[i].extra_pitch) < 0) {
420
filename = GetResourceFilename(filename, "testyuv.bmp");
421
bmp = SDL_LoadBMP(filename);
422
original = SDL_ConvertSurface(bmp, SDL_PIXELFORMAT_RGB24);
423
SDL_DestroySurface(bmp);
425
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Couldn't load %s: %s\n", filename, SDL_GetError());
429
yuv_mode = GetYUVConversionModeForResolution(original->w, original->h);
431
case YUV_CONVERSION_JPEG:
432
yuv_mode_name = "JPEG";
434
case YUV_CONVERSION_BT601:
435
yuv_mode_name = "BT.601";
437
case YUV_CONVERSION_BT709:
438
yuv_mode_name = "BT.709";
440
case YUV_CONVERSION_BT2020:
441
yuv_mode_name = "BT.2020";
442
yuv_format = SDL_PIXELFORMAT_P010;
443
rgb_format = SDL_PIXELFORMAT_XBGR2101010;
444
rgb_colorspace = SDL_COLORSPACE_HDR10;
447
yuv_mode_name = "UNKNOWN";
450
yuv_colorspace = GetColorspaceForYUVConversionMode(yuv_mode);
452
raw_yuv = SDL_calloc(1, MAX_YUV_SURFACE_SIZE(original->w, original->h, 0));
453
ConvertRGBtoYUV(yuv_format, original->pixels, original->pitch, raw_yuv, original->w, original->h, yuv_mode, monochrome, luminance);
454
pitch = CalculateYUVPitch(yuv_format, original->w);
456
converted = SDL_CreateSurface(original->w, original->h, rgb_format);
458
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Couldn't create converted surface: %s\n", SDL_GetError());
462
then = SDL_GetTicks();
463
for (i = 0; i < iterations; ++i) {
464
SDL_ConvertPixelsAndColorspace(original->w, original->h, yuv_format, yuv_colorspace, 0, raw_yuv, pitch, rgb_format, rgb_colorspace, 0, converted->pixels, converted->pitch);
466
now = SDL_GetTicks();
467
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION, "%d iterations in %" SDL_PRIu64 " ms, %.2fms each\n", iterations, (now - then), (float)(now - then) / iterations);
469
window = SDL_CreateWindow("YUV test", original->w, original->h, 0);
471
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Couldn't create window: %s\n", SDL_GetError());
475
renderer = SDL_CreateRenderer(window, NULL);
477
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Couldn't create renderer: %s\n", SDL_GetError());
481
output[0] = SDL_CreateTextureFromSurface(renderer, original);
482
output[1] = SDL_CreateTextureFromSurface(renderer, converted);
483
props = SDL_CreateProperties();
484
SDL_SetNumberProperty(props, SDL_PROP_TEXTURE_CREATE_COLORSPACE_NUMBER, yuv_colorspace);
485
SDL_SetNumberProperty(props, SDL_PROP_TEXTURE_CREATE_FORMAT_NUMBER, yuv_format);
486
SDL_SetNumberProperty(props, SDL_PROP_TEXTURE_CREATE_ACCESS_NUMBER, SDL_TEXTUREACCESS_STREAMING);
487
SDL_SetNumberProperty(props, SDL_PROP_TEXTURE_CREATE_WIDTH_NUMBER, original->w);
488
SDL_SetNumberProperty(props, SDL_PROP_TEXTURE_CREATE_HEIGHT_NUMBER, original->h);
489
output[2] = SDL_CreateTextureWithProperties(renderer, props);
490
SDL_DestroyProperties(props);
491
if (!output[0] || !output[1] || !output[2]) {
492
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Couldn't set create texture: %s\n", SDL_GetError());
495
SDL_UpdateTexture(output[2], NULL, raw_yuv, pitch);
497
yuv_format_name = SDL_GetPixelFormatName(yuv_format);
498
if (SDL_strncmp(yuv_format_name, "SDL_PIXELFORMAT_", 16) == 0) {
499
yuv_format_name += 16;
506
while (SDL_PollEvent(&event) > 0) {
507
if (event.type == SDL_EVENT_QUIT) {
510
if (event.type == SDL_EVENT_KEY_DOWN) {
511
if (event.key.key == SDLK_ESCAPE) {
513
} else if (event.key.key == SDLK_LEFT) {
515
} else if (event.key.key == SDLK_RIGHT) {
519
if (event.type == SDL_EVENT_MOUSE_BUTTON_DOWN) {
520
if (event.button.x < (original->w / 2)) {
530
current += SDL_arraysize(output);
532
if (current >= SDL_arraysize(output)) {
533
current -= SDL_arraysize(output);
536
SDL_RenderClear(renderer);
537
SDL_RenderTexture(renderer, output[current], NULL, NULL);
538
SDL_SetRenderDrawColor(renderer, 0xFF, 0xFF, 0xFF, 0xFF);
540
SDLTest_DrawString(renderer, 4, 4, titles[current]);
542
(void)SDL_snprintf(title, sizeof(title), "%s %s %s", titles[current], yuv_format_name, yuv_mode_name);
543
SDLTest_DrawString(renderer, 4, 4, title);
545
SDL_RenderPresent(renderer);
551
SDL_DestroySurface(original);
552
SDL_DestroySurface(converted);
553
SDLTest_CleanupTextDrawing();
554
SDL_DestroyRenderer(renderer);
555
SDL_DestroyWindow(window);
557
SDLTest_CommonDestroyState(state);