diff --git a/adapters/opencv/intrinsiccv_hal.cpp b/adapters/opencv/intrinsiccv_hal.cpp index 3076ea6d534b5d9bd1588e02e8e2c297c45fb2a9..ea9d27fa815d8f410353bcfdfaa0c6ee1c18e682 100644 --- a/adapters/opencv/intrinsiccv_hal.cpp +++ b/adapters/opencv/intrinsiccv_hal.cpp @@ -669,21 +669,49 @@ int min_max_idx(const uchar *src_data, size_t src_step, int width, int height, int convertTo(const uchar *src_data, size_t src_step, int src_depth, uchar *dst_data, size_t dst_step, int dst_depth, int width, int height, double scale, double shift) { - if (src_depth != dst_depth) { - return CV_HAL_ERROR_NOT_IMPLEMENTED; - } + // scaling only + if (src_depth == dst_depth) { + // no scaling, no advantage + if (fabs(scale - 1.0) < std::numeric_limits::epsilon() && + fabs(shift) < std::numeric_limits::epsilon()) { + return CV_HAL_ERROR_NOT_IMPLEMENTED; + } - // no scaling, no advantage - if (fabs(scale - 1.0) < std::numeric_limits::epsilon() && - fabs(shift) < std::numeric_limits::epsilon()) { + if (src_depth == CV_8U) { + return convert_error(intrinsiccv_scale_u8( + reinterpret_cast(src_data), src_step, + reinterpret_cast(dst_data), dst_step, width, height, + static_cast(scale), static_cast(shift))); + } return CV_HAL_ERROR_NOT_IMPLEMENTED; } - if (src_depth == CV_8U) { - return convert_error(intrinsiccv_scale_u8( - reinterpret_cast(src_data), src_step, - reinterpret_cast(dst_data), dst_step, width, height, - static_cast(scale), static_cast(shift))); + // type conversion only + if (scale == 1.0 && shift == 0.0) { + // float32 to int8 + if (src_depth == CV_32F && dst_depth == CV_8S) { + return convert_error(intrinsiccv_float_conversion_f32_s8( + reinterpret_cast(src_data), src_step, + reinterpret_cast(dst_data), dst_step, width, height)); + } + // float32 to uint8 + if (src_depth == CV_32F && dst_depth == CV_8U) { + return convert_error(intrinsiccv_float_conversion_f32_u8( + reinterpret_cast(src_data), src_step, + reinterpret_cast(dst_data), dst_step, width, height)); + } + // int8 to float32 + if (src_depth == CV_8S && dst_depth == CV_32F) { + return convert_error(intrinsiccv_float_conversion_s8_f32( + reinterpret_cast(src_data), src_step, + reinterpret_cast(dst_data), dst_step, width, height)); + } + // uint8 to float32 + if (src_depth == CV_8U && dst_depth == CV_32F) { + return convert_error(intrinsiccv_float_conversion_u8_f32( + reinterpret_cast(src_data), src_step, + reinterpret_cast(dst_data), dst_step, width, height)); + } } return CV_HAL_ERROR_NOT_IMPLEMENTED; } diff --git a/conformity/opencv/CMakeLists.txt b/conformity/opencv/CMakeLists.txt index 03c3f4f23b26b1802c3e5cae0910f495bb900416..8f821260e9e3405bcea7d95ed4d3ac15b3d63704 100644 --- a/conformity/opencv/CMakeLists.txt +++ b/conformity/opencv/CMakeLists.txt @@ -29,6 +29,9 @@ add_executable( manager manager.cpp tests.cpp + test_sobel.cpp + test_gaussian_blur.cpp + test_float_conv.cpp ) target_link_libraries( @@ -63,6 +66,9 @@ add_executable( subordinate subordinate.cpp tests.cpp + test_sobel.cpp + test_gaussian_blur.cpp + test_float_conv.cpp ) target_link_libraries( diff --git a/conformity/opencv/common.h b/conformity/opencv/common.h index f7736dcc275cccac4135161a408633c74dbd3d98..612b482830d28ae1ce8a7d4da06bbc4980acc4ff 100644 --- a/conformity/opencv/common.h +++ b/conformity/opencv/common.h @@ -21,11 +21,14 @@ #include "opencv2/core.hpp" #include "opencv2/imgproc.hpp" -#define SHM_ID "/opencv_intrinisiccv_conformity_check_shm" -#define SHM_SIZE (1024 * 1024) - -#define REQUEST_MQ_ID "/opencv_intrinisiccv_conformity_request_queue" -#define REPLY_MQ_ID "/opencv_intrinisiccv_conformity_reply_queue" +#define INTRINSICCV_CONFORMITY_SHM_ID \ + "/opencv_intrinisiccv_conformity_check_shm" +#define INTRINSICCV_CONFORMITY_SHM_SIZE (1024 * 1024) + +#define INTRINSICCV_CONFORMITY_REQUEST_MQ_ID \ + "/opencv_intrinisiccv_conformity_request_queue" +#define INTRINSICCV_CONFORMITY_REPLY_MQ_ID \ + "/opencv_intrinisiccv_conformity_reply_queue" class ExceptionWithErrno : public std::exception { public: diff --git a/conformity/opencv/manager.cpp b/conformity/opencv/manager.cpp index 0b3058a2a6d6719249383857d44f508161940689..116845dd952d541ea1700b19b38021ffdfe7babc 100644 --- a/conformity/opencv/manager.cpp +++ b/conformity/opencv/manager.cpp @@ -40,9 +40,10 @@ int main(int argc, char** argv) { throw ExceptionWithErrno("Cannot start subordinate executable"); } - RecreatedSharedMemory sm{SHM_ID, SHM_SIZE}; - RecreatedMessageQueue request_queue{REQUEST_MQ_ID, sm}; - RecreatedMessageQueue reply_queue{REPLY_MQ_ID, sm}; + RecreatedSharedMemory sm{INTRINSICCV_CONFORMITY_SHM_ID, + INTRINSICCV_CONFORMITY_SHM_SIZE}; + RecreatedMessageQueue request_queue{INTRINSICCV_CONFORMITY_REQUEST_MQ_ID, sm}; + RecreatedMessageQueue reply_queue{INTRINSICCV_CONFORMITY_REPLY_MQ_ID, sm}; // Let subordinate know that init is done kill(child_pid, SIGUSR1); diff --git a/conformity/opencv/subordinate.cpp b/conformity/opencv/subordinate.cpp index e7c77f327667023b2e3bae757561c77ebfc7885d..4018c0f79faab057cd48fb23cdf86a20204be796 100644 --- a/conformity/opencv/subordinate.cpp +++ b/conformity/opencv/subordinate.cpp @@ -8,9 +8,10 @@ #include "tests.h" int main(void) { - OpenedSharedMemory sm{SHM_ID, SHM_SIZE}; - OpenedMessageQueue request_queue{REQUEST_MQ_ID, sm}; - OpenedMessageQueue reply_queue{REPLY_MQ_ID, sm}; + OpenedSharedMemory sm{INTRINSICCV_CONFORMITY_SHM_ID, + INTRINSICCV_CONFORMITY_SHM_SIZE}; + OpenedMessageQueue request_queue{INTRINSICCV_CONFORMITY_REQUEST_MQ_ID, sm}; + OpenedMessageQueue reply_queue{INTRINSICCV_CONFORMITY_REPLY_MQ_ID, sm}; wait_for_requests(request_queue, reply_queue); diff --git a/conformity/opencv/test_float_conv.cpp b/conformity/opencv/test_float_conv.cpp new file mode 100644 index 0000000000000000000000000000000000000000..6b3abfe68b0020af794f69397d5e98e0977f0b7b --- /dev/null +++ b/conformity/opencv/test_float_conv.cpp @@ -0,0 +1,214 @@ +// SPDX-FileCopyrightText: 2024 Arm Limited and/or its affiliates +// +// SPDX-License-Identifier: Apache-2.0 + +#include "test_float_conv.h" + +#include + +float floatval_(uint32_t v) { + static_assert(sizeof(float) == 4); + return *reinterpret_cast(&v); +} + +float quietNaN = floatval_(0x7FC00000); +float signalingNaN = floatval_(0x7FA00000); +float posInfinity = floatval_(0x7F800000); +float negInfinity = floatval_(0xFF800000); + +float minusNaN = floatval_(0xFF800001); +float plusNaN = floatval_(0x7F800001); +float plusZero = floatval_(0x00000000); +float minusZero = floatval_(0x80000000); + +float oneNaN = floatval_(0x7FC00001); +float zeroDivZero = floatval_(0xFFC00000); +float floatMin = floatval_(0x00800000); +float floatMax = floatval_(0x7F7FFFFF); + +float posSubnormalMin = floatval_(0x00000001); +float posSubnormalMax = floatval_(0x007FFFFF); +float negSubnormalMin = floatval_(0x80000001); +float negSubnormalMax = floatval_(0x807FFFFF); + +template +cv::Mat exec_float32_to_int8(cv::Mat& input) { + cv::Mat result; + input.convertTo(result, Signed ? CV_8SC1 : CV_8UC1); + return result; +} + +cv::Mat exec_int8_to_float32(cv::Mat& input) { + cv::Mat result; + input.convertTo(result, CV_32FC1); + return result; +} + +#if MANAGER +template +bool test_float32_to_int8_random(int index, + RecreatedMessageQueue& request_queue, + RecreatedMessageQueue& reply_queue) { + cv::RNG rng(0); + + for (size_t x = 5; x <= 16; ++x) { + for (size_t y = 5; y <= 16; ++y) { + cv::Mat input(x, y, CV_32FC(Channels)); + rng.fill(input, cv::RNG::UNIFORM, Signed ? -1000 : 0, 1000); + + cv::Mat actual = exec_float32_to_int8(input); + cv::Mat expected = get_expected_from_subordinate(index, request_queue, + reply_queue, input); + + if (are_matrices_different(0, actual, expected)) { + fail_print_matrices(x, y, input, actual, expected); + return true; + } + } + } + + return false; +} + +template +bool test_int8_to_float32_random(int index, + RecreatedMessageQueue& request_queue, + RecreatedMessageQueue& reply_queue) { + cv::RNG rng(0); + + for (size_t x = 5; x <= 16; ++x) { + for (size_t y = 5; y <= 16; ++y) { + cv::Mat input(x, y, Signed ? CV_8SC(Channels) : CV_8UC(Channels)); + rng.fill(input, cv::RNG::UNIFORM, Signed ? -1000 : 0, 1000); + + cv::Mat actual = exec_int8_to_float32(input); + cv::Mat expected = get_expected_from_subordinate(index, request_queue, + reply_queue, input); + + if (are_matrices_different(0, actual, expected)) { + fail_print_matrices(x, y, input, actual, expected); + return true; + } + } + } + + return false; +} + +static constexpr int custom_data_height = 8; +static constexpr int custom_data_width = 4; + +static float custom_data_float[custom_data_height * custom_data_width] = { + // clang-format off + quietNaN, signalingNaN, posInfinity, negInfinity, + minusNaN, plusNaN, plusZero, minusZero, + oneNaN, zeroDivZero, floatMin, floatMax, + posSubnormalMin, posSubnormalMax, negSubnormalMin, negSubnormalMax, + 1111.11, -1112.22, 113.33, 114.44, + 111.51, 112.62, 113.73, 114.84, + 126.66, 127.11, 128.66, 129.11, + 11.5, 12.5, -11.5, -12.5, + // clang-format on +}; + +static int8_t custom_data_int8[custom_data_height * custom_data_width] = { + // clang-format off + -128, -128, 126, 127, + -128, -128, -128, -128, + -128, -128, -128, 126, + -127, -127, -127, 125, + 126, 127, 113, 114, + 112, 113, 114, 115, + 12, 12, 12, 12, + 11, 11, 11, 11, + // clang-format on +}; + +static uint8_t custom_data_uint8[custom_data_height * custom_data_width] = { + // clang-format off + 0, 0, 254, 255, + 0, 0, 0, 0, + 0, 0, 0, 254, + 1, 1, 1, 253, + 254, 255, 113, 114, + 112, 113, 114, 115, + 12, 12, 12, 12, + 11, 11, 11, 11, + // clang-format on +}; + +template +bool test_float32_to_int8_custom(int index, + RecreatedMessageQueue& request_queue, + RecreatedMessageQueue& reply_queue) { + cv::Mat input(custom_data_height, custom_data_width, CV_32FC1, + custom_data_float); + + cv::Mat actual = exec_float32_to_int8(input); + cv::Mat expected = + get_expected_from_subordinate(index, request_queue, reply_queue, input); + + if (are_matrices_different(0, actual, expected)) { + fail_print_matrices(custom_data_height, custom_data_width, input, actual, + expected); + return true; + } + + return false; +} + +template +bool test_int8_to_float32_custom(int index, + RecreatedMessageQueue& request_queue, + RecreatedMessageQueue& reply_queue) { + cv::Mat input(custom_data_height, custom_data_width, + Signed ? CV_8SC1 : CV_8UC1, + Signed ? static_cast(custom_data_int8) + : static_cast(custom_data_uint8)); + + cv::Mat actual = exec_int8_to_float32(input); + cv::Mat expected = + get_expected_from_subordinate(index, request_queue, reply_queue, input); + + if (are_matrices_different(0, actual, expected)) { + fail_print_matrices(custom_data_height, custom_data_width, input, actual, + expected); + return true; + } + + return false; +} +#endif + +std::vector& float_conversion_tests_singleton() { + // clang-format off + static std::vector tests = { + TEST("Float32 to Signed Int8, fill, 1 channel", (test_float32_to_int8_random), exec_float32_to_int8), + TEST("Float32 to Signed Int8, fill, 2 channel", (test_float32_to_int8_random), exec_float32_to_int8), + TEST("Float32 to Signed Int8, fill, 3 channel", (test_float32_to_int8_random), exec_float32_to_int8), + TEST("Float32 to Signed Int8, fill, 4 channel", (test_float32_to_int8_random), exec_float32_to_int8), + + TEST("Float32 to Unsigned Int8, fill, 1 channel", (test_float32_to_int8_random), exec_float32_to_int8), + TEST("Float32 to Unsigned Int8, fill, 2 channel", (test_float32_to_int8_random), exec_float32_to_int8), + TEST("Float32 to Unsigned Int8, fill, 3 channel", (test_float32_to_int8_random), exec_float32_to_int8), + TEST("Float32 to Unsigned Int8, fill, 4 channel", (test_float32_to_int8_random), exec_float32_to_int8), + + TEST("Float32 to Signed Int8, custom (special)", test_float32_to_int8_custom, exec_float32_to_int8), + TEST("Float32 to Unsigned Int8, custom (special)", test_float32_to_int8_custom, exec_float32_to_int8), + + TEST("Signed Int8 to Float32, fill, 1 channel", (test_int8_to_float32_random), exec_int8_to_float32), + TEST("Signed Int8 to Float32, fill, 2 channel", (test_int8_to_float32_random), exec_int8_to_float32), + TEST("Signed Int8 to Float32, fill, 3 channel", (test_int8_to_float32_random), exec_int8_to_float32), + TEST("Signed Int8 to Float32, fill, 4 channel", (test_int8_to_float32_random), exec_int8_to_float32), + + TEST("Unsigned Int8 to Float32, fill, 1 channel", (test_int8_to_float32_random), exec_int8_to_float32), + TEST("Unsigned Int8 to Float32, fill, 2 channel", (test_int8_to_float32_random), exec_int8_to_float32), + TEST("Unsigned Int8 to Float32, fill, 3 channel", (test_int8_to_float32_random), exec_int8_to_float32), + TEST("Unsigned Int8 to Float32, fill, 4 channel", (test_int8_to_float32_random), exec_int8_to_float32), + + TEST("Signed Int8 Float32, custom (special)", test_int8_to_float32_custom, exec_int8_to_float32), + TEST("Unigned Int8 Float32, custom (special)", test_int8_to_float32_custom, exec_int8_to_float32), + }; + // clang-format on + return tests; +} diff --git a/conformity/opencv/test_float_conv.h b/conformity/opencv/test_float_conv.h new file mode 100644 index 0000000000000000000000000000000000000000..ab7a6d8a1379dc4a883c04acb924d0c9ea29a162 --- /dev/null +++ b/conformity/opencv/test_float_conv.h @@ -0,0 +1,14 @@ +// SPDX-FileCopyrightText: 2024 Arm Limited and/or its affiliates +// +// SPDX-License-Identifier: Apache-2.0 + +#ifndef INTRINSICCV_OPENCV_CONFORMITY_TEST_FLOAT_CONV_H_ +#define INTRINSICCV_OPENCV_CONFORMITY_TEST_FLOAT_CONV_H_ + +#include + +#include "tests.h" + +std::vector& float_conversion_tests_singleton(); + +#endif // INTRINSICCV_OPENCV_CONFORMITY_TEST_FLOAT_CONV_H_ diff --git a/conformity/opencv/test_gaussian_blur.cpp b/conformity/opencv/test_gaussian_blur.cpp new file mode 100644 index 0000000000000000000000000000000000000000..27ae51e2d6952b11cc07799d1a9215bc6e3b875a --- /dev/null +++ b/conformity/opencv/test_gaussian_blur.cpp @@ -0,0 +1,88 @@ +// SPDX-FileCopyrightText: 2024 Arm Limited and/or its affiliates +// +// SPDX-License-Identifier: Apache-2.0 + +#include "test_gaussian_blur.h" + +#include + +template +cv::Mat exec_gaussian_blur(cv::Mat& input) { + cv::Size kernel(KernelSize, KernelSize); + cv::Mat result; + cv::GaussianBlur(input, result, kernel, 0, 0, BorderType); + return result; +} + +#if MANAGER +template +bool test_gaussian_blur(int index, RecreatedMessageQueue& request_queue, + RecreatedMessageQueue& reply_queue) { + cv::RNG rng(0); + + for (size_t x = 5; x <= 16; ++x) { + for (size_t y = 5; y <= 16; ++y) { + cv::Mat input(x, y, CV_8UC(Channels)); + rng.fill(input, cv::RNG::UNIFORM, 0, 255); + + cv::Mat actual = exec_gaussian_blur(input); + cv::Mat expected = get_expected_from_subordinate(index, request_queue, + reply_queue, input); + + if (are_matrices_different(0, actual, expected)) { + fail_print_matrices(x, y, input, actual, expected); + return true; + } + } + } + + return false; +} +#endif + +std::vector& gaussian_blur_tests_singleton() { + // clang-format off + static std::vector tests = { + TEST("Gaussian blur 3x3, BORDER_REFLECT_101, 1 channel", (test_gaussian_blur<3, cv::BORDER_REFLECT_101, 1>), (exec_gaussian_blur<3, cv::BORDER_REFLECT_101>)), + TEST("Gaussian blur 3x3, BORDER_REFLECT_101, 2 channel", (test_gaussian_blur<3, cv::BORDER_REFLECT_101, 2>), (exec_gaussian_blur<3, cv::BORDER_REFLECT_101>)), + TEST("Gaussian blur 3x3, BORDER_REFLECT_101, 3 channel", (test_gaussian_blur<3, cv::BORDER_REFLECT_101, 3>), (exec_gaussian_blur<3, cv::BORDER_REFLECT_101>)), + TEST("Gaussian blur 3x3, BORDER_REFLECT_101, 4 channel", (test_gaussian_blur<3, cv::BORDER_REFLECT_101, 4>), (exec_gaussian_blur<3, cv::BORDER_REFLECT_101>)), + + TEST("Gaussian blur 3x3, BORDER_REFLECT, 1 channel", (test_gaussian_blur<3, cv::BORDER_REFLECT, 1>), (exec_gaussian_blur<3, cv::BORDER_REFLECT>)), + TEST("Gaussian blur 3x3, BORDER_REFLECT, 2 channel", (test_gaussian_blur<3, cv::BORDER_REFLECT, 2>), (exec_gaussian_blur<3, cv::BORDER_REFLECT>)), + TEST("Gaussian blur 3x3, BORDER_REFLECT, 3 channel", (test_gaussian_blur<3, cv::BORDER_REFLECT, 3>), (exec_gaussian_blur<3, cv::BORDER_REFLECT>)), + TEST("Gaussian blur 3x3, BORDER_REFLECT, 4 channel", (test_gaussian_blur<3, cv::BORDER_REFLECT, 4>), (exec_gaussian_blur<3, cv::BORDER_REFLECT>)), + + TEST("Gaussian blur 3x3, BORDER_WRAP, 1 channel", (test_gaussian_blur<3, cv::BORDER_WRAP, 1>), (exec_gaussian_blur<3, cv::BORDER_WRAP>)), + TEST("Gaussian blur 3x3, BORDER_WRAP, 2 channel", (test_gaussian_blur<3, cv::BORDER_WRAP, 2>), (exec_gaussian_blur<3, cv::BORDER_WRAP>)), + TEST("Gaussian blur 3x3, BORDER_WRAP, 3 channel", (test_gaussian_blur<3, cv::BORDER_WRAP, 3>), (exec_gaussian_blur<3, cv::BORDER_WRAP>)), + TEST("Gaussian blur 3x3, BORDER_WRAP, 4 channel", (test_gaussian_blur<3, cv::BORDER_WRAP, 4>), (exec_gaussian_blur<3, cv::BORDER_WRAP>)), + + TEST("Gaussian blur 3x3, BORDER_REPLICATE, 1 channel", (test_gaussian_blur<3, cv::BORDER_REPLICATE, 1>), (exec_gaussian_blur<3, cv::BORDER_REPLICATE>)), + TEST("Gaussian blur 3x3, BORDER_REPLICATE, 2 channel", (test_gaussian_blur<3, cv::BORDER_REPLICATE, 2>), (exec_gaussian_blur<3, cv::BORDER_REPLICATE>)), + TEST("Gaussian blur 3x3, BORDER_REPLICATE, 3 channel", (test_gaussian_blur<3, cv::BORDER_REPLICATE, 3>), (exec_gaussian_blur<3, cv::BORDER_REPLICATE>)), + TEST("Gaussian blur 3x3, BORDER_REPLICATE, 4 channel", (test_gaussian_blur<3, cv::BORDER_REPLICATE, 4>), (exec_gaussian_blur<3, cv::BORDER_REPLICATE>)), + + TEST("Gaussian blur 5x5, BORDER_REFLECT_101, 1 channel", (test_gaussian_blur<5, cv::BORDER_REFLECT_101, 1>), (exec_gaussian_blur<5, cv::BORDER_REFLECT_101>)), + TEST("Gaussian blur 5x5, BORDER_REFLECT_101, 2 channel", (test_gaussian_blur<5, cv::BORDER_REFLECT_101, 2>), (exec_gaussian_blur<5, cv::BORDER_REFLECT_101>)), + TEST("Gaussian blur 5x5, BORDER_REFLECT_101, 3 channel", (test_gaussian_blur<5, cv::BORDER_REFLECT_101, 3>), (exec_gaussian_blur<5, cv::BORDER_REFLECT_101>)), + TEST("Gaussian blur 5x5, BORDER_REFLECT_101, 4 channel", (test_gaussian_blur<5, cv::BORDER_REFLECT_101, 4>), (exec_gaussian_blur<5, cv::BORDER_REFLECT_101>)), + + TEST("Gaussian blur 5x5, BORDER_REFLECT, 1 channel", (test_gaussian_blur<5, cv::BORDER_REFLECT, 1>), (exec_gaussian_blur<5, cv::BORDER_REFLECT>)), + TEST("Gaussian blur 5x5, BORDER_REFLECT, 2 channel", (test_gaussian_blur<5, cv::BORDER_REFLECT, 2>), (exec_gaussian_blur<5, cv::BORDER_REFLECT>)), + TEST("Gaussian blur 5x5, BORDER_REFLECT, 3 channel", (test_gaussian_blur<5, cv::BORDER_REFLECT, 3>), (exec_gaussian_blur<5, cv::BORDER_REFLECT>)), + TEST("Gaussian blur 5x5, BORDER_REFLECT, 4 channel", (test_gaussian_blur<5, cv::BORDER_REFLECT, 4>), (exec_gaussian_blur<5, cv::BORDER_REFLECT>)), + + TEST("Gaussian blur 5x5, BORDER_WRAP, 1 channel", (test_gaussian_blur<5, cv::BORDER_WRAP, 1>), (exec_gaussian_blur<5, cv::BORDER_WRAP>)), + TEST("Gaussian blur 5x5, BORDER_WRAP, 2 channel", (test_gaussian_blur<5, cv::BORDER_WRAP, 2>), (exec_gaussian_blur<5, cv::BORDER_WRAP>)), + TEST("Gaussian blur 5x5, BORDER_WRAP, 3 channel", (test_gaussian_blur<5, cv::BORDER_WRAP, 3>), (exec_gaussian_blur<5, cv::BORDER_WRAP>)), + TEST("Gaussian blur 5x5, BORDER_WRAP, 4 channel", (test_gaussian_blur<5, cv::BORDER_WRAP, 4>), (exec_gaussian_blur<5, cv::BORDER_WRAP>)), + + TEST("Gaussian blur 5x5, BORDER_REPLICATE, 1 channel", (test_gaussian_blur<5, cv::BORDER_REPLICATE, 1>), (exec_gaussian_blur<5, cv::BORDER_REPLICATE>)), + TEST("Gaussian blur 5x5, BORDER_REPLICATE, 2 channel", (test_gaussian_blur<5, cv::BORDER_REPLICATE, 2>), (exec_gaussian_blur<5, cv::BORDER_REPLICATE>)), + TEST("Gaussian blur 5x5, BORDER_REPLICATE, 3 channel", (test_gaussian_blur<5, cv::BORDER_REPLICATE, 3>), (exec_gaussian_blur<5, cv::BORDER_REPLICATE>)), + TEST("Gaussian blur 5x5, BORDER_REPLICATE, 4 channel", (test_gaussian_blur<5, cv::BORDER_REPLICATE, 4>), (exec_gaussian_blur<5, cv::BORDER_REPLICATE>)), + }; + // clang-format on + return tests; +} diff --git a/conformity/opencv/test_gaussian_blur.h b/conformity/opencv/test_gaussian_blur.h new file mode 100644 index 0000000000000000000000000000000000000000..45ee18a6cd82441e52e3591ec38c74a115bc3b9b --- /dev/null +++ b/conformity/opencv/test_gaussian_blur.h @@ -0,0 +1,14 @@ +// SPDX-FileCopyrightText: 2024 Arm Limited and/or its affiliates +// +// SPDX-License-Identifier: Apache-2.0 + +#ifndef INTRINSICCV_OPENCV_CONFORMITY_TEST_GAUSSIAN_BLUR_H_ +#define INTRINSICCV_OPENCV_CONFORMITY_TEST_GAUSSIAN_BLUR_H_ + +#include + +#include "tests.h" + +std::vector& gaussian_blur_tests_singleton(); + +#endif // INTRINSICCV_OPENCV_CONFORMITY_TEST_GAUSSIAN_BLUR_H_ diff --git a/conformity/opencv/test_sobel.cpp b/conformity/opencv/test_sobel.cpp new file mode 100644 index 0000000000000000000000000000000000000000..a87143aa0aa62e41a01cc0224fd9defbafe809f2 --- /dev/null +++ b/conformity/opencv/test_sobel.cpp @@ -0,0 +1,61 @@ +// SPDX-FileCopyrightText: 2024 Arm Limited and/or its affiliates +// +// SPDX-License-Identifier: Apache-2.0 + +#include "test_sobel.h" + +#include + +template +cv::Mat exec_sobel(cv::Mat& input) { + cv::Mat result; + if constexpr (Vertical) { + cv::Sobel(input, result, CV_16S, 0, 1, 3, 1.0, 0.0, cv::BORDER_REPLICATE); + } else { + cv::Sobel(input, result, CV_16S, 1, 0, 3, 1.0, 0.0, cv::BORDER_REPLICATE); + } + return result; +} + +#if MANAGER +template +bool test_sobel(int index, RecreatedMessageQueue& request_queue, + RecreatedMessageQueue& reply_queue) { + cv::RNG rng(0); + + for (size_t x = 5; x <= 16; ++x) { + for (size_t y = 5; y <= 16; ++y) { + cv::Mat input(x, y, CV_8UC(Channels)); + rng.fill(input, cv::RNG::UNIFORM, 0, 255); + + cv::Mat actual = exec_sobel(input); + cv::Mat expected = get_expected_from_subordinate(index, request_queue, + reply_queue, input); + + if (are_matrices_different(0, actual, expected)) { + fail_print_matrices(x, y, input, actual, expected); + return true; + } + } + } + + return false; +} +#endif + +std::vector& sobel_tests_singleton() { + // clang-format off + static std::vector tests = { + TEST("Sobel Vertical, 1 channel", (test_sobel), exec_sobel), + TEST("Sobel Vertical, 2 channel", (test_sobel), exec_sobel), + TEST("Sobel Vertical, 3 channel", (test_sobel), exec_sobel), + TEST("Sobel Vertical, 4 channel", (test_sobel), exec_sobel), + + TEST("Sobel Horizontal, 1 channel", (test_sobel), exec_sobel), + TEST("Sobel Horizontal, 2 channel", (test_sobel), exec_sobel), + TEST("Sobel Horizontal, 3 channel", (test_sobel), exec_sobel), + TEST("Sobel Horizontal, 4 channel", (test_sobel), exec_sobel), + }; + // clang-format on + return tests; +} diff --git a/conformity/opencv/test_sobel.h b/conformity/opencv/test_sobel.h new file mode 100644 index 0000000000000000000000000000000000000000..399945c614348fd351b35244a3bb65746613f467 --- /dev/null +++ b/conformity/opencv/test_sobel.h @@ -0,0 +1,14 @@ +// SPDX-FileCopyrightText: 2024 Arm Limited and/or its affiliates +// +// SPDX-License-Identifier: Apache-2.0 + +#ifndef INTRINSICCV_OPENCV_CONFORMITY_TEST_SOBEL_H_ +#define INTRINSICCV_OPENCV_CONFORMITY_TEST_SOBEL_H_ + +#include + +#include "tests.h" + +std::vector& sobel_tests_singleton(); + +#endif // INTRINSICCV_OPENCV_CONFORMITY_TEST_SOBEL_H_ diff --git a/conformity/opencv/tests.cpp b/conformity/opencv/tests.cpp index 7aede57f73dc018c6a48c7ccd42bd98be3079ea1..596282bfbd15c69fb601fb58be07353db4988544 100644 --- a/conformity/opencv/tests.cpp +++ b/conformity/opencv/tests.cpp @@ -4,220 +4,68 @@ #include "tests.h" +#include #include -#include -#include #include #include "opencv2/core.hpp" #include "opencv2/imgproc.hpp" +#include "test_float_conv.h" +#include "test_gaussian_blur.h" +#include "test_sobel.h" #if MANAGER - -template -static auto abs_diff(T a, T b) { - return a > b ? a - b : b - a; -} - -template -static bool are_matrices_different(T threshold, cv::Mat& A, cv::Mat& B) { - if (A.rows != B.rows || A.cols != B.cols || A.type() != B.type()) { - std::cout << "Matrix size/type mismatch" << std::endl; - return true; - } - - for (int i = 0; i < A.rows; ++i) { - for (int j = 0; j < (A.cols * CV_MAT_CN(A.type())); ++j) { - if (abs_diff(A.at(i, j), B.at(i, j)) > threshold) { - std::cout << "=== Mismatch at: " << i << " " << j << std::endl - << std::endl; - return true; - } - } - } - - return false; -} - -template -bool test_sobel(int index, RecreatedMessageQueue& request_queue, - RecreatedMessageQueue& reply_queue) { - cv::RNG rng(0); - - for (size_t x = 5; x <= 16; ++x) { - for (size_t y = 5; y <= 16; ++y) { - cv::Mat src(x, y, CV_8UC(Channels)); - rng.fill(src, cv::RNG::UNIFORM, 0, 255); - - cv::Mat manager_result; - if constexpr (Vertical) { - cv::Sobel(src, manager_result, CV_16S, 0, 1, 3, 1.0, 0.0, - cv::BORDER_REPLICATE); - } else { - cv::Sobel(src, manager_result, CV_16S, 1, 0, 3, 1.0, 0.0, - cv::BORDER_REPLICATE); - } - - request_queue.request_operation(index, src); - reply_queue.wait(); - if (reply_queue.last_cmd() != index) { - throw std::runtime_error("Invalid reply from subordinate"); - } - - cv::Mat subord_result = reply_queue.cv_mat_from_last_msg(); - - if (are_matrices_different(0, manager_result, subord_result)) { - std::cout << "[FAIL]" << std::endl; - std::cout << "height=" << x << std::endl; - std::cout << "width=" << y << std::endl; - std::cout << "=== Src Matrix:" << std::endl; - std::cout << src << std::endl << std::endl; - std::cout << "=== Manager result:" << std::endl; - std::cout << manager_result << std::endl << std::endl; - std::cout << "=== Subordinate result:" << std::endl; - std::cout << subord_result << std::endl << std::endl; - - return true; - } - } - } - - return false; +void fail_print_matrices(size_t height, size_t width, cv::Mat& input, + cv::Mat& manager_result, cv::Mat& subord_result) { + std::cout << "[FAIL]" << std::endl; + std::cout << "height=" << height << std::endl; + std::cout << "width=" << width << std::endl; + std::cout << "=== Input Matrix:" << std::endl; + std::cout << input << std::endl << std::endl; + std::cout << "=== Manager result (actual):" << std::endl; + std::cout << manager_result << std::endl << std::endl; + std::cout << "=== Subordinate result (expected):" << std::endl; + std::cout << subord_result << std::endl << std::endl; } -template -bool test_gaussian_blur(int index, RecreatedMessageQueue& request_queue, - RecreatedMessageQueue& reply_queue) { - cv::RNG rng(0); - cv::Size kernel(KernelSize, KernelSize); - - for (size_t x = 5; x <= 16; ++x) { - for (size_t y = 5; y <= 16; ++y) { - cv::Mat src(x, y, CV_8UC(Channels)); - rng.fill(src, cv::RNG::UNIFORM, 0, 255); - - cv::Mat manager_result; - cv::GaussianBlur(src, manager_result, kernel, 0, 0, BorderType); - - request_queue.request_operation(index, src); - reply_queue.wait(); - if (reply_queue.last_cmd() != index) { - throw std::runtime_error("Invalid reply from subordinate"); - } - - cv::Mat subord_result = reply_queue.cv_mat_from_last_msg(); - - if (are_matrices_different(0, manager_result, subord_result)) { - std::cout << "[FAIL]" << std::endl; - std::cout << "height=" << x << std::endl; - std::cout << "width=" << y << std::endl; - std::cout << "=== Src Matrix:" << std::endl; - std::cout << src << std::endl << std::endl; - std::cout << "=== Manager result:" << std::endl; - std::cout << manager_result << std::endl << std::endl; - std::cout << "=== Subordinate result:" << std::endl; - std::cout << subord_result << std::endl << std::endl; - - return true; - } - } +cv::Mat get_expected_from_subordinate(int index, + RecreatedMessageQueue& request_queue, + RecreatedMessageQueue& reply_queue, + cv::Mat& input) { + request_queue.request_operation(index, input); + reply_queue.wait(); + if (reply_queue.last_cmd() != index) { + throw std::runtime_error("Invalid reply from subordinate"); } - return false; + return reply_queue.cv_mat_from_last_msg(); } +#endif -using test = std::pair)*>; -#define TEST(name, manager_func, subordinate_func) \ - { name, manager_func } - -#else // MANAGER - -template -cv::Mat exec_sobel(cv::Mat& input) { - cv::Mat result; - if constexpr (Vertical) { - cv::Sobel(input, result, CV_16S, 0, 1, 3, 1.0, 0.0, cv::BORDER_REPLICATE); - } else { - cv::Sobel(input, result, CV_16S, 1, 0, 3, 1.0, 0.0, cv::BORDER_REPLICATE); +template +static std::vector merge_tests( + std::initializer_list& (*)()> test_groups) { + std::vector all_tests; + for (auto singleton : test_groups) { + std::vector& group = singleton(); + all_tests.insert(all_tests.cend(), group.cbegin(), group.cend()); } - return result; -} - -template -cv::Mat exec_gaussian_blur(cv::Mat& inp) { - cv::Size kernel(KernelSize, KernelSize); - cv::Mat out; - cv::GaussianBlur(inp, out, kernel, 0, 0, BorderType); - return out; + return all_tests; } -using test = std::pair)*>; -#define TEST(name, manager_func, subordinate_func) \ - { name, subordinate_func } - -#endif // MANAGER - -// clang-format off -std::vector tests = { - TEST("Sobel Vertical, 1 channel", (test_sobel), exec_sobel), - TEST("Sobel Vertical, 2 channel", (test_sobel), exec_sobel), - TEST("Sobel Vertical, 3 channel", (test_sobel), exec_sobel), - TEST("Sobel Vertical, 4 channel", (test_sobel), exec_sobel), - - TEST("Sobel Horizontal, 1 channel", (test_sobel), exec_sobel), - TEST("Sobel Horizontal, 2 channel", (test_sobel), exec_sobel), - TEST("Sobel Horizontal, 3 channel", (test_sobel), exec_sobel), - TEST("Sobel Horizontal, 4 channel", (test_sobel), exec_sobel), - - TEST("Gaussian blur 3x3, BORDER_REFLECT_101, 1 channel", (test_gaussian_blur<3, cv::BORDER_REFLECT_101, 1>), (exec_gaussian_blur<3, cv::BORDER_REFLECT_101>)), - TEST("Gaussian blur 3x3, BORDER_REFLECT_101, 2 channel", (test_gaussian_blur<3, cv::BORDER_REFLECT_101, 2>), (exec_gaussian_blur<3, cv::BORDER_REFLECT_101>)), - TEST("Gaussian blur 3x3, BORDER_REFLECT_101, 3 channel", (test_gaussian_blur<3, cv::BORDER_REFLECT_101, 3>), (exec_gaussian_blur<3, cv::BORDER_REFLECT_101>)), - TEST("Gaussian blur 3x3, BORDER_REFLECT_101, 4 channel", (test_gaussian_blur<3, cv::BORDER_REFLECT_101, 4>), (exec_gaussian_blur<3, cv::BORDER_REFLECT_101>)), - - TEST("Gaussian blur 3x3, BORDER_REFLECT, 1 channel", (test_gaussian_blur<3, cv::BORDER_REFLECT, 1>), (exec_gaussian_blur<3, cv::BORDER_REFLECT>)), - TEST("Gaussian blur 3x3, BORDER_REFLECT, 2 channel", (test_gaussian_blur<3, cv::BORDER_REFLECT, 2>), (exec_gaussian_blur<3, cv::BORDER_REFLECT>)), - TEST("Gaussian blur 3x3, BORDER_REFLECT, 3 channel", (test_gaussian_blur<3, cv::BORDER_REFLECT, 3>), (exec_gaussian_blur<3, cv::BORDER_REFLECT>)), - TEST("Gaussian blur 3x3, BORDER_REFLECT, 4 channel", (test_gaussian_blur<3, cv::BORDER_REFLECT, 4>), (exec_gaussian_blur<3, cv::BORDER_REFLECT>)), - - TEST("Gaussian blur 3x3, BORDER_WRAP, 1 channel", (test_gaussian_blur<3, cv::BORDER_WRAP, 1>), (exec_gaussian_blur<3, cv::BORDER_WRAP>)), - TEST("Gaussian blur 3x3, BORDER_WRAP, 2 channel", (test_gaussian_blur<3, cv::BORDER_WRAP, 2>), (exec_gaussian_blur<3, cv::BORDER_WRAP>)), - TEST("Gaussian blur 3x3, BORDER_WRAP, 3 channel", (test_gaussian_blur<3, cv::BORDER_WRAP, 3>), (exec_gaussian_blur<3, cv::BORDER_WRAP>)), - TEST("Gaussian blur 3x3, BORDER_WRAP, 4 channel", (test_gaussian_blur<3, cv::BORDER_WRAP, 4>), (exec_gaussian_blur<3, cv::BORDER_WRAP>)), - - TEST("Gaussian blur 3x3, BORDER_REPLICATE, 1 channel", (test_gaussian_blur<3, cv::BORDER_REPLICATE, 1>), (exec_gaussian_blur<3, cv::BORDER_REPLICATE>)), - TEST("Gaussian blur 3x3, BORDER_REPLICATE, 2 channel", (test_gaussian_blur<3, cv::BORDER_REPLICATE, 2>), (exec_gaussian_blur<3, cv::BORDER_REPLICATE>)), - TEST("Gaussian blur 3x3, BORDER_REPLICATE, 3 channel", (test_gaussian_blur<3, cv::BORDER_REPLICATE, 3>), (exec_gaussian_blur<3, cv::BORDER_REPLICATE>)), - TEST("Gaussian blur 3x3, BORDER_REPLICATE, 4 channel", (test_gaussian_blur<3, cv::BORDER_REPLICATE, 4>), (exec_gaussian_blur<3, cv::BORDER_REPLICATE>)), - - TEST("Gaussian blur 5x5, BORDER_REFLECT_101, 1 channel", (test_gaussian_blur<5, cv::BORDER_REFLECT_101, 1>), (exec_gaussian_blur<5, cv::BORDER_REFLECT_101>)), - TEST("Gaussian blur 5x5, BORDER_REFLECT_101, 2 channel", (test_gaussian_blur<5, cv::BORDER_REFLECT_101, 2>), (exec_gaussian_blur<5, cv::BORDER_REFLECT_101>)), - TEST("Gaussian blur 5x5, BORDER_REFLECT_101, 3 channel", (test_gaussian_blur<5, cv::BORDER_REFLECT_101, 3>), (exec_gaussian_blur<5, cv::BORDER_REFLECT_101>)), - TEST("Gaussian blur 5x5, BORDER_REFLECT_101, 4 channel", (test_gaussian_blur<5, cv::BORDER_REFLECT_101, 4>), (exec_gaussian_blur<5, cv::BORDER_REFLECT_101>)), - - TEST("Gaussian blur 5x5, BORDER_REFLECT, 1 channel", (test_gaussian_blur<5, cv::BORDER_REFLECT, 1>), (exec_gaussian_blur<5, cv::BORDER_REFLECT>)), - TEST("Gaussian blur 5x5, BORDER_REFLECT, 2 channel", (test_gaussian_blur<5, cv::BORDER_REFLECT, 2>), (exec_gaussian_blur<5, cv::BORDER_REFLECT>)), - TEST("Gaussian blur 5x5, BORDER_REFLECT, 3 channel", (test_gaussian_blur<5, cv::BORDER_REFLECT, 3>), (exec_gaussian_blur<5, cv::BORDER_REFLECT>)), - TEST("Gaussian blur 5x5, BORDER_REFLECT, 4 channel", (test_gaussian_blur<5, cv::BORDER_REFLECT, 4>), (exec_gaussian_blur<5, cv::BORDER_REFLECT>)), - - TEST("Gaussian blur 5x5, BORDER_WRAP, 1 channel", (test_gaussian_blur<5, cv::BORDER_WRAP, 1>), (exec_gaussian_blur<5, cv::BORDER_WRAP>)), - TEST("Gaussian blur 5x5, BORDER_WRAP, 2 channel", (test_gaussian_blur<5, cv::BORDER_WRAP, 2>), (exec_gaussian_blur<5, cv::BORDER_WRAP>)), - TEST("Gaussian blur 5x5, BORDER_WRAP, 3 channel", (test_gaussian_blur<5, cv::BORDER_WRAP, 3>), (exec_gaussian_blur<5, cv::BORDER_WRAP>)), - TEST("Gaussian blur 5x5, BORDER_WRAP, 4 channel", (test_gaussian_blur<5, cv::BORDER_WRAP, 4>), (exec_gaussian_blur<5, cv::BORDER_WRAP>)), - - TEST("Gaussian blur 5x5, BORDER_REPLICATE, 1 channel", (test_gaussian_blur<5, cv::BORDER_REPLICATE, 1>), (exec_gaussian_blur<5, cv::BORDER_REPLICATE>)), - TEST("Gaussian blur 5x5, BORDER_REPLICATE, 2 channel", (test_gaussian_blur<5, cv::BORDER_REPLICATE, 2>), (exec_gaussian_blur<5, cv::BORDER_REPLICATE>)), - TEST("Gaussian blur 5x5, BORDER_REPLICATE, 3 channel", (test_gaussian_blur<5, cv::BORDER_REPLICATE, 3>), (exec_gaussian_blur<5, cv::BORDER_REPLICATE>)), - TEST("Gaussian blur 5x5, BORDER_REPLICATE, 4 channel", (test_gaussian_blur<5, cv::BORDER_REPLICATE, 4>), (exec_gaussian_blur<5, cv::BORDER_REPLICATE>)), -}; -// clang-format on +std::vector all_tests = merge_tests({ + sobel_tests_singleton, + gaussian_blur_tests_singleton, + float_conversion_tests_singleton, +}); #if MANAGER int run_tests(RecreatedMessageQueue& request_queue, RecreatedMessageQueue& reply_queue) { int ret_val = 0; - for (int i = 0; i < static_cast(tests.size()); ++i) { - std::cout << "Testing " + tests[i].first << std::endl; - if (tests[i].second(i, request_queue, reply_queue)) { + for (int i = 0; i < static_cast(all_tests.size()); ++i) { + std::cout << "Testing " + all_tests[i].first << std::endl; + if (all_tests[i].second(i, request_queue, reply_queue)) { ret_val = 1; } } @@ -225,7 +73,7 @@ int run_tests(RecreatedMessageQueue& request_queue, return ret_val; } -#else // MANAGER +#else void wait_for_requests(OpenedMessageQueue& request_queue, OpenedMessageQueue& reply_queue) { while (true) { @@ -237,13 +85,13 @@ void wait_for_requests(OpenedMessageQueue& request_queue, break; } - if (cmd > static_cast(tests.size())) { - throw std::runtime_error("Invalid operation requestd in subordinate"); + if (cmd > static_cast(all_tests.size())) { + throw std::runtime_error("Invalid operation requested in subordinate"); } cv::Mat input = request_queue.cv_mat_from_last_msg(); - cv::Mat result = tests[cmd].second(input); + cv::Mat result = all_tests[cmd].second(input); reply_queue.reply_operation(cmd, result); } } -#endif // MANAGER +#endif diff --git a/conformity/opencv/tests.h b/conformity/opencv/tests.h index ba92cb050f35de01d4da041e6f8af1468c86f7dd..164ba5c7cf1dee29f7d1a039c57feaee3133b7b1 100644 --- a/conformity/opencv/tests.h +++ b/conformity/opencv/tests.h @@ -5,14 +5,62 @@ #ifndef INTRINSICCV_OPENCV_CONFORMITY_TESTS_H_ #define INTRINSICCV_OPENCV_CONFORMITY_TESTS_H_ +#include +#include +#include + #include "common.h" #if MANAGER +template +static auto abs_diff(T a, T b) { + return a > b ? a - b : b - a; +} + +template +bool are_matrices_different(T threshold, cv::Mat& A, cv::Mat& B) { + if (A.rows != B.rows || A.cols != B.cols || A.type() != B.type()) { + std::cout << "Matrix size/type mismatch" << std::endl; + return true; + } + + for (int i = 0; i < A.rows; ++i) { + for (int j = 0; j < (A.cols * CV_MAT_CN(A.type())); ++j) { + if (abs_diff(A.at(i, j), B.at(i, j)) > threshold) { + std::cout << "=== Mismatch at: " << i << " " << j << std::endl + << std::endl; + return true; + } + } + } + + return false; +} + +void fail_print_matrices(size_t height, size_t width, cv::Mat& input, + cv::Mat& manager_result, cv::Mat& subord_result); + +cv::Mat get_expected_from_subordinate(int index, + RecreatedMessageQueue& request_queue, + RecreatedMessageQueue& reply_queue, + cv::Mat& input); + int run_tests(RecreatedMessageQueue& request_queue, RecreatedMessageQueue& reply_queue); + +typedef bool (*test_function)(int index, RecreatedMessageQueue& request_queue, + RecreatedMessageQueue& reply_queue); +using test = std::pair; +#define TEST(name, test_func, x) \ + { name, test_func } #else void wait_for_requests(OpenedMessageQueue& request_queue, OpenedMessageQueue& reply_queue); + +typedef cv::Mat (*exec_function)(cv::Mat& input); +using test = std::pair; +#define TEST(name, x, exec_func) \ + { name, exec_func } #endif #endif // INTRINSICCV_OPENCV_CONFORMITY_TESTS_H_ diff --git a/intrinsiccv/include/intrinsiccv/dispatch.h b/intrinsiccv/include/intrinsiccv/dispatch.h index 486f934ac84a868af3cc6746ef2c56b6cc9cf5cd..c85b14198410557f6ad166bb54a4ae0ab36c92e2 100644 --- a/intrinsiccv/include/intrinsiccv/dispatch.h +++ b/intrinsiccv/include/intrinsiccv/dispatch.h @@ -60,7 +60,7 @@ static inline bool hwcaps_has_sme2(HwCaps hwcaps) { #define INTRINSICCV_MULTIVERSION_C_API(api_name, neon_impl, sve2_impl, \ sme2_impl) \ - static decltype(neon_impl) *api_name##_resolver() { \ + static decltype(neon_impl) api_name##_resolver() { \ [[maybe_unused]] INTRINSICCV_TARGET_NAMESPACE::HwCaps hwcaps = \ INTRINSICCV_TARGET_NAMESPACE::get_hwcaps(); \ INTRINSICCV_SME2_RESOLVE(sme2_impl); \ @@ -68,7 +68,7 @@ static inline bool hwcaps_has_sme2(HwCaps hwcaps) { return neon_impl; \ } \ extern "C" { \ - decltype(neon_impl) *api_name = api_name##_resolver(); \ + decltype(neon_impl) api_name = api_name##_resolver(); \ } #else // INTRINSICCV_HAVE_SVE2 || INTRINSICCV_HAVE_SME2 @@ -77,7 +77,7 @@ static inline bool hwcaps_has_sme2(HwCaps hwcaps) { sme2_impl) \ \ extern "C" { \ - decltype(neon_impl) *api_name = neon_impl; \ + decltype(neon_impl) api_name = neon_impl; \ } #endif // INTRINSICCV_HAVE_SVE2 || INTRINSICCV_HAVE_SME2 diff --git a/intrinsiccv/include/intrinsiccv/intrinsiccv.h b/intrinsiccv/include/intrinsiccv/intrinsiccv.h index c58397274cc4d0bdf683a22de17250ab78ef97cd..bc8a02fd60061ec8b1b3574f7dcd3f122c071598 100644 --- a/intrinsiccv/include/intrinsiccv/intrinsiccv.h +++ b/intrinsiccv/include/intrinsiccv/intrinsiccv.h @@ -1268,6 +1268,65 @@ INTRINSICCV_API_DECLARATION(intrinsiccv_scale_u8, const uint8_t *src, size_t width, size_t height, float scale, float shift); +/// Converts the elements in `src` from a floating-point type to an integer +/// type, then stores the result in `dst`. +/// +/// Each resulting element is saturated, i.e. it is the smallest/largest +/// number of the type of the element if the `src` data type cannot be +/// represented as the `dst` type. In case of special values, such as the +/// different variations of `NaN`, the result is `0`. Source and destination +/// data length is `width` * `height`. Number of elements is limited to @ref +/// INTRINSICCV_MAX_IMAGE_PIXELS. +/// +/// @param src Pointer to the source data. Must be non-null. +/// @param src_stride Distance in bytes from the start of one row to the +/// start of the next row for the source data. +/// Must not be less than width * sizeof(type). +/// @param dst Pointer to the destination data. Must be non-null. +/// @param dst_stride Distance in bytes from the start of one row to the +/// start of the next row for the destination data. +/// Must not be less than width * sizeof(type). +/// @param width Number of elements in a row. +/// @param height Number of rows in the data. +/// +INTRINSICCV_API_DECLARATION(intrinsiccv_float_conversion_f32_s8, + const float *src, size_t src_stride, int8_t *dst, + size_t dst_stride, size_t width, size_t height); +/// @copydoc intrinsiccv_float_conversion_f32_s8 +INTRINSICCV_API_DECLARATION(intrinsiccv_float_conversion_f32_u8, + const float *src, size_t src_stride, uint8_t *dst, + size_t dst_stride, size_t width, size_t height); + +/// Converts the elements in `src` from an integer type to a floating-point +/// type, then stores the result in `dst`. +/// +/// Each resulting element is saturated, i.e. it is the smallest/largest +/// number of the type of the element if the `src` data type cannot be +/// represented as the `dst` type. Source and destination data length is `width` +/// * `height`. Number of elements is limited to @ref +/// INTRINSICCV_MAX_IMAGE_PIXELS. +/// +/// @param src Pointer to the source data. Must be non-null. +/// @param src_stride Distance in bytes from the start of one row to the +/// start of the next row for the source data. Must +/// not be less than width * sizeof(type). +/// Must be a multiple of sizeof(type). +/// @param dst Pointer to the destination data. Must be non-null. +/// @param dst_stride Distance in bytes from the start of one row to the +/// start of the next row for the destination data. Must +/// not be less than width * sizeof(type). +/// Must be a multiple of sizeof(type). +/// @param width Number of pixels in a row. +/// @param height Number of rows in the data. +/// +INTRINSICCV_API_DECLARATION(intrinsiccv_float_conversion_s8_f32, + const int8_t *src, size_t src_stride, float *dst, + size_t dst_stride, size_t width, size_t height); +/// @copydoc intrinsiccv_float_conversion_s8_f32 +INTRINSICCV_API_DECLARATION(intrinsiccv_float_conversion_u8_f32, + const uint8_t *src, size_t src_stride, float *dst, + size_t dst_stride, size_t width, size_t height); + #ifdef __cplusplus } // extern "C" #endif // __cplusplus diff --git a/intrinsiccv/include/intrinsiccv/sve2.h b/intrinsiccv/include/intrinsiccv/sve2.h index cd326d0c79b906ed8e510b173e81fd0fd66ec5d2..df7b4f3e958dbe46d26d0ec54369f8c6133bab4f 100644 --- a/intrinsiccv/include/intrinsiccv/sve2.h +++ b/intrinsiccv/include/intrinsiccv/sve2.h @@ -491,7 +491,7 @@ class RemainingPathAdapter : public OperationBase { } }; // end of class RemainingPathAdapter -// Shorthand for applying a generic unrolled NEON operation. +// Shorthand for applying a generic unrolled SVE2 operation. template void apply_operation_by_rows(OperationType &operation, ArgTypes &&...args) INTRINSICCV_STREAMING_COMPATIBLE { diff --git a/intrinsiccv/src/analysis/min_max_api.cpp b/intrinsiccv/src/analysis/min_max_api.cpp index 093380d7b1c17f8f2f3b4bdf7b1dfbded4868f79..513fe491621dd99275e8aaa1f332205685bc05f0 100644 --- a/intrinsiccv/src/analysis/min_max_api.cpp +++ b/intrinsiccv/src/analysis/min_max_api.cpp @@ -27,8 +27,8 @@ namespace sme2 {} // namespace sme2 } // namespace intrinsiccv -#define INTRINSICCV_DEFINE_MINMAX_API(name, type) \ - INTRINSICCV_MULTIVERSION_C_API(name, intrinsiccv::neon::min_max, \ +#define INTRINSICCV_DEFINE_MINMAX_API(name, type) \ + INTRINSICCV_MULTIVERSION_C_API(name, &intrinsiccv::neon::min_max, \ nullptr, nullptr) INTRINSICCV_DEFINE_MINMAX_API(intrinsiccv_min_max_u8, uint8_t); @@ -37,8 +37,8 @@ INTRINSICCV_DEFINE_MINMAX_API(intrinsiccv_min_max_u16, uint16_t); INTRINSICCV_DEFINE_MINMAX_API(intrinsiccv_min_max_s16, int16_t); INTRINSICCV_DEFINE_MINMAX_API(intrinsiccv_min_max_s32, int32_t); -#define INTRINSICCV_DEFINE_MINMAXLOC_API(name, type) \ - INTRINSICCV_MULTIVERSION_C_API(name, intrinsiccv::neon::min_max_loc, \ +#define INTRINSICCV_DEFINE_MINMAXLOC_API(name, type) \ + INTRINSICCV_MULTIVERSION_C_API(name, &intrinsiccv::neon::min_max_loc, \ nullptr, nullptr) INTRINSICCV_DEFINE_MINMAXLOC_API(intrinsiccv_min_max_loc_u8, uint8_t); diff --git a/intrinsiccv/src/arithmetics/absdiff_api.cpp b/intrinsiccv/src/arithmetics/absdiff_api.cpp index f8e94809fbcf2f65b5f30427bc3834389cb6cd31..86fa56fc49cd434ca8eec378f078e465e331afe1 100644 --- a/intrinsiccv/src/arithmetics/absdiff_api.cpp +++ b/intrinsiccv/src/arithmetics/absdiff_api.cpp @@ -39,11 +39,11 @@ intrinsiccv_error_t saturating_absdiff(const T *src_a, size_t src_a_stride, } // namespace intrinsiccv -#define INTRINSICCV_DEFINE_C_API(name, type) \ - INTRINSICCV_MULTIVERSION_C_API( \ - name, intrinsiccv::neon::saturating_absdiff, \ - INTRINSICCV_SVE2_IMPL_IF(intrinsiccv::sve2::saturating_absdiff), \ - intrinsiccv::sme2::saturating_absdiff) +#define INTRINSICCV_DEFINE_C_API(name, type) \ + INTRINSICCV_MULTIVERSION_C_API( \ + name, &intrinsiccv::neon::saturating_absdiff, \ + INTRINSICCV_SVE2_IMPL_IF(&intrinsiccv::sve2::saturating_absdiff), \ + &intrinsiccv::sme2::saturating_absdiff) INTRINSICCV_DEFINE_C_API(intrinsiccv_saturating_absdiff_u8, uint8_t); INTRINSICCV_DEFINE_C_API(intrinsiccv_saturating_absdiff_s8, int8_t); diff --git a/intrinsiccv/src/arithmetics/add_abs_with_threshold_api.cpp b/intrinsiccv/src/arithmetics/add_abs_with_threshold_api.cpp index 1a60b79560518b31618bb8af753913d1cd30a7a4..d9bc53e777a5342bd6799cf5bf1c2c61e314b218 100644 --- a/intrinsiccv/src/arithmetics/add_abs_with_threshold_api.cpp +++ b/intrinsiccv/src/arithmetics/add_abs_with_threshold_api.cpp @@ -34,12 +34,12 @@ intrinsiccv_error_t saturating_add_abs_with_threshold( } // namespace intrinsiccv -#define INTRINSICCV_DEFINE_C_API(name, type) \ - INTRINSICCV_MULTIVERSION_C_API( \ - name, intrinsiccv::neon::saturating_add_abs_with_threshold, \ - INTRINSICCV_SVE2_IMPL_IF( \ - intrinsiccv::sve2::saturating_add_abs_with_threshold), \ - intrinsiccv::sme2::saturating_add_abs_with_threshold) +#define INTRINSICCV_DEFINE_C_API(name, type) \ + INTRINSICCV_MULTIVERSION_C_API( \ + name, &intrinsiccv::neon::saturating_add_abs_with_threshold, \ + INTRINSICCV_SVE2_IMPL_IF( \ + &intrinsiccv::sve2::saturating_add_abs_with_threshold), \ + &intrinsiccv::sme2::saturating_add_abs_with_threshold) INTRINSICCV_DEFINE_C_API(intrinsiccv_saturating_add_abs_with_threshold_s16, int16_t); diff --git a/intrinsiccv/src/arithmetics/add_api.cpp b/intrinsiccv/src/arithmetics/add_api.cpp index adf06cd046860d580f6350cca51f284bbde3495c..355e065b03d2f4f7feb0b1c8d5961b876c62510e 100644 --- a/intrinsiccv/src/arithmetics/add_api.cpp +++ b/intrinsiccv/src/arithmetics/add_api.cpp @@ -39,11 +39,11 @@ intrinsiccv_error_t saturating_add(const T *src_a, size_t src_a_stride, } // namespace intrinsiccv -#define INTRINSICCV_DEFINE_C_API(name, type) \ - INTRINSICCV_MULTIVERSION_C_API( \ - name, intrinsiccv::neon::saturating_add, \ - INTRINSICCV_SVE2_IMPL_IF(intrinsiccv::sve2::saturating_add), \ - intrinsiccv::sme2::saturating_add) +#define INTRINSICCV_DEFINE_C_API(name, type) \ + INTRINSICCV_MULTIVERSION_C_API( \ + name, &intrinsiccv::neon::saturating_add, \ + INTRINSICCV_SVE2_IMPL_IF(&intrinsiccv::sve2::saturating_add), \ + &intrinsiccv::sme2::saturating_add) INTRINSICCV_DEFINE_C_API(intrinsiccv_saturating_add_s8, int8_t); INTRINSICCV_DEFINE_C_API(intrinsiccv_saturating_add_u8, uint8_t); diff --git a/intrinsiccv/src/arithmetics/multiply_api.cpp b/intrinsiccv/src/arithmetics/multiply_api.cpp index 92ea6da21622c43bd5545f9a6a6191db01796663..d68b18afb331b9e84eda3ace2211af93896a5a11 100644 --- a/intrinsiccv/src/arithmetics/multiply_api.cpp +++ b/intrinsiccv/src/arithmetics/multiply_api.cpp @@ -40,10 +40,10 @@ intrinsiccv_error_t saturating_multiply(const T *src_a, size_t src_a_stride, } // namespace intrinsiccv -#define INTRINSICCV_DEFINE_C_API(name, type) \ - INTRINSICCV_MULTIVERSION_C_API( \ - name, intrinsiccv::neon::saturating_multiply, \ - INTRINSICCV_SVE2_IMPL_IF(intrinsiccv::sve2::saturating_multiply), \ +#define INTRINSICCV_DEFINE_C_API(name, type) \ + INTRINSICCV_MULTIVERSION_C_API( \ + name, &intrinsiccv::neon::saturating_multiply, \ + INTRINSICCV_SVE2_IMPL_IF(&intrinsiccv::sve2::saturating_multiply), \ nullptr) INTRINSICCV_DEFINE_C_API(intrinsiccv_saturating_multiply_u8, uint8_t); diff --git a/intrinsiccv/src/arithmetics/scale_api.cpp b/intrinsiccv/src/arithmetics/scale_api.cpp index e3c1775b556ba9974210661efee208dc43eb88b3..0af9b97be76a7f122304f070165a025de0f58289 100644 --- a/intrinsiccv/src/arithmetics/scale_api.cpp +++ b/intrinsiccv/src/arithmetics/scale_api.cpp @@ -22,8 +22,8 @@ namespace sme2 {} // namespace sme2 } // namespace intrinsiccv -#define INTRINSICCV_DEFINE_SCALE_API(name, type) \ - INTRINSICCV_MULTIVERSION_C_API(name, intrinsiccv::neon::scale, \ +#define INTRINSICCV_DEFINE_SCALE_API(name, type) \ + INTRINSICCV_MULTIVERSION_C_API(name, &intrinsiccv::neon::scale, \ nullptr, nullptr) INTRINSICCV_DEFINE_SCALE_API(intrinsiccv_scale_u8, uint8_t); diff --git a/intrinsiccv/src/arithmetics/sub_api.cpp b/intrinsiccv/src/arithmetics/sub_api.cpp index 691d6ca4b98ebb228c1fb3eb1b929c5608a19fc1..9b451636da18fd5793154e42b8ab96b38e62102e 100644 --- a/intrinsiccv/src/arithmetics/sub_api.cpp +++ b/intrinsiccv/src/arithmetics/sub_api.cpp @@ -38,11 +38,11 @@ intrinsiccv_error_t saturating_sub(const T *src_a, size_t src_a_stride, } // namespace intrinsiccv -#define INTRINSICCV_DEFINE_C_API(name, type) \ - INTRINSICCV_MULTIVERSION_C_API( \ - name, intrinsiccv::neon::saturating_sub, \ - INTRINSICCV_SVE2_IMPL_IF(intrinsiccv::sve2::saturating_sub), \ - intrinsiccv::sme2::saturating_sub) +#define INTRINSICCV_DEFINE_C_API(name, type) \ + INTRINSICCV_MULTIVERSION_C_API( \ + name, &intrinsiccv::neon::saturating_sub, \ + INTRINSICCV_SVE2_IMPL_IF(&intrinsiccv::sve2::saturating_sub), \ + &intrinsiccv::sme2::saturating_sub) INTRINSICCV_DEFINE_C_API(intrinsiccv_saturating_sub_s8, int8_t); INTRINSICCV_DEFINE_C_API(intrinsiccv_saturating_sub_u8, uint8_t); diff --git a/intrinsiccv/src/arithmetics/threshold_api.cpp b/intrinsiccv/src/arithmetics/threshold_api.cpp index 8338f3a725253d89b40a7ed5a87d127f7cd52a15..f7e70fa683e2bdf2d2d91ba09c0d5d54049bdca5 100644 --- a/intrinsiccv/src/arithmetics/threshold_api.cpp +++ b/intrinsiccv/src/arithmetics/threshold_api.cpp @@ -30,10 +30,10 @@ intrinsiccv_error_t threshold_binary(const T *src, size_t src_stride, T *dst, } // namespace intrinsiccv -#define INTRINSICCV_DEFINE_C_API(name, type) \ - INTRINSICCV_MULTIVERSION_C_API( \ - name, intrinsiccv::neon::threshold_binary, \ - INTRINSICCV_SVE2_IMPL_IF(intrinsiccv::sve2::threshold_binary), \ - intrinsiccv::sme2::threshold_binary) +#define INTRINSICCV_DEFINE_C_API(name, type) \ + INTRINSICCV_MULTIVERSION_C_API( \ + name, &intrinsiccv::neon::threshold_binary, \ + INTRINSICCV_SVE2_IMPL_IF(&intrinsiccv::sve2::threshold_binary), \ + &intrinsiccv::sme2::threshold_binary) INTRINSICCV_DEFINE_C_API(intrinsiccv_threshold_binary_u8, uint8_t); diff --git a/intrinsiccv/src/arithmetics/transpose_api.cpp b/intrinsiccv/src/arithmetics/transpose_api.cpp index 86f7d3ceda99f29bca09609f000b2cceec7aea05..7fab646630dafa63e1bd9ac6799bed5d9f215fd0 100644 --- a/intrinsiccv/src/arithmetics/transpose_api.cpp +++ b/intrinsiccv/src/arithmetics/transpose_api.cpp @@ -7,4 +7,4 @@ #include "intrinsiccv/intrinsiccv.h" INTRINSICCV_MULTIVERSION_C_API(intrinsiccv_transpose, - intrinsiccv::neon::transpose, nullptr, nullptr); + &intrinsiccv::neon::transpose, nullptr, nullptr); diff --git a/intrinsiccv/src/conversions/float_conv_api.cpp b/intrinsiccv/src/conversions/float_conv_api.cpp new file mode 100644 index 0000000000000000000000000000000000000000..165fa72c32fbc3a3420c7486c652b0cf7543e7ed --- /dev/null +++ b/intrinsiccv/src/conversions/float_conv_api.cpp @@ -0,0 +1,71 @@ +// SPDX-FileCopyrightText: 2024 Arm Limited and/or its affiliates +// +// SPDX-License-Identifier: Apache-2.0 + +#include "intrinsiccv/dispatch.h" +#include "intrinsiccv/intrinsiccv.h" +#include "intrinsiccv/types.h" + +namespace intrinsiccv { + +namespace neon { + +template +intrinsiccv_error_t float_conversion(const InputType* src, size_t src_stride, + OutputType* dst, size_t dst_stride, + size_t width, size_t height); + +} // namespace neon + +namespace sve2 { + +template +intrinsiccv_error_t float_conversion(const InputType* src, size_t src_stride, + OutputType* dst, size_t dst_stride, + size_t width, size_t height); + +} // namespace sve2 + +namespace sme2 { + +template +intrinsiccv_error_t float_conversion(const InputType* src, size_t src_stride, + OutputType* dst, size_t dst_stride, + size_t width, size_t height); + +} // namespace sme2 + +#ifdef INTRINSICCV_HAVE_SVE2 +#define SVE2_FUNC_POINTER(name, itype, otype) \ + [[maybe_unused]] static auto sve2_func_##itype##_##otype = \ + intrinsiccv::sve2::float_conversion; +#else +#define SVE2_FUNC_POINTER(name, itype, otype) +#endif // INTRINSICCV_HAVE_SVE2 + +#ifdef INTRINSICCV_HAVE_SME2 +#define SME2_FUNC_POINTER(name, itype, otype) \ + static auto sme2_func_##itype##_##otype = \ + intrinsiccv::sme2::float_conversion; +#else +#define SME2_FUNC_POINTER(name, itype, otype) +#endif // INTRINSICCV_HAVE_SME2 + +// NOLINTBEGIN(cppcoreguidelines-avoid-non-const-global-variables) +#define INTRINSICCV_DEFINE_C_API(name, itype, otype) \ + static auto neon_func_##itype##_##otype = \ + intrinsiccv::neon::float_conversion; \ + SVE2_FUNC_POINTER(name, itype, otype); \ + SME2_FUNC_POINTER(name, itype, otype); \ + INTRINSICCV_MULTIVERSION_C_API( \ + name, neon_func_##itype##_##otype, \ + INTRINSICCV_SVE2_IMPL_IF(sve2_func_##itype##_##otype), \ + sme2_func_##itype##_##otype) +// NOLINTEND(cppcoreguidelines-avoid-non-const-global-variables) + +INTRINSICCV_DEFINE_C_API(intrinsiccv_float_conversion_f32_s8, float, int8_t); +INTRINSICCV_DEFINE_C_API(intrinsiccv_float_conversion_f32_u8, float, uint8_t); +INTRINSICCV_DEFINE_C_API(intrinsiccv_float_conversion_s8_f32, int8_t, float); +INTRINSICCV_DEFINE_C_API(intrinsiccv_float_conversion_u8_f32, uint8_t, float); + +} // namespace intrinsiccv diff --git a/intrinsiccv/src/conversions/float_conv_neon.cpp b/intrinsiccv/src/conversions/float_conv_neon.cpp new file mode 100644 index 0000000000000000000000000000000000000000..d500d83341db212a1d0cff55515e92cafc97c695 --- /dev/null +++ b/intrinsiccv/src/conversions/float_conv_neon.cpp @@ -0,0 +1,27 @@ +// SPDX-FileCopyrightText: 2024 Arm Limited and/or its affiliates +// +// SPDX-License-Identifier: Apache-2.0 + +#include "intrinsiccv/intrinsiccv.h" +#include "intrinsiccv/neon.h" + +namespace intrinsiccv::neon { + +template +intrinsiccv_error_t float_conversion(const InputType*, size_t, OutputType*, + size_t, size_t, size_t) { + return INTRINSICCV_ERROR_NOT_IMPLEMENTED; +} + +#define INTRINSICCV_INSTANTIATE_TEMPLATE(itype, otype) \ + template INTRINSICCV_TARGET_FN_ATTRS intrinsiccv_error_t \ + float_conversion(const itype* src, size_t src_stride, \ + otype* dst, size_t dst_stride, size_t width, \ + size_t height) + +INTRINSICCV_INSTANTIATE_TEMPLATE(float, int8_t); +INTRINSICCV_INSTANTIATE_TEMPLATE(float, uint8_t); +INTRINSICCV_INSTANTIATE_TEMPLATE(int8_t, float); +INTRINSICCV_INSTANTIATE_TEMPLATE(uint8_t, float); + +} // namespace intrinsiccv::neon diff --git a/intrinsiccv/src/conversions/float_conv_sc.h b/intrinsiccv/src/conversions/float_conv_sc.h new file mode 100644 index 0000000000000000000000000000000000000000..b2190b0a20d3d3057813d46424f168f3a4205710 --- /dev/null +++ b/intrinsiccv/src/conversions/float_conv_sc.h @@ -0,0 +1,166 @@ +// SPDX-FileCopyrightText: 2024 Arm Limited and/or its affiliates +// +// SPDX-License-Identifier: Apache-2.0 + +#ifndef INTRINSICCV_FLOAT_CONV_SC_H +#define INTRINSICCV_FLOAT_CONV_SC_H + +#include +#include + +#include "intrinsiccv/intrinsiccv.h" +#include "intrinsiccv/sve2.h" + +namespace INTRINSICCV_TARGET_NAMESPACE { + +template +class float_conversion_operation; + +template +class float_conversion_operation { + public: + using SrcVecTraits = INTRINSICCV_TARGET_NAMESPACE::VecTraits; + using SrcVectorType = typename SrcVecTraits::VectorType; + using IntermediateVecTraits = INTRINSICCV_TARGET_NAMESPACE::VecTraits< + std::conditional_t, int32_t, uint32_t>>; + using IntermediateVectorType = typename IntermediateVecTraits::VectorType; + + void process_row(size_t width, Columns src, + Columns dst) INTRINSICCV_STREAMING_COMPATIBLE { + LoopUnroll{width, SrcVecTraits::num_lanes()} + .unroll_twice([&](size_t step) INTRINSICCV_STREAMING_COMPATIBLE { + svbool_t pg = SrcVecTraits::svptrue(); + SrcVectorType src_vector1 = svld1(pg, &src[0]); + SrcVectorType src_vector2 = svld1_vnum(pg, &src[0], 1); + IntermediateVectorType result_vector1 = + vector_path(pg, src_vector1); + IntermediateVectorType result_vector2 = + vector_path(pg, src_vector2); + svst1b(pg, &dst[0], result_vector1); + svst1b_vnum(pg, &dst[0], 1, result_vector2); + src += ptrdiff_t(step); + dst += ptrdiff_t(step); + }) + .remaining([&](size_t length, size_t) INTRINSICCV_STREAMING_COMPATIBLE { + size_t index = 0; + svbool_t pg = SrcVecTraits::svwhilelt(index, length); + while (svptest_first(SrcVecTraits::svptrue(), pg)) { + SrcVectorType src_vector = svld1(pg, &src[ptrdiff_t(index)]); + IntermediateVectorType result_vector = + vector_path(pg, src_vector); + svst1b(pg, &dst[ptrdiff_t(index)], result_vector); + // Update loop counter and calculate the next governing predicate. + index += SrcVecTraits::num_lanes(); + pg = SrcVecTraits::svwhilelt(index, length); + } + }); + } + + private: + template < + typename O, + std::enable_if_t && std::is_signed_v, int> = 0> + IntermediateVectorType vector_path(svbool_t& pg, SrcVectorType src) + INTRINSICCV_STREAMING_COMPATIBLE { + constexpr float min_val = std::numeric_limits::min(); + constexpr float max_val = std::numeric_limits::max(); + + src = svrinti_f32_x(pg, src); + + svbool_t less = svcmplt_n_f32(pg, src, min_val); + src = svdup_n_f32_m(src, less, min_val); + + svbool_t greater = svcmpgt_n_f32(pg, src, max_val); + src = svdup_n_f32_m(src, greater, max_val); + + return svcvt_s32_f32_x(pg, src); + } + + template < + typename O, + std::enable_if_t && !std::is_signed_v, int> = 0> + IntermediateVectorType vector_path(svbool_t& pg, SrcVectorType src) + INTRINSICCV_STREAMING_COMPATIBLE { + constexpr float max_val = std::numeric_limits::max(); + + src = svrinti_f32_x(pg, src); + + svbool_t greater = svcmpgt_n_f32(pg, src, max_val); + src = svdup_n_f32_m(src, greater, max_val); + + return svcvt_u32_f32_x(pg, src); + } +}; // end of class float_conversion_operation + +template +class float_conversion_operation { + public: + using VecTraits = INTRINSICCV_TARGET_NAMESPACE::VecTraits; + using VectorType = typename VecTraits::VectorType; + void process_row(size_t width, Columns src, + Columns dst) { + LoopUnroll{width, VecTraits::num_lanes()} + .unroll_twice([&](size_t step) INTRINSICCV_STREAMING_COMPATIBLE { + svbool_t pg = VecTraits::svptrue(); + VectorType dst_vector1 = vector_path(pg, &src[0]); + VectorType dst_vector2 = vector_path( + pg, &src.at(ptrdiff_t(VecTraits::num_lanes()))[0]); + svst1(pg, &dst[0], dst_vector1); + svst1_vnum(pg, &dst[0], 1, dst_vector2); + src += ptrdiff_t(step); + dst += ptrdiff_t(step); + }) + .remaining([&](size_t length, size_t) INTRINSICCV_STREAMING_COMPATIBLE { + size_t index = 0; + svbool_t pg = VecTraits::svwhilelt(index, length); + while (svptest_first(VecTraits::svptrue(), pg)) { + VectorType dst_vector = + vector_path(pg, &src[ptrdiff_t(index)]); + svst1(pg, &dst[ptrdiff_t(index)], dst_vector); + // Update loop counter and calculate the next governing predicate. + index += VecTraits::num_lanes(); + pg = VecTraits::svwhilelt(index, length); + } + }); + } + + private: + template < + typename I, + std::enable_if_t && std::is_signed_v, int> = 0> + VectorType vector_path(svbool_t& pg, + const I* src) INTRINSICCV_STREAMING_COMPATIBLE { + svint32_t src_vector = svld1sb_s32(pg, src); + return svcvt_f32_s32_x(pg, src_vector); + } + + template < + typename I, + std::enable_if_t && !std::is_signed_v, int> = 0> + VectorType vector_path(svbool_t& pg, + const I* src) INTRINSICCV_STREAMING_COMPATIBLE { + svuint32_t src_vector = svld1ub_u32(pg, src); + return svcvt_f32_u32_x(pg, src_vector); + } +}; + +template +static intrinsiccv_error_t float_conversion_sc( + const I* src, size_t src_stride, O* dst, size_t dst_stride, size_t width, + size_t height) INTRINSICCV_STREAMING_COMPATIBLE { + CHECK_POINTER_AND_STRIDE(src, src_stride); + CHECK_POINTER_AND_STRIDE(dst, dst_stride); + CHECK_IMAGE_SIZE(width, height); + + float_conversion_operation operation; + Rectangle rect{width, height}; + Rows src_rows{src, src_stride}; + Rows dst_rows{dst, dst_stride}; + zip_rows(operation, rect, src_rows, dst_rows); + + return INTRINSICCV_OK; +} + +} // namespace INTRINSICCV_TARGET_NAMESPACE + +#endif // INTRINSICCV_FLOAT_CONV_SC_H diff --git a/intrinsiccv/src/conversions/float_conv_sme2.cpp b/intrinsiccv/src/conversions/float_conv_sme2.cpp new file mode 100644 index 0000000000000000000000000000000000000000..c44fd8d5cd908c14df6f84ea9e860df398d271ba --- /dev/null +++ b/intrinsiccv/src/conversions/float_conv_sme2.cpp @@ -0,0 +1,28 @@ +// SPDX-FileCopyrightText: 2024 Arm Limited and/or its affiliates +// +// SPDX-License-Identifier: Apache-2.0 + +#include "float_conv_sc.h" + +namespace intrinsiccv::sme2 { + +template +INTRINSICCV_LOCALLY_STREAMING INTRINSICCV_TARGET_FN_ATTRS intrinsiccv_error_t +float_conversion(const InputType* src, size_t src_stride, OutputType* dst, + size_t dst_stride, size_t width, size_t height) { + return float_conversion_sc(src, src_stride, dst, + dst_stride, width, height); +} + +#define INTRINSICCV_INSTANTIATE_TEMPLATE(itype, otype) \ + template INTRINSICCV_TARGET_FN_ATTRS intrinsiccv_error_t \ + float_conversion(const itype* src, size_t src_stride, \ + otype* dst, size_t dst_stride, size_t width, \ + size_t height) + +INTRINSICCV_INSTANTIATE_TEMPLATE(float, int8_t); +INTRINSICCV_INSTANTIATE_TEMPLATE(float, uint8_t); +INTRINSICCV_INSTANTIATE_TEMPLATE(int8_t, float); +INTRINSICCV_INSTANTIATE_TEMPLATE(uint8_t, float); + +} // namespace intrinsiccv::sme2 diff --git a/intrinsiccv/src/conversions/float_conv_sve2.cpp b/intrinsiccv/src/conversions/float_conv_sve2.cpp new file mode 100644 index 0000000000000000000000000000000000000000..7005f18a5ffda5157a27c6bd7d6f4e7dc0e74bf2 --- /dev/null +++ b/intrinsiccv/src/conversions/float_conv_sve2.cpp @@ -0,0 +1,28 @@ +// SPDX-FileCopyrightText: 2024 Arm Limited and/or its affiliates +// +// SPDX-License-Identifier: Apache-2.0 + +#include "float_conv_sc.h" + +namespace intrinsiccv::sve2 { + +template +INTRINSICCV_TARGET_FN_ATTRS intrinsiccv_error_t +float_conversion(const InputType* src, size_t src_stride, OutputType* dst, + size_t dst_stride, size_t width, size_t height) { + return float_conversion_sc(src, src_stride, dst, + dst_stride, width, height); +} + +#define INTRINSICCV_INSTANTIATE_TEMPLATE(itype, otype) \ + template INTRINSICCV_TARGET_FN_ATTRS intrinsiccv_error_t \ + float_conversion(const itype* src, size_t src_stride, \ + otype* dst, size_t dst_stride, size_t width, \ + size_t height) + +INTRINSICCV_INSTANTIATE_TEMPLATE(float, int8_t); +INTRINSICCV_INSTANTIATE_TEMPLATE(float, uint8_t); +INTRINSICCV_INSTANTIATE_TEMPLATE(int8_t, float); +INTRINSICCV_INSTANTIATE_TEMPLATE(uint8_t, float); + +} // namespace intrinsiccv::sve2 diff --git a/intrinsiccv/src/conversions/gray_to_rgb_api.cpp b/intrinsiccv/src/conversions/gray_to_rgb_api.cpp index ce33a9e2d486512475716862aa353fdb0443cd70..14e49476b33727924f28be7cb30a7a7f54e749f9 100644 --- a/intrinsiccv/src/conversions/gray_to_rgb_api.cpp +++ b/intrinsiccv/src/conversions/gray_to_rgb_api.cpp @@ -6,11 +6,11 @@ #include "intrinsiccv/dispatch.h" #include "intrinsiccv/intrinsiccv.h" -#define INTRINSICCV_DEFINE_C_API(name, partialname) \ - INTRINSICCV_MULTIVERSION_C_API( \ - name, intrinsiccv::neon::partialname, \ - INTRINSICCV_SVE2_IMPL_IF(intrinsiccv::sve2::partialname), \ - intrinsiccv::sme2::partialname) +#define INTRINSICCV_DEFINE_C_API(name, partialname) \ + INTRINSICCV_MULTIVERSION_C_API( \ + name, &intrinsiccv::neon::partialname, \ + INTRINSICCV_SVE2_IMPL_IF(&intrinsiccv::sve2::partialname), \ + &intrinsiccv::sme2::partialname) INTRINSICCV_DEFINE_C_API(intrinsiccv_gray_to_rgb_u8, gray_to_rgb_u8); INTRINSICCV_DEFINE_C_API(intrinsiccv_gray_to_rgba_u8, gray_to_rgba_u8); diff --git a/intrinsiccv/src/conversions/merge_api.cpp b/intrinsiccv/src/conversions/merge_api.cpp index ec47a818bfb003112bd71435f1d030e9a41b6a35..d84ce62c97c8aaeb45daa853b8bd8a118e5fa3ff 100644 --- a/intrinsiccv/src/conversions/merge_api.cpp +++ b/intrinsiccv/src/conversions/merge_api.cpp @@ -6,5 +6,5 @@ #include "intrinsiccv/dispatch.h" #include "intrinsiccv/intrinsiccv.h" -INTRINSICCV_MULTIVERSION_C_API(intrinsiccv_merge, intrinsiccv::neon::merge, +INTRINSICCV_MULTIVERSION_C_API(intrinsiccv_merge, &intrinsiccv::neon::merge, nullptr, nullptr); diff --git a/intrinsiccv/src/conversions/rgb_to_rgb_api.cpp b/intrinsiccv/src/conversions/rgb_to_rgb_api.cpp index 4563975a0e10a817b005ed3d1b8f5882d508d907..434ea29b3377c01e2dcd53531ea9b4e414696226 100644 --- a/intrinsiccv/src/conversions/rgb_to_rgb_api.cpp +++ b/intrinsiccv/src/conversions/rgb_to_rgb_api.cpp @@ -7,11 +7,11 @@ #include "intrinsiccv/intrinsiccv.h" #include "intrinsiccv/types.h" -#define INTRINSICCV_DEFINE_C_API(name, partialname) \ - INTRINSICCV_MULTIVERSION_C_API( \ - name, intrinsiccv::neon::partialname, \ - INTRINSICCV_SVE2_IMPL_IF(intrinsiccv::sve2::partialname), \ - intrinsiccv::sme2::partialname) +#define INTRINSICCV_DEFINE_C_API(name, partialname) \ + INTRINSICCV_MULTIVERSION_C_API( \ + name, &intrinsiccv::neon::partialname, \ + INTRINSICCV_SVE2_IMPL_IF(&intrinsiccv::sve2::partialname), \ + &intrinsiccv::sme2::partialname) INTRINSICCV_DEFINE_C_API(intrinsiccv_rgb_to_bgr_u8, rgb_to_bgr_u8); INTRINSICCV_DEFINE_C_API(intrinsiccv_rgba_to_bgra_u8, rgba_to_bgra_u8); diff --git a/intrinsiccv/src/conversions/split_api.cpp b/intrinsiccv/src/conversions/split_api.cpp index 2794da8eaafcddddcf69d1b0fcb7c3da50cacffd..80286c9eb1da018a4c35d8c242093e12220bf826 100644 --- a/intrinsiccv/src/conversions/split_api.cpp +++ b/intrinsiccv/src/conversions/split_api.cpp @@ -6,5 +6,5 @@ #include "intrinsiccv/dispatch.h" #include "intrinsiccv/intrinsiccv.h" -INTRINSICCV_MULTIVERSION_C_API(intrinsiccv_split, intrinsiccv::neon::split, +INTRINSICCV_MULTIVERSION_C_API(intrinsiccv_split, &intrinsiccv::neon::split, nullptr, nullptr); diff --git a/intrinsiccv/src/conversions/yuv_to_rgb_api.cpp b/intrinsiccv/src/conversions/yuv_to_rgb_api.cpp index 24499bce5bd8e602049c494091618defe812a29f..4f051230f7b152adb29797bde4ed3be19022d918 100644 --- a/intrinsiccv/src/conversions/yuv_to_rgb_api.cpp +++ b/intrinsiccv/src/conversions/yuv_to_rgb_api.cpp @@ -6,10 +6,10 @@ #include "intrinsiccv/dispatch.h" #include "intrinsiccv/intrinsiccv.h" -#define INTRINSICCV_DEFINE_C_API(name, partialname) \ - INTRINSICCV_MULTIVERSION_C_API(name, intrinsiccv::neon::partialname, \ - intrinsiccv::sve2::partialname, \ - intrinsiccv::sme2::partialname) +#define INTRINSICCV_DEFINE_C_API(name, partialname) \ + INTRINSICCV_MULTIVERSION_C_API(name, &intrinsiccv::neon::partialname, \ + &intrinsiccv::sve2::partialname, \ + &intrinsiccv::sme2::partialname) INTRINSICCV_DEFINE_C_API(intrinsiccv_yuv_sp_to_rgb_u8, yuv_sp_to_rgb_u8); INTRINSICCV_DEFINE_C_API(intrinsiccv_yuv_sp_to_bgr_u8, yuv_sp_to_bgr_u8); diff --git a/intrinsiccv/src/filters/gaussian_blur_api.cpp b/intrinsiccv/src/filters/gaussian_blur_api.cpp index 29abc958568954897822d6f9118c5ee2b77682c1..39982b6a8236630c0693144255f75bb17307d3a3 100644 --- a/intrinsiccv/src/filters/gaussian_blur_api.cpp +++ b/intrinsiccv/src/filters/gaussian_blur_api.cpp @@ -54,10 +54,10 @@ intrinsiccv_error_t intrinsiccv_filter_release( } // extern "C" INTRINSICCV_MULTIVERSION_C_API(intrinsiccv_gaussian_blur_3x3_u8, - intrinsiccv::neon::gaussian_blur_3x3_u8, nullptr, - nullptr); + &intrinsiccv::neon::gaussian_blur_3x3_u8, + nullptr, nullptr); INTRINSICCV_MULTIVERSION_C_API( - intrinsiccv_gaussian_blur_5x5_u8, intrinsiccv::neon::gaussian_blur_5x5_u8, + intrinsiccv_gaussian_blur_5x5_u8, &intrinsiccv::neon::gaussian_blur_5x5_u8, INTRINSICCV_SVE2_IMPL_IF(intrinsiccv::sve2::gaussian_blur_5x5_u8), - intrinsiccv::sme2::gaussian_blur_5x5_u8); + &intrinsiccv::sme2::gaussian_blur_5x5_u8); diff --git a/intrinsiccv/src/filters/sobel_api.cpp b/intrinsiccv/src/filters/sobel_api.cpp index 7154dfdba6d9d695cef223c171edd0320dde0a26..b7663ca0fbd097ef674685cb535c210703185c14 100644 --- a/intrinsiccv/src/filters/sobel_api.cpp +++ b/intrinsiccv/src/filters/sobel_api.cpp @@ -6,11 +6,11 @@ #include "intrinsiccv/filters/sobel.h" #include "intrinsiccv/intrinsiccv.h" -#define INTRINSICCV_DEFINE_C_API(name, partialname) \ - INTRINSICCV_MULTIVERSION_C_API( \ - name, intrinsiccv::neon::partialname, \ - INTRINSICCV_SVE2_IMPL_IF(intrinsiccv::sve2::partialname), \ - intrinsiccv::sme2::partialname) +#define INTRINSICCV_DEFINE_C_API(name, partialname) \ + INTRINSICCV_MULTIVERSION_C_API( \ + name, &intrinsiccv::neon::partialname, \ + INTRINSICCV_SVE2_IMPL_IF(&intrinsiccv::sve2::partialname), \ + &intrinsiccv::sme2::partialname) INTRINSICCV_DEFINE_C_API(intrinsiccv_sobel_3x3_horizontal_s16_u8, sobel_3x3_horizontal_s16_u8); diff --git a/intrinsiccv/src/morphology/morphology_api.cpp b/intrinsiccv/src/morphology/morphology_api.cpp index 6c53f01d8bb0682f57387e12f5dd69e69b70d73e..5fc168e8cc60e353e3d9d50ada071ae453ea1cb6 100644 --- a/intrinsiccv/src/morphology/morphology_api.cpp +++ b/intrinsiccv/src/morphology/morphology_api.cpp @@ -108,11 +108,11 @@ intrinsiccv_error_t intrinsiccv_morphology_release( } // extern "C" -#define INTRINSICCV_DEFINE_C_API(name, tname, type) \ - INTRINSICCV_MULTIVERSION_C_API( \ - name, intrinsiccv::neon::tname, \ - INTRINSICCV_SVE2_IMPL_IF(intrinsiccv::sve2::tname), \ - intrinsiccv::sme2::tname) +#define INTRINSICCV_DEFINE_C_API(name, tname, type) \ + INTRINSICCV_MULTIVERSION_C_API( \ + name, &intrinsiccv::neon::tname, \ + INTRINSICCV_SVE2_IMPL_IF(&intrinsiccv::sve2::tname), \ + &intrinsiccv::sme2::tname) INTRINSICCV_DEFINE_C_API(intrinsiccv_dilate_u8, dilate, uint8_t); INTRINSICCV_DEFINE_C_API(intrinsiccv_erode_u8, erode, uint8_t); diff --git a/intrinsiccv/src/resize/resize_api.cpp b/intrinsiccv/src/resize/resize_api.cpp index c259626ea5212b474d3c1128747f62d8214dd332..00a8d9a44cb05366c7f96363f9b8ae8bb94769e3 100644 --- a/intrinsiccv/src/resize/resize_api.cpp +++ b/intrinsiccv/src/resize/resize_api.cpp @@ -7,6 +7,6 @@ #include "intrinsiccv/resize/resize.h" INTRINSICCV_MULTIVERSION_C_API( - intrinsiccv_resize_to_quarter_u8, intrinsiccv::neon::resize_to_quarter_u8, - INTRINSICCV_SVE2_IMPL_IF(intrinsiccv::sve2::resize_to_quarter_u8), - intrinsiccv::sme2::resize_to_quarter_u8); + intrinsiccv_resize_to_quarter_u8, &intrinsiccv::neon::resize_to_quarter_u8, + INTRINSICCV_SVE2_IMPL_IF(&intrinsiccv::sve2::resize_to_quarter_u8), + &intrinsiccv::sme2::resize_to_quarter_u8); diff --git a/intrinsiccv/src/resize/resize_linear_api.cpp b/intrinsiccv/src/resize/resize_linear_api.cpp index 17c87fb5961fb791032b2e01a295a9e5513141e3..d8c1ae8e6d5835f1bf64dc42745966091470b412 100644 --- a/intrinsiccv/src/resize/resize_linear_api.cpp +++ b/intrinsiccv/src/resize/resize_linear_api.cpp @@ -7,6 +7,6 @@ #include "intrinsiccv/resize/resize_linear.h" INTRINSICCV_MULTIVERSION_C_API( - intrinsiccv_resize_linear_u8, intrinsiccv::neon::resize_linear_u8, - INTRINSICCV_SVE2_IMPL_IF(intrinsiccv::sve2::resize_linear_u8), - intrinsiccv::sme2::resize_linear_u8); + intrinsiccv_resize_linear_u8, &intrinsiccv::neon::resize_linear_u8, + INTRINSICCV_SVE2_IMPL_IF(&intrinsiccv::sve2::resize_linear_u8), + &intrinsiccv::sme2::resize_linear_u8); diff --git a/scripts/ci.sh b/scripts/ci.sh index 0b956015791e4e2a1fcd49be0f5218ef39d19101..8555681ab9c3f74028ce09b24c6428afc5d4d771 100755 --- a/scripts/ci.sh +++ b/scripts/ci.sh @@ -51,16 +51,17 @@ ninja -C build/gcc # Run tests LONG_VECTOR_TESTS="GRAY2.*:RGB*" +EXCLUDE_FLOAT_CONVERSION_TESTS="-FloatConversion*" TESTRESULT=0 qemu-aarch64 build/test/framework/intrinsiccv-framework-test --gtest_output=xml:build/test-results/ || TESTRESULT=1 -qemu-aarch64 -cpu cortex-a35 build/test/api/intrinsiccv-api-test --gtest_output=xml:build/test-results/clang-neon/ || TESTRESULT=1 +qemu-aarch64 -cpu cortex-a35 build/test/api/intrinsiccv-api-test --gtest_filter="${EXCLUDE_FLOAT_CONVERSION_TESTS}" --gtest_output=xml:build/test-results/clang-neon/ || TESTRESULT=1 qemu-aarch64 -cpu max,sve128=on,sme=off \ build/test/api/intrinsiccv-api-test --gtest_output=xml:build/test-results/clang-sve128/ --vector-length=16 || TESTRESULT=1 qemu-aarch64 -cpu max,sve2048=on,sve-default-vector-length=256,sme=off \ build/test/api/intrinsiccv-api-test --gtest_filter="${LONG_VECTOR_TESTS}" --gtest_output=xml:build/test-results/clang-sve2048/ --vector-length=256 || TESTRESULT=1 qemu-aarch64 -cpu max,sve128=on,sme512=on \ build/test/api/intrinsiccv-api-test --gtest_output=xml:build/test-results/clang-sme/ --vector-length=64 || TESTRESULT=1 -qemu-aarch64 -cpu cortex-a35 build/gcc/test/api/intrinsiccv-api-test --gtest_output=xml:build/test-results/gcc-neon/ || TESTRESULT=1 +qemu-aarch64 -cpu cortex-a35 build/gcc/test/api/intrinsiccv-api-test --gtest_filter="${EXCLUDE_FLOAT_CONVERSION_TESTS}" --gtest_output=xml:build/test-results/gcc-neon/ || TESTRESULT=1 scripts/prefix_testsuite_names.py build/test-results/clang-neon/intrinsiccv-api-test.xml "clang-neon." scripts/prefix_testsuite_names.py build/test-results/clang-sve128/intrinsiccv-api-test.xml "clang-sve128." @@ -79,7 +80,7 @@ if [[ $(dpkg --print-architecture) = arm64 ]]; then -DINTRINSICCV_ENABLE_SME2=OFF \ -DCMAKE_CXX_FLAGS="-fsanitize=address,undefined -fno-sanitize-recover=all -Wno-pass-failed" ninja -C build/sanitize intrinsiccv-api-test - build/sanitize/test/api/intrinsiccv-api-test + build/sanitize/test/api/intrinsiccv-api-test --gtest_filter="${EXCLUDE_FLOAT_CONVERSION_TESTS}" fi # Build benchmarks, just to prevent bitrot. diff --git a/scripts/format.sh b/scripts/format.sh index d01f0d21c9a819b12c4c6c3d136e7a56d1e0ac1e..69953d462e021c0ff91ac5988e65812966b0b2f5 100755 --- a/scripts/format.sh +++ b/scripts/format.sh @@ -34,7 +34,7 @@ SOURCES="$(find \ "${INTRINSICCV_ROOT_PATH}/intrinsiccv" \ "${INTRINSICCV_ROOT_PATH}/test" \ "${INTRINSICCV_ROOT_PATH}/conformity/opencv" \ - \( -name \*.cpp -o -name \*.h \) \ + \( -name \*.cpp -o -name \*.h -o -name \*.h.in \) \ -print)" if [[ "${CHECK_ONLY}" == "ON" ]]; then diff --git a/test/api/test_float_conv.cpp b/test/api/test_float_conv.cpp new file mode 100644 index 0000000000000000000000000000000000000000..2f7a261374e6a8547f27453b4fa2901240c4e8da --- /dev/null +++ b/test/api/test_float_conv.cpp @@ -0,0 +1,476 @@ +// SPDX-FileCopyrightText: 2024 Arm Limited and/or its affiliates +// +// SPDX-License-Identifier: Apache-2.0 + +#include + +#include "framework/array.h" +#include "framework/generator.h" +#include "framework/operation.h" +#include "framework/utils.h" +#include "intrinsiccv/intrinsiccv.h" +#include "test_config.h" + +#define INTRINSICCV_float_conversion(I, input_type_name, O, output_type_name) \ + INTRINSICCV_DIFF_IO_API( \ + float_conversion, \ + intrinsiccv_float_conversion_##input_type_name##_##output_type_name, I, \ + O) + +INTRINSICCV_float_conversion(float, f32, int8_t, s8); +INTRINSICCV_float_conversion(float, f32, uint8_t, u8); +INTRINSICCV_float_conversion(int8_t, s8, float, f32); +INTRINSICCV_float_conversion(uint8_t, u8, float, f32); + +template +class FloatConversionTest final { + private: + template + static constexpr T min() { + return std::numeric_limits::min(); + } + + template + static constexpr T max() { + return std::numeric_limits::max(); + } + + struct Elements { + size_t width; + size_t height; + + std::vector> source_rows; + std::vector> expected_rows; + + Elements(size_t _width, size_t _height, + std::vector>&& _source_rows, + std::vector>&& _expected_rows) + : width(_width), + height(_height), + source_rows(std::move(_source_rows)), + expected_rows(std::move(_expected_rows)) {} + }; + + struct Values { + InputType source; + OutputType expected; + }; + + static constexpr uint32_t quietNaN = 0x7FC00000; + static constexpr uint32_t signalingNaN = 0x7FA00000; + static constexpr uint32_t posInfinity = 0x7F800000; + static constexpr uint32_t negInfinity = 0xFF800000; + + static constexpr uint32_t minusNaN = 0xFF800001; + static constexpr uint32_t plusNaN = 0x7F800001; + static constexpr uint32_t plusZero = 0x00000000; + static constexpr uint32_t minusZero = 0x80000000; + + static constexpr uint32_t oneNaN = 0x7FC00001; + static constexpr uint32_t zeroDivZero = 0xFFC00000; + static constexpr uint32_t floatMin = 0x00800000; + static constexpr uint32_t floatMax = 0x7F7FFFFF; + + static constexpr uint32_t posSubnormalMin = 0x00000001; + static constexpr uint32_t posSubnormalMax = 0x007FFFFF; + static constexpr uint32_t negSubnormalMin = 0x80000001; + static constexpr uint32_t negSubnormalMax = 0x807FFFFF; + + static constexpr float _floatval(uint32_t v) { + static_assert(sizeof(float) == 4); + INTRINSICCV_NO_STRICT_ALIASING_BEGIN + return *reinterpret_cast(&v); + INTRINSICCV_NO_STRICT_ALIASING_END + } + + template , bool> = true, + std::enable_if_t, bool> = true> + const Elements& get_custom_elements() { + static const Elements kTestElements = { + // clang-format off + 4, 8, + {{ + { _floatval(quietNaN), _floatval(signalingNaN), _floatval(posInfinity), _floatval(negInfinity) }, + { _floatval(minusNaN), _floatval(plusNaN), _floatval(plusZero), _floatval(minusZero) }, + { _floatval(oneNaN), _floatval(zeroDivZero), _floatval(floatMin), _floatval(floatMax) }, + { _floatval(posSubnormalMin), _floatval(posSubnormalMax), _floatval(negSubnormalMin), _floatval(negSubnormalMax) }, + { 1111.11, -1112.22, 113.33, 114.44 }, + { 111.51, 112.62, 113.73, 114.84 }, + { 126.66, 127.11, 128.66, 129.11 }, + { 11.5, 12.5, -11.5, -12.5 } + }}, + {{ + { 0, 0, 127, -128 }, + { 0, 0, 0, 0 }, + { 0, 0, 0, 127 }, + { 0, 0, 0, 0 }, + { 127, -128, 113, 114 }, + { 112, 113, 114, 115 }, + { 127, 127, 127, 127 }, + { 12, 12, -12, -12 } + }} + // clang-format on + }; + return kTestElements; + } + + template , bool> = true, + std::enable_if_t, bool> = true> + const Elements& get_custom_elements() { + static const Elements kTestElements = { + // clang-format off + 4, 8, + {{ + { _floatval(quietNaN), _floatval(signalingNaN), _floatval(posInfinity), _floatval(negInfinity) }, + { _floatval(minusNaN), _floatval(plusNaN), _floatval(plusZero), _floatval(minusZero) }, + { _floatval(oneNaN), _floatval(zeroDivZero), _floatval(floatMin), _floatval(floatMax) }, + { _floatval(posSubnormalMin), _floatval(posSubnormalMax), _floatval(negSubnormalMin), _floatval(negSubnormalMax) }, + { 1111.11, -1112.22, 113.33, 114.44 }, + { 111.51, 112.62, 113.73, 114.84 }, + { 126.66, 127.11, 128.66, 129.11 }, + { 11.5, 12.5, -11.5, -12.5 } + }}, + {{ + { 0, 0, 255, 0 }, + { 0, 0, 0, 0 }, + { 0, 0, 0, 255 }, + { 0, 0, 0, 0 }, + { 255, 0, 113, 114 }, + { 112, 113, 114, 115 }, + { 127, 127, 129, 129 }, + { 12, 12, 0, 0 } + }} + // clang-format on + }; + return kTestElements; + } + + template , bool> = true> + const Elements& get_custom_elements() { + static const Elements kTestElements = { + // clang-format off + 4, 6, + {{ + { min(), min(), max() - 1, max() }, + { min(), min(), min(), min() }, + { min(), min(), min(), max() - 1 }, + { max() - 1, max(), 113, 114 }, + { 112, 113, 114, 115 }, + { 12, 12, 12, 12 } + }}, + {{ + { min(), min(), max() - 1.0, max() }, + { min(), min(), min(), min() }, + { min(), min(), min(), max() - 1.0 }, + { max() - 1.0, max(), 113.0, 114.0 }, + { 112.0, 113.0, 114.0, 115.0 }, + { 12.0, 12.0, 12.0, 12.0 } + }} + // clang-format on + }; + return kTestElements; + } + + template , bool> = true> + const Values& get_values() { + static const Values kTestValues = { + // clang-format off + 10.67F, 11 + // clang-format on + }; + return kTestValues; + } + + template , bool> = true> + const Values& get_values() { + static const Values kTestValues = { + // clang-format off + 11, 11.0 + // clang-format on + }; + return kTestValues; + } + + template , bool> = true, + std::enable_if_t, bool> = true> + void calculate_expected(const test::Array2D& source, + test::Array2D& expected) { + for (size_t hindex = 0; hindex < source.height(); ++hindex) { + for (size_t vindex = 0; vindex < source.width(); ++vindex) { + O calculated = 0; + // NOLINTBEGIN(clang-analyzer-core.uninitialized.Assign) + I result = *source.at(hindex, vindex); + // NOLINTEND(clang-analyzer-core.uninitialized.Assign) + if (result > max()) { + calculated = max(); + } else if (result < min()) { + calculated = min(); + } else { + calculated = result; + } + *expected.at(hindex, vindex) = calculated; + } + } + } + + template , bool> = true, + std::enable_if_t, bool> = true> + void calculate_expected(const test::Array2D& source, + test::Array2D& expected) { + for (size_t hindex = 0; hindex < source.height(); ++hindex) { + for (size_t vindex = 0; vindex < source.width(); ++vindex) { + // NOLINTBEGIN(clang-analyzer-core.uninitialized.Assign) + *expected.at(hindex, vindex) = *source.at(hindex, vindex); + // NOLINTEND(clang-analyzer-core.uninitialized.Assign) + } + } + } + + template + size_t get_linear_height(size_t width, size_t minimum_size) { + size_t image_size = + std::max(minimum_size, static_cast(max() - min())); + size_t height = image_size / width + 1; + + return height; + } + + template , bool> = true, + std::enable_if_t, bool> = true> + std::tuple, test::Array2D, test::Array2D> + get_linear_arrays(size_t width, size_t height) { + test::Array2D source(width, height, 1, 1); + test::Array2D expected(width, height, 1, 1); + test::Array2D actual(width, height, 1, 1); + + test::GenerateLinearSeries generator(min()); + + source.fill(generator); + + calculate_expected(source, expected); + + return {source, expected, actual}; + } + + template , bool> = true, + std::enable_if_t, bool> = true> + std::tuple, test::Array2D, test::Array2D> + get_linear_arrays(size_t width, size_t height) { + test::Array2D source(width, height, 1, 1); + test::Array2D expected(width, height, 1, 1); + test::Array2D actual(width, height, 1, 1); + + test::GenerateLinearSeries generator(min()); + + source.fill(generator); + + calculate_expected(source, expected); + + return {source, expected, actual}; + } + + public: + // minimum_size set by caller to trigger the 'big' conversion path. + template , bool> = true, + std::enable_if_t, bool> = true> + void test_linear(size_t width, size_t minimum_size = 1) { + size_t height = get_linear_height(width, minimum_size); + + auto arrays = get_linear_arrays(width, height); + + test::Array2D& source = std::get<0>(arrays); + test::Array2D& expected = std::get<1>(arrays); + test::Array2D& actual = std::get<2>(arrays); + + ASSERT_EQ(INTRINSICCV_OK, (float_conversion()( + source.data(), source.stride(), actual.data(), + actual.stride(), width, height))); + + EXPECT_EQ_ARRAY2D(expected, actual); + } + + template , bool> = true, + std::enable_if_t, bool> = true> + void test_linear(size_t width, size_t minimum_size = 1) { + size_t height = get_linear_height(width, minimum_size); + + auto arrays = get_linear_arrays(width, height); + + test::Array2D& source = std::get<0>(arrays); + test::Array2D& expected = std::get<1>(arrays); + test::Array2D& actual = std::get<2>(arrays); + + ASSERT_EQ(INTRINSICCV_OK, (float_conversion()( + source.data(), source.stride(), actual.data(), + actual.stride(), width, height))); + + EXPECT_EQ_ARRAY2D(expected, actual); + } + + void test_custom() { + auto elements_list = get_custom_elements(); + const size_t& width = elements_list.width; + const size_t& height = elements_list.height; + + test::Array2D source(width, height); + test::Array2D expected(width, height); + test::Array2D actual(width, height); + + for (size_t i = 0; i < height; i++) { + source.set(i, 0, elements_list.source_rows[i]); + expected.set(i, 0, elements_list.expected_rows[i]); + } + + ASSERT_EQ(INTRINSICCV_OK, (float_conversion()( + source.data(), source.stride(), actual.data(), + actual.stride(), width, height))); + + EXPECT_EQ_ARRAY2D(expected, actual); + } + + void test_sizes(const size_t width, const size_t height) { + auto values_list = get_values(); + test::Array2D source(width, height, 1, 1); + + test::Array2D expected(width, height, 1, 1); + + test::Array2D actual(width, height, 1, 1); + + source.fill(values_list.source); + + expected.fill(values_list.expected); + + actual.fill(0); + + ASSERT_EQ(INTRINSICCV_OK, (float_conversion()( + source.data(), source.stride(), actual.data(), + actual.stride(), width, height))); + + EXPECT_EQ_ARRAY2D(expected, actual); + } +}; // end of class FloatConversionTest + +template +class FloatConversion : public testing::Test {}; + +using ElementTypes = + ::testing::Types, std::pair, + std::pair, std::pair>; + +// Tests intrinsiccv_float_conversion API. +TYPED_TEST_SUITE(FloatConversion, ElementTypes); + +TYPED_TEST(FloatConversion, NullPointer) { + using InputType = typename TypeParam::first_type; + using OutputType = typename TypeParam::second_type; + InputType src[1] = {}; + OutputType dst[1]; + test::test_null_args(float_conversion(), src, + sizeof(InputType), dst, sizeof(OutputType), 1, 1); +} + +TYPED_TEST(FloatConversion, OversizeImage) { + using InputType = typename TypeParam::first_type; + using OutputType = typename TypeParam::second_type; + InputType src[1] = {}; + OutputType dst[1]; + EXPECT_EQ(INTRINSICCV_ERROR_RANGE, + (float_conversion()( + src, sizeof(InputType), dst, sizeof(OutputType), + INTRINSICCV_MAX_IMAGE_PIXELS + 1, 1))); + EXPECT_EQ(INTRINSICCV_ERROR_RANGE, + (float_conversion()( + src, sizeof(InputType), dst, sizeof(OutputType), 1, + INTRINSICCV_MAX_IMAGE_PIXELS + 1))); + EXPECT_EQ( + INTRINSICCV_ERROR_RANGE, + (float_conversion()( + src, sizeof(TypeParam), dst, sizeof(OutputType), + INTRINSICCV_MAX_IMAGE_PIXELS + 1, INTRINSICCV_MAX_IMAGE_PIXELS + 1))); +} + +TYPED_TEST(FloatConversion, Scalar) { + using InputType = typename TypeParam::first_type; + using OutputType = typename TypeParam::second_type; + FloatConversionTest{} + .template test_linear( + test::Options::vector_length() - 1); +} +TYPED_TEST(FloatConversion, Vector) { + using InputType = typename TypeParam::first_type; + using OutputType = typename TypeParam::second_type; + FloatConversionTest{} + .template test_linear( + test::Options::vector_length() * 2); +} +TYPED_TEST(FloatConversion, Custom) { + using InputType = typename TypeParam::first_type; + using OutputType = typename TypeParam::second_type; + FloatConversionTest{}.test_custom(); +} +TYPED_TEST(FloatConversion, CustomFits128VectorSize) { + using InputType = typename TypeParam::first_type; + using OutputType = typename TypeParam::second_type; + FloatConversionTest{}.test_sizes(4, 1); +} +TYPED_TEST(FloatConversion, CustomFits128VectorSize2x) { + using InputType = typename TypeParam::first_type; + using OutputType = typename TypeParam::second_type; + FloatConversionTest{}.test_sizes(4, 2); +} +TYPED_TEST(FloatConversion, CustomFits128VectorSize3x) { + using InputType = typename TypeParam::first_type; + using OutputType = typename TypeParam::second_type; + FloatConversionTest{}.test_sizes(4, 3); +} +TYPED_TEST(FloatConversion, CustomFits512VectorSize) { + using InputType = typename TypeParam::first_type; + using OutputType = typename TypeParam::second_type; + FloatConversionTest{}.test_sizes(4, 4); +} +TYPED_TEST(FloatConversion, CustomFits512VectorSize2x) { + using InputType = typename TypeParam::first_type; + using OutputType = typename TypeParam::second_type; + FloatConversionTest{}.test_sizes(4, 8); +} +TYPED_TEST(FloatConversion, CustomFits512VectorSize3x) { + using InputType = typename TypeParam::first_type; + using OutputType = typename TypeParam::second_type; + FloatConversionTest{}.test_sizes(6, 8); +} +TYPED_TEST(FloatConversion, Custom128OneRemaining) { + using InputType = typename TypeParam::first_type; + using OutputType = typename TypeParam::second_type; + FloatConversionTest{}.test_sizes(1, 17); +} +TYPED_TEST(FloatConversion, Custom128AllButOneRemaining) { + using InputType = typename TypeParam::first_type; + using OutputType = typename TypeParam::second_type; + FloatConversionTest{}.test_sizes(5, 3); +} +TYPED_TEST(FloatConversion, CustomAboutHalfRemaining) { + using InputType = typename TypeParam::first_type; + using OutputType = typename TypeParam::second_type; + FloatConversionTest{}.test_sizes(19, 2); +} +TYPED_TEST(FloatConversion, CustomEmpty) { + using InputType = typename TypeParam::first_type; + using OutputType = typename TypeParam::second_type; + FloatConversionTest{}.test_sizes(0, 0); +} +TYPED_TEST(FloatConversion, CustomOne) { + using InputType = typename TypeParam::first_type; + using OutputType = typename TypeParam::second_type; + FloatConversionTest{}.test_sizes(1, 1); +} diff --git a/test/api/test_resize_linear.cpp b/test/api/test_resize_linear.cpp index 86902d00ddb00c87e90d937cf7a5e64a7313899c..a132474aec28a658db84c5df6f3309615c464f77 100644 --- a/test/api/test_resize_linear.cpp +++ b/test/api/test_resize_linear.cpp @@ -243,7 +243,7 @@ static void do_large_dimensions_test(size_t x_scale, size_t y_scale) { src.resize(src_stride * src_height); dst.resize(dst_stride * dst_height); expected_data.resize(dst_stride * dst_height); - std::mt19937 generator{static_cast(test::Options::seed())}; + std::mt19937 generator(test::Options::seed()); std::generate(src.begin(), src.end(), generator); resize_linear_unaccelerated_u8(src.data(), src_stride, src_width, src_height, expected_data.data(), dst_stride, dst_width, diff --git a/test/framework/array.h b/test/framework/array.h index f854c98191ef4a40bb93c7444d990a6de0de074c..7f010cac6a73ba7df60b026b82674bb2622ee1c4 100644 --- a/test/framework/array.h +++ b/test/framework/array.h @@ -141,6 +141,22 @@ class Array2D : public TwoDimensional { } } + // Sets values in a row starting at a given column from a const vector. + void set(size_t row, size_t column, const std::vector &values) { + ASSERT_EQ(valid(), true) << "Array is invalid."; + ASSERT_GE(width() - column, values.size()); + + ElementType *ptr = at(row, column); + if (!ptr) { + return; + } + + size_t index = 0; + for (ElementType value : values) { + ptr[index++] = value; + } + } + // Sets values starting in a given row starting at a given column. // // The layout of the input TwoDimensional object is not altered, meaning that diff --git a/test/framework/generator.h b/test/framework/generator.h index 23126630f97f016b5f5551c62b02704444263c1f..c8321ad787dad32e74a3d856a49f89f062909bda 100644 --- a/test/framework/generator.h +++ b/test/framework/generator.h @@ -12,6 +12,18 @@ namespace test { +template +class GenerateLinearSeries : public Generator { + public: + explicit GenerateLinearSeries(ElementType start_from) + : counter_{start_from} {} + + std::optional next() override { return counter_++; } + + private: + ElementType counter_; +}; // end of class GenerateLinearSeries + // Generates pseudo-random numbers of a given type. template class PseudoRandomNumberGenerator : public Generator { diff --git a/test/framework/utils.h b/test/framework/utils.h index 35e41e62ed92044d9d3694508472b1c8e8f6eeba..83943d0d05cf1c12a8473e7fd7d3cd77f29e6eb5 100644 --- a/test/framework/utils.h +++ b/test/framework/utils.h @@ -24,6 +24,14 @@ return impl; \ } +#define INTRINSICCV_DIFF_IO_API(name, impl, itype, otype) \ + template , bool> = true, \ + std::enable_if_t, bool> = true> \ + static decltype(auto) name() { \ + return impl; \ + } + // Generates a fatal failure with a generic message, and returns with a given // value. #define TEST_FAIL_WITH(return_value, message) \ diff --git a/test/test_config.h.in b/test/test_config.h.in index 8b91f4d501fa132f0995782e242c5ba9d381af0b..6cc98c850389a4ad99c9ffd24f8fdcb4b55e94d2 100644 --- a/test/test_config.h.in +++ b/test/test_config.h.in @@ -9,4 +9,15 @@ #cmakedefine INTRINSICCV_ALLOCATION_TESTS +// GCC and clang +#ifdef __GNUC__ +#define INTRINSICCV_NO_STRICT_ALIASING_BEGIN \ + _Pragma("GCC diagnostic push") \ + _Pragma("GCC diagnostic ignored \"-Wstrict-aliasing\"") +#define INTRINSICCV_NO_STRICT_ALIASING_END _Pragma("GCC diagnostic pop") +#else +#define INTRINSICCV_NO_STRICT_ALIASING_BEGIN +#define INTRINSICCV_NO_STRICT_ALIASING_END +#endif + #endif // INTRINSICCV_TEST_CONFIG_H