| /** |
| * 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 Lesser General Public |
| * License as published by the Free Software Foundation; either |
| * version 2.1 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 |
| * Lesser General Public License for more details. |
| * |
| * You should have received a copy of the GNU Lesser 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 "libavutil/avassert.h" |
| |
| #include "ops_backend.h" |
| |
| typedef struct MemcpyPriv { |
| int num_planes; |
| int index[4]; /* or -1 to clear plane */ |
| uint8_t clear_value[4]; |
| } MemcpyPriv; |
| |
| /* Memcpy backend for trivial cases */ |
| |
| static void process(const SwsOpExec *exec, const void *priv, |
| int x_start, int y_start, int x_end, int y_end) |
| { |
| const MemcpyPriv *p = priv; |
| const int lines = y_end - y_start; |
| av_assert1(x_start == 0 && x_end == exec->width); |
| |
| for (int i = 0; i < p->num_planes; i++) { |
| uint8_t *out = exec->out[i]; |
| const int idx = p->index[i]; |
| if (idx < 0) { |
| memset(out, p->clear_value[i], exec->out_stride[i] * lines); |
| } else if (exec->out_stride[i] == exec->in_stride[idx]) { |
| memcpy(out, exec->in[idx], exec->out_stride[i] * lines); |
| } else { |
| const int bytes = x_end * exec->block_size_out; |
| const uint8_t *in = exec->in[idx]; |
| for (int y = y_start; y < y_end; y++) { |
| memcpy(out, in, bytes); |
| out += exec->out_stride[i]; |
| in += exec->in_stride[idx]; |
| } |
| } |
| } |
| } |
| |
| static int compile(SwsContext *ctx, SwsOpList *ops, SwsCompiledOp *out) |
| { |
| MemcpyPriv p = {0}; |
| |
| for (int n = 0; n < ops->num_ops; n++) { |
| const SwsOp *op = &ops->ops[n]; |
| switch (op->op) { |
| case SWS_OP_READ: |
| if ((op->rw.packed && op->rw.elems != 1) || op->rw.frac) |
| return AVERROR(ENOTSUP); |
| for (int i = 0; i < op->rw.elems; i++) |
| p.index[i] = i; |
| break; |
| |
| case SWS_OP_SWIZZLE: { |
| const MemcpyPriv orig = p; |
| for (int i = 0; i < 4; i++) { |
| /* Explicitly exclude swizzle masks that contain duplicates, |
| * because these are wasteful to implement as a memcpy */ |
| for (int j = 0; j < i; j++) { |
| if (op->swizzle.in[i] == op->swizzle.in[j]) |
| return AVERROR(ENOTSUP); |
| } |
| p.index[i] = orig.index[op->swizzle.in[i]]; |
| } |
| break; |
| } |
| |
| case SWS_OP_CLEAR: |
| for (int i = 0; i < 4; i++) { |
| if (!op->c.q4[i].den) |
| continue; |
| if (op->c.q4[i].den != 1) |
| return AVERROR(ENOTSUP); |
| |
| /* Ensure all bytes to be cleared are the same, because we |
| * can't memset on multi-byte sequences */ |
| uint8_t val = op->c.q4[i].num & 0xFF; |
| uint32_t ref = val; |
| switch (ff_sws_pixel_type_size(op->type)) { |
| case 2: ref *= 0x101; break; |
| case 4: ref *= 0x1010101; break; |
| } |
| if (ref != op->c.q4[i].num) |
| return AVERROR(ENOTSUP); |
| p.clear_value[i] = val; |
| p.index[i] = -1; |
| } |
| break; |
| |
| case SWS_OP_WRITE: |
| if ((op->rw.packed && op->rw.elems != 1) || op->rw.frac) |
| return AVERROR(ENOTSUP); |
| p.num_planes = op->rw.elems; |
| break; |
| |
| default: |
| return AVERROR(ENOTSUP); |
| } |
| } |
| |
| *out = (SwsCompiledOp) { |
| .block_size = 1, |
| .func = process, |
| .priv = av_memdup(&p, sizeof(p)), |
| .free = av_free, |
| }; |
| return out->priv ? 0 : AVERROR(ENOMEM); |
| } |
| |
| const SwsOpBackend backend_murder = { |
| .name = "memcpy", |
| .compile = compile, |
| }; |