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 I444ToRGB_8u converts every pixel on its own (no AVC444 chroma
1144 * filter is reversed) and does not write past the end of a line.
1145 */
1146static BOOL compare_i444_to_rgb(prim_size_t roi, DWORD type)
1147{
1148 BOOL rc = FALSE;
1149 const UINT32 format = PIXEL_FORMAT_BGRX32;
1150 BYTE* yuv[3] = WINPR_C_ARRAY_INIT;
1151 const UINT32 yuvStep[3] = { roi.width, roi.width, roi.width };
1152 const size_t padding = 64;
1153 const size_t stride = 4ULL * roi.width + padding;
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* expected = calloc(roi.height, stride);
1163 BYTE* rgb = calloc(roi.height, stride);
1164 if (!expected || !rgb || !prims->I444ToRGB_8u)
1165 goto fail;
1166 if (!allocate_yuv(yuv, roi))
1167 goto fail;
1168
1169 memset(expected, PADDING_FILL_VALUE, roi.height * stride);
1170 memset(rgb, PADDING_FILL_VALUE, roi.height * stride);
1171
1172 const BYTE* cyuv[] = { yuv[0], yuv[1], yuv[2] };
1173 if (!yuv444_to_rgb(expected, stride, cyuv, yuvStep, roi))
1174 goto fail;
1175 if (prims->I444ToRGB_8u(cyuv, yuvStep, rgb, stride, format, &roi) != PRIMITIVES_SUCCESS)
1176 goto fail;
1177
1178 for (size_t y = 0; y < roi.height; y++)
1179 {
1180 if (memcmp(&expected[y * stride], &rgb[y * stride], stride) != 0)
1181 {
1182 (void)fprintf(stderr, "[%s] [%" PRIu32 "x%" PRIu32 "] mismatch in line %" PRIuz "\n",
1183 __func__, roi.width, roi.height, y);
1184 goto fail;
1185 }
1186 }
1187
1188 rc = TRUE;
1189fail:
1190 printf("%s [%" PRIu32 "x%" PRIu32 "] finished with %s\n", __func__, roi.width, roi.height,
1191 rc ? "SUCCESS" : "FAILURE");
1192 free_yuv(yuv);
1193 free(expected);
1194 free(rgb);
1195
1196 return rc;
1197}
1198
1199/* Check the result of generic matches the optimized routine.
1200 *
1201 */
1202static BOOL compare_rgb_to_yuv444(prim_size_t roi, DWORD type)
1203{
1204 BOOL rc = FALSE;
1205 const UINT32 format = PIXEL_FORMAT_BGRA32;
1206 const size_t stride = 4ULL * roi.width;
1207 const UINT32 yuvStep[] = { roi.width, roi.width, roi.width };
1208 BYTE* yuv1[3] = WINPR_C_ARRAY_INIT;
1209 BYTE* yuv2[3] = WINPR_C_ARRAY_INIT;
1210
1211 primitives_t* prims = primitives_get_by_type(type);
1212 if (!prims)
1213 {
1214 printf("primitives type %" PRIu32 " not supported, skipping\n", type);
1215 return TRUE;
1216 }
1217
1218 BYTE* rgb = calloc(roi.height, stride);
1219
1220 primitives_t* soft = primitives_get_by_type(PRIMITIVES_PURE_SOFT);
1221 if (!soft || !rgb)
1222 goto fail;
1223
1224 if (!allocate_yuv(yuv1, roi) || !allocate_yuv(yuv2, roi))
1225 goto fail;
1226
1227 if (soft->RGBToYUV444_8u_P3AC4R(rgb, format, stride, yuv1, yuvStep, &roi) != PRIMITIVES_SUCCESS)
1228 goto fail;
1229 if (prims->RGBToYUV444_8u_P3AC4R(rgb, format, stride, yuv2, yuvStep, &roi) !=
1230 PRIMITIVES_SUCCESS)
1231 goto fail;
1232
1233 for (size_t y = 0; y < roi.height; y++)
1234 {
1235 const BYTE* yline1[3] = {
1236 yuv1[0] + y * roi.width,
1237 yuv1[1] + y * roi.width,
1238 yuv1[2] + y * roi.width,
1239 };
1240 const BYTE* yline2[3] = {
1241 yuv2[0] + y * roi.width,
1242 yuv2[1] + y * roi.width,
1243 yuv2[2] + y * roi.width,
1244 };
1245
1246 for (size_t x = 0; x < ARRAYSIZE(yline1); x++)
1247 {
1248 if (memcmp(yline1[x], yline2[x], yuvStep[x]) != 0)
1249 {
1250 (void)fprintf(stderr, "[%s] compare failed in line %" PRIuz, __func__, x);
1251 goto fail;
1252 }
1253 }
1254 }
1255
1256 rc = TRUE;
1257fail:
1258 printf("%s finished with %s\n", __func__, rc ? "SUCCESS" : "FAILURE");
1259 free(rgb);
1260 free_yuv(yuv1);
1261 free_yuv(yuv2);
1262
1263 return rc;
1264}
1265
1266/* Check the result of generic matches the optimized routine.
1267 *
1268 */
1269static BOOL compare_yuv420_to_rgb(prim_size_t roi, DWORD type)
1270{
1271 BOOL rc = FALSE;
1272 const UINT32 format = PIXEL_FORMAT_BGRA32;
1273 BYTE* yuv[3] = WINPR_C_ARRAY_INIT;
1274 const UINT32 yuvStep[3] = { roi.width, roi.width / 2, roi.width / 2 };
1275 const size_t stride = 4ULL * roi.width;
1276
1277 primitives_t* prims = primitives_get_by_type(type);
1278 if (!prims)
1279 {
1280 printf("primitives type %" PRIu32 " not supported, skipping\n", type);
1281 return TRUE;
1282 }
1283
1284 BYTE* rgb1 = calloc(roi.height, stride);
1285 BYTE* rgb2 = calloc(roi.height, stride);
1286
1287 primitives_t* soft = primitives_get_by_type(PRIMITIVES_PURE_SOFT);
1288 if (!soft)
1289 goto fail;
1290 if (!allocate_yuv(yuv, roi) || !rgb1 || !rgb2)
1291 goto fail;
1292
1293 const BYTE* cyuv[3] = { yuv[0], yuv[1], yuv[2] };
1294 if (soft->YUV420ToRGB_8u_P3AC4R(cyuv, yuvStep, rgb1, stride, format, &roi) !=
1295 PRIMITIVES_SUCCESS)
1296 goto fail;
1297 if (prims->YUV420ToRGB_8u_P3AC4R(cyuv, yuvStep, rgb2, stride, format, &roi) !=
1298 PRIMITIVES_SUCCESS)
1299 goto fail;
1300
1301 for (size_t y = 0; y < roi.height; y++)
1302 {
1303 const BYTE* yline[3] = {
1304 yuv[0] + y * yuvStep[0],
1305 yuv[1] + y * yuvStep[1],
1306 yuv[2] + y * yuvStep[2],
1307 };
1308 const BYTE* line1 = &rgb1[y * stride];
1309 const BYTE* line2 = &rgb2[y * stride];
1310
1311 for (size_t x = 0; x < roi.width; x++)
1312 {
1313 const int Y = yline[0][x];
1314 const int U = yline[1][x / 2];
1315 const int V = yline[2][x / 2];
1316 const UINT32 color1 = FreeRDPReadColor(&line1[x * 4], format);
1317 const UINT32 color2 = FreeRDPReadColor(&line2[x * 4], format);
1318
1319 BYTE r1 = 0;
1320 BYTE g1 = 0;
1321 BYTE b1 = 0;
1322 FreeRDPSplitColor(color1, format, &r1, &g1, &b1, nullptr, nullptr);
1323
1324 BYTE r2 = 0;
1325 BYTE g2 = 0;
1326 BYTE b2 = 0;
1327 FreeRDPSplitColor(color2, format, &r2, &g2, &b2, nullptr, nullptr);
1328
1329 const int dr12 = abs(r1 - r2);
1330 const int dg12 = abs(g1 - g2);
1331 const int db12 = abs(b1 - b2);
1332
1333 if ((dr12 != 0) || (dg12 != 0) || (db12 != 0))
1334 {
1335 printf("{\n");
1336 printf("\tdiff 1/2: yuv {%d, %d, %d}, rgb {%d, %d, %d}\n", Y, U, V, dr12, dg12,
1337 db12);
1338 printf("}\n");
1339 }
1340
1341 if ((dr12 > 0) || (dg12 > 0) || (db12 > 0))
1342 {
1343 printf("[%s] failed: r[%" PRIx8 "|%" PRIx8 "] g[%" PRIx8 "|%" PRIx8 "] b[%" PRIx8
1344 "|%" PRIx8 "]\n",
1345 __func__, r1, r2, g1, g2, b1, b2);
1346 goto fail;
1347 }
1348 }
1349 }
1350
1351 rc = TRUE;
1352fail:
1353 printf("%s finished with %s\n", __func__, rc ? "SUCCESS" : "FAILURE");
1354 free_yuv(yuv);
1355 free(rgb1);
1356 free(rgb2);
1357
1358 return rc;
1359}
1360
1361static BOOL similarYUV(const BYTE* line1, const BYTE* line2, size_t len)
1362{
1363 for (size_t x = 0; x < len; x++)
1364 {
1365 const int a = line1[x];
1366 const int b = line2[x];
1367 const int diff = abs(a - b);
1368 if (diff >= 2)
1369 return FALSE;
1370 }
1371 return TRUE;
1372}
1373
1374/* Due to optimizations the Y value might be off by +/- 1 */
1375static int similarY(const BYTE* a, const BYTE* b, size_t size, size_t type)
1376{
1377 switch (type)
1378 {
1379 case 0:
1380 case 1:
1381 case 2:
1382 for (size_t x = 0; x < size; x++)
1383 {
1384 const int ba = a[x];
1385 const int bb = b[x];
1386 const int diff = abs(ba - bb);
1387 if (diff > 2)
1388 return diff;
1389 }
1390 return 0;
1391 break;
1392 default:
1393 return memcmp(a, b, size);
1394 }
1395}
1396/* Check the result of generic matches the optimized routine.
1397 *
1398 */
1399static BOOL compare_rgb_to_yuv420(prim_size_t roi, DWORD type)
1400{
1401 BOOL rc = FALSE;
1402 const UINT32 format = PIXEL_FORMAT_BGRA32;
1403 const size_t stride = 4ULL * roi.width;
1404 const UINT32 yuvStep[] = { roi.width, roi.width / 2, roi.width / 2 };
1405 BYTE* yuv1[3] = WINPR_C_ARRAY_INIT;
1406 BYTE* yuv2[3] = WINPR_C_ARRAY_INIT;
1407
1408 primitives_t* prims = primitives_get_by_type(type);
1409 if (!prims)
1410 {
1411 printf("primitives type %" PRIu32 " not supported, skipping\n", type);
1412 return TRUE;
1413 }
1414
1415 BYTE* rgb = calloc(roi.height, stride);
1416 BYTE* rgbcopy = calloc(roi.height, stride);
1417
1418 primitives_t* soft = primitives_get_by_type(PRIMITIVES_PURE_SOFT);
1419 if (!soft || !rgb || !rgbcopy)
1420 goto fail;
1421
1422 if (winpr_RAND(rgb, roi.height * stride) < 0)
1423 goto fail;
1424 memcpy(rgbcopy, rgb, roi.height * stride);
1425
1426 if (!allocate_yuv(yuv1, roi) || !allocate_yuv(yuv2, roi))
1427 goto fail;
1428
1429 if (soft->RGBToYUV420_8u_P3AC4R(rgb, format, stride, yuv1, yuvStep, &roi) != PRIMITIVES_SUCCESS)
1430 goto fail;
1431 if (memcmp(rgb, rgbcopy, roi.height * stride) != 0)
1432 goto fail;
1433 if (prims->RGBToYUV420_8u_P3AC4R(rgb, format, stride, yuv2, yuvStep, &roi) !=
1434 PRIMITIVES_SUCCESS)
1435 goto fail;
1436
1437 for (size_t y = 0; y < roi.height; y++)
1438 {
1439 // odd lines do produce artefacts in last line, skip check
1440 if (((y + 1) >= roi.height) && ((y % 2) == 0))
1441 continue;
1442
1443 const BYTE* yline1[3] = {
1444 &yuv1[0][y * yuvStep[0]],
1445 &yuv1[1][(y / 2) * yuvStep[1]],
1446 &yuv1[2][(y / 2) * yuvStep[2]],
1447 };
1448 const BYTE* yline2[3] = {
1449 &yuv2[0][y * yuvStep[0]],
1450 &yuv2[1][(y / 2) * yuvStep[1]],
1451 &yuv2[2][(y / 2) * yuvStep[2]],
1452 };
1453
1454 for (size_t x = 0; x < ARRAYSIZE(yline1); x++)
1455 {
1456 if (similarY(yline1[x], yline2[x], yuvStep[x], x) != 0)
1457 {
1458 (void)fprintf(stderr,
1459 "[%s] compare failed in component %" PRIuz ", line %" PRIuz "\n",
1460 __func__, x, y);
1461 (void)fprintf(stderr, "[%s] roi %" PRIu32 "x%" PRIu32 "\n", __func__, roi.width,
1462 roi.height);
1463 winpr_HexDump(TAG, WLOG_WARN, yline1[x], yuvStep[x]);
1464 winpr_HexDump(TAG, WLOG_WARN, yline2[x], yuvStep[x]);
1465 winpr_HexDump(TAG, WLOG_WARN, &rgb[y * stride], stride);
1466 goto fail;
1467 }
1468 }
1469 }
1470
1471 rc = TRUE;
1472fail:
1473 printf("%s finished with %s\n", __func__, rc ? "SUCCESS" : "FAILURE");
1474 free(rgb);
1475 free(rgbcopy);
1476 free_yuv(yuv1);
1477 free_yuv(yuv2);
1478
1479 return rc;
1480}
1481
1482/* Check the result of generic matches the optimized routine for the AVC444
1483 * combine: a luma (main) view followed by a v1 or v2 chroma (auxiliary) view,
1484 * on random data. Constant input planes cannot tell a misplaced column from a
1485 * correct one. */
1486static BOOL compare_yuv420_combine_to_yuv444(prim_size_t roi, DWORD type, avc444_frame_type chroma)
1487{
1488 BOOL rc = FALSE;
1489 /* Even sizes only: for an odd height the generic and SSE v1 paths still
1490 * disagree on the last row (nHeight / 2 against (nHeight + 1) / 2). */
1491 roi.width = MAX(roi.width & ~1u, 2u);
1492 roi.height = MAX(roi.height & ~1u, 2u);
1493 const UINT32 awidth = (roi.width + 31) & ~31u;
1494 const UINT32 aheight = (roi.height + 15) & ~15u;
1495 const UINT32 srcStep[3] = { awidth, awidth / 2, awidth / 2 };
1496 const UINT32 dstStep[3] = { awidth, awidth, awidth };
1497 const RECTANGLE_16 rect = { 0, 0, WINPR_ASSERTING_INT_CAST(UINT16, roi.width),
1498 WINPR_ASSERTING_INT_CAST(UINT16, roi.height) };
1499 BYTE* luma[3] = WINPR_C_ARRAY_INIT;
1500 BYTE* aux[3] = WINPR_C_ARRAY_INIT;
1501 BYTE* dst1[3] = WINPR_C_ARRAY_INIT;
1502 BYTE* dst2[3] = WINPR_C_ARRAY_INIT;
1503
1504 primitives_t* prims = primitives_get_by_type(type);
1505 if (!prims)
1506 {
1507 printf("primitives type %" PRIu32 " not supported, skipping\n", type);
1508 return TRUE;
1509 }
1510
1511 primitives_t* soft = primitives_get_by_type(PRIMITIVES_PURE_SOFT);
1512 if (!soft)
1513 goto fail;
1514
1515 for (size_t x = 0; x < 3; x++)
1516 {
1517 const size_t srcSize = 1ULL * srcStep[x] * ((x > 0) ? aheight / 2 : aheight);
1518 luma[x] = calloc(srcSize, 1);
1519 aux[x] = calloc(srcSize, 1);
1520 dst1[x] = calloc(dstStep[x], aheight);
1521 dst2[x] = calloc(dstStep[x], aheight);
1522 if (!luma[x] || !aux[x] || !dst1[x] || !dst2[x])
1523 goto fail;
1524 if (winpr_RAND(luma[x], srcSize) < 0)
1525 goto fail;
1526 if (winpr_RAND(aux[x], srcSize) < 0)
1527 goto fail;
1528 }
1529
1530 {
1531 const BYTE* cluma[] = { luma[0], luma[1], luma[2] };
1532 const BYTE* caux[] = { aux[0], aux[1], aux[2] };
1533
1534 if (soft->YUV420CombineToYUV444(AVC444_LUMA, cluma, srcStep, awidth, aheight, dst1, dstStep,
1535 &rect) != PRIMITIVES_SUCCESS)
1536 goto fail;
1537 if (soft->YUV420CombineToYUV444(chroma, caux, srcStep, awidth, aheight, dst1, dstStep,
1538 &rect) != PRIMITIVES_SUCCESS)
1539 goto fail;
1540 if (prims->YUV420CombineToYUV444(AVC444_LUMA, cluma, srcStep, awidth, aheight, dst2,
1541 dstStep, &rect) != PRIMITIVES_SUCCESS)
1542 goto fail;
1543 if (prims->YUV420CombineToYUV444(chroma, caux, srcStep, awidth, aheight, dst2, dstStep,
1544 &rect) != PRIMITIVES_SUCCESS)
1545 goto fail;
1546 }
1547
1548 for (size_t x = 0; x < 3; x++)
1549 {
1550 for (size_t y = 0; y < roi.height; y++)
1551 {
1552 const BYTE* line1 = &dst1[x][y * dstStep[x]];
1553 const BYTE* line2 = &dst2[x][y * dstStep[x]];
1554
1555 for (size_t z = 0; z < roi.width; z++)
1556 {
1557 if (line1[z] != line2[z])
1558 {
1559 (void)fprintf(
1560 stderr,
1561 "[%s] plane %" PRIuz " [%" PRIuz "x%" PRIuz
1562 "] generic and optimized data mismatch: 0x%02" PRIx8 " != 0x%02" PRIx8 "\n",
1563 (chroma == AVC444_CHROMAv1) ? "v1" : "v2", x, z, y, line1[z], line2[z]);
1564 (void)fprintf(stderr, "roi: %" PRIu32 "x%" PRIu32 "\n", roi.width, roi.height);
1565 goto fail;
1566 }
1567 }
1568 }
1569 }
1570
1571 rc = TRUE;
1572fail:
1573 printf("%s [%s] finished with %s\n", __func__, (chroma == AVC444_CHROMAv1) ? "v1" : "v2",
1574 rc ? "SUCCESS" : "FAILURE");
1575 for (size_t x = 0; x < 3; x++)
1576 {
1577 free(luma[x]);
1578 free(aux[x]);
1579 free(dst1[x]);
1580 free(dst2[x]);
1581 }
1582 return rc;
1583}
1584
1585int TestPrimitivesYUV(int argc, char* argv[])
1586{
1587 BOOL large = (argc > 1);
1588 int rc = -1;
1589 WINPR_UNUSED(argc);
1590 WINPR_UNUSED(argv);
1591 prim_size_t roi = WINPR_C_ARRAY_INIT;
1592
1593 if (argc > 1)
1594 {
1595 BOOL reset = TRUE;
1596 char* str = argv[1];
1597 char* ptr = strchr(str, 'x');
1598 if (ptr)
1599 {
1600 *ptr++ = '\0';
1601
1602 errno = 0;
1603 roi.width = strtoul(str, nullptr, 0);
1604 if (errno == 0)
1605 roi.height = strtoul(str, nullptr, 0);
1606 reset = errno != 0;
1607 }
1608
1609 if (reset)
1610 {
1611 roi.width = 1920;
1612 roi.height = 1080;
1613 }
1614 }
1615 else
1616 {
1617 if (!get_size(large, &roi.width, &roi.height))
1618 goto end;
1619 }
1620
1621 prim_test_setup(FALSE);
1622
1623 for (UINT32 type = PRIMITIVES_PURE_SOFT; type <= PRIMITIVES_AUTODETECT; type++)
1624 {
1625 if (!compare_yuv444_to_rgb(roi, type))
1626 goto end;
1627
1628 const prim_size_t oddroi = { roi.width - 1, roi.height };
1629 if (!compare_i444_to_rgb(roi, type) || !compare_i444_to_rgb(oddroi, type))
1630 goto end;
1631
1632 if (!compare_rgb_to_yuv444(roi, type))
1633 goto end;
1634
1635 if (!compare_yuv420_to_rgb(roi, type))
1636 goto end;
1637 if (!compare_rgb_to_yuv420(roi, type))
1638 goto end;
1639
1640 /* the random roi may be too narrow for the vector loops; 1744 is an
1641 * odd number of macroblocks */
1642 const prim_size_t combineRois[] = { roi, { 1744, 64 } };
1643 for (size_t x = 0; x < ARRAYSIZE(combineRois); x++)
1644 {
1645 if (!compare_yuv420_combine_to_yuv444(combineRois[x], type, AVC444_CHROMAv1))
1646 goto end;
1647 if (!compare_yuv420_combine_to_yuv444(combineRois[x], type, AVC444_CHROMAv2))
1648 goto end;
1649 }
1650 }
1651
1652 if (!run_tests(roi))
1653 goto end;
1654
1655 rc = 0;
1656end:
1657 printf("[%s] finished, status %s [%d]\n", __func__, (rc == 0) ? "SUCCESS" : "FAILURE", rc);
1658 return rc;
1659}
WINPR_ATTR_NODISCARD fn_YUV444ToRGB_8u_P3AC4R_t I444ToRGB_8u
Convert a plain YUV444 picture (e.g. decoded AV1) to RGB. Unlike YUV444ToRGB_8u_P3AC4R this does not ...
Definition primitives.h:310