From 07e29c2ac4281bccdebedd9081a99ac48fcef2d5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=83=A1=E6=99=A8=E5=AE=87?= <12410918@mail.sustech.edu.cn> Date: Tue, 6 Oct 2026 17:07:58 +0800 Subject: [PATCH] Merge pull request #30057 from hcy11123323:rvv-fused-rotate-hal-pr RVV: add fused HAL implementation for rotate - #30057 ## Pull Request Readiness Checklist See details at [https://github.com/opencv/opencv/wiki/How_to_contribute#making-a-good-pull-request](https://github.com/opencv/opencv/wiki/How_to_contribute#making-a-good-pull-request) - [x] I agree to contribute to the project under the Apache 2 License. - [x] To the best of my knowledge, the proposed patch is not based on code under GPL or another license that is incompatible with OpenCV. - [x] The PR is proposed to the proper branch (`4.x`). - [x] Accuracy tests are included and performance results are provided below. ## Summary This PR adds an RVV HAL implementation for `cv_hal_rotate90` and fuses the transpose and flip operations used by 90-degree and 270-degree rotations into a single vectorized pass. The previous path composes transpose and flip operations, which requires an intermediate image and additional memory traffic. The new RVV path loads several source rows, builds RVV vector tuples, and writes directly to the final rotated positions with strided segmented stores. ## Implementation details - Registers `cv_hal_rotate90` in the RISC-V RVV core HAL. - Processes 1-, 2-, and 8-byte elements in blocks of eight source rows using `vssseg8e8`, `vssseg8e16`, and `vssseg8e64`. - Processes 4-byte elements in blocks of four source rows using `vssseg4e32`. - Uses positive or negative destination strides to implement clockwise and counter-clockwise rotation without a separate flip pass. - Uses dynamic VL and strided vector stores for remaining rows and non-multiple tails. - Supports padded/non-contiguous source rows through `src_step` and `dst_step`. - Returns `CV_HAL_ERROR_NOT_IMPLEMENTED` for aliased input/output and unsupported element sizes, preserving the existing OpenCV fallback behavior. - Keeps the existing RVV flip path for 180-degree rotation when safe. - For 1-byte 180-degree rotation with `VLMAX > 256`, uses 16-bit gather indices with `vrgatherei16` to avoid index wraparound in the existing 8-bit gather path. - Includes compatibility helpers for Clang versions earlier than 18 that do not provide the required vector tuple creation intrinsics. The directly accelerated total element sizes are 1, 2, 4, and 8 bytes. Other sizes, including common 3- and 6-byte pixel formats, fall back to the existing implementation. ## Test environment ```text Build type: Release Compiler: GCC 14.2.0 ISA flags: -march=rv64gcv RISCV_RVV_SCALABLE: ON CPU_BASELINE_REQUIRE: RVV QEMU: 8.2.2, RVV 1.0 ``` ## Accuracy tests The new `Core_Rotate.OddSizesAndPaddedRows` test and the existing randomized rotate test passed under QEMU RVV at VLEN 128, 256, 512, and 1024. ## Performance tests Performance was measured with `opencv_perf_core` under the environment described above. Detailed performance results are provided in the comment below. These measurements are intended to show the relative difference between identical baseline and optimized builds under QEMU. Absolute performance should be confirmed on RISC-V hardware. --- hal/riscv-rvv/include/core.hpp | 8 ++ hal/riscv-rvv/src/core/flip.cpp | 81 ++++++++++------- hal/riscv-rvv/src/core/rotate.cpp | 115 +++++++++++++++++++++++++ hal/riscv-rvv/src/core/rvv_vcreate.hpp | 48 +++++++++++ hal/riscv-rvv/src/core/transpose.cpp | 26 +----- modules/core/src/matrix_transform.cpp | 12 +++ modules/core/test/test_arithm.cpp | 106 +++++++++++++++++++++++ 7 files changed, 341 insertions(+), 55 deletions(-) create mode 100644 hal/riscv-rvv/src/core/rotate.cpp create mode 100644 hal/riscv-rvv/src/core/rvv_vcreate.hpp diff --git a/hal/riscv-rvv/include/core.hpp b/hal/riscv-rvv/include/core.hpp index cfc0cc1046..da2cad1648 100644 --- a/hal/riscv-rvv/include/core.hpp +++ b/hal/riscv-rvv/include/core.hpp @@ -334,6 +334,14 @@ int transpose2d(const uchar* src_data, size_t src_step, uchar* dst_data, size_t #undef cv_hal_transpose2d #define cv_hal_transpose2d cv::rvv_hal::core::transpose2d +/* ############ rotate90 ############ */ + +int rotate90(int src_type, const uchar* src_data, size_t src_step, int src_width, int src_height, + uchar* dst_data, size_t dst_step, int angle); + +#undef cv_hal_rotate90 +#define cv_hal_rotate90 cv::rvv_hal::core::rotate90 + #endif // CV_HAL_RVV_1P0_ENABLED }}} // cv::rvv_hal::core diff --git a/hal/riscv-rvv/src/core/flip.cpp b/hal/riscv-rvv/src/core/flip.cpp index 8a39e98bd1..b730cce191 100644 --- a/hal/riscv-rvv/src/core/flip.cpp +++ b/hal/riscv-rvv/src/core/flip.cpp @@ -7,16 +7,7 @@ #include "rvv_hal.hpp" -#if defined (__clang__) && __clang_major__ < 18 -#define OPENCV_HAL_IMPL_RVV_VCREATE_x3(suffix, width, v0, v1, v2) \ - __riscv_vset_v_##suffix##m##width##_##suffix##m##width##x3(v, 0, v0); \ - v = __riscv_vset(v, 1, v1); \ - v = __riscv_vset(v, 2, v2); -#define __riscv_vcreate_v_u8m2x3(v0, v1, v2) OPENCV_HAL_IMPL_RVV_VCREATE_x3(u8, 2, v0, v1, v2) -#define __riscv_vcreate_v_u16m2x3(v0, v1, v2) OPENCV_HAL_IMPL_RVV_VCREATE_x3(u16, 2, v0, v1, v2) -#define __riscv_vcreate_v_u32m2x3(v0, v1, v2) OPENCV_HAL_IMPL_RVV_VCREATE_x3(u32, 2, v0, v1, v2) -#define __riscv_vcreate_v_u64m2x3(v0, v1, v2) OPENCV_HAL_IMPL_RVV_VCREATE_x3(u64, 2, v0, v1, v2) -#endif +#include "rvv_vcreate.hpp" namespace cv { namespace rvv_hal { namespace core { @@ -24,24 +15,50 @@ namespace cv { namespace rvv_hal { namespace core { namespace { +template +struct FlipC1 : RVV +{ + static inline typename RVV::VecType gather(typename RVV::VecType v, + typename RVV::VecType indices, size_t vl) + { + return __riscv_vrgather(v, indices, vl); + } +}; + +template <> +struct FlipC1 : RVV_U8M4 +{ + // e8,m4 data and e16,m8 indices have the same number of lanes. + // 8-bit indices wrap when more than 256 bytes are reversed at once. + static inline RVV_U16M8::VecType vid(size_t vl) + { + return RVV_U16M8::vid(vl); + } + + static inline VecType gather(VecType v, RVV_U16M8::VecType indices, size_t vl) + { + return __riscv_vrgatherei16(v, indices, vl); + } +}; + #define CV_HAL_RVV_FLIP_C1(name, _Tps, RVV) \ inline void flip_##name(const uchar* src_data, size_t src_step, uchar* dst_data, size_t dst_step, int src_width, int src_height, int flip_mode) { \ for (int h = 0; h < src_height; h++) { \ const _Tps* src_row = (const _Tps*)(src_data + src_step * h); \ - _Tps* dst_row = (_Tps*)(dst_data + dst_step * (flip_mode < 0 ? (src_height - h) : (h + 1))); \ + _Tps* dst_row = (_Tps*)(dst_data + dst_step * (flip_mode < 0 ? (src_height - 1 - h) : h)) + src_width; \ int vl; \ for (int w = 0; w < src_width; w += vl) { \ vl = RVV::setvl(src_width - w); \ - RVV::VecType indices = __riscv_vrsub(RVV::vid(vl), vl - 1, vl); \ + auto indices = __riscv_vrsub(RVV::vid(vl), vl - 1, vl); \ auto v = RVV::vload(src_row + w, vl); \ - RVV::vstore(dst_row - w - vl, __riscv_vrgather(v, indices, vl), vl); \ + RVV::vstore(dst_row - w - vl, RVV::gather(v, indices, vl), vl); \ } \ } \ } -CV_HAL_RVV_FLIP_C1(8UC1, uchar, RVV_U8M8) -CV_HAL_RVV_FLIP_C1(16UC1, ushort, RVV_U16M8) -CV_HAL_RVV_FLIP_C1(32UC1, unsigned, RVV_U32M8) -CV_HAL_RVV_FLIP_C1(64UC1, uint64_t, RVV_U64M8) +CV_HAL_RVV_FLIP_C1(8UC1, uchar, FlipC1) +CV_HAL_RVV_FLIP_C1(16UC1, ushort, FlipC1) +CV_HAL_RVV_FLIP_C1(32UC1, unsigned, FlipC1) +CV_HAL_RVV_FLIP_C1(64UC1, uint64_t, FlipC1) #define CV_HAL_RVV_FLIP_INPLACE_C1(name, _Tps, RVV) \ inline void flip_inplace_##name(uchar* data, size_t step, int width, int height, int flip_mode) { \ @@ -50,36 +67,36 @@ inline void flip_inplace_##name(uchar* data, size_t step, int width, int height, int h; \ for (h = 0; h < new_height; h++) { \ _Tps* row_begin = (_Tps*)(data + step * h); \ - _Tps* row_end = (_Tps*)(data + step * (flip_mode < 0 ? (height - h) : (h + 1))); \ + _Tps* row_end = (_Tps*)(data + step * (flip_mode < 0 ? (height - 1 - h) : h)) + width; \ int vl; \ for (int w = 0; w < new_width; w += vl) { \ vl = RVV::setvl(new_width - w); \ - RVV::VecType indices = __riscv_vrsub(RVV::vid(vl), vl - 1, vl); \ + auto indices = __riscv_vrsub(RVV::vid(vl), vl - 1, vl); \ auto v_left = RVV::vload(row_begin + w, vl); \ auto v_right = RVV::vload(row_end - w - vl, vl); \ - RVV::vstore(row_begin + w, __riscv_vrgather(v_right, indices, vl), vl); \ - RVV::vstore(row_end - w - vl, __riscv_vrgather(v_left, indices, vl), vl); \ + RVV::vstore(row_begin + w, RVV::gather(v_right, indices, vl), vl); \ + RVV::vstore(row_end - w - vl, RVV::gather(v_left, indices, vl), vl); \ } \ } \ if (flip_mode == -1 && new_height * 2 != height) { \ _Tps* row_begin = (_Tps*)(data + step * h); \ - _Tps* row_end = (_Tps*)(data + step * (h + 1)); \ + _Tps* row_end = (_Tps*)(data + step * h) + width; \ new_width /= 2; \ int vl; \ for (int w = 0; w < new_width; w += vl) { \ vl = RVV::setvl(new_width - w); \ - RVV::VecType indices = __riscv_vrsub(RVV::vid(vl), vl - 1, vl); \ + auto indices = __riscv_vrsub(RVV::vid(vl), vl - 1, vl); \ auto v_left = RVV::vload(row_begin + w, vl); \ auto v_right = RVV::vload(row_end - w - vl, vl); \ - RVV::vstore(row_begin + w, __riscv_vrgather(v_right, indices, vl), vl); \ - RVV::vstore(row_end - w - vl, __riscv_vrgather(v_left, indices, vl), vl); \ + RVV::vstore(row_begin + w, RVV::gather(v_right, indices, vl), vl); \ + RVV::vstore(row_end - w - vl, RVV::gather(v_left, indices, vl), vl); \ } \ } \ } -CV_HAL_RVV_FLIP_INPLACE_C1(8UC1, uchar, RVV_U8M8) -CV_HAL_RVV_FLIP_INPLACE_C1(16UC1, ushort, RVV_U16M8) -CV_HAL_RVV_FLIP_INPLACE_C1(32UC1, unsigned, RVV_U32M8) -CV_HAL_RVV_FLIP_INPLACE_C1(64UC1, uint64_t, RVV_U64M8) +CV_HAL_RVV_FLIP_INPLACE_C1(8UC1, uchar, FlipC1) +CV_HAL_RVV_FLIP_INPLACE_C1(16UC1, ushort, FlipC1) +CV_HAL_RVV_FLIP_INPLACE_C1(32UC1, unsigned, FlipC1) +CV_HAL_RVV_FLIP_INPLACE_C1(64UC1, uint64_t, FlipC1) // Suppress warnings of "ignoring attributes applied to VecType after definition", // VecType is vuint8m2x3_t, vuint16m2x3_t, vuint32m2x3_t or vuint64m2x3_t @@ -113,7 +130,7 @@ CV_HAL_RVV_FLIP_C3_TYPES(64) inline void flip_##name(const uchar* src_data, size_t src_step, uchar* dst_data, size_t dst_step, int src_width, int src_height, int flip_mode) { \ for (int h = 0; h < src_height; h++) { \ const _Tps* src_row = (const _Tps*)(src_data + src_step * h); \ - _Tps* dst_row = (_Tps*)(dst_data + dst_step * (flip_mode < 0 ? (src_height - h) : (h + 1))); \ + _Tps* dst_row = (_Tps*)(dst_data + dst_step * (flip_mode < 0 ? (src_height - 1 - h) : h)) + 3 * src_width; \ int vl; \ for (int w = 0; w < src_width; w += vl) { \ vl = RVV::setvl(src_width - w); \ @@ -136,7 +153,7 @@ inline void flip_inplace_##name(uchar* data, size_t step, int width, int height, int h; \ for (h = 0; h < new_height; h++) { \ _Tps* row_begin = (_Tps*)(data + step * h); \ - _Tps* row_end = (_Tps*)(data + step * (flip_mode < 0 ? (height - h) : (h + 1))); \ + _Tps* row_end = (_Tps*)(data + step * (flip_mode < 0 ? (height - 1 - h) : h)) + 3 * width; \ int vl; \ for (int w = 0; w < new_width; w += vl) { \ vl = RVV::setvl(new_width - w); \ @@ -151,7 +168,7 @@ inline void flip_inplace_##name(uchar* data, size_t step, int width, int height, } \ if (flip_mode == -1 && new_height * 2 != height) { \ _Tps* row_begin = (_Tps*)(data + step * h); \ - _Tps* row_end = (_Tps*)(data + step * (h + 1)); \ + _Tps* row_end = (_Tps*)(data + step * h) + 3 * width; \ new_width /= 2; \ int vl; \ for (int w = 0; w < new_width; w += vl) { \ diff --git a/hal/riscv-rvv/src/core/rotate.cpp b/hal/riscv-rvv/src/core/rotate.cpp new file mode 100644 index 0000000000..a4f2847237 --- /dev/null +++ b/hal/riscv-rvv/src/core/rotate.cpp @@ -0,0 +1,115 @@ +// This file is part of OpenCV project. +// It is subject to the license terms in the LICENSE file found in the top-level directory +// of this distribution and at http://opencv.org/license.html. + +#include "rvv_hal.hpp" +#include "rvv_vcreate.hpp" + +namespace cv { namespace rvv_hal { namespace core { + +#if CV_HAL_RVV_1P0_ENABLED + +namespace { + +#define CV_HAL_RVV_ROTATE_8_ROWS(func, T, RVV, suffix, bits) \ +static void func(const uchar* src_data, size_t src_step, int src_width, int src_height, \ + uchar* dst_data, size_t dst_step, int angle) \ +{ \ + const size_t src_step_elems = src_step / sizeof(T); \ + int y = 0; \ + for (; y <= src_height - 8; y += 8) \ + { \ + const T* src = reinterpret_cast(src_data) + y * src_step_elems; \ + for (int x = 0; x < src_width; ) \ + { \ + const size_t vl = __riscv_vsetvl_e##bits##m1(src_width - x); \ + auto v0 = __riscv_vle##bits##_v_##suffix##m1(src + x, vl); \ + auto v1 = __riscv_vle##bits##_v_##suffix##m1(src + src_step_elems + x, vl); \ + auto v2 = __riscv_vle##bits##_v_##suffix##m1(src + 2 * src_step_elems + x, vl); \ + auto v3 = __riscv_vle##bits##_v_##suffix##m1(src + 3 * src_step_elems + x, vl); \ + auto v4 = __riscv_vle##bits##_v_##suffix##m1(src + 4 * src_step_elems + x, vl); \ + auto v5 = __riscv_vle##bits##_v_##suffix##m1(src + 5 * src_step_elems + x, vl); \ + auto v6 = __riscv_vle##bits##_v_##suffix##m1(src + 6 * src_step_elems + x, vl); \ + auto v7 = __riscv_vle##bits##_v_##suffix##m1(src + 7 * src_step_elems + x, vl); \ + vuint##bits##m1x8_t v; \ + T* dst; \ + ptrdiff_t stride; \ + if (angle == 90) \ + { \ + v = __riscv_vcreate_v_##suffix##m1x8(v7, v6, v5, v4, v3, v2, v1, v0); \ + dst = reinterpret_cast(dst_data + static_cast(x) * dst_step) \ + + src_height - y - 8; \ + stride = static_cast(dst_step); \ + } \ + else \ + { \ + v = __riscv_vcreate_v_##suffix##m1x8(v0, v1, v2, v3, v4, v5, v6, v7); \ + dst = reinterpret_cast(dst_data + static_cast(src_width - 1 - x) * dst_step) + y; \ + stride = -static_cast(dst_step); \ + } \ + __riscv_vssseg8e##bits(dst, stride, v, vl); \ + x += static_cast(vl); \ + } \ + } \ + for (; y < src_height; ++y) \ + { \ + const T* src = reinterpret_cast(src_data + static_cast(y) * src_step); \ + for (int x = 0; x < src_width; ) \ + { \ + const size_t vl = RVV::setvl(src_width - x); \ + auto v = RVV::vload(src + x, vl); \ + T* dst; \ + ptrdiff_t stride; \ + if (angle == 90) \ + { \ + dst = reinterpret_cast(dst_data + static_cast(x) * dst_step) \ + + src_height - 1 - y; \ + stride = static_cast(dst_step); \ + } \ + else \ + { \ + dst = reinterpret_cast(dst_data + static_cast(src_width - 1 - x) * dst_step) + y; \ + stride = -static_cast(dst_step); \ + } \ + RVV::vstore_stride(dst, stride, v, vl); \ + x += static_cast(vl); \ + } \ + } \ +} + +CV_HAL_RVV_ROTATE_8_ROWS(rotate90_8u, uint8_t, RVV_U8M8, u8, 8) +CV_HAL_RVV_ROTATE_8_ROWS(rotate90_16u, uint16_t, RVV_U16M8, u16, 16) +CV_HAL_RVV_ROTATE_8_ROWS(rotate90_32u, uint32_t, RVV_U32M8, u32, 32) +CV_HAL_RVV_ROTATE_8_ROWS(rotate90_64u, uint64_t, RVV_U64M8, u64, 64) + +#undef CV_HAL_RVV_ROTATE_8_ROWS + +} // namespace + +int rotate90(int src_type, const uchar* src_data, size_t src_step, int src_width, int src_height, + uchar* dst_data, size_t dst_step, int angle) +{ + if (src_data == dst_data || (angle != 90 && angle != 270)) + return CV_HAL_ERROR_NOT_IMPLEMENTED; + + const size_t element_size = CV_ELEM_SIZE(src_type); + if (src_step % element_size != 0 || dst_step % element_size != 0) + return CV_HAL_ERROR_NOT_IMPLEMENTED; + + using RotateFunc = void (*)(const uchar*, size_t, int, int, uchar*, size_t, int); + static RotateFunc rotate90Funcs[] = { + nullptr, rotate90_8u, rotate90_16u, nullptr, rotate90_32u, + nullptr, nullptr, nullptr, rotate90_64u + }; + RotateFunc func = element_size < sizeof(rotate90Funcs) / sizeof(rotate90Funcs[0]) + ? rotate90Funcs[element_size] : nullptr; + if (!func) + return CV_HAL_ERROR_NOT_IMPLEMENTED; + + func(src_data, src_step, src_width, src_height, dst_data, dst_step, angle); + return CV_HAL_ERROR_OK; +} + +#endif // CV_HAL_RVV_1P0_ENABLED + +}}} // cv::rvv_hal::core diff --git a/hal/riscv-rvv/src/core/rvv_vcreate.hpp b/hal/riscv-rvv/src/core/rvv_vcreate.hpp new file mode 100644 index 0000000000..38967c8f6a --- /dev/null +++ b/hal/riscv-rvv/src/core/rvv_vcreate.hpp @@ -0,0 +1,48 @@ +// This file is part of OpenCV project. +// It is subject to the license terms in the LICENSE file found in the top-level directory +// of this distribution and at http://opencv.org/license.html. +// +// Copyright (C) 2025, Institute of Software, Chinese Academy of Sciences. +// Copyright (C) 2025, SpaceMIT Inc., all rights reserved. + +#ifndef OPENCV_HAL_RVV_VCREATE_HPP_INCLUDED +#define OPENCV_HAL_RVV_VCREATE_HPP_INCLUDED + +// Tuple creation intrinsics were added in Clang 18. +// These shims are shared by flip, transpose and rotate. +#if defined(__clang__) && __clang_major__ < 18 +#define OPENCV_HAL_IMPL_RVV_VCREATE_x3(suffix, width, v0, v1, v2) \ + __riscv_vset_v_##suffix##m##width##_##suffix##m##width##x3(v, 0, v0); \ + v = __riscv_vset(v, 1, v1); \ + v = __riscv_vset(v, 2, v2); +#define __riscv_vcreate_v_u8m2x3(v0, v1, v2) OPENCV_HAL_IMPL_RVV_VCREATE_x3(u8, 2, v0, v1, v2) +#define __riscv_vcreate_v_u16m2x3(v0, v1, v2) OPENCV_HAL_IMPL_RVV_VCREATE_x3(u16, 2, v0, v1, v2) +#define __riscv_vcreate_v_u32m2x3(v0, v1, v2) OPENCV_HAL_IMPL_RVV_VCREATE_x3(u32, 2, v0, v1, v2) +#define __riscv_vcreate_v_u64m2x3(v0, v1, v2) OPENCV_HAL_IMPL_RVV_VCREATE_x3(u64, 2, v0, v1, v2) + +#define OPENCV_HAL_IMPL_RVV_VCREATE_x4(suffix, width, v0, v1, v2, v3) \ + __riscv_vset_v_##suffix##m##width##_##suffix##m##width##x4(v, 0, v0); \ + v = __riscv_vset(v, 1, v1); \ + v = __riscv_vset(v, 2, v2); \ + v = __riscv_vset(v, 3, v3); + +#define OPENCV_HAL_IMPL_RVV_VCREATE_x8(suffix, width, v0, v1, v2, v3, v4, v5, v6, v7) \ + __riscv_vset_v_##suffix##m##width##_##suffix##m##width##x8(v, 0, v0); \ + v = __riscv_vset(v, 1, v1); \ + v = __riscv_vset(v, 2, v2); \ + v = __riscv_vset(v, 3, v3); \ + v = __riscv_vset(v, 4, v4); \ + v = __riscv_vset(v, 5, v5); \ + v = __riscv_vset(v, 6, v6); \ + v = __riscv_vset(v, 7, v7); + +#define __riscv_vcreate_v_u8m1x8(v0, v1, v2, v3, v4, v5, v6, v7) OPENCV_HAL_IMPL_RVV_VCREATE_x8(u8, 1, v0, v1, v2, v3, v4, v5, v6, v7) +#define __riscv_vcreate_v_u16m1x8(v0, v1, v2, v3, v4, v5, v6, v7) OPENCV_HAL_IMPL_RVV_VCREATE_x8(u16, 1, v0, v1, v2, v3, v4, v5, v6, v7) +#define __riscv_vcreate_v_i32m1x4(v0, v1, v2, v3) OPENCV_HAL_IMPL_RVV_VCREATE_x4(i32, 1, v0, v1, v2, v3) +#define __riscv_vcreate_v_i64m1x8(v0, v1, v2, v3, v4, v5, v6, v7) OPENCV_HAL_IMPL_RVV_VCREATE_x8(i64, 1, v0, v1, v2, v3, v4, v5, v6, v7) + +#define __riscv_vcreate_v_u32m1x8(v0, v1, v2, v3, v4, v5, v6, v7) OPENCV_HAL_IMPL_RVV_VCREATE_x8(u32, 1, v0, v1, v2, v3, v4, v5, v6, v7) +#define __riscv_vcreate_v_u64m1x8(v0, v1, v2, v3, v4, v5, v6, v7) OPENCV_HAL_IMPL_RVV_VCREATE_x8(u64, 1, v0, v1, v2, v3, v4, v5, v6, v7) +#endif + +#endif // OPENCV_HAL_RVV_VCREATE_HPP_INCLUDED diff --git a/hal/riscv-rvv/src/core/transpose.cpp b/hal/riscv-rvv/src/core/transpose.cpp index 4f7ccd63d2..7a0746a5b2 100644 --- a/hal/riscv-rvv/src/core/transpose.cpp +++ b/hal/riscv-rvv/src/core/transpose.cpp @@ -7,28 +7,7 @@ #include "rvv_hal.hpp" -#if defined (__clang__) && __clang_major__ < 18 -#define OPENCV_HAL_IMPL_RVV_VCREATE_x4(suffix, width, v0, v1, v2, v3) \ - __riscv_vset_v_##suffix##m##width##_##suffix##m##width##x4(v, 0, v0); \ - v = __riscv_vset(v, 1, v1); \ - v = __riscv_vset(v, 2, v2); \ - v = __riscv_vset(v, 3, v3); - -#define OPENCV_HAL_IMPL_RVV_VCREATE_x8(suffix, width, v0, v1, v2, v3, v4, v5, v6, v7) \ - __riscv_vset_v_##suffix##m##width##_##suffix##m##width##x8(v, 0, v0); \ - v = __riscv_vset(v, 1, v1); \ - v = __riscv_vset(v, 2, v2); \ - v = __riscv_vset(v, 3, v3); \ - v = __riscv_vset(v, 4, v4); \ - v = __riscv_vset(v, 5, v5); \ - v = __riscv_vset(v, 6, v6); \ - v = __riscv_vset(v, 7, v7); - -#define __riscv_vcreate_v_u8m1x8(v0, v1, v2, v3, v4, v5, v6, v7) OPENCV_HAL_IMPL_RVV_VCREATE_x8(u8, 1, v0, v1, v2, v3, v4, v5, v6, v7) -#define __riscv_vcreate_v_u16m1x8(v0, v1, v2, v3, v4, v5, v6, v7) OPENCV_HAL_IMPL_RVV_VCREATE_x8(u16, 1, v0, v1, v2, v3, v4, v5, v6, v7) -#define __riscv_vcreate_v_i32m1x4(v0, v1, v2, v3) OPENCV_HAL_IMPL_RVV_VCREATE_x4(i32, 1, v0, v1, v2, v3) -#define __riscv_vcreate_v_i64m1x8(v0, v1, v2, v3, v4, v5, v6, v7) OPENCV_HAL_IMPL_RVV_VCREATE_x8(i64, 1, v0, v1, v2, v3, v4, v5, v6, v7) -#endif +#include "rvv_vcreate.hpp" namespace cv { namespace rvv_hal { namespace core { @@ -186,7 +165,8 @@ static void transpose2d_32sC2(const uchar *src_data, size_t src_step, uchar *dst using Transpose2dFunc = void (*)(const uchar*, size_t, uchar*, size_t, int, int); int transpose2d(const uchar* src_data, size_t src_step, uchar* dst_data, size_t dst_step, int src_width, int src_height, int element_size) { - if (src_data == dst_data) { + if (src_data == dst_data || element_size <= 0 || + src_step % element_size != 0 || dst_step % element_size != 0) { return CV_HAL_ERROR_NOT_IMPLEMENTED; } diff --git a/modules/core/src/matrix_transform.cpp b/modules/core/src/matrix_transform.cpp index 309d8d7ec2..54a6b5560a 100644 --- a/modules/core/src/matrix_transform.cpp +++ b/modules/core/src/matrix_transform.cpp @@ -325,6 +325,18 @@ static void transpose_48bit_simd(const uchar* src, size_t sstep, uchar* dst, siz template static void transpose_( const uchar* src, size_t sstep, uchar* dst, size_t dstep, Size sz ) { + // Byte-channel matrices may have strides that are not aligned for T. + // Preserve byte strides and avoid unaligned typed accesses in this case. + if (!isAligned(reinterpret_cast(src) | + reinterpret_cast(dst) | sstep | dstep)) + { + for (int y = 0; y < sz.height; ++y) + for (int x = 0; x < sz.width; ++x) + std::memcpy(dst + static_cast(x) * dstep + y * sizeof(T), + src + static_cast(y) * sstep + x * sizeof(T), sizeof(T)); + return; + } + #if CV_SIMD128 switch (sizeof(T)) { diff --git a/modules/core/test/test_arithm.cpp b/modules/core/test/test_arithm.cpp index ffe6ead18e..d87ee2078c 100644 --- a/modules/core/test/test_arithm.cpp +++ b/modules/core/test/test_arithm.cpp @@ -1012,6 +1012,112 @@ struct RotateOp : public BaseElemWiseOp int rotatecode; }; +typedef testing::TestWithParam > RotatePaddedTest; + +TEST_P(RotatePaddedTest, accuracy) +{ + const int type = get<0>(GetParam()); + const Size size = get<1>(GetParam()); + const int rotateCode = get<2>(GetParam()); + RNG& rng = theRNG(); + + Mat storage(size.height + 2, size.width + 2, type); + Mat src = storage(Rect(1, 1, size.width, size.height)); + rng.fill(src, RNG::UNIFORM, Scalar::all(0), Scalar::all(255)); + const Size dstSize = rotateCode == ROTATE_180 ? size : Size(size.height, size.width); + Mat dstStorage(dstSize.height + 2, dstSize.width + 2, type, Scalar::all(0)); + Mat actual = dstStorage(Rect(1, 1, dstSize.width, dstSize.height)); + Mat expected; + reference::rotate(src, expected, rotateCode); + cv::rotate(src, actual, rotateCode); + EXPECT_EQ(0, cvtest::norm(expected, actual, NORM_INF)); + Mat expectedStorage(dstStorage.size(), type, Scalar::all(0)); + expected.copyTo(expectedStorage(Rect(1, 1, dstSize.width, dstSize.height))); + EXPECT_EQ(0, cvtest::norm(expectedStorage, dstStorage, NORM_INF)); +} + +INSTANTIATE_TEST_CASE_P(Core_Rotate, RotatePaddedTest, testing::Combine( + testing::Values(CV_8UC1, CV_16UC1, CV_32SC1, CV_64FC1), + testing::Values(Size(3550, 3), Size(513, 7), Size(1025, 9), Size(17, 19)), + testing::Values(ROTATE_90_CLOCKWISE, ROTATE_180, ROTATE_90_COUNTERCLOCKWISE))); + +typedef testing::TestWithParam > FlipWideTest; + +TEST_P(FlipWideTest, accuracy) +{ + const Size size = get<0>(GetParam()); + const int flipCode = get<1>(GetParam()); + const bool inPlace = get<2>(GetParam()); + const bool padded = get<3>(GetParam()); + const int border = padded ? 1 : 0; + const Rect roi(border, border, size.width, size.height); + Mat storage(size.height + 2 * border, size.width + 2 * border, CV_8UC1, Scalar::all(0)); + Mat src = storage(roi); + theRNG().fill(src, RNG::UNIFORM, 0, 256); + Mat expected; + reference::flip(src, expected, flipCode); + Mat dstStorage(storage.size(), CV_8UC1, Scalar::all(0)); + Mat actual = inPlace ? src : dstStorage(roi); + cv::flip(src, actual, flipCode); + EXPECT_EQ(0, cvtest::norm(expected, actual, NORM_INF)); + Mat expectedStorage(storage.size(), CV_8UC1, Scalar::all(0)); + expected.copyTo(expectedStorage(roi)); + EXPECT_EQ(0, cvtest::norm(expectedStorage, inPlace ? storage : dstStorage, NORM_INF)); +} + +INSTANTIATE_TEST_CASE_P(Core_Flip, FlipWideTest, testing::Combine( + testing::Values(Size(255, 1), Size(256, 2), Size(257, 3), Size(513, 3), + Size(1025, 4), Size(3550, 3)), + testing::Values(-1, 1), testing::Bool(), testing::Bool())); + +// Multi-channel byte matrices may have a row step not divisible by elemSize(). +typedef testing::TestWithParam > TransformByteStepTest; + +TEST_P(TransformByteStepTest, accuracy) +{ + const int type = get<0>(GetParam()); + const int operation = get<1>(GetParam()); + const int paddedSide = get<2>(GetParam()); + const Size size(17, 9); + const Size dstSize = operation == ROTATE_180 ? size : Size(size.height, size.width); + const size_t srcStep = size.width * CV_ELEM_SIZE(type) + (paddedSide != 1 ? 1 : 0); + const size_t dstStep = dstSize.width * CV_ELEM_SIZE(type) + (paddedSide != 0 ? 1 : 0); + vector srcBuffer(srcStep * size.height, 0); + vector dstBuffer(dstStep * dstSize.height, 0); + Mat src(size, type, srcBuffer.data(), srcStep); + Mat actual(dstSize, type, dstBuffer.data(), dstStep); + theRNG().fill(src, RNG::UNIFORM, 0, 256); + Mat expected(dstSize, type); + for (int y = 0; y < size.height; ++y) + for (int x = 0; x < size.width; ++x) + { + int dy = x, dx = y; + if (operation == ROTATE_90_CLOCKWISE) + dx = size.height - 1 - y; + else if (operation == ROTATE_180) + { + dy = size.height - 1 - y; + dx = size.width - 1 - x; + } + else if (operation == ROTATE_90_COUNTERCLOCKWISE) + dy = size.width - 1 - x; + std::memcpy(expected.ptr(dy) + dx * expected.elemSize(), + src.ptr(y) + x * src.elemSize(), src.elemSize()); + } + if (operation == -1) + cv::transpose(src, actual); + else + cv::rotate(src, actual, operation); + EXPECT_EQ(0, cvtest::norm(expected, actual, NORM_INF)); + for (int y = 0; y < dstSize.height; ++y) + for (size_t x = dstSize.width * actual.elemSize(); x < dstStep; ++x) + EXPECT_EQ(0, dstBuffer[y * dstStep + x]); +} + +INSTANTIATE_TEST_CASE_P(Core_Transform, TransformByteStepTest, testing::Combine( + testing::Values(CV_8UC2, CV_8UC4, CV_8UC(8)), + testing::Values(-1, 0, 1, 2), testing::Values(0, 1, 2))); + struct TransposeOp : public BaseElemWiseOp { TransposeOp() : BaseElemWiseOp(1, FIX_ALPHA+FIX_BETA+FIX_GAMMA, 1, 1, Scalar::all(0)) {}