/** * 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 #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); }