#include #include #include #include #include "prim_test.h" #include #include #include #include #include #include "../prim_internal.h" #define TAG __FILE__ #define PADDING_FILL_VALUE 0x37 /* YUV to RGB conversion is lossy, so consider every value only * differing by less than 2 abs equal. */ static BOOL similar(const BYTE* src, const BYTE* dst, size_t size) { for (size_t x = 0; x < size; x++) { int diff = src[x] - dst[x]; if (abs(diff) > 4) { (void)fprintf(stderr, "%" PRIuz " %02" PRIX8 " : %02" PRIX8 " diff=%d\n", x, src[x], dst[x], abs(diff)); return FALSE; } } return TRUE; } static BOOL similarRGB(size_t y, const BYTE* src, const BYTE* dst, size_t size, UINT32 format, BOOL use444) { BOOL rc = TRUE; /* Skip YUV420, too lossy */ if (!use444) return TRUE; const UINT32 bpp = FreeRDPGetBytesPerPixel(format); BYTE fill = PADDING_FILL_VALUE; if (!FreeRDPColorHasAlpha(format)) fill = 0xFF; for (size_t x = 0; x < size; x++) { const LONG maxDiff = 4; UINT32 sColor = 0; UINT32 dColor = 0; BYTE sR = 0; BYTE sG = 0; BYTE sB = 0; BYTE sA = 0; BYTE dR = 0; BYTE dG = 0; BYTE dB = 0; BYTE dA = 0; sColor = FreeRDPReadColor(src, format); dColor = FreeRDPReadColor(dst, format); src += bpp; dst += bpp; FreeRDPSplitColor(sColor, format, &sR, &sG, &sB, &sA, nullptr); FreeRDPSplitColor(dColor, format, &dR, &dG, &dB, &dA, nullptr); const long diffr = labs(1L * sR - dR); const long diffg = labs(1L * sG - dG); const long diffb = labs(1L * sB - dB); if ((diffr > maxDiff) || (diffg > maxDiff) || (diffb > maxDiff)) { /* AVC444 uses an averaging filter for luma pixel U/V and reverses it in YUV444 -> RGB * this is lossy and does not handle all combinations well so the 2x,2y pixel can be * quite different after RGB -> YUV444 -> RGB conversion. * * skip these pixels to avoid failing the test */ if (use444 && ((x % 2) == 0) && ((y % 2) == 0)) { continue; } const BYTE sY = RGB2Y(sR, sG, sB); const BYTE sU = RGB2U(sR, sG, sB); const BYTE sV = RGB2V(sR, sG, sB); const BYTE dY = RGB2Y(dR, dG, dB); const BYTE dU = RGB2U(dR, dG, dB); const BYTE dV = RGB2V(dR, dG, dB); (void)fprintf(stderr, "[%s] Color value mismatch R[%02X %02X], G[%02X %02X], B[%02X %02X] at " "position %" PRIuz "x%" PRIuz "\n", use444 ? "AVC444" : "AVC420", sR, dR, sG, dG, sA, dA, x, y); (void)fprintf(stderr, "[%s] Color value mismatch Y[%02X %02X], U[%02X %02X], V[%02X %02X] at " "position %" PRIuz "x%" PRIuz "\n", use444 ? "AVC444" : "AVC420", sY, dY, sU, dU, sV, dV, x, y); rc = FALSE; continue; } if (dA != fill) { (void)fprintf( stderr, "[%s] Invalid destination alpha value 0x%02X [expected 0x%02X] at position %" PRIuz "x%" PRIuz "\n", use444 ? "AVC444" : "AVC420", dA, fill, x, y); rc = FALSE; continue; } } return rc; } static BOOL get_size(BOOL large, UINT32* width, UINT32* height) { UINT32 shift = large ? 8 : 1; if (winpr_RAND(width, sizeof(*width)) < 0) return FALSE; if (winpr_RAND(height, sizeof(*height)) < 0) return FALSE; *width = (*width % 64 + 1) << shift; *height = (*height % 64 + 1); return TRUE; } static BOOL check_padding(const BYTE* psrc, size_t size, size_t padding, const char* buffer) { BOOL rc = TRUE; const BYTE* src = nullptr; const BYTE* esrc = nullptr; size_t halfPad = (padding + 1) / 2; if (!psrc) return FALSE; src = psrc - halfPad; esrc = src + size + halfPad; for (size_t x = 0; x < halfPad; x++) { const BYTE s = *src++; const BYTE d = *esrc++; if (s != 'A') { size_t start = x; while ((x < halfPad) && (*esrc++ != 'A')) x++; (void)fprintf(stderr, "Buffer underflow detected %02" PRIx8 " != %02X %s [%" PRIuz "-%" PRIuz "]\n", d, 'A', buffer, start, x); return FALSE; } if (d != 'A') { size_t start = x; while ((x < halfPad) && (*esrc++ != 'A')) x++; (void)fprintf(stderr, "Buffer overflow detected %02" PRIx8 " != %02X %s [%" PRIuz "-%" PRIuz "]\n", d, 'A', buffer, start, x); return FALSE; } } return rc; } static void* set_padding(size_t size, size_t padding) { size_t halfPad = (padding + 1) / 2; BYTE* psrc = nullptr; BYTE* src = winpr_aligned_malloc(size + 2 * halfPad, 16); if (!src) return nullptr; memset(&src[0], 'A', halfPad); memset(&src[halfPad], PADDING_FILL_VALUE, size); memset(&src[halfPad + size], 'A', halfPad); psrc = &src[halfPad]; if (!check_padding(psrc, size, padding, "init")) { winpr_aligned_free(src); return nullptr; } return psrc; } static void free_padding(void* src, size_t padding) { BYTE* ptr = nullptr; if (!src) return; ptr = ((BYTE*)src) - (padding + 1) / 2; winpr_aligned_free(ptr); } /* Create 2 pseudo YUV420 frames of same size. * Combine them and check, if the data is at the expected position. */ static BOOL TestPrimitiveYUVCombine(primitives_t* prims, prim_size_t roi) { union { const BYTE** cpv; BYTE** pv; } cnv; size_t awidth = 0; size_t aheight = 0; BOOL rc = FALSE; BYTE* luma[3] = WINPR_C_ARRAY_INIT; BYTE* chroma[3] = WINPR_C_ARRAY_INIT; BYTE* yuv[3] = WINPR_C_ARRAY_INIT; BYTE* pmain[3] = WINPR_C_ARRAY_INIT; BYTE* paux[3] = WINPR_C_ARRAY_INIT; UINT32 lumaStride[3] = WINPR_C_ARRAY_INIT; UINT32 chromaStride[3] = WINPR_C_ARRAY_INIT; UINT32 yuvStride[3] = WINPR_C_ARRAY_INIT; const size_t padding = 10000; RECTANGLE_16 rect = WINPR_C_ARRAY_INIT; PROFILER_DEFINE(yuvCombine) PROFILER_DEFINE(yuvSplit) // TODO: we only support even height values at the moment if (roi.height % 2) roi.height++; awidth = roi.width + 16 - roi.width % 16; aheight = roi.height + 16 - roi.height % 16; (void)fprintf(stderr, "Running YUVCombine on frame size %" PRIu32 "x%" PRIu32 " [%" PRIuz "x%" PRIuz "]\n", roi.width, roi.height, awidth, aheight); PROFILER_CREATE(yuvCombine, "YUV420CombineToYUV444") PROFILER_CREATE(yuvSplit, "YUV444SplitToYUV420") rect.left = 0; rect.top = 0; rect.right = roi.width; rect.bottom = roi.height; if (!prims || !prims->YUV420CombineToYUV444) goto fail; for (UINT32 x = 0; x < 3; x++) { size_t halfStride = ((x > 0) ? awidth / 2 : awidth); size_t size = aheight * awidth; size_t halfSize = ((x > 0) ? halfStride * aheight / 2 : awidth * aheight); yuvStride[x] = awidth; if (!(yuv[x] = set_padding(size, padding))) goto fail; lumaStride[x] = halfStride; if (!(luma[x] = set_padding(halfSize, padding))) goto fail; if (!(pmain[x] = set_padding(halfSize, padding))) goto fail; chromaStride[x] = halfStride; if (!(chroma[x] = set_padding(halfSize, padding))) goto fail; if (!(paux[x] = set_padding(halfSize, padding))) goto fail; memset(luma[x], WINPR_ASSERTING_INT_CAST(int, 0xAB + 3 * x), halfSize); memset(chroma[x], WINPR_ASSERTING_INT_CAST(int, 0x80 + 2 * x), halfSize); if (!check_padding(luma[x], halfSize, padding, "luma")) goto fail; if (!check_padding(chroma[x], halfSize, padding, "chroma")) goto fail; if (!check_padding(pmain[x], halfSize, padding, "main")) goto fail; if (!check_padding(paux[x], halfSize, padding, "aux")) goto fail; if (!check_padding(yuv[x], size, padding, "yuv")) goto fail; } PROFILER_ENTER(yuvCombine) cnv.pv = luma; if (prims->YUV420CombineToYUV444(AVC444_LUMA, cnv.cpv, lumaStride, roi.width, roi.height, yuv, yuvStride, &rect) != PRIMITIVES_SUCCESS) { PROFILER_EXIT(yuvCombine) goto fail; } cnv.pv = chroma; if (prims->YUV420CombineToYUV444(AVC444_CHROMAv1, cnv.cpv, chromaStride, roi.width, roi.height, yuv, yuvStride, &rect) != PRIMITIVES_SUCCESS) { PROFILER_EXIT(yuvCombine) goto fail; } PROFILER_EXIT(yuvCombine) for (size_t x = 0; x < 3; x++) { size_t halfStride = ((x > 0) ? awidth / 2 : awidth); size_t size = 1ULL * aheight * awidth; size_t halfSize = ((x > 0) ? halfStride * aheight / 2 : awidth * aheight); if (!check_padding(luma[x], halfSize, padding, "luma")) goto fail; if (!check_padding(chroma[x], halfSize, padding, "chroma")) goto fail; if (!check_padding(yuv[x], size, padding, "yuv")) goto fail; } PROFILER_ENTER(yuvSplit) cnv.pv = yuv; if (prims->YUV444SplitToYUV420(cnv.cpv, yuvStride, pmain, lumaStride, paux, chromaStride, &roi) != PRIMITIVES_SUCCESS) { PROFILER_EXIT(yuvSplit) goto fail; } PROFILER_EXIT(yuvSplit) for (UINT32 x = 0; x < 3; x++) { size_t halfStride = ((x > 0) ? awidth / 2 : awidth); size_t size = aheight * awidth; size_t halfSize = ((x > 0) ? halfStride * aheight / 2 : awidth * aheight); if (!check_padding(pmain[x], halfSize, padding, "main")) goto fail; if (!check_padding(paux[x], halfSize, padding, "aux")) goto fail; if (!check_padding(yuv[x], size, padding, "yuv")) goto fail; } for (size_t i = 0; i < 3; i++) { for (size_t y = 0; y < roi.height; y++) { UINT32 w = roi.width; UINT32 lstride = lumaStride[i]; UINT32 cstride = chromaStride[i]; if (i > 0) { w = (roi.width + 3) / 4; if (roi.height > (roi.height + 1) / 2) continue; } if (!similar(luma[i] + y * lstride, pmain[i] + y * lstride, w)) goto fail; /* Need to ignore lines of destination Y plane, * if the lines are not a multiple of 16 * as the UV planes are packed in 8 line stripes. */ if (i == 0) { /* TODO: This check is not perfect, it does not * include the last V lines packed to the Y * frame. */ UINT32 rem = roi.height % 16; if (y > roi.height - rem) continue; } if (!similar(chroma[i] + y * cstride, paux[i] + y * cstride, w)) goto fail; } } PROFILER_PRINT_HEADER PROFILER_PRINT(yuvSplit) PROFILER_PRINT(yuvCombine) PROFILER_PRINT_FOOTER rc = TRUE; fail: printf("[%s] run %s.\n", __func__, (rc) ? "SUCCESS" : "FAILED"); PROFILER_FREE(yuvCombine) PROFILER_FREE(yuvSplit) for (UINT32 x = 0; x < 3; x++) { free_padding(yuv[x], padding); free_padding(luma[x], padding); free_padding(chroma[x], padding); free_padding(pmain[x], padding); free_padding(paux[x], padding); } return rc; } static BOOL TestPrimitiveYUV(primitives_t* prims, prim_size_t roi, BOOL use444) { union { const BYTE** cpv; BYTE** pv; } cnv; BOOL res = FALSE; UINT32 awidth = 0; UINT32 aheight = 0; BYTE* yuv[3] = WINPR_C_ARRAY_INIT; UINT32 yuv_step[3] = WINPR_C_ARRAY_INIT; BYTE* rgb = nullptr; BYTE* rgb_dst = nullptr; size_t size = 0; size_t uvsize = 0; size_t uvwidth = 0; size_t padding = 100ULL * 16ULL; UINT32 stride = 0; const UINT32 formats[] = { PIXEL_FORMAT_XRGB32, PIXEL_FORMAT_XBGR32, PIXEL_FORMAT_ARGB32, PIXEL_FORMAT_ABGR32, PIXEL_FORMAT_RGBA32, PIXEL_FORMAT_RGBX32, PIXEL_FORMAT_BGRA32, PIXEL_FORMAT_BGRX32 }; PROFILER_DEFINE(rgbToYUV420) PROFILER_DEFINE(rgbToYUV444) PROFILER_DEFINE(yuv420ToRGB) PROFILER_DEFINE(yuv444ToRGB) /* Buffers need to be 16x16 aligned. */ awidth = roi.width + 16 - roi.width % 16; aheight = roi.height + 16 - roi.height % 16; stride = 1ULL * awidth * sizeof(UINT32); size = 1ULL * awidth * aheight; if (use444) { uvwidth = awidth; uvsize = size; if (!prims || !prims->RGBToYUV444_8u_P3AC4R || !prims->YUV444ToRGB_8u_P3AC4R) return FALSE; } else { uvwidth = (awidth + 1) / 2; uvsize = (aheight + 1) / 2 * uvwidth; if (!prims || !prims->RGBToYUV420_8u_P3AC4R || !prims->YUV420ToRGB_8u_P3AC4R) return FALSE; } (void)fprintf(stderr, "Running AVC%s on frame size %" PRIu32 "x%" PRIu32 "\n", use444 ? "444" : "420", roi.width, roi.height); /* Test RGB to YUV444 conversion and vice versa */ if (!(rgb = set_padding(size * sizeof(UINT32), padding))) goto fail; if (!(rgb_dst = set_padding(size * sizeof(UINT32), padding))) goto fail; if (!(yuv[0] = set_padding(size, padding))) goto fail; if (!(yuv[1] = set_padding(uvsize, padding))) goto fail; if (!(yuv[2] = set_padding(uvsize, padding))) goto fail; for (size_t y = 0; y < roi.height; y++) { BYTE* line = &rgb[y * stride]; if (winpr_RAND(line, stride) < 0) goto fail; } yuv_step[0] = awidth; yuv_step[1] = uvwidth; yuv_step[2] = uvwidth; for (UINT32 x = 0; x < ARRAYSIZE(formats); x++) { pstatus_t rc = 0; const UINT32 DstFormat = formats[x]; printf("Testing destination color format %s\n", FreeRDPGetColorFormatName(DstFormat)); memset(rgb_dst, PADDING_FILL_VALUE, size * sizeof(UINT32)); PROFILER_CREATE(rgbToYUV420, "RGBToYUV420") PROFILER_CREATE(rgbToYUV444, "RGBToYUV444") PROFILER_CREATE(yuv420ToRGB, "YUV420ToRGB") PROFILER_CREATE(yuv444ToRGB, "YUV444ToRGB") if (use444) { PROFILER_ENTER(rgbToYUV444) rc = prims->RGBToYUV444_8u_P3AC4R(rgb, DstFormat, stride, yuv, yuv_step, &roi); PROFILER_EXIT(rgbToYUV444) if (rc != PRIMITIVES_SUCCESS) goto loop_fail; PROFILER_PRINT_HEADER PROFILER_PRINT(rgbToYUV444) PROFILER_PRINT_FOOTER } else { PROFILER_ENTER(rgbToYUV420) rc = prims->RGBToYUV420_8u_P3AC4R(rgb, DstFormat, stride, yuv, yuv_step, &roi); PROFILER_EXIT(rgbToYUV420) if (rc != PRIMITIVES_SUCCESS) goto loop_fail; PROFILER_PRINT_HEADER PROFILER_PRINT(rgbToYUV420) PROFILER_PRINT_FOOTER } if (!check_padding(rgb, size * sizeof(UINT32), padding, "rgb")) { rc = -1; goto loop_fail; } if ((!check_padding(yuv[0], size, padding, "Y")) || (!check_padding(yuv[1], uvsize, padding, "U")) || (!check_padding(yuv[2], uvsize, padding, "V"))) { rc = -1; goto loop_fail; } cnv.pv = yuv; if (use444) { PROFILER_ENTER(yuv444ToRGB) rc = prims->YUV444ToRGB_8u_P3AC4R(cnv.cpv, yuv_step, rgb_dst, stride, DstFormat, &roi); PROFILER_EXIT(yuv444ToRGB) if (rc != PRIMITIVES_SUCCESS) goto loop_fail; loop_fail: PROFILER_EXIT(yuv444ToRGB) PROFILER_PRINT_HEADER PROFILER_PRINT(yuv444ToRGB) PROFILER_PRINT_FOOTER if (rc != PRIMITIVES_SUCCESS) goto fail; } else { PROFILER_ENTER(yuv420ToRGB) if (prims->YUV420ToRGB_8u_P3AC4R(cnv.cpv, yuv_step, rgb_dst, stride, DstFormat, &roi) != PRIMITIVES_SUCCESS) { PROFILER_EXIT(yuv420ToRGB) goto fail; } PROFILER_EXIT(yuv420ToRGB) PROFILER_PRINT_HEADER PROFILER_PRINT(yuv420ToRGB) PROFILER_PRINT_FOOTER } if (!check_padding(rgb_dst, size * sizeof(UINT32), padding, "rgb dst")) goto fail; if ((!check_padding(yuv[0], size, padding, "Y")) || (!check_padding(yuv[1], uvsize, padding, "U")) || (!check_padding(yuv[2], uvsize, padding, "V"))) goto fail; BOOL equal = TRUE; for (size_t y = 0; y < roi.height; y++) { BYTE* srgb = &rgb[y * stride]; BYTE* drgb = &rgb_dst[y * stride]; if (!similarRGB(y, srgb, drgb, roi.width, DstFormat, use444)) equal = FALSE; } if (!equal) goto fail; PROFILER_FREE(rgbToYUV420) PROFILER_FREE(rgbToYUV444) PROFILER_FREE(yuv420ToRGB) PROFILER_FREE(yuv444ToRGB) } res = TRUE; fail: printf("[%s] run %s.\n", __func__, (res) ? "SUCCESS" : "FAILED"); free_padding(rgb, padding); free_padding(rgb_dst, padding); free_padding(yuv[0], padding); free_padding(yuv[1], padding); free_padding(yuv[2], padding); return res; } static BOOL allocate_yuv420(BYTE** planes, UINT32 width, UINT32 height, UINT32 padding) { const size_t size = 1ULL * width * height; const size_t uvwidth = (1ULL + width) / 2; const size_t uvsize = (1ULL + height) / 2 * uvwidth; if (!(planes[0] = set_padding(size, padding))) goto fail; if (!(planes[1] = set_padding(uvsize, padding))) goto fail; if (!(planes[2] = set_padding(uvsize, padding))) goto fail; return TRUE; fail: free_padding(planes[0], padding); free_padding(planes[1], padding); free_padding(planes[2], padding); return FALSE; } static void free_yuv420(BYTE** planes, UINT32 padding) { if (!planes) return; free_padding(planes[0], padding); free_padding(planes[1], padding); free_padding(planes[2], padding); planes[0] = nullptr; planes[1] = nullptr; planes[2] = nullptr; } static BOOL check_yuv420(BYTE** planes, UINT32 width, UINT32 height, UINT32 padding) { const size_t size = 1ULL * width * height; const size_t uvwidth = (width + 1) / 2; const size_t uvsize = (height + 1) / 2 * uvwidth; const BOOL yOk = check_padding(planes[0], size, padding, "Y"); const BOOL uOk = check_padding(planes[1], uvsize, padding, "U"); const BOOL vOk = check_padding(planes[2], uvsize, padding, "V"); return (yOk && uOk && vOk); } static BOOL check_for_mismatches(const BYTE* planeA, const BYTE* planeB, UINT32 size) { BOOL rc = FALSE; for (UINT32 x = 0; x < size; x++) { const BYTE a = planeA[x]; const BYTE b = planeB[x]; if (fabsf((float)a - (float)b) > 2.0f) { rc = TRUE; (void)fprintf(stderr, "[%08x] %02x != %02x\n", x, a, b); } } return rc; } static BOOL compare_yuv420(BYTE** planesA, BYTE** planesB, UINT32 width, UINT32 height, UINT32 padding) { BOOL rc = TRUE; const size_t size = 1ULL * width * height; const size_t uvwidth = (1ULL * width + 1) / 2; const size_t uvsize = (1ULL * height + 1) / 2 * uvwidth; if (check_for_mismatches(planesA[0], planesB[0], size)) { (void)fprintf(stderr, "Mismatch in Y planes!\n"); rc = FALSE; } if (check_for_mismatches(planesA[1], planesB[1], uvsize)) { (void)fprintf(stderr, "Mismatch in U planes!\n"); rc = FALSE; } if (check_for_mismatches(planesA[2], planesB[2], uvsize)) { (void)fprintf(stderr, "Mismatch in V planes!\n"); rc = FALSE; } return rc; } static UINT32 prand(UINT32 max) { UINT32 tmp = 0; if (max <= 1) return 1; if (winpr_RAND(&tmp, sizeof(tmp)) < 0) { (void)fprintf(stderr, "winpr_RAND failed, retry...\n"); // NOLINTNEXTLINE(concurrency-mt-unsafe) exit(-1); } return tmp % (max - 1) + 1; } static BOOL TestPrimitiveRgbToLumaChroma(primitives_t* prims, prim_size_t roi, UINT32 version) { BOOL res = FALSE; UINT32 awidth = 0; UINT32 aheight = 0; BYTE* luma[3] = WINPR_C_ARRAY_INIT; BYTE* chroma[3] = WINPR_C_ARRAY_INIT; BYTE* lumaGeneric[3] = WINPR_C_ARRAY_INIT; BYTE* chromaGeneric[3] = WINPR_C_ARRAY_INIT; UINT32 yuv_step[3]; BYTE* rgb = nullptr; size_t size = 0; size_t uvwidth = 0; const size_t padding = 0x1000; UINT32 stride = 0; fn_RGBToAVC444YUV_t fkt = nullptr; fn_RGBToAVC444YUV_t gen = nullptr; const UINT32 formats[] = { PIXEL_FORMAT_XRGB32, PIXEL_FORMAT_XBGR32, PIXEL_FORMAT_ARGB32, PIXEL_FORMAT_ABGR32, PIXEL_FORMAT_RGBA32, PIXEL_FORMAT_RGBX32, PIXEL_FORMAT_BGRA32, PIXEL_FORMAT_BGRX32 }; PROFILER_DEFINE(rgbToYUV444) PROFILER_DEFINE(rgbToYUV444opt) /* Buffers need to be 16x16 aligned. */ awidth = roi.width; if (awidth % 16 != 0) awidth += 16 - roi.width % 16; aheight = roi.height; if (aheight % 16 != 0) aheight += 16 - roi.height % 16; stride = 1ULL * awidth * sizeof(UINT32); size = 1ULL * awidth * aheight; uvwidth = 1ULL * (awidth + 1) / 2; if (!prims || !generic) return FALSE; switch (version) { case 1: fkt = prims->RGBToAVC444YUV; gen = generic->RGBToAVC444YUV; break; case 2: fkt = prims->RGBToAVC444YUVv2; gen = generic->RGBToAVC444YUVv2; break; default: return FALSE; } if (!fkt || !gen) return FALSE; (void)fprintf(stderr, "Running AVC444 on frame size %" PRIu32 "x%" PRIu32 "\n", roi.width, roi.height); /* Test RGB to YUV444 conversion and vice versa */ if (!(rgb = set_padding(size * sizeof(UINT32), padding))) goto fail; if (!allocate_yuv420(luma, awidth, aheight, padding)) goto fail; if (!allocate_yuv420(chroma, awidth, aheight, padding)) goto fail; if (!allocate_yuv420(lumaGeneric, awidth, aheight, padding)) goto fail; if (!allocate_yuv420(chromaGeneric, awidth, aheight, padding)) goto fail; for (size_t y = 0; y < roi.height; y++) { BYTE* line = &rgb[y * stride]; if (winpr_RAND(line, 4ULL * roi.width) < 0) goto fail; } yuv_step[0] = awidth; yuv_step[1] = uvwidth; yuv_step[2] = uvwidth; for (UINT32 x = 0; x < ARRAYSIZE(formats); x++) { pstatus_t rc = -1; const UINT32 DstFormat = formats[x]; printf("Testing destination color format %s\n", FreeRDPGetColorFormatName(DstFormat)); PROFILER_CREATE(rgbToYUV444, "RGBToYUV444-generic") PROFILER_CREATE(rgbToYUV444opt, "RGBToYUV444-optimized") for (UINT32 cnt = 0; cnt < 10; cnt++) { PROFILER_ENTER(rgbToYUV444opt) rc = fkt(rgb, DstFormat, stride, luma, yuv_step, chroma, yuv_step, &roi); PROFILER_EXIT(rgbToYUV444opt) if (rc != PRIMITIVES_SUCCESS) goto loop_fail; } PROFILER_PRINT_HEADER PROFILER_PRINT(rgbToYUV444opt) PROFILER_PRINT_FOOTER if (!check_padding(rgb, size * sizeof(UINT32), padding, "rgb")) { rc = -1; goto loop_fail; } if (!check_yuv420(luma, awidth, aheight, padding) || !check_yuv420(chroma, awidth, aheight, padding)) { rc = -1; goto loop_fail; } for (UINT32 cnt = 0; cnt < 10; cnt++) { PROFILER_ENTER(rgbToYUV444) rc = gen(rgb, DstFormat, stride, lumaGeneric, yuv_step, chromaGeneric, yuv_step, &roi); PROFILER_EXIT(rgbToYUV444) if (rc != PRIMITIVES_SUCCESS) goto loop_fail; } PROFILER_PRINT_HEADER PROFILER_PRINT(rgbToYUV444) PROFILER_PRINT_FOOTER if (!check_padding(rgb, size * sizeof(UINT32), padding, "rgb")) { rc = -1; goto loop_fail; } if (!check_yuv420(lumaGeneric, awidth, aheight, padding) || !check_yuv420(chromaGeneric, awidth, aheight, padding)) { rc = -1; goto loop_fail; } if (!compare_yuv420(luma, lumaGeneric, awidth, aheight, padding) || !compare_yuv420(chroma, chromaGeneric, awidth, aheight, padding)) { rc = -1; goto loop_fail; } loop_fail: PROFILER_FREE(rgbToYUV444) PROFILER_FREE(rgbToYUV444opt) if (rc != PRIMITIVES_SUCCESS) goto fail; } res = TRUE; fail: printf("[%s][version %u] run %s.\n", __func__, (unsigned)version, (res) ? "SUCCESS" : "FAILED"); free_padding(rgb, padding); free_yuv420(luma, padding); free_yuv420(chroma, padding); free_yuv420(lumaGeneric, padding); free_yuv420(chromaGeneric, padding); return res; } static BOOL run_tests(prim_size_t roi) { BOOL rc = FALSE; for (UINT32 type = PRIMITIVES_PURE_SOFT; type <= PRIMITIVES_AUTODETECT; type++) { primitives_t* prims = primitives_get_by_type(type); if (!prims) { printf("primitives type %d not supported\n", type); continue; } for (UINT32 x = 0; x < 5; x++) { printf("-------------------- GENERIC ------------------------\n"); if (!TestPrimitiveYUV(prims, roi, TRUE)) goto fail; printf("---------------------- END --------------------------\n"); printf("-------------------- GENERIC ------------------------\n"); if (!TestPrimitiveYUV(prims, roi, FALSE)) goto fail; printf("---------------------- END --------------------------\n"); printf("-------------------- GENERIC ------------------------\n"); if (!TestPrimitiveYUVCombine(prims, roi)) goto fail; printf("---------------------- END --------------------------\n"); printf("-------------------- GENERIC ------------------------\n"); if (!TestPrimitiveRgbToLumaChroma(prims, roi, 1)) goto fail; printf("---------------------- END --------------------------\n"); printf("-------------------- GENERIC ------------------------\n"); if (!TestPrimitiveRgbToLumaChroma(prims, roi, 2)) goto fail; printf("---------------------- END --------------------------\n"); } } rc = TRUE; fail: printf("[%s] run %s.\n", __func__, (rc) ? "SUCCESS" : "FAILED"); return rc; } static void free_yuv(BYTE* yuv[3]) { for (size_t x = 0; x < 3; x++) { free(yuv[x]); yuv[x] = nullptr; } } static BOOL allocate_yuv(BYTE* yuv[3], prim_size_t roi) { yuv[0] = calloc(roi.width, roi.height); yuv[1] = calloc(roi.width, roi.height); yuv[2] = calloc(roi.width, roi.height); if (!yuv[0] || !yuv[1] || !yuv[2]) { free_yuv(yuv); return FALSE; } if (winpr_RAND(yuv[0], 1ULL * roi.width * roi.height) < 0) return FALSE; if (winpr_RAND(yuv[1], 1ULL * roi.width * roi.height) < 0) return FALSE; if (winpr_RAND(yuv[2], 1ULL * roi.width * roi.height) < 0) return FALSE; return TRUE; } static BOOL yuv444_to_rgb(BYTE* rgb, size_t stride, const BYTE* yuv[3], const UINT32 yuvStep[3], prim_size_t roi) { for (size_t y = 0; y < roi.height; y++) { const BYTE* yline[3] = { yuv[0] + y * roi.width, yuv[1] + y * roi.width, yuv[2] + y * roi.width, }; BYTE* line = &rgb[y * stride]; for (size_t x = 0; x < roi.width; x++) { const BYTE Y = yline[0][x]; const BYTE U = yline[1][x]; const BYTE V = yline[2][x]; writeYUVPixel(&line[x * 4], PIXEL_FORMAT_BGRX32, Y, U, V, writePixelBGRX); } } } /* Check the result of generic matches the optimized routine. * */ static BOOL compare_yuv444_to_rgb(prim_size_t roi, DWORD type) { BOOL rc = FALSE; const UINT32 format = PIXEL_FORMAT_BGRA32; BYTE* yuv[3] = WINPR_C_ARRAY_INIT; const UINT32 yuvStep[3] = { roi.width, roi.width, roi.width }; const size_t stride = 4ULL * roi.width; primitives_t* prims = primitives_get_by_type(type); if (!prims) { printf("primitives type %" PRIu32 " not supported, skipping\n", type); return TRUE; } BYTE* rgb1 = calloc(roi.height, stride); BYTE* rgb2 = calloc(roi.height, stride); primitives_t* soft = primitives_get_by_type(PRIMITIVES_PURE_SOFT); if (!soft) goto fail; if (!allocate_yuv(yuv, roi) || !rgb1 || !rgb2) goto fail; const BYTE* cyuv[] = { yuv[0], yuv[1], yuv[2] }; if (soft->YUV444ToRGB_8u_P3AC4R(cyuv, yuvStep, rgb1, stride, format, &roi) != PRIMITIVES_SUCCESS) goto fail; if (prims->YUV444ToRGB_8u_P3AC4R(cyuv, yuvStep, rgb2, stride, format, &roi) != PRIMITIVES_SUCCESS) goto fail; for (size_t y = 0; y < roi.height; y++) { const BYTE* yline[3] = { yuv[0] + y * roi.width, yuv[1] + y * roi.width, yuv[2] + y * roi.width, }; const BYTE* line1 = &rgb1[y * stride]; const BYTE* line2 = &rgb2[y * stride]; for (size_t x = 0; x < roi.width; x++) { const int Y = yline[0][x]; const int U = yline[1][x]; const int V = yline[2][x]; const UINT32 color1 = FreeRDPReadColor(&line1[x * 4], format); const UINT32 color2 = FreeRDPReadColor(&line2[x * 4], format); BYTE r1 = 0; BYTE g1 = 0; BYTE b1 = 0; FreeRDPSplitColor(color1, format, &r1, &g1, &b1, nullptr, nullptr); BYTE r2 = 0; BYTE g2 = 0; BYTE b2 = 0; FreeRDPSplitColor(color2, format, &r2, &g2, &b2, nullptr, nullptr); const int dr12 = abs(r1 - r2); const int dg12 = abs(g1 - g2); const int db12 = abs(b1 - b2); if ((dr12 != 0) || (dg12 != 0) || (db12 != 0)) { printf("{\n"); printf("\tdiff 1/2: yuv {%d, %d, %d}, rgb {%d, %d, %d}\n", Y, U, V, dr12, dg12, db12); printf("}\n"); } if ((dr12 > 0) || (dg12 > 0) || (db12 > 0)) { (void)fprintf(stderr, "[%" PRIuz "x%" PRIuz "] generic and optimized data mismatch: r[0x%" PRIx8 "|0x%" PRIx8 "] g[0x%" PRIx8 "|0x%" PRIx8 "] b[0x%" PRIx8 "|0x%" PRIx8 "]\n", x, y, r1, r2, g1, g2, b1, b2); (void)fprintf(stderr, "roi: %dx%d\n", roi.width, roi.height); winpr_HexDump("y0", WLOG_INFO, &yline[0][x], 16); winpr_HexDump("y1", WLOG_INFO, &yline[0][x + roi.width], 16); winpr_HexDump("u0", WLOG_INFO, &yline[1][x], 16); winpr_HexDump("u1", WLOG_INFO, &yline[1][x + roi.width], 16); winpr_HexDump("v0", WLOG_INFO, &yline[2][x], 16); winpr_HexDump("v1", WLOG_INFO, &yline[2][x + roi.width], 16); winpr_HexDump("foo1", WLOG_INFO, &line1[x * 4], 16); winpr_HexDump("foo2", WLOG_INFO, &line2[x * 4], 16); goto fail; } } } rc = TRUE; fail: printf("%s finished with %s\n", __func__, rc ? "SUCCESS" : "FAILURE"); free_yuv(yuv); free(rgb1); free(rgb2); return rc; } /* Check the result of generic matches the optimized routine. * */ static BOOL compare_rgb_to_yuv444(prim_size_t roi, DWORD type) { BOOL rc = FALSE; const UINT32 format = PIXEL_FORMAT_BGRA32; const size_t stride = 4ULL * roi.width; const UINT32 yuvStep[] = { roi.width, roi.width, roi.width }; BYTE* yuv1[3] = WINPR_C_ARRAY_INIT; BYTE* yuv2[3] = WINPR_C_ARRAY_INIT; primitives_t* prims = primitives_get_by_type(type); if (!prims) { printf("primitives type %" PRIu32 " not supported, skipping\n", type); return TRUE; } BYTE* rgb = calloc(roi.height, stride); primitives_t* soft = primitives_get_by_type(PRIMITIVES_PURE_SOFT); if (!soft || !rgb) goto fail; if (!allocate_yuv(yuv1, roi) || !allocate_yuv(yuv2, roi)) goto fail; if (soft->RGBToYUV444_8u_P3AC4R(rgb, format, stride, yuv1, yuvStep, &roi) != PRIMITIVES_SUCCESS) goto fail; if (prims->RGBToYUV444_8u_P3AC4R(rgb, format, stride, yuv2, yuvStep, &roi) != PRIMITIVES_SUCCESS) goto fail; for (size_t y = 0; y < roi.height; y++) { const BYTE* yline1[3] = { yuv1[0] + y * roi.width, yuv1[1] + y * roi.width, yuv1[2] + y * roi.width, }; const BYTE* yline2[3] = { yuv2[0] + y * roi.width, yuv2[1] + y * roi.width, yuv2[2] + y * roi.width, }; for (size_t x = 0; x < ARRAYSIZE(yline1); x++) { if (memcmp(yline1[x], yline2[x], yuvStep[x]) != 0) { (void)fprintf(stderr, "[%s] compare failed in line %" PRIuz, __func__, x); goto fail; } } } rc = TRUE; fail: printf("%s finished with %s\n", __func__, rc ? "SUCCESS" : "FAILURE"); free(rgb); free_yuv(yuv1); free_yuv(yuv2); return rc; } /* Check the result of generic matches the optimized routine. * */ static BOOL compare_yuv420_to_rgb(prim_size_t roi, DWORD type) { BOOL rc = FALSE; const UINT32 format = PIXEL_FORMAT_BGRA32; BYTE* yuv[3] = WINPR_C_ARRAY_INIT; const UINT32 yuvStep[3] = { roi.width, roi.width / 2, roi.width / 2 }; const size_t stride = 4ULL * roi.width; primitives_t* prims = primitives_get_by_type(type); if (!prims) { printf("primitives type %" PRIu32 " not supported, skipping\n", type); return TRUE; } BYTE* rgb1 = calloc(roi.height, stride); BYTE* rgb2 = calloc(roi.height, stride); primitives_t* soft = primitives_get_by_type(PRIMITIVES_PURE_SOFT); if (!soft) goto fail; if (!allocate_yuv(yuv, roi) || !rgb1 || !rgb2) goto fail; const BYTE* cyuv[3] = { yuv[0], yuv[1], yuv[2] }; if (soft->YUV420ToRGB_8u_P3AC4R(cyuv, yuvStep, rgb1, stride, format, &roi) != PRIMITIVES_SUCCESS) goto fail; if (prims->YUV420ToRGB_8u_P3AC4R(cyuv, yuvStep, rgb2, stride, format, &roi) != PRIMITIVES_SUCCESS) goto fail; for (size_t y = 0; y < roi.height; y++) { const BYTE* yline[3] = { yuv[0] + y * yuvStep[0], yuv[1] + y * yuvStep[1], yuv[2] + y * yuvStep[2], }; const BYTE* line1 = &rgb1[y * stride]; const BYTE* line2 = &rgb2[y * stride]; for (size_t x = 0; x < roi.width; x++) { const int Y = yline[0][x]; const int U = yline[1][x / 2]; const int V = yline[2][x / 2]; const UINT32 color1 = FreeRDPReadColor(&line1[x * 4], format); const UINT32 color2 = FreeRDPReadColor(&line2[x * 4], format); BYTE r1 = 0; BYTE g1 = 0; BYTE b1 = 0; FreeRDPSplitColor(color1, format, &r1, &g1, &b1, nullptr, nullptr); BYTE r2 = 0; BYTE g2 = 0; BYTE b2 = 0; FreeRDPSplitColor(color2, format, &r2, &g2, &b2, nullptr, nullptr); const int dr12 = abs(r1 - r2); const int dg12 = abs(g1 - g2); const int db12 = abs(b1 - b2); if ((dr12 != 0) || (dg12 != 0) || (db12 != 0)) { printf("{\n"); printf("\tdiff 1/2: yuv {%d, %d, %d}, rgb {%d, %d, %d}\n", Y, U, V, dr12, dg12, db12); printf("}\n"); } if ((dr12 > 0) || (dg12 > 0) || (db12 > 0)) { printf("[%s] failed: r[%" PRIx8 "|%" PRIx8 "] g[%" PRIx8 "|%" PRIx8 "] b[%" PRIx8 "|%" PRIx8 "]\n", __func__, r1, r2, g1, g2, b1, b2); goto fail; } } } rc = TRUE; fail: printf("%s finished with %s\n", __func__, rc ? "SUCCESS" : "FAILURE"); free_yuv(yuv); free(rgb1); free(rgb2); return rc; } static BOOL similarYUV(const BYTE* line1, const BYTE* line2, size_t len) { for (size_t x = 0; x < len; x++) { const int a = line1[x]; const int b = line2[x]; const int diff = abs(a - b); return (diff < 2); } } /* Due to optimizations the Y value might be off by +/- 1 */ static int similarY(const BYTE* a, const BYTE* b, size_t size, size_t type) { switch (type) { case 0: case 1: case 2: for (size_t x = 0; x < size; x++) { const int ba = a[x]; const int bb = b[x]; const int diff = abs(ba - bb); if (diff > 2) return diff; } return 0; break; default: return memcmp(a, b, size); } } /* Check the result of generic matches the optimized routine. * */ static BOOL compare_rgb_to_yuv420(prim_size_t roi, DWORD type) { BOOL rc = FALSE; const UINT32 format = PIXEL_FORMAT_BGRA32; const size_t stride = 4ULL * roi.width; const UINT32 yuvStep[] = { roi.width, roi.width / 2, roi.width / 2 }; BYTE* yuv1[3] = WINPR_C_ARRAY_INIT; BYTE* yuv2[3] = WINPR_C_ARRAY_INIT; primitives_t* prims = primitives_get_by_type(type); if (!prims) { printf("primitives type %" PRIu32 " not supported, skipping\n", type); return TRUE; } BYTE* rgb = calloc(roi.height, stride); BYTE* rgbcopy = calloc(roi.height, stride); primitives_t* soft = primitives_get_by_type(PRIMITIVES_PURE_SOFT); if (!soft || !rgb || !rgbcopy) goto fail; if (winpr_RAND(rgb, roi.height * stride) < 0) goto fail; memcpy(rgbcopy, rgb, roi.height * stride); if (!allocate_yuv(yuv1, roi) || !allocate_yuv(yuv2, roi)) goto fail; if (soft->RGBToYUV420_8u_P3AC4R(rgb, format, stride, yuv1, yuvStep, &roi) != PRIMITIVES_SUCCESS) goto fail; if (memcmp(rgb, rgbcopy, roi.height * stride) != 0) goto fail; if (prims->RGBToYUV420_8u_P3AC4R(rgb, format, stride, yuv2, yuvStep, &roi) != PRIMITIVES_SUCCESS) goto fail; for (size_t y = 0; y < roi.height; y++) { // odd lines do produce artefacts in last line, skip check if (((y + 1) >= roi.height) && ((y % 2) == 0)) continue; const BYTE* yline1[3] = { &yuv1[0][y * yuvStep[0]], &yuv1[1][(y / 2) * yuvStep[1]], &yuv1[2][(y / 2) * yuvStep[2]], }; const BYTE* yline2[3] = { &yuv2[0][y * yuvStep[0]], &yuv2[1][(y / 2) * yuvStep[1]], &yuv2[2][(y / 2) * yuvStep[2]], }; for (size_t x = 0; x < ARRAYSIZE(yline1); x++) { if (similarY(yline1[x], yline2[x], yuvStep[x], x) != 0) { (void)fprintf(stderr, "[%s] compare failed in component %" PRIuz ", line %" PRIuz "\n", __func__, x, y); (void)fprintf(stderr, "[%s] roi %" PRIu32 "x%" PRIu32 "\n", __func__, roi.width, roi.height); winpr_HexDump(TAG, WLOG_WARN, yline1[x], yuvStep[x]); winpr_HexDump(TAG, WLOG_WARN, yline2[x], yuvStep[x]); winpr_HexDump(TAG, WLOG_WARN, &rgb[y * stride], stride); goto fail; } } } rc = TRUE; fail: printf("%s finished with %s\n", __func__, rc ? "SUCCESS" : "FAILURE"); free(rgb); free(rgbcopy); free_yuv(yuv1); free_yuv(yuv2); return rc; } int TestPrimitivesYUV(int argc, char* argv[]) { BOOL large = (argc > 1); int rc = -1; WINPR_UNUSED(argc); WINPR_UNUSED(argv); prim_size_t roi = WINPR_C_ARRAY_INIT; if (argc > 1) { BOOL reset = TRUE; char* str = argv[1]; char* ptr = strchr(str, 'x'); if (ptr) { *ptr++ = '\0'; errno = 0; roi.width = strtoul(str, nullptr, 0); if (errno == 0) roi.height = strtoul(str, nullptr, 0); reset = errno != 0; } if (reset) { roi.width = 1920; roi.height = 1080; } } else get_size(large, &roi.width, &roi.height); prim_test_setup(FALSE); for (UINT32 type = PRIMITIVES_PURE_SOFT; type <= PRIMITIVES_AUTODETECT; type++) { if (!compare_yuv444_to_rgb(roi, type)) goto end; if (!compare_rgb_to_yuv444(roi, type)) goto end; if (!compare_yuv420_to_rgb(roi, type)) goto end; if (!compare_rgb_to_yuv420(roi, type)) goto end; } if (!run_tests(roi)) goto end; rc = 0; end: printf("[%s] finished, status %s [%d]\n", __func__, (rc == 0) ? "SUCCESS" : "FAILURE", rc); return rc; }