Skip to content

Commit

Permalink
test: add tests for zimg RGB repacking
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
wm4 committed Nov 9, 2019
1 parent 27d88e4 commit 94d853d
Show file tree
Hide file tree
Showing 10 changed files with 349 additions and 4 deletions.
9 changes: 5 additions & 4 deletions test/img_format.c
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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;
Expand Down Expand Up @@ -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");

Expand Down
18 changes: 18 additions & 0 deletions test/ref/repack_sws.log
Original file line number Diff line number Diff line change
@@ -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 changes: 8 additions & 0 deletions test/ref/repack_zimg.log
Original file line number Diff line number Diff line change
@@ -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 changes: 45 additions & 0 deletions test/scale_sws.c
Original file line number Diff line number Diff line change
@@ -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 changes: 190 additions & 0 deletions test/scale_test.c
Original file line number Diff line number Diff line change
@@ -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 changes: 28 additions & 0 deletions test/scale_test.h
Original file line number Diff line number Diff line change
@@ -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 changes: 40 additions & 0 deletions test/scale_zimg.c
Original file line number Diff line number Diff line change
@@ -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,
};
4 changes: 4 additions & 0 deletions test/tests.c
Original file line number Diff line number Diff line change
Expand Up @@ -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
};

Expand Down
8 changes: 8 additions & 0 deletions test/tests.h
Original file line number Diff line number Diff line change
Expand Up @@ -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))
Expand Down Expand Up @@ -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);
Loading

0 comments on commit 94d853d

Please sign in to comment.