802 lines
26 KiB
C
802 lines
26 KiB
C
/**
|
|
* Copyright (C) 2025 Niklas Haas
|
|
*
|
|
* This file is part of FFmpeg.
|
|
*
|
|
* FFmpeg is free software; you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License as published by
|
|
* the Free Software Foundation; either version 2 of the License, or
|
|
* (at your option) any later version.
|
|
*
|
|
* FFmpeg is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License along
|
|
* with FFmpeg; if not, write to the Free Software Foundation, Inc.,
|
|
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
|
*/
|
|
|
|
#include <string.h>
|
|
|
|
#include "libavutil/avassert.h"
|
|
#include "libavutil/mem_internal.h"
|
|
#include "libavutil/refstruct.h"
|
|
|
|
#include "libswscale/ops.h"
|
|
#include "libswscale/ops_dispatch.h"
|
|
#include "libswscale/uops.h"
|
|
#include "libswscale/uops_macros.h"
|
|
|
|
#include "checkasm.h"
|
|
|
|
enum {
|
|
NB_PLANES = 4,
|
|
PIXELS = 64,
|
|
LINES = 16,
|
|
};
|
|
|
|
enum {
|
|
U8 = SWS_PIXEL_U8,
|
|
U16 = SWS_PIXEL_U16,
|
|
U32 = SWS_PIXEL_U32,
|
|
F32 = SWS_PIXEL_F32,
|
|
};
|
|
|
|
#define FMT(fmt, ...) tprintf((char[256]) {0}, 256, fmt, __VA_ARGS__)
|
|
static const char *tprintf(char buf[], size_t size, const char *fmt, ...)
|
|
{
|
|
va_list ap;
|
|
va_start(ap, fmt);
|
|
vsnprintf(buf, size, fmt, ap);
|
|
va_end(ap);
|
|
return buf;
|
|
}
|
|
|
|
static int rw_pixel_bits(const SwsOp *op)
|
|
{
|
|
if (op->rw.mode == SWS_RW_PALETTE)
|
|
return 8; /* index size */
|
|
|
|
int elems = 0;
|
|
switch (op->rw.mode) {
|
|
case SWS_RW_PLANAR: elems = 1; break;
|
|
case SWS_RW_PACKED: elems = op->rw.elems; break;
|
|
}
|
|
|
|
const int size = ff_sws_pixel_type_size(op->type);
|
|
const int bits = 8 >> op->rw.frac;
|
|
av_assert1(bits >= 1);
|
|
return elems * size * bits;
|
|
}
|
|
|
|
static float rndf(void)
|
|
{
|
|
union { uint32_t u; float f; } x;
|
|
do {
|
|
x.u = rnd();
|
|
} while (!isnormal(x.f));
|
|
return x.f;
|
|
}
|
|
|
|
static void fill32f(float *line, int num, unsigned range)
|
|
{
|
|
const float scale = (float) range / UINT32_MAX;
|
|
for (int i = 0; i < num; i++)
|
|
line[i] = range ? scale * rnd() : rndf();
|
|
}
|
|
|
|
static void fill32(uint32_t *line, int num, unsigned range)
|
|
{
|
|
for (int i = 0; i < num; i++)
|
|
line[i] = (range && range < UINT_MAX) ? rnd() % (range + 1) : rnd();
|
|
}
|
|
|
|
static void fill16(uint16_t *line, int num, unsigned range)
|
|
{
|
|
if (!range) {
|
|
fill32((uint32_t *) line, AV_CEIL_RSHIFT(num, 1), 0);
|
|
} else {
|
|
for (int i = 0; i < num; i++)
|
|
line[i] = rnd() % (range + 1);
|
|
}
|
|
}
|
|
|
|
static void fill8(uint8_t *line, int num, unsigned range)
|
|
{
|
|
if (!range) {
|
|
fill32((uint32_t *) line, AV_CEIL_RSHIFT(num, 2), 0);
|
|
} else {
|
|
for (int i = 0; i < num; i++)
|
|
line[i] = rnd() % (range + 1);
|
|
}
|
|
}
|
|
|
|
static void set_range(AVRational64 *rangeq, unsigned range, unsigned range_def)
|
|
{
|
|
if (!range)
|
|
range = range_def;
|
|
if (range)
|
|
*rangeq = (AVRational64) { range, 1 };
|
|
}
|
|
|
|
static void check_compiled(const char *name,
|
|
const SwsOp *read_op, const SwsOp *write_op,
|
|
const int ranges[NB_PLANES],
|
|
const SwsCompiledOp *comp_ref,
|
|
const SwsCompiledOp *comp_new)
|
|
{
|
|
/**
|
|
* We can't use `check_func()` alone because the actual function pointer
|
|
* may be a wrapper or entry point shared by multiple implementations.
|
|
* Solve it by hashing in the active CPU flags as well.
|
|
*/
|
|
uintptr_t id = (uintptr_t) comp_new->func;
|
|
id ^= (id << 6) + (id >> 2) + 0x9e3779b97f4a7c15 + comp_new->cpu_flags;
|
|
if (!check_key(id, "%s", name))
|
|
return;
|
|
|
|
declare_func(void, const SwsOpExec *, const void *, int bx, int y, int bx_end, int y_end);
|
|
|
|
static DECLARE_ALIGNED_64(char, src0)[NB_PLANES][LINES][PIXELS * sizeof(uint32_t[4])];
|
|
static DECLARE_ALIGNED_64(char, src1)[NB_PLANES][LINES][PIXELS * sizeof(uint32_t[4])];
|
|
static DECLARE_ALIGNED_64(char, dst0)[NB_PLANES][LINES][PIXELS * sizeof(uint32_t[4])];
|
|
static DECLARE_ALIGNED_64(char, dst1)[NB_PLANES][LINES][PIXELS * sizeof(uint32_t[4])];
|
|
|
|
av_assert0(PIXELS % comp_new->block_size == 0);
|
|
for (int p = 0; p < NB_PLANES; p++) {
|
|
void *plane = src0[p];
|
|
switch (read_op->type) {
|
|
case U8:
|
|
fill8(plane, sizeof(src0[p]) / sizeof(uint8_t), ranges[p]);
|
|
break;
|
|
case U16:
|
|
fill16(plane, sizeof(src0[p]) / sizeof(uint16_t), ranges[p]);
|
|
break;
|
|
case U32:
|
|
fill32(plane, sizeof(src0[p]) / sizeof(uint32_t), ranges[p]);
|
|
break;
|
|
case F32:
|
|
fill32f(plane, sizeof(src0[p]) / sizeof(uint32_t), ranges[p]);
|
|
break;
|
|
}
|
|
}
|
|
|
|
memcpy(src1, src0, sizeof(src0));
|
|
memset(dst0, 0, sizeof(dst0));
|
|
memset(dst1, 0, sizeof(dst1));
|
|
|
|
const int read_size = PIXELS * rw_pixel_bits(read_op) >> 3;
|
|
const int write_size = PIXELS * rw_pixel_bits(write_op) >> 3;
|
|
|
|
SwsOpExec exec = {0};
|
|
exec.width = PIXELS;
|
|
exec.height = exec.slice_h = LINES;
|
|
for (int i = 0; i < NB_PLANES; i++) {
|
|
exec.in_stride[i] = sizeof(src0[i][0]);
|
|
exec.out_stride[i] = sizeof(dst0[i][0]);
|
|
exec.in_bump[i] = exec.in_stride[i] - read_size;
|
|
exec.out_bump[i] = exec.out_stride[i] - write_size;
|
|
}
|
|
|
|
if (read_op->rw.mode == SWS_RW_PALETTE) {
|
|
static_assert(sizeof(src0[1]) >= sizeof(uint32_t[256]), "palette plane too small");
|
|
exec.in_bump[1] = exec.in_stride[1] = 0;
|
|
}
|
|
|
|
int32_t in_bump_y[LINES];
|
|
if (read_op->rw.filter.op == SWS_OP_FILTER_V) {
|
|
const int *offsets = read_op->rw.filter.kernel->offsets;
|
|
for (int y = 0; y < LINES - 1; y++)
|
|
in_bump_y[y] = offsets[y + 1] - offsets[y] - 1;
|
|
in_bump_y[LINES - 1] = 0;
|
|
exec.in_bump_y = in_bump_y;
|
|
}
|
|
|
|
int32_t in_offset_x[PIXELS];
|
|
if (read_op->rw.filter.op == SWS_OP_FILTER_H) {
|
|
const int *offsets = read_op->rw.filter.kernel->offsets;
|
|
const int rw_bits = rw_pixel_bits(read_op);
|
|
for (int x = 0; x < PIXELS; x++)
|
|
in_offset_x[x] = offsets[x] * rw_bits >> 3;
|
|
exec.in_offset_x = in_offset_x;
|
|
}
|
|
|
|
for (int i = 0; i < NB_PLANES; i++) {
|
|
exec.in[i] = (void *) src0[i];
|
|
exec.out[i] = (void *) dst0[i];
|
|
exec.block_size_in[i] = comp_ref->block_size * rw_pixel_bits(read_op) >> 3;
|
|
exec.block_size_out[i] = comp_ref->block_size * rw_pixel_bits(write_op) >> 3;
|
|
}
|
|
checkasm_call(comp_ref->func, &exec, comp_ref->priv, 0, 0, PIXELS / comp_ref->block_size, LINES);
|
|
|
|
for (int i = 0; i < NB_PLANES; i++) {
|
|
exec.in[i] = (void *) src1[i];
|
|
exec.out[i] = (void *) dst1[i];
|
|
exec.block_size_in[i] = comp_new->block_size * rw_pixel_bits(read_op) >> 3;
|
|
exec.block_size_out[i] = comp_new->block_size * rw_pixel_bits(write_op) >> 3;
|
|
}
|
|
checkasm_call_checked(comp_new->func, &exec, comp_new->priv, 0, 0, PIXELS / comp_new->block_size, LINES);
|
|
|
|
for (int i = 0; i < NB_PLANES; i++) {
|
|
const char *desc = FMT("%s[%d]", name, i);
|
|
const int stride = sizeof(dst0[i][0]);
|
|
|
|
switch (write_op->type) {
|
|
case U8:
|
|
checkasm_check(uint8_t, (void *) dst0[i], stride,
|
|
(void *) dst1[i], stride,
|
|
write_size, LINES, desc);
|
|
break;
|
|
case U16:
|
|
checkasm_check(uint16_t, (void *) dst0[i], stride,
|
|
(void *) dst1[i], stride,
|
|
write_size >> 1, LINES, desc);
|
|
break;
|
|
case U32:
|
|
checkasm_check(uint32_t, (void *) dst0[i], stride,
|
|
(void *) dst1[i], stride,
|
|
write_size >> 2, LINES, desc);
|
|
break;
|
|
case F32:
|
|
checkasm_check(float_ulp, (void *) dst0[i], stride,
|
|
(void *) dst1[i], stride,
|
|
write_size >> 2, LINES, desc, 0);
|
|
break;
|
|
}
|
|
|
|
if (write_op->rw.mode == SWS_RW_PACKED)
|
|
break;
|
|
}
|
|
|
|
bench(comp_new->func, &exec, comp_new->priv, 0, 0, PIXELS / comp_new->block_size, LINES);
|
|
}
|
|
|
|
static void check_ops(const char *name, const unsigned ranges[NB_PLANES],
|
|
const SwsOp *ops)
|
|
{
|
|
SwsContext *ctx = sws_alloc_context();
|
|
if (!ctx)
|
|
return;
|
|
ctx->flags = SWS_BITEXACT;
|
|
|
|
static const unsigned def_ranges[4] = {0};
|
|
if (!ranges)
|
|
ranges = def_ranges;
|
|
|
|
const SwsOp *read_op, *write_op;
|
|
SwsOpList oplist = {
|
|
.ops = (SwsOp *) ops,
|
|
.plane_src = {0, 1, 2, 3},
|
|
.plane_dst = {0, 1, 2, 3},
|
|
};
|
|
|
|
read_op = &ops[0];
|
|
for (oplist.num_ops = 0; ops[oplist.num_ops].op; oplist.num_ops++)
|
|
write_op = &ops[oplist.num_ops];
|
|
|
|
for (int p = 0; p < NB_PLANES; p++) {
|
|
switch (read_op->type) {
|
|
case U8:
|
|
set_range(&oplist.comps_src.max[p], ranges[p], UINT8_MAX);
|
|
oplist.comps_src.min[p] = (AVRational64) { 0, 1 };
|
|
break;
|
|
case U16:
|
|
set_range(&oplist.comps_src.max[p], ranges[p], UINT16_MAX);
|
|
oplist.comps_src.min[p] = (AVRational64) { 0, 1 };
|
|
break;
|
|
case U32:
|
|
set_range(&oplist.comps_src.max[p], ranges[p], UINT32_MAX);
|
|
oplist.comps_src.min[p] = (AVRational64) { 0, 1 };
|
|
break;
|
|
case F32:
|
|
if (ranges[p]) {
|
|
oplist.comps_src.max[p] = (AVRational64) { ranges[p], 1 };
|
|
oplist.comps_src.min[p] = (AVRational64) { 0, 1 };
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
static const SwsOpBackend *backend_ref;
|
|
if (!backend_ref) {
|
|
for (int n = 0; ff_sws_op_backends[n]; n++) {
|
|
if (!strcmp(ff_sws_op_backends[n]->name, "c")) {
|
|
backend_ref = ff_sws_op_backends[n];
|
|
break;
|
|
}
|
|
}
|
|
av_assert0(backend_ref);
|
|
}
|
|
|
|
/* Always compile `ops` using the C backend as a reference */
|
|
SwsCompiledOp comp_ref = {0};
|
|
int ret = ff_sws_ops_compile(ctx, backend_ref, &oplist, &comp_ref);
|
|
if (ret < 0) {
|
|
av_assert0(ret != AVERROR(ENOTSUP));
|
|
fail();
|
|
goto done;
|
|
}
|
|
|
|
/* Check with the C backend to establish a reference */
|
|
check_compiled(name, read_op, write_op, ranges, &comp_ref, &comp_ref);
|
|
|
|
/* Iterate over every other backend, and test it against the C reference */
|
|
for (int n = 0; ff_sws_op_backends[n]; n++) {
|
|
const SwsOpBackend *backend = ff_sws_op_backends[n];
|
|
if (backend->hw_format != AV_PIX_FMT_NONE || backend == backend_ref)
|
|
continue;
|
|
|
|
SwsCompiledOp comp_new = {0};
|
|
int ret = ff_sws_ops_compile(ctx, backend, &oplist, &comp_new);
|
|
if (ret == AVERROR(ENOTSUP)) {
|
|
continue;
|
|
} else if (ret < 0) {
|
|
fail();
|
|
goto done;
|
|
}
|
|
|
|
/* Distinguish backends from each other even with same CPU flags */
|
|
checkasm_set_func_variant("%s_%s", backend->name, checkasm_get_cpu_suffix());
|
|
check_compiled(name, read_op, write_op, ranges, &comp_ref, &comp_new);
|
|
ff_sws_compiled_op_unref(&comp_new);
|
|
}
|
|
|
|
done:
|
|
ff_sws_compiled_op_unref(&comp_ref);
|
|
sws_free_context(&ctx);
|
|
}
|
|
|
|
#define CHECK_RANGES(NAME, RANGES, N_IN, N_OUT, IN, OUT, ...) \
|
|
do { \
|
|
check_ops(NAME, RANGES, (SwsOp[]) { \
|
|
{ \
|
|
.op = SWS_OP_READ, \
|
|
.type = IN, \
|
|
.rw.elems = N_IN, \
|
|
}, \
|
|
__VA_ARGS__, \
|
|
{ \
|
|
.op = SWS_OP_WRITE, \
|
|
.type = OUT, \
|
|
.rw.elems = N_OUT, \
|
|
}, {0} \
|
|
}); \
|
|
} while (0)
|
|
|
|
#define MK_RANGES(R) ((const unsigned[]) { R, R, R, R })
|
|
#define CHECK_RANGE(NAME, RANGE, N_IN, N_OUT, IN, OUT, ...) \
|
|
CHECK_RANGES(NAME, MK_RANGES(RANGE), N_IN, N_OUT, IN, OUT, __VA_ARGS__)
|
|
|
|
#define CHECK(NAME, N_IN, N_OUT, IN, OUT, ...) \
|
|
CHECK_RANGE(NAME, 0, N_IN, N_OUT, IN, OUT, __VA_ARGS__)
|
|
|
|
static inline int mask_num(const SwsCompMask mask)
|
|
{
|
|
switch (mask) {
|
|
case SWS_COMP_ELEMS(1): return 1;
|
|
case SWS_COMP_ELEMS(2): return 2;
|
|
case SWS_COMP_ELEMS(3): return 3;
|
|
case SWS_COMP_ELEMS(4): return 4;
|
|
default: return 0;
|
|
}
|
|
}
|
|
|
|
#define CHECK_MASK(NAME, MASK, RANGES, IN, OUT, ...) \
|
|
do { \
|
|
const SwsCompMask mask = (MASK); \
|
|
const int num = mask_num(mask); \
|
|
if (!num) \
|
|
break; /* can't test these with current infrastructure */ \
|
|
CHECK_RANGES(NAME, RANGES, 4, num, IN, OUT, __VA_ARGS__); \
|
|
} while (0)
|
|
|
|
static AVRational64 rndq(SwsPixelType t)
|
|
{
|
|
const unsigned num = rnd();
|
|
if (ff_sws_pixel_type_is_int(t)) {
|
|
const int bits = ff_sws_pixel_type_size(t) * 8;
|
|
const unsigned mask = UINT_MAX >> (32 - bits);
|
|
return (AVRational64) { num & mask, 1 };
|
|
} else {
|
|
const unsigned den = rnd();
|
|
return (AVRational64) { num, den ? den : 1 };
|
|
}
|
|
}
|
|
|
|
static void check_read(const char *name, const SwsUOp *uop)
|
|
{
|
|
SwsReadWriteMode mode;
|
|
switch (uop->uop) {
|
|
case SWS_UOP_READ_PACKED:
|
|
case SWS_UOP_READ_BIT:
|
|
case SWS_UOP_READ_NIBBLE: mode = SWS_RW_PACKED; break;
|
|
case SWS_UOP_READ_PLANAR: mode = SWS_RW_PLANAR; break;
|
|
case SWS_UOP_READ_PALETTE: mode = SWS_RW_PALETTE; break;
|
|
default: return;
|
|
}
|
|
|
|
const int num = mask_num(uop->mask);
|
|
check_ops(name, NULL, (SwsOp[]) {
|
|
{
|
|
.op = SWS_OP_READ,
|
|
.type = uop->type,
|
|
.rw.elems = num,
|
|
.rw.mode = mode,
|
|
.rw.frac = uop->uop == SWS_UOP_READ_BIT ? 3 :
|
|
uop->uop == SWS_UOP_READ_NIBBLE ? 1 : 0,
|
|
}, {
|
|
.op = SWS_OP_WRITE,
|
|
.type = uop->type,
|
|
.rw.elems = num,
|
|
}, {0}
|
|
});
|
|
}
|
|
|
|
static void check_write(const char *name, const SwsUOp *uop)
|
|
{
|
|
SwsReadWriteMode mode;
|
|
switch (uop->uop) {
|
|
case SWS_UOP_WRITE_BIT:
|
|
case SWS_UOP_WRITE_NIBBLE:
|
|
case SWS_UOP_WRITE_PACKED: mode = SWS_RW_PACKED; break;
|
|
case SWS_UOP_WRITE_PLANAR: mode = SWS_RW_PLANAR; break;
|
|
default: return;
|
|
}
|
|
|
|
const int frac = uop->uop == SWS_UOP_WRITE_BIT ? 3 :
|
|
uop->uop == SWS_UOP_WRITE_NIBBLE ? 1 : 0;
|
|
const int num = mask_num(uop->mask);
|
|
const int bits = 8 >> frac;
|
|
const unsigned range = (1 << bits) - 1;
|
|
|
|
check_ops(name, MK_RANGES(range), (SwsOp[]) {
|
|
{
|
|
.op = SWS_OP_READ,
|
|
.type = uop->type,
|
|
.rw.elems = num,
|
|
}, {
|
|
.op = SWS_OP_WRITE,
|
|
.type = uop->type,
|
|
.rw.elems = num,
|
|
.rw.mode = mode,
|
|
.rw.frac = frac,
|
|
}, {0}
|
|
});
|
|
}
|
|
|
|
static void check_filter(const char *name, const SwsUOp *uop)
|
|
{
|
|
const int num = mask_num(uop->mask);
|
|
const bool is_vert = uop->uop == SWS_UOP_READ_PLANAR_FV;
|
|
|
|
SwsFilterParams par = {
|
|
.scaler_params = { SWS_PARAM_DEFAULT, SWS_PARAM_DEFAULT },
|
|
.dst_size = is_vert ? LINES : PIXELS,
|
|
};
|
|
|
|
const SwsScaler scalers[] = {
|
|
SWS_SCALE_POINT,
|
|
SWS_SCALE_SINC,
|
|
};
|
|
|
|
for (int s = 0; s < FF_ARRAY_ELEMS(scalers); s++) {
|
|
par.scaler = scalers[s];
|
|
|
|
for (par.src_size = 1; par.src_size <= par.dst_size; par.src_size <<= 1) {
|
|
SwsFilterWeights *filter;
|
|
if (ff_sws_filter_generate(NULL, &par, &filter) < 0) {
|
|
fail();
|
|
return;
|
|
}
|
|
|
|
char desc[256];
|
|
snprintf(desc, sizeof(desc), "%s_%s_%d", name, filter->name, par.src_size);
|
|
check_ops(desc, NULL, (SwsOp[]) {
|
|
{
|
|
.op = SWS_OP_READ,
|
|
.type = uop->type,
|
|
.rw.elems = num,
|
|
.rw.filter = {
|
|
.op = is_vert ? SWS_OP_FILTER_V : SWS_OP_FILTER_H,
|
|
.kernel = filter,
|
|
.type = SWS_PIXEL_F32,
|
|
},
|
|
}, {
|
|
.op = SWS_OP_WRITE,
|
|
.type = SWS_PIXEL_F32,
|
|
.rw.elems = num,
|
|
}, {0}
|
|
});
|
|
|
|
av_refstruct_unref(&filter);
|
|
}
|
|
}
|
|
}
|
|
|
|
static void check_cast(const char *name, const SwsUOp *uop)
|
|
{
|
|
SwsPixelType dst;
|
|
switch (uop->uop) {
|
|
case SWS_UOP_TO_U8: dst = SWS_PIXEL_U8; break;
|
|
case SWS_UOP_TO_U16: dst = SWS_PIXEL_U16; break;
|
|
case SWS_UOP_TO_U32: dst = SWS_PIXEL_U32; break;
|
|
case SWS_UOP_TO_F32: dst = SWS_PIXEL_F32; break;
|
|
default: return;
|
|
}
|
|
|
|
const int isize = ff_sws_pixel_type_size(uop->type);
|
|
const int osize = ff_sws_pixel_type_size(dst);
|
|
unsigned range = UINT32_MAX >> (32 - osize * 8);
|
|
if (isize < osize || !ff_sws_pixel_type_is_int(dst))
|
|
range = 0;
|
|
|
|
CHECK_MASK(name, uop->mask, MK_RANGES(range), uop->type, dst, {
|
|
.op = SWS_OP_CONVERT,
|
|
.type = uop->type,
|
|
.convert.to = dst,
|
|
});
|
|
}
|
|
|
|
static void check_expand_bit(const char *name, const SwsUOp *uop)
|
|
{
|
|
AVRational64 factor = { .den = 1 };
|
|
switch (uop->type) {
|
|
case SWS_PIXEL_U8: factor.num = UINT8_MAX; break;
|
|
case SWS_PIXEL_U16: factor.num = UINT16_MAX; break;
|
|
case SWS_PIXEL_U32: factor.num = UINT32_MAX; break;
|
|
default: return;
|
|
}
|
|
|
|
CHECK_MASK(name, uop->mask, MK_RANGES(1), uop->type, uop->type, {
|
|
.op = SWS_OP_SCALE,
|
|
.type = uop->type,
|
|
.scale.factor = factor,
|
|
});
|
|
}
|
|
|
|
static void check_expand(const char *name, const SwsUOp *uop)
|
|
{
|
|
SwsPixelType dst = SWS_PIXEL_NONE;
|
|
switch (uop->uop) {
|
|
case SWS_UOP_EXPAND_PAIR: dst = SWS_PIXEL_U16; break;
|
|
case SWS_UOP_EXPAND_QUAD: dst = SWS_PIXEL_U32; break;
|
|
}
|
|
|
|
av_assert0(uop->type == SWS_PIXEL_U8);
|
|
CHECK_MASK(name, uop->mask, NULL, uop->type, dst, {
|
|
.op = SWS_OP_CONVERT,
|
|
.type = uop->type,
|
|
.convert = {
|
|
.to = dst,
|
|
.expand = true,
|
|
},
|
|
});
|
|
}
|
|
|
|
static void check_swizzle(const char *name, const SwsUOp *uop)
|
|
{
|
|
const SwsSwizzleUOp *swiz = &uop->par.swizzle;
|
|
CHECK_MASK(name, uop->mask, NULL, uop->type, uop->type, {
|
|
.op = SWS_OP_SWIZZLE,
|
|
.type = uop->type,
|
|
.swizzle.in = { swiz->in[0], swiz->in[1], swiz->in[2], swiz->in[3] },
|
|
});
|
|
}
|
|
|
|
static void check_scale(const char *name, const SwsUOp *uop)
|
|
{
|
|
unsigned range = 0;
|
|
AVRational64 scale;
|
|
|
|
if (ff_sws_pixel_type_is_int(uop->type)) {
|
|
/* Ensure the result won't exceed the value range */
|
|
const int bits = ff_sws_pixel_type_size(uop->type) * 8;
|
|
const unsigned max = UINT32_MAX >> (32 - bits);
|
|
scale = (AVRational64) { rnd() & (max >> 1), 1 };
|
|
range = max / (scale.num ? scale.num : 1);
|
|
} else {
|
|
scale = rndq(uop->type);
|
|
}
|
|
|
|
CHECK_MASK(name, uop->mask, MK_RANGES(range), uop->type, uop->type, {
|
|
.op = SWS_OP_SCALE,
|
|
.type = uop->type,
|
|
.scale.factor = scale,
|
|
});
|
|
}
|
|
|
|
static void check_clamp(const char *name, const SwsUOp *uop)
|
|
{
|
|
const SwsPixelType t = uop->type;
|
|
CHECK_MASK(name, uop->mask, NULL, t, t, {
|
|
.op = uop->uop == SWS_UOP_MIN ? SWS_OP_MIN : SWS_OP_MAX,
|
|
.type = t,
|
|
.clamp.limit = { rndq(t), rndq(t), rndq(t), rndq(t) },
|
|
});
|
|
}
|
|
|
|
static void check_swap_bytes(const char *name, const SwsUOp *uop)
|
|
{
|
|
CHECK_MASK(name, uop->mask, NULL, uop->type, uop->type, {
|
|
.op = SWS_OP_SWAP_BYTES,
|
|
.type = uop->type,
|
|
});
|
|
}
|
|
|
|
static void check_unpack(const char *name, const SwsUOp *uop)
|
|
{
|
|
const uint8_t *pat = uop->par.pack.pattern;
|
|
const int num = pat[3] ? 4 : 3;
|
|
const int total = pat[0] + pat[1] + pat[2] + pat[3];
|
|
const unsigned range = UINT32_MAX >> (32 - total);
|
|
|
|
CHECK_RANGE(name, range, 1, num, uop->type, uop->type, {
|
|
.op = SWS_OP_UNPACK,
|
|
.type = uop->type,
|
|
.pack.pattern = { pat[0], pat[1], pat[2], pat[3] },
|
|
});
|
|
}
|
|
|
|
static void check_pack(const char *name, const SwsUOp *uop)
|
|
{
|
|
const uint8_t *pat = uop->par.pack.pattern;
|
|
const unsigned ranges[4] = {
|
|
(1 << pat[0]) - 1, (1 << pat[1]) - 1,
|
|
(1 << pat[2]) - 1, (1 << pat[3]) - 1,
|
|
};
|
|
|
|
CHECK_RANGES(name, ranges, 4, 1, uop->type, uop->type, {
|
|
.op = SWS_OP_PACK,
|
|
.type = uop->type,
|
|
.pack.pattern = { pat[0], pat[1], pat[2], pat[3] },
|
|
});
|
|
}
|
|
|
|
static void check_shift(const char *name, const SwsUOp *uop)
|
|
{
|
|
CHECK_MASK(name, uop->mask, NULL, uop->type, uop->type, {
|
|
.op = uop->uop == SWS_UOP_LSHIFT ? SWS_OP_LSHIFT : SWS_OP_RSHIFT,
|
|
.type = uop->type,
|
|
.shift.amount = uop->par.shift.amount,
|
|
});
|
|
}
|
|
|
|
static void check_clear(const char *name, const SwsUOp *uop)
|
|
{
|
|
const SwsPixelType type = uop->type;
|
|
const int bits = ff_sws_pixel_type_size(type) * 8;
|
|
const unsigned range = UINT32_MAX >> (32 - bits);
|
|
const AVRational64 one = (AVRational64) { (int) range, 1};
|
|
const AVRational64 zero = (AVRational64) { 0, 1};
|
|
const AVRational64 val = { (rand() & 0x7F) | 1, 1 };
|
|
|
|
SwsClearOp clear = { .mask = uop->mask };
|
|
for (int i = 0; i < 4; i++) {
|
|
if (SWS_COMP_TEST(uop->par.clear.one, i))
|
|
clear.value[i] = one;
|
|
else if (SWS_COMP_TEST(uop->par.clear.zero, i))
|
|
clear.value[i] = zero;
|
|
else
|
|
clear.value[i] = val;
|
|
}
|
|
|
|
CHECK(name, 4, 4, type, type, {
|
|
.op = SWS_OP_CLEAR,
|
|
.type = type,
|
|
.clear = clear,
|
|
});
|
|
}
|
|
|
|
static void check_linear(const char *name, const SwsUOp *uop)
|
|
{
|
|
const SwsPixelType type = uop->type;
|
|
av_assert0(!ff_sws_pixel_type_is_int(type));
|
|
|
|
SwsLinearOp lin;
|
|
for (int i = 0; i < 4; i++) {
|
|
for (int j = 0; j < 5; j++) {
|
|
if (uop->par.lin.one & SWS_MASK(i, j))
|
|
lin.m[i][j] = (AVRational64) { 1, 1 };
|
|
else if (uop->par.lin.zero & SWS_MASK(i, j))
|
|
lin.m[i][j] = (AVRational64) { 0, 1 };
|
|
else
|
|
lin.m[i][j] = rndq(type);
|
|
}
|
|
}
|
|
|
|
lin.mask = ff_sws_linear_mask(&lin);
|
|
CHECK(name, 4, 4, type, type, {
|
|
.op = SWS_OP_LINEAR,
|
|
.type = type,
|
|
.lin = lin,
|
|
});
|
|
}
|
|
|
|
static void check_dither(const char *name, const SwsUOp *uop)
|
|
{
|
|
const SwsPixelType type = uop->type;
|
|
av_assert0(!ff_sws_pixel_type_is_int(type));
|
|
|
|
SwsDitherOp dither = { .size_log2 = uop->par.dither.size_log2 };
|
|
const int size = 1 << dither.size_log2;
|
|
const uint8_t *y_offset = uop->par.dither.y_offset;
|
|
for (int i = 0; i < 4; i++)
|
|
dither.y_offset[i] = SWS_COMP_TEST(uop->mask, i) ? y_offset[i] : -1;
|
|
|
|
dither.matrix = av_refstruct_allocz(size * size * sizeof(*dither.matrix));
|
|
if (!dither.matrix) {
|
|
fail();
|
|
return;
|
|
}
|
|
|
|
for (int i = 0; i < size * size; i++)
|
|
dither.matrix[i] = rndq(type);
|
|
|
|
CHECK(name, 4, 4, type, type, {
|
|
.op = SWS_OP_DITHER,
|
|
.type = type,
|
|
.dither = dither,
|
|
});
|
|
|
|
av_refstruct_unref(&dither.matrix);
|
|
}
|
|
|
|
static void check_add(const char *name, const SwsUOp *uop)
|
|
{
|
|
/* SwsOp has no concept of SWS_OP_ADD; this is only used for
|
|
* SWS_OP_DITHER with a 1x1 dither matrix; so translate the uop */
|
|
check_dither(name, &(SwsUOp) {
|
|
.uop = SWS_UOP_DITHER,
|
|
.type = uop->type,
|
|
.mask = uop->mask,
|
|
.par.dither.size_log2 = 0,
|
|
});
|
|
}
|
|
|
|
#define CHECK_FUNCTION(CHECK, NAME, ...) \
|
|
CHECK(#NAME, &(SwsUOp) { __VA_ARGS__ });
|
|
|
|
#define CHECK_FOR(UOP, CHECK) \
|
|
SWS_FOR_STRUCT(U8, UOP, CHECK_FUNCTION, CHECK) \
|
|
SWS_FOR_STRUCT(U16, UOP, CHECK_FUNCTION, CHECK) \
|
|
SWS_FOR_STRUCT(U32, UOP, CHECK_FUNCTION, CHECK) \
|
|
SWS_FOR_STRUCT(F32, UOP, CHECK_FUNCTION, CHECK) \
|
|
report(#UOP)
|
|
|
|
void checkasm_check_sw_ops(void)
|
|
{
|
|
CHECK_FOR(READ_PLANAR, check_read);
|
|
CHECK_FOR(READ_PLANAR_FH, check_filter);
|
|
CHECK_FOR(READ_PLANAR_FV, check_filter);
|
|
CHECK_FOR(READ_PACKED, check_read);
|
|
CHECK_FOR(READ_NIBBLE, check_read);
|
|
CHECK_FOR(READ_BIT, check_read);
|
|
CHECK_FOR(READ_PALETTE, check_read);
|
|
CHECK_FOR(WRITE_PLANAR, check_write);
|
|
CHECK_FOR(WRITE_PACKED, check_write);
|
|
CHECK_FOR(WRITE_NIBBLE, check_write);
|
|
CHECK_FOR(WRITE_BIT, check_write);
|
|
CHECK_FOR(PERMUTE, check_swizzle);
|
|
CHECK_FOR(COPY, check_swizzle);
|
|
CHECK_FOR(EXPAND_BIT, check_expand_bit);
|
|
CHECK_FOR(EXPAND_PAIR, check_expand);
|
|
CHECK_FOR(EXPAND_QUAD, check_expand);
|
|
CHECK_FOR(SWAP_BYTES, check_swap_bytes);
|
|
CHECK_FOR(TO_U8, check_cast);
|
|
CHECK_FOR(TO_U16, check_cast);
|
|
CHECK_FOR(TO_U32, check_cast);
|
|
CHECK_FOR(TO_F32, check_cast);
|
|
CHECK_FOR(SCALE, check_scale);
|
|
CHECK_FOR(ADD, check_add);
|
|
CHECK_FOR(MIN, check_clamp);
|
|
CHECK_FOR(MAX, check_clamp);
|
|
CHECK_FOR(UNPACK, check_unpack);
|
|
CHECK_FOR(PACK, check_pack);
|
|
CHECK_FOR(LSHIFT, check_shift);
|
|
CHECK_FOR(RSHIFT, check_shift);
|
|
CHECK_FOR(CLEAR, check_clear);
|
|
CHECK_FOR(LINEAR, check_linear);
|
|
CHECK_FOR(DITHER, check_dither);
|
|
}
|