diff --git a/include/upipe/ubuf_pic.h b/include/upipe/ubuf_pic.h index ea0ea6f93..2f63f4709 100644 --- a/include/upipe/ubuf_pic.h +++ b/include/upipe/ubuf_pic.h @@ -358,279 +358,20 @@ static inline int ubuf_pic_resize(struct ubuf *ubuf, int hskip, int vskip, * their alpha value is more than this value * @return an error code */ -static inline int ubuf_pic_blit_alpha(struct ubuf *dest, struct ubuf *src, +int ubuf_pic_blit_alpha(struct ubuf *dest, struct ubuf *src, int dest_hoffset, int dest_voffset, int src_hoffset, int src_voffset, int extract_hsize, int extract_vsize, const uint8_t *alpha_plane, int alpha_stride, - const int alpha, const int threshold) -{ - if (alpha_plane == NULL && alpha < threshold && threshold != 0xff) - return UBASE_ERR_NONE; /* nothing to do */ - - uint8_t src_macropixel; - UBASE_RETURN(ubuf_pic_size(src, NULL, NULL, &src_macropixel)) - uint8_t dest_macropixel; - UBASE_RETURN(ubuf_pic_size(dest, NULL, NULL, &dest_macropixel)) - if (unlikely(dest_macropixel != src_macropixel)) - return UBASE_ERR_INVALID; - -#define MAX_PLANES 3 - - const char *chroma; - ubuf_pic_foreach_plane(dest, chroma) { - const char *in_chroma[MAX_PLANES] = { chroma }; - unsigned in_planes = 1; - int err; - - size_t dest_stride; - uint8_t dest_hsub, dest_vsub, dest_macropixel_size; - UBASE_RETURN(ubuf_pic_plane_size(dest, chroma, - &dest_stride, &dest_hsub, &dest_vsub, - &dest_macropixel_size)) - - size_t src_stride; - uint8_t src_hsub, src_vsub, src_macropixel_size; - err = ubuf_pic_plane_size(src, in_chroma[0], &src_stride, &src_hsub, - &src_vsub, &src_macropixel_size); - if (!ubase_check(err)) { - if (!strcmp(chroma, "u8v8")) { - in_chroma[0] = "u8"; - in_chroma[1] = "v8"; - in_planes = 2; - } else - return err; - - for (unsigned i = 0; i < in_planes; i++) { - UBASE_RETURN(ubuf_pic_plane_size(src, in_chroma[i], &src_stride, - &src_hsub, &src_vsub, - &src_macropixel_size)); - if (unlikely(src_hsub != dest_hsub || src_vsub != dest_vsub || - src_macropixel_size != - dest_macropixel_size / in_planes)) - return UBASE_ERR_INVALID; - } - } else if (unlikely(src_hsub != dest_hsub || src_vsub != dest_vsub || - src_macropixel_size != dest_macropixel_size)) - return UBASE_ERR_INVALID; - - uint8_t *dest_buffer; - const uint8_t *in[MAX_PLANES]; - UBASE_RETURN(ubuf_pic_plane_write(dest, chroma, - dest_hoffset, dest_voffset, - extract_hsize, extract_vsize, &dest_buffer)) - for (unsigned i = 0; i < in_planes; i++) { - err = - ubuf_pic_plane_read(src, in_chroma[i], src_hoffset, src_voffset, - extract_hsize, extract_vsize, &in[i]); - if (unlikely(!ubase_check(err))) { - for (; i; i--) - ubuf_pic_plane_unmap(src, in_chroma[i - 1], src_hoffset, - src_voffset, extract_hsize, - extract_vsize); - ubuf_pic_plane_unmap(dest, chroma, dest_hoffset, dest_voffset, - extract_hsize, extract_vsize); - return err; - } - } - - int plane_hsize = extract_hsize / src_hsub / src_macropixel * - src_macropixel_size; - int plane_vsize = extract_vsize / src_vsub; - - for (int i = 0; i < plane_vsize; i++) { - if ((!alpha_plane && alpha == 0xff) || threshold == 0) { - if (in_planes == 1) - memcpy(dest_buffer, in[0], plane_hsize); - else { - for (int j = 0; j < plane_hsize; j++) { - for (int p = 0; p < in_planes; p++) - dest_buffer[j * in_planes + p] = - (dest_buffer[j * in_planes + p] * - (0xff - alpha) + - in[p][j] * alpha) / - 0xff; - } - } - } else if (!alpha_plane) { - for (int j = 0; j < plane_hsize; j++) { - for (int p = 0; p < in_planes; p++) - dest_buffer[j * in_planes + p] = - (dest_buffer[j * in_planes + p] * (0xff - alpha) + - in[p][j] * alpha) / - 0xff; - } - } else if (threshold != 0xff) { - /* This is an on/off blending - * if alpha is over the threshold, we use the subpicture pixel. - */ - if (alpha == 0xff) { - for (int j = 0; j < plane_hsize; j++) { - const uint8_t a = alpha_plane[alpha_stride * (i * src_vsub) + j * src_hsub]; - if (a > threshold) { - for (int p = 0; p < in_planes; p++) - dest_buffer[j * in_planes + p] = in[p][j]; - } - } - } else { - for (int j = 0; j < plane_hsize; j++) { - const uint8_t a = (uint16_t)alpha_plane[alpha_stride * (i * src_vsub) + j * src_hsub] * (uint16_t)alpha / 0xff; - if (a > threshold) { - for (int p = 0; p < in_planes; p++) - dest_buffer[j * in_planes + p] = in[p][j]; - } - } - } - } else { - /* smooth and slow blending */ - if (alpha == 0xff) { - for (int j = 0; j < plane_hsize; j++) { - const uint8_t a = alpha_plane[alpha_stride * (i * src_vsub) + j * src_hsub]; - for (int p = 0; p < in_planes; p++) - dest_buffer[j * in_planes + p] = - (dest_buffer[j * in_planes + p] * (0xff - a) + - in[p][j] * a) / - 0xff; - } - } else { - for (int j = 0; j < plane_hsize; j++) { - const uint8_t a = (uint16_t)alpha_plane[alpha_stride * (i * src_vsub) + j * src_hsub] * (uint16_t)alpha / 0xff; - for (int p = 0; p < in_planes; p++) - dest_buffer[j * in_planes + p] = - (dest_buffer[j * in_planes + p] * (0xff - a) + - in[p][j] * a) / - 0xff; - } - } - } - dest_buffer += dest_stride; - for (int p = 0; p < in_planes; p++) - in[p] += src_stride; - } - - err = ubuf_pic_plane_unmap(dest, chroma, - dest_hoffset, dest_voffset, - extract_hsize, extract_vsize); - for (int p = 0; p < in_planes; p++) - UBASE_RETURN(ubuf_pic_plane_unmap(src, in_chroma[p], src_hoffset, - src_voffset, extract_hsize, - extract_vsize)) - UBASE_RETURN(err) - } - -#undef MAX_PLANES - - return UBASE_ERR_NONE; -} + const int alpha, const int threshold); /** @see ubuf_pic_blit_alpha */ -static inline int ubuf_pic_blit_alpha10(struct ubuf *dest, struct ubuf *src, +int ubuf_pic_blit_alpha10(struct ubuf *dest, struct ubuf *src, int dest_hoffset, int dest_voffset, int src_hoffset, int src_voffset, int extract_hsize, int extract_vsize, const uint8_t *alpha_plane, int alpha_stride, - const int alpha, const int threshold) -{ - if (alpha_plane == NULL && alpha < threshold && threshold != 0x3ff) - return UBASE_ERR_NONE; /* nothing to do */ - - uint8_t src_macropixel; - UBASE_RETURN(ubuf_pic_size(src, NULL, NULL, &src_macropixel)) - uint8_t dest_macropixel; - UBASE_RETURN(ubuf_pic_size(dest, NULL, NULL, &dest_macropixel)) - if (unlikely(dest_macropixel != src_macropixel)) - return UBASE_ERR_INVALID; - - const char *chroma; - ubuf_pic_foreach_plane(dest, chroma) { - size_t src_stride; - uint8_t src_hsub, src_vsub, src_macropixel_size; - UBASE_RETURN(ubuf_pic_plane_size(src, chroma, &src_stride, - &src_hsub, &src_vsub, &src_macropixel_size)) - - size_t dest_stride; - uint8_t dest_hsub, dest_vsub, dest_macropixel_size; - UBASE_RETURN(ubuf_pic_plane_size(dest, chroma, - &dest_stride, &dest_hsub, &dest_vsub, - &dest_macropixel_size)) - - if (unlikely(src_hsub != dest_hsub || src_vsub != dest_vsub || - src_macropixel_size != dest_macropixel_size)) - return UBASE_ERR_INVALID; - - uint8_t *dest_buffer; - const uint8_t *src_buffer; - UBASE_RETURN(ubuf_pic_plane_write(dest, chroma, - dest_hoffset, dest_voffset, - extract_hsize, extract_vsize, &dest_buffer)) - int err = ubuf_pic_plane_read(src, chroma, src_hoffset, src_voffset, - extract_hsize, extract_vsize, - &src_buffer); - if (unlikely(!ubase_check(err))) { - ubuf_pic_plane_unmap(dest, chroma, - dest_hoffset, dest_voffset, - extract_hsize, extract_vsize); - return err; - } - - int plane_hsize = extract_hsize / src_hsub / src_macropixel * - src_macropixel_size; - int plane_vsize = extract_vsize / src_vsub; - - for (int i = 0; i < plane_vsize; i++) { - uint16_t *real_dst = (uint16_t *)dest_buffer; - const uint16_t *real_src = (const uint16_t *)src_buffer; - const uint16_t *real_alpha = (const uint16_t *)(alpha_plane + alpha_stride * i * src_vsub); - - if ((!alpha_plane && alpha == 0x3ff) || threshold == 0) { - memcpy(dest_buffer, src_buffer, plane_hsize); - } else if (!alpha_plane) { - for (int j = 0; j < plane_hsize/2; j++) { - real_dst[j] = (real_dst[j] * (0x3ff - alpha) + real_src[j] * alpha) / 0x3ff; - } - } else if (threshold != 0x3ff) { - /* This is an on/off blending - * if alpha is over the threshold, we use the subpicture pixel. - */ - if (alpha == 0x3ff) { - for (int j = 0; j < plane_hsize/2; j++) { - const uint16_t a = real_alpha[j * src_hsub]; - if (a > threshold) real_dst[j] = real_src[j]; - } - } else { - for (int j = 0; j < plane_hsize/2; j++) { - const uint16_t a = real_alpha[j * src_hsub] * alpha / 0x3ff; - if (a > threshold) real_dst[j] = real_src[j]; - } - } - } else { - /* smooth and slow blending */ - if (alpha == 0x3ff) { - for (int j = 0; j < plane_hsize/2; j++) { - const uint16_t a = real_alpha[j * src_hsub]; - real_dst[j] = (real_dst[j] * (0x3ff - a) + real_src[j] * a) / 0x3ff; - } - } else { - for (int j = 0; j < plane_hsize/2; j++) { - const uint16_t a = real_alpha[j * src_hsub] * alpha / 0x3ff; - real_dst[j] = (real_dst[j] * (0x3ff - a) + real_src[j] * a) / 0x3ff; - } - } - } - dest_buffer += dest_stride; - src_buffer += src_stride; - } - - err = ubuf_pic_plane_unmap(dest, chroma, - dest_hoffset, dest_voffset, - extract_hsize, extract_vsize); - UBASE_RETURN(ubuf_pic_plane_unmap(src, chroma, - src_hoffset, src_voffset, - extract_hsize, extract_vsize)) - UBASE_RETURN(err) - } - return UBASE_ERR_NONE; -} + const int alpha, const int threshold); /** @This blits a picture ubuf to another ubuf. * @@ -648,61 +389,11 @@ static inline int ubuf_pic_blit_alpha10(struct ubuf *dest, struct ubuf *src, * @param threshold threshold parameter for alpha * @return an error code */ -static inline int ubuf_pic_blit(struct ubuf *dest, struct ubuf *src, +int ubuf_pic_blit(struct ubuf *dest, struct ubuf *src, int dest_hoffset, int dest_voffset, int src_hoffset, int src_voffset, int extract_hsize, int extract_vsize, - const int alpha, const int threshold) -{ - const uint8_t *alpha_plane = NULL; - size_t alpha_stride = 0; - int ret; - - static const char *alpha_plane_names[] = { "a8", "a10l" }; - const char *apn; - for (size_t i = 0; i < UBASE_ARRAY_SIZE(alpha_plane_names); i++) { - apn = alpha_plane_names[i]; - - /* Check for the existence of the given alpha plane. */ - ret = ubuf_pic_plane_read(src, apn, 0, 0, -1, -1, &alpha_plane); - /* Continue to the next if it isn't found. */ - if (!ubase_check(ret)) - continue; - - /* Get the stride. */ - ret = ubuf_pic_plane_size(src, apn, &alpha_stride, NULL, NULL, NULL); - /* Jump out of the loop if there is an error. */ - if (!ubase_check(ret)) - goto end; - - /* Perform blit. */ - if (i == 0) - ret = ubuf_pic_blit_alpha(dest, src, dest_hoffset, dest_voffset, - src_hoffset, src_voffset, - extract_hsize, extract_vsize, - alpha_plane, alpha_stride, alpha, threshold); - else - ret = ubuf_pic_blit_alpha10(dest, src, dest_hoffset, dest_voffset, - src_hoffset, src_voffset, - extract_hsize, extract_vsize, - alpha_plane, alpha_stride, alpha, threshold); - - /* Jump out of the loop when it is done. */ - goto end; - } - - /* Fallback to no alpha plane. */ - ret = ubuf_pic_blit_alpha(dest, src, dest_hoffset, dest_voffset, - src_hoffset, src_voffset, - extract_hsize, extract_vsize, - NULL, 0, alpha, threshold); - -end: - if (alpha_plane) - ubuf_pic_plane_unmap(src, apn, 0, 0, -1, -1); - - return ret; -} + const int alpha, const int threshold); /** @This copies a picture ubuf to a newly allocated ubuf, and doesn't deal * with the old ubuf or a dictionary. diff --git a/lib/upipe/Build.mk b/lib/upipe/Build.mk index 109606200..ce95a4650 100644 --- a/lib/upipe/Build.mk +++ b/lib/upipe/Build.mk @@ -129,6 +129,7 @@ libupipe-src = \ ubuf_mem.c \ ubuf_mem_common.c \ ubuf_pic.c \ + ubuf_pic_blit_x86.h \ ubuf_pic_common.c \ ubuf_pic_mem.c \ ubuf_sound_common.c \ diff --git a/lib/upipe/ubuf_pic.c b/lib/upipe/ubuf_pic.c index fdfd0cfa4..82e31fbb8 100644 --- a/lib/upipe/ubuf_pic.c +++ b/lib/upipe/ubuf_pic.c @@ -18,9 +18,393 @@ #include #if defined(__GNUC__) && defined(__x86_64__) -#include +#include "ubuf_pic_blit_x86.h" #endif +/** @This blits a picture ubuf to another ubuf. + * + * @param dest destination ubuf + * @param src source ubuf + * @param dest_hoffset number of pixels to seek at the beginning of each line of + * dest + * @param dest_voffset number of lines to seek at the beginning of dest + * @param src_hoffset number of pixels to skip at the beginning of each line of + * src + * @param src_voffset number of lines to skip at the beginning of src + * @param extract_hsize horizontal size to copy + * @param extract_vsize vertical size to copy + * @param alpha_plane pointer to alpha plane buffer, if any + * @param alpha_stride horizontal stride of the alpha plane buffer + * @param alpha alpha multiplier + * @param threshold alpha blending method + * 0 means ignore alpha + * 255 means blends src and dest together using alpha levels (slow) + * Any value in between means using the src pixels if and only if + * their alpha value is more than this value + * @return an error code + */ +int ubuf_pic_blit_alpha(struct ubuf *dest, struct ubuf *src, + int dest_hoffset, int dest_voffset, + int src_hoffset, int src_voffset, + int extract_hsize, int extract_vsize, + const uint8_t *alpha_plane, int alpha_stride, + const int alpha, const int threshold) +{ + if (alpha_plane == NULL && alpha < threshold && threshold != 0xff) + return UBASE_ERR_NONE; /* nothing to do */ + + uint8_t src_macropixel; + UBASE_RETURN(ubuf_pic_size(src, NULL, NULL, &src_macropixel)) + uint8_t dest_macropixel; + UBASE_RETURN(ubuf_pic_size(dest, NULL, NULL, &dest_macropixel)) + if (unlikely(dest_macropixel != src_macropixel)) + return UBASE_ERR_INVALID; + +#define MAX_PLANES 3 + + const char *chroma; + ubuf_pic_foreach_plane(dest, chroma) { + const char *in_chroma[MAX_PLANES] = { chroma }; + unsigned in_planes = 1; + int err; + + size_t dest_stride; + uint8_t dest_hsub, dest_vsub, dest_macropixel_size; + UBASE_RETURN(ubuf_pic_plane_size(dest, chroma, + &dest_stride, &dest_hsub, &dest_vsub, + &dest_macropixel_size)) + + size_t src_stride; + uint8_t src_hsub, src_vsub, src_macropixel_size; + err = ubuf_pic_plane_size(src, in_chroma[0], &src_stride, &src_hsub, + &src_vsub, &src_macropixel_size); + if (!ubase_check(err)) { + if (!strcmp(chroma, "u8v8")) { + in_chroma[0] = "u8"; + in_chroma[1] = "v8"; + in_planes = 2; + } else + return err; + + for (unsigned i = 0; i < in_planes; i++) { + UBASE_RETURN(ubuf_pic_plane_size(src, in_chroma[i], &src_stride, + &src_hsub, &src_vsub, + &src_macropixel_size)); + if (unlikely(src_hsub != dest_hsub || src_vsub != dest_vsub || + src_macropixel_size != + dest_macropixel_size / in_planes)) + return UBASE_ERR_INVALID; + } + } else if (unlikely(src_hsub != dest_hsub || src_vsub != dest_vsub || + src_macropixel_size != dest_macropixel_size)) + return UBASE_ERR_INVALID; + + uint8_t *dest_buffer; + const uint8_t *in[MAX_PLANES]; + UBASE_RETURN(ubuf_pic_plane_write(dest, chroma, + dest_hoffset, dest_voffset, + extract_hsize, extract_vsize, &dest_buffer)) + for (unsigned i = 0; i < in_planes; i++) { + err = + ubuf_pic_plane_read(src, in_chroma[i], src_hoffset, src_voffset, + extract_hsize, extract_vsize, &in[i]); + if (unlikely(!ubase_check(err))) { + for (; i; i--) + ubuf_pic_plane_unmap(src, in_chroma[i - 1], src_hoffset, + src_voffset, extract_hsize, + extract_vsize); + ubuf_pic_plane_unmap(dest, chroma, dest_hoffset, dest_voffset, + extract_hsize, extract_vsize); + return err; + } + } + + int plane_hsize = extract_hsize / src_hsub / src_macropixel * + src_macropixel_size; + int plane_vsize = extract_vsize / src_vsub; + + for (int i = 0; i < plane_vsize; i++) { + +#if defined(__GNUC__) && defined(__x86_64__) + if (in_planes == 1 && blit8_handle_cases(dest_buffer, in[0], + alpha_plane + alpha_stride * (i * src_vsub), + alpha, threshold, plane_hsize, src_hsub)) + /* Nothing to do */; + else +#endif + + if ((!alpha_plane && alpha == 0xff) || threshold == 0) { + if (in_planes == 1) + memcpy(dest_buffer, in[0], plane_hsize); + else { + for (int j = 0; j < plane_hsize; j++) { + for (int p = 0; p < in_planes; p++) + dest_buffer[j * in_planes + p] = + (dest_buffer[j * in_planes + p] * + (0xff - alpha) + + in[p][j] * alpha) / + 0xff; + } + } + } else if (!alpha_plane) { + for (int j = 0; j < plane_hsize; j++) { + for (int p = 0; p < in_planes; p++) + dest_buffer[j * in_planes + p] = + (dest_buffer[j * in_planes + p] * (0xff - alpha) + + in[p][j] * alpha) / + 0xff; + } + } else if (threshold != 0xff) { + /* This is an on/off blending + * if alpha is over the threshold, we use the subpicture pixel. + */ + if (alpha == 0xff) { + for (int j = 0; j < plane_hsize; j++) { + const uint8_t a = alpha_plane[alpha_stride * (i * src_vsub) + j * src_hsub]; + if (a > threshold) { + for (int p = 0; p < in_planes; p++) + dest_buffer[j * in_planes + p] = in[p][j]; + } + } + } else { + for (int j = 0; j < plane_hsize; j++) { + const uint8_t a = (uint16_t)alpha_plane[alpha_stride * (i * src_vsub) + j * src_hsub] * (uint16_t)alpha / 0xff; + if (a > threshold) { + for (int p = 0; p < in_planes; p++) + dest_buffer[j * in_planes + p] = in[p][j]; + } + } + } + } else { + /* smooth and slow blending */ + if (alpha == 0xff) { + for (int j = 0; j < plane_hsize; j++) { + const uint8_t a = alpha_plane[alpha_stride * (i * src_vsub) + j * src_hsub]; + for (int p = 0; p < in_planes; p++) + dest_buffer[j * in_planes + p] = + (dest_buffer[j * in_planes + p] * (0xff - a) + + in[p][j] * a) / + 0xff; + } + } else { + for (int j = 0; j < plane_hsize; j++) { + const uint8_t a = (uint16_t)alpha_plane[alpha_stride * (i * src_vsub) + j * src_hsub] * (uint16_t)alpha / 0xff; + for (int p = 0; p < in_planes; p++) + dest_buffer[j * in_planes + p] = + (dest_buffer[j * in_planes + p] * (0xff - a) + + in[p][j] * a) / + 0xff; + } + } + } + dest_buffer += dest_stride; + for (int p = 0; p < in_planes; p++) + in[p] += src_stride; + } + + err = ubuf_pic_plane_unmap(dest, chroma, + dest_hoffset, dest_voffset, + extract_hsize, extract_vsize); + for (int p = 0; p < in_planes; p++) + UBASE_RETURN(ubuf_pic_plane_unmap(src, in_chroma[p], src_hoffset, + src_voffset, extract_hsize, + extract_vsize)) + UBASE_RETURN(err) + } + +#undef MAX_PLANES + + return UBASE_ERR_NONE; +} + +/** @see ubuf_pic_blit_alpha */ +int ubuf_pic_blit_alpha10(struct ubuf *dest, struct ubuf *src, + int dest_hoffset, int dest_voffset, + int src_hoffset, int src_voffset, + int extract_hsize, int extract_vsize, + const uint8_t *alpha_plane, int alpha_stride, + const int alpha, const int threshold) +{ + if (alpha_plane == NULL && alpha < threshold && threshold != 0x3ff) + return UBASE_ERR_NONE; /* nothing to do */ + + uint8_t src_macropixel; + UBASE_RETURN(ubuf_pic_size(src, NULL, NULL, &src_macropixel)) + uint8_t dest_macropixel; + UBASE_RETURN(ubuf_pic_size(dest, NULL, NULL, &dest_macropixel)) + if (unlikely(dest_macropixel != src_macropixel)) + return UBASE_ERR_INVALID; + + const char *chroma; + ubuf_pic_foreach_plane(dest, chroma) { + size_t src_stride; + uint8_t src_hsub, src_vsub, src_macropixel_size; + UBASE_RETURN(ubuf_pic_plane_size(src, chroma, &src_stride, + &src_hsub, &src_vsub, &src_macropixel_size)) + + size_t dest_stride; + uint8_t dest_hsub, dest_vsub, dest_macropixel_size; + UBASE_RETURN(ubuf_pic_plane_size(dest, chroma, + &dest_stride, &dest_hsub, &dest_vsub, + &dest_macropixel_size)) + + if (unlikely(src_hsub != dest_hsub || src_vsub != dest_vsub || + src_macropixel_size != dest_macropixel_size)) + return UBASE_ERR_INVALID; + + uint8_t *dest_buffer; + const uint8_t *src_buffer; + UBASE_RETURN(ubuf_pic_plane_write(dest, chroma, + dest_hoffset, dest_voffset, + extract_hsize, extract_vsize, &dest_buffer)) + int err = ubuf_pic_plane_read(src, chroma, src_hoffset, src_voffset, + extract_hsize, extract_vsize, + &src_buffer); + if (unlikely(!ubase_check(err))) { + ubuf_pic_plane_unmap(dest, chroma, + dest_hoffset, dest_voffset, + extract_hsize, extract_vsize); + return err; + } + + size_t plane_hsize = extract_hsize / src_hsub / src_macropixel * + src_macropixel_size / 2; + size_t plane_vsize = extract_vsize / src_vsub; + + for (size_t i = 0; i < plane_vsize; i++) { + uint16_t *real_dst = (uint16_t *)dest_buffer; + const uint16_t *real_src = (const uint16_t *)src_buffer; + const uint16_t *real_alpha = (const uint16_t *)(alpha_plane + alpha_stride * i * src_vsub); + +#if defined(__GNUC__) && defined(__x86_64__) + if (blit10_handle_cases(real_dst, real_src, real_alpha, alpha, + threshold, plane_hsize, src_hsub)) + /* Nothing to do */; + else +#endif + + if ((!alpha_plane && alpha == 0x3ff) || threshold == 0) { + memcpy(dest_buffer, src_buffer, plane_hsize*2); + } else if (!alpha_plane) { + for (size_t j = 0; j < plane_hsize; j++) { + real_dst[j] = (real_dst[j] * (0x3ff - alpha) + real_src[j] * alpha) / 0x3ff; + } + } else if (threshold != 0x3ff) { + /* This is an on/off blending + * if alpha is over the threshold, we use the subpicture pixel. + */ + if (alpha == 0x3ff) { + for (size_t j = 0; j < plane_hsize; j++) { + const uint16_t a = real_alpha[j * src_hsub]; + if (a > threshold) real_dst[j] = real_src[j]; + } + } else { + for (size_t j = 0; j < plane_hsize; j++) { + const uint16_t a = real_alpha[j * src_hsub] * alpha / 0x3ff; + if (a > threshold) real_dst[j] = real_src[j]; + } + } + } else { + /* smooth and slow blending */ + if (alpha == 0x3ff) { + for (size_t j = 0; j < plane_hsize; j++) { + const uint16_t a = real_alpha[j * src_hsub]; + real_dst[j] = (real_dst[j] * (0x3ff - a) + real_src[j] * a) / 0x3ff; + } + } else { + for (size_t j = 0; j < plane_hsize; j++) { + const uint16_t a = real_alpha[j * src_hsub] * alpha / 0x3ff; + real_dst[j] = (real_dst[j] * (0x3ff - a) + real_src[j] * a) / 0x3ff; + } + } + } + dest_buffer += dest_stride; + src_buffer += src_stride; + } + + err = ubuf_pic_plane_unmap(dest, chroma, + dest_hoffset, dest_voffset, + extract_hsize, extract_vsize); + UBASE_RETURN(ubuf_pic_plane_unmap(src, chroma, + src_hoffset, src_voffset, + extract_hsize, extract_vsize)) + UBASE_RETURN(err) + } + return UBASE_ERR_NONE; +} + +/** @This blits a picture ubuf to another ubuf. + * + * @param dest destination ubuf + * @param src source ubuf + * @param dest_hoffset number of pixels to seek at the beginning of each line of + * dest + * @param dest_voffset number of lines to seek at the beginning of dest + * @param src_hoffset number of pixels to skip at the beginning of each line of + * src + * @param src_voffset number of lines to skip at the beginning of src + * @param extract_hsize horizontal size to copy + * @param extract_vsize vertical size to copy + * @param alpha alpha multiplier + * @param threshold threshold parameter for alpha + * @return an error code + */ +int ubuf_pic_blit(struct ubuf *dest, struct ubuf *src, + int dest_hoffset, int dest_voffset, + int src_hoffset, int src_voffset, + int extract_hsize, int extract_vsize, + const int alpha, const int threshold) +{ + const uint8_t *alpha_plane = NULL; + size_t alpha_stride = 0; + int ret; + + static const char *alpha_plane_names[] = { "a8", "a10l" }; + const char *apn; + for (size_t i = 0; i < UBASE_ARRAY_SIZE(alpha_plane_names); i++) { + apn = alpha_plane_names[i]; + + /* Check for the existence of the given alpha plane. */ + ret = ubuf_pic_plane_read(src, apn, 0, 0, -1, -1, &alpha_plane); + /* Continue to the next if it isn't found. */ + if (!ubase_check(ret)) + continue; + + /* Get the stride. */ + ret = ubuf_pic_plane_size(src, apn, &alpha_stride, NULL, NULL, NULL); + /* Jump out of the loop if there is an error. */ + if (!ubase_check(ret)) + goto end; + + /* Perform blit. */ + if (i == 0) + ret = ubuf_pic_blit_alpha(dest, src, dest_hoffset, dest_voffset, + src_hoffset, src_voffset, + extract_hsize, extract_vsize, + alpha_plane, alpha_stride, alpha, threshold); + else + ret = ubuf_pic_blit_alpha10(dest, src, dest_hoffset, dest_voffset, + src_hoffset, src_voffset, + extract_hsize, extract_vsize, + alpha_plane, alpha_stride, alpha, threshold); + + /* Jump out of the loop when it is done. */ + goto end; + } + + /* Fallback to no alpha plane. */ + ret = ubuf_pic_blit_alpha(dest, src, dest_hoffset, dest_voffset, + src_hoffset, src_voffset, + extract_hsize, extract_vsize, + NULL, 0, alpha, threshold); + +end: + if (alpha_plane) + ubuf_pic_plane_unmap(src, apn, 0, 0, -1, -1); + + return ret; +} + /** @This clears (part of) the specified plane, depending on plane type * and size (set U/V chroma to 0x80 instead of 0 for instance) * diff --git a/lib/upipe/ubuf_pic_blit_x86.h b/lib/upipe/ubuf_pic_blit_x86.h new file mode 100644 index 000000000..b4b4680ab --- /dev/null +++ b/lib/upipe/ubuf_pic_blit_x86.h @@ -0,0 +1,370 @@ +#include +#include +#include + +/* + * It might be easier to make these external assembly, other than handling the + * tail as scalar. gcc cannot inline functions built with different options. + * See this quote from the manual. + * + * On the x86, the inliner does not inline a function that has different target + * options than the caller, unless the callee has a subset of the target options + * of the caller. For example a function declared with target("sse3") can inline + * a function with target("sse2"), since -msse3 implies -msse2. + */ + +/* + * SSE + */ + +__attribute__((target("sse2"))) +static void blit8_threshold_hsub1_sse2(uint8_t *dst_plane, + const uint8_t *src_plane, const uint8_t *alpha_plane, + size_t hsize, int threshold) +{ + size_t j = 0; + const __m128i t = _mm_set1_epi8(threshold); + const __m128i pb_0x80 = _mm_set1_epi8(0x80); + for (j = 0; (j+16) <= hsize; j += 16) { + __m128i dst = _mm_loadu_si128((void*)(dst_plane + j)); + __m128i src = _mm_loadu_si128((void*)(src_plane + j)); + __m128i alp = _mm_loadu_si128((void*)(alpha_plane + j)); + /* Packed compare bytes is for signed bytes so need to do a trick */ + __m128i msk = _mm_cmpgt_epi8(_mm_xor_si128(alp, pb_0x80), _mm_xor_si128(t, pb_0x80)); + src = _mm_and_si128(src, msk); + dst = _mm_andnot_si128(msk, dst); + _mm_storeu_si128((void*)(dst_plane + j), _mm_or_si128(dst, src)); + } + for (/* nothing */; j < hsize; j++) { + const uint8_t a = alpha_plane[j]; + if (a > threshold) { + dst_plane[j] = src_plane[j]; + } + } +} + +__attribute__((target("ssse3"))) +static void blit8_threshold_hsub2_ssse3(uint8_t *dst_plane, + const uint8_t *src_plane, const uint8_t *alpha_plane, + size_t hsize, int threshold) +{ + size_t j = 0; + const __m128i t = _mm_set1_epi8(threshold); + const __m128i pb_0x80 = _mm_set1_epi8(0x80); + const __m128i shuf = _mm_setr_epi8(0,2,4,6,8,10,12,14, -1,-1,-1,-1,-1,-1,-1,-1); + for (j = 0; (j+16) <= hsize; j += 16) { + __m128i dst = _mm_loadu_si128((void*)(dst_plane + j)); + __m128i src = _mm_loadu_si128((void*)(src_plane + j)); + /* Load and subsample alpha by selecting every 2nd word */ + __m128i alp0 = _mm_loadu_si128((void*)(alpha_plane + 2*j)); + __m128i alp1 = _mm_loadu_si128((void*)(alpha_plane + 2*j+16)); + alp0 = _mm_shuffle_epi8(alp0, shuf); + alp1 = _mm_shuffle_epi8(alp1, shuf); + __m128i alp = _mm_unpacklo_epi64(alp0, alp1); + /* Use alpha as normal */ + __m128i msk = _mm_cmpgt_epi8(_mm_xor_si128(alp, pb_0x80), _mm_xor_si128(t, pb_0x80)); + src = _mm_and_si128(src, msk); + dst = _mm_andnot_si128(msk, dst); + _mm_storeu_si128((void*)(dst_plane + j), _mm_or_si128(dst, src)); + } + for (/* nothing */; j < hsize; j++) { + const uint8_t a = alpha_plane[j * 2]; + if (a > threshold) dst_plane[j] = src_plane[j]; + } +} + +__attribute__((target("sse2"))) +static void blit10_threshold_hsub1_sse2(uint16_t *real_dst, + const uint16_t *real_src, const uint16_t *real_alpha, + size_t plane_hsize, int threshold) +{ + size_t j = 0; + const __m128i t = _mm_set1_epi16(threshold); + for (j = 0; (j+8) <= plane_hsize; j += 8) { + __m128i dst = _mm_loadu_si128((void*)(real_dst + j)); + __m128i src = _mm_loadu_si128((void*)(real_src + j)); + __m128i alp = _mm_loadu_si128((void*)(real_alpha + j)); + __m128i msk = _mm_cmpgt_epi16(alp, t); + src = _mm_and_si128(src, msk); + dst = _mm_andnot_si128(msk, dst); + _mm_storeu_si128((void*)(real_dst + j), _mm_or_si128(dst, src)); + } + for (/* nothing */; j < plane_hsize; j++) { + const uint16_t a = real_alpha[j]; + if (a > threshold) real_dst[j] = real_src[j]; + } +} + +__attribute__((target("ssse3"))) +static void blit10_threshold_hsub2_ssse3(uint16_t *real_dst, + const uint16_t *real_src, const uint16_t *real_alpha, + size_t plane_hsize, int threshold) +{ + size_t j = 0; + const __m128i t = _mm_set1_epi16(threshold); + const __m128i shuf = _mm_setr_epi8(0,1, 4,5, 8,9, 12,13, -1,-1,-1,-1,-1,-1,-1,-1); + for (j = 0; (j+8) <= plane_hsize; j += 8) { + __m128i dst = _mm_loadu_si128((void*)(real_dst + j)); + __m128i src = _mm_loadu_si128((void*)(real_src + j)); + /* Load and subsample alpha by selecting every 2nd word */ + __m128i alp0 = _mm_loadu_si128((void*)(real_alpha + 2*j)); + __m128i alp1 = _mm_loadu_si128((void*)(real_alpha + 2*j+8)); + alp0 = _mm_shuffle_epi8(alp0, shuf); + alp1 = _mm_shuffle_epi8(alp1, shuf); + __m128i alp = _mm_unpacklo_epi64(alp0, alp1); + /* Use alpha as normal */ + __m128i msk = _mm_cmpgt_epi16(alp, t); + src = _mm_and_si128(src, msk); + dst = _mm_andnot_si128(msk, dst); + _mm_storeu_si128((void*)(real_dst + j), _mm_or_si128(dst, src)); + } + for (/* nothing */; j < plane_hsize; j++) { + const uint16_t a = real_alpha[j * 2]; + if (a > threshold) real_dst[j] = real_src[j]; + } +} + +/* + * AVX2 + */ + +#if __GNUC__ >= 5 + +__attribute__((target("avx2"))) +static void blit8_threshold_hsub1_avx2(uint8_t *dst_plane, + const uint8_t *src_plane, const uint8_t *alpha_plane, + size_t hsize, int threshold) +{ + size_t j = 0; + const __m256i t = _mm256_set1_epi8(threshold); + const __m256i pb_0x80 = _mm256_set1_epi8(0x80); + for (j = 0; (j+32) <= hsize; j += 32) { + __m256i dst = _mm256_loadu_si256((void*)(dst_plane + j)); + __m256i src = _mm256_loadu_si256((void*)(src_plane + j)); + __m256i alp = _mm256_loadu_si256((void*)(alpha_plane + j)); + /* Packed compare bytes is for signed bytes so need to do a trick */ + __m256i msk = _mm256_cmpgt_epi8(_mm256_xor_si256(alp, pb_0x80), _mm256_xor_si256(t, pb_0x80)); + src = _mm256_and_si256(src, msk); + dst = _mm256_andnot_si256(msk, dst); + _mm256_storeu_si256((void*)(dst_plane + j), _mm256_or_si256(dst, src)); + } + for (/* nothing */; j < hsize; j++) { + const uint8_t a = alpha_plane[j]; + if (a > threshold) { + dst_plane[j] = src_plane[j]; + } + } +} + +__attribute__((target("avx2"))) +static void blit8_threshold_hsub2_avx2(uint8_t *dst_plane, + const uint8_t *src_plane, const uint8_t *alpha_plane, + size_t hsize, int threshold) +{ + size_t j = 0; + const __m256i t = _mm256_set1_epi8(threshold); + const __m256i pb_0x80 = _mm256_set1_epi8(0x80); + const __m256i shuf = _mm256_broadcastsi128_si256(_mm_setr_epi8(0,2,4,6,8,10,12,14, -1,-1,-1,-1,-1,-1,-1,-1)); + for (j = 0; (j+32) <= hsize; j += 32) { + __m256i dst = _mm256_loadu_si256((void*)(dst_plane + j)); + __m256i src = _mm256_loadu_si256((void*)(src_plane + j)); + /* Load and subsample alpha by selecting every 2nd word */ + __m256i alp0 = _mm256_loadu_si256((void*)(alpha_plane + 2*j)); + __m256i alp1 = _mm256_loadu_si256((void*)(alpha_plane + 2*j+32)); + alp0 = _mm256_shuffle_epi8(alp0, shuf); + alp1 = _mm256_shuffle_epi8(alp1, shuf); + __m256i alp = _mm256_permute4x64_epi64(_mm256_unpacklo_epi64(alp0, alp1), 0|2<<2|1<<4|3<<6); + /* Packed compare bytes is for signed bytes so need to do a trick */ + __m256i msk = _mm256_cmpgt_epi8(_mm256_xor_si256(alp, pb_0x80), _mm256_xor_si256(t, pb_0x80)); + src = _mm256_and_si256(src, msk); + dst = _mm256_andnot_si256(msk, dst); + _mm256_storeu_si256((void*)(dst_plane + j), _mm256_or_si256(dst, src)); + } + for (/* nothing */; j < hsize; j++) { + const uint8_t a = alpha_plane[j * 2]; + if (a > threshold) dst_plane[j] = src_plane[j]; + } +} + +__attribute__((target("avx2"))) +static void blit10_threshold_hsub1_avx2(uint16_t *real_dst, + const uint16_t *real_src, const uint16_t *real_alpha, + size_t plane_hsize, int threshold) +{ + size_t j = 0; + const __m256i t = _mm256_set1_epi16(threshold); + for (j = 0; (j+16) <= plane_hsize; j += 16) { + __m256i dst = _mm256_loadu_si256((void*)(real_dst + j)); + __m256i src = _mm256_loadu_si256((void*)(real_src + j)); + __m256i alp = _mm256_loadu_si256((void*)(real_alpha + j)); + __m256i msk = _mm256_cmpgt_epi16(alp, t); + src = _mm256_and_si256(src, msk); + dst = _mm256_andnot_si256(msk, dst); + _mm256_storeu_si256((void*)(real_dst + j), _mm256_or_si256(dst, src)); + } + for (/* nothing */; j < plane_hsize; j++) { + const uint16_t a = real_alpha[j]; + if (a > threshold) real_dst[j] = real_src[j]; + } +} + +__attribute__((target("avx2"))) +static void blit10_threshold_hsub2_avx2(uint16_t *real_dst, + const uint16_t *real_src, const uint16_t *real_alpha, + size_t plane_hsize, int threshold) +{ + size_t j = 0; + const __m256i t = _mm256_set1_epi16(threshold); + const __m256i shuf = _mm256_broadcastsi128_si256(_mm_setr_epi8(0,1, 4,5, 8,9, 12,13, -1,-1,-1,-1,-1,-1,-1,-1)); + for (j = 0; (j+16) <= plane_hsize; j += 16) { + __m256i dst = _mm256_loadu_si256((void*)(real_dst + j)); + __m256i src = _mm256_loadu_si256((void*)(real_src + j)); + /* Load and subsample alpha by selecting every 2nd word */ + __m256i alp0 = _mm256_loadu_si256((void*)(real_alpha + 2*j)); + __m256i alp1 = _mm256_loadu_si256((void*)(real_alpha + 2*j+16)); + alp0 = _mm256_shuffle_epi8(alp0, shuf); + alp1 = _mm256_shuffle_epi8(alp1, shuf); + __m256i alp = _mm256_permute4x64_epi64(_mm256_unpacklo_epi64(alp0, alp1), 0|2<<2|1<<4|3<<6); + /* Use alpha as normal */ + __m256i msk = _mm256_cmpgt_epi16(alp, t); + src = _mm256_and_si256(src, msk); + dst = _mm256_andnot_si256(msk, dst); + _mm256_storeu_si256((void*)(real_dst + j), _mm256_or_si256(dst, src)); + } + for (/* nothing */; j < plane_hsize; j++) { + const uint16_t a = real_alpha[j * 2]; + if (a > threshold) real_dst[j] = real_src[j]; + } +} + +#endif /* if __GNUC__ >= 5 */ + +/* + * @This handle particular blit cases. Exists to avoid more nested ifs. + * + * @param dst destination pointer + * @param src source pointer + * @param alpha alpha plane pointer + * @param alpha_multiplier alpha multiplier + * @param threshold alpha blending method + * @param hsize width in samples + * @param hsub source and destination plane horizontal subsampling + * + * TODO: whole planes + */ + +static inline bool blit8_handle_cases(uint8_t *dst, const uint8_t *src, + const uint8_t *alpha, int alpha_multiplier, int threshold, + size_t hsize, uint8_t hsub) +{ + /* Duplicating a cases not handled here to mirror the logic in caller. */ + if ((!alpha && alpha_multiplier == 0xff) || threshold == 0) { + /* Caller does a memcpy */ + return false; + } + else if (!alpha) { + /* Caller does a blend */ + return false; + } + else if (hsize < 16) { + /* Let caller handle small blits */ + return false; + } + + else if (threshold != 0xff && alpha_multiplier == 0xff) { + if (hsub == 1) { +#if __GNUC__ >= 5 + if (__builtin_cpu_supports("avx2")) { + blit8_threshold_hsub1_avx2(dst, src, alpha, hsize, threshold); + return true; + } +#endif + if (__builtin_cpu_supports("sse2")) { + blit8_threshold_hsub1_sse2(dst, src, alpha, hsize, threshold); + return true; + } + return false; + } + + if (hsub == 2) { +#if __GNUC__ >= 5 + if (__builtin_cpu_supports("avx2")) { + blit8_threshold_hsub2_avx2(dst, src, alpha, hsize, threshold); + return true; + } +#endif + if (__builtin_cpu_supports("ssse3")) { + blit8_threshold_hsub2_ssse3(dst, src, alpha, hsize, threshold); + return true; + } + return false; + } + return false; + } + return false; +} + +/* + * @This handle particular blit cases. Exists to avoid more nested ifs. + * + * @param dst destination pointer + * @param src source pointer + * @param alpha alpha plane pointer + * @param alpha_multiplier alpha multiplier + * @param threshold alpha blending method + * @param hsize width in samples + * @param hsub source and destination plane horizontal subsampling + * + * TODO: whole planes + */ + +static inline bool blit10_handle_cases(uint16_t *dst, const uint16_t *src, + const uint16_t *alpha, int alpha_multiplier, int threshold, + size_t hsize, uint8_t hsub) +{ + /* Duplicating a cases not handled here to mirror the logic in caller. */ + if ((!alpha && alpha_multiplier == 0x3ff) || threshold == 0) { + /* Caller does a memcpy */ + return false; + } + else if (!alpha) { + /* Caller does a blend */ + return false; + } + else if (hsize < 8) { + /* Let caller handle small blits */ + return false; + } + + else if (threshold != 0x3ff && alpha_multiplier == 0x3ff) { + if (hsub == 1) { +#if __GNUC__ >= 5 + if (__builtin_cpu_supports("avx2")) { + blit10_threshold_hsub1_avx2(dst, src, alpha, hsize, threshold); + return true; + } +#endif + if (__builtin_cpu_supports("sse2")) { + blit10_threshold_hsub1_sse2(dst, src, alpha, hsize, threshold); + return true; + } + return false; + } + + if (hsub == 2) { +#if __GNUC__ >= 5 + if (__builtin_cpu_supports("avx2")) { + blit10_threshold_hsub2_avx2(dst, src, alpha, hsize, threshold); + return true; + } +#endif + if (__builtin_cpu_supports("ssse3")) { + blit10_threshold_hsub2_ssse3(dst, src, alpha, hsize, threshold); + return true; + } + return false; + } + return false; + } + return false; +} diff --git a/tests/Build.mk b/tests/Build.mk index bd6ef4369..1901de881 100644 --- a/tests/Build.mk +++ b/tests/Build.mk @@ -30,6 +30,10 @@ tests += ubuf_block_mem_test ubuf_block_mem_test-src = ubuf_block_mem_test.c ubuf_block_mem_test-libs = libupipe +tests += ubuf_pic_blit_test +ubuf_pic_blit_test-src = ubuf_pic_blit_test.c +ubuf_pic_blit_test-libs = libupipe + tests += ubuf_pic_clear_test ubuf_pic_clear_test-src = ubuf_pic_clear_test.c ubuf_pic_clear_test-libs = libupipe diff --git a/tests/ubuf_pic_blit_test.c b/tests/ubuf_pic_blit_test.c new file mode 100644 index 000000000..05cea5a3c --- /dev/null +++ b/tests/ubuf_pic_blit_test.c @@ -0,0 +1,1539 @@ +/* + * Copyright (C) 2026 Open Broadcast Systems + * + * Authors: James Darnley + * + * SPDX-License-Identifier: MIT + */ + +/** @file + * @short unit tests for picture blit + */ + +#undef NDEBUG + +#include "upipe/umem.h" +#include "upipe/umem_alloc.h" +#include "upipe/ubuf.h" +#include "upipe/ubuf_pic.h" +#include "upipe/ubuf_pic_mem.h" + +#include +#include +#include +#include +#include +#include + +#define UBUF_POOL_DEPTH 1 +#define UBUF_PREPEND 2 +#define UBUF_APPEND 2 +#define UBUF_ALIGN 16 +#define UBUF_ALIGN_HOFFSET 0 + +/** @internal @This fills a rectangular region of a plane with a diagonal + * gradient: 0 at the top-left corner (0, 0), rising to 255 at the bottom-right + * corner (hsize, vsize). The offset and size are expressed in luma samples. */ +static void fill_plane_gradient(struct ubuf *ubuf, const char *chroma, + int hoffset, int voffset, int hsize, int vsize) +{ + uint8_t macropixel; + ubase_assert(ubuf_pic_size(ubuf, NULL, NULL, ¯opixel)); + + size_t stride; + uint8_t hsub, vsub, macropixel_size; + ubase_assert(ubuf_pic_plane_size(ubuf, chroma, &stride, &hsub, &vsub, + ¯opixel_size)); + int hoctets = hsize * macropixel_size / hsub / macropixel; + int rows = vsize / vsub; + + uint8_t *buffer; + ubase_assert(ubuf_pic_plane_write(ubuf, chroma, hoffset, voffset, + hsize, vsize, &buffer)); + for (int y = 0; y < rows; y++) { + for (int x = 0; x < hoctets; x++) + buffer[x] = 255 * (x + y) / (hoctets + rows); + buffer += stride; + } + ubase_assert(ubuf_pic_plane_unmap(ubuf, chroma, hoffset, voffset, + hsize, vsize)); +} + +/** @internal @This fills a rectangular region of a 16-bit plane with a diagonal + * gradient: 0 at the top-left corner (0, 0), rising to 1023 at the bottom-right + * corner (hsize, vsize). The offset and size are expressed in luma samples. */ +static void fill_plane_gradient16(struct ubuf *ubuf, const char *chroma, + int hoffset, int voffset, int hsize, int vsize) +{ + uint8_t macropixel; + ubase_assert(ubuf_pic_size(ubuf, NULL, NULL, ¯opixel)); + + size_t stride; + uint8_t hsub, vsub, macropixel_size; + ubase_assert(ubuf_pic_plane_size(ubuf, chroma, &stride, &hsub, &vsub, + ¯opixel_size)); + int cols = hsize * macropixel_size / hsub / macropixel / 2; + int rows = vsize / vsub; + + uint8_t *buffer; + ubase_assert(ubuf_pic_plane_write(ubuf, chroma, hoffset, voffset, + hsize, vsize, &buffer)); + for (int y = 0; y < rows; y++) { + uint16_t *words = (uint16_t *)buffer; + for (int x = 0; x < cols; x++) + words[x] = 1023 * (x + y) / (cols + rows); + buffer += stride; + } + ubase_assert(ubuf_pic_plane_unmap(ubuf, chroma, hoffset, voffset, + hsize, vsize)); +} + +/** @internal @This reads the first octet of a plane. */ +static uint8_t read_octet(struct ubuf *ubuf, const char *chroma) +{ + const uint8_t *buffer; + ubase_assert(ubuf_pic_plane_read(ubuf, chroma, 0, 0, -1, -1, &buffer)); + uint8_t value = buffer[0]; + ubase_assert(ubuf_pic_plane_unmap(ubuf, chroma, 0, 0, -1, -1)); + return value; +} + +/** @internal @This reads the first 16-bit sample of a plane (host byte order). */ +static uint16_t read_word(struct ubuf *ubuf, const char *chroma) +{ + const uint8_t *buffer; + ubase_assert(ubuf_pic_plane_read(ubuf, chroma, 0, 0, -1, -1, &buffer)); + uint16_t value = ((const uint16_t *)buffer)[0]; + ubase_assert(ubuf_pic_plane_unmap(ubuf, chroma, 0, 0, -1, -1)); + return value; +} + +/** @internal @This computes the value ubuf_pic_blit() is expected to leave in + * a destination octet, replicating exactly the integer arithmetic of + * ubuf_pic_blit_alpha() for the case where the source carries an "a8" alpha + * plane. + * + * @param dst destination octet value + * @param src source octet value + * @param a8 alpha plane octet value for this pixel + * @param alpha alpha multiplier passed to ubuf_pic_blit() + * @param threshold threshold passed to ubuf_pic_blit() + * @return the octet value expected in dst after the blit + */ +static uint8_t expected_blit(uint8_t dst, uint8_t src, uint8_t a8, + int alpha, int threshold) +{ + /* threshold == 0: straight copy (memcpy path). */ + if (threshold == 0) + return src; + + /* alpha == 0xff with threshold in ]0, 0xff[: on/off blending that uses the + * alpha plane sample directly, without scaling. */ + if (alpha == 0xff && threshold != 0xff) { + if (a8 > threshold) + return src; + else + return dst; + } + + /* alpha != 0xff with threshold in ]0, 0xff[: on/off blending that scales the + * alpha plane sample by the multiplier first. */ + if (alpha != 0xff && threshold != 0xff) { + uint8_t a = (uint8_t)((uint16_t)a8 * (uint16_t)alpha / 0xff); + if (a > threshold) + return src; + else + return dst; + } + + /* alpha == 0xff with threshold == 0xff: smooth (per-pixel) blending that + * uses the alpha plane sample directly. */ + if (alpha == 0xff && threshold == 0xff) { + return (uint8_t)((dst * (0xff - a8) + src * a8) / 0xff); + } else { + /* alpha != 0xff with threshold == 0xff: smooth blending with the scaled + * alpha plane sample. */ + uint8_t a = (uint8_t)((uint16_t)a8 * (uint16_t)alpha / 0xff); + return (uint8_t)((dst * (0xff - a) + src * a) / 0xff); + } +} + +/** @internal @This computes the value ubuf_pic_blit() is expected to leave in a + * destination 16-bit sample, replicating exactly the integer arithmetic of + * ubuf_pic_blit_alpha10() for the case where the source carries an "a10l" alpha + * plane. The saturation values are 10-bit (0x3ff) instead of 8-bit (0xff). + * + * @param dst destination sample value + * @param src source sample value + * @param a10 alpha plane sample value for this pixel + * @param alpha alpha multiplier passed to ubuf_pic_blit() + * @param threshold threshold passed to ubuf_pic_blit() + * @return the sample value expected in dst after the blit + */ +static uint16_t expected_blit10(uint16_t dst, uint16_t src, uint16_t a10, + int alpha, int threshold) +{ + /* threshold == 0: straight copy (memcpy path). */ + if (threshold == 0) + return src; + + /* alpha == 0x3ff with threshold in ]0, 0x3ff[: on/off blending that uses the + * alpha plane sample directly, without scaling. */ + if (alpha == 0x3ff && threshold != 0x3ff) { + if (a10 > threshold) + return src; + else + return dst; + } + + /* alpha != 0x3ff with threshold in ]0, 0x3ff[: on/off blending that scales + * the alpha plane sample by the multiplier first. */ + if (alpha != 0x3ff && threshold != 0x3ff) { + uint16_t a = (uint16_t)(a10 * alpha / 0x3ff); + if (a > threshold) + return src; + else + return dst; + } + + /* alpha == 0x3ff with threshold == 0x3ff: smooth (per-pixel) blending that + * uses the alpha plane sample directly. */ + if (alpha == 0x3ff && threshold == 0x3ff) { + return (uint16_t)((dst * (0x3ff - a10) + src * a10) / 0x3ff); + } else { + /* alpha != 0x3ff with threshold == 0x3ff: smooth blending with the + * scaled alpha plane sample. */ + uint16_t a = (uint16_t)(a10 * alpha / 0x3ff); + return (uint16_t)((dst * (0x3ff - a) + src * a) / 0x3ff); + } +} + +/** @internal @This computes the value ubuf_pic_blit() is expected to leave in + * a destination octet when the source carries no alpha plane, i.e. when the + * fallback call to ubuf_pic_blit_alpha() passes alpha_plane == NULL. The alpha + * multiplier is then applied uniformly to the whole plane. + * + * @param dst destination octet value + * @param src source octet value + * @param alpha alpha multiplier passed to ubuf_pic_blit() + * @param threshold threshold passed to ubuf_pic_blit() + * @return the octet value expected in dst after the blit + */ +static uint8_t expected_blit_noalpha(uint8_t dst, uint8_t src, + int alpha, int threshold) +{ + /* "nothing to do" early return: dst is left untouched. */ + if (alpha < threshold && threshold != 0xff) + return dst; + + /* alpha == 0xff or threshold == 0: straight copy (memcpy path). */ + if (alpha == 0xff || threshold == 0) + return src; + + /* Otherwise a uniform blend with the scalar alpha. */ + return (uint8_t)((dst * (0xff - alpha) + src * alpha) / 0xff); +} + +static uint16_t expected_blit10_noalpha(uint16_t dst, uint16_t src, + int alpha, int threshold) +{ + /* "nothing to do" early return: dst is left untouched. */ + if (alpha < threshold && threshold != 0x3ff) + return dst; + + /* alpha == 0x3ff or threshold == 0: straight copy (memcpy path). */ + if (alpha == 0x3ff || threshold == 0) + return src; + + /* Otherwise a uniform blend with the scalar alpha. */ + return (uint16_t)((dst * (0x3ff - alpha) + src * alpha) / 0x3ff); +} + +/** @internal @This checks the result of a yuv420p blit whose source carries an + * "a8" alpha plane. It reads that plane and compares every sampled dst octet on + * the y8/u8/v8 planes against expected_blit(), returning the number of checks + * performed. */ +static unsigned long check_yuv420p_blit(struct ubuf *dst, struct ubuf *src, + int alpha, int threshold) +{ + static const struct { int x, y; } points[] = { + { 8, 8 }, { 32, 8 }, { 56, 8 }, + { 8, 32 }, { 32, 32 }, { 56, 32 }, + { 8, 56 }, { 32, 56 }, { 56, 56 }, + { 16, 16 }, { 47, 47 }, + }; + size_t stride, a_stride; + const uint8_t *buffer; + const uint8_t *a_buffer; + unsigned long checks = 0; + + ubase_assert(ubuf_pic_plane_size(src, "a8", &a_stride, NULL, NULL, NULL)); + ubase_assert(ubuf_pic_plane_read(src, "a8", 0, 0, 64, 64, &a_buffer)); + + /* y8: dst's 16 blended with src's 235 by the alpha plane sample. */ + ubase_assert(ubuf_pic_plane_size(dst, "y8", &stride, NULL, NULL, NULL)); + ubase_assert(ubuf_pic_plane_read(dst, "y8", 0, 0, 64, 64, &buffer)); + for (size_t i = 0; i < UBASE_ARRAY_SIZE(points); i++) { + int x = points[i].x, y = points[i].y; + uint8_t a8 = a_buffer[y * a_stride + x]; + uint8_t got = buffer[y * stride + x]; + uint8_t exp = expected_blit(16, 235, a8, alpha, threshold); + if (got != exp) + fprintf(stderr, "mismatch on y8 at (%d, %d): got %u, expected %u\n", + x, y, got, exp); + assert(got == exp); + checks++; + } + ubase_assert(ubuf_pic_plane_unmap(dst, "y8", 0, 0, 64, 64)); + + /* u8: dst's 128 blended with src's 64 by the alpha plane sample. */ + ubase_assert(ubuf_pic_plane_size(dst, "u8", &stride, NULL, NULL, NULL)); + ubase_assert(ubuf_pic_plane_read(dst, "u8", 0, 0, 64, 64, &buffer)); + for (size_t i = 0; i < UBASE_ARRAY_SIZE(points); i++) { + int x = points[i].x, y = points[i].y; + uint8_t a8 = a_buffer[(y & ~1) * a_stride + (x & ~1)]; + uint8_t got = buffer[(y / 2) * stride + (x / 2)]; + uint8_t exp = expected_blit(128, 64, a8, alpha, threshold); + if (got != exp) + fprintf(stderr, "mismatch on u8 at (%d, %d): got %u, expected %u\n", + x, y, got, exp); + assert(got == exp); + checks++; + } + ubase_assert(ubuf_pic_plane_unmap(dst, "u8", 0, 0, 64, 64)); + + /* v8: dst's 128 blended with src's 192 by the alpha plane sample. */ + ubase_assert(ubuf_pic_plane_size(dst, "v8", &stride, NULL, NULL, NULL)); + ubase_assert(ubuf_pic_plane_read(dst, "v8", 0, 0, 64, 64, &buffer)); + for (size_t i = 0; i < UBASE_ARRAY_SIZE(points); i++) { + int x = points[i].x, y = points[i].y; + uint8_t a8 = a_buffer[(y & ~1) * a_stride + (x & ~1)]; + uint8_t got = buffer[(y / 2) * stride + (x / 2)]; + uint8_t exp = expected_blit(128, 192, a8, alpha, threshold); + if (got != exp) + fprintf(stderr, "mismatch on v8 at (%d, %d): got %u, expected %u\n", + x, y, got, exp); + assert(got == exp); + checks++; + } + ubase_assert(ubuf_pic_plane_unmap(dst, "v8", 0, 0, 64, 64)); + + ubase_assert(ubuf_pic_plane_unmap(src, "a8", 0, 0, 64, 64)); + return checks; +} + +/** @internal @This checks the result of a yuv420p10le blit whose source carries + * an "a10l" alpha plane. It reads that plane and compares every sampled dst word + * on the y10l/u10l/v10l planes against expected_blit10(), returning the number + * of checks performed. */ +static unsigned long check_yuv420p10le_blit(struct ubuf *dst, struct ubuf *src, + int alpha, int threshold) +{ + static const struct { int x, y; } points[] = { + { 8, 8 }, { 32, 8 }, { 56, 8 }, + { 8, 32 }, { 32, 32 }, { 56, 32 }, + { 8, 56 }, { 32, 56 }, { 56, 56 }, + { 16, 16 }, { 47, 47 }, + }; + size_t stride, a_stride; + const uint8_t *buffer; + const uint8_t *a_buffer; + unsigned long checks = 0; + + ubase_assert(ubuf_pic_plane_size(src, "a10l", &a_stride, NULL, NULL, NULL)); + ubase_assert(ubuf_pic_plane_read(src, "a10l", 0, 0, 64, 64, &a_buffer)); + + /* y10l: dst's 64 blended with src's 940 by the alpha plane sample. */ + ubase_assert(ubuf_pic_plane_size(dst, "y10l", &stride, NULL, NULL, NULL)); + ubase_assert(ubuf_pic_plane_read(dst, "y10l", 0, 0, 64, 64, &buffer)); + for (size_t i = 0; i < UBASE_ARRAY_SIZE(points); i++) { + int x = points[i].x, y = points[i].y; + uint16_t a10 = ((const uint16_t *)(a_buffer + y * a_stride))[x]; + uint16_t got = ((const uint16_t *)(buffer + y * stride))[x]; + uint16_t exp = expected_blit10(64, 940, a10, alpha, threshold); + if (got != exp) + fprintf(stderr, "mismatch on y10l at (%d, %d): got %u, expected %u\n", + x, y, got, exp); + assert(got == exp); + checks++; + } + ubase_assert(ubuf_pic_plane_unmap(dst, "y10l", 0, 0, 64, 64)); + + /* u10l: dst's 512 blended with src's 256 by the alpha plane sample. */ + ubase_assert(ubuf_pic_plane_size(dst, "u10l", &stride, NULL, NULL, NULL)); + ubase_assert(ubuf_pic_plane_read(dst, "u10l", 0, 0, 64, 64, &buffer)); + for (size_t i = 0; i < UBASE_ARRAY_SIZE(points); i++) { + int x = points[i].x, y = points[i].y; + uint16_t a10 = ((const uint16_t *)(a_buffer + (y & ~1) * a_stride))[x & ~1]; + uint16_t got = ((const uint16_t *)(buffer + (y / 2) * stride))[x / 2]; + uint16_t exp = expected_blit10(512, 256, a10, alpha, threshold); + if (got != exp) + fprintf(stderr, "mismatch on u10l at (%d, %d): got %u, expected %u\n", + x, y, got, exp); + assert(got == exp); + checks++; + } + ubase_assert(ubuf_pic_plane_unmap(dst, "u10l", 0, 0, 64, 64)); + + /* v10l: dst's 512 blended with src's 768 by the alpha plane sample. */ + ubase_assert(ubuf_pic_plane_size(dst, "v10l", &stride, NULL, NULL, NULL)); + ubase_assert(ubuf_pic_plane_read(dst, "v10l", 0, 0, 64, 64, &buffer)); + for (size_t i = 0; i < UBASE_ARRAY_SIZE(points); i++) { + int x = points[i].x, y = points[i].y; + uint16_t a10 = ((const uint16_t *)(a_buffer + (y & ~1) * a_stride))[x & ~1]; + uint16_t got = ((const uint16_t *)(buffer + (y / 2) * stride))[x / 2]; + uint16_t exp = expected_blit10(512, 768, a10, alpha, threshold); + if (got != exp) + fprintf(stderr, "mismatch on v10l at (%d, %d): got %u, expected %u\n", + x, y, got, exp); + assert(got == exp); + checks++; + } + ubase_assert(ubuf_pic_plane_unmap(dst, "v10l", 0, 0, 64, 64)); + + ubase_assert(ubuf_pic_plane_unmap(src, "a10l", 0, 0, 64, 64)); + return checks; +} + +int main(int argc, char **argv) +{ + struct umem_mgr *umem_mgr = umem_alloc_mgr_alloc(); + assert(umem_mgr != NULL); + + struct ubuf_mgr *dst_mgr, *src_mgr; + struct ubuf *dst, *src; + unsigned long checks; + int alpha, threshold; + + /* 8-bit alpha blending: "a8" alpha plane, saturation value 0xff. */ + + /* dst: single luma plane. */ + dst_mgr = ubuf_pic_mem_mgr_alloc(UBUF_POOL_DEPTH, UBUF_POOL_DEPTH, umem_mgr, 1, + UBUF_PREPEND, UBUF_APPEND, + UBUF_PREPEND, UBUF_APPEND, + UBUF_ALIGN, UBUF_ALIGN_HOFFSET); + assert(dst_mgr != NULL); + ubase_assert(ubuf_pic_mem_mgr_add_plane(dst_mgr, "y8", 1, 1, 1)); + + /* src: luma plane plus an 8-bit alpha plane. */ + src_mgr = ubuf_pic_mem_mgr_alloc(UBUF_POOL_DEPTH, UBUF_POOL_DEPTH, umem_mgr, 1, + UBUF_PREPEND, UBUF_APPEND, + UBUF_PREPEND, UBUF_APPEND, + UBUF_ALIGN, UBUF_ALIGN_HOFFSET); + assert(src_mgr != NULL); + ubase_assert(ubuf_pic_mem_mgr_add_plane(src_mgr, "y8", 1, 1, 1)); + ubase_assert(ubuf_pic_mem_mgr_add_plane(src_mgr, "a8", 1, 1, 1)); + + dst = ubuf_pic_alloc(dst_mgr, 1, 1); + assert(dst != NULL); + src = ubuf_pic_alloc(src_mgr, 1, 1); + assert(src != NULL); + + checks = 0; + + /* The copy branch (threshold == 0) is a plain memcpy of src, independent + * of both the alpha plane and the alpha multiplier, so a handful of + * representative values suffice. It is tested here rather than in the + * sweep below, where it would otherwise be repeated for every (a, alpha) + * pair without adding coverage. */ + { + static const int copy_a[] = { 0, 1, 128, 254, 255 }; + static const int copy_alpha[] = { 0, 128, 255 }; + for (size_t i = 0; i < UBASE_ARRAY_SIZE(copy_a); i++) { + ubase_assert(ubuf_pic_plane_set_color(src, "y8", 0, 0, 1, 1, + (const uint8_t []){ 255 }, 1)); + ubase_assert(ubuf_pic_plane_set_color(src, "a8", 0, 0, 1, 1, + (const uint8_t []){ copy_a[i] }, 1)); + for (size_t k = 0; k < UBASE_ARRAY_SIZE(copy_alpha); k++) { + ubase_assert(ubuf_pic_plane_set_color(dst, "y8", 0, 0, 1, 1, + (const uint8_t []){ 0 }, 1)); + ubase_assert(ubuf_pic_blit(dst, src, 0, 0, 0, 0, 1, 1, + copy_alpha[k], 0)); + uint8_t got = read_octet(dst, "y8"); + uint8_t exp = expected_blit(0, 255, copy_a[i], copy_alpha[k], 0); + assert(got == exp); + checks++; + } + } + } + + /* For every possible alpha plane value, exercise ubuf_pic_blit() with a + * spread of alpha/threshold values covering its remaining branches: + * - 0 < threshold < 0xff -> on/off blending, both with alpha == 0xff + * (raw alpha plane) and alpha != 0xff + * (scaled alpha plane) + * - threshold == 0xff -> smooth blending, again both alpha cases + * The values a-1, a and a+1 straddle the threshold comparison boundary. + * threshold == 0 (copy) is dropped from the sweep and tested above. */ + for (int a = 0; a <= 255; a++) { + /* src is constant across the inner loop: opaque luma, alpha = a. */ + ubase_assert(ubuf_pic_plane_set_color(src, "y8", 0, 0, 1, 1, + (const uint8_t []){ 255 }, 1)); + ubase_assert(ubuf_pic_plane_set_color(src, "a8", 0, 0, 1, 1, + (const uint8_t []){ a }, 1)); + + const int alpha_candidates[] = { 0, 255, a-32, a-1, a, a+1, a+32 }; + const int threshold_candidates[] = { 255, a-32, a-1, a, a+1, a+32 }; + + for (size_t ai = 0; ai < UBASE_ARRAY_SIZE(alpha_candidates); ai++) { + alpha = alpha_candidates[ai]; + if (alpha < 0 || alpha > 255) + continue; + + for (size_t ti = 0; ti < UBASE_ARRAY_SIZE(threshold_candidates); ti++) { + threshold = threshold_candidates[ti]; + if (threshold < 0 || threshold > 255) + continue; + + /* dst is reset before every call since blit overwrites it. */ + ubase_assert(ubuf_pic_plane_set_color(dst, "y8", 0, 0, 1, 1, + (const uint8_t []){ 0 }, 1)); + + ubase_assert(ubuf_pic_blit(dst, src, 0, 0, 0, 0, 1, 1, + alpha, threshold)); + + uint8_t got = read_octet(dst, "y8"); + uint8_t exp = expected_blit(0, 255, a, alpha, threshold); + if (got != exp) + fprintf(stderr, "mismatch: a=%d alpha=%d threshold=%d: " + "got %u, expected %u\n", + a, alpha, threshold, got, exp); + assert(got == exp); + checks++; + } + } + } + + printf("%lu 8-bit blit checks passed\n", checks); + + ubuf_free(src); + ubuf_free(dst); + ubuf_mgr_release(src_mgr); + ubuf_mgr_release(dst_mgr); + + /* 10-bit alpha blending: "a10l" alpha plane, saturation value 0x3ff. + * Identical in structure to the 8-bit section above, but the samples are + * 16-bit little-endian and the range spans 0 to 1023. */ + + /* dst: single 10-bit luma plane. */ + dst_mgr = ubuf_pic_mem_mgr_alloc(UBUF_POOL_DEPTH, UBUF_POOL_DEPTH, umem_mgr, 1, + UBUF_PREPEND, UBUF_APPEND, + UBUF_PREPEND, UBUF_APPEND, + UBUF_ALIGN, UBUF_ALIGN_HOFFSET); + assert(dst_mgr != NULL); + ubase_assert(ubuf_pic_mem_mgr_add_plane(dst_mgr, "y10l", 1, 1, 2)); + + /* src: 10-bit luma plane plus a 10-bit alpha plane. */ + src_mgr = ubuf_pic_mem_mgr_alloc(UBUF_POOL_DEPTH, UBUF_POOL_DEPTH, umem_mgr, 1, + UBUF_PREPEND, UBUF_APPEND, + UBUF_PREPEND, UBUF_APPEND, + UBUF_ALIGN, UBUF_ALIGN_HOFFSET); + assert(src_mgr != NULL); + ubase_assert(ubuf_pic_mem_mgr_add_plane(src_mgr, "y10l", 1, 1, 2)); + ubase_assert(ubuf_pic_mem_mgr_add_plane(src_mgr, "a10l", 1, 1, 2)); + + dst = ubuf_pic_alloc(dst_mgr, 1, 1); + assert(dst != NULL); + src = ubuf_pic_alloc(src_mgr, 1, 1); + assert(src != NULL); + + checks = 0; + + /* Copy branch (threshold == 0), tested separately as in the 8-bit case. */ + { + static const int copy_a[] = { 0, 1, 512, 1022, 1023 }; + static const int copy_alpha[] = { 0, 512, 1023 }; + for (size_t i = 0; i < UBASE_ARRAY_SIZE(copy_a); i++) { + ubase_assert(ubuf_pic_plane_set_color(src, "y10l", 0, 0, 1, 1, + (const uint8_t []){ 0xff, 0x3 }, 2)); + ubase_assert(ubuf_pic_plane_set_color(src, "a10l", 0, 0, 1, 1, + (const uint8_t []){ copy_a[i] & 0xff, copy_a[i] >> 8 }, 2)); + for (size_t k = 0; k < UBASE_ARRAY_SIZE(copy_alpha); k++) { + ubase_assert(ubuf_pic_plane_set_color(dst, "y10l", 0, 0, 1, 1, + (const uint8_t []){ 0, 0 }, 2)); + ubase_assert(ubuf_pic_blit(dst, src, 0, 0, 0, 0, 1, 1, + copy_alpha[k], 0)); + uint16_t got = read_word(dst, "y10l"); + uint16_t exp = expected_blit10(0, 1023, copy_a[i], + copy_alpha[k], 0); + assert(got == exp); + checks++; + } + } + } + + for (int a = 0; a <= 1023; a++) { + /* src is constant across the inner loop: opaque luma, alpha = a. */ + ubase_assert(ubuf_pic_plane_set_color(src, "y10l", 0, 0, 1, 1, + (const uint8_t []){ 0xff, 0x3 }, 2)); + ubase_assert(ubuf_pic_plane_set_color(src, "a10l", 0, 0, 1, 1, + (const uint8_t []){ a & 0xff, a >> 8 }, 2)); + + const int alpha_candidates[] = { 0, 1023, a-32, a-1, a, a+1, a+32 }; + const int threshold_candidates[] = { 1023, a-32, a-1, a, a+1, a+32 }; + + for (size_t ai = 0; ai < UBASE_ARRAY_SIZE(alpha_candidates); ai++) { + alpha = alpha_candidates[ai]; + if (alpha < 0 || alpha > 1023) + continue; + + for (size_t ti = 0; ti < UBASE_ARRAY_SIZE(threshold_candidates); ti++) { + threshold = threshold_candidates[ti]; + if (threshold < 0 || threshold > 1023) + continue; + + /* dst is reset before every call since blit overwrites it. */ + ubase_assert(ubuf_pic_plane_set_color(dst, "y10l", 0, 0, 1, 1, + (const uint8_t []){ 0, 0 }, 2)); + + ubase_assert(ubuf_pic_blit(dst, src, 0, 0, 0, 0, 1, 1, + alpha, threshold)); + + uint16_t got = read_word(dst, "y10l"); + uint16_t exp = expected_blit10(0, 1023, a, alpha, threshold); + if (got != exp) + fprintf(stderr, "mismatch: a=%d alpha=%d threshold=%d: " + "got %u, expected %u\n", + a, alpha, threshold, got, exp); + assert(got == exp); + checks++; + } + } + } + + printf("%lu 10-bit blit checks passed\n", checks); + + ubuf_free(src); + ubuf_free(dst); + ubuf_mgr_release(src_mgr); + ubuf_mgr_release(dst_mgr); + + /* No alpha plane, uniform blend: src has no "a8"/"a10l" plane, so + * ubuf_pic_blit() falls back to ubuf_pic_blit_alpha() with + * alpha_plane == NULL. With threshold 0xff the scalar alpha is applied + * uniformly across the plane: dst = (dst*(0xff-alpha) + src*alpha)/0xff. + * With dst = 0 and src = 255 the result is simply alpha. */ + + /* dst: single luma plane. */ + dst_mgr = ubuf_pic_mem_mgr_alloc(UBUF_POOL_DEPTH, UBUF_POOL_DEPTH, umem_mgr, 1, + UBUF_PREPEND, UBUF_APPEND, + UBUF_PREPEND, UBUF_APPEND, + UBUF_ALIGN, UBUF_ALIGN_HOFFSET); + assert(dst_mgr != NULL); + ubase_assert(ubuf_pic_mem_mgr_add_plane(dst_mgr, "y8", 1, 1, 1)); + + /* src: luma plane only, no alpha plane. */ + src_mgr = ubuf_pic_mem_mgr_alloc(UBUF_POOL_DEPTH, UBUF_POOL_DEPTH, umem_mgr, 1, + UBUF_PREPEND, UBUF_APPEND, + UBUF_PREPEND, UBUF_APPEND, + UBUF_ALIGN, UBUF_ALIGN_HOFFSET); + assert(src_mgr != NULL); + ubase_assert(ubuf_pic_mem_mgr_add_plane(src_mgr, "y8", 1, 1, 1)); + + dst = ubuf_pic_alloc(dst_mgr, 1, 1); + assert(dst != NULL); + src = ubuf_pic_alloc(src_mgr, 1, 1); + assert(src != NULL); + + checks = 0; + + for (alpha = 0; alpha <= 255; alpha++) { + ubase_assert(ubuf_pic_plane_set_color(src, "y8", 0, 0, 1, 1, + (const uint8_t []){ 255 }, 1)); + ubase_assert(ubuf_pic_plane_set_color(dst, "y8", 0, 0, 1, 1, + (const uint8_t []){ 0 }, 1)); + + ubase_assert(ubuf_pic_blit(dst, src, 0, 0, 0, 0, 1, 1, alpha, 255)); + + uint8_t got = read_octet(dst, "y8"); + uint8_t exp = expected_blit_noalpha(0, 255, alpha, 255); + if (got != exp) + fprintf(stderr, "mismatch: alpha=%d threshold=255: " + "got %u, expected %u\n", alpha, got, exp); + assert(got == exp); + checks++; + } + + printf("%lu no-alpha-plane blit checks passed\n", checks); + + ubuf_free(src); + ubuf_free(dst); + ubuf_mgr_release(src_mgr); + ubuf_mgr_release(dst_mgr); + + /* TODO: add a new section to test 10-bit blending without an alpha plane + * once that code can be run. (ubuf_pic_blit()'s no-alpha-plane fallback + * currently always calls the 8-bit ubuf_pic_blit_alpha(), so the 10-bit + * ubuf_pic_blit_alpha10() NULL path is not reachable through + * ubuf_pic_blit().) */ + + /* 64x64 yuv420p pictures. src additionally carries a full-resolution 8-bit + * alpha plane that is transparent (0) everywhere except an opaque (255) + * 32x32 square whose top-left corner is at (16, 16). A single on/off blit + * (alpha 255, threshold 20) copies that square into dst; the result is not + * checked here. */ + + /* dst: yuv420p. */ + dst_mgr = ubuf_pic_mem_mgr_alloc(UBUF_POOL_DEPTH, UBUF_POOL_DEPTH, umem_mgr, 1, + UBUF_PREPEND, UBUF_APPEND, + UBUF_PREPEND, UBUF_APPEND, + UBUF_ALIGN, UBUF_ALIGN_HOFFSET); + assert(dst_mgr != NULL); + ubase_assert(ubuf_pic_mem_mgr_add_plane(dst_mgr, "y8", 1, 1, 1)); + ubase_assert(ubuf_pic_mem_mgr_add_plane(dst_mgr, "u8", 2, 2, 1)); + ubase_assert(ubuf_pic_mem_mgr_add_plane(dst_mgr, "v8", 2, 2, 1)); + + /* src: yuv420p plus a full-resolution 8-bit alpha plane. */ + src_mgr = ubuf_pic_mem_mgr_alloc(UBUF_POOL_DEPTH, UBUF_POOL_DEPTH, umem_mgr, 1, + UBUF_PREPEND, UBUF_APPEND, + UBUF_PREPEND, UBUF_APPEND, + UBUF_ALIGN, UBUF_ALIGN_HOFFSET); + assert(src_mgr != NULL); + ubase_assert(ubuf_pic_mem_mgr_add_plane(src_mgr, "y8", 1, 1, 1)); + ubase_assert(ubuf_pic_mem_mgr_add_plane(src_mgr, "u8", 2, 2, 1)); + ubase_assert(ubuf_pic_mem_mgr_add_plane(src_mgr, "v8", 2, 2, 1)); + ubase_assert(ubuf_pic_mem_mgr_add_plane(src_mgr, "a8", 1, 1, 1)); + + dst = ubuf_pic_alloc(dst_mgr, 64, 64); + assert(dst != NULL); + src = ubuf_pic_alloc(src_mgr, 64, 64); + assert(src != NULL); + + /* dst: neutral black in limited-range YUV. */ + ubase_assert(ubuf_pic_plane_set_color(dst, "y8", 0, 0, 64, 64, + (const uint8_t []){ 16 }, 1)); + ubase_assert(ubuf_pic_plane_set_color(dst, "u8", 0, 0, 64, 64, + (const uint8_t []){ 128 }, 1)); + ubase_assert(ubuf_pic_plane_set_color(dst, "v8", 0, 0, 64, 64, + (const uint8_t []){ 128 }, 1)); + + /* src: a flat colour, opaque only inside the 32x32 square at (16, 16). */ + ubase_assert(ubuf_pic_plane_set_color(src, "y8", 0, 0, 64, 64, + (const uint8_t []){ 235 }, 1)); + ubase_assert(ubuf_pic_plane_set_color(src, "u8", 0, 0, 64, 64, + (const uint8_t []){ 64 }, 1)); + ubase_assert(ubuf_pic_plane_set_color(src, "v8", 0, 0, 64, 64, + (const uint8_t []){ 192 }, 1)); + ubase_assert(ubuf_pic_plane_set_color(src, "a8", 0, 0, 64, 64, + (const uint8_t []){ 0 }, 1)); + ubase_assert(ubuf_pic_plane_set_color(src, "a8", 16, 16, 32, 32, + (const uint8_t []){ 255 }, 1)); + + /* Check a 3x3 grid of samples on each plane. The alpha is opaque only + * inside the 32x32 square [16, 48) x [16, 48); with alpha 255 and threshold + * 20 the blit copies src there and leaves dst everywhere else, so only + * (32, 32) falls inside the square. On the subsampled chroma planes a luma + * point (x, y) maps to chroma sample (x/2, y/2). */ + alpha = 255; + threshold = 20; + ubase_assert(ubuf_pic_blit(dst, src, 0, 0, 0, 0, 64, 64, alpha, threshold)); + + checks = check_yuv420p_blit(dst, src, alpha, threshold); + printf("%lu yuv420p blit checks passed (alpha %d, threshold %d)\n", + checks, alpha, threshold); + + /* Second pass: reset dst to its original values and blit the same src + * again, this time with a reduced alpha of 128 (same threshold of 20). + * With alpha != 0xff the plane value is scaled (a = raw * 128 / 255) before + * the threshold test, exercising the on/off path the first blit skipped. + * Inside the square the effective alpha is 128, still above 20, so the + * copied region matches the first pass. */ + ubase_assert(ubuf_pic_plane_set_color(dst, "y8", 0, 0, 64, 64, + (const uint8_t []){ 16 }, 1)); + ubase_assert(ubuf_pic_plane_set_color(dst, "u8", 0, 0, 64, 64, + (const uint8_t []){ 128 }, 1)); + ubase_assert(ubuf_pic_plane_set_color(dst, "v8", 0, 0, 64, 64, + (const uint8_t []){ 128 }, 1)); + + alpha = 128; + threshold = 20; + ubase_assert(ubuf_pic_blit(dst, src, 0, 0, 0, 0, 64, 64, alpha, threshold)); + + checks = check_yuv420p_blit(dst, src, alpha, threshold); + printf("%lu yuv420p blit checks passed (alpha %d, threshold %d)\n", + checks, alpha, threshold); + + /* Third pass: reset dst, then overwrite src's alpha plane with a diagonal + * gradient (0 at the top-left, rising towards the bottom-right). Blit with + * alpha 255 and threshold 255: threshold 255 selects smooth per-pixel + * blending, so each dst sample becomes a weighted mix of dst and src, + * (dst * (255 - a) + src * a) / 255, using the local gradient alpha a. + * Rather than recomputing the gradient, the check reads the alpha the blit + * actually saw straight from the "a8" plane. All the sample points sit on + * even coordinates, so the subsampled chroma planes read the same alpha + * sample as luma. */ + ubase_assert(ubuf_pic_plane_set_color(dst, "y8", 0, 0, 64, 64, + (const uint8_t []){ 16 }, 1)); + ubase_assert(ubuf_pic_plane_set_color(dst, "u8", 0, 0, 64, 64, + (const uint8_t []){ 128 }, 1)); + ubase_assert(ubuf_pic_plane_set_color(dst, "v8", 0, 0, 64, 64, + (const uint8_t []){ 128 }, 1)); + + fill_plane_gradient(src, "a8", 0, 0, 64, 64); + + alpha = 255; + threshold = 255; + ubase_assert(ubuf_pic_blit(dst, src, 0, 0, 0, 0, 64, 64, alpha, threshold)); + + checks = check_yuv420p_blit(dst, src, alpha, threshold); + printf("%lu yuv420p gradient blit checks passed (alpha %d, threshold %d)\n", + checks, alpha, threshold); + + /* Fourth pass: the same gradient blit, but with alpha 128. Threshold 255 + * still selects smooth blending; with alpha != 0xff the gradient value is + * scaled first (a = raw * 128 / 255), so each dst sample becomes + * (dst * (255 - a) + src * a) / 255. As before the check reads the raw a8 + * value the blit saw and lets expected_blit apply the same scaling. */ + ubase_assert(ubuf_pic_plane_set_color(dst, "y8", 0, 0, 64, 64, + (const uint8_t []){ 16 }, 1)); + ubase_assert(ubuf_pic_plane_set_color(dst, "u8", 0, 0, 64, 64, + (const uint8_t []){ 128 }, 1)); + ubase_assert(ubuf_pic_plane_set_color(dst, "v8", 0, 0, 64, 64, + (const uint8_t []){ 128 }, 1)); + + fill_plane_gradient(src, "a8", 0, 0, 64, 64); + + alpha = 128; + threshold = 255; + ubase_assert(ubuf_pic_blit(dst, src, 0, 0, 0, 0, 64, 64, alpha, threshold)); + + checks = check_yuv420p_blit(dst, src, alpha, threshold); + printf("%lu yuv420p gradient blit checks passed (alpha %d, threshold %d)\n", + checks, alpha, threshold); + + /* Fifth pass: a source with no alpha plane. Rebuild src with only + * y8/u8/v8 (freeing the alpha-carrying one), reset dst, then blit with + * alpha 128 and threshold 1. With no alpha plane the blit applies the + * scalar alpha uniformly, so every sample is a blend + * (dst * (255 - 128) + src * 128) / 255. */ + ubuf_free(src); + ubuf_mgr_release(src_mgr); + + src_mgr = ubuf_pic_mem_mgr_alloc(UBUF_POOL_DEPTH, UBUF_POOL_DEPTH, umem_mgr, 1, + UBUF_PREPEND, UBUF_APPEND, + UBUF_PREPEND, UBUF_APPEND, + UBUF_ALIGN, UBUF_ALIGN_HOFFSET); + assert(src_mgr != NULL); + ubase_assert(ubuf_pic_mem_mgr_add_plane(src_mgr, "y8", 1, 1, 1)); + ubase_assert(ubuf_pic_mem_mgr_add_plane(src_mgr, "u8", 2, 2, 1)); + ubase_assert(ubuf_pic_mem_mgr_add_plane(src_mgr, "v8", 2, 2, 1)); + + src = ubuf_pic_alloc(src_mgr, 64, 64); + assert(src != NULL); + ubase_assert(ubuf_pic_plane_set_color(src, "y8", 0, 0, 64, 64, + (const uint8_t []){ 235 }, 1)); + ubase_assert(ubuf_pic_plane_set_color(src, "u8", 0, 0, 64, 64, + (const uint8_t []){ 64 }, 1)); + ubase_assert(ubuf_pic_plane_set_color(src, "v8", 0, 0, 64, 64, + (const uint8_t []){ 192 }, 1)); + + /* dst: neutral black in limited-range YUV. */ + ubase_assert(ubuf_pic_plane_set_color(dst, "y8", 0, 0, 64, 64, + (const uint8_t []){ 16 }, 1)); + ubase_assert(ubuf_pic_plane_set_color(dst, "u8", 0, 0, 64, 64, + (const uint8_t []){ 128 }, 1)); + ubase_assert(ubuf_pic_plane_set_color(dst, "v8", 0, 0, 64, 64, + (const uint8_t []){ 128 }, 1)); + + alpha = 128; + threshold = 1; + ubase_assert(ubuf_pic_blit(dst, src, 0, 0, 0, 0, 64, 64, alpha, threshold)); + + checks = 0; + { + static const struct { int x, y; } points[] = { + { 8, 8 }, { 32, 8 }, { 56, 8 }, + { 8, 32 }, { 32, 32 }, { 56, 32 }, + { 8, 56 }, { 32, 56 }, { 56, 56 }, + { 16, 16 }, { 47, 47 }, + }; + size_t stride; + const uint8_t *buffer; + + /* y8: uniform blend of dst's 16 with src's 235. */ + ubase_assert(ubuf_pic_plane_size(dst, "y8", &stride, NULL, NULL, NULL)); + ubase_assert(ubuf_pic_plane_read(dst, "y8", 0, 0, 64, 64, &buffer)); + for (size_t i = 0; i < UBASE_ARRAY_SIZE(points); i++) { + int x = points[i].x, y = points[i].y; + uint8_t got = buffer[y * stride + x]; + uint8_t exp = expected_blit_noalpha(16, 235, alpha, threshold); + if (got != exp) + fprintf(stderr, "mismatch on y8 at (%d, %d): got %u, expected %u\n", + x, y, got, exp); + assert(got == exp); + checks++; + } + ubase_assert(ubuf_pic_plane_unmap(dst, "y8", 0, 0, 64, 64)); + + /* u8: uniform blend of dst's 128 with src's 64. */ + ubase_assert(ubuf_pic_plane_size(dst, "u8", &stride, NULL, NULL, NULL)); + ubase_assert(ubuf_pic_plane_read(dst, "u8", 0, 0, 64, 64, &buffer)); + for (size_t i = 0; i < UBASE_ARRAY_SIZE(points); i++) { + int x = points[i].x, y = points[i].y; + uint8_t got = buffer[(y / 2) * stride + (x / 2)]; + uint8_t exp = expected_blit_noalpha(128, 64, alpha, threshold); + if (got != exp) + fprintf(stderr, "mismatch on u8 at (%d, %d): got %u, expected %u\n", + x, y, got, exp); + assert(got == exp); + checks++; + } + ubase_assert(ubuf_pic_plane_unmap(dst, "u8", 0, 0, 64, 64)); + + /* v8: uniform blend of dst's 128 with src's 192. */ + ubase_assert(ubuf_pic_plane_size(dst, "v8", &stride, NULL, NULL, NULL)); + ubase_assert(ubuf_pic_plane_read(dst, "v8", 0, 0, 64, 64, &buffer)); + for (size_t i = 0; i < UBASE_ARRAY_SIZE(points); i++) { + int x = points[i].x, y = points[i].y; + uint8_t got = buffer[(y / 2) * stride + (x / 2)]; + uint8_t exp = expected_blit_noalpha(128, 192, alpha, threshold); + if (got != exp) + fprintf(stderr, "mismatch on v8 at (%d, %d): got %u, expected %u\n", + x, y, got, exp); + assert(got == exp); + checks++; + } + ubase_assert(ubuf_pic_plane_unmap(dst, "v8", 0, 0, 64, 64)); + printf("%lu yuv420p no-alpha blit checks passed (alpha %d, threshold %d)\n", + checks, alpha, threshold); + } + + ubuf_free(src); + ubuf_free(dst); + ubuf_mgr_release(src_mgr); + ubuf_mgr_release(dst_mgr); + + /* 64x64 yuv420p10le pictures: the 10-bit analogue of the yuv420p case + * above. The sample values are scaled up by four for the two extra bits, + * and the alpha square is fully opaque (1023). A single on/off blit (alpha + * 1023, threshold 80) copies the 32x32 square at (16, 16) into dst. */ + + /* dst: yuv420p10le. */ + dst_mgr = ubuf_pic_mem_mgr_alloc(UBUF_POOL_DEPTH, UBUF_POOL_DEPTH, umem_mgr, 1, + UBUF_PREPEND, UBUF_APPEND, + UBUF_PREPEND, UBUF_APPEND, + UBUF_ALIGN, UBUF_ALIGN_HOFFSET); + assert(dst_mgr != NULL); + ubase_assert(ubuf_pic_mem_mgr_add_plane(dst_mgr, "y10l", 1, 1, 2)); + ubase_assert(ubuf_pic_mem_mgr_add_plane(dst_mgr, "u10l", 2, 2, 2)); + ubase_assert(ubuf_pic_mem_mgr_add_plane(dst_mgr, "v10l", 2, 2, 2)); + + /* src: yuv420p10le plus a full-resolution 10-bit alpha plane. */ + src_mgr = ubuf_pic_mem_mgr_alloc(UBUF_POOL_DEPTH, UBUF_POOL_DEPTH, umem_mgr, 1, + UBUF_PREPEND, UBUF_APPEND, + UBUF_PREPEND, UBUF_APPEND, + UBUF_ALIGN, UBUF_ALIGN_HOFFSET); + assert(src_mgr != NULL); + ubase_assert(ubuf_pic_mem_mgr_add_plane(src_mgr, "y10l", 1, 1, 2)); + ubase_assert(ubuf_pic_mem_mgr_add_plane(src_mgr, "u10l", 2, 2, 2)); + ubase_assert(ubuf_pic_mem_mgr_add_plane(src_mgr, "v10l", 2, 2, 2)); + ubase_assert(ubuf_pic_mem_mgr_add_plane(src_mgr, "a10l", 1, 1, 2)); + + dst = ubuf_pic_alloc(dst_mgr, 64, 64); + assert(dst != NULL); + src = ubuf_pic_alloc(src_mgr, 64, 64); + assert(src != NULL); + + /* dst: neutral black in limited-range YUV. */ + ubase_assert(ubuf_pic_plane_set_color(dst, "y10l", 0, 0, 64, 64, + (const uint8_t []){ 0x40, 0 }, 2)); + ubase_assert(ubuf_pic_plane_set_color(dst, "u10l", 0, 0, 64, 64, + (const uint8_t []){ 0, 0x2 }, 2)); + ubase_assert(ubuf_pic_plane_set_color(dst, "v10l", 0, 0, 64, 64, + (const uint8_t []){ 0, 0x2 }, 2)); + + /* src: a flat colour, opaque only inside the 32x32 square at (16, 16). */ + ubase_assert(ubuf_pic_plane_set_color(src, "y10l", 0, 0, 64, 64, + (const uint8_t []){ 0xac, 0x3 }, 2)); + ubase_assert(ubuf_pic_plane_set_color(src, "u10l", 0, 0, 64, 64, + (const uint8_t []){ 0, 0x1 }, 2)); + ubase_assert(ubuf_pic_plane_set_color(src, "v10l", 0, 0, 64, 64, + (const uint8_t []){ 0, 0x3 }, 2)); + ubase_assert(ubuf_pic_plane_set_color(src, "a10l", 0, 0, 64, 64, + (const uint8_t []){ 0, 0 }, 2)); + ubase_assert(ubuf_pic_plane_set_color(src, "a10l", 16, 16, 32, 32, + (const uint8_t []){ 0xff, 0x3 }, 2)); + + /* Check the same 3x3 grid on each plane. As before only (32, 32) is inside + * the opaque square; on the subsampled chroma planes a luma point (x, y) + * maps to sample (x/2, y/2). The samples are 16-bit. */ + alpha = 1023; + threshold = 80; + ubase_assert(ubuf_pic_blit(dst, src, 0, 0, 0, 0, 64, 64, alpha, threshold)); + checks = check_yuv420p10le_blit(dst, src, alpha, threshold); + printf("%lu yuv420p10le blit checks passed (alpha %d, threshold %d)\n", + checks, alpha, threshold); + + /* Second pass: reset dst and blit the same src again with a reduced alpha + * of 512 (same threshold of 80). With alpha != 0x3ff the plane value is + * scaled (a = raw * 512 / 1023) before the threshold test. Inside the + * square the effective alpha is 512, still above 80, so the copied region + * matches the first pass. */ + ubase_assert(ubuf_pic_plane_set_color(dst, "y10l", 0, 0, 64, 64, + (const uint8_t []){ 0x40, 0 }, 2)); + ubase_assert(ubuf_pic_plane_set_color(dst, "u10l", 0, 0, 64, 64, + (const uint8_t []){ 0, 0x2 }, 2)); + ubase_assert(ubuf_pic_plane_set_color(dst, "v10l", 0, 0, 64, 64, + (const uint8_t []){ 0, 0x2 }, 2)); + + alpha = 512; + threshold = 80; + ubase_assert(ubuf_pic_blit(dst, src, 0, 0, 0, 0, 64, 64, alpha, threshold)); + checks = check_yuv420p10le_blit(dst, src, alpha, threshold); + printf("%lu yuv420p10le blit checks passed (alpha %d, threshold %d)\n", + checks, alpha, threshold); + + /* Replace src's alpha with a diagonal gradient for the remaining passes. */ + fill_plane_gradient16(src, "a10l", 0, 0, 64, 64); + + /* Third pass: reset dst and blit with alpha 1023 and threshold 1023. + * Threshold 1023 selects smooth per-pixel blending; with alpha == 0x3ff the + * gradient alpha is used directly, so each dst sample becomes + * (dst * (1023 - a) + src * a) / 1023. The check reads the alpha the blit + * saw straight from the "a10l" plane. All the sample points sit on even + * coordinates, so the subsampled chroma planes read the same alpha sample + * as luma. */ + ubase_assert(ubuf_pic_plane_set_color(dst, "y10l", 0, 0, 64, 64, + (const uint8_t []){ 0x40, 0 }, 2)); + ubase_assert(ubuf_pic_plane_set_color(dst, "u10l", 0, 0, 64, 64, + (const uint8_t []){ 0, 0x2 }, 2)); + ubase_assert(ubuf_pic_plane_set_color(dst, "v10l", 0, 0, 64, 64, + (const uint8_t []){ 0, 0x2 }, 2)); + + alpha = 1023; + threshold = 1023; + ubase_assert(ubuf_pic_blit(dst, src, 0, 0, 0, 0, 64, 64, alpha, threshold)); + checks = check_yuv420p10le_blit(dst, src, alpha, threshold); + printf("%lu yuv420p10le gradient blit checks passed (alpha %d, threshold %d)\n", + checks, alpha, threshold); + + /* Fourth pass: the same gradient blit with alpha 512. Threshold 1023 still + * selects smooth blending; with alpha != 0x3ff the gradient value is scaled + * first (a = raw * 512 / 1023) before the blend. The check again reads the + * raw a10l value and lets expected_blit10 apply the scaling. */ + ubase_assert(ubuf_pic_plane_set_color(dst, "y10l", 0, 0, 64, 64, + (const uint8_t []){ 0x40, 0 }, 2)); + ubase_assert(ubuf_pic_plane_set_color(dst, "u10l", 0, 0, 64, 64, + (const uint8_t []){ 0, 0x2 }, 2)); + ubase_assert(ubuf_pic_plane_set_color(dst, "v10l", 0, 0, 64, 64, + (const uint8_t []){ 0, 0x2 }, 2)); + + alpha = 512; + threshold = 1023; + ubase_assert(ubuf_pic_blit(dst, src, 0, 0, 0, 0, 64, 64, alpha, threshold)); + checks = check_yuv420p10le_blit(dst, src, alpha, threshold); + printf("%lu yuv420p10le gradient blit checks passed (alpha %d, threshold %d)\n", + checks, alpha, threshold); + + /* Fifth pass: a 10-bit source with no alpha plane. Rebuild src with only + * y10l/u10l/v10l (freeing the alpha-carrying one), reset dst, then blit + * with alpha 1023 and threshold 0. threshold 0 forces the memcpy path, so + * with no alpha plane dst becomes an exact copy of src everywhere. */ + ubuf_free(src); + ubuf_mgr_release(src_mgr); + + src_mgr = ubuf_pic_mem_mgr_alloc(UBUF_POOL_DEPTH, UBUF_POOL_DEPTH, umem_mgr, 1, + UBUF_PREPEND, UBUF_APPEND, + UBUF_PREPEND, UBUF_APPEND, + UBUF_ALIGN, UBUF_ALIGN_HOFFSET); + assert(src_mgr != NULL); + ubase_assert(ubuf_pic_mem_mgr_add_plane(src_mgr, "y10l", 1, 1, 2)); + ubase_assert(ubuf_pic_mem_mgr_add_plane(src_mgr, "u10l", 2, 2, 2)); + ubase_assert(ubuf_pic_mem_mgr_add_plane(src_mgr, "v10l", 2, 2, 2)); + + src = ubuf_pic_alloc(src_mgr, 64, 64); + assert(src != NULL); + ubase_assert(ubuf_pic_plane_set_color(src, "y10l", 0, 0, 64, 64, + (const uint8_t []){ 0xac, 0x3 }, 2)); + ubase_assert(ubuf_pic_plane_set_color(src, "u10l", 0, 0, 64, 64, + (const uint8_t []){ 0, 0x1 }, 2)); + ubase_assert(ubuf_pic_plane_set_color(src, "v10l", 0, 0, 64, 64, + (const uint8_t []){ 0, 0x3 }, 2)); + + /* dst: neutral black in limited-range YUV. */ + ubase_assert(ubuf_pic_plane_set_color(dst, "y10l", 0, 0, 64, 64, + (const uint8_t []){ 0x40, 0 }, 2)); + ubase_assert(ubuf_pic_plane_set_color(dst, "u10l", 0, 0, 64, 64, + (const uint8_t []){ 0, 0x2 }, 2)); + ubase_assert(ubuf_pic_plane_set_color(dst, "v10l", 0, 0, 64, 64, + (const uint8_t []){ 0, 0x2 }, 2)); + + checks = 0; + { + alpha = 1023; + threshold = 0; + static const struct { int x, y; } points[] = { + { 8, 8 }, { 32, 8 }, { 56, 8 }, + { 8, 32 }, { 32, 32 }, { 56, 32 }, + { 8, 56 }, { 32, 56 }, { 56, 56 }, + { 16, 16 }, { 47, 47 }, + }; + size_t stride; + const uint8_t *buffer; + + ubase_assert(ubuf_pic_blit(dst, src, 0, 0, 0, 0, 64, 64, alpha, threshold)); + + /* y10l: a straight copy of src's 940. */ + ubase_assert(ubuf_pic_plane_size(dst, "y10l", &stride, NULL, NULL, NULL)); + ubase_assert(ubuf_pic_plane_read(dst, "y10l", 0, 0, 64, 64, &buffer)); + for (size_t i = 0; i < UBASE_ARRAY_SIZE(points); i++) { + int x = points[i].x, y = points[i].y; + uint16_t got = ((const uint16_t *)(buffer + y * stride))[x]; + uint16_t exp = expected_blit10_noalpha(64, 940, alpha, threshold); + if (got != exp) + fprintf(stderr, "mismatch on y10l at (%d, %d): got %u, expected %u\n", + x, y, got, exp); + assert(got == exp); + checks++; + } + ubase_assert(ubuf_pic_plane_unmap(dst, "y10l", 0, 0, 64, 64)); + + /* u10l: a straight copy of src's 256. */ + ubase_assert(ubuf_pic_plane_size(dst, "u10l", &stride, NULL, NULL, NULL)); + ubase_assert(ubuf_pic_plane_read(dst, "u10l", 0, 0, 64, 64, &buffer)); + for (size_t i = 0; i < UBASE_ARRAY_SIZE(points); i++) { + int x = points[i].x, y = points[i].y; + uint16_t got = ((const uint16_t *)(buffer + (y / 2) * stride))[x / 2]; + uint16_t exp = expected_blit10_noalpha(512, 256, alpha, threshold); + if (got != exp) + fprintf(stderr, "mismatch on u10l at (%d, %d): got %u, expected %u\n", + x, y, got, exp); + assert(got == exp); + checks++; + } + ubase_assert(ubuf_pic_plane_unmap(dst, "u10l", 0, 0, 64, 64)); + + /* v10l: a straight copy of src's 768. */ + ubase_assert(ubuf_pic_plane_size(dst, "v10l", &stride, NULL, NULL, NULL)); + ubase_assert(ubuf_pic_plane_read(dst, "v10l", 0, 0, 64, 64, &buffer)); + for (size_t i = 0; i < UBASE_ARRAY_SIZE(points); i++) { + int x = points[i].x, y = points[i].y; + uint16_t got = ((const uint16_t *)(buffer + (y / 2) * stride))[x / 2]; + uint16_t exp = expected_blit10_noalpha(512, 768, alpha, threshold); + if (got != exp) + fprintf(stderr, "mismatch on v10l at (%d, %d): got %u, expected %u\n", + x, y, got, exp); + assert(got == exp); + checks++; + } + ubase_assert(ubuf_pic_plane_unmap(dst, "v10l", 0, 0, 64, 64)); + printf("%lu yuv420p10le no-alpha blit checks passed (alpha %d, threshold %d)\n", + checks, alpha, threshold); + } + + /* Sixth pass: the same no-alpha 10-bit source, but blit with alpha 512 and + * threshold 1. This case is EXPECTED TO FAIL. When src has no alpha plane + * ubuf_pic_blit always falls back to the 8-bit ubuf_pic_blit_alpha, which + * blends the 10-bit little-endian buffer one byte at a time. With alpha 512 + * the 8-bit weight (0xff - alpha) is negative and the per-byte results + * overflow, so the recombined 16-bit samples (e.g. 1560 for luma) do not + * match expected_blit10_noalpha's ideal blend of + * (dst * (1023 - 512) + src * 512) / 1023. The asserts below are commented + * out so the mismatches are reported without aborting the run. */ + ubase_assert(ubuf_pic_plane_set_color(dst, "y10l", 0, 0, 64, 64, + (const uint8_t []){ 0x40, 0 }, 2)); + ubase_assert(ubuf_pic_plane_set_color(dst, "u10l", 0, 0, 64, 64, + (const uint8_t []){ 0, 0x2 }, 2)); + ubase_assert(ubuf_pic_plane_set_color(dst, "v10l", 0, 0, 64, 64, + (const uint8_t []){ 0, 0x2 }, 2)); + + checks = 0; + { + alpha = 512; + threshold = 1; + static const struct { int x, y; } points[] = { + { 8, 8 }, { 32, 8 }, { 56, 8 }, + { 8, 32 }, { 32, 32 }, { 56, 32 }, + { 8, 56 }, { 32, 56 }, { 56, 56 }, + { 16, 16 }, { 47, 47 }, + }; + size_t stride; + const uint8_t *buffer; + + ubase_assert(ubuf_pic_blit(dst, src, 0, 0, 0, 0, 64, 64, alpha, threshold)); + + /* y10l: uniform blend of dst's 64 with src's 940. */ + ubase_assert(ubuf_pic_plane_size(dst, "y10l", &stride, NULL, NULL, NULL)); + ubase_assert(ubuf_pic_plane_read(dst, "y10l", 0, 0, 64, 64, &buffer)); + for (size_t i = 0; i < UBASE_ARRAY_SIZE(points); i++) { + int x = points[i].x, y = points[i].y; + uint16_t got = ((const uint16_t *)(buffer + y * stride))[x]; + uint16_t exp = expected_blit10_noalpha(64, 940, alpha, threshold); + if (got != exp) + fprintf(stderr, "mismatch on y10l at (%d, %d): got %u, expected %u\n", + x, y, got, exp); + /* assert(got == exp); expected to fail; see note above */ + checks++; + } + ubase_assert(ubuf_pic_plane_unmap(dst, "y10l", 0, 0, 64, 64)); + + /* u10l: uniform blend of dst's 512 with src's 256. */ + ubase_assert(ubuf_pic_plane_size(dst, "u10l", &stride, NULL, NULL, NULL)); + ubase_assert(ubuf_pic_plane_read(dst, "u10l", 0, 0, 64, 64, &buffer)); + for (size_t i = 0; i < UBASE_ARRAY_SIZE(points); i++) { + int x = points[i].x, y = points[i].y; + uint16_t got = ((const uint16_t *)(buffer + (y / 2) * stride))[x / 2]; + uint16_t exp = expected_blit10_noalpha(512, 256, alpha, threshold); + if (got != exp) + fprintf(stderr, "mismatch on u10l at (%d, %d): got %u, expected %u\n", + x, y, got, exp); + /* assert(got == exp); expected to fail; see note above */ + checks++; + } + ubase_assert(ubuf_pic_plane_unmap(dst, "u10l", 0, 0, 64, 64)); + + /* v10l: uniform blend of dst's 512 with src's 768. */ + ubase_assert(ubuf_pic_plane_size(dst, "v10l", &stride, NULL, NULL, NULL)); + ubase_assert(ubuf_pic_plane_read(dst, "v10l", 0, 0, 64, 64, &buffer)); + for (size_t i = 0; i < UBASE_ARRAY_SIZE(points); i++) { + int x = points[i].x, y = points[i].y; + uint16_t got = ((const uint16_t *)(buffer + (y / 2) * stride))[x / 2]; + uint16_t exp = expected_blit10_noalpha(512, 768, alpha, threshold); + if (got != exp) + fprintf(stderr, "mismatch on v10l at (%d, %d): got %u, expected %u\n", + x, y, got, exp); + /* assert(got == exp); expected to fail; see note above */ + checks++; + } + ubase_assert(ubuf_pic_plane_unmap(dst, "v10l", 0, 0, 64, 64)); + printf("%lu yuv420p10le no-alpha blit checks passed (alpha %d, threshold %d)\n", + checks, alpha, threshold); + } + + ubuf_free(src); + ubuf_free(dst); + ubuf_mgr_release(src_mgr); + ubuf_mgr_release(dst_mgr); + + /* 64x64 pictures with a semi-planar nv12 dst and a planar yuv420p src. nv12 + * keeps luma in a full-resolution "y8" plane and interleaves the U and V + * chroma samples in a single "u8v8" plane (macropixel_size 2, subsampled + * 2x2), while yuv420p keeps them in separate "u8"/"v8" planes. */ + dst_mgr = ubuf_pic_mem_mgr_alloc(UBUF_POOL_DEPTH, UBUF_POOL_DEPTH, umem_mgr, 1, + UBUF_PREPEND, UBUF_APPEND, + UBUF_PREPEND, UBUF_APPEND, + UBUF_ALIGN, UBUF_ALIGN_HOFFSET); + assert(dst_mgr != NULL); + ubase_assert(ubuf_pic_mem_mgr_add_plane(dst_mgr, "y8", 1, 1, 1)); + ubase_assert(ubuf_pic_mem_mgr_add_plane(dst_mgr, "u8v8", 2, 2, 2)); + + src_mgr = ubuf_pic_mem_mgr_alloc(UBUF_POOL_DEPTH, UBUF_POOL_DEPTH, umem_mgr, 1, + UBUF_PREPEND, UBUF_APPEND, + UBUF_PREPEND, UBUF_APPEND, + UBUF_ALIGN, UBUF_ALIGN_HOFFSET); + assert(src_mgr != NULL); + ubase_assert(ubuf_pic_mem_mgr_add_plane(src_mgr, "y8", 1, 1, 1)); + ubase_assert(ubuf_pic_mem_mgr_add_plane(src_mgr, "u8", 2, 2, 1)); + ubase_assert(ubuf_pic_mem_mgr_add_plane(src_mgr, "v8", 2, 2, 1)); + + dst = ubuf_pic_alloc(dst_mgr, 64, 64); + assert(dst != NULL); + src = ubuf_pic_alloc(src_mgr, 64, 64); + assert(src != NULL); + + /* dst: neutral black in limited-range YUV (chroma 128 for both U and V). */ + ubase_assert(ubuf_pic_plane_set_color(dst, "y8", 0, 0, 64, 64, + (const uint8_t []){ 16 }, 1)); + ubase_assert(ubuf_pic_plane_set_color(dst, "u8v8", 0, 0, 64, 64, + (const uint8_t []){ 128 }, 1)); + + /* src: a flat colour (no alpha plane). */ + ubase_assert(ubuf_pic_plane_set_color(src, "y8", 0, 0, 64, 64, + (const uint8_t []){ 235 }, 1)); + ubase_assert(ubuf_pic_plane_set_color(src, "u8", 0, 0, 64, 64, + (const uint8_t []){ 64 }, 1)); + ubase_assert(ubuf_pic_plane_set_color(src, "v8", 0, 0, 64, 64, + (const uint8_t []){ 192 }, 1)); + + /* Blit with alpha 255 and threshold 0: threshold 0 is a straight copy, so + * dst takes src across the whole 64x64 area regardless of the alpha plane. + * The same 3x3 grid is checked on each plane as in the first yuv420p test; + * on the subsampled chroma planes a luma point (x, y) maps to sample + * (x/2, y/2). */ + checks = 0; + { + alpha = 255; + threshold = 0; + static const struct { int x, y; } points[] = { + { 8, 8 }, { 32, 8 }, { 56, 8 }, + { 8, 32 }, { 32, 32 }, { 56, 32 }, + { 8, 56 }, { 32, 56 }, { 56, 56 }, + { 16, 16 }, { 47, 47 }, + }; + size_t stride; + const uint8_t *buffer; + + ubase_assert(ubuf_pic_blit(dst, src, 0, 0, 0, 0, 64, 64, alpha, threshold)); + + /* y8: the copy replaces dst's 16 with src's 235 everywhere. */ + ubase_assert(ubuf_pic_plane_size(dst, "y8", &stride, NULL, NULL, NULL)); + ubase_assert(ubuf_pic_plane_read(dst, "y8", 0, 0, 64, 64, &buffer)); + for (size_t i = 0; i < UBASE_ARRAY_SIZE(points); i++) { + int x = points[i].x, y = points[i].y; + uint8_t got = buffer[y * stride + x]; + uint8_t exp = expected_blit_noalpha(16, 235, alpha, threshold); + if (got != exp) + fprintf(stderr, "mismatch on y8 at (%d, %d): got %u, expected %u\n", + x, y, got, exp); + assert(got == exp); + checks++; + } + ubase_assert(ubuf_pic_plane_unmap(dst, "y8", 0, 0, 64, 64)); + + /* u8v8: the interleaved chroma plane. Each 2-byte sample holds U at + * offset 0 and V at offset 1; the copy takes src's U = 64 and V = 192. */ + ubase_assert(ubuf_pic_plane_size(dst, "u8v8", &stride, NULL, NULL, NULL)); + ubase_assert(ubuf_pic_plane_read(dst, "u8v8", 0, 0, 64, 64, &buffer)); + for (size_t i = 0; i < UBASE_ARRAY_SIZE(points); i++) { + int x = points[i].x, y = points[i].y; + const uint8_t *sample = buffer + (y / 2) * stride + (x / 2) * 2; + uint8_t got_u = sample[0]; + uint8_t got_v = sample[1]; + uint8_t exp_u = expected_blit_noalpha(128, 64, alpha, threshold); + uint8_t exp_v = expected_blit_noalpha(128, 192, alpha, threshold); + if (got_u != exp_u) + fprintf(stderr, "mismatch on u8v8 U at (%d, %d): got %u, expected %u\n", + x, y, got_u, exp_u); + assert(got_u == exp_u); + checks++; + if (got_v != exp_v) + fprintf(stderr, "mismatch on u8v8 V at (%d, %d): got %u, expected %u\n", + x, y, got_v, exp_v); + assert(got_v == exp_v); + checks++; + } + ubase_assert(ubuf_pic_plane_unmap(dst, "u8v8", 0, 0, 64, 64)); + printf("%lu nv12 blit checks passed (alpha %d, threshold %d)\n", + checks, alpha, threshold); + } + + /* Second nv12 pass: the same no-alpha src, but blit with alpha 128 and + * threshold 0. This case is EXPECTED TO FAIL on the chroma plane. y8 is a + * single plane, so threshold 0 gives a plain memcpy and dst takes src's + * 235. But nv12's u8v8 is filled from src's two separate u8/v8 planes, and + * that de-planarising path blends even when threshold is 0: each byte + * becomes (dst * (255 - 128) + src * 128) / 255 rather than a straight + * copy, so the chroma samples do not match expected_blit_noalpha's + * threshold-0 copy. The asserts below are commented out so the mismatches + * are reported without aborting the run. */ + ubase_assert(ubuf_pic_plane_set_color(dst, "y8", 0, 0, 64, 64, + (const uint8_t []){ 16 }, 1)); + ubase_assert(ubuf_pic_plane_set_color(dst, "u8v8", 0, 0, 64, 64, + (const uint8_t []){ 128 }, 1)); + + checks = 0; + { + alpha = 128; + threshold = 0; + static const struct { int x, y; } points[] = { + { 8, 8 }, { 32, 8 }, { 56, 8 }, + { 8, 32 }, { 32, 32 }, { 56, 32 }, + { 8, 56 }, { 32, 56 }, { 56, 56 }, + { 16, 16 }, { 47, 47 }, + }; + size_t stride; + const uint8_t *buffer; + + ubase_assert(ubuf_pic_blit(dst, src, 0, 0, 0, 0, 64, 64, alpha, threshold)); + + /* y8: a single plane, so the copy replaces dst's 16 with src's 235. */ + ubase_assert(ubuf_pic_plane_size(dst, "y8", &stride, NULL, NULL, NULL)); + ubase_assert(ubuf_pic_plane_read(dst, "y8", 0, 0, 64, 64, &buffer)); + for (size_t i = 0; i < UBASE_ARRAY_SIZE(points); i++) { + int x = points[i].x, y = points[i].y; + uint8_t got = buffer[y * stride + x]; + uint8_t exp = expected_blit_noalpha(16, 235, alpha, threshold); + if (got != exp) + fprintf(stderr, "mismatch on y8 at (%d, %d): got %u, expected %u\n", + x, y, got, exp); + /* assert(got == exp); expected to fail; see note above */ + checks++; + } + ubase_assert(ubuf_pic_plane_unmap(dst, "y8", 0, 0, 64, 64)); + + /* u8v8: the interleaved chroma plane, filled from src's separate u8/v8 + * planes. The de-planarising path blends here even at threshold 0. */ + ubase_assert(ubuf_pic_plane_size(dst, "u8v8", &stride, NULL, NULL, NULL)); + ubase_assert(ubuf_pic_plane_read(dst, "u8v8", 0, 0, 64, 64, &buffer)); + for (size_t i = 0; i < UBASE_ARRAY_SIZE(points); i++) { + int x = points[i].x, y = points[i].y; + const uint8_t *sample = buffer + (y / 2) * stride + (x / 2) * 2; + uint8_t got_u = sample[0]; + uint8_t got_v = sample[1]; + uint8_t exp_u = expected_blit_noalpha(128, 64, alpha, threshold); + uint8_t exp_v = expected_blit_noalpha(128, 192, alpha, threshold); + if (got_u != exp_u) + fprintf(stderr, "mismatch on u8v8 U at (%d, %d): got %u, expected %u\n", + x, y, got_u, exp_u); + /* assert(got_u == exp_u); expected to fail; see note above */ + checks++; + if (got_v != exp_v) + fprintf(stderr, "mismatch on u8v8 V at (%d, %d): got %u, expected %u\n", + x, y, got_v, exp_v); + /* assert(got_v == exp_v); expected to fail; see note above */ + checks++; + } + ubase_assert(ubuf_pic_plane_unmap(dst, "u8v8", 0, 0, 64, 64)); + printf("%lu nv12 blit checks passed (alpha %d, threshold %d)\n", + checks, alpha, threshold); + } + + ubuf_free(src); + ubuf_free(dst); + ubuf_mgr_release(src_mgr); + ubuf_mgr_release(dst_mgr); + + /* Benchmark: repeatedly blit a full 1920x1080 yuv422p picture through a + * rectangular alpha mask and report the throughput. */ + int calls = 0; + if (argc > 1) + calls = atoi(argv[1]); + + /* First benchmark: 8-bit yuv422p. */ + if (calls > 0) { + dst_mgr = ubuf_pic_mem_mgr_alloc(UBUF_POOL_DEPTH, UBUF_POOL_DEPTH, umem_mgr, 1, + UBUF_PREPEND, UBUF_APPEND, + UBUF_PREPEND, UBUF_APPEND, + UBUF_ALIGN, UBUF_ALIGN_HOFFSET); + assert(dst_mgr != NULL); + ubase_assert(ubuf_pic_mem_mgr_add_plane(dst_mgr, "y8", 1, 1, 1)); + ubase_assert(ubuf_pic_mem_mgr_add_plane(dst_mgr, "u8", 2, 1, 1)); + ubase_assert(ubuf_pic_mem_mgr_add_plane(dst_mgr, "v8", 2, 1, 1)); + + src_mgr = ubuf_pic_mem_mgr_alloc(UBUF_POOL_DEPTH, UBUF_POOL_DEPTH, umem_mgr, 1, + UBUF_PREPEND, UBUF_APPEND, + UBUF_PREPEND, UBUF_APPEND, + UBUF_ALIGN, UBUF_ALIGN_HOFFSET); + assert(src_mgr != NULL); + ubase_assert(ubuf_pic_mem_mgr_add_plane(src_mgr, "y8", 1, 1, 1)); + ubase_assert(ubuf_pic_mem_mgr_add_plane(src_mgr, "u8", 2, 1, 1)); + ubase_assert(ubuf_pic_mem_mgr_add_plane(src_mgr, "v8", 2, 1, 1)); + ubase_assert(ubuf_pic_mem_mgr_add_plane(src_mgr, "a8", 1, 1, 1)); + + dst = ubuf_pic_alloc(dst_mgr, 1920, 1080); + assert(dst != NULL); + src = ubuf_pic_alloc(src_mgr, 1920, 1080); + assert(src != NULL); + + /* dst: middle gray (all planes 128). */ + ubase_assert(ubuf_pic_plane_set_color(dst, "y8", 0, 0, 1920, 1080, + (const uint8_t []){ 128 }, 1)); + ubase_assert(ubuf_pic_plane_set_color(dst, "u8", 0, 0, 1920, 1080, + (const uint8_t []){ 128 }, 1)); + ubase_assert(ubuf_pic_plane_set_color(dst, "v8", 0, 0, 1920, 1080, + (const uint8_t []){ 128 }, 1)); + + /* src: gradients on the colour planes; alpha zero everywhere except a + * 1280x120 opaque rectangle three quarters down and horizontally + * centered. */ + fill_plane_gradient(src, "y8", 0, 0, 1920, 1080); + fill_plane_gradient(src, "u8", 0, 0, 1920, 1080); + fill_plane_gradient(src, "v8", 0, 0, 1920, 1080); + ubase_assert(ubuf_pic_plane_set_color(src, "a8", 0, 0, 1920, 1080, + (const uint8_t []){ 0 }, 1)); + ubase_assert(ubuf_pic_plane_set_color(src, "a8", 320, 810, 1280, 120, + (const uint8_t []){ 255 }, 1)); + + { + struct timespec start, end; + + alpha = 255; + threshold = 80; + + clock_gettime(CLOCK_MONOTONIC, &start); + for (int i = 0; i < calls; i++) { + ubase_assert(ubuf_pic_blit(dst, src, 0, 0, 0, 0, 1920, 1080, + alpha, threshold)); + if ((i + 1) % 1000 == 0) { + clock_gettime(CLOCK_MONOTONIC, &end); + double ms = (end.tv_sec - start.tv_sec) * 1000.0 + + (end.tv_nsec - start.tv_nsec) / 1000000.0; + printf("1000 yuv422p blits: %.3f ms (%.1f fps)\n", + ms, 1000.0 / (ms / 1000.0)); + start = end; + } + } + } + + ubuf_free(src); + ubuf_free(dst); + ubuf_mgr_release(src_mgr); + ubuf_mgr_release(dst_mgr); + } + + /* Benchmark: repeatedly blit a full 1920x1080 yuv422p10le picture through a + * rectangular alpha mask and report the throughput. */ + if (calls > 0) { + dst_mgr = ubuf_pic_mem_mgr_alloc(UBUF_POOL_DEPTH, UBUF_POOL_DEPTH, umem_mgr, 1, + UBUF_PREPEND, UBUF_APPEND, + UBUF_PREPEND, UBUF_APPEND, + UBUF_ALIGN, UBUF_ALIGN_HOFFSET); + assert(dst_mgr != NULL); + ubase_assert(ubuf_pic_mem_mgr_add_plane(dst_mgr, "y10l", 1, 1, 2)); + ubase_assert(ubuf_pic_mem_mgr_add_plane(dst_mgr, "u10l", 2, 1, 2)); + ubase_assert(ubuf_pic_mem_mgr_add_plane(dst_mgr, "v10l", 2, 1, 2)); + + src_mgr = ubuf_pic_mem_mgr_alloc(UBUF_POOL_DEPTH, UBUF_POOL_DEPTH, umem_mgr, 1, + UBUF_PREPEND, UBUF_APPEND, + UBUF_PREPEND, UBUF_APPEND, + UBUF_ALIGN, UBUF_ALIGN_HOFFSET); + assert(src_mgr != NULL); + ubase_assert(ubuf_pic_mem_mgr_add_plane(src_mgr, "y10l", 1, 1, 2)); + ubase_assert(ubuf_pic_mem_mgr_add_plane(src_mgr, "u10l", 2, 1, 2)); + ubase_assert(ubuf_pic_mem_mgr_add_plane(src_mgr, "v10l", 2, 1, 2)); + ubase_assert(ubuf_pic_mem_mgr_add_plane(src_mgr, "a10l", 1, 1, 2)); + + dst = ubuf_pic_alloc(dst_mgr, 1920, 1080); + assert(dst != NULL); + src = ubuf_pic_alloc(src_mgr, 1920, 1080); + assert(src != NULL); + + /* dst: middle gray (all planes 512 == 0x200). */ + ubase_assert(ubuf_pic_plane_set_color(dst, "y10l", 0, 0, 1920, 1080, + (const uint8_t []){ 0x00, 0x02 }, 2)); + ubase_assert(ubuf_pic_plane_set_color(dst, "u10l", 0, 0, 1920, 1080, + (const uint8_t []){ 0x00, 0x02 }, 2)); + ubase_assert(ubuf_pic_plane_set_color(dst, "v10l", 0, 0, 1920, 1080, + (const uint8_t []){ 0x00, 0x02 }, 2)); + + /* src: gradients on the colour planes; alpha zero everywhere except a + * 1280x120 opaque rectangle three quarters down and horizontally + * centered. */ + fill_plane_gradient16(src, "y10l", 0, 0, 1920, 1080); + fill_plane_gradient16(src, "u10l", 0, 0, 1920, 1080); + fill_plane_gradient16(src, "v10l", 0, 0, 1920, 1080); + ubase_assert(ubuf_pic_plane_set_color(src, "a10l", 0, 0, 1920, 1080, + (const uint8_t []){ 0x00, 0x00 }, 2)); + ubase_assert(ubuf_pic_plane_set_color(src, "a10l", 320, 810, 1280, 120, + (const uint8_t []){ 0xff, 0x03 }, 2)); + + { + struct timespec start, end; + + alpha = 1023; + threshold = 80; + + clock_gettime(CLOCK_MONOTONIC, &start); + for (int i = 0; i < calls; i++) { + ubase_assert(ubuf_pic_blit(dst, src, 0, 0, 0, 0, 1920, 1080, + alpha, threshold)); + if ((i + 1) % 1000 == 0) { + clock_gettime(CLOCK_MONOTONIC, &end); + double ms = (end.tv_sec - start.tv_sec) * 1000.0 + + (end.tv_nsec - start.tv_nsec) / 1000000.0; + printf("1000 yuv422p10le blits: %.3f ms (%.1f fps)\n", + ms, 1000.0 / (ms / 1000.0)); + start = end; + } + } + } + + ubuf_free(src); + ubuf_free(dst); + ubuf_mgr_release(src_mgr); + ubuf_mgr_release(dst_mgr); + } + + umem_mgr_release(umem_mgr); + return 0; +}