1 /* 2 * Copyright 2011 The LibYuv Project Authors. All rights reserved. 3 * 4 * Use of this source code is governed by a BSD-style license 5 * that can be found in the LICENSE file in the root of the source 6 * tree. An additional intellectual property rights grant can be found 7 * in the file PATENTS. All contributing project authors may 8 * be found in the AUTHORS file in the root of the source tree. 9 */ 10 11 #include <stdlib.h> 12 #include <time.h> 13 14 #include "libyuv/convert_argb.h" 15 #include "libyuv/cpu_id.h" 16 #include "libyuv/scale_argb.h" 17 #include "libyuv/video_common.h" 18 #include "../unit_test/unit_test.h" 19 20 namespace libyuv { 21 22 #define STRINGIZE(line) #line 23 #define FILELINESTR(file, line) file ":" STRINGIZE(line) 24 25 // Test scaling with C vs Opt and return maximum pixel difference. 0 = exact. 26 static int ARGBTestFilter(int src_width, int src_height, 27 int dst_width, int dst_height, 28 FilterMode f, int benchmark_iterations, 29 int disable_cpu_flags, int benchmark_cpu_info) { 30 if (!SizeValid(src_width, src_height, dst_width, dst_height)) { 31 return 0; 32 } 33 34 int i, j; 35 const int b = 0; // 128 to test for padding/stride. 36 int64 src_argb_plane_size = (Abs(src_width) + b * 2) * 37 (Abs(src_height) + b * 2) * 4LL; 38 int src_stride_argb = (b * 2 + Abs(src_width)) * 4; 39 40 align_buffer_page_end(src_argb, src_argb_plane_size); 41 if (!src_argb) { 42 printf("Skipped. Alloc failed " FILELINESTR(__FILE__, __LINE__) "\n"); 43 return 0; 44 } 45 MemRandomize(src_argb, src_argb_plane_size); 46 47 int64 dst_argb_plane_size = (dst_width + b * 2) * (dst_height + b * 2) * 4LL; 48 int dst_stride_argb = (b * 2 + dst_width) * 4; 49 50 align_buffer_page_end(dst_argb_c, dst_argb_plane_size); 51 align_buffer_page_end(dst_argb_opt, dst_argb_plane_size); 52 if (!dst_argb_c || !dst_argb_opt) { 53 printf("Skipped. Alloc failed " FILELINESTR(__FILE__, __LINE__) "\n"); 54 return 0; 55 } 56 memset(dst_argb_c, 2, dst_argb_plane_size); 57 memset(dst_argb_opt, 3, dst_argb_plane_size); 58 59 // Warm up both versions for consistent benchmarks. 60 MaskCpuFlags(disable_cpu_flags); // Disable all CPU optimization. 61 ARGBScale(src_argb + (src_stride_argb * b) + b * 4, src_stride_argb, 62 src_width, src_height, 63 dst_argb_c + (dst_stride_argb * b) + b * 4, dst_stride_argb, 64 dst_width, dst_height, f); 65 MaskCpuFlags(benchmark_cpu_info); // Enable all CPU optimization. 66 ARGBScale(src_argb + (src_stride_argb * b) + b * 4, src_stride_argb, 67 src_width, src_height, 68 dst_argb_opt + (dst_stride_argb * b) + b * 4, dst_stride_argb, 69 dst_width, dst_height, f); 70 71 MaskCpuFlags(disable_cpu_flags); // Disable all CPU optimization. 72 double c_time = get_time(); 73 ARGBScale(src_argb + (src_stride_argb * b) + b * 4, src_stride_argb, 74 src_width, src_height, 75 dst_argb_c + (dst_stride_argb * b) + b * 4, dst_stride_argb, 76 dst_width, dst_height, f); 77 78 c_time = (get_time() - c_time); 79 80 MaskCpuFlags(benchmark_cpu_info); // Enable all CPU optimization. 81 double opt_time = get_time(); 82 for (i = 0; i < benchmark_iterations; ++i) { 83 ARGBScale(src_argb + (src_stride_argb * b) + b * 4, src_stride_argb, 84 src_width, src_height, 85 dst_argb_opt + (dst_stride_argb * b) + b * 4, dst_stride_argb, 86 dst_width, dst_height, f); 87 } 88 opt_time = (get_time() - opt_time) / benchmark_iterations; 89 90 // Report performance of C vs OPT 91 printf("filter %d - %8d us C - %8d us OPT\n", 92 f, static_cast<int>(c_time * 1e6), static_cast<int>(opt_time * 1e6)); 93 94 // C version may be a little off from the optimized. Order of 95 // operations may introduce rounding somewhere. So do a difference 96 // of the buffers and look to see that the max difference isn't 97 // over 2. 98 int max_diff = 0; 99 for (i = b; i < (dst_height + b); ++i) { 100 for (j = b * 4; j < (dst_width + b) * 4; ++j) { 101 int abs_diff = Abs(dst_argb_c[(i * dst_stride_argb) + j] - 102 dst_argb_opt[(i * dst_stride_argb) + j]); 103 if (abs_diff > max_diff) { 104 max_diff = abs_diff; 105 } 106 } 107 } 108 109 free_aligned_buffer_page_end(dst_argb_c); 110 free_aligned_buffer_page_end(dst_argb_opt); 111 free_aligned_buffer_page_end(src_argb); 112 return max_diff; 113 } 114 115 static const int kTileX = 8; 116 static const int kTileY = 8; 117 118 static int TileARGBScale(const uint8* src_argb, int src_stride_argb, 119 int src_width, int src_height, 120 uint8* dst_argb, int dst_stride_argb, 121 int dst_width, int dst_height, 122 FilterMode filtering) { 123 for (int y = 0; y < dst_height; y += kTileY) { 124 for (int x = 0; x < dst_width; x += kTileX) { 125 int clip_width = kTileX; 126 if (x + clip_width > dst_width) { 127 clip_width = dst_width - x; 128 } 129 int clip_height = kTileY; 130 if (y + clip_height > dst_height) { 131 clip_height = dst_height - y; 132 } 133 int r = ARGBScaleClip(src_argb, src_stride_argb, 134 src_width, src_height, 135 dst_argb, dst_stride_argb, 136 dst_width, dst_height, 137 x, y, clip_width, clip_height, filtering); 138 if (r) { 139 return r; 140 } 141 } 142 } 143 return 0; 144 } 145 146 static int ARGBClipTestFilter(int src_width, int src_height, 147 int dst_width, int dst_height, 148 FilterMode f, int benchmark_iterations) { 149 if (!SizeValid(src_width, src_height, dst_width, dst_height)) { 150 return 0; 151 } 152 153 const int b = 128; 154 int64 src_argb_plane_size = (Abs(src_width) + b * 2) * 155 (Abs(src_height) + b * 2) * 4; 156 int src_stride_argb = (b * 2 + Abs(src_width)) * 4; 157 158 align_buffer_page_end(src_argb, src_argb_plane_size); 159 if (!src_argb) { 160 printf("Skipped. Alloc failed " FILELINESTR(__FILE__, __LINE__) "\n"); 161 return 0; 162 } 163 memset(src_argb, 1, src_argb_plane_size); 164 165 int64 dst_argb_plane_size = (dst_width + b * 2) * (dst_height + b * 2) * 4; 166 int dst_stride_argb = (b * 2 + dst_width) * 4; 167 168 int i, j; 169 for (i = b; i < (Abs(src_height) + b); ++i) { 170 for (j = b; j < (Abs(src_width) + b) * 4; ++j) { 171 src_argb[(i * src_stride_argb) + j] = (fastrand() & 0xff); 172 } 173 } 174 175 align_buffer_page_end(dst_argb_c, dst_argb_plane_size); 176 align_buffer_page_end(dst_argb_opt, dst_argb_plane_size); 177 if (!dst_argb_c || !dst_argb_opt) { 178 printf("Skipped. Alloc failed " FILELINESTR(__FILE__, __LINE__) "\n"); 179 return 0; 180 } 181 memset(dst_argb_c, 2, dst_argb_plane_size); 182 memset(dst_argb_opt, 3, dst_argb_plane_size); 183 184 // Do full image, no clipping. 185 double c_time = get_time(); 186 ARGBScale(src_argb + (src_stride_argb * b) + b * 4, src_stride_argb, 187 src_width, src_height, 188 dst_argb_c + (dst_stride_argb * b) + b * 4, dst_stride_argb, 189 dst_width, dst_height, f); 190 c_time = (get_time() - c_time); 191 192 // Do tiled image, clipping scale to a tile at a time. 193 double opt_time = get_time(); 194 for (i = 0; i < benchmark_iterations; ++i) { 195 TileARGBScale(src_argb + (src_stride_argb * b) + b * 4, src_stride_argb, 196 src_width, src_height, 197 dst_argb_opt + (dst_stride_argb * b) + b * 4, dst_stride_argb, 198 dst_width, dst_height, f); 199 } 200 opt_time = (get_time() - opt_time) / benchmark_iterations; 201 202 // Report performance of Full vs Tiled. 203 printf("filter %d - %8d us Full - %8d us Tiled\n", 204 f, static_cast<int>(c_time * 1e6), static_cast<int>(opt_time * 1e6)); 205 206 // Compare full scaled image vs tiled image. 207 int max_diff = 0; 208 for (i = b; i < (dst_height + b); ++i) { 209 for (j = b * 4; j < (dst_width + b) * 4; ++j) { 210 int abs_diff = Abs(dst_argb_c[(i * dst_stride_argb) + j] - 211 dst_argb_opt[(i * dst_stride_argb) + j]); 212 if (abs_diff > max_diff) { 213 max_diff = abs_diff; 214 } 215 } 216 } 217 218 free_aligned_buffer_page_end(dst_argb_c); 219 free_aligned_buffer_page_end(dst_argb_opt); 220 free_aligned_buffer_page_end(src_argb); 221 return max_diff; 222 } 223 224 // The following adjustments in dimensions ensure the scale factor will be 225 // exactly achieved. 226 #define DX(x, nom, denom) static_cast<int>((Abs(x) / nom) * nom) 227 #define SX(x, nom, denom) static_cast<int>((x / nom) * denom) 228 229 #define TEST_FACTOR1(name, filter, nom, denom, max_diff) \ 230 TEST_F(LibYUVScaleTest, ARGBScaleDownBy##name##_##filter) { \ 231 int diff = ARGBTestFilter(SX(benchmark_width_, nom, denom), \ 232 SX(benchmark_height_, nom, denom), \ 233 DX(benchmark_width_, nom, denom), \ 234 DX(benchmark_height_, nom, denom), \ 235 kFilter##filter, benchmark_iterations_, \ 236 disable_cpu_flags_, benchmark_cpu_info_); \ 237 EXPECT_LE(diff, max_diff); \ 238 } \ 239 TEST_F(LibYUVScaleTest, ARGBScaleDownClipBy##name##_##filter) { \ 240 int diff = ARGBClipTestFilter(SX(benchmark_width_, nom, denom), \ 241 SX(benchmark_height_, nom, denom), \ 242 DX(benchmark_width_, nom, denom), \ 243 DX(benchmark_height_, nom, denom), \ 244 kFilter##filter, benchmark_iterations_); \ 245 EXPECT_LE(diff, max_diff); \ 246 } 247 248 // Test a scale factor with all 4 filters. Expect unfiltered to be exact, but 249 // filtering is different fixed point implementations for SSSE3, Neon and C. 250 #define TEST_FACTOR(name, nom, denom) \ 251 TEST_FACTOR1(name, None, nom, denom, 0) \ 252 TEST_FACTOR1(name, Linear, nom, denom, 3) \ 253 TEST_FACTOR1(name, Bilinear, nom, denom, 3) \ 254 TEST_FACTOR1(name, Box, nom, denom, 3) 255 256 TEST_FACTOR(2, 1, 2) 257 TEST_FACTOR(4, 1, 4) 258 TEST_FACTOR(8, 1, 8) 259 TEST_FACTOR(3by4, 3, 4) 260 TEST_FACTOR(3by8, 3, 8) 261 TEST_FACTOR(3, 1, 3) 262 #undef TEST_FACTOR1 263 #undef TEST_FACTOR 264 #undef SX 265 #undef DX 266 267 #define TEST_SCALETO1(name, width, height, filter, max_diff) \ 268 TEST_F(LibYUVScaleTest, name##To##width##x##height##_##filter) { \ 269 int diff = ARGBTestFilter(benchmark_width_, benchmark_height_, \ 270 width, height, \ 271 kFilter##filter, benchmark_iterations_, \ 272 disable_cpu_flags_, benchmark_cpu_info_); \ 273 EXPECT_LE(diff, max_diff); \ 274 } \ 275 TEST_F(LibYUVScaleTest, name##From##width##x##height##_##filter) { \ 276 int diff = ARGBTestFilter(width, height, \ 277 Abs(benchmark_width_), Abs(benchmark_height_), \ 278 kFilter##filter, benchmark_iterations_, \ 279 disable_cpu_flags_, benchmark_cpu_info_); \ 280 EXPECT_LE(diff, max_diff); \ 281 } \ 282 TEST_F(LibYUVScaleTest, name##ClipTo##width##x##height##_##filter) { \ 283 int diff = ARGBClipTestFilter(benchmark_width_, benchmark_height_, \ 284 width, height, \ 285 kFilter##filter, benchmark_iterations_); \ 286 EXPECT_LE(diff, max_diff); \ 287 } \ 288 TEST_F(LibYUVScaleTest, name##ClipFrom##width##x##height##_##filter) { \ 289 int diff = ARGBClipTestFilter(width, height, \ 290 Abs(benchmark_width_), \ 291 Abs(benchmark_height_), \ 292 kFilter##filter, benchmark_iterations_); \ 293 EXPECT_LE(diff, max_diff); \ 294 } 295 296 /// Test scale to a specified size with all 4 filters. 297 #define TEST_SCALETO(name, width, height) \ 298 TEST_SCALETO1(name, width, height, None, 0) \ 299 TEST_SCALETO1(name, width, height, Linear, 3) \ 300 TEST_SCALETO1(name, width, height, Bilinear, 3) 301 302 TEST_SCALETO(ARGBScale, 1, 1) 303 TEST_SCALETO(ARGBScale, 320, 240) 304 TEST_SCALETO(ARGBScale, 352, 288) 305 TEST_SCALETO(ARGBScale, 569, 480) 306 TEST_SCALETO(ARGBScale, 640, 360) 307 TEST_SCALETO(ARGBScale, 1280, 720) 308 #undef TEST_SCALETO1 309 #undef TEST_SCALETO 310 311 // Scale with YUV conversion to ARGB and clipping. 312 LIBYUV_API 313 int YUVToARGBScaleReference2(const uint8* src_y, int src_stride_y, 314 const uint8* src_u, int src_stride_u, 315 const uint8* src_v, int src_stride_v, 316 uint32 src_fourcc, 317 int src_width, int src_height, 318 uint8* dst_argb, int dst_stride_argb, 319 uint32 dst_fourcc, 320 int dst_width, int dst_height, 321 int clip_x, int clip_y, 322 int clip_width, int clip_height, 323 enum FilterMode filtering) { 324 uint8* argb_buffer = static_cast<uint8*>(malloc(src_width * src_height * 4)); 325 int r; 326 I420ToARGB(src_y, src_stride_y, 327 src_u, src_stride_u, 328 src_v, src_stride_v, 329 argb_buffer, src_width * 4, 330 src_width, src_height); 331 332 r = ARGBScaleClip(argb_buffer, src_width * 4, 333 src_width, src_height, 334 dst_argb, dst_stride_argb, 335 dst_width, dst_height, 336 clip_x, clip_y, clip_width, clip_height, 337 filtering); 338 free(argb_buffer); 339 return r; 340 } 341 342 static void FillRamp(uint8* buf, int width, int height, int v, int dx, int dy) { 343 int rv = v; 344 for (int y = 0; y < height; ++y) { 345 for (int x = 0; x < width; ++x) { 346 *buf++ = v; 347 v += dx; 348 if (v < 0 || v > 255) { 349 dx = -dx; 350 v += dx; 351 } 352 } 353 v = rv + dy; 354 if (v < 0 || v > 255) { 355 dy = -dy; 356 v += dy; 357 } 358 rv = v; 359 } 360 } 361 362 // Test scaling with C vs Opt and return maximum pixel difference. 0 = exact. 363 static int YUVToARGBTestFilter(int src_width, int src_height, 364 int dst_width, int dst_height, 365 FilterMode f, int benchmark_iterations, 366 int disable_cpu_flags, int benchmark_cpu_info) { 367 int64 src_y_plane_size = Abs(src_width) * Abs(src_height); 368 int64 src_uv_plane_size = ((Abs(src_width) + 1) / 2) * 369 ((Abs(src_height) + 1) / 2); 370 int src_stride_y = Abs(src_width); 371 int src_stride_uv = (Abs(src_width) + 1) / 2; 372 373 align_buffer_page_end(src_y, src_y_plane_size); 374 align_buffer_page_end(src_u, src_uv_plane_size); 375 align_buffer_page_end(src_v, src_uv_plane_size); 376 377 int64 dst_argb_plane_size = (dst_width) * (dst_height) * 4LL; 378 int dst_stride_argb = (dst_width) * 4; 379 align_buffer_page_end(dst_argb_c, dst_argb_plane_size); 380 align_buffer_page_end(dst_argb_opt, dst_argb_plane_size); 381 if (!dst_argb_c || !dst_argb_opt || !src_y || !src_u || !src_v) { 382 printf("Skipped. Alloc failed " FILELINESTR(__FILE__, __LINE__) "\n"); 383 return 0; 384 } 385 // Fill YUV image with continuous ramp, which is less sensitive to 386 // subsampling and filtering differences for test purposes. 387 FillRamp(src_y, Abs(src_width), Abs(src_height), 128, 1, 1); 388 FillRamp(src_u, (Abs(src_width) + 1) / 2, (Abs(src_height) + 1) / 2, 3, 1, 1); 389 FillRamp(src_v, (Abs(src_width) + 1) / 2, (Abs(src_height) + 1) / 2, 4, 1, 1); 390 memset(dst_argb_c, 2, dst_argb_plane_size); 391 memset(dst_argb_opt, 3, dst_argb_plane_size); 392 393 YUVToARGBScaleReference2(src_y, src_stride_y, 394 src_u, src_stride_uv, 395 src_v, src_stride_uv, 396 libyuv::FOURCC_I420, 397 src_width, src_height, 398 dst_argb_c, dst_stride_argb, 399 libyuv::FOURCC_I420, 400 dst_width, dst_height, 401 0, 0, dst_width, dst_height, 402 f); 403 404 for (int i = 0; i < benchmark_iterations; ++i) { 405 YUVToARGBScaleClip(src_y, src_stride_y, 406 src_u, src_stride_uv, 407 src_v, src_stride_uv, 408 libyuv::FOURCC_I420, 409 src_width, src_height, 410 dst_argb_opt, dst_stride_argb, 411 libyuv::FOURCC_I420, 412 dst_width, dst_height, 413 0, 0, dst_width, dst_height, 414 f); 415 } 416 int max_diff = 0; 417 for (int i = 0; i < dst_height; ++i) { 418 for (int j = 0; j < dst_width * 4; ++j) { 419 int abs_diff = Abs(dst_argb_c[(i * dst_stride_argb) + j] - 420 dst_argb_opt[(i * dst_stride_argb) + j]); 421 if (abs_diff > max_diff) { 422 printf("error %d at %d,%d c %d opt %d", 423 abs_diff, 424 j, i, 425 dst_argb_c[(i * dst_stride_argb) + j], 426 dst_argb_opt[(i * dst_stride_argb) + j]); 427 EXPECT_LE(abs_diff, 40); 428 max_diff = abs_diff; 429 } 430 } 431 } 432 433 free_aligned_buffer_page_end(dst_argb_c); 434 free_aligned_buffer_page_end(dst_argb_opt); 435 free_aligned_buffer_page_end(src_y); 436 free_aligned_buffer_page_end(src_u); 437 free_aligned_buffer_page_end(src_v); 438 return max_diff; 439 } 440 441 TEST_F(LibYUVScaleTest, YUVToRGBScaleUp) { 442 int diff = YUVToARGBTestFilter(benchmark_width_, benchmark_height_, 443 benchmark_width_ * 3 / 2, 444 benchmark_height_ * 3 / 2, 445 libyuv::kFilterBilinear, 446 benchmark_iterations_, 447 disable_cpu_flags_, benchmark_cpu_info_); 448 EXPECT_LE(diff, 10); 449 } 450 451 TEST_F(LibYUVScaleTest, YUVToRGBScaleDown) { 452 int diff = YUVToARGBTestFilter(benchmark_width_ * 3 / 2, 453 benchmark_height_ * 3 / 2, 454 benchmark_width_, benchmark_height_, 455 libyuv::kFilterBilinear, 456 benchmark_iterations_, 457 disable_cpu_flags_, benchmark_cpu_info_); 458 EXPECT_LE(diff, 10); 459 } 460 461 462 } // namespace libyuv 463