Atlas - testyuv.c

Home / ext / SDL / test Lines: 1 | Size: 39065 bytes [Download] [Show on GitHub] [Search similar files] [Raw] [Raw (proxy)]
[FILE BEGIN]
1/* 2 Copyright (C) 1997-2025 Sam Lantinga <[email protected]> 3 4 This software is provided 'as-is', without any express or implied 5 warranty. In no event will the authors be held liable for any damages 6 arising from the use of this software. 7 8 Permission is granted to anyone to use this software for any purpose, 9 including commercial applications, and to alter it and redistribute it 10 freely. 11*/ 12#include <SDL3/SDL.h> 13#include <SDL3/SDL_main.h> 14#include <SDL3/SDL_test.h> 15#include "testyuv_cvt.h" 16#include "testutils.h" 17 18/* 422 (YUY2, etc) and P010 formats are the largest */ 19#define MAX_YUV_SURFACE_SIZE(W, H, P) ((H + 1) * ((W + 1) + P) * 4) 20 21/* Return true if the YUV format is packed pixels */ 22static bool is_packed_yuv_format(Uint32 format) 23{ 24 return format == SDL_PIXELFORMAT_YUY2 || format == SDL_PIXELFORMAT_UYVY || format == SDL_PIXELFORMAT_YVYU; 25} 26 27/* Create a surface with a good pattern for verifying YUV conversion */ 28static SDL_Surface *generate_test_pattern(int pattern_size) 29{ 30 SDL_Surface *pattern = SDL_CreateSurface(pattern_size, pattern_size, SDL_PIXELFORMAT_RGB24); 31 32 if (pattern) { 33 int i, x, y; 34 Uint8 *p, c; 35 const int thickness = 2; /* Important so 2x2 blocks of color are the same, to avoid Cr/Cb interpolation over pixels */ 36 37 /* R, G, B in alternating horizontal bands */ 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) { 42 *p = 0xFF; 43 p += 3; 44 } 45 } 46 } 47 48 /* Black and white in alternating vertical bands */ 49 c = 0xFF; 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) { 54 SDL_memset(p, c, 3); 55 p += pattern->pitch; 56 } 57 } 58 if (c) { 59 c = 0x00; 60 } else { 61 c = 0xFF; 62 } 63 } 64 } 65 return pattern; 66} 67 68static bool verify_yuv_data(Uint32 format, SDL_Colorspace colorspace, const Uint8 *yuv, int yuv_pitch, SDL_Surface *surface, int tolerance) 69{ 70 const int size = (surface->h * surface->pitch); 71 Uint8 *rgb; 72 bool result = false; 73 74 rgb = (Uint8 *)SDL_malloc(size); 75 if (!rgb) { 76 SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Out of memory"); 77 return false; 78 } 79 80 if (SDL_ConvertPixelsAndColorspace(surface->w, surface->h, format, colorspace, 0, yuv, yuv_pitch, surface->format, SDL_COLORSPACE_SRGB, 0, rgb, surface->pitch)) { 81 int x, y; 82 result = true; 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", x, y, actual[0], actual[1], actual[2], expected[0], expected[1], expected[2], distance); 93 result = false; 94 } 95 actual += 3; 96 expected += 3; 97 } 98 } 99 } else { 100 SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Couldn't convert %s to %s: %s", SDL_GetPixelFormatName(format), SDL_GetPixelFormatName(surface->format), SDL_GetError()); 101 } 102 SDL_free(rgb); 103 104 return result; 105} 106 107static bool run_automated_tests(int pattern_size, int extra_pitch) 108{ 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, 116 SDL_PIXELFORMAT_YVYU 117 }; 118 int i, j; 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; 128 bool result = false; 129 130 if (!pattern || !yuv1 || !yuv2) { 131 SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Couldn't allocate test surfaces"); 132 goto done; 133 } 134 135 mode = GetYUVConversionModeForResolution(pattern->w, pattern->h); 136 colorspace = GetColorspaceForYUVConversionMode(mode); 137 138 /* Verify conversion from YUV formats */ 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", SDL_GetPixelFormatName(formats[i])); 142 goto done; 143 } 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", SDL_GetPixelFormatName(formats[i])); 147 goto done; 148 } 149 } 150 151 /* Verify conversion to YUV formats */ 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)) { 155 SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Couldn't convert %s to %s: %s", SDL_GetPixelFormatName(pattern->format), SDL_GetPixelFormatName(formats[i]), SDL_GetError()); 156 goto done; 157 } 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", SDL_GetPixelFormatName(formats[i])); 160 goto done; 161 } 162 } 163 164 /* Verify conversion between YUV formats */ 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)) { 170 SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Couldn't convert %s to %s: %s", SDL_GetPixelFormatName(pattern->format), SDL_GetPixelFormatName(formats[i]), SDL_GetError()); 171 goto done; 172 } 173 if (!SDL_ConvertPixelsAndColorspace(pattern->w, pattern->h, formats[i], colorspace, 0, yuv1, yuv1_pitch, formats[j], colorspace, 0, yuv2, yuv2_pitch)) { 174 SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Couldn't convert %s to %s: %s", SDL_GetPixelFormatName(formats[i]), SDL_GetPixelFormatName(formats[j]), SDL_GetError()); 175 goto done; 176 } 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", SDL_GetPixelFormatName(formats[i]), SDL_GetPixelFormatName(formats[j])); 179 goto done; 180 } 181 } 182 } 183 184 /* Verify conversion between YUV formats in-place */ 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])) { 188 /* Can't change plane vs packed pixel layout in-place */ 189 continue; 190 } 191 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)) { 195 SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Couldn't convert %s to %s: %s", SDL_GetPixelFormatName(pattern->format), SDL_GetPixelFormatName(formats[i]), SDL_GetError()); 196 goto done; 197 } 198 if (!SDL_ConvertPixelsAndColorspace(pattern->w, pattern->h, formats[i], colorspace, 0, yuv1, yuv1_pitch, formats[j], colorspace, 0, yuv1, yuv2_pitch)) { 199 SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Couldn't convert %s to %s: %s", SDL_GetPixelFormatName(formats[i]), SDL_GetPixelFormatName(formats[j]), SDL_GetError()); 200 goto done; 201 } 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", SDL_GetPixelFormatName(formats[i]), SDL_GetPixelFormatName(formats[j])); 204 goto done; 205 } 206 } 207 } 208 209 /* Verify round trip through BT.2020 */ 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", SDL_GetPixelFormatName(SDL_PIXELFORMAT_P010)); 213 goto done; 214 } 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", SDL_GetPixelFormatName(SDL_PIXELFORMAT_P010)); 218 goto done; 219 } 220 221 /* The pitch needs to be Uint16 aligned for P010 pixels */ 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)) { 224 SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Couldn't convert %s to %s: %s", SDL_GetPixelFormatName(pattern->format), SDL_GetPixelFormatName(SDL_PIXELFORMAT_P010), SDL_GetError()); 225 goto done; 226 } 227 /* Going through XRGB2101010 format during P010 conversion is slightly lossy, so use looser tolerance here */ 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", SDL_GetPixelFormatName(SDL_PIXELFORMAT_P010)); 230 goto done; 231 } 232 233 result = true; 234 235done: 236 SDL_free(yuv1); 237 SDL_free(yuv2); 238 SDL_DestroySurface(pattern); 239 return result; 240} 241 242static bool run_colorspace_test(void) 243{ 244 bool result = false; 245 SDL_Window *window; 246 SDL_Renderer *renderer; 247 struct { 248 const char *name; 249 SDL_Colorspace colorspace; 250 } colorspaces[] = { 251#define COLORSPACE(X) { #X, X } 252 COLORSPACE(SDL_COLORSPACE_JPEG), 253#if 0 /* We don't support converting color primaries here */ 254 COLORSPACE(SDL_COLORSPACE_BT601_LIMITED), 255 COLORSPACE(SDL_COLORSPACE_BT601_FULL), 256#endif 257 COLORSPACE(SDL_COLORSPACE_BT709_LIMITED), 258 COLORSPACE(SDL_COLORSPACE_BT709_FULL) 259#undef COLORSPACE 260 }; 261 SDL_Surface *rgb = NULL; 262 SDL_Surface *yuv = NULL; 263 SDL_Texture *texture = NULL; 264 int allowed_error = 2; 265 int i; 266 267 if (!SDL_CreateWindowAndRenderer("testyuv", 320, 240, 0, &window, &renderer)) { 268 SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Couldn't create window and renderer: %s", SDL_GetError()); 269 goto done; 270 } 271 272 rgb = SDL_CreateSurface(32, 32, SDL_PIXELFORMAT_XRGB8888); 273 if (!rgb) { 274 SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Couldn't create RGB surface: %s", SDL_GetError()); 275 goto done; 276 } 277 SDL_FillSurfaceRect(rgb, NULL, SDL_MapSurfaceRGB(rgb, 255, 0, 0)); 278 279 for (i = 0; i < SDL_arraysize(colorspaces); ++i) { 280 bool next = false; 281 Uint8 r, g, b, a; 282 283 SDL_Log("Checking colorspace %s", colorspaces[i].name); 284 285 yuv = SDL_ConvertSurfaceAndColorspace(rgb, SDL_PIXELFORMAT_NV12, NULL, colorspaces[i].colorspace, 0); 286 if (!yuv) { 287 SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Couldn't create YUV surface: %s", SDL_GetError()); 288 goto done; 289 } 290 291 if (!SDL_ReadSurfacePixel(yuv, 0, 0, &r, &g, &b, &a)) { 292 SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Couldn't read YUV surface: %s", SDL_GetError()); 293 goto done; 294 } 295 296 if (SDL_abs((int)r - 255) > allowed_error || 297 SDL_abs((int)g - 0) > allowed_error || 298 SDL_abs((int)b - 0) > allowed_error) { 299 SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Failed color conversion, expected 255,0,0, got %d,%d,%d", r, g, b); 300 } 301 302 texture = SDL_CreateTextureFromSurface(renderer, yuv); 303 if (!texture) { 304 SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Couldn't create YUV texture: %s", SDL_GetError()); 305 goto done; 306 } 307 308 SDL_DestroySurface(yuv); 309 yuv = NULL; 310 311 SDL_RenderTexture(renderer, texture, NULL, NULL); 312 yuv = SDL_RenderReadPixels(renderer, NULL); 313 314 if (!SDL_ReadSurfacePixel(yuv, 0, 0, &r, &g, &b, &a)) { 315 SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Couldn't read YUV surface: %s", SDL_GetError()); 316 goto done; 317 } 318 319 if (SDL_abs((int)r - 255) > allowed_error || 320 SDL_abs((int)g - 0) > allowed_error || 321 SDL_abs((int)b - 0) > allowed_error) { 322 SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Failed renderer color conversion, expected 255,0,0, got %d,%d,%d", r, g, b); 323 } 324 325 while (!next) { 326 SDL_Event event; 327 while (SDL_PollEvent(&event)) { 328 switch (event.type) { 329 case SDL_EVENT_KEY_DOWN: 330 if (event.key.key == SDLK_ESCAPE) { 331 result = true; 332 goto done; 333 } 334 if (event.key.key == SDLK_SPACE) { 335 next = true; 336 } 337 break; 338 case SDL_EVENT_QUIT: 339 result = true; 340 goto done; 341 } 342 } 343 344 SDL_RenderTexture(renderer, texture, NULL, NULL); 345 SDL_SetRenderDrawColor(renderer, 255, 255, 255, 255); 346 SDL_RenderDebugText(renderer, 4, 4, colorspaces[i].name); 347 SDL_RenderPresent(renderer); 348 SDL_Delay(10); 349 } 350 351 SDL_DestroyTexture(texture); 352 texture = NULL; 353 } 354 355 result = true; 356 357done: 358 SDL_DestroySurface(rgb); 359 SDL_DestroySurface(yuv); 360 SDL_DestroyTexture(texture); 361 SDL_Quit(); 362 return result; 363} 364 365static bool create_textures(SDL_Renderer *renderer, SDL_Surface *original, SDL_PixelFormat yuv_format, SDL_PixelFormat rgb_format, bool planar, bool monochrome, int luminance, SDL_Texture *output[3]) 366{ 367 SDL_Colorspace rgb_colorspace = SDL_COLORSPACE_SRGB; 368 SDL_Colorspace yuv_colorspace; 369 Uint8 *raw_yuv = NULL; 370 int pitch; 371 SDL_Surface *converted = NULL; 372 bool result = false; 373 374 YUV_CONVERSION_MODE yuv_mode = GetYUVConversionModeForResolution(original->w, original->h); 375 if (yuv_mode == YUV_CONVERSION_BT2020) { 376 yuv_format = SDL_PIXELFORMAT_P010; 377 rgb_format = SDL_PIXELFORMAT_XBGR2101010; 378 rgb_colorspace = SDL_COLORSPACE_HDR10; 379 } 380 yuv_colorspace = GetColorspaceForYUVConversionMode(yuv_mode); 381 382 raw_yuv = SDL_calloc(1, MAX_YUV_SURFACE_SIZE(original->w, original->h, 0)); 383 ConvertRGBtoYUV(yuv_format, original->pixels, original->pitch, raw_yuv, original->w, original->h, yuv_mode, monochrome, luminance); 384 pitch = CalculateYUVPitch(yuv_format, original->w); 385 386 converted = SDL_CreateSurface(original->w, original->h, rgb_format); 387 if (!converted) { 388 SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Couldn't create converted surface: %s", SDL_GetError()); 389 goto done; 390 } 391 SDL_ConvertPixelsAndColorspace(original->w, original->h, yuv_format, yuv_colorspace, 0, raw_yuv, pitch, rgb_format, rgb_colorspace, 0, converted->pixels, converted->pitch); 392 393 output[0] = SDL_CreateTextureFromSurface(renderer, original); 394 output[1] = SDL_CreateTextureFromSurface(renderer, converted); 395 SDL_PropertiesID props = SDL_CreateProperties(); 396 SDL_SetNumberProperty(props, SDL_PROP_TEXTURE_CREATE_COLORSPACE_NUMBER, yuv_colorspace); 397 SDL_SetNumberProperty(props, SDL_PROP_TEXTURE_CREATE_FORMAT_NUMBER, yuv_format); 398 SDL_SetNumberProperty(props, SDL_PROP_TEXTURE_CREATE_ACCESS_NUMBER, SDL_TEXTUREACCESS_STREAMING); 399 SDL_SetNumberProperty(props, SDL_PROP_TEXTURE_CREATE_WIDTH_NUMBER, original->w); 400 SDL_SetNumberProperty(props, SDL_PROP_TEXTURE_CREATE_HEIGHT_NUMBER, original->h); 401 output[2] = SDL_CreateTextureWithProperties(renderer, props); 402 SDL_DestroyProperties(props); 403 if (!output[0] || !output[1] || !output[2]) { 404 SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Couldn't set create texture: %s", SDL_GetError()); 405 goto done; 406 } 407 if (planar && (yuv_format == SDL_PIXELFORMAT_YV12 || yuv_format == SDL_PIXELFORMAT_IYUV)) { 408 const int Yrows = original->h; 409 const int UVrows = ((original->h + 1) / 2); 410 const int src_Ypitch = pitch; 411 const int src_UVpitch = ((pitch + 1) / 2); 412 const Uint8 *src_plane0 = (const Uint8 *)raw_yuv; 413 const Uint8 *src_plane1 = src_plane0 + Yrows * src_Ypitch; 414 const Uint8 *src_plane2 = src_plane1 + UVrows * src_UVpitch; 415 const int Ypitch = pitch + 37; 416 const int UVpitch = ((Ypitch + 1) / 2); 417 Uint8 *plane0 = (Uint8 *)SDL_calloc(1, Yrows * Ypitch); 418 Uint8 *plane1 = (Uint8 *)SDL_calloc(1, UVrows * UVpitch); 419 Uint8 *plane2 = (Uint8 *)SDL_calloc(1, UVrows * UVpitch); 420 int row; 421 const Uint8 *src; 422 Uint8 *dst; 423 424 if (!plane0 || !plane1 || !plane0) { 425 SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Couldn't create YUV planes: %s", SDL_GetError()); 426 goto done; 427 } 428 429 src = src_plane0; 430 dst = plane0; 431 for (row = 0; row < Yrows; ++row) { 432 SDL_memcpy(dst, src, src_Ypitch); 433 src += src_Ypitch; 434 dst += Ypitch; 435 } 436 437 src = src_plane1; 438 dst = plane1; 439 for (row = 0; row < UVrows; ++row) { 440 SDL_memcpy(dst, src, src_UVpitch); 441 src += src_UVpitch; 442 dst += UVpitch; 443 } 444 445 src = src_plane2; 446 dst = plane2; 447 for (row = 0; row < UVrows; ++row) { 448 SDL_memcpy(dst, src, src_UVpitch); 449 src += src_UVpitch; 450 dst += UVpitch; 451 } 452 453 if (yuv_format == SDL_PIXELFORMAT_YV12) { 454 SDL_UpdateYUVTexture(output[2], NULL, plane0, Ypitch, plane2, UVpitch, plane1, UVpitch); 455 } else { 456 SDL_UpdateYUVTexture(output[2], NULL, plane0, Ypitch, plane1, UVpitch, plane2, UVpitch); 457 } 458 SDL_free(plane0); 459 SDL_free(plane1); 460 SDL_free(plane2); 461 } else if (planar && (yuv_format == SDL_PIXELFORMAT_NV12 || yuv_format == SDL_PIXELFORMAT_NV21 || yuv_format == SDL_PIXELFORMAT_P010)) { 462 const int Yrows = original->h; 463 const int UVrows = ((original->h + 1) / 2); 464 const int src_Ypitch = pitch; 465 const int src_UVpitch = (yuv_format == SDL_PIXELFORMAT_P010) ? ((pitch + 3) & ~3) : ((pitch + 1) & ~1); 466 const Uint8 *src_plane0 = (const Uint8 *)raw_yuv; 467 const Uint8 *src_plane1 = src_plane0 + Yrows * src_Ypitch; 468 const int Ypitch = pitch + 37; 469 const int UVpitch = ((Ypitch + 1) / 2) * 2; 470 Uint8 *plane0 = (Uint8 *)SDL_calloc(1, Yrows * Ypitch); 471 Uint8 *plane1 = (Uint8 *)SDL_calloc(1, UVrows * UVpitch); 472 int row; 473 const Uint8 *src; 474 Uint8 *dst; 475 476 if (!plane0 || !plane1) { 477 SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Couldn't create YUV planes: %s", SDL_GetError()); 478 goto done; 479 } 480 481 src = src_plane0; 482 dst = plane0; 483 for (row = 0; row < Yrows; ++row) { 484 SDL_memcpy(dst, src, src_Ypitch); 485 src += src_Ypitch; 486 dst += Ypitch; 487 } 488 489 src = src_plane1; 490 dst = plane1; 491 for (row = 0; row < UVrows; ++row) { 492 SDL_memcpy(dst, src, src_UVpitch); 493 src += src_UVpitch; 494 dst += UVpitch; 495 } 496 497 SDL_UpdateNVTexture(output[2], NULL, plane0, Ypitch, plane1, UVpitch); 498 SDL_free(plane0); 499 SDL_free(plane1); 500 } else { 501 SDL_UpdateTexture(output[2], NULL, raw_yuv, pitch); 502 } 503 504 result = true; 505 506done: 507 SDL_DestroySurface(converted); 508 SDL_free(raw_yuv); 509 return result; 510} 511 512static bool has_10bit_texture_format(SDL_Renderer *renderer) 513{ 514 const SDL_PixelFormat *texture_formats = (const SDL_PixelFormat *)SDL_GetPointerProperty(SDL_GetRendererProperties(renderer), SDL_PROP_RENDERER_TEXTURE_FORMATS_POINTER, NULL); 515 if (texture_formats) { 516 for (int i = 0; texture_formats[i] != SDL_PIXELFORMAT_UNKNOWN; ++i) { 517 if (SDL_ISPIXELFORMAT_10BIT(texture_formats[i])) { 518 return true; 519 } 520 } 521 } 522 return false; 523} 524 525static bool check_output(SDL_Renderer *renderer, SDL_Surface *original, SDL_Texture *texture) 526{ 527 // Clear to yellow to clearly see unfilled pixels 528 SDL_SetRenderDrawColor(renderer, 255, 255, 0, SDL_ALPHA_OPAQUE); 529 SDL_RenderClear(renderer); 530 531 SDL_Rect rect = { 0, 0, texture->w, texture->h }; 532 SDL_FRect frect = { 0.0f, 0.0f, (float)texture->w, (float)texture->h }; 533 SDL_RenderTexture(renderer, texture, &frect, &frect); 534 SDL_Surface *output = SDL_RenderReadPixels(renderer, &rect); 535 if (!output) { 536 SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Couldn't read pixels: %s", SDL_GetError()); 537 return false; 538 } 539 SDL_RenderPresent(renderer); 540 541 // Allow some error for colorspace conversion and differences in color depth 542 const int MAX_ALLOWABLE_ERROR = 4096; 543 bool result; 544 if (SDLTest_CompareSurfaces(output, original, MAX_ALLOWABLE_ERROR) == 0) { 545 result = true; 546 } else { 547 result = false; 548 } 549 SDL_DestroySurface(output); 550 551 return result; 552} 553 554static bool run_single_format_test(SDL_Renderer *renderer, SDL_Surface *original, SDL_PixelFormat yuv_format, SDL_PixelFormat rgb_format, bool planar) 555{ 556 SDL_Texture *output[3]; 557 bool result = true; 558 559 if (!create_textures(renderer, original, yuv_format, rgb_format, planar, false, 100, output)) { 560 return false; 561 } 562 563 if (!check_output(renderer, original, output[0])) { 564 SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Original texture didn't match source data, failing"); 565 result = false; 566 } 567 568 if (!check_output(renderer, original, output[1])) { 569 SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "RGB output didn't match source data, failing"); 570 result = false; 571 } 572 573 if (!check_output(renderer, original, output[2])) { 574 SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "YUV output didn't match source data, failing"); 575 result = false; 576 } 577 578 for (int i = 0; i < SDL_arraysize(output); ++i) { 579 SDL_DestroyTexture(output[i]); 580 } 581 return result; 582} 583 584static bool run_all_format_test(SDL_Window *window, const char *requested_renderer, SDL_Surface *original) 585{ 586 const SDL_PixelFormat yuv_formats[] = { 587 SDL_PIXELFORMAT_YV12, 588 SDL_PIXELFORMAT_IYUV, 589 SDL_PIXELFORMAT_YUY2, 590 SDL_PIXELFORMAT_UYVY, 591 SDL_PIXELFORMAT_YVYU, 592 SDL_PIXELFORMAT_NV12, 593 SDL_PIXELFORMAT_NV21 594 }; 595 const SDL_PixelFormat rgb_formats[] = { 596 SDL_PIXELFORMAT_XRGB1555, 597 SDL_PIXELFORMAT_RGB565, 598 SDL_PIXELFORMAT_RGB24, 599 SDL_PIXELFORMAT_ABGR8888, 600 SDL_PIXELFORMAT_RGBA8888, 601 SDL_PIXELFORMAT_BGRA8888 602 }; 603 const struct 604 { 605 YUV_CONVERSION_MODE mode; 606 const char *name; 607 } colorspaces[] = { 608 { YUV_CONVERSION_JPEG, "JPEG" }, 609 { YUV_CONVERSION_BT601, "BT601" }, 610 { YUV_CONVERSION_BT709, "BT709" }, 611 { YUV_CONVERSION_BT2020, "BT2020" } 612 }; 613 bool quit = false; 614 bool result = true; 615 616 for (int i = 0; i < SDL_GetNumRenderDrivers() && !quit; ++i) { 617 const char *renderer_name = SDL_GetRenderDriver(i); 618 if (requested_renderer && SDL_strcmp(renderer_name, requested_renderer) != 0) { 619 continue; 620 } 621 622 SDL_Renderer *renderer = SDL_CreateRenderer(window, renderer_name); 623 if (!renderer) { 624 SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Couldn't create %s renderer: %s", renderer_name, SDL_GetError()); 625 result = false; 626 continue; 627 } 628 629 for (int j = 0; j < SDL_arraysize(colorspaces) && !quit; ++j) { 630 if (colorspaces[j].mode == YUV_CONVERSION_BT2020 && 631 !has_10bit_texture_format(renderer)) { 632 SDL_Log("Skipping %s %s, unsupported", renderer_name, colorspaces[j].name); 633 continue; 634 } 635 SetYUVConversionMode(colorspaces[j].mode); 636 637 for (int m = 0; m < SDL_arraysize(yuv_formats) && !quit; ++m) { 638 SDL_PixelFormat yuv_format = yuv_formats[m]; 639 for (int n = 0; n < SDL_arraysize(rgb_formats) && !quit; ++n) { 640 SDL_PixelFormat rgb_format = rgb_formats[n]; 641 642 SDL_Log("Testing: %s %s %s %s (planar)", renderer_name, colorspaces[j].name, SDL_GetPixelFormatName(yuv_format), SDL_GetPixelFormatName(rgb_format)); 643 result &= run_single_format_test(renderer, original, yuv_format, rgb_format, true); 644 645 SDL_Log("Testing: %s %s %s %s (packed)", renderer_name, colorspaces[j].name, SDL_GetPixelFormatName(yuv_format), SDL_GetPixelFormatName(rgb_format)); 646 result &= run_single_format_test(renderer, original, yuv_format, rgb_format, false); 647 648 SDL_Event event; 649 while (SDL_PollEvent(&event)) { 650 if (event.type == SDL_EVENT_QUIT || 651 (event.type == SDL_EVENT_KEY_DOWN && event.key.key == SDLK_ESCAPE)) { 652 quit = true; 653 } 654 } 655 } 656 } 657 } 658 SDL_DestroyRenderer(renderer); 659 } 660 return result; 661} 662 663static bool run_interactive(SDL_Window *window, const char *renderer_name, SDL_Surface *original, SDL_PixelFormat yuv_format, SDL_PixelFormat rgb_format, bool planar, bool monochrome, int luminance) 664{ 665 const char *titles[3] = { "ORIGINAL", "SOFTWARE", "HARDWARE" }; 666 char title[128]; 667 const char *yuv_mode_name; 668 YUV_CONVERSION_MODE yuv_mode; 669 const char *yuv_format_name; 670 int current = 0; 671 bool quit = false; 672 bool result = false; 673 674 SDL_Renderer *renderer = SDL_CreateRenderer(window, renderer_name); 675 if (!renderer) { 676 SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Couldn't create renderer: %s", SDL_GetError()); 677 return false; 678 } 679 680 SDL_Texture *output[3]; 681 if (!create_textures(renderer, original, yuv_format, rgb_format, planar, monochrome, luminance, output)) { 682 goto done; 683 } 684 685 yuv_mode = GetYUVConversionModeForResolution(original->w, original->h); 686 switch (yuv_mode) { 687 case YUV_CONVERSION_JPEG: 688 yuv_mode_name = "JPEG"; 689 break; 690 case YUV_CONVERSION_BT601: 691 yuv_mode_name = "BT.601"; 692 break; 693 case YUV_CONVERSION_BT709: 694 yuv_mode_name = "BT.709"; 695 break; 696 case YUV_CONVERSION_BT2020: 697 yuv_mode_name = "BT.2020"; 698 yuv_format = SDL_PIXELFORMAT_P010; 699 break; 700 default: 701 yuv_mode_name = "UNKNOWN"; 702 break; 703 } 704 705 yuv_format_name = SDL_GetPixelFormatName(yuv_format); 706 if (SDL_strncmp(yuv_format_name, "SDL_PIXELFORMAT_", 16) == 0) { 707 yuv_format_name += 16; 708 } 709 710 while (!quit) { 711 SDL_Event event; 712 while (SDL_PollEvent(&event) > 0) { 713 if (event.type == SDL_EVENT_QUIT) { 714 quit = true; 715 } 716 if (event.type == SDL_EVENT_KEY_DOWN) { 717 if (event.key.key == SDLK_ESCAPE) { 718 quit = true; 719 } else if (event.key.key == SDLK_LEFT) { 720 --current; 721 } else if (event.key.key == SDLK_RIGHT) { 722 ++current; 723 } 724 } 725 if (event.type == SDL_EVENT_MOUSE_BUTTON_DOWN) { 726 if (event.button.x < (original->w / 2)) { 727 --current; 728 } else { 729 ++current; 730 } 731 } 732 } 733 734 /* Handle wrapping */ 735 if (current < 0) { 736 current += SDL_arraysize(output); 737 } 738 if (current >= SDL_arraysize(output)) { 739 current -= SDL_arraysize(output); 740 } 741 742 SDL_RenderClear(renderer); 743 SDL_RenderTexture(renderer, output[current], NULL, NULL); 744 SDL_SetRenderDrawColor(renderer, 0xFF, 0xFF, 0xFF, 0xFF); 745 if (current == 0) { 746 SDLTest_DrawString(renderer, 4, 4, titles[current]); 747 } else { 748 if (SDL_snprintf(title, sizeof(title), "%s %s %s", titles[current], yuv_format_name, yuv_mode_name) > 0) { 749 SDLTest_DrawString(renderer, 4, 4, title); 750 } 751 } 752 SDL_RenderPresent(renderer); 753 SDL_Delay(10); 754 } 755 756 result = true; 757 758done: 759 for (int i = 0; i < SDL_arraysize(output); ++i) { 760 SDL_DestroyTexture(output[i]); 761 } 762 SDLTest_CleanupTextDrawing(); 763 SDL_DestroyRenderer(renderer); 764 return result; 765} 766 767int main(int argc, char **argv) 768{ 769 struct 770 { 771 bool enable_intrinsics; 772 int pattern_size; 773 int extra_pitch; 774 } automated_test_params[] = { 775 /* Test: single pixel */ 776 { false, 1, 0 }, 777 /* Test: even width and height */ 778 { false, 2, 0 }, 779 { false, 4, 0 }, 780 /* Test: odd width and height */ 781 { false, 1, 0 }, 782 { false, 3, 0 }, 783 /* Test: even width and height, extra pitch */ 784 { false, 2, 3 }, 785 { false, 4, 3 }, 786 /* Test: odd width and height, extra pitch */ 787 { false, 1, 3 }, 788 { false, 3, 3 }, 789 /* Test: even width and height with intrinsics */ 790 { true, 32, 0 }, 791 /* Test: odd width and height with intrinsics */ 792 { true, 33, 0 }, 793 { true, 37, 0 }, 794 /* Test: even width and height with intrinsics, extra pitch */ 795 { true, 32, 3 }, 796 /* Test: odd width and height with intrinsics, extra pitch */ 797 { true, 33, 3 }, 798 { true, 37, 3 }, 799 }; 800 char *filename = NULL; 801 SDL_Surface *original = NULL; 802 SDL_Surface *png = NULL; 803 SDL_Window *window = NULL; 804 const char *renderer_name = NULL; 805 Uint32 yuv_format = SDL_PIXELFORMAT_YV12; 806 Uint32 rgb_format = SDL_PIXELFORMAT_RGBX8888; 807 bool planar = false; 808 bool monochrome = false; 809 int luminance = 100; 810 int i; 811 bool should_run_automated_tests = false; 812 bool should_run_colorspace_test = false; 813 bool should_test_all_formats = false; 814 SDLTest_CommonState *state; 815 int result = 0; 816 817 /* Initialize test framework */ 818 state = SDLTest_CommonCreateState(argv, 0); 819 if (!state) { 820 return 1; 821 } 822 823 /* Parse command line */ 824 for (i = 1; i < argc;) { 825 int consumed; 826 827 consumed = SDLTest_CommonArg(state, i); 828 if (!consumed) { 829 if (SDL_strcmp(argv[i], "--all") == 0) { 830 should_test_all_formats = true; 831 consumed = 1; 832 } else if (SDL_strcmp(argv[i], "--jpeg") == 0) { 833 SetYUVConversionMode(YUV_CONVERSION_JPEG); 834 consumed = 1; 835 } else if (SDL_strcmp(argv[i], "--bt601") == 0) { 836 SetYUVConversionMode(YUV_CONVERSION_BT601); 837 consumed = 1; 838 } else if (SDL_strcmp(argv[i], "--bt709") == 0) { 839 SetYUVConversionMode(YUV_CONVERSION_BT709); 840 consumed = 1; 841 } else if (SDL_strcmp(argv[i], "--bt2020") == 0) { 842 SetYUVConversionMode(YUV_CONVERSION_BT2020); 843 consumed = 1; 844 } else if (SDL_strcmp(argv[i], "--auto") == 0) { 845 SetYUVConversionMode(YUV_CONVERSION_AUTOMATIC); 846 consumed = 1; 847 } else if (SDL_strcmp(argv[i], "--yv12") == 0) { 848 yuv_format = SDL_PIXELFORMAT_YV12; 849 consumed = 1; 850 } else if (SDL_strcmp(argv[i], "--iyuv") == 0) { 851 yuv_format = SDL_PIXELFORMAT_IYUV; 852 consumed = 1; 853 } else if (SDL_strcmp(argv[i], "--yuy2") == 0) { 854 yuv_format = SDL_PIXELFORMAT_YUY2; 855 consumed = 1; 856 } else if (SDL_strcmp(argv[i], "--uyvy") == 0) { 857 yuv_format = SDL_PIXELFORMAT_UYVY; 858 consumed = 1; 859 } else if (SDL_strcmp(argv[i], "--yvyu") == 0) { 860 yuv_format = SDL_PIXELFORMAT_YVYU; 861 consumed = 1; 862 } else if (SDL_strcmp(argv[i], "--nv12") == 0) { 863 yuv_format = SDL_PIXELFORMAT_NV12; 864 consumed = 1; 865 } else if (SDL_strcmp(argv[i], "--nv21") == 0) { 866 yuv_format = SDL_PIXELFORMAT_NV21; 867 consumed = 1; 868 } else if (SDL_strcmp(argv[i], "--rgb555") == 0) { 869 rgb_format = SDL_PIXELFORMAT_XRGB1555; 870 consumed = 1; 871 } else if (SDL_strcmp(argv[i], "--rgb565") == 0) { 872 rgb_format = SDL_PIXELFORMAT_RGB565; 873 consumed = 1; 874 } else if (SDL_strcmp(argv[i], "--rgb24") == 0) { 875 rgb_format = SDL_PIXELFORMAT_RGB24; 876 consumed = 1; 877 } else if (SDL_strcmp(argv[i], "--argb") == 0) { 878 rgb_format = SDL_PIXELFORMAT_ARGB8888; 879 consumed = 1; 880 } else if (SDL_strcmp(argv[i], "--abgr") == 0) { 881 rgb_format = SDL_PIXELFORMAT_ABGR8888; 882 consumed = 1; 883 } else if (SDL_strcmp(argv[i], "--rgba") == 0) { 884 rgb_format = SDL_PIXELFORMAT_RGBA8888; 885 consumed = 1; 886 } else if (SDL_strcmp(argv[i], "--bgra") == 0) { 887 rgb_format = SDL_PIXELFORMAT_BGRA8888; 888 consumed = 1; 889 } else if (SDL_strcmp(argv[i], "--planar") == 0) { 890 planar = true; 891 consumed = 1; 892 } else if (SDL_strcmp(argv[i], "--monochrome") == 0) { 893 monochrome = true; 894 consumed = 1; 895 } else if (SDL_strcmp(argv[i], "--luminance") == 0 && argv[i+1]) { 896 luminance = SDL_atoi(argv[i+1]); 897 consumed = 2; 898 } else if (SDL_strcmp(argv[i], "--automated") == 0) { 899 should_run_automated_tests = true; 900 consumed = 1; 901 } else if (SDL_strcmp(argv[i], "--colorspace-test") == 0) { 902 should_run_colorspace_test = true; 903 consumed = 1; 904 } else if (SDL_strcmp(argv[i], "--renderer") == 0 && argv[i + 1]) { 905 renderer_name = argv[i + 1]; 906 consumed = 2; 907 } else if (!filename) { 908 filename = argv[i]; 909 consumed = 1; 910 } 911 } 912 if (consumed <= 0) { 913 static const char *options[] = { 914 "[--jpeg|--bt601|--bt709|--bt2020|--auto]", 915 "[--yv12|--iyuv|--yuy2|--uyvy|--yvyu|--nv12|--nv21]", 916 "[--rgb555|--rgb565|--rgb24|--argb|--abgr|--rgba|--bgra]", 917 "[--monochrome] [--luminance N%] [--planar]", 918 "[--automated] [--colorspace-test] [--renderer NAME]", 919 "[sample.png]", 920 NULL, 921 }; 922 SDLTest_CommonLogUsage(state, argv[0], options); 923 SDL_Quit(); 924 SDLTest_CommonDestroyState(state); 925 return 1; 926 } 927 i += consumed; 928 } 929 930 /* Run automated tests */ 931 if (should_run_automated_tests) { 932 for (i = 0; i < (int)SDL_arraysize(automated_test_params); ++i) { 933 SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION, "Running automated test, pattern size %d, extra pitch %d, intrinsics %s", 934 automated_test_params[i].pattern_size, 935 automated_test_params[i].extra_pitch, 936 automated_test_params[i].enable_intrinsics ? "enabled" : "disabled"); 937 if (!run_automated_tests(automated_test_params[i].pattern_size, automated_test_params[i].extra_pitch)) { 938 result = 2; 939 } 940 } 941 goto done; 942 } 943 944 if (should_run_colorspace_test) { 945 if (!run_colorspace_test()) { 946 result = 2; 947 } 948 goto done; 949 } 950 951 filename = GetResourceFilename(filename, "testyuv.png"); 952 png = SDL_LoadSurface(filename); 953 if (png) { 954 original = SDL_ConvertSurface(png, SDL_PIXELFORMAT_RGB24); 955 SDL_DestroySurface(png); 956 } 957 if (!original) { 958 SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Couldn't load %s: %s", filename, SDL_GetError()); 959 result = 3; 960 goto done; 961 } 962 963 window = SDL_CreateWindow("YUV test", original->w, original->h, 0); 964 if (!window) { 965 SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Couldn't create window: %s", SDL_GetError()); 966 result = 4; 967 goto done; 968 } 969 970 if (should_test_all_formats) { 971 if (!run_all_format_test(window, renderer_name, original)) { 972 result = 5; 973 } 974 } else { 975 if (!run_interactive(window, renderer_name, original, yuv_format, rgb_format, planar, monochrome, luminance)) { 976 result = 5; 977 } 978 } 979 980done: 981 SDL_free(filename); 982 SDL_DestroySurface(original); 983 SDL_DestroyWindow(window); 984 SDL_Quit(); 985 SDLTest_CommonDestroyState(state); 986 return result; 987} 988
[FILE END]
(C) 2025 0x4248 (C) 2025 4248 Media and 4248 Systems, All part of 0x4248 See LICENCE files for more information. Not all files are by 0x4248 always check Licencing.