test: add tests for zimg RGB repacking

This tests the RGB repacker code in zimg, which deserves to be tested
because it's tricky and there will be more formats.

scale_test.c contains some code that can be used to test any scaler. Or
at least that would be great; currently it can only test repacking of
some byte-aligned-component RGB formats. It should be called
repack_test.c, but I'm too lazy to change the filename now.

The idea is that libswscale is used to cross-check the conversions
performed by the zimg wrapper. This is why it's "OK" that scale_test.c
does libswscale calls.

scale_sws.c is the equivalent to scale_zimg.c, and is of course
worthless (because it tests libswscale by comparing the results with
libswscale), but still might help with finding bugs in scale_test.c.

This borrows a sorted list of image formats from test/img_format.c, for
the same reason that file sorts them.

There's a slight possibility that this can be used to test vo_gpu.c too
some times in the future.
This commit is contained in:
wm4 2019-11-09 01:50:46 +01:00
parent 27d88e4a9b
commit 94d853d3a3
10 changed files with 349 additions and 4 deletions

View File

@ -7,8 +7,9 @@
#include "video/mp_image.h"
#include "video/sws_utils.h"
static int imgfmts[IMGFMT_AVPIXFMT_END - IMGFMT_AVPIXFMT_START + 100];
static int num_imgfmts;
int imgfmts[IMGFMT_AVPIXFMT_END - IMGFMT_AVPIXFMT_START + 100];
int num_imgfmts;
static enum AVPixelFormat pixfmt_unsup[100];
static int num_pixfmt_unsup;
static bool imgfmts_initialized;
@ -21,7 +22,7 @@ static int cmp_imgfmt_name(const void *a, const void *b)
return strcmp(name_a, name_b);
}
static void find_all_imgfmts(void)
void init_imgfmts_list(void)
{
if (imgfmts_initialized)
return;
@ -61,7 +62,7 @@ static const char *comp_type(enum mp_component_type type)
static void run(struct test_ctx *ctx)
{
find_all_imgfmts();
init_imgfmts_list();
FILE *f = test_open_out(ctx, "img_formats.txt");

18
test/ref/repack_sws.log Normal file
View File

@ -0,0 +1,18 @@
0bgr using gbrp
0rgb using gbrp
abgr using gbrap
argb using gbrap
bgr0 using gbrp
bgr24 using gbrp
bgr48 using gbrp16
bgr48be using gbrp16
bgra using gbrap
bgra64 using gbrap16
bgra64be using gbrap16
rgb0 using gbrp
rgb24 using gbrp
rgb48 using gbrp16
rgb48be using gbrp16
rgba using gbrap
rgba64 using gbrap16
rgba64be using gbrap16

8
test/ref/repack_zimg.log Normal file
View File

@ -0,0 +1,8 @@
0bgr using gbrp
0rgb using gbrp
bgr0 using gbrp
bgr24 using gbrp
bgr48 using gbrp16
rgb0 using gbrp
rgb24 using gbrp
rgb48 using gbrp16

45
test/scale_sws.c Normal file
View File

@ -0,0 +1,45 @@
// Test scaling using libswscale.
// Note: libswscale is already tested in FFmpeg. This code serves mostly to test
// the functionality scale_test.h using the already tested libswscale as
// reference.
#include "scale_test.h"
#include "video/sws_utils.h"
static bool scale(void *pctx, struct mp_image *dst, struct mp_image *src)
{
struct mp_sws_context *ctx = pctx;
return mp_sws_scale(ctx, dst, src) >= 0;
}
static bool supports_fmts(void *pctx, int imgfmt_dst, int imgfmt_src)
{
struct mp_sws_context *ctx = pctx;
return mp_sws_supports_formats(ctx, imgfmt_dst, imgfmt_src);
}
static const struct scale_test_fns fns = {
.scale = scale,
.supports_fmts = supports_fmts,
};
static void run(struct test_ctx *ctx)
{
struct mp_sws_context *sws = mp_sws_alloc(NULL);
struct scale_test *stest = talloc_zero(NULL, struct scale_test);
stest->fns = &fns;
stest->fns_priv = sws;
stest->test_name = "repack_sws";
stest->ctx = ctx;
repack_test_run(stest);
talloc_free(stest);
talloc_free(sws);
}
const struct unittest test_repack_sws = {
.name = "repack_sws",
.run = run,
};

190
test/scale_test.c Normal file
View File

@ -0,0 +1,190 @@
#include <libavcodec/avcodec.h>
#include "scale_test.h"
#include "video/image_writer.h"
#include "video/sws_utils.h"
static struct mp_image *gen_repack_test_img(int w, int h, int bytes, bool rgb,
bool alpha)
{
struct mp_regular_imgfmt planar_desc = {
.component_type = MP_COMPONENT_TYPE_UINT,
.component_size = bytes,
.forced_csp = rgb ? MP_CSP_RGB : 0,
.num_planes = alpha ? 4 : 3,
.planes = {
{1, {rgb ? 2 : 1}},
{1, {rgb ? 3 : 2}},
{1, {rgb ? 1 : 3}},
{1, {4}},
},
.chroma_w = 1,
.chroma_h = 1,
};
int mpfmt = mp_find_regular_imgfmt(&planar_desc);
assert(mpfmt);
struct mp_image *mpi = mp_image_alloc(mpfmt, w, h);
assert(mpi);
// Well, I have no idea what makes a good test image. So here's some crap.
// This contains bars/tiles of solid colors. For each of R/G/B, it toggles
// though 0/100% range, so 2*2*2 = 8 combinations (16 with alpha).
int b_h = 16, b_w = 16;
for (int y = 0; y < h; y++) {
for (int p = 0; p < mpi->num_planes; p++) {
void *line = mpi->planes[p] + mpi->stride[p] * (ptrdiff_t)y;
for (int x = 0; x < w; x += b_w) {
unsigned i = x / b_w + y / b_h * 2;
int c = ((i >> p) & 1);
if (bytes == 1) {
c *= (1 << 8) - 1;
for (int xs = x; xs < x + b_w; xs++)
((uint8_t *)line)[xs] = c;
} else if (bytes == 2) {
c *= (1 << 16) - 1;
for (int xs = x; xs < x + b_w; xs++)
((uint16_t *)line)[xs] = c;
}
}
}
}
return mpi;
}
static void dump_image(struct scale_test *stest, const char *name,
struct mp_image *img)
{
char *path = mp_tprintf(4096, "%s/%s.png", stest->ctx->out_path, name);
struct image_writer_opts opts = image_writer_opts_defaults;
opts.format = AV_CODEC_ID_PNG;
if (!write_image(img, &opts, path, stest->ctx->global, stest->ctx->log)) {
MP_FATAL(stest->ctx, "Failed to write '%s'.\n", path);
abort();
}
}
// Compare 2 images (same format and size) for exact pixel data match.
// Does generally not work with formats that include undefined padding.
// Does not work with non-byte aligned formats.
static void assert_imgs_equal(struct scale_test *stest, FILE *f,
struct mp_image *ref, struct mp_image *new)
{
assert(ref->imgfmt == new->imgfmt);
assert(ref->w == new->w);
assert(ref->h == new->h);
assert(ref->fmt.flags & MP_IMGFLAG_BYTE_ALIGNED);
assert(ref->fmt.bytes[0]);
for (int p = 0; p < ref->num_planes; p++) {
for (int y = 0; y < ref->h; y++) {
void *line_r = ref->planes[p] + ref->stride[p] * (ptrdiff_t)y;
void *line_o = new->planes[p] + new->stride[p] * (ptrdiff_t)y;
size_t size = ref->fmt.bytes[p] * (size_t)new->w;
bool ok = memcmp(line_r, line_o, size) == 0;
if (!ok) {
stest->fail += 1;
char *fn_a = mp_tprintf(80, "img%d_ref", stest->fail);
char *fn_b = mp_tprintf(80, "img%d_new", stest->fail);
fprintf(f, "Images mismatching, dumping to %s/%s\n", fn_a, fn_b);
dump_image(stest, fn_a, ref);
dump_image(stest, fn_b, new);
return;
}
}
}
}
void repack_test_run(struct scale_test *stest)
{
char *logname = mp_tprintf(80, "%s.log", stest->test_name);
FILE *f = test_open_out(stest->ctx, logname);
if (!stest->sws) {
init_imgfmts_list();
stest->sws = mp_sws_alloc(stest);
stest->img_repack_rgb8 = gen_repack_test_img(256, 128, 1, true, false);
stest->img_repack_rgba8 = gen_repack_test_img(256, 128, 1, true, true);
stest->img_repack_rgb16 = gen_repack_test_img(256, 128, 2, true, false);
stest->img_repack_rgba16 = gen_repack_test_img(256, 128, 2, true, true);
talloc_steal(stest, stest->img_repack_rgb8);
talloc_steal(stest, stest->img_repack_rgba8);
talloc_steal(stest, stest->img_repack_rgb16);
talloc_steal(stest, stest->img_repack_rgba16);
}
for (int a = 0; a < num_imgfmts; a++) {
int mpfmt = imgfmts[a];
struct mp_imgfmt_desc fmtdesc = mp_imgfmt_get_desc(mpfmt);
if (!fmtdesc.id || !(fmtdesc.flags & MP_IMGFLAG_RGB) ||
!fmtdesc.component_bits || (fmtdesc.component_bits % 8) ||
fmtdesc.num_planes > 1)
continue;
struct mp_image *test_img = NULL;
bool alpha = fmtdesc.flags & MP_IMGFLAG_ALPHA;
bool hidepth = fmtdesc.component_bits > 8;
if (alpha) {
test_img = hidepth ? stest->img_repack_rgba16 : stest->img_repack_rgba8;
} else {
test_img = hidepth ? stest->img_repack_rgb16 : stest->img_repack_rgb8;
}
if (test_img->imgfmt == mpfmt)
continue;
if (!stest->fns->supports_fmts(stest->fns_priv, mpfmt, test_img->imgfmt))
continue;
if (!mp_sws_supports_formats(stest->sws, mpfmt, test_img->imgfmt))
continue;
fprintf(f, "%s using %s\n", mp_imgfmt_to_name(mpfmt),
mp_imgfmt_to_name(test_img->imgfmt));
struct mp_image *dst = mp_image_alloc(mpfmt, test_img->w, test_img->h);
assert(dst);
// This tests packing.
bool ok = stest->fns->scale(stest->fns_priv, dst, test_img);
assert(ok);
// Cross-check with swscale in the other direction.
// (Mostly so we don't have to worry about padding.)
struct mp_image *src2 =
mp_image_alloc(test_img->imgfmt, test_img->w, test_img->h);
assert(src2);
ok = mp_sws_scale(stest->sws, src2, dst) >= 0;
assert_imgs_equal(stest, f, test_img, src2);
// Assume the other conversion direction also works.
assert(stest->fns->supports_fmts(stest->fns_priv, test_img->imgfmt, mpfmt));
struct mp_image *back = mp_image_alloc(test_img->imgfmt, dst->w, dst->h);
assert(back);
// This tests unpacking.
ok = stest->fns->scale(stest->fns_priv, back, dst);
assert(ok);
assert_imgs_equal(stest, f, test_img, back);
talloc_free(back);
talloc_free(src2);
talloc_free(dst);
}
fclose(f);
assert_text_files_equal(stest->ctx, logname, logname,
"This can fail if FFmpeg adds or removes pixfmts.");
}

28
test/scale_test.h Normal file
View File

@ -0,0 +1,28 @@
#pragma once
#include "tests.h"
#include "video/mp_image.h"
struct scale_test_fns {
bool (*scale)(void *ctx, struct mp_image *dst, struct mp_image *src);
bool (*supports_fmts)(void *ctx, int imgfmt_dst, int imgfmt_src);
};
struct scale_test {
// To be filled in by user.
const struct scale_test_fns *fns;
void *fns_priv;
const char *test_name;
struct test_ctx *ctx;
// Private.
struct mp_image *img_repack_rgb8;
struct mp_image *img_repack_rgba8;
struct mp_image *img_repack_rgb16;
struct mp_image *img_repack_rgba16;
struct mp_sws_context *sws;
int fail;
};
// Test color repacking between packed formats (typically RGB).
void repack_test_run(struct scale_test *stest);

40
test/scale_zimg.c Normal file
View File

@ -0,0 +1,40 @@
#include "scale_test.h"
#include "video/zimg.h"
static bool scale(void *pctx, struct mp_image *dst, struct mp_image *src)
{
struct mp_zimg_context *ctx = pctx;
return mp_zimg_convert(ctx, dst, src);
}
static bool supports_fmts(void *pctx, int imgfmt_dst, int imgfmt_src)
{
return mp_zimg_supports_in_format(imgfmt_src) &&
mp_zimg_supports_out_format(imgfmt_dst);
}
static const struct scale_test_fns fns = {
.scale = scale,
.supports_fmts = supports_fmts,
};
static void run(struct test_ctx *ctx)
{
struct mp_zimg_context *zimg = mp_zimg_alloc();
struct scale_test *stest = talloc_zero(NULL, struct scale_test);
stest->fns = &fns;
stest->fns_priv = zimg;
stest->test_name = "repack_zimg";
stest->ctx = ctx;
repack_test_run(stest);
talloc_free(stest);
talloc_free(zimg);
}
const struct unittest test_repack_zimg = {
.name = "repack_zimg",
.run = run,
};

View File

@ -9,6 +9,10 @@ static const struct unittest *unittests[] = {
&test_img_format,
&test_json,
&test_linked_list,
&test_repack_sws,
#if HAVE_ZIMG
&test_repack_zimg,
#endif
NULL
};

View File

@ -41,6 +41,8 @@ extern const struct unittest test_gl_video;
extern const struct unittest test_img_format;
extern const struct unittest test_json;
extern const struct unittest test_linked_list;
extern const struct unittest test_repack_sws;
extern const struct unittest test_repack_zimg;
#define assert_true(x) assert(x)
#define assert_false(x) assert(!(x))
@ -69,3 +71,9 @@ void assert_text_files_equal_impl(const char *file, int line,
// Open a new file in the out_path. Always succeeds.
FILE *test_open_out(struct test_ctx *ctx, const char *name);
// Sorted list of valid imgfmts. Call init_imgfmts_list() before use.
extern int imgfmts[];
extern int num_imgfmts;
void init_imgfmts_list(void);

View File

@ -400,6 +400,9 @@ def build(ctx):
( "test/img_format.c", "tests" ),
( "test/json.c", "tests" ),
( "test/linked_list.c", "tests" ),
( "test/scale_sws.c", "tests" ),
( "test/scale_test.c", "tests" ),
( "test/scale_zimg.c", "tests && zimg" ),
( "test/tests.c", "tests" ),
## Video