From 5cbb92f8ededd521c7a4bafbda0439ef5e1f9d98 Mon Sep 17 00:00:00 2001 From: Michael Platings Date: Wed, 10 Jul 2024 16:01:39 +0000 Subject: [PATCH 1/3] Automatically find conformity test sources --- conformity/opencv/CMakeLists.txt | 23 ++++------------------- 1 file changed, 4 insertions(+), 19 deletions(-) diff --git a/conformity/opencv/CMakeLists.txt b/conformity/opencv/CMakeLists.txt index 6c53bcaea..b6f9666a8 100644 --- a/conformity/opencv/CMakeLists.txt +++ b/conformity/opencv/CMakeLists.txt @@ -25,20 +25,14 @@ FetchContent_Declare( FetchContent_MakeAvailable(OpenCV) +file(GLOB conformity_test_sources CONFIGURE_DEPENDS "test_*.cpp") + # Manager add_executable( manager manager.cpp tests.cpp - test_binary_op.cpp - test_gaussian_blur.cpp - test_rgb2yuv.cpp - test_sobel.cpp - test_exp.cpp - test_float_conv.cpp - test_scale.cpp - test_min_max.cpp - test_resize.cpp + ${conformity_test_sources} ) target_link_libraries( @@ -73,16 +67,7 @@ add_executable( subordinate subordinate.cpp tests.cpp - test_binary_op.cpp - test_gaussian_blur.cpp - test_min_max.cpp - test_rgb2yuv.cpp - test_sobel.cpp - test_exp.cpp - test_float_conv.cpp - test_scale.cpp - test_min_max.cpp - test_resize.cpp + ${conformity_test_sources} ) target_link_libraries( -- GitLab From a5c8d5745bf05906cb8b605a2fe6b38ec59d9d2a Mon Sep 17 00:00:00 2001 From: Michael Platings Date: Wed, 10 Jul 2024 16:02:49 +0000 Subject: [PATCH 2/3] Allow using C++ headers like --- scripts/cpplint.sh | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/scripts/cpplint.sh b/scripts/cpplint.sh index 5bbd727e7..dc36ca41f 100755 --- a/scripts/cpplint.sh +++ b/scripts/cpplint.sh @@ -15,6 +15,8 @@ set -x # Run cpplint. # # Disabled categories rationale: +# * build/c++11 - cpplint's list of unapproved headers (like ) doesn't +# make much sense outside the projects it was originally designed for. # * build/header_guard - our header guards are not coupled to the directory # structure because they're designed to be used from other projects. # * build/include_subdir - it's OK to include a header in the same directory @@ -32,5 +34,5 @@ cpplint \ --recursive \ --exclude=build \ --counting=detailed \ - --filter=-build/header_guard,-build/include_subdir,-readability/todo,-runtime/references,-whitespace/indent,-whitespace/line_length \ + --filter=-build/c++11,-build/header_guard,-build/include_subdir,-readability/todo,-runtime/references,-whitespace/indent,-whitespace/line_length \ . -- GitLab From 4d6afe084241256ab4c457312ada7564f1ee60e8 Mon Sep 17 00:00:00 2001 From: Michael Platings Date: Wed, 10 Jul 2024 16:35:20 +0000 Subject: [PATCH 3/3] Multithread YUV to RGB conversion Multithreading is only enabled in the OpenCV HAL. The multithreaded API is likely to change and therefore is not supported for direct use. --- CHANGELOG.md | 1 + CMakeLists.txt | 3 +- adapters/opencv/CMakeLists.txt | 13 +++- adapters/opencv/kleidicv_hal.cpp | 28 +++++++- conformity/opencv/test_cvtcolor.cpp | 72 +++++++++++++++++++ conformity/opencv/test_cvtcolor.h | 14 ++++ conformity/opencv/tests.cpp | 2 + kleidicv_thread/CMakeLists.txt | 41 +++++++++++ .../include/kleidicv_thread/kleidicv_thread.h | 62 ++++++++++++++++ kleidicv_thread/src/kleidicv_thread.cpp | 48 +++++++++++++ scripts/format.sh | 1 + test/api/CMakeLists.txt | 6 +- test/api/multithreading_std_thread.cpp | 56 +++++++++++++++ test/api/multithreading_std_thread.h | 14 ++++ test/api/test_thread_yuv_to_rgb.cpp | 51 +++++++++++++ 15 files changed, 404 insertions(+), 8 deletions(-) create mode 100644 conformity/opencv/test_cvtcolor.cpp create mode 100644 conformity/opencv/test_cvtcolor.h create mode 100644 kleidicv_thread/CMakeLists.txt create mode 100644 kleidicv_thread/include/kleidicv_thread/kleidicv_thread.h create mode 100644 kleidicv_thread/src/kleidicv_thread.cpp create mode 100644 test/api/multithreading_std_thread.cpp create mode 100644 test/api/multithreading_std_thread.h create mode 100644 test/api/test_thread_yuv_to_rgb.cpp diff --git a/CHANGELOG.md b/CHANGELOG.md index 47e361235..6b38c14db 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -35,6 +35,7 @@ This changelog aims to follow the guiding principles of ### Changed - Filter context creation API specification. - Gaussian Blur API specification. +- In the OpenCV HAL, cvtColor YUV2RGB_NV21 is multithreaded. ### Removed diff --git a/CMakeLists.txt b/CMakeLists.txt index d885bd65f..01208d679 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2023 Arm Limited and/or its affiliates +# SPDX-FileCopyrightText: 2023 - 2024 Arm Limited and/or its affiliates # # SPDX-License-Identifier: Apache-2.0 @@ -7,5 +7,6 @@ cmake_minimum_required(VERSION 3.16) project("KleidiCV" CXX) add_subdirectory(kleidicv) +add_subdirectory(kleidicv_thread) add_subdirectory(test) add_subdirectory(benchmark) diff --git a/adapters/opencv/CMakeLists.txt b/adapters/opencv/CMakeLists.txt index 1317b966d..0a7d24ea0 100644 --- a/adapters/opencv/CMakeLists.txt +++ b/adapters/opencv/CMakeLists.txt @@ -3,18 +3,19 @@ # SPDX-License-Identifier: Apache-2.0 include("${CMAKE_CURRENT_LIST_DIR}/../../kleidicv/CMakeLists.txt") +include("${CMAKE_CURRENT_LIST_DIR}/../../kleidicv_thread/CMakeLists.txt") project("KleidiCV OpenCV HAL") set(KLEIDICV_HAL_VERSION "0.2.0" CACHE INTERNAL "") set(KLEIDICV_HAL_LIBRARIES "kleidicv_hal" CACHE INTERNAL "") set(KLEIDICV_HAL_HEADERS "${CMAKE_CURRENT_LIST_DIR}/kleidicv_hal.h" CACHE INTERNAL "") -set(KLEIDICV_HAL_INCLUDE_DIRS "$" CACHE INTERNAL "") +set(KLEIDICV_HAL_INCLUDE_DIRS "$" CACHE INTERNAL "") add_library(kleidicv_hal STATIC "${CMAKE_CURRENT_LIST_DIR}/kleidicv_hal.cpp") -target_link_libraries(kleidicv_hal PUBLIC kleidicv) +target_link_libraries(kleidicv_hal PUBLIC kleidicv kleidicv_thread) target_include_directories(kleidicv_hal PRIVATE - $ + ${KLEIDICV_HAL_INCLUDE_DIRS} ${CMAKE_CURRENT_LIST_DIR} ${OpenCV_SOURCE_DIR}/modules/core/include ${OpenCV_SOURCE_DIR}/modules/imgproc/include @@ -57,6 +58,12 @@ if(NOT BUILD_SHARED_LIBS) DESTINATION ${OPENCV_3P_LIB_INSTALL_PATH} COMPONENT dev ) + ocv_install_target(kleidicv_thread + EXPORT OpenCVModules + ARCHIVE + DESTINATION ${OPENCV_3P_LIB_INSTALL_PATH} + COMPONENT dev + ) ocv_install_target(kleidicv_hal EXPORT OpenCVModules ARCHIVE diff --git a/adapters/opencv/kleidicv_hal.cpp b/adapters/opencv/kleidicv_hal.cpp index 21e04c9ac..899589b34 100644 --- a/adapters/opencv/kleidicv_hal.cpp +++ b/adapters/opencv/kleidicv_hal.cpp @@ -4,6 +4,8 @@ #include "kleidicv_hal.h" +#include +#include #include #include #include @@ -11,8 +13,11 @@ #include #include "kleidicv/kleidicv.h" +#include "kleidicv_thread/kleidicv_thread.h" #include "opencv2/core/base.hpp" #include "opencv2/core/hal/interface.h" +#include "opencv2/core/types.hpp" +#include "opencv2/core/utility.hpp" #include "opencv2/imgproc/hal/interface.h" namespace kleidicv::hal { @@ -48,6 +53,25 @@ static size_t get_type_size(int depth) { } } +static kleidicv_error_t parallel(kleidicv_thread_callback callback, + void *callback_data, void * /*parallel_data*/, + unsigned task_count) { + std::atomic shared_result{KLEIDICV_OK}; + + auto invoke_callback = [&](const cv::Range &range) { + kleidicv_error_t result = callback(range.start, range.end, callback_data); + if (result != KLEIDICV_OK) { + shared_result.store(result); + } + }; + cv::parallel_for_(cv::Range(0, task_count), invoke_callback); + return shared_result; +} + +static kleidicv_thread_multithreading get_multithreading() { + return kleidicv_thread_multithreading{parallel, nullptr}; +} + // Note: 'dcn' is already accounted for in 'dst_step'. int gray_to_bgr(const uchar *src_data, size_t src_step, uchar *dst_data, size_t dst_step, int width, int height, int depth, int dcn) { @@ -128,11 +152,11 @@ int yuv_to_bgr_ex(const uchar *y_data, size_t y_step, const uchar *uv_data, reinterpret_cast(dst_data), dst_step, dst_width, dst_height, is_nv21)); } - return convert_error(kleidicv_yuv_sp_to_rgb_u8( + return convert_error(kleidicv_thread_yuv_sp_to_rgb_u8( reinterpret_cast(y_data), y_step, reinterpret_cast(uv_data), uv_step, reinterpret_cast(dst_data), dst_step, dst_width, dst_height, - is_nv21)); + is_nv21, get_multithreading())); } if (dcn == 4) { diff --git a/conformity/opencv/test_cvtcolor.cpp b/conformity/opencv/test_cvtcolor.cpp new file mode 100644 index 000000000..796aac7b4 --- /dev/null +++ b/conformity/opencv/test_cvtcolor.cpp @@ -0,0 +1,72 @@ +// SPDX-FileCopyrightText: 2024 Arm Limited and/or its affiliates +// +// SPDX-License-Identifier: Apache-2.0 + +#include "test_cvtcolor.h" + +template +static cv::Mat exec_cvtcolor(cv::Mat& input) { + cv::Mat result; + cv::cvtColor(input, result, Code); + return result; +} + +#if MANAGER +template +bool test_cvtcolor(int index, RecreatedMessageQueue& request_queue, + RecreatedMessageQueue& reply_queue) { + cv::RNG rng(0); + + auto check = [&](size_t x, size_t y) -> bool { + // Multiply height by 1.5 to fit Y & UV planes. + cv::Mat input(y * 3 / 2, x, CV_8UC1); + rng.fill(input, cv::RNG::UNIFORM, 0, 255); + + cv::Mat actual = exec_cvtcolor(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; + }; + + // OpenCV only accepts two-plane images with an even number of columns & rows. + for (size_t x = 4; x <= 16; x += 2) { + for (size_t y = 2; y <= 16; y += 2) { + if (check(x, y)) { + return true; + } + } + } + + // Check taller images - this number of rows was necessary to trigger a bug on + // a machine with 64 cores. + if (check(2, 1000)) { + return true; + } + + return false; +} +#endif + +#define CVTCOLOR_TEST(code) \ + TEST(#code, test_cvtcolor, exec_cvtcolor) + +std::vector& cvtcolor_tests_get() { + // clang-format off + static std::vector tests = { + CVTCOLOR_TEST(YUV2BGR_NV12), + CVTCOLOR_TEST(YUV2RGB_NV12), + CVTCOLOR_TEST(YUV2BGRA_NV12), + CVTCOLOR_TEST(YUV2RGBA_NV12), + CVTCOLOR_TEST(YUV2BGR_NV21), + CVTCOLOR_TEST(YUV2RGB_NV21), + CVTCOLOR_TEST(YUV2BGRA_NV21), + CVTCOLOR_TEST(YUV2RGBA_NV21), + }; + // clang-format on + return tests; +} diff --git a/conformity/opencv/test_cvtcolor.h b/conformity/opencv/test_cvtcolor.h new file mode 100644 index 000000000..6f48ede21 --- /dev/null +++ b/conformity/opencv/test_cvtcolor.h @@ -0,0 +1,14 @@ +// SPDX-FileCopyrightText: 2024 Arm Limited and/or its affiliates +// +// SPDX-License-Identifier: Apache-2.0 + +#ifndef KLEIDICV_OPENCV_CONFORMITY_TEST_CVTCOLOR_H_ +#define KLEIDICV_OPENCV_CONFORMITY_TEST_CVTCOLOR_H_ + +#include + +#include "tests.h" + +std::vector& cvtcolor_tests_get(); + +#endif // KLEIDICV_OPENCV_CONFORMITY_TEST_CVTCOLOR_H_ diff --git a/conformity/opencv/tests.cpp b/conformity/opencv/tests.cpp index 4924120ff..e1b20d322 100644 --- a/conformity/opencv/tests.cpp +++ b/conformity/opencv/tests.cpp @@ -11,6 +11,7 @@ #include "opencv2/core.hpp" #include "opencv2/imgproc.hpp" #include "test_binary_op.h" +#include "test_cvtcolor.h" #include "test_exp.h" #include "test_float_conv.h" #include "test_gaussian_blur.h" @@ -32,6 +33,7 @@ static std::vector merge_tests( std::vector all_tests = merge_tests({ binary_op_tests_get, + cvtcolor_tests_get, gaussian_blur_tests_get, rgb2yuv_tests_get, sobel_tests_get, diff --git a/kleidicv_thread/CMakeLists.txt b/kleidicv_thread/CMakeLists.txt new file mode 100644 index 000000000..73a4b1c7e --- /dev/null +++ b/kleidicv_thread/CMakeLists.txt @@ -0,0 +1,41 @@ +# SPDX-FileCopyrightText: 2024 Arm Limited and/or its affiliates +# +# SPDX-License-Identifier: Apache-2.0 + +cmake_minimum_required(VERSION 3.16) + +project("KleidiCV thread") + +file(GLOB KLEIDICV_THREAD_SOURCES + "${CMAKE_CURRENT_LIST_DIR}/src/*.cpp" +) + +set(KLEIDICV_THREAD_CXX_FLAGS + "-fno-exceptions" + "-fno-rtti" + "-Wall" + "-Wextra" + "-Wold-style-cast" + "-Wno-shadow" # GCC's shadow declaration check is too sensitive for the library +) + +if (KLEIDICV_CHECK_BANNED_FUNCTIONS) + # The `SHELL:` prefix is used to turn off de-duplication of compiler flags, + # it is necessary if other headers need to be force included. + # https://cmake.org/cmake/help/latest/command/target_compile_options.html#option-de-duplication + list(APPEND KLEIDICV_THREAD_CXX_FLAGS "SHELL:-include kleidicv/unsafe.h") +endif() + +if (CMAKE_BUILD_TYPE STREQUAL "Debug") + list(APPEND KLEIDICV_THREAD_CXX_FLAGS "-O0" "-g") +else() + list(APPEND KLEIDICV_THREAD_CXX_FLAGS "-O2" "-g0") +endif() + +add_library(kleidicv_thread STATIC ${KLEIDICV_THREAD_SOURCES}) +target_include_directories(kleidicv_thread + PRIVATE "${CMAKE_CURRENT_LIST_DIR}/include" $ +) +set_target_properties(kleidicv_thread PROPERTIES CXX_STANDARD 17) +target_compile_options(kleidicv_thread PRIVATE ${KLEIDICV_THREAD_CXX_FLAGS}) +target_link_libraries(kleidicv_thread PRIVATE kleidicv) diff --git a/kleidicv_thread/include/kleidicv_thread/kleidicv_thread.h b/kleidicv_thread/include/kleidicv_thread/kleidicv_thread.h new file mode 100644 index 000000000..b57fe5092 --- /dev/null +++ b/kleidicv_thread/include/kleidicv_thread/kleidicv_thread.h @@ -0,0 +1,62 @@ +// SPDX-FileCopyrightText: 2024 Arm Limited and/or its affiliates +// +// SPDX-License-Identifier: Apache-2.0 + +#ifndef KLEIDICV_THREAD_H +#define KLEIDICV_THREAD_H + +#include "kleidicv/kleidicv.h" + +#ifdef __cplusplus +extern "C" { +#endif // __cplusplus + +/// Internal - not part of the public API and its direct use is not supported. +/// +/// Signature of a function to be invoked for each thread. +/// @param begin index of first task. +/// @param end index of the last task + 1. +/// @param data opaque pointer to data internal to the callback implementation. +typedef kleidicv_error_t (*kleidicv_thread_callback)(unsigned begin, + unsigned end, void *data); + +/// Internal - not part of the public API and its direct use is not supported. +/// +/// Signature of a function to invoke callbacks in parallel. +/// +/// KleidiCV has no dependencies on any particular threading library. +/// Therefore, to allow multithreading to work an implementation of this +/// function must be provided to invoke callbacks across multiple threads via +/// the preferred threading library or framework. +/// +/// @param callback the function to invoke in parallel. +/// @param callback_data opaque pointer to pass as the callback's data argument. +/// @param parallel_data pointer from kleidicv_thread_multithreading_t, for use +/// by the implementation of this function. +/// @param task_count number of tasks to run. +typedef kleidicv_error_t (*kleidicv_thread_parallel)( + kleidicv_thread_callback callback, void *callback_data, void *parallel_data, + unsigned task_count); + +/// Internal - not part of the public API and its direct use is not supported. +/// +/// Encapsulates the data required to run callbacks across multiple threads. +typedef struct { + kleidicv_thread_parallel parallel; + void *parallel_data; +} kleidicv_thread_multithreading; + +/// Internal - not part of the public API and its direct use is not supported. +/// +/// Multithreaded implementation of kleidicv_yuv_sp_to_rgb_u8 - see the +/// documentation of that function for more details. +kleidicv_error_t kleidicv_thread_yuv_sp_to_rgb_u8( + const uint8_t *src_y, size_t src_y_stride, const uint8_t *src_uv, + size_t src_uv_stride, uint8_t *dst, size_t dst_stride, size_t width, + size_t height, bool is_nv21, kleidicv_thread_multithreading); + +#ifdef __cplusplus +} // extern "C" +#endif // __cplusplus + +#endif // KLEIDICV_THREAD_H diff --git a/kleidicv_thread/src/kleidicv_thread.cpp b/kleidicv_thread/src/kleidicv_thread.cpp new file mode 100644 index 000000000..bb1d66497 --- /dev/null +++ b/kleidicv_thread/src/kleidicv_thread.cpp @@ -0,0 +1,48 @@ +// SPDX-FileCopyrightText: 2024 Arm Limited and/or its affiliates +// +// SPDX-License-Identifier: Apache-2.0 + +#include "kleidicv_thread/kleidicv_thread.h" + +#include + +#include "kleidicv/kleidicv.h" + +struct kleidicv_thread_yuv_sp_to_rgb_u8_data { + const uint8_t *src_y; + size_t src_y_stride; + const uint8_t *src_uv; + size_t src_uv_stride; + uint8_t *dst; + size_t dst_stride; + size_t width; + size_t height; + bool is_nv21; +}; + +static kleidicv_error_t kleidicv_thread_yuv_sp_to_rgb_u8_callback( + unsigned task_begin, unsigned task_end, void *void_data) { + auto *data = + reinterpret_cast(void_data); + + size_t row_begin = size_t{task_begin} * 2; + size_t row_end = std::min(data->height, size_t{task_end} * 2); + size_t row_uv = task_begin; + + return kleidicv_yuv_sp_to_rgb_u8( + data->src_y + row_begin * data->src_y_stride, data->src_y_stride, + data->src_uv + row_uv * data->src_uv_stride, data->src_uv_stride, + data->dst + row_begin * data->dst_stride, data->dst_stride, data->width, + row_end - row_begin, data->is_nv21); +} + +kleidicv_error_t kleidicv_thread_yuv_sp_to_rgb_u8( + const uint8_t *src_y, size_t src_y_stride, const uint8_t *src_uv, + size_t src_uv_stride, uint8_t *dst, size_t dst_stride, size_t width, + size_t height, bool is_nv21, kleidicv_thread_multithreading mt) { + kleidicv_thread_yuv_sp_to_rgb_u8_data callback_data = { + src_y, src_y_stride, src_uv, src_uv_stride, dst, + dst_stride, width, height, is_nv21}; + return mt.parallel(kleidicv_thread_yuv_sp_to_rgb_u8_callback, &callback_data, + mt.parallel_data, (height + 1) / 2); +} diff --git a/scripts/format.sh b/scripts/format.sh index fb76d66ad..9e19c150c 100755 --- a/scripts/format.sh +++ b/scripts/format.sh @@ -32,6 +32,7 @@ SOURCES="$(find \ "${KLEIDICV_ROOT_PATH}/adapters" \ "${KLEIDICV_ROOT_PATH}/benchmark" \ "${KLEIDICV_ROOT_PATH}/kleidicv" \ + "${KLEIDICV_ROOT_PATH}/kleidicv_thread" \ "${KLEIDICV_ROOT_PATH}/test" \ "${KLEIDICV_ROOT_PATH}/conformity/opencv" \ \( -name \*.cpp -o -name \*.h -o -name \*.h.in \) \ diff --git a/test/api/CMakeLists.txt b/test/api/CMakeLists.txt index f3f35df76..aa30d3afd 100644 --- a/test/api/CMakeLists.txt +++ b/test/api/CMakeLists.txt @@ -2,7 +2,7 @@ # # SPDX-License-Identifier: Apache-2.0 -file(GLOB kleidicv_api_test_sources CONFIGURE_DEPENDS "*.h" "test_*.cpp") +file(GLOB kleidicv_api_test_sources CONFIGURE_DEPENDS "*.h" "*.cpp") list(APPEND kleidicv_api_test_sources ${KLEIDICV_TEST_FRAMEWORK_SOURCES}) @@ -25,6 +25,7 @@ target_include_directories( kleidicv-api-test PRIVATE ${KLEIDICV_INCLUDE_DIR} PRIVATE ${KLEIDICV_TEST_INCLUDE_DIR} + ${CMAKE_CURRENT_SOURCE_DIR}/../../kleidicv_thread/include ) if (KLEIDICV_ALLOCATION_TESTS) @@ -38,6 +39,7 @@ endif() target_link_libraries( kleidicv-api-test kleidicv - gtest_main + kleidicv_thread + gtest gmock ) diff --git a/test/api/multithreading_std_thread.cpp b/test/api/multithreading_std_thread.cpp new file mode 100644 index 000000000..e202be3a9 --- /dev/null +++ b/test/api/multithreading_std_thread.cpp @@ -0,0 +1,56 @@ +// SPDX-FileCopyrightText: 2024 Arm Limited and/or its affiliates +// +// SPDX-License-Identifier: Apache-2.0 + +#include "multithreading_std_thread.h" + +#include +#include +#include + +#include "kleidicv/kleidicv.h" +#include "kleidicv_thread/kleidicv_thread.h" + +/// An implementation of kleidicv_thread_parallel based on std::thread. +static kleidicv_error_t parallel_std_thread(kleidicv_thread_callback callback, + void *callback_data, + void *parallel_data, + unsigned task_count) { + const unsigned max_thread_count = + static_cast(reinterpret_cast(parallel_data)); + const unsigned thread_count = std::min(task_count, max_thread_count); + + std::vector results(thread_count, + KLEIDICV_ERROR_CONTEXT_MISMATCH); + + std::vector threads(thread_count); + for (unsigned i = 0; i < thread_count; ++i) { + unsigned task_begin = i * task_count / thread_count; + unsigned task_end = i + 1 == thread_count + ? task_count + : (i + 1) * task_count / thread_count; + + threads[i] = std::thread([=, &results]() { + results[i] = callback(task_begin, task_end, callback_data); + }); + } + + for (auto &t : threads) { + t.join(); + } + + for (auto r : results) { + if (r != KLEIDICV_OK) { + return r; + } + } + return KLEIDICV_OK; +} + +kleidicv_thread_multithreading get_multithreading_std_thread( + uintptr_t thread_count) { + // NOLINTBEGIN(performance-no-int-to-ptr) + void *parallel_data = reinterpret_cast(thread_count); + // NOLINTEND(performance-no-int-to-ptr) + return kleidicv_thread_multithreading{parallel_std_thread, parallel_data}; +} diff --git a/test/api/multithreading_std_thread.h b/test/api/multithreading_std_thread.h new file mode 100644 index 000000000..3ca96df8c --- /dev/null +++ b/test/api/multithreading_std_thread.h @@ -0,0 +1,14 @@ +// SPDX-FileCopyrightText: 2024 Arm Limited and/or its affiliates +// +// SPDX-License-Identifier: Apache-2.0 + +#ifndef KLEIDICV_THREAD_MULTITHREADING_STD_THREAD_H +#define KLEIDICV_THREAD_MULTITHREADING_STD_THREAD_H + +#include "kleidicv_thread/kleidicv_thread.h" + +/// Get a multithreading implementation based on std::thread. +kleidicv_thread_multithreading get_multithreading_std_thread( + uintptr_t thread_count); + +#endif // KLEIDICV_THREAD_MULTITHREADING_STD_THREAD_H diff --git a/test/api/test_thread_yuv_to_rgb.cpp b/test/api/test_thread_yuv_to_rgb.cpp new file mode 100644 index 000000000..bc45abbd9 --- /dev/null +++ b/test/api/test_thread_yuv_to_rgb.cpp @@ -0,0 +1,51 @@ +// SPDX-FileCopyrightText: 2024 Arm Limited and/or its affiliates +// +// SPDX-License-Identifier: Apache-2.0 + +#include +#include + +#include + +#include "framework/array.h" +#include "framework/generator.h" +#include "kleidicv/kleidicv.h" +#include "kleidicv_thread/kleidicv_thread.h" +#include "multithreading_std_thread.h" + +// Tuple of width, height, thread count. +typedef std::tuple P; + +class YUVtoRGB : public testing::TestWithParam

{}; + +TEST_P(YUVtoRGB, YUVtoRGB) { + unsigned width = 0, height = 0, thread_count = 0; + std::tie(width, height, thread_count) = GetParam(); + test::Array2D src_y(width, height), + src_uv((width + 1) & ~1, (height + 1) / 2), + dst_single(size_t{width} * 3, height), + dst_multi(size_t{width} * 3, height); + + test::PseudoRandomNumberGenerator generator; + src_y.fill(generator); + src_uv.fill(generator); + + kleidicv_error_t single_result = kleidicv_yuv_sp_to_rgb_u8( + src_y.data(), src_y.stride(), src_uv.data(), src_uv.stride(), + dst_single.data(), dst_single.stride(), width, height, false); + + kleidicv_error_t multi_result = kleidicv_thread_yuv_sp_to_rgb_u8( + src_y.data(), src_y.stride(), src_uv.data(), src_uv.stride(), + dst_multi.data(), dst_multi.stride(), width, height, false, + get_multithreading_std_thread(thread_count)); + + EXPECT_EQ(KLEIDICV_OK, single_result); + EXPECT_EQ(KLEIDICV_OK, multi_result); + EXPECT_EQ_ARRAY2D(dst_multi, dst_single); +} + +INSTANTIATE_TEST_SUITE_P(YUVtoRGB, YUVtoRGB, + testing::Values(P{1, 1, 1}, P{1, 2, 1}, P{1, 2, 2}, + P{2, 1, 2}, P{2, 2, 1}, P{1, 3, 2}, + P{2, 3, 1}, P{6, 4, 1}, P{4, 5, 2}, + P{2, 6, 3}, P{1, 7, 4}, P{12, 34, 5})); -- GitLab