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_calloc(1, 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 return TRUE;
1035}
1036
1037/* Check the result of generic matches the optimized routine.
1038 *
1039 */
1040static BOOL compare_yuv444_to_rgb(prim_size_t roi, DWORD type)
1041{
1042 BOOL rc = FALSE;
1043 const UINT32 format = PIXEL_FORMAT_BGRA32;
1044 BYTE* yuv[3] = WINPR_C_ARRAY_INIT;
1045 const UINT32 yuvStep[3] = { roi.width, roi.width, roi.width };
1046 const size_t stride = 4ULL * roi.width;
1047
1048 primitives_t* prims = primitives_get_by_type(type);
1049 if (!prims)
1050 {
1051 printf("primitives type %" PRIu32 " not supported, skipping\n", type);
1052 return TRUE;
1053 }
1054
1055 BYTE* rgb1 = calloc(roi.height, stride);
1056 BYTE* rgb2 = calloc(roi.height, stride);
1057
1058 primitives_t* soft = primitives_get_by_type(PRIMITIVES_PURE_SOFT);
1059 if (!soft)
1060 goto fail;
1061 if (!allocate_yuv(yuv, roi) || !rgb1 || !rgb2)
1062 goto fail;
1063
1064 const BYTE* cyuv[] = { yuv[0], yuv[1], yuv[2] };
1065 if (soft->YUV444ToRGB_8u_P3AC4R(cyuv, yuvStep, rgb1, stride, format, &roi) !=
1066 PRIMITIVES_SUCCESS)
1067 goto fail;
1068 if (prims->YUV444ToRGB_8u_P3AC4R(cyuv, yuvStep, rgb2, stride, format, &roi) !=
1069 PRIMITIVES_SUCCESS)
1070 goto fail;
1071
1072 for (size_t y = 0; y < roi.height; y++)
1073 {
1074 const BYTE* yline[3] = {
1075 yuv[0] + y * roi.width,
1076 yuv[1] + y * roi.width,
1077 yuv[2] + y * roi.width,
1078 };
1079 const BYTE* line1 = &rgb1[y * stride];
1080 const BYTE* line2 = &rgb2[y * stride];
1081
1082 for (size_t x = 0; x < roi.width; x++)
1083 {
1084 const int Y = yline[0][x];
1085 const int U = yline[1][x];
1086 const int V = yline[2][x];
1087 const UINT32 color1 = FreeRDPReadColor(&line1[x * 4], format);
1088 const UINT32 color2 = FreeRDPReadColor(&line2[x * 4], format);
1089
1090 BYTE r1 = 0;
1091 BYTE g1 = 0;
1092 BYTE b1 = 0;
1093 FreeRDPSplitColor(color1, format, &r1, &g1, &b1, nullptr, nullptr);
1094
1095 BYTE r2 = 0;
1096 BYTE g2 = 0;
1097 BYTE b2 = 0;
1098 FreeRDPSplitColor(color2, format, &r2, &g2, &b2, nullptr, nullptr);
1099
1100 const int dr12 = abs(r1 - r2);
1101 const int dg12 = abs(g1 - g2);
1102 const int db12 = abs(b1 - b2);
1103
1104 if ((dr12 != 0) || (dg12 != 0) || (db12 != 0))
1105 {
1106 printf("{\n");
1107 printf("\tdiff 1/2: yuv {%d, %d, %d}, rgb {%d, %d, %d}\n", Y, U, V, dr12, dg12,
1108 db12);
1109 printf("}\n");
1110 }
1111
1112 if ((dr12 > 0) || (dg12 > 0) || (db12 > 0))
1113 {
1114 (void)fprintf(stderr,
1115 "[%" PRIuz "x%" PRIuz
1116 "] generic and optimized data mismatch: r[0x%" PRIx8 "|0x%" PRIx8
1117 "] g[0x%" PRIx8 "|0x%" PRIx8 "] b[0x%" PRIx8 "|0x%" PRIx8 "]\n",
1118 x, y, r1, r2, g1, g2, b1, b2);
1119 (void)fprintf(stderr, "roi: %dx%d\n", roi.width, roi.height);
1120 winpr_HexDump("y0", WLOG_INFO, &yline[0][x], 16);
1121 winpr_HexDump("y1", WLOG_INFO, &yline[0][x + roi.width], 16);
1122 winpr_HexDump("u0", WLOG_INFO, &yline[1][x], 16);
1123 winpr_HexDump("u1", WLOG_INFO, &yline[1][x + roi.width], 16);
1124 winpr_HexDump("v0", WLOG_INFO, &yline[2][x], 16);
1125 winpr_HexDump("v1", WLOG_INFO, &yline[2][x + roi.width], 16);
1126 winpr_HexDump("foo1", WLOG_INFO, &line1[x * 4], 16);
1127 winpr_HexDump("foo2", WLOG_INFO, &line2[x * 4], 16);
1128 goto fail;
1129 }
1130 }
1131 }
1132
1133 rc = TRUE;
1134fail:
1135 printf("%s finished with %s\n", __func__, rc ? "SUCCESS" : "FAILURE");
1136 free_yuv(yuv);
1137 free(rgb1);
1138 free(rgb2);
1139
1140 return rc;
1141}
1142
1143/* Check the result of generic matches the optimized routine.
1144 *
1145 */
1146static BOOL compare_rgb_to_yuv444(prim_size_t roi, DWORD type)
1147{
1148 BOOL rc = FALSE;
1149 const UINT32 format = PIXEL_FORMAT_BGRA32;
1150 const size_t stride = 4ULL * roi.width;
1151 const UINT32 yuvStep[] = { roi.width, roi.width, roi.width };
1152 BYTE* yuv1[3] = WINPR_C_ARRAY_INIT;
1153 BYTE* yuv2[3] = WINPR_C_ARRAY_INIT;
1154
1155 primitives_t* prims = primitives_get_by_type(type);
1156 if (!prims)
1157 {
1158 printf("primitives type %" PRIu32 " not supported, skipping\n", type);
1159 return TRUE;
1160 }
1161
1162 BYTE* rgb = calloc(roi.height, stride);
1163
1164 primitives_t* soft = primitives_get_by_type(PRIMITIVES_PURE_SOFT);
1165 if (!soft || !rgb)
1166 goto fail;
1167
1168 if (!allocate_yuv(yuv1, roi) || !allocate_yuv(yuv2, roi))
1169 goto fail;
1170
1171 if (soft->RGBToYUV444_8u_P3AC4R(rgb, format, stride, yuv1, yuvStep, &roi) != PRIMITIVES_SUCCESS)
1172 goto fail;
1173 if (prims->RGBToYUV444_8u_P3AC4R(rgb, format, stride, yuv2, yuvStep, &roi) !=
1174 PRIMITIVES_SUCCESS)
1175 goto fail;
1176
1177 for (size_t y = 0; y < roi.height; y++)
1178 {
1179 const BYTE* yline1[3] = {
1180 yuv1[0] + y * roi.width,
1181 yuv1[1] + y * roi.width,
1182 yuv1[2] + y * roi.width,
1183 };
1184 const BYTE* yline2[3] = {
1185 yuv2[0] + y * roi.width,
1186 yuv2[1] + y * roi.width,
1187 yuv2[2] + y * roi.width,
1188 };
1189
1190 for (size_t x = 0; x < ARRAYSIZE(yline1); x++)
1191 {
1192 if (memcmp(yline1[x], yline2[x], yuvStep[x]) != 0)
1193 {
1194 (void)fprintf(stderr, "[%s] compare failed in line %" PRIuz, __func__, x);
1195 goto fail;
1196 }
1197 }
1198 }
1199
1200 rc = TRUE;
1201fail:
1202 printf("%s finished with %s\n", __func__, rc ? "SUCCESS" : "FAILURE");
1203 free(rgb);
1204 free_yuv(yuv1);
1205 free_yuv(yuv2);
1206
1207 return rc;
1208}
1209
1210/* Check the result of generic matches the optimized routine.
1211 *
1212 */
1213static BOOL compare_yuv420_to_rgb(prim_size_t roi, DWORD type)
1214{
1215 BOOL rc = FALSE;
1216 const UINT32 format = PIXEL_FORMAT_BGRA32;
1217 BYTE* yuv[3] = WINPR_C_ARRAY_INIT;
1218 const UINT32 yuvStep[3] = { roi.width, roi.width / 2, roi.width / 2 };
1219 const size_t stride = 4ULL * roi.width;
1220
1221 primitives_t* prims = primitives_get_by_type(type);
1222 if (!prims)
1223 {
1224 printf("primitives type %" PRIu32 " not supported, skipping\n", type);
1225 return TRUE;
1226 }
1227
1228 BYTE* rgb1 = calloc(roi.height, stride);
1229 BYTE* rgb2 = calloc(roi.height, stride);
1230
1231 primitives_t* soft = primitives_get_by_type(PRIMITIVES_PURE_SOFT);
1232 if (!soft)
1233 goto fail;
1234 if (!allocate_yuv(yuv, roi) || !rgb1 || !rgb2)
1235 goto fail;
1236
1237 const BYTE* cyuv[3] = { yuv[0], yuv[1], yuv[2] };
1238 if (soft->YUV420ToRGB_8u_P3AC4R(cyuv, yuvStep, rgb1, stride, format, &roi) !=
1239 PRIMITIVES_SUCCESS)
1240 goto fail;
1241 if (prims->YUV420ToRGB_8u_P3AC4R(cyuv, yuvStep, rgb2, stride, format, &roi) !=
1242 PRIMITIVES_SUCCESS)
1243 goto fail;
1244
1245 for (size_t y = 0; y < roi.height; y++)
1246 {
1247 const BYTE* yline[3] = {
1248 yuv[0] + y * yuvStep[0],
1249 yuv[1] + y * yuvStep[1],
1250 yuv[2] + y * yuvStep[2],
1251 };
1252 const BYTE* line1 = &rgb1[y * stride];
1253 const BYTE* line2 = &rgb2[y * stride];
1254
1255 for (size_t x = 0; x < roi.width; x++)
1256 {
1257 const int Y = yline[0][x];
1258 const int U = yline[1][x / 2];
1259 const int V = yline[2][x / 2];
1260 const UINT32 color1 = FreeRDPReadColor(&line1[x * 4], format);
1261 const UINT32 color2 = FreeRDPReadColor(&line2[x * 4], format);
1262
1263 BYTE r1 = 0;
1264 BYTE g1 = 0;
1265 BYTE b1 = 0;
1266 FreeRDPSplitColor(color1, format, &r1, &g1, &b1, nullptr, nullptr);
1267
1268 BYTE r2 = 0;
1269 BYTE g2 = 0;
1270 BYTE b2 = 0;
1271 FreeRDPSplitColor(color2, format, &r2, &g2, &b2, nullptr, nullptr);
1272
1273 const int dr12 = abs(r1 - r2);
1274 const int dg12 = abs(g1 - g2);
1275 const int db12 = abs(b1 - b2);
1276
1277 if ((dr12 != 0) || (dg12 != 0) || (db12 != 0))
1278 {
1279 printf("{\n");
1280 printf("\tdiff 1/2: yuv {%d, %d, %d}, rgb {%d, %d, %d}\n", Y, U, V, dr12, dg12,
1281 db12);
1282 printf("}\n");
1283 }
1284
1285 if ((dr12 > 0) || (dg12 > 0) || (db12 > 0))
1286 {
1287 printf("[%s] failed: r[%" PRIx8 "|%" PRIx8 "] g[%" PRIx8 "|%" PRIx8 "] b[%" PRIx8
1288 "|%" PRIx8 "]\n",
1289 __func__, r1, r2, g1, g2, b1, b2);
1290 goto fail;
1291 }
1292 }
1293 }
1294
1295 rc = TRUE;
1296fail:
1297 printf("%s finished with %s\n", __func__, rc ? "SUCCESS" : "FAILURE");
1298 free_yuv(yuv);
1299 free(rgb1);
1300 free(rgb2);
1301
1302 return rc;
1303}
1304
1305static BOOL similarYUV(const BYTE* line1, const BYTE* line2, size_t len)
1306{
1307 for (size_t x = 0; x < len; x++)
1308 {
1309 const int a = line1[x];
1310 const int b = line2[x];
1311 const int diff = abs(a - b);
1312 if (diff >= 2)
1313 return FALSE;
1314 }
1315 return TRUE;
1316}
1317
1318/* Due to optimizations the Y value might be off by +/- 1 */
1319static int similarY(const BYTE* a, const BYTE* b, size_t size, size_t type)
1320{
1321 switch (type)
1322 {
1323 case 0:
1324 case 1:
1325 case 2:
1326 for (size_t x = 0; x < size; x++)
1327 {
1328 const int ba = a[x];
1329 const int bb = b[x];
1330 const int diff = abs(ba - bb);
1331 if (diff > 2)
1332 return diff;
1333 }
1334 return 0;
1335 break;
1336 default:
1337 return memcmp(a, b, size);
1338 }
1339}
1340/* Check the result of generic matches the optimized routine.
1341 *
1342 */
1343static BOOL compare_rgb_to_yuv420(prim_size_t roi, DWORD type)
1344{
1345 BOOL rc = FALSE;
1346 const UINT32 format = PIXEL_FORMAT_BGRA32;
1347 const size_t stride = 4ULL * roi.width;
1348 const UINT32 yuvStep[] = { roi.width, roi.width / 2, roi.width / 2 };
1349 BYTE* yuv1[3] = WINPR_C_ARRAY_INIT;
1350 BYTE* yuv2[3] = WINPR_C_ARRAY_INIT;
1351
1352 primitives_t* prims = primitives_get_by_type(type);
1353 if (!prims)
1354 {
1355 printf("primitives type %" PRIu32 " not supported, skipping\n", type);
1356 return TRUE;
1357 }
1358
1359 BYTE* rgb = calloc(roi.height, stride);
1360 BYTE* rgbcopy = calloc(roi.height, stride);
1361
1362 primitives_t* soft = primitives_get_by_type(PRIMITIVES_PURE_SOFT);
1363 if (!soft || !rgb || !rgbcopy)
1364 goto fail;
1365
1366 if (winpr_RAND(rgb, roi.height * stride) < 0)
1367 goto fail;
1368 memcpy(rgbcopy, rgb, roi.height * stride);
1369
1370 if (!allocate_yuv(yuv1, roi) || !allocate_yuv(yuv2, roi))
1371 goto fail;
1372
1373 if (soft->RGBToYUV420_8u_P3AC4R(rgb, format, stride, yuv1, yuvStep, &roi) != PRIMITIVES_SUCCESS)
1374 goto fail;
1375 if (memcmp(rgb, rgbcopy, roi.height * stride) != 0)
1376 goto fail;
1377 if (prims->RGBToYUV420_8u_P3AC4R(rgb, format, stride, yuv2, yuvStep, &roi) !=
1378 PRIMITIVES_SUCCESS)
1379 goto fail;
1380
1381 for (size_t y = 0; y < roi.height; y++)
1382 {
1383 // odd lines do produce artefacts in last line, skip check
1384 if (((y + 1) >= roi.height) && ((y % 2) == 0))
1385 continue;
1386
1387 const BYTE* yline1[3] = {
1388 &yuv1[0][y * yuvStep[0]],
1389 &yuv1[1][(y / 2) * yuvStep[1]],
1390 &yuv1[2][(y / 2) * yuvStep[2]],
1391 };
1392 const BYTE* yline2[3] = {
1393 &yuv2[0][y * yuvStep[0]],
1394 &yuv2[1][(y / 2) * yuvStep[1]],
1395 &yuv2[2][(y / 2) * yuvStep[2]],
1396 };
1397
1398 for (size_t x = 0; x < ARRAYSIZE(yline1); x++)
1399 {
1400 if (similarY(yline1[x], yline2[x], yuvStep[x], x) != 0)
1401 {
1402 (void)fprintf(stderr,
1403 "[%s] compare failed in component %" PRIuz ", line %" PRIuz "\n",
1404 __func__, x, y);
1405 (void)fprintf(stderr, "[%s] roi %" PRIu32 "x%" PRIu32 "\n", __func__, roi.width,
1406 roi.height);
1407 winpr_HexDump(TAG, WLOG_WARN, yline1[x], yuvStep[x]);
1408 winpr_HexDump(TAG, WLOG_WARN, yline2[x], yuvStep[x]);
1409 winpr_HexDump(TAG, WLOG_WARN, &rgb[y * stride], stride);
1410 goto fail;
1411 }
1412 }
1413 }
1414
1415 rc = TRUE;
1416fail:
1417 printf("%s finished with %s\n", __func__, rc ? "SUCCESS" : "FAILURE");
1418 free(rgb);
1419 free(rgbcopy);
1420 free_yuv(yuv1);
1421 free_yuv(yuv2);
1422
1423 return rc;
1424}
1425
1426int TestPrimitivesYUV(int argc, char* argv[])
1427{
1428 BOOL large = (argc > 1);
1429 int rc = -1;
1430 WINPR_UNUSED(argc);
1431 WINPR_UNUSED(argv);
1432 prim_size_t roi = WINPR_C_ARRAY_INIT;
1433
1434 if (argc > 1)
1435 {
1436 BOOL reset = TRUE;
1437 char* str = argv[1];
1438 char* ptr = strchr(str, 'x');
1439 if (ptr)
1440 {
1441 *ptr++ = '\0';
1442
1443 errno = 0;
1444 roi.width = strtoul(str, nullptr, 0);
1445 if (errno == 0)
1446 roi.height = strtoul(str, nullptr, 0);
1447 reset = errno != 0;
1448 }
1449
1450 if (reset)
1451 {
1452 roi.width = 1920;
1453 roi.height = 1080;
1454 }
1455 }
1456 else
1457 {
1458 if (!get_size(large, &roi.width, &roi.height))
1459 goto end;
1460 }
1461
1462 prim_test_setup(FALSE);
1463
1464 for (UINT32 type = PRIMITIVES_PURE_SOFT; type <= PRIMITIVES_AUTODETECT; type++)
1465 {
1466 if (!compare_yuv444_to_rgb(roi, type))
1467 goto end;
1468 if (!compare_rgb_to_yuv444(roi, type))
1469 goto end;
1470
1471 if (!compare_yuv420_to_rgb(roi, type))
1472 goto end;
1473 if (!compare_rgb_to_yuv420(roi, type))
1474 goto end;
1475 }
1476
1477 if (!run_tests(roi))
1478 goto end;
1479
1480 rc = 0;
1481end:
1482 printf("[%s] finished, status %s [%d]\n", __func__, (rc == 0) ? "SUCCESS" : "FAILURE", rc);
1483 return rc;
1484}