FreeRDP
Loading...
Searching...
No Matches
TestPrimitivesYUV.c
1
2#include <freerdp/config.h>
3
4#include <stdlib.h>
5#include <math.h>
6#include <errno.h>
7
8#include "prim_test.h"
9
10#include <winpr/print.h>
11
12#include <winpr/wlog.h>
13#include <winpr/crypto.h>
14#include <freerdp/primitives.h>
15#include <freerdp/utils/profiler.h>
16
17#include "../prim_internal.h"
18
19#define TAG __FILE__
20
21#define PADDING_FILL_VALUE 0x37
22
23/* YUV to RGB conversion is lossy, so consider every value only
24 * differing by less than 2 abs equal. */
25WINPR_ATTR_NODISCARD
26static BOOL similar(const BYTE* src, const BYTE* dst, size_t size)
27{
28 for (size_t x = 0; x < size; x++)
29 {
30 int diff = src[x] - dst[x];
31
32 if (abs(diff) > 4)
33 {
34 (void)fprintf(stderr, "%" PRIuz " %02" PRIX8 " : %02" PRIX8 " diff=%d\n", x, src[x],
35 dst[x], abs(diff));
36 return FALSE;
37 }
38 }
39
40 return TRUE;
41}
42
43WINPR_ATTR_NODISCARD
44static BOOL similarRGB(size_t y, const BYTE* src, const BYTE* dst, size_t size, UINT32 format,
45 BOOL use444)
46{
47 BOOL rc = TRUE;
48 /* Skip YUV420, too lossy */
49 if (!use444)
50 return TRUE;
51
52 const UINT32 bpp = FreeRDPGetBytesPerPixel(format);
53 BYTE fill = PADDING_FILL_VALUE;
54 if (!FreeRDPColorHasAlpha(format))
55 fill = 0xFF;
56
57 for (size_t x = 0; x < size; x++)
58 {
59 const LONG maxDiff = 4;
60 UINT32 sColor = 0;
61 UINT32 dColor = 0;
62 BYTE sR = 0;
63 BYTE sG = 0;
64 BYTE sB = 0;
65 BYTE sA = 0;
66 BYTE dR = 0;
67 BYTE dG = 0;
68 BYTE dB = 0;
69 BYTE dA = 0;
70 sColor = FreeRDPReadColor(src, format);
71 dColor = FreeRDPReadColor(dst, format);
72 src += bpp;
73 dst += bpp;
74 FreeRDPSplitColor(sColor, format, &sR, &sG, &sB, &sA, nullptr);
75 FreeRDPSplitColor(dColor, format, &dR, &dG, &dB, &dA, nullptr);
76
77 const long diffr = labs(1L * sR - dR);
78 const long diffg = labs(1L * sG - dG);
79 const long diffb = labs(1L * sB - dB);
80 if ((diffr > maxDiff) || (diffg > maxDiff) || (diffb > maxDiff))
81 {
82 /* AVC444 uses an averaging filter for luma pixel U/V and reverses it in YUV444 -> RGB
83 * this is lossy and does not handle all combinations well so the 2x,2y pixel can be
84 * quite different after RGB -> YUV444 -> RGB conversion.
85 *
86 * skip these pixels to avoid failing the test
87 */
88 if (use444 && ((x % 2) == 0) && ((y % 2) == 0))
89 {
90 continue;
91 }
92
93 const BYTE sY = RGB2Y(sR, sG, sB);
94 const BYTE sU = RGB2U(sR, sG, sB);
95 const BYTE sV = RGB2V(sR, sG, sB);
96 const BYTE dY = RGB2Y(dR, dG, dB);
97 const BYTE dU = RGB2U(dR, dG, dB);
98 const BYTE dV = RGB2V(dR, dG, dB);
99 (void)fprintf(stderr,
100 "[%s] Color value mismatch R[%02X %02X], G[%02X %02X], B[%02X %02X] at "
101 "position %" PRIuz "x%" PRIuz "\n",
102 use444 ? "AVC444" : "AVC420", sR, dR, sG, dG, sA, dA, x, y);
103 (void)fprintf(stderr,
104 "[%s] Color value mismatch Y[%02X %02X], U[%02X %02X], V[%02X %02X] at "
105 "position %" PRIuz "x%" PRIuz "\n",
106 use444 ? "AVC444" : "AVC420", sY, dY, sU, dU, sV, dV, x, y);
107 rc = FALSE;
108 continue;
109 }
110
111 if (dA != fill)
112 {
113 (void)fprintf(
114 stderr,
115 "[%s] Invalid destination alpha value 0x%02X [expected 0x%02X] at position %" PRIuz
116 "x%" PRIuz "\n",
117 use444 ? "AVC444" : "AVC420", dA, fill, x, y);
118 rc = FALSE;
119 continue;
120 }
121 }
122
123 return rc;
124}
125
126WINPR_ATTR_NODISCARD
127static BOOL get_size(BOOL large, UINT32* width, UINT32* height)
128{
129 UINT32 shift = large ? 8 : 1;
130 if (winpr_RAND(width, sizeof(*width)) < 0)
131 return FALSE;
132 if (winpr_RAND(height, sizeof(*height)) < 0)
133 return FALSE;
134 *width = (*width % 64 + 1) << shift;
135 *height = (*height % 64 + 1);
136 return TRUE;
137}
138
139WINPR_ATTR_NODISCARD
140static BOOL check_padding(const BYTE* psrc, size_t size, size_t padding, const char* buffer)
141{
142 BOOL rc = TRUE;
143 const BYTE* src = nullptr;
144 const BYTE* esrc = nullptr;
145 size_t halfPad = (padding + 1) / 2;
146
147 if (!psrc)
148 return FALSE;
149
150 src = psrc - halfPad;
151 esrc = src + size + halfPad;
152
153 for (size_t x = 0; x < halfPad; x++)
154 {
155 const BYTE s = *src++;
156 const BYTE d = *esrc++;
157
158 if (s != 'A')
159 {
160 size_t start = x;
161
162 while ((x < halfPad) && (*esrc++ != 'A'))
163 x++;
164
165 (void)fprintf(stderr,
166 "Buffer underflow detected %02" PRIx8 " != %02X %s [%" PRIuz "-%" PRIuz
167 "]\n",
168 d, 'A', buffer, start, x);
169 return FALSE;
170 }
171
172 if (d != 'A')
173 {
174 size_t start = x;
175
176 while ((x < halfPad) && (*esrc++ != 'A'))
177 x++;
178
179 (void)fprintf(stderr,
180 "Buffer overflow detected %02" PRIx8 " != %02X %s [%" PRIuz "-%" PRIuz
181 "]\n",
182 d, 'A', buffer, start, x);
183 return FALSE;
184 }
185 }
186
187 return rc;
188}
189
190static void* set_padding(size_t size, size_t padding)
191{
192 size_t halfPad = (padding + 1) / 2;
193 BYTE* psrc = nullptr;
194 BYTE* src = winpr_aligned_malloc(size + 2 * halfPad, 16);
195
196 if (!src)
197 return nullptr;
198
199 memset(&src[0], 'A', halfPad);
200 memset(&src[halfPad], PADDING_FILL_VALUE, size);
201 memset(&src[halfPad + size], 'A', halfPad);
202 psrc = &src[halfPad];
203
204 if (!check_padding(psrc, size, padding, "init"))
205 {
206 winpr_aligned_free(src);
207 return nullptr;
208 }
209
210 return psrc;
211}
212
213static void free_padding(void* src, size_t padding)
214{
215 BYTE* ptr = nullptr;
216
217 if (!src)
218 return;
219
220 ptr = ((BYTE*)src) - (padding + 1) / 2;
221 winpr_aligned_free(ptr);
222}
223
224/* Create 2 pseudo YUV420 frames of same size.
225 * Combine them and check, if the data is at the expected position. */
226static BOOL TestPrimitiveYUVCombine(primitives_t* prims, prim_size_t roi)
227{
228 union
229 {
230 const BYTE** cpv;
231 BYTE** pv;
232 } cnv;
233 size_t awidth = 0;
234 size_t aheight = 0;
235 BOOL rc = FALSE;
236 BYTE* luma[3] = WINPR_C_ARRAY_INIT;
237 BYTE* chroma[3] = WINPR_C_ARRAY_INIT;
238 BYTE* yuv[3] = WINPR_C_ARRAY_INIT;
239 BYTE* pmain[3] = WINPR_C_ARRAY_INIT;
240 BYTE* paux[3] = WINPR_C_ARRAY_INIT;
241 UINT32 lumaStride[3] = WINPR_C_ARRAY_INIT;
242 UINT32 chromaStride[3] = WINPR_C_ARRAY_INIT;
243 UINT32 yuvStride[3] = WINPR_C_ARRAY_INIT;
244 const size_t padding = 10000;
245 RECTANGLE_16 rect = WINPR_C_ARRAY_INIT;
246 PROFILER_DEFINE(yuvCombine)
247 PROFILER_DEFINE(yuvSplit)
248
249 // TODO: we only support even height values at the moment
250 if (roi.height % 2)
251 roi.height++;
252
253 awidth = roi.width + 16 - roi.width % 16;
254 aheight = roi.height + 16 - roi.height % 16;
255 (void)fprintf(stderr,
256 "Running YUVCombine on frame size %" PRIu32 "x%" PRIu32 " [%" PRIuz "x%" PRIuz
257 "]\n",
258 roi.width, roi.height, awidth, aheight);
259 PROFILER_CREATE(yuvCombine, "YUV420CombineToYUV444")
260 PROFILER_CREATE(yuvSplit, "YUV444SplitToYUV420")
261 rect.left = 0;
262 rect.top = 0;
263 rect.right = roi.width;
264 rect.bottom = roi.height;
265
266 if (!prims || !prims->YUV420CombineToYUV444)
267 goto fail;
268
269 for (UINT32 x = 0; x < 3; x++)
270 {
271 size_t halfStride = ((x > 0) ? awidth / 2 : awidth);
272 size_t size = aheight * awidth;
273 size_t halfSize = ((x > 0) ? halfStride * aheight / 2 : awidth * aheight);
274 yuvStride[x] = awidth;
275
276 if (!(yuv[x] = set_padding(size, padding)))
277 goto fail;
278
279 lumaStride[x] = halfStride;
280
281 if (!(luma[x] = set_padding(halfSize, padding)))
282 goto fail;
283
284 if (!(pmain[x] = set_padding(halfSize, padding)))
285 goto fail;
286
287 chromaStride[x] = halfStride;
288
289 if (!(chroma[x] = set_padding(halfSize, padding)))
290 goto fail;
291
292 if (!(paux[x] = set_padding(halfSize, padding)))
293 goto fail;
294
295 memset(luma[x], WINPR_ASSERTING_INT_CAST(int, 0xAB + 3 * x), halfSize);
296 memset(chroma[x], WINPR_ASSERTING_INT_CAST(int, 0x80 + 2 * x), halfSize);
297
298 if (!check_padding(luma[x], halfSize, padding, "luma"))
299 goto fail;
300
301 if (!check_padding(chroma[x], halfSize, padding, "chroma"))
302 goto fail;
303
304 if (!check_padding(pmain[x], halfSize, padding, "main"))
305 goto fail;
306
307 if (!check_padding(paux[x], halfSize, padding, "aux"))
308 goto fail;
309
310 if (!check_padding(yuv[x], size, padding, "yuv"))
311 goto fail;
312 }
313
314 PROFILER_ENTER(yuvCombine)
315
316 cnv.pv = luma;
317 if (prims->YUV420CombineToYUV444(AVC444_LUMA, cnv.cpv, lumaStride, roi.width, roi.height, yuv,
318 yuvStride, &rect) != PRIMITIVES_SUCCESS)
319 {
320 PROFILER_EXIT(yuvCombine)
321 goto fail;
322 }
323
324 cnv.pv = chroma;
325 if (prims->YUV420CombineToYUV444(AVC444_CHROMAv1, cnv.cpv, chromaStride, roi.width, roi.height,
326 yuv, yuvStride, &rect) != PRIMITIVES_SUCCESS)
327 {
328 PROFILER_EXIT(yuvCombine)
329 goto fail;
330 }
331
332 PROFILER_EXIT(yuvCombine)
333
334 for (size_t x = 0; x < 3; x++)
335 {
336 size_t halfStride = ((x > 0) ? awidth / 2 : awidth);
337 size_t size = 1ULL * aheight * awidth;
338 size_t halfSize = ((x > 0) ? halfStride * aheight / 2 : awidth * aheight);
339
340 if (!check_padding(luma[x], halfSize, padding, "luma"))
341 goto fail;
342
343 if (!check_padding(chroma[x], halfSize, padding, "chroma"))
344 goto fail;
345
346 if (!check_padding(yuv[x], size, padding, "yuv"))
347 goto fail;
348 }
349
350 PROFILER_ENTER(yuvSplit)
351
352 cnv.pv = yuv;
353 if (prims->YUV444SplitToYUV420(cnv.cpv, yuvStride, pmain, lumaStride, paux, chromaStride,
354 &roi) != PRIMITIVES_SUCCESS)
355 {
356 PROFILER_EXIT(yuvSplit)
357 goto fail;
358 }
359
360 PROFILER_EXIT(yuvSplit)
361
362 for (UINT32 x = 0; x < 3; x++)
363 {
364 size_t halfStride = ((x > 0) ? awidth / 2 : awidth);
365 size_t size = aheight * awidth;
366 size_t halfSize = ((x > 0) ? halfStride * aheight / 2 : awidth * aheight);
367
368 if (!check_padding(pmain[x], halfSize, padding, "main"))
369 goto fail;
370
371 if (!check_padding(paux[x], halfSize, padding, "aux"))
372 goto fail;
373
374 if (!check_padding(yuv[x], size, padding, "yuv"))
375 goto fail;
376 }
377
378 for (size_t i = 0; i < 3; i++)
379 {
380 for (size_t y = 0; y < roi.height; y++)
381 {
382 UINT32 w = roi.width;
383 UINT32 lstride = lumaStride[i];
384 UINT32 cstride = chromaStride[i];
385
386 if (i > 0)
387 {
388 w = (roi.width + 3) / 4;
389
390 if (roi.height > (roi.height + 1) / 2)
391 continue;
392 }
393
394 if (!similar(luma[i] + y * lstride, pmain[i] + y * lstride, w))
395 goto fail;
396
397 /* Need to ignore lines of destination Y plane,
398 * if the lines are not a multiple of 16
399 * as the UV planes are packed in 8 line stripes. */
400 if (i == 0)
401 {
402 /* TODO: This check is not perfect, it does not
403 * include the last V lines packed to the Y
404 * frame. */
405 UINT32 rem = roi.height % 16;
406
407 if (y > roi.height - rem)
408 continue;
409 }
410
411 if (!similar(chroma[i] + y * cstride, paux[i] + y * cstride, w))
412 goto fail;
413 }
414 }
415
416 PROFILER_PRINT_HEADER
417 PROFILER_PRINT(yuvSplit)
418 PROFILER_PRINT(yuvCombine)
419 PROFILER_PRINT_FOOTER
420 rc = TRUE;
421fail:
422 printf("[%s] run %s.\n", __func__, (rc) ? "SUCCESS" : "FAILED");
423 PROFILER_FREE(yuvCombine)
424 PROFILER_FREE(yuvSplit)
425
426 for (UINT32 x = 0; x < 3; x++)
427 {
428 free_padding(yuv[x], padding);
429 free_padding(luma[x], padding);
430 free_padding(chroma[x], padding);
431 free_padding(pmain[x], padding);
432 free_padding(paux[x], padding);
433 }
434
435 return rc;
436}
437
438static BOOL TestPrimitiveYUV(primitives_t* prims, prim_size_t roi, BOOL use444)
439{
440 union
441 {
442 const BYTE** cpv;
443 BYTE** pv;
444 } cnv;
445 BOOL res = FALSE;
446 UINT32 awidth = 0;
447 UINT32 aheight = 0;
448 BYTE* yuv[3] = WINPR_C_ARRAY_INIT;
449 UINT32 yuv_step[3] = WINPR_C_ARRAY_INIT;
450 BYTE* rgb = nullptr;
451 BYTE* rgb_dst = nullptr;
452 size_t size = 0;
453 size_t uvsize = 0;
454 size_t uvwidth = 0;
455 size_t padding = 100ULL * 16ULL;
456 UINT32 stride = 0;
457 const UINT32 formats[] = { PIXEL_FORMAT_XRGB32, PIXEL_FORMAT_XBGR32, PIXEL_FORMAT_ARGB32,
458 PIXEL_FORMAT_ABGR32, PIXEL_FORMAT_RGBA32, PIXEL_FORMAT_RGBX32,
459 PIXEL_FORMAT_BGRA32, PIXEL_FORMAT_BGRX32 };
460 PROFILER_DEFINE(rgbToYUV420)
461 PROFILER_DEFINE(rgbToYUV444)
462 PROFILER_DEFINE(yuv420ToRGB)
463 PROFILER_DEFINE(yuv444ToRGB)
464 /* Buffers need to be 16x16 aligned. */
465 awidth = roi.width + 16 - roi.width % 16;
466 aheight = roi.height + 16 - roi.height % 16;
467 stride = 1ULL * awidth * sizeof(UINT32);
468 size = 1ULL * awidth * aheight;
469
470 if (use444)
471 {
472 uvwidth = awidth;
473 uvsize = size;
474
475 if (!prims || !prims->RGBToYUV444_8u_P3AC4R || !prims->YUV444ToRGB_8u_P3AC4R)
476 return FALSE;
477 }
478 else
479 {
480 uvwidth = (awidth + 1) / 2;
481 uvsize = (aheight + 1) / 2 * uvwidth;
482
483 if (!prims || !prims->RGBToYUV420_8u_P3AC4R || !prims->YUV420ToRGB_8u_P3AC4R)
484 return FALSE;
485 }
486
487 (void)fprintf(stderr, "Running AVC%s on frame size %" PRIu32 "x%" PRIu32 "\n",
488 use444 ? "444" : "420", roi.width, roi.height);
489
490 /* Test RGB to YUV444 conversion and vice versa */
491 if (!(rgb = set_padding(size * sizeof(UINT32), padding)))
492 goto fail;
493
494 if (!(rgb_dst = set_padding(size * sizeof(UINT32), padding)))
495 goto fail;
496
497 if (!(yuv[0] = set_padding(size, padding)))
498 goto fail;
499
500 if (!(yuv[1] = set_padding(uvsize, padding)))
501 goto fail;
502
503 if (!(yuv[2] = set_padding(uvsize, padding)))
504 goto fail;
505
506 for (size_t y = 0; y < roi.height; y++)
507 {
508 BYTE* line = &rgb[y * stride];
509 if (winpr_RAND(line, stride) < 0)
510 goto fail;
511 }
512
513 yuv_step[0] = awidth;
514 yuv_step[1] = uvwidth;
515 yuv_step[2] = uvwidth;
516
517 for (UINT32 x = 0; x < ARRAYSIZE(formats); x++)
518 {
519 pstatus_t rc = 0;
520 const UINT32 DstFormat = formats[x];
521 printf("Testing destination color format %s\n", FreeRDPGetColorFormatName(DstFormat));
522 memset(rgb_dst, PADDING_FILL_VALUE, size * sizeof(UINT32));
523
524 PROFILER_CREATE(rgbToYUV420, "RGBToYUV420")
525 PROFILER_CREATE(rgbToYUV444, "RGBToYUV444")
526 PROFILER_CREATE(yuv420ToRGB, "YUV420ToRGB")
527 PROFILER_CREATE(yuv444ToRGB, "YUV444ToRGB")
528
529 if (use444)
530 {
531 PROFILER_ENTER(rgbToYUV444)
532 rc = prims->RGBToYUV444_8u_P3AC4R(rgb, DstFormat, stride, yuv, yuv_step, &roi);
533 PROFILER_EXIT(rgbToYUV444)
534
535 if (rc != PRIMITIVES_SUCCESS)
536 goto loop_fail;
537
538 PROFILER_PRINT_HEADER
539 PROFILER_PRINT(rgbToYUV444)
540 PROFILER_PRINT_FOOTER
541 }
542 else
543 {
544 PROFILER_ENTER(rgbToYUV420)
545 rc = prims->RGBToYUV420_8u_P3AC4R(rgb, DstFormat, stride, yuv, yuv_step, &roi);
546 PROFILER_EXIT(rgbToYUV420)
547
548 if (rc != PRIMITIVES_SUCCESS)
549 goto loop_fail;
550
551 PROFILER_PRINT_HEADER
552 PROFILER_PRINT(rgbToYUV420)
553 PROFILER_PRINT_FOOTER
554 }
555
556 if (!check_padding(rgb, size * sizeof(UINT32), padding, "rgb"))
557 {
558 rc = -1;
559 goto loop_fail;
560 }
561
562 if ((!check_padding(yuv[0], size, padding, "Y")) ||
563 (!check_padding(yuv[1], uvsize, padding, "U")) ||
564 (!check_padding(yuv[2], uvsize, padding, "V")))
565 {
566 rc = -1;
567 goto loop_fail;
568 }
569
570 cnv.pv = yuv;
571 if (use444)
572 {
573 PROFILER_ENTER(yuv444ToRGB)
574 rc = prims->YUV444ToRGB_8u_P3AC4R(cnv.cpv, yuv_step, rgb_dst, stride, DstFormat, &roi);
575 PROFILER_EXIT(yuv444ToRGB)
576
577 if (rc != PRIMITIVES_SUCCESS)
578 goto loop_fail;
579
580 loop_fail:
581 PROFILER_EXIT(yuv444ToRGB)
582 PROFILER_PRINT_HEADER
583 PROFILER_PRINT(yuv444ToRGB)
584 PROFILER_PRINT_FOOTER
585
586 if (rc != PRIMITIVES_SUCCESS)
587 goto fail;
588 }
589 else
590 {
591 PROFILER_ENTER(yuv420ToRGB)
592
593 if (prims->YUV420ToRGB_8u_P3AC4R(cnv.cpv, yuv_step, rgb_dst, stride, DstFormat, &roi) !=
594 PRIMITIVES_SUCCESS)
595 {
596 PROFILER_EXIT(yuv420ToRGB)
597 goto fail;
598 }
599
600 PROFILER_EXIT(yuv420ToRGB)
601 PROFILER_PRINT_HEADER
602 PROFILER_PRINT(yuv420ToRGB)
603 PROFILER_PRINT_FOOTER
604 }
605
606 if (!check_padding(rgb_dst, size * sizeof(UINT32), padding, "rgb dst"))
607 goto fail;
608
609 if ((!check_padding(yuv[0], size, padding, "Y")) ||
610 (!check_padding(yuv[1], uvsize, padding, "U")) ||
611 (!check_padding(yuv[2], uvsize, padding, "V")))
612 goto fail;
613
614 BOOL equal = TRUE;
615 for (size_t y = 0; y < roi.height; y++)
616 {
617 BYTE* srgb = &rgb[y * stride];
618 BYTE* drgb = &rgb_dst[y * stride];
619
620 if (!similarRGB(y, srgb, drgb, roi.width, DstFormat, use444))
621 equal = FALSE;
622 }
623 if (!equal)
624 goto fail;
625
626 PROFILER_FREE(rgbToYUV420)
627 PROFILER_FREE(rgbToYUV444)
628 PROFILER_FREE(yuv420ToRGB)
629 PROFILER_FREE(yuv444ToRGB)
630 }
631
632 res = TRUE;
633fail:
634 printf("[%s] run %s.\n", __func__, (res) ? "SUCCESS" : "FAILED");
635 free_padding(rgb, padding);
636 free_padding(rgb_dst, padding);
637 free_padding(yuv[0], padding);
638 free_padding(yuv[1], padding);
639 free_padding(yuv[2], padding);
640 return res;
641}
642
643static BOOL allocate_yuv420(BYTE** planes, UINT32 width, UINT32 height, UINT32 padding)
644{
645 const size_t size = 1ULL * width * height;
646 const size_t uvwidth = (1ULL + width) / 2;
647 const size_t uvsize = (1ULL + height) / 2 * uvwidth;
648
649 if (!(planes[0] = set_padding(size, padding)))
650 goto fail;
651
652 if (!(planes[1] = set_padding(uvsize, padding)))
653 goto fail;
654
655 if (!(planes[2] = set_padding(uvsize, padding)))
656 goto fail;
657
658 return TRUE;
659fail:
660 free_padding(planes[0], padding);
661 free_padding(planes[1], padding);
662 free_padding(planes[2], padding);
663 return FALSE;
664}
665
666static void free_yuv420(BYTE** planes, UINT32 padding)
667{
668 if (!planes)
669 return;
670
671 free_padding(planes[0], padding);
672 free_padding(planes[1], padding);
673 free_padding(planes[2], padding);
674 planes[0] = nullptr;
675 planes[1] = nullptr;
676 planes[2] = nullptr;
677}
678
679static BOOL check_yuv420(BYTE** planes, UINT32 width, UINT32 height, UINT32 padding)
680{
681 const size_t size = 1ULL * width * height;
682 const size_t uvwidth = (width + 1) / 2;
683 const size_t uvsize = (height + 1) / 2 * uvwidth;
684 const BOOL yOk = check_padding(planes[0], size, padding, "Y");
685 const BOOL uOk = check_padding(planes[1], uvsize, padding, "U");
686 const BOOL vOk = check_padding(planes[2], uvsize, padding, "V");
687 return (yOk && uOk && vOk);
688}
689
690static BOOL check_for_mismatches(const BYTE* planeA, const BYTE* planeB, UINT32 size)
691{
692 BOOL rc = FALSE;
693
694 for (UINT32 x = 0; x < size; x++)
695 {
696 const BYTE a = planeA[x];
697 const BYTE b = planeB[x];
698
699 if (fabsf((float)a - (float)b) > 2.0f)
700 {
701 rc = TRUE;
702 (void)fprintf(stderr, "[%08x] %02x != %02x\n", x, a, b);
703 }
704 }
705
706 return rc;
707}
708
709static BOOL compare_yuv420(BYTE** planesA, BYTE** planesB, UINT32 width, UINT32 height,
710 UINT32 padding)
711{
712 BOOL rc = TRUE;
713 const size_t size = 1ULL * width * height;
714 const size_t uvwidth = (1ULL * width + 1) / 2;
715 const size_t uvsize = (1ULL * height + 1) / 2 * uvwidth;
716
717 if (check_for_mismatches(planesA[0], planesB[0], size))
718 {
719 (void)fprintf(stderr, "Mismatch in Y planes!\n");
720 rc = FALSE;
721 }
722
723 if (check_for_mismatches(planesA[1], planesB[1], uvsize))
724 {
725 (void)fprintf(stderr, "Mismatch in U planes!\n");
726 rc = FALSE;
727 }
728
729 if (check_for_mismatches(planesA[2], planesB[2], uvsize))
730 {
731 (void)fprintf(stderr, "Mismatch in V planes!\n");
732 rc = FALSE;
733 }
734
735 return rc;
736}
737
738static UINT32 prand(UINT32 max)
739{
740 UINT32 tmp = 0;
741 if (max <= 1)
742 return 1;
743 if (winpr_RAND(&tmp, sizeof(tmp)) < 0)
744 {
745 (void)fprintf(stderr, "winpr_RAND failed, retry...\n");
746 // NOLINTNEXTLINE(concurrency-mt-unsafe)
747 exit(-1);
748 }
749 return tmp % (max - 1) + 1;
750}
751
752static BOOL TestPrimitiveRgbToLumaChroma(primitives_t* prims, prim_size_t roi, UINT32 version)
753{
754 BOOL res = FALSE;
755 UINT32 awidth = 0;
756 UINT32 aheight = 0;
757 BYTE* luma[3] = WINPR_C_ARRAY_INIT;
758 BYTE* chroma[3] = WINPR_C_ARRAY_INIT;
759 BYTE* lumaGeneric[3] = WINPR_C_ARRAY_INIT;
760 BYTE* chromaGeneric[3] = WINPR_C_ARRAY_INIT;
761 UINT32 yuv_step[3];
762 BYTE* rgb = nullptr;
763 size_t size = 0;
764 size_t uvwidth = 0;
765 const size_t padding = 0x1000;
766 UINT32 stride = 0;
767 fn_RGBToAVC444YUV_t fkt = nullptr;
768 fn_RGBToAVC444YUV_t gen = nullptr;
769 const UINT32 formats[] = { PIXEL_FORMAT_XRGB32, PIXEL_FORMAT_XBGR32, PIXEL_FORMAT_ARGB32,
770 PIXEL_FORMAT_ABGR32, PIXEL_FORMAT_RGBA32, PIXEL_FORMAT_RGBX32,
771 PIXEL_FORMAT_BGRA32, PIXEL_FORMAT_BGRX32 };
772 PROFILER_DEFINE(rgbToYUV444)
773 PROFILER_DEFINE(rgbToYUV444opt)
774 /* Buffers need to be 16x16 aligned. */
775 awidth = roi.width;
776
777 if (awidth % 16 != 0)
778 awidth += 16 - roi.width % 16;
779
780 aheight = roi.height;
781
782 if (aheight % 16 != 0)
783 aheight += 16 - roi.height % 16;
784
785 stride = 1ULL * awidth * sizeof(UINT32);
786 size = 1ULL * awidth * aheight;
787 uvwidth = 1ULL * (awidth + 1) / 2;
788
789 if (!prims || !generic)
790 return FALSE;
791
792 switch (version)
793 {
794 case 1:
795 fkt = prims->RGBToAVC444YUV;
796 gen = generic->RGBToAVC444YUV;
797 break;
798
799 case 2:
800 fkt = prims->RGBToAVC444YUVv2;
801 gen = generic->RGBToAVC444YUVv2;
802 break;
803
804 default:
805 return FALSE;
806 }
807
808 if (!fkt || !gen)
809 return FALSE;
810
811 (void)fprintf(stderr, "Running AVC444 on frame size %" PRIu32 "x%" PRIu32 "\n", roi.width,
812 roi.height);
813
814 /* Test RGB to YUV444 conversion and vice versa */
815 if (!(rgb = set_padding(size * sizeof(UINT32), padding)))
816 goto fail;
817
818 if (!allocate_yuv420(luma, awidth, aheight, padding))
819 goto fail;
820
821 if (!allocate_yuv420(chroma, awidth, aheight, padding))
822 goto fail;
823
824 if (!allocate_yuv420(lumaGeneric, awidth, aheight, padding))
825 goto fail;
826
827 if (!allocate_yuv420(chromaGeneric, awidth, aheight, padding))
828 goto fail;
829
830 for (size_t y = 0; y < roi.height; y++)
831 {
832 BYTE* line = &rgb[y * stride];
833
834 if (winpr_RAND(line, 4ULL * roi.width) < 0)
835 goto fail;
836 }
837
838 yuv_step[0] = awidth;
839 yuv_step[1] = uvwidth;
840 yuv_step[2] = uvwidth;
841
842 for (UINT32 x = 0; x < ARRAYSIZE(formats); x++)
843 {
844 pstatus_t rc = -1;
845 const UINT32 DstFormat = formats[x];
846 printf("Testing destination color format %s\n", FreeRDPGetColorFormatName(DstFormat));
847 PROFILER_CREATE(rgbToYUV444, "RGBToYUV444-generic")
848 PROFILER_CREATE(rgbToYUV444opt, "RGBToYUV444-optimized")
849
850 for (UINT32 cnt = 0; cnt < 10; cnt++)
851 {
852 PROFILER_ENTER(rgbToYUV444opt)
853 rc = fkt(rgb, DstFormat, stride, luma, yuv_step, chroma, yuv_step, &roi);
854 PROFILER_EXIT(rgbToYUV444opt)
855
856 if (rc != PRIMITIVES_SUCCESS)
857 goto loop_fail;
858 }
859
860 PROFILER_PRINT_HEADER
861 PROFILER_PRINT(rgbToYUV444opt)
862 PROFILER_PRINT_FOOTER
863
864 if (!check_padding(rgb, size * sizeof(UINT32), padding, "rgb"))
865 {
866 rc = -1;
867 goto loop_fail;
868 }
869
870 if (!check_yuv420(luma, awidth, aheight, padding) ||
871 !check_yuv420(chroma, awidth, aheight, padding))
872 {
873 rc = -1;
874 goto loop_fail;
875 }
876
877 for (UINT32 cnt = 0; cnt < 10; cnt++)
878 {
879 PROFILER_ENTER(rgbToYUV444)
880 rc = gen(rgb, DstFormat, stride, lumaGeneric, yuv_step, chromaGeneric, yuv_step, &roi);
881 PROFILER_EXIT(rgbToYUV444)
882
883 if (rc != PRIMITIVES_SUCCESS)
884 goto loop_fail;
885 }
886
887 PROFILER_PRINT_HEADER
888 PROFILER_PRINT(rgbToYUV444)
889 PROFILER_PRINT_FOOTER
890
891 if (!check_padding(rgb, size * sizeof(UINT32), padding, "rgb"))
892 {
893 rc = -1;
894 goto loop_fail;
895 }
896
897 if (!check_yuv420(lumaGeneric, awidth, aheight, padding) ||
898 !check_yuv420(chromaGeneric, awidth, aheight, padding))
899 {
900 rc = -1;
901 goto loop_fail;
902 }
903
904 if (!compare_yuv420(luma, lumaGeneric, awidth, aheight, padding) ||
905 !compare_yuv420(chroma, chromaGeneric, awidth, aheight, padding))
906 {
907 rc = -1;
908 goto loop_fail;
909 }
910
911 loop_fail:
912 PROFILER_FREE(rgbToYUV444)
913 PROFILER_FREE(rgbToYUV444opt)
914
915 if (rc != PRIMITIVES_SUCCESS)
916 goto fail;
917 }
918
919 res = TRUE;
920fail:
921 printf("[%s][version %u] run %s.\n", __func__, (unsigned)version, (res) ? "SUCCESS" : "FAILED");
922 free_padding(rgb, padding);
923 free_yuv420(luma, padding);
924 free_yuv420(chroma, padding);
925 free_yuv420(lumaGeneric, padding);
926 free_yuv420(chromaGeneric, padding);
927 return res;
928}
929
930static BOOL run_tests(prim_size_t roi)
931{
932 BOOL rc = FALSE;
933 for (UINT32 type = PRIMITIVES_PURE_SOFT; type <= PRIMITIVES_AUTODETECT; type++)
934 {
935 primitives_t* prims = primitives_get_by_type(type);
936 if (!prims)
937 {
938 printf("primitives type %d not supported\n", type);
939 continue;
940 }
941
942 for (UINT32 x = 0; x < 5; x++)
943 {
944
945 printf("-------------------- GENERIC ------------------------\n");
946
947 if (!TestPrimitiveYUV(prims, roi, TRUE))
948 goto fail;
949
950 printf("---------------------- END --------------------------\n");
951 printf("-------------------- GENERIC ------------------------\n");
952
953 if (!TestPrimitiveYUV(prims, roi, FALSE))
954 goto fail;
955
956 printf("---------------------- END --------------------------\n");
957 printf("-------------------- GENERIC ------------------------\n");
958
959 if (!TestPrimitiveYUVCombine(prims, roi))
960 goto fail;
961
962 printf("---------------------- END --------------------------\n");
963 printf("-------------------- GENERIC ------------------------\n");
964
965 if (!TestPrimitiveRgbToLumaChroma(prims, roi, 1))
966 goto fail;
967
968 printf("---------------------- END --------------------------\n");
969 printf("-------------------- GENERIC ------------------------\n");
970
971 if (!TestPrimitiveRgbToLumaChroma(prims, roi, 2))
972 goto fail;
973
974 printf("---------------------- END --------------------------\n");
975 }
976 }
977 rc = TRUE;
978fail:
979 printf("[%s] run %s.\n", __func__, (rc) ? "SUCCESS" : "FAILED");
980 return rc;
981}
982
983static void free_yuv(BYTE* yuv[3])
984{
985 for (size_t x = 0; x < 3; x++)
986 {
987 free(yuv[x]);
988 yuv[x] = nullptr;
989 }
990}
991
992static BOOL allocate_yuv(BYTE* yuv[3], prim_size_t roi)
993{
994 yuv[0] = calloc(roi.width, roi.height);
995 yuv[1] = calloc(roi.width, roi.height);
996 yuv[2] = calloc(roi.width, roi.height);
997
998 if (!yuv[0] || !yuv[1] || !yuv[2])
999 {
1000 free_yuv(yuv);
1001 return FALSE;
1002 }
1003
1004 if (winpr_RAND(yuv[0], 1ULL * roi.width * roi.height) < 0)
1005 return FALSE;
1006 if (winpr_RAND(yuv[1], 1ULL * roi.width * roi.height) < 0)
1007 return FALSE;
1008 if (winpr_RAND(yuv[2], 1ULL * roi.width * roi.height) < 0)
1009 return FALSE;
1010 return TRUE;
1011}
1012
1013static BOOL yuv444_to_rgb(BYTE* rgb, size_t stride, const BYTE* yuv[3], const UINT32 yuvStep[3],
1014 prim_size_t roi)
1015{
1016 for (size_t y = 0; y < roi.height; y++)
1017 {
1018 const BYTE* yline[3] = {
1019 yuv[0] + y * roi.width,
1020 yuv[1] + y * roi.width,
1021 yuv[2] + y * roi.width,
1022 };
1023 BYTE* line = &rgb[y * stride];
1024
1025 for (size_t x = 0; x < roi.width; x++)
1026 {
1027 const BYTE Y = yline[0][x];
1028 const BYTE U = yline[1][x];
1029 const BYTE V = yline[2][x];
1030
1031 writeYUVPixel(&line[x * 4], PIXEL_FORMAT_BGRX32, Y, U, V, writePixelBGRX);
1032 }
1033 }
1034}
1035
1036/* Check the result of generic matches the optimized routine.
1037 *
1038 */
1039static BOOL compare_yuv444_to_rgb(prim_size_t roi, DWORD type)
1040{
1041 BOOL rc = FALSE;
1042 const UINT32 format = PIXEL_FORMAT_BGRA32;
1043 BYTE* yuv[3] = WINPR_C_ARRAY_INIT;
1044 const UINT32 yuvStep[3] = { roi.width, roi.width, roi.width };
1045 const size_t stride = 4ULL * roi.width;
1046
1047 primitives_t* prims = primitives_get_by_type(type);
1048 if (!prims)
1049 {
1050 printf("primitives type %" PRIu32 " not supported, skipping\n", type);
1051 return TRUE;
1052 }
1053
1054 BYTE* rgb1 = calloc(roi.height, stride);
1055 BYTE* rgb2 = calloc(roi.height, stride);
1056
1057 primitives_t* soft = primitives_get_by_type(PRIMITIVES_PURE_SOFT);
1058 if (!soft)
1059 goto fail;
1060 if (!allocate_yuv(yuv, roi) || !rgb1 || !rgb2)
1061 goto fail;
1062
1063 const BYTE* cyuv[] = { yuv[0], yuv[1], yuv[2] };
1064 if (soft->YUV444ToRGB_8u_P3AC4R(cyuv, yuvStep, rgb1, stride, format, &roi) !=
1065 PRIMITIVES_SUCCESS)
1066 goto fail;
1067 if (prims->YUV444ToRGB_8u_P3AC4R(cyuv, yuvStep, rgb2, stride, format, &roi) !=
1068 PRIMITIVES_SUCCESS)
1069 goto fail;
1070
1071 for (size_t y = 0; y < roi.height; y++)
1072 {
1073 const BYTE* yline[3] = {
1074 yuv[0] + y * roi.width,
1075 yuv[1] + y * roi.width,
1076 yuv[2] + y * roi.width,
1077 };
1078 const BYTE* line1 = &rgb1[y * stride];
1079 const BYTE* line2 = &rgb2[y * stride];
1080
1081 for (size_t x = 0; x < roi.width; x++)
1082 {
1083 const int Y = yline[0][x];
1084 const int U = yline[1][x];
1085 const int V = yline[2][x];
1086 const UINT32 color1 = FreeRDPReadColor(&line1[x * 4], format);
1087 const UINT32 color2 = FreeRDPReadColor(&line2[x * 4], format);
1088
1089 BYTE r1 = 0;
1090 BYTE g1 = 0;
1091 BYTE b1 = 0;
1092 FreeRDPSplitColor(color1, format, &r1, &g1, &b1, nullptr, nullptr);
1093
1094 BYTE r2 = 0;
1095 BYTE g2 = 0;
1096 BYTE b2 = 0;
1097 FreeRDPSplitColor(color2, format, &r2, &g2, &b2, nullptr, nullptr);
1098
1099 const int dr12 = abs(r1 - r2);
1100 const int dg12 = abs(g1 - g2);
1101 const int db12 = abs(b1 - b2);
1102
1103 if ((dr12 != 0) || (dg12 != 0) || (db12 != 0))
1104 {
1105 printf("{\n");
1106 printf("\tdiff 1/2: yuv {%d, %d, %d}, rgb {%d, %d, %d}\n", Y, U, V, dr12, dg12,
1107 db12);
1108 printf("}\n");
1109 }
1110
1111 if ((dr12 > 0) || (dg12 > 0) || (db12 > 0))
1112 {
1113 (void)fprintf(stderr,
1114 "[%" PRIuz "x%" PRIuz
1115 "] generic and optimized data mismatch: r[0x%" PRIx8 "|0x%" PRIx8
1116 "] g[0x%" PRIx8 "|0x%" PRIx8 "] b[0x%" PRIx8 "|0x%" PRIx8 "]\n",
1117 x, y, r1, r2, g1, g2, b1, b2);
1118 (void)fprintf(stderr, "roi: %dx%d\n", roi.width, roi.height);
1119 winpr_HexDump("y0", WLOG_INFO, &yline[0][x], 16);
1120 winpr_HexDump("y1", WLOG_INFO, &yline[0][x + roi.width], 16);
1121 winpr_HexDump("u0", WLOG_INFO, &yline[1][x], 16);
1122 winpr_HexDump("u1", WLOG_INFO, &yline[1][x + roi.width], 16);
1123 winpr_HexDump("v0", WLOG_INFO, &yline[2][x], 16);
1124 winpr_HexDump("v1", WLOG_INFO, &yline[2][x + roi.width], 16);
1125 winpr_HexDump("foo1", WLOG_INFO, &line1[x * 4], 16);
1126 winpr_HexDump("foo2", WLOG_INFO, &line2[x * 4], 16);
1127 goto fail;
1128 }
1129 }
1130 }
1131
1132 rc = TRUE;
1133fail:
1134 printf("%s finished with %s\n", __func__, rc ? "SUCCESS" : "FAILURE");
1135 free_yuv(yuv);
1136 free(rgb1);
1137 free(rgb2);
1138
1139 return rc;
1140}
1141
1142/* Check the result of generic matches the optimized routine.
1143 *
1144 */
1145static BOOL compare_rgb_to_yuv444(prim_size_t roi, DWORD type)
1146{
1147 BOOL rc = FALSE;
1148 const UINT32 format = PIXEL_FORMAT_BGRA32;
1149 const size_t stride = 4ULL * roi.width;
1150 const UINT32 yuvStep[] = { roi.width, roi.width, roi.width };
1151 BYTE* yuv1[3] = WINPR_C_ARRAY_INIT;
1152 BYTE* yuv2[3] = WINPR_C_ARRAY_INIT;
1153
1154 primitives_t* prims = primitives_get_by_type(type);
1155 if (!prims)
1156 {
1157 printf("primitives type %" PRIu32 " not supported, skipping\n", type);
1158 return TRUE;
1159 }
1160
1161 BYTE* rgb = calloc(roi.height, stride);
1162
1163 primitives_t* soft = primitives_get_by_type(PRIMITIVES_PURE_SOFT);
1164 if (!soft || !rgb)
1165 goto fail;
1166
1167 if (!allocate_yuv(yuv1, roi) || !allocate_yuv(yuv2, roi))
1168 goto fail;
1169
1170 if (soft->RGBToYUV444_8u_P3AC4R(rgb, format, stride, yuv1, yuvStep, &roi) != PRIMITIVES_SUCCESS)
1171 goto fail;
1172 if (prims->RGBToYUV444_8u_P3AC4R(rgb, format, stride, yuv2, yuvStep, &roi) !=
1173 PRIMITIVES_SUCCESS)
1174 goto fail;
1175
1176 for (size_t y = 0; y < roi.height; y++)
1177 {
1178 const BYTE* yline1[3] = {
1179 yuv1[0] + y * roi.width,
1180 yuv1[1] + y * roi.width,
1181 yuv1[2] + y * roi.width,
1182 };
1183 const BYTE* yline2[3] = {
1184 yuv2[0] + y * roi.width,
1185 yuv2[1] + y * roi.width,
1186 yuv2[2] + y * roi.width,
1187 };
1188
1189 for (size_t x = 0; x < ARRAYSIZE(yline1); x++)
1190 {
1191 if (memcmp(yline1[x], yline2[x], yuvStep[x]) != 0)
1192 {
1193 (void)fprintf(stderr, "[%s] compare failed in line %" PRIuz, __func__, x);
1194 goto fail;
1195 }
1196 }
1197 }
1198
1199 rc = TRUE;
1200fail:
1201 printf("%s finished with %s\n", __func__, rc ? "SUCCESS" : "FAILURE");
1202 free(rgb);
1203 free_yuv(yuv1);
1204 free_yuv(yuv2);
1205
1206 return rc;
1207}
1208
1209/* Check the result of generic matches the optimized routine.
1210 *
1211 */
1212static BOOL compare_yuv420_to_rgb(prim_size_t roi, DWORD type)
1213{
1214 BOOL rc = FALSE;
1215 const UINT32 format = PIXEL_FORMAT_BGRA32;
1216 BYTE* yuv[3] = WINPR_C_ARRAY_INIT;
1217 const UINT32 yuvStep[3] = { roi.width, roi.width / 2, roi.width / 2 };
1218 const size_t stride = 4ULL * roi.width;
1219
1220 primitives_t* prims = primitives_get_by_type(type);
1221 if (!prims)
1222 {
1223 printf("primitives type %" PRIu32 " not supported, skipping\n", type);
1224 return TRUE;
1225 }
1226
1227 BYTE* rgb1 = calloc(roi.height, stride);
1228 BYTE* rgb2 = calloc(roi.height, stride);
1229
1230 primitives_t* soft = primitives_get_by_type(PRIMITIVES_PURE_SOFT);
1231 if (!soft)
1232 goto fail;
1233 if (!allocate_yuv(yuv, roi) || !rgb1 || !rgb2)
1234 goto fail;
1235
1236 const BYTE* cyuv[3] = { yuv[0], yuv[1], yuv[2] };
1237 if (soft->YUV420ToRGB_8u_P3AC4R(cyuv, yuvStep, rgb1, stride, format, &roi) !=
1238 PRIMITIVES_SUCCESS)
1239 goto fail;
1240 if (prims->YUV420ToRGB_8u_P3AC4R(cyuv, yuvStep, rgb2, stride, format, &roi) !=
1241 PRIMITIVES_SUCCESS)
1242 goto fail;
1243
1244 for (size_t y = 0; y < roi.height; y++)
1245 {
1246 const BYTE* yline[3] = {
1247 yuv[0] + y * yuvStep[0],
1248 yuv[1] + y * yuvStep[1],
1249 yuv[2] + y * yuvStep[2],
1250 };
1251 const BYTE* line1 = &rgb1[y * stride];
1252 const BYTE* line2 = &rgb2[y * stride];
1253
1254 for (size_t x = 0; x < roi.width; x++)
1255 {
1256 const int Y = yline[0][x];
1257 const int U = yline[1][x / 2];
1258 const int V = yline[2][x / 2];
1259 const UINT32 color1 = FreeRDPReadColor(&line1[x * 4], format);
1260 const UINT32 color2 = FreeRDPReadColor(&line2[x * 4], format);
1261
1262 BYTE r1 = 0;
1263 BYTE g1 = 0;
1264 BYTE b1 = 0;
1265 FreeRDPSplitColor(color1, format, &r1, &g1, &b1, nullptr, nullptr);
1266
1267 BYTE r2 = 0;
1268 BYTE g2 = 0;
1269 BYTE b2 = 0;
1270 FreeRDPSplitColor(color2, format, &r2, &g2, &b2, nullptr, nullptr);
1271
1272 const int dr12 = abs(r1 - r2);
1273 const int dg12 = abs(g1 - g2);
1274 const int db12 = abs(b1 - b2);
1275
1276 if ((dr12 != 0) || (dg12 != 0) || (db12 != 0))
1277 {
1278 printf("{\n");
1279 printf("\tdiff 1/2: yuv {%d, %d, %d}, rgb {%d, %d, %d}\n", Y, U, V, dr12, dg12,
1280 db12);
1281 printf("}\n");
1282 }
1283
1284 if ((dr12 > 0) || (dg12 > 0) || (db12 > 0))
1285 {
1286 printf("[%s] failed: r[%" PRIx8 "|%" PRIx8 "] g[%" PRIx8 "|%" PRIx8 "] b[%" PRIx8
1287 "|%" PRIx8 "]\n",
1288 __func__, r1, r2, g1, g2, b1, b2);
1289 goto fail;
1290 }
1291 }
1292 }
1293
1294 rc = TRUE;
1295fail:
1296 printf("%s finished with %s\n", __func__, rc ? "SUCCESS" : "FAILURE");
1297 free_yuv(yuv);
1298 free(rgb1);
1299 free(rgb2);
1300
1301 return rc;
1302}
1303
1304static BOOL similarYUV(const BYTE* line1, const BYTE* line2, size_t len)
1305{
1306 for (size_t x = 0; x < len; x++)
1307 {
1308 const int a = line1[x];
1309 const int b = line2[x];
1310 const int diff = abs(a - b);
1311 return (diff < 2);
1312 }
1313}
1314
1315/* Due to optimizations the Y value might be off by +/- 1 */
1316static int similarY(const BYTE* a, const BYTE* b, size_t size, size_t type)
1317{
1318 switch (type)
1319 {
1320 case 0:
1321 case 1:
1322 case 2:
1323 for (size_t x = 0; x < size; x++)
1324 {
1325 const int ba = a[x];
1326 const int bb = b[x];
1327 const int diff = abs(ba - bb);
1328 if (diff > 2)
1329 return diff;
1330 }
1331 return 0;
1332 break;
1333 default:
1334 return memcmp(a, b, size);
1335 }
1336}
1337/* Check the result of generic matches the optimized routine.
1338 *
1339 */
1340static BOOL compare_rgb_to_yuv420(prim_size_t roi, DWORD type)
1341{
1342 BOOL rc = FALSE;
1343 const UINT32 format = PIXEL_FORMAT_BGRA32;
1344 const size_t stride = 4ULL * roi.width;
1345 const UINT32 yuvStep[] = { roi.width, roi.width / 2, roi.width / 2 };
1346 BYTE* yuv1[3] = WINPR_C_ARRAY_INIT;
1347 BYTE* yuv2[3] = WINPR_C_ARRAY_INIT;
1348
1349 primitives_t* prims = primitives_get_by_type(type);
1350 if (!prims)
1351 {
1352 printf("primitives type %" PRIu32 " not supported, skipping\n", type);
1353 return TRUE;
1354 }
1355
1356 BYTE* rgb = calloc(roi.height, stride);
1357 BYTE* rgbcopy = calloc(roi.height, stride);
1358
1359 primitives_t* soft = primitives_get_by_type(PRIMITIVES_PURE_SOFT);
1360 if (!soft || !rgb || !rgbcopy)
1361 goto fail;
1362
1363 if (winpr_RAND(rgb, roi.height * stride) < 0)
1364 goto fail;
1365 memcpy(rgbcopy, rgb, roi.height * stride);
1366
1367 if (!allocate_yuv(yuv1, roi) || !allocate_yuv(yuv2, roi))
1368 goto fail;
1369
1370 if (soft->RGBToYUV420_8u_P3AC4R(rgb, format, stride, yuv1, yuvStep, &roi) != PRIMITIVES_SUCCESS)
1371 goto fail;
1372 if (memcmp(rgb, rgbcopy, roi.height * stride) != 0)
1373 goto fail;
1374 if (prims->RGBToYUV420_8u_P3AC4R(rgb, format, stride, yuv2, yuvStep, &roi) !=
1375 PRIMITIVES_SUCCESS)
1376 goto fail;
1377
1378 for (size_t y = 0; y < roi.height; y++)
1379 {
1380 // odd lines do produce artefacts in last line, skip check
1381 if (((y + 1) >= roi.height) && ((y % 2) == 0))
1382 continue;
1383
1384 const BYTE* yline1[3] = {
1385 &yuv1[0][y * yuvStep[0]],
1386 &yuv1[1][(y / 2) * yuvStep[1]],
1387 &yuv1[2][(y / 2) * yuvStep[2]],
1388 };
1389 const BYTE* yline2[3] = {
1390 &yuv2[0][y * yuvStep[0]],
1391 &yuv2[1][(y / 2) * yuvStep[1]],
1392 &yuv2[2][(y / 2) * yuvStep[2]],
1393 };
1394
1395 for (size_t x = 0; x < ARRAYSIZE(yline1); x++)
1396 {
1397 if (similarY(yline1[x], yline2[x], yuvStep[x], x) != 0)
1398 {
1399 (void)fprintf(stderr,
1400 "[%s] compare failed in component %" PRIuz ", line %" PRIuz "\n",
1401 __func__, x, y);
1402 (void)fprintf(stderr, "[%s] roi %" PRIu32 "x%" PRIu32 "\n", __func__, roi.width,
1403 roi.height);
1404 winpr_HexDump(TAG, WLOG_WARN, yline1[x], yuvStep[x]);
1405 winpr_HexDump(TAG, WLOG_WARN, yline2[x], yuvStep[x]);
1406 winpr_HexDump(TAG, WLOG_WARN, &rgb[y * stride], stride);
1407 goto fail;
1408 }
1409 }
1410 }
1411
1412 rc = TRUE;
1413fail:
1414 printf("%s finished with %s\n", __func__, rc ? "SUCCESS" : "FAILURE");
1415 free(rgb);
1416 free(rgbcopy);
1417 free_yuv(yuv1);
1418 free_yuv(yuv2);
1419
1420 return rc;
1421}
1422
1423int TestPrimitivesYUV(int argc, char* argv[])
1424{
1425 BOOL large = (argc > 1);
1426 int rc = -1;
1427 WINPR_UNUSED(argc);
1428 WINPR_UNUSED(argv);
1429 prim_size_t roi = WINPR_C_ARRAY_INIT;
1430
1431 if (argc > 1)
1432 {
1433 BOOL reset = TRUE;
1434 char* str = argv[1];
1435 char* ptr = strchr(str, 'x');
1436 if (ptr)
1437 {
1438 *ptr++ = '\0';
1439
1440 errno = 0;
1441 roi.width = strtoul(str, nullptr, 0);
1442 if (errno == 0)
1443 roi.height = strtoul(str, nullptr, 0);
1444 reset = errno != 0;
1445 }
1446
1447 if (reset)
1448 {
1449 roi.width = 1920;
1450 roi.height = 1080;
1451 }
1452 }
1453 else
1454 get_size(large, &roi.width, &roi.height);
1455
1456 prim_test_setup(FALSE);
1457
1458 for (UINT32 type = PRIMITIVES_PURE_SOFT; type <= PRIMITIVES_AUTODETECT; type++)
1459 {
1460 if (!compare_yuv444_to_rgb(roi, type))
1461 goto end;
1462 if (!compare_rgb_to_yuv444(roi, type))
1463 goto end;
1464
1465 if (!compare_yuv420_to_rgb(roi, type))
1466 goto end;
1467 if (!compare_rgb_to_yuv420(roi, type))
1468 goto end;
1469 }
1470
1471 if (!run_tests(roi))
1472 goto end;
1473
1474 rc = 0;
1475end:
1476 printf("[%s] finished, status %s [%d]\n", __func__, (rc == 0) ? "SUCCESS" : "FAILURE", rc);
1477 return rc;
1478}