test/testyuv.c
author Sam Lantinga <slouken@libsdl.org>
Thu, 07 Dec 2017 16:08:09 -0800
changeset 11730 ac6c607e065c
parent 11702 cf166abbde4a
child 11811 5d94cb6b24d3
permissions -rw-r--r--
Enable building the Metal renderer by default, and weak link the Metal framework so the SDL library is safe to use on older Macs
Also generate iOS versions of the Metal shaders
     1 /*
     2   Copyright (C) 1997-2017 Sam Lantinga <slouken@libsdl.org>
     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 <stdlib.h>
    13 #include <stdio.h>
    14 #include <string.h>
    15 
    16 #include "SDL.h"
    17 #include "SDL_test_font.h"
    18 #include "testyuv_cvt.h"
    19 
    20 
    21 /* 422 (YUY2, etc) formats are the largest */
    22 #define MAX_YUV_SURFACE_SIZE(W, H, P)  (H*4*(W+P+1)/2)
    23 
    24 
    25 /* Return true if the YUV format is packed pixels */
    26 static SDL_bool is_packed_yuv_format(Uint32 format)
    27 {
    28     return (format == SDL_PIXELFORMAT_YUY2 ||
    29             format == SDL_PIXELFORMAT_UYVY ||
    30             format == SDL_PIXELFORMAT_YVYU);
    31 }
    32 
    33 /* Create a surface with a good pattern for verifying YUV conversion */
    34 static SDL_Surface *generate_test_pattern(int pattern_size)
    35 {
    36     SDL_Surface *pattern = SDL_CreateRGBSurfaceWithFormat(0, pattern_size, pattern_size, 0, SDL_PIXELFORMAT_RGB24);
    37 
    38     if (pattern) {
    39         int i, x, y;
    40         Uint8 *p, c;
    41         const int thickness = 2;    /* Important so 2x2 blocks of color are the same, to avoid Cr/Cb interpolation over pixels */
    42 
    43         /* R, G, B in alternating horizontal bands */
    44         for (y = 0; y < pattern->h; y += thickness) {
    45             for (i = 0; i < thickness; ++i) {
    46                 p = (Uint8 *)pattern->pixels + (y + i) * pattern->pitch + ((y/thickness) % 3);
    47                 for (x = 0; x < pattern->w; ++x) {
    48                     *p = 0xFF;
    49                     p += 3;
    50                 }
    51             }
    52         }
    53 
    54         /* Black and white in alternating vertical bands */
    55         c = 0xFF;
    56         for (x = 1*thickness; x < pattern->w; x += 2*thickness) {
    57             for (i = 0; i < thickness; ++i) {
    58                 p = (Uint8 *)pattern->pixels + (x + i)*3;
    59                 for (y = 0; y < pattern->h; ++y) {
    60                     SDL_memset(p, c, 3);
    61                     p += pattern->pitch;
    62                 }
    63             }
    64             if (c) {
    65                 c = 0x00;
    66             } else {
    67                 c = 0xFF;
    68             }
    69         }
    70     }
    71     return pattern;
    72 }
    73 
    74 static SDL_bool verify_yuv_data(Uint32 format, const Uint8 *yuv, int yuv_pitch, SDL_Surface *surface)
    75 {
    76     const int tolerance = 20;
    77     const int size = (surface->h * surface->pitch);
    78     Uint8 *rgb;
    79     SDL_bool result = SDL_FALSE;
    80 
    81     rgb = (Uint8 *)SDL_malloc(size);
    82     if (!rgb) {
    83         SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Out of memory");
    84         return SDL_FALSE;
    85     }
    86 
    87     if (SDL_ConvertPixels(surface->w, surface->h, format, yuv, yuv_pitch, surface->format->format, rgb, surface->pitch) == 0) {
    88         int x, y;
    89         result = SDL_TRUE;
    90         for (y = 0; y < surface->h; ++y) {
    91             const Uint8 *actual = rgb + y * surface->pitch;
    92             const Uint8 *expected = (const Uint8 *)surface->pixels + y * surface->pitch;
    93             for (x = 0; x < surface->w; ++x) {
    94                 int deltaR = (int)actual[0] - expected[0];
    95                 int deltaG = (int)actual[1] - expected[1];
    96                 int deltaB = (int)actual[2] - expected[2];
    97                 int distance = (deltaR * deltaR + deltaG * deltaG + deltaB * deltaB);
    98                 if (distance > tolerance) {
    99                     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                     result = SDL_FALSE;
   101                 }
   102                 actual += 3;
   103                 expected += 3;
   104             }
   105         }
   106     } else {
   107         SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Couldn't convert %s to %s: %s\n", SDL_GetPixelFormatName(format), SDL_GetPixelFormatName(surface->format->format), SDL_GetError());
   108     }
   109     SDL_free(rgb);
   110 
   111     return result;
   112 }
   113 
   114 static int run_automated_tests(int pattern_size, int extra_pitch)
   115 {
   116     const Uint32 formats[] = {
   117         SDL_PIXELFORMAT_YV12,
   118         SDL_PIXELFORMAT_IYUV,
   119         SDL_PIXELFORMAT_NV12,
   120         SDL_PIXELFORMAT_NV21,
   121         SDL_PIXELFORMAT_YUY2,
   122         SDL_PIXELFORMAT_UYVY,
   123         SDL_PIXELFORMAT_YVYU
   124     };
   125     int i, j;
   126     SDL_Surface *pattern = generate_test_pattern(pattern_size);
   127     const int yuv_len = MAX_YUV_SURFACE_SIZE(pattern->w, pattern->h, extra_pitch);
   128     Uint8 *yuv1 = (Uint8 *)SDL_malloc(yuv_len);
   129     Uint8 *yuv2 = (Uint8 *)SDL_malloc(yuv_len);
   130     int yuv1_pitch, yuv2_pitch;
   131     int result = -1;
   132     
   133     if (!pattern || !yuv1 || !yuv2) {
   134         SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Couldn't allocate test surfaces");
   135         goto done;
   136     }
   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, SDL_GetYUVConversionModeForResolution(pattern->w, pattern->h), 0, 100)) {
   141             SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "ConvertRGBtoYUV() doesn't support converting to %s\n", SDL_GetPixelFormatName(formats[i]));
   142             goto done;
   143         }
   144         yuv1_pitch = CalculateYUVPitch(formats[i], pattern->w);
   145         if (!verify_yuv_data(formats[i], yuv1, yuv1_pitch, pattern)) {
   146             SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Failed conversion from %s to RGB\n", 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_ConvertPixels(pattern->w, pattern->h, pattern->format->format, pattern->pixels, pattern->pitch, formats[i], yuv1, yuv1_pitch) < 0) {
   155             SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Couldn't convert %s to %s: %s\n", SDL_GetPixelFormatName(pattern->format->format), SDL_GetPixelFormatName(formats[i]), SDL_GetError());
   156             goto done;
   157         }
   158         if (!verify_yuv_data(formats[i], yuv1, yuv1_pitch, pattern)) {
   159             SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Failed conversion from RGB to %s\n", 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_ConvertPixels(pattern->w, pattern->h, pattern->format->format, pattern->pixels, pattern->pitch, formats[i], yuv1, yuv1_pitch) < 0) {
   170                 SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Couldn't convert %s to %s: %s\n", SDL_GetPixelFormatName(pattern->format->format), SDL_GetPixelFormatName(formats[i]), SDL_GetError());
   171                 goto done;
   172             }
   173             if (SDL_ConvertPixels(pattern->w, pattern->h, formats[i], yuv1, yuv1_pitch, formats[j], 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());
   175                 goto done;
   176             }
   177             if (!verify_yuv_data(formats[j], yuv2, yuv2_pitch, pattern)) {
   178                 SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Failed conversion from %s to %s\n", 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_ConvertPixels(pattern->w, pattern->h, pattern->format->format, pattern->pixels, pattern->pitch, formats[i], yuv1, yuv1_pitch) < 0) {
   195                 SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Couldn't convert %s to %s: %s\n", SDL_GetPixelFormatName(pattern->format->format), SDL_GetPixelFormatName(formats[i]), SDL_GetError());
   196                 goto done;
   197             }
   198             if (SDL_ConvertPixels(pattern->w, pattern->h, formats[i], yuv1, yuv1_pitch, formats[j], 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());
   200                 goto done;
   201             }
   202             if (!verify_yuv_data(formats[j], yuv1, yuv2_pitch, pattern)) {
   203                 SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Failed conversion from %s to %s\n", SDL_GetPixelFormatName(formats[i]), SDL_GetPixelFormatName(formats[j]));
   204                 goto done;
   205             }
   206         }
   207     }
   208 
   209 
   210     result = 0;
   211 
   212 done:
   213     SDL_free(yuv1);
   214     SDL_free(yuv2);
   215     SDL_FreeSurface(pattern);
   216     return result;
   217 }
   218 
   219 int
   220 main(int argc, char **argv)
   221 {
   222     struct {
   223         SDL_bool enable_intrinsics;
   224         int pattern_size;
   225         int extra_pitch;
   226     } automated_test_params[] = {
   227         /* Test: even width and height */
   228         { SDL_FALSE, 2, 0 },
   229         { SDL_FALSE, 4, 0 },
   230         /* Test: odd width and height */
   231         { SDL_FALSE, 1, 0 },
   232         { SDL_FALSE, 3, 0 },
   233         /* Test: even width and height, extra pitch */
   234         { SDL_FALSE, 2, 3 },
   235         { SDL_FALSE, 4, 3 },
   236         /* Test: odd width and height, extra pitch */
   237         { SDL_FALSE, 1, 3 },
   238         { SDL_FALSE, 3, 3 },
   239         /* Test: even width and height with intrinsics */
   240         { SDL_TRUE, 32, 0 },
   241         /* Test: odd width and height with intrinsics */
   242         { SDL_TRUE, 33, 0 },
   243         { SDL_TRUE, 37, 0 },
   244         /* Test: even width and height with intrinsics, extra pitch */
   245         { SDL_TRUE, 32, 3 },
   246         /* Test: odd width and height with intrinsics, extra pitch */
   247         { SDL_TRUE, 33, 3 },
   248         { SDL_TRUE, 37, 3 },
   249     };
   250     int arg = 1;
   251     const char *filename;
   252     SDL_Surface *original;
   253     SDL_Surface *converted;
   254     SDL_Window *window;
   255     SDL_Renderer *renderer;
   256     SDL_Texture *output[3];
   257     const char *titles[3] = { "ORIGINAL", "SOFTWARE", "HARDWARE" };
   258     char title[128];
   259     const char *yuv_name;
   260     const char *yuv_mode;
   261     Uint32 rgb_format = SDL_PIXELFORMAT_RGBX8888;
   262     Uint32 yuv_format = SDL_PIXELFORMAT_YV12;
   263     int current = 0;
   264     int pitch;
   265     Uint8 *raw_yuv;
   266     Uint32 then, now, i, iterations = 100;
   267     SDL_bool should_run_automated_tests = SDL_FALSE;
   268 
   269     while (argv[arg] && *argv[arg] == '-') {
   270         if (SDL_strcmp(argv[arg], "--jpeg") == 0) {
   271             SDL_SetYUVConversionMode(SDL_YUV_CONVERSION_JPEG);
   272         } else if (SDL_strcmp(argv[arg], "--bt601") == 0) {
   273             SDL_SetYUVConversionMode(SDL_YUV_CONVERSION_BT601);
   274         } else if (SDL_strcmp(argv[arg], "--bt709") == 0) {
   275             SDL_SetYUVConversionMode(SDL_YUV_CONVERSION_BT709);
   276         } else if (SDL_strcmp(argv[arg], "--auto") == 0) {
   277             SDL_SetYUVConversionMode(SDL_YUV_CONVERSION_AUTOMATIC);
   278         } else if (SDL_strcmp(argv[arg], "--yv12") == 0) {
   279             yuv_format = SDL_PIXELFORMAT_YV12;
   280         } else if (SDL_strcmp(argv[arg], "--iyuv") == 0) {
   281             yuv_format = SDL_PIXELFORMAT_IYUV;
   282         } else if (SDL_strcmp(argv[arg], "--yuy2") == 0) {
   283             yuv_format = SDL_PIXELFORMAT_YUY2;
   284         } else if (SDL_strcmp(argv[arg], "--uyvy") == 0) {
   285             yuv_format = SDL_PIXELFORMAT_UYVY;
   286         } else if (SDL_strcmp(argv[arg], "--yvyu") == 0) {
   287             yuv_format = SDL_PIXELFORMAT_YVYU;
   288         } else if (SDL_strcmp(argv[arg], "--nv12") == 0) {
   289             yuv_format = SDL_PIXELFORMAT_NV12;
   290         } else if (SDL_strcmp(argv[arg], "--nv21") == 0) {
   291             yuv_format = SDL_PIXELFORMAT_NV21;
   292         } else if (SDL_strcmp(argv[arg], "--rgb555") == 0) {
   293             rgb_format = SDL_PIXELFORMAT_RGB555;
   294         } else if (SDL_strcmp(argv[arg], "--rgb565") == 0) {
   295             rgb_format = SDL_PIXELFORMAT_RGB565;
   296         } else if (SDL_strcmp(argv[arg], "--rgb24") == 0) {
   297             rgb_format = SDL_PIXELFORMAT_RGB24;
   298         } else if (SDL_strcmp(argv[arg], "--argb") == 0) {
   299             rgb_format = SDL_PIXELFORMAT_ARGB8888;
   300         } else if (SDL_strcmp(argv[arg], "--abgr") == 0) {
   301             rgb_format = SDL_PIXELFORMAT_ABGR8888;
   302         } else if (SDL_strcmp(argv[arg], "--rgba") == 0) {
   303             rgb_format = SDL_PIXELFORMAT_RGBA8888;
   304         } else if (SDL_strcmp(argv[arg], "--bgra") == 0) {
   305             rgb_format = SDL_PIXELFORMAT_BGRA8888;
   306         } else if (SDL_strcmp(argv[arg], "--automated") == 0) {
   307             should_run_automated_tests = SDL_TRUE;
   308         } else {
   309             SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Usage: %s [--jpeg|--bt601|-bt709|--auto] [--yv12|--iyuv|--yuy2|--uyvy|--yvyu|--nv12|--nv21] [--rgb555|--rgb565|--rgb24|--argb|--abgr|--rgba|--bgra] [image_filename]\n", argv[0]);
   310             return 1;
   311         }
   312         ++arg;
   313     }
   314 
   315     /* Run automated tests */
   316     if (should_run_automated_tests) {
   317         for (i = 0; i < SDL_arraysize(automated_test_params); ++i) {
   318             SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION, "Running automated test, pattern size %d, extra pitch %d, intrinsics %s\n", 
   319                 automated_test_params[i].pattern_size,
   320                 automated_test_params[i].extra_pitch,
   321                 automated_test_params[i].enable_intrinsics ? "enabled" : "disabled");
   322             if (run_automated_tests(automated_test_params[i].pattern_size, automated_test_params[i].extra_pitch) < 0) {
   323                 return 2;
   324             }
   325         }
   326         return 0;
   327     }
   328 
   329     if (argv[arg]) {
   330         filename = argv[arg];
   331     } else {
   332         filename = "testyuv.bmp";
   333     }
   334     original = SDL_ConvertSurfaceFormat(SDL_LoadBMP(filename), SDL_PIXELFORMAT_RGB24, 0);
   335     if (!original) {
   336         SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Couldn't load %s: %s\n", filename, SDL_GetError());
   337         return 3;
   338     }
   339 
   340     raw_yuv = SDL_calloc(1, MAX_YUV_SURFACE_SIZE(original->w, original->h, 0));
   341     ConvertRGBtoYUV(yuv_format, original->pixels, original->pitch, raw_yuv, original->w, original->h,
   342         SDL_GetYUVConversionModeForResolution(original->w, original->h),
   343         0, 100);
   344     pitch = CalculateYUVPitch(yuv_format, original->w);
   345 
   346     converted = SDL_CreateRGBSurfaceWithFormat(0, original->w, original->h, 0, rgb_format);
   347     if (!converted) {
   348         SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Couldn't create converted surface: %s\n", SDL_GetError());
   349         return 3;
   350     }
   351 
   352     then = SDL_GetTicks();
   353     for ( i = 0; i < iterations; ++i ) {
   354         SDL_ConvertPixels(original->w, original->h, yuv_format, raw_yuv, pitch, rgb_format, converted->pixels, converted->pitch);
   355     }
   356     now = SDL_GetTicks();
   357     SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION, "%d iterations in %d ms, %.2fms each\n", iterations, (now - then), (float)(now - then)/iterations);
   358 
   359     window = SDL_CreateWindow("YUV test",
   360                               SDL_WINDOWPOS_UNDEFINED,
   361                               SDL_WINDOWPOS_UNDEFINED,
   362                               original->w, original->h,
   363                               0);
   364     if (!window) {
   365         SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Couldn't create window: %s\n", SDL_GetError());
   366         return 4;
   367     }
   368 
   369     renderer = SDL_CreateRenderer(window, -1, 0);
   370     if (!renderer) {
   371         SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Couldn't create renderer: %s\n", SDL_GetError());
   372         return 4;
   373     }
   374 
   375     output[0] = SDL_CreateTextureFromSurface(renderer, original);
   376     output[1] = SDL_CreateTextureFromSurface(renderer, converted);
   377     output[2] = SDL_CreateTexture(renderer, yuv_format, SDL_TEXTUREACCESS_STREAMING, original->w, original->h);
   378     if (!output[0] || !output[1] || !output[2]) {
   379         SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Couldn't set create texture: %s\n", SDL_GetError());
   380         return 5;
   381     }
   382     SDL_UpdateTexture(output[2], NULL, raw_yuv, pitch);
   383     
   384     yuv_name = SDL_GetPixelFormatName(yuv_format);
   385     if (SDL_strncmp(yuv_name, "SDL_PIXELFORMAT_", 16) == 0) {
   386         yuv_name += 16;
   387     }
   388 
   389     switch (SDL_GetYUVConversionModeForResolution(original->w, original->h)) {
   390     case SDL_YUV_CONVERSION_JPEG:
   391         yuv_mode = "JPEG";
   392         break;
   393     case SDL_YUV_CONVERSION_BT601:
   394         yuv_mode = "BT.601";
   395         break;
   396     case SDL_YUV_CONVERSION_BT709:
   397         yuv_mode = "BT.709";
   398         break;
   399     default:
   400         yuv_mode = "UNKNOWN";
   401         break;
   402     }
   403 
   404     { int done = 0;
   405         while ( !done )
   406         {
   407             SDL_Event event;
   408             while (SDL_PollEvent(&event) > 0) {
   409                 if (event.type == SDL_QUIT) {
   410                     done = 1;
   411                 }
   412                 if (event.type == SDL_KEYDOWN) {
   413                     if (event.key.keysym.sym == SDLK_ESCAPE) {
   414                         done = 1;
   415                     } else if (event.key.keysym.sym == SDLK_LEFT) {
   416                         --current;
   417                     } else if (event.key.keysym.sym == SDLK_RIGHT) {
   418                         ++current;
   419                     }
   420                 }
   421                 if (event.type == SDL_MOUSEBUTTONDOWN) {
   422                     if (event.button.x < (original->w/2)) {
   423                         --current;
   424                     } else {
   425                         ++current;
   426                     }
   427                 }
   428             }
   429 
   430             /* Handle wrapping */
   431             if (current < 0) {
   432                 current += SDL_arraysize(output);
   433             }
   434             if (current >= SDL_arraysize(output)) {
   435                 current -= SDL_arraysize(output);
   436             }
   437 
   438             SDL_RenderClear(renderer);
   439             SDL_RenderCopy(renderer, output[current], NULL, NULL);
   440             SDL_SetRenderDrawColor(renderer, 0xFF, 0xFF, 0xFF, 0xFF);
   441             if (current == 0) {
   442                 SDLTest_DrawString(renderer, 4, 4, titles[current]);
   443             } else {
   444                 SDL_snprintf(title, sizeof(title), "%s %s %s", titles[current], yuv_name, yuv_mode);
   445                 SDLTest_DrawString(renderer, 4, 4, title);
   446             }
   447             SDL_RenderPresent(renderer);
   448             SDL_Delay(10);
   449         }
   450     }
   451     SDL_Quit();
   452     return 0;
   453 }
   454 
   455 /* vi: set ts=4 sw=4 expandtab: */